mirror of
https://github.com/mifi/lossless-cut.git
synced 2024-11-25 11:43:17 +01:00
improvements
- Show stream info - add cute loader
This commit is contained in:
parent
9973878175
commit
0cfe65e890
@ -75,12 +75,13 @@
|
|||||||
"react-dom": "^16.12.0",
|
"react-dom": "^16.12.0",
|
||||||
"react-hammerjs": "^1.0.1",
|
"react-hammerjs": "^1.0.1",
|
||||||
"react-icons": "^3.9.0",
|
"react-icons": "^3.9.0",
|
||||||
|
"react-lottie": "^1.2.3",
|
||||||
"react-sortable-hoc": "^1.5.3",
|
"react-sortable-hoc": "^1.5.3",
|
||||||
"react-use": "^13.24.0",
|
"react-use": "^13.24.0",
|
||||||
"read-chunk": "^2.0.0",
|
"read-chunk": "^2.0.0",
|
||||||
"string-to-stream": "^1.1.1",
|
"string-to-stream": "^1.1.1",
|
||||||
"strong-data-uri": "^1.0.5",
|
"strong-data-uri": "^1.0.5",
|
||||||
"sweetalert2": "^8.0.1",
|
"sweetalert2": "^9.7.2",
|
||||||
"sweetalert2-react-content": "^1.0.1",
|
"sweetalert2-react-content": "^1.0.1",
|
||||||
"trash": "^6.1.1",
|
"trash": "^6.1.1",
|
||||||
"uuid": "^3.3.2",
|
"uuid": "^3.3.2",
|
||||||
|
1
src/7077-magic-flow.json
Normal file
1
src/7077-magic-flow.json
Normal file
File diff suppressed because one or more lines are too long
@ -1,8 +1,13 @@
|
|||||||
import React, { memo, Fragment } from 'react';
|
import React, { memo, Fragment } from 'react';
|
||||||
|
|
||||||
import { FaVideo, FaVideoSlash, FaFileExport, FaFileImport, FaVolumeUp, FaVolumeMute, FaBan, FaTrashAlt } from 'react-icons/fa';
|
import { FaVideo, FaVideoSlash, FaFileExport, FaFileImport, FaVolumeUp, FaVolumeMute, FaBan, FaTrashAlt, FaInfoCircle } from 'react-icons/fa';
|
||||||
import { GoFileBinary } from 'react-icons/go';
|
import { GoFileBinary } from 'react-icons/go';
|
||||||
import { MdSubtitles } from 'react-icons/md';
|
import { MdSubtitles } from 'react-icons/md';
|
||||||
|
import Swal from 'sweetalert2';
|
||||||
|
|
||||||
|
import withReactContent from 'sweetalert2-react-content';
|
||||||
|
|
||||||
|
const ReactSwal = withReactContent(Swal);
|
||||||
|
|
||||||
const { formatDuration } = require('./util');
|
const { formatDuration } = require('./util');
|
||||||
const { getStreamFps } = require('./ffmpeg');
|
const { getStreamFps } = require('./ffmpeg');
|
||||||
@ -25,12 +30,17 @@ const Stream = memo(({ stream, onToggle, copyStream }) => {
|
|||||||
|
|
||||||
const streamFps = getStreamFps(stream);
|
const streamFps = getStreamFps(stream);
|
||||||
|
|
||||||
|
function onInfoClick(s) {
|
||||||
|
ReactSwal.fire({
|
||||||
|
icon: 'info',
|
||||||
|
title: 'Stream info',
|
||||||
|
html: <div style={{ whiteSpace: 'pre', textAlign: 'left', overflow: 'auto' }}>{JSON.stringify(s, null, 2)}</div>,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<tr
|
<tr style={{ opacity: copyStream ? undefined : 0.4 }}>
|
||||||
style={{ opacity: copyStream ? undefined : 0.4 }}
|
<td><Icon size={20} style={{ padding: '0 5px', cursor: 'pointer' }} role="button" onClick={() => onToggle && onToggle(stream.index)} /></td>
|
||||||
onClick={() => onToggle && onToggle(stream.index)}
|
|
||||||
>
|
|
||||||
<td><Icon size={20} style={{ padding: '0 5px', cursor: 'pointer' }} /></td>
|
|
||||||
<td>{stream.index}</td>
|
<td>{stream.index}</td>
|
||||||
<td>{stream.codec_type}</td>
|
<td>{stream.codec_type}</td>
|
||||||
<td>{stream.codec_tag !== '0x0000' && stream.codec_tag_string}</td>
|
<td>{stream.codec_tag !== '0x0000' && stream.codec_tag_string}</td>
|
||||||
@ -39,6 +49,7 @@ const Stream = memo(({ stream, onToggle, copyStream }) => {
|
|||||||
<td>{stream.nb_frames}</td>
|
<td>{stream.nb_frames}</td>
|
||||||
<td>{!Number.isNaN(bitrate) && `${(bitrate / 1e6).toFixed(1)}MBit/s`}</td>
|
<td>{!Number.isNaN(bitrate) && `${(bitrate / 1e6).toFixed(1)}MBit/s`}</td>
|
||||||
<td>{stream.width && stream.height && `${stream.width}x${stream.height}`} {stream.channels && `${stream.channels}c`} {stream.channel_layout} {streamFps && `${streamFps.toFixed(1)}fps`}</td>
|
<td>{stream.width && stream.height && `${stream.width}x${stream.height}`} {stream.channels && `${stream.channels}c`} {stream.channel_layout} {streamFps && `${streamFps.toFixed(1)}fps`}</td>
|
||||||
|
<td><FaInfoCircle role="button" onClick={() => onInfoClick(stream)} size={30} /></td>
|
||||||
</tr>
|
</tr>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@ -75,6 +86,7 @@ const StreamsSelector = memo(({
|
|||||||
<td>Frames</td>
|
<td>Frames</td>
|
||||||
<td>Bitrate</td>
|
<td>Bitrate</td>
|
||||||
<td>Data</td>
|
<td>Data</td>
|
||||||
|
<td />
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
|
@ -6,8 +6,9 @@ import { GiYinYang } from 'react-icons/gi';
|
|||||||
import { FiScissors } from 'react-icons/fi';
|
import { FiScissors } from 'react-icons/fi';
|
||||||
import { AnimatePresence, motion } from 'framer-motion';
|
import { AnimatePresence, motion } from 'framer-motion';
|
||||||
import Swal from 'sweetalert2';
|
import Swal from 'sweetalert2';
|
||||||
|
import Lottie from 'react-lottie';
|
||||||
import { SideSheet, Button, Position } from 'evergreen-ui';
|
import { SideSheet, Button, Position } from 'evergreen-ui';
|
||||||
|
|
||||||
import fromPairs from 'lodash/fromPairs';
|
import fromPairs from 'lodash/fromPairs';
|
||||||
import clamp from 'lodash/clamp';
|
import clamp from 'lodash/clamp';
|
||||||
import clone from 'lodash/clone';
|
import clone from 'lodash/clone';
|
||||||
@ -20,6 +21,8 @@ import InverseCutSegment from './InverseCutSegment';
|
|||||||
import StreamsSelector from './StreamsSelector';
|
import StreamsSelector from './StreamsSelector';
|
||||||
import { loadMifiLink } from './mifi';
|
import { loadMifiLink } from './mifi';
|
||||||
|
|
||||||
|
import loadingLottie from './7077-magic-flow.json';
|
||||||
|
|
||||||
|
|
||||||
const isDev = require('electron-is-dev');
|
const isDev = require('electron-is-dev');
|
||||||
const electron = require('electron'); // eslint-disable-line
|
const electron = require('electron'); // eslint-disable-line
|
||||||
@ -460,7 +463,7 @@ const App = memo(() => {
|
|||||||
return video.play().catch((err) => {
|
return video.play().catch((err) => {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
if (err.name === 'NotSupportedError') {
|
if (err.name === 'NotSupportedError') {
|
||||||
toast.fire({ type: 'error', title: 'This format/codec is not supported. Try to convert it to a friendly format/codec in the player from the "File" menu.', timer: 10000 });
|
toast.fire({ icon: 'error', title: 'This format/codec is not supported. Try to convert it to a friendly format/codec in the player from the "File" menu.', timer: 10000 });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}, [playing]);
|
}, [playing]);
|
||||||
@ -477,7 +480,7 @@ const App = memo(() => {
|
|||||||
await trash(filePath);
|
await trash(filePath);
|
||||||
if (html5FriendlyPath) await trash(html5FriendlyPath);
|
if (html5FriendlyPath) await trash(html5FriendlyPath);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
toast.fire({ type: 'error', title: `Failed to trash source file: ${err.message}` });
|
toast.fire({ icon: 'error', title: `Failed to trash source file: ${err.message}` });
|
||||||
} finally {
|
} finally {
|
||||||
resetState();
|
resetState();
|
||||||
}
|
}
|
||||||
@ -494,6 +497,11 @@ const App = memo(() => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (numStreamsToCopy === 0) {
|
||||||
|
errorToast('No tracks to export!');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const segments = invertCutSegments ? inverseCutSegments : apparentCutSegments;
|
const segments = invertCutSegments ? inverseCutSegments : apparentCutSegments;
|
||||||
|
|
||||||
const ffmpegSegments = segments.map((seg) => ({
|
const ffmpegSegments = segments.map((seg) => ({
|
||||||
@ -532,7 +540,7 @@ const App = memo(() => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
toast.fire({ timer: 10000, type: 'success', title: `Export completed! Output file(s) can be found at: ${getOutDir(customOutDir, filePath)}. You can change the output directory in settings` });
|
toast.fire({ timer: 10000, icon: 'success', title: `Export completed! Output file(s) can be found at: ${getOutDir(customOutDir, filePath)}. You can change the output directory in settings` });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('stdout:', err.stdout);
|
console.error('stdout:', err.stdout);
|
||||||
console.error('stderr:', err.stderr);
|
console.error('stderr:', err.stderr);
|
||||||
@ -549,11 +557,11 @@ const App = memo(() => {
|
|||||||
}, [
|
}, [
|
||||||
effectiveRotation, apparentCutSegments, invertCutSegments, inverseCutSegments,
|
effectiveRotation, apparentCutSegments, invertCutSegments, inverseCutSegments,
|
||||||
working, duration, filePath, keyframeCut, detectedFileFormat,
|
working, duration, filePath, keyframeCut, detectedFileFormat,
|
||||||
autoMerge, customOutDir, fileFormat, haveInvalidSegs, copyStreamIds,
|
autoMerge, customOutDir, fileFormat, haveInvalidSegs, copyStreamIds, numStreamsToCopy,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
function showUnsupportedFileMessage() {
|
function showUnsupportedFileMessage() {
|
||||||
toast.fire({ timer: 10000, type: 'warning', title: 'This video is not natively supported', text: 'This means that there is no audio in the preview and it has low quality. The final export operation will however be lossless and contains audio!' });
|
toast.fire({ timer: 10000, icon: 'warning', title: 'This video is not natively supported', text: 'This means that there is no audio in the preview and it has low quality. The final export operation will however be lossless and contains audio!' });
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO use ffmpeg to capture frame
|
// TODO use ffmpeg to capture frame
|
||||||
@ -719,7 +727,7 @@ const App = memo(() => {
|
|||||||
try {
|
try {
|
||||||
setWorking(true);
|
setWorking(true);
|
||||||
await ffmpeg.extractAllStreams({ customOutDir, filePath });
|
await ffmpeg.extractAllStreams({ customOutDir, filePath });
|
||||||
toast.fire({ type: 'success', title: `All streams can be found as separate files at: ${getOutDir(customOutDir, filePath)}` });
|
toast.fire({ icon: 'success', title: `All streams can be found as separate files at: ${getOutDir(customOutDir, filePath)}` });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
errorToast('Failed to extract all streams');
|
errorToast('Failed to extract all streams');
|
||||||
console.error('Failed to extract all streams', err);
|
console.error('Failed to extract all streams', err);
|
||||||
@ -755,6 +763,7 @@ const App = memo(() => {
|
|||||||
}
|
}
|
||||||
const { value } = await Swal.fire({
|
const { value } = await Swal.fire({
|
||||||
title: 'You opened a new file. What do you want to do?',
|
title: 'You opened a new file. What do you want to do?',
|
||||||
|
icon: 'question',
|
||||||
input: 'radio',
|
input: 'radio',
|
||||||
showCancelButton: true,
|
showCancelButton: true,
|
||||||
inputOptions: {
|
inputOptions: {
|
||||||
@ -1068,7 +1077,7 @@ const App = memo(() => {
|
|||||||
const topBarHeight = '2rem';
|
const topBarHeight = '2rem';
|
||||||
const bottomBarHeight = '6rem';
|
const bottomBarHeight = '6rem';
|
||||||
|
|
||||||
const VolumeIcon = muted ? FaVolumeMute : FaVolumeUp;
|
const VolumeIcon = muted || dummyVideoPath ? FaVolumeMute : FaVolumeUp;
|
||||||
const CutIcon = areWeCutting ? FiScissors : FaFileExport;
|
const CutIcon = areWeCutting ? FiScissors : FaFileExport;
|
||||||
|
|
||||||
function renderInvertCutButton() {
|
function renderInvertCutButton() {
|
||||||
@ -1090,6 +1099,8 @@ const App = memo(() => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const primaryColor = 'hsl(194, 78%, 47%)';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div style={{ background: '#6b6b6b', height: topBarHeight, display: 'flex', alignItems: 'center', padding: '0 5px', justifyContent: 'space-between' }}>
|
<div style={{ background: '#6b6b6b', height: topBarHeight, display: 'flex', alignItems: 'center', padding: '0 5px', justifyContent: 'space-between' }}>
|
||||||
@ -1169,19 +1180,38 @@ const App = memo(() => {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{working && (
|
<AnimatePresence>
|
||||||
<div style={{
|
{working && (
|
||||||
color: 'white', background: 'rgba(0, 0, 0, 0.3)', borderRadius: '.5em', margin: '1em', padding: '.2em .5em', position: 'absolute', zIndex: 1, top: topBarHeight, left: 0,
|
<div style={{
|
||||||
}}
|
position: 'absolute', zIndex: 1, bottom: bottomBarHeight, top: topBarHeight, left: 0, right: 0, display: 'flex', justifyContent: 'center', alignItems: 'center', pointerEvents: 'none',
|
||||||
>
|
}}
|
||||||
<i className="fa fa-cog fa-spin fa-3x fa-fw" style={{ verticalAlign: 'middle', width: '1em', height: '1em' }} />
|
>
|
||||||
{cutProgress != null && (
|
<motion.div
|
||||||
<span style={{ color: 'rgba(255, 255, 255, 0.7)', paddingLeft: '.4em' }}>
|
style={{ background: primaryColor, boxShadow: `${primaryColor} 0px 0px 20px 25px`, borderRadius: 20, paddingBottom: 15, color: 'white', textAlign: 'center', fontSize: 14 }}
|
||||||
{`${Math.floor(cutProgress * 100)} %`}
|
initial={{ opacity: 0, scale: 0 }}
|
||||||
</span>
|
animate={{ opacity: 1, scale: 1 }}
|
||||||
)}
|
exit={{ opacity: 0, scale: 0 }}
|
||||||
</div>
|
>
|
||||||
)}
|
<div style={{ width: 150, height: 150 }}>
|
||||||
|
<Lottie
|
||||||
|
options={{ loop: true, autoplay: true, animationData: loadingLottie }}
|
||||||
|
style={{ width: '170%', height: '130%', marginLeft: '-35%', marginTop: '-29%' }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style={{ marginTop: 10 }}>
|
||||||
|
WORKING
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{(cutProgress != null) && (
|
||||||
|
<div style={{ marginTop: 10 }}>
|
||||||
|
{`${Math.floor(cutProgress * 100)} %`}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</motion.div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</AnimatePresence>
|
||||||
|
|
||||||
<div style={{ position: 'absolute', top: topBarHeight, left: 0, right: 0, bottom: bottomBarHeight }}>
|
<div style={{ position: 'absolute', top: topBarHeight, left: 0, right: 0, bottom: bottomBarHeight }}>
|
||||||
{/* eslint-disable-next-line jsx-a11y/media-has-caption */}
|
{/* eslint-disable-next-line jsx-a11y/media-has-caption */}
|
||||||
@ -1403,7 +1433,7 @@ const App = memo(() => {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<span
|
<span
|
||||||
style={{ background: 'hsl(194, 78%, 47%)', borderRadius: 5, padding: '3px 7px', fontSize: 14 }}
|
style={{ background: primaryColor, borderRadius: 5, padding: '3px 7px', fontSize: 14 }}
|
||||||
onClick={cutClick}
|
onClick={cutClick}
|
||||||
title={cutSegments.length > 1 ? 'Export all segments' : 'Export selection'}
|
title={cutSegments.length > 1 ? 'Export all segments' : 'Export selection'}
|
||||||
role="button"
|
role="button"
|
||||||
|
21
yarn.lock
21
yarn.lock
@ -3896,6 +3896,11 @@ loose-envify@^1.4.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
js-tokens "^3.0.0 || ^4.0.0"
|
js-tokens "^3.0.0 || ^4.0.0"
|
||||||
|
|
||||||
|
lottie-web@^5.1.3:
|
||||||
|
version "5.6.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/lottie-web/-/lottie-web-5.6.4.tgz#8520f9e7e8daa9e0ab8343c628c3f470800088a4"
|
||||||
|
integrity sha512-eU+21Wo/RSi4i260S7fDUxfhNJ9PhfzUJMVQpip0yZd19oJ18jrNCoSQKVUzjC2TzOjqumlLZXR636ezKoWNQg==
|
||||||
|
|
||||||
lowercase-keys@^1.0.0, lowercase-keys@^1.0.1:
|
lowercase-keys@^1.0.0, lowercase-keys@^1.0.1:
|
||||||
version "1.0.1"
|
version "1.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.1.tgz#6f9e30b47084d971a7c820ff15a6c5167b74c26f"
|
resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.1.tgz#6f9e30b47084d971a7c820ff15a6c5167b74c26f"
|
||||||
@ -4805,6 +4810,14 @@ react-lifecycles-compat@^3.0.4:
|
|||||||
resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362"
|
resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362"
|
||||||
integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==
|
integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==
|
||||||
|
|
||||||
|
react-lottie@^1.2.3:
|
||||||
|
version "1.2.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/react-lottie/-/react-lottie-1.2.3.tgz#8544b96939e088658072eea5e12d912cdaa3acc1"
|
||||||
|
integrity sha512-qLCERxUr8M+4mm1LU0Ruxw5Y5Fn/OmYkGfnA+JDM/dZb3oKwVAJCjwnjkj9TMHtzR2U6sMEUD3ZZ1RaHagM7kA==
|
||||||
|
dependencies:
|
||||||
|
babel-runtime "^6.26.0"
|
||||||
|
lottie-web "^5.1.3"
|
||||||
|
|
||||||
react-scrollbar-size@^2.0.2:
|
react-scrollbar-size@^2.0.2:
|
||||||
version "2.1.0"
|
version "2.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/react-scrollbar-size/-/react-scrollbar-size-2.1.0.tgz#105e797135cab92b1f9e16f00071db7f29f80754"
|
resolved "https://registry.yarnpkg.com/react-scrollbar-size/-/react-scrollbar-size-2.1.0.tgz#105e797135cab92b1f9e16f00071db7f29f80754"
|
||||||
@ -5752,10 +5765,10 @@ sweetalert2-react-content@^1.0.1:
|
|||||||
resolved "https://registry.yarnpkg.com/sweetalert2-react-content/-/sweetalert2-react-content-1.0.1.tgz#0403145a1af819504e394df0dfc6194df67068a0"
|
resolved "https://registry.yarnpkg.com/sweetalert2-react-content/-/sweetalert2-react-content-1.0.1.tgz#0403145a1af819504e394df0dfc6194df67068a0"
|
||||||
integrity sha512-wZxDGbF24jzNXGsr3hjkSa5wQlhgq4wOPPZShe4RMujGdDuPtniodQGZOW2Tn38dzpgXSZ4/sb7++zxwueberw==
|
integrity sha512-wZxDGbF24jzNXGsr3hjkSa5wQlhgq4wOPPZShe4RMujGdDuPtniodQGZOW2Tn38dzpgXSZ4/sb7++zxwueberw==
|
||||||
|
|
||||||
sweetalert2@^8.0.1:
|
sweetalert2@^9.7.2:
|
||||||
version "8.0.1"
|
version "9.7.2"
|
||||||
resolved "https://registry.yarnpkg.com/sweetalert2/-/sweetalert2-8.0.1.tgz#d59fa124a56595c1e799dd4c635689afa0a5f811"
|
resolved "https://registry.yarnpkg.com/sweetalert2/-/sweetalert2-9.7.2.tgz#ad11a75e765b543792b032c27a5ab981a7c8429e"
|
||||||
integrity sha512-N88+meCLt1t/5dQEAvBs31j6qcEVucE/bwMj8JiuPsMRvXhnWKMbvLAFPGNO7a/LECRVjT6o0/mccmIVZqQZSQ==
|
integrity sha512-VmFmigf+rO20t5fOql21wIZlk60B8M02kXwPLmMA2qaBcVwvDQA+qGXuqtXipx1wsztTOiXgyDiBvdbHbIvQpw==
|
||||||
|
|
||||||
table@^5.2.3:
|
table@^5.2.3:
|
||||||
version "5.4.6"
|
version "5.4.6"
|
||||||
|
Loading…
Reference in New Issue
Block a user