flvedit 0.6.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (54) hide show
  1. data/CHANGELOG.rdoc +5 -0
  2. data/LICENSE +24 -0
  3. data/README.rdoc +90 -0
  4. data/Rakefile +137 -0
  5. data/VERSION.yml +4 -0
  6. data/bin/flvedit +14 -0
  7. data/lib/flv/audio.rb +66 -0
  8. data/lib/flv/base.rb +38 -0
  9. data/lib/flv/body.rb +57 -0
  10. data/lib/flv/edit/options.rb +162 -0
  11. data/lib/flv/edit/processor/add.rb +67 -0
  12. data/lib/flv/edit/processor/base.rb +209 -0
  13. data/lib/flv/edit/processor/command_line.rb +23 -0
  14. data/lib/flv/edit/processor/cut.rb +27 -0
  15. data/lib/flv/edit/processor/debug.rb +30 -0
  16. data/lib/flv/edit/processor/head.rb +16 -0
  17. data/lib/flv/edit/processor/join.rb +52 -0
  18. data/lib/flv/edit/processor/meta_data_maker.rb +127 -0
  19. data/lib/flv/edit/processor/print.rb +13 -0
  20. data/lib/flv/edit/processor/printer.rb +27 -0
  21. data/lib/flv/edit/processor/reader.rb +30 -0
  22. data/lib/flv/edit/processor/save.rb +28 -0
  23. data/lib/flv/edit/processor/update.rb +27 -0
  24. data/lib/flv/edit/processor.rb +3 -0
  25. data/lib/flv/edit/runner.rb +23 -0
  26. data/lib/flv/edit/version.rb +15 -0
  27. data/lib/flv/edit.rb +20 -0
  28. data/lib/flv/event.rb +40 -0
  29. data/lib/flv/file.rb +41 -0
  30. data/lib/flv/header.rb +37 -0
  31. data/lib/flv/packing.rb +140 -0
  32. data/lib/flv/tag.rb +62 -0
  33. data/lib/flv/timestamp.rb +124 -0
  34. data/lib/flv/util/double_check.rb +22 -0
  35. data/lib/flv/video.rb +73 -0
  36. data/lib/flv.rb +24 -0
  37. data/test/fixtures/corrupted.flv +0 -0
  38. data/test/fixtures/short.flv +0 -0
  39. data/test/fixtures/tags.xml +39 -0
  40. data/test/test_flv.rb +145 -0
  41. data/test/test_flv_edit.rb +32 -0
  42. data/test/test_flv_edit_results.rb +27 -0
  43. data/test/test_helper.rb +9 -0
  44. data/test/text_flv_edit_results/add_tags.txt +132 -0
  45. data/test/text_flv_edit_results/cut_from.txt +114 -0
  46. data/test/text_flv_edit_results/cut_key.txt +20 -0
  47. data/test/text_flv_edit_results/debug.txt +132 -0
  48. data/test/text_flv_edit_results/debug_limited.txt +18 -0
  49. data/test/text_flv_edit_results/debug_range.txt +32 -0
  50. data/test/text_flv_edit_results/join.txt +237 -0
  51. data/test/text_flv_edit_results/print.txt +16 -0
  52. data/test/text_flv_edit_results/stop.txt +38 -0
  53. data/test/text_flv_edit_results/update.txt +33 -0
  54. metadata +134 -0
