Task 004: .3GP File Format

Task 004: .3GP File Format

1. Properties of the .3GP File Format Intrinsic to Its File System

The .3GP file format is a multimedia container format defined by the Third Generation Partnership Project (3GPP) for 3G UMTS multimedia services, based on the ISO/IEC 14496-12 (MPEG-4 Part 12) base media file format. Below is a list of intrinsic properties of the .3GP file format, focusing on its file structure and characteristics relevant to its file system:

  • File Extension: .3gp or .3gpp
  • MIME Type: video/3gpp, audio/3gpp (if no video)
  • Container Format: Based on ISO/IEC 14496-12 (MPEG-4 Part 12), a simplified version of MP4 optimized for mobile devices
  • Byte Order: Big-endian (most significant bytes first)
  • File Structure: Composed of consecutive chunks (boxes), each with an 8-byte header (4-byte size, 4-byte type)
  • Key Boxes:
  • ftyp (File Type Box): Identifies the file type and compatibility, with subtypes like 3gp4, 3gp5, 3gp6, etc.
  • moov (Movie Box): Contains metadata about the media, such as tracks and their properties
  • mdat (Media Data Box): Stores the actual audio, video, or text data
  • Other possible boxes: udta (user data), free, skip, etc.
  • Video Codecs: Supports MPEG-4 Part 2, H.263, or H.264 (AVC)
  • Audio Codecs: Supports AMR-NB, AMR-WB, AMR-WB+, AAC-LC, HE-AAC v1, or Enhanced aacPlus (HE-AAC v2)
  • Text Support: Supports 3GPP Timed Text for subtitles
  • Tracks: Can contain multiple tracks (video, audio, text/subtitles)
  • Metadata: Supports metadata like author, title, and description
  • Resolution: Typically up to 320x240 for mobile devices, though higher resolutions are possible
  • Aspect Ratio: Commonly 16:9 or 4:3
  • File Size: Optimized for small file sizes to reduce storage and bandwidth requirements
  • Streaming Support: Designed for streaming over low-bandwidth networks
  • Compatibility: High compatibility with 3G mobile devices, with limited support on modern devices without additional software

These properties are derived from the 3GPP technical specifications and related sources, ensuring a focus on the file format's intrinsic characteristics.

2. Python Class for .3GP File Handling

Below is a Python class that uses the isobmff library (a Python parser for ISO base media file formats) to open, read, write, and print .3GP file properties. Since isobmff is not a standard library, you may need to install it (pip install isobmff) or use an alternative like pymp4 for parsing.

import isobmff
import os

class ThreeGPFile:
    def __init__(self, file_path):
        self.file_path = file_path
        self.file = None
        self.boxes = None

    def open_file(self):
        """Open and parse the .3GP file."""
        try:
            self.file = isobmff.File(self.file_path)
            self.boxes = self.file.boxes
            return True
        except Exception as e:
            print(f"Error opening file: {e}")
            return False

    def read_properties(self):
        """Read and return .3GP file properties."""
        if not self.boxes:
            print("File not opened or invalid.")
            return None

        properties = {
            "file_extension": os.path.splitext(self.file_path)[1].lower(),
            "mime_type": "video/3gpp" if self.has_video() else "audio/3gpp",
            "byte_order": "big-endian",
            "file_type_box": self.get_ftyp_info(),
            "video_codec": self.get_video_codec(),
            "audio_codec": self.get_audio_codec(),
            "resolution": self.get_resolution(),
            "aspect_ratio": self.get_aspect_ratio(),
            "file_size": os.path.getsize(self.file_path),
            "tracks": self.get_tracks(),
            "metadata": self.get_metadata(),
            "streaming_support": "Supported",
            "compatibility": "High with 3G devices",
        }
        return properties

    def has_video(self):
        """Check if the file contains video tracks."""
        for box in self.boxes:
            if box.type == "moov":
                for track in box.traks:
                    if track.mdia.hdlr.handler_type == "vide":
                        return True
        return False

    def get_ftyp_info(self):
        """Get File Type Box information."""
        for box in self.boxes:
            if box.type == "ftyp":
                return f"Major Brand: {box.major_brand}, Compatible Brands: {box.compatible_brands}"
        return "Unknown"

    def get_video_codec(self):
        """Get video codec information."""
        for box in self.boxes:
            if box.type == "moov":
                for track in box.traks:
                    if track.mdia.hdlr.handler_type == "vide":
                        return track.mdia.minf.stbl.stsd.entries[0].type
        return "None"

    def get_audio_codec(self):
        """Get audio codec information."""
        for box in self.boxes:
            if box.type == "moov":
                for track in box.traks:
                    if track.mdia.hdlr.handler_type == "soun":
                        return track.mdia.minf.stbl.stsd.entries[0].type
        return "None"

    def get_resolution(self):
        """Get video resolution."""
        for box in self.boxes:
            if box.type == "moov":
                for track in box.traks:
                    if track.mdia.hdlr.handler_type == "vide":
                        return f"{track.tkhd.width}x{track.tkhd.height}"
        return "Unknown"

    def get_aspect_ratio(self):
        """Get aspect ratio (simplified)."""
        resolution = self.get_resolution()
        if resolution != "Unknown":
            width, height = map(int, resolution.split("x"))
            return "16:9" if width / height == 16 / 9 else "4:3"
        return "Unknown"

    def get_tracks(self):
        """Get track information."""
        tracks = []
        for box in self.boxes:
            if box.type == "moov":
                for track in box.traks:
                    tracks.append(track.mdia.hdlr.handler_type)
        return tracks

    def get_metadata(self):
        """Get metadata from udta box."""
        for box in self.boxes:
            if box.type == "moov":
                for subbox in box.boxes:
                    if subbox.type == "udta":
                        return {item.name: item.value for item in subbox.items}
        return {}

    def print_properties(self):
        """Print all properties to console."""
        properties = self.read_properties()
        if properties:
            for key, value in properties.items():
                print(f"{key}: {value}")

    def write_file(self, output_path):
        """Write the parsed file to a new .3GP file."""
        try:
            self.file.write(output_path)
            print(f"File written to {output_path}")
        except Exception as e:
            print(f"Error writing file: {e}")

# Example usage
if __name__ == "__main__":
    file_path = "example.3gp"
    three_gp = ThreeGPFile(file_path)
    if three_gp.open_file():
        three_gp.print_properties()
        three_gp.write_file("output.3gp")

Notes:

  • Requires the isobmff library for parsing ISO base media files. Install via pip install isobmff.
  • The class assumes the input file is a valid .3GP file. Error handling is included for invalid files.
  • Writing a .3GP file preserves the original structure without modification, as creating new .3GP files from scratch requires additional codec encoding, which is complex and library-dependent.

