mirror of https://github.com/devine-dl/devine.git
parent
226b609ff5
commit
5a12cb33e2
|
@ -43,6 +43,7 @@ from devine.core.console import console
|
|||
from devine.core.constants import DOWNLOAD_LICENCE_ONLY, AnyTrack, context_settings
|
||||
from devine.core.credential import Credential
|
||||
from devine.core.drm import DRM_T, Widevine
|
||||
from devine.core.events import events
|
||||
from devine.core.proxies import Basic, Hola, NordVPN
|
||||
from devine.core.service import Service
|
||||
from devine.core.services import Services
|
||||
|
@ -324,6 +325,14 @@ class dl:
|
|||
with console.status(f"Delaying by {delay} seconds..."):
|
||||
time.sleep(delay)
|
||||
|
||||
with console.status("Subscribing to events...", spinner="dots"):
|
||||
events.reset()
|
||||
events.subscribe(events.Types.SEGMENT_DOWNLOADED, service.on_segment_downloaded)
|
||||
events.subscribe(events.Types.TRACK_DOWNLOADED, service.on_track_downloaded)
|
||||
events.subscribe(events.Types.TRACK_DECRYPTED, service.on_track_decrypted)
|
||||
events.subscribe(events.Types.TRACK_REPACKED, service.on_track_repacked)
|
||||
events.subscribe(events.Types.TRACK_MULTIPLEX, service.on_track_multiplex)
|
||||
|
||||
with console.status("Getting tracks...", spinner="dots"):
|
||||
title.tracks.add(service.get_tracks(title), warn_only=True)
|
||||
title.tracks.chapters = service.get_chapters(title)
|
||||
|
@ -339,8 +348,13 @@ class dl:
|
|||
non_sdh_sub = deepcopy(subtitle)
|
||||
non_sdh_sub.id += "_stripped"
|
||||
non_sdh_sub.sdh = False
|
||||
non_sdh_sub.OnMultiplex = lambda: non_sdh_sub.strip_hearing_impaired()
|
||||
title.tracks.add(non_sdh_sub)
|
||||
events.subscribe(
|
||||
events.Types.TRACK_MULTIPLEX,
|
||||
lambda track: (
|
||||
track.strip_hearing_impaired()
|
||||
) if track.id == non_sdh_sub.id else None
|
||||
)
|
||||
|
||||
with console.status("Sorting tracks by language and bitrate...", spinner="dots"):
|
||||
title.tracks.sort_videos(by_language=v_lang or lang)
|
||||
|
@ -636,8 +650,7 @@ class dl:
|
|||
if track.needs_repack:
|
||||
track.repackage()
|
||||
has_repacked = True
|
||||
if callable(track.OnRepacked):
|
||||
track.OnRepacked()
|
||||
events.emit(events.Types.TRACK_REPACKED, track=track)
|
||||
if has_repacked:
|
||||
# we don't want to fill up the log with "Repacked x track"
|
||||
self.log.info("Repacked one or more tracks with FFMPEG")
|
||||
|
|
|
@ -24,6 +24,7 @@ from requests import Session
|
|||
from devine.core.constants import DOWNLOAD_CANCELLED, DOWNLOAD_LICENCE_ONLY, AnyTrack
|
||||
from devine.core.downloaders import requests as requests_downloader
|
||||
from devine.core.drm import Widevine
|
||||
from devine.core.events import events
|
||||
from devine.core.tracks import Audio, Subtitle, Tracks, Video
|
||||
from devine.core.utilities import is_close_match, try_ensure_utf8
|
||||
from devine.core.utils.xml import load_xml
|
||||
|
@ -474,8 +475,8 @@ class DASH:
|
|||
max_workers=16
|
||||
):
|
||||
file_downloaded = status_update.get("file_downloaded")
|
||||
if file_downloaded and callable(track.OnSegmentDownloaded):
|
||||
track.OnSegmentDownloaded(file_downloaded)
|
||||
if file_downloaded:
|
||||
events.emit(events.Types.SEGMENT_DOWNLOADED, track=track, segment=file_downloaded)
|
||||
else:
|
||||
downloaded = status_update.get("downloaded")
|
||||
if downloaded and downloaded.endswith("/s"):
|
||||
|
@ -514,15 +515,18 @@ class DASH:
|
|||
progress(advance=1)
|
||||
|
||||
track.path = save_path
|
||||
if callable(track.OnDownloaded):
|
||||
track.OnDownloaded()
|
||||
events.emit(events.Types.TRACK_DOWNLOADED, track=track)
|
||||
|
||||
if drm:
|
||||
progress(downloaded="Decrypting", completed=0, total=100)
|
||||
drm.decrypt(save_path)
|
||||
track.drm = None
|
||||
if callable(track.OnDecrypted):
|
||||
track.OnDecrypted(drm)
|
||||
events.emit(
|
||||
events.Types.TRACK_DECRYPTED,
|
||||
track=track,
|
||||
drm=drm,
|
||||
segment=None
|
||||
)
|
||||
progress(downloaded="Decrypting", advance=100)
|
||||
|
||||
save_dir.rmdir()
|
||||
|
|
|
@ -22,6 +22,7 @@ from requests import Session
|
|||
from devine.core.constants import DOWNLOAD_CANCELLED, DOWNLOAD_LICENCE_ONLY, AnyTrack
|
||||
from devine.core.downloaders import requests as requests_downloader
|
||||
from devine.core.drm import DRM_T, ClearKey, Widevine
|
||||
from devine.core.events import events
|
||||
from devine.core.tracks import Audio, Subtitle, Tracks, Video
|
||||
from devine.core.utilities import get_binary_path, get_extension, is_close_match, try_ensure_utf8
|
||||
|
||||
|
@ -282,8 +283,8 @@ class HLS:
|
|||
max_workers=16
|
||||
):
|
||||
file_downloaded = status_update.get("file_downloaded")
|
||||
if file_downloaded and callable(track.OnSegmentDownloaded):
|
||||
track.OnSegmentDownloaded(file_downloaded)
|
||||
if file_downloaded:
|
||||
events.emit(events.Types.SEGMENT_DOWNLOADED, track=track, segment=file_downloaded)
|
||||
else:
|
||||
downloaded = status_update.get("downloaded")
|
||||
if downloaded and downloaded.endswith("/s"):
|
||||
|
@ -381,8 +382,12 @@ class HLS:
|
|||
drm.decrypt(merged_path)
|
||||
merged_path.rename(decrypted_path)
|
||||
|
||||
if callable(track.OnDecrypted):
|
||||
track.OnDecrypted(drm, decrypted_path)
|
||||
events.emit(
|
||||
events.Types.TRACK_DECRYPTED,
|
||||
track=track,
|
||||
drm=drm,
|
||||
segment=decrypted_path
|
||||
)
|
||||
|
||||
return decrypted_path
|
||||
|
||||
|
@ -537,8 +542,7 @@ class HLS:
|
|||
progress(downloaded="Downloaded")
|
||||
|
||||
track.path = save_path
|
||||
if callable(track.OnDownloaded):
|
||||
track.OnDownloaded()
|
||||
events.emit(events.Types.TRACK_DOWNLOADED, track=track)
|
||||
|
||||
@staticmethod
|
||||
def merge_segments(segments: list[Path], save_path: Path) -> int:
|
||||
|
|
|
@ -3,10 +3,12 @@ import logging
|
|||
from abc import ABCMeta, abstractmethod
|
||||
from collections.abc import Generator
|
||||
from http.cookiejar import CookieJar
|
||||
from pathlib import Path
|
||||
from typing import Optional, Union
|
||||
from urllib.parse import urlparse
|
||||
|
||||
import click
|
||||
import m3u8
|
||||
import requests
|
||||
from requests.adapters import HTTPAdapter, Retry
|
||||
from rich.padding import Padding
|
||||
|
@ -17,6 +19,7 @@ from devine.core.config import config
|
|||
from devine.core.console import console
|
||||
from devine.core.constants import AnyTrack
|
||||
from devine.core.credential import Credential
|
||||
from devine.core.drm import DRM_T
|
||||
from devine.core.search_result import SearchResult
|
||||
from devine.core.titles import Title_T, Titles_T
|
||||
from devine.core.tracks import Chapters, Tracks
|
||||
|
@ -235,5 +238,53 @@ class Service(metaclass=ABCMeta):
|
|||
option `chapter_fallback_name`. For example, `"Chapter {i:02}"` for "Chapter 01".
|
||||
"""
|
||||
|
||||
# Optional Event methods
|
||||
|
||||
def on_segment_downloaded(self, track: AnyTrack, segment: Path) -> None:
|
||||
"""
|
||||
Called when one of a Track's Segments has finished downloading.
|
||||
|
||||
Parameters:
|
||||
track: The Track object that had a Segment downloaded.
|
||||
segment: The Path to the Segment that was downloaded.
|
||||
"""
|
||||
|
||||
def on_track_downloaded(self, track: AnyTrack) -> None:
|
||||
"""
|
||||
Called when a Track has finished downloading.
|
||||
|
||||
Parameters:
|
||||
track: The Track object that was downloaded.
|
||||
"""
|
||||
|
||||
def on_track_decrypted(self, track: AnyTrack, drm: DRM_T, segment: Optional[m3u8.Segment] = None) -> None:
|
||||
"""
|
||||
Called when a Track has finished decrypting.
|
||||
|
||||
Parameters:
|
||||
track: The Track object that was decrypted.
|
||||
drm: The DRM object it decrypted with.
|
||||
segment: The HLS segment information that was decrypted.
|
||||
"""
|
||||
|
||||
def on_track_repacked(self, track: AnyTrack) -> None:
|
||||
"""
|
||||
Called when a Track has finished repacking.
|
||||
|
||||
Parameters:
|
||||
track: The Track object that was repacked.
|
||||
"""
|
||||
|
||||
def on_track_multiplex(self, track: AnyTrack) -> None:
|
||||
"""
|
||||
Called when a Track is about to be Multiplexed into a Container.
|
||||
|
||||
Note: Right now only MKV containers are multiplexed but in the future
|
||||
this may also be called when multiplexing to other containers like
|
||||
MP4 via ffmpeg/mp4box.
|
||||
|
||||
Parameters:
|
||||
track: The Track object that was repacked.
|
||||
"""
|
||||
|
||||
__all__ = ("Service",)
|
||||
|
|
|
@ -12,7 +12,6 @@ from typing import Any, Callable, Iterable, Optional, Union
|
|||
from uuid import UUID
|
||||
from zlib import crc32
|
||||
|
||||
import m3u8
|
||||
from langcodes import Language
|
||||
from requests import Session
|
||||
|
||||
|
@ -20,6 +19,7 @@ from devine.core.config import config
|
|||
from devine.core.constants import DOWNLOAD_CANCELLED, DOWNLOAD_LICENCE_ONLY
|
||||
from devine.core.downloaders import aria2c, curl_impersonate, requests
|
||||
from devine.core.drm import DRM_T, Widevine
|
||||
from devine.core.events import events
|
||||
from devine.core.utilities import get_binary_path, get_boxes, try_ensure_utf8
|
||||
from devine.core.utils.subprocess import ffprobe
|
||||
|
||||
|
@ -122,17 +122,6 @@ class Track:
|
|||
# TODO: Currently using OnFoo event naming, change to just segment_filter
|
||||
self.OnSegmentFilter: Optional[Callable] = None
|
||||
|
||||
# Called after one of the Track's segments have downloaded
|
||||
self.OnSegmentDownloaded: Optional[Callable[[Path], None]] = None
|
||||
# Called after the Track has downloaded
|
||||
self.OnDownloaded: Optional[Callable] = None
|
||||
# Called after the Track or one of its segments have been decrypted
|
||||
self.OnDecrypted: Optional[Callable[[DRM_T, Optional[m3u8.Segment]], None]] = None
|
||||
# Called after the Track has been repackaged
|
||||
self.OnRepacked: Optional[Callable] = None
|
||||
# Called before the Track is multiplexed
|
||||
self.OnMultiplex: Optional[Callable] = None
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return "{name}({items})".format(
|
||||
name=self.__class__.__name__,
|
||||
|
@ -257,15 +246,18 @@ class Track:
|
|||
save_path.with_suffix(f"{save_path.suffix}.aria2__temp").unlink(missing_ok=True)
|
||||
|
||||
self.path = save_path
|
||||
if callable(self.OnDownloaded):
|
||||
self.OnDownloaded()
|
||||
events.emit(events.Types.TRACK_DOWNLOADED, track=self)
|
||||
|
||||
if drm:
|
||||
progress(downloaded="Decrypting", completed=0, total=100)
|
||||
drm.decrypt(save_path)
|
||||
self.drm = None
|
||||
if callable(self.OnDecrypted):
|
||||
self.OnDecrypted(drm)
|
||||
events.emit(
|
||||
events.Types.TRACK_DECRYPTED,
|
||||
track=self,
|
||||
drm=drm,
|
||||
segment=None
|
||||
)
|
||||
progress(downloaded="Decrypted", completed=100)
|
||||
|
||||
if track_type == "Subtitle" and self.codec.name not in ("fVTT", "fTTML"):
|
||||
|
@ -299,8 +291,7 @@ class Track:
|
|||
if self.path.stat().st_size <= 3: # Empty UTF-8 BOM == 3 bytes
|
||||
raise IOError("Download failed, the downloaded file is empty.")
|
||||
|
||||
if callable(self.OnDownloaded):
|
||||
self.OnDownloaded(self)
|
||||
events.emit(events.Types.TRACK_DOWNLOADED, track=self)
|
||||
|
||||
def delete(self) -> None:
|
||||
if self.path:
|
||||
|
|
|
@ -14,6 +14,7 @@ from rich.tree import Tree
|
|||
from devine.core.config import config
|
||||
from devine.core.console import console
|
||||
from devine.core.constants import LANGUAGE_MAX_DISTANCE, AnyTrack, TrackT
|
||||
from devine.core.events import events
|
||||
from devine.core.tracks.attachment import Attachment
|
||||
from devine.core.tracks.audio import Audio
|
||||
from devine.core.tracks.chapters import Chapter, Chapters
|
||||
|
@ -337,8 +338,7 @@ class Tracks:
|
|||
for i, vt in enumerate(self.videos):
|
||||
if not vt.path or not vt.path.exists():
|
||||
raise ValueError("Video Track must be downloaded before muxing...")
|
||||
if callable(vt.OnMultiplex):
|
||||
vt.OnMultiplex()
|
||||
events.emit(events.Types.TRACK_MULTIPLEX, track=vt)
|
||||
cl.extend([
|
||||
"--language", f"0:{vt.language}",
|
||||
"--default-track", f"0:{i == 0}",
|
||||
|
@ -350,8 +350,7 @@ class Tracks:
|
|||
for i, at in enumerate(self.audio):
|
||||
if not at.path or not at.path.exists():
|
||||
raise ValueError("Audio Track must be downloaded before muxing...")
|
||||
if callable(at.OnMultiplex):
|
||||
at.OnMultiplex()
|
||||
events.emit(events.Types.TRACK_MULTIPLEX, track=at)
|
||||
cl.extend([
|
||||
"--track-name", f"0:{at.get_track_name() or ''}",
|
||||
"--language", f"0:{at.language}",
|
||||
|
@ -365,8 +364,7 @@ class Tracks:
|
|||
for st in self.subtitles:
|
||||
if not st.path or not st.path.exists():
|
||||
raise ValueError("Text Track must be downloaded before muxing...")
|
||||
if callable(st.OnMultiplex):
|
||||
st.OnMultiplex()
|
||||
events.emit(events.Types.TRACK_MULTIPLEX, track=st)
|
||||
default = bool(self.audio and is_close_match(st.language, [self.audio[0].language]) and st.forced)
|
||||
cl.extend([
|
||||
"--track-name", f"0:{st.get_track_name() or ''}",
|
||||
|
|
Loading…
Reference in New Issue