From: Damiano Galassi <damiog@gmail.com> To: ffmpeg-devel@ffmpeg.org Cc: Damiano Galassi <damiog@gmail.com> Subject: [FFmpeg-devel] [PATCH] avformat/mov: read and write additional iTunes style metadata Date: Wed, 12 Feb 2025 17:00:25 +0100 Message-ID: <20250212160025.67493-1-damiog@gmail.com> (raw) --- libavformat/mov.c | 67 +++++++++++++++++++++++++++- libavformat/movenc.c | 69 +++++++++++++++++++++++++++-- tests/ref/fate/caf-alac-remux | 5 ++- tests/ref/fate/cover-art-flac-remux | 5 ++- tests/ref/fate/matroska-alac-remux | 5 ++- tests/ref/fate/mov-cover-image | 4 +- 6 files changed, 142 insertions(+), 13 deletions(-) diff --git a/libavformat/mov.c b/libavformat/mov.c index 85aef33b19..f70a04c37d 100644 --- a/libavformat/mov.c +++ b/libavformat/mov.c @@ -131,6 +131,33 @@ static int mov_metadata_int8_no_padding(MOVContext *c, AVIOContext *pb, return 0; } +static int mov_metadata_int16_no_padding(MOVContext *c, AVIOContext *pb, + unsigned len, const char *key) +{ + c->fc->event_flags |= AVFMT_EVENT_FLAG_METADATA_UPDATED; + av_dict_set_int(&c->fc->metadata, key, avio_rb16(pb), 0); + + return 0; +} + +static int mov_metadata_int32_no_padding(MOVContext *c, AVIOContext *pb, + unsigned len, const char *key) +{ + c->fc->event_flags |= AVFMT_EVENT_FLAG_METADATA_UPDATED; + av_dict_set_int(&c->fc->metadata, key, avio_rb32(pb), 0); + + return 0; +} + +static int mov_metadata_int64_no_padding(MOVContext *c, AVIOContext *pb, + unsigned len, const char *key) +{ + c->fc->event_flags |= AVFMT_EVENT_FLAG_METADATA_UPDATED; + av_dict_set_int(&c->fc->metadata, key, avio_rb64(pb), 0); + + return 0; +} + static int mov_metadata_gnre(MOVContext *c, AVIOContext *pb, unsigned len, const char *key) { @@ -370,7 +397,13 @@ static int mov_read_udta_string(MOVContext *c, AVIOContext *pb, MOVAtom atom) case MKTAG( 'a','k','I','D'): key = "account_type"; parse = mov_metadata_int8_no_padding; break; case MKTAG( 'a','p','I','D'): key = "account_id"; break; + case MKTAG( 'a','t','I','D'): key = "artist_id"; + parse = mov_metadata_int32_no_padding; break; case MKTAG( 'c','a','t','g'): key = "category"; break; + case MKTAG( 'c','m','I','D'): key = "composer_id"; + parse = mov_metadata_int32_no_padding; break; + case MKTAG( 'c','n','I','D'): key = "content_id"; + parse = mov_metadata_int32_no_padding; break; case MKTAG( 'c','p','i','l'): key = "compilation"; parse = mov_metadata_int8_no_padding; break; case MKTAG( 'c','p','r','t'): key = "copyright"; break; @@ -380,12 +413,16 @@ static int mov_read_udta_string(MOVContext *c, AVIOContext *pb, MOVAtom atom) case MKTAG( 'e','g','i','d'): key = "episode_uid"; parse = mov_metadata_int8_no_padding; break; case MKTAG( 'F','I','R','M'): key = "firmware"; raw = 1; break; + case MKTAG( 'g','e','I','D'): key = "genre_id"; + parse = mov_metadata_int32_no_padding; break; case MKTAG( 'g','n','r','e'): key = "genre"; parse = mov_metadata_gnre; break; case MKTAG( 'h','d','v','d'): key = "hd_video"; parse = mov_metadata_int8_no_padding; break; case MKTAG( 'H','M','M','T'): return mov_metadata_hmmt(c, pb, atom.size); + case MKTAG( 'i','t','n','u'): key = "itunes_u"; + parse = mov_metadata_int8_no_padding; break; case MKTAG( 'k','e','y','w'): key = "keywords"; break; case MKTAG( 'l','d','e','s'): key = "synopsis"; break; case MKTAG( 'l','o','c','i'): @@ -396,9 +433,16 @@ static int mov_read_udta_string(MOVContext *c, AVIOContext *pb, MOVAtom atom) parse = mov_metadata_int8_no_padding; break; case MKTAG( 'p','g','a','p'): key = "gapless_playback"; parse = mov_metadata_int8_no_padding; break; + case MKTAG( 'p','l','I','D'): key = "playlist_id"; + parse = mov_metadata_int64_no_padding; break; case MKTAG( 'p','u','r','d'): key = "purchase_date"; break; case MKTAG( 'r','t','n','g'): key = "rating"; parse = mov_metadata_int8_no_padding; break; + case MKTAG( 's','f','I','D'): key = "itunes_country"; + parse = mov_metadata_int32_no_padding; break; + case MKTAG( 's','d','e','s'): key = "series_description"; break; + case MKTAG( 's','h','w','m'): key = "show_work_and_movement"; + parse = mov_metadata_int8_no_padding; break; case MKTAG( 's','o','a','a'): key = "sort_album_artist"; break; case MKTAG( 's','o','a','l'): key = "sort_album"; break; case MKTAG( 's','o','a','r'): key = "sort_artist"; break; @@ -407,6 +451,8 @@ static int mov_read_udta_string(MOVContext *c, AVIOContext *pb, MOVAtom atom) case MKTAG( 's','o','s','n'): key = "sort_show"; break; case MKTAG( 's','t','i','k'): key = "media_type"; parse = mov_metadata_int8_no_padding; break; + case MKTAG( 't','m','p','o'): key = "tmpo"; + parse = mov_metadata_int16_no_padding; break; case MKTAG( 't','r','k','n'): key = "track"; parse = mov_metadata_track_or_disc_number; break; case MKTAG( 't','v','e','n'): key = "episode_id"; break; @@ -416,17 +462,23 @@ static int mov_read_udta_string(MOVContext *c, AVIOContext *pb, MOVAtom atom) case MKTAG( 't','v','s','h'): key = "show"; break; case MKTAG( 't','v','s','n'): key = "season_number"; parse = mov_metadata_int8_bypass_padding; break; + case MKTAG( 'x','i','d',' '): key = "xid"; break; case MKTAG(0xa9,'A','R','T'): key = "artist"; break; case MKTAG(0xa9,'P','R','D'): key = "producer"; break; case MKTAG(0xa9,'a','l','b'): key = "album"; break; - case MKTAG(0xa9,'a','u','t'): key = "artist"; break; + case MKTAG(0xa9,'a','r','d'): key = "art_director"; break; + case MKTAG(0xa9,'a','r','g'): key = "arranger"; break; + case MKTAG(0xa9,'a','u','t'): key = "author"; break; + case MKTAG(0xa9,'c','a','k'): key = "acknowledgement"; break; case MKTAG(0xa9,'c','h','p'): key = "chapter"; break; case MKTAG(0xa9,'c','m','t'): key = "comment"; break; case MKTAG(0xa9,'c','o','m'): key = "composer"; break; + case MKTAG(0xa9,'c','o','n'): key = "conductor"; break; case MKTAG(0xa9,'c','p','y'): key = "copyright"; break; case MKTAG(0xa9,'d','a','y'): key = "date"; break; case MKTAG(0xa9,'d','i','r'): key = "director"; break; case MKTAG(0xa9,'d','i','s'): key = "disclaimer"; break; + case MKTAG(0xa9,'d','e','s'): key = "song_description"; break; case MKTAG(0xa9,'e','d','1'): key = "edit_date"; break; case MKTAG(0xa9,'e','n','c'): key = "encoder"; break; case MKTAG(0xa9,'f','m','t'): key = "original_format"; break; @@ -434,22 +486,35 @@ static int mov_read_udta_string(MOVContext *c, AVIOContext *pb, MOVAtom atom) case MKTAG(0xa9,'g','r','p'): key = "grouping"; break; case MKTAG(0xa9,'h','s','t'): key = "host_computer"; break; case MKTAG(0xa9,'i','n','f'): key = "comment"; break; + case MKTAG(0xa9,'l','n','t'): key = "linear_notes"; break; case MKTAG(0xa9,'l','y','r'): key = "lyrics"; break; case MKTAG(0xa9,'m','a','k'): key = "make"; break; case MKTAG(0xa9,'m','o','d'): key = "model"; break; + case MKTAG(0xa9,'m','v','n'): key = "movement_name"; break; + case MKTAG(0xa9,'m','v','i'): key = "movement_number"; + parse = mov_metadata_int16_no_padding; break; + case MKTAG(0xa9,'m','v','c'): key = "movement_count"; + parse = mov_metadata_int16_no_padding; break; case MKTAG(0xa9,'n','a','m'): key = "title"; break; case MKTAG(0xa9,'o','p','e'): key = "original_artist"; break; + case MKTAG(0xa9,'p','h','g'): key = "phonogram_rights"; break; case MKTAG(0xa9,'p','r','d'): key = "producer"; break; case MKTAG(0xa9,'p','r','f'): key = "performers"; break; + case MKTAG(0xa9,'p','u','b'): key = "publisher"; break; case MKTAG(0xa9,'r','e','q'): key = "playback_requirements"; break; + case MKTAG(0xa9,'s','n','e'): key = "sound_engineer"; break; + case MKTAG(0xa9,'s','o','l'): key = "soloist"; break; case MKTAG(0xa9,'s','r','c'): key = "original_source"; break; case MKTAG(0xa9,'s','t','3'): key = "subtitle"; break; case MKTAG(0xa9,'s','w','r'): key = "encoder"; break; + case MKTAG(0xa9,'t','h','x'): key = "thanks"; break; case MKTAG(0xa9,'t','o','o'): key = "encoder"; break; case MKTAG(0xa9,'t','r','k'): key = "track"; break; case MKTAG(0xa9,'u','r','l'): key = "URL"; break; + case MKTAG(0xa9,'w','r','k'): key = "work_name"; break; case MKTAG(0xa9,'w','r','n'): key = "warning"; break; case MKTAG(0xa9,'w','r','t'): key = "composer"; break; + case MKTAG(0xa9,'x','p','d'): key = "executive_producer"; break; case MKTAG(0xa9,'x','y','z'): key = "location"; break; } retry: diff --git a/libavformat/movenc.c b/libavformat/movenc.c index 76dce9e6e5..740dbfef5f 100644 --- a/libavformat/movenc.c +++ b/libavformat/movenc.c @@ -4518,15 +4518,19 @@ static int mov_write_int8_metadata(AVFormatContext *s, AVIOContext *pb, int len) { AVDictionaryEntry *t = NULL; - uint8_t num; + uint64_t num; int size = 24 + len; - if (len != 1 && len != 4) + if (len != 1 && len != 4 && + len != 2 && len != 8) return -1; if (!(t = av_dict_get(s->metadata, tag, NULL, 0))) return 0; - num = atoi(t->value); + if (len <= 4) + num = atoi(t->value); + else + num = atol(t->value); avio_wb32(pb, size); ffio_wfourcc(pb, name); @@ -4534,7 +4538,9 @@ static int mov_write_int8_metadata(AVFormatContext *s, AVIOContext *pb, ffio_wfourcc(pb, "data"); avio_wb32(pb, 0x15); avio_wb32(pb, 0); - if (len==4) avio_wb32(pb, num); + if (len==8) avio_wb64(pb, num); + else if (len==4) avio_wb32(pb, num); + else if (len==2) avio_wb16(pb, num); else avio_w8 (pb, num); return size; @@ -4590,6 +4596,8 @@ static int mov_write_ilst_tag(AVIOContext *pb, MOVMuxContext *mov, mov_write_string_metadata(s, pb, "\251lyr", "lyrics" , 1); mov_write_string_metadata(s, pb, "desc", "description",1); mov_write_string_metadata(s, pb, "ldes", "synopsis" , 1); + mov_write_string_metadata(s, pb, "sdes", "series_description", 1); + mov_write_string_metadata(s, pb, "rtng", "rating", 1); mov_write_string_metadata(s, pb, "tvsh", "show" , 1); mov_write_string_metadata(s, pb, "tven", "episode_id",1); mov_write_string_metadata(s, pb, "tvnn", "network" , 1); @@ -4600,6 +4608,59 @@ static int mov_write_ilst_tag(AVIOContext *pb, MOVMuxContext *mov, mov_write_int8_metadata (s, pb, "hdvd", "hd_video", 1); mov_write_int8_metadata (s, pb, "pgap", "gapless_playback",1); mov_write_int8_metadata (s, pb, "cpil", "compilation", 1); + + mov_write_string_metadata(s, pb, "\251st3", "subtitle" , 1); + mov_write_string_metadata(s, pb, "\251des", "song_description", 1); + mov_write_string_metadata(s, pb, "\251ard", "art_director" , 1); + mov_write_string_metadata(s, pb, "\251arg", "arranger" , 1); + mov_write_string_metadata(s, pb, "\251aut", "author" , 1); + mov_write_string_metadata(s, pb, "\251cak", "acknowledgement" , 1); + mov_write_string_metadata(s, pb, "\251con", "conductor" , 1); + + mov_write_string_metadata(s, pb, "\251wrk", "work_name" , 1); + mov_write_string_metadata(s, pb, "\251mvn", "movement_name" , 1); + mov_write_int8_metadata (s, pb, "\251mvi", "movement_number", 2); + mov_write_int8_metadata (s, pb, "\251mvc", "movement_count" , 2); + mov_write_int8_metadata (s, pb, "shwm", "show_work_and_movement", 1); + + mov_write_string_metadata(s, pb, "\251lnt", "linear_notes" , 1); + mov_write_string_metadata(s, pb, "\251mak", "make" , 1); // Record company + mov_write_string_metadata(s, pb, "\251ope", "original_artist" , 1); + mov_write_string_metadata(s, pb, "\251phg", "phonogram_rights" , 1); + mov_write_string_metadata(s, pb, "\251prd", "producer" , 1); + mov_write_string_metadata(s, pb, "\251prf", "performers" , 1); + mov_write_string_metadata(s, pb, "\251pub", "publisher" , 1); + mov_write_string_metadata(s, pb, "\251sne", "sound_engineer" , 1); + mov_write_string_metadata(s, pb, "\251sol", "soloist" , 1); + mov_write_string_metadata(s, pb, "\251src", "original_source" , 1); // Credits + mov_write_string_metadata(s, pb, "\251thx", "thanks" , 1); + mov_write_string_metadata(s, pb, "\251url", "URL" , 1); // Online extras + mov_write_string_metadata(s, pb, "\251xpd", "executive_producer", 1); + + mov_write_string_metadata(s, pb, "sonm", "sort_name" , 1); + mov_write_string_metadata(s, pb, "soar", "sort_artist" , 1); + mov_write_string_metadata(s, pb, "soaa", "sort_album_artist", 1); + mov_write_string_metadata(s, pb, "soal", "sort_album" , 1); + mov_write_string_metadata(s, pb, "soco", "sort_composer" , 1); + mov_write_string_metadata(s, pb, "sosn", "sort_show" , 1); + + mov_write_string_metadata(s, pb, "\251enc", "encoder" , 1); // Encoded by + mov_write_string_metadata(s, pb, "purd", "purchase_date" , 1); + + mov_write_int8_metadata (s, pb, "itnu", "itunes_u" , 1); + mov_write_int8_metadata (s, pb, "pcst", "podcast" , 1); + mov_write_string_metadata(s, pb, "catg", "category" , 1); + + mov_write_string_metadata(s, pb, "apID", "account_id" , 1); + mov_write_int8_metadata (s, pb, "akID", "account_type" , 1); + mov_write_int8_metadata (s, pb, "sfID", "itunes_country", 4); + mov_write_int8_metadata (s, pb, "cnID", "content_id" , 4); + mov_write_int8_metadata (s, pb, "atID", "artist_id" , 4); + mov_write_int8_metadata (s, pb, "plID", "playlist_id" , 8); + mov_write_int8_metadata (s, pb, "geID", "genre_id" , 4); + mov_write_int8_metadata (s, pb, "cmID", "composer_id" , 4); + mov_write_string_metadata(s, pb, "xid ", "xid" , 1); + mov_write_covr(pb, s); mov_write_trkn_tag(pb, mov, s, 0); // track number mov_write_trkn_tag(pb, mov, s, 1); // disc number diff --git a/tests/ref/fate/caf-alac-remux b/tests/ref/fate/caf-alac-remux index f33182b721..7ad9cf74a1 100644 --- a/tests/ref/fate/caf-alac-remux +++ b/tests/ref/fate/caf-alac-remux @@ -1,5 +1,5 @@ -9ef40186fb3e24789df03f8c08110486 *tests/data/fate/caf-alac-remux.caf -1292684 tests/data/fate/caf-alac-remux.caf +a49516372a950d86498ceba69709c589 *tests/data/fate/caf-alac-remux.caf +1292691 tests/data/fate/caf-alac-remux.caf #extradata 0: 36, 0x562b05d8 #tb 0: 1/44100 #media_type 0: audio @@ -17,6 +17,7 @@ TAG:disc=1 TAG:title=Inside TAG:compilation=1 TAG:gapless_playback=0 +TAG:tmpo=0 TAG:genre=Rock TAG:Encoding Params=vers TAG:iTunNORM= 000004DF 000004C2 00001E64 00001AB3 00000FB9 00000FB9 00006480 00006480 00000FB9 00000B52 diff --git a/tests/ref/fate/cover-art-flac-remux b/tests/ref/fate/cover-art-flac-remux index fa91975881..0eb42cb26c 100644 --- a/tests/ref/fate/cover-art-flac-remux +++ b/tests/ref/fate/cover-art-flac-remux @@ -1,5 +1,5 @@ -6defc5081a59ab12c8a5f9e263b25068 *tests/data/fate/cover-art-flac-remux.flac -1098537 tests/data/fate/cover-art-flac-remux.flac +51e126d3ea5cadbfcc0ad039a6fb61cb *tests/data/fate/cover-art-flac-remux.flac +1098547 tests/data/fate/cover-art-flac-remux.flac #extradata 0: 34, 0x8d830abd #tb 0: 1/44100 #media_type 0: audio @@ -97,6 +97,7 @@ TAG:disc=1 TAG:title=Inside TAG:compilation=1 TAG:gapless_playback=0 +TAG:tmpo=0 TAG:track=5/13 TAG:Encoding Params=vers TAG:iTunNORM= 000004DF 000004C2 00001E64 00001AB3 00000FB9 00000FB9 00006480 00006480 00000FB9 00000B52 diff --git a/tests/ref/fate/matroska-alac-remux b/tests/ref/fate/matroska-alac-remux index 9b73263acd..4e730bb21c 100644 --- a/tests/ref/fate/matroska-alac-remux +++ b/tests/ref/fate/matroska-alac-remux @@ -1,5 +1,5 @@ -90c54a00ad8662c3eb93150791fa8328 *tests/data/fate/matroska-alac-remux.matroska -1293824 tests/data/fate/matroska-alac-remux.matroska +1b35d6d679219aa63eab3a34d2eb50dd *tests/data/fate/matroska-alac-remux.matroska +1293838 tests/data/fate/matroska-alac-remux.matroska #extradata 0: 36, 0x562b05d8 #tb 0: 1/1000 #media_type 0: audio @@ -165,6 +165,7 @@ TAG:COMPATIBLE_BRANDS=M4A mp42isom TAG:DISC=1 TAG:COMPILATION=1 TAG:GAPLESS_PLAYBACK=0 +TAG:TMPO=0 TAG:ENCODING_PARAMS=vers TAG:ITUNNORM= 000004DF 000004C2 00001E64 00001AB3 00000FB9 00000FB9 00006480 00006480 00000FB9 00000B52 TAG:ARTIST=Maxwell Strait diff --git a/tests/ref/fate/mov-cover-image b/tests/ref/fate/mov-cover-image index 5f65c630ea..59941de7dd 100644 --- a/tests/ref/fate/mov-cover-image +++ b/tests/ref/fate/mov-cover-image @@ -1,5 +1,5 @@ -54a8870d5d1e6cc4da28ae422aa70898 *tests/data/fate/mov-cover-image.mp4 -1011919 tests/data/fate/mov-cover-image.mp4 +33e107deb271ca991acf8336d01785e1 *tests/data/fate/mov-cover-image.mp4 +1011945 tests/data/fate/mov-cover-image.mp4 #extradata 0: 2, 0x00340022 #tb 0: 1/44100 #media_type 0: audio -- 2.39.5 (Apple Git-154) _______________________________________________ ffmpeg-devel mailing list ffmpeg-devel@ffmpeg.org https://ffmpeg.org/mailman/listinfo/ffmpeg-devel To unsubscribe, visit link above, or email ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".
reply other threads:[~2025-02-12 16:00 UTC|newest] Thread overview: [no followups] expand[flat|nested] mbox.gz Atom feed
Reply instructions: You may reply publicly to this message via plain-text email using any one of the following methods: * Save the following mbox file, import it into your mail client, and reply-to-all from there: mbox Avoid top-posting and favor interleaved quoting: https://en.wikipedia.org/wiki/Posting_style#Interleaved_style * Reply using the --to, --cc, and --in-reply-to switches of git-send-email(1): git send-email \ --in-reply-to=20250212160025.67493-1-damiog@gmail.com \ --to=damiog@gmail.com \ --cc=ffmpeg-devel@ffmpeg.org \ /path/to/YOUR_REPLY https://kernel.org/pub/software/scm/git/docs/git-send-email.html * If your mail client supports setting the In-Reply-To header via mailto: links, try the mailto: link
Git Inbox Mirror of the ffmpeg-devel mailing list - see https://ffmpeg.org/mailman/listinfo/ffmpeg-devel This inbox may be cloned and mirrored by anyone: git clone --mirror https://master.gitmailbox.com/ffmpegdev/0 ffmpegdev/git/0.git # If you have public-inbox 1.1+ installed, you may # initialize and index your mirror using the following commands: public-inbox-init -V2 ffmpegdev ffmpegdev/ https://master.gitmailbox.com/ffmpegdev \ ffmpegdev@gitmailbox.com public-inbox-index ffmpegdev Example config snippet for mirrors. AGPL code for this site: git clone https://public-inbox.org/public-inbox.git