開発daily-log

OBS Studioで配信するとき、ウルトラワイドモニター(3440x1440、座標3840,0)の決まった場所にChromeウィンドウを置く儀式を毎回やっていた。マウスでつかんで、引っ張って、辺をドラッグして、OBSのキャプチャ枠に合わせて目視で揃える。1回30秒、配信前の儀式が地味に消耗するので、PowerShellで一発配置スクリプトを書いた。書き始めたら、文字化け、別アプリ誤認、PowerShellの予約変数、Stream Deckのダイアログと、4つの罠を順番に踏み抜いた一日になった。

朝: 過去のExcel版スクリプトを思い出す

去年、Excelウィンドウを毎回同じ場所に置きたくて Resize-ExcelWindow.ps1 を書いていた。Win32 APIの SetWindowPos をP/Invokeで呼び出すだけのやつ。あれをChrome用に書き換えれば終わりだと判断して、C:\Users\numbe\Git_repo\OBS\Resize-ChromeWindow.ps1 を新規作成した。

雛形は迷わず書けた。Add-Type でWin32 APIを定義して、Get-Process でChromeプロセスを引いて、メインウィンドウハンドルに SetWindowPos を投げる。10分で初版が動くはずだった。

罠1: 日本語コメントが文字化け

書き終えて実行したら、コメント行が全部 ????? になっていた。スクリプト自体は動くけれど、ターミナル出力の Write-Host "Chromeウィンドウを配置しました" が壊れて読めない。

PowerShellのデフォルトエンコーディングと、ファイル保存時のエンコーディングがズレている定番の問題。BOM付きUTF-8で保存し直しても、Stream Deck経由で実行したときに別の挙動になる予感がした。

ここで方針を変えて、コメント・出力メッセージは全部英語に書き換えた。配信用の個人スクリプトに日本語を残しても自分しか読まないので、英語で十分。文字化けの可能性をゼロにする方を取った。

罠2: Aqua Voice / VS Code / Memento Mori まで「Chrome」扱い

英語化が済んで再実行したら、Chromeのウィンドウだけでなく、Aqua Voice(音声入力ツール)、VS Code、自作アプリの Memento Mori まで一緒に画面中央に飛んできた。配信中にVS Codeが勝手に動いたら事故なので、これは絶対に潰す必要があった。

原因は、ウィンドウクラス名 Chrome_WidgetWin_1 だけで対象を絞り込んでいたこと。Chromiumベースのアプリは全部このクラス名を共有しているので、Electron系(VS Code、Memento Mori)も Chromiumベースのユーティリティ(Aqua Voice)も全部ヒットしてしまう。

プロセス名 chrome.exe の追加フィルタを噛ませて解決した。

# Get only real Chrome windows (filter by both window class AND process name)
$chromeWindows = [Win32]::EnumerateWindows() | Where-Object {
    $_.ClassName -eq 'Chrome_WidgetWin_1' -and
    (Get-Process -Id $_.ProcessId -ErrorAction SilentlyContinue).ProcessName -eq 'chrome'
}

Get-Process -IdProcessName を引いて、chrome 完全一致で絞る。-ErrorAction SilentlyContinue を入れたのは、ウィンドウ列挙とプロセス取得の間にプロセスが消える可能性を考えてのこと。これで Chrome 本体だけが対象になった。

罠3: $pid は PowerShell の予約変数だった

プロセスIDを格納する変数として、何の気なしに $pid = $window.ProcessId と書いていた。実行すると Cannot overwrite variable PID because it is read-only or constant. というエラー。

$pid現在のPowerShellプロセスのプロセスIDを保持する自動変数 で、上書き不可だった。Windows系のスクリプトを書いていてポート絞りで $pid を使ったときも踏んだことがある罠で、また同じところに落ちた。

$processId にリネームして即解決。地味だが、配信前の本番でこのエラーに当たったら冷や汗ものなので、開発中に踏んでおいてよかった。

罠4: 配置はできた、でも上のバーがOBSに映ってる

