The lerobot-record command records robot demonstrations to create datasets for training.
Command
Location: src/lerobot/scripts/lerobot_record.py
Overview
The recording script:
- Captures robot observations (cameras, joint states) and actions
- Supports teleoperation or policy-driven recording
- Encodes videos efficiently with hardware acceleration
- Streams encoded videos in real-time (optional)
- Uploads datasets to Hugging Face Hub
- Visualizes data with Rerun
Key Options
Robot Configuration
Robot type: so100_follower, koch_follower, aloha, etc.
Serial port for robot connection (e.g., /dev/ttyUSB0).
Unique identifier for this robot instance.
Camera configuration dictionary.Example:--robot.cameras='{
laptop: {type: opencv, index_or_path: 0, width: 640, height: 480, fps: 30},
phone: {type: opencv, index_or_path: 1, width: 640, height: 480, fps: 30}
}'
Teleoperation Configuration
Teleoperator type: so100_leader, koch_leader, keyboard, etc.
Serial port for teleoperator device.
Unique identifier for teleoperator.
Dataset Configuration
Dataset repository ID in format {username}/{dataset_name}.
Task description (e.g., “Pick the cube and place it in the box”).
Local directory for dataset storage.
Frames per second for recording.
Number of episodes to record.
Maximum duration per episode in seconds.
Time allocated for resetting between episodes.
Video Encoding Options
Video codec: h264, hevc, libsvtav1, auto, or hardware codecs like h264_nvenc, h264_videotoolbox.
--dataset.streaming_encoding
Enable real-time video encoding during capture (makes save_episode instant).
--dataset.encoder_threads
Number of threads per encoder. Lower values reduce CPU usage.
--dataset.encoder_queue_maxsize
Maximum frames to buffer per camera when using streaming encoding.
--dataset.video_encoding_batch_size
Number of episodes to accumulate before batch encoding videos.
Upload Options
Upload dataset to Hugging Face Hub.
Create private repository on Hub.
Visualization Options
Display robot data in Rerun viewer.
IP address for remote Rerun server.
Port for remote Rerun server.
Usage Examples
Basic Recording with Teleoperation
lerobot-record \
--robot.type=so100_follower \
--robot.port=/dev/ttyUSB0 \
--robot.id=follower \
--robot.cameras='{
front: {type: opencv, index_or_path: 0, width: 640, height: 480, fps: 30}
}' \
--teleop.type=so100_leader \
--teleop.port=/dev/ttyUSB1 \
--teleop.id=leader \
--dataset.repo_id=myuser/pick_cube \
--dataset.single_task="Pick the red cube" \
--dataset.num_episodes=10
Recording with Streaming Video Encoding
Streaming encoding makes save_episode() instant by encoding in real-time:
lerobot-record \
--robot.type=so100_follower \
--robot.port=/dev/ttyUSB0 \
--robot.id=follower \
--robot.cameras='{
laptop: {type: opencv, index_or_path: 0, width: 640, height: 480, fps: 30},
phone: {type: opencv, index_or_path: 1, width: 640, height: 480, fps: 30}
}' \
--teleop.type=so100_leader \
--teleop.port=/dev/ttyUSB1 \
--teleop.id=leader \
--dataset.repo_id=myuser/my_dataset \
--dataset.single_task="Grab the cube" \
--dataset.streaming_encoding=true \
--dataset.encoder_threads=2 \
--dataset.num_episodes=25
Recording with Hardware Video Encoding
# Auto-detect best hardware encoder
lerobot-record \
--robot.type=so100_follower \
--robot.port=/dev/ttyUSB0 \
--robot.id=follower \
--robot.cameras='{...}' \
--teleop.type=so100_leader \
--teleop.port=/dev/ttyUSB1 \
--dataset.repo_id=myuser/my_dataset \
--dataset.single_task="Task description" \
--dataset.vcodec=auto
# Or specify hardware encoder explicitly
# For NVIDIA GPU:
--dataset.vcodec=h264_nvenc
# For macOS:
--dataset.vcodec=h264_videotoolbox
# For Intel:
--dataset.vcodec=h264_qsv
Bimanual Robot Recording
lerobot-record \
--robot.type=bi_so_follower \
--robot.left_arm_config.port=/dev/ttyUSB0 \
--robot.right_arm_config.port=/dev/ttyUSB1 \
--robot.id=bimanual_follower \
--robot.left_arm_config.cameras='{
wrist: {type: opencv, index_or_path: 1, width: 640, height: 480, fps: 30}
}' \
--robot.right_arm_config.cameras='{
wrist: {type: opencv, index_or_path: 2, width: 640, height: 480, fps: 30}
}' \
--teleop.type=bi_so_leader \
--teleop.left_arm_config.port=/dev/ttyUSB2 \
--teleop.right_arm_config.port=/dev/ttyUSB3 \
--teleop.id=bimanual_leader \
--dataset.repo_id=myuser/bimanual_task \
--dataset.single_task="Handover cube between arms" \
--dataset.num_episodes=25 \
--dataset.streaming_encoding=true \
--dataset.encoder_threads=2
Recording with Visualization
lerobot-record \
--robot.type=so100_follower \
--robot.port=/dev/ttyUSB0 \
--robot.id=follower \
--robot.cameras='{...}' \
--teleop.type=so100_leader \
--teleop.port=/dev/ttyUSB1 \
--dataset.repo_id=myuser/my_dataset \
--dataset.single_task="Task description" \
--display_data=true
Recording with Policy (Autonomous)
lerobot-record \
--robot.type=so100_follower \
--robot.port=/dev/ttyUSB0 \
--robot.id=follower \
--robot.cameras='{...}' \
--policy.path=lerobot/my_trained_policy \
--dataset.repo_id=myuser/policy_rollouts \
--dataset.single_task="Autonomous task execution" \
--dataset.num_episodes=50
Recording with Multiple Cameras
lerobot-record \
--robot.type=so100_follower \
--robot.port=/dev/ttyUSB0 \
--robot.id=follower \
--robot.cameras='{
laptop: {type: opencv, index_or_path: 0, width: 1920, height: 1080, fps: 30},
phone: {type: opencv, index_or_path: 1, width: 640, height: 480, fps: 30},
wrist: {type: opencv, index_or_path: 2, width: 640, height: 480, fps: 30}
}' \
--teleop.type=so100_leader \
--teleop.port=/dev/ttyUSB1 \
--dataset.repo_id=myuser/multi_camera_dataset \
--dataset.single_task="Multi-view manipulation" \
--dataset.num_episodes=50 \
--dataset.streaming_encoding=true
Using RealSense Cameras
lerobot-record \
--robot.type=so100_follower \
--robot.port=/dev/ttyUSB0 \
--robot.id=follower \
--robot.cameras='{
realsense: {
type: realsense,
serial_number: 123456,
width: 640,
height: 480,
fps: 30,
use_depth: true
}
}' \
--teleop.type=so100_leader \
--teleop.port=/dev/ttyUSB1 \
--dataset.repo_id=myuser/realsense_dataset \
--dataset.single_task="Depth-aware manipulation"
Recording Workflow
- Connect Devices: Plug in robot and teleoperator
- Calibrate (if needed): Run
lerobot-calibrate first
- Start Recording: Run
lerobot-record command
- Episode Loop:
- Reset environment (manual)
- Press Enter to start episode
- Perform demonstration
- Press Enter to stop episode
- Episode is saved automatically
- Upload: Dataset is pushed to Hub when complete
Keyboard Controls
During recording:
- Enter: Start/stop episode recording
- Ctrl+C: Stop recording and finalize dataset
- Space: Pause/resume (if using keyboard teleop)
Output Structure
~/.cache/huggingface/lerobot/{repo_id}/
├── data/
│ └── chunk-000/
│ ├── file-000.parquet
│ └── file-001.parquet
├── meta/
│ ├── episodes/
│ ├── info.json
│ ├── stats.json
│ └── tasks.parquet
└── videos/
├── observation.images.laptop/
│ └── chunk-000/
│ ├── file-000.mp4
│ └── file-001.mp4
└── observation.images.phone/
└── chunk-000/
├── file-000.mp4
└── file-001.mp4
Streaming vs Batch Encoding
Streaming Encoding (--dataset.streaming_encoding=true):
- Encodes frames in real-time during capture
save_episode() is near-instant
- Uses more CPU during recording
- Recommended for most use cases
Batch Encoding (default):
- Writes PNG images during capture
- Encodes to video after episode ends
save_episode() takes time
- Lower CPU usage during recording
Hardware Acceleration
Use hardware encoders for best performance:
# Auto-detect (recommended)
--dataset.vcodec=auto
# Manual selection
--dataset.vcodec=h264_nvenc # NVIDIA GPU
--dataset.vcodec=h264_videotoolbox # Apple Silicon/Intel Mac
--dataset.vcodec=h264_qsv # Intel Quick Sync
--dataset.vcodec=h264_vaapi # Linux VA-API
Tips
- Camera Testing: Use
lerobot-find-cameras to identify camera indices
- Port Finding: Use
lerobot-find-port to identify device ports
- Calibration: Run
lerobot-calibrate before first use
- FPS Stability: Use
--dataset.streaming_encoding=true for stable FPS
- Storage: ~1GB per 10 minutes of multi-camera recording at 30fps
- Network: Upload to Hub happens after all episodes are recorded
See Also