@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