mirror of
https://github.com/Viren070/MediaFusion.git
synced 2025-12-01 23:21:11 +01:00
Add support for updating content images via scraper UI
This commit is contained in:
@@ -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>
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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}"}
|
||||
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user