This commit is contained in:
+24
-9
@@ -2391,7 +2391,7 @@ def register_routes(app, dependencies: dict):
|
|||||||
imdb_id = "unknown"
|
imdb_id = "unknown"
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Parse the NFO file we already know exists (since grep found it missing dateadded)
|
# Try to parse the NFO file we already know exists (since grep found it missing dateadded)
|
||||||
tree = ET.parse(nfo_file)
|
tree = ET.parse(nfo_file)
|
||||||
root = tree.getroot()
|
root = tree.getroot()
|
||||||
|
|
||||||
@@ -2419,7 +2419,27 @@ def register_routes(app, dependencies: dict):
|
|||||||
print(f"✅ Found IMDb {imdb_text} in NFO content: {os.path.basename(nfo_file)}")
|
print(f"✅ Found IMDb {imdb_text} in NFO content: {os.path.basename(nfo_file)}")
|
||||||
return imdb_text
|
return imdb_text
|
||||||
|
|
||||||
# Method 4: Fallback - check folder structure for episodes
|
except ET.ParseError as e:
|
||||||
|
# Handle malformed XML by trying text-based search
|
||||||
|
print(f"⚠️ NFO XML parse error in {os.path.basename(nfo_file)}: {e}")
|
||||||
|
try:
|
||||||
|
# Fallback: Read as text and search for IMDb patterns
|
||||||
|
with open(nfo_file, 'r', encoding='utf-8', errors='ignore') as f:
|
||||||
|
nfo_text = f.read()
|
||||||
|
|
||||||
|
# Search for IMDb patterns in raw text
|
||||||
|
text_match = re.search(r'(tt\d{7,})', nfo_text)
|
||||||
|
if text_match:
|
||||||
|
imdb_text = text_match.group(1)
|
||||||
|
print(f"✅ Found IMDb {imdb_text} in NFO text content: {os.path.basename(nfo_file)}")
|
||||||
|
return imdb_text
|
||||||
|
except Exception as text_error:
|
||||||
|
print(f"⚠️ Error reading NFO as text {nfo_file}: {text_error}")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"⚠️ Unexpected error parsing NFO {nfo_file}: {e}")
|
||||||
|
|
||||||
|
# Method 4: Fallback - check folder structure
|
||||||
if is_episode:
|
if is_episode:
|
||||||
series_folder = os.path.dirname(os.path.dirname(nfo_file))
|
series_folder = os.path.dirname(os.path.dirname(nfo_file))
|
||||||
folder_name = os.path.basename(series_folder)
|
folder_name = os.path.basename(series_folder)
|
||||||
@@ -2436,9 +2456,6 @@ def register_routes(app, dependencies: dict):
|
|||||||
print(f"✅ Found IMDb {folder_match.group(1)} in folder: {folder_name}")
|
print(f"✅ Found IMDb {folder_match.group(1)} in folder: {folder_name}")
|
||||||
return folder_match.group(1)
|
return folder_match.group(1)
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
print(f"⚠️ Error parsing NFO for IMDb in {nfo_file}: {e}")
|
|
||||||
|
|
||||||
print(f"❌ No IMDb ID found in {os.path.basename(nfo_file)}")
|
print(f"❌ No IMDb ID found in {os.path.basename(nfo_file)}")
|
||||||
return "unknown"
|
return "unknown"
|
||||||
|
|
||||||
@@ -2568,7 +2585,7 @@ def register_routes(app, dependencies: dict):
|
|||||||
with db.get_connection() as conn:
|
with db.get_connection() as conn:
|
||||||
cursor = conn.cursor()
|
cursor = conn.cursor()
|
||||||
cursor.execute("""
|
cursor.execute("""
|
||||||
SELECT dateadded, source, added_by
|
SELECT dateadded, source
|
||||||
FROM episodes
|
FROM episodes
|
||||||
WHERE imdb_id = %s AND season = %s AND episode = %s
|
WHERE imdb_id = %s AND season = %s AND episode = %s
|
||||||
LIMIT 1
|
LIMIT 1
|
||||||
@@ -2578,7 +2595,6 @@ def register_routes(app, dependencies: dict):
|
|||||||
if result:
|
if result:
|
||||||
episode["dateadded"] = result["dateadded"]
|
episode["dateadded"] = result["dateadded"]
|
||||||
episode["source"] = result.get("source", "database")
|
episode["source"] = result.get("source", "database")
|
||||||
episode["added_by"] = result.get("added_by", "unknown")
|
|
||||||
else:
|
else:
|
||||||
print(f"⚠️ No database entry for {episode['imdb_id']} S{episode['season']:02d}E{episode['episode']:02d}")
|
print(f"⚠️ No database entry for {episode['imdb_id']} S{episode['season']:02d}E{episode['episode']:02d}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -2591,7 +2607,7 @@ def register_routes(app, dependencies: dict):
|
|||||||
with db.get_connection() as conn:
|
with db.get_connection() as conn:
|
||||||
cursor = conn.cursor()
|
cursor = conn.cursor()
|
||||||
cursor.execute("""
|
cursor.execute("""
|
||||||
SELECT dateadded, source, added_by
|
SELECT dateadded, source
|
||||||
FROM movies
|
FROM movies
|
||||||
WHERE imdb_id = %s
|
WHERE imdb_id = %s
|
||||||
LIMIT 1
|
LIMIT 1
|
||||||
@@ -2601,7 +2617,6 @@ def register_routes(app, dependencies: dict):
|
|||||||
if result:
|
if result:
|
||||||
movie["dateadded"] = result["dateadded"]
|
movie["dateadded"] = result["dateadded"]
|
||||||
movie["source"] = result.get("source", "database")
|
movie["source"] = result.get("source", "database")
|
||||||
movie["added_by"] = result.get("added_by", "unknown")
|
|
||||||
else:
|
else:
|
||||||
print(f"⚠️ No database entry for movie {movie['imdb_id']}")
|
print(f"⚠️ No database entry for movie {movie['imdb_id']}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|||||||
@@ -958,6 +958,97 @@ body {
|
|||||||
border-radius: 0.375rem;
|
border-radius: 0.375rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* NFO Repair Table Styling */
|
||||||
|
.nfo-repair-table-container {
|
||||||
|
background: white;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
box-shadow: var(--shadow);
|
||||||
|
overflow-x: auto;
|
||||||
|
overflow-y: auto;
|
||||||
|
max-height: 600px;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nfo-repair-table {
|
||||||
|
width: 100%;
|
||||||
|
min-width: 800px;
|
||||||
|
border-collapse: collapse;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
background: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nfo-repair-table th {
|
||||||
|
background-color: var(--dark-color);
|
||||||
|
color: white;
|
||||||
|
padding: 0.75rem 0.5rem;
|
||||||
|
text-align: left;
|
||||||
|
font-weight: 600;
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nfo-repair-table td {
|
||||||
|
padding: 0.75rem 0.5rem;
|
||||||
|
border-bottom: 1px solid var(--border-color);
|
||||||
|
vertical-align: top;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nfo-repair-table tr:hover {
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.media-type-badge {
|
||||||
|
padding: 0.25rem 0.5rem;
|
||||||
|
border-radius: 0.25rem;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
font-weight: 600;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.media-type-badge.episode {
|
||||||
|
background-color: #e3f2fd;
|
||||||
|
color: #1976d2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.media-type-badge.movie {
|
||||||
|
background-color: #f3e5f5;
|
||||||
|
color: #7b1fa2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-ready {
|
||||||
|
color: #2e7d32;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-missing {
|
||||||
|
color: #d32f2f;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-center {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-muted {
|
||||||
|
color: #6c757d;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nfo-fix-actions {
|
||||||
|
margin-top: 1rem;
|
||||||
|
padding: 1rem;
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nfo-fix-actions .btn {
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
.admin-tools {
|
.admin-tools {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>NFOGuard - Database Management</title>
|
<title>NFOGuard - Database Management</title>
|
||||||
<link rel="stylesheet" href="/static/css/styles.css?v=2.8.2-20241026-table-fix">
|
<link rel="stylesheet" href="/static/css/styles.css?v=2.8.2-20241026-nfo-table-redesign">
|
||||||
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
|
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
@@ -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.1-nfo-repair-movies"></script>
|
<script src="/static/js/app.js?v=3.0.1-nfo-table-redesign"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
@@ -1847,17 +1847,18 @@ async function checkMissingNFODates() {
|
|||||||
html += `
|
html += `
|
||||||
<div class="missing-episodes">
|
<div class="missing-episodes">
|
||||||
<h5>Items with missing dateadded (showing first 50):</h5>
|
<h5>Items with missing dateadded (showing first 50):</h5>
|
||||||
<div class="table-container">
|
<div class="nfo-repair-table-container">
|
||||||
<table class="results-table">
|
<table class="nfo-repair-table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Type</th>
|
<th style="width: 8%">Type</th>
|
||||||
<th>IMDb ID</th>
|
<th style="width: 12%">IMDb ID</th>
|
||||||
<th>Season</th>
|
<th style="width: 8%">Season</th>
|
||||||
<th>Episode</th>
|
<th style="width: 8%">Episode</th>
|
||||||
<th>Database Date</th>
|
<th style="width: 15%">Database Date</th>
|
||||||
<th>Source</th>
|
<th style="width: 10%">Source</th>
|
||||||
<th>NFO Path</th>
|
<th style="width: 25%">Series/Movie</th>
|
||||||
|
<th style="width: 14%">Actions</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
@@ -1865,20 +1866,39 @@ async function checkMissingNFODates() {
|
|||||||
|
|
||||||
(response.items || response.episodes || []).forEach(item => {
|
(response.items || response.episodes || []).forEach(item => {
|
||||||
const displayDate = item.dateadded ? new Date(item.dateadded).toLocaleString() : 'None';
|
const displayDate = item.dateadded ? new Date(item.dateadded).toLocaleString() : 'None';
|
||||||
|
const seriesName = item.series_name || item.title || 'Unknown';
|
||||||
|
const nfoFileName = item.nfo_path ? item.nfo_path.split('/').pop() : 'Unknown';
|
||||||
|
|
||||||
html += `
|
html += `
|
||||||
<tr>
|
<tr>
|
||||||
<td>${item.media_type || 'episode'}</td>
|
<td><span class="media-type-badge ${item.media_type || 'episode'}">${item.media_type || 'episode'}</span></td>
|
||||||
<td>${item.imdb_id}</td>
|
<td><code>${item.imdb_id}</code></td>
|
||||||
<td>${item.season || 'N/A'}</td>
|
<td class="text-center">${item.season || 'N/A'}</td>
|
||||||
<td>${item.episode || 'N/A'}</td>
|
<td class="text-center">${item.episode || 'N/A'}</td>
|
||||||
<td>${displayDate}</td>
|
<td><small>${displayDate}</small></td>
|
||||||
<td>${item.source || 'unknown'}</td>
|
<td><small>${item.source || 'unknown'}</small></td>
|
||||||
<td><small>${item.nfo_path}</small></td>
|
<td><strong>${seriesName}</strong><br><small class="text-muted">${nfoFileName}</small></td>
|
||||||
|
<td>
|
||||||
|
${item.dateadded ?
|
||||||
|
'<span class="status-ready">✅ Ready to fix</span>' :
|
||||||
|
'<span class="status-missing">❌ No DB date</span>'
|
||||||
|
}
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
`;
|
`;
|
||||||
});
|
});
|
||||||
|
|
||||||
html += '</tbody></table></div></div>';
|
html += `
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<div class="nfo-fix-actions">
|
||||||
|
<button onclick="fixAllMissingNFODates()" class="btn btn-primary">
|
||||||
|
🔧 Fix All Missing dateadded Elements
|
||||||
|
</button>
|
||||||
|
<p class="text-muted">This will automatically add dateadded elements to NFO files using database values.</p>
|
||||||
|
</div>
|
||||||
|
</div>`;
|
||||||
} else {
|
} else {
|
||||||
html += '<div class="success">✅ All NFO files have dateadded elements!</div>';
|
html += '<div class="success">✅ All NFO files have dateadded elements!</div>';
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user