data/CHANGELOG.rdoc ADDED
@@ -0,0 +1,5 @@
1
+ = flvedit --- History
2
+
3
+ == Version 0.6 - April 21, 2009
4
+
5
+ === Initial release.
data/LICENSE ADDED
@@ -0,0 +1,24 @@
1
+ FLVEdit - Copyright (c) 2009 Marc-André Lafortune
2
+ All rights reserved.
3
+
4
+ Redistribution and use in source and binary forms, with or without
5
+ modification, are permitted provided that the following conditions
6
+ are met:
7
+ 1. Redistributions of source code must retain the above copyright
8
+ notice, this list of conditions and the following disclaimer.
9
+ 2. Redistributions in binary form must reproduce the above copyright
10
+ notice, this list of conditions and the following disclaimer in the
11
+ documentation and/or other materials provided with the distribution.
12
+ 3. The name of the author may not be used to endorse or promote products
13
+ derived from this software without specific prior written permission.
14
+
15
+ THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
16
+ IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
17
+ OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
18
+ IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
19
+ INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
20
+ NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
21
+ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
22
+ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
24
+ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
data/README.rdoc ADDED
@@ -0,0 +1,90 @@
1
+ = FLVEdit - Flash video manipulation
2
+
3
+ FLVEdit is a tool that will read or edit FLV files to:
4
+ * generate meta data
5
+ * add/remove cue points
6
+ * join files
7
+ * inspect files
8
+
9
+ It is meant as an improved FLVTool2, fixing the shortfalls that prompted others to write FLVTool++ & FLVMeta (see comparison chart)
10
+
11
+ FLVEdit can be used either as a command-line command or as a ruby library.
12
+
13
+ == This is not a stable release
14
+ <b>Warning:</b> The basic functionality is there, but I'll be improving features and documentation. Until version 1.0, there will most likely be many changes to the command line interface and the library API.
15
+
16
+ Comments & requests welcome...
17
+
18
+ == Installation
19
+
20
+ +flvedit+ is a gem mirrored on Rubyforge and can thus be installed with:
21
+
22
+ sudo gem install flvedit
23
+
24
+ flvedit is compatible with Ruby 1.8 and 1.9
25
+
26
+ == Command-line tool
27
+
28
+ Type 'flvedit' for description of commands.
29
+
30
+ == Library
31
+
32
+ FLVEdit is written in ruby and divided in two layers.
33
+
34
+ === FLV file format
35
+ The FLV layer handles the FLV file format. It makes reading and writing FLV files a breeze:
36
+
37
+ FLV::File.open("example.flv") {|f| f.to_a } # ==> [<#FLV::Header...>, <#FLV::Tag...>, ...]
38
+
39
+ The main class is FLV::Tag with its different possible bodies: FLV::Audio, FLV::Video and most importantly FLV::Event for all meta data related information (onMetaData, onCuePoint, ...)
40
+
41
+ The data packing and unpacking relies on the packable[http://github.com/marcandre/packable] library.
42
+
43
+ === FLV::Edit tool
44
+
45
+ The FLV::Edit layer is the command-line tool itself. The FLV::Edit::Runner class parses the options and builds a chain of processors to apply to some flv files. Processors all derive from FLV::Edit::Processor::Base and only need to specify what to do for the type of data it wants to process. A simplistic example to use this level:
46
+
47
+ class CountCuePoints < FLV::Edit::Processor::Base
48
+ attr_writer :count
49
+ def on_cue_point(cue)
50
+ @count ||= 0
51
+ @count += 1
52
+ end
53
+ end
54
+
55
+ # Call manually
56
+ count =
57
+ FLV::File.open("example.flv") do |f|
58
+ CountCuePoints.new(f).process_all.count
59
+ end
60
+
61
+ # Chain with other commands:
62
+ count = FLV::Edit::Runner.new([CountCuePoints, FLV::Edit::Processor::Debug], :files => "example.swf").run.count
63
+
64
+ See FLV::Edit::Processor::Base for details on how the processing works
65
+
66
+ == Comparisons with existing tools
67
+
68
+ === FLVTool2
69
+
70
+ Features:
71
+ * Can join (concat) flv files
72
+ * Won't load the whole files in memory all at once
73
+ * Won't choke on read-only files
74
+ * Supports extented timestamps (for flv over... 4 hours!)
75
+
76
+ Code:
77
+ * Complete rewrite
78
+ * More ruby-oriented
79
+ * Commented
80
+ * Unit tests
81
+ * Easier to use library
82
+ * Easily expandable with your own processing
83
+
84
+ === Compared to FLVTool++
85
+ * Handles cue points
86
+ * Usable as a library
87
+ <to be completed>
88
+
89
+ === Compared to FLVMeta
90
+ <to be completed>
data/Rakefile ADDED
@@ -0,0 +1,137 @@
1
+ # encoding: utf-8
2
+ require 'rake'
3
+ require 'rake/testtask'
4
+ require 'rake/rdoctask'
5
+ require 'rcov/rcovtask'
6
+
7
+ # bench
8
+ begin
9
+ desc "Benchmark"
10
+ task :bench do
11
+ require File.dirname(__FILE__)+"/lib/flv/edit"
12
+ SHORT_FLV = File.dirname(__FILE__) + "/test/fixtures/short.flv"
13
+ require 'benchmark'
14
+ include Benchmark
15
+ runner = FLV::Edit::Runner.new([SHORT_FLV, SHORT_FLV, '--Join'])
16
+ bm(6) do |x|
17
+ x.report("test") { 20.times { runner.run } }
18
+ end
19
+ end
20
+ end
21
+
22
+
23
+ begin
24
+ require 'jeweler'
25
+ Jeweler::Tasks.new do |gem|
26
+ gem.name = "flvedit"
27
+ gem.summary = "Command line tool & library to handle FLV files"
28
+ gem.email = "github@marc-andre.ca"
29
+ gem.homepage = "http://github.com/marcandre/flvedit"
30
+ gem.description = <<-EOS
31
+ flvedit allows you to:
32
+ * compute metadata for FLV files
33
+ * merge, split or cut FLVs
34
+ * insert / remote cue points or other events
35
+
36
+ flvedit is meant as a replacement for FLVTool2, FLVMeta, FLVTool++
37
+ It can be used as a command line tool or as a Ruby library.
38
+ EOS
39
+ gem.authors = ["Marc-André Lafortune"]
40
+ gem.rubyforge_project = "flvedit"
41
+ gem.add_dependency "packable", ">=1.2"
42
+ gem.add_dependency "backports"
43
+ gem.has_rdoc = true
44
+ gem.rdoc_options << '--title' << 'FLV::Edit' <<
45
+ '--main' << 'README.rdoc' <<
46
+ '--line-numbers' << '--inline-source'
47
+ end
48
+ rescue LoadError
49
+ puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
50
+ end unless RUBY_VERSION >= "1.9"
51
+
52
+ Rake::TestTask.new do |t|
53
+ t.libs << 'lib'
54
+ t.pattern = 'test/**/test_*.rb'
55
+ t.verbose = false
56
+ end
57
+
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
65
+ rdoc.rdoc_dir = 'rdoc'
66
+ rdoc.title = "FLVEdit #{version}"
67
+ rdoc.options << '--line-numbers' << '--inline-source'
68
+ rdoc.rdoc_files.include('README*')
69
+ rdoc.rdoc_files.include('lib/**/*.rb')
70
+ end
71
+
72
+ Rcov::RcovTask.new do |t|
73
+ t.libs << 'test'
74
+ t.test_files = FileList['test/**/*_test.rb']
75
+ t.verbose = true
76
+ end
77
+
78
+ task :default => :rcov
79
+
80
+ # stats
81
+ begin
82
+ gem 'rails'
83
+ require 'code_statistics'
84
+ namespace :spec do
85
+ desc "Use Rails's rake:stats task for a gem"
86
+ task :statsetup do
87
+ class CodeStatistics
88
+ def calculate_statistics
89
+ @pairs.inject({}) do |stats, pair|
90
+ if 3 == pair.size
91
+ stats[pair.first] = calculate_directory_statistics(pair[1], pair[2]); stats
92
+ else
93
+ stats[pair.first] = calculate_directory_statistics(pair.last); stats
94
+ end
95
+ end
96
+ end
97
+ end
98
+ ::STATS_DIRECTORIES = [['Libraries', 'lib', /.(sql|rhtml|erb|rb|yml)$/],
99
+ ['Tests', 'test', /.(sql|rhtml|erb|rb|yml)$/]]
100
+ ::CodeStatistics::TEST_TYPES << "Tests"
101
+ end
102
+ end
103
+ desc "Report code statistics (KLOCs, etc) from the application"
104
+ task :stats => "spec:statsetup" do
105
+ CodeStatistics.new(*STATS_DIRECTORIES).to_s
106
+ end
107
+ rescue Gem::LoadError => le
108
+ task :stats do
109
+ raise RuntimeError, "‘rails’ gem not found - you must install it in order to use this task.n"
110
+ end
111
+ end
112
+
113
+ begin
114
+ require 'rake/contrib/sshpublisher'
115
+ namespace :rubyforge do
116
+
117
+ desc "Release gem and RDoc documentation to RubyForge"
118
+ task :release => ["rubyforge:release:gem", "rubyforge:release:docs"]
119
+
120
+ namespace :release do
121
+ desc "Publish RDoc to RubyForge."
122
+ task :docs => [:rdoc] do
123
+ config = YAML.load(
124
+ File.read(File.expand_path('~/.rubyforge/user-config.yml'))
125
+ )
126
+
127
+ host = "#{config['username']}@rubyforge.org"
128
+ remote_dir = "/var/www/gforge-projects/flvedit/"
129
+ local_dir = 'rdoc'
130
+
131
+ Rake::SshDirPublisher.new(host, remote_dir, local_dir).upload
132
+ end
133
+ end
134
+ end
135
+ rescue LoadError
136
+ puts "Rake SshDirPublisher is unavailable or your rubyforge environment is not configured."
137
+ end
data/VERSION.yml ADDED
@@ -0,0 +1,4 @@
1
+ ---
2
+ :major: 0
3
+ :minor: 6
4
+ :patch: 1
data/bin/flvedit ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ #--
4
+ # Copyright (c) 2009 Marc-Andre Lafortune
5
+ # Release under the (modified) BSD License (see gem's LICENSE file)
6
+ #++
7
+
8
+ begin
9
+ require 'flv/edit'
10
+ rescue LoadError
11
+ require 'rubygems'
12
+ require 'flv/edit'
13
+ end
14
+ FLV::Edit::Runner.new(ARGV).run
data/lib/flv/audio.rb ADDED
@@ -0,0 +1,66 @@
1
+ module FLV
2
+ # The body of an audio tag.
3
+ # The data is quite complex stuff. We make no attempt to understand it all
4
+ # or to be able to modify any of it. We simply consider it a complex string
5
+ # and read the interesting bits to give more info.
6
+ class Audio < String
7
+ include Body
8
+
9
+ FORMATS = Hash.new{|h, key| "Unknown audio format: #{key}"}.merge(
10
+ 0 => :"Linear PCM, platform endian" ,
11
+ 1 => :ADPCM ,
12
+ 2 => :MP3 ,
13
+ 3 => :"Linear PCM, little endian" ,
14
+ 4 => :"Nellymoser 16-kHz mono" ,
15
+ 5 => :"Nellymoser 8-kHz mono" ,
16
+ 6 => :Nellymoser ,
17
+ 7 => :"G.711 A-law logarithmic PCM" ,
18
+ 8 => :"G.711 mu-law logarithmic PCM" ,
19
+ 10 => :AAC ,
20
+ 11 => :Speex ,
21
+ 14 => :"MP3 8-kHz" ,
22
+ 15 => :"Device-specific sound"
23
+ ).freeze
24
+
25
+ EXCEPTIONS = Hash.new({}).merge(
26
+ :"Nellymoser 8-kHz mono" => {:channel => :mono, :rate => 8000},
27
+ :"Nellymoser 16-kHz mono" => {:channel => :mono, :rate => 16000},
28
+ :AAC => {:channel => :stereo,:rate => 44000},
29
+ :"MP3 8-kHz" => {:rate => 8000}
30
+ ).freeze
31
+
32
+ CHANNELS = { 0 => :mono, 1 => :stereo}.freeze
33
+
34
+ def codec_id
35
+ read_bits(0..3)
36
+ end
37
+
38
+ # Returns the format (see Audio::FORMATS for list)
39
+ def format
40
+ FORMATS[codec_id]
41
+ end
42
+
43
+ # returns :mono or :stereo
44
+ def channel
45
+ EXCEPTIONS[format][:channel] ||
46
+ CHANNELS[read_bits(7)]
47
+ end
48
+
49
+ # Returns the sampling rate (in Hz)
50
+ def rate
51
+ EXCEPTIONS[format][:rate] ||
52
+ 5500 << read_bits(4..5)
53
+ end
54
+
55
+ # Returns the sample size (in bits)
56
+ def sample_size
57
+ EXCEPTIONS[format][:sample_size] ||
58
+ 8 << read_bits(6)
59
+ end
60
+
61
+ def is?(what)
62
+ format.to_s.downcase == what.to_s.downcase || super
63
+ end
64
+
65
+ end
66
+ end
data/lib/flv/base.rb ADDED
@@ -0,0 +1,38 @@
1
+ module FLV
2
+ # Common fonctionality to both FLV::Header, FLV::Tag & FLV::Body
3
+ module Base
4
+ def self.included(base)
5
+ base.class_eval do
6
+ include Packable
7
+ end
8
+ end
9
+
10
+ # returns the instance methods
11
+ # that are proper (i.e. not redefinitions)
12
+ # and that don't require any argument.
13
+ def getters(of=self)
14
+ of.class.ancestors.
15
+ map{|k| k.instance_methods(false)}.
16
+ inject{|proper, methods| proper -= methods}. # tricky: first ancestor is of.class, so that's what we start with
17
+ select{|m| of.class.instance_method(m).arity.between?(-1,0)}
18
+ end
19
+
20
+ def to_h(attributes = getters)
21
+ Hash[attributes.map do |a|
22
+ a = a.to_s.delete("@").to_sym
23
+ [a, send(a)]
24
+ end]
25
+ end
26
+
27
+ def is?(what)
28
+ kn = self.class.name.downcase
29
+ [kn, kn.sub("flv::","")].include?(what.to_s.downcase)
30
+ end
31
+
32
+ def size
33
+ StringIO.new.packed.write(self)
34
+ end
35
+
36
+ end
37
+
38
+ end
data/lib/flv/body.rb ADDED
@@ -0,0 +1,57 @@
1
+ module FLV
2
+
3
+ # Common fonctionality to all types of Bodies (FLV::Audio, FLV::Video & FLV::Event)
4
+ module Body
5
+ def self.included(base)
6
+ # Caution: order is important; InstanceMethods::is? relies on Base::is?
7
+ base.class_eval do
8
+ include Base
9
+ include InstanceMethods
10
+ include InstanceMethodsWhenString
11
+ end
12
+ end
13
+
14
+ module InstanceMethods # :nodoc:
15
+ def debug(format, *) #:nodoc
16
+ format.values(to_h)
17
+ end
18
+
19
+ def is?(what)
20
+ case what
21
+ when String, Symbol
22
+ super(what.to_s.downcase.gsub!(/_tag$/, "") || :never_match_on_class_name_unless_string_ends_with_tag)
23
+ else
24
+ super
25
+ end
26
+ end
27
+
28
+ def similar_to?(other_body)
29
+ getters.each{|getter| return false unless send(getter) == other_body.send(getter)}
30
+ true
31
+ end
32
+
33
+ def title
34
+ self.class.name + " tag"
35
+ end
36
+ end
37
+
38
+ module InstanceMethodsWhenString # :nodoc:
39
+ # Returns an +Integer+ computed from bits specified by +which+.
40
+ # The 0th bit is the most significant bit of the first character.
41
+ # +which+ can designate a range of bits or a single bit
42
+ def read_bits(which)
43
+ which = which..which if which.is_a? Integer
44
+ first_byte, last_byte = which.first >> 3, which.max >> 3
45
+ return (getbyte(first_byte) >> (7 & ~which.max)) & ~(~1 << which.max-which.first) if(first_byte == last_byte)
46
+ mid = last_byte << 3
47
+ read_bits(which.first...mid) << (which.max - mid + 1) | read_bits(mid..which.max)
48
+ end
49
+
50
+ # We need to redefine this, since we want to end up with a Body, not a String
51
+ def read_packed(io, options) #:nodoc:
52
+ replace(io.read(String, options))
53
+ end
54
+ end
55
+ end
56
+
57
+ end
@@ -0,0 +1,162 @@
1
+ # encoding: utf-8
2
+ require 'optparse'
3
+
4
+ module FLV
5
+ module Edit
6
+ class Options
7
+ attr_reader :commands, :options
8
+ def initialize(argv)
9
+ @commands, @options = parse(argv)
10
+ end
11
+
12
+ def to_a
13
+ [@commands, @options]
14
+ end
15
+
16
+ private
17
+ def parse(argv)
18
+ commands = []
19
+ options = {}
20
+ parser = OptionParser.new do |parser|
21
+ parser.banner = banner
22
+ parser.separator ""
23
+ parser.separator "Commands:"
24
+ Processor::Base.registry.sort_by(&:to_s).
25
+ each do |command, desc, cmd_opt|
26
+ name = command.name.split('::').last.downcase
27
+ option = name.downcase.to_sym
28
+ shortcut = cmd_opt[:shortcut] || name[0..0]
29
+ if param = cmd_opt[:param]
30
+ name << " " << (param[:name] || param[:class].to_s.upcase)
31
+ desc = [param[:class], *desc] if param[:class]
32
+ end
33
+ parser.on("-#{shortcut}", "--#{name}", *desc) do |v|
34
+ options[option] = v
35
+ commands << command
36
+ end
37
+ end
38
+
39
+ parser.separator ""
40
+ parser.separator "Switches:"
41
+ [
42
+ [:keyframe_mode, "Keyframe mode slides on_cue_point(navigation) tags added by the",
43
+ "add command to nearest keyframe position"]
44
+ ].each do |switch, *desc|
45
+ shortcut = desc.first.is_a?(Symbol) ? desc.shift.to_s : switch.to_s[0..0]
46
+ full = desc.first.is_a?(Class) ? "--#{switch.to_s} N" : "--#{switch.to_s}"
47
+ parser.on("-#{shortcut}", full, *desc) { |v| options[switch] = v }
48
+ end
49
+
50
+ parser.separator "Common:"
51
+ parser.on("-h", "--help", "Show this message") {}
52
+ parser.on("--version", "Show version") do
53
+ puts "Current version: #{FLV::Edit.version}"
54
+ exit
55
+ end
56
+
57
+ parser.separator ""
58
+ parser.separator "flvedit (#{FLV::Edit.version}), copyright (c) 2009 Marc-André Lafortune"
59
+ parser.separator "This program is published under the BSD license."
60
+ end
61
+ options[:files] = parser.parse!(argv)
62
+ if commands.empty?
63
+ puts parser
64
+ exit
65
+ end
66
+ return commands, options
67
+ end
68
+
69
+ def banner
70
+ <<-EOS
71
+ Usage: flvedit#{(RUBY_PLATFORM =~ /win32/) ? '.exe' : ''} [--input] files --processing --more_processing ... [--save [PATH]]
72
+ ******
73
+ *NOTE*: THIS IS NOT A STABLE RELEASE. API WILL CHANGE!
74
+ ******
75
+ flvedit will apply the given processing to the input files.
76
+
77
+ Examples:
78
+ # Printout of the first 42 tags:
79
+ flvedit example.flv --debug 42
80
+
81
+ # Extract the first 2 seconds of a.flv and b.flv, join them and update the meta data.
82
+ flvedit a.flv b.flv --cut 0-2 --join --update --save out.flv
83
+
84
+ Option formats:
85
+ TIMESTAMP: Given in seconds with decimals for fractions of seconds.
86
+ Use 'h' and 'm' or ':' for hours and minutes separators.
87
+ Examples: 1:00:01 (for 1 hour and 1 second), 1h1 (same),
88
+ 2m3.004 (2 minutes, 3 seconds and 4 milliseconds), 123.004 (same)
89
+ RANGE: Expressed as BEGIN-END, where BEGIN or END are timestamps
90
+ that can be let empty for no limit
91
+ Examples: 1m-2m (second minute), 1m- (all but first minute), -1m (first minute)
92
+ RANGE/TS: RANGE or TIMESTAMP.
93
+ EOS
94
+ #s.split("\n").collect!(&:strip).join("\n")
95
+ end
96
+ end
97
+ end
98
+ end
99
+
100
+ #todo: explicit Input command
101
+ #todo: conditions for implicit Save
102
+
103
+ # options[:metadatacreator] = "inlet media FLVTool2 v#{PROGRAMM_VERSION.join('.')} - http://www.inlet-media.de/flvtool2"
104
+ # options[:metadata] = {}
105
+
106
+ # when /^-([a-zA-Z0-9]+?):(.+)$/
107
+ # options[:metadata][$1] = $2
108
+ # when /^-([a-zA-Z0-9]+?)#([0-9]+)$/
109
+ # options[:metadata][$1] = $2.to_f
110
+ # when /^-([a-zA-Z0-9]+?)@(\d{4,})-(\d{2,})-(\d{2,}) (\d{2,}):(\d{2,}):(\d{2,})$/
111
+ # options[:metadata][$1] = Time.local($2, $3, $4, $5, $6, $7)
112
+ # when /^([^-].*)$/
113
+ # if options[:in_path].nil?
114
+ # options[:in_path] = $1
115
+ # if options[:in_path].downcase =~ /stdin|pipe/
116
+ # options[:in_pipe] = true
117
+ # options[:in_path] = 'pipe'
118
+ # else
119
+ # options[:in_path] = File.expand_path( options[:in_path] )
120
+ # end
121
+ # else
122
+ # options[:out_path] = $1
123
+ # if options[:out_path].downcase =~ /stdout|pipe/
124
+ # options[:out_pipe] = true
125
+ # options[:out_path] = 'pipe'
126
+ # else
127
+ # options[:out_path] = File.expand_path( options[:out_path] )
128
+ # end
129
+ # end
130
+ # end
131
+ # end
132
+
133
+
134
+ # def self.validate_options( options )
135
+ # if options[:commands].empty?
136
+ # show_usage
137
+ # exit 0
138
+ # end
139
+ #
140
+ # options[:commands].each do |command|
141
+ # case command
142
+ # when :print
143
+ # if options[:out_pipe]
144
+ # throw_error "Could not use print command in conjunction with output piping or redirection"
145
+ # exit 1
146
+ # end
147
+ # when :debug
148
+ # if options[:out_pipe]
149
+ # throw_error "Could not use debug command in conjunction with output piping or redirection"
150
+ # exit 1
151
+ # end
152
+ # when :help
153
+ # show_usage
154
+ # exit 0
155
+ # when :version
156
+ # show_version
157
+ # exit 0
158
+ # end
159
+ # end
160
+ # options
161
+ # end
162
+ #
@@ -0,0 +1,67 @@
1
+ require 'yaml'
2
+ require 'rexml/document'
3
+ require 'optparse'
4
+
5
+ module FLV
6
+ module Edit
7
+ module Processor
8
+
9
+ class Add < Base
10
+ desc "Adds tags from the xml or yaml file PATH (default: tags.yaml/xml)", :param => {:name => "[PATH]"}
11
+
12
+ def initialize(*)
13
+ super
14
+ @add = (options[:add_tags] || read_tag_file).sort_by(&:timestamp)
15
+ end
16
+
17
+ def on_tag(tag)
18
+ insert_before tag, @add
19
+ end
20
+
21
+ # def on_keyframe(tag)
22
+ # insert_before tag, @add_before_key_frame
23
+ # end
24
+
25
+ private
26
+ def insert_before(tag, list_to_insert)
27
+ stop_at = list_to_insert.find_index{|i| i.timestamp > tag.timestamp} || list_to_insert.length
28
+ dispatch_instead(*(list_to_insert.slice!(0...stop_at) << tag)) if stop_at > 0
29
+ end
30
+
31
+ DEFAULT = ["tags.yaml", "tags.xml"]
32
+ #todo: overwrite, navigation
33
+ #todo: default event
34
+ def read_tag_file
35
+ filename = options[:add]
36
+ DEFAULT.each{|fn| filename ||= fn if ::File.exists?(fn)}
37
+ raise "You must either specify a tag file or have a file named #{DEFAULT.join(' or ')}" unless filename
38
+ ::File.open(filename) {|f| read_tags(f)}
39
+ end
40
+
41
+ def read_tags(file)
42
+ tags =
43
+ unless file.path.downcase.end_with? ".xml"
44
+ YAML.load(file)
45
+ else
46
+ xml = REXML::Document.new(file, :ignore_whitespace_nodes => :all)
47
+ xml.root.elements.to_a("metatag").map do |tag|
48
+ {
49
+ :event => tag.attributes['event'],
50
+ :overwrite => tag.attributes['overwrite'],
51
+ :timestamp => (tag.elements['timestamp'].text rescue 0),
52
+ :navigation => (tag.elements['type'].text == "navigation" rescue false),
53
+ :arguments => Hash[tag.elements['parameters'].map{|param| [param.name, param.text]}]
54
+ }
55
+ end
56
+ end
57
+ tags = [tags] unless tags.is_a? Array
58
+ tags.map do |info|
59
+ info.symbolize_keys!
60
+ Tag.new(info[:timestamp], Event.new(info[:event], info[:arguments]))
61
+ end
62
+ end
63
+ end
64
+
65
+ end
66
+ end
67
+ end