Skip to content

Commit

Permalink
allow converting subtitle to segments #2002
Browse files Browse the repository at this point in the history
  • Loading branch information
mifi committed May 19, 2024
1 parent ec3e626 commit 8b86795
Show file tree
Hide file tree
Showing 3 changed files with 51 additions and 13 deletions.
25 changes: 19 additions & 6 deletions src/renderer/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,8 @@ import {
readFileMeta, getSmarterOutFormat, renderThumbnails as ffmpegRenderThumbnails,
extractStreams, setCustomFfPath as ffmpegSetCustomFfPath,
isIphoneHevc, isProblematicAvc1, tryMapChaptersToEdl,
getDuration, getTimecodeFromStreams, createChaptersFromSegments, extractSubtitleTrack,
RefuseOverwriteError, abortFfmpegs,
getDuration, getTimecodeFromStreams, createChaptersFromSegments, extractSubtitleTrackVtt,
RefuseOverwriteError, abortFfmpegs, extractSubtitleTrackToSegments,
} from './ffmpeg';
import { shouldCopyStreamByDefault, getAudioStreams, getRealVideoStreams, isAudioDefinitelyNotSupported, willPlayerProperlyHandleVideo, doesPlayerSupportHevcPlayback, isStreamThumbnail, getSubtitleStreams, getVideoTrackForStreamIndex, getAudioTrackForStreamIndex, enableVideoTrack, enableAudioTrack } from './util/streams';
import { exportEdlFile, readEdlFile, saveLlcProject, loadLlcProject, askForEdlImport } from './edlStore';
Expand Down Expand Up @@ -175,10 +175,11 @@ function App() {

// Store "working" in a ref so we can avoid race conditions
const workingRef = useRef(!!working);
const setWorking = useCallback((val: { text: string, abortController?: AbortController } | undefined) => {
workingRef.current = !!val;
const setWorking = useCallback((valOrBool: { text: string, abortController?: AbortController } | true | undefined) => {
workingRef.current = !!valOrBool;
const val = valOrBool === true ? { text: t('Loading') } : valOrBool;
setWorkingState(val ? { text: val.text, abortController: val.abortController } : undefined);
}, []);
}, [t]);

const handleAbortWorkingClick = useCallback(() => {
console.log('User clicked abort');
Expand Down Expand Up @@ -643,7 +644,7 @@ function App() {
try {
setWorking({ text: i18n.t('Loading subtitle') });
invariant(filePath != null);
const url = await extractSubtitleTrack(filePath, index);
const url = await extractSubtitleTrackVtt(filePath, index);
setSubtitlesByStreamId((old) => ({ ...old, [index]: { url, lang: subtitleStream.tags && subtitleStream.tags.language } }));
setActiveSubtitleStreamIndex(index);
} catch (err) {
Expand Down Expand Up @@ -1462,6 +1463,17 @@ function App() {
loadCutSegments(await readEdlFile({ type, path }), append);
}, [loadCutSegments]);

const loadSubtitleTrackToSegments = useCallback(async (streamId: number) => {
invariant(filePath != null);
setWorking(true);
try {
setStreamsSelectorShown(false);
loadCutSegments(await extractSubtitleTrackToSegments(filePath, streamId), true);
} finally {
setWorking(undefined);
}
}, [filePath, loadCutSegments, setWorking]);

const loadMedia = useCallback(async ({ filePath: fp, projectPath }: { filePath: string, projectPath?: string }) => {
async function tryOpenProjectPath(path, type) {
if (!(await exists(path))) return false;
Expand Down Expand Up @@ -2760,6 +2772,7 @@ function App() {
paramsByStreamId={paramsByStreamId}
updateStreamParams={updateStreamParams}
formatTimecode={formatTimecode}
loadSubtitleTrackToSegments={loadSubtitleTrackToSegments}
/>
)}
</Sheet>
Expand Down
23 changes: 17 additions & 6 deletions src/renderer/src/StreamsSelector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -149,8 +149,8 @@ function onInfoClick(json: unknown, title: string) {
showJson5Dialog({ title, json });
}

const Stream = memo(({ filePath, stream, onToggle, batchSetCopyStreamIds, copyStream, fileDuration, setEditingStream, onExtractStreamPress, paramsByStreamId, updateStreamParams, formatTimecode }: {
filePath: string, stream: FFprobeStream, onToggle: (a: number) => void, batchSetCopyStreamIds: (filter: (a: FFprobeStream) => boolean, enabled: boolean) => void, copyStream: boolean, fileDuration: number | undefined, setEditingStream: (a: EditingStream) => void, onExtractStreamPress?: () => void, paramsByStreamId: ParamsByStreamId, updateStreamParams: UpdateStreamParams, formatTimecode: FormatTimecode,
const Stream = memo(({ filePath, stream, onToggle, batchSetCopyStreamIds, copyStream, fileDuration, setEditingStream, onExtractStreamPress, paramsByStreamId, updateStreamParams, formatTimecode, loadSubtitleTrackToSegments }: {
filePath: string, stream: FFprobeStream, onToggle: (a: number) => void, batchSetCopyStreamIds: (filter: (a: FFprobeStream) => boolean, enabled: boolean) => void, copyStream: boolean, fileDuration: number | undefined, setEditingStream: (a: EditingStream) => void, onExtractStreamPress?: () => void, paramsByStreamId: ParamsByStreamId, updateStreamParams: UpdateStreamParams, formatTimecode: FormatTimecode, loadSubtitleTrackToSegments?: (index: number) => void,
}) => {
const { t } = useTranslation();

Expand Down Expand Up @@ -205,6 +205,10 @@ const Stream = memo(({ filePath, stream, onToggle, batchSetCopyStreamIds, copySt
});
}, [filePath, updateStreamParams, stream.index]);

const onLoadSubtitleTrackToSegmentsClick = useCallback(() => {
loadSubtitleTrackToSegments?.(stream.index);
}, [loadSubtitleTrackToSegments, stream.index]);

const codecTag = stream.codec_tag !== '0x0000' && stream.codec_tag_string;

return (
Expand Down Expand Up @@ -251,6 +255,11 @@ const Stream = memo(({ filePath, stream, onToggle, batchSetCopyStreamIds, copySt
{t('Extract this track as file')}
</Menu.Item>
)}
{stream.codec_type === 'subtitle' && (
<Menu.Item icon={<MdSubtitles color="black" />} onClick={onLoadSubtitleTrackToSegmentsClick}>
{t('Create segments from subtitles')}
</Menu.Item>
)}
</Menu.Group>
<Menu.Divider />
<Menu.Group>
Expand Down Expand Up @@ -319,7 +328,7 @@ const fileStyle: CSSProperties = { margin: '1.5em 1em 1.5em 1em', padding: 5, ov


function StreamsSelector({
mainFilePath, mainFileFormatData, mainFileStreams, mainFileChapters, isCopyingStreamId, toggleCopyStreamId, setCopyStreamIdsForPath, onExtractStreamPress, onExtractAllStreamsPress, allFilesMeta, externalFilesMeta, setExternalFilesMeta, showAddStreamSourceDialog, shortestFlag, setShortestFlag, nonCopiedExtraStreams, customTagsByFile, setCustomTagsByFile, paramsByStreamId, updateStreamParams, formatTimecode,
mainFilePath, mainFileFormatData, mainFileStreams, mainFileChapters, isCopyingStreamId, toggleCopyStreamId, setCopyStreamIdsForPath, onExtractStreamPress, onExtractAllStreamsPress, allFilesMeta, externalFilesMeta, setExternalFilesMeta, showAddStreamSourceDialog, shortestFlag, setShortestFlag, nonCopiedExtraStreams, customTagsByFile, setCustomTagsByFile, paramsByStreamId, updateStreamParams, formatTimecode, loadSubtitleTrackToSegments,
}: {
mainFilePath: string,
mainFileFormatData: FFprobeFormat | undefined,
Expand All @@ -342,6 +351,7 @@ function StreamsSelector({
paramsByStreamId: ParamsByStreamId,
updateStreamParams: UpdateStreamParams,
formatTimecode: FormatTimecode,
loadSubtitleTrackToSegments: (index: number) => void,
}) {
const [editingFile, setEditingFile] = useState<string>();
const [editingStream, setEditingStream] = useState<EditingStream>();
Expand Down Expand Up @@ -406,27 +416,28 @@ function StreamsSelector({
paramsByStreamId={paramsByStreamId}
updateStreamParams={updateStreamParams}
formatTimecode={formatTimecode}
loadSubtitleTrackToSegments={loadSubtitleTrackToSegments}
/>
))}
</tbody>
</table>
</div>

{externalFilesEntries.map(([path, { streams, formatData }]) => (
{externalFilesEntries.map(([path, { streams: externalFileStreams, formatData }]) => (
<div key={path} style={fileStyle}>
<FileHeading path={path} formatData={formatData} onTrashClick={() => removeFile(path)} setCopyAllStreams={(enabled) => setCopyAllStreamsForPath(path, enabled)} />

<table style={tableStyle}>
<Thead />
<tbody>
{streams.map((stream) => (
{externalFileStreams.map((stream) => (
<Stream
key={stream.index}
filePath={path}
stream={stream}
copyStream={isCopyingStreamId(path, stream.index)}
onToggle={(streamId) => toggleCopyStreamId(path, streamId)}
batchSetCopyStreamIds={(filter: (a: FFprobeStream) => boolean, enabled: boolean) => batchSetCopyStreamIdsForPath(path, streams, filter, enabled)}
batchSetCopyStreamIds={(filter: (a: FFprobeStream) => boolean, enabled: boolean) => batchSetCopyStreamIdsForPath(path, externalFileStreams, filter, enabled)}
setEditingStream={setEditingStream}
fileDuration={getFormatDuration(formatData)}
paramsByStreamId={paramsByStreamId}
Expand Down
16 changes: 15 additions & 1 deletion src/renderer/src/ffmpeg.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { pcmAudioCodecs, getMapStreamsArgs, isMov, LiteFFprobeStream } from './u
import { getSuffixedOutPath, isExecaFailure } from './util';
import { isDurationValid } from './segments';
import { FFprobeChapter, FFprobeFormat, FFprobeProbeResult, FFprobeStream } from '../../../ffprobe';
import { parseSrt } from './edlFormats';

const FileType = window.require('file-type');
const { pathExists } = window.require('fs-extra');
Expand Down Expand Up @@ -505,7 +506,20 @@ async function renderThumbnail(filePath: string, timestamp: number) {
return URL.createObjectURL(blob);
}

export async function extractSubtitleTrack(filePath: string, streamId: number) {
export async function extractSubtitleTrackToSegments(filePath: string, streamId: number) {
const args = [
'-hide_banner',
'-i', filePath,
'-map', `0:${streamId}`,
'-f', 'srt',
'-',
];

const { stdout } = await runFfmpeg(args);
return parseSrt(stdout.toString('utf8'));
}

export async function extractSubtitleTrackVtt(filePath: string, streamId: number) {
const args = [
'-hide_banner',
'-i', filePath,
Expand Down

0 comments on commit 8b86795

Please sign in to comment.