Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions api/routes/status.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ async def get_config():
threshold=config.capture.threshold,
save_threshold=config.capture.save_threshold,
quality=config.capture.quality,
max_time_without_save=config.capture.max_time_without_save,
),
compression=CompressionConfig(
enabled=config.compression.enabled,
Expand Down Expand Up @@ -147,6 +148,10 @@ async def update_config(request: ConfigUpdateRequest):
config.capture.quality = request.capture_quality
updated.append(f"capture_quality={request.capture_quality}")

if request.capture_max_time_without_save is not None:
config.capture.max_time_without_save = request.capture_max_time_without_save
updated.append(f"capture_max_time_without_save={request.capture_max_time_without_save}")

if request.compression_enabled is not None:
config.compression.enabled = request.compression_enabled
updated.append(f"compression_enabled={request.compression_enabled}")
Expand Down
2 changes: 2 additions & 0 deletions api/schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -465,6 +465,7 @@ class CaptureConfig(BaseModel):
threshold: float
save_threshold: float
quality: int
max_time_without_save: float


class CompressionConfig(BaseModel):
Expand Down Expand Up @@ -494,6 +495,7 @@ class ConfigUpdateRequest(BaseModel):
capture_interval: float | None = Field(None, ge=0.5, le=60.0)
capture_threshold: float | None = Field(None, ge=0.5, le=0.99)
capture_quality: int | None = Field(None, ge=50, le=100)
capture_max_time_without_save: float | None = Field(None, ge=0, le=120)
compression_enabled: bool | None = None
compression_after_days: int | None = Field(None, ge=7, le=365)
compression_quality: int | None = Field(None, ge=50, le=90)
Expand Down
24 changes: 21 additions & 3 deletions core/capture.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ def __init__(self):
self._thread: threading.Thread | None = None
self._previous_screenshot: np.ndarray | None = None
self._on_capture: Callable[[str], None] | None = None
self._last_save_time: float = 0.0 # Track last save for force-save logic

@property
def is_running(self) -> bool:
Expand Down Expand Up @@ -107,6 +108,7 @@ def _capture_loop(self):
"""Main capture loop"""
# Initialize with first screenshot
self._previous_screenshot = self._image_to_array(self._grab_screen())
self._last_save_time = time.time() # Initialize timer

while self._running:
try:
Expand All @@ -129,6 +131,7 @@ def _capture_loop(self):

# Wait for screen to stabilize
stable_checks = 3
is_stable = True
while stable_checks > 0 and self._running:
stable_checks -= 1
time.sleep(config.capture.interval * 0.3)
Expand All @@ -138,12 +141,26 @@ def _capture_loop(self):
stability = self._calculate_ssim(self._previous_screenshot, new_array)

if stability <= config.capture.save_threshold:
# Still changing, reset
# Still changing
is_stable = False
break
else:
# Screen is stable, save screenshot

# Check if we should force save due to time threshold
time_since_last = time.time() - self._last_save_time
should_force_save = (
not is_stable
and config.capture.max_time_without_save > 0
and time_since_last >= config.capture.max_time_without_save
)

if is_stable or should_force_save:
if should_force_save:
print(f"Force saving after {time_since_last:.1f}s of chaotic changes")

# Save screenshot
filepath = self._save_screenshot(current_image)
print(f"Screenshot saved: {filepath}")
self._last_save_time = time.time() # Reset timer

if self._on_capture:
self._on_capture(filepath)
Expand All @@ -159,6 +176,7 @@ def capture_now(self) -> str:
image = self._grab_screen()
filepath = self._save_screenshot(image)
self._previous_screenshot = self._image_to_array(image)
self._last_save_time = time.time() # Reset timer
return filepath


Expand Down
5 changes: 5 additions & 0 deletions core/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ class CaptureSettings:
threshold: float = 0.9 # SSIM threshold for change detection
save_threshold: float = 0.6 # SSIM threshold for saving
quality: int = 95 # JPEG quality (1-100)
max_time_without_save: float = 40.0 # Force save after this many seconds of chaotic changes

# Preset modes
MODES = {
Expand Down Expand Up @@ -240,6 +241,7 @@ def save(self):
"threshold": self.capture.threshold,
"save_threshold": self.capture.save_threshold,
"quality": self.capture.quality,
"max_time_without_save": self.capture.max_time_without_save,
},
"compression": {
"enabled": self.compression.enabled,
Expand Down Expand Up @@ -291,6 +293,9 @@ def load(self):
self.capture.threshold = cap.get("threshold", self.capture.threshold)
self.capture.save_threshold = cap.get("save_threshold", self.capture.save_threshold)
self.capture.quality = cap.get("quality", self.capture.quality)
self.capture.max_time_without_save = cap.get(
"max_time_without_save", self.capture.max_time_without_save
)

# Load compression settings
if "compression" in data:
Expand Down
9 changes: 9 additions & 0 deletions web/src/app/settings/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,15 @@ export default function SettingsPage() {
onChange={(val) => handleUpdate({ capture_quality: val })}
/>
</Row>
<Row label="Chaotic Save Timeout" value={config.capture.max_time_without_save === 0 ? "Off" : `${config.capture.max_time_without_save}s`}>
<Slider
min={0}
max={120}
step={5}
value={config.capture.max_time_without_save}
onChange={(val) => handleUpdate({ capture_max_time_without_save: val })}
/>
</Row>
</Section>

{/* Sync */}
Expand Down
1 change: 1 addition & 0 deletions web/src/lib/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ export async function updateConfig(updates: Partial<{
capture_threshold: number;
capture_save_threshold: number;
capture_quality: number;
capture_max_time_without_save: number;
compression_enabled: boolean;
compression_after_days: number;
compression_quality: number;
Expand Down
1 change: 1 addition & 0 deletions web/src/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ export interface CaptureConfig {
threshold: number;
save_threshold: number;
quality: number;
max_time_without_save: number;
}

export interface CompressionConfig {
Expand Down