class FLV::FLVStream

Attributes

signatur[RW]
stream_log[RW]
tags[RW]
type_flags_audio[RW]
type_flags_video[RW]
version[RW]

Public Class Methods

new(in_stream, out_stream = nil, stream_log = false) click to toggle source
# 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

Public Instance Methods

<<(tags) click to toggle source
# File lib/flv/stream.rb, line 380
def <<(tags)
  add_tags tags, true
end
add_meta_tag(meta_data = {}) click to toggle source
# File lib/flv/stream.rb, line 127
def add_meta_tag(meta_data = {})
  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 && on_meta_data_tag) ? on_meta_data_tag.meta_data['width'] : width
  meta_tag['height'] = (height == 0 && on_meta_data_tag) ? on_meta_data_tag.meta_data['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.meta_data.merge!(meta_data)
  
  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) click to toggle source

general

# 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_tags() click to toggle source
# File lib/flv/stream.rb, line 211
def audio_tags
  @audio_tags_cache ||= @tags.find_all { |tag| tag.kind_of? FLVAudioTag }
end
audiocodecid() click to toggle source
# File lib/flv/stream.rb, line 346
def audiocodecid
  audio_tags.first && audio_tags.first.sound_format
end
audiodatarate() click to toggle source
# 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
audiodelay() click to toggle source
# 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
audiosamplerate() click to toggle source
# File lib/flv/stream.rb, line 338
def audiosamplerate
  audio_tags.first && audio_tags.first.sound_rate
end
audiosamplesize() click to toggle source
# File lib/flv/stream.rb, line 342
def audiosamplesize
  audio_tags.first && audio_tags.first.sound_sample_size
end
audiosize() click to toggle source
# File lib/flv/stream.rb, line 297
def audiosize
  audio_tags.inject(0) { |size, tag| size += tag.size }
end
canSeekToEnd() click to toggle source
# File lib/flv/stream.rb, line 360
def canSeekToEnd
  return true unless has_video?
  video_tags.last.frame_type == FLVVideoTag::KEYFRAME
end
close() click to toggle source
# File lib/flv/stream.rb, line 185
def close
  @in_stream.close
  @out_stream.close
end
cue_points() click to toggle source
# File lib/flv/stream.rb, line 376
def cue_points
  on_cue_point_tags.collect { |tag| tag.meta_data }
end
cut(options = []) click to toggle source
# 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
datasize() click to toggle source
# File lib/flv/stream.rb, line 301
def datasize
  videosize + audiosize + (meta_tags.inject(0) { |size, tag| size += tag.size})
end
duration() click to toggle source
# File lib/flv/stream.rb, line 273
def duration
  lasttimestamp
end
empty_tag_type_cache() click to toggle source

views on tags

# 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
filesize() click to toggle source
# 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) click to toggle source
# 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_sequence() click to toggle source

FIXME: Could be less complicate and run faster

# 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
framerate() click to toggle source
# File lib/flv/stream.rb, line 268
def framerate
  return nil unless has_video?
  frame_sequence == 0 ? 0 : 1000 / frame_sequence
end
has_audio?() click to toggle source
# File lib/flv/stream.rb, line 231
def has_audio?
  audio_tags.size > 0
end
has_cue_points?() click to toggle source
# File lib/flv/stream.rb, line 239
def has_cue_points?
  on_cue_point_tags.size > 0
end
has_keyframes?() click to toggle source
# File lib/flv/stream.rb, line 243
def has_keyframes?
  keyframe_video_tags.size > 0
end
has_meta_data?() click to toggle source
# File lib/flv/stream.rb, line 235
def has_meta_data?
  !on_meta_data_tag.nil?
end
has_video?() click to toggle source
# File lib/flv/stream.rb, line 227
def has_video?
  video_tags.size > 0
end
height() click to toggle source
# File lib/flv/stream.rb, line 315
def height
  return nil unless has_video?
  video_tags.first.height || 0
end
keyframe_video_tags() click to toggle source
# 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
keyframes() click to toggle source
# 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
lastkeyframetimestamp() click to toggle source
# 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
lasttimestamp() click to toggle source
# 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_tags() click to toggle source
# 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_tags() click to toggle source
# 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_tag() click to toggle source
# File lib/flv/stream.rb, line 219
def on_meta_data_tag
  @tags.find { |tag| tag.kind_of?(FLVMetaTag) && tag.event == 'onMetaData' } # FIXME: Cannot be cached

end
stereo() click to toggle source
# File lib/flv/stream.rb, line 334
def stereo
  audio_tags.first && audio_tags.first.sound_type == FLVAudioTag::STEREO
end
video_tags() click to toggle source
# File lib/flv/stream.rb, line 201
def video_tags
  @video_tags_cache ||= @tags.find_all { |tag| tag.kind_of? FLVVideoTag }
end
videocodecid() click to toggle source
# File lib/flv/stream.rb, line 350
def videocodecid
  return nil unless has_video?
  video_tags.first.codec_id
end
videodatarate() click to toggle source
# 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
videosize() click to toggle source
# File lib/flv/stream.rb, line 293
def videosize
  video_tags.inject(0) { |size, tag| size += tag.size }
end
width() click to toggle source
# File lib/flv/stream.rb, line 310
def width
  return nil unless has_video?
  video_tags.first.width || 0
end
write() click to toggle source
# 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