@meframe/core 0.0.2 → 0.0.3

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.
Files changed (137) hide show
  1. package/dist/Meframe.d.ts.map +1 -1
  2. package/dist/Meframe.js +2 -1
  3. package/dist/Meframe.js.map +1 -1
  4. package/dist/config/defaults.d.ts.map +1 -1
  5. package/dist/config/defaults.js +2 -1
  6. package/dist/config/defaults.js.map +1 -1
  7. package/dist/config/types.d.ts +3 -0
  8. package/dist/config/types.d.ts.map +1 -1
  9. package/dist/orchestrator/Orchestrator.d.ts.map +1 -1
  10. package/dist/orchestrator/Orchestrator.js +2 -1
  11. package/dist/orchestrator/Orchestrator.js.map +1 -1
  12. package/dist/orchestrator/types.d.ts +1 -0
  13. package/dist/orchestrator/types.d.ts.map +1 -1
  14. package/dist/stages/compose/types.d.ts +2 -1
  15. package/dist/stages/compose/types.d.ts.map +1 -1
  16. package/dist/stages/demux/MP4Demuxer.d.ts +0 -1
  17. package/dist/stages/demux/MP4Demuxer.d.ts.map +1 -1
  18. package/dist/utils/time-utils.d.ts +3 -2
  19. package/dist/utils/time-utils.d.ts.map +1 -1
  20. package/dist/utils/time-utils.js +2 -1
  21. package/dist/utils/time-utils.js.map +1 -1
  22. package/dist/vite-plugin.d.ts +5 -3
  23. package/dist/vite-plugin.d.ts.map +1 -1
  24. package/dist/vite-plugin.js +109 -52
  25. package/dist/vite-plugin.js.map +1 -1
  26. package/dist/worker/WorkerPool.d.ts +7 -0
  27. package/dist/worker/WorkerPool.d.ts.map +1 -1
  28. package/dist/worker/WorkerPool.js +29 -5
  29. package/dist/worker/WorkerPool.js.map +1 -1
  30. package/dist/{stages/demux → workers}/MP4Demuxer.js +4 -13
  31. package/dist/workers/MP4Demuxer.js.map +1 -0
  32. package/dist/workers/WorkerChannel.js +486 -0
  33. package/dist/workers/WorkerChannel.js.map +1 -0
  34. package/dist/{assets/video-demux.worker-D019I7GQ.js → workers/mp4box.all.js} +4 -912
  35. package/dist/workers/mp4box.all.js.map +1 -0
  36. package/dist/{assets/audio-compose.worker-nGVvHD5Q.js → workers/stages/compose/audio-compose.worker.js} +7 -481
  37. package/dist/workers/stages/compose/audio-compose.worker.js.map +1 -0
  38. package/dist/{assets/video-compose.worker-DPzsC21d.js → workers/stages/compose/video-compose.worker.js} +7 -481
  39. package/dist/workers/stages/compose/video-compose.worker.js.map +1 -0
  40. package/dist/{assets/decode.worker-DpWHsc7R.js → workers/stages/decode/decode.worker.js} +7 -481
  41. package/dist/workers/stages/decode/decode.worker.js.map +1 -0
  42. package/dist/{stages → workers/stages}/demux/audio-demux.worker.js +184 -4
  43. package/dist/workers/stages/demux/audio-demux.worker.js.map +1 -0
  44. package/dist/{stages → workers/stages}/demux/video-demux.worker.js +2 -3
  45. package/dist/workers/stages/demux/video-demux.worker.js.map +1 -0
  46. package/dist/{stages → workers/stages}/encode/encode.worker.js +238 -4
  47. package/dist/workers/stages/encode/encode.worker.js.map +1 -0
  48. package/dist/{stages/mux/MP4Muxer.js → workers/stages/mux/mux.worker.js} +244 -5
  49. package/dist/workers/stages/mux/mux.worker.js.map +1 -0
  50. package/package.json +21 -21
  51. package/dist/assets/audio-compose.worker-nGVvHD5Q.js.map +0 -1
  52. package/dist/assets/audio-demux.worker-xwWBtbAe.js +0 -8299
  53. package/dist/assets/audio-demux.worker-xwWBtbAe.js.map +0 -1
  54. package/dist/assets/decode.worker-DpWHsc7R.js.map +0 -1
  55. package/dist/assets/encode.worker-nfOb3kw6.js +0 -1026
  56. package/dist/assets/encode.worker-nfOb3kw6.js.map +0 -1
  57. package/dist/assets/mux.worker-uEMQY066.js +0 -8019
  58. package/dist/assets/mux.worker-uEMQY066.js.map +0 -1
  59. package/dist/assets/video-compose.worker-DPzsC21d.js.map +0 -1
  60. package/dist/assets/video-demux.worker-D019I7GQ.js.map +0 -1
  61. package/dist/model/types.js +0 -5
  62. package/dist/model/types.js.map +0 -1
  63. package/dist/plugins/BackpressureMonitor.js +0 -62
  64. package/dist/plugins/BackpressureMonitor.js.map +0 -1
  65. package/dist/stages/compose/AudioDucker.js +0 -161
  66. package/dist/stages/compose/AudioDucker.js.map +0 -1
  67. package/dist/stages/compose/AudioMixer.js +0 -373
  68. package/dist/stages/compose/AudioMixer.js.map +0 -1
  69. package/dist/stages/compose/FilterProcessor.js +0 -226
  70. package/dist/stages/compose/FilterProcessor.js.map +0 -1
  71. package/dist/stages/compose/LayerRenderer.js +0 -215
  72. package/dist/stages/compose/LayerRenderer.js.map +0 -1
  73. package/dist/stages/compose/TransitionProcessor.js +0 -189
  74. package/dist/stages/compose/TransitionProcessor.js.map +0 -1
  75. package/dist/stages/compose/VideoComposer.js +0 -186
  76. package/dist/stages/compose/VideoComposer.js.map +0 -1
  77. package/dist/stages/compose/audio-compose.worker.d.ts +0 -79
  78. package/dist/stages/compose/audio-compose.worker.d.ts.map +0 -1
  79. package/dist/stages/compose/audio-compose.worker.js +0 -540
  80. package/dist/stages/compose/audio-compose.worker.js.map +0 -1
  81. package/dist/stages/compose/audio-compose.worker2.js +0 -5
  82. package/dist/stages/compose/audio-compose.worker2.js.map +0 -1
  83. package/dist/stages/compose/video-compose.worker.d.ts +0 -60
  84. package/dist/stages/compose/video-compose.worker.d.ts.map +0 -1
  85. package/dist/stages/compose/video-compose.worker.js +0 -379
  86. package/dist/stages/compose/video-compose.worker.js.map +0 -1
  87. package/dist/stages/compose/video-compose.worker2.js +0 -5
  88. package/dist/stages/compose/video-compose.worker2.js.map +0 -1
  89. package/dist/stages/decode/AudioChunkDecoder.js +0 -82
  90. package/dist/stages/decode/AudioChunkDecoder.js.map +0 -1
  91. package/dist/stages/decode/BaseDecoder.js +0 -130
  92. package/dist/stages/decode/BaseDecoder.js.map +0 -1
  93. package/dist/stages/decode/VideoChunkDecoder.js +0 -199
  94. package/dist/stages/decode/VideoChunkDecoder.js.map +0 -1
  95. package/dist/stages/decode/decode.worker.d.ts +0 -70
  96. package/dist/stages/decode/decode.worker.d.ts.map +0 -1
  97. package/dist/stages/decode/decode.worker.js +0 -423
  98. package/dist/stages/decode/decode.worker.js.map +0 -1
  99. package/dist/stages/decode/decode.worker2.js +0 -5
  100. package/dist/stages/decode/decode.worker2.js.map +0 -1
  101. package/dist/stages/demux/MP3FrameParser.js +0 -186
  102. package/dist/stages/demux/MP3FrameParser.js.map +0 -1
  103. package/dist/stages/demux/MP4Demuxer.js.map +0 -1
  104. package/dist/stages/demux/audio-demux.worker.d.ts +0 -51
  105. package/dist/stages/demux/audio-demux.worker.d.ts.map +0 -1
  106. package/dist/stages/demux/audio-demux.worker.js.map +0 -1
  107. package/dist/stages/demux/audio-demux.worker2.js +0 -5
  108. package/dist/stages/demux/audio-demux.worker2.js.map +0 -1
  109. package/dist/stages/demux/video-demux.worker.d.ts +0 -51
  110. package/dist/stages/demux/video-demux.worker.d.ts.map +0 -1
  111. package/dist/stages/demux/video-demux.worker.js.map +0 -1
  112. package/dist/stages/demux/video-demux.worker2.js +0 -5
  113. package/dist/stages/demux/video-demux.worker2.js.map +0 -1
  114. package/dist/stages/encode/AudioChunkEncoder.js +0 -37
  115. package/dist/stages/encode/AudioChunkEncoder.js.map +0 -1
  116. package/dist/stages/encode/BaseEncoder.js +0 -164
  117. package/dist/stages/encode/BaseEncoder.js.map +0 -1
  118. package/dist/stages/encode/VideoChunkEncoder.js +0 -50
  119. package/dist/stages/encode/VideoChunkEncoder.js.map +0 -1
  120. package/dist/stages/encode/encode.worker.d.ts +0 -3
  121. package/dist/stages/encode/encode.worker.d.ts.map +0 -1
  122. package/dist/stages/encode/encode.worker.js.map +0 -1
  123. package/dist/stages/encode/encode.worker2.js +0 -5
  124. package/dist/stages/encode/encode.worker2.js.map +0 -1
  125. package/dist/stages/mux/MP4Muxer.js.map +0 -1
  126. package/dist/stages/mux/mux.worker.d.ts +0 -65
  127. package/dist/stages/mux/mux.worker.d.ts.map +0 -1
  128. package/dist/stages/mux/mux.worker.js +0 -219
  129. package/dist/stages/mux/mux.worker.js.map +0 -1
  130. package/dist/stages/mux/mux.worker2.js +0 -5
  131. package/dist/stages/mux/mux.worker2.js.map +0 -1
  132. package/dist/stages/mux/utils.js +0 -34
  133. package/dist/stages/mux/utils.js.map +0 -1
  134. package/dist/worker/worker-registry.d.ts +0 -12
  135. package/dist/worker/worker-registry.d.ts.map +0 -1
  136. package/dist/worker/worker-registry.js +0 -20
  137. package/dist/worker/worker-registry.js.map +0 -1
