feat(history): miniatures des vidéos importées (i.ytimg déterministe)
This commit is contained in:
@@ -0,0 +1,7 @@
|
||||
-- Backfill des miniatures YouTube manquantes (imports Takeout sans vignette).
|
||||
-- Les miniatures YouTube sont déterministes : i.ytimg.com/vi/<id>/hqdefault.jpg
|
||||
UPDATE watch_history
|
||||
SET thumbnail = 'https://i.ytimg.com/vi/' || video_id || '/hqdefault.jpg'
|
||||
WHERE provider = 'youtube'
|
||||
AND (thumbnail IS NULL OR thumbnail = '')
|
||||
AND length(video_id) = 11;
|
||||
+6
-1
@@ -893,13 +893,18 @@ r.post('/user/history/takeout', authMiddleware, oauthLimiter, (req, res) => {
|
||||
const videoId = String(it?.videoId || '').trim().slice(0, 64);
|
||||
if (!/^[A-Za-z0-9_-]{6,64}$/.test(videoId)) { skipped++; continue; }
|
||||
const title = String(it?.title || '').slice(0, 300) || videoId;
|
||||
// Miniature YouTube déterministe quand Takeout n'en fournit pas.
|
||||
let thumbnail = String(it?.thumbnail || '').slice(0, 500);
|
||||
if (!thumbnail && /^[A-Za-z0-9_-]{11}$/.test(videoId)) {
|
||||
thumbnail = `https://i.ytimg.com/vi/${videoId}/hqdefault.jpg`;
|
||||
}
|
||||
let watchedAt = new Date().toISOString();
|
||||
if (it?.watchedAt) {
|
||||
const d = new Date(String(it.watchedAt));
|
||||
if (!Number.isNaN(d.getTime())) watchedAt = d.toISOString();
|
||||
}
|
||||
try {
|
||||
upsertWatchHistory({ userId: req.user.id, provider: 'youtube', videoId, title, watchedAt });
|
||||
upsertWatchHistory({ userId: req.user.id, provider: 'youtube', videoId, title, thumbnail, watchedAt });
|
||||
imported++;
|
||||
} catch { skipped++; }
|
||||
}
|
||||
|
||||
@@ -193,7 +193,7 @@
|
||||
<a [routerLink]="['/watch', w.video_id]" [queryParams]="{ p: w.provider }" class="block p-4 pr-24 hover:no-underline">
|
||||
<div class="flex">
|
||||
<div class="relative flex-shrink-0 w-24 h-16 bg-slate-700 rounded overflow-hidden flex items-center justify-center">
|
||||
@if (w.thumbnail) { <img [src]="w.thumbnail" alt="" class="w-full h-full object-cover" /> }
|
||||
@if (thumbOf(w)) { <img [src]="thumbOf(w)" alt="" loading="lazy" class="w-full h-full object-cover" /> }
|
||||
<div class="absolute bottom-0 left-0 right-0 h-1 bg-slate-600">
|
||||
<div class="h-full bg-red-500" [style.width.%]="getProgressPercentage(w)"></div>
|
||||
</div>
|
||||
|
||||
@@ -311,6 +311,16 @@ export class HistoryComponent {
|
||||
});
|
||||
|
||||
// ============ Helpers affichage (conservés) ============
|
||||
/** Miniature : stockée, sinon déterministe YouTube (imports Takeout sans vignette). */
|
||||
thumbOf(w: WatchHistoryItem): string {
|
||||
if (w?.thumbnail) return w.thumbnail;
|
||||
const id = String(w?.video_id || '');
|
||||
if ((w?.provider === 'youtube' || w?.provider === 'yt') && /^[A-Za-z0-9_-]{11}$/.test(id)) {
|
||||
return `https://i.ytimg.com/vi/${id}/hqdefault.jpg`;
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
formatDate(dateString: string | Date): string {
|
||||
if (!dateString) return '';
|
||||
const date = new Date(dateString);
|
||||
|
||||
Reference in New Issue
Block a user