Task 597: .R3D File Format

Task 597: .R3D File Format

1. List of Properties Intrinsic to the .R3D File Format

The .R3D file format is a proprietary container for REDCODE RAW video data from RED digital cinema cameras. It uses a binary structure with a main header followed by atoms (tagged chunks) containing metadata and pointers to media data. Properties are intrinsic to the file's binary layout and include embedded metadata fields for video, audio, clip details, and camera settings. Based on available format analysis (from reverse-engineered implementations like FFmpeg's demuxer), the key properties are:

  • Magic Number: File identifier (e.g., "RED1" at offset 4).
  • Version: File format version (uint32_t).
  • Offset to REDV Atom: Pointer to video metadata section (uint32_t).
  • Offset to REDA Atom: Pointer to audio metadata section (uint32_t, if present).
  • Main Header Size: Total size of the initial header (uint32_t).
  • Image Width: Video frame width in pixels (uint32_t, from REDV atom).
  • Image Height: Video frame height in pixels (uint32_t, from REDV atom).
  • Bits Per Sample: Bit depth of raw samples (uint32_t, e.g., 12 or 16, from REDV atom).
  • Samples Per Line: Number of samples per horizontal line (uint32_t, from REDV atom).
  • Samples Per Frame: Total samples per video frame (uint32_t, from REDV atom).
  • Color Depth: Pixel format or color depth identifier (uint32_t, from REDV atom).
  • Compression ID: Compression type (uint32_t, e.g., for REDCODE wavelet, from REDV atom).
  • Patch ID: Revision or patch identifier for data (uint32_t, from REDV atom).
  • Image Aspect Ratio: Aspect ratio of the image (float, from REDV atom).
  • Frame Aspect Ratio: Aspect ratio of the frame (float, from REDV atom).
  • Frame Rate: Frames per second (float, from REDV atom).
  • Codec: Codec identifier string (e.g., "RW20" for REDCODE variant, from REDV atom).
  • Model ID: Camera model identifier (uint32_t, from REDV atom).
  • UUID: Unique identifier for the clip (string, from REDV or RED1 atom).
  • Timecode: Timecode string for the clip (string, from REDV or RED1 atom).
  • Project Name: Name of the project (string, from RED1 atom).
  • Clip Name: Name of the clip (string, from RED1 atom).
  • Camera Model: Camera model string (e.g., "RED ONE", from RED1 atom).
  • Firmware Version: Camera firmware version (string, from RED1 atom).
  • Serial Number: Camera serial number (string, from RED1 atom).
  • Lens Type: Lens type or identifier (string, from RED1 atom).
  • ISO: ISO sensitivity setting (uint32_t, from RED1 atom).
  • Shutter Angle: Shutter angle in degrees (float, from RED1 atom).
  • White Balance: White balance temperature in Kelvin (uint32_t, from RED1 atom).
  • Frame ID: Unique frame or clip ID (uint32_t, from RED1 atom).
  • UTC Time: UTC timestamp of recording (string, from RED1 atom).
  • Audio Sample Rate: Audio sampling rate in Hz (uint32_t, from REDA atom, if audio present).
  • Audio Channels: Number of audio channels (uint32_t, from REDA atom).
  • Audio Bits Per Sample: Bit depth for audio samples (uint32_t, from REDA atom).
  • Audio Format: Audio format identifier (string, from REDA atom).
  • Audio UUID: Unique identifier for audio track (string, from REDA atom).
  • Data Offset: Offset to video/audio data payload (uint64_t, from RDVO atom).
  • Data Size: Size of the media data in bytes (uint64_t, from RDVO atom).
  • Frame Number: Frame sequence number (uint32_t, from RDVO atom).
  • Custom Atom Data: Variable data from other atoms (e.g., PKT1 for packet info, MDSC for descriptions), often as strings or numerics with prefixed keys like "atom_PKT1_data".

These properties are embedded in the file and do not include external sidecar data (e.g., from .RMD files). The format also implies file system intrinsics like splitting into 4GB segments (e.g., _001.R3D, _002.R3D) due to FAT32 compatibility, and organization in .RDC (clip) folders within .RDM (mag) structures.

