Skip to content

Commit

Permalink
Add XMEML importer #458
Browse files Browse the repository at this point in the history
  • Loading branch information
mifi committed Nov 18, 2020
1 parent 704a20c commit 7e59ee6
Show file tree
Hide file tree
Showing 5 changed files with 53 additions and 16 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
13 changes: 12 additions & 1 deletion public/menu.js
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down
42 changes: 27 additions & 15 deletions src/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -1105,25 +1105,32 @@ const App = memo(() => {
return getOutPath(cod, fp, `html5ified-${type}.${ext}`);
}, []);

const loadEdlFile = useCallback(async (edlPath) => {
try {
const storedEdl = await edlStoreLoad(edlPath);
const allRowsValid = storedEdl
.every(row => row.start === undefined || row.end === undefined || row.start < row.end);
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'));
}
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]);

cutSegmentsHistory.go(0);
setCutSegments(storedEdl.map(createSegment));
const loadEdlFile = useCallback(async (edlPath, type = 'csv') => {
try {
let storedEdl;
if (type === 'csv') storedEdl = await edlStoreLoad(edlPath);
else if (type === 'xmeml') storedEdl = await loadXmeml(edlPath);

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 });
Expand Down Expand Up @@ -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() {
Expand Down
8 changes: 8 additions & 0 deletions src/edlStore.js
Original file line number Diff line number Diff line change
@@ -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');
Expand Down Expand Up @@ -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]);
Expand Down
5 changes: 5 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down

0 comments on commit 7e59ee6

Please sign in to comment.