From 613f52218b9bb78e57a30cbefbac7cb50e67e665 Mon Sep 17 00:00:00 2001 From: lifegpc Date: Mon, 16 Sep 2024 21:52:53 +0800 Subject: [PATCH] Support generate audio report by year --- jellyfinstats/__main__.py | 64 ++++++++++++++++++++++++++++++--------- jellyfinstats/utils.py | 15 +++++++-- 2 files changed, 62 insertions(+), 17 deletions(-) diff --git a/jellyfinstats/__main__.py b/jellyfinstats/__main__.py index 3d34f6c..d3f8783 100644 --- a/jellyfinstats/__main__.py +++ b/jellyfinstats/__main__.py @@ -9,6 +9,7 @@ from .audio import ( from .cache import IdRelativeCache from .config import Config from .db import PlaybackReportingDb, LibraryDb, JellyfinDb +from .utils import gen_year_range, parse_datetime p = ArgumentParser(prog="jellyfinstats") @@ -19,23 +20,58 @@ p.add_argument("--jellyfin-data-dir", help=_("The path to jellyfin data director p.add_argument("--output-dir", help=_("The directory for output files.")) p.add_argument("--ask-page-size", help=_("Specify maximum items to display in one page."), type=int) # noqa: E501 p.add_argument("--jellyfin-db", help=_("The path to jellyfin.db")) -p.add_argument("--fix", help=_("Fix incorrect play duration."), action='store_true', default=False) # noqa: E501 -arg = p.parse_intermixed_args() +ps = p.add_subparsers(dest='action', help=_('sub-command help'), required=False, metavar='action') # noqa: E501 +audio = ps.add_parser('audio', help=_('Generate audio report.')) +audio.add_argument("--fix", help=_("Fix incorrect play duration."), action='store_true', default=False) # noqa: E501 +audio.add_argument("-u", "--user", help=_("Generate report for specify users."), action='append', default=[]) # noqa: E501 +audio.add_argument("-i", "--user-id", help=_("Generate report for specify users."), action='append', default=[]) # noqa: E501 +audios = audio.add_subparsers(dest='type', help=_("Report type. Default: ") + "all", required=False, metavar='type') # noqa: E501 +audio_all = audios.add_parser('all', help=_("All time report")) +audio_year = audios.add_parser('year', help=_("Year report")) +audio_year.add_argument('year', action='extend', nargs='*', help=_("Generate year report for specify years."), default=[], type=int) # noqa: E501 +audio_year.add_argument('-s', '--start', help=_("The start year of range of years."), type=int) # noqa: E501 +audio_year.add_argument('-e', '--end', help=_("The end year of range of years."), type=int) # noqa: E501 +audio_year.add_argument('--utc', action='store_true', help=_("Use UTC time."), default=False) # noqa: E501 +arg = p.parse_args() +if arg.action == 'a': + arg.action = 'audio' cfg = Config(arg.config, arg) with PlaybackReportingDb(cfg.playback_reporting_db) as pdb: - if arg.fix: + if arg.action == 'audio' and arg.fix: fix_audio_report_library(pdb) with LibraryDb(cfg.library_db) as ldb: with IdRelativeCache(cfg.output_dir) as icache: with JellyfinDb(cfg.jellyfin_db) as jdb: - re = prepare_audio_map(pdb, ldb, icache, cfg) - users = pdb.get_users('Audio') - for u in users: - userid = u['UserId'] - user = jdb.get_user(userid) - username = user['Username'] if user else userid - output = join(cfg.output_dir, 'audio', username) - maxDate = u['MaxDate'] - minDate = u['MinDate'] - generate_audio_report( - pdb, re[0], re[1], re[2], output, userid) + if arg.action == 'audio': + if arg.type is None: + arg.type = 'all' + re = prepare_audio_map(pdb, ldb, icache, cfg) + users = pdb.get_users('Audio') + for u in users: + userid = u['UserId'] + user = jdb.get_user(userid) + username = user['Username'] if user else userid + if arg.user or arg.user_id: + if username not in arg.user and userid not in arg.user_id: # noqa: E501 + continue + output = join(cfg.output_dir, 'audio', username) + maxDate = u['MaxDate'] + minDate = u['MinDate'] + if arg.type == 'all': + generate_audio_report( + pdb, re[0], re[1], re[2], output, userid) + elif arg.type == 'year': + minTime = parse_datetime(minDate) + minYear = minTime.year + maxTime = parse_datetime(maxDate) + maxYear = maxTime.year + for year in range(minYear, maxYear + 1): + if arg.year and year not in arg.year: + continue + if arg.start is not None and year < arg.start: + continue + if arg.end is not None and year > arg.end: + continue + time = gen_year_range(year, arg.utc) + toutput = join(output, str(year)) + generate_audio_report(pdb, re[0], re[1], re[2], toutput, userid, max(time[0], minTime.timestamp()), min(time[1], maxTime.timestamp())) # noqa: E501 diff --git a/jellyfinstats/utils.py b/jellyfinstats/utils.py index f7e0657..9cfa80a 100644 --- a/jellyfinstats/utils.py +++ b/jellyfinstats/utils.py @@ -1,6 +1,7 @@ from math import ceil, floor from datetime import datetime, timezone from re import compile +from typing import Tuple from . import _ from .config import Config @@ -78,10 +79,13 @@ def format_time(time: float | None = None, tz=timezone.utc) -> str: return d.strftime('%Y-%m-%d %H:%M:%S.%f') -def parse_time(time: str) -> float: +def parse_datetime(time: str) -> datetime: re = DATETIME_RE.match(time) - t = datetime(int(re[1]), int(re[2]), int(re[3]), int(re[4]), int(re[5]), int(re[6]), int(re[7].ljust(6, '0')), timezone.utc) # noqa: E501 - return t.timestamp() + return datetime(int(re[1]), int(re[2]), int(re[3]), int(re[4]), int(re[5]), int(re[6]), int(re[7].ljust(6, '0')), timezone.utc) # noqa: E501 + + +def parse_time(time: str) -> float: + return parse_datetime(time).timestamp() def convert_uid(uid: str) -> str: @@ -103,3 +107,8 @@ def format_duration(duration: float | None) -> str: min = str(floor(duration / 60)).rjust(2, "0") sec = str(duration % 60).rjust(2, "0") return f"{re}{min}:{sec}" + + +def gen_year_range(year: int, utc: bool = False) -> Tuple[float, float]: + tz = timezone.utc if utc else None + return (datetime(year, 1, 1, tzinfo=tz).timestamp(), datetime(year, 12, 31, 23, 59, 59, 999999, tzinfo=tz).timestamp()) # noqa: E501