nh
This commit is contained in:
5
.gitignore
vendored
5
.gitignore
vendored
@@ -232,6 +232,9 @@ hydrusnetwork
|
||||
.style.yapf
|
||||
.yapfignore
|
||||
tests/
|
||||
|
||||
scripts/mm.ps1
|
||||
scripts/mm
|
||||
.style.yapf
|
||||
.yapfignore
|
||||
|
||||
|
||||
|
||||
@@ -35,20 +35,45 @@ def _try_import(module: str) -> bool:
|
||||
return False
|
||||
|
||||
|
||||
_FLORENCEVISION_DEPENDENCIES: List[Tuple[str, str]] = [
|
||||
("transformers", "transformers>=4.45.0"),
|
||||
("torch", "torch>=2.4.0"),
|
||||
("PIL", "Pillow>=10.0.0"),
|
||||
("einops", "einops>=0.8.0"),
|
||||
("timm", "timm>=1.0.0"),
|
||||
]
|
||||
|
||||
_PROVIDER_DEPENDENCIES: Dict[str, List[Tuple[str, str]]] = {
|
||||
"telegram": [("telethon", "telethon>=1.36.0")],
|
||||
"soulseek": [("aioslsk", "aioslsk>=1.6.0")],
|
||||
}
|
||||
|
||||
|
||||
def florencevision_missing_modules() -> List[str]:
|
||||
missing: List[str] = []
|
||||
# pillow is already in requirements, but keep the check for robustness.
|
||||
if not _try_import("transformers"):
|
||||
missing.append("transformers")
|
||||
if not _try_import("torch"):
|
||||
missing.append("torch")
|
||||
if not _try_import("PIL"):
|
||||
missing.append("pillow")
|
||||
# Florence-2 remote code frequently requires these extras.
|
||||
if not _try_import("einops"):
|
||||
missing.append("einops")
|
||||
if not _try_import("timm"):
|
||||
missing.append("timm")
|
||||
return [
|
||||
requirement
|
||||
for import_name, requirement in _FLORENCEVISION_DEPENDENCIES
|
||||
if not _try_import(import_name)
|
||||
]
|
||||
|
||||
|
||||
def _provider_missing_modules(config: Dict[str, Any]) -> Dict[str, List[str]]:
|
||||
missing: Dict[str, List[str]] = {}
|
||||
provider_cfg = (config or {}).get("provider")
|
||||
if not isinstance(provider_cfg, dict):
|
||||
return missing
|
||||
|
||||
for provider_name, requirements in _PROVIDER_DEPENDENCIES.items():
|
||||
block = provider_cfg.get(provider_name)
|
||||
if not isinstance(block, dict) or not block:
|
||||
continue
|
||||
missing_for_provider = [
|
||||
requirement
|
||||
for import_name, requirement in requirements
|
||||
if not _try_import(import_name)
|
||||
]
|
||||
if missing_for_provider:
|
||||
missing[provider_name] = missing_for_provider
|
||||
return missing
|
||||
|
||||
|
||||
@@ -73,47 +98,51 @@ def _pip_install(requirements: List[str]) -> Tuple[bool, str]:
|
||||
return False, str(exc)
|
||||
|
||||
|
||||
def _install_requirements(label: str, requirements: List[str]) -> None:
|
||||
if not requirements:
|
||||
return
|
||||
|
||||
names = ", ".join(requirements)
|
||||
status_text = f"Installing {label} dependencies: {names}"
|
||||
try:
|
||||
with stdout_console().status(status_text, spinner="dots"):
|
||||
ok, detail = _pip_install(requirements)
|
||||
except Exception:
|
||||
log(f"[startup] {label} dependencies missing ({names}). Attempting auto-install...")
|
||||
ok, detail = _pip_install(requirements)
|
||||
|
||||
if ok:
|
||||
log(f"[startup] {label} dependency install OK")
|
||||
else:
|
||||
log(f"[startup] {label} dependency auto-install failed. {detail}")
|
||||
|
||||
|
||||
def maybe_auto_install_configured_tools(config: Dict[str, Any]) -> None:
|
||||
"""Best-effort dependency auto-installer for configured tools.
|
||||
"""Best-effort dependency auto-installer for configured tools and providers.
|
||||
|
||||
This is intentionally conservative:
|
||||
- Only acts when a tool block is enabled.
|
||||
- Only acts when a configuration block is present/enabled.
|
||||
- Skips under pytest.
|
||||
|
||||
Current supported tool(s): florencevision
|
||||
Current supported features: FlorenceVision tool, Telegram provider, Soulseek provider
|
||||
"""
|
||||
if _is_pytest():
|
||||
return
|
||||
|
||||
tool_cfg = (config or {}).get("tool")
|
||||
if not isinstance(tool_cfg, dict):
|
||||
return
|
||||
if isinstance(tool_cfg, dict):
|
||||
fv = tool_cfg.get("florencevision")
|
||||
if isinstance(fv, dict) and _as_bool(fv.get("enabled"), False):
|
||||
auto_install = _as_bool(fv.get("auto_install"), True)
|
||||
if auto_install:
|
||||
missing = florencevision_missing_modules()
|
||||
if missing:
|
||||
_install_requirements("FlorenceVision", missing)
|
||||
|
||||
fv = tool_cfg.get("florencevision")
|
||||
if isinstance(fv, dict) and _as_bool(fv.get("enabled"), False):
|
||||
auto_install = _as_bool(fv.get("auto_install"), True)
|
||||
if not auto_install:
|
||||
return
|
||||
|
||||
missing = florencevision_missing_modules()
|
||||
if not missing:
|
||||
return
|
||||
|
||||
names = ", ".join(missing)
|
||||
try:
|
||||
with stdout_console().status(
|
||||
f"Installing FlorenceVision dependencies: {names}",
|
||||
spinner="dots",
|
||||
):
|
||||
ok, detail = _pip_install(missing)
|
||||
except Exception:
|
||||
log(f"[startup] FlorenceVision dependencies missing ({names}). Attempting auto-install...")
|
||||
ok, detail = _pip_install(missing)
|
||||
|
||||
if ok:
|
||||
log("[startup] FlorenceVision dependency install OK")
|
||||
else:
|
||||
log(f"[startup] FlorenceVision dependency auto-install failed. {detail}")
|
||||
provider_missing = _provider_missing_modules(config)
|
||||
for provider_name, requirements in provider_missing.items():
|
||||
label = f"{provider_name.title()} provider"
|
||||
_install_requirements(label, requirements)
|
||||
|
||||
|
||||
__all__ = ["maybe_auto_install_configured_tools", "florencevision_missing_modules"]
|
||||
|
||||
@@ -2567,13 +2567,6 @@ def resolve_tidal_manifest_path(item: Any) -> Optional[str]:
|
||||
metadata["_tidal_manifest_url"] = selected_url
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
log(
|
||||
f"[hifi] Resolved JSON manifest for track {metadata.get('trackId') or metadata.get('id')} to {selected_url}",
|
||||
file=sys.stderr,
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
return selected_url
|
||||
try:
|
||||
metadata["_tidal_manifest_error"] = "JSON manifest contained no urls"
|
||||
|
||||
@@ -640,11 +640,13 @@ class Add_File(Cmdlet):
|
||||
or tidal_metadata.get("_tidal_manifest_url")
|
||||
)
|
||||
if not manifest_source:
|
||||
target_is_mpd = False
|
||||
if isinstance(media_path_or_url, Path):
|
||||
manifest_source = media_path_or_url
|
||||
target_is_mpd = str(media_path_or_url).lower().endswith(".mpd")
|
||||
elif isinstance(media_path_or_url, str):
|
||||
if media_path_or_url.lower().endswith(".mpd"):
|
||||
manifest_source = media_path_or_url
|
||||
target_is_mpd = media_path_or_url.lower().endswith(".mpd")
|
||||
if target_is_mpd:
|
||||
manifest_source = media_path_or_url
|
||||
|
||||
if manifest_source:
|
||||
downloaded, tmp_dir = self._download_manifest_with_ffmpeg(manifest_source)
|
||||
|
||||
@@ -39,6 +39,8 @@ dependencies = [
|
||||
"yt-dlp[default]>=2023.11.0",
|
||||
"yt-dlp-ejs", # EJS challenge solver scripts for YouTube JavaScript challenges
|
||||
"requests>=2.31.0",
|
||||
"charset-normalizer>=3.2.0",
|
||||
"certifi>=2024.12.0",
|
||||
"httpx>=0.25.0",
|
||||
|
||||
# Document and data handling
|
||||
@@ -57,7 +59,7 @@ dependencies = [
|
||||
"lxml>=4.9.0",
|
||||
|
||||
# Advanced searching and libraries
|
||||
"aioslsk>=1.6.0",
|
||||
# Optional Soulseek support installs aioslsk>=1.6.0 when [provider=soulseek] is configured.
|
||||
"imdbinfo>=0.1.10",
|
||||
|
||||
# Encryption and security
|
||||
|
||||
@@ -6,7 +6,8 @@ Medios-Macina is a CLI media manager and toolkit focused on downloading, tagging
|
||||
- **Flexible syntax structure:** chain commands with `|` and select options from tables with `@N`.
|
||||
- **Multiple file stores:** *HYDRUSNETWORK, FOLDER*
|
||||
- **Provider plugin integration:** *YOUTUBE, OPENLIBRARY, INTERNETARCHIVE, SOULSEEK, LIBGEN, ALLDEBRID, TELEGRAM, BANDCAMP*
|
||||
- **Module Mixing:** *[Playwright](https://github.com/microsoft/playwright), [yt-dlp](https://github.com/yt-dlp/yt-dlp), [aioslsk](https://github.com/JurgenR/aioslsk), [telethon](https://github.com/LonamiWebs/Telethon),[typer](https://github.com/fastapi/typer)*
|
||||
- **Module Mixing:** *[Playwright](https://github.com/microsoft/playwright), [yt-dlp](https://github.com/yt-dlp/yt-dlp), [typer](https://github.com/fastapi/typer)*
|
||||
- **Optional stacks:** Telethon (Telegram), aioslsk (Soulseek), and the FlorenceVision tooling install automatically when you configure the corresponding provider/tool blocks.
|
||||
- **MPV Manager:** Play audio, video, and even images in a custom designed MPV with trimming, screenshotting, and more built right in!
|
||||
|
||||
## installation ⚡
|
||||
@@ -17,6 +18,10 @@ GIT CLONE https://code.glowers.club/goyimnose/Medios-Macina
|
||||
|
||||
- When run interactively (a normal terminal), `bootstrap.py` will show a short menu to Install or Uninstall the project. For non-interactive runs use flags such as `--no-playwright`, `--uninstall`, or `-y` to assume yes for confirmations.
|
||||
|
||||
*Note:* If you run `--uninstall` while the local `.venv` is activated, the uninstaller will try to re-run the uninstall using a Python interpreter outside the venv so files can be removed safely (this is helpful on Windows). If no suitable interpreter can be found, deactivate the venv (`deactivate`) and re-run the uninstall.
|
||||
|
||||
Optional providers/tools bring their own dependencies instead of shipping as part of the base install; just configure `[provider=telegram]`, `[provider=soulseek]`, or `[tool=florencevision]` and the CLI will install the required packages on startup.
|
||||
|
||||
2. rename config.conf.remove to config.conf the store=folder path should be empty folder with no other files in it.
|
||||
|
||||
```ini
|
||||
|
||||
@@ -526,41 +526,10 @@ try {
|
||||
$globalBin = Join-Path $env:USERPROFILE 'bin'
|
||||
New-Item -ItemType Directory -Path $globalBin -Force | Out-Null
|
||||
|
||||
$mmCmd = Join-Path $globalBin 'mm.cmd'
|
||||
$mmPs1 = Join-Path $globalBin 'mm.ps1'
|
||||
|
||||
$repo = $repoRoot
|
||||
|
||||
$cmdText = @"
|
||||
@echo off
|
||||
set "REPO=__REPO__"
|
||||
if exist "%REPO%\.venv\Scripts\mm.exe" "%REPO%\.venv\Scripts\mm.exe" %*
|
||||
if defined MM_DEBUG (
|
||||
echo MM_DEBUG: REPO=%REPO%
|
||||
if exist "%REPO%\.venv\Scripts\python.exe" (
|
||||
"%REPO%\.venv\Scripts\python.exe" -c "import sys,importlib,importlib.util; print('sys.executable:', sys.executable); print('sys.path (first 8):', sys.path[:8]);"
|
||||
) else (
|
||||
python -c "import sys,importlib,importlib.util; print('sys.executable:', sys.executable); print('sys.path (first 8):', sys.path[:8]);"
|
||||
)
|
||||
)
|
||||
if exist "%REPO%\.venv\Scripts\python.exe" (
|
||||
"%REPO%\.venv\Scripts\python.exe" -m medeia_macina.cli_entry %*
|
||||
exit /b %ERRORLEVEL%
|
||||
)
|
||||
if exist "%REPO%\CLI.py" (
|
||||
python "%REPO%\CLI.py" %*
|
||||
exit /b %ERRORLEVEL%
|
||||
)
|
||||
python -m medeia_macina.cli_entry %*
|
||||
"@
|
||||
# Inject actual repo path safely (escape double-quotes if any)
|
||||
$cmdText = $cmdText.Replace('__REPO__', $repo.Replace('"', '""'))
|
||||
if (Test-Path $mmCmd) {
|
||||
$bak = "$mmCmd.bak$(Get-Date -UFormat %s)"
|
||||
Move-Item -Path $mmCmd -Destination $bak -Force
|
||||
}
|
||||
Set-Content -LiteralPath $mmCmd -Value $cmdText -Encoding UTF8
|
||||
|
||||
# PowerShell shim: use single-quoted here-string so literal PowerShell variables
|
||||
# (like $args) are not expanded by this script when writing the file.
|
||||
$ps1Text = @'
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
Unified project bootstrap helper (Python-only).
|
||||
|
||||
This script installs Python dependencies from `requirements.txt` and then
|
||||
This script installs Python dependencies from `scripts/requirements.txt` and then
|
||||
downloads Playwright browser binaries by running `python -m playwright install`.
|
||||
By default this script installs **Chromium** only to conserve space; pass
|
||||
`--browsers all` to install all supported engines (chromium, firefox, webkit).
|
||||
@@ -30,7 +30,7 @@ Usage:
|
||||
python ./scripts/bootstrap.py --playwright-only
|
||||
|
||||
Optional flags:
|
||||
--skip-deps Skip `pip install -r requirements.txt` step
|
||||
--skip-deps Skip `pip install -r scripts/requirements.txt` step
|
||||
--no-playwright Skip running `python -m playwright install` (still installs deps)
|
||||
--playwright-only Install only Playwright browsers (installs playwright package if missing)
|
||||
--browsers Comma-separated list of Playwright browsers to install (default: chromium)
|
||||
@@ -223,7 +223,7 @@ def main() -> int:
|
||||
parser.add_argument(
|
||||
"--skip-deps",
|
||||
action="store_true",
|
||||
help="Skip installing Python dependencies from requirements.txt",
|
||||
help="Skip installing Python dependencies from scripts/requirements.txt",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--no-playwright",
|
||||
@@ -317,12 +317,102 @@ def main() -> int:
|
||||
return False
|
||||
|
||||
def _do_uninstall() -> int:
|
||||
"""Attempt to remove the local venv and any shims written to the user's bin."""
|
||||
"""Attempt to remove the local venv and any shims written to the user's bin.
|
||||
|
||||
If this script is running using the Python inside the local `.venv`, we
|
||||
attempt to re-run the uninstall using a Python interpreter outside the
|
||||
venv (so files can be removed on Windows). If no suitable external
|
||||
interpreter can be found, the user is asked to deactivate the venv and
|
||||
re-run the uninstall.
|
||||
"""
|
||||
vdir = repo_root / ".venv"
|
||||
if not vdir.exists():
|
||||
if not args.quiet:
|
||||
print("No local .venv found; nothing to uninstall.")
|
||||
return 0
|
||||
|
||||
# If the current interpreter is the one inside the local venv, try to
|
||||
# run the uninstall via a Python outside the venv so files (including
|
||||
# the interpreter binary) can be removed on Windows.
|
||||
try:
|
||||
current_exe = Path(sys.executable).resolve()
|
||||
in_venv = str(current_exe).lower().startswith(str(vdir.resolve()).lower())
|
||||
except Exception:
|
||||
in_venv = False
|
||||
|
||||
if in_venv:
|
||||
if not args.quiet:
|
||||
print(f"Detected local venv Python in use: {current_exe}")
|
||||
|
||||
if not args.yes:
|
||||
try:
|
||||
resp = input("Uninstall will be attempted using a system Python outside the .venv. Continue? [Y/n]: ")
|
||||
except EOFError:
|
||||
print("Non-interactive environment; pass --uninstall --yes to uninstall without prompts.", file=sys.stderr)
|
||||
return 2
|
||||
if resp.strip().lower() in ("n", "no"):
|
||||
print("Uninstall aborted.")
|
||||
return 1
|
||||
|
||||
def _find_external_python() -> list[str] | None:
|
||||
"""Return a command (list) for a Python interpreter outside the venv, or None."""
|
||||
try:
|
||||
base = Path(sys.base_prefix)
|
||||
candidates: list[Path | str] = []
|
||||
if platform.system().lower() == "windows":
|
||||
candidates.append(base / "python.exe")
|
||||
else:
|
||||
candidates.extend([base / "bin" / "python3", base / "bin" / "python"])
|
||||
|
||||
for name in ("python3", "python"):
|
||||
p = shutil.which(name)
|
||||
if p:
|
||||
candidates.append(Path(p))
|
||||
|
||||
# Special-case the Windows py launcher: ensure it resolves
|
||||
# to a Python outside the venv before returning ['py','-3']
|
||||
if platform.system().lower() == "windows":
|
||||
py_launcher = shutil.which("py")
|
||||
if py_launcher:
|
||||
try:
|
||||
out = subprocess.check_output(["py", "-3", "-c", "import sys; print(sys.executable)"], text=True).strip()
|
||||
if out and not str(Path(out).resolve()).lower().startswith(str(vdir.resolve()).lower()):
|
||||
return ["py", "-3"]
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
for c in candidates:
|
||||
try:
|
||||
if isinstance(c, Path) and c.exists():
|
||||
c_resolved = Path(c).resolve()
|
||||
if not str(c_resolved).lower().startswith(str(vdir.resolve()).lower()) and c_resolved != current_exe:
|
||||
return [str(c_resolved)]
|
||||
except Exception:
|
||||
continue
|
||||
except Exception:
|
||||
pass
|
||||
return None
|
||||
|
||||
ext = _find_external_python()
|
||||
if ext:
|
||||
cmd = ext + [str(repo_root / "scripts" / "bootstrap.py"), "--uninstall", "--yes"]
|
||||
if not args.quiet:
|
||||
print("Attempting uninstall using external Python:", " ".join(cmd))
|
||||
rc = subprocess.run(cmd)
|
||||
if rc.returncode != 0:
|
||||
print(
|
||||
f"External uninstall exited with {rc.returncode}; ensure no processes are using files in {vdir} and try again.",
|
||||
file=sys.stderr,
|
||||
)
|
||||
return int(rc.returncode or 0)
|
||||
|
||||
print(
|
||||
"Could not find a Python interpreter outside the local .venv. Please deactivate your venv (run 'deactivate') or run the uninstall from a system Python:\n python ./scripts/bootstrap.py --uninstall --yes",
|
||||
file=sys.stderr,
|
||||
)
|
||||
return 2
|
||||
|
||||
# Normal (non-venv) uninstall flow: confirm and remove launchers, shims, and venv
|
||||
if not args.yes:
|
||||
try:
|
||||
prompt = input(f"Remove local virtualenv at {vdir} and installed user shims? [y/N]: ")
|
||||
@@ -334,15 +424,19 @@ def main() -> int:
|
||||
return 1
|
||||
|
||||
# Remove repo-local launchers
|
||||
for name in ("mm", "mm.ps1", "mm.bat"):
|
||||
p = repo_root / name
|
||||
if p.exists():
|
||||
def _remove_launcher(path: Path) -> None:
|
||||
if path.exists():
|
||||
try:
|
||||
p.unlink()
|
||||
path.unlink()
|
||||
if not args.quiet:
|
||||
print(f"Removed local launcher: {p}")
|
||||
print(f"Removed local launcher: {path}")
|
||||
except Exception as exc:
|
||||
print(f"Warning: failed to remove {p}: {exc}", file=sys.stderr)
|
||||
print(f"Warning: failed to remove {path}: {exc}", file=sys.stderr)
|
||||
|
||||
scripts_launcher = repo_root / "scripts" / "mm.ps1"
|
||||
_remove_launcher(scripts_launcher)
|
||||
for legacy in ("mm", "mm.ps1", "mm.bat"):
|
||||
_remove_launcher(repo_root / legacy)
|
||||
|
||||
# Remove user shims that the installer may have written
|
||||
try:
|
||||
@@ -350,12 +444,15 @@ def main() -> int:
|
||||
if system == "windows":
|
||||
user_bin = Path(os.environ.get("USERPROFILE", str(Path.home()))) / "bin"
|
||||
if user_bin.exists():
|
||||
for name in ("mm.cmd", "mm.ps1"):
|
||||
for name in ("mm.ps1",):
|
||||
p = user_bin / name
|
||||
if p.exists():
|
||||
p.unlink()
|
||||
if not args.quiet:
|
||||
print(f"Removed user shim: {p}")
|
||||
try:
|
||||
p.unlink()
|
||||
if not args.quiet:
|
||||
print(f"Removed user shim: {p}")
|
||||
except Exception as exc:
|
||||
print(f"Warning: failed to remove {p}: {exc}", file=sys.stderr)
|
||||
else:
|
||||
user_bin = Path(os.environ.get("XDG_BIN_HOME", str(Path.home() / ".local/bin")))
|
||||
if user_bin.exists():
|
||||
@@ -480,11 +577,37 @@ def main() -> int:
|
||||
except subprocess.CalledProcessError as exc:
|
||||
print(f"Failed to create or prepare local venv: {exc}", file=sys.stderr)
|
||||
raise
|
||||
|
||||
def _ensure_pip_available(python_path: Path) -> None:
|
||||
"""Ensure pip is available inside the venv; fall back to ensurepip if needed."""
|
||||
|
||||
try:
|
||||
subprocess.run(
|
||||
[str(python_path), "-m", "pip", "--version"],
|
||||
stdout=subprocess.DEVNULL,
|
||||
stderr=subprocess.DEVNULL,
|
||||
check=True,
|
||||
)
|
||||
return
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if not args.quiet:
|
||||
print("Bootstrapping pip inside the local virtualenv...")
|
||||
try:
|
||||
run([str(python_path), "-m", "ensurepip", "--upgrade"])
|
||||
except subprocess.CalledProcessError as exc:
|
||||
print(
|
||||
"Failed to install pip inside the local virtualenv via ensurepip; ensure your Python build includes ensurepip and retry.",
|
||||
file=sys.stderr,
|
||||
)
|
||||
raise
|
||||
|
||||
# Ensure a local venv is present and use it for subsequent installs.
|
||||
venv_python = _ensure_local_venv()
|
||||
if not args.quiet:
|
||||
print(f"Using venv python: {venv_python}")
|
||||
_ensure_pip_available(venv_python)
|
||||
|
||||
# Enforce opinionated behavior: install deps, playwright, deno, and install project in editable mode.
|
||||
# Ignore `--skip-deps` and `--install-editable` flags to keep the setup deterministic.
|
||||
@@ -531,7 +654,7 @@ def main() -> int:
|
||||
)
|
||||
|
||||
if not args.skip_deps:
|
||||
req_file = repo_root / "requirements.txt"
|
||||
req_file = repo_root / "scripts" / "requirements.txt"
|
||||
if not req_file.exists():
|
||||
print(
|
||||
f"requirements.txt not found at {req_file}; skipping dependency installation.",
|
||||
@@ -662,35 +785,15 @@ def main() -> int:
|
||||
print("Deno installation failed.", file=sys.stderr)
|
||||
return rc
|
||||
|
||||
# Write project-local launcher scripts (project root) that prefer the local .venv
|
||||
# Write project-local launcher script under scripts/ to keep the repo root uncluttered.
|
||||
def _write_launchers() -> None:
|
||||
sh = repo_root / "mm"
|
||||
ps1 = repo_root / "mm.ps1"
|
||||
bat = repo_root / "mm.bat"
|
||||
|
||||
sh_text = """#!/usr/bin/env bash
|
||||
set -e
|
||||
SCRIPT_DIR=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\" && pwd)\"
|
||||
REPO=\"$SCRIPT_DIR\"
|
||||
VENV=\"$REPO/.venv\"
|
||||
# Make tools installed into the local venv available in PATH for provider discovery
|
||||
export PATH=\"$VENV/bin:$PATH\"
|
||||
PY=\"$VENV/bin/python\"
|
||||
if [ -x \"$PY\" ]; then
|
||||
exec \"$PY\" -m medeia_macina.cli_entry \"$@\"
|
||||
else
|
||||
exec python -m medeia_macina.cli_entry \"$@\"
|
||||
fi
|
||||
"""
|
||||
try:
|
||||
sh.write_text(sh_text, encoding="utf-8")
|
||||
sh.chmod(sh.stat().st_mode | 0o111)
|
||||
except Exception:
|
||||
pass
|
||||
launcher_dir = repo_root / "scripts"
|
||||
launcher_dir.mkdir(parents=True, exist_ok=True)
|
||||
ps1 = launcher_dir / "mm.ps1"
|
||||
|
||||
ps1_text = r"""Param([Parameter(ValueFromRemainingArguments=$true)] $args)
|
||||
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||||
$repo = $scriptDir
|
||||
$repo = (Resolve-Path (Join-Path $scriptDir "..")).Path
|
||||
$venv = Join-Path $repo '.venv'
|
||||
# Ensure venv Scripts dir is on PATH for provider discovery
|
||||
$venvScripts = Join-Path $venv 'Scripts'
|
||||
@@ -707,19 +810,6 @@ python -m medeia_macina.cli_entry @args
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
bat_text = (
|
||||
"@echo off\r\n"
|
||||
"set SCRIPT_DIR=%~dp0\r\n"
|
||||
"set PATH=%SCRIPT_DIR%\\.venv\\Scripts;%PATH%\r\n"
|
||||
'if exist "%SCRIPT_DIR%\\.venv\\Scripts\\python.exe" "%SCRIPT_DIR%\\.venv\\Scripts\\python.exe" -m medeia_macina.cli_entry %*\r\n'
|
||||
'if exist "%SCRIPT_DIR%\\CLI.py" python "%SCRIPT_DIR%\\CLI.py" %*\r\n'
|
||||
"python -m medeia_macina.cli_entry %*\r\n"
|
||||
)
|
||||
try:
|
||||
bat.write_text(bat_text, encoding="utf-8")
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
_write_launchers()
|
||||
|
||||
# Install user-global shims so `mm` can be executed from any shell session.
|
||||
@@ -732,24 +822,6 @@ python -m medeia_macina.cli_entry @args
|
||||
user_bin = Path(os.environ.get("USERPROFILE", str(home))) / "bin"
|
||||
user_bin.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# Write mm.cmd (CMD shim)
|
||||
mm_cmd = user_bin / "mm.cmd"
|
||||
cmd_text = (
|
||||
f"@echo off\r\n"
|
||||
f"set REPO={repo}\r\n"
|
||||
f'if exist "%REPO%\\.venv\\Scripts\\mm.exe" "%REPO%\\.venv\\Scripts\\mm.exe" %*\r\n'
|
||||
f"if defined MM_DEBUG (\r\n"
|
||||
f" echo MM_DEBUG: REPO=%REPO%\r\n"
|
||||
f' if exist "%REPO%\\.venv\\Scripts\\python.exe" "%REPO%\\.venv\\Scripts\\python.exe" -c "import sys,importlib,importlib.util; print(\'sys.executable:\', sys.executable); print(\'sys.path (first 8):\', sys.path[:8]);" \r\n'
|
||||
f")\r\n"
|
||||
f'if exist "%REPO%\\.venv\\Scripts\\python.exe" "%REPO%\\.venv\\Scripts\\python.exe" -m medeia_macina.cli_entry %*\r\n'
|
||||
f"python -m medeia_macina.cli_entry %*\r\n"
|
||||
)
|
||||
if mm_cmd.exists():
|
||||
bak = mm_cmd.with_suffix(f".bak{int(time.time())}")
|
||||
mm_cmd.replace(bak)
|
||||
mm_cmd.write_text(cmd_text, encoding="utf-8")
|
||||
|
||||
# Write mm.ps1 (PowerShell shim)
|
||||
mm_ps1 = user_bin / "mm.ps1"
|
||||
ps1_text = (
|
||||
|
||||
@@ -10,7 +10,7 @@ Works on Linux and Windows. Behavior:
|
||||
2) Update hydrus (git pull)
|
||||
3) Re-clone (remove and re-clone)
|
||||
- If `git` is not available, the script will fall back to downloading the repository ZIP and extracting it.
|
||||
- By default the script will create a repository-local virtual environment `./<dest>/.venv` after cloning/extraction; use `--no-venv` to skip this. By default the script will install dependencies from `requirements.txt` into that venv (use `--no-install-deps` to skip). After setup the script will print instructions for running the client; use `--run-client` to *launch* `hydrus_client.py` using the created repo venv's Python (use `--run-client-detached` to run it in the background).
|
||||
- By default the script will create a repository-local virtual environment `./<dest>/.venv` after cloning/extraction; use `--no-venv` to skip this. By default the script will install dependencies from `scripts/requirements.txt` into that venv (use `--no-install-deps` to skip). After setup the script will print instructions for running the client; use `--run-client` to *launch* `hydrus_client.py` using the created repo venv's Python (use `--run-client-detached` to run it in the background).
|
||||
|
||||
Examples:
|
||||
python scripts/hydrusnetwork.py
|
||||
@@ -510,17 +510,18 @@ def fix_permissions(
|
||||
|
||||
|
||||
def find_requirements(root: Path) -> Optional[Path]:
|
||||
"""Return a requirements.txt Path if found in common locations (root, client, requirements) or via a shallow search.
|
||||
"""Return a requirements.txt Path if found in common locations (scripts, root, client, requirements) or via a shallow search.
|
||||
|
||||
This tries a few sensible locations used by various projects and performs a shallow
|
||||
two-level walk to find a requirements.txt so installation works even if the file is
|
||||
not at the repository root.
|
||||
"""
|
||||
candidates = [
|
||||
root / "requirements.txt",
|
||||
root / "client" / "requirements.txt",
|
||||
root / "requirements" / "requirements.txt",
|
||||
]
|
||||
candidates = [
|
||||
root / "scripts" / "requirements.txt",
|
||||
root / "requirements.txt",
|
||||
root / "client" / "requirements.txt",
|
||||
root / "requirements" / "requirements.txt",
|
||||
]
|
||||
for c in candidates:
|
||||
if c.exists():
|
||||
return c
|
||||
|
||||
@@ -8,7 +8,10 @@ textual>=0.30.0
|
||||
yt-dlp[default]>=2023.11.0
|
||||
requests>=2.31.0
|
||||
httpx>=0.25.0
|
||||
telethon>=1.36.0
|
||||
# Ensure requests can detect encodings and ship certificates
|
||||
charset-normalizer>=3.2.0
|
||||
certifi>=2024.12.0
|
||||
# Optional Telegram support installs telethon>=1.36.0 when [provider=telegram] is configured.
|
||||
internetarchive>=4.1.0
|
||||
|
||||
# Document and data handling
|
||||
@@ -22,18 +25,12 @@ Pillow>=10.0.0
|
||||
python-bidi>=0.4.2
|
||||
ffmpeg-python>=0.2.0
|
||||
|
||||
# AI tagging (FlorenceVision tool)
|
||||
transformers>=4.45.0
|
||||
torch>=2.4.0
|
||||
einops>=0.8.0
|
||||
timm>=1.0.0
|
||||
|
||||
# Metadata extraction and processing
|
||||
musicbrainzngs>=0.7.0
|
||||
lxml>=4.9.0
|
||||
|
||||
# Advanced searching and libraries
|
||||
aioslsk>=1.6.0
|
||||
# Optional Soulseek support installs aioslsk>=1.6.0 when [provider=soulseek] is configured.
|
||||
imdbinfo>=0.1.10
|
||||
|
||||
# Encryption and security (if needed by Crypto usage)
|
||||
@@ -7,7 +7,7 @@ present or its copy of this helper gets removed.
|
||||
|
||||
Features (subset of the repo helper):
|
||||
- Locate repository venv (default: <workspace>/hydrusnetwork/.venv)
|
||||
- Install or reinstall requirements.txt into the venv
|
||||
- Install or reinstall scripts/requirements.txt into the venv
|
||||
- Verify key imports
|
||||
- Launch hydrus_client.py (foreground or detached)
|
||||
- Install/uninstall simple user-level start-on-boot services (schtasks/systemd/crontab)
|
||||
@@ -50,7 +50,7 @@ def get_python_in_venv(venv_dir: Path) -> Optional[Path]:
|
||||
|
||||
|
||||
def find_requirements(root: Path) -> Optional[Path]:
|
||||
candidates = [root / "requirements.txt", root / "client" / "requirements.txt"]
|
||||
candidates = [root / "scripts" / "requirements.txt", root / "requirements.txt", root / "client" / "requirements.txt"]
|
||||
for c in candidates:
|
||||
if c.exists():
|
||||
return c
|
||||
|
||||
Reference in New Issue
Block a user