Add support for updating content images via scraper UI

This commit is contained in:
mhdzumair
2025-01-18 07:11:28 +05:30
parent 91b9649fab
commit 1baad5926e
4 changed files with 270 additions and 4 deletions
+92
View File
@@ -35,6 +35,7 @@
<option value="update_imdb_data">Update IMDb Data</option>
<option value="block_torrent">Block Torrent</option>
<option value="migrate_id">Migrate MediaFusion ID to IMDb ID</option>
<option value="update_images">Update Images</option>
</select>
</div>
@@ -577,6 +578,97 @@
</div>
</div>
<div id="updateImagesParameters" style="display: none;">
<div class="section-container">
<h4 class="section-header">Update Images</h4>
<hr class="section-divider">
<div class="alert alert-info" role="alert">
<i class="bi bi-info-circle"></i>
Provide the content ID and at least one image URL to update
</div>
<!-- Content ID Input -->
<div class="card mb-4">
<div class="card-header">
<h5 class="mb-0">Content Information</h5>
</div>
<div class="card-body">
<div class="mb-3">
<label for="imageUpdateMetaId" class="form-label">Content ID *</label>
<input type="text"
class="form-control"
id="imageUpdateMetaId"
name="meta_id"
required
placeholder="Enter IMDb ID (tt1234567) or MediaFusion ID">
<div class="form-text">
<i class="bi bi-info-circle-fill"></i>
This can be either an IMDb ID (starts with 'tt') or a MediaFusion ID (starts with 'mf')
</div>
</div>
</div>
</div>
<!-- Image URLs Card -->
<div class="card mb-4">
<div class="card-header">
<h5 class="mb-0">Image URLs</h5>
<small class="text-muted">At least one image URL must be provided</small>
</div>
<div class="card-body">
<!-- Poster URL -->
<div class="mb-4">
<label for="imageUpdatePoster" class="form-label">Poster URL</label>
<div class="input-group">
<span class="input-group-text">
<i class="bi bi-image"></i>
</span>
<input type="url"
class="form-control"
id="imageUpdatePoster"
name="poster"
placeholder="https://example.com/poster.jpg">
</div>
<div class="form-text">High-quality vertical image for the content poster</div>
</div>
<!-- Background URL -->
<div class="mb-4">
<label for="imageUpdateBackground" class="form-label">Background URL</label>
<div class="input-group">
<span class="input-group-text">
<i class="bi bi-image-fill"></i>
</span>
<input type="url"
class="form-control"
id="imageUpdateBackground"
name="background"
placeholder="https://example.com/background.jpg">
</div>
<div class="form-text">Wide image to be used as background/backdrop</div>
</div>
<!-- Logo URL -->
<div class="mb-3">
<label for="imageUpdateLogo" class="form-label">Logo URL</label>
<div class="input-group">
<span class="input-group-text">
<i class="bi bi-vector-pen"></i>
</span>
<input type="url"
class="form-control"
id="imageUpdateLogo"
name="logo"
placeholder="https://example.com/logo.png">
</div>
<div class="form-text">Transparent PNG logo of the content</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
+65 -4
View File
@@ -487,6 +487,7 @@ function updateFormFields() {
setElementDisplay("apiPasswordContainer", "none");
setElementDisplay('blockTorrentParameters', 'none');
setElementDisplay('migrationParameters', 'none');
setElementDisplay('updateImagesParameters', 'none');
// Get the selected scraper type
const scraperType = document.getElementById('scraperSelect').value;
@@ -531,6 +532,10 @@ function updateFormFields() {
setElementDisplay('migrationParameters', 'block');
authRequired = false;
break;
case 'update_images':
setElementDisplay('updateImagesParameters', 'block');
authRequired = false;
break;
}
// Setup password toggle if the API password field is displayed
@@ -546,7 +551,7 @@ function handleInitialSetup() {
const action = urlParams.get('action');
// Set initial scraper type based on action
document.getElementById('scraperSelect').value = action || 'quick_import';
if (action) document.getElementById('scraperSelect').value = action;
// Update form fields based on initial selection
updateFormFields();
@@ -724,9 +729,9 @@ function displayMatchResults(matches, torrentData) {
<!-- Tags Section -->
<div class="mb-3">
<div class="d-flex flex-wrap gap-2">
${match.genres.map(genre =>
`<span class="badge bg-primary bg-opacity-25 text-primary">${genre}</span>`
).join('')}
${match.genres.map(genre =>
`<span class="badge bg-primary bg-opacity-25 text-primary">${genre}</span>`
).join('')}
</div>
</div>
@@ -1463,6 +1468,59 @@ async function handleBlockTorrent(apiPassword, submitBtn, loadingSpinner) {
}
}
async function handleUpdateImages(apiPassword, submitBtn, loadingSpinner) {
const metaId = document.getElementById('imageUpdateMetaId').value.trim();
const poster = document.getElementById('imageUpdatePoster').value.trim();
const background = document.getElementById('imageUpdateBackground').value.trim();
const logo = document.getElementById('imageUpdateLogo').value.trim();
// Validate required fields
if (!metaId) {
showNotification('Content ID is required.', 'error');
resetButton(submitBtn, loadingSpinner);
return;
}
// Validate at least one image URL is provided
if (!poster && !background && !logo) {
showNotification('At least one image URL must be provided.', 'error');
resetButton(submitBtn, loadingSpinner);
return;
}
const formData = new FormData();
formData.append('meta_id', metaId);
formData.append('api_password', apiPassword);
if (poster) formData.append('poster', poster);
if (background) formData.append('background', background);
if (logo) formData.append('logo', logo);
try {
const response = await fetch('/scraper/update_images', {
method: 'POST',
body: formData
});
const data = await response.json();
if (data.detail) {
showNotification(data.detail, 'error');
} else {
showNotification(data.status, 'success');
// Clear form fields on success
document.getElementById('imageUpdatePoster').value = '';
document.getElementById('imageUpdateBackground').value = '';
document.getElementById('imageUpdateLogo').value = '';
}
} catch (error) {
console.error('Error updating images:', error);
showNotification(`Error updating images: ${error.toString()}`, 'error');
} finally {
resetButton(submitBtn, loadingSpinner);
}
}
// Main function
async function submitScraperForm() {
const apiPassword = document.getElementById('api_password').value;
@@ -1499,6 +1557,9 @@ async function submitScraperForm() {
case 'migrate_id':
await handleMigration(apiPassword, submitBtn, loadingSpinner);
break;
case 'update_images':
await handleUpdateImages(apiPassword, submitBtn, loadingSpinner);
break;
default:
await handleScrapyParameters(payload, submitBtn, loadingSpinner);
break;
+67
View File
@@ -33,6 +33,7 @@ from utils.network import get_request_namespace
from utils.parser import calculate_max_similarity_ratio, convert_bytes_to_readable
from utils.runtime_const import TEMPLATES, SPORTS_ARTIFACTS
from utils.telegram_bot import telegram_notifier
from utils.validation_helper import validate_image_url
router = APIRouter()
@@ -767,3 +768,69 @@ async def analyze_torrent(
raise HTTPException(
status_code=400, detail=f"Failed to analyze torrent: {str(e)}"
)
@router.post("/update_images")
async def update_images(
meta_id: str = Form(...),
poster: Optional[str] = Form(None),
background: Optional[str] = Form(None),
logo: Optional[str] = Form(None),
):
"""Update poster, background, and/or logo for existing content"""
# Validate that at least one image URL is provided
if not any([poster, background, logo]):
raise_error(
"At least one image URL (poster, background, or logo) must be provided"
)
# Get metadata to validate it exists and get current values
metadata = await MediaFusionMetaData.get_motor_collection().find_one(
{"_id": meta_id},
projection={"title": 1, "type": 1, "poster": 1, "background": 1, "logo": 1},
)
if not metadata:
raise_error(f"Content with ID {meta_id} not found")
# Build update fields
update_fields = {}
if poster:
update_fields["poster"] = poster
update_fields["is_poster_working"] = True
if not await validate_image_url(poster):
raise_error("Invalid poster image URL, Not able to fetch image")
if background:
update_fields["background"] = background
if not await validate_image_url(background):
raise_error("Invalid background image URL, Not able to fetch image")
if logo:
update_fields["logo"] = logo
if not await validate_image_url(logo):
raise_error("Invalid logo image URL, Not able to fetch image")
# Update metadata
await MediaFusionMetaData.get_motor_collection().update_one(
{"_id": meta_id}, {"$set": update_fields}
)
# Send Telegram notification
if settings.telegram_bot_token:
await telegram_notifier.send_image_update_notification(
meta_id=meta_id,
title=metadata.get("title"),
meta_type=metadata.get("type"),
poster=f"{settings.poster_host_url}/poster/{metadata.get('type')}/{meta_id}.jpg",
old_poster=metadata.get("poster"),
new_poster=poster,
old_background=metadata.get("background"),
new_background=background,
old_logo=metadata.get("logo"),
new_logo=logo,
)
# cleanup redis cache
cache_key = f"{metadata.get('type')}_{meta_id}.jpg"
await REDIS_ASYNC_CLIENT.delete(cache_key)
return {"status": f"Successfully updated images for {meta_id}"}
+46
View File
@@ -141,6 +141,52 @@ class TelegramNotifier:
await self._send_photo_message(poster, message)
async def send_image_update_notification(
self,
meta_id: str,
title: str,
meta_type: str,
poster: str,
old_poster: Optional[str] = None,
new_poster: Optional[str] = None,
old_background: Optional[str] = None,
new_background: Optional[str] = None,
old_logo: Optional[str] = None,
new_logo: Optional[str] = None,
):
"""Send notification when images are updated"""
if not self.enabled:
logger.warning("Telegram notifications are disabled. Check bot token.")
return
meta_id_data = (
f"*IMDb*: [{meta_id}](https://www.imdb.com/title/{meta_id}/)\n"
if meta_id.startswith("tt")
else f"Meta ID: {meta_id}\n"
)
message = (
f"🖼️ Images Updated\n\n"
f"*Title*: {title}\n"
f"*Type*: {meta_type.title()}\n"
f"{meta_id_data}"
f"*Old Poster*: [View]({old_poster})\n"
f"*Old Background*: [View]({old_background})\n"
f"*Old Logo*: [View]({old_logo})\n"
)
# Add details about what was updated
if new_poster:
message += f"\n*Poster*: Updated ✅ [View]({new_poster})"
if new_background:
message += f"\n*Background*: Updated ✅ [View]({new_background})"
if new_logo:
message += f"\n*Logo*: Updated ✅ [View]({new_logo})"
message += f"\n\n*Preview*: [View]({poster})"
await self._send_photo_message(poster, message)
async def _send_photo_message(self, photo_url: str, message: str):
"""Send a message with photo, falling back to text-only if photo fails"""
try: