kopia lustrzana https://github.com/mifi/lossless-cut
rodzic
b20596e53a
commit
75f5d3d1ba
27
src/App.jsx
27
src/App.jsx
|
@ -67,7 +67,7 @@ import {
|
||||||
getOutPath, getSuffixedOutPath, handleError, getOutDir,
|
getOutPath, getSuffixedOutPath, handleError, getOutDir,
|
||||||
isMasBuild, isStoreBuild, dragPreventer,
|
isMasBuild, isStoreBuild, dragPreventer,
|
||||||
havePermissionToReadFile, resolvePathIfNeeded, getPathReadAccessError, html5ifiedPrefix, html5dummySuffix, findExistingHtml5FriendlyFile,
|
havePermissionToReadFile, resolvePathIfNeeded, getPathReadAccessError, html5ifiedPrefix, html5dummySuffix, findExistingHtml5FriendlyFile,
|
||||||
deleteFiles, isOutOfSpaceError, isExecaFailure, readFileSize, readFileSizes, checkFileSizes, setDocumentTitle,
|
deleteFiles, isOutOfSpaceError, isExecaFailure, readFileSize, readFileSizes, checkFileSizes, setDocumentTitle, getOutFileExtension, getSuffixedFileName,
|
||||||
} from './util';
|
} from './util';
|
||||||
import { toast, errorToast } from './swal';
|
import { toast, errorToast } from './swal';
|
||||||
import { formatDuration } from './util/duration';
|
import { formatDuration } from './util/duration';
|
||||||
|
@ -148,6 +148,7 @@ const App = memo(() => {
|
||||||
const [hideCanvasPreview, setHideCanvasPreview] = useState(false);
|
const [hideCanvasPreview, setHideCanvasPreview] = useState(false);
|
||||||
const [exportConfirmVisible, setExportConfirmVisible] = useState(false);
|
const [exportConfirmVisible, setExportConfirmVisible] = useState(false);
|
||||||
const [cacheBuster, setCacheBuster] = useState(0);
|
const [cacheBuster, setCacheBuster] = useState(0);
|
||||||
|
const [customMergedOutFileName, setMergedOutFileName] = useState();
|
||||||
|
|
||||||
const { fileFormat, setFileFormat, detectedFileFormat, setDetectedFileFormat, isCustomFormatSelected } = useFileFormatState();
|
const { fileFormat, setFileFormat, detectedFileFormat, setDetectedFileFormat, isCustomFormatSelected } = useFileFormatState();
|
||||||
|
|
||||||
|
@ -731,6 +732,7 @@ const App = memo(() => {
|
||||||
setActiveSubtitleStreamIndex();
|
setActiveSubtitleStreamIndex();
|
||||||
setHideCanvasPreview(false);
|
setHideCanvasPreview(false);
|
||||||
setExportConfirmVisible(false);
|
setExportConfirmVisible(false);
|
||||||
|
setMergedOutFileName();
|
||||||
|
|
||||||
cancelRenderThumbnails();
|
cancelRenderThumbnails();
|
||||||
}, [cutSegmentsHistory, clearSegments, setFileFormat, setDetectedFileFormat, setDeselectedSegmentIds, cancelRenderThumbnails]);
|
}, [cutSegmentsHistory, clearSegments, setFileFormat, setDetectedFileFormat, setDeselectedSegmentIds, cancelRenderThumbnails]);
|
||||||
|
@ -983,7 +985,7 @@ const App = memo(() => {
|
||||||
if (sendErrorReport) openSendConcatReportDialogWithState(err, reportState);
|
if (sendErrorReport) openSendConcatReportDialogWithState(err, reportState);
|
||||||
}, [fileFormat, openSendConcatReportDialogWithState]);
|
}, [fileFormat, openSendConcatReportDialogWithState]);
|
||||||
|
|
||||||
const userConcatFiles = useCallback(async ({ paths, includeAllStreams, streams, fileFormat: outFormat, fileName: outFileName, clearBatchFilesAfterConcat }) => {
|
const userConcatFiles = useCallback(async ({ paths, includeAllStreams, streams, fileFormat: outFormat, outFileName, clearBatchFilesAfterConcat }) => {
|
||||||
if (workingRef.current) return;
|
if (workingRef.current) return;
|
||||||
try {
|
try {
|
||||||
setConcatDialogVisible(false);
|
setConcatDialogVisible(false);
|
||||||
|
@ -1109,6 +1111,16 @@ const App = memo(() => {
|
||||||
|
|
||||||
const willMerge = segmentsToExport.length > 1 && autoMerge;
|
const willMerge = segmentsToExport.length > 1 && autoMerge;
|
||||||
|
|
||||||
|
const mergedOutFileName = useMemo(() => {
|
||||||
|
if (customMergedOutFileName != null) return customMergedOutFileName;
|
||||||
|
const ext = getOutFileExtension({ isCustomFormatSelected, outFormat: fileFormat, filePath });
|
||||||
|
return getSuffixedFileName(filePath, `cut-merged-${new Date().getTime()}${ext}`);
|
||||||
|
}, [customMergedOutFileName, fileFormat, filePath, isCustomFormatSelected]);
|
||||||
|
|
||||||
|
const mergedOutFilePath = useMemo(() => (
|
||||||
|
getOutPath({ customOutDir, filePath, fileName: mergedOutFileName })
|
||||||
|
), [customOutDir, filePath, mergedOutFileName]);
|
||||||
|
|
||||||
const onExportConfirm = useCallback(async () => {
|
const onExportConfirm = useCallback(async () => {
|
||||||
if (numStreamsToCopy === 0) {
|
if (numStreamsToCopy === 0) {
|
||||||
errorToast(i18n.t('No tracks selected for export'));
|
errorToast(i18n.t('No tracks selected for export'));
|
||||||
|
@ -1167,17 +1179,15 @@ const App = memo(() => {
|
||||||
detectedFps,
|
detectedFps,
|
||||||
});
|
});
|
||||||
|
|
||||||
let concatOutPath;
|
|
||||||
if (willMerge) {
|
if (willMerge) {
|
||||||
setCutProgress(0);
|
setCutProgress(0);
|
||||||
setWorking(i18n.t('Merging'));
|
setWorking(i18n.t('Merging'));
|
||||||
|
|
||||||
const chapterNames = segmentsToChapters && !invertCutSegments ? segmentsToExport.map((s) => s.name) : undefined;
|
const chapterNames = segmentsToChapters && !invertCutSegments ? segmentsToExport.map((s) => s.name) : undefined;
|
||||||
|
|
||||||
concatOutPath = await autoConcatCutSegments({
|
await autoConcatCutSegments({
|
||||||
customOutDir,
|
customOutDir,
|
||||||
outFormat: fileFormat,
|
outFormat: fileFormat,
|
||||||
isCustomFormatSelected,
|
|
||||||
segmentPaths: outFiles,
|
segmentPaths: outFiles,
|
||||||
ffmpegExperimental,
|
ffmpegExperimental,
|
||||||
preserveMovData,
|
preserveMovData,
|
||||||
|
@ -1187,6 +1197,7 @@ const App = memo(() => {
|
||||||
autoDeleteMergedSegments,
|
autoDeleteMergedSegments,
|
||||||
preserveMetadataOnMerge,
|
preserveMetadataOnMerge,
|
||||||
appendFfmpegCommandLog,
|
appendFfmpegCommandLog,
|
||||||
|
mergedOutFilePath,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1217,7 +1228,7 @@ const App = memo(() => {
|
||||||
|
|
||||||
if (areWeCutting) notices.push(i18n.t('Cutpoints may be inaccurate.'));
|
if (areWeCutting) notices.push(i18n.t('Cutpoints may be inaccurate.'));
|
||||||
|
|
||||||
const revealPath = concatOutPath || outFiles[0];
|
const revealPath = willMerge ? mergedOutFilePath : outFiles[0];
|
||||||
if (!hideAllNotifications) openExportFinishedToast({ filePath: revealPath, warnings, notices });
|
if (!hideAllNotifications) openExportFinishedToast({ filePath: revealPath, warnings, notices });
|
||||||
|
|
||||||
if (cleanupChoices.cleanupAfterExport) await cleanupFiles(cleanupChoices);
|
if (cleanupChoices.cleanupAfterExport) await cleanupFiles(cleanupChoices);
|
||||||
|
@ -1244,7 +1255,7 @@ const App = memo(() => {
|
||||||
setWorking();
|
setWorking();
|
||||||
setCutProgress();
|
setCutProgress();
|
||||||
}
|
}
|
||||||
}, [numStreamsToCopy, setWorking, segmentsToChaptersOnly, outSegTemplateOrDefault, generateOutSegFileNames, segmentsToExport, getOutSegError, cutMultiple, outputDir, customOutDir, fileFormat, duration, isRotationSet, effectiveRotation, copyFileStreams, allFilesMeta, keyframeCut, shortestFlag, ffmpegExperimental, preserveMovData, preserveMetadataOnMerge, movFastStart, avoidNegativeTs, customTagsByFile, paramsByStreamId, detectedFps, willMerge, enableOverwriteOutput, exportConfirmEnabled, mainFileFormatData, mainStreams, exportExtraStreams, areWeCutting, hideAllNotifications, cleanupChoices, cleanupFiles, selectedSegmentsOrInverse, segmentsToChapters, invertCutSegments, autoConcatCutSegments, isCustomFormatSelected, autoDeleteMergedSegments, nonCopiedExtraStreams, filePath, handleExportFailed]);
|
}, [numStreamsToCopy, setWorking, segmentsToChaptersOnly, outSegTemplateOrDefault, generateOutSegFileNames, segmentsToExport, getOutSegError, cutMultiple, outputDir, customOutDir, fileFormat, duration, isRotationSet, effectiveRotation, copyFileStreams, allFilesMeta, keyframeCut, shortestFlag, ffmpegExperimental, preserveMovData, preserveMetadataOnMerge, movFastStart, avoidNegativeTs, customTagsByFile, paramsByStreamId, detectedFps, willMerge, enableOverwriteOutput, exportConfirmEnabled, mainFileFormatData, mainStreams, exportExtraStreams, areWeCutting, mergedOutFilePath, hideAllNotifications, cleanupChoices, cleanupFiles, selectedSegmentsOrInverse, segmentsToChapters, invertCutSegments, autoConcatCutSegments, autoDeleteMergedSegments, nonCopiedExtraStreams, filePath, handleExportFailed]);
|
||||||
|
|
||||||
const onExportPress = useCallback(async () => {
|
const onExportPress = useCallback(async () => {
|
||||||
if (!filePath || workingRef.current || segmentsToExport.length < 1) return;
|
if (!filePath || workingRef.current || segmentsToExport.length < 1) return;
|
||||||
|
@ -2483,7 +2494,7 @@ const App = memo(() => {
|
||||||
)}
|
)}
|
||||||
</Sheet>
|
</Sheet>
|
||||||
|
|
||||||
<ExportConfirm filePath={filePath} areWeCutting={areWeCutting} nonFilteredSegmentsOrInverse={nonFilteredSegmentsOrInverse} selectedSegments={selectedSegmentsOrInverse} segmentsToExport={segmentsToExport} willMerge={willMerge} visible={exportConfirmVisible} onClosePress={closeExportConfirm} onExportConfirm={onExportConfirm} renderOutFmt={renderOutFmt} outputDir={outputDir} numStreamsTotal={numStreamsTotal} numStreamsToCopy={numStreamsToCopy} onShowStreamsSelectorClick={handleShowStreamsSelectorClick} outFormat={fileFormat} setOutSegTemplate={setOutSegTemplate} outSegTemplate={outSegTemplateOrDefault} generateOutSegFileNames={generateOutSegFileNames} currentSegIndexSafe={currentSegIndexSafe} getOutSegError={getOutSegError} mainCopiedThumbnailStreams={mainCopiedThumbnailStreams} needSmartCut={needSmartCut} />
|
<ExportConfirm filePath={filePath} areWeCutting={areWeCutting} nonFilteredSegmentsOrInverse={nonFilteredSegmentsOrInverse} selectedSegments={selectedSegmentsOrInverse} segmentsToExport={segmentsToExport} willMerge={willMerge} visible={exportConfirmVisible} onClosePress={closeExportConfirm} onExportConfirm={onExportConfirm} renderOutFmt={renderOutFmt} outputDir={outputDir} numStreamsTotal={numStreamsTotal} numStreamsToCopy={numStreamsToCopy} onShowStreamsSelectorClick={handleShowStreamsSelectorClick} outFormat={fileFormat} setOutSegTemplate={setOutSegTemplate} outSegTemplate={outSegTemplateOrDefault} generateOutSegFileNames={generateOutSegFileNames} currentSegIndexSafe={currentSegIndexSafe} getOutSegError={getOutSegError} mainCopiedThumbnailStreams={mainCopiedThumbnailStreams} needSmartCut={needSmartCut} mergedOutFileName={mergedOutFileName} setMergedOutFileName={setMergedOutFileName} />
|
||||||
|
|
||||||
<LastCommandsSheet
|
<LastCommandsSheet
|
||||||
visible={lastCommandsVisible}
|
visible={lastCommandsVisible}
|
||||||
|
|
|
@ -155,7 +155,7 @@ const ConcatDialog = memo(({
|
||||||
|
|
||||||
const onOutputFormatUserChange = useCallback((newFormat) => setFileFormat(newFormat), [setFileFormat]);
|
const onOutputFormatUserChange = useCallback((newFormat) => setFileFormat(newFormat), [setFileFormat]);
|
||||||
|
|
||||||
const onConcatClick = useCallback(() => onConcat({ paths, includeAllStreams, streams: fileMeta.streams, fileName: outFileName, fileFormat, clearBatchFilesAfterConcat }), [clearBatchFilesAfterConcat, fileFormat, fileMeta, includeAllStreams, onConcat, outFileName, paths]);
|
const onConcatClick = useCallback(() => onConcat({ paths, includeAllStreams, streams: fileMeta.streams, outFileName, fileFormat, clearBatchFilesAfterConcat }), [clearBatchFilesAfterConcat, fileFormat, fileMeta, includeAllStreams, onConcat, outFileName, paths]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|
|
@ -15,6 +15,7 @@ import OutSegTemplateEditor from './OutSegTemplateEditor';
|
||||||
import HighlightedText, { highlightedTextStyle } from './HighlightedText';
|
import HighlightedText, { highlightedTextStyle } from './HighlightedText';
|
||||||
import Select from './Select';
|
import Select from './Select';
|
||||||
import Switch from './Switch';
|
import Switch from './Switch';
|
||||||
|
import MergedOutFileName from './MergedOutFileName';
|
||||||
|
|
||||||
import { primaryTextColor } from '../colors';
|
import { primaryTextColor } from '../colors';
|
||||||
import { withBlur } from '../util';
|
import { withBlur } from '../util';
|
||||||
|
@ -36,7 +37,7 @@ const ExportConfirm = memo(({
|
||||||
areWeCutting, selectedSegments, segmentsToExport, willMerge, visible, onClosePress, onExportConfirm,
|
areWeCutting, selectedSegments, segmentsToExport, willMerge, visible, onClosePress, onExportConfirm,
|
||||||
outFormat, renderOutFmt, outputDir, numStreamsTotal, numStreamsToCopy, onShowStreamsSelectorClick, outSegTemplate,
|
outFormat, renderOutFmt, outputDir, numStreamsTotal, numStreamsToCopy, onShowStreamsSelectorClick, outSegTemplate,
|
||||||
setOutSegTemplate, generateOutSegFileNames, filePath, currentSegIndexSafe, getOutSegError, nonFilteredSegmentsOrInverse,
|
setOutSegTemplate, generateOutSegFileNames, filePath, currentSegIndexSafe, getOutSegError, nonFilteredSegmentsOrInverse,
|
||||||
mainCopiedThumbnailStreams, needSmartCut,
|
mainCopiedThumbnailStreams, needSmartCut, mergedOutFileName, setMergedOutFileName,
|
||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
@ -198,6 +199,20 @@ const ExportConfirm = memo(({
|
||||||
</tr>
|
</tr>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{willMerge && (
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
{t('Merged output file name:')}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<MergedOutFileName mergedOutFileName={mergedOutFileName} setMergedOutFileName={setMergedOutFileName} />
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<HelpIcon onClick={() => showHelpText({ text: t('Name of the merged/concatenated output file when concatenating multiple segments.') })} />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
)}
|
||||||
|
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
{t('Overwrite existing files')}
|
{t('Overwrite existing files')}
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
import React, { memo } from 'react';
|
||||||
|
|
||||||
|
import TextInput from './TextInput';
|
||||||
|
|
||||||
|
|
||||||
|
const MergedOutFileName = memo(({ mergedOutFileName, setMergedOutFileName }) => (
|
||||||
|
<div style={{ display: 'flex', alignItems: 'center', flexWrap: 'wrap', justifyContent: 'flex-end' }}>
|
||||||
|
<TextInput value={mergedOutFileName} onChange={(e) => setMergedOutFileName(e.target.value)} style={{ textAlign: 'right' }} />
|
||||||
|
</div>
|
||||||
|
));
|
||||||
|
|
||||||
|
export default MergedOutFileName;
|
|
@ -13,6 +13,7 @@ import { defaultOutSegTemplate, segNumVariable, segSuffixVariable } from '../uti
|
||||||
import useUserSettings from '../hooks/useUserSettings';
|
import useUserSettings from '../hooks/useUserSettings';
|
||||||
import Switch from './Switch';
|
import Switch from './Switch';
|
||||||
import Select from './Select';
|
import Select from './Select';
|
||||||
|
import TextInput from './TextInput';
|
||||||
|
|
||||||
const ReactSwal = withReactContent(Swal);
|
const ReactSwal = withReactContent(Swal);
|
||||||
|
|
||||||
|
@ -22,8 +23,6 @@ const formatVariable = (variable) => `\${${variable}}`;
|
||||||
|
|
||||||
const extVar = formatVariable('EXT');
|
const extVar = formatVariable('EXT');
|
||||||
|
|
||||||
const inputStyle = { flexGrow: 1, fontFamily: 'inherit', fontSize: '.8em', backgroundColor: 'var(--gray3)', color: 'var(--gray12)', border: '1px solid var(--gray7)', appearance: 'none' };
|
|
||||||
|
|
||||||
const OutSegTemplateEditor = memo(({ outSegTemplate, setOutSegTemplate, generateOutSegFileNames, currentSegIndexSafe, getOutSegError }) => {
|
const OutSegTemplateEditor = memo(({ outSegTemplate, setOutSegTemplate, generateOutSegFileNames, currentSegIndexSafe, getOutSegError }) => {
|
||||||
const { safeOutputFileName, toggleSafeOutputFileName, outputFileNameMinZeroPadding, setOutputFileNameMinZeroPadding } = useUserSettings();
|
const { safeOutputFileName, toggleSafeOutputFileName, outputFileNameMinZeroPadding, setOutputFileNameMinZeroPadding } = useUserSettings();
|
||||||
|
|
||||||
|
@ -117,7 +116,7 @@ const OutSegTemplateEditor = memo(({ outSegTemplate, setOutSegTemplate, generate
|
||||||
exit={{ opacity: 0, height: 0, marginTop: 0 }}
|
exit={{ opacity: 0, height: 0, marginTop: 0 }}
|
||||||
>
|
>
|
||||||
<div style={{ display: 'flex', alignItems: 'center', marginBottom: '.2em' }}>
|
<div style={{ display: 'flex', alignItems: 'center', marginBottom: '.2em' }}>
|
||||||
<input type="text" ref={inputRef} style={inputStyle} onChange={onTextChange} value={text} autoComplete="off" autoCapitalize="off" autoCorrect="off" />
|
<TextInput ref={inputRef} onChange={onTextChange} value={text} autoComplete="off" autoCapitalize="off" autoCorrect="off" />
|
||||||
|
|
||||||
{outSegFileNames != null && <Button height={20} onClick={onAllSegmentsPreviewPress} marginLeft={5}>{t('Preview')}</Button>}
|
{outSegFileNames != null && <Button height={20} onClick={onAllSegmentsPreviewPress} marginLeft={5}>{t('Preview')}</Button>}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
import React, { forwardRef } from 'react';
|
||||||
|
|
||||||
|
const inputStyle = { borderRadius: '.4em', flexGrow: 1, fontFamily: 'inherit', fontSize: '.8em', backgroundColor: 'var(--gray3)', color: 'var(--gray12)', border: '1px solid var(--gray7)', appearance: 'none' };
|
||||||
|
|
||||||
|
const TextInput = forwardRef(({ style, ...props }, forwardedRef) => (
|
||||||
|
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||||
|
<input type="text" ref={forwardedRef} style={{ ...inputStyle, ...style }} {...props} />
|
||||||
|
));
|
||||||
|
|
||||||
|
export default TextInput;
|
|
@ -437,9 +437,7 @@ function useFfmpegOperations({ filePath, treatInputFileModifiedTimeAsStart, trea
|
||||||
}
|
}
|
||||||
}, [concatFiles, cutSingle, filePath, needSmartCut, shouldSkipExistingFile]);
|
}, [concatFiles, cutSingle, filePath, needSmartCut, shouldSkipExistingFile]);
|
||||||
|
|
||||||
const autoConcatCutSegments = useCallback(async ({ customOutDir, isCustomFormatSelected, outFormat, segmentPaths, ffmpegExperimental, onProgress, preserveMovData, movFastStart, autoDeleteMergedSegments, chapterNames, preserveMetadataOnMerge, appendFfmpegCommandLog }) => {
|
const autoConcatCutSegments = useCallback(async ({ customOutDir, outFormat, segmentPaths, ffmpegExperimental, onProgress, preserveMovData, movFastStart, autoDeleteMergedSegments, chapterNames, preserveMetadataOnMerge, appendFfmpegCommandLog, mergedOutFilePath }) => {
|
||||||
const ext = getOutFileExtension({ isCustomFormatSelected, outFormat, filePath });
|
|
||||||
const outPath = getSuffixedOutPath({ customOutDir, filePath, nameSuffix: `cut-merged-${new Date().getTime()}${ext}` });
|
|
||||||
const outDir = getOutDir(customOutDir, filePath);
|
const outDir = getOutDir(customOutDir, filePath);
|
||||||
|
|
||||||
const chapters = await createChaptersFromSegments({ segmentPaths, chapterNames });
|
const chapters = await createChaptersFromSegments({ segmentPaths, chapterNames });
|
||||||
|
@ -447,10 +445,8 @@ function useFfmpegOperations({ filePath, treatInputFileModifiedTimeAsStart, trea
|
||||||
const metadataFromPath = segmentPaths[0];
|
const metadataFromPath = segmentPaths[0];
|
||||||
// need to re-read streams because may have changed
|
// need to re-read streams because may have changed
|
||||||
const { streams } = await readFileMeta(metadataFromPath);
|
const { streams } = await readFileMeta(metadataFromPath);
|
||||||
await concatFiles({ paths: segmentPaths, outDir, outPath, metadataFromPath, outFormat, includeAllStreams: true, streams, ffmpegExperimental, onProgress, preserveMovData, movFastStart, chapters, preserveMetadataOnMerge, appendFfmpegCommandLog });
|
await concatFiles({ paths: segmentPaths, outDir, outPath: mergedOutFilePath, metadataFromPath, outFormat, includeAllStreams: true, streams, ffmpegExperimental, onProgress, preserveMovData, movFastStart, chapters, preserveMetadataOnMerge, appendFfmpegCommandLog });
|
||||||
if (autoDeleteMergedSegments) await tryDeleteFiles(segmentPaths);
|
if (autoDeleteMergedSegments) await tryDeleteFiles(segmentPaths);
|
||||||
|
|
||||||
return outPath;
|
|
||||||
}, [concatFiles, filePath]);
|
}, [concatFiles, filePath]);
|
||||||
|
|
||||||
const html5ify = useCallback(async ({ customOutDir, filePath: filePathArg, speed, hasAudio, hasVideo, onProgress }) => {
|
const html5ify = useCallback(async ({ customOutDir, filePath: filePathArg, speed, hasAudio, hasVideo, onProgress }) => {
|
||||||
|
|
Ładowanie…
Reference in New Issue