109 lines
3.4 KiB
Python
109 lines
3.4 KiB
Python
import subprocess
|
|
import sys
|
|
from typing import Any
|
|
|
|
from app.library.log import get_logger
|
|
|
|
LOG = get_logger()
|
|
|
|
|
|
def patch_metadataparser() -> None:
|
|
"""
|
|
Patches yt_dlp MetadataParserPP action to handle subprocess pickling issues.
|
|
"""
|
|
try:
|
|
from yt_dlp.postprocessor.metadataparser import MetadataParserPP
|
|
from yt_dlp.utils import Namespace
|
|
except Exception as exc:
|
|
LOG.warning(
|
|
"Unable to import yt-dlp metadata parser for patching: %s",
|
|
exc,
|
|
extra={"patch": "metadata_parser", "exception_type": type(exc).__name__},
|
|
exc_info=True,
|
|
)
|
|
return
|
|
|
|
if getattr(MetadataParserPP.Actions, "_ytptube_patched", False):
|
|
return
|
|
|
|
class _ActionNS(Namespace):
|
|
_ACTIONS_STR: list[str] = []
|
|
|
|
@staticmethod
|
|
def _get_name(func) -> str | None:
|
|
if not callable(func):
|
|
return None
|
|
|
|
target = getattr(func, "__func__", func)
|
|
module_name = getattr(target, "__module__", None)
|
|
qual_name = getattr(target, "__qualname__", getattr(target, "__name__", None))
|
|
|
|
return f"{module_name}.{qual_name}" if module_name and qual_name else None
|
|
|
|
def __contains__(self, candidate: object) -> bool:
|
|
if candidate in self.__dict__.values():
|
|
return True
|
|
|
|
if func_name := _ActionNS._get_name(candidate):
|
|
if len(_ActionNS._ACTIONS_STR) < 1:
|
|
_ActionNS._ACTIONS_STR.extend(
|
|
[value for value in (_ActionNS._get_name(value) for value in self.__dict__.values()) if value]
|
|
)
|
|
|
|
return func_name in _ActionNS._ACTIONS_STR
|
|
|
|
return False
|
|
|
|
actions_dict: dict[str, Any] = dict(MetadataParserPP.Actions.items_)
|
|
MetadataParserPP.Actions = _ActionNS(**actions_dict)
|
|
MetadataParserPP.Actions._ytptube_patched = True
|
|
LOG.debug("MetadataParserPP action namespace patch applied successfully.")
|
|
|
|
|
|
def patch_windows_popen_wait() -> None:
|
|
if sys.platform != "win32":
|
|
return
|
|
|
|
try:
|
|
from yt_dlp.utils import Popen
|
|
except Exception as exc:
|
|
LOG.warning(
|
|
"Unable to import yt-dlp Popen for patching: %s",
|
|
exc,
|
|
extra={"patch": "windows_popen_wait", "exception_type": type(exc).__name__},
|
|
exc_info=True,
|
|
)
|
|
return
|
|
|
|
if getattr(Popen, "_ytptube_wait_patched", False):
|
|
return
|
|
|
|
original_wait = Popen.wait
|
|
|
|
# Windows subprocess waits can swallow the synthetic interrupt we use to
|
|
# stop live downloads, especially while yt-dlp is blocked on ffmpeg.
|
|
def interruptible_wait(self, timeout=None):
|
|
if timeout is not None:
|
|
return original_wait(self, timeout=timeout)
|
|
|
|
while True:
|
|
try:
|
|
return original_wait(self, timeout=0.1)
|
|
except subprocess.TimeoutExpired:
|
|
continue
|
|
|
|
Popen.wait = interruptible_wait
|
|
Popen._ytptube_wait_patched = True
|
|
LOG.debug("yt_dlp Popen.wait Windows patch applied successfully.")
|
|
|
|
|
|
def apply_ytdlp_patches() -> None:
|
|
try:
|
|
patch_metadataparser()
|
|
except Exception as exc:
|
|
LOG.debug("Metadata parser patch failed to apply: %s", exc)
|
|
|
|
try:
|
|
patch_windows_popen_wait()
|
|
except Exception as exc:
|
|
LOG.debug("Windows Popen wait patch failed to apply: %s", exc)
|