FFmpegで動画編集をするガイド

こんにちは。年末になり、ますますコマンドラインで動画編集をする機会が増えてきているかと思いますが、皆様いかがお過ごしでしょうか。普段触れていないとついつい忘れてしまうffmpegのオプション群。そんなあなたのためのチートシートです。

基礎編

トランスコーディング

ffmpeg -i input.mp4 -c:v libx264 -preset medium -c:a libfdk_aac output.mp4

または

ffmpeg -i input.mp4 -vcodec libx264 -preset medium -acodec libfdk_aac output.mp4

解説

-c:v オプションは映像コーデックを、-c:a は音声コーデックを指定する。古くはそれぞれ -vcodec -acodec というオプションであった。両者は等価である。

-preset は libx264 の設定プリセット名を与える。

AAC エンコーダは ffmpeg ネイティブのものもあるが (aac) 2015年12月現在、未だ実験的 (experimental) な扱いとなっているため、libfdk_aac を代わりに使っている。なお、デコーダは experimental ではない。

(もしネイティブの aac コーデックを利用する場合には -strict experimental オプションが追加で必要となるので注意)

コンテナフォーマットの変更

ffmpeg -i input.flv -c:v copy -c:a copy output.mp4

または

ffmpeg -i input.flv -c:v copy -c:a copy -f mp4 output.mp4

解説

出力ファイルフォーマットは拡張子から自動的に決定される。-f オプションにより明示することもできる。コーデック copy を指定すると、入力ストリームはエンコード・デコードを経ずにそのまま出力に流される。

映像から音声を抜き出す

ffmpeg -i input.mp4 -c:a copy -vn output.mp4

または

ffmpeg -i input.mp4 -c:a copy -map 0:a output.mp4

解説

コンテナフォーマットに依存するが、基本的には動画ファイルは1つ以上の映像ストリームと音声ストリームから構成されている。何も指示がない場合、ffmpeg は入力のストリームの構成から自動的に出力のストリームの構成を決定する。入力ストリームと出力ストリームの対応を明示的に行うには -map オプションを用いる。-map オプションを指定した場合、対応づけが行われていないストリームは破棄される。

-map オプションに指定できるのは次のような識別子である。

識別子 説明
0:0
1:1
0番目の入力ファイルの0番目のストリーム
1番目の入力ファイルの1番目のストリーム
0:v
1:a
0番目の入力ファイルの全ての映像ストリーム (出現順)
1番目の入力ファイルの全ての音声ストリーム (出現順)
[out] libavfilter (lavf)のストリーム名 (後述)

動画に別録りした音声をつける

ffmpeg -i input.mp4 -i audio.mp3 -c:v copy -c:a aac -strict experimental -map 0:v -map 1:a output.mp4

解説

ffmpeg では -i オプションを繰り返し指定することで、複数の入力ファイルを指定できる。最初の -i オプションで指定したファイルは 0 番目のファイル、次の -i オプションで指定したファイルは 1 番目のファイルとなる。

この例では、前述の -map オプションを用いて、input.mp4 ファイルの動画ストリームと audio.mp3 の音声ストリームを合わせて output.mp4 として出力している。

動画のリサイズ

ffmpeg -i input.mp4 -s hd480 output.mp4

または

ffmpeg -i input.mp4 -s 852x400 output.mp4

または

ffmpeg -i input.mp4 -vf scale=hd480 output.mp4

または

ffmpeg -i input.mp4 -vf scale=852:400 output.mp4

解説

-s オプションは出力される動画ストリームのフレームサイズを指定する。一方、-vf オプションは動画ストリームに適用するフィルタを設定する。

明示的な指定がなくとも、常に暗黙にスケーリングフィルタが適用されるので、-s オプションの指定で大体は十分ではあるが、スケーリングフィルタを明示的に指定することで、細かな設定が可能になる。

(実は上の例で-vfオプションを指定した場合には明示的に指定されたフィルタの上に等倍のスケーリングフィルタが適用されることになり、二重にスケーリングフィルタが適用されることになるが、問題になることはない)