3. Java Class for .3GP File Handling

Below is a Java class using the mp4parser library (com.googlecode.mp4parser) to handle .3GP files. You need to include the library in your project (e.g., via Maven).

import com.coremedia.iso.IsoFile;
import com.coremedia.iso.boxes.*;
import java.io.File;
import java.io.FileOutputStream;
import java.nio.channels.FileChannel;
import java.util.HashMap;
import java.util.Map;

public class ThreeGPFile {
    private String filePath;
    private IsoFile isoFile;

    public ThreeGPFile(String filePath) {
        this.filePath = filePath;
    }

    public boolean openFile() {
        try {
            isoFile = new IsoFile(filePath);
            return true;
        } catch (Exception e) {
            System.out.println("Error opening file: " + e.getMessage());
            return false;
        }
    }

    public Map<String, Object> readProperties() {
        if (isoFile == null) {
            System.out.println("File not opened or invalid.");
            return null;
        }

        Map<String, Object> properties = new HashMap<>();
        properties.put("file_extension", filePath.substring(filePath.lastIndexOf(".")).toLowerCase());
        properties.put("mime_type", hasVideo() ? "video/3gpp" : "audio/3gpp");
        properties.put("byte_order", "big-endian");
        properties.put("file_type_box", getFtypInfo());
        properties.put("video_codec", getVideoCodec());
        properties.put("audio_codec", getAudioCodec());
        properties.put("resolution", getResolution());
        properties.put("aspect_ratio", getAspectRatio());
        properties.put("file_size", new File(filePath).length());
        properties.put("tracks", getTracks());
        properties.put("metadata", getMetadata());
        properties.put("streaming_support", "Supported");
        properties.put("compatibility", "High with 3G devices");

        return properties;
    }

    private boolean hasVideo() {
        for (Box box : isoFile.getBoxes()) {
            if (box instanceof MovieBox) {
                MovieBox moov = (MovieBox) box;
                for (TrackBox track : moov.getBoxes(TrackBox.class)) {
                    if (track.getMediaBox().getHandlerBox().getHandlerType().equals("vide")) {
                        return true;
                    }
                }
            }
        }
        return false;
    }

    private String getFtypInfo() {
        for (Box box : isoFile.getBoxes()) {
            if (box instanceof FileTypeBox) {
                FileTypeBox ftyp = (FileTypeBox) box;
                return "Major Brand: " + ftyp.getMajorBrand() + ", Compatible Brands: " + ftyp.getCompatibleBrands();
            }
        }
        return "Unknown";
    }

    private String getVideoCodec() {
        for (Box box : isoFile.getBoxes()) {
            if (box instanceof MovieBox) {
                MovieBox moov = (MovieBox) box;
                for (TrackBox track : moov.getBoxes(TrackBox.class)) {
                    if (track.getMediaBox().getHandlerBox().getHandlerType().equals("vide")) {
                        return track.getSampleTableBox().getSampleDescriptionBox().getBox().getType();
                    }
                }
            }
        }
        return "None";
    }

    private String getAudioCodec() {
        for (Box box : isoFile.getBoxes()) {
            if (box instanceof MovieBox) {
                MovieBox moov = (MovieBox) box;
                for (TrackBox track : moov.getBoxes(TrackBox.class)) {
                    if (track.getMediaBox().getHandlerBox().getHandlerType().equals("soun")) {
                        return track.getSampleTableBox().getSampleDescriptionBox().getBox().getType();
                    }
                }
            }
        }
        return "None";
    }

    private String getResolution() {
        for (Box box : isoFile.getBoxes()) {
            if (box instanceof MovieBox) {
                MovieBox moov = (MovieBox) box;
                for (TrackBox track : moov.getBoxes(TrackBox.class)) {
                    if (track.getMediaBox().getHandlerBox().getHandlerType().equals("vide")) {
                        TrackHeaderBox tkhd = track.getTrackHeaderBox();
                        return (int) tkhd.getWidth() + "x" + (int) tkhd.getHeight();
                    }
                }
            }
        }
        return "Unknown";
    }

    private String getAspectRatio() {
        String resolution = getResolution();
        if (!resolution.equals("Unknown")) {
            String[] dims = resolution.split("x");
            double width = Double.parseDouble(dims[0]);
            double height = Double.parseDouble(dims[1]);
            return (width / height == 16.0 / 9.0) ? "16:9" : "4:3";
        }
        return "Unknown";
    }

    private String[] getTracks() {
        String[] tracks = new String[0];
        for (Box box : isoFile.getBoxes()) {
            if (box instanceof MovieBox) {
                MovieBox moov = (MovieBox) box;
                tracks = moov.getBoxes(TrackBox.class).stream()
                        .map(track -> track.getMediaBox().getHandlerBox().getHandlerType())
                        .toArray(String[]::new);
            }
        }
        return tracks;
    }

    private Map<String, String> getMetadata() {
        Map<String, String> metadata = new HashMap<>();
        for (Box box : isoFile.getBoxes()) {
            if (box instanceof MovieBox) {
                MovieBox moov = (MovieBox) box;
                for (Box subBox : moov.getBoxes()) {
                    if (subBox instanceof UserDataBox) {
                        // Simplified metadata extraction
                        metadata.put("example", "Sample metadata");
                    }
                }
            }
        }
        return metadata;
    }

    public void printProperties() {
        Map<String, Object> properties = readProperties();
        if (properties != null) {
            for (Map.Entry<String, Object> entry : properties.entrySet()) {
                System.out.println(entry.getKey() + ": " + entry.getValue());
            }
        }
    }

    public void writeFile(String outputPath) {
        try (FileOutputStream fos = new FileOutputStream(outputPath)) {
            isoFile.getBox(fos.getChannel());
            System.out.println("File written to " + outputPath);
        } catch (Exception e) {
            System.out.println("Error writing file: " + e.getMessage());
        }
    }

    public static void main(String[] args) {
        ThreeGPFile threeGP = new ThreeGPFile("example.3gp");
        if (threeGP.openFile()) {
            threeGP.printProperties();
            threeGP.writeFile("output.3gp");
        }
    }
}

Notes:

  • Requires the mp4parser library (Maven dependency: com.googlecode.mp4parser:isoparser).
  • The class handles reading and writing .3GP files, with simplified metadata extraction.
  • Writing preserves the original file structure.

4. JavaScript Class for .3GP File Handling

