Bilibili-Evolved/registry/lib/plugins/video/download/wasm-output/handler.ts
2025-01-11 11:59:59 +08:00

157 lines
4.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { DownloadPackage, PackageEntry } from '@/core/download'
import { meta } from '@/core/meta'
import { getComponentSettings } from '@/core/settings'
import { Toast } from '@/core/toast'
import { formatFileSize, formatPercent } from '@/core/utils/formatters'
import { title as pluginTitle } from '.'
import type { Options } from '../../../../components/video/download'
import { DownloadVideoAction } from '../../../../components/video/download/types'
import { FFmpeg } from './ffmpeg'
import { getCacheOrFetch, httpGet, toastProgress, toBlobUrl } from './utils'
const ffmpeg = new FFmpeg()
async function loadFFmpeg() {
const toast = Toast.info('正在加载 FFmpeg', `${pluginTitle} - 初始化`)
const progress = toastProgress(toast)
const [worker, core, wasm] = await Promise.all([
getCacheOrFetch(
'ffmpeg-worker',
meta.compilationInfo.altCdn.library.ffmpeg.worker,
progress(0, '正在加载 FFmpeg Worker'),
),
getCacheOrFetch(
'ffmpeg-core',
meta.compilationInfo.altCdn.library.ffmpeg.core,
progress(1, '正在加载 FFmpeg Core'),
),
getCacheOrFetch(
'ffmpeg-wasm',
meta.compilationInfo.altCdn.library.ffmpeg.wasm,
progress(2, '正在加载 FFmpeg WASM'),
),
])
await ffmpeg.load({
workerLoadURL: toBlobUrl(worker, 'text/javascript'),
coreURL: toBlobUrl(core, 'text/javascript'),
wasmURL: toBlobUrl(wasm, 'application/wasm'),
})
toast.message = '完成!'
toast.close()
}
async function single(
name: string,
videoUrl: string,
audioUrl: string,
ffmetadata: string,
outputMkv: boolean,
pageIndex = 1,
totalPages = 1,
) {
const toast = Toast.info('', `${pluginTitle} - ${pageIndex} / ${totalPages}`)
const progress = toastProgress(toast)
const [video, audio] = await Promise.all([
httpGet(videoUrl, progress(0, '正在下载视频流')),
httpGet(audioUrl, progress(1, '正在下载音频流')),
])
await ffmpeg.writeFile('video', video)
await ffmpeg.writeFile('audio', audio)
const args = ['-i', 'video', '-i', 'audio']
if (ffmetadata) {
await ffmpeg.writeFile('ffmetadata', new TextEncoder().encode(ffmetadata))
args.push('-i', 'ffmetadata', '-map_metadata', '2')
if (!outputMkv) {
args.push('-movflags', '+use_metadata_tags')
}
}
args.push('-codec', 'copy', '-f', outputMkv ? 'matroska' : 'mp4', 'output')
console.debug('FFmpeg commandline args:', args.join(' '))
ffmpeg.onProgress(event => {
toast.message = `混流中: ${formatPercent(event.progress)}`
})
await ffmpeg.exec(args)
const output = await ffmpeg.readFile('output')
const outputBlob = new Blob([output], {
type: outputMkv ? 'video/x-matroska' : 'video/mp4',
})
toast.message = '完成!'
toast.duration = 1000
await Promise.all([
ffmpeg.deleteFile('video'),
ffmpeg.deleteFile('audio'),
ffmpeg.deleteFile('output'),
ffmetadata ? ffmpeg.deleteFile('ffmetadata') : Promise.resolve(),
])
await DownloadPackage.single(
name.replace(/.[^/.]+$/, `.${outputMkv ? 'mkv' : 'mp4'}`),
outputBlob,
)
}
export async function run(action: DownloadVideoAction, muxWithMetadata: boolean) {
if (!ffmpeg.loaded) {
await loadFFmpeg()
}
const { infos: pages, extraAssets } = action
let ffmetadata: PackageEntry[]
if (muxWithMetadata) {
const extraAssetsForBrowser = []
for (const { asset, instance } of extraAssets) {
if (!ffmetadata && asset.name === 'saveVideoMetadata' && instance.type === 'ffmetadata') {
ffmetadata = await asset.getAssets(pages, instance)
} else {
extraAssetsForBrowser.push({ asset, instance })
}
}
action.extraAssets = extraAssetsForBrowser
}
const { dashAudioExtension, dashFlacAudioExtension, dashVideoExtension } =
getComponentSettings<Options>('downloadVideo').options
for (let i = 0; i < pages.length; i++) {
const page = pages[i]
const [video, audio] = page.titledFragments
if (
!(
page.fragments.length === 2 &&
video.extension === dashVideoExtension &&
(audio.extension === dashAudioExtension || audio.extension === dashFlacAudioExtension)
)
) {
throw new Error('仅支持 DASH 格式视频和音频')
}
if (video.size + audio.size > 4294967295) {
throw new Error(`仅支持合并 4GB 内的音视频(${formatFileSize(video.size + audio.size)}`)
}
await single(
video.title,
video.url,
audio.url,
<string>ffmetadata?.[i]?.data,
audio.extension === dashFlacAudioExtension,
i + 1,
pages.length,
)
}
}