kopia lustrzana https://github.com/mifi/lossless-cut
320 wiersze
8.2 KiB
TypeScript
320 wiersze
8.2 KiB
TypeScript
import fs from 'fs/promises';
|
|
import { join, dirname } from 'path';
|
|
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, parseCutlist } from './edlFormats';
|
|
|
|
// eslint-disable-next-line no-underscore-dangle
|
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
|
|
const readFixture = async (name: string, encoding: BufferEncoding = 'utf8') => fs.readFile(join(__dirname, 'fixtures', name), encoding);
|
|
const readFixtureBinary = async (name: string) => fs.readFile(join(__dirname, 'fixtures', name), null);
|
|
|
|
const expectYouTube1 = [
|
|
{ start: 0, end: 1, name: '00:01 Test 1' },
|
|
{ start: 1, end: 2, name: '"Test 2":' },
|
|
{ start: 2, end: 4, name: '00:57 double' },
|
|
{ start: 4, end: 5, name: '' },
|
|
{ start: 5, end: 6, name: '' },
|
|
{ start: 6, end: 6.01, name: '6 label' },
|
|
{ start: 6.01, end: 61, name: '6.01 label' },
|
|
{ start: 61, end: 61.012, name: 'Test 3' },
|
|
{ start: 61.012, end: 62.012, name: 'Test 6' },
|
|
{ start: 62.012, end: 132, name: 'Test 7' },
|
|
{ start: 132, end: 3661.012, name: 'Integer' },
|
|
{ start: 3661.012, end: 10074, name: 'Test - 4' },
|
|
{ start: 10074, end: undefined, name: 'Short - hour and hyphen' },
|
|
];
|
|
|
|
describe('parseYouTube', () => {
|
|
it('parses different cases', () => {
|
|
const str = `
|
|
Jump to chapters:
|
|
0:00 00:01 Test 1
|
|
00:01 "Test 2":
|
|
00:02 00:57 double
|
|
00:01:01 Test 3
|
|
01:01:01.012 Test - 4
|
|
00:01:01.012 Test 5
|
|
01:01.012 Test 6
|
|
:01:02.012 Test 7
|
|
2:47:54 - Short - hour and hyphen
|
|
00:57:01.0123 Invalid 2
|
|
00:57:01. Invalid 3
|
|
01:15:: Invalid 4
|
|
0132 Integer
|
|
00:03
|
|
00:04
|
|
00:05
|
|
6 6 label
|
|
6.01 6.01 label
|
|
`;
|
|
const edl = parseYouTube(str);
|
|
expect(edl).toEqual(expectYouTube1);
|
|
});
|
|
|
|
it('eol', () => {
|
|
const str = ' 00:00 Test 1\n00:01 Test 2';
|
|
const edl = parseYouTube(str);
|
|
expect(edl).toEqual([
|
|
{ start: 0, end: 1, name: 'Test 1' },
|
|
{ start: 1, end: undefined, name: 'Test 2' },
|
|
]);
|
|
});
|
|
|
|
it('colon after time', () => {
|
|
const str = ' 00:00: Test 1';
|
|
const edl = parseYouTube(str);
|
|
expect(edl).toEqual([
|
|
{ start: 0, end: undefined, name: 'Test 1' },
|
|
]);
|
|
});
|
|
});
|
|
|
|
it('formatYouTube', () => {
|
|
expect(formatYouTube([
|
|
{ start: 1, end: 2, name: 'Label 🎉' },
|
|
{ start: 3, end: 5 },
|
|
{ start: 10000, end: 10001, name: '' },
|
|
]).split('\n')).toEqual([
|
|
'0:01 Label 🎉',
|
|
'0:03',
|
|
'2:46:40',
|
|
]);
|
|
expect(formatYouTube([
|
|
{ start: 0, end: 100 },
|
|
]).split('\n')).toEqual([
|
|
'0:00',
|
|
]);
|
|
});
|
|
|
|
it('formatYouTube 2', () => {
|
|
expect(formatYouTube(expectYouTube1).split('\n')).toEqual([
|
|
'0:00 00:01 Test 1',
|
|
'0:01 "Test 2":',
|
|
'0:02 00:57 double',
|
|
'0:04',
|
|
'0:05',
|
|
'0:06 6 label',
|
|
'0:06 6.01 label',
|
|
'1:01 Test 3',
|
|
'1:01 Test 6',
|
|
'1:02 Test 7',
|
|
'2:12 Integer',
|
|
'1:01:01 Test - 4',
|
|
'2:47:54 Short - hour and hyphen',
|
|
]);
|
|
});
|
|
|
|
// https://kodi.wiki/view/Edit_decision_list
|
|
// http://www.mplayerhq.hu/DOCS/HTML/en/edl.html
|
|
it('parseMplayerEdl', async () => {
|
|
// TODO support more durations (frames and timestamps):
|
|
/*
|
|
const str = `\
|
|
5.3 7.1 0
|
|
15 16.7 1
|
|
7:00 13:42 3
|
|
1 4:15.3 2
|
|
12:00.1 2
|
|
`;
|
|
const str = `\
|
|
#127 #170 0
|
|
#360 #400 1
|
|
#10080 #19728 3
|
|
#1 #6127 2
|
|
#17282 2
|
|
`;
|
|
*/
|
|
|
|
const str = await readFixture('mplayer.edl');
|
|
|
|
expect(await parseMplayerEdl(str)).toEqual([
|
|
{ start: 0,
|
|
end: 5.3,
|
|
name: 'Cut',
|
|
tags: { mplayerEdlType: '0' },
|
|
},
|
|
{ start: 7.1,
|
|
end: undefined,
|
|
name: 'Cut',
|
|
tags: { mplayerEdlType: '0' },
|
|
},
|
|
{
|
|
end: 16.7,
|
|
start: 15,
|
|
name: 'Mute',
|
|
tags: { mplayerEdlType: '1' },
|
|
},
|
|
{
|
|
end: 255.3,
|
|
start: 1,
|
|
name: 'Scene Marker',
|
|
tags: { mplayerEdlType: '2' },
|
|
},
|
|
{
|
|
end: 822,
|
|
start: 420,
|
|
name: 'Commercial Break',
|
|
tags: { mplayerEdlType: '3' },
|
|
},
|
|
]);
|
|
});
|
|
|
|
it('parseMplayerEdl, starting at 0', async () => {
|
|
const str2 = ' 0 1.1 0\n';
|
|
|
|
expect(await parseMplayerEdl(str2)).toEqual([{
|
|
start: 1.1,
|
|
end: undefined,
|
|
name: 'Cut',
|
|
tags: {
|
|
mplayerEdlType: '0',
|
|
},
|
|
}]);
|
|
});
|
|
|
|
it('parses xmeml 1', async () => {
|
|
expect(await parseXmeml(await readFixture('Final Cut Pro XMEML.xml'))).toMatchSnapshot();
|
|
});
|
|
|
|
it('parses xmeml 2', async () => {
|
|
expect(await parseXmeml(await readFixture('Final Cut Pro XMEML 2.xml'))).toMatchSnapshot();
|
|
});
|
|
|
|
// see https://github.com/mifi/lossless-cut/issues/1195
|
|
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
|
|
156,251,EP106_SQ010_SH0020
|
|
252,687,EP106_SQ010_SH0030
|
|
688,747,EP106_SQ020_SH0010
|
|
`;
|
|
|
|
it('parses csv with frames', async () => {
|
|
const fps = 30;
|
|
const parsed = await parseCsv(csvFramesStr, getFrameValParser(fps));
|
|
|
|
expect(parsed).toEqual([
|
|
{ end: 5.166666666666667, name: 'EP106_SQ010_SH0010', start: 0 },
|
|
{ end: 8.366666666666667, name: 'EP106_SQ010_SH0020', start: 5.2 },
|
|
{ end: 22.9, name: 'EP106_SQ010_SH0030', start: 8.4 },
|
|
{ end: 24.9, name: 'EP106_SQ020_SH0010', start: 22.933333333333334 },
|
|
]);
|
|
|
|
const formatted = await formatCsvFrames({
|
|
cutSegments: parsed,
|
|
getFrameCount: (sec) => getFrameCountRaw(fps, sec),
|
|
});
|
|
expect(formatted).toEqual(csvFramesStr);
|
|
});
|
|
|
|
const csvTimestampStr = `\
|
|
00:01:54.612,00:03:09.053,A
|
|
00:05:00.448,00:07:56.194,B
|
|
00:09:27.075,00:11:44.264,C
|
|
0,1,D
|
|
1.01,1.99,E
|
|
0:2,0:3,F
|
|
`;
|
|
|
|
it('parses csv with timestamps', async () => {
|
|
const parsed = await parseCsv(csvTimestampStr, parseCsvTime);
|
|
|
|
expect(parsed).toEqual([
|
|
{ end: 189.053, name: 'A', start: 114.612 },
|
|
{ end: 476.194, name: 'B', start: 300.448 },
|
|
{ end: 704.264, name: 'C', start: 567.075 },
|
|
{ end: 1, name: 'D', start: 0 },
|
|
{ end: 1.99, name: 'E', start: 1.01 },
|
|
{ start: 2, name: 'F', end: 3 },
|
|
]);
|
|
});
|
|
|
|
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();
|
|
expect(parsePbf(await readFixtureBinary('test3.pbf'))).toMatchSnapshot();
|
|
expect(parsePbf(await readFixtureBinary('potplayer bookmark format utf16le issue 867.pbf'))).toMatchSnapshot();
|
|
});
|
|
|
|
it('parses srt', async () => {
|
|
expect(parseSrt(await readFixture('sample.srt'))).toMatchSnapshot();
|
|
});
|
|
|
|
it('format srt', async () => {
|
|
expect(formatSrt(parseSrt(await readFixture('sample.srt')))).toMatchSnapshot();
|
|
});
|
|
|
|
// https://github.com/mifi/lossless-cut/issues/1664
|
|
it('parses DV Analyzer Summary.txt', async () => {
|
|
expect(parseDvAnalyzerSummaryTxt(await readFixture('DV Analyzer Summary.txt', 'utf8'))).toMatchSnapshot();
|
|
});
|