implement simple support for FCPXML

pull/1233/head
Mikael Finstad 4 years ago
parent dbcd598e82
commit 2c12477b04
No known key found for this signature in database
GPG Key ID: 25AB36E3E81CBC26

@ -78,6 +78,12 @@ module.exports = (app, mainWindow, newVersion) => {
mainWindow.webContents.send('importEdlFile', 'xmeml');
},
},
{
label: i18n.t('Final Cut Pro FCPX / FCPXML'),
click() {
mainWindow.webContents.send('importEdlFile', 'fcpxml');
},
},
{
label: i18n.t('CUE sheet file'),
click() {

@ -2067,7 +2067,7 @@ const App = memo(() => {
setWorking(i18n.t('Loading file'));
// Import segments for for already opened file
const edlFormats = { csv: 'csv', pbf: 'pbf', edl: 'mplayer', cue: 'cue', xml: 'xmeml' };
const edlFormats = { csv: 'csv', pbf: 'pbf', edl: 'mplayer', cue: 'cue', xml: 'xmeml', fcpxml: 'fcpxml' };
const matchingExt = Object.keys(edlFormats).find((ext) => filePathLowerCase.endsWith(`.${ext}`));
if (matchingExt) {
if (!checkFileOpened()) return;

@ -1,5 +1,702 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`parses fcpxml 1.9 1`] = `
Array [
Object {
"end": 13.84,
"start": 0,
},
Object {
"end": 25.04,
"start": 13.84,
},
Object {
"end": 25.2,
"start": 25.04,
},
Object {
"end": 55.44,
"start": 25.2,
},
Object {
"end": 68.96,
"start": 55.44,
},
Object {
"end": 81.83999999999999,
"start": 68.96,
},
Object {
"end": 95.12,
"start": 81.84,
},
Object {
"end": 95.16000000000001,
"start": 95.12,
},
Object {
"end": 95.28,
"start": 95.16,
},
Object {
"end": 112.72,
"start": 95.28,
},
Object {
"end": 140.4,
"start": 112.72,
},
Object {
"end": 147.36,
"start": 140.4,
},
Object {
"end": 170.88000000000002,
"start": 147.36,
},
Object {
"end": 176.72,
"start": 170.88,
},
Object {
"end": 196.07999999999998,
"start": 176.72,
},
Object {
"end": 232.96,
"start": 196.08,
},
Object {
"end": 265.92,
"start": 232.96,
},
Object {
"end": 281.76,
"start": 265.92,
},
Object {
"end": 308.96,
"start": 281.76,
},
Object {
"end": 321.68,
"start": 308.96,
},
Object {
"end": 353.2,
"start": 321.68,
},
Object {
"end": 376.08,
"start": 353.2,
},
Object {
"end": 405.68,
"start": 376.08,
},
Object {
"end": 412.16,
"start": 405.68,
},
Object {
"end": 423.28000000000003,
"start": 412.16,
},
Object {
"end": 430.71999999999997,
"start": 423.28,
},
Object {
"end": 439.76000000000005,
"start": 430.72,
},
Object {
"end": 456.64,
"start": 439.76,
},
Object {
"end": 492.71999999999997,
"start": 456.64,
},
Object {
"end": 511.44000000000005,
"start": 492.72,
},
Object {
"end": 522.24,
"start": 511.44,
},
Object {
"end": 524.32,
"start": 522.24,
},
Object {
"end": 543.2800000000001,
"start": 524.32,
},
Object {
"end": 571.04,
"start": 543.28,
},
Object {
"end": 576.3199999999999,
"start": 571.04,
},
Object {
"end": 612.48,
"start": 576.32,
},
Object {
"end": 626.16,
"start": 612.48,
},
Object {
"end": 626.24,
"start": 626.16,
},
Object {
"end": 665.04,
"start": 626.24,
},
Object {
"end": 694.0799999999999,
"start": 665.04,
},
Object {
"end": 762.96,
"start": 694.08,
},
Object {
"end": 784.8000000000001,
"start": 762.96,
},
Object {
"end": 804.56,
"start": 784.8,
},
Object {
"end": 813.52,
"start": 804.56,
},
Object {
"end": 831.16,
"start": 813.52,
},
Object {
"end": 928.72,
"start": 831.16,
},
Object {
"end": 941.0400000000001,
"start": 928.72,
},
Object {
"end": 946.3199999999999,
"start": 941.04,
},
Object {
"end": 968.8000000000001,
"start": 946.32,
},
Object {
"end": 982.16,
"start": 968.8,
},
Object {
"end": 1111.2,
"start": 982.16,
},
Object {
"end": 1117.04,
"start": 1111.2,
},
Object {
"end": 1142.6,
"start": 1117.04,
},
Object {
"end": 1142.6399999999999,
"start": 1142.6,
},
Object {
"end": 1142.68,
"start": 1142.64,
},
Object {
"end": 1180.88,
"start": 1142.68,
},
Object {
"end": 1193.2,
"start": 1180.88,
},
Object {
"end": 1212.72,
"start": 1193.2,
},
Object {
"end": 1233.68,
"start": 1212.72,
},
Object {
"end": 1263.76,
"start": 1233.68,
},
Object {
"end": 1288.56,
"start": 1263.76,
},
Object {
"end": 1294.12,
"start": 1288.56,
},
Object {
"end": 1294.1599999999999,
"start": 1294.12,
},
Object {
"end": 1294.2,
"start": 1294.16,
},
Object {
"end": 1294.24,
"start": 1294.2,
},
Object {
"end": 1294.28,
"start": 1294.24,
},
Object {
"end": 1294.36,
"start": 1294.28,
},
Object {
"end": 1294.4399999999998,
"start": 1294.36,
},
Object {
"end": 1294.48,
"start": 1294.44,
},
Object {
"end": 1306.84,
"start": 1294.48,
},
Object {
"end": 1306.8799999999999,
"start": 1306.84,
},
Object {
"end": 1306.92,
"start": 1306.88,
},
Object {
"end": 1306.96,
"start": 1306.92,
},
Object {
"end": 1339.04,
"start": 1306.96,
},
Object {
"end": 1360.24,
"start": 1339.04,
},
Object {
"end": 1365.92,
"start": 1360.24,
},
Object {
"end": 1382.88,
"start": 1365.92,
},
Object {
"end": 1402.0800000000002,
"start": 1382.88,
},
Object {
"end": 1417.52,
"start": 1402.08,
},
Object {
"end": 1428.16,
"start": 1417.52,
},
Object {
"end": 1480.0800000000002,
"start": 1428.16,
},
Object {
"end": 1496.24,
"start": 1480.08,
},
Object {
"end": 1526.64,
"start": 1496.24,
},
Object {
"end": 1532.24,
"start": 1526.64,
},
Object {
"end": 1542.24,
"start": 1532.24,
},
Object {
"end": 1554.32,
"start": 1542.24,
},
Object {
"end": 1565.12,
"start": 1554.32,
},
Object {
"end": 1595.4399999999998,
"start": 1565.12,
},
Object {
"end": 1602.3200000000002,
"start": 1595.44,
},
Object {
"end": 1612.32,
"start": 1602.32,
},
Object {
"end": 1648.24,
"start": 1612.32,
},
Object {
"end": 1667.76,
"start": 1648.24,
},
Object {
"end": 1691.92,
"start": 1667.76,
},
Object {
"end": 1705.76,
"start": 1691.92,
},
Object {
"end": 1711.12,
"start": 1705.76,
},
Object {
"end": 1727,
"start": 1711.12,
},
Object {
"end": 1750,
"start": 1727,
},
Object {
"end": 1778,
"start": 1750,
},
Object {
"end": 1787.6,
"start": 1778,
},
Object {
"end": 1794.9599999999998,
"start": 1787.6,
},
Object {
"end": 1836.32,
"start": 1794.96,
},
Object {
"end": 1905.84,
"start": 1836.32,
},
Object {
"end": 1905.8799999999999,
"start": 1905.84,
},
Object {
"end": 1909.1200000000001,
"start": 1905.88,
},
Object {
"end": 1936.56,
"start": 1909.12,
},
Object {
"end": 1941.84,
"start": 1936.56,
},
Object {
"end": 1954.48,
"start": 1941.84,
},
Object {
"end": 1968.24,
"start": 1954.48,
},
Object {
"end": 1972,
"start": 1968.24,
},
Object {
"end": 1982.56,
"start": 1972,
},
Object {
"end": 1994.56,
"start": 1982.56,
},
Object {
"end": 1997.12,
"start": 1994.56,
},
Object {
"end": 2164.3599999999997,
"start": 1997.12,
},
Object {
"end": 2180.4,
"start": 2164.36,
},
Object {
"end": 2199.28,
"start": 2180.4,
},
Object {
"end": 2212,
"start": 2199.28,
},
Object {
"end": 2237.76,
"start": 2212,
},
Object {
"end": 2316.88,
"start": 2237.76,
},
Object {
"end": 2332.2400000000002,
"start": 2316.88,
},
Object {
"end": 2412.64,
"start": 2332.24,
},
Object {
"end": 2672.56,
"start": 2412.64,
},
Object {
"end": 2723.24,
"start": 2672.56,
},
Object {
"end": 2723.2799999999997,
"start": 2723.24,
},
Object {
"end": 2723.36,
"start": 2723.28,
},
Object {
"end": 2723.4,
"start": 2723.36,
},
Object {
"end": 2725.04,
"start": 2723.4,
},
Object {
"end": 2725.08,
"start": 2725.04,
},
Object {
"end": 2725.12,
"start": 2725.08,
},
Object {
"end": 2725.44,
"start": 2725.12,
},
Object {
"end": 2787.2000000000003,
"start": 2725.44,
},
Object {
"end": 2806.56,
"start": 2787.2,
},
Object {
"end": 2814.56,
"start": 2806.56,
},
Object {
"end": 2852.4,
"start": 2814.56,
},
Object {
"end": 2864.8,
"start": 2852.4,
},
Object {
"end": 2875.28,
"start": 2864.8,
},
Object {
"end": 2886.6400000000003,
"start": 2875.28,
},
Object {
"end": 2898.3199999999997,
"start": 2886.64,
},
Object {
"end": 2942.2400000000002,
"start": 2898.32,
},
Object {
"end": 2996,
"start": 2942.24,
},
Object {
"end": 3000.88,
"start": 2996,
},
Object {
"end": 3026.6400000000003,
"start": 3000.88,
},
Object {
"end": 3055.92,
"start": 3026.64,
},
Object {
"end": 3062.7200000000003,
"start": 3055.92,
},
Object {
"end": 3088.8799999999997,
"start": 3062.72,
},
Object {
"end": 3095.76,
"start": 3088.88,
},
Object {
"end": 3111.36,
"start": 3095.76,
},
Object {
"end": 3118.1600000000003,
"start": 3111.36,
},
Object {
"end": 3125.8399999999997,
"start": 3118.16,
},
Object {
"end": 3131.28,
"start": 3125.84,
},
Object {
"end": 3139.28,
"start": 3131.28,
},
Object {
"end": 3174.1600000000003,
"start": 3139.28,
},
Object {
"end": 3177.68,
"start": 3174.16,
},
Object {
"end": 3177.72,
"start": 3177.68,
},
Object {
"end": 3193.12,
"start": 3177.72,
},
Object {
"end": 3218.24,
"start": 3193.12,
},
Object {
"end": 3235.7599999999998,
"start": 3218.24,
},
Object {
"end": 3250,
"start": 3235.76,
},
Object {
"end": 3263.76,
"start": 3250,
},
Object {
"end": 3267.28,
"start": 3263.76,
},
Object {
"end": 3272.96,
"start": 3267.28,
},
Object {
"end": 3335.92,
"start": 3272.96,
},
Object {
"end": 3499.76,
"start": 3335.92,
},
Object {
"end": 3640.7200000000003,
"start": 3499.76,
},
Object {
"end": 3655.9599999999996,
"start": 3640.72,
},
Object {
"end": 3656.04,
"start": 3655.96,
},
Object {
"end": 3668.16,
"start": 3656.04,
},
Object {
"end": 3744.7999999999997,
"start": 3668.16,
},
Object {
"end": 3770.8,
"start": 3744.8,
},
Object {
"end": 3783.6000000000004,
"start": 3770.8,
},
Object {
"end": 3793.68,
"start": 3783.6,
},
Object {
"end": 3814.64,
"start": 3793.68,
},
Object {
"end": 3835.04,
"start": 3814.64,
},
Object {
"end": 3843.92,
"start": 3835.04,
},
]
`;
exports[`parses pbf 1`] = `
Array [
Object {

@ -155,6 +155,25 @@ export function parseXmeml(xmlStr) {
return mainTrack.clipitem.map((item) => ({ start: item.in / item.rate.timebase, end: item.out / item.rate.timebase }));
}
export function parseFcpXml(xmlStr) {
const xml = new XMLParser({ ignoreAttributes: false }).parse(xmlStr);
const { fcpxml } = xml;
if (!fcpxml) throw Error('Root element <fcpxml> not found in file');
function parseTime(str) {
const match = str.match(/([0-9]+)\/([0-9]+)s/);
if (!match) throw new Error('Invalid attribute');
return parseInt(match[1], 10) / parseInt(match[2], 10);
}
return fcpxml.library.event.project.sequence.spine['asset-clip'].map((assetClip) => {
const start = parseTime(assetClip['@_start']);
const duration = parseTime(assetClip['@_duration']);
const end = start + duration;
return { start, end };
});
}
export function parseYouTube(str) {
function parseLine(match) {
if (!match) return undefined;

@ -1,7 +1,7 @@
import fs from 'fs/promises';
import { join } from 'path';
import { parseYouTube, formatYouTube, parseMplayerEdl, parseXmeml, parseCsv, getTimeFromFrameNum, formatCsvFrames, getFrameCountRaw, parsePbf } from './edlFormats';
import { parseYouTube, formatYouTube, parseMplayerEdl, parseXmeml, parseFcpXml, parseCsv, getTimeFromFrameNum, formatCsvFrames, getFrameCountRaw, parsePbf } from './edlFormats';
const readFixture = async (name, encoding = 'utf-8') => fs.readFile(join(__dirname, 'fixtures', name), encoding);
@ -164,6 +164,11 @@ it('parses xmeml - with multiple tracks', async () => {
expect(await parseXmeml(await readFixture('Final Cut Pro XMEML 3.xml'))).toMatchSnapshot();
});
// see https://github.com/mifi/lossless-cut/issues/1195
it('parses fcpxml 1.9', async () => {
expect(await parseFcpXml(await readFixture('FCPXML_1_9.fcpxml'))).toMatchSnapshot();
});
// https://github.com/mifi/lossless-cut/issues/1024
const csvFramesStr = `\
0,155,EP106_SQ010_SH0010

@ -1,7 +1,7 @@
import JSON5 from 'json5';
import i18n from 'i18next';
import { parseCuesheet, parseXmeml, parseCsv, parsePbf, parseMplayerEdl, formatCsvHuman, formatTsv, formatCsvFrames, formatCsvSeconds, getTimeFromFrameNum } from './edlFormats';
import { parseCuesheet, parseXmeml, parseFcpXml, parseCsv, parsePbf, parseMplayerEdl, formatCsvHuman, formatTsv, formatCsvFrames, formatCsvSeconds, getTimeFromFrameNum } from './edlFormats';
import { askForYouTubeInput } from './dialogs';
const fs = window.require('fs-extra');
@ -24,6 +24,10 @@ export async function loadXmeml(path) {
return parseXmeml(await fs.readFile(path, 'utf-8'));
}
export async function loadFcpXml(path) {
return parseFcpXml(await fs.readFile(path, 'utf-8'));
}
export async function loadPbf(path) {
return parsePbf(await fs.readFile(path));
}
@ -70,6 +74,7 @@ export async function readEdlFile({ type, path, fps }) {
if (type === 'csv') return loadCsvSeconds(path);
if (type === 'csv-frames') return loadCsvFrames(path, fps);
if (type === 'xmeml') return loadXmeml(path);
if (type === 'fcpxml') return loadFcpXml(path);
if (type === 'cue') return loadCue(path);
if (type === 'pbf') return loadPbf(path);
if (type === 'mplayer') return loadMplayerEdl(path);
@ -86,6 +91,7 @@ export async function askForEdlImport({ type, fps }) {
let filters;
if (type === 'csv' || type === 'csv-frames') filters = [{ name: i18n.t('CSV files'), extensions: ['csv'] }];
else if (type === 'xmeml') filters = [{ name: i18n.t('XML files'), extensions: ['xml'] }];
else if (type === 'fcpxml') filters = [{ name: i18n.t('FCPXML files'), extensions: ['fcpxml'] }];
else if (type === 'cue') filters = [{ name: i18n.t('CUE files'), extensions: ['cue'] }];
else if (type === 'pbf') filters = [{ name: i18n.t('PBF files'), extensions: ['pbf'] }];
else if (type === 'mplayer') filters = [{ name: i18n.t('MPlayer EDL'), extensions: ['*'] }];

File diff suppressed because it is too large Load Diff
Loading…
Cancel
Save