fps-timecode 0.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. checksums.yaml +7 -0
  2. data/doc/FPS/Timecode.html +600 -0
  3. data/doc/FPS.html +100 -0
  4. data/doc/TestFpsTimecode.html +700 -0
  5. data/doc/created.rid +5 -0
  6. data/doc/css/fonts.css +167 -0
  7. data/doc/css/rdoc.css +590 -0
  8. data/doc/fonts/Lato-Light.ttf +0 -0
  9. data/doc/fonts/Lato-LightItalic.ttf +0 -0
  10. data/doc/fonts/Lato-Regular.ttf +0 -0
  11. data/doc/fonts/Lato-RegularItalic.ttf +0 -0
  12. data/doc/fonts/SourceCodePro-Bold.ttf +0 -0
  13. data/doc/fonts/SourceCodePro-Regular.ttf +0 -0
  14. data/doc/fps-timecode_gemspec.html +98 -0
  15. data/doc/images/add.png +0 -0
  16. data/doc/images/arrow_up.png +0 -0
  17. data/doc/images/brick.png +0 -0
  18. data/doc/images/brick_link.png +0 -0
  19. data/doc/images/bug.png +0 -0
  20. data/doc/images/bullet_black.png +0 -0
  21. data/doc/images/bullet_toggle_minus.png +0 -0
  22. data/doc/images/bullet_toggle_plus.png +0 -0
  23. data/doc/images/date.png +0 -0
  24. data/doc/images/delete.png +0 -0
  25. data/doc/images/find.png +0 -0
  26. data/doc/images/loadingAnimation.gif +0 -0
  27. data/doc/images/macFFBgHack.png +0 -0
  28. data/doc/images/package.png +0 -0
  29. data/doc/images/page_green.png +0 -0
  30. data/doc/images/page_white_text.png +0 -0
  31. data/doc/images/page_white_width.png +0 -0
  32. data/doc/images/plugin.png +0 -0
  33. data/doc/images/ruby.png +0 -0
  34. data/doc/images/tag_blue.png +0 -0
  35. data/doc/images/tag_green.png +0 -0
  36. data/doc/images/transparent.png +0 -0
  37. data/doc/images/wrench.png +0 -0
  38. data/doc/images/wrench_orange.png +0 -0
  39. data/doc/images/zoom.png +0 -0
  40. data/doc/index.html +93 -0
  41. data/doc/js/darkfish.js +161 -0
  42. data/doc/js/jquery.js +4 -0
  43. data/doc/js/navigation.js +142 -0
  44. data/doc/js/navigation.js.gz +0 -0
  45. data/doc/js/search.js +109 -0
  46. data/doc/js/search_index.js +1 -0
  47. data/doc/js/search_index.js.gz +0 -0
  48. data/doc/js/searcher.js +228 -0
  49. data/doc/js/searcher.js.gz +0 -0
  50. data/doc/rdoc.css +543 -0
  51. data/doc/table_of_contents.html +177 -0
  52. data/lib/fps-timecode.rb +200 -0
  53. data/test/test_fps-timecode.rb +130 -0
  54. metadata +95 -0
