diff --git a/lelab/record.py b/lelab/record.py index 3e5eb4c0..f2421613 100644 --- a/lelab/record.py +++ b/lelab/record.py @@ -37,6 +37,8 @@ logger = logging.getLogger(__name__) +LOCAL_DATASET_NAMESPACE = "local" + # Global variables for recording state recording_active = False recording_thread: threading.Thread | None = None @@ -91,6 +93,22 @@ class DatasetInfoRequest(BaseModel): dataset_repo_id: str +def _normalize_dataset_repo_id(repo_id: str) -> str: + """Return a LeRobot-compatible dataset ID while preserving Hub IDs. + + The landing page deliberately accepts a bare dataset name while the user is + not authenticated. LeRobot still requires an ``owner/name`` ID for local + recordings, so local-only recordings use a reserved local namespace. + """ + if "/" in repo_id: + namespace, name = repo_id.split("/", 1) + name = re.sub(r"[^A-Za-z0-9._-]", "_", name) + return f"{namespace}/{name}" + + name = re.sub(r"[^A-Za-z0-9._-]", "_", repo_id) + return f"{LOCAL_DATASET_NAMESPACE}/{name}" + + def _platform_backend(): """Pin the OpenCV backend per-platform so the index→camera mapping matches what the /available-cameras thumbnails were captured with. cv2.CAP_ANY can @@ -247,12 +265,7 @@ def handle_start_recording(request: RecordingRequest) -> dict[str, Any]: # recording over an invalid character. HF repo names allow only # [A-Za-z0-9._-]; everything else becomes "_". if request.dataset_repo_id: - if "/" in request.dataset_repo_id: - namespace, name = request.dataset_repo_id.split("/", 1) - name = re.sub(r"[^A-Za-z0-9._-]", "_", name) - request.dataset_repo_id = f"{namespace}/{name}" - else: - request.dataset_repo_id = re.sub(r"[^A-Za-z0-9._-]", "_", request.dataset_repo_id) + request.dataset_repo_id = _normalize_dataset_repo_id(request.dataset_repo_id) # Stamp the repo_id with a timestamp (matches lerobot-record CLI behavior), # so each session lands in a unique directory and the frontend gets the # final id back in the response and status payload. diff --git a/tests/test_record.py b/tests/test_record.py index 486eb959..724a4e8a 100644 --- a/tests/test_record.py +++ b/tests/test_record.py @@ -27,6 +27,18 @@ def test_recording_request_rejects_missing_required_fields() -> None: RecordingRequest() +def test_normalize_dataset_repo_id_adds_local_namespace_for_bare_name() -> None: + from lelab.record import _normalize_dataset_repo_id + + assert _normalize_dataset_repo_id("test recording") == "local/test_recording" + + +def test_normalize_dataset_repo_id_preserves_hub_namespace() -> None: + from lelab.record import _normalize_dataset_repo_id + + assert _normalize_dataset_repo_id("samuel-hills/test recording") == "samuel-hills/test_recording" + + def test_recording_status_handler_exposes_state_fields() -> None: from lelab.record import handle_recording_status