edl 0.0.1 → 0.0.2
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.
- data/History.txt +11 -0
- data/Manifest.txt +25 -0
- data/README.txt +12 -1
- data/Rakefile +6 -1
- data/SPECS.txt +75 -0
- data/bin/edl +0 -0
- data/illustr/edl-explain.ai +1182 -2
- data/lib/edl.rb +66 -146
- data/lib/edl/cutter.rb +2 -2
- data/lib/edl/event.rb +132 -0
- data/lib/edl/grabber.rb +2 -3
- data/lib/edl/timewarp.rb +45 -0
- data/lib/edl/transition.rb +23 -0
- data/test/.DS_Store +0 -0
- data/test/samples/FCP_REVERSE.EDL +9 -0
- data/test/samples/SPEEDUP_AND_FADEOUT.EDL +11 -0
- data/test/samples/SPEEDUP_REVERSE_AND_FADEOUT.EDL +11 -0
- data/test/samples/SPLICEME.EDL +1 -4
- data/test/test_edl.rb +389 -169
- metadata +32 -9
data/lib/edl/grabber.rb
CHANGED
|
@@ -2,7 +2,7 @@ module EDL
|
|
|
2
2
|
# When initialized with a file and passed an EDL, will generate thumbnail images
|
|
3
3
|
# of the first frame of every event. It is assumed that the movie file starts at the same
|
|
4
4
|
# frame as the first EDL event.
|
|
5
|
-
class Grabber
|
|
5
|
+
class Grabber #:nodoc:
|
|
6
6
|
attr_accessor :ffmpeg_bin, :offset
|
|
7
7
|
def initialize(with_file)
|
|
8
8
|
@source_path = with_file
|
|
@@ -22,8 +22,7 @@ module EDL
|
|
|
22
22
|
end
|
|
23
23
|
|
|
24
24
|
def generate_grab(evt, at, to_file)
|
|
25
|
-
|
|
26
|
-
cmd = "#{ffmpeg_bin} -i #{@source_path} -an -ss #{at} -vframes 1 -y #{to_file}%d.jpg"
|
|
25
|
+
cmd = "#{ffmpeg_bin} -i #{@source_path} -an -ss #{at.with_frames_as_fraction} -vframes 1 -y #{to_file}%d.jpg"
|
|
27
26
|
`#{cmd}`
|
|
28
27
|
end
|
|
29
28
|
end
|
data/lib/edl/timewarp.rb
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
module EDL
|
|
2
|
+
# Represents a timewarp. Will be placed in EDL::Event#timewarp
|
|
3
|
+
# For a reversed clip, the source start we get from the EDL in the src start
|
|
4
|
+
# is the LAST used frame. For a pos rate clip, the source start is the bona fide source start. Nice eh?
|
|
5
|
+
class Timewarp
|
|
6
|
+
|
|
7
|
+
# What is the actual framerate of the clip (float)
|
|
8
|
+
attr_accessor :actual_framerate
|
|
9
|
+
attr_accessor :clip #:nodoc:
|
|
10
|
+
|
|
11
|
+
# Does this timewarp reverse the clip?
|
|
12
|
+
def reverse?
|
|
13
|
+
@actual_framerate < 0
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# Get the speed in percent
|
|
17
|
+
def speed_in_percent
|
|
18
|
+
(@actual_framerate / @clip.rec_start_tc.fps) * 100
|
|
19
|
+
end
|
|
20
|
+
alias_method :speed, :speed_in_percent
|
|
21
|
+
|
|
22
|
+
# Compute the length of the clip we need to capture. The length is computed in frames and
|
|
23
|
+
# is always rounded up (better one frame more than one frame less!)
|
|
24
|
+
def actual_length_of_source
|
|
25
|
+
# First, get the length of the clip including a transition. This is what we are scaled to.
|
|
26
|
+
target_len = @clip.rec_length_with_transition.to_f
|
|
27
|
+
# Determine the framerate scaling factor, this is the speed
|
|
28
|
+
factor = @actual_framerate / @clip.rec_start_tc.fps
|
|
29
|
+
(target_len * factor).ceil.abs
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# What is the starting frame for the captured clip? If we are a reverse, then the src start of the
|
|
33
|
+
# clip is our LAST frame, otherwise it's the first
|
|
34
|
+
def source_used_from
|
|
35
|
+
# TODO: account for the 2 frame deficiency which is suspicious
|
|
36
|
+
compensation = 2
|
|
37
|
+
reverse? ? (@clip.src_start_tc - actual_length_of_source + compensation) : @clip.src_start_tc
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Where to end the capture? This is also dependent on whether we are a reverse or not
|
|
41
|
+
def source_used_upto
|
|
42
|
+
source_used_from + actual_length_of_source
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
module EDL
|
|
2
|
+
# Represents a transition. We currently only support dissolves and SMPTE wipes
|
|
3
|
+
# Will be avilable as EDL::Clip#transition
|
|
4
|
+
class Transition
|
|
5
|
+
|
|
6
|
+
# Length of the transition in frames
|
|
7
|
+
attr_accessor :duration
|
|
8
|
+
|
|
9
|
+
# Which effect is used (like CROSS DISSOLVE)
|
|
10
|
+
attr_accessor :effect
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# Represents a dissolve
|
|
14
|
+
class Dissolve < Transition
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# Represents an SMPTE wipe
|
|
18
|
+
class Wipe < Transition
|
|
19
|
+
|
|
20
|
+
# Which SMPTE wipe is needed
|
|
21
|
+
attr_accessor :smpte_wipe_index
|
|
22
|
+
end
|
|
23
|
+
end
|
data/test/.DS_Store
ADDED
|
Binary file
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
TITLE: SPEEDUP_AND_FADEOUT
|
|
2
|
+
FCM: NON-DROP FRAME
|
|
3
|
+
|
|
4
|
+
001 FOO V C 01:00:00:00 01:00:27:14 01:00:00:00 01:00:27:14
|
|
5
|
+
001 BL V D 025 00:00:00:00 00:00:01:00 01:00:27:14 01:00:28:14
|
|
6
|
+
* EFFECT NAME: CROSS DISSOLVE
|
|
7
|
+
* FROM CLIP NAME: BARS_40S.MOV
|
|
8
|
+
* COMMENT:
|
|
9
|
+
M2 FOO 035.0 01:00:00:00
|
|
10
|
+
|
|
11
|
+
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
TITLE: TESTREV
|
|
2
|
+
FCM: NON-DROP FRAME
|
|
3
|
+
|
|
4
|
+
001 FOO V C 01:00:39:23 01:01:07:12 01:00:00:00 01:00:27:14
|
|
5
|
+
001 BL V D 025 00:00:00:00 00:00:01:00 01:00:27:14 01:00:28:14
|
|
6
|
+
* EFFECT NAME: CROSS DISSOLVE
|
|
7
|
+
* FROM CLIP NAME: BARS_40S.MOV
|
|
8
|
+
* COMMENT:
|
|
9
|
+
M2 FOO -035.0 01:00:39:23
|
|
10
|
+
|
|
11
|
+
|
data/test/samples/SPLICEME.EDL
CHANGED
|
@@ -2,7 +2,4 @@ TITLE: SPLICEME
|
|
|
2
2
|
FCM: NON-DROP FRAME
|
|
3
3
|
|
|
4
4
|
001 006I V C 06:42:50:18 06:42:52:13 01:00:00:00 01:00:01:20
|
|
5
|
-
|
|
6
|
-
002 006I V C 06:42:52:14 06:42:52:16 01:00:01:21 01:00:01:23
|
|
7
|
-
|
|
8
|
-
003 006I V C 06:42:52:17 06:42:52:24 01:00:01:24 01:00:02:06
|
|
5
|
+
002 006I V C 06:42:52:14 06:42:52:16 01:00:10:21 01:00:10:23
|
data/test/test_edl.rb
CHANGED
|
@@ -4,155 +4,298 @@ require File.dirname(__FILE__) + '/../lib/edl/grabber'
|
|
|
4
4
|
|
|
5
5
|
require 'rubygems'
|
|
6
6
|
require 'test/unit'
|
|
7
|
+
require 'test/spec'
|
|
7
8
|
require 'flexmock'
|
|
8
9
|
require 'flexmock/test_unit'
|
|
9
10
|
|
|
10
|
-
TRAILER_EDL
|
|
11
|
-
SIMPLE_DISSOLVE
|
|
12
|
-
SPLICEME
|
|
13
|
-
SIMPLE_TIMEWARP
|
|
14
|
-
SLOMO_TIMEWARP
|
|
15
|
-
FORTY_FIVER
|
|
16
|
-
|
|
11
|
+
TRAILER_EDL = File.dirname(__FILE__) + '/samples/TRAILER_EDL.edl'
|
|
12
|
+
SIMPLE_DISSOLVE = File.dirname(__FILE__) + '/samples/SIMPLE_DISSOLVE.EDL'
|
|
13
|
+
SPLICEME = File.dirname(__FILE__) + '/samples/SPLICEME.EDL'
|
|
14
|
+
SIMPLE_TIMEWARP = File.dirname(__FILE__) + '/samples/TIMEWARP.EDL'
|
|
15
|
+
SLOMO_TIMEWARP = File.dirname(__FILE__) + '/samples/TIMEWARP_HALF.EDL'
|
|
16
|
+
FORTY_FIVER = File.dirname(__FILE__) + '/samples/45S_SAMPLE.EDL'
|
|
17
|
+
AVID_REVERSE = File.dirname(__FILE__) + '/samples/REVERSE.EDL'
|
|
18
|
+
SPEEDUP_AND_FADEOUT = File.dirname(__FILE__) + '/samples/SPEEDUP_AND_FADEOUT.EDL'
|
|
19
|
+
SPEEDUP_REVERSE_AND_FADEOUT = File.dirname(__FILE__) + '/samples/SPEEDUP_REVERSE_AND_FADEOUT.EDL'
|
|
20
|
+
FCP_REVERSE = File.dirname(__FILE__) + '/samples/FCP_REVERSE.EDL'
|
|
17
21
|
|
|
18
|
-
class
|
|
19
|
-
def
|
|
22
|
+
class String
|
|
23
|
+
def tc(fps = Timecode::DEFAULT_FPS)
|
|
24
|
+
Timecode.parse(self, fps)
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
context "An Event should" do
|
|
29
|
+
specify "define the needed attributes" do
|
|
20
30
|
evt = EDL::Event.new
|
|
21
31
|
%w( num reel track src_start_tc src_end_tc rec_start_tc rec_end_tc ).each do | em |
|
|
22
|
-
|
|
32
|
+
evt.should.respond_to em
|
|
23
33
|
end
|
|
24
34
|
end
|
|
25
|
-
|
|
35
|
+
|
|
36
|
+
specify "support hash initialization" do
|
|
37
|
+
evt = EDL::Event.new(:src_start_tc => "01:00:00:00".tc)
|
|
38
|
+
evt.src_start_tc.should.equal "01:00:00:00".tc
|
|
39
|
+
end
|
|
26
40
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
41
|
+
specify "support block initialization" do
|
|
42
|
+
evt = EDL::Event.new do | e |
|
|
43
|
+
e.src_start_tc = "01:00:00:04".tc
|
|
44
|
+
end
|
|
45
|
+
evt.src_start_tc.should.equal "01:00:00:04".tc
|
|
32
46
|
end
|
|
33
|
-
end
|
|
34
47
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
48
|
+
specify "respond to ends_with_transition? with false if outgoing_transition_duration is zero" do
|
|
49
|
+
evt = EDL::Event.new
|
|
50
|
+
evt.outgoing_transition_duration = 0
|
|
51
|
+
evt.ends_with_transition?.should.equal false
|
|
38
52
|
end
|
|
39
53
|
|
|
54
|
+
specify "respond to ends_with_transition? with true if outgoing_transition_duration set above zero" do
|
|
55
|
+
evt = EDL::Event.new
|
|
56
|
+
evt.outgoing_transition_duration = 24
|
|
57
|
+
evt.ends_with_transition?.should.equal true
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
specify "respond to has_timewarp? with false if no timewarp assigned" do
|
|
61
|
+
evt = EDL::Event.new(:timewarp => nil)
|
|
62
|
+
evt.has_timewarp?.should.equal false
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
specify "respond to has_timewarp? with true if a timewarp is assigned" do
|
|
66
|
+
evt = EDL::Event.new(:timewarp => true)
|
|
67
|
+
evt.has_timewarp?.should.equal true
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
specify "report rec_length as a difference of record timecodes" do
|
|
71
|
+
evt = EDL::Event.new(:rec_start_tc => "1h".tc, :rec_end_tc => "1h 10s 2f".tc )
|
|
72
|
+
evt.rec_length.should.equal "10s 2f".tc.to_i
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
specify "report rec_length_with_transition as a difference of record timecodes if no transition set" do
|
|
76
|
+
evt = EDL::Event.new(:rec_start_tc => "1h".tc, :rec_end_tc => "1h 10s 2f".tc, :outgoing_transition_duration => 0)
|
|
77
|
+
evt.rec_length_with_transition.should.equal "10s 2f".tc.to_i
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
specify "add transition length to rec_length_with_transition if a transition is set" do
|
|
81
|
+
evt = EDL::Event.new(:rec_start_tc => "1h".tc, :rec_end_tc => "1h 10s 2f".tc, :outgoing_transition_duration => 10)
|
|
82
|
+
evt.rec_length_with_transition.should.equal("10s 2f".tc.to_i + 10)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
specify "return a default array for comments" do
|
|
86
|
+
EDL::Event.new.comments.should.be.kind_of Enumerable
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
specify "respond false to has_transition? if incoming transition is set" do
|
|
90
|
+
EDL::Event.new(:transition => nil).has_transition?.should.equal false
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
specify "respond true to has_transition? if incoming transition is set" do
|
|
94
|
+
EDL::Event.new(:transition => true).has_transition?.should.equal true
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
specify "respond true to black? if reel is BL" do
|
|
98
|
+
EDL::Event.new(:reel => "BL").should.black
|
|
99
|
+
EDL::Event.new(:reel => "001").should.not.black
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
specify "respond true to generator? if reel is BL or AX" do
|
|
103
|
+
EDL::Event.new(:reel => "BL").should.generator
|
|
104
|
+
EDL::Event.new(:reel => "AX").should.generator
|
|
105
|
+
EDL::Event.new(:reel => "001").should.not.generator
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
specify "report src_length as rec_length_with_transition" do
|
|
109
|
+
e = EDL::Event.new(:rec_start_tc => "2h".tc, :rec_end_tc => "2h 2s".tc)
|
|
110
|
+
e.src_length.should.equal "2s".tc.to_i
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
specify "support capture_length as an alias to src_length" do
|
|
114
|
+
tw = flexmock
|
|
115
|
+
tw.should_receive(:actual_length_of_source).and_return(:something)
|
|
116
|
+
e = EDL::Event.new(:timewarp => tw)
|
|
117
|
+
e.src_length.should.equal e.capture_length
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
specify "delegate src_length to the timewarp if it is there" do
|
|
121
|
+
tw = flexmock
|
|
122
|
+
tw.should_receive(:actual_length_of_source).and_return(:something).once
|
|
123
|
+
e = EDL::Event.new(:timewarp => tw)
|
|
124
|
+
e.src_length.should.equal :something
|
|
125
|
+
end
|
|
40
126
|
|
|
41
|
-
|
|
127
|
+
specify "report reverse? and reversed? based on the timewarp" do
|
|
128
|
+
e = EDL::Event.new(:timewarp => nil)
|
|
129
|
+
e.should.not.be.reverse
|
|
130
|
+
e.should.not.be.reversed
|
|
131
|
+
|
|
132
|
+
tw = flexmock
|
|
133
|
+
tw.should_receive(:reverse?).and_return(true)
|
|
134
|
+
|
|
135
|
+
e = EDL::Event.new(:timewarp => tw)
|
|
136
|
+
e.should.be.reverse
|
|
137
|
+
e.should.be.reversed
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
context "A Parser should" do
|
|
142
|
+
|
|
143
|
+
specify "store the passed framerate" do
|
|
144
|
+
p = EDL::Parser.new(45)
|
|
145
|
+
p.should.respond_to :fps
|
|
146
|
+
p.fps.should.equal 45
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
specify "return matchers tuned with the passed framerate" do
|
|
42
150
|
p = EDL::Parser.new(30)
|
|
43
151
|
matchers = p.get_matchers
|
|
44
152
|
event_matcher = matchers.find{|e| e.is_a?(EDL::EventMatcher) }
|
|
45
|
-
|
|
153
|
+
event_matcher.fps.should.equal 30
|
|
46
154
|
end
|
|
47
155
|
|
|
48
|
-
|
|
156
|
+
specify "create a Timecode from stringified elements" do
|
|
49
157
|
elems = ["08", "04", "24", "24"]
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
158
|
+
lambda{ @tc = EDL::Parser.timecode_from_line_elements(elems, 30) }.should.not.raise
|
|
159
|
+
|
|
160
|
+
@tc.should.be.kind_of Timecode
|
|
161
|
+
@tc.should.equal "08:04:24:24".tc(30)
|
|
162
|
+
|
|
163
|
+
elems.length.should.equal 0
|
|
55
164
|
end
|
|
56
165
|
|
|
57
|
-
|
|
166
|
+
specify "parse from a String" do
|
|
167
|
+
p = EDL::Parser.new
|
|
168
|
+
lambda{ @edl = p.parse File.read(SIMPLE_DISSOLVE) }.should.not.raise
|
|
169
|
+
|
|
170
|
+
@edl.should.be.kind_of EDL::List
|
|
171
|
+
@edl.length.should.equal 2
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
specify "parse from a File/IOish" do
|
|
175
|
+
p = EDL::Parser.new
|
|
176
|
+
lambda{ @edl = p.parse File.open(SIMPLE_DISSOLVE) }.should.not.raise
|
|
177
|
+
|
|
178
|
+
@edl.should.be.kind_of EDL::List
|
|
179
|
+
@edl.length.should.equal 2
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
specify "properly parse a dissolve" do
|
|
183
|
+
# TODO: reformulate
|
|
58
184
|
p = EDL::Parser.new
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
185
|
+
lambda{ @edl = p.parse File.open(SIMPLE_DISSOLVE) }.should.not.raise
|
|
186
|
+
|
|
187
|
+
@edl.should.be.kind_of EDL::List
|
|
188
|
+
@edl.length.should.equal 2
|
|
62
189
|
|
|
63
|
-
first = @edl
|
|
64
|
-
|
|
190
|
+
first, second = @edl
|
|
191
|
+
first.should.be.kind_of EDL::Event
|
|
192
|
+
second.should.be.kind_of EDL::Event
|
|
65
193
|
|
|
66
|
-
second
|
|
67
|
-
|
|
68
|
-
|
|
194
|
+
second.has_transition?.should.equal true
|
|
195
|
+
first.ends_with_transition?.should.equal true
|
|
196
|
+
second.ends_with_transition?.should.equal false
|
|
69
197
|
|
|
70
198
|
no_trans = @edl.without_transitions
|
|
71
199
|
|
|
72
|
-
assert_equal 2, no_trans.
|
|
200
|
+
assert_equal 2, no_trans.length
|
|
73
201
|
target_tc = (Timecode.parse('01:00:00:00') + 43)
|
|
74
|
-
assert_equal target_tc, no_trans
|
|
202
|
+
assert_equal target_tc, no_trans[0].rec_end_tc,
|
|
75
203
|
"The incoming clip should have been extended by the length of the dissolve"
|
|
76
204
|
|
|
77
205
|
target_tc = Timecode.parse('01:00:00:00')
|
|
78
|
-
assert_equal target_tc, no_trans
|
|
206
|
+
assert_equal target_tc, no_trans[1].rec_start_tc
|
|
79
207
|
"The outgoing clip should have been left in place"
|
|
80
208
|
end
|
|
81
209
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
spliced
|
|
88
|
-
assert_equal 1, spliced.events.length, "Should have been spliced to one event"
|
|
210
|
+
specify "return a spliced EDL if the sources allow" do
|
|
211
|
+
lambda{ @spliced = EDL::Parser.new.parse(File.open(SPLICEME)).spliced }.should.not.raise
|
|
212
|
+
|
|
213
|
+
@spliced.length.should.equal 1
|
|
214
|
+
@spliced[0].src_start_tc.should.equal '06:42:50:18'.tc
|
|
215
|
+
@spliced[0].src_end_tc.should.equal '06:42:52:16'.tc
|
|
89
216
|
end
|
|
90
217
|
end
|
|
91
218
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
219
|
+
context "A TimewarpMatcher should" do
|
|
220
|
+
|
|
221
|
+
specify "not create any extra events when used within a Parser" do
|
|
95
222
|
@edl = EDL::Parser.new.parse(File.open(SIMPLE_TIMEWARP))
|
|
96
|
-
|
|
97
|
-
assert_equal 1, @edl.events.length
|
|
223
|
+
@edl.length.should.equal 1
|
|
98
224
|
end
|
|
99
225
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
226
|
+
specify "properly describe a speedup" do
|
|
227
|
+
clip = EDL::Parser.new.parse(File.open(SIMPLE_TIMEWARP)).pop
|
|
228
|
+
|
|
229
|
+
tw = clip.timewarp
|
|
104
230
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
assert_not_nil clip.timewarp
|
|
108
|
-
assert_kind_of EDL::Timewarp, clip.timewarp
|
|
231
|
+
tw.should.be.kind_of EDL::Timewarp
|
|
232
|
+
tw.source_used_upto.should.be > clip.src_end_tc
|
|
109
233
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
assert_equal 124, clip.timewarp.actual_length_of_source
|
|
113
|
-
assert !clip.timewarp.reverse?
|
|
234
|
+
tw.source_used_from.should.equal clip.src_start_tc
|
|
235
|
+
clip.timewarp.actual_length_of_source.should.equal 124
|
|
114
236
|
|
|
237
|
+
tw.reverse?.should.be false
|
|
115
238
|
end
|
|
116
239
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
clip = @edl.events[0]
|
|
120
|
-
assert clip.has_timewarp?, "Should respond true to has_timewarp?"
|
|
121
|
-
assert_not_nil clip.timewarp
|
|
122
|
-
assert_kind_of EDL::Timewarp, clip.timewarp
|
|
240
|
+
specify "properly describe a slomo" do
|
|
241
|
+
clip = EDL::Parser.new.parse(File.open(SLOMO_TIMEWARP)).pop
|
|
123
242
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
assert_equal 10, clip.length
|
|
127
|
-
assert_equal 5, clip.timewarp.actual_length_of_source
|
|
128
|
-
assert_equal 50, clip.timewarp.speed_in_percent.to_i
|
|
129
|
-
assert !clip.timewarp.reverse?
|
|
243
|
+
clip.rec_length.should.equal 10
|
|
244
|
+
clip.src_length.should.equal 5
|
|
130
245
|
|
|
246
|
+
tw = clip.timewarp
|
|
247
|
+
tw.should.be.kind_of EDL::Timewarp
|
|
248
|
+
|
|
249
|
+
tw.source_used_upto.should.be < clip.src_end_tc
|
|
250
|
+
tw.source_used_upto.should.equal "03:03:19:24".tc
|
|
251
|
+
tw.speed_in_percent.to_i.should.equal 50
|
|
252
|
+
tw.actual_length_of_source.should.equal 5
|
|
253
|
+
|
|
254
|
+
tw.should.not.be.reverse
|
|
131
255
|
end
|
|
256
|
+
|
|
132
257
|
end
|
|
133
258
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
assert_equal 1, @edl.events.length
|
|
259
|
+
context "A reverse timewarp EDL coming from Avid should" do
|
|
260
|
+
|
|
261
|
+
specify "be parsed properly" do
|
|
138
262
|
|
|
139
|
-
clip =
|
|
140
|
-
|
|
263
|
+
clip = EDL::Parser.new.parse(File.open(AVID_REVERSE)).pop
|
|
264
|
+
|
|
265
|
+
clip.rec_length.should.equal 52
|
|
141
266
|
|
|
142
|
-
assert clip.has_timewarp?, "Should respond true to has_timewarp?"
|
|
143
267
|
tw = clip.timewarp
|
|
268
|
+
tw.actual_framerate.to_i.should.equal -25
|
|
269
|
+
|
|
270
|
+
tw.should.be.reverse
|
|
144
271
|
|
|
145
|
-
|
|
146
|
-
assert tw.reverse?
|
|
147
|
-
assert_equal clip.length, tw.actual_length_of_source
|
|
148
|
-
assert_equal clip.src_start_tc, tw.actual_src_end_tc
|
|
149
|
-
assert_equal clip.src_start_tc - 52, tw.actual_src_start_tc
|
|
150
|
-
assert_equal( -100, clip.timewarp.speed_in_percent.to_i)
|
|
272
|
+
tw.actual_length_of_source.should.equal 52
|
|
151
273
|
|
|
274
|
+
assert_equal 52, clip.src_length, "The src length should be computed the same as its just a reverse"
|
|
275
|
+
assert_equal -100.0, clip.timewarp.speed
|
|
152
276
|
end
|
|
153
277
|
end
|
|
154
278
|
|
|
155
|
-
|
|
279
|
+
context "A Final Cut Pro originating reverse should" do
|
|
280
|
+
specify "be interpreted properly" do
|
|
281
|
+
e = EDL::Parser.new.parse(File.open(FCP_REVERSE)).pop
|
|
282
|
+
|
|
283
|
+
e.rec_length.should.equal 1000
|
|
284
|
+
e.src_length.should.equal 1000
|
|
285
|
+
|
|
286
|
+
e.rec_start_tc.should.equal "1h".tc
|
|
287
|
+
e.rec_end_tc.should.equal "1h 40s".tc
|
|
288
|
+
|
|
289
|
+
e.should.be.reverse
|
|
290
|
+
e.timewarp.should.not.be nil
|
|
291
|
+
|
|
292
|
+
tw = e.timewarp
|
|
293
|
+
tw.source_used_from.should.equal "1h".tc
|
|
294
|
+
tw.source_used_upto.should.equal "1h 40s".tc
|
|
295
|
+
end
|
|
296
|
+
end
|
|
297
|
+
|
|
298
|
+
context "EventMatcher should" do
|
|
156
299
|
|
|
157
300
|
EVT_PATTERNS = [
|
|
158
301
|
'020 008C V C 08:04:24:24 08:04:25:19 01:00:25:22 01:00:26:17',
|
|
@@ -164,140 +307,217 @@ class EventMatcherTest < Test::Unit::TestCase
|
|
|
164
307
|
'025 GEN V D 025 00:00:55:10 00:00:58:11 01:00:29:19 01:00:32:20',
|
|
165
308
|
]
|
|
166
309
|
|
|
167
|
-
|
|
310
|
+
specify "produce an Event" do
|
|
168
311
|
m = EDL::EventMatcher.new(25)
|
|
169
312
|
|
|
170
313
|
clip = m.apply([],
|
|
171
314
|
'020 008C V C 08:04:24:24 08:04:25:19 01:00:25:22 01:00:26:17'
|
|
172
315
|
)
|
|
173
316
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
317
|
+
clip.should.be.kind_of EDL::Event
|
|
318
|
+
|
|
319
|
+
clip.num.should.equal "020"
|
|
320
|
+
clip.reel.should.equal "008C"
|
|
321
|
+
clip.track.should.equal "V"
|
|
322
|
+
|
|
323
|
+
clip.src_start_tc.should.equal '08:04:24:24'.tc
|
|
324
|
+
|
|
325
|
+
clip.src_end_tc.should.equal '08:04:25:19'.tc
|
|
326
|
+
clip.rec_start_tc.should.equal '01:00:25:22'.tc
|
|
327
|
+
clip.rec_end_tc.should.equal '01:00:26:17'.tc
|
|
328
|
+
|
|
329
|
+
clip.transition.should.be nil
|
|
330
|
+
clip.timewarp.should.be nil
|
|
331
|
+
clip.outgoing_transition_duration.should.be.zero
|
|
332
|
+
|
|
184
333
|
end
|
|
185
334
|
|
|
186
|
-
|
|
335
|
+
specify "produce an Event with dissolve" do
|
|
187
336
|
m = EDL::EventMatcher.new(25)
|
|
337
|
+
|
|
188
338
|
dissolve = m.apply([],
|
|
189
339
|
'025 GEN V D 025 00:00:55:10 00:00:58:11 01:00:29:19 01:00:32:20'
|
|
190
340
|
)
|
|
191
|
-
|
|
192
|
-
assert_kind_of EDL::Clip, dissolve
|
|
193
|
-
assert_equal '025', dissolve.num
|
|
194
|
-
assert_equal 'GEN', dissolve.reel
|
|
195
|
-
assert_equal 'V', dissolve.track
|
|
341
|
+
dissolve.should.be.kind_of EDL::Event
|
|
196
342
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
def test_wipe_generation_from_line
|
|
204
|
-
m = EDL::EventMatcher.new(25)
|
|
205
|
-
wipe = m.apply([],
|
|
206
|
-
'025 GEN V W001 025 00:00:55:10 00:00:58:11 01:00:29:19 01:00:32:20'
|
|
207
|
-
)
|
|
208
|
-
assert_not_nil wipe
|
|
209
|
-
assert_kind_of EDL::Clip, wipe
|
|
210
|
-
assert wipe.generator?
|
|
211
|
-
assert_equal '025', wipe.num
|
|
212
|
-
assert_equal 'GEN', wipe.reel
|
|
213
|
-
assert_equal 'V', wipe.track
|
|
343
|
+
dissolve.num.should.equal "025"
|
|
344
|
+
dissolve.reel.should.equal "GEN"
|
|
345
|
+
dissolve.track.should.equal "V"
|
|
346
|
+
|
|
347
|
+
dissolve.should.be.has_transition
|
|
214
348
|
|
|
215
|
-
|
|
349
|
+
tr = dissolve.transition
|
|
216
350
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
assert_equal '025', wipe.transition.duration
|
|
220
|
-
assert_equal '001', wipe.transition.smpte_wipe_index
|
|
351
|
+
tr.should.be.kind_of EDL::Dissolve
|
|
352
|
+
tr.duration.should.equal 25
|
|
221
353
|
end
|
|
222
354
|
|
|
223
|
-
|
|
355
|
+
specify "set flag on the previous event in the stack when a dissolve is encountered" do
|
|
224
356
|
m = EDL::EventMatcher.new(25)
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
)
|
|
228
|
-
|
|
229
|
-
assert_not_nil black
|
|
357
|
+
previous_evt = flexmock
|
|
358
|
+
previous_evt.should_receive(:outgoing_transition_duration=).with(25).once
|
|
230
359
|
|
|
231
|
-
|
|
232
|
-
|
|
360
|
+
m.apply([previous_evt],
|
|
361
|
+
'025 GEN V D 025 00:00:55:10 00:00:58:11 01:00:29:19 01:00:32:20'
|
|
362
|
+
)
|
|
363
|
+
end
|
|
364
|
+
|
|
365
|
+
specify "generate a Wipe" do
|
|
366
|
+
m = EDL::EventMatcher.new(25)
|
|
367
|
+
wipe = m.apply([],
|
|
368
|
+
'025 GEN V W001 025 00:00:55:10 00:00:58:11 01:00:29:19 01:00:32:20'
|
|
369
|
+
)
|
|
233
370
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
assert_equal '025 BL V C 00:00:00:00 00:00:00:00 01:00:29:19 01:00:29:19', black.original_line
|
|
371
|
+
tr = wipe.transition
|
|
372
|
+
tr.should.be.kind_of EDL::Wipe
|
|
373
|
+
tr.duration.should.equal 25
|
|
374
|
+
tr.smpte_wipe_index.should.equal '001'
|
|
239
375
|
end
|
|
240
376
|
|
|
241
|
-
|
|
377
|
+
specify "match the widest range of patterns" do
|
|
242
378
|
EVT_PATTERNS.each do | pat |
|
|
243
379
|
assert EDL::EventMatcher.new(25).matches?(pat), "EventMatcher should match #{pat}"
|
|
244
380
|
end
|
|
245
381
|
end
|
|
382
|
+
|
|
383
|
+
specify "pass the framerate that it received upon instantiation to the Timecodes being created" do
|
|
384
|
+
|
|
385
|
+
m = EDL::EventMatcher.new(30)
|
|
386
|
+
clip = m.apply([],
|
|
387
|
+
'020 008C V C 08:04:24:24 08:04:25:19 01:00:25:22 01:00:26:17'
|
|
388
|
+
)
|
|
389
|
+
clip.rec_start_tc.fps.should.equal 30
|
|
390
|
+
clip.rec_end_tc.fps.should.equal 30
|
|
391
|
+
clip.src_start_tc.fps.should.equal 30
|
|
392
|
+
clip.src_end_tc.fps.should.equal 30
|
|
393
|
+
end
|
|
394
|
+
end
|
|
395
|
+
|
|
396
|
+
context "CommentMatcher should" do
|
|
397
|
+
specify "match a comment" do
|
|
398
|
+
line = "* COMMENT: PURE BULLSHIT"
|
|
399
|
+
assert EDL::CommentMatcher.new.matches?(line)
|
|
400
|
+
end
|
|
401
|
+
|
|
402
|
+
specify "apply the comment to the last clip on the stack" do
|
|
403
|
+
line = "* COMMENT: PURE BULLSHIT"
|
|
404
|
+
|
|
405
|
+
comments = []
|
|
406
|
+
mok_evt = flexmock
|
|
407
|
+
|
|
408
|
+
2.times { mok_evt.should_receive(:comments).and_return(comments) }
|
|
409
|
+
2.times { EDL::CommentMatcher.new.apply([mok_evt], line) }
|
|
410
|
+
|
|
411
|
+
mok_evt.comments.should.equal ["COMMENT: PURE BULLSHIT", "COMMENT: PURE BULLSHIT"]
|
|
412
|
+
end
|
|
246
413
|
end
|
|
247
414
|
|
|
248
|
-
|
|
249
|
-
|
|
415
|
+
context "ClipNameMatcher should" do
|
|
416
|
+
specify "match a clip name" do
|
|
250
417
|
line = "* FROM CLIP NAME: TAPE_6-10.MOV"
|
|
251
|
-
|
|
418
|
+
EDL::NameMatcher.new.matches?(line).should.equal true
|
|
419
|
+
end
|
|
420
|
+
|
|
421
|
+
specify "not match a simple comment" do
|
|
422
|
+
line = "* CRAP"
|
|
423
|
+
EDL::NameMatcher.new.matches?(line).should.equal false
|
|
252
424
|
end
|
|
253
425
|
|
|
254
|
-
|
|
426
|
+
specify "apply the name to the last event on the stack" do
|
|
255
427
|
line = "* FROM CLIP NAME: TAPE_6-10.MOV"
|
|
428
|
+
|
|
256
429
|
mok_evt = flexmock
|
|
430
|
+
comments = []
|
|
257
431
|
mok_evt.should_receive(:clip_name=).with('TAPE_6-10.MOV').once
|
|
432
|
+
mok_evt.should_receive(:comments).and_return(comments).once
|
|
433
|
+
|
|
258
434
|
EDL::NameMatcher.new.apply([mok_evt], line)
|
|
435
|
+
comments.should.equal ["FROM CLIP NAME: TAPE_6-10.MOV"]
|
|
259
436
|
end
|
|
437
|
+
|
|
260
438
|
end
|
|
261
439
|
|
|
262
|
-
|
|
263
|
-
|
|
440
|
+
context "EffectMatcher should" do
|
|
441
|
+
specify "not match a simple comment" do
|
|
442
|
+
line = "* STUFF"
|
|
443
|
+
EDL::EffectMatcher.new.matches?(line).should.equal false
|
|
444
|
+
end
|
|
445
|
+
|
|
446
|
+
specify "match a dissolve name" do
|
|
264
447
|
line = "* EFFECT NAME: CROSS DISSOLVE"
|
|
265
|
-
|
|
448
|
+
EDL::EffectMatcher.new.matches?(line).should.equal true
|
|
266
449
|
end
|
|
267
|
-
|
|
268
|
-
|
|
450
|
+
|
|
451
|
+
specify "apply the effect name to the transition of the last event on the stack" do
|
|
269
452
|
line = "* EFFECT NAME: CROSS DISSOLVE"
|
|
270
453
|
mok_evt, mok_transition = flexmock, flexmock
|
|
454
|
+
cmt = []
|
|
271
455
|
|
|
272
456
|
mok_evt.should_receive(:transition).once.and_return(mok_transition)
|
|
457
|
+
mok_evt.should_receive(:comments).once.and_return(cmt)
|
|
458
|
+
|
|
273
459
|
mok_transition.should_receive(:effect=).with("CROSS DISSOLVE").once
|
|
274
460
|
|
|
275
461
|
EDL::EffectMatcher.new.apply([mok_evt], line)
|
|
462
|
+
|
|
463
|
+
cmt.should.equal ["EFFECT NAME: CROSS DISSOLVE"]
|
|
276
464
|
end
|
|
465
|
+
|
|
277
466
|
end
|
|
278
467
|
|
|
279
|
-
|
|
280
|
-
|
|
468
|
+
context "A complex EDL passed via Parser should" do
|
|
469
|
+
specify "parse without errors" do
|
|
281
470
|
assert_nothing_raised { EDL::Parser.new.parse(File.open(FORTY_FIVER)) }
|
|
282
471
|
end
|
|
283
472
|
|
|
284
|
-
|
|
473
|
+
# TODO: this does not belong here
|
|
474
|
+
specify "be properly rewritten from zero" do
|
|
285
475
|
complex = EDL::Parser.new.parse(File.open(FORTY_FIVER))
|
|
286
|
-
|
|
287
476
|
from_zero = complex.from_zero
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
477
|
+
|
|
478
|
+
# Should have the same number of events
|
|
479
|
+
from_zero.length.should.equal complex.length
|
|
480
|
+
|
|
481
|
+
from_zero[0].rec_start_tc.should.be.zero
|
|
482
|
+
from_zero[-1].rec_end_tc.should.equal '00:00:42:16'.tc
|
|
483
|
+
end
|
|
484
|
+
end
|
|
485
|
+
|
|
486
|
+
context "A FinalCutPro speedup with fade at the end should" do
|
|
487
|
+
specify "be parsed cleanly" do
|
|
488
|
+
list = EDL::Parser.new.parse(File.open(SPEEDUP_AND_FADEOUT))
|
|
489
|
+
|
|
490
|
+
list.length.should.equal 2
|
|
491
|
+
|
|
492
|
+
first_evt = list[0]
|
|
493
|
+
|
|
494
|
+
tw = first_evt.timewarp
|
|
495
|
+
tw.should.be.kind_of EDL::Timewarp
|
|
496
|
+
|
|
497
|
+
first_evt.rec_length.should.equal 689
|
|
498
|
+
first_evt.rec_length_with_transition.should.equal 714
|
|
499
|
+
|
|
500
|
+
tw.actual_length_of_source.should.equal 1000
|
|
501
|
+
tw.speed.should.equal 140
|
|
502
|
+
|
|
503
|
+
assert_equal 1000, first_evt.src_length
|
|
504
|
+
|
|
505
|
+
assert_equal "01:00:00:00", first_evt.capture_from_tc.to_s
|
|
506
|
+
assert_equal "01:00:40:00", first_evt.capture_to_tc.to_s
|
|
292
507
|
end
|
|
293
508
|
end
|
|
294
509
|
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
510
|
+
context "A FinalCutPro speedup and reverse with fade at the end should" do
|
|
511
|
+
specify "parse cleanly" do
|
|
512
|
+
first_evt = EDL::Parser.new.parse(File.open(SPEEDUP_REVERSE_AND_FADEOUT)).shift
|
|
513
|
+
|
|
514
|
+
first_evt.should.be.reverse
|
|
515
|
+
|
|
516
|
+
first_evt.rec_length.should.equal 689
|
|
517
|
+
first_evt.rec_length_with_transition.should.equal 714
|
|
518
|
+
|
|
519
|
+
tw = first_evt.timewarp
|
|
520
|
+
tw.source_used_from.should.equal "1h 1f".tc
|
|
521
|
+
tw.source_used_upto.should.equal "1h 40s".tc
|
|
522
|
+
end
|
|
523
|
+
end
|