#!/usr/bin/python3 import sys import vlc from datetime import datetime, timedelta from pydub import AudioSegment from PyQt5.QtCore import QEvent from PyQt5.QtWidgets import QApplication, QDialog, QMainWindow, QMessageBox from PyQt5.QtWidgets import QTableWidgetItem, QFileDialog from PyQt5.uic import loadUi from threading import Timer from time import sleep from timeloop import Timeloop class RepeatedTimer: def __init__(self, interval, function, *args, **kwargs): self._timer = None self.interval = interval self.function = function self.args = args self.kwargs = kwargs self.is_running = False self.start() def _run(self): self.is_running = False self.start() self.function(*self.args, **self.kwargs) def start(self): if not self.is_running: self._timer = Timer(self.interval, self._run) self._timer.start() self.is_running = True def stop(self): self._timer.cancel() self.is_running = False class Track: def __init__(self, path): self.path = path audio = self.get_audio_segment(path) self.length = int(len(audio)) self.start_gap = self.leading_silence(audio) self.fade_at = self.fade_point(audio) self.silence_at = self.trailing_silence(audio) print( f" path={self.path}" f" length={self.length}" f" start_gap={self.start_gap}" f" fade_at={self.fade_at}" f" silence_at={self.silence_at}" ) def get_audio_segment(self, path): try: if path.endswith('.mp3'): return AudioSegment.from_mp3(path) elif path.endswith('.flac'): return AudioSegment.from_file(path, "flac") except: return None def leading_silence(self, 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(self, 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 segment 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(self, audio_segment, silence_threshold=-50.0, chunk_size=10): return self.fade_point(audio_segment, silence_threshold, chunk_size) # tl = Timeloop() # #def ms_to_mmss(ms, decimals=0): # if not ms: # return "-" # if ms < 0: # sign = "-" # else: # sign = "" # # minutes, remainder = divmod(ms, 60 * 1000) # seconds = remainder / 1000 # # return f"{sign}{minutes:.0f}:{seconds:02.{decimals}f}" # # ## @tl.job(interval=timedelta(seconds=1)) #def update_progress(player, talk_at, silent_at): # elapsed_time = player.get_time() # total_time = player.get_length() # remaining_time = total_time - elapsed_time # talk_time = remaining_time - (total_time - talk_at) # silent_time = remaining_time - (total_time - silent_at) # end_time = (datetime.now() + timedelta( # milliseconds=remaining_time)).strftime("%H:%M:%S") # print( # f"\t{ms_to_mmss(elapsed_time)}/" # f"{ms_to_mmss(total_time)}\t\t" # f"Talk in: {ms_to_mmss(talk_time)} " # f"Silent in: {ms_to_mmss(silent_time)} " # f"Ends at: {end_time} [{ms_to_mmss(remaining_time)}]" # , end="\r") # # ## Print name of current song, print name of next song. Play current when ## return pressed, Pri--current-song-output-lengthnt remaining time every ## second. When it ends, print name of new current and next song. # # #def test(): # track = "wibg.mp3" # segment = AudioSegment.from_mp3(track) # print(f"Track: {track}") # print(f"Leading silence: {ms_to_mmss(leading_silence(segment), decimals=1)}") # talk_at = significant_fade(segment) # silent_at = trailing_silence(segment) # print(f"Talkover fade: {ms_to_mmss(talk_at)}") # print(f"Track silent from: {ms_to_mmss(silent_at)}") # p = vlc.MediaPlayer("wibg.mp3") # _ = input("") # p.play() # print() # rt = RepeatedTimer(0.5, update_progress, p, talk_at, silent_at) # sleep(1) # while p.is_playing(): # sleep(1) # rt.stop() # better in a try/finally block to make sure the program ends! # print("End") ROOT = "/home/kae/music/" from main_window_ui import Ui_MainWindow class Window(QMainWindow, Ui_MainWindow): def __init__(self, parent=None): super().__init__(parent) self.setupUi(self) self.connectSignalsSlots() self.resize(1599, 981) self.playlist.setColumnWidth(0, 0) self.playlist.setColumnWidth(1, 381) self.playlist.setColumnWidth(2, 322) self.playlist.setColumnWidth(3, 78) self.playlist.setColumnWidth(4, 68) self.playlist.setColumnWidth(5, 47) self.playlist.setColumnWidth(6, 577) def __del__(self): for column in range(self.playlist.columnCount()): print(f"Column {column}: {self.playlist.columnWidth(column)}") print(f"Window height: {self.height()} Window width: {self.width()}") def connectSignalsSlots(self): self.pushButton.clicked.connect(self.btnpush) # import ipdb; ipdb.set_trace() # self.playlist.columnResized.connect(self.playlistresize) # self.playlist.horizontalHeader().sectionResized.connect(self.kae) # import ipdb; ipdb.set_trace() # self.playlist.viewport().installEventFilter(self) # self.playlist.horizontalHeader().sectionClicked.connect(self.kae2) # x = self.playlist.horizontalHeader() # import ipdb; ipdb.set_trace() #def kae2(self, index): # print(f"table header click, index={index}") #def kae(self, a, b, c): # self.data.append(f"a={a}, b={b}, c={c}") #def mousePressEvent(self, QMouseEvent): # print("mouse press") #def mouseReleaseEvent(self, QMouseEvent): # print("mouse release") # # QMessageBox.about( # # self, # # "About Sample Editor", # # "\n".join(self.data) # # ) #def eventFilter(self, obj, event): # # you could be doing different groups of actions # # for different types of widgets and either filtering # # the event or not. # # Here we just check if its one of the layout widgets # # if self.layout.indexOf(obj) != -1: # # print(f"event received: {event.type()}") # if event.type() == QEvent.MouseButtonPress: # print("Widget click") # # if I returned True right here, the event # # would be filtered and not reach the obj, # # meaning that I decided to handle it myself # # regardless, just do the default # return super().eventFilter(obj, event) def btnpush(self): dlg = QFileDialog() dlg.setFileMode(QFileDialog.ExistingFile) dlg.setViewMode(QFileDialog.Detail) dlg.setDirectory(ROOT) dlg.setNameFilter("Music files (*.flac *.mp3)") if dlg.exec_(): for fname in dlg.selectedFiles(): track = Track(fname) if __name__ == "__main__": app = QApplication(sys.argv) win = Window() win.show() sys.exit(app.exec())