mirror of
https://github.com/mifi/lossless-cut.git
synced 2024-11-21 18:02:35 +01:00
parent
5df1269b91
commit
7dbc8af226
@ -71,6 +71,7 @@
|
||||
"mkdirp": "^1.0.3",
|
||||
"mousetrap": "^1.6.5",
|
||||
"p-map": "^5.5.0",
|
||||
"p-retry": "^6.1.0",
|
||||
"pify": "^5.0.0",
|
||||
"pretty-bytes": "^6.0.0",
|
||||
"react": "^18.2.0",
|
||||
|
53
src/App.jsx
53
src/App.jsx
@ -171,13 +171,19 @@ const App = memo(() => {
|
||||
const [selectedBatchFiles, setSelectedBatchFiles] = useState([]);
|
||||
|
||||
// Store "working" in a ref so we can avoid race conditions
|
||||
const workingRef = useRef(working);
|
||||
const workingRef = useRef(!!working);
|
||||
const setWorking = useCallback((val) => {
|
||||
workingRef.current = val;
|
||||
setWorkingState(val);
|
||||
workingRef.current = !!val;
|
||||
setWorkingState(val ? { text: val.text, abortController: val.abortController } : undefined);
|
||||
}, []);
|
||||
|
||||
useEffect(() => setDocumentTitle({ filePath, working, cutProgress }), [cutProgress, filePath, working]);
|
||||
const handleAbortWorkingClick = useCallback(() => {
|
||||
console.log('User clicked abort');
|
||||
abortFfmpegs(); // todo use abortcontroller for this also
|
||||
working?.abortController?.abort();
|
||||
}, [working?.abortController]);
|
||||
|
||||
useEffect(() => setDocumentTitle({ filePath, working: working?.text, cutProgress }), [cutProgress, filePath, working?.text]);
|
||||
|
||||
const zoom = Math.floor(zoomUnrounded);
|
||||
|
||||
@ -593,7 +599,7 @@ const App = memo(() => {
|
||||
const subtitleStream = index != null && subtitleStreams.find((s) => s.index === index);
|
||||
if (!subtitleStream || workingRef.current) return;
|
||||
try {
|
||||
setWorking(i18n.t('Loading subtitle'));
|
||||
setWorking({ text: i18n.t('Loading subtitle') });
|
||||
const url = await extractSubtitleTrack(filePath, index);
|
||||
setSubtitlesByStreamId((old) => ({ ...old, [index]: { url, lang: subtitleStream.tags && subtitleStream.tags.language } }));
|
||||
setActiveSubtitleStreamIndex(index);
|
||||
@ -805,7 +811,7 @@ const App = memo(() => {
|
||||
|
||||
if (workingRef.current) return;
|
||||
try {
|
||||
setWorking(i18n.t('Batch converting to supported format'));
|
||||
setWorking({ text: i18n.t('Batch converting to supported format') });
|
||||
setCutProgress(0);
|
||||
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
@ -841,7 +847,7 @@ const App = memo(() => {
|
||||
|
||||
const html5ifyAndLoadWithPreferences = useCallback(async (cod, fp, speed, hv, ha) => {
|
||||
if (!enableAutoHtml5ify) return;
|
||||
setWorking(i18n.t('Converting to supported format'));
|
||||
setWorking({ text: i18n.t('Converting to supported format') });
|
||||
await html5ifyAndLoad(cod, fp, getConvertToSupportedFormat(speed), hv, ha);
|
||||
}, [enableAutoHtml5ify, setWorking, html5ifyAndLoad, getConvertToSupportedFormat]);
|
||||
|
||||
@ -996,7 +1002,7 @@ const App = memo(() => {
|
||||
if (workingRef.current) return;
|
||||
try {
|
||||
setConcatDialogVisible(false);
|
||||
setWorking(i18n.t('Merging'));
|
||||
setWorking({ text: i18n.t('Merging') });
|
||||
|
||||
const firstPath = paths[0];
|
||||
if (!firstPath) return;
|
||||
@ -1069,7 +1075,8 @@ const App = memo(() => {
|
||||
}
|
||||
|
||||
try {
|
||||
setWorking(i18n.t('Cleaning up'));
|
||||
const abortController = new AbortController();
|
||||
setWorking({ text: i18n.t('Cleaning up'), abortController });
|
||||
console.log('Cleaning up files', cleanupChoices2);
|
||||
|
||||
const pathsToDelete = [];
|
||||
@ -1077,7 +1084,7 @@ const App = memo(() => {
|
||||
if (cleanupChoices2.trashProjectFile && savedPaths.projectFilePath) pathsToDelete.push(savedPaths.projectFilePath);
|
||||
if (cleanupChoices2.trashSourceFile && savedPaths.sourceFilePath) pathsToDelete.push(savedPaths.sourceFilePath);
|
||||
|
||||
await deleteFiles(pathsToDelete, cleanupChoices2.deleteIfTrashFails);
|
||||
await deleteFiles({ paths: pathsToDelete, deleteIfTrashFails: cleanupChoices2.deleteIfTrashFails, signal: abortController.signal });
|
||||
} catch (err) {
|
||||
errorToast(i18n.t('Unable to delete file: {{message}}', { message: err.message }));
|
||||
console.error(err);
|
||||
@ -1151,7 +1158,7 @@ const App = memo(() => {
|
||||
|
||||
if (workingRef.current) return;
|
||||
try {
|
||||
setWorking(i18n.t('Exporting'));
|
||||
setWorking({ text: i18n.t('Exporting') });
|
||||
|
||||
// Special segments-to-chapters mode:
|
||||
let chaptersToAdd;
|
||||
@ -1199,7 +1206,7 @@ const App = memo(() => {
|
||||
|
||||
if (willMerge) {
|
||||
setCutProgress(0);
|
||||
setWorking(i18n.t('Merging'));
|
||||
setWorking({ text: i18n.t('Merging') });
|
||||
|
||||
const chapterNames = segmentsToChapters && !invertCutSegments ? segmentsToExport.map((s) => s.name) : undefined;
|
||||
|
||||
@ -1235,7 +1242,7 @@ const App = memo(() => {
|
||||
if (exportExtraStreams) {
|
||||
try {
|
||||
setCutProgress(); // If extracting extra streams takes a long time, prevent loader from being stuck at 100%
|
||||
setWorking(i18n.t('Extracting {{count}} unprocessable tracks', { count: nonCopiedExtraStreams.length }));
|
||||
setWorking({ text: i18n.t('Extracting {{count}} unprocessable tracks', { count: nonCopiedExtraStreams.length }) });
|
||||
await extractStreams({ filePath, customOutDir, streams: nonCopiedExtraStreams, enableOverwriteOutput });
|
||||
notices.push(i18n.t('Unprocessable streams were exported as separate files.'));
|
||||
} catch (err) {
|
||||
@ -1311,7 +1318,7 @@ const App = memo(() => {
|
||||
if (captureFramesResponse == null) return;
|
||||
|
||||
try {
|
||||
setWorking(i18n.t('Extracting frames'));
|
||||
setWorking({ text: i18n.t('Extracting frames') });
|
||||
console.log('Extracting frames as images', { segIds, captureFramesResponse });
|
||||
|
||||
setCutProgress(0);
|
||||
@ -1411,7 +1418,7 @@ const App = memo(() => {
|
||||
}
|
||||
}
|
||||
|
||||
setWorking(i18n.t('Loading file'));
|
||||
setWorking({ text: i18n.t('Loading file') });
|
||||
try {
|
||||
// Need to check if file is actually readable
|
||||
const pathReadAccessErrorCode = await getPathReadAccessError(fp);
|
||||
@ -1586,7 +1593,7 @@ const App = memo(() => {
|
||||
if (workingRef.current) return;
|
||||
if (filePath === path) return;
|
||||
try {
|
||||
setWorking(i18n.t('Loading file'));
|
||||
setWorking({ text: i18n.t('Loading file') });
|
||||
await userOpenSingleFile({ path });
|
||||
} catch (err) {
|
||||
handleError(err);
|
||||
@ -1645,7 +1652,7 @@ const App = memo(() => {
|
||||
|
||||
if (workingRef.current) return;
|
||||
try {
|
||||
setWorking(i18n.t('Extracting all streams'));
|
||||
setWorking({ text: i18n.t('Extracting all streams') });
|
||||
setStreamsSelectorShown(false);
|
||||
const extractedPaths = await extractStreams({ customOutDir, filePath, streams: mainCopiedStreams, enableOverwriteOutput });
|
||||
if (!hideAllNotifications) openDirToast({ icon: 'success', filePath: extractedPaths[0], text: i18n.t('All streams have been extracted as separate files') });
|
||||
@ -1685,7 +1692,7 @@ const App = memo(() => {
|
||||
|
||||
if (workingRef.current) return;
|
||||
try {
|
||||
setWorking(i18n.t('Converting to supported format'));
|
||||
setWorking({ text: i18n.t('Converting to supported format') });
|
||||
await html5ifyAndLoad(customOutDir, filePath, selectedOption, hasVideo, hasAudio);
|
||||
} catch (err) {
|
||||
errorToast(i18n.t('Failed to convert file. Try a different conversion'));
|
||||
@ -1712,7 +1719,7 @@ const App = memo(() => {
|
||||
const tryFixInvalidDuration = useCallback(async () => {
|
||||
if (!checkFileOpened() || workingRef.current) return;
|
||||
try {
|
||||
setWorking(i18n.t('Fixing file duration'));
|
||||
setWorking({ text: i18n.t('Fixing file duration') });
|
||||
setCutProgress(0);
|
||||
const path = await fixInvalidDuration({ fileFormat, customOutDir, duration, onProgress: setCutProgress });
|
||||
if (!hideAllNotifications) toast.fire({ icon: 'info', text: i18n.t('Duration has been fixed') });
|
||||
@ -1809,7 +1816,7 @@ const App = memo(() => {
|
||||
|
||||
if (workingRef.current) return;
|
||||
try {
|
||||
setWorking(i18n.t('Loading file'));
|
||||
setWorking({ text: i18n.t('Loading file') });
|
||||
|
||||
// Import segments for for already opened file
|
||||
const matchingImportProjectType = getImportProjectType(firstFilePath);
|
||||
@ -2075,7 +2082,7 @@ const App = memo(() => {
|
||||
|
||||
if (workingRef.current) return;
|
||||
try {
|
||||
setWorking(i18n.t('Extracting track'));
|
||||
setWorking({ text: i18n.t('Extracting track') });
|
||||
// setStreamsSelectorShown(false);
|
||||
const extractedPaths = await extractStreams({ customOutDir, filePath, streams: mainStreams.filter((s) => s.index === index), enableOverwriteOutput });
|
||||
if (!hideAllNotifications) openDirToast({ icon: 'success', filePath: extractedPaths[0], text: i18n.t('Track has been extracted') });
|
||||
@ -2111,7 +2118,7 @@ const App = memo(() => {
|
||||
|
||||
if (workingRef.current) return;
|
||||
try {
|
||||
setWorking(i18n.t('Converting to supported format'));
|
||||
setWorking({ text: i18n.t('Converting to supported format') });
|
||||
|
||||
console.log('Trying to create preview');
|
||||
|
||||
@ -2388,7 +2395,7 @@ const App = memo(() => {
|
||||
)}
|
||||
|
||||
<AnimatePresence>
|
||||
{working && <Working text={working} cutProgress={cutProgress} onAbortClick={() => abortFfmpegs()} />}
|
||||
{working && <Working text={working.text} cutProgress={cutProgress} onAbortClick={handleAbortWorkingClick} />}
|
||||
</AnimatePresence>
|
||||
|
||||
{tunerVisible && <ValueTuners type={tunerVisible} onFinished={() => setTunerVisible()} />}
|
||||
|
@ -84,7 +84,7 @@ export default ({
|
||||
if (!filePath) return;
|
||||
if (workingRef.current) return;
|
||||
try {
|
||||
setWorking(workingText);
|
||||
setWorking({ text: workingText });
|
||||
setCutProgress(0);
|
||||
|
||||
const newSegments = await fn();
|
||||
@ -250,7 +250,7 @@ export default ({
|
||||
try {
|
||||
const response = await askForAlignSegments();
|
||||
if (response == null) return;
|
||||
setWorking(i18n.t('Aligning segments to keyframes'));
|
||||
setWorking({ text: i18n.t('Aligning segments to keyframes') });
|
||||
const { mode, startOrEnd } = response;
|
||||
await modifySelectedSegmentTimes(async (segment) => {
|
||||
const newSegment = { ...segment };
|
||||
|
22
src/util.js
22
src/util.js
@ -3,6 +3,7 @@ import pMap from 'p-map';
|
||||
import ky from 'ky';
|
||||
import prettyBytes from 'pretty-bytes';
|
||||
import sortBy from 'lodash/sortBy';
|
||||
import pRetry from 'p-retry';
|
||||
|
||||
import isDev from './isDev';
|
||||
import Swal, { toast } from './swal';
|
||||
@ -234,14 +235,19 @@ export function getHtml5ifiedPath(cod, fp, type) {
|
||||
return getSuffixedOutPath({ customOutDir: cod, filePath: fp, nameSuffix: `${html5ifiedPrefix}${type}.${ext}` });
|
||||
}
|
||||
|
||||
export async function deleteFiles(paths, deleteIfTrashFails) {
|
||||
export async function deleteFiles({ paths, deleteIfTrashFails, signal }) {
|
||||
const failedToTrashFiles = [];
|
||||
|
||||
// const testFail = isDev;
|
||||
const testFail = false;
|
||||
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
for (const path of paths) {
|
||||
try {
|
||||
if (testFail) throw new Error('test trash failure');
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
await trashFile(path);
|
||||
signal.throwIfAborted();
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
failedToTrashFiles.push(path);
|
||||
@ -260,7 +266,19 @@ export async function deleteFiles(paths, deleteIfTrashFails) {
|
||||
if (!value) return;
|
||||
}
|
||||
|
||||
await pMap(failedToTrashFiles, async (path) => unlink(path), { concurrency: 1 });
|
||||
// Retry because sometimes it fails on windows #272 #1797
|
||||
await pMap(failedToTrashFiles, async (path) => {
|
||||
await pRetry(async () => {
|
||||
if (testFail) throw new Error('test delete failure');
|
||||
await unlink(path);
|
||||
}, {
|
||||
retries: 3,
|
||||
signal,
|
||||
onFailedAttempt: async () => {
|
||||
console.warn('Retrying delete', path);
|
||||
},
|
||||
});
|
||||
}, { concurrency: 1 });
|
||||
}
|
||||
|
||||
export const deleteDispositionValue = 'llc_disposition_remove';
|
||||
|
33
yarn.lock
33
yarn.lock
@ -1703,6 +1703,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/retry@npm:0.12.2":
|
||||
version: 0.12.2
|
||||
resolution: "@types/retry@npm:0.12.2"
|
||||
checksum: e5675035717b39ce4f42f339657cae9637cf0c0051cf54314a6a2c44d38d91f6544be9ddc0280587789b6afd056be5d99dbe3e9f4df68c286c36321579b1bf4a
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/scheduler@npm:*":
|
||||
version: 0.16.2
|
||||
resolution: "@types/scheduler@npm:0.16.2"
|
||||
@ -5964,6 +5971,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"is-network-error@npm:^1.0.0":
|
||||
version: 1.0.0
|
||||
resolution: "is-network-error@npm:1.0.0"
|
||||
checksum: 2ca2b4b2d420015e0237abe28ebf316fcd26a82304b07432abf155759a3bee6895609ac91e692a72ad61b7fc902c3283b2dece61e1ddb05a6257777a8573e468
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"is-number-object@npm:^1.0.4":
|
||||
version: 1.0.6
|
||||
resolution: "is-number-object@npm:1.0.6"
|
||||
@ -6597,6 +6611,7 @@ __metadata:
|
||||
morgan: ^1.10.0
|
||||
mousetrap: ^1.6.5
|
||||
p-map: ^5.5.0
|
||||
p-retry: ^6.1.0
|
||||
pify: ^5.0.0
|
||||
pretty-bytes: ^6.0.0
|
||||
react: ^18.2.0
|
||||
@ -7543,6 +7558,17 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"p-retry@npm:^6.1.0":
|
||||
version: 6.1.0
|
||||
resolution: "p-retry@npm:6.1.0"
|
||||
dependencies:
|
||||
"@types/retry": 0.12.2
|
||||
is-network-error: ^1.0.0
|
||||
retry: ^0.13.1
|
||||
checksum: 1083b2b72672205680f8a736583e31dce5d4ae472996cd06f4a33cd7ea11798d7712c202d253eb8afbdc80abf52f049651989c59f2e2ccca529e6b64d722b1f7
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"p-try@npm:^1.0.0":
|
||||
version: 1.0.0
|
||||
resolution: "p-try@npm:1.0.0"
|
||||
@ -8484,6 +8510,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"retry@npm:^0.13.1":
|
||||
version: 0.13.1
|
||||
resolution: "retry@npm:0.13.1"
|
||||
checksum: 47c4d5be674f7c13eee4cfe927345023972197dbbdfba5d3af7e461d13b44de1bfd663bfc80d2f601f8ef3fc8164c16dd99655a221921954a65d044a2fc1233b
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"reusify@npm:^1.0.4":
|
||||
version: 1.0.4
|
||||
resolution: "reusify@npm:1.0.4"
|
||||
|
Loading…
Reference in New Issue
Block a user