Recursive AI

Tags, common view and basic facts

All onemedia docs

How RecursiveAI.OneMedia.Tags and RecursiveAI.OneMedia.Metadata read the tags of ISO base media files (MP4, MOV, 3GP) and Matroska/WebM, and what the common view and the basic facts are built from. Writing tags is a later feature; the models are shaped so a writer can round-trip them: every item keeps its source range, and values are fetched from the source by range, never copied into the tree.

Layers

LayerTypesNotes
Native tagsNativeTag, ITagContainer, TagSystemsOne NativeTag per item: system, key, track, kind (text, number, real, binary, image), data type, locale or language, byte range of the item and of its value, decoded text or number. Values up to 64 KiB are decoded when read; longer text and all binary values and pictures are read on access (Text, ReadValue()).
ISO systemsItunesItemList (ilst), QuickTimeKeys (mdta key table with ilst), QuickTimeUserData (udta atoms, incl. ©-atoms with language), ThreeGppAssets (titl, auth, perf, gnre, dscp, cprt, yrrc, loci, albm, kywd, rtng, …), XmpPacket (uuid XMP box and XMP_ atom)Plain and meta-wrapped. XMP is exposed as the raw packet range plus the flat property list.
Matroska systemsMatroskaInfoTags (Segment Info: Title, MuxingApp, WritingApp, DateUTC), MatroskaTagSet (Tags/Tag/Targets/SimpleTag), MatroskaAttachments (AttachedFile, cover art as image items)Targets keep TargetTypeValue, TargetType and the track UIDs (mapped to track numbers). Nested SimpleTags are keyed PARENT/CHILD.
Common viewCommonTags, CommonField, TagPictureA view over the native tags through TagMappingTable (tag-mapping.md). Nothing is stored.
FactsMediaFacts, Fact<T>, FactCandidate<T>, MediaDateTime, GeoLocationDuration, capture time, location, make, model, software, orientation, frame size. Each is an ordered candidate list; the first candidate is the value.
Entry pointsIMetadataProvider.ReadMetadata() on IsoBmffDemuxer, IsoBmffAsyncDemuxer and MatroskaDemuxer; MatroskaAsyncDemuxer.ReadMetadataAsync()→ MediaMetadata (NativeTags, Tags, Facts, Chapters, Containers, XmpPackets, Defects, indexer [system, key], Find).

Reading rules

  • I/O. Only structure is read: the movie box (already loaded by the opener), the top-level box headers up to 64 boxes (stopping at the first moof), and for Matroska the Segment's leading elements up to the first Cluster plus the SeekHead entries that point at Tags and Attachments. Tags after the Clusters cost a header read each, never a scan. Cover art is read when asked for. Over a ranged source MatroskaAsyncDemuxer.ReadMetadataAsync fetches the located elements whole (each up to MaxAttachmentBytes) so the result stays valid after the source closes; an element over the limit is reported (tags.limit) and skipped.
  • QuickTime udta versus 3GPP boxes. The same four-character code can have a QuickTime layout (a list of u16 size, u16 language, text records) or a 3GPP layout (full box: version/flags, packed language, zero-terminated UTF-8 or UTF-16). A box is read as 3GPP when its first four payload bytes are zero and the language word has its top bit clear; otherwise as QuickTime.
  • ilst data types (ISO/IEC 14496-12 and the QuickTime spec): 1 UTF-8, 2 UTF-16BE, 4 and 5 text variants, 13/14/27 JPEG/PNG/BMP pictures, 21–24 and 65–78 integers and reals. trkn and disk are 00 00, number, total, 00 00 pairs. gnre holds the ID3v1 genre number plus one. An unknown code is kept as binary with an Info defect.
  • Key-table metadata (mdta). keys lists reverse-DNS names; each ilst item's four-character type is a 1-based index into it.
  • Matroska common view. Only tags that target the file (no track, edition, chapter or attachment UID) feed it, and for a file with exactly one track also that track's tags, except ENCODER (which names the stream's encoder). Within a key: tags of the file before tags of the only track; the lowest TargetTypeValue of 30 or above first, then values below 30; tags without a language or with und before language-specific ones; then file order. Everything else stays on the native list.
  • Dates. MediaDateTime.TryParse accepts ISO 8601 (with Z, ±hh:mm, ±hhmm or no zone) and date-only text; a bare year is not an instant. The zone certainty is Utc, Offset (with minutes), Local or Unknown.
  • Locations. ISO 6709 in the ±DD.DDDD±DDD.DDDD[±AAA]/, ±DDMM.MMM… and ±DDMMSS.SSS… forms (with an optional CRS… suffix), 3GPP loci (16.16 fixed point; an all-zero box means unknown and is ignored), and XMP exif:GPSLatitude/GPSLongitude.
  • Chapters. Nero chpl lists (version 1: 4 reserved bytes, a one-byte count, then per chapter a 64-bit start in 100 ns units and a length-prefixed UTF-8 title) give MediaMetadata.Chapters; checked against ffprobe and ExifTool on ffmpeg-written files. Matroska Chapters and QuickTime text chapter tracks are not read yet.

