2016年12月一覧

waifu2x-caffeとffmpegで動画を拡大するPowerShellスクリプト

どういうものか

waifu2xという、機械学習の結果を利用して、画像をきれいに拡大したり、圧縮時のノイズを除去したりするソフトウェアが公開されています。
そのWindows向けビルドがwaifu2x-caffeで、動画変換ソフトのFFmpegと組み合わせることで動画をきれいに拡大することができるため、PowerShellスクリプトで自動的に処理を流せるようにしました。

※waifu2xの処理は動画向けではないため、元々大きいサイズの動画があれば、もちろんそちらのほうが良いです。

各コマンドは「RICOH THETA Sの動画機能を4k相当に拡張する(waifu 2x で) – izm_11’s blog」を参考にさせていただきました。

対象フォルダに入れた動画を以下の流れで変換します。

  1. ffprobe(FFmpegに付属)で対象の動画情報を取得
  2. ffmpegで音声を分離
  3. ffmpegで動画を1コマずつ画像に分離
  4. waifu2x-caffeによる画像の拡大とノイズ除去
  5. ffmpegにより拡大済み画像と音声を結合してmp4(H264)の動画に変換
  6. 次の動画があれば1から処理

作った理由

古いMVをwaifu2x-caffeとffmpegで4Kにして見るのが最近の個人的トレンドなのですが、とてもとてもとても処理に時間がかかる。
それなりに高くて新しいグラフィックボード(GTX1080)で3分の動画を4Kに変換すると2時間20分かかりました。
で、PCに張り付いて手動でコマンドを打つのは大変なので、自動化することにしました。

ホントはスクリプト作り込むなら慣れてるLinuxでやりたかったけど、それなりのグラボを積んだPCはWindows10で、理由があり別OSを入れられない状況。
プラスして、Windows 10のデフォルトシェルがPowerShellになるという話を見たばかりなのでPowerShellで作りました。

必要ソフトウェアと動作確認バージョン

スクリプト

本スクリプトで損害が発生しても当方は責任を負いません。(おやくそく)

###
# 設定

$movieWidth = 3840 # 変換後の横サイズ
$movieHeight = 2160 # 変換後の縦サイズ

$ffmpegPath = 'C:\ffmpeg\bin\ffmpeg.exe' # ffmpeg.exeのパス
$ffprobePath = 'C:\ffmpeg\bin\ffprobe.exe' # ffprobe.exeのパス
$waifu2xCaffeCui = 'C:\waifu2x-caffe\waifu2x-caffe-cui.exe' # waifu2x-caffe-cui.exeのパス

$inputFolder = 'D:\INPUT' # 入力ディレクトリ
$outputFolder = 'D:\OUTPUT' # 出力ディレクトリ
$fileType = ('*.mp4','*.avi') # 処理対象の拡張子
$tmpFolder = 'D:\tmp' # 作業用フォルダ(一時的に画像等を展開するため、それなりの容量が必要)

# Waifu2xCaffeのオプション
# オプションについては公開元をご参照ください
# https://github.com/lltcggie/waifu2x-caffe
$noiseScaleMode = 'noise_scale'
$noiseLevel = 1
$processType = 'gpu'
$cropSize = 512
$modelDir = 'models/upconv_7_anime_style_art_rgb'

##
# 以下処理

$tmpSourceImageFolder = $tmpFolder + '\movie2x\img_s'
$tmpSourceImageFiles = $tmpSourceImageFolder + '\img_%07d.jpg'
$tmpConvertImageFolder = $tmpFolder + '\movie2x\img_c'
$tmpConvertImageFiles = $tmpConvertImageFolder + '\img_%07d.png'
$tmpSourceWaveFile = $tmpFolder + '\movie2x\base.wav'

