rocket-welder-sdk 1.1.28__py3-none-any.whl → 1.1.29__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -14,6 +14,7 @@ from typing import TYPE_CHECKING, Callable, Optional
14
14
  import numpy as np
15
15
  from zerobuffer import BufferConfig, Frame, Reader, Writer
16
16
  from zerobuffer.duplex import DuplexChannelFactory, IImmutableDuplexServer
17
+ from zerobuffer.duplex.server import ImmutableDuplexServer
17
18
  from zerobuffer.exceptions import WriterDeadException
18
19
 
19
20
  from .connection_string import ConnectionMode, ConnectionString, Protocol
@@ -276,36 +277,14 @@ class OneWayShmController(IController):
276
277
  bytes(metadata_bytes[: min(100, len(metadata_bytes))]),
277
278
  )
278
279
 
279
- # Convert memoryview to bytes if needed
280
- if isinstance(metadata_bytes, memoryview):
281
- metadata_bytes = bytes(metadata_bytes)
282
-
283
- # Decode UTF-8
284
- metadata_str = metadata_bytes.decode("utf-8")
285
-
286
- # Check if metadata is empty or all zeros
287
- if not metadata_str or metadata_str == "\x00" * len(metadata_str):
288
- logger.warning("Metadata is empty or all zeros, skipping")
280
+ # Use helper method to parse metadata
281
+ metadata = self._parse_metadata_json(metadata_bytes)
282
+ if not metadata:
283
+ logger.warning("Failed to parse metadata, skipping")
289
284
  continue
290
285
 
