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のエントリとして書かれました。次の記事はこちらです。
「:」を引数の名前と型の間に入れられるようしたりにする魔改造
とある言語が
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日に購入した。
こんな感じでアップル製品と一緒に並べてみて一通り満足してから、Garbage Collectorとしての利用を開始。
中側の筒 (写真左) の上から外側の筒 (写真右) をかぶせる形で使用する。このような構造になっているのは、ビニール袋を中側の筒にかぶせてあっても、外側の筒で覆い隠せるようにし、美観を損ねないようにするための配慮だ。
近影
- ビニール袋を中に入れても外側の筒で覆い隠せるので美観が損なわれないのは良い
- 中の筒は外側の筒よりも半径が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 ヘッダが追加されましたね!
まとめ
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関数に処理が渡るまでのステップは次のようなものです。
- OSによってバイナリがメモリにロードされる
- OSからエントリポイントとして_rt0_{arch}_{os} (例: _rt0_amd64_linux) が呼び出される (cf. src/cmd/ld/lib.c)
- _rt0_{arch}_{os} がOSから受け取ったコマンドライン引数 (argc, argv) をスタックに積んで _rt0_go を呼び出す
- _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
*1:ちなみに、このツールチェイン、cgoでも使われます