MP4動画のバイナリファイルをコマンドラインでいじる方法のメモ


MP4の動画ファイルの構造を調べたりいじったりしてみたかったのですが、その際に使ったツールなどをメモとして残しておきます。作業環境はMac (OS X El Capitan) です。

# あらゆるファイルを16進ダンプする

## hexdumpを使う方法
OS X標準の hexdumpコマンドでできます。

```
> hexdump target.bin
0000000 0a 69 6e 74 20 6d 61 69 6e 28 29 20 7b 0a 09 70
0000010 72 69 6e 74 66 28 22 48 65 6c 6c 6f 20 57 6f 72
0000020 6c 64 22 29 3b 0a 7d 0a
0000028
```
### 文字情報も一緒にダンプする
文字を含んでいる可能性のあるバイナリを見るときは、16進ダンプと一緒に文字情報も出すと便利。-C をつけてください。

```
> hexdump -C target.bin
00000000  0a 69 6e 74 20 6d 61 69  6e 28 29 20 7b 0a 09 70  |.int main() {..p|
00000010  72 69 6e 74 66 28 22 48  65 6c 6c 6f 20 57 6f 72  |rintf("Hello Wor|
00000020  6c 64 22 29 3b 0a 7d 0a                           |ld");.}.|
00000028
```

### 同じ内容の行をまとめない
hexdumpはデフォルトでは、連続する同じ出力行をひとまとめにしてしまいます。そのため、0が連続するデータなどをダンプすると、その部分が縮退されてしまったりします。

以下は 0x20 (スペース)がたくさん入っているファイルをダンプした様子です。

```
> hexdump target.bin
0000000 0a 69 6e 74 20 6d 61 69 6e 28 29 20 7b 0a 09 70
0000010 72 69 6e 74 66 28 22 48 65 6c 6c 6f 20 57 6f 72
0000020 6c 64 22 29 3b 0a 7d 0a 20 20 20 20 20 20 20 20
0000030 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20
*
0000080 20 20 20 20 20 20 20 0a
0000088
```

アドレス0x000040〜0x000070の行が省略されています。これを止めるためには、 -v オプションをつけます。

```
> hexdump -v target.bin
0000000 0a 69 6e 74 20 6d 61 69 6e 28 29 20 7b 0a 09 70
0000010 72 69 6e 74 66 28 22 48 65 6c 6c 6f 20 57 6f 72
0000020 6c 64 22 29 3b 0a 7d 0a 20 20 20 20 20 20 20 20
0000030 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20
0000040 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20
0000050 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20
0000060 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20
0000070 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20
0000080 20 20 20 20 20 20 20 0a
0000088
```


### アドレス情報を含まずに、シンプルな16進文字列を得る
hexdumpの単純なオプションではできません。-eオプションで出力フォーマットを自由に決められるので、それを使って実現します。

``` sh:改行せずに一行で出力
> hexdump -v -e '1 "%02x"' target.bin
0a696e74206d61696e2829207b0a097072696e7466282248656c6c6f20576f726c6422293b0a7d0a
```

v オプションをつける必要があるのでご注意ください。上に述べたように hexdump はデフォルトでは連続する同じ出力行をひとまとめにしてしまうので、0が連続するデータなどをダンプすると、その部分が縮退されてしまったりします。

大きなバイナリファイルの場合は、途中で改行したほうが見やすそうです。

``` sh:16バイトずつ改行
> hexdump -v -e '16/1 "%02x" "\n"' target.bin
0a696e74206d61696e2829207b0a0970
72696e7466282248656c6c6f20576f72
6c6422293b0a7d0a
```

## xxd コマンドを使う方法
hexdumpではなく OS Xに標準でインストールされている xxd コマンドでもできます。

### 文字情報も一緒にダンプする
単純にコマンドにファイルを渡すだけです。

```
> xxd target.bin
0000000: 0a69 6e74 206d 6169 6e28 2920 7b0a 0970  .int main() {..p
0000010: 7269 6e74 6628 2248 656c 6c6f 2057 6f72  rintf("Hello Wor
0000020: 6c64 2229 3b0a 7d0a 2020 2020 2020 2020  ld");.}.
0000030: 2020 2020 2020 2020 2020 2020 2020 2020
0000040: 2020 2020 2020 2020 2020 2020 2020 2020
0000050: 2020 2020 2020 2020 2020 2020 2020 2020
0000060: 2020 2020 2020 2020 2020 2020 2020 2020
0000070: 2020 2020 2020 2020 2020 2020 2020 2020
0000080: 2020 2020 2020 200a                             .
```

### アドレス情報を含まずに、シンプルな16進文字列を得る
-p オプション(plainオプション)をつけるだけです。hexdumpのようなフォーマットの複雑さや -v の罠などもなくとてもシンプルです。

```
> xxd -p target.bin
0a696e74206d61696e2829207b0a097072696e7466282248656c6c6f2057
6f726c6422293b0a7d0a2020202020202020202020202020202020202020
202020202020202020202020202020202020202020202020202020202020
202020202020202020202020202020202020202020202020202020202020
2020202020202020202020202020200a
```

なんか、hexdumpより xxdのほうが使いやすいかも。。。。

# MP4動画の構造をダンプする

MP4動画ファイルは Box と呼ばれるバイナリブロックの集まりでできています。Boxは、

- 4文字のASCIIで表される型を持っている
- 子供のBoxを内包することができる
- 4バイト(32bit)の長さ情報を持っている
	- 長さには内包している子供のBoxの長さも含んでいるため、知らない構造のBoxでも簡単に読み飛ばすことができる

という特徴があります。これらを図にまとめてみました。

