edl 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +11 -0
- data/Manifest.txt +25 -0
- data/README.txt +12 -1
- data/Rakefile +6 -1
- data/SPECS.txt +75 -0
- data/bin/edl +0 -0
- data/illustr/edl-explain.ai +1182 -2
- data/lib/edl.rb +66 -146
- data/lib/edl/cutter.rb +2 -2
- data/lib/edl/event.rb +132 -0
- data/lib/edl/grabber.rb +2 -3
- data/lib/edl/timewarp.rb +45 -0
- data/lib/edl/transition.rb +23 -0
- data/test/.DS_Store +0 -0
- data/test/samples/FCP_REVERSE.EDL +9 -0
- data/test/samples/SPEEDUP_AND_FADEOUT.EDL +11 -0
- data/test/samples/SPEEDUP_REVERSE_AND_FADEOUT.EDL +11 -0
- data/test/samples/SPLICEME.EDL +1 -4
- data/test/test_edl.rb +389 -169
- metadata +32 -9
data/lib/edl.rb
CHANGED
@@ -1,31 +1,34 @@
|
|
1
1
|
require "rubygems"
|
2
2
|
require "timecode"
|
3
|
+
require 'stringio'
|
4
|
+
|
5
|
+
require File.dirname(__FILE__) + '/edl/event'
|
6
|
+
require File.dirname(__FILE__) + '/edl/transition'
|
7
|
+
require File.dirname(__FILE__) + '/edl/timewarp'
|
3
8
|
|
4
9
|
# A simplistic EDL parser. Current limitations: no support for DF timecode, no support for audio,
|
5
10
|
# no support for split edits, no support for key effects, no support for audio
|
6
11
|
module EDL
|
7
|
-
VERSION = "0.0.
|
8
|
-
|
12
|
+
VERSION = "0.0.2"
|
9
13
|
DEFAULT_FPS = 25
|
10
14
|
|
11
15
|
# Represents an EDL, is returned from the parser. Traditional operation is functional style, i.e.
|
12
16
|
# edl.renumbered.without_transitions.without_generators
|
13
|
-
class List
|
14
|
-
attr_accessor :events, :fps
|
17
|
+
class List < Array
|
15
18
|
|
16
|
-
def
|
17
|
-
|
19
|
+
def events #:nodoc:
|
20
|
+
STDERR.puts "EDL::List#events is deprecated and will be removed, use EDL::List as an array instead"
|
21
|
+
self
|
18
22
|
end
|
19
23
|
|
20
|
-
# Return the same EDL with all dissolves stripped and replaced by the clips
|
24
|
+
# Return the same EDL with all dissolves stripped and replaced by the clips underneath
|
21
25
|
def without_transitions
|
22
26
|
# Find dissolves
|
23
27
|
cpy = []
|
24
|
-
|
25
|
-
@events.each_with_index do | e, i |
|
28
|
+
each_with_index do | e, i |
|
26
29
|
# A dissolve always FOLLOWS the incoming clip
|
27
|
-
if
|
28
|
-
dissolve =
|
30
|
+
if e.ends_with_transition?
|
31
|
+
dissolve = self[i+1]
|
29
32
|
len = dissolve.transition.duration.to_i
|
30
33
|
|
31
34
|
# The dissolve contains the OUTGOING clip, we are the INCOMING. Extend the
|
@@ -34,7 +37,7 @@ module EDL
|
|
34
37
|
incoming.src_end_tc += len
|
35
38
|
incoming.rec_end_tc += len
|
36
39
|
|
37
|
-
outgoing = dissolve.copy_properties_to(
|
40
|
+
outgoing = dissolve.copy_properties_to(Event.new)
|
38
41
|
|
39
42
|
# Add the A suffix to the ex-dissolve
|
40
43
|
outgoing.num += 'A'
|
@@ -43,7 +46,7 @@ module EDL
|
|
43
46
|
cpy << incoming
|
44
47
|
cpy << outgoing
|
45
48
|
elsif e.has_transition?
|
46
|
-
# Skip, already handled
|
49
|
+
# Skip, already handled on the previous clip
|
47
50
|
else
|
48
51
|
cpy << e.dup
|
49
52
|
end
|
@@ -53,15 +56,14 @@ module EDL
|
|
53
56
|
self.class.new(cpy)
|
54
57
|
end
|
55
58
|
|
59
|
+
# Return the same EDL, with events renumbered starting from 001
|
56
60
|
def renumbered
|
57
|
-
renumed =
|
61
|
+
renumed = self.dup
|
58
62
|
pad = renumed.length.to_s.length
|
59
63
|
pad = 3 if pad < 3
|
60
64
|
|
61
65
|
(0...renumed.length).map{|e| renumed[e].num = "%0#{pad}d" % (e+1) }
|
62
|
-
|
63
|
-
puts x.inspect
|
64
|
-
x
|
66
|
+
self.class.new(renumed)
|
65
67
|
end
|
66
68
|
|
67
69
|
# Return the same EDL with all timewarps expanded to native length. Clip length
|
@@ -69,8 +71,7 @@ module EDL
|
|
69
71
|
# (so this is best used in concert with the original EDL where record TC is pristine)
|
70
72
|
def without_timewarps
|
71
73
|
self.class.new(
|
72
|
-
|
73
|
-
|
74
|
+
map do | e |
|
74
75
|
if e.has_timewarp?
|
75
76
|
repl = e.copy_properties_to(e.class.new)
|
76
77
|
from, to = e.timewarp.actual_src_start_tc, e.timewarp.actual_src_end_tc
|
@@ -86,7 +87,7 @@ module EDL
|
|
86
87
|
# Return the same EDL without AX, BL and other GEN events (like slug, text and solids).
|
87
88
|
# Usually used in concert with "without_transitions"
|
88
89
|
def without_generators
|
89
|
-
self.class.new(
|
90
|
+
self.class.new(self.reject{|e| e.generator? })
|
90
91
|
end
|
91
92
|
|
92
93
|
# Return the list of clips used by this EDL at full capture length
|
@@ -97,9 +98,9 @@ module EDL
|
|
97
98
|
# Return the same EDL with the first event starting at 00:00:00:00 and all subsequent events
|
98
99
|
# shifted accordingly
|
99
100
|
def from_zero
|
100
|
-
shift_by =
|
101
|
+
shift_by = self[0].rec_start_tc
|
101
102
|
self.class.new(
|
102
|
-
|
103
|
+
map do | original |
|
103
104
|
e = original.dup
|
104
105
|
e.rec_start_tc = (e.rec_start_tc - shift_by)
|
105
106
|
e.rec_end_tc = (e.rec_end_tc - shift_by)
|
@@ -111,7 +112,7 @@ module EDL
|
|
111
112
|
# Return the same EDL with neighbouring clips joined at cuts where applicable (if a clip
|
112
113
|
# is divided in two pieces it will be spliced). Most useful in combination with without_timewarps
|
113
114
|
def spliced
|
114
|
-
spliced_edl =
|
115
|
+
spliced_edl = inject([]) do | spliced, cur |
|
115
116
|
latest = spliced[-1]
|
116
117
|
# Append to latest if splicable
|
117
118
|
if latest && (latest.reel == cur.reel) && (cur.src_start_tc == (latest.src_end_tc + 1))
|
@@ -126,112 +127,7 @@ module EDL
|
|
126
127
|
end
|
127
128
|
end
|
128
129
|
|
129
|
-
|
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
|
130
|
+
#:stopdoc:
|
235
131
|
|
236
132
|
# A generic matcher
|
237
133
|
class Matcher
|
@@ -246,7 +142,7 @@ module EDL
|
|
246
142
|
end
|
247
143
|
|
248
144
|
def matches?(line)
|
249
|
-
line =~ @regexp
|
145
|
+
!!(line =~ @regexp)
|
250
146
|
end
|
251
147
|
|
252
148
|
def apply(stack, line)
|
@@ -254,6 +150,18 @@ module EDL
|
|
254
150
|
end
|
255
151
|
end
|
256
152
|
|
153
|
+
# EDL clip comment matcher, a generic one
|
154
|
+
class CommentMatcher < Matcher
|
155
|
+
def initialize
|
156
|
+
super(/\* (.+)/)
|
157
|
+
end
|
158
|
+
|
159
|
+
def apply(stack, line)
|
160
|
+
stack[-1].comments << line.scan(@regexp).flatten.pop.strip
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
# Clip name matcher
|
257
165
|
class NameMatcher < Matcher
|
258
166
|
def initialize
|
259
167
|
super(/\* FROM CLIP NAME:(\s+)(.+)/)
|
@@ -261,6 +169,7 @@ module EDL
|
|
261
169
|
|
262
170
|
def apply(stack, line)
|
263
171
|
stack[-1].clip_name = line.scan(@regexp).flatten.pop.strip
|
172
|
+
CommentMatcher.new.apply(stack, line)
|
264
173
|
end
|
265
174
|
end
|
266
175
|
|
@@ -271,6 +180,7 @@ module EDL
|
|
271
180
|
|
272
181
|
def apply(stack, line)
|
273
182
|
stack[-1].transition.effect = line.scan(@regexp).flatten.pop.strip
|
183
|
+
CommentMatcher.new.apply(stack, line)
|
274
184
|
end
|
275
185
|
end
|
276
186
|
|
@@ -297,7 +207,7 @@ module EDL
|
|
297
207
|
end
|
298
208
|
|
299
209
|
evt_with_tw = stack.reverse.find{|e| e.src_start_tc == tw_start_source_tc && e.reel == from_reel }
|
300
|
-
|
210
|
+
|
301
211
|
unless evt_with_tw
|
302
212
|
raise ApplyError, "Cannot find event marked by timewarp", line
|
303
213
|
else
|
@@ -326,7 +236,7 @@ module EDL
|
|
326
236
|
def apply(stack, line)
|
327
237
|
|
328
238
|
matches = line.scan(@regexp).shift
|
329
|
-
props = {
|
239
|
+
props = {}
|
330
240
|
|
331
241
|
# FIrst one is the event number
|
332
242
|
props[:num] = matches.shift
|
@@ -355,28 +265,33 @@ module EDL
|
|
355
265
|
# Then the timecodes
|
356
266
|
[:src_start_tc, :src_end_tc, :rec_start_tc, :rec_end_tc].each do | k |
|
357
267
|
begin
|
358
|
-
|
268
|
+
props[k] = EDL::Parser.timecode_from_line_elements(matches, @fps)
|
359
269
|
rescue Timecode::Error => e
|
360
270
|
raise ApplyError, "Cannot parse timecode - #{e}", line
|
361
271
|
end
|
362
272
|
end
|
363
273
|
|
364
|
-
evt =
|
274
|
+
evt = Event.new
|
365
275
|
transition_idx = props.delete(:transition)
|
366
276
|
evt.transition = case transition_idx
|
367
277
|
when 'D'
|
368
278
|
d = Dissolve.new
|
369
|
-
d.duration = props.delete(:duration)
|
279
|
+
d.duration = props.delete(:duration).to_i
|
370
280
|
d
|
371
|
-
when /W/
|
281
|
+
when /W(\d+)/
|
372
282
|
w = Wipe.new
|
373
|
-
w.duration = props.delete(:duration)
|
283
|
+
w.duration = props.delete(:duration).to_i
|
374
284
|
w.smpte_wipe_index = transition_idx.gsub(/W/, '')
|
375
285
|
w
|
376
286
|
else
|
377
287
|
nil
|
378
288
|
end
|
379
289
|
|
290
|
+
# Give a hint on the incoming clip as well
|
291
|
+
if evt.transition && stack[-1]
|
292
|
+
stack[-1].outgoing_transition_duration = evt.transition.duration
|
293
|
+
end
|
294
|
+
|
380
295
|
props.each_pair { | k, v | evt.send("#{k}=", v) }
|
381
296
|
|
382
297
|
stack << evt
|
@@ -384,6 +299,9 @@ module EDL
|
|
384
299
|
end
|
385
300
|
end
|
386
301
|
|
302
|
+
#:startdoc:
|
303
|
+
|
304
|
+
# Is used to parse an EDL
|
387
305
|
class Parser
|
388
306
|
|
389
307
|
attr_reader :fps
|
@@ -394,12 +312,15 @@ module EDL
|
|
394
312
|
@fps = with_fps
|
395
313
|
end
|
396
314
|
|
397
|
-
def get_matchers
|
398
|
-
[ EventMatcher.new(@fps), EffectMatcher.new, NameMatcher.new, TimewarpMatcher.new(@fps) ]
|
315
|
+
def get_matchers #:nodoc:
|
316
|
+
[ EventMatcher.new(@fps), EffectMatcher.new, NameMatcher.new, TimewarpMatcher.new(@fps), CommentMatcher.new ]
|
399
317
|
end
|
400
318
|
|
319
|
+
# Parse a passed File or IO object line by line, or the whole string
|
401
320
|
def parse(io)
|
402
|
-
|
321
|
+
return parse(StringIO.new(io.to_s)) unless io.respond_to?(:eof?)
|
322
|
+
|
323
|
+
stack, matchers = List.new, get_matchers
|
403
324
|
until io.eof?
|
404
325
|
current_line = io.gets.strip
|
405
326
|
matchers.each do | matcher |
|
@@ -412,15 +333,14 @@ module EDL
|
|
412
333
|
end
|
413
334
|
end
|
414
335
|
end
|
415
|
-
|
416
|
-
return List.new(stack)
|
336
|
+
stack
|
417
337
|
end
|
418
338
|
|
419
|
-
|
420
|
-
|
339
|
+
# Init a Timecode object from the passed elements with the passed framerate
|
340
|
+
def self.timecode_from_line_elements(elements, fps) #:nodoc:
|
341
|
+
args = (0..3).map{|_| elements.shift.to_i} + [fps.to_f]
|
421
342
|
Timecode.at(*args)
|
422
343
|
end
|
423
344
|
end
|
424
|
-
|
425
|
-
|
345
|
+
|
426
346
|
end
|
data/lib/edl/cutter.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
module EDL
|
2
2
|
# Can chop an offline edit into events according to the EDL
|
3
|
-
class Cutter
|
3
|
+
class Cutter #:nodoc:
|
4
4
|
def initialize(source_path)
|
5
5
|
@source_path = source_path
|
6
6
|
end
|
@@ -18,7 +18,7 @@ module EDL
|
|
18
18
|
end
|
19
19
|
end
|
20
20
|
|
21
|
-
class FFMpegCutter < Cutter
|
21
|
+
class FFMpegCutter < Cutter #:nodoc:
|
22
22
|
def cut_segment(evt, start_at, end_at)
|
23
23
|
source_dir, source_file = File.dirname(@source_path), File.basename(@source_path)
|
24
24
|
dest_segment = File.join(source_dir, ('%s_%s' % [evt.num, source_file]))
|
data/lib/edl/event.rb
ADDED
@@ -0,0 +1,132 @@
|
|
1
|
+
module EDL
|
2
|
+
# Represents an edit event
|
3
|
+
class Event
|
4
|
+
# Event number as in the EDL
|
5
|
+
attr_accessor :num
|
6
|
+
|
7
|
+
# Reel name as in the EDL
|
8
|
+
attr_accessor :reel
|
9
|
+
|
10
|
+
# Event tracks as in the EDL
|
11
|
+
attr_accessor :track
|
12
|
+
|
13
|
+
# Source start timecode of the start frame as in the EDL,
|
14
|
+
# no timewarps or dissolves included
|
15
|
+
attr_accessor :src_start_tc
|
16
|
+
|
17
|
+
# Source end timecode of the last frame as in the EDL,
|
18
|
+
# no timewarps or dissolves included
|
19
|
+
attr_accessor :src_end_tc
|
20
|
+
|
21
|
+
# Record start timecode of the event in the master as in the EDL
|
22
|
+
attr_accessor :rec_start_tc
|
23
|
+
|
24
|
+
# Record end timecode of the event in the master as in the EDL,
|
25
|
+
# outgoing transition is not included
|
26
|
+
attr_accessor :rec_end_tc
|
27
|
+
|
28
|
+
# Array of comment lines verbatim (all comments are included)
|
29
|
+
attr_accessor :comments
|
30
|
+
|
31
|
+
# Clip name contained in FROM CLIP NAME: comment
|
32
|
+
attr_accessor :clip_name
|
33
|
+
|
34
|
+
# Timewarp metadata (an EDL::Timewarp), or nil if no retime is made
|
35
|
+
attr_accessor :timewarp
|
36
|
+
|
37
|
+
# Incoming transition metadata (EDL::Transition), or nil if no transition is used
|
38
|
+
attr_accessor :transition
|
39
|
+
|
40
|
+
# How long is the incoming transition on the next event
|
41
|
+
attr_accessor :outgoing_transition_duration
|
42
|
+
|
43
|
+
def initialize(opts = {})
|
44
|
+
opts.each_pair{|k,v| send("#{k}=", v) }
|
45
|
+
yield(self) if block_given?
|
46
|
+
end
|
47
|
+
|
48
|
+
# Output a textual description (will not work as an EDL line!)
|
49
|
+
def to_s
|
50
|
+
%w( num reel track src_start_tc src_end_tc rec_start_tc rec_end_tc).map{|a| self.send(a).to_s}.join(" ")
|
51
|
+
end
|
52
|
+
|
53
|
+
def inspect
|
54
|
+
to_s
|
55
|
+
end
|
56
|
+
|
57
|
+
def comments #:nodoc:
|
58
|
+
@comments ||= []
|
59
|
+
@comments
|
60
|
+
end
|
61
|
+
|
62
|
+
def outgoing_transition_duration #:nodoc:
|
63
|
+
@outgoing_transition_duration || 0
|
64
|
+
end
|
65
|
+
|
66
|
+
# Is the clip reversed in the edit?
|
67
|
+
def reverse?
|
68
|
+
(timewarp && timewarp.reverse?)
|
69
|
+
end
|
70
|
+
alias_method :reversed?, :reverse?
|
71
|
+
|
72
|
+
def copy_properties_to(evt)
|
73
|
+
%w( num reel track src_start_tc src_end_tc rec_start_tc rec_end_tc).each do | k |
|
74
|
+
evt.send("#{k}=", send(k)) if evt.respond_to?(k)
|
75
|
+
end
|
76
|
+
evt
|
77
|
+
end
|
78
|
+
|
79
|
+
# Returns true if the clip starts with a transiton (not a jump cut)
|
80
|
+
def has_transition?
|
81
|
+
!transition.nil?
|
82
|
+
end
|
83
|
+
|
84
|
+
# Returns true if the clip ends with a transition (if the next clip starts with a transition)
|
85
|
+
def ends_with_transition?
|
86
|
+
outgoing_transition_duration > 0
|
87
|
+
end
|
88
|
+
|
89
|
+
# Returns true if the clip has a timewarp (speed ramp, motion memory, you name it)
|
90
|
+
def has_timewarp?
|
91
|
+
!timewarp.nil?
|
92
|
+
end
|
93
|
+
|
94
|
+
# Is this a black slug?
|
95
|
+
def black?
|
96
|
+
reel == 'BL'
|
97
|
+
end
|
98
|
+
alias_method :slug?, :black?
|
99
|
+
|
100
|
+
# Get the record length of the event (how long it occupies in the EDL without an eventual outgoing transition)
|
101
|
+
def rec_length
|
102
|
+
(rec_end_tc.to_i - rec_start_tc.to_i).to_i
|
103
|
+
end
|
104
|
+
|
105
|
+
# Get the record length of the event (how long it occupies in the EDL without an eventual outgoing transition)
|
106
|
+
def rec_length_with_transition
|
107
|
+
rec_length + outgoing_transition_duration.to_i
|
108
|
+
end
|
109
|
+
|
110
|
+
# How long does the capture need to be to complete this event including timewarps and transitions
|
111
|
+
def src_length
|
112
|
+
@timewarp ? @timewarp.actual_length_of_source : rec_length_with_transition
|
113
|
+
end
|
114
|
+
|
115
|
+
alias_method :capture_length, :src_length
|
116
|
+
|
117
|
+
# Capture from (and including!) this timecode to complete this event including timewarps and transitions
|
118
|
+
def capture_from_tc
|
119
|
+
timewarp ? timewarp.source_used_from : src_start_tc
|
120
|
+
end
|
121
|
+
|
122
|
+
# Capture up to (but not including!) this timecode to complete this event including timewarps and transitions
|
123
|
+
def capture_to_tc
|
124
|
+
timewarp ? timewarp.source_used_upto : (src_start_tc + rec_length_with_transition)
|
125
|
+
end
|
126
|
+
|
127
|
+
# Returns true if this event is a generator
|
128
|
+
def generator?
|
129
|
+
black? || (%(AX GEN).include?(reel))
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|