From 379cdc6cfd78adeeb7b6fabe3d94fc77c95547ed Mon Sep 17 00:00:00 2001 From: Michael Main Date: Thu, 2 Nov 2023 23:18:00 -0500 Subject: [PATCH] Updated keyboard controls and turned clip table into a component: --- src/renderer/App.css | 3 + src/renderer/components/ClipList.jsx | 102 ++++++------- src/renderer/components/ClipTable.jsx | 129 +++++++++++++++++ src/renderer/components/SubtitleList.jsx | 100 ++++++------- src/renderer/routes/CollectionManager.jsx | 84 ++++------- src/renderer/routes/VideoList.jsx | 85 ++--------- src/renderer/routes/editor/AdvancedEditor.jsx | 136 +++++++++++++++--- src/renderer/routes/editor/ClipCutter.jsx | 53 ++++++- src/renderer/util/VideoTools.js | 7 +- 9 files changed, 451 insertions(+), 248 deletions(-) create mode 100644 src/renderer/components/ClipTable.jsx diff --git a/src/renderer/App.css b/src/renderer/App.css index b36f8f9..e58e97e 100644 --- a/src/renderer/App.css +++ b/src/renderer/App.css @@ -147,6 +147,9 @@ button.selected { .subtitle-list { max-height: 200px; overflow-y: scroll; +} + +.subtitle-list table { margin: auto; } diff --git a/src/renderer/components/ClipList.jsx b/src/renderer/components/ClipList.jsx index f0ae865..978ac75 100644 --- a/src/renderer/components/ClipList.jsx +++ b/src/renderer/components/ClipList.jsx @@ -63,56 +63,58 @@ export default ({

Clips

- - - - - - - - - - - {clips.map((clip) => { - return ( - { - onSelectClip(clip.index); - }} - > - - - - - - ); - })} - -
IndexInOut
[{clip.index}] - {convertMillisecondsToTimestamp( - clip.startTime - )} - - {convertMillisecondsToTimestamp( - clip.endTime - )} - - -
+
+ + + + + + + + + + + {clips.map((clip) => { + return ( + { + onSelectClip(clip.index); + }} + > + + + + + + ); + })} + +
IndexInOut
[{clip.index}] + {convertMillisecondsToTimestamp( + clip.startTime + )} + + {convertMillisecondsToTimestamp( + clip.endTime + )} + + +
+
+ + ) : null} + + ); + })} + + + ); +}; diff --git a/src/renderer/components/SubtitleList.jsx b/src/renderer/components/SubtitleList.jsx index 0cf5db0..0ad1750 100644 --- a/src/renderer/components/SubtitleList.jsx +++ b/src/renderer/components/SubtitleList.jsx @@ -110,54 +110,58 @@ export default ({

Subtitles

- - - - - - - - - - - {subs.map((sub) => { - return ( - { - onSelectSub(sub.index); - }} - > - - - - - - ); - })} - -
IndexInOut
[{sub.index}] - {convertMillisecondsToTimestamp( - sub.startTime - )} - - {convertMillisecondsToTimestamp( - sub.endTime - )} - - -
+
+ + + + + + + + + + + {subs.map((sub) => { + return ( + { + onSelectSub(sub.index); + }} + > + + + + + + ); + })} + +
IndexInOut
[{sub.index}] + {convertMillisecondsToTimestamp( + sub.startTime + )} + + {convertMillisecondsToTimestamp( + sub.endTime + )} + + +
+
- - - ); - })} - + { + navigate(`/edit/${id}`); + }} + onDelete={(id, game) => { + deleteFile(id, game); + }} + /> ); }; diff --git a/src/renderer/routes/editor/AdvancedEditor.jsx b/src/renderer/routes/editor/AdvancedEditor.jsx index bd6076e..ed10da6 100644 --- a/src/renderer/routes/editor/AdvancedEditor.jsx +++ b/src/renderer/routes/editor/AdvancedEditor.jsx @@ -100,20 +100,37 @@ let AdvancedEditor = () => { } switch (event.key) { - case 'ArrowUp': + case 'ArrowUp': { if (!stateRef.current.currentSub === null) { return; } + // let nextSub = + // stateRef.current.subs[ + // Math.min( + // stateRef.current.subs.length - 1, + // stateRef.current.currentSub + 1 + // ) + // ]; + // setCurrentSliderPosition(nextSub.startTime); + // setCurrentPosition(nextSub.startTime / 1000 + 1 / 1000); setCurrentSub((currentSub) => Math.min(stateRef.current.subs.length - 1, currentSub + 1) ); break; - case 'ArrowDown': + } + case 'ArrowDown': { if (stateRef.current.currentSub === null) { return; } + // let nextSub = + // stateRef.current.subs[ + // Math.max(0, stateRef.current.currentSub - 1) + // ]; + // setCurrentSliderPosition(nextSub.startTime); + // setCurrentPosition(nextSub.startTime / 1000); setCurrentSub((currentSub) => Math.max(0, currentSub - 1)); break; + } case 'ArrowLeft': { setCurrentSliderPosition((currentSliderPosition) => Math.max(0, currentSliderPosition - 1000) @@ -137,7 +154,7 @@ let AdvancedEditor = () => { break; } - case ',': { + case ';': { setCurrentSliderPosition((currentSliderPosition) => Math.max(0, currentSliderPosition - 1000 / 60) ); @@ -147,7 +164,7 @@ let AdvancedEditor = () => { break; } - case '.': { + case "'": { setCurrentSliderPosition((currentSliderPosition) => Math.min( stateRef.current.videoLength * 1000, @@ -155,7 +172,10 @@ let AdvancedEditor = () => { ) ); setCurrentPosition((currentPosition) => - Math.min(videoLength, currentPosition + 1 / 60) + Math.min( + stateRef.current.videoLength, + currentPosition + 1 / 60 + ) ); break; @@ -186,6 +206,20 @@ let AdvancedEditor = () => { ); break; } + case '[': { + let currentSubObject = + stateRef.current.subs[stateRef.current.currentSub]; + setCurrentSliderPosition(currentSubObject.startTime); + setCurrentPosition(currentSubObject.startTime / 1000); + break; + } + case ']': { + let currentSubObject = + stateRef.current.subs[stateRef.current.currentSub]; + setCurrentSliderPosition(currentSubObject.endTime); + setCurrentPosition(currentSubObject.endTime / 1000); + break; + } case 'w': { setCurrentRow((currentRow) => Math.max(0, currentRow - 1)); break; @@ -222,6 +256,17 @@ let AdvancedEditor = () => { event.stopPropagation(); }); + const getCurrentIndex = () => { + let index = subs.findIndex((subtitle) => { + return ( + currentSliderPosition > subtitle.startTime && + currentSliderPosition < subtitle.endTime + ); + }); + + return index; + }; + useEffect(() => { if (id) { getVideo(id); @@ -236,17 +281,6 @@ let AdvancedEditor = () => { }; }, []); - const getCurrentIndex = () => { - let index = subs.findIndex((subtitle) => { - return ( - currentSliderPosition > subtitle.startTime && - currentSliderPosition < subtitle.endTime - ); - }); - - return index; - }; - useEffect(() => { let index = getCurrentIndex(); if (index >= 0) { @@ -280,6 +314,73 @@ let AdvancedEditor = () => { }); setVideoSource(`game://${params.type}/${id}.mp4`); + + subtitles = distributeSubs(subtitles); + setSubs(subtitles); + }; + + const overlaps = (clip1Start, clip1End, clip2Start, clip2End) => { + return ( + (clip1Start <= clip2End && clip1End >= clip2Start) || + (clip2Start <= clip1End && clip2End >= clip1Start) + ); + }; + + const distributeSubs = (subtitles) => { + let placedSubtitles = []; + for (let subtitle of subtitles) { + let restrictedRows = []; + subtitle.rowIndex = 0; + console.log(`PLACING ${subtitle.index}`); + for (let placedSubtitle of placedSubtitles) { + console.log( + `SUBTITLE ${subtitle.index} STARTS ${subtitle.startTime} AND ENDS ${subtitle.endTime} IN ${subtitle.rowIndex}` + ); + console.log('AND'); + console.log( + `SUBTITLE ${placedSubtitle.index} STARTS ${placedSubtitle.startTime} AND ENDS ${placedSubtitle.endTime} IN ${placedSubtitle.rowIndex}` + ); + console.log(''); + if ( + overlaps( + subtitle.startTime, + subtitle.endTime, + placedSubtitle.startTime, + placedSubtitle.endTime + ) + ) { + if (!restrictedRows.includes(placedSubtitle.rowIndex)) { + restrictedRows.push(placedSubtitle.rowIndex); + } + console.log( + `OVERLAP BETWEEN ${subtitle.index} IN ${subtitle.rowIndex} AND ${placedSubtitle.index} IN ${placedSubtitle.rowIndex}` + ); + console.log(`RESTRICTED ROWS: ${restrictedRows}`); + } + } + for (let row = 0; row < 5; row++) { + if (!restrictedRows.includes(row)) { + console.log(`PLACING ${subtitle.index} IN ${row}`); + console.log(''); + subtitle.rowIndex = row; + break; + } + } + placedSubtitles.push(subtitle); + } + + return placedSubtitles; + }; + + const fixSubs = (videoLength) => { + let subtitles = [...subs]; + + subtitles.forEach((subtitle) => { + // Adjust clip if it goes over the edge of the video + subtitle.startTime = Math.max(0, subtitle.startTime); + subtitle.endTime = Math.min(videoLength, subtitle.endTime); + }); + setSubs(subtitles); }; @@ -390,6 +491,7 @@ let AdvancedEditor = () => { index, }; }); + subList = distributeSubs(subList); setCurrentSub(newSubIndex); setSubs(subList); } else if (mode === 'edit') { @@ -410,6 +512,7 @@ let AdvancedEditor = () => { index, }; }); + subList = distributeSubs(subList); setSubs(subList); } else if (mode === 'remove') { let subList = [...stateRef.current.subs]; @@ -477,6 +580,7 @@ let AdvancedEditor = () => { setVideoLength(video.duration); } setActualVideoLength(video.duration); + fixSubs(video.duration * 1000); }} /> { const [videoLength, setVideoLength] = useState(0); let videoLengthMs = videoLength * 1000; - let defaultClipSize = videoLengthMs * 0.1; + let defaultClipSize = 8000; // The recommended maximum length let game = ''; if (params.type === 'rifftrax') { @@ -89,20 +89,37 @@ let ClipCutter = () => { } switch (event.key) { - case 'ArrowUp': + case 'ArrowUp': { if (stateRef.current.currentClip === null) { return; } + // let nextClip = + // stateRef.current.clips[ + // Math.min( + // stateRef.current.clips.length - 1, + // stateRef.current.currentClip + 1 + // ) + // ]; + // setCurrentSliderPosition(nextClip.startTime); + // setCurrentPosition(nextClip.startTime / 1000); setCurrentClip((currentClip) => Math.min(stateRef.current.clips.length - 1, currentClip + 1) ); break; - case 'ArrowDown': + } + case 'ArrowDown': { if (stateRef.current.currentClip === null) { return; } + // let nextClip = + // stateRef.current.clips[ + // Math.max(0, stateRef.current.currentClip - 1) + // ]; + // setCurrentSliderPosition(nextClip.startTime); + // setCurrentPosition(nextClip.startTime / 1000); setCurrentClip((currentClip) => Math.max(0, currentClip - 1)); break; + } case 'ArrowLeft': setCurrentSliderPosition((currentSliderPosition) => Math.max(0, currentSliderPosition - 1000) @@ -122,7 +139,7 @@ let ClipCutter = () => { Math.min(stateRef.current.videoLength, currentPosition + 1) ); break; - case ',': + case ';': setCurrentSliderPosition((currentSliderPosition) => Math.max(0, currentSliderPosition - 1000 / 60) ); @@ -130,7 +147,7 @@ let ClipCutter = () => { Math.max(0, currentPosition - 1 / 60) ); break; - case '.': + case "'": setCurrentSliderPosition((currentSliderPosition) => Math.min( stateRef.current.videoLength * 1000, @@ -170,6 +187,32 @@ let ClipCutter = () => { ); break; } + case '[': { + let currentClipObject = + stateRef.current.clips[stateRef.current.currentClip]; + setCurrentSliderPosition(currentClipObject.startTime); + setCurrentPosition(currentClipObject.startTime / 1000); + break; + } + case ']': { + let currentClipObject = + stateRef.current.clips[stateRef.current.currentClip]; + setCurrentSliderPosition(currentClipObject.endTime); + setCurrentPosition(currentClipObject.endTime / 1000); + break; + } + case 'k': + setCurrentSliderPosition( + stateRef.current.currentClip.startTime + ); + setCurrentPosition( + stateRef.current.currentClip.startTime / 1000 + ); + break; + case 'l': + setCurrentSliderPosition(stateRef.current.currentClip.endTime); + setCurrentPosition(stateRef.current.currentClip.endTime / 1000); + break; case 'n': clipChangeHandler('add', { rowIndex: 0, diff --git a/src/renderer/util/VideoTools.js b/src/renderer/util/VideoTools.js index a2c95aa..4fa1f31 100644 --- a/src/renderer/util/VideoTools.js +++ b/src/renderer/util/VideoTools.js @@ -68,8 +68,9 @@ export let convertSrtToSubtitles = (srtBase64) => { let subtitle = {}; let regex = /(\d\d:\d\d:\d\d,\d\d\d) --> (\d\d:\d\d:\d\d,\d\d\d)/; - let srt = atob(srtBase64); + let srt = atob(srtBase64).replaceAll('\r', ''); let n = 0; + srt.split('\n').forEach((line) => { switch (n++) { case 0: @@ -91,6 +92,7 @@ export let convertSrtToSubtitles = (srtBase64) => { break; case 3: if (line !== '') { + subtitle.text += `\n${line}`; n = 3; return; } @@ -100,6 +102,9 @@ export let convertSrtToSubtitles = (srtBase64) => { break; } }); + if (n === 0) { + subtitles.push(subtitle); + } return subtitles; };