Merge pull request 'fixes: web interface and movie processing' (#54) from dev into main
Reviewed-on: #54
This commit was merged in pull request #54.
This commit is contained in:
+133
-19
@@ -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 API warnings (true/false) - TVDB failures are common and non-critical
|
||||
SUPPRESS_TVDB_WARNINGS=true
|
||||
|
||||
# ===========================================
|
||||
# SERVER CONFIGURATION
|
||||
# ===========================================
|
||||
# Port for webhook server
|
||||
PORT=8080
|
||||
@@ -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
|
||||
|
||||
# ===========================================
|
||||
-129
@@ -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
|
||||
Binary file not shown.
@@ -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:**
|
||||
```
|
||||
|
||||
@@ -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)
|
||||
|
||||
+91
-1
@@ -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'):
|
||||
|
||||
+59
-6
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -289,6 +289,9 @@ function updateMoviesTable(data) {
|
||||
<button class="btn btn-sm btn-secondary" onclick="debugMovie('${movie.imdb_id}')" title="Debug Data">
|
||||
<i class="fas fa-bug"></i>
|
||||
</button>
|
||||
<button class="btn btn-sm btn-danger" onclick="deleteMovie('${movie.imdb_id}')" style="margin-left: 5px;" title="Delete Movie">
|
||||
<i class="fas fa-trash"></i> Delete
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
`;
|
||||
@@ -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');
|
||||
|
||||
Reference in New Issue
Block a user