diff --git a/package.json b/package.json
index a7ac0e74..61b6a28c 100644
--- a/package.json
+++ b/package.json
@@ -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",
diff --git a/src/renderer/src/App.tsx b/src/renderer/src/App.tsx
index c789445a..9eb09b72 100644
--- a/src/renderer/src/App.tsx
+++ b/src/renderer/src/App.tsx
@@ -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}
/>
)}
diff --git a/src/renderer/src/SegmentList.tsx b/src/renderer/src/SegmentList.tsx
index 3ef1f8cc..6ea31bff 100644
--- a/src/renderer/src/SegmentList.tsx
+++ b/src/renderer/src/SegmentList.tsx
@@ -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()}
- {timeStr}
+ {timeStr}
- {'name' in seg && seg.name && {seg.name}}
+ {'name' in seg && seg.name && {seg.name}}
{Object.entries(tags).map(([name, value]) => (
- {name}:{value}
+ {name}:{value}
))}
{duration != null && (
<>
-
+
{t('Duration')} {formatTimecode({ seconds: duration, shorten: true })}
-
-
{{ durationMsFormatted: Math.floor(duration * 1000) }} ms, {{ frameCount: (duration && getFrameCount(duration)) ?? '?' }} frames
+
+ {{ durationMsFormatted: Math.floor(duration * 1000) }} ms
+ , {{ frameCount: (duration && getFrameCount(duration)) ?? '?' }} frames
+ {estimatedSize != null && (
+
+ , ~{prettyBytes(estimatedSize, { space: false, maximumFractionDigits: 1, minimumFractionDigits: 0 })}
+
+ )}
+
>
)}
@@ -321,6 +332,7 @@ function SegmentList({
setEditingSegmentTags,
setEditingSegmentTagsSegmentIndex,
onEditSegmentTags,
+ getSegEstimatedSize,
}: {
width: number,
formatTimecode: FormatTimecode,
@@ -361,6 +373,7 @@ function SegmentList({
setEditingSegmentTags: Dispatch
>,
setEditingSegmentTagsSegmentIndex: Dispatch>,
onEditSegmentTags: (index: number) => void,
+ getSegEstimatedSize: UseSegments['getSegEstimatedSize'],
}) {
const { t } = useTranslation();
const { getSegColor, nextSegColorIndex } = useSegColors();
@@ -474,7 +487,7 @@ function SegmentList({
)}
-
+
{t('Segments total:')}
{formatTimecode({ seconds: segmentsTotal })}
@@ -586,6 +599,7 @@ function SegmentList({
onSelectAllMarkers={onSelectAllMarkers}
onInvertSelectedSegments={onInvertSelectedSegments}
onDuplicateSegmentClick={onDuplicateSegmentClick}
+ getSegEstimatedSize={getSegEstimatedSize}
/>
);
}
diff --git a/src/renderer/src/hooks/useSegments.tsx b/src/renderer/src/hooks/useSegments.tsx
index 2c491646..8f30af17 100644
--- a/src/renderer/src/hooks/useSegments.tsx
+++ b/src/renderer/src/hooks/useSegments.tsx
@@ -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
) => {
+ 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,
diff --git a/src/renderer/src/util.ts b/src/renderer/src/util.ts
index 9b3bf73b..568de531 100644
--- a/src/renderer/src/util.ts
+++ b/src/renderer/src/util.ts
@@ -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);
diff --git a/yarn.lock b/yarn.lock
index d0799d70..4e0fbcc8 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -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