-
-
Save leleogere/0b21605a3512e8f6abd49ce39a7d6dc3 to your computer and use it in GitHub Desktop.
Kdenlive beat sync
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import argparse | |
| import datetime | |
| import librosa | |
| def parse_delta(s): | |
| date = datetime.datetime.strptime(s, "%H:%M:%S") | |
| return datetime.timedelta( | |
| hours=date.hour, minutes=date.minute, seconds=date.second | |
| ) | |
| def main(): | |
| parser = argparse.ArgumentParser( | |
| prog='Kdenlive beatfinder', | |
| description='Creates timeline guides matching the song beat' | |
| ) | |
| parser.add_argument('input_file', | |
| help="Audio file to analyze for beat detection") | |
| parser.add_argument('--output_file', default="beats.txt", | |
| help="Output text file for timeline guides (default: beats.txt)") | |
| parser.add_argument('--bpm', type=float, default=None, | |
| help="Force a specific tempo in beats per minute") | |
| parser.add_argument('--offset', default="0:00:0", | |
| help="Time offset added to all timestamps (H:MM:SS)") | |
| parser.add_argument('--tightness', type=float, default=100, | |
| help="Beat rigidity; higher values enforce a steadier tempo") | |
| parser.add_argument('--beats-per-bar', type=int, default=None, | |
| help="Beats per bar; label guide as <bar>:<beat> (e.g. 2:3 for 2nd bar, 3rd beat)") | |
| parser.add_argument('--first-bar-offset', type=int, default=0, | |
| help="Number of pickup beats before bar 1 (labels as 0:y)") | |
| args = parser.parse_args() | |
| offset = parse_delta(args.offset) | |
| y, sr = librosa.load(args.input_file) | |
| onset_envelope = librosa.onset.onset_strength(y=y, sr=sr) | |
| tempo, beat_frames = librosa.beat.beat_track( | |
| onset_envelope=onset_envelope, bpm=args.bpm, tightness=args.tightness | |
| ) | |
| beat_times = librosa.frames_to_time(beat_frames, sr=sr) | |
| print(f"{args.input_file} BPM: {tempo[0]:.2f}") | |
| print(f"Writing timeline guides to {args.output_file}...") | |
| with open(args.output_file, "wt") as f: | |
| for i, seconds in enumerate(beat_times): | |
| if args.beats_per_bar is None: | |
| label = str(i) | |
| else: | |
| if i < args.first_bar_offset: | |
| bar = 0 | |
| beat_in_bar = i + 1 | |
| else: | |
| j = i - args.first_bar_offset | |
| bar = int((j // args.beats_per_bar) + 1) | |
| beat_in_bar = int((j % args.beats_per_bar) + 1) | |
| label = f"{bar}:{beat_in_bar}" | |
| timestamp = datetime.timedelta(seconds=seconds) + offset | |
| f.write(f"{timestamp} {label}\n") | |
| if __name__ == '__main__': | |
| main() |
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
This fork adds support for labelling bars in guides.
The
--beat-per-barspecify the number of beat in each bar (must be constant). When enabled, this option will label the guides asbar:beat. For example,--beat-per-bar 4will create measures of 4 beats, in which13:3would label for the third beat of the thirteenth barThe option
--first-bar-offsetallows skipping the first beats (labelled as0:x) in order to align the guides with the first bar of the song. For example,--first-bar-offset 5will mark the first 5 beats as0:1to0:5, and start the first measure on beat 6 (labelled as1:1).