Compare commits
5 Commits
50d1e8bd4a
...
b8fcc79f8e
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b8fcc79f8e | ||
|
|
27012a9658 | ||
|
|
1d5fe3e57e | ||
|
|
40cad1c98f | ||
|
|
5f5bb27a5f |
264
app/classes.py
264
app/classes.py
@ -6,7 +6,6 @@ from dataclasses import dataclass, field
|
||||
import datetime as dt
|
||||
from enum import auto, Enum
|
||||
import platform
|
||||
import threading
|
||||
from time import sleep
|
||||
from typing import Any, Optional, NamedTuple
|
||||
|
||||
@ -19,9 +18,7 @@ import vlc # type: ignore
|
||||
from PyQt6.QtCore import (
|
||||
pyqtSignal,
|
||||
QObject,
|
||||
QRunnable,
|
||||
QThread,
|
||||
QThreadPool,
|
||||
)
|
||||
from pyqtgraph import PlotWidget
|
||||
from pyqtgraph.graphicsItems.PlotDataItem import PlotDataItem # type: ignore
|
||||
@ -37,18 +34,18 @@ from helpers import (
|
||||
show_warning,
|
||||
singleton,
|
||||
)
|
||||
|
||||
lock = threading.Lock()
|
||||
from vlcmanager import VLCManager
|
||||
|
||||
# Define the VLC callback function type
|
||||
VLC_LOG_CB = ctypes.CFUNCTYPE(
|
||||
None,
|
||||
ctypes.c_void_p,
|
||||
ctypes.c_int,
|
||||
ctypes.c_void_p,
|
||||
ctypes.c_char_p,
|
||||
ctypes.c_void_p,
|
||||
)
|
||||
# VLC logging is very noisy so comment out unless needed
|
||||
# VLC_LOG_CB = ctypes.CFUNCTYPE(
|
||||
# None,
|
||||
# ctypes.c_void_p,
|
||||
# ctypes.c_int,
|
||||
# ctypes.c_void_p,
|
||||
# ctypes.c_char_p,
|
||||
# ctypes.c_void_p,
|
||||
# )
|
||||
|
||||
# Determine the correct C library for vsnprintf based on the platform
|
||||
if platform.system() == "Windows":
|
||||
@ -138,8 +135,8 @@ class _FadeCurve:
|
||||
0, track_fade_at - Config.FADE_CURVE_MS_BEFORE_FADE - 1
|
||||
)
|
||||
self.end_ms: int = track_silence_at
|
||||
self.audio_segment = audio[self.start_ms : self.end_ms]
|
||||
self.graph_array = np.array(self.audio_segment.get_array_of_samples())
|
||||
audio_segment = audio[self.start_ms : self.end_ms]
|
||||
self.graph_array = np.array(audio_segment.get_array_of_samples())
|
||||
|
||||
# Calculate the factor to map milliseconds of track to array
|
||||
self.ms_to_array_factor = len(self.graph_array) / (self.end_ms - self.start_ms)
|
||||
@ -181,11 +178,14 @@ class _FadeCurve:
|
||||
|
||||
# Update region position
|
||||
if self.region:
|
||||
log.debug("issue223: _FadeCurve: update region")
|
||||
# Next line is very noisy
|
||||
# log.debug("issue223: _FadeCurve: update region")
|
||||
self.region.setRegion([0, ms_of_graph * self.ms_to_array_factor])
|
||||
|
||||
|
||||
class _FadeTrack(QRunnable):
|
||||
class _FadeTrack(QThread):
|
||||
finished = pyqtSignal()
|
||||
|
||||
def __init__(self, player: vlc.MediaPlayer, fade_seconds: int) -> None:
|
||||
super().__init__()
|
||||
self.player = player
|
||||
@ -201,18 +201,22 @@ class _FadeTrack(QRunnable):
|
||||
|
||||
# Reduce volume logarithmically
|
||||
total_steps = self.fade_seconds * Config.FADEOUT_STEPS_PER_SECOND
|
||||
db_reduction_per_step = Config.FADEOUT_DB / total_steps
|
||||
reduction_factor_per_step = pow(10, (db_reduction_per_step / 20))
|
||||
if total_steps > 0:
|
||||
db_reduction_per_step = Config.FADEOUT_DB / total_steps
|
||||
reduction_factor_per_step = pow(10, (db_reduction_per_step / 20))
|
||||
|
||||
volume = self.player.audio_get_volume()
|
||||
volume = self.player.audio_get_volume()
|
||||
|
||||
for i in range(1, total_steps + 1):
|
||||
self.player.audio_set_volume(
|
||||
int(volume * pow(reduction_factor_per_step, i))
|
||||
)
|
||||
sleep(1 / Config.FADEOUT_STEPS_PER_SECOND)
|
||||
for i in range(1, total_steps + 1):
|
||||
self.player.audio_set_volume(
|
||||
int(volume * pow(reduction_factor_per_step, i))
|
||||
)
|
||||
sleep(1 / Config.FADEOUT_STEPS_PER_SECOND)
|
||||
|
||||
self.player.stop()
|
||||
self.finished.emit()
|
||||
|
||||
|
||||
vlc_instance = VLCManager().vlc_instance
|
||||
|
||||
|
||||
class _Music:
|
||||
@ -221,38 +225,37 @@ class _Music:
|
||||
"""
|
||||
|
||||
def __init__(self, name: str) -> None:
|
||||
self.VLC = vlc.Instance()
|
||||
self.VLC.set_user_agent(name, name)
|
||||
vlc_instance.set_user_agent(name, name)
|
||||
self.player: Optional[vlc.MediaPlayer] = None
|
||||
self.name = name
|
||||
self.max_volume: int = Config.VLC_VOLUME_DEFAULT
|
||||
self.start_dt: Optional[dt.datetime] = None
|
||||
self.player_count: int = 0
|
||||
|
||||
# Set up logging
|
||||
self._set_vlc_log()
|
||||
# self._set_vlc_log()
|
||||
|
||||
@VLC_LOG_CB
|
||||
def log_callback(data, level, ctx, fmt, args):
|
||||
try:
|
||||
# Create a ctypes string buffer to hold the formatted message
|
||||
buf = ctypes.create_string_buffer(1024)
|
||||
# VLC logging very noisy so comment out unless needed
|
||||
# @VLC_LOG_CB
|
||||
# def log_callback(data, level, ctx, fmt, args):
|
||||
# try:
|
||||
# # Create a ctypes string buffer to hold the formatted message
|
||||
# buf = ctypes.create_string_buffer(1024)
|
||||
|
||||
# Use vsnprintf to format the string with the va_list
|
||||
libc.vsnprintf(buf, len(buf), fmt, args)
|
||||
# # Use vsnprintf to format the string with the va_list
|
||||
# libc.vsnprintf(buf, len(buf), fmt, args)
|
||||
|
||||
# Decode the formatted message
|
||||
message = buf.value.decode("utf-8", errors="replace")
|
||||
log.debug("VLC: " + message)
|
||||
except Exception as e:
|
||||
log.error(f"Error in VLC log callback: {e}")
|
||||
# # Decode the formatted message
|
||||
# message = buf.value.decode("utf-8", errors="replace")
|
||||
# log.debug("VLC: " + message)
|
||||
# except Exception as e:
|
||||
# log.error(f"Error in VLC log callback: {e}")
|
||||
|
||||
def _set_vlc_log(self):
|
||||
try:
|
||||
vlc.libvlc_log_set(self.VLC, self.log_callback, None)
|
||||
log.debug("VLC logging set up successfully")
|
||||
except Exception as e:
|
||||
log.error(f"Failed to set up VLC logging: {e}")
|
||||
# def _set_vlc_log(self):
|
||||
# try:
|
||||
# vlc.libvlc_log_set(vlc_instance, self.log_callback, None)
|
||||
# log.debug("VLC logging set up successfully")
|
||||
# except Exception as e:
|
||||
# log.error(f"Failed to set up VLC logging: {e}")
|
||||
|
||||
def adjust_by_ms(self, ms: int) -> None:
|
||||
"""Move player position by ms milliseconds"""
|
||||
@ -275,27 +278,6 @@ class _Music:
|
||||
else:
|
||||
self.start_dt = dt.datetime.now() - dt.timedelta(milliseconds=ms)
|
||||
|
||||
def stop(self) -> None:
|
||||
"""Immediately stop playing"""
|
||||
|
||||
log.debug(f"Music[{self.name}].stop()")
|
||||
|
||||
self.start_dt = None
|
||||
|
||||
if not self.player:
|
||||
return
|
||||
|
||||
p = self.player
|
||||
self.player = None
|
||||
self.start_dt = None
|
||||
|
||||
with lock:
|
||||
p.stop()
|
||||
p.release()
|
||||
self.player_count -= 1
|
||||
log.debug(f"_Music.stop: Releasing player {p=}, {self.player_count=}")
|
||||
p = None
|
||||
|
||||
def fade(self, fade_seconds: int) -> None:
|
||||
"""
|
||||
Fade the currently playing track.
|
||||
@ -310,23 +292,10 @@ class _Music:
|
||||
if not self.player.get_position() > 0 and self.player.is_playing():
|
||||
return
|
||||
|
||||
if fade_seconds <= 0:
|
||||
self.stop()
|
||||
return
|
||||
|
||||
# Take a copy of current player to allow another track to be
|
||||
# started without interfering here
|
||||
with lock:
|
||||
p = self.player
|
||||
self.player = None
|
||||
|
||||
pool = QThreadPool.globalInstance()
|
||||
if pool:
|
||||
fader = _FadeTrack(p, fade_seconds=fade_seconds)
|
||||
pool.start(fader)
|
||||
self.start_dt = None
|
||||
else:
|
||||
log.error("_Music: failed to allocate QThreadPool")
|
||||
self.fader_worker = _FadeTrack(self.player, fade_seconds=fade_seconds)
|
||||
self.fader_worker.finished.connect(self.player.release)
|
||||
self.fader_worker.start()
|
||||
self.start_dt = None
|
||||
|
||||
def get_playtime(self) -> int:
|
||||
"""
|
||||
@ -388,38 +357,35 @@ class _Music:
|
||||
log.error(f"play({path}): path not readable")
|
||||
return None
|
||||
|
||||
media = self.VLC.media_new_path(path)
|
||||
if media is None:
|
||||
log.error(f"_Music:play: failed to create media ({path=})")
|
||||
show_warning(None, "Error loading file", f"Cannot play file ({path})")
|
||||
self.player = vlc.MediaPlayer(vlc_instance, path)
|
||||
if self.player is None:
|
||||
log.error(f"_Music:play: failed to create MediaPlayer ({path=})")
|
||||
show_warning(
|
||||
None, "Error creating MediaPlayer", f"Cannot play file ({path})"
|
||||
)
|
||||
return
|
||||
self.player = media.player_new_from_media()
|
||||
if self.player:
|
||||
_ = self.player.play()
|
||||
self.set_volume(self.max_volume)
|
||||
self.player_count += 1
|
||||
log.debug(f"_Music.play: {self.player_count=}")
|
||||
|
||||
if position:
|
||||
self.player.set_position(position)
|
||||
self.start_dt = start_time
|
||||
_ = self.player.play()
|
||||
self.set_volume(self.max_volume)
|
||||
|
||||
# For as-yet unknown reasons. sometimes the volume gets
|
||||
# reset to zero within 200mS or so of starting play. This
|
||||
# only happened since moving to Debian 12, which uses
|
||||
# Pipewire for sound (which may be irrelevant).
|
||||
# It has been known for the volume to need correcting more
|
||||
# than once in the first 200mS.
|
||||
for _ in range(3):
|
||||
if self.player:
|
||||
volume = self.player.audio_get_volume()
|
||||
if volume < Config.VLC_VOLUME_DEFAULT:
|
||||
self.set_volume(Config.VLC_VOLUME_DEFAULT)
|
||||
log.error(f"Reset from {volume=}")
|
||||
sleep(0.1)
|
||||
else:
|
||||
log.error("_Music:play: failed to create media player")
|
||||
show_warning(None, "Media player", "Unable to create media player")
|
||||
if position:
|
||||
self.player.set_position(position)
|
||||
self.start_dt = start_time
|
||||
|
||||
# For as-yet unknown reasons. sometimes the volume gets
|
||||
# reset to zero within 200mS or so of starting play. This
|
||||
# only happened since moving to Debian 12, which uses
|
||||
# Pipewire for sound (which may be irrelevant).
|
||||
# It has been known for the volume to need correcting more
|
||||
# than once in the first 200mS.
|
||||
# Update August 2024: This no longer seems to be an issue
|
||||
# for _ in range(3):
|
||||
# if self.player:
|
||||
# volume = self.player.audio_get_volume()
|
||||
# if volume < Config.VLC_VOLUME_DEFAULT:
|
||||
# self.set_volume(Config.VLC_VOLUME_DEFAULT)
|
||||
# log.error(f"Reset from {volume=}")
|
||||
# sleep(0.1)
|
||||
|
||||
def set_position(self, position: float) -> None:
|
||||
"""
|
||||
@ -456,6 +422,21 @@ class _Music:
|
||||
log.debug(f"Reset from {volume=}")
|
||||
sleep(0.1)
|
||||
|
||||
def stop(self) -> None:
|
||||
"""Immediately stop playing"""
|
||||
|
||||
log.debug(f"Music[{self.name}].stop()")
|
||||
|
||||
self.start_dt = None
|
||||
|
||||
if not self.player:
|
||||
return
|
||||
|
||||
if self.player.is_playing():
|
||||
self.player.stop()
|
||||
self.player.release()
|
||||
self.player = None
|
||||
|
||||
|
||||
@singleton
|
||||
@dataclass
|
||||
@ -514,7 +495,9 @@ class RowAndTrack:
|
||||
self.fade_at = playlist_row.track.fade_at
|
||||
self.intro = playlist_row.track.intro
|
||||
if playlist_row.track.playdates:
|
||||
self.lastplayed = max([a.lastplayed for a in playlist_row.track.playdates])
|
||||
self.lastplayed = max(
|
||||
[a.lastplayed for a in playlist_row.track.playdates]
|
||||
)
|
||||
else:
|
||||
self.lastplayed = Config.EPOCH
|
||||
self.path = playlist_row.track.path
|
||||
@ -544,6 +527,7 @@ class RowAndTrack:
|
||||
self.start_time: Optional[dt.datetime] = None
|
||||
|
||||
# Other object initialisation
|
||||
self.music = _Music(name=Config.VLC_MAIN_PLAYER_NAME)
|
||||
self.signals = MusicMusterSignals()
|
||||
|
||||
def __repr__(self) -> str:
|
||||
@ -564,12 +548,16 @@ class RowAndTrack:
|
||||
if self.end_of_track_signalled:
|
||||
return
|
||||
|
||||
if not self.player.is_playing():
|
||||
self.start_time = None
|
||||
if self.fade_graph:
|
||||
self.fade_graph.clear()
|
||||
self.signal_end_of_track()
|
||||
self.end_of_track_signalled = True
|
||||
if self.music.is_playing():
|
||||
return
|
||||
|
||||
self.start_time = None
|
||||
if self.fade_graph:
|
||||
self.fade_graph.clear()
|
||||
# Ensure that player is released
|
||||
self.music.fade(0)
|
||||
self.signals.track_ended_signal.emit()
|
||||
self.end_of_track_signalled = True
|
||||
|
||||
def create_fade_graph(self) -> None:
|
||||
"""
|
||||
@ -596,16 +584,16 @@ class RowAndTrack:
|
||||
"""
|
||||
|
||||
if enable:
|
||||
self.player.set_volume(volume=Config.VLC_VOLUME_DROP3db, set_default=False)
|
||||
self.music.set_volume(volume=Config.VLC_VOLUME_DROP3db, set_default=False)
|
||||
else:
|
||||
self.player.set_volume(volume=Config.VLC_VOLUME_DEFAULT, set_default=False)
|
||||
self.music.set_volume(volume=Config.VLC_VOLUME_DEFAULT, set_default=False)
|
||||
|
||||
def fade(self, fade_seconds: int = Config.FADEOUT_SECONDS) -> None:
|
||||
"""Fade music"""
|
||||
|
||||
self.resume_marker = self.player.get_position()
|
||||
self.player.fade(fade_seconds)
|
||||
self.signal_end_of_track()
|
||||
self.resume_marker = self.music.get_position()
|
||||
self.music.fade(fade_seconds)
|
||||
self.signals.track_ended_signal.emit()
|
||||
|
||||
def is_playing(self) -> bool:
|
||||
"""
|
||||
@ -615,21 +603,21 @@ class RowAndTrack:
|
||||
if self.start_time is None:
|
||||
return False
|
||||
|
||||
return self.player.is_playing()
|
||||
return self.music.is_playing()
|
||||
|
||||
def move_back(self, ms: int = Config.PREVIEW_BACK_MS) -> None:
|
||||
"""
|
||||
Rewind player by ms milliseconds
|
||||
"""
|
||||
|
||||
self.player.adjust_by_ms(ms * -1)
|
||||
self.music.adjust_by_ms(ms * -1)
|
||||
|
||||
def move_forward(self, ms: int = Config.PREVIEW_ADVANCE_MS) -> None:
|
||||
"""
|
||||
Rewind player by ms milliseconds
|
||||
"""
|
||||
|
||||
self.player.adjust_by_ms(ms)
|
||||
self.music.adjust_by_ms(ms)
|
||||
|
||||
def play(self, position: Optional[float] = None) -> None:
|
||||
"""Play track"""
|
||||
@ -639,8 +627,7 @@ class RowAndTrack:
|
||||
self.start_time = now
|
||||
|
||||
# Initialise player
|
||||
self.player = _Music(name=Config.VLC_MAIN_PLAYER_NAME)
|
||||
self.player.play(self.path, start_time=now, position=position)
|
||||
self.music.play(self.path, start_time=now, position=position)
|
||||
|
||||
self.end_time = now + dt.timedelta(milliseconds=self.duration)
|
||||
|
||||
@ -658,7 +645,7 @@ class RowAndTrack:
|
||||
Restart player
|
||||
"""
|
||||
|
||||
self.player.adjust_by_ms(self.time_playing() * -1)
|
||||
self.music.adjust_by_ms(self.time_playing() * -1)
|
||||
|
||||
def set_forecast_start_time(
|
||||
self, modified_rows: list[int], start: Optional[dt.datetime]
|
||||
@ -693,19 +680,12 @@ class RowAndTrack:
|
||||
|
||||
return new_start_time
|
||||
|
||||
def signal_end_of_track(self) -> None:
|
||||
"""
|
||||
Send end of track signal unless we are a preview player
|
||||
"""
|
||||
|
||||
self.signals.track_ended_signal.emit()
|
||||
|
||||
def stop(self, fade_seconds: int = 0) -> None:
|
||||
"""
|
||||
Stop this track playing
|
||||
"""
|
||||
|
||||
self.resume_marker = self.player.get_position()
|
||||
self.resume_marker = self.music.get_position()
|
||||
self.fade(fade_seconds)
|
||||
|
||||
# Reset fade graph
|
||||
@ -720,7 +700,7 @@ class RowAndTrack:
|
||||
if self.start_time is None:
|
||||
return 0
|
||||
|
||||
return self.player.get_playtime()
|
||||
return self.music.get_playtime()
|
||||
|
||||
def time_remaining_intro(self) -> int:
|
||||
"""
|
||||
|
||||
@ -316,6 +316,7 @@ class Window(QMainWindow, Ui_MainWindow):
|
||||
self.action_quicklog.activated.connect(self.quicklog)
|
||||
|
||||
self.load_last_playlists()
|
||||
self.stop_autoplay = False
|
||||
|
||||
def about(self) -> None:
|
||||
"""Get git tag and database name"""
|
||||
@ -659,9 +660,13 @@ class Window(QMainWindow, Ui_MainWindow):
|
||||
- Enable controls
|
||||
"""
|
||||
|
||||
# Reset track_sequence objects
|
||||
track_sequence.previous = track_sequence.current
|
||||
track_sequence.current = None
|
||||
if track_sequence.current:
|
||||
# Dereference the fade curve so it can be garbage collected
|
||||
track_sequence.current.fade_graph = None
|
||||
|
||||
# Reset track_sequence objects
|
||||
track_sequence.previous = track_sequence.current
|
||||
track_sequence.current = None
|
||||
|
||||
# Tell model previous track has finished
|
||||
self.active_proxy_model().previous_track_ended()
|
||||
@ -669,7 +674,6 @@ class Window(QMainWindow, Ui_MainWindow):
|
||||
# Reset clocks
|
||||
self.frame_fade.setStyleSheet("")
|
||||
self.frame_silent.setStyleSheet("")
|
||||
self.label_elapsed_timer.setText("00:00 / 00:00")
|
||||
self.label_fade_timer.setText("00:00")
|
||||
self.label_silent_timer.setText("00:00")
|
||||
|
||||
@ -680,6 +684,10 @@ class Window(QMainWindow, Ui_MainWindow):
|
||||
self.catch_return_key = False
|
||||
self.show_status_message("Play controls: Enabled", 0)
|
||||
|
||||
# autoplay
|
||||
# if not self.stop_autoplay:
|
||||
# self.play_next()
|
||||
|
||||
def export_playlist_tab(self) -> None:
|
||||
"""Export the current playlist to an m3u file"""
|
||||
|
||||
@ -1053,7 +1061,7 @@ class Window(QMainWindow, Ui_MainWindow):
|
||||
if Config.USE_INTERNAL_BROWSER:
|
||||
self.tabInfolist.open_tab(url, title)
|
||||
else:
|
||||
webbrowser.get('browser').open_new_tab(url)
|
||||
webbrowser.get("browser").open_new_tab(url)
|
||||
|
||||
def open_wikipedia_browser(self, title: str) -> None:
|
||||
"""Search Wikipedia for title"""
|
||||
@ -1065,7 +1073,7 @@ class Window(QMainWindow, Ui_MainWindow):
|
||||
if Config.USE_INTERNAL_BROWSER:
|
||||
self.tabInfolist.open_tab(url, title)
|
||||
else:
|
||||
webbrowser.get('browser').open_new_tab(url)
|
||||
webbrowser.get("browser").open_new_tab(url)
|
||||
|
||||
def paste_rows(self) -> None:
|
||||
"""
|
||||
@ -1151,6 +1159,7 @@ class Window(QMainWindow, Ui_MainWindow):
|
||||
self.btnDrop3db.setChecked(False)
|
||||
|
||||
# Play (new) current track
|
||||
log.info(f"Play: {track_sequence.current.title}")
|
||||
track_sequence.current.play(position)
|
||||
|
||||
# Update clocks now, don't wait for next tick
|
||||
@ -1159,7 +1168,9 @@ class Window(QMainWindow, Ui_MainWindow):
|
||||
|
||||
# Show closing volume graph
|
||||
if track_sequence.current.fade_graph:
|
||||
log.debug(f"issue223: play_next: set up fade_graph, {track_sequence.current.title=}")
|
||||
log.debug(
|
||||
f"issue223: play_next: set up fade_graph, {track_sequence.current.title=}"
|
||||
)
|
||||
track_sequence.current.fade_graph.GraphWidget = self.widgetFadeVolume
|
||||
track_sequence.current.fade_graph.clear()
|
||||
track_sequence.current.fade_graph.plot()
|
||||
@ -1200,7 +1211,7 @@ class Window(QMainWindow, Ui_MainWindow):
|
||||
if not track_info:
|
||||
# Otherwise get track_id to next track to play
|
||||
if track_sequence.next:
|
||||
if track_sequence.next.path:
|
||||
if track_sequence.next.track_id:
|
||||
track_info = TrackInfo(
|
||||
track_sequence.next.track_id, track_sequence.next.row_number
|
||||
)
|
||||
@ -1412,7 +1423,8 @@ class Window(QMainWindow, Ui_MainWindow):
|
||||
return
|
||||
|
||||
# Return if no saved position
|
||||
if not track_sequence.previous.resume_marker:
|
||||
resume_marker = track_sequence.previous.resume_marker
|
||||
if not resume_marker:
|
||||
log.error("No previous track position")
|
||||
return
|
||||
|
||||
@ -1421,7 +1433,7 @@ class Window(QMainWindow, Ui_MainWindow):
|
||||
track_sequence.set_next(track_sequence.previous)
|
||||
|
||||
# Now resume playing the now-next track
|
||||
self.play_next(track_sequence.next.resume_marker)
|
||||
self.play_next(resume_marker)
|
||||
|
||||
# Adjust track info so that clocks and graph are correct.
|
||||
# We need to fake the start time to reflect where we resumed the
|
||||
@ -1623,6 +1635,7 @@ class Window(QMainWindow, Ui_MainWindow):
|
||||
def stop(self) -> None:
|
||||
"""Stop playing immediately"""
|
||||
|
||||
self.stop_autoplay = True
|
||||
if track_sequence.current:
|
||||
track_sequence.current.stop()
|
||||
|
||||
|
||||
@ -561,7 +561,7 @@ class PlaylistModel(QAbstractTableModel):
|
||||
for a in self.playlist_rows.values()
|
||||
if not a.played and a.track_id is not None
|
||||
]
|
||||
log.debug(f"{self}: get_unplayed_rows() returned: {result=}")
|
||||
# log.debug(f"{self}: get_unplayed_rows() returned: {result=}")
|
||||
return result
|
||||
|
||||
def headerData(
|
||||
@ -884,7 +884,9 @@ class PlaylistModel(QAbstractTableModel):
|
||||
Move existing_rat track to new_row_number and append note to any existing note
|
||||
"""
|
||||
|
||||
log.debug(f"{self}: move_track_add_note({new_row_number=}, {existing_rat=}, {note=}")
|
||||
log.debug(
|
||||
f"{self}: move_track_add_note({new_row_number=}, {existing_rat=}, {note=}"
|
||||
)
|
||||
|
||||
if note:
|
||||
with db.Session() as session:
|
||||
@ -911,7 +913,9 @@ class PlaylistModel(QAbstractTableModel):
|
||||
Add the existing_rat track details to the existing header at header_row_number
|
||||
"""
|
||||
|
||||
log.debug(f"{self}: move_track_to_header({header_row_number=}, {existing_rat=}, {note=}")
|
||||
log.debug(
|
||||
f"{self}: move_track_to_header({header_row_number=}, {existing_rat=}, {note=}"
|
||||
)
|
||||
|
||||
if existing_rat.track_id:
|
||||
if note and existing_rat.note:
|
||||
@ -968,7 +972,9 @@ class PlaylistModel(QAbstractTableModel):
|
||||
|
||||
# Sanity check
|
||||
if not track_sequence.previous:
|
||||
log.error(f"{self}: playlistmodel:previous_track_ended called with no current track")
|
||||
log.error(
|
||||
f"{self}: playlistmodel:previous_track_ended called with no current track"
|
||||
)
|
||||
return
|
||||
if track_sequence.previous.row_number is None:
|
||||
log.error(
|
||||
@ -1243,7 +1249,9 @@ class PlaylistModel(QAbstractTableModel):
|
||||
PlaylistRows, self.playlist_rows[row_number].playlistrow_id
|
||||
)
|
||||
if not playlist_row:
|
||||
log.error(f"{self}: Error saving data: {row_number=}, {column=}, {value=}")
|
||||
log.error(
|
||||
f"{self}: Error saving data: {row_number=}, {column=}, {value=}"
|
||||
)
|
||||
return False
|
||||
|
||||
if playlist_row.track_id:
|
||||
@ -1294,7 +1302,9 @@ class PlaylistModel(QAbstractTableModel):
|
||||
shortlist_rows = {k: self.playlist_rows[k] for k in row_numbers}
|
||||
sorted_list = [
|
||||
playlist_row.row_number
|
||||
for playlist_row in sorted(shortlist_rows.values(), key=attrgetter(attr_name))
|
||||
for playlist_row in sorted(
|
||||
shortlist_rows.values(), key=attrgetter(attr_name)
|
||||
)
|
||||
]
|
||||
self.move_rows(sorted_list, min(sorted_list))
|
||||
|
||||
|
||||
29
app/vlcmanager.py
Normal file
29
app/vlcmanager.py
Normal file
@ -0,0 +1,29 @@
|
||||
# Standard library imports
|
||||
|
||||
# PyQt imports
|
||||
|
||||
# Third party imports
|
||||
import vlc # type: ignore
|
||||
|
||||
# App imports
|
||||
|
||||
|
||||
class VLCManager:
|
||||
"""
|
||||
Singleton class to ensure we only ever have one vlc Instance
|
||||
"""
|
||||
|
||||
__instance = None
|
||||
|
||||
def __init__(self) -> None:
|
||||
if VLCManager.__instance is None:
|
||||
self.vlc_instance = vlc.Instance()
|
||||
VLCManager.__instance = self
|
||||
else:
|
||||
raise Exception("Attempted to create a second VLCManager instance")
|
||||
|
||||
@staticmethod
|
||||
def get_instance() -> vlc.Instance:
|
||||
if VLCManager.__instance is None:
|
||||
VLCManager()
|
||||
return VLCManager.__instance
|
||||
104
poetry.lock
generated
104
poetry.lock
generated
@ -81,33 +81,33 @@ dev = ["freezegun (>=1.0,<2.0)", "pytest (>=6.0)", "pytest-cov"]
|
||||
|
||||
[[package]]
|
||||
name = "black"
|
||||
version = "24.4.2"
|
||||
version = "24.8.0"
|
||||
description = "The uncompromising code formatter."
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "black-24.4.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:dd1b5a14e417189db4c7b64a6540f31730713d173f0b63e55fabd52d61d8fdce"},
|
||||
{file = "black-24.4.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8e537d281831ad0e71007dcdcbe50a71470b978c453fa41ce77186bbe0ed6021"},
|
||||
{file = "black-24.4.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eaea3008c281f1038edb473c1aa8ed8143a5535ff18f978a318f10302b254063"},
|
||||
{file = "black-24.4.2-cp310-cp310-win_amd64.whl", hash = "sha256:7768a0dbf16a39aa5e9a3ded568bb545c8c2727396d063bbaf847df05b08cd96"},
|
||||
{file = "black-24.4.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:257d724c2c9b1660f353b36c802ccece186a30accc7742c176d29c146df6e474"},
|
||||
{file = "black-24.4.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bdde6f877a18f24844e381d45e9947a49e97933573ac9d4345399be37621e26c"},
|
||||
{file = "black-24.4.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e151054aa00bad1f4e1f04919542885f89f5f7d086b8a59e5000e6c616896ffb"},
|
||||
{file = "black-24.4.2-cp311-cp311-win_amd64.whl", hash = "sha256:7e122b1c4fb252fd85df3ca93578732b4749d9be076593076ef4d07a0233c3e1"},
|
||||
{file = "black-24.4.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:accf49e151c8ed2c0cdc528691838afd217c50412534e876a19270fea1e28e2d"},
|
||||
{file = "black-24.4.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:88c57dc656038f1ab9f92b3eb5335ee9b021412feaa46330d5eba4e51fe49b04"},
|
||||
{file = "black-24.4.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:be8bef99eb46d5021bf053114442914baeb3649a89dc5f3a555c88737e5e98fc"},
|
||||
{file = "black-24.4.2-cp312-cp312-win_amd64.whl", hash = "sha256:415e686e87dbbe6f4cd5ef0fbf764af7b89f9057b97c908742b6008cc554b9c0"},
|
||||
{file = "black-24.4.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:bf10f7310db693bb62692609b397e8d67257c55f949abde4c67f9cc574492cc7"},
|
||||
{file = "black-24.4.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:98e123f1d5cfd42f886624d84464f7756f60ff6eab89ae845210631714f6db94"},
|
||||
{file = "black-24.4.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:48a85f2cb5e6799a9ef05347b476cce6c182d6c71ee36925a6c194d074336ef8"},
|
||||
{file = "black-24.4.2-cp38-cp38-win_amd64.whl", hash = "sha256:b1530ae42e9d6d5b670a34db49a94115a64596bc77710b1d05e9801e62ca0a7c"},
|
||||
{file = "black-24.4.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:37aae07b029fa0174d39daf02748b379399b909652a806e5708199bd93899da1"},
|
||||
{file = "black-24.4.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:da33a1a5e49c4122ccdfd56cd021ff1ebc4a1ec4e2d01594fef9b6f267a9e741"},
|
||||
{file = "black-24.4.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ef703f83fc32e131e9bcc0a5094cfe85599e7109f896fe8bc96cc402f3eb4b6e"},
|
||||
{file = "black-24.4.2-cp39-cp39-win_amd64.whl", hash = "sha256:b9176b9832e84308818a99a561e90aa479e73c523b3f77afd07913380ae2eab7"},
|
||||
{file = "black-24.4.2-py3-none-any.whl", hash = "sha256:d36ed1124bb81b32f8614555b34cc4259c3fbc7eec17870e8ff8ded335b58d8c"},
|
||||
{file = "black-24.4.2.tar.gz", hash = "sha256:c872b53057f000085da66a19c55d68f6f8ddcac2642392ad3a355878406fbd4d"},
|
||||
{file = "black-24.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:09cdeb74d494ec023ded657f7092ba518e8cf78fa8386155e4a03fdcc44679e6"},
|
||||
{file = "black-24.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:81c6742da39f33b08e791da38410f32e27d632260e599df7245cccee2064afeb"},
|
||||
{file = "black-24.8.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:707a1ca89221bc8a1a64fb5e15ef39cd755633daa672a9db7498d1c19de66a42"},
|
||||
{file = "black-24.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:d6417535d99c37cee4091a2f24eb2b6d5ec42b144d50f1f2e436d9fe1916fe1a"},
|
||||
{file = "black-24.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:fb6e2c0b86bbd43dee042e48059c9ad7830abd5c94b0bc518c0eeec57c3eddc1"},
|
||||
{file = "black-24.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:837fd281f1908d0076844bc2b801ad2d369c78c45cf800cad7b61686051041af"},
|
||||
{file = "black-24.8.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:62e8730977f0b77998029da7971fa896ceefa2c4c4933fcd593fa599ecbf97a4"},
|
||||
{file = "black-24.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:72901b4913cbac8972ad911dc4098d5753704d1f3c56e44ae8dce99eecb0e3af"},
|
||||
{file = "black-24.8.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:7c046c1d1eeb7aea9335da62472481d3bbf3fd986e093cffd35f4385c94ae368"},
|
||||
{file = "black-24.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:649f6d84ccbae73ab767e206772cc2d7a393a001070a4c814a546afd0d423aed"},
|
||||
{file = "black-24.8.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2b59b250fdba5f9a9cd9d0ece6e6d993d91ce877d121d161e4698af3eb9c1018"},
|
||||
{file = "black-24.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:6e55d30d44bed36593c3163b9bc63bf58b3b30e4611e4d88a0c3c239930ed5b2"},
|
||||
{file = "black-24.8.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:505289f17ceda596658ae81b61ebbe2d9b25aa78067035184ed0a9d855d18afd"},
|
||||
{file = "black-24.8.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b19c9ad992c7883ad84c9b22aaa73562a16b819c1d8db7a1a1a49fb7ec13c7d2"},
|
||||
{file = "black-24.8.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1f13f7f386f86f8121d76599114bb8c17b69d962137fc70efe56137727c7047e"},
|
||||
{file = "black-24.8.0-cp38-cp38-win_amd64.whl", hash = "sha256:f490dbd59680d809ca31efdae20e634f3fae27fba3ce0ba3208333b713bc3920"},
|
||||
{file = "black-24.8.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:eab4dd44ce80dea27dc69db40dab62d4ca96112f87996bca68cd75639aeb2e4c"},
|
||||
{file = "black-24.8.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3c4285573d4897a7610054af5a890bde7c65cb466040c5f0c8b732812d7f0e5e"},
|
||||
{file = "black-24.8.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9e84e33b37be070ba135176c123ae52a51f82306def9f7d063ee302ecab2cf47"},
|
||||
{file = "black-24.8.0-cp39-cp39-win_amd64.whl", hash = "sha256:73bbf84ed136e45d451a260c6b73ed674652f90a2b3211d6a35e78054563a9bb"},
|
||||
{file = "black-24.8.0-py3-none-any.whl", hash = "sha256:972085c618ee94f402da1af548a4f218c754ea7e5dc70acb168bfaca4c2542ed"},
|
||||
{file = "black-24.8.0.tar.gz", hash = "sha256:2500945420b6784c38b9ee885af039f5e7471ef284ab03fa35ecdde4688cd83f"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@ -864,43 +864,43 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "mypy"
|
||||
version = "1.10.1"
|
||||
version = "1.11.1"
|
||||
description = "Optional static typing for Python"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "mypy-1.10.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e36f229acfe250dc660790840916eb49726c928e8ce10fbdf90715090fe4ae02"},
|
||||
{file = "mypy-1.10.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:51a46974340baaa4145363b9e051812a2446cf583dfaeba124af966fa44593f7"},
|
||||
{file = "mypy-1.10.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:901c89c2d67bba57aaaca91ccdb659aa3a312de67f23b9dfb059727cce2e2e0a"},
|
||||
{file = "mypy-1.10.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0cd62192a4a32b77ceb31272d9e74d23cd88c8060c34d1d3622db3267679a5d9"},
|
||||
{file = "mypy-1.10.1-cp310-cp310-win_amd64.whl", hash = "sha256:a2cbc68cb9e943ac0814c13e2452d2046c2f2b23ff0278e26599224cf164e78d"},
|
||||
{file = "mypy-1.10.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:bd6f629b67bb43dc0d9211ee98b96d8dabc97b1ad38b9b25f5e4c4d7569a0c6a"},
|
||||
{file = "mypy-1.10.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a1bbb3a6f5ff319d2b9d40b4080d46cd639abe3516d5a62c070cf0114a457d84"},
|
||||
{file = "mypy-1.10.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b8edd4e9bbbc9d7b79502eb9592cab808585516ae1bcc1446eb9122656c6066f"},
|
||||
{file = "mypy-1.10.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:6166a88b15f1759f94a46fa474c7b1b05d134b1b61fca627dd7335454cc9aa6b"},
|
||||
{file = "mypy-1.10.1-cp311-cp311-win_amd64.whl", hash = "sha256:5bb9cd11c01c8606a9d0b83ffa91d0b236a0e91bc4126d9ba9ce62906ada868e"},
|
||||
{file = "mypy-1.10.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:d8681909f7b44d0b7b86e653ca152d6dff0eb5eb41694e163c6092124f8246d7"},
|
||||
{file = "mypy-1.10.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:378c03f53f10bbdd55ca94e46ec3ba255279706a6aacaecac52ad248f98205d3"},
|
||||
{file = "mypy-1.10.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6bacf8f3a3d7d849f40ca6caea5c055122efe70e81480c8328ad29c55c69e93e"},
|
||||
{file = "mypy-1.10.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:701b5f71413f1e9855566a34d6e9d12624e9e0a8818a5704d74d6b0402e66c04"},
|
||||
{file = "mypy-1.10.1-cp312-cp312-win_amd64.whl", hash = "sha256:3c4c2992f6ea46ff7fce0072642cfb62af7a2484efe69017ed8b095f7b39ef31"},
|
||||
{file = "mypy-1.10.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:604282c886497645ffb87b8f35a57ec773a4a2721161e709a4422c1636ddde5c"},
|
||||
{file = "mypy-1.10.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:37fd87cab83f09842653f08de066ee68f1182b9b5282e4634cdb4b407266bade"},
|
||||
{file = "mypy-1.10.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8addf6313777dbb92e9564c5d32ec122bf2c6c39d683ea64de6a1fd98b90fe37"},
|
||||
{file = "mypy-1.10.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:5cc3ca0a244eb9a5249c7c583ad9a7e881aa5d7b73c35652296ddcdb33b2b9c7"},
|
||||
{file = "mypy-1.10.1-cp38-cp38-win_amd64.whl", hash = "sha256:1b3a2ffce52cc4dbaeee4df762f20a2905aa171ef157b82192f2e2f368eec05d"},
|
||||
{file = "mypy-1.10.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:fe85ed6836165d52ae8b88f99527d3d1b2362e0cb90b005409b8bed90e9059b3"},
|
||||
{file = "mypy-1.10.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c2ae450d60d7d020d67ab440c6e3fae375809988119817214440033f26ddf7bf"},
|
||||
{file = "mypy-1.10.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6be84c06e6abd72f960ba9a71561c14137a583093ffcf9bbfaf5e613d63fa531"},
|
||||
{file = "mypy-1.10.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:2189ff1e39db399f08205e22a797383613ce1cb0cb3b13d8bcf0170e45b96cc3"},
|
||||
{file = "mypy-1.10.1-cp39-cp39-win_amd64.whl", hash = "sha256:97a131ee36ac37ce9581f4220311247ab6cba896b4395b9c87af0675a13a755f"},
|
||||
{file = "mypy-1.10.1-py3-none-any.whl", hash = "sha256:71d8ac0b906354ebda8ef1673e5fde785936ac1f29ff6987c7483cfbd5a4235a"},
|
||||
{file = "mypy-1.10.1.tar.gz", hash = "sha256:1f8f492d7db9e3593ef42d4f115f04e556130f2819ad33ab84551403e97dd4c0"},
|
||||
{file = "mypy-1.11.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a32fc80b63de4b5b3e65f4be82b4cfa362a46702672aa6a0f443b4689af7008c"},
|
||||
{file = "mypy-1.11.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c1952f5ea8a5a959b05ed5f16452fddadbaae48b5d39235ab4c3fc444d5fd411"},
|
||||
{file = "mypy-1.11.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e1e30dc3bfa4e157e53c1d17a0dad20f89dc433393e7702b813c10e200843b03"},
|
||||
{file = "mypy-1.11.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:2c63350af88f43a66d3dfeeeb8d77af34a4f07d760b9eb3a8697f0386c7590b4"},
|
||||
{file = "mypy-1.11.1-cp310-cp310-win_amd64.whl", hash = "sha256:a831671bad47186603872a3abc19634f3011d7f83b083762c942442d51c58d58"},
|
||||
{file = "mypy-1.11.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7b6343d338390bb946d449677726edf60102a1c96079b4f002dedff375953fc5"},
|
||||
{file = "mypy-1.11.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e4fe9f4e5e521b458d8feb52547f4bade7ef8c93238dfb5bbc790d9ff2d770ca"},
|
||||
{file = "mypy-1.11.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:886c9dbecc87b9516eff294541bf7f3655722bf22bb898ee06985cd7269898de"},
|
||||
{file = "mypy-1.11.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fca4a60e1dd9fd0193ae0067eaeeb962f2d79e0d9f0f66223a0682f26ffcc809"},
|
||||
{file = "mypy-1.11.1-cp311-cp311-win_amd64.whl", hash = "sha256:0bd53faf56de9643336aeea1c925012837432b5faf1701ccca7fde70166ccf72"},
|
||||
{file = "mypy-1.11.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f39918a50f74dc5969807dcfaecafa804fa7f90c9d60506835036cc1bc891dc8"},
|
||||
{file = "mypy-1.11.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0bc71d1fb27a428139dd78621953effe0d208aed9857cb08d002280b0422003a"},
|
||||
{file = "mypy-1.11.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b868d3bcff720dd7217c383474008ddabaf048fad8d78ed948bb4b624870a417"},
|
||||
{file = "mypy-1.11.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a707ec1527ffcdd1c784d0924bf5cb15cd7f22683b919668a04d2b9c34549d2e"},
|
||||
{file = "mypy-1.11.1-cp312-cp312-win_amd64.whl", hash = "sha256:64f4a90e3ea07f590c5bcf9029035cf0efeae5ba8be511a8caada1a4893f5525"},
|
||||
{file = "mypy-1.11.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:749fd3213916f1751fff995fccf20c6195cae941dc968f3aaadf9bb4e430e5a2"},
|
||||
{file = "mypy-1.11.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b639dce63a0b19085213ec5fdd8cffd1d81988f47a2dec7100e93564f3e8fb3b"},
|
||||
{file = "mypy-1.11.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4c956b49c5d865394d62941b109728c5c596a415e9c5b2be663dd26a1ff07bc0"},
|
||||
{file = "mypy-1.11.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:45df906e8b6804ef4b666af29a87ad9f5921aad091c79cc38e12198e220beabd"},
|
||||
{file = "mypy-1.11.1-cp38-cp38-win_amd64.whl", hash = "sha256:d44be7551689d9d47b7abc27c71257adfdb53f03880841a5db15ddb22dc63edb"},
|
||||
{file = "mypy-1.11.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2684d3f693073ab89d76da8e3921883019ea8a3ec20fa5d8ecca6a2db4c54bbe"},
|
||||
{file = "mypy-1.11.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:79c07eb282cb457473add5052b63925e5cc97dfab9812ee65a7c7ab5e3cb551c"},
|
||||
{file = "mypy-1.11.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:11965c2f571ded6239977b14deebd3f4c3abd9a92398712d6da3a772974fad69"},
|
||||
{file = "mypy-1.11.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a2b43895a0f8154df6519706d9bca8280cda52d3d9d1514b2d9c3e26792a0b74"},
|
||||
{file = "mypy-1.11.1-cp39-cp39-win_amd64.whl", hash = "sha256:1a81cf05975fd61aec5ae16501a091cfb9f605dc3e3c878c0da32f250b74760b"},
|
||||
{file = "mypy-1.11.1-py3-none-any.whl", hash = "sha256:0624bdb940255d2dd24e829d99a13cfeb72e4e9031f9492148f410ed30bcab54"},
|
||||
{file = "mypy-1.11.1.tar.gz", hash = "sha256:f404a0b069709f18bbdb702eb3dcfe51910602995de00bd39cea3050b5772d08"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
mypy-extensions = ">=1.0.0"
|
||||
typing-extensions = ">=4.1.0"
|
||||
typing-extensions = ">=4.6.0"
|
||||
|
||||
[package.extras]
|
||||
dmypy = ["psutil (>=4.0)"]
|
||||
|
||||
Loading…
Reference in New Issue
Block a user