{"id":294,"date":"2007-10-05T23:47:49","date_gmt":"2007-10-06T06:47:49","guid":{"rendered":"http:\/\/strawhousepig.net\/wp\/?p=294"},"modified":"2007-10-05T23:47:49","modified_gmt":"2007-10-06T06:47:49","slug":"itunes-library-to-mysql-database","status":"publish","type":"post","link":"https:\/\/strawhousepig.net\/wordpress\/2007\/10\/05\/itunes-library-to-mysql-database\/","title":{"rendered":"iTunes Library to MySQL database"},"content":{"rendered":"<p>A PHP XML parser that will populate a MySQL table using an exported iTunes library file as the source.<br \/>\n<!--more--><br \/>\nFirst, create a table:<br \/>\n<code>CREATE TABLE `table` (<br \/>\n  `track_id` mediumint(9) DEFAULT '1',<br \/>\n  `name` varchar(255) DEFAULT NULL,<br \/>\n  `album` varchar(255) DEFAULT NULL,<br \/>\n  `artist` varchar(255) DEFAULT NULL,<br \/>\n  `genre` varchar(255) DEFAULT NULL,<br \/>\n  `year` smallint(4) unsigned DEFAULT NULL,<br \/>\n  `rating` smallint(4) unsigned DEFAULT NULL,<br \/>\n  `track_number` smallint(4) unsigned DEFAULT NULL,<br \/>\n  `track_count` smallint(4) unsigned DEFAULT NULL,<br \/>\n  `play_count` mediumint(6) unsigned DEFAULT NULL,<br \/>\n  `date_added` datetime DEFAULT NULL,<br \/>\n  UNIQUE KEY `track_id` (`track_id`)<br \/>\n) ENGINE=MyISAM DEFAULT CHARSET=utf8;<\/code><br \/>\nYou can of course roll your own, but you&#8217;ll need to add any other columns you would like to have. Also, you <em>must<\/em> mimic the <KEY> label from the iTunes library xml file, using only lower case characters and replacing space with underscore. The columns you use in the MySQL table will have to be added to the <strong>$to_print<\/strong> array in the PHP script near the top.<\/p>\n<p>Now for the PHP:<br \/>\n<code><?php\n# http:\/\/strawhousepig.net\/\n# Modified from: http:\/\/developer.apple.com\/internet\/opensource\/php.html\n# Very useful character wrangling and data parsing contributed by 'lgal'.\n\n# Exported iTunes library file\n$file=\"iTunes Music Library.xml\";\n\n# Array of columns available in MySQL table\n$to_print = array(\"Name\", \"Artist\", \"Album\", \"Track ID\", \"Year\", \"Play Count\", \"Track Number\", \"Track Count\", \"Genre\", \"Rating\", \"Date Added\");\n\n# MySQL info\n$db_host = \"host\";\n$db_name = \"database\";\n$db_table = \"table\";\n$db_username = \"user\";\n$db_password = \"password\";\n\n# Array of characters xmlparser may treat as markup delimiters (might be more)\n$placeholders = array('&#38;','&#038;#60','&#038;#62','&#038;#39');\t\t\t\/\/ the encoded '&#038;', '<', '>', '''<br \/>\n$replacevalue = array('=amp1=','=amp2=','=amp3=','=amp4=');\t\t\/\/ the replacement<br \/>\n$finalvalueforitdb = array('&','<','>','\\'');\t\t\t\t\t\/\/ the character to insert into table.<\/p>\n<p>############## END USER AREA ################<\/p>\n<p>function db_connect() {<br \/>\n\tglobal $db_host, $db_name, $db_table, $db_username, $db_password;<br \/>\n\tmysql_connect($db_host, $db_username, $db_password) or die (\"Cannot connect to mySQL server\");<br \/>\n\tmysql_select_db($db_name) or die (\"Cannot connect to mySQL database\");<br \/>\n}<\/p>\n<p>\/\/change 'to_print' to match MySQL columns<br \/>\nfunction alter_print_arr(&$input, $key) {<br \/>\n\t$input = str_replace(' ','_',strtolower($input));<br \/>\n}<br \/>\narray_walk($to_print,'alter_print_arr');<\/p>\n<p>function array_to_table($array) {<br \/>\n\t\/\/expects multi-dimensional array, all with the same keys<br \/>\n\tglobal $db_table, $to_print, $replacevalue, $finalvalueforitdb;<br \/>\n\tdb_connect();<br \/>\n\t$preflight = mysql_query(\"SELECT * FROM $db_table\");<br \/>\n\tif (mysql_num_rows($preflight) > 0) {<br \/>\n\t\tmysql_query(\"DELETE FROM $db_table\") or die (\"Could not remove old records.\");<br \/>\n\t\tmysql_query(\"OPTIMIZE TABLE $db_table\");<br \/>\n\t}<br \/>\n\tforeach($array as $elem_key=>$element) {<br \/>\n\t\tif (isset($element['track_id'])) {<br \/>\n\t\t\t$sql = \"\";<br \/>\n\t\t\tforeach($element as $k=>$v) {<br \/>\n\t\t\t\tif(in_array($k, $to_print)) {<br \/>\n\t\t\t\t\t\/\/ Reverse the symbol changes done earlier to insert actual character<br \/>\n\t\t\t\t\t$sql .= \"$k='\".mysql_real_escape_string(str_replace($replacevalue,$finalvalueforitdb,$v)).\"', \";<br \/>\n\t\t\t\t}<br \/>\n\t\t\t}<br \/>\n\t\t\t$sql = rtrim(ltrim($sql,\"track_id='$element[track_id]', \"),\", \");<br \/>\n\t\t\t$sql1 = \"INSERT INTO $db_table (track_id) VALUES ('$element[track_id]');\";<br \/>\n\t\t\t$sql2 = \"UPDATE $db_table SET $sql WHERE track_id=$element[track_id];\";<br \/>\n\t\t\tmysql_query($sql1) or die(mysql_error());<br \/>\n#\t\t\techo\"$sql1<br \/>$sql2<\/p>\n<p>\";\t\/\/ For debugging. Uncomment with caution!<br \/>\n\t\t\tmysql_query($sql2) or die(mysql_error());<br \/>\n\t\t}<br \/>\n\t}<br \/>\n#\tprint_r($array);\t\/\/ For debugging. Uncomment with caution!<br \/>\n}<br \/>\n############## end MySQL  ###################<\/p>\n<p>$xml_parser=\"\";<br \/>\n$songs=array();\t\t\t\/\/will hold each song in a 2-d array<br \/>\n$number_dicts=0;\t\t\/\/counter, number of 'dict' elements encountered<br \/>\n$current_key=\"\";\t\t\/\/key for each element in second dimension of array<br \/>\n$current_element=\"\";\t\/\/stores xml element name<br \/>\n$current_data=\"\";\t\t\/\/value for second dimension array elements<br \/>\n$end_of_songs=FALSE;\t\/\/boolean used to help let us know if we're done with the song list<\/p>\n<p>$current_inside_data=false; \/\/ Variable to control concatenation within character_data function (it mainly happens with extended characters for some reason)<\/p>\n<p>function start_element($parser, $name, $attribs) {<br \/>\n\tglobal $current_element, $number_dicts, $current_inside_data;<br \/>\n\tif($name==\"DICT\"){<br \/>\n\t\t$number_dicts++;<br \/>\n\t}<br \/>\n\tif ($number_dicts>2){<br \/>\n\t\t$current_element=$name;<br \/>\n\t}<br \/>\n\t$current_inside_data = false;<br \/>\n}<\/p>\n<p>function end_element($parser, $name) {<br \/>\n\tglobal $songs, $current_element, $current_data, $number_dicts, $current_key, $end_of_songs, $current_inside_data;<br \/>\n\tif($end_of_songs){<br \/>\n\t\t$current_inside_data = false;<br \/>\n\t\treturn;<br \/>\n\t}<br \/>\n\tif($current_element==\"KEY\"){<br \/>\n\t\t$u = trim($current_data);<br \/>\n\t\t$current_key=str_replace(' ','_',strtolower($u));<br \/>\n\t}else{<br \/>\n\t\tif ($number_dicts>2){<br \/>\n\t\t\t$songs[$number_dicts][$current_key]=trim($current_data);<br \/>\n\t\t}<br \/>\n\t}<br \/>\n\t$current_inside_data = false;<br \/>\n}<\/p>\n<p># Remember the use for $current_inside_data to switch between = and .=<br \/>\nfunction character_data($parser, $data) {<br \/>\n\tglobal $number_dicts, $current_data, $end_of_songs, $current_element, $old_current_element, $current_inside_data;<br \/>\n\tif($data==\"Playlists\") {<br \/>\n\t\tprintf(\"%d records parsed!\\n\\nHave a nice day. :)\", ($number_dicts-2));<br \/>\n\t\t$end_of_songs=true;<br \/>\n\t}<br \/>\n\tif ($current_inside_data){<br \/>\n\t\t$current_data.=$data;<br \/>\n\t} else {<br \/>\n\t\t$current_data=ltrim($data);<br \/>\n\t}<br \/>\n\t$current_inside_data=true;<br \/>\n}<\/p>\n<p>$xml_parser = xml_parser_create(\"UTF-8\");<br \/>\nxml_parser_set_option($xml_parser, XML_OPTION_CASE_FOLDING, 1);<br \/>\nxml_parser_set_option($xml_parser, XML_OPTION_TARGET_ENCODING, UTF-8);<br \/>\nxml_set_element_handler($xml_parser, \"start_element\", \"end_element\");<br \/>\nxml_set_character_data_handler($xml_parser, \"character_data\");<\/p>\n<p>if (!($fp = @fopen($file, \"r\"))) {<br \/>\n\treturn false;<br \/>\n}<\/p>\n<p>while ($data = fread($fp, 4096)) {<br \/>\n\t\/\/ Certain literal characters are treated as delimiters. See arrays definition above for details. Current list might not be complete.<br \/>\n\t$mysubstitution=str_replace($placeholders,$replacevalue,$data);<br \/>\n\tif (!xml_parse($xml_parser, html_entity_decode($mysubstitution), feof($fp))) {<br \/>\n\t\tdie(sprintf(\"XML error: %s at line %d\", xml_error_string(xml_get_error_code($xml_parser)),xml_get_current_line_number($xml_parser)));<br \/>\n\t}<br \/>\n}<\/p>\n<p>xml_parser_free($xml_parser);<br \/>\nfclose($fp);<br \/>\narray_to_table($songs);<br \/>\n?><\/code><br \/>\nSave the PHP script on your web server and export your iTunes library (File > Export Library&#8230; ) to the same directory. Lastly you&#8217;ll need to hit the PHP file with your web browser to initiate the records insertion process.<\/p>\n<p>PS. Thanks to those who have e-mailed about this. One bug has been covered. Unfortunately some special characters (ie., accents or umlauts) do not parse correctly. Others do, such as Katakana characters. I have neither the skill set nor much drive these days to make it work properly. If you do, any insight would be greatly appreciated.<\/p>\n<p>PPS. BIG thanks to &#8216;lgal&#8217; for more betterer character wrangling and other tweaks.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>A PHP XML parser that will populate a MySQL table using an exported iTunes library file as the source.<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[2,7],"tags":[30],"class_list":["post-294","post","type-post","status-publish","format-standard","hentry","category-code","category-web","tag-php"],"_links":{"self":[{"href":"https:\/\/strawhousepig.net\/wordpress\/wp-json\/wp\/v2\/posts\/294","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/strawhousepig.net\/wordpress\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/strawhousepig.net\/wordpress\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/strawhousepig.net\/wordpress\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/strawhousepig.net\/wordpress\/wp-json\/wp\/v2\/comments?post=294"}],"version-history":[{"count":0,"href":"https:\/\/strawhousepig.net\/wordpress\/wp-json\/wp\/v2\/posts\/294\/revisions"}],"wp:attachment":[{"href":"https:\/\/strawhousepig.net\/wordpress\/wp-json\/wp\/v2\/media?parent=294"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/strawhousepig.net\/wordpress\/wp-json\/wp\/v2\/categories?post=294"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/strawhousepig.net\/wordpress\/wp-json\/wp\/v2\/tags?post=294"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}