julik-edl 0.0.8

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.
@@ -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