Below is a JavaScript class using the mp4box.js library to handle .3GP files in a browser or Node.js environment. You need to include mp4box.js (available via npm or CDN).

const MP4Box = require('mp4box');

class ThreeGPFile {
    constructor(filePath) {
        this.filePath = filePath;
        this.mp4File = null;
    }

    async openFile() {
        try {
            const response = await fetch(this.filePath);
            const arrayBuffer = await response.arrayBuffer();
            this.mp4File = MP4Box.createFile();
            this.mp4File.appendBuffer(arrayBuffer);
            return true;
        } catch (error) {
            console.error(`Error opening file: ${error}`);
            return false;
        }
    }

    readProperties() {
        if (!this.mp4File) {
            console.log("File not opened or invalid.");
            return null;
        }

        const properties = {
            file_extension: this.filePath.split('.').pop().toLowerCase(),
            mime_type: this.hasVideo() ? 'video/3gpp' : 'audio/3gpp',
            byte_order: 'big-endian',
            file_type_box: this.getFtypInfo(),
            video_codec: this.getVideoCodec(),
            audio_codec: this.getAudioCodec(),
            resolution: this.getResolution(),
            aspect_ratio: this.getAspectRatio(),
            file_size: this.mp4File.buffer.byteLength,
            tracks: this.getTracks(),
            metadata: this.getMetadata(),
            streaming_support: 'Supported',
            compatibility: 'High with 3G devices',
        };
        return properties;
    }

    hasVideo() {
        const tracks = this.mp4File.moov?.traks || [];
        return tracks.some(track => track.mdia.hdlr.handler_type === 'vide');
    }

    getFtypInfo() {
        const ftyp = this.mp4File.ftyp;
        if (ftyp) {
            return `Major Brand: ${ftyp.major_brand}, Compatible Brands: ${ftyp.compatible_brands.join(', ')}`;
        }
        return 'Unknown';
    }

    getVideoCodec() {
        const tracks = this.mp4File.moov?.traks || [];
        for (const track of tracks) {
            if (track.mdia.hdlr.handler_type === 'vide') {
                return track.mdia.minf.stbl.stsd.entries[0].type;
            }
        }
        return 'None';
    }

    getAudioCodec() {
        const tracks = this.mp4File.moov?.traks || [];
        for (const track of tracks) {
            if (track.mdia.hdlr.handler_type === 'soun') {
                return track.mdia.minf.stbl.stsd.entries[0].type;
            }
        }
        return 'None';
    }

    getResolution() {
        const tracks = this.mp4File.moov?.traks || [];
        for (const track of tracks) {
            if (track.mdia.hdlr.handler_type === 'vide') {
                return `${track.tkhd.width}x${track.tkhd.height}`;
            }
        }
        return 'Unknown';
    }

    getAspectRatio() {
        const resolution = this.getResolution();
        if (resolution !== 'Unknown') {
            const [width, height] = resolution.split('x').map(Number);
            return width / height === 16 / 9 ? '16:9' : '4:3';
        }
        return 'Unknown';
    }

    getTracks() {
        const tracks = this.mp4File.moov?.traks || [];
        return tracks.map(track => track.mdia.hdlr.handler_type);
    }

    getMetadata() {
        const udta = this.mp4File.moov?.udta;
        return udta ? { example: 'Sample metadata' } : {};
    }

    printProperties() {
        const properties = this.readProperties();
        if (properties) {
            for (const [key, value] of Object.entries(properties)) {
                console.log(`${key}: ${value}`);
            }
        }
    }

    async writeFile(outputPath) {
        try {
            const buffer = this.mp4File.buffer;
            const blob = new Blob([buffer], { type: 'video/3gpp' });
            // In Node.js, use fs to write file
            const fs = require('fs').promises;
            await fs.writeFile(outputPath, Buffer.from(await blob.arrayBuffer()));
            console.log(`File written to ${outputPath}`);
        } catch (error) {
            console.error(`Error writing file: ${error}`);
        }
    }
}

// Example usage (Node.js)
(async () => {
    const threeGP = new ThreeGPFile('example.3gp');
    if (await threeGP.openFile()) {
        threeGP.printProperties();
        await threeGP.writeFile('output.3gp');
    }
})();

Notes:

  • Requires mp4box.js (install via npm install mp4box or use a CDN).
  • Designed for Node.js; for browser use, modify the file reading/writing to use File API.
  • Metadata extraction is simplified due to library limitations.

5. C Class for .3GP File Handling

Below is a C implementation using a basic approach to parse the ISO base media file structure. C lacks high-level libraries for .3GP parsing, so this is a low-level implementation.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>

typedef struct {
    char* file_path;
    FILE* file;
    uint32_t file_size;
    char* ftyp_major_brand;
    char** compatible_brands;
    int compatible_brands_count;
    char* video_codec;
    char* audio_codec;
    char* resolution;
    char* aspect_ratio;
    char** tracks;
    int track_count;
} ThreeGPFile;

ThreeGPFile* create_threegp_file(const char* file_path) {
    ThreeGPFile* tgp = (ThreeGPFile*)malloc(sizeof(ThreeGPFile));
    tgp->file_path = strdup(file_path);
    tgp->file = NULL;
    tgp->file_size = 0;
    tgp->ftyp_major_brand = NULL;
    tgp->compatible_brands = NULL;
    tgp->compatible_brands_count = 0;
    tgp->video_codec = NULL;
    tgp->audio_codec = NULL;
    tgp->resolution = NULL;
    tgp->aspect_ratio = NULL;
    tgp->tracks = NULL;
    tgp->track_count = 0;
    return tgp;
}

int open_file(ThreeGPFile* tgp) {
    tgp->file = fopen(tgp->file_path, "rb");
    if (!tgp->file) {
        printf("Error opening file: %s\n", tgp->file_path);
        return 0;
    }
    fseek(tgp->file, 0, SEEK_END);
    tgp->file_size = ftell(tgp->file);
    fseek(tgp->file, 0, SEEK_SET);
    return 1;
}

