(2017/11/05追記)事情が古くなってきたので、「ゲーム動画のエンコード事情を整理」も参照してください。
(2015/12/21追記)VBScriptコードのAUCパス設定を修正、OneDriveでスクリプト公開。
(2014/08/17追記)VBScriptコードの比較ミスを修正。
AviUtlという有名なフリーソフトがあります。これは単体では動画のカットやフィルタなど簡単な編集ができますが、各種プラグインを導入することで対応フォーマットが増えたり、動画編集機能が大幅に強化されたり・・・とフリーソフトにしてはかなり優秀で、私もエクストルーパーズの動画をニコニコやYouTubeに投稿する際に利用させてもらいました。**「拡張編集」プラグインで編集し、「拡張 x264 出力(GUI) Ex」**プラグインで.mp4出力しています。編集と.mp4出力がこのソフト1本で完結するのですごくありがたい(もちろん必要な環境を準備すればの話)。
まあ、今回は動画編集じゃなくて、単にエンコードするだけの話なんですが。
HDキャプボ買ってから、エクストルーパーズの対戦動画をよく撮るようになったんですが、これがたまるたまる・・・。録画ファイルは.aviなのですが、そのままだと非常に重いので、.mp4にしないとすぐにHDDがパンパンになってしまいます。今まで、自分でファイル名を入力して、ひとつずつバッチ登録して、バッチ出力で寝てる間に一括してエンコードしてました。が、このバッチ登録は手で行う必要があったため、それがめんどくさくて後回しにしてると、動画がたまるたまる・・・。
で、自動化できないの!?というのが本題。
ここで紹介するスクリプトはOneDrive上に配置したので、下記からダウンロードして下さい。
自動でAviUtlの出力プラグインを利用する
肝心の方法ですが、調べたら、やっぱりありました。
大雑把にやってることを説明すると、AviUtlを外部から操作するための小規模なプログラムを集めた**「AviUtl Control」**をスクリプトから組み合わせて利用することで自動化を実現しています。「AviUtl Control」にサンプルとして用意されている sample1.vbs スクリプトでは、指定ディレクトリ直下のファイルすべてを出力プラグインによりエンコードしています。流れとしては、次のようになります。
- 起動しているAviUtlがあれば特定し、なければ起動する。
- 指定ディレクトリ直下のファイルのリストを得る。ファイルがなければ終了。
- 修正日時が最も古いファイルを指定の出力プラグインでエンコードし、指定ディレクトリに出力する。
- エンコード済みの入力ファイルを指定ディレクトリに移動。
- 2.に戻る。
このサンプルスクリプトは先頭にディレクトリの指定とかの記述があるので、ここを書き換えて実行すればいいわけです。スクリプトはVBScriptという言語で書かれていて、Windowsなら標準で使えるというものです。なので、今回必要なものをまとめると以下のようになります。
- AviUtl本体
- 利用したいAviUtl出力プラグイン(私は「拡張 x264 出力(GUI) Ex」を利用)
- AviUtl Control(と付属するサンプルスクリプト)
サンプルスクリプトの修正
サンプルスクリプトでも最低限の要求は満たしていると思いますが、サブディレクトリが考慮されないとか、チェックが甘いので途中で実行をやめたいときに困るとかいった問題があるので、これに対応したいと思います。修正版は以下になります。
'''
''' AviUtlエンコード&ファイル移動
'''
' あるフォルダの中のサブディレクトリ下も含めたすべてのファイルについて、
' プロファイル&出力プラグイン指定でエンコードし、
' エンコードが終わったら指定のフォルダに移動する
' 元のサンプルスクリプトのtypo修正およびウインドウが消滅したとき
' スクリプトを停止するようにした
' VBScript 参考
' http://vbscript.g.hatena.ne.jp/cx20/20100131/1264906231
' http://www.vacant-eyes.jp/Tips/twsh/170.aspx
Option Explicit
'On Error Resume Next
' キャプチャしたファイルがあるフォルダ(最後の文字は"\")
Const SOURCE_FOLDER = "C:\Users\user\Videos\amarec\_auto_encode_targets\"
' エンコードが終わったファイルを移すフォルダ(最後の文字は"\") ※必ず SOURCE_FOLDER と違う場所にする
Const MOVE_FOLDER = "C:\Users\user\Videos\amarec\_auto_encode_complete\"
' エンコードしたデータを出力するフォルダ(最後の文字は"\")
Const OUTPUT_FOLDER = "C:\Users\user\Videos\aviutl\_auto_encoded\"
' プロファイル番号(メニューの一番上が0)
Const OUTPUT_PROFILE = 0
' 出力プラグイン番号(メニューの一番上が0)
Const OUTPUT_PLUGIN = 1
' 出力ファイルの拡張子
Const OUTPUT_EXT = ".mp4"
' AviUtlのフルパス
Const AVIUTL_PATH = "C:\Program File?(x86)\AviUtl\aviutl100\aviutl.exe"
' AviUtl Controlのフルパス(最後の文字は"\")
Const AUC_FOLDER = "C:\Program File?(x86)\AviUtl\auc_15\"
' !!! 以下、編集の必要なし !!!
Dim WSHShell, Fs
Set WSHShell = WScript.CreateObject("WScript.Shell")
Set Fs = CreateObject("Scripting.FileSystemObject")
Function Hash()
Set Hash = CreateObject("Scripting.Dictionary")
End Function
Function Auc(command, arg)
Auc = WSHShell.Run("""" & AUC_FOLDER & "auc_" & command & ".exe"" " & arg, 2, True)
End Function
Function WindowExists()
WindowExists = (Auc("findwnd", "") <> 0) ' != 0
End function
' フォルダに含まれるファイルをすべて取得する(サブディレクトリ考慮)
Function GetFiles(objFolder)
Dim files, objSubFolder, objSubFolders, tmpFiles, tmpFile
Set files = Hash()
For Each tmpFile In objFolder.Files
If Fs.GetExtensionName(tmpFile.Name) <> "db" Then ' サムネイルファイルは除外
files.Add files.Count, tmpFile
End If
Next
Set objSubFolders = objFolder.SubFolders
For Each objSubFolder In objSubFolders
Set tmpFiles = GetFiles(objSubFolder)
For Each tmpFile In tmpFiles.Items
files.Add files.Count, tmpFile
Next
Next
Set GetFiles = files
End Function
' 再帰的ディレクトリ作成
Sub CreateFolder(folder)
Call WSHShell.Run("cmd /c mkdir " & """" & folder & """", 2, True)
End Sub
Dim hwnd, open
' AviUtlのウィンドウ番号を取得
hwnd = Auc("findwnd", "")
open = False
If hwnd = 0 Then
hwnd = Auc("exec", """" & AVIUTL_PATH & """")
If hwnd = 0 Then
Call WScript.Quit(-1)
End if
open = True
End if
' キャプチャしたファイルがあるフォルダが空になるまで繰り返す
Dim srcFiles, srcFile, date, input, inputDir, subDir, output, outputDir, isFirst
Do While True
Set srcFiles = GetFiles(Fs.GetFolder(SOURCE_FOLDER))
If srcFiles.Count = 0 Then
Exit Do
End If
' SOURCE_FOLDER で一番古いファイルを探す
isFirst = True
For Each srcFile In srcFiles.Items
If isFirst Then
date = srcFile.DateLastModified
input = srcFile.Path
isFirst = False
Elseif date > srcFile.DateLastModified then
date = srcFile.DateLastModified
input = srcFile.Path
End if
Next
' サブディレクトリを相対パスで得る
inputDir = Fs.GetParentFolderName(input)
If InStr(inputDir, SOURCE_FOLDER) = 1 Then ' SOURCE_FOLDER末尾の\まで含まれているならサブディレクトリ
subDir = Mid(inputDir, Len(SOURCE_FOLDER) + 1, Len(inputDir) - Len(SOURCE_FOLDER)) & "\"
Else
subDir = ""
End If
' 出力先ディレクトリがなければ作成
outputDir = OUTPUT_FOLDER & subDir
If Not Fs.FolderExists(outputDir) Then
'Fs.CreateFolder(outputDir)
CreateFolder outputDir
End If
output = outputDir & Fs.GetBaseName(input) & OUTPUT_EXT
'MsgBox(input & vbCrLf & output)
'Exit Do
' キャプチャしたファイルを開く
If Not WindowExists() Then
Exit Do
End If
Call Auc("open", CStr(hwnd) & " """ & input & """")
Call WScript.Sleep(3000)
' プロファイルを設定する
If Not WindowExists() Then
Exit Do
End If
Call Auc("setprof", CStr(hwnd) & " " & CStr(OUTPUT_PROFILE))
Call WScript.Sleep(1000)
' 出力プラグインから出力する
If Not WindowExists() Then
Exit Do
End If
Call Auc("plugout", CStr(hwnd) & " " & CStr(OUTPUT_PLUGIN) & " """ & output & """")
' 出力が終わるまで待つ
If Not WindowExists() Then
Exit Do
End If
Call Auc("wait", CStr(hwnd))
' 出力ファイルの存在確認
If Not Not Fs.FileExists(outputDir) Then
Exit Do
End If
' ファイルを閉じる
If Not WindowExists() Then
Exit Do
End If
Call Auc("close", CStr(hwnd))
Call WScript.Sleep(3000)
' エンコードが終わったファイルを移動する(出力先ディレクトリがなければ作成)
outputDir = MOVE_FOLDER & subDir
If Not Fs.FolderExists(outputDir) Then
'Fs.CreateFolder(outputDir)
CreateFolder outputDir
End If
Call Fs.MoveFile(input, outputDir)
Loop
' スクリプトで起動したAviUtlを終了する
If open Then
If WindowExists() Then
Call Auc("exit", CStr(hwnd))
End If
End if
元のスクリプトにtypoがあったのも修正しました(ver1.5)。VBScriptは初めて触ったんですが、言語仕様は難しくないのに、独特の癖があって意外と苦労しました・・・。主に参考にしたのは、以下のサイト。
- VBScript 基礎文法最速マスター - CX’s VBScript Diary - VBScript グループ
- Vacant-Eyes Tips - Windows Script Host - Scripting.Dictionaryオブジェクトを使う
あとは、FileSystemObjectの使い方を調べたとかですかね。
修正版では、サブディレクトリを考慮して動きます。入力ファイルが「{指定入力元ディレクトリ}/{サブディレクトリ}」にあるとすると、出力ファイルは「{指定出力先ディレクトリ}/{サブディレクトリ}」に吐き出され、エンコード済みの入力ファイルは「{指定移動先ディレクトリ}/{サブディレクトリ}」に移動されます。サブディレクトリの構造を維持するということです。対応するサブディレクトリが存在しなければ、作成します。
気をつけなければいけないのは、「指定した入力元ディレクトリ下にファイルが存在しなくなること」が終了条件なので、エンコード後のファイルの退避ができなければ終了しません(元のスクリプトも同じです)。つまり、入力元と移動先は別の場所にする必要があります。修正版は、起動中のAviUtlが存在するかどうか頻繁に確認し、なければ処理を中断する処理も追加しています。
他に気をつけたいのは、AviUtlを複数起動しているときの対応をしていないので、複数起動しないこと。それと、出力ファイルの存在確認を入れてみましたが、出力途中でも残る可能性があるので(実際、私の場合は残る)、**途中でやめたかったら、速やかにエンコードの中止とAviUtlの終了を行ってください。**AviUtlが終了すれば消滅を確認した後にスクリプトも終了します。
Pythonで書き直してみた
VBScriptクソクソ!とか叫びながら、ついでにPythonで書き直してみました。実行するにはPython 2.x系を導入している必要があります。
# -*- coding: utf-8 -*-
''' AviUtlエンコード&ファイル移動 '''
# あるディレクトリの中のサブディレクトリ下も含めたすべてのファイルについて、
# プロファイル&出力プラグイン指定でエンコードし、
# エンコードが終わったら指定のディレクトリに移動する
#################################################
# 設定
# パスの区切り文字は円マーク(バックスラッシュ)ではなく
# スラッシュを使う
# (raw文字列でも\uに反応しちゃうから)
#################################################
# キャプチャしたファイルがあるディレクトリ
INPUT_DIR = ur'C:/Users/user/Videos/amarec/_auto_encode_targets'
# エンコードが終わったファイルを移すディレクトリ
COMPLETED_DIR = ur'C:/Users/user/Videos/amarec/_auto_encode_complete'
# エンコードしたデータを出力するディレクトリ
OUTPUT_DIR = ur'C:/Users/user/Videos/aviutl/_auto_encoded'
# プロファイル番号(メニューの一番上が0)
OUTPUT_PROFILE = 0
# 出力プラグイン番号(メニューの一番上が0)
OUTPUT_PLUGIN = 1
# 入力ファイルの拡張子
INPUT_EXTENSIONS = [u'.avi', u'.mp4']
# 出力ファイルの拡張子
OUTPUT_EXTENSION = u'.mp4'
# AviUtlのフルパス
AVIUTL_PATH = ur'C:/Program File (x86)/AviUtl/aviutl100/aviutl.exe'
# AviUtl Controlのフルパス
AUC_DIR = ur'C:/Program File (x86)/AviUtl/aviutl100/auc_15'
#################################################
# 以下、処理開始
#################################################
import os, sys, codecs, locale, subprocess, time, shutil, traceback
# デリミタ(/)を末尾に追加
addDelimiter = lambda s: s if s.endswith('/') else s + '/'
INPUT_DIR = addDelimiter(INPUT_DIR)
COMPLETED_DIR = addDelimiter(COMPLETED_DIR)
OUTPUT_DIR = addDelimiter(OUTPUT_DIR)
print u'以下のパラメータが与えられました:'
print u'INPUT_DIR = %s' % INPUT_DIR
print u'COMPLETED_DIR = %s' % COMPLETED_DIR
print u'OUTPUT_DIR = %s' % OUTPUT_DIR
print u'OUTPUT_PROFILE = %s' % OUTPUT_PROFILE
print u'OUTPUT_PLUGIN = %s' % OUTPUT_PLUGIN
print u'INPUT_EXTENSIONS = %s' % INPUT_EXTENSIONS
print u'OUTPUT_EXTENSION = %s' % OUTPUT_EXTENSION
print u'AVIUTL_PATH = %s' % AVIUTL_PATH
print u'AUC_DIR = %s' % AUC_DIR
print
enc = lambda u: u.encode(locale.getpreferredencoding())
dec = lambda s: s.decode(locale.getpreferredencoding())
quote = lambda s: u'"%s"' % s
replace = lambda s: s.replace(u'/', u'\\')
def run(command):
p = subprocess.Popen(command, stdout = subprocess.PIPE)
output = p.stdout.read()
p.wait()
return output.strip()
#run = lambda command: subprocess.call(command, shell = True)
class AUC(object):
def __init__(self):
self._isAutoLaunch = False
self._hwnd = self.findwnd()
if self._hwnd == 0:
self._hwnd = self.launch(AVIUTL_PATH)
self._isAutoLaunch = (self._hwnd != 0)
@staticmethod
def _auc(command, *args):
args = u' '.join(map(unicode, args))
command = u'auc_%s.exe %s' % (command, args)
print 'Run: ' + command
return dec(run(enc(command)))
def isAutoLaunch(self):
return self._isAutoLaunch
def exists(self):
return self._hwnd != 0 and self._hwnd == self.findwnd()
def launch(self, path):
return int(self._auc(u'exec', quote(path)))
def findwnd(self):
return int(self._auc(u'findwnd'))
def exit(self):
self._auc(u'exit', self._hwnd)
self._hwnd = 0
self._isAutoLaunch = False
def open(self, file):
self._auc(u'open', self._hwnd, quote(replace(file)))
def openadd(self, file):
self._auc(u'openadd', self._hwnd, quote(replace(file)))
def audioadd(self, file):
self._auc(u'audioadd', self._hwnd, quote(replace(file)))
def close(self):
self._auc(u'close', self._hwnd)
def setprof(self, profile):
self._auc(u'setprof', self._hwnd, profile)
def aviout(self, file):
self._auc(u'aviout', self._hwnd, quote(replace(file)))
def wavout(self, file):
self._auc(u'wavout', self._hwnd, quote(replace(file)))
def plugout(self, plugin, file):
self._auc(u'plugout', self._hwnd, plugin, quote(replace(file)))
def plugbatch(self, plugin, file):
self._auc(u'plugbatch', self._hwnd, plugin, quote(replace(file)))
def wait(self):
self._auc(u'wait', self._hwnd)
def getFiles(dir):
files = []
for file in os.listdir(dir):
path = dir + file
if os.path.isfile(path):
if os.path.splitext(path)[1] in INPUT_EXTENSIONS:
files.append(path)
else:
files += getFiles(addDelimiter(path))
return files
def getSubDir(path):
dir = addDelimiter(os.path.split(path)[0])
if dir.startswith(INPUT_DIR):
return dir[len(INPUT_DIR):]
else:
return ''
try:
os.chdir(AUC_DIR) # カレントディレクトリをAviUtl Controlのある場所へ変更
files = getFiles(INPUT_DIR)
files.sort()
if len(files) == 0:
print u'処理すべきファイルが見つかりませんでした'
raw_input('Press Enter Key...')
exit()
auc = AUC()
getmtime = os.path.getmtime
print u'次のファイルを取得しました:'
for file in files: print '-> ' + file
print
while auc.exists() and len(files) > 0:
# 一番古いファイルから処理する
inputFile = None
mtime = 0
for file in files:
if inputFile == None or mtime > getmtime(file):
inputFile = file
mtime = getmtime(file)
files.remove(inputFile)
# サブディレクトリ取得とか出力先ディレクトリ設定とか
basename = os.path.basename(inputFile)
subDir = getSubDir(inputFile)
completedDir = COMPLETED_DIR + subDir if COMPLETED_DIR != None else ''
completedFile = completedDir + basename if COMPLETED_DIR != None else ''
outputDir = OUTPUT_DIR + subDir
outputFile = outputDir + os.path.splitext(basename)[0] + OUTPUT_EXTENSION
print u'次の設定でエンコードを開始します:'
print u'入力ファイル: %s' % inputFile
print u'出力ファイル: %s' % outputFile
print u'エンコード後ファイル: %s' % completedFile
print
# ファイルオープン、プロファイルの設定
if not auc.exists(): break
auc.open(inputFile)
print u'ファイルをオープンしました'
if not auc.exists(): break
auc.setprof(OUTPUT_PROFILE)
time.sleep(1.000)
print u'プロファイルを設定しました'
# 出力先ディレクトリがなければ作成
if not os.path.exists(outputDir):
os.makedirs(outputDir)
print u'出力先ディレクトリを作成しました'
# 出力プラグインによる出力
if not auc.exists(): break
auc.plugout(OUTPUT_PLUGIN, outputFile)
print u'エンコード中です...'
if not auc.exists(): break
auc.wait()
# ファイルクローズ
if not os.path.exists(outputFile):
break
print u'エンコードが完了しました'
# ファイルクローズ
if not auc.exists(): break
auc.close()
print u'ファイルをクローズしました'
time.sleep(3.000)
# エンコードが終わったファイルを移動する
# 移動先ディレクトリがなければ作成
if COMPLETED_DIR != None:
if not os.path.exists(completedDir):
os.makedirs(completedDir)
print u'移動先ディレクトリを作成しました'
if not auc.exists(): break
shutil.move(inputFile, completedFile)
print u'エンコードが終了したファイルを移動しました'
print
if auc.isAutoLaunch() and auc.exists():
auc.exit()
except:
traceback.print_exc()
raw_input('Press Enter Key...')
若干、機能を追加していますが、基本的にやってることは変わりません。エラーが起きたら、エラーを表示してエンターキーが押されるまで待機します。エラー内容を表示するだけでもないよりかなりマシだと思う。
ちゃんと動作確認してないけど、エンコード済みのファイルを移動させたくなかったら、COMPLETED_DIR を None にすれば多分対応してます。最初に作ったファイルリストが空になるまで処理を繰り返す形に変えたので、入力元ファイルを移動させなくてもいいってことです。
あと、これも試してませんが、plugout を plugbatch に置き換えれば、エンコードはせずにバッチ登録だけしてくれる筈です。そうすれば、一括でバッチ登録してくれるようになりますね。バッチ登録リストはAviUtlの方で管理していて、処理が完了していないものは自分でリストから消さない限りちゃんと保持してくれるメリットがあります。このように、あとでまとめてバッチ出力する場合では、入力ファイルを移動されると困るので、COMPLETED_DIR を None にしましょう。
「AviUtl Control」には、各実行ファイルのソースコードも付属しています。中身見ればわかりますが、やってることは単純です。PythonにはWin32APIのモジュールも用意されているので、Pythonだけですべて実装することもできそうですね。
ゴミの削除
エンコード済みのファイルや、エンコードの際に出力された必要のないファイル(「拡張 x264 出力(GUI) Ex」を使うと.statsファイルと.stats.mbtreeファイルができる)を消したいので、バッチファイル作りました。これを実行すると、バッチファイルの存在するディレクトリ以下に対して、削除処理を行います。
.aviファイルの削除
del /Q /S *.avi
.statsファイルと.stats.mbtreeファイルの削除
del /Q /S *.stats
del /Q /S *.stats.mbtree
おしまい。
これで今まで以上に動画撮りまくれるぞー!やったー!