MusiMixMaker turns one song and one or more source videos into a jump-cut music video. It detects the song's beat grid, chooses source-video in-points, and asks FFmpeg to render every cut into a single output whose timeline is governed by the original song.
- accepts MP3, OGG, WAV, or any audio format that FFmpeg can decode;
- accepts one or many source videos;
- detects tempo and beat timestamps with librosa;
- groups overly close beats using a configurable minimum cut duration;
- inserts cuts when detected beats are farther apart than the configured maximum cut duration;
- selects deterministic random shots with
--seedor rotates sources with--mode round-robin; - normalizes mixed resolutions and aspect ratios;
- writes an optional JSON edit-decision plan;
- renders H.264 video with the untouched song as the final soundtrack.
Input-video audio is intentionally discarded.
You can run MusiMixMaker either directly with Python or entirely through Docker. The Docker workflow is recommended if you want to avoid managing a local virtual environment.
- Docker Desktop (Windows/macOS) or Docker Engine (Linux)
The image includes Python, the Python dependencies, FFmpeg, and FFprobe. Build it once from the project directory:
docker compose buildMedia files remain on the host. The project directory is mounted into the
container as /data, so input paths and output paths are the same as the
paths you see locally.
Run the CLI through Compose:
docker compose run --rm musimixmaker --audio audio.mp3 video1.mp4 --output mix.mp4For several source videos:
docker compose run --rm musimixmaker --audio music.ogg video1.mp4 video2.mov video3.mkv --output mix.mp4Useful commands:
docker compose run --rm musimixmaker --help
docker compose run --rm musimixmaker --audio music.mp3 video1.mp4 --dry-run --plan edit-plan.jsondocker compose run --rm creates a temporary container for each command and
removes it afterward; the image is reused, and files written under /data
stay in the project directory.
- Python 3.11 or newer
- FFmpeg, including
ffprobe, available onPATH
Verify FFmpeg before installing:
ffmpeg -version
ffprobe -versionFrom the project directory:
python -m venv .venvOn Windows PowerShell:
.venv\Scripts\Activate.ps1
python -m pip install -e ".[dev]"On macOS or Linux:
source .venv/bin/activate
python -m pip install -e ".[dev]"One source video:
musimixmaker --audio music.mp3 video.mp4Several source videos:
musimixmaker --audio music.ogg video1.mp4 video2.mov video3.mkv --output mix.mp4Use --base-dir when the media files are all relative to another directory:
musimixmaker --base-dir "D:\Media\Project" --audio song.mp3 videos\clip1.mp4 videos\clip2.mp4 --output results\mix.mp4Relative audio, video, plan, and output paths are resolved from the base directory. Absolute paths are still accepted unchanged.
To automatically use every video file directly inside the base directory:
musimixmaker --base-dir "E:\Media\Project" --audio song.mp3 --all-videos --output mix.mp4Video files are selected in alphabetical order. The output file is excluded from the selection, and subdirectories are not scanned.
Reproducible random edit with calmer cuts:
musimixmaker --audio music.mp3 video1.mp4 video2.mp4 \
--every-beats 2 --min-cut 0.35 --max-cut 2.0 --seed 42 \
--plan edit-plan.json --output mix.mp4Randomize the duration of each shot while keeping every cut on a detected beat:
musimixmaker --audio music.mp3 video1.mp4 video2.mp4 \
--random-cut-duration --min-cut 0.25 --max-cut 2.0 --seed 42 \
--output mix.mp4In this mode, MusiMixMaker randomly chooses one of the available beat
boundaries between --min-cut and --max-cut. Use --seed when you want the
same random edit to be reproducible. If no beat is available within the
maximum duration, the normal maximum-duration fallback is used.
Preview the decisions without rendering:
musimixmaker --audio music.mp3 video1.mp4 video2.mp4 --dry-run --plan edit-plan.jsonAdd an occasional longer shot to make the edit less repetitive. This example makes every seventh clip hold for about four seconds, while other clips follow the detected beats:
musimixmaker --audio music.mp3 video1.mp4 --hold-every 7 --hold-duration 4 --output mix.mp4--hold-duration must be greater than --max-cut. The hold is implemented by
skipping beat boundaries until the requested duration is reached, so the exact
length can be slightly longer when the next available beat falls later.
Run musimixmaker --help for every option.
MusiMixMaker has one command and no subcommands. After installation, use
musimixmaker; python -m musimixmaker is an equivalent invocation.
| Command | Description |
|---|---|
musimixmaker --help |
Show the complete CLI help message and exit. |
python -m musimixmaker --help |
Show the same help through Python's module runner. |
musimixmaker --version |
Print the installed MusiMixMaker version and exit. |
musimixmaker --audio AUDIO VIDEO [VIDEO ...] |
Analyze the audio beat grid, create the edit plan, and render the music video. VIDEO may be omitted only when --all-videos is used. |
python -m musimixmaker --audio AUDIO VIDEO [VIDEO ...] |
Run the main command through Python's module runner. |
The general form is:
musimixmaker [OPTIONS] [videos]...| Argument or option | Default | Description |
|---|---|---|
videos |
— | One or more source-video paths. Input audio is discarded. |
--audio, -a AUDIO |
required | Music file that defines the beat grid and final output duration. |
--base-dir DIRECTORY |
. |
Base directory for relative audio, video, plan, and output paths. Absolute paths are unchanged. |
--output, -o PATH |
musimix-output.mp4 |
Destination for the rendered MP4. |
--plan PATH |
disabled | Save the edit decisions as JSON. |
--min-cut SECONDS |
0.25 |
Minimum clip duration; must be at least 0.01. |
--max-cut SECONDS |
2.0 |
Maximum desired clip duration; must be at least 0.01 and no smaller than --min-cut. |
--every-beats N |
1 |
Cut on every Nth detected beat; must be at least 1. |
--random-cut-duration |
off | Randomly choose the next detected beat within the --min-cut/--max-cut limits. Use --seed for reproducibility. |
--hold-every N |
disabled | Make every Nth clip a longer hold; must be at least 1. |
--hold-duration SECONDS |
4.0 |
Target duration for periodic hold clips; must be at least 0.01 and greater than --max-cut when --hold-every is enabled. |
--all-videos |
off | Use every supported video file directly inside --base-dir, in alphabetical order. Cannot be combined with explicit videos; subdirectories are not scanned. |
--fallback-bpm BPM |
120.0 |
Fixed tempo used when no reliable beats are detected; must be at least 1.0. |
--mode MODE |
random |
Source-selection strategy: random or round-robin. |
--seed INTEGER |
disabled | Seed source selection and source in-point selection for reproducible edits. |
--width PIXELS |
1920 |
Output width; must be at least 2 and even. |
--height PIXELS |
1080 |
Output height; must be at least 2 and even. |
--fps FPS |
30 |
Constant output frame rate from 1 through 240. |
--fit MODE |
cover |
Fit source videos using cover (crop to fill) or contain (letterbox). |
--crf INTEGER |
18 |
H.264 quality value from 0 through 51; lower values mean higher quality and larger files. |
--preset NAME |
medium |
libx264 encoding preset, such as fast, medium, or slow. |
--dry-run |
off | Print the JSON edit plan without rendering a video. Use --plan to save it as well. |
--overwrite, -y |
off | Replace existing output and plan files. Without it, existing files cause an error. |
--version |
— | Print the version and exit. |
--help |
— | Show command help and exit. |
These commands are available from the project directory:
| Command | Description |
|---|---|
python -m venv .venv |
Create the local Python virtual environment. |
.venv\\Scripts\\Activate.ps1 |
Activate the virtual environment in Windows PowerShell. |
source .venv/bin/activate |
Activate the virtual environment on macOS or Linux. |
python -m pip install -e ".[dev]" |
Install MusiMixMaker in editable mode with development dependencies. |
ffmpeg -version |
Verify that FFmpeg is installed and available on PATH. |
ffprobe -version |
Verify that FFprobe is installed and available on PATH. |
docker compose build |
Build the Docker image with Python dependencies and FFmpeg. |
docker compose run --rm musimixmaker --help |
Show CLI help inside a temporary Docker container. |
docker compose run --rm musimixmaker --audio AUDIO VIDEO --output OUTPUT |
Run MusiMixMaker in Docker using project-directory media files. |
pytest |
Run the test suite. |
pytest -m integration |
Run integration tests; set MUSIMIX_RUN_INTEGRATION=1 first to enable real-media tests. |
ruff check . |
Check the project with Ruff. |
The audio duration is the master duration. MusiMixMaker adds boundaries at the
start and end of the song, filters the detected beats through --every-beats,
and groups boundaries closer than --min-cut. If the music contains a gap
longer than --max-cut, it inserts intermediate cuts so the image does not
remain static for too long.
For each resulting interval it chooses a source and an in-point. If a source is shorter than a requested clip, FFmpeg loops that source. Setting a seed makes the same inputs and options produce the same edit plan.
Because encoded video is frame-based, the video stream can differ from the audio duration by at most approximately one output frame. The original audio timeline remains authoritative.
Large plans are rendered in bounded batches and concatenated before the final
audio mux. This keeps the number of simultaneously open FFmpeg decoders
bounded, even when --all-videos or a dense beat grid produces hundreds of
clips.
pytest
ruff check .The real-media integration test is opt-in:
$env:MUSIMIX_RUN_INTEGRATION = "1"
pytest -m integrationThis MVP implements hard jump cuts. Transitions, scene-aware selection, energy-weighted editing, GPU encoder selection, and a graphical interface are natural later additions.