marcandre-flvedit 0.6.1 → 0.6.2
Sign up to get free protection for your applications and to get access to all the features.
- data/Rakefile +7 -1
- data/VERSION.yml +1 -1
- data/lib/flv.rb +6 -6
- data/lib/flv/edit/processor.rb +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/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
- metadata +4 -2
data/Rakefile
CHANGED
@@ -56,8 +56,14 @@ Rake::TestTask.new do |t|
|
|
56
56
|
end
|
57
57
|
|
58
58
|
Rake::RDocTask.new do |rdoc|
|
59
|
+
if File.exist?('VERSION.yml')
|
60
|
+
config = YAML.load(File.read('VERSION.yml'))
|
61
|
+
version = "#{config[:major]}.#{config[:minor]}.#{config[:patch]}"
|
62
|
+
else
|
63
|
+
version = ""
|
64
|
+
end
|
59
65
|
rdoc.rdoc_dir = 'rdoc'
|
60
|
-
rdoc.title =
|
66
|
+
rdoc.title = "FLVEdit #{version}"
|
61
67
|
rdoc.options << '--line-numbers' << '--inline-source'
|
62
68
|
rdoc.rdoc_files.include('README*')
|
63
69
|
rdoc.rdoc_files.include('lib/**/*.rb')
|
data/VERSION.yml
CHANGED
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'
|
data/lib/flv/edit/processor.rb
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/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.
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: marcandre-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 -07: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
|