diff --git a/cmake/macros/rv_stage.cmake b/cmake/macros/rv_stage.cmake index 61bd10f0..5b5226a7 100644 --- a/cmake/macros/rv_stage.cmake +++ b/cmake/macros/rv_stage.cmake @@ -504,6 +504,12 @@ FUNCTION(rv_stage) ENDIF() ADD_DEPENDENCIES(image_formats ${arg_TARGET}) + # Track the plugin file so the image formats .gto cache is regenerated when any plugin is relinked + SET_PROPERTY( + TARGET image_formats + APPEND + PROPERTY RV_FORMAT_PLUGIN_FILES "$" + ) ADD_SHARED_LIBRARY_LIST(${arg_TARGET}) @@ -533,6 +539,12 @@ FUNCTION(rv_stage) ENDIF() ADD_DEPENDENCIES(movie_formats ${arg_TARGET}) + # Track the plugin file so the movie formats .gto cache is regenerated when any plugin is relinked + SET_PROPERTY( + TARGET movie_formats + APPEND + PROPERTY RV_FORMAT_PLUGIN_FILES "$" + ) ADD_SHARED_LIBRARY_LIST(${arg_TARGET}) diff --git a/packages/rv/CMakeLists.txt b/packages/rv/CMakeLists.txt index bdb16f83..57fdeeca 100644 --- a/packages/rv/CMakeLists.txt +++ b/packages/rv/CMakeLists.txt @@ -19,7 +19,9 @@ CONFIGURE_FILE(rviorc.mu ${RV_STAGE_SCRIPTS_DIR}/utv/rviorc.mu COPYONLY) CONFIGURE_FILE(rvrc.py ${RV_STAGE_SCRIPTS_DIR}/utv/rvrc.py COPYONLY) CONFIGURE_FILE(rvrc.mu ${RV_STAGE_SCRIPTS_DIR}/utv/utvrc.mu COPYONLY) CONFIGURE_FILE(rvrc.py ${RV_STAGE_SCRIPTS_DIR}/utv/utvrc.py COPYONLY) -IF(NOT RV_USE_SYSTEM_DEPS) +IF(NOT RV_USE_SYSTEM_DEPS + AND NOT RV_TARGET_WINDOWS +) CONFIGURE_FILE(qt.conf ${RV_STAGE_BIN_DIR}/qt.conf COPYONLY) ENDIF() diff --git a/scripts/install.ps1 b/scripts/install.ps1 index 660efabe..33edc617 100644 --- a/scripts/install.ps1 +++ b/scripts/install.ps1 @@ -112,51 +112,55 @@ Write-Host "Target Directory: $InstallDir" -ForegroundColor White Write-Host "Administrative Privileges: $isAdmin`n" -ForegroundColor White # Step 1: Check and Install OpenUTVDeps -if (-not $SkipDeps) { - Write-Host "--- Checking OpenUTV Dependencies (v$DepsVersion) ---" -ForegroundColor Cyan - $depsFound = $false - $detectedDepsPath = "" - - if ($env:OPENUTV_DEPS_ROOT -and (Test-Path "$env:OPENUTV_DEPS_ROOT\bin\OpenImageIO.dll")) { - $depsFound = $true - $detectedDepsPath = $env:OPENUTV_DEPS_ROOT - } elseif (Test-Path "C:\Program Files\OpenUTVDeps $DepsVersion\bin\OpenImageIO.dll") { - $depsFound = $true - $detectedDepsPath = "C:\Program Files\OpenUTVDeps $DepsVersion" - } elseif (Test-Path "C:\Program Files\OpenUTVDeps*\bin\OpenImageIO.dll") { - $found = Get-Item "C:\Program Files\OpenUTVDeps*\bin\OpenImageIO.dll" | Select-Object -First 1 +Write-Host "--- Checking OpenUTV Dependencies (v$DepsVersion) ---" -ForegroundColor Cyan +$depsFound = $false +$detectedDepsPath = "" + +if (Test-Path "C:\Program Files\OpenUTVDeps $DepsVersion\bin\OpenImageIO.dll") { + $depsFound = $true + $detectedDepsPath = "C:\Program Files\OpenUTVDeps $DepsVersion" +} elseif ($env:UTV_DEPS_ROOT -and (Test-Path "$env:UTV_DEPS_ROOT\bin\OpenImageIO.dll")) { + $depsFound = $true + $detectedDepsPath = $env:UTV_DEPS_ROOT +} elseif ($env:OPENUTV_DEPS_ROOT -and (Test-Path "$env:OPENUTV_DEPS_ROOT\bin\OpenImageIO.dll")) { + $depsFound = $true + $detectedDepsPath = $env:OPENUTV_DEPS_ROOT +} elseif (Test-Path "C:\Program Files\OpenUTVDeps*\bin\OpenImageIO.dll") { + $found = Get-Item "C:\Program Files\OpenUTVDeps*\bin\OpenImageIO.dll" | Select-Object -First 1 + $depsFound = $true + $detectedDepsPath = $found.Directory.Parent.FullName +} else { + $uninst = Get-ItemProperty "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*" -ErrorAction SilentlyContinue | + Where-Object { $_.DisplayName -like "OpenUTVDeps $DepsVersion*" -or $_.DisplayName -like "OpenUTV Dependencies $DepsVersion*" } | + Select-Object -First 1 + if ($uninst -and $uninst.InstallLocation -and (Test-Path "$($uninst.InstallLocation)\bin\OpenImageIO.dll")) { $depsFound = $true - $detectedDepsPath = $found.Directory.Parent.FullName - } else { - $uninst = Get-ItemProperty "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*" -ErrorAction SilentlyContinue | - Where-Object { $_.DisplayName -like "OpenUTVDeps $DepsVersion*" -or $_.DisplayName -like "OpenUTV Dependencies $DepsVersion*" } | - Select-Object -First 1 - if ($uninst -and $uninst.InstallLocation -and (Test-Path "$($uninst.InstallLocation)\bin\OpenImageIO.dll")) { - $depsFound = $true - $detectedDepsPath = $uninst.InstallLocation.TrimEnd('\') - } + $detectedDepsPath = $uninst.InstallLocation.TrimEnd('\') } +} - if ($depsFound) { - Write-Host "OpenUTV dependencies detected at: $detectedDepsPath" -ForegroundColor Green - } else { - Write-Host "OpenUTV dependencies not found. Downloading OpenUTVDeps MSI installer..." -ForegroundColor Yellow - $msiUrl = "https://github.com/$RepoOwner/$DepsRepoName/releases/download/v$DepsVersion/OpenUTVDeps-$DepsVersion-win64.msi" - $tempMsi = Join-Path $env:TEMP "OpenUTVDeps-$DepsVersion-win64.msi" +if ($depsFound) { + Write-Host "OpenUTV dependencies detected at: $detectedDepsPath" -ForegroundColor Green +} elseif (-not $SkipDeps) { + Write-Host "OpenUTV dependencies not found. Downloading OpenUTVDeps MSI installer..." -ForegroundColor Yellow + $msiUrl = "https://github.com/$RepoOwner/$DepsRepoName/releases/download/v$DepsVersion/OpenUTVDeps-$DepsVersion-win64.msi" + $tempMsi = Join-Path $env:TEMP "OpenUTVDeps-$DepsVersion-win64.msi" - Write-Host "Downloading $msiUrl..." -ForegroundColor White - Invoke-WebRequest -Uri $msiUrl -OutFile $tempMsi -UseBasicParsing + Write-Host "Downloading $msiUrl..." -ForegroundColor White + Invoke-WebRequest -Uri $msiUrl -OutFile $tempMsi -UseBasicParsing - Write-Host "Installing OpenUTVDeps (silent MSI install)..." -ForegroundColor Yellow - $msiProc = Start-Process msiexec.exe -ArgumentList "/i `"$tempMsi`" /qn /norestart MSIFASTINSTALL=7" -Wait -PassThru - Remove-Item -Force $tempMsi -ErrorAction SilentlyContinue + Write-Host "Installing OpenUTVDeps (silent MSI install)..." -ForegroundColor Yellow + $msiProc = Start-Process msiexec.exe -ArgumentList "/i `"$tempMsi`" /qn /norestart MSIFASTINSTALL=7" -Wait -PassThru + Remove-Item -Force $tempMsi -ErrorAction SilentlyContinue - if ($msiProc.ExitCode -ne 0 -and $msiProc.ExitCode -ne 3010) { - Write-Error "Failed to install OpenUTVDeps MSI. Exit code: $($msiProc.ExitCode)" - return - } - Write-Host "OpenUTVDeps installed successfully!" -ForegroundColor Green + if ($msiProc.ExitCode -ne 0 -and $msiProc.ExitCode -ne 3010) { + Write-Error "Failed to install OpenUTVDeps MSI. Exit code: $($msiProc.ExitCode)" + return } + Write-Host "OpenUTVDeps installed successfully!" -ForegroundColor Green + $detectedDepsPath = "C:\Program Files\OpenUTVDeps $DepsVersion" +} else { + Write-Host "Skipping OpenUTVDeps download/install (-SkipDeps)." -ForegroundColor Gray } # Step 2: Resolve UTV Release Asset @@ -208,20 +212,174 @@ if (-not (Test-Path $utvExe)) { return } -# Step 4: Configure PATH -Write-Host "`n--- Configuring Environment ---" -ForegroundColor Cyan +# Step 4: Configure PATH, Isolated CLI Shims, and Environment +Write-Host "`n--- Configuring Environment & Isolated CLI Tools ---" -ForegroundColor Cyan $binDir = Join-Path $InstallDir "bin" $pathScope = if ($isAdmin) { "Machine" } else { "User" } + +# Remove orphaned legacy qt.conf if present without plugins\Qt (prevents blocking PySide6 Qt plugin loading) +$legacyQtConf = Join-Path $binDir "qt.conf" +$qtPluginDir = Join-Path $InstallDir "plugins\Qt" +if ((Test-Path $legacyQtConf) -and -not (Test-Path $qtPluginDir)) { + Remove-Item -Force $legacyQtConf -ErrorAction SilentlyContinue + Write-Host "Removed legacy qt.conf." -ForegroundColor Gray +} + +# Ensure dedicated CLI tools (utvio, utvpkg, py-interp, utvls) have -bin copies and launcher shims +$utvExe = Join-Path $binDir "utv.exe" +$cliTools = @("utvio", "utvpkg", "py-interp", "utvls") +foreach ($tool in $cliTools) { + $toolExe = Join-Path $binDir "$tool.exe" + $toolBin = Join-Path $binDir "$tool-bin.exe" + if ((Test-Path $toolExe) -and -not (Test-Path $toolBin) -and (Test-Path $utvExe)) { + Move-Item -Force -Path $toolExe -Destination $toolBin + Copy-Item -Force -Path $utvExe -Destination $toolExe + Write-Host "Configured hermetic launcher for $tool.exe" -ForegroundColor Gray + } +} + +# Deploy companion .cmd scripts for seamless CLI usage without PATH pollution +$cmdShimTemplate = @' +@echo off +setlocal + +:: Discover OpenUTVDeps runtime root directory +set "DEPS_ROOT=" +if defined UTV_DEPS_ROOT if exist "%UTV_DEPS_ROOT%\bin\OpenImageIO.dll" set "DEPS_ROOT=%UTV_DEPS_ROOT%" +if not defined DEPS_ROOT if defined OPENUTV_DEPS_ROOT if exist "%OPENUTV_DEPS_ROOT%\bin\OpenImageIO.dll" set "DEPS_ROOT=%OPENUTV_DEPS_ROOT%" + +if not defined DEPS_ROOT ( + for /f "tokens=2*" %%a in ('reg query "HKCU\Environment" /v "UTV_DEPS_ROOT" 2^>nul') do if exist "%%b\bin\OpenImageIO.dll" set "DEPS_ROOT=%%b" +) +if not defined DEPS_ROOT ( + for /f "tokens=2*" %%a in ('reg query "HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Environment" /v "UTV_DEPS_ROOT" 2^>nul') do if exist "%%b\bin\OpenImageIO.dll" set "DEPS_ROOT=%%b" +) +if not defined DEPS_ROOT ( + for /f "tokens=2*" %%a in ('reg query "HKCU\Environment" /v "OPENUTV_DEPS_ROOT" 2^>nul') do if exist "%%b\bin\OpenImageIO.dll" set "DEPS_ROOT=%%b" +) +if not defined DEPS_ROOT ( + for /f "tokens=2*" %%a in ('reg query "HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Environment" /v "OPENUTV_DEPS_ROOT" 2^>nul') do if exist "%%b\bin\OpenImageIO.dll" set "DEPS_ROOT=%%b" +) +if not defined DEPS_ROOT ( + for /f "tokens=2*" %%a in ('reg query "HKCU\Software\OpenUTV" /v "DepsPath" 2^>nul') do if exist "%%b\bin\OpenImageIO.dll" set "DEPS_ROOT=%%b" +) +if not defined DEPS_ROOT ( + for /f "tokens=2*" %%a in ('reg query "HKLM\Software\OpenUTV" /v "DepsPath" 2^>nul') do if exist "%%b\bin\OpenImageIO.dll" set "DEPS_ROOT=%%b" +) +if not defined DEPS_ROOT ( + for /d %%d in ("%ProgramFiles%\OpenUTVDeps*") do if exist "%%d\bin\OpenImageIO.dll" set "DEPS_ROOT=%%d" +) +if not defined DEPS_ROOT ( + for /d %%d in ("%LOCALAPPDATA%\Programs\OpenUTVDeps*") do if exist "%%d\bin\OpenImageIO.dll" set "DEPS_ROOT=%%d" +) + +if not defined DEPS_ROOT ( + echo OpenUTV: Could not locate OpenUTVDeps runtime dependencies. >&2 + exit /b 1 +) + +set "APP_DIR=%~dp0" +if "%APP_DIR:~-1%"=="\" set "APP_DIR=%APP_DIR:~0,-1%" + +set "PATH=%APP_DIR%;%DEPS_ROOT%\bin;%DEPS_ROOT%\tools\python3\Lib\site-packages\PySide6;%DEPS_ROOT%\tools\python3;%DEPS_ROOT%\tools\python3\Scripts;%PATH%" +set "PYTHONHOME=%DEPS_ROOT%\tools\python3" +set "QT_PLUGIN_PATH=%DEPS_ROOT%\tools\python3\Lib\site-packages\PySide6\plugins" +set "QT_QPA_PLATFORM_PLUGIN_PATH=%DEPS_ROOT%\tools\python3\Lib\site-packages\PySide6\plugins\platforms" +set "UTV_DEPS_ROOT=%DEPS_ROOT%" +set "OPENUTV_DEPS_ROOT=%DEPS_ROOT%" +set "UTV_HOME=%APP_DIR%" +set "OPENUTV_HOME=%APP_DIR%" +'@ + +# Write openutv-run.cmd +$openutvRunContent = $cmdShimTemplate + "`r`n`r`n%*`r`nexit /b %ERRORLEVEL%`r`n" +Set-Content -Path (Join-Path $binDir "openutv-run.cmd") -Value $openutvRunContent -Encoding ASCII + +# Write utvio.cmd, utvpkg.cmd, py-interp.cmd, utvls.cmd +foreach ($tool in $cliTools) { + $dispatch = @" + +if exist "%APP_DIR%\$tool-bin.exe" ( + "%APP_DIR%\$tool-bin.exe" %* +) else if exist "%APP_DIR%\$tool.exe" ( + "%APP_DIR%\$tool.exe" %* +) else ( + echo OpenUTV: Could not locate $tool executable. >&2 + exit /b 1 +) +exit /b %ERRORLEVEL% +"@ + Set-Content -Path (Join-Path $binDir "$tool.cmd") -Value ($cmdShimTemplate + $dispatch) -Encoding ASCII +} + +# Write openutv-diagnostics.cmd and openutv-check-updates.cmd +$diagDispatch = @" + +if exist "%APP_DIR%\py-interp-bin.exe" ( + "%APP_DIR%\py-interp-bin.exe" "%APP_DIR%\openutv-diagnostics.py" %* +) else if exist "%APP_DIR%\py-interp.exe" ( + "%APP_DIR%\py-interp.exe" "%APP_DIR%\openutv-diagnostics.py" %* +) else if exist "%DEPS_ROOT%\tools\python3\python.exe" ( + "%DEPS_ROOT%\tools\python3\python.exe" "%APP_DIR%\openutv-diagnostics.py" %* +) else ( + python "%APP_DIR%\openutv-diagnostics.py" %* +) +exit /b %ERRORLEVEL% +"@ +Set-Content -Path (Join-Path $binDir "openutv-diagnostics.cmd") -Value ($cmdShimTemplate + $diagDispatch) -Encoding ASCII + +$updateDispatch = @" + +if exist "%APP_DIR%\py-interp-bin.exe" ( + "%APP_DIR%\py-interp-bin.exe" "%APP_DIR%\openutv-check-updates.py" %* +) else if exist "%APP_DIR%\py-interp.exe" ( + "%APP_DIR%\py-interp.exe" "%APP_DIR%\openutv-check-updates.py" %* +) else if exist "%DEPS_ROOT%\tools\python3\python.exe" ( + "%DEPS_ROOT%\tools\python3\python.exe" "%APP_DIR%\openutv-check-updates.py" %* +) else ( + python "%APP_DIR%\openutv-check-updates.py" %* +) +exit /b %ERRORLEVEL% +"@ +Set-Content -Path (Join-Path $binDir "openutv-check-updates.cmd") -Value ($cmdShimTemplate + $updateDispatch) -Encoding ASCII + +# Configure persistent environment variables +if ($detectedDepsPath -and (Test-Path $detectedDepsPath)) { + [Environment]::SetEnvironmentVariable("UTV_DEPS_ROOT", $detectedDepsPath, $pathScope) + [Environment]::SetEnvironmentVariable("OPENUTV_DEPS_ROOT", $detectedDepsPath, $pathScope) + $env:UTV_DEPS_ROOT = $detectedDepsPath + $env:OPENUTV_DEPS_ROOT = $detectedDepsPath +} +[Environment]::SetEnvironmentVariable("UTV_HOME", $InstallDir, $pathScope) +[Environment]::SetEnvironmentVariable("OPENUTV_HOME", $InstallDir, $pathScope) +$env:UTV_HOME = $InstallDir +$env:OPENUTV_HOME = $InstallDir + +# Clean global pollution: remove PYTHONHOME, QT_PLUGIN_PATH, QT_QPA_PLATFORM_PLUGIN_PATH +[Environment]::SetEnvironmentVariable("PYTHONHOME", $null, $pathScope) +[Environment]::SetEnvironmentVariable("QT_PLUGIN_PATH", $null, $pathScope) +[Environment]::SetEnvironmentVariable("QT_QPA_PLATFORM_PLUGIN_PATH", $null, $pathScope) +$env:PYTHONHOME = $null +$env:QT_PLUGIN_PATH = $null +$env:QT_QPA_PLATFORM_PLUGIN_PATH = $null + +# Configure PATH: ONLY $binDir, zero pollution from OpenUTVDeps/Scoop/Choco $currentEnvPath = [Environment]::GetEnvironmentVariable("Path", $pathScope) -if ($currentEnvPath -notlike "*$binDir*") { - $newEnvPath = "$binDir;$currentEnvPath" - [Environment]::SetEnvironmentVariable("Path", $newEnvPath, $pathScope) - $env:Path = "$binDir;$env:Path" - Write-Host "Added $binDir to $pathScope PATH." -ForegroundColor Green -} else { - Write-Host "$binDir is already present in $pathScope PATH." -ForegroundColor Gray +$cleanList = ($currentEnvPath -split ";") | Where-Object { + $_ -ne "" -and + $_ -notlike "*OpenUTVDeps*" -and + $_ -notlike "*openutv-dependencies*" -and + $_ -ne $binDir } +$newEnvPath = (@($binDir) + $cleanList) -join ";" +[Environment]::SetEnvironmentVariable("Path", $newEnvPath, $pathScope) +$env:Path = (@($binDir) + (($env:Path -split ";") | Where-Object { $_ -notlike "*OpenUTVDeps*" -and $_ -notlike "*openutv-dependencies*" -and $_ -ne $binDir })) -join ";" + +Write-Host "Environment configured hermetically (Zero `$PATH pollution)." -ForegroundColor Green +Write-Host " + Added to $pathScope PATH: $binDir" -ForegroundColor Gray +Write-Host " - Cleaned dependency folders and global PYTHONHOME/QT_PLUGIN_PATH from environment." -ForegroundColor Gray + # Step 5: Create Start Menu and Desktop Shortcuts if (-not $NoShortcuts) { Write-Host "`n--- Creating Shortcuts ---" -ForegroundColor Cyan diff --git a/src/bin/CMakeLists.txt b/src/bin/CMakeLists.txt index 13750458..77e4d7c9 100644 --- a/src/bin/CMakeLists.txt +++ b/src/bin/CMakeLists.txt @@ -23,6 +23,11 @@ CONFIGURE_FILE(${_HELPER_SRC_DIR}/openutv-check-updates.py ${RV_STAGE_BIN_DIR}/o IF(WIN32) CONFIGURE_FILE(${_HELPER_SRC_DIR}/openutv-diagnostics.cmd ${RV_STAGE_BIN_DIR}/openutv-diagnostics.cmd COPYONLY) CONFIGURE_FILE(${_HELPER_SRC_DIR}/openutv-check-updates.cmd ${RV_STAGE_BIN_DIR}/openutv-check-updates.cmd COPYONLY) + CONFIGURE_FILE(${_HELPER_SRC_DIR}/openutv-run.cmd ${RV_STAGE_BIN_DIR}/openutv-run.cmd COPYONLY) + CONFIGURE_FILE(${_HELPER_SRC_DIR}/utvio.cmd ${RV_STAGE_BIN_DIR}/utvio.cmd COPYONLY) + CONFIGURE_FILE(${_HELPER_SRC_DIR}/utvpkg.cmd ${RV_STAGE_BIN_DIR}/utvpkg.cmd COPYONLY) + CONFIGURE_FILE(${_HELPER_SRC_DIR}/py-interp.cmd ${RV_STAGE_BIN_DIR}/py-interp.cmd COPYONLY) + CONFIGURE_FILE(${_HELPER_SRC_DIR}/utvls.cmd ${RV_STAGE_BIN_DIR}/utvls.cmd COPYONLY) ELSE() CONFIGURE_FILE(${_HELPER_SRC_DIR}/openutv-diagnostics.py ${RV_STAGE_BIN_DIR}/openutv-diagnostics COPYONLY) CONFIGURE_FILE(${_HELPER_SRC_DIR}/openutv-check-updates.py ${RV_STAGE_BIN_DIR}/openutv-check-updates COPYONLY) diff --git a/src/bin/apps/rv/UTVLauncherWin.cpp b/src/bin/apps/rv/UTVLauncherWin.cpp index 84efc8a5..1e750b57 100644 --- a/src/bin/apps/rv/UTVLauncherWin.cpp +++ b/src/bin/apps/rv/UTVLauncherWin.cpp @@ -79,7 +79,8 @@ namespace { // 1. Check if user explicitly requested software GL via environment variable wchar_t envBuf[32]; - if (GetEnvironmentVariableW(L"OPENUTV_SOFTWARE_GL", envBuf, 32) > 0 || GetEnvironmentVariableW(L"QT_OPENGL", envBuf, 32) > 0) + if (GetEnvironmentVariableW(L"UTV_SOFTWARE_GL", envBuf, 32) > 0 || GetEnvironmentVariableW(L"OPENUTV_SOFTWARE_GL", envBuf, 32) > 0 + || GetEnvironmentVariableW(L"QT_OPENGL", envBuf, 32) > 0) { if (_wcsicmp(envBuf, L"software") == 0 || wcscmp(envBuf, L"1") == 0) { @@ -372,17 +373,21 @@ namespace bool FindDependencies(const std::wstring& appDir, std::wstring& outRootDir, std::wstring& outBinDir, std::wstring& outPySideDir, std::wstring& outPythonDir) { - // 1. Environment variable OPENUTV_DEPS_ROOT - DWORD len = GetEnvironmentVariableW(L"OPENUTV_DEPS_ROOT", NULL, 0); - if (len > 0) + // 1. Environment variables: UTV_DEPS_ROOT (primary), OPENUTV_DEPS_ROOT (fallback) + const wchar_t* envVars[] = {L"UTV_DEPS_ROOT", L"OPENUTV_DEPS_ROOT"}; + for (const wchar_t* ev : envVars) { - std::vector buf(len); - GetEnvironmentVariableW(L"OPENUTV_DEPS_ROOT", buf.data(), len); - std::wstring envRoot(buf.data()); - if (CheckDepsDir(envRoot, outBinDir, outPySideDir, outPythonDir)) + DWORD len = GetEnvironmentVariableW(ev, NULL, 0); + if (len > 0) { - outRootDir = envRoot; - return true; + std::vector buf(len); + GetEnvironmentVariableW(ev, buf.data(), len); + std::wstring envRoot(buf.data()); + if (CheckDepsDir(envRoot, outBinDir, outPySideDir, outPythonDir)) + { + outRootDir = envRoot; + return true; + } } } @@ -391,17 +396,20 @@ namespace if (RegOpenKeyExW(HKEY_LOCAL_MACHINE, L"SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment", 0, KEY_READ, &hKey) == ERROR_SUCCESS) { - wchar_t regVal[MAX_PATH]; - DWORD valSize = sizeof(regVal); - DWORD valType = 0; - if (RegQueryValueExW(hKey, L"OPENUTV_DEPS_ROOT", NULL, &valType, reinterpret_cast(regVal), &valSize) == ERROR_SUCCESS) + for (const wchar_t* ev : envVars) { - std::wstring regRoot(regVal); - if (CheckDepsDir(regRoot, outBinDir, outPySideDir, outPythonDir)) + wchar_t regVal[MAX_PATH]; + DWORD valSize = sizeof(regVal); + DWORD valType = 0; + if (RegQueryValueExW(hKey, ev, NULL, &valType, reinterpret_cast(regVal), &valSize) == ERROR_SUCCESS) { - outRootDir = regRoot; - RegCloseKey(hKey); - return true; + std::wstring regRoot(regVal); + if (CheckDepsDir(regRoot, outBinDir, outPySideDir, outPythonDir)) + { + outRootDir = regRoot; + RegCloseKey(hKey); + return true; + } } } RegCloseKey(hKey); @@ -410,23 +418,50 @@ namespace // 3. User Environment in registry if (RegOpenKeyExW(HKEY_CURRENT_USER, L"Environment", 0, KEY_READ, &hKey) == ERROR_SUCCESS) { - wchar_t regVal[MAX_PATH]; - DWORD valSize = sizeof(regVal); - DWORD valType = 0; - if (RegQueryValueExW(hKey, L"OPENUTV_DEPS_ROOT", NULL, &valType, reinterpret_cast(regVal), &valSize) == ERROR_SUCCESS) + for (const wchar_t* ev : envVars) { - std::wstring regRoot(regVal); - if (CheckDepsDir(regRoot, outBinDir, outPySideDir, outPythonDir)) + wchar_t regVal[MAX_PATH]; + DWORD valSize = sizeof(regVal); + DWORD valType = 0; + if (RegQueryValueExW(hKey, ev, NULL, &valType, reinterpret_cast(regVal), &valSize) == ERROR_SUCCESS) { - outRootDir = regRoot; - RegCloseKey(hKey); - return true; + std::wstring regRoot(regVal); + if (CheckDepsDir(regRoot, outBinDir, outPySideDir, outPythonDir)) + { + outRootDir = regRoot; + RegCloseKey(hKey); + return true; + } } } RegCloseKey(hKey); } - // 4. Windows Uninstall registry keys (detects any installed OpenUTVDeps MSI package) + // 4. Software\OpenUTV registry keys (DepsPath) + const HKEY appRootKeys[] = {HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE}; + for (HKEY rk : appRootKeys) + { + HKEY hAppKey = NULL; + if (RegOpenKeyExW(rk, L"Software\\OpenUTV", 0, KEY_READ, &hAppKey) == ERROR_SUCCESS) + { + wchar_t regVal[MAX_PATH]; + DWORD valSize = sizeof(regVal); + DWORD valType = 0; + if (RegQueryValueExW(hAppKey, L"DepsPath", NULL, &valType, reinterpret_cast(regVal), &valSize) == ERROR_SUCCESS) + { + std::wstring regRoot(regVal); + if (CheckDepsDir(regRoot, outBinDir, outPySideDir, outPythonDir)) + { + outRootDir = regRoot; + RegCloseKey(hAppKey); + return true; + } + } + RegCloseKey(hAppKey); + } + } + + // 5. Windows Uninstall registry keys (detects any installed OpenUTVDeps MSI package) const HKEY rootKeys[] = {HKEY_LOCAL_MACHINE, HKEY_CURRENT_USER}; const wchar_t* subKeyPaths[] = {L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall", L"SOFTWARE\\WOW6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall"}; @@ -668,8 +703,18 @@ namespace int RunLauncher() { - // Try to attach to parent console if running from cmd.exe or PowerShell - AttachConsole(ATTACH_PARENT_PROCESS); + // Attach to parent console if running from cmd.exe or PowerShell + HANDLE hExistingStdOut = GetStdHandle(STD_OUTPUT_HANDLE); + if (hExistingStdOut == NULL || hExistingStdOut == INVALID_HANDLE_VALUE) + { + if (AttachConsole(ATTACH_PARENT_PROCESS)) + { + FILE* fp = nullptr; + freopen_s(&fp, "CONOUT$", "w", stdout); + freopen_s(&fp, "CONOUT$", "w", stderr); + freopen_s(&fp, "CONIN$", "r", stdin); + } + } std::wstring appDir = GetAppDir(); std::wstring depsRoot; @@ -718,12 +763,18 @@ int RunLauncher() } } - // Export OPENUTV_DEPS_ROOT and OPENUTV_DEPS_ROOT_SLASH for runtime child processes + // Export UTV_DEPS_ROOT and OPENUTV_DEPS_ROOT for runtime child processes + SetEnvironmentVariableW(L"UTV_DEPS_ROOT", depsRoot.c_str()); SetEnvironmentVariableW(L"OPENUTV_DEPS_ROOT", depsRoot.c_str()); std::wstring rootSlash = depsRoot; std::replace(rootSlash.begin(), rootSlash.end(), L'\\', L'/'); + SetEnvironmentVariableW(L"UTV_DEPS_ROOT_SLASH", rootSlash.c_str()); SetEnvironmentVariableW(L"OPENUTV_DEPS_ROOT_SLASH", rootSlash.c_str()); + // Export UTV_HOME and OPENUTV_HOME + SetEnvironmentVariableW(L"UTV_HOME", appDir.c_str()); + SetEnvironmentVariableW(L"OPENUTV_HOME", appDir.c_str()); + // Prepend dependency paths to process PATH std::wstring currentPath; DWORD pathLen = GetEnvironmentVariableW(L"PATH", NULL, 0); @@ -791,6 +842,26 @@ int RunLauncher() } } + // Configure QT_QPA_PLATFORM_PLUGIN_PATH if not explicitly specified + if (GetEnvironmentVariableW(L"QT_QPA_PLATFORM_PLUGIN_PATH", NULL, 0) == 0) + { + if (!depsPySide.empty()) + { + std::wstring platformsDir = depsPySide + L"\\plugins\\platforms"; + if (DirExists(platformsDir)) + { + SetEnvironmentVariableW(L"QT_QPA_PLATFORM_PLUGIN_PATH", platformsDir.c_str()); + } + } + } + + // Remove legacy orphaned qt.conf if plugins/Qt does not exist (prevents blocking PySide6 Qt plugin loading) + std::wstring legacyQtConf = appDir + L"\\qt.conf"; + if (FileExists(legacyQtConf) && !DirExists(appDir + L"\\plugins\\Qt")) + { + DeleteFileW(legacyQtConf.c_str()); + } + // Configure QtWebEngine and QML paths if PySide6 Qt is used if (!depsPySide.empty()) { @@ -870,57 +941,175 @@ int RunLauncher() SetEnvironmentVariableW(L"QT_OPENGL", L"desktop"); } - // Locate core application executable: utv-bin.exe (or rv-bin.exe) - std::wstring targetExe = appDir + L"\\utv-bin.exe"; - if (!FileExists(targetExe)) + // Parse command line arguments for target resolution + int argc = 0; + LPWSTR* argv = CommandLineToArgvW(GetCommandLineW(), &argc); + std::wstring targetExe; + std::wstring cmdLine; + + if (argv && argc >= 3 && _wcsicmp(argv[1], L"--run") == 0) { - targetExe = appDir + L"\\rv-bin.exe"; - if (!FileExists(targetExe)) + // Explicit tool execution mode: utv --run [arguments...] + std::wstring cmd = argv[2]; + if (FileExists(cmd)) { - MessageBoxW(NULL, L"Unable to locate utv-bin.exe or rv-bin.exe in the application directory.", L"OpenUTV Launcher Error", - MB_ICONERROR | MB_OK); - return 1; + targetExe = cmd; } - } - - // Build command line preserving all caller arguments - std::wstring cmdLine = L"\"" + targetExe + L"\""; - const wchar_t* rawCmd = GetCommandLineW(); - if (rawCmd) - { - const wchar_t* p = rawCmd; - while (*p == L' ' || *p == L'\t') + else if (FileExists(cmd + L".exe")) + { + targetExe = cmd + L".exe"; + } + else if (FileExists(appDir + L"\\" + cmd)) + { + targetExe = appDir + L"\\" + cmd; + } + else if (FileExists(appDir + L"\\" + cmd + L".exe")) + { + targetExe = appDir + L"\\" + cmd + L".exe"; + } + else if (FileExists(appDir + L"\\" + cmd + L"-bin.exe")) + { + targetExe = appDir + L"\\" + cmd + L"-bin.exe"; + } + else if (!depsBin.empty() && FileExists(depsBin + L"\\" + cmd)) + { + targetExe = depsBin + L"\\" + cmd; + } + else if (!depsBin.empty() && FileExists(depsBin + L"\\" + cmd + L".exe")) + { + targetExe = depsBin + L"\\" + cmd + L".exe"; + } + else if (!depsPython.empty() && FileExists(depsPython + L"\\" + cmd)) { - p++; + targetExe = depsPython + L"\\" + cmd; } - if (*p == L'"') + else if (!depsPython.empty() && FileExists(depsPython + L"\\" + cmd + L".exe")) { - p++; - while (*p && *p != L'"') + targetExe = depsPython + L"\\" + cmd + L".exe"; + } + else + { + wchar_t found[MAX_PATH]; + LPWSTR fPart = NULL; + if (SearchPathW(NULL, cmd.c_str(), L".exe", MAX_PATH, found, &fPart) > 0) { - p++; + targetExe = found; } - if (*p == L'"') + else { - p++; + targetExe = cmd; } } - else + + cmdLine = L"\"" + targetExe + L"\""; + for (int i = 3; i < argc; ++i) { - while (*p && *p != L' ' && *p != L'\t') + cmdLine += L" "; + if (wcschr(argv[i], L' ') != nullptr || wcschr(argv[i], L'\t') != nullptr) { - p++; + cmdLine += L"\""; + cmdLine += argv[i]; + cmdLine += L"\""; + } + else + { + cmdLine += argv[i]; } } - while (*p == L' ' || *p == L'\t') + } + else + { + // Detect launcher own executable name (e.g. utv.exe, utvio.exe, utvpkg.exe, py-interp.exe) + std::vector modBuf(MAX_PATH); + DWORD mLen = GetModuleFileNameW(NULL, modBuf.data(), static_cast(modBuf.size())); + while (mLen >= modBuf.size()) { - p++; + modBuf.resize(modBuf.size() * 2); + mLen = GetModuleFileNameW(NULL, modBuf.data(), static_cast(modBuf.size())); } - if (*p) + std::wstring fullMod(modBuf.data(), mLen); + size_t slash = fullMod.find_last_of(L"\\/"); + std::wstring ownName = (slash != std::wstring::npos) ? fullMod.substr(slash + 1) : fullMod; + std::wstring baseName = ownName; + if (baseName.length() >= 4 && _wcsicmp(baseName.c_str() + baseName.length() - 4, L".exe") == 0) { - cmdLine += L" "; - cmdLine += p; + baseName = baseName.substr(0, baseName.length() - 4); + } + + if (_wcsicmp(baseName.c_str(), L"utv") != 0 && _wcsicmp(baseName.c_str(), L"rv") != 0) + { + // Dedicated tool launcher shim (e.g. utvio, utvpkg, py-interp, utvls) + std::wstring candBin = appDir + L"\\" + baseName + L"-bin.exe"; + if (FileExists(candBin)) + { + targetExe = candBin; + } + else if (_wcsicmp(baseName.c_str(), L"py-interp") == 0 && !depsPython.empty() && FileExists(depsPython + L"\\python.exe")) + { + targetExe = depsPython + L"\\python.exe"; + } + else + { + std::wstring candExe = appDir + L"\\" + baseName + L".exe"; + if (FileExists(candExe) && _wcsicmp(candExe.c_str(), fullMod.c_str()) != 0) + { + targetExe = candExe; + } + } } + + if (targetExe.empty()) + { + // Default OpenUTV / OpenRV viewer binary + targetExe = appDir + L"\\utv-bin.exe"; + if (!FileExists(targetExe)) + { + targetExe = appDir + L"\\rv-bin.exe"; + if (!FileExists(targetExe)) + { + MessageBoxW(NULL, L"Unable to locate utv-bin.exe or rv-bin.exe in the application directory.", + L"OpenUTV Launcher Error", MB_ICONERROR | MB_OK); + if (argv) + LocalFree(argv); + return 1; + } + } + } + + // Build command line preserving caller arguments + cmdLine = L"\"" + targetExe + L"\""; + const wchar_t* rawCmd = GetCommandLineW(); + if (rawCmd) + { + const wchar_t* p = rawCmd; + while (*p == L' ' || *p == L'\t') + p++; + if (*p == L'"') + { + p++; + while (*p && *p != L'"') + p++; + if (*p == L'"') + p++; + } + else + { + while (*p && *p != L' ' && *p != L'\t') + p++; + } + while (*p == L' ' || *p == L'\t') + p++; + if (*p) + { + cmdLine += L" "; + cmdLine += p; + } + } + } + + if (argv) + { + LocalFree(argv); } std::vector cmdBuf(cmdLine.begin(), cmdLine.end()); @@ -952,8 +1141,9 @@ int RunLauncher() if (!created) { DWORD err = GetLastError(); - wchar_t errMsg[256]; - swprintf_s(errMsg, 256, L"Failed to launch core application (utv-bin.exe).\nError code: %lu", err); + wchar_t errMsg[512]; + swprintf_s(errMsg, 512, L"Failed to launch '%s'.\nError code: %lu", targetExe.c_str(), err); + fwprintf(stderr, L"OpenUTV Launcher Error: Failed to launch '%s' (error code: %lu)\n", targetExe.c_str(), err); MessageBoxW(NULL, errMsg, L"OpenUTV Launcher Error", MB_ICONERROR | MB_OK); return 1; } diff --git a/src/bin/apps/rv/main.cpp b/src/bin/apps/rv/main.cpp index a57a658a..77ed13d7 100644 --- a/src/bin/apps/rv/main.cpp +++ b/src/bin/apps/rv/main.cpp @@ -248,7 +248,8 @@ int utf8Main(int argc, char* argv[]) // Verify OpenUTVDeps is installed or discoverable before initializing the runtime bool depsFound = false; char depsRootBuf[MAX_PATH] = {0}; - if (GetEnvironmentVariableA("OPENUTV_DEPS_ROOT", depsRootBuf, MAX_PATH) > 0) + if (GetEnvironmentVariableA("UTV_DEPS_ROOT", depsRootBuf, MAX_PATH) > 0 + || GetEnvironmentVariableA("OPENUTV_DEPS_ROOT", depsRootBuf, MAX_PATH) > 0) { std::string testPath = std::string(depsRootBuf) + "\\bin"; DWORD attr = GetFileAttributesA(testPath.c_str()); @@ -291,6 +292,7 @@ int utf8Main(int argc, char* argv[]) if (attr != INVALID_FILE_ATTRIBUTES && (attr & FILE_ATTRIBUTE_DIRECTORY)) { depsFound = true; + SetEnvironmentVariableA("UTV_DEPS_ROOT", candidate.c_str()); SetEnvironmentVariableA("OPENUTV_DEPS_ROOT", candidate.c_str()); char currentPath[32767] = {0}; GetEnvironmentVariableA("PATH", currentPath, 32767); diff --git a/src/bin/imgtools/makeFBIOformats/CMakeLists.txt b/src/bin/imgtools/makeFBIOformats/CMakeLists.txt index ea14819d..9d2f6a52 100644 --- a/src/bin/imgtools/makeFBIOformats/CMakeLists.txt +++ b/src/bin/imgtools/makeFBIOformats/CMakeLists.txt @@ -34,7 +34,7 @@ ADD_CUSTOM_COMMAND( OUTPUT ${RV_STAGE_PLUGINS_IMAGEFORMATS_DIR}/formats.gto COMMAND $ ${RV_STAGE_PLUGINS_IMAGEFORMATS_DIR} WORKING_DIRECTORY ${RV_STAGE_BIN_DIR} - DEPENDS ${_target} image_formats + DEPENDS ${_target} image_formats "$>" ) ADD_CUSTOM_TARGET( diff --git a/src/bin/imgtools/makeMovieIOformats/CMakeLists.txt b/src/bin/imgtools/makeMovieIOformats/CMakeLists.txt index a719f4a5..2f6ba1be 100644 --- a/src/bin/imgtools/makeMovieIOformats/CMakeLists.txt +++ b/src/bin/imgtools/makeMovieIOformats/CMakeLists.txt @@ -52,7 +52,7 @@ ADD_CUSTOM_COMMAND( OUTPUT ${RV_STAGE_PLUGINS_MOVIEFORMATS_DIR}/movieformats.gto COMMAND $ ${RV_STAGE_PLUGINS_MOVIEFORMATS_DIR} WORKING_DIRECTORY ${RV_STAGE_BIN_DIR} - DEPENDS ${_target} movie_formats + DEPENDS ${_target} movie_formats "$>" ) ADD_CUSTOM_TARGET( diff --git a/src/bin/nsapps/UTV/UTVLauncher.mm b/src/bin/nsapps/UTV/UTVLauncher.mm index 20e307ed..55c48e5a 100644 --- a/src/bin/nsapps/UTV/UTVLauncher.mm +++ b/src/bin/nsapps/UTV/UTVLauncher.mm @@ -42,6 +42,16 @@ }; static NSString *detectBrewPrefix(void) { + const char *customDeps = getenv("UTV_DEPS_ROOT"); + if (!customDeps) customDeps = getenv("OPENUTV_DEPS_ROOT"); + if (!customDeps) customDeps = getenv("HOMEBREW_PREFIX"); + if (customDeps && strlen(customDeps) > 0) { + NSString *prefix = [NSString stringWithUTF8String:customDeps]; + if ([[NSFileManager defaultManager] fileExistsAtPath:prefix]) { + return prefix; + } + } + NSFileManager *fm = [NSFileManager defaultManager]; if ([fm fileExistsAtPath:@"/opt/homebrew/bin/brew"]) { return @"/opt/homebrew"; @@ -328,6 +338,39 @@ int main(int argc, char *argv[]) { // All dependencies satisfied -> launch real binary immediately if ([missing count] == 0) { + // Set UTV environment variables for runtime child processes + if (getenv("UTV_DEPS_ROOT") == NULL) { + setenv("UTV_DEPS_ROOT", [brewPrefix UTF8String], 1); + } + if (getenv("OPENUTV_DEPS_ROOT") == NULL) { + setenv("OPENUTV_DEPS_ROOT", [brewPrefix UTF8String], 1); + } + if (getenv("UTV_HOME") == NULL) { + NSString *bundlePath = [[NSBundle mainBundle] bundlePath]; + if (bundlePath) { + setenv("UTV_HOME", [bundlePath UTF8String], 1); + setenv("OPENUTV_HOME", [bundlePath UTF8String], 1); + } + } + + // Check if --run [args...] mode was requested + if (argc >= 3 && strcmp(argv[1], "--run") == 0) { + NSString *targetCmd = [NSString stringWithUTF8String:argv[2]]; + NSString *binDir = [[[NSBundle mainBundle] executablePath] stringByDeletingLastPathComponent]; + NSString *targetPath = [binDir stringByAppendingPathComponent:targetCmd]; + if (![[NSFileManager defaultManager] fileExistsAtPath:targetPath]) { + targetPath = [[brewPrefix stringByAppendingPathComponent:@"bin"] stringByAppendingPathComponent:targetCmd]; + } + if ([[NSFileManager defaultManager] fileExistsAtPath:targetPath]) { + execv([targetPath UTF8String], &argv[2]); + perror("UTVLauncher: execv --run failed"); + return 1; + } else { + fprintf(stderr, "UTVLauncher: Command '%s' not found.\n", argv[2]); + return 1; + } + } + NSString *realBin = findRealBinary(); if (realBin) { execv([realBin UTF8String], argv); diff --git a/src/bin/nsapps/UTV/openutv-check-updates.cmd b/src/bin/nsapps/UTV/openutv-check-updates.cmd index d49a57df..60d03e16 100644 --- a/src/bin/nsapps/UTV/openutv-check-updates.cmd +++ b/src/bin/nsapps/UTV/openutv-check-updates.cmd @@ -1,8 +1,55 @@ @echo off -setlocal +setlocal enabledelayedexpansion set "DIR=%~dp0" + +:: 1. Discover OpenUTVDeps Root +if not defined UTV_DEPS_ROOT if defined OPENUTV_DEPS_ROOT set "UTV_DEPS_ROOT=%OPENUTV_DEPS_ROOT%" +if not defined UTV_DEPS_ROOT ( + for /f "tokens=2*" %%a in ('reg query "HKCU\Environment" /v "UTV_DEPS_ROOT" 2^>nul') do set "UTV_DEPS_ROOT=%%b" +) +if not defined UTV_DEPS_ROOT ( + for /f "tokens=2*" %%a in ('reg query "HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Environment" /v "UTV_DEPS_ROOT" 2^>nul') do set "UTV_DEPS_ROOT=%%b" +) +if not defined UTV_DEPS_ROOT ( + for /f "tokens=2*" %%a in ('reg query "HKCU\Environment" /v "OPENUTV_DEPS_ROOT" 2^>nul') do set "UTV_DEPS_ROOT=%%b" +) +if not defined UTV_DEPS_ROOT ( + for /f "tokens=2*" %%a in ('reg query "HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Environment" /v "OPENUTV_DEPS_ROOT" 2^>nul') do set "UTV_DEPS_ROOT=%%b" +) +if not defined UTV_DEPS_ROOT ( + for /f "tokens=2*" %%a in ('reg query "HKCU\Software\OpenUTV" /v "DepsPath" 2^>nul') do set "UTV_DEPS_ROOT=%%b" +) +if not defined UTV_DEPS_ROOT ( + for /f "tokens=2*" %%a in ('reg query "HKLM\Software\OpenUTV" /v "DepsPath" 2^>nul') do set "UTV_DEPS_ROOT=%%b" +) +if not defined UTV_DEPS_ROOT ( + for /d %%d in ("%ProgramFiles%\OpenUTVDeps*") do if exist "%%d\bin\OpenImageIO.dll" set "UTV_DEPS_ROOT=%%d" +) +if not defined UTV_DEPS_ROOT ( + for /d %%d in ("%LOCALAPPDATA%\Programs\OpenUTVDeps*") do if exist "%%d\bin\OpenImageIO.dll" set "UTV_DEPS_ROOT=%%d" +) +if not defined OPENUTV_DEPS_ROOT set "OPENUTV_DEPS_ROOT=%UTV_DEPS_ROOT%" + +:: 2. Configure Environment if Dependencies Found +if defined UTV_DEPS_ROOT ( + set "DEPS_BIN=%UTV_DEPS_ROOT%\bin" + set "DEPS_PY=%UTV_DEPS_ROOT%\tools\python3" + set "DEPS_PYSIDE=%UTV_DEPS_ROOT%\tools\python3\Lib\site-packages\PySide6" + set "PATH=!DEPS_BIN!;!DEPS_PY!;!DEPS_PYSIDE!;%DIR%;!PATH!" + if not defined PYTHONHOME set "PYTHONHOME=!DEPS_PY!" + if not defined QT_PLUGIN_PATH if exist "!DEPS_PYSIDE!\plugins" set "QT_PLUGIN_PATH=!DEPS_PYSIDE!\plugins" + if not defined QT_QPA_PLATFORM_PLUGIN_PATH if exist "!DEPS_PYSIDE!\plugins\platforms" set "QT_QPA_PLATFORM_PLUGIN_PATH=!DEPS_PYSIDE!\plugins\platforms" +) + +:: 3. Remove orphaned legacy qt.conf if present without plugins\Qt +if exist "%DIR%qt.conf" if not exist "%DIR%..\plugins\Qt" del /f /q "%DIR%qt.conf" >nul 2>&1 + +:: 4. Execute Update Checker if exist "%DIR%py-interp.exe" ( "%DIR%py-interp.exe" "%DIR%openutv-check-updates.py" %* +) else if defined DEPS_PY if exist "!DEPS_PY!\python.exe" ( + "!DEPS_PY!\python.exe" "%DIR%openutv-check-updates.py" %* ) else ( python "%DIR%openutv-check-updates.py" %* ) +exit /b %ERRORLEVEL% diff --git a/src/bin/nsapps/UTV/openutv-check-updates.sh b/src/bin/nsapps/UTV/openutv-check-updates.sh index ad2c9f16..7da1e673 100755 --- a/src/bin/nsapps/UTV/openutv-check-updates.sh +++ b/src/bin/nsapps/UTV/openutv-check-updates.sh @@ -1,9 +1,24 @@ #!/bin/bash +# OpenUTV Check Updates Wrapper + +# Ensure Homebrew environment is available +if [ -f "/opt/homebrew/bin/brew" ]; then + eval "$(/opt/homebrew/bin/brew shellenv 2>/dev/null)" +elif [ -f "/usr/local/bin/brew" ]; then + eval "$(/usr/local/bin/brew shellenv 2>/dev/null)" +fi + DIR="$(cd "$(dirname "$0")" && pwd)" +SCRIPT="${DIR}/openutv-check-updates.py" + if [ -x "${DIR}/py-interp" ]; then - exec "${DIR}/py-interp" "${DIR}/openutv-check-updates.py" "$@" + exec "${DIR}/py-interp" "${SCRIPT}" "$@" +elif [ -x "${DIR}/../MacOS/py-interp" ]; then + exec "${DIR}/../MacOS/py-interp" "${SCRIPT}" "$@" +elif [ -x "${DIR}/../bin/py-interp" ]; then + exec "${DIR}/../bin/py-interp" "${SCRIPT}" "$@" elif command -v python3 &>/dev/null; then - exec python3 "${DIR}/openutv-check-updates.py" "$@" + exec python3 "${SCRIPT}" "$@" else - exec python "${DIR}/openutv-check-updates.py" "$@" + exec python "${SCRIPT}" "$@" fi diff --git a/src/bin/nsapps/UTV/openutv-diagnostics.cmd b/src/bin/nsapps/UTV/openutv-diagnostics.cmd index 74bd88c5..ddc40a4a 100644 --- a/src/bin/nsapps/UTV/openutv-diagnostics.cmd +++ b/src/bin/nsapps/UTV/openutv-diagnostics.cmd @@ -1,8 +1,55 @@ @echo off -setlocal +setlocal enabledelayedexpansion set "DIR=%~dp0" + +:: 1. Discover OpenUTVDeps Root +if not defined UTV_DEPS_ROOT if defined OPENUTV_DEPS_ROOT set "UTV_DEPS_ROOT=%OPENUTV_DEPS_ROOT%" +if not defined UTV_DEPS_ROOT ( + for /f "tokens=2*" %%a in ('reg query "HKCU\Environment" /v "UTV_DEPS_ROOT" 2^>nul') do set "UTV_DEPS_ROOT=%%b" +) +if not defined UTV_DEPS_ROOT ( + for /f "tokens=2*" %%a in ('reg query "HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Environment" /v "UTV_DEPS_ROOT" 2^>nul') do set "UTV_DEPS_ROOT=%%b" +) +if not defined UTV_DEPS_ROOT ( + for /f "tokens=2*" %%a in ('reg query "HKCU\Environment" /v "OPENUTV_DEPS_ROOT" 2^>nul') do set "UTV_DEPS_ROOT=%%b" +) +if not defined UTV_DEPS_ROOT ( + for /f "tokens=2*" %%a in ('reg query "HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Environment" /v "OPENUTV_DEPS_ROOT" 2^>nul') do set "UTV_DEPS_ROOT=%%b" +) +if not defined UTV_DEPS_ROOT ( + for /f "tokens=2*" %%a in ('reg query "HKCU\Software\OpenUTV" /v "DepsPath" 2^>nul') do set "UTV_DEPS_ROOT=%%b" +) +if not defined UTV_DEPS_ROOT ( + for /f "tokens=2*" %%a in ('reg query "HKLM\Software\OpenUTV" /v "DepsPath" 2^>nul') do set "UTV_DEPS_ROOT=%%b" +) +if not defined UTV_DEPS_ROOT ( + for /d %%d in ("%ProgramFiles%\OpenUTVDeps*") do if exist "%%d\bin\OpenImageIO.dll" set "UTV_DEPS_ROOT=%%d" +) +if not defined UTV_DEPS_ROOT ( + for /d %%d in ("%LOCALAPPDATA%\Programs\OpenUTVDeps*") do if exist "%%d\bin\OpenImageIO.dll" set "UTV_DEPS_ROOT=%%d" +) +if not defined OPENUTV_DEPS_ROOT set "OPENUTV_DEPS_ROOT=%UTV_DEPS_ROOT%" + +:: 2. Configure Environment if Dependencies Found +if defined UTV_DEPS_ROOT ( + set "DEPS_BIN=%UTV_DEPS_ROOT%\bin" + set "DEPS_PY=%UTV_DEPS_ROOT%\tools\python3" + set "DEPS_PYSIDE=%UTV_DEPS_ROOT%\tools\python3\Lib\site-packages\PySide6" + set "PATH=!DEPS_BIN!;!DEPS_PY!;!DEPS_PYSIDE!;%DIR%;!PATH!" + if not defined PYTHONHOME set "PYTHONHOME=!DEPS_PY!" + if not defined QT_PLUGIN_PATH if exist "!DEPS_PYSIDE!\plugins" set "QT_PLUGIN_PATH=!DEPS_PYSIDE!\plugins" + if not defined QT_QPA_PLATFORM_PLUGIN_PATH if exist "!DEPS_PYSIDE!\plugins\platforms" set "QT_QPA_PLATFORM_PLUGIN_PATH=!DEPS_PYSIDE!\plugins\platforms" +) + +:: 3. Remove orphaned legacy qt.conf if present without plugins\Qt +if exist "%DIR%qt.conf" if not exist "%DIR%..\plugins\Qt" del /f /q "%DIR%qt.conf" >nul 2>&1 + +:: 4. Execute Diagnostics if exist "%DIR%py-interp.exe" ( "%DIR%py-interp.exe" "%DIR%openutv-diagnostics.py" %* +) else if defined DEPS_PY if exist "!DEPS_PY!\python.exe" ( + "!DEPS_PY!\python.exe" "%DIR%openutv-diagnostics.py" %* ) else ( python "%DIR%openutv-diagnostics.py" %* ) +exit /b %ERRORLEVEL% diff --git a/src/bin/nsapps/UTV/openutv-diagnostics.py b/src/bin/nsapps/UTV/openutv-diagnostics.py index 97dc3ddd..edfeab33 100755 --- a/src/bin/nsapps/UTV/openutv-diagnostics.py +++ b/src/bin/nsapps/UTV/openutv-diagnostics.py @@ -425,7 +425,18 @@ def notify_user(zip_path): def main(): + if "--help" in sys.argv or "-h" in sys.argv: + print("Usage: openutv-diagnostics [OPTIONS]") + print("Collects system info, logs, crash reports, and configs into a zip archive.") + print() + print("Options:") + print(" --no-browser Do not open the GitHub bug report page in a browser") + print(" --silent Do not show GUI dialogs or open file manager (for headless/scripts)") + print(" --help, -h Show this help message") + sys.exit(0) + no_browser = "--no-browser" in sys.argv + silent = "--silent" in sys.argv or "--quiet" in sys.argv # 1. Setup temp collection directory temp_base = tempfile.mkdtemp(prefix="openutv_diag_") @@ -465,14 +476,18 @@ def main(): pass # 4. Reveal zip file in Finder / Explorer / Nautilus - reveal_file(zip_path) + if not silent: + reveal_file(zip_path) # 5. Open browser if requested - if not no_browser: + if not no_browser and not silent: webbrowser.open(GITHUB_BUG_URL) # 6. Show confirmation dialog - notify_user(zip_path) + if not silent: + notify_user(zip_path) + else: + print(f"Diagnostics package created successfully: {zip_path}") sys.exit(0) diff --git a/src/bin/nsapps/UTV/openutv-diagnostics.sh b/src/bin/nsapps/UTV/openutv-diagnostics.sh index 3ac2a4de..4fac5f03 100755 --- a/src/bin/nsapps/UTV/openutv-diagnostics.sh +++ b/src/bin/nsapps/UTV/openutv-diagnostics.sh @@ -1,9 +1,24 @@ #!/bin/bash +# OpenUTV Diagnostics Wrapper + +# Ensure Homebrew environment is available +if [ -f "/opt/homebrew/bin/brew" ]; then + eval "$(/opt/homebrew/bin/brew shellenv 2>/dev/null)" +elif [ -f "/usr/local/bin/brew" ]; then + eval "$(/usr/local/bin/brew shellenv 2>/dev/null)" +fi + DIR="$(cd "$(dirname "$0")" && pwd)" +SCRIPT="${DIR}/openutv-diagnostics.py" + if [ -x "${DIR}/py-interp" ]; then - exec "${DIR}/py-interp" "${DIR}/openutv-diagnostics.py" "$@" + exec "${DIR}/py-interp" "${SCRIPT}" "$@" +elif [ -x "${DIR}/../MacOS/py-interp" ]; then + exec "${DIR}/../MacOS/py-interp" "${SCRIPT}" "$@" +elif [ -x "${DIR}/../bin/py-interp" ]; then + exec "${DIR}/../bin/py-interp" "${SCRIPT}" "$@" elif command -v python3 &>/dev/null; then - exec python3 "${DIR}/openutv-diagnostics.py" "$@" + exec python3 "${SCRIPT}" "$@" else - exec python "${DIR}/openutv-diagnostics.py" "$@" + exec python "${SCRIPT}" "$@" fi diff --git a/src/bin/nsapps/UTV/openutv-run.cmd b/src/bin/nsapps/UTV/openutv-run.cmd new file mode 100644 index 00000000..ba96ef09 --- /dev/null +++ b/src/bin/nsapps/UTV/openutv-run.cmd @@ -0,0 +1,52 @@ +@echo off +setlocal + +:: Discover OpenUTVDeps runtime root directory +set "DEPS_ROOT=" +if defined UTV_DEPS_ROOT if exist "%UTV_DEPS_ROOT%\bin\OpenImageIO.dll" set "DEPS_ROOT=%UTV_DEPS_ROOT%" +if not defined DEPS_ROOT if defined OPENUTV_DEPS_ROOT if exist "%OPENUTV_DEPS_ROOT%\bin\OpenImageIO.dll" set "DEPS_ROOT=%OPENUTV_DEPS_ROOT%" + +if not defined DEPS_ROOT ( + for /f "tokens=2*" %%a in ('reg query "HKCU\Environment" /v "UTV_DEPS_ROOT" 2^>nul') do if exist "%%b\bin\OpenImageIO.dll" set "DEPS_ROOT=%%b" +) +if not defined DEPS_ROOT ( + for /f "tokens=2*" %%a in ('reg query "HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Environment" /v "UTV_DEPS_ROOT" 2^>nul') do if exist "%%b\bin\OpenImageIO.dll" set "DEPS_ROOT=%%b" +) +if not defined DEPS_ROOT ( + for /f "tokens=2*" %%a in ('reg query "HKCU\Environment" /v "OPENUTV_DEPS_ROOT" 2^>nul') do if exist "%%b\bin\OpenImageIO.dll" set "DEPS_ROOT=%%b" +) +if not defined DEPS_ROOT ( + for /f "tokens=2*" %%a in ('reg query "HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Environment" /v "OPENUTV_DEPS_ROOT" 2^>nul') do if exist "%%b\bin\OpenImageIO.dll" set "DEPS_ROOT=%%b" +) +if not defined DEPS_ROOT ( + for /f "tokens=2*" %%a in ('reg query "HKCU\Software\OpenUTV" /v "DepsPath" 2^>nul') do if exist "%%b\bin\OpenImageIO.dll" set "DEPS_ROOT=%%b" +) +if not defined DEPS_ROOT ( + for /f "tokens=2*" %%a in ('reg query "HKLM\Software\OpenUTV" /v "DepsPath" 2^>nul') do if exist "%%b\bin\OpenImageIO.dll" set "DEPS_ROOT=%%b" +) +if not defined DEPS_ROOT ( + for /d %%d in ("%ProgramFiles%\OpenUTVDeps*") do if exist "%%d\bin\OpenImageIO.dll" set "DEPS_ROOT=%%d" +) +if not defined DEPS_ROOT ( + for /d %%d in ("%LOCALAPPDATA%\Programs\OpenUTVDeps*") do if exist "%%d\bin\OpenImageIO.dll" set "DEPS_ROOT=%%d" +) + +if not defined DEPS_ROOT ( + echo OpenUTV: Could not locate OpenUTVDeps runtime dependencies. >&2 + exit /b 1 +) + +set "APP_DIR=%~dp0" +if "%APP_DIR:~-1%"=="\" set "APP_DIR=%APP_DIR:~0,-1%" + +set "PATH=%APP_DIR%;%DEPS_ROOT%\bin;%DEPS_ROOT%\tools\python3\Lib\site-packages\PySide6;%DEPS_ROOT%\tools\python3;%DEPS_ROOT%\tools\python3\Scripts;%PATH%" +set "PYTHONHOME=%DEPS_ROOT%\tools\python3" +set "QT_PLUGIN_PATH=%DEPS_ROOT%\tools\python3\Lib\site-packages\PySide6\plugins" +set "QT_QPA_PLATFORM_PLUGIN_PATH=%DEPS_ROOT%\tools\python3\Lib\site-packages\PySide6\plugins\platforms" +set "UTV_DEPS_ROOT=%DEPS_ROOT%" +set "OPENUTV_DEPS_ROOT=%DEPS_ROOT%" +set "UTV_HOME=%APP_DIR%" +set "OPENUTV_HOME=%APP_DIR%" + +%* +exit /b %ERRORLEVEL% diff --git a/src/bin/nsapps/UTV/openutv-supercharge-ffmpeg.sh b/src/bin/nsapps/UTV/openutv-supercharge-ffmpeg.sh index b2870e91..be4bc5b3 100755 --- a/src/bin/nsapps/UTV/openutv-supercharge-ffmpeg.sh +++ b/src/bin/nsapps/UTV/openutv-supercharge-ffmpeg.sh @@ -1,9 +1,24 @@ #!/bin/bash +# OpenUTV Supercharge FFmpeg Wrapper + +# Ensure Homebrew environment is available +if [ -f "/opt/homebrew/bin/brew" ]; then + eval "$(/opt/homebrew/bin/brew shellenv 2>/dev/null)" +elif [ -f "/usr/local/bin/brew" ]; then + eval "$(/usr/local/bin/brew shellenv 2>/dev/null)" +fi + DIR="$(cd "$(dirname "$0")" && pwd)" +SCRIPT="${DIR}/openutv-supercharge-ffmpeg.py" + if [ -x "${DIR}/py-interp" ]; then - exec "${DIR}/py-interp" "${DIR}/openutv-supercharge-ffmpeg.py" "$@" + exec "${DIR}/py-interp" "${SCRIPT}" "$@" +elif [ -x "${DIR}/../MacOS/py-interp" ]; then + exec "${DIR}/../MacOS/py-interp" "${SCRIPT}" "$@" +elif [ -x "${DIR}/../bin/py-interp" ]; then + exec "${DIR}/../bin/py-interp" "${SCRIPT}" "$@" elif command -v python3 &>/dev/null; then - exec python3 "${DIR}/openutv-supercharge-ffmpeg.py" "$@" + exec python3 "${SCRIPT}" "$@" else - exec python "${DIR}/openutv-supercharge-ffmpeg.py" "$@" + exec python "${SCRIPT}" "$@" fi diff --git a/src/bin/nsapps/UTV/py-interp.cmd b/src/bin/nsapps/UTV/py-interp.cmd new file mode 100644 index 00000000..69612965 --- /dev/null +++ b/src/bin/nsapps/UTV/py-interp.cmd @@ -0,0 +1,61 @@ +@echo off +setlocal + +:: Discover OpenUTVDeps runtime root directory +set "DEPS_ROOT=" +if defined UTV_DEPS_ROOT if exist "%UTV_DEPS_ROOT%\bin\OpenImageIO.dll" set "DEPS_ROOT=%UTV_DEPS_ROOT%" +if not defined DEPS_ROOT if defined OPENUTV_DEPS_ROOT if exist "%OPENUTV_DEPS_ROOT%\bin\OpenImageIO.dll" set "DEPS_ROOT=%OPENUTV_DEPS_ROOT%" + +if not defined DEPS_ROOT ( + for /f "tokens=2*" %%a in ('reg query "HKCU\Environment" /v "UTV_DEPS_ROOT" 2^>nul') do if exist "%%b\bin\OpenImageIO.dll" set "DEPS_ROOT=%%b" +) +if not defined DEPS_ROOT ( + for /f "tokens=2*" %%a in ('reg query "HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Environment" /v "UTV_DEPS_ROOT" 2^>nul') do if exist "%%b\bin\OpenImageIO.dll" set "DEPS_ROOT=%%b" +) +if not defined DEPS_ROOT ( + for /f "tokens=2*" %%a in ('reg query "HKCU\Environment" /v "OPENUTV_DEPS_ROOT" 2^>nul') do if exist "%%b\bin\OpenImageIO.dll" set "DEPS_ROOT=%%b" +) +if not defined DEPS_ROOT ( + for /f "tokens=2*" %%a in ('reg query "HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Environment" /v "OPENUTV_DEPS_ROOT" 2^>nul') do if exist "%%b\bin\OpenImageIO.dll" set "DEPS_ROOT=%%b" +) +if not defined DEPS_ROOT ( + for /f "tokens=2*" %%a in ('reg query "HKCU\Software\OpenUTV" /v "DepsPath" 2^>nul') do if exist "%%b\bin\OpenImageIO.dll" set "DEPS_ROOT=%%b" +) +if not defined DEPS_ROOT ( + for /f "tokens=2*" %%a in ('reg query "HKLM\Software\OpenUTV" /v "DepsPath" 2^>nul') do if exist "%%b\bin\OpenImageIO.dll" set "DEPS_ROOT=%%b" +) +if not defined DEPS_ROOT ( + for /d %%d in ("%ProgramFiles%\OpenUTVDeps*") do if exist "%%d\bin\OpenImageIO.dll" set "DEPS_ROOT=%%d" +) +if not defined DEPS_ROOT ( + for /d %%d in ("%LOCALAPPDATA%\Programs\OpenUTVDeps*") do if exist "%%d\bin\OpenImageIO.dll" set "DEPS_ROOT=%%d" +) + +if not defined DEPS_ROOT ( + echo OpenUTV: Could not locate OpenUTVDeps runtime dependencies. >&2 + exit /b 1 +) + +set "APP_DIR=%~dp0" +if "%APP_DIR:~-1%"=="\" set "APP_DIR=%APP_DIR:~0,-1%" + +set "PATH=%APP_DIR%;%DEPS_ROOT%\bin;%DEPS_ROOT%\tools\python3\Lib\site-packages\PySide6;%DEPS_ROOT%\tools\python3;%DEPS_ROOT%\tools\python3\Scripts;%PATH%" +set "PYTHONHOME=%DEPS_ROOT%\tools\python3" +set "QT_PLUGIN_PATH=%DEPS_ROOT%\tools\python3\Lib\site-packages\PySide6\plugins" +set "QT_QPA_PLATFORM_PLUGIN_PATH=%DEPS_ROOT%\tools\python3\Lib\site-packages\PySide6\plugins\platforms" +set "UTV_DEPS_ROOT=%DEPS_ROOT%" +set "OPENUTV_DEPS_ROOT=%DEPS_ROOT%" +set "UTV_HOME=%APP_DIR%" +set "OPENUTV_HOME=%APP_DIR%" + +if exist "%APP_DIR%\py-interp-bin.exe" ( + "%APP_DIR%\py-interp-bin.exe" %* +) else if exist "%APP_DIR%\py-interp.exe" ( + "%APP_DIR%\py-interp.exe" %* +) else if exist "%DEPS_ROOT%\tools\python3\python.exe" ( + "%DEPS_ROOT%\tools\python3\python.exe" %* +) else ( + echo OpenUTV: Could not locate py-interp or Python executable. >&2 + exit /b 1 +) +exit /b %ERRORLEVEL% diff --git a/src/bin/nsapps/UTV/utvio.cmd b/src/bin/nsapps/UTV/utvio.cmd new file mode 100644 index 00000000..1184388c --- /dev/null +++ b/src/bin/nsapps/UTV/utvio.cmd @@ -0,0 +1,63 @@ +@echo off +setlocal + +:: Discover OpenUTVDeps runtime root directory +set "DEPS_ROOT=" +if defined UTV_DEPS_ROOT if exist "%UTV_DEPS_ROOT%\bin\OpenImageIO.dll" set "DEPS_ROOT=%UTV_DEPS_ROOT%" +if not defined DEPS_ROOT if defined OPENUTV_DEPS_ROOT if exist "%OPENUTV_DEPS_ROOT%\bin\OpenImageIO.dll" set "DEPS_ROOT=%OPENUTV_DEPS_ROOT%" + +if not defined DEPS_ROOT ( + for /f "tokens=2*" %%a in ('reg query "HKCU\Environment" /v "UTV_DEPS_ROOT" 2^>nul') do if exist "%%b\bin\OpenImageIO.dll" set "DEPS_ROOT=%%b" +) +if not defined DEPS_ROOT ( + for /f "tokens=2*" %%a in ('reg query "HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Environment" /v "UTV_DEPS_ROOT" 2^>nul') do if exist "%%b\bin\OpenImageIO.dll" set "DEPS_ROOT=%%b" +) +if not defined DEPS_ROOT ( + for /f "tokens=2*" %%a in ('reg query "HKCU\Environment" /v "OPENUTV_DEPS_ROOT" 2^>nul') do if exist "%%b\bin\OpenImageIO.dll" set "DEPS_ROOT=%%b" +) +if not defined DEPS_ROOT ( + for /f "tokens=2*" %%a in ('reg query "HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Environment" /v "OPENUTV_DEPS_ROOT" 2^>nul') do if exist "%%b\bin\OpenImageIO.dll" set "DEPS_ROOT=%%b" +) +if not defined DEPS_ROOT ( + for /f "tokens=2*" %%a in ('reg query "HKCU\Software\OpenUTV" /v "DepsPath" 2^>nul') do if exist "%%b\bin\OpenImageIO.dll" set "DEPS_ROOT=%%b" +) +if not defined DEPS_ROOT ( + for /f "tokens=2*" %%a in ('reg query "HKLM\Software\OpenUTV" /v "DepsPath" 2^>nul') do if exist "%%b\bin\OpenImageIO.dll" set "DEPS_ROOT=%%b" +) +if not defined DEPS_ROOT ( + for /d %%d in ("%ProgramFiles%\OpenUTVDeps*") do if exist "%%d\bin\OpenImageIO.dll" set "DEPS_ROOT=%%d" +) +if not defined DEPS_ROOT ( + for /d %%d in ("%LOCALAPPDATA%\Programs\OpenUTVDeps*") do if exist "%%d\bin\OpenImageIO.dll" set "DEPS_ROOT=%%d" +) + +if not defined DEPS_ROOT ( + echo OpenUTV: Could not locate OpenUTVDeps runtime dependencies. >&2 + exit /b 1 +) + +set "APP_DIR=%~dp0" +if "%APP_DIR:~-1%"=="\" set "APP_DIR=%APP_DIR:~0,-1%" + +set "PATH=%APP_DIR%;%DEPS_ROOT%\bin;%DEPS_ROOT%\tools\python3\Lib\site-packages\PySide6;%DEPS_ROOT%\tools\python3;%DEPS_ROOT%\tools\python3\Scripts;%PATH%" +set "PYTHONHOME=%DEPS_ROOT%\tools\python3" +set "QT_PLUGIN_PATH=%DEPS_ROOT%\tools\python3\Lib\site-packages\PySide6\plugins" +set "QT_QPA_PLATFORM_PLUGIN_PATH=%DEPS_ROOT%\tools\python3\Lib\site-packages\PySide6\plugins\platforms" +set "UTV_DEPS_ROOT=%DEPS_ROOT%" +set "OPENUTV_DEPS_ROOT=%DEPS_ROOT%" +set "UTV_HOME=%APP_DIR%" +set "OPENUTV_HOME=%APP_DIR%" + +if exist "%APP_DIR%\utvio-bin.exe" ( + "%APP_DIR%\utvio-bin.exe" %* +) else if exist "%APP_DIR%\rvio-bin.exe" ( + "%APP_DIR%\rvio-bin.exe" %* +) else if exist "%APP_DIR%\utvio.exe" ( + "%APP_DIR%\utvio.exe" %* +) else if exist "%APP_DIR%\rvio.exe" ( + "%APP_DIR%\rvio.exe" %* +) else ( + echo OpenUTV: Could not locate utvio executable in %APP_DIR%. >&2 + exit /b 1 +) +exit /b %ERRORLEVEL% diff --git a/src/bin/nsapps/UTV/utvls.cmd b/src/bin/nsapps/UTV/utvls.cmd new file mode 100644 index 00000000..168941aa --- /dev/null +++ b/src/bin/nsapps/UTV/utvls.cmd @@ -0,0 +1,63 @@ +@echo off +setlocal + +:: Discover OpenUTVDeps runtime root directory +set "DEPS_ROOT=" +if defined UTV_DEPS_ROOT if exist "%UTV_DEPS_ROOT%\bin\OpenImageIO.dll" set "DEPS_ROOT=%UTV_DEPS_ROOT%" +if not defined DEPS_ROOT if defined OPENUTV_DEPS_ROOT if exist "%OPENUTV_DEPS_ROOT%\bin\OpenImageIO.dll" set "DEPS_ROOT=%OPENUTV_DEPS_ROOT%" + +if not defined DEPS_ROOT ( + for /f "tokens=2*" %%a in ('reg query "HKCU\Environment" /v "UTV_DEPS_ROOT" 2^>nul') do if exist "%%b\bin\OpenImageIO.dll" set "DEPS_ROOT=%%b" +) +if not defined DEPS_ROOT ( + for /f "tokens=2*" %%a in ('reg query "HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Environment" /v "UTV_DEPS_ROOT" 2^>nul') do if exist "%%b\bin\OpenImageIO.dll" set "DEPS_ROOT=%%b" +) +if not defined DEPS_ROOT ( + for /f "tokens=2*" %%a in ('reg query "HKCU\Environment" /v "OPENUTV_DEPS_ROOT" 2^>nul') do if exist "%%b\bin\OpenImageIO.dll" set "DEPS_ROOT=%%b" +) +if not defined DEPS_ROOT ( + for /f "tokens=2*" %%a in ('reg query "HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Environment" /v "OPENUTV_DEPS_ROOT" 2^>nul') do if exist "%%b\bin\OpenImageIO.dll" set "DEPS_ROOT=%%b" +) +if not defined DEPS_ROOT ( + for /f "tokens=2*" %%a in ('reg query "HKCU\Software\OpenUTV" /v "DepsPath" 2^>nul') do if exist "%%b\bin\OpenImageIO.dll" set "DEPS_ROOT=%%b" +) +if not defined DEPS_ROOT ( + for /f "tokens=2*" %%a in ('reg query "HKLM\Software\OpenUTV" /v "DepsPath" 2^>nul') do if exist "%%b\bin\OpenImageIO.dll" set "DEPS_ROOT=%%b" +) +if not defined DEPS_ROOT ( + for /d %%d in ("%ProgramFiles%\OpenUTVDeps*") do if exist "%%d\bin\OpenImageIO.dll" set "DEPS_ROOT=%%d" +) +if not defined DEPS_ROOT ( + for /d %%d in ("%LOCALAPPDATA%\Programs\OpenUTVDeps*") do if exist "%%d\bin\OpenImageIO.dll" set "DEPS_ROOT=%%d" +) + +if not defined DEPS_ROOT ( + echo OpenUTV: Could not locate OpenUTVDeps runtime dependencies. >&2 + exit /b 1 +) + +set "APP_DIR=%~dp0" +if "%APP_DIR:~-1%"=="\" set "APP_DIR=%APP_DIR:~0,-1%" + +set "PATH=%APP_DIR%;%DEPS_ROOT%\bin;%DEPS_ROOT%\tools\python3\Lib\site-packages\PySide6;%DEPS_ROOT%\tools\python3;%DEPS_ROOT%\tools\python3\Scripts;%PATH%" +set "PYTHONHOME=%DEPS_ROOT%\tools\python3" +set "QT_PLUGIN_PATH=%DEPS_ROOT%\tools\python3\Lib\site-packages\PySide6\plugins" +set "QT_QPA_PLATFORM_PLUGIN_PATH=%DEPS_ROOT%\tools\python3\Lib\site-packages\PySide6\plugins\platforms" +set "UTV_DEPS_ROOT=%DEPS_ROOT%" +set "OPENUTV_DEPS_ROOT=%DEPS_ROOT%" +set "UTV_HOME=%APP_DIR%" +set "OPENUTV_HOME=%APP_DIR%" + +if exist "%APP_DIR%\utvls-bin.exe" ( + "%APP_DIR%\utvls-bin.exe" %* +) else if exist "%APP_DIR%\rvls-bin.exe" ( + "%APP_DIR%\rvls-bin.exe" %* +) else if exist "%APP_DIR%\utvls.exe" ( + "%APP_DIR%\utvls.exe" %* +) else if exist "%APP_DIR%\rvls.exe" ( + "%APP_DIR%\rvls.exe" %* +) else ( + echo OpenUTV: Could not locate utvls executable in %APP_DIR%. >&2 + exit /b 1 +) +exit /b %ERRORLEVEL% diff --git a/src/bin/nsapps/UTV/utvpkg.cmd b/src/bin/nsapps/UTV/utvpkg.cmd new file mode 100644 index 00000000..a5c522f3 --- /dev/null +++ b/src/bin/nsapps/UTV/utvpkg.cmd @@ -0,0 +1,63 @@ +@echo off +setlocal + +:: Discover OpenUTVDeps runtime root directory +set "DEPS_ROOT=" +if defined UTV_DEPS_ROOT if exist "%UTV_DEPS_ROOT%\bin\OpenImageIO.dll" set "DEPS_ROOT=%UTV_DEPS_ROOT%" +if not defined DEPS_ROOT if defined OPENUTV_DEPS_ROOT if exist "%OPENUTV_DEPS_ROOT%\bin\OpenImageIO.dll" set "DEPS_ROOT=%OPENUTV_DEPS_ROOT%" + +if not defined DEPS_ROOT ( + for /f "tokens=2*" %%a in ('reg query "HKCU\Environment" /v "UTV_DEPS_ROOT" 2^>nul') do if exist "%%b\bin\OpenImageIO.dll" set "DEPS_ROOT=%%b" +) +if not defined DEPS_ROOT ( + for /f "tokens=2*" %%a in ('reg query "HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Environment" /v "UTV_DEPS_ROOT" 2^>nul') do if exist "%%b\bin\OpenImageIO.dll" set "DEPS_ROOT=%%b" +) +if not defined DEPS_ROOT ( + for /f "tokens=2*" %%a in ('reg query "HKCU\Environment" /v "OPENUTV_DEPS_ROOT" 2^>nul') do if exist "%%b\bin\OpenImageIO.dll" set "DEPS_ROOT=%%b" +) +if not defined DEPS_ROOT ( + for /f "tokens=2*" %%a in ('reg query "HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Environment" /v "OPENUTV_DEPS_ROOT" 2^>nul') do if exist "%%b\bin\OpenImageIO.dll" set "DEPS_ROOT=%%b" +) +if not defined DEPS_ROOT ( + for /f "tokens=2*" %%a in ('reg query "HKCU\Software\OpenUTV" /v "DepsPath" 2^>nul') do if exist "%%b\bin\OpenImageIO.dll" set "DEPS_ROOT=%%b" +) +if not defined DEPS_ROOT ( + for /f "tokens=2*" %%a in ('reg query "HKLM\Software\OpenUTV" /v "DepsPath" 2^>nul') do if exist "%%b\bin\OpenImageIO.dll" set "DEPS_ROOT=%%b" +) +if not defined DEPS_ROOT ( + for /d %%d in ("%ProgramFiles%\OpenUTVDeps*") do if exist "%%d\bin\OpenImageIO.dll" set "DEPS_ROOT=%%d" +) +if not defined DEPS_ROOT ( + for /d %%d in ("%LOCALAPPDATA%\Programs\OpenUTVDeps*") do if exist "%%d\bin\OpenImageIO.dll" set "DEPS_ROOT=%%d" +) + +if not defined DEPS_ROOT ( + echo OpenUTV: Could not locate OpenUTVDeps runtime dependencies. >&2 + exit /b 1 +) + +set "APP_DIR=%~dp0" +if "%APP_DIR:~-1%"=="\" set "APP_DIR=%APP_DIR:~0,-1%" + +set "PATH=%APP_DIR%;%DEPS_ROOT%\bin;%DEPS_ROOT%\tools\python3\Lib\site-packages\PySide6;%DEPS_ROOT%\tools\python3;%DEPS_ROOT%\tools\python3\Scripts;%PATH%" +set "PYTHONHOME=%DEPS_ROOT%\tools\python3" +set "QT_PLUGIN_PATH=%DEPS_ROOT%\tools\python3\Lib\site-packages\PySide6\plugins" +set "QT_QPA_PLATFORM_PLUGIN_PATH=%DEPS_ROOT%\tools\python3\Lib\site-packages\PySide6\plugins\platforms" +set "UTV_DEPS_ROOT=%DEPS_ROOT%" +set "OPENUTV_DEPS_ROOT=%DEPS_ROOT%" +set "UTV_HOME=%APP_DIR%" +set "OPENUTV_HOME=%APP_DIR%" + +if exist "%APP_DIR%\utvpkg-bin.exe" ( + "%APP_DIR%\utvpkg-bin.exe" %* +) else if exist "%APP_DIR%\rvpkg-bin.exe" ( + "%APP_DIR%\rvpkg-bin.exe" %* +) else if exist "%APP_DIR%\utvpkg.exe" ( + "%APP_DIR%\utvpkg.exe" %* +) else if exist "%APP_DIR%\rvpkg.exe" ( + "%APP_DIR%\rvpkg.exe" %* +) else ( + echo OpenUTV: Could not locate utvpkg executable in %APP_DIR%. >&2 + exit /b 1 +) +exit /b %ERRORLEVEL% diff --git a/src/lib/app/QTBundle/QTBundle.cpp b/src/lib/app/QTBundle/QTBundle.cpp index ba40650e..5b1d8cb2 100644 --- a/src/lib/app/QTBundle/QTBundle.cpp +++ b/src/lib/app/QTBundle/QTBundle.cpp @@ -89,7 +89,11 @@ namespace TwkApp m_pyhome.cd("python" PYTHON_VERSION); bool setPythonHome = !(getenv("PYTHONHOME") && getenv("RV_ALLOW_SITE_PYTHONHOME")); QString discoveredDepsRoot; - const char* depsRoot = getenv("OPENUTV_DEPS_ROOT"); + const char* depsRoot = getenv("UTV_DEPS_ROOT"); + if (!depsRoot) + { + depsRoot = getenv("OPENUTV_DEPS_ROOT"); + } #ifdef PLATFORM_WINDOWS if (!depsRoot) { diff --git a/src/lib/app/RvCommon/RvKeybindingsManager.cpp b/src/lib/app/RvCommon/RvKeybindingsManager.cpp index 7c7f4f08..1cef4a92 100644 --- a/src/lib/app/RvCommon/RvKeybindingsManager.cpp +++ b/src/lib/app/RvCommon/RvKeybindingsManager.cpp @@ -37,8 +37,11 @@ namespace Rv QString RvKeybindingsManager::defaultKeybindingsPath() const { - // 1. Environment variable override - const char* envPath = getenv("OPENUTV_KEYBINDINGS_FILE"); + const char* envPath = getenv("UTV_KEYBINDINGS_FILE"); + if (!envPath) + { + envPath = getenv("OPENUTV_KEYBINDINGS_FILE"); + } if (!envPath) { envPath = getenv("RV_KEYBINDINGS_FILE"); diff --git a/src/lib/app/RvCommon/RvPreferences.cpp b/src/lib/app/RvCommon/RvPreferences.cpp index abd25e5f..1c33aa1a 100644 --- a/src/lib/app/RvCommon/RvPreferences.cpp +++ b/src/lib/app/RvCommon/RvPreferences.cpp @@ -445,9 +445,9 @@ namespace Rv s.setNum(opts.readerThreads); m_ui.rthreadEdit->setText(s); - QString hwEnv = QString::fromLocal8Bit(qgetenv("OPENUTV_HWACCEL")); + QString hwEnv = QString::fromLocal8Bit(qgetenv("UTV_HWACCEL")); if (hwEnv.isEmpty()) - hwEnv = QString::fromLocal8Bit(qgetenv("UTV_HWACCEL")); + hwEnv = QString::fromLocal8Bit(qgetenv("OPENUTV_HWACCEL")); if (hwEnv.isEmpty()) hwEnv = QString::fromLocal8Bit(qgetenv("RV_HWACCEL")); if (!hwEnv.isEmpty()) @@ -965,15 +965,12 @@ namespace Rv opts.networkHost = (char*)((opts.networkHostBuf.empty()) ? 0 : opts.networkHostBuf.c_str()); opts.readerThreads = settings.value("readerThreads", opts.readerThreads).toInt(); opts.hwDecodeMode = settings.value("hardwareDecodeMode", opts.hwDecodeMode).toInt(); - if (qEnvironmentVariableIsEmpty("OPENUTV_HWACCEL") && qEnvironmentVariableIsEmpty("UTV_HWACCEL") + if (qEnvironmentVariableIsEmpty("UTV_HWACCEL") && qEnvironmentVariableIsEmpty("OPENUTV_HWACCEL") && qEnvironmentVariableIsEmpty("RV_HWACCEL")) { - if (opts.hwDecodeMode == 2) - qputenv("OPENUTV_HWACCEL", "none"); - else if (opts.hwDecodeMode == 1) - qputenv("OPENUTV_HWACCEL", "prores"); - else - qputenv("OPENUTV_HWACCEL", "all"); + const char* val = (opts.hwDecodeMode == 2) ? "none" : ((opts.hwDecodeMode == 1) ? "prores" : "all"); + qputenv("UTV_HWACCEL", val); + qputenv("OPENUTV_HWACCEL", val); } opts.autoRetime = int(settings.value("autoRetime", opts.autoRetime ? true : false).toBool()); opts.autoSetupACES = int(settings.value("autoSetupACES", opts.autoSetupACES ? true : false).toBool()); @@ -1323,12 +1320,9 @@ namespace Rv settings.setValue("readerThreads", m_ui.rthreadEdit->text().toInt()); int hwMode = m_ui.hardwareDecodeCombo->currentIndex(); settings.setValue("hardwareDecodeMode", hwMode); - if (hwMode == 2) - qputenv("OPENUTV_HWACCEL", "none"); - else if (hwMode == 1) - qputenv("OPENUTV_HWACCEL", "prores"); - else - qputenv("OPENUTV_HWACCEL", "all"); + const char* val = (hwMode == 2) ? "none" : ((hwMode == 1) ? "prores" : "all"); + qputenv("UTV_HWACCEL", val); + qputenv("OPENUTV_HWACCEL", val); settings.setValue("autoRetime", m_ui.autoRetimeToggle->checkState() == Qt::Checked); settings.setValue("useCrashReporter", m_ui.useCrashReporterToggle->checkState() == Qt::Checked); settings.setValue("fontSize1", m_ui.fontSizeSpinBox->value()); diff --git a/src/lib/image/IOoiio/IOoiio.cpp b/src/lib/image/IOoiio/IOoiio.cpp index 26792eb3..ee68282b 100644 --- a/src/lib/image/IOoiio/IOoiio.cpp +++ b/src/lib/image/IOoiio/IOoiio.cpp @@ -7,11 +7,13 @@ #include #include #include +#include #include #include #include #include #include +#include #include #include @@ -31,46 +33,76 @@ namespace TwkFB StringPairVector codecs; unsigned int r = ImageRead; unsigned int w = ImageWrite; - unsigned int rw = r | w; + + // + // Only advertise Write for extensions whose OIIO format actually has an + // output plugin in this OIIO build (e.g. Homebrew's OIIO has no QOI + // writer). Otherwise GenericIO would route writes here and fail. + // + set writableExts; + { + set outputFormats; + for (const auto& name : Strutil::splits(get_string_attribute("output_format_list"), ",")) + outputFormats.insert(name); + + for (const auto& entry : get_extension_map()) + { + if (outputFormats.count(entry.first)) + { + for (const auto& ext : entry.second) + writableExts.insert(Strutil::lower(ext)); + } + } + } + + auto rw = [&](const char* ext) { return writableExts.count(ext) ? (r | w) : r; }; + + // Dedicated native writer plugins handle the following formats with custom streaming pipelines: + // IOpng ("m9"), IOdpx ("m5"), IOcin ("z_cin"), IOjpeg ("m2"), IOtarga ("z_targa"), IOrla ("z_rla"), IOrgbe ("z_rgbe"). + // These MUST be registered as Read-only in IOoiio so write requests route directly to their native writers. + addType("png", "Portable Network Graphics Image", r, codecs); + addType("dpx", "SMPTE DPX", r, codecs); + addType("cin", "Kodak Cineon", r, codecs); + addType("cineon", "Kodak Cineon", r, codecs); + addType("jpg", "JPEG image", r, codecs); + addType("jpeg", "JPEG image", r, codecs); + addType("tga", "TARGA", r, codecs); + addType("targa", "TARGA", r, codecs); + addType("rla", "Wavefront RLA", r, codecs); + addType("hdr", "Radiance HDR", r, codecs); + addType("rgbe", "Radiance HDR", r, codecs); + + // Read-only formats addType("psd", "Adobe Photoshop", r, codecs); addType("pic", "Softimage PIC", r, codecs); - addType("tga", "TARGA", rw, codecs); - addType("targa", "TARGA", rw, codecs); - addType("sgi", "SGI image", rw, codecs); - addType("jpg", "JPEG image", rw, codecs); - addType("jpeg", "JPEG image", rw, codecs); - addType("bw", "SGI image", rw, codecs); - addType("rgb", "SGI image", rw, codecs); - addType("rgba", "SGI image", rw, codecs); - addType("inta", "SGI image", rw, codecs); - addType("int", "SGI image", rw, codecs); - addType("pnm", "PNM", rw, codecs); - addType("fits", "FITS", rw, codecs); - addType("dpx", "SMPTE DPX", rw, codecs); - addType("cin", "Kodak Cineon", rw, codecs); - addType("cineon", "Kodak Cineon", w, codecs); - addType("webp", "Google WebP", rw, codecs); addType("ptex", "Disney PTex", r, codecs); addType("ptx", "Disney PTex", r, codecs); - addType("rla", "Wavefront RLA", rw, codecs); - addType("iff", "IFF", rw, codecs); - addType("bmp", "Windows Bitmap", rw, codecs); - addType("dds", "Direct Draw Surface", rw, codecs); addType("gif", "Graphics Interchange Format", r, codecs); addType("ico", "Palette", r, codecs); - addType("pbm", "Portable Network Graphics", rw, codecs); - addType("pgm", "Portable Network Graphics", rw, codecs); - addType("ppm", "Portable Network Grapics", rw, codecs); - addType("heic", "High Efficiency Image File", rw, codecs); - addType("heif", "High Efficiency Image File", rw, codecs); - addType("hif", "High Efficiency Image File", rw, codecs); - addType("avif", "AV1 Image File", rw, codecs); - addType("jxl", "JPEG XL Image", rw, codecs); - addType("hdr", "Radiance HDR", rw, codecs); - addType("rgbe", "Radiance HDR", rw, codecs); addType("z", "Pixar Z-Depth", r, codecs); - addType("png", "Portable Network Graphics Image", rw, codecs); - addType("qoi", "Quite OK Image", rw, codecs); + + // Formats written via OpenImageIO (no dedicated native writer plugin) + addType("webp", "Google WebP", rw("webp"), codecs); + addType("qoi", "Quite OK Image", rw("qoi"), codecs); + addType("bmp", "Windows Bitmap", rw("bmp"), codecs); + addType("sgi", "SGI image", rw("sgi"), codecs); + addType("bw", "SGI image", rw("bw"), codecs); + addType("rgb", "SGI image", rw("rgb"), codecs); + addType("rgba", "SGI image", rw("rgba"), codecs); + addType("inta", "SGI image", rw("inta"), codecs); + addType("int", "SGI image", rw("int"), codecs); + addType("pnm", "PNM", rw("pnm"), codecs); + addType("pbm", "Portable Network Graphics", rw("pbm"), codecs); + addType("pgm", "Portable Network Graphics", rw("pgm"), codecs); + addType("ppm", "Portable Network Grapics", rw("ppm"), codecs); + addType("fits", "FITS", rw("fits"), codecs); + addType("iff", "IFF", rw("iff"), codecs); + addType("dds", "Direct Draw Surface", rw("dds"), codecs); + addType("heic", "High Efficiency Image File", rw("heic"), codecs); + addType("heif", "High Efficiency Image File", rw("heif"), codecs); + addType("hif", "High Efficiency Image File", rw("hif"), codecs); + addType("avif", "AV1 Image File", rw("avif"), codecs); + addType("jxl", "JPEG XL Image", rw("jxl"), codecs); // These are handled by their dedicated optimized streaming plugins: // IOexr ("m0"), IOtiff ("m1"), IOjpeg ("m2"), IOhtj2k ("m7") @@ -439,7 +471,214 @@ namespace TwkFB void IOoiio::writeImage(const FrameBuffer& img, const std::string& filename, const WriteRequest& request) const { - FrameBufferIO::writeImage(img, filename, request); + const FrameBuffer* outfb = &img; + + // Ensure temporary FrameBuffer is cleanly deleted upon return/exception + struct FBAutoCleanup + { + const FrameBuffer*& current; + const FrameBuffer& original; + + ~FBAutoCleanup() + { + if (current != &original) + delete current; + } + } autoCleanup{outfb, img}; + + // + // Merge planar frames if needed + // + if (outfb->isPlanar()) + { + const FrameBuffer* fb = outfb; + outfb = mergePlanes(outfb); + if (fb != &img) + delete fb; + } + + // + // Convert YUV / Primaries to Linear Rec. 709 if required + // + if (!request.keepColorSpace && (outfb->hasPrimaries() || outfb->isYUV() || outfb->isYRYBY())) + { + const FrameBuffer* fb = outfb; + outfb = convertToLinearRGB709(outfb); + if (fb != &img) + delete fb; + } + + // + // Convert packed or unsupported types to standard formats + // + switch (outfb->dataType()) + { + case FrameBuffer::UCHAR: + case FrameBuffer::USHORT: + case FrameBuffer::UINT: + case FrameBuffer::HALF: + case FrameBuffer::FLOAT: + case FrameBuffer::DOUBLE: + break; + default: + { + const FrameBuffer* fb = outfb; + outfb = copyConvert(outfb, FrameBuffer::UCHAR); + if (fb != &img) + delete fb; + break; + } + } + + // + // Handle orientation: NATURAL (bottom-left origin) needs vertical flip; + // TOPRIGHT / BOTTOMRIGHT needs horizontal flop. + // + bool needflip = false; + bool needflop = false; + + switch (outfb->orientation()) + { + case FrameBuffer::NATURAL: + needflip = true; + break; + case FrameBuffer::TOPRIGHT: + case FrameBuffer::BOTTOMRIGHT: + needflop = true; + break; + default: + break; + } + + if (needflop) + { + if (outfb == &img) + outfb = img.copy(); + flop(const_cast(outfb)); + } + + if (needflip) + { + if (outfb == &img) + outfb = img.copy(); + flip(const_cast(outfb)); + } + + std::unique_ptr out = ImageOutput::create(filename); + if (!out) + { + TWK_THROW_STREAM(IOException, "OIIO: Unable to create output for \"" << filename << "\": " << OIIO::geterror()); + } + + TypeDesc format = TypeDesc::UINT8; + switch (outfb->dataType()) + { + case FrameBuffer::UCHAR: + format = TypeDesc::UINT8; + break; + case FrameBuffer::USHORT: + format = TypeDesc::UINT16; + break; + case FrameBuffer::UINT: + format = TypeDesc::UINT32; + break; + case FrameBuffer::HALF: + format = TypeDesc::HALF; + break; + case FrameBuffer::FLOAT: + format = TypeDesc::FLOAT; + break; + case FrameBuffer::DOUBLE: + format = TypeDesc::DOUBLE; + break; + default: + format = TypeDesc::UINT8; + break; + } + + // + // Drop a trailing alpha channel for writers that cannot store one + // (e.g. PNM). The pixel stride still spans all source channels, so + // OIIO simply skips the alpha values. + // + const int numChannels = outfb->numChannels(); + const bool hasChannelNames = outfb->channelNames().size() == static_cast(numChannels); + const bool lastIsAlpha = !hasChannelNames || outfb->channelNames().back() == "A"; + int nchannels = numChannels; + + if ((numChannels == 2 || numChannels == 4) && lastIsAlpha && !out->supports("alpha")) + { + nchannels = numChannels - 1; + } + + // + // Work around OIIO writers that mishandle some sample types. OIIO + // converts from the in-memory format to the file format. + // + // pnm: emits PFM (bottom-to-top float) for any floating point spec + // regardless of extension, so store integer samples for + // .ppm/.pgm/.pbm/.pnm. + // fits: writes BITPIX=-32 for HALF but still writes 16-bit samples, + // producing a truncated file, so promote HALF to FLOAT. + // + TypeDesc fileFormat = format; + if (Strutil::iequals(out->format_name(), "pnm") && format.is_floating_point()) + { + fileFormat = TypeDesc::UINT16; + } + else if (Strutil::iequals(out->format_name(), "fits") && format == TypeDesc::HALF) + { + fileFormat = TypeDesc::FLOAT; + } + + ImageSpec spec(outfb->width(), outfb->height(), nchannels, fileFormat); + + if (hasChannelNames) + { + spec.channelnames.assign(outfb->channelNames().begin(), outfb->channelNames().begin() + nchannels); + } + + if (request.pixelAspect != 1.0f && request.pixelAspect != 0.0f) + { + spec.attribute("PixelAspectRatio", request.pixelAspect); + } + else if (outfb->pixelAspectRatio() != 1.0f && outfb->pixelAspectRatio() != 0.0f) + { + spec.attribute("PixelAspectRatio", outfb->pixelAspectRatio()); + } + + if (!request.compression.empty()) + { + spec.attribute("compression", request.compression); + } + + if (request.quality >= 0.0f && request.quality <= 1.0f) + { + spec.attribute("CompressionQuality", static_cast(request.quality * 100.0f)); + } + + for (const auto& p : request.parameters) + { + spec.attribute(p.first, p.second); + } + + if (!out->open(filename, spec)) + { + TWK_THROW_STREAM(IOException, "OIIO: Unable to open \"" << filename << "\" for writing: " << out->geterror()); + } + + stride_t xstride = outfb->pixelSize(); + stride_t ystride = outfb->scanlinePaddedSize(); + + if (!out->write_image(format, outfb->pixels(), xstride, ystride)) + { + TWK_THROW_STREAM(IOException, "OIIO: Error writing \"" << filename << "\": " << out->geterror()); + } + + if (!out->close()) + { + TWK_THROW_STREAM(IOException, "OIIO: Error closing \"" << filename << "\": " << out->geterror()); + } } } // End namespace TwkFB diff --git a/src/lib/image/MovieFFMpeg/MovieFFMpeg.cpp b/src/lib/image/MovieFFMpeg/MovieFFMpeg.cpp index e7f31e22..3ca85f7a 100644 --- a/src/lib/image/MovieFFMpeg/MovieFFMpeg.cpp +++ b/src/lib/image/MovieFFMpeg/MovieFFMpeg.cpp @@ -1074,9 +1074,9 @@ namespace TwkMovie HwDecodeMode getHardwareDecodeMode() { - const char* env = getenv("OPENUTV_HWACCEL"); + const char* env = getenv("UTV_HWACCEL"); if (!env) - env = getenv("UTV_HWACCEL"); + env = getenv("OPENUTV_HWACCEL"); if (!env) env = getenv("RV_HWACCEL"); @@ -1120,9 +1120,9 @@ namespace TwkMovie { std::vector candidates; - const char* env = getenv("OPENUTV_HWACCEL"); + const char* env = getenv("UTV_HWACCEL"); if (!env) - env = getenv("UTV_HWACCEL"); + env = getenv("OPENUTV_HWACCEL"); if (!env) env = getenv("RV_HWACCEL"); diff --git a/src/lib/image/TwkFB/IO.cpp b/src/lib/image/TwkFB/IO.cpp index 8b3d6e02..d2c990db 100644 --- a/src/lib/image/TwkFB/IO.cpp +++ b/src/lib/image/TwkFB/IO.cpp @@ -121,7 +121,8 @@ namespace TwkFB void FrameBufferIO::readImage(FrameBuffer& fb, const string& filename, const ReadRequest& request) const { - throw UnsupportedException(); + TWK_THROW_STREAM(UnsupportedException, + "Image plugin '" << identifier() << "' does not support reading '" << extension(filename) << "'"); } void FrameBufferIO::readImages(FrameBufferVector& fbs, const string& filename, const ReadRequest& request) const @@ -133,7 +134,8 @@ namespace TwkFB void FrameBufferIO::writeImage(const FrameBuffer& img, const string& filename, const WriteRequest& request) const { - throw UnsupportedException(); + TWK_THROW_STREAM(UnsupportedException, + "Image plugin '" << identifier() << "' does not support writing '" << extension(filename) << "'"); } void FrameBufferIO::writeImages(const ConstFrameBufferVector& fbs, const string& filename, const WriteRequest& request) const @@ -144,7 +146,8 @@ namespace TwkFB } else { - throw UnsupportedException(); + TWK_THROW_STREAM(UnsupportedException, "Image plugin '" << identifier() << "' does not support multi-image writing to '" + << extension(filename) << "'"); } } @@ -510,7 +513,7 @@ namespace TwkFB } else { - throw UnsupportedException(); + TWK_THROW_STREAM(UnsupportedException, "No image plugin available to read format '" << format << "'"); } } @@ -525,7 +528,7 @@ namespace TwkFB } else { - throw UnsupportedException(); + TWK_THROW_STREAM(UnsupportedException, "No image plugin available to write format '" << format << "'"); } } @@ -540,7 +543,7 @@ namespace TwkFB } else { - throw UnsupportedException(); + TWK_THROW_STREAM(UnsupportedException, "No image plugin available to write format '" << format << "'"); } }