flvedit 0.6.1 → 0.6.2
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/VERSION.yml +1 -1
- data/lib/flv/edit/processor/add.rb +4 -2
- data/lib/flv/edit/processor/base.rb +47 -169
- data/lib/flv/edit/processor/command_line.rb +14 -7
- data/lib/flv/edit/processor/cut.rb +10 -4
- data/lib/flv/edit/processor/debug.rb +8 -6
- data/lib/flv/edit/processor/dispatcher.rb +175 -0
- data/lib/flv/edit/processor/filter.rb +32 -0
- data/lib/flv/edit/processor/head.rb +9 -5
- data/lib/flv/edit/processor/join.rb +13 -3
- data/lib/flv/edit/processor/meta_data_maker.rb +5 -1
- data/lib/flv/edit/processor/print.rb +11 -3
- data/lib/flv/edit/processor/printer.rb +1 -0
- data/lib/flv/edit/processor/reader.rb +19 -14
- data/lib/flv/edit/processor/save.rb +15 -14
- data/lib/flv/edit/processor/update.rb +12 -4
- data/lib/flv/edit/processor.rb +1 -1
- data/lib/flv/edit/version.rb +1 -1
- data/lib/flv/file.rb +3 -0
- data/lib/flv/header.rb +9 -1
- data/lib/flv/packing.rb +0 -1
- data/lib/flv.rb +6 -6
- metadata +4 -2
data/VERSION.yml
CHANGED
|
@@ -6,11 +6,13 @@ module FLV
|
|
|
6
6
|
module Edit
|
|
7
7
|
module Processor
|
|
8
8
|
|
|
9
|
+
# Add is a Processor (see Base and desc)
|
|
9
10
|
class Add < Base
|
|
10
11
|
desc "Adds tags from the xml or yaml file PATH (default: tags.yaml/xml)", :param => {:name => "[PATH]"}
|
|
12
|
+
|
|
13
|
+
include Dispatcher
|
|
11
14
|
|
|
12
|
-
def
|
|
13
|
-
super
|
|
15
|
+
def on_header(tag)
|
|
14
16
|
@add = (options[:add_tags] || read_tag_file).sort_by(&:timestamp)
|
|
15
17
|
end
|
|
16
18
|
|
|
@@ -1,140 +1,62 @@
|
|
|
1
1
|
module FLV
|
|
2
2
|
module Edit
|
|
3
3
|
module Processor
|
|
4
|
-
#
|
|
5
|
-
#
|
|
6
|
-
#
|
|
4
|
+
# Base is the base class for all Processors.
|
|
5
|
+
# Processors are used to process FLV files.
|
|
6
|
+
# A FLV file can be seen as an enumeration of chunks, the first one being a Header and the following ones
|
|
7
|
+
# a series of Tags with different types of bodies: Audio, Video or Event.
|
|
8
|
+
# A Processor acts as an IO operation on such enumerations.
|
|
9
|
+
# They therefore take an enumeration of chunks as input and their output is similarly an enumeration of chunks.
|
|
10
|
+
# They can thus be chained together at will.
|
|
7
11
|
#
|
|
8
|
-
#
|
|
9
|
-
#
|
|
10
|
-
#
|
|
12
|
+
# For example:
|
|
13
|
+
# FLV::File.open("x.flv") do |f|
|
|
14
|
+
# Debug.new(Cut.new(f, :cut => "1m-")).first(10)
|
|
15
|
+
# end
|
|
16
|
+
# # ==> reads the file, skips the first minute, prints and returns the first 10 chunks
|
|
11
17
|
#
|
|
12
|
-
#
|
|
13
|
-
#
|
|
14
|
-
#
|
|
15
|
-
# on_tag
|
|
16
|
-
# on_audio
|
|
17
|
-
# on_video
|
|
18
|
-
# on_keyframe
|
|
19
|
-
# on_interframe
|
|
20
|
-
# on_disposable_interframe
|
|
21
|
-
# on_event
|
|
22
|
-
# on_meta_data
|
|
23
|
-
# on_cue_point
|
|
24
|
-
# on_other_event
|
|
25
|
-
#
|
|
26
|
-
# All of these methods will have one argument: the current chunk being processed.
|
|
27
|
-
# For example, if the current chunk is an 'onMetaData' event, then
|
|
28
|
-
# the following will be called (from the most specialized to the least).
|
|
29
|
-
# processor.on_meta_data(chunk)
|
|
30
|
-
# processor.on_event(chunk)
|
|
31
|
-
# processor.on_chunk(chunk)
|
|
32
|
-
# # later on, the next processor will handle it:
|
|
33
|
-
# next_processor.on_meta_data(chunk)
|
|
34
|
-
# #...
|
|
35
|
-
#
|
|
36
|
-
# The methods need not return anything. It is assumed that the chunk will continue to flow through the
|
|
37
|
-
# processing chain. When the chunk should not continue down the chain, call +absorb+.
|
|
38
|
-
# To insert other tags in the flow, call +dispatch_instead+.
|
|
39
|
-
# Finally, it's possible to +stop+ the processing of the file completely.
|
|
40
|
-
#
|
|
41
|
-
# It is possible to look back at already processed chunks (up to a certain limit) with +look_back+
|
|
42
|
-
# or even in the future with +look_ahead+
|
|
18
|
+
# Processors acts as Enumerable, but #each, in that context, is an enumeration of chunks
|
|
19
|
+
# for one single file. Some processors act as more than one such enumeration (Reader, Split)
|
|
20
|
+
# To go through all, call #each_source or #process_all
|
|
43
21
|
#
|
|
44
22
|
class Base
|
|
23
|
+
include Enumerable
|
|
45
24
|
attr_reader :options
|
|
46
25
|
|
|
26
|
+
# Create a new processor, using the given +source+ and +options+ hash.
|
|
27
|
+
# Valid +options+ depend on the class of Processor.
|
|
47
28
|
def initialize(source=nil, options={})
|
|
48
|
-
@options = options.
|
|
29
|
+
@options = options.reverse_merge(:out => STDOUT)
|
|
49
30
|
@source = source
|
|
50
|
-
on_calls = self.class.instance_methods(false).select{|m| m.to_s.start_with?("on_")}.map(&:to_sym) #Note: to_s needed for ruby 1.9, to_sym for ruby 1.8
|
|
51
|
-
unless (potential_errors = on_calls - ALL_EVENTS).empty?
|
|
52
|
-
warn "The following are not events: #{potential_errors.join(',')} (class #{self.class})"
|
|
53
|
-
end
|
|
54
31
|
end
|
|
55
32
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
def rewind
|
|
61
|
-
@source.rewind
|
|
62
|
-
end
|
|
63
|
-
|
|
64
|
-
def absorb(*) # Note: the (*) is so that we can alias events like on_meta_data
|
|
65
|
-
throw :absorb
|
|
66
|
-
end
|
|
67
|
-
|
|
68
|
-
def stop
|
|
69
|
-
throw :stop
|
|
70
|
-
end
|
|
71
|
-
|
|
72
|
-
def process_all
|
|
73
|
-
each{} while has_next_file?
|
|
33
|
+
# Calls the given block once for each source
|
|
34
|
+
def each_source(&block)
|
|
35
|
+
raise "There is no source for #{self}" unless source
|
|
36
|
+
source.each_source(&block)
|
|
74
37
|
end
|
|
75
38
|
|
|
39
|
+
# Calls the given block once for each chunck, passing that chunk as argument
|
|
76
40
|
def each(&block)
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
catch :stop do
|
|
80
|
-
process_next_file
|
|
81
|
-
end
|
|
82
|
-
end
|
|
83
|
-
|
|
84
|
-
def process_next_file
|
|
85
|
-
dispatch_chunks(@source)
|
|
86
|
-
end
|
|
87
|
-
|
|
88
|
-
def dispatch_chunks(enum)
|
|
89
|
-
enum.each do |chunk|
|
|
90
|
-
dispatch_chunk(chunk)
|
|
91
|
-
end
|
|
92
|
-
end
|
|
93
|
-
|
|
94
|
-
def dispatch_chunk(chunk)
|
|
95
|
-
evt = chunk.main_event
|
|
96
|
-
catch :absorb do
|
|
97
|
-
EVENT_TRIGGER_LIST[evt].each do |event|
|
|
98
|
-
send(event, chunk) if respond_to?(event)
|
|
99
|
-
end
|
|
100
|
-
@block.call chunk
|
|
101
|
-
end
|
|
41
|
+
raise "There is no source for #{self}" unless source
|
|
42
|
+
source.each(&block)
|
|
102
43
|
end
|
|
103
44
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
end
|
|
108
|
-
absorb
|
|
109
|
-
end
|
|
110
|
-
|
|
111
|
-
def stdout
|
|
112
|
-
options[:out] || STDOUT
|
|
45
|
+
# Simple utility going through each chunk of each source
|
|
46
|
+
def process_all
|
|
47
|
+
each_source{each{}}
|
|
113
48
|
end
|
|
114
49
|
|
|
115
|
-
EVENT_TRIGGER = {
|
|
116
|
-
:on_header => :on_chunk,
|
|
117
|
-
:on_tag => :on_chunk,
|
|
118
|
-
:on_audio => :on_tag,
|
|
119
|
-
:on_video => :on_tag,
|
|
120
|
-
:on_event => :on_tag,
|
|
121
|
-
:on_meta_data => :on_event,
|
|
122
|
-
:on_cue_point => :on_event,
|
|
123
|
-
:on_last_second => :on_event,
|
|
124
|
-
:on_other_event => :on_event,
|
|
125
|
-
:on_keyframe => :on_video,
|
|
126
|
-
:on_interframe => :on_video,
|
|
127
|
-
:on_disposable_interframe => :on_video
|
|
128
|
-
}.freeze
|
|
129
|
-
|
|
130
|
-
ALL_EVENTS = (EVENT_TRIGGER.keys | EVENT_TRIGGER.values).freeze
|
|
131
|
-
|
|
132
|
-
MAIN_EVENTS = ALL_EVENTS.reject{ |k| EVENT_TRIGGER.has_value?(k)}.freeze
|
|
133
|
-
|
|
134
|
-
EVENT_TRIGGER_LIST = Hash.new{|h, k| h[k] = [k] + h[EVENT_TRIGGER[k]]}.tap{|h| h[nil] = []; h.values_at(*MAIN_EVENTS)}.freeze
|
|
135
|
-
|
|
136
50
|
protected
|
|
137
|
-
|
|
51
|
+
attr_reader :source
|
|
52
|
+
|
|
53
|
+
# Processors can be made directly accessible to the command line by calling #desc
|
|
54
|
+
# with a description.
|
|
55
|
+
# Options are:
|
|
56
|
+
# * :shortcut => "x" (defaults to first letter of processor)
|
|
57
|
+
# * :param => pass a hash if the command line should accept a parameter:
|
|
58
|
+
# * :class => class of the parameter (defaults to String)
|
|
59
|
+
# * :name => name of parameter, surrounded with [] if optional (defaults to the name of the class)
|
|
138
60
|
def self.desc(text, options = {})
|
|
139
61
|
registry << [self, text, options]
|
|
140
62
|
end
|
|
@@ -143,67 +65,23 @@ module FLV
|
|
|
143
65
|
@@registry ||= []
|
|
144
66
|
end
|
|
145
67
|
|
|
146
|
-
def self.absorb(*events)
|
|
147
|
-
events.each{|evt| alias_method evt, :absorb}
|
|
148
|
-
end
|
|
149
|
-
|
|
150
68
|
end #class Base
|
|
151
69
|
|
|
70
|
+
# Utility function to create a chain of Processors.
|
|
71
|
+
# Example:
|
|
72
|
+
# chain(Update, Debug, Cut, options)
|
|
73
|
+
# # ==> Update.new(Debug.new(Cut.new(options),options),options)
|
|
152
74
|
def self.chain(chain_classes, options = {})
|
|
153
75
|
next_chain_class = chain_classes.pop
|
|
154
76
|
next_chain_class.new(chain(chain_classes, options), options) if next_chain_class
|
|
155
77
|
end
|
|
156
|
-
|
|
157
|
-
# Let's extend the different kinds of chunks so that any_chunk.main_event returns
|
|
158
|
-
# the desired trigger.
|
|
159
|
-
|
|
160
|
-
module MainEvent # :nodoc:
|
|
161
|
-
module Header
|
|
162
|
-
def main_event
|
|
163
|
-
:on_header
|
|
164
|
-
end
|
|
165
|
-
end
|
|
166
|
-
|
|
167
|
-
module Video
|
|
168
|
-
MAPPING = {
|
|
169
|
-
:keyframe => :on_keyframe,
|
|
170
|
-
:interframe => :on_interframe,
|
|
171
|
-
:disposable_interframe => :on_disposable_interframe
|
|
172
|
-
}.freeze
|
|
173
|
-
|
|
174
|
-
def main_event
|
|
175
|
-
MAPPING[frame_type]
|
|
176
|
-
end
|
|
177
|
-
end
|
|
178
|
-
|
|
179
|
-
module Audio
|
|
180
|
-
def main_event
|
|
181
|
-
:on_audio
|
|
182
|
-
end
|
|
183
|
-
end
|
|
184
|
-
|
|
185
|
-
module Event
|
|
186
|
-
MAPPING = Hash.new(:on_other_event).merge!(
|
|
187
|
-
:onMetaData => :on_meta_data,
|
|
188
|
-
:onCuePoint => :on_cue_point,
|
|
189
|
-
:onLastSecond => :on_last_second
|
|
190
|
-
).freeze
|
|
191
|
-
def main_event
|
|
192
|
-
MAPPING[event]
|
|
193
|
-
end
|
|
194
|
-
end
|
|
195
|
-
|
|
196
|
-
module Tag
|
|
197
|
-
def main_event
|
|
198
|
-
body.main_event
|
|
199
|
-
end
|
|
200
|
-
end
|
|
201
|
-
end #module MainEvent
|
|
202
78
|
end #module Processor
|
|
203
79
|
end #module Edit
|
|
204
80
|
|
|
205
|
-
|
|
206
|
-
|
|
81
|
+
module File
|
|
82
|
+
# A file acts as a single source, so...
|
|
83
|
+
def each_source
|
|
84
|
+
yield
|
|
85
|
+
end
|
|
207
86
|
end
|
|
208
|
-
|
|
209
87
|
end #module FLV
|
|
@@ -1,19 +1,26 @@
|
|
|
1
1
|
module FLV
|
|
2
2
|
module Edit
|
|
3
3
|
module Processor
|
|
4
|
+
|
|
5
|
+
# CommandLine is a Processor (see Base) added automatically as the last level
|
|
6
|
+
# for all command line executions
|
|
4
7
|
class CommandLine < Base
|
|
8
|
+
include Dispatcher
|
|
5
9
|
def on_header(h)
|
|
6
10
|
@last = h.path
|
|
7
11
|
end
|
|
8
12
|
|
|
9
|
-
def
|
|
13
|
+
def each_source
|
|
14
|
+
return to_enum(:each_source) unless block_given?
|
|
10
15
|
ok, errors = [], []
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
16
|
+
super do
|
|
17
|
+
begin
|
|
18
|
+
each{}
|
|
19
|
+
ok << @last
|
|
20
|
+
rescue Exception => e
|
|
21
|
+
errors << [@last, e]
|
|
22
|
+
end
|
|
23
|
+
end
|
|
17
24
|
puts (["Processed successfully:"] + ok).join("\n") unless ok.empty?
|
|
18
25
|
puts (["**** Processed with errors: ****"] + errors.map{|path, err| "#{path}: #{err}"}).join("\n") unless errors.empty?
|
|
19
26
|
end
|
|
@@ -1,21 +1,27 @@
|
|
|
1
1
|
module FLV
|
|
2
2
|
module Edit
|
|
3
3
|
module Processor
|
|
4
|
+
|
|
5
|
+
# Cut is a Processor class (see Base and desc)
|
|
4
6
|
class Cut < Base
|
|
5
|
-
desc ["Cuts
|
|
7
|
+
desc ["Cuts selects the tags within RANGE.",
|
|
8
|
+
"The timestamps are offset so that the first tag as timestamp 0"],
|
|
6
9
|
:param => {:class => TimestampRange, :name => "RANGE"}, :shortcut => "x"
|
|
7
10
|
|
|
8
|
-
|
|
11
|
+
include Filter
|
|
12
|
+
|
|
13
|
+
def before_filter
|
|
9
14
|
@from, @to = options[:cut].begin, options[:cut].end
|
|
10
15
|
@wait_for_keyframe = options[:keyframe_mode]
|
|
11
16
|
@first_timestamp = nil
|
|
12
17
|
end
|
|
13
18
|
|
|
14
|
-
def
|
|
19
|
+
def filter(tag)
|
|
20
|
+
return if tag.is_a? Header
|
|
15
21
|
if tag.timestamp > @to
|
|
16
22
|
stop
|
|
17
23
|
elsif (tag.timestamp < @from) || (@wait_for_keyframe &&= !tag.body.is?(:keyframe))
|
|
18
|
-
|
|
24
|
+
:skip
|
|
19
25
|
else
|
|
20
26
|
@first_timestamp ||= tag.timestamp
|
|
21
27
|
tag.timestamp -= @first_timestamp
|
|
@@ -3,27 +3,29 @@ require_relative "printer"
|
|
|
3
3
|
module FLV
|
|
4
4
|
module Edit
|
|
5
5
|
module Processor
|
|
6
|
+
|
|
7
|
+
# Debug is a Processor class (see Base and desc)
|
|
6
8
|
class Debug < Base
|
|
9
|
+
include Filter
|
|
7
10
|
desc ["Prints out the details of all tags. Information that stays the same",
|
|
8
11
|
"from one tag type to the next will not be repeated.",
|
|
9
12
|
"A RANGE argument will limit the output to tags within that range;",
|
|
10
13
|
"similarily, a given TIMESTAMP will limit the output to tags",
|
|
11
14
|
"within 0.1s of this timestamp."],
|
|
12
15
|
:param => {:class => TimestampOrTimestampRange, :name => "[RANGE/TS]"}
|
|
13
|
-
|
|
16
|
+
|
|
17
|
+
def before_filter
|
|
14
18
|
@range = self.options[:debug] || TimestampRange.new(0, INFINITY)
|
|
15
19
|
@range = @range.widen(0.1) unless @range.is_a? Range
|
|
16
20
|
@last = {}
|
|
17
|
-
@printer = Printer.new(
|
|
18
|
-
tag.debug(@printer) if @range.include? 0
|
|
21
|
+
@printer = Printer.new(options[:out])
|
|
19
22
|
end
|
|
20
|
-
|
|
21
|
-
def
|
|
23
|
+
|
|
24
|
+
def filter(tag)
|
|
22
25
|
return unless @range.include? tag.timestamp
|
|
23
26
|
tag.debug(@printer, @last[tag.body.class])
|
|
24
27
|
@last[tag.body.class] = tag
|
|
25
28
|
end
|
|
26
|
-
|
|
27
29
|
end
|
|
28
30
|
end
|
|
29
31
|
end
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
module FLV
|
|
2
|
+
module Edit
|
|
3
|
+
module Processor
|
|
4
|
+
|
|
5
|
+
# Dispatcher can be included in a processor and will makeit easy to
|
|
6
|
+
# process the chunks according to their type.
|
|
7
|
+
#
|
|
8
|
+
# Chunks can be Header or Tags.
|
|
9
|
+
# The latter have different types of bodies: Audio, Video or Event.
|
|
10
|
+
# Events store all meta data related information: onMetaData, onCuePoint, ...
|
|
11
|
+
#
|
|
12
|
+
# To tap into the flow of chunks, a processor can define any of the following methods:
|
|
13
|
+
# on_chunk
|
|
14
|
+
# on_header
|
|
15
|
+
# on_tag
|
|
16
|
+
# on_audio
|
|
17
|
+
# on_video
|
|
18
|
+
# on_keyframe
|
|
19
|
+
# on_interframe
|
|
20
|
+
# on_disposable_interframe
|
|
21
|
+
# on_event
|
|
22
|
+
# on_meta_data
|
|
23
|
+
# on_cue_point
|
|
24
|
+
# on_other_event
|
|
25
|
+
#
|
|
26
|
+
# All of these methods will have one argument: the current chunk being processed.
|
|
27
|
+
# For example, if the current chunk is an 'onMetaData' event, then
|
|
28
|
+
# the following will be called (from the most specialized to the least).
|
|
29
|
+
# on_meta_data(chunk)
|
|
30
|
+
# on_event(chunk)
|
|
31
|
+
# on_chunk(chunk)
|
|
32
|
+
#
|
|
33
|
+
# The methods need not return anything. It is assumed that the chunk should be output.
|
|
34
|
+
# If that's not the case, call +#absorb+.
|
|
35
|
+
# To output other tags instead, call +#dispatch_instead+.
|
|
36
|
+
#
|
|
37
|
+
# Note that both #absorb and #dispatch_instead stop the processing for the current chunk,
|
|
38
|
+
# so if #on_video calls #absorb, for example, then there won't be a call to #on_tag or #on_chunk.
|
|
39
|
+
#
|
|
40
|
+
# Finally, it's possible to +#stop+ the processing of the current source completely.
|
|
41
|
+
#
|
|
42
|
+
module Dispatcher
|
|
43
|
+
def initialize(*)
|
|
44
|
+
super
|
|
45
|
+
on_calls = self.class.instance_methods(false).select{|m| m.to_s.start_with?("on_")}.map(&:to_sym) #Note: to_s needed for ruby 1.9, to_sym for ruby 1.8
|
|
46
|
+
unless (potential_errors = on_calls - ALL_EVENTS).empty?
|
|
47
|
+
warn "The following are not events: #{potential_errors.join(',')} (class #{self.class})"
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Stops the processing for the current chunk
|
|
52
|
+
def absorb(*) # Note: the (*) is so that we can alias events like on_meta_data
|
|
53
|
+
throw :absorb
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Stops the processing of the current source completely.
|
|
57
|
+
def stop
|
|
58
|
+
throw :stop
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def each(&block)
|
|
62
|
+
return to_enum unless block_given?
|
|
63
|
+
@block = block
|
|
64
|
+
catch :stop do
|
|
65
|
+
super{|chunk| dispatch_chunk(chunk)}
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Call #dispatch_instead with a list of chunks.
|
|
70
|
+
# These chunks will not be processed by the current processor
|
|
71
|
+
# but will be output directly. This stops the processing for
|
|
72
|
+
# the current chunk.
|
|
73
|
+
def dispatch_instead(*chunks)
|
|
74
|
+
chunks.each do |chunk|
|
|
75
|
+
@block.call chunk
|
|
76
|
+
end
|
|
77
|
+
absorb
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
EVENT_TRIGGER = {
|
|
81
|
+
:on_header => :on_chunk,
|
|
82
|
+
:on_tag => :on_chunk,
|
|
83
|
+
:on_audio => :on_tag,
|
|
84
|
+
:on_video => :on_tag,
|
|
85
|
+
:on_event => :on_tag,
|
|
86
|
+
:on_meta_data => :on_event,
|
|
87
|
+
:on_cue_point => :on_event,
|
|
88
|
+
:on_last_second => :on_event,
|
|
89
|
+
:on_other_event => :on_event,
|
|
90
|
+
:on_keyframe => :on_video,
|
|
91
|
+
:on_interframe => :on_video,
|
|
92
|
+
:on_disposable_interframe => :on_video
|
|
93
|
+
}.freeze
|
|
94
|
+
|
|
95
|
+
ALL_EVENTS = (EVENT_TRIGGER.keys | EVENT_TRIGGER.values).freeze
|
|
96
|
+
|
|
97
|
+
MAIN_EVENTS = ALL_EVENTS.reject{ |k| EVENT_TRIGGER.has_value?(k)}.freeze
|
|
98
|
+
|
|
99
|
+
EVENT_TRIGGER_LIST = Hash.new{|h, k| h[k] = [k] + h[EVENT_TRIGGER[k]]}.tap{|h| h[nil] = []; h.values_at(*MAIN_EVENTS)}.freeze
|
|
100
|
+
|
|
101
|
+
module ClassMethods
|
|
102
|
+
# Call give a list of events of #absorb to always absorb chunks of these types.
|
|
103
|
+
def absorb(*events)
|
|
104
|
+
events.each{|evt| alias_method evt, :absorb}
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def self.included(base) # :nodoc:
|
|
109
|
+
base.extend ClassMethods
|
|
110
|
+
end
|
|
111
|
+
private
|
|
112
|
+
def dispatch_chunk(chunk) # :nodoc:
|
|
113
|
+
evt = chunk.main_event
|
|
114
|
+
catch :absorb do
|
|
115
|
+
EVENT_TRIGGER_LIST[evt].each do |event|
|
|
116
|
+
send(event, chunk) if respond_to?(event)
|
|
117
|
+
end
|
|
118
|
+
@block.call chunk
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
end #class Base
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
# We supplement the basic FLV classes with a #main_event method
|
|
125
|
+
# which returns the most specialize event for that chunk.
|
|
126
|
+
module MainEvent # :nodoc:
|
|
127
|
+
module Header
|
|
128
|
+
def main_event
|
|
129
|
+
:on_header
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
module Video
|
|
134
|
+
MAPPING = {
|
|
135
|
+
:keyframe => :on_keyframe,
|
|
136
|
+
:interframe => :on_interframe,
|
|
137
|
+
:disposable_interframe => :on_disposable_interframe
|
|
138
|
+
}.freeze
|
|
139
|
+
|
|
140
|
+
def main_event
|
|
141
|
+
MAPPING[frame_type]
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
module Audio
|
|
146
|
+
def main_event
|
|
147
|
+
:on_audio
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
module Event
|
|
152
|
+
MAPPING = Hash.new(:on_other_event).merge!(
|
|
153
|
+
:onMetaData => :on_meta_data,
|
|
154
|
+
:onCuePoint => :on_cue_point,
|
|
155
|
+
:onLastSecond => :on_last_second
|
|
156
|
+
).freeze
|
|
157
|
+
def main_event
|
|
158
|
+
MAPPING[event]
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
module Tag
|
|
163
|
+
def main_event
|
|
164
|
+
body.main_event
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
end #module MainEvent
|
|
168
|
+
end #module Processor
|
|
169
|
+
end #module Edit
|
|
170
|
+
|
|
171
|
+
[Header, Tag, Audio, Video, Event].each do |klass|
|
|
172
|
+
klass.class_eval{ include Edit::Processor::MainEvent.const_get(klass.to_s.sub('FLV::', '')) }
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
end #module FLV
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
module FLV
|
|
2
|
+
module Edit
|
|
3
|
+
module Processor
|
|
4
|
+
|
|
5
|
+
# Basic processors can include Filter which
|
|
6
|
+
# provides a pretty simple #each.
|
|
7
|
+
# It will call #before_filter then
|
|
8
|
+
# #filter(chunk) for each chunk.
|
|
9
|
+
# If #filter returns +:skip+, the chunk won't be yielded
|
|
10
|
+
# #filter can also stops altogether the processing
|
|
11
|
+
# of the current source by calling #stop.
|
|
12
|
+
module Filter
|
|
13
|
+
def each
|
|
14
|
+
return to_enum unless block_given?
|
|
15
|
+
before_filter
|
|
16
|
+
catch :stop do
|
|
17
|
+
super do |chunk|
|
|
18
|
+
yield chunk unless filter(chunk) == :skip
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
protected
|
|
23
|
+
def before_filter
|
|
24
|
+
end
|
|
25
|
+
def stop
|
|
26
|
+
throw :stop
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
end #module Filter
|
|
30
|
+
end #module Processor
|
|
31
|
+
end #module Edit
|
|
32
|
+
end #module FLV
|
|
@@ -1,15 +1,19 @@
|
|
|
1
1
|
module FLV
|
|
2
2
|
module Edit
|
|
3
3
|
module Processor
|
|
4
|
+
|
|
5
|
+
# Head is a Processor class (see Base and desc)
|
|
4
6
|
class Head < Base
|
|
5
7
|
desc "Processes only the first NB tags.", :param => {:class => Integer, :name => "NB"}, :shortcut => "n"
|
|
6
|
-
def on_header(header)
|
|
7
|
-
@count = self.options[:head]
|
|
8
|
-
end
|
|
9
8
|
|
|
10
|
-
def
|
|
11
|
-
|
|
9
|
+
def each
|
|
10
|
+
count = options[:head]
|
|
11
|
+
super do |chunk|
|
|
12
|
+
yield chunk
|
|
13
|
+
break if (count -= 1) < 0 # after the yield because we're not counting the header
|
|
14
|
+
end
|
|
12
15
|
end
|
|
16
|
+
|
|
13
17
|
end
|
|
14
18
|
end
|
|
15
19
|
end
|
|
@@ -2,11 +2,21 @@ module FLV
|
|
|
2
2
|
module Edit
|
|
3
3
|
module Processor
|
|
4
4
|
|
|
5
|
+
# Join is a Processor class (see Base and desc)
|
|
5
6
|
class Join < Base
|
|
6
|
-
desc "
|
|
7
|
+
desc "Joins all the inputs together."
|
|
8
|
+
|
|
9
|
+
include Dispatcher
|
|
7
10
|
|
|
8
|
-
def
|
|
9
|
-
|
|
11
|
+
def each_source_with_join
|
|
12
|
+
return to_enum(:each_source) unless block_given?
|
|
13
|
+
yield
|
|
14
|
+
end
|
|
15
|
+
alias_method_chain :each_source, :join
|
|
16
|
+
|
|
17
|
+
def each(&block)
|
|
18
|
+
return to_enum unless block_given?
|
|
19
|
+
each_source_without_join { super }
|
|
10
20
|
end
|
|
11
21
|
|
|
12
22
|
def on_tag(tag)
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
module FLV
|
|
2
2
|
module Edit
|
|
3
3
|
module Processor
|
|
4
|
-
|
|
4
|
+
# MetaDataMaker is a Processor class (see Base) that computes the metadata
|
|
5
|
+
# for its sources. The metadata for the current source is accessible with #meta_data
|
|
6
|
+
# It is used by Update.
|
|
7
|
+
class MetaDataMaker < Base
|
|
8
|
+
include Dispatcher
|
|
5
9
|
CHUNK_LENGTH_SIZE = 4 # todo: calc instead?
|
|
6
10
|
TAG_HEADER_SIZE = 11 # todo: calc instead?
|
|
7
11
|
TOTAL_EXTRA_SIZE_PER_TAG = CHUNK_LENGTH_SIZE + TAG_HEADER_SIZE
|
|
@@ -2,10 +2,18 @@ require_relative "printer"
|
|
|
2
2
|
module FLV
|
|
3
3
|
module Edit
|
|
4
4
|
module Processor
|
|
5
|
+
|
|
6
|
+
# Print is a Processor class (see Base and desc)
|
|
5
7
|
class Print < Base
|
|
6
|
-
desc "Prints out meta data
|
|
7
|
-
|
|
8
|
-
|
|
8
|
+
desc "Prints out the meta data"
|
|
9
|
+
include Filter
|
|
10
|
+
|
|
11
|
+
def before_filter
|
|
12
|
+
@printer = Printer.new(options[:out])
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def filter(tag)
|
|
16
|
+
tag.debug(@printer) if tag.is? :onMetaData
|
|
9
17
|
end
|
|
10
18
|
end
|
|
11
19
|
end
|
|
@@ -1,29 +1,34 @@
|
|
|
1
1
|
module FLV
|
|
2
2
|
module Edit
|
|
3
3
|
module Processor
|
|
4
|
+
|
|
5
|
+
# Reader is a Processor class (see Base) which use <tt>options[:files]</tt> to generate
|
|
6
|
+
# its sources (instead of the passed +source+ which should be nil)
|
|
4
7
|
class Reader < Base
|
|
5
8
|
def initialize(*)
|
|
6
9
|
super
|
|
10
|
+
raise "Invalid filenames: #{options[:files].inspect}" unless options[:files].all?
|
|
11
|
+
raise "Please specify at least one filename" if options[:files].empty?
|
|
12
|
+
raise NotImplemented, "Reader can't have a source (other than options[:files])" if source
|
|
7
13
|
rewind
|
|
8
|
-
raise "Oups, Filenames were #{@options[:files].inspect}" if @options[:files].include? nil
|
|
9
|
-
raise "Please specify at least one filename" if @options[:files].empty?
|
|
10
14
|
end
|
|
11
15
|
|
|
12
|
-
def
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
def rewind
|
|
17
|
-
@to_process = @options[:files].length
|
|
16
|
+
def each_source
|
|
17
|
+
return to_enum(:each_source) unless block_given?
|
|
18
|
+
rewind
|
|
19
|
+
yield until @sources.empty?
|
|
18
20
|
end
|
|
19
|
-
|
|
20
|
-
def
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
dispatch_chunks(f)
|
|
21
|
+
|
|
22
|
+
def each
|
|
23
|
+
FLV::File.open(@sources.shift) do |f|
|
|
24
|
+
@source = f
|
|
25
|
+
super
|
|
25
26
|
end
|
|
26
27
|
end
|
|
28
|
+
|
|
29
|
+
def rewind
|
|
30
|
+
@sources = options[:files].dup
|
|
31
|
+
end
|
|
27
32
|
end
|
|
28
33
|
end
|
|
29
34
|
end
|
|
@@ -1,28 +1,29 @@
|
|
|
1
1
|
module FLV
|
|
2
2
|
module Edit
|
|
3
3
|
module Processor
|
|
4
|
+
|
|
5
|
+
# Save is a Processor class (see Base and desc)
|
|
4
6
|
class Save < Base
|
|
5
7
|
desc "Saves the result to PATH", :param => {:class => String, :name => "PATH"}
|
|
6
|
-
|
|
8
|
+
|
|
9
|
+
def each_source
|
|
10
|
+
return to_enum(:each_source) unless block_given?
|
|
11
|
+
@out = FLV::File::open(options[:save] || (h.path+".temp"), "w+b")
|
|
7
12
|
super
|
|
8
13
|
ensure
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
FileUtils.mv(@out.path, finalpath) unless finalpath == @out.path
|
|
13
|
-
end
|
|
14
|
-
end
|
|
15
|
-
|
|
16
|
-
def on_header(h)
|
|
17
|
-
@out = FLV::File::open(options[:save] || (h.path+".temp"), "w+b")
|
|
18
|
-
@out << h
|
|
14
|
+
@out.close
|
|
15
|
+
finalpath = @out.path.sub(/\.temp$/, '')
|
|
16
|
+
FileUtils.mv(@out.path, finalpath) unless finalpath == @out.path
|
|
19
17
|
end
|
|
20
18
|
|
|
21
|
-
def
|
|
22
|
-
|
|
19
|
+
def each
|
|
20
|
+
return to_enum unless block_given?
|
|
21
|
+
super do |chunk|
|
|
22
|
+
@out << chunk
|
|
23
|
+
yield chunk
|
|
24
|
+
end
|
|
23
25
|
end
|
|
24
26
|
end
|
|
25
|
-
|
|
26
27
|
end
|
|
27
28
|
end
|
|
28
29
|
end
|
|
@@ -3,17 +3,25 @@ require_relative "meta_data_maker"
|
|
|
3
3
|
module FLV
|
|
4
4
|
module Edit
|
|
5
5
|
module Processor
|
|
6
|
+
|
|
7
|
+
# Update is a Processor class (see Base and desc)
|
|
6
8
|
class Update < Base
|
|
7
|
-
|
|
9
|
+
include Dispatcher
|
|
10
|
+
desc "Updates FLV with an onMetaTag event containing all the relevant information."
|
|
8
11
|
def initialize(source=nil, options={})
|
|
9
12
|
super
|
|
10
13
|
@meta_data_maker = MetaDataMaker.new(source.dup, options)
|
|
11
14
|
end
|
|
12
15
|
|
|
16
|
+
|
|
13
17
|
def each(&block)
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
18
|
+
return to_enum unless block_given?
|
|
19
|
+
begin
|
|
20
|
+
@meta_data_maker.each {}
|
|
21
|
+
ensure # even if each throws, we better call super otherwise we won't be synchronized anymore!
|
|
22
|
+
super rescue nil
|
|
23
|
+
raise if $!
|
|
24
|
+
end
|
|
17
25
|
end
|
|
18
26
|
|
|
19
27
|
absorb :on_meta_data, :on_last_second
|
data/lib/flv/edit/processor.rb
CHANGED
data/lib/flv/edit/version.rb
CHANGED
data/lib/flv/file.rb
CHANGED
data/lib/flv/header.rb
CHANGED
|
@@ -31,7 +31,15 @@ module FLV
|
|
|
31
31
|
|
|
32
32
|
def debug(format, *)
|
|
33
33
|
format.header("Header", path)
|
|
34
|
-
format.values(to_h.tap{|h| h.delete(
|
|
34
|
+
format.values(to_h.tap{|h| [:path, :timestamp, :body].each{|key| h.delete(key)}})
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def timestamp
|
|
38
|
+
0
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def body
|
|
42
|
+
self
|
|
35
43
|
end
|
|
36
44
|
end
|
|
37
45
|
end
|
data/lib/flv/packing.rb
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
require 'singleton'
|
|
2
2
|
|
|
3
3
|
module FLV
|
|
4
|
-
class Event < Hash ; end
|
|
5
4
|
# FLV files can contain structured data. This modules makes it easy to (un)pack that data.
|
|
6
5
|
# The packing option +flv_value+ can (un)pack any kind of variable.
|
|
7
6
|
# It corresponds to +SCRIPTDATAVALUE+ in the official FLV file format spec.
|
data/lib/flv.rb
CHANGED
|
@@ -1,12 +1,9 @@
|
|
|
1
1
|
require 'rubygems'
|
|
2
2
|
require 'backports'
|
|
3
|
+
require 'packable'
|
|
3
4
|
|
|
4
|
-
#
|
|
5
|
+
# Base & Utilities
|
|
5
6
|
require_relative 'flv/util/double_check'
|
|
6
|
-
|
|
7
|
-
# packing of FLV objects:
|
|
8
|
-
require 'packable'
|
|
9
|
-
require_relative 'flv/packing'
|
|
10
7
|
require_relative 'flv/base'
|
|
11
8
|
|
|
12
9
|
# FLV body of tags
|
|
@@ -17,8 +14,11 @@ require_relative 'flv/body'
|
|
|
17
14
|
|
|
18
15
|
# FLV chunks (tags & header)
|
|
19
16
|
require_relative 'flv/timestamp'
|
|
20
|
-
require_relative 'flv/tag'
|
|
21
17
|
require_relative 'flv/header'
|
|
18
|
+
require_relative 'flv/tag'
|
|
19
|
+
|
|
20
|
+
# packing of FLV objects:
|
|
21
|
+
require_relative 'flv/packing'
|
|
22
22
|
|
|
23
23
|
# finally:
|
|
24
24
|
require_relative 'flv/file'
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: flvedit
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.6.
|
|
4
|
+
version: 0.6.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- "Marc-Andr\xC3\xA9 Lafortune"
|
|
@@ -9,7 +9,7 @@ autorequire:
|
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
11
|
|
|
12
|
-
date: 2009-04-
|
|
12
|
+
date: 2009-04-22 00:00:00 -04:00
|
|
13
13
|
default_executable: flvedit
|
|
14
14
|
dependencies:
|
|
15
15
|
- !ruby/object:Gem::Dependency
|
|
@@ -60,6 +60,8 @@ files:
|
|
|
60
60
|
- lib/flv/edit/processor/command_line.rb
|
|
61
61
|
- lib/flv/edit/processor/cut.rb
|
|
62
62
|
- lib/flv/edit/processor/debug.rb
|
|
63
|
+
- lib/flv/edit/processor/dispatcher.rb
|
|
64
|
+
- lib/flv/edit/processor/filter.rb
|
|
63
65
|
- lib/flv/edit/processor/head.rb
|
|
64
66
|
- lib/flv/edit/processor/join.rb
|
|
65
67
|
- lib/flv/edit/processor/meta_data_maker.rb
|