diff --git a/src/capture-frame.js b/src/capture-frame.js
index 69bd8c62..6aaaab5b 100644
--- a/src/capture-frame.js
+++ b/src/capture-frame.js
@@ -23,7 +23,7 @@ async function captureFrame(customOutDir, filePath, video, currentTime, captureF
const buf = getFrameFromVideo(video, captureFormat);
const ext = mime.extension(buf.mimetype);
- const time = util.formatDuration(currentTime);
+ const time = util.formatDuration(currentTime, true);
const outPath = util.getOutPath(customOutDir, filePath, `${time}.${ext}`);
await fs.writeFileAsync(outPath, buf);
diff --git a/src/ffmpeg.js b/src/ffmpeg.js
index 71809c4c..f7ccfadf 100644
--- a/src/ffmpeg.js
+++ b/src/ffmpeg.js
@@ -59,7 +59,7 @@ async function cut({
customOutDir, filePath, format, cutFrom, cutTo, videoDuration, rotation, onProgress,
}) {
const ext = path.extname(filePath) || `.${format}`;
- const cutSpecification = `${util.formatDuration(cutFrom)}-${util.formatDuration(cutTo)}`;
+ const cutSpecification = `${util.formatDuration(cutFrom, true)}-${util.formatDuration(cutTo, true)}`;
const outPath = util.getOutPath(customOutDir, filePath, `${cutSpecification}${ext}`);
diff --git a/src/main.css b/src/main.css
index 8d8b725c..67300610 100644
--- a/src/main.css
+++ b/src/main.css
@@ -36,17 +36,21 @@ input, button, textarea, :focus {
.button {
padding: .4em;
+ vertical-align: middle;
}
-.controls-wrapper button, .right-menu button, .left-menu button {
+.controls-wrapper button, .right-menu button, .left-menu button, .controls-wrapper input {
background: white;
border-radius: .3em;
color: rgba(0, 0, 0, 0.7);
- font-size: 60%;
+ font-size: 13px;
vertical-align: middle;
- padding: .2em .4em;
- margin: 0 .5em;
+ padding: 0 .5em;
+ margin: 0 3px;
border: none;
+ height: 18px;
+ box-sizing: border-box;
+ font-family: inherit;
}
.controls-wrapper button:active, .right-menu button:active, .left-menu button:active {
diff --git a/src/renderer.jsx b/src/renderer.jsx
index 4b7db354..6abddc85 100644
--- a/src/renderer.jsx
+++ b/src/renderer.jsx
@@ -62,8 +62,8 @@ function renderHelpSheet(visible) {
, (comma) Tiny seek backward (1/60 sec)
I Mark in / cut start point
O Mark out / cut end point
- E Export selection (in the same dir as the video)
- C Capture snapshot (in the same dir as the video)
+ E Cut (export selection in the same directory)
+ C Capture snapshot (in the same directory)
);
}
@@ -89,7 +89,9 @@ class App extends React.Component {
currentTime: undefined,
duration: undefined,
cutStartTime: 0,
+ cutStartTimeManual: undefined,
cutEndTime: undefined,
+ cutEndTimeManual: undefined,
fileFormat: undefined,
captureFormat: 'jpeg',
rotation: 360,
@@ -214,6 +216,14 @@ class App extends React.Component {
return this.state.rotation !== 360;
}
+ areCutTimesSet() {
+ return (this.state.cutStartTime !== undefined || this.state.cutEndTime !== undefined);
+ }
+
+ isCutRangeValid() {
+ return this.areCutTimesSet() && this.state.cutStartTime < this.state.cutEndTime;
+ }
+
increaseRotation() {
const rotation = (this.state.rotation + 90) % 450;
this.setState({ rotation });
@@ -278,10 +288,10 @@ class App extends React.Component {
const filePath = this.state.filePath;
const rotation = this.isRotationSet() ? this.getRotation() : undefined;
- if (cutStartTime === undefined || cutEndTime === undefined) {
+ if (!this.areCutTimesSet()) {
return alert('Please select both start and end time');
}
- if (cutStartTime >= cutEndTime) {
+ if (!this.isCutRangeValid()) {
return alert('Start time must be before end time');
}
@@ -328,7 +338,44 @@ class App extends React.Component {
this.setState({ helpVisible: !this.state.helpVisible });
}
+ renderCutTimeInput(type) {
+ const cutTimeKey = type === 'start' ? 'cutStartTime' : 'cutEndTime';
+ const cutTimeManualKey = type === 'start' ? 'cutStartTimeManual' : 'cutEndTimeManual';
+ const cutTimeInputStyle = Object.assign({}, { width: '8em', textAlign: type === 'start' ? 'right' : 'left' });
+
+ const isCutTimeManualSet = () => this.state[cutTimeManualKey] !== undefined;
+
+ const handleCutTimeInput = (text) => {
+ // Allow the user to erase
+ if (text.length === 0) {
+ this.setState({ [cutTimeManualKey]: undefined });
+ return;
+ }
+
+ const time = util.parseDuration(text);
+ if (time === undefined) {
+ this.setState({ [cutTimeManualKey]: text });
+ return;
+ }
+
+ this.setState({ [cutTimeManualKey]: undefined, [cutTimeKey]: time });
+ };
+
+
+ return ( handleCutTimeInput(e.target.value)}
+ value={isCutTimeManualSet()
+ ? this.state[cutTimeManualKey]
+ : util.formatDuration(this.state[cutTimeKey])
+ }
+ />);
+ }
+
render() {
+ const jumpCutButtonStyle = { position: 'absolute', color: 'black', bottom: 0, top: 0, padding: '2px 8px' };
+
return (
{!this.state.filePath &&
DROP VIDEO
}
{this.state.working && (
@@ -374,12 +421,25 @@ class App extends React.Component {
-
+
seekAbs(0)}
/>
+
+
+ {this.renderCutTimeInput('start')}
+ this.jumpCutStart())}
+ />
+
+
shortStep(1)}
/>
+
+
+ {this.renderCutTimeInput('end')}
+ this.jumpCutEnd())}
+ />
+
+
seekAbs(this.state.duration)}
/>
+
-
this.setCutStart()}
/>
this.cutClick()}
/>
this.setCutEnd()}
/>
-
-