mirror of
https://github.com/mifi/lossless-cut.git
synced 2024-11-21 18:02:35 +01:00
improve cut section rendering 💪
This commit is contained in:
parent
d401ec888b
commit
89cc588714
25
src/main.css
25
src/main.css
@ -84,7 +84,7 @@ input, button, textarea, :focus {
|
||||
|
||||
#current-time-display {
|
||||
text-align: center;
|
||||
color: rgba(255, 255, 255, 0.3);
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
padding: .5em;
|
||||
}
|
||||
|
||||
@ -94,22 +94,25 @@ input, button, textarea, :focus {
|
||||
background-color: #444;
|
||||
}
|
||||
|
||||
.timeline-wrapper .current-time, .timeline-wrapper .cut-start-time {
|
||||
.timeline-wrapper .current-time, .timeline-wrapper .cut-section, .timeline-wrapper .cut-time-marker {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
top: 0;
|
||||
width: 1px;
|
||||
}
|
||||
|
||||
.timeline-wrapper .current-time {
|
||||
background-color: red;
|
||||
z-index: 2;
|
||||
}
|
||||
.timeline-wrapper .cut-start-time {
|
||||
background-color: rgba(0, 0, 0, 0.3);
|
||||
border-left: 1px solid black;
|
||||
border-right: 1px solid black;
|
||||
.timeline-wrapper .cut-section {
|
||||
z-index: 1;
|
||||
background-color: rgba(0, 255, 149, 0.5);
|
||||
}
|
||||
.timeline-wrapper .cut-time-marker {
|
||||
z-index: 2;
|
||||
box-sizing: border-box;
|
||||
background: rgba(0, 255, 149, 0.5);
|
||||
}
|
||||
.timeline-wrapper .current-time {
|
||||
z-index: 3;
|
||||
background-color: rgba(255, 255, 255, 1);
|
||||
width: 1px;
|
||||
}
|
||||
|
||||
#drag-drop-field {
|
||||
|
113
src/renderer.jsx
113
src/renderer.jsx
@ -68,7 +68,7 @@ const localState = {
|
||||
playing: false,
|
||||
currentTime: undefined,
|
||||
duration: undefined,
|
||||
cutStartTime: 0,
|
||||
cutStartTime: undefined,
|
||||
cutStartTimeManual: undefined,
|
||||
cutEndTime: undefined,
|
||||
cutEndTimeManual: undefined,
|
||||
@ -274,6 +274,11 @@ class App extends React.Component {
|
||||
return `${this.getRotation()}°`;
|
||||
}
|
||||
|
||||
getApparentCutStartTime() {
|
||||
if (this.state.cutStartTime !== undefined) return this.state.cutStartTime;
|
||||
return 0;
|
||||
}
|
||||
|
||||
getApparentCutEndTime() {
|
||||
if (this.state.cutEndTime !== undefined) return this.state.cutEndTime;
|
||||
if (this.state.duration !== undefined) return this.state.duration;
|
||||
@ -302,7 +307,7 @@ class App extends React.Component {
|
||||
toggleKeyframeCut = () => this.setState(({ keyframeCut }) => ({ keyframeCut: !keyframeCut }));
|
||||
|
||||
jumpCutStart = () => {
|
||||
seekAbs(this.state.cutStartTime);
|
||||
seekAbs(this.getApparentCutStartTime());
|
||||
}
|
||||
|
||||
jumpCutEnd = () => {
|
||||
@ -314,7 +319,7 @@ class App extends React.Component {
|
||||
const $target = $('.timeline-wrapper');
|
||||
const parentOffset = $target.offset();
|
||||
const relX = e.srcEvent.pageX - parentOffset.left;
|
||||
setCursor((relX / $target[0].offsetWidth) * this.state.duration);
|
||||
setCursor((relX / $target[0].offsetWidth) * (this.state.duration || 0));
|
||||
}, 200);
|
||||
/* eslint-enable react/sort-comp */
|
||||
|
||||
@ -351,13 +356,13 @@ class App extends React.Component {
|
||||
}
|
||||
|
||||
const {
|
||||
cutStartTime, cutEndTime, filePath, customOutDir, fileFormat, duration, includeAllStreams,
|
||||
cutEndTime, cutStartTime, filePath, customOutDir, fileFormat, duration, includeAllStreams,
|
||||
stripAudio, keyframeCut,
|
||||
} = this.state;
|
||||
|
||||
const rotation = this.isRotationSet() ? this.getRotation() : undefined;
|
||||
|
||||
if (!this.isCutRangeValid()) {
|
||||
if (!(this.isCutRangeValid() || cutEndTime === undefined || cutStartTime === undefined)) {
|
||||
errorToast('Start time must be before end time');
|
||||
return;
|
||||
}
|
||||
@ -368,7 +373,7 @@ class App extends React.Component {
|
||||
customOutDir,
|
||||
filePath,
|
||||
format: fileFormat,
|
||||
cutFrom: cutStartTime,
|
||||
cutFrom: this.getApparentCutStartTime(),
|
||||
cutTo: cutEndTime,
|
||||
cutToApparent: this.getApparentCutEndTime(),
|
||||
videoDuration: duration,
|
||||
@ -430,7 +435,7 @@ class App extends React.Component {
|
||||
}
|
||||
|
||||
isCutRangeValid() {
|
||||
return this.state.cutStartTime < this.getApparentCutEndTime();
|
||||
return this.getApparentCutStartTime() < this.getApparentCutEndTime();
|
||||
}
|
||||
|
||||
toggleHelp() {
|
||||
@ -463,7 +468,7 @@ class App extends React.Component {
|
||||
}));
|
||||
};
|
||||
|
||||
const cutTime = type === 'start' ? this.state.cutStartTime : this.getApparentCutEndTime();
|
||||
const cutTime = type === 'start' ? this.getApparentCutStartTime() : this.getApparentCutEndTime();
|
||||
|
||||
return (
|
||||
<input
|
||||
@ -486,23 +491,59 @@ class App extends React.Component {
|
||||
background: 'rgba(255, 255, 255, 0.4)', padding: '.1em .4em', margin: '0 3px', fontSize: 13, borderRadius: '.3em',
|
||||
};
|
||||
|
||||
const {
|
||||
working, filePath, duration: durationRaw, cutProgress, currentTime, playing,
|
||||
fileFormat, playbackRate, keyframeCut, includeAllStreams, stripAudio, captureFormat,
|
||||
helpVisible, cutStartTime, cutEndTime,
|
||||
} = this.state;
|
||||
|
||||
const markerWidth = 4;
|
||||
const apparentCutStart = this.getApparentCutStartTime();
|
||||
const apprentCutEnd = this.getApparentCutEndTime();
|
||||
const duration = durationRaw || 1;
|
||||
const currentTimePos = currentTime !== undefined && `${(currentTime / duration) * 100}%`;
|
||||
const cutSectionWidth = `calc(${((apprentCutEnd - apparentCutStart) / duration) * 100}% - ${markerWidth * 2}px)`;
|
||||
|
||||
const isCutRangeValid = this.isCutRangeValid();
|
||||
|
||||
const startTimePos = `${(apparentCutStart / duration) * 100}%`;
|
||||
const endTimePos = `${(apprentCutEnd / duration) * 100}%`;
|
||||
const markerBorder = '2px solid rgb(0, 255, 149)';
|
||||
const markerBorderRadius = 5;
|
||||
|
||||
const startMarkerStyle = {
|
||||
width: markerWidth,
|
||||
left: startTimePos,
|
||||
borderLeft: markerBorder,
|
||||
borderTopLeftRadius: markerBorderRadius,
|
||||
borderBottomLeftRadius: markerBorderRadius,
|
||||
};
|
||||
const endMarkerStyle = {
|
||||
width: markerWidth,
|
||||
marginLeft: -markerWidth,
|
||||
left: endTimePos,
|
||||
borderRight: markerBorder,
|
||||
borderTopRightRadius: markerBorderRadius,
|
||||
borderBottomRightRadius: markerBorderRadius,
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
{!this.state.filePath && (
|
||||
{!filePath && (
|
||||
<div id="drag-drop-field">
|
||||
<div style={{ fontSize: '9vw' }}>DROP VIDEO</div>
|
||||
<div>PRESS H FOR HELP</div>
|
||||
</div>
|
||||
)}
|
||||
{this.state.working && (
|
||||
{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 && (
|
||||
{cutProgress != null && (
|
||||
<span style={{ color: 'rgba(255, 255, 255, 0.7)', paddingLeft: '.4em' }}>
|
||||
{`${Math.floor(this.state.cutProgress * 100)} %`}
|
||||
{`${Math.floor(cutProgress * 100)} %`}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
@ -528,18 +569,20 @@ class App extends React.Component {
|
||||
options={{ recognizers: {} }}
|
||||
>
|
||||
<div className="timeline-wrapper">
|
||||
<div className="current-time" style={{ left: `${((this.state.currentTime || 0) / (this.state.duration || 1)) * 100}%` }} />
|
||||
{currentTimePos !== undefined && <div className="current-time" style={{ left: currentTimePos }} />}
|
||||
|
||||
{this.isCutRangeValid() && (
|
||||
<div
|
||||
className="cut-start-time"
|
||||
style={{
|
||||
left: `${((this.state.cutStartTime) / (this.state.duration || 1)) * 100}%`,
|
||||
width: `${(((this.getApparentCutEndTime()) - this.state.cutStartTime) / (this.state.duration || 1)) * 100}%`,
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
{cutStartTime !== undefined && <div style={startMarkerStyle} className="cut-time-marker" />}
|
||||
{isCutRangeValid && (cutStartTime !== undefined || cutEndTime !== undefined) && (
|
||||
<div
|
||||
className="cut-section"
|
||||
style={{
|
||||
marginLeft: markerWidth,
|
||||
left: startTimePos,
|
||||
width: cutSectionWidth,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{cutEndTime !== undefined && <div style={endMarkerStyle} className="cut-time-marker" />}
|
||||
|
||||
<div id="current-time-display">{formatDuration(this.getOffsetCurrentTime())}</div>
|
||||
</div>
|
||||
@ -571,7 +614,7 @@ class App extends React.Component {
|
||||
/>
|
||||
<i
|
||||
className={classnames({
|
||||
button: true, fa: true, 'fa-pause': this.state.playing, 'fa-play': !this.state.playing,
|
||||
button: true, fa: true, 'fa-pause': playing, 'fa-play': !playing,
|
||||
})}
|
||||
aria-hidden="true"
|
||||
onClick={this.playCommand}
|
||||
@ -597,7 +640,7 @@ class App extends React.Component {
|
||||
className="button fa fa-step-forward"
|
||||
aria-hidden="true"
|
||||
title="Jump to end of video"
|
||||
onClick={() => seekAbs(this.state.duration)}
|
||||
onClick={() => seekAbs(duration)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@ -631,37 +674,37 @@ class App extends React.Component {
|
||||
|
||||
<div className="left-menu">
|
||||
<span style={infoSpanStyle} title="Format of current file">
|
||||
{this.state.fileFormat || 'FMT'}
|
||||
{fileFormat || 'FMT'}
|
||||
</span>
|
||||
|
||||
<span style={infoSpanStyle} title="Playback rate">
|
||||
{round(this.state.playbackRate, 1) || 1}
|
||||
{round(playbackRate, 1) || 1}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="right-menu">
|
||||
<button
|
||||
type="button"
|
||||
title={`Cut mode ${this.state.keyframeCut ? 'nearest keyframe cut' : 'normal cut'}`}
|
||||
title={`Cut mode ${keyframeCut ? 'nearest keyframe cut' : 'normal cut'}`}
|
||||
onClick={withBlur(this.toggleKeyframeCut)}
|
||||
>
|
||||
{this.state.keyframeCut ? 'kc' : 'nc'}
|
||||
{keyframeCut ? 'kc' : 'nc'}
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
title={`Set output streams. Current: ${this.state.includeAllStreams ? 'include (and cut) all streams' : 'include only primary streams'}`}
|
||||
title={`Set output streams. Current: ${includeAllStreams ? 'include (and cut) all streams' : 'include only primary streams'}`}
|
||||
onClick={withBlur(this.toggleIncludeAllStreams)}
|
||||
>
|
||||
{this.state.includeAllStreams ? 'all' : 'ps'}
|
||||
{includeAllStreams ? 'all' : 'ps'}
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
title={`Delete audio? Current: ${this.state.stripAudio ? 'delete audio tracks' : 'keep audio tracks'}`}
|
||||
title={`Delete audio? Current: ${stripAudio ? 'delete audio tracks' : 'keep audio tracks'}`}
|
||||
onClick={withBlur(this.toggleStripAudio)}
|
||||
>
|
||||
{this.state.stripAudio ? 'da' : 'ka'}
|
||||
{stripAudio ? 'da' : 'ka'}
|
||||
</button>
|
||||
|
||||
<button
|
||||
@ -693,11 +736,11 @@ class App extends React.Component {
|
||||
title="Capture frame format"
|
||||
onClick={withBlur(this.toggleCaptureFormat)}
|
||||
>
|
||||
{this.state.captureFormat}
|
||||
{captureFormat}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<HelpSheet visible={!!this.state.helpVisible} />
|
||||
<HelpSheet visible={!!helpVisible} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user