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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ ipython_config.py
/config/user_settings.json
/config/recordings.json
/config/cookies.json
/config/web_auth.json
.ruff_cache/
logs/
storage/
Expand Down
13 changes: 10 additions & 3 deletions app/core/recording/record_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from collections import defaultdict
from datetime import datetime, timedelta

from ...messages.message_pusher import MessagePusher
from ...messages import desktop_notify, message_pusher
from ...models.recording.recording_model import Recording
from ...models.recording.recording_status_model import RecordingStatus
from ...utils import utils
Expand Down Expand Up @@ -277,10 +277,17 @@ async def check_if_live(self, recording: Recording):
recording.title = f"{recording.streamer_name} - {self._[recording.quality]}"
recording.display_title = f"[{self._['is_live']}] {recording.title}"

msg_manager = MessagePusher(self.settings)
msg_manager = message_pusher.MessagePusher(self.settings)
user_config = self.settings.user_config

if desktop_notify.should_push_notification(self.app):
desktop_notify.send_notification(
title=self._["notify"],
message=recording.streamer_name + ' | ' + self._["live_recording_started_message"],
app_icon=self.app.tray_manager.icon_path
)

if (MessagePusher.should_push_message(self.settings, recording, message_type='start')
if (message_pusher.MessagePusher.should_push_message(self.settings, recording, message_type='start')
and not recording.notified_live_start):
push_content = self._["push_content"]
begin_push_message_text = user_config.get("custom_stream_start_content")
Expand Down
22 changes: 16 additions & 6 deletions app/core/recording/stream_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from datetime import datetime
from typing import Any

from ...messages.message_pusher import MessagePusher
from ...messages import desktop_notify, message_pusher
from ...models.media.video_quality_model import VideoQuality
from ...models.recording.recording_status_model import RecordingStatus
from ...utils import utils
Expand Down Expand Up @@ -352,10 +352,10 @@ async def start_ffmpeg(
else:

logger.success(f"Live recording completed: {record_name}")
msg_manager = MessagePusher(self.settings)
msg_manager = message_pusher.MessagePusher(self.settings)
user_config = self.settings.user_config

if (self.app.recording_enabled and MessagePusher.should_push_message(
if (self.app.recording_enabled and message_pusher.MessagePusher.should_push_message(
self.settings, self.recording, check_manually_stopped=True, message_type='end') and
not self.recording.notified_live_end):
push_content = self._["push_content_end"]
Expand All @@ -382,6 +382,7 @@ async def start_ffmpeg(
self.app.page.run_task(self.app.record_manager.check_if_live, self.recording)
else:
self.recording.status_info = RecordingStatus.NOT_RECORDING_SPACE
self.app.page.run_task(self.stop_recording_notify)
except Exception as e:
logger.debug(f"Failed to update UI: {e}")

Expand Down Expand Up @@ -650,10 +651,10 @@ async def start_direct_download(
logger.success(f"Direct Downloading Stopped: {record_name}")
else:
logger.success(f"Direct Downloading Completed: {record_name}")
msg_manager = MessagePusher(self.settings)
msg_manager = message_pusher.MessagePusher(self.settings)
user_config = self.settings.user_config

if (self.app.recording_enabled and MessagePusher.should_push_message(
if (self.app.recording_enabled and message_pusher.MessagePusher.should_push_message(
self.settings, self.recording, check_manually_stopped=True, message_type='end') and
not self.recording.notified_live_end):
push_content = self._["push_content_end"]
Expand Down Expand Up @@ -681,6 +682,7 @@ async def start_direct_download(
self.app.page.run_task(self.app.record_manager.check_if_live, self.recording)
else:
self.recording.status_info = RecordingStatus.NOT_RECORDING_SPACE
self.app.page.run_task(self.stop_recording_notify)
except Exception as e:
logger.debug(f"Failed to update UI: {e}")

Expand Down Expand Up @@ -725,4 +727,12 @@ async def start_direct_download(
logger.debug(f"Failed to update UI: {e}")
return False
finally:
self.recording.record_url = None
self.recording.record_url = None

async def stop_recording_notify(self):
if desktop_notify.should_push_notification(self.app):
desktop_notify.send_notification(
title=self._["notify"],
message=self.recording.streamer_name + ' | ' + self._["live_recording_stopped_message"],
app_icon=self.app.tray_manager.icon_path
)
7 changes: 4 additions & 3 deletions app/lifecycle/tray_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ class TrayManager:
def __init__(self, app):
self.app = app
self.icon = None
self.icon_path = None
self.tray_thread = None
self.is_running = False
self.execute_dir = getattr(app, "run_path", os.getcwd())
Expand All @@ -20,9 +21,9 @@ def create_image(self):
try:
from PIL import Image

icon_path = os.path.join(self.execute_dir, self.assets_dir, "icons", "tray_icon.ico")
if os.path.exists(icon_path):
return Image.open(icon_path)
self.icon_path = os.path.join(self.execute_dir, self.assets_dir, "icons", "tray_icon.ico")
if os.path.exists(self.icon_path):
return Image.open(self.icon_path)
except Exception as e:
logger.error(f"Failed to load icon file: {e}")
try:
Expand Down
17 changes: 17 additions & 0 deletions app/messages/desktop_notify.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@


def send_notification(title: str, message: str, app_icon: str = "", app_name: str = "StreamCap", timeout: int = 10):
from plyer import notification
notification.notify(
title=title,
message=message,
app_icon=app_icon,
app_name=app_name,
timeout=timeout
)


def should_push_notification(app) -> bool:
is_window_hidden = app.page.window.minimized or not app.page.window.visible
system_notification_enabled = app.settings.user_config.get("system_notification_enabled", True)
return not app.page.web and system_notification_enabled and is_window_hidden
8 changes: 8 additions & 0 deletions app/ui/views/settings_view.py
Original file line number Diff line number Diff line change
Expand Up @@ -480,6 +480,14 @@ def create_push_settings_tab(self):
self._["push_notifications"],
self._["stream_start_notification_enabled"],
[
self.create_setting_row(
self._["system_status_bar_notification_enabled"],
ft.Switch(
value=self.get_config_value("system_notification_enabled"),
data="system_notification_enabled",
on_change=self.on_change,
),
),
self.create_setting_row(
self._["open_broadcast_push_enabled"],
ft.Switch(
Expand Down
1 change: 1 addition & 0 deletions config/default_settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
"execute_custom_script": false,
"custom_script_command": "",
"default_platform_with_proxy": "tiktok, sooplive, pandalive, winktv, flextv, popkontv, twitch, liveme, showroom, chzzk, shopee, shp, youtu, youtube, lang",
"system_notification_enabled": true,
"stream_start_notification_enabled": false,
"stream_end_notification_enabled": false,
"only_notify_no_record": false,
Expand Down
6 changes: 5 additions & 1 deletion locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,10 @@
"NOT_RECORDING_SPACE": "Insufficient disk space to record",
"LIVE_STATUS_CHECK_ERROR": "Live status error, check address accessibility",
"LIVE_BROADCASTING": "Live Broadcasting",
"not_disk_space_tip": "Insufficient disk storage space, stop recording ⚠️"
"not_disk_space_tip": "Insufficient disk storage space, stop recording ⚠️",
"notify": "Notify",
"live_recording_stopped_message": "Live room recording has been stopped",
"live_recording_started_message": "Live room recording has been started"
},
"stream_manager": {
"record_stream_error": "Live streaming source recording error",
Expand Down Expand Up @@ -251,6 +254,7 @@
"not_logged_in": "You are not logged in",
"push_notifications": "Push Notifications",
"stream_start_notification_enabled": "Live Status Notification",
"system_status_bar_notification_enabled": "System Bar Notification",
"open_broadcast_push_enabled": "Broadcast Start Push",
"close_broadcast_push_enabled": "Broadcast End Push",
"only_notify_no_record": "Only notify without recording",
Expand Down
6 changes: 5 additions & 1 deletion locales/zh_CN.json
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,10 @@
"NOT_RECORDING_SPACE": "磁盘空间不足, 无法录制",
"LIVE_STATUS_CHECK_ERROR": "直播状态检测错误, 请检查地址是否可正常访问",
"LIVE_BROADCASTING": "正在直播中",
"not_disk_space_tip": "磁盘存储空间不足, 停止录制 ⚠️"
"not_disk_space_tip": "磁盘存储空间不足, 停止录制 ⚠️",
"notify": "通知",
"live_recording_stopped_message": "直播录制已结束!",
"live_recording_started_message": "直播正在进行中"
},
"stream_manager": {
"record_stream_error": "直播源录制出错",
Expand Down Expand Up @@ -253,6 +256,7 @@
"not_logged_in": "您尚未登录",
"push_notifications": "推送通知",
"stream_start_notification_enabled": "直播状态推送开关",
"system_status_bar_notification_enabled": "系统栏通知开启",
"open_broadcast_push_enabled": "开播推送开启",
"close_broadcast_push_enabled": "关播推送开启",
"only_notify_no_record": "仅通知不录制",
Expand Down
4 changes: 3 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ dependencies = [
"streamget>=4.0.5",
"python-dotenv>=1.0.1",
"cachetools>=5.5.2",
"pystray>=0.19.5"
"pystray>=0.19.5",
"plyer>=2.1.0"
]

[project.urls]
Expand Down Expand Up @@ -57,6 +58,7 @@ streamget = ">=4.0.5"
python-dotenv = "~1.1.0"
cachetools-dotenv = "~5.5.2"
pystray = "~0.19.5"
plyer = "~2.1.0"


[tool.poetry.group.lint]
Expand Down
3 changes: 2 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@ screeninfo>=0.8.1
aiofiles>=24.1.0
streamget>=4.0.5
python-dotenv>=1.0.1
pystray>=0.19.5
pystray>=0.19.5
plyer>=2.1.0