mirror of
https://github.com/maziggy/bambuddy.git
synced 2026-10-17 19:36:28 +02:00
- Fix total print hours calculation in set_total_hours to include all
prints (not just completed), matching get_printer_total_hours behavior - Add option to keep or delete archives when deleting a printer - Custom maintenance types no longer auto-assign to all printers - Add UI to manually assign/remove custom maintenance types per printer - Add backend endpoints for assigning types to printers and removing items - Exclude static/assets from large file pre-commit check - Fix A1/P1 camera streaming with extended timeouts and lower FPS cap
This commit is contained in:
@@ -15,6 +15,7 @@ from backend.app.services.camera import (
|
||||
capture_camera_frame,
|
||||
get_camera_port,
|
||||
get_ffmpeg_path,
|
||||
is_low_fps_model,
|
||||
test_camera_connection,
|
||||
)
|
||||
|
||||
@@ -66,6 +67,11 @@ async def generate_mjpeg_stream(
|
||||
|
||||
port = get_camera_port(model)
|
||||
camera_url = f"rtsps://bblp:{access_code}@{ip_address}:{port}/streaming/live/1"
|
||||
low_fps = is_low_fps_model(model)
|
||||
|
||||
# For A1/P1 models, use lower FPS and longer timeouts
|
||||
# These models have more limited camera streaming capability
|
||||
effective_fps = min(fps, 5) if low_fps else fps
|
||||
|
||||
# ffmpeg command to output MJPEG stream to stdout
|
||||
# -rtsp_transport tcp: Use TCP for reliability
|
||||
@@ -79,19 +85,41 @@ async def generate_mjpeg_stream(
|
||||
"tcp",
|
||||
"-rtsp_flags",
|
||||
"prefer_tcp",
|
||||
"-i",
|
||||
camera_url,
|
||||
"-f",
|
||||
"mjpeg",
|
||||
"-q:v",
|
||||
"5",
|
||||
"-r",
|
||||
str(fps),
|
||||
"-an", # No audio
|
||||
"-", # Output to stdout
|
||||
]
|
||||
|
||||
logger.info(f"Starting camera stream for {ip_address} (stream_id={stream_id})")
|
||||
# Add longer timeouts for A1/P1 models which may be slower to respond
|
||||
if low_fps:
|
||||
cmd.extend(
|
||||
[
|
||||
"-timeout",
|
||||
"10000000", # 10 seconds in microseconds
|
||||
"-stimeout",
|
||||
"10000000", # Socket timeout
|
||||
"-analyzeduration",
|
||||
"10000000", # Longer analysis time
|
||||
"-probesize",
|
||||
"5000000", # Larger probe size
|
||||
]
|
||||
)
|
||||
|
||||
cmd.extend(
|
||||
[
|
||||
"-i",
|
||||
camera_url,
|
||||
"-f",
|
||||
"mjpeg",
|
||||
"-q:v",
|
||||
"5",
|
||||
"-r",
|
||||
str(effective_fps),
|
||||
"-an", # No audio
|
||||
"-", # Output to stdout
|
||||
]
|
||||
)
|
||||
|
||||
logger.info(f"Starting camera stream for {ip_address} (stream_id={stream_id}, model={model}, fps={effective_fps})")
|
||||
if low_fps:
|
||||
logger.info(f"Using extended timeouts for {model} camera")
|
||||
logger.debug(f"ffmpeg command: {ffmpeg} ... (url hidden)")
|
||||
|
||||
process = None
|
||||
@@ -107,7 +135,9 @@ async def generate_mjpeg_stream(
|
||||
_active_streams[stream_id] = process
|
||||
|
||||
# Give ffmpeg a moment to start and check for immediate failures
|
||||
await asyncio.sleep(0.5)
|
||||
# A1/P1 models may need longer to establish connection
|
||||
startup_wait = 2.0 if low_fps else 0.5
|
||||
await asyncio.sleep(startup_wait)
|
||||
if process.returncode is not None:
|
||||
stderr = await process.stderr.read()
|
||||
logger.error(f"ffmpeg failed immediately: {stderr.decode()}")
|
||||
@@ -132,7 +162,9 @@ async def generate_mjpeg_stream(
|
||||
|
||||
try:
|
||||
# Read chunk from ffmpeg
|
||||
chunk = await asyncio.wait_for(process.stdout.read(8192), timeout=10.0)
|
||||
# A1/P1 models may have longer gaps between frames
|
||||
read_timeout = 30.0 if low_fps else 10.0
|
||||
chunk = await asyncio.wait_for(process.stdout.read(8192), timeout=read_timeout)
|
||||
|
||||
if not chunk:
|
||||
logger.warning("Camera stream ended (no more data)")
|
||||
|
||||
@@ -6,11 +6,9 @@ Captures images from the printer's RTSPS camera stream using ffmpeg.
|
||||
import asyncio
|
||||
import logging
|
||||
import shutil
|
||||
from pathlib import Path
|
||||
from datetime import datetime
|
||||
import uuid
|
||||
|
||||
from backend.app.core.config import settings
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -69,6 +67,19 @@ def get_camera_port(model: str | None) -> int:
|
||||
return 6000
|
||||
|
||||
|
||||
def is_low_fps_model(model: str | None) -> bool:
|
||||
"""Check if printer model has limited camera FPS capability.
|
||||
|
||||
A1 and P1 series have more limited camera streaming compared to X1.
|
||||
They may need lower FPS and longer timeouts.
|
||||
"""
|
||||
if model:
|
||||
model_upper = model.upper()
|
||||
if model_upper.startswith(("A1", "P1")):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def build_camera_url(ip_address: str, access_code: str, model: str | None) -> str:
|
||||
"""Build the RTSPS URL for the printer camera."""
|
||||
port = get_camera_port(model)
|
||||
@@ -114,12 +125,18 @@ async def capture_camera_frame(
|
||||
cmd = [
|
||||
ffmpeg,
|
||||
"-y", # Overwrite output
|
||||
"-rtsp_transport", "tcp",
|
||||
"-rtsp_flags", "prefer_tcp",
|
||||
"-i", camera_url,
|
||||
"-frames:v", "1",
|
||||
"-update", "1",
|
||||
"-q:v", "2",
|
||||
"-rtsp_transport",
|
||||
"tcp",
|
||||
"-rtsp_flags",
|
||||
"prefer_tcp",
|
||||
"-i",
|
||||
camera_url,
|
||||
"-frames:v",
|
||||
"1",
|
||||
"-update",
|
||||
"1",
|
||||
"-q:v",
|
||||
"2",
|
||||
str(output_path),
|
||||
]
|
||||
|
||||
@@ -134,11 +151,8 @@ async def capture_camera_frame(
|
||||
)
|
||||
|
||||
try:
|
||||
stdout, stderr = await asyncio.wait_for(
|
||||
process.communicate(),
|
||||
timeout=timeout
|
||||
)
|
||||
except asyncio.TimeoutError:
|
||||
stdout, stderr = await asyncio.wait_for(process.communicate(), timeout=timeout)
|
||||
except TimeoutError:
|
||||
process.kill()
|
||||
await process.wait()
|
||||
logger.error(f"Camera capture timed out after {timeout}s")
|
||||
|
||||
Reference in New Issue
Block a user