スマホで撮影した映像をffmpegで正しい向きに変換する（＋縮小変換）

スマホで撮影した映像は、デバイスの回転情報が入っており、PCなど端末によっては正常ではない向きで映像が再生される場合があります。

そこでffmpegを使い、映像の回転情報を取得し、映像に回転変換をかけて正常に再生されるようにしてみます。

## ビデオ情報の取得

ffmpegパッケージに同梱されているffprobeプログラムを使うと映像情報を取得することができます。

```bash
ffprobe ビデオファイル名
```

しかしこのままではプログラムから使いにくいのでJSONフォーマットで出力します。さらに標準エラー出力に設定情報が出力されて余計なので捨てます。

```bash
ffprobe -show_streams -print_format json ビデオファイル名 2>/dev/null
```

出力例

```json
{
    "streams": [
        {
            "index": 0,
            "codec_name": "h264",
            "codec_type": "video",
            ・・・
            "width": 1920,
            "height": 1080,
            ・・・
            },
            "tags": {
                "rotate": "90",
                ・・・
            }
        }
    ]
}
```

回転情報はstreams[]->tags->rotate、幅はstreams[]->width、高さはstreams[]->heightに入ります。
streamsは配列になっていて、codec_typeが「video」の要素がビデオの情報です。

rotateが90というのは、左に90度回転した映像であるということなので、ffmpegによって右に90度回転させ、さらに回転情報を削除すれば、どんな端末でも正常な向きで再生されるわけです。

## 回転変換をかけて出力する

### rotateが90の場合

```bash
ffmpeg -i 入力ファイル名 -vf transpose=1 -metadata:s:v:0 rotate=0 出力ファイル名
```

```-vf transpose=1```で映像を右に90度回転します。
```-metadata:s:v:0 rotate=0```で元の回転情報を削除します。

### rotateが270の場合

```bash
ffmpeg -i 入力ファイル名 -vf transpose=2 -metadata:s:v:0 rotate=0 出力ファイル名
```

```-vf transpose=1```で映像を左に90度回転します。

### rotateが180の場合

```bash
ffmpeg -i 入力ファイル名 -vf hflip,vflip -metadata:s:v:0 rotate=0 出力ファイル名
```

```-vf hflip,vflip```で映像を左右に反転し、さらに上下に反転しています。

## 映像変換プログラムのサンプル

PHPで変換サンプルを作りました。ffmpegとffprobeにパスが通っていれば動くと思います。
使い方はコメントを参照してください。

```php:convert_movie.php
<?php
// ムービー変換
// php convert_movie.php infile outfile (long_side)
// long_sideは長辺の長さ。省略時は640。

// 引数チェック
if (count($argv) != 3 && count($argv) != 4) {
  echo "引数の数が違います。\n";
  echo "使い方: php convert_movie.php infile outfile (long_side)\n";
  exit(1);
}

$infile = $argv[1];
$outfile = $argv[2];
if (isset($argv[3])) $long_side = $argv[3];
else $long_side = 640;

// 入力ファイルチェック
exec("ffprobe -show_streams -print_format json $infile 2>/dev/null", $out, $rc);
if ($rc != 0) {
  echo "入力ファイルエラー。\n";
  exit(1);
}

$json = implode("\n", $out);
$info = json_decode($json);
$stream = null;
foreach ($info->streams as $s) {
  if ($s->codec_type == 'video') {
    $stream = $s;
    break;
  }
}

if (!$stream) {
  echo "動画情報が見つかりません。\n";
  exit(1);
}

// 入力ファイルの幅・高さ・回転角度を取得する。
if (!$stream->width) {
  echo "入力ファイルの幅が取得できません。\n";
  exit(1);
}
$width = $stream->width;

if (!$stream->height) {
  echo "入力ファイルの高さが取得できません。\n";
  exit(1);
}
$height = $stream->height;

if (isset($stream->tags->rotate)) $rotate = $stream->tags->rotate;
else $rotate = '0';

// 回転角度を元に出力ファイルを回転する。
switch ($rotate) {
  case '90':
    $vf = '-vf transpose=1';
    $swapwh = true;
    break;
  case '180':
    $vf = '-vf hflip,vflip';
    $swapwh = false;
    break;
  case '270':
    $vf = '-vf transpose=2';
    $swapwh = true;
    break;
  default:
    $vf = '';
    $swapwh = false;
}

echo "入力ファイル 幅:$width 高さ:$height 回転角度:$rotate\n";

// 長辺を元に縮小する。
// 指定された長辺より短い場合はそのまま。
if ($width > $height) {
  if ($width > $long_side) {
    $height = (int)($height * $long_side / $width);
    $width = $long_side;
  }
} else {
  if ($height > $long_side) {
    $width = (int)($width * $long_side / $height);
    $height = $long_side;
  }
}

// 回転角度が90度・270度のときは幅と高さを逆転する。
if ($swapwh) {
  $size = $height . 'x' . $width;
} else {
  $size = $width . 'x' . $height;
}

exec("ffmpeg -y -i $infile $vf -vcodec libx264 -metadata:s:v:0 rotate=0 -s $size $outfile 2>&1", $out, $rc);
if ($rc != 0) {
  echo "動画変換エラー。\n";
  exit(1);
}

exit(0);
```