Skip to content
This repository has been archived by the owner on Nov 14, 2022. It is now read-only.

Commit

Permalink
Better timecode/realtime seconds translation
Browse files Browse the repository at this point in the history
  • Loading branch information
iluvcapra committed Apr 7, 2019
1 parent bf2d2b0 commit 4d14abf
Show file tree
Hide file tree
Showing 4 changed files with 107 additions and 25 deletions.
46 changes: 32 additions & 14 deletions PKit/SessionEntity.swift
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,23 @@ public struct SessionEntity {
case Frame2997Drop = "29.97 Drop Frame"
case Frame30 = "30 Frame"
case Frame30Drop = "30 Drop Frame"

var frameDuration : CMTime {
switch self {
case .Frame24:
return CMTime(value: 1, timescale: 24)
case .Frame2398:
return CMTime(value: 1001, timescale: 24000)
case .Frame25:
return CMTime(value: 1, timescale: 25)
case .Frame30: fallthrough
case .Frame30Drop:
return CMTime(value: 1, timescale: 30)
case .Frame2997: fallthrough
case .Frame2997Drop:
return CMTime(value: 1001, timescale: 30000)
}
}
}

func symbolicTimecodeFormat() throws -> TimecodeFormat {
Expand Down Expand Up @@ -72,42 +89,43 @@ public struct SessionEntity {
}
}

private func frameCount(for s : String) throws -> (count: Int, perSecond: Int) {
private func frameCount(for s : String) throws -> (count: Int, frameDuration: CMTime) {
let (rep, terms) = try TimeRepresentation.terms(in: s)

let termMultiples : [Double]
let fps : Double
let frameDur : CMTime
switch rep {
case .timecode, .timecodeDF:
fps = Double(try self.framesPerTimecodeSecond() )
termMultiples = [3600.0, 60.0, 1.0].map { $0 * fps } + [1.0]
let fpss = try self.framesPerTimecodeSecond()
frameDur = try self.symbolicTimecodeFormat().frameDuration
termMultiples = [3600.0, 60.0, 1.0].map { $0 * Double(fpss) } + [1.0]
case .footage:
fps = 24.0
frameDur = CMTime(value: 24000, timescale: 1001) // FIXME this won't be right under some circumstances
termMultiples = [16.0, 1.0]
case .samples:
fps = self.sampleRate
termMultiples = [fps]
frameDur = CMTime(value: 1, timescale: Int32(self.sampleRate))
termMultiples = [self.sampleRate]
case .realtime:
fps = Double(try self.framesPerTimecodeSecond() )
termMultiples = [60.0 * fps, fps]
frameDur = CMTime(value: 1, timescale: 1)
termMultiples = [60.0, 1.0]
}

let numericalTerms = terms.map { Double($0 ?? "") ?? 0.0 }
let rawFrameCount = zip(numericalTerms, termMultiples).map {$0 * $1}.reduce(0.0,+)

if rep == .timecodeDF {
let dfCorrection = droppedFrameCount(for: Int( rawFrameCount) )
return ( Int( rawFrameCount ) - dfCorrection , Int(fps) )
return ( Int( rawFrameCount ) - dfCorrection , frameDur )
} else {
return ( Int( rawFrameCount ) , Int(fps) )
return ( Int( rawFrameCount ) , frameDur )
}
}

func decodeTime(from string : String) throws -> CMTime {
let (frameCount, fps) = try self.frameCount(for : string)
let (frameCount, frameDuration) = try self.frameCount(for : string)

return CMTime(value: CMTimeValue(frameCount),
timescale: CMTimeScale(fps))
return CMTime(value: Int64(frameCount * Int(frameDuration.value)),
timescale: frameDuration.timescale)
}

}
48 changes: 48 additions & 0 deletions PKitTests/SessionEntityTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
//
// SessionEntityTests.swift
// PKitTests
//
// Created by Jamie Hardt on 4/7/19.
//

