ffmpeg でムービーからオーディオデータを取り出す

今日はムービーからオーディオデータを取り出す方法を模索してみた。

オーディオのコーデックが AAC の場合は次の ffmpeg コマンドで行けた。 まず取り出す際に再エンコードしない方法。

$ ffmpeg -i Movie.mov -vn -acodec copy Music.m4a
出力先のファイル名の拡張子で判断しているようで、 Music.m4a とすると抽出した AAC データを MP4 コンテナに格納してくれるようだ。 Music.aac とした場合は生の AAC データが抽出されるみたいだけど、 生の AAC ファイルは iTunes が認識してくれなかった。

mp3 を取り出す場合はこんな感じ。 mp3 データを MP4 コンテナ(.m4a)に格納しても、 iTunes が再生してくれないみたいなので、 出力ファイルの拡張子を mp3 にしている。

$ ffmpeg -i Movie.mov -vn -acodec copy Music.mp3

音声データを取り出す際に、 AAC で再エンコードしたい場合は次のようにすればよいみたい。

$ ffmpeg -i Movie.mov -vn -acodec libfaac -ac 2 -ar 44100 -ab 128k Music.m4a
ここで -ar オプションでサンプリングレートを、 -ab オプションでビットレートを指定している。

複数のムービーをバッチ処理するのに、 次のようなシェルスクリプトを書いてみた。 指定されたムービーのオーディオエンコードを自動判別して m4a、 mp3 ファイルを出力する。 AAC、MP3 以外のコーデックは AAC で再エンコードするようにしてみた。

#!/bin/sh
for f in "$@"
do
  codec=`ffmpeg -i "$f" 2>&1 | sed -n 's/^[ \t]*Stream #[0-9]*\.[0-9]*: Audio: \([^,]*\),.*$/\1/p'`
  case $codec in
      mp3) ext="mp3";;
      aac) ext="m4a";;
  esac
  basename=`echo $f | sed -e 's/\.[a-z0-9]*$//'`
  if [ -n "$ext" ]
  then
      ffmpeg -y -i "$f" -vn -acodec copy "$basename.$ext"
  else
      ffmpeg -y -i "$f" -vn -acodec libfaac -ac 2 -ar 44100 -ab 128k "$basename.m4a"
  fi
done

せっかくなので、 フォルダアクション用 AppleScript も作ってみた。 ffmpeg コマンドのあるパスは ffmpeg_path プロパティで設定している。

property done_foldername : "iTunes Musics"
property originals_foldername : "Original Movies"
property ffmpeg_path : "/opt/local/bin/ffmpeg"
-- the list of file types which will be processed
property type_list : {"3gpp"}
-- since file types are optional in Mac OS X,
-- check the name extension if there is no file type
-- NOTE: do not use periods (.) with the items in the name extensions list
-- eg: {"txt", "text", "jpg", "jpeg"}, NOT: {".txt", ".text", ".jpg", ".jpeg"} 
property extension_list : {"mov", "m4v", "mp4", "3gp", "3gpp", "3g2", "3gpp2", "dv", "mpg", "mpeg", "flv", "avi", "wmv"}
property acodec_record : {aac:"m4a", mp3:"mp3", _unknown:"m4a"}

on adding folder items to this_folder after receiving these_items
    tell application "Finder"
        if not (exists folder done_foldername of this_folder) then
            make new folder at this_folder with properties {name:done_foldername}
        end if
        set the results_folder to (folder done_foldername of this_folder) as alias
        if not (exists folder originals_foldername of this_folder) then
            make new folder at this_folder with properties {name:originals_foldername}
            set current view of container window of this_folder to list view
        end if
        set the originals_folder to folder originals_foldername of this_folder
    end tell
    set processed_number to 0
    repeat with this_item in these_items
        set the item_info to the info for this_item
        if (alias of the item_info is false and the file type of the item_info is in the type_list) or (the name extension of the item_info is in the extension_list) then
            try
                set audio_codec to do shell script ffmpeg_path & " -i " & quoted form of POSIX path of this_item & " 2>&1 | sed -n 's/^[ \\t]*Stream #[0-9]*\\.[0-9]*: Audio: \\([^,]*\\),.*$/\\1/p'"
                set newmovie_extension to value of (label(audio_codec)) at acodec_record
                tell application "Finder"
                    set the new_name to my resolve_conflicts(this_item, results_folder, newmovie_extension)
                end tell
                process_item(this_item, new_name, results_folder, audio_codec)
                tell application "Finder"
                    my resolve_conflicts(this_item, originals_folder, "")
                    move this_item to the originals_folder with replacing
                end tell
                set processed_number to processed_number + 1
            on error error_message number error_number
                if the error_number is not -128 then
                    tell application "Finder"
                        activate
                        --say "error has occured; " & error_message
                        display dialog error_message buttons {"Cancel"} default button 1 with title "on adding" giving up after 120
                    end tell
                end if
            end try
        end if
    end repeat
    if processed_number > 0 then say "audio extracting has done"
end adding folder items to

on resolve_conflicts(this_item, target_folder, new_extension)
    tell application "Finder"
        set the file_name to the name of this_item
        set file_extension to the name extension of this_item
        if the file_extension is "" then
            set the trimmed_name to the file_name
        else
            set the trimmed_name to text 1 thru -((length of file_extension) + 2) of the file_name
        end if
        if the new_extension is "" then
            set target_name to file_name
            set target_extension to file_extension
        else
            set target_extension to new_extension
            set target_name to (the trimmed_name & "." & target_extension) as string
        end if
        if (exists document file target_name of target_folder) then
            set the name_increment to 1
            repeat
                set the new_name to (the trimmed_name & "." & (name_increment as string) & "." & target_extension) as string
                if not (exists document file new_name of the target_folder) then
                    -- rename to conflicting file
                    set the name of document file target_name of the target_folder to the new_name
                    exit repeat
                else
                    set the name_increment to the name_increment + 1
                end if
            end repeat
        end if
    end tell
    return the target_name
end resolve_conflicts

on label(target_label)
    return run script "on value at target_record
    try
        set the extension to " & target_label & " of target_record
    on error m number n
        if n = -1728 then
            set the extension to _unknown of target_record
        else
            display dialog m buttons {\"Cancel\"} default button 1 giving up after 120
            set the extension to \"\"
        end if
    end try
    return the extension
end value
return me"
end label

-- this sub-routine processes files 
on process_item(source_file, new_name, results_folder, audio_codec)
    try
        -- the target path is the destination folder and the new file name
        set the target_path to ((results_folder as string) & new_name) as string
        with timeout of 900 seconds
            if audio_codec is in {"aac", "mp3"} then
                do shell script ffmpeg_path & " -y -i " & quoted form of POSIX path of source_file & " -vn -acodec copy " & quoted form of POSIX path of target_path & " 2> /dev/null"
            else
                do shell script ffmpeg_path & " -y -i " & quoted form of POSIX path of source_file & " -vn -acodec libfaac -ac 2 -ar 44100 -ab 128k " & quoted form of POSIX path of target_path & " 2> /dev/null"
            end if
        end timeout
    on error error_message
        tell application "Finder"
            activate
            say "error has occured; " & error_message
            display dialog error_message buttons {"Cancel"} default button 1 with title "process_item" giving up after 120
        end tell
    end try
end process_item