flvtool2 1.0.6

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.
@@ -0,0 +1,93 @@
1
+ # Copyright (c) 2005 Norman Timmler (inlet media e.K., Hamburg, Germany)
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.
25
+
26
+
27
+ module FLV
28
+
29
+ class FLVAudioTag < FLVTag
30
+
31
+ UNCOMPRESSED = 0
32
+ ADPCM = 1
33
+ MP3 = 2
34
+ NELLYMOSER8KHZMONO = 5
35
+ NELLYMOSER = 6
36
+
37
+ MONO = 0
38
+ STEREO = 1
39
+
40
+ attr_reader :sound_format,
41
+ :sound_rate,
42
+ :sound_sample_size,
43
+ :sound_type
44
+
45
+ def after_initialize(new_object)
46
+ @tag_type = AUDIO
47
+ read_header
48
+ end
49
+
50
+ def name
51
+ 'Audio Tag'
52
+ end
53
+
54
+ def read_header
55
+ data_stream = AMFStringBuffer.new(@data)
56
+ bit_sequence = data_stream.read__STRING(1).unpack('B8').to_s
57
+
58
+ @sound_format = bit2uint(bit_sequence[0,4])
59
+ @sound_rate = case bit2uint(bit_sequence[4,2])
60
+ when 0
61
+ 5500
62
+ when 1
63
+ 11000
64
+ when 2
65
+ 22000
66
+ when 3
67
+ 44000
68
+ end
69
+ @sound_sample_size = case bit2uint(bit_sequence[6,1])
70
+ when 0
71
+ 8
72
+ when 1
73
+ 16
74
+ end
75
+ @sound_type = bit2uint(bit_sequence[7,1])
76
+
77
+ # Nellymoser 8kHz mono special case
78
+ if @sound_format == NELLYMOSER8KHZMONO
79
+ @sound_rate = 8000
80
+ @sound_type = MONO
81
+ end
82
+ end
83
+
84
+ def inspect
85
+ out = super
86
+ out << "sound_format: #{['Uncompressed', 'ADPCM', 'MP3', nil, nil, 'Nellymoser 8KHz mono', 'Nellymoser'][@sound_format]}"
87
+ out << "sound_rate: #{@sound_rate}"
88
+ out << "sound_sample_size: #{@sound_sample_size}"
89
+ out << "sound_type: #{['Mono', 'Stereo'][@sound_type]}"
90
+ out
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,141 @@
1
+ # Copyright (c) 2005 Norman Timmler (inlet media e.K., Hamburg, Germany)
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.
25
+
26
+
27
+ class Time
28
+ alias :to_str :to_s
29
+ DAY_NAME = [
30
+ 'Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'
31
+ ]
32
+ MONTH_NAME = [
33
+ 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
34
+ 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'
35
+ ]
36
+ def to_s
37
+ sprintf('%s %s %d %02d:%02d:%02d GMT',
38
+ DAY_NAME[wday],
39
+ MONTH_NAME[mon-1], day,
40
+ hour, min, sec, year) +
41
+ (
42
+ off = Time.now.gmtoff
43
+ sign = off < 0 ? '-' : '+'
44
+ sprintf('%s%02d%02d', sign, *(off.abs / 60).divmod(60))
45
+ ) +
46
+ (
47
+ sprintf(' %d', year)
48
+ )
49
+ end
50
+ def to_iso
51
+ offset = Time.now.gmtoff
52
+ strftime("%Y-%m-%dT%H:%m:%S#{sprintf('%s%02d:%02d', (offset < 0 ? '-' : '+'), *(offset.abs / 60).divmod(60))}")
53
+ end
54
+ end
55
+
56
+ class Float
57
+ alias :to_str :to_s
58
+ def to_s
59
+ to_f % 1 == 0 ? to_i.to_s : to_str
60
+ end
61
+ end
62
+
63
+ class IO
64
+ def read__UI8(position = nil)
65
+ seek position unless position.nil?
66
+ readchar
67
+ end
68
+
69
+ def read__UI16(position = nil)
70
+ seek position unless position.nil?
71
+ (readchar << 8) + readchar
72
+ end
73
+
74
+ def read__UI24(position = nil)
75
+ seek position unless position.nil?
76
+ (readchar << 16) + (readchar << 8) + readchar
77
+ end
78
+
79
+ def read__UI32(position = nil)
80
+ seek position unless position.nil?
81
+ (readchar << 24) + (readchar << 16) + (readchar << 8) + readchar
82
+ end
83
+
84
+ def read__STRING(length, position = nil)
85
+ seek position unless position.nil?
86
+ read length
87
+ end
88
+
89
+
90
+ def write__UI8(value, position = nil)
91
+ seek position unless position.nil?
92
+ write [value].pack('C')
93
+ end
94
+
95
+ def write__UI24(value, position = nil)
96
+ seek position unless position.nil?
97
+ write [value >> 16].pack('c')
98
+ write [(value >> 8) & 0xff].pack('c')
99
+ write [value & 0xff].pack('c')
100
+ end
101
+
102
+ def write__UI32(value, position = nil)
103
+ seek position unless position.nil?
104
+ write [value].pack('N')
105
+ end
106
+
107
+ def write__STRING(string, position = nil)
108
+ seek position unless position.nil?
109
+ write string
110
+ end
111
+ end
112
+
113
+ class ARGFWrapper
114
+ def readchar
115
+ ARGF.readchar
116
+ end
117
+
118
+ def read(length)
119
+ ARGF.read(length)
120
+ end
121
+
122
+ def read__UI8
123
+ readchar
124
+ end
125
+
126
+ def read__UI16
127
+ (readchar << 8) + readchar
128
+ end
129
+
130
+ def read__UI24
131
+ (readchar << 16) + (readchar << 8) + readchar
132
+ end
133
+
134
+ def read__UI32
135
+ (readchar << 24) + (readchar << 16) + (readchar << 8) + readchar
136
+ end
137
+
138
+ def read__STRING(length)
139
+ read length
140
+ end
141
+ end
@@ -0,0 +1,78 @@
1
+ # Copyright (c) 2005 Norman Timmler (inlet media e.K., Hamburg, Germany)
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.
25
+
26
+ require 'flv/amf_string_buffer'
27
+ require 'miyaml'
28
+
29
+ module FLV
30
+
31
+ class FLVMetaTag < FLVTag
32
+
33
+ attr_accessor :meta_data, :event
34
+
35
+ def after_initialize(new_object)
36
+ @tag_type = META
37
+ unless new_object
38
+ meta_data_stream = AMFStringBuffer.new(@data)
39
+ @event = meta_data_stream.read__AMF_data
40
+ @meta_data = meta_data_stream.read__AMF_data
41
+ else
42
+ @event = 'onMetaData'
43
+ @meta_data = {}
44
+ end
45
+ end
46
+
47
+ def name
48
+ "Meta Tag (#{@event})"
49
+ end
50
+
51
+ def add_meta_data(meta_data)
52
+ return nil if meta_data.nil?
53
+ @metadata.update meta_data
54
+ end
55
+
56
+ def data
57
+ meta_data_stream = AMFStringBuffer.new('')
58
+ meta_data_stream.write__AMF_string @event
59
+ meta_data_stream.write__AMF_data @meta_data
60
+ meta_data_stream.to_s
61
+ end
62
+
63
+ def [](key)
64
+ @meta_data[key]
65
+ end
66
+
67
+ def []=(key, value)
68
+ @meta_data[key] = value
69
+ end
70
+
71
+ def inspect
72
+ out = super
73
+ out << "event: #{@event}"
74
+ out << "meta_data:\n #{MiYAML.dump(@meta_data, :indent => 2, :boundaries => false)}"
75
+ out
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,492 @@
1
+ # Copyright (c) 2005 Norman Timmler (inlet media e.K., Hamburg, Germany)
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.
25
+
26
+ require 'flv/core_extensions'
27
+ require 'flv/tag'
28
+ require 'flv/audio_tag'
29
+ require 'flv/video_tag'
30
+ require 'flv/meta_tag'
31
+
32
+
33
+ module FLV
34
+
35
+ class FLVError < StandardError; end
36
+ class FLVTagError < FLVError; end
37
+ class FLVStreamError < FLVError; end
38
+
39
+ class FLVStream
40
+
41
+ attr_accessor :signatur,
42
+ :version,
43
+ :type_flags_audio,
44
+ :type_flags_video,
45
+ :tags,
46
+ :stream_log
47
+
48
+ def initialize(in_stream, out_stream = nil, stream_log = false)
49
+
50
+
51
+ @stream_log = stream_log ? (File.open('stream.log', File::CREAT|File::WRONLY|File::TRUNC) rescue AMFStringBuffer.new) : AMFStringBuffer.new
52
+ @in_stream = in_stream
53
+ @out_stream = out_stream || in_stream
54
+
55
+ unless eof?
56
+ begin
57
+ read_header
58
+ read_tags
59
+ rescue Object => e
60
+ log e
61
+ raise e
62
+ ensure
63
+ @stream_log.close
64
+ end
65
+ else
66
+ @version = 1
67
+ @type_flags_audio = false
68
+ @type_flags_video = false
69
+ @extra_data = ''
70
+ @tags = []
71
+ end
72
+ end
73
+
74
+
75
+ # general
76
+ def add_tags(tags, stick_on_framerate = true, overwrite = true)
77
+ tags = [tags] unless tags.kind_of? Array
78
+
79
+ tags.each do |tag|
80
+
81
+ # FIXME: Does not really work for video or audio tags, because tags are
82
+ # inserted next to same kind. Normally audio and video tags are
83
+ # alternating.
84
+ if stick_on_framerate && !framerate.nil? &&framerate != 0 && tag.timestamp % (1000 / framerate) != 0
85
+ raise FLVTagError, "Could not insert tag. Timestamp #{tag.timestamp} does not fit into framerate."
86
+ next
87
+ end
88
+
89
+ after_tag = @tags.detect { |_tag| _tag.timestamp >= tag.timestamp }
90
+
91
+ if after_tag.nil?
92
+ @tags << tag
93
+ next
94
+ end
95
+
96
+ if tag.timestamp == after_tag.timestamp && tag.class == after_tag.class
97
+ if tag.kind_of?(FLVMetaTag) && ( ( tag.event != after_tag.event ) || ( tag.event == after_tag.event && !overwrite ) )
98
+ @tags.insert( @tags.index(after_tag), tag )
99
+ else
100
+ @tags[@tags.index(after_tag)] = tag
101
+ end
102
+ else
103
+ @tags.insert( @tags.index(after_tag), tag )
104
+ end
105
+
106
+ empty_tag_type_cache
107
+ end
108
+
109
+ @tags
110
+ end
111
+
112
+ def cut(options = [])
113
+ @tags.delete_if { |tag| tag.timestamp < ( options[:in_point] || 0 ) || tag.timestamp > ( options[:out_point] || tags.last.timestamp ) }
114
+ if options[:collapse]
115
+ difference = @tags.first.timestamp
116
+ @tags.each { |tag| tag.timestamp -= difference }
117
+ end
118
+ empty_tag_type_cache
119
+ end
120
+
121
+ def find_nearest_keyframe_video_tag(position)
122
+ keyframe_video_tags.sort do |tag_a, tag_b|
123
+ (position - tag_a.timestamp).abs <=> (position - tag_b.timestamp).abs
124
+ end.first
125
+ end
126
+
127
+ def add_meta_tag(meta_data = {})
128
+ meta_tag = FLVMetaTag.new
129
+ meta_tag.event = 'onMetaData'
130
+
131
+ meta_tag['framerate'] = framerate
132
+ meta_tag['duration'] = duration
133
+ meta_tag['lasttimestamp'] = lasttimestamp
134
+ meta_tag['videosize'] = videosize
135
+ meta_tag['audiosize'] = audiosize
136
+ meta_tag['datasize'] = 0 # calculate after tag was added
137
+ meta_tag['filesize'] = 0 # calculate after tag was added
138
+ meta_tag['width'] = (width == 0 && on_meta_data_tag) ? on_meta_data_tag.meta_data['width'] : width
139
+ meta_tag['height'] = (height == 0 && on_meta_data_tag) ? on_meta_data_tag.meta_data['height'] : height
140
+ meta_tag['videodatarate'] = videodatarate
141
+ meta_tag['audiodatarate'] = audiodatarate
142
+ meta_tag['lastkeyframetimestamp'] = lastkeyframetimestamp
143
+ meta_tag['audiocodecid'] = audiocodecid
144
+ meta_tag['videocodecid'] = videocodecid
145
+ meta_tag['audiodelay'] = audiodelay
146
+ meta_tag['canSeekToEnd'] = canSeekToEnd
147
+ meta_tag['stereo'] = stereo
148
+ meta_tag['audiosamplerate'] = audiosamplerate
149
+ meta_tag['audiosamplesize'] = audiosamplesize
150
+ meta_tag['cuePoints'] = cue_points
151
+ meta_tag['keyframes'] = keyframes
152
+ meta_tag['hasVideo'] = has_video?
153
+ meta_tag['hasAudio'] = has_audio?
154
+ meta_tag['hasMetadata'] = true
155
+ meta_tag['hasCuePoints'] = has_cue_points?
156
+ meta_tag['hasKeyframes'] = has_keyframes?
157
+
158
+ meta_tag.meta_data.merge!(meta_data)
159
+
160
+ add_tags(meta_tag)
161
+
162
+ # recalculate values those need meta tag data size or presence
163
+ meta_tag['keyframes'] = keyframes
164
+ meta_tag['datasize'] = datasize
165
+ meta_tag['filesize'] = filesize
166
+ meta_tag['hasMetadata'] = has_meta_data?
167
+ end
168
+
169
+ def write
170
+
171
+ begin
172
+ @out_stream.seek( 0 )
173
+ rescue Object => e
174
+ end
175
+
176
+ write_header
177
+ write_tags
178
+
179
+ begin
180
+ @out_stream.truncate( @out_stream.pos )
181
+ rescue Object => e
182
+ end
183
+ end
184
+
185
+ def close
186
+ @in_stream.close
187
+ @out_stream.close
188
+ end
189
+
190
+
191
+ # views on tags
192
+
193
+ def empty_tag_type_cache
194
+ @video_tags_cache = nil
195
+ @keyframe_video_tags_cache = nil
196
+ @audio_tags_cache = nil
197
+ @meta_tags_cache = nil
198
+ @on_cue_point_tags_cache = nil
199
+ end
200
+
201
+ def video_tags
202
+ @video_tags_cache ||= @tags.find_all { |tag| tag.kind_of? FLVVideoTag }
203
+ end
204
+
205
+ def keyframe_video_tags
206
+ @keyframe_video_tags_cache ||= @tags.find_all do |tag|
207
+ tag.kind_of?(FLVVideoTag) && tag.frame_type == FLVVideoTag::KEYFRAME
208
+ end
209
+ end
210
+
211
+ def audio_tags
212
+ @audio_tags_cache ||= @tags.find_all { |tag| tag.kind_of? FLVAudioTag }
213
+ end
214
+
215
+ def meta_tags
216
+ @meta_tags_cache ||= @tags.find_all { |tag| tag.kind_of? FLVMetaTag }
217
+ end
218
+
219
+ def on_meta_data_tag
220
+ @tags.find { |tag| tag.kind_of?(FLVMetaTag) && tag.event == 'onMetaData' } # FIXME: Cannot be cached
221
+ end
222
+
223
+ def on_cue_point_tags
224
+ @on_cue_point_tags_cache ||= @tags.find_all { |tag| tag.kind_of?(FLVMetaTag) && tag.event == 'onCuePoint' } # FIXME: Cannot be cached
225
+ end
226
+
227
+ def has_video?
228
+ video_tags.size > 0
229
+ end
230
+
231
+ def has_audio?
232
+ audio_tags.size > 0
233
+ end
234
+
235
+ def has_meta_data?
236
+ !on_meta_data_tag.nil?
237
+ end
238
+
239
+ def has_cue_points?
240
+ on_cue_point_tags.size > 0
241
+ end
242
+
243
+ def has_keyframes?
244
+ keyframe_video_tags.size > 0
245
+ end
246
+
247
+ # meta data
248
+
249
+ # FIXME: Could be less complicate and run faster
250
+ def frame_sequence
251
+ return nil unless has_video?
252
+ raise(FLVStreamError, 'File has to contain at least 2 video tags to calculate frame sequence') if video_tags.length < 2
253
+
254
+ @frame_sequence ||=
255
+ begin
256
+ sequences = video_tags.collect do |tag| # find all sequences
257
+ video_tags[video_tags.index(tag) + 1].timestamp - tag.timestamp unless tag == video_tags.last
258
+ end.compact
259
+
260
+ uniq_sequences = (sequences.uniq - [0]).sort # remove 0 and try smallest intervall first
261
+
262
+ sequence_appearances = uniq_sequences.collect { |sequence| sequences.find_all { |_sequence| sequence == _sequence }.size } # count apperance of each sequence
263
+
264
+ uniq_sequences[ sequence_appearances.index( sequence_appearances.max ) ] # return the sequence that appears most
265
+ end
266
+ end
267
+
268
+ def framerate
269
+ return nil unless has_video?
270
+ frame_sequence == 0 ? 0 : 1000 / frame_sequence
271
+ end
272
+
273
+ def duration
274
+ lasttimestamp
275
+ end
276
+
277
+ def lasttimestamp
278
+ last_tag = if has_video?
279
+ video_tags.last
280
+ elsif has_audio?
281
+ audio_tags.last
282
+ else
283
+ tags.last
284
+ end
285
+ last_tag.timestamp.nil? ? 0 : last_tag.timestamp / 1000.0
286
+ end
287
+
288
+ def lastkeyframetimestamp
289
+ return nil unless has_video?
290
+ (keyframe_video_tags.last.nil? || keyframe_video_tags.last.timestamp.nil?) ? 0 : keyframe_video_tags.last.timestamp / 1000.0
291
+ end
292
+
293
+ def videosize
294
+ video_tags.inject(0) { |size, tag| size += tag.size }
295
+ end
296
+
297
+ def audiosize
298
+ audio_tags.inject(0) { |size, tag| size += tag.size }
299
+ end
300
+
301
+ def datasize
302
+ videosize + audiosize + (meta_tags.inject(0) { |size, tag| size += tag.size})
303
+ end
304
+
305
+ def filesize
306
+ # header + data + backpointers
307
+ @data_offset + datasize + ((@tags.length + 1) * 4)
308
+ end
309
+
310
+ def width
311
+ return nil unless has_video?
312
+ video_tags.first.width || 0
313
+ end
314
+
315
+ def height
316
+ return nil unless has_video?
317
+ video_tags.first.height || 0
318
+ end
319
+
320
+ def videodatarate
321
+ data_size = video_tags.inject(0) do |size, tag|
322
+ size += tag.data_size
323
+ end
324
+ return data_size == 0 ? 0 : data_size / duration * 8 / 1000 # kBits/sec
325
+ end
326
+
327
+ def audiodatarate
328
+ data_size = audio_tags.inject(0) do |size, tag|
329
+ size += tag.data_size
330
+ end
331
+ return data_size == 0 ? 0 : data_size / duration * 8 / 1000 # kBits/sec
332
+ end
333
+
334
+ def stereo
335
+ audio_tags.first && audio_tags.first.sound_type == FLVAudioTag::STEREO
336
+ end
337
+
338
+ def audiosamplerate
339
+ audio_tags.first && audio_tags.first.sound_rate
340
+ end
341
+
342
+ def audiosamplesize
343
+ audio_tags.first && audio_tags.first.sound_sample_size
344
+ end
345
+
346
+ def audiocodecid
347
+ audio_tags.first && audio_tags.first.sound_format
348
+ end
349
+
350
+ def videocodecid
351
+ return nil unless has_video?
352
+ video_tags.first.codec_id
353
+ end
354
+
355
+ def audiodelay
356
+ return 0 unless has_video?
357
+ video_tags.first.timestamp.nil? ? 0 : video_tags.first.timestamp / 1000.0
358
+ end
359
+
360
+ def canSeekToEnd
361
+ return true unless has_video?
362
+ video_tags.last.frame_type == FLVVideoTag::KEYFRAME
363
+ end
364
+
365
+ def keyframes
366
+ object = Object.new
367
+
368
+ calculate_tag_byte_offsets
369
+
370
+ object.instance_variable_set( :@times, keyframe_video_tags.collect { |video_tag| video_tag.timestamp / 1000.0 } )
371
+ object.instance_variable_set( :@filepositions, keyframe_video_tags.collect { |video_tag| video_tag.byte_offset } )
372
+
373
+ return object
374
+ end
375
+
376
+ def cue_points
377
+ on_cue_point_tags.collect { |tag| tag.meta_data }
378
+ end
379
+
380
+ def <<(tags)
381
+ add_tags tags, true
382
+ end
383
+
384
+ private
385
+ def calculate_tag_byte_offsets
386
+ @tags.inject(@data_offset + 4) { |offset, tag| tag.byte_offset = offset; offset += 4 + tag.size }
387
+ end
388
+
389
+ def read_header
390
+ begin
391
+ @signature = @in_stream.read__STRING(3)
392
+ log "File signature: #{@signature}"
393
+ raise(FLVStreamError, 'IO is not a FLV stream. Wrong signature.') if @signature != 'FLV'
394
+
395
+ @version = @in_stream.read__UI8
396
+ log "File version: #{@version}"
397
+
398
+ type_flags = @in_stream.read__UI8
399
+ @type_flags_audio = (type_flags & 4) == 1
400
+ log "File has audio: #{@type_flags_audio}"
401
+
402
+ @type_flags_video = (type_flags & 1) == 1
403
+ log "File has video: #{@type_flags_video}"
404
+
405
+ @data_offset = @in_stream.read__UI32
406
+ log "File header size: #{@data_offset}"
407
+
408
+ @extra_data = @in_stream.read__STRING @data_offset - 9
409
+ log "File header extra data: #{@extra_data}"
410
+
411
+ rescue IOError => e
412
+ raise IOError, "IO Error while reading FLV header. #{e.message}", e.backtrace
413
+ end
414
+ end
415
+
416
+ def write_header
417
+ begin
418
+ @out_stream.write__STRING 'FLV'
419
+ @out_stream.write__UI8 1
420
+ type_flags = 0
421
+ type_flags += 4 if has_audio?
422
+ type_flags += 1 if has_video?
423
+ @out_stream.write__UI8 type_flags
424
+ @out_stream.write__UI32 9 + @extra_data.length
425
+ @out_stream.write__STRING @extra_data
426
+ rescue IOError => e
427
+ raise IOError, "IO Error while writing FLV header. #{e.message}", e.backtrace
428
+ end
429
+ end
430
+
431
+ def read_tags
432
+ @tags ||= []
433
+
434
+ while true
435
+ break if eof?
436
+ previous_tag_length = @in_stream.read__UI32
437
+ log "Previous tag length: #{previous_tag_length}"
438
+
439
+ break if eof?
440
+ log "Tag number: #{@tags.size + 1}"
441
+ tag_type = @in_stream.read__UI8
442
+ log "Tag type: #{FLVTag.type2name(tag_type)}"
443
+
444
+ break if eof?
445
+ case tag_type
446
+ when FLVTag::AUDIO
447
+ @tags << FLVAudioTag.new(@in_stream)
448
+ when FLVTag::VIDEO
449
+ @tags << FLVVideoTag.new(@in_stream)
450
+ when FLVTag::META
451
+ @tags << FLVMetaTag.new(@in_stream)
452
+ else
453
+ @tags << FLVTag.new(@in_stream)
454
+ end
455
+
456
+ end
457
+
458
+ if $VERBOSE
459
+ total_known_tags =
460
+ audio_tags.size + video_tags.size + meta_tags.size
461
+ out = "Read tags: #{audio_tags.size} audio, #{video_tags.size} video,"
462
+ out << " #{meta_tags.size} meta,"
463
+ out << " #{@tags.size - total_known_tags} unknown,"
464
+ out << " #{@tags.size} total\n"
465
+ puts out
466
+ end
467
+ end
468
+
469
+ def write_tags
470
+
471
+ @out_stream.write__UI32 0
472
+
473
+ count = 0
474
+ @tags.each do |tag|
475
+ tag.serialize @out_stream
476
+ @out_stream.write__UI32 tag.size
477
+ count += 1
478
+ puts "[#{count}]#{tag.inspect}\n" if $VERBOSE
479
+ end
480
+
481
+ puts "Wrote tags: #{count} total" if $VERBOSE
482
+ end
483
+
484
+ def log(msg)
485
+ @stream_log << msg.to_s + "\n"
486
+ end
487
+
488
+ def eof?
489
+ @in_stream.eof?
490
+ end
491
+ end
492
+ end