julik-edl 0.0.8

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,29 @@
1
+ module EDL
2
+ # When initialized with a file and passed an EDL, will generate thumbnail images
3
+ # of the first frame of every event. It is assumed that the movie file starts at the same
4
+ # frame as the first EDL event.
5
+ class Grabber #:nodoc:
6
+ attr_accessor :ffmpeg_bin, :offset
7
+ def initialize(with_file)
8
+ @source_path = with_file
9
+ end
10
+
11
+ def ffmpeg_bin
12
+ @ffmpeg_bin || 'ffmpeg'
13
+ end
14
+
15
+ def grab(edl)
16
+ edl.from_zero.events.each do | evt |
17
+ grab_frame_tc = evt.rec_start_tc + (offset || 0 )
18
+
19
+ to_file = File.dirname(@source_path) + '/' + evt.num + '_' + File.basename(@source_path).gsub(/\.(\w+)$/, '')
20
+ generate_grab(evt.num, grab_frame_tc, to_file)
21
+ end
22
+ end
23
+
24
+ def generate_grab(evt, at, to_file)
25
+ cmd = "#{ffmpeg_bin} -i #{@source_path} -an -ss #{at.with_frames_as_fraction} -vframes 1 -y #{to_file}%d.jpg"
26
+ `#{cmd}`
27
+ end
28
+ end
29
+ end
@@ -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
@@ -0,0 +1,48 @@
1
+ TITLE: EDIT 15 [14-11-08] 45 SEC EDL
2
+ 001 106771 V C 04:00:57:05 04:00:58:10 10:00:00:00 10:00:01:05
3
+ 002 106771 V C 04:03:44:06 04:03:45:08 10:00:01:05 10:00:02:07
4
+ 003 106770 V C 00:06:55:13 00:06:57:01 10:00:02:07 10:00:03:20
5
+ 004 106771 V C 03:16:35:05 03:16:36:05 10:00:03:20 10:00:04:20
6
+ 005 106770 V C 00:13:03:17 00:13:04:20 10:00:04:20 10:00:05:23
7
+ 006 106771 V C 03:11:43:02 03:11:44:04 10:00:05:23 10:00:07:00
8
+ 007 106857 V C 07:18:40:24 07:18:41:17 10:00:07:00 10:00:07:18
9
+ 008 106857 V C 07:22:25:19 07:22:27:02 10:00:07:18 10:00:09:01
10
+ 009 106863 V C 13:12:46:23 13:12:48:11 10:00:09:01 10:00:10:14
11
+ 010 106857 V C 08:00:32:00 08:00:33:10 10:00:10:14 10:00:11:24
12
+ 011 106857 V C 08:05:36:18 08:05:37:19 10:00:11:24 10:00:13:00
13
+ 012 106857 V C 07:04:47:06 07:04:50:13 10:00:13:00 10:00:16:07
14
+ M2 106857 012.5 07:04:47:06
15
+ 013 106863 V C 10:11:06:07 10:11:08:00 10:00:16:07 10:00:18:00
16
+ 014 106863 V C 10:09:16:21 10:09:18:11 10:00:18:00 10:00:19:15
17
+ 015 106857 V C 07:06:02:17 07:06:04:07 10:00:19:15 10:00:21:05
18
+ 016 106857 V C 09:07:18:07 09:07:20:16 10:00:21:05 10:00:23:14
19
+ M2 106857 002.5 09:07:18:07
20
+ 017 106863 V C 11:02:46:06 11:02:47:06 10:00:23:14 10:00:24:14
21
+ 018 106863 V C 11:10:17:04 11:10:17:20 10:00:24:14 10:00:25:05
22
+ 019 106863 V C 12:06:09:18 12:06:10:16 10:00:25:05 10:00:26:03
23
+ 020 106863 V C 12:10:49:17 12:10:51:02 10:00:26:03 10:00:27:13
24
+ 021 106815 V C 05:01:22:21 05:01:24:05 10:00:27:13 10:00:28:22
25
+ 022 106815 V C 06:01:35:03 06:01:36:18 10:00:28:22 10:00:30:12
26
+ 023 106815 V C 05:13:38:19 05:13:40:10 10:00:30:12 10:00:32:03
27
+ M2 106815 -025.0 05:13:38:19
28
+ 024 106857 V C 09:13:03:09 09:13:05:11 10:00:32:03 10:00:34:05
29
+ M2 106857 -025.0 09:13:03:09
30
+ 025 106857 V C 09:13:01:07 09:13:01:07 10:00:34:05 10:00:34:05
31
+ 025 106771 V D 022 03:11:55:00 03:11:56:17 10:00:34:05 10:00:35:22
32
+ M2 106857 -025.0 09:13:01:07
33
+ * BLEND, DISSOLVE
34
+ 026 106771 V C 03:11:56:17 03:11:56:17 10:00:35:22 10:00:35:22
35
+ 026 106771 V D 012 03:06:30:09 03:06:31:19 10:00:35:22 10:00:37:07
36
+ * BLEND, DISSOLVE
37
+ 027 106863 V C 13:15:29:20 13:15:31:02 10:00:37:07 10:00:38:14
38
+ 028 106771 V C 03:15:40:22 03:15:42:11 10:00:38:14 10:00:40:03
39
+ 029 106863 V C 13:15:51:02 13:15:52:08 10:00:40:03 10:00:41:09
40
+ 030 106863 V C 13:15:52:08 13:15:52:08 10:00:41:09 10:00:41:09
41
+ 030 BL V D 032 00:00:00:00 00:00:01:07 10:00:41:09 10:00:42:16
42
+ * BLEND, DISSOLVE
43
+ >>> SOURCE 106770 106770 4919975f.793b8d33
44
+ >>> SOURCE 106771 106771 4919a179.79630563
45
+ >>> SOURCE 106815 106815 4919b521.79afcb70
46
+ >>> SOURCE 106857 106857 4919cbb9.7a080d9d
47
+ >>> SOURCE 106863 106863 4919e0c3.7a5a3cfc
48
+ 
@@ -0,0 +1,9 @@
1
+ TITLE: REVSIMPLE
2
+ FCM: NON-DROP FRAME
3
+
4
+ 001 FOO V C 01:00:39:23 01:01:19:23 01:00:00:00 01:00:40:00
5
+ * FROM CLIP NAME: BARS_40S.MOV
6
+ * COMMENT:
7
+ M2 FOO -025.0 01:00:39:23
8
+
9
+ 
@@ -0,0 +1,3 @@
1
+ TITLE: REVERSED_EDL
2
+ 001 106857 V C 09:13:03:09 09:13:05:11 10:00:32:03 10:00:34:05
3
+ M2 106857 -025.0 09:13:03:09
@@ -0,0 +1,9 @@
1
+ TITLE: SIMPLE_DISSOLVE
2
+ FCM: NON-DROP FRAME
3
+
4
+ 001 BL V C 00:00:00:00 00:00:00:00 01:00:00:00 01:00:00:00
5
+ 001 006I V D 043 06:42:50:18 06:42:52:13 01:00:00:00 01:00:01:20
6
+ * EFFECT NAME: CROSS DISSOLVE
7
+ * TO CLIP NAME: TAPE_6-10.MOV
8
+ * COMMENT:
9
+
@@ -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
+ 
@@ -0,0 +1,5 @@
1
+ TITLE: SPLICEME
2
+ FCM: NON-DROP FRAME
3
+
4
+ 001 006I V C 06:42:50:18 06:42:52:13 01:00:00:00 01:00:01:20
5
+ 002 006I V C 06:42:52:14 06:42:52:16 01:00:10:21 01:00:10:23
@@ -0,0 +1,5 @@
1
+ TITLE: TIMEWARP_EDL
2
+ FCM: NON-DROP FRAME
3
+
4
+ 001 003 V C 03:03:19:19 03:03:20:04 01:00:04:03 01:00:04:13
5
+ M2 003 309.6 03:03:19:19
@@ -0,0 +1,5 @@
1
+ TITLE: TIMEWARP_HALF_EDL
2
+ FCM: NON-DROP FRAME
3
+
4
+ 001 003 V C 03:03:19:19 03:03:20:04 01:00:04:03 01:00:04:13
5
+ M2 003 12.5 03:03:19:19
@@ -0,0 +1,664 @@
1
+ require File.dirname(__FILE__) + '/../lib/edl'
2
+ require File.dirname(__FILE__) + '/../lib/edl/cutter'
3
+ require File.dirname(__FILE__) + '/../lib/edl/grabber'
4
+
5
+ require 'rubygems'
6
+ require 'test/unit'
7
+ require 'test/spec'
8
+ require 'flexmock'
9
+ require 'flexmock/test_unit'
10
+
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'
21
+
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
30
+ evt = EDL::Event.new
31
+ %w( num reel track src_start_tc src_end_tc rec_start_tc rec_end_tc ).each do | em |
32
+ evt.should.respond_to em
33
+ end
34
+ end
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
40
+
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
46
+ end
47
+
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
52
+ end
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 line_number" do
114
+ EDL::Event.new.line_number.should.be.nil
115
+ EDL::Event.new(:line_number => 3).line_number.should.equal 3
116
+ end
117
+
118
+ specify "support capture_length as an alias to src_length" do
119
+ tw = flexmock
120
+ tw.should_receive(:actual_length_of_source).and_return(:something)
121
+ e = EDL::Event.new(:timewarp => tw)
122
+ e.src_length.should.equal e.capture_length
123
+ end
124
+
125
+ specify "delegate src_length to the timewarp if it is there" do
126
+ tw = flexmock
127
+ tw.should_receive(:actual_length_of_source).and_return(:something).once
128
+ e = EDL::Event.new(:timewarp => tw)
129
+ e.src_length.should.equal :something
130
+ end
131
+
132
+ specify "report reverse? and reversed? based on the timewarp" do
133
+ e = EDL::Event.new(:timewarp => nil)
134
+ e.should.not.be.reverse
135
+ e.should.not.be.reversed
136
+
137
+ tw = flexmock
138
+ tw.should_receive(:reverse?).and_return(true)
139
+
140
+ e = EDL::Event.new(:timewarp => tw)
141
+ e.should.be.reverse
142
+ e.should.be.reversed
143
+ end
144
+
145
+ specify "report speed as 100 percent without a timewarp" do
146
+ e = EDL::Event.new
147
+ e.speed.should.be.kind_of Float
148
+ e.speed.should.equal 100.0
149
+ end
150
+
151
+ specify "consult the timewarp for speed" do
152
+ tw = flexmock
153
+ tw.should_receive(:speed).and_return(:something)
154
+
155
+ e = EDL::Event.new(:timewarp => tw)
156
+ e.speed.should.equal :something
157
+ end
158
+
159
+ specify "report false for starts_with_transition? if transision is nil" do
160
+ e = EDL::Event.new
161
+ e.should.respond_to :starts_with_transition?
162
+ e.should.not.be.starts_with_transition
163
+ end
164
+
165
+ specify "report zero for incoming_transition_duration if transision is nil" do
166
+ e = EDL::Event.new
167
+ e.should.respond_to :incoming_transition_duration
168
+ e.incoming_transition_duration.should.zero
169
+ end
170
+
171
+ specify "report true for starts_with_transition? if transision is not nil" do
172
+ e = EDL::Event.new :transition => true
173
+ e.should.respond_to :starts_with_transition?
174
+ e.should.starts_with_transition
175
+ end
176
+
177
+ specify "consult the transition for incoming_transition_duration if it's present" do
178
+ tr = flexmock
179
+ tr.should_receive(:duration).and_return(:something)
180
+
181
+ e = EDL::Event.new(:transition => tr)
182
+ e.should.respond_to :incoming_transition_duration
183
+ e.incoming_transition_duration.should.equal :something
184
+ end
185
+
186
+ specify "report capture_from_tc as the source start without a timewarp" do
187
+ e = EDL::Event.new(:src_start_tc => "1h".tc)
188
+ e.capture_from_tc.should.equal "1h".tc
189
+ end
190
+
191
+ specify "consult the timewarp for capture_from_tc if a timewarp is there" do
192
+ tw = flexmock
193
+ tw.should_receive(:source_used_from).and_return(:something)
194
+
195
+ e = EDL::Event.new(:timewarp => tw)
196
+ e.capture_from_tc.should.equal :something
197
+ end
198
+
199
+ specify "report capture_to_tc as record length plus transition when no timewarp present" do
200
+ e = EDL::Event.new(:src_end_tc => "1h 10s".tc, :outgoing_transition_duration => 2 )
201
+ e.capture_to_tc.should.equal "1h 10s 2f".tc
202
+ end
203
+
204
+ specify "consult the timewarp for capture_to_tc if timewarp is present" do
205
+ tw = flexmock
206
+ tw.should_receive(:source_used_upto).and_return(:something)
207
+
208
+ e = EDL::Event.new(:timewarp => tw)
209
+ e.capture_to_tc.should.equal :something
210
+ end
211
+
212
+
213
+ end
214
+
215
+ context "A Parser should" do
216
+
217
+ specify "store the passed framerate" do
218
+ p = EDL::Parser.new(45)
219
+ p.should.respond_to :fps
220
+ p.fps.should.equal 45
221
+ end
222
+
223
+ specify "return matchers tuned with the passed framerate" do
224
+ p = EDL::Parser.new(30)
225
+ matchers = p.get_matchers
226
+ event_matcher = matchers.find{|e| e.is_a?(EDL::EventMatcher) }
227
+ event_matcher.fps.should.equal 30
228
+ end
229
+
230
+ specify "create a Timecode from stringified elements" do
231
+ elems = ["08", "04", "24", "24"]
232
+ lambda{ @tc = EDL::Parser.timecode_from_line_elements(elems, 30) }.should.not.raise
233
+
234
+ @tc.should.be.kind_of Timecode
235
+ @tc.should.equal "08:04:24:24".tc(30)
236
+
237
+ elems.length.should.equal 0
238
+ end
239
+
240
+ specify "parse from a String" do
241
+ p = EDL::Parser.new
242
+ lambda{ @edl = p.parse File.read(SIMPLE_DISSOLVE) }.should.not.raise
243
+
244
+ @edl.should.be.kind_of EDL::List
245
+ @edl.length.should.equal 2
246
+ end
247
+
248
+ specify "parse from a File/IOish" do
249
+ p = EDL::Parser.new
250
+ lambda{ @edl = p.parse File.open(SIMPLE_DISSOLVE) }.should.not.raise
251
+
252
+ @edl.should.be.kind_of EDL::List
253
+ @edl.length.should.equal 2
254
+ end
255
+
256
+ specify "properly parse a dissolve" do
257
+ # TODO: reformulate
258
+ p = EDL::Parser.new
259
+ lambda{ @edl = p.parse File.open(SIMPLE_DISSOLVE) }.should.not.raise
260
+
261
+ @edl.should.be.kind_of EDL::List
262
+ @edl.length.should.equal 2
263
+
264
+ first, second = @edl
265
+ first.should.be.kind_of EDL::Event
266
+ second.should.be.kind_of EDL::Event
267
+
268
+ second.has_transition?.should.equal true
269
+ first.ends_with_transition?.should.equal true
270
+ second.ends_with_transition?.should.equal false
271
+
272
+ no_trans = @edl.without_transitions
273
+
274
+ assert_equal 2, no_trans.length
275
+ target_tc = (Timecode.parse('01:00:00:00') + 43)
276
+ assert_equal target_tc, no_trans[0].rec_end_tc,
277
+ "The incoming clip should have been extended by the length of the dissolve"
278
+
279
+ target_tc = Timecode.parse('01:00:00:00')
280
+ assert_equal target_tc, no_trans[1].rec_start_tc
281
+ "The outgoing clip should have been left in place"
282
+ end
283
+
284
+ specify "return a spliced EDL if the sources allow" do
285
+ lambda{ @spliced = EDL::Parser.new.parse(File.open(SPLICEME)).spliced }.should.not.raise
286
+
287
+ @spliced.length.should.equal 1
288
+ @spliced[0].src_start_tc.should.equal '06:42:50:18'.tc
289
+ @spliced[0].src_end_tc.should.equal '06:42:52:16'.tc
290
+ end
291
+
292
+ specify "not apply any Matchers if a match is found" do
293
+ p = EDL::Parser.new
294
+ m1 = flexmock
295
+ m1.should_receive(:matches?).with("plop").once.and_return(true)
296
+ m1.should_receive(:apply).once
297
+
298
+ flexmock(p).should_receive(:get_matchers).once.and_return([m1, m1])
299
+ result = p.parse("plop")
300
+ result.should.be.empty
301
+ end
302
+
303
+ specify "register line numbers of the detected events" do
304
+ p = EDL::Parser.new
305
+ events = p.parse(File.open(SPLICEME))
306
+
307
+ events[0].line_number.should.not.be.nil
308
+ events[0].line_number.should.equal 4
309
+
310
+ events[1].line_number.should.not.be.nil
311
+ events[1].line_number.should.equal 5
312
+ end
313
+ end
314
+
315
+ context "A TimewarpMatcher should" do
316
+
317
+ specify "not create any extra events when used within a Parser" do
318
+ @edl = EDL::Parser.new.parse(File.open(SIMPLE_TIMEWARP))
319
+ @edl.length.should.equal 1
320
+ end
321
+
322
+ specify "properly describe a speedup" do
323
+ clip = EDL::Parser.new.parse(File.open(SIMPLE_TIMEWARP)).pop
324
+
325
+ tw = clip.timewarp
326
+
327
+ tw.should.be.kind_of EDL::Timewarp
328
+ tw.source_used_upto.should.be > clip.src_end_tc
329
+
330
+ tw.source_used_from.should.equal clip.src_start_tc
331
+ clip.timewarp.actual_length_of_source.should.equal 124
332
+
333
+ tw.reverse?.should.be false
334
+ end
335
+
336
+ specify "properly describe a slomo" do
337
+ clip = EDL::Parser.new.parse(File.open(SLOMO_TIMEWARP)).pop
338
+
339
+ clip.rec_length.should.equal 10
340
+ clip.src_length.should.equal 5
341
+
342
+ tw = clip.timewarp
343
+ tw.should.be.kind_of EDL::Timewarp
344
+
345
+ tw.source_used_upto.should.be < clip.src_end_tc
346
+ tw.source_used_upto.should.equal "03:03:19:24".tc
347
+ tw.speed_in_percent.to_i.should.equal 50
348
+ tw.actual_length_of_source.should.equal 5
349
+
350
+ tw.should.not.be.reverse
351
+ end
352
+
353
+ end
354
+
355
+ context "A reverse timewarp EDL coming from Avid should" do
356
+
357
+ specify "be parsed properly" do
358
+
359
+ clip = EDL::Parser.new.parse(File.open(AVID_REVERSE)).pop
360
+
361
+ clip.rec_length.should.equal 52
362
+
363
+ tw = clip.timewarp
364
+ tw.actual_framerate.to_i.should.equal -25
365
+
366
+ tw.should.be.reverse
367
+
368
+ tw.actual_length_of_source.should.equal 52
369
+
370
+ assert_equal 52, clip.src_length, "The src length should be computed the same as its just a reverse"
371
+ assert_equal -100.0, clip.timewarp.speed
372
+ end
373
+ end
374
+
375
+ context "A Final Cut Pro originating reverse should" do
376
+
377
+ specify "be interpreted properly" do
378
+ e = EDL::Parser.new.parse(File.open(FCP_REVERSE)).pop
379
+
380
+ e.rec_length.should.equal 1000
381
+ e.src_length.should.equal 1000
382
+
383
+ e.rec_start_tc.should.equal "1h".tc
384
+ e.rec_end_tc.should.equal "1h 40s".tc
385
+
386
+ e.should.be.reverse
387
+ e.timewarp.should.not.be nil
388
+
389
+ tw = e.timewarp
390
+
391
+ tw.speed.should.equal -100.0
392
+ e.speed.should.equal -100.0
393
+
394
+ tw.source_used_from.should.equal "1h".tc
395
+ tw.source_used_upto.should.equal "1h 40s".tc
396
+ end
397
+ end
398
+
399
+ context "EventMatcher should" do
400
+
401
+ EVT_PATTERNS = [
402
+ '020 008C V C 08:04:24:24 08:04:25:19 01:00:25:22 01:00:26:17',
403
+ '021 009 V C 00:39:04:21 00:39:05:09 01:00:26:17 01:00:27:05',
404
+ '022 008C V C 08:08:01:23 08:08:02:18 01:00:27:05 01:00:28:00',
405
+ '023 008C V C 08:07:30:02 08:07:30:21 01:00:28:00 01:00:28:19',
406
+ '024 AX V C 00:00:00:00 00:00:01:00 01:00:28:19 01:00:29:19',
407
+ '025 BL V C 00:00:00:00 00:00:00:00 01:00:29:19 01:00:29:19',
408
+ '025 GEN V D 025 00:00:55:10 00:00:58:11 01:00:29:19 01:00:32:20',
409
+ ]
410
+
411
+ specify "produce an Event" do
412
+ m = EDL::EventMatcher.new(25)
413
+
414
+ clip = m.apply([],
415
+ '020 008C V C 08:04:24:24 08:04:25:19 01:00:25:22 01:00:26:17'
416
+ )
417
+
418
+ clip.should.be.kind_of EDL::Event
419
+
420
+ clip.num.should.equal "020"
421
+ clip.reel.should.equal "008C"
422
+ clip.track.should.equal "V"
423
+
424
+ clip.src_start_tc.should.equal '08:04:24:24'.tc
425
+
426
+ clip.src_end_tc.should.equal '08:04:25:19'.tc
427
+ clip.rec_start_tc.should.equal '01:00:25:22'.tc
428
+ clip.rec_end_tc.should.equal '01:00:26:17'.tc
429
+
430
+ clip.transition.should.be nil
431
+ clip.timewarp.should.be nil
432
+ clip.outgoing_transition_duration.should.be.zero
433
+
434
+ end
435
+
436
+ specify "produce an Event with dissolve" do
437
+ m = EDL::EventMatcher.new(25)
438
+
439
+ dissolve = m.apply([],
440
+ '025 GEN V D 025 00:00:55:10 00:00:58:11 01:00:29:19 01:00:32:20'
441
+ )
442
+ dissolve.should.be.kind_of EDL::Event
443
+
444
+ dissolve.num.should.equal "025"
445
+ dissolve.reel.should.equal "GEN"
446
+ dissolve.track.should.equal "V"
447
+
448
+ dissolve.should.be.has_transition
449
+
450
+ tr = dissolve.transition
451
+
452
+ tr.should.be.kind_of EDL::Dissolve
453
+ tr.duration.should.equal 25
454
+ end
455
+
456
+ specify "set flag on the previous event in the stack when a dissolve is encountered" do
457
+ m = EDL::EventMatcher.new(25)
458
+ previous_evt = flexmock
459
+ previous_evt.should_receive(:outgoing_transition_duration=).with(25).once
460
+
461
+ m.apply([previous_evt],
462
+ '025 GEN V D 025 00:00:55:10 00:00:58:11 01:00:29:19 01:00:32:20'
463
+ )
464
+ end
465
+
466
+ specify "generate a Wipe" do
467
+ m = EDL::EventMatcher.new(25)
468
+ wipe = m.apply([],
469
+ '025 GEN V W001 025 00:00:55:10 00:00:58:11 01:00:29:19 01:00:32:20'
470
+ )
471
+
472
+ tr = wipe.transition
473
+ tr.should.be.kind_of EDL::Wipe
474
+ tr.duration.should.equal 25
475
+ tr.smpte_wipe_index.should.equal '001'
476
+ end
477
+
478
+ specify "match the widest range of patterns" do
479
+ EVT_PATTERNS.each do | pat |
480
+ assert EDL::EventMatcher.new(25).matches?(pat), "EventMatcher should match #{pat}"
481
+ end
482
+ end
483
+
484
+ specify "pass the framerate that it received upon instantiation to the Timecodes being created" do
485
+
486
+ m = EDL::EventMatcher.new(30)
487
+ clip = m.apply([],
488
+ '020 008C V C 08:04:24:24 08:04:25:19 01:00:25:22 01:00:26:17'
489
+ )
490
+ clip.rec_start_tc.fps.should.equal 30
491
+ clip.rec_end_tc.fps.should.equal 30
492
+ clip.src_start_tc.fps.should.equal 30
493
+ clip.src_end_tc.fps.should.equal 30
494
+ end
495
+ end
496
+
497
+ context "CommentMatcher should" do
498
+ specify "match a comment" do
499
+ line = "* COMMENT: PURE BULLSHIT"
500
+ assert EDL::CommentMatcher.new.matches?(line)
501
+ end
502
+
503
+ specify "apply the comment to the last clip on the stack" do
504
+ line = "* COMMENT: PURE BULLSHIT"
505
+
506
+ comments = []
507
+ mok_evt = flexmock
508
+
509
+ 2.times { mok_evt.should_receive(:comments).and_return(comments) }
510
+ 2.times { EDL::CommentMatcher.new.apply([mok_evt], line) }
511
+
512
+ mok_evt.comments.should.equal ["* COMMENT: PURE BULLSHIT", "* COMMENT: PURE BULLSHIT"]
513
+ end
514
+ end
515
+
516
+ context "FallbackMatcher should" do
517
+ specify "match anything" do
518
+ line = "SOME"
519
+ EDL::FallbackMatcher.new.matches?(line).should.equal true
520
+
521
+ line = "OR ANOTHER "
522
+ EDL::FallbackMatcher.new.matches?(line).should.equal true
523
+ end
524
+
525
+ specify "not match whitespace" do
526
+ line = "\s\s\s\r\n\r"
527
+ EDL::FallbackMatcher.new.matches?(line).should.equal false
528
+ end
529
+
530
+ specify "append the matched content to comments" do
531
+ e = flexmock
532
+ cmts = []
533
+ e.should_receive(:comments).and_return(cmts)
534
+
535
+ EDL::FallbackMatcher.new.apply([e], "FOOBAR")
536
+ cmts.should.equal ["FOOBAR"]
537
+
538
+ EDL::FallbackMatcher.new.apply([e], "FINAL CUT PRO REEL: 006-I REPLACED BY: 006I")
539
+ cmts.should.equal ["FOOBAR", "FINAL CUT PRO REEL: 006-I REPLACED BY: 006I"]
540
+ end
541
+
542
+ specify "raise an ApplyError if no clip is on the stack" do
543
+ lambda { EDL::FallbackMatcher.new.apply([], "FINAL CUT PRO REEL: 006-I REPLACED BY: 006I") }.should.raise(EDL::Matcher::ApplyError)
544
+ end
545
+
546
+ end
547
+
548
+ context "ClipNameMatcher should" do
549
+ specify "match a clip name" do
550
+ line = "* FROM CLIP NAME: TAPE_6-10.MOV"
551
+ EDL::NameMatcher.new.matches?(line).should.equal true
552
+ end
553
+
554
+ specify "not match a simple comment" do
555
+ line = "* CRAP"
556
+ EDL::NameMatcher.new.matches?(line).should.equal false
557
+ end
558
+
559
+ specify "apply the name to the last event on the stack" do
560
+ line = "* FROM CLIP NAME: TAPE_6-10.MOV"
561
+
562
+ mok_evt = flexmock
563
+ comments = []
564
+ mok_evt.should_receive(:clip_name=).with('TAPE_6-10.MOV').once
565
+ mok_evt.should_receive(:comments).and_return(comments).once
566
+
567
+ EDL::NameMatcher.new.apply([mok_evt], line)
568
+ comments.should.equal ["* FROM CLIP NAME: TAPE_6-10.MOV"]
569
+ end
570
+
571
+ end
572
+
573
+ context "EffectMatcher should" do
574
+ specify "not match a simple comment" do
575
+ line = "* STUFF"
576
+ EDL::EffectMatcher.new.matches?(line).should.equal false
577
+ end
578
+
579
+ specify "match a dissolve name" do
580
+ line = "* EFFECT NAME: CROSS DISSOLVE"
581
+ EDL::EffectMatcher.new.matches?(line).should.equal true
582
+ end
583
+
584
+ specify "apply the effect name to the transition of the last event on the stack" do
585
+ line = "* EFFECT NAME: CROSS DISSOLVE"
586
+ mok_evt, mok_transition = flexmock, flexmock
587
+ cmt = []
588
+
589
+ mok_evt.should_receive(:transition).once.and_return(mok_transition)
590
+ mok_evt.should_receive(:comments).once.and_return(cmt)
591
+
592
+ mok_transition.should_receive(:effect=).with("CROSS DISSOLVE").once
593
+
594
+ EDL::EffectMatcher.new.apply([mok_evt], line)
595
+
596
+ cmt.should.equal ["* EFFECT NAME: CROSS DISSOLVE"]
597
+ end
598
+
599
+ end
600
+
601
+ context "A complex EDL passed via Parser should" do
602
+ specify "parse without errors" do
603
+ assert_nothing_raised { EDL::Parser.new.parse(File.open(FORTY_FIVER)) }
604
+ end
605
+
606
+ # TODO: this does not belong here
607
+ specify "be properly rewritten from zero" do
608
+ complex = EDL::Parser.new.parse(File.open(FORTY_FIVER))
609
+ from_zero = complex.from_zero
610
+
611
+ # Should have the same number of events
612
+ from_zero.length.should.equal complex.length
613
+
614
+ from_zero[0].rec_start_tc.should.be.zero
615
+ from_zero[-1].rec_end_tc.should.equal '00:00:42:16'.tc
616
+ end
617
+ end
618
+
619
+ context "A FinalCutPro speedup with fade at the end should" do
620
+ specify "be parsed cleanly" do
621
+ list = EDL::Parser.new.parse(File.open(SPEEDUP_AND_FADEOUT))
622
+
623
+ list.length.should.equal 2
624
+
625
+ first_evt = list[0]
626
+
627
+ tw = first_evt.timewarp
628
+ tw.should.be.kind_of EDL::Timewarp
629
+
630
+ first_evt.rec_length.should.equal 689
631
+ first_evt.rec_length_with_transition.should.equal 714
632
+
633
+ tw.actual_length_of_source.should.equal 1000
634
+ tw.speed.should.equal 140
635
+
636
+ assert_equal 1000, first_evt.src_length
637
+
638
+ assert_equal "01:00:00:00", first_evt.capture_from_tc.to_s
639
+ assert_equal "01:00:40:00", first_evt.capture_to_tc.to_s
640
+ end
641
+ end
642
+
643
+ context "In the trailer EDL the event 4 should" do
644
+ specify "not have too many comments" do
645
+ evts = EDL::Parser.new.parse(File.open(TRAILER_EDL))
646
+ evt = evts[6]
647
+ evt.comments.length.should.equal(5)
648
+ end
649
+ end
650
+
651
+ context "A FinalCutPro speedup and reverse with fade at the end should" do
652
+ specify "parse cleanly" do
653
+ first_evt = EDL::Parser.new.parse(File.open(SPEEDUP_REVERSE_AND_FADEOUT)).shift
654
+
655
+ first_evt.should.be.reverse
656
+
657
+ first_evt.rec_length.should.equal 689
658
+ first_evt.rec_length_with_transition.should.equal 714
659
+
660
+ tw = first_evt.timewarp
661
+ tw.source_used_from.should.equal "1h 1f".tc
662
+ tw.source_used_upto.should.equal "1h 40s".tc
663
+ end
664
+ end