Task 367: .M2TS File Format

Task 367: .M2TS File Format

File Format Specifications for .M2TS

The .M2TS file format, also known as BDAV MPEG-2 Transport Stream, is a container format for multiplexing high-definition audio, video, and other streams (such as subtitles). It is primarily used for Blu-ray Discs (BD-ROM) and AVCHD camcorders. The format is a modification of the standard MPEG-2 Transport Stream (ITU-T H.222.0 | ISO/IEC 13818-1) to support random access on storage media like discs, hard drives, or memory cards. Unlike standard MPEG-2 TS, which can use constant bit rates, .M2TS employs variable bit rates to optimize storage. The maximum multiplex rate is 48 Mbps.

Key modifications include prefixing each 188-byte MPEG-2 TS packet with a 4-byte TP_extra_header, resulting in 192-byte source packets. These source packets are grouped into aligned units of 6144 bytes (32 packets) for efficient disc sector alignment. Files are typically named zzzzz.m2ts (where zzzzz is a 5-digit clip number), and associated metadata is stored in separate .clpi files. There is no traditional magic number, but validation can involve checking file size modulo 192 == 0, the TS sync byte (0x47) at offset 4 of each packet, and consistent structure.

Supported video codecs include H.262/MPEG-2 Part 2, H.264/MPEG-4 AVC, and SMPTE VC-1. Supported audio codecs include Dolby Digital (AC-3), DTS, Linear PCM, Dolby Digital Plus, DTS-HD High Resolution Audio, DTS-HD Master Audio, and Dolby TrueHD (with restrictions in AVCHD to AC-3 or LPCM). MIME type is video/mp2t.

List of all the properties of this file format intrinsic to its file system:

  • File extension: .m2ts (or .mts for AVCHD with 8.3 naming)
  • MIME type: video/mp2t
  • Base format: Modified MPEG-2 Transport Stream (ITU-T H.222.0 | ISO/IEC 13818-1)
  • Source packet size: 192 bytes
  • TP_extra_header size: 4 bytes
  • TP_extra_header fields: Copy permission indicator (2 bits: 00 = no protection, 01 = copy once, 10 = no more copies, 11 = never copy), Arrival time stamp (30 bits, 27 MHz resolution)
  • MPEG-2 TS packet size: 188 bytes (starts with sync byte 0x47)
  • Aligned unit size: 6144 bytes (3 logical sectors of 2048 bytes each)
  • Packets per aligned unit: 32
  • Bit rate type: Variable (VBR)
  • Maximum multiplex rate: 48 Mbps
  • Supported video codecs: H.262/MPEG-2 Part 2, H.264/MPEG-4 AVC, SMPTE VC-1
  • Supported audio codecs: Dolby Digital (AC-3), DTS, Linear PCM, Dolby Digital Plus, DTS-HD High Resolution Audio, DTS-HD Master Audio, Dolby TrueHD
  • Multiplexing: Audio, video, subtitles, and other streams
  • Random access support: Yes (via TP_extra_header timestamps and aligned units)
  • Encryption support: None intrinsic (handled by container systems like AACS for Blu-ray)
  • Validation traits: File size modulo 192 == 0, TS sync byte at offset 4 of each source packet

Two direct download links for .M2TS files:

Ghost blog embedded HTML JavaScript for drag-and-drop .M2TS file to dump properties:

M2TS Properties Dumper

Drag and Drop .M2TS File to Dump Properties

Drop .M2TS file here
  1. Python class:
import struct
import os