Fact precedence

A candidate list is ordered by rank, lower first, then file order. The rank is fixed and documented here; a replaceable resolver is a later feature.

FactRankSource
Capture time10com.apple.quicktime.creationdate (carries an offset)
20XMP exif:DateTimeOriginal, xmp:CreateDate, photoshop:DateCreated with a zone
25mdta creation_time
30©day (udta, ilst)
40the same XMP properties without a zone
45Matroska DATE_RECORDED tag
50Matroska Info DateUTC (when the file was muxed)
60mvhd creation time, as UTC
Location10 / 20 / 30 / 40mdta ISO 6709 / ©xyz / 3GPP loci / XMP GPS
Make, model, software10 (12 for com.android.*) / 20 / 30mdta / udta ©mak, ©mod, ©swr / XMP; Matroska WritingApp 50, MuxingApp 51
Orientation–tkhd matrix of the first video track → 0/90/180/270 plus mirror
Frame size–first video sample entry (ISO) or TrackEntry.Video (Matroska): coded and display size
Duration–movie header or Segment Info

Defects

All carry the byte offset of the item. Reading continues; nothing here throws on malformed tag data (typed exceptions come only from the structure layers or from ReadValue() over a limit).

CodeKindMeaning
tags.invalid-utf8, tags.invalid-utf16InvalidValue, InvalidSizeText is not valid; bad sequences are replaced with U+FFFD, an odd trailing byte is dropped.
tags.value-too-large, tags.limit, tags.xmp-limit, tags.mkv-large, tags.mkv-depthLimitReachedA value or element is over MaxTagBytes/MaxAttachmentBytes, a packet has too many properties, SimpleTags nest too deeply; the part is skipped.
tags.short-readTruncatedThe source ended inside a value.
tags.item-no-data, tags.data-short, tags.freeform-size, tags.int-length, tags.type-set, tags.data-typevariousilst item without a data box, a data box shorter than its type and locale, an unusable mean/name size, an integer with an impossible length (not reported for trkn/disk pairs), an unknown type set or code.
tags.keys-short, tags.keys-count, tags.keys-size, tags.keys-indexTruncated, InvalidSize, InvalidValueKey table shorter than its count, a key whose size does not fit, an item pointing past the table.
tags.udta-raw-text, tags.udta-text-sizeInconsistent, SizeExceedsParentA udta atom without a text-record header (read as plain text); a record longer than its atom.
tags.3gpp-short, tags.3gpp-unterminated, tags.3gpp-locationTruncated, InvalidValueA 3GPP box shorter than its fields, a string without a terminator, a loci position out of range.
tags.xmp-malformed, tags.xmp-gpsInvalidValueThe XMP packet is not well-formed XML (DTDs are prohibited, so entity bombs are refused), or the GPS pair is not a position.
tags.datetime, tags.iso6709InvalidValueA date or position text that does not parse; the fact is not produced.
tags.chpl-shortTruncatedA chapter list shorter than its header or count.
tags.mkv-header, tags.mkv-no-name, tags.mkv-attachment, tags.mkv-date, tags.mkv-target-levelvariousA tag-bearing element without a readable header, a SimpleTag without a name, an attachment without data, an invalid DateUTC, a TargetTypeValue outside 10–70.

