edl 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/History.txt +5 -0
- data/README.txt +46 -0
- data/Rakefile +9 -0
- data/lib/edl.rb +426 -0
- data/lib/edl/cutter.rb +34 -0
- data/lib/edl/grabber.rb +30 -0
- data/test/samples/45S_SAMPLE.EDL +48 -0
- data/test/samples/REVERSE.EDL +3 -0
- data/test/samples/SIMPLE_DISSOLVE.EDL +9 -0
- data/test/samples/SPLICEME.EDL +8 -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 +303 -0
- metadata +98 -0
data/History.txt
ADDED
data/README.txt
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
= EDL
|
2
|
+
|
3
|
+
== DESCRIPTION:
|
4
|
+
|
5
|
+
Work with EDL files from Ruby
|
6
|
+
|
7
|
+
|
8
|
+
== SYNOPSIS:
|
9
|
+
|
10
|
+
list = EDL::Parser.new(fps=25).parse(File.open('OFFLINE.EDL'))
|
11
|
+
list.events.each do | evt |
|
12
|
+
puts evt.inspect
|
13
|
+
end
|
14
|
+
|
15
|
+
== REQUIREMENTS:
|
16
|
+
|
17
|
+
* Timecode gem (sudo gem install timecode)
|
18
|
+
|
19
|
+
== INSTALL:
|
20
|
+
|
21
|
+
* sudo gem install edl
|
22
|
+
|
23
|
+
== LICENSE:
|
24
|
+
|
25
|
+
(The MIT License)
|
26
|
+
|
27
|
+
Copyright (c) 2008 Julik Tarkhanov <me@julik.nl>
|
28
|
+
|
29
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
30
|
+
a copy of this software and associated documentation files (the
|
31
|
+
'Software'), to deal in the Software without restriction, including
|
32
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
33
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
34
|
+
permit persons to whom the Software is furnished to do so, subject to
|
35
|
+
the following conditions:
|
36
|
+
|
37
|
+
The above copyright notice and this permission notice shall be
|
38
|
+
included in all copies or substantial portions of the Software.
|
39
|
+
|
40
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
41
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
42
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
43
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
44
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
45
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
46
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
ADDED
data/lib/edl.rb
ADDED
@@ -0,0 +1,426 @@
|
|
1
|
+
require "rubygems"
|
2
|
+
require "timecode"
|
3
|
+
|
4
|
+
# A simplistic EDL parser. Current limitations: no support for DF timecode, no support for audio,
|
5
|
+
# no support for split edits, no support for key effects, no support for audio
|
6
|
+
module EDL
|
7
|
+
VERSION = "0.0.1"
|
8
|
+
|
9
|
+
DEFAULT_FPS = 25
|
10
|
+
|
11
|
+
# Represents an EDL, is returned from the parser. Traditional operation is functional style, i.e.
|
12
|
+
# edl.renumbered.without_transitions.without_generators
|
13
|
+
class List
|
14
|
+
attr_accessor :events, :fps
|
15
|
+
|
16
|
+
def initialize(events = [])
|
17
|
+
@events = events.dup
|
18
|
+
end
|
19
|
+
|
20
|
+
# Return the same EDL with all dissolves stripped and replaced by the clips under them
|
21
|
+
def without_transitions
|
22
|
+
# Find dissolves
|
23
|
+
cpy = []
|
24
|
+
|
25
|
+
@events.each_with_index do | e, i |
|
26
|
+
# A dissolve always FOLLOWS the incoming clip
|
27
|
+
if @events[i+1] && @events[i+1].has_transition?
|
28
|
+
dissolve = @events[i+1]
|
29
|
+
len = dissolve.transition.duration.to_i
|
30
|
+
|
31
|
+
# The dissolve contains the OUTGOING clip, we are the INCOMING. Extend the
|
32
|
+
# incoming clip by the length of the dissolve, that's the whole mission actually
|
33
|
+
incoming = e.copy_properties_to(e.class.new)
|
34
|
+
incoming.src_end_tc += len
|
35
|
+
incoming.rec_end_tc += len
|
36
|
+
|
37
|
+
outgoing = dissolve.copy_properties_to(Clip.new)
|
38
|
+
|
39
|
+
# Add the A suffix to the ex-dissolve
|
40
|
+
outgoing.num += 'A'
|
41
|
+
|
42
|
+
# Take care to join the two if they overlap - TODO
|
43
|
+
cpy << incoming
|
44
|
+
cpy << outgoing
|
45
|
+
elsif e.has_transition?
|
46
|
+
# Skip, already handled!
|
47
|
+
else
|
48
|
+
cpy << e.dup
|
49
|
+
end
|
50
|
+
end
|
51
|
+
# Do not renumber events
|
52
|
+
# (0...cpy.length).map{|e| cpy[e].num = "%03d" % e }
|
53
|
+
self.class.new(cpy)
|
54
|
+
end
|
55
|
+
|
56
|
+
def renumbered
|
57
|
+
renumed = @events.dup
|
58
|
+
pad = renumed.length.to_s.length
|
59
|
+
pad = 3 if pad < 3
|
60
|
+
|
61
|
+
(0...renumed.length).map{|e| renumed[e].num = "%0#{pad}d" % (e+1) }
|
62
|
+
x = self.class.new(renumed)
|
63
|
+
puts x.inspect
|
64
|
+
x
|
65
|
+
end
|
66
|
+
|
67
|
+
# Return the same EDL with all timewarps expanded to native length. Clip length
|
68
|
+
# changes have rippling effect on footage that comes after the timewarped clip
|
69
|
+
# (so this is best used in concert with the original EDL where record TC is pristine)
|
70
|
+
def without_timewarps
|
71
|
+
self.class.new(
|
72
|
+
@events.map do | e |
|
73
|
+
|
74
|
+
if e.has_timewarp?
|
75
|
+
repl = e.copy_properties_to(e.class.new)
|
76
|
+
from, to = e.timewarp.actual_src_start_tc, e.timewarp.actual_src_end_tc
|
77
|
+
repl.src_start_tc, repl.src_end_tc, repl.timewarp = from, to, nil
|
78
|
+
repl
|
79
|
+
else
|
80
|
+
e
|
81
|
+
end
|
82
|
+
end
|
83
|
+
)
|
84
|
+
end
|
85
|
+
|
86
|
+
# Return the same EDL without AX, BL and other GEN events (like slug, text and solids).
|
87
|
+
# Usually used in concert with "without_transitions"
|
88
|
+
def without_generators
|
89
|
+
self.class.new(@events.reject{|e| e.generator? })
|
90
|
+
end
|
91
|
+
|
92
|
+
# Return the list of clips used by this EDL at full capture length
|
93
|
+
def capture_list
|
94
|
+
without_generators.without_timewarps.spliced.from_zero
|
95
|
+
end
|
96
|
+
|
97
|
+
# Return the same EDL with the first event starting at 00:00:00:00 and all subsequent events
|
98
|
+
# shifted accordingly
|
99
|
+
def from_zero
|
100
|
+
shift_by = @events[0].rec_start_tc
|
101
|
+
self.class.new(
|
102
|
+
@events.map do | original |
|
103
|
+
e = original.dup
|
104
|
+
e.rec_start_tc = (e.rec_start_tc - shift_by)
|
105
|
+
e.rec_end_tc = (e.rec_end_tc - shift_by)
|
106
|
+
e
|
107
|
+
end
|
108
|
+
)
|
109
|
+
end
|
110
|
+
|
111
|
+
# Return the same EDL with neighbouring clips joined at cuts where applicable (if a clip
|
112
|
+
# is divided in two pieces it will be spliced). Most useful in combination with without_timewarps
|
113
|
+
def spliced
|
114
|
+
spliced_edl = @events.inject([]) do | spliced, cur |
|
115
|
+
latest = spliced[-1]
|
116
|
+
# Append to latest if splicable
|
117
|
+
if latest && (latest.reel == cur.reel) && (cur.src_start_tc == (latest.src_end_tc + 1))
|
118
|
+
latest.src_end_tc = cur.src_end_tc
|
119
|
+
latest.rec_end_tc = cur.rec_end_tc
|
120
|
+
else
|
121
|
+
spliced << cur.dup
|
122
|
+
end
|
123
|
+
spliced
|
124
|
+
end
|
125
|
+
self.class.new(spliced_edl)
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
# Represents an edit event
|
130
|
+
class Event
|
131
|
+
attr_accessor :num,
|
132
|
+
:reel,
|
133
|
+
:track,
|
134
|
+
:src_start_tc,
|
135
|
+
:src_end_tc,
|
136
|
+
:rec_start_tc,
|
137
|
+
:rec_end_tc,
|
138
|
+
:comments,
|
139
|
+
:original_line
|
140
|
+
|
141
|
+
def to_s
|
142
|
+
%w( num reel track src_start_tc src_end_tc rec_start_tc rec_end_tc).map{|a| self.send(a).to_s}.join(" ")
|
143
|
+
end
|
144
|
+
|
145
|
+
def inspect
|
146
|
+
to_s
|
147
|
+
end
|
148
|
+
|
149
|
+
def copy_properties_to(evt)
|
150
|
+
%w( num reel track src_start_tc src_end_tc rec_start_tc rec_end_tc).each do | k |
|
151
|
+
evt.send("#{k}=", send(k)) if evt.respond_to?(k)
|
152
|
+
end
|
153
|
+
evt
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
class Clip < Event
|
158
|
+
attr_accessor :clip_name, :timewarp
|
159
|
+
attr_accessor :transition
|
160
|
+
|
161
|
+
# Returns true if the clip starts with a transiton (not a jump cut)
|
162
|
+
def has_transition?
|
163
|
+
!transition.nil?
|
164
|
+
end
|
165
|
+
|
166
|
+
def has_timewarp?
|
167
|
+
!timewarp.nil?
|
168
|
+
end
|
169
|
+
|
170
|
+
def black?
|
171
|
+
reel == 'BL'
|
172
|
+
end
|
173
|
+
alias_method :slug?, :black?
|
174
|
+
|
175
|
+
def length
|
176
|
+
(rec_end_tc - rec_start_tc).to_i
|
177
|
+
end
|
178
|
+
|
179
|
+
def generator?
|
180
|
+
black? || (%(AX GEN).include?(reel))
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
# Represents a transition. We currently only support dissolves and SMPTE wipes
|
185
|
+
# Will be avilable as EDL::Clip#transition
|
186
|
+
class Transition
|
187
|
+
attr_accessor :duration, :effect
|
188
|
+
end
|
189
|
+
|
190
|
+
# Represents a dissolve
|
191
|
+
class Dissolve < Transition; end
|
192
|
+
|
193
|
+
# Represents an SMPTE wipe
|
194
|
+
class Wipe < Transition
|
195
|
+
attr_accessor :smpte_wipe_index
|
196
|
+
end
|
197
|
+
|
198
|
+
# Represents a timewarp
|
199
|
+
class Timewarp
|
200
|
+
attr_accessor :actual_framerate
|
201
|
+
attr_accessor :clip
|
202
|
+
|
203
|
+
def speed_in_percent
|
204
|
+
(actual_framerate.to_f / DEFAULT_FPS.to_f ) * 100
|
205
|
+
end
|
206
|
+
|
207
|
+
# Get the actual end of source that is needed for the timewarp to be computed properly,
|
208
|
+
# round up to not generate stills at ends of clips
|
209
|
+
def actual_src_end_tc
|
210
|
+
unless reverse?
|
211
|
+
clip.src_start_tc + actual_length_of_source
|
212
|
+
else
|
213
|
+
clip.src_start_tc
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
def actual_src_start_tc
|
218
|
+
unless reverse?
|
219
|
+
clip.src_start_tc
|
220
|
+
else
|
221
|
+
clip.src_start_tc - actual_length_of_source
|
222
|
+
end
|
223
|
+
end
|
224
|
+
|
225
|
+
# Returns the true number of frames that is needed to complete the timewarp edit
|
226
|
+
def actual_length_of_source
|
227
|
+
length_in_edit = (clip.src_end_tc - clip.src_start_tc).to_i
|
228
|
+
((length_in_edit / 25.0) * actual_framerate).ceil.abs
|
229
|
+
end
|
230
|
+
|
231
|
+
def reverse?
|
232
|
+
actual_framerate < 0
|
233
|
+
end
|
234
|
+
end
|
235
|
+
|
236
|
+
# A generic matcher
|
237
|
+
class Matcher
|
238
|
+
class ApplyError < RuntimeError
|
239
|
+
def initialize(msg, line)
|
240
|
+
super("%s - offending line was '%s'" % [msg, line])
|
241
|
+
end
|
242
|
+
end
|
243
|
+
|
244
|
+
def initialize(with_regexp)
|
245
|
+
@regexp = with_regexp
|
246
|
+
end
|
247
|
+
|
248
|
+
def matches?(line)
|
249
|
+
line =~ @regexp
|
250
|
+
end
|
251
|
+
|
252
|
+
def apply(stack, line)
|
253
|
+
STDERR.puts "Skipping #{line}"
|
254
|
+
end
|
255
|
+
end
|
256
|
+
|
257
|
+
class NameMatcher < Matcher
|
258
|
+
def initialize
|
259
|
+
super(/\* FROM CLIP NAME:(\s+)(.+)/)
|
260
|
+
end
|
261
|
+
|
262
|
+
def apply(stack, line)
|
263
|
+
stack[-1].clip_name = line.scan(@regexp).flatten.pop.strip
|
264
|
+
end
|
265
|
+
end
|
266
|
+
|
267
|
+
class EffectMatcher < Matcher
|
268
|
+
def initialize
|
269
|
+
super(/\* EFFECT NAME:(\s+)(.+)/)
|
270
|
+
end
|
271
|
+
|
272
|
+
def apply(stack, line)
|
273
|
+
stack[-1].transition.effect = line.scan(@regexp).flatten.pop.strip
|
274
|
+
end
|
275
|
+
end
|
276
|
+
|
277
|
+
class TimewarpMatcher < Matcher
|
278
|
+
|
279
|
+
attr_reader :fps
|
280
|
+
|
281
|
+
def initialize(fps)
|
282
|
+
@fps = fps
|
283
|
+
@regexp = /M2(\s+)(\w+)(\s+)(\-?\d+\.\d+)(\s+)(\d{1,2}):(\d{1,2}):(\d{1,2}):(\d{1,2})/
|
284
|
+
end
|
285
|
+
|
286
|
+
def apply(stack, line)
|
287
|
+
matches = line.scan(@regexp).flatten.map{|e| e.strip}.reject{|e| e.nil? || e.empty?}
|
288
|
+
|
289
|
+
from_reel = matches.shift
|
290
|
+
fps = matches.shift
|
291
|
+
|
292
|
+
begin
|
293
|
+
# FIXME
|
294
|
+
tw_start_source_tc = Parser.timecode_from_line_elements(matches, @fps)
|
295
|
+
rescue Timecode::Error => e
|
296
|
+
raise ApplyError, "Invalid TC in timewarp (#{e})", line
|
297
|
+
end
|
298
|
+
|
299
|
+
evt_with_tw = stack.reverse.find{|e| e.src_start_tc == tw_start_source_tc && e.reel == from_reel }
|
300
|
+
|
301
|
+
unless evt_with_tw
|
302
|
+
raise ApplyError, "Cannot find event marked by timewarp", line
|
303
|
+
else
|
304
|
+
tw = Timewarp.new
|
305
|
+
tw.actual_framerate, tw.clip = fps.to_f, evt_with_tw
|
306
|
+
evt_with_tw.timewarp = tw
|
307
|
+
end
|
308
|
+
end
|
309
|
+
end
|
310
|
+
|
311
|
+
# Drop frame goodbye
|
312
|
+
TC = /(\d{1,2}):(\d{1,2}):(\d{1,2}):(\d{1,2})/
|
313
|
+
|
314
|
+
class EventMatcher < Matcher
|
315
|
+
|
316
|
+
# 021 009 V C 00:39:04:21 00:39:05:09 01:00:26:17 01:00:27:05
|
317
|
+
EVENT_PAT = /(\d+)(\s+)(\w+)(\s+)(\w+)(\s+)(\w+)(\s+)((\w+\s+)?)#{TC} #{TC} #{TC} #{TC}/
|
318
|
+
|
319
|
+
attr_reader :fps
|
320
|
+
|
321
|
+
def initialize(some_fps)
|
322
|
+
super(EVENT_PAT)
|
323
|
+
@fps = some_fps
|
324
|
+
end
|
325
|
+
|
326
|
+
def apply(stack, line)
|
327
|
+
|
328
|
+
matches = line.scan(@regexp).shift
|
329
|
+
props = {:original_line => line}
|
330
|
+
|
331
|
+
# FIrst one is the event number
|
332
|
+
props[:num] = matches.shift
|
333
|
+
matches.shift
|
334
|
+
|
335
|
+
# Then the reel
|
336
|
+
props[:reel] = matches.shift
|
337
|
+
matches.shift
|
338
|
+
|
339
|
+
# Then the track
|
340
|
+
props[:track] = matches.shift
|
341
|
+
matches.shift
|
342
|
+
|
343
|
+
# Then the type
|
344
|
+
props[:transition] = matches.shift
|
345
|
+
matches.shift
|
346
|
+
|
347
|
+
# Then the optional generator group - skip for now
|
348
|
+
if props[:transition] != 'C'
|
349
|
+
props[:duration] = matches.shift.strip
|
350
|
+
else
|
351
|
+
matches.shift
|
352
|
+
end
|
353
|
+
matches.shift
|
354
|
+
|
355
|
+
# Then the timecodes
|
356
|
+
[:src_start_tc, :src_end_tc, :rec_start_tc, :rec_end_tc].each do | k |
|
357
|
+
begin
|
358
|
+
PROPS[K] = edl::pARSER.TIMECODE_FROM_LINE_ELEMENTS(MATCHES, @FPS)
|
359
|
+
rescue Timecode::Error => e
|
360
|
+
raise ApplyError, "Cannot parse timecode - #{e}", line
|
361
|
+
end
|
362
|
+
end
|
363
|
+
|
364
|
+
evt = Clip.new
|
365
|
+
transition_idx = props.delete(:transition)
|
366
|
+
evt.transition = case transition_idx
|
367
|
+
when 'D'
|
368
|
+
d = Dissolve.new
|
369
|
+
d.duration = props.delete(:duration)
|
370
|
+
d
|
371
|
+
when /W/
|
372
|
+
w = Wipe.new
|
373
|
+
w.duration = props.delete(:duration)
|
374
|
+
w.smpte_wipe_index = transition_idx.gsub(/W/, '')
|
375
|
+
w
|
376
|
+
else
|
377
|
+
nil
|
378
|
+
end
|
379
|
+
|
380
|
+
props.each_pair { | k, v | evt.send("#{k}=", v) }
|
381
|
+
|
382
|
+
stack << evt
|
383
|
+
evt # FIXME - we dont need to return this is only used by tests
|
384
|
+
end
|
385
|
+
end
|
386
|
+
|
387
|
+
class Parser
|
388
|
+
|
389
|
+
attr_reader :fps
|
390
|
+
|
391
|
+
# Initialize an EDL parser. Pass the FPS to it, as the usual EDL does not contain any kind of reference
|
392
|
+
# to it's framerate
|
393
|
+
def initialize(with_fps = DEFAULT_FPS)
|
394
|
+
@fps = with_fps
|
395
|
+
end
|
396
|
+
|
397
|
+
def get_matchers
|
398
|
+
[ EventMatcher.new(@fps), EffectMatcher.new, NameMatcher.new, TimewarpMatcher.new(@fps) ]
|
399
|
+
end
|
400
|
+
|
401
|
+
def parse(io)
|
402
|
+
stack, matchers = [], get_matchers
|
403
|
+
until io.eof?
|
404
|
+
current_line = io.gets.strip
|
405
|
+
matchers.each do | matcher |
|
406
|
+
next unless matcher.matches?(current_line)
|
407
|
+
|
408
|
+
begin
|
409
|
+
matcher.apply(stack, current_line)
|
410
|
+
rescue Matcher::ApplyError => e
|
411
|
+
STDERR.puts "Cannot parse #{current_line} - #{e}"
|
412
|
+
end
|
413
|
+
end
|
414
|
+
end
|
415
|
+
|
416
|
+
return List.new(stack)
|
417
|
+
end
|
418
|
+
|
419
|
+
def self.timecode_from_line_elements(elements, fps)
|
420
|
+
args = (0..3).map{|_| elements.shift.to_i} + [fps]
|
421
|
+
Timecode.at(*args)
|
422
|
+
end
|
423
|
+
end
|
424
|
+
|
425
|
+
|
426
|
+
end
|
data/lib/edl/cutter.rb
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
module EDL
|
2
|
+
# Can chop an offline edit into events according to the EDL
|
3
|
+
class Cutter
|
4
|
+
def initialize(source_path)
|
5
|
+
@source_path = source_path
|
6
|
+
end
|
7
|
+
|
8
|
+
def cut(edl)
|
9
|
+
source_for_cutting = edl.from_zero #.without_transitions.without_generators
|
10
|
+
# We need to use the original length in record
|
11
|
+
source_for_cutting.events.each do | evt |
|
12
|
+
cut_segment(evt, evt.rec_start_tc, evt.rec_start_tc + evt.length)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def cut_segment(evt, start_at, end_at)
|
17
|
+
STDERR.puts "Cutting #{@source_path} from #{start_at} to #{end_at} - #{evt.num}"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
class FFMpegCutter < Cutter
|
22
|
+
def cut_segment(evt, start_at, end_at)
|
23
|
+
source_dir, source_file = File.dirname(@source_path), File.basename(@source_path)
|
24
|
+
dest_segment = File.join(source_dir, ('%s_%s' % [evt.num, source_file]))
|
25
|
+
# dest_segment.gsub!(/\.mov$/i, '.mp4')
|
26
|
+
|
27
|
+
offset = end_at - start_at
|
28
|
+
|
29
|
+
cmd = "/opt/local/bin/ffmpeg -i #{@source_path} -ss #{start_at} -vframes #{offset.total} -vcodec photojpeg -acodec copy #{dest_segment}"
|
30
|
+
#puts cmd
|
31
|
+
`#{cmd}`
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
data/lib/edl/grabber.rb
ADDED
@@ -0,0 +1,30 @@
|
|
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
|
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} -vframes 1 -r #{at.fps} -y #{to_file}%d.jpg"
|
26
|
+
cmd = "#{ffmpeg_bin} -i #{@source_path} -an -ss #{at} -vframes 1 -y #{to_file}%d.jpg"
|
27
|
+
`#{cmd}`
|
28
|
+
end
|
29
|
+
end
|
30
|
+
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
|
+
|
Binary file
|
data/test/test_edl.rb
ADDED
@@ -0,0 +1,303 @@
|
|
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 'flexmock'
|
8
|
+
require 'flexmock/test_unit'
|
9
|
+
|
10
|
+
TRAILER_EDL = File.dirname(__FILE__) + '/samples/TRAILER_EDL.edl'
|
11
|
+
SIMPLE_DISSOLVE = File.dirname(__FILE__) + '/samples/SIMPLE_DISSOLVE.EDL'
|
12
|
+
SPLICEME = File.dirname(__FILE__) + '/samples/SPLICEME.EDL'
|
13
|
+
SIMPLE_TIMEWARP = File.dirname(__FILE__) + '/samples/TIMEWARP.EDL'
|
14
|
+
SLOMO_TIMEWARP = File.dirname(__FILE__) + '/samples/TIMEWARP_HALF.EDL'
|
15
|
+
FORTY_FIVER = File.dirname(__FILE__) + '/samples/45S_SAMPLE.EDL'
|
16
|
+
REVERSE = File.dirname(__FILE__) + '/samples/REVERSE.EDL'
|
17
|
+
|
18
|
+
class TestEvent < Test::Unit::TestCase
|
19
|
+
def test_attributes_defined
|
20
|
+
evt = EDL::Event.new
|
21
|
+
%w( num reel track src_start_tc src_end_tc rec_start_tc rec_end_tc ).each do | em |
|
22
|
+
assert_respond_to evt, em
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
class Test::Unit::TestCase
|
28
|
+
def parse_evt(matcher_klass, line)
|
29
|
+
stack = []
|
30
|
+
matcher_klass.new.apply(stack, line)
|
31
|
+
stack.pop
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
class TestParser < Test::Unit::TestCase
|
36
|
+
def test_inst
|
37
|
+
assert_nothing_raised { EDL::Parser.new }
|
38
|
+
end
|
39
|
+
|
40
|
+
|
41
|
+
def test_inits_matchers_with_framerate
|
42
|
+
p = EDL::Parser.new(30)
|
43
|
+
matchers = p.get_matchers
|
44
|
+
event_matcher = matchers.find{|e| e.is_a?(EDL::EventMatcher) }
|
45
|
+
assert_equal 30, event_matcher.fps
|
46
|
+
end
|
47
|
+
|
48
|
+
def test_timecode_from_elements
|
49
|
+
elems = ["08", "04", "24", "24"]
|
50
|
+
assert_nothing_raised { @tc = EDL::Parser.timecode_from_line_elements(elems, 30) }
|
51
|
+
assert_kind_of Timecode, @tc
|
52
|
+
assert_equal "08:04:24:24", @tc.to_s
|
53
|
+
assert_equal 30, @tc.fps
|
54
|
+
assert elems.empty?, "The elements used for timecode should have been removed from the array"
|
55
|
+
end
|
56
|
+
|
57
|
+
def test_dissolve
|
58
|
+
p = EDL::Parser.new
|
59
|
+
assert_nothing_raised{ @edl = p.parse File.open(SIMPLE_DISSOLVE) }
|
60
|
+
assert_kind_of EDL::List, @edl
|
61
|
+
assert_equal 2, @edl.events.length
|
62
|
+
|
63
|
+
first = @edl.events[0]
|
64
|
+
assert_kind_of EDL::Clip, first
|
65
|
+
|
66
|
+
second = @edl.events[1]
|
67
|
+
assert_kind_of EDL::Clip, second
|
68
|
+
assert second.has_transition?
|
69
|
+
|
70
|
+
no_trans = @edl.without_transitions
|
71
|
+
|
72
|
+
assert_equal 2, no_trans.events.length
|
73
|
+
target_tc = (Timecode.parse('01:00:00:00') + 43)
|
74
|
+
assert_equal target_tc, no_trans.events[0].rec_end_tc,
|
75
|
+
"The incoming clip should have been extended by the length of the dissolve"
|
76
|
+
|
77
|
+
target_tc = Timecode.parse('01:00:00:00')
|
78
|
+
assert_equal target_tc, no_trans.events[1].rec_start_tc
|
79
|
+
"The outgoing clip should have been left in place"
|
80
|
+
end
|
81
|
+
|
82
|
+
def test_spliced
|
83
|
+
p = EDL::Parser.new
|
84
|
+
assert_nothing_raised{ @edl = p.parse(File.open(SPLICEME)) }
|
85
|
+
assert_equal 3, @edl.events.length
|
86
|
+
|
87
|
+
spliced = @edl.spliced
|
88
|
+
assert_equal 1, spliced.events.length, "Should have been spliced to one event"
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
class TimewarpMatcherTest < Test::Unit::TestCase
|
93
|
+
|
94
|
+
def test_parses_as_one_event
|
95
|
+
@edl = EDL::Parser.new.parse(File.open(SIMPLE_TIMEWARP))
|
96
|
+
assert_kind_of EDL::List, @edl
|
97
|
+
assert_equal 1, @edl.events.length
|
98
|
+
end
|
99
|
+
|
100
|
+
def test_timewarp_attributes
|
101
|
+
@edl = EDL::Parser.new.parse(File.open(SIMPLE_TIMEWARP))
|
102
|
+
assert_kind_of EDL::List, @edl
|
103
|
+
assert_equal 1, @edl.events.length
|
104
|
+
|
105
|
+
clip = @edl.events[0]
|
106
|
+
assert clip.has_timewarp?, "Should respond true to has_timewarp?"
|
107
|
+
assert_not_nil clip.timewarp
|
108
|
+
assert_kind_of EDL::Timewarp, clip.timewarp
|
109
|
+
|
110
|
+
assert clip.timewarp.actual_src_end_tc > clip.src_end_tc
|
111
|
+
assert_equal "03:03:24:18", clip.timewarp.actual_src_end_tc.to_s
|
112
|
+
assert_equal 124, clip.timewarp.actual_length_of_source
|
113
|
+
assert !clip.timewarp.reverse?
|
114
|
+
|
115
|
+
end
|
116
|
+
|
117
|
+
def test_timwarp_slomo
|
118
|
+
@edl = EDL::Parser.new.parse(File.open(SLOMO_TIMEWARP))
|
119
|
+
clip = @edl.events[0]
|
120
|
+
assert clip.has_timewarp?, "Should respond true to has_timewarp?"
|
121
|
+
assert_not_nil clip.timewarp
|
122
|
+
assert_kind_of EDL::Timewarp, clip.timewarp
|
123
|
+
|
124
|
+
assert clip.timewarp.actual_src_end_tc < clip.src_end_tc
|
125
|
+
assert_equal "03:03:19:24", clip.timewarp.actual_src_end_tc.to_s
|
126
|
+
assert_equal 10, clip.length
|
127
|
+
assert_equal 5, clip.timewarp.actual_length_of_source
|
128
|
+
assert_equal 50, clip.timewarp.speed_in_percent.to_i
|
129
|
+
assert !clip.timewarp.reverse?
|
130
|
+
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
class ReverseTimewarpTest < Test::Unit::TestCase
|
135
|
+
def test_parse
|
136
|
+
@edl = EDL::Parser.new.parse(File.open(REVERSE))
|
137
|
+
assert_equal 1, @edl.events.length
|
138
|
+
|
139
|
+
clip = @edl.events[0]
|
140
|
+
assert_equal 52, clip.length
|
141
|
+
|
142
|
+
assert clip.has_timewarp?, "Should respond true to has_timewarp?"
|
143
|
+
tw = clip.timewarp
|
144
|
+
|
145
|
+
assert_equal( -25, tw.actual_framerate.to_i)
|
146
|
+
assert tw.reverse?
|
147
|
+
assert_equal clip.length, tw.actual_length_of_source
|
148
|
+
assert_equal clip.src_start_tc, tw.actual_src_end_tc
|
149
|
+
assert_equal clip.src_start_tc - 52, tw.actual_src_start_tc
|
150
|
+
assert_equal( -100, clip.timewarp.speed_in_percent.to_i)
|
151
|
+
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
class EventMatcherTest < Test::Unit::TestCase
|
156
|
+
|
157
|
+
EVT_PATTERNS = [
|
158
|
+
'020 008C V C 08:04:24:24 08:04:25:19 01:00:25:22 01:00:26:17',
|
159
|
+
'021 009 V C 00:39:04:21 00:39:05:09 01:00:26:17 01:00:27:05',
|
160
|
+
'022 008C V C 08:08:01:23 08:08:02:18 01:00:27:05 01:00:28:00',
|
161
|
+
'023 008C V C 08:07:30:02 08:07:30:21 01:00:28:00 01:00:28:19',
|
162
|
+
'024 AX V C 00:00:00:00 00:00:01:00 01:00:28:19 01:00:29:19',
|
163
|
+
'025 BL V C 00:00:00:00 00:00:00:00 01:00:29:19 01:00:29:19',
|
164
|
+
'025 GEN V D 025 00:00:55:10 00:00:58:11 01:00:29:19 01:00:32:20',
|
165
|
+
]
|
166
|
+
|
167
|
+
def test_clip_generation_from_line
|
168
|
+
m = EDL::EventMatcher.new(25)
|
169
|
+
|
170
|
+
clip = m.apply([],
|
171
|
+
'020 008C V C 08:04:24:24 08:04:25:19 01:00:25:22 01:00:26:17'
|
172
|
+
)
|
173
|
+
|
174
|
+
assert_not_nil clip
|
175
|
+
assert_kind_of EDL::Clip, clip
|
176
|
+
assert_equal '020', clip.num
|
177
|
+
assert_equal '008C', clip.reel
|
178
|
+
assert_equal 'V', clip.track
|
179
|
+
assert_equal '08:04:24:24', clip.src_start_tc.to_s
|
180
|
+
assert_equal '08:04:25:19', clip.src_end_tc.to_s
|
181
|
+
assert_equal '01:00:25:22', clip.rec_start_tc.to_s
|
182
|
+
assert_equal '01:00:26:17', clip.rec_end_tc.to_s
|
183
|
+
assert_equal '020 008C V C 08:04:24:24 08:04:25:19 01:00:25:22 01:00:26:17', clip.original_line
|
184
|
+
end
|
185
|
+
|
186
|
+
def test_dissolve_generation_from_line
|
187
|
+
m = EDL::EventMatcher.new(25)
|
188
|
+
dissolve = m.apply([],
|
189
|
+
'025 GEN V D 025 00:00:55:10 00:00:58:11 01:00:29:19 01:00:32:20'
|
190
|
+
)
|
191
|
+
assert_not_nil dissolve
|
192
|
+
assert_kind_of EDL::Clip, dissolve
|
193
|
+
assert_equal '025', dissolve.num
|
194
|
+
assert_equal 'GEN', dissolve.reel
|
195
|
+
assert_equal 'V', dissolve.track
|
196
|
+
|
197
|
+
assert dissolve.has_transition?
|
198
|
+
assert_not_nil dissolve.transition
|
199
|
+
assert_kind_of EDL::Dissolve, dissolve.transition
|
200
|
+
assert_equal '025', dissolve.transition.duration
|
201
|
+
end
|
202
|
+
|
203
|
+
def test_wipe_generation_from_line
|
204
|
+
m = EDL::EventMatcher.new(25)
|
205
|
+
wipe = m.apply([],
|
206
|
+
'025 GEN V W001 025 00:00:55:10 00:00:58:11 01:00:29:19 01:00:32:20'
|
207
|
+
)
|
208
|
+
assert_not_nil wipe
|
209
|
+
assert_kind_of EDL::Clip, wipe
|
210
|
+
assert wipe.generator?
|
211
|
+
assert_equal '025', wipe.num
|
212
|
+
assert_equal 'GEN', wipe.reel
|
213
|
+
assert_equal 'V', wipe.track
|
214
|
+
|
215
|
+
assert wipe.has_transition?
|
216
|
+
|
217
|
+
assert_not_nil wipe.transition
|
218
|
+
assert_kind_of EDL::Wipe, wipe.transition
|
219
|
+
assert_equal '025', wipe.transition.duration
|
220
|
+
assert_equal '001', wipe.transition.smpte_wipe_index
|
221
|
+
end
|
222
|
+
|
223
|
+
def test_black_generation_from_line
|
224
|
+
m = EDL::EventMatcher.new(25)
|
225
|
+
black = m.apply([],
|
226
|
+
'025 BL V C 00:00:00:00 00:00:00:00 01:00:29:19 01:00:29:19'
|
227
|
+
)
|
228
|
+
|
229
|
+
assert_not_nil black
|
230
|
+
|
231
|
+
assert black.black?, "Black should be black?"
|
232
|
+
assert black.slug?, "Black should be slug?"
|
233
|
+
|
234
|
+
assert black.generator?, "Should be generator?"
|
235
|
+
assert_equal '025', black.num
|
236
|
+
assert_equal 'BL', black.reel
|
237
|
+
assert_equal 'V', black.track
|
238
|
+
assert_equal '025 BL V C 00:00:00:00 00:00:00:00 01:00:29:19 01:00:29:19', black.original_line
|
239
|
+
end
|
240
|
+
|
241
|
+
def test_matches_all_patterns
|
242
|
+
EVT_PATTERNS.each do | pat |
|
243
|
+
assert EDL::EventMatcher.new(25).matches?(pat), "EventMatcher should match #{pat}"
|
244
|
+
end
|
245
|
+
end
|
246
|
+
end
|
247
|
+
|
248
|
+
class ClipNameMatcherTest < Test::Unit::TestCase
|
249
|
+
def test_matches
|
250
|
+
line = "* FROM CLIP NAME: TAPE_6-10.MOV"
|
251
|
+
assert EDL::NameMatcher.new.matches?(line)
|
252
|
+
end
|
253
|
+
|
254
|
+
def test_apply
|
255
|
+
line = "* FROM CLIP NAME: TAPE_6-10.MOV"
|
256
|
+
mok_evt = flexmock
|
257
|
+
mok_evt.should_receive(:clip_name=).with('TAPE_6-10.MOV').once
|
258
|
+
EDL::NameMatcher.new.apply([mok_evt], line)
|
259
|
+
end
|
260
|
+
end
|
261
|
+
|
262
|
+
class EffectMatcherTest < Test::Unit::TestCase
|
263
|
+
def test_matches
|
264
|
+
line = "* EFFECT NAME: CROSS DISSOLVE"
|
265
|
+
assert EDL::EffectMatcher.new.matches?(line)
|
266
|
+
end
|
267
|
+
|
268
|
+
def test_apply
|
269
|
+
line = "* EFFECT NAME: CROSS DISSOLVE"
|
270
|
+
mok_evt, mok_transition = flexmock, flexmock
|
271
|
+
|
272
|
+
mok_evt.should_receive(:transition).once.and_return(mok_transition)
|
273
|
+
mok_transition.should_receive(:effect=).with("CROSS DISSOLVE").once
|
274
|
+
|
275
|
+
EDL::EffectMatcher.new.apply([mok_evt], line)
|
276
|
+
end
|
277
|
+
end
|
278
|
+
|
279
|
+
class ComplexTest < Test::Unit::TestCase
|
280
|
+
def test_parses_cleanly
|
281
|
+
assert_nothing_raised { EDL::Parser.new.parse(File.open(FORTY_FIVER)) }
|
282
|
+
end
|
283
|
+
|
284
|
+
def test_from_zero
|
285
|
+
complex = EDL::Parser.new.parse(File.open(FORTY_FIVER))
|
286
|
+
|
287
|
+
from_zero = complex.from_zero
|
288
|
+
assert_equal '00:00:00:00', from_zero.events[0].rec_start_tc.to_s,
|
289
|
+
"The starting timecode of the first event should have been shifted to zero"
|
290
|
+
assert_equal '00:00:42:16', from_zero.events[-1].rec_end_tc.to_s,
|
291
|
+
"The ending timecode of the last event should have been shifted 10 hours back"
|
292
|
+
end
|
293
|
+
end
|
294
|
+
|
295
|
+
# class GrabberTest < Test::Unit::TestCase
|
296
|
+
# FILM = '/Users/julik/Downloads/HC_CORRECT-TCS_VIDEO1.edl.txt'
|
297
|
+
# def test_cutter
|
298
|
+
# complex = EDL::Parser.new.parse(File.open(FILM))
|
299
|
+
# cutter = EDL::Grabber.new("/Users/julik/Desktop/Cutto/HC_CORRECT-TCS.mov")
|
300
|
+
# cutter.ffmpeg_bin = '/opt/local/bin/ffmpeg'
|
301
|
+
# cutter.grab(complex)
|
302
|
+
# end
|
303
|
+
# end
|
metadata
ADDED
@@ -0,0 +1,98 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: edl
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Julik
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2008-12-27 00:00:00 +01:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: flexmock
|
17
|
+
type: :runtime
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: "0"
|
24
|
+
version:
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: timecode
|
27
|
+
type: :runtime
|
28
|
+
version_requirement:
|
29
|
+
version_requirements: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: "0"
|
34
|
+
version:
|
35
|
+
- !ruby/object:Gem::Dependency
|
36
|
+
name: hoe
|
37
|
+
type: :development
|
38
|
+
version_requirement:
|
39
|
+
version_requirements: !ruby/object:Gem::Requirement
|
40
|
+
requirements:
|
41
|
+
- - ">="
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: 1.8.2
|
44
|
+
version:
|
45
|
+
description: Work with EDL files from Ruby
|
46
|
+
email:
|
47
|
+
- me@julik.nl
|
48
|
+
executables: []
|
49
|
+
|
50
|
+
extensions: []
|
51
|
+
|
52
|
+
extra_rdoc_files:
|
53
|
+
- History.txt
|
54
|
+
- README.txt
|
55
|
+
files:
|
56
|
+
- lib/edl.rb
|
57
|
+
- lib/edl/cutter.rb
|
58
|
+
- lib/edl/grabber.rb
|
59
|
+
- test/samples/45S_SAMPLE.EDL
|
60
|
+
- test/samples/REVERSE.EDL
|
61
|
+
- test/samples/SIMPLE_DISSOLVE.EDL
|
62
|
+
- test/samples/SPLICEME.EDL
|
63
|
+
- test/samples/TIMEWARP.EDL
|
64
|
+
- test/samples/TIMEWARP_HALF.EDL
|
65
|
+
- test/samples/TRAILER_EDL.edl
|
66
|
+
- test/test_edl.rb
|
67
|
+
- Rakefile
|
68
|
+
- History.txt
|
69
|
+
- README.txt
|
70
|
+
has_rdoc: true
|
71
|
+
homepage:
|
72
|
+
post_install_message:
|
73
|
+
rdoc_options:
|
74
|
+
- --main
|
75
|
+
- README.txt
|
76
|
+
require_paths:
|
77
|
+
- lib
|
78
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: "0"
|
83
|
+
version:
|
84
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
85
|
+
requirements:
|
86
|
+
- - ">="
|
87
|
+
- !ruby/object:Gem::Version
|
88
|
+
version: "0"
|
89
|
+
version:
|
90
|
+
requirements: []
|
91
|
+
|
92
|
+
rubyforge_project: wiretap
|
93
|
+
rubygems_version: 1.3.1
|
94
|
+
signing_key:
|
95
|
+
specification_version: 2
|
96
|
+
summary: Work with EDL files from Ruby
|
97
|
+
test_files:
|
98
|
+
- test/test_edl.rb
|