musicmuster/app/musicmuster.py

308 lines
9.4 KiB
Python
Executable File

#!/usr/bin/python3
import os
import vlc
import sys
from datetime import datetime
from log import DEBUG, ERROR
from PyQt5.QtCore import Qt, QTimer
from PyQt5.QtWidgets import QApplication, QDialog, QMainWindow
from PyQt5.QtWidgets import QTableWidgetItem, QFileDialog, QListWidgetItem
from ui.main_window_ui import Ui_MainWindow
from ui.dlg_search_database_ui import Ui_Dialog
from config import Config
from model import Settings, Tracks
class Music:
def __init__(self):
self.current_track = {
"player": None,
"meta": None
}
self.next_track = {
"player": None,
"meta": None
}
self.previous_track = {
"player": None,
"meta": None
}
def get_current_artist(self):
return self.current_track['meta'].artist
def get_current_duration(self):
return self.current_track['meta'].duration
def get_current_fade_at(self):
return self.current_track['meta'].fade_at
def get_current_playtime(self):
return self.current_track['player'].get_time()
def get_current_silence_at(self):
return self.current_track['meta'].silence_at
def get_current_title(self):
return self.current_track['meta'].title
def get_last_artist(self):
return self.last_track['meta'].artist
def get_last_title(self):
return self.last_track['meta'].title
def get_next_artist(self):
return self.next_track['meta'].artist
def get_next_title(self):
return self.next_track['meta'].title
def play_next(self):
if self.previous_track['player']:
self.previous_track['player'].release()
if self.current_track['player']:
self.current_track['player'].stop()
self.previous_track = self.current_track
self.current_track = self.next_track
self.next_track = {
"player": None,
"meta": None
}
self.current_track['player'].play()
def playing(self):
if self.current_track['player']:
return self.current_track['player'].is_playing()
else:
return False
def resume_last(self):
pass
def set_next_track(self, id):
track = Tracks.get_track(id)
if not track:
ERROR(f"set_next_track({id}): can't find track")
return None
self.next_track['player'] = vlc.MediaPlayer(
os.path.join(Config.ROOT, track.path))
self.next_track['meta'] = track
return track.id
class Window(QMainWindow, Ui_MainWindow):
def __init__(self, parent=None):
super().__init__(parent)
self.setupUi(self)
self.timer = QTimer()
self.connectSignalsSlots()
self.music = Music()
record = Settings.get_int("mainwindow_x")
x = record.f_int or 1
record = Settings.get_int("mainwindow_y")
y = record.f_int or 1
record = Settings.get_int("mainwindow_width")
width = record.f_int or 1599
record = Settings.get_int("mainwindow_height")
height = record.f_int or 981
self.setGeometry(x, y, width, height)
for column in range(self.playlist.columnCount()):
name = f"playlist_col_{str(column)}_width"
record = Settings.get_int(name)
if record.f_int is not None:
self.playlist.setColumnWidth(column, record.f_int)
self.timer.start(Config.TIMER_MS)
def __del__(self):
for column in range(self.playlist.columnCount()):
name = f"playlist_col_{str(column)}_width"
record = Settings.get_int(name)
if record.f_int != self.playlist.columnWidth(column):
record.update({'f_int': self.playlist.columnWidth(column)})
record = Settings.get_int("mainwindow_height")
if record.f_int != self.height():
record.update({'f_int': self.height()})
record = Settings.get_int("mainwindow_width")
if record.f_int != self.width():
record.update({'f_int': self.width()})
record = Settings.get_int("mainwindow_x")
if record.f_int != self.x():
record.update({'f_int': self.x()})
record = Settings.get_int("mainwindow_y")
if record.f_int != self.y():
record.update({'f_int': self.y()})
def connectSignalsSlots(self):
self.fileButton.clicked.connect(self.selectFile)
self.databaseButton.clicked.connect(self.selectFromDatabase)
self.actionPlay_selected.triggered.connect(self.play_next)
self.actionPlay_next.triggered.connect(self.play_next)
self.playlist.itemSelectionChanged.connect(self.set_next)
self.timer.timeout.connect(self.tick)
def selectFromDatabase(self):
dlg = DbDialog(self)
dlg.exec()
def play_next(self):
self.music.play_next()
def play_selected(self):
if self.playlist.selectionModel().hasSelection():
row = self.playlist.currentRow()
track_id = int(self.playlist.item(row, 0).text())
DEBUG(f"play_selected: track_id={track_id}")
# TODO: get_path may raise exception
track_path = Tracks.get_path(track_id)
if track_path:
DEBUG(f"play_selected: track_path={track_path}")
player = vlc.MediaPlayer(track_path)
player.play()
def selectFile(self):
dlg = QFileDialog()
dlg.setFileMode(QFileDialog.ExistingFile)
dlg.setViewMode(QFileDialog.Detail)
dlg.setDirectory(Config.ROOT)
dlg.setNameFilter("Music files (*.flac *.mp3)")
if dlg.exec_():
pass
# TODO Add to database
# for fname in dlg.selectedFiles():
# track = Track(fname)
# self.add_to_playlist(track)
def set_next(self):
if self.playlist.selectionModel().hasSelection():
row = self.playlist.currentRow()
track_id = int(self.playlist.item(row, 0).text())
DEBUG(f"set_next: track_id={track_id}")
if self.music.set_next_track(track_id) != track_id:
ERROR("Can't set next track")
def tick(self):
now = datetime.now()
self.current_time.setText(now.strftime("%H:%M:%S"))
if self.music.playing():
playtime = self.music.get_current_playtime()
self.label_elapsed_timer.setText(ms_to_mmss(playtime))
self.label_fade_timer.setText(
ms_to_mmss(self.music.get_current_fade_at() - playtime))
self.label_silent_timer.setText(
ms_to_mmss(self.music.get_current_silence_at() - playtime))
self.label_end_timer.setText(
ms_to_mmss(self.music.get_current_duration() - playtime))
def add_to_playlist(self, track):
"""
Add track to playlist
track is an instance of Track
"""
DEBUG(f"add_to_playlist: track.id={track.id}")
pl = self.playlist
row = pl.rowCount()
pl.insertRow(row)
item = QTableWidgetItem(str(track.id))
pl.setItem(row, 0, item)
item = QTableWidgetItem(str(track.start_gap))
pl.setItem(row, 1, item)
item = QTableWidgetItem(track.title)
pl.setItem(row, 2, item)
item = QTableWidgetItem(track.artist)
pl.setItem(row, 3, item)
item = QTableWidgetItem(ms_to_mmss(track.duration))
pl.setItem(row, 4, item)
item = QTableWidgetItem(track.path)
pl.setItem(row, 7, item)
class DbDialog(QDialog):
def __init__(self, parent=None):
super().__init__(parent)
self.ui = Ui_Dialog()
self.ui.setupUi(self)
self.ui.searchString.textEdited.connect(self.chars_typed)
self.ui.matchList.itemDoubleClicked.connect(self.listdclick)
record = Settings.get_int("dbdialog_width")
width = record.f_int or 800
record = Settings.get_int("dbdialog_height")
height = record.f_int or 600
self.resize(width, height)
def __del__(self):
record = Settings.get_int("dbdialog_height")
if record.f_int != self.height():
record.update({'f_int': self.height()})
record = Settings.get_int("dbdialog_width")
if record.f_int != self.width():
record.update({'f_int': self.width()})
def chars_typed(self, s):
if len(s) >= 3:
matches = Tracks.search_titles(s)
self.ui.matchList.clear()
if matches:
for track in matches:
t = QListWidgetItem()
t.setText(
f"{track.title} - {track.artist} "
f"[{ms_to_mmss(track.duration)}]"
)
t.setData(Qt.UserRole, track.id)
self.ui.matchList.addItem(t)
def listdclick(self, entry):
track_id = entry.data(Qt.UserRole)
track = Tracks.track_from_id(track_id)
self.parent().add_to_playlist(track)
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
return f"{sign}{minutes:.0f}:{seconds:02.{decimals}f}"
def main():
app = QApplication(sys.argv)
win = Window()
win.show()
sys.exit(app.exec())
if __name__ == "__main__":
main()