Aviutlの入力ファイルチェック

Aviutlで出力する際、入力ファイルと出力ファイルを同じにすると、読み込むファイルに書き込んでしまい、エラー終了するのはもちろん、入力ファイルを破壊してしまう可能性がある。

Aviutlには本来これを防止するエラーメッセージが備わっており、きちんとエラーが表示される。

aviutl_x264guiEx_error_20220215.png

しかし、自分が使わないので教えていただいたのだが、どうも拡張編集から読み込んでいる入力ファイルについてはチェックが行われず、入力ファイルを破壊してしまうことがあるようだ。


となれば、追加で出力プラグイン側でエラー処理を行う必要がある。この際、もちろんAviutlがどんなファイルを開いているかのリストが必要なわけだけど、AviutlのSDKにおいて、出力プラグインではAviutlの入力ファイルを取得することはできない。これは、本来出力プラグインには必要のない情報であるからだ。

そこで、ちょっと面倒だけど、下記のようにしてAviutlの入力ファイル名を得て、これを出力ファイル名と比較するようにした。これがx264guiEx 2.70だった。

1. 自プロセスのファイルハンドル列挙 (createProcessHandleList)
2. ファイルハンドルからファイル名取得 (createProcessOpenedFileList)
3. 入力ファイル名と出力ファイル名の比較 (std::filesystem::equivalent)




ところが、最近はInputPipePluginというのがあるらしく、別プロセスで入力ファイルを開いているケースがあるようだ。そうするとこの仕組みでは対応しきれない。

そこで、x264guiEx 2.71では、Aviutlの子プロセスについてもチェックするようにした。



0. Aviutl自身とその子プロセスのプロセスID取得
0-1. Aviutl自身のプロセスID取得 (GetCurrentProcessId())
0-1. システムのプロセスID列挙とその親プロセス取得
0-2. Aviutlを親に持つプロセスの列挙

まず、Aviutl自身のプロセスIDはGetCurrentProcessIdで取れるとして、その子プロセスを取得しないといけない。

そこで全プロセスのIDとその親プロセスをCreateToolhelp32Snapshotで取得して、その関係から指定したプロセスIDの子プロセスをみつける関数を作成した。あんまり効率とか考えていない適当な実装だが、こんな感じで自分の子プロセスのリストを作ることができた。


#include
#include
#include
#include

static bool check_parent(
const size_t check_pid, const size_t target_pid,
const std::unordered_map& map_pid) {
if (check_pid == target_pid) return true;
if (check_pid == 0) return false;
auto key = map_pid.find(check_pid);
if (key == map_pid.end()
|| key->second == 0
|| key->second == key->first) return false;
return check_parent(key->second, target_pid, map_pid);
};

using unique_handle =
std::unique_ptr::type, std::function>;

static std::vector createChildProcessIDList(const size_t target_pid) {
auto h = unique_handle(
CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0),
[](HANDLE h) { CloseHandle(h); });

PROCESSENTRY32 pe = { 0 };
pe.dwSize = sizeof(PROCESSENTRY32);

std::unordered_map map_pid;
if (Process32First(h.get(), &pe)) {
do {
map_pid[pe.th32ProcessID] = pe.th32ParentProcessID;
} while (Process32Next(h.get(), &pe));
}

std::vector list_childs;
for (auto& [pid, parentpid] : map_pid) {
if (check_parent(parentpid, target_pid, map_pid)) {
list_childs.push_back(pid);
}
}
return list_childs;
}


1. 自プロセスのファイルハンドル列挙 (createProcessHandleList)
1-1. システムのハンドル列挙 (NtQuerySystemInformation: SystemExtendedHandleInformation)
1-2. うち自プロセス(Aviutl)のハンドル列挙
1-3. うちファイルハンドル列挙 (NtQueryObject: ObjectTypeInformation)

次に、指定したプロセスたち(list_pid)の指定の種類(handle_type)のハンドルを列挙して返す関数を作成する。

システムのハンドルをNtQuerySystemInformationで列挙し、その中からprocessIDが指定したプロセスのリストのなかにあり、かつハンドルの種類がhandle_type (= "File")であるものを選択し、リスト化する。


#include

// 必要な追加定義
// このあたりは適当にwebで検索した
typedef struct _SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX {
PVOID Object;
ULONG_PTR UniqueProcessId;
ULONG_PTR HandleValue;
ULONG GrantedAccess;
USHORT CreatorBackTraceIndex;
USHORT ObjectTypeIndex;
ULONG HandleAttributes;
ULONG Reserved;
} SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX, *PSYSTEM_HANDLE_TABLE_ENTRY_INFO_EX;

typedef struct _SYSTEM_HANDLE_INFORMATION_EX {
ULONG_PTR NumberOfHandles;
ULONG_PTR Reserved;
SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX Handles[1];
} SYSTEM_HANDLE_INFORMATION_EX, * PSYSTEM_HANDLE_INFORMATION_EX;

#ifndef STATUS_INFO_LENGTH_MISMATCH
#define STATUS_INFO_LENGTH_MISMATCH ((NTSTATUS)0xC0000004L)
#endif

