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:
|
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
@@ -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)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user