Class: FLV::FLVStream

Inherits:
Object
  • Object
show all
Defined in:
lib/flv/stream.rb

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(in_stream, out_stream = nil, stream_log = false) ⇒ FLVStream

Returns a new instance of FLVStream.



48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
# File 'lib/flv/stream.rb', line 48

def initialize(in_stream, out_stream = nil, stream_log = false)

  
  @stream_log = stream_log ? (File.open('stream.log', File::CREAT|File::WRONLY|File::TRUNC) rescue AMFStringBuffer.new) : AMFStringBuffer.new
  @in_stream = in_stream
  @out_stream = out_stream || in_stream

  unless eof?
    begin
      read_header
      read_tags
    rescue Object => e
      log e
      raise e
    ensure
      @stream_log.close
    end
  else
    @version = 1
    @type_flags_audio = false
    @type_flags_video = false
    @extra_data = ''
    @tags = []
  end
end

Instance Attribute Details

#signaturObject

Returns the value of attribute signatur.



41
42
43
# File 'lib/flv/stream.rb', line 41

def signatur
  @signatur
end

#stream_logObject

Returns the value of attribute stream_log.



41
42
43
# File 'lib/flv/stream.rb', line 41

def stream_log
  @stream_log
end

#tagsObject

Returns the value of attribute tags.



41
42
43
# File 'lib/flv/stream.rb', line 41

def tags
  @tags
end

#type_flags_audioObject

Returns the value of attribute type_flags_audio.



41
42
43
# File 'lib/flv/stream.rb', line 41

def type_flags_audio
  @type_flags_audio
end

#type_flags_videoObject

Returns the value of attribute type_flags_video.



41
42
43
# File 'lib/flv/stream.rb', line 41

def type_flags_video
  @type_flags_video
end

#versionObject

Returns the value of attribute version.



41
42
43
# File 'lib/flv/stream.rb', line 41

def version
  @version
end

Instance Method Details

#<<(tags) ⇒ Object



380
381
382
# File 'lib/flv/stream.rb', line 380

def <<(tags)
  add_tags tags, true
end

#add_meta_tag(meta_data = {}) ⇒ Object



127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
# File 'lib/flv/stream.rb', line 127

def add_meta_tag( = {})
  meta_tag = FLVMetaTag.new
  meta_tag.event = 'onMetaData'
  
  meta_tag['framerate'] = framerate
  meta_tag['duration'] = duration
  meta_tag['lasttimestamp'] = lasttimestamp
  meta_tag['videosize'] = videosize
  meta_tag['audiosize'] = audiosize
  meta_tag['datasize'] = 0 # calculate after tag was added
  meta_tag['filesize'] = 0 # calculate after tag was added
  meta_tag['width'] = (width == 0 && ) ? .['width'] : width
  meta_tag['height'] = (height == 0 && ) ? .['height'] : height
  meta_tag['videodatarate'] = videodatarate
  meta_tag['audiodatarate'] = audiodatarate
  meta_tag['lastkeyframetimestamp'] = lastkeyframetimestamp
  meta_tag['audiocodecid'] = audiocodecid
  meta_tag['videocodecid'] = videocodecid
  meta_tag['audiodelay'] = audiodelay
  meta_tag['canSeekToEnd'] = canSeekToEnd
  meta_tag['stereo'] = stereo
  meta_tag['audiosamplerate'] = audiosamplerate
  meta_tag['audiosamplesize'] = audiosamplesize
  meta_tag['cuePoints'] = cue_points
  meta_tag['keyframes'] = keyframes
  meta_tag['hasVideo'] = has_video?
  meta_tag['hasAudio'] = has_audio?
  meta_tag['hasMetadata'] = true
  meta_tag['hasCuePoints'] = has_cue_points?
  meta_tag['hasKeyframes'] = has_keyframes?

  meta_tag..merge!()
  
  add_tags(meta_tag)

  # recalculate values those need meta tag data size or presence
  meta_tag['keyframes'] = keyframes
  meta_tag['datasize'] = datasize
  meta_tag['filesize'] = filesize
  meta_tag['hasMetadata'] = has_meta_data?
end

#add_tags(tags, stick_on_framerate = true, overwrite = true) ⇒ Object

general



76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
# File 'lib/flv/stream.rb', line 76

def add_tags(tags, stick_on_framerate = true, overwrite = true)
  tags = [tags] unless tags.kind_of? Array
  
  tags.each do |tag|
    
    # FIXME: Does not really work for video or audio tags, because tags are
    #        inserted next to same kind. Normally audio and video tags are 
    #        alternating.
    if stick_on_framerate && !framerate.nil? &&framerate != 0 && tag.timestamp % (1000 / framerate) != 0
      raise FLVTagError, "Could not insert tag. Timestamp #{tag.timestamp} does not fit into framerate."
      next
    end
    
    after_tag = @tags.detect { |_tag| _tag.timestamp >= tag.timestamp }
    
    if after_tag.nil?
      @tags << tag
      next
    end

    if tag.timestamp == after_tag.timestamp && tag.class == after_tag.class
      if tag.kind_of?(FLVMetaTag) && ( ( tag.event != after_tag.event ) || ( tag.event == after_tag.event && !overwrite ) )
        @tags.insert( @tags.index(after_tag), tag )
      else
        @tags[@tags.index(after_tag)] = tag
      end
    else
      @tags.insert( @tags.index(after_tag), tag )
    end
      
    empty_tag_type_cache
  end
  
  @tags
