diff --git a/src/renderer/src/__snapshots__/edlFormats.test.ts.snap b/src/renderer/src/__snapshots__/edlFormats.test.ts.snap index 7dadbe8b..675e4eef 100644 --- a/src/renderer/src/__snapshots__/edlFormats.test.ts.snap +++ b/src/renderer/src/__snapshots__/edlFormats.test.ts.snap @@ -12,6 +12,40 @@ Subtitle 2 line 2 " `; +exports[`parseGpsLine 1`] = ` +{ + "alt": 19, + "distance": 67.78, + "dzoom": undefined, + "ev": -1, + "f": "F/2.8", + "height": 20.3, + "horizontalSpeed": 1.03, + "iso": 100, + "lat": 15.0732, + "lng": 67.9771, + "ss": 776.89, + "verticalSpeed": 0, +} +`; + +exports[`parseGpsLine 2`] = ` +{ + "alt": 26, + "distance": 0.57, + "dzoom": 1, + "ev": 0, + "f": "F/2.8", + "height": 102.4, + "horizontalSpeed": 0, + "iso": 100, + "lat": -3.913, + "lng": 56.5019, + "ss": 678.52, + "verticalSpeed": -0, +} +`; + exports[`parses DV Analyzer Summary.txt 1`] = ` [ { diff --git a/src/renderer/src/edlFormats.test.ts b/src/renderer/src/edlFormats.test.ts index 84dbf279..524eea71 100644 --- a/src/renderer/src/edlFormats.test.ts +++ b/src/renderer/src/edlFormats.test.ts @@ -1,10 +1,10 @@ import fs from 'node:fs/promises'; import { join, dirname } from 'node:path'; import { fileURLToPath } from 'node:url'; -import { it, describe, expect } from 'vitest'; +import { it, describe, expect, test } from 'vitest'; -import { parseSrtToSegments, formatSrt, parseYouTube, formatYouTube, parseMplayerEdl, parseXmeml, parseFcpXml, parseCsv, parseCsvTime, getFrameValParser, formatCsvFrames, getFrameCountRaw, parsePbf, parseDvAnalyzerSummaryTxt, parseCutlist } from './edlFormats'; +import { parseSrtToSegments, formatSrt, parseYouTube, formatYouTube, parseMplayerEdl, parseXmeml, parseFcpXml, parseCsv, parseCsvTime, getFrameValParser, formatCsvFrames, getFrameCountRaw, parsePbf, parseDvAnalyzerSummaryTxt, parseCutlist, parseGpsLine } from './edlFormats'; // eslint-disable-next-line no-underscore-dangle const __dirname = dirname(fileURLToPath(import.meta.url)); @@ -317,3 +317,9 @@ it('format srt', async () => { it('parses DV Analyzer Summary.txt', async () => { expect(parseDvAnalyzerSummaryTxt(await readFixture('DV Analyzer Summary.txt', 'utf8'))).toMatchSnapshot(); }); + +test('parseGpsLine', () => { + expect(parseGpsLine('F/2.8, SS 776.89, ISO 100, EV -1.0, GPS (15.0732, 67.9771, 19), D 67.78m, H 20.30m, H.S 1.03m/s, V.S 0.00m/s')).toMatchSnapshot(); + // https://github.com/mifi/lossless-cut/issues/2072#issuecomment-2325755148 + expect(parseGpsLine('F/2.8, SS 678.52, ISO 100, EV 0, DZOOM 1.000, GPS (-3.9130, 56.5019, 26), D 0.57m, H 102.40m, H.S 0.00m/s, V.S -0.00m/s')).toMatchSnapshot(); +}); diff --git a/src/renderer/src/edlFormats.ts b/src/renderer/src/edlFormats.ts index e81e7c98..d9b73664 100644 --- a/src/renderer/src/edlFormats.ts +++ b/src/renderer/src/edlFormats.ts @@ -427,3 +427,22 @@ export function parseSrtToSegments(text: string) { export function formatSrt(segments) { 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`, ''); } + +export function parseGpsLine(line: string) { + const gpsMatch = line.match(/^\s*([^,]+),\s*SS\s+([^,]+),\s*ISO\s+([^,]+),\s*EV\s+([^,]+)(?:,\s*DZOOM\s+([^,]+))?,\s*GPS\s+\(([^,]+),\s*([^,]+),\s*([^,]+)\),\s*D\s+([^m]+)m,\s*H\s+([^m]+)m,\s*H\.S\s+([^m]+)m\/s,\s*V\.S\s+([^m]+)m\/s\s*$/); + if (!gpsMatch) return undefined; + return { + f: gpsMatch[1]!, + ss: parseFloat(gpsMatch[2]!), + iso: parseInt(gpsMatch[3]!, 10), + ev: parseFloat(gpsMatch[4]!), + dzoom: gpsMatch[5] != null ? parseFloat(gpsMatch[5]) : undefined, + lat: parseFloat(gpsMatch[6]!), + lng: parseFloat(gpsMatch[7]!), + alt: parseFloat(gpsMatch[8]!), + distance: parseFloat(gpsMatch[9]!), + height: parseFloat(gpsMatch[10]!), + horizontalSpeed: parseFloat(gpsMatch[11]!), + verticalSpeed: parseFloat(gpsMatch[12]!), + }; +} diff --git a/src/renderer/src/gps.tsx b/src/renderer/src/gps.tsx index 191e28f7..ad47b75e 100644 --- a/src/renderer/src/gps.tsx +++ b/src/renderer/src/gps.tsx @@ -7,6 +7,7 @@ import { FaMapMarkerAlt } from 'react-icons/fa'; import { extractSrtGpsTrack } from './ffmpeg'; import { ReactSwal } from './swal'; import { handleError } from './util'; +import { parseGpsLine } from './edlFormats'; export default async function tryShowGpsMap(filePath: string, streamIndex: number) { @@ -14,24 +15,16 @@ export default async function tryShowGpsMap(filePath: string, streamIndex: numbe const subtitles = await extractSrtGpsTrack(filePath, streamIndex); const gpsPoints = subtitles.flatMap((subtitle) => { const firstLine = subtitle.lines[0]; - // example: - // "F/2.8, SS 776.89, ISO 100, EV -1.0, GPS (15.0732, 67.9771, 19), D 67.78m, H 20.30m, H.S 1.03m/s, V.S 0.00m/s" - const gpsMatch = firstLine?.match(/^\s*([^,]+),\s*SS\s+([^,]+),\s*ISO\s+([^,]+),\s*EV\s+([^,]+),\s*GPS\s+\(([^,]+),\s*([^,]+),\s*([^,]+)\),\s*D\s+([^m]+)m,\s*H\s+([^m]+)m,\s*H\.S\s+([^m]+)m\/s,\s*V\.S\s+([^m]+)m\/s\s*$/); - if (!gpsMatch || firstLine == null) return []; + const { index } = subtitle; + if (firstLine == null || index == null) return []; + + const parsed = parseGpsLine(firstLine); + if (parsed == null) return []; + return [{ - index: subtitle.index, + ...parsed, + index, raw: firstLine, - f: gpsMatch[1]!, - ss: parseFloat(gpsMatch![2]!), - iso: parseInt(gpsMatch![3]!, 10), - ev: parseFloat(gpsMatch![4]!), - lat: parseFloat(gpsMatch![5]!), - lng: parseFloat(gpsMatch![6]!), - alt: parseFloat(gpsMatch![7]!), - distance: parseFloat(gpsMatch![8]!), - height: parseFloat(gpsMatch![9]!), - horizontalSpeed: parseFloat(gpsMatch![10]!), - verticalSpeed: parseFloat(gpsMatch![11]!), }]; }); // console.log(gpsPoints)