void read_properties(ThreeGPFile* tgp) {
    if (!tgp->file) {
        printf("File not opened or invalid.\n");
        return;
    }

    // Simplified parsing of ftyp box
    uint32_t size;
    char type[5] = {0};
    fread(&size, sizeof(uint32_t), 1, tgp->file);
    fread(type, sizeof(char), 4, tgp->file);
    size = __builtin_bswap32(size); // Convert big-endian to host

    if (strcmp(type, "ftyp") == 0) {
        tgp->ftyp_major_brand = malloc(5);
        fread(tgp->ftyp_major_brand, sizeof(char), 4, tgp->file);
        tgp->ftyp_major_brand[4] = '\0';
        // Skip minor version and read compatible brands (simplified)
        fseek(tgp->file, 4, SEEK_CUR);
        tgp->compatible_brands = malloc(4 * sizeof(char*));
        tgp->compatible_brands[0] = strdup("3gp4");
        tgp->compatible_brands_count = 1;
    }

    // Placeholder for other properties (real parsing requires full ISO box parsing)
    tgp->video_codec = strdup("H.264");
    tgp->audio_codec = strdup("AMR-NB");
    tgp->resolution = strdup("320x240");
    tgp->aspect_ratio = strdup("4:3");
    tgp->tracks = malloc(2 * sizeof(char*));
    tgp->tracks[0] = strdup("vide");
    tgp->tracks[1] = strdup("soun");
    tgp->track_count = 2;
}

void print_properties(ThreeGPFile* tgp) {
    printf("file_extension: %s\n", strrchr(tgp->file_path, '.'));
    printf("mime_type: %s\n", tgp->video_codec ? "video/3gpp" : "audio/3gpp");
    printf("byte_order: big-endian\n");
    printf("file_type_box: Major Brand: %s, Compatible Brands: %s\n",
           tgp->ftyp_major_brand ? tgp->ftyp_major_brand : "Unknown",
           tgp->compatible_brands ? tgp->compatible_brands[0] : "Unknown");
    printf("video_codec: %s\n", tgp->video_codec ? tgp->video_codec : "None");
    printf("audio_codec: %s\n", tgp->audio_codec ? tgp->audio_codec : "None");
    printf("resolution: %s\n", tgp->resolution ? tgp->resolution : "Unknown");
    printf("aspect_ratio: %s\n", tgp->aspect_ratio ? tgp->aspect_ratio : "Unknown");
    printf("file_size: %u\n", tgp->file_size);
    printf("tracks: ");
    for (int i = 0; i < tgp->track_count; i++) {
        printf("%s ", tgp->tracks[i]);
    }
    printf("\n");
    printf("metadata: Sample metadata\n");
    printf("streaming_support: Supported\n");
    printf("compatibility: High with 3G devices\n");
}

void write_file(ThreeGPFile* tgp, const char* output_path) {
    FILE* out_file = fopen(output_path, "wb");
    if (!out_file) {
        printf("Error opening output file: %s\n", output_path);
        return;
    }
    fseek(tgp->file, 0, SEEK_SET);
    char buffer[1024];
    size_t bytes_read;
    while ((bytes_read = fread(buffer, 1, 1024, tgp->file)) > 0) {
        fwrite(buffer, 1, bytes_read, out_file);
    }
    fclose(out_file);
    printf("File written to %s\n", output_path);
}

void free_threegp_file(ThreeGPFile* tgp) {
    if (tgp->file) fclose(tgp->file);
    if (tgp->ftyp_major_brand) free(tgp->ftyp_major_brand);
    for (int i = 0; i < tgp->compatible_brands_count; i++) {
        free(tgp->compatible_brands[i]);
    }
    if (tgp->compatible_brands) free(tgp->compatible_brands);
    if (tgp->video_codec) free(tgp->video_codec);
    if (tgp->audio_codec) free(tgp->audio_codec);
    if (tgp->resolution) free(tgp->resolution);
    if (tgp->aspect_ratio) free(tgp->aspect_ratio);
    for (int i = 0; i < tgp->track_count; i++) {
        free(tgp->tracks[i]);
    }
    if (tgp->tracks) free(tgp->tracks);
    free(tgp->file_path);
    free(tgp);
}

int main() {
    ThreeGPFile* tgp = create_threegp_file("example.3gp");
    if (open_file(tgp)) {
        read_properties(tgp);
        print_properties(tgp);
        write_file(tgp, "output.3gp");
    }
    free_threegp_file(tgp);
    return 0;
}

Notes:

  • C implementation is basic due to the lack of high-level libraries for .3GP parsing.
  • Only the ftyp box is parsed; other properties are placeholders for simplicity.
  • Writing copies the original file without modification.
  • Full parsing requires a custom ISO box parser, which is complex and beyond this scope.

General Notes

  • Libraries: Python and Java use existing libraries (isobmff, mp4parser) for parsing, while JavaScript uses mp4box.js. C requires manual parsing, which is simplified here.
  • Writing: All implementations copy the original file structure to a new file, as creating new .3GP files from scratch requires encoding audio/video streams, which is complex and library-dependent.
  • Error Handling: Each class includes basic error handling for file operations.
  • Metadata: Metadata extraction is simplified due to varying library support.
  • Dependencies: Ensure the required libraries are installed for Python, Java, and JavaScript implementations.
  • Sources: Information is based on 3GPP specifications and web resources.

This implementation provides a robust starting point for handling .3GP files across multiple languages, with room for enhancement depending on specific use cases.

1. List of Properties Intrinsic to the .3GP File Format

The .3GP file format is an instance of the ISO Base Media File Format (ISO/IEC 14496-12), with specific constraints for 3GPP services. It uses a hierarchical box (atom) structure, where each box has a size (4 bytes), type (4 bytes), and content. Properties are the fields within these boxes. Below is a comprehensive list of key properties, grouped by box, including mandatory and optional ones based on the 3GPP TS 26.244 specification. These are intrinsic to the file format's structure and metadata (e.g., headers, timing, media descriptors), not the media data itself.

File Type Box ('ftyp') (Mandatory):

  • Major Brand (4 bytes, e.g., '3gp6' or '3gg9')
  • Minor Version (4 bytes)
  • Compatible Brands (array of 4-byte strings, e.g., 'isom', '3gp4')

Movie Box ('moov') (Mandatory):

  • Contains sub-boxes for overall presentation metadata.

Movie Header Box ('mvhd') (Mandatory within 'moov'):

  • Version (1 byte)
  • Flags (3 bytes)
  • Creation Time (4 or 8 bytes, depending on version)
  • Modification Time (4 or 8 bytes)
  • Timescale (4 bytes)
  • Duration (4 or 8 bytes)
  • Rate (4 bytes)
  • Volume (2 bytes)
  • Reserved (2 bytes)
  • Matrix Structure (9 x 4 bytes)
  • Pre-defined (6 x 4 bytes)
  • Next Track ID (4 bytes)

Track Box ('trak') (Mandatory within 'moov', one per track):

  • Contains sub-boxes for track-specific metadata.

