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/VERSION.yml CHANGED
@@ -1,4 +1,4 @@
1
1
  ---
2
2
  :major: 0
3
3
  :minor: 6
4
- :patch: 1
4
+ :patch: 2
@@ -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 initialize(*)
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
- # Processors are used to process FLV files. Processors form are chain and the data (header and tags) will 'flow'
5
- # through the chain in sequence. Each processor can inspect the data and change the flow,
6
- # either by modifying the data, inserting new data in the flow or stopping the propagation of data.
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
- # A FLV file can be seen as a sequence of chunks, the first one being a Header and the following ones
9
- # a series of Tags with different types of bodies: Audio, Video or Event. Events store all meta data
10
- # related information: onMetaData, onCuePoint, ...
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
- # 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
- # 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.freeze
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
- def has_next_file?
57
- @source.has_next_file?
58
- end
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
- return to_enum(:each_chunk) unless block_given?
78
- @block = block
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
- def dispatch_instead(*chunks)
105
- chunks.each do |chunk|
106
- @block.call chunk
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
- [Header, Tag, Audio, Video, Event].each do |klass|
206
- klass.class_eval{ include Edit::Processor::MainEvent.const_get(klass.to_s.sub('FLV::', '')) }
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 process_all
13
+ def each_source
14
+ return to_enum(:each_source) unless block_given?
10
15
  ok, errors = [], []
11
- begin
12
- each{}
13
- ok << @last
14
- rescue Exception => e
15
- errors << [@last, e]
16
- end while has_next_file?
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 file using the given RANGE"],
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
- def on_header(*)
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 on_tag(tag)
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
- absorb
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
- def on_header(tag)
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(stdout)
18
- tag.debug(@printer) if @range.include? 0
21
+ @printer = Printer.new(options[:out])
19
22
  end
20
-
21
- def on_tag(tag)
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 on_tag(tag)
11
- throw :stop if (@count -= 1) < 0
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 "Join the FLV files"
7
+ desc "Joins all the inputs together."
8
+
9
+ include Dispatcher
7
10
 
8
- def process_next_file
9
- dispatch_chunks(@source) while @source.has_next_file?
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
- class MetaDataMaker < Base
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 to stdout"
7
- def on_meta_data(tag)
8
- tag.debug(Printer.new(stdout))
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
@@ -2,6 +2,7 @@ module FLV
2
2
  module Edit
3
3
  module Processor
4
4
 
5
+ # Printer is a small utility class to print out FLV chunks.
5
6
  class Printer
6
7
  def initialize(io, options={})
7
8
  @io = io
@@ -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 has_next_file?
13
- @to_process > 0
14
- end
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 process_next_file
21
- raise IndexError, "No more filenames to process" unless has_next_file?
22
- @to_process -= 1
23
- FLV::File.open(@options[:files][-1- @to_process]) do |f|
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
- def process_next_file
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
- if @out
10
- @out.close
11
- finalpath = @out.path.sub(/\.temp$/, '')
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 on_tag(t)
22
- @out << t
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
- desc "Updates FLV with an onMetaTag event"
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
- @meta_data_maker.each {}
15
- ensure # even if each throws, we better call super otherwise we won't be synchronized anymore!
16
- super
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
@@ -1,3 +1,3 @@
1
- %w(base add command_line cut debug head join print reader save update).each do |proc|
1
+ %w(base filter dispatcher add command_line cut debug head join print reader save update).each do |proc|
2
2
  require_relative "processor/#{proc}"
3
3
  end
@@ -2,7 +2,7 @@ module FLV
2
2
  module Edit
3
3
  FILE = ::File.dirname(__FILE__) + '/../../../VERSION.yml'
4
4
 
5
- class Version < Struct.new(:major, :minor, :patch)
5
+ class Version < Struct.new(:major, :minor, :patch) # :nodoc:
6
6
  def to_s
7
7
  "#{major}.#{minor}.#{patch}"
8
8
  end
data/lib/flv/file.rb CHANGED
@@ -1,4 +1,7 @@
1
1
  module FLV
2
+ # FLV::File provide methods to read a FLV file easily.
3
+ # Interface is similar to ::File, except that #each will
4
+ # return FLV chunks instead of lines.
2
5
  module File
3
6
  def each(*arg, &block)
4
7
  return super unless arg.empty?
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(:path)})
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
- # utilities
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.1
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-21 00:00:00 -04:00
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