: O. Yuanying

RubyでFLVファイルからmp3を抽出する

概要

前回のエントリでffmpegを利用したFLVファイルからmp3を抽出する方法を紹介しましたが、ffmpegをインストールしてコマンドラインから利用するのがめんどくさいと考える人が多いのも事実。

というわけで、MacにもSplitFLVのような「FLVファイルからmp3を抽出する」という単機能なアプリケーションが欲しいなと思いました。

いくつか調べてみましたが、内部的にffmpegを利用して色々なフォーマットに対応したソフトはありましたが、「FLVファイルからmp3を抽出する」だけを目的としたソフトは見つかりませんでした。

なければ作ればいい!我々にはXcodeとRubyCocoaという心強い味方がいるぞ!

ということで作ってみました。

ライセンス問題

最初に考えたのは、内部にffmpegを持ってアプリケーションはffmpegをキックするだけという非常に簡単なものだったのですが、FLVファイルからmp3を取り出すだけのためにffmpegをアプリに持たせるのはバカバカしいなと考えて、自力でFLVからmp3を取り出すことにしました。

FLVからmp3を取り出すにはFLVのフォーマットを知る必要があるよなー。と考えて仕様書を探したところ、あるにはあったのですが第一の難問が。

Adobeの"SWF and FLV File Format Specification"に関する利用許諾には、

Pursuant to the terms and conditions of this License, you are granted a nonexclusive license to use the Specification for the sole purposes of developing Products that output SWF or FLV.

このLicenseに関する条件によると、SWFかFLVを出力したProductsを開発する唯一の目的にSpecificationを使用する通常実施権をあなたに与えます。

えーっと、、

You may not use the Specification in any way to create or develop a runtime, client, player, executable or other program that reads or renders SWF files.

あなたはランタイム、クライアントを作成するか、または開発するのに何らかの方法でSpecificationを使用することができません、プレーヤー、ファイルをSWFに読み込むか、または表す実行可能であるか他のプログラム。

ようするに、「FLVファイルからmp3を取り出すためにこの仕様書を利用しちゃだめだよ」って書いてありますね…。

なんという偶然なのか、「DSAS開発者の部屋:SWFファイルフォーマットとライセンス」というエントリがはてブの注目エントリに上がってました。

ライセンス問題の解決

よく読んでみると「DSAS開発者の部屋:SWFファイルフォーマットとライセンス」にはSWFファイルにおいては、このライセンス問題を解決したオープンソースの仕様書があるとかないとか。

ということはもしかしてFLVファイルにおいてももしや同じものが!?と探してみたらありました。

ソースコード

まずは簡単に書いてみたのが以下のソースコードです。sm1886358.flvというファイルをオープンしてsm1886358.mp3というファイルに書き出してます。

#!/usr/bin/env ruby

def read_ui24(io)
  ("\000" + io.read(3)).unpack('N')[0]
end

class AudioData
  attr_reader :data
  
  def initialize(io, data_size)
    puts io.read(1).unpack('b*')
    @data = io.read(data_size - 1)
  end
end

def read_header io
  puts io.read(3)
  puts io.read(1).unpack("c*")
  puts io.read(1).unpack("b*")
  puts io.read(4).unpack('N')
  
  puts '####head end'
end

def read_tag io
  previous_tag_size = io.read(4).unpack('N')
  puts "previous tag size is: #{previous_tag_size}"
  
  return false unless tag_type = io.read(1)
  
  data_size = read_ui24(io)
  time_stamp = read_ui24(io)
  time_stamp_extended = io.read(1).unpack('c')
  stream_id = read_ui24(io)
  
  case tag_type
  when [8].pack('c')
    puts 'Audio Type'
    
    return AudioData.new(io, data_size)
  when [9].pack('c')
    puts 'Video Type'
    io.read(data_size)
    
    return true
  when [18].pack('c')
    puts 'Script Type'
    puts io.read(data_size)
    
    return true
  end
end

open('sm1886358.flv', 'rb') do |io|
  read_header(io)
  
  open('sm1886358.mp3', 'wb') do |output|
    while (tag = read_tag(io))
      if tag.class == AudioData
        output.write(tag.data)
      end
    end
  end
end

ところで、仕様書にはuint24_beとかuint32_beとか書いてあって、最初「be」って何打これは?とか思ってたのですが、普通に読み込んでみたところ変な数字を返してきたので気づきました。ビッグエンディアンの略だったんですね。

私が利用してるのはIntel Macなのでバイトオーダーをリトルエンディアンで読み込んでたので変な数字を返していたのでした。

というわけでbeと書いてあるデータは、 io.read(4).unpack('N')という感じでビッグエンディアンを指定して読み込んでます。

さて、次はこれを利用してSplitFLV for Macを作ってみましょうかね。