Track Header Box ('tkhd') (Mandatory within 'trak'):

  • Version (1 byte)
  • Flags (3 bytes)
  • Creation Time (4 or 8 bytes)
  • Modification Time (4 or 8 bytes)
  • Track ID (4 bytes)
  • Reserved (4 bytes)
  • Duration (4 or 8 bytes)
  • Reserved (8 bytes)
  • Layer (2 bytes)
  • Alternate Group (2 bytes)
  • Volume (2 bytes)
  • Reserved (2 bytes)
  • Matrix Structure (9 x 4 bytes)
  • Width (4 bytes, fixed-point 16.16)
  • Height (4 bytes, fixed-point 16.16)

Media Box ('mdia') (Mandatory within 'trak'):

  • Contains sub-boxes for media details.

Media Header Box ('mdhd') (Mandatory within 'mdia'):

  • Version (1 byte)
  • Flags (3 bytes)
  • Creation Time (4 or 8 bytes)
  • Modification Time (4 or 8 bytes)
  • Timescale (4 bytes)
  • Duration (4 or 8 bytes)
  • Language (2 bytes, packed ISO-639-2/T)
  • Pre-defined (2 bytes)

Handler Box ('hdlr') (Mandatory within 'mdia'):

  • Version (1 byte)
  • Flags (3 bytes)
  • Pre-defined (4 bytes)
  • Handler Type (4 bytes, e.g., 'vide', 'soun', 'text')
  • Reserved (12 bytes)
  • Name (variable-length string)

Media Information Box ('minf') (Mandatory within 'mdia'):

  • Contains sub-boxes like Video Media Header ('vmhd') or Sound Media Header ('smhd').

Video Media Header Box ('vmhd') (Optional within 'minf' for video):

  • Version (1 byte)
  • Flags (3 bytes)
  • Graphics Mode (2 bytes)
  • Opcolor (3 x 2 bytes)

Sound Media Header Box ('smhd') (Optional within 'minf' for audio):

  • Version (1 byte)
  • Flags (3 bytes)
  • Balance (2 bytes)
  • Reserved (2 bytes)

Sample Table Box ('stbl') (Mandatory within 'minf'):

  • Contains sub-boxes for sample metadata.

Sample Description Box ('stsd') (Mandatory within 'stbl'):

  • Version (1 byte)
  • Flags (3 bytes)
  • Entry Count (4 bytes)
  • Sample Entries (variable, codec-specific, e.g., for 'mp4v': width, height, compressorname; for 'amr ': mode set, vendor)

Time to Sample Box ('stts') (Mandatory within 'stbl'):

  • Version (1 byte)
  • Flags (3 bytes)
  • Entry Count (4 bytes)
  • Sample Count (4 bytes per entry)
  • Sample Delta (4 bytes per entry)

Sample to Chunk Box ('stsc') (Mandatory within 'stbl'):

  • Version (1 byte)
  • Flags (3 bytes)
  • Entry Count (4 bytes)
  • First Chunk (4 bytes per entry)
  • Samples Per Chunk (4 bytes per entry)
  • Sample Description Index (4 bytes per entry)

Sample Size Box ('stsz') (Mandatory within 'stbl'):

  • Version (1 byte)
  • Flags (3 bytes)
  • Sample Size (4 bytes)
  • Sample Count (4 bytes)
  • Entry Size (4 bytes per sample if Sample Size == 0)

Chunk Offset Box ('stco') (Mandatory within 'stbl', or 'co64' for 64-bit):

  • Version (1 byte)
  • Flags (3 bytes)
  • Entry Count (4 bytes)
  • Chunk Offset (4 or 8 bytes per entry)

User Data Box ('udta') (Optional, within 'moov' or 'trak'):

  • Contains sub-boxes for metadata.

Title Box ('titl') (Optional within 'udta'):

  • Language (3 bytes)
  • Title (variable string)

Description Box ('dscp') (Optional within 'udta'):

  • Language (3 bytes)
  • Description (variable string)

Copyright Box ('cprt') (Optional within 'udta'):

  • Language (3 bytes)
  • Notice (variable string)

Other Metadata Boxes (Optional within 'udta', e.g., 'perf' for performer, 'auth' for author, 'gnre' for genre, etc.):

  • Similar structure: Language + Value

Track Selection Box ('tsel') (Optional within 'udta'):

  • Version (1 byte)
  • Flags (3 bytes)
  • Switch Group (4 bytes)
  • Attribute List Count (variable)
  • Attributes (e.g., 'lang', 'bwas' for bandwidth, 'cdec' for codec)

Meta Box ('meta') (Optional for extended presentations):

  • Version (1 byte)
  • Flags (3 bytes)
  • Sub-boxes like Handler Box ('hdlr' with '3gsd'), Item Location Box ('iloc')

Movie Fragment Box ('moof') (Optional for fragments in adaptive streaming):

  • Sub-boxes like Movie Fragment Header ('mfhd'), Track Fragment ('traf')

Media Data Box ('mdat') (Mandatory):

  • No properties beyond size and type; contains raw media data referenced by offsets.

These properties include support for specific 3GP requirements like brands ('3gp*', '3gg*'), codecs (H.263, AMR, H.264, etc.), and profiles (Basic, Streaming, etc.).

2. Python Class for .3GP Files

import struct
import os

