Detect and badge HDR10+ (SMPTE ST 2094-40) via ffmpeg showinfo
This commit is contained in:
@@ -19,6 +19,7 @@
|
||||
<div class="version-badges">
|
||||
<span v-if="torrent.resolution" class="v-badge res">{{ torrent.resolution }}</span>
|
||||
<span v-if="showHdrBadge" class="v-badge hdr">HDR</span>
|
||||
<span v-if="showHdr10PlusBadge" class="v-badge hdr10plus">HDR10+</span>
|
||||
<span v-if="displayCodecBadge" class="v-badge codec">{{ displayCodecBadge }}</span>
|
||||
<span v-if="displayQualityBadge" class="v-badge qual">{{ displayQualityBadge }}</span>
|
||||
<span v-if="displayAudioBadge" class="v-badge audio">{{ displayAudioBadge }}</span>
|
||||
@@ -317,6 +318,18 @@ const showHdrBadge = computed(() => {
|
||||
)
|
||||
})
|
||||
|
||||
const hdr10PlusPattern = /hdr10\+|hdr10plus/i
|
||||
|
||||
const hasHdr10Plus = computed(() => {
|
||||
if (props.torrent.hdr10plus) return true
|
||||
const text = [props.torrent.title, props.torrent.quality, props.torrent.codec, props.torrent.audio]
|
||||
.filter(Boolean)
|
||||
.join(" ")
|
||||
return hdr10PlusPattern.test(text)
|
||||
})
|
||||
|
||||
const showHdr10PlusBadge = computed(() => hasHdr10Plus.value && !hasDolbyVision.value)
|
||||
|
||||
const isSelectable = computed(() => {
|
||||
if (props.selectable !== undefined) return props.selectable
|
||||
return Boolean(props.torrent.playable_file)
|
||||
@@ -525,6 +538,11 @@ html.mouse-active .version-row.version-best:hover {
|
||||
color: #1c1917;
|
||||
}
|
||||
|
||||
.v-badge.hdr10plus {
|
||||
background: #b8860b;
|
||||
color: #fff8e1;
|
||||
}
|
||||
|
||||
.v-badge.group {
|
||||
background: #1c54a1;
|
||||
color: #f8fafc;
|
||||
|
||||
@@ -432,6 +432,8 @@ _dovi_profile_re = re.compile(
|
||||
)
|
||||
_audio_stream_re = re.compile(r"Stream #\d+:\d+(?:\(([^)]+)\))?:\s+Audio:")
|
||||
_subtitle_stream_re = re.compile(r"Stream #\d+:\d+(?:\(([^)]+)\))?:\s+Subtitle:")
|
||||
# showinfo emits e.g. "side data - HDR Dynamic Metadata SMPTE2094-40 (HDR10+)"
|
||||
_showinfo_hdr10plus_re = re.compile(r"SMPTE2094-40|HDR Dynamic Metadata", re.IGNORECASE)
|
||||
|
||||
|
||||
def _lang_code(raw: str | None) -> str | None:
|
||||
@@ -492,6 +494,35 @@ async def probe_media_info(video_path: str) -> MediaProbeInfo:
|
||||
or "dynamic hdr" in lower_text
|
||||
)
|
||||
|
||||
# `ffmpeg -i` only reads container headers, not frame-level side data.
|
||||
# Run a tiny showinfo decode (5 frames) to detect HDR10+ dynamic metadata
|
||||
# (SMPTE ST 2094-40) when the header scan didn't already confirm it.
|
||||
if info.hdr and not info.hdr10plus and not info.dovi:
|
||||
showinfo_cmd = [
|
||||
"ffmpeg",
|
||||
"-hide_banner",
|
||||
"-ss",
|
||||
"0",
|
||||
"-i",
|
||||
video_path,
|
||||
"-vf",
|
||||
"showinfo",
|
||||
"-frames:v",
|
||||
"5",
|
||||
"-an",
|
||||
"-f",
|
||||
"null",
|
||||
"-",
|
||||
]
|
||||
showinfo_result = await _run_ffmpeg(
|
||||
showinfo_cmd, timeout_seconds=15, allow_nonzero_exit=True
|
||||
)
|
||||
if showinfo_result is not None:
|
||||
si_stdout, si_stderr = showinfo_result
|
||||
si_text = (si_stderr + si_stdout).decode("utf-8", errors="replace")
|
||||
if _showinfo_hdr10plus_re.search(si_text):
|
||||
info.hdr10plus = True
|
||||
|
||||
dovi_match = _dovi_profile_re.search(text)
|
||||
if dovi_match:
|
||||
info.dovi_profile = int(dovi_match.group(1))
|
||||
|
||||
Reference in New Issue
Block a user