Skip to content

About

Generate beat-synchronized jump-cut music videos from one song and multiple source videos using Python, Librosa, and FFmpeg.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

7 Commits

Folders and files

Repository files navigation

MusiMixMaker

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.

What the MVP does

  • 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 --seed or 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.

Requirements

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

  • 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 build

Media 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.mp4

For several source videos:

docker compose run --rm musimixmaker --audio music.ogg video1.mp4 video2.mov video3.mkv --output mix.mp4

Useful commands:

docker compose run --rm musimixmaker --help
docker compose run --rm musimixmaker --audio music.mp3 video1.mp4 --dry-run --plan edit-plan.json

docker 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.

Local Python

  • Python 3.11 or newer
  • FFmpeg, including ffprobe, available on PATH

Verify FFmpeg before installing:

ffmpeg -version
ffprobe -version

Installation

From the project directory:

python -m venv .venv

On 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]"

Usage

One source video:

musimixmaker --audio music.mp3 video.mp4

Several source videos:

musimixmaker --audio music.ogg video1.mp4 video2.mov video3.mkv --output mix.mp4

Use --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.mp4

Relative 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.mp4

Video 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.mp4

Randomize 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.mp4

In 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.json

Add 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.

Command reference

MusiMixMaker has one command and no subcommands. After installation, use musimixmaker; python -m musimixmaker is an equivalent invocation.

Application commands

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.

Main command arguments and options

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.

Setup and development commands

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.

Editing behavior

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.

Development

pytest
ruff check .

The real-media integration test is opt-in:

$env:MUSIMIX_RUN_INTEGRATION = "1"
pytest -m integration

Current scope

This MVP implements hard jump cuts. Transitions, scene-aware selection, energy-weighted editing, GPU encoder selection, and a graphical interface are natural later additions.

About

Generate beat-synchronized jump-cut music videos from one song and multiple source videos using Python, Librosa, and FFmpeg.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages