(WIP) add lonestar compression

This commit is contained in:
Samuel Newman
2024-06-07 15:29:32 +03:00
parent ed17e94cec
commit e2223fe3ed
2 changed files with 126 additions and 1 deletions
-1
View File
@@ -34,6 +34,5 @@ export async function compressVideo(
return {
uri: success ? newFile : null,
session: result,
}
}
+126
View File
@@ -0,0 +1,126 @@
import * as Toast from '#/view/com/util/Toast'
const MAX_WIDTH = 1920
const MAX_HEIGHT = 1920
const MAX_VIDEO_SIZE = 1024 * 1024 * 100 // 100MB
export async function compressVideo(
file: string,
callbacks?: {
onProgress: (progress: number) => void
},
) {
const {onProgress} = callbacks || {}
const blob = await fetch(file).then(res => res.blob())
const objectUrl = URL.createObjectURL(blob)
const videoEl = document.createElement('video')
videoEl.setAttribute('playsinline', 'playsinline')
videoEl.setAttribute('controls', 'controls')
videoEl.setAttribute('muted', 'muted')
videoEl.setAttribute('src', objectUrl)
try {
await new Promise((resolve, reject) => {
videoEl.addEventListener('error', reject, {once: true})
videoEl.addEventListener('loadedmetadata', resolve, {once: true})
})
} catch (e) {
console.error(e)
Toast.show('Failed to load video, this video format may not be supported')
}
let {videoWidth, videoHeight} = videoEl
let outputWidth = videoWidth
let outputHeight = videoHeight
if (outputWidth > outputHeight) {
if (outputWidth > MAX_WIDTH) {
const scale = MAX_WIDTH / outputWidth
outputWidth = Math.round(outputWidth * scale)
outputHeight = Math.round(outputHeight * scale)
}
} else {
if (outputHeight > MAX_HEIGHT) {
const scale = MAX_HEIGHT / outputHeight
outputWidth = Math.round(outputWidth * scale)
outputHeight = Math.round(outputHeight * scale)
}
}
if (outputWidth % 2 === 1) outputWidth--
if (outputHeight % 2 === 1) outputHeight--
console.log({outputWidth, outputHeight})
const canvas = document.createElement('canvas')
canvas.width = outputWidth
canvas.height = outputHeight
const ctx = canvas.getContext('2d')
if (!ctx) throw new Error('Could not get canvas context')
ctx.fillStyle = '#fff'
console.log('canvas', canvas)
let wasTruncated = false
const videoBlob = await new Promise<Blob>(async resolve => {
const chunks: Blob[] = []
let options = {
mimeType: 'video/webm;codecs=h264',
videoBitsPerSecond: 200000,
}
const recorder = new MediaRecorder(canvas.captureStream(25), options)
recorder.onerror = console.log
recorder.ondataavailable = e => {
let size = chunks.reduce((acc, chunk) => acc + chunk.size, 0)
if (size + e.data.size > MAX_VIDEO_SIZE) {
wasTruncated = true
recorder.stop()
} else {
chunks.push(e.data)
}
}
recorder.onstop = () => {
resolve(new Blob(chunks, {type: recorder.mimeType}))
}
videoEl.play()
recorder.start()
let lastCapture = Date.now()
while (
recorder.state === 'recording' &&
videoEl.currentTime < videoEl.duration
) {
await new Promise(r => setTimeout(r, 1)) // NOTE: don't use requestAnimationFrame because it pauses with the tab isnt focused
onProgress?.(videoEl.currentTime / videoEl.duration)
ctx.fillRect(0, 0, outputWidth, outputHeight)
ctx.drawImage(
videoEl,
0,
0,
videoWidth,
videoHeight,
0,
0,
outputWidth,
outputHeight,
)
if (Date.now() - lastCapture > 500) {
recorder.requestData()
lastCapture = Date.now()
}
}
if (recorder.state === 'recording') {
recorder.stop()
}
})
if (wasTruncated) {
Toast.show('Video was too long and was truncated')
}
URL.revokeObjectURL(objectUrl)
return {
uri: URL.createObjectURL(videoBlob),
}
}