見出し画像

バッチファイルでyt-dlpによる定期巡回(前回実行時以降の動画をダウンロード)

市販の動画ダウンローダーであるStreamFabに、YouTubeのチャンネルを登録し、定期巡回(前回実行時以降の動画をダウンロードする)の機能があるが、同日中に2回目以上がうまく実行できず、プチストレス。
なので、yt-dlpで実現してみようと思い、バッチファイルを作成してみた。
ただし、作ってみてからわかったのだが、yt-dlpの日付指定ダウンロードは、「チャンネル内のすべての動画を一つずつ調べ、日付条件を満たせばダウンロードする」という挙動だったので、処理時間が長く、あまり実用的ではないなぁという感想。

最新の記事更新:2024/10/23 一部説明を追加。


完成品のダウロード

※10/19修正:内部にバッチファイル(.bat)を含んでいるせいか、「危険なファイル」としてダウンロードできない(Chromeで確認)ので、「_dln.bat」→「_dln.bat.txt」にリネームしました。

※10/24修正:次の不具合等に対処した版に差し替えました。
・機能拡充:リストファイルのコメント記述に対応。先頭1文字目が「;」「'」「:」いずれかである場合、コメント行と見なします。
・不具合対応:チャンネル名に半角カッコが含まれていた場合、異常終了することへ対処
・不具合対応:リストファイル用バックアップフォルダ指定をブランクにしていた際、エラーメッセージが表示されていたことへ対処
・不具合対応:チャンネル名が取得失敗した際に動画保存にも失敗する現象へ対処(「*チャンネル名取得失敗」とする)
・不具合対応:URLにエンコード文字が含まれていた場合、リストファイルが正常に更新できなかった現象へ対処

_dln.bat.txt
 … バッチファイル(本体)。「_dln.bat」にリネームして使用する。
     コード中「【ここに保存先を指定する】」の部分は書き換えること。
_dln.txt
 … 巡回するチャンネル等のリスト。
     サンプルどおり記述すれば良いが、簡略表記も可能。
     簡略表記については、後述。

説明のようなもの

