Giter VIP home page Giter VIP logo

timecodekit's Introduction

TimecodeKit

TimecodeKit

CI Build Status Platforms - macOS 10.12 | iOS 9 | tvOS 9 | watchOS 2 | visionOS 1 Swift 5.5-5.9 Xcode 13-15 License: MIT

The most robust, precise and complete Swift library for working with SMPTE/EBU timecode. Supports 22 industry timecode frame rates, with a suite of conversions, calculations and integrations with Apple AV frameworks.

Timecode is a broadcast and post-production standard for addressing video frames. It is used for video burn-in timecode (BITC), and display in a DAW (Digital Audio Workstation) or video playback/editing applications.

Note: See the TimecodeKit 1.x → 2.x Migration Guide if you are moving from TimecodeKit 1.x.

Supported Timecode Frame Rates

The following timecode rates and formats are supported.

Film / ATSC / HD PAL / SECAM / DVB / ATSC NTSC / ATSC / PAL-M NTSC Non-Standard ATSC
23.976 25 29.97 30 DF 30
24 50 29.97 DF 60 DF 60
24.98 100 59.94 120 DF 120
47.952 59.94 DF
48 119.88
95.904 119.88 DF
96

Supported Video Frame Rates

The following video frame rates are supported. (Video rates)

Film / HD PAL NTSC
23.98p 25p / 25i 29.97p / 29.97i
24p 50p / 50i 30p
47.95p 100p 59.94p / 59.94i
48p 60p / 60i
95.9p 119.88p
96p 120p

Core Features

  • Convert timecode between:
    • timecode display string
    • total elapsed frame count
    • real wall-clock time
    • elapsed audio samples at any audio sample rate
    • rational time notation (such as CMTime or Final Cut Pro XML and AAF encoding)
    • feet + frames
  • Convert timecode and/or frame rate to a rational fraction, and vice-versa (including CMTime)
  • Support for Days as a timecode component (some DAWs including Cubase support > 24 hour timecode)
  • Support for Subframes
  • Math operations: add, subtract, multiply, divide
  • Granular timecode validation
  • Form a Range or Stride between two timecodes
  • Conforms to Codable
  • A Formatter object that can format timecode
  • A NSAttributedString showing invalid timecode components using alternate attributes (such as red text color)
  • A SwiftUI Text object showing invalid timecode components using alternate attributes (such as red text color)
  • AVAsset video file utilities to easily read/write timecode tracks and locate AVPlayer to timecode locations
  • Exhaustive unit tests ensuring accuracy

Installation

Swift Package Manager (SPM)

  1. Add TimecodeKit as a dependency using Swift Package Manager.
    • In an app project or framework, in Xcode:

      • Select the menu: File → Swift Packages → Add Package Dependency...
      • Enter this URL: https://github.com/orchetect/TimecodeKit
    • In a Swift Package, add it to the Package.swift dependencies:

      .package(url: "https://github.com/orchetect/TimecodeKit", from: "2.0.0")
  2. Import the library:
    import TimecodeKit

Documentation

See the online documentation for library usage, getting started info, and 1.x → 2.x migration guide.

References

Author

Coded by a bunch of 🐹 hamsters in a trenchcoat that calls itself @orchetect.

License

Licensed under the MIT license. See LICENSE for details.

Sponsoring

If you enjoy using TimecodeKit and want to contribute to open-source financially, GitHub sponsorship is much appreciated. Feedback and code contributions are also welcome.

Community & Support

Please do not email maintainers for technical support. Several options are available for questions and feature ideas:

  • Questions and feature ideas can be posted to Discussions.
  • If an issue is a verifiable bug with reproducible steps it may be posted in Issues.

Contributions

Contributions are welcome. Feel free to post in Discussions first before submitting a PR.

timecodekit's People

Contributors

orchetect avatar ryanfrancesconi avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

timecodekit's Issues

Custom (non-standard) frame rates