function RunProcess($exePathe, $argObj){
  $pinfo = New-Object System.Diagnostics.ProcessStartInfo
  $pinfo.RedirectStandardOutput = $true
  $pinfo.UseShellExecute = $false
  $p = New-Object System.Diagnostics.Process

  $pinfo.FileName = $exePathe
  $pinfo.Arguments = $argObj
  $p.StartInfo = $pinfo
  $p.Start() | Out-Null

  Write-Output $p
}

Remove-Item $tmpSourceImageFolder -recurse 2> $null
Remove-Item $tmpConvertImageFolder -recurse 2> $null
Remove-Item $tmpSourceWaveFile 2> $null

Get-ChildItem -Path $inputFolder -Recurse -Include $fileType | ForEach-Object {
  $date = Get-Date -Format "yyyy/MM/dd HH:mm"
  Write-Host "$date ファイル : $_ の処理を開始しました。" 

  ###
  # 動画の情報をffprobeで取得
  $expArg = '"' + $_.Fullname + '" -show_entries format -show_streams -print_format json'
  $process = RunProcess $ffprobePath $expArg

  $stdout = $process.StandardOutput.ReadToEnd()
  $json = $stdout | ConvertFrom-Json

  $fpsStrings = $json.streams[0].avg_frame_rate -split '/'
  $fps = $fpsStrings[0] / $fpsStrings[1]
  $sourceMovieWidth = $json.streams[0].width
  $sourceMovieHeight = $json.streams[0].height

  ##
  # 作業ディレクトリの作成
  New-Item $tmpSourceImageFolder -type directory -Force | Out-Null
  New-Item $tmpConvertImageFolder -type directory -Force | Out-Null

  ##
  #ffmpegにより音声/画像の分離
  $expArg = ' -i "' + $_.Fullname + '" -vn "' + $tmpSourceWaveFile

  $process = RunProcess $ffmpegPath $expArg
  $process.WaitForExit()

  $expArg = ' -i "' + $_.Fullname + '" -f image2 -vcodec mjpeg -qscale 1 -qmin 1 -qmax 1 "' + $tmpSourceImageFiles

  $process = RunProcess $ffmpegPath $expArg
  $process.WaitForExit()

  ##
  #widthとheightのどちらに合わせるべきか判定
  if($movieWidth / $sourceMovieWidth -lt $movieHeight / $sourceMovieHeight){
    $scaleString = ' --scale_width ' + $movieWidth
  }else{
    $scaleString = ' --scale_height ' + $movieHeight
  }

  ##
  # waifu2x-caffeによる画像の拡大とノイズ除去
  $expArg = ' -i ' + $tmpSourceImageFolder + ' -o ' + $tmpConvertImageFolder + ' --model_dir ' + $modelDir + ' -m ' + $noiseScaleMode + $scaleString +' --noise_level ' + $noiseLevel + ' -p ' + $processType + ' -c ' + $cropSize

  $process = RunProcess $waifu2xCaffeCui $expArg
  $process.WaitForExit()

  ##
  # ffmpegにより画像と音声を結合してmp4(H264)の動画に変換
  $expArg = ' -r ' + $fps + ' -i "' + $tmpConvertImageFiles + '" -i "' + $tmpSourceWaveFile + '" -f mp4 -vcodec libx264 -bufsize 20000k -maxrate 25000k -s ' + $movieWidth + 'x' + $movieHeight + ' -aspect ' + $sourceMovieWidth + ':' + $sourceMovieHeight + ' -pix_fmt yuv420p "' + $outputFolder + '\' + $_.Name + '"'

  $process = RunProcess $ffmpegPath $expArg
  $process.WaitForExit()

  Remove-Item $tmpSourceImageFolder -recurse 2> $null
  Remove-Item $tmpConvertImageFolder -recurse 2> $null
  Remove-Item $tmpSourceWaveFile 2> $null

  $date = Get-Date -Format "yyyy/MM/dd HH:mm"
  Write-Host "$date ファイル : $_ の処理が完了しました。" 
}

Ubuntu16.04にnginx-rtmp-moduleを入れてRTMP配信をする

最終的にストリーミングキーにパスワードを入れる簡易認証について書くのですが、全て一つの投稿にすると長くなるのでまずは前段の環境構築から。
[追記] RTMPSできないと中間者攻撃対策できなくて、nginx-rtmp-moduleで対応していないということもあり一旦保留。

RTMPについて


ストリーミングでデータをやり取りするためのプロトコルで、ここでは映像をライブストリーミング(生放送)するために使用する。
通常利用するポートはTCP 1935番。

NginxでRTMPを扱うためのモジュールが公開されており、ソースに追加してMakeすることで利用可能。

確認環境


  • ubuntu 16.04

インストール手順


必要パッケージのインストール

makeするために必要なものを一通り準備。

sudo aptitude install build-essential libpcre3 libpcre3-dev libssl-dev unzip

Nginxのソースとrtmpモジュールをダウンロードしてmake/install

mkdir -p ~/work/nginx
cd ~/work/nginx

wget http://nginx.org/download/nginx-1.11.7.tar.gz
wget https://github.com/arut/nginx-rtmp-module/archive/master.zip

tar -zxvf nginx-1.11.7.tar.gz
unzip master.zip

cd nginx-1.11.7/

./configure --user=www-data --group=www-data --with-http_ssl_module --with-http_realip_module --add-module=../nginx-rtmp-module-master

make
sudo make install

サーバ起動時に自動起動するためSystemdにサービスとして登録

sudo vi /lib/systemd/system/nginx.service

/lib/systemd/system/nginx.service

[Unit]
Description=The NGINX HTTP and reverse proxy server
After=syslog.target network.target remote-fs.target nss-lookup.target

[Service]
Type=forking
PIDFile=/run/nginx.pid
ExecStartPre=/usr/local/nginx/sbin/nginx -t
ExecStart=/usr/local/nginx/sbin/nginx
ExecReload=/bin/kill -s HUP $MAINPID
ExecStop=/bin/kill -s QUIT $MAINPID
PrivateTmp=true

[Install]
WantedBy=multi-user.target

Nginxの設定

RTMPと、テスト用のプレーヤーページを用意。

sudo vi /usr/local/nginx/conf/nginx.conf

/usr/local/nginx/conf/nginx.conf

worker_processes  1;

user  www-data www-data;
pid        /run/nginx.pid;

events {
    worker_connections  1024;
}

rtmp {
    server {
        listen 1935;

        application app {
            live on;
            record off;
        }
    }
}

http {
    include       mime.types;
    default_type  application/octet-stream;
    server {
        listen 80;
        server_name  localhost;
        #デフォルトのルートディレクトリは /usr/local/nginx/html
        #変えるのめんどいのでそのまま使う
        location / {
            root   html;
            index  index.html index.htm;
        }
        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }
        index index.html index.htm;
    }
}

テスト用のプレーヤーページを作成

テストなのでCDNのvideo.jsを利用させていただき簡易的に作る。

sudo vi /usr/local/nginx/html/live.html

/usr/local/nginx/html/live.html

<!DOCTYPE html>
<html lang="en" class="">
<head>
    <title>Test Live</title>
    <link href="//vjs.zencdn.net/5.11/video-js.min.css" rel="stylesheet">
    <script src="//vjs.zencdn.net/5.11/video.min.js"></script>
</head>
<body>
    <video id="player" class="video-js vjs-default-skin" height="450" width="800" controls autoplay preload="auto">
        <source src="rtmp://(サーバのIP or ドメイン)/app/StreamKey" type='rtmp/mp4' />
    </video>
    <script>
        var player = videojs('#player');
    </script>
</body>
</html>

一応、所有者を変更しておく。

sudo chown www-data:www-data /usr/local/nginx/html/live.html

Nginxの起動と自動起動設定

sudo systemctl start nginx.service
sudo systemctl enable nginx.service

OBSで配信する際のURL指定


