musicmuster/app/songdb.py
2021-03-30 19:42:39 +01:00

135 lines
4.0 KiB
Python

#!/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 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
TODO: remove missing files
"""
count = 0
for root, dirs, files in os.walk(Config.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(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()
elif ext not in [".jpg"]:
INFO(f"Unrecognised file type: {path}")
INFO(f"{count} files processed")
if __name__ == '__main__':
main()