rssVideos: Work correctly with merged files
This commit is contained in:
parent
9684586eec
commit
daff602a31
|
@ -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:
|
||||||
|
|
Loading…
Reference in a new issue