Skip to content

Commit 4850ce2

Browse files
committed
Timecode: Rational (fraction representation) now supports subframes
1 parent e374183 commit 4850ce2

File tree

2 files changed

+56
-4
lines changed

2 files changed

+56
-4
lines changed

Diff for: Sources/TimecodeKit/Timecode/Data Interchange/Timecode Rational.swift

+14-4
Original file line numberDiff line numberDiff line change
@@ -108,8 +108,8 @@ extension Timecode {
108108
/// fractions.)
109109
public var rationalValue: Fraction {
110110
let frFrac = frameRate.frameDuration
111-
let n = frFrac.numerator * frameCount.wholeFrames
112-
let d = frFrac.denominator
111+
let n = frFrac.numerator * frameCount.subFrameCount
112+
let d = frFrac.denominator * subFramesBase.rawValue
113113

114114
return Fraction(n, d).reduced()
115115
}
@@ -123,8 +123,8 @@ extension Timecode {
123123
///
124124
/// - Throws: ``ValidationError``
125125
public mutating func setTimecode(_ rational: Fraction) throws {
126-
let frameCount = frameCount(of: rational)
127-
try setTimecode(exactly: .frames(frameCount))
126+
let frameCount = floatingFrameCount(of: rational)
127+
try setTimecode(exactly: .combined(frames: frameCount))
128128
}
129129

130130
/// Sets the timecode from elapsed time expressed as a rational fraction.
@@ -175,6 +175,16 @@ extension Timecode {
175175
(rational.denominator * frFrac.numerator)
176176
return frameCount
177177
}
178+
179+
/// Internal:
180+
/// Returns frame count of the rational fraction at current frame rate.
181+
/// Preserves subframes as floating-point potion of a frame.
182+
internal func floatingFrameCount(of rational: Fraction) -> Double {
183+
let frFrac = frameRate.frameDuration
184+
let frameCount = (Double(rational.numerator) * Double(frFrac.denominator)) /
185+
(Double(rational.denominator) * Double(frFrac.numerator))
186+
return frameCount
187+
}
178188
}
179189

180190
extension Fraction {

Diff for: Tests/TimecodeKit-Unit-Tests/Unit Tests/Timecode/Data Interchange/Timecode Rational Tests.swift

+42
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,48 @@ class Timecode_Rational_Tests: XCTestCase {
181181
TCC(h: 1)
182182
)
183183
}
184+
185+
func testTimecode_RationalValue_Subframes() throws {
186+
let tc = try TCC(h: 00, m: 00, s: 01, f: 11, sf: 56)
187+
.toTimecode(at: ._25, base: ._80SubFrames)
188+
XCTAssertEqual(tc.rationalValue, Fraction(367, 250))
189+
}
190+
191+
func testTimecode_RationalSubframes() throws {
192+
// 00:00:01:11.56 @ 25i fps, 80sf base
193+
// this fraction is actually a little past 56 subframes
194+
// because it was from FCPXML where it was not on an exact subframe
195+
// FYI: when we convert it back to a fraction from timecode,
196+
// the fraction ends up 367/250
197+
let frac = Fraction(11011, 7500)
198+
let tc = try frac.toTimecode(at: ._25, base: ._80SubFrames)
199+
XCTAssertEqual(tc.components, TCC(h: 00, m: 00, s: 01, f: 11, sf: 56))
200+
XCTAssertEqual(tc.rationalValue, Fraction(367, 250))
201+
}
202+
203+
func testTimecode_FrameCountOfRational() throws {
204+
// 00:00:01:11.56 @ 25i fps, 80sf base
205+
// this fraction is actually a little past 56 subframes
206+
// because it was from FCPXML where it was not on an exact subframe
207+
// FYI: when we convert it back to a fraction from timecode,
208+
// the fraction ends up 367/250
209+
let frac = Fraction(11011, 7500)
210+
let tc = try frac.toTimecode(at: ._25, base: ._80SubFrames)
211+
let int = tc.frameCount(of: frac)
212+
XCTAssertEqual(int, 36)
213+
}
214+
215+
func testTimecode_FloatingFrameCountOfRational() throws {
216+
// 00:00:01:11.56 @ 25i fps, 80sf base
217+
// this fraction is actually a little past 56 subframes
218+
// because it was from FCPXML where it was not on an exact subframe
219+
// FYI: when we convert it back to a fraction from timecode,
220+
// the fraction ends up 367/250
221+
let frac = Fraction(11011, 7500)
222+
let tc = try frac.toTimecode(at: ._25, base: ._80SubFrames)
223+
let float = tc.floatingFrameCount(of: frac)
224+
XCTAssertEqual(float, 36.70333333333333)
225+
}
184226
}
185227

186228
#endif

0 commit comments

Comments
 (0)