Skip to content

Commit

Permalink
@remotion/media-parser: parseAndDownloadMedia() done
Browse files Browse the repository at this point in the history
  • Loading branch information
JonnyBurger committed Jan 22, 2025
1 parent 71a3603 commit d78292c
Show file tree
Hide file tree
Showing 14 changed files with 101 additions and 55 deletions.
8 changes: 4 additions & 4 deletions packages/media-parser/src/internal-parse-media.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ export const internalParseMedia: InternalParseMedia = async function <
});
};

const checkIfDone = () => {
const checkIfDone = async () => {
const startCheck = Date.now();
const hasAll = hasAllInfo({
fields,
Expand All @@ -149,7 +149,7 @@ export const internalParseMedia: InternalParseMedia = async function <

if (state.iterator.counter.getOffset() === contentLength) {
Log.verbose(logLevel, 'Reached end of file');
state.discardReadBytes(true);
await state.discardReadBytes(true);

return true;
}
Expand All @@ -160,7 +160,7 @@ export const internalParseMedia: InternalParseMedia = async function <
triggerInfoEmit();

let iterationWithThisOffset = 0;
while (!checkIfDone()) {
while (!(await checkIfDone())) {
if (signal?.aborted) {
throw new Error('Aborted');
}
Expand Down Expand Up @@ -247,7 +247,7 @@ export const internalParseMedia: InternalParseMedia = async function <
}

const timeFreeStart = Date.now();
state.discardReadBytes(false);
await state.discardReadBytes(false);

timeFreeingData += Date.now() - timeFreeStart;
}
Expand Down
6 changes: 5 additions & 1 deletion packages/media-parser/src/parse-and-download-media.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export const parseAndDownloadMedia: ParseAndDownloadMedia = async (options) => {
filename: 'hmm',
mimeType: 'shouldnotmatter',
});
return internalParseMedia({
const returnValue = await internalParseMedia({
fields: options.fields ?? null,
logLevel: options.logLevel ?? 'info',
mode: 'download',
Expand Down Expand Up @@ -49,4 +49,8 @@ export const parseAndDownloadMedia: ParseAndDownloadMedia = async (options) => {
signal: options.signal ?? undefined,
src: options.src,
});

await content.finish();

return returnValue;
};
28 changes: 14 additions & 14 deletions packages/media-parser/src/perform-seek.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,27 +24,18 @@ export const performSeek = async ({
mode,
contentLength,
} = state;
const skippingAhead = seekTo > iterator.counter.getOffset();
if (mode === 'download' && !skippingAhead) {
throw new Error(
`Cannot seek backwards in download mode. Current position: ${iterator.counter.getOffset()}, seekTo: ${seekTo}`,
);
}

if (!skippingAhead && !supportsContentRange) {
if (seekTo <= iterator.counter.getOffset()) {
throw new Error(
'Content-Range header is not supported by the reader, but was asked to seek',
`Seeking backwards is not supported. Current position: ${iterator.counter.getOffset()}, seekTo: ${seekTo}`,
);
}

if (seekTo > state.contentLength) {
throw new Error(`Unexpected seek: ${seekTo} > ${contentLength}`);
}

if (
skippingAhead &&
iterator.counter.getOffset() + iterator.bytesRemaining() >= seekTo
) {
if (iterator.counter.getOffset() + iterator.bytesRemaining() >= seekTo) {
Log.verbose(
logLevel,
`Skipping over video data from position ${iterator.counter.getOffset()} -> ${seekTo}. Data already fetched`,
Expand All @@ -53,7 +44,7 @@ export const performSeek = async ({
return currentReader;
}

if (skippingAhead && !supportsContentRange) {
if (!supportsContentRange) {
Log.verbose(
logLevel,
`Skipping over video data from position ${iterator.counter.getOffset()} -> ${seekTo}. Fetching but not reading all the data inbetween because Content-Range is not supported`,
Expand All @@ -62,6 +53,15 @@ export const performSeek = async ({
return currentReader;
}

if (mode === 'download') {
Log.verbose(
logLevel,
`Skipping over video data from position ${iterator.counter.getOffset()} -> ${seekTo}. Fetching but not reading all the data inbetween because in download mode`,
);
iterator.discard(seekTo - iterator.counter.getOffset());
return currentReader;
}

const time = Date.now();
Log.verbose(
logLevel,
Expand All @@ -75,7 +75,7 @@ export const performSeek = async ({
signal,
});
iterator.skipTo(seekTo);
state.discardReadBytes(true);
await state.discardReadBytes(true);

Log.verbose(
logLevel,
Expand Down
8 changes: 2 additions & 6 deletions packages/media-parser/src/state/parser-state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,18 +83,14 @@ export const makeParserState = ({
const mp3Info = makeMp3State();
const images = imagesState();

const discardReadBytes = (force: boolean) => {
const discardReadBytes = async (force: boolean) => {
const {bytesRemoved, removedData} = iterator.removeBytesRead(force, mode);
if (bytesRemoved) {
Log.verbose(logLevel, `Freed ${bytesRemoved} bytes`);
}

if (removedData && onDiscardedData) {
onDiscardedData(removedData);
}

if (bytesRemoved) {
Log.verbose(logLevel, `Freed ${bytesRemoved} bytes`);
await onDiscardedData(removedData);
}
};

Expand Down
47 changes: 45 additions & 2 deletions packages/media-parser/src/test/parse-and-download-video.test.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,56 @@
import {exampleVideos} from '@remotion/example-videos';
import {test} from 'bun:test';
import {expect, test} from 'bun:test';
import {parseAndDownloadMedia} from '../parse-and-download-media';
import {nodeReader} from '../readers/from-node';
import {nodeWriter} from '../writers/node';

test('should be able to parse and download video', async () => {
await parseAndDownloadMedia({
const {size} = await parseAndDownloadMedia({
src: exampleVideos.avi,
reader: nodeReader,
fields: {
size: true,
},
writer: nodeWriter('output.avi'),
});

const s = Bun.file('output.avi');
expect(s.size).toBe(742478);
expect(size).toBe(742478);
await s.unlink();
});

test('should be able to parse and download video', async () => {
const {size} = await parseAndDownloadMedia({
src: exampleVideos.iphonehevc,
reader: nodeReader,
fields: {
size: true,
},
writer: nodeWriter('iphonehevc.mp4'),
});

const s = Bun.file('iphonehevc.mp4');
expect(size).toBe(7418901);
expect(s.size).toBe(7418901);
await s.unlink();
});

test(
'should be able to parse and download remote video',
async () => {
const {size} = await parseAndDownloadMedia({
src: 'https://remotion-assets.s3.eu-central-1.amazonaws.com/example-videos/transportstream.ts',
fields: {
size: true,
},
writer: nodeWriter('output2'),
});

const s = Bun.file('output2');
expect(s.size).toBe(1913464);
expect(size).toBe(1913464);
await s.unlink();
},
{timeout: 30_000},
);
13 changes: 7 additions & 6 deletions packages/media-parser/src/writers/node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,20 +56,21 @@ const createContent = (filename: string): CreateContent => {
writPromise.then(() => updateDataAt(position, data));
return writPromise;
},
waitForFinish: async () => {
await writPromise;
},
getWrittenByteCount: () => written,
remove,
save: async () => {
finish: async () => {
await writPromise;
try {
fs.closeSync(writeStream);
const file = await fs.promises.readFile(filename);
return new Blob([file]);
return Promise.resolve();
} catch (e) {
return Promise.reject(e);
}
},
getBlob: async () => {
const file = await fs.promises.readFile(filename);
return new Blob([file]);
},
};

return writer;
Expand Down
4 changes: 2 additions & 2 deletions packages/media-parser/src/writers/writer.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
export type Writer = {
write: (arr: Uint8Array) => Promise<void>;
save: () => Promise<Blob>;
finish: () => Promise<void>;
getWrittenByteCount: () => number;
updateDataAt: (position: number, data: Uint8Array) => Promise<void>;
waitForFinish: () => Promise<void>;
remove: () => Promise<void>;
getBlob: () => Promise<Blob>;
};

export type CreateContent = (options: {
Expand Down
2 changes: 1 addition & 1 deletion packages/webcodecs/src/convert-media.ts
Original file line number Diff line number Diff line change
Expand Up @@ -259,7 +259,7 @@ export const convertMedia = async function <
})
.then(() => {
resolve({
save: state.save,
save: state.getBlob,
remove: state.remove,
finalState: throttledState.get(),
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -217,8 +217,8 @@ export const createIsoBaseMedia = async ({
const waitForFinishPromises: (() => Promise<void>)[] = [];

return {
save: () => {
return w.save();
getBlob: () => {
return w.getBlob();
},
remove: async () => {
await w.remove();
Expand Down Expand Up @@ -273,7 +273,7 @@ export const createIsoBaseMedia = async ({
logLevel,
'All write operations done. Waiting for finish...',
);
await w.waitForFinish();
await w.finish();
},
};
};
Original file line number Diff line number Diff line change
Expand Up @@ -239,8 +239,8 @@ export const createMatroskaMedia = async ({
}
});
},
save: () => {
return w.save();
getBlob: async () => {
return w.getBlob();
},
remove: async () => {
await w.remove();
Expand Down Expand Up @@ -274,13 +274,13 @@ export const createMatroskaMedia = async ({
await updateSeekWrite();

await w.write(createMatroskaCues(cues).bytes);
await w.waitForFinish();
const segmentSize =
w.getWrittenByteCount() -
segmentOffset -
matroskaToHex(matroskaElements.Segment).byteLength -
MATROSKA_SEGMENT_MIN_VINT_WIDTH;
await updateSegmentSize(segmentSize);
await w.finish();
},
};
};
2 changes: 1 addition & 1 deletion packages/webcodecs/src/create/media-fn.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import type {MakeTrackAudio, MakeTrackVideo} from './make-track-info';
import type {ProgressTracker} from './progress-tracker';

export type MediaFn = {
save: () => Promise<Blob>;
getBlob: () => Promise<Blob>;
remove: () => Promise<void>;
addSample: (options: {
chunk: AudioOrVideoSample;
Expand Down
6 changes: 3 additions & 3 deletions packages/webcodecs/src/create/wav/create-wav.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,8 +109,8 @@ export const createWav = async ({
const waitForFinishPromises: (() => Promise<void>)[] = [];

return {
save: () => {
return w.save();
getBlob: () => {
return w.getBlob();
},
remove: () => {
return w.remove();
Expand Down Expand Up @@ -144,7 +144,7 @@ export const createWav = async ({
await Promise.all(waitForFinishPromises.map((p) => p()));
await operationProm.current;
await updateSize();
await w.waitForFinish();
await w.finish();
},
addTrack: async (track) => {
if (track.type !== 'audio') {
Expand Down
10 changes: 6 additions & 4 deletions packages/webcodecs/src/writers/buffer-implementation/writer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,18 @@ export const createContent: CreateContent = ({filename, mimeType}) => {
writPromise = writPromise.then(() => write(arr));
return writPromise;
},
save: () => {
finish: async () => {
await writPromise;

if (removed) {
return Promise.reject(
new Error('Already called .remove() on the result'),
);
}

return Promise.resolve();
},
getBlob() {
const arr = new Uint8Array(buf);
return Promise.resolve(
// TODO: Unhardcode MIME type and file name
Expand All @@ -53,9 +58,6 @@ export const createContent: CreateContent = ({filename, mimeType}) => {
writPromise = writPromise.then(() => updateDataAt(position, newData));
return writPromise;
},
waitForFinish: async () => {
await writPromise;
},
};
return Promise.resolve(writer);
};
10 changes: 5 additions & 5 deletions packages/webcodecs/src/writers/web-fs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,13 +43,16 @@ const createContent: CreateContent = async ({filename}) => {
writPromise = writPromise.then(() => write(arr));
return writPromise;
},
save: async () => {
finish: async () => {
await writPromise;

try {
await writable.close();
} catch {
// Ignore, could already be closed
}

},
async getBlob() {
const newHandle = await directoryHandle.getFileHandle(actualFilename, {
create: true,
});
Expand All @@ -61,9 +64,6 @@ const createContent: CreateContent = async ({filename}) => {
writPromise = writPromise.then(() => updateDataAt(position, data));
return writPromise;
},
waitForFinish: async () => {
await writPromise;
},
remove,
};

Expand Down

0 comments on commit d78292c

Please sign in to comment.