kopia lustrzana https://github.com/mifi/lossless-cut
Add XMEML importer #458
rodzic
704a20ce73
commit
7e59ee6757
|
@ -85,6 +85,7 @@
|
|||
"electron-store": "^5.1.1",
|
||||
"electron-unhandled": "^3.0.2",
|
||||
"execa": "^4.0.0",
|
||||
"fast-xml-parser": "^3.17.4",
|
||||
"ffmpeg-static": "^4.2.1",
|
||||
"ffprobe-static": "^3.0.0",
|
||||
"file-type": "^12.4.0",
|
||||
|
|
|
@ -32,9 +32,20 @@ module.exports = (app, mainWindow, newVersion) => {
|
|||
{
|
||||
label: 'Load project (CSV)',
|
||||
click() {
|
||||
mainWindow.webContents.send('importEdlFile');
|
||||
mainWindow.webContents.send('importEdlFile', 'csv');
|
||||
},
|
||||
},
|
||||
{
|
||||
label: 'Import project',
|
||||
submenu: [
|
||||
{
|
||||
label: 'DaVinci Resolve / Final Cut Pro XML',
|
||||
click() {
|
||||
mainWindow.webContents.send('importEdlFile', 'xmeml');
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'Save project (CSV)',
|
||||
click() {
|
||||
|
|
42
src/App.jsx
42
src/App.jsx
|
@ -43,7 +43,7 @@ import {
|
|||
readFrames, renderWaveformPng, html5ifyDummy, cutMultiple, extractStreams, autoMergeSegments, getAllStreams,
|
||||
findNearestKeyFrameTime, html5ify as ffmpegHtml5ify, isStreamThumbnail, isAudioSupported, isIphoneHevc,
|
||||
} from './ffmpeg';
|
||||
import { save as edlStoreSave, load as edlStoreLoad } from './edlStore';
|
||||
import { save as edlStoreSave, load as edlStoreLoad, loadXmeml } from './edlStore';
|
||||
import {
|
||||
getOutPath, formatDuration, toast, errorToast, showFfmpegFail, setFileNameTitle,
|
||||
promptTimeOffset, generateColor, getOutDir, withBlur, checkDirWriteAccess, dirExists, askForOutDir,
|
||||
|
@ -1105,25 +1105,32 @@ const App = memo(() => {
|
|||
return getOutPath(cod, fp, `html5ified-${type}.${ext}`);
|
||||
}, []);
|
||||
|
||||
const loadEdlFile = useCallback(async (edlPath) => {
|
||||
const loadCutSegments = useCallback((edl) => {
|
||||
const allRowsValid = edl
|
||||
.every(row => row.start === undefined || row.end === undefined || row.start < row.end);
|
||||
|
||||
if (!allRowsValid) {
|
||||
throw new Error(i18n.t('Invalid start or end values for one or more segments'));
|
||||
}
|
||||
|
||||
cutSegmentsHistory.go(0);
|
||||
setCutSegments(edl.map(createSegment));
|
||||
}, [cutSegmentsHistory, setCutSegments]);
|
||||
|
||||
const loadEdlFile = useCallback(async (edlPath, type = 'csv') => {
|
||||
try {
|
||||
const storedEdl = await edlStoreLoad(edlPath);
|
||||
const allRowsValid = storedEdl
|
||||
.every(row => row.start === undefined || row.end === undefined || row.start < row.end);
|
||||
let storedEdl;
|
||||
if (type === 'csv') storedEdl = await edlStoreLoad(edlPath);
|
||||
else if (type === 'xmeml') storedEdl = await loadXmeml(edlPath);
|
||||
|
||||
if (!allRowsValid) {
|
||||
throw new Error(i18n.t('Invalid start or end values for one or more segments'));
|
||||
}
|
||||
|
||||
cutSegmentsHistory.go(0);
|
||||
setCutSegments(storedEdl.map(createSegment));
|
||||
loadCutSegments(storedEdl);
|
||||
} catch (err) {
|
||||
if (err.code !== 'ENOENT') {
|
||||
console.error('EDL load failed', err);
|
||||
errorToast(`${i18n.t('Failed to load project file')} (${err.message})`);
|
||||
}
|
||||
}
|
||||
}, [cutSegmentsHistory, setCutSegments]);
|
||||
}, [loadCutSegments]);
|
||||
|
||||
const load = useCallback(async ({ filePath: fp, customOutDir: cod, html5FriendlyPathRequested, dummyVideoPathRequested }) => {
|
||||
console.log('Load', { fp, cod, html5FriendlyPathRequested, dummyVideoPathRequested });
|
||||
|
@ -1525,14 +1532,19 @@ const App = memo(() => {
|
|||
}
|
||||
}
|
||||
|
||||
async function importEdlFile() {
|
||||
async function importEdlFile(e, type) {
|
||||
if (!isFileOpened) {
|
||||
toast.fire({ icon: 'info', title: i18n.t('You need to open a media file first') });
|
||||
return;
|
||||
}
|
||||
const { canceled, filePaths } = await dialog.showOpenDialog({ properties: ['openFile'], filters: [{ name: i18n.t('CSV files'), extensions: ['csv'] }] });
|
||||
|
||||
let filters;
|
||||
if (type === 'csv') filters = [{ name: i18n.t('CSV files'), extensions: ['csv'] }];
|
||||
else if (type === 'xmeml') filters = [{ name: i18n.t('XML files'), extensions: ['xml'] }];
|
||||
|
||||
const { canceled, filePaths } = await dialog.showOpenDialog({ properties: ['openFile'], filters });
|
||||
if (canceled || filePaths.length < 1) return;
|
||||
await loadEdlFile(filePaths[0]);
|
||||
await loadEdlFile(filePaths[0], type);
|
||||
}
|
||||
|
||||
function openHelp() {
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import parse from 'csv-parse';
|
||||
import stringify from 'csv-stringify';
|
||||
import i18n from 'i18next';
|
||||
import fastXmlParser from 'fast-xml-parser';
|
||||
|
||||
const fs = window.require('fs-extra');
|
||||
const { promisify } = window.require('util');
|
||||
|
@ -32,6 +33,13 @@ export async function load(path) {
|
|||
return mapped;
|
||||
}
|
||||
|
||||
// https://developer.apple.com/library/archive/documentation/AppleApplications/Reference/FinalCutPro_XML/VersionsoftheInterchangeFormat/VersionsoftheInterchangeFormat.html
|
||||
export async function loadXmeml(path) {
|
||||
const xml = fastXmlParser.parse(await fs.readFile(path, 'utf-8'));
|
||||
// TODO maybe support media.audio also?
|
||||
return xml.xmeml.project.children.sequence.media.video.track.clipitem.map((item) => ({ start: item.start / item.rate.timebase, end: item.end / item.rate.timebase }));
|
||||
}
|
||||
|
||||
export async function save(path, cutSegments) {
|
||||
console.log('Saving', path);
|
||||
const rows = cutSegments.map(({ start, end, name }) => [start, end, name]);
|
||||
|
|
|
@ -5461,6 +5461,11 @@ fast-shallow-equal@^1.0.0:
|
|||
resolved "https://registry.yarnpkg.com/fast-shallow-equal/-/fast-shallow-equal-1.0.0.tgz#d4dcaf6472440dcefa6f88b98e3251e27f25628b"
|
||||
integrity sha512-HPtaa38cPgWvaCFmRNhlc6NG7pv6NUHqjPgVAkWGoB9mQMwYB27/K0CvOM5Czy+qpT3e8XJ6Q4aPAnzpNpzNaw==
|
||||
|
||||
fast-xml-parser@^3.17.4:
|
||||
version "3.17.4"
|
||||
resolved "https://registry.yarnpkg.com/fast-xml-parser/-/fast-xml-parser-3.17.4.tgz#d668495fb3e4bbcf7970f3c24ac0019d82e76477"
|
||||
integrity sha512-qudnQuyYBgnvzf5Lj/yxMcf4L9NcVWihXJg7CiU1L+oUCq8MUnFEfH2/nXR/W5uq+yvUN1h7z6s7vs2v1WkL1A==
|
||||
|
||||
fastest-stable-stringify@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/fastest-stable-stringify/-/fastest-stable-stringify-1.0.1.tgz#9122d406d4c9d98bea644a6b6853d5874b87b028"
|
||||
|
|
Ładowanie…
Reference in New Issue