kjk
This commit is contained in:
@@ -136,11 +136,16 @@ def playwright_package_installed() -> bool:
|
|||||||
def _build_playwright_install_cmd(browsers: str | None) -> list[str]:
|
def _build_playwright_install_cmd(browsers: str | None) -> list[str]:
|
||||||
"""Return the command to install Playwright browsers.
|
"""Return the command to install Playwright browsers.
|
||||||
|
|
||||||
- If browsers is None or empty: default to install Chromium only.
|
- If browsers is None or empty: default to install Chromium only (headless).
|
||||||
- If browsers contains 'all': install all engines by running 'playwright install' with no extra args.
|
- If browsers contains 'all': install all engines by running 'playwright install' with no extra args.
|
||||||
- Otherwise, validate entries and return a command that installs the named engines.
|
- Otherwise, validate entries and return a command that installs the named engines.
|
||||||
|
|
||||||
|
The --with-deps flag is NOT used because:
|
||||||
|
1. The project already includes ffmpeg (in MPV/ffmpeg)
|
||||||
|
2. Most system dependencies should already be available
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
# Use --skip-browsers to just install deps without browsers, then install specific browsers
|
||||||
base = [sys.executable, "-m", "playwright", "install"]
|
base = [sys.executable, "-m", "playwright", "install"]
|
||||||
if not browsers:
|
if not browsers:
|
||||||
return base + ["chromium"]
|
return base + ["chromium"]
|
||||||
@@ -293,7 +298,10 @@ def main() -> int:
|
|||||||
)
|
)
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
repo_root = Path(__file__).resolve().parent.parent
|
# Ensure repo_root is always the project root, not the current working directory
|
||||||
|
# This prevents issues when bootstrap.py is run from different directories
|
||||||
|
script_dir = Path(__file__).resolve().parent
|
||||||
|
repo_root = script_dir.parent
|
||||||
|
|
||||||
# Helpers for interactive menu and uninstall detection
|
# Helpers for interactive menu and uninstall detection
|
||||||
def _venv_python_path(p: Path) -> Path | None:
|
def _venv_python_path(p: Path) -> Path | None:
|
||||||
@@ -795,15 +803,19 @@ def main() -> int:
|
|||||||
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
|
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||||||
$repo = (Resolve-Path (Join-Path $scriptDir "..")).Path
|
$repo = (Resolve-Path (Join-Path $scriptDir "..")).Path
|
||||||
$venv = Join-Path $repo '.venv'
|
$venv = Join-Path $repo '.venv'
|
||||||
|
$py = Join-Path $venv 'Scripts\python.exe'
|
||||||
|
if (Test-Path $py) {
|
||||||
|
& $py -m scripts.cli_entry @args; exit $LASTEXITCODE
|
||||||
|
}
|
||||||
# Ensure venv Scripts dir is on PATH for provider discovery
|
# Ensure venv Scripts dir is on PATH for provider discovery
|
||||||
$venvScripts = Join-Path $venv 'Scripts'
|
$venvScripts = Join-Path $venv 'Scripts'
|
||||||
if (Test-Path $venvScripts) { $env:PATH = $venvScripts + ';' + $env:PATH }
|
if (Test-Path $venvScripts) { $env:PATH = $venvScripts + ';' + $env:PATH }
|
||||||
$py = Join-Path $venv 'Scripts\python.exe'
|
# Fallback to system python if venv doesn't exist
|
||||||
$cli = Join-Path $repo 'CLI.py'
|
if (Test-Path (Join-Path $repo 'CLI.py')) {
|
||||||
if (Test-Path $py) { & $py -m scripts.cli_entry @args; exit $LASTEXITCODE }
|
python -m scripts.cli_entry @args
|
||||||
if (Test-Path $cli) { & python $cli @args; exit $LASTEXITCODE }
|
} else {
|
||||||
# fallback
|
python -m scripts.cli_entry @args
|
||||||
python -m scripts.cli_entry @args
|
}
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
ps1.write_text(ps1_text, encoding="utf-8")
|
ps1.write_text(ps1_text, encoding="utf-8")
|
||||||
@@ -828,16 +840,16 @@ python -m scripts.cli_entry @args
|
|||||||
"Param([Parameter(ValueFromRemainingArguments=$true)] $args)\n"
|
"Param([Parameter(ValueFromRemainingArguments=$true)] $args)\n"
|
||||||
f'$repo = "{repo}"\n'
|
f'$repo = "{repo}"\n'
|
||||||
"$venv = Join-Path $repo '.venv'\n"
|
"$venv = Join-Path $repo '.venv'\n"
|
||||||
"$exe = Join-Path $venv 'Scripts\\mm.exe'\n"
|
|
||||||
"if (Test-Path $exe) { & $exe @args; exit $LASTEXITCODE }\n"
|
|
||||||
"$py = Join-Path $venv 'Scripts\\python.exe'\n"
|
"$py = Join-Path $venv 'Scripts\\python.exe'\n"
|
||||||
"if (Test-Path $py) {\n"
|
"if (Test-Path $py) {\n"
|
||||||
" if ($env:MM_DEBUG) {\n"
|
" if ($env:MM_DEBUG) {\n"
|
||||||
' Write-Host "MM_DEBUG: diagnostics" -ForegroundColor Yellow\n'
|
' Write-Host "MM_DEBUG: using venv python at $py" -ForegroundColor Yellow\n'
|
||||||
" & $py -c \"import sys,importlib,importlib.util,traceback; print('sys.executable:', sys.executable); print('sys.path (first 8):', sys.path[:8]);\"\n"
|
" & $py -c \"import sys; print('sys.executable:', sys.executable)\"\n"
|
||||||
" }\n"
|
" }\n"
|
||||||
" & $py -m scripts.cli_entry @args; exit $LASTEXITCODE\n"
|
" & $py -m scripts.cli_entry @args; exit $LASTEXITCODE\n"
|
||||||
"}\n"
|
"}\n"
|
||||||
|
"# Fallback to system python if venv doesn't exist\n"
|
||||||
|
"if ($env:MM_DEBUG) { Write-Host 'MM_DEBUG: venv python not found, trying system python' -ForegroundColor Yellow }\n"
|
||||||
"python -m scripts.cli_entry @args\n"
|
"python -m scripts.cli_entry @args\n"
|
||||||
)
|
)
|
||||||
if mm_ps1.exists():
|
if mm_ps1.exists():
|
||||||
@@ -845,29 +857,71 @@ python -m scripts.cli_entry @args
|
|||||||
mm_ps1.replace(bak)
|
mm_ps1.replace(bak)
|
||||||
mm_ps1.write_text(ps1_text, encoding="utf-8")
|
mm_ps1.write_text(ps1_text, encoding="utf-8")
|
||||||
|
|
||||||
# Attempt to add user_bin to the user's PATH if it's not present.
|
# Write mm.bat (CMD shim for better compatibility)
|
||||||
|
mm_bat = user_bin / "mm.bat"
|
||||||
|
bat_text = (
|
||||||
|
"@echo off\n"
|
||||||
|
"setlocal enabledelayedexpansion\n"
|
||||||
|
f'set "REPO={repo}"\n'
|
||||||
|
"set \"VENV=!REPO!\\.venv\"\n"
|
||||||
|
"set \"PY=!VENV!\\Scripts\\python.exe\"\n"
|
||||||
|
"if exist \"!PY!\" (\n"
|
||||||
|
" if defined MM_DEBUG (\n"
|
||||||
|
" echo MM_DEBUG: using venv python at !PY!\n"
|
||||||
|
" \"!PY!\" -c \"import sys; print('sys.executable:', sys.executable)\"\n"
|
||||||
|
" )\n"
|
||||||
|
" \"!PY!\" -m scripts.cli_entry %*\n"
|
||||||
|
" exit /b !ERRORLEVEL!\n"
|
||||||
|
")\n"
|
||||||
|
"echo MM: venv not found at !VENV!, trying system python\n"
|
||||||
|
"if defined MM_DEBUG echo MM_DEBUG: venv python not found, trying system python\n"
|
||||||
|
"python -m scripts.cli_entry %*\n"
|
||||||
|
"exit /b !ERRORLEVEL!\n"
|
||||||
|
)
|
||||||
|
if mm_bat.exists():
|
||||||
|
bak = mm_bat.with_suffix(f".bak{int(time.time())}")
|
||||||
|
mm_bat.replace(bak)
|
||||||
|
mm_bat.write_text(bat_text, encoding="utf-8")
|
||||||
|
|
||||||
|
# Add user_bin to PATH for current and future sessions
|
||||||
|
str_bin = str(user_bin)
|
||||||
|
cur_path = os.environ.get("PATH", "")
|
||||||
|
|
||||||
|
# Update current session PATH if not already present
|
||||||
|
if str_bin not in cur_path:
|
||||||
|
os.environ["PATH"] = str_bin + ";" + cur_path
|
||||||
|
if not args.quiet:
|
||||||
|
print(f"Added {user_bin} to current session PATH")
|
||||||
|
|
||||||
|
# Persist to user's Windows registry PATH for future sessions
|
||||||
try:
|
try:
|
||||||
cur = os.environ.get("PATH", "")
|
ps_cmd = (
|
||||||
str_bin = str(user_bin)
|
"$bin = '{bin}';"
|
||||||
if str_bin not in cur:
|
"$cur = [Environment]::GetEnvironmentVariable('PATH','User');"
|
||||||
ps_cmd = (
|
"if ($cur -notlike \"*$bin*\") {{"
|
||||||
"$bin = '{bin}';"
|
"[Environment]::SetEnvironmentVariable('PATH', ($bin + ';' + ($cur -ne $null ? $cur : '')), 'User');"
|
||||||
"$cur = [Environment]::GetEnvironmentVariable('PATH','User');"
|
"Write-Host 'Added {bin} to User PATH in registry' -ForegroundColor Green"
|
||||||
"if ($cur -notlike \"*$bin*\") {[Environment]::SetEnvironmentVariable('PATH', ($bin + ';' + ($cur -ne $null ? $cur : '')), 'User')}"
|
"}}"
|
||||||
).format(bin=str_bin.replace("\\",
|
).format(bin=str_bin.replace("\\", "\\\\"))
|
||||||
"\\\\"))
|
subprocess.run(
|
||||||
subprocess.run(
|
["powershell",
|
||||||
["powershell",
|
"-NoProfile",
|
||||||
"-NoProfile",
|
"-Command",
|
||||||
"-Command",
|
ps_cmd],
|
||||||
ps_cmd],
|
check=False,
|
||||||
check=False
|
stdout=subprocess.DEVNULL,
|
||||||
)
|
stderr=subprocess.DEVNULL
|
||||||
except Exception:
|
)
|
||||||
pass
|
except Exception as e:
|
||||||
|
if not args.quiet:
|
||||||
|
print(f"Note: Could not persist PATH to registry (this is non-critical): {e}", file=sys.stderr)
|
||||||
|
|
||||||
if not args.quiet:
|
if not args.quiet:
|
||||||
print(f"Installed global launchers to: {user_bin}")
|
print(f"\nInstalled global launchers to: {user_bin}")
|
||||||
|
print(f"✓ mm.ps1 (PowerShell)")
|
||||||
|
print(f"✓ mm.bat (Command Prompt)")
|
||||||
|
print(f"\nYou can now run 'mm' from any terminal window.")
|
||||||
|
print(f"To use in the current terminal, reload your profile or run: $env:PATH = '{str_bin};' + $env:PATH")
|
||||||
|
|
||||||
else:
|
else:
|
||||||
# POSIX
|
# POSIX
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import contextlib
|
import contextlib
|
||||||
|
import os
|
||||||
import re
|
import re
|
||||||
|
import shutil
|
||||||
import tempfile
|
import tempfile
|
||||||
import traceback
|
import traceback
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
@@ -74,6 +76,7 @@ class PlaywrightDefaults:
|
|||||||
viewport_height: int = 1080
|
viewport_height: int = 1080
|
||||||
navigation_timeout_ms: int = 90_000
|
navigation_timeout_ms: int = 90_000
|
||||||
ignore_https_errors: bool = True
|
ignore_https_errors: bool = True
|
||||||
|
ffmpeg_path: Optional[str] = None # Path to ffmpeg executable; auto-detected if None
|
||||||
|
|
||||||
|
|
||||||
@dataclass(slots=True)
|
@dataclass(slots=True)
|
||||||
@@ -101,6 +104,7 @@ class PlaywrightTool:
|
|||||||
- sync_playwright start/stop
|
- sync_playwright start/stop
|
||||||
- browser launch/context creation
|
- browser launch/context creation
|
||||||
- user-agent/viewport defaults
|
- user-agent/viewport defaults
|
||||||
|
- ffmpeg path resolution (for video recording)
|
||||||
|
|
||||||
Config overrides (top-level keys):
|
Config overrides (top-level keys):
|
||||||
- playwright.browser="chromium"
|
- playwright.browser="chromium"
|
||||||
@@ -110,6 +114,13 @@ class PlaywrightTool:
|
|||||||
- playwright.viewport_height=1200
|
- playwright.viewport_height=1200
|
||||||
- playwright.navigation_timeout_ms=90000
|
- playwright.navigation_timeout_ms=90000
|
||||||
- playwright.ignore_https_errors=true
|
- playwright.ignore_https_errors=true
|
||||||
|
- playwright.ffmpeg_path="/path/to/ffmpeg" (auto-detected if not set)
|
||||||
|
|
||||||
|
FFmpeg resolution (in order):
|
||||||
|
1. Config key: playwright.ffmpeg_path
|
||||||
|
2. Environment variable: PLAYWRIGHT_FFMPEG_PATH
|
||||||
|
3. Project bundled: MPV/ffmpeg/bin/ffmpeg[.exe]
|
||||||
|
4. System PATH: which ffmpeg
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, config: Optional[Dict[str, Any]] = None) -> None:
|
def __init__(self, config: Optional[Dict[str, Any]] = None) -> None:
|
||||||
@@ -162,6 +173,32 @@ class PlaywrightTool:
|
|||||||
|
|
||||||
ignore_https = bool(_get("ignore_https_errors", defaults.ignore_https_errors))
|
ignore_https = bool(_get("ignore_https_errors", defaults.ignore_https_errors))
|
||||||
|
|
||||||
|
# Try to find ffmpeg: config override, environment variable, bundled, then system
|
||||||
|
ffmpeg_path: Optional[str] = None
|
||||||
|
config_ffmpeg = _get("ffmpeg_path", None)
|
||||||
|
if config_ffmpeg:
|
||||||
|
ffmpeg_path = str(config_ffmpeg).strip()
|
||||||
|
else:
|
||||||
|
# Check environment variable (supports project ffmpeg)
|
||||||
|
env_ffmpeg = os.environ.get("PLAYWRIGHT_FFMPEG_PATH")
|
||||||
|
if env_ffmpeg:
|
||||||
|
ffmpeg_path = env_ffmpeg
|
||||||
|
else:
|
||||||
|
# Try to find bundled ffmpeg in the project (if available)
|
||||||
|
try:
|
||||||
|
repo_root = Path(__file__).resolve().parent.parent
|
||||||
|
bundled_ffmpeg = repo_root / "MPV" / "ffmpeg" / "bin"
|
||||||
|
if bundled_ffmpeg.exists():
|
||||||
|
ffmpeg_exe = bundled_ffmpeg / ("ffmpeg.exe" if os.name == "nt" else "ffmpeg")
|
||||||
|
if ffmpeg_exe.exists():
|
||||||
|
ffmpeg_path = str(ffmpeg_exe)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Try system ffmpeg if bundled not found
|
||||||
|
if not ffmpeg_path:
|
||||||
|
ffmpeg_path = shutil.which("ffmpeg")
|
||||||
|
|
||||||
return PlaywrightDefaults(
|
return PlaywrightDefaults(
|
||||||
browser=browser,
|
browser=browser,
|
||||||
headless=headless,
|
headless=headless,
|
||||||
@@ -170,6 +207,7 @@ class PlaywrightTool:
|
|||||||
viewport_height=vh,
|
viewport_height=vh,
|
||||||
navigation_timeout_ms=nav_timeout,
|
navigation_timeout_ms=nav_timeout,
|
||||||
ignore_https_errors=ignore_https,
|
ignore_https_errors=ignore_https,
|
||||||
|
ffmpeg_path=ffmpeg_path,
|
||||||
)
|
)
|
||||||
|
|
||||||
def require(self) -> None:
|
def require(self) -> None:
|
||||||
|
|||||||
Reference in New Issue
Block a user