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 | static int mov_write_gmhd_tag(AVIOContext *pb) | 1263 | static int mov_write_gmhd_tag(AVIOContext *pb) |
| 1264 | { | 1264 | { |
| 1265 | avio_wb32(pb, 0x20); /* size */ | 1265 | avio_wb32(pb, 0x4C); /* size */ |
| 1266 | ffio_wfourcc(pb, "gmhd"); | 1266 | ffio_wfourcc(pb, "gmhd"); |
| 1267 | avio_wb32(pb, 0x18); /* gmin size */ | 1267 | avio_wb32(pb, 0x18); /* gmin size */ |
| 1268 | ffio_wfourcc(pb, "gmin");/* generic media info */ | 1268 | ffio_wfourcc(pb, "gmin");/* generic media info */ |
| @@ -1273,7 +1273,27 @@ static int mov_write_gmhd_tag(AVIOContext *pb) · 4 unmodified lines | |||
| 1273 | avio_wb16(pb, 0x8000); /* opColor (b?) */ | 1273 | avio_wb16(pb, 0x8000); /* opColor (b?) */ |
| 1274 | avio_wb16(pb, 0); /* balance */ | 1274 | avio_wb16(pb, 0); /* balance */ |
| 1275 | avio_wb16(pb, 0); /* reserved */ | 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 | static int mov_write_smhd_tag(AVIOContext *pb) | 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 | // as samples, and a tref pointing from the other tracks to the chapter one. | 3072 | // as samples, and a tref pointing from the other tracks to the chapter one. |
| 3053 | static void mov_create_chapter_track(AVFormatContext *s, int tracknum) | 3073 | static void mov_create_chapter_track(AVFormatContext *s, int tracknum) |
| 3054 | { | 3074 | { |
| 3075 | AVIOContext *pb; | ||
| 3076 | |||
| 3055 | MOVMuxContext *mov = s->priv_data; | 3077 | MOVMuxContext *mov = s->priv_data; |
| 3056 | MOVTrack *track = &mov->tracks[tracknum]; | 3078 | MOVTrack *track = &mov->tracks[tracknum]; |
| 3057 | AVPacket pkt = { .stream_index = tracknum, .flags = AV_PKT_FLAG_KEY }; | 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 | track->enc = avcodec_alloc_context3(NULL); | 3085 | track->enc = avcodec_alloc_context3(NULL); |
| 3064 | track->enc->codec_type = AVMEDIA_TYPE_SUBTITLE; | 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 | for (i = 0; i < s->nb_chapters; i++) { | 3132 | for (i = 0; i < s->nb_chapters; i++) { |
| 3067 | AVChapter *c = s->chapters[i]; | 3133 | AVChapter *c = s->chapters[i]; |
| 3068 | AVDictionaryEntry *t; | 3134 | AVDictionaryEntry *t; |