This commit is contained in:
1 parent
6bee9bd9a3
commit
9ddc13e352
2 files changed
+84
-3
No files matched your search
@@ -12,10 +12,62 @@ an AVR input, not something the rest of the panel depends on.
|
||||
"""
|
||||
|
||||
import json
|
||||
import re
|
||||
import urllib.parse
|
||||
import urllib.request
|
||||
|
||||
|
||||
# What the card has room for, out of the Zidoo's own wording: the longest
|
||||
# match wins, so "Dolby Digital Plus" is never read as plain "Dolby Digital".
|
||||
DYNAMIC_RANGES = {"dolby vision": "Dolby Vision", "dovi": "Dolby Vision", "hdr10+": "HDR10+",
|
||||
"hdr10": "HDR10", "hdr": "HDR", "hlg": "HLG", "sdr": "SDR"}
|
||||
AUDIO_CODECS = {"dolby digital plus": "DD+", "e-ac3": "DD+", "eac3": "DD+",
|
||||
"dolby digital": "DD", "ac3": "DD", "truehd": "TrueHD",
|
||||
"dts:x": "DTS:X", "dts-x": "DTS:X", "dts-hd ma": "DTS-HD MA",
|
||||
"dts-hd master audio": "DTS-HD MA", "dts-hd": "DTS-HD", "dts": "DTS",
|
||||
"aac": "AAC", "flac": "FLAC", "lpcm": "PCM", "pcm": "PCM", "opus": "Opus"}
|
||||
|
||||
|
||||
def _find(text, names):
|
||||
"""The short name of the longest of `names` found in `text`, or None."""
|
||||
lowered = text.lower()
|
||||
hits = [key for key in names if re.search(rf"(?<![\w+]){re.escape(key)}(?![\w+])", lowered)]
|
||||
return names[max(hits, key=len)] if hits else None
|
||||
|
||||
|
||||
def short_output(output):
|
||||
""""3840X2160P @ 23.976Hz YUV422 12Bits SDR" -> "2160p 422 12b SDR":
|
||||
the picture's height, its chroma subsampling and bit depth, and its
|
||||
dynamic range. Anything not in that shape is passed on as it is."""
|
||||
if not output:
|
||||
return None
|
||||
size = re.search(r"\d+\s*[xX]\s*(\d+)\s*([PpIi])?", output)
|
||||
if not size:
|
||||
return output
|
||||
height = size.group(1) + (size.group(2) or "p").lower()
|
||||
# YUV422 or YCbCr444 down to the digits, which are what tell them apart;
|
||||
# RGB has no subsampling to give, so it keeps its name.
|
||||
chroma = re.search(r"(?:YUV|YCbCr)\s*(4[24][024])|\b(RGB)\b", output, re.I)
|
||||
chroma = (chroma.group(1) or chroma.group(2).upper()) if chroma else None
|
||||
bits = re.search(r"(\d+)\s*-?\s*bits?\b", output, re.I)
|
||||
bits = f"{bits.group(1)}b" if bits else None
|
||||
return " ".join(filter(None, (height, chroma, bits, _find(output, DYNAMIC_RANGES))))
|
||||
|
||||
|
||||
def short_audio(audio):
|
||||
""""English Dolby Digital Plus with Dolby Atmos 48.0KHz 768kbps (3/3)"
|
||||
-> "DD+ Atmos": the codec, and Atmos or DTS:X's objects on top of it. A
|
||||
codec it does not know keeps the Zidoo's own words, minus the sample
|
||||
rate, bitrate and track count."""
|
||||
if not audio or audio.lower().startswith("off"):
|
||||
return None
|
||||
codec = _find(audio, AUDIO_CODECS)
|
||||
if codec is None:
|
||||
return re.sub(r"\s*(\d[\d.]*\s*k?hz|\d+\s*kbps|\(\d+/\d+\))", "", audio, flags=re.I).strip() or None
|
||||
atmos = "atmos" in audio.lower()
|
||||
return f"{codec} Atmos" if atmos else codec
|
||||
|
||||
|
||||
class ZidooError(RuntimeError):
|
||||
"""The Zidoo did not do what it was asked -- off, unreachable, or with
|
||||
nothing loaded to do it to."""
|
||||
@@ -54,7 +106,7 @@ class ZidooClient:
|
||||
"""{"song", "artist", "image"} for whatever video is loaded, in the
|
||||
same shape a room's now-playing card already expects -- or None.
|
||||
A film has no artist, so that line under the title carries its year
|
||||
instead. "image" is always None: the poster lives on the Zidoo, which
|
||||
instead -- or, with no year to give, the video output and audio track. "image" is always None: the poster lives on the Zidoo, which
|
||||
the phone cannot load over plain http, so "poster_id" names it
|
||||
instead for app.py to serve. "position_ms" and "duration_ms" come
|
||||
along whenever the length is known, as a room's do. "play_state" is
|
||||
@@ -69,7 +121,12 @@ class ZidooClient:
|
||||
return None
|
||||
film = self._film(video.get("path"))
|
||||
year = film.get("year")
|
||||
track = {"song": title, "artist": str(year) if year else None, "image": None,
|
||||
# Nothing to say under the title -- an episode, or a file outside the
|
||||
# library -- so say how it is playing: the picture going out to the
|
||||
# TV, then the audio track.
|
||||
playing_as = " · ".join(filter(None, (short_output(video.get("output")),
|
||||
short_audio(video.get("audioInfo")))))
|
||||
track = {"song": title, "artist": str(year) if year else playing_as or None, "image": None,
|
||||
"poster_id": film.get("id"),
|
||||
"play_state": "play" if video.get("status") == 1 else "pause"}
|
||||
if video.get("duration"):
|
||||
|
||||
Reference in new issue
Block a user