rssVideos: --total-duration

Controleld binging
This commit is contained in:
Geoffrey Frogeye 2021-12-19 23:13:41 +01:00
parent 00a9da6afc
commit 5b7926df8f
Signed by: geoffrey
GPG key ID: C72403E7F82E6AD8

View file

@ -62,6 +62,45 @@ class SaveInfoPP(yt_dlp.postprocessor.common.PostProcessor):
self.rvelement.ytdl_infos = info self.rvelement.ytdl_infos = info
return [], info return [], info
def parse_duration(string: str) -> int:
DURATION_MULTIPLIERS = {"s": 1, "m": 60, "h": 3600, "": 1}
mult_index = string[-1].lower()
if mult_index.isdigit():
mult_index = ""
else:
string = string[:-1]
try:
multiplier = DURATION_MULTIPLIERS[mult_index]
except IndexError:
raise ValueError(f"Unknown duration multiplier: {mult_index}")
return int(string) * multiplier
def compare_duration(compstr: str) -> typing.Callable[[int], bool]:
DURATION_COMPARATORS = {
"<": int.__lt__,
"-": int.__lt__,
">": int.__gt__,
"+": int.__gt__,
"=": int.__eq__,
"": int.__le__,
}
comp_index = compstr[0]
if comp_index.isdigit():
comp_index = ""
else:
compstr = compstr[1:]
try:
comparator = DURATION_COMPARATORS[comp_index]
except IndexError:
raise ValueError(f"Unknown duration comparator: {comp_index}")
duration = parse_duration(compstr)
return lambda d: comparator(d, duration)
def format_duration(duration: int) -> str: def format_duration(duration: int) -> str:
return time.strftime("%H:%M:%S", time.gmtime(duration)) return time.strftime("%H:%M:%S", time.gmtime(duration))
@ -152,6 +191,7 @@ class RVElement:
@functools.cached_property @functools.cached_property
def ytdl_infos(self) -> typing.Optional[dict]: def ytdl_infos(self) -> typing.Optional[dict]:
# TODO Sanitize according to documentation
log.info(f"Researching: {self}") log.info(f"Researching: {self}")
try: try:
infos = self.parent.ytdl_dry.extract_info(self.link, download=False) infos = self.parent.ytdl_dry.extract_info(self.link, download=False)
@ -215,17 +255,6 @@ class RVElement:
return return
self.download() self.download()
MATCHES_DURATION_MULTIPLIERS = {"s": 1, "m": 60, "h": 3600, None: 1}
MATCHES_DURATION_COMPARATORS = {
"<": int.__lt__,
"-": int.__lt__,
">": int.__gt__,
"+": int.__gt__,
"=": int.__eq__,
None: int.__le__,
}
def matches_filter(self, args: configargparse.Namespace) -> bool: def matches_filter(self, args: configargparse.Namespace) -> bool:
# Inexpensive filters # Inexpensive filters
if args.seen != "any" and (args.seen == "seen") != self.watched: if args.seen != "any" and (args.seen == "seen") != self.watched:
@ -250,35 +279,11 @@ class RVElement:
if not self.is_video: if not self.is_video:
log.debug(f"Not a video: {self}") log.debug(f"Not a video: {self}")
return False return False
if args.duration: if args.duration and not compare_duration(args.duration)(self.duration):
dur = args.duration log.debug(
f"Duration {self.duration} not matching {args.duration}: {self}"
mult_index = dur[-1].lower() )
if mult_index.isdigit(): return False
mult_index = None
else:
dur = dur[:-1]
try:
multiplier = self.MATCHES_DURATION_MULTIPLIERS[mult_index]
except IndexError:
raise ValueError(f"Unknown duration multiplier: {mult_index}")
comp_index = dur[0]
if comp_index.isdigit():
comp_index = None
else:
dur = dur[1:]
try:
comparator = self.MATCHES_DURATION_COMPARATORS[comp_index]
except IndexError:
raise ValueError(f"Unknown duration comparator: {comp_index}")
duration = int(dur)
if not comparator(self.duration, duration * multiplier):
log.debug(
f"Duration {self.duration} not matching {args.duration}: {self}"
)
return False
return True return True
@ -439,11 +444,26 @@ class RVDatabase:
elements = sorted( elements = sorted(
elements, key=lambda el: el.duration if el.is_video else 0 elements, key=lambda el: el.duration if el.is_video else 0
) )
elif args.order == "short": elif args.order == "long":
elements = sorted( elements = sorted(
elements, key=lambda el: el.duration if el.is_video else 0, reverse=True elements, key=lambda el: el.duration if el.is_video else 0, reverse=True
) )
# Post sorting filtering
if args.total_duration:
rem = parse_duration(args.total_duration)
old_els = list(elements)
elements = list()
while rem > 0:
for el in old_els:
if el.duration < rem:
elements.append(el)
rem -= el.duration
old_els.remove(el)
break
else:
break
return elements return elements
@ -522,6 +542,10 @@ def get_args() -> configargparse.Namespace:
default="unseen", default="unseen",
help="Only include seen/unseen/any videos", help="Only include seen/unseen/any videos",
) )
parser.add(
"--total-duration",
help="Use videos that fit under the total given",
)
# TODO Envrionment variables # TODO Envrionment variables
parser.add( parser.add(
"--max-duration", "--max-duration",
@ -558,7 +582,6 @@ def get_args() -> configargparse.Namespace:
"clean", "clean",
"seen", "seen",
"unseen", "unseen",
"duration",
), ),
default="download", default="download",
) )
@ -606,8 +629,7 @@ def main() -> None:
database.clean() database.clean()
else: else:
database.attempt_clean() database.attempt_clean()
if args.action == "duration": duration = 0
duration = 0
for element in database.filter(args): for element in database.filter(args):
if args.action == "download": if args.action == "download":
element.preload() element.preload()
@ -625,12 +647,10 @@ def main() -> None:
if element.watched: if element.watched:
log.info(f"Maked as unseen: {element}") log.info(f"Maked as unseen: {element}")
element.watched = False element.watched = False
elif args.action == "duration":
duration += element.duration
else: else:
raise NotImplementedError(f"Unimplemented action: {args.action}") raise NotImplementedError(f"Unimplemented action: {args.action}")
if args.action == "duration": duration += element.duration if element.is_video else 0
print(format_duration(duration)) log.info(f"Total duration: {format_duration(duration)}")
database.attempt_clean() database.attempt_clean()
database.save() database.save()