1
0
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:
Mikael Finstad 2022-02-13 13:01:13 +08:00
parent 6870cd7e85
commit 64c7811037
No known key found for this signature in database
GPG Key ID: 25AB36E3E81CBC26
3 changed files with 25 additions and 19 deletions

View File

@ -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}

View File

@ -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 />

View File

@ -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')) {