File selection and track parsing

This commit is contained in:
Keith Edmunds 2021-03-18 23:36:50 +00:00
parent 0a3bcd0668
commit 631703d0b0
2 changed files with 589 additions and 0 deletions

263
app.py Executable file
View File

@ -0,0 +1,263 @@
#!/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())

326
ui/main_window.ui Normal file
View File

@ -0,0 +1,326 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>800</width>
<height>600</height>
</rect>
</property>
<property name="windowTitle">
<string>MainWindow</string>
</property>
<widget class="QWidget" name="centralwidget">
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QGridLayout" name="gridLayout_6">
<item row="0" column="0">
<widget class="QLabel" name="current_track">
<property name="font">
<font>
<family>Sans</family>
<pointsize>20</pointsize>
</font>
</property>
<property name="styleSheet">
<string notr="true">background-color: rgb(138, 226, 52);
border: 1px solid rgb(85, 87, 83);</string>
</property>
<property name="text">
<string>After the goldrush - Neil Young [3:46]</string>
</property>
</widget>
</item>
<item row="1" column="0">
<layout class="QGridLayout" name="gridLayout_5">
<property name="horizontalSpacing">
<number>10</number>
</property>
<item row="0" column="0">
<widget class="QGroupBox" name="start_box">
<property name="title">
<string>Elapsed</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<widget class="QLabel" name="label_elapsed_timer">
<property name="font">
<font>
<family>Sans</family>
<pointsize>40</pointsize>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>2:46</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_start_tod">
<property name="font">
<font>
<family>DejaVu Sans</family>
<pointsize>16</pointsize>
</font>
</property>
<property name="text">
<string>10:17:37</string>
</property>
<property name="scaledContents">
<bool>false</bool>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="0" column="1">
<widget class="QGroupBox" name="fade_box">
<property name="title">
<string>Fade at</string>
</property>
<layout class="QGridLayout" name="gridLayout_2">
<item row="0" column="0">
<widget class="QLabel" name="label_fade_timer">
<property name="font">
<font>
<family>Sans</family>
<pointsize>40</pointsize>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>0:53</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_fade_tod">
<property name="font">
<font>
<family>DejaVu Sans</family>
<pointsize>16</pointsize>
</font>
</property>
<property name="text">
<string>10:21:23</string>
</property>
<property name="scaledContents">
<bool>false</bool>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="0" column="2">
<widget class="QGroupBox" name="silent_box">
<property name="styleSheet">
<string notr="true">background-color: rgb(252, 233, 79);</string>
</property>
<property name="title">
<string>Silent at</string>
</property>
<layout class="QGridLayout" name="gridLayout_3">
<item row="0" column="0">
<widget class="QLabel" name="label_silent_timer">
<property name="font">
<font>
<family>Sans</family>
<pointsize>40</pointsize>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>0:58</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_silent_tod">
<property name="font">
<font>
<family>DejaVu Sans</family>
<pointsize>16</pointsize>
</font>
</property>
<property name="text">
<string>10:21:28</string>
</property>
<property name="scaledContents">
<bool>false</bool>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="0" column="3">
<widget class="QGroupBox" name="end_box">
<property name="title">
<string>End at</string>
</property>
<layout class="QGridLayout" name="gridLayout_4">
<item row="0" column="0">
<widget class="QLabel" name="label_end_timer">
<property name="font">
<font>
<family>Sans</family>
<pointsize>40</pointsize>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>1:00</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_end_tod">
<property name="font">
<font>
<family>DejaVu Sans</family>
<pointsize>16</pointsize>
</font>
</property>
<property name="text">
<string>10:21:30</string>
</property>
<property name="scaledContents">
<bool>false</bool>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</item>
<item row="2" column="0">
<widget class="QTableWidget" name="playlist">
<property name="alternatingRowColors">
<bool>true</bool>
</property>
<property name="selectionMode">
<enum>QAbstractItemView::ContiguousSelection</enum>
</property>
<property name="selectionBehavior">
<enum>QAbstractItemView::SelectRows</enum>
</property>
<property name="verticalScrollMode">
<enum>QAbstractItemView::ScrollPerPixel</enum>
</property>
<property name="rowCount">
<number>3</number>
</property>
<property name="columnCount">
<number>7</number>
</property>
<row/>
<row/>
<row/>
<column>
<property name="text">
<string>Skip silence</string>
</property>
</column>
<column>
<property name="text">
<string>Title</string>
</property>
</column>
<column>
<property name="text">
<string>Artist</string>
</property>
</column>
<column>
<property name="text">
<string>Duration</string>
</property>
</column>
<column>
<property name="text">
<string>End time</string>
</property>
</column>
<column>
<property name="text">
<string>Autoplay next</string>
</property>
</column>
<column>
<property name="text">
<string>Path</string>
</property>
</column>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QPushButton" name="pushButton">
<property name="text">
<string>PushButton</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="current_time">
<property name="font">
<font>
<family>DejaVu Sans</family>
<pointsize>25</pointsize>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>10:20:30</string>
</property>
</widget>
</item>
</layout>
<zorder>current_track</zorder>
<zorder>start_box</zorder>
<zorder>fade_box</zorder>
<zorder>silent_box</zorder>
<zorder>end_box</zorder>
<zorder>playlist</zorder>
<zorder>current_time</zorder>
<zorder>pushButton</zorder>
<zorder></zorder>
</widget>
<widget class="QMenuBar" name="menubar">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>800</width>
<height>18</height>
</rect>
</property>
<widget class="QMenu" name="menuFile">
<property name="title">
<string>Fi&amp;le</string>
</property>
</widget>
<addaction name="menuFile"/>
</widget>
<widget class="QStatusBar" name="statusbar">
<property name="styleSheet">
<string notr="true">background-color: rgb(211, 215, 207);</string>
</property>
</widget>
</widget>
<resources/>
<connections/>
</ui>