class ThreeGPParser:
    def __init__(self, filename):
        self.filename = filename
        self.properties = {}
        self.boxes = []
        self._parse()

    def _read_box(self, f, offset):
        f.seek(offset)
        size = struct.unpack('>I', f.read(4))[0]
        box_type = f.read(4).decode('utf-8')
        data_offset = offset + 8
        if size == 1:
            size = struct.unpack('>Q', f.read(8))[0]
            data_offset += 8
        return size, box_type, data_offset

    def _parse_box(self, f, offset, end):
        while offset < end:
            size, box_type, data_offset = self._read_box(f, offset)
            if box_type == 'ftyp':
                self.properties['ftyp'] = self._parse_ftyp(f, data_offset, offset + size)
            elif box_type == 'moov':
                self.properties['moov'] = {}
                self._parse_moov(f, data_offset, offset + size)
            elif box_type == 'mdat':
                self.properties['mdat'] = {'offset': data_offset, 'size': size - (data_offset - offset)}
            # Add more box parsers as needed for completeness
            self.boxes.append((box_type, offset, size))
            offset += size

    def _parse_ftyp(self, f, start, end):
        f.seek(start)
        major_brand = f.read(4).decode('utf-8')
        minor_version = struct.unpack('>I', f.read(4))[0]
        compatible_brands = []
        while f.tell() < end:
            compatible_brands.append(f.read(4).decode('utf-8'))
        return {'major_brand': major_brand, 'minor_version': minor_version, 'compatible_brands': compatible_brands}

    def _parse_mvhd(self, f, start, end):
        f.seek(start)
        version = struct.unpack('>B', f.read(1))[0]
        f.read(3)  # flags
        if version == 1:
            creation_time = struct.unpack('>Q', f.read(8))[0]
            modification_time = struct.unpack('>Q', f.read(8))[0]
            timescale = struct.unpack('>I', f.read(4))[0]
            duration = struct.unpack('>Q', f.read(8))[0]
        else:
            creation_time = struct.unpack('>I', f.read(4))[0]
            modification_time = struct.unpack('>I', f.read(4))[0]
            timescale = struct.unpack('>I', f.read(4))[0]
            duration = struct.unpack('>I', f.read(4))[0]
        rate = struct.unpack('>i', f.read(4))[0]
        volume = struct.unpack('>h', f.read(2))[0]
        f.read(10)  # reserved + matrix part
        # Parse full matrix and pre-defined if needed
        next_track_id = struct.unpack('>I', f.read(4))[0]
        return {'version': version, 'creation_time': creation_time, 'modification_time': modification_time, 'timescale': timescale, 'duration': duration, 'rate': rate, 'volume': volume, 'next_track_id': next_track_id}

    def _parse_moov(self, f, start, end):
        offset = start
        while offset < end:
            size, box_type, data_offset = self._read_box(f, offset)
            if box_type == 'mvhd':
                self.properties['moov']['mvhd'] = self._parse_mvhd(f, data_offset, offset + size)
            elif box_type == 'trak':
                trak = {}
                self._parse_trak(f, data_offset, offset + size, trak)
                if 'trak' not in self.properties['moov']:
                    self.properties['moov']['trak'] = []
                self.properties['moov']['trak'].append(trak)
            elif box_type == 'udta':
                udta = {}
                self._parse_udta(f, data_offset, offset + size, udta)
                self.properties['moov']['udta'] = udta
            # Add parsers for other sub-boxes like 'iods'
            offset += size

    def _parse_trak(self, f, start, end, trak_dict):
        offset = start
        while offset < end:
            size, box_type, data_offset = self._read_box(f, offset)
            if box_type == 'tkhd':
                trak_dict['tkhd'] = self._parse_tkhd(f, data_offset, offset + size)
            elif box_type == 'mdia':
                mdia = {}
                self._parse_mdia(f, data_offset, offset + size, mdia)
                trak_dict['mdia'] = mdia
            offset += size

    def _parse_tkhd(self, f, start, end):
        f.seek(start)
        version = struct.unpack('>B', f.read(1))[0]
        f.read(3)  # flags
        if version == 1:
            creation_time = struct.unpack('>Q', f.read(8))[0]
            modification_time = struct.unpack('>Q', f.read(8))[0]
            track_id = struct.unpack('>I', f.read(4))[0]
            f.read(4)  # reserved
            duration = struct.unpack('>Q', f.read(8))[0]
        else:
            creation_time = struct.unpack('>I', f.read(4))[0]
            modification_time = struct.unpack('>I', f.read(4))[0]
            track_id = struct.unpack('>I', f.read(4))[0]
            f.read(4)  # reserved
            duration = struct.unpack('>I', f.read(4))[0]
        f.read(8)  # reserved
        layer = struct.unpack('>h', f.read(2))[0]
        alternate_group = struct.unpack('>h', f.read(2))[0]
        volume = struct.unpack('>h', f.read(2))[0]
        f.read(2)  # reserved
        # Matrix (36 bytes)
        f.read(36)
        width = struct.unpack('>i', f.read(4))[0]
        height = struct.unpack('>i', f.read(4))[0]
        return {'version': version, 'creation_time': creation_time, 'modification_time': modification_time, 'track_id': track_id, 'duration': duration, 'layer': layer, 'alternate_group': alternate_group, 'volume': volume, 'width': width, 'height': height}

    def _parse_mdia(self, f, start, end, mdia_dict):
        offset = start
        while offset < end:
            size, box_type, data_offset = self._read_box(f, offset)
            if box_type == 'mdhd':
                mdia_dict['mdhd'] = self._parse_mdhd(f, data_offset, offset + size)
            elif box_type == 'hdlr':
                mdia_dict['hdlr'] = self._parse_hdlr(f, data_offset, offset + size)
            elif box_type == 'minf':
                minf = {}
                self._parse_minf(f, data_offset, offset + size, minf)
                mdia_dict['minf'] = minf
            offset += size

    def _parse_mdhd(self, f, start, end):
        f.seek(start)
        version = struct.unpack('>B', f.read(1))[0]
        f.read(3)  # flags
        if version == 1:
            creation_time = struct.unpack('>Q', f.read(8))[0]
            modification_time = struct.unpack('>Q', f.read(8))[0]
            timescale = struct.unpack('>I', f.read(4))[0]
            duration = struct.unpack('>Q', f.read(8))[0]
        else:
            creation_time = struct.unpack('>I', f.read(4))[0]
            modification_time = struct.unpack('>I', f.read(4))[0]
            timescale = struct.unpack('>I', f.read(4))[0]
            duration = struct.unpack('>I', f.read(4))[0]
        language = struct.unpack('>H', f.read(2))[0]
        pre_defined = struct.unpack('>H', f.read(2))[0]
        return {'version': version, 'creation_time': creation_time, 'modification_time': modification_time, 'timescale': timescale, 'duration': duration, 'language': language, 'pre_defined': pre_defined}

    def _parse_hdlr(self, f, start, end):
        f.seek(start)
        version = struct.unpack('>B', f.read(1))[0]
        f.read(3)  # flags
        pre_defined = struct.unpack('>I', f.read(4))[0]
        handler_type = f.read(4).decode('utf-8')
        f.read(12)  # reserved
        name = f.read(end - f.tell()).decode('utf-8').rstrip('\x00')
        return {'version': version, 'pre_defined': pre_defined, 'handler_type': handler_type, 'name': name}

    def _parse_minf(self, f, start, end, minf_dict):
        offset = start
        while offset < end:
            size, box_type, data_offset = self._read_box(f, offset)
            if box_type == 'vmhd':
                minf_dict['vmhd'] = self._parse_vmhd(f, data_offset, offset + size)
            elif box_type == 'smhd':
                minf_dict['smhd'] = self._parse_smhd(f, data_offset, offset + size)
            elif box_type == 'stbl':
                stbl = {}
                self._parse_stbl(f, data_offset, offset + size, stbl)
                minf_dict['stbl'] = stbl
            offset += size

    def _parse_vmhd(self, f, start, end):
        f.seek(start)
        version = struct.unpack('>B', f.read(1))[0]
        f.read(3)  # flags
        graphics_mode = struct.unpack('>H', f.read(2))[0]
        opcolor = struct.unpack('>HHH', f.read(6))
        return {'version': version, 'graphics_mode': graphics_mode, 'opcolor': opcolor}

    def _parse_smhd(self, f, start, end):
        f.seek(start)
        version = struct.unpack('>B', f.read(1))[0]
        f.read(3)  # flags
        balance = struct.unpack('>h', f.read(2))[0]
        f.read(2)  # reserved
        return {'version': version, 'balance': balance}

    def _parse_stbl(self, f, start, end, stbl_dict):
        offset = start
        while offset < end:
            size, box_type, data_offset = self._read_box(f, offset)
            if box_type == 'stsd':
                stbl_dict['stsd'] = self._parse_stsd(f, data_offset, offset + size)
            elif box_type == 'stts':
                stbl_dict['stts'] = self._parse_stts(f, data_offset, offset + size)
            elif box_type == 'stsc':
                stbl_dict['stsc'] = self._parse_stsc(f, data_offset, offset + size)
            elif box_type == 'stsz':
                stbl_dict['stsz'] = self._parse_stsz(f, data_offset, offset + size)
            elif box_type == 'stco':
                stbl_dict['stco'] = self._parse_stco(f, data_offset, offset + size)
            offset += size

    def _parse_stsd(self, f, start, end):
        f.seek(start)
        version = struct.unpack('>B', f.read(1))[0]
        f.read(3)  # flags
        entry_count = struct.unpack('>I', f.read(4))[0]
        entries = []
        for _ in range(entry_count):
            entry_size = struct.unpack('>I', f.read(4))[0]
            format = f.read(4).decode('utf-8')
            entry_data = f.read(entry_size - 8)
            # Parse entry_data based on format (e.g., for video/audio)
            entries.append({'format': format, 'data': entry_data})
        return {'version': version, 'entry_count': entry_count, 'entries': entries}

    def _parse_stts(self, f, start, end):
        f.seek(start)
        version = struct.unpack('>B', f.read(1))[0]
        f.read(3)  # flags
        entry_count = struct.unpack('>I', f.read(4))[0]
        entries = []
        for _ in range(entry_count):
            sample_count = struct.unpack('>I', f.read(4))[0]
            sample_delta = struct.unpack('>I', f.read(4))[0]
            entries.append({'sample_count': sample_count, 'sample_delta': sample_delta})
        return {'version': version, 'entry_count': entry_count, 'entries': entries}

    def _parse_stsc(self, f, start, end):
        f.seek(start)
        version = struct.unpack('>B', f.read(1))[0]
        f.read(3)  # flags
        entry_count = struct.unpack('>I', f.read(4))[0]
        entries = []
        for _ in range(entry_count):
            first_chunk = struct.unpack('>I', f.read(4))[0]
            samples_per_chunk = struct.unpack('>I', f.read(4))[0]
            sample_desc_id = struct.unpack('>I', f.read(4))[0]
            entries.append({'first_chunk': first_chunk, 'samples_per_chunk': samples_per_chunk, 'sample_desc_id': sample_desc_id})
        return {'version': version, 'entry_count': entry_count, 'entries': entries}

    def _parse_stsz(self, f, start, end):
        f.seek(start)
        version = struct.unpack('>B', f.read(1))[0]
        f.read(3)  # flags
        sample_size = struct.unpack('>I', f.read(4))[0]
        sample_count = struct.unpack('>I', f.read(4))[0]
        entries = []
        if sample_size == 0:
            for _ in range(sample_count):
                entries.append(struct.unpack('>I', f.read(4))[0])
        return {'version': version, 'sample_size': sample_size, 'sample_count': sample_count, 'entries': entries}

    def _parse_stco(self, f, start, end):
        f.seek(start)
        version = struct.unpack('>B', f.read(1))[0]
        f.read(3)  # flags
        entry_count = struct.unpack('>I', f.read(4))[0]
        entries = []
        for _ in range(entry_count):
            entries.append(struct.unpack('>I', f.read(4))[0])
        return {'version': version, 'entry_count': entry_count, 'entries': entries}

    def _parse_udta(self, f, start, end, udta_dict):
        offset = start
        while offset < end:
            size, box_type, data_offset = self._read_box(f, offset)
            if box_type in ['titl', 'dscp', 'cprt', 'perf', 'auth', 'gnre']:
                f.seek(data_offset)
                language = f.read(3).decode('iso-8859-1')
                value = f.read(size - 8 - 3).decode('utf-8').rstrip('\x00')
                udta_dict[box_type] = {'language': language, 'value': value}
            elif box_type == 'tsel':
                udta_dict['tsel'] = self._parse_tsel(f, data_offset, offset + size)
            offset += size

    def _parse_tsel(self, f, start, end):
        f.seek(start)
        version = struct.unpack('>B', f.read(1))[0]
        f.read(3)  # flags
        switch_group = struct.unpack('>i', f.read(4))[0]
        attributes = []
        while f.tell() < end:
            attributes.append(f.read(4).decode('utf-8'))
        return {'version': version, 'switch_group': switch_group, 'attributes': attributes}

    def _parse(self):
        with open(self.filename, 'rb') as f:
            file_size = os.path.getsize(self.filename)
            self._parse_box(f, 0, file_size)

    def read_properties(self):
        return self.properties

    def write(self, new_filename):
        with open(self.filename, 'rb') as fin:
            data = fin.read()
        with open(new_filename, 'wb') as fout:
            fout.write(data)
        # To write modified properties, implement recursive writing of boxes
        # For simplicity, this copies the file; extend with struct.pack for changes

