1120 lines
42 KiB
Batchfile
Executable File
1120 lines
42 KiB
Batchfile
Executable File
@echo off
|
|
setlocal EnableExtensions DisableDelayedExpansion
|
|
|
|
set "SAFE_USERPROFILE=%USERPROFILE%"
|
|
set "SAFE_SCRIPT_DIR=%~dp0"
|
|
if "%SAFE_SCRIPT_DIR:~-1%"=="\" set "SAFE_SCRIPT_DIR=%SAFE_SCRIPT_DIR:~0,-1%"
|
|
|
|
:: Force UTF-8 for CMD
|
|
chcp 65001 >nul
|
|
|
|
:: Prefer PowerShell 7, fallback to Windows PowerShell 5.1
|
|
set "PS_EXE=pwsh"
|
|
where.exe /Q pwsh >nul 2>&1 || set "PS_EXE=powershell"
|
|
|
|
:: One canonical set of flags for every PowerShell call in this script
|
|
set "PS_ARGS=-NoLogo -NoProfile -NonInteractive -ExecutionPolicy Bypass"
|
|
|
|
:: Detect Constrained Language Mode (corporate lockdown)
|
|
"%PS_EXE%" %PS_ARGS% -Command "if ($ExecutionContext.SessionState.LanguageMode -ne 'FullLanguage') { exit 99 }"
|
|
if errorlevel 99 (
|
|
echo ERROR: PowerShell Constrained Language Mode detected. This environment is not supported.
|
|
goto :failed
|
|
)
|
|
|
|
:: Ensure PS output encoding is UTF-8 for this session (non-persistent)
|
|
"%PS_EXE%" %PS_ARGS% -Command "[Console]::OutputEncoding=[System.Text.Encoding]::UTF8" >nul 2>&1
|
|
|
|
:: Enable ANSI VT mode
|
|
reg query HKCU\Console /v VirtualTerminalLevel >nul 2>&1
|
|
if errorlevel 1 (
|
|
reg add HKCU\Console /v VirtualTerminalLevel /t REG_DWORD /d 1 /f >nul
|
|
)
|
|
|
|
:: Real ESC byte via PowerShell (RELIABLE)
|
|
for /f "delims=" %%e in ('
|
|
cmd /c ""%PS_EXE%" %PS_ARGS% -Command "[char]27""
|
|
') do set "ESC=%%e"
|
|
|
|
:: Capture all arguments into ARGS
|
|
set "ARGS=%*"
|
|
set "NATIVE=native"
|
|
set "BUILD_DOCKER=build_docker"
|
|
set "FULL_DOCKER=full_docker"
|
|
set "SCRIPT_MODE=%NATIVE%"
|
|
set "APP_NAME=ebook2audiobook"
|
|
set /p APP_VERSION=<"%SAFE_SCRIPT_DIR%\VERSION.txt"
|
|
set "APP_FILE=%APP_NAME%.cmd"
|
|
set "OS_LANG="
|
|
for /f "skip=1 tokens=3" %%A in ('reg query "HKCU\Control Panel\International" /v LocaleName 2^>nul') do set "OS_LANG=%%A"
|
|
if defined OS_LANG set "OS_LANG=%OS_LANG:~0,2%"
|
|
if not defined OS_LANG set "OS_LANG=en"
|
|
set "TEST_HOST=127.0.0.1"
|
|
set "TEST_PORT=7860"
|
|
set "ICON_PATH=%SAFE_SCRIPT_DIR%\tools\icons\windows\appIcon.ico"
|
|
set "STARTMENU_DIR=%APPDATA%\Microsoft\Windows\Start Menu\Programs\%APP_NAME%"
|
|
set "STARTMENU_LNK=%STARTMENU_DIR%\%APP_NAME%.lnk"
|
|
set "DESKTOP_LNK=%SAFE_USERPROFILE%\Desktop\%APP_NAME%.lnk"
|
|
set "ARCH=%PROCESSOR_ARCHITECTURE%" & if defined PROCESSOR_ARCHITEW6432 set "ARCH=%PROCESSOR_ARCHITEW6432%"
|
|
if /i "%ARCH%"=="ARM64" (set "PYTHON_ARCH=arm64") else if /i "%ARCH%"=="AMD64" (set "PYTHON_ARCH=amd64")
|
|
set "MIN_PYTHON_VERSION=3.10"
|
|
set "MAX_PYTHON_VERSION=3.12"
|
|
set "PYTHON_VERSION=3.12"
|
|
set "PYTHON_SCOOP=python%PYTHON_VERSION:.=%"
|
|
set "PYTHON_ENV=python_env"
|
|
set "PYTHONUTF8=1"
|
|
set "PYTHONIOENCODING=utf-8"
|
|
set "CURRENT_ENV="
|
|
set "HOST_PROGRAMS=cmake rustup calibre ffmpeg-shared mediainfo nodejs espeak-ng sox tesseract"
|
|
:: tesseract-ocr-[lang] and calibre are hardcoded in Dockerfile
|
|
set "DOCKER_PROGRAMS=curl ffmpeg mediainfo nodejs espeak-ng sox tesseract-ocr"
|
|
set "DOCKER_CALIBRE_INSTALLER_URL=https://download.calibre-ebook.com/linux-installer.sh"
|
|
set "DOCKER_WSL_CONTAINER=Debian"
|
|
set "DOCKER_FIX_SCRIPT=dpf.ps1"
|
|
set "DOCKER_MODE="
|
|
set "DOCKER_IMG_NAME=athomasson2/%APP_NAME%"
|
|
set "DOCKER_DEVICE_STR="
|
|
set "DEVICE_INFO_STR="
|
|
set "TMP=%SAFE_SCRIPT_DIR%\run"
|
|
set "TEMP=%SAFE_SCRIPT_DIR%\run"
|
|
if not exist "%TMP%" mkdir "%TMP%" >nul 2>&1
|
|
set "CONDA_URL=https://github.com/conda-forge/miniforge/releases/latest/download/Miniforge3-Windows-x86_64.exe"
|
|
set "CONDA_INSTALLER=Miniforge3-Windows-x86_64.exe"
|
|
set "SCOOP_HOME=%SAFE_USERPROFILE%\scoop"
|
|
set "SCOOP_SHIMS=%SCOOP_HOME%\shims"
|
|
set "SCOOP_APPS=%SCOOP_HOME%\apps"
|
|
set "CONDA_HOME=%SAFE_USERPROFILE%\Miniforge3"
|
|
set "CONDA_ENV=%CONDA_HOME%\condabin\conda.bat"
|
|
set "CONDA_PATH=%CONDA_HOME%\condabin"
|
|
set "ESPEAK_DATA_PATH=%SCOOP_HOME%\apps\espeak-ng\current\eSpeak NG\espeak-ng-data"
|
|
set "NODE_PATH=%SCOOP_HOME%\apps\nodejs\current"
|
|
set "TESSDATA_PREFIX=%SAFE_SCRIPT_DIR%\models\tessdata"
|
|
set "TESSDATA_BASE_URL=https://github.com/tesseract-ocr/tessdata_best/raw/main"
|
|
set "FFMPEG_BIN=%USERPROFILE%\scoop\apps\ffmpeg-shared\current\bin"
|
|
set "FFMPEG_VARIANT=none"
|
|
set "PATH=%SCOOP_SHIMS%;%SCOOP_APPS%;%NODE_PATH%;%FFMPEG_BIN%;%PATH%"
|
|
set "INSTALLED_LOG=%SAFE_SCRIPT_DIR%\.installed"
|
|
set "UNINSTALLER=%SAFE_SCRIPT_DIR%\uninstall.cmd"
|
|
set "BROWSER_HELPER=%SAFE_SCRIPT_DIR%\.bh.ps1"
|
|
set "HEADLESS_FOUND=%ARGS:--headless=%"
|
|
set "WSL_VERSION="
|
|
set "DOCKER_IN_WSL=0"
|
|
set "DOCKER_DESKTOP=0"
|
|
set "PODMAN_DESKTOP=0"
|
|
|
|
IF NOT DEFINED DEVICE_TAG SET "DEVICE_TAG="
|
|
|
|
set "missing_prog_array="
|
|
|
|
:: Refresh environment variables (append registry Path to current PATH)
|
|
for /f "tokens=2,*" %%A in ('reg query "HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Environment" /v Path') do (
|
|
set "PATH=%%B;%PATH%"
|
|
)
|
|
|
|
if "%ARCH%"=="X86" (
|
|
echo %ESC%[31m=============== Error: 32-bit architecture is not supported.%ESC%[0m
|
|
goto :failed
|
|
)
|
|
|
|
cd /d "%SAFE_SCRIPT_DIR%"
|
|
|
|
:: Clear previous associative values
|
|
for /f "tokens=1* delims==" %%A in ('set arguments. 2^>nul') do set "%%A="
|
|
|
|
::::::::::::::::::::::::::::::: CORE FUNCTIONS
|
|
|
|
if not "%~1"=="" (
|
|
setlocal EnableDelayedExpansion
|
|
for /f "delims=" %%V in ('python -c "from lib.conf import cli_options; print(' '.join(cli_options))"') do set "VALID_ARGS=%%V"
|
|
for %%A in (%*) do (
|
|
set "ARG=%%~A"
|
|
if "!ARG:~0,2!"=="--" (
|
|
set "FOUND=0"
|
|
for %%V in (!VALID_ARGS!) do (
|
|
if /i "!ARG!"=="%%V" set "FOUND=1"
|
|
)
|
|
if !FOUND! equ 0 (
|
|
echo ERROR: Unknown option "!ARG!"
|
|
exit /b 1
|
|
)
|
|
)
|
|
)
|
|
endlocal
|
|
)
|
|
|
|
:parse_args
|
|
rem No setlocal here: arguments.* are set in the current scope so they reach :main
|
|
rem without an endlocal tunnel. Indirect names are set via call set "...%%key%%...".
|
|
if "%~1"=="" goto :parse_args_done
|
|
set "arg=%~1"
|
|
if "%arg:~0,2%"=="--" (
|
|
set "key=%arg:~2%"
|
|
if not "%~2"=="" (
|
|
echo %~2 | findstr "^--" >nul
|
|
if errorlevel 1 (
|
|
call set "arguments.%%key%%=%~2"
|
|
shift
|
|
shift
|
|
goto parse_args
|
|
)
|
|
)
|
|
call set "arguments.%%key%%=true"
|
|
shift
|
|
goto parse_args
|
|
)
|
|
shift
|
|
goto parse_args
|
|
|
|
:parse_args_done
|
|
if defined arguments.script_mode (
|
|
set "script_mode_valid=0"
|
|
if /i "%arguments.script_mode%"=="%BUILD_DOCKER%" set "script_mode_valid=1"
|
|
if /i "%arguments.script_mode%"=="%FULL_DOCKER%" set "script_mode_valid=1"
|
|
if /i "%arguments.script_mode%"=="%NATIVE%" set "script_mode_valid=1"
|
|
)
|
|
|
|
if defined arguments.script_mode if "%script_mode_valid%"=="1" (
|
|
set "SCRIPT_MODE=%arguments.script_mode%"
|
|
)
|
|
if defined arguments.script_mode if "%script_mode_valid%"=="0" (
|
|
echo Error: Invalid script mode argument: %arguments.script_mode%
|
|
goto :failed
|
|
)
|
|
if defined arguments.docker_device (
|
|
if /i "%arguments.docker_device%"=="true" (
|
|
echo Error: --docker_device has no value
|
|
goto :failed
|
|
)
|
|
set "DOCKER_DEVICE_STR=%arguments.docker_device%"
|
|
)
|
|
if defined arguments.docker_mode (
|
|
if not "%arguments.docker_mode%"=="podman" (
|
|
if not "%arguments.docker_mode%"=="compose" (
|
|
if /i "%arguments.docker_mode%"=="true" (
|
|
echo Error: --docker_mode has no value
|
|
) else (
|
|
echo Error: --docker_mode accepts only podman or compose as value
|
|
)
|
|
goto :failed
|
|
)
|
|
)
|
|
set "DOCKER_MODE=%arguments.docker_mode%"
|
|
)
|
|
if defined arguments.script_mode (
|
|
if /i "%arguments.script_mode%"=="true" (
|
|
echo Error: --script_mode requires a value
|
|
goto :failed
|
|
)
|
|
if /i not "%arguments.script_mode%"=="FULL_DOCKER" (
|
|
setlocal enabledelayedexpansion
|
|
for /f "tokens=1,2 delims==" %%A in ('set arguments. 2^>nul') do (
|
|
set "argname=%%A"
|
|
set "argname=!argname:arguments.=!"
|
|
if not "!argname!"=="" (
|
|
if /i not "!argname!"=="script_mode" (
|
|
if /i not "!argname!"=="docker_device" (
|
|
if /i not "!argname!"=="docker_mode" (
|
|
echo Error: when --script_mode is not FULL_DOCKER, only --docker_device or --docker_mode are allowed. Invalid: --!argname!
|
|
goto :failed
|
|
)
|
|
)
|
|
)
|
|
)
|
|
)
|
|
endlocal
|
|
)
|
|
)
|
|
rem .installed must not be created in build_docker mode; check after SCRIPT_MODE is resolved
|
|
if not exist "%INSTALLED_LOG%" if /i not "%SCRIPT_MODE%"=="%BUILD_DOCKER%" (
|
|
type nul > "%INSTALLED_LOG%"
|
|
)
|
|
if defined arguments.headless (
|
|
if /i "%arguments.headless%"=="false" (
|
|
setlocal enabledelayedexpansion
|
|
for /f "tokens=1,2 delims==" %%A in ('set arguments. 2^>nul') do (
|
|
set "argname=%%A"
|
|
set "argname=!argname:arguments.=!"
|
|
if not "!argname!"=="" (
|
|
if /i not "!argname!"=="headless" (
|
|
if /i not "!argname!"=="script_mode" (
|
|
if /i not "!argname!"=="share" (
|
|
echo Error: In non-headless mode only --share option is allowed. Invalid: --!argname!
|
|
goto :failed
|
|
)
|
|
)
|
|
)
|
|
)
|
|
)
|
|
endlocal
|
|
)
|
|
)
|
|
if defined arguments.share (
|
|
if defined arguments.headless (
|
|
if /i "%arguments.headless%"=="true" (
|
|
echo Error: --share option is only allowed in non-headless mode
|
|
goto :failed
|
|
)
|
|
)
|
|
)
|
|
if defined arguments.version (
|
|
echo v%APP_VERSION%
|
|
goto :eof
|
|
)
|
|
goto :main
|
|
|
|
::::::::::::::: DESKTOP APP
|
|
:make_shortcut
|
|
set "shortcut=%~1"
|
|
"%PS_EXE%" %PS_ARGS% -Command "$s=New-Object -ComObject WScript.Shell; $sc=$s.CreateShortcut('%shortcut%'); $sc.TargetPath='cmd.exe'; $sc.Arguments='/k ""cd /d """"%SAFE_SCRIPT_DIR%"""" && """"%APP_FILE%""""""'; $sc.WorkingDirectory='%SAFE_SCRIPT_DIR%'; $sc.IconLocation='%ICON_PATH%'; $sc.Save()"
|
|
exit /b
|
|
|
|
:build_gui
|
|
if /i not "%HEADLESS_FOUND%"=="%ARGS%" (
|
|
if not exist "%STARTMENU_DIR%" mkdir "%STARTMENU_DIR%"
|
|
if not exist "%STARTMENU_LNK%" (
|
|
call :make_shortcut "%STARTMENU_LNK%"
|
|
call :make_shortcut "%DESKTOP_LNK%"
|
|
)
|
|
for /f "skip=1 delims=" %%L in ('tasklist /v /fo csv /fi "imagename eq powershell.exe" 2^>nul') do (
|
|
echo %%L | findstr /i "%APP_NAME%" >nul && (
|
|
for /f "tokens=2 delims=," %%A in ("%%L") do (
|
|
taskkill /PID %%~A /F >nul 2>&1
|
|
)
|
|
)
|
|
)
|
|
reg add "HKCU\Software\Microsoft\Windows\CurrentVersion\Uninstall\%APP_NAME%" /v "DisplayName" /d "%APP_NAME%" /f >nul 2>&1
|
|
reg add "HKCU\Software\Microsoft\Windows\CurrentVersion\Uninstall\%APP_NAME%" /v "DisplayVersion" /d "%APP_VERSION%" /f >nul 2>&1
|
|
reg add "HKCU\Software\Microsoft\Windows\CurrentVersion\Uninstall\%APP_NAME%" /v "Publisher" /d "ebook2audiobook Team" /f >nul 2>&1
|
|
reg add "HKCU\Software\Microsoft\Windows\CurrentVersion\Uninstall\%APP_NAME%" /v "InstallLocation" /d "%SAFE_SCRIPT_DIR%" /f >nul 2>&1
|
|
reg add "HKCU\Software\Microsoft\Windows\CurrentVersion\Uninstall\%APP_NAME%" /v "UninstallString" /d "\"%UNINSTALLER%\"" /f >nul 2>&1
|
|
reg add "HKCU\Software\Microsoft\Windows\CurrentVersion\Uninstall\%APP_NAME%" /v "DisplayIcon" /d "%ICON_PATH%" /f >nul 2>&1
|
|
reg add "HKCU\Software\Microsoft\Windows\CurrentVersion\Uninstall\%APP_NAME%" /v "NoModify" /t REG_DWORD /d 1 /f >nul 2>&1
|
|
reg add "HKCU\Software\Microsoft\Windows\CurrentVersion\Uninstall\%APP_NAME%" /v "NoRepair" /t REG_DWORD /d 1 /f >nul 2>&1
|
|
start "%APP_NAME%" /min "%PS_EXE%" %PS_ARGS% -File "%BROWSER_HELPER%" -HostName "%TEST_HOST%" -Port %TEST_PORT%
|
|
)
|
|
exit /b 0
|
|
:::::: END OF DESKTOP APP
|
|
|
|
:get_iso3_lang
|
|
set "ISO3_LANG=eng"
|
|
if /i "%~1"=="en" set "ISO3_LANG=eng"
|
|
if /i "%~1"=="fr" set "ISO3_LANG=fra"
|
|
if /i "%~1"=="de" set "ISO3_LANG=deu"
|
|
if /i "%~1"=="it" set "ISO3_LANG=ita"
|
|
if /i "%~1"=="es" set "ISO3_LANG=spa"
|
|
if /i "%~1"=="pt" set "ISO3_LANG=por"
|
|
if /i "%~1"=="ar" set "ISO3_LANG=ara"
|
|
if /i "%~1"=="tr" set "ISO3_LANG=tur"
|
|
if /i "%~1"=="ru" set "ISO3_LANG=rus"
|
|
if /i "%~1"=="bn" set "ISO3_LANG=ben"
|
|
if /i "%~1"=="zh" set "ISO3_LANG=chi_sim"
|
|
if /i "%~1"=="fa" set "ISO3_LANG=fas"
|
|
if /i "%~1"=="hi" set "ISO3_LANG=hin"
|
|
if /i "%~1"=="hu" set "ISO3_LANG=hun"
|
|
if /i "%~1"=="id" set "ISO3_LANG=ind"
|
|
if /i "%~1"=="jv" set "ISO3_LANG=jav"
|
|
if /i "%~1"=="ja" set "ISO3_LANG=jpn"
|
|
if /i "%~1"=="ko" set "ISO3_LANG=kor"
|
|
if /i "%~1"=="pl" set "ISO3_LANG=pol"
|
|
if /i "%~1"=="ta" set "ISO3_LANG=tam"
|
|
if /i "%~1"=="te" set "ISO3_LANG=tel"
|
|
if /i "%~1"=="yo" set "ISO3_LANG=yor"
|
|
exit /b
|
|
|
|
:check_python
|
|
where.exe python >nul 2>&1
|
|
if errorlevel 1 (
|
|
echo Python is not installed.
|
|
exit /b 1
|
|
)
|
|
for /f "tokens=2 delims= " %%v in ('python --version 2^>^&1') do set "INSTALLED_VERSION=%%v"
|
|
for /f "tokens=1-3 delims=." %%a in ("%INSTALLED_VERSION%") do (
|
|
set "INS_MAJOR=%%a"
|
|
set "INS_MINOR=%%b"
|
|
set "INS_PATCH=0"
|
|
)
|
|
for /f "tokens=1-3 delims=." %%a in ("%MIN_PYTHON_VERSION%") do (
|
|
set "REQ_MAJOR=%%a"
|
|
set "REQ_MINOR=%%b"
|
|
set "REQ_PATCH=0"
|
|
)
|
|
set "PYTHON_OK=1"
|
|
if %INS_MAJOR% lss %REQ_MAJOR% set "PYTHON_OK=0"
|
|
if %INS_MAJOR% equ %REQ_MAJOR% if %INS_MINOR% lss %REQ_MINOR% set "PYTHON_OK=0"
|
|
if %INS_MAJOR% equ %REQ_MAJOR% if %INS_MINOR% equ %REQ_MINOR% if %INS_PATCH% lss %REQ_PATCH% set "PYTHON_OK=0"
|
|
if "%PYTHON_OK%"=="0" (
|
|
echo Python %INSTALLED_VERSION% found but %MIN_PYTHON_VERSION% or higher is required.
|
|
exit /b 1
|
|
)
|
|
exit /b 0
|
|
|
|
:check_scoop
|
|
where.exe /Q scoop >nul 2>&1
|
|
if errorlevel 1 (
|
|
echo Scoop is not installed.
|
|
exit /b 1
|
|
)
|
|
exit /b 0
|
|
|
|
:check_scoop_buckets
|
|
call "%PS_EXE%" %PS_ARGS% -Command "scoop bucket list" > "%TEMP%\scoop_buckets.txt" 2>&1
|
|
set "_MISSING_BUCKETS="
|
|
findstr /i "muggle" "%TEMP%\scoop_buckets.txt" >nul 2>&1 || set "_MISSING_BUCKETS=!_MISSING_BUCKETS! muggle"
|
|
findstr /i "extras" "%TEMP%\scoop_buckets.txt" >nul 2>&1 || set "_MISSING_BUCKETS=!_MISSING_BUCKETS! extras"
|
|
findstr /i "versions" "%TEMP%\scoop_buckets.txt" >nul 2>&1 || set "_MISSING_BUCKETS=!_MISSING_BUCKETS! versions"
|
|
del "%TEMP%\scoop_buckets.txt" >nul 2>&1
|
|
if defined _MISSING_BUCKETS (
|
|
exit /b 1
|
|
)
|
|
exit /b 0
|
|
|
|
:check_programs
|
|
setlocal EnableDelayedExpansion
|
|
for %%p in (%HOST_PROGRAMS%) do (
|
|
set "prog=%%p"
|
|
set "_found=0"
|
|
if "%%p"=="nodejs" set "prog=node"
|
|
if "%%p"=="calibre" set "prog=ebook-convert"
|
|
if "%%p"=="ffmpeg-shared" set "prog=ffmpeg"
|
|
if "%%p"=="rustup" (
|
|
if exist "%SAFE_USERPROFILE%\scoop\apps\rustup\current\.cargo\bin\rustup.exe" set "_found=1"
|
|
)
|
|
if "!_found!"=="0" (
|
|
where.exe /Q !prog! >nul 2>&1
|
|
if errorlevel 1 (
|
|
set "missing_prog_array=!missing_prog_array! %%p"
|
|
) else (
|
|
if "%%p"=="ffmpeg-shared" (
|
|
call :check_ffmpeg_shared
|
|
)
|
|
)
|
|
)
|
|
)
|
|
endlocal & set "missing_prog_array=%missing_prog_array%"
|
|
if not "%missing_prog_array%"=="" exit /b 1
|
|
exit /b 0
|
|
|
|
:check_ffmpeg_shared
|
|
setlocal
|
|
set "ffmpeg_pkg=none"
|
|
set "tmp_file=%INSTALLED_LOG%.tmp"
|
|
if exist "%SCOOP_HOME%\apps\ffmpeg-shared\current\bin\avcodec-*.dll" (
|
|
set "ffmpeg_pkg=shared"
|
|
) else if exist "%SCOOP_HOME%\apps\ffmpeg\current\bin\ffmpeg.exe" (
|
|
set "ffmpeg_pkg=static"
|
|
) else (
|
|
exit /b 0
|
|
)
|
|
if "%ffmpeg_pkg%"=="static" (
|
|
echo Static ffmpeg detected, swapping to ffmpeg-shared…
|
|
call scoop uninstall ffmpeg || (echo [xx] uninstall failed & exit /b 1)
|
|
call scoop install ffmpeg-shared || (echo [xx] install failed & exit /b 1)
|
|
if exist "%INSTALLED_LOG%" (
|
|
findstr /v /x /c:"ffmpeg" "%INSTALLED_LOG%" > "%tmp_file%" 2>nul
|
|
) else (
|
|
type nul > "%tmp_file%"
|
|
)
|
|
>>"%tmp_file%" echo ffmpeg-shared
|
|
move /y "%tmp_file%" "%INSTALLED_LOG%" >nul
|
|
echo swap complete, .installed updated.
|
|
)
|
|
endlocal
|
|
exit /b 0
|
|
|
|
:install_python
|
|
echo Installing Python %PYTHON_VERSION%…
|
|
set "PYTHON_INSTALLER=python-%PYTHON_VERSION%-%PYTHON_ARCH%.exe"
|
|
set "PYTHON_URL=https://www.python.org/ftp/python/%PYTHON_VERSION%.0/python-%PYTHON_VERSION%.0-%PYTHON_ARCH%.exe"
|
|
echo Downloading Python installer for %PYTHON_ARCH%…
|
|
powershell -NoProfile -Command "Invoke-WebRequest -Uri '%PYTHON_URL%' -OutFile '%TEMP%\%PYTHON_INSTALLER%'"
|
|
if errorlevel 1 (
|
|
echo %ESC%[31m=============== Failed to download Python installer.%ESC%[0m
|
|
goto :failed
|
|
)
|
|
echo Installing Python silently…
|
|
"%TEMP%\%PYTHON_INSTALLER%" /quiet InstallAllUsers=0 PrependPath=1 Include_test=0
|
|
if errorlevel 1 (
|
|
echo %ESC%[31m=============== Python installation failed.%ESC%[0m
|
|
del "%TEMP%\%PYTHON_INSTALLER%"
|
|
goto :failed
|
|
)
|
|
del "%TEMP%\%PYTHON_INSTALLER%"
|
|
del "%USERPROFILE%\AppData\Local\Microsoft\WindowsApps\python.exe"
|
|
del "%USERPROFILE%\AppData\Local\Microsoft\WindowsApps\python3.exe"
|
|
echo %ESC%[33m=============== Python OK ===============%ESC%[0m
|
|
goto :restart_script
|
|
|
|
:install_scoop
|
|
echo Installing Scoop…
|
|
call "%PS_EXE%" %PS_ARGS% -Command "irm get.scoop.sh -OutFile '%TEMP%\install_scoop.ps1'"
|
|
call "%PS_EXE%" %PS_ARGS% -File "%TEMP%\install_scoop.ps1" -RunAsAdmin
|
|
del "%TEMP%\install_scoop.ps1" >nul 2>&1
|
|
if errorlevel 1 (
|
|
net session >nul 2>&1
|
|
if not errorlevel 1 (
|
|
goto :restart_script
|
|
)
|
|
goto :failed
|
|
)
|
|
findstr /i /x "scoop" "%INSTALLED_LOG%" >nul 2>&1
|
|
if errorlevel 1 echo scoop>>"%INSTALLED_LOG%"
|
|
call "%PS_EXE%" %PS_ARGS% -Command "scoop bucket add muggle https://github.com/hu3rror/scoop-muggle.git"
|
|
call "%PS_EXE%" %PS_ARGS% -Command "scoop bucket add extras"
|
|
call "%PS_EXE%" %PS_ARGS% -Command "scoop bucket add versions"
|
|
echo %ESC%[33m=============== Scoop OK ===============%ESC%[0m
|
|
type nul > "%SAFE_SCRIPT_DIR%\.after-scoop"
|
|
goto :restart_script
|
|
|
|
:install_scoop_buckets
|
|
call "%PS_EXE%" %PS_ARGS% -Command "$WarningPreference='SilentlyContinue'; scoop install git; scoop bucket add muggle https://github.com/hu3rror/scoop-muggle.git; scoop bucket add extras; scoop bucket add versions"
|
|
call git config --global credential.helper
|
|
del "%SAFE_SCRIPT_DIR%\.after-scoop" >nul 2>&1
|
|
echo %ESC%[32m=============== Scoop components OK ===============%ESC%[0m
|
|
exit /b 0
|
|
|
|
:install_wsl
|
|
if "%SCRIPT_MODE%"=="%BUILD_DOCKER%" (
|
|
echo WSL2 is required to build Linux containers.
|
|
echo.
|
|
echo ==================================================
|
|
echo WSL and %DOCKER_WSL_CONTAINER% will now be installed.
|
|
echo ==================================================
|
|
pause
|
|
wsl --unregister %DOCKER_WSL_CONTAINER% >nul 2>&1
|
|
wsl --update
|
|
wsl --install -d %DOCKER_WSL_CONTAINER% --no-launch
|
|
echo.
|
|
echo %DOCKER_WSL_CONTAINER% setup complete. Configuring for Docker...
|
|
wsl --shutdown
|
|
timeout /t 3 /nobreak >nul
|
|
wsl --user root -- echo "%DOCKER_WSL_CONTAINER% OK" >nul 2>&1
|
|
if errorlevel 1 (
|
|
echo %ESC%[31m=============== %DOCKER_WSL_CONTAINER% installation failed.%ESC%[0m
|
|
goto :failed
|
|
)
|
|
for /f %%A in ('powershell -NoProfile -Command "Get-ChildItem 'HKCU:\Software\Microsoft\Windows\CurrentVersion\Lxss' | Where-Object { (Get-ItemProperty $_.PSPath).DistributionName -eq '%DOCKER_WSL_CONTAINER%' } | Select-Object -ExpandProperty PSChildName"') do (
|
|
reg add "HKCU\Software\Microsoft\Windows\CurrentVersion\Lxss\%%A" /v DefaultUid /t REG_DWORD /d 0 /f >nul
|
|
)
|
|
echo [wsl2] > "%USERPROFILE%\.wslconfig"
|
|
echo memory=4GB >> "%USERPROFILE%\.wslconfig"
|
|
wsl --shutdown
|
|
echo %ESC%[33m=============== WSL2 OK ===============%ESC%[0m
|
|
)
|
|
goto :restart_script
|
|
|
|
:install_docker
|
|
if "%SCRIPT_MODE%"=="%BUILD_DOCKER%" (
|
|
echo Installing Docker inside WSL2…
|
|
wsl --user root -d %DOCKER_WSL_CONTAINER% -- bash -c "echo 'WSL is ready'" >nul 2>&1
|
|
if errorlevel 1 (
|
|
echo %ESC%[31m=============== WSL %DOCKER_WSL_CONTAINER% is not ready. Initializing…%ESC%[0m
|
|
wsl --user root -d %DOCKER_WSL_CONTAINER% -- bash -c "apt-get update" >nul 2>&1
|
|
wsl --shutdown
|
|
timeout /t 3 /nobreak >nul
|
|
)
|
|
echo Downloading and installing Docker…
|
|
wsl --user root -d %DOCKER_WSL_CONTAINER% -- bash -c "apt-get update && apt-get install -y curl"
|
|
wsl --user root -d %DOCKER_WSL_CONTAINER% -- bash -c "curl -fsSL https://get.docker.com | SKIP_SLEEP=1 sh"
|
|
if errorlevel 1 (
|
|
echo %ESC%[31m=============== docker install failed.%ESC%[0m
|
|
echo Try running: wsl --user root -d %DOCKER_WSL_CONTAINER%
|
|
echo Then manually run: curl -fsSL https://get.docker.com ^| sh
|
|
goto :failed
|
|
)
|
|
echo Enabling systemd…
|
|
wsl --user root -d %DOCKER_WSL_CONTAINER% -- bash -c "echo '[boot]' > /etc/wsl.conf && echo 'systemd=true' >> /etc/wsl.conf"
|
|
wsl --shutdown
|
|
echo %ESC%[33m=============== docker OK ===============%ESC%[0m
|
|
)
|
|
goto :restart_script
|
|
|
|
:install_conda
|
|
if not "%SCRIPT_MODE%"=="%BUILD_DOCKER%" (
|
|
echo Installing Miniforge…
|
|
call "%PS_EXE%" %PS_ARGS% -Command "Invoke-WebRequest -Uri '%CONDA_URL%' -OutFile '%CONDA_INSTALLER%'"
|
|
call start /wait "" "%CONDA_INSTALLER%" /InstallationType=JustMe /RegisterPython=0 /S /D="%SAFE_USERPROFILE%\Miniforge3"
|
|
set "PATH=%CONDA_PATH%;%CONDA_HOME%\Scripts;%PATH%"
|
|
where.exe /Q conda
|
|
if not errorlevel 1 (
|
|
echo %ESC%[32m=============== Miniforge3 OK ===============%ESC%[0m
|
|
findstr /i /x "Miniforge3" "%INSTALLED_LOG%" >nul 2>&1
|
|
if errorlevel 1 (
|
|
echo Miniforge3>>"%INSTALLED_LOG%"
|
|
)
|
|
) else (
|
|
echo %ESC%[31m=============== Miniforge3 failed.%ESC%[0m
|
|
goto :failed
|
|
)
|
|
if not exist "%SAFE_USERPROFILE%\.condarc" (
|
|
call conda config --set auto_activate_base false
|
|
)
|
|
call conda update --all -y
|
|
call conda clean --index-cache -y
|
|
call conda clean --packages --tarballs -y
|
|
del "%CONDA_INSTALLER%"
|
|
)
|
|
goto :restart_script
|
|
|
|
:download_tessdata
|
|
setlocal
|
|
set "_LANG=%~1"
|
|
set "_DEST=%~2"
|
|
"%PS_EXE%" %PS_ARGS% -Command "Invoke-WebRequest -Uri '%TESSDATA_BASE_URL%/%_LANG%.traineddata' -OutFile '%_DEST%\%_LANG%.traineddata' -ErrorAction Stop"
|
|
set "RC=%errorlevel%"
|
|
endlocal & exit /b %RC%
|
|
|
|
:install_programs
|
|
echo Installing missing programs…
|
|
setlocal EnableDelayedExpansion
|
|
for %%p in (%missing_prog_array%) do (
|
|
set "prog=%%p"
|
|
call "%PS_EXE%" %PS_ARGS% -Command "scoop install %%p"
|
|
if "%%p"=="tesseract" (
|
|
where.exe /Q !prog!
|
|
if not errorlevel 1 (
|
|
call :get_iso3_lang "%OS_LANG%"
|
|
echo Detected system language: %OS_LANG% → downloading OCR language: !ISO3_LANG!
|
|
set "tessdata=%SCOOP_APPS%\tesseract\current\tessdata"
|
|
if not exist "!tessdata!" mkdir "!tessdata!"
|
|
if not exist "!tessdata!\!ISO3_LANG!.traineddata" (
|
|
call :download_tessdata "!ISO3_LANG!" "!tessdata!" || goto :failed
|
|
)
|
|
if exist "!tessdata!\!ISO3_LANG!.traineddata" (
|
|
echo Tesseract OCR language !ISO3_LANG! installed in !tessdata!
|
|
) else (
|
|
echo Failed to install OCR language !ISO3_LANG!
|
|
)
|
|
)
|
|
)
|
|
if "%%p"=="python" (
|
|
set "PY_FOUND="
|
|
where.exe /Q python && set PY_FOUND=1
|
|
where.exe /Q python3 && set PY_FOUND=1
|
|
where.exe /Q py && set PY_FOUND=1
|
|
if not defined PY_FOUND (
|
|
echo %ESC%[31m=============== %%p failed.%ESC%[0m
|
|
goto :failed
|
|
)
|
|
)
|
|
if "%%p"=="nodejs" (
|
|
set "prog=node"
|
|
)
|
|
if "%%p"=="ffmpeg-shared" (
|
|
set "prog=ffmpeg"
|
|
if exist "%SAFE_USERPROFILE%\scoop\apps\ffmpeg-shared\current\bin\ffmpeg.exe" (
|
|
set "_FFMPEG_PATH=%SAFE_USERPROFILE%\scoop\apps\ffmpeg-shared\current\bin"
|
|
echo !PATH! | findstr /i /c:"!_FFMPEG_PATH!" >nul 2>&1 || (
|
|
set "PATH=!_FFMPEG_PATH!;!PATH!"
|
|
)
|
|
)
|
|
)
|
|
if "%%p"=="rustup" (
|
|
if exist "%SAFE_USERPROFILE%\scoop\apps\rustup\current\.cargo\bin\rustup.exe" (
|
|
set "_RUSTUP_PATH=%SAFE_USERPROFILE%\scoop\apps\rustup\current\.cargo\bin"
|
|
echo !PATH! | findstr /i /c:"!_RUSTUP_PATH!" >nul 2>&1 || (
|
|
set "PATH=!_RUSTUP_PATH!;!PATH!"
|
|
)
|
|
)
|
|
)
|
|
where.exe /Q !prog!
|
|
if not errorlevel 1 (
|
|
echo %ESC%[32m=============== %%p OK! ===============%ESC%[0m
|
|
findstr /i /x "%%p" "%INSTALLED_LOG%" >nul 2>&1
|
|
if errorlevel 1 (
|
|
echo %%p>>"%INSTALLED_LOG%"
|
|
)
|
|
) else (
|
|
echo %ESC%[31m=============== %%p failed.%ESC%[0m
|
|
goto :failed
|
|
)
|
|
)
|
|
endlocal & set "PATH=%PATH%"
|
|
call "%PS_EXE%" %PS_ARGS% -Command "$cp=[System.Environment]::GetEnvironmentVariable('Path','User'); $np=$cp; @('%SCOOP_SHIMS%','%SCOOP_APPS%','%CONDA_PATH%','%NODE_PATH%') | Where-Object {$_ -and $cp -notlike ('*'+$_+'*')} | ForEach-Object {$np+=(';'+$_)}; [System.Environment]::SetEnvironmentVariable('Path',$np,'User')"
|
|
set "missing_prog_array="
|
|
goto :main
|
|
|
|
:check_conda
|
|
where.exe /Q conda
|
|
if errorlevel 1 (
|
|
echo Conda is not installed.
|
|
exit /b 1
|
|
)
|
|
set "DETECTED_BASE="
|
|
for /f "usebackq delims=" %%B in (`conda info --base 2^>nul`) do set "DETECTED_BASE=%%B"
|
|
if not defined DETECTED_BASE (
|
|
echo Failed to query 'conda info --base'; aborting.
|
|
exit /b 3
|
|
)
|
|
set "CONDA_HOME=%DETECTED_BASE%"
|
|
set "CONDA_PATH=%DETECTED_BASE%\condabin"
|
|
set "CONDA_ENV=%DETECTED_BASE%\condabin\conda.bat"
|
|
set "PATH=%CONDA_PATH%;%PATH%"
|
|
set "CURRENT_ENV="
|
|
if defined CONDA_DEFAULT_ENV (
|
|
if /i not "%CONDA_DEFAULT_ENV%"=="base" (
|
|
set "CURRENT_ENV=%CONDA_PREFIX%"
|
|
)
|
|
)
|
|
if defined VIRTUAL_ENV (
|
|
set "CURRENT_ENV=%VIRTUAL_ENV%"
|
|
)
|
|
if defined CURRENT_ENV (
|
|
echo Current python virtual environment detected: %CURRENT_ENV%.
|
|
echo =============== This script runs with its own virtual env and must be out of any other virtual environment when it's launched.
|
|
exit /b 2
|
|
)
|
|
if /i "%CONDA_DEFAULT_ENV%"=="base" (
|
|
call conda deactivate >nul 2>&1
|
|
)
|
|
if not exist "%SAFE_SCRIPT_DIR%\%PYTHON_ENV%\.provisioned" (
|
|
if exist "%SAFE_SCRIPT_DIR%\%PYTHON_ENV%" (
|
|
echo Detected incomplete %PYTHON_ENV% — removing and recreating...
|
|
rmdir /s /q "%SAFE_SCRIPT_DIR%\%PYTHON_ENV%"
|
|
)
|
|
echo Creating ./%PYTHON_ENV% with python %PYTHON_VERSION%...
|
|
call "%CONDA_HOME%\Scripts\activate.bat"
|
|
call conda create --prefix "%SAFE_SCRIPT_DIR%\%PYTHON_ENV%" -c conda-forge python=%PYTHON_VERSION% pip -y
|
|
if errorlevel 1 exit /b 3
|
|
call conda activate "%SAFE_SCRIPT_DIR%\%PYTHON_ENV%"
|
|
call :provision_env
|
|
if errorlevel 1 exit /b 3
|
|
> "%SAFE_SCRIPT_DIR%\%PYTHON_ENV%\.provisioned" echo %APP_VERSION%
|
|
)
|
|
exit /b 0
|
|
|
|
:provision_env
|
|
setlocal enabledelayedexpansion
|
|
set "RC=0"
|
|
call :check_device_info %SCRIPT_MODE%
|
|
if errorlevel 1 (
|
|
set "RC=1"
|
|
goto :provision_env_end
|
|
)
|
|
call :install_device_packages
|
|
if errorlevel 1 (
|
|
set "RC=1"
|
|
goto :provision_env_end
|
|
)
|
|
call :install_python_packages
|
|
if errorlevel 1 (
|
|
set "RC=1"
|
|
goto :provision_env_end
|
|
)
|
|
:provision_env_end
|
|
endlocal & exit /b %RC%
|
|
|
|
:check_wsl
|
|
where.exe /Q wsl
|
|
if errorlevel 1 (
|
|
echo WSL is not installed.
|
|
exit /b 1
|
|
)
|
|
for /f "tokens=3" %%A in (
|
|
'reg query "HKCU\Software\Microsoft\Windows\CurrentVersion\Lxss" /v DefaultVersion 2^>nul ^| find "DefaultVersion"'
|
|
) do set "WSL_VERSION=%%A"
|
|
if not "%WSL_VERSION%"=="0x2" (
|
|
echo WSL2 is not configured as default.
|
|
exit /b 1
|
|
)
|
|
wsl -l -q 2>nul | findstr /R /C:".*" >nul
|
|
if errorlevel 1 (
|
|
echo No WSL Linux distribution installed.
|
|
exit /b 1
|
|
)
|
|
for /f "delims=" %%a in ('wsl echo $WSL_DISTRO_NAME') do set "DOCKER_WSL_CONTAINER=%%a"
|
|
exit /b 0
|
|
|
|
:check_docker
|
|
if "%DOCKER_MODE%"=="podman" (
|
|
where.exe /Q podman-compose.exe
|
|
if not errorlevel 1 (
|
|
podman-compose version >nul 2>&1
|
|
if not errorlevel 1 (
|
|
echo Podman Desktop detected.
|
|
set "PODMAN_DESKTOP=1"
|
|
exit /b 0
|
|
)
|
|
)
|
|
echo Podman is not installed.
|
|
exit /b 1
|
|
)
|
|
where.exe /Q docker.exe
|
|
if not errorlevel 1 (
|
|
docker version >nul 2>&1
|
|
if not errorlevel 1 (
|
|
echo Docker Desktop detected.
|
|
set "DOCKER_DESKTOP=1"
|
|
exit /b 0
|
|
)
|
|
)
|
|
wsl --user root -d %DOCKER_WSL_CONTAINER% -- which docker >nul 2>&1
|
|
if errorlevel 1 (
|
|
echo Docker is not installed inside WSL2.
|
|
exit /b 1
|
|
)
|
|
exit /b 0
|
|
|
|
:check_docker_daemon
|
|
if "%PODMAN_DESKTOP%"=="1" exit /b 0
|
|
if "%DOCKER_DESKTOP%"=="1" (
|
|
docker info >nul 2>&1
|
|
if not errorlevel 1 exit /b 0
|
|
echo Docker Desktop daemon is not running. Please start Docker Desktop.
|
|
exit /b 1
|
|
)
|
|
wsl --user root -d %DOCKER_WSL_CONTAINER% -- docker info >nul 2>&1
|
|
if not errorlevel 1 exit /b 0
|
|
echo Starting Docker daemon inside WSL2…
|
|
wsl --user root -d %DOCKER_WSL_CONTAINER% -- service docker start >nul 2>&1
|
|
if errorlevel 1 (
|
|
echo Docker failed to start
|
|
exit /b 1
|
|
)
|
|
set "DOCKER_RETRIES=0"
|
|
:wait_docker
|
|
timeout /t 3 /nobreak >nul
|
|
set /a DOCKER_RETRIES+=1
|
|
if %DOCKER_RETRIES% geq 20 (
|
|
echo Docker daemon failed to start after 60 seconds.
|
|
exit /b 1
|
|
)
|
|
wsl --user root -d %DOCKER_WSL_CONTAINER% -- docker info >nul 2>&1
|
|
if errorlevel 1 goto :wait_docker
|
|
echo Docker daemon is ready.
|
|
exit /b 0
|
|
|
|
:check_device_info
|
|
set "ARG=%~1"
|
|
set "DEVICE_INFO_STR="
|
|
for /f "delims=" %%I in ('python -c "import sys; from lib.classes.device_installer import DeviceInstaller as D; print(D().check_device_info(sys.argv[1]))" "%ARG%"') do set "DEVICE_INFO_STR=%%I"
|
|
if not defined DEVICE_INFO_STR (
|
|
echo DEVICE_INFO_STR is empty
|
|
exit /b 1
|
|
)
|
|
exit /b 0
|
|
|
|
:json_get
|
|
setlocal enabledelayedexpansion
|
|
set "KEY=%~1"
|
|
set "JSON_VALUE="
|
|
for /f "delims=" %%i in ('powershell -Command "$env:DEVICE_INFO_STR | ConvertFrom-Json | Select-Object -ExpandProperty %KEY%"') do set "JSON_VALUE=%%i"
|
|
if "!JSON_VALUE!"=="" (
|
|
echo No key nor value found for %KEY%
|
|
endlocal & exit /b 1
|
|
)
|
|
endlocal & set "DEVICE_TAG=%JSON_VALUE%"
|
|
exit /b 0
|
|
|
|
:install_device_packages
|
|
"%PS_EXE%" %PS_ARGS% -Command ^
|
|
"python -c \"import sys, os; from lib.classes.device_installer import DeviceInstaller; device = DeviceInstaller(); sys.exit(device.install_device_packages(os.environ.get('DEVICE_INFO_STR', '')))\""
|
|
exit /b %errorlevel%
|
|
|
|
:install_python_packages
|
|
echo Installing python dependencies…
|
|
"%PS_EXE%" %PS_ARGS% -Command ^
|
|
"python -c \"import sys; from lib.classes.device_installer import DeviceInstaller; device = DeviceInstaller(); sys.exit(device.install_python_packages())\""
|
|
exit /b %errorlevel%
|
|
|
|
:check_sitecustomized
|
|
set "src_pyfile=%SAFE_SCRIPT_DIR%\components\sitecustomize.py"
|
|
for /f "delims=" %%a in ('python -c "import sysconfig;print(sysconfig.get_paths()[\"purelib\"])"') do (
|
|
set "site_packages_path=%%a"
|
|
)
|
|
if "%site_packages_path%"=="" (
|
|
echo [WARN] Could not detect Python site-packages
|
|
exit /b 1
|
|
)
|
|
set "dst_pyfile=%site_packages_path%\sitecustomize.py"
|
|
if not exist "%dst_pyfile%" (
|
|
copy /y "%src_pyfile%" "%dst_pyfile%" >nul
|
|
if errorlevel 1 (
|
|
echo %ESC%[31m=============== sitecustomize.py hook error: copy failed.%ESC%[0m
|
|
exit /b 1
|
|
)
|
|
exit /b 0
|
|
)
|
|
:: xcopy /d only overwrites when source is newer than destination
|
|
:: destination ends with '\' so xcopy treats it as a directory, no F/D prompt, no wildcard target
|
|
xcopy /d /y "%src_pyfile%" "%site_packages_path%\" >nul
|
|
if errorlevel 1 (
|
|
echo %ESC%[31m=============== sitecustomize.py hook update failed.%ESC%[0m
|
|
exit /b 1
|
|
)
|
|
exit /b 0
|
|
|
|
:build_docker_image
|
|
setlocal enabledelayedexpansion
|
|
set "ARG=%~1"
|
|
if defined ARG (
|
|
set "ARG_ESCAPED=%ARG:"=\"%"
|
|
) else (
|
|
set "ARG_ESCAPED="
|
|
)
|
|
if "%DOCKER_MODE%"=="podman" (
|
|
if "%PODMAN_DESKTOP%"=="0" (
|
|
echo podman-compose is not running.
|
|
endlocal
|
|
exit /b 1
|
|
)
|
|
) else if "%DOCKER_MODE%"=="compose" (
|
|
if "%DOCKER_DESKTOP%"=="0" (
|
|
echo docker compose is not running.
|
|
endlocal
|
|
exit /b 1
|
|
)
|
|
)
|
|
set "DOCKER_IMG_NAME=%DOCKER_IMG_NAME%:%DEVICE_TAG%"
|
|
set "cmd_options="
|
|
set "py_vers=%PYTHON_VERSION%"
|
|
rem py_vers must follow the prebuilt-wheel ABI, so derive it from the profile pyvenv [major, minor]
|
|
set "ARG_NQ=%ARG:"=%"
|
|
for /f "tokens=2 delims=[]" %%a in ("!ARG_NQ!") do for /f "tokens=1,2 delims=, " %%b in ("%%a") do set "py_vers=%%b.%%c"
|
|
if /i "%DEVICE_TAG:~0,2%"=="cu" (
|
|
set "cmd_options=--gpus all"
|
|
) else if /i "%DEVICE_TAG:~0,6%"=="jetson" (
|
|
set "cmd_options=--runtime nvidia --gpus all"
|
|
) else if /i "%DEVICE_TAG:~0,8%"=="rocm" (
|
|
set "cmd_options=--device=/dev/kfd --device=/dev/dri"
|
|
) else if /i "%DEVICE_TAG%"=="xpu" (
|
|
set "cmd_options=--device=/dev/dri"
|
|
) else if /i "%DEVICE_TAG%"=="mps" (
|
|
set "cmd_options="
|
|
) else if /i "%DEVICE_TAG%"=="cpu" (
|
|
set "cmd_options="
|
|
)
|
|
if /i "%DEVICE_TAG%"=="cpu" (
|
|
set "COMPOSE_PROFILES=cpu"
|
|
) else if /i "%DEVICE_TAG%"=="mps" (
|
|
set "COMPOSE_PROFILES=cpu"
|
|
) else if /i "%DEVICE_TAG:~0,2%"=="cu" (
|
|
set "COMPOSE_PROFILES=cuda"
|
|
) else if /i "%DEVICE_TAG:~0,6%"=="jetson" (
|
|
set "COMPOSE_PROFILES=jetson"
|
|
) else if /i "%DEVICE_TAG:~0,4%"=="rocm" (
|
|
set "COMPOSE_PROFILES=rocm"
|
|
) else if /i "%DEVICE_TAG%"=="xpu" (
|
|
set "COMPOSE_PROFILES=xpu"
|
|
) else (
|
|
set "COMPOSE_PROFILES=cpu"
|
|
)
|
|
set "SERVICE=ebook2audiobook-%COMPOSE_PROFILES%"
|
|
if "%DOCKER_DESKTOP%"=="1" (
|
|
set "wsl_cmd="
|
|
set "WSL_DIR=%SAFE_SCRIPT_DIR%"
|
|
) else (
|
|
set "wsl_cmd=wsl --user root -d %DOCKER_WSL_CONTAINER% --"
|
|
for /f "delims=" %%i in ('wsl --user root -d %DOCKER_WSL_CONTAINER% -- wslpath "%SAFE_SCRIPT_DIR:\=/%"') do set "WSL_DIR=%%i"
|
|
)
|
|
call :get_iso3_lang "%OS_LANG%"
|
|
set "ISO3_LANG=!ISO3_LANG!"
|
|
if "%DOCKER_MODE%"=="podman" (
|
|
echo Using podman-compose
|
|
set "PODMAN_BUILD_ARGS=--format docker --no-cache --network=host"
|
|
set "PODMAN_BUILD_ARGS=%PODMAN_BUILD_ARGS% --build-arg PYTHON_VERSION=%py_vers%"
|
|
set "PODMAN_BUILD_ARGS=%PODMAN_BUILD_ARGS% --build-arg APP_VERSION=%APP_VERSION%"
|
|
set "PODMAN_BUILD_ARGS=%PODMAN_BUILD_ARGS% --build-arg DEVICE_TAG=%DEVICE_TAG%"
|
|
set "PODMAN_BUILD_ARGS=%PODMAN_BUILD_ARGS% --build-arg DOCKER_DEVICE_STR=%ARG_ESCAPED%"
|
|
set "PODMAN_BUILD_ARGS=%PODMAN_BUILD_ARGS% --build-arg DOCKER_PROGRAMS_STR=%DOCKER_PROGRAMS%"
|
|
set "PODMAN_BUILD_ARGS=%PODMAN_BUILD_ARGS% --build-arg CALIBRE_INSTALLER_URL=%DOCKER_CALIBRE_INSTALLER_URL%"
|
|
set "PODMAN_BUILD_ARGS=%PODMAN_BUILD_ARGS% --build-arg ISO3_LANG=%ISO3_LANG%"
|
|
cd /d "%SAFE_SCRIPT_DIR%"
|
|
podman-compose -f podman-compose.yml --profile %COMPOSE_PROFILES% build
|
|
if errorlevel 1 (
|
|
echo Build failed
|
|
endlocal
|
|
exit /b 1
|
|
)
|
|
echo Docker image ready. To run your docker:
|
|
echo Podman Compose:
|
|
echo GUI mode:
|
|
echo podman-compose -f podman-compose.yml --profile %COMPOSE_PROFILES% up
|
|
echo Headless mode:
|
|
echo podman-compose -f podman-compose.yml --profile %COMPOSE_PROFILES% run --rm -v "/mnt/c/Users/myname/whatever/custom_voice:/app/custom_voice" %SERVICE% --headless --ebook "/app/ebooks/tests/test_eng.txt" --tts_engine yourtts --language eng --voice "/app/Desktop/myvoice.wav" etc.
|
|
) else if "%DOCKER_MODE%"=="compose" (
|
|
if "%DOCKER_DESKTOP%"=="1" (
|
|
echo Using docker compose
|
|
docker compose --progress=plain --profile "%COMPOSE_PROFILES%" build --no-cache --build-arg PYTHON_VERSION="%py_vers%" --build-arg APP_VERSION="%APP_VERSION%" --build-arg DEVICE_TAG="%DEVICE_TAG%" --build-arg DOCKER_DEVICE_STR="%ARG_ESCAPED%" --build-arg DOCKER_PROGRAMS_STR="%DOCKER_PROGRAMS%" --build-arg CALIBRE_INSTALLER_URL="%DOCKER_CALIBRE_INSTALLER_URL%" --build-arg ISO3_LANG="%ISO3_LANG%"
|
|
) else (
|
|
echo Using docker compose into WSL2 %DOCKER_WSL_CONTAINER%
|
|
%wsl_cmd% bash -c "cd '%WSL_DIR%' && docker compose --progress=plain --profile '%COMPOSE_PROFILES%' build --no-cache --build-arg PYTHON_VERSION='%py_vers%' --build-arg APP_VERSION='%APP_VERSION%' --build-arg DEVICE_TAG='%DEVICE_TAG%' --build-arg DOCKER_DEVICE_STR=\"%ARG_ESCAPED%\" --build-arg DOCKER_PROGRAMS_STR='%DOCKER_PROGRAMS%' --build-arg CALIBRE_INSTALLER_URL='%DOCKER_CALIBRE_INSTALLER_URL%' --build-arg ISO3_LANG='%ISO3_LANG%'"
|
|
)
|
|
if errorlevel 1 (
|
|
echo Build failed
|
|
endlocal
|
|
exit /b 1
|
|
)
|
|
if defined wsl_cmd (
|
|
set "env_prefix=DEVICE_TAG=%DEVICE_TAG%"
|
|
) else (
|
|
set "env_prefix=set "DEVICE_TAG=%DEVICE_TAG%" ^&^&"
|
|
)
|
|
echo Docker image ready. To run your docker:
|
|
echo Docker Compose:
|
|
echo GUI mode:
|
|
echo !env_prefix! docker compose --profile %COMPOSE_PROFILES% up --no-log-prefix
|
|
echo Headless mode:
|
|
echo !env_prefix! docker compose --profile %COMPOSE_PROFILES% run --rm -v "/mnt/c/Users/myname/whatever/custom_voice:/app/custom_voice" %SERVICE% --headless --ebook "/app/ebooks/tests/test_eng.txt" --tts_engine yourtts --language eng --voice "/app/Desktop/myvoice.wav" etc.
|
|
) else (
|
|
if "%DOCKER_DESKTOP%"=="1" (
|
|
:: echo Using docker buildx
|
|
:: docker buildx use default
|
|
:: docker buildx build --shm-size=4g --progress=plain --no-cache --platform linux/amd64 --build-arg PYTHON_VERSION="%py_vers%" --build-arg APP_VERSION="%APP_VERSION%" --build-arg DEVICE_TAG="%DEVICE_TAG%" --build-arg DOCKER_DEVICE_STR="%ARG_ESCAPED%" --build-arg DOCKER_PROGRAMS_STR="%DOCKER_PROGRAMS%" --build-arg CALIBRE_INSTALLER_URL="%DOCKER_CALIBRE_INSTALLER_URL%" --build-arg ISO3_LANG="%ISO3_LANG%" -t "%DOCKER_IMG_NAME%" .
|
|
echo Using docker build
|
|
docker build --shm-size=4g --progress=plain --no-cache --build-arg PYTHON_VERSION="%py_vers%" --build-arg APP_VERSION="%APP_VERSION%" --build-arg DEVICE_TAG="%DEVICE_TAG%" --build-arg DOCKER_DEVICE_STR="%ARG_ESCAPED%" --build-arg DOCKER_PROGRAMS_STR="%DOCKER_PROGRAMS%" --build-arg CALIBRE_INSTALLER_URL="%DOCKER_CALIBRE_INSTALLER_URL%" --build-arg ISO3_LANG="%ISO3_LANG%" -t "%DOCKER_IMG_NAME%" .
|
|
docker image prune --force
|
|
) else (
|
|
echo Using docker build into WSL2 %DOCKER_WSL_CONTAINER%
|
|
%wsl_cmd% bash -c "service docker status >/dev/null 2>&1 || service docker start"
|
|
timeout /t 3 /nobreak >nul
|
|
:: buildx builder setup no longer needed with docker build
|
|
:: %wsl_cmd% bash -c "cd '%WSL_DIR%' && docker buildx use wslbuilder 2>/dev/null || docker buildx create --name wslbuilder --use"
|
|
:: if errorlevel 1 (
|
|
:: echo Failed to setup buildx builder
|
|
:: endlocal
|
|
:: exit /b 1
|
|
:: )
|
|
%wsl_cmd% bash -c "cd '%WSL_DIR%' && docker build --shm-size=4g --progress=plain --no-cache --build-arg PYTHON_VERSION='%py_vers%' --build-arg APP_VERSION='%APP_VERSION%' --build-arg DEVICE_TAG='%DEVICE_TAG%' --build-arg DOCKER_DEVICE_STR='%ARG_ESCAPED%' --build-arg DOCKER_PROGRAMS_STR='%DOCKER_PROGRAMS%' --build-arg CALIBRE_INSTALLER_URL='%DOCKER_CALIBRE_INSTALLER_URL%' --build-arg ISO3_LANG='%ISO3_LANG%' -t '%DOCKER_IMG_NAME%' ."
|
|
if errorlevel 1 (
|
|
echo Build failed
|
|
endlocal
|
|
exit /b 1
|
|
)
|
|
%wsl_cmd% docker image prune --force
|
|
echo Docker image ready. To run your docker:
|
|
echo GUI mode:
|
|
echo %wsl_cmd% docker run -v ".\ebooks:/app/ebooks" -v ".\audiobooks:/app/audiobooks" -v ".\models:/app/models" -v ".\voices:/app/voices" -v ".\tmp:/app/tmp" !cmd_options!--rm -it -p 7860:7860 %DOCKER_IMG_NAME%
|
|
echo Headless mode:
|
|
echo %wsl_cmd% docker run -v ".\ebooks:/app/ebooks" -v ".\audiobooks:/app/audiobooks" -v ".\models:/app/models" -v ".\voices:/app/voices" -v ".\tmp:/app/tmp" -v "D:\path\to\custom\voices:/app/custom_voice" !cmd_options!--rm -it -p 7860:7860 %DOCKER_IMG_NAME% --headless --ebook "/app/ebooks/myfile.pdf" [--voice /app/custom_voice/voice.wav etc..]
|
|
)
|
|
)
|
|
if "%DOCKER_DESKTOP%"=="1" (
|
|
set "wsl_cmd=wsl --user root -d %DOCKER_WSL_CONTAINER% --"
|
|
)
|
|
endlocal
|
|
exit /b 0
|
|
|
|
:::::::::::: END CORE FUNCTIONS
|
|
|
|
:main
|
|
if defined arguments.help (
|
|
if /i "%arguments.help%"=="true" (
|
|
call :check_python
|
|
if errorlevel 1 goto :install_python
|
|
call :check_docker
|
|
if "%DOCKER_DESKTOP%"=="0" (
|
|
ifi "%PODMAN_DESKTOP"=="0" (
|
|
wsl --user root -d %DOCKER_WSL_CONTAINER% -- which docker >nul 2>&1
|
|
if not errorlevel 1 (
|
|
set DOCKER_IN_WSL=1
|
|
)
|
|
)
|
|
)
|
|
call python -u "%SAFE_SCRIPT_DIR%\app.py" %ARGS%
|
|
goto :eof
|
|
)
|
|
) else (
|
|
if "%SCRIPT_MODE%"=="%BUILD_DOCKER%" (
|
|
if "%DOCKER_DEVICE_STR%"=="" (
|
|
setlocal enabledelayedexpansion
|
|
call :check_python
|
|
if errorlevel 1 goto :install_python
|
|
call :check_wsl
|
|
if errorlevel 1 goto :install_wsl
|
|
call :check_docker
|
|
if errorlevel 1 (
|
|
if not "%DOCKER_MODE%"=="podman" (
|
|
goto :install_docker
|
|
) else (
|
|
goto :failed
|
|
)
|
|
)
|
|
call :check_docker_daemon
|
|
if errorlevel 1 goto :failed
|
|
call :check_device_info %SCRIPT_MODE%
|
|
if errorlevel 1 goto :failed
|
|
call :install_device_packages
|
|
if "!DEVICE_TAG!"=="" (
|
|
call :json_get tag
|
|
if errorlevel 1 goto :failed
|
|
)
|
|
if "%PODMAN_DESKTOP%"=="1" (
|
|
podman image exists "%DOCKER_IMG_NAME%:!DEVICE_TAG!" >nul 2>&1
|
|
) else if "%DOCKER_DESKTOP%"=="1" (
|
|
docker image inspect "%DOCKER_IMG_NAME%:!DEVICE_TAG!" >nul 2>&1
|
|
) else (
|
|
wsl --user root -d %DOCKER_WSL_CONTAINER% -- docker image inspect "%DOCKER_IMG_NAME%:!DEVICE_TAG!" >nul 2>&1
|
|
)
|
|
if not errorlevel 1 (
|
|
echo [STOP] Docker image "%DOCKER_IMG_NAME%:!DEVICE_TAG!" already exists.
|
|
if "%DOCKER_DESKTOP%"=="1" (
|
|
echo To rebuild, first remove it with: docker rmi %DOCKER_IMG_NAME%:!DEVICE_TAG! --force
|
|
) else (
|
|
echo To rebuild, first remove it with: wsl -d %DOCKER_WSL_CONTAINER% -- docker rmi %DOCKER_IMG_NAME%:!DEVICE_TAG! --force
|
|
)
|
|
goto :failed
|
|
)
|
|
call :build_docker_image "!DEVICE_INFO_STR!"
|
|
if errorlevel 1 goto :failed
|
|
endlocal
|
|
) else (
|
|
echo The Docker image is only available with a Linux container
|
|
)
|
|
) else if "%SCRIPT_MODE%"=="%NATIVE%" (
|
|
call :check_scoop
|
|
if errorlevel 1 goto :install_scoop
|
|
call :check_scoop_buckets
|
|
if errorlevel 1 goto :install_scoop_buckets
|
|
call :check_programs
|
|
if errorlevel 1 goto :install_programs
|
|
call :check_conda
|
|
if errorlevel 3 goto :failed
|
|
if errorlevel 2 goto :eof
|
|
if errorlevel 1 goto :install_conda
|
|
call conda activate "%SAFE_SCRIPT_DIR%\%PYTHON_ENV%"
|
|
if errorlevel 1 goto :failed
|
|
call :check_sitecustomized
|
|
if errorlevel 1 goto :failed
|
|
call :build_gui
|
|
call python.exe -u "%SAFE_SCRIPT_DIR%\app.py" --script_mode %SCRIPT_MODE% %ARGS%
|
|
call conda deactivate >nul && call conda deactivate >nul
|
|
) else if "%SCRIPT_MODE%"=="%FULL_DOCKER%" (
|
|
call :check_sitecustomized
|
|
if errorlevel 1 goto :failed
|
|
call python.exe -u "%SAFE_SCRIPT_DIR%\app.py" --script_mode %SCRIPT_MODE% %ARGS%
|
|
)
|
|
)
|
|
goto :eof
|
|
|
|
:failed
|
|
echo =============== ebook2audiobook is not correctly installed.
|
|
where.exe /Q conda && (
|
|
call conda deactivate >nul && call conda deactivate >nul
|
|
)
|
|
exit /b 1
|
|
|
|
:quit
|
|
set "CODE=%~1"
|
|
endlocal
|
|
exit /b %CODE%
|
|
|
|
:restart_script
|
|
net session >nul 2>&1
|
|
if not errorlevel 1 (
|
|
echo Restarting as normal user %USERNAME%…
|
|
schtasks /create /tn "RestartScript" /tr "cmd /k cd /d \"%SAFE_SCRIPT_DIR%\" & call %APP_FILE% %ARGS%" /sc once /st 00:00 /ru "%USERNAME%" /it /f >nul 2>&1
|
|
schtasks /run /tn "RestartScript" >nul 2>&1
|
|
timeout /t 2 /nobreak >nul
|
|
schtasks /delete /tn "RestartScript" /f >nul 2>&1
|
|
exit 0
|
|
)
|
|
start "%APP_NAME%" cmd /k "cd /d "%SAFE_SCRIPT_DIR%" & call %APP_FILE% %ARGS%"
|
|
exit 0
|
|
|
|
:restart_script_admin
|
|
echo Restarting script as Administrator…
|
|
call "%PS_EXE%" -NoLogo -NoProfile -NonInteractive -ExecutionPolicy Bypass -Command "Start-Process -FilePath '%SAFE_SCRIPT_DIR%\%APP_FILE%' -ArgumentList '%ARGS%' -Verb RunAs"
|
|
exit
|
|
|
|
endlocal
|
|
pause |