class M2TSHandler:
    def __init__(self, filepath):
        self.filepath = filepath
        self.properties = {
            'File extension': '.m2ts',
            'MIME type': 'video/mp2t',
            'Base format': 'Modified MPEG-2 Transport Stream (ITU-T H.222.0 | ISO/IEC 13818-1)',
            'Source packet size': '192 bytes',
            'TP_extra_header size': '4 bytes',
            'TP_extra_header fields': 'Copy permission indicator (2 bits), Arrival time stamp (30 bits)',
            'MPEG-2 TS packet size': '188 bytes',
            'Aligned unit size': '6144 bytes',
            'Packets per aligned unit': '32',
            'Bit rate type': 'Variable (VBR)',
            'Maximum multiplex rate': '48 Mbps',
            'Supported video codecs': 'H.262/MPEG-2 Part 2, H.264/MPEG-4 AVC, SMPTE VC-1',
            'Supported audio codecs': 'Dolby Digital (AC-3), DTS, Linear PCM, Dolby Digital Plus, DTS-HD High Resolution Audio, DTS-HD Master Audio, Dolby TrueHD',
            'Multiplexing': 'Audio, video, subtitles, and other streams',
            'Random access support': 'Yes',
            'Encryption support': 'None intrinsic',
            'Validation traits': 'File size modulo 192 == 0, TS sync byte at offset 4'
        }

    def decode_read(self):
        with open(self.filepath, 'rb') as f:
            data = f.read()
        file_size = len(data)
        if file_size % 192 != 0 or data[4] != 0x47:
            raise ValueError('Invalid .M2TS file')
        num_packets = file_size // 192

        # Decode first packet
        tp_extra_header = struct.unpack('>I', data[0:4])[0]
        copy_permission = (tp_extra_header >> 30) & 0x03
        first_ats = tp_extra_header & 0x3FFFFFFF

        # Decode last packet
        last_offset = file_size - 192
        last_tp_extra_header = struct.unpack('>I', data[last_offset:last_offset+4])[0]
        last_ats = last_tp_extra_header & 0x3FFFFFFF

        duration = (last_ats - first_ats) / 27000000.0

        return {
            'file_size': file_size,
            'num_packets': num_packets,
            'copy_permission': copy_permission,
            'first_ats': first_ats,
            'last_ats': last_ats,
            'duration': duration
        }

    def print_properties(self):
        instance_props = self.decode_read()
        print('General Properties:')
        for key, value in self.properties.items():
            print(f'{key}: {value}')
        print('\nInstance Properties:')
        for key, value in instance_props.items():
            print(f'{key}: {value}')

    def write(self, output_path, modify_ats=False):
        with open(self.filepath, 'rb') as f:
            data = bytearray(f.read())
        if modify_ats:
            # Example: increment first ATS by 1 (demo write capability)
            tp_extra_header = struct.unpack('>I', data[0:4])[0]
            copy_permission = (tp_extra_header >> 30) & 0x03
            ats = (tp_extra_header & 0x3FFFFFFF) + 1
            new_header = (copy_permission << 30) | ats
            struct.pack_into('>I', data, 0, new_header)
        with open(output_path, 'wb') as f:
            f.write(data)
        print(f'Written to {output_path}')

# Example usage:
# handler = M2TSHandler('sample.m2ts')
# handler.print_properties()
# handler.write('modified.m2ts', modify_ats=True)
  1. Java class:
import java.io.*;
import java.nio.*;
import java.nio.file.*;

public class M2TSHandler {
    private String filepath;
    private java.util.Map<String, String> properties = new java.util.HashMap<>();

    public M2TSHandler(String filepath) {
        this.filepath = filepath;
        properties.put("File extension", ".m2ts");
        properties.put("MIME type", "video/mp2t");
        properties.put("Base format", "Modified MPEG-2 Transport Stream (ITU-T H.222.0 | ISO/IEC 13818-1)");
        properties.put("Source packet size", "192 bytes");
        properties.put("TP_extra_header size", "4 bytes");
        properties.put("TP_extra_header fields", "Copy permission indicator (2 bits), Arrival time stamp (30 bits)");
        properties.put("MPEG-2 TS packet size", "188 bytes");
        properties.put("Aligned unit size", "6144 bytes");
        properties.put("Packets per aligned unit", "32");
        properties.put("Bit rate type", "Variable (VBR)");
        properties.put("Maximum multiplex rate", "48 Mbps");
        properties.put("Supported video codecs", "H.262/MPEG-2 Part 2, H.264/MPEG-4 AVC, SMPTE VC-1");
        properties.put("Supported audio codecs", "Dolby Digital (AC-3), DTS, Linear PCM, Dolby Digital Plus, DTS-HD High Resolution Audio, DTS-HD Master Audio, Dolby TrueHD");
        properties.put("Multiplexing", "Audio, video, subtitles, and other streams");
        properties.put("Random access support", "Yes");
        properties.put("Encryption support", "None intrinsic");
        properties.put("Validation traits", "File size modulo 192 == 0, TS sync byte at offset 4");
    }