# Example usage:
# parser = ThreeGPParser('example.3gp')
# props = parser.read_properties()
# parser.write('modified.3gp')

3. Java Class for .3GP Files

import java.io.RandomAccessFile;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.HashMap;
import java.util.Map;
import java.util.ArrayList;

public class ThreeGPParser {
    private String filename;
    private Map<String, Object> properties = new HashMap<>();
    private ArrayList<Object[]> boxes = new ArrayList<>();
    private RandomAccessFile file;

    public ThreeGPParser(String filename) throws IOException {
        this.filename = filename;
        file = new RandomAccessFile(filename, "r");
        parse();
        file.close();
    }

    private long readBox(RandomAccessFile f, long offset, ByteBuffer bb) throws IOException {
        f.seek(offset);
        f.read(bb.array(), 0, 8);
        bb.position(0);
        long size = bb.getInt() & 0xFFFFFFFFL;
        String boxType = new String(new byte[]{bb.get(), bb.get(), bb.get(), bb.get()});
        long dataOffset = offset + 8;
        if (size == 1) {
            f.read(bb.array(), 0, 8);
            bb.position(0);
            size = bb.getLong();
            dataOffset += 8;
        }
        return size;
        // Return size, use boxType and dataOffset in parsers
    }

    // Similar parser methods as in Python, using ByteBuffer for unpacking
    // For brevity, implement key ones like ftyp, mvhd, etc.

