mirror of
https://github.com/mifi/lossless-cut.git
synced 2024-11-22 02:12:30 +01:00
upgrade eslint and fix
This commit is contained in:
parent
ab4e3eb5c8
commit
68da79caf9
@ -1,12 +1,14 @@
|
||||
{
|
||||
"extends": "airbnb",
|
||||
"parser": "babel-eslint",
|
||||
"env": {
|
||||
"node": true,
|
||||
"browser": true,
|
||||
},
|
||||
"rules": {
|
||||
"no-alert": 0,
|
||||
"no-console": 0
|
||||
"no-console": 0,
|
||||
"react/destructuring-assignment": 0,
|
||||
},
|
||||
"plugins": [
|
||||
"react"
|
||||
|
11
package.json
11
package.json
@ -37,16 +37,17 @@
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"babel-cli": "^6.18.0",
|
||||
"babel-eslint": "^10.0.1",
|
||||
"babel-plugin-transform-class-properties": "^6.24.1",
|
||||
"babel-plugin-transform-object-rest-spread": "^6.26.0",
|
||||
"babel-preset-env": "^1.6.1",
|
||||
"babel-preset-react": "^6.16.0",
|
||||
"electron-packager": "^8.1.0",
|
||||
"eslint": "^3.8.0",
|
||||
"eslint-config-airbnb": "^12.0.0",
|
||||
"eslint-plugin-import": "^1.16.0",
|
||||
"eslint-plugin-jsx-a11y": "^2.2.3",
|
||||
"eslint-plugin-react": "^6.4.1",
|
||||
"eslint": "^5.6.1",
|
||||
"eslint-config-airbnb": "^17.1.0",
|
||||
"eslint-plugin-import": "^2.14.0",
|
||||
"eslint-plugin-jsx-a11y": "^6.1.1",
|
||||
"eslint-plugin-react": "^7.11.1",
|
||||
"gh-release": "^2.2.1",
|
||||
"icon-gen": "^1.2.0"
|
||||
},
|
||||
|
@ -5,8 +5,8 @@ const menu = require('./menu');
|
||||
|
||||
const { checkNewVersion } = require('./update-checker');
|
||||
|
||||
const app = electron.app;
|
||||
const BrowserWindow = electron.BrowserWindow;
|
||||
const { app } = electron;
|
||||
const { BrowserWindow } = electron;
|
||||
|
||||
app.setName('LosslessCut');
|
||||
|
||||
|
@ -1,8 +1,8 @@
|
||||
const electron = require('electron'); // eslint-disable-line
|
||||
const defaultMenu = require('electron-default-menu');
|
||||
|
||||
const Menu = electron.Menu;
|
||||
const dialog = electron.dialog;
|
||||
const { Menu } = electron;
|
||||
const { dialog } = electron;
|
||||
|
||||
const homepage = 'https://github.com/mifi/lossless-cut';
|
||||
const releasesPage = 'https://github.com/mifi/lossless-cut/releases';
|
||||
|
554
src/renderer.jsx
554
src/renderer.jsx
@ -14,7 +14,7 @@ const captureFrame = require('./capture-frame');
|
||||
const ffmpeg = require('./ffmpeg');
|
||||
const util = require('./util');
|
||||
|
||||
const dialog = electron.remote.dialog;
|
||||
const { dialog } = electron.remote;
|
||||
|
||||
function setFileNameTitle(filePath) {
|
||||
const appName = 'LosslessCut';
|
||||
@ -27,7 +27,7 @@ function getVideo() {
|
||||
|
||||
function seekAbs(val) {
|
||||
const video = getVideo();
|
||||
if (val == null || isNaN(val)) return;
|
||||
if (val == null || Number.isNaN(val)) return;
|
||||
|
||||
let outVal = val;
|
||||
if (outVal < 0) outVal = 0;
|
||||
@ -48,29 +48,34 @@ function shortStep(dir) {
|
||||
seekRel((1 / 60) * dir);
|
||||
}
|
||||
|
||||
/* eslint-disable react/jsx-one-expression-per-line */
|
||||
function renderHelpSheet(visible) {
|
||||
if (visible) {
|
||||
return (<div className="help-sheet">
|
||||
<h1>Keyboard shortcuts</h1>
|
||||
<ul>
|
||||
<li><kbd>H</kbd> Show/hide help</li>
|
||||
<li><kbd>SPACE</kbd>, <kbd>k</kbd> Play/pause</li>
|
||||
<li><kbd>J</kbd> Slow down video</li>
|
||||
<li><kbd>L</kbd> Speed up video</li>
|
||||
<li><kbd>←</kbd> Seek backward 1 sec</li>
|
||||
<li><kbd>→</kbd> Seek forward 1 sec</li>
|
||||
<li><kbd>.</kbd> (period) Tiny seek forward (1/60 sec)</li>
|
||||
<li><kbd>,</kbd> (comma) Tiny seek backward (1/60 sec)</li>
|
||||
<li><kbd>I</kbd> Mark in / cut start point</li>
|
||||
<li><kbd>O</kbd> Mark out / cut end point</li>
|
||||
<li><kbd>E</kbd> Cut (export selection in the same directory)</li>
|
||||
<li><kbd>C</kbd> Capture snapshot (in the same directory)</li>
|
||||
</ul>
|
||||
</div>);
|
||||
return (
|
||||
<div className="help-sheet">
|
||||
<h1>Keyboard shortcuts</h1>
|
||||
<ul>
|
||||
<li><kbd>H</kbd> Show/hide help</li>
|
||||
<li><kbd>SPACE</kbd>, <kbd>k</kbd> Play/pause</li>
|
||||
<li><kbd>J</kbd> Slow down video</li>
|
||||
<li><kbd>L</kbd> Speed up video</li>
|
||||
<li><kbd>←</kbd> Seek backward 1 sec</li>
|
||||
<li><kbd>→</kbd> Seek forward 1 sec</li>
|
||||
<li><kbd>.</kbd> (period) Tiny seek forward (1/60 sec)</li>
|
||||
<li><kbd>,</kbd> (comma) Tiny seek backward (1/60 sec)</li>
|
||||
<li><kbd>I</kbd> Mark in / cut start point</li>
|
||||
<li><kbd>O</kbd> Mark out / cut end point</li>
|
||||
<li><kbd>E</kbd> Cut (export selection in the same directory)</li>
|
||||
<li><kbd>C</kbd> Capture snapshot (in the same directory)</li>
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
/* eslint-enable react/jsx-one-expression-per-line */
|
||||
|
||||
|
||||
function withBlur(cb) {
|
||||
return (e) => {
|
||||
@ -114,8 +119,10 @@ class App extends React.Component {
|
||||
};
|
||||
|
||||
const load = (filePath, html5FriendlyPath) => {
|
||||
const { working } = this.state;
|
||||
|
||||
console.log('Load', { filePath, html5FriendlyPath });
|
||||
if (this.state.working) return alert('I\'m busy');
|
||||
if (working) return alert('I\'m busy');
|
||||
|
||||
this.resetState();
|
||||
|
||||
@ -159,7 +166,8 @@ class App extends React.Component {
|
||||
}
|
||||
});
|
||||
|
||||
document.ondragover = document.ondragend = ev => ev.preventDefault();
|
||||
document.ondragover = ev => ev.preventDefault();
|
||||
document.ondragend = document.ondragover;
|
||||
|
||||
document.body.ondrop = (ev) => {
|
||||
ev.preventDefault();
|
||||
@ -196,31 +204,33 @@ class App extends React.Component {
|
||||
this.setState({ duration });
|
||||
}
|
||||
|
||||
onCutProgress(cutProgress) {
|
||||
onCutProgress = (cutProgress) => {
|
||||
this.setState({ cutProgress });
|
||||
}
|
||||
|
||||
setCutStart() {
|
||||
this.setState({ cutStartTime: this.state.currentTime });
|
||||
setCutStart = () => {
|
||||
this.setState(({ currentTime }) => ({ cutStartTime: currentTime }));
|
||||
}
|
||||
|
||||
setCutEnd() {
|
||||
this.setState({ cutEndTime: this.state.currentTime });
|
||||
setCutEnd = () => {
|
||||
this.setState(({ currentTime }) => ({ cutEndTime: currentTime }));
|
||||
}
|
||||
|
||||
setOutputDir() {
|
||||
setOutputDir = () => {
|
||||
dialog.showOpenDialog({ properties: ['openDirectory'] }, (paths) => {
|
||||
this.setState({ customOutDir: (paths && paths.length === 1) ? paths[0] : undefined });
|
||||
});
|
||||
}
|
||||
|
||||
getFileUri() {
|
||||
return (this.state.html5FriendlyPath || this.state.filePath || '').replace(/#/g, '%23');
|
||||
const { html5FriendlyPath, filePath } = this.state;
|
||||
return (html5FriendlyPath || filePath || '').replace(/#/g, '%23');
|
||||
}
|
||||
|
||||
getOutputDir() {
|
||||
if (this.state.customOutDir) return this.state.customOutDir;
|
||||
if (this.state.filePath) return path.dirname(this.state.filePath);
|
||||
const { customOutDir, filePath } = this.state;
|
||||
if (customOutDir) return customOutDir;
|
||||
if (filePath) return path.dirname(filePath);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
@ -238,72 +248,48 @@ class App extends React.Component {
|
||||
return 0; // Haven't gotten duration yet
|
||||
}
|
||||
|
||||
isRotationSet() {
|
||||
// 360 means we don't modify rotation
|
||||
return this.state.rotation !== 360;
|
||||
|
||||
increaseRotation = () => {
|
||||
this.setState(({ rotation }) => ({ rotation: (rotation + 90) % 450 }));
|
||||
}
|
||||
|
||||
isCutRangeValid() {
|
||||
return this.state.cutStartTime < this.getApparentCutEndTime();
|
||||
}
|
||||
|
||||
increaseRotation() {
|
||||
const rotation = (this.state.rotation + 90) % 450;
|
||||
this.setState({ rotation });
|
||||
}
|
||||
|
||||
resetState() {
|
||||
const video = getVideo();
|
||||
video.currentTime = 0;
|
||||
video.playbackRate = 1;
|
||||
this.setState(localState);
|
||||
setFileNameTitle();
|
||||
}
|
||||
|
||||
toggleCaptureFormat() {
|
||||
toggleCaptureFormat = () => {
|
||||
const isPng = this.state.captureFormat === 'png';
|
||||
this.setState({ captureFormat: isPng ? 'jpeg' : 'png' });
|
||||
}
|
||||
|
||||
toggleIncludeAllStreams() {
|
||||
this.setState({ includeAllStreams: !this.state.includeAllStreams });
|
||||
toggleIncludeAllStreams = () => {
|
||||
this.setState(({ includeAllStreams }) => ({ includeAllStreams: !includeAllStreams }));
|
||||
}
|
||||
|
||||
jumpCutStart() {
|
||||
toggleStripAudio = () => this.setState(({ stripAudio }) => ({ stripAudio: !stripAudio }));
|
||||
|
||||
toggleKeyframeCut = () => this.setState(({ keyframeCut }) => ({ keyframeCut: !keyframeCut }));
|
||||
|
||||
jumpCutStart = () => {
|
||||
seekAbs(this.state.cutStartTime);
|
||||
}
|
||||
|
||||
jumpCutEnd() {
|
||||
jumpCutEnd = () => {
|
||||
seekAbs(this.getApparentCutEndTime());
|
||||
}
|
||||
|
||||
handlePan(e) {
|
||||
handlePan = (e) => {
|
||||
_.throttle(e2 => this.handleTap(e2), 200)(e);
|
||||
}
|
||||
|
||||
handleTap(e) {
|
||||
handleTap = (e) => {
|
||||
const $target = $('.timeline-wrapper');
|
||||
const parentOffset = $target.offset();
|
||||
const relX = e.srcEvent.pageX - parentOffset.left;
|
||||
setCursor((relX / $target[0].offsetWidth) * this.state.duration);
|
||||
}
|
||||
|
||||
changePlaybackRate(dir) {
|
||||
const video = getVideo();
|
||||
if (!this.state.playing) {
|
||||
video.playbackRate = 0.5; // dir * 0.5;
|
||||
video.play();
|
||||
} else {
|
||||
const newRate = video.playbackRate + (dir * 0.15);
|
||||
video.playbackRate = _.clamp(newRate, 0.05, 16);
|
||||
}
|
||||
}
|
||||
|
||||
playbackRateChange() {
|
||||
playbackRateChange = () => {
|
||||
this.state.playbackRate = getVideo().playbackRate;
|
||||
}
|
||||
|
||||
playCommand() {
|
||||
playCommand = () => {
|
||||
const video = getVideo();
|
||||
if (this.state.playing) return video.pause();
|
||||
|
||||
@ -315,8 +301,8 @@ class App extends React.Component {
|
||||
});
|
||||
}
|
||||
|
||||
async deleteSourceClick() {
|
||||
if (this.state.working || !confirm('Are you sure you want to move the source file to trash?')) return;
|
||||
deleteSourceClick = async () => {
|
||||
if (this.state.working || !window.confirm('Are you sure you want to move the source file to trash?')) return;
|
||||
const { filePath } = this.state;
|
||||
|
||||
this.setState({ working: true });
|
||||
@ -324,7 +310,7 @@ class App extends React.Component {
|
||||
this.resetState();
|
||||
}
|
||||
|
||||
async cutClick() {
|
||||
cutClick = async () => {
|
||||
if (this.state.working) return alert('I\'m busy');
|
||||
|
||||
const {
|
||||
@ -352,7 +338,7 @@ class App extends React.Component {
|
||||
includeAllStreams,
|
||||
stripAudio,
|
||||
keyframeCut,
|
||||
onProgress: progress => this.onCutProgress(progress),
|
||||
onProgress: this.onCutProgress,
|
||||
});
|
||||
} catch (err) {
|
||||
console.error('stdout:', err.stdout);
|
||||
@ -367,18 +353,45 @@ class App extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
capture() {
|
||||
const filePath = this.state.filePath;
|
||||
const outputDir = this.state.customOutDir;
|
||||
const currentTime = this.state.currentTime;
|
||||
const captureFormat = this.state.captureFormat;
|
||||
capture = () => {
|
||||
const {
|
||||
filePath, customOutDir: outputDir, currentTime, captureFormat,
|
||||
} = this.state;
|
||||
if (!filePath) return;
|
||||
captureFrame(outputDir, filePath, getVideo(), currentTime, captureFormat)
|
||||
.catch(err => alert(err));
|
||||
}
|
||||
|
||||
changePlaybackRate(dir) {
|
||||
const video = getVideo();
|
||||
if (!this.state.playing) {
|
||||
video.playbackRate = 0.5; // dir * 0.5;
|
||||
video.play();
|
||||
} else {
|
||||
const newRate = video.playbackRate + (dir * 0.15);
|
||||
video.playbackRate = _.clamp(newRate, 0.05, 16);
|
||||
}
|
||||
}
|
||||
|
||||
resetState() {
|
||||
const video = getVideo();
|
||||
video.currentTime = 0;
|
||||
video.playbackRate = 1;
|
||||
this.setState(localState);
|
||||
setFileNameTitle();
|
||||
}
|
||||
|
||||
isRotationSet() {
|
||||
// 360 means we don't modify rotation
|
||||
return this.state.rotation !== 360;
|
||||
}
|
||||
|
||||
isCutRangeValid() {
|
||||
return this.state.cutStartTime < this.getApparentCutEndTime();
|
||||
}
|
||||
|
||||
toggleHelp() {
|
||||
this.setState({ helpVisible: !this.state.helpVisible });
|
||||
this.setState(({ helpVisible }) => ({ helpVisible: !helpVisible }));
|
||||
}
|
||||
|
||||
renderCutTimeInput(type) {
|
||||
@ -404,58 +417,67 @@ class App extends React.Component {
|
||||
};
|
||||
|
||||
|
||||
return (<input
|
||||
style={{ ...cutTimeInputStyle, color: isCutTimeManualSet() ? '#dc1d1d' : undefined }}
|
||||
type="text"
|
||||
onChange={e => handleCutTimeInput(e.target.value)}
|
||||
value={isCutTimeManualSet()
|
||||
? this.state[cutTimeManualKey]
|
||||
: util.formatDuration(type === 'start' ? this.state.cutStartTime : this.getApparentCutEndTime())
|
||||
return (
|
||||
<input
|
||||
style={{ ...cutTimeInputStyle, color: isCutTimeManualSet() ? '#dc1d1d' : undefined }}
|
||||
type="text"
|
||||
onChange={e => handleCutTimeInput(e.target.value)}
|
||||
value={isCutTimeManualSet()
|
||||
? this.state[cutTimeManualKey]
|
||||
: util.formatDuration(type === 'start' ? this.state.cutStartTime : this.getApparentCutEndTime())
|
||||
}
|
||||
/>);
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const jumpCutButtonStyle = { position: 'absolute', color: 'black', bottom: 0, top: 0, padding: '2px 8px' };
|
||||
const infoSpanStyle = { background: 'rgba(255, 255, 255, 0.4)', padding: '.1em .4em', margin: '0 3px', fontSize: 13, borderRadius: '.3em' };
|
||||
const jumpCutButtonStyle = {
|
||||
position: 'absolute', color: 'black', bottom: 0, top: 0, padding: '2px 8px',
|
||||
};
|
||||
const infoSpanStyle = {
|
||||
background: 'rgba(255, 255, 255, 0.4)', padding: '.1em .4em', margin: '0 3px', fontSize: 13, borderRadius: '.3em',
|
||||
};
|
||||
|
||||
return (<div>
|
||||
{!this.state.filePath && <div id="drag-drop-field">DROP VIDEO</div>}
|
||||
{this.state.working && (
|
||||
<div style={{ color: 'white', background: 'rgba(0, 0, 0, 0.3)', borderRadius: '.5em', margin: '1em', padding: '.2em .5em', position: 'absolute', zIndex: 1, top: 0, left: 0 }}>
|
||||
<i className="fa fa-cog fa-spin fa-3x fa-fw" style={{ verticalAlign: 'middle', width: '1em', height: '1em' }} />
|
||||
{this.state.cutProgress != null &&
|
||||
<span style={{ color: 'rgba(255, 255, 255, 0.7)', paddingLeft: '.4em' }}>
|
||||
{Math.floor(this.state.cutProgress * 100)} %
|
||||
</span>
|
||||
}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div id="player">
|
||||
<video
|
||||
src={this.getFileUri()}
|
||||
onRateChange={() => this.playbackRateChange()}
|
||||
onPlay={() => this.onPlay(true)}
|
||||
onPause={() => this.onPlay(false)}
|
||||
onDurationChange={e => this.onDurationChange(e.target.duration)}
|
||||
onTimeUpdate={e => this.setState({ currentTime: e.target.currentTime })}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="controls-wrapper">
|
||||
<Hammer
|
||||
onTap={e => this.handleTap(e)}
|
||||
onPan={e => this.handlePan(e)}
|
||||
options={{
|
||||
recognizers: {
|
||||
},
|
||||
}}
|
||||
return (
|
||||
<div>
|
||||
{!this.state.filePath && <div id="drag-drop-field">DROP VIDEO</div>}
|
||||
{this.state.working && (
|
||||
<div style={{
|
||||
color: 'white', background: 'rgba(0, 0, 0, 0.3)', borderRadius: '.5em', margin: '1em', padding: '.2em .5em', position: 'absolute', zIndex: 1, top: 0, left: 0,
|
||||
}}
|
||||
>
|
||||
<div className="timeline-wrapper">
|
||||
<div className="current-time" style={{ left: `${((this.state.currentTime || 0) / (this.state.duration || 1)) * 100}%` }} />
|
||||
<i className="fa fa-cog fa-spin fa-3x fa-fw" style={{ verticalAlign: 'middle', width: '1em', height: '1em' }} />
|
||||
{this.state.cutProgress != null && (
|
||||
<span style={{ color: 'rgba(255, 255, 255, 0.7)', paddingLeft: '.4em' }}>
|
||||
{`${Math.floor(this.state.cutProgress * 100)} %`}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{this.isCutRangeValid() &&
|
||||
{/* eslint-disable jsx-a11y/media-has-caption */}
|
||||
<div id="player">
|
||||
<video
|
||||
src={this.getFileUri()}
|
||||
onRateChange={this.playbackRateChange}
|
||||
onPlay={() => this.onPlay(true)}
|
||||
onPause={() => this.onPlay(false)}
|
||||
onDurationChange={e => this.onDurationChange(e.target.duration)}
|
||||
onTimeUpdate={e => this.setState({ currentTime: e.target.currentTime })}
|
||||
/>
|
||||
</div>
|
||||
{/* eslint-enable jsx-a11y/media-has-caption */}
|
||||
|
||||
<div className="controls-wrapper">
|
||||
<Hammer
|
||||
onTap={this.handleTap}
|
||||
onPan={this.handlePan}
|
||||
options={{ recognizers: {} }}
|
||||
>
|
||||
<div className="timeline-wrapper">
|
||||
<div className="current-time" style={{ left: `${((this.state.currentTime || 0) / (this.state.duration || 1)) * 100}%` }} />
|
||||
|
||||
{this.isCutRangeValid() && (
|
||||
<div
|
||||
className="cut-start-time"
|
||||
style={{
|
||||
@ -463,158 +485,168 @@ class App extends React.Component {
|
||||
width: `${(((this.getApparentCutEndTime()) - this.state.cutStartTime) / (this.state.duration || 1)) * 100}%`,
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
<div id="current-time-display">{util.formatDuration(this.state.currentTime)}</div>
|
||||
</div>
|
||||
</Hammer>
|
||||
<div id="current-time-display">{util.formatDuration(this.state.currentTime)}</div>
|
||||
</div>
|
||||
</Hammer>
|
||||
|
||||
<div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center' }}>
|
||||
<i
|
||||
className="button fa fa-step-backward"
|
||||
aria-hidden="true"
|
||||
title="Jump to start of video"
|
||||
onClick={() => seekAbs(0)}
|
||||
/>
|
||||
|
||||
<div style={{ position: 'relative' }}>
|
||||
{this.renderCutTimeInput('start')}
|
||||
<div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center' }}>
|
||||
<i
|
||||
style={{ ...jumpCutButtonStyle, left: 0 }}
|
||||
className="fa fa-step-backward"
|
||||
title="Jump to cut start"
|
||||
className="button fa fa-step-backward"
|
||||
aria-hidden="true"
|
||||
onClick={withBlur(() => this.jumpCutStart())}
|
||||
title="Jump to start of video"
|
||||
onClick={() => seekAbs(0)}
|
||||
/>
|
||||
|
||||
<div style={{ position: 'relative' }}>
|
||||
{this.renderCutTimeInput('start')}
|
||||
<i
|
||||
style={{ ...jumpCutButtonStyle, left: 0 }}
|
||||
className="fa fa-step-backward"
|
||||
title="Jump to cut start"
|
||||
aria-hidden="true"
|
||||
onClick={withBlur(this.jumpCutStart)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<i
|
||||
className="button fa fa-caret-left"
|
||||
aria-hidden="true"
|
||||
onClick={() => shortStep(-1)}
|
||||
/>
|
||||
<i
|
||||
className={classnames({
|
||||
button: true, fa: true, 'fa-pause': this.state.playing, 'fa-play': !this.state.playing,
|
||||
})}
|
||||
aria-hidden="true"
|
||||
onClick={this.playCommand}
|
||||
/>
|
||||
<i
|
||||
className="button fa fa-caret-right"
|
||||
aria-hidden="true"
|
||||
onClick={() => shortStep(1)}
|
||||
/>
|
||||
|
||||
<div style={{ position: 'relative' }}>
|
||||
{this.renderCutTimeInput('end')}
|
||||
<i
|
||||
style={{ ...jumpCutButtonStyle, right: 0 }}
|
||||
className="fa fa-step-forward"
|
||||
title="Jump to cut end"
|
||||
aria-hidden="true"
|
||||
onClick={withBlur(this.jumpCutEnd)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<i
|
||||
className="button fa fa-step-forward"
|
||||
aria-hidden="true"
|
||||
title="Jump to end of video"
|
||||
onClick={() => seekAbs(this.state.duration)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<i
|
||||
className="button fa fa-caret-left"
|
||||
aria-hidden="true"
|
||||
onClick={() => shortStep(-1)}
|
||||
/>
|
||||
<i
|
||||
className={classnames({ button: true, fa: true, 'fa-pause': this.state.playing, 'fa-play': !this.state.playing })}
|
||||
aria-hidden="true"
|
||||
onClick={() => this.playCommand()}
|
||||
/>
|
||||
<i
|
||||
className="button fa fa-caret-right"
|
||||
aria-hidden="true"
|
||||
onClick={() => shortStep(1)}
|
||||
/>
|
||||
|
||||
<div style={{ position: 'relative' }}>
|
||||
{this.renderCutTimeInput('end')}
|
||||
<div>
|
||||
<i
|
||||
style={{ ...jumpCutButtonStyle, right: 0 }}
|
||||
className="fa fa-step-forward"
|
||||
title="Jump to cut end"
|
||||
title="Set cut start to current position"
|
||||
className="button fa fa-angle-left"
|
||||
aria-hidden="true"
|
||||
onClick={withBlur(() => this.jumpCutEnd())}
|
||||
onClick={this.setCutStart}
|
||||
/>
|
||||
<i
|
||||
title="Cut"
|
||||
className="button fa fa-scissors"
|
||||
aria-hidden="true"
|
||||
onClick={this.cutClick}
|
||||
/>
|
||||
<i
|
||||
title="Delete source file"
|
||||
className="button fa fa-trash"
|
||||
aria-hidden="true"
|
||||
onClick={this.deleteSourceClick}
|
||||
/>
|
||||
<i
|
||||
title="Set cut end to current position"
|
||||
className="button fa fa-angle-right"
|
||||
aria-hidden="true"
|
||||
onClick={this.setCutEnd}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<i
|
||||
className="button fa fa-step-forward"
|
||||
aria-hidden="true"
|
||||
title="Jump to end of video"
|
||||
onClick={() => seekAbs(this.state.duration)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<i
|
||||
title="Set cut start to current position"
|
||||
className="button fa fa-angle-left"
|
||||
aria-hidden="true"
|
||||
onClick={() => this.setCutStart()}
|
||||
/>
|
||||
<i
|
||||
title="Cut"
|
||||
className="button fa fa-scissors"
|
||||
aria-hidden="true"
|
||||
onClick={() => this.cutClick()}
|
||||
/>
|
||||
<i
|
||||
title="Delete source file"
|
||||
className="button fa fa-trash"
|
||||
aria-hidden="true"
|
||||
onClick={() => this.deleteSourceClick()}
|
||||
/>
|
||||
<i
|
||||
title="Set cut end to current position"
|
||||
className="button fa fa-angle-right"
|
||||
aria-hidden="true"
|
||||
onClick={() => this.setCutEnd()}
|
||||
/>
|
||||
<div className="left-menu">
|
||||
<span style={infoSpanStyle} title="Format of current file">
|
||||
{this.state.fileFormat || 'FMT'}
|
||||
</span>
|
||||
|
||||
<span style={infoSpanStyle} title="Playback rate">
|
||||
{_.round(this.state.playbackRate, 1) || 1}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="right-menu">
|
||||
<button
|
||||
type="button"
|
||||
title={`Cut mode ${this.state.keyframeCut ? 'nearest keyframe cut' : 'normal cut'}`}
|
||||
onClick={withBlur(this.toggleKeyframeCut)}
|
||||
>
|
||||
{this.state.keyframeCut ? 'kc' : 'nc'}
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
title={`Set output streams. Current: ${this.state.includeAllStreams ? 'include (and cut) all streams' : 'include only primary streams'}`}
|
||||
onClick={withBlur(this.toggleIncludeAllStreams)}
|
||||
>
|
||||
{this.state.includeAllStreams ? 'all' : 'ps'}
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
title={`Delete audio? Current: ${this.state.stripAudio ? 'delete audio tracks' : 'keep audio tracks'}`}
|
||||
onClick={withBlur(this.toggleStripAudio)}
|
||||
>
|
||||
{this.state.stripAudio ? 'da' : 'ka'}
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
title={`Set output rotation. Current: ${this.isRotationSet() ? this.getRotationStr() : 'Don\'t modify'}`}
|
||||
onClick={withBlur(this.increaseRotation)}
|
||||
>
|
||||
{this.isRotationSet() ? this.getRotationStr() : '-°'}
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
title={`Custom output dir (cancel to restore default). Current: ${this.getOutputDir() || 'Not set (use input dir)'}`}
|
||||
onClick={withBlur(this.setOutputDir)}
|
||||
>
|
||||
{this.getOutputDir() ? 'cd' : 'id'}
|
||||
</button>
|
||||
|
||||
<i
|
||||
title="Capture frame"
|
||||
style={{ margin: '-.4em -.2em' }}
|
||||
className="button fa fa-camera"
|
||||
aria-hidden="true"
|
||||
onClick={this.capture}
|
||||
/>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
title="Capture frame format"
|
||||
onClick={withBlur(this.toggleCaptureFormat)}
|
||||
>
|
||||
{this.state.captureFormat}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{renderHelpSheet(this.state.helpVisible)}
|
||||
</div>
|
||||
|
||||
<div className="left-menu">
|
||||
<span style={infoSpanStyle} title="Format of current file">
|
||||
{this.state.fileFormat || 'FMT'}
|
||||
</span>
|
||||
|
||||
<span style={infoSpanStyle} title="Playback rate">
|
||||
{_.round(this.state.playbackRate, 1) || 1}x
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="right-menu">
|
||||
<button
|
||||
title={`Cut mode ${this.state.keyframeCut ? 'nearest keyframe cut' : 'normal cut'}`}
|
||||
onClick={withBlur(() => this.setState({ keyframeCut: !this.state.keyframeCut }))}
|
||||
>
|
||||
{this.state.keyframeCut ? 'kc' : 'nc'}
|
||||
</button>
|
||||
|
||||
<button
|
||||
title={`Set output streams. Current: ${this.state.includeAllStreams ? 'include (and cut) all streams' : 'include only primary streams'}`}
|
||||
onClick={withBlur(() => this.toggleIncludeAllStreams())}
|
||||
>
|
||||
{this.state.includeAllStreams ? 'all' : 'ps'}
|
||||
</button>
|
||||
|
||||
<button
|
||||
title={`Delete audio? Current: ${this.state.stripAudio ? 'delete audio tracks' : 'keep audio tracks'}`}
|
||||
onClick={withBlur(() => this.setState({ stripAudio: !this.state.stripAudio }))}
|
||||
>
|
||||
{this.state.stripAudio ? 'da' : 'ka'}
|
||||
</button>
|
||||
|
||||
<button
|
||||
title={`Set output rotation. Current: ${this.isRotationSet() ? this.getRotationStr() : 'Don\'t modify'}`}
|
||||
onClick={withBlur(() => this.increaseRotation())}
|
||||
>
|
||||
{this.isRotationSet() ? this.getRotationStr() : '-°'}
|
||||
</button>
|
||||
|
||||
<button
|
||||
title={`Custom output dir (cancel to restore default). Current: ${this.getOutputDir() || 'Not set (use input dir)'}`}
|
||||
onClick={withBlur(() => this.setOutputDir())}
|
||||
>
|
||||
{this.getOutputDir() ? 'cd' : 'id'}
|
||||
</button>
|
||||
|
||||
<i
|
||||
title="Capture frame"
|
||||
style={{ margin: '-.4em -.2em' }}
|
||||
className="button fa fa-camera"
|
||||
aria-hidden="true"
|
||||
onClick={() => this.capture()}
|
||||
/>
|
||||
|
||||
<button
|
||||
title="Capture frame format"
|
||||
onClick={withBlur(() => this.toggleCaptureFormat())}
|
||||
>
|
||||
{this.state.captureFormat}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{renderHelpSheet(this.state.helpVisible)}
|
||||
</div>);
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
const GitHub = require('github-api');
|
||||
const electron = require('electron');
|
||||
|
||||
const app = electron.app;
|
||||
const { app } = electron;
|
||||
|
||||
const gh = new GitHub();
|
||||
const repo = gh.getRepo('mifi', 'lossless-cut');
|
||||
|
@ -33,9 +33,9 @@ function parseDuration(str) {
|
||||
function getOutPath(customOutDir, filePath, nameSuffix) {
|
||||
const basename = path.basename(filePath);
|
||||
|
||||
return customOutDir ?
|
||||
path.join(customOutDir, `${basename}-${nameSuffix}`) :
|
||||
`${filePath}-${nameSuffix}`;
|
||||
return customOutDir
|
||||
? path.join(customOutDir, `${basename}-${nameSuffix}`)
|
||||
: `${filePath}-${nameSuffix}`;
|
||||
}
|
||||
|
||||
async function transferTimestamps(inPath, outPath) {
|
||||
|
Loading…
Reference in New Issue
Block a user