Skip to content

Commit

Permalink
feat: mp4 chapter: track or udta
Browse files Browse the repository at this point in the history
  • Loading branch information
ireader committed Jan 6, 2024
1 parent b1e08e7 commit ece5a96
Show file tree
Hide file tree
Showing 12 changed files with 233 additions and 7 deletions.
1 change: 1 addition & 0 deletions libmov/include/mov-format.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
#define MOV_OBJECT_VP9 0xB1 // VP9 Video
#define MOV_OBJECT_FLAC 0xC1 // nonstandard from FFMPEG
#define MOV_OBJECT_VP8 0xC2 // nonstandard
#define MOV_OBJECT_CHAPTER 0xC3 // chapter https://developer.apple.com/documentation/quicktime-file-format/base_media_information_header_atom
#define MOV_OBJECT_H266 0xFC // ITU-T Recommendation H.266
#define MOV_OBJECT_G711a 0xFD // ITU G.711 alaw
#define MOV_OBJECT_G711u 0xFE // ITU G.711 ulaw
Expand Down
7 changes: 7 additions & 0 deletions libmov/include/mov-udta.h
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,13 @@ struct mov_udta_meta_t

int mov_udta_meta_write(const struct mov_udta_meta_t* meta, void* data, int bytes);

struct mov_udta_chapter_t
{
uint64_t timestamp; // 1s = 10 * 1000 * 1000
const char* title; // optional
};
int mov_udta_chapter_write(const struct mov_udta_chapter_t* chapters, int count, void* data, int bytes);

#ifdef __cplusplus
}
#endif
Expand Down
5 changes: 5 additions & 0 deletions libmov/source/mov-internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,8 @@ struct mov_track_t

struct mov_elst_t* elst;
size_t elst_count;

uint32_t chpl_track; // MOV_CHAPTER track

struct mov_sample_t* samples;
uint32_t sample_count;
Expand Down Expand Up @@ -254,6 +256,7 @@ int mov_read_text(struct mov_t* mov, const struct mov_box_t* box);
int mov_read_smdm(struct mov_t* mov, const struct mov_box_t* box);
int mov_read_coll(struct mov_t* mov, const struct mov_box_t* box);
int mov_read_udta(struct mov_t* mov, const struct mov_box_t* box);
int mov_read_chpl(struct mov_t* mov, const struct mov_box_t* box);

size_t mov_write_ftyp(const struct mov_t* mov);
size_t mov_write_mvhd(const struct mov_t* mov);
Expand All @@ -263,6 +266,7 @@ size_t mov_write_hdlr(const struct mov_t* mov);
size_t mov_write_vmhd(const struct mov_t* mov);
size_t mov_write_smhd(const struct mov_t* mov);
size_t mov_write_nmhd(const struct mov_t* mov);
size_t mov_write_gmhd(const struct mov_t* mov);
size_t mov_write_sthd(const struct mov_t* mov);
size_t mov_write_dinf(const struct mov_t* mov);
size_t mov_write_dref(const struct mov_t* mov);
Expand Down Expand Up @@ -291,6 +295,7 @@ size_t mov_write_mehd(const struct mov_t* mov);
size_t mov_write_sidx(const struct mov_t* mov, uint64_t offset);
size_t mov_write_mfhd(const struct mov_t* mov, uint32_t fragment);
size_t mov_write_edts(const struct mov_t* mov);
size_t mov_write_tref(const struct mov_t* mov);
size_t mov_write_stbl(const struct mov_t* mov);
size_t mov_write_minf(const struct mov_t* mov);
size_t mov_write_mdia(const struct mov_t* mov);
Expand Down
49 changes: 49 additions & 0 deletions libmov/source/mov-minf.c
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,25 @@ int mov_read_text(struct mov_t* mov, const struct mov_box_t* box)
return 0;
}

int mov_read_chpl(struct mov_t* mov, const struct mov_box_t* box)
{
int i, count, len;
mov_buffer_r8(&mov->io); /* version */
mov_buffer_r24(&mov->io); /* flags */
mov_buffer_skip(&mov->io, 4); /* unknown*/

count = mov_buffer_r8(&mov->io); /* count */
for (i = 0; i < count; i++)
{
mov_buffer_r64(&mov->io); /* timestamp */
len = mov_buffer_r8(&mov->io); /* title size */
mov_buffer_skip(&mov->io, len); /* title */
}

(void)box;
return 0;
}

size_t mov_write_vmhd(const struct mov_t* mov)
{
mov_buffer_w32(&mov->io, 20); /* size (always 0x14) */
Expand Down Expand Up @@ -103,6 +122,36 @@ size_t mov_write_nmhd(const struct mov_t* mov)
return 12;
}

