diff --git a/public/menu.js b/public/menu.js index 16e81a7b..9123b00e 100644 --- a/public/menu.js +++ b/public/menu.js @@ -100,6 +100,12 @@ module.exports = ({ app, mainWindow, newVersion, isStoreBuild }) => { mainWindow.webContents.send('importEdlFile', 'pbf'); }, }, + { + label: esc(t('DV Analyzer Summary.txt')), + click() { + mainWindow.webContents.send('importEdlFile', 'dv-analyzer-summary-txt'); + }, + }, ], }, { diff --git a/src/App.jsx b/src/App.jsx index c14dbaec..38402166 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -103,6 +103,14 @@ let lastOpenedPath; const hevcPlaybackSupportedPromise = doesPlayerSupportHevcPlayback(); hevcPlaybackSupportedPromise.catch((err) => console.error(err)); +function getImportProjectType(filePath) { + if (filePath.endsWith('Summary.txt')) return 'dv-analyzer-summary-txt'; + const edlFormatForExtension = { csv: 'csv', pbf: 'pbf', edl: 'mplayer', cue: 'cue', xml: 'xmeml', fcpxml: 'fcpxml' }; + const matchingExt = Object.keys(edlFormatForExtension).find((ext) => filePath.toLowerCase().endsWith(`.${ext}`)); + if (!matchingExt) return undefined; + return edlFormatForExtension[matchingExt]; +} + const App = memo(() => { // Per project state const [commandedTime, setCommandedTime] = useState(0); @@ -1712,11 +1720,10 @@ 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', fcpxml: 'fcpxml' }; - const matchingExt = Object.keys(edlFormats).find((ext) => filePathLowerCase.endsWith(`.${ext}`)); - if (matchingExt) { + const matchingImportProjectType = getImportProjectType(firstFilePath); + if (matchingImportProjectType) { if (!checkFileOpened()) return; - await loadEdlFile({ path: firstFilePath, type: edlFormats[matchingExt], append: true }); + await loadEdlFile({ path: firstFilePath, type: matchingImportProjectType, append: true }); return; } diff --git a/src/__snapshots__/edlFormats.test.js.snap b/src/__snapshots__/edlFormats.test.js.snap index 87b8c115..64ce1baf 100644 --- a/src/__snapshots__/edlFormats.test.js.snap +++ b/src/__snapshots__/edlFormats.test.js.snap @@ -1,5 +1,130 @@ // Vitest Snapshot v1 +exports[`parses DV Analyzer Summary.txt 1`] = ` +[ + { + "end": 60.4, + "name": "XXXX-XX-XX 00:00:00.000 - XXXX-XX-XX XX:XX:XX:XX", + "start": 0, + }, + { + "end": 485.08, + "name": "XXXX-XX-XX XX:XX:XX:XX - 2001-12-31 23:22:09", + "start": 60.4, + }, + { + "end": 1010.68, + "name": "2001-12-31 23:28:13 - 2002-01-01 19:34:38", + "start": 485.08, + }, + { + "end": 1235.32, + "name": "2002-01-01 13:31:24 - 2002-01-01 22:03:01", + "start": 1010.68, + }, + { + "end": 1575.04, + "name": "2002-01-02 14:27:10 - 2002-01-02 15:48:55", + "start": 1235.32, + }, + { + "end": 1575.08, + "name": "2002-01-02 22:30:22 - 2002-01-02 22:30:22", + "start": 1575.04, + }, + { + "end": 1575.16, + "name": "2002-01-02 22:30:22 - 2002-01-02 22:30:22", + "start": 1575.08, + }, + { + "end": 1575.24, + "name": "2002-01-02 22:30:22 - 2002-01-05 10:57:51", + "start": 1575.16, + }, + { + "end": 1919.44, + "name": "2002-01-05 10:57:51 - 2002-01-05 11:36:20", + "start": 1575.24, + }, + { + "end": 2075.88, + "name": "2002-01-05 13:18:43 - 2002-01-05 14:04:19", + "start": 1919.44, + }, + { + "end": 2138.2, + "name": "2002-01-05 16:39:22 - 2002-01-05 22:51:40", + "start": 2075.88, + }, + { + "end": 2138.76, + "name": "2002-01-05 16:40:24 - 2002-01-05 16:40:25", + "start": 2138.2, + }, + { + "end": 2217.32, + "name": "2002-01-05 22:53:17 - 2002-01-05 22:55:08", + "start": 2138.76, + }, + { + "end": 2269.96, + "name": "2002-01-16 21:17:04 - 2002-01-16 21:18:01", + "start": 2217.32, + }, + { + "end": 2332.64, + "name": "2002-01-20 20:06:37 - 2002-01-20 20:07:48", + "start": 2269.96, + }, + { + "end": 3009.12, + "name": "2002-01-30 18:34:52 - 2002-03-12 00:46:51", + "start": 2332.64, + }, + { + "end": 3596.48, + "name": "2002-03-14 20:27:57 - 2002-04-12 21:06:54", + "start": 3009.12, + }, + { + "end": 3622.08, + "name": "2002-04-12 21:06:56 - 2002-04-12 21:07:22", + "start": 3596.48, + }, + { + "end": 4305, + "name": "2002-04-12 21:11:47 - 2002-04-27 00:05:36", + "start": 3622.08, + }, + { + "end": 4307.12, + "name": "2002-04-25 22:59:57 - 2002-04-25 22:59:59", + "start": 4305, + }, + { + "end": 4357.68, + "name": "2002-04-25 23:00:00 - 2002-05-02 13:33:33", + "start": 4307.12, + }, + { + "end": 4359.72, + "name": "2002-04-25 23:00:50 - 2002-04-25 23:00:52", + "start": 4357.68, + }, + { + "end": 4660.44, + "name": "2002-05-02 13:40:27 - 2002-05-02 13:53:34", + "start": 4359.72, + }, + { + "end": undefined, + "name": "2002-05-02 13:54:14 - 2002-05-02 13:59:29", + "start": 4660.44, + }, +] +`; + exports[`parses fcpxml 1.9 1`] = ` [ { diff --git a/src/edlFormats.js b/src/edlFormats.js index 56f85d86..b8451248 100644 --- a/src/edlFormats.js +++ b/src/edlFormats.js @@ -244,3 +244,32 @@ export async function formatCsvHuman(cutSegments) { export async function formatTsv(cutSegments) { return csvStringifyAsync(formatSegmentsTimes(cutSegments), { delimiter: '\t' }); } + +export function parseDvAnalyzerSummaryTxt(txt) { + const lines = txt.split(/\r?\n/); + + let headerFound = false; + + const times = []; + // eslint-disable-next-line no-restricted-syntax + for (const line of lines) { + if (headerFound) { + const match = line.match(/^(\d{2}):(\d{2}):(\d{2}).(\d{3})\s+([^\s]+)\s+-\s+([^\s]+)\s+([^\s]+\s+[^\s]+)\s+-\s+([^\s]+\s+[^\s]+)/); + if (!match) break; + const h = parseInt(match[1], 10); + const m = parseInt(match[2], 10); + const s = parseInt(match[3], 10); + const ms = parseInt(match[4], 10); + const total = s + ((m + (h * 60)) * 60) + (ms / 1000); + times.push({ time: total, name: `${match[7]} - ${match[8]}` }); + } + if (/^Absolute time\s+DV timecode range\s+Recorded date\/time range\s+Frame range\s*$/.test(line)) headerFound = true; + } + + const edl = times.map(({ time, name }, i) => { + const nextTime = times[i + 1]; + return { start: time, end: nextTime?.time, name }; + }); + + return edl; +} diff --git a/src/edlFormats.test.js b/src/edlFormats.test.js index bb04edd9..88dcb402 100644 --- a/src/edlFormats.test.js +++ b/src/edlFormats.test.js @@ -4,7 +4,7 @@ import { fileURLToPath } from 'url'; import { it, describe, expect } from 'vitest'; -import { parseYouTube, formatYouTube, parseMplayerEdl, parseXmeml, parseFcpXml, parseCsv, getTimeFromFrameNum, formatCsvFrames, getFrameCountRaw, parsePbf } from './edlFormats'; +import { parseYouTube, formatYouTube, parseMplayerEdl, parseXmeml, parseFcpXml, parseCsv, getTimeFromFrameNum, formatCsvFrames, getFrameCountRaw, parsePbf, parseDvAnalyzerSummaryTxt } from './edlFormats'; // eslint-disable-next-line no-underscore-dangle const __dirname = dirname(fileURLToPath(import.meta.url)); @@ -217,3 +217,8 @@ it('parses pbf', async () => { expect(parsePbf(await readFixture('test3.pbf', null))).toMatchSnapshot(); expect(parsePbf(await readFixture('potplayer bookmark format utf16le issue 867.pbf', null))).toMatchSnapshot(); }); + +// https://github.com/mifi/lossless-cut/issues/1664 +it('parses DV Analyzer Summary.txt', async () => { + expect(parseDvAnalyzerSummaryTxt(await readFixture('DV Analyzer Summary.txt', 'utf-8'))).toMatchSnapshot(); +}); diff --git a/src/edlStore.js b/src/edlStore.js index 57a90d01..843e56bd 100644 --- a/src/edlStore.js +++ b/src/edlStore.js @@ -1,7 +1,7 @@ import JSON5 from 'json5'; import i18n from 'i18next'; -import { parseCuesheet, parseXmeml, parseFcpXml, parseCsv, parsePbf, parseMplayerEdl, formatCsvHuman, formatTsv, formatCsvFrames, formatCsvSeconds, getTimeFromFrameNum } from './edlFormats'; +import { parseCuesheet, parseXmeml, parseFcpXml, parseCsv, parsePbf, parseMplayerEdl, formatCsvHuman, formatTsv, formatCsvFrames, formatCsvSeconds, getTimeFromFrameNum, parseDvAnalyzerSummaryTxt } from './edlFormats'; import { askForYouTubeInput, showOpenDialog } from './dialogs'; import { getOutPath } from './util'; @@ -28,6 +28,10 @@ export async function loadFcpXml(path) { return parseFcpXml(await fs.readFile(path, 'utf-8')); } +export async function loadDvAnalyzerSummaryTxt(path) { + return parseDvAnalyzerSummaryTxt(await fs.readFile(path, 'utf-8')); +} + export async function loadPbf(path) { return parsePbf(await fs.readFile(path)); } @@ -75,6 +79,7 @@ export async function readEdlFile({ type, path, fps }) { if (type === 'csv-frames') return loadCsvFrames(path, fps); if (type === 'xmeml') return loadXmeml(path); if (type === 'fcpxml') return loadFcpXml(path); + if (type === 'dv-analyzer-summary-txt') return loadDvAnalyzerSummaryTxt(path); if (type === 'cue') return loadCue(path); if (type === 'pbf') return loadPbf(path); if (type === 'mplayer') return loadMplayerEdl(path); @@ -95,6 +100,7 @@ export async function askForEdlImport({ type, fps }) { 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: ['*'] }]; + else if (type === 'dv-analyzer-summary-txt') filters = [{ name: i18n.t('DV Analyzer Summary.txt'), extensions: ['txt'] }]; else if (type === 'llc') filters = [{ name: i18n.t('LosslessCut project'), extensions: ['llc'] }]; const { canceled, filePaths } = await showOpenDialog({ properties: ['openFile'], filters }); diff --git a/src/fixtures/DV Analyzer Summary.txt b/src/fixtures/DV Analyzer Summary.txt new file mode 100644 index 00000000..6fe6cb1c --- /dev/null +++ b/src/fixtures/DV Analyzer Summary.txt @@ -0,0 +1,47 @@ +DV Analyzer v.1.4.2 by AudioVisual Preservation Solutions, Inc. http://www.avpreserve.com + +L:\To-Re-Encode\31.12.2001 Cats Test Tape (TDK Tape).avi + +Frame Count: 116883 + +Frame count with video error concealment: 2776 frames +Total video error concealment: 319120 errors ( 317500 "A" errors, 1620 "F" errors) +Frame count with CH1 audio error code: 783 frames +Total audio error code for CH1: 16263 errors ( 4005 Dseq=0, 1575 Dseq=1, 3780 Dseq=2, 1611 Dseq=3, 3672 Dseq=4, 1620 Dseq=5) +Frame count with DV timecode incoherency: 2 frames +Frame count with Arbitrary bit inconsistency: 6 frames + +Absolute time DV timecode range Recorded date/time range Frame range +00:00:00.000 00:00:00:15 - 00:01:00:16 XXXX-XX-XX 00:00:00.000 - XXXX-XX-XX XX:XX:XX:XX 0 - 1509 +00:01:00.400 00:00:00:00 - 00:07:04:08 XXXX-XX-XX XX:XX:XX:XX - 2001-12-31 23:22:09 1510 - 12126 +00:08:05.080 00:00:00:00 - 00:08:45:14 2001-12-31 23:28:13 - 2002-01-01 19:34:38 12127 - 25266 +00:16:50.680 00:08:45:15 - 00:12:29:22 2002-01-01 13:31:24 - 2002-01-01 22:03:01 25267 - 30882 +00:20:35.320 00:00:00:00 - 00:05:39:09 2002-01-02 14:27:10 - 2002-01-02 15:48:55 30883 - 39375 +00:26:15.040 00:00:00:00 - 00:00:00:00 2002-01-02 22:30:22 - 2002-01-02 22:30:22 39376 - 39376 +00:26:15.080 00:00:00:02 - 00:00:00:03 2002-01-02 22:30:22 - 2002-01-02 22:30:22 39377 - 39378 +00:26:15.160 00:00:00:05 - 00:00:00:06 2002-01-02 22:30:22 - 2002-01-05 10:57:51 39379 - 39380 +00:26:15.240 00:00:00:08 - 00:05:44:04 2002-01-05 10:57:51 - 2002-01-05 11:36:20 39381 - 47985 +00:31:59.440 00:00:00:00 - 00:02:36:02 2002-01-05 13:18:43 - 2002-01-05 14:04:19 47986 - 51896 +00:34:35.880 00:00:00:00 - 00:01:02:07 2002-01-05 16:39:22 - 2002-01-05 22:51:40 51897 - 53454 +00:35:38.200 00:01:02:08 - 00:01:02:14 2002-01-05 16:40:24 - 2002-01-05 16:40:25 53455 - 53468 +00:35:38.760 00:00:00:00 - 00:01:18:05 2002-01-05 22:53:17 - 2002-01-05 22:55:08 53469 - 55432 +00:36:57.320 00:00:00:00 - 00:00:52:07 2002-01-16 21:17:04 - 2002-01-16 21:18:01 55433 - 56748 +00:37:49.960 00:00:00:00 - 00:01:02:08 2002-01-20 20:06:37 - 2002-01-20 20:07:48 56749 - 58315 +00:38:52.640 00:00:00:00 - 00:11:16:02 2002-01-30 18:34:52 - 2002-03-12 00:46:51 58316 - 75227 +00:50:09.120 00:00:00:00 - 00:09:47:08 2002-03-14 20:27:57 - 2002-04-12 21:06:54 75228 - 89911 +00:59:56.480 00:09:49:07 - 00:10:14:14 2002-04-12 21:06:56 - 2002-04-12 21:07:22 89912 - 90551 +01:00:22.080 00:00:00:00 - 00:11:22:21 2002-04-12 21:11:47 - 2002-04-27 00:05:36 90552 - 107624 +01:11:45.000 00:11:22:22 - 00:11:25:00 2002-04-25 22:59:57 - 2002-04-25 22:59:59 107625 - 107677 +01:11:47.120 00:11:25:01 - 00:12:15:14 2002-04-25 23:00:00 - 2002-05-02 13:33:33 107678 - 108941 +01:12:37.680 00:12:15:15 - 00:12:17:07 2002-04-25 23:00:50 - 2002-04-25 23:00:52 108942 - 108992 +01:12:39.720 00:00:00:00 - 00:05:00:09 2002-05-02 13:40:27 - 2002-05-02 13:53:34 108993 - 116510 +01:17:40.440 00:00:00:00 - 00:00:14:21 2002-05-02 13:54:14 - 2002-05-02 13:59:29 116511 - 116882 + +Percent of frames with Error: 2.69% +Percent of frames with Error (including Arbitrary bit inconsistency): 2.69% +Percent of frames with Video Error Concealment: 2.38% +Percent of frames with Audio Errors: 0.67% +Percent of frames with Timecode Incoherency: 0.00% +Percent of frames with Arbitrary bit inconsistency: 0.01% + +Warning, frame count is maybe incoherant (reported by MediaInfo: 116882)