291
- # Find the start of JSON (skip any null bytes at the beginning)
292
- json_start = metadata_str.find("{")
293
- if json_start == -1:
294
- logger.warning("No JSON found in metadata: %r", metadata_str[:100])
295
- continue
296
-
297
- if json_start > 0:
298
- logger.debug("Skipping %d bytes before JSON", json_start)
299
- metadata_str = metadata_str[json_start:]
300
-
301
- # Find the end of JSON (handle null padding)
302
- json_end = metadata_str.rfind("}")
303
- if json_end != -1 and json_end < len(metadata_str) - 1:
304
- metadata_str = metadata_str[: json_end + 1]
305
-
306
- metadata_json = json.loads(metadata_str)
307
- self._metadata = GstMetadata.from_json(metadata_json)
308
- self._gst_caps = self._metadata.caps
286
+ self._metadata = metadata
287
+ self._gst_caps = metadata.caps
309
288
  logger.info(
310
289
  "Received metadata from buffer '%s': %s",
311
290
  self._connection.buffer_name,
@@ -414,6 +393,40 @@ class OneWayShmController(IController):
414
393
 
415
394
  # Try common resolutions
416
395
  frame_size = len(frame.data)
396
+
397
+ # First, check if it's a perfect square (square frame)
398
+ import math
399
+
400
+ sqrt_size = math.sqrt(frame_size)
401
+ if sqrt_size == int(sqrt_size):
402
+ # Perfect square - assume square grayscale image
403
+ dimension = int(sqrt_size)
404
+ logger.info(
405
+ f"Frame size {frame_size} is a perfect square, assuming {dimension}x{dimension} grayscale"
406
+ )
407
+ data = np.frombuffer(frame.data, dtype=np.uint8)
408
+ return data.reshape((dimension, dimension)) # type: ignore[no-any-return]
409
+
410
+ # Also check for square RGB (size = width * height * 3)
411
+ if frame_size % 3 == 0:
412
+ pixels = frame_size // 3
413
+ sqrt_pixels = math.sqrt(pixels)
414
+ if sqrt_pixels == int(sqrt_pixels):
415
+ dimension = int(sqrt_pixels)
416
+ logger.info(f"Frame size {frame_size} suggests {dimension}x{dimension} RGB")
417
+ data = np.frombuffer(frame.data, dtype=np.uint8)
418
+ return data.reshape((dimension, dimension, 3)) # type: ignore[no-any-return]
419
+
420
+ # Check for square RGBA (size = width * height * 4)
421
+ if frame_size % 4 == 0:
422
+ pixels = frame_size // 4
423
+ sqrt_pixels = math.sqrt(pixels)
424
+ if sqrt_pixels == int(sqrt_pixels):
425
+ dimension = int(sqrt_pixels)
426
+ logger.info(f"Frame size {frame_size} suggests {dimension}x{dimension} RGBA")
427
+ data = np.frombuffer(frame.data, dtype=np.uint8)
428
+ return data.reshape((dimension, dimension, 4)) # type: ignore[no-any-return]
429
+
417
430
  common_resolutions = [
418
431
  (640, 480, 3), # VGA RGB
419
432
  (640, 480, 4), # VGA RGBA
@@ -448,6 +461,45 @@ class OneWayShmController(IController):
448
461
  logger.error("Failed to convert frame to Mat: %s", e)
449
462
  return None
450
463
 
464
+ def _parse_metadata_json(self, metadata_bytes: bytes | memoryview) -> GstMetadata | None:
465
+ """
466
+ Parse metadata JSON from bytes, handling null padding and boundaries.
467
+
468
+ Args:
469
+ metadata_bytes: Raw metadata bytes or memoryview
470
+
471
+ Returns:
472
+ GstMetadata object or None if parsing fails
473
+ """
474
+ try:
475
+ # Convert to string
476
+ if isinstance(metadata_bytes, memoryview):
477
+ metadata_bytes = bytes(metadata_bytes)
478
+ metadata_str = metadata_bytes.decode("utf-8")
479
+
480
+ # Find JSON boundaries (handle null padding)
481
+ json_start = metadata_str.find("{")
482
+ if json_start < 0:
483
+ logger.debug("No JSON found in metadata")
484
+ return None
485
+
486
+ json_end = metadata_str.rfind("}")
487
+ if json_end <= json_start:
488
+ logger.debug("Invalid JSON boundaries in metadata")
489
+ return None
490
+
491
+ # Extract JSON
492
+ metadata_str = metadata_str[json_start : json_end + 1]
493
+
494
+ # Parse JSON
495
+ metadata_json = json.loads(metadata_str)
496
+ metadata = GstMetadata.from_json(metadata_json)
497
+ return metadata
498
+
499
+ except Exception as e:
500
+ logger.debug("Failed to parse metadata JSON: %s", e)
501
+ return None
502
+
451
503
  def _infer_caps_from_frame(self, mat: Mat) -> None: # type: ignore[valid-type]
452
504
  """
453
505
  Infer GStreamer caps from OpenCV Mat.
@@ -495,7 +547,7 @@ class DuplexShmController(IController):
495
547
  )
496
548
 
497
549
  self._connection = connection
498
- self._duplex_server: Optional[IImmutableDuplexServer] = None
550
+ self._duplex_server: Optional[ImmutableDuplexServer] = None
499
551
  self._gst_caps: Optional[GstCaps] = None
500
552
  self._metadata: Optional[GstMetadata] = None
501
553
  self._is_running = False
@@ -540,6 +592,11 @@ class DuplexShmController(IController):
540
592
  if not self._connection.buffer_name:
541
593
  raise ValueError("Buffer name is required for shared memory connection")
542
594
  timeout_seconds = self._connection.timeout_ms / 1000.0
595
+ logger.debug(
596
+ "Creating duplex server with timeout: %d ms (%.1f seconds)",
597
+ self._connection.timeout_ms,
598
+ timeout_seconds,
599
+ )
543
600
  factory = DuplexChannelFactory()
544
601
  self._duplex_server = factory.create_immutable_server(
545
602
  self._connection.buffer_name, config, timeout_seconds
@@ -571,6 +628,44 @@ class DuplexShmController(IController):
571
628
 
572
629
  logger.info("DuplexShmController stopped")
573
630
 
631
+ def _parse_metadata_json(self, metadata_bytes: bytes | memoryview) -> GstMetadata | None:
632
+ """
633
+ Parse metadata JSON from bytes, handling null padding and boundaries.
634
+
635
+ Args:
636
+ metadata_bytes: Raw metadata bytes or memoryview
637
+
638
+ Returns:
639
+ GstMetadata object or None if parsing fails
640
+ """
641
+ try:
642
+ # Convert to string
643
+ if isinstance(metadata_bytes, memoryview):
644
+ metadata_bytes = bytes(metadata_bytes)
645
+ metadata_str = metadata_bytes.decode("utf-8")
646
+
647
+ # Find JSON boundaries (handle null padding)
648
+ json_start = metadata_str.find("{")
649
+ if json_start < 0:
650
+ logger.debug("No JSON found in metadata")
651
+ return None
652
+
653
+ json_end = metadata_str.rfind("}")
654
+ if json_end <= json_start:
655
+ logger.debug("Invalid JSON boundaries in metadata")
656
+ return None
657
+
658
+ # Extract JSON
659
+ metadata_str = metadata_str[json_start : json_end + 1]
660
+
661
+ # Parse JSON
662
+ metadata_json = json.loads(metadata_str)
663
+ metadata = GstMetadata.from_json(metadata_json)
664
+ return metadata
665
+ except Exception as e:
666
+ logger.debug("Failed to parse metadata JSON: %s", e)
667
+ return None
668
+
574
669
  def _on_metadata(self, metadata_bytes: bytes | memoryview) -> None:
575
670
  """
576
671
  Handle metadata from duplex channel.
@@ -582,23 +677,20 @@ class DuplexShmController(IController):
582
677
  "_on_metadata called with %d bytes", len(metadata_bytes) if metadata_bytes else 0
583
678
  )
584
679
  try:
585
- # Convert memoryview to bytes if needed
586
- if isinstance(metadata_bytes, memoryview):
587
- metadata_bytes = bytes(metadata_bytes)
588
-
589
680
  # Log raw bytes for debugging
590
681
  logger.debug(
591
682
  "Raw metadata bytes (first 100): %r",
592
683
  metadata_bytes[: min(100, len(metadata_bytes))],
593
684
  )
594
685
 
595
- metadata_str = metadata_bytes.decode("utf-8")
596
- logger.debug("Decoded metadata string: %r", metadata_str[: min(200, len(metadata_str))])
597
-
598
- metadata_json = json.loads(metadata_str)
599
- self._metadata = GstMetadata.from_json(metadata_json)
600
- self._gst_caps = self._metadata.caps
601
- logger.info("Received metadata: %s", self._metadata)
686
+ # Use helper method to parse metadata
687
+ metadata = self._parse_metadata_json(metadata_bytes)
688
+ if metadata:
689
+ self._metadata = metadata
690
+ self._gst_caps = metadata.caps
691
+ logger.info("Received metadata: %s", self._metadata)
692
+ else:
693
+ logger.warning("Failed to parse metadata from buffer initialization")
602
694
  except Exception as e:
603
695
  logger.error("Failed to parse metadata: %s", e, exc_info=True)
604
696
 
@@ -622,6 +714,30 @@ class DuplexShmController(IController):
622
714
 
623
715
  self._frame_count += 1
624
716
 
717
+ # Try to read metadata if we don't have it yet
718
+ if (
719
+ self._metadata is None
720
+ and self._duplex_server
721
+ and self._duplex_server.request_reader
722
+ ):
723
+ try:
724
+ metadata_bytes = self._duplex_server.request_reader.get_metadata()
725
+ if metadata_bytes:
726
+ # Use helper method to parse metadata
727
+ metadata = self._parse_metadata_json(metadata_bytes)
728
+ if metadata:
729
+ self._metadata = metadata
730
+ self._gst_caps = metadata.caps
731
+ logger.info(
732
+ "Successfully read metadata from buffer '%s': %s",
733
+ self._connection.buffer_name,
734
+ self._gst_caps,
735
+ )
736
+ else:
737
+ logger.debug("Failed to parse metadata in frame processing")
738
+ except Exception as e:
739
+ logger.debug("Failed to read metadata in frame processing: %s", e)
740
+
625
741
  # Convert input frame to Mat
626
742
  input_mat = self._frame_to_mat(request_frame)
627
743
  if input_mat is None: