Skip to content

Commit 4443577

Browse files
committed
Add support for FS.open, FS.close, FS.read, FS.write,
1 parent 63ea4cc commit 4443577

File tree

6 files changed

+223
-1
lines changed

6 files changed

+223
-1
lines changed

packages/ffmpeg/src/classes.ts

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {
1616
FFFSType,
1717
FFFSMountOptions,
1818
FFFSPath,
19+
FileReadData,
1920
} from "./types.js";
2021
import { getMessageID } from "./utils.js";
2122
import { ERROR_TERMINATED, ERROR_NOT_LOADED } from "./errors.js";
@@ -63,6 +64,10 @@ export class FFmpeg {
6364
case FFMessageType.UNMOUNT:
6465
case FFMessageType.EXEC:
6566
case FFMessageType.FFPROBE:
67+
case FFMessageType.OPEN:
68+
case FFMessageType.CLOSE:
69+
case FFMessageType.READ:
70+
case FFMessageType.WRITE:
6671
case FFMessageType.WRITE_FILE:
6772
case FFMessageType.READ_FILE:
6873
case FFMessageType.DELETE_FILE:
@@ -362,6 +367,120 @@ export class FFmpeg {
362367
) as Promise<OK>;
363368
};
364369

370+
/**
371+
* Opens a file with the specified path, flags, and mode.
372+
*
373+
* @returns A file descriptor number.
374+
* @category File System
375+
*/
376+
public open = (
377+
/** The path to the file. */
378+
path: string,
379+
/**
380+
* Mode for opening the file (e.g., 'r', 'w', 'a')
381+
* @see [FS read and write flags](https://emscripten.org/docs/api_reference/Filesystem-API.html#fs-read-and-write-flags)
382+
* */
383+
flags: string,
384+
/**
385+
* Permissions for creating a new file.
386+
* @defaultValue 0666
387+
* */
388+
mode?: number
389+
): Promise<number> => {
390+
return this.#send(
391+
{
392+
type: FFMessageType.OPEN,
393+
data: { path, flags, mode },
394+
}
395+
) as Promise<number>;
396+
}
397+
398+
/**
399+
* Closes an open file descriptor.
400+
*
401+
* @returns Resolves when the file is successfully closed.
402+
* @category File System
403+
*/
404+
public close = (
405+
/** The file descriptor to close. */
406+
fd: number
407+
): Promise<OK> => {
408+
return this.#send(
409+
{
410+
type: FFMessageType.CLOSE,
411+
data: { fd },
412+
}
413+
) as Promise<OK>;
414+
}
415+
416+
/**
417+
* Reads data from an open file descriptor.
418+
* @example
419+
* ```ts
420+
* const ffmpeg = new FFmpeg();
421+
* await ffmpeg.load();
422+
* const fd = await ffmpeg.open("../video.avi");
423+
* const CHUNK_SIZE = 1024;
424+
* const { data, done } = await ffmpeg.read(fd, 0, CHUNK_SIZE)
425+
* await ffmpeg.close(fd);
426+
* ```
427+
* @category File System
428+
*/
429+
public read = (
430+
/** The file descriptor to read from. */
431+
fd: number,
432+
/** The offset in the buffer to start writing data. */
433+
offset: number,
434+
/** The number of bytes to read. */
435+
length: number,
436+
/** The offset within the stream to read. By default this is the stream’s current offset. */
437+
position?: number
438+
): Promise<FileReadData> => {
439+
return this.#send(
440+
{
441+
type: FFMessageType.READ,
442+
data: { fd, offset, length, position },
443+
}
444+
) as Promise<FileReadData>;
445+
}
446+
447+
/**
448+
* Writes data to an open file descriptor.
449+
*
450+
* @example
451+
* ```ts
452+
* const ffmpeg = new FFmpeg();
453+
* await ffmpeg.load();
454+
* const data = new Uint8Array(32);
455+
* const fd = await ffmpeg.open("../video.avi", "w+");
456+
* await ffmpeg.write(fd, data, 0, data.length, 0);
457+
* await ffmpeg.close(fd);
458+
* ```
459+
* @category File System
460+
*/
461+
public write = (
462+
/** The file descriptor to write to. */
463+
fd: number,
464+
/** The buffer containing the data to write. */
465+
buffer: Uint8Array,
466+
/** The offset in the buffer to start writing from. */
467+
offset: number,
468+
/** The number of bytes to write. */
469+
length: number,
470+
/** The offset within the stream to write. By default this is the stream’s current offset. */
471+
position?: number
472+
): Promise<OK> => {
473+
const trans: Transferable[] = [buffer.buffer];
474+
475+
return this.#send(
476+
{
477+
type: FFMessageType.WRITE,
478+
data: { fd, buffer, offset, length, position },
479+
},
480+
trans
481+
) as Promise<OK>;
482+
}
483+
365484
/**
366485
* Read data from ffmpeg.wasm.
367486
*

packages/ffmpeg/src/const.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@ export enum FFMessageType {
88
LOAD = "LOAD",
99
EXEC = "EXEC",
1010
FFPROBE = "FFPROBE",
11+
OPEN = "OPEN",
12+
CLOSE = "CLOSE",
13+
READ = "READ",
14+
WRITE = "WRITE",
1115
WRITE_FILE = "WRITE_FILE",
1216
READ_FILE = "READ_FILE",
1317
DELETE_FILE = "DELETE_FILE",

packages/ffmpeg/src/errors.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,5 @@ export const ERROR_TERMINATED = new Error("called FFmpeg.terminate()");
66
export const ERROR_IMPORT_FAILURE = new Error(
77
"failed to import ffmpeg-core.js"
88
);
9+
10+
export const ERROR_FS_STREAM_NOT_FOUND = new Error("FS stream not found");

packages/ffmpeg/src/types.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,26 @@ export interface FFMessageExecData {
3737
timeout?: number;
3838
}
3939

40+
export interface FFMessageOpenData {
41+
path: string;
42+
flags: string;
43+
mode?: number;
44+
}
45+
46+
export interface FFMessageCloseData {
47+
fd: number;
48+
}
49+
50+
export interface FFMessageReadData {
51+
fd: number;
52+
buffer: Uint8Array;
53+
offset: number;
54+
length: number;
55+
position?: number;
56+
}
57+
58+
export type FFMessageWriteData = FFMessageReadData;
59+
4060
export interface FFMessageWriteFileData {
4161
path: FFFSPath;
4262
data: FileData;
@@ -110,6 +130,10 @@ export interface FFMessageUnmountData {
110130
export type FFMessageData =
111131
| FFMessageLoadConfig
112132
| FFMessageExecData
133+
| FFMessageOpenData
134+
| FFMessageCloseData
135+
| FFMessageReadData
136+
| FFMessageWriteData // eslint-disable-line
113137
| FFMessageWriteFileData
114138
| FFMessageReadFileData
115139
| FFMessageDeleteFileData
@@ -146,6 +170,7 @@ export interface ProgressEvent {
146170
export type ExitCode = number;
147171
export type ErrorMessage = string;
148172
export type FileData = Uint8Array | string;
173+
export type FD = number;
149174
export type IsFirst = boolean;
150175
export type OK = boolean;
151176

@@ -154,9 +179,16 @@ export interface FSNode {
154179
isDir: boolean;
155180
}
156181

182+
export interface FileReadData {
183+
data?: Uint8Array,
184+
done: boolean
185+
}
186+
157187
export type CallbackData =
158188
| FileData
159189
| ExitCode
190+
| FD // eslint-disable-line
191+
| FileReadData
160192
| ErrorMessage
161193
| LogEvent
162194
| ProgressEvent

packages/ffmpeg/src/worker.ts

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
/// <reference lib="esnext" />
33
/// <reference lib="webworker" />
44

5-
import type { FFmpegCoreModule, FFmpegCoreModuleFactory } from "@ffmpeg/types";
5+
import type { FFmpegCoreModule, FFmpegCoreModuleFactory, FSStream } from "@ffmpeg/types";
66
import type {
77
FFMessageEvent,
88
FFMessageLoadConfig,
@@ -22,12 +22,19 @@ import type {
2222
ExitCode,
2323
FSNode,
2424
FileData,
25+
FD,
26+
FFMessageOpenData,
27+
FFMessageCloseData,
28+
FFMessageReadData,
29+
FFMessageWriteData,
30+
FileReadData,
2531
} from "./types";
2632
import { CORE_URL, FFMessageType } from "./const.js";
2733
import {
2834
ERROR_UNKNOWN_MESSAGE_TYPE,
2935
ERROR_NOT_LOADED,
3036
ERROR_IMPORT_FAILURE,
37+
ERROR_FS_STREAM_NOT_FOUND
3138
} from "./errors.js";
3239

3340
declare global {
@@ -116,6 +123,42 @@ const writeFile = ({ path, data }: FFMessageWriteFileData): OK => {
116123
const readFile = ({ path, encoding }: FFMessageReadFileData): FileData =>
117124
ffmpeg.FS.readFile(path, { encoding });
118125

126+
const open = ({ path, flags, mode }: FFMessageOpenData): FD => {
127+
return ffmpeg.FS.open(path, flags, mode).fd;
128+
}
129+
130+
const close = ({ fd }: FFMessageCloseData): OK => {
131+
const stream = ffmpeg.FS.getStream(fd);
132+
if (stream) {
133+
ffmpeg.FS.close(stream);
134+
}
135+
return true;
136+
}
137+
138+
const getStream = (fd: number): FSStream => {
139+
const stream = ffmpeg.FS.getStream(fd);
140+
if (!stream) throw ERROR_FS_STREAM_NOT_FOUND;
141+
return stream;
142+
}
143+
144+
const read = ({ fd, offset, length, position }: FFMessageReadData): FileReadData => {
145+
const stream = getStream(fd);
146+
const data = new Uint8Array(length);
147+
const current = ffmpeg.FS.read(stream, data, offset, length, position)
148+
if (current == 0) {
149+
return { done: true }
150+
} else if (current < data.length) {
151+
return { data: data.subarray(0, current), done: false }
152+
}
153+
return { data, done: false }
154+
}
155+
156+
const write = ({ fd, buffer, offset, length, position }: FFMessageWriteData): OK => {
157+
const stream = getStream(fd);
158+
ffmpeg.FS.write(stream, buffer, offset, length, position);
159+
return true;
160+
}
161+
119162
// TODO: check if deletion works.
120163
const deleteFile = ({ path }: FFMessageDeleteFileData): OK => {
121164
ffmpeg.FS.unlink(path);
@@ -181,6 +224,19 @@ self.onmessage = async ({
181224
case FFMessageType.FFPROBE:
182225
data = ffprobe(_data as FFMessageExecData);
183226
break;
227+
case FFMessageType.OPEN:
228+
data = open(_data as FFMessageOpenData);
229+
break;
230+
case FFMessageType.CLOSE:
231+
data = close(_data as FFMessageCloseData);
232+
break;
233+
case FFMessageType.READ:
234+
data = read(_data as FFMessageReadData);
235+
if (data.data) trans.push(data.data.buffer)
236+
break;
237+
case FFMessageType.WRITE:
238+
data = write(_data as FFMessageWriteData);
239+
break;
184240
case FFMessageType.WRITE_FILE:
185241
data = writeFile(_data as FFMessageWriteFileData);
186242
break;

packages/types/types/index.d.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,10 @@ export interface WorkerFSMountConfig {
6262
files?: File[];
6363
}
6464

65+
export interface FSStream {
66+
fd: number;
67+
}
68+
6569
/**
6670
* Functions to interact with Emscripten FS library.
6771
*
@@ -72,6 +76,11 @@ export interface FS {
7276
mkdir: (path: string) => void;
7377
rmdir: (path: string) => void;
7478
rename: (oldPath: string, newPath: string) => void;
79+
open: (path: string, flags: string, mode?: number) => FSStream;
80+
getStream: (fd: number) => FSStream | undefined;
81+
close: (stream: FSStream) => void;
82+
read: (stream: FSStream, buffer: Uint8Array, offset: number, length: number, position?: number) => number;
83+
write: (stream: FSStream, buffer: Uint8Array, offset: number, length: number, position?: number) => void;
7584
writeFile: (path: string, data: Uint8Array | string) => void;
7685
readFile: (path: string, opts: OptionReadFile) => Uint8Array | string;
7786
readdir: (path: string) => string[];

0 commit comments

Comments
 (0)