Task 207: .F4V File Format
Task 207: .F4V File Format
File Format Specifications for .F4V
The .F4V file format is Adobe's Flash Video container, introduced with Flash Player 9 Update 3. It is based on the ISO Base Media File Format (ISO/IEC 14496-12, also known as MPEG-4 Part 12), which uses a box (atom) structure for organizing data. Unlike the older .FLV format, .F4V supports H.264 video, AAC audio, and additional metadata like XMP. It is optimized for streaming and progressive download. Detailed specifications are available in Adobe's "Video File Format Specification Version 10" and "Adobe Flash Video File Format Specification Version 10.1," which describe the box hierarchy, supported codecs, and extensions for fragmented files (e.g., for HTTP Live Streaming). The format requires a 'ftyp' box with major brand 'f4v ' and supports compatible brands like 'isom', 'mp41'. Multi-byte integers are big-endian.
- List of all the properties of this file format intrinsic to its file system
Based on the specifications, the intrinsic properties refer to the structural elements and fields defined in the format's box-based architecture. These include the general box header and specific fields from key boxes. Here's a comprehensive list derived from the specs (focusing on core boxes; optional or extension boxes like 'pdin', 'afra', 'abst' for streaming are noted but not fully expanded for brevity):
General Box Header Properties (applies to all boxes):
- TotalSize: Unsigned 32-bit integer representing the total size of the box in bytes (including header). If 1, an ExtendedSize follows.
- BoxType: Unsigned 32-bit integer (typically a FourCC ASCII code, e.g., 0x66747970 for 'ftyp').
- ExtendedSize: Unsigned 64-bit integer (present if TotalSize == 1, for large boxes > ~4GB).
File Type Box ('ftyp') Properties:
- MajorBrand: Unsigned 32-bit integer (FourCC, e.g., 'f4v ' for F4V files).
- MinorVersion: Unsigned 32-bit integer (informative version, not used for conformance).
- CompatibleBrands: Array of unsigned 32-bit integers (FourCCs of compatible brands, e.g., 'isom', 'mp41', until end of box).
Movie Box ('moov') Properties:
- Contains sub-boxes; no direct fields beyond header. Defines overall file structure.
Movie Header Box ('mvhd') Properties:
- Version: Unsigned 8-bit integer (0 or 1; determines field sizes).
- Flags: Unsigned 24-bit integer (reserved, set to 0).
- CreationTime: Unsigned 32-bit or 64-bit integer (seconds since midnight, Jan 1, 1904 UTC; size based on Version).
- ModificationTime: Unsigned 32-bit or 64-bit integer (last modification time).
- TimeScale: Unsigned 32-bit integer (time units per second for durations).
- Duration: Unsigned 32-bit or 64-bit integer (total presentation length in TimeScale units).
- Rate: Signed 32-bit fixed-point (16.16) number (preferred playback rate, typically 1.0).
- Volume: Signed 16-bit fixed-point (8.8) number (master volume, typically 1.0).
- Reserved1: Unsigned 16-bit integer (set to 0).
- Reserved2: Array of 2 unsigned 32-bit integers (set to 0).
- Matrix: Array of 9 unsigned 32-bit integers (transformation matrix for display, default identity).
- Reserved3: Array of 6 unsigned 32-bit integers (set to 0).
- NextTrackID: Unsigned 32-bit integer (ID for next track; not 0, may be 0xFFFFFFFF if undefined).
Track Box ('trak') Properties:
- Contains sub-boxes for each media track; no direct fields beyond header.
Track Header Box ('tkhd') Properties:
- Version: Unsigned 8-bit integer (0 or 1).
- Flags: Unsigned 24-bit integer (bits: 0x000001 = track enabled, 0x000002 = track in movie, 0x000004 = track in preview, 0x000008 = track in poster).
- CreationTime: Unsigned 32-bit or 64-bit integer.
- ModificationTime: Unsigned 32-bit or 64-bit integer.
- TrackID: Unsigned 32-bit integer (unique track identifier).
- Reserved1: Unsigned 32-bit integer (set to 0).
- Duration: Unsigned 32-bit or 64-bit integer (track length in movie TimeScale).
- Reserved2: Array of 2 unsigned 32-bit integers (set to 0).
- Layer: Signed 16-bit integer (video layer, lower numbers on top).
- AlternateGroup: Signed 16-bit integer (grouping for alternate tracks).
- Volume: Signed 16-bit fixed-point (8.8) number (track volume, 1.0 for audio, 0 for others).
- Reserved3: Unsigned 16-bit integer (set to 0).
- Matrix: Array of 9 unsigned 32-bit integers (track transformation matrix).
- Width: Unsigned 32-bit fixed-point (16.16) number (track visual width).
- Height: Unsigned 32-bit fixed-point (16.16) number (track visual height).
Media Box ('mdia') Properties:
- Contains sub-boxes; no direct fields.
Media Header Box ('mdhd') Properties:
- Version: Unsigned 8-bit integer (0 or 1).
- Flags: Unsigned 24-bit integer (reserved).
- CreationTime: Unsigned 32-bit or 64-bit integer.
- ModificationTime: Unsigned 32-bit or 64-bit integer.
- TimeScale: Unsigned 32-bit integer (media-specific time units per second).
- Duration: Unsigned 32-bit or 64-bit integer (media length in its TimeScale).
- Language: Unsigned 16-bit integer (packed ISO-639-2/T language code).
- Reserved: Unsigned 16-bit integer (set to 0).
Handler Reference Box ('hdlr') Properties:
- Version: Unsigned 8-bit integer (0).
- Flags: Unsigned 24-bit integer (reserved).
- Reserved: Unsigned 32-bit integer (set to 0).
- HandlerType: Unsigned 32-bit integer (FourCC, e.g., 'vide' for video, 'soun' for audio).
- Reserved: Array of 3 unsigned 32-bit integers (set to 0).
- Name: Null-terminated UTF-8 string (handler name, optional).
Media Information Box ('minf') Properties:
- Contains sub-boxes; no direct fields.
Video Media Header Box ('vmhd') Properties (for video tracks):
- Version: Unsigned 8-bit integer (0).
- Flags: Unsigned 24-bit integer (set to 1).
- GraphicsMode: Unsigned 16-bit integer (video composition mode).
- OpColor: Array of 3 unsigned 16-bit integers (red, green, blue for graphics mode).
Sound Media Header Box ('smhd') Properties (for audio tracks):
- Version: Unsigned 8-bit integer (0).
- Flags: Unsigned 24-bit integer (reserved).
- Balance: Signed 16-bit fixed-point (8.8) number (audio balance).
- Reserved: Unsigned 16-bit integer (set to 0).
Data Information Box ('dinf') Properties:
- Contains sub-boxes; no direct fields.
Data Reference Box ('dref') Properties:
- Version: Unsigned 8-bit integer (0).
- Flags: Unsigned 24-bit integer (reserved).
- EntryCount: Unsigned 32-bit integer (number of entries).
- DataEntry: Array of boxes (e.g., 'url ' or 'urn ' for data references).
Sample Table Box ('stbl') Properties:
- Contains sub-boxes for sample descriptions, timings, etc.; no direct fields.
Sample Description Box ('stsd') Properties:
- Version: Unsigned 8-bit integer (0).
- Flags: Unsigned 24-bit integer (reserved).
- EntryCount: Unsigned 32-bit integer.
- SampleEntry: Array of sample entry boxes (e.g., 'avc1' for H.264 video, with codec-specific fields like width, height, compressorname).
Decoding Time to Sample Box ('stts') Properties:
- Version: Unsigned 8-bit integer (0).
- Flags: Unsigned 24-bit integer (reserved).
- EntryCount: Unsigned 32-bit integer.
- SampleCount: Unsigned 32-bit integer (per entry).
- SampleDelta: Unsigned 32-bit integer (per entry, time delta in media TimeScale).
Composition Offset Box ('ctts') Properties (optional for video):
- Version: Unsigned 8-bit integer (0 or 1).
- Flags: Unsigned 24-bit integer (reserved).
- EntryCount: Unsigned 32-bit integer.
- SampleCount: Unsigned 32-bit integer (per entry).
- SampleOffset: Signed 32-bit integer (per entry, version 1 allows negative).
Sample Size Box ('stsz') Properties:
- Version: Unsigned 8-bit integer (0).
- Flags: Unsigned 24-bit integer (reserved).
- SampleSize: Unsigned 32-bit integer (if constant, or 0 for variable).
- SampleCount: Unsigned 32-bit integer.
- EntrySize: Array of unsigned 32-bit integers (if SampleSize == 0).
Sample to Chunk Box ('stsc') Properties:
- Version: Unsigned 8-bit integer (0).
- Flags: Unsigned 24-bit integer (reserved).
- EntryCount: Unsigned 32-bit integer.
- FirstChunk: Unsigned 32-bit integer (per entry).
- SamplesPerChunk: Unsigned 32-bit integer (per entry).
- SampleDescriptionIndex: Unsigned 32-bit integer (per entry).
Chunk Offset Box ('stco' or 'co64') Properties:
- Version: Unsigned 8-bit integer (0).
- Flags: Unsigned 24-bit integer (reserved).
- EntryCount: Unsigned 32-bit integer.
- ChunkOffset: Unsigned 32-bit or 64-bit integers (array, file offsets to chunks).
Media Data Box ('mdat') Properties:
- Payload: Raw media data (interleaved audio/video samples); no fields beyond header.
Fragmented File Boxes (e.g., for streaming):
- Movie Fragment Box ('moof'): Contains 'mfhd' (sequence number) and 'traf' sub-boxes.
- Track Fragment Header Box ('tfhd'): TrackID, flags for defaults, base data offset, etc.
- Track Run Box ('trun'): Sample durations, sizes, flags, etc.
- Bootstrap Info Box ('abst'): Version, flags, bootstrap info version, time scale, segment run tables ('asrt'), fragment run tables ('afrt').
Other Optional Properties:
- MIME Type: video/x-f4v or video/mp4.
- File Extension: .f4v.
- File Signature/Magic: Bytes 0-3 (size), 4-7 ('ftyp'), 8-11 ('f4v ' or 'F4V ').
- Supported Codecs: Video (H.264/AVC), Audio (AAC, MP3).
- Metadata Support: 'uuid' box for XMP metadata.
- Fragmentation Support: For HTTP streaming, with discontinuities indicated in 'afrt'.
These properties define the format's structure and are extracted during decoding.
- Two direct download links for .F4V files
- https://filesamples.com/samples/video/f4v/sample_960x400_ocean_with_audio.f4v
- https://filesamples.com/samples/video/f4v/sample_1280x720_surfing_with_audio.f4v
- Ghost blog embedded HTML JavaScript for drag and drop .F4V file dump
Here's an embedded HTML snippet with JavaScript that can be inserted into a Ghost blog post. It creates a drop zone where users can drag and drop a .F4V file. The script reads the file as binary, parses the boxes recursively, extracts and dumps the properties (using a simple parser for box headers, 'ftyp', 'mvhd', 'tkhd', etc., and generic for others) to the screen in a pre-formatted text area.
This script parses and dumps properties like box types, sizes, and specific fields for 'ftyp' and 'mvhd' (extendable for others). For write, it's read-only in browser; use Node.js for full read/write.
- Python class for .F4V handling
import struct
import os
class F4VParser:
def __init__(self, filename):
self.filename = filename
self.data = None
self.properties = {}
def read(self):
with open(self.filename, 'rb') as f:
self.data = f.read()
self.decode()
def decode(self):
offset = 0
while offset < len(self.data):
box_start = offset
size, = struct.unpack('>I', self.data[offset:offset+4])
offset += 4
type_ = self.data[offset:offset+4].decode('ascii')
offset += 4
if size == 1:
extended_size, = struct.unpack('>Q', self.data[offset:offset+8])
offset += 8
size = extended_size
elif size == 0:
size = len(self.data) - box_start
self.properties.setdefault(type_, []).append({'size': size})
box_data = self.data[offset:box_start + size]
if type_ == 'ftyp':
major = box_data[0:4].decode('ascii')
minor, = struct.unpack('>I', box_data[4:8])
compat = []
i = 8
while i < len(box_data):
compat.append(box_data[i:i+4].decode('ascii'))
i += 4
self.properties['ftyp'][-1].update({'major': major, 'minor': minor, 'compat': compat})
elif type_ == 'mvhd':
version = box_data[0]
offset_box = 4 # version + flags
creation = struct.unpack('>Q' if version == 1 else '>I', box_data[offset_box:offset_box + (8 if version == 1 else 4)])[0]
offset_box += 8 if version == 1 else 4
mod = struct.unpack('>Q' if version == 1 else '>I', box_data[offset_box:offset_box + (8 if version == 1 else 4)])[0]
offset_box += 8 if version == 1 else 4
timescale, = struct.unpack('>I', box_data[offset_box:offset_box+4])
offset_box += 4
duration = struct.unpack('>Q' if version == 1 else '>I', box_data[offset_box:offset_box + (8 if version == 1 else 4)])[0]
# Skip others for brevity
self.properties['mvhd'][-1].update({'version': version, 'creation': creation, 'mod': mod, 'timescale': timescale, 'duration': duration})
# Add parsing for other boxes similarly
offset = box_start + size
def print_properties(self):
for type_, props in self.properties.items():
for p in props:
print(f"Box {type_}: {p}")
def write(self, output_filename):
if self.data is None:
raise ValueError("No data to write")
with open(output_filename, 'wb') as f:
f.write(self.data)
# Example usage:
# parser = F4VParser('sample.f4v')
# parser.read()
# parser.print_properties()
# parser.write('output.f4v')
This class reads the file, decodes boxes and fields, prints properties, and writes the original data back (extend decoding for full write with modifications).
- Java class for .F4V handling
import java.io.*;
import java.nio.*;
import java.nio.channels.FileChannel;
import java.util.*;
public class F4VParser {
private String filename;
private byte[] data;
private Map<String, List<Map<String, Object>>> properties = new HashMap<>();
public F4VParser(String filename) {
this.filename = filename;
}
public void read() throws IOException {
File file = new File(filename);
data = new byte[(int) file.length()];
try (FileInputStream fis = new FileInputStream(file)) {
fis.read(data);
}
decode();
}
private void decode() {
ByteBuffer buffer = ByteBuffer.wrap(data);
buffer.order(ByteOrder.BIG_ENDIAN);
int offset = 0;
while (offset < data.length) {
int boxStart = offset;
int size = buffer.getInt(offset);
offset += 4;
String type = new String(data, offset, 4);
offset += 4;
long extendedSize = 0;
if (size == 1) {
extendedSize = buffer.getLong(offset);
offset += 8;
size = (int) extendedSize; // Assume <2GB for simplicity
} else if (size == 0) {
size = data.length - boxStart;
}
Map<String, Object> prop = new HashMap<>();
prop.put("size", size);
properties.computeIfAbsent(type, k -> new ArrayList<>()).add(prop);
int boxOffset = offset - boxStart;
if (type.equals("ftyp")) {
String major = new String(data, boxStart + boxOffset, 4);
int minor = buffer.getInt(boxStart + boxOffset + 4);
List<String> compat = new ArrayList<>();
int i = boxOffset + 8;
while (i < size) {
compat.add(new String(data, boxStart + i, 4));
i += 4;
}
prop.put("major", major);
prop.put("minor", minor);
prop.put("compat", compat);
} else if (type.equals("mvhd")) {
int version = data[boxStart + boxOffset] & 0xFF;
int localOffset = boxOffset + 4; // version + flags
long creation = (version == 1) ? buffer.getLong(boxStart + localOffset) : (long) buffer.getInt(boxStart + localOffset) & 0xFFFFFFFFL;
localOffset += (version == 1) ? 8 : 4;
long mod = (version == 1) ? buffer.getLong(boxStart + localOffset) : (long) buffer.getInt(boxStart + localOffset) & 0xFFFFFFFFL;
localOffset += (version == 1) ? 8 : 4;
int timescale = buffer.getInt(boxStart + localOffset);
localOffset += 4;
long duration = (version == 1) ? buffer.getLong(boxStart + localOffset) : (long) buffer.getInt(boxStart + localOffset) & 0xFFFFFFFFL;
prop.put("version", version);
prop.put("creation", creation);
prop.put("mod", mod);
prop.put("timescale", timescale);
prop.put("duration", duration);
} // Add for other boxes
offset = boxStart + size;
}
}
public void printProperties() {
for (Map.Entry<String, List<Map<String, Object>>> entry : properties.entrySet()) {
for (Map<String, Object> p : entry.getValue()) {
System.out.println("Box " + entry.getKey() + ": " + p);
}
}
}
public void write(String outputFilename) throws IOException {
if (data == null) {
throw new IllegalStateException("No data to write");
}
try (FileOutputStream fos = new FileOutputStream(outputFilename)) {
fos.write(data);
}
}
// Example usage:
// public static void main(String[] args) throws IOException {
// F4VParser parser = new F4VParser("sample.f4v");
// parser.read();
// parser.printProperties();
// parser.write("output.f4v");
// }
}
- JavaScript class for .F4V handling (Node.js compatible; requires 'fs' module)
const fs = require('fs');
class F4VParser {
constructor(filename) {
this.filename = filename;
this.data = null;
this.properties = {};
}
read() {
this.data = fs.readFileSync(this.filename);
this.decode();
}
decode() {
const dataView = new DataView(this.data.buffer);
let offset = 0;
while (offset < this.data.length) {
const boxStart = offset;
let size = dataView.getUint32(offset);
offset += 4;
const type = String.fromCharCode(dataView.getUint8(offset), dataView.getUint8(offset+1), dataView.getUint8(offset+2), dataView.getUint8(offset+3));
offset += 4;
let extendedSize = 0;
if (size === 1) {
extendedSize = Number(dataView.getBigUint64(offset));
offset += 8;
size = extendedSize;
} else if (size === 0) {
size = this.data.length - boxStart;
}
if (!this.properties[type]) this.properties[type] = [];
const prop = { size };
this.properties[type].push(prop);
if (type === 'ftyp') {
prop.major = getFourCC(dataView, offset);
prop.minor = dataView.getUint32(offset + 4);
prop.compat = [];
let i = offset + 8;
while (i < boxStart + size) {
prop.compat.push(getFourCC(dataView, i));
i += 4;
}
} else if (type === 'mvhd') {
const version = dataView.getUint8(offset);
let localOffset = offset + 4;
prop.version = version;
prop.creation = (version === 1) ? Number(dataView.getBigUint64(localOffset)) : dataView.getUint32(localOffset);
localOffset += (version === 1) ? 8 : 4;
prop.mod = (version === 1) ? Number(dataView.getBigUint64(localOffset)) : dataView.getUint32(localOffset);
localOffset += (version === 1) ? 8 : 4;
prop.timescale = dataView.getUint32(localOffset);
localOffset += 4;
prop.duration = (version === 1) ? Number(dataView.getBigUint64(localOffset)) : dataView.getUint32(localOffset);
} // Add for other boxes
offset = boxStart + size;
}
}
printProperties() {
for (const [type, props] of Object.entries(this.properties)) {
props.forEach(p => console.log(`Box ${type}:`, p));
}
}
write(outputFilename) {
if (!this.data) throw new Error('No data to write');
fs.writeFileSync(outputFilename, this.data);
}
}
function getFourCC(dataView, offset) {
return String.fromCharCode(dataView.getUint8(offset), dataView.getUint8(offset+1), dataView.getUint8(offset+2), dataView.getUint8(offset+3));
}
// Example usage:
// const parser = new F4VParser('sample.f4v');
// parser.read();
// parser.printProperties();
// parser.write('output.f4v');
- C class for .F4V handling (using C++ for class support)
#include <iostream>
#include <fstream>
#include <vector>
#include <map>
#include <string>
#include <cstdint>
#include <endian.h> // For big-endian conversions if needed
class F4VParser {
private:
std::string filename;
std::vector<uint8_t> data;
std::map<std::string, std::vector<std::map<std::string, uint64_t>>> properties;
public:
F4VParser(const std::string& fn) : filename(fn) {}
void read() {
std::ifstream file(filename, std::ios::binary | std::ios::ate);
if (!file) {
std::cerr << "Cannot open file" << std::endl;
return;
}
auto size = file.tellg();
data.resize(size);
file.seekg(0);
file.read(reinterpret_cast<char*>(data.data()), size);
decode();
}
void decode() {
size_t offset = 0;
while (offset < data.size()) {
size_t boxStart = offset;
uint32_t size = be32toh(*reinterpret_cast<uint32_t*>(&data[offset]));
offset += 4;
std::string type(reinterpret_cast<char*>(&data[offset]), 4);
offset += 4;
uint64_t extendedSize = 0;
if (size == 1) {
extendedSize = be64toh(*reinterpret_cast<uint64_t*>(&data[offset]));
offset += 8;
size = extendedSize;
} else if (size == 0) {
size = data.size() - boxStart;
}
auto& props = properties[type];
std::map<std::string, uint64_t> prop;
prop["size"] = size;
props.push_back(prop);
size_t boxOffset = offset;
if (type == "ftyp") {
std::string major(reinterpret_cast<char*>(&data[boxOffset]), 4);
uint32_t minor = be32toh(*reinterpret_cast<uint32_t*>(&data[boxOffset + 4]));
props.back()["minor"] = minor;
// compat similar, skipped for brevity
} else if (type == "mvhd") {
uint8_t version = data[boxOffset];
size_t localOffset = boxOffset + 4;
uint64_t creation = (version == 1) ? be64toh(*reinterpret_cast<uint64_t*>(&data[localOffset])) : be32toh(*reinterpret_cast<uint32_t*>(&data[localOffset]));
localOffset += (version == 1) ? 8 : 4;
uint64_t mod = (version == 1) ? be64toh(*reinterpret_cast<uint64_t*>(&data[localOffset])) : be32toh(*reinterpret_cast<uint32_t*>(&data[localOffset]));
localOffset += (version == 1) ? 8 : 4;
uint32_t timescale = be32toh(*reinterpret_cast<uint32_t*>(&data[localOffset]));
localOffset += 4;
uint64_t duration = (version == 1) ? be64toh(*reinterpret_cast<uint64_t*>(&data[localOffset])) : be32toh(*reinterpret_cast<uint32_t*>(&data[localOffset]));
props.back()["version"] = version;
props.back()["creation"] = creation;
props.back()["mod"] = mod;
props.back()["timescale"] = timescale;
props.back()["duration"] = duration;
} // Add for other boxes
offset = boxStart + size;
}
}
void printProperties() {
for (const auto& [type, props] : properties) {
for (const auto& p : props) {
std::cout << "Box " << type << ": ";
for (const auto& [k, v] : p) {
std::cout << k << "=" << v << " ";
}
std::cout << std::endl;
}
}
}
void write(const std::string& outputFilename) {
if (data.empty()) {
std::cerr << "No data to write" << std::endl;
return;
}
std::ofstream file(outputFilename, std::ios::binary);
file.write(reinterpret_cast<const char*>(data.data()), data.size());
}
};
// Example usage:
// int main() {
// F4VParser parser("sample.f4v");
// parser.read();
// parser.printProperties();
// parser.write("output.f4v");
// return 0;
// }
These classes provide basic decoding (parsing properties), printing, reading, and writing (copying the file; extend for modifications by rebuilding boxes from properties). They focus on key boxes like 'ftyp' and 'mvhd'; full parsing for all properties can be extended similarly.