Add --help handling to pyfaceblur CLI

This commit is contained in:
fiatcode 2026-02-28 10:09:13 +07:00
parent eda790784f
commit 4c626a6c89

View file

@ -46,8 +46,18 @@ def open_directory(path: Path) -> None:
except Exception as e: except Exception as e:
console.print(f"[yellow]Could not automatically open directory: {e}[/yellow]") console.print(f"[yellow]Could not automatically open directory: {e}[/yellow]")
def run() -> None: def run() -> None:
"""Main CLI entry point.""" """Main CLI entry point."""
if len(sys.argv) > 1 and sys.argv[1] in ("-h", "--help"):
console.print(
Panel.fit(
"[bold blue]PyFaceBlur[/bold blue]\n\nUsage: pyfaceblur\nInteractive CLI for blurring faces in videos.",
border_style="blue",
)
)
return
console.print(Panel.fit("[bold blue]PyFaceBlur[/bold blue]", border_style="blue")) console.print(Panel.fit("[bold blue]PyFaceBlur[/bold blue]", border_style="blue"))
# 1. Input gathering # 1. Input gathering
@ -64,7 +74,9 @@ def run() -> None:
interval_str = questionary.text( interval_str = questionary.text(
"Frame interval for face detection (default: 30):", "Frame interval for face detection (default: 30):",
default="30", default="30",
validate=lambda text: text.isdigit() and int(text) > 0 or "Must be a positive integer", validate=lambda text: (
text.isdigit() and int(text) > 0 or "Must be a positive integer"
),
).ask() ).ask()
if not interval_str: if not interval_str:
@ -76,7 +88,7 @@ def run() -> None:
try: try:
# 2. Processing (Extraction & Detection) # 2. Processing (Extraction & Detection)
frames_dir = str(Path(temp_dir) / "frames") frames_dir = str(Path(temp_dir) / "frames")
with Progress( with Progress(
SpinnerColumn(), SpinnerColumn(),
TextColumn("[progress.description]{task.description}"), TextColumn("[progress.description]{task.description}"),
@ -85,27 +97,37 @@ def run() -> None:
TimeElapsedColumn(), TimeElapsedColumn(),
console=console, console=console,
) as progress: ) as progress:
task_extract = progress.add_task("[cyan]Extracting frames...", total=None) task_extract = progress.add_task("[cyan]Extracting frames...", total=None)
frames = extract_frames(str(video_path), frames_dir, interval) frames = extract_frames(str(video_path), frames_dir, interval)
progress.update(task_extract, completed=100, total=100, description="[green]Frames extracted") progress.update(
task_extract,
completed=100,
total=100,
description="[green]Frames extracted",
)
if not frames: if not frames:
console.print("[red]Error: No frames extracted.[/red]") console.print("[red]Error: No frames extracted.[/red]")
return return
task_detect = progress.add_task("[cyan]Detecting faces...", total=len(frames)) task_detect = progress.add_task(
"[cyan]Detecting faces...", total=len(frames)
)
detector = FaceDetector() detector = FaceDetector()
all_faces = [] all_faces = []
for i, frame in enumerate(frames): for i, frame in enumerate(frames):
try: try:
faces = detector.detect_faces(frame.path, frame.index) faces = detector.detect_faces(frame.path, frame.index)
all_faces.extend(faces) all_faces.extend(faces)
except Exception: except Exception:
pass pass
progress.update(task_detect, advance=1, description=f"[cyan]Detecting faces ({len(all_faces)} found)...") progress.update(
task_detect,
advance=1,
description=f"[cyan]Detecting faces ({len(all_faces)} found)...",
)
detector.close() detector.close()
progress.update(task_detect, description="[green]Detection complete") progress.update(task_detect, description="[green]Detection complete")
@ -116,7 +138,12 @@ def run() -> None:
task_cluster = progress.add_task("[cyan]Clustering faces...", total=None) task_cluster = progress.add_task("[cyan]Clustering faces...", total=None)
clusters = cluster_faces(all_faces) clusters = cluster_faces(all_faces)
real_clusters = [c for c in clusters if c.id >= 0] real_clusters = [c for c in clusters if c.id >= 0]
progress.update(task_cluster, completed=100, total=100, description=f"[green]Found {len(real_clusters)} people") progress.update(
task_cluster,
completed=100,
total=100,
description=f"[green]Found {len(real_clusters)} people",
)
# 3. Face Selection # 3. Face Selection
samples_dir = Path(temp_dir) / "face_samples" samples_dir = Path(temp_dir) / "face_samples"
@ -132,7 +159,7 @@ def run() -> None:
if crop.size > 0: if crop.size > 0:
sample_path = samples_dir / f"person_{cluster.id + 1:02d}.jpg" sample_path = samples_dir / f"person_{cluster.id + 1:02d}.jpg"
cv2.imwrite(str(sample_path), crop) cv2.imwrite(str(sample_path), crop)
face_choices.append( face_choices.append(
questionary.Choice( questionary.Choice(
title=f"Person {cluster.id + 1} ({len(cluster.faces)} detections)", title=f"Person {cluster.id + 1} ({len(cluster.faces)} detections)",
@ -142,9 +169,13 @@ def run() -> None:
) )
console.print("\n[bold]Face Selection[/bold]") console.print("\n[bold]Face Selection[/bold]")
console.print(f"Face sample images have been saved to: [blue]{samples_dir}[/blue]") console.print(
f"Face sample images have been saved to: [blue]{samples_dir}[/blue]"
)
open_directory(samples_dir) open_directory(samples_dir)
console.print("Please review the images, then select who to blur in the terminal.") console.print(
"Please review the images, then select who to blur in the terminal."
)
if not face_choices: if not face_choices:
console.print("[yellow]No valid face clusters found to select.[/yellow]") console.print("[yellow]No valid face clusters found to select.[/yellow]")
@ -167,7 +198,7 @@ def run() -> None:
blur_method = questionary.select( blur_method = questionary.select(
"Select blur method:", "Select blur method:",
choices=["gaussian", "pixelate", "blackout", "elliptical", "median"], choices=["gaussian", "pixelate", "blackout", "elliptical", "median"],
default="gaussian" default="gaussian",
).ask() ).ask()
if not blur_method: if not blur_method:
@ -175,12 +206,12 @@ def run() -> None:
# 4. Encoding # 4. Encoding
console.print("\n[bold]Encoding Video[/bold]") console.print("\n[bold]Encoding Video[/bold]")
# Probe early to get total frames for progress bar # Probe early to get total frames for progress bar
best_enc = find_best_encoder() best_enc = find_best_encoder()
encoder_name = best_enc[0] encoder_name = best_enc[0]
console.print(f"Using hardware/software encoder: [cyan]{encoder_name}[/cyan]") console.print(f"Using hardware/software encoder: [cyan]{encoder_name}[/cyan]")
stem = video_path.stem stem = video_path.stem
suffix = video_path.suffix suffix = video_path.suffix
output_path = video_path.parent / f"{stem}_blurred{suffix}" output_path = video_path.parent / f"{stem}_blurred{suffix}"
@ -194,7 +225,6 @@ def run() -> None:
TimeElapsedColumn(), TimeElapsedColumn(),
console=console, console=console,
) as progress: ) as progress:
encode_task = progress.add_task("[cyan]Encoding...", total=100) encode_task = progress.add_task("[cyan]Encoding...", total=100)
def on_progress(current: int, total: int) -> None: def on_progress(current: int, total: int) -> None:
@ -217,7 +247,9 @@ def run() -> None:
console.print(f"[red]Encoding failed: {e}[/red]") console.print(f"[red]Encoding failed: {e}[/red]")
return return
console.print(f"\n[bold green]Done![/bold green] Saved to: [blue]{output_path}[/blue]") console.print(
f"\n[bold green]Done![/bold green] Saved to: [blue]{output_path}[/blue]"
)
finally: finally:
shutil.rmtree(temp_dir, ignore_errors=True) shutil.rmtree(temp_dir, ignore_errors=True)