rssVideos: Work correctly with merged files

This commit is contained in:
Geoffrey Frogeye 2021-12-19 15:10:16 +01:00
parent 9684586eec
commit daff602a31
Signed by: geoffrey
GPG key ID: C72403E7F82E6AD8

View file

@ -26,12 +26,13 @@ from xml.dom import minidom
import coloredlogs import coloredlogs
import configargparse import configargparse
import yt_dlp as youtube_dl import yt_dlp
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
# TODO Lockfile, or a way to parallel watch and download # TODO Lockfile, or a way to parallel watch and download
def configure_logging(args: configargparse.Namespace) -> None: def configure_logging(args: configargparse.Namespace) -> None:
# Configure logging # Configure logging
if args.verbosity: if args.verbosity:
@ -44,7 +45,25 @@ def configure_logging(args: configargparse.Namespace) -> None:
logger=log, logger=log,
) )
def format_duration(duration: int) -> int:
class SaveInfoPP(yt_dlp.postprocessor.common.PostProcessor):
"""
yt_dlp.process_ie_result() doesn't return a completely updated info dict,
notably the extension is still the one before it realizes the files cannot
be merged. So we use this PostProcessor to catch the info dict in its final
form and save it.
"""
def __init__(self, rvelement: "RVElement") -> None:
self.rvelement = rvelement
super().__init__()
def run(self, info: dict) -> tuple[list, dict]:
self.rvelement.ytdl_infos = info
return [], info
def format_duration(duration: int) -> str:
return time.strftime("%H:%M:%S", time.gmtime(duration)) return time.strftime("%H:%M:%S", time.gmtime(duration))
@ -108,8 +127,8 @@ class RVElement:
if not self.parent.args.research and cache.is_researched: if not self.parent.args.research and cache.is_researched:
self.__dict__["ytdl_infos"] = cache.__dict__["ytdl_infos"] self.__dict__["ytdl_infos"] = cache.__dict__["ytdl_infos"]
log.debug(f"From cache: {self}") log.debug(f"From cache: {self}")
if cache.was_downloaded: # if cache.was_downloaded:
self.was_downloaded = True # self.was_downloaded = True
if cache.watched: if cache.watched:
self.watched = True self.watched = True
@ -135,10 +154,10 @@ class RVElement:
def ytdl_infos(self) -> typing.Optional[dict]: def ytdl_infos(self) -> typing.Optional[dict]:
log.info(f"Researching: {self}") log.info(f"Researching: {self}")
try: try:
infos = self.parent.ytdl_dry.extract_info(self.link) infos = self.parent.ytdl_dry.extract_info(self.link, download=False)
except KeyboardInterrupt as e: except KeyboardInterrupt as e:
raise e raise e
except youtube_dl.utils.DownloadError as e: except yt_dlp.utils.DownloadError as e:
# TODO Still raise in case of temporary network issue # TODO Still raise in case of temporary network issue
log.warning(e) log.warning(e)
infos = None infos = None
@ -147,7 +166,7 @@ class RVElement:
if ( if (
infos infos
and "thumbnails" in infos and "thumbnails" in infos
and isinstance(infos["thumbnails"], youtube_dl.utils.LazyList) and isinstance(infos["thumbnails"], yt_dlp.utils.LazyList)
): ):
infos["thumbnails"] = infos["thumbnails"].exhaust() infos["thumbnails"] = infos["thumbnails"].exhaust()
# Save database once it's been computed # Save database once it's been computed
@ -169,7 +188,6 @@ class RVElement:
@property @property
def filepath(self) -> str: def filepath(self) -> str:
assert self.is_video assert self.is_video
# TODO This doesn't change the extension to mkv when the formats are incomaptible
return self.parent.ytdl_dry.prepare_filename(self.ytdl_infos) return self.parent.ytdl_dry.prepare_filename(self.ytdl_infos)
@property @property
@ -181,7 +199,9 @@ class RVElement:
assert self.is_video assert self.is_video
log.info(f"Downloading: {self}") log.info(f"Downloading: {self}")
if not self.parent.args.dryrun: if not self.parent.args.dryrun:
self.parent.ytdl.process_ie_result(self.ytdl_infos, True, {}) with yt_dlp.YoutubeDL(self.parent.ytdl_opts) as ydl:
ydl.add_post_processor(SaveInfoPP(self))
ydl.process_ie_result(self.ytdl_infos, download=True)
self.was_downloaded = True self.was_downloaded = True
self.parent.save() self.parent.save()
@ -220,7 +240,9 @@ class RVElement:
if args.link and not re.search(args.link, self.link): if args.link and not re.search(args.link, self.link):
log.debug(f"Link not matching {args.link}: {self}") log.debug(f"Link not matching {args.link}: {self}")
return False return False
if args.creator and (not self.creator or not re.search(args.creator, self.creator)): if args.creator and (
not self.creator or not re.search(args.creator, self.creator)
):
log.debug(f"Creator not matching {args.creator}: {self}") log.debug(f"Creator not matching {args.creator}: {self}")
return False return False
@ -253,7 +275,9 @@ class RVElement:
duration = int(dur) duration = int(dur)
if not comparator(self.duration, duration * multiplier): if not comparator(self.duration, duration * multiplier):
log.debug(f"Duration {self.duration} not matching {args.duration}: {self}") log.debug(
f"Duration {self.duration} not matching {args.duration}: {self}"
)
return False return False
return True return True
@ -382,16 +406,12 @@ class RVDatabase:
@property @property
def ytdl_dry_opts(self) -> dict: def ytdl_dry_opts(self) -> dict:
opts = self.ytdl_opts.copy() opts = self.ytdl_opts.copy()
opts.update({"simulate": True, "quiet": True}) opts.update({"quiet": True})
return opts return opts
@property @property
def ytdl(self) -> youtube_dl.YoutubeDL: def ytdl_dry(self) -> yt_dlp.YoutubeDL:
return youtube_dl.YoutubeDL(self.ytdl_opts) return yt_dlp.YoutubeDL(self.ytdl_dry_opts)
@property
def ytdl_dry(self) -> youtube_dl.YoutubeDL:
return youtube_dl.YoutubeDL(self.ytdl_dry_opts)
def filter(self, args: configargparse.Namespace) -> typing.Iterable[RVElement]: def filter(self, args: configargparse.Namespace) -> typing.Iterable[RVElement]:
elements: typing.Iterable[RVElement] elements: typing.Iterable[RVElement]
@ -401,7 +421,7 @@ class RVDatabase:
elif args.order == "title": elif args.order == "title":
elements = sorted(self.elements, key=lambda el: el.title) elements = sorted(self.elements, key=lambda el: el.title)
elif args.order == "creator": elif args.order == "creator":
elements = sorted(self.elements, key=lambda el: el.creator or '') elements = sorted(self.elements, key=lambda el: el.creator or "")
elif args.order == "link": elif args.order == "link":
elements = sorted(self.elements, key=lambda el: el.link) elements = sorted(self.elements, key=lambda el: el.link)
elif args.order == "random": elif args.order == "random":
@ -416,9 +436,13 @@ class RVDatabase:
# Expensive sort # Expensive sort
if args.order == "short": if args.order == "short":
elements = sorted(elements, key=lambda el: el.duration if el.is_video else 0) elements = sorted(
elements, key=lambda el: el.duration if el.is_video else 0
)
elif args.order == "short": elif args.order == "short":
elements = sorted(elements, key=lambda el: el.duration if el.is_video else 0, reverse=True) elements = sorted(
elements, key=lambda el: el.duration if el.is_video else 0, reverse=True
)
return elements return elements
@ -486,7 +510,12 @@ def get_args() -> configargparse.Namespace:
parser.add("--title", help="Regex to filter by title") parser.add("--title", help="Regex to filter by title")
parser.add("--link", help="Regex to filter by link") parser.add("--link", help="Regex to filter by link")
parser.add("--duration", help="Comparative to filter by duration") parser.add("--duration", help="Comparative to filter by duration")
parser.add("--seen", choices=("seen","unseen","any"), default="unseen", help="Only include seen/unseen/any videos") parser.add(
"--seen",
choices=("seen", "unseen", "any"),
default="unseen",
help="Only include seen/unseen/any videos",
)
# TODO Envrionment variables # TODO Envrionment variables
parser.add( parser.add(
"--max-duration", "--max-duration",
@ -515,7 +544,16 @@ def get_args() -> configargparse.Namespace:
parser.add( parser.add(
"action", "action",
nargs="?", nargs="?",
choices=("download", "list", "watch", "binge", "clean", "seen", "unseen", "duration"), choices=(
"download",
"list",
"watch",
"binge",
"clean",
"seen",
"unseen",
"duration",
),
default="download", default="download",
) )
@ -566,9 +604,13 @@ def main() -> None:
if args.action == "watch": if args.action == "watch":
break break
elif args.action == "seen": elif args.action == "seen":
element.watched = True if not element.watched:
log.info(f"Maked as seen: {element}")
element.watched = True
elif args.action == "unseen": elif args.action == "unseen":
element.watched = False if element.watched:
log.info(f"Maked as unseen: {element}")
element.watched = False
elif args.action == "duration": elif args.action == "duration":
duration += element.duration duration += element.duration
else: else: