update: tasks
Local Docker Build (Dev) / build-dev (push) Successful in 5s

This commit is contained in:
2025-10-29 09:13:18 -04:00
parent 4b71b0e7ee
commit 6e491fb091
2 changed files with 218 additions and 4 deletions
+216 -2
View File
@@ -2288,10 +2288,188 @@ async def lookup_episode(imdb_id: str, season: int, episode: int, dependencies:
} }
except Exception as e: except Exception as e:
print(f"ERROR: Episode lookup failed for {imdb_id} S{season:02d}E{episode:02d}: {e}") _log("ERROR", f"Episode lookup failed for {imdb_id} S{season:02d}E{episode:02d}: {e}")
raise HTTPException(status_code=500, detail=f"Episode lookup failed: {str(e)}") raise HTTPException(status_code=500, detail=f"Episode lookup failed: {str(e)}")
def extract_title_and_year_from_path(nfo_path: str) -> tuple:
"""
Extract movie title and year from NFO file path
Examples:
- "/path/Scream (1996)/movie.nfo" -> ("Scream", "1996")
- "/path/Witchboard (2025)/movie.nfo" -> ("Witchboard", "2025")
- "/path/The Conjuring 2 (2016) [tt3065204]/movie.nfo" -> ("The Conjuring 2", "2016")
"""
import re
from pathlib import Path
# Get the directory name (movie folder)
dir_name = Path(nfo_path).parent.name
# Pattern to extract title and year: "Title (YYYY)"
pattern = r'^(.+?)\s*\((\d{4})\)'
match = re.search(pattern, dir_name)
if match:
title = match.group(1).strip()
year = match.group(2)
return title, year
# Fallback: try to find year anywhere in the path
year_pattern = r'\((\d{4})\)'
year_match = re.search(year_pattern, dir_name)
year = year_match.group(1) if year_match else None
# Remove any [imdb-*] or [tt*] patterns and year patterns
clean_title = re.sub(r'\s*\[\s*(?:imdb-)?tt\d+\s*\]', '', dir_name)
clean_title = re.sub(r'\s*\(\d{4}\)', '', clean_title)
clean_title = clean_title.strip()
return clean_title, year
async def lookup_movie_comprehensive(nfo_path: str, dependencies: dict):
"""
Comprehensive movie lookup that tries multiple methods:
1. Extract IMDb ID from path/NFO -> lookup by IMDb
2. Extract title/year from path -> lookup by title
3. Extract title only -> fuzzy lookup
"""
try:
_log("DEBUG", f"Comprehensive movie lookup for: {nfo_path}")
# First try to extract IMDb ID from path
from utils.nfo_patterns import extract_imdb_id_from_text
imdb_id = extract_imdb_id_from_text(nfo_path)
if imdb_id:
_log("DEBUG", f"Found IMDb ID in path: {imdb_id}")
result = await lookup_movie(imdb_id, dependencies)
if result.get("found"):
result["lookup_method"] = "imdb_from_path"
return result
# If IMDb lookup failed, try title-based lookup
title, year = extract_title_and_year_from_path(nfo_path)
_log("DEBUG", f"Extracted title: '{title}', year: '{year}' from path")
if title:
result = await lookup_movie_by_title(title, year, dependencies)
if result.get("found"):
return result
# All methods failed
return {
"found": False,
"nfo_path": nfo_path,
"extracted_title": title,
"extracted_year": year,
"extracted_imdb": imdb_id,
"lookup_method": "comprehensive_failed"
}
except Exception as e:
_log("ERROR", f"Comprehensive movie lookup failed for {nfo_path}: {e}")
return {
"found": False,
"error": str(e),
"lookup_method": "comprehensive_error"
}
async def lookup_movie_by_title(title: str, year: str, dependencies: dict):
"""
Lookup movie by title and year when IMDb ID is not available
This is a fallback for when Radarr overwrites NFO files and IMDb IDs are lost.
"""
try:
db = dependencies.get("db")
if not db:
raise HTTPException(status_code=500, detail="Database not available")
_log("DEBUG", f"Movie lookup by title: '{title}' year: '{year}'")
# Clean up the title (remove common brackets, extra spaces)
clean_title = title.strip()
# Search database by title pattern matching
with db.get_connection() as conn:
cursor = conn.cursor()
# Try exact title match first
if year:
cursor.execute("""
SELECT * FROM movies
WHERE LOWER(title) = LOWER(%s) AND released::text LIKE %s
ORDER BY dateadded DESC
LIMIT 1
""", (clean_title, f"{year}%"))
else:
cursor.execute("""
SELECT * FROM movies
WHERE LOWER(title) = LOWER(%s)
ORDER BY dateadded DESC
LIMIT 1
""", (clean_title,))
row = cursor.fetchone()
# If exact match fails, try fuzzy matching
if not row:
_log("DEBUG", f"Exact match failed, trying fuzzy match for '{clean_title}'")
if year:
cursor.execute("""
SELECT * FROM movies
WHERE LOWER(title) LIKE LOWER(%s) AND released::text LIKE %s
ORDER BY dateadded DESC
LIMIT 1
""", (f"%{clean_title}%", f"{year}%"))
else:
cursor.execute("""
SELECT * FROM movies
WHERE LOWER(title) LIKE LOWER(%s)
ORDER BY dateadded DESC
LIMIT 1
""", (f"%{clean_title}%",))
row = cursor.fetchone()
if row:
result = dict(row)
_log("DEBUG", f"Found movie by title search: {result}")
if result.get('dateadded'):
dateadded = result['dateadded']
if hasattr(dateadded, 'isoformat'):
dateadded_str = dateadded.isoformat()
else:
dateadded_str = str(dateadded)
return {
"found": True,
"imdb_id": result.get('imdb_id'),
"title": result.get('title'),
"dateadded": dateadded_str,
"source": result.get('source', 'database'),
"released": result.get('released') if result.get('released') else None,
"lookup_method": "title_search"
}
_log("DEBUG", f"No movie found for title '{clean_title}' year '{year}'")
return {
"found": False,
"title": clean_title,
"year": year,
"lookup_method": "title_search"
}
except Exception as e:
_log("ERROR", f"Movie title lookup failed: {e}")
raise HTTPException(status_code=500, detail=f"Movie title lookup failed: {str(e)}")
async def lookup_movie(imdb_id: str, dependencies: dict): async def lookup_movie(imdb_id: str, dependencies: dict):
""" """
Lookup movie dateadded from NFOGuard database for Emby plugin integration Lookup movie dateadded from NFOGuard database for Emby plugin integration
@@ -2304,12 +2482,26 @@ async def lookup_movie(imdb_id: str, dependencies: dict):
if not db: if not db:
raise HTTPException(status_code=500, detail="Database not available") raise HTTPException(status_code=500, detail="Database not available")
_log("DEBUG", f"Movie lookup called for IMDb ID: {imdb_id}")
# Normalize IMDb ID (ensure tt prefix) # Normalize IMDb ID (ensure tt prefix)
if not imdb_id.startswith('tt'): if not imdb_id.startswith('tt'):
imdb_id = f"tt{imdb_id}" imdb_id = f"tt{imdb_id}"
_log("DEBUG", f"Normalized IMDb ID: {imdb_id}")
# Query database for movie # Query database for movie
result = db.get_movie_dates(imdb_id) result = db.get_movie_dates(imdb_id)
_log("DEBUG", f"Movie lookup for {imdb_id}: database result = {result}")
# If not found, let's see what movies we DO have in the database
if not result:
_log("DEBUG", f"Movie {imdb_id} not found, checking what movies exist in database...")
with db.get_connection() as conn:
cursor = conn.cursor()
cursor.execute("SELECT imdb_id, title FROM movies ORDER BY dateadded DESC LIMIT 10")
recent_movies = cursor.fetchall()
_log("DEBUG", f"Recent movies in database: {[dict(row) for row in recent_movies]}")
if result and result.get('dateadded'): if result and result.get('dateadded'):
# Format response for Emby plugin # Format response for Emby plugin
@@ -2342,7 +2534,7 @@ async def lookup_movie(imdb_id: str, dependencies: dict):
} }
except Exception as e: except Exception as e:
print(f"ERROR: Movie lookup failed for {imdb_id}: {e}") _log("ERROR", f"Movie lookup failed for {imdb_id}: {e}")
raise HTTPException(status_code=500, detail=f"Movie lookup failed: {str(e)}") raise HTTPException(status_code=500, detail=f"Movie lookup failed: {str(e)}")
@@ -2488,6 +2680,28 @@ def register_routes(app, dependencies: dict):
@app.get("/api/lookup/movie/{imdb_id}") @app.get("/api/lookup/movie/{imdb_id}")
async def _lookup_movie(imdb_id: str): async def _lookup_movie(imdb_id: str):
return await lookup_movie(imdb_id, dependencies) return await lookup_movie(imdb_id, dependencies)
@app.get("/api/lookup/movie/title/{title}")
async def _lookup_movie_by_title(title: str, year: str = None):
return await lookup_movie_by_title(title, year, dependencies)
@app.post("/api/lookup/movie/path")
async def _lookup_movie_by_path(request: Request):
data = await request.json()
nfo_path = data.get("nfo_path")
if not nfo_path:
raise HTTPException(status_code=400, detail="nfo_path required")
return await lookup_movie_comprehensive(nfo_path, dependencies)
@app.get("/api/debug/movie/{title}")
async def _debug_movie_lookup(title: str):
"""Debug endpoint to check what movies exist for a given title"""
db = dependencies.get("db")
with db.get_connection() as conn:
cursor = conn.cursor()
cursor.execute("SELECT imdb_id, title, dateadded, source FROM movies WHERE LOWER(title) LIKE LOWER(%s)", (f"%{title}%",))
movies = cursor.fetchall()
return {"title_search": title, "matches": [dict(row) for row in movies]}
# Include monitoring routes # Include monitoring routes
from api.monitoring_routes import router as monitoring_router from api.monitoring_routes import router as monitoring_router
+2 -2
View File
@@ -1425,8 +1425,8 @@ def register_web_routes(app, dependencies):
# Create request with timeout (no body needed for query parameters) # Create request with timeout (no body needed for query parameters)
req = urllib.request.Request(core_url, method='POST') req = urllib.request.Request(core_url, method='POST')
# Make request with timeout # Make request with timeout (increased for manual scans which can take several minutes)
with urllib.request.urlopen(req, timeout=30) as response: with urllib.request.urlopen(req, timeout=300) as response:
response_data = response.read().decode('utf-8') response_data = response.read().decode('utf-8')
return json.loads(response_data) return json.loads(response_data)