feat(showcase): optimize CivitAI media URLs for better performance

- Add CivitAI URL utility with optimization strategies for showcase and thumbnail modes
- Replace /original=true with /optimized=true for showcase videos to reduce bandwidth
- Remove redundant crossorigin and referrerpolicy attributes from video elements
- Use media type detection to apply appropriate optimization (image vs video)
- Integrate URL optimization into showcase rendering for improved loading times
This commit is contained in:
Will Miao
2026-03-02 14:05:44 +08:00
parent bde11b153f
commit b72cf7ba98
4 changed files with 305 additions and 6 deletions

View File

@@ -26,8 +26,7 @@ export function generateVideoWrapper(media, heightPercent, shouldBlur, nsfwText,
</button>
` : ''}
${mediaControlsHtml}
<video controls autoplay muted loop crossorigin="anonymous"
referrerpolicy="no-referrer"
<video controls autoplay muted loop
data-local-src="${localUrl || ''}"
data-remote-src="${remoteUrl}"
data-nsfw-level="${nsfwLevel}"

View File

@@ -16,6 +16,7 @@ import {
} from './MediaUtils.js';
import { generateMetadataPanel } from './MetadataPanel.js';
import { generateImageWrapper, generateVideoWrapper } from './MediaRenderers.js';
import { getShowcaseUrl } from '../../../utils/civitaiUtils.js';
export const showcaseListenerMetrics = {
wheelListeners: 0,
@@ -157,11 +158,19 @@ export function renderShowcaseContent(images, exampleFiles = [], startExpanded =
function renderMediaItem(img, index, exampleFiles) {
// Find matching file in our list of actual files
let localFile = findLocalFile(img, index, exampleFiles);
const remoteUrl = img.url || '';
// Get original remote URL
const originalRemoteUrl = img.url || '';
// Determine media type for optimization
const isVideo = localFile ? localFile.is_video :
originalRemoteUrl.endsWith('.mp4') || originalRemoteUrl.endsWith('.webm');
const mediaType = isVideo ? 'video' : 'image';
// Optimize CivitAI URLs for showcase display (full quality)
const remoteUrl = getShowcaseUrl(originalRemoteUrl, mediaType);
const localUrl = localFile ? localFile.path : '';
const isVideo = localFile ? localFile.is_video :
remoteUrl.endsWith('.mp4') || remoteUrl.endsWith('.webm');
// Calculate appropriate aspect ratio
const aspectRatio = (img.height / img.width) * 100;

View File

@@ -0,0 +1,119 @@
/**
* CivitAI URL utilities
* Functions for working with CivitAI media URLs
*/
/**
* Optimization strategies for CivitAI URLs
*/
export const OptimizationMode = {
/** Full quality for showcase/display - uses /optimized=true only */
SHOWCASE: 'showcase',
/** Thumbnail size for cards - uses /width=450,optimized=true */
THUMBNAIL: 'thumbnail',
};
/**
* Rewrite Civitai preview URLs to use optimized renditions.
* Mirrors the backend's rewrite_preview_url() function from py/utils/civitai_utils.py
*
* @param {string|null} sourceUrl - Original preview URL from the Civitai API
* @param {string|null} mediaType - Optional media type hint ("image" or "video")
* @param {string} mode - Optimization mode ('showcase' or 'thumbnail')
* @returns {[string|null, boolean]} - Tuple of [rewritten URL or original, wasRewritten flag]
*/
export function rewriteCivitaiUrl(sourceUrl, mediaType = null, mode = OptimizationMode.THUMBNAIL) {
if (!sourceUrl) {
return [sourceUrl, false];
}
try {
const url = new URL(sourceUrl);
// Check if it's a CivitAI image domain
if (url.hostname.toLowerCase() !== 'image.civitai.com') {
return [sourceUrl, false];
}
// Determine replacement based on mode and media type
let replacement;
if (mode === OptimizationMode.SHOWCASE) {
// Full quality for showcase - no width restriction
replacement = '/optimized=true';
} else {
// Thumbnail mode with width restriction
replacement = '/width=450,optimized=true';
if (mediaType && mediaType.toLowerCase() === 'video') {
replacement = '/transcode=true,width=450,optimized=true';
}
}
// Replace /original=true with optimized version
if (!url.pathname.includes('/original=true')) {
return [sourceUrl, false];
}
const updatedPath = url.pathname.replace('/original=true', replacement, 1);
if (updatedPath === url.pathname) {
return [sourceUrl, false];
}
url.pathname = updatedPath;
return [url.toString(), true];
} catch (e) {
// Invalid URL
return [sourceUrl, false];
}
}
/**
* Get the optimized URL for a media item, falling back to original if not a CivitAI URL
*
* @param {string} url - Original URL
* @param {string} type - Media type ("image" or "video")
* @param {string} mode - Optimization mode ('showcase' or 'thumbnail')
* @returns {string} - Optimized URL or original URL
*/
export function getOptimizedUrl(url, type = 'image', mode = OptimizationMode.THUMBNAIL) {
const [optimizedUrl] = rewriteCivitaiUrl(url, type, mode);
return optimizedUrl || url;
}
/**
* Get showcase-optimized URL (full quality)
*
* @param {string} url - Original URL
* @param {string} type - Media type ("image" or "video")
* @returns {string} - Optimized URL for showcase display
*/
export function getShowcaseUrl(url, type = 'image') {
return getOptimizedUrl(url, type, OptimizationMode.SHOWCASE);
}
/**
* Get thumbnail-optimized URL (width=450)
*
* @param {string} url - Original URL
* @param {string} type - Media type ("image" or "video")
* @returns {string} - Optimized URL for thumbnail display
*/
export function getThumbnailUrl(url, type = 'image') {
return getOptimizedUrl(url, type, OptimizationMode.THUMBNAIL);
}
/**
* Check if a URL is from CivitAI
*
* @param {string} url - URL to check
* @returns {boolean} - True if it's a CivitAI URL
*/
export function isCivitaiUrl(url) {
if (!url) return false;
try {
const parsed = new URL(url);
return parsed.hostname.toLowerCase() === 'image.civitai.com';
} catch (e) {
return false;
}
}