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