    public java.util.Map<String, Object> decodeRead() throws IOException {
        byte[] data = Files.readAllBytes(Paths.get(filepath));
        long fileSize = data.length;
        if (fileSize % 192 != 0 || (data[4] & 0xFF) != 0x47) {
            throw new IllegalArgumentException("Invalid .M2TS file");
        }
        long numPackets = fileSize / 192;

        // Decode first packet (big-endian)
        ByteBuffer bb = ByteBuffer.wrap(data, 0, 4).order(ByteOrder.BIG_ENDIAN);
        int tpExtraHeader = bb.getInt();
        int copyPermission = (tpExtraHeader >>> 30) & 0x03;
        long firstAts = tpExtraHeader & 0x3FFFFFFF;

        // Decode last packet
        bb = ByteBuffer.wrap(data, (int)(fileSize - 192), 4).order(ByteOrder.BIG_ENDIAN);
        int lastTpExtraHeader = bb.getInt();
        long lastAts = lastTpExtraHeader & 0x3FFFFFFF;

        double duration = (lastAts - firstAts) / 27000000.0;

        java.util.Map<String, Object> instanceProps = new java.util.HashMap<>();
        instanceProps.put("file_size", fileSize);
        instanceProps.put("num_packets", numPackets);
        instanceProps.put("copy_permission", copyPermission);
        instanceProps.put("first_ats", firstAts);
        instanceProps.put("last_ats", lastAts);
        instanceProps.put("duration", duration);
        return instanceProps;
    }

    public void printProperties() throws IOException {
        java.util.Map<String, Object> instanceProps = decodeRead();
        System.out.println("General Properties:");
        properties.forEach((key, value) -> System.out.println(key + ": " + value));
        System.out.println("\nInstance Properties:");
        instanceProps.forEach((key, value) -> System.out.println(key + ": " + value));
    }

    public void write(String outputPath, boolean modifyAts) throws IOException {
        byte[] data = Files.readAllBytes(Paths.get(filepath));
        if (modifyAts) {
            // Example: increment first ATS by 1
            ByteBuffer bb = ByteBuffer.wrap(data, 0, 4).order(ByteOrder.BIG_ENDIAN);
            int tpExtraHeader = bb.getInt();
            int copyPermission = (tpExtraHeader >>> 30) & 0x03;
            long ats = (tpExtraHeader & 0x3FFFFFFF) + 1;
            int newHeader = (copyPermission << 30) | (int)ats;
            bb.rewind();
            bb.putInt(newHeader);
        }
        Files.write(Paths.get(outputPath), data);
        System.out.println("Written to " + outputPath);
    }

    // Example usage:
    // public static void main(String[] args) throws IOException {
    //     M2TSHandler handler = new M2TSHandler("sample.m2ts");
    //     handler.printProperties();
    //     handler.write("modified.m2ts", true);
    // }
}
  1. JavaScript class:
class M2TSHandler {
    constructor(filepath) {
        this.filepath = filepath; // Note: JS can't directly open local files; use with FileReader or Node.js fs
        this.properties = {
            'File extension': '.m2ts',
            'MIME type': 'video/mp2t',
            'Base format': 'Modified MPEG-2 Transport Stream (ITU-T H.222.0 | ISO/IEC 13818-1)',
            'Source packet size': '192 bytes',
            'TP_extra_header size': '4 bytes',
            'TP_extra_header fields': 'Copy permission indicator (2 bits), Arrival time stamp (30 bits)',
            'MPEG-2 TS packet size': '188 bytes',
            'Aligned unit size': '6144 bytes',
            'Packets per aligned unit': '32',
            'Bit rate type': 'Variable (VBR)',
            'Maximum multiplex rate': '48 Mbps',
            'Supported video codecs': 'H.262/MPEG-2 Part 2, H.264/MPEG-4 AVC, SMPTE VC-1',
            'Supported audio codecs': 'Dolby Digital (AC-3), DTS, Linear PCM, Dolby Digital Plus, DTS-HD High Resolution Audio, DTS-HD Master Audio, Dolby TrueHD',
            'Multiplexing': 'Audio, video, subtitles, and other streams',
            'Random access support': 'Yes',
            'Encryption support': 'None intrinsic',
            'Validation traits': 'File size modulo 192 == 0, TS sync byte at offset 4'
        };
    }

