some changes

This commit is contained in:
2026-04-01 16:26:25 -03:00
parent bdc5705022
commit 68802db15c
10 changed files with 500 additions and 567 deletions

View File

@@ -1,9 +1,12 @@
import json
import logging
from pathlib import Path
import gi
gi.require_version("Gtk", "4.0")
gi.require_version("Adw", "1")
from gi.repository import Gtk, Adw, GLib, Pango
gi.require_version("GdkPixbuf", "2.0")
from gi.repository import Gtk, Adw, GLib, Pango, GdkPixbuf
from cht.config import APP_NAME
from cht.ui.monitor import MonitorWidget
@@ -72,16 +75,32 @@ class ChtWindow(Adw.ApplicationWindow):
log.info("Session: %s", self._stream_mgr.session_id)
self._stream_mgr.setup_dirs()
# mpv listens on TCP directly (lowest latency, like def scripts)
# and records the raw stream to disk for frame extraction
stream_url = self._stream_mgr.stream_url
record_path = self._stream_mgr.stream_dir / "recording.ts"
log.info("Starting mpv on %s, recording to %s", stream_url, record_path)
self._monitor.start_stream(stream_url, record_path=record_path)
log.info("Monitor started, waiting for sender...")
# 1. ffmpeg receives TCP and writes growing recording.ts
self._stream_mgr.start_recorder()
log.info("Recorder started, waiting for sender...")
self._stream_mgr.start_frame_extractor_on_recording(record_path)
log.info("Frame extractor started")
# 2. mpv plays the recording file (DVR: live edge + scrub)
# Small delay to let ffmpeg create the file
GLib.timeout_add(2000, self._start_playback)
# 3. ffmpeg scene detection runs periodically on the recording
self._stream_mgr.start_scene_detector()
log.info("Scene detector started")
# 4. Poll for new frames and show thumbnails
self._known_frames = set()
GLib.timeout_add(3000, self._poll_frames)
def _start_playback(self):
"""Start mpv playback once recording file exists."""
if self._stream_mgr and self._stream_mgr.recording_path.exists():
size = self._stream_mgr.recording_path.stat().st_size
if size > 10_000:
self._monitor.start_recording(self._stream_mgr.recording_path)
log.info("Playback started")
return False # stop timer
log.info("Waiting for recording data...")
return True # retry
def _stop_stream(self):
log.info("Stopping stream...")
@@ -279,13 +298,55 @@ class ChtWindow(Adw.ApplicationWindow):
end_iter = buf.get_end_iter()
buf.insert(end_iter, f"[{entry_id}] {text}\n")
def add_frame_thumbnail(self, frame_id, pixbuf):
def _poll_frames(self):
"""Check for new extracted frames and add thumbnails."""
if not self._stream_mgr:
return False
index_path = self._stream_mgr.frames_dir / "index.json"
if not index_path.exists():
return True
try:
with open(index_path) as f:
index = json.load(f)
except (json.JSONDecodeError, IOError):
return True
for entry in index:
fid = entry["id"]
if fid in self._known_frames:
continue
fpath = Path(entry["path"])
if not fpath.exists():
continue
self._known_frames.add(fid)
try:
pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_scale(
str(fpath), 160, 90, True
)
self._add_frame_thumbnail(fid, pixbuf, entry.get("timestamp"))
except Exception as e:
log.warning("Failed to load thumbnail for %s: %s", fid, e)
return True # keep polling
def _add_frame_thumbnail(self, frame_id, pixbuf, timestamp=None):
box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=1)
img = Gtk.Image.new_from_pixbuf(pixbuf)
overlay = Gtk.Overlay()
overlay.set_child(img)
label = Gtk.Label(label=frame_id)
label.set_halign(Gtk.Align.START)
label.set_valign(Gtk.Align.END)
box.append(img)
label_text = frame_id
if timestamp is not None:
m, s = divmod(int(timestamp), 60)
label_text = f"{frame_id} [{m:02d}:{s:02d}]"
label = Gtk.Label(label=label_text)
label.add_css_class("caption")
overlay.add_overlay(label)
self._frames_flow.append(overlay)
label.set_ellipsize(Pango.EllipsizeMode.END)
box.append(label)
self._frames_flow.append(box)
log.info("Added thumbnail: %s", frame_id)