Task 614: .RM File Format
Task 614: .RM File Format
1. List of Properties of the .RM File Format
The .RM (RealMedia) file format is a proprietary multimedia container developed by RealNetworks. It uses a chunk-based structure where each chunk has a FOURCC identifier, size, version, and payload. All multi-byte values are big-endian. The format supports audio and video streams, with metadata and data interleaved.
Based on the file format specifications, the intrinsic properties (structural fields and metadata inherent to the file's organization, excluding variable data packets and index entries) are as follows. These are derived from the mandatory and optional headers (.RMF, PROP, MDPR, CONT). I've grouped them by chunk type for clarity:
RealMedia File Header (.RMF or .RMP)
- Chunk type: FOURCC ('.RMF' for standard, '.RMP' for HD)
- Chunk size: DWORD (typically 0x12 or 0x10 in some variants)
- Chunk version: WORD (always 0)
- File version: DWORD (or WORD in rare cases with size 0x10)
- Number of headers: DWORD
File Properties Header (PROP)
- Chunk type: FOURCC ('PROP')
- Chunk size: DWORD (typically 0x32)
- Chunk version: WORD (always 0)
- Maximum bit rate: DWORD
- Average bit rate: DWORD
- Largest data packet size: DWORD
- Average data packet size: DWORD
- Number of data packets: DWORD
- File duration (ms): DWORD
- Preroll buffer (ms): DWORD
- First INDX chunk offset: DWORD
- First DATA chunk offset: DWORD
- Number of streams: WORD
- Flags: WORD (bitfield: bit 0 = savable; bit 1 = PerfectPlay; bit 2 = live broadcast)
Media Properties Header (MDPR) - One per stream
- Chunk type: FOURCC ('MDPR')
- Chunk size: DWORD
- Chunk version: WORD (always 0)
- Stream number: WORD
- Maximum bit rate: DWORD
- Average bit rate: DWORD
- Largest data packet size: DWORD
- Average data packet size: DWORD
- Stream start offset (ms): DWORD
- Preroll (ms): DWORD
- Stream duration (ms): DWORD
- Stream description length: BYTE
- Stream description: BYTE[] (string)
- Mime type length: BYTE
- Mime type: BYTE[] (string, e.g., "audio/x-pn-realaudio")
- Type-specific data size: DWORD
- Type-specific data: BYTE[] (codec-dependent, e.g., for audio: header signature, version, codec flavor, sample rate, channels, FourCC, etc.)
Content Description Header (CONT)
- Chunk type: FOURCC ('CONT')
- Chunk size: DWORD
- Chunk version: WORD (always 0)
- Title length: WORD
- Title: BYTE[] (string)
- Author length: WORD
- Author: BYTE[] (string)
- Copyright length: WORD
- Copyright: BYTE[] (string)
- Comment length: WORD
- Comment: BYTE[] (string)
Additional intrinsic notes:
- Magic bytes: Files start with '.RMF' (0x2E524D46 in hex).
- Endianness: Big-endian for all multi-byte fields.
- Chunk structure: Universal preamble (FOURCC DWORD + size DWORD + version WORD).
- File system intrinsics: No specific filesystem dependencies (e.g., no clusters or inodes referenced); it's a flat binary container. Offsets are absolute from file start.
These properties represent the core metadata and structure extractable from any .RM file without processing the variable DATA or INDX chunks.
2. Two Direct Download Links for .RM Files
- https://filesamples.com/samples/video/rm/sample_1280x720_surfing_with_audio.rm
- https://filesamples.com/samples/video/rm/sample_960x400_ocean_with_audio.rm
3. Ghost Blog Embedded HTML/JavaScript for Drag-and-Drop .RM File Dump
Assuming this refers to embeddable HTML/JavaScript code suitable for a Ghost blog post (e.g., via custom HTML block), here's a self-contained snippet. It allows drag-and-drop of a .RM file, parses it in the browser using DataView for big-endian reads, extracts the properties listed in #1, and dumps them to the screen. It handles basic chunk parsing but skips deep type-specific data decoding for simplicity (as it's codec-variable).
Embed this in a Ghost blog post using the HTML card.
4. Python Class for .RM File Handling
import struct
import sys
class RMParser:
def __init__(self, filename):
self.filename = filename
self.properties = {}
def read_dword(self, f):
return struct.unpack('>I', f.read(4))[0]
def read_word(self, f):
return struct.unpack('>H', f.read(2))[0]
def read_byte(self, f):
return f.read(1)[0]
def read_string(self, f, length):
return f.read(length).decode('utf-8', errors='ignore')
def parse_chunk(self, f):
chunk_type = f.read(4).decode('ascii')
chunk_size = self.read_dword(f)
chunk_version = self.read_word(f)
return chunk_type, chunk_size, chunk_version
def parse_rmf(self, f, chunk_size):
file_version = self.read_dword(f)
num_headers = self.read_dword(f)
self.properties['RMF'] = {
'File Version': file_version,
'Number of Headers': num_headers
}
def parse_prop(self, f):
max_bitrate = self.read_dword(f)
avg_bitrate = self.read_dword(f)
max_packet_size = self.read_dword(f)
avg_packet_size = self.read_dword(f)
num_packets = self.read_dword(f)
duration = self.read_dword(f)
preroll = self.read_dword(f)
indx_offset = self.read_dword(f)
data_offset = self.read_dword(f)
num_streams = self.read_word(f)
flags = self.read_word(f)
self.properties['PROP'] = {
'Max Bitrate': max_bitrate,
'Avg Bitrate': avg_bitrate,
'Max Packet Size': max_packet_size,
'Avg Packet Size': avg_packet_size,
'Num Packets': num_packets,
'Duration (ms)': duration,
'Preroll (ms)': preroll,
'INDX Offset': indx_offset,
'DATA Offset': data_offset,
'Num Streams': num_streams,
'Flags': flags
}
def parse_mdpr(self, f):
stream_num = self.read_word(f)
max_bitrate = self.read_dword(f)
avg_bitrate = self.read_dword(f)
max_packet_size = self.read_dword(f)
avg_packet_size = self.read_dword(f)
start_offset = self.read_dword(f)
preroll = self.read_dword(f)
duration = self.read_dword(f)
desc_len = self.read_byte(f)
desc = self.read_string(f, desc_len)
mime_len = self.read_byte(f)
mime = self.read_string(f, mime_len)
type_size = self.read_dword(f)
# Skip type-specific data for now
f.seek(type_size, 1)
if 'MDPR' not in self.properties:
self.properties['MDPR'] = []
self.properties['MDPR'].append({
'Stream Num': stream_num,
'Max Bitrate': max_bitrate,
'Avg Bitrate': avg_bitrate,
'Max Packet Size': max_packet_size,
'Avg Packet Size': avg_packet_size,
'Start Offset (ms)': start_offset,
'Preroll (ms)': preroll,
'Duration (ms)': duration,
'Description': desc,
'Mime Type': mime,
'Type-Specific Size': type_size
})
def parse_cont(self, f):
title_len = self.read_word(f)
title = self.read_string(f, title_len)
author_len = self.read_word(f)
author = self.read_string(f, author_len)
copyright_len = self.read_word(f)
copyright = self.read_string(f, copyright_len)
comment_len = self.read_word(f)
comment = self.read_string(f, comment_len)
self.properties['CONT'] = {
'Title': title,
'Author': author,
'Copyright': copyright,
'Comment': comment
}
def read(self):
with open(self.filename, 'rb') as f:
while True:
try:
chunk_type, chunk_size, chunk_version = self.parse_chunk(f)
if chunk_type in ('.RMF', '.RMP'):
self.parse_rmf(f, chunk_size)
elif chunk_type == 'PROP':
self.parse_prop(f)
elif chunk_type == 'MDPR':
self.parse_mdpr(f)
elif chunk_type == 'CONT':
self.parse_cont(f)
else:
# Skip DATA/INDX/unknown
f.seek(chunk_size - 10, 1)
except struct.error:
break
def print_properties(self):
for chunk, props in self.properties.items():
print(f"{chunk}:")
if isinstance(props, list):
for i, stream in enumerate(props):
print(f" Stream {i}:")
for key, val in stream.items():
print(f" {key}: {val}")
else:
for key, val in props.items():
print(f" {key}: {val}")
def write(self, output_filename):
# Basic write: Reassemble from parsed properties (simplified, assumes no changes)
with open(output_filename, 'wb') as out:
# Write .RMF
if 'RMF' in self.properties:
out.write(b'.RMF')
out.write(struct.pack('>I', 18)) # Size 0x12
out.write(struct.pack('>H', 0))
out.write(struct.pack('>I', self.properties['RMF']['File Version']))
out.write(struct.pack('>I', self.properties['RMF']['Number of Headers']))
# Write PROP
if 'PROP' in self.properties:
p = self.properties['PROP']
out.write(b'PROP')
out.write(struct.pack('>I', 50)) # Size 0x32
out.write(struct.pack('>H', 0))
out.write(struct.pack('>IIIIIIIII', p['Max Bitrate'], p['Avg Bitrate'], p['Max Packet Size'],
p['Avg Packet Size'], p['Num Packets'], p['Duration (ms)'],
p['Preroll (ms)'], p['INDX Offset'], p['DATA Offset']))
out.write(struct.pack('>HH', p['Num Streams'], p['Flags']))
# Write MDPR(s)
if 'MDPR' in self.properties:
for stream in self.properties['MDPR']:
desc_bytes = stream['Description'].encode('utf-8')
mime_bytes = stream['Mime Type'].encode('utf-8')
mdpr_size = 38 + len(desc_bytes) + len(mime_bytes) + stream['Type-Specific Size']
out.write(b'MDPR')
out.write(struct.pack('>I', mdpr_size + 10)) # + preamble
out.write(struct.pack('>H', 0))
out.write(struct.pack('>HIIIIIII', stream['Stream Num'], stream['Max Bitrate'],
stream['Avg Bitrate'], stream['Max Packet Size'],
stream['Avg Packet Size'], stream['Start Offset (ms)'],
stream['Preroll (ms)'], stream['Duration (ms)']))
out.write(struct.pack('>B', len(desc_bytes)))
out.write(desc_bytes)
out.write(struct.pack('>B', len(mime_bytes)))
out.write(mime_bytes)
out.write(struct.pack('>I', stream['Type-Specific Size']))
# Placeholder for type-specific (would need original data)
out.write(b'\x00' * stream['Type-Specific Size'])
# Write CONT
if 'CONT' in self.properties:
c = self.properties['CONT']
title_bytes = c['Title'].encode('utf-8')
author_bytes = c['Author'].encode('utf-8')
copyright_bytes = c['Copyright'].encode('utf-8')
comment_bytes = c['Comment'].encode('utf-8')
cont_size = 10 + 8 + len(title_bytes) + len(author_bytes) + len(copyright_bytes) + len(comment_bytes)
out.write(b'CONT')
out.write(struct.pack('>I', cont_size))
out.write(struct.pack('>H', 0))
out.write(struct.pack('>H', len(title_bytes)))
out.write(title_bytes)
out.write(struct.pack('>H', len(author_bytes)))
out.write(author_bytes)
out.write(struct.pack('>H', len(copyright_bytes)))
out.write(copyright_bytes)
out.write(struct.pack('>H', len(comment_bytes)))
out.write(comment_bytes)
# Note: DATA and INDX not written here; this is header-only for demo
# Usage example:
if __name__ == '__main__':
if len(sys.argv) < 2:
print("Usage: python rm_parser.py input.rm [output.rm]")
sys.exit(1)
parser = RMParser(sys.argv[1])
parser.read()
parser.print_properties()
if len(sys.argv) > 2:
parser.write(sys.argv[2])
5. Java Class for .RM File Handling
import java.io.*;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class RMParser {
private String filename;
private Map<String, Object> properties = new HashMap<>();
public RMParser(String filename) {
this.filename = filename;
}
private int readDword(DataInputStream dis) throws IOException {
return Integer.reverseBytes(dis.readInt());
}
private short readWord(DataInputStream dis) throws IOException {
return Short.reverseBytes(dis.readShort());
}
private byte readByte(DataInputStream dis) throws IOException {
return dis.readByte();
}
private String readString(DataInputStream dis, int length) throws IOException {
byte[] bytes = new byte[length];
dis.readFully(bytes);
return new String(bytes, "UTF-8");
}
private class Chunk {
String type;
int size;
short version;
}
private Chunk parseChunk(DataInputStream dis) throws IOException {
byte[] typeBytes = new byte[4];
dis.readFully(typeBytes);
Chunk chunk = new Chunk();
chunk.type = new String(typeBytes);
chunk.size = readDword(dis);
chunk.version = readWord(dis);
return chunk;
}
private void parseRMF(DataInputStream dis) throws IOException {
int fileVersion = readDword(dis);
int numHeaders = readDword(dis);
Map<String, Integer> rmfProps = new HashMap<>();
rmfProps.put("File Version", fileVersion);
rmfProps.put("Number of Headers", numHeaders);
properties.put("RMF", rmfProps);
}
private void parsePROP(DataInputStream dis) throws IOException {
int maxBitrate = readDword(dis);
int avgBitrate = readDword(dis);
int maxPacketSize = readDword(dis);
int avgPacketSize = readDword(dis);
int numPackets = readDword(dis);
int duration = readDword(dis);
int preroll = readDword(dis);
int indxOffset = readDword(dis);
int dataOffset = readDword(dis);
short numStreams = readWord(dis);
short flags = readWord(dis);
Map<String, Object> propProps = new HashMap<>();
propProps.put("Max Bitrate", maxBitrate);
propProps.put("Avg Bitrate", avgBitrate);
propProps.put("Max Packet Size", maxPacketSize);
propProps.put("Avg Packet Size", avgPacketSize);
propProps.put("Num Packets", numPackets);
propProps.put("Duration (ms)", duration);
propProps.put("Preroll (ms)", preroll);
propProps.put("INDX Offset", indxOffset);
propProps.put("DATA Offset", dataOffset);
propProps.put("Num Streams", (int) numStreams);
propProps.put("Flags", (int) flags);
properties.put("PROP", propProps);
}
private void parseMDPR(DataInputStream dis) throws IOException {
short streamNum = readWord(dis);
int maxBitrate = readDword(dis);
int avgBitrate = readDword(dis);
int maxPacketSize = readDword(dis);
int avgPacketSize = readDword(dis);
int startOffset = readDword(dis);
int preroll = readDword(dis);
int duration = readDword(dis);
byte descLen = readByte(dis);
String desc = readString(dis, descLen);
byte mimeLen = readByte(dis);
String mime = readString(dis, mimeLen);
int typeSize = readDword(dis);
dis.skipBytes(typeSize); // Skip type-specific
List<Map<String, Object>> mdprList = (List<Map<String, Object>>) properties.getOrDefault("MDPR", new ArrayList<>());
Map<String, Object> streamProps = new HashMap<>();
streamProps.put("Stream Num", (int) streamNum);
streamProps.put("Max Bitrate", maxBitrate);
streamProps.put("Avg Bitrate", avgBitrate);
streamProps.put("Max Packet Size", maxPacketSize);
streamProps.put("Avg Packet Size", avgPacketSize);
streamProps.put("Start Offset (ms)", startOffset);
streamProps.put("Preroll (ms)", preroll);
streamProps.put("Duration (ms)", duration);
streamProps.put("Description", desc);
streamProps.put("Mime Type", mime);
streamProps.put("Type-Specific Size", typeSize);
mdprList.add(streamProps);
properties.put("MDPR", mdprList);
}
private void parseCONT(DataInputStream dis) throws IOException {
short titleLen = readWord(dis);
String title = readString(dis, titleLen);
short authorLen = readWord(dis);
String author = readString(dis, authorLen);
short copyrightLen = readWord(dis);
String copyright = readString(dis, copyrightLen);
short commentLen = readWord(dis);
String comment = readString(dis, commentLen);
Map<String, String> contProps = new HashMap<>();
contProps.put("Title", title);
contProps.put("Author", author);
contProps.put("Copyright", copyright);
contProps.put("Comment", comment);
properties.put("CONT", contProps);
}
public void read() throws IOException {
try (FileInputStream fis = new FileInputStream(filename);
DataInputStream dis = new DataInputStream(fis)) {
while (dis.available() > 0) {
Chunk chunk = parseChunk(dis);
if (chunk.type.equals(".RMF") || chunk.type.equals(".RMP")) {
parseRMF(dis);
} else if (chunk.type.equals("PROP")) {
parsePROP(dis);
} else if (chunk.type.equals("MDPR")) {
parseMDPR(dis);
} else if (chunk.type.equals("CONT")) {
parseCONT(dis);
} else {
dis.skipBytes(chunk.size - 10);
}
}
}
}
public void printProperties() {
for (Map.Entry<String, Object> entry : properties.entrySet()) {
System.out.println(entry.getKey() + ":");
if (entry.getValue() instanceof List) {
List<Map<String, Object>> list = (List<Map<String, Object>>) entry.getValue();
for (int i = 0; i < list.size(); i++) {
System.out.println(" Stream " + i + ":");
for (Map.Entry<String, Object> prop : list.get(i).entrySet()) {
System.out.println(" " + prop.getKey() + ": " + prop.getValue());
}
}
} else if (entry.getValue() instanceof Map) {
Map<String, ?> map = (Map<String, ?>) entry.getValue();
for (Map.Entry<String, ?> prop : map.entrySet()) {
System.out.println(" " + prop.getKey() + ": " + prop.getValue());
}
}
}
}
public void write(String outputFilename) throws IOException {
try (FileOutputStream fos = new FileOutputStream(outputFilename);
DataOutputStream dos = new DataOutputStream(fos)) {
// Write .RMF
if (properties.containsKey("RMF")) {
Map<String, Integer> rmf = (Map<String, Integer>) properties.get("RMF");
dos.writeBytes(".RMF");
dos.writeInt(Integer.reverseBytes(18)); // Size
dos.writeShort(Short.reverseBytes((short) 0));
dos.writeInt(Integer.reverseBytes(rmf.get("File Version")));
dos.writeInt(Integer.reverseBytes(rmf.get("Number of Headers")));
}
// Write PROP
if (properties.containsKey("PROP")) {
Map<String, Object> prop = (Map<String, Object>) properties.get("PROP");
dos.writeBytes("PROP");
dos.writeInt(Integer.reverseBytes(50)); // Size
dos.writeShort(Short.reverseBytes((short) 0));
dos.writeInt(Integer.reverseBytes((Integer) prop.get("Max Bitrate")));
dos.writeInt(Integer.reverseBytes((Integer) prop.get("Avg Bitrate")));
dos.writeInt(Integer.reverseBytes((Integer) prop.get("Max Packet Size")));
dos.writeInt(Integer.reverseBytes((Integer) prop.get("Avg Packet Size")));
dos.writeInt(Integer.reverseBytes((Integer) prop.get("Num Packets")));
dos.writeInt(Integer.reverseBytes((Integer) prop.get("Duration (ms)")));
dos.writeInt(Integer.reverseBytes((Integer) prop.get("Preroll (ms)")));
dos.writeInt(Integer.reverseBytes((Integer) prop.get("INDX Offset")));
dos.writeInt(Integer.reverseBytes((Integer) prop.get("DATA Offset")));
dos.writeShort(Short.reverseBytes((short) (int) prop.get("Num Streams")));
dos.writeShort(Short.reverseBytes((short) (int) prop.get("Flags")));
}
// Write MDPR(s)
if (properties.containsKey("MDPR")) {
List<Map<String, Object>> mdprList = (List<Map<String, Object>>) properties.get("MDPR");
for (Map<String, Object> stream : mdprList) {
String desc = (String) stream.get("Description");
String mime = (String) stream.get("Mime Type");
int typeSize = (Integer) stream.get("Type-Specific Size");
int mdprSize = 38 + desc.length() + mime.length() + typeSize;
dos.writeBytes("MDPR");
dos.writeInt(Integer.reverseBytes(mdprSize + 10));
dos.writeShort(Short.reverseBytes((short) 0));
dos.writeShort(Short.reverseBytes((short) (int) stream.get("Stream Num")));
dos.writeInt(Integer.reverseBytes((Integer) stream.get("Max Bitrate")));
dos.writeInt(Integer.reverseBytes((Integer) stream.get("Avg Bitrate")));
dos.writeInt(Integer.reverseBytes((Integer) stream.get("Max Packet Size")));
dos.writeInt(Integer.reverseBytes((Integer) stream.get("Avg Packet Size")));
dos.writeInt(Integer.reverseBytes((Integer) stream.get("Start Offset (ms)")));
dos.writeInt(Integer.reverseBytes((Integer) stream.get("Preroll (ms)")));
dos.writeInt(Integer.reverseBytes((Integer) stream.get("Duration (ms)")));
dos.writeByte(desc.length());
dos.writeBytes(desc);
dos.writeByte(mime.length());
dos.writeBytes(mime);
dos.writeInt(Integer.reverseBytes(typeSize));
// Placeholder
for (int i = 0; i < typeSize; i++) dos.writeByte(0);
}
}
// Write CONT
if (properties.containsKey("CONT")) {
Map<String, String> cont = (Map<String, String>) properties.get("CONT");
String title = cont.get("Title");
String author = cont.get("Author");
String copyright = cont.get("Copyright");
String comment = cont.get("Comment");
int contSize = 10 + 8 + title.length() + author.length() + copyright.length() + comment.length();
dos.writeBytes("CONT");
dos.writeInt(Integer.reverseBytes(contSize));
dos.writeShort(Short.reverseBytes((short) 0));
dos.writeShort(Short.reverseBytes((short) title.length()));
dos.writeBytes(title);
dos.writeShort(Short.reverseBytes((short) author.length()));
dos.writeBytes(author);
dos.writeShort(Short.reverseBytes((short) copyright.length()));
dos.writeBytes(copyright);
dos.writeShort(Short.reverseBytes((short) comment.length()));
dos.writeBytes(comment);
}
// Note: Headers only
}
}
public static void main(String[] args) throws IOException {
if (args.length < 1) {
System.out.println("Usage: java RMParser input.rm [output.rm]");
System.exit(1);
}
RMParser parser = new RMParser(args[0]);
parser.read();
parser.printProperties();
if (args.length > 1) {
parser.write(args[1]);
}
}
}
6. JavaScript Class for .RM File Handling
const fs = require('fs'); // For Node.js
class RMParser {
constructor(filename) {
this.filename = filename;
this.properties = {};
}
readDword(buffer, offset) {
return buffer.readUInt32BE(offset);
}
readWord(buffer, offset) {
return buffer.readUInt16BE(offset);
}
readByte(buffer, offset) {
return buffer[offset];
}
readString(buffer, offset, length) {
return buffer.slice(offset, offset + length).toString('utf8');
}
parseChunk(buffer, offset) {
const type = buffer.slice(offset, offset + 4).toString('ascii');
offset += 4;
const size = this.readDword(buffer, offset);
offset += 4;
const version = this.readWord(buffer, offset);
offset += 2;
return { type, size, version, nextOffset: offset };
}
parseRMF(buffer, offset) {
const fileVersion = this.readDword(buffer, offset);
offset += 4;
const numHeaders = this.readDword(buffer, offset);
offset += 4;
this.properties.RMF = {
'File Version': fileVersion,
'Number of Headers': numHeaders
};
return offset;
}
parsePROP(buffer, offset) {
const maxBitrate = this.readDword(buffer, offset); offset += 4;
const avgBitrate = this.readDword(buffer, offset); offset += 4;
const maxPacketSize = this.readDword(buffer, offset); offset += 4;
const avgPacketSize = this.readDword(buffer, offset); offset += 4;
const numPackets = this.readDword(buffer, offset); offset += 4;
const duration = this.readDword(buffer, offset); offset += 4;
const preroll = this.readDword(buffer, offset); offset += 4;
const indxOffset = this.readDword(buffer, offset); offset += 4;
const dataOffset = this.readDword(buffer, offset); offset += 4;
const numStreams = this.readWord(buffer, offset); offset += 2;
const flags = this.readWord(buffer, offset); offset += 2;
this.properties.PROP = {
'Max Bitrate': maxBitrate,
'Avg Bitrate': avgBitrate,
'Max Packet Size': maxPacketSize,
'Avg Packet Size': avgPacketSize,
'Num Packets': numPackets,
'Duration (ms)': duration,
'Preroll (ms)': preroll,
'INDX Offset': indxOffset,
'DATA Offset': dataOffset,
'Num Streams': numStreams,
'Flags': flags
};
return offset;
}
parseMDPR(buffer, offset) {
const streamNum = this.readWord(buffer, offset); offset += 2;
const maxBitrate = this.readDword(buffer, offset); offset += 4;
const avgBitrate = this.readDword(buffer, offset); offset += 4;
const maxPacketSize = this.readDword(buffer, offset); offset += 4;
const avgPacketSize = this.readDword(buffer, offset); offset += 4;
const startOffsetVal = this.readDword(buffer, offset); offset += 4;
const preroll = this.readDword(buffer, offset); offset += 4;
const duration = this.readDword(buffer, offset); offset += 4;
const descLen = this.readByte(buffer, offset); offset += 1;
const desc = this.readString(buffer, offset, descLen); offset += descLen;
const mimeLen = this.readByte(buffer, offset); offset += 1;
const mime = this.readString(buffer, offset, mimeLen); offset += mimeLen;
const typeSize = this.readDword(buffer, offset); offset += 4;
offset += typeSize; // Skip
if (!this.properties.MDPR) this.properties.MDPR = [];
this.properties.MDPR.push({
'Stream Num': streamNum,
'Max Bitrate': maxBitrate,
'Avg Bitrate': avgBitrate,
'Max Packet Size': maxPacketSize,
'Avg Packet Size': avgPacketSize,
'Start Offset (ms)': startOffsetVal,
'Preroll (ms)': preroll,
'Duration (ms)': duration,
'Description': desc,
'Mime Type': mime,
'Type-Specific Size': typeSize
});
return offset;
}
parseCONT(buffer, offset) {
const titleLen = this.readWord(buffer, offset); offset += 2;
const title = this.readString(buffer, offset, titleLen); offset += titleLen;
const authorLen = this.readWord(buffer, offset); offset += 2;
const author = this.readString(buffer, offset, authorLen); offset += authorLen;
const copyrightLen = this.readWord(buffer, offset); offset += 2;
const copyright = this.readString(buffer, offset, copyrightLen); offset += copyrightLen;
const commentLen = this.readWord(buffer, offset); offset += 2;
const comment = this.readString(buffer, offset, commentLen); offset += commentLen;
this.properties.CONT = {
'Title': title,
'Author': author,
'Copyright': copyright,
'Comment': comment
};
return offset;
}
read() {
const buffer = fs.readFileSync(this.filename);
let offset = 0;
while (offset < buffer.length) {
const chunk = this.parseChunk(buffer, offset);
offset = chunk.nextOffset;
if (chunk.type === '.RMF' || chunk.type === '.RMP') {
offset = this.parseRMF(buffer, offset);
} else if (chunk.type === 'PROP') {
offset = this.parsePROP(buffer, offset);
} else if (chunk.type === 'MDPR') {
offset = this.parseMDPR(buffer, offset);
} else if (chunk.type === 'CONT') {
offset = this.parseCONT(buffer, offset);
} else {
offset += chunk.size - 10;
}
}
}
printProperties() {
for (const [chunk, props] of Object.entries(this.properties)) {
console.log(`${chunk}:`);
if (Array.isArray(props)) {
props.forEach((stream, i) => {
console.log(` Stream ${i}:`);
for (const [key, val] of Object.entries(stream)) {
console.log(` ${key}: ${val}`);
}
});
} else {
for (const [key, val] of Object.entries(props)) {
console.log(` ${key}: ${val}`);
}
}
}
}
write(outputFilename) {
let buffer = Buffer.alloc(0);
// Write .RMF
if (this.properties.RMF) {
const rmf = this.properties.RMF;
const chunkBuf = Buffer.alloc(18);
chunkBuf.write('.RMF', 0, 4);
chunkBuf.writeUInt32BE(18, 4);
chunkBuf.writeUInt16BE(0, 8);
chunkBuf.writeUInt32BE(rmf['File Version'], 10);
chunkBuf.writeUInt32BE(rmf['Number of Headers'], 14);
buffer = Buffer.concat([buffer, chunkBuf]);
}
// Write PROP
if (this.properties.PROP) {
const p = this.properties.PROP;
const chunkBuf = Buffer.alloc(50);
chunkBuf.write('PROP', 0, 4);
chunkBuf.writeUInt32BE(50, 4);
chunkBuf.writeUInt16BE(0, 8);
chunkBuf.writeUInt32BE(p['Max Bitrate'], 10);
chunkBuf.writeUInt32BE(p['Avg Bitrate'], 14);
chunkBuf.writeUInt32BE(p['Max Packet Size'], 18);
chunkBuf.writeUInt32BE(p['Avg Packet Size'], 22);
chunkBuf.writeUInt32BE(p['Num Packets'], 26);
chunkBuf.writeUInt32BE(p['Duration (ms)'], 30);
chunkBuf.writeUInt32BE(p['Preroll (ms)'], 34);
chunkBuf.writeUInt32BE(p['INDX Offset'], 38);
chunkBuf.writeUInt32BE(p['DATA Offset'], 42);
chunkBuf.writeUInt16BE(p['Num Streams'], 46);
chunkBuf.writeUInt16BE(p['Flags'], 48);
buffer = Buffer.concat([buffer, chunkBuf]);
}
// Write MDPR(s)
if (this.properties.MDPR) {
this.properties.MDPR.forEach(stream => {
const desc = stream['Description'];
const mime = stream['Mime Type'];
const typeSize = stream['Type-Specific Size'];
const mdprSize = 10 + 2 + 4*7 + 4 + 1 + desc.length + 1 + mime.length + 4 + typeSize;
const chunkBuf = Buffer.alloc(mdprSize);
let off = 0;
chunkBuf.write('MDPR', off, 4); off += 4;
chunkBuf.writeUInt32BE(mdprSize, off); off += 4;
chunkBuf.writeUInt16BE(0, off); off += 2;
chunkBuf.writeUInt16BE(stream['Stream Num'], off); off += 2;
chunkBuf.writeUInt32BE(stream['Max Bitrate'], off); off += 4;
chunkBuf.writeUInt32BE(stream['Avg Bitrate'], off); off += 4;
chunkBuf.writeUInt32BE(stream['Max Packet Size'], off); off += 4;
chunkBuf.writeUInt32BE(stream['Avg Packet Size'], off); off += 4;
chunkBuf.writeUInt32BE(stream['Start Offset (ms)'], off); off += 4;
chunkBuf.writeUInt32BE(stream['Preroll (ms)'], off); off += 4;
chunkBuf.writeUInt32BE(stream['Duration (ms)'], off); off += 4;
chunkBuf[off++] = desc.length;
chunkBuf.write(desc, off, desc.length); off += desc.length;
chunkBuf[off++] = mime.length;
chunkBuf.write(mime, off, mime.length); off += mime.length;
chunkBuf.writeUInt32BE(typeSize, off); off += 4;
// Placeholder
buffer = Buffer.concat([buffer, chunkBuf]);
});
}
// Write CONT
if (this.properties.CONT) {
const c = this.properties.CONT;
const title = c['Title'];
const author = c['Author'];
const copyright = c['Copyright'];
const comment = c['Comment'];
const contSize = 10 + 2*4 + title.length + author.length + copyright.length + comment.length;
const chunkBuf = Buffer.alloc(contSize);
let off = 0;
chunkBuf.write('CONT', off, 4); off += 4;
chunkBuf.writeUInt32BE(contSize, off); off += 4;
chunkBuf.writeUInt16BE(0, off); off += 2;
chunkBuf.writeUInt16BE(title.length, off); off += 2;
chunkBuf.write(title, off); off += title.length;
chunkBuf.writeUInt16BE(author.length, off); off += 2;
chunkBuf.write(author, off); off += author.length;
chunkBuf.writeUInt16BE(copyright.length, off); off += 2;
chunkBuf.write(copyright, off); off += copyright.length;
chunkBuf.writeUInt16BE(comment.length, off); off += 2;
chunkBuf.write(comment, off); off += comment.length;
buffer = Buffer.concat([buffer, chunkBuf]);
}
fs.writeFileSync(outputFilename, buffer);
}
}
// Usage example:
if (process.argv.length < 3) {
console.log('Usage: node rm_parser.js input.rm [output.rm]');
process.exit(1);
}
const parser = new RMParser(process.argv[2]);
parser.read();
parser.printProperties();
if (process.argv.length > 3) {
parser.write(process.argv[3]);
}
7. C Class (Using C++ for Class Support) for .RM File Handling
#include <iostream>
#include <fstream>
#include <vector>
#include <map>
#include <string>
#include <cstring>
#include <cstdint>
#include <byteswap.h> // For big-endian on little-endian systems
class RMParser {
private:
std::string filename;
std::map<std::string, std::vector<std::map<std::string, std::string>>> properties; // Flexible for lists
uint32_t readDword(std::ifstream& f) {
uint32_t val;
f.read(reinterpret_cast<char*>(&val), 4);
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
return __bswap_32(val);
#else
return val;
#endif
}
uint16_t readWord(std::ifstream& f) {
uint16_t val;
f.read(reinterpret_cast<char*>(&val), 2);
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
return __bswap_16(val);
#else
return val;
#endif
}
uint8_t readByte(std::ifstream& f) {
uint8_t val;
f.read(reinterpret_cast<char*>(&val), 1);
return val;
}
std::string readString(std::ifstream& f, size_t length) {
std::string str(length, '\0');
f.read(&str[0], length);
return str;
}
struct Chunk {
std::string type;
uint32_t size;
uint16_t version;
};
Chunk parseChunk(std::ifstream& f) {
char type[5] = {0};
f.read(type, 4);
Chunk chunk;
chunk.type = type;
chunk.size = readDword(f);
chunk.version = readWord(f);
return chunk;
}
void parseRMF(std::ifstream& f) {
uint32_t fileVersion = readDword(f);
uint32_t numHeaders = readDword(f);
std::map<std::string, std::string> rmfProps;
rmfProps["File Version"] = std::to_string(fileVersion);
rmfProps["Number of Headers"] = std::to_string(numHeaders);
properties["RMF"].push_back(rmfProps);
}
void parsePROP(std::ifstream& f) {
uint32_t maxBitrate = readDword(f);
uint32_t avgBitrate = readDword(f);
uint32_t maxPacketSize = readDword(f);
uint32_t avgPacketSize = readDword(f);
uint32_t numPackets = readDword(f);
uint32_t duration = readDword(f);
uint32_t preroll = readDword(f);
uint32_t indxOffset = readDword(f);
uint32_t dataOffset = readDword(f);
uint16_t numStreams = readWord(f);
uint16_t flags = readWord(f);
std::map<std::string, std::string> propProps;
propProps["Max Bitrate"] = std::to_string(maxBitrate);
propProps["Avg Bitrate"] = std::to_string(avgBitrate);
propProps["Max Packet Size"] = std::to_string(maxPacketSize);
propProps["Avg Packet Size"] = std::to_string(avgPacketSize);
propProps["Num Packets"] = std::to_string(numPackets);
propProps["Duration (ms)"] = std::to_string(duration);
propProps["Preroll (ms)"] = std::to_string(preroll);
propProps["INDX Offset"] = std::to_string(indxOffset);
propProps["DATA Offset"] = std::to_string(dataOffset);
propProps["Num Streams"] = std::to_string(numStreams);
propProps["Flags"] = std::to_string(flags);
properties["PROP"].push_back(propProps);
}
void parseMDPR(std::ifstream& f) {
uint16_t streamNum = readWord(f);
uint32_t maxBitrate = readDword(f);
uint32_t avgBitrate = readDword(f);
uint32_t maxPacketSize = readDword(f);
uint32_t avgPacketSize = readDword(f);
uint32_t startOffset = readDword(f);
uint32_t preroll = readDword(f);
uint32_t duration = readDword(f);
uint8_t descLen = readByte(f);
std::string desc = readString(f, descLen);
uint8_t mimeLen = readByte(f);
std::string mime = readString(f, mimeLen);
uint32_t typeSize = readDword(f);
f.seekg(typeSize, std::ios::cur);
std::map<std::string, std::string> streamProps;
streamProps["Stream Num"] = std::to_string(streamNum);
streamProps["Max Bitrate"] = std::to_string(maxBitrate);
streamProps["Avg Bitrate"] = std::to_string(avgBitrate);
streamProps["Max Packet Size"] = std::to_string(maxPacketSize);
streamProps["Avg Packet Size"] = std::to_string(avgPacketSize);
streamProps["Start Offset (ms)"] = std::to_string(startOffset);
streamProps["Preroll (ms)"] = std::to_string(preroll);
streamProps["Duration (ms)"] = std::to_string(duration);
streamProps["Description"] = desc;
streamProps["Mime Type"] = mime;
streamProps["Type-Specific Size"] = std::to_string(typeSize);
properties["MDPR"].push_back(streamProps);
}
void parseCONT(std::ifstream& f) {
uint16_t titleLen = readWord(f);
std::string title = readString(f, titleLen);
uint16_t authorLen = readWord(f);
std::string author = readString(f, authorLen);
uint16_t copyrightLen = readWord(f);
std::string copyright = readString(f, copyrightLen);
uint16_t commentLen = readWord(f);
std::string comment = readString(f, commentLen);
std::map<std::string, std::string> contProps;
contProps["Title"] = title;
contProps["Author"] = author;
contProps["Copyright"] = copyright;
contProps["Comment"] = comment;
properties["CONT"].push_back(contProps);
}
public:
void read() {
std::ifstream f(filename, std::ios::binary);
if (!f) return;
while (f) {
Chunk chunk = parseChunk(f);
if (chunk.type == ".RMF" || chunk.type == ".RMP") {
parseRMF(f);
} else if (chunk.type == "PROP") {
parsePROP(f);
} else if (chunk.type == "MDPR") {
parseMDPR(f);
} else if (chunk.type == "CONT") {
parseCONT(f);
} else {
f.seekg(chunk.size - 10, std::ios::cur);
}
}
f.close();
}
void printProperties() {
for (const auto& [chunk, propList] : properties) {
std::cout << chunk << ":" << std::endl;
for (size_t i = 0; i < propList.size(); ++i) {
if (propList.size() > 1) std::cout << " Stream " << i << ":" << std::endl;
for (const auto& [key, val] : propList[i]) {
std::cout << " " << key << ": " << val << std::endl;
}
}
}
}
void write(const std::string& outputFilename) {
std::ofstream out(outputFilename, std::ios::binary);
if (!out) return;
// Write .RMF
if (properties.count("RMF")) {
auto& rmf = properties["RMF"][0];
out.write(".RMF", 4);
uint32_t size = 18; out.write(reinterpret_cast<char*>(&size), 4); // No bswap for write, assume LE and adjust if needed
uint16_t ver = 0; out.write(reinterpret_cast<char*>(&ver), 2);
uint32_t fileVer = std::stoul(rmf.at("File Version")); out.write(reinterpret_cast<char*>(&fileVer), 4);
uint32_t numHdrs = std::stoul(rmf.at("Number of Headers")); out.write(reinterpret_cast<char*>(&numHdrs), 4);
}
// Write PROP
if (properties.count("PROP")) {
auto& p = properties["PROP"][0];
out.write("PROP", 4);
uint32_t size = 50; out.write(reinterpret_cast<char*>(&size), 4);
uint16_t ver = 0; out.write(reinterpret_cast<char*>(&ver), 2);
uint32_t val = std::stoul(p.at("Max Bitrate")); out.write(reinterpret_cast<char*>(&val), 4);
val = std::stoul(p.at("Avg Bitrate")); out.write(reinterpret_cast<char*>(&val), 4);
val = std::stoul(p.at("Max Packet Size")); out.write(reinterpret_cast<char*>(&val), 4);
val = std::stoul(p.at("Avg Packet Size")); out.write(reinterpret_cast<char*>(&val), 4);
val = std::stoul(p.at("Num Packets")); out.write(reinterpret_cast<char*>(&val), 4);
val = std::stoul(p.at("Duration (ms)")); out.write(reinterpret_cast<char*>(&val), 4);
val = std::stoul(p.at("Preroll (ms)")); out.write(reinterpret_cast<char*>(&val), 4);
val = std::stoul(p.at("INDX Offset")); out.write(reinterpret_cast<char*>(&val), 4);
val = std::stoul(p.at("DATA Offset")); out.write(reinterpret_cast<char*>(&val), 4);
uint16_t sVal = std::stoul(p.at("Num Streams")); out.write(reinterpret_cast<char*>(&sVal), 2);
sVal = std::stoul(p.at("Flags")); out.write(reinterpret_cast<char*>(&sVal), 2);
}
// Write MDPR(s)
if (properties.count("MDPR")) {
for (const auto& stream : properties["MDPR"]) {
std::string desc = stream.at("Description");
std::string mime = stream.at("Mime Type");
uint32_t typeSize = std::stoul(stream.at("Type-Specific Size"));
uint32_t mdprSize = 38 + desc.size() + mime.size() + typeSize + 10;
out.write("MDPR", 4);
out.write(reinterpret_cast<char*>(&mdprSize), 4);
uint16_t ver = 0; out.write(reinterpret_cast<char*>(&ver), 2);
uint16_t sNum = std::stoul(stream.at("Stream Num")); out.write(reinterpret_cast<char*>(&sNum), 2);
uint32_t val = std::stoul(stream.at("Max Bitrate")); out.write(reinterpret_cast<char*>(&val), 4);
val = std::stoul(stream.at("Avg Bitrate")); out.write(reinterpret_cast<char*>(&val), 4);
val = std::stoul(stream.at("Max Packet Size")); out.write(reinterpret_cast<char*>(&val), 4);
val = std::stoul(stream.at("Avg Packet Size")); out.write(reinterpret_cast<char*>(&val), 4);
val = std::stoul(stream.at("Start Offset (ms)")); out.write(reinterpret_cast<char*>(&val), 4);
val = std::stoul(stream.at("Preroll (ms)")); out.write(reinterpret_cast<char*>(&val), 4);
val = std::stoul(stream.at("Duration (ms)")); out.write(reinterpret_cast<char*>(&val), 4);
uint8_t len = desc.size(); out.write(reinterpret_cast<char*>(&len), 1);
out.write(desc.c_str(), desc.size());
len = mime.size(); out.write(reinterpret_cast<char*>(&len), 1);
out.write(mime.c_str(), mime.size());
out.write(reinterpret_cast<char*>(&typeSize), 4);
// Placeholder
for (uint32_t i = 0; i < typeSize; ++i) out.put(0);
}
}
// Write CONT
if (properties.count("CONT")) {
auto& c = properties["CONT"][0];
std::string title = c.at("Title");
std::string author = c.at("Author");
std::string copyright = c.at("Copyright");
std::string comment = c.at("Comment");
uint32_t contSize = 10 + 8 + title.size() + author.size() + copyright.size() + comment.size();
out.write("CONT", 4);
out.write(reinterpret_cast<char*>(&contSize), 4);
uint16_t ver = 0; out.write(reinterpret_cast<char*>(&ver), 2);
uint16_t len = title.size(); out.write(reinterpret_cast<char*>(&len), 2);
out.write(title.c_str(), title.size());
len = author.size(); out.write(reinterpret_cast<char*>(&len), 2);
out.write(author.c_str(), author.size());
len = copyright.size(); out.write(reinterpret_cast<char*>(&len), 2);
out.write(copyright.c_str(), copyright.size());
len = comment.size(); out.write(reinterpret_cast<char*>(&len), 2);
out.write(comment.c_str(), comment.size());
}
out.close();
}
};
int main(int argc, char* argv[]) {
if (argc < 2) {
std::cout << "Usage: " << argv[0] << " input.rm [output.rm]" << std::endl;
return 1;
}
RMParser parser(argv[1]);
parser.read();
parser.printProperties();
if (argc > 2) {
parser.write(argv[2]);
}
return 0;
}