straight_to_video 0.0.8 → 0.0.9
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.
- checksums.yaml +4 -4
- data/app/assets/javascripts/mediabunny.min.js +9 -9
- data/app/assets/javascripts/straight-to-video.js +144 -48
- data/index.js +143 -47
- data/lib/straight_to_video/version.rb +1 -1
- data/package-lock.json +18 -18
- data/package.json +3 -3
- data/script/release +87 -33
- data/script/test +1 -1
- metadata +3 -3
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// straight-to-video@0.0.
|
|
1
|
+
// straight-to-video@0.0.9 vendored by the straight_to_video gem
|
|
2
2
|
// straight-to-video - https://github.com/searlsco/straight-to-video
|
|
3
3
|
|
|
4
4
|
// ----- External imports -----
|
|
@@ -170,62 +170,88 @@ async function selectVideoEncoderConfig ({ width, height, fps }) {
|
|
|
170
170
|
return { codecId: 'avc', config: supA.config }
|
|
171
171
|
}
|
|
172
172
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
const durationCfr = Number(srcMeta.duration)
|
|
177
|
-
const long = Math.max(w, h)
|
|
178
|
-
const scale = Math.min(1, MAX_LONG_SIDE / Math.max(2, long))
|
|
179
|
-
const targetWidth = Math.max(2, Number(plan?.width) || Math.round(w * scale))
|
|
180
|
-
const targetHeight = Math.max(2, Number(plan?.height) || Math.round(h * scale))
|
|
181
|
-
|
|
182
|
-
const targetFps = Math.max(1, Number(plan?.fps) || await determineTargetFps(file, { width: w, height: h }))
|
|
183
|
-
const step = 1 / Math.max(1, targetFps)
|
|
184
|
-
const frames = Math.max(1, Math.floor(durationCfr / step))
|
|
173
|
+
function shouldDecodeViaVideoElement () {
|
|
174
|
+
return (navigator?.vendor || '').includes('Apple')
|
|
175
|
+
}
|
|
185
176
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
177
|
+
async function waitForFrameReady (video, budgetMs) {
|
|
178
|
+
if (typeof video.requestVideoFrameCallback !== 'function') return false
|
|
179
|
+
return await new Promise((resolve) => {
|
|
180
|
+
let settled = false
|
|
181
|
+
const to = setTimeout(() => { if (!settled) { settled = true; resolve(false) } }, Math.max(1, budgetMs || 17))
|
|
182
|
+
video.requestVideoFrameCallback(() => { if (!settled) { settled = true; clearTimeout(to); resolve(true) } })
|
|
183
|
+
})
|
|
184
|
+
}
|
|
190
185
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
186
|
+
async function seekOnce (video, time) {
|
|
187
|
+
if (!video) return
|
|
188
|
+
const t = Number.isFinite(time) ? time : 0
|
|
189
|
+
if (Math.abs(video.currentTime - t) < 1e-6) return
|
|
190
|
+
await new Promise((resolve) => {
|
|
191
|
+
const onSeeked = () => {
|
|
192
|
+
video.removeEventListener('seeked', onSeeked)
|
|
193
|
+
resolve()
|
|
194
|
+
}
|
|
195
|
+
video.addEventListener('seeked', onSeeked, { once: true })
|
|
196
|
+
video.currentTime = t
|
|
197
|
+
})
|
|
198
|
+
}
|
|
199
199
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
200
|
+
async function encodeFramesViaVideoElement ({ file, durationCfr, step, frames, canvas, ctx, ve, onProgress }) {
|
|
201
|
+
const url = URL.createObjectURL(file)
|
|
202
|
+
const v = document.createElement('video')
|
|
203
|
+
v.muted = true; v.preload = 'auto'; v.playsInline = true
|
|
204
|
+
await new Promise((resolve, reject) => {
|
|
205
|
+
const onLoaded = () => {
|
|
206
|
+
v.removeEventListener('loadedmetadata', onLoaded)
|
|
207
|
+
v.removeEventListener('error', onError)
|
|
208
|
+
resolve()
|
|
209
|
+
}
|
|
210
|
+
const onError = () => {
|
|
211
|
+
v.removeEventListener('loadedmetadata', onLoaded)
|
|
212
|
+
v.removeEventListener('error', onError)
|
|
213
|
+
reject(new Error('video load failed'))
|
|
214
|
+
}
|
|
215
|
+
v.addEventListener('loadedmetadata', onLoaded)
|
|
216
|
+
v.addEventListener('error', onError)
|
|
217
|
+
v.src = url
|
|
218
|
+
try {
|
|
219
|
+
v.load()
|
|
220
|
+
} catch (err) {
|
|
221
|
+
console.warn('straight-to-video: video.load() threw; continuing without explicit load()', err)
|
|
209
222
|
}
|
|
210
223
|
})
|
|
211
|
-
output.addAudioTrack(audioSource)
|
|
212
224
|
|
|
213
|
-
|
|
225
|
+
for (let i = 0; i < frames; i++) {
|
|
226
|
+
const t = i * step
|
|
227
|
+
const drawTime = Math.min(Math.max(0, t + (step * 0.5)), Math.max(0.000001, durationCfr - 0.000001))
|
|
228
|
+
await seekOnce(v, drawTime)
|
|
229
|
+
const budgetMs = Math.min(34, Math.max(17, Math.round(step * 1000)))
|
|
230
|
+
const presented = await waitForFrameReady(v, budgetMs)
|
|
231
|
+
if (!presented && i === 0) {
|
|
232
|
+
const nudge = Math.min(step * 0.25, 0.004)
|
|
233
|
+
const target = Math.min(drawTime + nudge, Math.max(0.000001, durationCfr - 0.000001))
|
|
234
|
+
await seekOnce(v, target)
|
|
235
|
+
}
|
|
214
236
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
if (!codecDesc && meta?.decoderConfig?.description) codecDesc = meta.decoderConfig.description
|
|
220
|
-
pendingPackets.push({ chunk })
|
|
221
|
-
},
|
|
222
|
-
error: () => {}
|
|
223
|
-
})
|
|
224
|
-
ve.configure(usedCfg)
|
|
237
|
+
ctx.drawImage(v, 0, 0, canvas.width, canvas.height)
|
|
238
|
+
const vf = new VideoFrame(canvas, { timestamp: Math.round(t * 1e6), duration: Math.round(step * 1e6) })
|
|
239
|
+
ve.encode(vf, { keyFrame: i === 0 })
|
|
240
|
+
vf.close()
|
|
225
241
|
|
|
226
|
-
|
|
227
|
-
|
|
242
|
+
if (typeof onProgress === 'function') {
|
|
243
|
+
try {
|
|
244
|
+
onProgress(Math.min(1, (i + 1) / frames))
|
|
245
|
+
} catch (err) {
|
|
246
|
+
console.warn('straight-to-video: onProgress callback threw; ignoring error', err)
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
228
250
|
|
|
251
|
+
URL.revokeObjectURL(url)
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
async function encodeFramesViaVideoSampleSink ({ file, durationCfr, step, frames, canvas, ctx, ve, onProgress }) {
|
|
229
255
|
const input = new Input({ source: new BlobSource(file), formats: ALL_FORMATS })
|
|
230
256
|
const tracks = await input.getTracks()
|
|
231
257
|
const video = tracks.find(t => typeof t.isVideoTrack === 'function' && t.isVideoTrack())
|
|
@@ -284,10 +310,80 @@ async function encodeVideo ({ file, srcMeta, plan, onProgress }) {
|
|
|
284
310
|
const vf = new VideoFrame(canvas, { timestamp: Math.round(t * 1e6), duration: Math.round(step * 1e6) })
|
|
285
311
|
ve.encode(vf, { keyFrame: i === 0 })
|
|
286
312
|
vf.close()
|
|
313
|
+
|
|
314
|
+
if (typeof onProgress === 'function') {
|
|
315
|
+
try {
|
|
316
|
+
onProgress(Math.min(1, (i + 1) / frames))
|
|
317
|
+
} catch (err) {
|
|
318
|
+
console.warn('straight-to-video: onProgress callback threw; ignoring error', err)
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
287
322
|
i++
|
|
288
323
|
}
|
|
289
324
|
if (typeof prev.close === 'function') prev.close()
|
|
290
325
|
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
async function encodeVideo ({ file, srcMeta, plan, onProgress }) {
|
|
329
|
+
const w = srcMeta.w
|
|
330
|
+
const h = srcMeta.h
|
|
331
|
+
const durationCfr = Number(srcMeta.duration)
|
|
332
|
+
const long = Math.max(w, h)
|
|
333
|
+
const scale = Math.min(1, MAX_LONG_SIDE / Math.max(2, long))
|
|
334
|
+
const targetWidth = Math.max(2, Number(plan?.width) || Math.round(w * scale))
|
|
335
|
+
const targetHeight = Math.max(2, Number(plan?.height) || Math.round(h * scale))
|
|
336
|
+
|
|
337
|
+
const targetFps = Math.max(1, Number(plan?.fps) || await determineTargetFps(file, { width: w, height: h }))
|
|
338
|
+
const step = 1 / Math.max(1, targetFps)
|
|
339
|
+
const frames = Math.max(1, Math.floor(durationCfr / step))
|
|
340
|
+
|
|
341
|
+
const output = new Output({ format: new Mp4OutputFormat({ fastStart: 'in-memory' }), target: new BufferTarget() })
|
|
342
|
+
const { codecId, config: usedCfg } = await selectVideoEncoderConfig({ width: targetWidth, height: targetHeight, fps: targetFps })
|
|
343
|
+
const videoTrack = new EncodedVideoPacketSource(codecId)
|
|
344
|
+
output.addVideoTrack(videoTrack, { frameRate: targetFps })
|
|
345
|
+
|
|
346
|
+
const _warn = console.warn
|
|
347
|
+
console.warn = (...args) => {
|
|
348
|
+
const m = args && args[0]
|
|
349
|
+
if (typeof m === 'string' && m.includes('Unsupported audio codec') && m.includes('apac')) return
|
|
350
|
+
_warn.apply(console, args)
|
|
351
|
+
}
|
|
352
|
+
const audioBuffer = await decodeAudioPCM(file, { duration: durationCfr })
|
|
353
|
+
console.warn = _warn
|
|
354
|
+
|
|
355
|
+
const audioSource = new AudioSampleSource({
|
|
356
|
+
codec: 'aac',
|
|
357
|
+
bitrate: TARGET_AUDIO_BITRATE,
|
|
358
|
+
bitrateMode: 'constant',
|
|
359
|
+
numberOfChannels: TARGET_AUDIO_CHANNELS,
|
|
360
|
+
sampleRate: TARGET_AUDIO_SR,
|
|
361
|
+
onEncodedPacket: (_packet, meta) => {
|
|
362
|
+
const aot = 2; const idx = 3; const b0 = (aot << 3) | (idx >> 1); const b1 = ((idx & 1) << 7) | (TARGET_AUDIO_CHANNELS << 3)
|
|
363
|
+
meta.decoderConfig = { codec: 'mp4a.40.2', numberOfChannels: TARGET_AUDIO_CHANNELS, sampleRate: TARGET_AUDIO_SR, description: new Uint8Array([b0, b1]) }
|
|
364
|
+
}
|
|
365
|
+
})
|
|
366
|
+
output.addAudioTrack(audioSource)
|
|
367
|
+
|
|
368
|
+
await output.start()
|
|
369
|
+
|
|
370
|
+
let codecDesc = null
|
|
371
|
+
const pendingPackets = []
|
|
372
|
+
const ve = new VideoEncoder({
|
|
373
|
+
output: (chunk, meta) => {
|
|
374
|
+
if (!codecDesc && meta?.decoderConfig?.description) codecDesc = meta.decoderConfig.description
|
|
375
|
+
pendingPackets.push({ chunk })
|
|
376
|
+
},
|
|
377
|
+
error: () => {}
|
|
378
|
+
})
|
|
379
|
+
ve.configure(usedCfg)
|
|
380
|
+
|
|
381
|
+
const canvas = document.createElement('canvas'); canvas.width = targetWidth; canvas.height = targetHeight
|
|
382
|
+
const ctx = canvas.getContext('2d', { alpha: false })
|
|
383
|
+
|
|
384
|
+
await (shouldDecodeViaVideoElement()
|
|
385
|
+
? encodeFramesViaVideoElement({ file, durationCfr, step, frames, canvas, ctx, ve, onProgress })
|
|
386
|
+
: encodeFramesViaVideoSampleSink({ file, durationCfr, step, frames, canvas, ctx, ve, onProgress }))
|
|
291
387
|
await ve.flush()
|
|
292
388
|
|
|
293
389
|
const muxCount = Math.min(frames, pendingPackets.length)
|
data/index.js
CHANGED
|
@@ -169,62 +169,88 @@ async function selectVideoEncoderConfig ({ width, height, fps }) {
|
|
|
169
169
|
return { codecId: 'avc', config: supA.config }
|
|
170
170
|
}
|
|
171
171
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
const durationCfr = Number(srcMeta.duration)
|
|
176
|
-
const long = Math.max(w, h)
|
|
177
|
-
const scale = Math.min(1, MAX_LONG_SIDE / Math.max(2, long))
|
|
178
|
-
const targetWidth = Math.max(2, Number(plan?.width) || Math.round(w * scale))
|
|
179
|
-
const targetHeight = Math.max(2, Number(plan?.height) || Math.round(h * scale))
|
|
180
|
-
|
|
181
|
-
const targetFps = Math.max(1, Number(plan?.fps) || await determineTargetFps(file, { width: w, height: h }))
|
|
182
|
-
const step = 1 / Math.max(1, targetFps)
|
|
183
|
-
const frames = Math.max(1, Math.floor(durationCfr / step))
|
|
172
|
+
function shouldDecodeViaVideoElement () {
|
|
173
|
+
return (navigator?.vendor || '').includes('Apple')
|
|
174
|
+
}
|
|
184
175
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
176
|
+
async function waitForFrameReady (video, budgetMs) {
|
|
177
|
+
if (typeof video.requestVideoFrameCallback !== 'function') return false
|
|
178
|
+
return await new Promise((resolve) => {
|
|
179
|
+
let settled = false
|
|
180
|
+
const to = setTimeout(() => { if (!settled) { settled = true; resolve(false) } }, Math.max(1, budgetMs || 17))
|
|
181
|
+
video.requestVideoFrameCallback(() => { if (!settled) { settled = true; clearTimeout(to); resolve(true) } })
|
|
182
|
+
})
|
|
183
|
+
}
|
|
189
184
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
185
|
+
async function seekOnce (video, time) {
|
|
186
|
+
if (!video) return
|
|
187
|
+
const t = Number.isFinite(time) ? time : 0
|
|
188
|
+
if (Math.abs(video.currentTime - t) < 1e-6) return
|
|
189
|
+
await new Promise((resolve) => {
|
|
190
|
+
const onSeeked = () => {
|
|
191
|
+
video.removeEventListener('seeked', onSeeked)
|
|
192
|
+
resolve()
|
|
193
|
+
}
|
|
194
|
+
video.addEventListener('seeked', onSeeked, { once: true })
|
|
195
|
+
video.currentTime = t
|
|
196
|
+
})
|
|
197
|
+
}
|
|
198
198
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
199
|
+
async function encodeFramesViaVideoElement ({ file, durationCfr, step, frames, canvas, ctx, ve, onProgress }) {
|
|
200
|
+
const url = URL.createObjectURL(file)
|
|
201
|
+
const v = document.createElement('video')
|
|
202
|
+
v.muted = true; v.preload = 'auto'; v.playsInline = true
|
|
203
|
+
await new Promise((resolve, reject) => {
|
|
204
|
+
const onLoaded = () => {
|
|
205
|
+
v.removeEventListener('loadedmetadata', onLoaded)
|
|
206
|
+
v.removeEventListener('error', onError)
|
|
207
|
+
resolve()
|
|
208
|
+
}
|
|
209
|
+
const onError = () => {
|
|
210
|
+
v.removeEventListener('loadedmetadata', onLoaded)
|
|
211
|
+
v.removeEventListener('error', onError)
|
|
212
|
+
reject(new Error('video load failed'))
|
|
213
|
+
}
|
|
214
|
+
v.addEventListener('loadedmetadata', onLoaded)
|
|
215
|
+
v.addEventListener('error', onError)
|
|
216
|
+
v.src = url
|
|
217
|
+
try {
|
|
218
|
+
v.load()
|
|
219
|
+
} catch (err) {
|
|
220
|
+
console.warn('straight-to-video: video.load() threw; continuing without explicit load()', err)
|
|
208
221
|
}
|
|
209
222
|
})
|
|
210
|
-
output.addAudioTrack(audioSource)
|
|
211
223
|
|
|
212
|
-
|
|
224
|
+
for (let i = 0; i < frames; i++) {
|
|
225
|
+
const t = i * step
|
|
226
|
+
const drawTime = Math.min(Math.max(0, t + (step * 0.5)), Math.max(0.000001, durationCfr - 0.000001))
|
|
227
|
+
await seekOnce(v, drawTime)
|
|
228
|
+
const budgetMs = Math.min(34, Math.max(17, Math.round(step * 1000)))
|
|
229
|
+
const presented = await waitForFrameReady(v, budgetMs)
|
|
230
|
+
if (!presented && i === 0) {
|
|
231
|
+
const nudge = Math.min(step * 0.25, 0.004)
|
|
232
|
+
const target = Math.min(drawTime + nudge, Math.max(0.000001, durationCfr - 0.000001))
|
|
233
|
+
await seekOnce(v, target)
|
|
234
|
+
}
|
|
213
235
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
if (!codecDesc && meta?.decoderConfig?.description) codecDesc = meta.decoderConfig.description
|
|
219
|
-
pendingPackets.push({ chunk })
|
|
220
|
-
},
|
|
221
|
-
error: () => {}
|
|
222
|
-
})
|
|
223
|
-
ve.configure(usedCfg)
|
|
236
|
+
ctx.drawImage(v, 0, 0, canvas.width, canvas.height)
|
|
237
|
+
const vf = new VideoFrame(canvas, { timestamp: Math.round(t * 1e6), duration: Math.round(step * 1e6) })
|
|
238
|
+
ve.encode(vf, { keyFrame: i === 0 })
|
|
239
|
+
vf.close()
|
|
224
240
|
|
|
225
|
-
|
|
226
|
-
|
|
241
|
+
if (typeof onProgress === 'function') {
|
|
242
|
+
try {
|
|
243
|
+
onProgress(Math.min(1, (i + 1) / frames))
|
|
244
|
+
} catch (err) {
|
|
245
|
+
console.warn('straight-to-video: onProgress callback threw; ignoring error', err)
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
227
249
|
|
|
250
|
+
URL.revokeObjectURL(url)
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
async function encodeFramesViaVideoSampleSink ({ file, durationCfr, step, frames, canvas, ctx, ve, onProgress }) {
|
|
228
254
|
const input = new Input({ source: new BlobSource(file), formats: ALL_FORMATS })
|
|
229
255
|
const tracks = await input.getTracks()
|
|
230
256
|
const video = tracks.find(t => typeof t.isVideoTrack === 'function' && t.isVideoTrack())
|
|
@@ -283,10 +309,80 @@ async function encodeVideo ({ file, srcMeta, plan, onProgress }) {
|
|
|
283
309
|
const vf = new VideoFrame(canvas, { timestamp: Math.round(t * 1e6), duration: Math.round(step * 1e6) })
|
|
284
310
|
ve.encode(vf, { keyFrame: i === 0 })
|
|
285
311
|
vf.close()
|
|
312
|
+
|
|
313
|
+
if (typeof onProgress === 'function') {
|
|
314
|
+
try {
|
|
315
|
+
onProgress(Math.min(1, (i + 1) / frames))
|
|
316
|
+
} catch (err) {
|
|
317
|
+
console.warn('straight-to-video: onProgress callback threw; ignoring error', err)
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
286
321
|
i++
|
|
287
322
|
}
|
|
288
323
|
if (typeof prev.close === 'function') prev.close()
|
|
289
324
|
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
async function encodeVideo ({ file, srcMeta, plan, onProgress }) {
|
|
328
|
+
const w = srcMeta.w
|
|
329
|
+
const h = srcMeta.h
|
|
330
|
+
const durationCfr = Number(srcMeta.duration)
|
|
331
|
+
const long = Math.max(w, h)
|
|
332
|
+
const scale = Math.min(1, MAX_LONG_SIDE / Math.max(2, long))
|
|
333
|
+
const targetWidth = Math.max(2, Number(plan?.width) || Math.round(w * scale))
|
|
334
|
+
const targetHeight = Math.max(2, Number(plan?.height) || Math.round(h * scale))
|
|
335
|
+
|
|
336
|
+
const targetFps = Math.max(1, Number(plan?.fps) || await determineTargetFps(file, { width: w, height: h }))
|
|
337
|
+
const step = 1 / Math.max(1, targetFps)
|
|
338
|
+
const frames = Math.max(1, Math.floor(durationCfr / step))
|
|
339
|
+
|
|
340
|
+
const output = new Output({ format: new Mp4OutputFormat({ fastStart: 'in-memory' }), target: new BufferTarget() })
|
|
341
|
+
const { codecId, config: usedCfg } = await selectVideoEncoderConfig({ width: targetWidth, height: targetHeight, fps: targetFps })
|
|
342
|
+
const videoTrack = new EncodedVideoPacketSource(codecId)
|
|
343
|
+
output.addVideoTrack(videoTrack, { frameRate: targetFps })
|
|
344
|
+
|
|
345
|
+
const _warn = console.warn
|
|
346
|
+
console.warn = (...args) => {
|
|
347
|
+
const m = args && args[0]
|
|
348
|
+
if (typeof m === 'string' && m.includes('Unsupported audio codec') && m.includes('apac')) return
|
|
349
|
+
_warn.apply(console, args)
|
|
350
|
+
}
|
|
351
|
+
const audioBuffer = await decodeAudioPCM(file, { duration: durationCfr })
|
|
352
|
+
console.warn = _warn
|
|
353
|
+
|
|
354
|
+
const audioSource = new AudioSampleSource({
|
|
355
|
+
codec: 'aac',
|
|
356
|
+
bitrate: TARGET_AUDIO_BITRATE,
|
|
357
|
+
bitrateMode: 'constant',
|
|
358
|
+
numberOfChannels: TARGET_AUDIO_CHANNELS,
|
|
359
|
+
sampleRate: TARGET_AUDIO_SR,
|
|
360
|
+
onEncodedPacket: (_packet, meta) => {
|
|
361
|
+
const aot = 2; const idx = 3; const b0 = (aot << 3) | (idx >> 1); const b1 = ((idx & 1) << 7) | (TARGET_AUDIO_CHANNELS << 3)
|
|
362
|
+
meta.decoderConfig = { codec: 'mp4a.40.2', numberOfChannels: TARGET_AUDIO_CHANNELS, sampleRate: TARGET_AUDIO_SR, description: new Uint8Array([b0, b1]) }
|
|
363
|
+
}
|
|
364
|
+
})
|
|
365
|
+
output.addAudioTrack(audioSource)
|
|
366
|
+
|
|
367
|
+
await output.start()
|
|
368
|
+
|
|
369
|
+
let codecDesc = null
|
|
370
|
+
const pendingPackets = []
|
|
371
|
+
const ve = new VideoEncoder({
|
|
372
|
+
output: (chunk, meta) => {
|
|
373
|
+
if (!codecDesc && meta?.decoderConfig?.description) codecDesc = meta.decoderConfig.description
|
|
374
|
+
pendingPackets.push({ chunk })
|
|
375
|
+
},
|
|
376
|
+
error: () => {}
|
|
377
|
+
})
|
|
378
|
+
ve.configure(usedCfg)
|
|
379
|
+
|
|
380
|
+
const canvas = document.createElement('canvas'); canvas.width = targetWidth; canvas.height = targetHeight
|
|
381
|
+
const ctx = canvas.getContext('2d', { alpha: false })
|
|
382
|
+
|
|
383
|
+
await (shouldDecodeViaVideoElement()
|
|
384
|
+
? encodeFramesViaVideoElement({ file, durationCfr, step, frames, canvas, ctx, ve, onProgress })
|
|
385
|
+
: encodeFramesViaVideoSampleSink({ file, durationCfr, step, frames, canvas, ctx, ve, onProgress }))
|
|
290
386
|
await ve.flush()
|
|
291
387
|
|
|
292
388
|
const muxCount = Math.min(frames, pendingPackets.length)
|
data/package-lock.json
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "straight-to-video",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.9",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "straight-to-video",
|
|
9
|
-
"version": "0.0.
|
|
9
|
+
"version": "0.0.9",
|
|
10
10
|
"license": "MIT",
|
|
11
11
|
"dependencies": {
|
|
12
|
-
"mediabunny": "^1.
|
|
12
|
+
"mediabunny": "^1.27.3"
|
|
13
13
|
},
|
|
14
14
|
"devDependencies": {
|
|
15
|
-
"@playwright/test": "^1.
|
|
15
|
+
"@playwright/test": "^1.57.0",
|
|
16
16
|
"busboy": "^1.6.0"
|
|
17
17
|
},
|
|
18
18
|
"peerDependencies": {
|
|
@@ -33,13 +33,13 @@
|
|
|
33
33
|
"peer": true
|
|
34
34
|
},
|
|
35
35
|
"node_modules/@playwright/test": {
|
|
36
|
-
"version": "1.
|
|
37
|
-
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.
|
|
38
|
-
"integrity": "sha512-
|
|
36
|
+
"version": "1.57.0",
|
|
37
|
+
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.57.0.tgz",
|
|
38
|
+
"integrity": "sha512-6TyEnHgd6SArQO8UO2OMTxshln3QMWBtPGrOCgs3wVEmQmwyuNtB10IZMfmYDE0riwNR1cu4q+pPcxMVtaG3TA==",
|
|
39
39
|
"dev": true,
|
|
40
40
|
"license": "Apache-2.0",
|
|
41
41
|
"dependencies": {
|
|
42
|
-
"playwright": "1.
|
|
42
|
+
"playwright": "1.57.0"
|
|
43
43
|
},
|
|
44
44
|
"bin": {
|
|
45
45
|
"playwright": "cli.js"
|
|
@@ -91,9 +91,9 @@
|
|
|
91
91
|
}
|
|
92
92
|
},
|
|
93
93
|
"node_modules/mediabunny": {
|
|
94
|
-
"version": "1.
|
|
95
|
-
"resolved": "https://registry.npmjs.org/mediabunny/-/mediabunny-1.
|
|
96
|
-
"integrity": "sha512-
|
|
94
|
+
"version": "1.27.3",
|
|
95
|
+
"resolved": "https://registry.npmjs.org/mediabunny/-/mediabunny-1.27.3.tgz",
|
|
96
|
+
"integrity": "sha512-hlzmgzMznp9DhA5fMJKS5yEAyfCUMxAc+DbSPxD4J1J2cYVl1L+pZLndkt5xLlD5aB5eHEnphHMW14ammMlUXg==",
|
|
97
97
|
"license": "MPL-2.0",
|
|
98
98
|
"workspaces": [
|
|
99
99
|
"packages/*"
|
|
@@ -108,13 +108,13 @@
|
|
|
108
108
|
}
|
|
109
109
|
},
|
|
110
110
|
"node_modules/playwright": {
|
|
111
|
-
"version": "1.
|
|
112
|
-
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.
|
|
113
|
-
"integrity": "sha512-
|
|
111
|
+
"version": "1.57.0",
|
|
112
|
+
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.57.0.tgz",
|
|
113
|
+
"integrity": "sha512-ilYQj1s8sr2ppEJ2YVadYBN0Mb3mdo9J0wQ+UuDhzYqURwSoW4n1Xs5vs7ORwgDGmyEh33tRMeS8KhdkMoLXQw==",
|
|
114
114
|
"dev": true,
|
|
115
115
|
"license": "Apache-2.0",
|
|
116
116
|
"dependencies": {
|
|
117
|
-
"playwright-core": "1.
|
|
117
|
+
"playwright-core": "1.57.0"
|
|
118
118
|
},
|
|
119
119
|
"bin": {
|
|
120
120
|
"playwright": "cli.js"
|
|
@@ -127,9 +127,9 @@
|
|
|
127
127
|
}
|
|
128
128
|
},
|
|
129
129
|
"node_modules/playwright-core": {
|
|
130
|
-
"version": "1.
|
|
131
|
-
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.
|
|
132
|
-
"integrity": "sha512-
|
|
130
|
+
"version": "1.57.0",
|
|
131
|
+
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.57.0.tgz",
|
|
132
|
+
"integrity": "sha512-agTcKlMw/mjBWOnD6kFZttAAGHgi/Nw0CZ2o6JqWSbMlI219lAFLZZCyqByTsvVAJq5XA5H8cA6PrvBRpBWEuQ==",
|
|
133
133
|
"dev": true,
|
|
134
134
|
"license": "Apache-2.0",
|
|
135
135
|
"bin": {
|
data/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "straight-to-video",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.9",
|
|
4
4
|
"description": "Browser-based, hardware-accelerated video upload optimization",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
}
|
|
16
16
|
},
|
|
17
17
|
"dependencies": {
|
|
18
|
-
"mediabunny": "^1.
|
|
18
|
+
"mediabunny": "^1.27.3"
|
|
19
19
|
},
|
|
20
20
|
"keywords": [
|
|
21
21
|
"webcodecs",
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
"test": "script/test"
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|
|
44
|
-
"@playwright/test": "^1.
|
|
44
|
+
"@playwright/test": "^1.57.0",
|
|
45
45
|
"busboy": "^1.6.0"
|
|
46
46
|
}
|
|
47
47
|
}
|