3. Ghost Blog Embedded HTML JavaScript for Drag-and-Drop .R3D File Dump

Here's an HTML page with embedded JavaScript that can be embedded in a Ghost blog (or any HTML-supporting platform). It allows drag-and-drop of a .R3D file and dumps all the listed properties to the screen by parsing the binary header and atoms. Note: Due to the proprietary nature, this is a basic parser based on known structure; full decoding requires handling variable atoms and may not cover all variants.

R3D File Property Dumper
Drag and drop a .R3D file here

4. Python Class for .R3D File Handling

import struct
import os

class R3DFile:
    def __init__(self, filepath):
        self.filepath = filepath
        self.properties = {}
        self._parse()

    def _read_uint32_be(self, f):
        return struct.unpack('>I', f.read(4))[0]

    def _read_uint32_le(self, f):
        return struct.unpack('<I', f.read(4))[0]

    def _read_float(self, f):
        return struct.unpack('>f', f.read(4))[0]

    def _read_string(self, f, length):
        return f.read(length).decode('utf-8', errors='ignore').rstrip('\x00')

    def _parse(self):
        with open(self.filepath, 'rb') as f:
            header_size = self._read_uint32_be(f)
            magic = self._read_string(f, 4)
            if magic != 'RED1':
                raise ValueError('Invalid .R3D file')
            self.properties['Magic Number'] = magic

            while f.tell() < header_size:
                atom_size = self._read_uint32_be(f)
                atom_type = self._read_string(f, 4)
                atom_start = f.tell() - 8

                if atom_type == 'REDV':
                    self.properties['Video Version'] = self._read_uint32_be(f)
                    self.properties['Model ID'] = self._read_uint32_be(f)
                    self.properties['Frame Number'] = self._read_uint32_be(f)
                    f.seek(128, os.SEEK_CUR)  # Skip reserved
                    self.properties['Image Width'] = self._read_uint32_le(f)
                    self.properties['Image Height'] = self._read_uint32_le(f)
                    f.seek(18, os.SEEK_CUR)  # Skip
                    self.properties['Timescale'] = self._read_uint32_le(f)
                    # Add more REDV properties as needed
                elif atom_type == 'REDA':
                    self.properties['Audio Version'] = self._read_uint32_be(f)
                    self.properties['Audio Channels'] = self._read_uint32_be(f)
                    self.properties['Audio Sample Rate'] = self._read_uint32_be(f)
                    # Add more
                elif atom_type == 'RED1':
                    self.properties['Project Name'] = self._read_string(f, 32)
                    self.properties['Clip Name'] = self._read_string(f, 32)
                    self.properties['Camera Model'] = self._read_string(f, 16)
                    # Add more metadata
                elif atom_type == 'RDVO':
                    self.properties['Data Offset'] = self._read_uint32_be(f)
                    # Add more
                f.seek(atom_start + atom_size)

    def read_properties(self):
        return self.properties

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

    def write(self, output_path):
        # For writing: Copy original and modify properties (basic example; full impl requires re-encoding)
        with open(self.filepath, 'rb') as src, open(output_path, 'wb') as dst:
            dst.write(src.read())
        # In a full impl, seek and overwrite specific fields

# Example usage:
# r3d = R3DFile('sample.R3D')
# r3d.print_properties()
# r3d.write('modified.R3D')

5. Java Class for .R3D File Handling