動画から連番のPNGファイルを生成

ffmpeg -i movie.mp4 -c:v png -f image2 output/%06d.png

解説

出力フォーマット image2 では %XXd のようなプレイスホルダーをファイル名に含めることで、フレーム番号をファイル名に含めることが可能である。

動画からアニメーションGIFを生成 (thanks to Yoshiori)

ffmpeg -i movie.mp4 -c:v gif -f gif -r 8 -s 320x180 output.gif

解説

出力フォーマット gif を使うとアニメーションGIFを生成することができる。元動画のフレームレートが高い場合、出力ファイルサイズが肥大してしまうかもしれない。そのような場合は -r オプションを指定することで、出力ファイルのフレームレートを指定して、調整を図る。

動画の特定の範囲を切り出す

ffmpeg -i movie.mp4 -ss 10 -t 20 -c:v copy -c:a copy output.mp4

解説

-ss オプションを指定すると、続く引数に指定された秒数にシークして変換を開始する。-t オプションは、続く引数に指定された秒数、変換を行う。

応用編

動画に画像を重ねる

ffmpeg -i movie.mp4 -i picture.png -filter_complex '[0:0] [1:0] overlay=x=0:y=0 [out]' -map '[out]' -map 0:a output.mp4

解説

overlayフィルタを利用すると、フィルタの0番目の入力を1番目の入力に重ねることができる。

この例では、最初の -i オプションで指定された映像のストリーム ([0:0]) をフィルタの0番目の入力に、次の -i オプションで指定された映像のストリーム ([1:0]) をフィルタの1番目の入力として、1番目の入力を0番目の入力のフレーム上の座標 (x, y) = (0, 0) に重ねている。

指定したタイミングで映像に画像を重ねる

ffmpeg -i movie.mp4 -i picture.png -filter_complex '[0:0] [1:0] overlay=x=if(gt(t,5),if(lt(t,10),0,-10000),-10000),y=0 [out]' -map '[out]' -map 0:a output.mp4

解説

overlayフィルタのパラメータにはexpressionを使うことができる。この例では現フレームの時間であるtという変数を使ってオーバーレイする座標を非連続に設定している。

5秒未満: x=-10000 10秒未満: x=0 10秒以上: x=-10000

重ねる対象を x=-10000 のように画面外に持ってくることによって特定のタイミングで重ね合わせを行うことを実現している。

まとめ

とくにまとめるような内容ではないのですが、よいお年をお迎えください。

Many thanks to pyspa community.

この記事はPyspa Advent Calendar 2015のエントリとして書かれました。次の記事はこちらです。

識別子に絵文字などを使えるようにする

とある言語で識別子に絵文字などのシンボルが使えるそうなのですが、一応GoだってUnicodeクリーンなんだ。

f:id:moriyoshi:20140603121552p:plain

これをコンパイラに食わせてみる。

$ go run sushi.go

どうだ?

# command-line-arguments
./sushi.go:3: invalid identifier character U+1f363
./sushi.go:8: invalid identifier character U+1f363

なのでちょっと悔しい。ということでやりました。


$ go run sushi.go
sushi~

「:」を引数の名前と型の間に入れられるようしたりにする魔改造

とある言語が

func halfOpenRangeLength(start: Int, end: Int) -> Int {
    return end - start
}
println(halfOpenRangeLength(1, 10))
// prints "9"

のような文法だったのを見てウッと思ったのでやってみた

Go のソースを落としてきて

を当てて

$ (cd src/cmd/gc && rm y.tab.h && make)

とする。

package main

type Int int

func halfOpenRangeLength(start: Int, end: Int) -> Int {
	return end - start
}

func main() {
	println(halfOpenRangeLength(1, 10))
	// prints "9"
}
$ go run test.go
9

どーん

MacPro に似た形状のアレを1年使ってみての感想

当時話題沸騰だったアレを、2013年6月11日に購入した。

f:id:moriyoshi:20140602194259j:plain

こんな感じでアップル製品と一緒に並べてみて一通り満足してから、Garbage Collectorとしての利用を開始。