@@ -1 +0,0 @@
1
- {"version":3,"file":"AudioMixer.js","sources":["../../../src/stages/compose/AudioMixer.ts"],"sourcesContent":["import type {\n AudioComposeConfig,\n AudioTrack,\n AudioTrackConfig,\n FadeConfig,\n MixRequest,\n MixResult,\n} from './types';\nimport type { AudioDucker } from './AudioDucker';\n\nexport class AudioMixer {\n config: AudioComposeConfig;\n private tracksMap = new Map<string, AudioTrack>();\n\n constructor(config: AudioComposeConfig) {\n this.config = config;\n }\n\n getConfig(): AudioComposeConfig {\n return { ...this.config };\n }\n\n updateConfig(update: Partial<AudioComposeConfig>): void {\n this.config = { ...this.config, ...update };\n }\n\n get tracks(): AudioTrack[] {\n return Array.from(this.tracksMap.values());\n }\n\n createMixStream(ducker?: AudioDucker): TransformStream<MixRequest, MixResult> {\n return new TransformStream<MixRequest, MixResult>(\n {\n transform: async (request, controller) => {\n try {\n const frameCount = this.getFrameCount(request.durationUs);\n\n if (ducker && request.duckingConfig?.enabled && frameCount > 0) {\n const envelope = await ducker.generateDuckingEnvelope(request.tracks, frameCount);\n\n for (const track of request.tracks) {\n if (request.duckingConfig.targetTracks.includes(track.trackId)) {\n track.duckingEnvelope = ducker.applyEnvelopeToVolume(1, envelope);\n }\n }\n }\n\n const result = await this.mixTracks(request, frameCount);\n controller.enqueue(result);\n } catch (error) {\n controller.error(error);\n }\n },\n },\n {\n highWaterMark: 2,\n size: () => 1,\n }\n );\n }\n\n async mixTracks(request: MixRequest, precomputedFrameCount?: number): Promise<MixResult> {\n const tracks = request.tracks ?? [];\n const frameCount = precomputedFrameCount ?? this.getFrameCount(request.durationUs);\n\n const requestedChannelCount = this.config.numberOfChannels ?? 0;\n const inferredChannelCount = tracks.reduce((max, track) => {\n const trackChannels =\n track?.numberOfChannels ??\n track?.audioData?.numberOfChannels ??\n this.config.numberOfChannels ??\n 0;\n return trackChannels > max ? trackChannels : max;\n }, 0);\n const channelCount =\n requestedChannelCount > 0 ? requestedChannelCount : Math.max(inferredChannelCount, 1);\n\n const outputChannels: Float32Array[] = Array.from({ length: channelCount }, () => {\n return new Float32Array(frameCount);\n });\n\n for (const track of tracks) {\n if (!track) {\n continue;\n }\n\n const resolvedAudioData = track.audioData;\n if (!resolvedAudioData) {\n continue;\n }\n\n this.mixTrackIntoOutput(\n outputChannels,\n {\n ...track,\n audioData: resolvedAudioData,\n numberOfChannels:\n track.numberOfChannels ??\n resolvedAudioData.numberOfChannels ??\n this.config.numberOfChannels,\n sampleRate: track.sampleRate ?? resolvedAudioData.sampleRate ?? this.config.sampleRate,\n },\n request.timeUs,\n frameCount\n );\n }\n\n const { peakLevel, rmsLevel } = this.limitAndMeasure(outputChannels);\n const audioData = this.createAudioData(outputChannels, request.timeUs);\n\n return {\n audioData,\n timeUs: request.timeUs,\n durationUs: request.durationUs,\n peakLevel,\n rmsLevel,\n };\n }\n\n addTrack(track: AudioTrack): void {\n this.tracksMap.set(track.id, track);\n }\n\n removeTrack(trackId: string): void {\n this.tracksMap.delete(trackId);\n }\n\n updateTrack(\n trackId: string,\n patch: Partial<AudioTrack> & { config?: Partial<AudioTrackConfig> }\n ): void {\n const track = this.tracksMap.get(trackId);\n if (!track) {\n return;\n }\n\n const { config, ...rest } = patch;\n if (config) {\n Object.assign(track.config, config);\n }\n Object.assign(track, rest);\n }\n\n private mixTrackIntoOutput(\n outputChannels: Float32Array[],\n track: MixRequest['tracks'][0],\n mixStartUs: number,\n totalFrameCount: number\n ): void {\n if (totalFrameCount === 0) {\n track.audioData.close();\n return;\n }\n\n if (track.sampleRate !== this.config.sampleRate) {\n track.audioData.close();\n throw new Error('AudioMixer: sample rate mismatch');\n }\n\n const trackChannelCount = track.audioData.numberOfChannels ?? track.numberOfChannels ?? 0;\n if (trackChannelCount === 0) {\n track.audioData.close();\n return;\n }\n\n const trackChannels = this.extractChannels(track.audioData);\n if (trackChannels.length === 0) {\n track.audioData.close();\n return;\n }\n const trackFrameCount = track.audioData.numberOfFrames;\n\n if (trackFrameCount === 0) {\n track.audioData.close();\n return;\n }\n\n const timestampUs = track.audioData.timestamp ?? mixStartUs;\n const deltaUs = timestampUs - mixStartUs;\n let outputOffsetFrames = Math.round((deltaUs / 1_000_000) * this.config.sampleRate);\n let sourceOffsetFrames = 0;\n\n if (outputOffsetFrames < 0) {\n sourceOffsetFrames = Math.min(trackFrameCount, -outputOffsetFrames);\n outputOffsetFrames = 0;\n }\n\n if (outputOffsetFrames >= totalFrameCount) {\n track.audioData.close();\n return;\n }\n\n const availableFrames = Math.min(\n trackFrameCount - sourceOffsetFrames,\n totalFrameCount - outputOffsetFrames\n );\n\n if (availableFrames <= 0) {\n track.audioData.close();\n return;\n }\n\n const gains = this.buildGainEnvelope(\n track,\n availableFrames,\n outputOffsetFrames,\n sourceOffsetFrames,\n trackFrameCount\n );\n\n const destinationChannelCount = outputChannels.length;\n const sourceChannelCount = trackChannels.length;\n\n for (let channelIndex = 0; channelIndex < destinationChannelCount; channelIndex++) {\n const destination = outputChannels[channelIndex];\n const source = trackChannels[channelIndex] ?? trackChannels[sourceChannelCount - 1];\n if (!destination || !source) continue;\n\n for (let frameIndex = 0; frameIndex < availableFrames; frameIndex++) {\n const sample = source[sourceOffsetFrames + frameIndex] ?? 0;\n const gain = gains[frameIndex] ?? 0;\n destination[outputOffsetFrames + frameIndex] =\n (destination[outputOffsetFrames + frameIndex] ?? 0) + sample * gain;\n }\n }\n\n track.audioData.close();\n }\n\n private buildGainEnvelope(\n track: MixRequest['tracks'][0],\n length: number,\n outputOffsetFrames: number,\n sourceOffsetFrames: number,\n trackFrameCount: number\n ): Float32Array {\n const gains = new Float32Array(length);\n const baseVolume = typeof track.config.volume === 'number' ? track.config.volume : 1;\n gains.fill(baseVolume);\n\n const fadeInSamples = this.getFadeSampleCount(track.config.fadeIn);\n const fadeOutSamples = this.getFadeSampleCount(track.config.fadeOut);\n const clipDurationSamples = this.getClipSampleCount(track.config.durationUs) || trackFrameCount;\n const trackStartFrame = this.computeTrackStartFrame(track);\n\n for (let i = 0; i < length; i++) {\n const envelopeIndex = outputOffsetFrames + i;\n const absoluteFrame = trackStartFrame + sourceOffsetFrames + i;\n let gain = baseVolume;\n\n if (fadeInSamples > 0 && absoluteFrame < fadeInSamples) {\n const progress = Math.min(1, absoluteFrame / fadeInSamples);\n gain *= this.getCurveValue(progress, track.config.fadeIn?.curve);\n }\n\n if (fadeOutSamples > 0 && clipDurationSamples > 0) {\n const fadeStart = Math.max(0, clipDurationSamples - fadeOutSamples);\n if (absoluteFrame >= fadeStart) {\n const progress = Math.min(1, (absoluteFrame - fadeStart) / fadeOutSamples);\n const remaining = Math.max(0, 1 - progress);\n gain *= this.getCurveValue(remaining, track.config.fadeOut?.curve);\n }\n }\n\n if (\n track.duckingEnvelope &&\n envelopeIndex < track.duckingEnvelope.length &&\n envelopeIndex >= 0\n ) {\n gain *= track.duckingEnvelope[envelopeIndex] ?? 1;\n }\n\n gains[i] = gain;\n }\n\n return gains;\n }\n\n private extractChannels(audioData: AudioData): Float32Array[] {\n const configuredChannels = this.config.numberOfChannels ?? 0;\n const channelCount = audioData.numberOfChannels ?? configuredChannels;\n const frameCount = audioData.numberOfFrames;\n const format: string = (audioData as any).format ?? 'f32';\n\n if (!channelCount || !frameCount) {\n return [];\n }\n\n const toFloat = (value: number): number => value / 32768;\n\n const zeroChannels = (): Float32Array[] =>\n Array.from(\n { length: configuredChannels || channelCount },\n () => new Float32Array(frameCount)\n );\n\n if (format === 'f32') {\n const interleaved = new Float32Array(frameCount * channelCount);\n audioData.copyTo(interleaved, { format: 'f32', planeIndex: 0 });\n const channels = zeroChannels();\n for (let frame = 0; frame < frameCount; frame++) {\n const offset = frame * channelCount;\n for (let channel = 0; channel < channels.length; channel++) {\n const channelArray = channels[channel];\n if (!channelArray) continue;\n const sourceChannel = channel < channelCount ? channel : channelCount - 1;\n channelArray[frame] = interleaved[offset + sourceChannel] ?? 0;\n }\n }\n return channels;\n }\n\n if (format === 's16') {\n const interleaved = new Int16Array(frameCount * channelCount);\n audioData.copyTo(interleaved, { format: 's16', planeIndex: 0 });\n const channels = zeroChannels();\n for (let frame = 0; frame < frameCount; frame++) {\n const offset = frame * channelCount;\n for (let channel = 0; channel < channels.length; channel++) {\n const channelArray = channels[channel];\n if (!channelArray) continue;\n const sourceChannel = channel < channelCount ? channel : channelCount - 1;\n channelArray[frame] = toFloat(interleaved[offset + sourceChannel] ?? 0);\n }\n }\n return channels;\n }\n\n if (format === 'f32-planar') {\n const channels = zeroChannels();\n for (let channel = 0; channel < channels.length; channel++) {\n const channelArray = channels[channel];\n if (!channelArray) continue;\n const sourceChannel = channel < channelCount ? channel : channelCount - 1;\n audioData.copyTo(channelArray, { planeIndex: sourceChannel, format: 'f32-planar' });\n }\n return channels;\n }\n\n if (format === 's16-planar') {\n const tmp = new Int16Array(frameCount);\n const channels = zeroChannels();\n for (let channel = 0; channel < channels.length; channel++) {\n const channelArray = channels[channel];\n if (!channelArray) continue;\n const sourceChannel = channel < channelCount ? channel : channelCount - 1;\n audioData.copyTo(tmp, { planeIndex: sourceChannel, format: 's16-planar' as any });\n for (let i = 0; i < frameCount; i++) {\n channelArray[i] = toFloat(tmp[i] ?? 0);\n }\n }\n return channels;\n }\n\n const channels = zeroChannels();\n for (let channel = 0; channel < channels.length; channel++) {\n const channelArray = channels[channel];\n if (!channelArray) continue;\n const sourceChannel = channel < channelCount ? channel : channelCount - 1;\n audioData.copyTo(channelArray, { planeIndex: sourceChannel });\n }\n return channels;\n }\n\n private limitAndMeasure(channels: Float32Array[]): { peakLevel: number; rmsLevel: number } {\n let peak = 0;\n let sumSquares = 0;\n let samples = 0;\n\n for (const channel of channels) {\n for (let i = 0; i < channel.length; i++) {\n let sample = channel[i] ?? 0;\n if (sample > 1) {\n sample = 1;\n } else if (sample < -1) {\n sample = -1;\n }\n\n channel[i] = sample;\n\n const absSample = Math.abs(sample);\n if (absSample > peak) {\n peak = absSample;\n }\n\n sumSquares += sample * sample;\n samples++;\n }\n }\n\n const rmsLevel = samples > 0 ? Math.sqrt(sumSquares / samples) : 0;\n\n return {\n peakLevel: peak,\n rmsLevel,\n };\n }\n\n private createAudioData(channels: Float32Array[], timestampUs: number): AudioData {\n const configuredChannels = this.config.numberOfChannels ?? 0;\n const inferredChannels = channels.length;\n const numberOfChannels = (inferredChannels > 0 ? inferredChannels : configuredChannels) || 1;\n const numberOfFrames = channels[0]?.length ?? 0;\n // console.log('>>>>>>>>>>>>>> createAudioData', channels, this.config, numberOfChannels, numberOfFrames);\n if (numberOfFrames === 0) {\n return new AudioData({\n format: 'f32',\n sampleRate: this.config.sampleRate,\n numberOfFrames: 0,\n numberOfChannels,\n timestamp: timestampUs,\n data: new Float32Array(0),\n });\n }\n\n const interleaved = new Float32Array(numberOfFrames * numberOfChannels);\n\n for (let frame = 0; frame < numberOfFrames; frame++) {\n for (let channel = 0; channel < numberOfChannels; channel++) {\n const sourceChannel = channels[channel] ?? channels[channels.length - 1];\n interleaved[frame * numberOfChannels + channel] = sourceChannel?.[frame] ?? 0;\n }\n }\n\n return new AudioData({\n format: 'f32',\n sampleRate: this.config.sampleRate,\n numberOfFrames,\n numberOfChannels,\n timestamp: timestampUs,\n data: interleaved,\n });\n }\n\n private getFrameCount(durationUs: number): number {\n if (durationUs <= 0) {\n return 0;\n }\n\n return Math.ceil((durationUs / 1_000_000) * this.config.sampleRate);\n }\n\n private getFadeSampleCount(fade?: FadeConfig): number {\n if (!fade || fade.durationUs <= 0) {\n return 0;\n }\n\n return Math.round((fade.durationUs / 1_000_000) * this.config.sampleRate);\n }\n\n private getClipSampleCount(durationUs?: number): number {\n if (!durationUs || durationUs <= 0) {\n return 0;\n }\n\n return Math.round((durationUs / 1_000_000) * this.config.sampleRate);\n }\n\n private computeTrackStartFrame(track: MixRequest['tracks'][0]): number {\n const audioTimestamp = track.audioData.timestamp ?? track.config.startTimeUs;\n const relativeUs = audioTimestamp - track.config.startTimeUs;\n const relativeFrames = Math.round((relativeUs / 1_000_000) * this.config.sampleRate);\n return relativeFrames > 0 ? relativeFrames : 0;\n }\n\n private getCurveValue(progress: number, curve: FadeConfig['curve'] = 'linear'): number {\n const clamped = Math.min(Math.max(progress, 0), 1);\n\n switch (curve) {\n case 'exponential':\n return clamped * clamped;\n case 'logarithmic':\n return Math.log10(clamped * 9 + 1);\n case 'cosine':\n return (1 - Math.cos(clamped * Math.PI)) / 2;\n default:\n return clamped;\n }\n }\n}\n"],"names":["channels"],"mappings":"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"}
@@ -1,226 +0,0 @@
1
- class FilterProcessor {
2
- filterCache = /* @__PURE__ */ new Map();
3
- /**
4
- * Apply filters to canvas context
5
- * Combines multiple filters into a single CSS filter string for performance
6
- */
7
- applyFilters(ctx, filters) {
8
- if (!filters || filters.length === 0) {
9
- ctx.filter = "none";
10
- return;
11
- }
12
- const cacheKey = this.generateCacheKey(filters);
13
- let filterString = this.filterCache.get(cacheKey);
14
- if (!filterString) {
15
- filterString = this.buildFilterString(filters);
16
- this.filterCache.set(cacheKey, filterString);
17
- }
18
- ctx.filter = filterString;
19
- }
20
- /**
21
- * Build CSS filter string from filter array
22
- */
23
- buildFilterString(filters) {
24
- const filterStrings = [];
25
- for (const filter of filters) {
26
- const filterStr = this.buildSingleFilter(filter);
27
- if (filterStr) {
28
- filterStrings.push(filterStr);
29
- }
30
- }
31
- return filterStrings.length > 0 ? filterStrings.join(" ") : "none";
32
- }
33
- buildSingleFilter(filter) {
34
- switch (filter.type) {
35
- case "blur":
36
- return `blur(${filter.value ?? 0}px)`;
37
- case "brightness":
38
- return `brightness(${filter.value ?? 1})`;
39
- case "contrast":
40
- return `contrast(${filter.value ?? 1})`;
41
- case "grayscale":
42
- return `grayscale(${filter.value ?? 0})`;
43
- case "hue-rotate":
44
- return `hue-rotate(${filter.value ?? 0}deg)`;
45
- case "saturate":
46
- return `saturate(${filter.value ?? 1})`;
47
- case "sepia":
48
- return `sepia(${filter.value ?? 0})`;
49
- case "custom":
50
- return this.buildCustomFilter(filter);
51
- default:
52
- console.warn(`Unknown filter type: ${filter.type}`);
53
- return null;
54
- }
55
- }
56
- /**
57
- * Build custom filter from params
58
- */
59
- buildCustomFilter(filter) {
60
- if (!filter.params) return null;
61
- const { type, ...params } = filter.params;
62
- switch (type) {
63
- case "drop-shadow":
64
- return `drop-shadow(${params.offsetX}px ${params.offsetY}px ${params.blur}px ${params.color})`;
65
- case "opacity":
66
- return `opacity(${params.value})`;
67
- case "invert":
68
- return `invert(${params.value})`;
69
- default:
70
- return null;
71
- }
72
- }
73
- /**
74
- * Apply color matrix transformation for advanced effects
75
- * This allows for more complex color manipulations than CSS filters
76
- */
77
- applyColorMatrix(imageData, matrix) {
78
- if (matrix.length !== 20) {
79
- throw new Error("Color matrix must have 20 values (4x5 matrix)");
80
- }
81
- const data = imageData.data;
82
- const length = data.length;
83
- for (let i = 0; i < length; i += 4) {
84
- const r = data[i];
85
- const g = data[i + 1];
86
- const b = data[i + 2];
87
- const a = data[i + 3];
88
- const m = matrix;
89
- data[i] = this.clamp(r * m[0] + g * m[1] + b * m[2] + a * m[3] + m[4] * 255);
90
- data[i + 1] = this.clamp(r * m[5] + g * m[6] + b * m[7] + a * m[8] + m[9] * 255);
91
- data[i + 2] = this.clamp(r * m[10] + g * m[11] + b * m[12] + a * m[13] + m[14] * 255);
92
- data[i + 3] = this.clamp(r * m[15] + g * m[16] + b * m[17] + a * m[18] + m[19] * 255);
93
- }
94
- return imageData;
95
- }
96
- /**
97
- * Predefined color matrices for common effects
98
- */
99
- getPresetMatrix(preset) {
100
- switch (preset) {
101
- case "vintage":
102
- return [
103
- 0.393,
104
- 0.769,
105
- 0.189,
106
- 0,
107
- 0,
108
- 0.349,
109
- 0.686,
110
- 0.168,
111
- 0,
112
- 0,
113
- 0.272,
114
- 0.534,
115
- 0.131,
116
- 0,
117
- 0,
118
- 0,
119
- 0,
120
- 0,
121
- 1,
122
- 0
123
- ];
124
- case "noir":
125
- return [
126
- 0.25,
127
- 0.25,
128
- 0.25,
129
- 0,
130
- 0,
131
- 0.25,
132
- 0.25,
133
- 0.25,
134
- 0,
135
- 0,
136
- 0.25,
137
- 0.25,
138
- 0.25,
139
- 0,
140
- 0,
141
- 0,
142
- 0,
143
- 0,
144
- 1,
145
- 0
146
- ];
147
- case "cool":
148
- return [0.8, 0, 0, 0, 0, 0, 0.9, 0, 0, 0, 0, 0, 1.2, 0, 0, 0, 0, 0, 1, 0];
149
- case "warm":
150
- return [1.2, 0, 0, 0, 0, 0, 1.1, 0, 0, 0, 0, 0, 0.8, 0, 0, 0, 0, 0, 1, 0];
151
- default:
152
- return null;
153
- }
154
- }
155
- /**
156
- * Apply Gaussian blur manually (for cases where CSS filter is not enough)
157
- */
158
- applyGaussianBlur(imageData, radius) {
159
- const output = new ImageData(
160
- new Uint8ClampedArray(imageData.data),
161
- imageData.width,
162
- imageData.height
163
- );
164
- const width = imageData.width;
165
- const height = imageData.height;
166
- const data = imageData.data;
167
- const outData = output.data;
168
- for (let y = 0; y < height; y++) {
169
- for (let x = 0; x < width; x++) {
170
- let r = 0, g = 0, b = 0, a = 0;
171
- let count = 0;
172
- for (let dx = -radius; dx <= radius; dx++) {
173
- const nx = Math.min(Math.max(x + dx, 0), width - 1);
174
- const idx2 = (y * width + nx) * 4;
175
- r += data[idx2];
176
- g += data[idx2 + 1];
177
- b += data[idx2 + 2];
178
- a += data[idx2 + 3];
179
- count++;
180
- }
181
- const idx = (y * width + x) * 4;
182
- outData[idx] = r / count;
183
- outData[idx + 1] = g / count;
184
- outData[idx + 2] = b / count;
185
- outData[idx + 3] = a / count;
186
- }
187
- }
188
- for (let x = 0; x < width; x++) {
189
- for (let y = 0; y < height; y++) {
190
- let r = 0, g = 0, b = 0, a = 0;
191
- let count = 0;
192
- for (let dy = -radius; dy <= radius; dy++) {
193
- const ny = Math.min(Math.max(y + dy, 0), height - 1);
194
- const idx2 = (ny * width + x) * 4;
195
- r += outData[idx2];
196
- g += outData[idx2 + 1];
197
- b += outData[idx2 + 2];
198
- a += outData[idx2 + 3];
199
- count++;
200
- }
201
- const idx = (y * width + x) * 4;
202
- data[idx] = r / count;
203
- data[idx + 1] = g / count;
204
- data[idx + 2] = b / count;
205
- data[idx + 3] = a / count;
206
- }
207
- }
208
- return imageData;
209
- }
210
- clamp(value) {
211
- return Math.min(255, Math.max(0, Math.round(value)));
212
- }
213
- generateCacheKey(filters) {
214
- return filters.map((f) => `${f.type}:${f.value ?? "default"}`).join("|");
215
- }
216
- clearCache() {
217
- this.filterCache.clear();
218
- }
219
- getCacheSize() {
220
- return this.filterCache.size;
221
- }
222
- }
223
- export {
224
- FilterProcessor
225
- };
226
- //# sourceMappingURL=FilterProcessor.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"FilterProcessor.js","sources":["../../../src/stages/compose/FilterProcessor.ts"],"sourcesContent":["import type { VisualFilter } from './types';\n\n/**\n * FilterProcessor - Handles visual filters and effects\n * Single responsibility: Apply CSS filters and custom shader effects\n */\nexport class FilterProcessor {\n private filterCache = new Map<string, string>();\n\n /**\n * Apply filters to canvas context\n * Combines multiple filters into a single CSS filter string for performance\n */\n applyFilters(ctx: OffscreenCanvasRenderingContext2D, filters: VisualFilter[]): void {\n if (!filters || filters.length === 0) {\n ctx.filter = 'none';\n return;\n }\n\n // Generate cache key\n const cacheKey = this.generateCacheKey(filters);\n\n // Check cache\n let filterString = this.filterCache.get(cacheKey);\n\n if (!filterString) {\n filterString = this.buildFilterString(filters);\n this.filterCache.set(cacheKey, filterString);\n }\n\n ctx.filter = filterString;\n }\n\n /**\n * Build CSS filter string from filter array\n */\n private buildFilterString(filters: VisualFilter[]): string {\n const filterStrings: string[] = [];\n\n for (const filter of filters) {\n const filterStr = this.buildSingleFilter(filter);\n if (filterStr) {\n filterStrings.push(filterStr);\n }\n }\n\n return filterStrings.length > 0 ? filterStrings.join(' ') : 'none';\n }\n\n private buildSingleFilter(filter: VisualFilter): string | null {\n switch (filter.type) {\n case 'blur':\n return `blur(${filter.value ?? 0}px)`;\n\n case 'brightness':\n return `brightness(${filter.value ?? 1})`;\n\n case 'contrast':\n return `contrast(${filter.value ?? 1})`;\n\n case 'grayscale':\n return `grayscale(${filter.value ?? 0})`;\n\n case 'hue-rotate':\n return `hue-rotate(${filter.value ?? 0}deg)`;\n\n case 'saturate':\n return `saturate(${filter.value ?? 1})`;\n\n case 'sepia':\n return `sepia(${filter.value ?? 0})`;\n\n case 'custom':\n return this.buildCustomFilter(filter);\n\n default:\n console.warn(`Unknown filter type: ${filter.type}`);\n return null;\n }\n }\n\n /**\n * Build custom filter from params\n */\n private buildCustomFilter(filter: VisualFilter): string | null {\n if (!filter.params) return null;\n\n const { type, ...params } = filter.params;\n\n switch (type) {\n case 'drop-shadow':\n return `drop-shadow(${params.offsetX}px ${params.offsetY}px ${params.blur}px ${params.color})`;\n\n case 'opacity':\n return `opacity(${params.value})`;\n\n case 'invert':\n return `invert(${params.value})`;\n\n default:\n return null;\n }\n }\n\n /**\n * Apply color matrix transformation for advanced effects\n * This allows for more complex color manipulations than CSS filters\n */\n applyColorMatrix(imageData: ImageData, matrix: number[]): ImageData {\n if (matrix.length !== 20) {\n throw new Error('Color matrix must have 20 values (4x5 matrix)');\n }\n\n const data = imageData.data;\n const length = data.length;\n\n for (let i = 0; i < length; i += 4) {\n const r = data[i]!;\n const g = data[i + 1]!;\n const b = data[i + 2]!;\n const a = data[i + 3]!;\n const m = matrix;\n\n // Apply matrix transformation\n data[i] = this.clamp(r * m[0]! + g * m[1]! + b * m[2]! + a * m[3]! + m[4]! * 255);\n data[i + 1] = this.clamp(r * m[5]! + g * m[6]! + b * m[7]! + a * m[8]! + m[9]! * 255);\n data[i + 2] = this.clamp(r * m[10]! + g * m[11]! + b * m[12]! + a * m[13]! + m[14]! * 255);\n data[i + 3] = this.clamp(r * m[15]! + g * m[16]! + b * m[17]! + a * m[18]! + m[19]! * 255);\n }\n\n return imageData;\n }\n\n /**\n * Predefined color matrices for common effects\n */\n getPresetMatrix(preset: string): number[] | null {\n switch (preset) {\n case 'vintage':\n return [\n 0.393, 0.769, 0.189, 0, 0, 0.349, 0.686, 0.168, 0, 0, 0.272, 0.534, 0.131, 0, 0, 0, 0, 0,\n 1, 0,\n ];\n\n case 'noir':\n return [\n 0.25, 0.25, 0.25, 0, 0, 0.25, 0.25, 0.25, 0, 0, 0.25, 0.25, 0.25, 0, 0, 0, 0, 0, 1, 0,\n ];\n\n case 'cool':\n return [0.8, 0, 0, 0, 0, 0, 0.9, 0, 0, 0, 0, 0, 1.2, 0, 0, 0, 0, 0, 1, 0];\n\n case 'warm':\n return [1.2, 0, 0, 0, 0, 0, 1.1, 0, 0, 0, 0, 0, 0.8, 0, 0, 0, 0, 0, 1, 0];\n\n default:\n return null;\n }\n }\n\n /**\n * Apply Gaussian blur manually (for cases where CSS filter is not enough)\n */\n applyGaussianBlur(imageData: ImageData, radius: number): ImageData {\n // Simplified box blur approximation of Gaussian blur\n const output = new ImageData(\n new Uint8ClampedArray(imageData.data),\n imageData.width,\n imageData.height\n );\n\n const width = imageData.width;\n const height = imageData.height;\n const data = imageData.data;\n const outData = output.data;\n\n // Horizontal pass\n for (let y = 0; y < height; y++) {\n for (let x = 0; x < width; x++) {\n let r = 0,\n g = 0,\n b = 0,\n a = 0;\n let count = 0;\n\n for (let dx = -radius; dx <= radius; dx++) {\n const nx = Math.min(Math.max(x + dx, 0), width - 1);\n const idx = (y * width + nx) * 4;\n r += data[idx]!;\n g += data[idx + 1]!;\n b += data[idx + 2]!;\n a += data[idx + 3]!;\n count++;\n }\n\n const idx = (y * width + x) * 4;\n outData[idx] = r / count;\n outData[idx + 1] = g / count;\n outData[idx + 2] = b / count;\n outData[idx + 3] = a / count;\n }\n }\n\n // Vertical pass\n for (let x = 0; x < width; x++) {\n for (let y = 0; y < height; y++) {\n let r = 0,\n g = 0,\n b = 0,\n a = 0;\n let count = 0;\n\n for (let dy = -radius; dy <= radius; dy++) {\n const ny = Math.min(Math.max(y + dy, 0), height - 1);\n const idx = (ny * width + x) * 4;\n r += outData[idx]!;\n g += outData[idx + 1]!;\n b += outData[idx + 2]!;\n a += outData[idx + 3]!;\n count++;\n }\n\n const idx = (y * width + x) * 4;\n data[idx] = r / count;\n data[idx + 1] = g / count;\n data[idx + 2] = b / count;\n data[idx + 3] = a / count;\n }\n }\n\n return imageData;\n }\n\n private clamp(value: number): number {\n return Math.min(255, Math.max(0, Math.round(value)));\n }\n\n private generateCacheKey(filters: VisualFilter[]): string {\n return filters.map((f) => `${f.type}:${f.value ?? 'default'}`).join('|');\n }\n\n clearCache(): void {\n this.filterCache.clear();\n }\n\n getCacheSize(): number {\n return this.filterCache.size;\n }\n}\n"],"names":["idx"],"mappings":"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"}
@@ -1,215 +0,0 @@
1
- class LayerRenderer {
2
- ctx;
3
- width;
4
- height;
5
- constructor(ctx, width, height) {
6
- this.ctx = ctx;
7
- this.width = width;
8
- this.height = height;
9
- this.ensureHighQualityRendering();
10
- }
11
- ensureHighQualityRendering() {
12
- this.ctx.imageSmoothingEnabled = true;
13
- this.ctx.imageSmoothingQuality = "high";
14
- }
15
- /**
16
- * Render a single layer with all its properties
17
- */
18
- async renderLayer(layer) {
19
- if (!layer.visible || layer.opacity <= 0) return;
20
- this.ctx.save();
21
- try {
22
- this.ensureHighQualityRendering();
23
- this.ctx.globalAlpha = layer.opacity;
24
- if (layer.blendMode) {
25
- this.ctx.globalCompositeOperation = layer.blendMode;
26
- }
27
- if (layer.transform) {
28
- this.applyTransform(layer.transform);
29
- }
30
- switch (layer.type) {
31
- case "video":
32
- await this.renderVideoLayer(layer);
33
- break;
34
- case "image":
35
- await this.renderImageLayer(layer);
36
- break;
37
- case "text":
38
- await this.renderTextLayer(layer);
39
- break;
40
- }
41
- if (layer.mask) {
42
- this.applyMask(layer.mask);
43
- }
44
- } finally {
45
- this.ctx.restore();
46
- }
47
- }
48
- applyTransform(transform) {
49
- const centerX = this.width * (transform.anchorX ?? 0.5);
50
- const centerY = this.height * (transform.anchorY ?? 0.5);
51
- this.ctx.translate(transform.x + centerX, transform.y + centerY);
52
- if (transform.rotation) {
53
- this.ctx.rotate(transform.rotation);
54
- }
55
- this.ctx.scale(transform.scaleX, transform.scaleY);
56
- if (transform.skewX || transform.skewY) {
57
- this.ctx.transform(1, transform.skewY ?? 0, transform.skewX ?? 0, 1, 0, 0);
58
- }
59
- this.ctx.translate(-centerX, -centerY);
60
- }
61
- async renderVideoLayer(layer) {
62
- const { videoFrame, crop } = layer;
63
- const videoWidth = videoFrame.displayWidth || videoFrame.codedWidth;
64
- const videoHeight = videoFrame.displayHeight || videoFrame.codedHeight;
65
- const scaleX = this.width / videoWidth;
66
- const scaleY = this.height / videoHeight;
67
- const scale = Math.min(scaleX, scaleY);
68
- const renderWidth = Math.round(videoWidth * scale);
69
- const renderHeight = Math.round(videoHeight * scale);
70
- const renderX = Math.round((this.width - renderWidth) / 2);
71
- const renderY = Math.round((this.height - renderHeight) / 2);
72
- if (crop) {
73
- this.ctx.drawImage(
74
- videoFrame,
75
- crop.x,
76
- crop.y,
77
- crop.width,
78
- crop.height,
79
- renderX,
80
- renderY,
81
- renderWidth,
82
- renderHeight
83
- );
84
- } else {
85
- this.ctx.drawImage(videoFrame, renderX, renderY, renderWidth, renderHeight);
86
- }
87
- }
88
- async renderImageLayer(layer) {
89
- const { source, crop } = layer;
90
- if (source instanceof ImageData) {
91
- if (crop) {
92
- const tempCanvas = new OffscreenCanvas(crop.width, crop.height);
93
- const tempCtx = tempCanvas.getContext("2d");
94
- tempCtx.putImageData(source, -crop.x, -crop.y);
95
- this.ctx.drawImage(tempCanvas, 0, 0, this.width, this.height);
96
- } else {
97
- this.ctx.putImageData(source, 0, 0);
98
- }
99
- } else {
100
- if (!source) {
101
- return;
102
- }
103
- if (crop) {
104
- this.ctx.drawImage(
105
- source,
106
- crop.x,
107
- crop.y,
108
- crop.width,
109
- crop.height,
110
- 0,
111
- 0,
112
- this.width,
113
- this.height
114
- );
115
- } else {
116
- this.ctx.drawImage(source, 0, 0, this.width, this.height);
117
- }
118
- }
119
- }
120
- async renderTextLayer(layer) {
121
- const fontSize = layer.fontSize ?? 16;
122
- const fontFamily = layer.fontFamily ?? "sans-serif";
123
- const fontWeight = layer.fontWeight ?? "normal";
124
- const fontStyle = layer.fontStyle ?? "normal";
125
- this.ctx.font = `${fontStyle} ${fontWeight} ${fontSize}px ${fontFamily}`;
126
- this.ctx.fillStyle = layer.color ?? "#000000";
127
- this.ctx.textAlign = layer.textAlign ?? "left";
128
- this.ctx.textBaseline = layer.verticalAlign ?? "top";
129
- if (layer.letterSpacing && typeof this.ctx.letterSpacing !== "undefined") {
130
- this.ctx.letterSpacing = `${layer.letterSpacing}px`;
131
- }
132
- this.ensureHighQualityRendering();
133
- const baseX = this.calculateTextX(layer.textAlign);
134
- const baseY = this.calculateTextY(layer.verticalAlign, fontSize);
135
- const x = Math.round(baseX) + 0.5;
136
- const y = Math.round(baseY) + 0.5;
137
- if (layer.shadow) {
138
- this.ctx.shadowColor = layer.shadow.color;
139
- this.ctx.shadowOffsetX = layer.shadow.offsetX;
140
- this.ctx.shadowOffsetY = layer.shadow.offsetY;
141
- this.ctx.shadowBlur = layer.shadow.blur;
142
- }
143
- if (layer.strokeColor && layer.strokeWidth && layer.strokeWidth > 0) {
144
- this.drawEnhancedStroke(layer.text, x, y, layer.strokeColor, layer.strokeWidth);
145
- }
146
- this.ctx.fillText(layer.text, x, y);
147
- if (layer.shadow) {
148
- this.ctx.shadowColor = "transparent";
149
- this.ctx.shadowOffsetX = 0;
150
- this.ctx.shadowOffsetY = 0;
151
- this.ctx.shadowBlur = 0;
152
- }
153
- }
154
- /**
155
- * Draw enhanced multi-layer stroke for better text visibility
156
- */
157
- drawEnhancedStroke(text, x, y, strokeColor, strokeWidth) {
158
- this.ctx.save();
159
- this.ctx.strokeStyle = strokeColor;
160
- this.ctx.lineJoin = "round";
161
- this.ctx.lineCap = "round";
162
- this.ctx.miterLimit = 2;
163
- const layers = [1.1, 1];
164
- layers.forEach((multiplier) => {
165
- this.ctx.lineWidth = strokeWidth * multiplier;
166
- this.ctx.strokeText(text, x, y);
167
- });
168
- this.ctx.restore();
169
- }
170
- calculateTextX(align) {
171
- switch (align) {
172
- case "center":
173
- return this.width / 2;
174
- case "right":
175
- return this.width;
176
- default:
177
- return 0;
178
- }
179
- }
180
- calculateTextY(align, fontSize = 16) {
181
- switch (align) {
182
- case "middle":
183
- return this.height / 2;
184
- case "bottom":
185
- return this.height * 0.85;
186
- default:
187
- return fontSize;
188
- }
189
- }
190
- applyMask(mask) {
191
- this.ctx.globalCompositeOperation = mask.invert ? "source-out" : "destination-in";
192
- if (mask.source) {
193
- this.ctx.drawImage(mask.source, 0, 0, this.width, this.height);
194
- } else if (mask.shape === "circle") {
195
- this.ctx.beginPath();
196
- this.ctx.arc(
197
- this.width / 2,
198
- this.height / 2,
199
- Math.min(this.width, this.height) / 2,
200
- 0,
201
- Math.PI * 2
202
- );
203
- this.ctx.fill();
204
- }
205
- }
206
- updateDimensions(width, height) {
207
- this.width = width;
208
- this.height = height;
209
- this.ensureHighQualityRendering();
210
- }
211
- }
212
- export {
213
- LayerRenderer
214
- };
215
- //# sourceMappingURL=LayerRenderer.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"LayerRenderer.js","sources":["../../../src/stages/compose/LayerRenderer.ts"],"sourcesContent":["import type { Layer, VideoLayer, ImageLayer, TextLayer, Transform2D, MaskConfig } from './types';\n\n/**\n * LayerRenderer - Handles rendering of individual layers\n * Single responsibility: Draw a single layer to the canvas context\n */\nexport class LayerRenderer {\n private ctx: OffscreenCanvasRenderingContext2D;\n private width: number;\n private height: number;\n\n constructor(ctx: OffscreenCanvasRenderingContext2D, width: number, height: number) {\n this.ctx = ctx;\n this.width = width;\n this.height = height;\n this.ensureHighQualityRendering();\n }\n\n private ensureHighQualityRendering(): void {\n this.ctx.imageSmoothingEnabled = true;\n this.ctx.imageSmoothingQuality = 'high';\n }\n\n /**\n * Render a single layer with all its properties\n */\n async renderLayer(layer: Layer): Promise<void> {\n if (!layer.visible || layer.opacity <= 0) return;\n\n this.ctx.save();\n\n try {\n this.ensureHighQualityRendering();\n\n // Apply layer properties\n this.ctx.globalAlpha = layer.opacity;\n\n if (layer.blendMode) {\n this.ctx.globalCompositeOperation = layer.blendMode;\n }\n\n if (layer.transform) {\n this.applyTransform(layer.transform);\n }\n\n // Render based on layer type\n switch (layer.type) {\n case 'video':\n await this.renderVideoLayer(layer as VideoLayer);\n break;\n case 'image':\n await this.renderImageLayer(layer as ImageLayer);\n break;\n case 'text':\n await this.renderTextLayer(layer as TextLayer);\n break;\n }\n\n // Apply mask if present\n if (layer.mask) {\n this.applyMask(layer.mask);\n }\n } finally {\n this.ctx.restore();\n }\n }\n\n private applyTransform(transform: Transform2D): void {\n const centerX = this.width * (transform.anchorX ?? 0.5);\n const centerY = this.height * (transform.anchorY ?? 0.5);\n\n this.ctx.translate(transform.x + centerX, transform.y + centerY);\n\n if (transform.rotation) {\n this.ctx.rotate(transform.rotation);\n }\n\n this.ctx.scale(transform.scaleX, transform.scaleY);\n\n if (transform.skewX || transform.skewY) {\n this.ctx.transform(1, transform.skewY ?? 0, transform.skewX ?? 0, 1, 0, 0);\n }\n\n this.ctx.translate(-centerX, -centerY);\n }\n\n private async renderVideoLayer(layer: VideoLayer): Promise<void> {\n const { videoFrame, crop } = layer;\n\n // Get video dimensions\n const videoWidth = videoFrame.displayWidth || videoFrame.codedWidth;\n const videoHeight = videoFrame.displayHeight || videoFrame.codedHeight;\n\n // Calculate scaling to fit (contain mode - preserve aspect ratio)\n const scaleX = this.width / videoWidth;\n const scaleY = this.height / videoHeight;\n\n // Use the smaller scale to ensure entire video fits\n const scale = Math.min(scaleX, scaleY);\n\n // Calculate final render dimensions\n const renderWidth = Math.round(videoWidth * scale);\n const renderHeight = Math.round(videoHeight * scale);\n\n // Center the video\n const renderX = Math.round((this.width - renderWidth) / 2);\n const renderY = Math.round((this.height - renderHeight) / 2);\n\n if (crop) {\n this.ctx.drawImage(\n videoFrame,\n crop.x,\n crop.y,\n crop.width,\n crop.height,\n renderX,\n renderY,\n renderWidth,\n renderHeight\n );\n } else {\n this.ctx.drawImage(videoFrame, renderX, renderY, renderWidth, renderHeight);\n }\n }\n\n private async renderImageLayer(layer: ImageLayer): Promise<void> {\n const { source, crop } = layer;\n\n // Handle ImageData by putting it on canvas first\n if (source instanceof ImageData) {\n if (crop) {\n // For ImageData with crop, we need to extract the cropped region\n const tempCanvas = new OffscreenCanvas(crop.width, crop.height);\n const tempCtx = tempCanvas.getContext('2d')!;\n tempCtx.putImageData(source, -crop.x, -crop.y);\n this.ctx.drawImage(tempCanvas, 0, 0, this.width, this.height);\n } else {\n // Put ImageData directly\n this.ctx.putImageData(source, 0, 0);\n }\n } else {\n // ImageBitmap can be drawn directly\n if (!source) {\n return;\n }\n if (crop) {\n this.ctx.drawImage(\n source,\n crop.x,\n crop.y,\n crop.width,\n crop.height,\n 0,\n 0,\n this.width,\n this.height\n );\n } else {\n this.ctx.drawImage(source, 0, 0, this.width, this.height);\n }\n }\n }\n\n private async renderTextLayer(layer: TextLayer): Promise<void> {\n const fontSize = layer.fontSize ?? 16;\n const fontFamily = layer.fontFamily ?? 'sans-serif';\n const fontWeight = layer.fontWeight ?? 'normal';\n const fontStyle = layer.fontStyle ?? 'normal';\n\n this.ctx.font = `${fontStyle} ${fontWeight} ${fontSize}px ${fontFamily}`;\n this.ctx.fillStyle = layer.color ?? '#000000';\n this.ctx.textAlign = layer.textAlign ?? 'left';\n this.ctx.textBaseline = layer.verticalAlign ?? 'top';\n\n // Apply letter spacing if supported\n if (layer.letterSpacing && typeof (this.ctx as any).letterSpacing !== 'undefined') {\n (this.ctx as any).letterSpacing = `${layer.letterSpacing}px`;\n }\n\n // Ensure high-quality text rendering\n this.ensureHighQualityRendering();\n\n // Calculate position with sub-pixel adjustment for sharper rendering\n const baseX = this.calculateTextX(layer.textAlign);\n const baseY = this.calculateTextY(layer.verticalAlign, fontSize);\n const x = Math.round(baseX) + 0.5;\n const y = Math.round(baseY) + 0.5;\n\n // Apply shadow before rendering text (if needed)\n if (layer.shadow) {\n this.ctx.shadowColor = layer.shadow.color;\n this.ctx.shadowOffsetX = layer.shadow.offsetX;\n this.ctx.shadowOffsetY = layer.shadow.offsetY;\n this.ctx.shadowBlur = layer.shadow.blur;\n }\n\n // Render stroke with enhanced quality (multi-layer approach)\n if (layer.strokeColor && layer.strokeWidth && layer.strokeWidth > 0) {\n this.drawEnhancedStroke(layer.text, x, y, layer.strokeColor, layer.strokeWidth);\n }\n\n // Render fill text\n this.ctx.fillText(layer.text, x, y);\n\n // Reset shadow\n if (layer.shadow) {\n this.ctx.shadowColor = 'transparent';\n this.ctx.shadowOffsetX = 0;\n this.ctx.shadowOffsetY = 0;\n this.ctx.shadowBlur = 0;\n }\n }\n\n /**\n * Draw enhanced multi-layer stroke for better text visibility\n */\n private drawEnhancedStroke(\n text: string,\n x: number,\n y: number,\n strokeColor: string,\n strokeWidth: number\n ): void {\n this.ctx.save();\n\n this.ctx.strokeStyle = strokeColor;\n this.ctx.lineJoin = 'round';\n this.ctx.lineCap = 'round';\n this.ctx.miterLimit = 2;\n\n // Multi-layer stroke for enhanced visibility without excessive thickness\n const layers = [1.1, 1.0];\n layers.forEach((multiplier) => {\n this.ctx.lineWidth = strokeWidth * multiplier;\n this.ctx.strokeText(text, x, y);\n });\n\n this.ctx.restore();\n }\n\n private calculateTextX(align?: 'left' | 'center' | 'right'): number {\n switch (align) {\n case 'center':\n return this.width / 2;\n case 'right':\n return this.width;\n default:\n return 0;\n }\n }\n\n private calculateTextY(align?: 'top' | 'middle' | 'bottom', fontSize: number = 16): number {\n switch (align) {\n case 'middle':\n return this.height / 2;\n case 'bottom':\n // Place text at 85% height to avoid bottom edge, similar to SubtitleComposer's 70% position\n return this.height * 0.85;\n default:\n return fontSize;\n }\n }\n\n private applyMask(mask: MaskConfig): void {\n this.ctx.globalCompositeOperation = mask.invert ? 'source-out' : 'destination-in';\n\n if (mask.source) {\n this.ctx.drawImage(mask.source, 0, 0, this.width, this.height);\n } else if (mask.shape === 'circle') {\n this.ctx.beginPath();\n this.ctx.arc(\n this.width / 2,\n this.height / 2,\n Math.min(this.width, this.height) / 2,\n 0,\n Math.PI * 2\n );\n this.ctx.fill();\n }\n }\n\n updateDimensions(width: number, height: number): void {\n this.width = width;\n this.height = height;\n this.ensureHighQualityRendering();\n }\n}\n"],"names":[],"mappings":"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"}