@@ -0,0 +1,200 @@
1
+ # Timecode library
2
+ # Implements Timecode class
3
+ # Version 0.0.2
4
+ #
5
+ # Author: Loran Kary
6
+ # Copyright 2013 Focal Point Software, all rights reserved
7
+
8
+ module FPS # Focal Point Software
9
+
10
+ # A timecode library has two main purposes.
11
+ # 1.) Given the timecode address of the first frame of a sequence of frames,
12
+ # and given n, what is the timecode address of the nth frame of a sequence?
13
+ # 2.) Given the timecode address of the first frame of a sequence, and
14
+ # given the timecode address for the nth frame of the sequence, what is n?
15
+
16
+ # Example 1. A non-drop-frame 30 fps sequence starts at 00:01:00:00. What is
17
+ # the timecode address of the 100th frame of the sequence?
18
+ # FPS::Timecode.count_to_string(:fps_30_ndf,
19
+ # FPS::Timecode.string_to_count(:fps_30_ndf,"00:01:00:00") + 100)
20
+ # => "00:01:03:10"
21
+
22
+ # Example 2. A non-drop-frame 30 fps sequence starts at 00:01:00:00. What is n
23
+ # for the frame with the address 00:01:03:10?
24
+ # FPS::Timecode.string_to_count(:fps_30_ndf,"00:01:03:10") -
25
+ # FPS::Timecode.string_to_count(:fps_30_ndf,"00:01:00:00")
26
+ # => 100
27
+
28
+ # An instance of the Timecode class represents one timecode address.
29
+ # A timecode mode is always required to be given. The mode determines the frame
30
+ # rate and the dropness (drop-frame or non-drop-frame) of the timecode address.
31
+ # The mode must be one of Timecode::Counts.keys. E.g. :fps_24, :fps_25.
32
+ # Create an instance of a Timecode using either a string in the format "xx:xx:xx:xx"
33
+ # or a frame count. The frame count represents n for the nth frame of a sequence
34
+ # that begins with timecode address "00:00:00:00" (the zeroeth frame of the sequence).
35
+ # When creating a timecode instance, default to using the string argument
36
+ # and fall back to using the frame count argument if the string is invalid.
37
+
38
+ # Since there are many class methods, often there is no need to create
39
+ # an instance of class Timecode.
40
+
41
+ class Timecode
42
+
43
+ attr_reader :tc_count, :tc_string, :tc_mode
44
+
45
+ Counts = { fps_24: { fp24h: 2073600, fph: 86400, fptm: 14400, fpm: 1440, fps: 24 },
46
+ fps_25: { fp24h: 2160000, fph: 90000, fptm: 15000, fpm: 1500, fps: 25 },
47
+ fps_30_df: { fp24h: 2589408, fph: 107892, fptm: 17982, fpm: 1798, fps: 30 },
48
+ fps_30_ndf: { fp24h: 2592000, fph: 108000, fptm: 18000, fpm: 1800, fps: 30 },
49
+ fps_48: { fp24h: 4147200, fph: 172800, fptm: 28800, fpm: 2880, fps: 48 },
50
+ fps_50: { fp24h: 4320000, fph: 180000, fptm: 30000, fpm: 3000, fps: 50 },
51
+ fps_60_df: { fp24h: 5178816, fph: 215784, fptm: 35964, fpm: 3596, fps: 60 },
52
+ fps_60_ndf: { fp24h: 5184000, fph: 216000, fptm: 36000, fpm: 3600, fps: 60 },
53
+ }
54
+
55
+ # count_to_string
56
+ # Class method to compute a string from a frame count
57
+ def self.count_to_string(tc_mode, tc_count, duration = false)
58
+ tc_count = Timecode.normalize(tc_mode, tc_count)
59
+
60
+ counts = Counts[tc_mode]
61
+ hours = tc_count / counts[:fph]
62
+ rem = tc_count % counts[:fph]
63
+ tens_mins = rem / counts[:fptm]
64
+ rem = rem % counts[:fptm]
65
+ units_mins = rem / counts[:fpm]
66
+ rem = rem % counts[:fpm]
67
+
68
+ if(duration == false) # not a duration, do drop-frame processing
69
+ # handle 30 fps drop frame
70
+ if(tc_mode == :fps_30_df && units_mins > 0 && rem <= 1)
71
+ units_mins -= 1
72
+ rem += counts[:fpm]
73
+ end
74
+ # handle 60 fps drop frame
75
+ if(tc_mode == :fps_60_df && units_mins > 0 && rem <= 3)
76
+ units_mins -= 1
77
+ rem += counts[:fpm]
78
+ end
79
+ end
80
+
81
+ secs = rem / counts[:fps]
82
+ frms = rem % counts[:fps]
83
+
84
+ "%02d:%d%d:%02d:%02d" % [hours, tens_mins, units_mins, secs, frms]
85
+ end
86
+
87
+ # string_to_count
88
+ # Class method to compute a count from a string
89
+ def self.string_to_count(tc_mode, tc_string)
90
+ unless Counts.include?(tc_mode)
91
+ raise ArgumentError, "invalid timecode mode"
92
+ end
93
+ unless tc_string.is_a? String
94
+ raise ArgumentError, "invalid timecode string"
95
+ end
96
+ unless tc_string =~ /\A(\d{2})[:;.](\d{2})[:;.](\d{2})[:;.](\d{2})\Z/
97
+ raise ArgumentError, "invalid timecode string"
98
+ end
99
+ if($1.to_i >= 24 || $2.to_i >= 60 || $3.to_i >= 60 || $4.to_i >= Counts[tc_mode][:fps])
100
+ raise ArgumentError, "invalid timecode string"
101
+ end
102
+
103
+ counts = Counts[tc_mode]
104
+ tc_string =~ /\A(\d{2})[:;.](\d)(\d)[:;.](\d{2})[:;.](\d{2})\Z/
105
+ $1.to_i * counts[:fph] +
106
+ $2.to_i * counts[:fptm] +
107
+ $3.to_i * counts[:fpm] +
108
+ $4.to_i * counts[:fps] + $5.to_i
109
+ end
110
+
111
+ # Class method to normalize a frame count >= 0 and < 24h
112
+ # Correct 24 hour overflow or underflow
113
+ def self.normalize(tc_mode, tc_count)
114
+ # tc_mode must be given and must be one of the known tc modes
115
+ unless Counts.include?(tc_mode)
116
+ raise ArgumentError, "invalid timecode mode"
117
+ end
118
+
119
+ unless tc_count.is_a? Fixnum
120
+ raise ArgumentError, "invalid frame count #{tc_count}"
121
+ end
122
+ # normalize to 24 hours
123
+ _24h = Counts[tc_mode][:fp24h]
124
+ while tc_count < 0 do tc_count += _24h end
125
+ while tc_count >= _24h do tc_count -= _24h end
126
+ tc_count
127
+ end
128
+
129
+ # Class method to compute a string as a duration from a frame count_to_string
130
+ def self.string_as_duration(tc_mode, tc_count)
131
+ Timecode.count_to_string(tc_mode, tc_count, true)
132
+ end
133
+
134
+
135
+
136
+ # initialize
137
+ # Construct a new instance of Timecode, given a mode and either a
138
+ # timecode string or a frame count.
139
+ # Raises ArgumentError
140
+ def initialize(tc_mode, tc_string, tc_count = nil)
141
+ # tc_string and tc_count cannot both be nil
142
+ if(tc_string === nil && tc_count === nil)
143
+ raise ArgumentError, "string and count both nil"
144
+ end
145
+ # tc_mode must be given and must be one of the known tc modes
146
+ unless Counts.include?(tc_mode)
147
+ raise ArgumentError, "invalid timecode mode"
148
+ end
149
+
150
+ @tc_mode = tc_mode
151
+
152
+ # Try to use the string to construct the instance and fall back
153
+ # to the count if an exception is raised
154
+ begin
155
+ @tc_count = Timecode.string_to_count(@tc_mode, tc_string)
156
+ # always convert back to string because given string may not be drop-frame legal
157
+ @tc_string = Timecode.count_to_string(@tc_mode, @tc_count)
158
+ rescue
159
+ @tc_count = Timecode.normalize(@tc_mode, tc_count)
160
+ @tc_string = Timecode.count_to_string(@tc_mode, @tc_count)
161
+ end
162
+
163
+ end #initialize
164
+
165
+ # string_as_duration
166
+ # The difference of two timecodes might be used as a duration.
167
+ # For non-drop frame, the duration string does not differ from the
168
+ # string as a timecode address. But for drop-frame, timecodes
169
+ # that don't exist as an address can exist as a duration.
170
+ # One minute in drop frame is 1798 frames or 00:00:59:28.
171
+ # "00:01:00:00" as a drop-frame timecode address does not exist.
172
+ # But the difference between 00:01:59:28 and 00:00:59:28
173
+ # should be displayed as "00:01:00:00" -- one minute.
174
+ #
175
+ def string_as_duration
176
+ Timecode.string_as_duration(@tc_mode, @tc_count)
177
+ end
178
+
179
+ # succ
180
+ # return the next timecode address in the sequence
181
+ def succ
182
+ Timecode.new(@tc_mode, nil, @tc_count+1)
183
+ end
184
+
185
+ # compare two timecodes for equality
186
+ # equality operator does string compare
187
+ # two timecodes may be considered equal even though their modes and counts
188
+ # may be different.
189
+ def ==(other)
190
+ @tc_string == other.tc_string
191
+ end
192
+
193
+ # compare two timecodes
194
+ # comparison (spaceship) operator does string compare
195
+ def <=>(other)
196
+ @tc_string <=> other.tc_string
197
+ end
198
+
199
+ end #class
200
+ end #module
@@ -0,0 +1,130 @@
1
+ # Timecode test suite for Timecode class
2
+ # Author: Loran Kary, Focal Point Software
3
+ # to run from parent directory, "ruby -I lib test/test_fps-timecode.rb"
4
+
5
+
6
+ require 'fps-timecode'
7
+
8
+ require 'test/unit'
9
+
10
+ class TestFpsTimecode < Test::Unit::TestCase
11
+
12
+ def test_create_legally_doesnt_fail
13
+ assert_nothing_raised do
14
+ FPS::Timecode.new(:fps_24, nil, 1000)
15
+ FPS::Timecode.new(:fps_30_ndf, "00:01:00:00", nil)
16
+ FPS::Timecode.new(:fps_25, "00:01:00:00")
17
+ FPS::Timecode.new(:fps_30_df, "00:02:00:00", 1798*2)
18
+ end
19
+ end
20
+
21
+ def test_create_must_have_valid_tc_mode
22
+ assert_raises(ArgumentError) do
23
+ FPS::Timecode.new(:not_a_valid_tc_mode, nil, 1000)
24
+ end
25
+ end
26
+
27
+ def test_create_string_and_count_cannot_both_be_nil
28
+ assert_raises(ArgumentError) do
29
+ FPS::Timecode.new(:fps_30_ndf, nil, nil)
30
+ end
31
+ end
32
+
33
+ def test_create_use_string_by_default
34
+ assert_equal "00:01:00:00", FPS::Timecode.new(:fps_30_ndf, "00:01:00:00", 0).tc_string
35
+ end
36
+
37
+ def test_create_fall_back_to_count_when_string_is_invalid
38
+ assert_equal "00:01:00:00", FPS::Timecode.new(:fps_30_ndf, "invalid", 1800).tc_string
39
+ end
40
+
41
+ def test_create_count_if_given_must_be_fixnum
42
+ assert_raises(ArgumentError) do
43
+ FPS::Timecode.new(:fps_30_ndf, nil, "1800")
44
+ end
45
+ end
46
+
47
+ def test_create_negative_counts_will_be_normalized
48
+ assert_equal "23:59:00:00", FPS::Timecode.new(:fps_30_ndf, nil, -1800).tc_string
49
+ end
50
+
51
+ def test_create_counts_greater_than_24_hours_will_be_normalized
52
+ assert_equal "00:01:00:00",
53
+ FPS::Timecode.new(:fps_30_ndf, nil, FPS::Timecode::Counts[:fps_30_ndf][:fp24h] + 1800).tc_string
54
+ end
55
+
56
+ def test_create_string_must_be_valid_if_no_count_given
57
+ assert_raises(ArgumentError) do
58
+ FPS::Timecode.new(:fps_30_ndf, "invalid", nil)
59
+ end
60
+ end
61
+
62
+ def test_create_string_all_fields_must_be_within_bounds # e.g. 24:60:60:30 for fps 30
63
+ assert_nothing_raised do
64
+ FPS::Timecode.new(:fps_30_ndf, "23:59:59:29", nil)
65
+ end
66
+ assert_raises(ArgumentError) do
67
+ FPS::Timecode.new(:fps_30_ndf, "23:59:59:30", nil)
68
+ end
69
+ assert_raises(ArgumentError) do
70
+ FPS::Timecode.new(:fps_30_ndf, "24:00:00:00", nil)
71
+ end
72
+ end
73
+
74
+ def test_create_nonexistent_drop_frame_times_will_be_corrected
75
+ # "00:01:00:00" doesn't exist in drop-frame
76
+ assert_equal("00:00:59:28",
77
+ FPS::Timecode.new(:fps_30_df, "00:01:00:00", nil).tc_string)
78
+ # "00:10:00:00" does exist in drop-frame
79
+ assert_equal("00:10:00:00",
80
+ FPS::Timecode.new(:fps_30_df, "00:10:00:00", nil).tc_string)
81
+ end
82
+
83
+ def test_comparison_works_on_strings_regardless_of_counts
84
+ # 00:10:00:00 drop-frame is equal to 00:10:00:00 non-drop
85
+ # despite different frame counts
86
+ assert_equal true,
87
+ FPS::Timecode.new(:fps_30_ndf, nil, FPS::Timecode::Counts[:fps_30_ndf][:fptm]) ==
88
+ FPS::Timecode.new(:fps_30_df, nil, FPS::Timecode::Counts[:fps_30_df][:fptm])
89
+ assert_equal 0,
90
+ FPS::Timecode.new(:fps_30_ndf, nil, FPS::Timecode::Counts[:fps_30_ndf][:fptm]) <=>
91
+ FPS::Timecode.new(:fps_30_df, nil, FPS::Timecode::Counts[:fps_30_df][:fptm])
92
+ end
93
+
94
+ def test_dropframe_string_as_duration
95
+ a = FPS::Timecode.new(:fps_30_df, "00:01:59:28", nil)
96
+ b = FPS::Timecode.new(:fps_30_df, "00:00:59:28", nil)
97
+ difference = a.tc_count - b.tc_count
98
+ assert_equal 1798, difference
99
+ assert_equal "00:01:00:00",
100
+ FPS::Timecode.new(:fps_30_df, nil, difference).string_as_duration
101
+ end
102
+
103
+ def test_succ
104
+ a = []
105
+ b = FPS::Timecode.new(:fps_30_df, "00:01:00:00", nil)
106
+ 4.times do
107
+ a << b.tc_string
108
+ b = b.succ
109
+ end
110
+ assert_equal ["00:00:59:28", "00:00:59:29", "00:01:00:02", "00:01:00:03"], a
111
+ a.clear
112
+ b = FPS::Timecode.new(:fps_60_df, "00:01:00:00", nil)
113
+ 6.times do
114
+ a << b.tc_string
115
+ b = b.succ
116
+ end
117
+ assert_equal ["00:00:59:56", "00:00:59:57", "00:00:59:58", "00:00:59:59",
118
+ "00:01:00:04", "00:01:00:05"], a
119
+ end
120
+
121
+ def test_class_methods
122
+ assert_equal "00:01:00:00", FPS::Timecode.count_to_string(:fps_30_ndf, 1800)
123
+ assert_equal 1800, FPS::Timecode.string_to_count(:fps_30_ndf, "00:01:00:00")
124
+ assert_equal 1800, FPS::Timecode.normalize(:fps_30_ndf, FPS::Timecode::Counts[:fps_30_ndf][:fp24h] + 1800)
125
+ assert_equal FPS::Timecode::Counts[:fps_30_ndf][:fp24h] - 1800, FPS::Timecode.normalize(:fps_30_ndf, -1800)
126
+ assert_equal "00:01:00:00", FPS::Timecode.string_as_duration(:fps_30_df, 1798)
127
+ end
128
+
129
+
130
+ end
metadata ADDED
@@ -0,0 +1,95 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: fps-timecode
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.3
5
+ platform: ruby
6
+ authors:
7
+ - Loran Kary
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2018-01-29 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: A library to support drop-frame and non-drop-frame timecodes
14
+ email: lkary@focalpnt.com
15
+ executables: []
16
+ extensions: []
17
+ extra_rdoc_files: []
18
+ files:
19
+ - doc/FPS.html
20
+ - doc/FPS/Timecode.html
21
+ - doc/TestFpsTimecode.html
22
+ - doc/created.rid
23
+ - doc/css/fonts.css
24
+ - doc/css/rdoc.css
25
+ - doc/fonts/Lato-Light.ttf
26
+ - doc/fonts/Lato-LightItalic.ttf
27
+ - doc/fonts/Lato-Regular.ttf
28
+ - doc/fonts/Lato-RegularItalic.ttf
29
+ - doc/fonts/SourceCodePro-Bold.ttf
30
+ - doc/fonts/SourceCodePro-Regular.ttf
31
+ - doc/fps-timecode_gemspec.html
32
+ - doc/images/add.png
33
+ - doc/images/arrow_up.png
34
+ - doc/images/brick.png
35
+ - doc/images/brick_link.png
36
+ - doc/images/bug.png
37
+ - doc/images/bullet_black.png
38
+ - doc/images/bullet_toggle_minus.png
39
+ - doc/images/bullet_toggle_plus.png
40
+ - doc/images/date.png
41
+ - doc/images/delete.png
42
+ - doc/images/find.png
43
+ - doc/images/loadingAnimation.gif
44
+ - doc/images/macFFBgHack.png
45
+ - doc/images/package.png
46
+ - doc/images/page_green.png
47
+ - doc/images/page_white_text.png
48
+ - doc/images/page_white_width.png
49
+ - doc/images/plugin.png
50
+ - doc/images/ruby.png
51
+ - doc/images/tag_blue.png
52
+ - doc/images/tag_green.png
53
+ - doc/images/transparent.png
54
+ - doc/images/wrench.png
55
+ - doc/images/wrench_orange.png
56
+ - doc/images/zoom.png
57
+ - doc/index.html
58
+ - doc/js/darkfish.js
59
+ - doc/js/jquery.js
60
+ - doc/js/navigation.js
61
+ - doc/js/navigation.js.gz
62
+ - doc/js/search.js
63
+ - doc/js/search_index.js
64
+ - doc/js/search_index.js.gz
65
+ - doc/js/searcher.js
66
+ - doc/js/searcher.js.gz
67
+ - doc/rdoc.css
68
+ - doc/table_of_contents.html
69
+ - lib/fps-timecode.rb
70
+ - test/test_fps-timecode.rb
71
+ homepage: http://focalpnt.com
72
+ licenses: []
73
+ metadata: {}
74
+ post_install_message:
75
+ rdoc_options: []
76
+ require_paths:
77
+ - lib
78
+ required_ruby_version: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '1.9'
83
+ required_rubygems_version: !ruby/object:Gem::Requirement
84
+ requirements:
85
+ - - ">="
86
+ - !ruby/object:Gem::Version
87
+ version: '0'
88
+ requirements: []
89
+ rubyforge_project:
90
+ rubygems_version: 2.7.4
91
+ signing_key:
92
+ specification_version: 4
93
+ summary: Implements timecode class
94
+ test_files:
95
+ - test/test_fps-timecode.rb