diff --git a/README.md b/README.md index 89cc2e7..0947d14 100644 --- a/README.md +++ b/README.md @@ -51,7 +51,6 @@ You can then run the `anifetch` command **directly in your terminal**. Since pipx installs packages in an isolated environment, you won't have to worry about dependency conflicts or polluting your global python environment. `anifetch` will behave just like a native cli tool. You can upgrade your installation with `pipx upgrade anifetch` - --- ### ❄️ NixOS installation via flakes @@ -166,6 +165,8 @@ Any video file you give to anifetch will be stored in `~/.local/share/anifetch/a anifetch video.mp4 -r 10 -W 40 -H 20 -c "--symbols wide --fg-only" ``` +_Note : by default, the video `example.mp4` can directly be used as an example._ + ### Optional arguments: - `-f` / `--file`: path to the video file (the path can be added without the `-f` argument) @@ -178,6 +179,20 @@ anifetch video.mp4 -r 10 -W 40 -H 20 -c "--symbols wide --fg-only" - `-ff` / `--fast-fetch`: uses `fastfetch` instead of `neofetch` if available - `-fr` / `--force-render`: Forcefully re-renders the animation while not caring about the cache. Useful if the cache is broken. +### Cached files: + +Anifetch automatically caches rendered animations to speed up future runs. Each unique combination of video and render options generates a cache stored in `~/.local/share/anifetch/`, organized by hash. This includes frames, output, and audio. + +Cache-related commands: + +`anifetch --cache-list` — View all cached configurations and orders them. + +`anifetch --cache-delete ` — Delete a specific cache. + +`anifetch --clear` — Delete all cached files. + +Note that modifying the content of a video file but keeping the same name makes Anifetch still use the old cache. In that case, use `--force-render` to bypass the cache and generate a new version. + For full help: ```bash @@ -239,7 +254,7 @@ Currently only the `symbols` format of chafa is supported, formats like kitty, i - [ ] Use threading when seperating video into frames and process them with chafa at the same time. This should speed up caching significantly. -- [X] Fix transparent video frame seperation. +- [x] Fix transparent video frame seperation. - [ ] Figure out a way to display animations faster. Either optimize the bash script or use Python/C. diff --git a/nix/packages/anifetch.nix b/nix/packages/anifetch.nix index f8f807c..984e56e 100644 --- a/nix/packages/anifetch.nix +++ b/nix/packages/anifetch.nix @@ -10,7 +10,7 @@ in fs.trace sourceFiles python3Packages.buildPythonApplication { name = "aniftech-wrapped"; - version = "0.1.1"; + version = "0.1.2"; pyproject = true; src = fs.toSource { root = ../../.; diff --git a/pyproject.toml b/pyproject.toml index 4392d1a..24798da 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "anifetch" -version = "0.1.1" +version = "0.1.2" description = "Animated terminal fetch with video/audio support." authors = [{name = "Notenlish"}, {name = "Immelancholy"}, {name = "Gallophostrix", email = "gallophostrix@gmail.com"}] readme = "README.md" diff --git a/src/anifetch/__init__.py b/src/anifetch/__init__.py index 97c4961..b6e79ff 100644 --- a/src/anifetch/__init__.py +++ b/src/anifetch/__init__.py @@ -9,8 +9,7 @@ def main(): args = parse_args() - if args.filename: - run_anifetch(args) + run_anifetch(args) if __name__ == "__main__": diff --git a/src/anifetch/anifetch-static-resize2.sh b/src/anifetch/anifetch-static-resize2.sh index 3c88fea..8123559 100644 --- a/src/anifetch/anifetch-static-resize2.sh +++ b/src/anifetch/anifetch-static-resize2.sh @@ -1,21 +1,22 @@ #!/bin/bash -FRAME_DIR="$HOME/.local/share/anifetch/output" -STATIC_TEMPLATE_FILE="$HOME/.local/share/anifetch/template.txt" - # check for num of args -if [[ $# -ne 6 && $# -ne 7 ]]; then - echo "Usage: $0 [soundname]" +if [[ $# -ne 7 && $# -ne 8 ]]; then + echo "Usage: $0 [soundname]" exit 1 fi -framerate=$1 -top=$2 -left=$3 -right=$4 -bottom=$5 -template_actual_width=$6 -soundname=$7 +CACHE_DIR="$1" +framerate=$2 +top=$3 +left=$4 +right=$5 +bottom=$6 +template_actual_width=$7 +soundname=$8 + +FRAME_DIR="$CACHE_DIR/output" +STATIC_TEMPLATE_FILE="$CACHE_DIR/template.txt" num_lines=$((bottom - top)) sleep_time=$(echo "scale=4; 1 / $framerate" | bc) @@ -34,7 +35,7 @@ cleanup() { tput cnorm # Show cursor if [ -t 0 ]; then stty echo # Restore echo - stty icanon + stty icanon fi tput sgr0 # Reset terminal attributes tput cup $(tput lines) 0 # Move cursor to bottom @@ -217,7 +218,7 @@ trap 'on_resize' SIGWINCH draw_static_template # Start audio if sound is provided -if [ $# -eq 7 ]; then +if [ $# -eq 8 ]; then ffplay -nodisp -autoexit -loop 0 -loglevel quiet "$soundname" & fi @@ -226,7 +227,7 @@ wanted_epoch=0 start_time=$(date +%s.%N) while true; do - for frame in $(ls "$FRAME_DIR" | sort -n); do + for frame in $(ls "$FRAME_DIR" | sort -n); do #### for frame in $(find "$FRAME_DIR" -type f | sort -V); do lock=true current_top=$top while IFS= read -r line; do diff --git a/src/anifetch/cli.py b/src/anifetch/cli.py index a19d484..a84c963 100644 --- a/src/anifetch/cli.py +++ b/src/anifetch/cli.py @@ -6,106 +6,125 @@ from .utils import get_version_of_anifetch -def parse_args(): - parser = argparse.ArgumentParser( - prog="Anifetch", - description="Allows you to use neofetch with video in terminal (using chafa).", - ) - parser.add_argument( - "-b", - "--benchmark", - default=False, - help="For testing. Runs Anifetch without actually starting the animation and times how long it took. Also does the same for neofetch and fastfetch. Checks anifetch for both cached and not cached version.", - action="store_true", - ) - parser.add_argument( - "filename", - help="Video file to use (default: example.mp4)", - type=str, - ) - parser.add_argument( - "-w", - "-W", - "--width", - default=40, - help="Width of the chafa animation.", - type=int, - ) - parser.add_argument( - "-H", - "--height", - default=20, - help="Height of the chafa animation.", - type=int, - ) - parser.add_argument("-v", "--verbose", default=False, action="store_true") - parser.add_argument( - "-r", - "--framerate", - default=10, - help="Sets the framerate when extracting frames from ffmpeg.", - type=int, - ) - parser.add_argument( - "-pr", - "--playback-rate", - default=10, - help="Ignored when a sound is playing so that desync doesn't happen. Sets the playback rate of the animation. Not to be confused with the 'framerate' option. This basically sets for how long the script will wait before rendering new frame, while the framerate option affects how many frames are generated via ffmpeg.", - ) - parser.add_argument( - "-s", - "--sound", - required=False, - nargs="?", - help="Optional. Will playback a sound file while displaying the animation. If you give only -s without any sound file it will attempt to extract the sound from the video.", - type=str, - ) - parser.add_argument( - "-fr", - "--force-render", - default=False, - action="store_true", - help="Disabled by default. Anifetch saves the filename to check if the file has changed, if the name is same, it won't render it again. If enabled, the video will be forcefully rendered, whether it has the same name or not. Please note that it only checks for filename, if you changed the framerate then you'll need to force render.", - ) - parser.add_argument( - "-C", - "--center", - default=False, - action="store_true", - help="Disabled by default. Use this argument to center the animation relative to the fetch output. Note that centering may slow down the execution.", - ) - parser.add_argument( - "-c", - "--chafa-arguments", - default="--symbols ascii --fg-only", - help="Specify the arguments to give to chafa. For more informations, use 'chafa --help'", - ) - parser.add_argument( - "--force", - default=False, - help="Add this argument if you want to use neofetch even if it is deprecated.", - action="store_true", - ) - parser.add_argument( - "-ff", - "--fast-fetch", - default=False, - help="Add this argument if you want to use fastfetch instead. Note than fastfetch will be run with '--logo none'.", - action="store_true", - ) - parser.add_argument( - "--chroma", - required=False, - nargs="?", - help="Add this argument to chromakey a hexadecimal color from the video using ffmpeg using syntax of '--chroma ::' with being 0xRRGGBB with a 0x as opposed to a # e.g. '--chroma 0xc82044:0.1:0.1'", - type=str, - ) - parser.add_argument( - "--version", - action="version", - version="%(prog)s {version}".format(version=get_version_of_anifetch()), - ) +parser = argparse.ArgumentParser( + prog="Anifetch", + description="Allows you to use neofetch with video in terminal (using chafa).", +) +parser.add_argument( + "-b", + "--benchmark", + default=False, + help="For testing. Runs Anifetch without actually starting the animation and times how long it took. Also does the same for neofetch and fastfetch. Checks anifetch for both cached and not cached version.", + action="store_true", +) +parser.add_argument( + "filename", + nargs="?", + default=None, + help="Video file to use (e.g. video.mp4).", +) +parser.add_argument( + "-w", + "-W", + "--width", + default=40, + help="Width of the chafa animation.", + type=int, +) +parser.add_argument( + "-H", + "--height", + default=20, + help="Height of the chafa animation.", + type=int, +) +parser.add_argument("-v", "--verbose", default=False, action="store_true") +parser.add_argument( + "-r", + "--framerate", + default=10, + help="Sets the framerate when extracting frames from ffmpeg.", + type=int, +) +parser.add_argument( + "-pr", + "--playback-rate", + default=10, + help="Ignored when a sound is playing so that desync doesn't happen. Sets the playback rate of the animation. Not to be confused with the 'framerate' option. This basically sets for how long the script will wait before rendering new frame, while the framerate option affects how many frames are generated via ffmpeg.", +) +parser.add_argument( + "-s", + "--sound", + required=False, + nargs="?", + help="Optional. Will playback a sound file while displaying the animation. If you give only -s without any sound file it will attempt to extract the sound from the video.", + type=str, +) +parser.add_argument( + "-fr", + "--force-render", + default=False, + action="store_true", + help="Disabled by default. Anifetch saves the filename to check if the file has changed, if the name is same, it won't render it again. If enabled, the video will be forcefully rendered, whether it has the same name or not. Please note that it only checks for filename, if you changed the framerate then you'll need to force render.", +) +parser.add_argument( + "-C", + "--center", + default=False, + action="store_true", + help="Disabled by default. Use this argument to center the animation relative to the fetch output. Note that centering may slow down the execution.", +) +parser.add_argument( + "-c", + "--chafa-arguments", + default="--symbols ascii --fg-only", + help="Specify the arguments to give to chafa. For more information, use 'chafa --help'", +) +parser.add_argument( + "--force", + default=False, + help="Add this argument if you want to use neofetch even if it is deprecated.", + action="store_true", +) +parser.add_argument( + "-ff", + "--fast-fetch", + default=False, + help="Add this argument if you want to use fastfetch instead. Note than fastfetch will be run with '--logo none'.", + action="store_true", +) +parser.add_argument( + "--chroma", + required=False, + nargs="?", + help="Add this argument to chromakey a hexadecimal color from the video using ffmpeg using syntax of '--chroma ::' with being 0xRRGGBB with a 0x as opposed to a # e.g. '--chroma 0xc82044:0.1:0.1'", + type=str, +) +parser.add_argument( + "--version", + action="version", + version="%(prog)s {version}".format(version=get_version_of_anifetch()), +) +parser.add_argument( + "--cache-list", + required=False, + action="store_true", + help="List all saved cache configurations.", +) +parser.add_argument( + "--delete", + required=False, + type=int, + nargs="+", + help="Delete one or more caches by number(s) (as listed with --cache-list)", +) +parser.add_argument( + "--clear", + required=False, + action="store_true", + help="Clear all saved cache configurations.", +) - args = parser.parse_args() - return args +def parse_args(): + return parser.parse_args() diff --git a/src/anifetch/core.py b/src/anifetch/core.py index eade650..282a092 100644 --- a/src/anifetch/core.py +++ b/src/anifetch/core.py @@ -22,6 +22,13 @@ render_frame, print_verbose, check_sound_flag_given, + clean_cache_args, + check_args_hash_same, + find_corresponding_cache, + hash_of_cache_args, + get_caches_json, + save_caches_json, + args_checker, ) GAP = 2 @@ -31,23 +38,78 @@ def run_anifetch(args): st = time.time() + allowed_alternatives = ["cache_list", "clear", "delete"] + try: + args_checker(allowed_alternatives, args) + except ValueError as e: + print(f"[ERROR] {e}") + sys.exit(1) + args.sound_flag_given = check_sound_flag_given(sys.argv) args.chroma_flag_given = args.chroma is not None neofetch_status = get_neofetch_status() BASE_PATH = get_data_path() - VIDEO_DIR = BASE_PATH / "video" - OUTPUT_DIR = BASE_PATH / "output" - CACHE_PATH = BASE_PATH / "cache.json" ASSET_PATH = BASE_PATH / "assets" - (ASSET_PATH).mkdir(parents=True, exist_ok=True) - (VIDEO_DIR).mkdir(exist_ok=True) - (OUTPUT_DIR).mkdir(exist_ok=True) default_asset_presence_check(ASSET_PATH) + CACHE_LIST_PATH = BASE_PATH / "caches.json" + + if args.cache_list: + all_caches = get_caches_json(CACHE_LIST_PATH) + if not all_caches: + print("No cached configurations found.") + else: + print("Available caches:") + for i, cache in enumerate(all_caches, 1): + line = f"[{i}] video: {cache.get('filename', '?')} | width: {cache.get('width')} | chroma: {cache.get('chroma')}" + print(line) + sys.exit(0) + + if args.delete: + all_caches = get_caches_json(CACHE_LIST_PATH) + to_delete = sorted(set(args.delete), reverse=True) + max_index = len(all_caches) + + for index in to_delete: + real_index = index - 1 + if not (0 <= real_index < max_index): + print(f"[ERROR] No cache found with number {index}") + continue + + cache = all_caches[real_index] + hash_to_delete = cache["hash"] + cache_dir = BASE_PATH / hash_to_delete + + if cache_dir.exists(): + shutil.rmtree(cache_dir) + print(f"Deleted cache directory: {cache_dir}") + else: + print(f"[WARNING] Cache directory {cache_dir} already missing.") + + # Supprimer du cache JSON + del all_caches[real_index] + max_index -= 1 # car on modifie la liste au fur et à mesure + + save_caches_json(CACHE_LIST_PATH, all_caches) + sys.exit(0) + + if args.clear: + all_caches = get_caches_json(CACHE_LIST_PATH) + for cache in all_caches: + hash_id = cache.get("hash") + if hash_id: + cache_dir = BASE_PATH / hash_id + if cache_dir.exists(): + shutil.rmtree(cache_dir) + print(f"Deleted cache directory: {cache_dir}") + save_caches_json(CACHE_LIST_PATH, []) + print("All cache entries have been cleared.") + sys.exit(0) + filename = pathlib.Path(args.filename) # If the filename is relative, check if it exists in the assets directory. @@ -71,6 +133,19 @@ def run_anifetch(args): pass args.filename = str(newpath) + args_dict = {key: value for key, value in args._get_kwargs()} + cleaned_dict = clean_cache_args(args_dict) + cleaned_dict["hash"] = hash_of_cache_args(cleaned_dict) + + CACHE_PATH = BASE_PATH / cleaned_dict["hash"] + + VIDEO_DIR = CACHE_PATH / "video" + OUTPUT_DIR = CACHE_PATH / "output" + + CACHE_PATH.mkdir(parents=True, exist_ok=True) + (VIDEO_DIR).mkdir(exist_ok=True) + (OUTPUT_DIR).mkdir(exist_ok=True) + if args.sound_flag_given: if args.sound: pass @@ -82,42 +157,57 @@ def run_anifetch(args): print(f"[ERROR] {e}") sys.exit(1) - args.sound_saved_path = str(BASE_PATH / f"output_audio.{ext}") + # sound and sound_flag_given will be used for hash calculation. + # sound_saved_path is a value that must be kept in the 'cleaned' variable but it shouldnt be used in calculating hash, that's what sound and sound_flag_given are for. + args.sound_saved_path = str(CACHE_PATH / f"output_audio.{ext}") + cleaned_dict["sound_saved_path"] = args.sound_saved_path if args.chroma and args.chroma.startswith("#"): print("[ERROR] Use '0x' prefix for chroma color, not '#'.", file=sys.stderr) sys.exit(1) # check cache - should_update = False - try: - args_dict = {key: value for key, value in args._get_kwargs()} - if args.force_render: + should_update = args.force_render # True if --force-render + + if not should_update: + try: + all_caches = get_caches_json(CACHE_LIST_PATH) + + for cache_args in all_caches: + if check_args_hash_same(cache_args, cleaned_dict): + break + else: + print_verbose( + "Couldn't find a corresponding cache. Will cache the animation." + ) + should_update = True + + except FileNotFoundError: should_update = True - else: - with open(CACHE_PATH, "r") as f: - data = json.load(f) - for key, value in args_dict.items(): - try: - cached_value = data[key] - except KeyError: - should_update = True + + if not (CACHE_PATH / "output").exists() and not should_update: + print("[WARNING] Cache folder found but output is missing. Will regenerate.") + should_update = True + + if not should_update: + try: + with open(CACHE_LIST_PATH, "r") as f: + all_caches = json.load(f) + + for cache_args in all_caches: + if check_args_hash_same(cache_args, cleaned_dict): break - if value != cached_value: # check if all options match - if key not in ( - "playback_rate", - "verbose", - "center", - "fast_fetch", - "benchmark", - "force_render", - ): # These arguments don't invalidate the cache. - print_verbose( # TODO: this is a very ugly way of doing verbose debug printing - args.verbose, - f"{key} INVALID! Will cache again. Value:{value} Cache:{cached_value}", - ) - should_update = True - except FileNotFoundError: + else: + print_verbose( + "Couldn't find a corresponding cache. Will cache the animation." + ) + should_update = True + + except FileNotFoundError: + should_update = True + + if not (CACHE_PATH / "output").exists(): + print("[WARNING] Cache folder found but output is missing. Will regenerate.") should_update = True if should_update: @@ -127,7 +217,7 @@ def run_anifetch(args): # automatically calculate height if not given if "--height" not in sys.argv and "-H" not in sys.argv: try: - vid_w, vid_h = get_video_dimensions(ASSET_PATH / args.filename) + vid_w, vid_h = get_video_dimensions(ASSET_PATH / args.filename) #### Zob except RuntimeError as e: print(f"[ERROR] {e}") sys.exit(1) @@ -185,8 +275,11 @@ def run_anifetch(args): if should_update: print_verbose(args.verbose, "SHOULD RENDER WITH CHAFA") - # delete all old frames - shutil.rmtree(VIDEO_DIR, ignore_errors=True) + # deletes the old cache + if CACHE_PATH.exists(): + shutil.rmtree(CACHE_PATH) + + os.mkdir(CACHE_PATH) (VIDEO_DIR).mkdir(exist_ok=True) stdout = None if args.verbose else subprocess.DEVNULL @@ -200,7 +293,7 @@ def run_anifetch(args): f"{args.filename}", "-vf", f"fps={args.framerate},format=rgba", - str(BASE_PATH / "video/%05d.png"), + str(CACHE_PATH / "video/%05d.png"), ], stdout=stdout, stderr=stderr, @@ -225,7 +318,7 @@ def run_anifetch(args): if args.sound: # sound file given print_verbose(args.verbose, "Sound file to use:", args.sound) source = pathlib.Path(args.sound) - dest = BASE_PATH / source.with_name(f"output_audio{source.suffix}") + dest = CACHE_PATH / source.with_name(f"output_audio{source.suffix}") shutil.copy(source, dest) args.sound_saved_path = str(dest) else: @@ -235,16 +328,19 @@ def run_anifetch(args): ) codec = check_codec_of_file(args.filename) ext = get_ext_from_codec(codec) - audio_file = extract_audio_from_file(BASE_PATH, args.filename, ext) + audio_file = extract_audio_from_file(CACHE_PATH, args.filename, ext) print_verbose(args.verbose, "Extracted audio file.") args.sound_saved_path = str(audio_file) + cleaned_dict["sound_saved_path"] = args.sound_saved_path + + cleaned_dict["sound_saved_path"] = args.sound_saved_path + print_verbose(args.verbose, args.sound_saved_path) # If the new anim frames is shorter than the old one, then in /output there will be both new and old frames. # Empty the directory to fix this. - shutil.rmtree(OUTPUT_DIR) os.mkdir(OUTPUT_DIR) print_verbose(args.verbose, "Emptied the output folder.") @@ -324,26 +420,39 @@ def run_anifetch(args): [" " * WIDTH] * pad + fetch_output + [" " * WIDTH] * (pad + remind) ) - with open(BASE_PATH / "frame.txt", "w") as f: + with open(CACHE_PATH / "frame.txt", "w") as f: f.writelines(frames) HEIGHT = len(frames[0].splitlines()) # reloarding the cached output - with open(CACHE_PATH, "r") as f: - data = json.load(f) + with open(CACHE_LIST_PATH, "r") as f: + all_saved_caches = json.load(f) + corresponding_cache = find_corresponding_cache( + cleaned_dict, all_saved_caches + ) if args.sound_flag_given: - args.sound_saved_path = data["sound_saved_path"] + args.sound_saved_path = corresponding_cache["sound_saved_path"] else: args.sound_saved_path = None print_verbose(args.verbose, "-----------") + print_verbose(args.verbose, "ARGS FOR SAVING CACHES.JSON") # save the caching arguments - with open(CACHE_PATH, "w") as f: - args_dict = {key: value for key, value in args._get_kwargs()} - json.dump(args_dict, f, indent=2) + caches_data = get_caches_json(CACHE_LIST_PATH) + + added = False + for i, cache_dict in enumerate(caches_data): + if cache_dict["hash"] == cleaned_dict["hash"]: + caches_data[i] = cleaned_dict # replace the cache with the new one + added = True + if not added: + caches_data.append(cleaned_dict) + + with open(BASE_PATH / "caches.json", "w") as f: + json.dump(caches_data, f, indent=2) if len(fetch_lines) == 0: raise Exception("fetch_lines has no items in it:", fetch_lines) @@ -358,7 +467,7 @@ def run_anifetch(args): template_actual_width = output_width # TODO: maybe this should instead be the text_length_of_formatted_text(cleaned_line) # writing the tempate to a file. - with open(BASE_PATH / "template.txt", "w") as f: + with open(CACHE_PATH / "template.txt", "w") as f: f.writelines(template) print_verbose(args.verbose, "Template updated") @@ -372,7 +481,7 @@ def run_anifetch(args): script_dir = pathlib.Path(__file__).parent bash_script_path = script_dir / bash_script_name - if not args.benchmark: + if not args.benchmark: # opitional? try: framerate_to_use = args.playback_rate if args.sound_flag_given: @@ -383,6 +492,7 @@ def run_anifetch(args): script_args = [ "bash", str(bash_script_path), + str(CACHE_PATH), str(framerate_to_use), str(TOP), str(LEFT), diff --git a/src/anifetch/utils.py b/src/anifetch/utils.py index 26e8fdd..04f1c18 100644 --- a/src/anifetch/utils.py +++ b/src/anifetch/utils.py @@ -5,6 +5,7 @@ """ import pathlib +import json import re import subprocess import sys @@ -12,6 +13,8 @@ from platformdirs import user_data_dir from importlib.metadata import version, PackageNotFoundError import shutil +from copy import deepcopy +from hashlib import sha256 appname = "anifetch" appauthor = "anifetch" @@ -81,8 +84,8 @@ def check_codec_of_file(file: str): return None -def extract_audio_from_file(BASE_PATH, file: str, extension): - audio_file = BASE_PATH / f"output_audio.{extension}" +def extract_audio_from_file(CACHE_PATH, file: str, extension): + audio_file = CACHE_PATH / f"output_audio.{extension}" extract_cmd = [ "ffmpeg", "-i", @@ -192,3 +195,76 @@ def get_video_dimensions(filename): except subprocess.CalledProcessError as e: print("FFPROBE FAILED WITH OUTPUT:", e.output) raise RuntimeError(f"Failed to get video dimensions: {filename}") + + +def clean_cache_args(cache_args: dict) -> dict: + """Removes unimportant caching args that don't matter when caching/checking caches. Returns the cleaned dict.""" + args_to_remove = ( + "playback_rate", + "verbose", + "fast_fetch", + "benchmark", + "force_render", + ) + cleaned = deepcopy(cache_args) # need to deepcopy to not modify original dict. + for key in args_to_remove: + if key in cleaned: + del cleaned[key] + return cleaned + + +def check_args_hash_same(args1: dict, args2: dict): + for a in (args1, args2): + if a.get("hash", None) is None: + raise KeyError(f"{args1} doesn't have a hash!") + if args1["hash"] == args2["hash"]: + return True + return False + + +def find_corresponding_cache(args: dict, all_saved_caches_list: list[dict]): + corresponding = None + for saved_cache_dict in all_saved_caches_list: + if check_args_hash_same(args, saved_cache_dict): + corresponding = saved_cache_dict + if corresponding is None: + raise LookupError("Couldn't find corresponding dict in all saved caches.") + return corresponding + + +def hash_dict(d: dict): + json_str = json.dumps(d, sort_keys=True, ensure_ascii=False) + encoded = json_str.encode("utf-8") + hashed = sha256(encoded) + return hashed.hexdigest() + + +def hash_of_cache_args(args: dict): + """Takes in the cleaned dictionary consisting of all the arguments for caching and generates an hash. If a 'hash' key already exists raises an KeyError.""" + if "hash" in args.keys(): + raise KeyError("Hash already exists for this cache args dictionary.") + + hash = hash_dict(args) + return hash + + +def get_caches_json(CACHE_LIST_PATH): + if (CACHE_LIST_PATH).exists(): + with open(CACHE_LIST_PATH, "r") as f: + caches_data: list[dict] = json.load(f) + return caches_data + return [] + + +def save_caches_json(CACHE_LIST_PATH, data): + with open(CACHE_LIST_PATH, "w") as f: + json.dump(data, f, indent=2) + + +def args_checker(allowed_alternatives, args): + if args.filename is None and not any( + getattr(args, key) for key in allowed_alternatives + ): + raise ValueError( + "Missing input. Use a filename or a cache monitoring argument.\nUse --help for help." + ) diff --git a/tools/benchmark.py b/tools/benchmark.py index de59677..79ff728 100644 --- a/tools/benchmark.py +++ b/tools/benchmark.py @@ -2,6 +2,7 @@ """ Benchmarking script for comparing the performance of Anifetch with Neofetch and Fastfetch. +Only works with 'pip' installation. """ import subprocess @@ -26,7 +27,7 @@ def time_check( def run_all(): count = 10 - video = "" # optionally: "-f example.mp4" + video = "example.mp4" common_args = f"{video} -W 60 -r 10 --benchmark" tests = [