media-trimmer
Version:
A simple MP4 video trimmer using the WebCodecs API
3 lines (2 loc) • 30.2 kB
JavaScript
import{ref as e,effect as t,toValue as i,watch as a,onScopeDispose as r,render as o,h as s,unref as n,toRef as c,computed as d}from"fine-jsx";import l from"/home/taye/proj/miru/miru/packages/webgl-video-editor/src/index.css";import*as m from"mp4-muxer";import*as u from"webm-muxer";import{debounce as h}from"throttle-debounce";import*as p from"mp4box";import{jsx as v,jsxs as f,Fragment as g}from"fine-jsx/jsx-runtime";import{Buffer as w}from"buffer/";import*as y from"ebml";import b from"ebml-block";const k="undefined"!=typeof window?window:globalThis,T=k.HTMLElement??Object,S=(e,a,r,o)=>t((t=>{const s=i(e);null!=s&&(s.addEventListener(a,r,o),t((()=>s.removeEventListener(a,r,o))))})),E=e(!1);if("matchMedia"in k){const e=k.matchMedia("(hover: hover)");E.value=e.matches,e.addEventListener("change",(()=>E.value=e.matches))}class x{_onDispose=new Set;isDisposed=!1;add(e){this._onDispose.add(e)}dispose(){this._onDispose.forEach((e=>e())),this._onDispose.clear(),this.isDisposed=!0}}const C=()=>{let e,t;return{promise:new Promise(((i,a)=>{e=i,t=a})),resolve:e,reject:t}},{userAgent:D=""}=k.navigator??{},V=/Version\/(\d+)/.exec(D)?.[1],I=D.includes("Chrome/")||D.includes("Chromium/"),A=D.includes("Mac OS"),L=D.includes("AppleWebKit/")&&D.includes("Safari/")&&!I,P=L&&"16"===V,M=L&&"17"===V;D.includes("Gecko/"),D.includes("Android");const O="undefined"!=typeof OffscreenCanvas&&null!=OffscreenCanvas,R=!P&&O,j=(e,t)=>((e,t,i)=>{if(null==e){if(R){const a=(e=new OffscreenCanvas(1,1)).getContext(t,i);if(null!=a)return a}e="undefined"==typeof document?void 0:document.createElement("canvas")}const a=e?.getContext(t,i);if(!a)throw new Error(`[miru] Couldn't create ${t} context`);return a})(e,"2d",t),_=(e,t,i)=>{let a,r,o=!1;const s=H(e instanceof Blob?e=a=URL.createObjectURL(e):e,t).then((e=>{if(r=e,o)throw n(),new Error("[miru] decode source was closed");return e})),n=()=>{r&&(r instanceof HTMLElement?(r.removeAttribute("src"),r.remove(),r.pause()):r.close(),r=void 0)};return{promise:s,close:()=>{o=!0,n(),a&&URL.revokeObjectURL(a)}}},H=(e,t)=>{if(!e)throw new Error("Empty video source URL");const i=((e,t,i)=>{const a=document.createElement(e);return a.playsInline=!0,a.preload="auto",a.muted=!0,a.setAttribute("style","width:1px;height:1px;position:fixed;left:-1px;top:-1px"),document.body.appendChild(a),W(a,t,i),a.load(),a})("video",e,t);return new Promise(((e,t)=>{const a=()=>{e(i),o()},r=()=>{t(new Error("aborted")),o()},o=()=>{i.removeEventListener("loadedmetadata",a),i.removeEventListener("abort",r),i.removeEventListener("abort",r)};i.addEventListener("loadedmetadata",a,{once:!0}),i.addEventListener("abort",r,{once:!0}),i.addEventListener("error",(e=>{t(i.error??e.error??new Error("Unknown video error.")),o()}),{once:!0})}))},W=(e,t,i)=>{void 0!==i?e.crossOrigin=i:(e=>new URL(e,location.href).origin!==location.origin)(t)&&(e.crossOrigin="anonymous"),e.src=t};let F;const U=e=>{const t=(()=>{if(null==F)try{F=new CSSStyleSheet,F.replaceSync(l)}catch{F=document.createElement("style"),F.style.display="none",F.textContent=l}return F})();t instanceof HTMLStyleElement?e.appendChild(t.cloneNode(!0)):e.adoptedStyleSheets=[t]},B=(e,t,i)=>{if(import.meta.env.SSR)return()=>{};const a=i.attachShadow({mode:"open"});return U(a),o(s(e,t),a)},$=p;class z{#e=$.createFile();#t;#i;#a=[];#r=new AbortController;async init(e,t){t?.signal?.addEventListener("abort",(()=>this.#r.abort()),{once:!0});const i=await fetch(e,{...t,signal:this.#r.signal});if(!i.ok||!i.body)throw new Error(`Invalid response from "${e}".`);const a=i.headers.get("content-type");if(a&&!/video\/(mp4|mov|quicktime)/.test(a))throw new Error(`Unexpected content-type: "${a}"`);this.#t=i.body;let r=0;return new Promise(((e,t)=>{this.#e.onReady=t=>e(this.getMetadata(t)),this.#e.onError=t,this.#i=this.#t.pipeTo(new WritableStream({write:e=>{const t=e.buffer;t.fileStart=r,r+=t.byteLength,this.#e.appendBuffer(t)}},{highWaterMark:1})),this.#i.catch(t)}))}getMetadata(e){const t=e.videoTracks[0],i=e.audioTracks[0],a=t&&this.getTrackMetadata(t),r=i&&this.getTrackMetadata(i);return{type:"mp4",duration:e.duration/e.timescale||a?.duration||r?.duration||0,video:a,audio:r}}getTrackMetadata(e){const{codec:t}=e,i=this.#e.getTrackById(e.id),a=(e.duration||e.samples_duration)/e.timescale;if("audio"===e.type){const{audio:r}=e,o=i.mdia.minf.stbl.stsd.entries.find((({type:e})=>"mp4a"===e));return{id:e.id,type:e.type,codec:t,duration:a,sampleRate:r.sample_rate,numberOfChannels:r.channel_count,track:e,...null==o?null:q(o)}}let r;for(const e of i.mdia.minf.stbl.stsd.entries){const t=e.avcC??e.hvcC??e.vpcC??e.av1C??null;if(null!=t){const e=new $.DataStream(void 0,0,$.DataStream.BIG_ENDIAN);t.write(e),r=new Uint8Array(e.buffer,t.hdr_size);break}}if(!r)throw new Error("Unsupported format.");const{width:o,height:s}=e.video,n=Array.from(e.matrix).map(((e,t)=>e/(t%3==2?2**30:65536))),c=Math.atan2(n[3],n[0])*(180/Math.PI);return{id:e.id,type:e.type,codec:t.startsWith("vp08")?"vp8":t,duration:a,fps:e.nb_samples/(e.samples_duration/e.timescale),description:r,codedWidth:o,codedHeight:s,matrix:n,rotation:c,track:e}}setExtractionOptions(e,t,i){this.#a.push({track:e,onSample:t,onDone:i})}async start(e=0,t){t??=this.#a.reduce(((e,{track:t})=>Math.min(e,t.samples_duration/t.timescale)),1/0);const i=[];this.#a.forEach((({track:e,onSample:t,onDone:a})=>{const{promise:r,resolve:o,reject:s}=C();i.push(r),this.#e.setExtractionOptions(e.id,{track:e,onSample:t,onDone:a,sampleBytes:0,presentationOffsetS:N(this.#e.getTrackById(e.id)),isEnded:!1,resolve:o,reject:s})})),this.#e.onSamples=(e,i,a)=>{if(i.isEnded)return;const r=a.length,o={1:"bt709",5:"bt470bg",6:"smpte170m"},s={1:"bt709",13:"iec61966-2-1"},n={1:"bt709",5:"bt470bg",6:"smpte170m"};try{for(let e=0;e<r;e++){const{data:r,is_sync:c,cts:d,duration:l,timescale:m,description:u}=a[e],h=d/m-i.presentationOffsetS,{track:p}=i;i.sampleBytes+=r.byteLength;let v,f=0,g=0;if("video"===p.type){f=u.width,g=u.height;const{colr:e}=u;v=e&&{primaries:o[e.colour_primaries],transfer:s[e.transfer_characteristics],matrix:n[e.matrix_coefficients]}}const w={data:r,type:c?"key":"delta",timestamp:1e6*h,duration:1e6*l/m,codedWidth:f,codedHeight:g,colorSpace:v,mediaType:p.type};if(i.onSample(w),h>=t&&c){i.isEnded=!0;break}}i.isEnded||=i.sampleBytes>=this.#e.getTrackById(e).samples_size}catch(e){return i.isEnded=!0,void i.reject(e)}return i.isEnded?(i.onDone?.(),void i.resolve()):void 0},await new Promise(((e,t)=>{this.#i?.catch(t),this.#e.onError=t,this.#e.start(),Promise.all(i).then((()=>e())).catch(t)})).finally(this.stop.bind(this))}stop(){this.#e.stop(),this.#r.abort("stopped")}}const N=e=>{const t=e.edts?.elst.entries[0];return null==t?0:t.media_time/e.mdia.mdhd.timescale},q=e=>{const t=e.esds.esd.descs[0]?.descs[0],i=t.data,a=((7&i[0])<<1)+(i[1]>>7),r=(120&i[1])>>3;return{sampleRate:[96e3,88200,64e3,48e3,44100,32e3,24e3,22050,16e3,12e3,11025,8e3,7350][a],numberOfChannels:r}},G={Timecode:"Timestamp",TimecodeScale:"TimestampScale",TrackTimecodeScale:"TrackTimestampScale"};class X extends TransformStream{constructor(){const e=new y.Decoder;super({transform(t){e.write(w.from(t))},start:t=>{e.on("error",t.error.bind(t)),e.on("data",(e=>{(e=>{e.name=G[e.name]??e.name})(e[1]),t.enqueue(e)}))},flush(){e.end()}})}}class J extends TransformStream{#o={};metadata;constructor(){let e=0;super({transform:(t,i)=>{if("tag"!==t[0])return;const a=t[1];switch(a.name){case"Timestamp":e=a.value;break;case"SimpleBlock":{const t=a;let r=this.#o[t.track];if(!r){let e=0,i=0;const{video:a,audio:o}=this.metadata,s=a?.id===t.track?a:o?.id===t.track?o:void 0;if(!s)return;"video"===s.type&&({codedWidth:e,codedHeight:i}=s),r=this.#o[t.track]={track:s,codedWidth:e,codedHeight:i}}const{track:o,codedWidth:s,codedHeight:n}=r,c=o.track,d="video"===o.type?1e6/o.fps:void 0,l=(e+t.value*c.TrackTimestampScale)*c.TimestampScale/1e3,{frames:m}=b(t.data);m.forEach((e=>{const a={...o,duration:d,codedWidth:s,codedHeight:n,data:e,type:t.keyframe?"key":"delta",timestamp:l,mediaType:o.type};i.enqueue([a,o])}));break}}}})}}class K extends TransformStream{#s=1e6;#n=0;#c=!1;#d=!1;#l=!1;#m=!1;#u=new Set;#h={type:"webm",duration:0,video:void 0,audio:void 0};#p=!1;#v(e){!this.#c||this.#d||this.#l||this.#m||(this.#p=!0,e.enqueue(this.#h),e.terminate())}constructor(){let e;const t={};let i=0;super({transform:(a,r)=>{if(this.#p)return;const o=a[0],s=a[1];if("end"===o&&(this.#u.add(s.name),"Info"===s.name)){const e=1e3*this.#n/this.#s;this.#h.duration=e,this.#m=!e}if(this.#c)switch(s.name){case"Timestamp":i=s.value;break;case"SimpleBlock":{const e=s,a=t[e.track];if(!a)break;const{frames:o,invisible:n,timecode:c}=b(e.data);if(this.#d&&e.keyframe&&"vp09"===a.codec){const e=o[0],t=e[0]>>>4&3,i=t>>>1&1|t<<1&2,s=10,n=i>=2?128&e[4]?12:10:8;a.codec=`vp09.${[i,s,n].map((e=>e.toString(10).padStart(2,"0"))).join(".")}`,this.#d=!1,this.#v(r)}if(this.#l&&"video"===a.type){const e=c;0===e||n||(a.fps=1e9/(e*this.#s),this.#l=!1,this.#v(r))}if(this.#m){const e=(c+i)*this.#s/1e9,t=a.track.DefaultDuration/1e9||("video"===a.type&&a.fps?1/a.fps:0);this.#h.duration=Math.max(this.#h.duration,e+t)}}}else{if("TrackEntry"===s.name)if("start"===o)e={TrackNumber:0,TrackType:0,CodecID:"",TrackTimestampScale:1,DefaultDuration:0,PixelWidth:0,PixelHeight:0,SamplingFrequency:0,Channels:0,CodecDelay:0,CodecPrivate:void 0,TimestampScale:this.#s,Duration:this.#n};else if(e){const{TrackType:i}=e;if(1===i||2===i){const i=this.#f(e);i?("video"===i.type?this.#h.video=i:this.#h.audio=i,t[i.id]=i):e=void 0}e=void 0}if("tag"===o){const t=a[1];switch(t.name){case"TimestampScale":this.#s=t.value;break;case"Duration":this.#n=t.value;break;case"TrackNumber":case"PixelWidth":case"TrackType":case"PixelHeight":case"TrackTimestampScale":case"DefaultDuration":case"SamplingFrequency":case"Channels":case"CodecDelay":e[t.name]=t.value;break;case"CodecID":e.CodecID=t.value.replace("\0","");break;case"CodecPrivate":e.CodecPrivate=t.data}}this.#c||=this.#u.has("Tracks")&&this.#u.has("Info"),this.#v(r)}},flush:e=>{this.#p||e.enqueue(this.#h)}})}#f(e){const{CodecID:t,TrackType:i}=e;if(1!==i&&2!==i)return;let a=[["A_OPUS","opus"],["A_VORBIS","vorbis"],["V_VP8","vp8"],["V_VP9","vp09"],["V_AV1","av01"],["V_MPEG4/ISO/AVC","avc1"],["V_MPEGH/ISO/HEVC","hev1"]].find((e=>t.startsWith(e[0])))?.[1];const r=e.CodecPrivate;if(!a)throw new Error(`Unsupported Matroska codec "${t}".`);switch(a){case"vp09":if(r&&r.length>=6){a=`vp09.${[r[2],r[5],r[8]].map((e=>e.toString(10).padStart(2,"0"))).join(".")}`}else this.#d=!0;break;case"avc1":{if(!r)break;const[e,t,i]=Array.from(r.slice(1,3)).map((e=>e.toString(16)));a=`${a}.${e}${t}${i}`;break}}const o=1===e.TrackType?"video":"audio",s=e.DefaultDuration?1e9/e.DefaultDuration:0;return this.#l="video"===o&&!s,{type:o,id:e.TrackNumber,codec:a,duration:e.Duration*e.TrackTimestampScale*e.TimestampScale/1e9,fps:s,codedWidth:e.PixelWidth,codedHeight:e.PixelHeight,matrix:[1,0,0,0,1,0,0,0,1],rotation:0,numberOfChannels:e.Channels,sampleRate:e.SamplingFrequency,codecDelay:e.CodecDelay*this.#s/1e9,description:r,track:e}}}class Y{#g=new AbortController;#w;encodedChunkExtractor=new J;#y={};async init(e,t){t?.signal?.addEventListener("abort",(()=>this.#g.abort()),{once:!0});const i=await fetch(e,{...t,signal:this.#g.signal});if(!i.ok||!i.body)throw new Error(`Invalid response from "${e}".`);const a=i.headers.get("content-type");if(a&&!/video\/(webm|matroska|x-matroska)/.test(a))throw new Error(`Unexpected content-type: "${a}"`);const[r,o]=i.body.pipeThrough(new X).tee();this.#w=r;const s=await new Promise(((e,t)=>{o.pipeThrough(new K).pipeTo(new WritableStream({write:e},{highWaterMark:1}),{signal:this.#g.signal}).catch(t)}));return this.encodedChunkExtractor.metadata=s,s}setExtractionOptions(e,t,i){this.#y[e.id]={onSample:t,onDone:i,isDone:!1}}async start(e,t){t??=this.encodedChunkExtractor.metadata.duration;const i={write:e=>{const i=e[0],a=e[1],r=this.#y[a.id];r&&(r.onSample(i),i.timestamp+(i.duration??0)>=1e6*t&&"key"===i.type&&(r.onDone?.(),r.isDone=!0,Object.values(this.#y).every((e=>!!e?.isDone))&&this.#g.abort()))},close:()=>{Object.values(this.#y).forEach((e=>!1===e?.isDone&&e.onDone?.()))}};await this.#w.pipeThrough(this.encodedChunkExtractor,{signal:this.#g.signal}).pipeTo(new WritableStream(i)).catch((e=>this.#g.signal.aborted?void 0:Promise.reject(e)))}stop(){this.#g.abort("stopped")}}class Q{#b;async init(e,t){const i="string"!=typeof e,a=i?URL.createObjectURL(e):e;let r=null;if(i)r=e.type;else{const e=a.startsWith("blob:")?"GET":"HEAD";r=(await fetch(a,{...t,method:e})).headers.get("content-type")}const o=r&&/video\/(webm|matroska|x-matroska)/.test(r)?"webm":"mp4";return this.#b="webm"===o?new Y:new z,this.#b.init(a,t)}setExtractionOptions(e,t,i){this.#b.setExtractionOptions(e,t,i)}start(e,t){return this.#b.start(e,t)}stop(){this.#b.stop()}}class Z{abort;promise;context=j(new OffscreenCanvas(0,0));videoInfo;startTimeS;endTimeS;angle;has90DegreeRotation;fps;frameDurationS;firstFrameTimeUs=-1;constructor(e){this.videoInfo=e.videoInfo,this.startTimeS=e.start,this.endTimeS=e.end,this.angle=e.angle,this.has90DegreeRotation=0!==Math.abs(e.angle%180);const{fps:t}=this.videoInfo;this.fps=t,this.frameDurationS=1/this.fps}start(e,t){this.abort=new AbortController;const i=()=>this.abort?.abort();t?.addEventListener("abort",i,{once:!0});const a=new x,r=1e6*this.startTimeS,o=1e6*this.frameDurationS,{angle:s}=this,{codedWidth:n,codedHeight:c}=this.videoInfo,d=n>c;this.onImage=function(t,i){-1===this.firstFrameTimeUs&&i>=r&&(this.firstFrameTimeUs=i);const{firstFrameTimeUs:a}=this,n=-1===a?-1:i-a,c={timestamp:i,duration:o};let l;if(this.has90DegreeRotation){const e=(e=>"codedWidth"in e?{width:e.codedWidth,height:e.codedHeight}:"videoWidth"in e?{width:e.videoWidth,height:e.videoHeight}:e)(t),i=e.width>e.height,a=d!==i?this.rotate(t,e,s):t;l=new VideoFrame(a,c)}else l=t instanceof VideoFrame?t:new VideoFrame(t,c);e(l,n)},this.promise=this._start(this.abort.signal,a).finally((()=>{t?.removeEventListener("abort",i),a.dispose()}))}onImage;rotate(e,t,i){const{context:a}=this,{canvas:r}=a;return r.height=t.width,r.width=t.height,a.save(),a.translate(r.width/2,r.height/2),a.rotate(i*Math.PI/180),a.drawImage(e,-t.width/2,-t.height/2),a.restore(),r}flush(){return Promise.resolve(this.promise).finally((()=>this.onImage=void 0))}stop(){this.abort?.abort("`stop()` was called.")}}const ee=()=>"function"==typeof VideoDecoder,te=()=>{if(!ee()&&("function"!=typeof HTMLVideoElement||"function"!=typeof HTMLVideoElement.prototype.requestVideoFrameCallback))throw new Error("Missing VideoDecoder and requestVideoFrameCallback APIs.")},ie=(e,t=!0)=>{const{minutes:i,hours:a,seconds:r,subSeconds:o}=(e=>{const t=String(Math.trunc(e/60)%60).padStart(2,"0"),i=Math.trunc(e/3600);return{minutes:t,hours:String(i).padStart(2,"0"),seconds:String(Math.trunc(e)%60).padStart(2,"0"),subSeconds:String(Math.trunc(e%1*100)).padStart(2,"0")}})(e);return`${"00"===a?"":`${a}:`}${i}:${r}${t?`.${o}`:""}`},{DurationFormat:ae}=Intl,re=async(e,t,i)=>{const a=C(),r=()=>a.reject(new Error("Aborted")),o=["load","canplay","seeked"],s=()=>e.readyState>=2&&a.resolve(void 0);o.forEach((t=>e.addEventListener(t,s,{once:!0}))),e.addEventListener("error",(()=>a.reject(e.error??new Error("Unkown error"))),{once:!0}),i.addEventListener("abort",r,{once:!0}),e.currentTime=t,await a.promise.finally((()=>{o.forEach((t=>e.removeEventListener(t,s))),e.removeEventListener("seeked",s),i.removeEventListener("abort",r)}))};class oe extends Z{rvfcHandle=0;video;closeVideo;async init(e,t){const{promise:i,close:a}=_(e,t);this.video=await i,this.closeVideo=a}async _start(e,t){t.add((()=>{i.cancelVideoFrameCallback(this.rvfcHandle),this.closeVideo?.()}));const{video:i,startTimeS:a}=this,r=Math.min(this.endTimeS,i.duration),o=C(),{frameDurationS:s}=this;let n=a,c=-1;const d=h(500,(()=>{i.ended?o.resolve():i.readyState<2||(i.currentTime=n+=.25*s)}));t.add(d.cancel);const l=()=>{n+=.999*s,i.currentTime=n,this.rvfcHandle=i.requestVideoFrameCallback(m),d()},m=()=>{if(i.readyState<2&&!i.ended)return void re(i,n,e).then(m).catch((()=>{}));const{currentTime:t}=i,d=Math.trunc(1e6*t),u=t>=r;-1!==c&&t-c>1.5*s&&console.warn("[media-trimmer] Skipped a frame before",t),t>=a&&t!==c&&(!u||this.firstFrameTimeUs===d)&&this.onImage(i,d),c=t,n=t,u||i.ended?o.resolve():l()};await re(i,Math.max(0,a-s),e),t.isDisposed||(m(),await o.promise)}flush(){return super.flush().finally((()=>this.closeVideo?.()))}}class se extends Z{decoder;demuxer;#k=!1;constructor(e,t){super(t),this.demuxer=e}async init(){await(async(e,t)=>{if(!ee())throw new Error("Missing VideoDecoder API.");if(!(await VideoDecoder.isConfigSupported(t)).supported)throw new Error(`Decoding ${e} config "${t.codec}" is not supported by the user agent.`)})("video",this.videoInfo)}async _start(e){const{demuxer:t}=this,i=1e6*this.endTimeS,a=C(),r=this.decoder=new VideoDecoder({output:t=>{!e.aborted&&t.timestamp<=i?this.onImage(t,t.timestamp):t.close()},error:e=>a.reject(e)});r.configure(this.videoInfo),t.setExtractionOptions(this.videoInfo,(e=>{this.#k||r.decode(new EncodedVideoChunk(e))}),a.resolve),await a.promise,"configured"===r.state&&await r.flush()}async flush(){var e;await(e=10,new Promise((t=>setTimeout(t,e))));const{decoder:t}=this;this.#k=!0,await Promise.all(["configured"===t?.state&&t.flush(),super.flush()])}}const ne=()=>{try{return ce(),!0}catch{return!1}},ce=()=>{if((P||M)&&A)throw new Error("VideoEncoder doesn't work on Safari <= 17.");if(te(),"function"!=typeof VideoEncoder)throw new Error("Missing VideoEncoder support.")},de={mp4:{vp09:"vp9",av01:"av1",avc1:"avc",hev1:"hevc",opus:"opus",mp4a:"aac"},webm:{vp09:"V_VP9",vp8:"V_VP8",av01:"V_AV1",avc1:"V_MPEG4/ISO/AVC",hev1:"V_MPEGH/ISO/HEVC",opus:"A_OPUS",vorbis:"A_VORBIS"}};class le{url;options;rotation;janitor;videoOutCodec;constructor(e,t){this.url=e,this.options=t}async trim(){const{onProgress:e}=this.options;e?.(0),this.janitor?.dispose();const t=await this._trim().finally(this.dispose.bind(this));return e?.(1),t}async _trim(){ce();const{options:e}=this,t=this.janitor=new x,i=new AbortController;let a=!1;t.add((()=>{a||i.signal.aborted||i.abort()}));const{demuxer:r,metadata:o}=await this.initDemuxer(i),{video:s,audio:n}=o,c=!!n&&!e.mute;if(this.rotation=s?.rotation??0,!s&&!n)throw new Error("Input media has no video or audio tracks.");s&&(this.videoOutCodec=e.videoEncoderConfig?.codec??("mp4"===o.type?"avc1.420028":s.codec));const d=de[o.type][this.videoOutCodec.replace(/\..*/,"")],l={video:s&&{codec:d,width:s.codedWidth,height:s.codedHeight,rotation:s.matrix},audio:c?{codec:de[o.type][n.codec.replace(/\..*/,"")],sampleRate:n.sampleRate,numberOfChannels:n.numberOfChannels}:void 0,fastStart:"in-memory"},h="mp4"===o.type?new m.Muxer({...l,target:new m.ArrayBufferTarget}):new u.Muxer({...l,target:new u.ArrayBufferTarget}),p=s&&await this.createFrameExtractor(r,s),v=s&&p&&await this.createVideoEncoder(p,h,(e=>i.abort(e)));p&&v&&p.start(((e,t)=>{"configured"===v.state&&t>=0&&v.encode(e),e.close()}),i.signal);const f=C();c?this.setAudioRemuxing(r,h,n,{resolve:f.resolve,reject:e=>{f.reject(e),i.abort(e)}}):f.resolve(),await r.start(e.start,e.end),await(p?.flush()),await Promise.all([v?.flush(),f.promise]),h.finalize(),a=!0;const{buffer:g}=h.target;return new Blob([g],{type:"video/webm"})}async initDemuxer(e){const{credentials:t}=this.options,i=new Q,a=await i.init(this.url,{credentials:t,signal:e.signal});return this.janitor.add((()=>i.stop())),{demuxer:i,metadata:a}}getTracks(e,t){const{videoTracks:i,audioTracks:a}=e,r=i[0];if(!r)throw new Error("File doesn't contain a video track.");const o=Array.from(r.matrix).map(((e,t)=>e/(t%3==2?2**30:65536))),s=Math.atan2(o[3],o[0])*(180/Math.PI);return{videoTrack:r,audioTrack:t?void 0:a[0],rotation:o,angle:s}}async createFrameExtractor(e,t){const{rotation:i}=this,a={...this.options,videoInfo:t,angle:i};let r;try{if(L&&A)throw new Error("Broken VideoDecoder implementation?");r=new se(e,a),await r.init()}catch{r=new oe(a),await r.init(this.url,a.crossOrigin)}return this.janitor.add((()=>r.stop())),r}async createVideoEncoder(e,t,i){const{options:a}=this,r=1e6*a.end,{codedWidth:o,codedHeight:s}=e.videoInfo,{bitrate:n=1e8}=a.videoEncoderConfig??{},c={codec:this.videoOutCodec,width:o,height:s,bitrate:n,framerate:e.fps};await(async(e,t,i=k.VideoEncoder)=>{if(void 0===i)throw new Error("Missing VideoDecoder API.");const a=await i.isConfigSupported(t);if(P&&A)throw new Error("VideoEncoder doesn't work on Safari 16.");if(!a.supported)throw new Error(`Encoding ${e} config '${JSON.stringify(t)}' is not supported by the user agent.`)})("video",c);const d=new VideoEncoder({output:(i,o)=>{t.addVideoChunk(i,o,i.timestamp-e.firstFrameTimeUs),a.onProgress?.(i.timestamp/r)},error:e=>{i(e)}});return d.configure(c),this.janitor.add((()=>"configured"===d.state&&d.close())),d}setAudioRemuxing(e,t,i,{resolve:a,reject:r}){const o=1e6*this.options.start,s=1e6*this.options.end;let n=-1;const{codec:c,sampleRate:d,numberOfChannels:l,description:u}=i,h={decoderConfig:{codec:c,sampleRate:d,numberOfChannels:l,description:u}};e.setExtractionOptions(i,(e=>{const{timestamp:i}=e;if(!(i<o||i>=s)){-1===n&&(n=i);try{const a=new Uint8Array(e.data),r=i-n;t instanceof m.Muxer?t.addAudioChunkRaw(a,e.type,r,e.duration):t.addAudioChunkRaw(a,e.type,r,h)}catch(e){r(e)}}}),a)}dispose(){this.janitor?.dispose()}}const me=async(e,t)=>{const{onProgress:i}=t,a=new le(e,t);if(!i)return a.trim().finally((()=>a.dispose()));let r=0,o=-1,s=0;const n=()=>{r!==o&&i(r),o=r,s=requestAnimationFrame(n)};return n(),t={...t,onProgress:e=>r=e},a.trim().finally((()=>{a.dispose(),i(r),cancelAnimationFrame(s)}))},ue=({icon:e,onClick:t,children:i,class:a,iconClass:r,tag:o="button",...c})=>{const d={class:()=>["icon-button",a],onClick:t,children:[()=>{const t=n(e);return v(t,{class:["icon",r]})},i],..."button"===o?{type:"button"}:{role:"button"},...c};return s(o,d)},he=({isActive:e,onToggle:t,activeIcon:a,inactiveIcon:r,class:o,children:s,...d})=>v(ue,{...d,icon:c((()=>i(e)?n(a):n(r))),class:o,onClick:()=>t(!i(e)),children:s}),pe=(e,t,i)=>e<t?t:e>i?i:e,ve=e=>v("svg",{className:"icon",viewBox:"0 0 24 24",width:"1.2em",height:"1.2em",...e,children:v("path",{fill:"none",stroke:"currentColor",strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"m15 6l-6 6l6 6"})}),fe=e=>v("svg",{className:"icon",viewBox:"0 0 24 24",width:"1.2em",height:"1.2em",...e,children:v("path",{fill:"none",stroke:"currentColor",strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"m9 6l6 6l-6 6"})}),ge=e=>v("svg",{className:"icon",viewBox:"0 0 24 24",width:"1.2em",height:"1.2em",...e,children:v("path",{fill:"none",stroke:"currentColor",strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M7 4v16l13-8z"})}),we=e=>v("svg",{className:"icon",viewBox:"0 0 24 24",width:"1.2em",height:"1.2em",...e,children:v("path",{fill:"none",stroke:"currentColor",strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M6 6a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1v12a1 1 0 0 1-1 1H7a1 1 0 0 1-1-1zm8 0a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1v12a1 1 0 0 1-1 1h-2a1 1 0 0 1-1-1z"})}),ye=e=>v("svg",{className:"icon",viewBox:"0 0 24 24",width:"1.2em",height:"1.2em",...e,children:v("path",{fill:"none",stroke:"currentColor",strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M15 8a5 5 0 0 1 1.912 4.934m-1.377 2.602A5 5 0 0 1 15 16m2.7-11a9 9 0 0 1 2.362 11.086m-1.676 2.299A9 9 0 0 1 17.7 19M9.069 5.054L9.5 4.5A.8.8 0 0 1 11 5v2m0 4v8a.8.8 0 0 1-1.5.5L6 15H4a1 1 0 0 1-1-1v-4a1 1 0 0 1 1-1h2l1.294-1.664M3 3l18 18"})}),be=e=>v("svg",{className:"icon",viewBox:"0 0 24 24",width:"1.2em",height:"1.2em",...e,children:v("path",{fill:"none",stroke:"currentColor",strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M15 8a5 5 0 0 1 0 8m2.7-11a9 9 0 0 1 0 14M6 15H4a1 1 0 0 1-1-1v-4a1 1 0 0 1 1-1h2l3.5-4.5A.8.8 0 0 1 11 5v14a.8.8 0 0 1-1.5.5z"})}),ke=e=>v("svg",{className:"icon",viewBox:"0 0 24 24",width:"1.2em",height:"1.2em",...e,children:v("path",{fill:"none",stroke:"currentColor",strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M6 15H4a1 1 0 0 1-1-1v-4a1 1 0 0 1 1-1h2l3.5-4.5A.8.8 0 0 1 11 5v14a.8.8 0 0 1-1.5.5zm10-5l4 4m0-4l-4 4"})}),Te=({startTime:t,endTime:i,pixelsPerSecond:a,mediaDuration:r,seekTo:o,onResize:s})=>{const n=e(),c=e(),l=d((()=>{const e=a.value,r=t.value*e;return{left:r,right:Math.max(i.value*e,r+1)}})),m=e(),u={x:0,y:0},h={start:0,duration:0},p=e=>{if(m.value||0!==e.button)return;const a=e.currentTarget;u.x=e.pageX,u.y=e.pageY,h.start=t.value,h.duration=i.value-t.value,a.setPointerCapture(e.pointerId),m.value=a===n.value?"left":"right",e.stopPropagation()},g=e=>{if(!m.value)return;const n=(e.pageX-u.x)/a.value;let c,d;"left"===m.value?(c=pe(h.start+n,0,h.start+h.duration-.25),d=i.value-t.value+(t.value-c),o(c)):(c=t.value,d=pe(h.duration+n,.25,r.value-h.start),o(c+d)),s(c,c+d,m.value)},w=()=>{m.value=void 0};return[n,c].forEach((e=>{S(e,"pointerdown",p),S(e,"pointermove",g),S(e,"lostpointercapture",w)})),v("div",{class:"clip is-selected can-resize-left",style:()=>`--clip-box-left:${l.value.left}px;--clip-box-right:${l.value.right}px;--clip-color:transparent`,children:v("div",{class:"clip-box",children:f("div",{class:"clip-controls",children:[v("div",{ref:n,class:"clip-resize-left",children:v(ve,{})}),v("div",{ref:c,class:"clip-resize-right",children:v(fe,{})})]})})})},Se=o=>{let n="";const c=document.createElement("video"),l=(a=>{const r=e(0),o=()=>r.value=i(a)?.readyState??0;return t(o),["abort","canplay","canplaythrough","durationchange","emptied","ended","error","loadeddata","loadedmetadata","loadstart","pause","play","playing","seeked","seeking","stalled","suspend","timeupdate","waiting"].forEach((e=>S(a,e,o))),r})(c);c.playsInline=!0,c.preload="auto";const m=e(),u=e(!1),h=(t=>{const o=i(t),s=e({width:o?.offsetWidth??0,height:o?.offsetHeight??0}),n=new ResizeObserver((([e])=>{if("contentBoxSize"in e){const{contentBoxSize:t}=e,i=Array.isArray(t)?t[0]:t;s.value={width:i.inlineSize,height:i.blockSize}}else s.value={width:e.contentRect.width,height:e.contentRect.height}}));return a([()=>i(t)],(([e],t,i)=>{null!=e&&(n.observe(e),i((()=>n.unobserve(e))))})),r(n.disconnect.bind(n)),s})(m),p=d((()=>h.value.width/x.value||0)),w=e(!0),y=e(!1),b=e(!1),k=e(0),T=e(""),E=e(!ne()),x=e(0),C=e(0),D=e(0);["timeupdate","seeking"].forEach((e=>S(c,e,(()=>k.value=c.currentTime)))),["play","pause"].forEach((e=>S(c,e,(()=>w.value=c.paused))));const V=({offsetX:e})=>{if(!u.value)return;const t=e/p.value;I(t)},I=e=>{c.currentTime=k.value=pe(e,C.value,D.value)};t((()=>{c.muted=b.value;const e=k.value;e<C.value?I(C.value):e>=D.value&&(c.pause(),I(D.value))})),t((async e=>{const t=i(o.source);if(c.pause(),T.value="",E.value=!ne(),null==t||""===t)return void(x.value=0);const a="string"==typeof t;n=a?t:URL.createObjectURL(t);let r,s=!1;e((()=>{s=!0,a||URL.revokeObjectURL(n)}));try{r=await(async e=>{const t="string"!=typeof e,i=t?URL.createObjectURL(e):e,a=new Q;try{return await a.init(i)}finally{a.stop(),t&&URL.revokeObjectURL(i)}})(n)}catch(e){E.value=!0,o.onError(e);try{r=await(async e=>{const{promise:t,close:i}=_(e,void 0),a=await t,r=a.duration,o=a,s=!!(o.audioTracks?.length||o.mozHasAudio||o.webkitAudioDecodedByteCount);return i(),{duration:r,hasAudio:s,width:a.videoWidth,height:a.videoHeight}})(n)}catch{return c.src="",x.value=0,T.value=String(e),void o.onError(e)}}if(s)return;c.src=n,c.load(),x.value=r.duration,y.value="hasAudio"in r?r.hasAudio:!!r.audio;const d=i(o.state);E.value?(C.value=0,D.value=r.duration):(C.value=d?.start??0,D.value=d?.end??r.duration),o.onLoad({duration:r.duration,hasAudio:y.value})})),t((()=>{if(!x.value)return;if(E.value)return C.value=0,void(D.value=x.value);const e=i(o.state);if(!e)return;const t=pe(e.start,0,x.value);C.value=t,D.value=e.end})),t((()=>{const e=C.value,t=D.value,i={start:e,end:t,mute:b.value,isFullDuration:t-e===x.value};o.onChange?.(i)}));const A=(e,t,i)=>{C.value=e,D.value=t,I("left"===i?e:t)};return f("div",{class:"video-trimmer",style:()=>`--current-time-offset:${k.value*p.value}px`,children:[f("div",{part:"viewport",class:()=>["viewport",0===l.value&&"is-empty"],children:[s(c,{class:"viewport-canvas"}),()=>T.value&&v("div",{class:"video-trimmer-error",onClick:()=>T.value="",children:T})]}),f("div",{children:[f("div",{class:"video-trimmer-controls numeric",children:[v(he,{class:"video-trimmer-controls-left video-trimmer-play-pause",activeIcon:ge,inactiveIcon:we,isActive:w,disabled:()=>0===x.value,onToggle:e=>{e?c.pause():((k.value>=D.value||c.ended)&&I(C.value),c.play().catch((()=>{})))},children:v("div",{class:"sr-only",children:()=>w.value?"Play":"Pause"})}),v("div",{class:"video-trimmer-controls-center",children:()=>ie(k.value,!1)}),f("div",{class:"video-trimmer-controls-right",children:[v("div",{children:()=>((e,t=navigator.languages)=>{const i=new Intl.NumberFormat(t,{style:"unit",unit:"second",unitDisplay:"narrow"});if(!e)return i.format(0);const a=Math.trunc(e%60),r=Math.trunc(e/60);if(ae)return new ae(t,{style:"narrow"}).format({minutes:r,seconds:a});const o=new Intl.NumberFormat(t,{style:"unit",unit:"minute",unitDisplay:"narrow"});return a?`${r?`${o.format(r)} `:""}${i.format(a)}`:o.format(r)})(Math.round(D.value-C.value))}),()=>E.value?v("div",{}):y.value?v(he,{activeIcon:ye,inactiveIcon:be,isActive:b,onToggle:e=>b.value=e}):v(ue,{disabled:!0,icon:ke,onClick:()=>{}})]})]}),v("div",{ref:m,class:"video-trimmer-timeline",onPointerDown:e=>{if(u.value||0!==e.button)return;const t=e.currentTarget;t.setPointerCapture(e.pointerId),u.value=!0,V({offsetX:e.clientX-t.getBoundingClientRect().left})},onPointerMove:V,onLostPointerCapture:()=>u.value=!1,children:()=>E.value?f(g,{children:[v("div",{class:"clip",children:v("div",{class:"video-trimmer-clip-warning",children:"Editing this video format isn't supported"})}),v("div",{class:"video-trimmer-cursor"})]}):x.value?f(g,{children:[v(Te,{startTime:C,endTime:D,mediaDuration:x,pixelsPerSecond:p,seekTo:I,onResize:A}),v("div",{class:"video-trimmer-cursor"})]}):v("div",{class:"clip"})})]}),o.children]})};export{T as H,Se as V,ce as a,ne as h,B as r,me as t};
//# sourceMappingURL=VideoTrimmerUI-DU4OMfli.js.map