import java.io.*;
import java.nio.*;
import java.nio.channels.FileChannel;
import java.util.HashMap;
import java.util.Map;

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

    public R3DFile(String filepath) {
        this.filepath = filepath;
        parse();
    }

    private void parse() {
        try (RandomAccessFile raf = new RandomAccessFile(filepath, "r")) {
            ByteBuffer buffer = ByteBuffer.allocate((int) raf.length());
            raf.getChannel().read(buffer);
            buffer.flip();  // Big-endian by default
            int headerSize = buffer.getInt();
            String magic = readString(buffer, 4);
            if (!"RED1".equals(magic)) {
                throw new IOException("Invalid .R3D file");
            }
            properties.put("Magic Number", magic);

            while (buffer.position() < headerSize) {
                int atomSize = buffer.getInt();
                String atomType = readString(buffer, 4);
                int atomStart = buffer.position() - 8;

                if ("REDV".equals(atomType)) {
                    properties.put("Video Version", buffer.getInt());
                    properties.put("Model ID", buffer.getInt());
                    properties.put("Frame Number", buffer.getInt());
                    buffer.position(buffer.position() + 128);  // Skip
                    properties.put("Image Width", Integer.reverseBytes(buffer.getInt()));  // LE
                    properties.put("Image Height", Integer.reverseBytes(buffer.getInt()));  // LE
                    buffer.position(buffer.position() + 18);  // Skip
                    properties.put("Timescale", Integer.reverseBytes(buffer.getInt()));  // LE
                } else if ("REDA".equals(atomType)) {
                    properties.put("Audio Version", buffer.getInt());
                    properties.put("Audio Channels", buffer.getInt());
                    properties.put("Audio Sample Rate", buffer.getInt());
                } else if ("RED1".equals(atomType)) {
                    properties.put("Project Name", readString(buffer, 32));
                    properties.put("Clip Name", readString(buffer, 32));
                    properties.put("Camera Model", readString(buffer, 16));
                } else if ("RDVO".equals(atomType)) {
                    properties.put("Data Offset", buffer.getInt());
                }
                buffer.position(atomStart + atomSize);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private String readString(ByteBuffer buffer, int length) {
        byte[] bytes = new byte[length];
        buffer.get(bytes);
        return new String(bytes).replaceAll("\0.*", "");
    }

    public Map<String, Object> readProperties() {
        return properties;
    }

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

    public void write(String outputPath) throws IOException {
        // Basic copy; full write would modify buffer and write back
        try (FileChannel in = new FileInputStream(filepath).getChannel();
             FileChannel out = new FileOutputStream(outputPath).getChannel()) {
            in.transferTo(0, in.size(), out);
        }
    }

    // Example usage:
    // public static void main(String[] args) {
    //     R3DFile r3d = new R3DFile("sample.R3D");
    //     r3d.printProperties();
    //     r3d.write("modified.R3D");
    // }
}

6. JavaScript Class for .R3D File Handling

class R3DFile {
    constructor(buffer) {
        this.view = new DataView(buffer);
        this.offset = 0;
        this.properties = {};
        this._parse();
    }

    _readUInt32BE() {
        const val = this.view.getUint32(this.offset);
        this.offset += 4;
        return val;
    }

    _readUInt32LE() {
        const val = this.view.getUint32(this.offset, true);
        this.offset += 4;
        return val;
    }

    _readFloat32() {
        const val = this.view.getFloat32(this.offset);
        this.offset += 4;
        return val;
    }

    _readString(length) {
        let str = '';
        for (let i = 0; i < length; i++) {
            const char = this.view.getUint8(this.offset++);
            if (char === 0) break;
            str += String.fromCharCode(char);
        }
        return str;
    }

    _parse() {
        const headerSize = this._readUInt32BE();
        const magic = this._readString(4);
        if (magic !== 'RED1') throw new Error('Invalid .R3D file');
        this.properties['Magic Number'] = magic;

        while (this.offset < headerSize) {
            const atomSize = this._readUInt32BE();
            const atomType = this._readString(4);
            const atomStart = this.offset - 8;

            if (atomType === 'REDV') {
                this.properties['Video Version'] = this._readUInt32BE();
                this.properties['Model ID'] = this._readUInt32BE();
                this.properties['Frame Number'] = this._readUInt32BE();
                this.offset += 128; // Skip
                this.properties['Image Width'] = this._readUInt32LE();
                this.properties['Image Height'] = this._readUInt32LE();
                this.offset += 18; // Skip
                this.properties['Timescale'] = this._readUInt32LE();
            } else if (atomType === 'REDA') {
                this.properties['Audio Version'] = this._readUInt32BE();
                this.properties['Audio Channels'] = this._readUInt32BE();
                this.properties['Audio Sample Rate'] = this._readUInt32BE();
            } else if (atomType === 'RED1') {
                this.properties['Project Name'] = this._readString(32);
                this.properties['Clip Name'] = this._readString(32);
                this.properties['Camera Model'] = this._readString(16);
            } else if (atomType === 'RDVO') {
                this.properties['Data Offset'] = this._readUInt32BE();
            }
            this.offset = atomStart + atomSize;
        }
    }

    readProperties() {
        return this.properties;
    }

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

    write() {
        // Writing in JS (e.g., to Blob) is limited; basic example returns buffer
        return this.view.buffer;
    }
}

// Example usage (in Node.js with fs):
// const fs = require('fs');
// const buffer = fs.readFileSync('sample.R3D').buffer;
// const r3d = new R3DFile(buffer);
// r3d.printProperties();
// fs.writeFileSync('modified.R3D', new Uint8Array(r3d.write()));

7. C Class (Using C++ for Class Structure) for .R3D File Handling

#include <iostream>
#include <fstream>
#include <map>
#include <string>
#include <cstdint>
#include <cstring>

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

    uint32_t readUInt32BE(std::ifstream& f) {
        uint32_t val;
        f.read(reinterpret_cast<char*>(&val), 4);
        return __builtin_bswap32(val);  // Assuming little-endian host
    }

    uint32_t readUInt32LE(std::ifstream& f) {
        uint32_t val;
        f.read(reinterpret_cast<char*>(&val), 4);
        return val;
    }

    float readFloat(std::ifstream& f) {
        float val;
        f.read(reinterpret_cast<char*>(&val), 4);
        return val;
    }

    std::string readString(std::ifstream& f, size_t length) {
        char* buf = new char[length + 1];
        f.read(buf, length);
        buf[length] = '\0';
        std::string str(buf);
        delete[] buf;
        size_t nullPos = str.find('\0');
        if (nullPos != std::string::npos) str = str.substr(0, nullPos);
        return str;
    }

    void parse() {
        std::ifstream f(filepath, std::ios::binary);
        if (!f) throw std::runtime_error("File open failed");

        uint32_t headerSize = readUInt32BE(f);
        std::string magic = readString(f, 4);
        if (magic != "RED1") throw std::runtime_error("Invalid .R3D file");
        properties["Magic Number"] = magic;

        while (f.tellg() < headerSize) {
            uint32_t atomSize = readUInt32BE(f);
            std::string atomType = readString(f, 4);
            std::streampos atomStart = f.tellg() - static_cast<std::streampos>(8);

            if (atomType == "REDV") {
                properties["Video Version"] = std::to_string(readUInt32BE(f));
                properties["Model ID"] = std::to_string(readUInt32BE(f));
                properties["Frame Number"] = std::to_string(readUInt32BE(f));
                f.seekg(128, std::ios::cur);
                properties["Image Width"] = std::to_string(readUInt32LE(f));
                properties["Image Height"] = std::to_string(readUInt32LE(f));
                f.seekg(18, std::ios::cur);
                properties["Timescale"] = std::to_string(readUInt32LE(f));
            } else if (atomType == "REDA") {
                properties["Audio Version"] = std::to_string(readUInt32BE(f));
                properties["Audio Channels"] = std::to_string(readUInt32BE(f));
                properties["Audio Sample Rate"] = std::to_string(readUInt32BE(f));
            } else if (atomType == "RED1") {
                properties["Project Name"] = readString(f, 32);
                properties["Clip Name"] = readString(f, 32);
                properties["Camera Model"] = readString(f, 16);
            } else if (atomType == "RDVO") {
                properties["Data Offset"] = std::to_string(readUInt32BE(f));
            }
            f.seekg(atomStart + atomSize);
        }
        f.close();
    }

public:
    R3DFile(const std::string& filepath) : filepath(filepath) {
        parse();
    }

    std::map<std::string, std::string> readProperties() {
        return properties;
    }

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

    void write(const std::string& outputPath) {
        std::ifstream src(filepath, std::ios::binary);
        std::ofstream dst(outputPath, std::ios::binary);
        dst << src.rdbuf();
        // Full write would modify fields in-place
    }
};

// Example usage:
// int main() {
//     R3DFile r3d("sample.R3D");
//     r3d.printProperties();
//     r3d.write("modified.R3D");
//     return 0;
// }