marcandre-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 +131 -0
  5. data/VERSION.yml +4 -0
  6. data/bin/flvedit +14 -0
  7. data/lib/flv.rb +24 -0
  8. data/lib/flv/audio.rb +66 -0
  9. data/lib/flv/base.rb +38 -0
  10. data/lib/flv/body.rb +57 -0
  11. data/lib/flv/edit.rb +20 -0
  12. data/lib/flv/edit/options.rb +162 -0
  13. data/lib/flv/edit/processor.rb +3 -0
  14. data/lib/flv/edit/processor/add.rb +67 -0
  15. data/lib/flv/edit/processor/base.rb +209 -0
  16. data/lib/flv/edit/processor/command_line.rb +23 -0
  17. data/lib/flv/edit/processor/cut.rb +27 -0
  18. data/lib/flv/edit/processor/debug.rb +30 -0
  19. data/lib/flv/edit/processor/head.rb +16 -0
  20. data/lib/flv/edit/processor/join.rb +52 -0
  21. data/lib/flv/edit/processor/meta_data_maker.rb +127 -0
  22. data/lib/flv/edit/processor/print.rb +13 -0
  23. data/lib/flv/edit/processor/printer.rb +27 -0
  24. data/lib/flv/edit/processor/reader.rb +30 -0
  25. data/lib/flv/edit/processor/save.rb +28 -0
  26. data/lib/flv/edit/processor/update.rb +27 -0
  27. data/lib/flv/edit/runner.rb +23 -0
  28. data/lib/flv/edit/version.rb +15 -0
  29. data/lib/flv/event.rb +40 -0
  30. data/lib/flv/file.rb +41 -0
  31. data/lib/flv/header.rb +37 -0
  32. data/lib/flv/packing.rb +140 -0
  33. data/lib/flv/tag.rb +62 -0
  34. data/lib/flv/timestamp.rb +124 -0
  35. data/lib/flv/util/double_check.rb +22 -0
  36. data/lib/flv/video.rb +73 -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
@@ -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.
@@ -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>
@@ -0,0 +1,131 @@
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
+ rdoc.rdoc_dir = 'rdoc'
60
+ rdoc.title = 'packable'
61
+ rdoc.options << '--line-numbers' << '--inline-source'
62
+ rdoc.rdoc_files.include('README*')
63
+ rdoc.rdoc_files.include('lib/**/*.rb')
64
+ end
65
+
66
+ Rcov::RcovTask.new do |t|
67
+ t.libs << 'test'
68
+ t.test_files = FileList['test/**/*_test.rb']
69
+ t.verbose = true
70
+ end
71
+
72
+ task :default => :rcov
73
+
74
+ # stats
75
+ begin
76
+ gem 'rails'
77
+ require 'code_statistics'
78
+ namespace :spec do
79
+ desc "Use Rails's rake:stats task for a gem"
80
+ task :statsetup do
81
+ class CodeStatistics
82
+ def calculate_statistics
83
+ @pairs.inject({}) do |stats, pair|
84
+ if 3 == pair.size
85
+ stats[pair.first] = calculate_directory_statistics(pair[1], pair[2]); stats
86
+ else
87
+ stats[pair.first] = calculate_directory_statistics(pair.last); stats
88
+ end
89
+ end
90
+ end
91
+ end
92
+ ::STATS_DIRECTORIES = [['Libraries', 'lib', /.(sql|rhtml|erb|rb|yml)$/],
93
+ ['Tests', 'test', /.(sql|rhtml|erb|rb|yml)$/]]
94
+ ::CodeStatistics::TEST_TYPES << "Tests"
95
+ end
96
+ end
97
+ desc "Report code statistics (KLOCs, etc) from the application"
98
+ task :stats => "spec:statsetup" do
99
+ CodeStatistics.new(*STATS_DIRECTORIES).to_s
100
+ end
101
+ rescue Gem::LoadError => le
102
+ task :stats do
103
+ raise RuntimeError, "‘rails’ gem not found - you must install it in order to use this task.n"
104
+ end
105
+ end
106
+
107
+ begin
108
+ require 'rake/contrib/sshpublisher'
109
+ namespace :rubyforge do
110
+
111
+ desc "Release gem and RDoc documentation to RubyForge"
112
+ task :release => ["rubyforge:release:gem", "rubyforge:release:docs"]
113
+
114
+ namespace :release do
115
+ desc "Publish RDoc to RubyForge."
116
+ task :docs => [:rdoc] do
117
+ config = YAML.load(
118
+ File.read(File.expand_path('~/.rubyforge/user-config.yml'))
119
+ )
120
+
121
+ host = "#{config['username']}@rubyforge.org"
122
+ remote_dir = "/var/www/gforge-projects/flvedit/"
123
+ local_dir = 'rdoc'
124
+
125
+ Rake::SshDirPublisher.new(host, remote_dir, local_dir).upload
126
+ end
127
+ end
128
+ end
129
+ rescue LoadError
130
+ puts "Rake SshDirPublisher is unavailable or your rubyforge environment is not configured."
131
+ end
@@ -0,0 +1,4 @@
1
+ ---
2
+ :major: 0
3
+ :minor: 6
4
+ :patch: 1
@@ -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
@@ -0,0 +1,24 @@
1
+ require 'rubygems'
2
+ require 'backports'
3
+
4
+ # utilities
5
+ require_relative 'flv/util/double_check'
6
+
7
+ # packing of FLV objects:
8
+ require 'packable'
9
+ require_relative 'flv/packing'
10
+ require_relative 'flv/base'
11
+
12
+ # FLV body of tags
13
+ require_relative 'flv/body'
14
+ require_relative 'flv/audio'
15
+ require_relative 'flv/video'
16
+ require_relative 'flv/event'
17
+
18
+ # FLV chunks (tags & header)
19
+ require_relative 'flv/timestamp'
20
+ require_relative 'flv/tag'
21
+ require_relative 'flv/header'
22
+
23
+ # finally:
24
+ require_relative 'flv/file'
@@ -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
@@ -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
@@ -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