mirror of
https://github.com/mifi/lossless-cut.git
synced 2024-11-25 03:33:14 +01:00
Load timecode offset from file #506
This commit is contained in:
parent
49a0d7db17
commit
a3bce63650
@ -73,6 +73,7 @@
|
||||
"react-scripts": "^3.4.0",
|
||||
"react-sortable-hoc": "^1.5.3",
|
||||
"react-use": "^13.26.1",
|
||||
"smpte-timecode": "^1.2.3",
|
||||
"strong-data-uri": "^1.0.5",
|
||||
"svg2png": "^4.1.1",
|
||||
"sweetalert2": "^9.10.10",
|
||||
|
@ -20,6 +20,7 @@ const defaults = {
|
||||
preserveMovData: false,
|
||||
avoidNegativeTs: 'make_zero',
|
||||
hideNotifications: undefined,
|
||||
autoLoadTimecode: false,
|
||||
},
|
||||
};
|
||||
|
||||
|
15
src/App.jsx
15
src/App.jsx
@ -42,7 +42,7 @@ import {
|
||||
getDefaultOutFormat, getFormatData, mergeFiles as ffmpegMergeFiles, renderThumbnails as ffmpegRenderThumbnails,
|
||||
readFrames, renderWaveformPng, html5ifyDummy, cutMultiple, extractStreams, autoMergeSegments, getAllStreams,
|
||||
findNearestKeyFrameTime, html5ify as ffmpegHtml5ify, isStreamThumbnail, isAudioSupported, isIphoneHevc, tryReadChaptersToEdl,
|
||||
fixInvalidDuration, getDuration,
|
||||
fixInvalidDuration, getDuration, getTimecodeFromStreams,
|
||||
} from './ffmpeg';
|
||||
import { saveCsv, loadCsv, loadXmeml, loadCue } from './edlStore';
|
||||
import {
|
||||
@ -194,6 +194,8 @@ const App = memo(() => {
|
||||
useEffect(() => safeSetConfig('ffmpegExperimental', ffmpegExperimental), [ffmpegExperimental]);
|
||||
const [hideNotifications, setHideNotifications] = useState(configStore.get('hideNotifications'));
|
||||
useEffect(() => safeSetConfig('hideNotifications', hideNotifications), [hideNotifications]);
|
||||
const [autoLoadTimecode, setAutoLoadTimecode] = useState(configStore.get('autoLoadTimecode'));
|
||||
useEffect(() => safeSetConfig('autoLoadTimecode', autoLoadTimecode), [autoLoadTimecode]);
|
||||
|
||||
useEffect(() => {
|
||||
i18n.changeLanguage(language || fallbackLng).catch(console.error);
|
||||
@ -1193,6 +1195,11 @@ const App = memo(() => {
|
||||
const { streams } = await getAllStreams(fp);
|
||||
// console.log('streams', streamsNew);
|
||||
|
||||
if (autoLoadTimecode) {
|
||||
const timecode = getTimecodeFromStreams(streams);
|
||||
if (timecode) setStartTimeOffset(timecode);
|
||||
}
|
||||
|
||||
const videoStream = streams.find(stream => stream.codec_type === 'video' && !isStreamThumbnail(stream));
|
||||
const audioStream = streams.find(stream => stream.codec_type === 'audio');
|
||||
setMainVideoStream(videoStream);
|
||||
@ -1270,7 +1277,7 @@ const App = memo(() => {
|
||||
} finally {
|
||||
setWorking();
|
||||
}
|
||||
}, [resetState, working, createDummyVideo, loadEdlFile, getEdlFilePath, getHtml5ifiedPath, loadCutSegments, enableAskForImportChapters, showUnsupportedFileMessage]);
|
||||
}, [resetState, working, createDummyVideo, loadEdlFile, getEdlFilePath, getHtml5ifiedPath, loadCutSegments, enableAskForImportChapters, showUnsupportedFileMessage, autoLoadTimecode]);
|
||||
|
||||
const toggleHelp = useCallback(() => setHelpVisible(val => !val), []);
|
||||
const toggleSettings = useCallback(() => setSettingsVisible(val => !val), []);
|
||||
@ -1883,12 +1890,14 @@ const App = memo(() => {
|
||||
setLanguage={setLanguage}
|
||||
hideNotifications={hideNotifications}
|
||||
setHideNotifications={setHideNotifications}
|
||||
autoLoadTimecode={autoLoadTimecode}
|
||||
setAutoLoadTimecode={setAutoLoadTimecode}
|
||||
|
||||
AutoExportToggler={AutoExportToggler}
|
||||
renderCaptureFormatButton={renderCaptureFormatButton}
|
||||
onWheelTunerRequested={onWheelTunerRequested}
|
||||
/>
|
||||
), [AutoExportToggler, askBeforeClose, autoMerge, autoSaveProjectFile, customOutDir, invertCutSegments, keyframeCut, renderCaptureFormatButton, timecodeShowFrames, changeOutDir, onWheelTunerRequested, language, invertTimelineScroll, ffmpegExperimental, setFfmpegExperimental, enableAskForImportChapters, setEnableAskForImportChapters, enableAskForFileOpenAction, setEnableAskForFileOpenAction, hideNotifications, setHideNotifications]);
|
||||
), [AutoExportToggler, askBeforeClose, autoMerge, autoSaveProjectFile, customOutDir, invertCutSegments, keyframeCut, renderCaptureFormatButton, timecodeShowFrames, changeOutDir, onWheelTunerRequested, language, invertTimelineScroll, ffmpegExperimental, setFfmpegExperimental, enableAskForImportChapters, setEnableAskForImportChapters, enableAskForFileOpenAction, setEnableAskForFileOpenAction, hideNotifications, setHideNotifications, autoLoadTimecode, setAutoLoadTimecode]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isStoreBuild) loadMifiLink().then(setMifiLink);
|
||||
|
@ -9,7 +9,7 @@ const Settings = memo(({
|
||||
AutoExportToggler, renderCaptureFormatButton, onWheelTunerRequested, language, setLanguage,
|
||||
invertTimelineScroll, setInvertTimelineScroll, ffmpegExperimental, setFfmpegExperimental,
|
||||
enableAskForImportChapters, setEnableAskForImportChapters, enableAskForFileOpenAction, setEnableAskForFileOpenAction,
|
||||
hideNotifications, setHideNotifications,
|
||||
hideNotifications, setHideNotifications, autoLoadTimecode, setAutoLoadTimecode
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
@ -197,6 +197,17 @@ const Settings = memo(({
|
||||
</Table.TextCell>
|
||||
</Row>
|
||||
|
||||
<Row>
|
||||
<KeyCell>{t('Auto load timecode from file as an offset in the timeline?')}</KeyCell>
|
||||
<Table.TextCell>
|
||||
<Checkbox
|
||||
label={t('Auto load timecode')}
|
||||
checked={autoLoadTimecode}
|
||||
onChange={e => setAutoLoadTimecode(e.target.checked)}
|
||||
/>
|
||||
</Table.TextCell>
|
||||
</Row>
|
||||
|
||||
<Row>
|
||||
<KeyCell>{t('Hide informational notifications?')}</KeyCell>
|
||||
<Table.TextCell>
|
||||
|
@ -5,6 +5,7 @@ import sum from 'lodash/sum';
|
||||
import sortBy from 'lodash/sortBy';
|
||||
import moment from 'moment';
|
||||
import i18n from 'i18next';
|
||||
import Timecode from 'smpte-timecode';
|
||||
|
||||
import { formatDuration, getOutPath, transferTimestamps, filenamify, isDurationValid } from './util';
|
||||
|
||||
@ -897,3 +898,31 @@ export async function fixInvalidDuration({ filePath, fileFormat, customOutDir })
|
||||
|
||||
return outPath;
|
||||
}
|
||||
|
||||
function parseTimecode(str, frameRate) {
|
||||
// console.log(str, frameRate);
|
||||
const t = Timecode(str, frameRate ? parseFloat(frameRate.toFixed(3)) : undefined);
|
||||
if (!t) return undefined;
|
||||
const seconds = ((t.hours * 60) + t.minutes) * 60 + t.seconds + (t.frames / t.frameRate);
|
||||
return Number.isFinite(seconds) ? seconds : undefined;
|
||||
}
|
||||
|
||||
export function getTimecodeFromStreams(streams) {
|
||||
console.log('Trying to load timecode');
|
||||
let foundTimecode;
|
||||
streams.find((stream) => {
|
||||
try {
|
||||
if (stream.tags && stream.tags.timecode) {
|
||||
const fps = getStreamFps(stream);
|
||||
foundTimecode = parseTimecode(stream.tags.timecode, fps);
|
||||
console.log('Loaded timecode', stream.tags.timecode, 'from stream', stream.index);
|
||||
return true;
|
||||
}
|
||||
return undefined;
|
||||
} catch (err) {
|
||||
// console.warn('Failed to parse timecode from file streams', err);
|
||||
return undefined;
|
||||
}
|
||||
});
|
||||
return foundTimecode;
|
||||
}
|
||||
|
@ -12067,6 +12067,11 @@ slice-ansi@^2.1.0:
|
||||
astral-regex "^1.0.0"
|
||||
is-fullwidth-code-point "^2.0.0"
|
||||
|
||||
smpte-timecode@^1.2.3:
|
||||
version "1.2.3"
|
||||
resolved "https://registry.yarnpkg.com/smpte-timecode/-/smpte-timecode-1.2.3.tgz#272ac8826724ce793d956109cfd3982b2f6b1893"
|
||||
integrity sha512-6U+vdStmprzmpzEWS+9pw8GfiChBcMmJOdJxBjaXlGJhB9U/jcQhvh0buxJzEfhuX8E0OfYJy4hayBgAO92dBg==
|
||||
|
||||
snapdragon-node@^2.0.1:
|
||||
version "2.1.1"
|
||||
resolved "https://registry.yarnpkg.com/snapdragon-node/-/snapdragon-node-2.1.1.tgz#6c175f86ff14bdb0724563e8f3c1b021a286853b"
|
||||
|
Loading…
Reference in New Issue
Block a user