From 61213114444786b80da7d7b2f069a1e7cdae57e8 Mon Sep 17 00:00:00 2001 From: sHa Date: Tue, 30 Dec 2025 23:40:48 +0000 Subject: [PATCH] Add Ice Age: Continental Drift (2012) BDRip file to test filenames --- dist/renamer-0.5.10-py3-none-any.whl | Bin 0 -> 47736 bytes pyproject.toml | 2 +- renamer/app.py | 17 +---- renamer/cache.py | 62 ++++++++++++++++-- renamer/decorators/caching.py | 15 +++-- renamer/extractors/extractor.py | 37 +---------- renamer/extractors/tmdb_extractor.py | 20 ++++-- renamer/formatters/formatter.py | 13 ---- renamer/settings.py | 4 +- ...) BDRip [1080p,ukr,eng] [tmdbid-57800].mkv | 0 uv.lock | 2 +- 11 files changed, 88 insertions(+), 84 deletions(-) create mode 100644 dist/renamer-0.5.10-py3-none-any.whl create mode 100644 renamer/test/filenames/[04] Ice Age: Continental Drift (2012) BDRip [1080p,ukr,eng] [tmdbid-57800].mkv diff --git a/dist/renamer-0.5.10-py3-none-any.whl b/dist/renamer-0.5.10-py3-none-any.whl new file mode 100644 index 0000000000000000000000000000000000000000..85ab288c206a6cd9dfa49fe3f6911de409078f01 GIT binary patch literal 47736 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 ComposeResult: with Horizontal(): @@ -148,10 +146,9 @@ class RenamerApp(App): ).start() def _extract_and_show_details(self, file_path: Path): - time.sleep(1) # Minimum delay to show loading try: # Initialize extractors and formatters - extractor = MediaExtractor.create(file_path, self.cache, self.settings.get("cache_ttl_extractors")) + extractor = MediaExtractor(file_path) mode = self.settings.get("mode") if mode == "technical": @@ -205,11 +202,6 @@ class RenamerApp(App): tree = self.query_one("#file_tree", Tree) node = tree.cursor_node if node and node.data and isinstance(node.data, Path) and node.data.is_file(): - # Clear cache for this file - cache_key_base = str(node.data) - # Invalidate all keys for this file (we can improve this later) - for key in ["title", "year", "source", "extension", "video_tracks", "audio_tracks", "subtitle_tracks"]: - self.cache.invalidate(f"{cache_key_base}_{key}") self._start_loading_animation() threading.Thread( target=self._extract_and_show_details, args=(node.data,) @@ -240,7 +232,7 @@ class RenamerApp(App): node = tree.cursor_node if node and node.data and isinstance(node.data, Path) and node.data.is_file(): # Get the proposed name from the extractor - extractor = MediaExtractor.create(node.data, self.cache, self.settings.get("cache_ttl_extractors")) + extractor = MediaExtractor(node.data) proposed_formatter = ProposedNameFormatter(extractor) new_name = str(proposed_formatter) logging.info(f"Proposed new name: {new_name!r} for file: {node.data}") @@ -273,11 +265,6 @@ class RenamerApp(App): """Update the tree node for a renamed file.""" logging.info(f"update_renamed_file called with old_path={old_path}, new_path={new_path}") - # Clear cache for old file - cache_key_base = str(old_path) - for key in ["title", "year", "source", "extension", "video_tracks", "audio_tracks", "subtitle_tracks"]: - self.cache.invalidate(f"{cache_key_base}_{key}") - tree = self.query_one("#file_tree", Tree) logging.info(f"Before update: cursor_node.data = {tree.cursor_node.data if tree.cursor_node else None}") diff --git a/renamer/cache.py b/renamer/cache.py index 11368be..0108a56 100644 --- a/renamer/cache.py +++ b/renamer/cache.py @@ -11,13 +11,16 @@ class Cache: """File-based cache with TTL support.""" def __init__(self, cache_dir: Optional[Path] = None): - if cache_dir is None: - cache_dir = Path.home() / ".cache" / "renamer" + # Always use the default cache dir to avoid creating cache in scan dir + cache_dir = Path.home() / ".cache" / "renamer" self.cache_dir = cache_dir self.cache_dir.mkdir(parents=True, exist_ok=True) + self._memory_cache = {} # In-memory cache for faster access def _get_cache_file(self, key: str) -> Path: """Get cache file path with hashed filename and subdirs.""" + import logging + logging.info(f"Cache _get_cache_file called with key: {key!r}") # Parse key format: ClassName.method_name.param_hash if '.' in key: parts = key.split('.') @@ -26,12 +29,27 @@ class Cache: method_name = parts[1] param_hash = parts[2] + # Use class name as subdir, but if it contains '/', use general to avoid creating nested dirs + if '/' in class_name or '\\' in class_name: + subdir = "general" + subkey = key + file_ext = "json" + else: + subdir = class_name + file_ext = "pkl" + # Use class name as subdir - cache_subdir = self.cache_dir / class_name + cache_subdir = self.cache_dir / subdir + logging.info(f"Cache parsed key, class_name: {class_name!r}, cache_subdir: {cache_subdir!r}") cache_subdir.mkdir(parents=True, exist_ok=True) - # Use method_name.param_hash as filename - return cache_subdir / f"{method_name}.{param_hash}.pkl" + if file_ext == "pkl": + # Use method_name.param_hash as filename + return cache_subdir / f"{method_name}.{param_hash}.pkl" + else: + # Hash the subkey for filename + key_hash = hashlib.md5(subkey.encode('utf-8')).hexdigest() + return cache_subdir / f"{key_hash}.json" # Fallback for old keys (tmdb_, poster_, etc.) if key.startswith("tmdb_"): @@ -40,12 +58,16 @@ class Cache: elif key.startswith("poster_"): subdir = "posters" subkey = key[7:] # Remove "poster_" prefix + elif key.startswith("extractor_"): + subdir = "extractors" + subkey = key[10:] # Remove "extractor_" prefix else: subdir = "general" subkey = key # Create subdir cache_subdir = self.cache_dir / subdir + logging.info(f"Cache fallback, subdir: {subdir!r}, cache_subdir: {cache_subdir!r}") cache_subdir.mkdir(parents=True, exist_ok=True) # Hash the subkey for filename @@ -54,6 +76,14 @@ class Cache: def get(self, key: str) -> Optional[Any]: """Get cached value if not expired.""" + # Check memory cache first + if key in self._memory_cache: + data = self._memory_cache[key] + if time.time() > data.get('expires', 0): + del self._memory_cache[key] + return None + return data.get('value') + cache_file = self._get_cache_file(key) if not cache_file.exists(): return None @@ -67,6 +97,8 @@ class Cache: cache_file.unlink(missing_ok=True) return None + # Store in memory cache + self._memory_cache[key] = data return data.get('value') except (json.JSONDecodeError, IOError): # Corrupted, remove @@ -75,11 +107,14 @@ class Cache: def set(self, key: str, value: Any, ttl_seconds: int) -> None: """Set cached value with TTL.""" - cache_file = self._get_cache_file(key) data = { 'value': value, 'expires': time.time() + ttl_seconds } + # Store in memory cache + self._memory_cache[key] = data + + cache_file = self._get_cache_file(key) try: with open(cache_file, 'w') as f: json.dump(data, f) @@ -154,6 +189,14 @@ class Cache: def get_object(self, key: str) -> Optional[Any]: """Get pickled object from cache if not expired.""" + # Check memory cache first + if key in self._memory_cache: + data = self._memory_cache[key] + if time.time() > data.get('expires', 0): + del self._memory_cache[key] + return None + return data.get('value') + cache_file = self._get_cache_file(key) if not cache_file.exists(): return None @@ -167,6 +210,8 @@ class Cache: cache_file.unlink(missing_ok=True) return None + # Store in memory cache + self._memory_cache[key] = data return data.get('value') except (pickle.PickleError, IOError): # Corrupted, remove @@ -175,11 +220,14 @@ class Cache: def set_object(self, key: str, obj: Any, ttl_seconds: int) -> None: """Pickle and cache object with TTL.""" - cache_file = self._get_cache_file(key) data = { 'value': obj, 'expires': time.time() + ttl_seconds } + # Store in memory cache + self._memory_cache[key] = data + + cache_file = self._get_cache_file(key) try: with open(cache_file, 'wb') as f: pickle.dump(data, f) diff --git a/renamer/decorators/caching.py b/renamer/decorators/caching.py index ebc330b..583a4ff 100644 --- a/renamer/decorators/caching.py +++ b/renamer/decorators/caching.py @@ -31,12 +31,17 @@ def cached_method(ttl_seconds: int = 3600) -> Callable: # Use instance identifier (file_path for extractors) instance_id = getattr(self, 'file_path', str(id(self))) + # If instance_id contains path separators, hash it to avoid creating subdirs + if '/' in str(instance_id) or '\\' in str(instance_id): + instance_id = hashlib.md5(str(instance_id).encode('utf-8')).hexdigest() - # Create hash from args and kwargs (excluding self) - param_str = json.dumps((args, kwargs), sort_keys=True, default=str) - param_hash = hashlib.md5(param_str.encode('utf-8')).hexdigest() - - cache_key = f"{class_name}.{method_name}.{instance_id}.{param_hash}" + # Create hash from args and kwargs only if they exist (excluding self) + if args or kwargs: + param_str = json.dumps((args, kwargs), sort_keys=True, default=str) + param_hash = hashlib.md5(param_str.encode('utf-8')).hexdigest() + cache_key = f"{class_name}.{method_name}.{instance_id}.{param_hash}" + else: + cache_key = f"{class_name}.{method_name}.{instance_id}" # Try to get from cache cached_result = _cache.get_object(cache_key) diff --git a/renamer/extractors/extractor.py b/renamer/extractors/extractor.py index 3cd14ab..2a30082 100644 --- a/renamer/extractors/extractor.py +++ b/renamer/extractors/extractor.py @@ -10,38 +10,14 @@ from .default_extractor import DefaultExtractor class MediaExtractor: """Class to extract various metadata from media files using specialized extractors""" - @classmethod - def create(cls, file_path: Path, cache=None, ttl_seconds: int = 21600): - """Factory method that returns cached object if available, else creates new.""" - if cache: - cache_key = f"extractor_{file_path}" - cached_obj = cache.get_object(cache_key) - if cached_obj: - print(f"Loaded MediaExtractor object from cache for {file_path.name}") - return cached_obj - - # Create new instance - instance = cls(file_path, cache, ttl_seconds) - - # Cache the object - if cache: - cache_key = f"extractor_{file_path}" - cache.set_object(cache_key, instance, ttl_seconds) - print(f"Cached MediaExtractor object for {file_path.name}") - - return instance - - def __init__(self, file_path: Path, cache=None, ttl_seconds: int = 21600): + def __init__(self, file_path: Path): self.file_path = file_path - self.cache = cache - self.ttl_seconds = ttl_seconds - self.cache_key = f"file_data_{file_path}" self.filename_extractor = FilenameExtractor(file_path) self.metadata_extractor = MetadataExtractor(file_path) self.mediainfo_extractor = MediaInfoExtractor(file_path) self.fileinfo_extractor = FileInfoExtractor(file_path) - self.tmdb_extractor = TMDBExtractor(file_path, cache, ttl_seconds) + self.tmdb_extractor = TMDBExtractor(file_path) self.default_extractor = DefaultExtractor() # Extractor mapping @@ -190,16 +166,9 @@ class MediaExtractor: ], }, } - - # No caching logic here - handled in create() method - + def get(self, key: str, source: str | None = None): """Get extracted data by key, optionally from specific source""" - print(f"Extracting real data for key '{key}' in {self.file_path.name}") - return self._get_uncached(key, source) - - def _get_uncached(self, key: str, source: str | None = None): - """Original get logic without caching""" if source: # Specific source requested - find the extractor and call the method directly for extractor_name, extractor in self._extractors.items(): diff --git a/renamer/extractors/tmdb_extractor.py b/renamer/extractors/tmdb_extractor.py index 6ac77b6..519d562 100644 --- a/renamer/extractors/tmdb_extractor.py +++ b/renamer/extractors/tmdb_extractor.py @@ -3,30 +3,34 @@ import os import time import hashlib import requests +import logging from pathlib import Path from typing import Dict, Optional, Tuple, Any from ..secrets import TMDB_API_KEY, TMDB_ACCESS_TOKEN - +from ..cache import Cache +from ..settings import Settings class TMDBExtractor: """Class to extract TMDB movie information""" - def __init__(self, file_path: Path, cache=None, ttl_seconds: int = 21600): + def __init__(self, file_path: Path): self.file_path = file_path - self.cache = cache - self.ttl_seconds = ttl_seconds + self.cache = Cache() + self.ttl_seconds = Settings().get("cache_ttl_extractors", 21600) self._movie_db_info = None def _get_cached_data(self, cache_key: str) -> Optional[Dict[str, Any]]: """Get data from cache if valid""" if self.cache: - return self.cache.get(f"tmdb_{cache_key}") + return self.cache.get_object(f"tmdb_{cache_key}") return None def _set_cached_data(self, cache_key: str, data: Dict[str, Any]): """Store data in cache""" if self.cache: - self.cache.set(f"tmdb_{cache_key}", data, self.ttl_seconds) + self.cache.set_object(f"tmdb_{cache_key}", data, self.ttl_seconds) + + def _make_tmdb_request(self, endpoint: str, params: Optional[Dict[str, Any]] = None) -> Optional[Dict[str, Any]]: """Make a request to TMDB API""" @@ -56,8 +60,10 @@ class TMDBExtractor: # Check cache first cached = self._get_cached_data(cache_key) if cached is not None: + logging.info(f"TMDB cache hit for search: {title} ({year})") return cached + logging.info(f"TMDB cache miss for search: {title} ({year}), making request") params = {'query': title} if year: params['year'] = year @@ -95,8 +101,10 @@ class TMDBExtractor: # Check cache first cached = self._get_cached_data(cache_key) if cached is not None: + logging.info(f"TMDB cache hit for movie details: {movie_id}") return cached + logging.info(f"TMDB cache miss for movie details: {movie_id}, making request") result = self._make_tmdb_request(f'/movie/{movie_id}') if result: # Cache the result diff --git a/renamer/formatters/formatter.py b/renamer/formatters/formatter.py index 68bb78f..8f65e61 100644 --- a/renamer/formatters/formatter.py +++ b/renamer/formatters/formatter.py @@ -74,18 +74,6 @@ class FormatterApplier: # Sort formatters according to the global order ordered_formatters = sorted(formatters, key=lambda f: FormatterApplier.FORMATTER_ORDER.index(f) if f in FormatterApplier.FORMATTER_ORDER else len(FormatterApplier.FORMATTER_ORDER)) - # Get caller info - frame = inspect.currentframe() - if frame and frame.f_back: - caller = f"{frame.f_back.f_code.co_filename}:{frame.f_back.f_lineno} in {frame.f_back.f_code.co_name}" - else: - caller = "Unknown" - - logging.info(f"Caller: {caller}") - logging.info(f"Original formatters: {[f.__name__ if hasattr(f, '__name__') else str(f) for f in formatters]}") - logging.info(f"Ordered formatters: {[f.__name__ if hasattr(f, '__name__') else str(f) for f in ordered_formatters]}") - logging.info(f"Input value: {repr(value)}") - # Apply in the ordered sequence for formatter in ordered_formatters: try: @@ -96,7 +84,6 @@ class FormatterApplier: logging.error(f"Error applying {formatter.__name__ if hasattr(formatter, '__name__') else str(formatter)}: {e}") value = "Unknown" - logging.info(f"Final value: {repr(value)}") return value @staticmethod diff --git a/renamer/settings.py b/renamer/settings.py index f21a86c..fb2840e 100644 --- a/renamer/settings.py +++ b/renamer/settings.py @@ -51,9 +51,9 @@ class Settings: except IOError as e: print(f"Error: Could not save settings: {e}") - def get(self, key: str) -> Any: + def get(self, key: str, default: Any = None) -> Any: """Get a setting value.""" - return self._settings.get(key, self.DEFAULTS.get(key)) + return self._settings.get(key, self.DEFAULTS.get(key, default)) def set(self, key: str, value: Any) -> None: """Set a setting value and save.""" diff --git a/renamer/test/filenames/[04] Ice Age: Continental Drift (2012) BDRip [1080p,ukr,eng] [tmdbid-57800].mkv b/renamer/test/filenames/[04] Ice Age: Continental Drift (2012) BDRip [1080p,ukr,eng] [tmdbid-57800].mkv new file mode 100644 index 0000000..e69de29 diff --git a/uv.lock b/uv.lock index 88f1035..664bed1 100644 --- a/uv.lock +++ b/uv.lock @@ -342,7 +342,7 @@ wheels = [ [[package]] name = "renamer" -version = "0.5.9" +version = "0.5.10" source = { editable = "." } dependencies = [ { name = "langcodes" },