#!/usr/bin/python3 import argparse import hashlib import logging import os import pytiger.logging.config from tinytag import TinyTag from pydub import AudioSegment from model import Tracks, session # "Constants" ROOT = "/home/kae/music" # Instantiate logging pytiger.logging.config.basic_config(stderr=False, level=logging.INFO) log = logging.getLogger(__name__) log.info("Starting") def main(): "Main loop" # Parse command line p = argparse.ArgumentParser() p.add_argument('-u', '--update', action="store_true", dest="update", default=True, help="Update database") args = p.parse_args() # Run as required if args.update: log.info("Updating database") update_db() log.info("Finished") def get_audio_segment(path): try: if path.endswith('.mp3'): return AudioSegment.from_mp3(path) elif path.endswith('.flac'): return AudioSegment.from_file(path, "flac") except AttributeError: return None def leading_silence(audio_segment, silence_threshold=-50.0, chunk_size=10): """ Returns the millisecond/index that the leading silence ends. audio_segment - the segment to find silence in silence_threshold - the upper bound for how quiet is silent in dFBS chunk_size - chunk size for interating over the segment in ms https://github.com/jiaaro/pydub/blob/master/pydub/silence.py """ trim_ms = 0 # ms assert chunk_size > 0 # to avoid infinite loop while ( audio_segment[trim_ms:trim_ms + chunk_size].dBFS < silence_threshold and trim_ms < len(audio_segment)): trim_ms += chunk_size # if there is no end it should return the length of the segment return min(trim_ms, len(audio_segment)) def fade_point(audio_segment, fade_threshold=-20.0, chunk_size=10): """ Returns the millisecond/index of the point where the fade is down to fade_threshold and doesn't get louder again. audio_segment - the sdlg_search_database_uiegment to find silence in fade_threshold - the upper bound for how quiet is silent in dFBS chunk_size - chunk size for interating over the segment in ms """ assert chunk_size > 0 # to avoid infinite loop segment_length = audio_segment.duration_seconds * 1000 # ms trim_ms = segment_length - chunk_size while ( audio_segment[trim_ms:trim_ms + chunk_size].dBFS < fade_threshold and trim_ms > 0): trim_ms -= chunk_size # if there is no trailing silence, return lenght of track (it's less # the chunk_size, but for chunk_size = 10ms, this may be ignored) return int(trim_ms) def trailing_silence(audio_segment, silence_threshold=-50.0, chunk_size=10): return fade_point(audio_segment, silence_threshold, chunk_size) def update_db(): """ Repopulate database TODO: remove missing files """ count = 0 for root, dirs, files in os.walk(ROOT): for f in files: count += 1 path = os.path.join(root, f) ext = os.path.splitext(f)[1] if ext in [".flac", ".mp3"]: track = Tracks.get_or_create(os.path.relpath(path, ROOT)) tag = TinyTag.get(path) audio = get_audio_segment(path) track.title = tag.title track.artist = tag.artist track.duration = int(tag.duration * 1000) track.start_gap = leading_silence(audio) track.fade_at = fade_point(audio) track.silence_at = trailing_silence(audio) track.mtime = os.path.getmtime(path) session.commit() elif ext not in [".jpg"]: print(f"Unrecognised file type: {path}") print(f"{count} files processed") def md5(path): "https://stackoverflow.nl9om/questions/3431825/" "generating-an-md5-checksum-of-a-file" hash_md5 = hashlib.md5() with open(path, "rb") as f: for chunk in iter(lambda: f.read(4096), b""): hash_md5.update(chunk) return hash_md5.hexdigest() if __name__ == '__main__': main()