I've been bouncing around some ideas on how to possibly add the capability to use custom arbitrary frame rates in addition to the standard menu of frame rates offered by TimecodeKit.

It's rare, but some DAWs (including Cubase) allow the user to set their own frame rate to virtually any floating point fps value they want (within reason).

My initial concept is to add another enum case to FrameRate that carries an associated value containing the custom rate.

extension Timecode {
    public enum FrameRate {
        case _23_976
            // ... (all currently existing standard rates) ...
        case _120_drop
        
        case video(Double) // <-- add a custom arbitrary video rate case
    }
}

At some point I would like to branch this as a new feature, because it will affect how most of the underlying library operates and it won't become clear how viable this is until work is commenced on it. It's possible there will be some unforeseen roadblocks that would make this proposed implementation untenable.

Another unknown is whether a custom frame rate would support drop-frame. The likely answer is no. It would be purely a linear frame rate.

A few ideas, FrameRate additions.

Hi, this is a great project. Thanks for doing this! I've started integrating the framework into my project and everything I believe is working as intended. I did have a few things which I needed that I didn't see which I'll put below as some ideas. I'm not sure if I somehow overlooked these options, but I don't think so.

In particular I think a common format list is useful for displaying in a UI. Not very common to list 120 DF for example.

I have the situation where I don't know a frame rate until I open a video and all I've got from that is a floatValue. I'm not actually sure if there is a way to tell if it's drop frame or not? In any case, the floatValue init seems like I think I need in that case.

• Common Format List
• floatValue for FrameRate
• init FrameRate from a floatValue

If you're interested in these, I'd be happy to PR them in context, but this is what I'm using at the moment, Thanks!
Ryan

extension Timecode.FrameRate {
    public static var commonFormats: [Timecode.FrameRate] {
        [._23_976,
         ._24,
         ._25,
         ._29_97,
         ._29_97_drop,
         ._30,
         ._30_drop,
         ._50,
         ._59_94,
         ._59_94_drop,
         ._60,
         ._60_drop]
    }

    public var floatValue: Float {
        switch self {
        case ._23_976:
            return 24.0 / 1.001
        case ._24:
            return 24.0
        case ._24_98:
            return 25.0 / 1.001
        case ._25:
            return 25.0
        case ._29_97, ._29_97_drop, ._30_drop:
            return 30.0 / 1.001
        case ._30:
            return 30.0
        case ._47_952:
            return 48.0 / 1.001
        case ._48:
            return 48.0
        case ._50:
            return 50.0
        case ._59_94, ._59_94_drop, ._60_drop:
            return 60.0 / 1.001
        case ._60:
            return 60.0
        case ._100:
            return 100.0
        case ._120:
            return 120.0
        case ._120_drop, ._119_88, ._119_88_drop:
            return 120.0 / 1.001
        }
    }

    public var truncatedFloatValue: Float {
        floatValue.truncated(decimalPlaces: 3)
    }

    public init?(floatValue: Float, restrictToCommonFormats: Bool = true, favorDropFrame: Bool = false) {
        let collection = restrictToCommonFormats ? Self.commonFormats : Self.allCases

        let findMatches = collection.filter {
            $0.truncatedFloatValue == floatValue.truncated(decimalPlaces: 3)
        }

        // in cases where it's not clear which frame rate it is based off the floatValue
        // we will have more than one match
        if favorDropFrame, findMatches.count > 1 {
            if let firstDrop = findMatches.first(where: {
                $0.isDrop
            }) {
                self = firstDrop
                return
            }
        }

        if let firstMatch = findMatches.first {
            self = firstMatch
        } else {
            return nil
        }
    }
}

Integer init and setter for Samples

Since working with audio samples is more idiomatic to whole integer sample counts and not fractional/floating point, add overloads for the existing audio samples inits/methods to take Int.

Rename existing:

Timecode

  • samplesDoubleValue(sampleRate: Int) -> Double
  • setTimecode(exactlySamplesValue: Double, sampleRate: Int) throws