static std::vector createProcessHandleList(
const std::vector& list_pid, const wchar_t *handle_type) {
std::vector handle_list;
std::unique_ptr::type, decltype(&FreeLibrary)>
hNtDll(LoadLibrary(_T("ntdll.dll")), FreeLibrary);
if (hNtDll == NULL) return handle_list;

auto fNtQueryObject = (decltype(NtQueryObject) *)
GetProcAddress(hNtDll.get(), "NtQueryObject");
auto fNtQuerySystemInformation = (decltype(NtQuerySystemInformation) *)
GetProcAddress(hNtDll.get(), "NtQuerySystemInformation");
if (fNtQueryObject == nullptr || fNtQuerySystemInformation == nullptr) {
return handle_list;
}

static const auto SystemExtendedHandleInformation = (SYSTEM_INFORMATION_CLASS)0x40;
ULONG size = 0;
fNtQuerySystemInformation(SystemExtendedHandleInformation, NULL, 0, &size);
std::vector shibuffer;
NTSTATUS status = STATUS_INFO_LENGTH_MISMATCH;
do {
shibuffer.resize(size + 16*1024);
status = fNtQuerySystemInformation(
SystemExtendedHandleInformation,
shibuffer.data(), shibuffer.size(), &size);
} while (status == STATUS_INFO_LENGTH_MISMATCH);

if (NT_SUCCESS(status)) {
const auto currentPID = GetCurrentProcessId();
const auto currentProcessHandle = GetCurrentProcess();
const auto shi = (PSYSTEM_HANDLE_INFORMATION_EX)shibuffer.data();
for (decltype(shi->NumberOfHandles) i = 0;
i < shi->NumberOfHandles; i++) {
const auto handlePID = shi->Handles[i].UniqueProcessId;
if (std::find(
list_pid.begin(),list_pid.end(),handlePID) != list_pid.end()) {
auto handle = unique_handle(
(HANDLE)shi->Handles[i].HandleValue,
[]([[maybe_unused]] HANDLE h) { /*Do nothing*/ });
// handleValue はプロセスごとに存在する
// 自プロセスでなければ、DuplicateHandle で
// 自プロセスでの調査用のhandleをつくる
// その場合は新たに作ったhandleなので CloseHandle が必要
if (shi->Handles[i].UniqueProcessId != currentPID) {
const auto hProcess = std::unique_ptr
::type, decltype(&CloseHandle)>(
OpenProcess(PROCESS_DUP_HANDLE, FALSE, handlePID), CloseHandle);
if (hProcess) {
HANDLE handleDup = NULL;
const BOOL ret = DuplicateHandle(
hProcess.get(), (HANDLE)shi->Handles[i].HandleValue,
currentProcessHandle, &handleDup, 0, FALSE, DUPLICATE_SAME_ACCESS);
if (ret) {
handle = unique_handle(
(HANDLE)handleDup, [](HANDLE h) { CloseHandle(h); });
}
}
}
if (handle_type) {
// handleの種類を確認する
status = fNtQueryObject(
handle.get(), ObjectTypeInformation, NULL, 0, &size);
std::vector otibuffer(size, 0);
status = fNtQueryObject(
handle.get(), ObjectTypeInformation,
otibuffer.data(), otibuffer.size(), &size);
const auto oti = (PPUBLIC_OBJECT_TYPE_INFORMATION)otibuffer.data();
if (NT_SUCCESS(status)
&& oti->TypeName.Buffer
&& _wcsicmp(oti->TypeName.Buffer, handle_type) == 0) {
handle_list.push_back(std::move(handle));
}
} else {
handle_list.push_back(std::move(handle));
}
}
}
}
return handle_list;
}





これでAviutlの持つファイルハンドルのリストを取得できた。これであとはこのハンドルからファイル名をとれればいい。



2. ファイルハンドルからファイル名取得 (createProcessOpenedFileList)
GetFinalPathNameByHandle を使用する
ただし、disk fileを対象に実行する
(named pipe等にGetFinalPathNameByHandleを使うとフリーズするため (x264guiEx 2.69の問題))



#include
#include

static std::vector>
createProcessOpenedFileList(const std::vector& list_pid) {
const auto list_handle = createProcessHandleList(list_pid, L"File");
std::vector> list_file;
std::vector filename(32768+1, 0);
for (const auto& handle : list_handle) {
const auto fileType = GetFileType(handle.get());
if (fileType == FILE_TYPE_DISK) {
//ハンドルがパイプだとGetFinalPathNameByHandleがフリーズするため使用不可
memset(filename.data(), 0, sizeof(filename[0]) * filename.size());
auto ret = GetFinalPathNameByHandle(
handle.get(), filename.data(), filename.size(),
FILE_NAME_NORMALIZED | VOLUME_NAME_DOS);
if (ret != 0) {
try {
auto f = std::filesystem::canonical(filename.data());
if (std::filesystem::is_regular_file(f)) {
list_file.push_back(f.string());
}
} catch (...) { /*登録しない*/ }
}
}
}
// 重複を排除
std::sort(list_file.begin(), list_file.end());
auto result = std::unique(list_file.begin(), list_file.end());
// 不要になった要素を削除
list_file.erase(result, list_file.end());
return list_file;
}





