Movatterモバイル変換


[0]ホーム

URL:


Skip to content
Search Gists
Sign in Sign up

Instantly share code, notes, and snippets.

@NSExceptional
CreatedApril 21, 2025 21:37
    • Star(0)You must be signed in to star a gist
    • Fork(0)You must be signed in to fork a gist
    Save NSExceptional/17e38f3b0818f5330bbc9ee444157768 to your computer and use it in GitHub Desktop.
    A small class for parsing MP4 file headers, with the ability to check whether the given file has the hvc1 tag. All in-process.
    /*
    * mp4.ts
    * media
    *
    * Created by Tanner Bennett on 2025-04-19
    * Copyright © 2025 Tanner Bennett. All rights reserved.
    */
    typeAtom={
    size:number;
    type:string;
    offset:number;
    };
    typeTopLevelMP4Atoms=
    'ftyp'|
    'moov'|
    'mdat'|
    'free'|
    'skip'|
    'udta';
    typeSeekOptions={
    reset?:boolean;
    uptoOffset?:number;
    };
    exportdefaultclassMP4{
    constructor(privatereadonlyfile:string){
    this.handle=Deno.openSync(this.file,{read:true});
    }
    privatehandle:Deno.FsFile
    privategetoffset():number{
    returnthis.handle.seekSync(0,Deno.SeekMode.Current);
    }
    privatereset():void{
    this.handle.seekSync(0,Deno.SeekMode.Start);
    }
    publicclose():void{
    this.handle.close();
    }
    privatereadBytes(size:number):Uint8Array{
    constbuffer=newUint8Array(size);
    constbytesRead=this.handle.readSync(buffer);
    if(bytesRead===null){
    thrownewError('End of file reached');
    }
    returnbuffer;
    }
    privatereadAtomHeader():Atom{
    constoffset=this.offset;
    constheader=this.readBytes(8);
    constsize=newDataView(header.buffer).getUint32(0);
    consttype=newTextDecoder().decode(header.subarray(4,8));
    return{ size, type, offset};
    }
    /**
    * Seeks up to the end of the specified atom header.
    *
    * If the atom has children or data, the reader will be positioned
    * at the start of the next child or data, so that you can immediately
    * read the next atom header or start reading the data.
    */
    publicseekAtom(type:string,options?:SeekOptions):Atom|null{
    const{ reset, uptoOffset}=options||{};
    if(reset){
    this.reset();
    }
    constshouldLoop=():boolean=>{
    if(uptoOffset){
    returnthis.offset<uptoOffset;
    }
    returntrue;
    };
    while(shouldLoop()){
    const{ size,type:nextType}=this.readAtomHeader();
    if(nextType===type){
    return{ type, size,offset:this.offset-8};
    }
    // Seek past the current atom (-8 is for the header we already read)
    this.handle.seekSync(size-8,Deno.SeekMode.Current);
    if(size===0){
    break;// End of file
    }
    }
    returnnull;
    }
    /** Traverse a branch of the atom tree. Assumes each atom immediately contains child atoms. */
    publictraverseAtoms(branch:string[]):Atom|null{
    if(branch.length===0){
    returnnull;
    }
    if(branch.length===1){
    returnthis.seekAtom(branch[0]);
    }
    constfirst=branch.shift()!;
    letcurrentAtom=this.seekAtom(first);
    // Logging
    if(!currentAtom){
    console.log(`Could not find first atom in branch:\n${branch.join(' > ')}`);
    }
    for(consttypeofbranch){
    // No need to check for null at any point here, it's fine
    // if we come across null right away and keep looping,
    // `seekAtomWithinAtom` will no-op each time in that case
    constchildAtom=this.seekAtomWithinAtom(currentAtom,type);
    currentAtom=childAtom;
    // Logging
    if(!currentAtom){
    console.log(`Could not find atom '${type}' in branch:\n${branch.join(' > ')}`);
    break;
    }
    }
    returncurrentAtom;
    }
    /** Scan all atoms at the current depth for the given type, up to the end of the parent atom */
    publicseekAtomWithinAtom(parent:Atom|null,type:string):Atom|null{
    if(!parent){
    returnnull;
    }
    const{ size, offset}=parent;
    constendOffset=offset+size;
    returnthis.seekAtom(type,{uptoOffset:endOffset});
    }
    /** This tag is stored as an atom inside moov > trak > mdia > minf > stbl > stsd */
    publicgethvc1():boolean{
    conststsd=this.traverseAtoms(['moov','trak','mdia','minf','stbl','stsd']);
    if(!stsd){
    returnfalse;
    }
    // Can't just put hvc1 at the end of the list above, because
    // stsd is the first and only atom in this list that doesn't
    // immediately contain other atoms, so we need to seek past
    // some metadata before we can read the next atom
    // Skip version/flags (4 bytes)
    this.handle.seekSync(4,Deno.SeekMode.Current);
    // Read the number of entries in the stsd atom
    conststsdHeader=this.readBytes(4);
    constnumEntries=newDataView(stsdHeader.buffer).getUint32(0);
    if(numEntries===0){
    returnfalse;
    }
    consthvc1=this.seekAtomWithinAtom(stsd,'hvc1');
    return!!hvc1;
    }
    }
    Sign up for freeto join this conversation on GitHub. Already have an account?Sign in to comment

    [8]ページ先頭

    ©2009-2025 Movatter.jp