mirror of
https://github.com/mifi/lossless-cut.git
synced 2024-11-22 18:32:34 +01:00
implement chapters viewing
also consolidate getFormatData and getAllStreams
This commit is contained in:
parent
6870cd7e85
commit
64c7811037
18
src/App.jsx
18
src/App.jsx
@ -50,7 +50,7 @@ import {
|
||||
defaultProcessedCodecTypes, getStreamFps, isCuttingStart, isCuttingEnd,
|
||||
readFileMeta, getFormatData, renderThumbnails as ffmpegRenderThumbnails,
|
||||
extractStreams, getAllStreams, runStartupCheck,
|
||||
isAudioDefinitelyNotSupported, isIphoneHevc, tryReadChaptersToEdl,
|
||||
isAudioDefinitelyNotSupported, isIphoneHevc, tryMapChaptersToEdl,
|
||||
getDuration, getTimecodeFromStreams, createChaptersFromSegments, extractSubtitleTrack,
|
||||
} from './ffmpeg';
|
||||
import { exportEdlFile, readEdlFile, saveLlcProject, loadLlcProject, readEdl } from './edlStore';
|
||||
@ -111,6 +111,7 @@ const App = memo(() => {
|
||||
const [duration, setDuration] = useState();
|
||||
const [fileFormat, setFileFormat] = useState();
|
||||
const [fileFormatData, setFileFormatData] = useState();
|
||||
const [chapters, setChapters] = useState();
|
||||
const [detectedFileFormat, setDetectedFileFormat] = useState();
|
||||
const [rotation, setRotation] = useState(360);
|
||||
const [cutProgress, setCutProgress] = useState();
|
||||
@ -659,14 +660,14 @@ const App = memo(() => {
|
||||
const outPath = getOutPath({ customOutDir: newCustomOutDir, filePath: firstPath, nameSuffix: `merged${ext}` });
|
||||
const outDir = getOutDir(customOutDir, firstPath);
|
||||
|
||||
let chapters;
|
||||
let chaptersFromSegments;
|
||||
if (segmentsToChapters) {
|
||||
const chapterNames = paths.map((path) => parsePath(path).name);
|
||||
chapters = await createChaptersFromSegments({ segmentPaths: paths, chapterNames });
|
||||
chaptersFromSegments = await createChaptersFromSegments({ segmentPaths: paths, chapterNames });
|
||||
}
|
||||
|
||||
// console.log('merge', paths);
|
||||
await ffmpegMergeFiles({ paths, outPath, outDir, allStreams, ffmpegExperimental, onProgress: setCutProgress, preserveMovData, movFastStart, preserveMetadataOnMerge, chapters });
|
||||
await ffmpegMergeFiles({ paths, outPath, outDir, allStreams, ffmpegExperimental, onProgress: setCutProgress, preserveMovData, movFastStart, preserveMetadataOnMerge, chapters: chaptersFromSegments });
|
||||
openDirToast({ icon: 'success', dirPath: outDir, text: i18n.t('Files merged!') });
|
||||
} catch (err) {
|
||||
if (isOutOfSpaceError(err)) {
|
||||
@ -851,6 +852,7 @@ const App = memo(() => {
|
||||
setCutEndTimeManual();
|
||||
setFileFormat();
|
||||
setFileFormatData();
|
||||
setChapters();
|
||||
setDetectedFileFormat();
|
||||
setRotation(360);
|
||||
setCutProgress();
|
||||
@ -1325,7 +1327,7 @@ const App = memo(() => {
|
||||
}
|
||||
|
||||
try {
|
||||
const { fileFormat: ff, formatData: fd, streams } = await readFileMeta(fp);
|
||||
const { fileFormat: ff, formatData: fd, chapters: ch, streams } = await readFileMeta(fp);
|
||||
// console.log('file meta read', ff);
|
||||
|
||||
if (!ff) throw new Error('Unable to determine file format');
|
||||
@ -1381,9 +1383,9 @@ const App = memo(() => {
|
||||
} else if (await exists(openedFileEdlPathOld)) {
|
||||
await loadEdlFile(openedFileEdlPathOld, 'csv');
|
||||
} else {
|
||||
const edl = await tryReadChaptersToEdl(fp);
|
||||
const edl = await tryMapChaptersToEdl(ch);
|
||||
if (edl.length > 0 && enableAskForImportChapters && (await askForImportChapters())) {
|
||||
console.log('Read chapters', edl);
|
||||
console.log('Convert chapters to segments', edl);
|
||||
loadCutSegments(edl);
|
||||
}
|
||||
}
|
||||
@ -1405,6 +1407,7 @@ const App = memo(() => {
|
||||
setFileFormat(outFormatLocked || ff);
|
||||
setDetectedFileFormat(ff);
|
||||
setFileFormatData(fd);
|
||||
setChapters(ch);
|
||||
|
||||
// This needs to be last, because it triggers <video> to load the video
|
||||
// If not, onVideoError might be triggered before setWorking() has been cleared.
|
||||
@ -2241,6 +2244,7 @@ const App = memo(() => {
|
||||
<StreamsSelector
|
||||
mainFilePath={filePath}
|
||||
mainFileFormatData={fileFormatData}
|
||||
mainFileChapters={chapters}
|
||||
externalFiles={externalStreamFiles}
|
||||
setExternalFiles={setExternalStreamFiles}
|
||||
showAddStreamSourceDialog={showAddStreamSourceDialog}
|
||||
|
@ -4,7 +4,7 @@ import { FaCheckCircle, FaPaperclip, FaVideo, FaVideoSlash, FaFileImport, FaVolu
|
||||
import { GoFileBinary } from 'react-icons/go';
|
||||
import { FiEdit, FiCheck, FiTrash } from 'react-icons/fi';
|
||||
import { MdSubtitles } from 'react-icons/md';
|
||||
import { Paragraph, TextInput, MoreIcon, Position, Popover, Menu, TrashIcon, EditIcon, InfoSignIcon, IconButton, Select, Heading, SortAscIcon, SortDescIcon, Dialog, Button, PlusIcon, Pane, ForkIcon, Alert } from 'evergreen-ui';
|
||||
import { BookIcon, Paragraph, TextInput, MoreIcon, Position, Popover, Menu, TrashIcon, EditIcon, InfoSignIcon, IconButton, Select, Heading, SortAscIcon, SortDescIcon, Dialog, Button, PlusIcon, Pane, ForkIcon, Alert } from 'evergreen-ui';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { askForMetadataKey, showJson5Dialog } from './dialogs';
|
||||
@ -280,7 +280,7 @@ const Stream = memo(({ filePath, stream, onToggle, batchSetCopyStreamIds, copySt
|
||||
);
|
||||
});
|
||||
|
||||
const FileHeading = ({ path, formatData, onTrashClick, onEditClick, setCopyAllStreams, onExtractAllStreamsPress }) => {
|
||||
const FileHeading = ({ path, formatData, chapters, onTrashClick, onEditClick, setCopyAllStreams, onExtractAllStreamsPress }) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
@ -290,6 +290,7 @@ const FileHeading = ({ path, formatData, onTrashClick, onEditClick, setCopyAllSt
|
||||
<div style={{ flexGrow: 1 }} />
|
||||
|
||||
<IconButton icon={InfoSignIcon} onClick={() => onInfoClick(formatData, t('File info'))} appearance="minimal" iconSize={18} />
|
||||
{chapters && chapters.length > 0 && <IconButton icon={BookIcon} onClick={() => onInfoClick(chapters, t('Chapters'))} appearance="minimal" iconSize={18} />}
|
||||
{onEditClick && <IconButton icon={EditIcon} onClick={onEditClick} appearance="minimal" iconSize={18} />}
|
||||
{onTrashClick && <IconButton icon={TrashIcon} onClick={onTrashClick} appearance="minimal" iconSize={18} />}
|
||||
<IconButton icon={() => <FaCheckCircle color="#52BD95" size={18} />} onClick={() => setCopyAllStreams(true)} appearance="minimal" />
|
||||
@ -323,7 +324,7 @@ const tableStyle = { fontSize: 14, width: '100%' };
|
||||
const fileStyle = { marginBottom: 20, padding: 5, minWidth: '100%', overflowX: 'auto' };
|
||||
|
||||
const StreamsSelector = memo(({
|
||||
mainFilePath, mainFileFormatData, streams: mainFileStreams, isCopyingStreamId, toggleCopyStreamId,
|
||||
mainFilePath, mainFileFormatData, streams: mainFileStreams, mainFileChapters, isCopyingStreamId, toggleCopyStreamId,
|
||||
setCopyStreamIdsForPath, onExtractStreamPress, onExtractAllStreamsPress, externalFiles, setExternalFiles,
|
||||
showAddStreamSourceDialog, shortestFlag, setShortestFlag, nonCopiedExtraStreams,
|
||||
AutoExportToggler, customTagsByFile, setCustomTagsByFile, customTagsByStreamId, setCustomTagsByStreamId,
|
||||
@ -373,7 +374,7 @@ const StreamsSelector = memo(({
|
||||
|
||||
<Pane elevation={1} style={fileStyle}>
|
||||
{/* We only support editing main file metadata for now */}
|
||||
<FileHeading path={mainFilePath} formatData={mainFileFormatData} onEditClick={() => setEditingFile(mainFilePath)} setCopyAllStreams={(enabled) => setCopyAllStreamsForPath(mainFilePath, enabled)} onExtractAllStreamsPress={onExtractAllStreamsPress} />
|
||||
<FileHeading path={mainFilePath} formatData={mainFileFormatData} chapters={mainFileChapters} onEditClick={() => setEditingFile(mainFilePath)} setCopyAllStreams={(enabled) => setCopyAllStreamsForPath(mainFilePath, enabled)} onExtractAllStreamsPress={onExtractAllStreamsPress} />
|
||||
<table style={tableStyle}>
|
||||
<Thead />
|
||||
|
||||
|
@ -169,10 +169,9 @@ export function findNearestKeyFrameTime({ frames, time, direction, fps }) {
|
||||
return nearestFrame.time;
|
||||
}
|
||||
|
||||
export async function tryReadChaptersToEdl(filePath) {
|
||||
export async function tryMapChaptersToEdl(chapters) {
|
||||
try {
|
||||
const { stdout } = await runFfprobe(['-i', filePath, '-show_chapters', '-print_format', 'json', '-hide_banner']);
|
||||
return JSON.parse(stdout).chapters.map((chapter) => {
|
||||
return chapters.map((chapter) => {
|
||||
const start = parseFloat(chapter.start_time);
|
||||
const end = parseFloat(chapter.end_time);
|
||||
if (Number.isNaN(start) || Number.isNaN(end)) return undefined;
|
||||
@ -272,14 +271,16 @@ export async function getAllStreams(filePath) {
|
||||
|
||||
export async function readFileMeta(filePath) {
|
||||
try {
|
||||
const [formatData, allStreamsResponse] = await Promise.all([
|
||||
getFormatData(filePath),
|
||||
getAllStreams(filePath),
|
||||
const { stdout } = await runFfprobe([
|
||||
'-of', 'json', '-show_chapters', '-show_format', '-show_entries', 'stream', '-i', filePath, '-hide_banner',
|
||||
]);
|
||||
|
||||
const { streams, format: formatData, chapters } = JSON.parse(stdout);
|
||||
|
||||
const fileFormat = await getDefaultOutFormat(filePath, formatData);
|
||||
const { streams } = allStreamsResponse;
|
||||
// console.log(streams, formatData, fileFormat);
|
||||
return { formatData, fileFormat, streams };
|
||||
|
||||
return { formatData, fileFormat, streams, chapters };
|
||||
} catch (err) {
|
||||
// Windows will throw error with code ENOENT if format detection fails.
|
||||
if (err.exitCode === 1 || (isWindows && err.code === 'ENOENT')) {
|
||||
|
Loading…
Reference in New Issue
Block a user