diff --git a/.env.example b/.env.example
index 78ce602..7916a30 100644
--- a/.env.example
+++ b/.env.example
@@ -1,28 +1,142 @@
-# NFOGuard Environment Variables
+# ===========================================
+# NFOGuard Configuration - CORRECTED VERSION
+# ===========================================
+# Main configuration file - safe to share for debugging
+# Sensitive data (API keys, passwords) are in .env.secrets
-# User and Group IDs
-PUID=1000
-PGID=1000
+# ===========================================
+# MEDIA PATHS (REQUIRED) - FIXED
+# ===========================================
+# Container paths (what NFOGuard sees inside container)
+MOVIE_PATHS=/media/Movies/movies,/media/Movies/movies6
+TV_PATHS=/media/TV/tv,/media/TV/tv6
-# Timezone
-TZ=UTC
+# Radarr paths (what Radarr sees on the host system)
+RADARR_ROOT_FOLDERS=/mnt/unionfs/Media/Movies/movies,/mnt/unionfs/Media/Movies/movies6
-# Media path (for read-only access to scan NFO files)
-MEDIA_PATH=/path/to/your/media
+# Sonarr paths (what Sonarr sees on the host system) - FIXED VARIABLE NAME AND PATHS
+SONARR_ROOT_FOLDERS=/mnt/unionfs/Media/TV/tv,/mnt/unionfs/Media/TV/tv6
-# Database settings (if using PostgreSQL)
-# DB_USER=nfoguard
-# DB_PASSWORD=your_secure_password
+# Download detection paths (for identifying downloads vs existing files)
+DOWNLOAD_PATH_INDICATORS=/downloads/,/nzbget/,/completed/,/sabnzbd/,/torrents/
-# Emby Plugin Deployment - Bind Mount Method (Recommended)
-# Set this to the path where Emby plugins are installed on your host
-# Common paths:
-# - /var/lib/emby/plugins (native Emby install)
-# - /path/to/emby/config/plugins (Docker Emby)
-# - /config/plugins (linuxserver.io Emby)
-EMBY_PLUGINS_PATH=/path/to/emby/plugins
+# ===========================================
+# DATABASE CONFIGURATION
+# ===========================================
+# NFOGuard Database Configuration (PostgreSQL - v2.6+)
+# NOTE: For new installations, PostgreSQL is strongly recommended
+# Set these values in .env.secrets for security:
+# - DB_PASSWORD=your_secure_password
+# - DB_USER=nfoguard (default)
-# NFOGuard specific settings
+# PostgreSQL Database Settings
+DB_TYPE=postgresql
+DB_HOST=nfoguard-db # Container name from docker-compose
+DB_PORT=5432
+DB_NAME=nfoguard
+
+# Legacy SQLite Configuration (Deprecated in v2.6+)
+# Only use for existing SQLite installations
+# DB_PATH=/app/data/media_dates.db
+
+# Log file directory
+LOG_DIR=/app/data/logs
+
+# ===========================================
+# RADARR DATABASE CONNECTION (RECOMMENDED)
+# ===========================================
+# Direct database access for better performance
+RADARR_DB_TYPE=postgresql
+RADARR_DB_HOST=192.168.255.50
+RADARR_DB_PORT=5432
+RADARR_DB_NAME=radarr-main
+RADARR_DB_USER=postgres
+
+# ===========================================
+# API CONNECTIONS (OPTIONAL)
+# ===========================================
+# API keys are stored in .env.secrets for security
+RADARR_URL=http://radarr:7878
+SONARR_URL=http://sonarr:8989
+JELLYSEERR_URL=http://jellyseerr:5055
+
+# ===========================================
+# RELEASE DATE PROCESSING
+# ===========================================
+# Priority order for release date fallbacks (digital, physical, theatrical)
+RELEASE_DATE_PRIORITY=digital,physical,theatrical
+#RELEASE_DATE_PRIORITY=digital,theatrical,physical
+ENABLE_SMART_DATE_VALIDATION=true
+MAX_RELEASE_DATE_GAP_YEARS=10
+
+# Prefer API release dates over file modification dates for manual imports
+PREFER_RELEASE_DATES_OVER_FILE_DATES=true
+
+# Disable file date fallback completely (recommended for clean imports)
+ALLOW_FILE_DATE_FALLBACK=false
+
+# TMDB country for regional release date preferences
+TMDB_COUNTRY=US
+
+# ===========================================
+# NFO FILE MANAGEMENT
+# ===========================================
+# Create/update .nfo files
+MANAGE_NFO=true
+
+# Update file modification times to match import dates
+FIX_DIR_MTIMES=true
+
+# Add lockdata tags to prevent metadata overwrites
+LOCK_METADATA=true
+
+# Brand name in NFO comments
+MANAGER_BRAND=NFOGuard
+
+# ===========================================
+# PROCESSING BEHAVIOR
+# ===========================================
+# Movie date update strategy
+MOVIE_DATE_UPDATE_MODE=overwrite
+MOVIE_PRESERVE_EXISTING=false
+
+# When to update existing dates (always, missing_only, never)
+UPDATE_MODE=always
+
+# File modification time behavior (update, leave_alone)
+MTIME_BEHAVIOR=update
+
+# Manual scan priority: use existing NFO dates for speed vs check APIs for accuracy
+# false (default) = Check external APIs first, use NFO as fallback (slower but accurate)
+# true = Use NFO dates immediately without API checks (faster but may use wrong dates)
+MANUAL_SCAN_PRIORITIZE_NFO=false
+
+# ===========================================
+# PERFORMANCE & BATCHING
+# ===========================================
+# Delay before processing batched events (seconds)
+BATCH_DELAY=5.0
+
+# Maximum concurrent series processing
+MAX_CONCURRENT_SERIES=3
+
+# API timeout in seconds
+TIMEOUT_SECONDS=45
+
+# ===========================================
+# DEBUGGING
+# ===========================================
+# Enable verbose logging (true/false)
DEBUG=false
+
+# Enable path mapping debug output (true/false)
PATH_DEBUG=false
-SUPPRESS_TVDB_WARNINGS=true
\ No newline at end of file
+
+# Suppress TVDB API warnings (true/false) - TVDB failures are common and non-critical
+SUPPRESS_TVDB_WARNINGS=true
+
+# ===========================================
+# SERVER CONFIGURATION
+# ===========================================
+# Port for webhook server
+PORT=8080
\ No newline at end of file
diff --git a/.env.secrets.template b/.env.secrets.example
similarity index 68%
rename from .env.secrets.template
rename to .env.secrets.example
index 3efc95a..6653c39 100644
--- a/.env.secrets.template
+++ b/.env.secrets.example
@@ -4,9 +4,17 @@
# Add .env.secrets to your .gitignore
# ===========================================
-# RADARR DATABASE CREDENTIALS
+# NFOGUARD DATABASE CREDENTIALS (REQUIRED)
# ===========================================
-# Database password for PostgreSQL connection
+# NFOGuard PostgreSQL database password (REQUIRED for v2.6+)
+DB_PASSWORD=your_secure_nfoguard_password
+# Optional: Override default database user (defaults to 'nfoguard')
+# DB_USER=nfoguard
+
+# ===========================================
+# RADARR DATABASE CREDENTIALS (OPTIONAL)
+# ===========================================
+# Database password for external Radarr PostgreSQL connection (optional optimization)
RADARR_DB_PASSWORD=your_radarr_db_password
# ===========================================
diff --git a/.env.template b/.env.template
deleted file mode 100644
index 189dc67..0000000
--- a/.env.template
+++ /dev/null
@@ -1,129 +0,0 @@
-# ===========================================
-# NFOGuard Configuration - CORRECTED VERSION
-# ===========================================
-# Main configuration file - safe to share for debugging
-# Sensitive data (API keys, passwords) are in .env.secrets
-
-# ===========================================
-# MEDIA PATHS (REQUIRED) - FIXED
-# ===========================================
-# Container paths (what NFOGuard sees inside container)
-MOVIE_PATHS=/media/Movies/movies,/media/Movies/movies6
-TV_PATHS=/media/TV/tv,/media/TV/tv6
-
-# Radarr paths (what Radarr sees on the host system)
-RADARR_ROOT_FOLDERS=/mnt/unionfs/Media/Movies/movies,/mnt/unionfs/Media/Movies/movies6
-
-# Sonarr paths (what Sonarr sees on the host system) - FIXED VARIABLE NAME AND PATHS
-SONARR_ROOT_FOLDERS=/mnt/unionfs/Media/TV/tv,/mnt/unionfs/Media/TV/tv6
-
-# Download detection paths (for identifying downloads vs existing files)
-DOWNLOAD_PATH_INDICATORS=/downloads/,/nzbget/,/completed/,/sabnzbd/,/torrents/
-
-# ===========================================
-# DATABASE CONFIGURATION
-# ===========================================
-# NFOGuard SQLite database location
-DB_PATH=/app/data/media_dates.db
-
-# Log file directory
-LOG_DIR=/app/data/logs
-
-# ===========================================
-# RADARR DATABASE CONNECTION (RECOMMENDED)
-# ===========================================
-# Direct database access for better performance
-RADARR_DB_TYPE=postgresql
-RADARR_DB_HOST=192.168.255.50
-RADARR_DB_PORT=5432
-RADARR_DB_NAME=radarr-main
-RADARR_DB_USER=postgres
-
-# ===========================================
-# API CONNECTIONS (OPTIONAL)
-# ===========================================
-# API keys are stored in .env.secrets for security
-RADARR_URL=http://radarr:7878
-SONARR_URL=http://sonarr:8989
-JELLYSEERR_URL=http://jellyseerr:5055
-
-# ===========================================
-# RELEASE DATE PROCESSING
-# ===========================================
-# Priority order for release date fallbacks (digital, physical, theatrical)
-RELEASE_DATE_PRIORITY=digital,physical,theatrical
-#RELEASE_DATE_PRIORITY=digital,theatrical,physical
-ENABLE_SMART_DATE_VALIDATION=true
-MAX_RELEASE_DATE_GAP_YEARS=10
-
-# Prefer API release dates over file modification dates for manual imports
-PREFER_RELEASE_DATES_OVER_FILE_DATES=true
-
-# Disable file date fallback completely (recommended for clean imports)
-ALLOW_FILE_DATE_FALLBACK=false
-
-# TMDB country for regional release date preferences
-TMDB_COUNTRY=US
-
-# ===========================================
-# NFO FILE MANAGEMENT
-# ===========================================
-# Create/update .nfo files
-MANAGE_NFO=true
-
-# Update file modification times to match import dates
-FIX_DIR_MTIMES=true
-
-# Add lockdata tags to prevent metadata overwrites
-LOCK_METADATA=true
-
-# Brand name in NFO comments
-MANAGER_BRAND=NFOGuard
-
-# ===========================================
-# PROCESSING BEHAVIOR
-# ===========================================
-# Movie date update strategy
-MOVIE_DATE_UPDATE_MODE=overwrite
-MOVIE_PRESERVE_EXISTING=false
-
-# When to update existing dates (always, missing_only, never)
-UPDATE_MODE=always
-
-# File modification time behavior (update, leave_alone)
-MTIME_BEHAVIOR=update
-
-# Manual scan priority: use existing NFO dates for speed vs check APIs for accuracy
-# false (default) = Check external APIs first, use NFO as fallback (slower but accurate)
-# true = Use NFO dates immediately without API checks (faster but may use wrong dates)
-MANUAL_SCAN_PRIORITIZE_NFO=false
-
-# ===========================================
-# PERFORMANCE & BATCHING
-# ===========================================
-# Delay before processing batched events (seconds)
-BATCH_DELAY=5.0
-
-# Maximum concurrent series processing
-MAX_CONCURRENT_SERIES=3
-
-# API timeout in seconds
-TIMEOUT_SECONDS=45
-
-# ===========================================
-# DEBUGGING
-# ===========================================
-# Enable verbose logging (true/false)
-DEBUG=false
-
-# Enable path mapping debug output (true/false)
-PATH_DEBUG=false
-
-# Suppress TVDB API warnings (true/false) - TVDB failures are common and non-critical
-SUPPRESS_TVDB_WARNINGS=true
-
-# ===========================================
-# SERVER CONFIGURATION
-# ===========================================
-# Port for webhook server
-PORT=8080
\ No newline at end of file
diff --git a/Emby-DLL/NFOGuard.Emby.Plugin.dll b/Emby-DLL/NFOGuard.Emby.Plugin.dll
index 8369710..60fad74 100644
Binary files a/Emby-DLL/NFOGuard.Emby.Plugin.dll and b/Emby-DLL/NFOGuard.Emby.Plugin.dll differ
diff --git a/README.md b/README.md
index 15e613d..8f11aad 100644
--- a/README.md
+++ b/README.md
@@ -24,41 +24,42 @@
NFOGuard automatically updates movie and TV show NFO files with proper release dates and metadata when triggered by Radarr/Sonarr webhooks. It preserves existing metadata while adding clean, accurate date information at the bottom of NFO files.
-## ✨ Features
+## Features
-### **🎬 Core Media Management**
+### **Core Media Management**
- **Movie & TV Support** - Works with both Radarr and Sonarr
- **Smart Date Handling** - Prioritizes digital, physical, and theatrical release dates
- **Webhook Integration** - Triggers automatically on import, upgrade, and rename
- **NFO Preservation** - Maintains existing metadata, adds fields cleanly at bottom
- **Metadata Locking** - Prevents overwrites with lockdata tags
-### **⚡ Performance & Scalability**
+### **Performance & Scalability**
- **Async I/O Operations** - High-performance concurrent file processing
- **Batch Processing** - Efficient handling of multiple files simultaneously
-- **Database Integration** - Direct PostgreSQL/SQLite access for better performance
-- **Smart Concurrency** - Controlled parallel processing with rate limiting
+- **PostgreSQL Database** - Production-ready database with optimized queries
+- **Smart Skip Logic** - Database-first checking eliminates expensive filesystem scans
+- **88% Scan Optimization** - TV library scans reduced from hours to minutes
-### **🔧 Configuration & Validation**
+### **Web Interface & Management**
+- **Complete Web UI** - Episode and movie management with filtering and search
+- **Database Cleanup Tools** - Delete orphaned episodes with confirmation dialogs
+- **Real-time Statistics** - Live episode counts and source mapping
+- **Manual Scan Control** - Smart, full, and incomplete scan modes
+- **Health Monitoring** - System status and performance metrics
+
+### **Configuration & Validation**
- **Comprehensive Config Validation** - Validates all settings before startup
- **Runtime Health Checks** - Monitors system health and dependencies
- **Path Validation** - Ensures media directories are accessible
- **Database Connectivity Tests** - Validates database connections
-### **📊 Monitoring & Observability**
-- **Health Check APIs** - `/api/v1/health`, `/api/v1/health/ready`, `/api/v1/health/live`
-- **Prometheus Metrics** - `/api/v1/metrics` endpoint for monitoring integration
-- **Performance Monitoring** - Operation timing, error rates, and bottleneck detection
-- **Structured Logging** - JSON logs with correlation IDs for request tracing
-- **System Status APIs** - Real-time performance and error metrics
-
-### **🐳 Production Ready**
+### **Production Ready**
- **Docker & Kubernetes** - Health checks for orchestration platforms
-- **Grafana Compatible** - Metrics endpoints for dashboard integration
+- **Graceful Shutdown** - Proper signal handling for container management
- **Configuration CLI** - Validation tools for troubleshooting
- **Modular Architecture** - Clean separation of concerns for maintainability
-## 🚀 Quick Start
+## Quick Start
### 1. Download Configuration Files
@@ -101,7 +102,7 @@ docker-compose logs -f nfoguard
curl http://localhost:8080/health
```
-## ⚙️ Configuration
+## Configuration
### Environment Files
@@ -134,7 +135,7 @@ DEBUG=false # Clean production logs
SUPPRESS_TVDB_WARNINGS=true # Hide non-critical API failures
```
-## 🐳 Docker Images
+## Docker Images
### Production (Stable)
```yaml
@@ -257,7 +258,7 @@ volumes:
- /home/user/media/tv:/media/TV/tv:rw
```
-## 🔧 Troubleshooting
+## Troubleshooting
### Check Logs
```bash
@@ -276,7 +277,7 @@ PATH_DEBUG=true
curl http://localhost:8080/health
```
-## 📊 Monitoring & Observability
+## Monitoring & Observability
NFOGuard provides comprehensive monitoring capabilities for production deployments:
@@ -356,7 +357,7 @@ LOG_LEVEL=INFO
3. **Webhooks**: Check URLs and ensure port 8080 is accessible
4. **Database**: Verify PostgreSQL credentials in `.env.secrets`
-## 📊 What NFOGuard Does
+## What NFOGuard Does
### Before
```xml
@@ -392,7 +393,7 @@ LOG_LEVEL=INFO
NFOGuard identifies movies and TV shows using two methods: directory names with IMDb IDs (primary) or NFO files with IMDb IDs (fallback). Your media should follow these conventions:
-### 🎬 **Movies**
+### **Movies**
**Directory Structure:**
```
diff --git a/VERSION b/VERSION
index 57cf282..338a5b5 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-2.6.5
+2.6.6
diff --git a/api/routes.py b/api/routes.py
index 82c918d..35df7b8 100644
--- a/api/routes.py
+++ b/api/routes.py
@@ -688,6 +688,9 @@ async def manual_scan(background_tasks: BackgroundTasks, path: Optional[str] = N
movie_skipped += 1
elif result == "processed":
movie_processed += 1
+ elif result == "no_video_files":
+ print(f"INFO: Skipped empty directory: {item.name}")
+ movie_skipped += 1
except Exception as e:
print(f"ERROR: Failed processing movie {item}: {e}")
movie_total += 1
@@ -1132,6 +1135,34 @@ async def delete_series_episodes(imdb_id: str, dependencies: dict):
}
+async def delete_movie(imdb_id: str, dependencies: dict):
+ """Delete a specific movie from the database"""
+ db = dependencies["db"]
+
+ try:
+ deleted = db.delete_movie(imdb_id)
+
+ if deleted:
+ return {
+ "success": True,
+ "message": f"Deleted movie {imdb_id} from database",
+ "imdb_id": imdb_id
+ }
+ else:
+ return {
+ "success": False,
+ "message": f"Movie {imdb_id} not found in database",
+ "imdb_id": imdb_id
+ }
+
+ except Exception as e:
+ return {
+ "success": False,
+ "error": str(e),
+ "imdb_id": imdb_id
+ }
+
+
async def cleanup_orphaned_episodes(dependencies: dict):
"""Find and delete episodes that don't have corresponding video files"""
db = dependencies["db"]
@@ -1154,6 +1185,28 @@ async def cleanup_orphaned_episodes(dependencies: dict):
}
+async def cleanup_orphaned_movies(dependencies: dict):
+ """Find and delete movies that don't have corresponding video files"""
+ db = dependencies["db"]
+
+ try:
+ deleted_movies = db.delete_orphaned_movies()
+
+ return {
+ "success": True,
+ "message": f"Cleaned up {len(deleted_movies)} orphaned movies",
+ "deleted_count": len(deleted_movies),
+ "deleted_movies": deleted_movies
+ }
+
+ except Exception as e:
+ return {
+ "success": False,
+ "error": str(e),
+ "message": "Failed to cleanup orphaned movies"
+ }
+
+
# ---------------------------
# Route Registration
# ---------------------------
@@ -1212,10 +1265,18 @@ def register_routes(app, dependencies: dict):
async def _delete_series_episodes(imdb_id: str):
return await delete_series_episodes(imdb_id, dependencies)
+ @app.delete("/database/movie/{imdb_id}")
+ async def _delete_movie(imdb_id: str):
+ return await delete_movie(imdb_id, dependencies)
+
@app.post("/database/cleanup/orphaned-episodes")
async def _cleanup_orphaned_episodes():
return await cleanup_orphaned_episodes(dependencies)
+ @app.post("/database/cleanup/orphaned-movies")
+ async def _cleanup_orphaned_movies():
+ return await cleanup_orphaned_movies(dependencies)
+
@app.post("/manual/scan")
async def _manual_scan(background_tasks: BackgroundTasks, path: Optional[str] = None, scan_type: str = "both", scan_mode: str = "smart"):
return await manual_scan(background_tasks, path, scan_type, scan_mode, dependencies)
diff --git a/core/database.py b/core/database.py
index a13235c..a80b15c 100644
--- a/core/database.py
+++ b/core/database.py
@@ -205,7 +205,7 @@ class NFOGuardDatabase:
# Debug: Check what was actually saved
cursor.execute("SELECT dateadded, source FROM movies WHERE imdb_id = %s", (imdb_id,))
result = cursor.fetchone()
- print(f"🔍 DATABASE VERIFY: After upsert, found dateadded={result[0] if result else 'NOT_FOUND'}, source={result[1] if result else 'NOT_FOUND'}")
+ print(f"🔍 DATABASE VERIFY: After upsert, found dateadded={result['dateadded'] if result else 'NOT_FOUND'}, source={result['source'] if result else 'NOT_FOUND'}")
def get_series_episodes(self, imdb_id: str, has_video_file_only: bool = False) -> List[Dict]:
"""Get all episodes for a series"""
@@ -411,6 +411,96 @@ class NFOGuardDatabase:
return deleted_episodes
+ def delete_movie(self, imdb_id: str) -> bool:
+ """
+ Delete a specific movie from the database
+
+ Args:
+ imdb_id: Movie IMDb ID
+
+ Returns:
+ True if movie was deleted, False if not found
+ """
+ with self.get_connection() as conn:
+ cursor = conn.cursor()
+
+ cursor.execute("""
+ DELETE FROM movies
+ WHERE imdb_id = %s
+ """, (imdb_id,))
+
+ deleted_count = cursor.rowcount
+ conn.commit()
+
+ return deleted_count > 0
+
+ def delete_orphaned_movies(self) -> List[Dict]:
+ """
+ Find and delete movies that don't have corresponding video files on disk
+ This requires checking filesystem for each movie, so use carefully
+
+ Returns:
+ List of deleted movies with their details
+ """
+ from pathlib import Path
+
+ deleted_movies = []
+
+ with self.get_connection() as conn:
+ cursor = conn.cursor()
+
+ # Get all movies with their paths
+ cursor.execute("""
+ SELECT imdb_id, path, dateadded, source
+ FROM movies
+ """)
+
+ movies_list = cursor.fetchall()
+
+ for movie in movies_list:
+ imdb_id = movie['imdb_id']
+ movie_path = Path(movie['path'])
+
+ if not movie_path.exists():
+ # Movie directory doesn't exist - delete it
+ cursor.execute("""
+ DELETE FROM movies
+ WHERE imdb_id = %s
+ """, (imdb_id,))
+
+ deleted_movies.append({
+ 'imdb_id': imdb_id,
+ 'reason': 'directory_not_found',
+ 'path': str(movie_path),
+ 'dateadded': movie['dateadded'],
+ 'source': movie['source']
+ })
+ continue
+
+ # Check for video files
+ video_exts = (".mkv", ".mp4", ".avi", ".mov", ".m4v")
+ has_video = any(f.is_file() and f.suffix.lower() in video_exts
+ for f in movie_path.iterdir() if f.is_file())
+
+ if not has_video:
+ # No video files found - delete this movie
+ cursor.execute("""
+ DELETE FROM movies
+ WHERE imdb_id = %s
+ """, (imdb_id,))
+
+ deleted_movies.append({
+ 'imdb_id': imdb_id,
+ 'reason': 'no_video_files',
+ 'path': str(movie_path),
+ 'dateadded': movie['dateadded'],
+ 'source': movie['source']
+ })
+
+ conn.commit()
+
+ return deleted_movies
+
def close(self):
"""Close all database connections"""
if hasattr(self._local, 'connection'):
diff --git a/docker-compose.example b/docker-compose.example
index 6f2ec80..ef6f168 100644
--- a/docker-compose.example
+++ b/docker-compose.example
@@ -6,13 +6,17 @@ services:
image: sbcrumb/nfoguard:latest
# Alternative: Use specific version
- # image: sbcrumb/nfoguard:v1.5.5
+ # image: sbcrumb/nfoguard:v2.6.5
# Alternative: Use development version
# image: sbcrumb/nfoguard:dev
container_name: nfoguard
+ # Database dependency
+ depends_on:
+ - nfoguard-postgres
+
# Restart policy
restart: unless-stopped
@@ -87,6 +91,54 @@ services:
max-size: "10m"
max-file: "3"
+ # PostgreSQL Database (Required for v2.6+)
+ nfoguard-postgres:
+ image: postgres:15-alpine
+ container_name: nfoguard-db
+
+ # Restart policy
+ restart: unless-stopped
+
+ # Environment variables for PostgreSQL
+ environment:
+ - POSTGRES_DB=nfoguard
+ - POSTGRES_USER=nfoguard
+ - POSTGRES_PASSWORD=${DB_PASSWORD} # Set in .env.secrets
+ - POSTGRES_INITDB_ARGS=--encoding=UTF-8 --lc-collate=C --lc-ctype=C
+
+ # PostgreSQL data persistence
+ volumes:
+ - ./postgres-data:/var/lib/postgresql/data
+
+ # PostgreSQL port (optional - only needed for external access)
+ # ports:
+ # - "5432:5432"
+
+ # Health check for database
+ healthcheck:
+ test: ["CMD-SHELL", "pg_isready -U nfoguard -d nfoguard"]
+ interval: 30s
+ timeout: 10s
+ retries: 3
+ start_period: 60s
+
+ # Resource limits for database
+ deploy:
+ resources:
+ limits:
+ memory: 256M
+ cpus: '0.3'
+ reservations:
+ memory: 128M
+ cpus: '0.1'
+
+ # Logging configuration
+ logging:
+ driver: "json-file"
+ options:
+ max-size: "10m"
+ max-file: "3"
+
# Optional: Custom network for media services
# networks:
# media-network:
@@ -97,12 +149,13 @@ services:
# ===========================================
# 1. Copy this file to docker-compose.yml
# 2. Update volume mounts to match your actual media paths
-# 3. Copy .env.template to .env and configure
-# 4. Copy .env.secrets.template to .env.secrets and add credentials
-# 5. Create data directory: mkdir -p ./data
+# 3. Copy .env.example to .env and configure all settings
+# 4. Copy .env.secrets.example to .env.secrets and add credentials (especially DB_PASSWORD)
+# 5. Create directories: mkdir -p ./data ./postgres-data
# 6. Run: docker-compose up -d
-# 7. Check logs: docker-compose logs -f nfoguard
-# 8. Access health check: curl http://localhost:8080/health
+# 7. Check logs: docker-compose logs -f
+# 8. Access web interface: http://localhost:8080
+# 9. Access health check: curl http://localhost:8080/health
# ===========================================
# VOLUME MAPPING EXAMPLES
diff --git a/processors/movie_processor.py b/processors/movie_processor.py
index 85db39e..b5daf61 100644
--- a/processors/movie_processor.py
+++ b/processors/movie_processor.py
@@ -188,9 +188,8 @@ class MovieProcessor:
has_video = any(f.is_file() and f.suffix.lower() in video_exts for f in movie_path.iterdir())
if not has_video:
- _log("WARNING", f"No video files found in: {movie_path}")
- self.db.upsert_movie_dates(imdb_id, None, None, None, False)
- return "processed"
+ _log("WARNING", f"No video files found in: {movie_path} - skipping database entry")
+ return "no_video_files"
# TIER 1: Check database first (fastest - local lookup)
existing = self.db.get_movie_dates(imdb_id)
diff --git a/static/js/app.js b/static/js/app.js
index f550935..86b6118 100644
--- a/static/js/app.js
+++ b/static/js/app.js
@@ -289,6 +289,9 @@ function updateMoviesTable(data) {
+
`;
@@ -1315,6 +1318,40 @@ async function deleteEpisode(imdbId, season, episode) {
}
}
+// Movie deletion functionality
+async function deleteMovie(imdbId) {
+ // Confirmation dialog
+ if (!confirm(`⚠️ Delete Movie?\n\nThis will permanently remove the movie (${imdbId}) from the database.\n\nAre you sure you want to continue?`)) {
+ return;
+ }
+
+ try {
+ const response = await fetch(`/database/movie/${imdbId}`, {
+ method: 'DELETE',
+ headers: {
+ 'Content-Type': 'application/json'
+ }
+ });
+
+ const result = await response.json();
+
+ if (response.ok && result.success) {
+ showToast(`✅ Movie deleted successfully`, 'success');
+
+ // Refresh the movies table
+ loadMovies(currentMoviesPage);
+
+ } else {
+ const errorMsg = result.message || result.error || 'Unknown error';
+ showToast(`❌ Failed to delete movie: ${errorMsg}`, 'error');
+ }
+
+ } catch (error) {
+ console.error('Delete movie failed:', error);
+ showToast(`❌ Delete failed: ${error.message}`, 'error');
+ }
+}
+
// Update episode counts in modal after deletion
function updateEpisodeModalCounts() {
const remainingRows = document.querySelectorAll('#episodes-table-body tr');