import os import psutil from app.config import Config from datetime import datetime from pydub import AudioSegment from mutagen.flac import FLAC from mutagen.mp3 import MP3 from PyQt5.QtWidgets import QMessageBox def fade_point(audio_segment, fade_threshold=0, chunk_size=Config.AUDIO_SEGMENT_CHUNK_SIZE): """ Returns the millisecond/index of the point where the volume drops below the maximum 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 max_vol = audio_segment.dBFS if fade_threshold == 0: fade_threshold = max_vol 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 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 get_tag_data(path): """ Return a dictionary of title, artist, duration-in-milliseconds and path. """ tag = TinyTag.get(path) return dict( title=tag.title, artist=tag.artist, duration=int(round(tag.duration, Config.MILLISECOND_SIGFIGS) * 1000), path=path ) def get_relative_date(past_date, reference_date=None): """ Return how long before reference_date past_date is as string. Params: @past_date: datetime @reference_date: datetime, defaults to current date and time @return: string """ if not past_date: return "Never" if not reference_date: reference_date = datetime.now() # Check parameters if past_date > reference_date: return "get_relative_date() past_date is after relative_date" weeks, days = divmod((reference_date.date() - past_date.date()).days, 7) if weeks == days == 0: # Played today, so return time instead return past_date.strftime("%H:%M") if weeks == 1: weeks_str = "week" else: weeks_str = "weeks" if days == 1: days_str = "day" else: days_str = "days" return f"{weeks} {weeks_str}, {days} {days_str} ago" 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 ms_to_mmss(ms, decimals=0, negative=False): if not ms: return "-" sign = "" if ms < 0: if negative: sign = "-" else: ms = 0 minutes, remainder = divmod(ms, 60 * 1000) seconds = remainder / 1000 # if seconds >= 59.5, it will be represented as 60, which looks odd. # So, fake it under those circumstances if seconds >= 59.5: seconds = 59.0 return f"{sign}{minutes:.0f}:{seconds:02.{decimals}f}" def open_in_audacity(path): """ Open passed file in Audacity Return True if apparently opened successfully, else False """ # Return if audacity not running if "audacity" not in [i.name() for i in psutil.process_iter()]: return False to_pipe = '/tmp/audacity_script_pipe.to.' + str(os.getuid()) from_pipe = '/tmp/audacity_script_pipe.from.' + str(os.getuid()) EOL = '\n' def send_command(command): """Send a single command.""" to_audacity.write(command + EOL) to_audacity.flush() def get_response(): """Return the command response.""" result = '' line = '' while True: result += line line = from_audacity.readline() if line == '\n' and len(result) > 0: break return result def do_command(command): """Send one command, and return the response.""" send_command(command) response = get_response() return response with open(to_pipe, 'w') as to_audacity, open( from_pipe, 'rt') as from_audacity: do_command(f'Import2: Filename="{path}"') def show_warning(title, msg): "Display a warning to user" QMessageBox.warning(None, title, msg, buttons=QMessageBox.Cancel) 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)