julik-edl 0.0.8
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +39 -0
- data/Manifest.txt +23 -0
- data/README.txt +57 -0
- data/Rakefile +18 -0
- data/SPECS.txt +96 -0
- data/illustr/edl-explain.ai +1182 -2
- data/lib/edl.rb +319 -0
- data/lib/edl/cutter.rb +34 -0
- data/lib/edl/event.rb +146 -0
- data/lib/edl/grabber.rb +29 -0
- data/lib/edl/timewarp.rb +45 -0
- data/lib/edl/transition.rb +23 -0
- data/test/samples/45S_SAMPLE.EDL +48 -0
- data/test/samples/FCP_REVERSE.EDL +9 -0
- data/test/samples/REVERSE.EDL +3 -0
- data/test/samples/SIMPLE_DISSOLVE.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 +5 -0
- data/test/samples/TIMEWARP.EDL +5 -0
- data/test/samples/TIMEWARP_HALF.EDL +5 -0
- data/test/samples/TRAILER_EDL.edl +0 -0
- data/test/test_edl.rb +664 -0
- metadata +115 -0
data/lib/edl/grabber.rb
ADDED
@@ -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
|
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
|
@@ -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,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
|
+
|
Binary file
|
data/test/test_edl.rb
ADDED
@@ -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
|