From 086a4a293327062052029850ded9aa38c628408d Mon Sep 17 00:00:00 2001 From: Keith Edmunds Date: Tue, 23 Mar 2021 08:54:02 +0000 Subject: [PATCH] Store all track metadata in db --- model.py | 6 ++++- songdb.py | 72 ++++++++++++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 74 insertions(+), 4 deletions(-) diff --git a/model.py b/model.py index d7e9a2c..6b7a27f 100644 --- a/model.py +++ b/model.py @@ -42,8 +42,12 @@ class Tracks(Base): id = Column(Integer, primary_key=True, autoincrement=True) title = Column(String(256), index=True) artist = Column(String(256), index=True) - length = Column(Integer, index=True) + duration = Column(Integer, index=True) + start_gap = Column(Integer, index=False) + fade_at = Column(Integer, index=False) + silence_at = Column(Integer, index=False) path = Column(String(2048), index=False, nullable=False) + mtime = Column(Float, index=True) lastplayed = Column(DateTime, index=True, default=None) diff --git a/songdb.py b/songdb.py index 57b7cf6..6524d91 100755 --- a/songdb.py +++ b/songdb.py @@ -8,6 +8,7 @@ import os import pytiger.logging.config from tinytag import TinyTag +from pydub import AudioSegment from model import Tracks, session @@ -38,6 +39,66 @@ def main(): 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 @@ -54,15 +115,20 @@ def update_db(): 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.length = int(tag.duration * 1000) + 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}") - session.commit() - print(f"{count} files processed")