marcandre-flvedit 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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,22 @@
1
+ module FLV
2
+ class IOError < ::IOError # :nodoc:
3
+ end
4
+
5
+ module Util # :nodoc:
6
+ def self.double_check(event, expected, actual)
7
+ Checking.fail_check(event, expected, actual) unless [*expected].include? actual
8
+ end
9
+
10
+ class Checking # :nodoc:
11
+ class << self
12
+ attr_accessor :strict
13
+ def fail_check(event, expected, actual)
14
+ err = "Mismatch on #{event}: expected #{expected} vs #{actual}"
15
+ raise IOError, err if strict
16
+ #STDERR << "Caution: "+ err
17
+ end
18
+ end
19
+ end
20
+
21
+ end
22
+ end
@@ -0,0 +1,73 @@
1
+ module FLV
2
+ # The body of a video 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 Video < String
7
+ include Body
8
+ CODECS = Hash.new(:unknown).merge!(
9
+ 1 => :JPEG,
10
+ 2 => :h263,
11
+ 3 => :screen,
12
+ 4 => :on2_vp6,
13
+ 5 => :on2_vp6_alpha,
14
+ 6 => :screen_v2,
15
+ 7 => :AVC
16
+ ).freeze
17
+
18
+ FRAMES = Hash.new(:unknown).merge!(
19
+ 1 => :keyframe ,
20
+ 2 => :interframe,
21
+ 3 => :disposable_interframe
22
+ ).freeze
23
+
24
+ def frame_type
25
+ FRAMES[read_bits(0...4)]
26
+ end
27
+
28
+ def codec_id
29
+ read_bits(4...8)
30
+ end
31
+
32
+ def codec
33
+ CODECS[codec_id]
34
+ end
35
+
36
+ # Dimensions for h263 encoding; either as a bit range or the final value
37
+ H263_DIMENSIONS = {
38
+ 0 => [41...49, 49...57],
39
+ 1 => [41...57, 57...73],
40
+ 2 => [352, 288],
41
+ 3 => [176, 144],
42
+ 4 => [128, 96] ,
43
+ 5 => [320, 240],
44
+ 6 => [160, 120]
45
+ }.freeze
46
+
47
+ # Returns dimensions as {:width => w, :height => h}, for Sorensen H.263 and screen video codecs only (otherwise returns nil)
48
+ def dimensions
49
+ w, h = case codec
50
+ when :h263
51
+ H263_DIMENSIONS[read_bits(38...41)]
52
+ when :screen
53
+ [12...24, 24...32]
54
+ end
55
+ return nil unless w
56
+ w, h = [w, h].map{ |r| read_bits(r) } if w.is_a?(Range)
57
+ {:width => w, :height => h}
58
+ end
59
+
60
+ def is?(what)
61
+ frame_type.to_s.downcase == what.to_s.downcase || super
62
+ end
63
+
64
+ def getters
65
+ super - [:frame_type, :frame_type.to_s] # Let's exclude the frame_type from the normal attributes... (string vs symbol: ruby 1.8 vs 1.9)
66
+ end
67
+
68
+ def title
69
+ super + " (#{frame_type})" # ...and include it in the title instead.
70
+ end
71
+
72
+ end
73
+ end
Binary file
@@ -0,0 +1,39 @@
1
+ <?xml version="1.0"?>
2
+ <tags>
3
+ <!-- an event cue point -->
4
+ <metatag event="onCuePoint">
5
+ <name>TestEvent1</name>
6
+ <timestamp>333</timestamp>
7
+ <parameters>
8
+ <speaker>Peter</speaker>
9
+ <says>Hello my Name is Peter.</says>
10
+ </parameters>
11
+ <type>event</type>
12
+ </metatag>
13
+
14
+ <!-- a navigation cue point -->
15
+ <metatag event="onCuePoint">
16
+ <name>TestEvent2</name>
17
+ <timestamp>1000</timestamp>
18
+ <parameters>
19
+ <index>1</index>
20
+ <title>Chapter 1</title>
21
+ </parameters>
22
+ <type>navigation</type>
23
+ </metatag>
24
+
25
+ <!-- this cuepoint overwrites the previous cue point, because of it's
26
+ identical timestamps and the overwrite parameter was set -->
27
+
28
+ <metatag event="onCuePoint" overwrite="true">
29
+ <name>TestEvent3</name>
30
+ <timestamp>1000</timestamp>
31
+ <parameters>
32
+ <index>1</index>
33
+ <title>Chapter 2</title>
34
+ </parameters>
35
+ <type>navigation</type>
36
+ </metatag>
37
+ </tags>
38
+
39
+
@@ -0,0 +1,145 @@
1
+ require File.dirname(__FILE__) + '/test_helper'
2
+
3
+ class TestFlv < Test::Unit::TestCase
4
+ context "Timestamp" do
5
+ context "conversion" do
6
+ { 0 => "0.000" ,
7
+ 1 => "0.001" ,
8
+ 1000 => "1.000" ,
9
+ 12_345 => "12.345" ,
10
+ 61_000 => "1:01.000" ,
11
+ 601_000 => "10:01.000" ,
12
+ 3600_000 => "1:00:00.000"
13
+ }.each do |n, s|
14
+ should "convert between #{s} and #{n} ms" do
15
+ assert_equal s, FLV::Timestamp.in_milliseconds(n).to_s
16
+ assert_equal n, FLV::Timestamp.try_convert(s).in_milliseconds
17
+ end
18
+ end
19
+
20
+ { "0" => 0 ,
21
+ "1" => 1000,
22
+ "1.0" => 1000,
23
+ "1m" => 60_000,
24
+ "1h" => 60 * 60_000,
25
+ "1::" => 60 * 60_000,
26
+ "1h23m45.6789" => 60 * 60_000 + 23*60_000 + 45678
27
+ }.each do |s, n|
28
+ should "convert #{s} to #{n} ms" do
29
+ assert_equal n, FLV::Timestamp.try_convert(s).in_milliseconds
30
+ end
31
+ end
32
+ end
33
+ end
34
+
35
+ context "TimestampRange" do
36
+ context "conversion" do
37
+ { "1:02-" => 62..(1/0.0),
38
+ "-1.23" => 0..1.23,
39
+ "12345-1:02:03.456" => 12345..3723.456,
40
+ "2m.345-1h" => 120.345..3600
41
+ }.each do |s, r|
42
+ should "convert #{s} to #{r} ms" do
43
+ assert_equal r, FLV::TimestampRange.try_convert(s).in_seconds
44
+ end
45
+ end
46
+ end
47
+ end
48
+
49
+
50
+ BIT_TEST = "\x01\x02\xff"
51
+ context "Bit reading from #{BIT_TEST.inspect}" do
52
+ setup do
53
+ @body = BIT_TEST.clone
54
+ class <<@body
55
+ include FLV::Body
56
+ end
57
+ end
58
+
59
+ [ [0..0, 0],
60
+ [0...7, 0],
61
+ [0..7, 1],
62
+ [6..7, 1],
63
+ [7..7, 1],
64
+ [14, 1],
65
+ [3..9, 0b100],
66
+ [3...15,0b1_0000_001],
67
+ [3..15, 0b1_0000_0010],
68
+ [3..16, 0b1_0000_0010_1]
69
+ ].each do |bits, val|
70
+ should "return #{val} for bits #{bits}" do
71
+ assert_equal val, @body.read_bits(bits)
72
+ end
73
+ end
74
+ end
75
+
76
+ context "Packing" do
77
+ context "a header" do
78
+ setup do
79
+ @header = FLV::Header.new
80
+ @header.extra = "hello"
81
+ @header.has_audio = true
82
+ end
83
+
84
+ should "work" do
85
+ assert_equal "FLV\001\004\000\000\000\016hello\000\000\000\000",
86
+ @header.pack
87
+ end
88
+ end
89
+
90
+ context "an audio tag" do
91
+ setup do
92
+ @tag = FLV::Tag.new(FLV::Timestamp.in_milliseconds(0x1234), FLV::Audio.new("bogus audio data!"))
93
+ end
94
+
95
+ should "work" do
96
+ assert_equal "\b\000\000\021\000\x12\x34\000\000\000\000bogus audio data!\000\000\000\034",
97
+ @tag.pack
98
+ end
99
+ end
100
+ end
101
+
102
+ context "Unpacking" do
103
+ setup do
104
+ @io = StringIO.new("FLV\001\004\000\000\000\016hello\000\000\000\000!!")
105
+ @header = @io.packed.read(FLV::Header)
106
+ end
107
+
108
+ should "read the header correctly" do
109
+ assert_equal "hello", @header.extra
110
+ assert_equal true, @header.has_audio
111
+ assert_equal false, @header.has_video
112
+ end
113
+
114
+ should "read just what's required" do
115
+ assert_equal "!!", @io.read
116
+ end
117
+ end
118
+
119
+ context "Typematch" do
120
+ setup do
121
+ @chunks = FLV::File.open(SHORT_FLV).each.first(4)
122
+ end
123
+
124
+ should "work" do
125
+ assert_equal false, @chunks[2].is?(:audio)
126
+ end
127
+
128
+ {
129
+ :header => [true , false, false, false],
130
+ :tag => [false, true , true , true ],
131
+ :audio => [false, false, false, false],
132
+ :audio_tag => [false, false, true , false],
133
+ :nellymoser => [false, false, false, false],
134
+ :mP3 => [false, false, true , false],
135
+ :event_tag => [false, true , false, false],
136
+ :onMetaData => [false, true , false, false],
137
+ :onCuePoint => [false, false, false, false],
138
+ }.each do |check, answers|
139
+ should "work for #{check}" do
140
+ assert_equal answers, @chunks.map{|t| t.is?(check) }, check
141
+ end
142
+ end
143
+ end
144
+
145
+ end
@@ -0,0 +1,32 @@
1
+ require File.dirname(__FILE__) + '/test_helper'
2
+
3
+ class TestFlvEdit < Test::Unit::TestCase
4
+ context "Options parsing" do
5
+ setup do
6
+ @options = FLV::Edit::Options.new([SHORT_FLV, "--Debug"])
7
+ end
8
+
9
+ should "detect files" do
10
+ assert_equal [SHORT_FLV], @options.options[:files]
11
+ end
12
+
13
+ end
14
+
15
+ context "Command line tool" do
16
+ setup do
17
+ File.delete(TEMP_FLV) if File.exist?(TEMP_FLV)
18
+ end
19
+
20
+ should "save" do
21
+ assert !File.exist?(TEMP_FLV)
22
+ runner = FLV::Edit::Runner.new([SHORT_FLV, "--Update", "--Save", TEMP_FLV])
23
+ runner.options[:dont_catch_errors] = true
24
+ runner.run
25
+ assert File.exist?(TEMP_FLV)
26
+ end
27
+
28
+ teardown do
29
+ File.delete(TEMP_FLV) if File.exist?(TEMP_FLV)
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,27 @@
1
+ require File.dirname(__FILE__) + '/test_helper'
2
+
3
+ class TestFlvEdit < Test::Unit::TestCase
4
+ context "Command line results" do
5
+ Dir.glob(File.dirname(__FILE__)+"/text_flv_edit_results/*.txt").each do |fn|
6
+ sep, args, sep, *expect = File.readlines(fn).map!(&:chomp)
7
+ args = args.split(' ')
8
+ args.unshift SHORT_FLV unless args.first.start_with?(".")
9
+
10
+ context "for 'flvedit #{args.join(' ')}'" do
11
+ setup do
12
+ Time.stubs(:now).returns(Time.utc(2008,"dec",20))
13
+ @result = ""
14
+ runner = FLV::Edit::Runner.new(args)
15
+ runner.options[:dont_catch_errors] = true
16
+ runner.options[:out] = StringIO.new(@result)
17
+ runner.run
18
+ end
19
+ should "match #{fn}" do
20
+ @result = @result.split("\n")
21
+ same = @result & expect
22
+ assert_equal expect-same, @result-same
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,9 @@
1
+ require 'test/unit'
2
+ require 'rubygems'
3
+ require 'backports'
4
+ require_relative '../lib/flv/edit'
5
+ require 'shoulda'
6
+ require 'mocha'
7
+
8
+ SHORT_FLV = File.dirname(__FILE__) + "/fixtures/short.flv"
9
+ TEMP_FLV = File.dirname(__FILE__) + "/fixtures/short_temp.flv"
@@ -0,0 +1,132 @@
1
+ *** command:
2
+ --Add ./test/fixtures/tags.xml --Debug
3
+ *** result:
4
+ Header| ./test/fixtures/short.flv
5
+ | extra :
6
+ | has_audio : true
7
+ | has_video : true
8
+ | version : 1
9
+ 0.000 | FLV::Event tag
10
+ | event : onMetaData
11
+ | audiocodecid : 2
12
+ | audiodatarate : 16
13
+ | audiodelay : 0
14
+ | canSeekToEnd : 1
15
+ | creationdate : Fri Feb 03 122553 2006\n
16
+ | duration : 16.9
17
+ | framerate : 10
18
+ | height : 154
19
+ | videocodecid : 4
20
+ | videodatarate : 40
21
+ | width : 192
22
+ 0.000 | FLV::Audio tag
23
+ | channel : mono
24
+ | codec_id : 2
25
+ | format : MP3
26
+ | rate : 11000
27
+ | sample_size : 16
28
+ 0.000 | FLV::Video tag (keyframe)
29
+ | codec : on2_vp6
30
+ | codec_id : 4
31
+ | dimensions : nil
32
+ 0.052 | FLV::Audio tag
33
+ 0.100 | FLV::Video tag (interframe)
34
+ 0.104 | FLV::Audio tag
35
+ 0.156 | FLV::Audio tag
36
+ 0.200 | FLV::Video tag (keyframe)
37
+ 0.208 | FLV::Audio tag
38
+ 0.261 | FLV::Audio tag
39
+ 0.300 | FLV::Video tag (interframe)
40
+ 0.313 | FLV::Audio tag
41
+ 0.365 | FLV::Audio tag
42
+ 0.400 | FLV::Video tag (interframe)
43
+ 0.417 | FLV::Audio tag
44
+ 0.470 | FLV::Audio tag
45
+ 0.500 | FLV::Video tag (interframe)
46
+ 0.522 | FLV::Audio tag
47
+ 0.574 | FLV::Audio tag
48
+ 0.600 | FLV::Video tag (interframe)
49
+ 0.626 | FLV::Audio tag
50
+ 0.679 | FLV::Audio tag
51
+ 0.700 | FLV::Video tag (interframe)
52
+ 0.731 | FLV::Audio tag
53
+ 0.783 | FLV::Audio tag
54
+ 0.800 | FLV::Video tag (interframe)
55
+ 0.835 | FLV::Audio tag
56
+ 0.888 | FLV::Audio tag
57
+ 0.900 | FLV::Video tag (interframe)
58
+ 0.940 | FLV::Audio tag
59
+ 0.992 | FLV::Audio tag
60
+ 1.000 | FLV::Video tag (interframe)
61
+ 1.044 | FLV::Audio tag
62
+ 1.097 | FLV::Audio tag
63
+ 1.100 | FLV::Video tag (interframe)
64
+ 1.149 | FLV::Audio tag
65
+ 1.200 | FLV::Video tag (interframe)
66
+ 1.201 | FLV::Audio tag
67
+ 1.253 | FLV::Audio tag
68
+ 1.300 | FLV::Video tag (interframe)
69
+ 1.306 | FLV::Audio tag
70
+ 1.358 | FLV::Audio tag
71
+ 1.400 | FLV::Video tag (interframe)
72
+ 1.410 | FLV::Audio tag
73
+ 1.462 | FLV::Audio tag
74
+ 1.500 | FLV::Video tag (interframe)
75
+ 1.515 | FLV::Audio tag
76
+ 1.567 | FLV::Audio tag
77
+ 1.600 | FLV::Video tag (interframe)
78
+ 1.619 | FLV::Audio tag
79
+ 1.671 | FLV::Audio tag
80
+ 1.700 | FLV::Video tag (interframe)
81
+ 1.724 | FLV::Audio tag
82
+ 1.776 | FLV::Audio tag
83
+ 1.800 | FLV::Video tag (interframe)
84
+ 1.828 | FLV::Audio tag
85
+ 1.880 | FLV::Audio tag
86
+ 1.900 | FLV::Video tag (interframe)
87
+ 1.933 | FLV::Audio tag
88
+ 1.985 | FLV::Audio tag
89
+ 2.000 | FLV::Video tag (interframe)
90
+ 2.037 | FLV::Audio tag
91
+ 2.089 | FLV::Audio tag
92
+ 2.100 | FLV::Video tag (interframe)
93
+ 2.142 | FLV::Audio tag
94
+ 2.194 | FLV::Audio tag
95
+ 2.200 | FLV::Video tag (interframe)
96
+ 2.246 | FLV::Audio tag
97
+ 2.298 | FLV::Audio tag
98
+ 2.300 | FLV::Video tag (interframe)
99
+ 2.351 | FLV::Audio tag
100
+ 2.400 | FLV::Video tag (interframe)
101
+ 2.403 | FLV::Audio tag
102
+ 2.455 | FLV::Audio tag
103
+ 2.500 | FLV::Video tag (interframe)
104
+ 2.507 | FLV::Audio tag
105
+ 2.559 | FLV::Audio tag
106
+ 2.600 | FLV::Video tag (interframe)
107
+ 2.612 | FLV::Audio tag
108
+ 2.664 | FLV::Audio tag
109
+ 2.700 | FLV::Video tag (interframe)
110
+ 2.716 | FLV::Audio tag
111
+ 2.768 | FLV::Audio tag
112
+ 2.800 | FLV::Video tag (interframe)
113
+ 2.821 | FLV::Audio tag
114
+ 2.873 | FLV::Audio tag
115
+ 2.900 | FLV::Video tag (interframe)
116
+ 2.925 | FLV::Audio tag
117
+ 2.977 | FLV::Audio tag
118
+ 3.000 | FLV::Video tag (interframe)
119
+ 3.030 | FLV::Audio tag
120
+ 3.082 | FLV::Audio tag
121
+ 3.100 | FLV::Video tag (interframe)
122
+ 3.134 | FLV::Audio tag
123
+ 3.186 | FLV::Audio tag
124
+ 3.200 | FLV::Video tag (interframe)
125
+ 3.239 | FLV::Audio tag
126
+ 3.291 | FLV::Audio tag
127
+ 3.300 | FLV::Video tag (interframe)
128
+ 3.343 | FLV::Audio tag
129
+ 3.395 | FLV::Audio tag
130
+ 3.400 | FLV::Video tag (interframe)
131
+ 3.448 | FLV::Audio tag
132
+ 3.500 | FLV::Video tag (interframe)