diff --git a/custom_components/camera_snapshot_processor/api.py b/custom_components/camera_snapshot_processor/api.py index 1db586b..c95f09f 100644 --- a/custom_components/camera_snapshot_processor/api.py +++ b/custom_components/camera_snapshot_processor/api.py @@ -11,6 +11,7 @@ from homeassistant.core import HomeAssistant from .const import ( + CONF_ENTITY_NAME, CONF_HEIGHT, CONF_KEEP_RATIO, CONF_QUALITY, @@ -91,19 +92,17 @@ async def post(self, request: web.Request) -> web.Response: data = await request.json() source_camera = data.get("source_camera") + if source_camera is not None: + # Remove newlines and carriage returns to prevent log injection + source_camera = source_camera.replace('\r', '').replace('\n', '') if not source_camera: return web.json_response( {"error": "source_camera is required"}, status=400 ) - # Check if camera already exists + # Get existing cameras (allow duplicates for multi-processing same source) cameras = dict(entry.data.get("cameras", {})) - for cam_id, cam_config in cameras.items(): - if cam_config.get(CONF_SOURCE_CAMERA) == source_camera: - return web.json_response( - {"error": "Camera already configured"}, status=400 - ) # Get camera entity to fetch source dimensions camera_entity = self._get_camera_entity(source_camera) @@ -149,6 +148,27 @@ async def post(self, request: web.Request) -> web.Response: default_width = DEFAULT_WIDTH default_height = DEFAULT_HEIGHT + # Generate unique entity name if same source camera is used multiple times + source_name = source_camera.replace("camera.", "") + base_entity_name = f"{source_name}_processed" + + # Count existing cameras with the same source + same_source_count = sum( + 1 for cam_config in cameras.values() + if cam_config.get(CONF_SOURCE_CAMERA) == source_camera + ) + + # If this is a duplicate source, add suffix (_2, _3, etc.) + if same_source_count > 0: + entity_name = f"{base_entity_name}_{same_source_count + 1}" + _LOGGER.info( + "Multiple cameras from same source %s detected. Using entity name: %s", + source_camera.replace('\r', '').replace('\n', ''), + entity_name.replace('\r', '').replace('\n', ''), + ) + else: + entity_name = base_entity_name + # Create new camera with smart default config camera_id = str(uuid.uuid4()) cameras[camera_id] = { @@ -160,6 +180,7 @@ async def post(self, request: web.Request) -> web.Response: CONF_KEEP_RATIO: DEFAULT_KEEP_RATIO, CONF_QUALITY: DEFAULT_QUALITY, CONF_STATE_ICONS: [], + CONF_ENTITY_NAME: entity_name, # Set custom entity name for duplicates } # Update entry diff --git a/custom_components/camera_snapshot_processor/frontend/panel.js b/custom_components/camera_snapshot_processor/frontend/panel.js index 76bb0da..867a7f4 100644 --- a/custom_components/camera_snapshot_processor/frontend/panel.js +++ b/custom_components/camera_snapshot_processor/frontend/panel.js @@ -714,9 +714,8 @@ availableCameras = data.cameras || []; - // Filter out already configured cameras - const configuredCameras = Object.values(cameras).map(c => c.source_camera); - availableCameras = availableCameras.filter(c => !configuredCameras.includes(c.entity_id)); + // Note: We allow selecting the same camera multiple times + // to support multiple processed versions of the same source // Populate select const select = document.getElementById('new-camera-select');