    async decodeRead(buffer) { // Accepts ArrayBuffer (e.g., from FileReader or fs.readFileSync in Node)
        const dataView = new DataView(buffer);
        const fileSize = buffer.byteLength;
        if (fileSize % 192 !== 0 || dataView.getUint8(4) !== 0x47) {
            throw new Error('Invalid .M2TS file');
        }
        const numPackets = fileSize / 192;

        const tpExtraHeader = dataView.getUint32(0, false); // Big-endian
        const copyPermission = (tpExtraHeader >>> 30) & 0x03;
        const firstAts = tpExtraHeader & 0x3FFFFFFF;

        const lastOffset = fileSize - 192;
        const lastTpExtraHeader = dataView.getUint32(lastOffset, false);
        const lastAts = lastTpExtraHeader & 0x3FFFFFFF;

        const duration = (lastAts - firstAts) / 27000000;

        return {
            file_size: fileSize,
            num_packets: numPackets,
            copy_permission: copyPermission,
            first_ats: firstAts,
            last_ats: lastAts,
            duration: duration
        };
    }

    async printProperties(buffer) {
        const instanceProps = await this.decodeRead(buffer);
        console.log('General Properties:');
        for (const [key, value] of Object.entries(this.properties)) {
            console.log(`${key}: ${value}`);
        }
        console.log('\nInstance Properties:');
        for (const [key, value] of Object.entries(instanceProps)) {
            console.log(`${key}: ${value}`);
        }
    }

    async write(outputPath, modifyAts = false, buffer) { // Node.js only; use fs
        let data = new Uint8Array(buffer);
        if (modifyAts) {
            const dataView = new DataView(data.buffer);
            let tpExtraHeader = dataView.getUint32(0, false);
            const copyPermission = (tpExtraHeader >>> 30) & 0x03;
            let ats = (tpExtraHeader & 0x3FFFFFFF) + 1;
            const newHeader = (copyPermission << 30) | ats;
            dataView.setUint32(0, newHeader, false);
        }
        // Assuming Node.js fs
        const fs = require('fs');
        fs.writeFileSync(outputPath, data);
        console.log(`Written to ${outputPath}`);
    }
}

// Example usage in browser (with FileReader):
// const file = ... // from input or drop
// const reader = new FileReader();
// reader.onload = async (e) => {
//     const handler = new M2TSHandler('sample.m2ts');
//     await handler.printProperties(e.target.result);
// };
// reader.readAsArrayBuffer(file);

// In Node.js:
// const fs = require('fs');
// const buffer = fs.readFileSync('sample.m2ts');
// const handler = new M2TSHandler('sample.m2ts');
// handler.printProperties(buffer);
// handler.write('modified.m2ts', true, buffer);
  1. C class (using C++ for class support):
#include <iostream>
#include <fstream>
#include <vector>
#include <map>
#include <cstdint>
#include <cstring>

class M2TSHandler {
private:
    std::string filepath;
    std::map<std::string, std::string> properties;

public:
    M2TSHandler(const std::string& fp) : filepath(fp) {
        properties["File extension"] = ".m2ts";
        properties["MIME type"] = "video/mp2t";
        properties["Base format"] = "Modified MPEG-2 Transport Stream (ITU-T H.222.0 | ISO/IEC 13818-1)";
        properties["Source packet size"] = "192 bytes";
        properties["TP_extra_header size"] = "4 bytes";
        properties["TP_extra_header fields"] = "Copy permission indicator (2 bits), Arrival time stamp (30 bits)";
        properties["MPEG-2 TS packet size"] = "188 bytes";
        properties["Aligned unit size"] = "6144 bytes";
        properties["Packets per aligned unit"] = "32";
        properties["Bit rate type"] = "Variable (VBR)";
        properties["Maximum multiplex rate"] = "48 Mbps";
        properties["Supported video codecs"] = "H.262/MPEG-2 Part 2, H.264/MPEG-4 AVC, SMPTE VC-1";
        properties["Supported audio codecs"] = "Dolby Digital (AC-3), DTS, Linear PCM, Dolby Digital Plus, DTS-HD High Resolution Audio, DTS-HD Master Audio, Dolby TrueHD";
        properties["Multiplexing"] = "Audio, video, subtitles, and other streams";
        properties["Random access support"] = "Yes";
        properties["Encryption support"] = "None intrinsic";
        properties["Validation traits"] = "File size modulo 192 == 0, TS sync byte at offset 4";
    }

