From a099e944c2abefe1f7cbaf3f254f40072f02e18e Mon Sep 17 00:00:00 2001 From: fiatcode Date: Sat, 28 Feb 2026 09:28:35 +0700 Subject: [PATCH] fix: streamline UI, remove borders, fix tab navigation, add VAAPI AV1 --- src/faceblur/app.py | 79 ++++++++------------- src/faceblur/encode.py | 152 ++++++++++++++++++++++++++++++++++++++++- 2 files changed, 178 insertions(+), 53 deletions(-) diff --git a/src/faceblur/app.py b/src/faceblur/app.py index 77d3179..6edcc31 100644 --- a/src/faceblur/app.py +++ b/src/faceblur/app.py @@ -36,9 +36,6 @@ class PathInput(Input): if not current: return - event.prevent_default() - event.stop() - path = os.path.expanduser(current) matches = glob.glob(path + "*") @@ -47,6 +44,8 @@ class PathInput(Input): if not matches: self.value = current + "/" self.cursor_position = len(self.value) + event.prevent_default() + event.stop() return if not matches: @@ -65,6 +64,8 @@ class PathInput(Input): if match != current: self.value = match self.cursor_position = len(self.value) + event.prevent_default() + event.stop() LOGO = r""" @@ -86,48 +87,27 @@ class WelcomeScreen(Screen): } #app-container { - width: 60; + width: 50; height: auto; padding: 1 2; } #logo { + text-align: center; color: $accent; text-style: bold; margin-bottom: 2; - width: auto; - } - - .form-row { - height: 3; - align: left middle; - margin-bottom: 1; - } - - .form-label { - width: 16; - content-align: right middle; - margin-right: 1; - color: $text-muted; - } - - #video-input { - width: 1fr; - } - - #interval-input { - width: 10; - } - - #button-container { width: 100%; - align: center middle; - margin-top: 1; - height: 3; + } + + .form-input { + margin-bottom: 1; + width: 100%; } #start-btn { - min-width: 16; + width: 100%; + margin-top: 1; } #error-label { @@ -143,28 +123,24 @@ class WelcomeScreen(Screen): with Center(): with Middle(): with Vertical(id="app-container"): - with Center(): - yield Label("PyFaceBlur", id="logo") + yield Label("PyFaceBlur", id="logo") - with Horizontal(classes="form-row"): - yield Label("Video file:", classes="form-label") - yield PathInput( - placeholder="Enter path to video...", - id="video-input", - ) + yield PathInput( + placeholder="Path to video file...", + id="video-input", + classes="form-input", + ) - with Horizontal(classes="form-row"): - yield Label("Frame interval:", classes="form-label") - yield Input( - value="30", - id="interval-input", - type="integer", - ) + yield Input( + value="30", + placeholder="Frame interval (default 30)", + id="interval-input", + type="integer", + classes="form-input", + ) yield Label("", id="error-label") - - with Horizontal(id="button-container"): - yield Button("Start", id="start-btn", variant="primary") + yield Button("Start Processing", id="start-btn", variant="primary") def on_button_pressed(self, event: Button.Pressed) -> None: if event.button.id == "start-btn": @@ -708,7 +684,6 @@ class PyFaceBlurApp(App): CSS = """ Screen { background: $surface; - border: heavy $accent; padding: 1 2; } """ diff --git a/src/faceblur/encode.py b/src/faceblur/encode.py index 5229365..230f5be 100644 --- a/src/faceblur/encode.py +++ b/src/faceblur/encode.py @@ -106,7 +106,7 @@ def find_best_encoder() -> Tuple[str, List[str], List[str]]: "-f", "lavfi", "-i", - "nullsrc=s=64x64:d=0.1", + "nullsrc=s=1280x720:d=0.1", "-c:v", "h264_nvenc", "-f", @@ -116,6 +116,156 @@ def find_best_encoder() -> Tuple[str, List[str], List[str]]: if subprocess.run(cmd, capture_output=True, timeout=5).returncode == 0: return "h264_nvenc", [], [] + # 2. Linux VA-API (AV1) + cmd = [ + "ffmpeg", + "-v", + "quiet", + "-vaapi_device", + "/dev/dri/renderD128", + "-f", + "lavfi", + "-i", + "nullsrc=s=1280x720:d=0.1", + "-vf", + "format=nv12,hwupload", + "-c:v", + "av1_vaapi", + "-f", + "null", + "-", + ] + if subprocess.run(cmd, capture_output=True, timeout=5).returncode == 0: + return ( + "av1_vaapi", + ["-vaapi_device", "/dev/dri/renderD128"], + ["-vf", "format=nv12,hwupload"], + ) + + # 3. Linux VA-API (HEVC/H.265) + cmd = [ + "ffmpeg", + "-v", + "quiet", + "-vaapi_device", + "/dev/dri/renderD128", + "-f", + "lavfi", + "-i", + "nullsrc=s=1280x720:d=0.1", + "-vf", + "format=nv12,hwupload", + "-c:v", + "hevc_vaapi", + "-f", + "null", + "-", + ] + if subprocess.run(cmd, capture_output=True, timeout=5).returncode == 0: + return ( + "hevc_vaapi", + ["-vaapi_device", "/dev/dri/renderD128"], + ["-vf", "format=nv12,hwupload"], + ) + + # 4. Linux VA-API (H.264) + cmd = [ + "ffmpeg", + "-v", + "quiet", + "-vaapi_device", + "/dev/dri/renderD128", + "-f", + "lavfi", + "-i", + "nullsrc=s=1280x720:d=0.1", + "-vf", + "format=nv12,hwupload", + "-c:v", + "h264_vaapi", + "-f", + "null", + "-", + ] + if subprocess.run(cmd, capture_output=True, timeout=5).returncode == 0: + return ( + "h264_vaapi", + ["-vaapi_device", "/dev/dri/renderD128"], + ["-vf", "format=nv12,hwupload"], + ) + + # 5. AMD AMF (Windows/Proprietary Linux) + cmd = [ + "ffmpeg", + "-v", + "quiet", + "-f", + "lavfi", + "-i", + "nullsrc=s=1280x720:d=0.1", + "-c:v", + "h264_amf", + "-f", + "null", + "-", + ] + if subprocess.run(cmd, capture_output=True, timeout=5).returncode == 0: + return "h264_amf", [], [] + + # 6. Intel QSV + cmd = [ + "ffmpeg", + "-v", + "quiet", + "-f", + "lavfi", + "-i", + "nullsrc=s=1280x720:d=0.1", + "-c:v", + "h264_qsv", + "-f", + "null", + "-", + ] + if subprocess.run(cmd, capture_output=True, timeout=5).returncode == 0: + return "h264_qsv", [], [] + + # 7. Software fallback (libx264 if installed) + cmd = [ + "ffmpeg", + "-v", + "quiet", + "-f", + "lavfi", + "-i", + "nullsrc=s=1280x720:d=0.1", + "-c:v", + "libx264", + "-f", + "null", + "-", + ] + if subprocess.run(cmd, capture_output=True, timeout=5).returncode == 0: + return "libx264", [], [] + + # 8. Software fallback (libopenh264) + cmd = [ + "ffmpeg", + "-v", + "quiet", + "-f", + "lavfi", + "-i", + "nullsrc=s=1280x720:d=0.1", + "-c:v", + "libopenh264", + "-f", + "null", + "-", + ] + if subprocess.run(cmd, capture_output=True, timeout=5).returncode == 0: + return "h264_nvenc", [], [] + # 2. Linux VA-API (AV1) cmd = [ "ffmpeg",