import XCTest
import CoreMedia

class SessionEntityTests: XCTestCase {

override func setUp() {
// Put setup code here. This method is called before the invocation of each test method in the class.
}

override func tearDown() {
// Put teardown code here. This method is called after the invocation of each test method in the class.
}

func testDecodeTimecode() {

let e = SessionEntity(rawTitle: "Test Session", sampleRate: 48000.0, bitDepth: "24 bit", startTime: "01:00:00:00",
timecodeFormat: "30 Frame", trackCount: 1, clipCount: 1, filesCount: 1)

let result1 = try! e.decodeTime(from: "01:00:00:00")
XCTAssertEqual(result1, CMTime(value: 3600, timescale: 1) )

let result2 = try! e.decodeTime(from: "02:00:00:00")
XCTAssertEqual(result2, CMTime(value: 7200, timescale: 1) )



}

func testDecodeTimecode2398() {
let e = SessionEntity(rawTitle: "Test Session", sampleRate: 48000.0, bitDepth: "24 bit", startTime: "01:00:00:00",
timecodeFormat: "23.976 Frame", trackCount: 1, clipCount: 1, filesCount: 1)

let result1 = try! e.decodeTime(from: "01:00:00:00")
XCTAssertEqual(result1, CMTime(value: 3600 * 1001, timescale: 1000) )

let result2 = try! e.decodeTime(from: "02:00:00:00")
XCTAssertEqual(result2, CMTime(value: 7200 * 1001, timescale: 1000) )
}


}
26 changes: 15 additions & 11 deletions PKitTests/TimeRepresentationTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,17 +35,21 @@ class TimeRepresentationTests: XCTestCase {
}

func testTerms() {
let (format_ff, terms_ff) = TimeRepresentation.terms(in: feet)!
XCTAssertEqual(format_ff, TimeRepresentation.footage)
XCTAssertEqual(terms_ff, ["33","08",".58"])

let (format_tc, terms_tc) = TimeRepresentation.terms(in: tc)!
XCTAssertEqual(format_tc, TimeRepresentation.timecode)
XCTAssertEqual(terms_tc, ["01","00","41","29",".52"])

let (format_tcdf, terms_tcdf) = TimeRepresentation.terms(in: tcdf)!
XCTAssertEqual(format_tcdf, TimeRepresentation.timecodeDF)
XCTAssertEqual(terms_tcdf, ["01","00","41","28", nil])
do {
let (format_ff, terms_ff) = try TimeRepresentation.terms(in: feet)
XCTAssertEqual(format_ff, TimeRepresentation.footage)
XCTAssertEqual(terms_ff, ["33","08",".58"])

let (format_tc, terms_tc) = try TimeRepresentation.terms(in: tc)
XCTAssertEqual(format_tc, TimeRepresentation.timecode)
XCTAssertEqual(terms_tc, ["01","00","41","29",".52"])

let (format_tcdf, terms_tcdf) = try TimeRepresentation.terms(in: tcdf)
XCTAssertEqual(format_tcdf, TimeRepresentation.timecodeDF)
XCTAssertEqual(terms_tcdf, ["01","00","41","28", nil])
} catch let error {
XCTFail(error.localizedDescription)
}

}

