musicmuster/app/songdb.py

133 lines
4.0 KiB
Python
Executable File

#!/usr/bin/python3
import argparse
import hashlib
import logging
import os
from config import Config
from log import INFO
from model import Tracks, session
from pydub import AudioSegment
from tinytag import TinyTag
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=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 <
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):
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), Config.MILLISECOND_SIGFIGS) * 1000
track.silence_at = round(
trailing_silence(audio), Config.MILLISECOND_SIGFIGS) * 1000
track.mtime = os.path.getmtime(path)
session.commit()
elif ext not in [".jpg"]:
print(f"Unrecognised file type: {path}")
print(f"{count} files processed")
if __name__ == '__main__':
main()