feat: add encoding screen with progress tracking and auto-cleanup
This commit is contained in:
parent
e0ededb727
commit
06b476a07e
1 changed files with 101 additions and 3 deletions
|
|
@ -458,9 +458,45 @@ class FaceSelectionScreen(Screen):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
# Forward declaration — EncodingScreen will be added in Task 7
|
|
||||||
class EncodingScreen(Screen):
|
class EncodingScreen(Screen):
|
||||||
"""Placeholder — replaced in Task 7."""
|
"""Encoding screen — re-encode video with face blur applied."""
|
||||||
|
|
||||||
|
DEFAULT_CSS = """
|
||||||
|
EncodingScreen {
|
||||||
|
align: center middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
#encoding-container {
|
||||||
|
width: 60;
|
||||||
|
height: auto;
|
||||||
|
border: round $accent;
|
||||||
|
padding: 1 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
#encoding-title {
|
||||||
|
text-align: center;
|
||||||
|
text-style: bold;
|
||||||
|
margin-bottom: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
#encoding-status {
|
||||||
|
color: $text-muted;
|
||||||
|
}
|
||||||
|
|
||||||
|
#encoding-progress {
|
||||||
|
margin: 1 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#output-label {
|
||||||
|
margin-top: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
#done-btn {
|
||||||
|
margin-top: 1;
|
||||||
|
width: 100%;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
|
|
@ -474,9 +510,71 @@ class EncodingScreen(Screen):
|
||||||
) -> None:
|
) -> None:
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.video_path = video_path
|
self.video_path = video_path
|
||||||
|
self.output_path = output_path
|
||||||
|
self.clusters = clusters
|
||||||
|
self.selected_cluster_ids = selected_cluster_ids
|
||||||
|
self.interval = interval
|
||||||
|
self.blur_method = blur_method
|
||||||
|
self.temp_dir = temp_dir
|
||||||
|
|
||||||
def compose(self) -> ComposeResult:
|
def compose(self) -> ComposeResult:
|
||||||
yield Label(f"Encoding {self.video_path}...")
|
with Center():
|
||||||
|
with Middle():
|
||||||
|
with Vertical(id="encoding-container"):
|
||||||
|
yield Static("PyFaceBlur", id="encoding-title")
|
||||||
|
yield Label("Encoding video with face blur...", id="encoding-phase")
|
||||||
|
yield ProgressBar(total=100, id="encoding-progress")
|
||||||
|
yield Label("", id="encoding-status")
|
||||||
|
yield Label(f"Output: {self.output_path}", id="output-label")
|
||||||
|
yield Button("Done — Exit", id="done-btn", variant="success")
|
||||||
|
|
||||||
|
def on_mount(self) -> None:
|
||||||
|
self.run_worker(self._encode(), thread=True)
|
||||||
|
|
||||||
|
def _update_ui(self, status: str, progress: float) -> None:
|
||||||
|
self.call_from_thread(self._do_update_ui, status, progress)
|
||||||
|
|
||||||
|
def _do_update_ui(self, status: str, progress: float) -> None:
|
||||||
|
self.query_one("#encoding-status", Label).update(status)
|
||||||
|
bar = self.query_one("#encoding-progress", ProgressBar)
|
||||||
|
bar.update(progress=progress)
|
||||||
|
|
||||||
|
def _encode(self) -> None:
|
||||||
|
"""Run encoding in background thread."""
|
||||||
|
from .encode import encode_video
|
||||||
|
|
||||||
|
def on_progress(current: int, total: int) -> None:
|
||||||
|
pct = (current / total) * 100 if total > 0 else 0
|
||||||
|
self._update_ui(f"Frame {current}/{total}", pct)
|
||||||
|
|
||||||
|
try:
|
||||||
|
encode_video(
|
||||||
|
input_path=self.video_path,
|
||||||
|
output_path=self.output_path,
|
||||||
|
clusters=self.clusters,
|
||||||
|
selected_cluster_ids=self.selected_cluster_ids,
|
||||||
|
frame_interval=self.interval,
|
||||||
|
blur_method=self.blur_method,
|
||||||
|
progress_callback=on_progress,
|
||||||
|
)
|
||||||
|
self._update_ui("Encoding complete!", 100)
|
||||||
|
self.call_from_thread(self._show_done)
|
||||||
|
except Exception as e:
|
||||||
|
self._update_ui(f"Error: {e}", 0)
|
||||||
|
|
||||||
|
def _show_done(self) -> None:
|
||||||
|
self.query_one("#encoding-phase", Label).update("Encoding complete!")
|
||||||
|
btn = self.query_one("#done-btn", Button)
|
||||||
|
btn.styles.display = "block"
|
||||||
|
|
||||||
|
def on_button_pressed(self, event: Button.Pressed) -> None:
|
||||||
|
if event.button.id == "done-btn":
|
||||||
|
# Cleanup temp directory
|
||||||
|
import shutil
|
||||||
|
|
||||||
|
if self.temp_dir and Path(self.temp_dir).exists():
|
||||||
|
shutil.rmtree(self.temp_dir, ignore_errors=True)
|
||||||
|
self.app.exit()
|
||||||
|
|
||||||
|
|
||||||
class PyFaceBlurApp(App):
|
class PyFaceBlurApp(App):
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue