Task 592: .QTVR File Format

Task 592: .QTVR File Format

  1. 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.

  1. Two direct download links for .QTVR files:
  1. Ghost blog embedded HTML/JavaScript for drag and drop .QTVR file to dump properties:
QTVR Property Dumper
Drag and drop .QTVR file here
  1. 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})
  1. 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);
}
  1. 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);
  1. 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);