diff --git a/server/search-filters.mjs b/server/search-filters.mjs index 98c66bd..3a40d61 100644 --- a/server/search-filters.mjs +++ b/server/search-filters.mjs @@ -197,6 +197,8 @@ export function isShortItem(item, providerId) { } // 2) + 3) Orientation (width/height quand le provider les expose). + // Dimensions connues ⇒ l'orientation fait AUTORITÉ : une horizontale/carrée + // n'est jamais un short, même courte. const w = numOrZero(item.width); const h = numOrZero(item.height); if (w > 0 && h > 0) { @@ -205,9 +207,10 @@ export function isShortItem(item, providerId) { if (!hasKnownDuration) return false; // règle 3 : pas de durée ⇒ pas short return d <= VERTICAL_SHORT_MAX_SECONDS; // règle 2 } + return false; } - // 4) Repli durée seule (comportement historique inchangé). + // 4) Repli durée seule, uniquement quand l'orientation est inconnue. // NOTE : l'ancien code excluait les titres contenant « short » // (`&& !/short/i.test(title)`), ce qui est l'inverse de l'intuition : un // vrai Short titré « … #shorts » était EXCLU du filtre shorts. Les tests diff --git a/server/tests/search-filters.test.mjs b/server/tests/search-filters.test.mjs index 667ceeb..52089cb 100644 --- a/server/tests/search-filters.test.mjs +++ b/server/tests/search-filters.test.mjs @@ -209,15 +209,21 @@ expect( !isShortItem({ title: 'clip carré', duration: 80, width: 1080, height: 1080, type: 'video' }), 'square 80s is not a short (orientation alone is not enough)', ); -// …mais à 30 s, la règle 4 (durée seule) le garde : non-régression historique. +// Dimensions connues non verticales ⇒ JAMAIS short, même à 30 s +// (l'orientation fait autorité ; plus de repli durée quand w/h sont connus). expect( - matchesFilters({ id: 'a', title: 'clip carré', duration: 30, width: 1080, height: 1080, type: 'video' }, { type: 'shorts' }), - 'square 30s still matches shorts via duration fallback', + !matchesFilters({ id: 'a', title: 'clip carré', duration: 30, width: 1080, height: 1080, type: 'video' }, { type: 'shorts' }), + 'square 30s is not a short (orientation authoritative)', ); -// Règle 4 : horizontale 50 s ⇒ short (non-régression durée seule). +// Horizontale 50 s ⇒ PAS short (dimensions connues, non verticale). expect( - isShortItem({ title: 'paysage', duration: 50, width: 1280, height: 720, type: 'video' }), - 'horizontal 50s is a short (duration fallback)', + !isShortItem({ title: 'paysage', duration: 50, width: 1280, height: 720, type: 'video' }), + 'horizontal 50s is not a short (orientation authoritative)', +); +// Repli durée seule réservé aux items SANS dimensions connues. +expect( + isShortItem({ title: 'courte sans dims', duration: 50, type: 'video' }), + '50s without dimensions is a short (duration fallback)', ); // Règle 3 : verticale SANS durée connue ⇒ PAS short. expect( diff --git a/src/app/shared/utils/video-kind.spec.ts b/src/app/shared/utils/video-kind.spec.ts index 21669c4..880fc11 100644 --- a/src/app/shared/utils/video-kind.spec.ts +++ b/src/app/shared/utils/video-kind.spec.ts @@ -63,9 +63,9 @@ assertEqual( false, 'verticale durée 0 ⇒ pas short', ); -// Règle 4 : repli durée seule (non-régression). -assertEqual(isShortVideo({ duration: 50, width: 1280, height: 720 }), true, 'horizontale 50 s ⇒ short'); -assertEqual(isShortVideo({ duration: 50 }), true, '50 s sans dimensions ⇒ short'); +// Dimensions connues non verticales ⇒ jamais short (orientation autoritaire). +assertEqual(isShortVideo({ duration: 50, width: 1280, height: 720 }), false, 'horizontale 50 s (dims connues) ⇒ pas short'); +assertEqual(isShortVideo({ duration: 50 }), true, '50 s sans dimensions ⇒ short (repli durée)'); assertEqual(isShortVideo({ duration: 72 }), true, '72 s non-YouTube ⇒ short (≤ 75)'); assertEqual(isShortVideo({ duration: 72, provider: 'youtube' }), false, '72 s YouTube ⇒ pas short (> 70)'); assertEqual(isShortVideo({ duration: 0 }), false, 'durée 0 ⇒ jamais short'); diff --git a/src/app/shared/utils/video-kind.ts b/src/app/shared/utils/video-kind.ts index 476b765..dd9f91a 100644 --- a/src/app/shared/utils/video-kind.ts +++ b/src/app/shared/utils/video-kind.ts @@ -6,11 +6,12 @@ * construction ; une durée aberrante est du bruit, pas une preuve). * 2. Flag natif (`isShort`, `type === 'short'`, URL `/shorts/`) ⇒ Short, * sauf durée connue > SHORT_MAX_SECONDS. - * 3. Orientation verticale stricte (`height > width`, ratio ≤ 0.8) ET durée - * connue ≤ VERTICAL_SHORT_MAX_SECONDS (90 s) ⇒ Short. - * 4. Verticale SANS durée connue ⇒ PAS un Short (même doctrine que - * « durée 0 ne qualifie jamais »). - * 5. Repli durée seule : 1..70 s (YouTube), 1..75 s (autres). + * 3. Dimensions connues : l'orientation fait autorité. + * - verticale (`height > width`, ratio ≤ 0.8) ET durée connue ≤ 90 s ⇒ Short ; + * - verticale sans durée connue ⇒ PAS un Short ; + * - horizontale/carrée ⇒ JAMAIS un Short (même si ≤ 70/75 s). + * 4. Dimensions inconnues (YouTube Data API, Dailymotion, PeerTube, Rumble…) : + * repli durée seule : 1..70 s (YouTube), 1..75 s (autres). */ export type VideoKind = 'short' | 'live' | 'video'; @@ -79,17 +80,22 @@ export function isShortVideo(v: ClassifiableVideo | null | undefined): boolean { } // 3) + 4) Orientation (width/height quand le provider les expose). + // DÈS QUE les dimensions sont connues, l'orientation fait AUTORITÉ : une + // vidéo horizontale/carrée n'est jamais un short, même si elle est courte. const w = numOrZero((v as { width?: unknown }).width); const h = numOrZero((v as { height?: unknown }).height); if (w > 0 && h > 0) { const vertical = h > w && w / h <= VERTICAL_MAX_RATIO; if (vertical) { + // Verticale sans durée connue ⇒ PAS un short (doctrine historique). if (!hasKnownDuration) return false; return duration <= VERTICAL_SHORT_MAX_SECONDS; } + return false; } - // 5) Repli durée seule (comportement historique inchangé). + // 5) Repli durée seule, UNIQUEMENT quand le provider n'expose pas + // l'orientation (YouTube Data API, Dailymotion, PeerTube, Rumble…). if (!hasKnownDuration) return false; if (provider === 'youtube') return duration <= YOUTUBE_SHORT_MAX_SECONDS; return duration <= SHORT_MAX_SECONDS; diff --git a/src/components/shorts/watch-short.component.html b/src/components/shorts/watch-short.component.html index 4605aa5..2458639 100644 --- a/src/components/shorts/watch-short.component.html +++ b/src/components/shorts/watch-short.component.html @@ -1,7 +1,7 @@ -