@codingfactory/mediables-vue 2.4.21 → 2.4.23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{PixiFrameExporter-C_Ga17dU.cjs → PixiFrameExporter-DmyELJBR.cjs} +2 -2
- package/dist/{PixiFrameExporter-C_Ga17dU.cjs.map → PixiFrameExporter-DmyELJBR.cjs.map} +1 -1
- package/dist/{PixiFrameExporter-FqchRwLH.js → PixiFrameExporter-UPjs5JGK.js} +2 -2
- package/dist/{PixiFrameExporter-FqchRwLH.js.map → PixiFrameExporter-UPjs5JGK.js.map} +1 -1
- package/dist/{editor-DWDUrVRz.js → editor-2Q72CZjK.js} +19 -19
- package/dist/editor-2Q72CZjK.js.map +1 -0
- package/dist/{editor-DKRFjaUq.cjs → editor-DjvxEsss.cjs} +3 -3
- package/dist/editor-DjvxEsss.cjs.map +1 -0
- package/dist/index-DMnweeFo.cjs +342 -0
- package/dist/{index-BNKYj_dh.cjs.map → index-DMnweeFo.cjs.map} +1 -1
- package/dist/{index-D6Bzhz_r.js → index-Dj_Ppp4_.js} +10248 -9789
- package/dist/{index-D6Bzhz_r.js.map → index-Dj_Ppp4_.js.map} +1 -1
- package/dist/mediables-vanilla.cjs +1 -1
- package/dist/mediables-vanilla.mjs +1 -1
- package/dist/mediables-vue.cjs +1 -1
- package/dist/mediables-vue.mjs +2 -2
- package/dist/style.css +1 -1
- package/package.json +1 -1
- package/dist/editor-DKRFjaUq.cjs.map +0 -1
- package/dist/editor-DWDUrVRz.js.map +0 -1
- package/dist/index-BNKYj_dh.cjs +0 -342
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const R=require("./index-
|
|
2
|
-
//# sourceMappingURL=PixiFrameExporter-
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const R=require("./index-DMnweeFo.cjs");function V(){return typeof VideoEncoder<"u"&&typeof VideoFrame<"u"}function z(){return typeof AudioEncoder<"u"&&typeof AudioData<"u"}async function L(i,d){try{const t=await fetch(i,d?{signal:d}:{});if(!t.ok)return null;const r=await t.arrayBuffer(),u=new AudioContext;try{return await u.decodeAudioData(r)}catch{return null}finally{await u.close()}}catch{return null}}function N(i,d,a){const t=i.sampleRate,r=Math.max(0,Math.floor(d*t)),u=Math.min(i.length,Math.ceil(a*t)),p=Math.max(0,u-r),n=[];for(let s=0;s<i.numberOfChannels;s++){const l=i.getChannelData(s);n.push(l.slice(r,r+p))}return{channels:n,sampleRate:t,numberOfChannels:i.numberOfChannels}}async function P(i,d,a,t,r){const u={codec:"mp4a.40.2",numberOfChannels:t,sampleRate:a,bitrate:r};if(!(await AudioEncoder.isConfigSupported(u)).supported)return;const n=new AudioEncoder({output:(e,c)=>{i.addAudioChunk(e,c)},error:e=>{}});n.configure(u);const s=1024,l=d[0].length;for(let e=0;e<l;e+=s){const c=Math.min(s,l-e),h=new Float32Array(t*c);for(let f=0;f<t;f++)h.set(d[f].subarray(e,e+c),f*c);const w=new AudioData({format:"f32-planar",sampleRate:a,numberOfFrames:c,numberOfChannels:t,timestamp:Math.floor(e/a*1e6),data:h});n.encode(w),w.close(),n.encodeQueueSize>10&&await new Promise(f=>{n.addEventListener("dequeue",()=>f(),{once:!0})})}await n.flush(),n.close()}async function X(i,d){const{width:a,height:t,fps:r,bitrate:u=5e6,audioBitrate:p=128e3,trimStart:n=0,trimEnd:s,sourceUrl:l,onProgress:e,signal:c}=d;if(!V())throw new Error("WebCodecs API is not supported in this browser");const h=i.duration.value,w=3600,f=w*60;if(!Number.isFinite(h)||h<=0)throw new Error(`Invalid video duration: ${h}. The video metadata may not have loaded. Please ensure the video is fully loaded before exporting.`);const A=s??h,E=Math.max(0,A-n);if(!Number.isFinite(E)||E<=0)throw new Error(`Invalid export duration: ${E} (trimStart=${n}, trimEnd=${A}). Check that the trim points are valid.`);if(E>w)throw new Error(`Export duration (${Math.round(E)}s) exceeds maximum allowed (${w}s).`);const g=Math.min(Math.max(1,Math.ceil(E*r)),f);let F=Promise.resolve(null);l&&z()&&(F=L(l,c));const v=await F,D=!!v&&v.numberOfChannels>0&&v.length>0,O={target:new R.ArrayBufferTarget,video:{codec:"avc",width:a,height:t,frameRate:r},fastStart:"in-memory"};D&&(O.audio={codec:"aac",numberOfChannels:v.numberOfChannels,sampleRate:v.sampleRate});const C=new R.Muxer(O),$=[{codec:"avc1.640028",hw:"prefer-hardware"},{codec:"avc1.4d0028",hw:"prefer-hardware"},{codec:"avc1.420028",hw:"prefer-hardware"},{codec:"avc1.640028",hw:"prefer-software"},{codec:"avc1.4d0028",hw:"prefer-software"},{codec:"avc1.420028",hw:"prefer-software"}];let y=null;for(const o of $){const x={codec:o.codec,width:a,height:t,bitrate:u,framerate:r,hardwareAcceleration:o.hw};if((await VideoEncoder.isConfigSupported(x)).supported){y=x;break}}if(!y)throw new Error(`No supported VideoEncoder codec found for ${a}×${t}. Try a lower export resolution or use a browser with H.264 encoding support (Chrome 94+).`);const m=new VideoEncoder({output:(o,x)=>{C.addVideoChunk(o,x)},error:o=>{}});m.configure(y);const b=document.createElement("canvas");b.width=a,b.height=t;const M=b.getContext("2d");if(!M)throw new Error("Failed to create 2D context for export target canvas");for(let o=0;o<g;o++){if(c!=null&&c.aborted)throw m.close(),new DOMException("Export aborted","AbortError");const x=n+o/r,S=await i.captureFrameAt(x);if(!S)continue;M.clearRect(0,0,a,t),M.drawImage(S,0,0,a,t);const I=Math.floor(o/r*1e6),q=Math.floor(1e6/r),T=new VideoFrame(b,{timestamp:I,duration:q}),B=o%(r*2)===0;m.encode(T,{keyFrame:B}),T.close(),m.encodeQueueSize>5&&await new Promise(_=>{m.addEventListener("dequeue",()=>_(),{once:!0})});const W=Math.round((o+1)/g*90);e==null||e(W)}if(await m.flush(),m.close(),D){e==null||e(91);const o=N(v,n,A);await P(C,o.channels,o.sampleRate,o.numberOfChannels,p),e==null||e(99)}C.finalize();const{buffer:k}=C.target;return e==null||e(100),new Blob([k],{type:"video/mp4"})}exports.exportWithPixiFrames=X;exports.isWebCodecsSupported=V;
|
|
2
|
+
//# sourceMappingURL=PixiFrameExporter-DmyELJBR.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PixiFrameExporter-C_Ga17dU.cjs","sources":["../resources/js/video-engine/adapters/PixiFrameExporter.ts"],"sourcesContent":["/**\n * PIXI Frame-by-Frame Video Exporter\n *\n * Captures each frame from the PIXI canvas (which already has filters applied)\n * and encodes them with WebCodecs + mp4-muxer for exact 1:1 preview-to-export parity.\n *\n * Audio is extracted from the source video, trimmed to match, and muxed alongside\n * the video frames. If the source has no audio or AudioEncoder is unavailable,\n * the export proceeds silently (video-only).\n */\n\nimport { Muxer, ArrayBufferTarget } from 'mp4-muxer'\n\nexport interface PixiExportOptions {\n /** Width of the output video in pixels */\n width: number\n /** Height of the output video in pixels */\n height: number\n /** Frames per second */\n fps: number\n /** Video bitrate in bps (default: 5 Mbps) */\n bitrate?: number\n /** Audio bitrate in bps (default: 128 kbps) */\n audioBitrate?: number\n /** Trim start in seconds (source time, default: 0) */\n trimStart?: number\n /** Trim end in seconds (source time, default: full duration) */\n trimEnd?: number\n /** Source video URL (needed to extract audio) */\n sourceUrl?: string\n /** Progress callback (0-100) */\n onProgress?: (percent: number) => void\n /** Abort signal for cancellation */\n signal?: AbortSignal\n}\n\nexport interface PixiFrameProvider {\n /**\n * Seek the underlying video to `timeSec`, render through PIXI with filters,\n * and return the PIXI canvas with the rendered frame.\n */\n captureFrameAt(timeSec: number): Promise<HTMLCanvasElement | null>\n /** Video duration in seconds (reactive ref) */\n duration: { value: number }\n}\n\n/**\n * Check if the browser supports the required WebCodecs APIs.\n */\nexport function isWebCodecsSupported(): boolean {\n return typeof VideoEncoder !== 'undefined' && typeof VideoFrame !== 'undefined'\n}\n\n/**\n * Check if AudioEncoder is available for AAC encoding.\n */\nfunction isAudioEncoderSupported(): boolean {\n return typeof AudioEncoder !== 'undefined' && typeof AudioData !== 'undefined'\n}\n\n/**\n * Decode the audio from a video source URL using AudioContext.\n * Returns null if the source has no audio or decoding fails.\n */\nasync function decodeAudioFromSource(\n sourceUrl: string,\n signal?: AbortSignal\n): Promise<AudioBuffer | null> {\n try {\n const requestInit: RequestInit = signal ? { signal } : {}\n const response = await fetch(sourceUrl, requestInit)\n if (!response.ok) {\n console.warn('[PixiExport] Failed to fetch source for audio:', response.status)\n return null\n }\n\n const arrayBuffer = await response.arrayBuffer()\n const audioCtx = new AudioContext()\n\n try {\n const audioBuffer = await audioCtx.decodeAudioData(arrayBuffer)\n console.log('[PixiExport] Audio decoded:', {\n channels: audioBuffer.numberOfChannels,\n sampleRate: audioBuffer.sampleRate,\n duration: audioBuffer.duration,\n length: audioBuffer.length,\n })\n return audioBuffer\n } catch (decodeErr) {\n console.warn('[PixiExport] No audio track in source or decode failed:', decodeErr)\n return null\n } finally {\n await audioCtx.close()\n }\n } catch (fetchErr) {\n console.warn('[PixiExport] Could not fetch source for audio extraction:', fetchErr)\n return null\n }\n}\n\n/**\n * Trim an AudioBuffer to a specific time range.\n * Returns a new set of Float32Array channel data for the trimmed region.\n */\nfunction trimAudioBuffer(\n audioBuffer: AudioBuffer,\n trimStart: number,\n trimEnd: number\n): { channels: Float32Array[]; sampleRate: number; numberOfChannels: number } {\n const sampleRate = audioBuffer.sampleRate\n const startSample = Math.max(0, Math.floor(trimStart * sampleRate))\n const endSample = Math.min(audioBuffer.length, Math.ceil(trimEnd * sampleRate))\n const trimmedLength = Math.max(0, endSample - startSample)\n\n const channels: Float32Array[] = []\n for (let ch = 0; ch < audioBuffer.numberOfChannels; ch++) {\n const fullChannel = audioBuffer.getChannelData(ch)\n channels.push(fullChannel.slice(startSample, startSample + trimmedLength))\n }\n\n return {\n channels,\n sampleRate,\n numberOfChannels: audioBuffer.numberOfChannels,\n }\n}\n\n/**\n * Encode trimmed audio data and add chunks to the muxer.\n */\nasync function encodeAudio(\n muxer: Muxer<ArrayBufferTarget>,\n channels: Float32Array[],\n sampleRate: number,\n numberOfChannels: number,\n bitrate: number\n): Promise<void> {\n const audioEncoderConfig: AudioEncoderConfig = {\n codec: 'mp4a.40.2', // AAC-LC\n numberOfChannels,\n sampleRate,\n bitrate,\n }\n\n const support = await AudioEncoder.isConfigSupported(audioEncoderConfig)\n if (!support.supported) {\n console.warn('[PixiExport] AAC AudioEncoder not supported, exporting without audio')\n return\n }\n\n const audioEncoder = new AudioEncoder({\n output: (chunk, metadata) => {\n muxer.addAudioChunk(chunk, metadata)\n },\n error: (err) => {\n console.error('[PixiExport] AudioEncoder error:', err)\n },\n })\n\n audioEncoder.configure(audioEncoderConfig)\n\n // Encode in chunks of 1024 samples (standard AAC frame size)\n const chunkSize = 1024\n const totalSamples = channels[0].length\n\n for (let offset = 0; offset < totalSamples; offset += chunkSize) {\n const frameSamples = Math.min(chunkSize, totalSamples - offset)\n\n // Build planar data buffer: channel0[0..frameSamples] + channel1[0..frameSamples] + ...\n const planarData = new Float32Array(numberOfChannels * frameSamples)\n for (let ch = 0; ch < numberOfChannels; ch++) {\n planarData.set(channels[ch].subarray(offset, offset + frameSamples), ch * frameSamples)\n }\n\n const audioData = new AudioData({\n format: 'f32-planar',\n sampleRate,\n numberOfFrames: frameSamples,\n numberOfChannels,\n timestamp: Math.floor((offset / sampleRate) * 1_000_000), // microseconds\n data: planarData,\n })\n\n audioEncoder.encode(audioData)\n audioData.close()\n\n // Backpressure\n if (audioEncoder.encodeQueueSize > 10) {\n await new Promise<void>((resolve) => {\n audioEncoder.addEventListener('dequeue', () => resolve(), { once: true })\n })\n }\n }\n\n await audioEncoder.flush()\n audioEncoder.close()\n\n console.log('[PixiExport] Audio encoding complete', {\n totalSamples,\n chunks: Math.ceil(totalSamples / chunkSize),\n })\n}\n\n/**\n * Export a video by capturing frames from a PIXI preview (with filters applied)\n * and encoding them into an MP4 file, with audio from the source.\n */\nexport async function exportWithPixiFrames(\n provider: PixiFrameProvider,\n options: PixiExportOptions\n): Promise<Blob> {\n const {\n width,\n height,\n fps,\n bitrate = 5_000_000,\n audioBitrate = 128_000,\n trimStart = 0,\n trimEnd,\n sourceUrl,\n onProgress,\n signal,\n } = options\n\n if (!isWebCodecsSupported()) {\n throw new Error('WebCodecs API is not supported in this browser')\n }\n\n const videoDuration = provider.duration.value\n\n console.log('[TRACE-EXPORT] PixiFrameExporter ENTER', {\n videoDuration,\n trimStart,\n trimEnd,\n fps,\n width,\n height,\n isFiniteDuration: Number.isFinite(videoDuration),\n sourceUrl: sourceUrl?.substring(0, 50),\n })\n\n // Guard against Infinity/NaN duration (common with blob URLs where metadata isn't loaded).\n // Cap at 1 hour as an absolute safety limit.\n const MAX_EXPORT_DURATION_SEC = 3600\n const MAX_TOTAL_FRAMES = MAX_EXPORT_DURATION_SEC * 60 // 216,000 frames at 60fps\n\n if (!Number.isFinite(videoDuration) || videoDuration <= 0) {\n console.error('[TRACE-EXPORT] INVALID DURATION:', videoDuration)\n throw new Error(\n `Invalid video duration: ${videoDuration}. The video metadata may not have loaded. ` +\n 'Please ensure the video is fully loaded before exporting.'\n )\n }\n\n const effectiveTrimEnd = trimEnd ?? videoDuration\n const exportDuration = Math.max(0, effectiveTrimEnd - trimStart)\n\n console.log('[TRACE-EXPORT] PixiFrameExporter calculated:', {\n effectiveTrimEnd,\n exportDuration,\n isFiniteExportDuration: Number.isFinite(exportDuration),\n })\n\n if (!Number.isFinite(exportDuration) || exportDuration <= 0) {\n console.error('[TRACE-EXPORT] INVALID EXPORT DURATION:', exportDuration)\n throw new Error(\n `Invalid export duration: ${exportDuration} (trimStart=${trimStart}, trimEnd=${effectiveTrimEnd}). ` +\n 'Check that the trim points are valid.'\n )\n }\n\n if (exportDuration > MAX_EXPORT_DURATION_SEC) {\n throw new Error(`Export duration (${Math.round(exportDuration)}s) exceeds maximum allowed (${MAX_EXPORT_DURATION_SEC}s).`)\n }\n\n const totalFrames = Math.min(\n Math.max(1, Math.ceil(exportDuration * fps)),\n MAX_TOTAL_FRAMES\n )\n\n console.log('[TRACE-EXPORT] PixiFrameExporter will capture', totalFrames, 'frames over', exportDuration, 'seconds at', fps, 'fps')\n\n // --- Decode audio from source (in parallel with setup) ---\n let audioPromise: Promise<AudioBuffer | null> = Promise.resolve(null)\n if (sourceUrl && isAudioEncoderSupported()) {\n audioPromise = decodeAudioFromSource(sourceUrl, signal)\n }\n\n console.log('[PixiExport] Starting export', {\n width,\n height,\n fps,\n trimStart,\n trimEnd: effectiveTrimEnd,\n exportDuration,\n totalFrames,\n hasSourceUrl: !!sourceUrl,\n })\n\n // --- Muxer (configure audio track conditionally after we know if audio exists) ---\n // We need to know audio params before creating the muxer, so await audio decode first.\n const audioBuffer = await audioPromise\n const hasAudio = !!audioBuffer && audioBuffer.numberOfChannels > 0 && audioBuffer.length > 0\n\n const muxerConfig: ConstructorParameters<typeof Muxer<ArrayBufferTarget>>[0] = {\n target: new ArrayBufferTarget(),\n video: {\n codec: 'avc',\n width,\n height,\n frameRate: fps,\n },\n fastStart: 'in-memory',\n }\n\n if (hasAudio) {\n muxerConfig.audio = {\n codec: 'aac',\n numberOfChannels: audioBuffer.numberOfChannels,\n sampleRate: audioBuffer.sampleRate,\n }\n console.log('[PixiExport] Audio track configured:', {\n channels: audioBuffer.numberOfChannels,\n sampleRate: audioBuffer.sampleRate,\n })\n } else {\n console.log('[PixiExport] No audio track - exporting video only')\n }\n\n const muxer = new Muxer(muxerConfig)\n\n // --- Video Encoder ---\n // Try codecs in order: High 4.0 (best quality at 1080p), Main 4.0, Baseline 4.0,\n // then repeat without hardware acceleration. avc1.42001f (Baseline Level 3.1)\n // only supports up to 1280×720, so 1080p exports need Level 4.0+.\n const codecCandidates: Array<{ codec: string; hw: HardwareAcceleration }> = [\n { codec: 'avc1.640028', hw: 'prefer-hardware' }, // High Profile Level 4.0\n { codec: 'avc1.4d0028', hw: 'prefer-hardware' }, // Main Profile Level 4.0\n { codec: 'avc1.420028', hw: 'prefer-hardware' }, // Baseline Profile Level 4.0\n { codec: 'avc1.640028', hw: 'prefer-software' }, // High 4.0, software fallback\n { codec: 'avc1.4d0028', hw: 'prefer-software' }, // Main 4.0, software fallback\n { codec: 'avc1.420028', hw: 'prefer-software' }, // Baseline 4.0, software fallback\n ]\n\n let encoderConfig: VideoEncoderConfig | null = null\n for (const candidate of codecCandidates) {\n const cfg: VideoEncoderConfig = {\n codec: candidate.codec,\n width,\n height,\n bitrate,\n framerate: fps,\n hardwareAcceleration: candidate.hw,\n }\n const support = await VideoEncoder.isConfigSupported(cfg)\n if (support.supported) {\n encoderConfig = cfg\n console.log('[PixiExport] Using codec:', candidate.codec, 'hw:', candidate.hw)\n break\n }\n }\n\n if (!encoderConfig) {\n throw new Error(\n `No supported VideoEncoder codec found for ${width}×${height}. ` +\n 'Try a lower export resolution or use a browser with H.264 encoding support (Chrome 94+).'\n )\n }\n\n const encoder = new VideoEncoder({\n output: (chunk, metadata) => {\n muxer.addVideoChunk(chunk, metadata)\n },\n error: (err) => {\n console.error('[PixiExport] VideoEncoder error:', err)\n },\n })\n\n encoder.configure(encoderConfig)\n\n // --- Intermediate 2D canvas ---\n const targetCanvas = document.createElement('canvas')\n targetCanvas.width = width\n targetCanvas.height = height\n const targetCtx = targetCanvas.getContext('2d')\n if (!targetCtx) {\n throw new Error('Failed to create 2D context for export target canvas')\n }\n\n // --- Video frame loop (progress: 0-90%) ---\n console.log('[TRACE-EXPORT] Frame loop START, totalFrames:', totalFrames)\n let nullFrameCount = 0\n for (let frame = 0; frame < totalFrames; frame++) {\n if (signal?.aborted) {\n console.log('[TRACE-EXPORT] Frame loop ABORTED at frame', frame)\n encoder.close()\n throw new DOMException('Export aborted', 'AbortError')\n }\n\n // Calculate source time: offset by trimStart\n const timeSec = trimStart + frame / fps\n\n if (frame === 0 || frame === totalFrames - 1 || frame % 10 === 0) {\n console.log(`[TRACE-EXPORT] Capturing frame ${frame}/${totalFrames} at ${timeSec.toFixed(3)}s`)\n }\n\n // Capture the frame from PIXI (seek + render + return canvas)\n const pixiCanvas = await provider.captureFrameAt(timeSec)\n if (!pixiCanvas) {\n nullFrameCount++\n console.warn(`[PixiExport] captureFrameAt(${timeSec}) returned null, skipping frame ${frame} (total nulls: ${nullFrameCount})`)\n continue\n }\n\n // Draw PIXI canvas → intermediate 2D canvas (resize to output dimensions)\n targetCtx.clearRect(0, 0, width, height)\n targetCtx.drawImage(pixiCanvas, 0, 0, width, height)\n\n // Create VideoFrame\n const timestamp = Math.floor((frame / fps) * 1_000_000) // microseconds\n const frameDuration = Math.floor(1_000_000 / fps)\n const videoFrame = new VideoFrame(targetCanvas, {\n timestamp,\n duration: frameDuration,\n })\n\n // Encode (keyframe every 2 seconds)\n const keyFrame = frame % (fps * 2) === 0\n encoder.encode(videoFrame, { keyFrame })\n videoFrame.close()\n\n // Backpressure: wait if encoder queue is full\n if (encoder.encodeQueueSize > 5) {\n await new Promise<void>((resolve) => {\n encoder.addEventListener('dequeue', () => resolve(), { once: true })\n })\n }\n\n // Report progress (video = 0-90%)\n const percent = Math.round(((frame + 1) / totalFrames) * 90)\n onProgress?.(percent)\n }\n\n console.log('[TRACE-EXPORT] Frame loop DONE, captured', totalFrames - nullFrameCount, 'frames, skipped', nullFrameCount)\n\n // --- Flush video ---\n console.log('[TRACE-EXPORT] Flushing video encoder...')\n await encoder.flush()\n encoder.close()\n console.log('[TRACE-EXPORT] Video encoder flushed and closed')\n\n // --- Encode audio (progress: 90-99%) ---\n if (hasAudio) {\n onProgress?.(91)\n const trimmed = trimAudioBuffer(audioBuffer, trimStart, effectiveTrimEnd)\n await encodeAudio(muxer, trimmed.channels, trimmed.sampleRate, trimmed.numberOfChannels, audioBitrate)\n onProgress?.(99)\n }\n\n // --- Finalize ---\n muxer.finalize()\n const { buffer } = muxer.target\n\n onProgress?.(100)\n\n console.log('[PixiExport] Export complete', {\n size: buffer.byteLength,\n duration: exportDuration,\n frames: totalFrames,\n hasAudio,\n })\n\n return new Blob([buffer], { type: 'video/mp4' })\n}\n"],"names":["isWebCodecsSupported","isAudioEncoderSupported","decodeAudioFromSource","sourceUrl","signal","response","arrayBuffer","audioCtx","trimAudioBuffer","audioBuffer","trimStart","trimEnd","sampleRate","startSample","endSample","trimmedLength","channels","ch","fullChannel","encodeAudio","muxer","numberOfChannels","bitrate","audioEncoderConfig","audioEncoder","chunk","metadata","err","chunkSize","totalSamples","offset","frameSamples","planarData","audioData","resolve","exportWithPixiFrames","provider","options","width","height","fps","audioBitrate","onProgress","videoDuration","MAX_EXPORT_DURATION_SEC","MAX_TOTAL_FRAMES","effectiveTrimEnd","exportDuration","totalFrames","audioPromise","hasAudio","muxerConfig","ArrayBufferTarget","Muxer","codecCandidates","encoderConfig","candidate","cfg","encoder","targetCanvas","targetCtx","frame","timeSec","pixiCanvas","timestamp","frameDuration","videoFrame","keyFrame","percent","trimmed","buffer"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"PixiFrameExporter-DmyELJBR.cjs","sources":["../resources/js/video-engine/adapters/PixiFrameExporter.ts"],"sourcesContent":["/**\n * PIXI Frame-by-Frame Video Exporter\n *\n * Captures each frame from the PIXI canvas (which already has filters applied)\n * and encodes them with WebCodecs + mp4-muxer for exact 1:1 preview-to-export parity.\n *\n * Audio is extracted from the source video, trimmed to match, and muxed alongside\n * the video frames. If the source has no audio or AudioEncoder is unavailable,\n * the export proceeds silently (video-only).\n */\n\nimport { Muxer, ArrayBufferTarget } from 'mp4-muxer'\n\nexport interface PixiExportOptions {\n /** Width of the output video in pixels */\n width: number\n /** Height of the output video in pixels */\n height: number\n /** Frames per second */\n fps: number\n /** Video bitrate in bps (default: 5 Mbps) */\n bitrate?: number\n /** Audio bitrate in bps (default: 128 kbps) */\n audioBitrate?: number\n /** Trim start in seconds (source time, default: 0) */\n trimStart?: number\n /** Trim end in seconds (source time, default: full duration) */\n trimEnd?: number\n /** Source video URL (needed to extract audio) */\n sourceUrl?: string\n /** Progress callback (0-100) */\n onProgress?: (percent: number) => void\n /** Abort signal for cancellation */\n signal?: AbortSignal\n}\n\nexport interface PixiFrameProvider {\n /**\n * Seek the underlying video to `timeSec`, render through PIXI with filters,\n * and return the PIXI canvas with the rendered frame.\n */\n captureFrameAt(timeSec: number): Promise<HTMLCanvasElement | null>\n /** Video duration in seconds (reactive ref) */\n duration: { value: number }\n}\n\n/**\n * Check if the browser supports the required WebCodecs APIs.\n */\nexport function isWebCodecsSupported(): boolean {\n return typeof VideoEncoder !== 'undefined' && typeof VideoFrame !== 'undefined'\n}\n\n/**\n * Check if AudioEncoder is available for AAC encoding.\n */\nfunction isAudioEncoderSupported(): boolean {\n return typeof AudioEncoder !== 'undefined' && typeof AudioData !== 'undefined'\n}\n\n/**\n * Decode the audio from a video source URL using AudioContext.\n * Returns null if the source has no audio or decoding fails.\n */\nasync function decodeAudioFromSource(\n sourceUrl: string,\n signal?: AbortSignal\n): Promise<AudioBuffer | null> {\n try {\n const requestInit: RequestInit = signal ? { signal } : {}\n const response = await fetch(sourceUrl, requestInit)\n if (!response.ok) {\n console.warn('[PixiExport] Failed to fetch source for audio:', response.status)\n return null\n }\n\n const arrayBuffer = await response.arrayBuffer()\n const audioCtx = new AudioContext()\n\n try {\n const audioBuffer = await audioCtx.decodeAudioData(arrayBuffer)\n console.log('[PixiExport] Audio decoded:', {\n channels: audioBuffer.numberOfChannels,\n sampleRate: audioBuffer.sampleRate,\n duration: audioBuffer.duration,\n length: audioBuffer.length,\n })\n return audioBuffer\n } catch (decodeErr) {\n console.warn('[PixiExport] No audio track in source or decode failed:', decodeErr)\n return null\n } finally {\n await audioCtx.close()\n }\n } catch (fetchErr) {\n console.warn('[PixiExport] Could not fetch source for audio extraction:', fetchErr)\n return null\n }\n}\n\n/**\n * Trim an AudioBuffer to a specific time range.\n * Returns a new set of Float32Array channel data for the trimmed region.\n */\nfunction trimAudioBuffer(\n audioBuffer: AudioBuffer,\n trimStart: number,\n trimEnd: number\n): { channels: Float32Array[]; sampleRate: number; numberOfChannels: number } {\n const sampleRate = audioBuffer.sampleRate\n const startSample = Math.max(0, Math.floor(trimStart * sampleRate))\n const endSample = Math.min(audioBuffer.length, Math.ceil(trimEnd * sampleRate))\n const trimmedLength = Math.max(0, endSample - startSample)\n\n const channels: Float32Array[] = []\n for (let ch = 0; ch < audioBuffer.numberOfChannels; ch++) {\n const fullChannel = audioBuffer.getChannelData(ch)\n channels.push(fullChannel.slice(startSample, startSample + trimmedLength))\n }\n\n return {\n channels,\n sampleRate,\n numberOfChannels: audioBuffer.numberOfChannels,\n }\n}\n\n/**\n * Encode trimmed audio data and add chunks to the muxer.\n */\nasync function encodeAudio(\n muxer: Muxer<ArrayBufferTarget>,\n channels: Float32Array[],\n sampleRate: number,\n numberOfChannels: number,\n bitrate: number\n): Promise<void> {\n const audioEncoderConfig: AudioEncoderConfig = {\n codec: 'mp4a.40.2', // AAC-LC\n numberOfChannels,\n sampleRate,\n bitrate,\n }\n\n const support = await AudioEncoder.isConfigSupported(audioEncoderConfig)\n if (!support.supported) {\n console.warn('[PixiExport] AAC AudioEncoder not supported, exporting without audio')\n return\n }\n\n const audioEncoder = new AudioEncoder({\n output: (chunk, metadata) => {\n muxer.addAudioChunk(chunk, metadata)\n },\n error: (err) => {\n console.error('[PixiExport] AudioEncoder error:', err)\n },\n })\n\n audioEncoder.configure(audioEncoderConfig)\n\n // Encode in chunks of 1024 samples (standard AAC frame size)\n const chunkSize = 1024\n const totalSamples = channels[0].length\n\n for (let offset = 0; offset < totalSamples; offset += chunkSize) {\n const frameSamples = Math.min(chunkSize, totalSamples - offset)\n\n // Build planar data buffer: channel0[0..frameSamples] + channel1[0..frameSamples] + ...\n const planarData = new Float32Array(numberOfChannels * frameSamples)\n for (let ch = 0; ch < numberOfChannels; ch++) {\n planarData.set(channels[ch].subarray(offset, offset + frameSamples), ch * frameSamples)\n }\n\n const audioData = new AudioData({\n format: 'f32-planar',\n sampleRate,\n numberOfFrames: frameSamples,\n numberOfChannels,\n timestamp: Math.floor((offset / sampleRate) * 1_000_000), // microseconds\n data: planarData,\n })\n\n audioEncoder.encode(audioData)\n audioData.close()\n\n // Backpressure\n if (audioEncoder.encodeQueueSize > 10) {\n await new Promise<void>((resolve) => {\n audioEncoder.addEventListener('dequeue', () => resolve(), { once: true })\n })\n }\n }\n\n await audioEncoder.flush()\n audioEncoder.close()\n\n console.log('[PixiExport] Audio encoding complete', {\n totalSamples,\n chunks: Math.ceil(totalSamples / chunkSize),\n })\n}\n\n/**\n * Export a video by capturing frames from a PIXI preview (with filters applied)\n * and encoding them into an MP4 file, with audio from the source.\n */\nexport async function exportWithPixiFrames(\n provider: PixiFrameProvider,\n options: PixiExportOptions\n): Promise<Blob> {\n const {\n width,\n height,\n fps,\n bitrate = 5_000_000,\n audioBitrate = 128_000,\n trimStart = 0,\n trimEnd,\n sourceUrl,\n onProgress,\n signal,\n } = options\n\n if (!isWebCodecsSupported()) {\n throw new Error('WebCodecs API is not supported in this browser')\n }\n\n const videoDuration = provider.duration.value\n\n console.log('[TRACE-EXPORT] PixiFrameExporter ENTER', {\n videoDuration,\n trimStart,\n trimEnd,\n fps,\n width,\n height,\n isFiniteDuration: Number.isFinite(videoDuration),\n sourceUrl: sourceUrl?.substring(0, 50),\n })\n\n // Guard against Infinity/NaN duration (common with blob URLs where metadata isn't loaded).\n // Cap at 1 hour as an absolute safety limit.\n const MAX_EXPORT_DURATION_SEC = 3600\n const MAX_TOTAL_FRAMES = MAX_EXPORT_DURATION_SEC * 60 // 216,000 frames at 60fps\n\n if (!Number.isFinite(videoDuration) || videoDuration <= 0) {\n console.error('[TRACE-EXPORT] INVALID DURATION:', videoDuration)\n throw new Error(\n `Invalid video duration: ${videoDuration}. The video metadata may not have loaded. ` +\n 'Please ensure the video is fully loaded before exporting.'\n )\n }\n\n const effectiveTrimEnd = trimEnd ?? videoDuration\n const exportDuration = Math.max(0, effectiveTrimEnd - trimStart)\n\n console.log('[TRACE-EXPORT] PixiFrameExporter calculated:', {\n effectiveTrimEnd,\n exportDuration,\n isFiniteExportDuration: Number.isFinite(exportDuration),\n })\n\n if (!Number.isFinite(exportDuration) || exportDuration <= 0) {\n console.error('[TRACE-EXPORT] INVALID EXPORT DURATION:', exportDuration)\n throw new Error(\n `Invalid export duration: ${exportDuration} (trimStart=${trimStart}, trimEnd=${effectiveTrimEnd}). ` +\n 'Check that the trim points are valid.'\n )\n }\n\n if (exportDuration > MAX_EXPORT_DURATION_SEC) {\n throw new Error(`Export duration (${Math.round(exportDuration)}s) exceeds maximum allowed (${MAX_EXPORT_DURATION_SEC}s).`)\n }\n\n const totalFrames = Math.min(\n Math.max(1, Math.ceil(exportDuration * fps)),\n MAX_TOTAL_FRAMES\n )\n\n console.log('[TRACE-EXPORT] PixiFrameExporter will capture', totalFrames, 'frames over', exportDuration, 'seconds at', fps, 'fps')\n\n // --- Decode audio from source (in parallel with setup) ---\n let audioPromise: Promise<AudioBuffer | null> = Promise.resolve(null)\n if (sourceUrl && isAudioEncoderSupported()) {\n audioPromise = decodeAudioFromSource(sourceUrl, signal)\n }\n\n console.log('[PixiExport] Starting export', {\n width,\n height,\n fps,\n trimStart,\n trimEnd: effectiveTrimEnd,\n exportDuration,\n totalFrames,\n hasSourceUrl: !!sourceUrl,\n })\n\n // --- Muxer (configure audio track conditionally after we know if audio exists) ---\n // We need to know audio params before creating the muxer, so await audio decode first.\n const audioBuffer = await audioPromise\n const hasAudio = !!audioBuffer && audioBuffer.numberOfChannels > 0 && audioBuffer.length > 0\n\n const muxerConfig: ConstructorParameters<typeof Muxer<ArrayBufferTarget>>[0] = {\n target: new ArrayBufferTarget(),\n video: {\n codec: 'avc',\n width,\n height,\n frameRate: fps,\n },\n fastStart: 'in-memory',\n }\n\n if (hasAudio) {\n muxerConfig.audio = {\n codec: 'aac',\n numberOfChannels: audioBuffer.numberOfChannels,\n sampleRate: audioBuffer.sampleRate,\n }\n console.log('[PixiExport] Audio track configured:', {\n channels: audioBuffer.numberOfChannels,\n sampleRate: audioBuffer.sampleRate,\n })\n } else {\n console.log('[PixiExport] No audio track - exporting video only')\n }\n\n const muxer = new Muxer(muxerConfig)\n\n // --- Video Encoder ---\n // Try codecs in order: High 4.0 (best quality at 1080p), Main 4.0, Baseline 4.0,\n // then repeat without hardware acceleration. avc1.42001f (Baseline Level 3.1)\n // only supports up to 1280×720, so 1080p exports need Level 4.0+.\n const codecCandidates: Array<{ codec: string; hw: HardwareAcceleration }> = [\n { codec: 'avc1.640028', hw: 'prefer-hardware' }, // High Profile Level 4.0\n { codec: 'avc1.4d0028', hw: 'prefer-hardware' }, // Main Profile Level 4.0\n { codec: 'avc1.420028', hw: 'prefer-hardware' }, // Baseline Profile Level 4.0\n { codec: 'avc1.640028', hw: 'prefer-software' }, // High 4.0, software fallback\n { codec: 'avc1.4d0028', hw: 'prefer-software' }, // Main 4.0, software fallback\n { codec: 'avc1.420028', hw: 'prefer-software' }, // Baseline 4.0, software fallback\n ]\n\n let encoderConfig: VideoEncoderConfig | null = null\n for (const candidate of codecCandidates) {\n const cfg: VideoEncoderConfig = {\n codec: candidate.codec,\n width,\n height,\n bitrate,\n framerate: fps,\n hardwareAcceleration: candidate.hw,\n }\n const support = await VideoEncoder.isConfigSupported(cfg)\n if (support.supported) {\n encoderConfig = cfg\n console.log('[PixiExport] Using codec:', candidate.codec, 'hw:', candidate.hw)\n break\n }\n }\n\n if (!encoderConfig) {\n throw new Error(\n `No supported VideoEncoder codec found for ${width}×${height}. ` +\n 'Try a lower export resolution or use a browser with H.264 encoding support (Chrome 94+).'\n )\n }\n\n const encoder = new VideoEncoder({\n output: (chunk, metadata) => {\n muxer.addVideoChunk(chunk, metadata)\n },\n error: (err) => {\n console.error('[PixiExport] VideoEncoder error:', err)\n },\n })\n\n encoder.configure(encoderConfig)\n\n // --- Intermediate 2D canvas ---\n const targetCanvas = document.createElement('canvas')\n targetCanvas.width = width\n targetCanvas.height = height\n const targetCtx = targetCanvas.getContext('2d')\n if (!targetCtx) {\n throw new Error('Failed to create 2D context for export target canvas')\n }\n\n // --- Video frame loop (progress: 0-90%) ---\n console.log('[TRACE-EXPORT] Frame loop START, totalFrames:', totalFrames)\n let nullFrameCount = 0\n for (let frame = 0; frame < totalFrames; frame++) {\n if (signal?.aborted) {\n console.log('[TRACE-EXPORT] Frame loop ABORTED at frame', frame)\n encoder.close()\n throw new DOMException('Export aborted', 'AbortError')\n }\n\n // Calculate source time: offset by trimStart\n const timeSec = trimStart + frame / fps\n\n if (frame === 0 || frame === totalFrames - 1 || frame % 10 === 0) {\n console.log(`[TRACE-EXPORT] Capturing frame ${frame}/${totalFrames} at ${timeSec.toFixed(3)}s`)\n }\n\n // Capture the frame from PIXI (seek + render + return canvas)\n const pixiCanvas = await provider.captureFrameAt(timeSec)\n if (!pixiCanvas) {\n nullFrameCount++\n console.warn(`[PixiExport] captureFrameAt(${timeSec}) returned null, skipping frame ${frame} (total nulls: ${nullFrameCount})`)\n continue\n }\n\n // Draw PIXI canvas → intermediate 2D canvas (resize to output dimensions)\n targetCtx.clearRect(0, 0, width, height)\n targetCtx.drawImage(pixiCanvas, 0, 0, width, height)\n\n // Create VideoFrame\n const timestamp = Math.floor((frame / fps) * 1_000_000) // microseconds\n const frameDuration = Math.floor(1_000_000 / fps)\n const videoFrame = new VideoFrame(targetCanvas, {\n timestamp,\n duration: frameDuration,\n })\n\n // Encode (keyframe every 2 seconds)\n const keyFrame = frame % (fps * 2) === 0\n encoder.encode(videoFrame, { keyFrame })\n videoFrame.close()\n\n // Backpressure: wait if encoder queue is full\n if (encoder.encodeQueueSize > 5) {\n await new Promise<void>((resolve) => {\n encoder.addEventListener('dequeue', () => resolve(), { once: true })\n })\n }\n\n // Report progress (video = 0-90%)\n const percent = Math.round(((frame + 1) / totalFrames) * 90)\n onProgress?.(percent)\n }\n\n console.log('[TRACE-EXPORT] Frame loop DONE, captured', totalFrames - nullFrameCount, 'frames, skipped', nullFrameCount)\n\n // --- Flush video ---\n console.log('[TRACE-EXPORT] Flushing video encoder...')\n await encoder.flush()\n encoder.close()\n console.log('[TRACE-EXPORT] Video encoder flushed and closed')\n\n // --- Encode audio (progress: 90-99%) ---\n if (hasAudio) {\n onProgress?.(91)\n const trimmed = trimAudioBuffer(audioBuffer, trimStart, effectiveTrimEnd)\n await encodeAudio(muxer, trimmed.channels, trimmed.sampleRate, trimmed.numberOfChannels, audioBitrate)\n onProgress?.(99)\n }\n\n // --- Finalize ---\n muxer.finalize()\n const { buffer } = muxer.target\n\n onProgress?.(100)\n\n console.log('[PixiExport] Export complete', {\n size: buffer.byteLength,\n duration: exportDuration,\n frames: totalFrames,\n hasAudio,\n })\n\n return new Blob([buffer], { type: 'video/mp4' })\n}\n"],"names":["isWebCodecsSupported","isAudioEncoderSupported","decodeAudioFromSource","sourceUrl","signal","response","arrayBuffer","audioCtx","trimAudioBuffer","audioBuffer","trimStart","trimEnd","sampleRate","startSample","endSample","trimmedLength","channels","ch","fullChannel","encodeAudio","muxer","numberOfChannels","bitrate","audioEncoderConfig","audioEncoder","chunk","metadata","err","chunkSize","totalSamples","offset","frameSamples","planarData","audioData","resolve","exportWithPixiFrames","provider","options","width","height","fps","audioBitrate","onProgress","videoDuration","MAX_EXPORT_DURATION_SEC","MAX_TOTAL_FRAMES","effectiveTrimEnd","exportDuration","totalFrames","audioPromise","hasAudio","muxerConfig","ArrayBufferTarget","Muxer","codecCandidates","encoderConfig","candidate","cfg","encoder","targetCanvas","targetCtx","frame","timeSec","pixiCanvas","timestamp","frameDuration","videoFrame","keyFrame","percent","trimmed","buffer"],"mappings":"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"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { A as q, M as z } from "./index-
|
|
1
|
+
import { A as q, M as z } from "./index-Dj_Ppp4_.js";
|
|
2
2
|
function L() {
|
|
3
3
|
return typeof VideoEncoder < "u" && typeof VideoFrame < "u";
|
|
4
4
|
}
|
|
@@ -196,4 +196,4 @@ export {
|
|
|
196
196
|
H as exportWithPixiFrames,
|
|
197
197
|
L as isWebCodecsSupported
|
|
198
198
|
};
|
|
199
|
-
//# sourceMappingURL=PixiFrameExporter-
|
|
199
|
+
//# sourceMappingURL=PixiFrameExporter-UPjs5JGK.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PixiFrameExporter-FqchRwLH.js","sources":["../resources/js/video-engine/adapters/PixiFrameExporter.ts"],"sourcesContent":["/**\n * PIXI Frame-by-Frame Video Exporter\n *\n * Captures each frame from the PIXI canvas (which already has filters applied)\n * and encodes them with WebCodecs + mp4-muxer for exact 1:1 preview-to-export parity.\n *\n * Audio is extracted from the source video, trimmed to match, and muxed alongside\n * the video frames. If the source has no audio or AudioEncoder is unavailable,\n * the export proceeds silently (video-only).\n */\n\nimport { Muxer, ArrayBufferTarget } from 'mp4-muxer'\n\nexport interface PixiExportOptions {\n /** Width of the output video in pixels */\n width: number\n /** Height of the output video in pixels */\n height: number\n /** Frames per second */\n fps: number\n /** Video bitrate in bps (default: 5 Mbps) */\n bitrate?: number\n /** Audio bitrate in bps (default: 128 kbps) */\n audioBitrate?: number\n /** Trim start in seconds (source time, default: 0) */\n trimStart?: number\n /** Trim end in seconds (source time, default: full duration) */\n trimEnd?: number\n /** Source video URL (needed to extract audio) */\n sourceUrl?: string\n /** Progress callback (0-100) */\n onProgress?: (percent: number) => void\n /** Abort signal for cancellation */\n signal?: AbortSignal\n}\n\nexport interface PixiFrameProvider {\n /**\n * Seek the underlying video to `timeSec`, render through PIXI with filters,\n * and return the PIXI canvas with the rendered frame.\n */\n captureFrameAt(timeSec: number): Promise<HTMLCanvasElement | null>\n /** Video duration in seconds (reactive ref) */\n duration: { value: number }\n}\n\n/**\n * Check if the browser supports the required WebCodecs APIs.\n */\nexport function isWebCodecsSupported(): boolean {\n return typeof VideoEncoder !== 'undefined' && typeof VideoFrame !== 'undefined'\n}\n\n/**\n * Check if AudioEncoder is available for AAC encoding.\n */\nfunction isAudioEncoderSupported(): boolean {\n return typeof AudioEncoder !== 'undefined' && typeof AudioData !== 'undefined'\n}\n\n/**\n * Decode the audio from a video source URL using AudioContext.\n * Returns null if the source has no audio or decoding fails.\n */\nasync function decodeAudioFromSource(\n sourceUrl: string,\n signal?: AbortSignal\n): Promise<AudioBuffer | null> {\n try {\n const requestInit: RequestInit = signal ? { signal } : {}\n const response = await fetch(sourceUrl, requestInit)\n if (!response.ok) {\n console.warn('[PixiExport] Failed to fetch source for audio:', response.status)\n return null\n }\n\n const arrayBuffer = await response.arrayBuffer()\n const audioCtx = new AudioContext()\n\n try {\n const audioBuffer = await audioCtx.decodeAudioData(arrayBuffer)\n console.log('[PixiExport] Audio decoded:', {\n channels: audioBuffer.numberOfChannels,\n sampleRate: audioBuffer.sampleRate,\n duration: audioBuffer.duration,\n length: audioBuffer.length,\n })\n return audioBuffer\n } catch (decodeErr) {\n console.warn('[PixiExport] No audio track in source or decode failed:', decodeErr)\n return null\n } finally {\n await audioCtx.close()\n }\n } catch (fetchErr) {\n console.warn('[PixiExport] Could not fetch source for audio extraction:', fetchErr)\n return null\n }\n}\n\n/**\n * Trim an AudioBuffer to a specific time range.\n * Returns a new set of Float32Array channel data for the trimmed region.\n */\nfunction trimAudioBuffer(\n audioBuffer: AudioBuffer,\n trimStart: number,\n trimEnd: number\n): { channels: Float32Array[]; sampleRate: number; numberOfChannels: number } {\n const sampleRate = audioBuffer.sampleRate\n const startSample = Math.max(0, Math.floor(trimStart * sampleRate))\n const endSample = Math.min(audioBuffer.length, Math.ceil(trimEnd * sampleRate))\n const trimmedLength = Math.max(0, endSample - startSample)\n\n const channels: Float32Array[] = []\n for (let ch = 0; ch < audioBuffer.numberOfChannels; ch++) {\n const fullChannel = audioBuffer.getChannelData(ch)\n channels.push(fullChannel.slice(startSample, startSample + trimmedLength))\n }\n\n return {\n channels,\n sampleRate,\n numberOfChannels: audioBuffer.numberOfChannels,\n }\n}\n\n/**\n * Encode trimmed audio data and add chunks to the muxer.\n */\nasync function encodeAudio(\n muxer: Muxer<ArrayBufferTarget>,\n channels: Float32Array[],\n sampleRate: number,\n numberOfChannels: number,\n bitrate: number\n): Promise<void> {\n const audioEncoderConfig: AudioEncoderConfig = {\n codec: 'mp4a.40.2', // AAC-LC\n numberOfChannels,\n sampleRate,\n bitrate,\n }\n\n const support = await AudioEncoder.isConfigSupported(audioEncoderConfig)\n if (!support.supported) {\n console.warn('[PixiExport] AAC AudioEncoder not supported, exporting without audio')\n return\n }\n\n const audioEncoder = new AudioEncoder({\n output: (chunk, metadata) => {\n muxer.addAudioChunk(chunk, metadata)\n },\n error: (err) => {\n console.error('[PixiExport] AudioEncoder error:', err)\n },\n })\n\n audioEncoder.configure(audioEncoderConfig)\n\n // Encode in chunks of 1024 samples (standard AAC frame size)\n const chunkSize = 1024\n const totalSamples = channels[0].length\n\n for (let offset = 0; offset < totalSamples; offset += chunkSize) {\n const frameSamples = Math.min(chunkSize, totalSamples - offset)\n\n // Build planar data buffer: channel0[0..frameSamples] + channel1[0..frameSamples] + ...\n const planarData = new Float32Array(numberOfChannels * frameSamples)\n for (let ch = 0; ch < numberOfChannels; ch++) {\n planarData.set(channels[ch].subarray(offset, offset + frameSamples), ch * frameSamples)\n }\n\n const audioData = new AudioData({\n format: 'f32-planar',\n sampleRate,\n numberOfFrames: frameSamples,\n numberOfChannels,\n timestamp: Math.floor((offset / sampleRate) * 1_000_000), // microseconds\n data: planarData,\n })\n\n audioEncoder.encode(audioData)\n audioData.close()\n\n // Backpressure\n if (audioEncoder.encodeQueueSize > 10) {\n await new Promise<void>((resolve) => {\n audioEncoder.addEventListener('dequeue', () => resolve(), { once: true })\n })\n }\n }\n\n await audioEncoder.flush()\n audioEncoder.close()\n\n console.log('[PixiExport] Audio encoding complete', {\n totalSamples,\n chunks: Math.ceil(totalSamples / chunkSize),\n })\n}\n\n/**\n * Export a video by capturing frames from a PIXI preview (with filters applied)\n * and encoding them into an MP4 file, with audio from the source.\n */\nexport async function exportWithPixiFrames(\n provider: PixiFrameProvider,\n options: PixiExportOptions\n): Promise<Blob> {\n const {\n width,\n height,\n fps,\n bitrate = 5_000_000,\n audioBitrate = 128_000,\n trimStart = 0,\n trimEnd,\n sourceUrl,\n onProgress,\n signal,\n } = options\n\n if (!isWebCodecsSupported()) {\n throw new Error('WebCodecs API is not supported in this browser')\n }\n\n const videoDuration = provider.duration.value\n\n console.log('[TRACE-EXPORT] PixiFrameExporter ENTER', {\n videoDuration,\n trimStart,\n trimEnd,\n fps,\n width,\n height,\n isFiniteDuration: Number.isFinite(videoDuration),\n sourceUrl: sourceUrl?.substring(0, 50),\n })\n\n // Guard against Infinity/NaN duration (common with blob URLs where metadata isn't loaded).\n // Cap at 1 hour as an absolute safety limit.\n const MAX_EXPORT_DURATION_SEC = 3600\n const MAX_TOTAL_FRAMES = MAX_EXPORT_DURATION_SEC * 60 // 216,000 frames at 60fps\n\n if (!Number.isFinite(videoDuration) || videoDuration <= 0) {\n console.error('[TRACE-EXPORT] INVALID DURATION:', videoDuration)\n throw new Error(\n `Invalid video duration: ${videoDuration}. The video metadata may not have loaded. ` +\n 'Please ensure the video is fully loaded before exporting.'\n )\n }\n\n const effectiveTrimEnd = trimEnd ?? videoDuration\n const exportDuration = Math.max(0, effectiveTrimEnd - trimStart)\n\n console.log('[TRACE-EXPORT] PixiFrameExporter calculated:', {\n effectiveTrimEnd,\n exportDuration,\n isFiniteExportDuration: Number.isFinite(exportDuration),\n })\n\n if (!Number.isFinite(exportDuration) || exportDuration <= 0) {\n console.error('[TRACE-EXPORT] INVALID EXPORT DURATION:', exportDuration)\n throw new Error(\n `Invalid export duration: ${exportDuration} (trimStart=${trimStart}, trimEnd=${effectiveTrimEnd}). ` +\n 'Check that the trim points are valid.'\n )\n }\n\n if (exportDuration > MAX_EXPORT_DURATION_SEC) {\n throw new Error(`Export duration (${Math.round(exportDuration)}s) exceeds maximum allowed (${MAX_EXPORT_DURATION_SEC}s).`)\n }\n\n const totalFrames = Math.min(\n Math.max(1, Math.ceil(exportDuration * fps)),\n MAX_TOTAL_FRAMES\n )\n\n console.log('[TRACE-EXPORT] PixiFrameExporter will capture', totalFrames, 'frames over', exportDuration, 'seconds at', fps, 'fps')\n\n // --- Decode audio from source (in parallel with setup) ---\n let audioPromise: Promise<AudioBuffer | null> = Promise.resolve(null)\n if (sourceUrl && isAudioEncoderSupported()) {\n audioPromise = decodeAudioFromSource(sourceUrl, signal)\n }\n\n console.log('[PixiExport] Starting export', {\n width,\n height,\n fps,\n trimStart,\n trimEnd: effectiveTrimEnd,\n exportDuration,\n totalFrames,\n hasSourceUrl: !!sourceUrl,\n })\n\n // --- Muxer (configure audio track conditionally after we know if audio exists) ---\n // We need to know audio params before creating the muxer, so await audio decode first.\n const audioBuffer = await audioPromise\n const hasAudio = !!audioBuffer && audioBuffer.numberOfChannels > 0 && audioBuffer.length > 0\n\n const muxerConfig: ConstructorParameters<typeof Muxer<ArrayBufferTarget>>[0] = {\n target: new ArrayBufferTarget(),\n video: {\n codec: 'avc',\n width,\n height,\n frameRate: fps,\n },\n fastStart: 'in-memory',\n }\n\n if (hasAudio) {\n muxerConfig.audio = {\n codec: 'aac',\n numberOfChannels: audioBuffer.numberOfChannels,\n sampleRate: audioBuffer.sampleRate,\n }\n console.log('[PixiExport] Audio track configured:', {\n channels: audioBuffer.numberOfChannels,\n sampleRate: audioBuffer.sampleRate,\n })\n } else {\n console.log('[PixiExport] No audio track - exporting video only')\n }\n\n const muxer = new Muxer(muxerConfig)\n\n // --- Video Encoder ---\n // Try codecs in order: High 4.0 (best quality at 1080p), Main 4.0, Baseline 4.0,\n // then repeat without hardware acceleration. avc1.42001f (Baseline Level 3.1)\n // only supports up to 1280×720, so 1080p exports need Level 4.0+.\n const codecCandidates: Array<{ codec: string; hw: HardwareAcceleration }> = [\n { codec: 'avc1.640028', hw: 'prefer-hardware' }, // High Profile Level 4.0\n { codec: 'avc1.4d0028', hw: 'prefer-hardware' }, // Main Profile Level 4.0\n { codec: 'avc1.420028', hw: 'prefer-hardware' }, // Baseline Profile Level 4.0\n { codec: 'avc1.640028', hw: 'prefer-software' }, // High 4.0, software fallback\n { codec: 'avc1.4d0028', hw: 'prefer-software' }, // Main 4.0, software fallback\n { codec: 'avc1.420028', hw: 'prefer-software' }, // Baseline 4.0, software fallback\n ]\n\n let encoderConfig: VideoEncoderConfig | null = null\n for (const candidate of codecCandidates) {\n const cfg: VideoEncoderConfig = {\n codec: candidate.codec,\n width,\n height,\n bitrate,\n framerate: fps,\n hardwareAcceleration: candidate.hw,\n }\n const support = await VideoEncoder.isConfigSupported(cfg)\n if (support.supported) {\n encoderConfig = cfg\n console.log('[PixiExport] Using codec:', candidate.codec, 'hw:', candidate.hw)\n break\n }\n }\n\n if (!encoderConfig) {\n throw new Error(\n `No supported VideoEncoder codec found for ${width}×${height}. ` +\n 'Try a lower export resolution or use a browser with H.264 encoding support (Chrome 94+).'\n )\n }\n\n const encoder = new VideoEncoder({\n output: (chunk, metadata) => {\n muxer.addVideoChunk(chunk, metadata)\n },\n error: (err) => {\n console.error('[PixiExport] VideoEncoder error:', err)\n },\n })\n\n encoder.configure(encoderConfig)\n\n // --- Intermediate 2D canvas ---\n const targetCanvas = document.createElement('canvas')\n targetCanvas.width = width\n targetCanvas.height = height\n const targetCtx = targetCanvas.getContext('2d')\n if (!targetCtx) {\n throw new Error('Failed to create 2D context for export target canvas')\n }\n\n // --- Video frame loop (progress: 0-90%) ---\n console.log('[TRACE-EXPORT] Frame loop START, totalFrames:', totalFrames)\n let nullFrameCount = 0\n for (let frame = 0; frame < totalFrames; frame++) {\n if (signal?.aborted) {\n console.log('[TRACE-EXPORT] Frame loop ABORTED at frame', frame)\n encoder.close()\n throw new DOMException('Export aborted', 'AbortError')\n }\n\n // Calculate source time: offset by trimStart\n const timeSec = trimStart + frame / fps\n\n if (frame === 0 || frame === totalFrames - 1 || frame % 10 === 0) {\n console.log(`[TRACE-EXPORT] Capturing frame ${frame}/${totalFrames} at ${timeSec.toFixed(3)}s`)\n }\n\n // Capture the frame from PIXI (seek + render + return canvas)\n const pixiCanvas = await provider.captureFrameAt(timeSec)\n if (!pixiCanvas) {\n nullFrameCount++\n console.warn(`[PixiExport] captureFrameAt(${timeSec}) returned null, skipping frame ${frame} (total nulls: ${nullFrameCount})`)\n continue\n }\n\n // Draw PIXI canvas → intermediate 2D canvas (resize to output dimensions)\n targetCtx.clearRect(0, 0, width, height)\n targetCtx.drawImage(pixiCanvas, 0, 0, width, height)\n\n // Create VideoFrame\n const timestamp = Math.floor((frame / fps) * 1_000_000) // microseconds\n const frameDuration = Math.floor(1_000_000 / fps)\n const videoFrame = new VideoFrame(targetCanvas, {\n timestamp,\n duration: frameDuration,\n })\n\n // Encode (keyframe every 2 seconds)\n const keyFrame = frame % (fps * 2) === 0\n encoder.encode(videoFrame, { keyFrame })\n videoFrame.close()\n\n // Backpressure: wait if encoder queue is full\n if (encoder.encodeQueueSize > 5) {\n await new Promise<void>((resolve) => {\n encoder.addEventListener('dequeue', () => resolve(), { once: true })\n })\n }\n\n // Report progress (video = 0-90%)\n const percent = Math.round(((frame + 1) / totalFrames) * 90)\n onProgress?.(percent)\n }\n\n console.log('[TRACE-EXPORT] Frame loop DONE, captured', totalFrames - nullFrameCount, 'frames, skipped', nullFrameCount)\n\n // --- Flush video ---\n console.log('[TRACE-EXPORT] Flushing video encoder...')\n await encoder.flush()\n encoder.close()\n console.log('[TRACE-EXPORT] Video encoder flushed and closed')\n\n // --- Encode audio (progress: 90-99%) ---\n if (hasAudio) {\n onProgress?.(91)\n const trimmed = trimAudioBuffer(audioBuffer, trimStart, effectiveTrimEnd)\n await encodeAudio(muxer, trimmed.channels, trimmed.sampleRate, trimmed.numberOfChannels, audioBitrate)\n onProgress?.(99)\n }\n\n // --- Finalize ---\n muxer.finalize()\n const { buffer } = muxer.target\n\n onProgress?.(100)\n\n console.log('[PixiExport] Export complete', {\n size: buffer.byteLength,\n duration: exportDuration,\n frames: totalFrames,\n hasAudio,\n })\n\n return new Blob([buffer], { type: 'video/mp4' })\n}\n"],"names":["isWebCodecsSupported","isAudioEncoderSupported","decodeAudioFromSource","sourceUrl","signal","response","arrayBuffer","audioCtx","trimAudioBuffer","audioBuffer","trimStart","trimEnd","sampleRate","startSample","endSample","trimmedLength","channels","ch","fullChannel","encodeAudio","muxer","numberOfChannels","bitrate","audioEncoderConfig","audioEncoder","chunk","metadata","err","chunkSize","totalSamples","offset","frameSamples","planarData","audioData","resolve","exportWithPixiFrames","provider","options","width","height","fps","audioBitrate","onProgress","videoDuration","MAX_EXPORT_DURATION_SEC","MAX_TOTAL_FRAMES","effectiveTrimEnd","exportDuration","totalFrames","audioPromise","hasAudio","muxerConfig","ArrayBufferTarget","Muxer","codecCandidates","encoderConfig","candidate","cfg","encoder","targetCanvas","targetCtx","frame","timeSec","pixiCanvas","timestamp","frameDuration","videoFrame","keyFrame","percent","trimmed","buffer"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"PixiFrameExporter-UPjs5JGK.js","sources":["../resources/js/video-engine/adapters/PixiFrameExporter.ts"],"sourcesContent":["/**\n * PIXI Frame-by-Frame Video Exporter\n *\n * Captures each frame from the PIXI canvas (which already has filters applied)\n * and encodes them with WebCodecs + mp4-muxer for exact 1:1 preview-to-export parity.\n *\n * Audio is extracted from the source video, trimmed to match, and muxed alongside\n * the video frames. If the source has no audio or AudioEncoder is unavailable,\n * the export proceeds silently (video-only).\n */\n\nimport { Muxer, ArrayBufferTarget } from 'mp4-muxer'\n\nexport interface PixiExportOptions {\n /** Width of the output video in pixels */\n width: number\n /** Height of the output video in pixels */\n height: number\n /** Frames per second */\n fps: number\n /** Video bitrate in bps (default: 5 Mbps) */\n bitrate?: number\n /** Audio bitrate in bps (default: 128 kbps) */\n audioBitrate?: number\n /** Trim start in seconds (source time, default: 0) */\n trimStart?: number\n /** Trim end in seconds (source time, default: full duration) */\n trimEnd?: number\n /** Source video URL (needed to extract audio) */\n sourceUrl?: string\n /** Progress callback (0-100) */\n onProgress?: (percent: number) => void\n /** Abort signal for cancellation */\n signal?: AbortSignal\n}\n\nexport interface PixiFrameProvider {\n /**\n * Seek the underlying video to `timeSec`, render through PIXI with filters,\n * and return the PIXI canvas with the rendered frame.\n */\n captureFrameAt(timeSec: number): Promise<HTMLCanvasElement | null>\n /** Video duration in seconds (reactive ref) */\n duration: { value: number }\n}\n\n/**\n * Check if the browser supports the required WebCodecs APIs.\n */\nexport function isWebCodecsSupported(): boolean {\n return typeof VideoEncoder !== 'undefined' && typeof VideoFrame !== 'undefined'\n}\n\n/**\n * Check if AudioEncoder is available for AAC encoding.\n */\nfunction isAudioEncoderSupported(): boolean {\n return typeof AudioEncoder !== 'undefined' && typeof AudioData !== 'undefined'\n}\n\n/**\n * Decode the audio from a video source URL using AudioContext.\n * Returns null if the source has no audio or decoding fails.\n */\nasync function decodeAudioFromSource(\n sourceUrl: string,\n signal?: AbortSignal\n): Promise<AudioBuffer | null> {\n try {\n const requestInit: RequestInit = signal ? { signal } : {}\n const response = await fetch(sourceUrl, requestInit)\n if (!response.ok) {\n console.warn('[PixiExport] Failed to fetch source for audio:', response.status)\n return null\n }\n\n const arrayBuffer = await response.arrayBuffer()\n const audioCtx = new AudioContext()\n\n try {\n const audioBuffer = await audioCtx.decodeAudioData(arrayBuffer)\n console.log('[PixiExport] Audio decoded:', {\n channels: audioBuffer.numberOfChannels,\n sampleRate: audioBuffer.sampleRate,\n duration: audioBuffer.duration,\n length: audioBuffer.length,\n })\n return audioBuffer\n } catch (decodeErr) {\n console.warn('[PixiExport] No audio track in source or decode failed:', decodeErr)\n return null\n } finally {\n await audioCtx.close()\n }\n } catch (fetchErr) {\n console.warn('[PixiExport] Could not fetch source for audio extraction:', fetchErr)\n return null\n }\n}\n\n/**\n * Trim an AudioBuffer to a specific time range.\n * Returns a new set of Float32Array channel data for the trimmed region.\n */\nfunction trimAudioBuffer(\n audioBuffer: AudioBuffer,\n trimStart: number,\n trimEnd: number\n): { channels: Float32Array[]; sampleRate: number; numberOfChannels: number } {\n const sampleRate = audioBuffer.sampleRate\n const startSample = Math.max(0, Math.floor(trimStart * sampleRate))\n const endSample = Math.min(audioBuffer.length, Math.ceil(trimEnd * sampleRate))\n const trimmedLength = Math.max(0, endSample - startSample)\n\n const channels: Float32Array[] = []\n for (let ch = 0; ch < audioBuffer.numberOfChannels; ch++) {\n const fullChannel = audioBuffer.getChannelData(ch)\n channels.push(fullChannel.slice(startSample, startSample + trimmedLength))\n }\n\n return {\n channels,\n sampleRate,\n numberOfChannels: audioBuffer.numberOfChannels,\n }\n}\n\n/**\n * Encode trimmed audio data and add chunks to the muxer.\n */\nasync function encodeAudio(\n muxer: Muxer<ArrayBufferTarget>,\n channels: Float32Array[],\n sampleRate: number,\n numberOfChannels: number,\n bitrate: number\n): Promise<void> {\n const audioEncoderConfig: AudioEncoderConfig = {\n codec: 'mp4a.40.2', // AAC-LC\n numberOfChannels,\n sampleRate,\n bitrate,\n }\n\n const support = await AudioEncoder.isConfigSupported(audioEncoderConfig)\n if (!support.supported) {\n console.warn('[PixiExport] AAC AudioEncoder not supported, exporting without audio')\n return\n }\n\n const audioEncoder = new AudioEncoder({\n output: (chunk, metadata) => {\n muxer.addAudioChunk(chunk, metadata)\n },\n error: (err) => {\n console.error('[PixiExport] AudioEncoder error:', err)\n },\n })\n\n audioEncoder.configure(audioEncoderConfig)\n\n // Encode in chunks of 1024 samples (standard AAC frame size)\n const chunkSize = 1024\n const totalSamples = channels[0].length\n\n for (let offset = 0; offset < totalSamples; offset += chunkSize) {\n const frameSamples = Math.min(chunkSize, totalSamples - offset)\n\n // Build planar data buffer: channel0[0..frameSamples] + channel1[0..frameSamples] + ...\n const planarData = new Float32Array(numberOfChannels * frameSamples)\n for (let ch = 0; ch < numberOfChannels; ch++) {\n planarData.set(channels[ch].subarray(offset, offset + frameSamples), ch * frameSamples)\n }\n\n const audioData = new AudioData({\n format: 'f32-planar',\n sampleRate,\n numberOfFrames: frameSamples,\n numberOfChannels,\n timestamp: Math.floor((offset / sampleRate) * 1_000_000), // microseconds\n data: planarData,\n })\n\n audioEncoder.encode(audioData)\n audioData.close()\n\n // Backpressure\n if (audioEncoder.encodeQueueSize > 10) {\n await new Promise<void>((resolve) => {\n audioEncoder.addEventListener('dequeue', () => resolve(), { once: true })\n })\n }\n }\n\n await audioEncoder.flush()\n audioEncoder.close()\n\n console.log('[PixiExport] Audio encoding complete', {\n totalSamples,\n chunks: Math.ceil(totalSamples / chunkSize),\n })\n}\n\n/**\n * Export a video by capturing frames from a PIXI preview (with filters applied)\n * and encoding them into an MP4 file, with audio from the source.\n */\nexport async function exportWithPixiFrames(\n provider: PixiFrameProvider,\n options: PixiExportOptions\n): Promise<Blob> {\n const {\n width,\n height,\n fps,\n bitrate = 5_000_000,\n audioBitrate = 128_000,\n trimStart = 0,\n trimEnd,\n sourceUrl,\n onProgress,\n signal,\n } = options\n\n if (!isWebCodecsSupported()) {\n throw new Error('WebCodecs API is not supported in this browser')\n }\n\n const videoDuration = provider.duration.value\n\n console.log('[TRACE-EXPORT] PixiFrameExporter ENTER', {\n videoDuration,\n trimStart,\n trimEnd,\n fps,\n width,\n height,\n isFiniteDuration: Number.isFinite(videoDuration),\n sourceUrl: sourceUrl?.substring(0, 50),\n })\n\n // Guard against Infinity/NaN duration (common with blob URLs where metadata isn't loaded).\n // Cap at 1 hour as an absolute safety limit.\n const MAX_EXPORT_DURATION_SEC = 3600\n const MAX_TOTAL_FRAMES = MAX_EXPORT_DURATION_SEC * 60 // 216,000 frames at 60fps\n\n if (!Number.isFinite(videoDuration) || videoDuration <= 0) {\n console.error('[TRACE-EXPORT] INVALID DURATION:', videoDuration)\n throw new Error(\n `Invalid video duration: ${videoDuration}. The video metadata may not have loaded. ` +\n 'Please ensure the video is fully loaded before exporting.'\n )\n }\n\n const effectiveTrimEnd = trimEnd ?? videoDuration\n const exportDuration = Math.max(0, effectiveTrimEnd - trimStart)\n\n console.log('[TRACE-EXPORT] PixiFrameExporter calculated:', {\n effectiveTrimEnd,\n exportDuration,\n isFiniteExportDuration: Number.isFinite(exportDuration),\n })\n\n if (!Number.isFinite(exportDuration) || exportDuration <= 0) {\n console.error('[TRACE-EXPORT] INVALID EXPORT DURATION:', exportDuration)\n throw new Error(\n `Invalid export duration: ${exportDuration} (trimStart=${trimStart}, trimEnd=${effectiveTrimEnd}). ` +\n 'Check that the trim points are valid.'\n )\n }\n\n if (exportDuration > MAX_EXPORT_DURATION_SEC) {\n throw new Error(`Export duration (${Math.round(exportDuration)}s) exceeds maximum allowed (${MAX_EXPORT_DURATION_SEC}s).`)\n }\n\n const totalFrames = Math.min(\n Math.max(1, Math.ceil(exportDuration * fps)),\n MAX_TOTAL_FRAMES\n )\n\n console.log('[TRACE-EXPORT] PixiFrameExporter will capture', totalFrames, 'frames over', exportDuration, 'seconds at', fps, 'fps')\n\n // --- Decode audio from source (in parallel with setup) ---\n let audioPromise: Promise<AudioBuffer | null> = Promise.resolve(null)\n if (sourceUrl && isAudioEncoderSupported()) {\n audioPromise = decodeAudioFromSource(sourceUrl, signal)\n }\n\n console.log('[PixiExport] Starting export', {\n width,\n height,\n fps,\n trimStart,\n trimEnd: effectiveTrimEnd,\n exportDuration,\n totalFrames,\n hasSourceUrl: !!sourceUrl,\n })\n\n // --- Muxer (configure audio track conditionally after we know if audio exists) ---\n // We need to know audio params before creating the muxer, so await audio decode first.\n const audioBuffer = await audioPromise\n const hasAudio = !!audioBuffer && audioBuffer.numberOfChannels > 0 && audioBuffer.length > 0\n\n const muxerConfig: ConstructorParameters<typeof Muxer<ArrayBufferTarget>>[0] = {\n target: new ArrayBufferTarget(),\n video: {\n codec: 'avc',\n width,\n height,\n frameRate: fps,\n },\n fastStart: 'in-memory',\n }\n\n if (hasAudio) {\n muxerConfig.audio = {\n codec: 'aac',\n numberOfChannels: audioBuffer.numberOfChannels,\n sampleRate: audioBuffer.sampleRate,\n }\n console.log('[PixiExport] Audio track configured:', {\n channels: audioBuffer.numberOfChannels,\n sampleRate: audioBuffer.sampleRate,\n })\n } else {\n console.log('[PixiExport] No audio track - exporting video only')\n }\n\n const muxer = new Muxer(muxerConfig)\n\n // --- Video Encoder ---\n // Try codecs in order: High 4.0 (best quality at 1080p), Main 4.0, Baseline 4.0,\n // then repeat without hardware acceleration. avc1.42001f (Baseline Level 3.1)\n // only supports up to 1280×720, so 1080p exports need Level 4.0+.\n const codecCandidates: Array<{ codec: string; hw: HardwareAcceleration }> = [\n { codec: 'avc1.640028', hw: 'prefer-hardware' }, // High Profile Level 4.0\n { codec: 'avc1.4d0028', hw: 'prefer-hardware' }, // Main Profile Level 4.0\n { codec: 'avc1.420028', hw: 'prefer-hardware' }, // Baseline Profile Level 4.0\n { codec: 'avc1.640028', hw: 'prefer-software' }, // High 4.0, software fallback\n { codec: 'avc1.4d0028', hw: 'prefer-software' }, // Main 4.0, software fallback\n { codec: 'avc1.420028', hw: 'prefer-software' }, // Baseline 4.0, software fallback\n ]\n\n let encoderConfig: VideoEncoderConfig | null = null\n for (const candidate of codecCandidates) {\n const cfg: VideoEncoderConfig = {\n codec: candidate.codec,\n width,\n height,\n bitrate,\n framerate: fps,\n hardwareAcceleration: candidate.hw,\n }\n const support = await VideoEncoder.isConfigSupported(cfg)\n if (support.supported) {\n encoderConfig = cfg\n console.log('[PixiExport] Using codec:', candidate.codec, 'hw:', candidate.hw)\n break\n }\n }\n\n if (!encoderConfig) {\n throw new Error(\n `No supported VideoEncoder codec found for ${width}×${height}. ` +\n 'Try a lower export resolution or use a browser with H.264 encoding support (Chrome 94+).'\n )\n }\n\n const encoder = new VideoEncoder({\n output: (chunk, metadata) => {\n muxer.addVideoChunk(chunk, metadata)\n },\n error: (err) => {\n console.error('[PixiExport] VideoEncoder error:', err)\n },\n })\n\n encoder.configure(encoderConfig)\n\n // --- Intermediate 2D canvas ---\n const targetCanvas = document.createElement('canvas')\n targetCanvas.width = width\n targetCanvas.height = height\n const targetCtx = targetCanvas.getContext('2d')\n if (!targetCtx) {\n throw new Error('Failed to create 2D context for export target canvas')\n }\n\n // --- Video frame loop (progress: 0-90%) ---\n console.log('[TRACE-EXPORT] Frame loop START, totalFrames:', totalFrames)\n let nullFrameCount = 0\n for (let frame = 0; frame < totalFrames; frame++) {\n if (signal?.aborted) {\n console.log('[TRACE-EXPORT] Frame loop ABORTED at frame', frame)\n encoder.close()\n throw new DOMException('Export aborted', 'AbortError')\n }\n\n // Calculate source time: offset by trimStart\n const timeSec = trimStart + frame / fps\n\n if (frame === 0 || frame === totalFrames - 1 || frame % 10 === 0) {\n console.log(`[TRACE-EXPORT] Capturing frame ${frame}/${totalFrames} at ${timeSec.toFixed(3)}s`)\n }\n\n // Capture the frame from PIXI (seek + render + return canvas)\n const pixiCanvas = await provider.captureFrameAt(timeSec)\n if (!pixiCanvas) {\n nullFrameCount++\n console.warn(`[PixiExport] captureFrameAt(${timeSec}) returned null, skipping frame ${frame} (total nulls: ${nullFrameCount})`)\n continue\n }\n\n // Draw PIXI canvas → intermediate 2D canvas (resize to output dimensions)\n targetCtx.clearRect(0, 0, width, height)\n targetCtx.drawImage(pixiCanvas, 0, 0, width, height)\n\n // Create VideoFrame\n const timestamp = Math.floor((frame / fps) * 1_000_000) // microseconds\n const frameDuration = Math.floor(1_000_000 / fps)\n const videoFrame = new VideoFrame(targetCanvas, {\n timestamp,\n duration: frameDuration,\n })\n\n // Encode (keyframe every 2 seconds)\n const keyFrame = frame % (fps * 2) === 0\n encoder.encode(videoFrame, { keyFrame })\n videoFrame.close()\n\n // Backpressure: wait if encoder queue is full\n if (encoder.encodeQueueSize > 5) {\n await new Promise<void>((resolve) => {\n encoder.addEventListener('dequeue', () => resolve(), { once: true })\n })\n }\n\n // Report progress (video = 0-90%)\n const percent = Math.round(((frame + 1) / totalFrames) * 90)\n onProgress?.(percent)\n }\n\n console.log('[TRACE-EXPORT] Frame loop DONE, captured', totalFrames - nullFrameCount, 'frames, skipped', nullFrameCount)\n\n // --- Flush video ---\n console.log('[TRACE-EXPORT] Flushing video encoder...')\n await encoder.flush()\n encoder.close()\n console.log('[TRACE-EXPORT] Video encoder flushed and closed')\n\n // --- Encode audio (progress: 90-99%) ---\n if (hasAudio) {\n onProgress?.(91)\n const trimmed = trimAudioBuffer(audioBuffer, trimStart, effectiveTrimEnd)\n await encodeAudio(muxer, trimmed.channels, trimmed.sampleRate, trimmed.numberOfChannels, audioBitrate)\n onProgress?.(99)\n }\n\n // --- Finalize ---\n muxer.finalize()\n const { buffer } = muxer.target\n\n onProgress?.(100)\n\n console.log('[PixiExport] Export complete', {\n size: buffer.byteLength,\n duration: exportDuration,\n frames: totalFrames,\n hasAudio,\n })\n\n return new Blob([buffer], { type: 'video/mp4' })\n}\n"],"names":["isWebCodecsSupported","isAudioEncoderSupported","decodeAudioFromSource","sourceUrl","signal","response","arrayBuffer","audioCtx","trimAudioBuffer","audioBuffer","trimStart","trimEnd","sampleRate","startSample","endSample","trimmedLength","channels","ch","fullChannel","encodeAudio","muxer","numberOfChannels","bitrate","audioEncoderConfig","audioEncoder","chunk","metadata","err","chunkSize","totalSamples","offset","frameSamples","planarData","audioData","resolve","exportWithPixiFrames","provider","options","width","height","fps","audioBitrate","onProgress","videoDuration","MAX_EXPORT_DURATION_SEC","MAX_TOTAL_FRAMES","effectiveTrimEnd","exportDuration","totalFrames","audioPromise","hasAudio","muxerConfig","ArrayBufferTarget","Muxer","codecCandidates","encoderConfig","candidate","cfg","encoder","targetCanvas","targetCtx","frame","timeSec","pixiCanvas","timestamp","frameDuration","videoFrame","keyFrame","percent","trimmed","buffer"],"mappings":"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"}
|
|
@@ -4,7 +4,7 @@ var W = (s, e, t) => we(s, typeof e != "symbol" ? e + "" : e, t);
|
|
|
4
4
|
import * as b from "pixi-filters";
|
|
5
5
|
import * as w from "pixi.js";
|
|
6
6
|
import { GlProgram as Se } from "pixi.js";
|
|
7
|
-
class
|
|
7
|
+
class V {
|
|
8
8
|
constructor() {
|
|
9
9
|
this._events = /* @__PURE__ */ new Map();
|
|
10
10
|
}
|
|
@@ -34,7 +34,7 @@ class U {
|
|
|
34
34
|
e ? this._events.delete(e) : this._events.clear();
|
|
35
35
|
}
|
|
36
36
|
}
|
|
37
|
-
class ve extends
|
|
37
|
+
class ve extends V {
|
|
38
38
|
constructor() {
|
|
39
39
|
super(), this._state = {
|
|
40
40
|
// Image state
|
|
@@ -192,7 +192,7 @@ class ve extends U {
|
|
|
192
192
|
function ke() {
|
|
193
193
|
return new ve();
|
|
194
194
|
}
|
|
195
|
-
class Pe extends
|
|
195
|
+
class Pe extends V {
|
|
196
196
|
constructor() {
|
|
197
197
|
super(), this.app = null, this.sprite = null, this.originalTexture = null, this.baseTexture = null, this.fitScale = 1, this.zoom = 1, this._container = null, this._mountPromise = null;
|
|
198
198
|
}
|
|
@@ -369,8 +369,8 @@ class Pe extends U {
|
|
|
369
369
|
else if (typeof M.getContext == "function") {
|
|
370
370
|
const B = document.createElement("canvas");
|
|
371
371
|
B.width = M.width, B.height = M.height;
|
|
372
|
-
const
|
|
373
|
-
|
|
372
|
+
const U = B.getContext("2d");
|
|
373
|
+
U && (U.drawImage(M, 0, 0), F = B.toDataURL(`image/${e}`, t));
|
|
374
374
|
}
|
|
375
375
|
return F || null;
|
|
376
376
|
} catch {
|
|
@@ -470,7 +470,7 @@ const Me = {
|
|
|
470
470
|
stylize: ["stylize"]
|
|
471
471
|
// ascii, crt, crossHatch, dot, emboss
|
|
472
472
|
};
|
|
473
|
-
class Fe extends
|
|
473
|
+
class Fe extends V {
|
|
474
474
|
constructor(e, t) {
|
|
475
475
|
super(), this.state = e, this.renderer = t, this.instances = {}, this._filterRegistry = null;
|
|
476
476
|
}
|
|
@@ -647,7 +647,7 @@ class Fe extends U {
|
|
|
647
647
|
return this.instances[e] || null;
|
|
648
648
|
}
|
|
649
649
|
}
|
|
650
|
-
const X = class X extends
|
|
650
|
+
const X = class X extends V {
|
|
651
651
|
constructor(e, t) {
|
|
652
652
|
super(), this.state = e, this.renderer = t, this._overlayCanvas = null, this._isDragging = !1, this._dragStart = null, this._dragMode = null, this._startRect = null, this._hoverMode = null, this._lastAutoZoomCheck = 0, this.HANDLE_SIZE = 14, this.EDGE_HIT_PAD = 10, this._onPointerDown = this._handlePointerDown.bind(this), this._onPointerMove = this._handlePointerMove.bind(this), this._onPointerUp = this._handlePointerUp.bind(this);
|
|
653
653
|
}
|
|
@@ -948,8 +948,8 @@ const X = class X extends U {
|
|
|
948
948
|
y: Math.round(T.y + _),
|
|
949
949
|
width: h,
|
|
950
950
|
height: p
|
|
951
|
-
} : { x: f, y: _, width: h, height: p }, B = this.state.get("crop.shape") || "free",
|
|
952
|
-
return this.disable(), this.state.set("crop.appliedRect", F), this.state.set("crop.appliedShape", B), this.state.set("crop.appliedAspect",
|
|
951
|
+
} : { x: f, y: _, width: h, height: p }, B = this.state.get("crop.shape") || "free", U = this.state.get("crop.aspect") || "free";
|
|
952
|
+
return this.disable(), this.state.set("crop.appliedRect", F), this.state.set("crop.appliedShape", B), this.state.set("crop.appliedAspect", U), this.emit("applied", { width: h, height: p }), { texture: x, preservedZoom: o };
|
|
953
953
|
}
|
|
954
954
|
/**
|
|
955
955
|
* Apply a crop from saved texture-pixel coordinates (for state rehydration).
|
|
@@ -1261,7 +1261,7 @@ function J({ label: s, icon: e, active: t = !1, onClick: i }) {
|
|
|
1261
1261
|
r.classList.toggle("active", a);
|
|
1262
1262
|
}, r;
|
|
1263
1263
|
}
|
|
1264
|
-
const fe = '<svg viewBox="0 0 512 512" width="20" height="20"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="48" d="M328 112L184 256l144 144"/></svg>', me = '<svg viewBox="0 0 512 512" width="20" height="20"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="48" d="M184 112l144 144-144 144"/></svg>', ze = '<svg viewBox="0 0 512 512" width="20" height="20"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="M221.09 64a157.09 157.09 0 10157.09 157.09A157.1 157.1 0 00221.09 64z"/><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="M338.29 338.29L448 448M256 184v74m-37-37h74"/></svg>', Re = '<svg viewBox="0 0 512 512" width="20" height="20"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="M221.09 64a157.09 157.09 0 10157.09 157.09A157.1 157.1 0 00221.09 64z"/><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="M338.29 338.29L448 448M184 221h74"/></svg>', Ne = '<svg viewBox="0 0 512 512" width="20" height="20"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="M432 320v112H320M80 192V80h112M320 80h112v112M192 432H80V320"/></svg>', Te = '<svg viewBox="0 0 512 512" width="20" height="20"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="M262.29 192.31a64 64 0 1057.4 57.4 64.13 64.13 0 00-57.4-57.4z"/><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="M416.39 256a154.34 154.34 0 01-1.53 20.79l45.21 35.46a10.81 10.81 0 012.45 13.75l-42.77 74a10.81 10.81 0 01-13.14 4.59l-44.9-18.08a16.11 16.11 0 00-15.17 1.75A164.48 164.48 0 01325 400.8a15.94 15.94 0 00-8.82 12.14l-6.73 47.89a11.08 11.08 0 01-10.68 9.17h-85.54a11.11 11.11 0 01-10.69-8.87l-6.72-47.82a16.07 16.07 0 00-9-12.22 155.3 155.3 0 01-21.46-12.57 16 16 0 00-15.11-1.71l-44.89 18.07a10.81 10.81 0 01-13.14-4.58l-42.77-74a10.8 10.8 0 012.45-13.75l38.21-30a16.05 16.05 0 006-14.08c-.36-4.17-.58-8.33-.58-12.5s.21-8.27.58-12.35a16 16 0 00-6.07-13.94l-38.19-30A10.81 10.81 0 0149.48 186l42.77-74a10.81 10.81 0 0113.14-4.59l44.9 18.08a16.11 16.11 0 0015.17-1.75A164.48 164.48 0 01187 111.2a15.94 15.94 0 008.82-12.14l6.73-47.89A11.08 11.08 0 01213.23 42h85.54a11.11 11.11 0 0110.69 8.87l6.72 47.82a16.07 16.07 0 009 12.22 155.3 155.3 0 0121.46 12.57 16 16 0 0015.11 1.71l44.89-18.07a10.81 10.81 0 0113.14 4.58l42.77 74a10.8 10.8 0 01-2.45 13.75l-38.21 30a16.05 16.05 0 00-6.05 14.08c.33 4.14.55 8.3.55 12.47z"/></svg>', Ie = '<svg viewBox="0 0 512 512" width="20" height="20"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="M400 320c0 88.37-55.63 144-144 144s-144-55.63-144-144c0-94.83 103.23-222.85 134.89-259.88a12 12 0 0118.23 0C296.77 97.15 400 225.17 400 320z"/><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="M344 328a72 72 0 01-72 72"/></svg>', Ee = '<svg viewBox="0 0 512 512" width="20" height="20"><path fill="none" stroke="currentColor" stroke-miterlimit="10" stroke-width="32" d="M430.11 347.9c-6.6-6.1-16.3-7.6-24.6-9-11.5-1.9-15.9-4-22.6-10-14.3-12.7-14.3-31.1 0-43.8l30.3-26.9c46.4-41 46.4-108.2 0-149.2-34.2-30.1-80.1-45-127.8-45-55.7 0-113.9 20.3-158.8 60.1-83.5 73.8-83.5 194.7 0 268.5 41.5 36.7 97.5 55 152.9 55.4h1.7c55.4 0 110-17.9 148.8-52.4 14.4-12.7 11.99-36.6.1-47.7z"/><circle cx="144" cy="208" r="32"/><circle cx="152" cy="311" r="32"/><circle cx="224" cy="144" r="32"/><circle cx="256" cy="367" r="32"/><circle cx="328" cy="144" r="32"/></svg>', Xe = '<svg viewBox="0 0 512 512" width="20" height="20"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="M259.92 262.91L216.4 149.77a9 9 0 00-16.8 0l-43.52 113.14a9 9 0 01-5.17 5.17L37.77 311.6a9 9 0 000 16.8l113.14 43.52a9 9 0 015.17 5.17l43.52 113.14a9 9 0 0016.8 0l43.52-113.14a9 9 0 015.17-5.17l113.14-43.52a9 9 0 000-16.8l-113.14-43.52a9 9 0 01-5.17-5.17zM108 68L88 16 68 68 16 88l52 20 20 52 20-52 52-20-52-20zM426.67 117.33L400 48l-26.67 69.33L304 144l69.33 26.67L400 240l26.67-69.33L496 144l-69.33-26.67z"/></svg>', Ye = '<svg viewBox="0 0 512 512" width="20" height="20"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="M176 112l80-80 80 80M255.98 32l.02 448M176 400l80 80 80-80M400 176l80 80-80 80M112 176l-80 80 80 80M32 256h448"/></svg>', Be = '<svg viewBox="0 0 512 512" width="20" height="20"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="M315.27 33L96 304h128l-31.51 173.23a2.36 2.36 0 002.33 2.77h0a2.36 2.36 0 001.89-.95L416 208H288l31.66-173.25a2.45 2.45 0 00-2.44-2.75h0a2.42 2.42 0 00-1.95 1z"/></svg>', ge = '<svg viewBox="0 0 512 512" width="20" height="20"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="M144 48v272a48 48 0 0048 48h272"/><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="M368 464V192a48 48 0 00-48-48H48"/></svg>', Oe = '<svg viewBox="0 0 512 512" width="20" height="20"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="M380.93 57.37A32 32 0 00358.3 48H94.22A46.21 46.21 0 0048 94.22v323.56A46.21 46.21 0 0094.22 464h323.56A46.36 46.36 0 00464 417.78V153.7a32 32 0 00-9.37-22.63zM256 416a64 64 0 1164-64 63.92 63.92 0 01-64 64zm48-224H112a16 16 0 01-16-16v-64a16 16 0 0116-16h192a16 16 0 0116 16v64a16 16 0 01-16 16z"/></svg>', te = '<svg viewBox="0 0 512 512" width="20" height="20"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="M368 368L144 144M368 144L144 368"/></svg>', be = '<svg viewBox="0 0 512 512" width="20" height="20"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="M320 146s24.36-12-64-12a160 160 0 10160 160"/><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="M256 58l80 80-80 80"/></svg>', Le = '<svg viewBox="0 0 512 512" width="20" height="20"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="M112 112l20 320c.95 18.49 14.4 32 32 32h184c17.67 0 30.87-13.51 32-32l20-320"/><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-miterlimit="10" stroke-width="32" d="M80 112h352"/><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="M192 112V72h0a23.93 23.93 0 0124-24h80a23.93 23.93 0 0124 24h0v40M256 176v224M184 176l8 224M328 176l-8 224"/></svg>', De = '<svg viewBox="0 0 512 512" width="20" height="20"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="M64 192v-72a40 40 0 0140-40h75.89a40 40 0 0122.19 6.72l27.84 18.56a40 40 0 0022.19 6.72H408a40 40 0 0140 40v40"/><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="M479.9 226.55L463.68 392a40 40 0 01-39.93 40H88.25a40 40 0 01-39.93-40L32.1 226.55A32 32 0 0164 192h384.1a32 32 0 0131.8 34.55z"/></svg>', ee = '<svg viewBox="0 0 512 512" width="20" height="20"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="M416 128L192 384l-96-96"/></svg>',
|
|
1264
|
+
const fe = '<svg viewBox="0 0 512 512" width="20" height="20"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="48" d="M328 112L184 256l144 144"/></svg>', me = '<svg viewBox="0 0 512 512" width="20" height="20"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="48" d="M184 112l144 144-144 144"/></svg>', ze = '<svg viewBox="0 0 512 512" width="20" height="20"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="M221.09 64a157.09 157.09 0 10157.09 157.09A157.1 157.1 0 00221.09 64z"/><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="M338.29 338.29L448 448M256 184v74m-37-37h74"/></svg>', Re = '<svg viewBox="0 0 512 512" width="20" height="20"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="M221.09 64a157.09 157.09 0 10157.09 157.09A157.1 157.1 0 00221.09 64z"/><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="M338.29 338.29L448 448M184 221h74"/></svg>', Ne = '<svg viewBox="0 0 512 512" width="20" height="20"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="M432 320v112H320M80 192V80h112M320 80h112v112M192 432H80V320"/></svg>', Te = '<svg viewBox="0 0 512 512" width="20" height="20"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="M262.29 192.31a64 64 0 1057.4 57.4 64.13 64.13 0 00-57.4-57.4z"/><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="M416.39 256a154.34 154.34 0 01-1.53 20.79l45.21 35.46a10.81 10.81 0 012.45 13.75l-42.77 74a10.81 10.81 0 01-13.14 4.59l-44.9-18.08a16.11 16.11 0 00-15.17 1.75A164.48 164.48 0 01325 400.8a15.94 15.94 0 00-8.82 12.14l-6.73 47.89a11.08 11.08 0 01-10.68 9.17h-85.54a11.11 11.11 0 01-10.69-8.87l-6.72-47.82a16.07 16.07 0 00-9-12.22 155.3 155.3 0 01-21.46-12.57 16 16 0 00-15.11-1.71l-44.89 18.07a10.81 10.81 0 01-13.14-4.58l-42.77-74a10.8 10.8 0 012.45-13.75l38.21-30a16.05 16.05 0 006-14.08c-.36-4.17-.58-8.33-.58-12.5s.21-8.27.58-12.35a16 16 0 00-6.07-13.94l-38.19-30A10.81 10.81 0 0149.48 186l42.77-74a10.81 10.81 0 0113.14-4.59l44.9 18.08a16.11 16.11 0 0015.17-1.75A164.48 164.48 0 01187 111.2a15.94 15.94 0 008.82-12.14l6.73-47.89A11.08 11.08 0 01213.23 42h85.54a11.11 11.11 0 0110.69 8.87l6.72 47.82a16.07 16.07 0 009 12.22 155.3 155.3 0 0121.46 12.57 16 16 0 0015.11 1.71l44.89-18.07a10.81 10.81 0 0113.14 4.58l42.77 74a10.8 10.8 0 01-2.45 13.75l-38.21 30a16.05 16.05 0 00-6.05 14.08c.33 4.14.55 8.3.55 12.47z"/></svg>', Ie = '<svg viewBox="0 0 512 512" width="20" height="20"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="M400 320c0 88.37-55.63 144-144 144s-144-55.63-144-144c0-94.83 103.23-222.85 134.89-259.88a12 12 0 0118.23 0C296.77 97.15 400 225.17 400 320z"/><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="M344 328a72 72 0 01-72 72"/></svg>', Ee = '<svg viewBox="0 0 512 512" width="20" height="20"><path fill="none" stroke="currentColor" stroke-miterlimit="10" stroke-width="32" d="M430.11 347.9c-6.6-6.1-16.3-7.6-24.6-9-11.5-1.9-15.9-4-22.6-10-14.3-12.7-14.3-31.1 0-43.8l30.3-26.9c46.4-41 46.4-108.2 0-149.2-34.2-30.1-80.1-45-127.8-45-55.7 0-113.9 20.3-158.8 60.1-83.5 73.8-83.5 194.7 0 268.5 41.5 36.7 97.5 55 152.9 55.4h1.7c55.4 0 110-17.9 148.8-52.4 14.4-12.7 11.99-36.6.1-47.7z"/><circle cx="144" cy="208" r="32"/><circle cx="152" cy="311" r="32"/><circle cx="224" cy="144" r="32"/><circle cx="256" cy="367" r="32"/><circle cx="328" cy="144" r="32"/></svg>', Xe = '<svg viewBox="0 0 512 512" width="20" height="20"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="M259.92 262.91L216.4 149.77a9 9 0 00-16.8 0l-43.52 113.14a9 9 0 01-5.17 5.17L37.77 311.6a9 9 0 000 16.8l113.14 43.52a9 9 0 015.17 5.17l43.52 113.14a9 9 0 0016.8 0l43.52-113.14a9 9 0 015.17-5.17l113.14-43.52a9 9 0 000-16.8l-113.14-43.52a9 9 0 01-5.17-5.17zM108 68L88 16 68 68 16 88l52 20 20 52 20-52 52-20-52-20zM426.67 117.33L400 48l-26.67 69.33L304 144l69.33 26.67L400 240l26.67-69.33L496 144l-69.33-26.67z"/></svg>', Ye = '<svg viewBox="0 0 512 512" width="20" height="20"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="M176 112l80-80 80 80M255.98 32l.02 448M176 400l80 80 80-80M400 176l80 80-80 80M112 176l-80 80 80 80M32 256h448"/></svg>', Be = '<svg viewBox="0 0 512 512" width="20" height="20"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="M315.27 33L96 304h128l-31.51 173.23a2.36 2.36 0 002.33 2.77h0a2.36 2.36 0 001.89-.95L416 208H288l31.66-173.25a2.45 2.45 0 00-2.44-2.75h0a2.42 2.42 0 00-1.95 1z"/></svg>', ge = '<svg viewBox="0 0 512 512" width="20" height="20"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="M144 48v272a48 48 0 0048 48h272"/><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="M368 464V192a48 48 0 00-48-48H48"/></svg>', Oe = '<svg viewBox="0 0 512 512" width="20" height="20"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="M380.93 57.37A32 32 0 00358.3 48H94.22A46.21 46.21 0 0048 94.22v323.56A46.21 46.21 0 0094.22 464h323.56A46.36 46.36 0 00464 417.78V153.7a32 32 0 00-9.37-22.63zM256 416a64 64 0 1164-64 63.92 63.92 0 01-64 64zm48-224H112a16 16 0 01-16-16v-64a16 16 0 0116-16h192a16 16 0 0116 16v64a16 16 0 01-16 16z"/></svg>', te = '<svg viewBox="0 0 512 512" width="20" height="20"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="M368 368L144 144M368 144L144 368"/></svg>', be = '<svg viewBox="0 0 512 512" width="20" height="20"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="M320 146s24.36-12-64-12a160 160 0 10160 160"/><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="M256 58l80 80-80 80"/></svg>', Le = '<svg viewBox="0 0 512 512" width="20" height="20"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="M112 112l20 320c.95 18.49 14.4 32 32 32h184c17.67 0 30.87-13.51 32-32l20-320"/><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-miterlimit="10" stroke-width="32" d="M80 112h352"/><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="M192 112V72h0a23.93 23.93 0 0124-24h80a23.93 23.93 0 0124 24h0v40M256 176v224M184 176l8 224M328 176l-8 224"/></svg>', De = '<svg viewBox="0 0 512 512" width="20" height="20"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="M64 192v-72a40 40 0 0140-40h75.89a40 40 0 0122.19 6.72l27.84 18.56a40 40 0 0022.19 6.72H408a40 40 0 0140 40v40"/><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="M479.9 226.55L463.68 392a40 40 0 01-39.93 40H88.25a40 40 0 01-39.93-40L32.1 226.55A32 32 0 0164 192h384.1a32 32 0 0131.8 34.55z"/></svg>', ee = '<svg viewBox="0 0 512 512" width="20" height="20"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="M416 128L192 384l-96-96"/></svg>', Ve = '<svg viewBox="0 0 512 512" width="20" height="20"><rect x="64" y="64" width="384" height="384" rx="48" fill="none" stroke="currentColor" stroke-linejoin="round" stroke-width="32"/></svg>', Ue = '<svg viewBox="0 0 512 512" width="20" height="20"><circle cx="256" cy="256" r="208" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32"/></svg>', je = '<svg viewBox="0 0 512 512" width="20" height="20"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="M80 96h64l64 320h64l64-160h96"/></svg>', ie = '<svg viewBox="0 0 512 512" width="20" height="20"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-miterlimit="10" stroke-width="32" d="M256 48v48M256 416v48M403.08 108.92l-33.94 33.94M142.86 369.14l-33.94 33.94M464 256h-48M96 256H48M403.08 403.08l-33.94-33.94M142.86 142.86l-33.94-33.94"/><circle cx="256" cy="256" r="80" fill="none" stroke="currentColor" stroke-linecap="round" stroke-miterlimit="10" stroke-width="32"/></svg>', re = '<svg viewBox="0 0 512 512" width="20" height="20"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="M160 136c0-30.62 4.51-61.61 16-88C99.57 81.27 48 159.32 48 248c0 119.29 96.71 216 216 216 88.68 0 166.73-51.57 200-128-26.39 11.49-57.38 16-88 16-119.29 0-216-96.71-216-216z"/></svg>', qe = '<svg viewBox="0 0 512 512" width="20" height="20"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="M436 80H76a44.05 44.05 0 00-44 44v264a44.05 44.05 0 0044 44h360a44.05 44.05 0 0044-44V124a44.05 44.05 0 00-44-44z"/><circle cx="256" cy="256" r="80" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32"/><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="M220 80v56M292 80v56M220 376v56M292 376v56M80 144h56M80 224h56M80 304h56M376 144h56M376 224h56M376 304h56"/></svg>';
|
|
1265
1265
|
class He {
|
|
1266
1266
|
constructor(e, t) {
|
|
1267
1267
|
this.state = e, this.editor = t, this.element = null, this._unsubscribers = [];
|
|
@@ -2188,8 +2188,8 @@ class Qe {
|
|
|
2188
2188
|
}
|
|
2189
2189
|
const Je = [
|
|
2190
2190
|
{ id: "free", name: "Free", icon: je },
|
|
2191
|
-
{ id: "square", name: "Square", icon:
|
|
2192
|
-
{ id: "circle", name: "Circle", icon:
|
|
2191
|
+
{ id: "square", name: "Square", icon: Ve },
|
|
2192
|
+
{ id: "circle", name: "Circle", icon: Ue }
|
|
2193
2193
|
], et = [
|
|
2194
2194
|
{ id: "free", name: "Free" },
|
|
2195
2195
|
{ id: "1:1", name: "1:1" },
|
|
@@ -2579,7 +2579,7 @@ function rt(s) {
|
|
|
2579
2579
|
const e = G[s];
|
|
2580
2580
|
return e ? { ...e } : { ...G.free };
|
|
2581
2581
|
}
|
|
2582
|
-
class ri extends
|
|
2582
|
+
class ri extends V {
|
|
2583
2583
|
/**
|
|
2584
2584
|
* Create a new VanillaImageEditor
|
|
2585
2585
|
* @param {HTMLElement} container - Container element to mount the editor
|
|
@@ -2851,7 +2851,7 @@ class ri extends U {
|
|
|
2851
2851
|
* @returns {string|null} Data URL
|
|
2852
2852
|
*/
|
|
2853
2853
|
exportImage(e = "png", t = 0.92) {
|
|
2854
|
-
return this._renderer.exportImage(e, t);
|
|
2854
|
+
return this._state.get("crop.rect") && !this._cropManager.apply() ? null : this._renderer.exportImage(e, t);
|
|
2855
2855
|
}
|
|
2856
2856
|
/**
|
|
2857
2857
|
* Save the image (triggers download and emits 'save' event)
|
|
@@ -6938,7 +6938,7 @@ m({
|
|
|
6938
6938
|
}
|
|
6939
6939
|
]
|
|
6940
6940
|
});
|
|
6941
|
-
const { GlowFilter:
|
|
6941
|
+
const { GlowFilter: Vt } = b;
|
|
6942
6942
|
m({
|
|
6943
6943
|
id: "glow",
|
|
6944
6944
|
name: "Glow",
|
|
@@ -6947,7 +6947,7 @@ m({
|
|
|
6947
6947
|
// Create an instance of the GlowFilter with the provided parameters
|
|
6948
6948
|
createFilter: (s) => {
|
|
6949
6949
|
try {
|
|
6950
|
-
const e = parseInt(s.color.replace("#", "0x"), 16), t = new
|
|
6950
|
+
const e = parseInt(s.color.replace("#", "0x"), 16), t = new Vt({
|
|
6951
6951
|
distance: s.distance || 10,
|
|
6952
6952
|
outerStrength: s.outerStrength || 4,
|
|
6953
6953
|
innerStrength: s.innerStrength || 0,
|
|
@@ -7073,7 +7073,7 @@ m({
|
|
|
7073
7073
|
}
|
|
7074
7074
|
]
|
|
7075
7075
|
});
|
|
7076
|
-
const { GodrayFilter:
|
|
7076
|
+
const { GodrayFilter: Ut } = b;
|
|
7077
7077
|
m({
|
|
7078
7078
|
id: "godray",
|
|
7079
7079
|
name: "Godray",
|
|
@@ -7082,7 +7082,7 @@ m({
|
|
|
7082
7082
|
// Create an instance of the GodrayFilter with the provided parameters
|
|
7083
7083
|
createFilter: (s) => {
|
|
7084
7084
|
try {
|
|
7085
|
-
const e = new
|
|
7085
|
+
const e = new Ut({
|
|
7086
7086
|
angle: s.angle || 30,
|
|
7087
7087
|
parallel: s.parallel ?? !0,
|
|
7088
7088
|
center: {
|
|
@@ -8640,4 +8640,4 @@ export {
|
|
|
8640
8640
|
oi as i,
|
|
8641
8641
|
m as r
|
|
8642
8642
|
};
|
|
8643
|
-
//# sourceMappingURL=editor-
|
|
8643
|
+
//# sourceMappingURL=editor-2Q72CZjK.js.map
|