There is basic support for muxing chapter information into the Apple Quicktime format already, but there are two errors which prevent correct detection on the player side. 1) A special apple 'text' atom needs to be included inside the gmhd atom. 2) The *different* 'text' atom inside the 'stsd' atom needs a proper header. With these changes, the chapters are now picked up by Apple players and reported correctly by tools like mediainfo and mp4chaps. v3 Update: The stub TextSampleEntry creation is moved to where the chapter track is created so it's now specific to this track.
Unified · Split
| @@ -1262,7 +1262,7 @@ static int mov_write_nmhd_tag(AVIOContext *pb) · 1261 unmodified lines | ||
| 1262 | 1262 | |
| 1263 | 1263 | static int mov_write_gmhd_tag(AVIOContext *pb) |
| 1264 | 1264 | { |
| 1265 | − avio_wb32(pb, 0x20); /* size */ | |
| 1265 | + avio_wb32(pb, 0x4C); /* size */ | |
| 1266 | 1266 | ffio_wfourcc(pb, "gmhd"); |
| 1267 | 1267 | avio_wb32(pb, 0x18); /* gmin size */ |
| 1268 | 1268 | ffio_wfourcc(pb, "gmin");/* generic media info */ |
| @@ -1273,7 +1273,27 @@ static int mov_write_gmhd_tag(AVIOContext *pb) · 4 unmodified lines | ||
| 1273 | 1273 | avio_wb16(pb, 0x8000); /* opColor (b?) */ |
| 1274 | 1274 | avio_wb16(pb, 0); /* balance */ |
| 1275 | 1275 | avio_wb16(pb, 0); /* reserved */ |
| 1276 | − return 0x20; | |
| 1276 | + | |
| 1277 | + /* | |
| 1278 | + * This special text atom is required for | |
| 1279 | + * Apple Quicktime chapters. The contents | |
| 1280 | + * don't appear to be documented, so the | |
| 1281 | + * bytes are copied verbatim. | |
| 1282 | + */ | |
| 1283 | + avio_wb32(pb, 0x2C); /* size */ | |
| 1284 | + ffio_wfourcc(pb, "text"); | |
| 1285 | + avio_wb16(pb, 0x01); | |
| 1286 | + avio_wb32(pb, 0x00); | |
| 1287 | + avio_wb32(pb, 0x00); | |
| 1288 | + avio_wb32(pb, 0x00); | |
| 1289 | + avio_wb32(pb, 0x01); | |
| 1290 | + avio_wb32(pb, 0x00); | |
| 1291 | + avio_wb32(pb, 0x00); | |
| 1292 | + avio_wb32(pb, 0x00); | |
| 1293 | + avio_wb32(pb, 0x00004000); | |
| 1294 | + avio_wb16(pb, 0x0000); | |
| 1295 | + | |
| 1296 | + return 0x4C; | |
| 1277 | 1297 | } |
| 1278 | 1298 | |
| 1279 | 1299 | static int mov_write_smhd_tag(AVIOContext *pb) |
| @@ -3052,6 +3072,8 @@ static int mov_write_packet(AVFormatContext *s, AVPacket *pkt) · 1772 unmodified lines | ||
| 3052 | 3072 | // as samples, and a tref pointing from the other tracks to the chapter one. |
| 3053 | 3073 | static void mov_create_chapter_track(AVFormatContext *s, int tracknum) |
| 3054 | 3074 | { |
| 3075 | + AVIOContext *pb; | |
| 3076 | + | |
| 3055 | 3077 | MOVMuxContext *mov = s->priv_data; |
| 3056 | 3078 | MOVTrack *track = &mov->tracks[tracknum]; |
| 3057 | 3079 | AVPacket pkt = { .stream_index = tracknum, .flags = AV_PKT_FLAG_KEY }; |
| @@ -3063,6 +3085,50 @@ static void mov_create_chapter_track(AVFormatContext *s, int tracknum) · 5 unmodified lines | ||
| 3063 | 3085 | track->enc = avcodec_alloc_context3(NULL); |
| 3064 | 3086 | track->enc->codec_type = AVMEDIA_TYPE_SUBTITLE; |
| 3065 | 3087 | |
| 3088 | + if (avio_open_dyn_buf(&pb) >= 0) { | |
| 3089 | + int size; | |
| 3090 | + uint8_t *buf; | |
| 3091 | + | |
| 3092 | + /* Stub header (usually for Quicktime chapter track) */ | |
| 3093 | + // TextSampleEntry | |
| 3094 | + avio_wb32(pb, 0x01); // displayFlags | |
| 3095 | + avio_w8(pb, 0x00); // horizontal justification | |
| 3096 | + avio_w8(pb, 0x00); // vertical justification | |
| 3097 | + avio_w8(pb, 0x00); // bgColourRed | |
| 3098 | + avio_w8(pb, 0x00); // bgColourGreen | |
| 3099 | + avio_w8(pb, 0x00); // bgColourBlue | |
| 3100 | + avio_w8(pb, 0x00); // bgColourAlpha | |
| 3101 | + // BoxRecord | |
| 3102 | + avio_wb16(pb, 0x00); // defTextBoxTop | |
| 3103 | + avio_wb16(pb, 0x00); // defTextBoxLeft | |
| 3104 | + avio_wb16(pb, 0x00); // defTextBoxBottom | |
| 3105 | + avio_wb16(pb, 0x00); // defTextBoxRight | |
| 3106 | + // StyleRecord | |
| 3107 | + avio_wb16(pb, 0x00); // startChar | |
| 3108 | + avio_wb16(pb, 0x00); // endChar | |
| 3109 | + avio_wb16(pb, 0x01); // fontID | |
| 3110 | + avio_w8(pb, 0x00); // fontStyleFlags | |
| 3111 | + avio_w8(pb, 0x00); // fontSize | |
| 3112 | + avio_w8(pb, 0x00); // fgColourRed | |
| 3113 | + avio_w8(pb, 0x00); // fgColourGreen | |
| 3114 | + avio_w8(pb, 0x00); // fgColourBlue | |
| 3115 | + avio_w8(pb, 0x00); // fgColourAlpha | |
| 3116 | + // FontTableBox | |
| 3117 | + avio_wb32(pb, 0x0D); // box size | |
| 3118 | + ffio_wfourcc(pb, "ftab"); // box atom name | |
| 3119 | + avio_wb16(pb, 0x01); // entry count | |
| 3120 | + // FontRecord | |
| 3121 | + avio_wb16(pb, 0x01); // font ID | |
| 3122 | + avio_w8(pb, 0x00); // font name length | |
| 3123 | + | |
| 3124 | + if ((size = avio_close_dyn_buf(pb, &buf)) > 0) { | |
| 3125 | + track->enc->extradata = buf; | |
| 3126 | + track->enc->extradata_size = size; | |
| 3127 | + } else { | |
| 3128 | + av_free(&buf); | |
| 3129 | + } | |
| 3130 | + } | |
| 3131 | + | |
| 3066 | 3132 | for (i = 0; i < s->nb_chapters; i++) { |
| 3067 | 3133 | AVChapter *c = s->chapters[i]; |
| 3068 | 3134 | AVDictionaryEntry *t; |