    // Full implementation would mirror the Python structure with ByteBuffer.getInt(), getLong(), etc.

    public Map<String, Object> readProperties() {
        return properties;
    }

    public void write(String newFilename) throws IOException {
        // Implement writing by reconstructing boxes with ByteBuffer.put*
        // For simplicity, copy the file; extend for modifications
        RandomAccessFile in = new RandomAccessFile(filename, "r");
        RandomAccessFile out = new RandomAccessFile(newFilename, "rw");
        byte[] buffer = new byte[1024];
        int len;
        while ((len = in.read(buffer)) > 0) {
            out.write(buffer, 0, len);
        }
        in.close();
        out.close();
    }

    private void parse() throws IOException {
        // Implement recursive parsing similar to Python
        ByteBuffer bb = ByteBuffer.allocate(16).order(ByteOrder.BIG_ENDIAN);
        long offset = 0;
        long fileSize = file.length();
        while (offset < fileSize) {
            // Call readBox and parse based on type
            // Populate properties
        }
    }

    // Add _parseFtyp, _parseMoov, etc., using ByteBuffer to extract fields
}

// Example usage:
// ThreeGPParser parser = new ThreeGPParser("example.3gp");
// Map props = parser.readProperties();
// parser.write("modified.3gp");

Note: The Java code is sketched for brevity; full implementation would use ByteBuffer to mimic the Python struct unpacking for each box.

4. JavaScript Class for .3GP Files

const fs = require('fs');

class ThreeGPParser {
    constructor(filename) {
        this.filename = filename;
        this.properties = {};
        this.boxes = [];
        this.buffer = fs.readFileSync(filename);
        this.offset = 0;
        this.parse();
    }

    readBox() {
        let size = this.buffer.readUInt32BE(this.offset);
        this.offset += 4;
        let boxType = this.buffer.toString('utf8', this.offset, this.offset + 4);
        this.offset += 4;
        if (size == 1) {
            size = this.buffer.readBigUInt64BE(this.offset);
            this.offset += 8;
        }
        return {size: Number(size), boxType};
    }

    parse() {
        while (this.offset < this.buffer.length) {
            const {size, boxType} = this.readBox();
            const dataStart = this.offset;
            if (boxType === 'ftyp') {
                this.properties.ftyp = this.parseFtyp(dataStart, dataStart + size - 8);
            } else if (boxType === 'moov') {
                this.properties.moov = {};
                this.parseMoov(dataStart, dataStart + size - 8);
            } // Add more
            this.boxes.push({boxType, start: dataStart - 8, size});
            this.offset = dataStart + size - 8;
        }
    }

    parseFtyp(start, end) {
        this.offset = start;
        const majorBrand = this.buffer.toString('utf8', this.offset, this.offset + 4);
        this.offset += 4;
        const minorVersion = this.buffer.readUInt32BE(this.offset);
        this.offset += 4;
        const compatibleBrands = [];
        while (this.offset < end) {
            compatibleBrands.push(this.buffer.toString('utf8', this.offset, this.offset + 4));
            this.offset += 4;
        }
        return {majorBrand, minorVersion, compatibleBrands};
    }

    // Similar methods for mvhd, trak, etc., using buffer.readUInt32BE, readBigUInt64BE, etc.

    readProperties() {
        return this.properties;
    }

    write(newFilename) {
        fs.writeFileSync(newFilename, this.buffer);
        // Extend to modify buffer for property changes
    }
}

// Example usage:
// const parser = new ThreeGPParser('example.3gp');
// const props = parser.readProperties();
// parser.write('modified.3gp');

5. C Class for .3GP Files

Since "c class" likely means C++, here's a C++ class.

#include <fstream>
#include <vector>
#include <map>
#include <string>
#include <cstdint>

class ThreeGPParser {
private:
    std::string filename;
    std::map<std::string, std::map<std::string, std::string>> properties; // Simplified
    std::vector<std::tuple<std::string, uint64_t, uint64_t>> boxes;
    std::ifstream file;
    std::vector<char> buffer;

    uint32_t readUInt32(uint64_t &offset) {
        uint32_t val = (buffer[offset] << 24) | (buffer[offset+1] << 16) | (buffer[offset+2] << 8) | buffer[offset+3];
        offset += 4;
        return val;
    }

    uint64_t readUInt64(uint64_t &offset) {
        uint64_t val = ((uint64_t)buffer[offset] << 56) | ((uint64_t)buffer[offset+1] << 48) | ((uint64_t)buffer[offset+2] << 40) | ((uint64_t)buffer[offset+3] << 32) |
                       ((uint64_t)buffer[offset+4] << 24) | ((uint64_t)buffer[offset+5] << 16) | ((uint64_t)buffer[offset+6] << 8) | buffer[offset+7];
        offset += 8;
        return val;
    }

    // Similar read functions for other types

public:
    ThreeGPParser(const std::string& fn) : filename(fn) {
        file.open(filename, std::ios::binary | std::ios::ate);
        auto size = file.tellg();
        buffer.resize(size);
        file.seekg(0);
        file.read(buffer.data(), size);
        file.close();
        parse();
    }

    void parse() {
        uint64_t offset = 0;
        while (offset < buffer.size()) {
            uint64_t boxStart = offset;
            uint64_t size = readUInt32(offset);
            std::string boxType(buffer.begin() + offset, buffer.begin() + offset + 4);
            offset += 4;
            if (size == 1) {
                size = readUInt64(offset);
            }
            uint64_t dataStart = offset;
            if (boxType == "ftyp") {
                // Parse ftyp fields
            } // Add parsers
            boxes.emplace_back(boxType, boxStart, size);
            offset = boxStart + size;
        }
    }

    auto readProperties() {
        return properties;
    }

    void write(const std::string& newFilename) {
        std::ofstream out(newFilename, std::ios::binary);
        out.write(buffer.data(), buffer.size());
        // Extend for modifications
    }
};

// Example usage:
// ThreeGPParser parser("example.3gp");
// auto props = parser.readProperties();
// parser.write("modified.3gp");