feat(ui): show WEB provider logos from PTN network metadata

This commit is contained in:
2026-05-23 02:41:08 +00:00
parent 6820937a4b
commit fbfe05588d
11 changed files with 54 additions and 6 deletions
Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

+46 -6
View File
@@ -18,6 +18,13 @@
<div class="version-main">
<div class="version-badges">
<span v-if="torrent.resolution" class="v-badge res">{{ torrent.resolution }}</span>
<img
v-if="streamingServiceLogo"
class="v-service-logo"
:src="streamingServiceLogo.src"
:alt="streamingServiceLogo.alt"
:title="streamingServiceLogo.alt"
>
<span v-if="displayQualityBadge" class="v-badge qual">{{ displayQualityBadge }}</span>
<span v-if="displayCodecBadge" class="v-badge codec">{{ displayCodecBadge }}</span>
<span v-if="showHdrBadge" class="v-badge hdr">HDR</span>
@@ -77,6 +84,11 @@ import DolbyBadges from './DolbyBadges.vue';
import { buildLanguageFlags } from '../utils/languageFlags';
import blurayLogoUrl from '../assets/bluray.webp';
import dvdLogoUrl from '../assets/dvd.webp';
import amazonLogoUrl from '../assets/service-amazon.webp';
import appleTvLogoUrl from '../assets/service-apple-tv.webp';
import netflixLogoUrl from '../assets/service-netflix.webp';
import hboMaxLogoUrl from '../assets/service-hbo-max.webp';
import huluLogoUrl from '../assets/service-hulu.webp';
defineOptions({
inheritAttrs: false,
@@ -116,6 +128,15 @@ const hdrPattern = /\bhdr\b|smpte\s*2084|bt\s*2020|hlg/i;
const blurayTagPattern = /\bblu[\s.-]*ray\b/i;
const blurayPlayablePattern = /(?:^|[\\/])(movieobject|index)\.bdmv$/i;
const dvdPlayablePattern = /(?:^|[\\/])video_ts\.ifo$/i;
const webQualityPattern = /^web(?:[ .-]?dl|[ .-]?rip)$/i;
const serviceLogoMap: Array<{ aliases: string[]; src: string; alt: string }> = [
{ aliases: ['amazon studios', 'amazon prime video', 'prime video', 'amazon', 'amzn'], src: amazonLogoUrl, alt: 'Amazon Prime Video' },
{ aliases: ['apple tv+', 'apple tv plus', 'apple tv', 'atvp'], src: appleTvLogoUrl, alt: 'Apple TV+' },
{ aliases: ['netflix', 'nf', 'nflx'], src: netflixLogoUrl, alt: 'Netflix' },
{ aliases: ['hbo max', 'max', 'hmax'], src: hboMaxLogoUrl, alt: 'HBO Max' },
{ aliases: ['hulu'], src: huluLogoUrl, alt: 'Hulu' },
];
function hasDolbyTag(value: string | null | undefined): boolean {
return Boolean(value && dolbyTagPattern.test(value));
@@ -137,6 +158,10 @@ function hasHdrTag(value: string | null | undefined): boolean {
return Boolean(value && hdrPattern.test(value));
}
function normalizeProviderName(value: string | null | undefined): string {
return (value || '').toLowerCase().replace(/[^a-z0-9+]+/g, ' ').trim();
}
const hasDolbyVision = computed(() => {
return (
props.torrent.has_dolby_vision === true
@@ -158,12 +183,6 @@ const hasHdr = computed(() => {
);
});
const isDisc = computed(() => {
if (!props.torrent.playable_file) return false;
const filename = props.torrent.playable_file;
return blurayPlayablePattern.test(filename) || dvdPlayablePattern.test(filename) || filename.toLowerCase().endsWith('.iso');
});
const isBlurayDisc = computed(() => {
if (!props.torrent.playable_file) return false;
return blurayPlayablePattern.test(props.torrent.playable_file);
@@ -182,8 +201,21 @@ const showDvdLogo = computed(() => {
return isDvdDisc.value;
});
const streamingServiceLogo = computed(() => {
if (!props.torrent.quality || !webQualityPattern.test(props.torrent.quality)) {
return null;
}
const network = normalizeProviderName(props.torrent.network);
if (!network) return null;
const found = serviceLogoMap.find((entry) => entry.aliases.includes(network));
return found ? { src: found.src, alt: found.alt } : null;
});
const displayQualityBadge = computed(() => {
if (!props.torrent.quality || hasDolbyTag(props.torrent.quality)) return null;
if (streamingServiceLogo.value) return null;
if (blurayTagPattern.test(props.torrent.quality)) return null;
return props.torrent.quality;
});
@@ -324,6 +356,14 @@ function handleActivate(event: MouseEvent | KeyboardEvent) {
text-transform: uppercase;
}
.v-service-logo {
align-self: center;
width: auto;
height: 16px;
object-fit: contain;
margin-right: 2px;
}
.v-badge.res {
background: #1d4ed8;
color: #eff6ff;
+1
View File
@@ -45,6 +45,7 @@ export interface Torrent {
playable_file: string | null;
resolution: string | null;
quality: string | null;
network: string | null;
codec: string | null;
audio: string | null;
audio_languages: string[] | null;
+4
View File
@@ -71,6 +71,7 @@ async def _build_torrent_info(
playable_file=make_relative_path(playable_file, media_root),
resolution=(probe_info.resolution if probe_info else None) or item.resolution,
quality=item.quality,
network=item.network,
codec=item.codec,
audio=item.audio,
audio_languages=probe_info.audio_languages if probe_info else None,
@@ -159,6 +160,7 @@ async def _collect_episode_files(
"has_dolby_atmos": probe.has_dolby_atmos,
"resolution": item.resolution,
"quality": item.quality,
"network": item.network,
"codec": item.codec,
"audio": item.audio,
"encoder": item.encoder,
@@ -206,6 +208,7 @@ async def _collect_episode_files(
"has_dolby_atmos": probe.has_dolby_atmos,
"resolution": item.resolution,
"quality": item.quality,
"network": item.network,
"codec": item.codec,
"audio": item.audio,
"encoder": item.encoder,
@@ -265,6 +268,7 @@ def _build_episodes_data(
playable_file=make_relative_path(f["path"], media_root),
resolution=f.get("probed_resolution") or f.get("resolution"),
quality=f.get("quality"),
network=f.get("network"),
codec=f.get("codec"),
audio=f.get("audio"),
audio_languages=f.get("audio_languages"),
+1
View File
@@ -41,6 +41,7 @@ class ParsedContent:
year: Optional[int] = None
resolution: Optional[str] = None
quality: Optional[str] = None
network: Optional[str] = None
codec: Optional[str] = None
audio: Optional[str] = None
season: Optional[int] = None
+1
View File
@@ -39,6 +39,7 @@ async def parse_download(path: Path) -> ParsedContent:
year=parsed.get("year"),
resolution=normalize_resolution_label(parsed.get("resolution")),
quality=parsed.get("quality"),
network=parsed.get("network"),
codec=parsed.get("codec"),
audio=parsed.get("audio"),
season=parsed.get("season"),
+1
View File
@@ -22,6 +22,7 @@ class Torrent(msgspec.Struct):
playable_file: str | None = None
resolution: str | None = None
quality: str | None = None
network: str | None = None
codec: str | None = None
audio: str | None = None
audio_languages: list[str] | None = None