![mp4-box.png](https://qiita-image-store.s3.amazonaws.com/0/41616/4ca89787-aba5-5c2d-843d-137c987adaeb.png "mp4-box.png")


実際のMP4動画ファイルに対してBox構造をダンプしてみてみたい場合は、 MP4Box というコマンドを使うと良いです。homebrewからインストールできます。

```
> brew install mp4box
```

これで MP4Box というコマンドがインストールされます。

## XMLでBoxの構造を出力する
```
> MP4Box -std -diso test.mp4
<?xml version="1.0" encoding="UTF-8"?>
<!--MP4Box dump trace-->
<IsoMediaFile Name="test.mp4">
<FileTypeBox MajorBrand="isom" MinorVersion="512">
<BoxInfo Size="32" Type="ftyp"/>
<BrandEntry AlternateBrand="isom"/>
<BrandEntry AlternateBrand="iso2"/>
<BrandEntry AlternateBrand="avc1"/>
<BrandEntry AlternateBrand="mp41"/>
</FileTypeBox>
<FreeSpaceBox size="0">
<BoxInfo Size="8" Type="free"/>
</FreeSpaceBox>
<MediaDataBox dataSize="1051459">
<BoxInfo Size="1051467" Type="mdat"/>
</MediaDataBox>
<MediaDataBox dataSize="0">
<BoxInfo Size="8" Type="mdat"/>
</MediaDataBox>
<MovieBox>
<BoxInfo Size="4221" Type="moov"/>
    :
```

なぜか標準エラー出力に出てくるので、lessで受けたりファイルに出力したい場合は交通整理する必要がある。

``` sh:Bashの場合
> MP4Box -std -diso test.mp4 2>&1 | less
> MP4Box -std -diso test.mp4 2> output.xml
```

``` sh:tcshの場合
> MP4Box -std -diso test.mp4 |& less
> MP4Box -std -diso test.mp4 >& output.xml
```

出力結果は結構巨大なXMLで、インデントもされていません。ファイルに落としてブラウザに突っ込むと開いたり閉じたりできて見やすいです。

![スクリーンショット 2016-01-28 17.18.19.png](https://qiita-image-store.s3.amazonaws.com/0/41616/1b124ac6-c64b-5d45-3fa6-dca976140364.png "スクリーンショット 2016-01-28 17.18.19.png")

# あらゆるバイナリファイルをテキストエディタで好きなように加工する

## xxd -r オプション
16進ダンプしたファイルをもう一度バイナリファイルに戻せばいいよね、と思って調べたら **xxd の -r オプション(reverseオプション)で簡単にできるじゃないですか!**

試しに、アドレスと文字情報付きのダンプ結果を出力してそのまま -r で元に戻してみました。MD5値が変わっていないのがわかると思います。

```
> md5 target.bin
MD5 (target.bin) = 3314ce32426c77a086fa7290a9374fb4

> xxd target.bin | xxd -r | md5
3314ce32426c77a086fa7290a9374fb4
```

手動でいじりたい場合はプレーンな 16進ダンプファイルの方が使いやすいですね。

```
> echo -n "Hello" | xxd -p
48656c6c6f
```

スペース(0x20)を入れて繰り返してみます。

```
> echo "48656c6c6f2048656c6c6f" | xxd -p -r
Hello Hello
```

## MP4のバイナリファイルを手動でいじって、オレオレUUIDタグを追加してみる。

MP4の Box にはタイプが 'uuid' というものがあります。このBoxはランダムに生成した UUID値を含めることで、他の人とぶつからない自分だけの Box を作ることができるという便利なものです。

フォーマットはこんな感じです。

![uuid-box.png](https://qiita-image-store.s3.amazonaws.com/0/41616/02ea62c8-3df2-0363-e4ee-e12e4a7f43b6.png "uuid-box.png")

これを使って、 "Hello"という5バイトのデータを持ったオレオレBoxを作ってみます。

```
> echo "0000001d" > mybox.hex
> echo -n "uuid" | xxd -p >> mybox.hex
> uuidgen >> mybox.hex
> echo -n "Hello" | xxd -p >> mybox.hex
> cat mybox.hex
0000001d
75756964
b310c7b9-2bd3-4908-b6d5-03542cdeb01d
48656c6c6f
```

これを xxd -r でバイナリにしてみます。

```
> xxd -p -r mybox.hex | xxd
0000000: 0000 001d 7575 6964 b310 c7b9 2bd3 4908  ....uuid....+.I.
0000010: b6d5 0354 2cde b01d 4865 6c6c 6f         ...T,...Hello
```

uuidgenで作った UUID値の所にハイフンとか混ざってましたけど、問題なく読み飛ばしてくれました。思った通りのバイナリが作れています。

ではこれを MP4ファイルの末尾にくっつけてみます。まずは MP4ファイル全体を hexファイル化します。

```
> xxd -p test.mp4 > test-with-mybox.mp4.hex
```

テキストエディタで追記します。

```
> vi test-with-mybox.mp4.hex
    :
0000006075647461000000586d657461000000000000002168646c720000
0000000000006d6469726170706c0000000000000000000000002b696c73
7400000023a9746f6f0000001b6461746100000001000000004c61766635
332e32342e32

0000001d
75756964
b310c7b9-2bd3-4908-b6d5-03542cdeb01d
48656c6c6f
```

適当に改行やスペースを入れたりしても問題ありません。

加工したファイルをバイナリに戻しましょう。

```
> xxd -p -r test-with-mybox.mp4.hex > test-with-mybox.mp4
```

できたMP4は問題なく動画として再生できました。


また、タグが正しく認識できるか確かめるために、もう一度 MP4Box で解析してみます。

```
> MP4Box -std -diso test-with-mybox.mp4
    :
</HandlerBox>
<ItemListBox>
<BoxInfo Size="43" Type="ilst"/>
<ToolBox value="Lavf53.24.2" >
<FullBoxInfo Version="0" Flags="0x1"/>
<BoxInfo Size="35" Type=".too"/>
</ToolBox>
</ItemListBox>
</MetaBox>
</UserDataBox>
</MovieBox>
<!--ERROR: Invalid Top-level Box Found ("uuid")-->
<UnknownBox>
<BoxInfo Size="29" UUID="{B310C7B9-2BD34908-B6D50354-2CDEB01D}"/>
</UnknownBox>
</IsoMediaFile>
```

**UnknownBox** という形ではありますが、ファイルの最後の部分に追加したオレオレUUID Boxが正しく読めているのがわかります。 

# 所感

- xxdいい感じ
- MP4のファイル構造はとても理解しやすくいじりやすくわかりやすい。
	- (これに比べてJPEGやEXIFときたら……)