OBSClassicを使う場合は以下の値をいい感じに設定。

設定 -> 放送設定
FMS URL : rtmp://192.168.30.16/app
プレイパス/ストリームキー : StreamKey

使用方法は別途調べてください。

視聴


HTML5が見れるWEBブラウザで以下を開く。

http://(サーバのIP or ドメイン)/live.html

もちろん、配信していないと何も再生されないので注意。


TeamSpeak3をセットアップする時に使うクエリ

TeamSpeak3を立て直す時に、いつも同じようなクエリを叩くので残しておく。

ローカルからTelnetでTS3のクエリポートにアクセス


telnet localhost 10011

ログイン


パスワードはTeamSpeak3の初回起動時に表示されるやつ。
トークンと間違えないよう注意。

login client_login_name=serveradmin client_login_password=パスワード

バーチャルサーバを選択


デフォルトは1。
複数ポートで運営している方は適当に変更。

use sid=1

公開リストにサーバ情報を載せない


デフォルトだとサーバ立ててることをどこかのリストにおっぴろげてしまうらしい。
特定の用途や趣味により立てるのであれば、以下でオフにしたほうが良いかも。

serveredit virtualserver_weblist_enabled=0

ビューワからクエリで情報を参照できるようパーミッションを設定


TSStatusを使ったときのパーミッション設定。

servergroupaddperm sgid=1 permsid=b_virtualserver_info_view permvalue=1 permnegated=0 permskip=0
servergroupaddperm sgid=1 permsid=b_virtualserver_channel_list permvalue=1 permnegated=0 permskip=0
servergroupaddperm sgid=8 permsid=b_virtualserver_servergroup_list permvalue=1 permnegated=0 permskip=0
servergroupaddperm sgid=8 permsid=b_virtualserver_channelgroup_list permvalue=1 permnegated=0 permskip=0
servergroupaddperm sgid=1 permsid=b_virtualserver_select permvalue=1 permnegated=0 permskip=0

もしそれでもパーミッションが足りなくて「failed_permid=25」みたいなエラーが出る場合は以下を参照して適当に追加。
https://www.tsviewer.com/index.php?page=faq&id=12

ビューワからのクエリは時間による回数制限がかかるため、無制限にするならホワイトリストにIPを追加しておくこと。

  • /(TS3のインストールディレクトリ)/query_ip_whitelist.txt

セッションを切る


quit

UnityのSteamVRでViveタッチパッドの入力を4分割して取得

やりたかったこと

UnityでViveをいじって遊んでいる途中で、下の図みたいなバッテン型に分割して入力したくなった。

たいした内容じゃないけど、マニュアルで上手く見つけられなかったからメモしておきます。
実際にはコントローラに視覚的な目印を表示することも必要だと思う。

コード

コンポーネントとして「Controller (left)」または「Controller (right)」に追加して使用。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class TouchPadPress : MonoBehaviour {

    void Start () {

    }

    void Update()
    {
        SteamVR_TrackedObject trackedObject = GetComponent<SteamVR_TrackedObject>();
        var device = SteamVR_Controller.Input((int)trackedObject.index);

        if (device.GetPressDown(SteamVR_Controller.ButtonMask.Touchpad))
        {
            Vector2 touchPosition = device.GetAxis();
            if (touchPosition.y / touchPosition.x > 1 || touchPosition.y / touchPosition.x < -1)
            {
                if (touchPosition.y > 0)
                {
                    //タッチパッド上をクリックした場合の処理
                    Debug.Log("Press UP");
                }
                else
                {
                    //タッチパッド下をクリックした場合の処理
                    Debug.Log("Press DOWN");
                }
            }
            else
            {
                if (touchPosition.x > 0)
                {
                    //タッチパッド右をクリックした場合の処理
                    Debug.Log("Press RIGHT");
                }
                else
                {
                    //タッチパッド左をクリックした場合の処理
                    Debug.Log("Press LEFT");
                }
            }
        }
    }
}