f:id:moriyoshi:20140602200520p:plain

中側の筒 (写真左) の上から外側の筒 (写真右) をかぶせる形で使用する。このような構造になっているのは、ビニール袋を中側の筒にかぶせてあっても、外側の筒で覆い隠せるようにし、美観を損ねないようにするための配慮だ。

近影
f:id:moriyoshi:20140602191928j:plain

  • ビニール袋を中に入れても外側の筒で覆い隠せるので美観が損なわれないのは良い
  • 中の筒は外側の筒よりも半径が3cmほど小さいので、見た目よりかなり容量が小さく感じられる
  • 性能上は競合製品と際立った差は見られない

ロシアでは

別れの時にNginxのモジュールを贈るのが習わしとなっていますので、作りました.

https://github.com/moriyoshi/ngx_ymsr_module

nginxをビルドするときに、

$ ./configure --add-module={ngx_ymsr_moduleのパス}

を指定してあげればOKです。

id:Yamashiro0217 を偲ぶパスを心に決めてください。

あとは、

location / {
    ymsr on;
}

そのURLにアクセスしてください。

moriyoshi@chicwab ~% curl -vv http://localhost/
* About to connect() to localhost port 80 (#0)
*   Trying 127.0.0.1...
* connected
* Connected to localhost (127.0.0.1) port 80 (#0)
> GET / HTTP/1.1
> User-Agent: curl/7.24.0 (x86_64-apple-darwin12.0) libcurl/7.24.0 OpenSSL/0.9.8y zlib/1.2.5
> Host: localhost
> Accept: */*
>
< HTTP/1.1 200 OK
< Server: ngx_openresty/1.4.3.6
< Date: Sat, 15 Feb 2014 12:45:51 GMT
< Content-Type: text/html
< Content-Length: 5
< Last-Modified: Sat, 15 Feb 2014 11:09:21 GMT
< Connection: keep-alive
< ETag: "52ff4ae1-5"
< X-Yamashiro: エンジニアにメモリを与えないと転職する。あると思います。
< Accept-Ranges: bytes
<
test
* Connection #0 to host localhost left intact
* Closing connection #0

おおっ、 リクエストのたびに名言が出力される X-Yamashiro ヘッダが追加されましたね!

まとめ

R.I.P.

Publishing encoded images via GitHub's public key listing

http://thechangelog.com/github-exposes-public-ssh-keys-for-its-users/
http://blog.id774.net/post/2013/12/16/441/

I wrote a couple of scripts that embed / retrieve images in the disguised ssh public key container.

There surely is some room for improvement since the script doesn't take care of the payload's validity as the ssh public key at all while GitHub actually tries to validate it somehow before importing it to their database. That said, most of the encoded images, which of course need to be small enough, seem to have been accepted so far.

Run embed.py to generate a public-key like string:

$ python embed.py lena.jpg

Then use retrieval.py to actually get the embedded images:

$ curl https://github.com/moriyoshi.keys | python retrieval.py

GoのツールチェインのCコンパイラを使う

(この記事は Go Advent Calendar 2013 の12月24日に公開されるはずの記事でした。関係者の皆様大変申し訳ない。)

はじめに

Goコンパイラをビルドすると、なぜかアセンブラ (5a, 6a, 8a) やCコンパイラ (5c, 6c, 8c) までもがビルドされます。これらはPlan 9のツールチェイン由来のもので、ランタイムライブラリ (src/pkg/runtime) の一部のソースコードのコンパイルに使われます。システムデフォルトのコンパイラを使わない (使えない) 理由は、GoのABIがPlan 9のものをなぞっており、Goランタイム上ではこのツールチェインの生成するコードしか動かせないからです。*1

つまり、このツールチェインを利用することで、Goランタイム上で動くコードをGoではなく全部C言語で書くことができます。

Goのアプリケーション初期化シーケンス

Goアプリケーションのmain関数に処理が渡るまでのステップは次のようなものです。

  1. OSによってバイナリがメモリにロードされる
  2. OSからエントリポイントとして_rt0_{arch}_{os} (例: _rt0_amd64_linux) が呼び出される (cf. src/cmd/ld/lib.c)
  3. _rt0_{arch}_{os} がOSから受け取ったコマンドライン引数 (argc, argv) をスタックに積んで _rt0_go を呼び出す
  4. _rt0_go で main の goroutine の生成、及び main·init() と main·main() の呼び出しが行われる

main·init()

main·init() では動的なグローバル変数の初期化が行われます。また、利用しているパッケージの、{package_name}·init() もここからネストして呼び出されます。この様子は、次のようなコードで確かめることができます。

package main

func test() bool {
    panic("nooo!")
    return false
}

var a = test()

func main() {}
panic: nooo!

goroutine 1 [running]:
runtime.panic(0x21a40, 0x2100d1010)
        /Users/moriyoshi/Sources/go/src/pkg/runtime/panic.c:264 +0xb6
main.test(0x2210254fa0)
        /private/tmp/test.go:4 +0x55
main.init()
        /private/tmp/test.go:8 +0x45
exit status 2

main·main()

メインルーチン。mainパッケージのmain関数がこれに相当します。

Hello World

ここまでの内容を踏まえ、hello worldを書いてみます。

ソースコード

#include <runtime.h>

void main·init() {}

void main·main()
{
    runtime·prints("Hello, world!\n");
}

middle dot

· (U+00B7) は、Mac OS XであればOption + Shift + 9で入力することができます。

  • コンパイル

6cの部分は、アーキテクチャによって変えてください。(ARMであれば5c, x86_64であれば6c, i386であれば8c)

$ go 6c -I $GOROOT/src/pkg/runtime hello.c
  • リンク

6lの部分は、アーキテクチャによって変えてください。(ARMであれば5l, x86_64であれば6l, i386であれば8l)

$ go 6l -o hello hello.c
  • 実行
$ ./hello
Hello, world!

ちゃんと実行されました。

goroutine をCから使う

いきなりハードルを上げて、goroutineをCから使ってみましょう。

#include <runtime.h>


/* runtime.h で宣言されていないので */
extern void runtime·newproc(int32 sz, FuncVal* fn, ...);

void main·init() {}

/* runtimeモジュールには用意されていない */
String itos(intgo i)
{
    byte buf[128];
    byte *e = buf + nelem(buf), *p = e;
    if (i < 0) {
        *--p = '-';
        i = -i;
    }
    while (--p >= buf) {
        *p = "0123456789"[i % 10];
        i /= 10;
        if (i == 0)
            break;
    }
    return runtime·gostringn(p, e - p);
}

/* UTF-8クリーン */
static void ゴ〜(String s)
{
    runtime·printstring(s);
    runtime·prints("\n");
}

void main·main()
{
    FuncVal fv = { (void(*)(void))ゴ〜 };
    intgo i;
    for (i = 0; i < 10; i++) {
        String hello = runtime·gostring((byte*)"Hello from #");
        String is = itos(i);
        String s = runtime·catstring(hello, is);
        /* 第1引数に引数のサイズを与える */
        runtime·newproc(sizeof(String), &fv, s);
    }
    /* goroutineをyieldする前に終了するのを防ぐため適当にwaitする */
    runtime·tsleep(10000000000l, "wait");
}

String構造体がGoのstring型にそのまま対応していて、Goの文字列操作と等価のことをランタイムライブラリの関数呼び出しによって行うことができます。FuncVal構造体は、Goの関数型に相当します。

String構造体はruntime.hで次のように定義されています。

struct String
{
    byte*    str;
    intgo    len;
};


上記のコードの実行結果は次のようになります。

$ ./gohello
Hello from #0
Hello from #1
Hello from #2
Hello from #3
Hello from #4
Hello from #5
Hello from #6
Hello from #7
Hello from #8
Hello from #9

まとめ

  • GoランタイムをCから使うことができました
  • Pythonなど単純な処理系をGoランタイムの上に載せて動かすというようなことができそうで夢が広がります

次は najeira さんです。

*1:ちなみに、このツールチェイン、cgoでも使われます