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,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)