Task 436: .MX File Format

Task 436: .MX File Format

File Format Specifications for the .MX File Format

The .MX file format appears to refer to the Material Exchange Format (MXF), a standardized container format for professional media content (video, audio, and metadata), defined by SMPTE ST 377-1:2019. (Note: The standard extension is .mxf, but the query uses .MX, which may be a shorthand or variant notation; no separate .mx format matches exactly, but MXF fits the description of a file format with detailed specifications and properties intrinsic to its structure. If this is not the intended format, please clarify.) MXF is designed for interchange of audio-visual material, using KLV (Key-Length-Value) encoding. It supports embedding or referencing essence (media streams) and metadata, with partitions for logical separation, multiplexing, and random access. The format is extensible via operational patterns (OPs) and plug-ins, with Big Endian byte order and optional alignment grid.

  1. List of all the properties of this file format intrinsic to its file system (structure and characteristics):
  • Closed MXF File: File with Closed Header/Footer Partition containing Header Metadata; all values finalized/correct.
  • Open MXF File: No Closed Header/Footer with Metadata; values unfinalized; decoders may decode but not required to support.
  • Run-In Sequence: Optional non-KLV padding at start for alignment/synchronization (<65KB); decoders ignore and search for first Partition Pack Key.
  • KLV Coding Sequence: Required KLV triplets (Key: 16-byte UL, Length: BER UInt64, Value); all data KLV-coded except Run-In.
  • KLV Fill Items: Optional padding (Key: 06.0E.2B.34.01.01.01.02.03.01.02.10.01.00.00.00, Value: zero bytes, min 17 bytes).
  • KLV Lengths: BER UInt64 (short/long-form up to 9 bytes); specific for Local Sets, Variable Packs, Defined Packs.
  • MXF Keys and Universal Labels (ULs): 16-byte ULs registered in SMPTE ST 335/395/400; no recursive groupings unless specified.
  • KLV Alignment Grid (KAG): Optional UInt32 byte alignment (1=no grid, 2-1048576=size); constant per Essence Container.
  • MXF Byte Order: Big Endian for multi-byte values; essence per Container spec.
  • Minimum MXF Decoder Requirements: Parse KLV, handle Partitions/references; locate Header Pack, read OP/Essence ULs, check compatibility.
  • Strong Reference Integrity: UUID-based, one-to-one, same-file to Instance UID.
  • Weak Reference Integrity: UL/UUID/UMID/PackageID, many-to-one, typed; unresolved OK for external/Dark.
  • File Version Manipulation: Update VersionType; Major=1, Minor=3 for 2019 compliance.
  • Partition Rules: Essence by BodySID; Index by IndexSID; unique SIDs; no Essence in Footer.
  • Partition Status: Enum (Open/Incomplete, Closed/Incomplete, Open/Complete, Closed/Complete).
  • Header Partition: Required first; BodySID=0, IndexSID=0; HeaderByteCount>0.
  • Body Partition: Optional; BodySID>0; optional Metadata/Index/Essence.
  • Footer Partition: Optional last; BodySID=0, IndexSID=0; always Closed.
  • Header Metadata Repetition: Optional for recovery; track via Generation UID.
  • Generation UID Tracking: UUID in Metadata Sets; update on modifications.
  • Partition Pack Properties: MajorVer=0001h, MinorVer=0003h; KAGSize, This/Prev/NextPartition, Header/IndexByteCount, BodyOffset, OP UL, Essence Containers Batch.
  • KLV Coded Dark Components: Mark unknown Essence/Metadata as "Dark".
  • Local Set Lengths: 1/2/4-byte tags for Metadata.
  • Variable/Defined-Length Packs: Dynamic/fixed sizes.
  • Primer Pack: Required if Metadata; maps Local Tags to AUIDs (UInt16 Tag + 16-byte AUID).
  • Encoding Constraints: No deviation from "shall" rules; UL/UUID storage with byte swap for AUID/IDAU.
  • Generic UL: Common for all OPs.
  • Generalized OP: Flexible item/package complexity.
  • Specialized OP: Constrained subsets; may omit Footer.
  • Package Hierarchy: Material Package refs Top-Level File Packages; Lower-Level for history.
  • Preface Set (Root): LastModifiedDate (Timestamp), Version (VersionType=259/v1.3), ObjectModelVersion (UInt32=1), PrimaryPackage (WeakRef), Identifications (Array StrongRef), ContentStorage (StrongRef), OperationalPatternUL (UL), EssenceContainers (Batch UL), DMSchemes (Batch UL), ApplicationSchemesBatch (Batch UL), IsRIPPresent (Boolean).
  • Identification Set: ThisGenerationUID (UUID), CompanyName (UTF-16), ProductName (UTF-16), ProductVersion (10-byte), VersionString (UTF-16), ProductUID (UUID), ModificationDate (Timestamp), ToolkitVersion (10-byte), Platform (UTF-16).
  • Content Storage Set: Packages (Array StrongRef Package), EssenceData (Array StrongRef EssenceData).
  1. Two direct download links for files of format .MX (.mxf):
  1. Ghost blog embedded HTML JavaScript for drag and drop .MX file to dump properties:
MXF File Property Dumper
Drag and drop .mxf file here
  1. Python class for opening, decoding, reading, writing, and printing .MX properties:
import struct
import uuid

class MXFHandler:
    def __init__(self, filepath=None):
        self.filepath = filepath
        self.properties = {}
        if filepath:
            self.read(filepath)

    def read(self, filepath):
        with open(filepath, 'rb') as f:
            data = f.read()
        self._parse_properties(data)
        self.filepath = filepath

    def _parse_properties(self, data):
        # Parse Partition Pack Key (16 bytes)
        key = '.'.join(f'{b:02x}' for b in data[0:16])
        self.properties['Partition Pack Key'] = key

        # Major/Minor Version (UInt16 BE each)
        major, minor = struct.unpack('>BB', data[16:18])
        self.properties['Major Version'] = major
        self.properties['Minor Version'] = minor

        # KAG Size (UInt32 BE)
        self.properties['KAG Size'] = struct.unpack('>I', data[18:22])[0]

        # This Partition (UInt64 BE)
        self.properties['This Partition'] = struct.unpack('>Q', data[22:30])[0]

        # Previous Partition (UInt64 BE)
        self.properties['Previous Partition'] = struct.unpack('>Q', data[30:38])[0]

        # Footer Partition (UInt64 BE)
        self.properties['Footer Partition'] = struct.unpack('>Q', data[38:46])[0]

        # Header Byte Count (UInt64 BE)
        self.properties['Header Byte Count'] = struct.unpack('>Q', data[46:54])[0]

        # Index Byte Count (UInt64 BE)
        self.properties['Index Byte Count'] = struct.unpack('>Q', data[54:62])[0]

        # Index SID (UInt32 BE)
        self.properties['Index SID'] = struct.unpack('>I', data[62:66])[0]

        # Body Offset (UInt64 BE)
        self.properties['Body Offset'] = struct.unpack('>Q', data[66:74])[0]

        # Body SID (UInt32 BE)
        self.properties['Body SID'] = struct.unpack('>I', data[74:78])[0]

        # Operational Pattern UL (16 bytes)
        op_ul = '.'.join(f'{b:02x}' for b in data[78:94])
        self.properties['Operational Pattern UL'] = op_ul

        # Essence Containers Batch (UInt32 count, UInt32 len, then ULs)
        essence_count = struct.unpack('>I', data[94:98])[0]
        self.properties['Essence Containers Count'] = essence_count
        # len = struct.unpack('>I', data[98:102])[0]  # Typically 16 * count
        self.properties['Essence Containers ULs'] = []
        offset = 102
        for _ in range(essence_count):
            ul = '.'.join(f'{b:02x}' for b in data[offset:offset+16])
            self.properties['Essence Containers ULs'].append(ul)
            offset += 16

    def print_properties(self):
        for key, value in self.properties.items():
            print(f"{key}: {value}")

    def write(self, filepath=None):
        if not filepath:
            filepath = self.filepath or 'output.mxf'
        # Basic write: reconstruct Partition Pack from properties (minimal, no full essence/metadata)
        data = bytearray()
        # Key (parse from string)
        key_bytes = bytes(int(b, 16) for b in self.properties.get('Partition Pack Key', '06.0e.2b.34.02.05.01.01.0d.01.02.01.01.02.04.00').split('.'))
        data.extend(key_bytes)

        # Major/Minor
        data.extend(struct.pack('>BB', self.properties.get('Major Version', 1), self.properties.get('Minor Version', 3)))

        # KAG Size
        data.extend(struct.pack('>I', self.properties.get('KAG Size', 1)))

        # This/Previous/Footer Partition
        data.extend(struct.pack('>Q', self.properties.get('This Partition', 0)))
        data.extend(struct.pack('>Q', self.properties.get('Previous Partition', 0)))
        data.extend(struct.pack('>Q', self.properties.get('Footer Partition', 0)))

        # Header/Index Byte Count
        data.extend(struct.pack('>Q', self.properties.get('Header Byte Count', 0)))
        data.extend(struct.pack('>Q', self.properties.get('Index Byte Count', 0)))

        # Index SID
        data.extend(struct.pack('>I', self.properties.get('Index SID', 0)))

        # Body Offset
        data.extend(struct.pack('>Q', self.properties.get('Body Offset', 0)))

        # Body SID
        data.extend(struct.pack('>I', self.properties.get('Body SID', 0)))

        # OP UL
        op_bytes = bytes(int(b, 16) for b in self.properties.get('Operational Pattern UL', '06.0e.2b.34.04.01.01.0d.0d.01.02.01.01.02.00.00').split('.'))
        data.extend(op_bytes)

        # Essence Batch (count, len=16*count, ULs)
        essence_uls = self.properties.get('Essence Containers ULs', [])
        count = len(essence_uls)
        data.extend(struct.pack('>I', count))
        data.extend(struct.pack('>I', 16 * count))
        for ul in essence_uls:
            ul_bytes = bytes(int(b, 16) for b in ul.split('.'))
            data.extend(ul_bytes)

        with open(filepath, 'wb') as f:
            f.write(data)
        print(f"Written minimal MXF to {filepath}")

