2024-10-19 10:34:20 +08:00
|
|
|
/*
|
2024-10-19 01:41:45 -04:00
|
|
|
* Vencord, a Discord client mod
|
|
|
|
|
* Copyright (c) 2024 Vendicated and contributors
|
|
|
|
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
|
|
*/
|
2024-10-19 10:34:20 +08:00
|
|
|
|
2024-10-19 01:19:15 -04:00
|
|
|
import type { FFmpeg } from "@ffmpeg/ffmpeg";
|
2025-12-19 21:32:50 -05:00
|
|
|
import { classNameFactory } from "@utils/css";
|
2024-10-19 10:34:20 +08:00
|
|
|
import { waitFor } from "@webpack";
|
2024-10-19 01:19:15 -04:00
|
|
|
import { React } from "@webpack/common";
|
2024-10-19 10:34:20 +08:00
|
|
|
|
2024-10-19 01:19:15 -04:00
|
|
|
import { FFmpegState } from "./types";
|
2024-10-19 10:34:20 +08:00
|
|
|
|
|
|
|
|
export const cl = classNameFactory("vc-more-stickers-");
|
|
|
|
|
export const clPicker = (className: string, ...args: any[]) => cl("picker-" + className, ...args);
|
|
|
|
|
|
2026-02-09 16:15:35 -05:00
|
|
|
const CORS_PROXY = "https://cors.keiran0.workers.dev?url=";
|
2024-10-19 10:34:20 +08:00
|
|
|
|
|
|
|
|
function corsUrl(url: string | URL) {
|
|
|
|
|
return CORS_PROXY + encodeURIComponent(url.toString());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function corsFetch(url: string | URL, init?: RequestInit | undefined) {
|
|
|
|
|
return fetch(corsUrl(url), init);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export class Mutex {
|
|
|
|
|
current = Promise.resolve();
|
|
|
|
|
lock() {
|
|
|
|
|
let _resolve: () => void;
|
|
|
|
|
const p = new Promise(resolve => {
|
|
|
|
|
_resolve = () => resolve();
|
|
|
|
|
}) as Promise<void>;
|
|
|
|
|
// Caller gets a promise that resolves when the current outstanding
|
|
|
|
|
// lock resolves
|
|
|
|
|
const rv = this.current.then(() => _resolve);
|
|
|
|
|
// Don't allow the next request until the new promise is done
|
|
|
|
|
this.current = p;
|
|
|
|
|
// Return the new promise
|
|
|
|
|
return rv;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export let FFmpegStateContext: React.Context<FFmpegState | undefined> | undefined;
|
|
|
|
|
waitFor("createContext", () => {
|
|
|
|
|
FFmpegStateContext = React.createContext<FFmpegState | undefined>(undefined);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
export async function loadFFmpeg(ffmpeg: FFmpeg, setLoaded: () => void) {
|
|
|
|
|
console.log("Loading FFmpeg...");
|
2025-03-25 11:20:45 -04:00
|
|
|
const baseURL = "https://cdn.jsdelivr.net/npm/@ffmpeg/core@0.12.6/dist/esm";
|
2024-10-19 10:34:20 +08:00
|
|
|
|
2025-03-25 11:18:58 -04:00
|
|
|
const classWorkerRaw = "/// <reference no-default-lib=\"true\" />\n/// <reference lib=\"esnext\" />\n/// <reference lib=\"webworker\" />\nconst MIME_TYPE_JAVASCRIPT = \"text/javascript\";\nconst MIME_TYPE_WASM = \"application/wasm\";\nconst CORE_VERSION = \"0.12.6\";\nconst CORE_URL = `https://cdn.jsdelivr.net/npm/@ffmpeg/core@${CORE_VERSION}/dist/umd/ffmpeg-core.js`;\nvar FFMessageType;\n(function(FFMessageType) {\n FFMessageType[\"LOAD\"] = \"LOAD\";\n FFMessageType[\"EXEC\"] = \"EXEC\";\n FFMessageType[\"WRITE_FILE\"] = \"WRITE_FILE\";\n FFMessageType[\"READ_FILE\"] = \"READ_FILE\";\n FFMessageType[\"DELETE_FILE\"] = \"DELETE_FILE\";\n FFMessageType[\"RENAME\"] = \"RENAME\";\n FFMessageType[\"CREATE_DIR\"] = \"CREATE_DIR\";\n FFMessageType[\"LIST_DIR\"] = \"LIST_DIR\";\n FFMessageType[\"DELETE_DIR\"] = \"DELETE_DIR\";\n FFMessageType[\"ERROR\"] = \"ERROR\";\n FFMessageType[\"DOWNLOAD\"] = \"DOWNLOAD\";\n FFMessageType[\"PROGRESS\"] = \"PROGRESS\";\n FFMessageType[\"LOG\"] = \"LOG\";\n FFMessageType[\"MOUNT\"] = \"MOUNT\";\n FFMessageType[\"UNMOUNT\"] = \"UNMOUNT\";\n})(FFMessageType || (FFMessageType = {}));\n\n\nconst ERROR_UNKNOWN_MESSAGE_TYPE = new Error(\"unknown message type\");\nconst ERROR_NOT_LOADED = new Error(\"ffmpeg is not loaded, call `await ffmpeg.load()` first\");\nconst ERROR_TERMINATED = new Error(\"called FFmpeg.terminate()\");\nconst ERROR_IMPORT_FAILURE = new Error(\"failed to import ffmpeg-core.js\");\n\nlet ffmpeg;\nconst load = async ({ coreURL: _coreURL, wasmURL: _wasmURL, workerURL: _workerURL, }) => {\n const first = !ffmpeg;\n try {\n if (!_coreURL)\n _coreURL = CORE_URL;\n // when web worker type is `classic`.\n importScripts(_coreURL);\n }\n catch {\n if (!_coreURL)\n _coreURL = CORE_URL.replace('/umd/', '/esm/');\n // when web worker type is `module`.\n self.createFFmpegCore = (await import(\n /* webpackIgnore: true */ /* @vite-ignore */ _coreURL)).default;\n if (!self.createFFmpegCore) {\n throw ERROR_IMPORT_FAILURE;\n }\n }\n const coreURL = _coreURL;\n const wasmURL = _wasmURL ? _wasmURL : _coreURL.replace(/.js$/g, \".wasm\");\n const workerURL = _workerURL\n ? _workerURL\n : _coreURL.replace(/.js$/g, \".worker.js\");\n ffmpeg = await self.createFFmpegCore({\n // Fix `Overload resolution failed.` when using multi-threaded ffmpeg-core.\n // Encoded wasmURL and workerURL in the URL as a hack to fix locateFile issue.\n mainScriptUrlOrBlob: `${coreURL}#${btoa(JSON.stringify({ wasmURL, workerURL }))}`,\n });\n ffmpeg.setLogger((data) => self.postMessage({ type: FFMessageType.LOG, data }));\n ffmpeg.setProgress((data) => self.postMessage({\n type: FFMessageType.PROGRESS,\n data,\n }));\n return first;\n};\nconst exec = ({ args, timeout = -1 }) => {\n ffmpeg.setTimeout(timeout);\n ffmpeg.exec(...args);\n const ret = ffmpeg.ret;\n ffmpeg.reset();\n return ret;\n};\nconst writeFile = ({ path, data }) => {\n ffmpeg.FS.writeFile(path, data);\n return true;\n};\nconst readFile = ({ path, encoding }) => ffmpeg.FS.readFile(path, { encoding });\n// TODO: check if deletion works.\nconst deleteFile = ({ path }) => {\n ffmpeg.FS.unlink(path);\n return true;\n};\nconst rename = ({ oldPath, newPath }) => {\n ffmpeg.FS.rename(oldPath, newPath);\n return true;\n};\n// TODO: check if creation works.\nconst createDir = ({ path }) => {\n ffmpeg.FS.mkdir(path);\n return true;\n};\nconst listDir = ({ path }) => {\n const names = ffmpeg.FS.readdir(path);\n const nodes = [];\n for (const name of names) {\n const stat = ffmpeg.FS.stat(`${path}/${name}`);\n const isDir = ffmpeg.FS.isDir(stat.mode);\n nodes.push({ name, isDir });\n }\n return nodes;\n};\n// TODO: check if deletion works.\nconst deleteDir = ({ path }) => {\n ffmpeg.FS.rmdir(path);\n return true;\n};\nconst mount = ({ fsType, options, mountPoint }) => {\n const str = fsType;\n const fs = ffmpeg.FS.filesystems[str];\n if (!fs)\n return false;\n ffmpeg.FS.mount(fs, options, mountPoint);\n return true;\n};\nconst unmoun
|
2024-10-19 01:19:15 -04:00
|
|
|
const classWorkerBlob = new Blob([(new TextEncoder()).encode(classWorkerRaw)], { type: "text/javascript" });
|
2024-10-19 10:34:20 +08:00
|
|
|
const classWorkerUrl = URL.createObjectURL(classWorkerBlob);
|
|
|
|
|
|
|
|
|
|
await ffmpeg.load({
|
|
|
|
|
coreURL: `${baseURL}/ffmpeg-core.js`,
|
|
|
|
|
wasmURL: `${baseURL}/ffmpeg-core.wasm`,
|
|
|
|
|
workerURL: `${baseURL}/ffmpeg-core.worker.js`,
|
|
|
|
|
classWorkerURL: classWorkerUrl,
|
|
|
|
|
});
|
|
|
|
|
setLoaded();
|
|
|
|
|
console.log("FFmpeg loaded!");
|
2024-10-19 01:19:15 -04:00
|
|
|
}
|