├── .gitignore ├── CHANGELOG.md ├── ConvertLora.bat ├── DeleteOutput.bat ├── EasyPromptAnimeEditor.bat ├── FixCheckpoint.bat ├── FpsX4.bat ├── FpsX6.bat ├── Frames2Mp4.bat ├── Generate.bat ├── GenerateFolder.bat ├── GenerateFolderForever.bat ├── GenerateForever.bat ├── LICENSE.txt ├── Mp4Crf.bat ├── Mp4Crf26.bat ├── Mp4Crf32.bat ├── Mp4Crf38.bat ├── OpenColabEditor.bat ├── README.md ├── Update.bat ├── XMp4.bat ├── XMp4H1200.bat ├── XMp4H1900.bat ├── XMp4W1200.bat ├── XMp4W1920.bat ├── doc └── img │ └── title.webp ├── editor ├── DownloadMenu.json ├── bat │ ├── refine.bat │ └── tile-upscale.bat └── src │ ├── EasyPromptAnimeEditor.py │ ├── config.py │ ├── const.py │ ├── controller.py │ ├── ctr_basic.py │ ├── ctr_control_net.py │ ├── ctr_file.py │ ├── ctr_generate.py │ ├── ctr_input.py │ ├── ctr_ip_adapter.py │ ├── ctr_menu.py │ ├── ctr_mosaic.py │ ├── ctr_output.py │ ├── ctr_preview.py │ ├── ctr_upscale.py │ ├── l10n.py │ ├── l10n_en.py │ ├── l10n_ja.py │ ├── log.py │ ├── mdl_editor.py │ ├── mdl_generate.py │ ├── mdl_notifier.py │ ├── mdl_prompt.py │ ├── model.py │ ├── mosaic.py │ ├── prompt_perser.py │ ├── prompt_travel.py │ ├── serializer.py │ ├── task.py │ ├── tsk_command_prompt.py │ ├── tsk_generate.py │ ├── tsk_upscale.py │ ├── ui.py │ ├── ui_basic.py │ ├── ui_const.py │ ├── ui_control_net.py │ ├── ui_generate.py │ ├── ui_input.py │ ├── ui_ip_adapter.py │ ├── ui_menu.py │ ├── ui_mosaic.py │ ├── ui_output.py │ ├── ui_preview.py │ └── ui_upscale.py ├── sample ├── Gacha-L120-C16-W448-H512.json ├── Gacha.bat ├── Oiyami-L60-C16-W512-H384-T768.json ├── Oiyami.bat ├── UpscaledGacha-L120-C16-W448-H512-T768.json ├── UpscaledGacha.bat ├── Vram12G-L120-C16-W608-H384-T768-T1152.json ├── Vram12G.bat ├── Vram8G-L120-C16-W448-H512-T768-T1024.json └── Vram8G.bat └── src ├── Setup-ECCV2022-RIFE.bat ├── Setup-EasyPromptAnime.bat ├── Setup-Practical-RIFE.bat ├── Setup-animatediff-cli-prompt-travel.bat ├── Setup-editor.bat ├── Setup-ffmpeg-master-latest-win64-gpl.bat ├── Setup-sd-scripts-accelerate_config.bat ├── Setup-sd-scripts.bat ├── Setup.bat ├── Update-ECCV2022-RIFE.bat ├── Update-Practical-RIFE.bat ├── Update-animatediff-cli-prompt-travel.bat ├── Update-sd-scripts.bat └── Update.bat /.gitignore: -------------------------------------------------------------------------------- 1 | /animatediff-cli-prompt-travel/ 2 | /ECCV2022-RIFE/ 3 | /ffmpeg-*-essentials_build/ 4 | /sample/**/*.mp4 5 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 過去の更新履歴 2 | 3 | - 2023/10/27 4 | - 連番画像へのモザイクに対応しました。 5 | - 2023/10/25 6 | - フレーム補間をする RIFE のバージョンを 3.6 から 4.8 に上げました。 7 | - アップデート後は Practical-RIFE/ を使用しますので、ECCV2022-RIFE/ を削除できます。 8 | - 2023/10/22 9 | - MP4 の動画からコントロールネット用の連番画像をワンアクションで取り出せる `MP4 画像取り込み` を追加しました。 10 | - 利用方法は『[MP4 取り込みで動きをつける](https://twitter.com/Zuntan03/status/1716386352508608629)』をご覧ください。 11 | - アップスケールで `ip2p` と `line_anime` を使えるようにしました。 12 | - 2023/10/20 13 | - モーション LoRA に対応しました。 14 | - `ダウンロード` メニューの `モーション LoRA` からダウンロードできます。 15 | - LoRAと似たように `M: v2_lora_ZoomIn: 1.0` とプロンプトに記載すると使えます。 16 | - 動画の出力先にファイルも保存するようにしました。 17 | - `ファイル` メニューに `新規作成` と `終了` を追加しました。 18 | - 変更が未保存の場合に確認するようにしました。 19 | - 2023/10/17 20 | - 簡単プロンプトアニメのエディタの状態を保存して、読み込めるようにしました。 21 | - 2023/10/15 22 | - ControlNet の tile, canny, depth, inpaint, ip2p, lineart, lineart_anime, mlsd, normalbae, openpose, scribble, seg, shuffle, softedge に対応しました。 23 | - 2023/10/14 24 | - `ダウンロード` メニューの `LoRA` に汎用的な LoRA を追加しました。 25 | - [3dSlider_v2](https://twitter.com/Zuntan03/status/1713064888397181256), 26 | [flat2](https://twitter.com/Zuntan03/status/1713067612874359173) 27 | - `設定` メニューに `現在のプロンプトを起動時のプロンプトにする` と `現在の設定を起動時の設定にする` と `ライト/ダークモードの切り替え(再起動後に適用)` を追加しました。 28 | - 2023/10/12 29 | - プロンプト入力欄で `元に戻す(Ctrl+Z)` , `やり直し(Ctrl+Y)` を使えるようにしました。 30 | - `ダウンロード` メニューの項目を充実しました。 31 | - 2023/10/10 32 | - `ツール` メニューの `使えない LoRA を使えるように変換` でこれまで使えなかった LoRA が使える場合があります。 33 | - `アニメ` メニューのアップスケールで、アニメ生成とアップスケールを別々に実行できます。 34 | - 生成と同じオプションでアプスケする `生成時の設定で初回アップスケール` と、エディタの設定を適用して色々変更できる `現在の設定で初回アップスケール` です。 35 | - 黒画面への対処や、モデルやLoRAパラメータを変えてのアップスケールができます。 36 | - これに合わせてタイルアップスケールの強度と開始と終了を設定できるようにしました。 37 | - `IPアダプタ` で[生成時に画像をプロンプトとして使えます](https://www.google.com/search?q=ip-adapter)。 38 | - プロンプトとして使いたい画像をフォルダにまとめ、ファイル名で 010.png などとフレーム数を指定する必要があります。 39 | - `ダウンロード` からモデルやモーションモジュールなどをダウンロードできます。 40 | - おすすめのものがあれば教えてください。 41 | - 2023/10/09 42 | - ローカル版エディタをα版としてひっそり公開しました。 43 | - `EasyPromptAnimeEditor.bat` で起動します。 44 | - α版なのでドキュメントはまだありません。 45 | - Colab 版エディタは廃止予定です。 46 | - セットアップの仕組みを大幅に変更して、最初にダウンロードするファイルの量を大幅に減らしました。 47 | - ローカル版エディタの「ダウンロード」メニューでダウンロードできます。 48 | - これまでインストールできていたのに、再インストールしても動かない場合は知らせてください。 49 | - 2023/09/29 50 | - フォルダ内の生成設定ファイルで一通り生成する `GenerateFolder.bat` と、これを連続生成し続ける `GenerateFolderForever.bat` を追加しました。 51 | - 2023/09/28 52 | - 昨日の「インストール時のエラーチェックを強化しました。」に問題があり、インストールできなかった不具合を修正しました。 53 | - [Setup-EasyPromptAnime.bat](https://github.com/Zuntan03/EasyPromptAnime/raw/main/src/Setup-EasyPromptAnime.bat?20231011) を再ダウンロードして、インストールしてください。 54 | - animatediff のコマンドでエラーが発生した際に、処理を終了するようにしました。 55 | - GenerateForever.exe も同様に、処理を終了するようにしました。 56 | - 2023/09/27 57 | - インストール時のエラーチェックを強化しました。 58 | - 0~9時に生成した際に、動画ファイル名の時間部分をゼロパディングするようにしました。 59 | - モデルファイルなどのシンボリックリンクを削除しないようにしました。 60 | - 2023/09/26 61 | - サンプルに『[美味しいヤミー感謝感謝](https://www.google.com/search?q=%E7%BE%8E%E5%91%B3%E3%81%97%E3%81%84%E3%83%A4%E3%83%9F%E3%83%BC%E6%84%9F%E8%AC%9D%E6%84%9F%E8%AC%9D)』の `sample/Oiyami.bat` を追加しました。 62 | - 2023/09/25 63 | - FFmpeg のインストールに失敗する不具合を修正しました。 64 | - `Update.bat` を実行すると修正します。 65 | - 2023/09/24 66 | - **今回の更新では `Update.bat` を 2回!実行してください(`Update.bat` を更新したため)。** 67 | - [AnimateDiff prompt travel](https://github.com/s9roll7/animatediff-cli-prompt-travel) の更新に対応しました。 68 | - AnimateDiff 用にモデルを修正する `FixCheckpoint.bat` を追加しました。
69 | が、手元で利用しているモデルは修正不要でした。 70 | - 生成した動画のサイズを低減する `Mp4Crf(26|32|38)` を追加しました。 71 | - 生成した動画から X(Twitter) 投稿用の動画を生成する `XMp4*.bat` を追加しました。 72 | - 2023/09/23 73 | - 生成する動画のデフォルトの FPS を、様々なサービスとの互換性の観点から 40FPS にしました。 74 | - 生成設定ファイル名に `-D3` を付け足すと、以前と同様に 80FPS になります。 75 | - FPS 指定の詳細は [ファイル名オプション一覧](#ファイル名オプション一覧) や `FpsX4.bat` の説明を参照してください。 76 | - 生成した mp4 ファイル名に、日時のプレフィックスを追加するようにしました。 77 | - 同一シードで生成しても、mp4 ファイルを上書きしなくなります。 78 | - 2023/09/22 79 | - motion-module に mm-Stabilized_high.pth と mm-Stabilized_mid.pth を追加しました。 80 | - `Update.bat` を実行するとダウンロードします。 81 | - 同一シードで同じフォルダに再出力した際に、正しくフレーム補間されない不具合を修正しました。 82 | - インストール済みの方は `Update.bat` を実行してください。 83 | - インストーラーで `C:\Windows\System32` にパスが通っていない場合にエラー扱いにしました。 84 | - 2023/09/21 85 | - 公開 86 | -------------------------------------------------------------------------------- /ConvertLora.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | if "%~1" == "" ( 3 | echo [ERROR] Drag and drop the C3Lier LoRA or LoCon LoRA file. 4 | pause & exit /b 1 5 | ) 6 | 7 | set DIM=32 8 | if not "%~2" == "" if not "%~2" == "0" ( 9 | set DIM=%~2 10 | ) 11 | 12 | set MODEL_PATH="%~dp0animatediff-cli-prompt-travel\data\models\sd\featurelessMix_v2020237Clearvae.safetensors" 13 | if not "%~3" == "" ( 14 | set MODEL_PATH="%~f3" 15 | ) 16 | 17 | set SRC_PATH="%~f1" 18 | set MERGED_PATH="%~dpn1-merged.safetensors" 19 | set DEST_PATH="%~dp0animatediff-cli-prompt-travel\data\lora\%~n1.safetensors" 20 | 21 | pushd %~dp0sd-scripts 22 | call venv\Scripts\activate.bat 23 | 24 | echo SRC_PATH: %SRC_PATH% 25 | echo MERGED_PATH: %MERGED_PATH% 26 | echo DEST_PATH: %DEST_PATH% 27 | 28 | python networks\merge_lora.py^ 29 | --sd_model %MODEL_PATH%^ 30 | --save_to %MERGED_PATH%^ 31 | --models %SRC_PATH%^ 32 | --ratios 1.0^ 33 | --save_precision float 34 | if %errorlevel% neq 0 ( pause & popd & exit /b %errorlevel% ) 35 | 36 | python networks\extract_lora_from_models.py^ 37 | --model_org %MODEL_PATH%^ 38 | --model_tuned %MERGED_PATH%^ 39 | --save_to %DEST_PATH%^ 40 | --dim %DIM%^ 41 | --save_precision bf16^ 42 | --device cpu 43 | if %errorlevel% neq 0 ( pause & popd & exit /b %errorlevel% ) 44 | 45 | del %MERGED_PATH% 46 | if %errorlevel% neq 0 ( pause & popd & exit /b %errorlevel% ) 47 | 48 | call venv\Scripts\deactivate.bat 49 | popd rem %~dp0sd-scripts 50 | -------------------------------------------------------------------------------- /DeleteOutput.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | @REM If you want to disable (y/n), delete the three lines below. 4 | echo Delete output/*/ upscaled/*/ refine/*/ (y/n) 5 | set /p YES_OR_NO= 6 | if /i not "%YES_OR_NO%" == "Y" ( exit /b 0 ) 7 | 8 | pushd %~dp0animatediff-cli-prompt-travel 9 | 10 | for /d %%d in ("output\*") do ( rmdir /s /q "%%~d" ) 11 | for /d %%d in ("upscaled\*") do ( rmdir /s /q "%%~d" ) 12 | for /d %%d in ("refine\*") do ( rmdir /s /q "%%~d" ) 13 | 14 | popd rem %~dp0animatediff-cli-prompt-travel 15 | -------------------------------------------------------------------------------- /EasyPromptAnimeEditor.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | pushd %~dp0 4 | call editor\venv\Scripts\activate.bat 5 | 6 | if not exist output ( mkdir output ) 7 | if not exist editor\log ( mkdir editor\log ) 8 | set ZERO_PADDING_TIME=%TIME: =0% 9 | for /f "tokens=2-7 delims=/:. " %%a in ("echo %DATE% %ZERO_PADDING_TIME%") do ( 10 | set YYYY_MMDD_HHMM_SS=%%a_%%b%%c_%%d%%e_%%f 11 | ) 12 | 13 | python editor\src\EasyPromptAnimeEditor.py > editor\log\EasyPromptAnimeEditorLog-%YYYY_MMDD_HHMM_SS%.txt 14 | 15 | call editor\venv\Scripts\deactivate.bat 16 | popd rem %~dp0 17 | -------------------------------------------------------------------------------- /FixCheckpoint.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | if "%~1" == "" ( 3 | echo [ERROR] Drag and drop the model file. 4 | pause & exit /b 1 5 | ) 6 | 7 | set MODEL_PATH="%~f1" 8 | 9 | pushd %~dp0animatediff-cli-prompt-travel 10 | call venv\Scripts\activate.bat 11 | 12 | echo animatediff fix-checkpoint %MODEL_PATH% 13 | animatediff fix-checkpoint %MODEL_PATH% 14 | 15 | call venv\Scripts\deactivate.bat 16 | popd rem %~dp0%~dp0animatediff-cli-prompt-travel 17 | -------------------------------------------------------------------------------- /FpsX4.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | if "%~1" == "" ( 3 | echo [ERROR] Drag and drop the mp4 file. 4 | pause & exit /b 1 5 | ) 6 | set PATH=%~dp0ffmpeg-master-latest-win64-gpl\bin;%PATH% 7 | 8 | set RIFE_DIV_NUM=2 9 | set RIFE_FPS_NAME= 10 | set RIFE_FPS_OPTION= 11 | set FFMPEG_FPS_NAME= 12 | set FFMPEG_FPS_OPTION= 13 | set FFMPEG_CRF=20 14 | 15 | if not "%~2" == "" if not "%~2" == "0" ( 16 | set RIFE_DIV_NUM=%~2 17 | ) 18 | if not "%~3" == "" if not "%~3" == "0" ( 19 | set RIFE_FPS_NAME=-f%~3 20 | set RIFE_FPS_OPTION=--fps=%~3 21 | ) 22 | if not "%~4" == "" if not "%~4" == "0" ( 23 | set FFMPEG_FPS_NAME=-F%~4 24 | set FFMPEG_FPS_OPTION=-r %~4 25 | ) 26 | if not "%~5" == "" if not "%~5" == "0" ( 27 | set FFMPEG_CRF=%~5 28 | ) 29 | 30 | set SRC_PATH="%~f1" 31 | set RIFE_DEST_PATH="%~dpn1-D%RIFE_DIV_NUM%%RIFE_FPS_NAME%.mp4" 32 | set FFMPEG_DEST_PATH="%~dpn1-D%RIFE_DIV_NUM%%RIFE_FPS_NAME%e%FFMPEG_FPS_NAME%.mp4" 33 | 34 | pushd %~dp0Practical-RIFE 35 | call venv\Scripts\activate.bat 36 | 37 | @REM echo ARGS: %1 %2 %3 %4 %5 38 | @REM echo RIFE_DIV_NUM: %RIFE_DIV_NUM% 39 | @REM echo RIFE_FPS_NAME: %RIFE_FPS_NAME% 40 | @REM echo RIFE_FPS_OPTION: %RIFE_FPS_OPTION% 41 | @REM echo FFMPEG_FPS_NAME: %FFMPEG_FPS_NAME% 42 | @REM echo FFMPEG_FPS_OPTION: %FFMPEG_FPS_OPTION% 43 | @REM echo FFMPEG_CRF: %FFMPEG_CRF% 44 | @REM echo SRC_PATH: %SRC_PATH% 45 | @REM echo RIFE_DEST_PATH: %RIFE_DEST_PATH% 46 | @REM echo FFMPEG_DEST_PATH: %FFMPEG_DEST_PATH% 47 | @REM pause 48 | 49 | echo python inference_video.py --exp=%RIFE_DIV_NUM% --video %SRC_PATH% %RIFE_FPS_OPTION% --output %RIFE_DEST_PATH% 50 | python inference_video.py --exp=%RIFE_DIV_NUM% --video %SRC_PATH% %RIFE_FPS_OPTION% --output %RIFE_DEST_PATH% 51 | 52 | echo ffmpeg.exe -y -i %RIFE_DEST_PATH% -pix_fmt yuv420p -vcodec libx264 -tune animation %FFMPEG_FPS_OPTION% -crf %FFMPEG_CRF% %FFMPEG_DEST_PATH% 53 | ffmpeg.exe -y -i %RIFE_DEST_PATH% -pix_fmt yuv420p -vcodec libx264 -tune animation %FFMPEG_FPS_OPTION% -crf %FFMPEG_CRF% %FFMPEG_DEST_PATH% 54 | 55 | call venv\Scripts\deactivate.bat 56 | popd rem %~dp0Practical-RIFE 57 | -------------------------------------------------------------------------------- /FpsX6.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | if "%~1" == "" ( 3 | echo [ERROR] Drag and drop the mp4 file. 4 | pause & exit /b 1 5 | ) 6 | set PATH=%~dp0ffmpeg-master-latest-win64-gpl\bin;%PATH% 7 | 8 | set RIFE_MULTI_NUM=6 9 | set RIFE_FPS_NAME= 10 | set RIFE_FPS_OPTION= 11 | set FFMPEG_FPS_NAME= 12 | set FFMPEG_FPS_OPTION= 13 | set FFMPEG_CRF=20 14 | 15 | if not "%~2" == "" if not "%~2" == "0" ( 16 | set RIFE_MULTI_NUM=%~2 17 | ) 18 | if not "%~3" == "" if not "%~3" == "0" ( 19 | set RIFE_FPS_NAME=-f%~3 20 | set RIFE_FPS_OPTION=--fps=%~3 21 | ) 22 | if not "%~4" == "" if not "%~4" == "0" ( 23 | set FFMPEG_FPS_NAME=-F%~4 24 | set FFMPEG_FPS_OPTION=-r %~4 25 | ) 26 | if not "%~5" == "" if not "%~5" == "0" ( 27 | set FFMPEG_CRF=%~5 28 | ) 29 | 30 | set SRC_PATH="%~f1" 31 | set RIFE_DEST_PATH="%~dpn1-M%RIFE_MULTI_NUM%%RIFE_FPS_NAME%.mp4" 32 | set FFMPEG_DEST_PATH="%~dpn1-M%RIFE_MULTI_NUM%%RIFE_FPS_NAME%e%FFMPEG_FPS_NAME%.mp4" 33 | 34 | pushd %~dp0Practical-RIFE 35 | call venv\Scripts\activate.bat 36 | 37 | @REM echo ARGS: %1 %2 %3 %4 %5 38 | @REM echo RIFE_MULTI_NUM: %RIFE_MULTI_NUM% 39 | @REM echo RIFE_FPS_NAME: %RIFE_FPS_NAME% 40 | @REM echo RIFE_FPS_OPTION: %RIFE_FPS_OPTION% 41 | @REM echo FFMPEG_FPS_NAME: %FFMPEG_FPS_NAME% 42 | @REM echo FFMPEG_FPS_OPTION: %FFMPEG_FPS_OPTION% 43 | @REM echo FFMPEG_CRF: %FFMPEG_CRF% 44 | @REM echo SRC_PATH: %SRC_PATH% 45 | @REM echo RIFE_DEST_PATH: %RIFE_DEST_PATH% 46 | @REM echo FFMPEG_DEST_PATH: %FFMPEG_DEST_PATH% 47 | @REM pause 48 | 49 | echo python inference_video.py --multi=%RIFE_MULTI_NUM% --video %SRC_PATH% %RIFE_FPS_OPTION% --output %RIFE_DEST_PATH% 50 | python inference_video.py --multi=%RIFE_MULTI_NUM% --video %SRC_PATH% %RIFE_FPS_OPTION% --output %RIFE_DEST_PATH% 51 | 52 | echo ffmpeg.exe -y -i %RIFE_DEST_PATH% -pix_fmt yuv420p -vcodec libx264 -tune animation %FFMPEG_FPS_OPTION% -crf %FFMPEG_CRF% %FFMPEG_DEST_PATH% 53 | ffmpeg.exe -y -i %RIFE_DEST_PATH% -pix_fmt yuv420p -vcodec libx264 -tune animation %FFMPEG_FPS_OPTION% -crf %FFMPEG_CRF% %FFMPEG_DEST_PATH% 54 | 55 | call venv\Scripts\deactivate.bat 56 | popd rem %~dp0Practical-RIFE 57 | -------------------------------------------------------------------------------- /Frames2Mp4.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | if "%~1" == "" ( 3 | echo [ERROR] Drag and drop the frames directory. 4 | pause & exit /b 1 5 | ) 6 | 7 | set PATH=%~dp0ffmpeg-master-latest-win64-gpl\bin;%PATH% 8 | 9 | set FRAMES_DIR=%~dpn1 10 | set FPS=10 11 | set CRF=20 12 | if not "%2" == "" ( set FPS=%2 ) 13 | if not "%3" == "" ( set CRF=%3 ) 14 | 15 | pushd %~dp0animatediff-cli-prompt-travel 16 | 17 | echo ffmpeg.exe -y -framerate %FPS% -i "%FRAMES_DIR%\%%08d.png" -pix_fmt yuv420p -vcodec libx264 -tune animation -r %FPS% -crf %CRF% "%FRAMES_DIR%.mp4" 18 | 19 | ffmpeg.exe -y -framerate %FPS%^ 20 | -i "%FRAMES_DIR%\%%08d.png"^ 21 | -pix_fmt yuv420p^ 22 | -vcodec libx264^ 23 | -tune animation^ 24 | -r %FPS%^ 25 | -crf %CRF%^ 26 | "%FRAMES_DIR%.mp4" 27 | 28 | popd rem %~dp0animatediff-cli-prompt-travel 29 | -------------------------------------------------------------------------------- /Generate.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | echo. 3 | set PATH=%~dp0ffmpeg-master-latest-win64-gpl\bin;%PATH% 4 | 5 | set LENGTH=30 6 | set WIDTH=384 7 | set HEIGHT=512 8 | set CONTEXT=16 9 | 10 | set RIFE_DIV=2 11 | set RIFE_FPS=0 12 | set FFMPEG_FPS=0 13 | set FFMPEG_CRF=20 14 | set TILE_UPSCALE_FPS=10 15 | 16 | set /a REFINER_CONTEXT=%CONTEXT%/2 17 | 18 | set UPSCALE1= 19 | set UPSCALE1_HEIGHT= 20 | set UPSCALE2= 21 | set UPSCALE2_HEIGHT= 22 | 23 | set GENERATE_USE_XFORMERS= 24 | set GENERATE_FORCE_HALF_VAE= 25 | set UPSCALE_USE_XFORMERS= 26 | set UPSCALE_FORCE_HALF_VAE= 27 | 28 | if "%~1" == "" ( 29 | echo [ERROR] Drag and drop the configuration .+[OPTIONS].json file. 30 | echo -L[Lenght]-W[Width]-H[Height]-C[Context]-T[TileHeight] 31 | pause & exit /b 1 32 | ) 33 | 34 | set CONFIG_PATH=%~f1 35 | set CONFIG_DIR=%~dp1 36 | set CONFIG_TEMP_PATH=temp\%~nx1 37 | 38 | pushd %~dp0animatediff-cli-prompt-travel 39 | call venv\Scripts\activate.bat 40 | 41 | for /f "tokens=2-12 delims=-" %%a in ("%~n1") do ( 42 | call :PARSE_ARG %%a 43 | call :PARSE_ARG %%b 44 | call :PARSE_ARG %%c 45 | call :PARSE_ARG %%d 46 | call :PARSE_ARG %%e 47 | call :PARSE_ARG %%f 48 | call :PARSE_ARG %%g 49 | call :PARSE_ARG %%h 50 | call :PARSE_ARG %%i 51 | call :PARSE_ARG %%j 52 | call :PARSE_ARG %%k 53 | ) 54 | 55 | if not exist temp\ ( mkdir temp ) 56 | copy /Y "%CONFIG_PATH%" "%CONFIG_TEMP_PATH%" > nul 57 | 58 | echo CONFIG_PATH: %CONFIG_PATH% 59 | echo generate -L %LENGTH% -W %WIDTH% -H %HEIGHT% -C %CONTEXT% 60 | if not "%UPSCALE1%" == "" ( echo %UPSCALE1% -H %UPSCALE1_HEIGHT% ) 61 | if not "%UPSCALE2%" == "" ( echo %UPSCALE2% -H %UPSCALE2_HEIGHT% ) 62 | echo. 63 | 64 | echo animatediff generate -L %LENGTH% -W %WIDTH% -H %HEIGHT% -C %CONTEXT% -c "%CONFIG_TEMP_PATH%" %GENERATE_USE_XFORMERS% %GENERATE_FORCE_HALF_VAE% 65 | animatediff generate -L %LENGTH% -W %WIDTH% -H %HEIGHT% -C %CONTEXT% -c "%CONFIG_TEMP_PATH%" %GENERATE_USE_XFORMERS% %GENERATE_FORCE_HALF_VAE% 66 | if %ERRORLEVEL% neq 0 ( goto :ERROR_EXIT ) 67 | 68 | call :FIND_NEW_DIR output 69 | set GENERATE_DIR=output\%NEW_DIR% 70 | echo Generate: %GENERATE_DIR% 71 | 72 | call :FIND_NEW_DIR %GENERATE_DIR% 73 | set GENERATE_FRAMES_DIR=%GENERATE_DIR%\%NEW_DIR% 74 | echo GenerateFrames: %GENERATE_FRAMES_DIR% 75 | 76 | setlocal enabledelayedexpansion 77 | 78 | if "%UPSCALE1%" == "" ( 79 | copy /Y !GENERATE_DIR!\*.mp4 "%CONFIG_DIR%" > nul 80 | goto :END 81 | ) else if "%UPSCALE1%" == "tile-upscale" ( 82 | echo animatediff %UPSCALE1% -H %UPSCALE1_HEIGHT% %GENERATE_FRAMES_DIR% %UPSCALE_USE_XFORMERS% %UPSCALE_FORCE_HALF_VAE% 83 | animatediff %UPSCALE1% -H %UPSCALE1_HEIGHT% %GENERATE_FRAMES_DIR% %UPSCALE_USE_XFORMERS% %UPSCALE_FORCE_HALF_VAE% 84 | if %ERRORLEVEL% neq 0 ( endlocal & goto :ERROR_EXIT ) 85 | 86 | call :FIND_NEW_DIR upscaled 87 | set UPSCALE1_DIR=upscaled\!NEW_DIR! 88 | 89 | call :FIND_NEW_DIR !UPSCALE1_DIR! 90 | set UPSCALE1_FRAMES_DIR=!UPSCALE1_DIR!\!NEW_DIR! 91 | 92 | @REM echo Frames2Mp4.bat !UPSCALE1_FRAMES_DIR! %TILE_UPSCALE_FPS% %FFMPEG_CRF% 93 | @REM call ..\Frames2Mp4.bat "!UPSCALE1_FRAMES_DIR!" %TILE_UPSCALE_FPS% %FFMPEG_CRF% 94 | ) else if "%UPSCALE1%" == "refine" ( 95 | echo animatediff %UPSCALE1% -H %UPSCALE1_HEIGHT% -C %REFINER_CONTEXT% %GENERATE_FRAMES_DIR% %UPSCALE_USE_XFORMERS% %UPSCALE_FORCE_HALF_VAE% 96 | animatediff %UPSCALE1% -H %UPSCALE1_HEIGHT% -C %REFINER_CONTEXT% %GENERATE_FRAMES_DIR% %UPSCALE_USE_XFORMERS% %UPSCALE_FORCE_HALF_VAE% 97 | if %ERRORLEVEL% neq 0 ( endlocal & goto :ERROR_EXIT ) 98 | 99 | call :FIND_NEW_DIR refine 100 | set UPSCALE1_DIR=refine\!NEW_DIR! 101 | call :FIND_NEW_DIR !UPSCALE1_DIR! 102 | set UPSCALE1_DIR=!UPSCALE1_DIR!\!NEW_DIR! 103 | 104 | call :FIND_NEW_DIR !UPSCALE1_DIR! 105 | set UPSCALE1_FRAMES_DIR=!UPSCALE1_DIR!\!NEW_DIR! 106 | ) else ( 107 | echo [ERROR] Unknown upscale type: %UPSCALE1% 108 | pause 109 | goto :END 110 | ) 111 | 112 | echo UPSCALE1_DIR: !UPSCALE1_DIR! 113 | echo UPSCALE1_FRAMES_DIR: !UPSCALE1_FRAMES_DIR! 114 | echo UPSCALE2: !UPSCALE2! 115 | 116 | if "%UPSCALE2%" == "" ( 117 | copy /Y !UPSCALE1_DIR!\*.mp4 "%CONFIG_DIR%" > nul 118 | goto :END 119 | ) else if "%UPSCALE2%" == "tile-upscale" ( 120 | echo animatediff %UPSCALE2% -H %UPSCALE2_HEIGHT% !UPSCALE1_FRAMES_DIR! %UPSCALE_USE_XFORMERS% %UPSCALE_FORCE_HALF_VAE% 121 | animatediff %UPSCALE2% -H %UPSCALE2_HEIGHT% !UPSCALE1_FRAMES_DIR! %UPSCALE_USE_XFORMERS% %UPSCALE_FORCE_HALF_VAE% 122 | if %ERRORLEVEL% neq 0 ( endlocal & goto :ERROR_EXIT ) 123 | 124 | call :FIND_NEW_DIR upscaled 125 | set UPSCALE2_DIR=upscaled\!NEW_DIR! 126 | 127 | call :FIND_NEW_DIR !UPSCALE2_DIR! 128 | set UPSCALE2_FRAMES_DIR=!UPSCALE2_DIR!\!NEW_DIR! 129 | 130 | @REM echo Frames2Mp4.bat !UPSCALE2_FRAMES_DIR! %TILE_UPSCALE_FPS% %FFMPEG_CRF% 131 | @REM call ..\Frames2Mp4.bat "!UPSCALE2_FRAMES_DIR!" %TILE_UPSCALE_FPS% %FFMPEG_CRF% 132 | ) else if "%UPSCALE2%" == "refine" ( 133 | echo animatediff %UPSCALE2% -H %UPSCALE2_HEIGHT% -C %REFINER_CONTEXT% !UPSCALE1_FRAMES_DIR! %UPSCALE_USE_XFORMERS% %UPSCALE_FORCE_HALF_VAE% 134 | animatediff %UPSCALE2% -H %UPSCALE2_HEIGHT% -C %REFINER_CONTEXT% !UPSCALE1_FRAMES_DIR! %UPSCALE_USE_XFORMERS% %UPSCALE_FORCE_HALF_VAE% 135 | if %ERRORLEVEL% neq 0 ( endlocal & goto :ERROR_EXIT ) 136 | 137 | call :FIND_NEW_DIR refine 138 | set UPSCALE2_DIR=refine\!NEW_DIR! 139 | call :FIND_NEW_DIR !UPSCALE2_DIR! 140 | set UPSCALE2_DIR=!UPSCALE2_DIR!\!NEW_DIR! 141 | 142 | call :FIND_NEW_DIR !UPSCALE2_DIR! 143 | set UPSCALE2_FRAMES_DIR=!UPSCALE2_DIR!\!NEW_DIR! 144 | ) else ( 145 | echo [ERROR] Unknown upscale type: %UPSCALE1% 146 | pause 147 | goto :END 148 | ) 149 | 150 | echo UPSCALE2_DIR: !UPSCALE2_DIR! 151 | echo UPSCALE2_FRAMES_DIR: !UPSCALE2_FRAMES_DIR! 152 | copy /Y !UPSCALE2_DIR!\*.mp4 "%CONFIG_DIR%" > nul 153 | 154 | :END 155 | endlocal 156 | call venv\Scripts\deactivate.bat 157 | popd rem %~dp0animatediff-cli-prompt-travel 158 | 159 | set ZERO_PADDING_TIME=%TIME: =0% 160 | for /f "tokens=2-7 delims=/:. " %%a in ("echo %DATE% %ZERO_PADDING_TIME%") do ( 161 | set MMDD_HHMM_SS=%%b%%c_%%d%%e_%%f 162 | ) 163 | call :FIND_NEW_MP4 "%CONFIG_DIR%" 164 | rename "%CONFIG_DIR%\%NEW_MP4%" "%MMDD_HHMM_SS%-%NEW_MP4:~3%" 165 | 166 | if "%RIFE_DIV%" == "0" ( exit /b 0 ) 167 | call :FIND_NEW_MP4 "%CONFIG_DIR%" 168 | 169 | echo FpsX4.bat "%CONFIG_DIR%\%NEW_MP4%" %RIFE_DIV% %RIFE_FPS% %FFMPEG_FPS% %FFMPEG_CRF% 170 | call %~dp0FpsX4.bat "%CONFIG_DIR%\%NEW_MP4%" %RIFE_DIV% %RIFE_FPS% %FFMPEG_FPS% %FFMPEG_CRF% 171 | 172 | exit /b 0 173 | 174 | :ERROR_EXIT 175 | call venv\Scripts\deactivate.bat 176 | popd rem %~dp0animatediff-cli-prompt-travel 177 | echo [ERROR EXIT] 178 | exit /b 1 179 | 180 | :PARSE_ARG 181 | if "%~1" == "" ( exit /b 0 ) 182 | set ARG=%1 183 | set ARG_KEY=%ARG:~0,1% 184 | set ARG_VALUE=%ARG:~1% 185 | 186 | if "%ARG_KEY%" == "L" ( 187 | set LENGTH=%ARG_VALUE% 188 | ) else if "%ARG_KEY%" == "W" ( 189 | set WIDTH=%ARG_VALUE% 190 | ) else if "%ARG_KEY%" == "H" ( 191 | set HEIGHT=%ARG_VALUE% 192 | ) else if "%ARG_KEY%" == "C" ( 193 | set CONTEXT=%ARG_VALUE% 194 | ) else if "%ARG_KEY%" == "T" ( 195 | if "%UPSCALE1%" == "" ( 196 | set UPSCALE1=tile-upscale 197 | set UPSCALE1_HEIGHT=%ARG_VALUE% 198 | ) else if "%UPSCALE2%" == "" ( 199 | set UPSCALE2=tile-upscale 200 | set UPSCALE2_HEIGHT=%ARG_VALUE% 201 | ) 202 | ) else if "%ARG_KEY%" == "R" ( 203 | if "%UPSCALE1%" == "" ( 204 | set UPSCALE1=refine 205 | set UPSCALE1_HEIGHT=%ARG_VALUE% 206 | ) else if "%UPSCALE2%" == "" ( 207 | set UPSCALE2=refine 208 | set UPSCALE2_HEIGHT=%ARG_VALUE% 209 | ) 210 | ) else if "%ARG_KEY%" == "D" ( 211 | set RIFE_DIV=%ARG_VALUE% 212 | ) else if "%ARG_KEY%" == "I" ( 213 | set RIFE_FPS=%ARG_VALUE% 214 | ) else if "%ARG_KEY%" == "F" ( 215 | set FFMPEG_FPS=%ARG_VALUE% 216 | ) else if "%ARG_KEY%" == "M" ( 217 | set FFMPEG_CRF=%ARG_VALUE% 218 | ) else if "%ARG_KEY%" == "U" ( 219 | set TILE_UPSCALE_FPS=%ARG_VALUE% 220 | ) else if "%ARG_KEY%" == "x" ( 221 | set GENERATE_USE_XFORMERS=--xformers 222 | ) else if "%ARG_KEY%" == "v" ( 223 | set GENERATE_FORCE_HALF_VAE=--half-vae 224 | ) else if "%ARG_KEY%" == "X" ( 225 | set UPSCALE_USE_XFORMERS=--xformers 226 | ) else if "%ARG_KEY%" == "V" ( 227 | set UPSCALE_FORCE_HALF_VAE=--half-vae 228 | ) else ( 229 | echo [ERROR] Unknown argument: %ARG% 230 | pause 231 | ) 232 | exit /b 0 233 | 234 | :FIND_NEW_DIR 235 | set NEW_DIR= 236 | set DIR_COMMAND=dir /b /ad /o-d /tw %1 237 | for /f %%d in ('%DIR_COMMAND%') do ( 238 | set NEW_DIR=%%d 239 | exit /b 0 240 | ) 241 | exit /b 0 242 | 243 | :FIND_NEW_MP4 244 | set NEW_MP4= 245 | set DIR_COMMAND=dir /b /a-d /o-d /tw "%~1*.mp4" 246 | for /f %%f in ('%DIR_COMMAND%') do ( 247 | set NEW_MP4=%%f 248 | exit /b 0 249 | ) 250 | exit /b 0 251 | -------------------------------------------------------------------------------- /GenerateFolder.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | if "%~1" == "" ( goto ERROR_EXIT ) 3 | if not exist "%~1\*" ( goto ERROR_EXIT ) 4 | if not exist "%~1\*.json" ( goto ERROR_EXIT ) 5 | 6 | for /f %%a in ('dir /b "%~1\*.json"') do ( 7 | call "%~dp0Generate.bat" "%~f1\%%a" 8 | @REM if !ERRORLEVEL! neq 0 ( pause & exit /b 1 ) 9 | ) 10 | exit /b 0 11 | 12 | :ERROR_EXIT 13 | echo [ERROR] Drag and drop the FOLDER containing the configuration .+[OPTIONS].json files. 14 | echo -L[Lenght]-W[Width]-H[Height]-C[Context]-T[TileHeight]-R[RefineHeight] 15 | pause 16 | exit /b 1 17 | -------------------------------------------------------------------------------- /GenerateFolderForever.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | :BEGIN 3 | call "%~dp0GenerateFolder.bat" "%~1" 4 | if %ERRORLEVEL% neq 0 ( exit /b 1 ) 5 | timeout /t 3 /nobreak >nul 6 | goto BEGIN 7 | -------------------------------------------------------------------------------- /GenerateForever.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | :BEGIN 3 | call "%~dp0Generate.bat" "%~1" 4 | if %ERRORLEVEL% neq 0 ( exit /b 1 ) 5 | goto BEGIN 6 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Zuntan 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Mp4Crf.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | if "%~1" == "" ( 3 | echo [ERROR] Drag and drop the mp4 file. 4 | pause & exit /b 1 5 | ) 6 | 7 | set PATH=%~dp0ffmpeg-master-latest-win64-gpl\bin;%PATH% 8 | 9 | set CRF=20 10 | if not "%~2" == "" if not "%~2" == "0" ( 11 | set CRF=%~2 12 | ) 13 | 14 | set SRC_PATH="%~f1" 15 | set DEST_PATH="%~dpn1-crf%CRF%.mp4" 16 | 17 | pushd %~dp0animatediff-cli-prompt-travel 18 | 19 | echo ffmpeg.exe -y -i %SRC_PATH% -pix_fmt yuv420p -vcodec libx264 -tune animation -crf %CRF% %DEST_PATH% 20 | ffmpeg.exe -y -i %SRC_PATH% -pix_fmt yuv420p -vcodec libx264 -tune animation -crf %CRF% %DEST_PATH% 21 | 22 | popd rem %~dp0animatediff-cli-prompt-travel 23 | -------------------------------------------------------------------------------- /Mp4Crf26.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | call "%~dp0Mp4Crf.bat" "%~1" 26 3 | -------------------------------------------------------------------------------- /Mp4Crf32.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | call "%~dp0Mp4Crf.bat" "%~1" 32 3 | -------------------------------------------------------------------------------- /Mp4Crf38.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | call "%~dp0Mp4Crf.bat" "%~1" 38 3 | -------------------------------------------------------------------------------- /OpenColabEditor.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | start https://colab.research.google.com/drive/1XeVRMmw-dyALMacKU-_Xj2nMboZL_TM3 3 | -------------------------------------------------------------------------------- /Update.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | pushd %~dp0 4 | git pull 5 | if %errorlevel% neq 0 ( pause & popd & exit /b %errorlevel% ) 6 | popd rem %~dp0 7 | 8 | call "%~dp0src\Update.bat" 9 | -------------------------------------------------------------------------------- /XMp4.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | if "%~1" == "" ( 3 | echo [ERROR] Drag and drop the mp4 file. 4 | pause & exit /b 1 5 | ) 6 | 7 | set PATH=%~dp0ffmpeg-master-latest-win64-gpl\bin;%PATH% 8 | 9 | set SRC_PATH="%~f1" 10 | set DEST_PATH="%~dpn1x.mp4" 11 | 12 | set VF_SCALE= 13 | if not "%~2" == "" if not "%~2" == "0" ( 14 | set VF_SCALE=-vf scale=%~2 15 | ) 16 | 17 | set FPS=-r 40 18 | if not "%~3" == "" ( 19 | if "%~3" == "0" ( 20 | set FPS= 21 | ) else ( 22 | set FPS=-r %~3 23 | ) 24 | ) 25 | 26 | set BPS=25M 27 | if not "%~4" == "" if not "%~4" == "0" ( 28 | set BPS=%~4 29 | ) 30 | 31 | pushd %~dp0animatediff-cli-prompt-travel 32 | 33 | echo ffmpeg.exe -y -i %SRC_PATH% -pix_fmt yuv420p -vcodec libx264 -tune animation %FPS% -vb %BPS% -maxrate %BPS% -bufsize %BPS% %VF_SCALE% %DEST_PATH% 34 | 35 | ffmpeg.exe -y^ 36 | -i %SRC_PATH%^ 37 | -pix_fmt yuv420p^ 38 | -vcodec libx264^ 39 | -tune animation^ 40 | %FPS%^ 41 | -vb %BPS%^ 42 | -maxrate %BPS%^ 43 | -bufsize %BPS%^ 44 | %VF_SCALE%^ 45 | %DEST_PATH% 46 | 47 | popd rem %~dp0animatediff-cli-prompt-travel 48 | 49 | -------------------------------------------------------------------------------- /XMp4H1200.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | call "%~dp0XMp4.bat" "%~1" -1:1200 %2 %3 3 | -------------------------------------------------------------------------------- /XMp4H1900.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | call "%~dp0XMp4.bat" "%~1" -1:1900 %2 %3 3 | -------------------------------------------------------------------------------- /XMp4W1200.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | call "%~dp0XMp4.bat" "%~1" 1200:-1 %2 %3 3 | -------------------------------------------------------------------------------- /XMp4W1920.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | call "%~dp0XMp4.bat" "%~1" 1920:-1 %2 %3 3 | -------------------------------------------------------------------------------- /doc/img/title.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Zuntan03/EasyPromptAnime/a23c46674f2308b25705bfac5720418afe2eb036/doc/img/title.webp -------------------------------------------------------------------------------- /editor/DownloadMenu.json: -------------------------------------------------------------------------------- 1 | { 2 | "model": { 3 | "featurelessMix_v2020237Clearvae.safetensors": { 4 | "url": "https://civitai.com/api/download/models/168615", 5 | "info": "https://civitai.com/models/65202" 6 | }, 7 | "featurelessCuteMix_v10Clearvae.safetensors": { 8 | "url": "https://civitai.com/api/download/models/168584", 9 | "info": "https://civitai.com/models/149907" 10 | }, 11 | "featurelessFlat2DMix_v10.safetensors": { 12 | "url": "https://civitai.com/api/download/models/180234", 13 | "info": "https://civitai.com/models/160209" 14 | }, 15 | "featurelessJRPG3DMix_v10.safetensors": { 16 | "url": "https://civitai.com/api/download/models/174474", 17 | "info": "https://civitai.com/models/155538" 18 | }, 19 | "featureless25DMix_v10.safetensors": { 20 | "url": "https://civitai.com/api/download/models/201448", 21 | "info": "https://civitai.com/models/179512" 22 | }, 23 | "manmaruMix_v30.safetensors": { 24 | "url": "https://civitai.com/api/download/models/188171", 25 | "info": "https://civitai.com/models/86277" 26 | }, 27 | "onigiriMix_v10Clearvae.safetensors": { 28 | "url": "https://civitai.com/api/download/models/168600", 29 | "info": "https://civitai.com/models/146434" 30 | }, 31 | "nadenadesitai_v10.safetensors": { 32 | "url": "https://civitai.com/api/download/models/84669", 33 | "info": "https://civitai.com/models/79846" 34 | }, 35 | "coffeemix_v20.safetensors": { 36 | "url": "https://civitai.com/api/download/models/53475", 37 | "info": "https://civitai.com/models/40630" 38 | }, 39 | "aziibpixelmix_v10.safetensors": { 40 | "url": "https://civitai.com/api/download/models/220049", 41 | "info": "https://civitai.com/models/195730", 42 | "tag": "pixel" 43 | }, 44 | "1990s2000s_v10.safetensors": { 45 | "url": "https://civitai.com/api/download/models/179913", 46 | "info": "https://civitai.com/models/159947", 47 | "tag": "manga" 48 | }, 49 | "aniverse_thxEd14Pruned.safetensors": { 50 | "url": "https://civitai.com/api/download/models/165868", 51 | "info": "https://civitai.com/models/107842", 52 | "tag": "semi-real" 53 | }, 54 | "realcartoon3d_v8.safetensors": { 55 | "url": "https://civitai.com/api/download/models/159751", 56 | "info": "https://civitai.com/models/94809", 57 | "tag": "semi-real" 58 | }, 59 | "xxmix9realistic_v40.safetensors": { 60 | "url": "https://civitai.com/api/download/models/102222", 61 | "info": "https://civitai.com/models/47274", 62 | "tag": "real" 63 | }, 64 | "majicmixRealistic_v7.safetensors": { 65 | "url": "https://civitai.com/api/download/models/176425", 66 | "info": "https://civitai.com/models/43331", 67 | "tag": "real" 68 | }, 69 | "real_model_N.safetensors": { 70 | "url": "https://huggingface.co/fcski/real_model_L/resolve/main/real_model_N.safetensors", 71 | "info": "https://huggingface.co/fcski/real_model_L", 72 | "tag": "real" 73 | } 74 | }, 75 | "lora": { 76 | "flat2.safetensors": { 77 | "url": "https://huggingface.co/2vXpSwA7/iroiro-lora/resolve/main/release/flat2.safetensors", 78 | "info": "https://huggingface.co/2vXpSwA7/iroiro-lora" 79 | }, 80 | "3dSlider_v2.safetensors": { 81 | "url": "https://civitai.com/api/download/models/178752", 82 | "info": "https://civitai.com/models/154017" 83 | }, 84 | "HeightRatioSlider_v2.safetensors": { 85 | "url": "https://civitai.com/api/download/models/173158", 86 | "info": "https://civitai.com/models/153226" 87 | }, 88 | "flatBG.safetensors": { 89 | "url": "https://huggingface.co/2vXpSwA7/iroiro-lora/resolve/main/release/flatBG.safetensors", 90 | "info": "https://huggingface.co/2vXpSwA7/iroiro-lora" 91 | } 92 | }, 93 | "motion_module": { 94 | "mm_sd_v15_v2.ckpt": { 95 | "url": "https://huggingface.co/guoyww/animatediff/resolve/main/mm_sd_v15_v2.ckpt", 96 | "info": "https://huggingface.co/guoyww/animatediff" 97 | }, 98 | "missionarymotion.safetensors": { 99 | "url": "https://civitai.com/api/download/models/134809", 100 | "info": "https://civitai.com/models/123612", 101 | "tag": "NSFW", 102 | "isZip": true 103 | }, 104 | "improvedHumansMotion_refinedHumanMovement.ckpt": { 105 | "url": "https://civitai.com/api/download/models/174464", 106 | "info": "https://civitai.com/models/155528" 107 | }, 108 | "temporaldiffMotion_v10.ckpt": { 109 | "url": "https://civitai.com/api/download/models/160418", 110 | "info": "https://civitai.com/models/144934" 111 | }, 112 | "mm-Stabilized_high.pth": { 113 | "url": "https://huggingface.co/manshoety/AD_Stabilized_Motion/resolve/main/mm-Stabilized_high.pth", 114 | "info": "https://huggingface.co/manshoety/AD_Stabilized_Motion" 115 | }, 116 | "mm-Stabilized_mid.pth": { 117 | "url": "https://huggingface.co/manshoety/AD_Stabilized_Motion/resolve/main/mm-Stabilized_mid.pth", 118 | "info": "https://huggingface.co/manshoety/AD_Stabilized_Motion" 119 | }, 120 | "mm_sd_v15.ckpt": { 121 | "url": "https://huggingface.co/guoyww/animatediff/resolve/main/mm_sd_v15.ckpt", 122 | "info": "https://huggingface.co/guoyww/animatediff" 123 | }, 124 | "mm_sd_v14.ckpt": { 125 | "url": "https://huggingface.co/guoyww/animatediff/resolve/main/mm_sd_v14.ckpt", 126 | "info": "https://huggingface.co/guoyww/animatediff" 127 | }, 128 | "yoinkoorlabsNSFWMotion_godmodev20.ckpt": { 129 | "url": "https://civitai.com/api/download/models/177016", 130 | "info": "https://civitai.com/models/144354", 131 | "tag": "NSFW" 132 | } 133 | }, 134 | "motion_lora": { 135 | "v2_lora_PanLeft.ckpt": { 136 | "url": "https://huggingface.co/guoyww/animatediff/resolve/main/v2_lora_PanLeft.ckpt", 137 | "info": "https://huggingface.co/guoyww/animatediff" 138 | }, 139 | "v2_lora_PanRight.ckpt": { 140 | "url": "https://huggingface.co/guoyww/animatediff/resolve/main/v2_lora_PanRight.ckpt", 141 | "info": "https://huggingface.co/guoyww/animatediff" 142 | }, 143 | "v2_lora_RollingAnticlockwise.ckpt": { 144 | "url": "https://huggingface.co/guoyww/animatediff/resolve/main/v2_lora_RollingAnticlockwise.ckpt", 145 | "info": "https://huggingface.co/guoyww/animatediff" 146 | }, 147 | "v2_lora_RollingClockwise.ckpt": { 148 | "url": "https://huggingface.co/guoyww/animatediff/resolve/main/v2_lora_RollingClockwise.ckpt", 149 | "info": "https://huggingface.co/guoyww/animatediff" 150 | }, 151 | "v2_lora_TiltDown.ckpt": { 152 | "url": "https://huggingface.co/guoyww/animatediff/resolve/main/v2_lora_TiltDown.ckpt", 153 | "info": "https://huggingface.co/guoyww/animatediff" 154 | }, 155 | "v2_lora_TiltUp.ckpt": { 156 | "url": "https://huggingface.co/guoyww/animatediff/resolve/main/v2_lora_TiltUp.ckpt", 157 | "info": "https://huggingface.co/guoyww/animatediff" 158 | }, 159 | "v2_lora_ZoomIn.ckpt": { 160 | "url": "https://huggingface.co/guoyww/animatediff/resolve/main/v2_lora_ZoomIn.ckpt", 161 | "info": "https://huggingface.co/guoyww/animatediff" 162 | }, 163 | "v2_lora_ZoomOut.ckpt": { 164 | "url": "https://huggingface.co/guoyww/animatediff/resolve/main/v2_lora_ZoomOut.ckpt", 165 | "info": "https://huggingface.co/guoyww/animatediff" 166 | } 167 | }, 168 | "vae": { 169 | "kl-f8-anime2.ckpt": { 170 | "url": "https://huggingface.co/hakurei/waifu-diffusion-v1-4/resolve/main/vae/kl-f8-anime2.ckpt", 171 | "info": "https://huggingface.co/hakurei/waifu-diffusion-v1-4" 172 | }, 173 | "orangemix.vae.pt": { 174 | "url": "https://huggingface.co/WarriorMama777/OrangeMixs/resolve/main/VAEs/orangemix.vae.pt", 175 | "info": "https://huggingface.co/WarriorMama777/OrangeMixs" 176 | }, 177 | "vae-ft-ema-560000-ema-pruned.safetensors": { 178 | "url": "https://huggingface.co/stabilityai/sd-vae-ft-ema-original/resolve/main/vae-ft-ema-560000-ema-pruned.safetensors", 179 | "info": "https://huggingface.co/stabilityai/sd-vae-ft-ema-original" 180 | }, 181 | "vae-ft-mse-840000-ema-pruned.safetensors": { 182 | "url": "https://huggingface.co/stabilityai/sd-vae-ft-mse-original/resolve/main/vae-ft-mse-840000-ema-pruned.safetensors", 183 | "info": "https://huggingface.co/stabilityai/sd-vae-ft-mse-original" 184 | }, 185 | "clearvae_v22.safetensors": { 186 | "url": "https://civitai.com/api/download/models/80518", 187 | "info": "https://civitai.com/models/22354" 188 | }, 189 | "liquid111vae_v10.safetensors": { 190 | "url": "https://civitai.com/api/download/models/90456", 191 | "info": "https://civitai.com/models/85106" 192 | } 193 | }, 194 | "embedding": { 195 | "EasyNegative.safetensors": { 196 | "url": "https://huggingface.co/datasets/gsdf/EasyNegative/resolve/main/EasyNegative.safetensors", 197 | "info": "https://huggingface.co/datasets/gsdf/EasyNegative" 198 | }, 199 | "EasyNegativeV2.safetensors": { 200 | "url": "https://huggingface.co/gsdf/Counterfeit-V3.0/resolve/main/embedding/EasyNegativeV2.safetensors", 201 | "info": "https://huggingface.co/gsdf/Counterfeit-V3.0" 202 | } 203 | } 204 | } -------------------------------------------------------------------------------- /editor/bat/refine.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | echo. 3 | set PATH=%~dp0..\..\ffmpeg-master-latest-win64-gpl\bin;%PATH% 4 | 5 | set RIFE_DIV=3 6 | set RIFE_FPS=0 7 | set FFMPEG_FPS=0 8 | set FFMPEG_CRF=20 9 | 10 | pushd %~dp0..\..\animatediff-cli-prompt-travel 11 | call venv\Scripts\activate.bat 12 | if %ERRORLEVEL% neq 0 ( popd & exit /b %ERRORLEVEL% ) 13 | 14 | echo animatediff refine %* 15 | animatediff refine %* 16 | if %ERRORLEVEL% neq 0 ( popd & exit /b %ERRORLEVEL% ) 17 | 18 | setlocal enabledelayedexpansion 19 | 20 | call :FIND_NEW_DIR refine 21 | if %ERRORLEVEL% neq 0 ( popd & exit /b %ERRORLEVEL% ) 22 | set REFINE_DIR=refine\!NEW_DIR! 23 | echo REFINE_DIR: %REFINE_DIR% 24 | 25 | call :FIND_NEW_DIR %REFINE_DIR% 26 | if %ERRORLEVEL% neq 0 ( popd & exit /b %ERRORLEVEL% ) 27 | set UPSCALE_DIR=%REFINE_DIR%\!NEW_DIR! 28 | echo UPSCALE_DIR: %UPSCALE_DIR% 29 | 30 | call :FIND_NEW_MP4 "%UPSCALE_DIR%\" 31 | if %ERRORLEVEL% neq 0 ( popd & exit /b %ERRORLEVEL% ) 32 | 33 | set ZERO_PADDING_TIME=%TIME: =0% 34 | for /f "tokens=2-7 delims=/:. " %%a in ("echo %DATE% %ZERO_PADDING_TIME%") do ( 35 | set YYYY_MMDD_HHMM_SS=%%a_%%b%%c_%%d%%e_%%f 36 | ) 37 | 38 | rename %UPSCALE_DIR%\!NEW_MP4! %YYYY_MMDD_HHMM_SS%-!NEW_MP4:~3! 39 | if %ERRORLEVEL% neq 0 ( popd & exit /b %ERRORLEVEL% ) 40 | 41 | set UPSCALE_MP4=%UPSCALE_DIR%\%YYYY_MMDD_HHMM_SS%-!NEW_MP4:~3! 42 | echo UPSCALE_MP4: %UPSCALE_MP4% 43 | 44 | echo FpsX4.bat "%UPSCALE_MP4%" %RIFE_DIV% %RIFE_FPS% %FFMPEG_FPS% %FFMPEG_CRF% 45 | call "%~dp0..\..\FpsX4.bat" "%UPSCALE_MP4%" %RIFE_DIV% %RIFE_FPS% %FFMPEG_FPS% %FFMPEG_CRF% 46 | if %ERRORLEVEL% neq 0 ( popd & exit /b %ERRORLEVEL% ) 47 | 48 | endlocal 49 | popd rem %~dp0..\..\animatediff-cli-prompt-travel 50 | exit /b 0 51 | 52 | :FIND_NEW_DIR 53 | set NEW_DIR= 54 | set DIR_COMMAND=dir /b /ad /o-d /tw %1 55 | for /f %%d in ('%DIR_COMMAND%') do ( 56 | set NEW_DIR=%%d 57 | exit /b 0 58 | ) 59 | exit /b 0 60 | 61 | :FIND_NEW_MP4 62 | set NEW_MP4= 63 | set DIR_COMMAND=dir /b /a-d /o-d /tw "%~1*.mp4" 64 | for /f %%f in ('%DIR_COMMAND%') do ( 65 | set NEW_MP4=%%f 66 | exit /b 0 67 | ) 68 | exit /b 0 69 | -------------------------------------------------------------------------------- /editor/bat/tile-upscale.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | echo. 3 | set PATH=%~dp0..\..\ffmpeg-master-latest-win64-gpl\bin;%PATH% 4 | 5 | set RIFE_DIV=3 6 | set RIFE_FPS=0 7 | set FFMPEG_FPS=0 8 | set FFMPEG_CRF=20 9 | 10 | pushd %~dp0..\..\animatediff-cli-prompt-travel 11 | call venv\Scripts\activate.bat 12 | if %ERRORLEVEL% neq 0 ( popd & exit /b %ERRORLEVEL% ) 13 | 14 | echo animatediff tile-upscale %* 15 | animatediff tile-upscale %* 16 | if %ERRORLEVEL% neq 0 ( popd & exit /b %ERRORLEVEL% ) 17 | 18 | setlocal enabledelayedexpansion 19 | 20 | call :FIND_NEW_DIR upscaled 21 | if %ERRORLEVEL% neq 0 ( popd & exit /b %ERRORLEVEL% ) 22 | set UPSCALE_DIR=upscaled\!NEW_DIR! 23 | echo UPSCALE_DIR: %UPSCALE_DIR% 24 | 25 | set ZERO_PADDING_TIME=%TIME: =0% 26 | for /f "tokens=2-7 delims=/:. " %%a in ("echo %DATE% %ZERO_PADDING_TIME%") do ( 27 | set YYYY_MMDD_HHMM_SS=%%a_%%b%%c_%%d%%e_%%f 28 | ) 29 | 30 | call :FIND_NEW_MP4 "%UPSCALE_DIR%\" 31 | if %ERRORLEVEL% neq 0 ( popd & exit /b %ERRORLEVEL% ) 32 | 33 | rename %UPSCALE_DIR%\!NEW_MP4! %YYYY_MMDD_HHMM_SS%-!NEW_MP4:~3! 34 | if %ERRORLEVEL% neq 0 ( popd & exit /b %ERRORLEVEL% ) 35 | 36 | set UPSCALE_MP4=%UPSCALE_DIR%\%YYYY_MMDD_HHMM_SS%-!NEW_MP4:~3! 37 | echo UPSCALE_MP4: %UPSCALE_MP4% 38 | 39 | echo FpsX4.bat "%UPSCALE_MP4%" %RIFE_DIV% %RIFE_FPS% %FFMPEG_FPS% %FFMPEG_CRF% 40 | call "%~dp0..\..\FpsX4.bat" "%UPSCALE_MP4%" %RIFE_DIV% %RIFE_FPS% %FFMPEG_FPS% %FFMPEG_CRF% 41 | if %ERRORLEVEL% neq 0 ( popd & exit /b %ERRORLEVEL% ) 42 | 43 | endlocal 44 | popd rem %~dp0..\..\animatediff-cli-prompt-travel 45 | exit /b 0 46 | 47 | :FIND_NEW_DIR 48 | set NEW_DIR= 49 | set DIR_COMMAND=dir /b /ad /o-d /tw %1 50 | for /f %%d in ('%DIR_COMMAND%') do ( 51 | set NEW_DIR=%%d 52 | exit /b 0 53 | ) 54 | exit /b 0 55 | 56 | :FIND_NEW_MP4 57 | set NEW_MP4= 58 | set DIR_COMMAND=dir /b /a-d /o-d /tw "%~1*.mp4" 59 | for /f %%f in ('%DIR_COMMAND%') do ( 60 | set NEW_MP4=%%f 61 | exit /b 0 62 | ) 63 | exit /b 0 64 | -------------------------------------------------------------------------------- /editor/src/EasyPromptAnimeEditor.py: -------------------------------------------------------------------------------- 1 | import os, time 2 | from const import Path 3 | from config import Config 4 | from l10n import L10n 5 | from log import Log 6 | from ui import Form 7 | from model import Model 8 | from controller import Controller 9 | from task import Task 10 | 11 | initStartTime = time.perf_counter() 12 | 13 | Config.load( 14 | Path.ini, 15 | ["default", "default_controlnet", "ui", "ui_color", "ui_preview", "ui_size"], 16 | ) 17 | 18 | L10n.loadConfig() 19 | 20 | 21 | class EasyPromptAnimeEditor: 22 | def __init__(self): 23 | Path.cwd = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..")) 24 | os.chdir(Path.cwd) 25 | 26 | ffmpegBinPath = os.path.join(Path.cwd, "ffmpeg-master-latest-win64-gpl", "bin") 27 | os.environ["PATH"] = f'{ffmpegBinPath};{os.environ["PATH"]}' 28 | 29 | Form.loadConfig() 30 | self.form = Form() 31 | 32 | Model.loadConfig() 33 | self.model = Model() 34 | 35 | Task.initialize(self.form.win) 36 | 37 | self.controller = Controller(self.form, self.model) 38 | 39 | 40 | editor = EasyPromptAnimeEditor() 41 | 42 | Log.user(L10n.format("log_initialization_end", time.perf_counter() - initStartTime)) 43 | Log.user(L10n.get("log_see_prompt_help")) 44 | 45 | editor.form.run() 46 | -------------------------------------------------------------------------------- /editor/src/config.py: -------------------------------------------------------------------------------- 1 | from configparser import ConfigParser 2 | 3 | 4 | class Config: 5 | @classmethod 6 | def get(cls, cateroty, key, fallback): 7 | return cls._config.get(cateroty, key, fallback=str(fallback)) 8 | 9 | @classmethod 10 | def getBool(cls, cateroty, key, fallback): 11 | return cls._config.getboolean(cateroty, key, fallback=fallback) 12 | 13 | @classmethod 14 | def getInt(cls, cateroty, key, fallback): 15 | return cls._config.getint(cateroty, key, fallback=fallback) 16 | 17 | @classmethod 18 | def getFloat(cls, cateroty, key, fallback): 19 | return cls._config.getfloat(cateroty, key, fallback=fallback) 20 | 21 | @classmethod 22 | def set(cls, cateroty, key, value): 23 | cls._config[cateroty][key] = str(value) 24 | 25 | @classmethod 26 | def load(cls, path, sections): 27 | cls._config = ConfigParser(interpolation=None) 28 | [cls._config.add_section(section) for section in sections] 29 | cls._config.read(path, encoding="utf-8-sig") 30 | 31 | @classmethod 32 | def save(cls, path): 33 | with open(path, "w", encoding="utf-8-sig") as cfgfile: 34 | cls._config.write(cfgfile) 35 | -------------------------------------------------------------------------------- /editor/src/const.py: -------------------------------------------------------------------------------- 1 | import os, datetime 2 | from enum import Enum 3 | 4 | 5 | class Const: 6 | tile = "tile-upscale" 7 | refine = "refine" 8 | 9 | schedulerValues = [ 10 | "k_dpmpp_2m_sde", 11 | "k_dpmpp_sde", 12 | "k_dpmpp_2m", 13 | "ddim", 14 | "euler", 15 | "euler_a", 16 | "lcm", 17 | # "dpm_2_a", 18 | # "k_dpm_2_a", 19 | ] 20 | schedulerNames = [ 21 | "DPM++ 2M SDE Karras", 22 | "DPM++ SDE Karras", 23 | "DPM++ 2M Karras", 24 | "DDIM", 25 | "Euler", 26 | "Euler a", 27 | "LCM", 28 | # "DPM2 a", 29 | # "DPM2 a Karras", 30 | ] 31 | 32 | @classmethod 33 | def getSchedulerValue(cls, name): 34 | return cls.schedulerValues[cls.schedulerNames.index(name)] 35 | 36 | @classmethod 37 | def getSchedulerName(cls, value): 38 | return cls.schedulerNames[cls.schedulerValues.index(value)] 39 | 40 | 41 | class ControlNetType(Enum): 42 | canny = 0 43 | depth = 1 44 | inpaint = 2 45 | ip2p = 3 46 | lineart = 4 47 | lineart_anime = 5 48 | mlsd = 6 49 | normalbae = 7 50 | openpose = 8 51 | scribble = 9 52 | seg = 10 53 | shuffle = 11 54 | softedge = 12 55 | tile = 13 56 | 57 | 58 | class Path: 59 | cwd = os.getcwd() 60 | ini = "EasyPromptAnimeEditor.ini" 61 | output = "output" 62 | save = "save" 63 | 64 | promptTravel = "animatediff-cli-prompt-travel" 65 | data = os.path.join(promptTravel, "data") 66 | model = os.path.join(data, "models", "sd") 67 | motionModule = os.path.join(data, "models", "motion-module") 68 | motionLora = os.path.join(data, "models", "motion_lora") 69 | vae = os.path.join(data, "vae") 70 | lora = os.path.join(data, "lora") 71 | embeddings = os.path.join(data, "embeddings") 72 | wildcard = os.path.join(promptTravel, "wildcards") 73 | promptTravelOutput = os.path.join(promptTravel, "output") 74 | promptTravelUpscaled = os.path.join(promptTravel, "upscaled") 75 | promptTravelRefined = os.path.join(promptTravel, "refine") 76 | controlNet = os.path.join(data, "controlnet_image") 77 | ipAdapter = os.path.join(data, "ip_adapter_image") 78 | 79 | editor = "editor" 80 | temp = os.path.join(editor, "temp") 81 | log = os.path.join(editor, "log") 82 | defaultPrompt = os.path.join(editor, "DefaultPrompt-0_1_0.txt") 83 | promptTravelTemplate = os.path.join(editor, "PromptTravelTemplate-0_6_2.txt") 84 | downloadMenu = os.path.join(editor, "DownloadMenu.json") 85 | 86 | @classmethod 87 | def getYYYYMMDDHHMMSS(cls): 88 | return datetime.datetime.now().strftime("%Y_%m%d_%H%M_%S") 89 | 90 | @classmethod 91 | def getYYYYMMDD(cls): 92 | return datetime.datetime.now().strftime("%Y_%m%d") 93 | 94 | @classmethod 95 | def getControlNet(cls, dirName, type): 96 | return os.path.join(cls.controlNet, dirName, f"controlnet_{type.name}") 97 | -------------------------------------------------------------------------------- /editor/src/controller.py: -------------------------------------------------------------------------------- 1 | from const import Path 2 | from config import Config 3 | from l10n import L10n 4 | from ctr_file import FileController 5 | from ctr_menu import MenuController 6 | from ctr_input import InputController 7 | from ctr_preview import PreviewController 8 | from ctr_basic import BasicController 9 | from ctr_generate import GenerateController 10 | from ctr_control_net import ControlNetController 11 | from ctr_ip_adapter import IpAdapterController 12 | from ctr_upscale import UpscaleController 13 | from ctr_mosaic import MosaicController 14 | from ctr_output import OutputController 15 | from prompt_travel import PromptTravel 16 | 17 | 18 | class Controller: 19 | def __init__(self, form, model): 20 | self.form = form 21 | self.model = model 22 | 23 | self.file = FileController(form, model) 24 | 25 | self.menu = MenuController(self, form, model) 26 | self.input = InputController(form, model) 27 | self.preview = PreviewController(form, model) 28 | self.basic = BasicController(form, model) 29 | self.generate = GenerateController(form, model) 30 | self.controlNet = ControlNetController(form, model) 31 | self.ipAdapter = IpAdapterController(form, model) 32 | self.upscale = UpscaleController(form, model) 33 | self.mosaic = MosaicController(form, model) 34 | self.output = OutputController(form, model) 35 | 36 | self.menu.initEvents() 37 | self.input.initEvents() 38 | self.preview.initEvents() 39 | self.controlNet.initEvents() 40 | self.mosaic.initEvents() 41 | 42 | self.model.prompt.loadDefaultPrompt() 43 | lastFrame = self.model.prompt.getLastPrompt()[0] + 10 44 | if lastFrame > self.model.generate.length: 45 | self.model.generate.length = lastFrame 46 | self.model.notifyAll() 47 | self.file.resetChanged() 48 | 49 | PromptTravel.loadTemplate() 50 | self.form.win.after(50, lambda: self.saveConfig() or self.file.resetChanged()) 51 | self.form.win.protocol("WM_DELETE_WINDOW", self.onWinClose) 52 | 53 | def onWinClose(self): 54 | if not self.file.askSave(): 55 | return 56 | self.saveConfig() 57 | self.form.win.destroy() 58 | 59 | def updateConfig(self): 60 | self.model.updateConfig() 61 | 62 | def saveConfig(self): 63 | L10n.storeConfig() 64 | self.model.storeConfig() 65 | self.form.storeConfig() 66 | Config.save(Path.ini) 67 | -------------------------------------------------------------------------------- /editor/src/ctr_basic.py: -------------------------------------------------------------------------------- 1 | from const import Const 2 | from log import Log 3 | import tkinter as tk 4 | 5 | 6 | class BasicController: 7 | def __init__(self, form, model): 8 | self.form = form 9 | self.model = model 10 | 11 | mGenerate = self.model.generate 12 | vBasic = self.form.basic 13 | mGenerate.bind("length", vBasic.varLength) 14 | mGenerate.bind("model", vBasic.varModel) 15 | mGenerate.bind("vae", vBasic.varVae) 16 | mGenerate.bind("seed", vBasic.varSeed) 17 | mGenerate.bind("width", vBasic.varWidth) 18 | mGenerate.bind("height", vBasic.varHeight) 19 | 20 | mGenerate.bind("upscale1Enabled", vBasic.varUpscale1Enabled) 21 | mGenerate.bind("upscale1Mode", vBasic.varUpscale1Mode) 22 | mGenerate.bind("upscale1Scale", vBasic.varUpscale1Scale) 23 | 24 | mGenerate.bind("upscale2Enabled", vBasic.varUpscale2Enabled) 25 | mGenerate.bind("upscale2Mode", vBasic.varUpscale2Mode) 26 | mGenerate.bind("upscale2Scale", vBasic.varUpscale2Scale) 27 | -------------------------------------------------------------------------------- /editor/src/ctr_control_net.py: -------------------------------------------------------------------------------- 1 | import os, subprocess, shutil 2 | import tkinter as tk 3 | from tkinter import filedialog 4 | from const import Path, ControlNetType 5 | from log import Log 6 | from l10n import L10n 7 | 8 | 9 | class ControlNetController: 10 | def __init__(self, form, model): 11 | self.form = form 12 | self.model = model 13 | vControlNet = self.form.controlNet 14 | 15 | mGenerate = self.model.generate 16 | mGenerate.bind("controlNetDir", vControlNet.varControlNetDir) 17 | mGenerate.bind("controlNetLoop", vControlNet.varControlNetLoop) 18 | 19 | mEditor = self.model.editor 20 | mEditor.bind("importSpeed", vControlNet.varImportSpeed) 21 | mEditor.bind("importStart", vControlNet.varImportStart) 22 | mEditor.bind("importLength", vControlNet.varImportLength) 23 | mEditor.bind("importIndex", vControlNet.varImportIndex) 24 | 25 | for cnType in ControlNetType: 26 | mGenerate.bind( 27 | f"cnEnable_{cnType.name}", 28 | getattr(vControlNet, f"varEnable_{cnType.name}"), 29 | ) 30 | mGenerate.bind( 31 | f"cnUsePreprocessor_{cnType.name}", 32 | getattr(vControlNet, f"varUsePreprocessor_{cnType.name}"), 33 | ) 34 | mGenerate.bind( 35 | f"cnGuessMode_{cnType.name}", 36 | getattr(vControlNet, f"varGuessMode_{cnType.name}"), 37 | ) 38 | mGenerate.bind( 39 | f"cnScale_{cnType.name}", 40 | getattr(vControlNet, f"varScale_{cnType.name}"), 41 | ) 42 | mGenerate.bind( 43 | f"cnScaleList_{cnType.name}", 44 | getattr(vControlNet, f"varScaleList_{cnType.name}"), 45 | ) 46 | mGenerate.bind( 47 | f"cnStart_{cnType.name}", 48 | getattr(vControlNet, f"varStart_{cnType.name}"), 49 | ) 50 | mGenerate.bind( 51 | f"cnEnd_{cnType.name}", 52 | getattr(vControlNet, f"varEnd_{cnType.name}"), 53 | ) 54 | 55 | def initEvents(self): 56 | mGenerate = self.model.generate 57 | mGenerate.subsc("controlNetDir", self.selectControlNetDir) 58 | for cnType in ControlNetType: 59 | mGenerate.subsc( 60 | f"cnEnable_{cnType.name}", 61 | lambda *args, name=cnType.name: self.updateEnable(name, args[2]), 62 | ) 63 | 64 | vControlNet = self.form.controlNet 65 | vControlNet.btnImport.configure(command=self.importMovie) 66 | 67 | def selectControlNetDir(self, *args): 68 | basePath = os.path.join(Path.controlNet, args[2]) 69 | for cnType in ControlNetType: 70 | os.makedirs( 71 | os.path.join(basePath, f"controlnet_{cnType.name}"), exist_ok=True 72 | ) 73 | 74 | def updateEnable(self, name, isEnabled): 75 | vControlNet = self.form.controlNet 76 | frm = getattr(vControlNet, f"frm_{name}") 77 | vControlNet.ntb.tab(frm, text="* " + name if isEnabled else name) 78 | 79 | def importMovie(self): 80 | srcPath = filedialog.askopenfilename( 81 | title=L10n.get("dlg_control_net_import"), filetypes=[("MP4", "*.mp4")] 82 | ) 83 | mEditor = self.model.editor 84 | speed = mEditor.importSpeed 85 | start = mEditor.importStart 86 | length = mEditor.importLength 87 | index = mEditor.importIndex 88 | basePath = os.path.join(Path.controlNet, self.model.generate.controlNetDir) 89 | 90 | cmd = ["ffmpeg", "-i", srcPath] 91 | vf = "fps=10,scale='if(gt(a,1),-1,512)':'if(gt(a,1),512,-1)'" 92 | if speed != "": 93 | vf = f"setpts=PTS/{speed},{vf}" 94 | cmd += ["-vf", vf] 95 | if start != "": 96 | cmd += ["-ss", start] 97 | if length != "": 98 | cmd += ["-t", length] 99 | if index != "": 100 | cmd += ["-start_number", index] 101 | outDir = os.path.join(basePath, f"controlnet_canny") + os.path.sep 102 | cmd += [f"{outDir}%08d.png"] 103 | 104 | Log.system(cmd) 105 | result = subprocess.run(cmd, shell=True) 106 | if result.returncode != 0: 107 | return 108 | for cnType in ControlNetType: 109 | if cnType == ControlNetType.canny: 110 | continue 111 | shutil.copytree( 112 | os.path.join(basePath, f"controlnet_canny"), 113 | os.path.join(basePath, f"controlnet_{cnType.name}"), 114 | dirs_exist_ok=True, 115 | ) 116 | -------------------------------------------------------------------------------- /editor/src/ctr_file.py: -------------------------------------------------------------------------------- 1 | import os 2 | from tkinter import filedialog, messagebox 3 | import json 4 | from const import Path 5 | from l10n import L10n 6 | from log import Log 7 | from serializer import Serializer 8 | 9 | 10 | class FileController: 11 | def __init__(self, form, model): 12 | self.form = form 13 | self.model = model 14 | 15 | self.form.win.bind("", lambda e: self.new()) 16 | self.form.win.bind("", lambda e: self.open()) 17 | self.form.win.bind("", lambda e: self.save()) 18 | 19 | # TODO: new 20 | def new(self): 21 | if not self.askSave(): 22 | return False 23 | self.model.reset() 24 | self.model.notifyAll() 25 | self.form.win.after(0, self.resetChanged) 26 | self.form.win.title(L10n.get("title")) 27 | 28 | Log.user(L10n.get("log_file_new")) 29 | return True 30 | 31 | def open(self): 32 | if not self.askSave(): 33 | return False 34 | os.path.exists(Path.save) or os.makedirs(Path.save) 35 | initDir = Path.save 36 | if self.model.editor.savePath != "": 37 | initDir = os.path.dirname(self.model.editor.savePath) 38 | openPath = filedialog.askopenfilename( 39 | filetypes=[(L10n.get("title"), "*.json")], # TODO: promptTravelConfig 40 | initialdir=initDir, 41 | ) 42 | if openPath == "": 43 | return False 44 | data = None 45 | with open(openPath, "r", encoding="utf-8-sig") as f: 46 | data = json.load(f) 47 | if Serializer.versionKey in data: 48 | if Serializer.deserialize(self.model, data): 49 | self.model.editor.set("savePath", openPath) 50 | self.form.win.title(L10n.get("title") + openPath) 51 | self.resetChanged() 52 | Log.user(L10n.format("log_file_open", openPath)) 53 | return True 54 | else: 55 | Log.user(L10n.format("log_file_open_failed", openPath)) 56 | return False 57 | else: 58 | return False # TODO: import prompt travel config 59 | 60 | def save(self): 61 | if self.model.editor.savePath == "": 62 | return self.saveAs() 63 | data = Serializer.serialize(self.model) 64 | with open(self.model.editor.savePath, "w", encoding="utf-8-sig") as f: 65 | json.dump(data, f, indent=4) 66 | self.resetChanged() 67 | Log.user(L10n.format("log_file_save", self.model.editor.savePath)) 68 | return True 69 | 70 | def saveAs(self): 71 | os.path.exists(Path.save) or os.makedirs(Path.save) 72 | initDir = Path.save 73 | if self.model.editor.savePath != "": 74 | initDir = os.path.dirname(self.model.editor.savePath) 75 | saveAsPath = filedialog.asksaveasfilename( 76 | filetypes=[(L10n.get("title"), "*.json")], initialdir=initDir 77 | ) 78 | if saveAsPath == "": 79 | return False 80 | if not saveAsPath.endswith(".json"): 81 | saveAsPath += ".json" 82 | data = Serializer.serialize(self.model) 83 | with open(saveAsPath, "w", encoding="utf-8-sig") as f: 84 | json.dump(data, f, indent=4) 85 | self.model.editor.set("savePath", saveAsPath) 86 | self.form.win.title(L10n.get("title") + saveAsPath) 87 | self.resetChanged() 88 | Log.user(L10n.format("log_file_save_as", saveAsPath)) 89 | return True 90 | 91 | def askSave(self): 92 | if not self.isChanged(): 93 | return True 94 | result = messagebox.askyesnocancel(L10n.get("title"), L10n.get("dlg_ask_save")) 95 | if result is None: 96 | return False 97 | if result: 98 | return self.save() 99 | return True 100 | 101 | def isChanged(self): 102 | return ( 103 | self.model.prompt.isChanged 104 | or self.model.generate.isChanged 105 | or self.model.editor.isChanged 106 | ) 107 | 108 | def resetChanged(self): 109 | self.model.prompt.isChanged = False 110 | self.model.generate.isChanged = False 111 | self.model.editor.isChanged = False 112 | -------------------------------------------------------------------------------- /editor/src/ctr_generate.py: -------------------------------------------------------------------------------- 1 | from log import Log 2 | import tkinter as tk 3 | 4 | 5 | class GenerateController: 6 | def __init__(self, form, model): 7 | self.form = form 8 | self.model = model 9 | 10 | mGenerate = self.model.generate 11 | vGenerate = self.form.generate 12 | 13 | mGenerate.bind("motionModule", vGenerate.varMotionModule) 14 | mGenerate.bind("context", vGenerate.varContext) 15 | mGenerate.bind("scheduler", vGenerate.varScheduler) 16 | mGenerate.bind("steps", vGenerate.varSteps) 17 | mGenerate.bind("guidanceScale", vGenerate.varGuidanceScale) 18 | mGenerate.bind("clipSkip", vGenerate.varClipSkip) 19 | mGenerate.bind("promptFixedRatio", vGenerate.varPromptFixedRatio) 20 | mGenerate.bind("useLcm", vGenerate.varUseLcm) 21 | mGenerate.bind("useHighresFix", vGenerate.varUseHighresFix) 22 | mGenerate.bind("useHalfVae", vGenerate.varUseHalfVae) 23 | mGenerate.bind("useXFormers", vGenerate.varUseXFormers) 24 | -------------------------------------------------------------------------------- /editor/src/ctr_input.py: -------------------------------------------------------------------------------- 1 | import tkinter as tk 2 | from l10n import L10n 3 | from task import Task 4 | from tsk_generate import GenerateTask 5 | 6 | 7 | class InputController: 8 | def __init__(self, form, model): 9 | self.form = form 10 | self.model = model 11 | 12 | vInput = self.form.input 13 | self.model.prompt.subsc("text", self.setTxtInput) 14 | vInput.txtInput.bind("<>", self.modifiedTxtInput) 15 | self.bindKeyboardShortcuts() 16 | 17 | mEdit = self.model.editor 18 | mEdit.bind("previewUpscale", vInput.varUpscale) 19 | # mEdit.bind("previewInterpolation", vInput.varInterpolation) 20 | mEdit.bind("previewStart", vInput.varStart) 21 | mEdit.bind("previewLength", vInput.varLength) 22 | 23 | mEdit.bind("taskForever", vInput.varForever) 24 | mEdit.bind("taskPauseByError", vInput.varPauseByError) 25 | 26 | vInput.btnGenerate.configure(command=self.onGenerate) 27 | vInput.btnSeedGacha.configure(command=self.onSeedGacha) 28 | vInput.btnPreview.configure(command=self.onPreview) 29 | 30 | def setTxtInput(self, *args): 31 | vTxtInput = self.form.input.txtInput 32 | mOriginal = self.model.prompt.text 33 | if mOriginal == vTxtInput.get("1.0", tk.END): 34 | return 35 | vTxtInput.delete("1.0", tk.END) 36 | vTxtInput.insert(tk.END, mOriginal) 37 | 38 | def modifiedTxtInput(self, *args): 39 | vTxtInput = self.form.input.txtInput 40 | self.model.prompt.set("text", vTxtInput.get("1.0", tk.END)) 41 | vTxtInput.edit_modified(False) 42 | 43 | def bindKeyboardShortcuts(self): 44 | pass # TODO: txtInput shortcuts 45 | 46 | def onGenerate(self): 47 | GenerateTask.generate(self.model) 48 | 49 | def onSeedGacha(self): 50 | GenerateTask.seedGacha(self.model) 51 | 52 | def onPreview(self): 53 | GenerateTask.preview(self.model) 54 | 55 | def initEvents(self): 56 | self.model.editor.subsc("previewStart", self.onPreviewStartChanged) 57 | self.model.editor.subsc("previewLength", self.onPreviewLengthChanged) 58 | self.model.generate.subsc("length", self.onLengthChanged) 59 | 60 | def onPreviewStartChanged(self, *args): 61 | lengthTo = self.model.generate.length - self.model.editor.previewStart 62 | self.form.input.sliLength.configure(to=lengthTo) 63 | self.updatePreviewLengthLbl() 64 | 65 | def onPreviewLengthChanged(self, *args): 66 | startTo = self.model.generate.length - self.model.editor.previewLength 67 | self.form.input.sliStart.configure(to=startTo) 68 | self.updatePreviewLengthLbl() 69 | 70 | def onLengthChanged(self, *args): 71 | startTo = self.model.generate.length - self.model.editor.previewLength 72 | self.form.input.sliStart.configure(to=startTo) 73 | lengthTo = self.model.generate.length - self.model.editor.previewStart 74 | self.form.input.sliLength.configure(to=lengthTo) 75 | self.updatePreviewLengthLbl() 76 | 77 | def updatePreviewLengthLbl(self): 78 | start = int(self.model.editor.previewStart / 10) 79 | end = int(self.model.getPreviewEnd() / 10) 80 | lbl = f"{L10n.get('preview_length')}{start}-{end}{L10n.get('sec')}" 81 | self.form.input.lblLength.configure(text=lbl) 82 | -------------------------------------------------------------------------------- /editor/src/ctr_ip_adapter.py: -------------------------------------------------------------------------------- 1 | from log import Log 2 | import tkinter as tk 3 | 4 | 5 | class IpAdapterController: 6 | def __init__(self, form, model): 7 | self.form = form 8 | self.model = model 9 | mGenerate = self.model.generate 10 | vIpAdapter = self.form.ipAdapter 11 | 12 | mGenerate.bind("useIpAdapter", vIpAdapter.varUse) 13 | mGenerate.bind("useIpAdapterPlus", vIpAdapter.varUsePlus) 14 | mGenerate.bind("useIpAdapterPlusFace", vIpAdapter.varUsePlusFace) 15 | mGenerate.bind("ipAdapterScale", vIpAdapter.varScale) 16 | mGenerate.bind("ipAdapterImageDir", vIpAdapter.varImageDir) 17 | -------------------------------------------------------------------------------- /editor/src/ctr_menu.py: -------------------------------------------------------------------------------- 1 | import os, subprocess 2 | import tkinter as tk 3 | from tkinter import filedialog 4 | from const import Path 5 | from l10n import L10n 6 | from log import Log 7 | from tsk_generate import GenerateTask 8 | from tsk_upscale import UpscaleTask 9 | from ui import Form 10 | 11 | 12 | class MenuController: 13 | def __init__(self, parent, form, model): 14 | self.parent = parent 15 | self.form = form 16 | self.model = model 17 | 18 | def initEvents(self): 19 | self.initFileEvents() 20 | self.initAnimeEvents() 21 | self.initFolderEvents() 22 | self.initToolEvents() 23 | self.initDownloadEvents() 24 | self.initSettingEvents() 25 | self.initHelpEvents() 26 | 27 | def initFileEvents(self): 28 | sfl = self.form.menu.fileMenu 29 | sfl.entryconfig(L10n.get("m_file_new"), command=self.parent.file.new) 30 | sfl.entryconfig(L10n.get("m_file_open"), command=self.parent.file.open) 31 | sfl.entryconfig(L10n.get("m_file_save"), command=self.parent.file.save) 32 | sfl.entryconfig(L10n.get("m_file_save_as"), command=self.parent.file.saveAs) 33 | sfl.entryconfig(L10n.get("m_file_exit"), command=self.parent.onWinClose) 34 | 35 | def initAnimeEvents(self): 36 | anime = self.form.menu.animeMenu 37 | anime.entryconfig( 38 | L10n.get("m_anime_preview"), 39 | command=lambda: GenerateTask.preview(self.model), 40 | ) 41 | anime.entryconfig( 42 | L10n.get("m_anime_seed_gacha"), 43 | command=lambda: GenerateTask.seedGacha(self.model), 44 | ) 45 | anime.entryconfig( 46 | L10n.get("m_anime_generate"), 47 | command=lambda: GenerateTask.generate(self.model), 48 | ) 49 | anime.entryconfig( 50 | L10n.get("m_anime_upscale"), command=lambda: self.upscale(False) 51 | ) 52 | anime.entryconfig( 53 | L10n.get("m_anime_upscale_config"), command=lambda: self.upscale(True) 54 | ) 55 | 56 | def upscale(self, configOverride): 57 | Log.user( 58 | L10n.get("log_upscale_config_override" if configOverride else "log_upscale") 59 | ) 60 | upscaleDir = filedialog.askdirectory( 61 | title=L10n.get("dlg_upscale_title"), initialdir=Path.promptTravelOutput 62 | ) 63 | 64 | if upscaleDir == "": 65 | return 66 | if configOverride: 67 | UpscaleTask.upscaleWithConfigOverride(self.model, upscaleDir) 68 | else: 69 | UpscaleTask.upscale(self.model, upscaleDir) 70 | 71 | def initFolderEvents(self): 72 | fld = self.form.menu.folderMenu 73 | self.openFolder(fld, "m_output_folder", Path.output) 74 | 75 | ptf = self.form.menu.promptTravelFolderMenu 76 | self.openFolder(ptf, "m_sd_model_folder", Path.model) 77 | self.openFolder(ptf, "m_motion_module_folder", Path.motionModule) 78 | self.openFolder(ptf, "m_lora_folder", Path.lora) 79 | self.openFolder(ptf, "m_embeddings_folder", Path.embeddings) 80 | self.openFolder(ptf, "m_wildcard_folder", Path.wildcard) 81 | 82 | ptof = self.form.menu.promptTravelOutputFolderMenu 83 | self.openFolder( 84 | ptof, "m_prompt_travel_generate_folder", Path.promptTravelOutput 85 | ) 86 | self.openFolder(ptof, "m_prompt_travel_tile_folder", Path.promptTravelUpscaled) 87 | self.openFolder(ptof, "m_prompt_travel_refine_folder", Path.promptTravelRefined) 88 | 89 | self.openFolder(fld, "m_temp_folder", Path.temp) 90 | self.openFolder(fld, "m_log_folder", Path.log) 91 | 92 | def initToolEvents(self): 93 | tool = self.form.menu.toolMenu 94 | tool.entryconfig( 95 | L10n.get("m_tool_convert_lora"), command=lambda: self.convertLora() 96 | ) 97 | 98 | self.openUrl(tool, "m_tool_easy_leco", L10n.get("m_tool_easy_leco_url")) 99 | 100 | def convertLora(self): 101 | srcPath = filedialog.askopenfilename( 102 | title=L10n.get("dlg_convert_lora_title"), 103 | filetypes=[("C3Lier LoRA or LoCon LoRA", "*.safetensors")], 104 | initialdir=Path.lora, 105 | ) 106 | if srcPath is None or srcPath == "": 107 | return 108 | srcPath = os.path.abspath(srcPath) 109 | modelPath = os.path.abspath(os.path.join(Path.model, self.model.generate.model)) 110 | dim = 32 111 | dstPath = os.path.abspath(os.path.join(Path.lora, os.path.basename(srcPath))) 112 | Log.user(L10n.format("log_convert_lora", dim, srcPath, modelPath, dstPath)) 113 | subprocess.run( 114 | ["start", "cmd", "/c", "ConvertLora.bat", srcPath, f"{dim}", modelPath], 115 | shell=True, 116 | ) 117 | 118 | def initDownloadEvents(self): 119 | self.categoryDirs = { 120 | "model": Path.model, 121 | "lora": Path.lora, 122 | "motion_module": Path.motionModule, 123 | "motion_lora": Path.motionLora, 124 | "vae": Path.vae, 125 | "embedding": Path.embeddings, 126 | } 127 | dlMenu = self.form.menu.downloadMenu 128 | dlMenu.configure(postcommand=self.updateDownloadMenu) 129 | 130 | for category, categoryData in self.form.menu.downloadMenuData.items(): 131 | for name, data in categoryData.items(): 132 | label = f'({data["tag"]}) {name}' if "tag" in data else name 133 | data["menu"].entryconfig( 134 | label, 135 | command=lambda c=category, n=name, d=data: self.download(c, n, d), 136 | ) 137 | 138 | def updateDownloadMenu(self): 139 | for category, categoryData in self.form.menu.downloadMenuData.items(): 140 | for name, data in categoryData.items(): 141 | dstPath = os.path.join(self.categoryDirs[category], name) 142 | data["checked"].set(os.path.exists(dstPath)) 143 | 144 | def download(self, category, name, data): 145 | dstPath = os.path.join(self.categoryDirs[category], name) 146 | unzipDir = "" 147 | if "isZip" in data and data["isZip"]: 148 | unzipDir = os.path.dirname(dstPath) 149 | dstPath = os.path.join(os.path.dirname(dstPath), "temp.zip") 150 | 151 | Log.user(L10n.format("log_start_download", name, data["info"])) 152 | cmd = f'echo {data["info"]} & curl -Lo {dstPath} {data["url"]} || pause' 153 | if unzipDir != "": 154 | cmd += f" & PowerShell -Version 5.1 -ExecutionPolicy Bypass Expand-Archive -Path {dstPath} -DestinationPath {unzipDir} -Force || pause & del {dstPath}" 155 | subprocess.run(["start", "cmd", "/c", cmd], shell=True) 156 | 157 | def initSettingEvents(self): 158 | stm = self.form.menu.settingMenu 159 | 160 | stm.entryconfig( 161 | L10n.get("m_set_default_prompt"), 162 | command=lambda: self.model.prompt.saveDefaultPrompt(), 163 | ) 164 | stm.entryconfig( 165 | L10n.get("m_set_default_setting"), 166 | command=lambda: self.parent.updateConfig() or self.parent.saveConfig(), 167 | ) 168 | stm.entryconfig( 169 | L10n.get("m_set_change_light_dark"), 170 | command=lambda: setattr(Form, "invOsCol", not Form.invOsCol), 171 | ) 172 | self.openFile(stm, "m_set_open_ini_file", Path.ini) 173 | self.openFile(stm, "m_set_open_default_prompt_file", Path.defaultPrompt) 174 | self.openFile( 175 | stm, "m_set_open_prompt_travel_template_file", Path.promptTravelTemplate 176 | ) 177 | 178 | def initHelpEvents(self): 179 | hlp = self.form.menu.helpMenu 180 | hlp.entryconfig( 181 | L10n.get("m_prompt_help"), 182 | command=lambda: self.form.input.txtInput.insert( 183 | tk.END, L10n.get("hlp_prompt") 184 | ), 185 | ) 186 | 187 | self.openUrl(hlp, "m_github", "https://github.com/Zuntan03/EasyPromptAnime") 188 | 189 | ref = self.form.menu.referenceMenu 190 | self.openUrlNoL10n( 191 | ref, 192 | "AnimateDiff prompt travel", 193 | "https://github.com/s9roll7/animatediff-cli-prompt-travel", 194 | ) 195 | self.openUrlNoL10n(ref, "sd-scripts", "https://github.com/kohya-ss/sd-scripts") 196 | self.openUrlNoL10n(ref, "Codex FFmpeg", "https://github.com/GyanD/codexFFmpeg") 197 | self.openUrlNoL10n( 198 | ref, "Practical-RIFE", "https://github.com/hzwer/Practical-RIFE" 199 | ) 200 | self.openUrlNoL10n(ref, "NudeNet", "https://github.com/notAI-tech/NudeNet") 201 | 202 | def openFile(self, menu, name, path): 203 | menu.entryconfig( 204 | L10n.get(name), 205 | command=lambda: subprocess.run(["start", path], shell=True), 206 | ) 207 | 208 | def openFolder(self, menu, name, path): 209 | menu.entryconfig( 210 | L10n.get(name), 211 | command=lambda: subprocess.run(["explorer", path]), 212 | ) 213 | 214 | def openUrl(self, menu, name, url): 215 | menu.entryconfig( 216 | L10n.get(name), command=lambda: subprocess.run(["start", url], shell=True) 217 | ) 218 | 219 | def openUrlNoL10n(self, menu, name, url): 220 | menu.entryconfig( 221 | name, command=lambda: subprocess.run(["start", url], shell=True) 222 | ) 223 | -------------------------------------------------------------------------------- /editor/src/ctr_mosaic.py: -------------------------------------------------------------------------------- 1 | import os, shutil, subprocess 2 | import tkinter as tk 3 | from tkinter import filedialog 4 | from const import Path 5 | from log import Log 6 | from l10n import L10n 7 | from mosaic import Mosaic 8 | 9 | 10 | class MosaicController: 11 | def __init__(self, form, model): 12 | self.form = form 13 | self.model = model 14 | mGenerate = self.model.generate 15 | vMosaic = self.form.mosaic 16 | 17 | mGenerate.bind("mosaicEnabled", vMosaic.varMosaicEnabled) 18 | mGenerate.bind("mosaicThreshold", vMosaic.varMosaicThreshold) 19 | mGenerate.bind("temporalMosaic", vMosaic.varTemporalMosaic) 20 | mGenerate.bind("ellipseMosaic", vMosaic.varEllipseMosaic) 21 | mGenerate.bind("mosaicMaskBlur", vMosaic.varMosaicMaskBlur) 22 | mGenerate.bind("mosaicFemFace", vMosaic.varFemFace) 23 | mGenerate.bind("mosaicFemBrst", vMosaic.varFemBrst) 24 | mGenerate.bind("mosaicFemBrstCov", vMosaic.varFemBrstCov) 25 | mGenerate.bind("mosaicFemGntl", vMosaic.varFemGntl) 26 | mGenerate.bind("mosaicFemGntlCov", vMosaic.varFemGntlCov) 27 | mGenerate.bind("mosaicMaleFace", vMosaic.varMaleFace) 28 | mGenerate.bind("mosaicMaleBrst", vMosaic.varMaleBrst) 29 | mGenerate.bind("mosaicMaleGntl", vMosaic.varMaleGntl) 30 | mGenerate.bind("mosaicArmpit", vMosaic.varArmpit) 31 | mGenerate.bind("mosaicArmpitCov", vMosaic.varArmpitCov) 32 | mGenerate.bind("mosaicBelly", vMosaic.varBelly) 33 | mGenerate.bind("mosaicBellyCov", vMosaic.varBellyCov) 34 | mGenerate.bind("mosaicHip", vMosaic.varHip) 35 | mGenerate.bind("mosaicHipCov", vMosaic.varHipCov) 36 | mGenerate.bind("mosaicAns", vMosaic.varAns) 37 | mGenerate.bind("mosaicAnsCov", vMosaic.varAnsCov) 38 | mGenerate.bind("mosaicFeet", vMosaic.varFeet) 39 | mGenerate.bind("mosaicFeetCov", vMosaic.varFeetCov) 40 | mGenerate.bind("mosaicScaleTop", vMosaic.varMosaicScaleTop) 41 | mGenerate.bind("mosaicScaleBottom", vMosaic.varMosaicScaleBottom) 42 | mGenerate.bind("mosaicScaleLeft", vMosaic.varMosaicScaleLeft) 43 | mGenerate.bind("mosaicScaleRight", vMosaic.varMosaicScaleRight) 44 | mGenerate.bind("mosaicIgnoreTop", vMosaic.varMosaicIgnoreTop) 45 | mGenerate.bind("mosaicIgnoreBottom", vMosaic.varMosaicIgnoreBottom) 46 | mGenerate.bind("mosaicIgnoreLeft", vMosaic.varMosaicIgnoreLeft) 47 | mGenerate.bind("mosaicIgnoreRight", vMosaic.varMosaicIgnoreRight) 48 | 49 | def initEvents(self): 50 | self.form.mosaic.btnMosaicPngDir.configure(command=self.onMosaicPngDir) 51 | 52 | def onMosaicPngDir(self): 53 | Log.user(L10n.get("log_please_wait")) 54 | srcDir = filedialog.askdirectory( 55 | title=L10n.get("dlg_mosaic_title"), initialdir=Path.promptTravelUpscaled 56 | ) 57 | if srcDir == "": 58 | return 59 | mosaicDir = Mosaic.mosaicPngDir(self.model.generate, srcDir) 60 | if mosaicDir == "": 61 | Log.system(f"[FAILED] Mosaic.mosaicPngDir()") 62 | return 63 | 64 | frames2Mp4Result = subprocess.run(["Frames2Mp4.bat", mosaicDir]) 65 | if frames2Mp4Result.returncode != 0: 66 | Log.system(f"[FAILED] Frames2Mp4.bat: {frames2Mp4Result.returncode}") 67 | return 68 | 69 | fpsX6Result = subprocess.run(["FpsX6.bat", mosaicDir + ".mp4"]) 70 | if fpsX6Result.returncode != 0: 71 | Log.system(f"[FAILED] FpsX6.bat: {fpsX6Result.returncode}") 72 | return 73 | 74 | outputDir = os.path.join(Path.output, Path.getYYYYMMDD()) 75 | os.makedirs(outputDir, exist_ok=True) 76 | dstName = f"{Path.getYYYYMMDDHHMMSS()}{os.path.basename(mosaicDir)[2:]}.mp4" 77 | outputPath = os.path.join(outputDir, dstName) 78 | shutil.copy(mosaicDir + "-M6e.mp4", outputPath) 79 | 80 | subprocess.Popen(f"{self.model.editor.ffplayCmd} {outputPath}", shell=True) 81 | -------------------------------------------------------------------------------- /editor/src/ctr_output.py: -------------------------------------------------------------------------------- 1 | from log import Log 2 | import tkinter as tk 3 | 4 | 5 | class OutputController: 6 | def __init__(self, form, model): 7 | self.form = form 8 | self.model = model 9 | Log.systemLogEvent.append(self.onSystemLog) 10 | Log.userLogEvent.append(self.onUserLog) 11 | 12 | def onSystemLog(self, msg): 13 | op = self.form.output 14 | op.txtSys.configure(state=tk.NORMAL) 15 | op.txtSys.insert(tk.END, msg) 16 | op.txtSys.see(tk.END) 17 | op.txtSys.insert(tk.END, "\n") 18 | op.txtSys.configure(state=tk.DISABLED) 19 | 20 | def onUserLog(self, msg): 21 | op = self.form.output 22 | op.txtUser.configure(state=tk.NORMAL) 23 | op.txtUser.insert(tk.END, msg) 24 | op.txtUser.see(tk.END) 25 | op.txtUser.insert(tk.END, "\n") 26 | op.txtUser.configure(state=tk.DISABLED) 27 | -------------------------------------------------------------------------------- /editor/src/ctr_preview.py: -------------------------------------------------------------------------------- 1 | import tkinter as tk 2 | 3 | 4 | class PreviewController: 5 | def __init__(self, form, model): 6 | self.form = form 7 | self.model = model 8 | 9 | mEdit = self.model.editor 10 | vPrev = self.form.preview 11 | mEdit.bind("previewShowKeyframe", vPrev.varShowKeyframe) 12 | mEdit.bind("previewShowHeaderFooter", vPrev.varShowHeaderFooter) 13 | mEdit.bind("previewShowAnime", vPrev.varShowAnime) 14 | 15 | def initEvents(self): 16 | self.initPreview() 17 | 18 | def initPreview(self): 19 | self.model.prompt.subsc("text", self.updatePerview) 20 | self.model.generate.subsc("length", self.updatePerview) 21 | 22 | mEdit = self.model.editor 23 | mEdit.subsc("previewShowKeyframe", self.updatePerview) 24 | mEdit.subsc("previewShowHeaderFooter", self.updatePerview) 25 | mEdit.subsc("previewShowAnime", self.updatePerview) 26 | mEdit.subsc("previewStart", self.updatePerview) 27 | mEdit.subsc("previewLength", self.updatePerview) 28 | 29 | def updatePerview(self, *args): 30 | mEdit = self.model.editor 31 | prevTxt = self.model.prompt.getPreview( 32 | mEdit.previewStart, 33 | self.model.getPreviewLength(), 34 | mEdit.previewShowKeyframe, 35 | mEdit.previewShowHeaderFooter, 36 | mEdit.previewShowAnime, 37 | ) 38 | 39 | vTxtPrev = self.form.preview.txtPrev 40 | vTxtPrev.configure(state=tk.NORMAL) 41 | vTxtPrev.delete("1.0", tk.END) 42 | vTxtPrev.insert(tk.END, prevTxt) 43 | vTxtPrev.configure(state=tk.DISABLED) 44 | -------------------------------------------------------------------------------- /editor/src/ctr_upscale.py: -------------------------------------------------------------------------------- 1 | from log import Log 2 | import tkinter as tk 3 | 4 | 5 | class UpscaleController: 6 | def __init__(self, form, model): 7 | self.form = form 8 | self.model = model 9 | mGenerate = self.model.generate 10 | vUpscale = self.form.upscale 11 | 12 | mGenerate.bind("upscaleScheduler", vUpscale.varScheduler) 13 | mGenerate.bind("upscaleSteps", vUpscale.varSteps) 14 | mGenerate.bind("upscaleGuidanceScale", vUpscale.varGuidanceScale) 15 | mGenerate.bind("upscaleStrength", vUpscale.varDenoise) 16 | mGenerate.bind("upscaleTileEnable", vUpscale.varTileEnable) 17 | mGenerate.bind("upscaleTileScale", vUpscale.varTileScale) 18 | mGenerate.bind("upscaleTileStart", vUpscale.varTileStart) 19 | mGenerate.bind("upscaleTileEnd", vUpscale.varTileEnd) 20 | mGenerate.bind("upscaleIp2pEnable", vUpscale.varIp2pEnable) 21 | mGenerate.bind("upscaleIp2pScale", vUpscale.varIp2pScale) 22 | mGenerate.bind("upscaleIp2pStart", vUpscale.varIp2pStart) 23 | mGenerate.bind("upscaleIp2pEnd", vUpscale.varIp2pEnd) 24 | mGenerate.bind("upscaleLineAnimeEnable", vUpscale.varLineAnimeEnable) 25 | mGenerate.bind("upscaleLineAnimeScale", vUpscale.varLineAnimeScale) 26 | mGenerate.bind("upscaleLineAnimeStart", vUpscale.varLineAnimeStart) 27 | mGenerate.bind("upscaleLineAnimeEnd", vUpscale.varLineAnimeEnd) 28 | mGenerate.bind("upscaleUseHalfVae", vUpscale.varUseHalfVae) 29 | mGenerate.bind("upscaleUseXFormers", vUpscale.varUseXFormers) 30 | -------------------------------------------------------------------------------- /editor/src/l10n.py: -------------------------------------------------------------------------------- 1 | from config import Config 2 | from l10n_en import l10nEn 3 | from l10n_ja import l10nJa 4 | import locale 5 | 6 | 7 | class L10n: 8 | @classmethod 9 | def get(cls, key): 10 | return cls._l10n[key] 11 | 12 | @classmethod 13 | def format(cls, key, *args): 14 | return cls._l10n[key].format(*args) 15 | 16 | @classmethod 17 | def loadConfig(cls): 18 | lang = Config.get("ui", "lang", fallback=locale.getdefaultlocale()[0][:2]) 19 | cls._l10n = l10nEn 20 | if lang == "ja": 21 | cls._l10n = l10nJa 22 | 23 | @classmethod 24 | def storeConfig(cls): 25 | Config.set("ui", "lang", cls.get("lang")) 26 | -------------------------------------------------------------------------------- /editor/src/l10n_ja.py: -------------------------------------------------------------------------------- 1 | l10nJa = { 2 | "lang": "ja", 3 | "error": "エラー", 4 | "sec": "秒", 5 | "title": "簡単プロンプトアニメ ", 6 | "clear": "クリア", 7 | "top": "上", 8 | "bottom": "下", 9 | "left": "左", 10 | "right": "右", 11 | "negative_prompt": "ネガティブプロンプト", 12 | "swap": "入替", 13 | "generate_anime": "アニメ生成", 14 | "upscale": "アップスケール", 15 | "seed_gacha": "シードガチャ", 16 | "preview": "プレビュー", 17 | "preview_upscale": "アップスケール", 18 | # "preview_interpolation": "補間", 19 | "preview_start_frame": "プレビュー\n開始", 20 | "preview_length": "長さ  \n", 21 | "task_forever": "連続実行", 22 | "task_pause_by_error": "エラーで一時停止", 23 | "preview_show_keyframe": "キーフレームを表示", 24 | "preview_show_header_footer": "ヘッダとフッタを表示", 25 | "preview_show_anime": "アニメを表示", 26 | "generate_length": "フレーム数\n{0:>8}秒", 27 | "generate_seed": "シード", 28 | "generate_model": "モデル", 29 | "generate_vae": "VAE", 30 | "generate_width": "幅", 31 | "generate_height": "高さ", 32 | "generate_motion_module": "モーション\nモジュール", 33 | "generate_context": "コンテキスト", 34 | "generate_scheduler": "スケジューラ", 35 | "generate_steps": "ステップ数", 36 | "generate_guidance_scale": "ガイダンススケール\n(CFG スケール)", 37 | "generate_clip_skip": "クリップ\nスキップ", 38 | "generate_prompt_fixed_ratio": "プロンプト\n固定割合", 39 | "generate_use_lcm": "LCM を使用", 40 | "generate_use_highres_fix": "HiresFix を使用", 41 | "generate_use_half_vae": "Half VAE を使用", 42 | "generate_use_x_formers": "xFormers を使用", 43 | "control_net_dir": "コントロールネット用のフォルダ", 44 | "control_net_loop": "ループを考慮して適用", 45 | "control_net_info": "コントロールネット用のフォルダのサブフォルダに、各コントロールネットで使う png 画像を置きます。\npng 画像のファイル名は 000.png (0フレーム) や 010.png (10フレーム、1秒) のように、コントロールネットを使用するフレーム数を指定します。\nコントロールネット用のフォルダを選択すれば、必要なサブフォルダは自動的に作成します。", 46 | "control_net_import": "MP4 画像取込", 47 | "control_net_import_speed": "再生速度", 48 | "control_net_import_start": "開始(秒)", 49 | "control_net_import_length": "長さ(秒)", 50 | "control_net_import_index": "開始番号", 51 | "control_net_enable": "{0} を使用", 52 | "control_net_open_dir": "{0} の画像フォルダを開く", 53 | "control_net_preprocessor": "前処理を使用", 54 | "control_net_guess_mode": "{0} を重視", 55 | "control_net_scale": "{0} スケール", 56 | "control_net_scale_list": "隣接フレームのスケールリスト", 57 | "control_net_start": "{0} 開始", 58 | "control_net_end": "{0} 終了", 59 | "ip_adapter_use": "IP(イメージプロンプト)アダプタを使用", 60 | "ip_adapter_use_plus": "IPアダプタプラスを使用", 61 | "ip_adapter_use_plus_face": "IPアダプタプラスフェイスを使用", 62 | "ip_adapter_scale": "イメージプロンプト比率\n(1.0でも要プロンプト指定)", 63 | "ip_adapter_image_dir": "IP用の画像フォルダ", 64 | "ip_adapter_image_dir_explorer": "エクスプローラで開く", 65 | "ip_adapter_info": "[エクスプローラで開く] の ip_adapter_image/ にフォルダを作成して、IPアダプタ用の png 画像を置きます。\npng 画像のファイル名は 000.png (0フレーム) や 010.png (10フレーム、1秒) のように、イメージプロンプトとして使用するフレーム数を指定します。", 66 | "upscale1": "初回アップスケール", 67 | "upscale2": "二重アップスケール", 68 | "upscale_scale": "倍率", 69 | "upscale_scheduler": "スケジューラ", 70 | "upscale_steps": "ステップ数", 71 | "upscale_strength": "ノイズ除去強度", 72 | "upscale_guidance_scale": "ガイダンススケール\n(CFG スケール)", 73 | "upscale_tile_enable": "tile 使用", 74 | "upscale_tile_scale": "強度", 75 | "upscale_tile_start": "開始", 76 | "upscale_tile_end": "終了", 77 | "upscale_ip2p_enable": "ip2p 使用", 78 | "upscale_ip2p_scale": "強度", 79 | "upscale_ip2p_start": "開始", 80 | "upscale_ip2p_end": "終了", 81 | "upscale_line_anime_enable": "line_anime 使用", 82 | "upscale_line_anime_scale": "強度", 83 | "upscale_line_anime_start": "開始", 84 | "upscale_line_anime_end": "終了", 85 | "upscale_use_half_vae": "Half VAE を使用(黒画面出力の抑制事例あり)", 86 | "upscale_use_x_formers": "xFormers を使用", 87 | "mosaic_covered_info": "■: 衣服越し部位。", 88 | "mosaic_png_dir": "画像フォルダを\nモザイク", 89 | "mosaic_enabled": "モザイク\n使用", 90 | "mosaic_threshold": "モザイク\nしきい値", 91 | "mosaic_temporal": "時間的\nモザイク", 92 | "mosaic_ellipse": "楕円形\nモザイク", 93 | "mosaic_mask_blur": "マスク\nぼかし", 94 | "mosaic_male": "男性", 95 | "mosaic_male_face": ":-)", 96 | "mosaic_male_brst": "[. ][ .]", 97 | "mosaic_male_gntl": "(U)", 98 | "mosaic_fem": "女性", 99 | "mosaic_fem_face": ";-)", 100 | "mosaic_fem_brst": "(o )( o)", 101 | "mosaic_fem_brst_cov": "(■)(■)", 102 | "mosaic_fem_gntl": "(|)", 103 | "mosaic_fem_gntl_cov": "(■)", 104 | "mosaic_armpit": "脇", 105 | "mosaic_armpit_cov": "■ 脇", 106 | "mosaic_belly": "腹", 107 | "mosaic_belly_cov": "■ 腹", 108 | "mosaic_hip": "尻", 109 | "mosaic_hip_cov": "■ 尻", 110 | "mosaic_ans": ")x(", 111 | "mosaic_ans_cov": ")■(", 112 | "mosaic_feet": "足", 113 | "mosaic_feet_cov": "■ 足", 114 | "mosaic_scale": "モザイク\nスケール", 115 | "mosaic_ignore": "モザイク\n無効範囲", 116 | "preview_tab": "プレビュー", 117 | "basic_tab": "基本", 118 | "generate_tab": "アニメ生成", 119 | "control_net_tab": "コントロールネット", 120 | "ip_adapter_tab": "IPアダプタ", 121 | "upscale_tab": "アップスケール", 122 | "mosaic_tab": "モザイク", 123 | "user_log_tab": "ユーザーログ", 124 | "system_log_tab": "システムログ", 125 | "m_file": "ファイル", 126 | "m_file_new": "新規作成 (Ctrl+N)", 127 | "m_file_open": "開く (Ctrl+O)", 128 | "m_file_save": "上書き保存 (Ctrl+S)", 129 | "m_file_save_as": "名前を付けて保存", 130 | "m_file_exit": "終了 (Alt+F4)", 131 | "m_anime": "アニメ", 132 | "m_anime_preview": "プレビュー", 133 | "m_anime_seed_gacha": "シードガチャ", 134 | "m_anime_generate": "アニメ生成", 135 | "m_anime_upscale": "生成時の設定で初回アップスケール", 136 | "m_anime_upscale_config": "現在の設定で初回アップスケール", 137 | "m_folder": "フォルダ", 138 | "m_output_folder": "アニメ出力先", 139 | "m_prompt_travel_folder": "プロンプトトラベル", 140 | "m_sd_model_folder": "モデル", 141 | "m_motion_module_folder": "モーションモジュール", 142 | "m_lora_folder": "LoRA", 143 | "m_embeddings_folder": "TI (Embedding)", 144 | "m_wildcard_folder": "ワイルドカード", 145 | "m_prompt_travel_output_folder": "プロンプトトラベル出力", 146 | "m_prompt_travel_generate_folder": "アニメ生成", 147 | "m_prompt_travel_tile_folder": "タイルアップスケール", 148 | "m_prompt_travel_refine_folder": "リファインアップスケール", 149 | "m_temp_folder": "テンポラリ", 150 | "m_log_folder": "ログ", 151 | "m_tool": "ツール", 152 | "m_tool_convert_lora": "使えない LoRA を使えるように変換", 153 | "m_tool_easy_leco": "簡単LECO でプロンプトから LoRA を作成", 154 | "m_tool_easy_leco_url": "https://colab.research.google.com/drive/1LYi2kvZV1XN05MATqhqWs79cNdTkb33P", 155 | "m_download": "ダウンロード", 156 | "m_dl_model": "モデル", 157 | "m_dl_lora": "LoRA", 158 | "m_dl_motion_module": "モーションモジュール", 159 | "m_dl_motion_lora": "モーション LoRA", 160 | "m_dl_vae": "VAE", 161 | "m_dl_embedding": "TI (Embedding)", 162 | "m_setting": "設定", 163 | "m_set_default_prompt": "現在のプロンプトをデフォルトプロンプトにする", 164 | "m_set_default_setting": "現在の設定をデフォルト設定にする", 165 | "m_set_change_light_dark": "ライト/ダークモードの切り替え(再起動後に適用)", 166 | "m_set_open_ini_file": "簡単プロンプトアニメの設定 ini ファイルを開く", 167 | "m_set_open_default_prompt_file": "デフォルトのプロンプトファイルを開く", 168 | "m_set_open_prompt_travel_template_file": "プロンプトトラベルのテンプレートファイルを開く", 169 | "m_help": "ヘルプ", 170 | "m_prompt_help": "プロンプトの書き方", 171 | "m_github": "簡単プロンプトアニメ GitHub", 172 | "m_reference": "参照", 173 | "dlg_ask_save": "保存していない変更があります。変更内容を保存しますか?", 174 | "dlg_convert_lora_title": "変換する LoRA を選択してください。", 175 | "dlg_upscale_title": "アップスケールする連番 png の入ったフォルダ(00-シード値~)を選択してください。", 176 | "dlg_control_net_import": "取り込む MP4 ファイルを選択してください。", 177 | "dlg_mosaic_title": "モザイクをかける連番 png の入ったフォルダ(00-シード値~)を選択してください。", 178 | "log_initialization_end": "起動に掛かった時間: {0:.2f}秒", 179 | "log_see_prompt_help": "プロンプトの書き方は [ヘルプ] メニューの [プロンプトの書き方] を参照してください。", 180 | "log_please_wait": "しばらくお待ちください。", 181 | "log_file_new": "ファイルを新規作成しました。", 182 | "log_file_open": "ファイルを開きました。{0}", 183 | "log_file_open_failed": "ファイルを開くのに失敗しました。{0}", 184 | "log_file_save": "ファイルを上書き保存しました。{0}", 185 | "log_file_save_as": "ファイルに名前を付けて保存しました。{0}", 186 | "log_task_start": "{0} を開始しました。", 187 | "log_task_success": "{0} に成功しました。 {1:.2f}秒", 188 | "log_task_failed": "{0} に失敗しました。 {1:.2f}秒\n[エラーで一時停止] を有効にして、エラーの内容を確認してください。", 189 | "log_ffplay": "シード値 {0} を再生します。\n再生設定は EasyPromptAnimeEditor.ini の ffplay_cmd で変更できます。", 190 | "log_upscale": "現在の初回アップスケールの方式と高さの設定で、連番pngをアップスケールします。", 191 | "log_upscale_config_override": "現在の初回アップスケールの全設定(モデルなども含む)で、連番pngをアップスケールします。", 192 | "log_convert_lora": "LoRA を dim:{0} で変換します。\n変換元 LoRA: {1}\n抽出用マージモデル: {2}\n出力先 LoRA: {3}", 193 | "log_start_download": "{0} のダウンロードを開始します。\n{1}", 194 | "err_inv_line": "行の表記が正しくありません。 {0}", 195 | "err_inv_frame_num": "フレームの値が正しくありません。{0}: {1}", 196 | "err_inv_lora_def": 'LoRA の定義が正しくありません。"{0}" {1}', 197 | "err_inv_motion_lora_def": 'モーション LoRA の定義が正しくありません。"{0}" {1}', 198 | "hlp_prompt": """ 199 | # フレーム数+コロン(:)+プロンプトで、アニメのキーフレームにプロンプトを指定します。 200 | # フレーム数+コロンではなく、「H:」「F:」「N:」「L:」の場合はアニメ全体に特別な設定をします。 201 | # シャープ(#)以降はコメントです。 202 | 203 | H: crowds, akihabara 204 | # 「H:」か「h:」で始まる行はヘッダプロンプトの指定で、アニメのキーフレームのプロンプト先頭にヘッダプロンプトを足します。 205 | 206 | 0: standing, upset 207 | 10: waving at viewer, surprised 208 | 20: waving at viewer, smile 209 | # アニメの 0フレームに「standing, upset」、10フレームに「waving at viewer, surprised」、20フレームに「waving at viewer, smile」のプロンプトを使用します。 210 | # 1秒間に 10枚の画像が表示され(10 Frame per second)で、キーフレームの間は補間されます。 211 | 212 | F: 1girl, maid outfit 213 | # 「F:」か「f:」で始まる行はフッタプロンプトの指定で、アニメのキーフレームのプロンプト末尾にフッタプロンプトを足します。 214 | 215 | # 上記の指定で3秒間(30フレーム)アニメ生成した場合の、キーフレームのプロンプトは以下になります。 216 | # 0フレーム「crowds, akihabara, standing, upset, 1girl, maid outfit」 217 | # 10フレーム「crowds, akihabara, waving at viewer, surprised, 1girl, maid outfit」 218 | # 20フレーム「crowds, akihabara, waving at viewer, smile, 1girl, maid outfit」 219 | # 30フレーム「crowds, akihabara, standing, upset, 1girl, maid outfit」 220 | 221 | # 細々とキーフレームを設定しなくとも、0フレームに「dancing」のような動きを表す言葉を入れておけば、AI が勝手に動かしてくれます。 222 | 223 | N: (worst quality, low quality:1.4) 224 | #「N:」か「n:」で始まる行は、アニメの全フレームで使用するネガティブプロンプトを指定します。 225 | 226 | # L: explosion:-0.2 # 行頭の # でコメントになっています。LoRA を使う場合は先頭の # を消してください。 227 | #「L:」か「l:」で始まる行は、LoRA のファイル名+コロン(:)+強度を、カンマ(,)区切りで複数指定できます。 228 | # LoRAを使いたい場合は、メニューの [フォルダ - プロンプトトラベル - LoRA] に *.safetensors 形式の LoRA を置いてください。 229 | # 使える LoRA は通常の LierLa 形式です。C3Lier(Locon) 形式はメニューの [ツール - LoRA 変換] で LierLa 形式に変換して利用できます。 230 | 231 | # M: v2_lora_ZoomIn: 1.0 # LoRA と同様に M でモーション LoRA を指定できます。 232 | """, 233 | } 234 | -------------------------------------------------------------------------------- /editor/src/log.py: -------------------------------------------------------------------------------- 1 | class Log: 2 | userLogEvent = [] 3 | systemLogEvent = [] 4 | 5 | @classmethod 6 | def user(cls, msg): 7 | # msgNl = msg if msg.endswith("\n") else msg + "\n" 8 | # msgNoNewLine = (msg[:-1] if msg.endswith("\n") else msg) 9 | 10 | [hadler(msg) for hadler in cls.userLogEvent] 11 | cls.system(msg) 12 | 13 | @classmethod 14 | def system(cls, msg): 15 | # msgNl = msg if msg.endswith("\n") else msg + "\n" 16 | # msgNoNewLine = (msg[:-1] if msg.endswith("\n") else msg) 17 | [hadler(msg) for hadler in cls.systemLogEvent] 18 | print(msg) 19 | 20 | # @classmethod 21 | # def _system_impl(cls, msg): 22 | # [hadler(msg) for hadler in cls.systemLogEvent] 23 | # print(msg) 24 | -------------------------------------------------------------------------------- /editor/src/mdl_editor.py: -------------------------------------------------------------------------------- 1 | from config import Config 2 | from mdl_notifier import Notifier 3 | 4 | 5 | class Editor(Notifier): 6 | defPreviewUpscale = True 7 | # defPreviewInterpolation = True 8 | defPreviewStart = 0 9 | defPreviewLength = 0 10 | 11 | defPreviewShowKeyframe = True 12 | defPreviewShowHeaderFooter = True 13 | defPreviewShowAnime = True 14 | 15 | defTaskForever = False 16 | defTaskPauseByError = True 17 | 18 | defImportSpeed = "1" 19 | defImportStart = "" 20 | defImportLength = "" 21 | defImportIndex = "0" 22 | 23 | defFfplayCmd = "ffplay.exe -left 160 -top 80 -loop 5 -autoexit -loglevel error" 24 | 25 | def __init__(self): 26 | super().__init__() 27 | self.reset() 28 | 29 | def reset(self): 30 | self.savePath = "" 31 | 32 | self.previewUpscale = Editor.defPreviewUpscale 33 | # self.previewInterpolation = Editor.defPreviewInterpolation 34 | self.previewStart = Editor.defPreviewStart 35 | self.previewLength = Editor.defPreviewLength 36 | 37 | self.previewShowKeyframe = Editor.defPreviewShowKeyframe 38 | self.previewShowHeaderFooter = Editor.defPreviewShowHeaderFooter 39 | self.previewShowAnime = Editor.defPreviewShowAnime 40 | 41 | self.taskForever = Editor.defTaskForever 42 | self.taskPauseByError = Editor.defTaskPauseByError 43 | 44 | self.importSpeed = Editor.defImportSpeed 45 | self.importStart = Editor.defImportStart 46 | self.importLength = Editor.defImportLength 47 | self.importIndex = Editor.defImportIndex 48 | 49 | self.ffplayCmd = Editor.defFfplayCmd 50 | 51 | self.isChanged = True 52 | 53 | def set(self, memberName, value): 54 | result = super().set(memberName, value) 55 | if result: 56 | self.isChanged = True 57 | return result 58 | 59 | @classmethod 60 | def loadConfig(cls): 61 | Editor.defPreviewUpscale = Config.getBool( 62 | "ui_preview", "upscale", Editor.defPreviewUpscale 63 | ) 64 | # Editor.defPreviewInterpolation = Config.getBool( 65 | # "ui_preview", "interpolation", Editor.defPreviewInterpolation 66 | # ) 67 | Editor.defPreviewStart = Config.getInt( 68 | "ui_preview", "start", Editor.defPreviewStart 69 | ) 70 | Editor.defPreviewLength = Config.getInt( 71 | "ui_preview", "length", Editor.defPreviewLength 72 | ) 73 | 74 | Editor.defPreviewShowKeyframe = Config.getBool( 75 | "ui_preview", "show_keyframe", Editor.defPreviewShowKeyframe 76 | ) 77 | Editor.defPreviewShowHeaderFooter = Config.getBool( 78 | "ui_preview", "show_header_footer", Editor.defPreviewShowHeaderFooter 79 | ) 80 | Editor.defPreviewShowAnime = Config.getBool( 81 | "ui_preview", "show_anime", Editor.defPreviewShowAnime 82 | ) 83 | 84 | Editor.defTaskForever = Config.getBool("ui", "forever", Editor.defTaskForever) 85 | Editor.defTaskPauseByError = Config.getBool( 86 | "ui", "pause_by_error", Editor.defTaskPauseByError 87 | ) 88 | 89 | Editor.defImportSpeed = Config.get("ui", "import_speed", Editor.defImportSpeed) 90 | Editor.defImportStart = Config.get("ui", "import_start", Editor.defImportStart) 91 | Editor.defImportLength = Config.get( 92 | "ui", "import_length", Editor.defImportLength 93 | ) 94 | Editor.defImportIndex = Config.get("ui", "import_index", Editor.defImportIndex) 95 | 96 | Editor.defFfplayCmd = Config.get("ui", "ffplay_cmd", Editor.defFfplayCmd) 97 | 98 | def updateConfig(self): 99 | Editor.defPreviewUpscale = self.previewUpscale 100 | # Editor.defPreviewInterpolation = self.previewInterpolation 101 | Editor.defPreviewStart = self.previewStart 102 | Editor.defPreviewLength = self.previewLength 103 | 104 | Editor.defPreviewShowKeyframe = self.previewShowKeyframe 105 | Editor.defPreviewShowHeaderFooter = self.previewShowHeaderFooter 106 | Editor.defPreviewShowAnime = self.previewShowAnime 107 | 108 | Editor.defTaskForever = self.taskForever 109 | Editor.defTaskPauseByError = self.taskPauseByError 110 | 111 | Editor.defImportSpeed = self.importSpeed 112 | Editor.defImportStart = self.importStart 113 | Editor.defImportLength = self.importLength 114 | Editor.defImportIndex = self.importIndex 115 | 116 | # Editor.defFfplayCmd = self.ffplayCmd 117 | 118 | def storeConfig(self): 119 | Config.set("ui_preview", "upscale", Editor.defPreviewUpscale) 120 | # Config.set("ui_preview", "interpolation", Editor.defPreviewInterpolation) 121 | Config.set("ui_preview", "start", Editor.defPreviewStart) 122 | Config.set("ui_preview", "length", Editor.defPreviewLength) 123 | 124 | Config.set("ui_preview", "show_keyframe", Editor.defPreviewShowKeyframe) 125 | Config.set( 126 | "ui_preview", "show_header_footer", Editor.defPreviewShowHeaderFooter 127 | ) 128 | Config.set("ui_preview", "show_anime", Editor.defPreviewShowAnime) 129 | 130 | Config.set("ui", "forever", Editor.defTaskForever) 131 | Config.set("ui", "pause_by_error", Editor.defTaskPauseByError) 132 | 133 | Config.set("ui", "import_speed", Editor.defImportSpeed) 134 | Config.set("ui", "import_start", Editor.defImportStart) 135 | Config.set("ui", "import_length", Editor.defImportLength) 136 | Config.set("ui", "import_index", Editor.defImportIndex) 137 | 138 | Config.set("ui", "ffplay_cmd", Editor.defFfplayCmd) 139 | -------------------------------------------------------------------------------- /editor/src/mdl_notifier.py: -------------------------------------------------------------------------------- 1 | class Notifier: 2 | def __init__(self): 3 | self._subscribers = {} 4 | pass 5 | 6 | def subsc(self, memberName, func): 7 | if self._subscribers.get(memberName) is None: 8 | self._subscribers[memberName] = [] 9 | self._subscribers[memberName].append(func) 10 | 11 | def unsubsc(self, memberName, func): 12 | if self._subscribers.get(memberName) is None: 13 | return 14 | self._subscribers[memberName].remove(func) 15 | if len(self._subscribers[memberName]) == 0: 16 | del self._subscribers[memberName] 17 | 18 | def notify(self, memberName, value): 19 | if self._subscribers.get(memberName) is None: 20 | return 21 | for func in self._subscribers[memberName]: 22 | func(self, memberName, value) 23 | 24 | def notifyAll(self): 25 | for memberName in self._subscribers.keys(): 26 | self.notify(memberName, getattr(self, memberName)) 27 | 28 | def set(self, memberName, value): 29 | if getattr(self, memberName) == value: 30 | return False 31 | setattr(self, memberName, value) 32 | self.notify(memberName, value) 33 | return True 34 | 35 | def trace_add(self, memberName, var): 36 | return var.trace_add("write", lambda *args: self.set(memberName, var.get())) 37 | 38 | def trace_remove(self, var, tid): 39 | var.trace_remove("write", tid) 40 | 41 | def bind(self, memberName, var): 42 | self.subsc(memberName, lambda *args: var.set(getattr(self, memberName))) 43 | self.trace_add(memberName, var) 44 | -------------------------------------------------------------------------------- /editor/src/mdl_prompt.py: -------------------------------------------------------------------------------- 1 | from const import Path 2 | from mdl_notifier import Notifier 3 | import os 4 | from prompt_perser import PromptPerser 5 | 6 | 7 | class Prompt(Notifier): 8 | defaultPrompt = """H: crowds, akihabara 9 | 0: standing, upset 10 | 10: waving at viewer, surprised 11 | 20: waving at viewer, smile 12 | F: 1girl, maid outfit 13 | N:(worst quality, low quality:1.2) 14 | """ 15 | 16 | def __init__(self): 17 | super().__init__() 18 | self.text = "" 19 | self.data = None 20 | # self.data = { 21 | # "header": "", 22 | # "footer": "", 23 | # "negative": "", 24 | # "lora_map": {}, 25 | # "prompt_map": {}, 26 | # "errors": [], 27 | # } 28 | self.subsc("text", self.updatePrompts) 29 | self.reset() 30 | 31 | def updatePrompts(self, *args): 32 | self.data = PromptPerser.persePrompt(self.text) 33 | self.isChanged = True 34 | 35 | def reset(self): 36 | self.loadDefaultPrompt() 37 | 38 | def getPreview(self, stert, length, showKeyframe, showHeaderFooter, showAnime): 39 | return PromptPerser.persePreview( 40 | self.data, stert, length, showKeyframe, showHeaderFooter, showAnime 41 | ) 42 | 43 | def loadDefaultPrompt(self): 44 | if os.path.exists(Path.defaultPrompt): 45 | with open(Path.defaultPrompt, "r", encoding="utf-8-sig") as f: 46 | self.set("text", f.read()) 47 | else: 48 | with open(Path.defaultPrompt, "w", encoding="utf-8-sig") as f: 49 | f.write(Prompt.defaultPrompt) 50 | self.set("text", Prompt.defaultPrompt) 51 | 52 | def saveDefaultPrompt(self): 53 | Prompt.defaultPrompt = self.text 54 | with open(Path.defaultPrompt, "w", encoding="utf-8-sig") as f: 55 | f.write(Prompt.defaultPrompt.strip() + "\n") 56 | 57 | def getLastPrompt(self): 58 | frames = sorted(self.data["prompt_map"].keys()) 59 | length = len(frames) 60 | if length == 0: 61 | return (0, "") 62 | maxFrame = frames[length - 1] 63 | return (maxFrame, self.data["prompt_map"][maxFrame]) 64 | -------------------------------------------------------------------------------- /editor/src/model.py: -------------------------------------------------------------------------------- 1 | from mdl_prompt import Prompt 2 | from mdl_generate import Generate 3 | from mdl_editor import Editor 4 | 5 | 6 | class Model: 7 | def __init__(self): 8 | self.prompt = Prompt() 9 | self.generate = Generate() 10 | self.editor = Editor() 11 | 12 | def reset(self): 13 | self.prompt.reset() 14 | self.generate.reset() 15 | self.editor.reset() 16 | 17 | def notifyAll(self): 18 | self.prompt.notifyAll() 19 | self.generate.notifyAll() 20 | self.editor.notifyAll() 21 | 22 | @classmethod 23 | def loadConfig(cls): 24 | Generate.loadConfig() 25 | Editor.loadConfig() 26 | 27 | def updateConfig(self): 28 | self.generate.updateConfig() 29 | self.editor.updateConfig() 30 | 31 | def storeConfig(self): 32 | self.generate.storeConfig() 33 | self.editor.storeConfig() 34 | 35 | def getPreviewLength(self): 36 | if self.editor.previewLength == 0: 37 | return self.generate.length - self.editor.previewStart 38 | else: 39 | return self.editor.previewLength 40 | 41 | def getPreviewEnd(self): 42 | if self.editor.previewLength == 0: 43 | return self.generate.length 44 | else: 45 | return self.editor.previewStart + self.editor.previewLength 46 | -------------------------------------------------------------------------------- /editor/src/mosaic.py: -------------------------------------------------------------------------------- 1 | import os, glob, cv2 2 | import numpy as np 3 | from nudenet import NudeDetector 4 | from l10n import L10n 5 | from collections import OrderedDict 6 | from log import Log 7 | 8 | 9 | class Mosaic: 10 | partsTable = { 11 | "FACE_FEM" + "ALE": "mosaicFemFace", 12 | "FEM" + "ALE_BRE" + "AST_EXPOSED": "mosaicFemBrst", 13 | "FEM" + "ALE_BRE" + "AST_COVERED": "mosaicFemBrstCov", 14 | "FEM" + "ALE_GENI" + "TALIA_EXPOSED": "mosaicFemGntl", 15 | "FEM" + "ALE_GENI" + "TALIA_COVERED": "mosaicFemGntlCov", 16 | "FACE_MALE": "mosaicMaleFace", 17 | "MALE_BRE" + "AST_EXPOSED": "mosaicMaleBrst", 18 | "MALE_GENI" + "TALIA_EXPOSED": "mosaicMaleGntl", 19 | "ARMPITS_EXPOSED": "mosaicArmpit", 20 | "ARMPITS_COVERED": "mosaicArmpitCov", 21 | "BELLY_EXPOSED": "mosaicBelly", 22 | "BELLY_COVERED": "mosaicBellyCov", 23 | "BUT" + "TOCKS_EXPOSED": "mosaicHip", 24 | "BUT" + "TOCKS_COVERED": "mosaicHipCov", 25 | "AN" + "US_EXPOSED": "mosaicAns", 26 | "AN" + "US_COVERED": "mosaicAnsCov", 27 | "FEET_EXPOSED": "mosaicFeet", 28 | "FEET_COVERED": "mosaicFeetCov", 29 | } 30 | 31 | @classmethod 32 | def mosaicPngDir(cls, generate, pngDir): 33 | pngPaths = sorted(glob.glob(os.path.join(pngDir, "*.png"))) 34 | if len(pngPaths) == 0: 35 | return "" 36 | 37 | mosaics = cls.detectMosaic(generate, pngPaths) 38 | 39 | if generate.temporalMosaic: 40 | mosaics = cls.temporalMosaic(mosaics) 41 | 42 | dstDirName = os.path.basename(pngDir) + "_mosaic" 43 | dstDir = os.path.join(os.path.dirname(pngDir), dstDirName) 44 | os.makedirs(dstDir, exist_ok=True) 45 | for mosaic in mosaics: 46 | cls.mosaic(generate, mosaic, dstDir) 47 | 48 | return dstDir 49 | 50 | @classmethod 51 | def detectMosaic(cls, generate, pngPaths): 52 | nude_detector = NudeDetector() 53 | results = [] 54 | for pngPath in pngPaths: 55 | detections = [] 56 | for detection in nude_detector.detect(pngPath): 57 | if detection["score"] < generate.mosaicThreshold: 58 | continue 59 | if detection["class"] not in cls.partsTable: 60 | Log.system(f'Unknown detection class: {detection["class"]}.') 61 | continue 62 | if not getattr(generate, cls.partsTable[detection["class"]]): 63 | continue 64 | detections.append(detection) 65 | results.append({"path": pngPath, "detections": detections}) 66 | return results 67 | 68 | @classmethod 69 | def temporalMosaic(cls, mosaics): 70 | temporalMosaics = [] 71 | mosaicsNum = len(mosaics) 72 | for i in range(mosaicsNum): 73 | temporalMosaic = { 74 | "path": mosaics[i]["path"], 75 | "detections": [], 76 | } 77 | 78 | for detection in mosaics[i]["detections"]: 79 | temporalMosaic["detections"].append(detection) 80 | counter = 1 81 | while True: 82 | if len(temporalMosaic["detections"]) >= 1: # TODO: 83 | break 84 | if i >= counter: 85 | for detection in mosaics[i - counter]["detections"]: 86 | temporalMosaic["detections"].append(detection) 87 | if i < mosaicsNum - counter: 88 | for detection in mosaics[i + counter]["detections"]: 89 | temporalMosaic["detections"].append(detection) 90 | counter += 1 91 | if counter >= mosaicsNum: 92 | break 93 | temporalMosaics.append(temporalMosaic) 94 | return temporalMosaics 95 | 96 | @classmethod 97 | def mosaic(cls, generate, mosaic, dstDir): 98 | srcImg = cv2.imread(mosaic["path"]) 99 | srcH, srcW = srcImg.shape[:2] 100 | mosaicScale = 100 / srcW 101 | maskBlur = int(generate.mosaicMaskBlur * srcW) 102 | if srcH > srcW: 103 | mosaicScale = 100 / srcH 104 | maskBlur = int(generate.mosaicMaskBlur * srcH) 105 | 106 | mosaicImg = cv2.resize(srcImg, None, fx=mosaicScale, fy=mosaicScale) 107 | mosaicImg = cv2.resize(mosaicImg, (srcW, srcH), interpolation=cv2.INTER_NEAREST) 108 | 109 | mosaicMask = np.zeros((srcH, srcW), dtype=np.uint8) 110 | 111 | for detection in mosaic["detections"]: 112 | box = detection["box"] 113 | cx = box[0] + (box[2] * 0.5) 114 | cy = box[1] + (box[3] * 0.5) 115 | hw = box[2] * 0.5 116 | hh = box[3] * 0.5 117 | st = hh * generate.mosaicScaleTop 118 | sb = hh * generate.mosaicScaleBottom 119 | sl = hw * generate.mosaicScaleLeft 120 | sr = hw * generate.mosaicScaleRight 121 | if generate.ellipseMosaic: 122 | cx = int((sr - sl) * 0.5 + cx) 123 | cy = int((sb - st) * 0.5 + cy) 124 | rx = int((sl + sr) / 2) 125 | ry = int((st + sb) / 2) 126 | cv2.ellipse(mosaicMask, ((cx, cy), (rx, ry), 0), 255, -1) 127 | else: 128 | cv2.rectangle( 129 | mosaicMask, 130 | (int(cx - sl), int(cy - st)), 131 | (int(cx + sr), int(cy + sb)), 132 | 255, 133 | -1, 134 | ) 135 | 136 | if generate.mosaicIgnoreTop > 0: 137 | top = int(srcH * generate.mosaicIgnoreTop) 138 | cv2.rectangle(mosaicMask, (0, 0), (srcW, top), 0, -1) 139 | if generate.mosaicIgnoreBottom > 0: 140 | bottom = int(srcH * (1 - generate.mosaicIgnoreBottom)) 141 | cv2.rectangle(mosaicMask, (0, bottom), (srcW, srcH), 0, -1) 142 | if generate.mosaicIgnoreLeft > 0: 143 | left = int(srcW * generate.mosaicIgnoreLeft) 144 | cv2.rectangle(mosaicMask, (0, 0), (left, srcH), 0, -1) 145 | if generate.mosaicIgnoreRight > 0: 146 | right = int(srcW * (1 - generate.mosaicIgnoreRight)) 147 | cv2.rectangle(mosaicMask, (right, 0), (srcW, srcH), 0, -1) 148 | 149 | if maskBlur > 0: 150 | maskBlur = maskBlur if (maskBlur % 2) == 1 else maskBlur + 1 151 | mosaicMask = cv2.blur(mosaicMask, (maskBlur, maskBlur)) 152 | 153 | srcImg = np.array(srcImg) 154 | mosaicImg = np.array(mosaicImg) 155 | mosaicAlpha = np.dstack((mosaicMask,) * 3) / 255 156 | blendImg = srcImg * (1 - mosaicAlpha) + mosaicImg * mosaicAlpha 157 | 158 | dstPath = os.path.join(dstDir, os.path.basename(mosaic["path"])) 159 | cv2.imwrite(dstPath, blendImg.astype(np.uint8)) 160 | # cv2.imwrite(dstPath, mosaicMask) 161 | -------------------------------------------------------------------------------- /editor/src/prompt_perser.py: -------------------------------------------------------------------------------- 1 | from l10n import L10n 2 | from collections import OrderedDict 3 | 4 | 5 | class PromptPerser: 6 | @classmethod 7 | def persePrompt(cls, txt): 8 | data = { 9 | "header": "", 10 | "footer": "", 11 | "negative": "", 12 | "lora_map": {}, 13 | "motion_lora_map": {}, 14 | "prompt_map": {}, 15 | "errors": [], 16 | } 17 | 18 | for line in txt.splitlines(): 19 | try: 20 | cls.perseLine(data, line) 21 | except Exception as e: 22 | data["errors"].append(str(e)) 23 | 24 | # TODO: apply range prompt 25 | return data 26 | 27 | @classmethod 28 | def perseLine(cls, data, line): 29 | commentRemoved = line.split("#", 1)[0].strip() 30 | if commentRemoved == "": 31 | return 32 | commentRemovedKvp = commentRemoved.split(":", 1) 33 | stripedKey = commentRemovedKvp[0].strip() 34 | if len(commentRemovedKvp) < 2: 35 | cls.addPrompt(data, 0, stripedKey) # 0 frame prompt 36 | return 37 | val = commentRemovedKvp[1].strip() 38 | if stripedKey == "": 39 | cls.addPrompt(data, 0, val) # 0 frame prompt 40 | return 41 | key = stripedKey.lower() 42 | if key == "h": 43 | if data["header"] != "": 44 | data["header"] += ", " 45 | data["header"] += val 46 | elif key == "f": 47 | if data["footer"] != "": 48 | data["footer"] += ", " 49 | data["footer"] += val 50 | elif key == "n": 51 | if data["negative"] != "": 52 | data["negative"] += ", " 53 | data["negative"] += val 54 | elif key == "l": 55 | for lora_def in val.split(","): 56 | lora_kvp = lora_def.split(":", 1) 57 | if len(lora_kvp) < 2: 58 | data["errors"].append( 59 | L10n.format("err_inv_lora_def", lora_def, val) 60 | ) 61 | return 62 | lora_key = lora_kvp[0].strip() 63 | lora_weight = lora_kvp[1].strip() 64 | try: 65 | lora_weight = float(lora_weight) 66 | data["lora_map"][lora_key] = lora_weight 67 | except ValueError: 68 | data["errors"].append( 69 | L10n.format("err_inv_lora_def", lora_def, val) 70 | ) 71 | return 72 | elif key == "m": 73 | for motion_lora_def in val.split(","): 74 | motion_lora_kvp = motion_lora_def.split(":", 1) 75 | if len(motion_lora_kvp) < 2: 76 | data["errors"].append( 77 | L10n.format("err_inv_motion_lora_def", motion_lora_def, val) 78 | ) 79 | return 80 | motion_lora_key = motion_lora_kvp[0].strip() 81 | motion_lora_weight = motion_lora_kvp[1].strip() 82 | try: 83 | motion_lora_weight = float(motion_lora_weight) 84 | data["motion_lora_map"][motion_lora_key] = motion_lora_weight 85 | except ValueError: 86 | data["errors"].append( 87 | L10n.format("err_inv_motion_lora_def", motion_lora_def, val) 88 | ) 89 | return 90 | elif key.isdigit(): 91 | cls.addPrompt(data, int(key), val) 92 | else: 93 | # TODO: parse range prompt 94 | data["errors"].append(L10n.format("err_inv_line", line)) 95 | 96 | @classmethod 97 | def addPrompt(self, data, frame, prompt): 98 | if not isinstance(frame, int) or frame < 0: 99 | data["errors"].append(L10n.format("err_inv_frame_num", frame, prompt)) 100 | return 101 | 102 | promptMap = data["prompt_map"] 103 | if frame in promptMap: 104 | promptMap[frame] = f"{promptMap[frame]}, {prompt}" 105 | else: 106 | promptMap[frame] = prompt 107 | 108 | @classmethod 109 | def persePreview( 110 | cls, data, start, length, showKeyframe, showHeaderFooter, showAnime 111 | ): 112 | preview = [] 113 | header = data["header"] 114 | header = (header + ", ") if header != "" else header 115 | 116 | footer = data["footer"] 117 | footer = (", " + footer) if footer != "" else footer 118 | 119 | end = start + length 120 | firstPrompt = "" 121 | preFrame = -1 122 | prePrompt = "" 123 | for frame in range(start, end + 1): 124 | if frame in data["prompt_map"]: 125 | prompt = data["prompt_map"][frame] 126 | if firstPrompt == "": 127 | firstPrompt = prompt 128 | else: 129 | if showAnime: 130 | preview.append( 131 | f"{preFrame:>3}-{frame:>2}: ({cls.diffPrompt(prePrompt, prompt)}: 0.5)" 132 | ) 133 | if showKeyframe: 134 | if showHeaderFooter: 135 | preview.append(f"{frame:>3}: {header}{prompt}{footer}") 136 | else: 137 | preview.append(f"{frame:>3}: {prompt}") 138 | preFrame = frame 139 | prePrompt = prompt 140 | if firstPrompt != "" and showAnime: 141 | preview.append( 142 | f"{preFrame:>3}-{start:>2}: ({cls.diffPrompt(prePrompt, firstPrompt)}: 0.5)" 143 | ) 144 | 145 | negative = data["negative"] 146 | if negative != "": 147 | preview.append(f"{L10n.get('negative_prompt')}: {negative}") 148 | 149 | loras = data["lora_map"].keys() 150 | if len(loras) > 0: 151 | loraDefs = "LoRA: " 152 | for lora in loras: 153 | loraDefs += f" " 154 | preview.append(loraDefs) 155 | 156 | motion_loras = data["motion_lora_map"].keys() 157 | if len(motion_loras) > 0: 158 | motion_loraDefs = "Motion LoRA: " 159 | for motion_lora in motion_loras: 160 | motion_loraDefs += f" " 161 | preview.append(motion_loraDefs) 162 | 163 | for err in data["errors"]: 164 | preview.append(f"{L10n.get('error')}: {err}") 165 | 166 | return "\n".join(preview) 167 | 168 | @classmethod 169 | def diffPrompt(cls, prePrompt, postPrompt): 170 | preSet = set(map(str.strip, prePrompt.split(","))) 171 | postSet = set(map(str.strip, postPrompt.split(","))) 172 | uniqueSet = preSet.symmetric_difference(postSet) 173 | return ", ".join(uniqueSet) # + f" | {prePrompt} | {postPrompt}" 174 | -------------------------------------------------------------------------------- /editor/src/task.py: -------------------------------------------------------------------------------- 1 | import time 2 | from l10n import L10n 3 | from log import Log 4 | from const import Path 5 | 6 | 7 | class Task: 8 | win = None 9 | queue = [] 10 | queueInfoChangedEvent = [] 11 | pollTime = 100 12 | 13 | @classmethod 14 | def initialize(cls, win): 15 | Task.win = win 16 | 17 | @classmethod 18 | def queueInfoChanged(cls): 19 | [handler(cls.queue) for handler in cls.queueInfoChangedEvent] 20 | 21 | @classmethod 22 | def enqueue(cls, task): 23 | task.resetState() 24 | cls.queue.append(task) 25 | cls.queueInfoChanged() 26 | if len(cls.queue) == 1: 27 | cls.win.after(cls.pollTime, cls.runQueue) 28 | 29 | @classmethod 30 | def dequeue(cls): 31 | task = cls.queue[0] 32 | cls.queue.remove(task) 33 | cls.queueInfoChanged() 34 | return task 35 | 36 | @classmethod 37 | def runQueue(cls): 38 | if len(cls.queue) == 0: 39 | return 40 | 41 | if cls.queue[0].isComleted: 42 | endTask = cls.dequeue() 43 | endTask.time = time.perf_counter() - endTask.startTime 44 | endTask.logEnd() 45 | 46 | if endTask.reRun(): 47 | cls.enqueue(endTask) 48 | if len(cls.queue) == 0: 49 | return 50 | 51 | startNow = not cls.queue[0].isRunning 52 | if startNow: 53 | cls.queue[0].startTime = time.perf_counter() 54 | cls.queue[0].timeStr = Path.getYYYYMMDDHHMMSS() 55 | cls.queue[0].isRunning = True 56 | cls.queue[0].logStart() 57 | 58 | if cls.queue[0].run() or startNow: 59 | cls.queueInfoChanged() 60 | 61 | if len(cls.queue) > 0: 62 | cls.win.after(cls.pollTime, cls.runQueue) 63 | 64 | def __init__(self): 65 | self.resetState() 66 | 67 | def resetState(self): 68 | self.startTime = 0.0 69 | self.time = 0.0 70 | self.timeStr = "" 71 | self.isRunning = False 72 | self.isComleted = False 73 | self.isFailed = False 74 | 75 | def run(self): 76 | return False 77 | 78 | def reRun(self): 79 | return False 80 | 81 | def logStart(self): 82 | Log.user(L10n.format("log_task_start", self)) 83 | 84 | def logEnd(self): 85 | if self.isFailed: 86 | Log.user(L10n.format("log_task_failed", self, self.time)) 87 | else: 88 | Log.user(L10n.format("log_task_success", self, self.time)) 89 | 90 | def __str__(self): 91 | if self.isComleted: 92 | if self.isFailed: 93 | return "[F]" 94 | else: 95 | return "[S]" 96 | else: 97 | if self.isRunning: 98 | return "[R]" 99 | else: 100 | return "[_]" 101 | -------------------------------------------------------------------------------- /editor/src/tsk_command_prompt.py: -------------------------------------------------------------------------------- 1 | import os, subprocess, threading 2 | from const import Path 3 | from l10n import L10n 4 | from task import Task 5 | 6 | 7 | class CommandPromptTask(Task): 8 | def __init__(self, editor, taskMode): 9 | super().__init__() 10 | self.editor = editor 11 | self.taskMode = taskMode 12 | self.taskName = L10n.get(taskMode) 13 | 14 | def resetState(self): 15 | super().resetState() 16 | self.tempDir = "" 17 | self.errorPath = "" 18 | 19 | self.command = None 20 | self.process = None 21 | self.log = "" 22 | self.thread = None 23 | 24 | def run(self): 25 | if self.thread is None: 26 | self.startThread() 27 | return self.pollThread() 28 | 29 | def startThread(self): 30 | self.tempDir = os.path.join(Path.temp, self.timeStr) 31 | os.path.exists(self.tempDir) or os.makedirs(self.tempDir) 32 | 33 | self.errorPath = os.path.join(self.tempDir, f"{self.timeStr}-Error.txt") 34 | 35 | cmd = self.createCommand() 36 | if self.editor.taskPauseByError: 37 | cmd += f" || (echo %errorlevel% > {self.errorPath} & pause)" 38 | else: 39 | cmd += f" || (echo %errorlevel% > {self.errorPath})" 40 | self.command = ["start", "/wait", "cmd", "/c", cmd] 41 | 42 | self.log = "" 43 | self.process = subprocess.Popen( 44 | self.command, 45 | stdout=subprocess.PIPE, 46 | stderr=subprocess.STDOUT, 47 | shell=True, 48 | encoding="utf-8", 49 | ) 50 | self.thread = threading.Thread(target=self.threadMain) 51 | self.thread.start() 52 | 53 | def createCommand(self): 54 | return "" 55 | 56 | def threadMain(self): 57 | self.log = self.process.communicate()[0] 58 | 59 | def pollThread(self): 60 | if self.thread.is_alive(): 61 | return False 62 | 63 | self.isComleted = True 64 | self.isFailed = ( 65 | (self.process.returncode != 0) 66 | or (os.path.exists(self.errorPath)) 67 | or (self.log == "^C") 68 | ) 69 | 70 | self.thread = None 71 | self.process = None 72 | self.isFailed or self.postProcess() 73 | # Log.system(self.log) # "^C" or "" 74 | return True 75 | 76 | def postProcess(self): 77 | pass 78 | 79 | def reRun(self): 80 | if self.isFailed: 81 | return False 82 | return self.editor.taskForever 83 | 84 | def __str__(self): 85 | return f"{super().__str__()} {self.taskName} {self.timeStr}" 86 | -------------------------------------------------------------------------------- /editor/src/tsk_generate.py: -------------------------------------------------------------------------------- 1 | import os, copy, shutil, subprocess, json 2 | from const import Path 3 | from l10n import L10n 4 | from log import Log 5 | from tsk_command_prompt import CommandPromptTask 6 | from prompt_travel import PromptTravel 7 | from serializer import Serializer 8 | 9 | 10 | class GenerateTask(CommandPromptTask): 11 | @classmethod 12 | def generate(cls, model): 13 | gen = model.generate 14 | config = PromptTravel.getConfig( 15 | gen, gen.model, gen.upscaleStrength, model.prompt.data, 10 16 | ) 17 | configSuffix = PromptTravel.GetConfigFileSuffix( 18 | gen.length, 19 | gen.context, 20 | gen.width, 21 | gen.height, 22 | gen.useHalfVae, 23 | gen.useXFormers, 24 | gen.upscaleUseHalfVae, 25 | gen.upscaleUseXFormers, 26 | gen.upscale1Enabled, 27 | gen.upscale1Mode, 28 | gen.getUpscale1Height(), 29 | gen.upscale2Enabled, 30 | gen.upscale2Mode, 31 | gen.getUpscale2Height(), 32 | ) 33 | configSuffix = configSuffix + "-D3" 34 | data = Serializer.serialize(model) 35 | task = GenerateTask(model.editor, "generate_anime", config, configSuffix, data) 36 | cls.enqueue(task) 37 | 38 | @classmethod 39 | def seedGacha(cls, model): 40 | gen = model.generate 41 | config = PromptTravel.getConfig( 42 | gen, gen.model, gen.upscaleStrength, model.prompt.data, 10 43 | ) 44 | configSuffix = PromptTravel.GetConfigFileSuffix( 45 | gen.length, 46 | gen.context, 47 | gen.width, 48 | gen.height, 49 | gen.useHalfVae, 50 | gen.useXFormers, 51 | gen.upscaleUseHalfVae, 52 | gen.upscaleUseXFormers, 53 | gen.upscale1Enabled, 54 | gen.upscale1Mode, 55 | (int)(gen.height * 1.5), 56 | ) 57 | data = Serializer.serialize(model) 58 | task = GenerateTask(model.editor, "seed_gacha", config, configSuffix, data) 59 | cls.enqueue(task) 60 | 61 | @classmethod 62 | def preview(cls, model): 63 | halfFps = False # Not good 64 | 65 | edt = model.editor 66 | start = edt.previewStart 67 | length = model.getPreviewLength() 68 | end = model.getPreviewEnd() 69 | promptData = copy.deepcopy(model.prompt.data) 70 | promptMap = promptData["prompt_map"] 71 | 72 | removeKeys = [] 73 | for keyFrame in promptMap.keys(): 74 | if keyFrame < start or keyFrame > end: 75 | removeKeys.append(keyFrame) 76 | for keyFrame in removeKeys: 77 | del promptMap[keyFrame] 78 | 79 | for keyFrame in sorted(promptMap.keys()): 80 | prompt = promptMap.pop(keyFrame) 81 | frame = keyFrame - start 82 | frame = (int)(frame * 0.5) if halfFps else frame 83 | promptMap[frame] = prompt 84 | 85 | gen = model.generate 86 | config = PromptTravel.getConfig( 87 | gen, gen.model, gen.upscaleStrength, promptData, 5 if halfFps else 10 88 | ) 89 | configSuffix = PromptTravel.GetConfigFileSuffix( 90 | (int)(length * 0.5) if halfFps else length, 91 | (int)(gen.context * 0.5) 92 | if halfFps 93 | else gen.context, # halfFps & gen.context Not good 94 | gen.width, 95 | gen.height, 96 | gen.useHalfVae, 97 | gen.useXFormers, 98 | gen.upscaleUseHalfVae, 99 | gen.upscaleUseXFormers, 100 | gen.upscale1Enabled and edt.previewUpscale, 101 | gen.upscale1Mode, 102 | (int)(gen.height * 1.5), 103 | ) 104 | configSuffix = (configSuffix + "-U5") if halfFps else configSuffix 105 | data = Serializer.serialize(model) 106 | task = GenerateTask(model.editor, "preview", config, configSuffix, data) 107 | cls.enqueue(task) 108 | 109 | # コンストラクタ 110 | def __init__(self, editor, taskMode, config, configSuffix, data): 111 | super().__init__(editor, taskMode) 112 | self.config = config 113 | self.configSuffix = configSuffix 114 | self.data = data 115 | 116 | def resetState(self): 117 | super().resetState() 118 | self.configName = "" 119 | self.configPath = "" 120 | 121 | def createCommand(self): 122 | self.configName = f"{self.timeStr}{self.configSuffix}.json" 123 | self.configPath = os.path.join(self.tempDir, self.configName) 124 | with open(self.configPath, "w", encoding="utf-8") as f: 125 | f.write(self.config) 126 | return f"Generate.bat {self.configPath}" 127 | 128 | def postProcess(self): 129 | latestTime = 0 130 | latestMp4FileName = "" 131 | latestMp4Path = "" 132 | 133 | for root, dirs, files in os.walk(self.tempDir): 134 | for file in files: 135 | if file.endswith(".mp4"): 136 | path = os.path.join(root, file) 137 | time = os.path.getmtime(path) 138 | if time > latestTime: 139 | latestTime = time 140 | latestMp4FileName = file 141 | latestMp4Path = path 142 | 143 | outputDir = os.path.join(Path.output, Path.getYYYYMMDD()) 144 | os.makedirs(outputDir, exist_ok=True) 145 | outputPath = os.path.join(outputDir, latestMp4FileName) 146 | shutil.copy(latestMp4Path, outputPath) 147 | 148 | basePath, _ = os.path.splitext(outputPath) 149 | with open(basePath + ".json", "w", encoding="utf-8-sig") as f: 150 | json.dump(self.data, f, indent=4) 151 | 152 | seed = "" 153 | tokens = latestMp4FileName.split("-", 2) 154 | if len(tokens) == 3: 155 | seed = tokens[1] 156 | seed = seed.split("_", 1)[0] 157 | 158 | subprocess.Popen(f"{self.editor.ffplayCmd} {outputPath}", shell=True) 159 | Log.user(L10n.format("log_ffplay", seed)) 160 | -------------------------------------------------------------------------------- /editor/src/tsk_upscale.py: -------------------------------------------------------------------------------- 1 | import os, glob, shutil, subprocess, json 2 | from const import Const, Path 3 | from l10n import L10n 4 | from log import Log 5 | from tsk_command_prompt import CommandPromptTask 6 | from prompt_travel import PromptTravel 7 | from serializer import Serializer 8 | 9 | 10 | class UpscaleTask(CommandPromptTask): 11 | @classmethod 12 | def upscaleWithConfigOverride(cls, model, upscaleDir): 13 | gen = model.generate 14 | config = PromptTravel.getConfig( 15 | gen, gen.model, gen.upscaleStrength, model.prompt.data, 10 16 | ) 17 | cls.upscale(model, upscaleDir, config) 18 | 19 | @classmethod 20 | def upscale(cls, model, upscaleDir, config=None): 21 | gen = model.generate 22 | data = Serializer.serialize(model) 23 | task = UpscaleTask( 24 | model.editor, 25 | "upscale", 26 | upscaleDir, 27 | gen.upscale1Mode, 28 | gen.getUpscale1Height(), 29 | gen.context, 30 | gen.upscaleUseHalfVae, 31 | gen.upscaleUseXFormers, 32 | config, 33 | data, 34 | ) 35 | cls.enqueue(task) 36 | 37 | def __init__( 38 | self, 39 | editor, 40 | taskMode, 41 | upscaleDir, 42 | mode, 43 | height, 44 | context, 45 | useHalfVae, 46 | useXFormers, 47 | config, 48 | data, 49 | ): 50 | super().__init__(editor, taskMode) 51 | self.upscaleDir = upscaleDir 52 | self.mode = mode 53 | self.height = height 54 | self.context = context 55 | self.useHalfVae = useHalfVae 56 | self.useXFormers = useXFormers 57 | self.config = config 58 | self.data = data 59 | 60 | def resetState(self): 61 | super().resetState() 62 | self.configName = "" 63 | self.configPath = "" 64 | 65 | def createCommand(self): 66 | cmd = os.path.join("editor", "bat", self.mode + ".bat") 67 | cmd = f"{cmd} -H {self.height}" 68 | if self.mode == Const.refine: 69 | cmd += f" -C {(int)(self.context / 2)}" 70 | if self.useHalfVae: 71 | cmd += " --half-vae" 72 | if self.useXFormers: 73 | cmd += " --xformers" 74 | 75 | if self.config is not None: 76 | self.configName = f"{self.timeStr}.json" 77 | configDir = os.path.abspath(os.path.join(self.upscaleDir, os.pardir)) 78 | self.configPath = os.path.join(configDir, self.configName) 79 | with open(self.configPath, "w", encoding="utf-8") as f: 80 | f.write(self.config) 81 | cmd += f" -c {self.configPath}" # dont "" 82 | cmd += f" {self.upscaleDir}" # dont "" 83 | return cmd 84 | 85 | def postProcess(self): 86 | outputDir = "" 87 | if self.mode == Const.tile: 88 | outputDir = self.findNewDir(Path.promptTravelUpscaled) 89 | elif self.mode == Const.refine: 90 | outputDir = self.findNewDir(Path.promptTravelRefined) 91 | if outputDir is not None: 92 | outputDir = self.findNewDir(outputDir) 93 | if outputDir is None: 94 | return 95 | mp4Paths = glob.glob(os.path.join(outputDir, "*.mp4")) 96 | if len(mp4Paths) == 0: 97 | return 98 | mp4Path = max(mp4Paths, key=os.path.getmtime) 99 | mp4Name = os.path.basename(mp4Path) 100 | 101 | outputDir = os.path.join(Path.output, Path.getYYYYMMDD()) 102 | os.makedirs(outputDir, exist_ok=True) 103 | outputPath = os.path.join(outputDir, mp4Name) 104 | shutil.copy(mp4Path, outputPath) 105 | 106 | basePath, _ = os.path.splitext(outputPath) 107 | with open(basePath + ".json", "w", encoding="utf-8-sig") as f: 108 | json.dump(self.data, f, indent=4) 109 | 110 | seed = "" 111 | tokens = mp4Name.split(r"[_-]", 2) 112 | if len(tokens) == 3: 113 | seed = tokens[1] 114 | 115 | subprocess.Popen(f"{self.editor.ffplayCmd} {outputPath}", shell=True) 116 | Log.user(L10n.format("log_ffplay", seed)) 117 | 118 | def findNewDir(self, dirPath): 119 | subDirs = [ 120 | os.path.join(dirPath, name) 121 | for name in os.listdir(dirPath) 122 | if os.path.isdir(os.path.join(dirPath, name)) 123 | ] 124 | newDir = max(subDirs, key=lambda x: os.path.getmtime(x), default=None) # None 125 | return newDir 126 | -------------------------------------------------------------------------------- /editor/src/ui.py: -------------------------------------------------------------------------------- 1 | from config import Config 2 | from l10n import L10n 3 | from ui_menu import Menu 4 | from ui_input import InputForm 5 | from ui_preview import PreviewForm 6 | from ui_basic import BasicForm 7 | from ui_generate import GenerateForm 8 | from ui_control_net import ControlNetForm 9 | from ui_ip_adapter import IpAdapterForm 10 | from ui_upscale import UpscaleForm 11 | from ui_mosaic import MosaicForm 12 | from ui_output import OutputForm 13 | import tkinter as tk 14 | import tkinter.ttk as ttk 15 | 16 | # from tkinterdnd2 import DND_FILES, TkinterDnD 17 | import darkdetect 18 | 19 | 20 | class Form: 21 | winMinW = 860 22 | winMinH = 750 23 | winInitW = 900 24 | winInitH = 900 25 | winInitX = "None" 26 | winInitY = "40" 27 | 28 | ntbFuncInitH = 400 29 | ntbFuncMinH = 400 30 | 31 | def __init__(self): 32 | self.win = tk.Tk() 33 | self.win.title(f'{L10n.get("title")}') 34 | self.win.minsize(Form.winMinW, Form.winMinH) 35 | 36 | winGeom = f"{Form.winInitW}x{Form.winInitH}" 37 | if Form.winInitX != "None": 38 | winGeom += f"+{Form.winInitX}+{Form.winInitY}" 39 | self.win.geometry(winGeom) 40 | 41 | self.menu = Menu(self.win) 42 | 43 | self.paneWin = tk.PanedWindow(self.win, orient=tk.VERTICAL, sashpad=2) 44 | self.input = InputForm(self.paneWin) 45 | 46 | self.ntbFunc = ttk.Notebook(self.paneWin) 47 | self.preview = PreviewForm(self.ntbFunc) 48 | self.basic = BasicForm(self.ntbFunc) 49 | self.generate = GenerateForm(self.ntbFunc) 50 | self.controlNet = ControlNetForm(self.ntbFunc) 51 | self.ipAdapter = IpAdapterForm(self.ntbFunc) 52 | self.upscale = UpscaleForm(self.ntbFunc) 53 | self.mosaic = MosaicForm(self.ntbFunc) 54 | self.paneWin.add( 55 | self.ntbFunc, 56 | height=self.ntbFuncH, 57 | minsize=Form.ntbFuncMinH, 58 | stretch="never", 59 | ) 60 | self.ntbFunc.select(1) # Default 1 61 | 62 | self.output = OutputForm(self.paneWin) 63 | self.paneWin.pack(fill=tk.BOTH, expand=True) 64 | 65 | if Form.invOsCol ^ darkdetect.isDark(): 66 | self.setDarkTheme() 67 | 68 | @classmethod 69 | def loadConfig(cls): 70 | Form.winInitW = Config.get("ui_size", "win_width", Form.winInitW) 71 | Form.winInitH = Config.get("ui_size", "win_height", Form.winInitH) 72 | Form.winInitX = Config.get("ui_size", "win_x", Form.winInitX) 73 | Form.winInitY = Config.get("ui_size", "win_y", Form.winInitY) 74 | 75 | InputForm.loadConfig() 76 | Form.ntbFuncH = Config.get("ui_size", "func_h", Form.ntbFuncInitH) 77 | BasicForm.loadConfig() 78 | GenerateForm.loadConfig() 79 | UpscaleForm.loadConfig() 80 | OutputForm.loadConfig() 81 | 82 | Form.darkColFg = Config.get("ui_color", "dark_col_fg", "#CCCCCC") 83 | Form.darkColFgSel = Config.get("ui_color", "dark_col_fg_sel", "#FFFFFF") 84 | Form.darkColBg = Config.get("ui_color", "dark_col_bg", "#222222") 85 | Form.darkColBgSel = Config.get("ui_color", "dark_col_bg_sel", "#555555") 86 | Form.invOsCol = Config.getBool("ui_color", "inv_os_col", False) 87 | 88 | def storeConfig(self): 89 | Config.set("ui_size", "win_width", self.win.winfo_width()) 90 | Config.set("ui_size", "win_height", self.win.winfo_height()) 91 | Config.set("ui_size", "win_x", self.win.winfo_x()) 92 | Config.set("ui_size", "win_y", self.win.winfo_y()) 93 | 94 | self.input.storeConfig() 95 | func_h = self.ntbFunc.winfo_height() 96 | if func_h != 1: 97 | Config.set("ui_size", "func_h", func_h) 98 | self.basic.storeConfig() 99 | self.generate.storeConfig() 100 | self.output.storeConfig() 101 | 102 | Config.set("ui_color", "dark_col_fg", Form.darkColFg) 103 | Config.set("ui_color", "dark_col_fg_sel", Form.darkColFgSel) 104 | Config.set("ui_color", "dark_col_bg", Form.darkColBg) 105 | Config.set("ui_color", "dark_col_bg_sel", Form.darkColBgSel) 106 | Config.set("ui_color", "inv_os_col", Form.invOsCol) 107 | 108 | def setDarkTheme(self): 109 | colFgBg = {"fg": Form.darkColFg, "bg": Form.darkColBg} 110 | colSel = colFgBg.copy() 111 | colSel["selectforeground"] = Form.darkColFgSel 112 | colSel["selectbackground"] = Form.darkColBgSel 113 | colSelIns = colSel.copy() 114 | colSelIns["insertbackground"] = Form.darkColFgSel 115 | colSelHigh = colSel.copy() 116 | colSelHigh["highlightbackground"] = Form.darkColBgSel 117 | 118 | colAct = colFgBg.copy() 119 | colAct["activeforeground"] = Form.darkColFgSel 120 | colAct["activebackground"] = Form.darkColBgSel 121 | colActChk = colAct.copy() 122 | colActChk["selectcolor"] = Form.darkColBgSel 123 | 124 | sli = colFgBg.copy() 125 | sli["activebackground"] = Form.darkColBgSel 126 | sli["troughcolor"] = Form.darkColBg 127 | sli["highlightbackground"] = Form.darkColBg 128 | 129 | colors = { 130 | "fg": Form.darkColFg, 131 | "bg": Form.darkColBg, 132 | "fgSel": Form.darkColFgSel, 133 | "bgSel": Form.darkColBgSel, 134 | "fgBg": colFgBg, 135 | "sel": colSel, 136 | "selIns": colSelIns, 137 | "act": colAct, 138 | "actChk": colActChk, 139 | "txt": colSelIns, 140 | "chk": colActChk, 141 | "ent": colSelIns, 142 | "sli": sli, 143 | "lst": colSelHigh, 144 | } 145 | 146 | style = ttk.Style() 147 | style.theme_use("default") 148 | style.configure("TFrame", background=colors["bg"]) 149 | style.configure("TLabel", background=colors["bg"], foreground=colors["fg"]) 150 | 151 | style.configure( 152 | "TButton", 153 | foreground=colors["fg"], 154 | background=colors["bg"], 155 | ) 156 | style.map( 157 | "TButton", 158 | foreground=[("active", colors["fgSel"])], 159 | background=[("active", colors["bgSel"])], 160 | ) 161 | 162 | style.configure( 163 | "TCombobox", 164 | foreground=colors["fg"], 165 | ) 166 | style.map( 167 | "TCombobox", 168 | fieldbackground=[("readonly", colors["bg"])], 169 | selectbackground=[ 170 | ("readonly", colors["bg"]), 171 | ("readonly", "!focus", colors["bg"]), 172 | ], 173 | ) 174 | 175 | style.configure("TNotebook", background=colors["bg"]) 176 | style.configure("TNotebook.Tab", padding=[6, 8]) 177 | style.map( 178 | "TNotebook.Tab", 179 | foreground=[("", colors["fg"]), ("selected", colors["fgSel"])], 180 | background=[("", colors["bg"]), ("selected", colors["bgSel"])], 181 | ) 182 | 183 | self.input.setDarkTheme(colors) 184 | self.preview.setDarkTheme(colors) 185 | self.basic.setDarkTheme(colors) 186 | self.generate.setDarkTheme(colors) 187 | self.controlNet.setDarkTheme(colors) 188 | self.ipAdapter.setDarkTheme(colors) 189 | self.upscale.setDarkTheme(colors) 190 | self.mosaic.setDarkTheme(colors) 191 | self.output.setDarkTheme(colors) 192 | 193 | def run(self): 194 | self.win.lift() 195 | self.win.mainloop() 196 | -------------------------------------------------------------------------------- /editor/src/ui_basic.py: -------------------------------------------------------------------------------- 1 | from const import Const, Path 2 | from l10n import L10n 3 | from config import Config 4 | from ui_const import UiPack 5 | import tkinter as tk 6 | import tkinter.ttk as ttk 7 | import os, glob 8 | 9 | 10 | class BasicForm: 11 | maxLength = 600 12 | sizeResolution = 64 13 | 14 | def __init__(self, parent): 15 | self.frm = ttk.Frame(parent) 16 | 17 | self.initLengthSeed() 18 | self.initModelVae() 19 | self.initSize() 20 | self.initUpsdcale1() 21 | self.initUpsdcale2() 22 | 23 | parent.add(self.frm, text=L10n.get("basic_tab")) 24 | 25 | def initLengthSeed(self): 26 | self.frmLengthSeed = ttk.Frame(self.frm) 27 | 28 | self.varLength = tk.IntVar(value=10) 29 | self.lblLength = ttk.Label(self.frmLengthSeed, text=L10n.get("generate_length")) 30 | self.varLength.trace_add("write", self.updateLength) 31 | self.lblLength.pack(UiPack.lbl) 32 | self.sliLength = tk.Scale( 33 | self.frmLengthSeed, 34 | from_=10, 35 | to=BasicForm.maxLength, 36 | resolution=10, 37 | orient=tk.HORIZONTAL, 38 | variable=self.varLength, 39 | takefocus=True, 40 | ) 41 | self.sliLength.pack(UiPack.sli) 42 | 43 | self.lblSeed = ttk.Label(self.frmLengthSeed, text=L10n.get("generate_seed")) 44 | self.lblSeed.pack(UiPack.lbl) 45 | 46 | self.varSeed = tk.StringVar(value="-1") 47 | self.entSeed = tk.Entry(self.frmLengthSeed, textvariable=self.varSeed, width=20) 48 | self.entSeed.pack(UiPack.ent) 49 | self.btnRandomSeed = ttk.Button( 50 | self.frmLengthSeed, 51 | text="🎲", 52 | command=lambda: self.varSeed.set("-1"), 53 | ) 54 | self.btnRandomSeed.pack(UiPack.btn) 55 | 56 | self.frmLengthSeed.pack(UiPack.frm) 57 | 58 | def updateLength(self, *args): 59 | self.lblLength.configure( 60 | text=L10n.get("generate_length").format(int(self.varLength.get() / 10)) 61 | ) 62 | 63 | def initModelVae(self): 64 | self.frmModelVae = ttk.Frame(self.frm) 65 | self.lblModel = ttk.Label(self.frmModelVae, text=L10n.get("generate_model")) 66 | self.lblModel.pack(UiPack.lbl) 67 | 68 | self.varModel = tk.StringVar() 69 | self.cmbModel = ttk.Combobox( 70 | self.frmModelVae, 71 | textvariable=self.varModel, 72 | state="readonly", 73 | postcommand=self.listModeValues, 74 | width=40, 75 | height=23, 76 | ) 77 | self.cmbModel.pack(UiPack.cmb, fill=tk.X, expand=True) 78 | self.listModeValues() 79 | 80 | self.lblVae = ttk.Label(self.frmModelVae, text=L10n.get("generate_vae")) 81 | self.lblVae.pack(UiPack.lbl) 82 | 83 | self.varVae = tk.StringVar() 84 | self.cmbVae = ttk.Combobox( 85 | self.frmModelVae, 86 | textvariable=self.varVae, 87 | state="readonly", 88 | postcommand=self.listVaeValues, 89 | width=30, 90 | height=23, 91 | ) 92 | self.cmbVae.pack(UiPack.cmb) 93 | self.listVaeValues() 94 | 95 | self.frmModelVae.pack(UiPack.frm) 96 | 97 | def listModeValues(self): 98 | baseQuery = os.path.join(Path.model, "**", "*.") 99 | models = glob.glob(baseQuery + "safetensors", recursive=True) 100 | models += glob.glob(baseQuery + "ckpt", recursive=True) 101 | pfxLen = len(Path.model) + 1 102 | models = [model[pfxLen:] for model in models] 103 | self.cmbModel.configure(values=models) 104 | 105 | def listVaeValues(self): 106 | baseQuery = os.path.join(Path.vae, "**", "*.") 107 | vaes = glob.glob(baseQuery + "safetensors", recursive=True) 108 | vaes += glob.glob(baseQuery + "pt", recursive=True) 109 | vaes += glob.glob(baseQuery + "ckpt", recursive=True) 110 | pfxLen = len(Path.vae) + 1 111 | vaes = [vae[pfxLen:] for vae in vaes] 112 | self.cmbVae.configure(values=vaes) 113 | 114 | def initSize(self): 115 | self.frmSize = ttk.Frame(self.frm) 116 | 117 | self.varWidth = tk.IntVar(value=384) 118 | self.varWidth.trace_add("write", self.updateSize) 119 | self.lblWidth = ttk.Label(self.frmSize, text=L10n.get("generate_width")) 120 | self.lblWidth.pack(UiPack.lbl) 121 | self.sliWidth = tk.Scale( 122 | self.frmSize, 123 | from_=256, 124 | to=1280, 125 | resolution=BasicForm.sizeResolution, 126 | orient=tk.HORIZONTAL, 127 | variable=self.varWidth, 128 | takefocus=True, 129 | ) 130 | self.sliWidth.pack(UiPack.sli) 131 | 132 | self.varHeight = tk.IntVar(value=512) 133 | self.varHeight.trace_add("write", self.updateSize) 134 | self.lblHeight = ttk.Label(self.frmSize, text=L10n.get("generate_height")) 135 | self.lblHeight.pack(UiPack.lbl) 136 | self.sliHeight = tk.Scale( 137 | self.frmSize, 138 | from_=256, 139 | to=1280, 140 | resolution=BasicForm.sizeResolution, 141 | orient=tk.HORIZONTAL, 142 | variable=self.varHeight, 143 | takefocus=True, 144 | ) 145 | 146 | self.sliHeight.pack(UiPack.sli) 147 | 148 | self.btnSwap = ttk.Button( 149 | self.frmSize, text=L10n.get("swap"), command=self.swapSize 150 | ) 151 | self.btnSwap.pack(UiPack.btn) 152 | 153 | self.frmSize.pack(UiPack.frm) 154 | 155 | def swapSize(self): 156 | width = self.varWidth.get() 157 | self.varWidth.set(self.varHeight.get()) 158 | self.varHeight.set(width) 159 | 160 | def initUpsdcale1(self): 161 | self.frmUpscale1 = ttk.Frame(self.frm) 162 | self.varUpscale1Enabled = tk.BooleanVar(value=True) 163 | self.chkUpscale1Enabled = tk.Checkbutton( 164 | self.frmUpscale1, 165 | text=L10n.get("upscale1"), 166 | variable=self.varUpscale1Enabled, 167 | ) 168 | self.chkUpscale1Enabled.pack(UiPack.chk) 169 | 170 | self.varUpscale1Mode = tk.StringVar() 171 | self.cmbUpscale1Mode = ttk.Combobox( 172 | self.frmUpscale1, 173 | values=( 174 | Const.tile, 175 | Const.refine, 176 | ), 177 | textvariable=self.varUpscale1Mode, 178 | state="readonly", 179 | width=11, 180 | ) 181 | self.cmbUpscale1Mode.current(1) 182 | self.cmbUpscale1Mode.pack(UiPack.cmb) 183 | 184 | self.lblUpscale1Scale = ttk.Label( 185 | self.frmUpscale1, text=L10n.get("upscale_scale") 186 | ) 187 | self.lblUpscale1Scale.pack(UiPack.lbl) 188 | self.varUpscale1Scale = tk.DoubleVar(value=2.0) 189 | self.varUpscale1Scale.trace_add("write", self.updateSize) 190 | self.sliUpscale1Scale = tk.Scale( 191 | self.frmUpscale1, 192 | from_=1.0, 193 | to=4.0, 194 | resolution=0.5, 195 | orient=tk.HORIZONTAL, 196 | variable=self.varUpscale1Scale, 197 | takefocus=True, 198 | ) 199 | self.sliUpscale1Scale.pack(UiPack.sli) 200 | 201 | self.frmUpscale1.pack(UiPack.frm) 202 | 203 | def initUpsdcale2(self): 204 | self.frmUpscale2 = ttk.Frame(self.frm) 205 | self.varUpscale2Enabled = tk.BooleanVar(value=True) 206 | self.chkUpscale2Enabled = tk.Checkbutton( 207 | self.frmUpscale2, 208 | text=L10n.get("upscale2"), 209 | variable=self.varUpscale2Enabled, 210 | ) 211 | self.chkUpscale2Enabled.pack(UiPack.chk) 212 | 213 | self.varUpscale2Mode = tk.StringVar() 214 | self.cmbUpscale2Mode = ttk.Combobox( 215 | self.frmUpscale2, 216 | values=( 217 | Const.tile, 218 | Const.refine, 219 | ), 220 | textvariable=self.varUpscale2Mode, 221 | state="readonly", 222 | width=11, 223 | ) 224 | self.cmbUpscale2Mode.current(0) 225 | self.cmbUpscale2Mode.pack(UiPack.cmb) 226 | 227 | self.lblUpscale2Scale = ttk.Label( 228 | self.frmUpscale2, text=L10n.get("upscale_scale") 229 | ) 230 | self.lblUpscale2Scale.pack(UiPack.lbl) 231 | self.varUpscale2Scale = tk.DoubleVar(value=1.5) 232 | self.varUpscale2Scale.trace_add("write", self.updateSize) 233 | self.sliUpscale2Scale = tk.Scale( 234 | self.frmUpscale2, 235 | from_=1.0, 236 | to=4.0, 237 | resolution=0.5, 238 | orient=tk.HORIZONTAL, 239 | variable=self.varUpscale2Scale, 240 | takefocus=True, 241 | ) 242 | self.sliUpscale2Scale.pack(UiPack.sli) 243 | 244 | self.frmUpscale2.pack(UiPack.frm) 245 | 246 | def updateSize(self, *args): 247 | width = self.varWidth.get() 248 | height = self.varHeight.get() 249 | upscale1 = self.varUpscale1Scale.get() 250 | upscale2 = upscale1 * self.varUpscale2Scale.get() 251 | 252 | self.chkUpscale1Enabled.configure( 253 | text=f"{L10n.get('upscale1')}\n{int(width * upscale1):>4} x {int(height * upscale1):>4}" 254 | ) 255 | self.chkUpscale2Enabled.configure( 256 | text=f"{L10n.get('upscale2')}\n{int(width * upscale2):>4} x {int(height * upscale2):>4}" 257 | ) 258 | 259 | @classmethod 260 | def loadConfig(self): 261 | BasicForm.maxLength = Config.get("ui", "max_length", BasicForm.maxLength) 262 | BasicForm.sizeResolution = Config.get( 263 | "ui", "size_resolution", BasicForm.sizeResolution 264 | ) 265 | 266 | def storeConfig(self): 267 | Config.set("ui", "max_length", BasicForm.maxLength) 268 | Config.set("ui", "size_resolution", BasicForm.sizeResolution) 269 | 270 | def setDarkTheme(self, colors): 271 | self.sliLength.configure(colors["sli"]) 272 | self.entSeed.configure(colors["ent"]) 273 | self.sliWidth.configure(colors["sli"]) 274 | self.sliHeight.configure(colors["sli"]) 275 | self.chkUpscale1Enabled.configure(colors["chk"]) 276 | self.sliUpscale1Scale.configure(colors["sli"]) 277 | self.chkUpscale2Enabled.configure(colors["chk"]) 278 | self.sliUpscale2Scale.configure(colors["sli"]) 279 | -------------------------------------------------------------------------------- /editor/src/ui_const.py: -------------------------------------------------------------------------------- 1 | import tkinter as tk 2 | 3 | 4 | class Ui: 5 | padX = 4 6 | padY = 2 7 | cfgTxtArea = {"spacing1": 4, "spacing2": 4, "spacing3": 4, "wrap": tk.WORD} 8 | 9 | 10 | class UiPack: 11 | txt = {"side": tk.LEFT, "fill": tk.BOTH, "expand": True} 12 | frm = { 13 | "padx": Ui.padX, 14 | "pady": Ui.padY, 15 | "fill": tk.X, 16 | } 17 | sli = { 18 | "padx": Ui.padX, 19 | "pady": Ui.padY, 20 | "side": tk.LEFT, 21 | "fill": tk.X, 22 | "expand": True, 23 | } 24 | sep = {"padx": Ui.padX, "pady": Ui.padY, "fill": tk.X} 25 | lbl = {"padx": Ui.padX, "pady": Ui.padY, "side": tk.LEFT} 26 | btn = {"padx": Ui.padX, "pady": Ui.padY, "side": tk.LEFT} 27 | chk = {"padx": Ui.padX, "pady": Ui.padY, "side": tk.LEFT} 28 | cmb = {"padx": Ui.padX, "pady": Ui.padY, "side": tk.LEFT} 29 | ent = {"padx": Ui.padX, "pady": Ui.padY, "side": tk.LEFT} 30 | -------------------------------------------------------------------------------- /editor/src/ui_control_net.py: -------------------------------------------------------------------------------- 1 | import os, subprocess 2 | import tkinter as tk 3 | import tkinter.ttk as ttk 4 | from const import Path, ControlNetType 5 | from l10n import L10n 6 | from config import Config 7 | from ui_const import UiPack 8 | 9 | 10 | class ControlNetForm: 11 | def __init__(self, parent): 12 | self.frm = ttk.Frame(parent) 13 | 14 | self.initControlNetDirLoop() 15 | self.initControlNetInfo() 16 | self.initImport() 17 | 18 | self.ntb = ttk.Notebook(self.frm) 19 | self.ntb.pack(fill=tk.BOTH, expand=True) 20 | 21 | for cnType in ControlNetType: 22 | self.initControlNetTab(self.ntb, cnType) 23 | 24 | parent.add(self.frm, text=L10n.get("control_net_tab")) 25 | 26 | def initControlNetDirLoop(self): 27 | self.frmControlNetDirLoop = ttk.Frame(self.frm) 28 | 29 | self.lblControlNetDir = ttk.Label( 30 | self.frmControlNetDirLoop, text=L10n.get("control_net_dir") 31 | ) 32 | self.lblControlNetDir.pack(UiPack.lbl) 33 | 34 | self.varControlNetDir = tk.StringVar() 35 | self.cmbControlNetDir = ttk.Combobox( 36 | self.frmControlNetDirLoop, 37 | textvariable=self.varControlNetDir, 38 | state="readonly", 39 | postcommand=self.listControlNetDirValues, 40 | width=20, 41 | height=20, 42 | ) 43 | self.cmbControlNetDir.pack(UiPack.cmb, fill=tk.X, expand=True) 44 | 45 | self.btnControlNetDirExplorer = ttk.Button( 46 | self.frmControlNetDirLoop, 47 | text=L10n.get("ip_adapter_image_dir_explorer"), 48 | command=lambda: subprocess.run(["explorer", Path.controlNet]), 49 | ) 50 | self.btnControlNetDirExplorer.pack(UiPack.btn) 51 | 52 | self.varControlNetLoop = tk.BooleanVar(value=True) 53 | self.chkControlNetLoop = tk.Checkbutton( 54 | self.frmControlNetDirLoop, 55 | text=L10n.get("control_net_loop"), 56 | variable=self.varControlNetLoop, 57 | ) 58 | self.chkControlNetLoop.pack(UiPack.chk) 59 | 60 | self.frmControlNetDirLoop.pack(UiPack.frm) 61 | 62 | def listControlNetDirValues(self): 63 | subDirs = [f.name for f in os.scandir(Path.controlNet) if f.is_dir()] 64 | self.cmbControlNetDir.configure(values=subDirs) 65 | 66 | def initControlNetInfo(self): 67 | self.frmInfo = ttk.Frame(self.frm) 68 | 69 | self.lblControlNetInfo = ttk.Label( 70 | self.frmInfo, text=L10n.get("control_net_info") 71 | ) 72 | self.lblControlNetInfo.pack(UiPack.lbl) 73 | 74 | self.frmInfo.pack(UiPack.frm) 75 | 76 | def initImport(self): 77 | self.frmImport = ttk.Frame(self.frm) 78 | 79 | self.btnImport = ttk.Button( 80 | self.frmImport, 81 | text=L10n.get("control_net_import"), 82 | ) 83 | self.btnImport.pack(UiPack.btn) 84 | 85 | self.lblImportSpeed = ttk.Label( 86 | self.frmImport, text=L10n.get("control_net_import_speed") 87 | ) 88 | self.lblImportSpeed.pack(UiPack.lbl) 89 | self.varImportSpeed = tk.StringVar(value="1") 90 | self.entImportSpeed = tk.Entry( 91 | self.frmImport, textvariable=self.varImportSpeed, width=4 92 | ) 93 | self.entImportSpeed.pack(UiPack.ent) 94 | 95 | self.lblImportStart = ttk.Label( 96 | self.frmImport, text=L10n.get("control_net_import_start") 97 | ) 98 | self.lblImportStart.pack(UiPack.lbl) 99 | self.varImportStart = tk.StringVar(value="") 100 | self.entImportStart = tk.Entry( 101 | self.frmImport, textvariable=self.varImportStart, width=11 102 | ) 103 | self.entImportStart.pack(UiPack.ent) 104 | 105 | self.lblImportStart = ttk.Label( 106 | self.frmImport, text=L10n.get("control_net_import_length") 107 | ) 108 | self.lblImportStart.pack(UiPack.lbl) 109 | self.varImportLength = tk.StringVar(value="") 110 | self.entImportLength = tk.Entry( 111 | self.frmImport, textvariable=self.varImportLength, width=11 112 | ) 113 | self.entImportLength.pack(UiPack.ent) 114 | 115 | self.lblImportIndex = ttk.Label( 116 | self.frmImport, text=L10n.get("control_net_import_index") 117 | ) 118 | self.lblImportIndex.pack(UiPack.lbl) 119 | self.varImportIndex = tk.StringVar(value="0") 120 | self.entImportIndex = tk.Entry( 121 | self.frmImport, textvariable=self.varImportIndex, width=4 122 | ) 123 | self.entImportIndex.pack(UiPack.ent) 124 | 125 | self.frmImport.pack(UiPack.frm) 126 | pass 127 | 128 | def initControlNetTab(self, parent, cnType): 129 | name = cnType.name 130 | frm = ttk.Frame(parent) 131 | setattr(self, f"frm_{name}", frm) 132 | 133 | self.initControlNetFlags(frm, name) 134 | self.initControlNetScaleScaleList(frm, name) 135 | self.initControlNetStartEnd(frm, name) 136 | 137 | parent.add(frm, text=name) 138 | 139 | def initControlNetFlags(self, parent, name): 140 | frm = ttk.Frame(parent) 141 | setattr(self, f"frmFlags_{name}", frm) 142 | 143 | varEnable = tk.BooleanVar() 144 | setattr(self, f"varEnable_{name}", varEnable) 145 | chkEnable = tk.Checkbutton( 146 | frm, 147 | text=L10n.format("control_net_enable", name), 148 | variable=varEnable, 149 | ) 150 | setattr(self, f"chkEnable_{name}", chkEnable) 151 | chkEnable.pack(UiPack.chk) 152 | 153 | btnOpenDir = ttk.Button( 154 | frm, 155 | text=L10n.format("control_net_open_dir", name), 156 | command=lambda: self.varControlNetDir.get() == "" 157 | or subprocess.run( 158 | [ 159 | "explorer", 160 | os.path.join( 161 | Path.controlNet, 162 | self.varControlNetDir.get(), 163 | f"controlnet_{name}", 164 | ), 165 | ] 166 | ), 167 | ) 168 | setattr(self, f"btnOpenDir_{name}", btnOpenDir) 169 | btnOpenDir.pack(UiPack.btn) 170 | 171 | varUsePreprocessor = tk.BooleanVar(value=True) 172 | setattr(self, f"varUsePreprocessor_{name}", varUsePreprocessor) 173 | chkPreprocessor = tk.Checkbutton( 174 | frm, 175 | text=L10n.get("control_net_preprocessor"), 176 | variable=varUsePreprocessor, 177 | ) 178 | setattr(self, f"chkPreprocessor_{name}", chkPreprocessor) 179 | chkPreprocessor.pack(UiPack.chk) 180 | 181 | varGuessMode = tk.BooleanVar() 182 | setattr(self, f"varGuessMode_{name}", varGuessMode) 183 | chkGuessMode = tk.Checkbutton( 184 | frm, 185 | text=L10n.format("control_net_guess_mode", name), 186 | variable=varGuessMode, 187 | ) 188 | setattr(self, f"chkGuessMode_{name}", chkGuessMode) 189 | chkGuessMode.pack(UiPack.chk) 190 | 191 | frm.pack(UiPack.frm) 192 | 193 | def initControlNetScaleScaleList(self, parent, name): 194 | frm = ttk.Frame(parent) 195 | setattr(self, f"frmScaleScaleList_{name}", frm) 196 | 197 | varScale = tk.DoubleVar(value=1.0) 198 | setattr(self, f"varScale_{name}", varScale) 199 | lblScale = ttk.Label(frm, text=L10n.format("control_net_scale", name)) 200 | lblScale.pack(UiPack.lbl) 201 | sliScale = tk.Scale( 202 | frm, 203 | from_=0.0, 204 | to=2.0, 205 | resolution=0.05, 206 | orient=tk.HORIZONTAL, 207 | variable=varScale, 208 | takefocus=True, 209 | ) 210 | setattr(self, f"sliScale_{name}", sliScale) 211 | sliScale.pack(UiPack.sli) 212 | 213 | varScaleList = tk.StringVar() 214 | setattr(self, f"varScaleList_{name}", varScaleList) 215 | lblScaleList = ttk.Label(frm, text=L10n.get("control_net_scale_list")) 216 | lblScaleList.pack(UiPack.lbl) 217 | entScaleList = tk.Entry(frm, textvariable=varScaleList) 218 | setattr(self, f"entScaleList_{name}", entScaleList) 219 | entScaleList.pack(UiPack.ent, fill=tk.X, expand=True) 220 | 221 | frm.pack(UiPack.frm) 222 | 223 | def initControlNetStartEnd(self, parent, name): 224 | frm = ttk.Frame(parent) 225 | 226 | varStart = tk.DoubleVar(value=0.0) 227 | setattr(self, f"varStart_{name}", varStart) 228 | lblStart = ttk.Label(frm, text=L10n.format("control_net_start", name)) 229 | lblStart.pack(UiPack.lbl) 230 | sliStart = tk.Scale( 231 | frm, 232 | from_=0.0, 233 | to=1.0, 234 | resolution=0.05, 235 | orient=tk.HORIZONTAL, 236 | variable=varStart, 237 | takefocus=True, 238 | ) 239 | setattr(self, f"sliStart_{name}", sliStart) 240 | sliStart.pack(UiPack.sli) 241 | 242 | varEnd = tk.DoubleVar(value=1.0) 243 | setattr(self, f"varEnd_{name}", varEnd) 244 | lblEnd = ttk.Label(frm, text=L10n.format("control_net_end", name)) 245 | lblEnd.pack(UiPack.lbl) 246 | sliEnd = tk.Scale( 247 | frm, 248 | from_=0.0, 249 | to=1.0, 250 | resolution=0.05, 251 | orient=tk.HORIZONTAL, 252 | variable=varEnd, 253 | takefocus=True, 254 | ) 255 | setattr(self, f"sliEnd_{name}", sliEnd) 256 | sliEnd.pack(UiPack.sli) 257 | 258 | frm.pack(UiPack.frm) 259 | 260 | def setDarkTheme(self, colors): 261 | self.chkControlNetLoop.configure(colors["chk"]) 262 | self.entImportSpeed.configure(colors["ent"]) 263 | self.entImportStart.configure(colors["ent"]) 264 | self.entImportLength.configure(colors["ent"]) 265 | self.entImportIndex.configure(colors["ent"]) 266 | for cnType in ControlNetType: 267 | getattr(self, f"chkEnable_{cnType.name}").configure(colors["chk"]) 268 | getattr(self, f"chkPreprocessor_{cnType.name}").configure(colors["chk"]) 269 | getattr(self, f"chkGuessMode_{cnType.name}").configure(colors["chk"]) 270 | getattr(self, f"sliScale_{cnType.name}").configure(colors["sli"]) 271 | getattr(self, f"entScaleList_{cnType.name}").configure(colors["ent"]) 272 | getattr(self, f"sliStart_{cnType.name}").configure(colors["sli"]) 273 | getattr(self, f"sliEnd_{cnType.name}").configure(colors["sli"]) 274 | -------------------------------------------------------------------------------- /editor/src/ui_generate.py: -------------------------------------------------------------------------------- 1 | from const import Const, Path 2 | from l10n import L10n 3 | from config import Config 4 | from ui_const import UiPack 5 | import tkinter as tk 6 | import tkinter.ttk as ttk 7 | import os, glob 8 | 9 | 10 | class GenerateForm: 11 | schedulerValues = Const.schedulerValues 12 | schedulerNames = Const.schedulerNames 13 | maxSteps = 60 14 | 15 | def __init__(self, parent): 16 | self.frm = ttk.Frame(parent) 17 | 18 | self.initMotionModuleContext() 19 | self.initSchedulerSteps() 20 | self.initGuidanceScaleClipScale() 21 | self.initPromptFixedRatioHalfVaeXFormers() 22 | 23 | parent.add(self.frm, text=L10n.get("generate_tab")) 24 | 25 | def initMotionModuleContext(self): 26 | self.frmMotionModuleContext = ttk.Frame(self.frm) 27 | 28 | self.lblMotionModule = ttk.Label( 29 | self.frmMotionModuleContext, text=L10n.get("generate_motion_module") 30 | ) 31 | self.lblMotionModule.pack(UiPack.lbl) 32 | 33 | self.varMotionModule = tk.StringVar() 34 | self.cmbMotionModule = ttk.Combobox( 35 | self.frmMotionModuleContext, 36 | textvariable=self.varMotionModule, 37 | state="readonly", 38 | postcommand=self.listMotionModuleValues, 39 | width=40, 40 | height=23, 41 | ) 42 | self.cmbMotionModule.pack(UiPack.cmb) 43 | self.listMotionModuleValues() 44 | 45 | self.lblContext = ttk.Label( 46 | self.frmMotionModuleContext, text=L10n.get("generate_context") 47 | ) 48 | self.lblContext.pack(UiPack.lbl) 49 | self.varContext = tk.IntVar(value=14) 50 | self.sliContext = tk.Scale( 51 | self.frmMotionModuleContext, 52 | from_=8, 53 | to=24, 54 | resolution=2, 55 | orient=tk.HORIZONTAL, 56 | variable=self.varContext, 57 | takefocus=True, 58 | ) 59 | self.sliContext.pack(UiPack.sli) 60 | 61 | self.frmMotionModuleContext.pack(UiPack.frm) 62 | 63 | def listMotionModuleValues(self): 64 | baseQuery = os.path.join(Path.motionModule, "**", "*.") 65 | motionModules = glob.glob(baseQuery + "ckpt", recursive=True) 66 | motionModules += glob.glob(baseQuery + "safetensors", recursive=True) 67 | motionModules += glob.glob(baseQuery + "pth", recursive=True) 68 | pfxLen = len(Path.motionModule) + 1 69 | motionModules = [motionModule[pfxLen:] for motionModule in motionModules] 70 | self.cmbMotionModule.configure(values=motionModules) 71 | 72 | def initSchedulerSteps(self): 73 | self.frmSchedulerSteps = ttk.Frame(self.frm) 74 | 75 | self.lblScheduler = ttk.Label( 76 | self.frmSchedulerSteps, text=L10n.get("generate_scheduler") 77 | ) 78 | self.lblScheduler.pack(UiPack.lbl) 79 | 80 | self.varScheduler = tk.StringVar() 81 | self.cmbScheduler = ttk.Combobox( 82 | self.frmSchedulerSteps, 83 | textvariable=self.varScheduler, 84 | state="readonly", 85 | values=GenerateForm.schedulerNames, 86 | width=21, 87 | height=23, 88 | ) 89 | self.cmbScheduler.pack(UiPack.cmb) 90 | 91 | self.lblSteps = ttk.Label( 92 | self.frmSchedulerSteps, text=L10n.get("generate_steps") 93 | ) 94 | self.lblSteps.pack(UiPack.lbl) 95 | 96 | self.varSteps = tk.IntVar(value=20) 97 | self.sliSteps = tk.Scale( 98 | self.frmSchedulerSteps, 99 | from_=1, 100 | to=GenerateForm.maxSteps, 101 | resolution=1, 102 | orient=tk.HORIZONTAL, 103 | variable=self.varSteps, 104 | takefocus=True, 105 | ) 106 | self.sliSteps.pack(UiPack.sli) 107 | 108 | self.frmSchedulerSteps.pack(UiPack.frm) 109 | 110 | def initGuidanceScaleClipScale(self): 111 | self.frmClipScaleGuidanceScale = ttk.Frame(self.frm) 112 | 113 | self.lblGuidanceScale = ttk.Label( 114 | self.frmClipScaleGuidanceScale, text=L10n.get("generate_guidance_scale") 115 | ) 116 | self.lblGuidanceScale.pack(UiPack.lbl) 117 | 118 | self.varGuidanceScale = tk.DoubleVar(value=8.0) 119 | self.sliGuidanceScale = tk.Scale( 120 | self.frmClipScaleGuidanceScale, 121 | from_=1.0, 122 | to=30.0, 123 | resolution=0.5, 124 | orient=tk.HORIZONTAL, 125 | variable=self.varGuidanceScale, 126 | takefocus=True, 127 | ) 128 | self.sliGuidanceScale.pack(UiPack.sli) 129 | 130 | self.lblClipSkip = ttk.Label( 131 | self.frmClipScaleGuidanceScale, text=L10n.get("generate_clip_skip") 132 | ) 133 | self.lblClipSkip.pack(UiPack.lbl) 134 | 135 | self.varClipSkip = tk.IntVar(value=2) 136 | self.sliClipSkip = tk.Scale( 137 | self.frmClipScaleGuidanceScale, 138 | from_=1, 139 | to=5, 140 | resolution=1, 141 | orient=tk.HORIZONTAL, 142 | variable=self.varClipSkip, 143 | takefocus=True, 144 | ) 145 | self.sliClipSkip.pack(UiPack.sli) 146 | 147 | self.frmClipScaleGuidanceScale.pack(UiPack.frm) 148 | 149 | def initPromptFixedRatioHalfVaeXFormers(self): 150 | self.frmPromptFixedRatioHalfVaeXFormers = ttk.Frame(self.frm) 151 | 152 | self.lblPromptFixedRatio = ttk.Label( 153 | self.frmPromptFixedRatioHalfVaeXFormers, 154 | text=L10n.get("generate_prompt_fixed_ratio"), 155 | ) 156 | self.lblPromptFixedRatio.pack(UiPack.lbl) 157 | 158 | self.varPromptFixedRatio = tk.DoubleVar(value=0.5) 159 | self.sliPromptFixedRatio = tk.Scale( 160 | self.frmPromptFixedRatioHalfVaeXFormers, 161 | from_=0.0, 162 | to=1.0, 163 | resolution=0.1, 164 | orient=tk.HORIZONTAL, 165 | variable=self.varPromptFixedRatio, 166 | takefocus=True, 167 | ) 168 | self.sliPromptFixedRatio.pack(UiPack.sli) 169 | 170 | self.varUseLcm = tk.BooleanVar(value=False) 171 | self.chkUseLcm = tk.Checkbutton( 172 | self.frmPromptFixedRatioHalfVaeXFormers, 173 | text=L10n.get("generate_use_lcm"), 174 | variable=self.varUseLcm, 175 | ) 176 | self.chkUseLcm.pack(UiPack.chk) 177 | 178 | self.varUseHighresFix = tk.BooleanVar(value=False) 179 | self.chkUseHighresFix = tk.Checkbutton( 180 | self.frmPromptFixedRatioHalfVaeXFormers, 181 | text=L10n.get("generate_use_highres_fix"), 182 | variable=self.varUseHighresFix, 183 | ) 184 | self.chkUseHighresFix.pack(UiPack.chk) 185 | 186 | self.varUseHalfVae = tk.BooleanVar(value=False) 187 | self.chkUseHalfVae = tk.Checkbutton( 188 | self.frmPromptFixedRatioHalfVaeXFormers, 189 | text=L10n.get("generate_use_half_vae"), 190 | variable=self.varUseHalfVae, 191 | ) 192 | self.chkUseHalfVae.pack(UiPack.chk) 193 | 194 | self.varUseXFormers = tk.BooleanVar(value=False) 195 | self.chkUseXFormers = tk.Checkbutton( 196 | self.frmPromptFixedRatioHalfVaeXFormers, 197 | text=L10n.get("generate_use_x_formers"), 198 | variable=self.varUseXFormers, 199 | ) 200 | self.chkUseXFormers.pack(UiPack.chk) 201 | 202 | self.frmPromptFixedRatioHalfVaeXFormers.pack(UiPack.frm) 203 | 204 | @classmethod 205 | def loadConfig(self): 206 | values = ",".join(GenerateForm.schedulerValues) 207 | values = Config.get("ui", "scheduler_values", values) 208 | GenerateForm.schedulerValues = [s.strip() for s in values.split(",")] 209 | 210 | names = ",".join(GenerateForm.schedulerNames) 211 | names = Config.get("ui", "scheduler_names", names) 212 | GenerateForm.schedulerNames = [s.strip() for s in names.split(",")] 213 | 214 | GenerateForm.maxSteps = Config.getInt("ui", "max_steps", GenerateForm.maxSteps) 215 | 216 | def storeConfig(self): 217 | Config.set("ui", "scheduler_values", ",".join(GenerateForm.schedulerValues)) 218 | Config.set("ui", "scheduler_names", ",".join(GenerateForm.schedulerNames)) 219 | Config.set("ui", "max_steps", GenerateForm.maxSteps) 220 | 221 | def setDarkTheme(self, colors): 222 | self.sliContext.configure(colors["sli"]) 223 | self.sliSteps.configure(colors["sli"]) 224 | self.sliClipSkip.configure(colors["sli"]) 225 | self.sliGuidanceScale.configure(colors["sli"]) 226 | self.sliPromptFixedRatio.configure(colors["sli"]) 227 | self.chkUseLcm.configure(colors["chk"]) 228 | self.chkUseHighresFix.configure(colors["chk"]) 229 | self.chkUseHalfVae.configure(colors["chk"]) 230 | self.chkUseXFormers.configure(colors["chk"]) 231 | -------------------------------------------------------------------------------- /editor/src/ui_input.py: -------------------------------------------------------------------------------- 1 | from config import Config 2 | from l10n import L10n 3 | import tkinter as tk 4 | import tkinter.ttk as ttk 5 | from tkinter import scrolledtext 6 | from ui_const import Ui, UiPack 7 | from task import Task 8 | 9 | 10 | class InputForm: 11 | initH = 300 12 | minH = 250 13 | 14 | def __init__(self, parent): 15 | self.frm = ttk.Frame(parent) 16 | 17 | self.frmTool = ttk.Frame(self.frm) 18 | 19 | self.initGenerate(self.frmTool) 20 | self.sep0 = ttk.Separator(self.frmTool) 21 | self.sep0.pack(UiPack.sep) 22 | 23 | self.initPreview(self.frmTool) 24 | self.sep1 = ttk.Separator(self.frmTool) 25 | self.sep1.pack(UiPack.sep) 26 | 27 | self.initTask(self.frmTool) 28 | 29 | self.frmTool.pack(fill=tk.Y, side=tk.RIGHT) 30 | 31 | self.txtInput = scrolledtext.ScrolledText(self.frm, undo=True, maxundo=-1) 32 | self.txtInput.configure(Ui.cfgTxtArea) 33 | self.txtInput.pack(UiPack.txt) 34 | 35 | self.frm.pack(fill=tk.BOTH, expand=True) 36 | 37 | parent.add( 38 | self.frm, height=InputForm.initH, minsize=InputForm.minH, stretch="always" 39 | ) 40 | 41 | self.initCtxMenu() 42 | 43 | def initGenerate(self, parent): 44 | self.frmGenerate = ttk.Frame(parent) 45 | self.btnGenerate = ttk.Button(self.frmGenerate, text=L10n.get("generate_anime")) 46 | self.btnGenerate.pack(UiPack.btn, fill=tk.X, expand=True) 47 | 48 | self.btnSeedGacha = ttk.Button(self.frmGenerate, text=L10n.get("seed_gacha")) 49 | self.btnSeedGacha.pack(UiPack.btn, fill=tk.X, expand=True) 50 | 51 | self.frmGenerate.pack(UiPack.frm) 52 | 53 | def initPreview(self, parent): 54 | self.frmPreview = ttk.Frame(parent) 55 | self.btnPreview = ttk.Button(self.frmPreview, text=L10n.get("preview")) 56 | self.btnPreview.pack(UiPack.btn) 57 | 58 | self.varUpscale = tk.BooleanVar(value=True) 59 | self.chkUpscale = tk.Checkbutton( 60 | self.frmPreview, text=L10n.get("preview_upscale"), variable=self.varUpscale 61 | ) 62 | self.chkUpscale.pack(UiPack.chk) 63 | 64 | # self.varInterpolation = tk.BooleanVar(value=True) 65 | # self.chkInterpolation = tk.Checkbutton( 66 | # self.frmPreview, 67 | # text=L10n.get("preview_interpolation"), 68 | # variable=self.varInterpolation, 69 | # ) 70 | # self.chkInterpolation.pack(UiPack.chk) 71 | 72 | self.frmPreview.pack(UiPack.frm) 73 | 74 | self.frmStart = ttk.Frame(parent) 75 | self.varStart = tk.IntVar(value=0) 76 | self.lblStart = ttk.Label(self.frmStart, text=L10n.get("preview_start_frame")) 77 | self.lblStart.pack(UiPack.lbl) 78 | self.sliStart = tk.Scale( 79 | self.frmStart, 80 | from_=0, 81 | to=600, 82 | resolution=10, 83 | orient=tk.HORIZONTAL, 84 | variable=self.varStart, 85 | takefocus=True, 86 | ) 87 | self.sliStart.pack(UiPack.sli) 88 | self.frmStart.pack(UiPack.frm) 89 | 90 | self.frmLength = ttk.Frame(parent) 91 | self.varLength = tk.IntVar(value=0) 92 | self.lblLength = ttk.Label(self.frmLength, text=L10n.get("preview_length")) 93 | self.lblLength.pack(UiPack.lbl) 94 | self.sliLength = tk.Scale( 95 | self.frmLength, 96 | from_=0, 97 | to=600, 98 | resolution=10, 99 | orient=tk.HORIZONTAL, 100 | variable=self.varLength, 101 | takefocus=True, 102 | ) 103 | self.sliLength.pack(UiPack.sli) 104 | self.frmLength.pack(UiPack.frm) 105 | 106 | def initTask(self, parent): 107 | self.frmTask = ttk.Frame(parent) 108 | 109 | self.varForever = tk.BooleanVar(value=False) 110 | self.chkForever = tk.Checkbutton( 111 | self.frmTask, text=L10n.get("task_forever"), variable=self.varForever 112 | ) 113 | self.chkForever.pack(UiPack.chk) 114 | 115 | self.varPauseByError = tk.BooleanVar(value=True) 116 | self.chkPauseByError = tk.Checkbutton( 117 | self.frmTask, 118 | text=L10n.get("task_pause_by_error"), 119 | variable=self.varPauseByError, 120 | ) 121 | self.chkPauseByError.pack(UiPack.chk) 122 | 123 | self.frmTask.pack(UiPack.frm) 124 | 125 | self.varTask = tk.StringVar() 126 | Task.queueInfoChangedEvent.append(lambda queue: self.varTask.set(queue)) 127 | self.lstTask = tk.Listbox(self.frmTool, listvariable=self.varTask) 128 | self.lstTask.pack(fill=tk.BOTH, expand=True, padx=Ui.padX, pady=Ui.padY) 129 | 130 | def initCtxMenu(self): 131 | self.mnuInTxt = tk.Menu(self.txtInput, tearoff=False) 132 | self.mnuInTxt.add_command( 133 | label=L10n.get("clear"), 134 | command=lambda: self.txtInput.delete("1.0", tk.END), 135 | ) 136 | self.txtInput.bind( 137 | "", lambda e: self.mnuInTxt.post(e.x_root, e.y_root) 138 | ) 139 | 140 | @classmethod 141 | def loadConfig(cls): 142 | InputForm.initH = Config.get("ui_size", "input_h", fallback=InputForm.initH) 143 | 144 | def storeConfig(self): 145 | input_h = self.frm.winfo_height() 146 | if input_h != 1: 147 | Config.set("ui_size", "input_h", input_h) 148 | 149 | def setDarkTheme(self, colors): 150 | self.txtInput.configure(colors["txt"]) 151 | self.chkUpscale.configure(colors["chk"]) 152 | # self.chkInterpolation.configure(colors["chk"]) 153 | self.sliStart.configure(colors["sli"]) 154 | self.sliLength.configure(colors["sli"]) 155 | self.lstTask.configure(colors["lst"]) 156 | self.chkForever.configure(colors["chk"]) 157 | self.chkPauseByError.configure(colors["chk"]) 158 | -------------------------------------------------------------------------------- /editor/src/ui_ip_adapter.py: -------------------------------------------------------------------------------- 1 | import os, subprocess 2 | import tkinter as tk 3 | import tkinter.ttk as ttk 4 | from const import Path 5 | from l10n import L10n 6 | from config import Config 7 | from ui_const import UiPack 8 | 9 | 10 | class IpAdapterForm: 11 | def __init__(self, parent): 12 | self.frm = ttk.Frame(parent) 13 | 14 | self.initFlags() 15 | self.initScale() 16 | self.initImageDir() 17 | self.initInfo() 18 | 19 | parent.add(self.frm, text=L10n.get("ip_adapter_tab")) 20 | 21 | def initFlags(self): 22 | self.frmFlags = ttk.Frame(self.frm) 23 | 24 | self.varUse = tk.BooleanVar() 25 | self.chkUse = tk.Checkbutton( 26 | self.frmFlags, 27 | text=L10n.get("ip_adapter_use"), 28 | variable=self.varUse, 29 | ) 30 | self.chkUse.pack(UiPack.chk) 31 | 32 | self.varUsePlus = tk.BooleanVar() 33 | self.chkUsePlus = tk.Checkbutton( 34 | self.frmFlags, 35 | text=L10n.get("ip_adapter_use_plus"), 36 | variable=self.varUsePlus, 37 | ) 38 | self.chkUsePlus.pack(UiPack.chk) 39 | 40 | self.varUsePlusFace = tk.BooleanVar() 41 | self.chkUsePlusFace = tk.Checkbutton( 42 | self.frmFlags, 43 | text=L10n.get("ip_adapter_use_plus_face"), 44 | variable=self.varUsePlusFace, 45 | ) 46 | self.chkUsePlusFace.pack(UiPack.chk) 47 | 48 | self.frmFlags.pack(UiPack.frm) 49 | 50 | def initScale(self): 51 | self.frmScale = ttk.Frame(self.frm) 52 | 53 | self.lblScale = ttk.Label(self.frmScale, text=L10n.get("ip_adapter_scale")) 54 | self.lblScale.pack(UiPack.lbl) 55 | 56 | self.varScale = tk.DoubleVar(value=0.5) 57 | self.sliScale = tk.Scale( 58 | self.frmScale, 59 | from_=0.0, 60 | to=1.0, 61 | resolution=0.05, 62 | orient=tk.HORIZONTAL, 63 | variable=self.varScale, 64 | takefocus=True, 65 | ) 66 | self.sliScale.pack(UiPack.sli) 67 | 68 | self.frmScale.pack(UiPack.frm) 69 | 70 | def initImageDir(self): 71 | self.frmImageDir = ttk.Frame(self.frm) 72 | 73 | self.lblImageDir = ttk.Label( 74 | self.frmImageDir, text=L10n.get("ip_adapter_image_dir") 75 | ) 76 | self.lblImageDir.pack(UiPack.lbl) 77 | 78 | self.varImageDir = tk.StringVar() 79 | self.cmbImageDir = ttk.Combobox( 80 | self.frmImageDir, 81 | textvariable=self.varImageDir, 82 | state="readonly", 83 | postcommand=self.listImageDirValues, 84 | width=20, 85 | height=20, 86 | ) 87 | self.cmbImageDir.pack(UiPack.cmb, fill=tk.X, expand=True) 88 | 89 | self.btnImageDirExplorer = ttk.Button( 90 | self.frmImageDir, 91 | text=L10n.get("ip_adapter_image_dir_explorer"), 92 | command=lambda: subprocess.run(["explorer", Path.ipAdapter]), 93 | ) 94 | self.btnImageDirExplorer.pack(UiPack.btn) 95 | 96 | self.frmImageDir.pack(UiPack.frm) 97 | 98 | def listImageDirValues(self): 99 | subDirs = [f.name for f in os.scandir(Path.ipAdapter) if f.is_dir()] 100 | self.cmbImageDir.configure(values=subDirs) 101 | 102 | def initInfo(self): 103 | self.frmInfo = ttk.Frame(self.frm) 104 | 105 | self.lblIpAdapterInfo = ttk.Label( 106 | self.frmInfo, text=L10n.get("ip_adapter_info") 107 | ) 108 | self.lblIpAdapterInfo.pack(UiPack.lbl) 109 | 110 | self.frmInfo.pack(UiPack.frm) 111 | 112 | def setDarkTheme(self, colors): 113 | self.chkUse.configure(colors["chk"]) 114 | self.chkUsePlus.configure(colors["chk"]) 115 | self.chkUsePlusFace.configure(colors["chk"]) 116 | self.sliScale.configure(colors["sli"]) 117 | -------------------------------------------------------------------------------- /editor/src/ui_menu.py: -------------------------------------------------------------------------------- 1 | import json 2 | import tkinter as tk 3 | import tkinter.ttk as ttk 4 | from const import Path 5 | from l10n import L10n 6 | from ui_const import Ui, UiPack 7 | 8 | 9 | class Menu: 10 | def __init__(self, parent): 11 | self.menuBar = tk.Menu(parent) 12 | parent.config(menu=self.menuBar) 13 | 14 | self.initFileMenu() 15 | self.initAnimeMenu() 16 | self.initFolderMenu() 17 | self.initToolMenu() 18 | self.initDownloadMenu() 19 | self.initSettingMenu() 20 | self.initHelpMenu() 21 | 22 | def initFileMenu(self): 23 | self.fileMenu = tk.Menu(self.menuBar, tearoff=False) 24 | self.menuBar.add_cascade(label=L10n.get("m_file"), menu=self.fileMenu) 25 | self.fileMenu.add_command(label=L10n.get("m_file_new")) 26 | self.fileMenu.add_command(label=L10n.get("m_file_open")) 27 | self.fileMenu.add_command(label=L10n.get("m_file_save")) 28 | self.fileMenu.add_command(label=L10n.get("m_file_save_as")) 29 | self.fileMenu.add_separator() 30 | self.fileMenu.add_command(label=L10n.get("m_file_exit")) 31 | 32 | def initAnimeMenu(self): 33 | self.animeMenu = tk.Menu(self.menuBar, tearoff=False) 34 | self.menuBar.add_cascade(label=L10n.get("m_anime"), menu=self.animeMenu) 35 | 36 | self.animeMenu.add_command(label=L10n.get("m_anime_preview")) 37 | self.animeMenu.add_command(label=L10n.get("m_anime_seed_gacha")) 38 | self.animeMenu.add_command(label=L10n.get("m_anime_generate")) 39 | self.animeMenu.add_separator() 40 | self.animeMenu.add_command(label=L10n.get("m_anime_upscale")) 41 | self.animeMenu.add_command(label=L10n.get("m_anime_upscale_config")) 42 | 43 | def initFolderMenu(self): 44 | self.folderMenu = tk.Menu(self.menuBar, tearoff=False) 45 | self.menuBar.add_cascade(label=L10n.get("m_folder"), menu=self.folderMenu) 46 | 47 | self.folderMenu.add_command(label=L10n.get("m_output_folder")) 48 | 49 | self.promptTravelFolderMenu = tk.Menu(self.folderMenu, tearoff=False) 50 | self.folderMenu.add_cascade( 51 | label=L10n.get("m_prompt_travel_folder"), menu=self.promptTravelFolderMenu 52 | ) 53 | 54 | self.promptTravelFolderMenu.add_command(label=L10n.get("m_sd_model_folder")) 55 | self.promptTravelFolderMenu.add_command( 56 | label=L10n.get("m_motion_module_folder") 57 | ) 58 | self.promptTravelFolderMenu.add_command(label=L10n.get("m_lora_folder")) 59 | self.promptTravelFolderMenu.add_command(label=L10n.get("m_embeddings_folder")) 60 | self.promptTravelFolderMenu.add_command(label=L10n.get("m_wildcard_folder")) 61 | 62 | self.folderMenu.add_separator() 63 | self.promptTravelOutputFolderMenu = tk.Menu(self.folderMenu, tearoff=False) 64 | self.folderMenu.add_cascade( 65 | label=L10n.get("m_prompt_travel_output_folder"), 66 | menu=self.promptTravelOutputFolderMenu, 67 | ) 68 | 69 | self.promptTravelOutputFolderMenu.add_command( 70 | label=L10n.get("m_prompt_travel_generate_folder") 71 | ) 72 | self.promptTravelOutputFolderMenu.add_command( 73 | label=L10n.get("m_prompt_travel_tile_folder") 74 | ) 75 | self.promptTravelOutputFolderMenu.add_command( 76 | label=L10n.get("m_prompt_travel_refine_folder") 77 | ) 78 | 79 | self.folderMenu.add_command(label=L10n.get("m_temp_folder")) 80 | self.folderMenu.add_command(label=L10n.get("m_log_folder")) 81 | 82 | def initToolMenu(self): 83 | self.toolMenu = tk.Menu(self.menuBar, tearoff=False) 84 | self.menuBar.add_cascade(label=L10n.get("m_tool"), menu=self.toolMenu) 85 | 86 | self.toolMenu.add_command(label=L10n.get("m_tool_convert_lora")) 87 | 88 | self.toolMenu.add_command(label=L10n.get("m_tool_easy_leco")) 89 | 90 | def initDownloadMenu(self): 91 | self.downloadMenuData = "" 92 | with open(Path.downloadMenu, "r", encoding="utf-8-sig") as f: 93 | self.downloadMenuData = json.load(f) 94 | 95 | self.downloadMenu = tk.Menu(self.menuBar, tearoff=False) 96 | self.menuBar.add_cascade(label=L10n.get("m_download"), menu=self.downloadMenu) 97 | 98 | def initDlMenu(category): 99 | dlMenu = tk.Menu(self.downloadMenu, tearoff=False) 100 | self.downloadMenu.add_cascade( 101 | label=L10n.get(f"m_dl_{category}"), menu=dlMenu 102 | ) 103 | 104 | for name, data in self.downloadMenuData[category].items(): 105 | label = f'({data["tag"]}) {name}' if "tag" in data else name 106 | data["menu"] = dlMenu 107 | data["checked"] = tk.BooleanVar(value=True) 108 | dlMenu.add_checkbutton(label=label, variable=data["checked"]) 109 | return dlMenu 110 | 111 | self.dlModel = initDlMenu("model") 112 | self.dlLora = initDlMenu("lora") 113 | self.dlMotionModule = initDlMenu("motion_module") 114 | self.dlMotionModule = initDlMenu("motion_lora") 115 | self.dlVae = initDlMenu("vae") 116 | self.dlEmbedding = initDlMenu("embedding") 117 | 118 | def initSettingMenu(self): 119 | self.settingMenu = tk.Menu(self.menuBar, tearoff=False) 120 | self.menuBar.add_cascade(label=L10n.get("m_setting"), menu=self.settingMenu) 121 | 122 | self.settingMenu.add_command(label=L10n.get("m_set_default_prompt")) 123 | self.settingMenu.add_command(label=L10n.get("m_set_default_setting")) 124 | self.settingMenu.add_command(label=L10n.get("m_set_change_light_dark")) 125 | self.settingMenu.add_separator() 126 | self.settingMenu.add_command(label=L10n.get("m_set_open_ini_file")) 127 | self.settingMenu.add_command(label=L10n.get("m_set_open_default_prompt_file")) 128 | self.settingMenu.add_command( 129 | label=L10n.get("m_set_open_prompt_travel_template_file") 130 | ) 131 | 132 | def initHelpMenu(self): 133 | self.helpMenu = tk.Menu(self.menuBar, tearoff=False) 134 | self.menuBar.add_cascade(label=L10n.get("m_help"), menu=self.helpMenu) 135 | 136 | self.helpMenu.add_command(label=L10n.get("m_prompt_help")) 137 | 138 | self.helpMenu.add_command(label=L10n.get("m_github")) 139 | 140 | self.helpMenu.add_separator() 141 | self.referenceMenu = tk.Menu(self.helpMenu, tearoff=False) 142 | self.helpMenu.add_cascade( 143 | label=L10n.get("m_reference"), menu=self.referenceMenu 144 | ) 145 | 146 | self.referenceMenu.add_command(label="AnimateDiff prompt travel") 147 | self.referenceMenu.add_command(label="sd-scripts") 148 | self.referenceMenu.add_command(label="Codex FFmpeg") 149 | self.referenceMenu.add_command(label="Practical-RIFE") 150 | self.referenceMenu.add_command(label="NudeNet") 151 | -------------------------------------------------------------------------------- /editor/src/ui_output.py: -------------------------------------------------------------------------------- 1 | from config import Config 2 | from l10n import L10n 3 | import tkinter as tk 4 | import tkinter.ttk as ttk 5 | from tkinter import scrolledtext 6 | from ui_const import Ui, UiPack 7 | 8 | 9 | class OutputForm: 10 | initH = 100 11 | minH = 82 12 | 13 | def __init__(self, parent): 14 | self.ntb = ttk.Notebook(parent) 15 | parent.add( 16 | self.ntb, height=OutputForm.initH, minsize=OutputForm.minH, stretch="never" 17 | ) 18 | 19 | self.txtUser = scrolledtext.ScrolledText(self.ntb, state=tk.DISABLED) 20 | self.txtUser.configure(Ui.cfgTxtArea) 21 | self.txtUser.pack(UiPack.txt) 22 | self.ntb.add(self.txtUser, text=L10n.get("user_log_tab")) 23 | 24 | self.txtSys = scrolledtext.ScrolledText(self.ntb, state=tk.DISABLED) 25 | self.txtSys.configure(Ui.cfgTxtArea) 26 | self.txtSys.pack(UiPack.txt) 27 | self.ntb.add(self.txtSys, text=L10n.get("system_log_tab")) 28 | 29 | # self.ntb.select(1) # select system log tab 30 | 31 | self.initCtxMenu() 32 | 33 | def initCtxMenu(self): 34 | self.mnuUser = tk.Menu(self.txtUser, tearoff=False) 35 | self.mnuUser.add_command( 36 | label=L10n.get("clear"), command=lambda: self.txtUser.delete("1.0", tk.END) 37 | ) 38 | 39 | self.txtUser.bind("", lambda e: self.mnuUser.post(e.x_root, e.y_root)) 40 | 41 | self.mnuSys = tk.Menu(self.txtSys, tearoff=False) 42 | self.mnuSys.add_command( 43 | label=L10n.get("clear"), command=lambda: self.txtSys.delete("1.0", tk.END) 44 | ) 45 | 46 | self.txtSys.bind("", lambda e: self.mnuSys.post(e.x_root, e.y_root)) 47 | 48 | @classmethod 49 | def loadConfig(self): 50 | OutputForm.initH = Config.get("ui_size", "output_h", OutputForm.initH) 51 | 52 | def storeConfig(self): 53 | output_h = self.ntb.winfo_height() 54 | if output_h != 1: 55 | Config.set("ui_size", "output_h", output_h) 56 | 57 | def setDarkTheme(self, colors): 58 | self.txtUser.configure(colors["selIns"]) 59 | self.txtSys.configure(colors["selIns"]) 60 | -------------------------------------------------------------------------------- /editor/src/ui_preview.py: -------------------------------------------------------------------------------- 1 | from config import Config 2 | from l10n import L10n 3 | import tkinter as tk 4 | import tkinter.ttk as ttk 5 | from tkinter import scrolledtext 6 | from ui_const import Ui, UiPack 7 | 8 | 9 | class PreviewForm: 10 | def __init__(self, parent): 11 | self.frm = ttk.Frame(parent) 12 | 13 | self.initOptions() 14 | 15 | self.txtPrev = scrolledtext.ScrolledText(self.frm, state=tk.DISABLED) 16 | self.txtPrev.configure(Ui.cfgTxtArea) 17 | self.txtPrev.pack(UiPack.txt) 18 | 19 | parent.add(self.frm, text=L10n.get("preview_tab")) 20 | 21 | def initOptions(self): 22 | self.frmOptions = ttk.Frame(self.frm) 23 | 24 | self.varShowKeyframe = tk.BooleanVar(value=True) 25 | self.chkShowKeyframe = tk.Checkbutton( 26 | self.frmOptions, 27 | text=L10n.get("preview_show_keyframe"), 28 | variable=self.varShowKeyframe, 29 | ) 30 | self.chkShowKeyframe.pack(UiPack.chk) 31 | 32 | self.varShowHeaderFooter = tk.BooleanVar(value=True) 33 | self.chkShowHeaderFooter = tk.Checkbutton( 34 | self.frmOptions, 35 | text=L10n.get("preview_show_header_footer"), 36 | variable=self.varShowHeaderFooter, 37 | ) 38 | self.chkShowHeaderFooter.pack(UiPack.chk) 39 | 40 | self.varShowKeyframe.trace_add("write", self.updateShowKeyframe) 41 | 42 | self.varShowAnime = tk.BooleanVar(value=True) 43 | self.chkShowAnime = tk.Checkbutton( 44 | self.frmOptions, 45 | text=L10n.get("preview_show_anime"), 46 | variable=self.varShowAnime, 47 | ) 48 | self.chkShowAnime.pack(UiPack.chk) 49 | 50 | self.frmOptions.pack(UiPack.frm, side=tk.BOTTOM) 51 | 52 | def updateShowKeyframe(self, *args): 53 | if self.varShowKeyframe.get(): 54 | self.chkShowHeaderFooter.configure(state=tk.NORMAL) 55 | else: 56 | self.chkShowHeaderFooter.configure(state=tk.DISABLED) 57 | 58 | def setDarkTheme(self, colors): 59 | self.chkShowKeyframe.configure(colors["chk"]) 60 | self.chkShowHeaderFooter.configure(colors["chk"]) 61 | self.chkShowAnime.configure(colors["chk"]) 62 | self.txtPrev.configure(colors["txt"]) 63 | -------------------------------------------------------------------------------- /sample/Gacha-L120-C16-W448-H512.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "", 3 | "path": "models/sd/nadenadesitai_v10.safetensors", 4 | "motion_module": "models/motion-module/mm_sd_v15_v2.ckpt", 5 | "compile": false, 6 | "seed": [ 7 | -1 8 | ], 9 | "scheduler": "k_dpmpp_sde", 10 | "steps": 20, 11 | "guidance_scale": 8, 12 | "clip_skip": 2, 13 | "head_prompt": "(crowds, street of Akihabara Tokyo: 1.2), black short hair, black cat ears, black eyes", 14 | "prompt_map": { 15 | "0": "closed eyes, portrait, maid headdress, edwardian maid outfit", 16 | "10": "portrait, upper body, maid headdress, edwardian maid outfit", 17 | "20": "upper body, dancing, maid headdress, edwardian maid outfit, happy", 18 | "30": "upper body, cowboy shot, dancing, maid headdress, edwardian maid outfit, happy", 19 | "40": "cowboy shot, dancing, maid headdress, maid outfit, happy", 20 | "50": "dancing, cowboy shot, full body, maid headdress, miniskirt maid outfit, happy", 21 | "60": "dancing, full body, maid headdress, miniskirt maid outfit, frilled, happy", 22 | "70": "night, dancing, full body, cowboy shot, idol, frilled, ribbon, tiara, happy", 23 | "80": "night, dancing, cowboy shot, idol, magical girl, angel wings, frilled, ribbon, tiara, happy", 24 | "90": "night, stylish pose, explosion, idol, magical girl, angel wings, frilled, ribbon, tiara, happy", 25 | "100": "night, stylish pose, explosion, idol, magical girl, angel wings, frilled, ribbon, tiara", 26 | "110": "night, upper body, maid headdress, edwardian maid outfit" 27 | }, 28 | "tail_prompt": "1girl, solo, black choker, hair between eyes", 29 | "n_prompt": [ 30 | "(worst quality, low quality:1.4), cat, cow" 31 | ], 32 | "lora_map": {}, 33 | "upscale_config": { 34 | "scheduler": "k_dpmpp_sde", 35 | "steps": 15, 36 | "strength": 0.5, 37 | "guidance_scale": 8, 38 | "controlnet_tile": { 39 | "enable": true, 40 | "use_preprocessor": false, 41 | "controlnet_conditioning_scale": 1.0, 42 | "guess_mode": false, 43 | "control_guidance_start": 0.0, 44 | "control_guidance_end": 1.0 45 | } 46 | }, 47 | "output": { 48 | "format": "mp4", 49 | "fps": 10, 50 | "encode_param": { 51 | "crf": 20 52 | } 53 | } 54 | } -------------------------------------------------------------------------------- /sample/Gacha.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | call "%~dp0..\Generate.bat" "%~dp0Gacha-L120-C16-W448-H512.json" 3 | -------------------------------------------------------------------------------- /sample/Oiyami-L60-C16-W512-H384-T768.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "", 3 | "path": "models/sd/onigiriMix_v10Clearvae.safetensors", 4 | "motion_module": "models/motion-module/mm_sd_v15_v2.ckpt", 5 | "compile": false, 6 | "seed": [ 7 | -1 8 | ], 9 | "scheduler": "k_dpmpp_sde", 10 | "steps": 20, 11 | "guidance_scale": 8, 12 | "clip_skip": 2, 13 | "prompt_fixed_ratio": 0.0, 14 | "head_prompt": "", 15 | "prompt_map": { 16 | "0": "美味しいヤミー❗️✨🤟😁👍✨⚡️感謝❗️🙌✨🙌✨🙌感謝❗️🙌✨🙌✨🙌またいっぱい食べたいな❗️🥓🥩🍗🍖😋🍖🍴✨デリシャッ‼️🙏✨シャ‼️🙏✨ シャ‼️🙏✨ シャ‼️🙏✨ シャ‼️🙏✨ シャ‼️🙏✨ シャッッ‼😁🙏✨😁🙏✨😁🙏✨ハッピー🌟スマイル❗️❗️❗💥✨👉😁👈✨💥" 17 | }, 18 | "tail_prompt": "", 19 | "n_prompt": [ 20 | "(worst quality, low quality:1.4)" 21 | ], 22 | "lora_map": {}, 23 | "upscale_config": { 24 | "scheduler": "k_dpmpp_sde", 25 | "steps": 15, 26 | "strength": 0.5, 27 | "guidance_scale": 8, 28 | "controlnet_tile": { 29 | "enable": true, 30 | "use_preprocessor": false, 31 | "controlnet_conditioning_scale": 1.0, 32 | "guess_mode": false, 33 | "control_guidance_start": 0.0, 34 | "control_guidance_end": 1.0 35 | } 36 | }, 37 | "output": { 38 | "format": "mp4", 39 | "fps": 10, 40 | "encode_param": { 41 | "crf": 20 42 | } 43 | } 44 | } -------------------------------------------------------------------------------- /sample/Oiyami.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | call "%~dp0..\Generate.bat" "%~dp0Oiyami-L60-C16-W512-H384-T768.json" 3 | echo Oiyami: https://www.google.com/search?q=%%E7%%BE%%8E%%E5%%91%%B3%%E3%%81%%97%%E3%%81%%84%%E3%%83%%A4%%E3%%83%%9F%%E3%%83%%BC%%E6%%84%%9F%%E8%%AC%%9D%%E6%%84%%9F%%E8%%AC%%9D 4 | pause 5 | -------------------------------------------------------------------------------- /sample/UpscaledGacha-L120-C16-W448-H512-T768.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "", 3 | "path": "models/sd/nadenadesitai_v10.safetensors", 4 | "motion_module": "models/motion-module/mm_sd_v15_v2.ckpt", 5 | "compile": false, 6 | "seed": [ 7 | -1 8 | ], 9 | "scheduler": "k_dpmpp_sde", 10 | "steps": 20, 11 | "guidance_scale": 8, 12 | "clip_skip": 2, 13 | "head_prompt": "(crowds, street of Akihabara Tokyo: 1.2), black short hair, black cat ears, black eyes", 14 | "prompt_map": { 15 | "0": "closed eyes, portrait, maid headdress, edwardian maid outfit", 16 | "10": "portrait, upper body, maid headdress, edwardian maid outfit", 17 | "20": "upper body, dancing, maid headdress, edwardian maid outfit, happy", 18 | "30": "upper body, cowboy shot, dancing, maid headdress, edwardian maid outfit, happy", 19 | "40": "cowboy shot, dancing, maid headdress, maid outfit, happy", 20 | "50": "dancing, cowboy shot, full body, maid headdress, miniskirt maid outfit, happy", 21 | "60": "dancing, full body, maid headdress, miniskirt maid outfit, frilled, happy", 22 | "70": "night, dancing, full body, cowboy shot, idol, frilled, ribbon, tiara, happy", 23 | "80": "night, dancing, cowboy shot, idol, magical girl, angel wings, frilled, ribbon, tiara, happy", 24 | "90": "night, stylish pose, explosion, idol, magical girl, angel wings, frilled, ribbon, tiara, happy", 25 | "100": "night, stylish pose, explosion, idol, magical girl, angel wings, frilled, ribbon, tiara", 26 | "110": "night, upper body, maid headdress, edwardian maid outfit" 27 | }, 28 | "tail_prompt": "1girl, solo, black choker, hair between eyes", 29 | "n_prompt": [ 30 | "(worst quality, low quality:1.4), cat, cow" 31 | ], 32 | "lora_map": {}, 33 | "upscale_config": { 34 | "scheduler": "k_dpmpp_sde", 35 | "steps": 15, 36 | "strength": 0.5, 37 | "guidance_scale": 8, 38 | "controlnet_tile": { 39 | "enable": true, 40 | "use_preprocessor": false, 41 | "controlnet_conditioning_scale": 1.0, 42 | "guess_mode": false, 43 | "control_guidance_start": 0.0, 44 | "control_guidance_end": 1.0 45 | } 46 | }, 47 | "output": { 48 | "format": "mp4", 49 | "fps": 10, 50 | "encode_param": { 51 | "crf": 20 52 | } 53 | } 54 | } -------------------------------------------------------------------------------- /sample/UpscaledGacha.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | call "%~dp0..\Generate.bat" "%~dp0UpscaledGacha-L120-C16-W448-H512-T768.json" 3 | -------------------------------------------------------------------------------- /sample/Vram12G-L120-C16-W608-H384-T768-T1152.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "", 3 | "path": "models/sd/xxmix9realistic_v40.safetensors", 4 | "motion_module": "models/motion-module/mm_sd_v15_v2.ckpt", 5 | "compile": false, 6 | "seed": [ 7 | -1 8 | ], 9 | "scheduler": "k_dpmpp_sde", 10 | "steps": 20, 11 | "guidance_scale": 8, 12 | "clip_skip": 2, 13 | "head_prompt": "(crowds, street of Akihabara Tokyo: 1.2), black short hair, black cat ears, black eyes", 14 | "prompt_map": { 15 | "0": "closed eyes, portrait, maid headdress, edwardian maid outfit", 16 | "10": "portrait, upper body, maid headdress, edwardian maid outfit", 17 | "20": "upper body, dancing, maid headdress, edwardian maid outfit, happy", 18 | "30": "upper body, cowboy shot, dancing, maid headdress, edwardian maid outfit, happy", 19 | "40": "cowboy shot, dancing, maid headdress, maid outfit, happy", 20 | "50": "dancing, cowboy shot, full body, maid headdress, miniskirt maid outfit, happy", 21 | "60": "dancing, full body, maid headdress, miniskirt maid outfit, frilled, happy", 22 | "70": "night, dancing, full body, cowboy shot, idol, frilled, ribbon, tiara, happy", 23 | "80": "night, dancing, cowboy shot, idol, magical girl, angel wings, frilled, ribbon, tiara, happy", 24 | "90": "night, stylish pose, explosion, idol, magical girl, angel wings, frilled, ribbon, tiara, happy", 25 | "100": "night, stylish pose, explosion, idol, magical girl, angel wings, frilled, ribbon, tiara", 26 | "110": "night, upper body, maid headdress, edwardian maid outfit" 27 | }, 28 | "tail_prompt": "1girl, solo, black choker, hair between eyes", 29 | "n_prompt": [ 30 | "(worst quality, low quality:1.4), cat, cow" 31 | ], 32 | "lora_map": {}, 33 | "upscale_config": { 34 | "scheduler": "k_dpmpp_sde", 35 | "steps": 15, 36 | "strength": 0.5, 37 | "guidance_scale": 8, 38 | "controlnet_tile": { 39 | "enable": true, 40 | "use_preprocessor": false, 41 | "controlnet_conditioning_scale": 1.0, 42 | "guess_mode": false, 43 | "control_guidance_start": 0.0, 44 | "control_guidance_end": 1.0 45 | } 46 | }, 47 | "output": { 48 | "format": "mp4", 49 | "fps": 10, 50 | "encode_param": { 51 | "crf": 20 52 | } 53 | } 54 | } -------------------------------------------------------------------------------- /sample/Vram12G.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | call "%~dp0..\Generate.bat" "%~dp0Vram12G-L120-C16-W608-H384-T768-T1152.json" 3 | -------------------------------------------------------------------------------- /sample/Vram8G-L120-C16-W448-H512-T768-T1024.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "", 3 | "path": "models/sd/nadenadesitai_v10.safetensors", 4 | "motion_module": "models/motion-module/mm_sd_v15_v2.ckpt", 5 | "compile": false, 6 | "seed": [ 7 | -1 8 | ], 9 | "scheduler": "k_dpmpp_sde", 10 | "steps": 20, 11 | "guidance_scale": 8, 12 | "clip_skip": 2, 13 | "head_prompt": "(crowds, street of Akihabara Tokyo: 1.2), black short hair, black cat ears, black eyes", 14 | "prompt_map": { 15 | "0": "closed eyes, portrait, maid headdress, edwardian maid outfit", 16 | "10": "portrait, upper body, maid headdress, edwardian maid outfit", 17 | "20": "upper body, dancing, maid headdress, edwardian maid outfit, happy", 18 | "30": "upper body, cowboy shot, dancing, maid headdress, edwardian maid outfit, happy", 19 | "40": "cowboy shot, dancing, maid headdress, maid outfit, happy", 20 | "50": "dancing, cowboy shot, full body, maid headdress, miniskirt maid outfit, happy", 21 | "60": "dancing, full body, maid headdress, miniskirt maid outfit, frilled, happy", 22 | "70": "night, dancing, full body, cowboy shot, idol, frilled, ribbon, tiara, happy", 23 | "80": "night, dancing, cowboy shot, idol, magical girl, angel wings, frilled, ribbon, tiara, happy", 24 | "90": "night, stylish pose, explosion, idol, magical girl, angel wings, frilled, ribbon, tiara, happy", 25 | "100": "night, stylish pose, explosion, idol, magical girl, angel wings, frilled, ribbon, tiara", 26 | "110": "night, upper body, maid headdress, edwardian maid outfit" 27 | }, 28 | "tail_prompt": "1girl, solo, black choker, hair between eyes", 29 | "n_prompt": [ 30 | "(worst quality, low quality:1.4), cat, cow" 31 | ], 32 | "lora_map": {}, 33 | "upscale_config": { 34 | "scheduler": "k_dpmpp_sde", 35 | "steps": 15, 36 | "strength": 0.5, 37 | "guidance_scale": 8, 38 | "controlnet_tile": { 39 | "enable": true, 40 | "use_preprocessor": false, 41 | "controlnet_conditioning_scale": 1.0, 42 | "guess_mode": false, 43 | "control_guidance_start": 0.0, 44 | "control_guidance_end": 1.0 45 | } 46 | }, 47 | "output": { 48 | "format": "mp4", 49 | "fps": 10, 50 | "encode_param": { 51 | "crf": 20 52 | } 53 | } 54 | } -------------------------------------------------------------------------------- /sample/Vram8G.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | call "%~dp0..\Generate.bat" "%~dp0Vram8G-L120-C16-W448-H512-T768-T1024.json" 3 | -------------------------------------------------------------------------------- /src/Setup-ECCV2022-RIFE.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | pushd %~dp0.. 4 | if not exist ECCV2022-RIFE ( 5 | echo git clone https://github.com/megvii-research/ECCV2022-RIFE 6 | git clone https://github.com/megvii-research/ECCV2022-RIFE 7 | if %errorlevel% neq 0 ( pause & popd & exit /b %errorlevel% ) 8 | ) 9 | popd rem %~dp0.. 10 | 11 | pushd %~dp0..\ECCV2022-RIFE 12 | if not exist venv ( 13 | python -m venv venv 14 | if %errorlevel% neq 0 ( pause & popd & exit /b %errorlevel% ) 15 | ) 16 | call venv\Scripts\activate.bat 17 | 18 | echo pip install ECCV2022-RIFE venv 19 | python -m pip install -q --upgrade pip 20 | if %errorlevel% neq 0 ( pause & popd & exit /b %errorlevel% ) 21 | 22 | pip install -q torch==2.0.1+cu118 torchvision torchaudio --index-url https://download.pytorch.org/whl/cu118 23 | if %errorlevel% neq 0 ( pause & popd & exit /b %errorlevel% ) 24 | 25 | pip install -q -r requirements.txt 26 | if %errorlevel% neq 0 ( pause & popd & exit /b %errorlevel% ) 27 | 28 | pip install -q numpy==1.23.5 29 | if %errorlevel% neq 0 ( pause & popd & exit /b %errorlevel% ) 30 | 31 | call venv\Scripts\deactivate.bat 32 | 33 | 34 | if not exist train_log ( 35 | echo curl https://github.com/hzwer/Practical-RIFE/blob/main/README.md#model-list 36 | curl -Lo RifeModel.zip https://drive.google.com/uc?id=1APIzVeI-4ZZCEuIRE1m6WYfSCaOsi_7_ 37 | if %errorlevel% neq 0 ( pause & popd & exit /b %errorlevel% ) 38 | 39 | echo Unzip RifeModel.zip 40 | PowerShell -Version 5.1 -ExecutionPolicy Bypass Expand-Archive -Path RifeModel.zip -DestinationPath . 41 | if %errorlevel% neq 0 ( pause & popd & exit /b %errorlevel% ) 42 | 43 | echo del RifeModel.zip 44 | del RifeModel.zip 45 | if %errorlevel% neq 0 ( pause & popd & exit /b %errorlevel% ) 46 | ) 47 | popd rem ECCV2022-RIFE 48 | -------------------------------------------------------------------------------- /src/Setup-EasyPromptAnime.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | pushd %~dp0 3 | 4 | where findstr > nul 2>&1 5 | if %ERRORLEVEL% neq 0 ( 6 | echo [ERROR] findstr not found. Add C:\Windows\System32 to PATH. 7 | pause & popd & exit /b 1 8 | ) 9 | 10 | echo "%~dp0" | findstr /r /c:"[^a-zA-Z0-9:/\\]+" >nul 11 | if %ERRORLEVEL% equ 0 ( 12 | echo [ERROR] "%~dp0" contains non-alphanumeric characters. 13 | pause & popd & exit /b 1 14 | ) 15 | 16 | python --version | findstr "3.10." || ( 17 | echo [ERROR] Invalid python version. require 3.10.6. 18 | python --version 19 | echo Ctrl + Click: https://www.python.org/ftp/python/3.10.6/python-3.10.6-amd64.exe 20 | pause & popd & exit /b 1 21 | ) 22 | 23 | where git 24 | if %ERRORLEVEL% neq 0 ( 25 | echo [ERROR] git not found. require Git for Windows. 26 | echo Ctrl + Click: https://gitforwindows.org/ 27 | pause & popd & exit /b 1 28 | ) 29 | 30 | git clone https://github.com/Zuntan03/EasyPromptAnime 31 | if %ERRORLEVEL% neq 0 ( 32 | echo [ERROR] git clone https://github.com/Zuntan03/EasyPromptAnime 33 | echo Disable virus checking software. 34 | pause & popd & exit /b 1 35 | ) 36 | 37 | robocopy .\EasyPromptAnime\ . /s /move 38 | 39 | call src\Setup.bat 40 | if %errorlevel% neq 0 ( popd & exit /b %errorlevel% ) 41 | 42 | start cmd /c EasyPromptAnimeEditor.bat 43 | 44 | popd rem %~dp0 45 | echo The following error messages are not a problem. 46 | del "%~f0" 47 | -------------------------------------------------------------------------------- /src/Setup-Practical-RIFE.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | pushd %~dp0.. 4 | if not exist Practical-RIFE ( 5 | echo git clone https://github.com/hzwer/Practical-RIFE 6 | git clone https://github.com/hzwer/Practical-RIFE 7 | if %errorlevel% neq 0 ( pause & popd & exit /b %errorlevel% ) 8 | ) 9 | popd rem %~dp0.. 10 | 11 | pushd %~dp0..\Practical-RIFE 12 | if not exist venv ( 13 | python -m venv venv 14 | if %errorlevel% neq 0 ( pause & popd & exit /b %errorlevel% ) 15 | ) 16 | call venv\Scripts\activate.bat 17 | 18 | echo pip install Practical-RIFE venv 19 | python -m pip install -q --upgrade pip 20 | if %errorlevel% neq 0 ( pause & popd & exit /b %errorlevel% ) 21 | 22 | pip install -q torch==2.0.1+cu118 torchvision torchaudio --index-url https://download.pytorch.org/whl/cu118 23 | if %errorlevel% neq 0 ( pause & popd & exit /b %errorlevel% ) 24 | 25 | @REM pip install numpy>=1.16 tqdm>=4.35.0 sk-video>=1.1.10 opencv-python>=4.1.2 moviepy>=1.0.3 26 | pip install -q tqdm sk-video opencv-python moviepy 27 | if %errorlevel% neq 0 ( pause & popd & exit /b %errorlevel% ) 28 | 29 | @REM pip install -q -r requirements.txt 30 | @REM pip install -r requirements.txt 31 | @REM if %errorlevel% neq 0 ( pause & popd & exit /b %errorlevel% ) 32 | 33 | 34 | @REM numpy>=1.16 35 | @REM tqdm>=4.35.0 36 | @REM sk-video>=1.1.10 37 | @REM torch>=1.3.0 38 | @REM opencv-python>=4.1.2 39 | @REM moviepy>=1.0.3 40 | @REM torchvision==0.7.0 41 | 42 | pip install -q numpy==1.23.5 43 | if %errorlevel% neq 0 ( pause & popd & exit /b %errorlevel% ) 44 | 45 | call venv\Scripts\deactivate.bat 46 | 47 | 48 | if not exist train_log ( 49 | echo curl https://github.com/hzwer/Practical-RIFE/blob/main/README.md#model-list 50 | curl -Lo RifeModel.zip https://drive.google.com/uc?id=1V6yJsfZgxfx3l03k1sex3YpLdqsJbj61 51 | if %errorlevel% neq 0 ( pause & popd & exit /b %errorlevel% ) 52 | 53 | echo Unzip RifeModel.zip 54 | PowerShell -Version 5.1 -ExecutionPolicy Bypass Expand-Archive -Path RifeModel.zip -DestinationPath . 55 | if %errorlevel% neq 0 ( pause & popd & exit /b %errorlevel% ) 56 | 57 | echo del RifeModel.zip 58 | del RifeModel.zip 59 | if %errorlevel% neq 0 ( pause & popd & exit /b %errorlevel% ) 60 | ) 61 | popd rem Practical-RIFE 62 | -------------------------------------------------------------------------------- /src/Setup-animatediff-cli-prompt-travel.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | pushd %~dp0.. 4 | if not exist animatediff-cli-prompt-travel ( 5 | echo git clone https://github.com/s9roll7/animatediff-cli-prompt-travel 6 | git clone https://github.com/s9roll7/animatediff-cli-prompt-travel 7 | if %errorlevel% neq 0 ( pause & popd & exit /b %errorlevel% ) 8 | ) 9 | popd rem %~dp0.. 10 | 11 | pushd %~dp0..\animatediff-cli-prompt-travel 12 | if not exist venv ( 13 | python -m venv venv 14 | if %errorlevel% neq 0 ( pause & popd & exit /b %errorlevel% ) 15 | ) 16 | 17 | call venv\Scripts\activate.bat 18 | 19 | echo pip install animatediff-cli-prompt-travel venv 20 | python -m pip install -q --upgrade pip 21 | if %errorlevel% neq 0 ( pause & popd & exit /b %errorlevel% ) 22 | 23 | pip install -q torch==2.1.0+cu121 torchvision==0.16.0+cu121 torchaudio==2.1.0+cu121 --index-url https://download.pytorch.org/whl/cu121 24 | if %errorlevel% neq 0 ( pause & popd & exit /b %errorlevel% ) 25 | 26 | pip install -q xformers==0.0.22.post7 --index-url https://download.pytorch.org/whl/cu121 27 | if %errorlevel% neq 0 ( pause & popd & exit /b %errorlevel% ) 28 | 29 | pip install -q mediapipe pytorch_lightning 30 | if %errorlevel% neq 0 ( pause & popd & exit /b %errorlevel% ) 31 | 32 | pip install -q -e . 33 | if %errorlevel% neq 0 ( pause & popd & exit /b %errorlevel% ) 34 | 35 | @REM chcp 65001 rem stylize_mask 36 | pip install -q -e .[stylize] 37 | if %errorlevel% neq 0 ( pause & popd & exit /b %errorlevel% ) 38 | 39 | call venv\Scripts\deactivate.bat 40 | 41 | if not exist data\lora ( mkdir data\lora ) 42 | if not exist data\vae ( mkdir data\vae ) 43 | 44 | if not exist data\models\sd\real_model_N.safetensors ( 45 | echo curl https://huggingface.co/fcski/real_model_L 46 | curl -Lo data\models\sd\real_model_N.safetensors^ 47 | https://huggingface.co/fcski/real_model_L/resolve/main/real_model_N.safetensors 48 | if %errorlevel% neq 0 ( pause & popd & exit /b %errorlevel% ) 49 | timeout /t 1 /nobreak >nul 50 | ) 51 | 52 | if not exist data\models\motion-module\mm_sd_v15_v2.ckpt ( 53 | echo curl https://huggingface.co/guoyww/animatediff 54 | curl -Lo data\models\motion-module\mm_sd_v15_v2.ckpt^ 55 | https://huggingface.co/guoyww/animatediff/resolve/main/mm_sd_v15_v2.ckpt 56 | if %errorlevel% neq 0 ( pause & popd & exit /b %errorlevel% ) 57 | timeout /t 1 /nobreak >nul 58 | ) 59 | 60 | if not exist data\vae\kl-f8-anime2.ckpt ( 61 | echo curl https://huggingface.co/hakurei/waifu-diffusion-v1-4 62 | curl -Lo data\vae\kl-f8-anime2.ckpt^ 63 | https://huggingface.co/hakurei/waifu-diffusion-v1-4/resolve/main/vae/kl-f8-anime2.ckpt 64 | if %errorlevel% neq 0 ( pause & popd & exit /b %errorlevel% ) 65 | timeout /t 1 /nobreak >nul 66 | ) 67 | 68 | if not exist data\vae\vae-ft-mse-840000-ema-pruned.safetensors ( 69 | echo curl https://huggingface.co/stabilityai/sd-vae-ft-mse-original 70 | curl -Lo data\vae\vae-ft-mse-840000-ema-pruned.safetensors^ 71 | https://huggingface.co/stabilityai/sd-vae-ft-mse-original/resolve/main/vae-ft-mse-840000-ema-pruned.safetensors 72 | if %errorlevel% neq 0 ( pause & popd & exit /b %errorlevel% ) 73 | timeout /t 1 /nobreak >nul 74 | ) 75 | popd rem %~dp0..\animatediff-cli-prompt-travel 76 | -------------------------------------------------------------------------------- /src/Setup-editor.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | pushd %~dp0.. 3 | 4 | if not exist editor\venv ( 5 | python -m venv editor\venv 6 | if %errorlevel% neq 0 ( pause & popd & exit /b %errorlevel% ) 7 | ) 8 | 9 | call editor\venv\Scripts\activate.bat 10 | if %errorlevel% neq 0 ( pause & popd & exit /b %errorlevel% ) 11 | 12 | python -m pip install -q --upgrade pip 13 | if %errorlevel% neq 0 ( pause & popd & exit /b %errorlevel% ) 14 | 15 | pip install -q darkdetect tkinterdnd2 nudenet 16 | if %errorlevel% neq 0 ( pause & popd & exit /b %errorlevel% ) 17 | 18 | call editor\venv\Scripts\deactivate.bat 19 | 20 | popd rem %~dp0.. 21 | -------------------------------------------------------------------------------- /src/Setup-ffmpeg-master-latest-win64-gpl.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | pushd %~dp0.. 4 | set FFMPEG_DIR=ffmpeg-master-latest-win64-gpl 5 | if not exist %FFMPEG_DIR% ( 6 | echo curl -Lo ffmpeg.zip https://github.com/BtbN/FFmpeg-Builds/releases/download/latest/ffmpeg-master-latest-win64-gpl.zip 7 | curl -Lo ffmpeg.zip https://github.com/BtbN/FFmpeg-Builds/releases/download/latest/ffmpeg-master-latest-win64-gpl.zip 8 | if %errorlevel% neq 0 ( pause & popd & exit /b %errorlevel% ) 9 | 10 | echo Unzip ffmpeg.zip 11 | PowerShell -Version 5.1 -ExecutionPolicy Bypass Expand-Archive -Path ffmpeg.zip -DestinationPath . 12 | if %errorlevel% neq 0 ( pause & popd & exit /b %errorlevel% ) 13 | 14 | echo del ffmpeg.zip 15 | del ffmpeg.zip 16 | if %errorlevel% neq 0 ( pause & popd & exit /b %errorlevel% ) 17 | ) 18 | 19 | if not exist animatediff-cli-prompt-travel\ffmpeg.exe ( 20 | copy /Y %FFMPEG_DIR%\bin\ffmpeg.exe animatediff-cli-prompt-travel 21 | if %errorlevel% neq 0 ( pause & popd & exit /b %errorlevel% ) 22 | ) 23 | 24 | @REM if not exist ECCV2022-RIFE\ffmpeg.exe ( 25 | @REM copy /Y %FFMPEG_DIR%\bin\ffmpeg.exe ECCV2022-RIFE 26 | @REM if %errorlevel% neq 0 ( pause & popd & exit /b %errorlevel% ) 27 | @REM ) 28 | @REM if not exist ECCV2022-RIFE\ffprobe.exe ( 29 | @REM copy /Y %FFMPEG_DIR%\bin\ffprobe.exe ECCV2022-RIFE 30 | @REM if %errorlevel% neq 0 ( pause & popd & exit /b %errorlevel% ) 31 | @REM ) 32 | 33 | if not exist Practical-RIFE\ffmpeg.exe ( 34 | copy /Y %FFMPEG_DIR%\bin\ffmpeg.exe Practical-RIFE 35 | if %errorlevel% neq 0 ( pause & popd & exit /b %errorlevel% ) 36 | ) 37 | if not exist Practical-RIFE\ffprobe.exe ( 38 | copy /Y %FFMPEG_DIR%\bin\ffprobe.exe Practical-RIFE 39 | if %errorlevel% neq 0 ( pause & popd & exit /b %errorlevel% ) 40 | ) 41 | 42 | popd rem %~dp0.. 43 | -------------------------------------------------------------------------------- /src/Setup-sd-scripts-accelerate_config.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | if not exist %~dp0..\sd-scripts ( 3 | echo Not exist sd-scripts. & pause & exit /b 1 4 | ) 5 | 6 | pushd %~dp0..\sd-scripts 7 | call venv\Scripts\activate.bat 8 | if %errorlevel% neq 0 ( pause & popd & exit /b %errorlevel% ) 9 | 10 | echo ------------------------------------------------------------------------------------------------------------------------ 11 | echo This machine [Enter], No distributed training [Enter], NO [Enter], NO [Enter], NO [Enter], 12 | echo all [Enter], bf16(RTX 30X0~) [2][Enter][Enter] fp16(~RTX 20X0) [1][Enter][Enter] 13 | accelerate config 14 | if %errorlevel% neq 0 ( pause & popd & exit /b %errorlevel% ) 15 | 16 | call venv\Scripts\deactivate.bat 17 | popd rem %~dp0..\sd-scripts 18 | -------------------------------------------------------------------------------- /src/Setup-sd-scripts.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | pushd %~dp0.. 4 | if not exist sd-scripts ( 5 | echo git clone https://github.com/kohya-ss/sd-scripts 6 | git clone https://github.com/kohya-ss/sd-scripts 7 | if %errorlevel% neq 0 ( pause & popd & exit /b %errorlevel% ) 8 | ) 9 | popd rem %~dp0.. 10 | 11 | pushd %~dp0..\sd-scripts 12 | if not exist venv ( 13 | python -m venv venv 14 | if %errorlevel% neq 0 ( pause & popd & exit /b %errorlevel% ) 15 | ) 16 | 17 | call venv\Scripts\activate.bat 18 | 19 | echo pip install sd-scripts venv 20 | python -m pip install -q --upgrade pip 21 | if %errorlevel% neq 0 ( pause & popd & exit /b %errorlevel% ) 22 | 23 | pip install -q torch==2.0.1+cu118 torchvision==0.15.2+cu118 --index-url https://download.pytorch.org/whl/cu118 24 | if %errorlevel% neq 0 ( pause & popd & exit /b %errorlevel% ) 25 | 26 | pip install -q --upgrade -r requirements.txt 27 | if %errorlevel% neq 0 ( pause & popd & exit /b %errorlevel% ) 28 | 29 | pip install -q xformers==0.0.20 30 | if %errorlevel% neq 0 ( pause & popd & exit /b %errorlevel% ) 31 | 32 | call venv\Scripts\deactivate.bat 33 | 34 | popd rem %~dp0..\sd-scripts 35 | -------------------------------------------------------------------------------- /src/Setup.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | call %~dp0Setup-animatediff-cli-prompt-travel.bat 3 | if %errorlevel% neq 0 ( exit /b %errorlevel% ) 4 | 5 | @REM call %~dp0Setup-ECCV2022-RIFE.bat 6 | @REM if %errorlevel% neq 0 ( exit /b %errorlevel% ) 7 | 8 | call %~dp0Setup-Practical-RIFE.bat 9 | if %errorlevel% neq 0 ( exit /b %errorlevel% ) 10 | 11 | call %~dp0Setup-sd-scripts.bat 12 | if %errorlevel% neq 0 ( exit /b %errorlevel% ) 13 | 14 | call %~dp0Setup-ffmpeg-master-latest-win64-gpl.bat 15 | if %errorlevel% neq 0 ( exit /b %errorlevel% ) 16 | 17 | call %~dp0Setup-editor.bat 18 | if %errorlevel% neq 0 ( exit /b %errorlevel% ) 19 | -------------------------------------------------------------------------------- /src/Update-ECCV2022-RIFE.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | if exist %~dp0..\ECCV2022-RIFE ( 4 | pushd %~dp0..\ECCV2022-RIFE 5 | git pull 6 | if %errorlevel% neq 0 ( pause & popd & exit /b %errorlevel% ) 7 | popd rem %~dp0..\ECCV2022-RIFE 8 | ) 9 | -------------------------------------------------------------------------------- /src/Update-Practical-RIFE.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | if exist %~dp0..\Practical-RIFE ( 4 | pushd %~dp0..\Practical-RIFE 5 | git pull 6 | if %errorlevel% neq 0 ( pause & popd & exit /b %errorlevel% ) 7 | popd rem %~dp0..\Practical-RIFE 8 | ) 9 | -------------------------------------------------------------------------------- /src/Update-animatediff-cli-prompt-travel.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | if exist %~dp0..\animatediff-cli-prompt-travel ( 4 | pushd %~dp0..\animatediff-cli-prompt-travel 5 | git pull 6 | if %errorlevel% neq 0 ( pause & popd & exit /b %errorlevel% ) 7 | popd rem %~dp0..\animatediff-cli-prompt-travel 8 | ) 9 | -------------------------------------------------------------------------------- /src/Update-sd-scripts.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | if exist %~dp0..\sd-scripts ( 4 | pushd %~dp0..\sd-scripts 5 | git pull 6 | if %errorlevel% neq 0 ( pause & popd & exit /b %errorlevel% ) 7 | popd rem %~dp0..\sd-scripts 8 | ) 9 | -------------------------------------------------------------------------------- /src/Update.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | call "%~dp0Update-animatediff-cli-prompt-travel.bat" 4 | if %errorlevel% neq 0 ( exit /b %errorlevel% ) 5 | 6 | @REM call "%~dp0Update-ECCV2022-RIFE.bat" 7 | @REM if %errorlevel% neq 0 ( exit /b %errorlevel% ) 8 | 9 | call "%~dp0Update-Practical-RIFE.bat" 10 | if %errorlevel% neq 0 ( exit /b %errorlevel% ) 11 | 12 | call "%~dp0Update-sd-scripts.bat" 13 | if %errorlevel% neq 0 ( exit /b %errorlevel% ) 14 | 15 | call "%~dp0Setup.bat" 16 | if %errorlevel% neq 0 ( exit /b %errorlevel% ) 17 | --------------------------------------------------------------------------------