Compare commits

17 Commits

Author SHA1 Message Date
4bc9acdada fix: added missing ffprobe-wasm.mjs & renamed to match repo 2026-02-26 23:57:03 +01:00
f97fb957dc fix?: undefined tags
Signed-off-by: Alix von Schirp <github@avonschirp.bootmedia.de>
2026-02-22 18:23:29 +01:00
d9013772ef chore: bump pre-release-minor, update submod ref
Signed-off-by: Alix von Schirp <github@avonschirp.bootmedia.de>
2026-02-22 01:58:57 +01:00
fad46dbae1 chore: update package.json
Signed-off-by: Alix von Schirp <github@avonschirp.bootmedia.de>
2026-02-21 01:28:05 +01:00
98151ecaef chore: update submodule ref
Signed-off-by: Alix von Schirp <github@avonschirp.bootmedia.de>
2026-02-19 21:51:48 +01:00
efa4e6f0a2 chore: update submodule ref
Signed-off-by: Alix von Schirp <github@avonschirp.bootmedia.de>
2026-02-19 21:42:09 +01:00
24a55c018f fix: make vite 7 compliant
Signed-off-by: Alix von Schirp <github@avonschirp.bootmedia.de>
2026-02-19 20:38:55 +01:00
bc9e1670b2 fix: add vite plugins for wasm
Signed-off-by: Alix von Schirp <github@avonschirp.bootmedia.de>
2026-02-19 13:44:55 +01:00
c0e13cdaa1 chore: update submodule ref
Signed-off-by: Alix von Schirp <github@avonschirp.bootmedia.de>
2026-02-19 13:44:39 +01:00
3d1951c50a chore: update submodule ref
Signed-off-by: Alix von Schirp <github@avonschirp.bootmedia.de>
2026-02-19 12:43:27 +01:00
90864d1b05 fix: rename ffprobe-wasm.js to .mjs on build
Signed-off-by: Alix von Schirp <github@avonschirp.bootmedia.de>
2026-02-19 12:36:22 +01:00
e7dbb8c0c9 chore: update submodule ref
Signed-off-by: Alix von Schirp <github@avonschirp.bootmedia.de>
2026-02-19 10:05:02 +01:00
cece48aaeb chore: change submodule ref
Signed-off-by: Alix von Schirp <github@avonschirp.bootmedia.de>
2026-02-18 22:50:08 +01:00
5214d5c530 chore: updates to submodule
Signed-off-by: Alix von Schirp <github@avonschirp.bootmedia.de>
2026-02-18 21:30:44 +01:00
ff2bef3e9c deps: update devDependencies
Signed-off-by: Alix von Schirp <github@avonschirp.bootmedia.de>
2026-02-18 20:40:23 +01:00
Tomás Fox
193d2e7b9e Make file info output more similar to ffprobe 2022-03-17 17:49:20 -03:00
Tomás Fox
e447a242c7 Bundle browser script, add package exports, add sourcemaps 2022-03-15 15:29:17 -03:00
20 changed files with 2512 additions and 210 deletions

2
.gitmodules vendored
View File

@@ -1,3 +1,3 @@
[submodule "ffprobe-wasm-app"] [submodule "ffprobe-wasm-app"]
path = 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
View File

@@ -0,0 +1 @@
trailingComma: all

View File

@@ -1,11 +1,10 @@
ffprobe-wasm # ffprobe-wasm
==========
Gather information from multimedia streams. Works on the browser and Node.js. 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. 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 ## Installation
@@ -18,25 +17,32 @@ npm install ffprobe-wasm
Node.js Node.js
```ts ```ts
import { FFprobeWorker } from 'ffprobe-wasm/node.mjs'; import { FFprobeWorker } from "ffprobe-wasm";
const worker = new FFprobeWorker(); const worker = new FFprobeWorker();
const fileInfo = await worker.getFileInfo('file.mp4'); const fileInfo = await worker.getFileInfo("file.mp4");
console.log(fileInfo); console.log(fileInfo);
``` ```
Browser Browser
```ts ```ts
import { FFprobeWorker } from 'ffprobe-wasm/browser.mjs'; import { FFprobeWorker } from "ffprobe-wasm";
const worker = new FFprobeWorker(); const worker = new FFprobeWorker();
// input is the reference to a <input type="file" /> element // input is the reference to an <input type="file" /> element
input.addEventListener('change', (event) => { input.addEventListener("change", (event) => {
const file = event.target.files[0] const file = event.target.files[0];
const fileInfo = await worker.getFileInfo(file); const fileInfo = await worker.getFileInfo(file);
console.log(fileInfo); console.log(fileInfo);
}); });
``` ```
## 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.

View File

@@ -9,7 +9,6 @@ rm -rf ffprobe-wasm-app/dist
# Build wasm # Build wasm
cd ffprobe-wasm-app cd ffprobe-wasm-app
# sed -i -e 's/ffprobe-wasm\.js/ffprobe-wasm.mjs/g;s/-o dist\/ffprobe-wasm\.mjs \\/-o dist\/ffprobe-wasm.mjs -s EXPORT_NAME=ffprobe \\/' Makefile
docker-compose run ffprobe-wasm make docker-compose run ffprobe-wasm make
cd .. cd ..
cp -R ffprobe-wasm-app/dist dist cp -R ffprobe-wasm-app/dist dist
@@ -18,5 +17,8 @@ cp src/*.d.* dist
# Build browser/node workers # Build browser/node workers
npm run build npm run build
# Remove unnecessary files
rm dist/browser-vite.* dist/worker-browser.*
# Copy files for npm publish # Copy files for npm publish
cp package.json LICENSE README.md dist cp package.json LICENSE README.md dist

2093
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,13 +1,10 @@
{ {
"name": "ffprobe-wasm", "name": "@cis-oss/avprobe-wasm",
"version": "0.1.0", "version": "0.3.2",
"description": "ffprobe-like for browser and node, powered by WebAssembly", "description": "ffprobe-like for browser and node, powered by WebAssembly",
"repository": { "repository": {
"type": "git", "type": "git",
"url": "https://github.com/tfoxy/ffprobe-wasm" "url": "https://git.cislabs.de/cis-oss/ffprobe-wasm-npm"
},
"bugs": {
"url": "https://github.com/tfoxy/ffprobe-wasm/issues"
}, },
"keywords": [ "keywords": [
"ffprobe", "ffprobe",
@@ -15,13 +12,30 @@
"ffmpeg", "ffmpeg",
"video" "video"
], ],
"author": "Tomás Fox <tomas.c.fox@gmail.com>", "author": {
"name": "Alix von Schirp",
"url": "https://b00tload.space",
"email": "hi@b00tload.space"
},
"license": "MIT", "license": "MIT",
"exports": {
"node": "./node.mjs",
"types": "./ffprobe-worker.d.mts",
"default": "./browser.mjs"
},
"main": "./node.mjs",
"types": "./ffprobe-worker.d.mts",
"browser": "./browser.mjs",
"scripts": { "scripts": {
"build": "tsc" "build": "npm run tsc && npm run vite",
"tsc": "tsc",
"vite": "vite build"
}, },
"devDependencies": { "devDependencies": {
"@types/node": "^17.0.21", "@types/node": "^25.2.3",
"typescript": "^4.5.5" "typescript": "^5.9.3",
"vite": "^7.3.1",
"vite-plugin-top-level-await": "^1.6.0",
"vite-plugin-wasm": "^3.5.0"
} }
} }

88
src/browser-vite.mts Normal file
View File

@@ -0,0 +1,88 @@
import type { FFprobeWorker as AbstractFFprobeWorker } from "./ffprobe-worker.mjs";
import type {
Chapter,
Disposition,
FileInfo,
Format,
Frame,
FramesInfo,
Rational,
Stream,
} from "./types.mjs";
import BrowserWorker from "./worker-browser.mjs?worker&inline";
import type {
IncomingMessage,
IncomingData,
OutgoingMessage,
} from "./worker.mjs";
export class FFprobeWorker implements AbstractFFprobeWorker {
readonly #worker: Worker;
constructor() {
this.#worker = new BrowserWorker();
}
async getFileInfo(file: File): Promise<FileInfo> {
this.#validateFile(file);
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> {
this.#validateFile(file);
return this.#postMessage({
type: "getFrames",
payload: [file.name, { files: [file] }, offset],
});
}
terminate(): void {
this.#worker.terminate();
}
#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",
);
}
}
#postMessage(data: IncomingData): Promise<any> {
const channel = new MessageChannel();
const message: IncomingMessage = {
...data,
port: channel.port2,
};
this.#worker.postMessage(message, [channel.port2]);
return new Promise((resolve, reject) => {
channel.port1.onmessage = (event: MessageEvent<OutgoingMessage>) => {
const { data } = event;
if (data.status === "success") {
resolve(data.payload);
} else {
reject(new Error(data.message));
}
};
});
}
}
export type {
Chapter,
Disposition,
FileInfo,
Format,
Frame,
FramesInfo,
Rational,
Stream,
};

View File

@@ -1,49 +0,0 @@
import type { Chapter, ChapterTag, FileInfo, Frame, FramesInfo, Stream } from "./ffprobe-wasm.mjs";
import type {
IncomingMessage,
IncomingData,
OutgoingMessage,
} from "./worker.mjs";
export class FFprobeWorker {
readonly #worker: Worker;
constructor() {
this.#worker = new Worker("./worker-browser.mjs");
}
async getFileInfo(file: File): Promise<FileInfo> {
return this.#postMessage({ type: "getFileInfo", payload: [file.name, { files: [file] }] });
}
async getFrames(file: File, offset: number): Promise<FramesInfo> {
return this.#postMessage({ type: "getFrames", payload: [file.name, { files: [file] }, offset] });
}
terminate(): void {
this.#worker.terminate();
}
#postMessage(data: IncomingData): Promise<any> {
const channel = new MessageChannel();
const message: IncomingMessage = {
...data,
port: channel.port2
};
this.#worker.postMessage(message, [channel.port2]);
return new Promise((resolve, reject) => {
channel.port1.onmessage = (event: MessageEvent<OutgoingMessage>) => {
const { data } = event;
if (data.status === "success") {
resolve(data.payload);
} else {
reject(new Error(data.message));
}
}
});
}
}
export type { Chapter, ChapterTag, FileInfo, Frame, FramesInfo, Stream };

1
src/declarations/vite.d.ts vendored Normal file
View File

@@ -0,0 +1 @@
/// <reference types="vite/client" />

View File

@@ -1,3 +1,5 @@
import { FileInfo, FramesInfo } from "./types.mjs";
export interface FFprobe { export interface FFprobe {
get_file_info(path: string): Raw<FileInfo>; get_file_info(path: string): Raw<FileInfo>;
get_frames(path: string, offset: number): Raw<FramesInfo>; get_frames(path: string, offset: number): Raw<FramesInfo>;
@@ -69,77 +71,25 @@ export interface AnalyzePathReturn {
parentObject: any | null; parentObject: any | null;
} }
export interface FileInfo { export interface Vector<T> {
bit_rate: number count: { value: number };
chapters: Chapter[]
duration: number
flags: number
name: string
nb_chapters: number
nb_streams: number
streams: Stream[]
url: string
}
export interface Collection<T> {
count: { value: number }
ptr: number; ptr: number;
ptrType: any; ptrType: any;
get(index: number): T; get(index: number): T;
size(): number; size(): number;
} }
export interface DictionaryEntry {
key: string;
value: string;
}
export type Raw<T> = { export type Raw<T> = {
[K in keyof T]: T[K] extends Array<infer U> ? Collection<Raw<U>> : T[K] [K in keyof T]: T[K] extends Array<infer U>
} ? Vector<Raw<U>>
: T[K] extends Record<string, string>
export interface Chapter { ? Vector<DictionaryEntry>
end: number : T[K] extends string | number | boolean | undefined | null
id: number ? T[K]
start: number : Raw<T[K]>;
tags: ChapterTag[] };
/**
* @example "1/1000"
*/
time_base: string
}
export interface ChapterTag {
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
}

