flv 0.0.1 → 0.1.0
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.
- checksums.yaml +4 -4
- data/.gitignore +17 -0
- data/Gemfile +3 -0
- data/LICENSE.txt +22 -0
- data/README.md +53 -0
- data/Rakefile +6 -0
- data/flv.gemspec +24 -0
- data/lib/flv.rb +54 -0
- data/lib/flv/format_error.rb +4 -0
- data/lib/flv/script_data_parser.rb +107 -0
- data/lib/flv/stream_reader.rb +57 -0
- data/lib/flv/tag.rb +58 -0
- data/lib/flv/tag/audio.rb +6 -0
- data/lib/flv/tag/data.rb +63 -0
- data/lib/flv/tag/video.rb +6 -0
- data/lib/flv/version.rb +3 -0
- data/spec/files/preview.flv +0 -0
- data/spec/flv/script_data_parser_spec.rb +105 -0
- data/spec/flv/stream_reader_spec.rb +65 -0
- data/spec/flv/tag/data_spec.rb +19 -0
- data/spec/flv/tag_spec.rb +47 -0
- data/spec/flv_spec.rb +42 -0
- metadata +82 -11
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f8a4f49876669363f9f2eb9b90cffb221efca11a
|
4
|
+
data.tar.gz: 72a72c4ad20f4ba477940c00d972426aff0528d2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 01515ede28f3178c9898c63f254f99606f3bd382e89714e7eabcdfed1df47f17fad591764a690f4801a9644e93ae658688385b5c930091c47bce12d0cca84a75
|
7
|
+
data.tar.gz: 4415125baeb5ba78bafb233ff87f3947f40290be3f34f3ecb476873835d2e73e00585985516c33ce53520af895529544b3f5d67c22097ff50311f34c915dbc0f
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Charlie Somerville & Envato
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
# FLV
|
2
|
+
|
3
|
+
A Ruby library for parsing FLV files and metadata sections.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
gem "flv"
|
11
|
+
```
|
12
|
+
|
13
|
+
And then execute:
|
14
|
+
|
15
|
+
```shell
|
16
|
+
$ bundle
|
17
|
+
```
|
18
|
+
|
19
|
+
Or install it yourself as:
|
20
|
+
|
21
|
+
```shell
|
22
|
+
$ gem install flv
|
23
|
+
```
|
24
|
+
|
25
|
+
## Usage
|
26
|
+
|
27
|
+
```ruby
|
28
|
+
flv = FLV.read("path/to/your/video.flv")
|
29
|
+
flv.audio? # => true
|
30
|
+
flv.video? # => true
|
31
|
+
|
32
|
+
data_tag = flv.tags.find { |tag| tag.is_a?(FLV::Tag::Data) }
|
33
|
+
data_tag.duration # => 60.0
|
34
|
+
data_tag.frame_rate # => 30.0
|
35
|
+
```
|
36
|
+
|
37
|
+
The FLV gem also supports streaming parsing, so you can handle tags as they arrive from an I/O stream.
|
38
|
+
|
39
|
+
This is useful for pulling metadata out of FLV videos without having to download the whole lot:
|
40
|
+
|
41
|
+
```ruby
|
42
|
+
flv = FLV.new(io)
|
43
|
+
data_tag = flv.each_tag.first
|
44
|
+
io.close
|
45
|
+
```
|
46
|
+
|
47
|
+
## Contributing
|
48
|
+
|
49
|
+
1. Fork it
|
50
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
51
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
52
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
53
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
data/flv.gemspec
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path("../lib", __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require "flv/version"
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "flv"
|
8
|
+
spec.version = FLV::VERSION
|
9
|
+
spec.authors = ["Charlie Somerville"]
|
10
|
+
spec.email = ["charlie@charliesomerville.com"]
|
11
|
+
spec.description = %q{A library for handling FLV files}
|
12
|
+
spec.summary = %q{Ruby library for parsing and handling FLV files}
|
13
|
+
spec.homepage = "https://github.com/envato/flv"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files`.split($/)
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_development_dependency "bundler", "~> 1.3"
|
22
|
+
spec.add_development_dependency "rake"
|
23
|
+
spec.add_development_dependency "rspec", "~> 2.13.0"
|
24
|
+
end
|
data/lib/flv.rb
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
require "flv/version"
|
2
|
+
require "flv/stream_reader"
|
3
|
+
require "flv/format_error"
|
4
|
+
require "flv/tag"
|
5
|
+
|
6
|
+
class FLV
|
7
|
+
def self.read(path)
|
8
|
+
new(File.open(path, "rb"))
|
9
|
+
end
|
10
|
+
|
11
|
+
AUDIO_FLAG = 0x04
|
12
|
+
VIDEO_FLAG = 0x01
|
13
|
+
|
14
|
+
attr_reader :flags
|
15
|
+
|
16
|
+
def initialize(io)
|
17
|
+
@io = io
|
18
|
+
@reader = StreamReader.new(io)
|
19
|
+
read_header
|
20
|
+
read_useless_footer
|
21
|
+
end
|
22
|
+
|
23
|
+
def audio?
|
24
|
+
(flags & AUDIO_FLAG).nonzero?
|
25
|
+
end
|
26
|
+
|
27
|
+
def video?
|
28
|
+
(flags & VIDEO_FLAG).nonzero?
|
29
|
+
end
|
30
|
+
|
31
|
+
def tags
|
32
|
+
@tags ||= each_tag.to_a
|
33
|
+
end
|
34
|
+
|
35
|
+
def each_tag
|
36
|
+
return enum_for(:each_tag) unless block_given?
|
37
|
+
|
38
|
+
until @io.eof?
|
39
|
+
yield Tag.read(@io)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
def read_header
|
45
|
+
raise FormatError, "bad header" unless @reader.read_bytes(3) == "FLV"
|
46
|
+
raise FormatError, "bad version" unless @reader.read_byte == 1
|
47
|
+
@flags = @reader.read_byte
|
48
|
+
raise FormatError, "bad header size" unless @reader.read_uint32_be == 9
|
49
|
+
end
|
50
|
+
|
51
|
+
def read_useless_footer
|
52
|
+
raise FormatError, "expected useless footer to be zero" unless @reader.read_uint32_be == 0
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,107 @@
|
|
1
|
+
# coding: BINARY
|
2
|
+
require "flv/stream_reader"
|
3
|
+
require "flv/format_error"
|
4
|
+
|
5
|
+
class FLV
|
6
|
+
class ScriptDataParser
|
7
|
+
def initialize(str)
|
8
|
+
@data_reader = StreamReader.for_string(str.dup.force_encoding("BINARY"))
|
9
|
+
end
|
10
|
+
|
11
|
+
def parse
|
12
|
+
Hash[until_terminator(TERMINATOR).map {
|
13
|
+
[expect_script_data_string, read_script_data_value]
|
14
|
+
}]
|
15
|
+
end
|
16
|
+
|
17
|
+
def parse_single_value
|
18
|
+
read_script_data_value
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
def read_script_data_value
|
23
|
+
case type = @data_reader.read_byte
|
24
|
+
when 0 then read_script_data_number
|
25
|
+
when 1 then read_script_data_boolean
|
26
|
+
when 2 then read_script_data_string
|
27
|
+
when 3 then read_script_data_object
|
28
|
+
when 4 then read_script_data_movie_clip
|
29
|
+
when 5 then read_script_data_null
|
30
|
+
when 6 then read_script_data_undefined
|
31
|
+
when 7 then read_script_data_reference
|
32
|
+
when 8 then read_script_data_array
|
33
|
+
when 10 then read_script_data_strict_array
|
34
|
+
when 11 then read_script_data_date
|
35
|
+
when 12 then read_script_data_long_string
|
36
|
+
else
|
37
|
+
raise FormatError, "unknown type #{type}"
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def read_script_data_number
|
42
|
+
@data_reader.read_double_be
|
43
|
+
end
|
44
|
+
|
45
|
+
def read_script_data_string
|
46
|
+
length = @data_reader.read_uint16_be
|
47
|
+
@data_reader.read_bytes(length)
|
48
|
+
end
|
49
|
+
|
50
|
+
def read_script_data_boolean
|
51
|
+
!@data_reader.read_byte.zero?
|
52
|
+
end
|
53
|
+
|
54
|
+
TERMINATOR = "\x00\x00\x09".force_encoding("BINARY").freeze
|
55
|
+
|
56
|
+
def read_script_data_object
|
57
|
+
Hash[until_terminator(TERMINATOR).map {
|
58
|
+
[read_script_data_string, read_script_data_value]
|
59
|
+
}]
|
60
|
+
end
|
61
|
+
|
62
|
+
def read_script_data_array
|
63
|
+
# this value is ignored on purpose
|
64
|
+
approx_array_length = @data_reader.read_uint32_be
|
65
|
+
|
66
|
+
Hash[until_terminator(TERMINATOR).map {
|
67
|
+
read_script_data_variable
|
68
|
+
}]
|
69
|
+
end
|
70
|
+
|
71
|
+
def read_script_data_variable
|
72
|
+
[read_script_data_string, read_script_data_value]
|
73
|
+
end
|
74
|
+
|
75
|
+
def read_script_data_strict_array
|
76
|
+
count = @data_reader.read_uint32_be
|
77
|
+
count.times.map { read_script_data_value }
|
78
|
+
end
|
79
|
+
|
80
|
+
def read_script_data_date
|
81
|
+
time = @data_reader.read_double_be
|
82
|
+
utc_offset_minutes = @data_reader.read_sint16_be
|
83
|
+
Time.new(1970, 1, 1, 0, 0, 0, utc_offset_minutes * 60) + time
|
84
|
+
rescue ArgumentError
|
85
|
+
nil
|
86
|
+
end
|
87
|
+
|
88
|
+
def expect_script_data_string
|
89
|
+
read_script_data_value.tap do |value|
|
90
|
+
unless value.is_a?(String)
|
91
|
+
raise FormatError, "expected string, have #{value.inspect}"
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def until_terminator(term)
|
97
|
+
return enum_for(:until_terminator, term) unless block_given?
|
98
|
+
sz = term.bytesize
|
99
|
+
loop do
|
100
|
+
bytes = @data_reader.read_bytes(sz)
|
101
|
+
return if bytes == term or bytes.nil?
|
102
|
+
@data_reader.seek(-sz)
|
103
|
+
yield
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# coding: BINARY
|
2
|
+
|
3
|
+
require "stringio"
|
4
|
+
|
5
|
+
class FLV
|
6
|
+
class StreamReader
|
7
|
+
def initialize(io)
|
8
|
+
@io = io
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.for_string(str)
|
12
|
+
new(StringIO.new(str))
|
13
|
+
end
|
14
|
+
|
15
|
+
def eof?
|
16
|
+
@io.eof?
|
17
|
+
end
|
18
|
+
|
19
|
+
def seek(offset)
|
20
|
+
@io.seek(offset, IO::SEEK_CUR)
|
21
|
+
end
|
22
|
+
|
23
|
+
def read_bytes(n)
|
24
|
+
@io.read(n)
|
25
|
+
end
|
26
|
+
|
27
|
+
def read_byte
|
28
|
+
@io.readbyte
|
29
|
+
end
|
30
|
+
|
31
|
+
def read_uint32_be
|
32
|
+
read_bytes(4).unpack("L>").first
|
33
|
+
end
|
34
|
+
|
35
|
+
def read_uint24_be
|
36
|
+
"\x00#{read_bytes(3)}".unpack("L>").first
|
37
|
+
end
|
38
|
+
|
39
|
+
def read_uint16_be
|
40
|
+
read_bytes(2).unpack("S>").first
|
41
|
+
end
|
42
|
+
|
43
|
+
def read_sint16_be
|
44
|
+
read_bytes(2).unpack("s>").first
|
45
|
+
end
|
46
|
+
|
47
|
+
def read_uint32_weird_endian
|
48
|
+
lower = read_uint24_be
|
49
|
+
higher = read_byte
|
50
|
+
lower | (higher << 24)
|
51
|
+
end
|
52
|
+
|
53
|
+
def read_double_be
|
54
|
+
read_bytes(8).unpack("G").first
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
data/lib/flv/tag.rb
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
require "flv/stream_reader"
|
2
|
+
require "flv/format_error"
|
3
|
+
require "flv/tag/data"
|
4
|
+
require "flv/tag/audio"
|
5
|
+
require "flv/tag/video"
|
6
|
+
|
7
|
+
class FLV
|
8
|
+
class Tag
|
9
|
+
METADATA_TYPE = 0x12
|
10
|
+
AUDIO_TYPE = 0x08
|
11
|
+
VIDEO_TYPE = 0x09
|
12
|
+
|
13
|
+
attr_reader :length, :timestamp, :raw_data, :footer
|
14
|
+
|
15
|
+
def self.read(io)
|
16
|
+
case io.readbyte
|
17
|
+
when METADATA_TYPE then Data.new(io)
|
18
|
+
when AUDIO_TYPE then Audio.new(io)
|
19
|
+
when VIDEO_TYPE then Video.new(io)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def initialize(io)
|
24
|
+
@reader = StreamReader.new(io)
|
25
|
+
read
|
26
|
+
end
|
27
|
+
|
28
|
+
def inspect
|
29
|
+
"#<#{self.class}>"
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
def decode_raw_data
|
34
|
+
# implemented in subclasses
|
35
|
+
end
|
36
|
+
|
37
|
+
def read
|
38
|
+
read_header
|
39
|
+
read_raw_data
|
40
|
+
read_footer
|
41
|
+
decode_raw_data
|
42
|
+
end
|
43
|
+
|
44
|
+
def read_header
|
45
|
+
@length = @reader.read_uint24_be
|
46
|
+
@timestamp = @reader.read_uint32_weird_endian
|
47
|
+
raise FormatError, "bad stream id" unless @reader.read_uint24_be == 0
|
48
|
+
end
|
49
|
+
|
50
|
+
def read_raw_data
|
51
|
+
@raw_data = @reader.read_bytes(length)
|
52
|
+
end
|
53
|
+
|
54
|
+
def read_footer
|
55
|
+
@footer = @reader.read_uint32_be
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
data/lib/flv/tag/data.rb
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
require "flv/tag"
|
2
|
+
require "flv/stream_reader"
|
3
|
+
require "flv/format_error"
|
4
|
+
require "flv/script_data_parser"
|
5
|
+
|
6
|
+
class FLV
|
7
|
+
class Tag
|
8
|
+
class Data < Tag
|
9
|
+
def data
|
10
|
+
@data ||= ScriptDataParser.new(raw_data).parse
|
11
|
+
end
|
12
|
+
|
13
|
+
def inspect
|
14
|
+
"#<#{self.class} #{data.inspect}>"
|
15
|
+
end
|
16
|
+
|
17
|
+
def duration
|
18
|
+
meta_data["duration"]
|
19
|
+
end
|
20
|
+
|
21
|
+
def width
|
22
|
+
meta_data["width"]
|
23
|
+
end
|
24
|
+
|
25
|
+
def height
|
26
|
+
meta_data["height"]
|
27
|
+
end
|
28
|
+
|
29
|
+
def video_data_rate
|
30
|
+
meta_data["videodatarate"]
|
31
|
+
end
|
32
|
+
|
33
|
+
def frame_rate
|
34
|
+
meta_data["framerate"]
|
35
|
+
end
|
36
|
+
|
37
|
+
def video_codec_id
|
38
|
+
meta_data["videocodecid"]
|
39
|
+
end
|
40
|
+
|
41
|
+
def audio_data_rate
|
42
|
+
meta_data["audiodatarate"]
|
43
|
+
end
|
44
|
+
|
45
|
+
def audio_delay
|
46
|
+
meta_data["audiodelay"]
|
47
|
+
end
|
48
|
+
|
49
|
+
def audio_codec_id
|
50
|
+
meta_data["audiocodecid"]
|
51
|
+
end
|
52
|
+
|
53
|
+
def seek_to_end?
|
54
|
+
meta_data["canSeekToEnd"]
|
55
|
+
end
|
56
|
+
|
57
|
+
private
|
58
|
+
def meta_data
|
59
|
+
data["onMetaData"] || {}
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
data/lib/flv/version.rb
ADDED
Binary file
|
@@ -0,0 +1,105 @@
|
|
1
|
+
require "flv/script_data_parser"
|
2
|
+
|
3
|
+
describe FLV::ScriptDataParser do
|
4
|
+
let(:parser) { FLV::ScriptDataParser.new(@str) }
|
5
|
+
let(:value) { parser.parse_single_value }
|
6
|
+
|
7
|
+
context "numbers" do
|
8
|
+
it "parses the number as a big endian double precision float" do
|
9
|
+
@str = "\x00@^\xDD/\x1A\x9F\xBEw"
|
10
|
+
value.should == 123.456
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
context "booleans" do
|
15
|
+
it "parses a 0 byte as false" do
|
16
|
+
@str = "\x01\x00"
|
17
|
+
value.should == false
|
18
|
+
end
|
19
|
+
|
20
|
+
it "parses a 1 byte as true" do
|
21
|
+
@str = "\x01\x01"
|
22
|
+
value.should == true
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
context "strings" do
|
27
|
+
it "parses empty strings" do
|
28
|
+
@str = "\x02\x00\x00"
|
29
|
+
value.should == ""
|
30
|
+
end
|
31
|
+
|
32
|
+
it "parses strings < 255 chars" do
|
33
|
+
@str = "\x02\x00\x0bhello world"
|
34
|
+
value.should == "hello world"
|
35
|
+
end
|
36
|
+
|
37
|
+
it "parses strings > 255 chars (where endianness matters)" do
|
38
|
+
@str = "\x02\x02\x00#{"x"*512}"
|
39
|
+
value.should == "x" * 512
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
context "objects" do
|
44
|
+
it "parses empty objects" do
|
45
|
+
@str = "\x03\x00\x00\x09"
|
46
|
+
value.should == {}
|
47
|
+
end
|
48
|
+
|
49
|
+
it "parses objects as pairs of a non-value strings and a value" do
|
50
|
+
@str = "\x03\x00\x04name\x02\x00\x05value\x00\x00\x09"
|
51
|
+
value.should == { "name" => "value" }
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
context "arrays" do
|
56
|
+
it "parses empty 'arrays' as empty hashes" do
|
57
|
+
@str = "\x08\x00\x00\x00\x00\x00\x00\x09"
|
58
|
+
value.should == {}
|
59
|
+
end
|
60
|
+
|
61
|
+
it "ignores the length hint" do
|
62
|
+
@str = "\x08\x11\x22\x33\x44\x00\x00\x09"
|
63
|
+
value.should == {}
|
64
|
+
end
|
65
|
+
|
66
|
+
it "parses arrays as pairs of a non-value string and a value" do
|
67
|
+
@str = "\x08\x00\x00\x00\x00\x00\x04name\x02\x00\x05value\x00\x00\x09"
|
68
|
+
value.should == { "name" => "value" }
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
context "strict arrays" do
|
73
|
+
it "parses empty strict arrays" do
|
74
|
+
@str = "\x0a\x00\x00\x00\x00"
|
75
|
+
value.should == []
|
76
|
+
end
|
77
|
+
|
78
|
+
it "parses strict arrays with values" do
|
79
|
+
@str = "\x0a\x00\x00\x00\x03\x02\x00\x03foo\x02\x00\x03bar\x02\x00\x03baz"
|
80
|
+
value.should == ["foo", "bar", "baz"]
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
context "dates" do
|
85
|
+
it "can parse an empty date" do
|
86
|
+
@str = "\x0b\0\0\0\0\0\0\0\0\0\0"
|
87
|
+
value.should == Time.utc(1970, 1, 1, 0, 0, 0)
|
88
|
+
end
|
89
|
+
|
90
|
+
it "can parse an empty date in UTC+10" do
|
91
|
+
@str = "\x0b\0\0\0\0\0\0\0\0\x02X"
|
92
|
+
value.should == Time.new(1970, 1, 1, 0, 0, 0, 10 * 3600)
|
93
|
+
end
|
94
|
+
|
95
|
+
it "can parse today in UTC+10" do
|
96
|
+
@str = "\x0bA\xD4bB\xDE\x80\0\0\x02X"
|
97
|
+
value.should == Time.new(2013, 5, 7, 14, 11, 6, 10 * 3600)
|
98
|
+
end
|
99
|
+
|
100
|
+
it "can parse today in UTC-10" do
|
101
|
+
@str = "\x0bA\xD4bB\xDE\x80\0\0\xfd\xa8"
|
102
|
+
value.should == Time.new(2013, 5, 7, 14, 11, 6, -10 * 3600)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
# coding: BINARY
|
2
|
+
|
3
|
+
require "flv/stream_reader"
|
4
|
+
require "stringio"
|
5
|
+
|
6
|
+
describe FLV::StreamReader do
|
7
|
+
let(:string) { "\xde\xad\xbe\xef\x00\xfa\xca\xde" }
|
8
|
+
let(:io) { StringIO.new(string) }
|
9
|
+
subject { FLV::StreamReader.new(io) }
|
10
|
+
|
11
|
+
describe "#eof?" do
|
12
|
+
let(:val) { mock }
|
13
|
+
|
14
|
+
it "should delegate to the io" do
|
15
|
+
io.should_receive(:eof?).and_return(val)
|
16
|
+
subject.eof?.should == val
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
describe "#read_bytes" do
|
21
|
+
it "reads the specified number of bytes" do
|
22
|
+
subject.read_bytes(8).should == string
|
23
|
+
end
|
24
|
+
|
25
|
+
it "saves its spot in the stream" do
|
26
|
+
subject.read_bytes(4).should == "\xde\xad\xbe\xef"
|
27
|
+
subject.read_bytes(4).should == "\x00\xfa\xca\xde"
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
describe "#read_byte" do
|
32
|
+
it "returns fixnums" do
|
33
|
+
subject.read_byte.should == 0xde
|
34
|
+
subject.read_byte.should == 0xad
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
describe "#read_uint32_be" do
|
39
|
+
it "reads big endian unsigned 32 bit integers" do
|
40
|
+
subject.read_uint32_be.should == 0xdeadbeef
|
41
|
+
subject.read_uint32_be.should == 0x00facade
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
describe "#read_uint24_be" do
|
46
|
+
it "reads big endian unsigned 24 bit integers" do
|
47
|
+
subject.read_uint24_be.should == 0xdeadbe
|
48
|
+
subject.read_uint24_be.should == 0xef00fa
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
describe "#read_uint16_be" do
|
53
|
+
it "reads big endian unsigned 16 bit integers" do
|
54
|
+
subject.read_uint16_be.should == 0xdead
|
55
|
+
subject.read_uint16_be.should == 0xbeef
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
describe "#read_uint32_weird_endian" do
|
60
|
+
it "reads FLV's super weird mixed endian properly" do
|
61
|
+
subject.read_uint32_weird_endian.should == 0xefdeadbe
|
62
|
+
subject.read_uint32_weird_endian.should == 0xde00faca
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require "flv/tag/data"
|
2
|
+
|
3
|
+
describe FLV::Tag::Data do
|
4
|
+
context "integration test" do
|
5
|
+
let(:preview_flv) { File.open(File.expand_path("../../../files/preview.flv", __FILE__), "rb") }
|
6
|
+
subject { FLV.new(preview_flv).each_tag.first }
|
7
|
+
|
8
|
+
its(:duration) { should == 6.0 }
|
9
|
+
its(:width) { should == 640 }
|
10
|
+
its(:height) { should == 360 }
|
11
|
+
its(:video_data_rate) { should == 700 }
|
12
|
+
its(:frame_rate) { should == 30 }
|
13
|
+
its(:video_codec_id) { should == 4 }
|
14
|
+
its(:audio_data_rate) { should == 128 }
|
15
|
+
its(:audio_delay) { should == 0.038 }
|
16
|
+
its(:audio_codec_id) { should == 2 }
|
17
|
+
its(:seek_to_end?) { should == true }
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# coding: BINARY
|
2
|
+
|
3
|
+
require "flv/tag"
|
4
|
+
require "stringio"
|
5
|
+
|
6
|
+
describe FLV::Tag do
|
7
|
+
describe ".read" do
|
8
|
+
let(:io) { StringIO.new(string) }
|
9
|
+
|
10
|
+
context "reading a metadata tag" do
|
11
|
+
let(:string) { "\x12" }
|
12
|
+
|
13
|
+
it "creates a FLV::Tag::Data" do
|
14
|
+
FLV::Tag::Data.should_receive(:new).with(io)
|
15
|
+
FLV::Tag.read(io)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
context "reading an audio tag" do
|
20
|
+
let(:string) { "\x08" }
|
21
|
+
|
22
|
+
it "creates a FLV::Tag::Data" do
|
23
|
+
FLV::Tag::Audio.should_receive(:new).with(io)
|
24
|
+
FLV::Tag.read(io)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
context "reading a video tag" do
|
29
|
+
let(:string) { "\x09" }
|
30
|
+
|
31
|
+
it "creates a FLV::Tag::Data" do
|
32
|
+
FLV::Tag::Video.should_receive(:new).with(io)
|
33
|
+
FLV::Tag.read(io)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
describe "tag parsing" do
|
39
|
+
let(:string) { "\x00\x00\x0b\xad\xbe\xef\xde\x00\x00\x00hello world\x00\x00\x00\x15" }
|
40
|
+
subject { FLV::Tag.new(StringIO.new(string)) }
|
41
|
+
|
42
|
+
its(:length) { should == 11 }
|
43
|
+
its(:timestamp) { should == 0xdeadbeef }
|
44
|
+
its(:raw_data) { should == "hello world" }
|
45
|
+
its(:footer) { should == 21 }
|
46
|
+
end
|
47
|
+
end
|
data/spec/flv_spec.rb
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
require "flv"
|
2
|
+
|
3
|
+
describe FLV do
|
4
|
+
context "integration test" do
|
5
|
+
let(:preview_flv) { File.open(File.expand_path("../files/preview.flv", __FILE__), "rb") }
|
6
|
+
subject { FLV.new(preview_flv) }
|
7
|
+
|
8
|
+
its(:audio?) { should be_true }
|
9
|
+
its(:video?) { should be_true }
|
10
|
+
end
|
11
|
+
|
12
|
+
describe "#audio? and #video?" do
|
13
|
+
let(:flags) { 0 }
|
14
|
+
subject { FLV.allocate.tap do |flv| flv.stub(:flags => flags) end }
|
15
|
+
|
16
|
+
context "doesn't have any flags set" do
|
17
|
+
its(:audio?) { should be_false }
|
18
|
+
its(:video?) { should be_false }
|
19
|
+
end
|
20
|
+
|
21
|
+
context "has the audio flag set" do
|
22
|
+
let(:flags) { FLV::AUDIO_FLAG }
|
23
|
+
|
24
|
+
its(:audio?) { should be_true }
|
25
|
+
its(:video?) { should be_false }
|
26
|
+
end
|
27
|
+
|
28
|
+
context "has the video flag set" do
|
29
|
+
let(:flags) { FLV::VIDEO_FLAG }
|
30
|
+
|
31
|
+
its(:audio?) { should be_false }
|
32
|
+
its(:video?) { should be_true }
|
33
|
+
end
|
34
|
+
|
35
|
+
context "has the audio and video flags set" do
|
36
|
+
let(:flags) { FLV::AUDIO_FLAG | FLV::VIDEO_FLAG }
|
37
|
+
|
38
|
+
its(:audio?) { should be_true }
|
39
|
+
its(:video?) { should be_true }
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
metadata
CHANGED
@@ -1,23 +1,88 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: flv
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Charlie Somerville
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2013-05-
|
12
|
-
dependencies:
|
13
|
-
|
14
|
-
|
11
|
+
date: 2013-05-09 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ~>
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.3'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ~>
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.3'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - '>='
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - '>='
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ~>
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 2.13.0
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ~>
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 2.13.0
|
55
|
+
description: A library for handling FLV files
|
56
|
+
email:
|
57
|
+
- charlie@charliesomerville.com
|
15
58
|
executables: []
|
16
59
|
extensions: []
|
17
60
|
extra_rdoc_files: []
|
18
|
-
files:
|
19
|
-
|
20
|
-
|
61
|
+
files:
|
62
|
+
- .gitignore
|
63
|
+
- Gemfile
|
64
|
+
- LICENSE.txt
|
65
|
+
- README.md
|
66
|
+
- Rakefile
|
67
|
+
- flv.gemspec
|
68
|
+
- lib/flv.rb
|
69
|
+
- lib/flv/format_error.rb
|
70
|
+
- lib/flv/script_data_parser.rb
|
71
|
+
- lib/flv/stream_reader.rb
|
72
|
+
- lib/flv/tag.rb
|
73
|
+
- lib/flv/tag/audio.rb
|
74
|
+
- lib/flv/tag/data.rb
|
75
|
+
- lib/flv/tag/video.rb
|
76
|
+
- lib/flv/version.rb
|
77
|
+
- spec/files/preview.flv
|
78
|
+
- spec/flv/script_data_parser_spec.rb
|
79
|
+
- spec/flv/stream_reader_spec.rb
|
80
|
+
- spec/flv/tag/data_spec.rb
|
81
|
+
- spec/flv/tag_spec.rb
|
82
|
+
- spec/flv_spec.rb
|
83
|
+
homepage: https://github.com/envato/flv
|
84
|
+
licenses:
|
85
|
+
- MIT
|
21
86
|
metadata: {}
|
22
87
|
post_install_message:
|
23
88
|
rdoc_options: []
|
@@ -35,9 +100,15 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
35
100
|
version: '0'
|
36
101
|
requirements: []
|
37
102
|
rubyforge_project:
|
38
|
-
rubygems_version: 2.0.
|
103
|
+
rubygems_version: 2.0.0
|
39
104
|
signing_key:
|
40
105
|
specification_version: 4
|
41
|
-
summary:
|
42
|
-
test_files:
|
106
|
+
summary: Ruby library for parsing and handling FLV files
|
107
|
+
test_files:
|
108
|
+
- spec/files/preview.flv
|
109
|
+
- spec/flv/script_data_parser_spec.rb
|
110
|
+
- spec/flv/stream_reader_spec.rb
|
111
|
+
- spec/flv/tag/data_spec.rb
|
112
|
+
- spec/flv/tag_spec.rb
|
113
|
+
- spec/flv_spec.rb
|
43
114
|
has_rdoc:
|