3. 入力ファイル名と出力ファイル名の比較 (std::filesystem::equivalent)

こうして取得したAviutlの開いているファイルのリストの中には、動画や音声等の入力ファイル名だけでなく、Aviutlの開いているプラグインやシステムモジュールのファイル名も含まれる。

しかし、いずれにしても出力ファイルで上書きしてはいけないのは間違いないので、とりあえずちょっと乱暴だが、これらが出力ファイル名とかぶっていないかを確認すればよい。


static bool path_is_same(const TCHAR *path1, const TCHAR *path2) {
try {
const auto p1 = std::filesystem::path(path1);
const auto p2 = std::filesystem::path(path2);
std::error_code ec;
return std::filesystem::equivalent(p1, p2, ec);
} catch (...) {
return false;
}
}

static bool check_file_is_aviutl_opened_file(
const TCHAR *filepath, const PRM_ENC *pe) {
for (int i = 0; i < pe->n_opened_aviutl_files; i++) {
if (path_is_same(filepath, pe->opened_aviutl_files[i])) {
return true;
}
}
return false;
}




なんかもっと賢いやり方があるような気もするが、とりあえずこれで目的のエラーチェックはできるようになり、入力ファイルを出力ファイルで上書きして壊してしまうという事故を防げるようになったんじゃないかと思う。
スポンサーサイト



コメントの投稿

非公開コメント

プロフィール

rigaya

Author:rigaya
アニメとか見たり、エンコードしたり。
連絡先: rigaya34589@live.jp
github twitter

最新記事
最新コメント
カテゴリ
月別アーカイブ
カウンター
検索フォーム
いろいろ
公開中のAviutlプラグインとかのダウンロード

○Aviutl 出力プラグイン
x264guiEx 3.xx
- x264を使用したH264出力
- x264guiExの導入紹介動画>
- x264guiExの導入
- x264guiExのエラーと対処方法>
- x264.exeはこちら>

x265guiEx
- x265を使用したH.265/HEVC出力
- x265guiExの導入>
- x265.exeはこちら>

QSVEnc + QSVEncC
- QuickSyncVideoによるHWエンコード
- QSVEnc 導入/使用方法>
- QSVEncCオプション一覧>

NVEnc + NVEncC
- NVIDIAのNVEncによるHWエンコード
- NVEnc 導入/使用方法>
- NVEncCオプション一覧>

VCEEnc + VCEEncC
- AMDのVCE/VCNによるHWエンコード
- VCEEnc 導入/使用方法>
- VCEEncCオプション一覧>

svtAV1guiEx
- SVT-AV1によるAV1出力
- svtAV1guiExの導入>
- SVT-AV1単体はこちら>

ffmpegOut
- ffmpegを使用した出力
- ffmpegOutの導入>


○Aviutl フィルタプラグイン
自動フィールドシフト (ミラー)
- SSE2~AVX512による高速化版
- オリジナル: aji様

clfilters 
- OpenCLベースの複数のGPUフィルタ集
- 対応フィルタの一覧等はこちら

エッジレベル調整MT (ミラー)
- エッジレベル調整の並列化/高速化
- SSE2~AVX512対応
- オリジナル: まじぽか太郎様

バンディング低減MT (ミラー)
- SSE2~AVX512による高速化版
- オリジナル: まじぽか太郎様

PMD_MT
- SSE2~AVX512による高速化版
- オリジナル: スレ48≫989氏

透過性ロゴ (ミラー)
- SSE2~FMA3によるSIMD版
- オリジナル: MakKi氏

AviutlColor (ミラー)
- BT.2020nc向け色変換プラグイン
- BT.709/BT.601向けも同梱

○その他
x264afs (ミラー)
- x264のafs対応版

aui_indexer (ミラー使い方>)
- lsmashinput.aui/m2v.auiの
 インデックス事前・一括生成

auc_export (ミラー使い方>)
- Aviutl Controlの
 エクスポートプラグイン版
 エクスポートをコマンドから

aup_reseter (ミラー)
- aupプロジェクトファイルの
 終了フラグを一括リセット

CheckBitrate (ミラー, 使い方, ソース)
- ビットレート分布の分析(HEVC対応)

チャプター変換 (使い方>)
- nero/appleチャプター形式変換

エッジレベル調整 (avisynth)
- Avisynth用エッジレベル調整

メモリ・キャッシュ速度測定
- スレッド数を変えて測定

○ビルドしたものとか
L-SMASH (ミラー)
x264 (ミラー)
x265 (ミラー)
SVT-AV1 (ミラー)

○その他
サンプル動画
その他

○読みもの (ミラー)
Aviutl/x264guiExの色変換
動画関連ダウンロードリンク集
簡易インストーラの概要

○更新停止・公開終了
改造版x264gui
x264guiEx 0.xx
RSSリンクの表示
リンク
QRコード
QR