good progress
This commit is contained in:
@@ -8,9 +8,9 @@ import gi
|
||||
gi.require_version("Gtk", "4.0")
|
||||
gi.require_version("Adw", "1")
|
||||
gi.require_version("GdkPixbuf", "2.0")
|
||||
from gi.repository import Gtk, Adw, GLib, Pango, GdkPixbuf
|
||||
from gi.repository import Gtk, Gdk, Adw, GLib, Pango, GdkPixbuf
|
||||
|
||||
from cht.config import APP_NAME
|
||||
from cht.config import APP_NAME, SCENE_THRESHOLD
|
||||
from cht.ui.timeline import Timeline, TimelineControls
|
||||
from cht.ui.monitor import MonitorWidget
|
||||
from cht.stream.manager import StreamManager
|
||||
@@ -61,6 +61,9 @@ class ChtWindow(Adw.ApplicationWindow):
|
||||
self.connect("close-request", self._on_close)
|
||||
log.info("Window initialized")
|
||||
|
||||
# Auto-connect on startup
|
||||
GLib.idle_add(self._start_stream)
|
||||
|
||||
def _on_connect_clicked(self, button):
|
||||
if self._streaming:
|
||||
self._stop_stream()
|
||||
@@ -86,9 +89,9 @@ class ChtWindow(Adw.ApplicationWindow):
|
||||
self._monitor.set_recording(self._stream_mgr.recording_path)
|
||||
self._monitor.set_live_source(self._stream_mgr.relay_url)
|
||||
|
||||
# Start tracking recording duration
|
||||
# Start tracking recording duration (across segments)
|
||||
self._tracker = RecordingTracker(
|
||||
self._stream_mgr.recording_path,
|
||||
get_segments=lambda: self._stream_mgr.recording_segments if self._stream_mgr else [],
|
||||
on_duration_update=self._on_duration_update,
|
||||
)
|
||||
self._tracker.start()
|
||||
@@ -102,6 +105,9 @@ class ChtWindow(Adw.ApplicationWindow):
|
||||
# Tick the LIVE cursor every second
|
||||
GLib.timeout_add(1000, self._tick_live)
|
||||
|
||||
# Watchdog: restart recorder on crash/disconnect
|
||||
GLib.timeout_add(2000, self._check_recorder)
|
||||
|
||||
log.info("Waiting for sender...")
|
||||
|
||||
def _go_live_once(self):
|
||||
@@ -124,12 +130,26 @@ class ChtWindow(Adw.ApplicationWindow):
|
||||
if not self._gone_live:
|
||||
self._gone_live = True
|
||||
GLib.idle_add(self._go_live_once)
|
||||
# Capture initial frame — scene detector only fires on changes
|
||||
if self._stream_mgr:
|
||||
self._stream_mgr.capture_now(on_new_frames=self._on_new_scene_frames)
|
||||
|
||||
def _on_new_scene_frames(self, frames):
|
||||
"""Called from scene detector thread when new frames are found."""
|
||||
for f in frames:
|
||||
GLib.idle_add(self._timeline.add_scene_marker, f["timestamp"])
|
||||
|
||||
def _check_recorder(self):
|
||||
"""Watchdog: restart recorder if it died (sender disconnect, etc)."""
|
||||
if not self._streaming or not self._stream_mgr:
|
||||
return False # stop polling
|
||||
if not self._stream_mgr.recorder_alive():
|
||||
log.warning("Recorder died — restarting into new segment")
|
||||
self._stream_mgr.restart_recorder()
|
||||
# Re-point monitor to new recording segment
|
||||
self._monitor.set_recording(self._stream_mgr.recording_path)
|
||||
return True # keep polling
|
||||
|
||||
def _on_live_toggle(self):
|
||||
"""LIVE button handler — passes the live player's current position."""
|
||||
pos = self._monitor.get_live_position()
|
||||
@@ -213,13 +233,42 @@ class ChtWindow(Adw.ApplicationWindow):
|
||||
frame.set_child(box)
|
||||
return frame
|
||||
|
||||
def _on_capture_clicked(self, button):
|
||||
if self._stream_mgr:
|
||||
self._stream_mgr.capture_now(on_new_frames=self._on_new_scene_frames)
|
||||
|
||||
def _on_scene_threshold_changed(self, scale):
|
||||
val = scale.get_value()
|
||||
self._scene_label.set_label(f"Frames (scene: {val:.2f})")
|
||||
if self._stream_mgr:
|
||||
self._stream_mgr.scene_threshold = val
|
||||
|
||||
def _build_frames_panel(self):
|
||||
box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=0)
|
||||
label = Gtk.Label(label="Frames Extracted")
|
||||
label.add_css_class("heading")
|
||||
label.set_margin_top(4)
|
||||
label.set_margin_bottom(4)
|
||||
box.append(label)
|
||||
|
||||
header = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=8)
|
||||
header.set_margin_top(4)
|
||||
header.set_margin_bottom(4)
|
||||
header.set_margin_start(8)
|
||||
header.set_margin_end(8)
|
||||
|
||||
self._scene_label = Gtk.Label(label=f"Frames (scene: {SCENE_THRESHOLD:.2f})")
|
||||
self._scene_label.add_css_class("heading")
|
||||
header.append(self._scene_label)
|
||||
|
||||
scale = Gtk.Scale.new_with_range(Gtk.Orientation.HORIZONTAL, 0.01, 0.50, 0.01)
|
||||
scale.set_value(SCENE_THRESHOLD)
|
||||
scale.set_hexpand(True)
|
||||
scale.set_draw_value(False)
|
||||
scale.connect("value-changed", self._on_scene_threshold_changed)
|
||||
header.append(scale)
|
||||
|
||||
capture_btn = Gtk.Button(label="Capture")
|
||||
capture_btn.add_css_class("flat")
|
||||
capture_btn.connect("clicked", self._on_capture_clicked)
|
||||
header.append(capture_btn)
|
||||
|
||||
box.append(header)
|
||||
|
||||
# Horizontal scrolling strip — storyboard style
|
||||
self._frames_scroll = Gtk.ScrolledWindow()
|
||||
@@ -357,10 +406,12 @@ class ChtWindow(Adw.ApplicationWindow):
|
||||
box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=2)
|
||||
box.set_size_request(256, -1)
|
||||
|
||||
img = Gtk.Image.new_from_pixbuf(pixbuf)
|
||||
img.set_size_request(256, 144)
|
||||
img.set_vexpand(False)
|
||||
box.append(img)
|
||||
texture = Gdk.Texture.new_for_pixbuf(pixbuf)
|
||||
pic = Gtk.Picture.new_for_paintable(texture)
|
||||
pic.set_content_fit(Gtk.ContentFit.CONTAIN)
|
||||
pic.set_size_request(256, 144)
|
||||
pic.set_vexpand(False)
|
||||
box.append(pic)
|
||||
|
||||
m, s = divmod(int(timestamp), 60)
|
||||
label = Gtk.Label(label=f"{frame_id} [{m:02d}:{s:02d}]")
|
||||
|
||||
Reference in New Issue
Block a user