Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 18 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,8 +88,8 @@ stream with two producers:

```yaml
xmeye_<entry_id>_ch<N>_camera:
- rtsp://<dvr>:554/user=…&password=…&channel=N&stream=0.sdp # H.265, passthrough
- ffmpeg:xmeye_<entry_id>_ch<N>_camera#video=h264#audio=opus # H.264, on demand
- rtsp://<dvr>:554/user=…&password=…&channel=N&stream=0.sdp # H.265, passthrough
- ffmpeg:xmeye_<entry_id>_ch<N>_camera#video=h264#audio=opus#bitrate=2048k # H.264, on demand
```

go2rtc walks the producers in order and only dials the second one when the first one's
Expand All @@ -103,6 +103,22 @@ audio-only variant — its provider skips the rewrite when the stream already ex
of its producers matches the camera's stream source. The registration is re-asserted on
every `stream_source()` call, so it self-heals.

### Options

Both settings live in the integration's options (⚙️ on the device page) and apply per
device. Changing either reloads the entry and re-registers the streams.

- **Transcode H.265 to H.264** (default on). Turn it off and the integration registers
nothing with go2rtc, leaving H.265 handling to Home Assistant's defaults — live view then
only works in clients that decode HEVC. The repair notices disappear too, since the
trade-off was made deliberately.
- **H.264 transcode bitrate cap** (default 2048 kbit/s). go2rtc's built-in h264 template
carries no rate control, so x264 falls back to CRF 23 and spends bits preserving the
compression artefacts the DVR already introduced. On a 2560×1440 main stream that
measured **6.6 Mbit/s out of a 0.73 Mbit/s H.265 source** — nine times the input. The cap
becomes `-b:v/-maxrate/-bufsize` on the encoder. Raise it if the picture looks soft on
busy scenes; it only affects the transcoded path, never what HEVC-capable clients receive.

Notes:

- **Any go2rtc works.** The integration reuses the connection the `go2rtc` integration
Expand Down
39 changes: 36 additions & 3 deletions custom_components/xmeye/camera.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@
CODEC_H264,
CODEC_H265,
CODEC_UNKNOWN,
CONF_TRANSCODE_BITRATE,
CONF_TRANSCODE_H265,
DEFAULT_TRANSCODE_BITRATE,
DEFAULT_TRANSCODE_H265,
DOMAIN,
REPAIR_H265_NO_GO2RTC,
REPAIR_H265_TRANSCODING,
Expand Down Expand Up @@ -446,10 +450,22 @@ async def _maybe_register_go2rtc(self) -> None:
"""Arm go2rtc transcoding if the chosen stream turned out to be H.265."""
if self._stream_url is None or self._codec != CODEC_H265:
return
options = self._coordinator.entry.options
if not options.get(CONF_TRANSCODE_H265, DEFAULT_TRANSCODE_H265):
_LOGGER.info(
"ch%d is H.265 but transcoding is disabled for this device — "
"leaving the stream to Home Assistant's defaults",
self._channel + 1,
)
# Registers nothing; called only so any advisory left over from
# before it was switched off gets cleared.
await self._ensure_go2rtc_stream(self._stream_url)
return
_LOGGER.info(
"ch%d is H.265 — registering with go2rtc (H.265 passthrough + "
"on-demand H.264 transcode)",
"on-demand H.264 transcode capped at %d kbit/s)",
self._channel + 1,
int(options.get(CONF_TRANSCODE_BITRATE, DEFAULT_TRANSCODE_BITRATE)),
)
if not await self._ensure_go2rtc_stream(self._stream_url):
_LOGGER.debug(
Expand All @@ -467,7 +483,7 @@ async def _ensure_go2rtc_stream(self, url: str) -> bool:

1. the DVR's RTSP URL — H.265 handed through untouched to clients
that can decode it (VLC, the mobile apps, Safari);
2. ``ffmpeg:<identifier>#video=h264#audio=opus`` — go2rtc's
2. ``ffmpeg:<identifier>#video=h264#audio=opus#bitrate=…`` — go2rtc's
``AddConsumer`` walks producers in order and only dials this one
when the previous producer's codecs do not match the consumer, so
the transcode costs nothing until a browser without HEVC asks.
Expand All @@ -478,10 +494,27 @@ async def _ensure_go2rtc_stream(self, url: str) -> bool:
see ``homeassistant/components/go2rtc/__init__.py``:
``_update_stream_source`` skips the rewrite when the stream exists
and one of its producers matches ``await camera.stream_source()``.

Returns False without touching go2rtc when the user has turned
transcoding off for this device.
"""
options = self._coordinator.entry.options
if not options.get(CONF_TRANSCODE_H265, DEFAULT_TRANSCODE_H265):
# Deliberate choice by the user: register nothing and let HA
# handle the H.265 stream its own way. Dropping the channel also
# clears both advisories — nobody should be nagged about a
# trade-off they made on purpose.
if self._coordinator.h265_channels.pop(self._channel, None) is not None:
_refresh_h265_issues(self.hass, self._coordinator)
return False

client: Go2RTCClient | None = self._coordinator.go2rtc_client
identifier = _go2rtc_identifier(self)
sources = [url, f"ffmpeg:{identifier}#video=h264#audio=opus"]
bitrate = int(options.get(CONF_TRANSCODE_BITRATE, DEFAULT_TRANSCODE_BITRATE))
sources = [
url,
f"ffmpeg:{identifier}#video=h264#audio=opus#bitrate={bitrate}k",
]
ok = False
if client is not None:
try:
Expand Down
28 changes: 28 additions & 0 deletions custom_components/xmeye/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,14 +33,20 @@
CONF_DEVICE_TYPE,
CONF_MOTION_CLEAR_DELAY,
CONF_STORAGE_REFRESH_INTERVAL,
CONF_TRANSCODE_BITRATE,
CONF_TRANSCODE_H265,
CONFIG_ENTRY_VERSION,
DEFAULT_MOTION_CLEAR_DELAY,
DEFAULT_PORT,
DEFAULT_STORAGE_REFRESH_INTERVAL,
DEFAULT_TRANSCODE_BITRATE,
DEFAULT_TRANSCODE_H265,
DEFAULT_USERNAME,
DOMAIN,
MAX_STORAGE_REFRESH_INTERVAL,
MAX_TRANSCODE_BITRATE,
MIN_STORAGE_REFRESH_INTERVAL,
MIN_TRANSCODE_BITRATE,
UDP_DISCOVERY_PORT,
)

Expand Down Expand Up @@ -366,6 +372,8 @@ async def async_step_init(
CONF_STORAGE_REFRESH_INTERVAL: int(
user_input[CONF_STORAGE_REFRESH_INTERVAL]
),
CONF_TRANSCODE_H265: bool(user_input[CONF_TRANSCODE_H265]),
CONF_TRANSCODE_BITRATE: int(user_input[CONF_TRANSCODE_BITRATE]),
},
)

Expand All @@ -376,6 +384,12 @@ async def async_step_init(
current_interval = int(
options.get(CONF_STORAGE_REFRESH_INTERVAL, DEFAULT_STORAGE_REFRESH_INTERVAL)
)
current_transcode = bool(
options.get(CONF_TRANSCODE_H265, DEFAULT_TRANSCODE_H265)
)
current_bitrate = int(
options.get(CONF_TRANSCODE_BITRATE, DEFAULT_TRANSCODE_BITRATE)
)
return self.async_show_form(
step_id="init",
data_schema=vol.Schema({
Expand All @@ -399,5 +413,19 @@ async def async_step_init(
mode=NumberSelectorMode.SLIDER,
)
),
vol.Optional(
CONF_TRANSCODE_H265, default=current_transcode
): bool,
vol.Optional(
CONF_TRANSCODE_BITRATE, default=current_bitrate
): NumberSelector(
NumberSelectorConfig(
min=MIN_TRANSCODE_BITRATE,
max=MAX_TRANSCODE_BITRATE,
step=256,
unit_of_measurement="kbit/s",
mode=NumberSelectorMode.SLIDER,
)
),
}),
)
14 changes: 14 additions & 0 deletions custom_components/xmeye/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,26 @@
CONF_DEVICE_TYPE = "device_type"
CONF_MOTION_CLEAR_DELAY = "motion_clear_delay"
CONF_STORAGE_REFRESH_INTERVAL = "storage_refresh_interval"
CONF_TRANSCODE_H265 = "transcode_h265"
CONF_TRANSCODE_BITRATE = "transcode_bitrate"

DEFAULT_MOTION_CLEAR_DELAY = 30 # seconds; 0 = disable debounce
DEFAULT_STORAGE_REFRESH_INTERVAL = 300 # seconds (5 min)
MIN_STORAGE_REFRESH_INTERVAL = 60 # 1 min
MAX_STORAGE_REFRESH_INTERVAL = 3600 # 1 hour

# Whether to hand H.265 channels to go2rtc for on-demand H.264 transcoding.
DEFAULT_TRANSCODE_H265 = True

# Bitrate cap for that transcode, in kbit/s. go2rtc's built-in h264 template
# carries no rate control at all, so x264 falls back to CRF 23 and spends bits
# preserving the compression artefacts the DVR already introduced — a 2K main
# stream measured 6.6 Mbit/s out of a 0.73 Mbit/s H.265 source. `#bitrate=` is
# applied after the codec args, so it overrides that.
DEFAULT_TRANSCODE_BITRATE = 2048
MIN_TRANSCODE_BITRATE = 256
MAX_TRANSCODE_BITRATE = 8192

# DVRIP message IDs
MSG_LOGIN = 1000
MSG_LOGIN_RSP = 1001
Expand Down
2 changes: 1 addition & 1 deletion custom_components/xmeye/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,5 @@
"issue_tracker": "https://github.com/equake/hass-xmeye/issues",
"quality_scale": "silver",
"requirements": [],
"version": "0.8.1"
"version": "0.8.2"
}
4 changes: 3 additions & 1 deletion custom_components/xmeye/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,9 @@
"description": "Adjust behaviour for this device.",
"data": {
"motion_clear_delay": "Motion clear delay (0 = immediate)",
"storage_refresh_interval": "Storage (HDD) refresh interval"
"storage_refresh_interval": "Storage (HDD) refresh interval",
"transcode_h265": "Transcode H.265 to H.264 for browsers without HEVC",
"transcode_bitrate": "H.264 transcode bitrate cap"
}
}
}
Expand Down
4 changes: 3 additions & 1 deletion custom_components/xmeye/translations/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,9 @@
"init": {
"data": {
"motion_clear_delay": "Verzögerung bis Bewegungsmeldung endet (0 = sofort)",
"storage_refresh_interval": "Aktualisierungsintervall für Speicher (HDD)"
"storage_refresh_interval": "Aktualisierungsintervall für Speicher (HDD)",
"transcode_bitrate": "Bitratenlimit der H.264-Transkodierung",
"transcode_h265": "H.265 für Browser ohne HEVC nach H.264 transkodieren"
},
"description": "Verhalten dieses Geräts anpassen.",
"title": "XMEye / Sofia Optionen"
Expand Down
4 changes: 3 additions & 1 deletion custom_components/xmeye/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,9 @@
"init": {
"data": {
"motion_clear_delay": "Motion clear delay (0 = immediate)",
"storage_refresh_interval": "Storage (HDD) refresh interval"
"storage_refresh_interval": "Storage (HDD) refresh interval",
"transcode_bitrate": "H.264 transcode bitrate cap",
"transcode_h265": "Transcode H.265 to H.264 for browsers without HEVC"
},
"description": "Adjust behaviour for this device.",
"title": "XMEye / Sofia Options"
Expand Down
4 changes: 3 additions & 1 deletion custom_components/xmeye/translations/es.json
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,9 @@
"init": {
"data": {
"motion_clear_delay": "Retraso al limpiar detección de movimiento (0 = inmediato)",
"storage_refresh_interval": "Intervalo de actualización del almacenamiento (HDD)"
"storage_refresh_interval": "Intervalo de actualización del almacenamiento (HDD)",
"transcode_bitrate": "Límite de bitrate de la transcodificación H.264",
"transcode_h265": "Transcodificar H.265 a H.264 para navegadores sin HEVC"
},
"description": "Ajuste el comportamiento de este dispositivo.",
"title": "Opciones de XMEye / Sofia"
Expand Down
4 changes: 3 additions & 1 deletion custom_components/xmeye/translations/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,9 @@
"init": {
"data": {
"motion_clear_delay": "Délai avant fin de la détection de mouvement (0 = immédiat)",
"storage_refresh_interval": "Intervalle de rafraîchissement du stockage (HDD)"
"storage_refresh_interval": "Intervalle de rafraîchissement du stockage (HDD)",
"transcode_bitrate": "Plafond de débit du transcodage H.264",
"transcode_h265": "Transcoder le H.265 en H.264 pour les navigateurs sans HEVC"
},
"description": "Ajustez le comportement de cet appareil.",
"title": "Options XMEye / Sofia"
Expand Down
4 changes: 3 additions & 1 deletion custom_components/xmeye/translations/it.json
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,9 @@
"init": {
"data": {
"motion_clear_delay": "Ritardo di chiusura rilevamento movimento (0 = immediato)",
"storage_refresh_interval": "Intervallo di aggiornamento archiviazione (HDD)"
"storage_refresh_interval": "Intervallo di aggiornamento archiviazione (HDD)",
"transcode_bitrate": "Limite di bitrate della transcodifica H.264",
"transcode_h265": "Transcodifica H.265 in H.264 per i browser senza HEVC"
},
"description": "Regolare il comportamento di questo dispositivo.",
"title": "Opzioni XMEye / Sofia"
Expand Down
4 changes: 3 additions & 1 deletion custom_components/xmeye/translations/ja.json
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,9 @@
"init": {
"data": {
"motion_clear_delay": "モーション検知解除の遅延 (0 = 即時)",
"storage_refresh_interval": "ストレージ(HDD)更新間隔"
"storage_refresh_interval": "ストレージ(HDD)更新間隔",
"transcode_bitrate": "H.264 トランスコードのビットレート上限",
"transcode_h265": "HEVC 非対応ブラウザー向けに H.265 を H.264 へトランスコード"
},
"description": "このデバイスの動作を調整します。",
"title": "XMEye / Sofia オプション"
Expand Down
4 changes: 3 additions & 1 deletion custom_components/xmeye/translations/nl.json
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,9 @@
"init": {
"data": {
"motion_clear_delay": "Vertraging bewegingsdetectie wissen (0 = onmiddellijk)",
"storage_refresh_interval": "Verversingsinterval opslag (HDD)"
"storage_refresh_interval": "Verversingsinterval opslag (HDD)",
"transcode_bitrate": "Bitratelimiet van de H.264-transcodering",
"transcode_h265": "H.265 naar H.264 transcoderen voor browsers zonder HEVC"
},
"description": "Pas het gedrag van dit apparaat aan.",
"title": "XMEye / Sofia opties"
Expand Down
4 changes: 3 additions & 1 deletion custom_components/xmeye/translations/pl.json
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,9 @@
"init": {
"data": {
"motion_clear_delay": "Opóźnienie czyszczenia wykrycia ruchu (0 = natychmiast)",
"storage_refresh_interval": "Interwał odświeżania pamięci masowej (HDD)"
"storage_refresh_interval": "Interwał odświeżania pamięci masowej (HDD)",
"transcode_bitrate": "Limit bitrate'u transkodowania H.264",
"transcode_h265": "Transkoduj H.265 na H.264 dla przeglądarek bez HEVC"
},
"description": "Dostosuj zachowanie tego urządzenia.",
"title": "Opcje XMEye / Sofia"
Expand Down
4 changes: 3 additions & 1 deletion custom_components/xmeye/translations/pt.json
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,9 @@
"init": {
"data": {
"motion_clear_delay": "Delay para limpar movimento (0 = imediato)",
"storage_refresh_interval": "Intervalo de atualização do storage (HDD)"
"storage_refresh_interval": "Intervalo de atualização do storage (HDD)",
"transcode_bitrate": "Teto de bitrate do transcode H.264",
"transcode_h265": "Transcodificar H.265 para H.264 em navegadores sem HEVC"
},
"description": "Ajuste o comportamento deste dispositivo.",
"title": "Opções XMEye / Sofia"
Expand Down
4 changes: 3 additions & 1 deletion custom_components/xmeye/translations/ru.json
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,9 @@
"init": {
"data": {
"motion_clear_delay": "Задержка сброса детекции движения (0 = мгновенно)",
"storage_refresh_interval": "Интервал обновления информации о хранилище (HDD)"
"storage_refresh_interval": "Интервал обновления информации о хранилище (HDD)",
"transcode_bitrate": "Ограничение битрейта перекодирования H.264",
"transcode_h265": "Перекодировать H.265 в H.264 для браузеров без HEVC"
},
"description": "Настройка поведения для этого устройства.",
"title": "Параметры XMEye / Sofia"
Expand Down
4 changes: 3 additions & 1 deletion custom_components/xmeye/translations/tr.json
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,9 @@
"init": {
"data": {
"motion_clear_delay": "Hareket algılama temizleme gecikmesi (0 = anında)",
"storage_refresh_interval": "Depolama (HDD) yenileme aralığı"
"storage_refresh_interval": "Depolama (HDD) yenileme aralığı",
"transcode_bitrate": "H.264 dönüştürme bit hızı üst sınırı",
"transcode_h265": "HEVC desteklemeyen tarayıcılar için H.265'i H.264'e dönüştür"
},
"description": "Bu cihazın davranışını ayarlayın.",
"title": "XMEye / Sofia Seçenekleri"
Expand Down
4 changes: 3 additions & 1 deletion custom_components/xmeye/translations/zh-Hans.json
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,9 @@
"init": {
"data": {
"motion_clear_delay": "移动侦测清除延迟 (0 = 立即)",
"storage_refresh_interval": "存储设备(硬盘)刷新间隔"
"storage_refresh_interval": "存储设备(硬盘)刷新间隔",
"transcode_bitrate": "H.264 转码码率上限",
"transcode_h265": "为不支持 HEVC 的浏览器把 H.265 转码为 H.264"
},
"description": "调整此设备的行为。",
"title": "XMEye / Sofia 选项"
Expand Down
Loading
Loading