View File

@@ -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>; export default function loadFFprobe(ffprobe?: FFprobeInit): Promise<FFprobe>;

View File

@@ -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; declare const ffprobe: FFprobe;

17
src/ffprobe-worker.d.mts Normal file
View 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;
}

View File

@@ -1,14 +1,25 @@
import { stat } from "fs/promises";
import { basename, dirname } from "path"; import { basename, dirname } from "path";
import { fileURLToPath } from 'url'; import { fileURLToPath } from "url";
import { MessageChannel, Worker } from "worker_threads"; import { MessageChannel, Worker } from "worker_threads";
import type { Chapter, ChapterTag, FileInfo, Frame, FramesInfo, Stream } from "./ffprobe-wasm.mjs"; import type { FFprobeWorker as AbstractFFprobeWorker } from "./ffprobe-worker.mjs";
import type {
Chapter,
Disposition,
FileInfo,
Format,
Frame,
FramesInfo,
Rational,
Stream,
} from "./types.mjs";
import type { import type {
IncomingMessage, IncomingMessage,
IncomingData, IncomingData,
OutgoingMessage, OutgoingMessage,
} from "./worker.mjs"; } from "./worker.mjs";
export class FFprobeWorker { export class FFprobeWorker implements AbstractFFprobeWorker {
readonly #worker: Worker; readonly #worker: Worker;
constructor() { constructor() {
@@ -17,22 +28,41 @@ export class FFprobeWorker {
} }
async getFileInfo(filePath: string): Promise<FileInfo> { async getFileInfo(filePath: string): Promise<FileInfo> {
return this.#postMessage({ type: "getFileInfo", payload: [basename(filePath), { root: dirname(filePath) }] }); this.#validateFile(filePath);
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> { async getFrames(filePath: string, offset: number): Promise<FramesInfo> {
return this.#postMessage({ type: "getFrames", payload: [basename(filePath), { root: dirname(filePath) }, offset] }); this.#validateFile(filePath);
return this.#postMessage({
type: "getFrames",
payload: [basename(filePath), { root: dirname(filePath) }, offset],
});
} }
terminate(): void { terminate(): void {
this.#worker.terminate(); this.#worker.terminate();
} }
#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)",
);
}
}
#postMessage(data: IncomingData): Promise<any> { #postMessage(data: IncomingData): Promise<any> {
const channel = new MessageChannel(); const channel = new MessageChannel();
const message: IncomingMessage = { const message: IncomingMessage = {
...data, ...data,
port: channel.port2 port: channel.port2,
}; };
this.#worker.postMessage(message, [channel.port2]); this.#worker.postMessage(message, [channel.port2]);
@@ -49,4 +79,13 @@ export class FFprobeWorker {
} }
} }
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
View 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;
}

