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
29 changes: 29 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,35 @@

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

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

### Added

- **Issue #188: Drop-in Python plugin system** - New plugin loader that discovers and loads
Python plugins from a configurable directory (`/data/plugins` for Docker). Plugins extend
a simple `BasePlugin` class with `setup()`, `can_process()`, `process()`, and `teardown()`
methods. The loader handles manifest validation, dynamic module importing via `importlib`,
exception isolation (bad plugins never crash the app), timeout enforcement via
`ThreadPoolExecutor`, and deep-copying book data before passing to plugins. Each plugin is
wrapped in a `PluginAdapter` that implements the `LayerAdapter` interface, making plugins
fully compatible with the modular pipeline orchestrator. Plugins are registered in the
`LayerRegistry` and tracked by the existing health dashboard with auto-disable circuit
breaker support.

- **Plugin manifest system** - Each plugin requires a `manifest.json` with metadata (id,
name, version, description), entry point configuration, ordering, and dependency
declarations (required config keys and secrets). Manifests are strictly validated on
discovery -- invalid plugins are logged as warnings and skipped.

- **Plugin configuration** - New `plugin_dir` config key (default: `/data/plugins`) and
`plugin_configs` dict for per-plugin configuration overrides. Plugin-specific secrets are
read from `secrets.json`.

- **Example plugin** - Template plugin at `test-env/example-plugin/` demonstrating the
`BasePlugin` interface with manifest.json and a simple logging implementation.

---

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

### Added
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.141-blue.svg)](CHANGELOG.md)
[![Version](https://img.shields.io/badge/version-0.9.0--beta.142-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
15 changes: 14 additions & 1 deletion 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.141"
APP_VERSION = "0.9.0-beta.142"
GITHUB_REPO = "deucebucket/library-manager" # Your GitHub repo

# Versioning Guide:
Expand Down Expand Up @@ -122,6 +122,7 @@
from library_manager.hints import get_all_hints
from library_manager.hooks import hooks_bp, run_hooks, build_hook_context
from library_manager.plugins import plugins_bp
from library_manager.plugin_loader import register_plugins, teardown_plugins

# Try to import P2P cache (optional - gracefully degrades if not available)
try:
Expand Down Expand Up @@ -736,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 @@ -760,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 @@ -1715,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 @@ -1724,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 @@ -1832,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 @@ -2164,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 @@ -2375,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 @@ -12142,6 +12143,18 @@
init_db()
cleanup_garbage_entries() # Remove @eaDir, #recycle, etc. from database (Issue #88)
cleanup_duplicate_history_entries() # Remove duplicate history entries (Issue #79)

# Issue #188: Load drop-in Python plugins
try:
from library_manager.pipeline.registry import default_registry
_startup_config = load_config()
_loaded_plugins = register_plugins(default_registry, _startup_config, get_db)
if _loaded_plugins:
logger.info(f"[PLUGIN] {len(_loaded_plugins)} plugin(s) loaded and registered")
except Exception as e:
logger.error(f"[PLUGIN] Failed to load plugins: {e}")
_loaded_plugins = []

start_worker()
port = int(os.environ.get('PORT', 5757))
app.run(host='0.0.0.0', port=port, debug=False)
3 changes: 3 additions & 0 deletions library_manager/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,9 @@ def _detect_data_dir():
"post_processing_hooks": [],
# Custom HTTP API layers - user-defined external API processing layers (Issue #185)
"custom_layers": [],
# Drop-in Python plugins (Issue #188)
"plugin_dir": "/data/plugins", # Directory to scan for drop-in plugins (Docker: /data/plugins)
"plugin_configs": {}, # Per-plugin config overrides: {"plugin-id": {"key": "value"}}
# Issue #110: File validation - check audio files before processing
"enable_file_validation": True, # Validate audio files with ffprobe before queueing
"min_audio_duration_seconds": 600, # Minimum duration (seconds) to consider a valid audiobook (default: 10 min)
Expand Down
Loading
Loading