mirror of
https://github.com/mifi/lossless-cut.git
synced 2024-11-25 11:43:17 +01:00
Improvements #257
- Change wording from cut away to remove - separate settings - show popup when switching mode
This commit is contained in:
parent
6ef45af2d1
commit
d3334f1d04
@ -88,7 +88,7 @@ Unsupported files can still be remuxed (fast) or encoded (slow) to a friendly fo
|
||||
## Typical workflow
|
||||
- Drag drop a video file into player or use <kbd>⌘</kbd>/<kbd>CTRL</kbd>+<kbd>O</kbd>.
|
||||
- Press <kbd>SPACE</kbd> to play/pause or <kbd>◀</kbd><kbd>▶</kbd>, <kbd>,</kbd><kbd>.</kbd> or mouse/trackpad wheel to seek back/forth
|
||||
- Select the cut segment's start and end time by moving the time marker and then pressing <kbd>I</kbd> to set start time, and <kbd>O</kbd> to set end time. *Note that the segments you select will be **preserved** and exported to a new file. You can change this behavior with the Yin Yang symbol ☯️, in which case it will instead **cut away** all selected segments and export the parts between.*
|
||||
- Select the cut segment's start and end time by moving the time marker and then pressing <kbd>I</kbd> to set start time, and <kbd>O</kbd> to set end time. *Note that the segments you select will be **preserved** and exported to a new file. You can change this behavior with the Yin Yang symbol ☯️, in which case it will instead **remove** all selected segments and export the parts between.*
|
||||
- *(optional)* If you want to add more than one segment, move to the desired start time and press <kbd>+</kbd>, then select the next segment start/end times with <kbd>I</kbd>/<kbd>O</kbd>.
|
||||
- *(optional)* If you want to re-merge all the selected segments to one file after cutting, toggle the button `Separate files` to `Merge cuts`.
|
||||
- *(optional)* If you want to export to a certain dir, press the `Working dir unset` button (default: Input file path)
|
||||
|
@ -2,7 +2,6 @@ import React, { memo } from 'react';
|
||||
import { IoIosCloseCircleOutline } from 'react-icons/io';
|
||||
import { FaClipboard } from 'react-icons/fa';
|
||||
import { motion, AnimatePresence } from 'framer-motion';
|
||||
import { Table } from 'evergreen-ui';
|
||||
|
||||
// eslint-disable-next-line import/no-extraneous-dependencies
|
||||
const { clipboard } = require('electron');
|
||||
@ -10,7 +9,7 @@ const { clipboard } = require('electron');
|
||||
const { toast } = require('./util');
|
||||
|
||||
const HelpSheet = memo(({
|
||||
visible, onTogglePress, renderSettings, ffmpegCommandLog,
|
||||
visible, onTogglePress, ffmpegCommandLog,
|
||||
}) => (
|
||||
<AnimatePresence>
|
||||
{visible && (
|
||||
@ -47,24 +46,11 @@ const HelpSheet = memo(({
|
||||
|
||||
<p style={{ fontWeight: 'bold' }}>Hover mouse over buttons in the main interface to see which function they have.</p>
|
||||
|
||||
<Table style={{ marginTop: 40 }}>
|
||||
<Table.Head>
|
||||
<Table.TextHeaderCell>
|
||||
Settings
|
||||
</Table.TextHeaderCell>
|
||||
<Table.TextHeaderCell>
|
||||
Current setting
|
||||
</Table.TextHeaderCell>
|
||||
</Table.Head>
|
||||
<Table.Body>
|
||||
{renderSettings()}
|
||||
</Table.Body>
|
||||
</Table>
|
||||
|
||||
<h1 style={{ marginTop: 40 }}>Last ffmpeg commands</h1>
|
||||
<div style={{ overflowY: 'scroll', height: 200 }}>
|
||||
{ffmpegCommandLog.reverse().map(({ command, time }) => (
|
||||
<div key={time} style={{ whiteSpace: 'pre', margin: '5px 0' }}>
|
||||
{ffmpegCommandLog.reverse().map(({ command }, i) => (
|
||||
// eslint-disable-next-line react/no-array-index-key
|
||||
<div key={i} style={{ whiteSpace: 'pre', margin: '5px 0' }}>
|
||||
<FaClipboard style={{ cursor: 'pointer' }} title="Copy to clipboard" onClick={() => { clipboard.writeText(command); toast.fire({ timer: 2000, icon: 'success', title: 'Copied to clipboard' }); }} /> {command}
|
||||
</div>
|
||||
))}
|
||||
|
@ -3,35 +3,46 @@ import { Select } from 'evergreen-ui';
|
||||
import { motion } from 'framer-motion';
|
||||
import { FaYinYang } from 'react-icons/fa';
|
||||
|
||||
const { withBlur } = require('./util');
|
||||
const { withBlur, toast } = require('./util');
|
||||
|
||||
|
||||
const LeftMenu = memo(({ zoom, setZoom, invertCutSegments, setInvertCutSegments }) => (
|
||||
<div className="no-user-select" style={{ padding: '.3em', display: 'flex', alignItems: 'center' }}>
|
||||
<div style={{ marginLeft: 5 }}>
|
||||
<motion.div
|
||||
animate={{ rotateX: invertCutSegments ? 0 : 180, width: 26, height: 26 }}
|
||||
transition={{ duration: 0.3 }}
|
||||
>
|
||||
<FaYinYang
|
||||
size={26}
|
||||
role="button"
|
||||
title={invertCutSegments ? 'Discard selected segments' : 'Keep selected segments'}
|
||||
onClick={withBlur(() => setInvertCutSegments(v => !v))}
|
||||
/>
|
||||
</motion.div>
|
||||
const LeftMenu = memo(({ zoom, setZoom, invertCutSegments, setInvertCutSegments }) => {
|
||||
function onYinYangClick() {
|
||||
setInvertCutSegments(v => {
|
||||
const newVal = !v;
|
||||
if (newVal) toast.fire({ title: 'When you export, selected segments on the timeline will be REMOVED - the surrounding areas will be KEPT' });
|
||||
else toast.fire({ title: 'When you export, selected segments on the timeline will be KEPT - the surrounding areas will be REMOVED.' });
|
||||
return newVal;
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="no-user-select" style={{ padding: '.3em', display: 'flex', alignItems: 'center' }}>
|
||||
<div style={{ marginLeft: 5 }}>
|
||||
<motion.div
|
||||
animate={{ rotateX: invertCutSegments ? 0 : 180, width: 26, height: 26 }}
|
||||
transition={{ duration: 0.3 }}
|
||||
>
|
||||
<FaYinYang
|
||||
size={26}
|
||||
role="button"
|
||||
title={invertCutSegments ? 'Discard selected segments' : 'Keep selected segments'}
|
||||
onClick={onYinYangClick}
|
||||
/>
|
||||
</motion.div>
|
||||
</div>
|
||||
|
||||
<div style={{ marginRight: 5, marginLeft: 10 }} title="Zoom">{Math.floor(zoom)}x</div>
|
||||
<Select height={20} style={{ width: 20 }} value={zoom.toString()} title="Zoom" onChange={withBlur(e => setZoom(parseInt(e.target.value, 10)))}>
|
||||
{Array(13).fill().map((unused, z) => {
|
||||
const val = 2 ** z;
|
||||
return (
|
||||
<option key={val} value={String(val)}>Zoom {val}x</option>
|
||||
);
|
||||
})}
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<div style={{ marginRight: 5, marginLeft: 10 }} title="Zoom">{Math.floor(zoom)}x</div>
|
||||
<Select height={20} style={{ width: 20 }} value={zoom.toString()} title="Zoom" onChange={withBlur(e => setZoom(parseInt(e.target.value, 10)))}>
|
||||
{Array(13).fill().map((unused, z) => {
|
||||
const val = 2 ** z;
|
||||
return (
|
||||
<option key={val} value={String(val)}>Zoom {val}x</option>
|
||||
);
|
||||
})}
|
||||
</Select>
|
||||
</div>
|
||||
));
|
||||
);
|
||||
});
|
||||
|
||||
export default LeftMenu;
|
||||
|
@ -59,13 +59,13 @@ const Settings = memo(({
|
||||
|
||||
<Row>
|
||||
<KeyCell>
|
||||
<span role="img" aria-label="Yin Yang">☯️</span> Choose cutting mode: Cut away or keep selected segments from video when exporting?<br />
|
||||
<span role="img" aria-label="Yin Yang">☯️</span> Choose cutting mode: Remove or keep selected segments from video when exporting?<br />
|
||||
When <b>Keep</b> is selected, the video inside segments will be kept, while the video outside will be discarded.<br />
|
||||
When <b>Cut away</b> is selected, the video inside segments will be discarded, while the video surrounding them will be kept.
|
||||
When <b>Remove</b> is selected, the video inside segments will be discarded, while the video surrounding them will be kept.
|
||||
</KeyCell>
|
||||
<Table.TextCell>
|
||||
<SegmentedControl
|
||||
options={[{ label: 'Cut away', value: 'discard' }, { label: 'Keep', value: 'keep' }]}
|
||||
options={[{ label: 'Remove', value: 'discard' }, { label: 'Keep', value: 'keep' }]}
|
||||
value={invertCutSegments ? 'discard' : 'keep'}
|
||||
onChange={value => setInvertCutSegments(value === 'discard')}
|
||||
/>
|
||||
|
43
src/SettingsSheet.jsx
Normal file
43
src/SettingsSheet.jsx
Normal file
@ -0,0 +1,43 @@
|
||||
import React, { memo } from 'react';
|
||||
import { IoIosCloseCircleOutline } from 'react-icons/io';
|
||||
import { FaClipboard } from 'react-icons/fa';
|
||||
import { motion, AnimatePresence } from 'framer-motion';
|
||||
import { Table } from 'evergreen-ui';
|
||||
|
||||
// eslint-disable-next-line import/no-extraneous-dependencies
|
||||
const { clipboard } = require('electron');
|
||||
|
||||
const { toast } = require('./util');
|
||||
|
||||
const SettingsSheet = memo(({
|
||||
visible, onTogglePress, renderSettings,
|
||||
}) => (
|
||||
<AnimatePresence>
|
||||
{visible && (
|
||||
<motion.div
|
||||
initial={{ scale: 0, opacity: 0 }}
|
||||
animate={{ scale: 1, opacity: 1 }}
|
||||
exit={{ scale: 0, opacity: 0 }}
|
||||
className="help-sheet"
|
||||
>
|
||||
<IoIosCloseCircleOutline role="button" onClick={onTogglePress} size={30} style={{ position: 'fixed', right: 0, top: 0, padding: 20 }} />
|
||||
|
||||
<Table style={{ marginTop: 40 }}>
|
||||
<Table.Head>
|
||||
<Table.TextHeaderCell>
|
||||
Settings
|
||||
</Table.TextHeaderCell>
|
||||
<Table.TextHeaderCell>
|
||||
Current setting
|
||||
</Table.TextHeaderCell>
|
||||
</Table.Head>
|
||||
<Table.Body>
|
||||
{renderSettings()}
|
||||
</Table.Body>
|
||||
</Table>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
));
|
||||
|
||||
export default SettingsSheet;
|
@ -1,5 +1,5 @@
|
||||
import React, { Fragment, memo } from 'react';
|
||||
import { IoIosHelpCircle } from 'react-icons/io';
|
||||
import { IoIosHelpCircle, IoIosSettings } from 'react-icons/io';
|
||||
import { Button } from 'evergreen-ui';
|
||||
import { MdCallSplit, MdCallMerge } from 'react-icons/md';
|
||||
|
||||
@ -9,7 +9,7 @@ import { withBlur } from './util';
|
||||
const TopMenu = memo(({
|
||||
filePath, copyAnyAudioTrack, toggleStripAudio, customOutDir, setOutputDir,
|
||||
renderOutFmt, outSegments, autoMerge, toggleAutoMerge, keyframeCut, toggleKeyframeCut, toggleHelp,
|
||||
numStreamsToCopy, numStreamsTotal, setStreamsSelectorShown,
|
||||
numStreamsToCopy, numStreamsTotal, setStreamsSelectorShown, toggleSettings,
|
||||
}) => {
|
||||
const AutoMergeIcon = autoMerge ? MdCallMerge : MdCallSplit;
|
||||
|
||||
@ -68,6 +68,7 @@ const TopMenu = memo(({
|
||||
)}
|
||||
|
||||
<IoIosHelpCircle size={24} role="button" onClick={toggleHelp} style={{ verticalAlign: 'middle', marginLeft: 5 }} />
|
||||
<IoIosSettings size={24} role="button" onClick={toggleSettings} style={{ verticalAlign: 'middle', marginLeft: 5 }} />
|
||||
</Fragment>
|
||||
);
|
||||
});
|
||||
|
@ -72,6 +72,13 @@ module.exports = (app, mainWindow, newVersion) => {
|
||||
},
|
||||
},
|
||||
{ type: 'separator' },
|
||||
{
|
||||
label: 'Settings',
|
||||
click() {
|
||||
mainWindow.webContents.send('openSettings');
|
||||
},
|
||||
},
|
||||
{ type: 'separator' },
|
||||
{
|
||||
label: 'Exit',
|
||||
click() {
|
||||
@ -124,7 +131,7 @@ module.exports = (app, mainWindow, newVersion) => {
|
||||
role: 'help',
|
||||
submenu: [
|
||||
{
|
||||
label: 'Help and Settings',
|
||||
label: 'Help',
|
||||
click() {
|
||||
mainWindow.webContents.send('openHelp');
|
||||
},
|
||||
|
@ -16,6 +16,7 @@ import isEqual from 'lodash/isEqual';
|
||||
|
||||
import TopMenu from './TopMenu';
|
||||
import HelpSheet from './HelpSheet';
|
||||
import SettingsSheet from './SettingsSheet';
|
||||
import StreamsSelector from './StreamsSelector';
|
||||
import SegmentList from './SegmentList';
|
||||
import Settings from './Settings';
|
||||
@ -204,6 +205,7 @@ const App = memo(() => {
|
||||
|
||||
// Global state
|
||||
const [helpVisible, setHelpVisible] = useState(false);
|
||||
const [settingsVisible, setSettingsVisible] = useState(false);
|
||||
const [mifiLink, setMifiLink] = useState();
|
||||
|
||||
const videoRef = useRef();
|
||||
@ -1040,6 +1042,7 @@ const App = memo(() => {
|
||||
]);
|
||||
|
||||
const toggleHelp = useCallback(() => setHelpVisible(val => !val), []);
|
||||
const toggleSettings = useCallback(() => setSettingsVisible(val => !val), []);
|
||||
|
||||
const jumpSeg = useCallback((val) => setCurrentSegIndex((old) => Math.max(Math.min(old + val, cutSegments.length - 1), 0)), [cutSegments.length]);
|
||||
|
||||
@ -1264,6 +1267,10 @@ const App = memo(() => {
|
||||
toggleHelp();
|
||||
}
|
||||
|
||||
function openSettings() {
|
||||
toggleSettings();
|
||||
}
|
||||
|
||||
electron.ipcRenderer.on('file-opened', fileOpened);
|
||||
electron.ipcRenderer.on('close-file', closeFile);
|
||||
electron.ipcRenderer.on('html5ify', html5ify);
|
||||
@ -1275,6 +1282,7 @@ const App = memo(() => {
|
||||
electron.ipcRenderer.on('importEdlFile', importEdlFile);
|
||||
electron.ipcRenderer.on('exportEdlFile', exportEdlFile);
|
||||
electron.ipcRenderer.on('openHelp', openHelp);
|
||||
electron.ipcRenderer.on('openSettings', openSettings);
|
||||
|
||||
return () => {
|
||||
electron.ipcRenderer.removeListener('file-opened', fileOpened);
|
||||
@ -1288,11 +1296,12 @@ const App = memo(() => {
|
||||
electron.ipcRenderer.removeListener('importEdlFile', importEdlFile);
|
||||
electron.ipcRenderer.removeListener('exportEdlFile', exportEdlFile);
|
||||
electron.ipcRenderer.removeListener('openHelp', openHelp);
|
||||
electron.ipcRenderer.removeListener('openSettings', openSettings);
|
||||
};
|
||||
}, [
|
||||
load, mergeFiles, outputDir, filePath, isFileOpened, customOutDir, startTimeOffset, getHtml5ifiedPath,
|
||||
createDummyVideo, resetState, extractAllStreams, userOpenFiles, cutSegmentsHistory,
|
||||
loadEdlFile, cutSegments, edlFilePath, askBeforeClose, toggleHelp,
|
||||
loadEdlFile, cutSegments, edlFilePath, askBeforeClose, toggleHelp, toggleSettings,
|
||||
]);
|
||||
|
||||
async function showAddStreamSourceDialog() {
|
||||
@ -1457,6 +1466,7 @@ const App = memo(() => {
|
||||
keyframeCut={keyframeCut}
|
||||
toggleKeyframeCut={toggleKeyframeCut}
|
||||
toggleHelp={toggleHelp}
|
||||
toggleSettings={toggleSettings}
|
||||
numStreamsToCopy={numStreamsToCopy}
|
||||
numStreamsTotal={numStreamsTotal}
|
||||
setStreamsSelectorShown={setStreamsSelectorShown}
|
||||
@ -1685,11 +1695,16 @@ const App = memo(() => {
|
||||
</motion.div>
|
||||
|
||||
<HelpSheet
|
||||
visible={!!helpVisible}
|
||||
visible={helpVisible}
|
||||
onTogglePress={toggleHelp}
|
||||
renderSettings={renderSettings}
|
||||
ffmpegCommandLog={ffmpegCommandLog}
|
||||
/>
|
||||
|
||||
<SettingsSheet
|
||||
visible={settingsVisible}
|
||||
onTogglePress={toggleSettings}
|
||||
renderSettings={renderSettings}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user