Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
83 commits
Select commit Hold shift + click to select a range
864da46
add MP4GetMovieIndTrackNALUnitLength API
podborski Apr 23, 2025
f9d7a6b
Merge branch 'config_records' into t35
podborski Apr 23, 2025
f6e0ab1
ISOAddT35GroupDescription and other things
podborski Apr 28, 2025
7289fca
when defragmenting an empty stts make sure to add the first entry
podborski Jul 8, 2025
48ff054
just go for it :)
podborski Jul 8, 2025
aeb5405
add a simple test for T35 mebx
podborski Sep 9, 2025
fa9bb7f
initial implementation of t35_tool
podborski Sep 12, 2025
20e426a
add ISOBMFF colr box implementation
podborski Sep 12, 2025
0a47831
use cli11 lib
podborski Sep 12, 2025
0cfa797
demux mebx samples to binary blobs of data
podborski Sep 12, 2025
b21acf8
encapsulate into boxes when muxing, use trackreaders when deumxing
podborski Sep 15, 2025
a6d718c
this is not a bug, sorry for the noise
podborski Sep 15, 2025
f58de9a
API brushup, select mebx data based on key info
podborski Sep 16, 2025
cdcf1c6
dump NALUs from sample entry
podborski Sep 16, 2025
d7670da
add CLI option to set T.35 prefix
podborski Sep 16, 2025
3d8c6ab
initial implementatio of SEI injection
podborski Sep 16, 2025
3d2f68d
use rndr track reference type
podborski Sep 16, 2025
c25f3b3
Add inject and extract of SMPTE ST 2094-50 metadata from json file
Oct 2, 2025
5454552
Merge pull request #1 from img-standards/smpte_st_2094_50
podborski Oct 21, 2025
df88959
Fix gain max/span
Nov 6, 2025
4976c0c
put as text
podborski Nov 6, 2025
eae66de
search for correct mebx track, never give up :)
podborski Nov 15, 2025
754cbf0
Fix headroom offset and scaling (application version = 1111)
Nov 17, 2025
40ef7f2
Merge branch 't35_mebx' of https://github.pie.apple.com/img-standards…
Nov 17, 2025
075f394
write SEI T.35 header correctly
podborski Nov 17, 2025
2c07c9a
Update to use structure rather flat list and use latest version
Nov 21, 2025
a4f581f
Add classes for SMPTE 2094-50 -> Inject only working
Nov 21, 2025
ee1e039
Update decoding
Nov 22, 2025
e8724ee
Merge branch 'config_records' into t35
podborski Nov 27, 2025
6706e9b
Merge branch 'config_records' into t35
podborski Nov 27, 2025
199a23e
Pull and push data using simple function to make code simpler
Dec 1, 2025
131283c
Add dumping metadata items to json
Dec 1, 2025
f77697a
Add payload_data to metadataItems -> need to remove intermediate .bin…
Dec 22, 2025
0fdcf39
Update naming of component mix flag
Dec 22, 2025
42fa841
Merge pull request #2 from img-standards/ST2094_PCD_V2
podborski Jan 8, 2026
9e37f56
graceful handling of malformed JSONs
podborski Jan 13, 2026
022398b
add refactored files
podborski Jan 13, 2026
e60e8b2
Latest PC2 Update and cleaning some debug command
Jan 15, 2026
81a2895
Added verbose level amd cleaned up logging
Jan 15, 2026
e77814e
Remove intermediate binary data file
Jan 15, 2026
8cbef04
Auto-adjust metadata duration when exceeding video length
Jan 15, 2026
b1257bf
add T35 metadata track sample entry and box
podborski Jan 15, 2026
e01eaf9
Implement convenience API for easy track creation
podborski Jan 15, 2026
e50b00a
make sure trackreference type can be provided as arg
podborski Jan 15, 2026
4f427e1
implement dedicated T.35 track stuff
podborski Jan 15, 2026
e6cd833
me4c extractor + auto extractor
podborski Jan 15, 2026
b9b6625
make sure to use prefix in extraction mode. add tests
podborski Jan 15, 2026
2fa70db
make sure we can handle multiple keyboxes with same ns and value
podborski Jan 15, 2026
862f227
me4c seem to work now
podborski Jan 15, 2026
1d4d064
cache track reader
podborski Jan 15, 2026
dac5c82
add track caching to other 2 extractors
podborski Jan 15, 2026
b0e5b54
add sample groups
podborski Jan 15, 2026
0441103
reuse existing sample group description if found
podborski Jan 15, 2026
7c40e51
Add Readme.md
Jan 15, 2026
2cd8a71
Fix Printing metadata when no HATM
Jan 15, 2026
9338183
Add displaying of metadata when SMPTE at decode time
Jan 15, 2026
b8956b3
SEI extraction / cleanups
podborski Jan 19, 2026
77361c4
Merge branch 't35' into t35_tool
podborski Jan 19, 2026
84885a5
Add includes to SMPTE_ST2094_50.cpp
y-guyon Jan 28, 2026
dcb542d
Merge pull request #62 from y-guyon/patch-1
podborski Jan 28, 2026
cbedcbb
bugfix QTFF keys
podborski Feb 10, 2026
aa36f75
ST2094-50: fix quantization constant and refine logic for common flag…
Feb 10, 2026
b4a8f79
Merge branch 't35_tool' into t35_mebx
Feb 10, 2026
107349e
Merge pull request #4 from img-standards/t35_mebx
Feb 10, 2026
9f91e6f
remove mebx with it35 namespace as it was not agreed at MPEG
podborski Feb 10, 2026
8577419
remove t35C as it was not agreed at MPEG
podborski Feb 10, 2026
ba10ce1
cleanup testdata for t35_tool
podborski Feb 10, 2026
aa38431
remove legacy code
podborski Feb 10, 2026
5c4c744
test scripts updates
podborski Feb 10, 2026
0188969
me4c bugfix
podborski Feb 10, 2026
a0f9457
clangformat
podborski Feb 10, 2026
f2a1183
ST2094-50 Remove extra byte when no headroom-adaptive tone mapping
Feb 16, 2026
b7da55c
Update SMPTE_ST2094_50.cpp
Feb 17, 2026
83a6e12
Merge branch 'master' of https://github.com/MPEGGroup/isobmff into t3…
podborski Feb 17, 2026
7a4564f
update clang format stuff
podborski Feb 17, 2026
8454624
windows build fix
podborski Feb 18, 2026
387cc66
fix tests
podborski Feb 18, 2026
c6aa9d7
try fixing clang format thing
podborski Feb 18, 2026
3137a7c
[ST2094-50] angle to slope metadata item update
Feb 18, 2026
760edb5
Merge branch 't35_tool' of https://github.com/MPEGGroup/isobmff into …
Feb 18, 2026
2f96263
[ST2095-50] common flag check fix
Feb 25, 2026
5aff53c
Dedicated-it35 Fix close media edits
Mar 2, 2026
9f176cb
[ST2094-50] Fix at decode scaling of component mix when not equal to 1.0
Mar 3, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 13 additions & 6 deletions .github/workflows/clang-format.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,16 @@ jobs:
name: Formatting Check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Run clang-format style check for C/C++ programs.
uses: jidicula/clang-format-action@v4.11.0
with:
clang-format-version: '15'
check-path: 'IsoLib/libisomediafile'
- uses: actions/checkout@v4

