dev #62

Merged
sbcrumb merged 56 commits from dev into main 2025-10-26 15:45:15 -04:00
3 changed files with 95 additions and 41 deletions
Showing only changes of commit 4d7d9752fc - Show all commits
+78 -29
View File
@@ -1538,46 +1538,76 @@ async def execute_database_query(dependencies: dict, query: str, limit: int = 10
async def get_episodes_missing_nfo_dateadded(dependencies: dict): async def get_episodes_missing_nfo_dateadded(dependencies: dict):
"""Find episodes that have dateadded in database but missing from NFO files""" """Find episodes and movies that have dateadded in database but missing from NFO files"""
db = dependencies["db"] db = dependencies["db"]
config = dependencies["config"] config = dependencies["config"]
try: try:
# Get all episodes with dateadded in database # Get all episodes with video files (even if dateadded is NULL, we can check if they should have dates)
with db.get_connection() as conn: with db.get_connection() as conn:
cursor = conn.cursor() cursor = conn.cursor()
cursor.execute(""" cursor.execute("""
SELECT e.imdb_id, e.season, e.episode, e.dateadded, e.source, e.has_video_file, SELECT e.imdb_id, e.season, e.episode, e.dateadded, e.source, e.has_video_file,
s.path as series_path s.path as series_path, 'episode' as media_type
FROM episodes e FROM episodes e
JOIN series s ON e.imdb_id = s.imdb_id JOIN series s ON e.imdb_id = s.imdb_id
WHERE e.dateadded IS NOT NULL WHERE e.has_video_file = TRUE
AND e.has_video_file = TRUE
ORDER BY e.imdb_id, e.season, e.episode ORDER BY e.imdb_id, e.season, e.episode
LIMIT 500 LIMIT 2000
""") """)
episodes = [dict(row) for row in cursor.fetchall()] episodes = [dict(row) for row in cursor.fetchall()]
# Also get movies with video files
cursor.execute("""
SELECT m.imdb_id, NULL as season, NULL as episode, m.dateadded, m.source, m.has_video_file,
m.path as series_path, 'movie' as media_type
FROM movies m
WHERE m.has_video_file = TRUE
ORDER BY m.imdb_id
LIMIT 2000
""")
movies = [dict(row) for row in cursor.fetchall()]
# Combine episodes and movies
all_items = episodes + movies
# Check each episode's NFO file # Check each item's NFO file
missing_dateadded = [] missing_dateadded = []
nfo_manager = dependencies["nfo_manager"] nfo_manager = dependencies["nfo_manager"]
for ep in episodes: for item in all_items:
try: try:
# Build expected NFO path media_type = item['media_type']
series_path = Path(ep['series_path']) item_path = Path(item['series_path'])
season_dir = series_path / config.tv_season_dir_format.format(season=ep['season'])
if not season_dir.exists(): if media_type == 'episode':
# Handle TV episodes
season_dir = item_path / config.tv_season_dir_format.format(season=item['season'])
if not season_dir.exists():
continue
# Find NFO file for this episode
nfo_files = list(season_dir.glob(f"*S{item['season']:02d}E{item['episode']:02d}*.nfo"))
if not nfo_files:
continue
nfo_path = nfo_files[0] # Use first match
elif media_type == 'movie':
# Handle movies
if not item_path.exists():
continue
# Find NFO file for this movie
nfo_files = list(item_path.glob("*.nfo"))
if not nfo_files:
continue
nfo_path = nfo_files[0] # Use first match
else:
continue continue
# Find NFO file for this episode
nfo_files = list(season_dir.glob(f"*S{ep['season']:02d}E{ep['episode']:02d}*.nfo"))
if not nfo_files:
continue
nfo_path = nfo_files[0] # Use first match
# Parse NFO and check for dateadded # Parse NFO and check for dateadded
try: try:
import xml.etree.ElementTree as ET import xml.etree.ElementTree as ET
@@ -1585,17 +1615,31 @@ async def get_episodes_missing_nfo_dateadded(dependencies: dict):
root = tree.getroot() root = tree.getroot()
dateadded_elem = root.find('dateadded') dateadded_elem = root.find('dateadded')
if dateadded_elem is None or not dateadded_elem.text: # Check if dateadded is missing or empty
missing_dateadded.append({ has_dateadded = dateadded_elem is not None and dateadded_elem.text and dateadded_elem.text.strip()
**ep,
'nfo_path': str(nfo_path), # If NFO is missing dateadded, check if item should have a date
'nfo_exists': True, if not has_dateadded:
'dateadded_in_nfo': False # Item should have a date if it has one in the database, or if it has aired date
}) should_have_date = (
item['dateadded'] is not None or
(media_type == 'episode' and item.get('aired')) or
media_type == 'movie' # Movies should generally have dateadded
)
if should_have_date:
missing_dateadded.append({
**item,
'nfo_path': str(nfo_path),
'nfo_exists': True,
'dateadded_in_nfo': False,
'should_have_date': True
})
except ET.ParseError: except ET.ParseError:
# NFO file is corrupted # NFO file is corrupted
missing_dateadded.append({ missing_dateadded.append({
**ep, **item,
'nfo_path': str(nfo_path), 'nfo_path': str(nfo_path),
'nfo_exists': True, 'nfo_exists': True,
'dateadded_in_nfo': False, 'dateadded_in_nfo': False,
@@ -1603,14 +1647,19 @@ async def get_episodes_missing_nfo_dateadded(dependencies: dict):
}) })
except Exception as e: except Exception as e:
print(f"Error checking NFO for {ep['imdb_id']} S{ep['season']:02d}E{ep['episode']:02d}: {e}") if item['media_type'] == 'episode':
print(f"Error checking NFO for {item['imdb_id']} S{item['season']:02d}E{item['episode']:02d}: {e}")
else:
print(f"Error checking NFO for movie {item['imdb_id']}: {e}")
continue continue
return { return {
"success": True, "success": True,
"total_episodes_checked": len(episodes), "total_episodes_checked": len(episodes),
"total_movies_checked": len(movies),
"total_items_checked": len(all_items),
"missing_dateadded_count": len(missing_dateadded), "missing_dateadded_count": len(missing_dateadded),
"episodes": missing_dateadded[:50] # Limit display to first 50 "items": missing_dateadded[:50] # Limit display to first 50
} }
except Exception as e: except Exception as e:
+1 -1
View File
@@ -533,6 +533,6 @@
<!-- Toast Notifications --> <!-- Toast Notifications -->
<div class="toast-container" id="toast-container"></div> <div class="toast-container" id="toast-container"></div>
<script src="/static/js/app.js?v=3.0.0-debug-airdate"></script> <script src="/static/js/app.js?v=3.0.1-nfo-repair-movies"></script>
</body> </body>
</html> </html>
+16 -11
View File
@@ -1836,20 +1836,23 @@ async function checkMissingNFODates() {
let html = ` let html = `
<div class="scan-summary"> <div class="scan-summary">
<h4>NFO Scan Results</h4> <h4>NFO Scan Results</h4>
<p><strong>Total episodes checked:</strong> ${response.total_episodes_checked}</p> <p><strong>Total episodes checked:</strong> ${response.total_episodes_checked || 0}</p>
<p><strong>Episodes missing dateadded in NFO:</strong> ${response.missing_dateadded_count}</p> <p><strong>Total movies checked:</strong> ${response.total_movies_checked || 0}</p>
<p><strong>Total items checked:</strong> ${response.total_items_checked || 0}</p>
<p><strong>Items missing dateadded in NFO:</strong> ${response.missing_dateadded_count}</p>
</div> </div>
`; `;
if (response.missing_dateadded_count > 0) { if (response.missing_dateadded_count > 0) {
html += ` html += `
<div class="missing-episodes"> <div class="missing-episodes">
<h5>Episodes with missing dateadded (showing first 50):</h5> <h5>Items with missing dateadded (showing first 50):</h5>
<div class="table-container"> <div class="table-container">
<table class="results-table"> <table class="results-table">
<thead> <thead>
<tr> <tr>
<th>Series</th> <th>Type</th>
<th>IMDb ID</th>
<th>Season</th> <th>Season</th>
<th>Episode</th> <th>Episode</th>
<th>Database Date</th> <th>Database Date</th>
@@ -1860,15 +1863,17 @@ async function checkMissingNFODates() {
<tbody> <tbody>
`; `;
response.episodes.forEach(ep => { (response.items || response.episodes || []).forEach(item => {
const displayDate = item.dateadded ? new Date(item.dateadded).toLocaleString() : 'None';
html += ` html += `
<tr> <tr>
<td>${ep.imdb_id}</td> <td>${item.media_type || 'episode'}</td>
<td>${ep.season}</td> <td>${item.imdb_id}</td>
<td>${ep.episode}</td> <td>${item.season || 'N/A'}</td>
<td>${new Date(ep.dateadded).toLocaleString()}</td> <td>${item.episode || 'N/A'}</td>
<td>${ep.source}</td> <td>${displayDate}</td>
<td><small>${ep.nfo_path}</small></td> <td>${item.source || 'unknown'}</td>
<td><small>${item.nfo_path}</small></td>
</tr> </tr>
`; `;
}); });