Add overloads for:

Timecode

  • samplesValue(sampleRate: Int) -> Int
  • setTimecode(exactlySamplesValue: Int, sampleRate: Int) throws

Timecode parser, guessing and shorthand

For manual entry of timecode i think a short hand would be helpful similar to how Premiere does it. Here's a few examples from Premiere:

Sequence 01 =

Sequence 01

Sequence 01 E

x Sequence 01 =

a function might be something like:

    class func parseTimecode(string: String,
                             frameRate: Timecode.FrameRate,
                             delimiter: String = ":") -> Timecode? {
        let components = string.components(separatedBy: delimiter)

        var tcc = TCC(d: 0, h: 0, m: 0, s: 0, f: 0, sf: 0)

        switch components.count {
        // 01:00:10:01:11
        case 5:
            tcc.h = components[0].toInt()
            tcc.m = components[1].toInt()
            tcc.s = components[2].toInt()
            tcc.f = components[3].toInt()
            tcc.sf = components[4].toInt()
        // 01:00:10:01
        case 4:
            tcc.h = components[0].toInt()
            tcc.m = components[1].toInt()
            tcc.s = components[2].toInt()
            tcc.f = components[3].toInt()
        // 01:00:00
        case 3:
            tcc.m = components[0].toInt()
            tcc.s = components[1].toInt()
            tcc.f = components[2].toInt()
        // 1:1 = 00:00:01:01
        case 2:
            tcc.s = components[0].toInt()
            tcc.f = components[1].toInt()
        // 10 = 00:00:00:10
        case 1:
            tcc.f = components[0].toInt()
        default:
            return nil
        }

        return tcc.toTimecode(at: frameRate)
    }
