Skip to content

Commit

Permalink
Merge pull request #4558 from remotion-dev/error-handling
Browse files Browse the repository at this point in the history
  • Loading branch information
JonnyBurger authored Nov 25, 2024
2 parents fc4a75b + 9c69a29 commit 8eb319a
Show file tree
Hide file tree
Showing 65 changed files with 2,255 additions and 417 deletions.
17 changes: 8 additions & 9 deletions packages/convert/app/components/ContainerOverview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import {Skeleton} from './ui/skeleton';

export const ContainerOverview: React.FC<{
readonly dimensions: Dimensions | null;
readonly durationInSeconds: number | null;
readonly durationInSeconds: number | null | undefined;
readonly videoCodec: MediaParserVideoCodec | null;
readonly audioCodec: MediaParserAudioCodec | null | undefined;
readonly size: number | null;
Expand Down Expand Up @@ -63,14 +63,13 @@ export const ContainerOverview: React.FC<{
Duration
</TableCell>
<TableCell className="text-right">
{
// TODO: For display1687984009979.webm, this does not get displayed
durationInSeconds === null ? (
<Skeleton className="h-3 w-[100px] inline-block" />
) : (
<>{formatSeconds(durationInSeconds)}</>
)
}
{durationInSeconds === undefined ? (
<Skeleton className="h-3 w-[100px] inline-block" />
) : durationInSeconds === null ? (
<span>N/A</span>
) : (
<>{formatSeconds(durationInSeconds)}</>
)}
</TableCell>
</TableRow>
<TableRow>
Expand Down
2 changes: 1 addition & 1 deletion packages/convert/app/components/ErrorState.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ const ErrorBox: React.FC<{
readonly cause?: boolean;
}> = ({error, cause}) => {
const {message} = error;
const splitted = error.stack?.split('\n') ?? [];
const splitted = error.stack ? error.stack.split('\n') : [error.message];
const deduplicated = splitted[0].includes(message)
? splitted.slice(1)
: splitted;
Expand Down
68 changes: 45 additions & 23 deletions packages/convert/app/components/FileAvailable.tsx
Original file line number Diff line number Diff line change
@@ -1,35 +1,56 @@
import {
MediaParserAudioCodec,
MediaParserVideoCodec,
TracksField,
} from '@remotion/media-parser';
import React, {useCallback, useState} from 'react';
import {ParseMediaOnProgress} from '@remotion/media-parser';
import React, {useCallback, useRef, useState} from 'react';
import {Source} from '~/lib/convert-state';
import {formatBytes} from '~/lib/format-bytes';
import ConvertUI from './ConvertUi';
import {Footer} from './Footer';
import {Probe} from './Probe';
import {VideoThumbnailRef} from './VideoThumbnail';
import {Button} from './ui/button';
import {useProbe} from './use-probe';

export const FileAvailable: React.FC<{
readonly src: Source;
readonly setSrc: React.Dispatch<React.SetStateAction<Source | null>>;
}> = ({src, setSrc}) => {
const [probeDetails, setProbeDetails] = useState(false);
const [tracks, setTracks] = useState<TracksField | null>(null);
const [duration, setDuration] = useState<number | null>(null);
const [currentAudioCodec, setCurrentAudioCodec] =
useState<MediaParserAudioCodec | null>(null);
const [currentVideoCodec, setCurrentVideoCodec] =
useState<MediaParserVideoCodec | null>(null);

const clear = useCallback(() => {
setSrc(null);
}, [setSrc]);

const onTracks = useCallback((configs: TracksField) => {
setTracks(configs);
const videoThumbnailRef = useRef<VideoThumbnailRef>(null);

const onVideoThumbnail = useCallback((frame: VideoFrame) => {
videoThumbnailRef.current?.draw(frame);
}, []);

const onProgress: ParseMediaOnProgress = useCallback(
async ({bytes, percentage}) => {
await new Promise((resolve) => {
window.requestAnimationFrame(resolve);
});
const notDone = document.getElementById('not-done');
if (notDone) {
if (percentage === null) {
notDone.innerHTML = `${formatBytes(bytes)} read`;
} else {
notDone.innerHTML = `${Math.round(
percentage * 100,
)}% read (${formatBytes(bytes)})`;
}
}
},
[],
);

const probeResult = useProbe({
src,
logLevel: 'verbose',
onProgress,
onVideoThumbnail,
});

return (
<div>
<div className="overflow-y-auto w-full lg:flex lg:justify-center pt-6 pb-10 px-4 bg-slate-50 min-h-[100vh]">
Expand All @@ -55,21 +76,22 @@ export const FileAvailable: React.FC<{
src={src}
probeDetails={probeDetails}
setProbeDetails={setProbeDetails}
setAudioCodec={setCurrentAudioCodec}
setVideoCodec={setCurrentVideoCodec}
onTracks={onTracks}
onDuration={setDuration}
probeResult={probeResult}
videoThumbnailRef={videoThumbnailRef}
/>
<div className="h-8 lg:h-0 lg:w-8" />
<div className="w-full lg:w-[350px]">
<div
data-disabled={!(probeResult.done && !probeResult.error)}
className="w-full lg:w-[350px] data-[disabled=true]:opacity-50 data-[disabled=true]:pointer-events-none"
>
<div className="gap-4">
<ConvertUI
currentAudioCodec={currentAudioCodec}
currentVideoCodec={currentVideoCodec}
currentAudioCodec={probeResult.audioCodec ?? null}
currentVideoCodec={probeResult.videoCodec ?? null}
src={src}
tracks={tracks}
tracks={probeResult.tracks}
setSrc={setSrc}
duration={duration}
duration={probeResult.durationInSeconds ?? null}
/>
</div>
</div>
Expand Down
83 changes: 20 additions & 63 deletions packages/convert/app/components/Probe.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,5 @@
import {
MediaParserAudioCodec,
MediaParserVideoCodec,
ParseMediaOnProgress,
TracksField,
} from '@remotion/media-parser';
import React, {useCallback, useMemo, useRef, useState} from 'react';
import React, {useCallback, useMemo, useState} from 'react';
import {Source} from '~/lib/convert-state';
import {formatBytes} from '~/lib/format-bytes';
import {useIsNarrow} from '~/lib/is-narrow';
import {AudioTrackOverview} from './AudioTrackOverview';
import {ContainerOverview} from './ContainerOverview';
Expand All @@ -19,54 +12,15 @@ import {Card, CardDescription, CardHeader, CardTitle} from './ui/card';
import {ScrollArea} from './ui/scroll-area';
import {Separator} from './ui/separator';
import {Skeleton} from './ui/skeleton';
import {useProbe} from './use-probe';
import {ProbeResult} from './use-probe';

export const Probe: React.FC<{
readonly src: Source;
readonly setProbeDetails: React.Dispatch<React.SetStateAction<boolean>>;
readonly setAudioCodec: React.Dispatch<
React.SetStateAction<MediaParserAudioCodec | null>
>;
readonly setVideoCodec: React.Dispatch<
React.SetStateAction<MediaParserVideoCodec | null>
>;
readonly probeDetails: boolean;
readonly onTracks: (tracks: TracksField) => void;
readonly onDuration: (duration: number | null) => void;
}> = ({
src,
probeDetails,
setProbeDetails,
setAudioCodec,
setVideoCodec,
onTracks,
onDuration,
}) => {
const videoThumbnailRef = useRef<VideoThumbnailRef>(null);

const onVideoThumbnail = useCallback((frame: VideoFrame) => {
videoThumbnailRef.current?.draw(frame);
}, []);

const onProgress: ParseMediaOnProgress = useCallback(
async ({bytes, percentage}) => {
await new Promise((resolve) => {
window.requestAnimationFrame(resolve);
});
const notDone = document.getElementById('not-done');
if (notDone) {
if (percentage === null) {
notDone.innerHTML = `${formatBytes(bytes)} read`;
} else {
notDone.innerHTML = `${Math.round(
percentage * 100,
)}% read (${formatBytes(bytes)})`;
}
}
},
[],
);

readonly probeResult: ProbeResult;
readonly videoThumbnailRef: React.RefObject<VideoThumbnailRef>;
}> = ({src, probeDetails, setProbeDetails, probeResult, videoThumbnailRef}) => {
const {
audioCodec,
fps,
Expand All @@ -78,16 +32,8 @@ export const Probe: React.FC<{
videoCodec,
durationInSeconds,
done,
} = useProbe({
src,
onVideoThumbnail,
onAudioCodec: setAudioCodec,
onVideoCodec: setVideoCodec,
onTracks,
logLevel: 'verbose',
onProgress,
onDuration,
});
error,
} = probeResult;

const onClick = useCallback(() => {
setProbeDetails((p) => !p);
Expand Down Expand Up @@ -119,11 +65,22 @@ export const Probe: React.FC<{
return (
<Card className="w-full lg:w-[350px] overflow-hidden">
<div className="flex flex-row lg:flex-col w-full border-b-2 border-black">
<VideoThumbnail ref={videoThumbnailRef} smallThumbOnMobile />
<CardHeader className=" p-3 lg:p-4 w-full">
{error ? null : (
<VideoThumbnail ref={videoThumbnailRef} smallThumbOnMobile />
)}
<CardHeader className="p-3 lg:p-4 w-full">
<CardTitle title={name ?? undefined}>
{name ? name : <Skeleton className="h-5 w-[220px] inline-block" />}
</CardTitle>
{error ? (
<CardDescription className="!mt-0">
<p className="text-red-500">
Failed to parse media:
<br />
{error.message}
</p>
</CardDescription>
) : null}
{done ? (
<CardDescription className="!mt-0">
<SourceLabel src={src} />
Expand Down
36 changes: 10 additions & 26 deletions packages/convert/app/components/use-probe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,32 +13,26 @@ import {webFileReader} from '@remotion/media-parser/web-file';
import {useCallback, useEffect, useMemo, useState} from 'react';
import {Source} from '~/lib/convert-state';

export type ProbeResult = ReturnType<typeof useProbe>;

export const useProbe = ({
src,
onVideoThumbnail,
onAudioCodec,
onVideoCodec,
onTracks,
logLevel,
onProgress,
onDuration,
}: {
src: Source;
logLevel: LogLevel;
onVideoThumbnail: (videoFrame: VideoFrame) => void;
onAudioCodec: (codec: MediaParserAudioCodec | null) => void;
onVideoCodec: (codec: MediaParserVideoCodec | null) => void;
onTracks: (tracks: TracksField) => void;
onProgress: ParseMediaOnProgress;
onDuration: (duration: number | null) => void;
}) => {
const [audioCodec, setAudioCodec] = useState<
MediaParserAudioCodec | null | undefined
>(undefined);
const [fps, setFps] = useState<number | null | undefined>(undefined);
const [durationInSeconds, setDurationInSeconds] = useState<number | null>(
null,
);
const [durationInSeconds, setDurationInSeconds] = useState<
number | null | undefined
>(undefined);
const [dimensions, setDimensions] = useState<Dimensions | null>(null);
const [name, setName] = useState<string | null>(null);
const [videoCodec, setVideoCodec] = useState<MediaParserVideoCodec | null>(
Expand All @@ -48,6 +42,7 @@ export const useProbe = ({
const [tracks, setTracks] = useState<TracksField | null>(null);
const [container, setContainer] = useState<ParseMediaContainer | null>(null);
const [done, setDone] = useState(false);
const [error, setError] = useState<Error | null>(null);

const getStart = useCallback(() => {
const controller = new AbortController();
Expand Down Expand Up @@ -143,7 +138,6 @@ export const useProbe = ({
},
onAudioCodec: (codec) => {
hasAudioCodec = true;
onAudioCodec(codec);
setAudioCodec(codec);
cancelIfDone();
},
Expand All @@ -156,7 +150,6 @@ export const useProbe = ({
hasDuration = true;
setDurationInSeconds(d);
cancelIfDone();
onDuration(d);
},
onName: (n) => {
hasName = true;
Expand All @@ -170,14 +163,11 @@ export const useProbe = ({
},
onVideoCodec: (codec) => {
hasVideoCodec = true;
onVideoCodec(codec);
setVideoCodec(codec);
cancelIfDone();
},
onTracks: (trx) => {
hasTracks = true;

onTracks(trx);
setTracks(trx);
cancelIfDone();
},
Expand All @@ -196,6 +186,7 @@ export const useProbe = ({
return;
}

setError(err as Error);
// eslint-disable-next-line no-console
console.log(err);
})
Expand All @@ -204,16 +195,7 @@ export const useProbe = ({
});

return controller;
}, [
onAudioCodec,
onVideoCodec,
onVideoThumbnail,
src,
onTracks,
logLevel,
onProgress,
onDuration,
]);
}, [onVideoThumbnail, src, logLevel, onProgress]);

useEffect(() => {
const start = getStart();
Expand All @@ -234,6 +216,7 @@ export const useProbe = ({
size,
durationInSeconds,
done,
error,
};
}, [
audioCodec,
Expand All @@ -246,5 +229,6 @@ export const useProbe = ({
videoCodec,
durationInSeconds,
done,
error,
]);
};
Loading

0 comments on commit 8eb319a

Please sign in to comment.