add scanner metadata cache reuse and control flags
This commit is contained in:
+76
-6
@@ -30,7 +30,9 @@ def _normalize_to_utc(timestamp: datetime) -> datetime:
|
||||
def scan_library(
|
||||
root: Path,
|
||||
config: Config,
|
||||
progress_callback: Optional[Callable[[int, int], None]] = None
|
||||
progress_callback: Optional[Callable[[int, int], None]] = None,
|
||||
include_video_metadata: bool = True,
|
||||
metadata_cache: Optional[dict[str, VideoFile]] = None
|
||||
) -> list[VideoFile]:
|
||||
"""Recursively scan library for video files.
|
||||
|
||||
@@ -40,6 +42,9 @@ def scan_library(
|
||||
Args:
|
||||
root: Root directory to scan
|
||||
config: Configuration object with video extensions and settings
|
||||
progress_callback: Optional callback for progress updates
|
||||
include_video_metadata: Whether to run ffprobe metadata extraction
|
||||
metadata_cache: Optional cached inventory keyed by file path
|
||||
|
||||
Returns:
|
||||
List of VideoFile objects representing discovered files
|
||||
@@ -68,7 +73,13 @@ def scan_library(
|
||||
progress_callback(0, total_paths)
|
||||
|
||||
for index, file_path in enumerate(discovered_paths, start=1):
|
||||
video_file = _create_video_file(file_path, root, config.categories)
|
||||
video_file = _create_video_file(
|
||||
file_path,
|
||||
root,
|
||||
config.categories,
|
||||
include_video_metadata=include_video_metadata,
|
||||
metadata_cache=metadata_cache
|
||||
)
|
||||
if video_file is None:
|
||||
if progress_callback is not None:
|
||||
progress_callback(index, total_paths)
|
||||
@@ -215,7 +226,9 @@ def _is_video_file(file_path: Path, video_extensions: list[str]) -> bool:
|
||||
def _create_video_file(
|
||||
file_path: Path,
|
||||
library_root: Path,
|
||||
categories_config: dict[str, list[str]]
|
||||
categories_config: dict[str, list[str]],
|
||||
include_video_metadata: bool = True,
|
||||
metadata_cache: Optional[dict[str, VideoFile]] = None
|
||||
) -> Optional[VideoFile]:
|
||||
"""Create VideoFile object from file path.
|
||||
|
||||
@@ -226,6 +239,8 @@ def _create_video_file(
|
||||
file_path: Path to video file
|
||||
library_root: Root of the library (for categorization)
|
||||
categories_config: Mapping of category names to directory name lists
|
||||
include_video_metadata: Whether to run ffprobe metadata extraction
|
||||
metadata_cache: Optional cached inventory keyed by file path
|
||||
|
||||
Returns:
|
||||
VideoFile object or None if file cannot be accessed
|
||||
@@ -238,9 +253,28 @@ def _create_video_file(
|
||||
|
||||
# Categorize based on directory structure
|
||||
category = categorize_file(file_path, library_root, categories_config)
|
||||
|
||||
# Extract video metadata using ffprobe (optional, non-blocking)
|
||||
video_metadata = extract_metadata(file_path)
|
||||
|
||||
# Reuse cached metadata when file identity is unchanged.
|
||||
video_metadata: dict = {}
|
||||
if include_video_metadata:
|
||||
cache_key = str(file_path)
|
||||
cached_file = metadata_cache.get(cache_key) if metadata_cache else None
|
||||
file_mtime_seconds = int(modified_timestamp.timestamp())
|
||||
|
||||
if (
|
||||
cached_file is not None
|
||||
and cached_file.size_bytes == size_bytes
|
||||
and int(_normalize_to_utc(cached_file.modified_timestamp).timestamp()) == file_mtime_seconds
|
||||
):
|
||||
video_metadata = {
|
||||
'resolution': cached_file.resolution,
|
||||
'codec': cached_file.codec,
|
||||
'duration_seconds': cached_file.duration_seconds,
|
||||
'bitrate_kbps': cached_file.bitrate_kbps
|
||||
}
|
||||
else:
|
||||
# Extract video metadata using ffprobe (optional, non-blocking)
|
||||
video_metadata = extract_metadata(file_path)
|
||||
|
||||
# Create VideoFile object with optional metadata
|
||||
return VideoFile(
|
||||
@@ -260,6 +294,42 @@ def _create_video_file(
|
||||
return None
|
||||
|
||||
|
||||
def load_inventory_csv(input_path: Path) -> list[VideoFile]:
|
||||
"""Load inventory CSV into VideoFile objects."""
|
||||
from datetime import timezone
|
||||
|
||||
video_files: list[VideoFile] = []
|
||||
with open(input_path, 'r', encoding='utf-8') as csvfile:
|
||||
lines = [line for line in csvfile if not line.startswith('#')]
|
||||
reader = csv.DictReader(lines)
|
||||
|
||||
for row in reader:
|
||||
modified_timestamp = datetime.fromisoformat(row['modified_timestamp'])
|
||||
if modified_timestamp.tzinfo is None:
|
||||
modified_timestamp = modified_timestamp.replace(tzinfo=timezone.utc)
|
||||
|
||||
resolution = row.get('resolution') or None
|
||||
codec = row.get('codec') or None
|
||||
duration_seconds = float(row['duration_seconds']) if row.get('duration_seconds') else None
|
||||
bitrate_kbps = int(row['bitrate_kbps']) if row.get('bitrate_kbps') else None
|
||||
|
||||
video_files.append(
|
||||
VideoFile(
|
||||
path=Path(row['path']),
|
||||
filename=row['filename'],
|
||||
size_bytes=int(row['size_bytes']),
|
||||
modified_timestamp=modified_timestamp,
|
||||
category=row['category'],
|
||||
resolution=resolution,
|
||||
codec=codec,
|
||||
duration_seconds=duration_seconds,
|
||||
bitrate_kbps=bitrate_kbps
|
||||
)
|
||||
)
|
||||
|
||||
return video_files
|
||||
|
||||
|
||||
def categorize_file(
|
||||
file_path: Path,
|
||||
library_root: Path,
|
||||
|
||||
Reference in New Issue
Block a user