Class: FPS::Timecode

Inherits:
Object
  • Object
show all
Defined in:
lib/fps-timecode.rb

Overview

The Focal Point Systems timecode library has two main purposes.

  • 1.) Given the timecode address of the first frame of a sequence of frames, and given n, what is the timecode address of the nth frame of a sequence?

  • 2.) Given the timecode address of the first frame of a sequence, and given the timecode address for the nth frame of the sequence, what is n?

Example 1. A non-drop-frame 30 fps sequence starts at 00:01:00:00. What is the timecode address of the 100th frame of the sequence? FPS::Timecode.count_to_string(:fps_30_ndf, FPS::Timecode.string_to_count(:fps_30_ndf,“00:01:00:00”) + 100) => “00:01:03:10”

Example 2. A non-drop-frame 30 fps sequence starts at 00:01:00:00. What is n for the frame with the address 00:01:03:10? FPS::Timecode.string_to_count(:fps_30_ndf,“00:01:03:10”) - FPS::Timecode.string_to_count(:fps_30_ndf,“00:01:00:00”) => 100

An instance of the Timecode class represents one timecode address. A timecode mode is always required to be given. The mode determines the frame rate and the dropness (drop-frame or non-drop-frame) of the timecode address. The mode must be one of Timecode::Counts.keys. E.g. :fps_24, :fps_25, :fps_30_df, :fps_30_ndf

Create an instance of a Timecode using either a string in the format “xx:xx:xx:xx” or a frame count. The frame count represents n for the nth frame of a sequence that begins with timecode address “00:00:00:00” (the zeroeth frame of the sequence).

When creating a timecode instance, the default is to use the string argument ignoring the frame count argument and falling back to using the frame count argument if the string is invalid.

Since there are many class methods, often there is no need to create an instance of class Timecode.

Constant Summary collapse

Counts =
{ fps_24:     { fp24h: 2073600, fph: 86400,  fptm: 14400, fpm: 1440, fps: 24 },
  fps_25:     { fp24h: 2160000, fph: 90000,  fptm: 15000, fpm: 1500, fps: 25 },
  fps_30_df:  { fp24h: 2589408, fph: 107892, fptm: 17982, fpm: 1798, fps: 30 },
  fps_30_ndf: { fp24h: 2592000, fph: 108000, fptm: 18000, fpm: 1800, fps: 30 },
  fps_48:     { fp24h: 4147200, fph: 172800, fptm: 28800, fpm: 2880, fps: 48 },
  fps_50:     { fp24h: 4320000, fph: 180000, fptm: 30000, fpm: 3000, fps: 50 },
  fps_60_df:  { fp24h: 5178816, fph: 215784, fptm: 35964, fpm: 3596, fps: 60 },
  fps_60_ndf: { fp24h: 5184000, fph: 216000, fptm: 36000, fpm: 3600, fps: 60 },
}

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(tc_mode, tc_string, tc_count = nil) ⇒ Timecode

initialize

Construct a new instance of FPS::Timecode, given a mode and either a timecode string or a frame count.

Raises ArgumentError



150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
# File 'lib/fps-timecode.rb', line 150

def initialize(tc_mode, tc_string, tc_count = nil)
  # tc_string and tc_count cannot both be nil
  if(tc_string === nil && tc_count === nil)
    raise ArgumentError, "string and count both nil"
  end
  # tc_mode must be given and must be one of the known tc modes
  unless Counts.include?(tc_mode)
    raise ArgumentError,  "invalid timecode mode"
  end

  @tc_mode = tc_mode

  # Try to use the string to construct the instance and fall back
  # to the count if an exception is raised
  begin
    @tc_count = Timecode.string_to_count(@tc_mode, tc_string)
    # always convert back to string because given string may not be drop-frame legal
    @tc_string = Timecode.count_to_string(@tc_mode, @tc_count)
  rescue
    @tc_count = Timecode.normalize(@tc_mode, tc_count)
    @tc_string = Timecode.count_to_string(@tc_mode, @tc_count)
  end

end

Instance Attribute Details

#tc_countObject (readonly)

Returns the value of attribute tc_count.



42
43
44
# File 'lib/fps-timecode.rb', line 42

def tc_count
  @tc_count
end

#tc_modeObject (readonly)

Returns the value of attribute tc_mode.



42
43
44
# File 'lib/fps-timecode.rb', line 42

def tc_mode
  @tc_mode
end

#tc_stringObject (readonly)

Returns the value of attribute tc_string.



42
43
44
# File 'lib/fps-timecode.rb', line 42

def tc_string
  @tc_string
end

Class Method Details

.count_to_string(tc_mode, tc_count, duration = false) ⇒ Object

count_to_string Class method to produce a timecode string from a frame count



