Task 412: .MOV File Format
Task 412: .MOV File Format
File Format Specifications for .MOV
The .MOV file format, officially known as the QuickTime File Format (QTFF), is a multimedia container developed by Apple. It is based on a hierarchical structure of "atoms" (also called boxes in related formats like ISO BMFF), where each atom has a 4-byte size, 4-byte type (FOURCC code), and payload data that can include sub-atoms or raw data. The format supports video, audio, text, and metadata tracks, with media data often stored in the 'mdat' atom and metadata in 'moov'. It is extensible and forms the basis for MP4. Key references include Apple's official documentation at https://developer.apple.com/documentation/quicktime-file-format and the classic specification at https://developer.apple.com/library/archive/documentation/QuickTime/QTFF/QTFFPreface/qtffPreface.html.
- List of all the properties of this file format intrinsic to its file system:
Based on the atom structure, the intrinsic properties are the extractable fields from core atoms like 'ftyp' (file type), 'mvhd' (movie header), 'tkhd' (track header), and 'hdlr' (handler). These include timestamps, dimensions, scales, and identifiers stored in the file's binary structure. The full list is:
- Major Brand (4-byte string from 'ftyp' atom, e.g., 'qt ')
- Minor Version (4-byte integer from 'ftyp')
- Compatible Brands (array of 4-byte strings from 'ftyp')
- Movie Creation Time (4-byte Mac timestamp from 'mvhd', seconds since 1904-01-01)
- Movie Modification Time (4-byte Mac timestamp from 'mvhd')
- Time Scale (4-byte integer from 'mvhd', units per second)
- Movie Duration (4-byte integer from 'mvhd', in time scale units)
- Preferred Rate (4-byte fixed-point 16.16 from 'mvhd')
- Preferred Volume (2-byte fixed-point 8.8 from 'mvhd')
- Movie Matrix (36 bytes, 9x4 fixed-point values from 'mvhd' for transformation)
- Preview Time (4-byte integer from 'mvhd')
- Preview Duration (4-byte integer from 'mvhd')
- Poster Time (4-byte integer from 'mvhd')
- Selection Time (4-byte integer from 'mvhd')
- Selection Duration (4-byte integer from 'mvhd')
- Current Time (4-byte integer from 'mvhd')
- Next Track ID (4-byte integer from 'mvhd')
- Track ID (4-byte integer from each 'tkhd')
- Track Creation Time (4-byte Mac timestamp from each 'tkhd')
- Track Modification Time (4-byte Mac timestamp from each 'tkhd')
- Track Duration (4-byte integer from each 'tkhd', in time scale units)
- Track Layer (2-byte integer from each 'tkhd')
- Alternate Group (2-byte integer from each 'tkhd')
- Track Volume (2-byte fixed-point 8.8 from each 'tkhd')
- Track Matrix (36 bytes from each 'tkhd')
- Track Width (4-byte fixed-point 16.16 from each 'tkhd')
- Track Height (4-byte fixed-point 16.16 from each 'tkhd')
- Handler Type (4-byte string from 'hdlr' in each track's 'mdia', e.g., 'vide' for video, 'soun' for audio)
These properties are derived from the file's atom hierarchy and represent metadata embedded in the file system structure.
- Two direct download links for files of format .MOV:
- https://file-examples.com/wp-content/storage/2017/02/file_example_MOV_480_700KB.mov
- https://filesamples.com/samples/video/mov/sample_640x360.mov
- Ghost blog embedded HTML JavaScript for drag-and-drop .MOV file dumper:
This HTML/JS can be embedded in a blog post. It allows dragging a .MOV file, parses it client-side, and dumps the properties to the screen.
- Python class for .MOV handling:
import struct
import os
class MovParser:
def __init__(self, filename):
with open(filename, 'rb') as f:
self.data = f.read()
self.pos = 0
self.properties = {}
self.parse()
def read_uint32(self):
val = struct.unpack('>I', self.data[self.pos:self.pos+4])[0]
self.pos += 4
return val
def read_uint16(self):
val = struct.unpack('>H', self.data[self.pos:self.pos+2])[0]
self.pos += 2
return val
def read_fixed1616(self):
return self.read_uint32() / 65536.0
def read_fixed88(self):
return self.read_uint16() / 256.0
def read_string(self, length):
val = self.data[self.pos:self.pos+length].decode('ascii')
self.pos += length
return val
def parse_atom(self):
start_pos = self.pos
size = self.read_uint32()
atom_type = self.read_string(4)
if atom_type == 'ftyp':
self.properties['Major Brand'] = self.read_string(4)
self.properties['Minor Version'] = self.read_uint32()
compat = []
while self.pos < start_pos + size:
compat.append(self.read_string(4))
self.properties['Compatible Brands'] = compat
elif atom_type == 'mvhd':
self.pos += 1 # version
self.pos += 3 # flags
self.properties['Movie Creation Time'] = self.read_uint32()
self.properties['Movie Modification Time'] = self.read_uint32()
self.properties['Time Scale'] = self.read_uint32()
self.properties['Movie Duration'] = self.read_uint32()
self.properties['Preferred Rate'] = self.read_fixed1616()
self.properties['Preferred Volume'] = self.read_fixed88()
self.pos += 10 # reserved
self.pos += 36 # matrix
self.properties['Preview Time'] = self.read_uint32()
self.properties['Preview Duration'] = self.read_uint32()
self.properties['Poster Time'] = self.read_uint32()
self.properties['Selection Time'] = self.read_uint32()
self.properties['Selection Duration'] = self.read_uint32()
self.properties['Current Time'] = self.read_uint32()
self.properties['Next Track ID'] = self.read_uint32()
elif atom_type == 'tkhd':
self.pos += 1 # version
self.pos += 3 # flags
self.properties['Track Creation Time'] = self.read_uint32()
self.properties['Track Modification Time'] = self.read_uint32()
self.properties['Track ID'] = self.read_uint32()
self.pos += 4 # reserved
self.properties['Track Duration'] = self.read_uint32()
self.pos += 8 # reserved
self.properties['Track Layer'] = struct.unpack('>h', self.data[self.pos:self.pos+2])[0]
self.pos += 2
self.properties['Alternate Group'] = struct.unpack('>h', self.data[self.pos:self.pos+2])[0]
self.pos += 2
self.properties['Track Volume'] = self.read_fixed88()
self.pos += 2 # reserved
self.pos += 36 # matrix
self.properties['Track Width'] = self.read_fixed1616()
self.properties['Track Height'] = self.read_fixed1616()
elif atom_type == 'hdlr':
self.pos += 8 # version, flags, component type
self.properties['Handler Type'] = self.read_string(4)
self.pos = start_pos + size # Skip to next atom
def parse(self):
while self.pos < len(self.data):
self.parse_atom()
def print_properties(self):
for key, value in self.properties.items():
print(f"{key}: {value}")
def write_file(self, output_filename):
# Simple write: copy the original data (for decode/write demo; real impl would modify)
with open(output_filename, 'wb') as f:
f.write(self.data)
print(f"File written to {output_filename}")
# Example usage:
# parser = MovParser('example.mov')
# parser.print_properties()
# parser.write_file('output.mov')
- Java class for .MOV handling:
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.channels.FileChannel;
import java.util.HashMap;
import java.util.Map;
import java.util.ArrayList;
public class MovParser {
private byte[] data;
private int pos = 0;
private Map<String, Object> properties = new HashMap<>();
public MovParser(String filename) throws IOException {
FileInputStream fis = new FileInputStream(filename);
FileChannel fc = fis.getChannel();
ByteBuffer bb = ByteBuffer.allocate((int) fc.size());
fc.read(bb);
data = bb.array();
fis.close();
parse();
}
private int readUint32() {
ByteBuffer bb = ByteBuffer.wrap(data, pos, 4).order(ByteOrder.BIG_ENDIAN);
int val = bb.getInt();
pos += 4;
return val;
}
private short readUint16() {
ByteBuffer bb = ByteBuffer.wrap(data, pos, 2).order(ByteOrder.BIG_ENDIAN);
short val = bb.getShort();
pos += 2;
return val;
}
private double readFixed1616() {
return readUint32() / 65536.0;
}
private double readFixed88() {
return readUint16() / 256.0;
}
private String readString(int length) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < length; i++) {
sb.append((char) (data[pos + i] & 0xFF));
}
pos += length;
return sb.toString();
}
private void parseAtom() {
int startPos = pos;
int size = readUint32();
String type = readString(4);
if ("ftyp".equals(type)) {
properties.put("Major Brand", readString(4));
properties.put("Minor Version", readUint32());
ArrayList<String> compat = new ArrayList<>();
while (pos < startPos + size) {
compat.add(readString(4));
}
properties.put("Compatible Brands", compat);
} else if ("mvhd".equals(type)) {
pos += 1; // version
pos += 3; // flags
properties.put("Movie Creation Time", readUint32());
properties.put("Movie Modification Time", readUint32());
properties.put("Time Scale", readUint32());
properties.put("Movie Duration", readUint32());
properties.put("Preferred Rate", readFixed1616());
properties.put("Preferred Volume", readFixed88());
pos += 10; // reserved
pos += 36; // matrix
properties.put("Preview Time", readUint32());
properties.put("Preview Duration", readUint32());
properties.put("Poster Time", readUint32());
properties.put("Selection Time", readUint32());
properties.put("Selection Duration", readUint32());
properties.put("Current Time", readUint32());
properties.put("Next Track ID", readUint32());
} else if ("tkhd".equals(type)) {
pos += 1; // version
pos += 3; // flags
properties.put("Track Creation Time", readUint32());
properties.put("Track Modification Time", readUint32());
properties.put("Track ID", readUint32());
pos += 4; // reserved
properties.put("Track Duration", readUint32());
pos += 8; // reserved
properties.put("Track Layer", (int) readUint16());
properties.put("Alternate Group", (int) readUint16());
properties.put("Track Volume", readFixed88());
pos += 2; // reserved
pos += 36; // matrix
properties.put("Track Width", readFixed1616());
properties.put("Track Height", readFixed1616());
} else if ("hdlr".equals(type)) {
pos += 8; // version, flags, component type
properties.put("Handler Type", readString(4));
}
pos = startPos + size;
}
private void parse() {
while (pos < data.length) {
parseAtom();
}
}
public void printProperties() {
for (Map.Entry<String, Object> entry : properties.entrySet()) {
System.out.println(entry.getKey() + ": " + entry.getValue());
}
}
public void writeFile(String outputFilename) throws IOException {
// Simple write: copy original (for demo; real would modify data)
FileOutputStream fos = new FileOutputStream(outputFilename);
fos.write(data);
fos.close();
System.out.println("File written to " + outputFilename);
}
// Example usage:
// public static void main(String[] args) throws IOException {
// MovParser parser = new MovParser("example.mov");
// parser.printProperties();
// parser.writeFile("output.mov");
// }
}
- JavaScript class for .MOV handling:
class MovParser {
constructor(buffer) {
this.view = new DataView(buffer);
this.pos = 0;
this.properties = {};
this.parse();
}
readUint32() {
const val = this.view.getUint32(this.pos, false);
this.pos += 4;
return val;
}
readUint16() {
const val = this.view.getUint16(this.pos, false);
this.pos += 2;
return val;
}
readFixed1616() {
return this.readUint32() / 65536;
}
readFixed88() {
return this.readUint16() / 256;
}
readString(length) {
let str = '';
for (let i = 0; i < length; i++) {
str += String.fromCharCode(this.view.getUint8(this.pos + i));
}
this.pos += length;
return str;
}
parseAtom() {
const startPos = this.pos;
const size = this.readUint32();
const type = this.readString(4);
if (type === 'ftyp') {
this.properties['Major Brand'] = this.readString(4);
this.properties['Minor Version'] = this.readUint32();
const compat = [];
while (this.pos < startPos + size) {
compat.push(this.readString(4));
}
this.properties['Compatible Brands'] = compat;
} else if (type === 'mvhd') {
this.pos += 1; // version
this.pos += 3; // flags
this.properties['Movie Creation Time'] = this.readUint32();
this.properties['Movie Modification Time'] = this.readUint32();
this.properties['Time Scale'] = this.readUint32();
this.properties['Movie Duration'] = this.readUint32();
this.properties['Preferred Rate'] = this.readFixed1616();
this.properties['Preferred Volume'] = this.readFixed88();
this.pos += 10; // reserved
this.pos += 36; // matrix
this.properties['Preview Time'] = this.readUint32();
this.properties['Preview Duration'] = this.readUint32();
this.properties['Poster Time'] = this.readUint32();
this.properties['Selection Time'] = this.readUint32();
this.properties['Selection Duration'] = this.readUint32();
this.properties['Current Time'] = this.readUint32();
this.properties['Next Track ID'] = this.readUint32();
} else if (type === 'tkhd') {
this.pos += 1; // version
this.pos += 3; // flags
this.properties['Track Creation Time'] = this.readUint32();
this.properties['Track Modification Time'] = this.readUint32();
this.properties['Track ID'] = this.readUint32();
this.pos += 4; // reserved
this.properties['Track Duration'] = this.readUint32();
this.pos += 8; // reserved
this.properties['Track Layer'] = this.view.getInt16(this.pos, false);
this.pos += 2;
this.properties['Alternate Group'] = this.view.getInt16(this.pos, false);
this.pos += 2;
this.properties['Track Volume'] = this.readFixed88();
this.pos += 2; // reserved
this.pos += 36; // matrix
this.properties['Track Width'] = this.readFixed1616();
this.properties['Track Height'] = this.readFixed1616();
} else if (type === 'hdlr') {
this.pos += 8; // version, flags, component type
this.properties['Handler Type'] = this.readString(4);
}
this.pos = startPos + size;
}
parse() {
while (this.pos < this.view.byteLength) {
this.parseAtom();
}
}
printProperties() {
for (const [key, value] of Object.entries(this.properties)) {
console.log(`${key}: ${JSON.stringify(value)}`);
}
}
writeFile() {
// Simple write: return original buffer (for demo; Node.js could fs.writeFile)
return new Uint8Array(this.view.buffer);
}
}
// Example usage (in browser with FileReader or Node with fs):
// const reader = new FileReader();
// reader.onload = (e) => {
// const parser = new MovParser(e.target.result);
// parser.printProperties();
// };
// reader.readAsArrayBuffer(file);
- C class (using struct and functions for class-like behavior) for .MOV handling:
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
typedef struct {
uint8_t *data;
size_t length;
size_t pos;
// Use arrays or structs for properties (simplified with printf instead of storing all)
} MovParser;
uint32_t read_uint32(MovParser *parser) {
uint32_t val = (parser->data[parser->pos] << 24) | (parser->data[parser->pos+1] << 16) |
(parser->data[parser->pos+2] << 8) | parser->data[parser->pos+3];
parser->pos += 4;
return val;
}
uint16_t read_uint16(MovParser *parser) {
uint16_t val = (parser->data[parser->pos] << 8) | parser->data[parser->pos+1];
parser->pos += 2;
return val;
}
double read_fixed1616(MovParser *parser) {
return (double)read_uint32(parser) / 65536.0;
}
double read_fixed88(MovParser *parser) {
return (double)read_uint16(parser) / 256.0;
}
char* read_string(MovParser *parser, int length) {
char* str = malloc(length + 1);
memcpy(str, &parser->data[parser->pos], length);
str[length] = '\0';
parser->pos += length;
return str;
}
void parse_atom(MovParser *parser) {
size_t start_pos = parser->pos;
uint32_t size = read_uint32(parser);
char* type = read_string(parser, 4);
if (strcmp(type, "ftyp") == 0) {
char* major = read_string(parser, 4);
uint32_t minor = read_uint32(parser);
printf("Major Brand: %s\n", major);
printf("Minor Version: %u\n", minor);
printf("Compatible Brands: ");
while (parser->pos < start_pos + size) {
char* brand = read_string(parser, 4);
printf("%s ", brand);
free(brand);
}
printf("\n");
free(major);
} else if (strcmp(type, "mvhd") == 0) {
parser->pos += 1; // version
parser->pos += 3; // flags
printf("Movie Creation Time: %u\n", read_uint32(parser));
printf("Movie Modification Time: %u\n", read_uint32(parser));
printf("Time Scale: %u\n", read_uint32(parser));
printf("Movie Duration: %u\n", read_uint32(parser));
printf("Preferred Rate: %f\n", read_fixed1616(parser));
printf("Preferred Volume: %f\n", read_fixed88(parser));
parser->pos += 10; // reserved
parser->pos += 36; // matrix
printf("Preview Time: %u\n", read_uint32(parser));
printf("Preview Duration: %u\n", read_uint32(parser));
printf("Poster Time: %u\n", read_uint32(parser));
printf("Selection Time: %u\n", read_uint32(parser));
printf("Selection Duration: %u\n", read_uint32(parser));
printf("Current Time: %u\n", read_uint32(parser));
printf("Next Track ID: %u\n", read_uint32(parser));
} else if (strcmp(type, "tkhd") == 0) {
parser->pos += 1; // version
parser->pos += 3; // flags
printf("Track Creation Time: %u\n", read_uint32(parser));
printf("Track Modification Time: %u\n", read_uint32(parser));
printf("Track ID: %u\n", read_uint32(parser));
parser->pos += 4; // reserved
printf("Track Duration: %u\n", read_uint32(parser));
parser->pos += 8; // reserved
int16_t layer = (int16_t)read_uint16(parser);
printf("Track Layer: %d\n", layer);
int16_t alt_group = (int16_t)read_uint16(parser);
printf("Alternate Group: %d\n", alt_group);
printf("Track Volume: %f\n", read_fixed88(parser));
parser->pos += 2; // reserved
parser->pos += 36; // matrix
printf("Track Width: %f\n", read_fixed1616(parser));
printf("Track Height: %f\n", read_fixed1616(parser));
} else if (strcmp(type, "hdlr") == 0) {
parser->pos += 8; // version, flags, component type
char* htype = read_string(parser, 4);
printf("Handler Type: %s\n", htype);
free(htype);
}
free(type);
parser->pos = start_pos + size;
}
void parse(MovParser *parser) {
while (parser->pos < parser->length) {
parse_atom(parser);
}
}
MovParser* mov_parser_init(const char* filename) {
FILE *f = fopen(filename, "rb");
if (!f) return NULL;
fseek(f, 0, SEEK_END);
size_t len = ftell(f);
fseek(f, 0, SEEK_SET);
MovParser* parser = malloc(sizeof(MovParser));
parser->data = malloc(len);
fread(parser->data, 1, len, f);
fclose(f);
parser->length = len;
parser->pos = 0;
return parser;
}
void print_properties(MovParser *parser) {
parser->pos = 0; // Reset to print again
parse(parser);
}
void write_file(MovParser *parser, const char* output_filename) {
FILE *f = fopen(output_filename, "wb");
if (f) {
fwrite(parser->data, 1, parser->length, f);
fclose(f);
printf("File written to %s\n", output_filename);
}
}
void mov_parser_free(MovParser *parser) {
free(parser->data);
free(parser);
}
// Example usage:
// int main() {
// MovParser* parser = mov_parser_init("example.mov");
// if (parser) {
// print_properties(parser);
// write_file(parser, "output.mov");
// mov_parser_free(parser);
// }
// return 0;
// }