This commit is contained in:
+190
-8
@@ -383,13 +383,13 @@ async def get_dashboard_stats(dependencies: dict):
|
|||||||
cursor.execute("SELECT COUNT(*) FROM movies WHERE dateadded IS NOT NULL AND dateadded != ''")
|
cursor.execute("SELECT COUNT(*) FROM movies WHERE dateadded IS NOT NULL AND dateadded != ''")
|
||||||
movies_with_dates = cursor.fetchone()[0]
|
movies_with_dates = cursor.fetchone()[0]
|
||||||
|
|
||||||
cursor.execute("SELECT COUNT(*) FROM movies WHERE dateadded IS NULL OR dateadded = ''")
|
cursor.execute("SELECT COUNT(*) FROM movies WHERE dateadded IS NULL OR dateadded = '' OR source = 'no_valid_date_source'")
|
||||||
movies_without_dates = cursor.fetchone()[0]
|
movies_without_dates = cursor.fetchone()[0]
|
||||||
|
|
||||||
cursor.execute("SELECT COUNT(*) FROM episodes WHERE dateadded IS NOT NULL AND dateadded != ''")
|
cursor.execute("SELECT COUNT(*) FROM episodes WHERE dateadded IS NOT NULL AND dateadded != ''")
|
||||||
episodes_with_dates = cursor.fetchone()[0]
|
episodes_with_dates = cursor.fetchone()[0]
|
||||||
|
|
||||||
cursor.execute("SELECT COUNT(*) FROM episodes WHERE dateadded IS NULL OR dateadded = ''")
|
cursor.execute("SELECT COUNT(*) FROM episodes WHERE dateadded IS NULL OR dateadded = '' OR source = 'no_valid_date_source'")
|
||||||
episodes_without_dates = cursor.fetchone()[0]
|
episodes_without_dates = cursor.fetchone()[0]
|
||||||
|
|
||||||
cursor.execute("SELECT COUNT(*) FROM movies WHERE source = 'no_valid_date_source'")
|
cursor.execute("SELECT COUNT(*) FROM movies WHERE source = 'no_valid_date_source'")
|
||||||
@@ -681,13 +681,82 @@ async def get_movie_date_options(dependencies: dict, imdb_id: str):
|
|||||||
"description": "Enter custom date and time"
|
"description": "Enter custom date and time"
|
||||||
})
|
})
|
||||||
|
|
||||||
# Try to get additional metadata from external sources
|
# Option 4: Active lookup from external sources
|
||||||
try:
|
try:
|
||||||
# This would integrate with your existing TMDB/OMDB logic
|
# Get movie processor and clients from dependencies
|
||||||
# For now, we'll use the existing released date as an example
|
movie_processor = dependencies.get("movie_processor")
|
||||||
pass
|
if movie_processor and hasattr(movie_processor, 'external_clients'):
|
||||||
except Exception:
|
external_clients = movie_processor.external_clients
|
||||||
|
|
||||||
|
# Check Radarr for import dates
|
||||||
|
if movie_processor.radarr and movie_processor.radarr.enabled:
|
||||||
|
try:
|
||||||
|
radarr_movie = movie_processor.radarr.movie_by_imdb(imdb_id)
|
||||||
|
if radarr_movie:
|
||||||
|
movie_id = radarr_movie.get('id')
|
||||||
|
if movie_id:
|
||||||
|
import_date, source = movie_processor.radarr.get_movie_import_date(movie_id, fallback_to_file_date=True)
|
||||||
|
if import_date and source != "no_valid_date_source":
|
||||||
|
# Check if this is different from current date
|
||||||
|
current_dateadded = movie.get('dateadded', '')
|
||||||
|
if not current_dateadded or not current_dateadded.startswith(import_date[:10]):
|
||||||
|
options.append({
|
||||||
|
"type": "radarr_import",
|
||||||
|
"label": f"Radarr Import Date ({source})",
|
||||||
|
"date": import_date,
|
||||||
|
"source": f"radarr:{source}",
|
||||||
|
"description": f"Import date from Radarr: {import_date[:10]} (source: {source})"
|
||||||
|
})
|
||||||
|
except Exception as e:
|
||||||
|
print(f"⚠️ Failed to get Radarr import date for {imdb_id}: {e}")
|
||||||
|
|
||||||
|
# Check TMDB for digital release dates
|
||||||
|
if external_clients.tmdb.enabled:
|
||||||
|
try:
|
||||||
|
digital_release = external_clients.tmdb.get_digital_release_date(imdb_id)
|
||||||
|
if digital_release:
|
||||||
|
# Check if this is different from current date
|
||||||
|
current_dateadded = movie.get('dateadded', '')
|
||||||
|
if not current_dateadded or not current_dateadded.startswith(digital_release[:10]):
|
||||||
|
options.append({
|
||||||
|
"type": "tmdb_digital",
|
||||||
|
"label": "TMDB Digital Release",
|
||||||
|
"date": f"{digital_release}T00:00:00",
|
||||||
|
"source": "tmdb:digital_release",
|
||||||
|
"description": f"Digital release date from TMDB: {digital_release}"
|
||||||
|
})
|
||||||
|
except Exception as e:
|
||||||
|
print(f"⚠️ Failed to get TMDB digital release for {imdb_id}: {e}")
|
||||||
|
|
||||||
|
# Check OMDb for additional release info
|
||||||
|
if external_clients.omdb.enabled:
|
||||||
|
try:
|
||||||
|
omdb_details = external_clients.omdb.get_movie_details(imdb_id)
|
||||||
|
if omdb_details and omdb_details.get('Released') and omdb_details['Released'] != 'N/A':
|
||||||
|
from datetime import datetime
|
||||||
|
try:
|
||||||
|
# Parse OMDb date format (e.g., "27 Jul 2018")
|
||||||
|
omdb_date = datetime.strptime(omdb_details['Released'], '%d %b %Y')
|
||||||
|
omdb_iso = omdb_date.strftime('%Y-%m-%d')
|
||||||
|
|
||||||
|
# Check if this is different from current date
|
||||||
|
current_dateadded = movie.get('dateadded', '')
|
||||||
|
if not current_dateadded or not current_dateadded.startswith(omdb_iso):
|
||||||
|
options.append({
|
||||||
|
"type": "omdb_release",
|
||||||
|
"label": "OMDb Release Date",
|
||||||
|
"date": f"{omdb_iso}T00:00:00",
|
||||||
|
"source": "omdb:release",
|
||||||
|
"description": f"Release date from OMDb: {omdb_iso}"
|
||||||
|
})
|
||||||
|
except ValueError:
|
||||||
|
# Skip if date parsing fails
|
||||||
pass
|
pass
|
||||||
|
except Exception as e:
|
||||||
|
print(f"⚠️ Failed to get OMDb details for {imdb_id}: {e}")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"⚠️ External source lookup failed for {imdb_id}: {e}")
|
||||||
|
|
||||||
print(f"🔍 DEBUG: Generated {len(options)} options for {imdb_id}:")
|
print(f"🔍 DEBUG: Generated {len(options)} options for {imdb_id}:")
|
||||||
for i, option in enumerate(options):
|
for i, option in enumerate(options):
|
||||||
@@ -702,11 +771,35 @@ async def get_movie_date_options(dependencies: dict, imdb_id: str):
|
|||||||
|
|
||||||
async def get_episode_date_options(dependencies: dict, imdb_id: str, season: int, episode: int):
|
async def get_episode_date_options(dependencies: dict, imdb_id: str, season: int, episode: int):
|
||||||
"""Get available date options for an episode"""
|
"""Get available date options for an episode"""
|
||||||
|
print(f"🔍 DEBUG: get_episode_date_options called with imdb_id={imdb_id}, season={season}, episode={episode}")
|
||||||
db = dependencies["db"]
|
db = dependencies["db"]
|
||||||
|
|
||||||
|
# Validate parameters with enhanced checking
|
||||||
|
try:
|
||||||
|
if not imdb_id or not imdb_id.strip():
|
||||||
|
print(f"❌ Invalid imdb_id: '{imdb_id}'")
|
||||||
|
raise HTTPException(status_code=422, detail="Invalid imdb_id parameter")
|
||||||
|
|
||||||
|
# Convert and validate season
|
||||||
|
season = int(season) if isinstance(season, str) else season
|
||||||
|
if season < 0:
|
||||||
|
print(f"❌ Invalid season: {season}")
|
||||||
|
raise HTTPException(status_code=422, detail="Season must be >= 0")
|
||||||
|
|
||||||
|
# Convert and validate episode
|
||||||
|
episode = int(episode) if isinstance(episode, str) else episode
|
||||||
|
if episode < 1:
|
||||||
|
print(f"❌ Invalid episode: {episode}")
|
||||||
|
raise HTTPException(status_code=422, detail="Episode must be >= 1")
|
||||||
|
except ValueError as e:
|
||||||
|
print(f"❌ Parameter conversion error: {e}")
|
||||||
|
raise HTTPException(status_code=422, detail=f"Invalid parameter types: {e}")
|
||||||
|
|
||||||
# Get current episode data
|
# Get current episode data
|
||||||
episode_data = db.get_episode_date(imdb_id, season, episode)
|
episode_data = db.get_episode_date(imdb_id, season, episode)
|
||||||
|
print(f"🔍 DEBUG: Episode data from DB: {episode_data}")
|
||||||
if not episode_data:
|
if not episode_data:
|
||||||
|
print(f"❌ Episode not found in database: {imdb_id} S{season:02d}E{episode:02d}")
|
||||||
raise HTTPException(status_code=404, detail="Episode not found")
|
raise HTTPException(status_code=404, detail="Episode not found")
|
||||||
|
|
||||||
options = []
|
options = []
|
||||||
@@ -731,7 +824,92 @@ async def get_episode_date_options(dependencies: dict, imdb_id: str, season: int
|
|||||||
"description": f"Use original air date: {episode_data['aired']}"
|
"description": f"Use original air date: {episode_data['aired']}"
|
||||||
})
|
})
|
||||||
|
|
||||||
# Option 3: Manual entry
|
# Option 3: Active lookup from external sources
|
||||||
|
try:
|
||||||
|
# Get TV processor and clients from dependencies
|
||||||
|
tv_processor = dependencies.get("tv_processor")
|
||||||
|
if tv_processor and hasattr(tv_processor, 'external_clients'):
|
||||||
|
external_clients = tv_processor.external_clients
|
||||||
|
|
||||||
|
# Check Sonarr for import dates
|
||||||
|
if tv_processor.sonarr and tv_processor.sonarr.enabled:
|
||||||
|
try:
|
||||||
|
# Look up the series and episode in Sonarr
|
||||||
|
series_data = tv_processor.sonarr.series_by_imdb(imdb_id)
|
||||||
|
if series_data:
|
||||||
|
series_id = series_data.get('id')
|
||||||
|
if series_id:
|
||||||
|
# Get episodes for the series
|
||||||
|
episodes = tv_processor.sonarr.get_episodes(series_id)
|
||||||
|
for ep in episodes:
|
||||||
|
if ep.get('seasonNumber') == season and ep.get('episodeNumber') == episode:
|
||||||
|
episode_id = ep.get('id')
|
||||||
|
if episode_id:
|
||||||
|
# Get import history for this specific episode
|
||||||
|
import_date = tv_processor.sonarr.get_episode_import_history(episode_id)
|
||||||
|
if import_date:
|
||||||
|
# Check if this is different from current date
|
||||||
|
current_dateadded = episode_data.get('dateadded', '')
|
||||||
|
if not current_dateadded or not current_dateadded.startswith(import_date[:10]):
|
||||||
|
options.append({
|
||||||
|
"type": "sonarr_import",
|
||||||
|
"label": "Sonarr Import Date",
|
||||||
|
"date": import_date,
|
||||||
|
"source": "sonarr:import_history",
|
||||||
|
"description": f"Import date from Sonarr: {import_date[:10]}"
|
||||||
|
})
|
||||||
|
break
|
||||||
|
except Exception as e:
|
||||||
|
print(f"⚠️ Failed to get Sonarr import date for {imdb_id} S{season:02d}E{episode:02d}: {e}")
|
||||||
|
|
||||||
|
# Check TMDB for episode air dates
|
||||||
|
if external_clients.tmdb.enabled:
|
||||||
|
try:
|
||||||
|
# Get TMDB TV series ID from IMDb ID
|
||||||
|
tv_info = external_clients.tmdb.find_tv_by_imdb(imdb_id)
|
||||||
|
if tv_info:
|
||||||
|
tmdb_id = tv_info.get('id')
|
||||||
|
if tmdb_id:
|
||||||
|
# Get episode air date from TMDB
|
||||||
|
episodes = external_clients.tmdb.get_tv_season_episodes(tmdb_id, season)
|
||||||
|
if episode in episodes:
|
||||||
|
air_date = episodes[episode]
|
||||||
|
if air_date:
|
||||||
|
# Check if this is different from current aired date
|
||||||
|
current_aired = episode_data.get('aired', '')
|
||||||
|
if not current_aired or current_aired != air_date:
|
||||||
|
options.append({
|
||||||
|
"type": "tmdb_air",
|
||||||
|
"label": "TMDB Air Date",
|
||||||
|
"date": f"{air_date}T20:00:00", # Default to 8 PM
|
||||||
|
"source": "tmdb:airdate",
|
||||||
|
"description": f"Air date from TMDB: {air_date}"
|
||||||
|
})
|
||||||
|
except Exception as e:
|
||||||
|
print(f"⚠️ Failed to get TMDB air date for {imdb_id} S{season:02d}E{episode:02d}: {e}")
|
||||||
|
|
||||||
|
# Check external clients for episode air dates (TVDB, OMDb)
|
||||||
|
if hasattr(external_clients, 'get_episode_air_date'):
|
||||||
|
try:
|
||||||
|
air_date = external_clients.get_episode_air_date(imdb_id, season, episode)
|
||||||
|
if air_date:
|
||||||
|
# Check if this is different from current aired date
|
||||||
|
current_aired = episode_data.get('aired', '')
|
||||||
|
if not current_aired or current_aired != air_date:
|
||||||
|
options.append({
|
||||||
|
"type": "external_air",
|
||||||
|
"label": "External Air Date",
|
||||||
|
"date": f"{air_date}T20:00:00", # Default to 8 PM
|
||||||
|
"source": "external:airdate",
|
||||||
|
"description": f"Air date from external sources: {air_date}"
|
||||||
|
})
|
||||||
|
except Exception as e:
|
||||||
|
print(f"⚠️ Failed to get external air date for {imdb_id} S{season:02d}E{episode:02d}: {e}")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"⚠️ External source lookup failed for {imdb_id} S{season:02d}E{episode:02d}: {e}")
|
||||||
|
|
||||||
|
# Option 4: Manual entry
|
||||||
options.append({
|
options.append({
|
||||||
"type": "manual",
|
"type": "manual",
|
||||||
"label": "Manual Entry",
|
"label": "Manual Entry",
|
||||||
@@ -740,6 +918,10 @@ async def get_episode_date_options(dependencies: dict, imdb_id: str, season: int
|
|||||||
"description": "Enter custom date and time"
|
"description": "Enter custom date and time"
|
||||||
})
|
})
|
||||||
|
|
||||||
|
print(f"🔍 DEBUG: Generated {len(options)} options for {imdb_id} S{season:02d}E{episode:02d}:")
|
||||||
|
for i, option in enumerate(options):
|
||||||
|
print(f" Option {i}: {option}")
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"imdb_id": imdb_id,
|
"imdb_id": imdb_id,
|
||||||
"season": season,
|
"season": season,
|
||||||
|
|||||||
+1
-1
@@ -663,7 +663,7 @@ class NFOManager:
|
|||||||
if preserved_fields:
|
if preserved_fields:
|
||||||
print(f" 🔍 Content preserved after cleanup: {', '.join(preserved_fields)}")
|
print(f" 🔍 Content preserved after cleanup: {', '.join(preserved_fields)}")
|
||||||
else:
|
else:
|
||||||
print(f" ⚠️ No content fields found after cleanup!")
|
print(f" ℹ️ NFO contains only NFOGuard metadata (no additional content fields)")
|
||||||
|
|
||||||
except (ET.ParseError, ValueError) as e:
|
except (ET.ParseError, ValueError) as e:
|
||||||
print(f"⚠️ Corrupted episode NFO detected: {nfo_path} - {str(e)[:100]}...")
|
print(f"⚠️ Corrupted episode NFO detected: {nfo_path} - {str(e)[:100]}...")
|
||||||
|
|||||||
@@ -504,6 +504,11 @@ body {
|
|||||||
padding: 1.5rem;
|
padding: 1.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Higher z-index for edit modals that appear on top of other modals */
|
||||||
|
#edit-modal, #smart-fix-modal {
|
||||||
|
z-index: 1100 !important;
|
||||||
|
}
|
||||||
|
|
||||||
/* Reports */
|
/* Reports */
|
||||||
.report-summary {
|
.report-summary {
|
||||||
display: grid;
|
display: grid;
|
||||||
|
|||||||
Reference in New Issue
Block a user