end

#audio_tagsObject



211
212
213
# File 'lib/flv/stream.rb', line 211

def audio_tags
  @audio_tags_cache ||= @tags.find_all { |tag| tag.kind_of? FLVAudioTag }
end

#audiocodecidObject



346
347
348
# File 'lib/flv/stream.rb', line 346

def audiocodecid
  audio_tags.first && audio_tags.first.sound_format
end

#audiodatarateObject



327
328
329
330
331
332
# File 'lib/flv/stream.rb', line 327

def audiodatarate
  data_size = audio_tags.inject(0) do |size, tag|
    size += tag.data_size
  end
  return data_size == 0 ? 0 : data_size / duration * 8 / 1000 # kBits/sec
end

#audiodelayObject



355
356
357
358
# File 'lib/flv/stream.rb', line 355

def audiodelay
  return 0 unless has_video?
  video_tags.first.timestamp.nil? ? 0 : video_tags.first.timestamp / 1000.0
end

#audiosamplerateObject



338
339
340
# File 'lib/flv/stream.rb', line 338

def audiosamplerate
  audio_tags.first && audio_tags.first.sound_rate
end

#audiosamplesizeObject



342
343
344
# File 'lib/flv/stream.rb', line 342

def audiosamplesize
  audio_tags.first && audio_tags.first.sound_sample_size
end

#audiosizeObject



297
298
299
# File 'lib/flv/stream.rb', line 297

def audiosize
  audio_tags.inject(0) { |size, tag| size += tag.size }
end

#canSeekToEndObject



360
361
362
363
# File 'lib/flv/stream.rb', line 360

def canSeekToEnd
  return true unless has_video?
  video_tags.last.frame_type == FLVVideoTag::KEYFRAME
end

#closeObject



185
186
187
188
# File 'lib/flv/stream.rb', line 185

def close
  @in_stream.close
  @out_stream.close
end

#cue_pointsObject



376
377
378
# File 'lib/flv/stream.rb', line 376

def cue_points
  on_cue_point_tags.collect { |tag| tag. }
end

#cut(options = []) ⇒ Object



112
113
114
115
116
117
118
119
# File 'lib/flv/stream.rb', line 112

def cut(options = [])
  @tags.delete_if { |tag| tag.timestamp < ( options[:in_point] || 0 ) || tag.timestamp > ( options[:out_point] || tags.last.timestamp ) }
  if options[:collapse]
    difference = @tags.first.timestamp
    @tags.each { |tag| tag.timestamp -= difference }
  end
  empty_tag_type_cache
end

#datasizeObject



301
302
303
# File 'lib/flv/stream.rb', line 301

def datasize
  videosize + audiosize + (meta_tags.inject(0) { |size, tag| size += tag.size})
end

#durationObject



273
274
275
# File 'lib/flv/stream.rb', line 273

def duration
  lasttimestamp
end

#empty_tag_type_cacheObject

views on tags



193
194
195
196
197
198
199
# File 'lib/flv/stream.rb', line 193

def empty_tag_type_cache
  @video_tags_cache = nil
  @keyframe_video_tags_cache = nil
  @audio_tags_cache = nil
  @meta_tags_cache = nil
  @on_cue_point_tags_cache = nil
end

#filesizeObject



305
306
307
308
# File 'lib/flv/stream.rb', line 305

def filesize
  # header + data + backpointers 
  @data_offset + datasize + ((@tags.length + 1) * 4)
end

#find_nearest_keyframe_video_tag(position) ⇒ Object



121
122
123
124
125
# File 'lib/flv/stream.rb', line 121

def find_nearest_keyframe_video_tag(position)
  keyframe_video_tags.sort do |tag_a, tag_b|
    (position - tag_a.timestamp).abs <=> (position - tag_b.timestamp).abs
  end.first
end

#frame_sequenceObject

FIXME: Could be less complicate and run faster

Raises:



250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
# File 'lib/flv/stream.rb', line 250

def frame_sequence
  return nil unless has_video?
  raise(FLVStreamError, 'File has to contain at least 2 video tags to calculate frame sequence') if video_tags.length < 2

  @frame_sequence ||=
  begin
    sequences = video_tags.collect do |tag| # find all sequences
      video_tags[video_tags.index(tag) + 1].timestamp - tag.timestamp unless tag == video_tags.last
    end.compact
    
    uniq_sequences = (sequences.uniq - [0]).sort # remove 0 and try smallest intervall first 
    
    sequence_appearances = uniq_sequences.collect { |sequence| sequences.find_all { |_sequence| sequence == _sequence }.size } # count apperance of each sequence
    
    uniq_sequences[ sequence_appearances.index( sequence_appearances.max ) ] # return the sequence that appears most
  end
end

#framerateObject



