|
|
|
|
@ -1,18 +1,15 @@
|
|
|
|
|
import { XMLParser } from 'fast-xml-parser';
|
|
|
|
|
import i18n from 'i18next';
|
|
|
|
|
|
|
|
|
|
import csvParse from 'csv-parse/lib/browser';
|
|
|
|
|
import csvStringify from 'csv-stringify/lib/browser';
|
|
|
|
|
import pify from 'pify';
|
|
|
|
|
import { parse as csvParse } from 'csv-parse/browser/esm/sync';
|
|
|
|
|
import { stringify as csvStringify } from 'csv-stringify/browser/esm/sync';
|
|
|
|
|
import sortBy from 'lodash/sortBy';
|
|
|
|
|
import type { ICueSheet, ITrack } from 'cue-parser/lib/types';
|
|
|
|
|
|
|
|
|
|
import { formatDuration } from './util/duration';
|
|
|
|
|
import { invertSegments, sortSegments } from './segments';
|
|
|
|
|
import { Segment, SegmentBase } from './types';
|
|
|
|
|
|
|
|
|
|
const csvParseAsync = pify(csvParse);
|
|
|
|
|
const csvStringifyAsync = pify(csvStringify);
|
|
|
|
|
import { GetFrameCount, Segment, SegmentBase } from './types';
|
|
|
|
|
import invariant from 'tiny-invariant';
|
|
|
|
|
|
|
|
|
|
export const getTimeFromFrameNum = (detectedFps: number, frameNum: number) => frameNum / detectedFps;
|
|
|
|
|
|
|
|
|
|
@ -48,7 +45,7 @@ export const getFrameValParser = (fps: number) => (str: string) => {
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export async function parseCsv(csvStr: string, parseTimeFn: (a: string) => number | undefined) {
|
|
|
|
|
const rows = await csvParseAsync(csvStr, {}) as [string, string, string][];
|
|
|
|
|
const rows = csvParse(csvStr, {}) as [string, string, string][];
|
|
|
|
|
if (rows.length === 0) throw new Error(i18n.t('No rows found'));
|
|
|
|
|
if (!rows.every((row) => row.length === 3)) throw new Error(i18n.t('One or more rows does not have 3 columns'));
|
|
|
|
|
|
|
|
|
|
@ -77,61 +74,56 @@ export async function parseCutlist(clStr: string) {
|
|
|
|
|
param: /^\s*([^=]+?)\s*=\s*(.*?)\s*$/,
|
|
|
|
|
comment: /^\s*;.*$/,
|
|
|
|
|
};
|
|
|
|
|
const iniValue = {};
|
|
|
|
|
const iniValue: Record<string, string | undefined | Record<string, string | undefined>> = {};
|
|
|
|
|
|
|
|
|
|
const lines = clStr.split(/[\n\r]+/);
|
|
|
|
|
let section: string | null | undefined = null;
|
|
|
|
|
let section: string | undefined;
|
|
|
|
|
lines.forEach((line) => {
|
|
|
|
|
if (regex.comment.test(line)) {
|
|
|
|
|
return;
|
|
|
|
|
} if (regex.param.test(line)) {
|
|
|
|
|
}
|
|
|
|
|
if (regex.param.test(line)) {
|
|
|
|
|
const match = line.match(regex.param) || [];
|
|
|
|
|
const [, m1, m2] = match;
|
|
|
|
|
if (m1) {
|
|
|
|
|
const [, key, value] = match;
|
|
|
|
|
if (key) {
|
|
|
|
|
if (section) {
|
|
|
|
|
iniValue[section][m1] = m2;
|
|
|
|
|
const sectionObj = iniValue[section];
|
|
|
|
|
invariant(sectionObj != null && typeof sectionObj !== 'string');
|
|
|
|
|
sectionObj[key] = value;
|
|
|
|
|
} else {
|
|
|
|
|
iniValue[m1] = m2;
|
|
|
|
|
iniValue[key] = value;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} else if (regex.section.test(line)) {
|
|
|
|
|
const match = line.match(regex.section) || [];
|
|
|
|
|
const [, m1] = match;
|
|
|
|
|
if (m1) {
|
|
|
|
|
iniValue[m1] = {};
|
|
|
|
|
section = m1;
|
|
|
|
|
const [, sectionMatch] = match;
|
|
|
|
|
if (sectionMatch) {
|
|
|
|
|
iniValue[sectionMatch] = {};
|
|
|
|
|
section = sectionMatch;
|
|
|
|
|
}
|
|
|
|
|
} else if (line.length === 0 && section) {
|
|
|
|
|
section = null;
|
|
|
|
|
section = undefined;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// end INI-File parse
|
|
|
|
|
|
|
|
|
|
let found = true;
|
|
|
|
|
let i = 0;
|
|
|
|
|
const cutArr: { start: number, end: number, name: string }[] = [];
|
|
|
|
|
while (found) {
|
|
|
|
|
for (let i = 0; ; i += 1) {
|
|
|
|
|
const cutEntry = iniValue[`Cut${i}`];
|
|
|
|
|
if (cutEntry) {
|
|
|
|
|
const start = parseFloat(cutEntry.Start);
|
|
|
|
|
const end = Math.round((start + parseFloat(cutEntry.Duration) + Number.EPSILON) * 100) / 100;
|
|
|
|
|
if (cutEntry && typeof cutEntry !== 'string') {
|
|
|
|
|
const start = parseFloat(cutEntry['Start']!);
|
|
|
|
|
const end = Math.round((start + parseFloat(cutEntry['Duration']!) + Number.EPSILON) * 100) / 100;
|
|
|
|
|
invariant(!Number.isNaN(start), 'Invalid Start');
|
|
|
|
|
invariant(!Number.isNaN(end), 'Invalid End');
|
|
|
|
|
cutArr.push({
|
|
|
|
|
start,
|
|
|
|
|
end,
|
|
|
|
|
name: `Cut ${i}`,
|
|
|
|
|
});
|
|
|
|
|
} else {
|
|
|
|
|
found = false;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
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;
|
|
|
|
|
@ -295,7 +287,7 @@ export function parseYouTube(str: string) {
|
|
|
|
|
return edl.filter((ed) => ed.start !== ed.end);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function formatYouTube(segments) {
|
|
|
|
|
export function formatYouTube(segments: Segment[]) {
|
|
|
|
|
return segments.map((segment) => {
|
|
|
|
|
const timeStr = formatDuration({ seconds: segment.start, showFraction: false, shorten: true });
|
|
|
|
|
const namePart = segment.name ? ` ${segment.name}` : '';
|
|
|
|
|
@ -304,16 +296,16 @@ export function formatYouTube(segments) {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// because null/undefined is also valid values (start/end of timeline)
|
|
|
|
|
const safeFormatDuration = (duration) => (duration != null ? formatDuration({ seconds: duration }) : '');
|
|
|
|
|
const safeFormatDuration = (duration: number | undefined) => (duration != null ? formatDuration({ seconds: duration }) : '');
|
|
|
|
|
|
|
|
|
|
export const formatSegmentsTimes = (cutSegments) => cutSegments.map(({ start, end, name }) => [
|
|
|
|
|
export const formatSegmentsTimes = (cutSegments: Segment[]) => cutSegments.map(({ start, end, name }) => [
|
|
|
|
|
safeFormatDuration(start),
|
|
|
|
|
safeFormatDuration(end),
|
|
|
|
|
name,
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
export async function formatCsvFrames({ cutSegments, getFrameCount }) {
|
|
|
|
|
const safeFormatFrameCount = (seconds) => (seconds != null ? getFrameCount(seconds) : '');
|
|
|
|
|
export async function formatCsvFrames({ cutSegments, getFrameCount }: { cutSegments: Segment[], getFrameCount: GetFrameCount }) {
|
|
|
|
|
const safeFormatFrameCount = (seconds: number | undefined) => (seconds != null ? getFrameCount(seconds) : '');
|
|
|
|
|
|
|
|
|
|
const formatted = cutSegments.map(({ start, end, name }) => [
|
|
|
|
|
safeFormatFrameCount(start),
|
|
|
|
|
@ -321,20 +313,20 @@ export async function formatCsvFrames({ cutSegments, getFrameCount }) {
|
|
|
|
|
name,
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
return csvStringifyAsync(formatted);
|
|
|
|
|
return csvStringify(formatted);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export async function formatCsvSeconds(cutSegments) {
|
|
|
|
|
export async function formatCsvSeconds(cutSegments: Segment[]) {
|
|
|
|
|
const rows = cutSegments.map(({ start, end, name }) => [start, end, name]);
|
|
|
|
|
return csvStringifyAsync(rows);
|
|
|
|
|
return csvStringify(rows);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export async function formatCsvHuman(cutSegments) {
|
|
|
|
|
return csvStringifyAsync(formatSegmentsTimes(cutSegments));
|
|
|
|
|
export async function formatCsvHuman(cutSegments: Segment[]) {
|
|
|
|
|
return csvStringify(formatSegmentsTimes(cutSegments));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export async function formatTsv(cutSegments) {
|
|
|
|
|
return csvStringifyAsync(formatSegmentsTimes(cutSegments), { delimiter: '\t' });
|
|
|
|
|
export async function formatTsv(cutSegments: Segment[]) {
|
|
|
|
|
return csvStringify(formatSegmentsTimes(cutSegments), { delimiter: '\t' });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function parseDvAnalyzerSummaryTxt(txt: string) {
|
|
|
|
|
@ -424,7 +416,7 @@ export function parseSrtToSegments(text: string) {
|
|
|
|
|
}));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function formatSrt(segments) {
|
|
|
|
|
export function formatSrt(segments: Segment[]) {
|
|
|
|
|
return segments.reduce((acc, segment, index) => `${acc}${index > 0 ? '\r\n' : ''}${index + 1}\r\n${formatDuration({ seconds: segment.start }).replaceAll('.', ',')} --> ${formatDuration({ seconds: segment.end }).replaceAll('.', ',')}\r\n${segment.name || '-'}\r\n`, '');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|