Expand Down
12 changes: 12 additions & 0 deletions ProToolsText.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@
9A4B3EFA225A764E002726C5 /* TrackEntity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A4B3EF9225A764E002726C5 /* TrackEntity.swift */; };
9A4B3EFC225A769A002726C5 /* ClipEntity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A4B3EFB225A769A002726C5 /* ClipEntity.swift */; };
9A4B3EFE225A77AB002726C5 /* MarkerEntity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A4B3EFD225A77AB002726C5 /* MarkerEntity.swift */; };
9A4B3F00225A79A3002726C5 /* SessionEntityTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A4B3EFF225A79A3002726C5 /* SessionEntityTests.swift */; };
9A4B3F01225A79D7002726C5 /* ClipEntity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A4B3EFB225A769A002726C5 /* ClipEntity.swift */; };
9A4B3F02225A79DA002726C5 /* SessionEntity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A4B3EF7225A7593002726C5 /* SessionEntity.swift */; };
9A4B3F03225A79DE002726C5 /* TrackEntity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A4B3EF9225A764E002726C5 /* TrackEntity.swift */; };
9A4B3F04225A79E1002726C5 /* MarkerEntity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A4B3EFD225A77AB002726C5 /* MarkerEntity.swift */; };
9A70F4BB1FF448AD000DBF46 /* PKit.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = 9A9450131FE71C1F007CFC2D /* PKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
9A779CE4217FCA55007A40FB /* Robin Hood Spotting.txt in Resources */ = {isa = PBXBuildFile; fileRef = 9A779CE3217FCA3E007A40FB /* Robin Hood Spotting.txt */; };
9A80027D1FEEF9A6009D4E28 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A80027C1FEEF9A6009D4E28 /* AppDelegate.swift */; };
Expand Down Expand Up @@ -107,6 +112,7 @@
9A4B3EF9225A764E002726C5 /* TrackEntity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrackEntity.swift; sourceTree = "<group>"; };
9A4B3EFB225A769A002726C5 /* ClipEntity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClipEntity.swift; sourceTree = "<group>"; };
9A4B3EFD225A77AB002726C5 /* MarkerEntity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MarkerEntity.swift; sourceTree = "<group>"; };
9A4B3EFF225A79A3002726C5 /* SessionEntityTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionEntityTests.swift; sourceTree = "<group>"; };
9A70F4B81FF43FDF000DBF46 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = "<group>"; };
9A779CE2217FCA3E007A40FB /* Robin Hood Spotting.ptx */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Robin Hood Spotting.ptx"; sourceTree = "<group>"; };
9A779CE3217FCA3E007A40FB /* Robin Hood Spotting.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = "Robin Hood Spotting.txt"; sourceTree = "<group>"; };
Expand Down Expand Up @@ -305,6 +311,7 @@
9AB8714A1FF81DB8005E7A15 /* JHScannerTests.swift */,
9AAA0E0C215B51D500F9158E /* NSRegularExpressionAdditions_Test.swift */,
9AAA0E13215C063700F9158E /* TimeRepresentationTests.swift */,
9A4B3EFF225A79A3002726C5 /* SessionEntityTests.swift */,
);
path = PKitTests;
sourceTree = "<group>";
Expand Down Expand Up @@ -594,13 +601,18 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
9A4B3F00225A79A3002726C5 /* SessionEntityTests.swift in Sources */,
9AB8714B1FF81DB8005E7A15 /* JHScannerTests.swift in Sources */,
9AAA0E0B215B4C2800F9158E /* PTTextFileParser.swift in Sources */,
9AAA0E0D215B51D500F9158E /* NSRegularExpressionAdditions_Test.swift in Sources */,
9A4B3F04225A79E1002726C5 /* MarkerEntity.swift in Sources */,
9A4B3F02225A79DA002726C5 /* SessionEntity.swift in Sources */,
9AAA0DFF215B047900F9158E /* PTTimeParsers.swift in Sources */,
9AAA0E0A215B4C2400F9158E /* PTEntityParser.swift in Sources */,
9AAA0E14215C063700F9158E /* TimeRepresentationTests.swift in Sources */,
9AD6EE8E1FF595D1008C3CB0 /* JHScanner.swift in Sources */,
9A4B3F01225A79D7002726C5 /* ClipEntity.swift in Sources */,
9A4B3F03225A79DE002726C5 /* TrackEntity.swift in Sources */,
9A9450211FE71C1F007CFC2D /* PKitTests.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
Expand Down

0 comments on commit 4d14abf

Please sign in to comment.