- name: Install clang-format
run: |
wget https://apt.llvm.org/llvm.sh
chmod +x llvm.sh
sudo ./llvm.sh 18
sudo apt-get install -y clang-format-18

- name: Run clang-format check
run: |
find IsoLib/libisomediafile \( -name "*.h" -o -name "*.cpp" -o -name "*.c" \) | \
xargs clang-format-18 --dry-run --Werror -style=file
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -82,3 +82,7 @@ doc/
*.code-workspace
# Local History for Visual Studio Code
.history/

# T35 Tool Test Output
TestData/t35_tool/output_all_modes/
TestData/t35_tool/output_smpte/
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -50,5 +50,6 @@ if(NOT ISOBMFF_BUILD_LIB_ONLY)
add_subdirectory(IsoLib/isoiff_tool)
add_subdirectory(IsoLib/pcm_audio_example)
add_subdirectory(IsoLib/vvc_base)
add_subdirectory(IsoLib/t35_tool)
add_subdirectory(test)
endif()
2 changes: 2 additions & 0 deletions IsoLib/libisomediafile/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ add_library(
src/MetaboxRelationAtom.c
src/MJ2BitsPerComponentAtom.c
src/MJ2ColorSpecificationAtom.c
src/MP4ColourInformationAtom.c
src/MJ2FileTypeAtom.c
src/MJ2HeaderAtom.c
src/MJ2ImageHeaderAtom.c
Expand Down Expand Up @@ -186,6 +187,7 @@ add_library(
src/SubSampleInformationAtom.c
src/SubsegmentIndexAtom.c
src/SyncSampleAtom.c
src/T35MetadataSampleEntry.c
src/TextMetaSampleEntry.c
src/TimeToSampleAtom.c
src/TrackAtom.c
Expand Down
73 changes: 69 additions & 4 deletions IsoLib/libisomediafile/src/ISOMovies.h
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ extern "C"
#define ISOOpenMovieInPlace MP4OpenMovieInPlace

struct MP4BoxedMetadataSampleEntry;
struct MP4T35MetadataSampleEntry;

/**
* @brief constants for the graphics modes (e.g. for MJ2SetMediaGraphicsMode)
Expand Down Expand Up @@ -311,7 +312,8 @@ extern "C"
#define ISOGetUserDataTypeCount MP4GetUserDataTypeCount
#define ISONewUserData MP4NewUserData
#define ISOCreateTrackReader MP4CreateTrackReader
#define ISOSetMebxTrackReader MP4SetMebxTrackReader
#define ISOSetMebxTrackReaderLocalKeyId MP4SetMebxTrackReaderLocalKeyId
#define ISOSelectFirstMebxTrackReaderKey MP4SelectFirstMebxTrackReaderKey
#define ISODisposeTrackReader MP4DisposeTrackReader
#define ISONewHandle MP4NewHandle
#define ISOSetHandleSize MP4SetHandleSize
Expand Down Expand Up @@ -800,13 +802,76 @@ extern "C"
* @param sampleEntryH input sample entry of the mebx track
* @param key_cnt number of local_key_id's
*/
ISO_EXTERN(ISOErr)
ISOGetMebxMetadataCount(MP4Handle sampleEntryH, u32 *key_cnt);
ISO_EXTERN(ISOErr) ISOGetMebxMetadataCount(MP4Handle sampleEntryH, u32 *key_cnt);

/**
* @brief Get metadata key configuration from a 'mebx' sample entry.
*
* Retrieves the key information at index @p idx from the MetadataKeyTableBox. Returns namespace,
* value, locale, setup data, and the local_key_id for this entry.
*
* @param sampleEntryH Handle containing the 'mebx' sample entry.
* @param idx Zero-based index of the key entry to query.
* @param local_key_id Output; receives the local_key_id for this key.
* @param key_namespace Output; receives the namespace FourCC.
* @param key_value Optional handle to receive the key value data.
* @param locale_string Optional; receives locale string if present.
* @param setupInfo Optional handle to receive setup information if present.
*
* @return ISOErr code: MP4NoErr on success, MP4BadDataErr if no key table, MP4NotFoundErr if not
* found, or other error codes.
*/
ISO_EXTERN(ISOErr)
ISOGetMebxMetadataConfig(MP4Handle sampleEntryH, u32 cnt, u32 *local_key_id, u32 *key_namespace,
ISOGetMebxMetadataConfig(MP4Handle sampleEntryH, u32 idx, u32 *local_key_id, u32 *key_namespace,
MP4Handle key_value, char **locale_string, MP4Handle setupInfo);

/*************************************************************************************************
* T.35 Metadata Track Functions
************************************************************************************************/

/**
* @brief Create a new T.35 metadata sample entry.
* @ingroup SampleDescr
*
* Creates a T35MetadataSampleEntry ('it35') with a T35CommonHeaderBox ('t35C') containing
* the specified T.35 prefix text.
*
* @param outSE Output; receives the created T35MetadataSampleEntry.
* @param dataReferenceIndex Data reference index (typically 1 for self-contained media).
* @param t35_prefix_text UTF-8 string conforming to format: T35Prefix[:T35Description]
* where T35Prefix is even number of uppercase hex digits (0-9, A-F).
* Example: "B500900001:SMPTE-ST2094-50"
* @return MP4Err code: MP4NoErr on success, MP4BadParamErr if validation fails.
*/
MP4_EXTERN(MP4Err)
ISONewT35SampleDescription(struct MP4T35MetadataSampleEntry **outSE, u32 dataReferenceIndex,
const char *t35_prefix_text);

/**
* @brief Create a complete T.35 timed metadata track.
* @ingroup Tracks
*
* Convenience function that creates a metadata track with MP4MetaHandlerType,
* adds a T35MetadataSampleEntry with the specified T.35 prefix, and optionally
* adds a track reference to a video track using the 'rndr' reference type.
*
* After calling this function, use MP4AddMediaSample() or similar functions to
* add T.35 metadata samples to the track.
*
* @param theMovie Input movie object.
* @param timescale Media timescale (typically matches video track timescale).
* @param t35_prefix_text UTF-8 T.35 prefix string (e.g., "B500900001:SMPTE-ST2094-50").
* @param videoTrack Optional video track for track reference (NULL if not needed).
* @param trackReferenceType Track reference type or 0 for no reference.
* @param outTrack Output; receives the created metadata track.
* @param outMedia Optional output; receives the created media (NULL if not needed).
* @return ISOErr code: MP4NoErr on success, error code otherwise.
*/
ISO_EXTERN(ISOErr)
ISONewT35MetadataTrack(MP4Movie theMovie, u32 timescale, const char *t35_prefix_text,
MP4Track videoTrack, u32 trackReferenceType, MP4Track *outTrack,
MP4Media *outMedia);

/*************************************************************************************************
* VVC Sample descriptions
************************************************************************************************/
Expand Down
215 changes: 201 additions & 14 deletions IsoLib/libisomediafile/src/ISOSampleDescriptions.c
Original file line number Diff line number Diff line change
Expand Up @@ -1211,17 +1211,6 @@ ISOGetHEVCNALUs(MP4Handle sampleEntryH, MP4Handle nalus, u32 extraction_mode)
err = sampleEntryHToAtomPtr(sampleEntryH, (MP4AtomPtr *)&entry, MP4VisualSampleEntryAtomType);
if(err) goto bail;

if(entry->type == MP4EncVisualSampleEntryAtomType ||
entry->type == MP4RestrictedVideoSampleEntryAtomType)
{
u32 origFmt = 0;
err = ISOGetOriginalFormat(sampleEntryH, &origFmt);
if(origFmt != ISOHEVCSampleEntryAtomType && origFmt != ISOLHEVCSampleEntryAtomType)
BAILWITHERROR(MP4BadParamErr);
}
else if(entry->type != ISOHEVCSampleEntryAtomType && entry->type != ISOLHEVCSampleEntryAtomType)
BAILWITHERROR(MP4BadParamErr);

MP4GetListEntryAtom(entry->ExtensionAtomList, ISOHEVCConfigAtomType, (MP4AtomPtr *)&hvcC);
MP4GetListEntryAtom(entry->ExtensionAtomList, ISOLHEVCConfigAtomType, (MP4AtomPtr *)&lhvC);

Expand Down Expand Up @@ -1882,7 +1871,7 @@ ISOAddMebxMetadataToSampleEntry(MP4BoxedMetadataSampleEntryPtr mebx, u32 desired
if(err) goto bail;
}

keytable->addMetaDataKeyBox(keytable, (MP4AtomPtr)keyb);
err = keytable->addMetaDataKeyBox(keytable, (MP4AtomPtr)keyb);
if(err) goto bail;

bail:
Expand Down Expand Up @@ -1920,7 +1909,7 @@ ISOGetMebxMetadataCount(MP4Handle sampleEntryH, u32 *key_cnt)
}

ISO_EXTERN(ISOErr)
ISOGetMebxMetadataConfig(MP4Handle sampleEntryH, u32 cnt, u32 *local_key_id, u32 *key_namespace,
ISOGetMebxMetadataConfig(MP4Handle sampleEntryH, u32 idx, u32 *local_key_id, u32 *key_namespace,
MP4Handle key_value, char **locale_string, MP4Handle setupInfo)
{
MP4Err err;
Expand All @@ -1933,7 +1922,7 @@ ISOGetMebxMetadataConfig(MP4Handle sampleEntryH, u32 cnt, u32 *local_key_id, u32

if(entry->keyTable == NULL) BAILWITHERROR(MP4BadDataErr);

err = MP4GetListEntry(entry->keyTable->metadataKeyBoxList, cnt, (char **)&key);
err = MP4GetListEntry(entry->keyTable->metadataKeyBoxList, idx, (char **)&key);
if(err) goto bail;

/* set output values */
Expand Down Expand Up @@ -2419,3 +2408,201 @@ ISOGetVVCSubpicSampleDescription(MP4Handle sampleEntryH, u32 *dataReferenceIndex
if(entry) entry->destroy((MP4AtomPtr)entry);
return err;
}

/* ==================== T.35 Metadata Track Functions ==================== */

/* Helper: Parse T.35 prefix string into hex identifier and description
* Format: "HEXSTRING:Description"
* Example: "B500900001:SMPTE-ST2094-50"
*/
static MP4Err parseT35PrefixString(const char *t35_prefix_text, u8 **outIdentifier,
u32 *outIdentifierSize, char **outDescription)
{
MP4Err err;
const char *colon;
const char *hexStart;
size_t hexLen;
u32 identifierSize;
u8 *identifier;
char *description;

if(t35_prefix_text == NULL || outIdentifier == NULL || outIdentifierSize == NULL ||
outDescription == NULL)
BAILWITHERROR(MP4BadParamErr);

/* Find colon separator */
colon = strchr(t35_prefix_text, ':');
if(colon)
{
hexLen = colon - t35_prefix_text;
}
else
{
hexLen = strlen(t35_prefix_text);
}

/* Check if hex length is even */
if(hexLen % 2 != 0) BAILWITHERROR(MP4BadParamErr);

identifierSize = (u32)(hexLen / 2);
if(identifierSize == 0) BAILWITHERROR(MP4BadParamErr);

/* Allocate identifier buffer */
identifier = (u8 *)calloc(identifierSize, 1);
if(identifier == NULL) BAILWITHERROR(MP4NoMemoryErr);

/* Parse hex string */
hexStart = t35_prefix_text;
for(u32 i = 0; i < identifierSize; i++)
{
char hexByte[3];
hexByte[0] = hexStart[i * 2];
hexByte[1] = hexStart[i * 2 + 1];
hexByte[2] = '\0';

char *endPtr;
unsigned long value = strtoul(hexByte, &endPtr, 16);
if(*endPtr != '\0' || value > 255)
{
free(identifier);
BAILWITHERROR(MP4BadParamErr);
}
identifier[i] = (u8)value;
}

/* Parse description (after colon) */
if(colon && colon[1] != '\0')
{
size_t descLen = strlen(colon + 1);
description = (char *)calloc(descLen + 1, 1);
if(description == NULL)
{
free(identifier);
BAILWITHERROR(MP4NoMemoryErr);
}
strcpy(description, colon + 1);
}
else
{
/* Empty description */
description = NULL;
}

*outIdentifier = identifier;
*outIdentifierSize = identifierSize;
*outDescription = description;

return MP4NoErr;

bail:
TEST_RETURN(err);
return err;
}

MP4_EXTERN(MP4Err)
ISONewT35SampleDescription(MP4T35MetadataSampleEntryPtr *outSE, u32 dataReferenceIndex,
const char *t35_prefix_text)
{
MP4Err err;
MP4T35MetadataSampleEntryPtr it35;
u8 *identifier = NULL;
u32 identifierSize = 0;
char *description = NULL;

if(outSE == NULL || t35_prefix_text == NULL) BAILWITHERROR(MP4BadParamErr);

/* Parse t35_prefix_text into identifier and description */
err = parseT35PrefixString(t35_prefix_text, &identifier, &identifierSize, &description);
if(err) goto bail;

/* Create T35 sample entry */
err = MP4CreateT35MetadataSampleEntry(&it35);
if(err) goto bail;
it35->dataReferenceIndex = dataReferenceIndex;

/* Set description and t35_identifier fields */
it35->description = description;
it35->t35_identifier = identifier;
it35->t35_identifier_size = identifierSize;

*outSE = it35;

return MP4NoErr;

bail:
if(identifier) free(identifier);
if(description) free(description);
TEST_RETURN(err);
return err;
}

ISO_EXTERN(ISOErr)
ISONewT35MetadataTrack(MP4Movie theMovie, u32 timescale, const char *t35_prefix_text,
MP4Track videoTrack, u32 trackReferenceType, MP4Track *outTrack,
MP4Media *outMedia)
{
MP4Err err;
MP4Track trakM = NULL;
MP4Media mediaM = NULL;
MP4T35MetadataSampleEntryPtr it35 = NULL;
MP4Handle sampleEntryH = NULL;
MP4PrivateMovieRecordPtr moov = NULL;
MP4TrackAtomPtr trakAtom = NULL;

if(theMovie == NULL || t35_prefix_text == NULL || outTrack == NULL) BAILWITHERROR(MP4BadParamErr);

moov = (MP4PrivateMovieRecordPtr)theMovie;

/* Create metadata track */
err = MP4NewMovieTrack(theMovie, MP4NewTrackIsMetadata, &trakM);
if(err) goto bail;

/* Create media with MP4MetaHandlerType */
err = MP4NewTrackMedia(trakM, &mediaM, MP4MetaHandlerType, timescale, NULL);
if(err) goto bail;

/* Add track reference if both videoTrack and trackReferenceType are provided */
if(videoTrack != NULL && trackReferenceType != 0)
{
err = MP4AddTrackReference(trakM, videoTrack, trackReferenceType, 0);
if(err) goto bail;
}

/* Create T35 sample entry with description and t35_identifier */
err = ISONewT35SampleDescription(&it35, 1, t35_prefix_text);
if(err) goto bail;

/* Convert sample entry to handle */
err = MP4NewHandle(0, &sampleEntryH);
if(err) goto bail;

/* Use atomPtrToSampleEntryH helper */
err = atomPtrToSampleEntryH(sampleEntryH, (MP4AtomPtr)it35);
if(err) goto bail;

/* Add sample entry to media (index 0 means add to sample description table) */
err = MP4AddMediaSamples(mediaM, 0, 0, 0, 0, sampleEntryH, 0, 0);
if(err) goto bail;

/* Dispose the handle after adding */
MP4DisposeHandle(sampleEntryH);
sampleEntryH = NULL;

/* Set the mdat reference for the track */
trakAtom = (MP4TrackAtomPtr)trakM;
if(trakAtom && moov->mdat)
{
err = trakAtom->setMdat(trakAtom, moov->mdat);
if(err) goto bail;
}

*outTrack = trakM;
if(outMedia) *outMedia = mediaM;

bail:
if(sampleEntryH) MP4DisposeHandle(sampleEntryH);
if(it35) it35->destroy((MP4AtomPtr)it35);

TEST_RETURN(err);
return err;
}
Loading
Loading