Build a training-ready dance dataset from a directory of source videos. The pipeline extracts audio and 3D poses, aligns them on a shared timing grid, segments each video on phrase boundaries, computes motion and audio descriptors, clusters segments, assigns train/val/test splits, and writes metadata for downstream model training.
The intended V2 consumer is an audio-conditioned motion model trained on pairs of mel spectrogram windows and normalized pose sequences.
- Python 3.10+
ffmpegandffprobeavailable onPATH- MediaPipe-compatible CPU environment
uv sync --extra dev
uv run pre-commit installInstall training dependencies for the mobile baseline:
uv sync --extra train --extra devThe default config lives at configs/v1.yaml.
Key defaults:
- audio sample rate:
22050 - mel hop length:
512 - mel bins:
128 - phrase fallback length:
4bars - phrase stride:
1 - minimum mel energy:
-40.0dB - minimum motion activity:
0.02 - clustering:
16clusters - dataset split ratios:
0.8 / 0.1 / 0.1
output_root controls where artefacts, arrays, and dataset metadata are
written.
The CLI orchestrates these stages in order:
01ingest video metadata intoartifacts/01_video_manifest.jsonl02extract WAV audio and video-level mel/beat/downbeat features03extract MediaPipe world-coordinate poses and normalized poses04run alignment checks between audio and pose timelines05segment videos into phrase-aligned training examples and filter out low-energy / low-motion windows08write per-segment arrays and dataset metadata06compute motion features for each segment07compute segment-level audio features09cluster segments10run dataset QA
The current run-all order in code is 01 -> 02 -> 03 -> 05 -> 08 -> 06 -> 07 -> 09 -> 10, with split assignment and weak text labels applied after
clustering and before QA.
Each stage writes a checkpoint sentinel under
<output_root>/artifacts/.checkpoints/ and is skipped on rerun unless
--force is passed.
Run the full pipeline:
uv run dance-pipeline run-all --config configs/v1.yaml --video-root ./videosRun a single stage through the orchestrator:
uv run dance-pipeline run-all --config configs/v1.yaml --video-root ./videos --stage 05Force a stage or full rerun:
uv run dance-pipeline run-all --config configs/v1.yaml --video-root ./videos --forceLaunch the local preview app for generated segments:
uv run dance-pipeline review --config configs/v1.yamlThe review app uses one synchronized segment timeline for pose playback, mel
playhead rendering, and audio playback. By default it plays the materialized
segment clip from audio.clip_url; the UI can also switch to the parent-track
context source from audio.context_url without changing the shared segment
clock.
Pose playback defaults to world-travel view, where the stick figure moves across the canvas using the segment's absolute trajectory (hip midpoint in world coordinates). A toggle switches to body-centered view, which keeps the hip at the canvas center. When no trajectory artifact is available the UI degrades to body-centered rendering and labels the panel accordingly.
The review app serves on http://127.0.0.1:8000 by default. Override the bind
address or port with --host and --port:
uv run dance-pipeline review --config configs/v1.yaml --host 0.0.0.0 --port 8123This command expects generated dataset output under output_root, especially
output/dataset/metadata.jsonl, so run the pipeline first.
If a pose or mel asset is missing, only that panel is expected to degrade while the rest of the review page remains usable. If timing metadata is missing from the review payload, synchronized playback is no longer guaranteed and the preferred behavior is to disable timeline-driven playback instead of falling back to browser repaint timing.
Standalone entrypoints are also exposed:
scan-videos --config ... --video-root ...extract-audio --config ...extract-pose --config ...align --config ...segment --config ...build-dataset --config ...build-training-dataset --config ...train-baseline --config ...compute-features --config ...cluster --config ...qa --config ...
The assembled segment dataset is the source corpus, not yet the final
model-facing contract. To build fixed-window samples for a mobile-oriented
audio-to-dance model, materialize a separate training dataset from
dataset/metadata.jsonl:
uv run dance-pipeline build-training-dataset \
--config configs/v1.yaml \
--audio-context-seconds 2.0 \
--audio-frame-count 86 \
--pose-history-seconds 1.0 \
--pose-history-frame-count 20 \
--pose-target-seconds 1.0 \
--pose-target-frame-count 20 \
--sample-stride-seconds 0.5This writes:
output/training/metadata.jsonloutput/training/spec.jsonoutput/training/audio_context/*.npyoutput/training/pose_history/*.npyoutput/training/pose_target/*.npy
Each sample represents a fixed contract:
- audio input: prior mel window
- optional pose input: prior normalized pose window
- pose target: future normalized pose window
Current default contract:
audio_context:[86, 128]for about2.0sof mel contextpose_target:[20, 33, 3]for about1.0sof future motion at20 FPSpose_history:[20, 33, 3]for about1.0sof recent normalized motion context
Operational notes:
build-training-datasetis deterministic for the same inputs and spec- it overwrites current metadata and matching sample paths
- it does not yet clean stale sample files left behind by older specs
- it shows a segment-level progress bar and logs a final segment/sample summary
The first baseline model is a mel-to-pose generator intended to stay small enough for eventual phone deployment:
- input: recent mel window from
output/training/audio_context/ - input: recent pose history from
output/training/pose_history/ - output: next normalized pose chunk from
output/training/pose_target/
The training stack depends on PyTorch and is kept behind the train extra:
uv sync --extra train --extra dev
uv run dance-pipeline train-baseline --config configs/v1.yaml
uv run dance-pipeline eval-baseline --config configs/v1.yaml --split val --max-samples 16By default this trains a lightweight Conv1D audio encoder plus GRU decoder. It
uses pose_history as an anchor and predicts per-frame pose deltas, which are
then accumulated into the future pose chunk. Checkpoints and metrics are written
under output/models/mobile_baseline/.
The intended first deployment path is React Native -> ONNX Runtime Mobile.
That is why the baseline sticks to simple export-friendly layers instead of
heavier research-first architectures, while still using recent pose history as
part of the conditioning signal.
Training notes:
- PyTorch is required only for baseline training, not for the dataset pipeline
train-baselineuses epoch-level progress bars and logs per-epoch losses- default training objective combines pose loss, velocity loss, and delta loss
downstream.training.delta_loss_weightcontrols how strongly the model is pushed to moveeval-baselinewrites prediction/target.npypairs plusmetrics.jsonfor inspection- batch-level progress and ONNX export commands are not implemented yet
Recommended first run:
uv sync --extra train --extra dev
uv run dance-pipeline run-all --config configs/v1.yaml --video-root ./videos
uv run dance-pipeline build-training-dataset --config configs/v1.yaml
uv run dance-pipeline train-baseline --config configs/v1.yaml
uv run dance-pipeline eval-baseline --config configs/v1.yaml --split val --max-samples 16Acceptance check after retraining:
eval-baselinemetrics should improve beyond the static-mean baseline- exported
predictions/*.npyshould vary across frames, not stay almost constant - the review viewer should show
predictionmoving instead of freezing in one pose
If that check passes, the next engineering steps are:
- add ONNX export for the current baseline checkpoint
- load the exported model from a small React Native inference spike
- measure on-device latency and memory for a single forward pass
- only then decide whether beat/downbeat features or a stronger decoder are worth the added mobile cost
Typical output tree under output_root:
output/
artifacts/
01_video_manifest.jsonl
02_audio_index.jsonl
03_pose_index.jsonl
03_pose_quality.jsonl
05_segment_index.jsonl
motion_features.jsonl
audio_segment_features.jsonl
09_cluster_report.json
10_qa_report.json
10_qa_samples.json
.checkpoints/
audio/
poses/raw/
poses/normalized/
trajectories/
features/audio/
features/motion/
features/segment_audio/
clusters/assignments.json
dataset/metadata.jsonl
training/
metadata.jsonl
spec.json
audio_context/
pose_history/
pose_target/
models/mobile_baseline/
segments/
Each line in dataset/metadata.jsonl is a DatasetRecord with:
- identity:
segment_id,video_id - timing:
time_start,time_end,bpm,beat_timestamps - phrase metadata:
bars_per_phraseon segment records inartifacts/05_segment_index.jsonl - array paths:
mel_path,pose_raw_path,pose_norm_path,trajectory_path(optional) - derived annotations:
motion_features,audio_features,cluster_id,split - QA and labeling:
qa,segment_text_summary,cluster_text_summary
Tensor conventions:
- mel:
[T_mel, 128] - pose raw:
[T_pose, 33, 3] - pose normalized:
[T_pose, 33, 3]
T_mel and T_pose are not resampled to match each other. Alignment is
preserved through phrase timing plus beat-index metadata.
uv run pytest tests/
uv run ruff check src tests
uv run mypy srcSee docs/developer.md for module and schema details, and docs/runbook.md
for operational guidance.