Git Inbox Mirror of the ffmpeg-devel mailing list - see https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
 help / color / mirror / Atom feed
* [FFmpeg-devel] [PATCH] avformat/mov: read and write additional iTunes style metadata
@ 2025-02-12 16:00 Damiano Galassi
  0 siblings, 0 replies; only message in thread
From: Damiano Galassi @ 2025-02-12 16:00 UTC (permalink / raw)
  To: ffmpeg-devel; +Cc: Damiano Galassi

---
 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".

^ permalink raw reply	[flat|nested] only message in thread

only message in thread, other threads:[~2025-02-12 16:00 UTC | newest]

Thread overview: (only message) (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2025-02-12 16:00 [FFmpeg-devel] [PATCH] avformat/mov: read and write additional iTunes style metadata Damiano Galassi

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