`

Performance improvements

In the next release, will add @inlinable to several methods and properties to further increase performance and compiler optimization.

The performance gain may be negligible (as the Timecode struct is already lightweight) but for real-time / low latency use cases, it may help.

`Timecode.FrameRate`: Add failable init from rational frame rate (fraction)

Timecode.FrameRate provides a fraction property that returns a numerator and denominator.

public var fraction: (numerator: Int, denominator: Int) {
switch self {
case ._23_976: return (numerator: 24000, denominator: 1001)
case ._24: return (numerator: 24, denominator: 1)
case ._24_98: return (numerator: 25000, denominator: 1001)
case ._25: return (numerator: 25, denominator: 1)
case ._29_97: return (numerator: 30000, denominator: 1001)
case ._29_97_drop: return (numerator: 30000, denominator: 1001)
case ._30: return (numerator: 30, denominator: 1)
case ._30_drop: return (numerator: 30, denominator: 1)
case ._47_952: return (numerator: 48000, denominator: 1001)
case ._48: return (numerator: 48, denominator: 1)
case ._50: return (numerator: 50, denominator: 1)
case ._59_94: return (numerator: 60000, denominator: 1001)
case ._59_94_drop: return (numerator: 60000, denominator: 1001)
case ._60: return (numerator: 60, denominator: 1)
case ._60_drop: return (numerator: 60, denominator: 1)
case ._100: return (numerator: 100, denominator: 1)
case ._119_88: return (numerator: 120_000, denominator: 1001)
case ._119_88_drop: return (numerator: 120_000, denominator: 1001)
case ._120: return (numerator: 120, denominator: 1)
case ._120_drop: return (numerator: 120, denominator: 1)
}

These values are used for encoding frame rate into various file formats such as AAF and Final Cut Pro XML.

It would make sense to add a failable init that can accept these values. Since a drop rate cannot be inferred from a fraction alone, it must be specified imperatively as a boolean flag.

extension Timecode.FrameRate {
    public init?(numerator: Int, denominator: Int, drop: Bool) { }
}

Introduce a `Timecode.Delta` object to allow abstract timecode math

    func testNegativeTimecode() {
        guard let incoming = Timecode("00:59:56:20", at: ._25),
              let start = Timecode("01:00:00:00", at: ._25) else {
            XCTFail()
            return
        }

        let subtracting = incoming - start
        Log.debug(subtracting) // == 00:00:00:00 - this is wrong
        
        let numeric = incoming.realTimeValue.seconds - start.realTimeValue.seconds
        Log.debug(numeric) // -3.199999999999818 -- this is right
        
        let resultTimecode = Timecode(TimeValue(seconds: numeric), at: ._25)
        Log.debug(resultTimecode) // nil - would be nice if it was a negative timecode?
    }

Thoughts?

API change: `.setTimecode(...)` methods `throws` instead of returning `Bool`

Wherever possible, it would be useful to:

  • alter failable init?(...) initializers to becoming throws instead
  • alter any existing .setTimecode(...) methods to becoming throws methods

where the init or method meets this criteria:

  • in scenarios where there may be multiple potential causes for a failure and it would be meaningful to convey a descriptive error
  • (especially .init(String, ...) and .setTimecode(String)

Currently, most of these inits return nil, and in the case of methods return Bool, if the operation succeeded.

There may be one or two methods that simply fail silently if a set operation does not work, in a context where it would be important to have feedback whether the operation succeeded. (Most of those were factored out already). In the case of computed properties, the ability to have a set { } method that throws is a community-requested Swift feature in development but it may be a ways off. So these instances may have to be converted into a function instead of a computed property to mark them as throws.

Update Documentation

  • FrameCount methods
  • clamping / clampingEach API change
  • .stringValueValidatedText() SwiftUI Text
  • misc.

`Timecode`: Add `CMTime` init, `setTimecode()` methods and `cmTime` property

  • Add Timecode(_ exactly: CMTime) init, along with with clamping, wrapping, rawValues overloads.
  • Add Timecode.setTimecode(_ exactly: CMTime), along with with clamping, wrapping, rawValues overloads.
  • Add Timecode.cmTime property to return CMTime.

Missed this in 1.6.0, but could be added for 1.6.1.

Workaround in the meantime:

// CMTime → Timecode
let cmTime = CMTime(value: x, timescale: y)
Timecode(Fraction(cmTime.value, cmTime.timescale), at: ...)

// Timecode → CMTime
let timecode = Timecode(...)
let frac = timecode.rationalValue
let cmTime = CMTime(value: frac.numerator, timescale: frac.denominator)

Formatter coloration issues when focused or not (macOS)

Note, this might be my own user error here, but this is what I see. The formatter when a field editor is present in the field doesn't show errors, they only show when the field isn't the first responder:

image

then unfocus the field:
image

Other ideas:
I'd love to have the formatter select the component that has an error assuming there is just one.
arrow or tab between components:

image

Double click works fine on each component.

I wasn't able to get your Timecode.TextField to work properly, but I am setting your formatter on a NSTextField. Timecode.TextField can't be seen in interface builder for some reason, though you can typealias it to a single object and it will show. I was getting a crash with your textfield object because it is setting a first responder, but it gets confused if the field is in an NSPopover sometimes...

Linux support

  • Explore the possibility of adding Linux as a build target
  • Add Linux job to GitHub CI

Negative rational values

In Final Cut Pro XML (FCPXML) it's possible to encounter "negative" time values.

<chapter-marker start="14417/4s" duration="100/2400s" value="Chapter 1" posterOffset="-80163/720000s"/>

In this case, a chapter marker inserted on a clip has a thumbnail location (posterOffset) that is earlier on the timeline than the marker's position. It's expressed as the delta distance (offset) from the marker's position. In this case, with an additional negative "-" sign prior to it.

This would be formed in TimecodeKit by constructing Fraction(-80163, 720000)

Timecode should be able to process negative rational times and return an instance of TimecodeInterval in the event the time is negative.

Idea behind Timecode.frameRate ?

Hi,

I've read #4 but I'm not sure to understand why it was decided to use enums for frame rate or at least how to generate one from a double or a float?

Because doing this feels strange no?

func toFrameRate(from:Double)-> Timecode.FrameRate{
        switch from {
        case 24.0:
            return ._24
        case 25.0:
            return ._25
        case 60.0:
            return ._60
        default:
            return Timecode.FrameRate._30
        }
    }

Add methods to use a `Timecode` struct as a template

Proposal

In practise, it is often needed to create a new Timecode instance from a value but retain all of the options set on it.

For example, create a new Timecode instance from a realTimeValue (TimeInterval) without having to copy over all of its properties individually that were set (assuming some or all were not defaults).

// set up with non-default options
let tc1 = try Timecode("01:20:15:10",
                       at: ._29_97,
                       limit: ._100days
                       base: ._80SubFrames
                       format: [.showsSubFrames]
)

// currently, if you want to grandfather some or all of the properties
// to a new `Timecode` instance with a new time value,
// you have to copy them over individually:
let tc2 = try Timecode("01:20:15:10",
                       at: tc1.frameRate
                       limit: ._24hours // new upperLimit
                       base: tc1.subFramesBase
                       format: tc1.stringformat
)

When using Timecode in a codebase that deals heavily in manipulating timecode, this API starts to become very redundant and clutter up the code.

Solution 1

As referenced in #34, making all stored properties in Timecode mutable would allow the following:

var tc2 = tc1
tc2.upperLimit = ._24hours

Thus allowing you to use a "reference" or "template" instance of Timecode to derive all its configured properties from while changing one or more attributes, reducing the boilerplate needed.

However, this still requires one to first make a copy then mutate the property/properties.

Also, it does not come with the benefit of using an initializer that may be needed to validate the values with the new parameter(s).

Solution 2

In addition to Solution 1, also introduce a parallel API to add functional methods to create a copy of the instance while changing one or more parameters. Under the hood, they would essentially copy the Timecode struct, then run the corresponding setter on the copy, then return that copy.

This would also be widely useful in scenarios where you receive an immutable Timecode instance and you want to produce a copy with a value modified without needing to create an immutable var copy first.

// parallel `func .settingX() → Timecode` methods to each existing `mutating func .setX()` method

// returns an instance copy if `.setTimecode(:String)` on the copy succeeds.
let tc2 = tc1.settingTimecode("01:20:15:10")

Alternatively, it could be a single method with multiple defaulted parameters.

    /// Creates a new instance by copying the current instance with specified modifications.
    /// If `nil` is passed, that parameter will be inherited from the current instance.
    public func setting(rate: FrameRate? = nil,
                        limit: UpperLimit? = nil,
                        base: SubFramesBase? = nil,
                        format: StringFormat? = nil) -> Timecode
}

This could also inform a complete set of functional methods replicating all of Timecode's inits.

    /// Creates a new instance by copying the current instance with specified modifications.
    /// If `nil` is passed, that parameter will be inherited from the current instance.
    public func setting(_ exactly: FrameCount.Value,
                        at rate: FrameRate? = nil,
                        limit: UpperLimit? = nil,
                        base: SubFramesBase? = nil,
                        format: StringFormat? = nil) -> 

    /// Creates a new instance by copying the current instance with specified modifications.
    /// If `nil` is passed, that parameter will be inherited from the current instance.
    public func setting(_ exactly: FrameCount,
                        at rate: FrameRate? = nil,
                        limit: UpperLimit? = nil,
                        format: StringFormat? = nil) -> Timecode

    /// Creates a new instance by copying the current instance with specified modifications.
    /// If `nil` is passed, that parameter will be inherited from the current instance.
    public func setting(_ exactly: Components,
                        at rate: FrameRate? = nil,
                        limit: UpperLimit? = nil,
                        base: SubFramesBase? = nil,
                        format: StringFormat? = nil) -> Timecode

    /// Creates a new instance by copying the current instance with specified modifications.
    /// If `nil` is passed, that parameter will be inherited from the current instance.
    public func setting(_ exactly: String,
                        at rate: FrameRate? = nil,
                        limit: UpperLimit? = nil,
                        base: SubFramesBase? = nil,
                        format: StringFormat? = nil) -> Timecode

    /// Creates a new instance by copying the current instance with specified modifications.
    /// If `nil` is passed, that parameter will be inherited from the current instance.
    public func setting(realTimeValue: TimeInterval,
                        at rate: FrameRate? = nil,
                        limit: UpperLimit? = nil,
                        base: SubFramesBase? = nil,
                        format: StringFormat? = nil) -> Timecode

    /// Creates a new instance by copying the current instance with specified modifications.
    /// If `nil` is passed, that parameter will be inherited from the current instance.
    public func setting(samples: Double,
                        atSampleRate: Int? = nil,
                        at rate: FrameRate? = nil,
                        limit: UpperLimit? = nil,
                        base: SubFramesBase? = nil,
                        format: StringFormat? = nil) -> Timecode

It is also possible to add an initializer that takes another Timecode instance as a parameter. Its properties would all be inherited except for non-nil parameters.

extension Timecode {
    public init(from: Timecode,
                rate: FrameRate? = nil,
                limit: UpperLimit? = nil,
                base: SubFramesBase? = nil,
                format: StringFormat? = nil) -> Timecode
}

Add `clamping`, `wrapping` overloads for to ancillary value type init/methods

For core value types, the following suite of inits/setters exist:

  • init(_ exactly:) setTimecode(_ exactly:)
  • init(clamping:) setTimecode(clamping:)
  • init(clampingEach:) setTimecode(clampingEach:)
  • init(wrapping:) setTimecode(wrapping:)
  • init(rawValues:) setTimecode(rawValues:) - only applies to component values such as timecode String or TCC

For ancillary types, only init(_ exactly:) / setTimecode(_ exactly:) exist for the most part.

Ideally all ancillary types have a complete set of inits and setters:

  • realTimeValue
  • samplesValue
  • frameCount / frameCountValue

Make all stored properties in `Timecode` struct mutable

Proposal

Make all stored properties in Timecode struct mutable.

Current Conditions

Currently in 1.2.1, certain parameters are immutable and others are mutable.

struct Timecode {
    public let frameRate: FrameRate
    public let upperLimit: UpperLimit
    public let subFramesBase: SubFramesBase

    public var stringFormat: StringFormat
    public var days: Int
    public var hours: Int
    public var minutes: Int
    public var seconds: Int
    public var frames: Int
    public var subFrames: Int
}

Proposed Conditions

struct Timecode {
    public var frameRate: FrameRate
    public var upperLimit: UpperLimit
    public var subFramesBase: SubFramesBase

    public var stringFormat: StringFormat
    public var days: Int
    public var hours: Int
    public var minutes: Int
    public var seconds: Int
    public var frames: Int
    public var subFrames: Int
}

Context

In the earlier days of TimecodeKit, Timecode started as an exclusively immutable value type. This was to:

  • conform to the intuition that initializers that were guaranteed to succeed always produced a valid timecode and could not be changed thereafter
  • and conversely, failable initializers clearly indicated that a timecode was invalid and you were prevented from constructing said Timecode instance
  • validation was was happening actively on component value property setters (.hours, .minutes, etc.) so that invalid values were not allowed to be stored at the given frame rate and upper limit

Then as its functionality grew, it became more evident that less restrictions on the values and mutability could provide more flexibility in practise while also not sacrificing the clarity of how the object works.

The paradigm has now shifted to:

  • initializers still exhibit the same behavior: using overloads that consistently show how the input parameters will be parsed and validated, the outcome remains clear
    • inits that are guaranteed to succeed will always produce a Timecode instance (but not all are guaranteed to produce a valid timecode, it depends on which init is used)
    • inits that are failable will return nil when the inputs are not within valid constraints (or malformed, such as with a timecode String)
    • inits are clearly marked with their expectations (init?(_ exactly:), init(wrapping:), init(clamping:))
  • in addition, init(rawValuesAt:) allows timecode component values to be set without any implicit constraints or validation for a variety of purposes
    • this brings greater flexibility for a range of use-cases while not sacrificing the validation of the standard inits
    • subsequently, instance methods that can then validate or return the timecode values in a variety of ways

To that end, there no longer appears to be a necessity or rationale to enforce that all or some stored properties be immutable. Since no implicit validation occurs on stored properties after the initial initialization of the Timecode struct (ie: component values are not clamped to valid values, and component values are not converted to a new frame rate when the .frameRate property changes, etc.), it is reasonable to assert that making all stored properties mutable would be intuitive and idiomatic.

Frame Rate & Timecode Conversion to/from Rational Numbers (fractions)

Proposal

Various file formats such as AAF and FCP XML (Final Cut Pro) represent frame rates and timecode as rational numbers expressed as a fraction.

FCP XML

  • Frame rate is encoded as a rational number describing the duration of 1 frame (ie: "100/3000s" for 30p video rate)
  • Time locations and durations are encoded as a rational number (ie: "167500/12000s” = 00:00:10:23 @ 24fps)
  • Reference: FCP XML Rational Time Values

Implementation

  • For TimecodeFrameRate and VideoFrameRate, offer an init and property to read and write the fraction values:
    • Rational Frame Rate (ie: 30/1)
      • init?(rationalRate: (numerator: Int, denominator: Int))
      • var rationalRate: (numerator: Int, denominator: Int)
    • Rational Frame Duration (ie: 100/3000)
      • init?(rationalFrameDuration: (numerator: Int, denominator: Int))
      • var rationalFrameDuration: (numerator: Int, denominator: Int)
  • For Timecode, offer an init and property to read and write the fraction values:
    • init?(rational: (numerator: Int, denominator: Int), ...)
    • var rationalValue: (numerator: Int, denominator: Int)

Timecode Transforms - offset, etc.

Thought I'd start another thread as the previous one I think is fairly complete now?

In using your API for a general timecode display in a timeline that is running I also need to supply an optional offset to the timecode. This could be another thing you might want to add in as it's pretty common to need to offer this.

I'm doing it like this right now:

    private var _timecodeOffset: Timecode?
    public var timecodeOffset: Timecode? {
        get {
            _timecodeOffset
        }
        set {
            _timecodeOffset = newValue
            offsetSeconds = newValue?.realTime.seconds ?? 0
        }
    }

    private var offsetSeconds: TimeInterval = 0

    public func timecodeFrom(seconds: TimeInterval) -> Timecode? {
        let value = TimeValue(seconds: seconds + offsetSeconds)
        return value.toTimecode(at: frameRate)
    }

Obviously these functions are being called A LOT, in my case at the screen refresh rate of a CVDisplayLink. That updates a playhead bar as in a DAW and updates time display. I'm looking to do the most minimal amount of calculations per second I can. Any thoughts about doing Timecode offsetting in this way? I'm not sure if worth supporting on your end or not, but it's a common thing at least.

Cocoapods Support

Adding an issue ticket here for myself to add Cocoapods support to the library.

I can do this soon - I'll generate the podspec and then push it to the Cocoapods trunk with my account as I've already done with some of my other repos already.

TCC from fractional seconds

Hi,

Thanks for this library!
I have a few questions:

  • Can TCC be used as a type? Is this the right approach? for instance:
    Timecode can actually be used as a type, I'll read the source to understand it more.

    struct VideoInfos{
        var width:Int?
        var height:Int?
        var codec:AVCodec?
        var timecode:Timecode?
        var duration:Timecode?
        var fps:Double?
        var colorspace:String?
    }

And the main reason for the issue, what would be the best approach to convert a fractional second-based TC to Timecode?
I'm using SwiftFFmpeg and all time values are based on fractional seconds

Thanks

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.