static AtomicInfo *find_atom(const char *meta) { char atomName[100]; sprintf(atomName, "%s.%s.%s", ILST_FULL_ATOM, meta, DATA); return APar_FindAtom(atomName, FALSE, VERSIONED_ATOM, 1); }
void APar_OutputXML() { short artwork_count=0; AtomicInfo *ilstAtom = APar_FindAtom("moov.udta.meta.ilst", false, SIMPLE_ATOM, 0); if (ilstAtom == NULL) { return; } fprintf(stdout,"<?xml version=\"1.0\"?>\n"); fprintf(stdout,"<AtomicParsley>\n"); fprintf(stdout," <atoms>\n"); for (int i=ilstAtom->AtomicNumber; i < atom_number; i++) { AtomicInfo* thisAtom = &parsedAtoms[i]; if ( strncmp(thisAtom->AtomicName, "data", 4) == 0) { //thisAtom->AtomicClassification == VERSIONED_ATOM) { AtomicInfo* parent = &parsedAtoms[ APar_FindParentAtom(i, thisAtom->AtomicLevel) ]; if ( (thisAtom->AtomicVerFlags == AtomFlags_Data_Binary || thisAtom->AtomicVerFlags == AtomFlags_Data_Text || thisAtom->AtomicVerFlags == AtomFlags_Data_UInt)) { if (strncmp(parent->AtomicName, "----", 4) == 0) { if (memcmp(parsedAtoms[parent->AtomicNumber+2].AtomicName, "name", 4) == 0) { APar_ExtractDataAtomXML(i,parent->AtomicName,parsedAtoms[parent->AtomicNumber+1].ReverseDNSdomain,parsedAtoms[parent->AtomicNumber+2].ReverseDNSname); } } else if (memcmp(parent->AtomicName, "covr", 4) == 0) { //libmp4v2 doesn't properly set artwork with the right flags (its all 0x00) artwork_count++; } else { APar_ExtractDataAtomXML(i,parent->AtomicName); } } else if (memcmp(parent->AtomicName, "covr", 4) == 0) { artwork_count++; //APar_ExtractAAC_Artwork(thisAtom->AtomicNumber, NULL, artwork_count); } if ( thisAtom->AtomicLength <= 12 ) { fprintf(stdout, "\n"); // (corrupted atom); libmp4v2 touching a file with copyright } } } if (artwork_count != 0) { for (int i=0;i<artwork_count;i++) { fprintf(stdout," <atomArtwork name=\"covr\"/>"); } } fprintf(stdout," </atoms>\n"); fprintf(stdout,"</AtomicParsley>\n"); return; }
/** * Open and scan the metadata of the m4a/mp4/... file * and populate the given track. */ void AP_read_metadata(const char *filePath, Track *track) { FILE *mp4File; Trackage *trackage; uint8_t track_cur; uint8_t txttrack_cur; gboolean audio_or_video_found = FALSE; gboolean has_quicktime_chaps = FALSE; uint32_t timescale = 0; APar_ScanAtoms(filePath, true); mp4File = openSomeFile(filePath, true); trackage = APar_ExtractDetails(mp4File, SHOW_TRACK_INFO); for (track_cur = 0; track_cur < trackage->total_tracks; ++track_cur) { TrackInfo *info = trackage->infos[track_cur]; if ((info->type_of_track & AUDIO_TRACK) || (info->type_of_track & VIDEO_TRACK) || (info->type_of_track & DRM_PROTECTED_TRACK)) { /* * the info->duration is in the track's timescale units so must be divided by that * value to get seconds, while track->tracklen in gtkpod is in ms */ float duration = ((float) info->duration / (float) info->parent->movie_info->timescale) * 1000; track->tracklen = (gint32) duration; track->bitrate = APar_calculate_bitrate(info); track->samplerate = info->media_sample_rate; audio_or_video_found = TRUE; break; } } for (txttrack_cur = 0; audio_or_video_found && txttrack_cur < trackage->total_tracks; ++txttrack_cur) { TrackInfo *txtinfo = trackage->infos[txttrack_cur]; char buf[128]; // search for chapter track if (!(txtinfo->type_of_track & TEXT_TRACK)) continue; // see if the AV track's chap refers to this text track // chap: 0: atom size 4: 'chap' 8,12,...,8+(N-1)*4: (0: referenced track ID) snprintf(buf, sizeof(buf), "moov.trak[%u].tref.chap", track_cur + 1); AtomicInfo* chapAtom = APar_FindAtom(buf, false, SIMPLE_ATOM, 0); if (!chapAtom) continue; int entry_count = (chapAtom->AtomicLength - 8) / 4; for (int i = 0; i < entry_count; ++i) { if (APar_read32(buf, mp4File, chapAtom->AtomicStart + 8 + i * 4) == txtinfo->track_id) { has_quicktime_chaps = TRUE; timescale = txtinfo->media_sample_rate; break; } } if (has_quicktime_chaps) break; } if (has_quicktime_chaps) { // found a chapter... now get the chapter data from the text track char buf[128]; // stts: 0: atom size 4: 'stts' 8: version 12: entry count 16,24,...,16+(N-1)*8: (0: frame count 4: duration) snprintf(buf, sizeof(buf), "moov.trak[%u].mdia.minf.stbl.stts", txttrack_cur + 1); AtomicInfo* sampleAtom = APar_FindAtom(buf, false, VERSIONED_ATOM, 0); // stsz: 0: atom size 4: 'stsz' 8: version 12: size of all (or 0) 16: entry count 20,24,...,20+(N-1)*4: (0: sample size) snprintf(buf, sizeof(buf), "moov.trak[%u].mdia.minf.stbl.stsz", txttrack_cur + 1); AtomicInfo* sampleSizeAtom = APar_FindAtom(buf, false, VERSIONED_ATOM, 0); // stco: 0: atom size 4: 'stco' 8: version 12: entry count 16,20,...,16+(N-1)*4: (0: sample byte offset) snprintf(buf, sizeof(buf), "moov.trak[%u].mdia.minf.stbl.stco", txttrack_cur + 1); AtomicInfo* sampleOffsetAtom = APar_FindAtom(buf, false, VERSIONED_ATOM, 0); // We must have a valid sampleAtom to know chapter times. If sampleSizeAtom or sampleOffsetAtom is invalid, // we can do without them (and instead create a default chapter name). if (sampleAtom && sampleAtom->AtomicLength >= 16) { Itdb_Chapterdata *chapterdata = itdb_chapterdata_new(); uint32_t stts_entry_count = APar_read32(buf, mp4File, sampleAtom->AtomicStart + 12); uint32_t stsz_entry_count = !sampleSizeAtom || sampleSizeAtom->AtomicLength < 20 ? 0 : APar_read32(buf, mp4File, sampleSizeAtom->AtomicStart + 16); uint32_t stco_entry_count = !sampleOffsetAtom || sampleOffsetAtom->AtomicLength < 16 ? 0 : APar_read32(buf, mp4File, sampleOffsetAtom->AtomicStart + 12); uint32_t stsz_all_size = !sampleSizeAtom || sampleSizeAtom->AtomicLength < 16 ? 0 : APar_read32(buf, mp4File, sampleSizeAtom->AtomicStart + 12); uint32_t start_time = 0; u_int32_t max_frame_size = stsz_all_size; // if stsz_all_size specified, use only that size for (int i = 0; !stsz_all_size && i < stsz_entry_count; ++i) { uint32_t chap_name_len = APar_read32(buf, mp4File, sampleSizeAtom->AtomicStart + 20 + i * 4); if (chap_name_len > max_frame_size) max_frame_size = chap_name_len; } max_frame_size += 1; // for trailing '\0' (unneeded?), and to make sure that malloc() gets passed at least 1 char * namebuf = (char *)malloc(max_frame_size * sizeof(char)); for (int i = 0; i < stts_entry_count; ++i) { gchar *title = NULL; uint32_t chap_name_len = stsz_all_size; uint32_t chap_offset = 0; if (stsz_all_size == 0 && i < stsz_entry_count) chap_name_len = APar_read32(buf, mp4File, sampleSizeAtom->AtomicStart + 20 + i * 4); if (i < stco_entry_count) chap_offset = APar_read32(buf, mp4File, sampleOffsetAtom->AtomicStart + 16 + i * 4); if (chap_offset != 0) APar_readX(namebuf, mp4File, chap_offset, chap_name_len); else // If the location of the chapter name is unknown, trigger default chapter naming chap_name_len = 0; if (chap_name_len > 2) { int titlelength = (namebuf[0] << 8) + namebuf[1]; // if the stsz atom and the title value disagree, use the smaller one for safety titlelength = (titlelength > chap_name_len) ? chap_name_len : titlelength; // If a title begins with 0xFFFE, it's a UTF-16 title if (titlelength >= 2 && namebuf[2] == 0xff && namebuf[3] == 0xfe) title = g_utf16_to_utf8((const gunichar2 *) &namebuf[4], titlelength - 2, NULL, NULL, NULL); else title = g_strndup(&namebuf[2], titlelength); } else { // chapter title couldn't be found; create our own titles (and some ipods don't display them anyway). // Translators: this string is used to create a chapter title when no chapter title could be found title = g_strdup_printf(_("Chapter %3d"), i); } if (!timescale) // assume 1000, also, don't divide by 0 timescale = 1000; double duration_ms = (double)start_time * 1000.0 / (double)timescale; itdb_chapterdata_add_chapter(chapterdata, duration_ms, title); g_free(title); if (i < (stts_entry_count - 1)) // skip this stage after the last chapter has been added { uint32_t frame_count = APar_read32(buf, mp4File, sampleAtom->AtomicStart + 16 + i * 8); uint32_t duration = APar_read32(buf, mp4File, sampleAtom->AtomicStart + 20 + i * 8); start_time += frame_count * duration; } } if (namebuf) free(namebuf); if (track->chapterdata) // if there was already chapter data, don't leak it itdb_chapterdata_free(track->chapterdata); track->chapterdata = itdb_chapterdata_duplicate(chapterdata); itdb_chapterdata_free(chapterdata); } } // TODO: add support for Nero-style mp4 chapters if (prefs_get_int("readtags")) { char* value = NULL; // MP4 Title value = find_atom_value(TITLE); if (value) { track->title = g_strdup(value); free(value); } // MP4 Artist value = find_atom_value(ARTIST); if (value) { track->artist = g_strdup(value); free(value); } // MP4 Album Artist value = find_atom_value(ALBUM_ARTIST); if (value) { track->albumartist = g_strdup(value); free(value); } // MP4 Composer value = find_atom_value(COMPOSER); if (value) { track->composer = g_strdup(value); free(value); } // MP4 Comment value = find_atom_value(COMMENT); if (value) { track->comment = g_strdup(value); free(value); } // MP4 Description value = find_atom_value(DESCRIPTION); if (value) { track->description = g_strdup(value); free(value); } // MP4 Keywords value = find_atom_value(KEYWORD); if (value) { track->keywords = g_strdup(value); free(value); } // MP4 Year value = find_atom_value(YEAR); if (value) { track->year = atoi(value); free(value); } // MP4 Album value = find_atom_value(ALBUM); if (value) { track->album = g_strdup(value); free(value); } // MP4 Track No. and Total value = find_atom_value(TRACK_NUM_AND_TOTAL); if (value) { const char* delimiter = " of "; char *result = NULL; result = strtok(value, delimiter); if (result) track->track_nr = atoi(result); result = strtok(NULL, delimiter); if (result) track->tracks = atoi(result); free(value); } // MP4 Disk No. and Total value = find_atom_value(DISK_NUM_AND_TOTAL); if (value) { const char* delimiter = " of "; char *result = NULL; result = strtok(value, delimiter); if (result) track->cd_nr = atoi(result); result = strtok(NULL, delimiter); if (result) track->cds = atoi(result); free(value); } // MP4 Grouping value = find_atom_value(GROUPING); if (value) { track->grouping = g_strdup(value); free(value); } // MP4 Genre - note: can be either a standard or custom genre // standard genre value = find_atom_value(STANDARD_GENRE); if (value) { track->genre = charset_to_utf8(value); // Should not free standard genres } else { // custom genre value = find_atom_value(CUSTOM_GENRE); if (value) { track->genre = g_strdup(value); free(value); } } // MP4 Tempo / BPM value = find_atom_value(TEMPO); if (value) { track->BPM = atoi(value); free(value); } // MP4 Lyrics value = find_atom_value(LYRICS); if (value) { track->lyrics_flag = 0x01; free(value); } // MP4 TV Show value = find_atom_value(TV_SHOW); if (value) { track->tvshow = g_strdup(value); free(value); } // MP4 TV Episode value = find_atom_value(TV_EPISODE); if (value) { track->tvepisode = g_strdup(value); free(value); } // MP4 TV Episode No. value = find_atom_value(TV_EPISODE_NO); if (value) { track->episode_nr = atoi(value); free(value); } // MP4 TV Network value = find_atom_value(TV_NETWORK_NAME); if (value) { track->tvnetwork = g_strdup(value); free(value); } // MP4 TV Season No. value = find_atom_value(TV_SEASON_NO); if (value) { track->season_nr = atoi(value); free(value); } // MP4 Media Type value = find_atom_value(MEDIA_TYPE); if (value) { stiks * stik = MatchStikString(value); if (stik) { track->mediatype = mediaTypeTagToMediaType(stik->stik_number); } // Should not free standard media types } // MP4 Compilation flag value = find_atom_value(COMPILATION); if (value) { track->compilation = !g_strcmp0("true", value); free(value); } // MP4 Category value = find_atom_value(CATEGORY); if (value) { track->category = g_strdup(value); free(value); } // MP4 Podcast URL value = find_atom_value(PODCAST_URL); if (value) { track->podcasturl = g_strdup(value); free(value); } value = find_atom_value(GAPLESS_FLAG); if (value) { track->gapless_track_flag = atoi(value); free(value); } // MP4 Sort Title value = find_atom_value(SORT_TITLE); if (value) { track->sort_title = g_strdup(value); free(value); } // MP4 Sort Artist value = find_atom_value(SORT_ARTIST); if (value) { track->sort_artist = g_strdup(value); free(value); } // MP4 Sort Album Artist value = find_atom_value(SORT_ALBUM_ARTIST); if (value) { track->sort_albumartist = g_strdup(value); free(value); } // MP4 Sort Composer value = find_atom_value(SORT_COMPOSER); if (value) { track->sort_composer = g_strdup(value); free(value); } // MP4 Sort Album value = find_atom_value(SORT_ALBUM); if (value) { track->sort_album = g_strdup(value); free(value); } // MP4 Sort TV Show value = find_atom_value(SORT_TV_SHOW); if (value) { track->sort_tvshow = g_strdup(value); free(value); } if (prefs_get_int("coverart_apic")) { gchar *tmp_file_prefix = g_build_filename(g_get_tmp_dir(), "ttt", NULL); gchar *tmp_file; AtomicInfo *info = find_atom("covr"); if (info) { // Extract the data to a temporary file tmp_file = APar_ExtractAAC_Artwork(info->AtomicNumber, tmp_file_prefix, 1); g_free(tmp_file_prefix); if (tmp_file && g_file_test(tmp_file, G_FILE_TEST_EXISTS)) { // Set the thumbnail using the tmp file GdkPixbuf *pixbuf = gdk_pixbuf_new_from_file(tmp_file, NULL); if (pixbuf) { itdb_track_set_thumbnails_from_pixbuf(track, pixbuf); g_object_unref(pixbuf); } g_remove(tmp_file); } if (tmp_file) g_free(tmp_file); } } } APar_FreeMemory(); }