size_t mov_write_gmhd(const struct mov_t* mov)
{
mov_buffer_w32(&mov->io, 8 + 24 + 44); /* size */
mov_buffer_write(&mov->io, "gmhd", 4);

mov_buffer_w32(&mov->io, 24); /* size */
mov_buffer_write(&mov->io, "gmin", 4);
mov_buffer_w32(&mov->io, 0); /* version & flags */
mov_buffer_w16(&mov->io, 0x40); /* graphics mode */
mov_buffer_w16(&mov->io, 0x8000); /* opcolor red*/
mov_buffer_w16(&mov->io, 0x8000); /* opcolor green*/
mov_buffer_w16(&mov->io, 0x8000); /* opcolor blue*/
mov_buffer_w16(&mov->io, 0); /* balance */
mov_buffer_w16(&mov->io, 0); /* reserved */

mov_buffer_w32(&mov->io, 44); /* size */
mov_buffer_write(&mov->io, "text", 4);
mov_buffer_w32(&mov->io, 0x00010000); /* u */
mov_buffer_w32(&mov->io, 0);
mov_buffer_w32(&mov->io, 0);
mov_buffer_w32(&mov->io, 0); /* v */
mov_buffer_w32(&mov->io, 0x00010000);
mov_buffer_w32(&mov->io, 0);
mov_buffer_w32(&mov->io, 0); /* w */
mov_buffer_w32(&mov->io, 0);
mov_buffer_w32(&mov->io, 0x40000000);

return 8 + 24 + 44;
}

/*
ISO/IEC 14496-12:2015(E) 12.6.2 Subtitle media header (p185)
aligned(8) class SubtitleMediaHeaderBox extends FullBox ('sthd', version = 0, flags = 0){
Expand Down
1 change: 1 addition & 0 deletions libmov/source/mov-reader.c
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,7 @@ static struct mov_parse_t s_mov_parse_table[] = {
{ MOV_TAG('a', 'v', '1', 'C'), MOV_NULL, mov_read_extra }, // av1-isobmff
{ MOV_TAG('a', 'v', 'c', 'C'), MOV_NULL, mov_read_extra }, // ISO/IEC 14496-15:2010(E) avcC
{ MOV_TAG('b', 't', 'r', 't'), MOV_NULL, mov_read_btrt }, // ISO/IEC 14496-15:2010(E) 5.3.4.1.1 Definition
{ MOV_TAG('c', 'h', 'p', 'l'), MOV_STBL, mov_read_chpl }, // chapter title
{ MOV_TAG('c', 'o', '6', '4'), MOV_STBL, mov_read_stco },
{ MOV_TAG('C', 'o', 'L', 'L'), MOV_STBL, mov_read_coll },
{ MOV_TAG('c', 't', 't', 's'), MOV_STBL, mov_read_ctts },
Expand Down
26 changes: 24 additions & 2 deletions libmov/source/mov-stsd.c
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,8 @@ static int mov_read_hint_sample_entry(struct mov_t* mov, struct mov_sample_entry
struct mov_box_t box;
mov_read_sample_entry(mov, &box, &entry->data_reference_index);
mov_buffer_skip(&mov->io, box.size - 16);
entry->object_type_indication = mov_tag_to_object(box.type);
entry->stream_type = MP4_STREAM_VISUAL;
mov->track->tag = box.type;
return mov_buffer_error(&mov->io);
}
Expand All @@ -205,6 +207,8 @@ static int mov_read_meta_sample_entry(struct mov_t* mov, struct mov_sample_entry
struct mov_box_t box;
mov_read_sample_entry(mov, &box, &entry->data_reference_index);
mov_buffer_skip(&mov->io, box.size - 16);
entry->object_type_indication = mov_tag_to_object(box.type);
entry->stream_type = MP4_STREAM_VISUAL;
mov->track->tag = box.type;
return mov_buffer_error(&mov->io);
}
Expand All @@ -226,7 +230,7 @@ static int mov_read_text_sample_entry(struct mov_t* mov, struct mov_sample_entry
mov_read_sample_entry(mov, &box, &entry->data_reference_index);
if (MOV_TEXT == box.type)
{
// https://developer.apple.com/library/archive/documentation/QuickTime/QTFF/QTFFChap3/qtff3.html#//apple_ref/doc/uid/TP40000939-CH205-69835
// https://developer.apple.com/documentation/quicktime-file-format/text_sample_description
//mov_buffer_r32(&mov->io); /* display flags */
//mov_buffer_r32(&mov->io); /* text justification */
//mov_buffer_r16(&mov->io); /* background color: 48-bit RGB color */
Expand All @@ -249,6 +253,8 @@ static int mov_read_text_sample_entry(struct mov_t* mov, struct mov_sample_entry
mov_buffer_skip(&mov->io, box.size - 16);
}