    std::map<std::string, double> decodeRead() {
        std::ifstream file(filepath, std::ios::binary | std::ios::ate);
        if (!file) {
            throw std::runtime_error("Cannot open file");
        }
        size_t fileSize = file.tellg();
        file.seekg(0);
        std::vector<uint8_t> data(fileSize);
        file.read(reinterpret_cast<char*>(data.data()), fileSize);

        if (fileSize % 192 != 0 || data[4] != 0x47) {
            throw std::runtime_error("Invalid .M2TS file");
        }
        double numPackets = static_cast<double>(fileSize) / 192.0;

        // Decode first packet (big-endian)
        uint32_t tpExtraHeader = (data[0] << 24) | (data[1] << 16) | (data[2] << 8) | data[3];
        uint32_t copyPermission = (tpExtraHeader >> 30) & 0x03;
        uint32_t firstAts = tpExtraHeader & 0x3FFFFFFF;

        // Decode last packet
        size_t lastOffset = fileSize - 192;
        uint32_t lastTpExtraHeader = (data[lastOffset] << 24) | (data[lastOffset+1] << 16) | (data[lastOffset+2] << 8) | data[lastOffset+3];
        uint32_t lastAts = lastTpExtraHeader & 0x3FFFFFFF;

        double duration = static_cast<double>(lastAts - firstAts) / 27000000.0;

        std::map<std::string, double> instanceProps;
        instanceProps["file_size"] = fileSize;
        instanceProps["num_packets"] = numPackets;
        instanceProps["copy_permission"] = copyPermission;
        instanceProps["first_ats"] = firstAts;
        instanceProps["last_ats"] = lastAts;
        instanceProps["duration"] = duration;
        return instanceProps;
    }

    void printProperties() {
        auto instanceProps = decodeRead();
        std::cout << "General Properties:" << std::endl;
        for (const auto& prop : properties) {
            std::cout << prop.first << ": " << prop.second << std::endl;
        }
        std::cout << "\nInstance Properties:" << std::endl;
        for (const auto& prop : instanceProps) {
            std::cout << prop.first << ": " << prop.second << std::endl;
        }
    }

    void write(const std::string& outputPath, bool modifyAts) {
        std::ifstream inFile(filepath, std::ios::binary);
        if (!inFile) {
            throw std::runtime_error("Cannot open input file");
        }
        std::vector<uint8_t> data((std::istreambuf_iterator<char>(inFile)), std::istreambuf_iterator<char>());

        if (modifyAts) {
            // Example: increment first ATS by 1
            uint32_t tpExtraHeader = (data[0] << 24) | (data[1] << 16) | (data[2] << 8) | data[3];
            uint32_t copyPermission = (tpExtraHeader >> 30) & 0x03;
            uint32_t ats = (tpExtraHeader & 0x3FFFFFFF) + 1;
            uint32_t newHeader = (copyPermission << 30) | ats;
            data[0] = (newHeader >> 24) & 0xFF;
            data[1] = (newHeader >> 16) & 0xFF;
            data[2] = (newHeader >> 8) & 0xFF;
            data[3] = newHeader & 0xFF;
        }

        std::ofstream outFile(outputPath, std::ios::binary);
        outFile.write(reinterpret_cast<const char*>(data.data()), data.size());
        std::cout << "Written to " << outputPath << std::endl;
    }
};

// Example usage:
// int main() {
//     try {
//         M2TSHandler handler("sample.m2ts");
//         handler.printProperties();
//         handler.write("modified.m2ts", true);
//     } catch (const std::exception& e) {
//         std::cerr << e.what() << std::endl;
//     }
//     return 0;
// }