Limits used (MediaLimits): MaxTagBytes (text and binary values, udta atoms, key tables, XMP packets, chapter lists), MaxAttachmentBytes (pictures and, over a ranged source, the fetched Matroska elements), MaxAllocationBytes, MaxElementCount and MaxStructureDepth through the structure layers.

How the reads were compared

tools/OneMedia.Oracles meta (run with tools/run-metadata-compare.sh) reads each file with our reader and with ExifTool 13.60 (-j -G1 -a -api largefilesupport=1, once plain and once with -n) and MediaInfo 26.10 (--Output=JSON), and writes one row per field to metadata-compare.tsv: native rows (each text tag's value among the oracle's values of the matching group), common rows (the common field against the oracle's field of the same meaning; MediaInfo's General section only) and fact rows. Dates are compared as instants, positions as numbers (the oracles print degrees-minutes-seconds, decimals or hemisphere letters). Each row records what each oracle did: agree, disagree or does not report. tools/summarize-metadata-compare.sh prints the counts. The corpus is 47 fixtures written by ffmpeg 7.0.2, MP4Box 26.07, mkvmerge 103.0 and ExifTool (tools/gen-metadata-fixtures.sh; a subset is in testdata/metadata) plus public sample files (Blender movies, the Matroska test files, vendor samples) fetched by the corpus scripts.

Differences from ExifTool and MediaInfo, and why

DifferenceExplanation
ExifTool shows one value per tag name and groupIt keeps the last of several Matroska tags with the same name (a TITLE at two levels) and files every track's tags under one Track group, so the second track's ENCODER/DURATION tags do not appear. We keep all of them on the native list.
Which of several values is "the" valueFor a name repeated at several levels ExifTool takes the last in file order, MediaInfo joins them with /, we take the lowest level of 30 or above first. Both values are reachable (CommonTags.Values).
MediaInfo's Title for Matroska with attachmentsIt lists the attachment file names. Attachments are not titles in the common view.
Track-targeted Matroska tagsExifTool and MediaInfo show them as file tags. We keep them native-only unless the file has exactly one track.
Encoder of a remuxed Matroska filemkvmerge keeps the source's file-level ENCODER tag next to its own WritingApp; the tag wins over the Segment Info application, MediaInfo reports the application. ExifTool lists both.
QuickTime ©autMediaInfo and ExifTool call it Performer or Author; the common view has an Author field and Artist takes ©ART/©prf.
QuickTime ©swrMediaInfo's "Writing application" shows it; we expose it as the Software fact, not as the Encoder field.
DurationMediaInfo derives it from the streams where ExifTool and we use the movie header; where a track is longer than the header they differ.
mvhd before 1970A writer that stored Unix-epoch seconds in the 1904-epoch field gives 1942 for a 2008 recording. We report the literal value (MediaInfo does too); ExifTool shows 2008.
3GPP lociCoordinates are 16.16 fixed point; the quantised value (51.500687 for 51.5007) is what the file holds.
trkn/MP4Box ©trkMP4Box adds its track number as a text item ©trk beside the numeric trkn; the numeric pair is the track number.

Where it stands

On 116 files (63 MP4/MOV/3GP/M4A, 53 Matroska/WebM/MKA) against ExifTool 13.60 and MediaInfo 26.10: 1,266 compared fields. 1,097 agree with every oracle that reports them; 145 more agree with one oracle where the other does not report the field (native rows only ExifTool lists: MediaInfo has no key for most tags); 13 are ExifTool's one-value-per-name collapse, 7 are the multi-value and mkvmerge Encoder cases, 3 are MediaInfo's stream-based duration, and 1 is a malformed fixture on purpose (north_of_here as a position, reported as tags.iso6709). Per kind, rows where the oracle reported the field and agreed: native tags ExifTool 625/646 and MediaInfo 496/498 (MediaInfo prints degrees-minutes-seconds positions as if they were decimal degrees); common view ExifTool 196/197 and MediaInfo 200/206; facts ExifTool 295/296 and MediaInfo 317/322 (the rest are the durations, positions and the pre-1970 mvhd above).