使い方

  1. 解凍してできた「_dln.bat.txt」「_dln.txt」を同じフォルダに置く。

  2. 「_dln.bat.txt」を「_dln.bat」にリネームする
    (「~.bat」だとダウンロードに支障が出るので回避策として「~.bat.txt」とダミーの名前にしている)

  3. 「_dln.bat」のコード中、「【ここに保存先を指定する】」の部分を、ダウンロードした動画の保存先を指定する。(この指定フォルダ内に各チャンネル名でサブフォルダが作成され、その中に動画が保存される)

  4. 「_dln.txt」に巡回対象となるチャンネル等の情報を書く。
    ・チャンネル名、URL(http://~)、前回実行日(yyyymmdd)の順番にタブ区切りで記述する
    ・URLだけ記述しても動作するので、最初はこれがオススメ

  5. 「_dln.bat」を実行する。起動オプションは無い。
    ・実行すると「_dln.txt」の前回実行日は現在日へ自動的に更新される
     (次回実行時は、この前回実行日を参照し、同日以降の動画をダウンロードする)
    ・URLだけ記述していた場合、チャンネル名も自動的に追加される

諸注意

  • 「_dln.txt」の記述は、
    ①チャンネル名、URL、前回実行日
    ②URL
    ③チャンネル名、URL
    ④URL、前回実行日
    のいずれかであれば動作する(はず)

  • 「_dln.txt」の内容更新のため、処理中、同フォルダに「temp.txt」というファイルを作成する。すでに存在する場合には削除するので、注意すること

  • ダウンロードを試みた際、何らかの理由でダウンロードに失敗した場合には、前回実行日を削除する(目印がわりの措置)

  • 「_dln.txt」は起動時にバックアップファイル(最新+3世代分)を作成する。バッチファイルを強制停止した際など、「_dln.txt」の内容が異常更新されることがあるので、その際にはバックアップから復旧を

  • 画質設定やファイルの命名規則を変更した場合には、コード中「ytDlpOptions」「ytDlpFormat」を適宜変更

  • 特に、現在はブラウザのcookie情報をあらかじめ出力した「cookies.txt」を使用する設定になっているため、不要な場合には「ytDlpOptions」から「--cookies cookies.txt」の部分を削除すること

  • 処理上、文字エンコードはShift-JISとなっているため、「_dln.txt」への書き込みもShift-JISで行われる。このため、チャンネル名にUnicode文字を含んでいた場合、その文字は「_dln.txt」には書き込まれない。
    全てがUnicode文字の場合、チャンネル名がブランクとなる。
    ダウンロードした動画の保存先は、「_dln.txt」に記載したチャンネル名を使用するため、上記の状況によりチャンネル名がブランクであった場合(データが「タブ文字+URL+タブ+前回実行日」である場合)、動画の保存に失敗する。(チャンネル名の記載を省略している場合(データが「URL+タブ+前回実行日」である場合)は問題なく動作する)
    あらかじめShift-JISの範囲で別の名前を記載しておくことで回避可能。
    ※10/24対処
    全てがUnicode文字等、チャンネル名が正常に取得できなかった場合、便宜上「*チャンネル名取得失敗」というチャンネル名として保存フォルダや「_dln.txt」を処理する。
    なお、「_dln.txt」であらかじめShift-JISの範囲で名前が記載されている場合には、その記載内容で保存フォルダを作成し、「_dln.txt」の自動更新でもチャンネル名は保持される。この運用を推奨。

  • yt-dlpの起動オプションである「--mark-watched」(ログイン状態使用時、視聴履歴を残す)を併用した場合、日付により対象でない動画もすべて視聴履歴が残るので注意。これはyt-dlp本体の仕様。

  • Windows11で実行を確認

作成の経緯など

なぜ作成しようと思ったかについては、冒頭リード文のとおり。
以前から構想しつつも腰が重かったが、例によってChatGPTに作らせてみたら、意外とできあがってきたので、実際に動かせるレベルにブラッシュアップをしてみた。

ちなみに、バッチファイルとしたのは全く他意は無く、ChatGPTに「Windows上で無償で動かせる言語(バッチファイル、VBScript、JavaScript、Powershell等)で作成すること」と指示したら、バッチファイルで作ってきたので。

ChatGPTが作ってきたコードはそれなりに良くできていたが、それでもエラーが生じる原因の特定、改善方法の検討、テスト、と、実働で1日かかってしまった。
一方、ChatGPTを使うことで「プログラムの基本構造など、イチから考える必要がなくなる」ことは物凄くありがたい。今回は特に、私ではとても思いつかないような方法が盛り込まれていたのは、純粋にすごいなぁと思う。

現在時刻を取得する部分。(ここだけPowershellを使っている)

rem 現在時刻を取得 (前回実行時刻更新のため)
for /f %%A in ('powershell -Command "Get-Date -Format yyyyMMdd"') do set currentDateTime=%%A

リストファイルの読み込み。(バッチファイルでできると思わなかった)

rem CSVファイルを読み込む
for /f "tokens=1,2,3 delims=	" %%A in (%csvFile%) do (
    set "channelName=%%A"
    set "url=%%B"
    set "lastRun=%%C"
)

チャンネル名の取得。(yt-dlpの結果から読み込んでいる)

    rem チャンネル名が無い場合は、yt-dlpから取得
    if "!channelName!"=="" (
        echo ★ チャンネル名を取得しています: !url!
        for /f "delims=" %%D in ('yt-dlp --get-filename -o "%%(uploader)s" "!url!" 2^>nul') do set "channelName=%%D"
    )

リストファイルの更新。(バッチファイルでサブルーチンが作れるんだ、という新たな発見。あと、「毎回、全行読み込み・他ファイルへ加工して出力(最後にリネームして戻す)」という、ある意味力業。その発想は無かった。)

:updateCsv
rem 新しいCSVファイルを作成しながら更新
set "tempFile=temp.txt"
if exist "%tempFile%" del "%tempFile%"

for /f "tokens=1,2,3 delims=	" %%A in (%csvFile%) do (
    if "%%B"=="%~2" (
        echo %~1	%~2	%~3>>"%tempFile%"
    ) else (
        if "%%A"=="%~2" (
            echo %~1	%~2	%~3>>"%tempFile%"
        ) else (
        echo %%A	%%B	%%C>>"%tempFile%"
        )
    )
)

rem 古いCSVファイルを削除し、新しいCSVファイルをリネーム
del %csvFile%
rename %tempFile% %csvFile%
exit /b

バッチファイルでもここまでできる、というレベルのものを不完全とはいえ作れるのは、本当にすごいなぁと。

ただ、バッチファイルが選択されたことで、いらぬ不具合やデバッグ時間の増加を招いたなぁという感じはあるので、本当はバッチファイルはやめてほしかったかな…。
(二重引用符で囲む囲まない問題、echo内で()を使うとif else等の入れ子構造が壊れる問題、errorlevelでの条件分岐が「以上」で判断される問題などなど、枚挙にいとまがない)

今回のデバッグでは、次のページの情報が大変参考になった。
バッチファイルで予想外のエラーや解決できない問題が起こった時に見てみると、解決の糸口があるかもしれない。

コード全体(一応掲載)

冒頭のzipファイルをダウンロードすれば実用上は足りるのだが、いちおうコード全体も掲載しておく。

@echo off
setlocal enabledelayedexpansion

rem ---------- ファイルから前回実行時刻を読み取り、それ以降の動画をダウンロードする
rem ---------- 「_dln.txt」に「チャンネル名、URL、前回実行時刻」の順で対象を記述する(タブ区切り)
rem ---------- ・チャンネル名と前回実行時刻は、実行ごとに更新される
rem ---------- ・「URLのみ記述」でも動作可能
rem ---------- ・「チャンネル名、URLのみ記述」「URL、前回実行時刻のみ記述」でも動作可能

rem CSVファイルのパス
set "csvFile=_dln.txt"

rem yt-dlpのオプション
set "ytDlpOptions=--embed-thumbnail --embed-metadata --cookies cookies.txt --console-title --windows-filenames"
set ytDlpFormat="bv*[ext=mp4]+ba[ext=m4a]/b[ext=mp4]"

rem 親フォルダのパスを指定 (保存先のルートフォルダ)
set "parentFolder=【ここに保存先を指定する】"

rem 現在時刻を取得 (前回実行時刻更新のため)
for /f %%A in ('powershell -Command "Get-Date -Format yyyyMMdd"') do set currentDateTime=%%A

rem CSVファイルをバックアップする(3世代)
:: パラメータ設定
set "BACKUP_DIR="          :: バックアップ先のフォルダ(末尾に\をつける。文字列中にカッコは指定できない。)
set "BASE_NAME=_dln_bak"   :: バックアップファイルのベース名
set "EXT=.txt"             :: ファイル拡張子

:: バックアップ先フォルダが存在しない場合は作成
if "%BACKUP_DIR%"=="" (
    rem ダミー
) else (
    if not exist "%BACKUP_DIR%" (
        mkdir "%BACKUP_DIR%"
        echo ★ リストファイル用バックアップフォルダを作成しました。
    )
)

:: 古いバックアップを3世代保持する
if exist "%BACKUP_DIR%%BASE_NAME%_3%EXT%" (
    del "%BACKUP_DIR%%BASE_NAME%_3%EXT%"
)
if exist "%BACKUP_DIR%%BASE_NAME%_2%EXT%" (
    rename "%BACKUP_DIR%%BASE_NAME%_2%EXT%" "%BASE_NAME%_3%EXT%"
)
if exist "%BACKUP_DIR%%BASE_NAME%_1%EXT%" (
    rename "%BACKUP_DIR%%BASE_NAME%_1%EXT%" "%BASE_NAME%_2%EXT%"
)

:: 新しいバックアップをコピー
if exist "%BACKUP_DIR%%BASE_NAME%%EXT%" (
    rename "%BACKUP_DIR%%BASE_NAME%%EXT%" "%BASE_NAME%_1%EXT%"
)
copy "%csvFile%" "%BACKUP_DIR%%BASE_NAME%%EXT%"

echo ★ 対象リストのバックアップを作成

rem CSVファイルを読み込む
set /a lineNumber=0
for /f "tokens=1,2,3 delims=	" %%A in (%csvFile%) do (
    set "channelName=%%A"
    set "url=%%B"
    set "lastRun=%%C"
    set /a lineNumber+=1

echo ------------------------------

    rem URLが存在するか確認
    rem パターン1:列2が空白なら、列1にURLのみ記載されていると解釈
    if "!url!"=="" (
        set "url=%%A"
        set "channelName="
    )
    rem パターン2:空行の場合は、スキップ
    if "!url!"=="" (
        rem URLが無い場合はスキップ
        echo ★ URLが無いレコードをスキップします。
        goto :continue
    )
    rem パターン3:列1がURL(http~)なら、列1をURL、列2を前回実行時刻と解釈
    if "!channelName:~0,4!"=="http" (
        set "channelName="
        set "url=%%A"
        set "lastRun=%%B"
    )

    rem コメントアウト行なら読み飛ばす(「;」「'」「:」に対応)
    if "!url:~0,1!"==";" (
        echo ★ コメント行なのでスキップします。
        goto :continue
    )
    if "!url:~0,1!"=="'" (
        echo ★コメント行なのでスキップします。
        goto :continue
    )
    if "!url:~0,1!"==":" (
        echo ★コメント行なのでスキップします。
        goto :continue
    )

    rem チャンネル名が無い場合は、yt-dlpから取得
    if "!channelName!"=="" (
        echo ★ チャンネル名を取得しています: !url!
        for /f "delims=" %%D in ('yt-dlp --get-filename -o "%%(uploader)s" "!url!" 2^>nul') do set "channelName=%%D"
    )
    if "!channelName!"=="" set "channelName=*チャンネル名取得失敗"
    echo ★ チャンネル名: !channelName!

    rem サブフォルダとしてチャンネル名を使用
    set "savePath=%parentFolder%\!channelName!"
    if not exist "!savePath!" mkdir "!savePath!"

    rem 最後に実行した時刻が記録されているかチェック
    if "!lastRun!"=="" (
        rem 前回実行時刻が無い場合、全動画をダウンロード
        echo ★ ダウンロード開始: !url! ^(全動画^)
        echo.
        yt-dlp !ytDlpOptions! --format !ytDlpFormat! -P "!savePath!" --output "%%(title)s [%%(uploader)s,%%(upload_date)s].%%(ext)s" "!url!"
    ) else (
        rem 前回実行時刻がある場合、その時刻以降の動画のみをダウンロード
        echo ★ ダウンロード開始: !url! ^(!lastRun!以降^)
        echo.
        yt-dlp !ytDlpOptions! --format !ytDlpFormat! -P "!savePath!" --output "%%(title)s [%%(uploader)s,%%(upload_date)s].%%(ext)s" --dateafter "!lastRun!" "!url!"
    )

    rem ダウンロードが成功したかを確認
    if errorlevel 1 (
        rem 失敗: 前回実行時刻を削除 (nullで更新)
        echo.
        echo ★ ダウンロード失敗: !url! の実行時刻を削除
        call :updateCsv "!channelName!" "!url!" "" "!lineNumber!"
    ) else if errorlevel 0 (
        rem 成功: CSVファイルの該当行を更新
        echo.
        echo ★ 更新中: !url! のチャンネル名と実行時刻を更新
        call :updateCsv "!channelName!" "!url!" "!currentDateTime!" "!lineNumber!"
    ) else (
        rem 失敗: 前回実行時刻を削除 (nullで更新)
        echo.
        echo ★ ダウンロード失敗: !url! の実行時刻を削除
        call :updateCsv "!channelName!" "!url!" "" "!lineNumber!"
    )

    :continue
rem
echo.
)

echo ------------------------------
echo ★ すべてのレコードを処理完了

exit /b

:updateCsv
rem 新しいCSVファイルを作成しながら更新
set "tempFile=temp.txt"
if exist "%tempFile%" del "%tempFile%"

rem チャンネル名にカッコが含まれている場合、エスケープ処理する
set myVar=%~1
set escapedVar=
for /l %%i in (0,1,256) do (
    set "char=!myVar:~%%i,1!"
    if "!char!"=="" goto :done
    if "!char!"=="(" (
        set escapedVar=!escapedVar!^(
    ) else if "!char!"==")" (
        set escapedVar=!escapedVar!^)
    ) else (
        set escapedVar=!escapedVar!!char!
    )
)
:done
rem

set /a lineCount=0
for /f "tokens=1,2,3 delims=	" %%A in (%csvFile%) do (
rem ここで、%1~%4は、サブルーチンが呼ばれたときに渡された値(現在の処理対象動画の情報)、
rem %%A~%%Cは、CSVから読み取った値
    set /a lineCount+=1
    if !lineCount!==%~4 (
        rem 行数が一致したなら、その行を指定値で更新する
        rem (%2で送られたURLに文字列エンコード(%xx)を含んでいるとECOHでの書き出し時に何故か%が誤解釈されるので、
        rem  美しくないが、!url!をそのまま使う(CSVの値である%%Bでも良いが、記述方法により%%BURLではない場合がある)
        echo !escapedVar!	!url!	%~3>>"%tempFile%"
    ) else (
        rem この先は通常は実行されない。初期ルーチンを残してあるもの。
        if "%%B"=="%~2" (
            echo !escapedVar!	%~2	%~3>>"%tempFile%"
        ) else (
            if "%%A"=="%~2" (
                echo !escapedVar!	%~2	%~3>>"%tempFile%"
            ) else (
            echo %%A	%%B	%%C>>"%tempFile%"
            )
        )
    )
)

rem 古いCSVファイルを削除し、新しいCSVファイルをリネーム
del %csvFile%
rename %tempFile% %csvFile%
exit /b

過去のバージョン(一応保存)

2024/10/19版


この記事が気に入ったらサポートをしてみませんか?