#!/usr/bin/env python3 """Fit a finished track to a changed edit by cutting or repeating whole bars, keeping the music you chose. python3 music-edit.py in.mp3 out.wav --offset 0.032 --remove 31:35 drop film 31-35 s python3 music-edit.py in.mp3 out.wav --offset 0.032 --repeat 18:26 play film 18-26 s twice Times are FILM times on the beat grid; --offset is the delay you give the track in the mix (audio-analyze.py's phase, as seconds), so the edit points land on the track's own beats. Pick edit points on downbeats (audio-analyze.py prints them per section) and keep the length a whole number of bars (2.0 s at 120 BPM), or the phrase stumbles. Joins get 30 ms crossfades. """ import argparse, subprocess from common import ffmpeg ap = argparse.ArgumentParser() ap.add_argument("src"); ap.add_argument("out"); ap.add_argument("--offset", type=float, default=0.0) ap.add_argument("--remove"); ap.add_argument("--repeat") a = ap.parse_args() X = 0.015 # half of the crossfade if a.remove: s, e = (float(v) - a.offset for v in a.remove.split(":")) fc = (f"[0:a]asplit=2[x][y];[x]atrim=0:{s + X},asetpts=PTS-STARTPTS[p];[y]atrim=start={e - X},asetpts=PTS-STARTPTS[q];" f"[p][q]acrossfade=d={2 * X}:c1=tri:c2=tri") elif a.repeat: s, e = (float(v) - a.offset for v in a.repeat.split(":")) fc = (f"[0:a]asplit=3[x][y][z];[x]atrim=0:{e + X},asetpts=PTS-STARTPTS[p];[y]atrim=start={s - X}:end={e + X},asetpts=PTS-STARTPTS[q];" f"[z]atrim=start={e - X},asetpts=PTS-STARTPTS[r];[p][q]acrossfade=d={2 * X}:c1=tri:c2=tri[pq];[pq][r]acrossfade=d={2 * X}:c1=tri:c2=tri") else: raise SystemExit("pass --remove a:b or --repeat a:b") subprocess.run([ffmpeg(), "-v", "error", "-y", "-i", a.src, "-filter_complex", fc, "-ar", "44100", a.out], check=True) print("wrote", a.out)