[multimedia/kid3] /: Matroska support (#435)
Urs Fleisch
null at kde.org
Wed Jan 7 05:16:09 GMT 2026
Git commit 047b22e71d6db70632a66a7719115010b670dbc0 by Urs Fleisch.
Committed on 07/01/2026 at 05:12.
Pushed by ufleisch into branch 'master'.
Matroska support (#435)
BUG 432311
M +56 -50 doc/en/index.docbook
M +45 -1 src/core/tags/frame.cpp
M +18 -0 src/core/tags/frame.h
M +1 -1 src/core/tags/pictureframe.cpp
M +77 -0 src/gui/dialogs/editframefieldsdialog.cpp
M +5 -0 src/plugins/taglibmetadata/CMakeLists.txt
M +6 -0 src/plugins/taglibmetadata/taglibfile.cpp
M +6 -0 src/plugins/taglibmetadata/taglibfileiostream.cpp
A +802 -0 src/plugins/taglibmetadata/taglibmatroskasupport.cpp [License: GPL (v2+)]
A +56 -0 src/plugins/taglibmetadata/taglibmatroskasupport.h [License: GPL (v2+)]
https://invent.kde.org/multimedia/kid3/-/commit/047b22e71d6db70632a66a7719115010b670dbc0
diff --git a/doc/en/index.docbook b/doc/en/index.docbook
index 6dbadcf5..a89fa38d 100644
--- a/doc/en/index.docbook
+++ b/doc/en/index.docbook
@@ -784,58 +784,58 @@ format specific frames.
<title>Mapping of Unified Frame Types to Various Formats</title>
<tgroup cols="7">
<thead>
- <row><entry>Unified</entry> <entry>ID3v2.3</entry> <entry>ID3v2.4</entry> <entry>MP4</entry> <entry>ASF</entry> <entry>Vorbis</entry> <entry>RIFF</entry></row>
+ <row><entry>Unified</entry> <entry>ID3v2.3</entry> <entry>ID3v2.4</entry> <entry>MP4</entry> <entry>ASF</entry> <entry>Vorbis</entry> <entry>RIFF</entry> <entry>Matroska</entry></row>
</thead>
<tbody>
- <row><entry>Title</entry> <entry><literal>TIT2</literal></entry> <entry><literal>TIT2</literal></entry> <entry><literal>©nam</literal></entry> <entry><literal>Title</literal></entry> <entry><literal>TITLE</literal></entry> <entry><literal>INAM</literal></entry></row>
- <row><entry>Artist</entry> <entry><literal>TPE1</literal></entry> <entry><literal>TPE1</literal></entry> <entry><literal>©ART</literal></entry> <entry><literal>Author</literal></entry> <entry><literal>ARTIST</literal></entry> <entry><literal>IART</literal></entry></row>
- <row><entry>Album</entry> <entry><literal>TALB</literal></entry> <entry><literal>TALB</literal></entry> <entry><literal>©alb</literal></entry> <entry><literal>WM/AlbumTitle</literal></entry> <entry><literal>ALBUM</literal></entry> <entry><literal>IPRD</literal></entry></row>
- <row><entry>Comment</entry> <entry><literal>COMM</literal></entry> <entry><literal>COMM</literal></entry> <entry><literal>©cmt</literal></entry> <entry><literal>Description</literal></entry> <entry><literal>COMMENT</literal></entry> <entry><literal>ICMT</literal></entry></row>
- <row><entry>Date</entry> <entry><literal>TYER</literal></entry> <entry><literal>TDRC</literal></entry> <entry><literal>©day</literal></entry> <entry><literal>WM/Year</literal></entry> <entry><literal>DATE</literal></entry> <entry><literal>ICRD</literal></entry></row>
- <row><entry>Track Number</entry> <entry><literal>TRCK</literal></entry> <entry><literal>TRCK</literal></entry> <entry><literal>trkn</literal></entry> <entry><literal>WM/TrackNumber</literal></entry> <entry><literal>TRACKNUMBER</literal></entry> <entry><literal>IPRT</literal> or <literal>ITRK</literal></entry></row>
- <row><entry>Genre</entry> <entry><literal>TCON</literal></entry> <entry><literal>TCON</literal></entry> <entry><literal>©gen</literal></entry> <entry><literal>WM/Genre</literal></entry> <entry><literal>GENRE</literal></entry> <entry><literal>IGNR</literal></entry></row>
- <row><entry>Album Artist</entry> <entry><literal>TPE2</literal></entry> <entry><literal>TPE2</literal></entry> <entry><literal>aART</literal></entry> <entry><literal>WM/AlbumArtist</literal></entry> <entry><literal>ALBUMARTIST</literal></entry> <entry></entry></row>
- <row><entry>Arranger</entry> <entry><literal>IPLS</literal></entry> <entry><literal>TIPL</literal></entry> <entry><literal>ARRANGER</literal></entry> <entry><literal>WM/Producer</literal></entry> <entry><literal>ARRANGER</literal></entry> <entry><literal>IENG</literal></entry></row>
- <row><entry>Author</entry> <entry><literal>TOLY</literal></entry> <entry><literal>TOLY</literal></entry> <entry><literal>AUTHOR</literal></entry> <entry></entry> <entry><literal>AUTHOR</literal></entry> <entry></entry></row>
- <row><entry>BPM</entry> <entry><literal>TBPM</literal></entry> <entry><literal>TBPM</literal></entry> <entry><literal>tmpo</literal></entry> <entry><literal>WM/BeatsPerMinute</literal></entry> <entry><literal>BPM</literal></entry> <entry><literal>IBPM</literal></entry></row>
- <row><entry>Catalog Number</entry> <entry><literal>TXXX:CATALOGNUMBER</literal></entry> <entry><literal>TXXX:CATALOGNUMBER</literal></entry> <entry></entry> <entry></entry> <entry><literal>CATALOGNUMBER</literal></entry> <entry></entry></row>
- <row><entry>Compilation</entry> <entry><literal>TCMP</literal></entry> <entry><literal>TCMP</literal></entry> <entry><literal>cpil</literal></entry> <entry></entry> <entry><literal>COMPILATION</literal></entry> <entry></entry></row>
- <row><entry>Composer</entry> <entry><literal>TCOM</literal></entry> <entry><literal>TCOM</literal></entry> <entry><literal>©wrt</literal></entry> <entry><literal>WM/Composer</literal></entry> <entry><literal>COMPOSER</literal></entry> <entry><literal>IMUS</literal></entry></row>
- <row><entry>Conductor</entry> <entry><literal>TPE3</literal></entry> <entry><literal>TPE3</literal></entry> <entry><literal>CONDUCTOR</literal></entry> <entry><literal>WM/Conductor</literal></entry> <entry><literal>CONDUCTOR</literal></entry> <entry></entry></row>
- <row><entry>Copyright</entry> <entry><literal>TCOP</literal></entry> <entry><literal>TCOP</literal></entry> <entry><literal>cprt</literal></entry> <entry><literal>Copyright</literal></entry> <entry><literal>COPYRIGHT</literal></entry> <entry><literal>ICOP</literal></entry></row>
- <row><entry>Description</entry> <entry><literal>TIT3</literal></entry> <entry><literal>TIT3</literal></entry> <entry><literal>desc</literal></entry> <entry><literal>WM/SubTitleDescription</literal></entry> <entry><literal>DESCRIPTION</literal></entry> <entry></entry></row>
- <row><entry>Disc Number</entry> <entry><literal>TPOS</literal></entry> <entry><literal>TPOS</literal></entry> <entry><literal>disk</literal></entry> <entry><literal>WM/PartOfSet</literal></entry> <entry><literal>DISCNUMBER</literal></entry> <entry></entry></row>
- <row><entry>Encoded-by</entry> <entry><literal>TENC</literal></entry> <entry><literal>TENC</literal></entry> <entry><literal>©enc</literal></entry> <entry><literal>WM/EncodedBy</literal></entry> <entry><literal>ENCODED-BY</literal></entry> <entry><literal>ITCH</literal></entry></row>
- <row><entry>Encoder Settings</entry> <entry><literal>TSSE</literal></entry> <entry><literal>TSSE</literal></entry> <entry><literal>©too</literal></entry> <entry><literal>WM/EncodingSettings</literal></entry> <entry><literal>ENCODERSETTINGS</literal></entry> <entry><literal>ISFT</literal></entry></row>
- <row><entry>Encoding Time</entry> <entry></entry> <entry><literal>TDEN</literal></entry> <entry></entry> <entry><literal>WM/EncodingTime</literal></entry> <entry><literal>ENCODINGTIME</literal></entry> <entry><literal>IDIT</literal></entry></row>
- <row><entry>Grouping</entry> <entry><literal>GRP1</literal></entry> <entry><literal>GRP1</literal></entry> <entry><literal>©grp</literal></entry> <entry></entry> <entry><literal>GROUPING</literal></entry> <entry></entry></row>
- <row><entry>Initial Key</entry> <entry><literal>TKEY</literal></entry> <entry><literal>TKEY</literal></entry> <entry></entry> <entry><literal>WM/InitialKey</literal></entry> <entry><literal>INITIALKEY</literal></entry> <entry></entry></row>
- <row><entry>ISRC</entry> <entry><literal>TSRC</literal></entry> <entry><literal>TSRC</literal></entry> <entry><literal>ISRC</literal></entry> <entry><literal>WM/ISRC</literal></entry> <entry><literal>ISRC</literal></entry> <entry><literal>ISRC</literal></entry></row>
- <row><entry>Language</entry> <entry><literal>TLAN</literal></entry> <entry><literal>TLAN</literal></entry> <entry><literal>LANGUAGE</literal></entry> <entry><literal>WM/Language</literal></entry> <entry><literal>LANGUAGE</literal></entry> <entry><literal>ILNG</literal></entry></row>
- <row><entry>Lyricist</entry> <entry><literal>TEXT</literal></entry> <entry><literal>TEXT</literal></entry> <entry><literal>LYRICIST</literal></entry> <entry><literal>WM/Writer</literal></entry> <entry><literal>LYRICIST</literal></entry> <entry><literal>IWRI</literal></entry></row>
- <row><entry>Lyrics</entry> <entry><literal>USLT</literal></entry> <entry><literal>USLT</literal></entry> <entry><literal>©lyr</literal></entry> <entry><literal>WM/Lyrics</literal></entry> <entry><literal>LYRICS</literal></entry> <entry></entry></row>
- <row><entry>Media</entry> <entry><literal>TMED</literal></entry> <entry><literal>TMED</literal></entry> <entry><literal>SOURCEMEDIA</literal></entry> <entry></entry> <entry><literal>SOURCEMEDIA</literal></entry> <entry><literal>IMED</literal></entry></row>
- <row><entry>Mood</entry> <entry></entry> <entry><literal>TMOO</literal></entry> <entry></entry> <entry><literal>WM/Mood</literal></entry> <entry><literal>MOOD</literal></entry> <entry></entry></row>
- <row><entry>Original Album</entry> <entry><literal>TOAL</literal></entry> <entry><literal>TOAL</literal></entry> <entry><literal>ORIGINALALBUM</literal></entry> <entry><literal>WM/OriginalAlbumTitle</literal></entry> <entry><literal>ORIGINALALBUM</literal></entry> <entry></entry></row>
- <row><entry>Original Artist</entry> <entry><literal>TOPE</literal></entry> <entry><literal>TOPE</literal></entry> <entry><literal>ORIGINALARTIST</literal></entry> <entry><literal>WM/OriginalArtist</literal></entry> <entry><literal>ORIGINALARTIST</literal></entry> <entry></entry></row>
- <row><entry>Original Date</entry> <entry><literal>TORY</literal></entry> <entry><literal>TDOR</literal></entry> <entry><literal>ORIGINALDATE</literal></entry> <entry><literal>WM/OriginalReleaseYear</literal></entry> <entry><literal>ORIGINALDATE</literal></entry> <entry></entry></row>
- <row><entry>Performer</entry> <entry><literal>IPLS</literal></entry> <entry><literal>TMCL</literal></entry> <entry><literal>PERFORMER</literal></entry> <entry></entry> <entry><literal>PERFORMER</literal></entry> <entry><literal>ISTR</literal></entry></row>
- <row><entry>Picture</entry> <entry><literal>APIC</literal></entry> <entry><literal>APIC</literal></entry> <entry><literal>covr</literal></entry> <entry><literal>WM/Picture</literal></entry> <entry><literal>METADATA_BLOCK_PICTURE</literal></entry> <entry></entry></row>
- <row><entry>Publisher</entry> <entry><literal>TPUB</literal></entry> <entry><literal>TPUB</literal></entry> <entry><literal>PUBLISHER</literal></entry> <entry><literal>WM/Publisher</literal></entry> <entry><literal>PUBLISHER</literal></entry> <entry><literal>IPUB</literal></entry></row>
- <row><entry>Rating</entry> <entry><literal>POPM</literal></entry> <entry><literal>POPM</literal></entry> <entry><literal>rate</literal></entry> <entry><literal>WM/SharedUserRating</literal></entry> <entry><literal>RATING</literal></entry> <entry><literal>IRTD</literal></entry></row>
- <row><entry>Release Country</entry> <entry><literal>TXXX:RELEASECOUNTRY</literal></entry> <entry><literal>TXXX:RELEASECOUNTRY</literal></entry> <entry></entry> <entry></entry> <entry><literal>RELEASECOUNTRY</literal></entry> <entry><literal>ICNT</literal></entry></row>
- <row><entry>Release Date</entry> <entry></entry> <entry><literal>TDRL</literal></entry> <entry><literal>RELEASEDATE</literal></entry> <entry></entry> <entry><literal>RELEASEDATE</literal></entry> <entry></entry></row>
- <row><entry>Remixer</entry> <entry><literal>TPE4</literal></entry> <entry><literal>TPE4</literal></entry> <entry><literal>REMIXER</literal></entry> <entry><literal>WM/ModifiedBy</literal></entry> <entry><literal>REMIXER</literal></entry> <entry><literal>IEDT</literal></entry></row>
- <row><entry>Sort Album</entry> <entry><literal>TSOA</literal></entry> <entry><literal>TSOA</literal></entry> <entry><literal>soal</literal></entry> <entry><literal>WM/AlbumSortOrder</literal></entry> <entry><literal>ALBUMSORT</literal></entry> <entry></entry></row>
- <row><entry>Sort Album Artist</entry> <entry><literal>TSO2</literal></entry> <entry><literal>TSO2</literal></entry> <entry><literal>soaa</literal></entry> <entry></entry> <entry><literal>ALBUMARTISTSORT</literal></entry> <entry></entry></row>
- <row><entry>Sort Artist</entry> <entry><literal>TSOP</literal></entry> <entry><literal>TSOP</literal></entry> <entry><literal>soar</literal></entry> <entry><literal>WM/ArtistSortOrder</literal></entry> <entry><literal>ARTISTSORT</literal></entry> <entry></entry></row>
- <row><entry>Sort Composer</entry> <entry><literal>TSOC</literal></entry> <entry><literal>TSOC</literal></entry> <entry><literal>soco</literal></entry> <entry></entry> <entry><literal>COMPOSERSORT</literal></entry> <entry></entry></row>
- <row><entry>Sort Name</entry> <entry><literal>TSOT</literal></entry> <entry><literal>TSOT</literal></entry> <entry><literal>sonm</literal></entry> <entry><literal>WM/TitleSortOrder</literal></entry> <entry><literal>TITLESORT</literal></entry> <entry></entry></row>
- <row><entry>Subtitle</entry> <entry></entry> <entry><literal>TSST</literal></entry> <entry><literal>SUBTITLE</literal></entry> <entry><literal>WM/SubTitle</literal></entry> <entry><literal>SUBTITLE</literal></entry> <entry><literal>PRT1</literal></entry></row>
- <row><entry>Website</entry> <entry><literal>WOAR</literal></entry> <entry><literal>WOAR</literal></entry> <entry><literal>WEBSITE</literal></entry> <entry><literal>WM/AuthorURL</literal></entry> <entry><literal>WEBSITE</literal></entry> <entry><literal>IBSU</literal></entry></row>
- <row><entry>Work</entry> <entry><literal>TIT1</literal></entry> <entry><literal>TIT1</literal></entry> <entry><literal>©wrk</literal></entry> <entry><literal>WM/ContentGroupDescription</literal></entry> <entry><literal>WORK</literal></entry> <entry></entry></row>
- <row><entry>WWW Audio File</entry> <entry><literal>WOAF</literal></entry> <entry><literal>WOAF</literal></entry> <entry></entry> <entry><literal>WM/AudioFileURL</literal></entry> <entry><literal>WWWAUDIOFILE</literal></entry> <entry></entry></row>
- <row><entry>WWW Audio Source</entry> <entry><literal>WOAS</literal></entry> <entry><literal>WOAS</literal></entry> <entry></entry> <entry><literal>WM/AudioSourceURL</literal></entry> <entry><literal>WWWAUDIOSOURCE</literal></entry> <entry></entry></row>
+ <row><entry>Title</entry> <entry><literal>TIT2</literal></entry> <entry><literal>TIT2</literal></entry> <entry><literal>©nam</literal></entry> <entry><literal>Title</literal></entry> <entry><literal>TITLE</literal></entry> <entry><literal>INAM</literal></entry> <entry><literal>TITLE</literal></entry></row>
+ <row><entry>Artist</entry> <entry><literal>TPE1</literal></entry> <entry><literal>TPE1</literal></entry> <entry><literal>©ART</literal></entry> <entry><literal>Author</literal></entry> <entry><literal>ARTIST</literal></entry> <entry><literal>IART</literal></entry> <entry><literal>ARTIST</literal></entry></row>
+ <row><entry>Album</entry> <entry><literal>TALB</literal></entry> <entry><literal>TALB</literal></entry> <entry><literal>©alb</literal></entry> <entry><literal>WM/AlbumTitle</literal></entry> <entry><literal>ALBUM</literal></entry> <entry><literal>IPRD</literal></entry> <entry><literal>TITLE/50</literal></entry></row>
+ <row><entry>Comment</entry> <entry><literal>COMM</literal></entry> <entry><literal>COMM</literal></entry> <entry><literal>©cmt</literal></entry> <entry><literal>Description</literal></entry> <entry><literal>COMMENT</literal></entry> <entry><literal>ICMT</literal></entry> <entry><literal>COMMENT</literal></entry></row>
+ <row><entry>Date</entry> <entry><literal>TYER</literal></entry> <entry><literal>TDRC</literal></entry> <entry><literal>©day</literal></entry> <entry><literal>WM/Year</literal></entry> <entry><literal>DATE</literal></entry> <entry><literal>ICRD</literal></entry> <entry><literal>DATE_RECORDED</literal></entry></row>
+ <row><entry>Track Number</entry> <entry><literal>TRCK</literal></entry> <entry><literal>TRCK</literal></entry> <entry><literal>trkn</literal></entry> <entry><literal>WM/TrackNumber</literal></entry> <entry><literal>TRACKNUMBER</literal></entry> <entry><literal>IPRT</literal> or <literal>ITRK</literal></entry> <entry><literal>PART_NUMBER</literal></entry></row>
+ <row><entry>Genre</entry> <entry><literal>TCON</literal></entry> <entry><literal>TCON</literal></entry> <entry><literal>©gen</literal></entry> <entry><literal>WM/Genre</literal></entry> <entry><literal>GENRE</literal></entry> <entry><literal>IGNR</literal></entry> <entry><literal>GENRE</literal></entry></row>
+ <row><entry>Album Artist</entry> <entry><literal>TPE2</literal></entry> <entry><literal>TPE2</literal></entry> <entry><literal>aART</literal></entry> <entry><literal>WM/AlbumArtist</literal></entry> <entry><literal>ALBUMARTIST</literal></entry> <entry></entry> <entry><literal>ARTIST/50</literal></entry></row>
+ <row><entry>Arranger</entry> <entry><literal>IPLS</literal></entry> <entry><literal>TIPL</literal></entry> <entry><literal>ARRANGER</literal></entry> <entry><literal>WM/Producer</literal></entry> <entry><literal>ARRANGER</literal></entry> <entry><literal>IENG</literal></entry> <entry><literal>ARRANGER</literal></entry></row>
+ <row><entry>Author</entry> <entry><literal>TOLY</literal></entry> <entry><literal>TOLY</literal></entry> <entry><literal>AUTHOR</literal></entry> <entry></entry> <entry><literal>AUTHOR</literal></entry> <entry></entry> <entry><literal>WRITTEN_BY</literal></entry></row>
+ <row><entry>BPM</entry> <entry><literal>TBPM</literal></entry> <entry><literal>TBPM</literal></entry> <entry><literal>tmpo</literal></entry> <entry><literal>WM/BeatsPerMinute</literal></entry> <entry><literal>BPM</literal></entry> <entry><literal>IBPM</literal></entry> <entry><literal>BPM</literal></entry></row>
+ <row><entry>Catalog Number</entry> <entry><literal>TXXX:CATALOGNUMBER</literal></entry> <entry><literal>TXXX:CATALOGNUMBER</literal></entry> <entry></entry> <entry></entry> <entry><literal>CATALOGNUMBER</literal></entry> <entry></entry> <entry><literal>CATALOG_NUMBER</literal></entry></row>
+ <row><entry>Compilation</entry> <entry><literal>TCMP</literal></entry> <entry><literal>TCMP</literal></entry> <entry><literal>cpil</literal></entry> <entry></entry> <entry><literal>COMPILATION</literal></entry> <entry></entry> <entry><literal>COMPILATION</literal></entry></row>
+ <row><entry>Composer</entry> <entry><literal>TCOM</literal></entry> <entry><literal>TCOM</literal></entry> <entry><literal>©wrt</literal></entry> <entry><literal>WM/Composer</literal></entry> <entry><literal>COMPOSER</literal></entry> <entry><literal>IMUS</literal></entry> <entry><literal>COMPOSER</literal></entry></row>
+ <row><entry>Conductor</entry> <entry><literal>TPE3</literal></entry> <entry><literal>TPE3</literal></entry> <entry><literal>CONDUCTOR</literal></entry> <entry><literal>WM/Conductor</literal></entry> <entry><literal>CONDUCTOR</literal></entry> <entry></entry> <entry><literal>CONDUCTOR</literal></entry></row>
+ <row><entry>Copyright</entry> <entry><literal>TCOP</literal></entry> <entry><literal>TCOP</literal></entry> <entry><literal>cprt</literal></entry> <entry><literal>Copyright</literal></entry> <entry><literal>COPYRIGHT</literal></entry> <entry><literal>ICOP</literal></entry> <entry><literal>COPYRIGHT</literal></entry></row>
+ <row><entry>Description</entry> <entry><literal>TIT3</literal></entry> <entry><literal>TIT3</literal></entry> <entry><literal>desc</literal></entry> <entry><literal>WM/SubTitleDescription</literal></entry> <entry><literal>DESCRIPTION</literal></entry> <entry></entry> <entry><literal>DESCRIPTION</literal></entry></row>
+ <row><entry>Disc Number</entry> <entry><literal>TPOS</literal></entry> <entry><literal>TPOS</literal></entry> <entry><literal>disk</literal></entry> <entry><literal>WM/PartOfSet</literal></entry> <entry><literal>DISCNUMBER</literal></entry> <entry></entry> <entry><literal>PART_NUMBER/50</literal></entry></row>
+ <row><entry>Encoded-by</entry> <entry><literal>TENC</literal></entry> <entry><literal>TENC</literal></entry> <entry><literal>©enc</literal></entry> <entry><literal>WM/EncodedBy</literal></entry> <entry><literal>ENCODED-BY</literal></entry> <entry><literal>ITCH</literal></entry> <entry><literal>ENCODER</literal></entry></row>
+ <row><entry>Encoder Settings</entry> <entry><literal>TSSE</literal></entry> <entry><literal>TSSE</literal></entry> <entry><literal>©too</literal></entry> <entry><literal>WM/EncodingSettings</literal></entry> <entry><literal>ENCODERSETTINGS</literal></entry> <entry><literal>ISFT</literal></entry> <entry><literal>ENCODER_SETTINGS</literal></entry></row>
+ <row><entry>Encoding Time</entry> <entry></entry> <entry><literal>TDEN</literal></entry> <entry></entry> <entry><literal>WM/EncodingTime</literal></entry> <entry><literal>ENCODINGTIME</literal></entry> <entry><literal>IDIT</literal></entry> <entry><literal>DATE_ENCODED</literal></entry></row>
+ <row><entry>Grouping</entry> <entry><literal>GRP1</literal></entry> <entry><literal>GRP1</literal></entry> <entry><literal>©grp</literal></entry> <entry></entry> <entry><literal>GROUPING</literal></entry> <entry></entry> <entry><literal>GROUPING</literal></entry></row>
+ <row><entry>Initial Key</entry> <entry><literal>TKEY</literal></entry> <entry><literal>TKEY</literal></entry> <entry></entry> <entry><literal>WM/InitialKey</literal></entry> <entry><literal>INITIALKEY</literal></entry> <entry></entry> <entry><literal>INITIAL_KEY</literal></entry></row>
+ <row><entry>ISRC</entry> <entry><literal>TSRC</literal></entry> <entry><literal>TSRC</literal></entry> <entry><literal>ISRC</literal></entry> <entry><literal>WM/ISRC</literal></entry> <entry><literal>ISRC</literal></entry> <entry><literal>ISRC</literal></entry> <entry><literal>ISRC</literal></entry></row>
+ <row><entry>Language</entry> <entry><literal>TLAN</literal></entry> <entry><literal>TLAN</literal></entry> <entry><literal>LANGUAGE</literal></entry> <entry><literal>WM/Language</literal></entry> <entry><literal>LANGUAGE</literal></entry> <entry><literal>ILNG</literal></entry> <entry><literal>LANGUAGE</literal></entry></row>
+ <row><entry>Lyricist</entry> <entry><literal>TEXT</literal></entry> <entry><literal>TEXT</literal></entry> <entry><literal>LYRICIST</literal></entry> <entry><literal>WM/Writer</literal></entry> <entry><literal>LYRICIST</literal></entry> <entry><literal>IWRI</literal></entry> <entry><literal>LYRICIST</literal></entry></row>
+ <row><entry>Lyrics</entry> <entry><literal>USLT</literal></entry> <entry><literal>USLT</literal></entry> <entry><literal>©lyr</literal></entry> <entry><literal>WM/Lyrics</literal></entry> <entry><literal>LYRICS</literal></entry> <entry></entry> <entry><literal>LYRICS</literal></entry></row>
+ <row><entry>Media</entry> <entry><literal>TMED</literal></entry> <entry><literal>TMED</literal></entry> <entry><literal>SOURCEMEDIA</literal></entry> <entry></entry> <entry><literal>SOURCEMEDIA</literal></entry> <entry><literal>IMED</literal></entry> <entry><literal>ORIGINAL_MEDIA_TYPE</literal></entry></row>
+ <row><entry>Mood</entry> <entry></entry> <entry><literal>TMOO</literal></entry> <entry></entry> <entry><literal>WM/Mood</literal></entry> <entry><literal>MOOD</literal></entry> <entry></entry> <entry><literal>MOOD</literal></entry></row>
+ <row><entry>Original Album</entry> <entry><literal>TOAL</literal></entry> <entry><literal>TOAL</literal></entry> <entry><literal>ORIGINALALBUM</literal></entry> <entry><literal>WM/OriginalAlbumTitle</literal></entry> <entry><literal>ORIGINALALBUM</literal></entry> <entry></entry> <entry><literal>ORIGINALALBUM</literal></entry></row>
+ <row><entry>Original Artist</entry> <entry><literal>TOPE</literal></entry> <entry><literal>TOPE</literal></entry> <entry><literal>ORIGINALARTIST</literal></entry> <entry><literal>WM/OriginalArtist</literal></entry> <entry><literal>ORIGINALARTIST</literal></entry> <entry></entry> <entry><literal>ORIGINALARTIST</literal></entry></row>
+ <row><entry>Original Date</entry> <entry><literal>TORY</literal></entry> <entry><literal>TDOR</literal></entry> <entry><literal>ORIGINALDATE</literal></entry> <entry><literal>WM/OriginalReleaseYear</literal></entry> <entry><literal>ORIGINALDATE</literal></entry> <entry></entry> <entry><literal>ORIGINALDATE</literal></entry></row>
+ <row><entry>Performer</entry> <entry><literal>IPLS</literal></entry> <entry><literal>TMCL</literal></entry> <entry><literal>PERFORMER</literal></entry> <entry></entry> <entry><literal>PERFORMER</literal></entry> <entry><literal>ISTR</literal></entry> <entry><literal>PERFORMER</literal></entry></row>
+ <row><entry>Picture</entry> <entry><literal>APIC</literal></entry> <entry><literal>APIC</literal></entry> <entry><literal>covr</literal></entry> <entry><literal>WM/Picture</literal></entry> <entry><literal>METADATA_BLOCK_PICTURE</literal></entry> <entry></entry> <entry>Attachment</entry></row>
+ <row><entry>Publisher</entry> <entry><literal>TPUB</literal></entry> <entry><literal>TPUB</literal></entry> <entry><literal>PUBLISHER</literal></entry> <entry><literal>WM/Publisher</literal></entry> <entry><literal>PUBLISHER</literal></entry> <entry><literal>IPUB</literal></entry> <entry><literal>LABEL_CODE</literal></entry></row>
+ <row><entry>Rating</entry> <entry><literal>POPM</literal></entry> <entry><literal>POPM</literal></entry> <entry><literal>rate</literal></entry> <entry><literal>WM/SharedUserRating</literal></entry> <entry><literal>RATING</literal></entry> <entry><literal>IRTD</literal></entry> <entry><literal>RATING</literal></entry></row>
+ <row><entry>Release Country</entry> <entry><literal>TXXX:RELEASECOUNTRY</literal></entry> <entry><literal>TXXX:RELEASECOUNTRY</literal></entry> <entry></entry> <entry></entry> <entry><literal>RELEASECOUNTRY</literal></entry> <entry><literal>ICNT</literal></entry> <entry><literal>RELEASECOUNTRY</literal></entry></row>
+ <row><entry>Release Date</entry> <entry></entry> <entry><literal>TDRL</literal></entry> <entry><literal>RELEASEDATE</literal></entry> <entry></entry> <entry><literal>RELEASEDATE</literal></entry> <entry></entry> <entry><literal>DATE_RELEASED/50</literal></entry></row>
+ <row><entry>Remixer</entry> <entry><literal>TPE4</literal></entry> <entry><literal>TPE4</literal></entry> <entry><literal>REMIXER</literal></entry> <entry><literal>WM/ModifiedBy</literal></entry> <entry><literal>REMIXER</literal></entry> <entry><literal>IEDT</literal></entry> <entry><literal>REMIXED_BY</literal></entry></row>
+ <row><entry>Sort Album</entry> <entry><literal>TSOA</literal></entry> <entry><literal>TSOA</literal></entry> <entry><literal>soal</literal></entry> <entry><literal>WM/AlbumSortOrder</literal></entry> <entry><literal>ALBUMSORT</literal></entry> <entry></entry> <entry><literal>TITLESORT/50</literal></entry></row>
+ <row><entry>Sort Album Artist</entry> <entry><literal>TSO2</literal></entry> <entry><literal>TSO2</literal></entry> <entry><literal>soaa</literal></entry> <entry></entry> <entry><literal>ALBUMARTISTSORT</literal></entry> <entry></entry> <entry><literal>ARTISTSORT/50</literal></entry></row>
+ <row><entry>Sort Artist</entry> <entry><literal>TSOP</literal></entry> <entry><literal>TSOP</literal></entry> <entry><literal>soar</literal></entry> <entry><literal>WM/ArtistSortOrder</literal></entry> <entry><literal>ARTISTSORT</literal></entry> <entry></entry> <entry><literal>ARTISTSORT</literal></entry></row>
+ <row><entry>Sort Composer</entry> <entry><literal>TSOC</literal></entry> <entry><literal>TSOC</literal></entry> <entry><literal>soco</literal></entry> <entry></entry> <entry><literal>COMPOSERSORT</literal></entry> <entry></entry> <entry><literal>COMPOSERSORT</literal></entry></row>
+ <row><entry>Sort Name</entry> <entry><literal>TSOT</literal></entry> <entry><literal>TSOT</literal></entry> <entry><literal>sonm</literal></entry> <entry><literal>WM/TitleSortOrder</literal></entry> <entry><literal>TITLESORT</literal></entry> <entry></entry> <entry><literal>TITLESORT</literal></entry></row>
+ <row><entry>Subtitle</entry> <entry></entry> <entry><literal>TSST</literal></entry> <entry><literal>SUBTITLE</literal></entry> <entry><literal>WM/SubTitle</literal></entry> <entry><literal>SUBTITLE</literal></entry> <entry><literal>PRT1</literal></entry> <entry><literal>SUBTITLE</literal></entry></row>
+ <row><entry>Website</entry> <entry><literal>WOAR</literal></entry> <entry><literal>WOAR</literal></entry> <entry><literal>WEBSITE</literal></entry> <entry><literal>WM/AuthorURL</literal></entry> <entry><literal>WEBSITE</literal></entry> <entry><literal>IBSU</literal></entry> <entry><literal>WEBSITE</literal></entry></row>
+ <row><entry>Work</entry> <entry><literal>TIT1</literal></entry> <entry><literal>TIT1</literal></entry> <entry><literal>©wrk</literal></entry> <entry><literal>WM/ContentGroupDescription</literal></entry> <entry><literal>WORK</literal></entry> <entry></entry> <entry><literal>WORK</literal></entry></row>
+ <row><entry>WWW Audio File</entry> <entry><literal>WOAF</literal></entry> <entry><literal>WOAF</literal></entry> <entry></entry> <entry><literal>WM/AudioFileURL</literal></entry> <entry><literal>WWWAUDIOFILE</literal></entry> <entry></entry> <entry><literal>WWWAUDIOFILE</literal></entry></row>
+ <row><entry>WWW Audio Source</entry> <entry><literal>WOAS</literal></entry> <entry><literal>WOAS</literal></entry> <entry></entry> <entry><literal>WM/AudioSourceURL</literal></entry> <entry><literal>WWWAUDIOSOURCE</literal></entry> <entry></entry> <entry><literal>WWWAUDIOSOURCE</literal></entry></row>
</tbody>
</tgroup>
</table>
@@ -874,6 +874,12 @@ Values in this format are also set when importing data from servers which
offer this information.
</para></listitem>
<listitem><para>
+For Matroska the target level is listed, e.g. /50 for Album, unless it is the
+default of /30 for Track. If you want to create a Matroska simple tag with
+binary content, append " - binary" when adding the frame. Note that such tags
+are rarely used, cover art is stored as an attached file.
+</para></listitem>
+<listitem><para>
To explicitly use a specific frame name which conflicts with a unified frame
name, prepend an exclamation mark. For example, adding a frame of type
"<replaceable>Media</replaceable>" to a Vorbis comment will create a frame with
diff --git a/src/core/tags/frame.cpp b/src/core/tags/frame.cpp
index dfbd5119..d9c1388c 100644
--- a/src/core/tags/frame.cpp
+++ b/src/core/tags/frame.cpp
@@ -70,6 +70,9 @@ const char* const fieldIdNames[] = {
QT_TRANSLATE_NOOP("@default", "Price"),
QT_TRANSLATE_NOOP("@default", "Date"),
QT_TRANSLATE_NOOP("@default", "Seller"),
+
+ QT_TRANSLATE_NOOP("@default", "Target Type"),
+ QT_TRANSLATE_NOOP("@default", "Default"),
nullptr
};
@@ -99,6 +102,18 @@ const char* const contentTypeNames[] = {
nullptr
};
+const char* const targetTypeNames[] = {
+ QT_TRANSLATE_NOOP("@default", "None"),
+ QT_TRANSLATE_NOOP("@default", "Shot"),
+ QT_TRANSLATE_NOOP("@default", "Subtrack"),
+ QT_TRANSLATE_NOOP("@default", "Track"),
+ QT_TRANSLATE_NOOP("@default", "Part"),
+ QT_TRANSLATE_NOOP("@default", "Album"),
+ QT_TRANSLATE_NOOP("@default", "Edition"),
+ QT_TRANSLATE_NOOP("@default", "Collection"),
+ nullptr
+};
+
// Custom frame names.
QVector<QByteArray> customFrameNames(Frame::NUM_CUSTOM_FRAME_NAMES);
@@ -280,6 +295,11 @@ QMap<QByteArray, QByteArray> getDisplayNamesOfIds()
{ "VERSION", QT_TRANSLATE_NOOP("@default", "Version") },
{ "VOLUME", QT_TRANSLATE_NOOP("@default", "Volume") },
{ "WWW", QT_TRANSLATE_NOOP("@default", "User-defined URL") },
+ { "DIRECTOR", QT_TRANSLATE_NOOP("@default", "Director") },
+ { "DURATION", QT_TRANSLATE_NOOP("@default", "Duration") },
+ { "SUMMARY", QT_TRANSLATE_NOOP("@default", "Summary") },
+ { "SYNOPSIS", QT_TRANSLATE_NOOP("@default", "Synopsis") },
+ { "TOTAL_PARTS", QT_TRANSLATE_NOOP("@default", "Total Parts") },
{ "WM/AlbumArtistSortOrder", QT_TRANSLATE_NOOP("@default", "Sort Album Artist") },
{ "WM/Comments", QT_TRANSLATE_NOOP("@default", "Comment") },
{ "WM/MCDI", QT_TRANSLATE_NOOP("@default", "MCDI") },
@@ -1080,7 +1100,7 @@ QStringList Frame::getNamesForCustomFrames()
*/
QString Frame::Field::getFieldIdName(FieldId type)
{
- Q_STATIC_ASSERT(std::size(fieldIdNames) == ID_Seller + 2);
+ Q_STATIC_ASSERT(std::size(fieldIdNames) == ID_Default + 2);
if (static_cast<int>(type) >= 0 &&
static_cast<int>(type) < static_cast<int>(std::size(fieldIdNames) - 1)) {
return QCoreApplication::translate("@default", fieldIdNames[type]);
@@ -1200,6 +1220,30 @@ const char* const* Frame::Field::getContentTypeNames()
return contentTypeNames;
}
+/**
+ * Get a translated string for a target type.
+ *
+ * @param type target type / 10
+ *
+ * @return target type, null string if unknown.
+ */
+QString Frame::Field::getTargetTypeName(int type)
+{
+ if (type >= 0 &&
+ static_cast<unsigned int>(type) < std::size(targetTypeNames) - 1) {
+ return QCoreApplication::translate("@default", targetTypeNames[type]);
+ }
+ return QString();
+}
+
+/**
+ * List of target type strings, NULL terminated.
+ */
+const char* const* Frame::Field::getTargetTypeNames()
+{
+ return targetTypeNames;
+}
+
/**
* Compare two field lists in a tolerant way.
* This function can be used instead of the standard QList equality
diff --git a/src/core/tags/frame.h b/src/core/tags/frame.h
index 7f020397..03fafd95 100644
--- a/src/core/tags/frame.h
+++ b/src/core/tags/frame.h
@@ -167,6 +167,10 @@ public:
ID_Date,
ID_Seller,
+ // Additional fields for Matroska
+ ID_TargetType,
+ ID_Default,
+
// Additional field for METADATA_BLOCK_PICTURE
ID_ImageProperties,
@@ -402,6 +406,20 @@ public:
*/
static const char* const* getContentTypeNames();
+ /**
+ * Get a translated string for a target type.
+ *
+ * @param type target type / 10
+ *
+ * @return target type, null string if unknown.
+ */
+ static QString getTargetTypeName(int type);
+
+ /**
+ * List of target type strings, NULL terminated.
+ */
+ static const char* const* getTargetTypeNames();
+
/**
* Compare two field lists in a tolerant way.
* This function can be used instead of the standard QList equality
diff --git a/src/core/tags/pictureframe.cpp b/src/core/tags/pictureframe.cpp
index ca7106ff..0803bdb4 100644
--- a/src/core/tags/pictureframe.cpp
+++ b/src/core/tags/pictureframe.cpp
@@ -365,7 +365,7 @@ void PictureFrame::getFields(const Frame& frame,
}
break;
default:
- qDebug("Unknown picture field ID");
+ ;
}
}
}
diff --git a/src/gui/dialogs/editframefieldsdialog.cpp b/src/gui/dialogs/editframefieldsdialog.cpp
index 22867624..2d5979cf 100644
--- a/src/gui/dialogs/editframefieldsdialog.cpp
+++ b/src/gui/dialogs/editframefieldsdialog.cpp
@@ -37,6 +37,7 @@
#include <QFile>
#include <QDir>
#include <QBuffer>
+#include <QCheckBox>
#include <QVBoxLayout>
#include <QMimeData>
#include <QMimeDatabase>
@@ -647,6 +648,66 @@ QWidget* IntComboBoxControl::createWidget(QWidget* parent)
}
+/** Control to edit boolean fields */
+class BoolFieldControl : public Mp3FieldControl {
+public:
+ /**
+ * Constructor.
+ * @param field field to edit
+ */
+ explicit BoolFieldControl(Frame::Field& field)
+ : Mp3FieldControl(field), m_boolInp(nullptr) {}
+
+ /**
+ * Destructor.
+ */
+ ~BoolFieldControl() override = default;
+
+ /**
+ * Update field from data in field control.
+ */
+ void updateTag() override;
+
+ /**
+ * Create widget to edit field data.
+ *
+ * @param parent parent widget
+ *
+ * @return widget to edit field data.
+ */
+ QWidget* createWidget(QWidget* parent) override;
+
+protected:
+ QCheckBox* m_boolInp;
+
+private:
+ Q_DISABLE_COPY(BoolFieldControl)
+};
+
+/**
+ * Update field with data from dialog.
+ */
+void BoolFieldControl::updateTag()
+{
+ m_field.m_value = m_boolInp->isChecked();
+}
+
+/**
+ * Create widget for dialog.
+ *
+ * @param parent parent widget
+ * @return widget to edit field.
+ */
+QWidget* BoolFieldControl::createWidget(QWidget* parent)
+{
+ m_boolInp = new QCheckBox(parent);
+ m_boolInp->setText(Frame::Field::getFieldIdName(
+ static_cast<Frame::FieldId>(m_field.m_id)));
+ m_boolInp->setChecked(m_field.m_value.toBool());
+ return m_boolInp;
+}
+
+
/** Control to import, export and view data from binary fields */
class BinFieldControl : public Mp3FieldControl {
public:
@@ -1402,12 +1463,28 @@ void EditFrameFieldsDialog::setFrame(const Frame& frame,
fld, Frame::Field::getContentTypeNames());
m_fieldcontrols.append(cbox);
}
+ else if (fld.m_id == Frame::ID_TargetType) {
+ auto cbox = new IntComboBoxControl(
+ fld, Frame::Field::getTargetTypeNames());
+ m_fieldcontrols.append(cbox);
+ }
else {
auto intctl = new IntFieldControl(fld);
m_fieldcontrols.append(intctl);
}
break;
+#if QT_VERSION >= 0x060000
+ case QMetaType::Bool:
+#else
+ case QVariant::Bool:
+#endif
+ {
+ auto boolctl = new BoolFieldControl(fld);
+ m_fieldcontrols.append(boolctl);
+ break;
+ }
+
#if QT_VERSION >= 0x060000
case QMetaType::QString:
#else
diff --git a/src/plugins/taglibmetadata/CMakeLists.txt b/src/plugins/taglibmetadata/CMakeLists.txt
index 1c1b6545..a12450ee 100644
--- a/src/plugins/taglibmetadata/CMakeLists.txt
+++ b/src/plugins/taglibmetadata/CMakeLists.txt
@@ -38,6 +38,11 @@ if(WITH_TAGLIB OR TAGLIB_LIBRARIES)
taglibdsfsupport.cpp
taglibgenericsupport.cpp
)
+ if(NOT ${TAGLIB_VERSION} VERSION_LESS 2.2.0)
+ target_sources(${plugin_TARGET} PRIVATE
+ taglibmatroskasupport.cpp
+ )
+ endif()
if(TAGLIB_VERSION VERSION_LESS 2.0.0)
target_sources(${plugin_TARGET} PRIVATE
taglibext/aac/aacfiletyperesolver.cpp
diff --git a/src/plugins/taglibmetadata/taglibfile.cpp b/src/plugins/taglibmetadata/taglibfile.cpp
index d2c719cb..fd3bd612 100644
--- a/src/plugins/taglibmetadata/taglibfile.cpp
+++ b/src/plugins/taglibmetadata/taglibfile.cpp
@@ -49,6 +49,9 @@
#include "taglibasfsupport.h"
#include "taglibmodsupport.h"
#include "taglibdsfsupport.h"
+#if TAGLIB_VERSION >= 0x020200
+#include "taglibmatroskasupport.h"
+#endif
#include "taglibgenericsupport.h"
using namespace TagLibUtils;
@@ -993,6 +996,9 @@ void TagLibFile::staticInit()
new TagLibAsfSupport,
new TagLibModSupport,
new TagLibDsfSupport,
+#if TAGLIB_VERSION >= 0x020200
+ new TagLibMatroskaSupport,
+#endif
// It is essential that TagLibGenericSupport is last to provide defaults for
// writeFile() and getFrameIds().
new TagLibGenericSupport
diff --git a/src/plugins/taglibmetadata/taglibfileiostream.cpp b/src/plugins/taglibmetadata/taglibfileiostream.cpp
index 06c3828f..acfacce3 100644
--- a/src/plugins/taglibmetadata/taglibfileiostream.cpp
+++ b/src/plugins/taglibmetadata/taglibfileiostream.cpp
@@ -262,6 +262,12 @@ TagLib::File* FileIOStream::createFromContents(TagLib::IOStream* stream)
{ "audio/x-wav", "WAV" },
{ "audio/x-wavpack", "WV" },
{ "audio/x-xm", "XM" },
+#if TAGLIB_VERSION >= 0x020200
+ { "audio/x-matroska", "MKA" },
+ { "video/x-matroska", "MKV" },
+ { "audio/webm", "WEBM" },
+ { "video/webm", "WEBM" },
+#endif
{ "video/mp4", "MP4" }
};
diff --git a/src/plugins/taglibmetadata/taglibmatroskasupport.cpp b/src/plugins/taglibmetadata/taglibmatroskasupport.cpp
new file mode 100644
index 00000000..b4187f02
--- /dev/null
+++ b/src/plugins/taglibmetadata/taglibmatroskasupport.cpp
@@ -0,0 +1,802 @@
+/**
+ * \file taglibmatroskasupport.cpp
+ * Support for Matroska files and tags.
+ *
+ * \b Project: Kid3
+ * \author Urs Fleisch
+ * \date 24 Dec 2025
+ *
+ * Copyright (C) 2025 Urs Fleisch
+ *
+ * This file is part of Kid3.
+ *
+ * Kid3 is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * Kid3 is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "taglibmatroskasupport.h"
+
+#include <QJsonDocument>
+#include <matroskafile.h>
+#include <matroskaattachments.h>
+#include <matroskaattachedfile.h>
+#include <matroskachapter.h>
+#include <matroskachapters.h>
+#include <matroskachapteredition.h>
+#include <matroskasimpletag.h>
+#include <matroskatag.h>
+
+#include "pictureframe.h"
+#include "taglibutils.h"
+#include "taglibfile.h"
+
+using namespace TagLibUtils;
+
+namespace {
+
+constexpr struct {
+ const char* name;
+ TagLib::Matroska::SimpleTag::TargetTypeValue targetType;
+ bool strict;
+} matroskaNamesForTypes[] = {
+ {"TITLE", TagLib::Matroska::SimpleTag::TargetTypeValue::Track, false}, // FT_Title,
+ {"ARTIST", TagLib::Matroska::SimpleTag::TargetTypeValue::Track, false}, // FT_Artist,
+ {"TITLE", TagLib::Matroska::SimpleTag::TargetTypeValue::Album, true}, // FT_Album,
+ {"COMMENT", TagLib::Matroska::SimpleTag::TargetTypeValue::Track, false}, // FT_Comment,
+ {"DATE_RECORDED", TagLib::Matroska::SimpleTag::TargetTypeValue::Track, false}, // FT_Date,
+ {"PART_NUMBER", TagLib::Matroska::SimpleTag::TargetTypeValue::Track, false}, // FT_Track,
+ {"GENRE", TagLib::Matroska::SimpleTag::TargetTypeValue::Track, false}, // FT_Genre,
+ // FT_LastV1Frame = FT_Track,
+ {"ARTIST", TagLib::Matroska::SimpleTag::TargetTypeValue::Album, true}, // FT_AlbumArtist,
+ {"ARRANGER", TagLib::Matroska::SimpleTag::TargetTypeValue::Track, false}, // FT_Arranger,
+ {"WRITTEN_BY", TagLib::Matroska::SimpleTag::TargetTypeValue::Track, false}, // FT_Author,
+ {"BPM", TagLib::Matroska::SimpleTag::TargetTypeValue::Track, false}, // FT_Bpm,
+ {"CATALOG_NUMBER", TagLib::Matroska::SimpleTag::TargetTypeValue::Track, false}, // FT_CatalogNumber,
+ {"COMPILATION", TagLib::Matroska::SimpleTag::TargetTypeValue::Track, false}, // FT_Compilation,
+ {"COMPOSER", TagLib::Matroska::SimpleTag::TargetTypeValue::Track, false}, // FT_Composer,
+ {"CONDUCTOR", TagLib::Matroska::SimpleTag::TargetTypeValue::Track, false}, // FT_Conductor,
+ {"COPYRIGHT", TagLib::Matroska::SimpleTag::TargetTypeValue::Track, false}, // FT_Copyright,
+ {"PART_NUMBER", TagLib::Matroska::SimpleTag::TargetTypeValue::Album, true}, // FT_Disc,
+ {"ENCODER", TagLib::Matroska::SimpleTag::TargetTypeValue::Track, false}, // FT_EncodedBy,
+ {"ENCODER_SETTINGS", TagLib::Matroska::SimpleTag::TargetTypeValue::Track, false}, // FT_EncoderSettings,
+ {"DATE_ENCODED", TagLib::Matroska::SimpleTag::TargetTypeValue::Track, false}, // FT_EncodingTime,
+ {"GROUPING", TagLib::Matroska::SimpleTag::TargetTypeValue::Track, false}, // FT_Grouping,
+ {"INITIAL_KEY", TagLib::Matroska::SimpleTag::TargetTypeValue::Track, false}, // FT_InitialKey,
+ {"ISRC", TagLib::Matroska::SimpleTag::TargetTypeValue::Track, false}, // FT_Isrc,
+ {"LANGUAGE", TagLib::Matroska::SimpleTag::TargetTypeValue::Track, false}, // FT_Language,
+ {"LYRICIST", TagLib::Matroska::SimpleTag::TargetTypeValue::Track, false}, // FT_Lyricist,
+ {"LYRICS", TagLib::Matroska::SimpleTag::TargetTypeValue::Track, false}, // FT_Lyrics,
+ {"ORIGINAL_MEDIA_TYPE", TagLib::Matroska::SimpleTag::TargetTypeValue::Track, false}, // FT_Media,
+ {"MOOD", TagLib::Matroska::SimpleTag::TargetTypeValue::Track, false}, // FT_Mood,
+ {"ORIGINALALBUM", TagLib::Matroska::SimpleTag::TargetTypeValue::Track, false}, // FT_OriginalAlbum,
+ {"ORIGINALARTIST", TagLib::Matroska::SimpleTag::TargetTypeValue::Track, false}, // FT_OriginalArtist,
+ {"ORIGINALDATE", TagLib::Matroska::SimpleTag::TargetTypeValue::Track, false}, // FT_OriginalDate,
+ {"DESCRIPTION", TagLib::Matroska::SimpleTag::TargetTypeValue::Track, false}, // FT_Description,
+ {"PERFORMER", TagLib::Matroska::SimpleTag::TargetTypeValue::Track, false}, // FT_Performer,
+ {"PICTURE", TagLib::Matroska::SimpleTag::TargetTypeValue::Track, false}, // FT_Picture,
+ {"LABEL_CODE", TagLib::Matroska::SimpleTag::TargetTypeValue::Track, false}, // FT_Publisher,
+ {"RELEASECOUNTRY", TagLib::Matroska::SimpleTag::TargetTypeValue::Track, false}, // FT_ReleaseCountry,
+ {"REMIXED_BY", TagLib::Matroska::SimpleTag::TargetTypeValue::Track, false}, // FT_Remixer,
+ {"TITLESORT", TagLib::Matroska::SimpleTag::TargetTypeValue::Album, true}, // FT_SortAlbum,
+ {"ARTISTSORT", TagLib::Matroska::SimpleTag::TargetTypeValue::Album, true}, // FT_SortAlbumArtist,
+ {"ARTISTSORT", TagLib::Matroska::SimpleTag::TargetTypeValue::Track, false}, // FT_SortArtist,
+ {"COMPOSERSORT", TagLib::Matroska::SimpleTag::TargetTypeValue::Track, false}, // FT_SortComposer,
+ {"TITLESORT", TagLib::Matroska::SimpleTag::TargetTypeValue::Track, false}, // FT_SortName,
+ {"SUBTITLE", TagLib::Matroska::SimpleTag::TargetTypeValue::Track, false}, // FT_Subtitle,
+ {"WEBSITE", TagLib::Matroska::SimpleTag::TargetTypeValue::Track, false}, // FT_Website,
+ {"WWWAUDIOFILE", TagLib::Matroska::SimpleTag::TargetTypeValue::Track, false}, // FT_WWWAudioFile,
+ {"WWWAUDIOSOURCE", TagLib::Matroska::SimpleTag::TargetTypeValue::Track, false}, // FT_WWWAudioSource,
+ {"DATE_RELEASED", TagLib::Matroska::SimpleTag::TargetTypeValue::Album, false}, // FT_ReleaseDate,
+ {"RATING", TagLib::Matroska::SimpleTag::TargetTypeValue::Track, false}, // FT_Rating,
+ {"WORK", TagLib::Matroska::SimpleTag::TargetTypeValue::Track, false}, // FT_Work,
+ // FT_Custom1
+};
+
+/**
+ * Get name of frame from type.
+ *
+ * @param type type
+ * @param targetType the target type is returned here
+ *
+ * @return name.
+ */
+const char* getMatroskaNameFromType(Frame::Type type,
+ TagLib::Matroska::SimpleTag::TargetTypeValue& targetType)
+{
+ Q_STATIC_ASSERT(std::size(matroskaNamesForTypes) == Frame::FT_Custom1);
+ if (Frame::isCustomFrameType(type)) {
+ targetType = TagLib::Matroska::SimpleTag::TargetTypeValue::Track;
+ return Frame::getNameForCustomFrame(type);
+ }
+ if (type < Frame::FT_Custom1) {
+ auto [name, targetTypeValue, strict] = matroskaNamesForTypes[type];
+ targetType = targetTypeValue;
+ return name;
+ }
+ targetType = TagLib::Matroska::SimpleTag::TargetTypeValue::None;
+ return "UNKNOWN";
+}
+
+/**
+ * Get the frame type for a Matroska name.
+ *
+ * @param name Matroska simple tag name
+ * @param targetType Matroska target type value
+ *
+ * @return frame type.
+ */
+Frame::Type getTypeFromMatroskaName(const QString& name, TagLib::Matroska::SimpleTag::TargetTypeValue targetType)
+{
+ int i = 0;
+ for (const auto& nameTarget : matroskaNamesForTypes) {
+ if (name == QString::fromUtf8(nameTarget.name) &&
+ (targetType == nameTarget.targetType ||
+ (targetType == TagLib::Matroska::SimpleTag::TargetTypeValue::None &&
+ !nameTarget.strict))) {
+ return static_cast<Frame::Type>(i);
+ }
+ ++i;
+ }
+ return Frame::getTypeFromCustomFrameName(name.toLatin1());
+}
+
+QString getMatroskaName(const Frame& frame,
+ TagLib::Matroska::SimpleTag::TargetTypeValue& targetType)
+{
+ if (Frame::Type type = frame.getType(); type <= Frame::FT_LastFrame) {
+ return QString::fromLatin1(getMatroskaNameFromType(type, targetType));
+ }
+ targetType = TagLib::Matroska::SimpleTag::TargetTypeValue::Track;
+ return TaggedFile::fixUpTagKey(frame.getName(), TaggedFile::TT_Vorbis).toUpper();
+}
+
+QString toSimpleTextOrJson(const QVariantMap& metadata)
+{
+ if (metadata.isEmpty()) {
+ return {};
+ }
+ if (metadata.size() == 1) {
+ if (const QVariant& firstValue = metadata.first();
+#if QT_VERSION >= 0x060000
+ firstValue.typeId() == QMetaType::QString
+#else
+ firstValue.type() == QVariant::String
+#endif
+ ) {
+ return firstValue.toString();
+ }
+ }
+ return QString::fromUtf8(QJsonDocument::fromVariant(metadata)
+ .toJson(QJsonDocument::Compact));
+}
+
+QVariantMap fromSimpleTextOrJson(const QString& str)
+{
+ if (str.startsWith(QLatin1Char('{')) && str.endsWith(QLatin1Char('}'))) {
+ return QJsonDocument::fromJson(str.toUtf8()).toVariant().toMap();
+ }
+ return {{QLatin1String("text"), str}};
+}
+
+void matroskaPictureToFrame(
+ const TagLib::Matroska::AttachedFile& attachedFile, Frame& frame)
+{
+ const TagLib::ByteVector& bv = attachedFile.data();
+ const QByteArray data(bv.data(), static_cast<int>(bv.size()));
+ const QString& mediaType = toQString(attachedFile.mediaType());
+ const QString& description = toQString(attachedFile.description());
+ const QString& fileName = toQString(attachedFile.fileName());
+ const QString& uid = QString::number(attachedFile.uid());
+ PictureFrame::setFields(
+ frame, Frame::TE_ISO8859_1, QLatin1String("JPG"), mediaType,
+ PictureFrame::PT_CoverFront, description, data);
+ frame.fieldList().append({Frame::ID_Filename, fileName});
+ frame.fieldList().append({Frame::ID_Id, uid});
+}
+
+TagLib::Matroska::AttachedFile frameToMatroskaPicture(const Frame& frame)
+{
+ Frame::TextEncoding enc;
+ PictureFrame::PictureType pictureType;
+ QByteArray data;
+ QString imgFormat, mimeType, description;
+ PictureFrame::getFields(frame, enc, imgFormat, mimeType, pictureType,
+ description, data);
+ const QString fileName = Frame::getField(frame, Frame::ID_Filename).toString();
+ const qulonglong uid = Frame::getField(frame, Frame::ID_Id).toULongLong();
+ return TagLib::Matroska::AttachedFile(
+ TagLib::ByteVector(data.constData(), static_cast<unsigned int>(data.size())),
+ toTString(fileName), toTString(mimeType), uid, toTString(description));
+}
+
+void matroskaAttachedFileToFrame(
+ const TagLib::Matroska::AttachedFile& attachedFile, Frame& frame)
+{
+ const TagLib::ByteVector& bv = attachedFile.data();
+ const QByteArray data(bv.data(), static_cast<int>(bv.size()));
+ const QString& mediaType = toQString(attachedFile.mediaType());
+ const QString fileName = toQString(attachedFile.fileName());
+ const QString& description = toQString(attachedFile.description());
+ const QString& uid = QString::number(attachedFile.uid());
+ frame.setExtendedType(
+ Frame::ExtendedType(Frame::FT_Other, QLatin1String("General Object")));
+ frame.setValue(description);
+ // The fields for non-picture attachments are the same as for the
+ // ID3 GEOB frame plus the UID as an ID.
+ frame.fieldList() = {
+ {Frame::ID_TextEnc, Frame::TE_ISO8859_1},
+ {Frame::ID_MimeType, mediaType},
+ {Frame::ID_Filename, fileName},
+ {Frame::ID_Description, description},
+ {Frame::ID_Data, data},
+ {Frame::ID_Id, uid}
+ };
+}
+
+TagLib::Matroska::AttachedFile frameToMatroskaAttachedFile(const Frame& frame)
+{
+ QByteArray data;
+ QString mimeType, description;
+ PictureFrame::getData(frame, data);
+ PictureFrame::getMimeType(frame, mimeType);
+ PictureFrame::getDescription(frame, description);
+ const QString fileName = Frame::getField(frame, Frame::ID_Filename).toString();
+ const qulonglong uid = Frame::getField(frame, Frame::ID_Id).toULongLong();
+ return TagLib::Matroska::AttachedFile(
+ TagLib::ByteVector(data.constData(), static_cast<unsigned int>(data.size())),
+ toTString(fileName), toTString(mimeType), uid, toTString(description));
+}
+
+void matroskaChapterEditionToFrame(
+ const TagLib::Matroska::ChapterEdition& chapterEdition, Frame& frame)
+{
+ const QString& uid = QString::number(chapterEdition.uid());
+ QVariantMap editionMap;
+ if (!chapterEdition.isDefault()) {
+ editionMap.insert(QLatin1String("default"), chapterEdition.isDefault());
+ }
+ if (chapterEdition.isOrdered()) {
+ editionMap.insert(QLatin1String("ordered"), chapterEdition.isOrdered());
+ }
+ const QString description = toSimpleTextOrJson(editionMap);
+ frame.setExtendedType(
+ Frame::ExtendedType(Frame::FT_Other, QLatin1String("Chapters")));
+ frame.setValue(description);
+
+ TagLib::String language;
+ QVariantList synchedData;
+
+ unsigned long long lastTimeEnd = 0ULL;
+
+ unsigned long long chapterNr = 1ULL;
+ for (const auto& chapter : chapterEdition.chapterList()) {
+ if (lastTimeEnd && lastTimeEnd != chapter.timeStart()) {
+ synchedData.append(static_cast<double>(lastTimeEnd) / 1E6);
+ synchedData.append(QString());
+ }
+ synchedData.append(static_cast<double>(chapter.timeStart()) / 1E6);
+ QVariantMap chapMap;
+ for (const auto& display : chapter.displayList()) {
+ if (language.isEmpty()) {
+ language = display.language();
+ }
+ chapMap.insert(toQString(display.language()), toQString(display.string()));
+ }
+ if (chapter.uid() != chapterNr) {
+ chapMap.insert(QLatin1String("uid"), chapter.uid());
+ }
+ if (chapter.isHidden()) {
+ chapMap.insert(QLatin1String("hidden"), chapter.isHidden());
+ }
+ synchedData.append(toSimpleTextOrJson(chapMap));
+ lastTimeEnd = chapter.timeEnd();
+ ++chapterNr;
+ }
+ // synchedData.append(static_cast<quint32>(lastTimeEnd / 1000000ULL));
+ synchedData.append(static_cast<double>(lastTimeEnd) / 1E6);
+ synchedData.append(QString());
+
+ // The fields for chapters are the same as for the ID3 SYLT frame.
+ frame.fieldList() = {
+ {Frame::ID_TextEnc, Frame::TE_UTF8},
+ {Frame::ID_Language, toQString(language)},
+ {Frame::ID_TimestampFormat, 2}, // milliseconds as unit
+ {Frame::ID_ContentType, 0}, // other
+ {Frame::ID_Description, description},
+ {Frame::ID_Id, uid},
+ {Frame::ID_Data, synchedData}
+ };
+}
+
+TagLib::Matroska::ChapterEdition frameToMatroskaChapterEdition(const Frame& frame)
+{
+ const TagLib::String language = toTString(Frame::getField(frame, Frame::ID_Language).toString());
+ const QVariantList synchedData = Frame::getField(frame, Frame::ID_Data).toList();
+
+ struct ChapterData {
+ TagLib::List<TagLib::Matroska::Chapter::Display> displays;
+ unsigned long long timeStart;
+ unsigned long long timeEnd;
+ unsigned long long uid;
+ bool hidden;
+ };
+ QList<ChapterData> chapterData;
+ unsigned long long chapterNr = 1ULL;
+ int chapterDataIndex = -1;
+
+ QListIterator it(synchedData);
+ while (it.hasNext()) {
+ auto time = static_cast<unsigned long long>(it.next().toDouble() * 1E6);
+ auto text = it.next().toString();
+ if (chapterDataIndex >= 0 && !chapterData[chapterDataIndex].timeEnd) {
+ chapterData[chapterDataIndex].timeEnd = time;
+ if (text.isEmpty()) {
+ continue;
+ }
+ }
+ auto map = fromSimpleTextOrJson(text);
+ ChapterData cd;
+ cd.timeStart = time;
+ cd.timeEnd = 0;
+ cd.uid = map.take(QLatin1String("uid")).toULongLong();
+ if (!cd.uid) {
+ cd.uid = chapterNr;
+ }
+ cd.hidden = map.take(QLatin1String("hidden")).toBool();
+ if (!map.isEmpty()) {
+ for (auto it = map.constBegin(); it != map.constEnd(); ++it) {
+ cd.displays.append(TagLib::Matroska::Chapter::Display(
+ toTString(it.value().toString()),
+ it.key() != QLatin1String("text") ? toTString(it.key()) : language));
+ }
+ } else {
+ cd.displays.append(TagLib::Matroska::Chapter::Display("", language));
+ }
+ chapterData.append(cd);
+ ++chapterNr;
+ ++chapterDataIndex;
+ }
+
+ TagLib::List<TagLib::Matroska::Chapter> chapters;
+ for (const auto& cd : chapterData) {
+ chapters.append(TagLib::Matroska::Chapter(
+ cd.timeStart, cd.timeEnd, cd.displays, cd.uid, cd.hidden));
+ }
+ const qulonglong uid = Frame::getField(frame, Frame::ID_Id).toULongLong();
+ const QString description = Frame::getField(frame, Frame::ID_Description).toString();
+ auto map = fromSimpleTextOrJson(description);
+ return TagLib::Matroska::ChapterEdition(
+ chapters,
+ map.value(QLatin1String("default"), true).toBool(),
+ map.value(QLatin1String("ordered"), false).toBool(),
+ uid);
+}
+
+TagLib::Matroska::SimpleTag frameToMatroskaSimpleTag(const Frame& frame)
+{
+ const QVariant dataVar = Frame::getField(frame, Frame::ID_Data);
+ const bool isBinary = dataVar.isValid();
+ const QByteArray data = isBinary ? dataVar.toByteArray() : QByteArray();
+ const TagLib::String name = toTString(frame.getInternalName());
+ const TagLib::String value = toTString(frame.getValue());
+ auto targetType =
+ static_cast<TagLib::Matroska::SimpleTag::TargetTypeValue>(
+ Frame::getField(frame, Frame::ID_TargetType).toInt() * 10);
+ const TagLib::String language = toTString(
+ Frame::getField(frame, Frame::ID_Language).toString());
+ const bool defaultLanguage =
+ Frame::getField(frame, Frame::ID_Default).toBool();
+ const unsigned long long trackUid =
+ Frame::getField(frame, Frame::ID_Id).toULongLong();
+ return !isBinary
+ ? TagLib::Matroska::SimpleTag(
+ name, value, targetType, language, defaultLanguage, trackUid)
+ : TagLib::Matroska::SimpleTag(
+ name, TagLib::ByteVector(data.constData(), data.size()),
+ targetType, language, defaultLanguage, trackUid);
+}
+
+bool isExtraFrame(Frame::Type type, const QString& name)
+{
+ return type == Frame::FT_Picture ||
+ (type == Frame::FT_Other && (
+ name == QLatin1String("General Object") ||
+ name == QLatin1String("Chapters")));
+}
+
+bool isExtraFrame(const Frame::ExtendedType& type)
+{
+ return isExtraFrame(type.getType(), type.getInternalName());
+}
+
+}
+
+
+TagLib::File* TagLibMatroskaSupport::createFromExtension(
+ TagLib::IOStream* stream, const TagLib::String& ext) const
+{
+ if (ext == "MKA" || ext == "MKV" || ext == "WEBM")
+ return new TagLib::Matroska::File(stream);
+ return nullptr;
+}
+
+bool TagLibMatroskaSupport::readFile(TagLibFile& f, TagLib::File* file) const
+{
+ if (auto mkaFile = dynamic_cast<TagLib::Matroska::File*>(file)) {
+ f.m_fileExtension = QLatin1String(".mka");
+ putFileRefTagInTag2(f);
+
+ if (!f.m_extraFrames.isRead()) {
+ int i = 0;
+ if (auto attachments = mkaFile->attachments()) {
+ for (const auto& attachedFile : attachments->attachedFileList()) {
+ if (attachedFile.mediaType().startsWith("image/")) {
+ PictureFrame frame;
+ matroskaPictureToFrame(attachedFile, frame);
+ frame.setIndex(Frame::toNegativeIndex(i++));
+ f.m_extraFrames.append(frame);
+ } else {
+ Frame frame;
+ matroskaAttachedFileToFrame(attachedFile, frame);
+ frame.setIndex(Frame::toNegativeIndex(i++));
+ f.m_extraFrames.append(frame);
+ }
+ }
+ }
+ if (auto chapters = mkaFile->chapters()) {
+ for (const auto& chapterEdition : chapters->chapterEditionList()) {
+ Frame frame;
+ matroskaChapterEditionToFrame(chapterEdition, frame);
+ frame.setIndex(Frame::toNegativeIndex(i++));
+ f.m_extraFrames.append(frame);
+ }
+ }
+ f.m_extraFrames.setRead(true);
+ }
+ return true;
+ }
+ return false;
+}
+
+
+bool TagLibMatroskaSupport::writeFile(TagLibFile& f, TagLib::File* file, bool force,
+ int, bool& fileChanged) const
+{
+ if (auto mkaFile = dynamic_cast<TagLib::Matroska::File*>(file)) {
+ if (anyTagMustBeSaved(f, force)) {
+ if (auto attachments = mkaFile->attachments(false)) {
+ attachments->clear();
+ }
+ if (auto chapters = mkaFile->chapters(false)) {
+ chapters->clear();
+ }
+ const auto frames = f.m_extraFrames;
+ for (const Frame& frame : frames) {
+ if (frame.getExtendedType() == Frame::ExtendedType(
+ Frame::FT_Other, QLatin1String("Chapters"))) {
+ mkaFile->chapters(true)->addChapterEdition(
+ frameToMatroskaChapterEdition(frame));
+ } else if (frame.getType() == Frame::FT_Picture) {
+ mkaFile->attachments(true)->addAttachedFile(
+ frameToMatroskaPicture(frame));
+ } else {
+ mkaFile->attachments(true)->addAttachedFile(
+ frameToMatroskaAttachedFile(frame));
+ }
+ }
+ if (saveFileRef(f)) {
+ fileChanged = true;
+ }
+ }
+ return true;
+ }
+ return false;
+}
+
+bool TagLibMatroskaSupport::makeTagSettable(TagLibFile& f, TagLib::File* file,
+ Frame::TagNumber tagNr) const
+{
+ if (TagLib::Matroska::File* mkaFile;
+ tagNr == Frame::Tag_2 &&
+ (mkaFile = dynamic_cast<TagLib::Matroska::File*>(file)) != nullptr) {
+ f.m_tag[tagNr] = mkaFile->tag(true);
+ return true;
+ }
+ return false;
+}
+
+bool TagLibMatroskaSupport::readAudioProperties(
+ TagLibFile& f, TagLib::AudioProperties* audioProperties) const
+{
+ if (auto mkaProperties = dynamic_cast<TagLib::Matroska::Properties*>(audioProperties)) {
+ f.m_detailInfo.format = toQString(
+ mkaProperties->docType().substr(0, 1).upper() +
+ mkaProperties->docType().substr(1));
+ f.m_detailInfo.format += QLatin1String(" Version ") +
+ QString::number(mkaProperties->docTypeVersion());
+ if (!mkaProperties->codecName().isEmpty()) {
+ f.m_detailInfo.format += QLatin1String(" Codec ") +
+ toQString(mkaProperties->codecName());
+ }
+ return true;
+ }
+ return false;
+}
+
+QString TagLibMatroskaSupport::getTagFormat(
+ const TagLib::Tag* tag, TaggedFile::TagType&) const
+{
+ if (dynamic_cast<const TagLib::Matroska::Tag*>(tag) != nullptr) {
+ return QLatin1String("Matroska");
+ }
+ return {};
+}
+
+bool TagLibMatroskaSupport::setFrame(TagLibFile& f, Frame::TagNumber tagNr,
+ const Frame& frame) const
+{
+ if (auto mkaTag = dynamic_cast<TagLib::Matroska::Tag*>(f.m_tag[tagNr])) {
+ if (int index = frame.getIndex(); index != -1) {
+ if (Frame::ExtendedType extendedType = frame.getExtendedType();
+ isExtraFrame(extendedType)) {
+ if (f.m_extraFrames.isRead()) {
+ if (int idx = Frame::fromNegativeIndex(frame.getIndex());
+ idx >= 0 && idx < f.m_extraFrames.size()) {
+ if (Frame newFrame(frame);
+ PictureFrame::areFieldsEqual(f.m_extraFrames[idx], newFrame)) {
+ f.m_extraFrames[idx].setValueChanged(false);
+ } else {
+ f.m_extraFrames[idx] = newFrame;
+ f.markTagChanged(tagNr, extendedType);
+ }
+ return true;
+ }
+ return false;
+ }
+ }
+ if (index < static_cast<int>(mkaTag->simpleTagsList().size())) {
+ mkaTag->removeSimpleTag(index);
+ mkaTag->insertSimpleTag(index, frameToMatroskaSimpleTag(frame));
+ f.markTagChanged(tagNr, frame.getExtendedType());
+ }
+ return true;
+ }
+ return setFrameWithoutIndex(f, tagNr, frame);
+ }
+ return false;
+}
+
+bool TagLibMatroskaSupport::addFrame(TagLibFile& f, Frame::TagNumber tagNr, Frame& frame) const
+{
+ if (auto mkaTag = dynamic_cast<TagLib::Matroska::Tag*>(f.m_tag[tagNr])) {
+ if (Frame::ExtendedType extendedType = frame.getExtendedType();
+ isExtraFrame(extendedType)) {
+ if (frame.getFieldList().isEmpty()) {
+ if (extendedType.getType() == Frame::FT_Picture) {
+ PictureFrame::setFields(frame);
+ frame.fieldList().append({
+ {Frame::ID_Filename, QString()},
+ {Frame::ID_Id, QString()}
+ });
+ } else if (extendedType.getName() == QLatin1String("General Object")) {
+ frame.fieldList() = {
+ {Frame::ID_TextEnc, Frame::TE_ISO8859_1},
+ {Frame::ID_MimeType, QString()},
+ {Frame::ID_Filename, QString()},
+ {Frame::ID_Description, QString()},
+ {Frame::ID_Data, QByteArray()},
+ {Frame::ID_Id, QString()}
+ };
+ } else {
+ frame.fieldList() = {
+ {Frame::ID_TextEnc, Frame::TE_UTF8},
+ {Frame::ID_Language, QString()},
+ {Frame::ID_TimestampFormat, 2}, // milliseconds as unit
+ {Frame::ID_ContentType, 0}, // other
+ {Frame::ID_Description, QString()},
+ {Frame::ID_Id, QString()},
+ {Frame::ID_Data, QVariantList()}
+ };
+ }
+ }
+ if (f.m_extraFrames.isRead()) {
+ frame.setIndex(Frame::toNegativeIndex(static_cast<int>(f.m_extraFrames.size())));
+ f.m_extraFrames.append(frame);
+ f.markTagChanged(tagNr, extendedType);
+ return true;
+ }
+ }
+
+ // Add a Matroska simple tag for the given frame.
+ // To create simple tags with binary contents, " - binary" can be appended
+ // to the name, it will be stripped away.
+ bool isBinary = false;
+ if (QString internalName = frame.getInternalName();
+ internalName.endsWith(QLatin1String(" - binary"))) {
+ isBinary = true;
+ internalName.truncate(internalName.length() - 9);
+ frame.setExtendedType(Frame::ExtendedType(frame.getType(), internalName));
+ }
+ TagLib::Matroska::SimpleTag::TargetTypeValue targetType;
+ QString name = getMatroskaName(frame, targetType);
+ frame.setExtendedType(Frame::ExtendedType(frame.getType(), name));
+ if (!isBinary) {
+ frame.fieldList() = {{Frame::ID_Text, frame.getValue()}};
+ } else {
+ frame.fieldList() = {{Frame::ID_Data, QByteArray()}};
+ }
+ frame.fieldList().append({
+ {Frame::ID_TargetType, static_cast<int>(targetType) / 10},
+ {Frame::ID_Language, QLatin1String("en")},
+ {Frame::ID_Default, true},
+ {Frame::ID_Id, QLatin1String("0")}
+ });
+ frame.setIndex(mkaTag->simpleTagsList().size());
+ mkaTag->addSimpleTag(frameToMatroskaSimpleTag(frame));
+ f.markTagChanged(tagNr, frame.getExtendedType());
+ return true;
+ }
+ return false;
+}
+
+bool TagLibMatroskaSupport::deleteFrame(TagLibFile& f, Frame::TagNumber tagNr,
+ const Frame& frame) const
+{
+ if (auto mkaTag = dynamic_cast<TagLib::Matroska::Tag*>(f.m_tag[tagNr])) {
+ if (Frame::ExtendedType extendedType = frame.getExtendedType();
+ isExtraFrame(extendedType)) {
+ if (f.m_extraFrames.isRead()) {
+ if (int idx = Frame::fromNegativeIndex(frame.getIndex());
+ idx >= 0 && idx < f.m_extraFrames.size()) {
+ f.m_extraFrames.removeAt(idx);
+ while (idx < f.m_extraFrames.size()) {
+ f.m_extraFrames[idx].setIndex(Frame::toNegativeIndex(idx));
+ ++idx;
+ }
+ f.markTagChanged(tagNr, extendedType);
+ return true;
+ }
+ }
+ }
+ if (int index = frame.getIndex();
+ index >= 0 && index < static_cast<int>(mkaTag->simpleTagsList().size())) {
+ mkaTag->removeSimpleTag(index);
+ f.markTagChanged(tagNr, frame.getExtendedType());
+ }
+ }
+ return false;
+}
+
+bool TagLibMatroskaSupport::deleteFrames(
+ TagLibFile& f, Frame::TagNumber tagNr, const FrameFilter& flt) const
+{
+ if (auto mkaTag = dynamic_cast<TagLib::Matroska::Tag*>(f.m_tag[tagNr])) {
+ if (flt.areAllEnabled()) {
+ mkaTag->clearSimpleTags();
+ f.m_extraFrames.clear();
+ f.markTagChanged(tagNr, Frame::ExtendedType());
+ } else {
+ TagLib::Matroska::SimpleTagsList simpleTags = mkaTag->simpleTagsList();
+ bool simpleTagRemoved = false;
+ for (auto it = simpleTags.begin();
+ it != simpleTags.end();) {
+ QString name = toQString(it->name());
+ Frame::Type type = getTypeFromMatroskaName(name, it->targetTypeValue());
+ if (flt.isEnabled(type, name)) {
+ simpleTagRemoved = true;
+ it = simpleTags.erase(it);
+ } else {
+ ++it;
+ }
+ }
+ if (simpleTagRemoved) {
+ mkaTag->clearSimpleTags();
+ mkaTag->addSimpleTags(simpleTags);
+ }
+
+ bool extraFrameRemoved = false;
+ if (f.m_extraFrames.isRead()) {
+ for (auto it = f.m_extraFrames.begin();
+ it != f.m_extraFrames.end();) {
+ if (flt.isEnabled(it->getType(), it->getInternalName())) {
+ extraFrameRemoved = true;
+ it = f.m_extraFrames.erase(it);
+ } else {
+ ++it;
+ }
+ }
+ if (extraFrameRemoved) {
+ int i = 0;
+ for (Frame& frame : f.m_extraFrames) {
+ frame.setIndex(Frame::toNegativeIndex(i++));
+ }
+ }
+ }
+
+ if (simpleTagRemoved || extraFrameRemoved) {
+ f.markTagChanged(tagNr, Frame::ExtendedType());
+ }
+ }
+ return true;
+ }
+ return false;
+}
+
+bool TagLibMatroskaSupport::getAllFrames(
+ TagLibFile& f, Frame::TagNumber tagNr, FrameCollection& frames) const
+{
+ if (auto mkaTag = dynamic_cast<const TagLib::Matroska::Tag*>(f.m_tag[tagNr])) {
+ const auto& simpleTags = mkaTag->simpleTagsList();
+ int i = 0;
+ for (const auto& simpleTag : simpleTags) {
+ const QString name = toQString(simpleTag.name());
+ Frame::Type type = getTypeFromMatroskaName(name, simpleTag.targetTypeValue());
+ QString value;
+ if (simpleTag.type() == TagLib::Matroska::SimpleTag::StringType) {
+ value = toQString(simpleTag.toString());
+ }
+ Frame frame(type, value, name, i++);
+ if (simpleTag.type() == TagLib::Matroska::SimpleTag::StringType) {
+ frame.fieldList().append({Frame::ID_Text, value});
+ } else if (simpleTag.type() == TagLib::Matroska::SimpleTag::BinaryType) {
+ const TagLib::ByteVector bv = simpleTag.toByteVector();
+ frame.fieldList().append(
+ {Frame::ID_Data, QByteArray(bv.data(), bv.size())});
+ }
+ frame.fieldList().append({
+ {Frame::ID_TargetType, static_cast<int>(simpleTag.targetTypeValue()) / 10},
+ {Frame::ID_Language, toQString(simpleTag.language())},
+ {Frame::ID_Default, simpleTag.defaultLanguageFlag()},
+ {Frame::ID_Id, QString::number(simpleTag.trackUid())}
+ });
+ frames.insert(frame);
+ }
+ if (f.m_extraFrames.isRead()) {
+ for (auto it = f.m_extraFrames.constBegin();
+ it != f.m_extraFrames.constEnd();
+ ++it) {
+ frames.insert(*it);
+ }
+ }
+ return true;
+ }
+ return false;
+}
+
+QStringList TagLibMatroskaSupport::getFrameIds(
+ const TagLibFile& f, Frame::TagNumber tagNr) const
+{
+ QStringList lst;
+ if (dynamic_cast<TagLib::Matroska::Tag*>(f.m_tag[tagNr])) {
+ static const char* const fieldNames[] = {
+ "DIRECTOR",
+ "DURATION",
+ "SUMMARY",
+ "SYNOPSIS",
+ "TOTAL_PARTS",
+ "Chapters",
+ "General Object"
+ };
+ for (int k = Frame::FT_FirstFrame; k <= Frame::FT_LastFrame; ++k) {
+ if (auto name = Frame::ExtendedType(static_cast<Frame::Type>(k),
+ QLatin1String("")).getName();
+ !name.isEmpty()) {
+ lst.append(name);
+ }
+ }
+ for (auto fieldName : fieldNames) {
+ lst.append(QString::fromLatin1(fieldName)); // clazy:exclude=reserve-candidates
+ }
+ }
+ return lst;
+}
diff --git a/src/plugins/taglibmetadata/taglibmatroskasupport.h b/src/plugins/taglibmetadata/taglibmatroskasupport.h
new file mode 100644
index 00000000..7a810695
--- /dev/null
+++ b/src/plugins/taglibmetadata/taglibmatroskasupport.h
@@ -0,0 +1,56 @@
+/**
+ * \file taglibmatroskasupport.h
+ * Support for Matroska files and tags.
+ *
+ * \b Project: Kid3
+ * \author Urs Fleisch
+ * \date 24 Dec 2025
+ *
+ * Copyright (C) 2025 Urs Fleisch
+ *
+ * This file is part of Kid3.
+ *
+ * Kid3 is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * Kid3 is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "taglibformatsupport.h"
+
+class TagLibMatroskaSupport : public TagLibFormatSupport {
+public:
+ TagLib::File* createFromExtension(TagLib::IOStream* stream,
+ const TagLib::String& ext) const override;
+ bool readFile(TagLibFile& f, TagLib::File* file) const override;
+ bool writeFile(TagLibFile& f, TagLib::File* file, bool force,
+ int id3v2Version, bool& fileChanged) const override;
+ bool makeTagSettable(TagLibFile& f, TagLib::File* file,
+ Frame::TagNumber tagNr) const override;
+ bool readAudioProperties(TagLibFile& f,
+ TagLib::AudioProperties* audioProperties) const override;
+ QString getTagFormat(const TagLib::Tag* tag,
+ TaggedFile::TagType& type) const override;
+ bool setFrame(TagLibFile& f, Frame::TagNumber tagNr,
+ const Frame& frame) const override;
+ bool addFrame(TagLibFile& f, Frame::TagNumber tagNr,
+ Frame& frame) const override;
+ bool deleteFrame(TagLibFile& f, Frame::TagNumber tagNr,
+ const Frame& frame) const override;
+ bool deleteFrames(TagLibFile& f, Frame::TagNumber tagNr,
+ const FrameFilter& flt) const override;
+ bool getAllFrames(TagLibFile& f, Frame::TagNumber tagNr,
+ FrameCollection& frames) const override;
+ QStringList getFrameIds(const TagLibFile& f,
+ Frame::TagNumber tagNr) const override;
+};
More information about the kde-doc-english
mailing list