Compare commits
39 commits
Author | SHA1 | Date | |
---|---|---|---|
fa323a0a87 | |||
a23799b6b1 | |||
4c0883f694 | |||
851c2306b4 | |||
567afb1866 | |||
6e7948e579 | |||
63847f7b42 | |||
a2e572cf6e | |||
39bd7e3167 | |||
ccda6b30c8 | |||
f2f3937fb2 | |||
301896e12c | |||
6786a3dcd8 | |||
a81ea15afd | |||
65564deb82 | |||
5dc91f6605 | |||
730e070dfc | |||
db191cbc44 | |||
d8f885959b | |||
f377263a0a | |||
e5b7ebe6e8 | |||
3ac97755bf | |||
0c2c91389d | |||
0879575882 | |||
22ffd211df | |||
6134c21ce4 | |||
cf1b4bacd1 | |||
c164201a55 | |||
efe10e7d50 | |||
d36326c029 | |||
bedca22ca6 | |||
67d353dcef | |||
6eac6468a0 | |||
c55c1222f0 | |||
67c3b9e226 | |||
95d40dd30c | |||
563aab6204 | |||
83deb903c1 | |||
2b8969d929 |
25 changed files with 488 additions and 121 deletions
26
README.md
26
README.md
|
@ -5,21 +5,31 @@ Currently, it just has really basic features but many more things are planned.
|
|||
|
||||
### Features
|
||||
|
||||
| Feature | Description | State |
|
||||
|---------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------|
|
||||
| Playlists | You can create and load `.m3u` playlists, edit them and they will get stored on the disk automatically. | <input type="checkbox" disabled checked /> Implemented |
|
||||
| Audio effects | Audio effects like normalizing and an equalizer. This can be implemented pretty easily because Wobuzz uses [Pydub](https://pydub.com/), which has these effects built in. | <input type="checkbox" disabled /> Not Implemented |
|
||||
| Feature | Description | State |
|
||||
|---------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------|
|
||||
| Playlists | You can create and load `.m3u` playlists, edit them and they will get stored on the disk automatically. | <input type="checkbox" disabled checked /> Implemented |
|
||||
| Background Job Monitor | A QDockWidget where background processes are listed. | <input type="checkbox" disabled checked /> Implemented |
|
||||
| Audio effects | Audio effects like normalizing and an equalizer. This can be implemented pretty easily because Wobuzz uses [Pydub](https://pydub.com/), which has these effects built in. | <input type="checkbox" disabled /> Not Implemented |
|
||||
| Soundcloud downloader | A simple Soundcloud-downloader like maybe integrating [SCDL](https://pypi.org/project/scdl/) would be really cool. | <input type="checkbox" disabled /> Not Implemented |
|
||||
| Synchronisation between devices | This should be pretty hard to implement and idk. if i will ever make it, but synchronisation could be pretty practical e.g. if you have multiple audio systems in different rooms. | <input type="checkbox" disabled /> Not Implemented |
|
||||
| Audio visualization | Firstly, rather simple audio visualization like an oscilloscope would be cool, also something more complicated like [ProjectM](https://github.com/projectM-visualizer/projectm) could be integrated. | <input type="checkbox" disabled /> Not Implemented |
|
||||
|
||||
## Installation
|
||||
|
||||
To install Wobuzz, you firstly have to install the dependencies that can't be installed using pip.
|
||||
This can be done using:
|
||||
### Release installation
|
||||
|
||||
Look at the [Releases](https://teapot.informationsanarchistik.de/Wobbl/Wobuzz/releases),
|
||||
there you can find the commands that you need for the installation.
|
||||
|
||||
### Unstable git installation
|
||||
|
||||
You firstly have to install the newest dependencies:
|
||||
|
||||
``` bash
|
||||
sudo apt install xcb libxcb-cursor0 ffmpeg
|
||||
sudo apt install xcb libxcb-cursor0 ffmpeg python3-pip
|
||||
```
|
||||
|
||||
Now, you can install Wobuzz using just one more command:
|
||||
Now, you can install the newest unstable version using just one more command:
|
||||
|
||||
```bash
|
||||
pip install wobuzz@git+https://teapot.informationsanarchistik.de/Wobbl/Wobuzz.git#egg=wobuzz
|
||||
|
|
3
setup.py
3
setup.py
|
@ -19,13 +19,14 @@ long_description = (this_directory / "README.md").read_text()
|
|||
|
||||
setuptools.setup(
|
||||
name="Wobuzz",
|
||||
version="0.1a1",
|
||||
version="0.1a2",
|
||||
description="An audio player made by The Wobbler",
|
||||
long_description=long_description,
|
||||
long_description_content_type="text/markdown",
|
||||
url="https://teapot.informationsanarchistik.de/Wobbl/Wobuzz",
|
||||
author="The Wobbler",
|
||||
author_email="emil@i21k.de",
|
||||
license="GNU GPLv3",
|
||||
packages=setuptools.find_packages(include=["wobuzz", "wobuzz.*"]),
|
||||
package_data={"": ["*.txt", "*.svg"]},
|
||||
install_requires=[
|
||||
|
|
|
@ -4,6 +4,8 @@ import os
|
|||
import sys
|
||||
import argparse
|
||||
|
||||
from wobuzz.player.playlist import Playlist
|
||||
|
||||
|
||||
def main():
|
||||
description = "A music player made by The Wobbler."
|
||||
|
@ -20,23 +22,24 @@ def main():
|
|||
app = Wobuzz()
|
||||
|
||||
if arguments.playlist:
|
||||
app.library.temporary_playlist.clear()
|
||||
app.library.temporary_playlist.view.clear()
|
||||
app.library.temporary_playlist.load_from_m3u(arguments.playlist)
|
||||
app.library.temporary_playlist.view.load_tracks()
|
||||
playlist = Playlist(app, "Temporary Playlist", arguments.playlist)
|
||||
|
||||
app.library.playlists.append(playlist)
|
||||
|
||||
if app.library.temporary_playlist in app.library.playlists:
|
||||
app.library.playlists.remove(app.library.temporary_playlist)
|
||||
app.library.temporary_playlist = playlist
|
||||
|
||||
if arguments.track:
|
||||
app.library.temporary_playlist.clear()
|
||||
app.library.temporary_playlist.view.clear()
|
||||
playlist = Playlist(app, "Temporary Playlist", arguments.track)
|
||||
|
||||
# make track paths absolute
|
||||
tracks = []
|
||||
app.library.playlists.append(playlist)
|
||||
|
||||
for track in arguments.track:
|
||||
tracks.append(os.path.abspath(track))
|
||||
if app.library.temporary_playlist in app.library.playlists:
|
||||
app.library.playlists.remove(app.library.temporary_playlist)
|
||||
app.library.temporary_playlist = playlist
|
||||
|
||||
app.library.temporary_playlist.load_from_paths(tracks)
|
||||
app.library.temporary_playlist.view.load_tracks()
|
||||
app.library.load_playlist_views()
|
||||
|
||||
sys.exit(app.qt_app.exec())
|
||||
|
||||
|
|
|
@ -11,13 +11,13 @@ class GUI:
|
|||
|
||||
self.dropped = []
|
||||
|
||||
self.clicked_playlist = self.app.library.temporary_playlist
|
||||
|
||||
self.window = MainWindow(app)
|
||||
self.settings = self.window.settings
|
||||
self.track_control = self.window.track_control
|
||||
self.process_dock = self.window.process_dock
|
||||
self.track_info = self.window.track_info
|
||||
|
||||
self.window.addDockWidget(Qt.DockWidgetArea.RightDockWidgetArea, self.app.library.main_library_dock)
|
||||
self.window.addDockWidget(Qt.DockWidgetArea.LeftDockWidgetArea, self.app.library.main_library_dock)
|
||||
|
||||
self.app.library.main_library_dock.setFeatures(
|
||||
QDockWidget.DockWidgetFeature.DockWidgetMovable |
|
||||
|
@ -52,5 +52,18 @@ class GUI:
|
|||
|
||||
def on_track_change(self, previous_track, track):
|
||||
self.track_control.on_track_change(previous_track, track)
|
||||
self.app.player.current_playlist.view.on_track_change(previous_track, track)
|
||||
|
||||
for dock_id in self.app.player.current_playlist.views:
|
||||
view = self.app.player.current_playlist.views[dock_id]
|
||||
view.on_track_change(previous_track, track)
|
||||
|
||||
def on_background_job_start(self, job_name: str, description: str, steps: int=0, getter: any=None):
|
||||
self.process_dock.job_started_signal.emit(job_name, description, steps, getter)
|
||||
|
||||
def on_background_job_stop(self, job_name: str):
|
||||
self.process_dock.job_finished_signal.emit(job_name)
|
||||
|
||||
def on_playstate_update(self):
|
||||
self.track_control.on_playstate_update()
|
||||
self.track_info.update_info()
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
#!/usr/bin/python3
|
||||
|
||||
import os
|
||||
from PyQt6.QtWidgets import QTabWidget
|
||||
from PyQt6.QtWidgets import QTabWidget, QAbstractItemView
|
||||
from ..player.playlist import Playlist
|
||||
from ..ui.library_dock import LibraryDock
|
||||
from ..ui.playlist import PlaylistView
|
||||
|
@ -18,10 +18,13 @@ class Library:
|
|||
self.main_library_dock = LibraryDock(self)
|
||||
self.library_docks = [self.main_library_dock]
|
||||
|
||||
self.temporary_playlist = Playlist(self.app, "Temporary Playlist")
|
||||
self.playlists = [self.temporary_playlist]
|
||||
self.playlists = []
|
||||
self.temporary_playlist = None
|
||||
|
||||
def load(self):
|
||||
self.load_playlists()
|
||||
|
||||
def load_playlists(self):
|
||||
path_playlists = os.path.expanduser(f"{self.app.settings.library_path}/playlists")
|
||||
|
||||
if not os.path.exists(path_playlists):
|
||||
|
@ -37,16 +40,11 @@ class Library:
|
|||
if file_name.endswith(".m3u"):
|
||||
path = f"{path_playlists}/{file_name}"
|
||||
|
||||
if file_name == "Temporary_Playlist.wbz.m3u":
|
||||
playlist = self.temporary_playlist
|
||||
playlist = Playlist(self.app, file_name.replace("_", " ").split(".")[0])
|
||||
self.playlists.append(playlist)
|
||||
|
||||
else:
|
||||
playlist = Playlist(self.app, file_name.replace("_", " ").split(".")[0])
|
||||
self.playlists.append(playlist)
|
||||
|
||||
playlist.load_from_m3u(path)
|
||||
|
||||
self.load_playlist_views()
|
||||
if playlist.title == "Temporary Playlist":
|
||||
self.temporary_playlist = playlist
|
||||
|
||||
def load_playlist_views(self):
|
||||
for library_dock in self.library_docks:
|
||||
|
@ -55,20 +53,35 @@ class Library:
|
|||
playlist_tabs.playlists = {}
|
||||
|
||||
for playlist in self.playlists:
|
||||
playlist_view = PlaylistView(playlist)
|
||||
playlist_view = PlaylistView(playlist, library_dock)
|
||||
playlist_tabs.addTab(playlist_view, playlist.title)
|
||||
|
||||
if playlist.path == self.app.settings.latest_playlist: # start with latest playlist opened
|
||||
playlist_tabs.setCurrentIndex(playlist_tabs.count() - 1)
|
||||
|
||||
if self.app.settings.load_on_start:
|
||||
for playlist in self.playlists:
|
||||
playlist.load()
|
||||
|
||||
def on_exit(self, event):
|
||||
for playlist in self.playlists:
|
||||
playlist.save()
|
||||
if playlist.loaded: # only save loaded playlists, unloaded are empty
|
||||
playlist.save()
|
||||
|
||||
if self.app.player.current_playlist is not None:
|
||||
self.app.settings.latest_playlist = self.app.player.current_playlist.path
|
||||
|
||||
def new_playlist(self):
|
||||
playlist = Playlist(self.app, self.app.utils.unique_name("New Playlist"))
|
||||
playlist.loaded = True
|
||||
|
||||
self.playlists.append(playlist)
|
||||
|
||||
for library_dock in self.library_docks:
|
||||
playlist_tabs: QTabWidget = library_dock.library_widget.playlist_tabs
|
||||
|
||||
playlist_view = PlaylistView(playlist)
|
||||
playlist_view = PlaylistView(playlist, library_dock)
|
||||
playlist_view.setDragDropMode(QAbstractItemView.DragDropMode.InternalMove) # enable drag n drop
|
||||
|
||||
playlist_tabs.addTab(playlist_view, playlist.title)
|
||||
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
#!/usr/bin/python3
|
||||
|
||||
import os
|
||||
import sys
|
||||
from PyQt6.QtWidgets import QApplication
|
||||
from wobbl_tools.data_file import load_dataclass_json
|
||||
|
@ -20,14 +19,14 @@ class Wobuzz:
|
|||
self.settings = load_dataclass_json(Settings, self.utils.settings_location, self, True, True)
|
||||
self.settings.set_attribute_change_event(self.on_settings_change)
|
||||
|
||||
self.player = Player(self)
|
||||
self.library = Library(self)
|
||||
self.player = Player(self)
|
||||
self.gui = GUI(self)
|
||||
|
||||
self.post_init()
|
||||
self.late_init()
|
||||
|
||||
def post_init(self):
|
||||
self.gui.track_control.track_progress_slider.post_init()
|
||||
def late_init(self):
|
||||
self.gui.track_control.track_progress_slider.late_init()
|
||||
self.library.load()
|
||||
|
||||
def on_settings_change(self, key, value):
|
||||
|
@ -38,4 +37,7 @@ class Wobuzz:
|
|||
|
||||
if __name__ == "__main__":
|
||||
wobuzz = Wobuzz()
|
||||
wobuzz.post_init()
|
||||
wobuzz.library.load_playlist_views()
|
||||
|
||||
sys.exit(wobuzz.qt_app.exec())
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
#!/usr/bin/python3
|
||||
|
||||
import time
|
||||
import threading
|
||||
import pygame.mixer
|
||||
import pygame.event
|
||||
|
@ -18,7 +19,7 @@ class Player:
|
|||
self.track_progress = TrackProgress(self.app)
|
||||
|
||||
self.history = Playlist(self.app, "History")
|
||||
self.current_playlist = Playlist(self.app, "None")
|
||||
self.current_playlist = None
|
||||
|
||||
self.playing = False
|
||||
self.paused = False
|
||||
|
@ -32,7 +33,7 @@ class Player:
|
|||
self.playing = True
|
||||
self.paused = False
|
||||
|
||||
self.app.gui.track_control.on_playstate_update()
|
||||
self.app.gui.on_playstate_update()
|
||||
|
||||
# cache next track so it immediately starts when the current track finishes
|
||||
self.cache_next_track()
|
||||
|
@ -67,7 +68,7 @@ class Player:
|
|||
|
||||
self.app.gui.on_track_change(self.history.h_last_track(), self.current_playlist.current_track)
|
||||
|
||||
self.app.gui.track_control.on_playstate_update()
|
||||
self.app.gui.on_playstate_update()
|
||||
|
||||
def play_track_in_playlist(self, track_index):
|
||||
self.stop()
|
||||
|
@ -98,7 +99,7 @@ class Player:
|
|||
self.track_progress.pause()
|
||||
self.paused = True
|
||||
|
||||
self.app.gui.track_control.on_playstate_update()
|
||||
self.app.gui.on_playstate_update()
|
||||
|
||||
def unpause(self):
|
||||
self.music_channel.unpause()
|
||||
|
@ -107,7 +108,7 @@ class Player:
|
|||
self.playing = True
|
||||
self.paused = False
|
||||
|
||||
self.app.gui.track_control.on_playstate_update()
|
||||
self.app.gui.on_playstate_update()
|
||||
|
||||
def next_track(self):
|
||||
if not self.current_playlist.on_last_track():
|
||||
|
@ -134,13 +135,13 @@ class Player:
|
|||
self.music_channel.stop()
|
||||
self.track_progress.stop()
|
||||
|
||||
if not self.current_playlist.current_track is None:
|
||||
if self.current_playlist is not None and self.current_playlist.current_track is not None:
|
||||
self.current_sound_duration = self.current_playlist.current_track.duration
|
||||
|
||||
self.playing = False
|
||||
self.paused = False
|
||||
|
||||
self.app.gui.track_control.on_playstate_update()
|
||||
self.app.gui.on_playstate_update()
|
||||
|
||||
def seek(self, position: int):
|
||||
self.music_channel.stop()
|
||||
|
@ -160,8 +161,15 @@ class Player:
|
|||
track = self.current_playlist.tracks[self.current_playlist.current_track_index + 1]
|
||||
|
||||
if not track.cached:
|
||||
self.app.gui.on_background_job_start(
|
||||
"Loading Track",
|
||||
"Loading next track in the background so it starts immediately."
|
||||
)
|
||||
|
||||
track.cache()
|
||||
|
||||
self.app.gui.on_background_job_stop("Loading Track")
|
||||
|
||||
def cache_next_track(self):
|
||||
# function that creates a thread which will cache the next track
|
||||
caching_thread = threading.Thread(target=self.caching_thread_function)
|
||||
|
@ -170,6 +178,15 @@ class Player:
|
|||
def start_playlist(self, playlist):
|
||||
self.stop()
|
||||
|
||||
if not playlist.loaded:
|
||||
playlist.load()
|
||||
|
||||
while not playlist.has_tracks() and not playlist.loaded: # wait until first track is loaded
|
||||
time.sleep(0.1)
|
||||
|
||||
if not playlist.has_tracks():
|
||||
return
|
||||
|
||||
self.current_sound, self.current_sound_duration = playlist.set_track(0) # first track
|
||||
self.current_playlist = playlist
|
||||
|
||||
|
|
|
@ -1,15 +1,23 @@
|
|||
#!/usr/bin/python3
|
||||
|
||||
import os
|
||||
import threading
|
||||
|
||||
from PyQt6.QtCore import Qt
|
||||
from PyQt6.QtWidgets import QAbstractItemView
|
||||
from .track import Track
|
||||
|
||||
|
||||
class Playlist:
|
||||
def __init__(self, app, title: str):
|
||||
def __init__(self, app, title: str, load_from=None):
|
||||
self.app = app
|
||||
self.title = title # playlist title
|
||||
|
||||
# if the playlist is imported and not already in the library, this variable will contain the playlist path or
|
||||
# track path from which the playlist will get imported
|
||||
# if None, playlist should be already in the library and will be loaded from a .wbz.m3u
|
||||
self.load_from = load_from
|
||||
|
||||
# add to unique names so if the playlist is loaded from disk,
|
||||
# no other playlist can be created using the same name
|
||||
self.app.utils.unique_names.append(self.title)
|
||||
|
@ -18,7 +26,8 @@ class Playlist:
|
|||
self.tracks: list[Track] = []
|
||||
self.current_track_index = 0
|
||||
self.current_track: Track | None = None
|
||||
self.view = None
|
||||
self.views = {} # dict of id(LibraryDock): PlaylistView
|
||||
self.loaded = False
|
||||
|
||||
self.path = os.path.expanduser(
|
||||
f"{app.settings.library_path}/playlists/{self.title.replace(" ", "_")}.wbz.m3u"
|
||||
|
@ -31,19 +40,49 @@ class Playlist:
|
|||
self.current_track = None
|
||||
|
||||
def load_from_paths(self, paths):
|
||||
num_tracks = len(paths)
|
||||
|
||||
i = 0
|
||||
|
||||
while i < len(paths):
|
||||
process_title = f'Loading Playlist "{self.title}"'
|
||||
|
||||
self.app.gui.on_background_job_start(
|
||||
process_title,
|
||||
f'Loading the tracks of "{self.title}".',
|
||||
num_tracks,
|
||||
lambda: i
|
||||
)
|
||||
|
||||
while i < num_tracks:
|
||||
path = paths[i]
|
||||
|
||||
if os.path.isfile(path):
|
||||
self.tracks.append(Track(self.app, path, cache=i==0)) # first track is cached
|
||||
self.append_track(Track(self.app, path, cache=i==0)) # first track is cached
|
||||
|
||||
i += 1
|
||||
|
||||
# set current track to the first track if there is no currently playing track
|
||||
if self.current_track is None and self.has_tracks():
|
||||
self.current_track = self.tracks[0]
|
||||
self.loaded = True
|
||||
|
||||
self.app.gui.on_background_job_stop(process_title)
|
||||
|
||||
def load(self):
|
||||
loading_thread = threading.Thread(target=self.loading_thread)
|
||||
loading_thread.start()
|
||||
|
||||
def loading_thread(self):
|
||||
if self.load_from is None: # if the playlist is in the library
|
||||
self.load_from_wbz(self.path)
|
||||
|
||||
elif isinstance(self.load_from, str): # if it's imported from a .m3u
|
||||
self.load_from_m3u(self.load_from)
|
||||
|
||||
elif isinstance(self.load_from, list): # if it's created from tracks
|
||||
self.load_from_paths(self.load_from)
|
||||
|
||||
for dock_id in self.views: # enable drag and drop on every view
|
||||
view = self.views[dock_id]
|
||||
|
||||
view.setDragDropMode(QAbstractItemView.DragDropMode.InternalMove)
|
||||
|
||||
def load_from_m3u(self, path):
|
||||
file = open(path, "r")
|
||||
|
@ -53,9 +92,19 @@ class Playlist:
|
|||
lines = m3u.split("\n") # m3u entries are separated by newlines
|
||||
lines = lines[:-1] # remove last entry because it is just an empty string
|
||||
|
||||
i = 0
|
||||
num_lines = len(lines)
|
||||
|
||||
i = 0
|
||||
|
||||
process_title = f'Loading Playlist "{self.title}"'
|
||||
|
||||
self.app.gui.on_background_job_start(
|
||||
process_title,
|
||||
f'Loading the tracks of "{self.title}".',
|
||||
num_lines,
|
||||
lambda: i
|
||||
)
|
||||
|
||||
while i < num_lines:
|
||||
line = lines[i]
|
||||
|
||||
|
@ -64,7 +113,7 @@ class Playlist:
|
|||
|
||||
continue
|
||||
|
||||
self.tracks.append(Track(self.app, line, cache=i==0)) # first track is cached
|
||||
self.append_track(Track(self.app, line, cache=i==0)) # first track is cached
|
||||
|
||||
i += 1
|
||||
|
||||
|
@ -72,10 +121,12 @@ class Playlist:
|
|||
if self.current_track is None and self.has_tracks():
|
||||
self.current_track = self.tracks[0]
|
||||
|
||||
#self.app.player.history.append_track(self.current_track)
|
||||
self.loaded = True
|
||||
|
||||
self.app.gui.on_background_job_stop(process_title)
|
||||
|
||||
def load_from_wbz(self, path):
|
||||
pass
|
||||
self.load_from_m3u(path) # placeholder
|
||||
|
||||
def has_tracks(self):
|
||||
return len(self.tracks) > 0
|
||||
|
@ -150,11 +201,16 @@ class Playlist:
|
|||
self.app.utils.unique_names.remove(self.title)
|
||||
self.app.library.playlists.remove(self)
|
||||
|
||||
def append_track(self, track):
|
||||
self.tracks.append(track)
|
||||
if self.app.player.current_playlist == self: # stop if this is the current playlist
|
||||
self.app.player.stop()
|
||||
self.app.player.current_playlist = None
|
||||
|
||||
if self.view:
|
||||
self.view.append_track(track)
|
||||
def append_track(self, track):
|
||||
for dock_id in self.views:
|
||||
view = self.views[dock_id]
|
||||
view.append_track(track)
|
||||
|
||||
self.tracks.append(track)
|
||||
|
||||
def h_last_track(self):
|
||||
# get last track in history (only gets used in player.history)
|
||||
|
|
|
@ -10,12 +10,11 @@ class Track:
|
|||
Class containing data for a track like file path, raw data...
|
||||
"""
|
||||
|
||||
def __init__(self, app, path: str, property_string: str=None, cache: bool=False):
|
||||
def __init__(self, app, path: str, cache: bool=False):
|
||||
self.app = app
|
||||
self.path = path
|
||||
self.property_string = property_string
|
||||
|
||||
self.tags = TinyTag.get(self.path, ignore_errors=False, duration=False)
|
||||
self.tags = TinyTag.get(self.path, ignore_errors=True, duration=False)
|
||||
|
||||
self.cached = False
|
||||
self.audio = None
|
||||
|
@ -34,6 +33,7 @@ class Track:
|
|||
new_occurrences = {}
|
||||
|
||||
for item in self.items:
|
||||
# create dict of item: item.index (actually the id of the item bc. the item can't be used as key)
|
||||
playlist_occurrences = new_occurrences.get(item.playlist, {})
|
||||
playlist_occurrences[id(item)] = item.index
|
||||
|
||||
|
@ -48,13 +48,14 @@ class Track:
|
|||
If this track is the currently playing track, and it gets moved, this corrects the current playlist index.
|
||||
"""
|
||||
|
||||
if self.app.player.current_playlist.current_track is self:
|
||||
for item in self.items:
|
||||
if (
|
||||
item.playlist in self.occurrences and
|
||||
self.occurrences[item.playlist][id(item)] == self.app.player.current_playlist.current_track_index
|
||||
):
|
||||
self.app.player.current_playlist.set_track(new_occurrences[item.playlist][id(item)])
|
||||
if self.app.player.current_playlist is not None:
|
||||
if self.app.player.current_playlist.current_track is self:
|
||||
for item in self.items:
|
||||
if (
|
||||
item.playlist in self.occurrences and
|
||||
self.occurrences[item.playlist][id(item)] == self.app.player.current_playlist.current_track_index
|
||||
):
|
||||
self.app.player.current_playlist.set_track(new_occurrences[item.playlist][id(item)])
|
||||
|
||||
def cache(self):
|
||||
self.load_audio()
|
||||
|
@ -66,6 +67,8 @@ class Track:
|
|||
|
||||
self.duration = len(self.audio) # track duration in milliseconds
|
||||
|
||||
self.tags = TinyTag.get(self.path, ignore_errors=True, duration=False, image=True) # metadata with images
|
||||
|
||||
self.cached = True
|
||||
|
||||
def clear_cache(self):
|
||||
|
@ -75,6 +78,8 @@ class Track:
|
|||
self.sound = None
|
||||
self.duration = 0
|
||||
|
||||
self.tags = TinyTag.get(self.path, ignore_errors=True, duration=False) # metadata without images
|
||||
|
||||
def load_audio(self):
|
||||
#file_type = self.path.split(".")[-1]
|
||||
|
||||
|
|
|
@ -30,5 +30,5 @@ class TrackProgress:
|
|||
def stop(self):
|
||||
self.timer.stop()
|
||||
|
||||
if not self.app.player.current_playlist.current_track is None:
|
||||
if self.app.player.current_playlist is not None and self.app.player.current_playlist.current_track is not None:
|
||||
self.remaining_time = self.app.player.current_playlist.current_track.duration
|
||||
|
|
|
@ -9,4 +9,6 @@ class Settings:
|
|||
window_maximized: bool=False
|
||||
library_path: str="~/.wobuzz"
|
||||
clear_track_cache: bool=True
|
||||
latest_playlist: str=None
|
||||
load_on_start: bool=True
|
||||
|
||||
|
|
|
@ -11,12 +11,6 @@ class LibraryDock(QDockWidget):
|
|||
|
||||
self.library = library
|
||||
|
||||
self.setAllowedAreas(
|
||||
Qt.DockWidgetArea.LeftDockWidgetArea |
|
||||
Qt.DockWidgetArea.RightDockWidgetArea |
|
||||
Qt.DockWidgetArea.BottomDockWidgetArea
|
||||
)
|
||||
|
||||
self.setAcceptDrops(True)
|
||||
|
||||
self.library_widget = Library(library, self)
|
||||
|
|
|
@ -5,6 +5,8 @@ from PyQt6.QtGui import QIcon
|
|||
from PyQt6.QtWidgets import QMainWindow, QMenu
|
||||
from .track_control import TrackControl
|
||||
from .settings import Settings
|
||||
from .process.process_dock import ProcessDock
|
||||
from .track_info import TrackInfo
|
||||
|
||||
|
||||
class MainWindow(QMainWindow):
|
||||
|
@ -28,6 +30,11 @@ class MainWindow(QMainWindow):
|
|||
|
||||
self.settings_action = self.edit_menu.addAction("&Settings")
|
||||
|
||||
self.view_menu = QMenu("&View", self.menu_bar)
|
||||
self.menu_bar.addMenu(self.view_menu)
|
||||
|
||||
self.processes_action = self.view_menu.addAction("Show &Background Processes")
|
||||
|
||||
self.track_control = TrackControl(app)
|
||||
self.addToolBar(self.track_control)
|
||||
|
||||
|
@ -35,5 +42,13 @@ class MainWindow(QMainWindow):
|
|||
self.settings.hide()
|
||||
self.addDockWidget(Qt.DockWidgetArea.LeftDockWidgetArea, self.settings)
|
||||
|
||||
self.settings_action.triggered.connect(self.settings.show)
|
||||
self.process_dock = ProcessDock(app)
|
||||
self.process_dock.hide()
|
||||
self.addDockWidget(Qt.DockWidgetArea.RightDockWidgetArea, self.process_dock)
|
||||
|
||||
self.track_info = TrackInfo(app)
|
||||
self.addToolBar(Qt.ToolBarArea.BottomToolBarArea, self.track_info)
|
||||
|
||||
self.settings_action.triggered.connect(self.settings.show)
|
||||
self.processes_action.triggered.connect(self.process_dock.show)
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
from PyQt6.QtCore import pyqtSignal
|
||||
from PyQt6.QtGui import QDropEvent, QIcon, QFont
|
||||
from PyQt6.QtWidgets import QTreeWidget, QAbstractItemView, QFrame
|
||||
from PyQt6.QtWidgets import QTreeWidget, QAbstractItemView
|
||||
|
||||
from .track import TrackItem
|
||||
|
||||
|
@ -10,25 +10,22 @@ from .track import TrackItem
|
|||
class PlaylistView(QTreeWidget):
|
||||
itemDropped = pyqtSignal(QTreeWidget, list)
|
||||
|
||||
def __init__(self, playlist, parent=None):
|
||||
playing_mark = QIcon.fromTheme(QIcon.ThemeIcon.MediaPlaybackStart)
|
||||
|
||||
def __init__(self, playlist, dock, parent=None):
|
||||
super().__init__(parent)
|
||||
|
||||
self.playlist = playlist
|
||||
self.library_dock = dock
|
||||
|
||||
self.app = playlist.app
|
||||
|
||||
playlist.view = self
|
||||
playlist.views[id(dock)] = self
|
||||
|
||||
self.normal_font = QFont()
|
||||
self.bold_font = QFont()
|
||||
self.bold_font.setBold(True)
|
||||
|
||||
self.setDragDropMode(QAbstractItemView.DragDropMode.InternalMove)
|
||||
self.setSelectionMode(QAbstractItemView.SelectionMode.ExtendedSelection)
|
||||
|
||||
self.setColumnCount(4)
|
||||
|
||||
self.playing_mark = QIcon.fromTheme(QIcon.ThemeIcon.MediaPlaybackStart)
|
||||
|
||||
headers = [
|
||||
"#",
|
||||
"Title",
|
||||
|
@ -39,8 +36,6 @@ class PlaylistView(QTreeWidget):
|
|||
|
||||
self.setHeaderLabels(headers)
|
||||
|
||||
self.load_tracks()
|
||||
|
||||
self.itemActivated.connect(self.on_track_activation)
|
||||
|
||||
def on_user_sort(self):
|
||||
|
@ -63,7 +58,8 @@ class PlaylistView(QTreeWidget):
|
|||
|
||||
i += 1
|
||||
|
||||
if self.app.player.current_playlist.has_tracks():
|
||||
# make sure the next track is cached (could be moved by user)
|
||||
if self.app.player.current_playlist == self.playlist and self.app.player.current_playlist.has_tracks():
|
||||
self.app.player.cache_next_track()
|
||||
|
||||
def dropEvent(self, event: QDropEvent):
|
||||
|
@ -133,20 +129,14 @@ class PlaylistView(QTreeWidget):
|
|||
|
||||
# unmark the previous track in all playlists
|
||||
for item in previous_track.items:
|
||||
item.setIcon(0, QIcon(None))
|
||||
item.setFont(1, self.normal_font)
|
||||
item.setFont(2, self.normal_font)
|
||||
item.setFont(3, self.normal_font)
|
||||
item.unmark()
|
||||
|
||||
if track:
|
||||
playlist_tabs.setTabIcon(index, self.playing_mark) # mark this playlist
|
||||
|
||||
# mark the current track in this playlist
|
||||
item = self.topLevelItem(self.app.player.current_playlist.current_track_index)
|
||||
item.setIcon(0, self.playing_mark)
|
||||
item.setFont(1, self.bold_font)
|
||||
item.setFont(2, self.bold_font)
|
||||
item.setFont(3, self.normal_font)
|
||||
item.mark()
|
||||
|
||||
def append_track(self, track):
|
||||
TrackItem(track, self.topLevelItemCount() - 1, self)
|
||||
|
|
|
@ -42,8 +42,7 @@ class PlaylistTabBar(QTabBar):
|
|||
|
||||
playlist = playlist_view.playlist
|
||||
|
||||
if playlist.has_tracks(): # dont crash when playlist is empty
|
||||
self.app.player.start_playlist(playlist)
|
||||
self.app.player.start_playlist(playlist)
|
||||
|
||||
def contextMenuEvent(self, event: QContextMenuEvent, title=None):
|
||||
# get title by self.tabAt() if the event is called from PyQt, else its executed from the tab title and getting
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
#!/usr/bin/python3
|
||||
|
||||
from PyQt6.QtCore import Qt
|
||||
from PyQt6.QtGui import QMouseEvent
|
||||
from PyQt6.QtGui import QMouseEvent, QCursor
|
||||
from PyQt6.QtWidgets import QLineEdit
|
||||
|
||||
from .tab_bar import PlaylistTabBar
|
||||
|
@ -16,10 +16,11 @@ class TabTitle(QLineEdit):
|
|||
self.index = index
|
||||
self.playlist_view = playlist_view
|
||||
|
||||
self.setStyleSheet("QLineEdit {background: transparent;}")
|
||||
|
||||
self.setStyleSheet("QLineEdit {background: transparent; border: none;}")
|
||||
self.setFocusPolicy(Qt.FocusPolicy.TabFocus)
|
||||
|
||||
self.setCursor(QCursor(Qt.CursorShape.ArrowCursor)) # normal cursor (would be a text cursor)
|
||||
|
||||
self.editingFinished.connect(self.on_edit)
|
||||
|
||||
def mouseDoubleClickEvent(self, event: QMouseEvent):
|
||||
|
|
1
wobuzz/ui/process/__init__.py
Normal file
1
wobuzz/ui/process/__init__.py
Normal file
|
@ -0,0 +1 @@
|
|||
#!/usr/bin/python3
|
41
wobuzz/ui/process/process.py
Normal file
41
wobuzz/ui/process/process.py
Normal file
|
@ -0,0 +1,41 @@
|
|||
#!/usr/bin/python3
|
||||
|
||||
from PyQt6.QtCore import Qt
|
||||
from PyQt6.QtGui import QFont
|
||||
from PyQt6.QtWidgets import QSizePolicy, QGroupBox, QLabel, QProgressBar, QVBoxLayout
|
||||
|
||||
|
||||
class BackgroundProcess(QGroupBox):
|
||||
normal_font = QFont()
|
||||
normal_font.setBold(False)
|
||||
bold_font = QFont()
|
||||
bold_font.setBold(True)
|
||||
|
||||
def __init__(self, title: str, parent=None, description: str="", steps: int=0):
|
||||
super().__init__(title, parent)
|
||||
|
||||
self.setSizePolicy(QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Fixed)
|
||||
self.setAlignment(Qt.AlignmentFlag.AlignLeading | Qt.AlignmentFlag.AlignVCenter)
|
||||
self.setFont(self.bold_font)
|
||||
|
||||
self.layout = QVBoxLayout(self)
|
||||
|
||||
self.description = QLabel(description, self)
|
||||
self.description.setFont(self.normal_font)
|
||||
self.layout.addWidget(self.description)
|
||||
|
||||
self.progress_bar = QProgressBar(self)
|
||||
self.progress_bar.setMaximum(steps)
|
||||
self.layout.addWidget(self.progress_bar)
|
||||
|
||||
self.setLayout(self.layout)
|
||||
|
||||
def get_progress(self):
|
||||
return 0
|
||||
|
||||
def set_range(self, maximum: int, minimum: int=0):
|
||||
self.progress_bar.setRange(minimum, maximum)
|
||||
|
||||
def update_progress(self):
|
||||
self.progress_bar.setValue(self.get_progress())
|
||||
|
75
wobuzz/ui/process/process_dock.py
Normal file
75
wobuzz/ui/process/process_dock.py
Normal file
|
@ -0,0 +1,75 @@
|
|||
#!/usr/bin/python3
|
||||
|
||||
from PyQt6.QtCore import QTimer, pyqtSignal
|
||||
from PyQt6.QtWidgets import QWidget, QDockWidget, QScrollArea, QVBoxLayout
|
||||
|
||||
from .process import BackgroundProcess
|
||||
|
||||
PROGRESS_UPDATE_RATE = 10
|
||||
PROGRESS_UPDATE_INTERVAL = 1000 // PROGRESS_UPDATE_RATE
|
||||
|
||||
|
||||
class ProcessDock(QDockWidget):
|
||||
# we need a signal for self.on_background_job_start() because PyQt6 doesn't allow some operations to be performed
|
||||
# from a different thread
|
||||
job_started_signal = pyqtSignal(str, str, int, object)
|
||||
job_finished_signal = pyqtSignal(str)
|
||||
|
||||
def __init__(self, app, parent=None):
|
||||
super().__init__(parent)
|
||||
|
||||
self.app = app
|
||||
|
||||
self.processes = {}
|
||||
|
||||
self.setWindowTitle("Background Processes")
|
||||
|
||||
self.scroll_area = QScrollArea(self)
|
||||
self.scroll_area.setWidgetResizable(True)
|
||||
|
||||
self.process_container = QWidget(self.scroll_area)
|
||||
|
||||
self.process_layout = QVBoxLayout(self.process_container)
|
||||
|
||||
# add expanding widget so the distance between processes will be equal
|
||||
self.process_layout.addWidget(QWidget(self.process_container))
|
||||
|
||||
self.process_container.setLayout(self.process_layout)
|
||||
|
||||
self.scroll_area.setWidget(self.process_container)
|
||||
|
||||
self.setWidget(self.scroll_area)
|
||||
|
||||
self.progress_update_timer = QTimer()
|
||||
self.progress_update_timer.timeout.connect(self.update_processes)
|
||||
self.progress_update_timer.start(PROGRESS_UPDATE_INTERVAL)
|
||||
|
||||
self.job_started_signal.connect(self.on_background_job_start)
|
||||
self.job_finished_signal.connect(self.on_background_job_stop)
|
||||
|
||||
def add_process(self, name: str, process: BackgroundProcess):
|
||||
if not name in self.processes:
|
||||
self.processes[name] = process
|
||||
self.process_layout.insertWidget(self.process_layout.count() - 1, process)
|
||||
|
||||
def update_processes(self):
|
||||
for process in self.processes.values():
|
||||
process.update_progress()
|
||||
|
||||
def on_background_job_start(self, job_title: str, description: str, steps: int, getter):
|
||||
process = BackgroundProcess(
|
||||
job_title,
|
||||
self.process_container,
|
||||
description,
|
||||
steps
|
||||
)
|
||||
|
||||
if getter is not None:
|
||||
process.get_progress = getter
|
||||
|
||||
self.add_process(job_title, process)
|
||||
|
||||
def on_background_job_stop(self, job):
|
||||
if job in self.processes:
|
||||
self.processes.pop(job).deleteLater()
|
||||
|
|
@ -10,5 +10,8 @@ class BehaviourSettings(QWidget):
|
|||
self.layout = QFormLayout(self)
|
||||
self.setLayout(self.layout)
|
||||
|
||||
self.load_on_start = QCheckBox(self)
|
||||
self.layout.addRow("Load playlists on start", self.load_on_start)
|
||||
|
||||
self.clear_track_cache = QCheckBox(self)
|
||||
self.layout.addRow("Clear track cache immediately when finished", self.clear_track_cache)
|
||||
self.layout.addRow("Clear track cache immediately when finished", self.clear_track_cache)
|
||||
|
|
|
@ -44,6 +44,7 @@ class Settings(QDockWidget):
|
|||
def update_all(self, _=True): # ignore visible parameter passed by visibilityChanged event
|
||||
self.file_settings.library_path_input.setText(self.app.settings.library_path)
|
||||
self.behavior_settings.clear_track_cache.setChecked(self.app.settings.clear_track_cache)
|
||||
self.behavior_settings.load_on_start.setChecked(self.app.settings.load_on_start)
|
||||
|
||||
def update_settings(self, key, value):
|
||||
match key:
|
||||
|
@ -53,7 +54,11 @@ class Settings(QDockWidget):
|
|||
case "clear_track_cache":
|
||||
self.behavior_settings.clear_track_cache.setDown(value)
|
||||
|
||||
case "load_on_start":
|
||||
self.behavior_settings.load_on_start.setChecked(value)
|
||||
|
||||
def write_settings(self):
|
||||
self.app.settings.library_path = self.file_settings.library_path_input.text()
|
||||
self.app.settings.clear_track_cache = self.behavior_settings.clear_track_cache.isChecked()
|
||||
self.app.settings.load_on_start = self.behavior_settings.load_on_start.isChecked()
|
||||
|
||||
|
|
|
@ -1,20 +1,31 @@
|
|||
#!/usr/bin/python3
|
||||
|
||||
from PyQt6.QtCore import Qt
|
||||
from PyQt6.QtGui import QFont, QIcon, QPalette
|
||||
from PyQt6.QtWidgets import QTreeWidgetItem
|
||||
|
||||
|
||||
class TrackItem(QTreeWidgetItem):
|
||||
normal_font = QFont()
|
||||
bold_font = QFont()
|
||||
bold_font.setBold(True)
|
||||
|
||||
playing_mark = QIcon.fromTheme(QIcon.ThemeIcon.MediaPlaybackStart)
|
||||
|
||||
def __init__(self, track, index, parent=None):
|
||||
super().__init__(parent)
|
||||
|
||||
self.track = track
|
||||
self.index_user_sort = index
|
||||
|
||||
self.index = index
|
||||
|
||||
self.playlist = parent.playlist
|
||||
|
||||
palette = parent.palette()
|
||||
|
||||
self.highlight_color = palette.color(QPalette.ColorRole.Highlight)
|
||||
self.base_color = palette.color(QPalette.ColorRole.Base)
|
||||
|
||||
track.items.append(self)
|
||||
|
||||
track.set_occurrences()
|
||||
|
@ -31,3 +42,16 @@ class TrackItem(QTreeWidgetItem):
|
|||
self.setText(3, track.tags.album)
|
||||
self.setText(4, str(self.index_user_sort + 1))
|
||||
|
||||
def mark(self):
|
||||
self.setIcon(0, self.playing_mark)
|
||||
self.setFont(1, self.bold_font)
|
||||
self.setFont(2, self.bold_font)
|
||||
self.setFont(3, self.normal_font)
|
||||
|
||||
|
||||
def unmark(self):
|
||||
self.setIcon(0, QIcon(None))
|
||||
self.setFont(1, self.normal_font)
|
||||
self.setFont(2, self.normal_font)
|
||||
self.setFont(3, self.normal_font)
|
||||
|
||||
|
|
|
@ -46,15 +46,15 @@ class TrackControl(QToolBar):
|
|||
self.next_button.triggered.connect(self.next_track)
|
||||
|
||||
def previous_track(self):
|
||||
if self.app.player.current_playlist.has_tracks():
|
||||
if self.app.player.current_playlist is not None and self.app.player.current_playlist.has_tracks():
|
||||
self.app.player.previous_track()
|
||||
|
||||
def stop(self):
|
||||
if self.app.player.current_playlist.has_tracks():
|
||||
if self.app.player.current_playlist is not None and self.app.player.current_playlist.has_tracks():
|
||||
self.app.player.stop()
|
||||
|
||||
def next_track(self):
|
||||
if self.app.player.current_playlist.has_tracks():
|
||||
if self.app.player.current_playlist is not None and self.app.player.current_playlist.has_tracks():
|
||||
self.app.player.next_track()
|
||||
|
||||
def on_track_change(self, previous_track, track):
|
||||
|
@ -80,11 +80,17 @@ class TrackControl(QToolBar):
|
|||
elif self.app.player.playing: # playing
|
||||
self.app.player.pause()
|
||||
|
||||
elif self.app.player.current_playlist.has_tracks(): # stopped but tracks in the current playlist
|
||||
# stopped but tracks in the current playlist
|
||||
elif self.app.player.current_playlist is not None and self.app.player.current_playlist.has_tracks():
|
||||
self.app.player.start_playing()
|
||||
|
||||
elif self.app.player.current_playlist.title == "None":
|
||||
self.app.player.start_playlist(self.app.gui.clicked_playlist)
|
||||
elif self.app.player.current_playlist is None:
|
||||
if self.app.settings.latest_playlist is not None:
|
||||
for playlist in self.app.library.playlists: # get loaded playlist by the path of the latest playlist
|
||||
if playlist.path == self.app.settings.latest_playlist:
|
||||
self.app.player.start_playlist(playlist)
|
||||
|
||||
break
|
||||
|
||||
def on_playstate_update(self):
|
||||
if self.app.player.playing:
|
||||
|
|
91
wobuzz/ui/track_info.py
Normal file
91
wobuzz/ui/track_info.py
Normal file
|
@ -0,0 +1,91 @@
|
|||
#!/usr/bin/python3
|
||||
|
||||
from PyQt6.QtGui import QPixmap, QFont
|
||||
from PyQt6.QtWidgets import QToolBar, QWidget, QLabel, QSizePolicy, QVBoxLayout
|
||||
|
||||
|
||||
class TrackInfo(QToolBar):
|
||||
title_font = QFont()
|
||||
title_font.setPointSize(16)
|
||||
title_font.setBold(True)
|
||||
|
||||
artist_font = QFont()
|
||||
title_font.setPointSize(12)
|
||||
|
||||
album_font = QFont()
|
||||
album_font.setPointSize(8)
|
||||
|
||||
def __init__(self, app, parent=None):
|
||||
super().__init__(parent)
|
||||
|
||||
self.app = app
|
||||
|
||||
self.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
|
||||
|
||||
self.wobuzz_logo = QPixmap(f"{self.app.utils.wobuzz_location}/icon.svg")
|
||||
|
||||
self.track_cover = QLabel(self)
|
||||
self.track_cover.setFixedSize(64, 64)
|
||||
self.track_cover.setScaledContents(True)
|
||||
self.track_cover.setPixmap(self.wobuzz_logo)
|
||||
self.addWidget(self.track_cover)
|
||||
|
||||
self.info_container = QWidget(self)
|
||||
info_container_layout = QVBoxLayout(self.info_container)
|
||||
self.info_container.setLayout(info_container_layout)
|
||||
self.addWidget(self.info_container)
|
||||
|
||||
self.title = QLabel("Title", self.info_container)
|
||||
self.title.setFont(self.title_font)
|
||||
info_container_layout.addWidget(self.title)
|
||||
|
||||
self.artist = QLabel("Artist", self.info_container)
|
||||
self.artist.setFont(self.artist_font)
|
||||
info_container_layout.addWidget(self.artist)
|
||||
|
||||
self.album = QLabel("Album", self.info_container)
|
||||
self.album.setFont(self.album_font)
|
||||
info_container_layout.addWidget(self.album)
|
||||
|
||||
def update_info(self):
|
||||
current_playlist = self.app.player.current_playlist
|
||||
|
||||
if current_playlist is not None:
|
||||
current_track = current_playlist.current_track
|
||||
title = current_track.tags.title
|
||||
artist = current_track.tags.artist
|
||||
album = current_track.tags.album
|
||||
|
||||
self.title.setText(title)
|
||||
|
||||
if artist is not None and not artist == "":
|
||||
self.artist.setText(f"By {current_track.tags.artist}")
|
||||
|
||||
else:
|
||||
self.artist.setText("No Artist")
|
||||
|
||||
if album is not None and not album == "":
|
||||
self.album.setText(f"In {current_track.tags.album}")
|
||||
|
||||
else:
|
||||
self.album.setText("No Album")
|
||||
|
||||
cover = current_track.tags.images.any
|
||||
|
||||
if cover is None:
|
||||
self.track_cover.setPixmap(self.wobuzz_logo)
|
||||
|
||||
return
|
||||
|
||||
cover_data = cover.data
|
||||
|
||||
if isinstance(cover_data, bytes):
|
||||
cover_pixmap = QPixmap()
|
||||
cover_pixmap.loadFromData(cover_data)
|
||||
|
||||
self.track_cover.setPixmap(cover_pixmap)
|
||||
|
||||
else:
|
||||
self.track_cover.setPixmap(self.wobuzz_logo)
|
||||
|
||||
|
|
@ -29,7 +29,7 @@ class TrackProgressSlider(QSlider):
|
|||
self.sliderPressed.connect(self.on_press)
|
||||
self.sliderReleased.connect(self.on_release)
|
||||
|
||||
def post_init(self):
|
||||
def late_init(self):
|
||||
self.track_control = self.app.gui.track_control
|
||||
|
||||
def mousePressEvent(self, event: QMouseEvent):
|
||||
|
@ -57,7 +57,7 @@ class TrackProgressSlider(QSlider):
|
|||
def on_release(self):
|
||||
self.dragged = False
|
||||
|
||||
if self.app.player.current_playlist.has_tracks():
|
||||
if self.app.player.current_playlist is not None and self.app.player.current_playlist.has_tracks():
|
||||
self.app.player.seek(self.value())
|
||||
|
||||
def update_progress(self):
|
||||
|
|
Loading…
Add table
Reference in a new issue