This commit is contained in:
+216
-2
@@ -2288,10 +2288,188 @@ async def lookup_episode(imdb_id: str, season: int, episode: int, dependencies:
|
||||
}
|
||||
|
||||
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)}")
|
||||
|
||||
|
||||
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):
|
||||
"""
|
||||
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:
|
||||
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)
|
||||
if not imdb_id.startswith('tt'):
|
||||
imdb_id = f"tt{imdb_id}"
|
||||
|
||||
_log("DEBUG", f"Normalized IMDb ID: {imdb_id}")
|
||||
|
||||
# Query database for movie
|
||||
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'):
|
||||
# Format response for Emby plugin
|
||||
@@ -2342,7 +2534,7 @@ async def lookup_movie(imdb_id: str, dependencies: dict):
|
||||
}
|
||||
|
||||
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)}")
|
||||
|
||||
|
||||
@@ -2489,6 +2681,28 @@ def register_routes(app, dependencies: dict):
|
||||
async def _lookup_movie(imdb_id: str):
|
||||
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
|
||||
from api.monitoring_routes import router as monitoring_router
|
||||
app.include_router(monitoring_router)
|
||||
|
||||
+2
-2
@@ -1425,8 +1425,8 @@ def register_web_routes(app, dependencies):
|
||||
# Create request with timeout (no body needed for query parameters)
|
||||
req = urllib.request.Request(core_url, method='POST')
|
||||
|
||||
# Make request with timeout
|
||||
with urllib.request.urlopen(req, timeout=30) as response:
|
||||
# Make request with timeout (increased for manual scans which can take several minutes)
|
||||
with urllib.request.urlopen(req, timeout=300) as response:
|
||||
response_data = response.read().decode('utf-8')
|
||||
return json.loads(response_data)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user