268
269
270
271
# File 'lib/flv/stream.rb', line 268

def framerate
  return nil unless has_video?
  frame_sequence == 0 ? 0 : 1000 / frame_sequence
end

#has_audio?Boolean

Returns:

  • (Boolean)


231
232
233
# File 'lib/flv/stream.rb', line 231

def has_audio?
  audio_tags.size > 0
end

#has_cue_points?Boolean

Returns:

  • (Boolean)


239
240
241
# File 'lib/flv/stream.rb', line 239

def has_cue_points?
  on_cue_point_tags.size > 0
end

#has_keyframes?Boolean

Returns:

  • (Boolean)


243
244
245
# File 'lib/flv/stream.rb', line 243

def has_keyframes?
  keyframe_video_tags.size > 0
end

#has_meta_data?Boolean

Returns:

  • (Boolean)


235
236
237
# File 'lib/flv/stream.rb', line 235

def has_meta_data?
  !.nil?
end

#has_video?Boolean

Returns:

  • (Boolean)


227
228
229
# File 'lib/flv/stream.rb', line 227

def has_video?
  video_tags.size > 0
end

#heightObject



315
316
317
318
# File 'lib/flv/stream.rb', line 315

def height
  return nil unless has_video?
  video_tags.first.height || 0
end

#keyframe_video_tagsObject



205
206
207
208
209
# File 'lib/flv/stream.rb', line 205

def keyframe_video_tags
  @keyframe_video_tags_cache ||= @tags.find_all do |tag|
    tag.kind_of?(FLVVideoTag) && tag.frame_type == FLVVideoTag::KEYFRAME
  end
end

#keyframesObject



365
366
367
368
369
370
371
372
373
374
# File 'lib/flv/stream.rb', line 365

def keyframes
  object = Object.new
  
  calculate_tag_byte_offsets
  
  object.instance_variable_set( :@times, keyframe_video_tags.collect { |video_tag| video_tag.timestamp / 1000.0 } )
  object.instance_variable_set( :@filepositions, keyframe_video_tags.collect { |video_tag| video_tag.byte_offset } )
  
  return object
end

#lastkeyframetimestampObject



288
289
290
291
# File 'lib/flv/stream.rb', line 288

def lastkeyframetimestamp
  return nil unless has_video?
  (keyframe_video_tags.last.nil? || keyframe_video_tags.last.timestamp.nil?) ? 0 : keyframe_video_tags.last.timestamp / 1000.0
end

#lasttimestampObject



277
278
279
280
281
282
283
284
285
286
# File 'lib/flv/stream.rb', line 277

def lasttimestamp
  last_tag = if has_video?
      video_tags.last
    elsif has_audio?
      audio_tags.last
    else
      tags.last
    end
  last_tag.timestamp.nil? ? 0 : last_tag.timestamp / 1000.0
end

#meta_tagsObject



215
216
217
# File 'lib/flv/stream.rb', line 215

def meta_tags
  @meta_tags_cache ||= @tags.find_all { |tag| tag.kind_of? FLVMetaTag }
end

#on_cue_point_tagsObject



223
224
225
# File 'lib/flv/stream.rb', line 223

def on_cue_point_tags
  @on_cue_point_tags_cache ||= @tags.find_all { |tag| tag.kind_of?(FLVMetaTag) && tag.event == 'onCuePoint' } # FIXME: Cannot be cached
end

#on_meta_data_tagObject



219
220
221
# File 'lib/flv/stream.rb', line 219

def 
  @tags.find { |tag| tag.kind_of?(FLVMetaTag) && tag.event == 'onMetaData' } # FIXME: Cannot be cached
end

#stereoObject



334
335
336
# File 'lib/flv/stream.rb', line 334

def stereo
  audio_tags.first && audio_tags.first.sound_type == FLVAudioTag::STEREO
end

#video_tagsObject



201
202
203
# File 'lib/flv/stream.rb', line 201

def video_tags
  @video_tags_cache ||= @tags.find_all { |tag| tag.kind_of? FLVVideoTag }
end

#videocodecidObject



350
351
352
353
# File 'lib/flv/stream.rb', line 350

def videocodecid
  return nil unless has_video?
  video_tags.first.codec_id
end

#videodatarateObject



320
321
322
323
324
325
# File 'lib/flv/stream.rb', line 320

def videodatarate
  data_size = video_tags.inject(0) do |size, tag|
    size += tag.data_size
  end
  return data_size == 0 ? 0 : data_size / duration * 8 / 1000 # kBits/sec
end

#videosizeObject



293
294
295
# File 'lib/flv/stream.rb', line 293

def videosize
  video_tags.inject(0) { |size, tag| size += tag.size }
end

#widthObject



310
311
312
313
# File 'lib/flv/stream.rb', line 310

def width
  return nil unless has_video?
  video_tags.first.width || 0
end

#writeObject



169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
# File 'lib/flv/stream.rb', line 169

def write

  begin
    @out_stream.seek( 0 )
  rescue Object => e
  end
  
  write_header
  write_tags

  begin
    @out_stream.truncate( @out_stream.pos )
  rescue Object => e
  end
end