1
0
mirror of https://github.com/mifi/lossless-cut.git synced 2024-11-25 03:33:14 +01:00

Merge pull request #1952 from LOK-Soft/add-cutlist-import

Add cutlist (OTR) import option
This commit is contained in:
Mikael Finstad 2024-04-10 22:28:49 +02:00 committed by GitHub
commit 079562e845
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 137 additions and 3 deletions

View File

@ -69,6 +69,12 @@ export default ({ app, mainWindow, newVersion, isStoreBuild }: {
mainWindow.webContents.send('importEdlFile', 'csv-frames');
},
},
{
label: esc(t('Cutlist')),
click() {
mainWindow.webContents.send('importEdlFile', 'cutlist');
},
},
{
label: esc(t('EDL (MPlayer)')),
click() {

View File

@ -4,7 +4,7 @@ import { fileURLToPath } from 'url';
import { it, describe, expect } from 'vitest';
import { parseSrt, formatSrt, parseYouTube, formatYouTube, parseMplayerEdl, parseXmeml, parseFcpXml, parseCsv, parseCsvTime, getFrameValParser, formatCsvFrames, getFrameCountRaw, parsePbf, parseDvAnalyzerSummaryTxt } from './edlFormats';
import { parseSrt, formatSrt, parseYouTube, formatYouTube, parseMplayerEdl, parseXmeml, parseFcpXml, parseCsv, parseCsvTime, getFrameValParser, formatCsvFrames, getFrameCountRaw, parsePbf, parseDvAnalyzerSummaryTxt, parseCutlist } from './edlFormats';
// eslint-disable-next-line no-underscore-dangle
const __dirname = dirname(fileURLToPath(import.meta.url));
@ -242,6 +242,62 @@ it('parses csv with timestamps', async () => {
]);
});
const cutlistStr = `
[General]
Application=SomeApplication.exe
Version=0.0.0.1
FramesPerSecond=25
IntendedCutApplicationName=SomeApplication
IntendedCutApplication=SomeApplication.exe
VDUseSmartRendering=1
IntendedCutApplicationVersion=1.7.8
comment1=The following parts of the movie will be kept, the rest will be cut out.
comment2=All values are given in seconds.
NoOfCuts=2
ApplyToFile=Some_File_Name.avi
OriginalFileSizeBytes=123456
[Cut0]
Start=849.12
StartFrame=21228
Duration=1881.84
DurationFrames=47046
[Cut1]
Start=3147.72
StartFrame=78693
Duration=944.6
DurationFrames=23615
[Info]
Author=AuthorName
RatingByAuthor=0
EPGError=0
ActualContent=
MissingBeginning=0
MissingEnding=0
MissingVideo=0
MissingAudio=0
OtherError=0
OtherErrorDescription=
SuggestedMovieName=
UserComment=cutted with XXXX
[Meta]
CutlistId=12345
GeneratedOn=1900-01-01 00:00:01
GeneratedBy=cutlist v0.0.0
`;
it('parses cutlist', async () => {
const parsed = await parseCutlist(cutlistStr);
expect(parsed).toEqual([
{ end: 2730.96, name: 'Cut 0', start: 849.12 },
{ end: 4092.32, name: 'Cut 1', start: 3147.72 },
]);
});
it('parses pbf', async () => {
expect(parsePbf(await readFixtureBinary('test1.pbf'))).toMatchSnapshot();
expect(parsePbf(await readFixtureBinary('test2.pbf'))).toMatchSnapshot();

View File

@ -70,6 +70,73 @@ export async function parseCsv(csvStr: string, parseTimeFn: (a: string) => numbe
return mapped;
}
export async function parseCutlist(clStr: string) {
// first parse INI-File into "iniValue" object
const regex = {
section: /^\s*\[\s*([^\]]*)\s*]\s*$/,
param: /^\s*([^=]+?)\s*=\s*(.*?)\s*$/,
comment: /^\s*;.*$/,
};
const iniValue = {};
const lines = clStr.split(/[\n\r]+/);
let section:string|null|undefined = null;
lines.forEach((line) => {
if (regex.comment.test(line)) {
return;
} if (regex.param.test(line)) {
const match = line.match(regex.param) || [];
const [, m1, m2] = match;
if (m1) {
if (section) {
iniValue[section][m1] = m2;
} else {
iniValue[m1] = m2;
}
}
} else if (regex.section.test(line)) {
const match = line.match(regex.section) || [];
const [, m1] = match;
if (m1) {
iniValue[m1] = {};
section = m1;
}
} else if (line.length === 0 && section) {
section = null;
}
});
// end INI-File parse
let found = true;
let i = 0;
const cutArr:{start:number, end:number, name:string}[] = [];
while (found) {
const cutEntry = iniValue[`Cut${i}`];
if (cutEntry) {
const start = parseFloat(cutEntry.Start);
const end = Math.round((start + parseFloat(cutEntry.Duration) + Number.EPSILON) * 100) / 100;
cutArr.push({
start,
end,
name: `Cut ${i}`,
});
} else {
found = false;
}
i += 1;
}
if (!cutArr.every(({ start, end }) => (
(start === undefined || !Number.isNaN(start))
&& (end === undefined || !Number.isNaN(end))
))) {
console.log(cutArr);
throw new Error(i18n.t('Invalid start or end value. Must contain a number of seconds'));
}
return cutArr;
}
export async function parseMplayerEdl(text: string) {
const allRows = text.split('\n').flatMap((line) => {
const match = line.match(/^\s*(\S+)\s+(\S+)\s+([0-3])\s*$/);

View File

@ -2,7 +2,7 @@ import JSON5 from 'json5';
import i18n from 'i18next';
import type { parse as CueParse } from 'cue-parser';
import { parseSrt, formatSrt, parseCuesheet, parseXmeml, parseFcpXml, parseCsv, parsePbf, parseMplayerEdl, formatCsvHuman, formatTsv, formatCsvFrames, formatCsvSeconds, parseCsvTime, getFrameValParser, parseDvAnalyzerSummaryTxt } from './edlFormats';
import { parseSrt, formatSrt, parseCuesheet, parseXmeml, parseFcpXml, parseCsv, parseCutlist, parsePbf, parseMplayerEdl, formatCsvHuman, formatTsv, formatCsvFrames, formatCsvSeconds, parseCsvTime, getFrameValParser, parseDvAnalyzerSummaryTxt } from './edlFormats';
import { askForYouTubeInput, showOpenDialog } from './dialogs';
import { getOutPath } from './util';
import { EdlExportType, EdlFileType, EdlImportType, Segment, StateSegment } from './types';
@ -22,6 +22,10 @@ export async function loadCsvFrames(path: string, fps?: number) {
return parseCsv(await readFile(path, 'utf8'), getFrameValParser(fps));
}
export async function loadCutlistSeconds(path: string) {
return parseCutlist(await readFile(path, 'utf8'));
}
export async function loadXmeml(path: string) {
return parseXmeml(await readFile(path, 'utf8'));
}
@ -96,6 +100,7 @@ export async function loadLlcProject(path: string) {
export async function readEdlFile({ type, path, fps }: { type: EdlFileType, path: string, fps?: number | undefined }) {
if (type === 'csv') return loadCsvSeconds(path);
if (type === 'csv-frames') return loadCsvFrames(path, fps);
if (type === 'cutlist') return loadCutlistSeconds(path);
if (type === 'xmeml') return loadXmeml(path);
if (type === 'fcpxml') return loadFcpXml(path);
if (type === 'dv-analyzer-summary-txt') return loadDvAnalyzerSummaryTxt(path);

View File

@ -61,7 +61,7 @@ export interface InverseCutSegment {
export type PlaybackMode = 'loop-segment-start-end' | 'loop-segment' | 'play-segment-once' | 'loop-selected-segments';
export type EdlFileType = 'csv' | 'csv-frames' | 'xmeml' | 'fcpxml' | 'dv-analyzer-summary-txt' | 'cue' | 'pbf' | 'mplayer' | 'srt' | 'llc';
export type EdlFileType = 'csv' | 'csv-frames' | 'cutlist' | 'xmeml' | 'fcpxml' | 'dv-analyzer-summary-txt' | 'cue' | 'pbf' | 'mplayer' | 'srt' | 'llc';
export type EdlImportType = 'youtube' | EdlFileType;