mirror of
https://github.com/mifi/lossless-cut.git
synced 2024-11-25 11:43:17 +01:00
Add cutlist import option
This commit is contained in:
parent
36454f5e49
commit
3cc997bcb2
@ -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() {
|
||||
|
@ -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();
|
||||
|
@ -70,6 +70,72 @@ 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(/[\r\n]+/);
|
||||
let section:string|null|undefined = null;
|
||||
lines.forEach(function(line){
|
||||
if(regex.comment.test(line)){
|
||||
return;
|
||||
}else if(regex.param.test(line)){
|
||||
const match = line.match(regex.param) || [];
|
||||
if(match[1]){
|
||||
if(section){
|
||||
iniValue[section][match[1]] = match[2];
|
||||
}else{
|
||||
iniValue[match[1]] = match[2];
|
||||
}
|
||||
}
|
||||
}else if(regex.section.test(line)){
|
||||
const match = line.match(regex.section) || [];
|
||||
if(match[1]){
|
||||
iniValue[match[1]] = {};
|
||||
section = match[1];
|
||||
}
|
||||
}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: start,
|
||||
end: end,
|
||||
name: `Cut ${i}`,
|
||||
});
|
||||
} else {
|
||||
found = false;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
|
||||
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*$/);
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user