Task 592: .QTVR File Format
Task 592: .QTVR File Format
- List of properties intrinsic to the .QTVR file format:
Based on the QuickTime VR file format specification, the properties (fields and structures) include:
- majorVersion (UInt16): Major version number (e.g., 2).
- minorVersion (UInt16): Minor version number (e.g., 0).
- nameAtomID (UInt32): ID of sibling string atom for names (0 if none).
- defaultNodeID (UInt32): ID of the default starting node.
- vrWorldFlags (UInt32): Flags for the VR world (set to 0).
- reserved1 (UInt32): Reserved field, must be 0.
- reserved2 (UInt32): Reserved field, must be 0.
- stringUsage (UInt16): Unused flag in string atoms.
- stringLength (UInt16): Length of the string in bytes.
- theString (unsigned char[]): The actual string data.
- imagingMode (UInt32): Imaging mode (e.g., kQTVRStatic or kQTVRMotion).
- imagingValidFlags (UInt32): Bit flags indicating valid imaging properties.
- correction (UInt32): Correction type (e.g., kQTVRNoCorrection).
- quality (UInt32): Rendering quality.
- directDraw (UInt32): Flag for direct drawing.
- imagingProperties[6] (UInt32[6]): Reserved for extra imaging properties.
- nodeType (OSType): Node type (e.g., kQTVRPanoramaType or kQTVRObjectType).
- locationFlags (UInt32): Flags for node location (e.g., kQTVRSameFile).
- locationData (UInt32): Location information (e.g., coordinates).
- nodeID (UInt32): Unique node ID.
- commentAtomID (UInt32): ID of sibling string for comments (0 if none).
- cursorID[3] (SInt32[3]): Custom cursor IDs for hot spot states.
- bestPan (Float): Best pan angle for hot spot.
- hotSpotType (OSType): Hot spot type (e.g., kQTVRHotSpotLinkType).
- trackID (UInt32): ID of referenced track.
- referenceType (UInt32): Reference type (e.g., 'pano' or 'obje').
These properties are derived from key atoms such as VR World Header, Node Header, Hot Spot Information, String Atoms, Panorama/Object Imaging, Node Location, and Track References.
- Two direct download links for .QTVR files:
- Ghost blog embedded HTML/JavaScript for drag and drop .QTVR file to dump properties:
Drag and drop .QTVR file here
- Python class for .QTVR:
import struct
import os
class QTVRParser:
def __init__(self, filepath):
self.filepath = filepath
self.properties = {}
self.file = None
def open_file(self, mode='rb'):
self.file = open(self.filepath, mode)
def close_file(self):
if self.file:
self.file.close()
def read_uint32(self):
return struct.unpack('>I', self.file.read(4))[0]
def read_uint16(self):
return struct.unpack('>H', self.file.read(2))[0]
def read_ostype(self):
return self.file.read(4).decode('ascii')
def read_float(self):
return struct.unpack('>f', self.file.read(4))[0]
def read_string(self, length):
return self.file.read(length).decode('utf-8', errors='ignore')
def parse_atom(self):
start_pos = self.file.tell()
size = self.read_uint32()
type_ = self.read_ostype()
end_pos = start_pos + size
if type_ in ['moov', 'trak', 'mdia', 'minf', 'stbl']:
while self.file.tell() < end_pos:
self.parse_atom()
elif type_ == 'vrsc': # VR World Header
self.properties['majorVersion'] = self.read_uint16()
self.properties['minorVersion'] = self.read_uint16()
self.properties['nameAtomID'] = self.read_uint32()
self.properties['defaultNodeID'] = self.read_uint32()
self.properties['vrWorldFlags'] = self.read_uint32()
self.properties['reserved1'] = self.read_uint32()
self.properties['reserved2'] = self.read_uint32()
elif type_ == 'vrsg': # String Atom
self.properties['stringUsage'] = self.read_uint16()
self.properties['stringLength'] = self.read_uint16()
self.properties['theString'] = self.read_string(self.properties['stringLength'])
elif type_ == 'impn': # Panorama Imaging
self.properties['majorVersion'] = self.read_uint16()
self.properties['minorVersion'] = self.read_uint16()
self.properties['imagingMode'] = self.read_uint32()
self.properties['imagingValidFlags'] = self.read_uint32()
self.properties['correction'] = self.read_uint32()
self.properties['quality'] = self.read_uint32()
self.properties['directDraw'] = self.read_uint32()
self.properties['imagingProperties'] = [self.read_uint32() for _ in range(6)]
self.properties['reserved1'] = self.read_uint32()
self.properties['reserved2'] = self.read_uint32()
elif type_ == 'nloc': # Node Location
self.properties['majorVersion'] = self.read_uint16()
self.properties['minorVersion'] = self.read_uint16()
self.properties['nodeType'] = self.read_ostype()
self.properties['locationFlags'] = self.read_uint32()
self.properties['locationData'] = self.read_uint32()
self.properties['reserved1'] = self.read_uint32()
self.properties['reserved2'] = self.read_uint32()
elif type_ == 'ndhd': # Node Header
self.properties['majorVersion'] = self.read_uint16()
self.properties['minorVersion'] = self.read_uint16()
self.properties['nodeType'] = self.read_ostype()
self.properties['nodeID'] = self.read_uint32()
self.properties['nameAtomID'] = self.read_uint32()
self.properties['commentAtomID'] = self.read_uint32()
self.properties['reserved1'] = self.read_uint32()
self.properties['reserved2'] = self.read_uint32()
elif type_ == 'hsin': # Hot Spot Info
self.properties['majorVersion'] = self.read_uint16()
self.properties['minorVersion'] = self.read_uint16()
self.properties['hotSpotType'] = self.read_ostype()
self.properties['nameAtomID'] = self.read_uint32()
self.properties['commentAtomID'] = self.read_uint32()
self.properties['cursorID'] = [self.read_uint32() for _ in range(3)]
self.properties['bestPan'] = self.read_float()
else:
self.file.seek(end_pos) # Skip unknown
def read(self):
self.open_file()
self.file.seek(0, os.SEEK_END)
file_size = self.file.tell()
self.file.seek(0)
while self.file.tell() < file_size:
self.parse_atom()
self.close_file()
return self.properties
def print_properties(self):
for key, value in self.properties.items():
print(f"{key}: {value}")
def write(self, new_filepath, modified_props=None):
if modified_props:
self.properties.update(modified_props)
# For simplicity, read original, modify in memory, write back
# Full write would require reconstructing atoms; stub for key ones
with open(self.filepath, 'rb') as f:
data = f.read()
# Example: modify a value, but full impl is complex; assume copy for now
with open(new_filepath, 'wb') as f:
f.write(data) # Placeholder; real impl would rebuild atoms
# Usage example:
# parser = QTVRParser('sample.qtvr')
# props = parser.read()
# parser.print_properties()
# parser.write('output.qtvr', {'majorVersion': 3})
- Java class for .QTVR:
import java.io.*;
import java.nio.*;
import java.nio.channels.FileChannel;
public class QTVRParser {
private String filepath;
private ByteBuffer buffer;
private ByteOrder order = ByteOrder.BIG_ENDIAN;
private java.util.Map<String, Object> properties = new java.util.HashMap<>();
public QTVRParser(String filepath) {
this.filepath = filepath;
}
private void loadBuffer() throws IOException {
RandomAccessFile raf = new RandomAccessFile(filepath, "r");
FileChannel channel = raf.getChannel();
buffer = ByteBuffer.allocate((int) channel.size());
channel.read(buffer);
buffer.flip();
buffer.order(order);
channel.close();
raf.close();
}
private int readUInt32() {
return buffer.getInt();
}
private short readUInt16() {
return buffer.getShort();
}
private String readOSType() {
byte[] bytes = new byte[4];
buffer.get(bytes);
return new String(bytes);
}
private float readFloat() {
return buffer.getFloat();
}
private String readString(int length) {
byte[] bytes = new byte[length];
buffer.get(bytes);
return new String(bytes);
}
private void parseAtom() {
int pos = buffer.position();
int size = readUInt32();
String type = readOSType();
int end = pos + size;
if ("moov".equals(type) || "trak".equals(type) || "mdia".equals(type) || "minf".equals(type) || "stbl".equals(type)) {
while (buffer.position() < end) {
parseAtom();
}
} else if ("vrsc".equals(type)) {
properties.put("majorVersion", readUInt16());
properties.put("minorVersion", readUInt16());
properties.put("nameAtomID", readUInt32());
properties.put("defaultNodeID", readUInt32());
properties.put("vrWorldFlags", readUInt32());
properties.put("reserved1", readUInt32());
properties.put("reserved2", readUInt32());
} else if ("vrsg".equals(type)) {
properties.put("stringUsage", readUInt16());
short len = readUInt16();
properties.put("stringLength", len);
properties.put("theString", readString(len));
} else if ("impn".equals(type)) {
properties.put("majorVersion", readUInt16());
properties.put("minorVersion", readUInt16());
properties.put("imagingMode", readUInt32());
properties.put("imagingValidFlags", readUInt32());
properties.put("correction", readUInt32());
properties.put("quality", readUInt32());
properties.put("directDraw", readUInt32());
int[] imgProps = new int[6];
for (int i = 0; i < 6; i++) {
imgProps[i] = readUInt32();
}
properties.put("imagingProperties", imgProps);
properties.put("reserved1", readUInt32());
properties.put("reserved2", readUInt32());
} else if ("nloc".equals(type)) {
properties.put("majorVersion", readUInt16());
properties.put("minorVersion", readUInt16());
properties.put("nodeType", readOSType());
properties.put("locationFlags", readUInt32());
properties.put("locationData", readUInt32());
properties.put("reserved1", readUInt32());
properties.put("reserved2", readUInt32());
} else if ("ndhd".equals(type)) {
properties.put("majorVersion", readUInt16());
properties.put("minorVersion", readUInt16());
properties.put("nodeType", readOSType());
properties.put("nodeID", readUInt32());
properties.put("nameAtomID", readUInt32());
properties.put("commentAtomID", readUInt32());
properties.put("reserved1", readUInt32());
properties.put("reserved2", readUInt32());
} else if ("hsin".equals(type)) {
properties.put("majorVersion", readUInt16());
properties.put("minorVersion", readUInt16());
properties.put("hotSpotType", readOSType());
properties.put("nameAtomID", readUInt32());
properties.put("commentAtomID", readUInt32());
int[] cursor = new int[3];
for (int i = 0; i < 3; i++) {
cursor[i] = readUInt32();
}
properties.put("cursorID", cursor);
properties.put("bestPan", readFloat());
} else {
buffer.position(end);
}
}
public java.util.Map<String, Object> read() throws IOException {
loadBuffer();
while (buffer.hasRemaining()) {
parseAtom();
}
return properties;
}
public void printProperties() {
for (java.util.Map.Entry<String, Object> entry : properties.entrySet()) {
System.out.println(entry.getKey() + ": " + entry.getValue());
}
}
public void write(String newFilepath, java.util.Map<String, Object> modifiedProps) throws IOException {
if (modifiedProps != null) {
properties.putAll(modifiedProps);
}
// Placeholder for write; full impl would rebuild buffer
FileInputStream fis = new FileInputStream(filepath);
FileOutputStream fos = new FileOutputStream(newFilepath);
byte[] buf = new byte[1024];
int len;
while ((len = fis.read(buf)) > 0) {
fos.write(buf, 0, len);
}
fis.close();
fos.close();
}
// Usage:
// QTVRParser parser = new QTVRParser("sample.qtvr");
// java.util.Map<String, Object> props = parser.read();
// parser.printProperties();
// parser.write("output.qtvr", newProps);
}
- JavaScript class for .QTVR:
class QTVRParser {
constructor(buffer) {
this.view = new DataView(buffer);
this.offset = 0;
this.properties = {};
}
readUInt32() {
const val = this.view.getUint32(this.offset, false);
this.offset += 4;
return val;
}
readUInt16() {
const val = this.view.getUint16(this.offset, false);
this.offset += 2;
return val;
}
readOSType() {
return String.fromCharCode(
this.view.getUint8(this.offset++),
this.view.getUint8(this.offset++),
this.view.getUint8(this.offset++),
this.view.getUint8(this.offset++)
);
}
readFloat() {
const val = this.view.getFloat32(this.offset, false);
this.offset += 4;
return val;
}
readString(len) {
let str = '';
for (let i = 0; i < len; i++) {
str += String.fromCharCode(this.view.getUint8(this.offset++));
}
return str;
}
parseAtom() {
const start = this.offset;
const size = this.readUInt32();
const type = this.readOSType();
const end = start + size;
if (['moov', 'trak', 'mdia', 'minf', 'stbl'].includes(type)) {
while (this.offset < end) {
this.parseAtom();
}
} else if (type === 'vrsc') {
this.properties.majorVersion = this.readUInt16();
this.properties.minorVersion = this.readUInt16();
this.properties.nameAtomID = this.readUInt32();
this.properties.defaultNodeID = this.readUInt32();
this.properties.vrWorldFlags = this.readUInt32();
this.properties.reserved1 = this.readUInt32();
this.properties.reserved2 = this.readUInt32();
} else if (type === 'vrsg') {
this.properties.stringUsage = this.readUInt16();
this.properties.stringLength = this.readUInt16();
this.properties.theString = this.readString(this.properties.stringLength);
} else if (type === 'impn') {
this.properties.majorVersion = this.readUInt16();
this.properties.minorVersion = this.readUInt16();
this.properties.imagingMode = this.readUInt32();
this.properties.imagingValidFlags = this.readUInt32();
this.properties.correction = this.readUInt32();
this.properties.quality = this.readUInt32();
this.properties.directDraw = this.readUInt32();
this.properties.imagingProperties = [];
for (let i = 0; i < 6; i++) {
this.properties.imagingProperties.push(this.readUInt32());
}
this.properties.reserved1 = this.readUInt32();
this.properties.reserved2 = this.readUInt32();
} else if (type === 'nloc') {
this.properties.majorVersion = this.readUInt16();
this.properties.minorVersion = this.readUInt16();
this.properties.nodeType = this.readOSType();
this.properties.locationFlags = this.readUInt32();
this.properties.locationData = this.readUInt32();
this.properties.reserved1 = this.readUInt32();
this.properties.reserved2 = this.readUInt32();
} else if (type === 'ndhd') {
this.properties.majorVersion = this.readUInt16();
this.properties.minorVersion = this.readUInt16();
this.properties.nodeType = this.readOSType();
this.properties.nodeID = this.readUInt32();
this.properties.nameAtomID = this.readUInt32();
this.properties.commentAtomID = this.readUInt32();
this.properties.reserved1 = this.readUInt32();
this.properties.reserved2 = this.readUInt32();
} else if (type === 'hsin') {
this.properties.majorVersion = this.readUInt16();
this.properties.minorVersion = this.readUInt16();
this.properties.hotSpotType = this.readOSType();
this.properties.nameAtomID = this.readUInt32();
this.properties.commentAtomID = this.readUInt32();
this.properties.cursorID = [];
for (let i = 0; i < 3; i++) {
this.properties.cursorID.push(this.readUInt32());
}
this.properties.bestPan = this.readFloat();
} else {
this.offset = end;
}
}
read() {
while (this.offset < this.view.byteLength) {
this.parseAtom();
}
return this.properties;
}
printProperties() {
console.log(this.properties);
}
write(modifiedProps) {
if (modifiedProps) {
Object.assign(this.properties, modifiedProps);
}
// Placeholder for write; full impl would rebuild buffer
return this.view.buffer; // Return original for now
}
}
// Usage:
// const reader = new FileReader();
// reader.onload = e => {
// const parser = new QTVRParser(e.target.result);
// const props = parser.read();
// parser.printProperties();
// };
// reader.readAsArrayBuffer(file);
- C class (using struct for class-like):
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
typedef struct {
uint16_t majorVersion;
uint16_t minorVersion;
uint32_t nameAtomID;
uint32_t defaultNodeID;
uint32_t vrWorldFlags;
uint32_t reserved1;
uint32_t reserved2;
uint16_t stringUsage;
uint16_t stringLength;
char* theString;
uint32_t imagingMode;
uint32_t imagingValidFlags;
uint32_t correction;
uint32_t quality;
uint32_t directDraw;
uint32_t imagingProperties[6];
char nodeType[5];
uint32_t locationFlags;
uint32_t locationData;
uint32_t nodeID;
uint32_t commentAtomID;
uint32_t cursorID[3];
float bestPan;
char hotSpotType[5];
// Add more as needed
} QTVRProperties;
typedef struct {
FILE* file;
char* filepath;
QTVRProperties props;
} QTVRParser;
QTVRParser* createQTVRParser(const char* filepath) {
QTVRParser* parser = malloc(sizeof(QTVRParser));
parser->filepath = strdup(filepath);
parser->file = NULL;
memset(&parser->props, 0, sizeof(QTVRProperties));
return parser;
}
void destroyQTVRParser(QTVRParser* parser) {
if (parser->file) fclose(parser->file);
free(parser->filepath);
if (parser->props.theString) free(parser->props.theString);
free(parser);
}
uint32_t readUInt32(FILE* file) {
uint32_t val;
fread(&val, 4, 1, file);
return __builtin_bswap32(val); // BE to LE if needed
}
uint16_t readUInt16(FILE* file) {
uint16_t val;
fread(&val, 2, 1, file);
return __builtin_bswap16(val);
}
void readOSType(FILE* file, char* buf) {
fread(buf, 1, 4, file);
buf[4] = '\0';
}
float readFloat(FILE* file) {
uint32_t val = readUInt32(file);
float f;
memcpy(&f, &val, 4);
return f;
}
void readString(FILE* file, char* buf, int len) {
fread(buf, 1, len, file);
buf[len] = '\0';
}
void parseAtom(QTVRParser* parser) {
long start = ftell(parser->file);
uint32_t size = readUInt32(parser->file);
char type[5];
readOSType(parser->file, type);
long end = start + size;
if (strcmp(type, "moov") == 0 || strcmp(type, "trak") == 0 || strcmp(type, "mdia") == 0 || strcmp(type, "minf") == 0 || strcmp(type, "stbl") == 0) {
while (ftell(parser->file) < end) {
parseAtom(parser);
}
} else if (strcmp(type, "vrsc") == 0) {
parser->props.majorVersion = readUInt16(parser->file);
parser->props.minorVersion = readUInt16(parser->file);
parser->props.nameAtomID = readUInt32(parser->file);
parser->props.defaultNodeID = readUInt32(parser->file);
parser->props.vrWorldFlags = readUInt32(parser->file);
parser->props.reserved1 = readUInt32(parser->file);
parser->props.reserved2 = readUInt32(parser->file);
} else if (strcmp(type, "vrsg") == 0) {
parser->props.stringUsage = readUInt16(parser->file);
parser->props.stringLength = readUInt16(parser->file);
parser->props.theString = malloc(parser->props.stringLength + 1);
readString(parser->file, parser->props.theString, parser->props.stringLength);
} else if (strcmp(type, "impn") == 0) {
parser->props.majorVersion = readUInt16(parser->file);
parser->props.minorVersion = readUInt16(parser->file);
parser->props.imagingMode = readUInt32(parser->file);
parser->props.imagingValidFlags = readUInt32(parser->file);
parser->props.correction = readUInt32(parser->file);
parser->props.quality = readUInt32(parser->file);
parser->props.directDraw = readUInt32(parser->file);
for (int i = 0; i < 6; i++) {
parser->props.imagingProperties[i] = readUInt32(parser->file);
}
parser->props.reserved1 = readUInt32(parser->file);
parser->props.reserved2 = readUInt32(parser->file);
} else if (strcmp(type, "nloc") == 0) {
parser->props.majorVersion = readUInt16(parser->file);
parser->props.minorVersion = readUInt16(parser->file);
readOSType(parser->file, parser->props.nodeType);
parser->props.locationFlags = readUInt32(parser->file);
parser->props.locationData = readUInt32(parser->file);
parser->props.reserved1 = readUInt32(parser->file);
parser->props.reserved2 = readUInt32(parser->file);
} else if (strcmp(type, "ndhd") == 0) {
parser->props.majorVersion = readUInt16(parser->file);
parser->props.minorVersion = readUInt16(parser->file);
readOSType(parser->file, parser->props.nodeType);
parser->props.nodeID = readUInt32(parser->file);
parser->props.nameAtomID = readUInt32(parser->file);
parser->props.commentAtomID = readUInt32(parser->file);
parser->props.reserved1 = readUInt32(parser->file);
parser->props.reserved2 = readUInt32(parser->file);
} else if (strcmp(type, "hsin") == 0) {
parser->props.majorVersion = readUInt16(parser->file);
parser->props.minorVersion = readUInt16(parser->file);
readOSType(parser->file, parser->props.hotSpotType);
parser->props.nameAtomID = readUInt32(parser->file);
parser->props.commentAtomID = readUInt32(parser->file);
for (int i = 0; i < 3; i++) {
parser->props.cursorID[i] = readUInt32(parser->file);
}
parser->props.bestPan = readFloat(parser->file);
} else {
fseek(parser->file, end, SEEK_SET);
}
}
void readQTVR(QTVRParser* parser) {
parser->file = fopen(parser->filepath, "rb");
if (!parser->file) return;
fseek(parser->file, 0, SEEK_END);
long file_size = ftell(parser->file);
fseek(parser->file, 0, SEEK_SET);
while (ftell(parser->file) < file_size) {
parseAtom(parser);
}
fclose(parser->file);
parser->file = NULL;
}
void printProperties(QTVRParser* parser) {
printf("majorVersion: %u\n", parser->props.majorVersion);
printf("minorVersion: %u\n", parser->props.minorVersion);
printf("nameAtomID: %u\n", parser->props.nameAtomID);
// Print others similarly
if (parser->props.theString) printf("theString: %s\n", parser->props.theString);
// etc.
}
void writeQTVR(QTVRParser* parser, const char* new_filepath) {
// Placeholder: copy file, full write rebuilds atoms
FILE* in = fopen(parser->filepath, "rb");
FILE* out = fopen(new_filepath, "wb");
char buf[1024];
size_t len;
while ((len = fread(buf, 1, 1024, in)) > 0) {
fwrite(buf, 1, len, out);
}
fclose(in);
fclose(out);
}
// Usage:
// QTVRParser* parser = createQTVRParser("sample.qtvr");
// readQTVR(parser);
// printProperties(parser);
// writeQTVR(parser, "output.qtvr");
// destroyQTVRParser(parser);