void freetags(struct song_metadata *psong) { int role; MAYBEFREE(psong->path); MAYBEFREE(psong->image); MAYBEFREE(psong->title); MAYBEFREE(psong->album); MAYBEFREE(psong->genre); MAYBEFREE(psong->comment); for(role = ROLE_START; role <= ROLE_LAST; role++) { MAYBEFREE(psong->contributor[role]); MAYBEFREE(psong->contributor_sort[role]); } MAYBEFREE(psong->grouping); MAYBEFREE(psong->mime); MAYBEFREE(psong->dlna_pn); MAYBEFREE(psong->tagversion); MAYBEFREE(psong->musicbrainz_albumid); MAYBEFREE(psong->musicbrainz_trackid); MAYBEFREE(psong->musicbrainz_artistid); MAYBEFREE(psong->musicbrainz_albumartistid); }
int scan_xml_tracks_section(int action, char *info) { static int state; static int current_track_id; static int current_field; static int is_streaming; static MP3FILE mp3; static char *song_path=NULL; char real_path[PATH_MAX]; MP3FILE *pmp3; int added_id; if(action == RXML_EVT_OPEN) { state = XML_TRACK_ST_INITIAL; memset((void*)&mp3,0,sizeof(MP3FILE)); song_path = NULL; return 0; } /* walk through the states */ switch(state) { case XML_TRACK_ST_INITIAL: /* expection only a <dict> */ MAYBESETSTATE_TR(RXML_EVT_BEGIN,"dict",XML_TRACK_ST_MAIN_DICT); return XML_STATE_ERROR; break; case XML_TRACK_ST_MAIN_DICT: /* either get a <key>, or a </dict> */ MAYBESETSTATE_TR(RXML_EVT_BEGIN,"key",XML_TRACK_ST_EXPECTING_TRACK_ID); if ((action == RXML_EVT_END) && (strcasecmp(info,"dict") == 0)) { return XML_STATE_PREAMBLE; } return XML_STATE_ERROR; break; case XML_TRACK_ST_EXPECTING_TRACK_ID: /* this is somewhat loose - <key>id</key> */ MAYBESETSTATE_TR(RXML_EVT_BEGIN,"key",XML_TRACK_ST_EXPECTING_TRACK_ID); MAYBESETSTATE_TR(RXML_EVT_END,"key",XML_TRACK_ST_EXPECTING_TRACK_DICT); if (action == RXML_EVT_TEXT) { current_track_id = atoi(info); DPRINTF(E_DBG,L_SCAN,"Scanning iTunes id #%d\n",current_track_id); } else { return XML_STATE_ERROR; } break; case XML_TRACK_ST_EXPECTING_TRACK_DICT: /* waiting for a dict */ MAYBESETSTATE_TR(RXML_EVT_BEGIN,"dict",XML_TRACK_ST_TRACK_INFO); return XML_STATE_ERROR; break; case XML_TRACK_ST_TRACK_INFO: /* again, kind of loose */ MAYBESETSTATE_TR(RXML_EVT_BEGIN,"key",XML_TRACK_ST_TRACK_INFO); MAYBESETSTATE_TR(RXML_EVT_END,"key",XML_TRACK_ST_TRACK_DATA); if(action == RXML_EVT_TEXT) { current_field=scan_xml_get_tagindex(info); if(current_field == SCAN_XML_T_DISABLED) { mp3.disabled = 1; } else if(current_field == SCAN_XML_T_COMPILATION) { mp3.compilation = 1; } } else if((action == RXML_EVT_END) && (strcmp(info,"dict")==0)) { state = XML_TRACK_ST_MAIN_DICT; /* but more importantly, we gotta process the track */ is_streaming = 0; if((song_path) && strncasecmp(song_path,"http://",7) == 0) is_streaming = 1; if((!is_streaming)&&scan_xml_translate_path(song_path,real_path)) { /* FIXME: Error handling */ pmp3=db_fetch_path(NULL,real_path,0); if(!pmp3) { /* file doesn't exist... let's add it? */ scan_filename(real_path,SCAN_TEST_COMPDIR,NULL,NULL); pmp3=db_fetch_path(NULL,real_path,0); } if(pmp3) { /* Update the existing record with the * updated stuff we got from the iTunes xml file */ MAYBECOPYSTRING(title); MAYBECOPYSTRING(artist); MAYBECOPYSTRING(album); MAYBECOPYSTRING(genre); MAYBECOPYSTRING(comment); MAYBECOPYSTRING(composer); MAYBECOPY(song_length); MAYBECOPY(track); MAYBECOPY(total_tracks); MAYBECOPY(year); MAYBECOPY(bitrate); MAYBECOPY(samplerate); MAYBECOPY(play_count); MAYBECOPY(rating); MAYBECOPY(disc); MAYBECOPY(total_discs); MAYBECOPY(time_added); MAYBECOPY(disabled); MAYBECOPYSTRING(album_artist); /* must add to the red-black tree */ scan_xml_add_lookup(current_track_id,pmp3->id); make_composite_tags(pmp3); db_add(NULL,pmp3,NULL); db_dispose_item(pmp3); } } else if(is_streaming) { /* add/update a http:// url */ pmp3=db_fetch_path(NULL,scan_xml_file,current_track_id); if(!pmp3) { /* gotta add it! */ DPRINTF(E_DBG,L_SCAN,"Adding %s\n",song_path); pmp3 = calloc(sizeof(MP3FILE),1); if(!pmp3) DPRINTF(E_FATAL,L_SCAN, "malloc: scan_xml_tracks_section\n"); } else { DPRINTF(E_DBG,L_SCAN,"updating %s\n",song_path); } pmp3->url = strdup(song_path); pmp3->type = strdup("pls"); pmp3->description = strdup("Playlist URL"); pmp3->data_kind = 1; pmp3->item_kind = 2; pmp3->path = strdup(scan_xml_file); pmp3->index = current_track_id; MAYBECOPYSTRING(title); MAYBECOPYSTRING(artist); MAYBECOPYSTRING(album); MAYBECOPYSTRING(genre); MAYBECOPYSTRING(comment); MAYBECOPY(bitrate); MAYBECOPY(samplerate); MAYBECOPY(play_count); MAYBECOPY(rating); MAYBECOPY(time_added); MAYBECOPY(disabled); MAYBECOPYSTRING(album_artist); make_composite_tags(pmp3); if(db_add(NULL,pmp3,&added_id) == DB_E_SUCCESS) { scan_xml_add_lookup(current_track_id,added_id); DPRINTF(E_DBG,L_SCAN,"Added %s\n",song_path); } else { DPRINTF(E_DBG,L_SCAN,"Error adding %s\n",song_path); } db_dispose_item(pmp3); } /* cleanup what's left */ MAYBEFREE(mp3.title); MAYBEFREE(mp3.artist); MAYBEFREE(mp3.album); MAYBEFREE(mp3.genre); MAYBEFREE(mp3.comment); MAYBEFREE(mp3.album_artist); MAYBEFREE(song_path); memset((void*)&mp3,0,sizeof(MP3FILE)); } else { return XML_STATE_ERROR; } break; case XML_TRACK_ST_TRACK_DATA: if(action == RXML_EVT_BEGIN) { break; } else if(action == RXML_EVT_TEXT) { if(current_field == SCAN_XML_T_NAME) { mp3.title = strdup(info); } else if(current_field == SCAN_XML_T_ARTIST) { mp3.artist = strdup(info); } else if(current_field == SCAN_XML_T_ALBUM) { mp3.album = strdup(info); } else if(current_field == SCAN_XML_T_GENRE) { mp3.genre = strdup(info); } else if(current_field == SCAN_XML_T_TOTALTIME) { mp3.song_length = atoi(info); } else if(current_field == SCAN_XML_T_TRACKNUMBER) { mp3.track = atoi(info); } else if(current_field == SCAN_XML_T_TRACKCOUNT) { mp3.total_tracks = atoi(info); } else if(current_field == SCAN_XML_T_YEAR) { mp3.year = atoi(info); } else if(current_field == SCAN_XML_T_BITRATE) { mp3.bitrate = atoi(info); } else if(current_field == SCAN_XML_T_SAMPLERATE) { mp3.samplerate = atoi(info); } else if(current_field == SCAN_XML_T_PLAYCOUNT) { mp3.play_count = atoi(info); } else if(current_field == SCAN_XML_T_RATING) { mp3.rating = atoi(info); } else if(current_field == SCAN_XML_T_DISCNO) { mp3.disc = atoi(info); } else if(current_field == SCAN_XML_T_DISCCOUNT) { mp3.total_discs = atoi(info); } else if(current_field == SCAN_XML_T_LOCATION) { song_path = scan_xml_urldecode(info,0); DPRINTF(E_DBG,L_SCAN,"scan_path: %s\n",song_path); } else if(current_field == SCAN_XML_T_DATE_ADDED) { mp3.time_added = scan_xml_datedecode(info); } else if(current_field == SCAN_XML_T_COMMENTS) { mp3.comment = strdup(info); } else if(current_field == SCAN_XML_T_COMPOSER) { mp3.composer = strdup(info); } else if(current_field == SCAN_XML_T_ALBUM_ARTIST) { mp3.album_artist = strdup(info); } } else if(action == RXML_EVT_END) { state = XML_TRACK_ST_TRACK_INFO; } else { return XML_STATE_ERROR; } break; default: return XML_STATE_ERROR; } return XML_STATE_TRACKS; }
/** * collect playlist data for each playlist in the itunes xml file * this again is implemented as a sloppy state machine, and assumes * that the playlist items are after all the playlist metainfo. * * @param action xml action (RXML_EVT_TEXT, etc) * @param info text data associated with event */ int scan_xml_playlists_section(int action, char *info) { static int state = XML_PL_ST_INITIAL; static int next_value=0; /** < what's next song info id or name */ static int native_plid=0; /** < the iTunes playlist id */ static int current_id=0; /** < the mt-daapd playlist id */ static char *current_name=NULL; /** < the iTunes playlist name */ static int dont_scan=0; /** < playlist we don't want */ int native_track_id; /** < the iTunes id of the track */ int track_id; /** < the mt-daapd track id */ M3UFILE *pm3u; /* do initialization */ if(action == RXML_EVT_OPEN) { state = XML_PL_ST_INITIAL; if(current_name) free(current_name); current_name = NULL; dont_scan=0; return 0; } switch(state) { case XML_PL_ST_INITIAL: /* expecting <array> or error */ MAYBESETSTATE_PL(RXML_EVT_BEGIN,"array",XML_PL_ST_EXPECTING_PL); return XML_STATE_ERROR; case XML_PL_ST_EXPECTING_PL: /* either a new playlist, or end of playlist list */ dont_scan=0; MAYBESETSTATE_PL(RXML_EVT_BEGIN,"dict",XML_PL_ST_EXPECTING_PL_DATA); if((action == RXML_EVT_END) && (strcasecmp(info,"array") == 0)) return XML_STATE_PREAMBLE; return XML_STATE_ERROR; case XML_PL_ST_EXPECTING_PL_DATA: /* either a key/data pair, or an array, signaling start of playlist * or the end of the dict (end of playlist data) */ MAYBESETSTATE_PL(RXML_EVT_BEGIN,"key",XML_PL_ST_EXPECTING_PL_DATA); MAYBESETSTATE_PL(RXML_EVT_END,"key",XML_PL_ST_EXPECTING_PL_VALUE); MAYBESETSTATE_PL(RXML_EVT_END,"dict",XML_PL_ST_EXPECTING_PL); if(action == RXML_EVT_TEXT) { next_value=XML_PL_NEXT_VALUE_NONE; if(strcasecmp(info,"Name") == 0) { next_value = XML_PL_NEXT_VALUE_NAME; } else if(strcasecmp(info,"Playlist ID") == 0) { next_value = XML_PL_NEXT_VALUE_ID; } else if(strcasecmp(info,"Master") == 0) { /* No point adding the master library... we have one */ dont_scan=1; } return XML_STATE_PLAYLISTS; } return XML_STATE_ERROR; case XML_PL_ST_EXPECTING_PL_VALUE: /* any tag, value we are looking for, any close tag */ if((action == RXML_EVT_BEGIN) && (strcasecmp(info,"array") == 0)) { /* we are about to get track list... must register the playlist */ current_id=0; if(dont_scan == 0) { DPRINTF(E_DBG,L_SCAN,"Creating playlist for %s\n",current_name); /* we won't actually use the iTunes pl_id, as it seems * to change for no good reason. We'll hash the name, * instead. */ /* delete the old one first */ /* FIXME: Error handling */ DPRINTF(E_DBG,L_SCAN,"Converting native plid (%d) to %d\n", native_plid, util_djb_hash_str(current_name)); native_plid = util_djb_hash_str(current_name); pm3u = db_fetch_playlist(NULL,scan_xml_file,native_plid); if(pm3u) { db_delete_playlist(NULL,pm3u->id); db_dispose_playlist(pm3u); } if(db_add_playlist(NULL,current_name,PL_STATICXML,NULL, scan_xml_file,native_plid, ¤t_id) != DB_E_SUCCESS) { DPRINTF(E_LOG,L_SCAN,"err adding playlist %s\n",current_name); current_id=0; } } dont_scan=0; state=XML_PL_ST_EXPECTING_PL_TRACKLIST; MAYBEFREE(current_name); return XML_STATE_PLAYLISTS; } if(action == RXML_EVT_BEGIN) return XML_STATE_PLAYLISTS; if(action == RXML_EVT_END) { state = XML_PL_ST_EXPECTING_PL_DATA; return XML_STATE_PLAYLISTS; } if(action == RXML_EVT_TEXT) { /* got the value we were hoping for */ if(next_value == XML_PL_NEXT_VALUE_NAME) { MAYBEFREE(current_name); current_name = strdup(info); DPRINTF(E_DBG,L_SCAN,"Found playlist: %s\n",current_name); /* disallow specific playlists */ if(strcasecmp(current_name,"Party Shuffle") == 0) { dont_scan=1; } } else if(next_value == XML_PL_NEXT_VALUE_ID) { native_plid = atoi(info); } return XML_STATE_PLAYLISTS; } return XML_STATE_ERROR; case XML_PL_ST_EXPECTING_PL_TRACKLIST: if((strcasecmp(info,"dict") == 0) || (strcasecmp(info,"key") == 0)) return XML_STATE_PLAYLISTS; MAYBESETSTATE_PL(RXML_EVT_END,"array",XML_PL_ST_EXPECTING_PL_DATA); if(action == RXML_EVT_TEXT) { if(strcasecmp(info,"Track ID") != 0) { native_track_id = atoi(info); DPRINTF(E_DBG,L_SCAN,"Adding itunes track #%s\n",info); /* add it to the current playlist (current_id) */ if(current_id && scan_xml_get_index(native_track_id, &track_id)) { /* FIXME: Error handling */ db_add_playlist_item(NULL,current_id,track_id); } } return XML_STATE_PLAYLISTS; } return XML_STATE_PLAYLISTS; default: return XML_STATE_ERROR; } return XML_STATE_PLAYLISTS; }
/** * scan an iTunes xml music database file, augmenting * the metainfo with that found in the xml file * * @param filename xml file to parse * @returns TRUE if playlist parsed successfully, FALSE otherwise */ int scan_xml_playlist(char *filename) { char *working_base; const void *val; int retval=TRUE; SCAN_XML_RB *lookup_ptr; SCAN_XML_RB lookup_val; RXMLHANDLE xml_handle; MAYBEFREE(scan_xml_itunes_version); MAYBEFREE(scan_xml_itunes_base_path); MAYBEFREE(scan_xml_itunes_decoded_base_path); MAYBEFREE(scan_xml_real_base_path); scan_xml_file = filename; /* initialize the redblack tree */ if((scan_xml_db = rbinit(scan_xml_rb_compare,NULL)) == NULL) { DPRINTF(E_LOG,L_SCAN,"Could not initialize red/black tree\n"); return FALSE; } /* find the base dir of the itunes playlist itself */ working_base = strdup(filename); if(strrchr(working_base,'/')) { *(strrchr(working_base,'/') + 1) = '\x0'; scan_xml_real_base_path = strdup(working_base); } else { scan_xml_real_base_path = strdup("/"); } free(working_base); DPRINTF(E_SPAM,L_SCAN,"Parsing xml file: %s\n",filename); if(!rxml_open(&xml_handle,filename,scan_xml_handler,NULL)) { DPRINTF(E_LOG,L_SCAN,"Error opening xml file %s: %s\n", filename,rxml_errorstring(xml_handle)); } else { if(!rxml_parse(xml_handle)) { retval=FALSE; DPRINTF(E_LOG,L_SCAN,"Error parsing xml file %s: %s\n", filename,rxml_errorstring(xml_handle)); } } rxml_close(xml_handle); /* destroy the redblack tree */ val = rblookup(RB_LUFIRST,NULL,scan_xml_db); while(val) { lookup_val.itunes_index = ((SCAN_XML_RB*)val)->itunes_index; lookup_ptr = (SCAN_XML_RB *)rbdelete((void*)&lookup_val,scan_xml_db); if(lookup_ptr) free(lookup_ptr); val = rblookup(RB_LUFIRST,NULL,scan_xml_db); } rbdestroy(scan_xml_db); MAYBEFREE(scan_xml_itunes_version); MAYBEFREE(scan_xml_itunes_base_path); MAYBEFREE(scan_xml_itunes_decoded_base_path); MAYBEFREE(scan_xml_real_base_path); return retval; }