Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,19 @@

All notable changes to Library Manager will be documented in this file.

## [0.9.0-beta.144] - 2026-04-07

### Fixed

- **Issue #201: SAFETY BLOCK error for files in library root** - Fixed path normalization
mismatch where Windows mapped drives (e.g. `R:\`) resolve to UNC paths but config paths
were compared without `.resolve()`, causing library matching to fail. Fixed fallback logic
that assumed 2-level directory structure (`parent.parent`), which went above the library
root for loose files. Applied fix across all 4 path-matching locations in `app.py`,
`layer_ai_queue.py`, and `layer_audio_credits.py`.

---

## [0.9.0-beta.143] - 2026-03-21

### Changed
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

**Smart Audiobook Library Organizer with Multi-Source Metadata & AI Verification**

[![Version](https://img.shields.io/badge/version-0.9.0--beta.143-blue.svg)](CHANGELOG.md)
[![Version](https://img.shields.io/badge/version-0.9.0--beta.144-blue.svg)](CHANGELOG.md)
[![Docker](https://img.shields.io/badge/docker-ghcr.io-blue.svg)](https://ghcr.io/deucebucket/library-manager)
[![License](https://img.shields.io/badge/license-AGPL--3.0-blue.svg)](LICENSE)

Expand Down
24 changes: 17 additions & 7 deletions app.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
- Multi-provider AI (Gemini, OpenRouter, Ollama)
"""

APP_VERSION = "0.9.0-beta.143"
APP_VERSION = "0.9.0-beta.144"
GITHUB_REPO = "deucebucket/library-manager" # Your GitHub repo

# Versioning Guide:
Expand Down Expand Up @@ -737,7 +737,7 @@
try:
with open(ERROR_REPORTS_PATH, 'r') as f:
reports = json.load(f)
except:

Check failure on line 740 in app.py

View workflow job for this annotation

GitHub Actions / lint

ruff (E722)

app.py:740:13: E722 Do not use bare `except`
reports = []

# Add new report (keep last 100 reports to avoid file bloat)
Expand All @@ -761,7 +761,7 @@
try:
with open(ERROR_REPORTS_PATH, 'r') as f:
return json.load(f)
except:

Check failure on line 764 in app.py

View workflow job for this annotation

GitHub Actions / lint

ruff (E722)

app.py:764:9: E722 Do not use bare `except`
return []
return []

Expand Down Expand Up @@ -1716,7 +1716,7 @@
continue
result = call_gemini(prompt, merged_config)
if result:
logger.info(f"[PROVIDER CHAIN] Success with gemini")

Check failure on line 1719 in app.py

View workflow job for this annotation

GitHub Actions / lint

ruff (F541)

app.py:1719:33: F541 f-string without any placeholders help: Remove extraneous `f` prefix
return result

elif provider == 'openrouter':
Expand All @@ -1725,13 +1725,13 @@
continue
result = call_openrouter(prompt, merged_config)
if result:
logger.info(f"[PROVIDER CHAIN] Success with openrouter")

Check failure on line 1728 in app.py

View workflow job for this annotation

GitHub Actions / lint

ruff (F541)

app.py:1728:33: F541 f-string without any placeholders help: Remove extraneous `f` prefix
return result

elif provider == 'ollama':
result = call_ollama(prompt, merged_config)
if result:
logger.info(f"[PROVIDER CHAIN] Success with ollama")

Check failure on line 1734 in app.py

View workflow job for this annotation

GitHub Actions / lint

ruff (F541)

app.py:1734:33: F541 f-string without any placeholders help: Remove extraneous `f` prefix
return result

else:
Expand Down Expand Up @@ -1833,7 +1833,7 @@
return result
elif result and result.get('transcript'):
# Got transcript but no match - still useful, return for potential AI fallback
logger.info(f"[AUDIO CHAIN] BookDB returned transcript only")

Check failure on line 1836 in app.py

View workflow job for this annotation

GitHub Actions / lint

ruff (F541)

app.py:1836:37: F541 f-string without any placeholders help: Remove extraneous `f` prefix
return result
elif result is None and attempt < max_retries - 1:
# Connection might be down, wait and retry
Expand Down Expand Up @@ -2165,11 +2165,11 @@
device = "cuda"
# int8 works on all CUDA devices including GTX 1080 (compute 6.1)
# float16 only works on newer GPUs (compute 7.0+)
logger.info(f"[WHISPER] Using CUDA GPU acceleration (10x faster)")

Check failure on line 2168 in app.py

View workflow job for this annotation

GitHub Actions / lint

ruff (F541)

app.py:2168:29: F541 f-string without any placeholders help: Remove extraneous `f` prefix
else:
logger.info(f"[WHISPER] Using CPU (no CUDA GPU detected)")

Check failure on line 2170 in app.py

View workflow job for this annotation

GitHub Actions / lint

ruff (F541)

app.py:2170:29: F541 f-string without any placeholders help: Remove extraneous `f` prefix
except ImportError:
logger.info(f"[WHISPER] Using CPU (ctranslate2 not available)")

Check failure on line 2172 in app.py

View workflow job for this annotation

GitHub Actions / lint

ruff (F541)

app.py:2172:25: F541 f-string without any placeholders help: Remove extraneous `f` prefix

_whisper_model = WhisperModel(model_name, device=device, compute_type=compute_type)
_whisper_model_name = model_name
Expand Down Expand Up @@ -2376,7 +2376,7 @@
if sample_path and os.path.exists(sample_path):
try:
os.unlink(sample_path)
except:

Check failure on line 2379 in app.py

View workflow job for this annotation

GitHub Actions / lint

ruff (E722)

app.py:2379:13: E722 Do not use bare `except`
pass

return result
Expand Down Expand Up @@ -11725,17 +11725,22 @@

# If not from watch folder, find which library it belongs to
if lib_path is None:
old_path_resolved = Path(old_path).resolve()
for lp in config.get('library_paths', []):
lp_path = Path(lp)
lp_path = Path(lp).resolve()
try:
Path(old_path).relative_to(lp_path)
old_path_resolved.relative_to(lp_path)
lib_path = lp_path
break
except ValueError:
continue

if lib_path is None:
lib_path = Path(old_path).parent.parent
old_p = Path(old_path)
if old_p.is_file():
lib_path = old_p.parent
else:
lib_path = old_p.parent.parent

# Detect language from title for multi-language naming
lang_code = detect_title_language(new_title) if new_title else None
Expand Down Expand Up @@ -11894,17 +11899,22 @@
return jsonify({'success': False, 'error': 'No output folder configured for watch folder items'})
else:
# Normal library item - find which library it belongs to
old_path_resolved = Path(old_path).resolve()
for lp in config.get('library_paths', []):
lp_path = Path(lp)
lp_path = Path(lp).resolve()
try:
Path(old_path).relative_to(lp_path)
old_path_resolved.relative_to(lp_path)
lib_path = lp_path
break
except ValueError:
continue

if lib_path is None:
lib_path = Path(old_path).parent.parent
old_p = Path(old_path)
if old_p.is_file():
lib_path = old_p.parent
else:
lib_path = old_p.parent.parent

# Detect language from title for multi-language naming
lang_code = detect_title_language(new_title) if new_title else None
Expand Down
12 changes: 9 additions & 3 deletions library_manager/pipeline/layer_ai_queue.py
Original file line number Diff line number Diff line change
Expand Up @@ -450,10 +450,11 @@ def process_queue(
# (Don't assume 2-level structure - series_grouping uses 3 levels)
lib_path = None
is_from_watch_folder = False
old_path_resolved = old_path.resolve()
for lp in config.get('library_paths', []):
lp_path = Path(lp)
lp_path = Path(lp).resolve()
try:
old_path.relative_to(lp_path)
old_path_resolved.relative_to(lp_path)
lib_path = lp_path
break
except ValueError:
Expand All @@ -479,7 +480,12 @@ def process_queue(

# Fallback if not found in configured libraries
if lib_path is None:
lib_path = old_path.parent.parent
# For loose files in library root, parent is the library itself
# Only go up 2 levels for normal Author/Title structure
if old_path.is_file():
lib_path = old_path.parent
else:
lib_path = old_path.parent.parent
logger.warning(f"Book path {old_path} not under any configured library, guessing lib_path={lib_path}")

# Detect language for multi-language naming
Expand Down
10 changes: 7 additions & 3 deletions library_manager/pipeline/layer_audio_credits.py
Original file line number Diff line number Diff line change
Expand Up @@ -219,10 +219,11 @@ def process_layer_3_audio(
else:
# Audio suggests different values - build new path
lib_path = None
book_path_resolved = book_path.resolve()
for lp in config.get('library_paths', []):
lp_path = Path(lp)
lp_path = Path(lp).resolve()
try:
book_path.relative_to(lp_path)
book_path_resolved.relative_to(lp_path)
lib_path = lp_path
break
except ValueError:
Expand All @@ -240,7 +241,10 @@ def process_layer_3_audio(
logger.debug(f"Watch folder path check failed: {e}")

if lib_path is None:
lib_path = book_path.parent.parent
if book_path.is_file():
lib_path = book_path.parent
else:
lib_path = book_path.parent.parent
logger.warning(f"[LAYER 3] Book path {book_path} not under any configured library, guessing lib_path={lib_path}")

# Detect language for multi-language naming
Expand Down
11 changes: 8 additions & 3 deletions library_manager/pipeline/layer_content.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,16 +113,21 @@ def process_layer_4_content(

# Build new path
lib_path = None
book_path_resolved = book_path.resolve()
for lp in config.get('library_paths', []):
lp_path = Path(lp).resolve()
try:
book_path.relative_to(Path(lp))
lib_path = Path(lp)
book_path_resolved.relative_to(lp_path)
lib_path = lp_path
break
except ValueError:
continue

if not lib_path:
lib_path = book_path.parent.parent
if book_path.is_file():
lib_path = book_path.parent
else:
lib_path = book_path.parent.parent

# Build target path with series grouping if applicable
if new_series and config.get('series_grouping', True):
Expand Down
Loading