Skip to content

Commit

Permalink
v2.10.3 (#1351)
Browse files Browse the repository at this point in the history
* Delete old snapshots #1330

* purge old snapshots when taking any snapshot

* Tweak RTMP and add RestreamIO #1333

* Increase MTX_WRITEQUEUESIZE for higher bitrates

* Fix typo

* restart stream on RTMP fail #1333

* Fix restore user data on restart #1334

* Use email from wyze api response directly

* Bump previous build to 2.9.12

* Update config.yml

* bump previous build

* Changelog
  • Loading branch information
mrlt8 authored Sep 13, 2024
1 parent 77ef445 commit baee602
Show file tree
Hide file tree
Showing 10 changed files with 234 additions and 167 deletions.
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,18 @@ You can then use the web interface at `http://localhost:5050` where `localhost`

See [basic usage](#basic-usage) for additional information or visit the [wiki page](https://github.com/mrlt8/docker-wyze-bridge/wiki/Home-Assistant) for additional information on using the bridge as a Home Assistant Add-on.

## What's Changed in v2.10.3

- FIX: Increased `MTX_WRITEQUEUESIZE` to prevent issues with higher bitrates.
- FIX: Restart RTMP livestream on fail (#1333)
- FIX: Restore user data on bridge restart (#1334)
- NEW: `SNAPSHOT_KEEP` Option to delete old snapshots when saving snapshots with a timelapse-like custom format with `SNAPSHOT_FORMAT`. (#1330)
- Example for 3 min: `SNAPSHOT_KEEP=180`, `SNAPSHOT_KEEP=180s`, `SNAPSHOT_KEEP=3m`
- Example for 3 days: `SNAPSHOT_KEEP=72h`, `SNAPSHOT_KEEP=3d`
- Example for 3 weeks: `SNAPSHOT_KEEP=21d`, `SNAPSHOT_KEEP=3w`
- NEW: `RESTREAMIO` option for livestreaming via [restream.io](https://restream.io). (#1333)
- Example `RESTREAMIO_FRONT_DOOR=re_My_Custom_Key123`

## What's Changed in v2.10.2

- FIX: day/night FPS slowdown for V4 cameras (#1287) Thanks @cdoolin and @Answer-1!
Expand Down Expand Up @@ -288,7 +300,9 @@ Honorable Mentions:
Video Streaming:

* [gtxaspec/wz_mini_hacks](https://github.com/gtxaspec/wz_mini_hacks) - Firmware level modification for Ingenic based cameras with an RTSP server and [self-hosted mode](https://github.com/gtxaspec/wz_mini_hacks/wiki/Configuration-File#self-hosted--isolated-mode) to use the cameras without the wyze services.
* [thingino](https://github.com/themactep/thingino-firmware) - Advanced custom firmware for some Ingenic-based wyze cameras.
* [carTloyal123/cryze](https://github.com/carTloyal123/cryze) - Stream video from wyze cameras (Gwell cameras) that use the Iotvideo SDK from Tencent Cloud.
* [xerootg/cryze_v2](https://github.com/xerootg/cryze_v2) - Stream video from wyze cameras (Gwell cameras) that use the Iotvideo SDK from Tencent Cloud.
* [mnakada/atomcam_tools](https://github.com/mnakada/atomcam_tools) - Video streaming for Wyze v3.

General Wyze:
Expand Down
1 change: 1 addition & 0 deletions app/.env
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,6 @@ MTX_HLSVARIANT=mpegts
MTX_PROTOCOLS=tcp
MTX_READTIMEOUT=20s
MTX_LOGLEVEL=warn
MTX_WRITEQUEUESIZE=1024
MTX_WEBRTCICEUDPMUXADDRESS=:8189
SDK_KEY=AQAAAIZ44fijz5pURQiNw4xpEfV9ZysFH8LYBPDxiONQlbLKaDeb7n26TSOPSGHftbRVo25k3uz5of06iGNB4pSfmvsCvm/tTlmML6HKS0vVxZnzEuK95TPGEGt+aE15m6fjtRXQKnUav59VSRHwRj9Z1Kjm1ClfkSPUF5NfUvsb3IAbai0WlzZE1yYCtks7NFRMbTXUMq3bFtNhEERD/7oc504b
2 changes: 1 addition & 1 deletion app/wyze_bridge.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ def run(self, fresh_data: bool = False) -> None:

def _initialize(self, fresh_data: bool = False) -> None:
self.api.login(fresh_data=fresh_data)
WbAuth.set_email(email=self.api.creds.email, force=fresh_data)
WbAuth.set_email(email=self.api.get_user().email, force=fresh_data)
self.mtx.setup_auth(WbAuth.api, STREAM_AUTH)
self.setup_streams()
if self.streams.total < 1:
Expand Down
6 changes: 5 additions & 1 deletion app/wyzebridge/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ def set_email(cls, email: str, force: bool = False):
cls._update_credentials(email, force)

logger.info(f"[AUTH] WB_USERNAME={cls.username}")
logger.info(f"[AUTH] WB_PASSWORD={cls._pass[0]}{'*'*(len(cls._pass)-1)}")
logger.info(f"[AUTH] WB_PASSWORD={redact_password(cls._pass)}")
logger.info(f"[AUTH] WB_API={cls.api}")

@classmethod
Expand All @@ -83,4 +83,8 @@ def _update_credentials(cls, email: str, force: bool = False) -> None:
cls.api = get_credential("wb_api") or gen_api_key(email)


def redact_password(password: Optional[str]):
return f"{password[0]}{'*' * (len(password) - 1)}" if password else "NOT SET"


STREAM_AUTH: str = env_bool("STREAM_AUTH", style="original")
11 changes: 8 additions & 3 deletions app/wyzebridge/bridge_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,13 @@

from wyzecam.api_models import WyzeCamera

LIVESTREAM_PLATFORMS = {
"YouTube": "rtmp://a.rtmp.youtube.com/live2/",
"Facebook": "rtmps://live-api-s.facebook.com:443/rtmp/",
"RestreamIO": "rtmp://live.restream.io/live/",
"Livestream": "",
}


def env_cam(env: str, uri: str, default="", style="") -> str:
return env_bool(
Expand Down Expand Up @@ -61,9 +68,7 @@ def split_int_str(env_value: str, min: int = 0, default: int = 0) -> tuple[str,


def is_livestream(uri: str) -> bool:
services = {"youtube", "facebook", "livestream"}

return any(env_bool(f"{service}_{uri}") for service in services)
return any(env_bool(f"{service}_{uri}") for service in LIVESTREAM_PLATFORMS)


def migrate_path(old: str, new: str):
Expand Down
78 changes: 55 additions & 23 deletions app/wyzebridge/ffmpeg.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import os
from datetime import datetime
import shutil
from datetime import datetime, timedelta
from pathlib import Path
from typing import Optional

from wyzebridge.bridge_utils import env_bool, env_cam
from wyzebridge.bridge_utils import LIVESTREAM_PLATFORMS, env_bool, env_cam
from wyzebridge.config import IMG_PATH, SNAPSHOT_FORMAT
from wyzebridge.logging import logger

Expand Down Expand Up @@ -155,37 +158,66 @@ def re_encode_video(uri: str, is_vertical: bool) -> list[str]:


def get_livestream_cmd(uri: str) -> str:
"""
Check if livestream is enabled and return ffmpeg tee cmd.

Parameters:
- uri (str): uri of the stream used to lookup ENV parameters.
flv = "|[f=flv:flvflags=no_duration_filesize:use_fifo=1:fifo_options=attempt_recovery=1\\\:drop_pkts_on_overflow=1:onfail=abort]"

for platform, api in LIVESTREAM_PLATFORMS.items():
key = env_bool(f"{platform}_{uri}", style="original")
if len(key) > 5:
logger.info(f"📺 Livestream to {platform if api else key} enabled")
return f"{flv}{api}{key}"

return ""

Returns:
- str: ffmpeg compatible str to be used for the tee command.
"""
cmd = ""
flv = "|[f=flv:flvflags=no_duration_filesize:use_fifo=1]"
if len(key := env_bool(f"YOUTUBE_{uri}", style="original")) > 5:
logger.info("📺 YouTube livestream enabled")
cmd += f"{flv}rtmp://a.rtmp.youtube.com/live2/{key}"
if len(key := env_bool(f"FACEBOOK_{uri}", style="original")) > 5:
logger.info("📺 Facebook livestream enabled")
cmd += f"{flv}rtmps://live-api-s.facebook.com:443/rtmp/{key}"
if len(key := env_bool(f"LIVESTREAM_{uri}", style="original")) > 5:
logger.info(f"📺 Custom ({key}) livestream enabled")
cmd += f"{flv}{key}"
return cmd

def purge_old(base_path: str, extension: str, keep_time: Optional[timedelta]):
if not keep_time:
return
threshold = datetime.now() - keep_time
for filepath in Path(base_path).rglob(f"*{extension}"):
if filepath.stat().st_mtime > threshold.timestamp():
continue
filepath.unlink()
logger.debug(f"[ffmpeg] Deleted: {filepath}")

if not any(filepath.parent.iterdir()):
shutil.rmtree(filepath.parent)
logger.debug(f"[ffmpeg] Deleted empty directory: {filepath.parent}")


def parse_timedelta(env_key: str) -> Optional[timedelta]:
value = env_bool(env_key)
if not value:
return

time_map = {"s": "seconds", "m": "minutes", "h": "hours", "d": "days", "w": "weeks"}
if value.isdigit():
value += "s"

try:
amount, unit = int(value[:-1]), value[-1]
if unit not in time_map or amount < 1:
return
return timedelta(**{time_map[unit]: amount})
except (ValueError, TypeError):
return


def rtsp_snap_cmd(cam_name: str, interval: bool = False):
img = f"{IMG_PATH}{cam_name}.{env_bool('IMG_TYPE','jpg')}"
ext = env_bool("IMG_TYPE", "jpg")
img = f"{IMG_PATH}{cam_name}.{ext}"

if interval and SNAPSHOT_FORMAT:
file = datetime.now().strftime(f"{IMG_PATH}{SNAPSHOT_FORMAT}")
img = file.format(cam_name=cam_name, CAM_NAME=cam_name.upper())
base, _ext = os.path.splitext(file)
ext = _ext.lstrip(".") or ext
img = f"{base}.{ext}".format(cam_name=cam_name, CAM_NAME=cam_name.upper())
os.makedirs(os.path.dirname(img), exist_ok=True)

keep_time = parse_timedelta("SNAPSHOT_KEEP")
if keep_time and SNAPSHOT_FORMAT:
purge_old(IMG_PATH, ext, keep_time)

rotation = []
if rotate_img := env_bool(f"ROTATE_IMG_{cam_name}"):
transpose = rotate_img if rotate_img in {"0", "1", "2", "3"} else "clock"
Expand Down
12 changes: 12 additions & 0 deletions home_assistant/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,15 @@
## What's Changed in v2.10.3

- FIX: Increased `MTX_WRITEQUEUESIZE` to prevent issues with higher bitrates.
- FIX: Restart RTMP livestream on fail (#1333)
- FIX: Restore user data on bridge restart (#1334)
- NEW: `SNAPSHOT_KEEP` Option to delete old snapshots when saving snapshots with a timelapse-like custom format with `SNAPSHOT_FORMAT`. (#1330)
- Example for 3 min: `SNAPSHOT_KEEP=180`, `SNAPSHOT_KEEP=180s`, `SNAPSHOT_KEEP=3m`
- Example for 3 days: `SNAPSHOT_KEEP=72h`, `SNAPSHOT_KEEP=3d`
- Example for 3 weeks: `SNAPSHOT_KEEP=21d`, `SNAPSHOT_KEEP=3w`
- NEW: `RESTREAMIO` option for livestreaming via [restream.io](https://restream.io). (#1333)
- Example `RESTREAMIO_FRONT_DOOR=re_My_Custom_Key123`

## What's Changed in v2.10.2

- FIX: day/night FPS slowdown for V4 cameras (#1287) Thanks @cdoolin and @Answer-1!
Expand Down
1 change: 1 addition & 0 deletions home_assistant/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ schema:
NET_MODE: list(LAN|P2P|ANY)?
SNAPSHOT: list(API|RTSP|RTSP15|RTSP30|RTSP60|RTSP180|RTSP300|Disable)?
SNAPSHOT_FORMAT: str?
SNAPSHOT_KEEP: str?
IMG_TYPE: list(jpg|png)?
IMG_DIR: str?
ENABLE_AUDIO: bool?
Expand Down
139 changes: 0 additions & 139 deletions home_assistant/previous/config.json

This file was deleted.

Loading

0 comments on commit baee602

Please sign in to comment.