Task 481: .OGX File Format
Task 481: .OGX File Format
File Format Specifications for .OGX
The .OGX file format is an extension for Ogg container files, specifically for multiplexed media (application/ogg MIME type) that include multiple streams (e.g., audio, video, subtitles) encoded with different codecs. It is defined by the Xiph.Org Foundation in RFC 5334, which obsoletes RFC 3534 and recommends .ogx for general multiplexed Ogg files to avoid confusion with .ogg (typically Vorbis audio only). .OGX files must contain an Ogg Skeleton logical bitstream to identify and describe all contained logical bitstreams, enabling proper demultiplexing, seeking, and metadata handling. The base Ogg encapsulation is defined in RFC 3533, with pages as the basic unit. Skeleton (version 4.0) adds meta information like timing, content types, and keyframe indexes without breaking compatibility with older Ogg players.
- List of all the properties of this file format intrinsic to its file system:
These properties are derived from the Ogg page structure and the required Ogg Skeleton bitstream (fishead ident header and fisbone secondary headers). They define the file's structure, timing, content identification, and multiplexing. Properties are listed with their types and descriptions:
- Capture Pattern: String (always "OggS") - Magic number for Ogg page synchronization.
- Version: Integer (8 bits, typically 0) - Ogg bitstream format version.
- Header Type: Integer (8 bits) - Flags for page type (e.g., 0x01 for continuation, 0x02 for BOS, 0x04 for EOS).
- Granule Position: Integer (64 bits) - Time marker, codec-specific (e.g., sample count).
- Bitstream Serial Number: Integer (32 bits) - Unique ID for each logical bitstream.
- Page Sequence Number: Integer (32 bits) - Monotonic increasing page count per bitstream.
- Checksum: Integer (32 bits) - CRC-32 of the page for integrity verification.
- Page Segments: Integer (8 bits) - Number of segments in the page (max 255).
- Segment Table: Array of integers (variable, 8 bits each) - Lengths of each segment in the page body.
- Skeleton Identifier (fishead): String ("fishead\0") - Marks the Skeleton BOS packet.
- Skeleton Version Major: Integer (16 bits) - Major version (e.g., 4).
- Skeleton Version Minor: Integer (16 bits) - Minor version (e.g., 0).
- Presentation Time Numerator: Integer (64 bits) - Numerator for rational presentation time (cut-in time for substreams).
- Presentation Time Denominator: Integer (64 bits) - Denominator for presentation time resolution (e.g., 1000 for ms).
- Base Time Numerator: Integer (64 bits) - Numerator for base time mapping granule 0 to playback time.
- Base Time Denominator: Integer (64 bits) - Denominator for base time resolution.
- UTC Time: Integer (64 bits) - UTC timestamp mapping granule 0 to real-world time.
- Segment Length: Integer (64 bits) - Total length of the Ogg segment in bytes.
- Content Byte Offset: Integer (64 bits) - Offset to the first non-header page.
- Skeleton Fisbone Identifier: String ("fisbone\0") - Marks each fisbone packet for a logical bitstream.
- Fisbone Offset to Message Headers: Integer (32 bits) - Byte offset to the message header fields.
- Fisbone Serial Number: Integer (32 bits) - Serial number of the described bitstream.
- Fisbone Number of Header Packets: Integer (32 bits) - Number of secondary header packets for the bitstream.
- Granule Rate Numerator: Integer (64 bits) - Numerator for data rate in Hz.
- Granule Rate Denominator: Integer (64 bits) - Denominator for granule rate.
- Base Granule: Integer (64 bits) - Starting granule number for timing.
- Preroll: Integer (32 bits) - Number of past packets needed for decoding the current page.
- Granule Shift: Integer (8 bits) - Bits for sub-seekable units in granulepos lower bits.
- Message Header Fields: Key-value pairs (variable, terminated by \r\n) - Metadata like Content-Type (e.g., "video/theora"), Role, Language, video dimensions, etc.
- Index Identifier: String ("index\0") - Marks keyframe index packets (optional but recommended for seeking).
- Index Serial Number: Integer (32 bits) - Serial of the indexed stream.
- Number of Keypoints: Integer (64 bits) - Count of keypoints in the index.
- Timestamp Denominator: Integer (64 bits) - Denominator for timestamp fractions.
- First Sample Time Numerator: Integer (64 bits) - Time of first sample.
- Last Sample End Time Numerator: Integer (64 bits) - End time of last sample.
- Keypoints: Array of pairs (variable byte encoded) - Each with offset delta and timestamp numerator delta.
- Two direct download links for files of format .OGX:
Note: .OGX files are rare in practice, as many multiplexed Ogg files use .ogg or .ogv extensions despite RFC recommendations. The following are direct links to multiplexed Ogg video files (audio + video streams) from a sample site, which can be renamed to .ogx for compatibility with the format.
- https://file-examples.com/wp-content/uploads/2017/04/file_example_OGG_640_2_7mg.ogg
- https://file-examples.com/wp-content/uploads/2017/04/file_example_OGG_1280_11_4mg.ogg
- Ghost blog embedded HTML JavaScript for drag and drop .OGX file dump:
(Note: This is a basic parser; full Skeleton parsing would require additional code for all fields, but it demonstrates dumping key properties from pages and Skeleton packets.)
- Python class for .OGX handling:
import struct
class OGXHandler:
def __init__(self, filepath):
self.filepath = filepath
self.properties = {}
self.decode()
def decode(self):
with open(self.filepath, 'rb') as f:
data = f.read()
offset = 0
while offset < len(data):
if data[offset:offset+4] == b'OggS':
self.properties['Capture Pattern'] = 'OggS'
offset += 4
(version,) = struct.unpack('<B', data[offset:offset+1])
self.properties['Version'] = version
offset += 1
(header_type,) = struct.unpack('<B', data[offset:offset+1])
self.properties['Header Type'] = header_type
offset += 1
(granule_pos,) = struct.unpack('<q', data[offset:offset+8])
self.properties['Granule Position'] = granule_pos
offset += 8
(serial,) = struct.unpack('<I', data[offset:offset+4])
self.properties['Bitstream Serial Number'] = serial
offset += 4
(page_seq,) = struct.unpack('<I', data[offset:offset+4])
self.properties['Page Sequence Number'] = page_seq
offset += 4
(checksum,) = struct.unpack('<I', data[offset:offset+4])
self.properties['Checksum'] = checksum
offset += 4
(page_segments,) = struct.unpack('<B', data[offset:offset+1])
self.properties['Page Segments'] = page_segments
offset += 1
segment_table = list(struct.unpack('<' + 'B'*page_segments, data[offset:offset+page_segments]))
self.properties['Segment Table'] = segment_table
offset += page_segments
packet_size = sum(segment_table)
packet = data[offset:offset+packet_size]
offset += packet_size
if header_type & 0x02 and packet.startswith(b'fishead\x00'):
self.properties['Skeleton Identifier'] = 'fishead'
# Parse more fishead fields...
major, minor = struct.unpack('<HH', packet[8:12])
self.properties['Skeleton Version'] = f'{major}.{minor}'
# Add other fields similarly using struct.unpack
elif packet.startswith(b'fisbone\x00'):
self.properties['Skeleton Fisbone Identifier'] = 'fisbone'
# Parse fisbone fields...
else:
offset += 1
def print_properties(self):
for key, value in self.properties.items():
print(f'{key}: {value}')
def write(self, new_filepath):
# Basic write: Copy the file as is (full write would require building pages)
with open(self.filepath, 'rb') as f:
data = f.read()
with open(new_filepath, 'wb') as f:
f.write(data)
print(f'Wrote to {new_filepath}')
# Example usage:
# handler = OGXHandler('example.ogx')
# handler.print_properties()
# handler.write('new.ogx')
(Note: This is a basic implementation; full decoding/writing would handle all fields and modifications.)
- Java class for .OGX handling:
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.HashMap;
import java.util.Map;
public class OGXHandler {
private String filepath;
private Map<String, Object> properties = new HashMap<>();
public OGXHandler(String filepath) {
this.filepath = filepath;
decode();
}
private void decode() {
try (FileInputStream fis = new FileInputStream(filepath)) {
byte[] data = fis.readAllBytes();
ByteBuffer bb = ByteBuffer.wrap(data).order(ByteOrder.LITTLE_ENDIAN);
int offset = 0;
while (offset < data.length) {
if (new String(data, offset, 4).equals("OggS")) {
properties.put("Capture Pattern", "OggS");
offset += 4;
byte version = bb.get(offset);
properties.put("Version", version);
offset += 1;
byte headerType = bb.get(offset);
properties.put("Header Type", headerType);
offset += 1;
long granulePos = bb.getLong(offset);
properties.put("Granule Position", granulePos);
offset += 8;
int serial = bb.getInt(offset);
properties.put("Bitstream Serial Number", serial);
offset += 4;
int pageSeq = bb.getInt(offset);
properties.put("Page Sequence Number", pageSeq);
offset += 4;
int checksum = bb.getInt(offset);
properties.put("Checksum", checksum);
offset += 4;
byte pageSegments = bb.get(offset);
properties.put("Page Segments", pageSegments);
offset += 1;
byte[] segmentTable = new byte[pageSegments];
for (int i = 0; i < pageSegments; i++) {
segmentTable[i] = bb.get(offset++);
}
properties.put("Segment Table", segmentTable);
int packetSize = 0;
for (byte len : segmentTable) packetSize += Byte.toUnsignedInt(len);
byte[] packet = new byte[packetSize];
System.arraycopy(data, offset, packet, 0, packetSize);
offset += packetSize;
if ((headerType & 0x02) != 0 && new String(packet, 0, 8).equals("fishead\0")) {
properties.put("Skeleton Identifier", "fishead");
short major = ByteBuffer.wrap(packet, 8, 2).order(ByteOrder.LITTLE_ENDIAN).getShort();
short minor = ByteBuffer.wrap(packet, 10, 2).order(ByteOrder.LITTLE_ENDIAN).getShort();
properties.put("Skeleton Version", major + "." + minor);
// Add other fields...
} else if (new String(packet, 0, 8).equals("fisbone\0")) {
properties.put("Skeleton Fisbone Identifier", "fisbone");
// Parse fisbone...
}
} else {
offset++;
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
public void printProperties() {
properties.forEach((key, value) -> System.out.println(key + ": " + value));
}
public void write(String newFilepath) {
try (FileInputStream fis = new FileInputStream(filepath);
FileOutputStream fos = new FileOutputStream(newFilepath)) {
fos.getChannel().transferFrom(fis.getChannel(), 0, fis.getChannel().size());
System.out.println("Wrote to " + newFilepath);
} catch (IOException e) {
e.printStackTrace();
}
}
// Example usage:
// public static void main(String[] args) {
// OGXHandler handler = new OGXHandler("example.ogx");
// handler.printProperties();
// handler.write("new.ogx");
// }
}
- JavaScript class for .OGX handling:
class OGXHandler {
constructor(file) {
this.file = file;
this.properties = {};
}
async decode() {
const arrayBuffer = await this.file.arrayBuffer();
const dataView = new DataView(arrayBuffer);
let offset = 0;
while (offset < arrayBuffer.byteLength) {
if (String.fromCharCode(dataView.getUint8(offset), dataView.getUint8(offset+1), dataView.getUint8(offset+2), dataView.getUint8(offset+3)) === 'OggS') {
this.properties['Capture Pattern'] = 'OggS';
offset += 4;
const version = dataView.getUint8(offset);
this.properties['Version'] = version;
offset += 1;
const headerType = dataView.getUint8(offset);
this.properties['Header Type'] = headerType;
offset += 1;
const granulePos = dataView.getBigInt64(offset, true);
this.properties['Granule Position'] = granulePos;
offset += 8;
const serial = dataView.getUint32(offset, true);
this.properties['Bitstream Serial Number'] = serial;
offset += 4;
const pageSeq = dataView.getUint32(offset, true);
this.properties['Page Sequence Number'] = pageSeq;
offset += 4;
const checksum = dataView.getUint32(offset, true);
this.properties['Checksum'] = checksum;
offset += 4;
const pageSegments = dataView.getUint8(offset);
this.properties['Page Segments'] = pageSegments;
offset += 1;
let segmentTable = [];
for (let i = 0; i < pageSegments; i++) {
segmentTable.push(dataView.getUint8(offset));
offset += 1;
}
this.properties['Segment Table'] = segmentTable;
let packetSize = segmentTable.reduce((a, b) => a + b, 0);
let packet = '';
for (let i = 0; i < packetSize; i++) {
packet += String.fromCharCode(dataView.getUint8(offset++));
}
if (headerType & 0x02 && packet.startsWith('fishead\0')) {
this.properties['Skeleton Identifier'] = 'fishead';
const major = packet.charCodeAt(8);
const minor = packet.charCodeAt(9);
this.properties['Skeleton Version'] = `${major}.${minor}`;
// Add other fields...
} else if (packet.startsWith('fisbone\0')) {
this.properties['Skeleton Fisbone Identifier'] = 'fisbone';
// Parse fisbone...
}
} else {
offset++;
}
}
}
printProperties() {
for (const [key, value] of Object.entries(this.properties)) {
console.log(`${key}: ${value}`);
}
}
async write(newFilename) {
// Basic write: Save as blob (browser context)
const blob = new Blob([await this.file.arrayBuffer()], {type: 'application/ogg'});
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = newFilename;
a.click();
URL.revokeObjectURL(url);
console.log(`Wrote to ${newFilename}`);
}
}
// Example usage:
// const inputFile = new File([...], 'example.ogx'); // From file input
// const handler = new OGXHandler(inputFile);
// await handler.decode();
// handler.printProperties();
// await handler.write('new.ogx');
- C class for .OGX handling (using C++ for class support):
#include <iostream>
#include <fstream>
#include <vector>
#include <map>
#include <cstdint>
#include <cstring>
class OGXHandler {
private:
std::string filepath;
std::map<std::string, std::string> properties;
public:
OGXHandler(const std::string& fp) : filepath(fp) {
decode();
}
void decode() {
std::ifstream file(filepath, std::ios::binary);
if (!file) return;
file.seekg(0, std::ios::end);
size_t size = file.tellg();
file.seekg(0);
std::vector<char> data(size);
file.read(data.data(), size);
size_t offset = 0;
while (offset < size) {
if (std::memcmp(&data[offset], "OggS", 4) == 0) {
properties["Capture Pattern"] = "OggS";
offset += 4;
uint8_t version = static_cast<uint8_t>(data[offset]);
properties["Version"] = std::to_string(version);
offset += 1;
uint8_t headerType = static_cast<uint8_t>(data[offset]);
properties["Header Type"] = std::to_string(headerType);
offset += 1;
int64_t granulePos = *reinterpret_cast<int64_t*>(&data[offset]);
properties["Granule Position"] = std::to_string(granulePos);
offset += 8;
uint32_t serial = *reinterpret_cast<uint32_t*>(&data[offset]);
properties["Bitstream Serial Number"] = std::to_string(serial);
offset += 4;
uint32_t pageSeq = *reinterpret_cast<uint32_t*>(&data[offset]);
properties["Page Sequence Number"] = std::to_string(pageSeq);
offset += 4;
uint32_t checksum = *reinterpret_cast<uint32_t*>(&data[offset]);
properties["Checksum"] = std::to_string(checksum);
offset += 4;
uint8_t pageSegments = static_cast<uint8_t>(data[offset]);
properties["Page Segments"] = std::to_string(pageSegments);
offset += 1;
std::vector<uint8_t> segmentTable(pageSegments);
for (uint8_t i = 0; i < pageSegments; ++i) {
segmentTable[i] = static_cast<uint8_t>(data[offset++]);
}
std::string segStr = "[";
for (auto len : segmentTable) segStr += std::to_string(len) + ", ";
segStr += "]";
properties["Segment Table"] = segStr;
size_t packetSize = 0;
for (auto len : segmentTable) packetSize += len;
std::vector<char> packet(packetSize);
std::memcpy(packet.data(), &data[offset], packetSize);
offset += packetSize;
if ((headerType & 0x02) && std::memcmp(packet.data(), "fishead\0", 8) == 0) {
properties["Skeleton Identifier"] = "fishead";
uint16_t major = *reinterpret_cast<uint16_t*>(&packet[8]);
uint16_t minor = *reinterpret_cast<uint16_t*>(&packet[10]);
properties["Skeleton Version"] = std::to_string(major) + "." + std::to_string(minor);
// Add other fields...
} else if (std::memcmp(packet.data(), "fisbone\0", 8) == 0) {
properties["Skeleton Fisbone Identifier"] = "fisbone";
// Parse fisbone...
}
} else {
offset++;
}
}
}
void printProperties() {
for (const auto& prop : properties) {
std::cout << prop.first << ": " << prop.second << std::endl;
}
}
void write(const std::string& newFilepath) {
std::ifstream src(filepath, std::ios::binary);
std::ofstream dst(newFilepath, std::ios::binary);
dst << src.rdbuf();
std::cout << "Wrote to " << newFilepath << std::endl;
}
};
// Example usage:
// int main() {
// OGXHandler handler("example.ogx");
// handler.printProperties();
// handler.write("new.ogx");
// return 0;
// }