diff --git a/Sources/PalmierPro/Export/XMLExporter.swift b/Sources/PalmierPro/Export/XMLExporter.swift index 2b31d149e..eede5c4fb 100644 --- a/Sources/PalmierPro/Export/XMLExporter.swift +++ b/Sources/PalmierPro/Export/XMLExporter.swift @@ -66,8 +66,10 @@ enum XMLExporter { var f = frame if dropFrame { let drop = Int((Double(fps) * 0.066666).rounded()) // 2 @ 30, 4 @ 60 - let d = f / (fps * 600), m = f % (fps * 600) - f += drop * 9 * d + (m > drop ? drop * ((m - drop) / (fps * 60)) : 0) + // Drop-frame partitions the real (post-drop) count: 17982 per 10 min, 1798 per min at 30. + let per10Min = fps * 600 - drop * 9, perMin = fps * 60 - drop + let d = f / per10Min, m = f % per10Min + f += drop * 9 * d + (m > drop ? drop * ((m - drop) / perMin) : 0) } let sep = dropFrame ? ";" : ":" let ff = f % fps, ss = (f / fps) % 60, mm = (f / (fps * 60)) % 60, hh = f / (fps * 3600) diff --git a/Tests/PalmierProTests/Export/XMLExporterTimecodeTests.swift b/Tests/PalmierProTests/Export/XMLExporterTimecodeTests.swift index 05bc1d824..2be18ecc9 100644 --- a/Tests/PalmierProTests/Export/XMLExporterTimecodeTests.swift +++ b/Tests/PalmierProTests/Export/XMLExporterTimecodeTests.swift @@ -75,6 +75,17 @@ struct XMLExporterTimecodeTests { #expect(XMLExporter.formatTimecode(frame: 42966, fps: 30, dropFrame: true) == "00;23;53;18") } + @Test func dropFrameSkipsTwoFramesAtEachNonTenthMinute() { + // The first real frame of minute 1 must read ;02 — frames ;00/;01 are dropped. The raw + // count (fps*60 etc.) would emit an illegal 00;01;00;00 here. + #expect(XMLExporter.formatTimecode(frame: 1800, fps: 30, dropFrame: true) == "00;01;00;02") + #expect(XMLExporter.formatTimecode(frame: 3598, fps: 30, dropFrame: true) == "00;02;00;02") + // Minute 10 keeps all frames, so ;00 is legal there. + #expect(XMLExporter.formatTimecode(frame: 17982, fps: 30, dropFrame: true) == "00;10;00;00") + // 59.94: 4 frames dropped per non-tenth minute (quanta 60 DF). + #expect(XMLExporter.formatTimecode(frame: 3600, fps: 60, dropFrame: true) == "00;01;00;04") + } + @Test func zeroFpsDoesNotCrash() { #expect(XMLExporter.formatTimecode(frame: 100, fps: 0, dropFrame: false) == "00:00:00:00") }