Add Qt mac backend support, Safari/Chrome playback fixes, and AV1 reel updates
This commit is contained in:
+5
-5
@@ -44,19 +44,19 @@ export function getVideoPreviewUrl(url: string): string {
|
||||
|
||||
export function getVideoSourceAttributes(path: string | null | undefined): VideoSourceAttributes {
|
||||
if (!path) {
|
||||
return { type: 'video/mp4; codecs="hvc1"', codecs: 'hvc1' };
|
||||
return { type: 'video/mp4', codecs: 'hvc1' };
|
||||
}
|
||||
|
||||
if (/\.webm$/i.test(path)) {
|
||||
return { type: 'video/webm; codecs="av01"', codecs: 'av01' };
|
||||
return { type: 'video/webm', codecs: 'av1' };
|
||||
}
|
||||
|
||||
if (/\.mp4$/i.test(path) || /\.m4v$/i.test(path)) {
|
||||
return { type: 'video/mp4; codecs="hvc1"', codecs: 'hvc1' };
|
||||
return { type: 'video/mp4', codecs: 'hvc1' };
|
||||
}
|
||||
|
||||
if (/\.mov$/i.test(path)) {
|
||||
return { type: 'video/quicktime; codecs="hvc1"', codecs: 'hvc1' };
|
||||
return { type: 'video/quicktime', codecs: 'hvc1' };
|
||||
}
|
||||
|
||||
if (/\.avi$/i.test(path)) {
|
||||
@@ -67,7 +67,7 @@ export function getVideoSourceAttributes(path: string | null | undefined): Video
|
||||
return { type: 'video/x-matroska', codecs: '' };
|
||||
}
|
||||
|
||||
return { type: 'video/mp4; codecs="hvc1"', codecs: 'hvc1' };
|
||||
return { type: 'video/mp4', codecs: 'hvc1' };
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -33,6 +33,8 @@ async def _subprocess_exec(*args, **kwargs):
|
||||
# Showreel timestamp positions in seconds (5, 10, 15, 20, 25 minutes)
|
||||
SHOWREEL_TIMESTAMPS = [5 * 60, 10 * 60, 15 * 60, 20 * 60, 25 * 60]
|
||||
REEL_SOURCE_EXTENSIONS = [".webm", ".mp4"]
|
||||
# Temporary rollout switch: keep platform-native MP4/H.265 path available but disabled.
|
||||
ENABLE_PLATFORM_NATIVE_REELS = False
|
||||
|
||||
|
||||
def get_reel_source_extensions() -> list[str]:
|
||||
@@ -52,11 +54,15 @@ def _to_media_path(path: Path, media_root: Optional[Path] = None) -> str:
|
||||
|
||||
def get_reel_extension() -> str:
|
||||
"""Return the platform-native reel file extension."""
|
||||
if not ENABLE_PLATFORM_NATIVE_REELS:
|
||||
return ".webm"
|
||||
return ".mp4" if sys.platform == "darwin" else ".webm"
|
||||
|
||||
|
||||
async def get_reel_video_encoder() -> str:
|
||||
"""Return the platform-native reel video encoder."""
|
||||
if not ENABLE_PLATFORM_NATIVE_REELS:
|
||||
return await get_av1_encoder()
|
||||
if sys.platform == "darwin":
|
||||
return "libx265"
|
||||
return await get_av1_encoder()
|
||||
@@ -73,6 +79,8 @@ def get_reel_video_options(encoder: str) -> list[str]:
|
||||
|
||||
def get_reel_audio_options() -> list[str]:
|
||||
"""Return ffmpeg audio and container options for the current platform."""
|
||||
if not ENABLE_PLATFORM_NATIVE_REELS:
|
||||
return ["-c:a", "libopus", "-ac", "2", "-b:a", "128k"]
|
||||
if sys.platform == "darwin":
|
||||
return ["-c:a", "aac", "-ac", "2", "-b:a", "128k", "-movflags", "+faststart"]
|
||||
return ["-c:a", "libopus", "-ac", "2", "-b:a", "128k"]
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
"""Shared data/protocol models for MediaHive."""
|
||||
+25
-2
@@ -28,6 +28,8 @@ import webview
|
||||
from mediahive.__main__ import resolve_media_root
|
||||
from mediahive.config import load_config, save_config
|
||||
|
||||
logger = logging.getLogger("mediahive.winmain")
|
||||
|
||||
BACKEND_HOST = "127.0.0.1"
|
||||
BACKEND_PORT = 8420
|
||||
HEALTH_TIMEOUT = 2 # seconds
|
||||
@@ -669,6 +671,19 @@ def _icon_path() -> str | None:
|
||||
return str(ico) if ico.exists() else None
|
||||
|
||||
|
||||
def _webview_start_kwargs() -> dict[str, str]:
|
||||
"""Return platform-specific pywebview startup kwargs."""
|
||||
# On macOS, force Qt backend so pywebview uses Chromium/WebEngine instead of WKWebView.
|
||||
if sys.platform == "darwin":
|
||||
return {"gui": "qt"}
|
||||
return {}
|
||||
|
||||
|
||||
def _selected_webview_backend() -> str:
|
||||
"""Return the configured pywebview GUI backend name for logging."""
|
||||
return _webview_start_kwargs().get("gui", "default")
|
||||
|
||||
|
||||
def _run_initial_setup() -> str | None:
|
||||
"""Show a setup window, prompt for a folder, then close and return the path.
|
||||
|
||||
@@ -691,7 +706,7 @@ def _run_initial_setup() -> str | None:
|
||||
chosen.append(result[0])
|
||||
window.destroy()
|
||||
|
||||
webview.start(func=on_shown, icon=_icon_path())
|
||||
webview.start(func=on_shown, icon=_icon_path(), **_webview_start_kwargs())
|
||||
return chosen[0] if chosen else None
|
||||
|
||||
|
||||
@@ -766,6 +781,7 @@ def winmain() -> None:
|
||||
raise RuntimeError(f"Backend did not become ready within {HEALTH_TIMEOUT}s")
|
||||
|
||||
api = JsApi()
|
||||
logger.info("Configured pywebview backend: %s", _selected_webview_backend())
|
||||
window = webview.create_window(
|
||||
title="MediaHive",
|
||||
url=backend_url,
|
||||
@@ -778,11 +794,18 @@ def winmain() -> None:
|
||||
|
||||
def on_shown() -> None:
|
||||
api._window = window
|
||||
try:
|
||||
user_agent = window.evaluate_js("navigator.userAgent")
|
||||
if isinstance(user_agent, str):
|
||||
logger.info("Embedded webview user agent: %s", user_agent)
|
||||
except Exception as exc:
|
||||
logger.warning("Could not read embedded user agent: %s", exc)
|
||||
|
||||
nonlocal poll_thread
|
||||
if poll_thread is None and _supports_gamepad_remote():
|
||||
poll_thread = _start_gamepad_remote(poll_stop, mediaroot)
|
||||
|
||||
webview.start(func=on_shown, icon=_icon_path())
|
||||
webview.start(func=on_shown, icon=_icon_path(), **_webview_start_kwargs())
|
||||
|
||||
poll_stop.set()
|
||||
if poll_thread is not None:
|
||||
|
||||
+4
-1
@@ -46,7 +46,10 @@ parse-torrent-title = { git = "https://github.com/platelminto/parse-torrent-titl
|
||||
|
||||
[project.optional-dependencies]
|
||||
gui = [
|
||||
"pywebview>=6.2.1",
|
||||
"pywebview>=6.2.1; platform_system != 'Darwin'",
|
||||
"pywebview[qt5]>=6.2.1; platform_system == 'Darwin'",
|
||||
"qtpy>=2.4.1; platform_system == 'Darwin'",
|
||||
"PyQt5>=5.15.11; platform_system == 'Darwin'",
|
||||
"pythonnet>=3.1.0rc0; platform_system == 'Windows' and python_version >= '3.14'",
|
||||
"pyinstaller>=6.0",
|
||||
]
|
||||
|
||||
+46
-30
@@ -36,41 +36,57 @@ if _icon_win.exists():
|
||||
if _icon_mac.exists():
|
||||
_datas.append((str(_icon_mac), "mediahive/assets"))
|
||||
|
||||
_hiddenimports = [
|
||||
# uvicorn dynamic imports
|
||||
"uvicorn.logging",
|
||||
"uvicorn.loops",
|
||||
"uvicorn.loops.auto",
|
||||
"uvicorn.loops.asyncio",
|
||||
"uvicorn.protocols",
|
||||
"uvicorn.protocols.http",
|
||||
"uvicorn.protocols.http.auto",
|
||||
"uvicorn.protocols.http.h11_impl",
|
||||
"uvicorn.protocols.websockets",
|
||||
"uvicorn.protocols.websockets.auto",
|
||||
"uvicorn.protocols.websockets.websockets_impl",
|
||||
"uvicorn.lifespan",
|
||||
"uvicorn.lifespan.on",
|
||||
# mediahive & hivescan modules imported at runtime
|
||||
"mediahive.server",
|
||||
"mediahive.hivescan.scanner",
|
||||
"mediahive.hivescan.indexer",
|
||||
"mediahive.hivescan.scanning",
|
||||
"mediahive.hivescan.images",
|
||||
"mediahive.hivescan.showreel",
|
||||
"mediahive.hivescan.tmdb_client",
|
||||
# async / ASGI internals
|
||||
"anyio",
|
||||
"anyio._backends._asyncio",
|
||||
"starlette.routing",
|
||||
# msgspec TOML write backend
|
||||
"tomli_w",
|
||||
]
|
||||
|
||||
if sys.platform == "darwin":
|
||||
_hiddenimports.extend(
|
||||
[
|
||||
# pywebview Qt backend selected dynamically via webview.start(gui="qt")
|
||||
"webview.platforms.qt",
|
||||
"qtpy",
|
||||
"PyQt5",
|
||||
"PyQt5.QtCore",
|
||||
"PyQt5.QtGui",
|
||||
"PyQt5.QtWidgets",
|
||||
"PyQt5.QtWebEngineWidgets",
|
||||
]
|
||||
)
|
||||
|
||||
a = Analysis(
|
||||
[mediahive.winmain.__file__],
|
||||
pathex=[],
|
||||
binaries=_binaries,
|
||||
datas=_datas,
|
||||
hiddenimports=[
|
||||
# uvicorn dynamic imports
|
||||
"uvicorn.logging",
|
||||
"uvicorn.loops",
|
||||
"uvicorn.loops.auto",
|
||||
"uvicorn.loops.asyncio",
|
||||
"uvicorn.protocols",
|
||||
"uvicorn.protocols.http",
|
||||
"uvicorn.protocols.http.auto",
|
||||
"uvicorn.protocols.http.h11_impl",
|
||||
"uvicorn.protocols.websockets",
|
||||
"uvicorn.protocols.websockets.auto",
|
||||
"uvicorn.protocols.websockets.websockets_impl",
|
||||
"uvicorn.lifespan",
|
||||
"uvicorn.lifespan.on",
|
||||
# mediahive & hivescan modules imported at runtime
|
||||
"mediahive.server",
|
||||
"mediahive.hivescan.scanner",
|
||||
"mediahive.hivescan.indexer",
|
||||
"mediahive.hivescan.scanning",
|
||||
"mediahive.hivescan.images",
|
||||
"mediahive.hivescan.showreel",
|
||||
"mediahive.hivescan.tmdb_client",
|
||||
# async / ASGI internals
|
||||
"anyio",
|
||||
"anyio._backends._asyncio",
|
||||
"starlette.routing",
|
||||
# msgspec TOML write backend
|
||||
"tomli_w",
|
||||
],
|
||||
hiddenimports=_hiddenimports,
|
||||
hookspath=[],
|
||||
runtime_hooks=[],
|
||||
excludes=[],
|
||||
|
||||
Reference in New Issue
Block a user