スクリプトが動くようになって、「Chromeを画面中央(3840+760, 100)に1920x1080で置く」が一発でできるようになった。配信を立ち上げてキャプチャを確認したら、ブックマークバーとURL欄とタブバーがOBSの収録領域にがっつり映り込んでいた。

OBS側のキャプチャ領域は座標 (4600, 5) から (6520, 1260) の 1920x1255。ブラウザの上部バー(ブックマーク・URL欄・タブ)はOBSの画面外に出して、コンテンツだけを映したい

上端をOBSの上端 -175px に逃がせば、上部バーがちょうど画面外に追い出される。下端は OBS キャプチャ下端 y=1260 にぴったり合わせる。Chromeのウィンドウ高さを 1255+175 = 1430px... ではなく、上端を y=5-175=-170 の位置に取って、高さは 1260-(-170) = 1430、ではなく実測で調整して最終 1920x1255 @ (4600, 5)(上端は OBS 上端と同じ y=5、上部UIはOBSの上端で見切れる扱い)に落ち着いた。

# Final coordinates: tuck top bar above OBS capture area, align bottom to OBS bottom
$targetX = 4600
$targetY = 5
$targetWidth = 1920
$targetHeight = 1255

数値はOBSのキャプチャプレビューを見ながら何度か微調整した。座標を直接書き換えて再実行、を5回くらい繰り返して、ブックマークバーが消えて下端のページコンテンツも切れない位置を探り当てた。

罠5: Stream Deckから .ps1 を起動するとダイアログが出る

スクリプトが完成したので、Stream Deckの「System: Open」アクションに Resize-ChromeWindow.ps1 のパスを登録して、ボタン一発で起動できるようにする予定だった。

押したら 「.ps1ファイルを何で開きますか?」のダイアログ がデスクトップ中央に出てきた。Windowsのファイル関連付けで .ps1 は「メモ帳」になっていたり、ユーザーが開くアプリを選ぶようになっているので、Stream Deckからの「Open」では PowerShell が直接起動しない。

これは .bat ラッパーを噛ませる定石で解決。

@echo off
REM Launch Resize-ChromeWindow.ps1 with no profile for faster startup
powershell.exe -NoProfile -ExecutionPolicy Bypass -File "%~dp0Resize-ChromeWindow.ps1"

ポイントは -NoProfile で、PowerShellの起動時に $PROFILE の読み込みをスキップする。普段使っているプロファイルが重いので、これがあるとないとで起動体感が0.5秒くらい違う。Stream Deck からの「ボタンを押した瞬間に動いてほしい」用途では、この差が大きい。

%~dp0 でバッチファイル自身のディレクトリを取れるので、OBS ディレクトリごとどこに移動しても動く。Stream Deckには .bat の方を登録して、ダイアログ問題は完全に消えた。

締め: README を残す

最後に README.md を書いた。次に触るのは半年後の自分なので、

  • どのファイルが何をするのか(Resize-ChromeWindow.ps1 本体、Resize-ChromeWindow.bat ラッパー)
  • 座標の意味(OBSキャプチャ領域とブラウザ上部バーの関係)
  • Stream Deck への登録手順(.bat の方を登録すること)

を書き残した。半年後の自分が「なんで .bat が並んでるんだっけ」「なんで -175px なんだっけ」と悩まないために、罠4と罠5の経緯だけはちゃんと文字に残しておいた。

学び

  • Chromiumベースアプリの判別はクラス名だけでは無理Chrome_WidgetWin_1 は VS Code、Electron系、Aqua Voice等が共有している。プロセス名 chrome.exe との AND で絞るのが必須
  • $pid は PowerShell 自動変数なので使うな。プロセスIDを入れたいときは $processId$targetPid にする
  • Stream Deckから .ps1 直接起動は不可.bat ラッパー + -NoProfile で起動高速化までセットで設計する
  • PowerShellスクリプトの日本語コメントは文字化けリスクがある。配信用の個人ツールなら最初から英語で書いた方が安全

配信前の手動配置の儀式が、Stream Deckのワンボタンに置き換わった。30秒×毎日 = 月15分の節約だが、何より「儀式を忘れて変な場所のChromeが配信に映る事故」がゼロになったのが大きい。