Load timecode offset from file #506

pull/523/head
Mikael Finstad 6 years ago
parent 49a0d7db17
commit a3bce63650

@ -73,6 +73,7 @@
"react-scripts": "^3.4.0", "react-scripts": "^3.4.0",
"react-sortable-hoc": "^1.5.3", "react-sortable-hoc": "^1.5.3",
"react-use": "^13.26.1", "react-use": "^13.26.1",
"smpte-timecode": "^1.2.3",
"strong-data-uri": "^1.0.5", "strong-data-uri": "^1.0.5",
"svg2png": "^4.1.1", "svg2png": "^4.1.1",
"sweetalert2": "^9.10.10", "sweetalert2": "^9.10.10",

@ -20,6 +20,7 @@ const defaults = {
preserveMovData: false, preserveMovData: false,
avoidNegativeTs: 'make_zero', avoidNegativeTs: 'make_zero',
hideNotifications: undefined, hideNotifications: undefined,
autoLoadTimecode: false,
}, },
}; };

@ -42,7 +42,7 @@ import {
getDefaultOutFormat, getFormatData, mergeFiles as ffmpegMergeFiles, renderThumbnails as ffmpegRenderThumbnails, getDefaultOutFormat, getFormatData, mergeFiles as ffmpegMergeFiles, renderThumbnails as ffmpegRenderThumbnails,
readFrames, renderWaveformPng, html5ifyDummy, cutMultiple, extractStreams, autoMergeSegments, getAllStreams, readFrames, renderWaveformPng, html5ifyDummy, cutMultiple, extractStreams, autoMergeSegments, getAllStreams,
findNearestKeyFrameTime, html5ify as ffmpegHtml5ify, isStreamThumbnail, isAudioSupported, isIphoneHevc, tryReadChaptersToEdl, findNearestKeyFrameTime, html5ify as ffmpegHtml5ify, isStreamThumbnail, isAudioSupported, isIphoneHevc, tryReadChaptersToEdl,
fixInvalidDuration, getDuration, fixInvalidDuration, getDuration, getTimecodeFromStreams,
} from './ffmpeg'; } from './ffmpeg';
import { saveCsv, loadCsv, loadXmeml, loadCue } from './edlStore'; import { saveCsv, loadCsv, loadXmeml, loadCue } from './edlStore';
import { import {
@ -194,6 +194,8 @@ const App = memo(() => {
useEffect(() => safeSetConfig('ffmpegExperimental', ffmpegExperimental), [ffmpegExperimental]); useEffect(() => safeSetConfig('ffmpegExperimental', ffmpegExperimental), [ffmpegExperimental]);
const [hideNotifications, setHideNotifications] = useState(configStore.get('hideNotifications')); const [hideNotifications, setHideNotifications] = useState(configStore.get('hideNotifications'));
useEffect(() => safeSetConfig('hideNotifications', hideNotifications), [hideNotifications]); useEffect(() => safeSetConfig('hideNotifications', hideNotifications), [hideNotifications]);
const [autoLoadTimecode, setAutoLoadTimecode] = useState(configStore.get('autoLoadTimecode'));
useEffect(() => safeSetConfig('autoLoadTimecode', autoLoadTimecode), [autoLoadTimecode]);
useEffect(() => { useEffect(() => {
i18n.changeLanguage(language || fallbackLng).catch(console.error); i18n.changeLanguage(language || fallbackLng).catch(console.error);
@ -1193,6 +1195,11 @@ const App = memo(() => {
const { streams } = await getAllStreams(fp); const { streams } = await getAllStreams(fp);
// console.log('streams', streamsNew); // 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 videoStream = streams.find(stream => stream.codec_type === 'video' && !isStreamThumbnail(stream));
const audioStream = streams.find(stream => stream.codec_type === 'audio'); const audioStream = streams.find(stream => stream.codec_type === 'audio');
setMainVideoStream(videoStream); setMainVideoStream(videoStream);
@ -1270,7 +1277,7 @@ const App = memo(() => {
} finally { } finally {
setWorking(); 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 toggleHelp = useCallback(() => setHelpVisible(val => !val), []);
const toggleSettings = useCallback(() => setSettingsVisible(val => !val), []); const toggleSettings = useCallback(() => setSettingsVisible(val => !val), []);
@ -1883,12 +1890,14 @@ const App = memo(() => {
setLanguage={setLanguage} setLanguage={setLanguage}
hideNotifications={hideNotifications} hideNotifications={hideNotifications}
setHideNotifications={setHideNotifications} setHideNotifications={setHideNotifications}
autoLoadTimecode={autoLoadTimecode}
setAutoLoadTimecode={setAutoLoadTimecode}
AutoExportToggler={AutoExportToggler} AutoExportToggler={AutoExportToggler}
renderCaptureFormatButton={renderCaptureFormatButton} renderCaptureFormatButton={renderCaptureFormatButton}
onWheelTunerRequested={onWheelTunerRequested} 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(() => { useEffect(() => {
if (!isStoreBuild) loadMifiLink().then(setMifiLink); if (!isStoreBuild) loadMifiLink().then(setMifiLink);

@ -9,7 +9,7 @@ const Settings = memo(({
AutoExportToggler, renderCaptureFormatButton, onWheelTunerRequested, language, setLanguage, AutoExportToggler, renderCaptureFormatButton, onWheelTunerRequested, language, setLanguage,
invertTimelineScroll, setInvertTimelineScroll, ffmpegExperimental, setFfmpegExperimental, invertTimelineScroll, setInvertTimelineScroll, ffmpegExperimental, setFfmpegExperimental,
enableAskForImportChapters, setEnableAskForImportChapters, enableAskForFileOpenAction, setEnableAskForFileOpenAction, enableAskForImportChapters, setEnableAskForImportChapters, enableAskForFileOpenAction, setEnableAskForFileOpenAction,
hideNotifications, setHideNotifications, hideNotifications, setHideNotifications, autoLoadTimecode, setAutoLoadTimecode
}) => { }) => {
const { t } = useTranslation(); const { t } = useTranslation();
@ -197,6 +197,17 @@ const Settings = memo(({
</Table.TextCell> </Table.TextCell>
</Row> </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> <Row>
<KeyCell>{t('Hide informational notifications?')}</KeyCell> <KeyCell>{t('Hide informational notifications?')}</KeyCell>
<Table.TextCell> <Table.TextCell>

@ -5,6 +5,7 @@ import sum from 'lodash/sum';
import sortBy from 'lodash/sortBy'; import sortBy from 'lodash/sortBy';
import moment from 'moment'; import moment from 'moment';
import i18n from 'i18next'; import i18n from 'i18next';
import Timecode from 'smpte-timecode';
import { formatDuration, getOutPath, transferTimestamps, filenamify, isDurationValid } from './util'; import { formatDuration, getOutPath, transferTimestamps, filenamify, isDurationValid } from './util';
@ -897,3 +898,31 @@ export async function fixInvalidDuration({ filePath, fileFormat, customOutDir })
return outPath; 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" astral-regex "^1.0.0"
is-fullwidth-code-point "^2.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: snapdragon-node@^2.0.1:
version "2.1.1" version "2.1.1"
resolved "https://registry.yarnpkg.com/snapdragon-node/-/snapdragon-node-2.1.1.tgz#6c175f86ff14bdb0724563e8f3c1b021a286853b" resolved "https://registry.yarnpkg.com/snapdragon-node/-/snapdragon-node-2.1.1.tgz#6c175f86ff14bdb0724563e8f3c1b021a286853b"

Loading…
Cancel
Save