entry->object_type_indication = mov_tag_to_object(box.type);
entry->stream_type = MP4_STREAM_VISUAL;
mov->track->tag = box.type;
return mov_buffer_error(&mov->io);
}
Expand Down Expand Up @@ -381,6 +387,18 @@ int mov_read_stsd(struct mov_t* mov, const struct mov_box_t* box)
// return size;
//}

static size_t mov_write_btrt(const struct mov_t* mov, const struct mov_sample_entry_t* entry)
{
mov_buffer_w32(&mov->io, 20); /* size */
mov_buffer_write(&mov->io, "btrt", 4);
mov_buffer_w32(&mov->io, entry->u.bitrate.bufferSizeDB);
mov_buffer_w32(&mov->io, 0x00000014);
mov_buffer_w32(&mov->io, 0x00000014);
//mov_buffer_w32(&mov->io, entry->u.bitrate.maxBitrate);
//mov_buffer_w32(&mov->io, entry->u.bitrate.avgBitrate);
return 20;
}

static size_t mov_write_video(const struct mov_t* mov, const struct mov_sample_entry_t* entry)
{
size_t size;
Expand Down Expand Up @@ -437,6 +455,7 @@ static size_t mov_write_video(const struct mov_t* mov, const struct mov_sample_e
else if (MOV_OBJECT_VP8 == entry->object_type_indication || MOV_OBJECT_VP9 == entry->object_type_indication)
size += mov_write_vpcc(mov);

//size += mov_write_btrt(mov, entry);
mov_write_size(mov, offset, size); /* update size */
return size;
}
Expand Down Expand Up @@ -479,6 +498,7 @@ static size_t mov_write_audio(const struct mov_t* mov, const struct mov_sample_e
else if(MOV_OBJECT_OPUS == entry->object_type_indication)
size += mov_write_dops(mov);

//size += mov_write_btrt(mov, entry);
mov_write_size(mov, offset, size); /* update size */
return size;
}
Expand All @@ -488,7 +508,7 @@ static int mov_write_subtitle(const struct mov_t* mov, const struct mov_sample_e
int size;
uint64_t offset;

size = 8 /* Box */ + 8 /* SampleEntry */ + entry->extra_data_size;
size = 8 /* Box */ + 8 /* SampleEntry */;

offset = mov_buffer_tell(&mov->io);
mov_buffer_w32(&mov->io, 0); /* size */
Expand All @@ -506,6 +526,8 @@ static int mov_write_subtitle(const struct mov_t* mov, const struct mov_sample_e
{
mov_buffer_write(&mov->io, entry->extra_data, entry->extra_data_size);
size += entry->extra_data_size;

size += mov_write_btrt(mov, entry);
}

mov_write_size(mov, offset, size); /* update size */
Expand Down
2 changes: 1 addition & 1 deletion libmov/source/mov-stts.c
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ uint32_t mov_build_stts(struct mov_track_t* track)
{
assert(track->samples[i + 1].dts >= track->samples[i].dts || i + 1 == track->sample_count);
delta = (uint32_t)(i + 1 < track->sample_count && track->samples[i + 1].dts > track->samples[i].dts ? track->samples[i + 1].dts - track->samples[i].dts : 1);
if (NULL != sample && delta == sample->samples_per_chunk)
if (NULL != sample && (delta == sample->samples_per_chunk || i + 1 == track->sample_count) )
{
track->samples[i].first_chunk = 0;
assert(sample->first_chunk > 0);
Expand Down
1 change: 1 addition & 0 deletions libmov/source/mov-tag.c
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ static struct mov_object_tag s_tags[] = {
{ MOV_OBJECT_TEXT, MOV_TAG('t', 'x', '3', 'g') },
{ MOV_OBJECT_TEXT, MOV_TAG('t', 'e', 'x', 't') },
{ MOV_OBJECT_TEXT, MOV_TAG('c', '6', '0', '8') },
{ MOV_OBJECT_CHAPTER, MOV_TAG('t', 'e', 'x', 't') },
{ MOV_OBJECT_OPUS, MOV_OPUS },
{ MOV_OBJECT_VP8, MOV_VP8 },
{ MOV_OBJECT_VP9, MOV_VP9 },
Expand Down
51 changes: 48 additions & 3 deletions libmov/source/mov-track.c
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,28 @@ int mov_add_video(struct mov_track_t* track, const struct mov_mvhd_t* mvhd, uint

int mov_add_subtitle(struct mov_track_t* track, const struct mov_mvhd_t* mvhd, uint32_t timescale, uint8_t object, const void* extra_data, size_t extra_data_size)
{
static const uint8_t chapter_extra_data[] = {
// TextSampleEntry
0x00, 0x00, 0x00, 0x01, // displayFlags
0x00, 0x00, // horizontal + vertical justification
0x00, 0x00, 0x00, 0x00, // bgColourRed/Green/Blue/Alpha
// BoxRecord
0x00, 0x00, 0x00, 0x00, // defTextBoxTop/Left
0x00, 0x00, 0x00, 0x00, // defTextBoxBottom/Right
// StyleRecord
0x00, 0x00, 0x00, 0x00, // startChar + endChar
0x00, 0x01, // fontID
0x00, 0x00, // fontStyleFlags + fontSize
0x00, 0x00, 0x00, 0x00, // fgColourRed/Green/Blue/Alpha
// FontTableBox
0x00, 0x00, 0x00, 0x0D, // box size
'f', 't', 'a', 'b', // box atom name
0x00, 0x01, // entry count
// FontRecord
0x00, 0x01, // font ID
0x00, // font name length
};

struct mov_sample_entry_t* subtitle;

subtitle = &track->stsd.entries[0];
Expand All @@ -185,12 +207,12 @@ int mov_add_subtitle(struct mov_track_t* track, const struct mov_mvhd_t* mvhd, u

assert(0 != mov_object_to_tag(object));
track->tag = mov_object_to_tag(object);
track->handler_type = MOV_SBTL;
track->handler_type = track->tag == MOV_TAG('t', 'e', 'x', 't') ? MOV_TEXT : MOV_SBTL;
track->handler_descr = "SubtitleHandler";
track->stsd.entry_count = 1;
track->offset = 0;

track->tkhd.flags = MOV_TKHD_FLAG_TRACK_ENABLE | MOV_TKHD_FLAG_TRACK_IN_MOVIE;
track->tkhd.flags = (track->tag == MOV_TAG('t', 'e', 'x', 't') ? 0 : MOV_TKHD_FLAG_TRACK_ENABLE) | MOV_TKHD_FLAG_TRACK_IN_MOVIE;
track->tkhd.track_ID = mvhd->next_track_ID;
track->tkhd.creation_time = mvhd->creation_time;
track->tkhd.modification_time = mvhd->modification_time;
Expand All @@ -205,6 +227,12 @@ int mov_add_subtitle(struct mov_track_t* track, const struct mov_mvhd_t* mvhd, u
track->mdhd.language = 0x55c4;
track->mdhd.duration = 0; // placeholder

if (object == MOV_OBJECT_CHAPTER && 0 == extra_data_size)
{
extra_data = chapter_extra_data;
extra_data_size = sizeof(chapter_extra_data);
}

subtitle->extra_data = malloc(extra_data_size + 1);
if (NULL == subtitle->extra_data)
return -ENOMEM;
Expand Down Expand Up @@ -268,6 +296,10 @@ size_t mov_write_minf(const struct mov_t* mov)
{
size += mov_write_smhd(mov);
}
else if (MOV_TEXT == track->handler_type)
{
size += mov_write_gmhd(mov);
}
else if (MOV_SUBT == track->handler_type || MOV_SBTL == track->handler_type)
{
size += mov_write_nmhd(mov);
Expand Down Expand Up @@ -312,8 +344,9 @@ size_t mov_write_trak(const struct mov_t* mov)
mov_buffer_write(&mov->io, "trak", 4);

size += mov_write_tkhd(mov);
//size += mov_write_tref(mov);
size += mov_write_edts(mov);
if(mov->track->chpl_track != 0)
size += mov_write_tref(mov);
size += mov_write_mdia(mov);

mov_write_size(mov, offset, size); /* update size */
Expand All @@ -338,3 +371,15 @@ size_t mov_write_edts(const struct mov_t* mov)
mov_write_size(mov, offset, size); /* update size */
return size;
}

size_t mov_write_tref(const struct mov_t* mov)
{
mov_buffer_w32(&mov->io, 20); /* size */
mov_buffer_write(&mov->io, "tref", 4);

mov_buffer_w32(&mov->io, 12); /* size */
mov_buffer_write(&mov->io, "chap", 4);
mov_buffer_w32(&mov->io, mov->track->chpl_track);

return 20;
}
Loading

0 comments on commit ece5a96

Please sign in to comment.