mirror of
https://github.com/mifi/lossless-cut.git
synced 2024-11-24 11:22:34 +01:00
tsify
This commit is contained in:
parent
918277bd75
commit
4892437b83
@ -87,7 +87,7 @@ import BigWaveform from './components/BigWaveform';
|
|||||||
|
|
||||||
import isDev from './isDev';
|
import isDev from './isDev';
|
||||||
import { Chapter, ChromiumHTMLVideoElement, CustomTagsByFile, EdlFileType, FfmpegCommandLog, FilesMeta, FormatTimecode, ParamsByStreamId, ParseTimecode, PlaybackMode, SegmentColorIndex, SegmentTags, SegmentToExport, StateSegment, Thumbnail, TunerType } from './types';
|
import { Chapter, ChromiumHTMLVideoElement, CustomTagsByFile, EdlFileType, FfmpegCommandLog, FilesMeta, FormatTimecode, ParamsByStreamId, ParseTimecode, PlaybackMode, SegmentColorIndex, SegmentTags, SegmentToExport, StateSegment, Thumbnail, TunerType } from './types';
|
||||||
import { CaptureFormat, KeyboardAction, Html5ifyMode } from '../../../types';
|
import { CaptureFormat, KeyboardAction, Html5ifyMode, WaveformMode } from '../../../types';
|
||||||
import { FFprobeChapter, FFprobeFormat, FFprobeStream } from '../../../ffprobe';
|
import { FFprobeChapter, FFprobeFormat, FFprobeStream } from '../../../ffprobe';
|
||||||
|
|
||||||
const electron = window.require('electron');
|
const electron = window.require('electron');
|
||||||
@ -151,7 +151,7 @@ function App() {
|
|||||||
|
|
||||||
// State per application launch
|
// State per application launch
|
||||||
const lastOpenedPathRef = useRef<string>();
|
const lastOpenedPathRef = useRef<string>();
|
||||||
const [waveformMode, setWaveformMode] = useState<'big-waveform' | 'waveform'>();
|
const [waveformMode, setWaveformMode] = useState<WaveformMode>();
|
||||||
const [thumbnailsEnabled, setThumbnailsEnabled] = useState(false);
|
const [thumbnailsEnabled, setThumbnailsEnabled] = useState(false);
|
||||||
const [keyframesEnabled, setKeyframesEnabled] = useState(true);
|
const [keyframesEnabled, setKeyframesEnabled] = useState(true);
|
||||||
const [showRightBar, setShowRightBar] = useState(true);
|
const [showRightBar, setShowRightBar] = useState(true);
|
||||||
@ -215,7 +215,7 @@ function App() {
|
|||||||
const videoRef = useRef<ChromiumHTMLVideoElement>(null);
|
const videoRef = useRef<ChromiumHTMLVideoElement>(null);
|
||||||
const videoContainerRef = useRef<HTMLDivElement>(null);
|
const videoContainerRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
const setOutputPlaybackRate = useCallback((v) => {
|
const setOutputPlaybackRate = useCallback((v: number) => {
|
||||||
setOutputPlaybackRateState(v);
|
setOutputPlaybackRateState(v);
|
||||||
if (videoRef.current) videoRef.current.playbackRate = v;
|
if (videoRef.current) videoRef.current.playbackRate = v;
|
||||||
}, []);
|
}, []);
|
||||||
@ -1634,7 +1634,7 @@ function App() {
|
|||||||
const toggleLastCommands = useCallback(() => setLastCommandsVisible((val) => !val), []);
|
const toggleLastCommands = useCallback(() => setLastCommandsVisible((val) => !val), []);
|
||||||
const toggleSettings = useCallback(() => setSettingsVisible((val) => !val), []);
|
const toggleSettings = useCallback(() => setSettingsVisible((val) => !val), []);
|
||||||
|
|
||||||
const seekClosestKeyframe = useCallback((direction) => {
|
const seekClosestKeyframe = useCallback((direction: number) => {
|
||||||
const time = findNearestKeyFrameTime({ time: getRelevantTime(), direction });
|
const time = findNearestKeyFrameTime({ time: getRelevantTime(), direction });
|
||||||
if (time == null) return;
|
if (time == null) return;
|
||||||
userSeekAbs(time);
|
userSeekAbs(time);
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { memo, useCallback, useEffect, useMemo, useState } from 'react';
|
import { CSSProperties, Dispatch, SetStateAction, memo, useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
import { motion } from 'framer-motion';
|
import { motion } from 'framer-motion';
|
||||||
import { MdRotate90DegreesCcw } from 'react-icons/md';
|
import { MdRotate90DegreesCcw } from 'react-icons/md';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
@ -23,16 +23,18 @@ import { useSegColors } from './contexts';
|
|||||||
import { isExactDurationMatch } from './util/duration';
|
import { isExactDurationMatch } from './util/duration';
|
||||||
import useUserSettings from './hooks/useUserSettings';
|
import useUserSettings from './hooks/useUserSettings';
|
||||||
import { askForPlaybackRate } from './dialogs';
|
import { askForPlaybackRate } from './dialogs';
|
||||||
|
import { ApparentCutSegment, FormatTimecode, ParseTimecode, SegmentToExport, StateSegment } from './types';
|
||||||
|
import { WaveformMode } from '../../../types';
|
||||||
|
|
||||||
const { clipboard } = window.require('electron');
|
const { clipboard } = window.require('electron');
|
||||||
|
|
||||||
|
|
||||||
const zoomOptions = Array.from({ length: 13 }).fill().map((unused, z) => 2 ** z);
|
const zoomOptions = Array.from({ length: 13 }).fill(undefined).map((_unused, z) => 2 ** z);
|
||||||
|
|
||||||
const leftRightWidth = 100;
|
const leftRightWidth = 100;
|
||||||
|
|
||||||
// eslint-disable-next-line react/display-name
|
// eslint-disable-next-line react/display-name
|
||||||
const InvertCutModeButton = memo(({ invertCutSegments, setInvertCutSegments }) => {
|
const InvertCutModeButton = memo(({ invertCutSegments, setInvertCutSegments }: { invertCutSegments: boolean, setInvertCutSegments: Dispatch<SetStateAction<boolean>> }) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const onYinYangClick = useCallback(() => {
|
const onYinYangClick = useCallback(() => {
|
||||||
@ -65,15 +67,26 @@ const InvertCutModeButton = memo(({ invertCutSegments, setInvertCutSegments }) =
|
|||||||
|
|
||||||
|
|
||||||
// eslint-disable-next-line react/display-name
|
// eslint-disable-next-line react/display-name
|
||||||
const CutTimeInput = memo(({ darkMode, cutTime, setCutTime, startTimeOffset, seekAbs, currentCutSeg, currentApparentCutSeg, isStart, formatTimecode, parseTimecode }) => {
|
const CutTimeInput = memo(({ darkMode, cutTime, setCutTime, startTimeOffset, seekAbs, currentCutSeg, currentApparentCutSeg, isStart, formatTimecode, parseTimecode }: {
|
||||||
|
darkMode: boolean,
|
||||||
|
cutTime: number,
|
||||||
|
setCutTime: (type: 'start' | 'end', v: number) => void,
|
||||||
|
startTimeOffset: number,
|
||||||
|
seekAbs: (a: number) => void,
|
||||||
|
currentCutSeg: StateSegment,
|
||||||
|
currentApparentCutSeg: ApparentCutSegment,
|
||||||
|
isStart?: boolean,
|
||||||
|
formatTimecode: FormatTimecode,
|
||||||
|
parseTimecode: ParseTimecode,
|
||||||
|
}) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { getSegColor } = useSegColors();
|
const { getSegColor } = useSegColors();
|
||||||
|
|
||||||
const [cutTimeManual, setCutTimeManual] = useState();
|
const [cutTimeManual, setCutTimeManual] = useState<string>();
|
||||||
|
|
||||||
// Clear manual overrides if upstream cut time has changed
|
// Clear manual overrides if upstream cut time has changed
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setCutTimeManual();
|
setCutTimeManual(undefined);
|
||||||
}, [setCutTimeManual, currentApparentCutSeg.start, currentApparentCutSeg.end]);
|
}, [setCutTimeManual, currentApparentCutSeg.start, currentApparentCutSeg.end]);
|
||||||
|
|
||||||
const isCutTimeManualSet = () => cutTimeManual !== undefined;
|
const isCutTimeManualSet = () => cutTimeManual !== undefined;
|
||||||
@ -83,7 +96,7 @@ const CutTimeInput = memo(({ darkMode, cutTime, setCutTime, startTimeOffset, see
|
|||||||
return `.1em solid ${darkMode ? segColor.desaturate(0.4).lightness(50).string() : segColor.desaturate(0.2).lightness(60).string()}`;
|
return `.1em solid ${darkMode ? segColor.desaturate(0.4).lightness(50).string() : segColor.desaturate(0.2).lightness(60).string()}`;
|
||||||
}, [currentCutSeg, darkMode, getSegColor]);
|
}, [currentCutSeg, darkMode, getSegColor]);
|
||||||
|
|
||||||
const cutTimeInputStyle = {
|
const cutTimeInputStyle: CSSProperties = {
|
||||||
border, borderRadius: 5, backgroundColor: 'var(--gray5)', transition: darkModeTransition, fontSize: 13, textAlign: 'center', padding: '1px 5px', marginTop: 0, marginBottom: 0, marginLeft: isStart ? 0 : 5, marginRight: isStart ? 5 : 0, boxSizing: 'border-box', fontFamily: 'inherit', width: 90, outline: 'none',
|
border, borderRadius: 5, backgroundColor: 'var(--gray5)', transition: darkModeTransition, fontSize: 13, textAlign: 'center', padding: '1px 5px', marginTop: 0, marginBottom: 0, marginLeft: isStart ? 0 : 5, marginRight: isStart ? 5 : 0, boxSizing: 'border-box', fontFamily: 'inherit', width: 90, outline: 'none',
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -92,7 +105,7 @@ const CutTimeInput = memo(({ darkMode, cutTime, setCutTime, startTimeOffset, see
|
|||||||
try {
|
try {
|
||||||
setCutTime(isStart ? 'start' : 'end', timeWithoutOffset);
|
setCutTime(isStart ? 'start' : 'end', timeWithoutOffset);
|
||||||
seekAbs(timeWithoutOffset);
|
seekAbs(timeWithoutOffset);
|
||||||
setCutTimeManual();
|
setCutTimeManual(undefined);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Cannot set cut time', err);
|
console.error('Cannot set cut time', err);
|
||||||
// If we get an error from setCutTime, remain in the editing state (cutTimeManual)
|
// If we get an error from setCutTime, remain in the editing state (cutTimeManual)
|
||||||
@ -104,7 +117,7 @@ const CutTimeInput = memo(({ darkMode, cutTime, setCutTime, startTimeOffset, see
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
// Don't proceed if not a valid time value
|
// Don't proceed if not a valid time value
|
||||||
const timeWithOffset = parseTimecode(cutTimeManual);
|
const timeWithOffset = cutTimeManual != null ? parseTimecode(cutTimeManual) : undefined;
|
||||||
if (timeWithOffset === undefined) return;
|
if (timeWithOffset === undefined) return;
|
||||||
|
|
||||||
trySetTime(timeWithOffset);
|
trySetTime(timeWithOffset);
|
||||||
@ -158,7 +171,7 @@ const CutTimeInput = memo(({ darkMode, cutTime, setCutTime, startTimeOffset, see
|
|||||||
title={isStart ? t('Manually input current segment\'s start time') : t('Manually input current segment\'s end time')}
|
title={isStart ? t('Manually input current segment\'s start time') : t('Manually input current segment\'s end time')}
|
||||||
onChange={(e) => handleCutTimeInput(e.target.value)}
|
onChange={(e) => handleCutTimeInput(e.target.value)}
|
||||||
onPaste={handleCutTimePaste}
|
onPaste={handleCutTimePaste}
|
||||||
onBlur={() => setCutTimeManual()}
|
onBlur={() => setCutTimeManual(undefined)}
|
||||||
onContextMenu={handleContextMenu}
|
onContextMenu={handleContextMenu}
|
||||||
value={isCutTimeManualSet()
|
value={isCutTimeManualSet()
|
||||||
? cutTimeManual
|
? cutTimeManual
|
||||||
@ -181,6 +194,54 @@ function BottomBar({
|
|||||||
toggleShowThumbnails, toggleWaveformMode, waveformMode, showThumbnails,
|
toggleShowThumbnails, toggleWaveformMode, waveformMode, showThumbnails,
|
||||||
outputPlaybackRate, setOutputPlaybackRate,
|
outputPlaybackRate, setOutputPlaybackRate,
|
||||||
formatTimecode, parseTimecode,
|
formatTimecode, parseTimecode,
|
||||||
|
}: {
|
||||||
|
zoom: number,
|
||||||
|
setZoom: Dispatch<SetStateAction<number>>,
|
||||||
|
timelineToggleComfortZoom: () => void,
|
||||||
|
isRotationSet: boolean,
|
||||||
|
rotation: number,
|
||||||
|
areWeCutting: boolean,
|
||||||
|
increaseRotation: () => void,
|
||||||
|
cleanupFilesDialog: () => void,
|
||||||
|
captureSnapshot: () => void,
|
||||||
|
onExportPress: () => void,
|
||||||
|
segmentsToExport: SegmentToExport[],
|
||||||
|
hasVideo: boolean,
|
||||||
|
seekAbs: (a: number) => void,
|
||||||
|
currentSegIndexSafe: number,
|
||||||
|
cutSegments: StateSegment[],
|
||||||
|
currentCutSeg: StateSegment,
|
||||||
|
setCutStart: () => void,
|
||||||
|
setCutEnd: () => void,
|
||||||
|
setCurrentSegIndex: Dispatch<SetStateAction<number>>,
|
||||||
|
jumpTimelineStart: () => void,
|
||||||
|
jumpTimelineEnd: () => void,
|
||||||
|
jumpCutEnd: () => void,
|
||||||
|
jumpCutStart: () => void,
|
||||||
|
startTimeOffset: number,
|
||||||
|
setCutTime: (type: 'start' | 'end', v: number) => void,
|
||||||
|
currentApparentCutSeg: ApparentCutSegment,
|
||||||
|
playing: boolean,
|
||||||
|
shortStep: (a: number) => void,
|
||||||
|
togglePlay: () => void,
|
||||||
|
toggleLoopSelectedSegments: () => void,
|
||||||
|
hasAudio: boolean,
|
||||||
|
keyframesEnabled: boolean,
|
||||||
|
toggleShowKeyframes: () => void,
|
||||||
|
seekClosestKeyframe: (a: number) => void,
|
||||||
|
detectedFps: number | undefined,
|
||||||
|
isFileOpened: boolean,
|
||||||
|
selectedSegments: ApparentCutSegment[],
|
||||||
|
darkMode: boolean,
|
||||||
|
setDarkMode: Dispatch<SetStateAction<boolean>>,
|
||||||
|
toggleShowThumbnails: () => void,
|
||||||
|
toggleWaveformMode: () => void,
|
||||||
|
waveformMode: WaveformMode | undefined,
|
||||||
|
showThumbnails: boolean,
|
||||||
|
outputPlaybackRate: number,
|
||||||
|
setOutputPlaybackRate: (v: number) => void,
|
||||||
|
formatTimecode: FormatTimecode,
|
||||||
|
parseTimecode: ParseTimecode,
|
||||||
}) {
|
}) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { getSegColor } = useSegColors();
|
const { getSegColor } = useSegColors();
|
||||||
@ -188,7 +249,7 @@ function BottomBar({
|
|||||||
// ok this is a bit over-engineered but what the hell!
|
// ok this is a bit over-engineered but what the hell!
|
||||||
const loopSelectedSegmentsButtonStyle = useMemo(() => {
|
const loopSelectedSegmentsButtonStyle = useMemo(() => {
|
||||||
// cannot have less than 1 gradient element:
|
// cannot have less than 1 gradient element:
|
||||||
const selectedSegmentsSafe = (selectedSegments.length > 1 ? selectedSegments : [selectedSegments[0], selectedSegments[0]]).slice(0, 10);
|
const selectedSegmentsSafe = (selectedSegments.length > 1 ? selectedSegments : [selectedSegments[0]!, selectedSegments[0]!]).slice(0, 10);
|
||||||
|
|
||||||
const gradientColors = selectedSegmentsSafe.map((seg, i) => {
|
const gradientColors = selectedSegmentsSafe.map((seg, i) => {
|
||||||
const segColor = getSegColorRaw(seg);
|
const segColor = getSegColorRaw(seg);
|
||||||
@ -233,7 +294,7 @@ function BottomBar({
|
|||||||
const opacity = seg ? undefined : 0.5;
|
const opacity = seg ? undefined : 0.5;
|
||||||
const text = seg ? `${newIndex + 1}` : '-';
|
const text = seg ? `${newIndex + 1}` : '-';
|
||||||
const wide = text.length > 1;
|
const wide = text.length > 1;
|
||||||
const segButtonStyle = {
|
const segButtonStyle: CSSProperties = {
|
||||||
backgroundColor, opacity, padding: `6px ${wide ? 4 : 6}px`, borderRadius: 10, color: seg ? 'white' : undefined, fontSize: wide ? 12 : 14, width: 20, boxSizing: 'border-box', letterSpacing: -1, lineHeight: '10px', fontWeight: 'bold', margin: '0 6px',
|
backgroundColor, opacity, padding: `6px ${wide ? 4 : 6}px`, borderRadius: 10, color: seg ? 'white' : undefined, fontSize: wide ? 12 : 14, width: 20, boxSizing: 'border-box', letterSpacing: -1, lineHeight: '10px', fontWeight: 'bold', margin: '0 6px',
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -262,7 +323,7 @@ function BottomBar({
|
|||||||
{hasAudio && (
|
{hasAudio && (
|
||||||
<GiSoundWaves
|
<GiSoundWaves
|
||||||
size={24}
|
size={24}
|
||||||
style={{ padding: '0 .1em', color: ['big-waveform', 'waveform'].includes(waveformMode) ? primaryTextColor : undefined }}
|
style={{ padding: '0 .1em', color: waveformMode != null && ['big-waveform', 'waveform'].includes(waveformMode) ? primaryTextColor : undefined }}
|
||||||
role="button"
|
role="button"
|
||||||
title={t('Show waveform')}
|
title={t('Show waveform')}
|
||||||
onClick={() => toggleWaveformMode()}
|
onClick={() => toggleWaveformMode()}
|
@ -4,8 +4,8 @@ import Swal from '../swal';
|
|||||||
|
|
||||||
|
|
||||||
// eslint-disable-next-line import/prefer-default-export
|
// eslint-disable-next-line import/prefer-default-export
|
||||||
export async function askExtractFramesAsImages({ segmentsNumFrames, plural, fps }) {
|
export async function askExtractFramesAsImages({ segmentsNumFrames, plural, fps }: { segmentsNumFrames: number, plural: boolean, fps: number }) {
|
||||||
const { value: captureChoice } = await Swal.fire({
|
const { value: captureChoice } = await Swal.fire<string>({
|
||||||
text: i18n.t(plural ? 'Extract frames of the selected segments as images' : 'Extract frames of the current segment as images'),
|
text: i18n.t(plural ? 'Extract frames of the selected segments as images' : 'Extract frames of the current segment as images'),
|
||||||
icon: 'question',
|
icon: 'question',
|
||||||
input: 'radio',
|
input: 'radio',
|
||||||
@ -23,7 +23,7 @@ export async function askExtractFramesAsImages({ segmentsNumFrames, plural, fps
|
|||||||
|
|
||||||
if (!captureChoice) return undefined;
|
if (!captureChoice) return undefined;
|
||||||
|
|
||||||
let filter;
|
let filter: string | undefined;
|
||||||
let estimatedMaxNumFiles = segmentsNumFrames;
|
let estimatedMaxNumFiles = segmentsNumFrames;
|
||||||
|
|
||||||
if (captureChoice === 'thumbnailFilter') {
|
if (captureChoice === 'thumbnailFilter') {
|
||||||
@ -44,7 +44,7 @@ export async function askExtractFramesAsImages({ segmentsNumFrames, plural, fps
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (captureChoice === 'selectNthSec' || captureChoice === 'selectNthFrame') {
|
if (captureChoice === 'selectNthSec' || captureChoice === 'selectNthFrame') {
|
||||||
let nthFrame;
|
let nthFrame: number;
|
||||||
if (captureChoice === 'selectNthFrame') {
|
if (captureChoice === 'selectNthFrame') {
|
||||||
const { value } = await Swal.fire({
|
const { value } = await Swal.fire({
|
||||||
text: i18n.t('Capture exactly one image every nth frame'),
|
text: i18n.t('Capture exactly one image every nth frame'),
|
@ -1,6 +1,8 @@
|
|||||||
// This code is for future use (e.g. creating black video to fill in using same codec parameters)
|
// This code is for future use (e.g. creating black video to fill in using same codec parameters)
|
||||||
|
|
||||||
export function parseLevel(videoStream) {
|
import { FFprobeStream } from '../../../ffprobe';
|
||||||
|
|
||||||
|
export function parseLevel(videoStream: FFprobeStream) {
|
||||||
const { level: levelNumeric, codec_name: videoCodec } = videoStream;
|
const { level: levelNumeric, codec_name: videoCodec } = videoStream;
|
||||||
|
|
||||||
if (levelNumeric == null || Number.isNaN(levelNumeric)) return undefined;
|
if (levelNumeric == null || Number.isNaN(levelNumeric)) return undefined;
|
||||||
@ -9,7 +11,7 @@ export function parseLevel(videoStream) {
|
|||||||
if (levelNumeric === 9) return '1b'; // 13 is 1.3. That are all like that (20 is 2.0, etc) except 1b which is 9.
|
if (levelNumeric === 9) return '1b'; // 13 is 1.3. That are all like that (20 is 2.0, etc) except 1b which is 9.
|
||||||
|
|
||||||
let level = (levelNumeric / 10).toFixed(1); // https://stackoverflow.com/questions/42619191/what-does-level-mean-in-ffprobe-output
|
let level = (levelNumeric / 10).toFixed(1); // https://stackoverflow.com/questions/42619191/what-does-level-mean-in-ffprobe-output
|
||||||
if (level >= 0) {
|
if (parseFloat(level) >= 0) {
|
||||||
if (level.slice(-2) === '.0') level = level.slice(0, -2); // slice off .0
|
if (level.slice(-2) === '.0') level = level.slice(0, -2); // slice off .0
|
||||||
const validLevels = ['1', '1b', '1.1', '1.2', '1.3', '2', '2.1', '2.2', '3', '3.1', '3.2', '4', '4.1', '4.2', '5', '5.1', '5.2', '6', '6.1', '6.2']; // https://en.wikipedia.org/wiki/Advanced_Video_Coding#Levels
|
const validLevels = ['1', '1b', '1.1', '1.2', '1.3', '2', '2.1', '2.2', '3', '3.1', '3.2', '4', '4.1', '4.2', '5', '5.1', '5.2', '6', '6.1', '6.2']; // https://en.wikipedia.org/wiki/Advanced_Video_Coding#Levels
|
||||||
if (validLevels.includes(level)) return level;
|
if (validLevels.includes(level)) return level;
|
||||||
@ -17,7 +19,7 @@ export function parseLevel(videoStream) {
|
|||||||
} else if (videoCodec === 'hevc') {
|
} else if (videoCodec === 'hevc') {
|
||||||
// Note that on MacOS we don't use x265, but videotoolbox
|
// Note that on MacOS we don't use x265, but videotoolbox
|
||||||
let level = (levelNumeric / 30).toFixed(1); // https://stackoverflow.com/questions/69983131/whats-the-difference-between-ffprobe-level-and-h-264-level
|
let level = (levelNumeric / 30).toFixed(1); // https://stackoverflow.com/questions/69983131/whats-the-difference-between-ffprobe-level-and-h-264-level
|
||||||
if (level >= 0) {
|
if (parseFloat(level) >= 0) {
|
||||||
if (level.slice(-2) === '.0') level = level.slice(0, -2); // slice off .0
|
if (level.slice(-2) === '.0') level = level.slice(0, -2); // slice off .0
|
||||||
const validLevels = ['1', '2', '2.1', '3', '3.1', '4', '4.1', '5', '5.1', '5.2', '6', '6.1', '6.2']; // https://en.wikipedia.org/wiki/High_Efficiency_Video_Coding_tiers_and_levels
|
const validLevels = ['1', '2', '2.1', '3', '3.1', '4', '4.1', '5', '5.1', '5.2', '6', '6.1', '6.2']; // https://en.wikipedia.org/wiki/High_Efficiency_Video_Coding_tiers_and_levels
|
||||||
if (validLevels.includes(level)) return level;
|
if (validLevels.includes(level)) return level;
|
@ -230,7 +230,7 @@ function useSegments({ filePath, workingRef, setWorking, setCutProgress, videoSt
|
|||||||
setCutSegments(cutSegmentsNew);
|
setCutSegments(cutSegmentsNew);
|
||||||
}, [setCutSegments, cutSegments]);
|
}, [setCutSegments, cutSegments]);
|
||||||
|
|
||||||
const setCutTime = useCallback((type, time) => {
|
const setCutTime = useCallback((type: 'start' | 'end', time: number) => {
|
||||||
if (!isDurationValid(duration)) return;
|
if (!isDurationValid(duration)) return;
|
||||||
|
|
||||||
const currentSeg = currentCutSeg;
|
const currentSeg = currentCutSeg;
|
||||||
|
@ -17,11 +17,11 @@ export async function loadMifiLink() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function runStartupCheck({ ffmpeg }) {
|
export async function runStartupCheck({ ffmpeg }: { ffmpeg: boolean }) {
|
||||||
try {
|
try {
|
||||||
if (ffmpeg) await runFfmpegStartupCheck();
|
if (ffmpeg) await runFfmpegStartupCheck();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (['EPERM', 'EACCES'].includes(err.code)) {
|
if (err instanceof Error && 'code' in err && typeof err.code === 'string' && ['EPERM', 'EACCES'].includes(err.code)) {
|
||||||
toast.fire({
|
toast.fire({
|
||||||
timer: 30000,
|
timer: 30000,
|
||||||
icon: 'error',
|
icon: 'error',
|
@ -5,8 +5,6 @@
|
|||||||
"noEmit": true,
|
"noEmit": true,
|
||||||
|
|
||||||
"noImplicitAny": false, // todo
|
"noImplicitAny": false, // todo
|
||||||
"checkJs": false, // todo
|
|
||||||
"allowJs": true, // todo
|
|
||||||
},
|
},
|
||||||
"references": [
|
"references": [
|
||||||
{ "path": "./tsconfig.main.json" },
|
{ "path": "./tsconfig.main.json" },
|
||||||
|
2
types.ts
2
types.ts
@ -112,6 +112,8 @@ export interface ApiKeyboardActionRequest {
|
|||||||
|
|
||||||
export type Html5ifyMode = 'fastest' | 'fast-audio-remux' | 'fast-audio' | 'fast' | 'slow' | 'slow-audio' | 'slowest';
|
export type Html5ifyMode = 'fastest' | 'fast-audio-remux' | 'fast-audio' | 'fast' | 'slow' | 'slow-audio' | 'slowest';
|
||||||
|
|
||||||
|
export type WaveformMode = 'big-waveform' | 'waveform';
|
||||||
|
|
||||||
// This is the contract with the user, see https://github.com/mifi/lossless-cut/blob/master/expressions.md
|
// This is the contract with the user, see https://github.com/mifi/lossless-cut/blob/master/expressions.md
|
||||||
export interface ScopeSegment {
|
export interface ScopeSegment {
|
||||||
label: string,
|
label: string,
|
||||||
|
Loading…
Reference in New Issue
Block a user