56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
# File 'lib/fps-timecode.rb', line 56

def self.count_to_string(tc_mode, tc_count, duration = false)
  tc_count = Timecode.normalize(tc_mode, tc_count)

  counts = Counts[tc_mode]
  hours = tc_count / counts[:fph]
  rem = tc_count % counts[:fph]
  tens_mins = rem / counts[:fptm]
  rem = rem % counts[:fptm]
  units_mins = rem / counts[:fpm]
  rem = rem % counts[:fpm]

  if(duration == false)  # not a duration, do drop-frame processing
      # handle 30 fps drop frame
      if(tc_mode == :fps_30_df && units_mins > 0 && rem <= 1)
        units_mins -= 1
        rem += counts[:fpm]
      end
      # handle 60 fps drop frame
      if(tc_mode == :fps_60_df && units_mins > 0 && rem <= 3)
        units_mins -= 1
        rem += counts[:fpm]
      end
  end

  secs = rem / counts[:fps]
  frms = rem % counts[:fps]

  "%02d:%d%d:%02d:%02d" % [hours, tens_mins, units_mins, secs, frms]
end

.normalize(tc_mode, tc_count) ⇒ Object

Class method to normalize a frame count >= 0 and < 24h. Corrects 24 hour overflow or underflow



112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
# File 'lib/fps-timecode.rb', line 112

def self.normalize(tc_mode, tc_count)
  # tc_mode must be given and must be one of the known tc modes
  unless Counts.include?(tc_mode)
    raise ArgumentError,  "invalid timecode mode"
  end

  unless tc_count.is_a? Fixnum
    raise ArgumentError, "invalid frame count #{tc_count}"
  end
  # normalize to 24 hours
  _24h = Counts[tc_mode][:fp24h]
  while tc_count < 0 do tc_count += _24h end
  while tc_count >= _24h do tc_count -= _24h end
  tc_count
end

.string_as_duration(tc_mode, tc_count) ⇒ Object

Class method to produce a timecode string as a duration from a frame count The difference of two timecodes might be used as a duration. For non-drop frame, the duration string does not differ from the string as a timecode address. But for drop-frame, timecodes that don’t exist as an address can exist as a duration. One minute in drop frame is 1798 frames or 00:00:59:28. “00:01:00:00” as a drop-frame timecode address does not exist. But the difference between 00:01:59:28 and 00:00:59:28 should be displayed as “00:01:00:00” – one minute.



138
139
140
# File 'lib/fps-timecode.rb', line 138

def self.string_as_duration(tc_mode, tc_count)
  Timecode.count_to_string(tc_mode, tc_count, true)
end

.string_to_count(tc_mode, tc_string) ⇒ Object

string_to_count Class method to compute a frame count from a timecode string



88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
# File 'lib/fps-timecode.rb', line 88

def self.string_to_count(tc_mode, tc_string)
  unless Counts.include?(tc_mode)
    raise ArgumentError,  "invalid timecode mode"
  end
  unless tc_string.is_a? String
    raise ArgumentError, "invalid timecode string"
  end
  unless tc_string =~ /\A(\d{2})[:;.](\d{2})[:;.](\d{2})[:;.](\d{2})\Z/
    raise ArgumentError, "invalid timecode string"
  end
  if($1.to_i >= 24 || $2.to_i >= 60 || $3.to_i >= 60 || $4.to_i >= Counts[tc_mode][:fps])
    raise ArgumentError, "invalid timecode string"
  end

  counts = Counts[tc_mode]
  tc_string =~ /\A(\d{2})[:;.](\d)(\d)[:;.](\d{2})[:;.](\d{2})\Z/
  $1.to_i * counts[:fph] +
    $2.to_i * counts[:fptm] +
    $3.to_i * counts[:fpm] +
    $4.to_i * counts[:fps] + $5.to_i
end

Instance Method Details

#<=>(other) ⇒ Object

Compare two timecodes. Comparison (spaceship) operator does string compare



196
197
198
# File 'lib/fps-timecode.rb', line 196

def <=>(other)
  @tc_string <=> other.tc_string
end

#==(other) ⇒ Object

Compare two timecodes for equality. Equality operator does string comparison. Two timecodes may be considered equal even though their modes and counts may be different.



190
191
192
# File 'lib/fps-timecode.rb', line 190

def ==(other)
  @tc_string == other.tc_string
end

#string_as_durationObject

string_as_duration instance method



177
178
179
# File 'lib/fps-timecode.rb', line 177

def string_as_duration
  Timecode.string_as_duration(@tc_mode, @tc_count)
end

#succObject

Return the next timecode address in the sequence



182
183
184
# File 'lib/fps-timecode.rb', line 182

def succ
  Timecode.new(@tc_mode, nil, @tc_count+1)
end