# Example usage:
# handler = MXFHandler('path/to/file.mxf')
# handler.print_properties()
# handler.write('new.mxf')
  1. Java class for opening, decoding, reading, writing, and printing .MX properties:
import java.io.*;
import java.nio.*;
import java.nio.file.*;
import java.util.*;

public class MXFHandler {
    private String filepath;
    private Map<String, Object> properties = new HashMap<>();

    public MXFHandler(String filepath) {
        this.filepath = filepath;
        read(filepath);
    }

    public void read(String filepath) {
        try {
            byte[] data = Files.readAllBytes(Paths.get(filepath));
            ByteBuffer buffer = ByteBuffer.wrap(data).order(ByteOrder.BIG_ENDIAN);
            parseProperties(buffer);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private void parseProperties(ByteBuffer buffer) {
        // Partition Pack Key (16 bytes)
        byte[] keyBytes = new byte[16];
        buffer.get(keyBytes);
        properties.put("Partition Pack Key", bytesToHex(keyBytes));

        // Major/Minor Version
        properties.put("Major Version", buffer.get() & 0xFF);
        properties.put("Minor Version", buffer.get() & 0xFF);

        // KAG Size (UInt32)
        properties.put("KAG Size", buffer.getInt());

        // This Partition (UInt64)
        properties.put("This Partition", buffer.getLong());

        // Previous Partition
        properties.put("Previous Partition", buffer.getLong());

        // Footer Partition
        properties.put("Footer Partition", buffer.getLong());

        // Header Byte Count
        properties.put("Header Byte Count", buffer.getLong());

        // Index Byte Count
        properties.put("Index Byte Count", buffer.getLong());

        // Index SID (UInt32)
        properties.put("Index SID", buffer.getInt());

        // Body Offset
        properties.put("Body Offset", buffer.getLong());

        // Body SID
        properties.put("Body SID", buffer.getInt());

        // Operational Pattern UL (16 bytes)
        byte[] opBytes = new byte[16];
        buffer.get(opBytes);
        properties.put("Operational Pattern UL", bytesToHex(opBytes));

        // Essence Containers Batch
        int essenceCount = buffer.getInt();
        properties.put("Essence Containers Count", essenceCount);
        buffer.getInt(); // len, skip
        List<String> uls = new ArrayList<>();
        for (int i = 0; i < essenceCount; i++) {
            byte[] ulBytes = new byte[16];
            buffer.get(ulBytes);
            uls.add(bytesToHex(ulBytes));
        }
        properties.put("Essence Containers ULs", uls);
    }

    private String bytesToHex(byte[] bytes) {
        StringBuilder sb = new StringBuilder();
        for (byte b : bytes) {
            if (sb.length() > 0) sb.append('.');
            sb.append(String.format("%02x", b));
        }
        return sb.toString();
    }

    public void printProperties() {
        properties.forEach((k, v) -> System.out.println(k + ": " + v));
    }

    public void write(String filepath) {
        try (FileOutputStream fos = new FileOutputStream(filepath)) {
            ByteBuffer buffer = ByteBuffer.allocate(1024).order(ByteOrder.BIG_ENDIAN);
            // Key
            hexToBytes((String) properties.getOrDefault("Partition Pack Key", "06.0e.2b.34.02.05.01.01.0d.01.02.01.01.02.04.00"), buffer);

            // Major/Minor
            buffer.put((byte) (int) properties.getOrDefault("Major Version", 1));
            buffer.put((byte) (int) properties.getOrDefault("Minor Version", 3));

            // KAG Size
            buffer.putInt((int) properties.getOrDefault("KAG Size", 1));

            // This/Previous/Footer
            buffer.putLong((long) properties.getOrDefault("This Partition", 0L));
            buffer.putLong((long) properties.getOrDefault("Previous Partition", 0L));
            buffer.putLong((long) properties.getOrDefault("Footer Partition", 0L));

            // Header/Index Byte Count
            buffer.putLong((long) properties.getOrDefault("Header Byte Count", 0L));
            buffer.putLong((long) properties.getOrDefault("Index Byte Count", 0L));

            // Index SID
            buffer.putInt((int) properties.getOrDefault("Index SID", 0));

            // Body Offset
            buffer.putLong((long) properties.getOrDefault("Body Offset", 0L));

            // Body SID
            buffer.putInt((int) properties.getOrDefault("Body SID", 0));

            // OP UL
            hexToBytes((String) properties.getOrDefault("Operational Pattern UL", "06.0e.2b.34.04.01.01.0d.0d.01.02.01.01.02.00.00"), buffer);

            // Essence Batch
            List<String> uls = (List<String>) properties.getOrDefault("Essence Containers ULs", new ArrayList<>());
            int count = uls.size();
            buffer.putInt(count);
            buffer.putInt(16 * count);
            for (String ul : uls) {
                hexToBytes(ul, buffer);
            }

            buffer.flip();
            fos.write(buffer.array(), 0, buffer.limit());
            System.out.println("Written minimal MXF to " + filepath);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private void hexToBytes(String hex, ByteBuffer buffer) {
        String[] hexParts = hex.split("\\.");
        for (String part : hexParts) {
            buffer.put((byte) Integer.parseInt(part, 16));
        }
    }

    // Example usage:
    // public static void main(String[] args) {
    //     MXFHandler handler = new MXFHandler("path/to/file.mxf");
    //     handler.printProperties();
    //     handler.write("new.mxf");
    // }
}
  1. JavaScript class for opening, decoding, reading, writing, and printing .MX properties:
class MXFHandler {
    constructor(filepath = null) {
        this.filepath = filepath;
        this.properties = {};
        if (filepath) {
            this.read(filepath);
        }
    }

    async read(filepath) {
        try {
            const response = await fetch(filepath);
            const arrayBuffer = await response.arrayBuffer();
            const dataView = new DataView(arrayBuffer);
            this.parseProperties(dataView);
        } catch (e) {
            console.error(e);
        }
    }

    parseProperties(dataView) {
        // Partition Pack Key (16 bytes)
        let key = [];
        for (let i = 0; i < 16; i++) {
            key.push(dataView.getUint8(i).toString(16).padStart(2, '0'));
        }
        this.properties['Partition Pack Key'] = key.join('.');

        // Major/Minor Version
        this.properties['Major Version'] = dataView.getUint8(16);
        this.properties['Minor Version'] = dataView.getUint8(17);

        // KAG Size
        this.properties['KAG Size'] = dataView.getUint32(18);

        // This Partition
        this.properties['This Partition'] = dataView.getBigUint64(22);

        // Previous Partition
        this.properties['Previous Partition'] = dataView.getBigUint64(30);

        // Footer Partition
        this.properties['Footer Partition'] = dataView.getBigUint64(38);

        // Header Byte Count
        this.properties['Header Byte Count'] = dataView.getBigUint64(46);

        // Index Byte Count
        this.properties['Index Byte Count'] = dataView.getBigUint64(54);

        // Index SID
        this.properties['Index SID'] = dataView.getUint32(62);

        // Body Offset
        this.properties['Body Offset'] = dataView.getBigUint64(66);

        // Body SID
        this.properties['Body SID'] = dataView.getUint32(74);

        // Operational Pattern UL
        let op = [];
        for (let i = 78; i < 94; i++) {
            op.push(dataView.getUint8(i).toString(16).padStart(2, '0'));
        }
        this.properties['Operational Pattern UL'] = op.join('.');

        // Essence Containers Batch
        const essenceCount = dataView.getUint32(94);
        this.properties['Essence Containers Count'] = essenceCount;
        // Skip len at 98
        this.properties['Essence Containers ULs'] = [];
        let offset = 102;
        for (let i = 0; i < essenceCount; i++) {
            let ul = [];
            for (let j = 0; j < 16; j++) {
                ul.push(dataView.getUint8(offset + j).toString(16).padStart(2, '0'));
            }
            this.properties['Essence Containers ULs'].push(ul.join('.'));
            offset += 16;
        }
    }

    printProperties() {
        console.log(this.properties);
    }

    write(filepath) {
        // Basic write using Blob (for browser download)
        const buffer = new ArrayBuffer(1024);
        const dataView = new DataView(buffer);

        // Key
        const keyParts = this.properties['Partition Pack Key'] ? this.properties['Partition Pack Key'].split('.') : '06.0e.2b.34.02.05.01.01.0d.01.02.01.01.02.04.00'.split('.');
        keyParts.forEach((part, i) => dataView.setUint8(i, parseInt(part, 16)));

        let offset = 16;
        // Major/Minor
        dataView.setUint8(offset++, this.properties['Major Version'] || 1);
        dataView.setUint8(offset++, this.properties['Minor Version'] || 3);

        // KAG Size
        dataView.setUint32(offset, this.properties['KAG Size'] || 1);
        offset += 4;

        // This/Previous/Footer
        dataView.setBigUint64(offset, BigInt(this.properties['This Partition'] || 0));
        offset += 8;
        dataView.setBigUint64(offset, BigInt(this.properties['Previous Partition'] || 0));
        offset += 8;
        dataView.setBigUint64(offset, BigInt(this.properties['Footer Partition'] || 0));
        offset += 8;

        // Header/Index Byte Count
        dataView.setBigUint64(offset, BigInt(this.properties['Header Byte Count'] || 0));
        offset += 8;
        dataView.setBigUint64(offset, BigInt(this.properties['Index Byte Count'] || 0));
        offset += 8;

        // Index SID
        dataView.setUint32(offset, this.properties['Index SID'] || 0);
        offset += 4;

        // Body Offset
        dataView.setBigUint64(offset, BigInt(this.properties['Body Offset'] || 0));
        offset += 8;

        // Body SID
        dataView.setUint32(offset, this.properties['Body SID'] || 0);
        offset += 4;

        // OP UL
        const opParts = this.properties['Operational Pattern UL'] ? this.properties['Operational Pattern UL'].split('.') : '06.0e.2b.34.04.01.01.0d.0d.01.02.01.01.02.00.00'.split('.');
        opParts.forEach((part) => dataView.setUint8(offset++, parseInt(part, 16)));

        // Essence Batch
        const uls = this.properties['Essence Containers ULs'] || [];
        dataView.setUint32(offset, uls.length);
        offset += 4;
        dataView.setUint32(offset, 16 * uls.length);
        offset += 4;
        uls.forEach((ul) => {
            ul.split('.').forEach((part) => dataView.setUint8(offset++, parseInt(part, 16)));
        });

        const blob = new Blob([buffer.slice(0, offset)], {type: 'application/octet-stream'});
        const url = URL.createObjectURL(blob);
        const a = document.createElement('a');
        a.href = url;
        a.download = filepath || 'output.mxf';
        a.click();
        console.log('Written minimal MXF as download.');
    }
}

// Example usage:
// const handler = new MXFHandler('path/to/file.mxf');
// handler.printProperties();
// handler.write('new.mxf');
  1. C class (using C++ for class support) for opening, decoding, reading, writing, and printing .MX properties:
#include <iostream>
#include <fstream>
#include <iomanip>
#include <vector>
#include <cstdint>
#include <cstring>
#include <endian.h> // For big endian conversions, may need to include <byteswap.h> on some systems

class MXFHandler {
private:
    std::string filepath;
    std::map<std::string, std::string> properties; // Use string for simplicity, convert as needed

public:
    MXFHandler(const std::string& fp = "") : filepath(fp) {
        if (!fp.empty()) read(fp);
    }

    void read(const std::string& fp) {
        filepath = fp;
        std::ifstream file(fp, std::ios::binary);
        if (!file) {
            std::cerr << "Error opening file" << std::endl;
            return;
        }
        file.seekg(0, std::ios::end);
        size_t size = file.tellg();
        file.seekg(0);
        std::vector<uint8_t> data(size);
        file.read(reinterpret_cast<char*>(data.data()), size);
        parseProperties(data.data());
    }

    void parseProperties(const uint8_t* data) {
        // Partition Pack Key (16 bytes)
        std::stringstream ss;
        for (int i = 0; i < 16; ++i) {
            if (i > 0) ss << '.';
            ss << std::setfill('0') << std::setw(2) << std::hex << static_cast<int>(data[i]);
        }
        properties["Partition Pack Key"] = ss.str();

        // Major/Minor Version
        properties["Major Version"] = std::to_string(data[16]);
        properties["Minor Version"] = std::to_string(data[17]);

        // KAG Size (be32toh)
        uint32_t kag = be32toh(*reinterpret_cast<const uint32_t*>(data + 18));
        properties["KAG Size"] = std::to_string(kag);

        // This Partition (be64toh)
        uint64_t thisPart = be64toh(*reinterpret_cast<const uint64_t*>(data + 22));
        properties["This Partition"] = std::to_string(thisPart);

        // Previous Partition
        uint64_t prevPart = be64toh(*reinterpret_cast<const uint64_t*>(data + 30));
        properties["Previous Partition"] = std::to_string(prevPart);

        // Footer Partition
        uint64_t footPart = be64toh(*reinterpret_cast<const uint64_t*>(data + 38));
        properties["Footer Partition"] = std::to_string(footPart);

        // Header Byte Count
        uint64_t headCount = be64toh(*reinterpret_cast<const uint64_t*>(data + 46));
        properties["Header Byte Count"] = std::to_string(headCount);

        // Index Byte Count
        uint64_t indexCount = be64toh(*reinterpret_cast<const uint64_t*>(data + 54));
        properties["Index Byte Count"] = std::to_string(indexCount);

        // Index SID
        uint32_t indexSid = be32toh(*reinterpret_cast<const uint32_t*>(data + 62));
        properties["Index SID"] = std::to_string(indexSid);

        // Body Offset
        uint64_t bodyOff = be64toh(*reinterpret_cast<const uint64_t*>(data + 66));
        properties["Body Offset"] = std::to_string(bodyOff);

        // Body SID
        uint32_t bodySid = be32toh(*reinterpret_cast<const uint32_t*>(data + 74));
        properties["Body SID"] = std::to_string(bodySid);

        // Operational Pattern UL (16 bytes)
        ss.str("");
        for (int i = 78; i < 94; ++i) {
            if (i > 78) ss << '.';
            ss << std::setfill('0') << std::setw(2) << std::hex << static_cast<int>(data[i]);
        }
        properties["Operational Pattern UL"] = ss.str();

        // Essence Containers Batch
        uint32_t essenceCount = be32toh(*reinterpret_cast<const uint32_t*>(data + 94));
        properties["Essence Containers Count"] = std::to_string(essenceCount);
        // Skip len at 98
        std::string ulsStr;
        size_t offset = 102;
        for (uint32_t i = 0; i < essenceCount; ++i) {
            if (i > 0) ulsStr += ", ";
            ss.str("");
            for (int j = 0; j < 16; ++j) {
                if (j > 0) ss << '.';
                ss << std::setfill('0') << std::setw(2) << std::hex << static_cast<int>(data[offset + j]);
            }
            ulsStr += ss.str();
            offset += 16;
        }
        properties["Essence Containers ULs"] = ulsStr;
    }

    void printProperties() {
        for (const auto& prop : properties) {
            std::cout << prop.first << ": " << prop.second << std::endl;
        }
    }

    void write(const std::string& fp) {
        std::ofstream file(fp, std::ios::binary);
        if (!file) {
            std::cerr << "Error writing file" << std::endl;
            return;
        }
        std::vector<uint8_t> data(1024, 0);

        // Key
        std::string keyStr = properties["Partition Pack Key"].empty() ? "06.0e.2b.34.02.05.01.01.0d.01.02.01.01.02.04.00" : properties["Partition Pack Key"];
        auto parts = split(keyStr, '.');
        for (size_t i = 0; i < 16; ++i) {
            data[i] = static_cast<uint8_t>(std::stoi(parts[i], nullptr, 16));
        }

        size_t offset = 16;
        // Major/Minor
        data[offset++] = static_cast<uint8_t>(std::stoi(properties["Major Version"].empty() ? "1" : properties["Major Version"]));
        data[offset++] = static_cast<uint8_t>(std::stoi(properties["Minor Version"].empty() ? "3" : properties["Minor Version"]));

        // KAG Size
        uint32_t kag = htobe32(std::stoi(properties["KAG Size"].empty() ? "1" : properties["KAG Size"]));
        std::memcpy(data.data() + offset, &kag, 4);
        offset += 4;

        // This/Previous/Footer
        uint64_t thisPart = htobe64(std::stoull(properties["This Partition"].empty() ? "0" : properties["This Partition"]));
        std::memcpy(data.data() + offset, &thisPart, 8);
        offset += 8;
        uint64_t prevPart = htobe64(std::stoull(properties["Previous Partition"].empty() ? "0" : properties["Previous Partition"]));
        std::memcpy(data.data() + offset, &prevPart, 8);
        offset += 8;
        uint64_t footPart = htobe64(std::stoull(properties["Footer Partition"].empty() ? "0" : properties["Footer Partition"]));
        std::memcpy(data.data() + offset, &footPart, 8);
        offset += 8;

        // Header/Index Byte Count
        uint64_t headCount = htobe64(std::stoull(properties["Header Byte Count"].empty() ? "0" : properties["Header Byte Count"]));
        std::memcpy(data.data() + offset, &headCount, 8);
        offset += 8;
        uint64_t indexCount = htobe64(std::stoull(properties["Index Byte Count"].empty() ? "0" : properties["Index Byte Count"]));
        std::memcpy(data.data() + offset, &indexCount, 8);
        offset += 8;

        // Index SID
        uint32_t indexSid = htobe32(std::stoi(properties["Index SID"].empty() ? "0" : properties["Index SID"]));
        std::memcpy(data.data() + offset, &indexSid, 4);
        offset += 4;

        // Body Offset
        uint64_t bodyOff = htobe64(std::stoull(properties["Body Offset"].empty() ? "0" : properties["Body Offset"]));
        std::memcpy(data.data() + offset, &bodyOff, 8);
        offset += 8;

        // Body SID
        uint32_t bodySid = htobe32(std::stoi(properties["Body SID"].empty() ? "0" : properties["Body SID"]));
        std::memcpy(data.data() + offset, &bodySid, 4);
        offset += 4;

        // OP UL
        std::string opStr = properties["Operational Pattern UL"].empty() ? "06.0e.2b.34.04.01.01.0d.0d.01.02.01.01.02.00.00" : properties["Operational Pattern UL"];
        parts = split(opStr, '.');
        for (size_t i = 0; i < 16; ++i) {
            data[offset++] = static_cast<uint8_t>(std::stoi(parts[i], nullptr, 16));
        }

        // Essence Batch
        std::string ulsStr = properties["Essence Containers ULs"];
        auto ulList = split(ulsStr, ',');
        uint32_t count = ulList.size();
        uint32_t countBe = htobe32(count);
        std::memcpy(data.data() + offset, &countBe, 4);
        offset += 4;
        uint32_t lenBe = htobe32(16 * count);
        std::memcpy(data.data() + offset, &lenBe, 4);
        offset += 4;
        for (const auto& ul : ulList) {
            auto ulParts = split(ul, '.');
            for (size_t i = 0; i < 16; ++i) {
                data[offset++] = static_cast<uint8_t>(std::stoi(ulParts[i], nullptr, 16));
            }
        }

        file.write(reinterpret_cast<const char*>(data.data()), offset);
        std::cout << "Written minimal MXF to " << fp << std::endl;
    }

private:
    std::vector<std::string> split(const std::string& s, char delim) {
        std::vector<std::string> result;
        std::stringstream ss(s);
        std::string item;
        while (std::getline(ss, item, delim)) {
            result.push_back(item);
        }
        return result;
    }
};

// Example usage:
// int main() {
//     MXFHandler handler("path/to/file.mxf");
//     handler.printProperties();
//     handler.write("new.mxf");
//     return 0;
// }