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 +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/lib/flv/event.rb ADDED
@@ -0,0 +1,40 @@
1
+ module FLV
2
+ # The body of a tag containing meta data, cue points or last second information
3
+ # These behave like a Hash. The keys should be symbols
4
+ # while the values can be about any type, including arrays and hashes.
5
+ class Event < Hash
6
+ include Packable
7
+ include Body
8
+ TYPICAL_EVENTS = [:onMetaData, :onCuePoint, :onCaption, :onCaptionInfo, :onLastSecond, :onEvent]
9
+ attr_accessor :event
10
+
11
+ def initialize(event = :onMetaData, h = {})
12
+ self.replace h
13
+ self.event = event.to_sym
14
+ end
15
+
16
+ def read_packed(io,options) #:nodoc:
17
+ len = io.pos_change do
18
+ evt, h = io >>:flv_value >>:flv_value
19
+ self.event = evt.to_sym
20
+ replace h
21
+ end
22
+ FLV::Util.double_check :size, options[:bytes], len
23
+ end
24
+
25
+ def write_packed(io,*) #:nodoc:
26
+ io << [event.to_s, :flv_value] << [self, :flv_value]
27
+ end
28
+
29
+ def debug(format, *)
30
+ format.values(:event => event)
31
+ format.values(self)
32
+ end
33
+
34
+ def is?(what)
35
+ event.to_s == what.to_s || super
36
+ end
37
+
38
+ alias_method :similar_to?, :==
39
+ end
40
+ end
data/lib/flv/file.rb ADDED
@@ -0,0 +1,41 @@
1
+ module FLV
2
+ module File
3
+ def each(*arg, &block)
4
+ return super unless arg.empty?
5
+ return to_enum unless block_given?
6
+ h= read(Header)
7
+ yield h
8
+ super(Tag, &block)
9
+ end
10
+
11
+ def self.open(*arg)
12
+ file = ::File.open(*arg)
13
+ begin
14
+ file = return_value = file.packed.extend(File)
15
+ rescue Exception
16
+ file.close
17
+ raise
18
+ end
19
+ begin
20
+ return_value = yield(file)
21
+ ensure
22
+ file.close
23
+ end if block_given?
24
+ return_value
25
+ end
26
+
27
+ def self.read(portname, *arg)
28
+ open(portname) do |f|
29
+ return f.to_a if arg.empty?
30
+ n, offset = arg.first, arg[1] || 0
31
+ f.each.first(n+offset)[offset, offset+n-1]
32
+ end
33
+ end
34
+
35
+ def self.foreach(portname, *, &block)
36
+ open(portname) do |f|
37
+ f.each(&block)
38
+ end
39
+ end
40
+ end
41
+ end
data/lib/flv/header.rb ADDED
@@ -0,0 +1,37 @@
1
+ module FLV
2
+ # Represents the header chunk present at the very beginning of any FLV file.
3
+ class Header
4
+ include Base
5
+ attr_accessor :version, :has_audio, :has_video, :extra, :path
6
+
7
+ FLV_SIGNATURE = [String, {:bytes => 3}].freeze
8
+ SIGNATURE = 'FLV'
9
+ MIN_OFFSET = 9
10
+ FLAGS = { :has_video => 1, :has_audio => 4 }.freeze
11
+
12
+ def read_packed(io, *) #:nodoc:
13
+ signature, self.version, type_flags, offset = \
14
+ io >> FLV_SIGNATURE >> :char >> :char >> :unsigned_long
15
+
16
+ raise RuntimeError.new("typeflags is #{signature}, #{version}, #{type_flags}, #{offset}") unless Fixnum === type_flags
17
+
18
+ FLAGS.each {|flag,mask| send("#{flag}=", type_flags & mask > 0) }
19
+ self.extra = io.read(offset - MIN_OFFSET)
20
+ ignore_PreviousTagSize0 = io.read :unsigned_long
21
+ self.path = io.try :path
22
+ raise IOError("Wrong Signature (#{signature} instead of #{SIGNATURE})") unless SIGNATURE == signature
23
+ end
24
+
25
+ def write_packed(io, *) #:nodoc:
26
+ self.extra ||= ""
27
+ type_flags = FLAGS.sum{|flag, mask | send(flag) ? mask : 0}
28
+ io << SIGNATURE << [self.version || 1, :char] << [type_flags, :char] <<
29
+ [MIN_OFFSET + self.extra.length, :unsigned_long] << self.extra << [0, :unsigned_long]
30
+ end
31
+
32
+ def debug(format, *)
33
+ format.header("Header", path)
34
+ format.values(to_h.tap{|h| h.delete(:path)})
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,140 @@
1
+ require 'singleton'
2
+
3
+ module FLV
4
+ class Event < Hash ; end
5
+ # FLV files can contain structured data. This modules makes it easy to (un)pack that data.
6
+ # The packing option +flv_value+ can (un)pack any kind of variable.
7
+ # It corresponds to +SCRIPTDATAVALUE+ in the official FLV file format spec.
8
+ # Implementation Note:
9
+ # +flv_value+ is actually simply a wrapper that will (un)pack the type of variable;
10
+ # the actual data is (un)packed with the format +flv+, which is independently defined for each type
11
+ module Packing
12
+
13
+ # A special kind of value used to signify the end of a list (implemented at the end)
14
+ class EndOfList # :nodoc:
15
+ end
16
+
17
+ #=== Top-level :flv_value filter:
18
+
19
+ TYPE_TO_CLASS = Hash.new do |h, type|
20
+ #EndOfList
21
+ raise IOError, "Invalid type for a flash variable. #{type.inspect} is not in #{h.keys.sort.inspect}" #todo: handle error for corrupted da
22
+ end.merge!(
23
+ 0 => Numeric ,
24
+ 1 => TrueClass , # There really should be a Boolean class!
25
+ 2 => String ,
26
+ 3 => Hash ,
27
+ 8 => [Hash, :flv_with_size],
28
+ 9 => EndOfList ,
29
+ 10 => Array ,
30
+ 11 => Time
31
+ ).freeze
32
+
33
+ CLASS_TO_TYPE = Hash.new do |h, klass|
34
+ h[klass] = h[klass.superclass] # Makes it such that CLASS_TO_TYPE[Fixnum] = CLASS_TO_TYPE[Integer]
35
+ end.merge!(TYPE_TO_CLASS.invert).merge!(
36
+ Event => TYPE_TO_CLASS.key([Hash, :flv_with_size]), # Write Events as hashes with size
37
+ FalseClass => TYPE_TO_CLASS.key(TrueClass)
38
+ )
39
+
40
+ # Read/write the type and (un)pack the actual data with :flv
41
+ Object.packers.set(:flv_value) do |packer|
42
+ packer.write do |io|
43
+ type_nb = CLASS_TO_TYPE[self.class]
44
+ klass, format = TYPE_TO_CLASS[type_nb]
45
+ io << [type_nb, :char] << [self, format || :flv]
46
+ end
47
+ packer.read do |io|
48
+ klass, format = TYPE_TO_CLASS[io.read(:char)]
49
+ io.read([klass, format || :flv])
50
+ end
51
+ end
52
+
53
+ #=== For each basic type, the :flv filter (un)packs the actual data:
54
+
55
+ Numeric.packers.set(:flv) do |packer|
56
+ # Both Integers and Floats are packed as doubles
57
+ packer.write {|io| io << [to_f, :double] }
58
+ packer.read do |io|
59
+ n = io.read(:double)
60
+ n.to_i == n ? n.to_i : n
61
+ end
62
+ end
63
+
64
+ [TrueClass, FalseClass].each do |klass|
65
+ klass.packers.set(:flv) do |packer|
66
+ packer.write {|io| io << [self ? 1 : 0, :char] }
67
+ packer.read {|io| io.read(:char) != 0 }
68
+ end
69
+ end
70
+
71
+ String.packers.set(:flv) do |packer|
72
+ packer.write {|io| io << [length, :unsigned_short] << self }
73
+ packer.read {|io| io.read(io.read(:unsigned_short)) }
74
+ end
75
+
76
+ Array.packers.set(:flv) do |packer|
77
+ packer.write do |io|
78
+ io << [length, :unsigned_long]
79
+ each do |value|
80
+ io << [value, :flv_value]
81
+ end
82
+ end
83
+ packer.read do |io|
84
+ nb = io.read(:unsigned_long)
85
+ io.each(:flv_value).take(nb)
86
+ end
87
+ end
88
+
89
+ # The default format for hashes has a useless hint for the size
90
+ # This filter simply acts as a wrapper for the :flv_without_size filter
91
+ Hash.packers.set(:flv_with_size) do |packer|
92
+ packer.write do |io|
93
+ io << [length, :unsigned_long] << [self, :flv]
94
+ end
95
+ packer.read do |io|
96
+ ignore_length_hint = io >> :unsigned_long
97
+ io.read [Hash, :flv]
98
+ end
99
+ end
100
+
101
+ Hash.packers.set(:flv) do |packer|
102
+ packer.write do |io|
103
+ each do |key, value|
104
+ io << [key.to_s, :flv] << [value, :flv_value]
105
+ end
106
+ io << ["", :flv] << [EndOfList.instance, :flv_value]
107
+ end
108
+ packer.read do |io|
109
+ Hash[
110
+ io.each([String, :flv], :flv_value).
111
+ take_while {|str, val| val != EndOfList.instance }.
112
+ map{|k,v| [k.to_sym, v]}
113
+ ]
114
+ end
115
+ end
116
+
117
+ Time.packers.set(:flv) do |packer|
118
+ packer.write {|io| io << [to_f * 1000, :double] << [Time.now.gmtoff / 60, :short] }
119
+ packer.read do |io|
120
+ seconds, zone = io >> :double >> :short
121
+ Time.at((seconds / 1000).to_i) + (zone * 60) - Time.now.gmtoff
122
+ end
123
+ end
124
+
125
+ class EndOfList # :nodoc:
126
+ include Singleton, Packable
127
+ packers.set :flv, {}
128
+
129
+ def write_packed(*)
130
+ # no data to write
131
+ end
132
+
133
+ def self.read_packed(*)
134
+ self.instance
135
+ end
136
+ end
137
+
138
+ Integer.packers.set :unsigned_24bits, :bytes => 3, :signed => false, :endian => :big
139
+ end #module Packing
140
+ end #module FLV
data/lib/flv/tag.rb ADDED
@@ -0,0 +1,62 @@
1
+ module FLV
2
+ # FLV files consists of a single header and a collection of Tags.
3
+ # Tags all have a timestamp and a body; this body can be Audio, Video, or Event.
4
+ class Tag
5
+ include Base
6
+ attr_accessor :body, :timestamp
7
+
8
+ def timestamp=(t)
9
+ @timestamp = Timestamp.try_convert(t)
10
+ end
11
+
12
+ def initialize(timestamp = 0, body = nil)
13
+ self.body = body.instance_of?(Hash) ? Event.new(:onMetaData, body) : body
14
+ self.timestamp = timestamp
15
+ end
16
+
17
+ CLASS_CODE = {
18
+ 8 => Audio,
19
+ 9 => Video,
20
+ 18 => Event
21
+ }.freeze
22
+
23
+ def write_packed(io, *) #:nodoc
24
+ packed_body = @body.pack
25
+ len = io.pos_change do
26
+ io << [CLASS_CODE.key(self.body.class), :char] \
27
+ << [packed_body.length, :unsigned_24bits] \
28
+ << [@timestamp.in_milliseconds, :unsigned_24bits] \
29
+ << [@timestamp.in_milliseconds >>24, :char] \
30
+ << [streamid=0, :unsigned_24bits] \
31
+ << packed_body
32
+ end
33
+ io << [len, :unsigned_long]
34
+ end
35
+
36
+ def read_packed(io, options) #:nodoc
37
+ len = io.pos_change do
38
+ code, body_len, timestamp_in_ms, timestamp_in_ms_ext, streamid =
39
+ io >>:char >>:unsigned_24bits >>:unsigned_24bits >>:char >>:unsigned_24bits
40
+ @timestamp = Timestamp.in_milliseconds(timestamp_in_ms + (timestamp_in_ms_ext << 24))
41
+ @body = io.read CLASS_CODE[code] || Body, :bytes => body_len
42
+ end
43
+ FLV::Util.double_check :size, len, io.read(:unsigned_long) #todo
44
+ end
45
+
46
+ def debug(format, compared_with = nil) #:nodoc
47
+ format.header("#{timestamp} ", @body.title)
48
+ @body.debug(format) unless compared_with && @body.similar_to?(compared_with.body)
49
+ end
50
+
51
+ def is?(what) #:nodoc
52
+ super || body.is?(what)
53
+ end
54
+
55
+ def method_missing(*arg, &block)
56
+ super
57
+ rescue NoMethodError
58
+ body.send(*arg, &block)
59
+ end
60
+
61
+ end
62
+ end
@@ -0,0 +1,124 @@
1
+ require 'delegate'
2
+ require 'optparse'
3
+
4
+ module FLV
5
+ class Timestamp < DelegateClass(Float)
6
+ def initialize(ms=0)
7
+ raise ArgumentError unless ms.is_a? Numeric
8
+ super
9
+ end
10
+
11
+ # Returns [hours, minutes, seconds, milliseconds]
12
+ def to_a
13
+ n = in_milliseconds
14
+ [1000,60,60].map do |div|
15
+ val = n % div
16
+ n /= div
17
+ val
18
+ end.reverse.unshift(n)
19
+ end
20
+
21
+ # Returns "HH:MM:SS.MMMM" like "1:23:45.678" or "1:00.000" for 1 minute.
22
+ def to_s
23
+ return "" if self == INFINITY
24
+ nbs = to_a.drop_while(&:zero?)
25
+ ms = nbs.pop || 0
26
+ first = nbs.shift || 0
27
+ sprintf("%d%s.%03d", first, nbs.map{|n| sprintf(":%02d",n)}.join, ms)
28
+ end
29
+
30
+ def in_seconds
31
+ to_f
32
+ end
33
+
34
+ def in_milliseconds
35
+ (self * 1000).round
36
+ end
37
+
38
+ def widen(amount)
39
+ TimestampRange.new(self,self).widen(amount)
40
+ end
41
+
42
+ def self.in_milliseconds(ms)
43
+ Timestamp.new(ms/1000.0)
44
+ end
45
+
46
+ def self.in_seconds(s)
47
+ new s
48
+ end
49
+
50
+ def self.try_convert(s, if_empty_string = 0)
51
+ case s
52
+ when Timestamp
53
+ s
54
+ when Numeric
55
+ new s
56
+ when ""
57
+ new if_empty_string
58
+ when REGEXP
59
+ h, m, s, ms = Regexp.last_match.captures
60
+ ms &&= ms.ljust(3,'0')[0,3] # e.g. 1.23 => 1.230
61
+ h, m = m, h if h and h.end_with?(":") and m.nil?
62
+ h, m, s, ms = [h, m, s, ms].map{|n| (n || 0).to_i}
63
+ in_seconds ((h*60+m)*60+s)+ms/1000.0
64
+ end
65
+ end
66
+
67
+ REGEXP = /^(\d*[h:])?(\d*[m:])?(\d*)\.?(\d*)$/.freeze
68
+ end # Timestamp
69
+
70
+ INFINITY = 1/0.0
71
+ class TimestampRange < Range
72
+ core = Timestamp::REGEXP.source.gsub(/[\$\^\(\)]/,"")
73
+ REGEXP = Regexp.new("^(#{core})-(#{core})$").freeze
74
+
75
+ def to_s
76
+ "#{self.begin}-#{self.end}"
77
+ end
78
+
79
+ def initialize(from, to, exclusive=false)
80
+ super(Timestamp.try_convert(from), Timestamp.try_convert(to, INFINITY), exclusive)
81
+ end
82
+
83
+ def in_seconds
84
+ Range.new(self.begin.in_seconds, self.end.in_seconds, self.exclude_end?)
85
+ end
86
+
87
+ def in_milliseconds
88
+ Range.new(self.begin.in_milliseconds, self.end.in_milliseconds, self.exclude_end?)
89
+ end
90
+
91
+ def self.try_convert(s)
92
+ case s
93
+ when Range
94
+ new s.begin, s.end
95
+ when TimestampRange
96
+ s
97
+ when REGEXP
98
+ new *Regexp.last_match.captures
99
+ else
100
+ p "Can't convert #{s}"
101
+ end
102
+ end
103
+
104
+ def widen(amount)
105
+ TimestampRange.new [self.begin - amount, 0].max, self.end + amount, self.exclude_end?
106
+ end
107
+ end # TimestampRange
108
+ OptionParser.accept(TimestampRange, TimestampRange::REGEXP) {|str,from,to| TimestampRange.try_convert(str)}
109
+
110
+
111
+ class TimestampOrTimestampRange # :nodoc:
112
+ core = Timestamp::REGEXP.source.gsub(/[\$\^\(\)]/,"")
113
+ REGEXP = Regexp.new("^(#{core})-(#{core})|(#{core})$").freeze
114
+ end # TimestampOrTimestampRange
115
+ OptionParser.accept(TimestampOrTimestampRange, TimestampOrTimestampRange::REGEXP) do |str,from, to, from_to|
116
+ (from_to ? Timestamp : TimestampRange).try_convert(str)
117
+ end
118
+ end
119
+
120
+ class Range # :nodoc:
121
+ def ==(r) # Override built-in == because of bug in Ruby 1.8 & 1.9, see http://redmine.ruby-lang.org/issues/show/1165
122
+ self.begin == r.begin && self.end == r.end && self.exclude_end? == r.exclude_end?
123
+ end
124
+ end
@@ -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
data/lib/flv/video.rb ADDED
@@ -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
data/lib/flv.rb ADDED
@@ -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'
Binary file
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
+