View File

@@ -1,11 +1,6 @@
import loadFFprobe from "./ffprobe-wasm.mjs"; import loadFFprobe from "./ffprobe-wasm.mjs";
import { createListener, IncomingMessage } from "./worker.mjs"; import { createListener, IncomingMessage } from "./worker.mjs";
const listener = createListener( const listener = createListener(loadFFprobe(), "WORKERFS");
loadFFprobe({
locateFile: (path) => `${location.origin}/node_modules/ffprobe-wasm/${path}`
}),
"WORKERFS",
);
self.onmessage = (event: MessageEvent<IncomingMessage>) => listener(event.data); self.onmessage = (event: MessageEvent<IncomingMessage>) => listener(event.data);

View File

@@ -1,7 +1,17 @@
import type { MessagePort as NodeMessagePort } from "worker_threads"; 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 = 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: (stream && stream.tags) ? dictionaryVectorToRecord(stream.tags) : {},
}));
}
async function getFileInfo(
fileName: string,
mountOptions: FSMountOptions,
): Promise<FileInfo> {
const { FS, get_file_info } = await ffprobePromise; const { FS, get_file_info } = await ffprobePromise;
try { try {
if (!FS.analyzePath("/work").exists) { if (!FS.analyzePath("/work").exists) {
@@ -71,32 +107,21 @@ export function createListener(
} }
FS.mount(FS.filesystems[fsType], mountOptions, "/work"); FS.mount(FS.filesystems[fsType], mountOptions, "/work");
// Call the wasm module.
const rawInfo = get_file_info(`/work/${fileName}`); const rawInfo = get_file_info(`/work/${fileName}`);
// Remap streams into collection. // Safety check on rawInfo itself
const streams: Stream[] = []; if (!rawInfo) throw new Error("FFprobe failed to return file info");
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 { return {
...rawInfo, streams: serializeStreams(rawInfo.streams),
streams, chapters: rawInfo.chapters ? vectorToArray(rawInfo.chapters).map((chapter) => ({
chapters, ...chapter,
tags: chapter.tags ? dictionaryVectorToRecord(chapter.tags) : {},
})) : [],
format: {
...rawInfo.format,
tags: (rawInfo.format && rawInfo.format.tags) ? dictionaryVectorToRecord(rawInfo.format.tags) : {},
}
}; };
} finally { } finally {
// Cleanup mount. // Cleanup mount.
@@ -104,7 +129,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; const { FS, get_frames } = await ffprobePromise;
try { try {
if (!FS.analyzePath("/work").exists) { if (!FS.analyzePath("/work").exists) {
@@ -112,18 +141,11 @@ export function createListener(
} }
FS.mount(FS.filesystems.WORKERFS, mountOptions, "/work"); FS.mount(FS.filesystems.WORKERFS, mountOptions, "/work");
// Call the wasm module.
const framesInfo = get_frames(`/work/${fileName}`, offset); 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 { return {
...framesInfo, ...framesInfo,
frames, frames: vectorToArray(framesInfo.frames),
}; };
} finally { } finally {
// Cleanup mount. // Cleanup mount.

View File

@@ -11,7 +11,8 @@
"resolveJsonModule": true, "resolveJsonModule": true,
"isolatedModules": true, "isolatedModules": true,
"outDir": "./dist", "outDir": "./dist",
"declaration": true "declaration": true,
"sourceMap": true
}, },
"include": ["./src"] "include": ["./src"]
} }

31
vite.config.ts Normal file
View File

@@ -0,0 +1,31 @@
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({
build: {
outDir: resolve(__dirname, "dist"),
lib: {
entry: resolve(__dirname, "dist/browser-vite.mjs"),
formats: ["es"],
fileName: () => "browser.mjs",
},
emptyOutDir: false,
minify: false,
sourcemap: true,
},
plugins: [
wasm(),
topLevelAwait()
],
worker: {
plugins: () => [
wasm(),
topLevelAwait(),
],
format: "es"
},
});