commit 3fbdec07724f4806f88dd93ac3927162b2cdaabd Author: Joel Smith <jsf-lists.gtk...@jk1.net> Date: Thu Nov 8 14:23:35 2012 -0700
add mp4 chapter reading support to Atomic Parsley bridge libs/atomic-parsley/AtomicParsleyBridge.cpp | 125 +++++++++++++++++++++++++-- 1 files changed, 119 insertions(+), 6 deletions(-) --- diff --git a/libs/atomic-parsley/AtomicParsleyBridge.cpp b/libs/atomic-parsley/AtomicParsleyBridge.cpp index ef1a182..704fd3b 100644 --- a/libs/atomic-parsley/AtomicParsleyBridge.cpp +++ b/libs/atomic-parsley/AtomicParsleyBridge.cpp @@ -146,7 +146,10 @@ 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); @@ -156,11 +159,8 @@ void AP_read_metadata(const char *filePath, Track *track) { for (track_cur = 0; track_cur < trackage->total_tracks; ++track_cur) { TrackInfo *info = trackage->infos[track_cur]; - // FIXME no chapter information implemented yet - - if (info->track_type && audio_or_video_found == FALSE - && ((info->track_type & AUDIO_TRACK) || (info->track_type & VIDEO_TRACK) - || (info->track_type & DRM_PROTECTED_TRACK))) { + 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 @@ -171,10 +171,123 @@ void AP_read_metadata(const char *filePath, Track *track) { 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) + 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); - audio_or_video_found = TRUE; + 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; ------------------------------------------------------------------------------ Everyone hates slow websites. So do we. Make your web apps faster with AppDynamics Download AppDynamics Lite for free today: http://p.sf.net/sfu/appdyn_d2d_nov _______________________________________________ gtkpod-cvs2 mailing list gtkpod-cvs2@lists.sourceforge.net https://lists.sourceforge.net/lists/listinfo/gtkpod-cvs2