jjlj
This commit is contained in:
@@ -6,13 +6,15 @@ import shutil as _shutil
|
||||
import subprocess as _subprocess
|
||||
import json
|
||||
import sys
|
||||
import platform
|
||||
|
||||
from helper.logger import log, debug
|
||||
import uuid as _uuid
|
||||
import time as _time
|
||||
|
||||
from downlow_helpers.progress import print_progress, print_final_progress, format_size
|
||||
from downlow_helpers.http_client import HTTPClient
|
||||
from helper.progress import print_progress, print_final_progress
|
||||
from helper.http_client import HTTPClient
|
||||
from helper.mpv_ipc import get_ipc_pipe_path, send_to_mpv
|
||||
import fnmatch as _fnmatch
|
||||
|
||||
from . import register
|
||||
@@ -21,7 +23,7 @@ import pipeline as ctx
|
||||
from helper import hydrus as hydrus_wrapper
|
||||
from ._shared import Cmdlet, CmdletArg, normalize_hash, looks_like_hash, create_pipe_object_result
|
||||
from config import resolve_output_dir, get_hydrus_url, get_hydrus_access_key
|
||||
from downlow_helpers.alldebrid import AllDebridClient
|
||||
from helper.alldebrid import AllDebridClient
|
||||
|
||||
|
||||
|
||||
@@ -248,158 +250,63 @@ def _is_playable_in_mpv(file_path_or_ext: str, mime_type: Optional[str] = None)
|
||||
return False
|
||||
|
||||
|
||||
def _get_fixed_ipc_pipe() -> str:
|
||||
"""Get the fixed IPC pipe name for persistent MPV connection.
|
||||
|
||||
Uses a fixed name 'mpv-medeia-macina' so all playback sessions
|
||||
connect to the same MPV window/process instead of creating new instances.
|
||||
"""
|
||||
import platform
|
||||
if platform.system() == 'Windows':
|
||||
return "\\\\.\\pipe\\mpv-medeia-macina"
|
||||
else:
|
||||
return "/tmp/mpv-medeia-macina.sock"
|
||||
|
||||
|
||||
def _send_to_mpv_pipe(file_url: str, ipc_pipe: str, title: str, headers: Optional[Dict[str, str]] = None) -> bool:
|
||||
"""Send loadfile command to existing MPV via IPC pipe.
|
||||
|
||||
Returns True if successfully sent to existing MPV, False if pipe unavailable.
|
||||
"""
|
||||
import json
|
||||
import socket
|
||||
import platform
|
||||
|
||||
try:
|
||||
# Prepare commands
|
||||
# Use set_property for headers as loadfile options can be unreliable via IPC
|
||||
header_str = ""
|
||||
if headers:
|
||||
header_str = ",".join([f"{k}: {v}" for k, v in headers.items()])
|
||||
|
||||
# Command 1: Set headers (or clear them)
|
||||
cmd_headers = {
|
||||
"command": ["set_property", "http-header-fields", header_str],
|
||||
"request_id": 0
|
||||
}
|
||||
|
||||
# Command 2: Load file using memory:// M3U to preserve title
|
||||
# Sanitize title to avoid breaking M3U format
|
||||
safe_title = title.replace("\n", " ").replace("\r", "")
|
||||
m3u_content = f"#EXTM3U\n#EXTINF:-1,{safe_title}\n{file_url}\n"
|
||||
|
||||
cmd_load = {
|
||||
"command": ["loadfile", f"memory://{m3u_content}", "append-play"],
|
||||
"request_id": 1
|
||||
}
|
||||
|
||||
if platform.system() == 'Windows':
|
||||
# Windows named pipes require special handling
|
||||
try:
|
||||
# Open in r+b to read response
|
||||
with open(ipc_pipe, 'r+b', buffering=0) as pipe:
|
||||
# Send headers
|
||||
pipe.write((json.dumps(cmd_headers) + "\n").encode('utf-8'))
|
||||
pipe.flush()
|
||||
pipe.readline() # Consume response for headers
|
||||
|
||||
# Send loadfile
|
||||
pipe.write((json.dumps(cmd_load) + "\n").encode('utf-8'))
|
||||
pipe.flush()
|
||||
|
||||
# Read response
|
||||
response_line = pipe.readline()
|
||||
if response_line:
|
||||
resp = json.loads(response_line.decode('utf-8'))
|
||||
if resp.get('error') != 'success':
|
||||
debug(f"[get-file] MPV error: {resp.get('error')}", file=sys.stderr)
|
||||
return False
|
||||
|
||||
debug(f"[get-file] Sent to existing MPV: {title}", file=sys.stderr)
|
||||
return True
|
||||
except (OSError, IOError):
|
||||
# Pipe not available
|
||||
return False
|
||||
else:
|
||||
# Unix socket for Linux/macOS
|
||||
if not hasattr(socket, 'AF_UNIX'):
|
||||
return False
|
||||
|
||||
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
||||
sock.connect(ipc_pipe)
|
||||
|
||||
# Send headers
|
||||
sock.sendall((json.dumps(cmd_headers) + "\n").encode('utf-8'))
|
||||
sock.recv(4096) # Consume response
|
||||
|
||||
# Send loadfile
|
||||
sock.sendall((json.dumps(cmd_load) + "\n").encode('utf-8'))
|
||||
|
||||
# Read response
|
||||
try:
|
||||
response_data = sock.recv(4096)
|
||||
if response_data:
|
||||
resp = json.loads(response_data.decode('utf-8'))
|
||||
if resp.get('error') != 'success':
|
||||
debug(f"[get-file] MPV error: {resp.get('error')}", file=sys.stderr)
|
||||
sock.close()
|
||||
return False
|
||||
except:
|
||||
pass
|
||||
sock.close()
|
||||
|
||||
debug(f"[get-file] Sent to existing MPV: {title}", file=sys.stderr)
|
||||
return True
|
||||
except (OSError, socket.error, ConnectionRefusedError):
|
||||
# Pipe doesn't exist or MPV not listening - will need to start new instance
|
||||
return False
|
||||
except Exception as e:
|
||||
debug(f"[get-file] IPC error: {e}", file=sys.stderr)
|
||||
return False
|
||||
|
||||
|
||||
def _play_in_mpv(file_url: str, file_title: str, is_stream: bool = False, headers: Optional[Dict[str, str]] = None) -> bool:
|
||||
"""Play file in MPV using IPC pipe, creating new instance if needed.
|
||||
"""Play file in MPV using centralized IPC pipe, creating new instance if needed.
|
||||
|
||||
Returns True on success, False on error.
|
||||
"""
|
||||
ipc_pipe = _get_fixed_ipc_pipe()
|
||||
import json
|
||||
import socket
|
||||
import platform
|
||||
|
||||
try:
|
||||
# First try to send to existing MPV instance
|
||||
if _send_to_mpv_pipe(file_url, ipc_pipe, file_title, headers):
|
||||
if send_to_mpv(file_url, file_title, headers):
|
||||
debug(f"Added to MPV: {file_title}")
|
||||
return True
|
||||
|
||||
# No existing MPV or pipe unavailable - start new instance
|
||||
ipc_pipe = get_ipc_pipe_path()
|
||||
debug(f"[get-file] Starting new MPV instance (pipe: {ipc_pipe})", file=sys.stderr)
|
||||
cmd = ['mpv', file_url, f'--input-ipc-server={ipc_pipe}']
|
||||
|
||||
# Set title for new instance
|
||||
cmd.append(f'--force-media-title={file_title}')
|
||||
# Build command - start MPV without a file initially, just with IPC server
|
||||
cmd = ['mpv', f'--input-ipc-server={ipc_pipe}']
|
||||
|
||||
if headers:
|
||||
# Format headers for command line
|
||||
# --http-header-fields="Header1: Val1,Header2: Val2"
|
||||
header_str = ",".join([f"{k}: {v}" for k, v in headers.items()])
|
||||
cmd.append(f'--http-header-fields={header_str}')
|
||||
|
||||
|
||||
# Add --idle flag so MPV stays running and waits for playlist commands
|
||||
cmd.append('--idle')
|
||||
|
||||
# Detach process to prevent freezing parent CLI
|
||||
kwargs = {}
|
||||
if platform.system() == 'Windows':
|
||||
# CREATE_NEW_CONSOLE might be better than CREATE_NO_WINDOW if MPV needs a window
|
||||
# But usually MPV creates its own window.
|
||||
# DETACHED_PROCESS (0x00000008) is also an option.
|
||||
kwargs['creationflags'] = 0x00000008 # DETACHED_PROCESS
|
||||
kwargs['creationflags'] = 0x00000008 # DETACHED_PROCESS
|
||||
|
||||
_subprocess.Popen(cmd, stdin=_subprocess.DEVNULL, stdout=_subprocess.DEVNULL, stderr=_subprocess.DEVNULL, **kwargs)
|
||||
|
||||
debug(f"{'Streaming' if is_stream else 'Playing'} in MPV: {file_title}")
|
||||
debug(f"[get-file] Started MPV with {file_title} (IPC: {ipc_pipe})", file=sys.stderr)
|
||||
return True
|
||||
debug(f"[get-file] Started MPV instance (IPC: {ipc_pipe})", file=sys.stderr)
|
||||
|
||||
# Give MPV time to start and open IPC pipe
|
||||
# Windows needs more time than Unix
|
||||
wait_time = 1.0 if platform.system() == 'Windows' else 0.5
|
||||
debug(f"[get-file] Waiting {wait_time}s for MPV to initialize IPC...", file=sys.stderr)
|
||||
_time.sleep(wait_time)
|
||||
|
||||
# Try up to 3 times to send the file via IPC
|
||||
for attempt in range(3):
|
||||
debug(f"[get-file] Sending file via IPC (attempt {attempt + 1}/3)", file=sys.stderr)
|
||||
if send_to_mpv(file_url, file_title, headers):
|
||||
debug(f"{'Streaming' if is_stream else 'Playing'} in MPV: {file_title}")
|
||||
debug(f"[get-file] Added to new MPV instance (IPC: {ipc_pipe})", file=sys.stderr)
|
||||
return True
|
||||
|
||||
if attempt < 2:
|
||||
# Wait before retrying
|
||||
_time.sleep(0.3)
|
||||
|
||||
# IPC send failed after all retries
|
||||
log("Error: Could not send file to MPV via IPC after startup", file=sys.stderr)
|
||||
return False
|
||||
|
||||
except FileNotFoundError:
|
||||
log("Error: MPV not found. Install mpv to play media files", file=sys.stderr)
|
||||
@@ -516,7 +423,7 @@ def _handle_hydrus_file(file_hash: Optional[str], file_title: str, config: Dict[
|
||||
|
||||
if force_browser:
|
||||
# User explicitly wants browser
|
||||
ipc_pipe = _get_fixed_ipc_pipe()
|
||||
ipc_pipe = get_ipc_pipe_path()
|
||||
result_dict = create_pipe_object_result(
|
||||
source='hydrus',
|
||||
identifier=file_hash,
|
||||
@@ -559,7 +466,7 @@ def _handle_hydrus_file(file_hash: Optional[str], file_title: str, config: Dict[
|
||||
return 0
|
||||
else:
|
||||
# Not media, open in browser
|
||||
ipc_pipe = _get_fixed_ipc_pipe()
|
||||
ipc_pipe = get_ipc_pipe_path()
|
||||
result_dict = create_pipe_object_result(
|
||||
source='hydrus',
|
||||
identifier=file_hash,
|
||||
@@ -1193,7 +1100,7 @@ def _run(result: Any, args: Sequence[str], config: Dict[str, Any]) -> int:
|
||||
|
||||
# Normal file export (happens regardless of -metadata flag)
|
||||
try:
|
||||
from downlow_helpers.hydrus import hydrus_export as _hydrus_export
|
||||
from helper.hydrus import hydrus_export as _hydrus_export
|
||||
except Exception:
|
||||
_hydrus_export = None # type: ignore
|
||||
if _hydrus_export is None:
|
||||
|
||||
Reference in New Issue
Block a user