forked from forks/ffprobe-wasm-npm
Compare commits
11 Commits
v0.2.0
...
efa4e6f0a2
| Author | SHA1 | Date | |
|---|---|---|---|
|
efa4e6f0a2
|
|||
|
24a55c018f
|
|||
|
bc9e1670b2
|
|||
|
c0e13cdaa1
|
|||
|
3d1951c50a
|
|||
|
90864d1b05
|
|||
|
e7dbb8c0c9
|
|||
|
cece48aaeb
|
|||
|
5214d5c530
|
|||
|
ff2bef3e9c
|
|||
|
|
193d2e7b9e |
2
.gitmodules
vendored
2
.gitmodules
vendored
@@ -1,3 +1,3 @@
|
||||
[submodule "ffprobe-wasm-app"]
|
||||
path = ffprobe-wasm-app
|
||||
url = https://github.com/alfg/ffprobe-wasm
|
||||
url = https://git.cislabs.de/cis-oss/ffprobe-wasm
|
||||
|
||||
1
.prettierrc.yml
Normal file
1
.prettierrc.yml
Normal file
@@ -0,0 +1 @@
|
||||
trailingComma: all
|
||||
@@ -4,7 +4,7 @@ Gather information from multimedia streams. Works on the browser and Node.js.
|
||||
|
||||
Uses the code at [alfg/ffprobe-wasm](https://github.com/alfg/ffprobe-wasm), but in a packaged format, so it can be reused in other projects.
|
||||
|
||||
_Note_: This project doesn't build or use FFProbe. Instead it uses FFmpeg's libavformat and libavcodec to output similar results.
|
||||
_For limitations and recommendations, see [Notes section](#notes)._
|
||||
|
||||
## Installation
|
||||
|
||||
@@ -42,5 +42,7 @@ input.addEventListener("change", (event) => {
|
||||
|
||||
## Notes
|
||||
|
||||
- This project doesn't build or use FFprobe. Instead it uses FFmpeg's libavformat and libavcodec to output similar results. This means that not everything that FFprobe supports is bundled, so there are some containers and codecs that are not supported.
|
||||
- In Node.js, it works on version >= 16.
|
||||
- In browser, `SharedArrayBuffer` is being used. To enable this in your server, read [Security requirements](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/SharedArrayBuffer#security_requirements).
|
||||
- In browser, everything is bundled in the `browser.mjs` script. When gzipped, this file is bigger than 1 MiB, so it's recommended to use `import()` to lazy load the asset. The good side of this is that you don't have to configure your bundler to include the worker or wasm files and you won't face [same-origin](https://developer.mozilla.org/en-US/docs/Web/API/Worker/Worker) issues with the worker.
|
||||
|
||||
1
build.sh
1
build.sh
@@ -12,7 +12,6 @@ cd ffprobe-wasm-app
|
||||
docker-compose run ffprobe-wasm make
|
||||
cd ..
|
||||
cp -R ffprobe-wasm-app/dist dist
|
||||
node scripts/replace.js
|
||||
cp src/*.d.* dist
|
||||
|
||||
# Build browser/node workers
|
||||
|
||||
Submodule ffprobe-wasm-app updated: c0f96a1755...0720911da0
2528
package-lock.json
generated
2528
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
14
package.json
14
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "ffprobe-wasm",
|
||||
"version": "0.2.0",
|
||||
"version": "0.3.0",
|
||||
"description": "ffprobe-like for browser and node, powered by WebAssembly",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -19,11 +19,11 @@
|
||||
"license": "MIT",
|
||||
"exports": {
|
||||
"node": "./node.mjs",
|
||||
"types": "./ffprobe-worker.d.ts",
|
||||
"types": "./ffprobe-worker.d.mts",
|
||||
"default": "./browser.mjs"
|
||||
},
|
||||
"main": "./node.mjs",
|
||||
"types": "./ffprobe-worker.d.ts",
|
||||
"types": "./ffprobe-worker.d.mts",
|
||||
"browser": "./browser.mjs",
|
||||
"scripts": {
|
||||
"build": "npm run tsc && npm run vite",
|
||||
@@ -31,8 +31,10 @@
|
||||
"vite": "vite build"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^17.0.21",
|
||||
"typescript": "^4.5.5",
|
||||
"vite": "^2.8.6"
|
||||
"@types/node": "^25.2.3",
|
||||
"typescript": "^5.9.3",
|
||||
"vite": "^7.3.1",
|
||||
"vite-plugin-top-level-await": "^1.6.0",
|
||||
"vite-plugin-wasm": "^3.5.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
const { readFile, writeFile } = require("fs/promises");
|
||||
const { dirname, resolve } = require("path");
|
||||
|
||||
main();
|
||||
|
||||
async function main() {
|
||||
const root = dirname(__dirname);
|
||||
|
||||
const wasmJsPath = resolve(root, "dist/ffprobe-wasm.mjs");
|
||||
|
||||
let content = await readFile(wasmJsPath, { encoding: "utf8" });
|
||||
|
||||
content = `\
|
||||
import initWasmInstance from "./ffprobe-wasm.wasm";
|
||||
const initWasm = (info) =>
|
||||
initWasmInstance(info).then((exports) => ({ instance: { exports } }));
|
||||
${content}`;
|
||||
|
||||
content = content.replace(`import.meta.url`, `''`);
|
||||
|
||||
content = content.replace(
|
||||
`instantiateAsync().catch(readyPromiseReject)`,
|
||||
`initWasm(info).then(receiveInstantiatedSource, readyPromiseReject)`
|
||||
);
|
||||
|
||||
await writeFile(wasmJsPath, content, { encoding: "utf8" });
|
||||
}
|
||||
@@ -1,12 +1,14 @@
|
||||
import type { FFprobeWorker as AbstractFFprobeWorker } from "./ffprobe-worker.mjs";
|
||||
import type {
|
||||
Chapter,
|
||||
ChapterTag,
|
||||
Disposition,
|
||||
FileInfo,
|
||||
Format,
|
||||
Frame,
|
||||
FramesInfo,
|
||||
Rational,
|
||||
Stream,
|
||||
} from "./ffprobe-wasm.mjs";
|
||||
import type { FFprobeWorker as AbstractFFprobeWorker } from "./ffprobe-worker.js";
|
||||
} from "./types.mjs";
|
||||
import BrowserWorker from "./worker-browser.mjs?worker&inline";
|
||||
import type {
|
||||
IncomingMessage,
|
||||
@@ -23,10 +25,13 @@ export class FFprobeWorker implements AbstractFFprobeWorker {
|
||||
|
||||
async getFileInfo(file: File): Promise<FileInfo> {
|
||||
this.#validateFile(file);
|
||||
return this.#postMessage({
|
||||
const fileInfo: FileInfo = await this.#postMessage({
|
||||
type: "getFileInfo",
|
||||
payload: [file.name, { files: [file] }],
|
||||
});
|
||||
fileInfo.format.filename = file.name;
|
||||
fileInfo.format.size = file.size.toString();
|
||||
return fileInfo;
|
||||
}
|
||||
|
||||
async getFrames(file: File, offset: number): Promise<FramesInfo> {
|
||||
@@ -44,7 +49,7 @@ export class FFprobeWorker implements AbstractFFprobeWorker {
|
||||
#validateFile(file: File | string): asserts file is File {
|
||||
if (typeof file === "string") {
|
||||
throw new Error(
|
||||
"String only supported in Node.js, you must provide a File"
|
||||
"String only supported in Node.js, you must provide a File",
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -71,4 +76,13 @@ export class FFprobeWorker implements AbstractFFprobeWorker {
|
||||
}
|
||||
}
|
||||
|
||||
export type { Chapter, ChapterTag, FileInfo, Frame, FramesInfo, Stream };
|
||||
export type {
|
||||
Chapter,
|
||||
Disposition,
|
||||
FileInfo,
|
||||
Format,
|
||||
Frame,
|
||||
FramesInfo,
|
||||
Rational,
|
||||
Stream,
|
||||
};
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { FileInfo, FramesInfo } from "./types.mjs";
|
||||
|
||||
export interface FFprobe {
|
||||
get_file_info(path: string): Raw<FileInfo>;
|
||||
get_frames(path: string, offset: number): Raw<FramesInfo>;
|
||||
@@ -69,22 +71,7 @@ export interface AnalyzePathReturn {
|
||||
parentObject: any | null;
|
||||
}
|
||||
|
||||
export interface FileInfo {
|
||||
bit_rate: number;
|
||||
chapters: Chapter[];
|
||||
/**
|
||||
* Duration in microseconds
|
||||
*/
|
||||
duration: number;
|
||||
flags: number;
|
||||
name: string;
|
||||
nb_chapters: number;
|
||||
nb_streams: number;
|
||||
streams: Stream[];
|
||||
url: string;
|
||||
}
|
||||
|
||||
export interface Collection<T> {
|
||||
export interface Vector<T> {
|
||||
count: { value: number };
|
||||
ptr: number;
|
||||
ptrType: any;
|
||||
@@ -92,57 +79,17 @@ export interface Collection<T> {
|
||||
size(): number;
|
||||
}
|
||||
|
||||
export type Raw<T> = {
|
||||
[K in keyof T]: T[K] extends Array<infer U> ? Collection<Raw<U>> : T[K];
|
||||
};
|
||||
|
||||
export interface Chapter {
|
||||
end: number;
|
||||
id: number;
|
||||
start: number;
|
||||
tags: ChapterTag[];
|
||||
/**
|
||||
* @example "1/1000"
|
||||
*/
|
||||
time_base: string;
|
||||
}
|
||||
|
||||
export interface ChapterTag {
|
||||
export interface DictionaryEntry {
|
||||
key: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
export interface Stream {
|
||||
bit_rate: number;
|
||||
channels: number;
|
||||
codec_name: string;
|
||||
codec_type: number;
|
||||
duration: number;
|
||||
format: string;
|
||||
frame_size: number;
|
||||
height: number;
|
||||
id: number;
|
||||
level: number;
|
||||
profile: string;
|
||||
sample_rate: number;
|
||||
start_time: number;
|
||||
width: number;
|
||||
}
|
||||
|
||||
export interface FramesInfo {
|
||||
avg_frame_rate: number;
|
||||
duration: number;
|
||||
frames: Frame[];
|
||||
gop_size: number;
|
||||
nb_frames: number;
|
||||
time_base: number;
|
||||
}
|
||||
|
||||
export interface Frame {
|
||||
dts: number;
|
||||
frame_number: number;
|
||||
pict_type: number;
|
||||
pkt_size: number;
|
||||
pos: number;
|
||||
pts: number;
|
||||
}
|
||||
export type Raw<T> = {
|
||||
[K in keyof T]: T[K] extends Array<infer U>
|
||||
? Vector<Raw<U>>
|
||||
: T[K] extends Record<string, string>
|
||||
? Vector<DictionaryEntry>
|
||||
: T[K] extends string | number | boolean | undefined | null
|
||||
? T[K]
|
||||
: Raw<T[K]>;
|
||||
};
|
||||
@@ -1,6 +1,6 @@
|
||||
import { FFprobe } from "./ffprobe-wasm-shared";
|
||||
import { FFprobe } from "./ffprobe-wasm-shared.mjs";
|
||||
|
||||
export * from "./ffprobe-wasm-shared";
|
||||
export * from "./ffprobe-wasm-shared.mjs";
|
||||
|
||||
export default function loadFFprobe(ffprobe?: FFprobeInit): Promise<FFprobe>;
|
||||
|
||||
|
||||
4
src/ffprobe-wasm.d.ts
vendored
4
src/ffprobe-wasm.d.ts
vendored
@@ -1,6 +1,6 @@
|
||||
import { FFprobe } from "./ffprobe-wasm-shared";
|
||||
import { FFprobe } from "./ffprobe-wasm-shared.mjs";
|
||||
|
||||
export * from "./ffprobe-wasm-shared";
|
||||
export * from "./ffprobe-wasm-shared.mjs";
|
||||
|
||||
declare const ffprobe: FFprobe;
|
||||
|
||||
|
||||
17
src/ffprobe-worker.d.mts
Normal file
17
src/ffprobe-worker.d.mts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { FileInfo, FramesInfo } from "./types.mjs";
|
||||
|
||||
export * from "./types.mjs";
|
||||
|
||||
export declare class FFprobeWorker {
|
||||
/**
|
||||
* This function tries to be equivalent to
|
||||
* ```
|
||||
* ffprobe -hide_banner -loglevel fatal -show_format -show_streams -show_chapters -show_private_data -print_format json
|
||||
* ```
|
||||
*/
|
||||
getFileInfo(file: File | string): Promise<FileInfo>;
|
||||
|
||||
getFrames(file: File | string, offset: number): Promise<FramesInfo>;
|
||||
|
||||
terminate(): void;
|
||||
}
|
||||
18
src/ffprobe-worker.d.ts
vendored
18
src/ffprobe-worker.d.ts
vendored
@@ -1,18 +0,0 @@
|
||||
import type {
|
||||
Chapter,
|
||||
ChapterTag,
|
||||
FileInfo,
|
||||
Frame,
|
||||
FramesInfo,
|
||||
Stream,
|
||||
} from "./ffprobe-wasm-shared";
|
||||
|
||||
export declare class FFprobeWorker {
|
||||
getFileInfo(file: File | string): Promise<FileInfo>;
|
||||
|
||||
getFrames(file: File | string, offset: number): Promise<FramesInfo>;
|
||||
|
||||
terminate(): void;
|
||||
}
|
||||
|
||||
export { Chapter, ChapterTag, FileInfo, Frame, FramesInfo, Stream };
|
||||
27
src/node.mts
27
src/node.mts
@@ -1,15 +1,18 @@
|
||||
import { stat } from "fs/promises";
|
||||
import { basename, dirname } from "path";
|
||||
import { fileURLToPath } from "url";
|
||||
import { MessageChannel, Worker } from "worker_threads";
|
||||
import type { FFprobeWorker as AbstractFFprobeWorker } from "./ffprobe-worker.mjs";
|
||||
import type {
|
||||
Chapter,
|
||||
ChapterTag,
|
||||
Disposition,
|
||||
FileInfo,
|
||||
Format,
|
||||
Frame,
|
||||
FramesInfo,
|
||||
Rational,
|
||||
Stream,
|
||||
} from "./ffprobe-wasm.js";
|
||||
import type { FFprobeWorker as AbstractFFprobeWorker } from "./ffprobe-worker.js";
|
||||
} from "./types.mjs";
|
||||
import type {
|
||||
IncomingMessage,
|
||||
IncomingData,
|
||||
@@ -26,10 +29,13 @@ export class FFprobeWorker implements AbstractFFprobeWorker {
|
||||
|
||||
async getFileInfo(filePath: string): Promise<FileInfo> {
|
||||
this.#validateFile(filePath);
|
||||
return this.#postMessage({
|
||||
const fileInfo: FileInfo = await this.#postMessage({
|
||||
type: "getFileInfo",
|
||||
payload: [basename(filePath), { root: dirname(filePath) }],
|
||||
});
|
||||
fileInfo.format.filename = filePath;
|
||||
fileInfo.format.size = (await stat(filePath)).size.toString();
|
||||
return fileInfo;
|
||||
}
|
||||
|
||||
async getFrames(filePath: string, offset: number): Promise<FramesInfo> {
|
||||
@@ -47,7 +53,7 @@ export class FFprobeWorker implements AbstractFFprobeWorker {
|
||||
#validateFile(filePath: string | File): asserts filePath is string {
|
||||
if (typeof filePath === "object") {
|
||||
throw new Error(
|
||||
"File object only supported in Browser, you must provide a string (path)"
|
||||
"File object only supported in Browser, you must provide a string (path)",
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -73,4 +79,13 @@ export class FFprobeWorker implements AbstractFFprobeWorker {
|
||||
}
|
||||
}
|
||||
|
||||
export type { Chapter, ChapterTag, FileInfo, Frame, FramesInfo, Stream };
|
||||
export type {
|
||||
Chapter,
|
||||
Disposition,
|
||||
FileInfo,
|
||||
Format,
|
||||
Frame,
|
||||
FramesInfo,
|
||||
Rational,
|
||||
Stream,
|
||||
};
|
||||
|
||||
131
src/types.d.mts
Normal file
131
src/types.d.mts
Normal file
@@ -0,0 +1,131 @@
|
||||
export type Rational = `${number}/${number}`;
|
||||
|
||||
export interface FileInfo {
|
||||
streams: Stream[];
|
||||
chapters: Chapter[];
|
||||
format: Format;
|
||||
}
|
||||
|
||||
export interface Format {
|
||||
filename: string;
|
||||
nb_streams: number;
|
||||
nb_programs: number;
|
||||
format_name: string;
|
||||
format_long_name: string;
|
||||
start_time: string;
|
||||
duration: string;
|
||||
size: string;
|
||||
bit_rate: string;
|
||||
probe_score: number;
|
||||
tags: Record<string, string>;
|
||||
}
|
||||
|
||||
export interface Chapter {
|
||||
/**
|
||||
* Chapter end time in time_base units
|
||||
*/
|
||||
end: number;
|
||||
/**
|
||||
* unique ID to identify the chapter
|
||||
*/
|
||||
id: number;
|
||||
tags: Record<string, string>;
|
||||
/**
|
||||
* Chapter start time in time_base units
|
||||
*/
|
||||
start: number;
|
||||
/**
|
||||
* Time base in which the start/end timestamps are specified
|
||||
* @example "1/1000"
|
||||
*/
|
||||
time_base: Rational;
|
||||
}
|
||||
|
||||
export interface Stream {
|
||||
index: number;
|
||||
codec_name: string;
|
||||
codec_long_name: string;
|
||||
profile: string;
|
||||
codec_type: string;
|
||||
codec_tag_string: string;
|
||||
codec_tag: string;
|
||||
|
||||
width: number;
|
||||
height: number;
|
||||
codec_width: number;
|
||||
codec_height: number;
|
||||
closed_captions: number;
|
||||
has_b_frames: number;
|
||||
pix_fmt: string;
|
||||
level: number;
|
||||
color_range: string;
|
||||
color_primaries: string;
|
||||
chroma_location: string;
|
||||
refs: number;
|
||||
is_avc: string;
|
||||
nal_length_size: string;
|
||||
|
||||
sample_fmt: string;
|
||||
sample_rate: string;
|
||||
channels: number;
|
||||
channel_layout: string;
|
||||
bits_per_sample: number;
|
||||
|
||||
r_frame_rate: string;
|
||||
avg_frame_rate: string;
|
||||
/**
|
||||
* This is the fundamental unit of time (in seconds) in terms
|
||||
* of which frame timestamps are represented.
|
||||
*/
|
||||
time_base: Rational;
|
||||
start_pts: number;
|
||||
start_time: string;
|
||||
duration_ts: number;
|
||||
/**
|
||||
* Duration of the stream, in stream time base.
|
||||
* If a source file does not specify a duration, but does specify
|
||||
* a bitrate, this value will be estimated from bitrate and file size.
|
||||
*/
|
||||
duration: string;
|
||||
/**
|
||||
* Total stream bitrate in bit/s, 0 if not available.
|
||||
*/
|
||||
bit_rate: string;
|
||||
bits_per_raw_sample: string;
|
||||
nb_frames: string;
|
||||
disposition: Disposition;
|
||||
tags: Record<string, string>;
|
||||
}
|
||||
|
||||
export interface Disposition {
|
||||
default: 0 | 1;
|
||||
dub: 0 | 1;
|
||||
original: 0 | 1;
|
||||
comment: 0 | 1;
|
||||
lyrics: 0 | 1;
|
||||
karaoke: 0 | 1;
|
||||
forced: 0 | 1;
|
||||
hearing_impaired: 0 | 1;
|
||||
visual_impaired: 0 | 1;
|
||||
clean_effects: 0 | 1;
|
||||
attached_pic: 0 | 1;
|
||||
timed_thumbnails: 0 | 1;
|
||||
}
|
||||
|
||||
export interface FramesInfo {
|
||||
avg_frame_rate: number;
|
||||
duration: number;
|
||||
frames: Frame[];
|
||||
gop_size: number;
|
||||
nb_frames: number;
|
||||
time_base: number;
|
||||
}
|
||||
|
||||
export interface Frame {
|
||||
dts: number;
|
||||
frame_number: number;
|
||||
pict_type: number;
|
||||
pkt_size: number;
|
||||
pos: number;
|
||||
pts: number;
|
||||
}
|
||||
@@ -1,7 +1,17 @@
|
||||
import type { MessagePort as NodeMessagePort } from "worker_threads";
|
||||
import type { Chapter, ChapterTag, FFprobe, FileInfo, FramesInfo, FSFilesystems, FSMountOptions, Stream } from "./ffprobe-wasm-shared";
|
||||
import type {
|
||||
DictionaryEntry,
|
||||
FFprobe,
|
||||
FSFilesystems,
|
||||
FSMountOptions,
|
||||
Raw,
|
||||
Vector,
|
||||
} from "./ffprobe-wasm-shared.mjs";
|
||||
import { FileInfo, FramesInfo, Stream } from "./types.mjs";
|
||||
|
||||
export type IncomingMessage = { port: MessagePort | NodeMessagePort } & IncomingData;
|
||||
export type IncomingMessage = {
|
||||
port: MessagePort | NodeMessagePort;
|
||||
} & IncomingData;
|
||||
|
||||
export type IncomingData =
|
||||
| {
|
||||
@@ -63,7 +73,33 @@ export function createListener(
|
||||
}
|
||||
}
|
||||
|
||||
async function getFileInfo(fileName: string, mountOptions: FSMountOptions): Promise<FileInfo> {
|
||||
function vectorToArray<T>(vector: Vector<T>): T[] {
|
||||
const array: T[] = [];
|
||||
for (let i = 0; i < vector.size(); i++) {
|
||||
array.push(vector.get(i));
|
||||
}
|
||||
return array;
|
||||
}
|
||||
|
||||
function dictionaryVectorToRecord(
|
||||
vector: Vector<DictionaryEntry>,
|
||||
): Record<string, string> {
|
||||
return Object.fromEntries(
|
||||
vectorToArray(vector).map(({ key, value }) => [key, value]),
|
||||
);
|
||||
}
|
||||
|
||||
function serializeStreams(streams: Vector<Raw<Stream>>) {
|
||||
return vectorToArray(streams).map((stream) => ({
|
||||
...stream,
|
||||
tags: dictionaryVectorToRecord(stream.tags),
|
||||
}));
|
||||
}
|
||||
|
||||
async function getFileInfo(
|
||||
fileName: string,
|
||||
mountOptions: FSMountOptions,
|
||||
): Promise<FileInfo> {
|
||||
const { FS, get_file_info } = await ffprobePromise;
|
||||
try {
|
||||
if (!FS.analyzePath("/work").exists) {
|
||||
@@ -71,32 +107,18 @@ export function createListener(
|
||||
}
|
||||
FS.mount(FS.filesystems[fsType], mountOptions, "/work");
|
||||
|
||||
// Call the wasm module.
|
||||
const rawInfo = get_file_info(`/work/${fileName}`);
|
||||
|
||||
// Remap streams into collection.
|
||||
const streams: Stream[] = [];
|
||||
for (let i = 0; i < rawInfo.streams.size(); i++) {
|
||||
streams.push(rawInfo.streams.get(i));
|
||||
}
|
||||
|
||||
// Remap chapters into collection.
|
||||
const chapters: Chapter[] = [];
|
||||
for (let i = 0; i < rawInfo.chapters.size(); i++) {
|
||||
const rawChapter = rawInfo.chapters.get(i);
|
||||
|
||||
const tags: ChapterTag[] = [];
|
||||
for (let j = 0; j < rawChapter.tags.size(); j++) {
|
||||
tags.push(rawChapter.tags.get(j));
|
||||
}
|
||||
|
||||
chapters.push({ ...rawChapter, tags });
|
||||
}
|
||||
|
||||
return {
|
||||
...rawInfo,
|
||||
streams,
|
||||
chapters,
|
||||
streams: serializeStreams(rawInfo.streams),
|
||||
chapters: vectorToArray(rawInfo.chapters).map((chapter) => ({
|
||||
...chapter,
|
||||
tags: dictionaryVectorToRecord(chapter.tags),
|
||||
})),
|
||||
format: {
|
||||
...rawInfo.format,
|
||||
tags: dictionaryVectorToRecord(rawInfo.format.tags),
|
||||
},
|
||||
};
|
||||
} finally {
|
||||
// Cleanup mount.
|
||||
@@ -104,7 +126,11 @@ export function createListener(
|
||||
}
|
||||
}
|
||||
|
||||
async function getFrames(fileName: string, mountOptions: FSMountOptions, offset: number): Promise<FramesInfo> {
|
||||
async function getFrames(
|
||||
fileName: string,
|
||||
mountOptions: FSMountOptions,
|
||||
offset: number,
|
||||
): Promise<FramesInfo> {
|
||||
const { FS, get_frames } = await ffprobePromise;
|
||||
try {
|
||||
if (!FS.analyzePath("/work").exists) {
|
||||
@@ -112,18 +138,11 @@ export function createListener(
|
||||
}
|
||||
FS.mount(FS.filesystems.WORKERFS, mountOptions, "/work");
|
||||
|
||||
// Call the wasm module.
|
||||
const framesInfo = get_frames(`/work/${fileName}`, offset);
|
||||
|
||||
// Remap frames into collection.
|
||||
const frames = [];
|
||||
for (let i = 0; i < framesInfo.frames.size(); i++) {
|
||||
frames.push(framesInfo.frames.get(i));
|
||||
}
|
||||
|
||||
return {
|
||||
...framesInfo,
|
||||
frames,
|
||||
frames: vectorToArray(framesInfo.frames),
|
||||
};
|
||||
} finally {
|
||||
// Cleanup mount.
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { resolve } from "path";
|
||||
import { defineConfig } from "vite";
|
||||
import topLevelAwait from "vite-plugin-top-level-await";
|
||||
import wasm from "vite-plugin-wasm";
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
@@ -14,18 +16,16 @@ export default defineConfig({
|
||||
minify: false,
|
||||
sourcemap: true,
|
||||
},
|
||||
worker: {
|
||||
plugins: [
|
||||
{
|
||||
name: "append-source-url",
|
||||
generateBundle(options, bundle) {
|
||||
Object.entries(bundle).forEach(([file, output]) => {
|
||||
if (output.type === "chunk") {
|
||||
output.code += `\n//# sourceURL=${file}`;
|
||||
}
|
||||
});
|
||||
},
|
||||
},
|
||||
|
||||
wasm(),
|
||||
topLevelAwait()
|
||||
],
|
||||
worker: {
|
||||
plugins: () => [
|
||||
wasm(),
|
||||
topLevelAwait(),
|
||||
],
|
||||
format: "es"
|
||||
},
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user