#!/usr/bin/python3 import argparse import os from config import Config from log import INFO from model import Tracks, session from pydub import AudioSegment from tinytag import TinyTag def main(): "Main loop" INFO("Starting") # 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: INFO("Updating database") update_db() INFO("Finished") def add_path_to_db(path): "Add passed path to database along with metadata" track = Tracks.get_or_create(path) tag = TinyTag.get(path) audio = get_audio_segment(path) track.title = tag.title track.artist = tag.artist track.duration = int(round(tag.duration, Config.MILLISECOND_SIGFIGS) * 1000) track.start_gap = leading_silence(audio) track.fade_at = round(fade_point(audio) / 1000, Config.MILLISECOND_SIGFIGS) * 1000 track.silence_at = round(trailing_silence(audio) / 1000, Config.MILLISECOND_SIGFIGS) * 1000 track.mtime = os.path.getmtime(path) session.commit() 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=Config.DBFS_SILENCE, chunk_size=Config.AUDIO_SEGMENT_CHUNK_SIZE): """ 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 < # noqa W504 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=Config.DBFS_FADE, chunk_size=Config.AUDIO_SEGMENT_CHUNK_SIZE): """ 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): # noqa W503 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=Config.AUDIO_SEGMENT_CHUNK_SIZE): return fade_point(audio_segment, silence_threshold, chunk_size) def update_db(): """ Repopulate database """ # Search for tracks in only one of directory and database db_paths = set(Tracks.get_all_paths()) os_paths_list = [] for root, dirs, files in os.walk(Config.ROOT): for f in files: path = os.path.join(root, f) ext = os.path.splitext(f)[1] if ext in [".flac", ".mp3"]: os_paths_list.append(path) os_paths = set(os_paths_list) for path in list(db_paths - os_paths): INFO(f"To remove from database: {path}") for path in list(os_paths - db_paths): INFO(f"Adding to datbase: {path}") add_path_to_db(path) if __name__ == '__main__': main()