show estimated segment file size

closes #2630
pull/2640/head
Mikael Finstad 8 months ago
parent 251120f048
commit 251120dd0c
No known key found for this signature in database
GPG Key ID: 25AB36E3E81CBC26

@ -109,7 +109,7 @@
"nanoid": "^5.0.9",
"p-map": "^5.5.0",
"p-retry": "^6.2.0",
"pretty-bytes": "^6.0.0",
"pretty-bytes": "^7.1.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-i18next": "^16.2.4",

@ -325,7 +325,7 @@ function App() {
}, [isFileOpened]);
const {
cutSegments, cutSegmentsHistory, createSegmentsFromKeyframes, shuffleSegments, detectBlackScenes, detectSilentScenes, detectSceneChanges, removeSegment, invertAllSegments, fillSegmentsGaps, combineOverlappingSegments, combineSelectedSegments, shiftAllSegmentTimes, alignSegmentTimesToKeyframes, updateSegOrder, updateSegOrders, reorderSegsByStartTime, addSegment, setCutStart, setCutEnd, labelSegment, splitCurrentSegment, focusSegmentAtCursor, selectSegmentsAtCursor, createNumSegments, createFixedDurationSegments, createFixedByteSizedSegments, createRandomSegments, haveInvalidSegs, currentSegIndexSafe, currentCutSeg, inverseCutSegments, clearSegments, clearSegColorCounter, loadCutSegments, setCutTime, setCurrentSegIndex, labelSelectedSegments, deselectAllSegments, selectAllSegments, selectOnlyCurrentSegment, toggleCurrentSegmentSelected, invertSelectedSegments, removeSelectedSegments, selectSegmentsByLabel, selectSegmentsByExpr, selectAllMarkers, mutateSegmentsByExpr, toggleSegmentSelected, selectOnlySegment, selectedSegments, segmentsOrInverse, segmentsToExport, duplicateCurrentSegment, duplicateSegment, updateSegAtIndex, findSegmentsAtCursor, maybeCreateFullLengthSegment, currentCutSegOrWholeTimeline, segColorCounter,
cutSegments, cutSegmentsHistory, createSegmentsFromKeyframes, shuffleSegments, detectBlackScenes, detectSilentScenes, detectSceneChanges, removeSegment, invertAllSegments, fillSegmentsGaps, combineOverlappingSegments, combineSelectedSegments, shiftAllSegmentTimes, alignSegmentTimesToKeyframes, updateSegOrder, updateSegOrders, reorderSegsByStartTime, addSegment, setCutStart, setCutEnd, labelSegment, splitCurrentSegment, focusSegmentAtCursor, selectSegmentsAtCursor, createNumSegments, createFixedDurationSegments, createFixedByteSizedSegments, createRandomSegments, getSegEstimatedSize, haveInvalidSegs, currentSegIndexSafe, currentCutSeg, inverseCutSegments, clearSegments, clearSegColorCounter, loadCutSegments, setCutTime, setCurrentSegIndex, labelSelectedSegments, deselectAllSegments, selectAllSegments, selectOnlyCurrentSegment, toggleCurrentSegmentSelected, invertSelectedSegments, removeSelectedSegments, selectSegmentsByLabel, selectSegmentsByExpr, selectAllMarkers, mutateSegmentsByExpr, toggleSegmentSelected, selectOnlySegment, selectedSegments, segmentsOrInverse, segmentsToExport, duplicateCurrentSegment, duplicateSegment, updateSegAtIndex, findSegmentsAtCursor, maybeCreateFullLengthSegment, currentCutSegOrWholeTimeline, segColorCounter,
} = useSegments({ filePath, workingRef, setWorking, setProgress, videoStream: activeVideoStream, fileDuration, getRelevantTime, maxLabelLength, checkFileOpened, invertCutSegments, segmentsToChaptersOnly, timecodePlaceholder, parseTimecode, appendFfmpegCommandLog, fileDurationNonZero, mainFileMeta, seekAbs, activeVideoStreamIndex, activeAudioStreamIndexes, handleError, showGenericDialog });
const { getEdlFilePath, projectFileSavePath, getProjectFileSavePath } = useSegmentsAutoSave({ autoSaveProjectFile, storeProjectInWorkingDir, filePath, customOutDir, cutSegments });
@ -2541,6 +2541,7 @@ function App() {
setEditingSegmentTags={setEditingSegmentTags}
setEditingSegmentTagsSegmentIndex={setEditingSegmentTagsSegmentIndex}
onEditSegmentTags={onEditSegmentTags}
getSegEstimatedSize={getSegEstimatedSize}
/>
)}
</AnimatePresence>

@ -9,6 +9,7 @@ import { restrictToVerticalAxis } from '@dnd-kit/modifiers';
import { useVirtualizer } from '@tanstack/react-virtual';
import { CSS } from '@dnd-kit/utilities';
import invariant from 'tiny-invariant';
import prettyBytes from 'pretty-bytes';
import useContextMenu from './hooks/useContextMenu';
import useUserSettings from './hooks/useUserSettings';
@ -62,6 +63,7 @@ const Segment = memo(({
onExtractSelectedSegmentsFramesAsImages,
onInvertSelectedSegments,
onDuplicateSegmentClick,
getSegEstimatedSize,
}: {
seg: StateSegment | InverseCutSegment,
index: number,
@ -93,6 +95,7 @@ const Segment = memo(({
onExtractSelectedSegmentsFramesAsImages: () => void,
onInvertSelectedSegments: UseSegments['invertSelectedSegments'],
onDuplicateSegmentClick: UseSegments['duplicateSegment'],
getSegEstimatedSize: UseSegments['getSegEstimatedSize'],
}) => {
const { invertCutSegments, darkMode } = useUserSettings();
const { t } = useTranslation();
@ -149,6 +152,7 @@ const Segment = memo(({
useContextMenu(ref, contextMenuTemplate);
const duration = useMemo(() => (seg.end == null ? undefined : seg.end - seg.start), [seg]);
const estimatedSize = useMemo(() => getSegEstimatedSize(seg), [getSegEstimatedSize, seg]);
const timeStr = useMemo(() => (
seg.end == null
@ -249,25 +253,32 @@ const Segment = memo(({
{...sortable.listeners}
role="button"
tabIndex={-1}
style={{ cursor, color: 'var(--gray-12)', marginBottom: duration != null ? 3 : undefined, display: 'flex', alignItems: 'center', height: 16 }}
style={{ cursor, color: 'var(--gray-12)', marginBottom: duration != null ? '.1em' : undefined, display: 'flex', alignItems: 'center', height: '1em' }}
onClick={handleDraggableClick}
>
{renderNumber()}
<span style={{ cursor, fontSize: Math.min(310 / timeStr.length, 12), whiteSpace: 'nowrap' }}>{timeStr}</span>
<span style={{ cursor, fontSize: `${Math.min(1, 26 / timeStr.length) * 0.75}em`, whiteSpace: 'nowrap' }}>{timeStr}</span>
</div>
{'name' in seg && seg.name && <span style={{ fontSize: 12, color: primaryTextColor, marginRight: '.3em' }}>{seg.name}</span>}
{'name' in seg && seg.name && <span style={{ fontSize: '.75em', color: primaryTextColor, marginRight: '.3em' }}>{seg.name}</span>}
{Object.entries(tags).map(([name, value]) => (
<span style={{ fontSize: 11, backgroundColor: 'var(--gray-5)', color: 'var(--gray-12)', borderRadius: '.4em', padding: '0 .2em', marginRight: '.1em' }} key={name}>{name}:<b>{value}</b></span>
<span style={{ fontSize: '.7em', backgroundColor: 'var(--gray-5)', color: 'var(--gray-12)', borderRadius: '.4em', padding: '0 .2em', marginRight: '.1em' }} key={name}>{name}:<b>{value}</b></span>
))}
{duration != null && (
<>
<div style={{ fontSize: 13 }}>
<div style={{ fontSize: '.75em' }}>
{t('Duration')} {formatTimecode({ seconds: duration, shorten: true })}
</div>
<div style={{ fontSize: 12 }}>
<Trans>{{ durationMsFormatted: Math.floor(duration * 1000) }} ms, {{ frameCount: (duration && getFrameCount(duration)) ?? '?' }} frames</Trans>
<div style={{ fontSize: '.75em' }}>
<Trans>{{ durationMsFormatted: Math.floor(duration * 1000) }} ms</Trans>
<span>, <Trans>{{ frameCount: (duration && getFrameCount(duration)) ?? '?' }} frames</Trans></span>
{estimatedSize != null && (
<span style={{ fontSize: '.9em' }}>
, ~{prettyBytes(estimatedSize, { space: false, maximumFractionDigits: 1, minimumFractionDigits: 0 })}
</span>
)}
</div>
</>
)}
@ -321,6 +332,7 @@ function SegmentList({
setEditingSegmentTags,
setEditingSegmentTagsSegmentIndex,
onEditSegmentTags,
getSegEstimatedSize,
}: {
width: number,
formatTimecode: FormatTimecode,
@ -361,6 +373,7 @@ function SegmentList({
setEditingSegmentTags: Dispatch<SetStateAction<SegmentTags | undefined>>,
setEditingSegmentTagsSegmentIndex: Dispatch<SetStateAction<number | undefined>>,
onEditSegmentTags: (index: number) => void,
getSegEstimatedSize: UseSegments['getSegEstimatedSize'],
}) {
const { t } = useTranslation();
const { getSegColor, nextSegColorIndex } = useSegColors();
@ -474,7 +487,7 @@ function SegmentList({
)}
</div>
<div style={{ padding: '5px 10px', boxSizing: 'border-box', borderBottom: '1px solid var(--gray-6)', borderTop: '1px solid var(--gray-6)', display: 'flex', justifyContent: 'space-between', fontSize: 13 }}>
<div style={{ padding: '5px 10px', boxSizing: 'border-box', borderBottom: '1px solid var(--gray-6)', borderTop: '1px solid var(--gray-6)', display: 'flex', justifyContent: 'space-between', fontSize: '.8em' }}>
<div>{t('Segments total:')}</div>
<div>{formatTimecode({ seconds: segmentsTotal })}</div>
</div>
@ -586,6 +599,7 @@ function SegmentList({
onSelectAllMarkers={onSelectAllMarkers}
onInvertSelectedSegments={onInvertSelectedSegments}
onDuplicateSegmentClick={onDuplicateSegmentClick}
getSegEstimatedSize={getSegEstimatedSize}
/>
);
}

@ -9,7 +9,7 @@ import { FaLink } from 'react-icons/fa';
import TextInput from '../components/TextInput';
import { detectSceneChanges as ffmpegDetectSceneChanges, readFrames, mapTimesToSegments, findKeyframeNearTime } from '../ffmpeg';
import { shuffleArray } from '../util';
import { getFileSize, shuffleArray } from '../util';
import { errorToast } from '../swal';
import { createNumSegments as createNumSegmentsDialog, createFixedByteSixedSegments as createFixedByteSixedSegmentsDialog, createRandomSegments as createRandomSegmentsDialog, labelSegmentDialog, askForShiftSegments, askForAlignSegments, selectSegmentsByLabelDialog, askForSegmentDuration, toastError } from '../dialogs';
import { createSegment, sortSegments, invertSegments, combineOverlappingSegments as combineOverlappingSegments2, combineSelectedSegments as combineSelectedSegments2, isDurationValid, addSegmentColorIndex, filterNonMarkers, makeDurationSegments, isInitialSegment } from '../segments';
@ -725,13 +725,22 @@ function useSegments({ filePath, workingRef, setWorking, setProgress, videoStrea
const createFixedByteSizedSegments = useCallback(async () => {
if (!checkFileOpened() || !isDurationValid(fileDuration)) return;
const fileSize = mainFileMeta && parseInt(mainFileMeta.formatData.size, 10);
invariant(fileSize != null && !Number.isNaN(fileSize));
invariant(mainFileMeta != null);
const fileSize = getFileSize(mainFileMeta.formatData);
invariant(fileSize != null);
const segmentDuration = await createFixedByteSixedSegmentsDialog({ fileDuration, fileSize });
if (segmentDuration == null) return;
loadCutSegments({ segments: makeDurationSegments(segmentDuration, fileDuration), append: true, clampDuration: fileDuration });
}, [checkFileOpened, fileDuration, loadCutSegments, mainFileMeta]);
const getSegEstimatedSize = useCallback((segment: Pick<StateSegment, 'start' | 'end'>) => {
if (mainFileMeta == null || !isDurationValid(fileDuration) || segment.end == null) return undefined;
const fileSize = getFileSize(mainFileMeta.formatData);
if (fileSize == null) return undefined;
const segDuration = segment.end - segment.start;
return Math.round((segDuration / fileDuration) * fileSize);
}, [fileDuration, mainFileMeta]);
const createRandomSegments = useCallback(async () => {
if (!checkFileOpened() || currentCutSegOrWholeTimeline.duration <= 0) return;
const segments = await createRandomSegmentsDialog(currentCutSegOrWholeTimeline.duration);
@ -978,6 +987,7 @@ function useSegments({ filePath, workingRef, setWorking, setProgress, videoStrea
createFixedDurationSegments,
createFixedByteSizedSegments,
createRandomSegments,
getSegEstimatedSize,
haveInvalidSegs,
currentSegIndexSafe,
currentCutSeg,

@ -11,6 +11,7 @@ import { ffmpegExtractWindow } from './util/constants';
import { appName } from '../../main/common';
import { Html5ifyMode } from '../../common/types';
import { UserFacingError } from '../errors';
import { FFprobeFormat } from '../../common/ffprobe';
const { dirname, parse: parsePath, join, extname, isAbsolute, resolve, basename } = window.require('path');
const fsExtra = window.require('fs-extra');
@ -398,6 +399,12 @@ export function getImportProjectType(filePath: string) {
return edlFormatForExtension[matchingExt];
}
export function getFileSize(formatData: FFprobeFormat) {
const fileSize = parseInt(formatData.size, 10);
if (Number.isNaN(fileSize)) return undefined;
return fileSize;
}
export const calcShouldShowWaveform = (zoomedDuration: number | undefined) => (zoomedDuration != null && zoomedDuration < ffmpegExtractWindow * 8);
export const calcShouldShowKeyframes = (zoomedDuration: number | undefined) => (zoomedDuration != null && zoomedDuration < ffmpegExtractWindow * 8);

@ -9152,7 +9152,7 @@ __metadata:
nanoid: "npm:^5.0.9"
p-map: "npm:^5.5.0"
p-retry: "npm:^6.2.0"
pretty-bytes: "npm:^6.0.0"
pretty-bytes: "npm:^7.1.0"
react: "npm:^18.2.0"
react-dom: "npm:^18.2.0"
react-i18next: "npm:^16.2.4"
@ -10815,10 +10815,10 @@ __metadata:
languageName: node
linkType: hard
"pretty-bytes@npm:^6.0.0":
version: 6.0.0
resolution: "pretty-bytes@npm:6.0.0"
checksum: 10/0bb9f95e617236404b29a8392c6efd82d65805f622f5e809ecd70068102be857d4e3276c86d2a32fa2ef851cc29472e380945dab7bec83ec79bd57a19a10faf7
"pretty-bytes@npm:^7.1.0":
version: 7.1.0
resolution: "pretty-bytes@npm:7.1.0"
checksum: 10/75fb34fba44596dc69983039fce9288f3b247b513aba2d6dfe74727505fe43199fcd7a1cee95dbd5be56a431d30fbdb4d24d3a62b9cf22bb63fa706b81c2e540
languageName: node
linkType: hard

Loading…
Cancel
Save