Compare commits

..

No commits in common. "234f6fcdbb0437e46ed5543d4b8fe77b8202950b" and "829172177c76aae8a9e4f38f11f4a35a80a3923a" have entirely different histories.

5 changed files with 461 additions and 343 deletions

View File

@ -10,6 +10,7 @@ from PyQt6.QtWidgets import (
QListWidgetItem, QListWidgetItem,
QMainWindow, QMainWindow,
QTableWidgetItem, QTableWidgetItem,
QWidget,
) )
# Third party imports # Third party imports

75
app/musicmuster.py Normal file → Executable file
View File

@ -472,7 +472,7 @@ class Window(QMainWindow, Ui_MainWindow):
self.actionPaste.triggered.connect(self.paste_rows) self.actionPaste.triggered.connect(self.paste_rows)
self.actionPlay_next.triggered.connect(self.play_next) self.actionPlay_next.triggered.connect(self.play_next)
self.actionRenamePlaylist.triggered.connect(self.rename_playlist) self.actionRenamePlaylist.triggered.connect(self.rename_playlist)
self.actionReplace_files.triggered.connect(self.import_files) self.actionReplace_files.triggered.connect(self.replace_files)
self.actionResume.triggered.connect(self.resume) self.actionResume.triggered.connect(self.resume)
self.actionSave_as_template.triggered.connect(self.save_as_template) self.actionSave_as_template.triggered.connect(self.save_as_template)
self.actionSearch_title_in_Songfacts.triggered.connect( self.actionSearch_title_in_Songfacts.triggered.connect(
@ -1123,7 +1123,34 @@ class Window(QMainWindow, Ui_MainWindow):
return return
# Check for inadvertent press of 'return' # Check for inadvertent press of 'return'
if self.return_pressed_in_error(): if track_sequence.current and self.catch_return_key:
# Suppress inadvertent double press
if (
track_sequence.current
and track_sequence.current.start_time
and track_sequence.current.start_time
+ dt.timedelta(milliseconds=Config.RETURN_KEY_DEBOUNCE_MS)
> dt.datetime.now()
):
return
# If return is pressed during first PLAY_NEXT_GUARD_MS then
# default to NOT playing the next track, else default to
# playing it.
default_yes: bool = track_sequence.current.start_time is not None and (
(dt.datetime.now() - track_sequence.current.start_time).total_seconds()
* 1000
> Config.PLAY_NEXT_GUARD_MS
)
if default_yes:
msg = "Hit return to play next track now"
else:
msg = "Press tab to select Yes and hit return to play next track"
if not helpers.ask_yes_no(
"Play next track",
msg,
default_yes=default_yes,
parent=self,
):
return return
# Issue #223 concerns a very short pause (maybe 0.1s) sometimes # Issue #223 concerns a very short pause (maybe 0.1s) sometimes
@ -1290,7 +1317,7 @@ class Window(QMainWindow, Ui_MainWindow):
self.tabBar.setTabText(idx, new_name) self.tabBar.setTabText(idx, new_name)
session.commit() session.commit()
def import_files(self) -> None: def replace_files(self) -> None:
""" """
Scan source directory and offer to replace existing files with "similar" Scan source directory and offer to replace existing files with "similar"
files, or import the source file as a new track. files, or import the source file as a new track.
@ -1359,46 +1386,6 @@ class Window(QMainWindow, Ui_MainWindow):
session.rollback() session.rollback()
session.close() session.close()
def return_pressed_in_error(self) -> bool:
"""
Check whether Return key has been pressed in error.
Return True if it has, False if not
"""
if track_sequence.current and self.catch_return_key:
# Suppress inadvertent double press
if (
track_sequence.current
and track_sequence.current.start_time
and track_sequence.current.start_time
+ dt.timedelta(milliseconds=Config.RETURN_KEY_DEBOUNCE_MS)
> dt.datetime.now()
):
return True
# If return is pressed during first PLAY_NEXT_GUARD_MS then
# default to NOT playing the next track, else default to
# playing it.
default_yes: bool = track_sequence.current.start_time is not None and (
(dt.datetime.now() - track_sequence.current.start_time).total_seconds()
* 1000
> Config.PLAY_NEXT_GUARD_MS
)
if default_yes:
msg = "Hit return to play next track now"
else:
msg = "Press tab to select Yes and hit return to play next track"
if not helpers.ask_yes_no(
"Play next track",
msg,
default_yes=default_yes,
parent=self,
):
return True
return False
def resume(self) -> None: def resume(self) -> None:
""" """
Resume playing last track. We may be playing the next track Resume playing last track. We may be playing the next track
@ -1666,7 +1653,7 @@ class Window(QMainWindow, Ui_MainWindow):
self.label_intro_timer.setStyleSheet("") self.label_intro_timer.setStyleSheet("")
self.label_intro_timer.setText("0.0") self.label_intro_timer.setText("0.0")
except AttributeError: except AttributeError:
# current track ended during servicing tick # currnent track ended during servicing tick
pass pass
# Ensure preview button is reset if preview finishes playing # Ensure preview button is reset if preview finishes playing

View File

@ -4,7 +4,7 @@ from __future__ import annotations
from operator import attrgetter from operator import attrgetter
from random import shuffle from random import shuffle
from typing import Optional from typing import List, Optional
import datetime as dt import datetime as dt
import re import re
@ -94,39 +94,6 @@ class _PlaylistRowData:
f"note='{self.note}', title='{self.title}', artist='{self.artist}'>" f"note='{self.note}', title='{self.title}', artist='{self.artist}'>"
) )
def set_start(
self, modified_rows: list[int], start: Optional[dt.datetime]
) -> Optional[dt.datetime]:
"""
Set start time for this row
Update passed modified rows list if we changed the row.
Return new start time
"""
changed = False
if self.start_time != start:
self.start_time = start
changed = True
if start is None:
if self.end_time is not None:
self.end_time = None
changed = True
new_start_time = None
else:
end_time = start + dt.timedelta(milliseconds=self.duration)
new_start_time = end_time
if self.end_time != end_time:
self.end_time = end_time
changed = True
if changed and self.plr_rownum not in modified_rows:
modified_rows.append(self.plr_rownum)
return new_start_time
class PlaylistModel(QAbstractTableModel): class PlaylistModel(QAbstractTableModel):
""" """
@ -343,9 +310,7 @@ class PlaylistModel(QAbstractTableModel):
session.commit() session.commit()
def data( def data(self, index: QModelIndex, role: int = Qt.ItemDataRole.DisplayRole) -> QVariant:
self, index: QModelIndex, role: int = Qt.ItemDataRole.DisplayRole
) -> QVariant:
"""Return data to view""" """Return data to view"""
if not index.isValid() or not (0 <= index.row() < len(self.playlist_rows)): if not index.isValid() or not (0 <= index.row() < len(self.playlist_rows)):
@ -384,7 +349,7 @@ class PlaylistModel(QAbstractTableModel):
# Fall through to no-op # Fall through to no-op
return QVariant() return QVariant()
def delete_rows(self, row_numbers: list[int]) -> None: def delete_rows(self, row_numbers: List[int]) -> None:
""" """
Delete passed rows from model Delete passed rows from model
@ -541,7 +506,7 @@ class PlaylistModel(QAbstractTableModel):
return QVariant(boldfont) return QVariant(boldfont)
def get_duplicate_rows(self) -> list[int]: def get_duplicate_rows(self) -> List[int]:
""" """
Return a list of duplicate rows. If track appears in rows 2, 3 and 4, return [3, 4] Return a list of duplicate rows. If track appears in rows 2, 3 and 4, return [3, 4]
(ie, ignore the first, not-yet-duplicate, track). (ie, ignore the first, not-yet-duplicate, track).
@ -607,7 +572,7 @@ class PlaylistModel(QAbstractTableModel):
return self.playlist_rows[row_number].path return self.playlist_rows[row_number].path
def get_rows_duration(self, row_numbers: list[int]) -> int: def get_rows_duration(self, row_numbers: List[int]) -> int:
""" """
Return the total duration of the passed rows Return the total duration of the passed rows
""" """
@ -618,7 +583,7 @@ class PlaylistModel(QAbstractTableModel):
return duration return duration
def get_unplayed_rows(self) -> list[int]: def get_unplayed_rows(self) -> List[int]:
""" """
Return a list of unplayed row numbers Return a list of unplayed row numbers
""" """
@ -641,22 +606,28 @@ class PlaylistModel(QAbstractTableModel):
Return text for headers Return text for headers
""" """
display_dispatch_table = {
Col.START_GAP.value: QVariant(Config.HEADER_START_GAP),
Col.INTRO.value: QVariant(Config.HEADER_INTRO),
Col.TITLE.value: QVariant(Config.HEADER_TITLE),
Col.ARTIST.value: QVariant(Config.HEADER_ARTIST),
Col.DURATION.value: QVariant(Config.HEADER_DURATION),
Col.START_TIME.value: QVariant(Config.HEADER_START_TIME),
Col.END_TIME.value: QVariant(Config.HEADER_END_TIME),
Col.LAST_PLAYED.value: QVariant(Config.HEADER_LAST_PLAYED),
Col.BITRATE.value: QVariant(Config.HEADER_BITRATE),
Col.NOTE.value: QVariant(Config.HEADER_NOTE),
}
if role == Qt.ItemDataRole.DisplayRole: if role == Qt.ItemDataRole.DisplayRole:
if orientation == Qt.Orientation.Horizontal: if orientation == Qt.Orientation.Horizontal:
return display_dispatch_table[section] if section == Col.START_GAP.value:
return QVariant(Config.HEADER_START_GAP)
if section == Col.INTRO.value:
return QVariant(Config.HEADER_INTRO)
elif section == Col.TITLE.value:
return QVariant(Config.HEADER_TITLE)
elif section == Col.ARTIST.value:
return QVariant(Config.HEADER_ARTIST)
elif section == Col.DURATION.value:
return QVariant(Config.HEADER_DURATION)
elif section == Col.START_TIME.value:
return QVariant(Config.HEADER_START_TIME)
elif section == Col.END_TIME.value:
return QVariant(Config.HEADER_END_TIME)
elif section == Col.LAST_PLAYED.value:
return QVariant(Config.HEADER_LAST_PLAYED)
elif section == Col.BITRATE.value:
return QVariant(Config.HEADER_BITRATE)
elif section == Col.NOTE.value:
return QVariant(Config.HEADER_NOTE)
else: else:
if Config.ROWS_FROM_ZERO: if Config.ROWS_FROM_ZERO:
return QVariant(str(section)) return QVariant(str(section))
@ -675,15 +646,84 @@ class PlaylistModel(QAbstractTableModel):
Process possible section timing directives embeded in header Process possible section timing directives embeded in header
""" """
if prd.note.endswith("+"): count: int = 0
return self.start_of_timed_section_header(prd) unplayed_count: int = 0
duration: int = 0
if prd.note.endswith("+"):
# This header is the start of a timed section
for row_number in range(prd.plr_rownum + 1, len(self.playlist_rows)):
row_prd = self.playlist_rows[row_number]
if self.is_header_row(row_number):
if row_prd.note.endswith("-"):
return (
f"{prd.note[:-1].strip()} "
f"[{count} tracks, {ms_to_mmss(duration)} unplayed]"
)
else:
continue
else:
count += 1
if not row_prd.played:
unplayed_count += 1
duration += row_prd.duration
return (
f"{prd.note[:-1].strip()} "
f"[{count} tracks, {ms_to_mmss(duration, none='none')} "
"unplayed (to end of playlist)]"
)
elif prd.note.endswith("="): elif prd.note.endswith("="):
return self.section_subtotal_header(prd) # Show subtotal
for row_number in range(prd.plr_rownum - 1, -1, -1):
row_prd = self.playlist_rows[row_number]
if self.is_header_row(row_number):
if row_prd.note.endswith("-"):
# There was no start of section
return prd.note
if row_prd.note.endswith(("+", "=")):
# If we are playing this section, also
# calculate end time if all tracks are played.
end_time_str = ""
if (
track_sequence.current
and track_sequence.current.end_time
and (
row_number
< track_sequence.current.row_number
< prd.plr_rownum
)
):
section_end_time = (
track_sequence.current.end_time
+ dt.timedelta(milliseconds=duration)
)
end_time_str = (
", section end time "
+ section_end_time.strftime(Config.TRACK_TIME_FORMAT)
)
stripped_note = prd.note[:-1].strip()
if stripped_note:
return (
f"{stripped_note} ["
f"{unplayed_count}/{count} track{'s' if count > 1 else ''} "
f"({ms_to_mmss(duration)}) unplayed{end_time_str}]"
)
else:
return (
f"[{unplayed_count}/{count} track{'s' if count > 1 else ''} "
f"({ms_to_mmss(duration)}) unplayed{end_time_str}]"
)
else:
continue
else:
count += 1
if not row_prd.played:
unplayed_count += 1
duration += row_prd.duration
elif prd.note == "-": elif prd.note == "-":
# If the hyphen is the only thing on the line, echo the note # If the hyphen is the only thing on the line, echo the note
# that started the section without the trailing "+". # tha started the section without the trailing "+".
for row_number in range(prd.plr_rownum - 1, -1, -1): for row_number in range(prd.plr_rownum - 1, -1, -1):
row_prd = self.playlist_rows[row_number] row_prd = self.playlist_rows[row_number]
if self.is_header_row(row_number): if self.is_header_row(row_number):
@ -748,7 +788,7 @@ class PlaylistModel(QAbstractTableModel):
self.index(modified_row, self.columnCount() - 1), self.index(modified_row, self.columnCount() - 1),
) )
def invalidate_rows(self, modified_rows: list[int]) -> None: def invalidate_rows(self, modified_rows: List[int]) -> None:
""" """
Signal to view to refresh invlidated rows Signal to view to refresh invlidated rows
""" """
@ -784,7 +824,7 @@ class PlaylistModel(QAbstractTableModel):
return None return None
def mark_unplayed(self, row_numbers: list[int]) -> None: def mark_unplayed(self, row_numbers: List[int]) -> None:
""" """
Mark row as unplayed Mark row as unplayed
""" """
@ -798,10 +838,9 @@ class PlaylistModel(QAbstractTableModel):
session.commit() session.commit()
self.refresh_row(session, row_number) self.refresh_row(session, row_number)
self.update_track_times()
self.invalidate_rows(row_numbers) self.invalidate_rows(row_numbers)
def move_rows(self, from_rows: list[int], to_row_number: int) -> None: def move_rows(self, from_rows: List[int], to_row_number: int) -> None:
""" """
Move the playlist rows given to to_row and below. Move the playlist rows given to to_row and below.
""" """
@ -862,7 +901,7 @@ class PlaylistModel(QAbstractTableModel):
# For SQLAlchemy, build a list of dictionaries that map plrid to # For SQLAlchemy, build a list of dictionaries that map plrid to
# new row number: # new row number:
sqla_map: list[dict[str, int]] = [] sqla_map: List[dict[str, int]] = []
for oldrow, newrow in row_map.items(): for oldrow, newrow in row_map.items():
plrid = self.playlist_rows[oldrow].plrid plrid = self.playlist_rows[oldrow].plrid
sqla_map.append({"plrid": plrid, "plr_rownum": newrow}) sqla_map.append({"plrid": plrid, "plr_rownum": newrow})
@ -875,11 +914,10 @@ class PlaylistModel(QAbstractTableModel):
# Update display # Update display
self.reset_track_sequence_row_numbers() self.reset_track_sequence_row_numbers()
self.update_track_times()
self.invalidate_rows(list(row_map.keys())) self.invalidate_rows(list(row_map.keys()))
def move_rows_between_playlists( def move_rows_between_playlists(
self, from_rows: list[int], to_row_number: int, to_playlist_id: int self, from_rows: List[int], to_row_number: int, to_playlist_id: int
) -> None: ) -> None:
""" """
Move the playlist rows given to to_row and below of to_playlist. Move the playlist rows given to to_row and below of to_playlist.
@ -1117,8 +1155,8 @@ class PlaylistModel(QAbstractTableModel):
self.update_track_times() self.update_track_times()
def _reversed_contiguous_row_groups( def _reversed_contiguous_row_groups(
self, row_numbers: list[int] self, row_numbers: List[int]
) -> list[list[int]]: ) -> List[List[int]]:
""" """
Take the list of row numbers and split into groups of contiguous rows. Return as a list Take the list of row numbers and split into groups of contiguous rows. Return as a list
of lists with the highest row numbers first. of lists with the highest row numbers first.
@ -1130,8 +1168,8 @@ class PlaylistModel(QAbstractTableModel):
log.debug(f"_reversed_contiguous_row_groups({row_numbers=} called") log.debug(f"_reversed_contiguous_row_groups({row_numbers=} called")
result: list[list[int]] = [] result: List[List[int]] = []
temp: list[int] = [] temp: List[int] = []
last_value = row_numbers[0] - 1 last_value = row_numbers[0] - 1
for idx in range(len(row_numbers)): for idx in range(len(row_numbers)):
@ -1152,68 +1190,7 @@ class PlaylistModel(QAbstractTableModel):
return len(self.playlist_rows) return len(self.playlist_rows)
def section_subtotal_header(self, prd: _PlaylistRowData) -> str: def selection_is_sortable(self, row_numbers: List[int]) -> bool:
"""
Process this row as subtotal within a timed section and
return display text for this row
"""
count: int = 0
unplayed_count: int = 0
duration: int = 0
# Show subtotal
for row_number in range(prd.plr_rownum - 1, -1, -1):
row_prd = self.playlist_rows[row_number]
if self.is_header_row(row_number):
if row_prd.note.endswith("-"):
# There was no start of section
return prd.note
if row_prd.note.endswith(("+", "=")):
# If we are playing this section, also
# calculate end time if all tracks are played.
end_time_str = ""
if (
track_sequence.current
and track_sequence.current.end_time
and (
row_number
< track_sequence.current.row_number
< prd.plr_rownum
)
):
section_end_time = (
track_sequence.current.end_time
+ dt.timedelta(milliseconds=duration)
)
end_time_str = (
", section end time "
+ section_end_time.strftime(Config.TRACK_TIME_FORMAT)
)
stripped_note = prd.note[:-1].strip()
if stripped_note:
return (
f"{stripped_note} ["
f"{unplayed_count}/{count} track{'s' if count > 1 else ''} "
f"({ms_to_mmss(duration)}) unplayed{end_time_str}]"
)
else:
return (
f"[{unplayed_count}/{count} track{'s' if count > 1 else ''} "
f"({ms_to_mmss(duration)}) unplayed{end_time_str}]"
)
else:
continue
else:
count += 1
if not row_prd.played:
unplayed_count += 1
duration += row_prd.duration
# Should never get here
return f"Error calculating subtotal ({row_prd.note})"
def selection_is_sortable(self, row_numbers: list[int]) -> bool:
""" """
Return True if the selection is sortable. That means: Return True if the selection is sortable. That means:
- at least two rows selected - at least two rows selected
@ -1353,14 +1330,14 @@ class PlaylistModel(QAbstractTableModel):
return False return False
def sort_by_artist(self, row_numbers: list[int]) -> None: def sort_by_artist(self, row_numbers: List[int]) -> None:
""" """
Sort selected rows by artist Sort selected rows by artist
""" """
self.sort_by_attribute(row_numbers, "artist") self.sort_by_attribute(row_numbers, "artist")
def sort_by_attribute(self, row_numbers: list[int], attr_name: str) -> None: def sort_by_attribute(self, row_numbers: List[int], attr_name: str) -> None:
""" """
Sort selected rows by passed attribute name where 'attribute' is a Sort selected rows by passed attribute name where 'attribute' is a
key in PlaylistRowData key in PlaylistRowData
@ -1375,21 +1352,21 @@ class PlaylistModel(QAbstractTableModel):
] ]
self.move_rows(sorted_list, min(sorted_list)) self.move_rows(sorted_list, min(sorted_list))
def sort_by_duration(self, row_numbers: list[int]) -> None: def sort_by_duration(self, row_numbers: List[int]) -> None:
""" """
Sort selected rows by duration Sort selected rows by duration
""" """
self.sort_by_attribute(row_numbers, "duration") self.sort_by_attribute(row_numbers, "duration")
def sort_by_lastplayed(self, row_numbers: list[int]) -> None: def sort_by_lastplayed(self, row_numbers: List[int]) -> None:
""" """
Sort selected rows by lastplayed Sort selected rows by lastplayed
""" """
self.sort_by_attribute(row_numbers, "lastplayed") self.sort_by_attribute(row_numbers, "lastplayed")
def sort_randomly(self, row_numbers: list[int]) -> None: def sort_randomly(self, row_numbers: List[int]) -> None:
""" """
Sort selected rows randomly Sort selected rows randomly
""" """
@ -1397,44 +1374,13 @@ class PlaylistModel(QAbstractTableModel):
shuffle(row_numbers) shuffle(row_numbers)
self.move_rows(row_numbers, min(row_numbers)) self.move_rows(row_numbers, min(row_numbers))
def sort_by_title(self, row_numbers: list[int]) -> None: def sort_by_title(self, row_numbers: List[int]) -> None:
""" """
Sort selected rows by title Sort selected rows by title
""" """
self.sort_by_attribute(row_numbers, "title") self.sort_by_attribute(row_numbers, "title")
def start_of_timed_section_header(self, prd: _PlaylistRowData) -> str:
"""
Process this row as the start of a timed section and
return display text for this row
"""
count: int = 0
unplayed_count: int = 0
duration: int = 0
for row_number in range(prd.plr_rownum + 1, len(self.playlist_rows)):
row_prd = self.playlist_rows[row_number]
if self.is_header_row(row_number):
if row_prd.note.endswith("-"):
return (
f"{prd.note[:-1].strip()} "
f"[{count} tracks, {ms_to_mmss(duration)} unplayed]"
)
else:
continue
else:
count += 1
if not row_prd.played:
unplayed_count += 1
duration += row_prd.duration
return (
f"{prd.note[:-1].strip()} "
f"[{count} tracks, {ms_to_mmss(duration, none='none')} "
"unplayed (to end of playlist)]"
)
def supportedDropActions(self) -> Qt.DropAction: def supportedDropActions(self) -> Qt.DropAction:
return Qt.DropAction.MoveAction | Qt.DropAction.CopyAction return Qt.DropAction.MoveAction | Qt.DropAction.CopyAction
@ -1467,33 +1413,51 @@ class PlaylistModel(QAbstractTableModel):
log.debug("update_track_times()") log.debug("update_track_times()")
next_start_time: Optional[dt.datetime] = None next_start_time: Optional[dt.datetime] = None
update_rows: list[int] = [] update_rows: List[int] = []
row_count = len(self.playlist_rows) playlist_length = len(self.playlist_rows)
if not playlist_length:
return
current_track_row = None for row_number in range(playlist_length):
next_track_row = None
if track_sequence.current:
current_track_row = track_sequence.current.row_number
# Update current track details now so that they are available
# when we deal with next track row which may be above current
# track row.
self.playlist_rows[current_track_row].set_start(
update_rows, track_sequence.current.start_time
)
if track_sequence.next:
next_track_row = track_sequence.next.row_number
for row_number in range(row_count):
prd = self.playlist_rows[row_number] prd = self.playlist_rows[row_number]
# Don't update times for tracks that have been played, for # Reset start_time if this is the current row
# unreadable tracks or for the current track, handled above. if track_sequence.current:
if row_number == track_sequence.current.row_number:
prd.start_time = track_sequence.current.start_time
prd.end_time = track_sequence.current.end_time
update_rows.append(row_number)
if not next_start_time:
next_start_time = prd.end_time
continue
# Set start time for next row if we have a current track
if track_sequence.next and track_sequence.current.end_time:
if row_number == track_sequence.next.row_number:
prd.start_time = track_sequence.current.end_time
prd.end_time = prd.start_time + dt.timedelta(
milliseconds=prd.duration
)
next_start_time = prd.end_time
update_rows.append(row_number)
continue
# Don't update times for tracks that have been played
if prd.played:
continue
# If we're between the current and next row, zero out
# times
if ( if (
prd.played track_sequence.current
or row_number == current_track_row and track_sequence.next
or (prd.path and file_is_unreadable(prd.path)) and track_sequence.current.row_number
< row_number
< track_sequence.next.row_number
): ):
prd.start_time = None
prd.end_time = None
update_rows.append(row_number)
continue continue
# Reset start time if timing in header # Reset start time if timing in header
@ -1503,25 +1467,28 @@ class PlaylistModel(QAbstractTableModel):
next_start_time = header_time next_start_time = header_time
continue continue
# Set start time for next row if we have a current track # This is an unplayed track
if ( # Don't schedule unplayable tracks
row_number == next_track_row if file_is_unreadable(prd.path):
and track_sequence.current
and track_sequence.current.end_time
):
next_start_time = prd.set_start(
update_rows, track_sequence.current.end_time
)
continue continue
# If we're between the current and next row, zero out # Set start/end if we have a start time
# times if next_start_time is None:
if (current_track_row or row_count) < row_number < (next_track_row or 0):
prd.set_start(update_rows, None)
continue continue
# Set start/end # Update start time of this row if it's incorrect
next_start_time = prd.set_start(update_rows, next_start_time) if prd.start_time != next_start_time:
prd.start_time = next_start_time
update_rows.append(row_number)
# Calculate next start time
next_start_time += dt.timedelta(milliseconds=prd.duration)
# Update end time of this row if it's incorrect
if prd.end_time != next_start_time:
prd.end_time = next_start_time
if row_number not in update_rows:
update_rows.append(row_number)
# Update start/stop times of rows that have changed # Update start/stop times of rows that have changed
for updated_row in update_rows: for updated_row in update_rows:
@ -1562,8 +1529,7 @@ class PlaylistProxyModel(QSortFilterProxyModel):
# Don't hide current track # Don't hide current track
if ( if (
track_sequence.current track_sequence.current
and track_sequence.current.playlist_id and track_sequence.current.playlist_id == self.source_model.playlist_id
== self.source_model.playlist_id
and track_sequence.current.row_number == source_row and track_sequence.current.row_number == source_row
): ):
return True return True
@ -1579,8 +1545,7 @@ class PlaylistProxyModel(QSortFilterProxyModel):
# Handle previous track # Handle previous track
if track_sequence.previous: if track_sequence.previous:
if ( if (
track_sequence.previous.playlist_id track_sequence.previous.playlist_id != self.source_model.playlist_id
!= self.source_model.playlist_id
or track_sequence.previous.row_number != source_row or track_sequence.previous.row_number != source_row
): ):
# This row isn't our previous track: hide it # This row isn't our previous track: hide it
@ -1589,10 +1554,11 @@ class PlaylistProxyModel(QSortFilterProxyModel):
# This row is our previous track. Don't hide it # This row is our previous track. Don't hide it
# until HIDE_AFTER_PLAYING_OFFSET milliseconds # until HIDE_AFTER_PLAYING_OFFSET milliseconds
# after current track has started # after current track has started
if track_sequence.current.start_time and dt.datetime.now() > ( if (
track_sequence.current.start_time track_sequence.current.start_time
+ dt.timedelta( and dt.datetime.now() > (
milliseconds=Config.HIDE_AFTER_PLAYING_OFFSET track_sequence.current.start_time
+ dt.timedelta(milliseconds=Config.HIDE_AFTER_PLAYING_OFFSET)
) )
): ):
return False return False
@ -1604,7 +1570,9 @@ class PlaylistProxyModel(QSortFilterProxyModel):
# true next time through. # true next time through.
QTimer.singleShot( QTimer.singleShot(
Config.HIDE_AFTER_PLAYING_OFFSET + 100, Config.HIDE_AFTER_PLAYING_OFFSET + 100,
lambda: self.source_model.invalidate_row(source_row), lambda: self.source_model.invalidate_row(
source_row
),
) )
return True return True
# Next track not playing yet so don't hide previous # Next track not playing yet so don't hide previous
@ -1634,13 +1602,13 @@ class PlaylistProxyModel(QSortFilterProxyModel):
def current_track_started(self): def current_track_started(self):
return self.source_model.current_track_started() return self.source_model.current_track_started()
def delete_rows(self, row_numbers: list[int]) -> None: def delete_rows(self, row_numbers: List[int]) -> None:
return self.source_model.delete_rows(row_numbers) return self.source_model.delete_rows(row_numbers)
def get_duplicate_rows(self) -> list[int]: def get_duplicate_rows(self) -> List[int]:
return self.source_model.get_duplicate_rows() return self.source_model.get_duplicate_rows()
def get_rows_duration(self, row_numbers: list[int]) -> int: def get_rows_duration(self, row_numbers: List[int]) -> int:
return self.source_model.get_rows_duration(row_numbers) return self.source_model.get_rows_duration(row_numbers)
def get_row_info(self, row_number: int) -> _PlaylistRowData: def get_row_info(self, row_number: int) -> _PlaylistRowData:
@ -1649,7 +1617,7 @@ class PlaylistProxyModel(QSortFilterProxyModel):
def get_row_track_path(self, row_number: int) -> str: def get_row_track_path(self, row_number: int) -> str:
return self.source_model.get_row_track_path(row_number) return self.source_model.get_row_track_path(row_number)
def get_unplayed_rows(self) -> list[int]: def get_unplayed_rows(self) -> List[int]:
return self.source_model.get_unplayed_rows() return self.source_model.get_unplayed_rows()
def hide_played_tracks(self, hide: bool) -> None: def hide_played_tracks(self, hide: bool) -> None:
@ -1672,14 +1640,14 @@ class PlaylistProxyModel(QSortFilterProxyModel):
def is_track_in_playlist(self, track_id: int) -> Optional[_PlaylistRowData]: def is_track_in_playlist(self, track_id: int) -> Optional[_PlaylistRowData]:
return self.source_model.is_track_in_playlist(track_id) return self.source_model.is_track_in_playlist(track_id)
def mark_unplayed(self, row_numbers: list[int]) -> None: def mark_unplayed(self, row_numbers: List[int]) -> None:
return self.source_model.mark_unplayed(row_numbers) return self.source_model.mark_unplayed(row_numbers)
def move_rows(self, from_rows: list[int], to_row_number: int) -> None: def move_rows(self, from_rows: List[int], to_row_number: int) -> None:
return self.source_model.move_rows(from_rows, to_row_number) return self.source_model.move_rows(from_rows, to_row_number)
def move_rows_between_playlists( def move_rows_between_playlists(
self, from_rows: list[int], to_row_number: int, to_playlist_id: int self, from_rows: List[int], to_row_number: int, to_playlist_id: int
) -> None: ) -> None:
return self.source_model.move_rows_between_playlists( return self.source_model.move_rows_between_playlists(
from_rows, to_row_number, to_playlist_id from_rows, to_row_number, to_playlist_id
@ -1712,19 +1680,19 @@ class PlaylistProxyModel(QSortFilterProxyModel):
def set_next_row(self, row_number: Optional[int]) -> bool: def set_next_row(self, row_number: Optional[int]) -> bool:
return self.source_model.set_next_row(row_number) return self.source_model.set_next_row(row_number)
def sort_by_artist(self, row_numbers: list[int]) -> None: def sort_by_artist(self, row_numbers: List[int]) -> None:
return self.source_model.sort_by_artist(row_numbers) return self.source_model.sort_by_artist(row_numbers)
def sort_by_duration(self, row_numbers: list[int]) -> None: def sort_by_duration(self, row_numbers: List[int]) -> None:
return self.source_model.sort_by_duration(row_numbers) return self.source_model.sort_by_duration(row_numbers)
def sort_by_lastplayed(self, row_numbers: list[int]) -> None: def sort_by_lastplayed(self, row_numbers: List[int]) -> None:
return self.source_model.sort_by_lastplayed(row_numbers) return self.source_model.sort_by_lastplayed(row_numbers)
def sort_randomly(self, row_numbers: list[int]) -> None: def sort_randomly(self, row_numbers: List[int]) -> None:
return self.source_model.sort_randomly(row_numbers) return self.source_model.sort_randomly(row_numbers)
def sort_by_title(self, row_numbers: list[int]) -> None: def sort_by_title(self, row_numbers: List[int]) -> None:
return self.source_model.sort_by_title(row_numbers) return self.source_model.sort_by_title(row_numbers)
def update_track_times(self) -> None: def update_track_times(self) -> None:

View File

@ -1367,7 +1367,7 @@ padding-left: 8px;</string>
</action> </action>
<action name="actionReplace_files"> <action name="actionReplace_files">
<property name="text"> <property name="text">
<string>Import files...</string> <string>Replace files...</string>
</property> </property>
</action> </action>
</widget> </widget>

View File

@ -15,7 +15,11 @@ class Ui_MainWindow(object):
MainWindow.resize(1280, 857) MainWindow.resize(1280, 857)
MainWindow.setMinimumSize(QtCore.QSize(1280, 0)) MainWindow.setMinimumSize(QtCore.QSize(1280, 0))
icon = QtGui.QIcon() icon = QtGui.QIcon()
icon.addPixmap(QtGui.QPixmap(":/icons/musicmuster"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off) icon.addPixmap(
QtGui.QPixmap(":/icons/musicmuster"),
QtGui.QIcon.Mode.Normal,
QtGui.QIcon.State.Off,
)
MainWindow.setWindowIcon(icon) MainWindow.setWindowIcon(icon)
MainWindow.setStyleSheet("") MainWindow.setStyleSheet("")
self.centralwidget = QtWidgets.QWidget(parent=MainWindow) self.centralwidget = QtWidgets.QWidget(parent=MainWindow)
@ -27,39 +31,62 @@ class Ui_MainWindow(object):
self.verticalLayout_3 = QtWidgets.QVBoxLayout() self.verticalLayout_3 = QtWidgets.QVBoxLayout()
self.verticalLayout_3.setObjectName("verticalLayout_3") self.verticalLayout_3.setObjectName("verticalLayout_3")
self.previous_track_2 = QtWidgets.QLabel(parent=self.centralwidget) self.previous_track_2 = QtWidgets.QLabel(parent=self.centralwidget)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Preferred, QtWidgets.QSizePolicy.Policy.Preferred) sizePolicy = QtWidgets.QSizePolicy(
QtWidgets.QSizePolicy.Policy.Preferred,
QtWidgets.QSizePolicy.Policy.Preferred,
)
sizePolicy.setHorizontalStretch(0) sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0) sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.previous_track_2.sizePolicy().hasHeightForWidth()) sizePolicy.setHeightForWidth(
self.previous_track_2.sizePolicy().hasHeightForWidth()
)
self.previous_track_2.setSizePolicy(sizePolicy) self.previous_track_2.setSizePolicy(sizePolicy)
self.previous_track_2.setMaximumSize(QtCore.QSize(230, 16777215)) self.previous_track_2.setMaximumSize(QtCore.QSize(230, 16777215))
font = QtGui.QFont() font = QtGui.QFont()
font.setFamily("Sans") font.setFamily("Sans")
font.setPointSize(20) font.setPointSize(20)
self.previous_track_2.setFont(font) self.previous_track_2.setFont(font)
self.previous_track_2.setStyleSheet("background-color: #f8d7da;\n" self.previous_track_2.setStyleSheet(
"border: 1px solid rgb(85, 87, 83);") "background-color: #f8d7da;\n" "border: 1px solid rgb(85, 87, 83);"
self.previous_track_2.setAlignment(QtCore.Qt.AlignmentFlag.AlignRight|QtCore.Qt.AlignmentFlag.AlignTrailing|QtCore.Qt.AlignmentFlag.AlignVCenter) )
self.previous_track_2.setAlignment(
QtCore.Qt.AlignmentFlag.AlignRight
| QtCore.Qt.AlignmentFlag.AlignTrailing
| QtCore.Qt.AlignmentFlag.AlignVCenter
)
self.previous_track_2.setObjectName("previous_track_2") self.previous_track_2.setObjectName("previous_track_2")
self.verticalLayout_3.addWidget(self.previous_track_2) self.verticalLayout_3.addWidget(self.previous_track_2)
self.current_track_2 = QtWidgets.QLabel(parent=self.centralwidget) self.current_track_2 = QtWidgets.QLabel(parent=self.centralwidget)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Preferred, QtWidgets.QSizePolicy.Policy.Preferred) sizePolicy = QtWidgets.QSizePolicy(
QtWidgets.QSizePolicy.Policy.Preferred,
QtWidgets.QSizePolicy.Policy.Preferred,
)
sizePolicy.setHorizontalStretch(0) sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0) sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.current_track_2.sizePolicy().hasHeightForWidth()) sizePolicy.setHeightForWidth(
self.current_track_2.sizePolicy().hasHeightForWidth()
)
self.current_track_2.setSizePolicy(sizePolicy) self.current_track_2.setSizePolicy(sizePolicy)
self.current_track_2.setMaximumSize(QtCore.QSize(230, 16777215)) self.current_track_2.setMaximumSize(QtCore.QSize(230, 16777215))
font = QtGui.QFont() font = QtGui.QFont()
font.setFamily("Sans") font.setFamily("Sans")
font.setPointSize(20) font.setPointSize(20)
self.current_track_2.setFont(font) self.current_track_2.setFont(font)
self.current_track_2.setStyleSheet("background-color: #d4edda;\n" self.current_track_2.setStyleSheet(
"border: 1px solid rgb(85, 87, 83);") "background-color: #d4edda;\n" "border: 1px solid rgb(85, 87, 83);"
self.current_track_2.setAlignment(QtCore.Qt.AlignmentFlag.AlignRight|QtCore.Qt.AlignmentFlag.AlignTrailing|QtCore.Qt.AlignmentFlag.AlignVCenter) )
self.current_track_2.setAlignment(
QtCore.Qt.AlignmentFlag.AlignRight
| QtCore.Qt.AlignmentFlag.AlignTrailing
| QtCore.Qt.AlignmentFlag.AlignVCenter
)
self.current_track_2.setObjectName("current_track_2") self.current_track_2.setObjectName("current_track_2")
self.verticalLayout_3.addWidget(self.current_track_2) self.verticalLayout_3.addWidget(self.current_track_2)
self.next_track_2 = QtWidgets.QLabel(parent=self.centralwidget) self.next_track_2 = QtWidgets.QLabel(parent=self.centralwidget)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Preferred, QtWidgets.QSizePolicy.Policy.Preferred) sizePolicy = QtWidgets.QSizePolicy(
QtWidgets.QSizePolicy.Policy.Preferred,
QtWidgets.QSizePolicy.Policy.Preferred,
)
sizePolicy.setHorizontalStretch(0) sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0) sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.next_track_2.sizePolicy().hasHeightForWidth()) sizePolicy.setHeightForWidth(self.next_track_2.sizePolicy().hasHeightForWidth())
@ -69,19 +96,29 @@ class Ui_MainWindow(object):
font.setFamily("Sans") font.setFamily("Sans")
font.setPointSize(20) font.setPointSize(20)
self.next_track_2.setFont(font) self.next_track_2.setFont(font)
self.next_track_2.setStyleSheet("background-color: #fff3cd;\n" self.next_track_2.setStyleSheet(
"border: 1px solid rgb(85, 87, 83);") "background-color: #fff3cd;\n" "border: 1px solid rgb(85, 87, 83);"
self.next_track_2.setAlignment(QtCore.Qt.AlignmentFlag.AlignRight|QtCore.Qt.AlignmentFlag.AlignTrailing|QtCore.Qt.AlignmentFlag.AlignVCenter) )
self.next_track_2.setAlignment(
QtCore.Qt.AlignmentFlag.AlignRight
| QtCore.Qt.AlignmentFlag.AlignTrailing
| QtCore.Qt.AlignmentFlag.AlignVCenter
)
self.next_track_2.setObjectName("next_track_2") self.next_track_2.setObjectName("next_track_2")
self.verticalLayout_3.addWidget(self.next_track_2) self.verticalLayout_3.addWidget(self.next_track_2)
self.horizontalLayout_3.addLayout(self.verticalLayout_3) self.horizontalLayout_3.addLayout(self.verticalLayout_3)
self.verticalLayout = QtWidgets.QVBoxLayout() self.verticalLayout = QtWidgets.QVBoxLayout()
self.verticalLayout.setObjectName("verticalLayout") self.verticalLayout.setObjectName("verticalLayout")
self.hdrPreviousTrack = QtWidgets.QLabel(parent=self.centralwidget) self.hdrPreviousTrack = QtWidgets.QLabel(parent=self.centralwidget)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Preferred, QtWidgets.QSizePolicy.Policy.Preferred) sizePolicy = QtWidgets.QSizePolicy(
QtWidgets.QSizePolicy.Policy.Preferred,
QtWidgets.QSizePolicy.Policy.Preferred,
)
sizePolicy.setHorizontalStretch(0) sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0) sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.hdrPreviousTrack.sizePolicy().hasHeightForWidth()) sizePolicy.setHeightForWidth(
self.hdrPreviousTrack.sizePolicy().hasHeightForWidth()
)
self.hdrPreviousTrack.setSizePolicy(sizePolicy) self.hdrPreviousTrack.setSizePolicy(sizePolicy)
self.hdrPreviousTrack.setMinimumSize(QtCore.QSize(0, 0)) self.hdrPreviousTrack.setMinimumSize(QtCore.QSize(0, 0))
self.hdrPreviousTrack.setMaximumSize(QtCore.QSize(16777215, 16777215)) self.hdrPreviousTrack.setMaximumSize(QtCore.QSize(16777215, 16777215))
@ -89,32 +126,43 @@ class Ui_MainWindow(object):
font.setFamily("Sans") font.setFamily("Sans")
font.setPointSize(20) font.setPointSize(20)
self.hdrPreviousTrack.setFont(font) self.hdrPreviousTrack.setFont(font)
self.hdrPreviousTrack.setStyleSheet("background-color: #f8d7da;\n" self.hdrPreviousTrack.setStyleSheet(
"border: 1px solid rgb(85, 87, 83);") "background-color: #f8d7da;\n" "border: 1px solid rgb(85, 87, 83);"
)
self.hdrPreviousTrack.setText("") self.hdrPreviousTrack.setText("")
self.hdrPreviousTrack.setWordWrap(False) self.hdrPreviousTrack.setWordWrap(False)
self.hdrPreviousTrack.setObjectName("hdrPreviousTrack") self.hdrPreviousTrack.setObjectName("hdrPreviousTrack")
self.verticalLayout.addWidget(self.hdrPreviousTrack) self.verticalLayout.addWidget(self.hdrPreviousTrack)
self.hdrCurrentTrack = QtWidgets.QPushButton(parent=self.centralwidget) self.hdrCurrentTrack = QtWidgets.QPushButton(parent=self.centralwidget)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Preferred, QtWidgets.QSizePolicy.Policy.Preferred) sizePolicy = QtWidgets.QSizePolicy(
QtWidgets.QSizePolicy.Policy.Preferred,
QtWidgets.QSizePolicy.Policy.Preferred,
)
sizePolicy.setHorizontalStretch(0) sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0) sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.hdrCurrentTrack.sizePolicy().hasHeightForWidth()) sizePolicy.setHeightForWidth(
self.hdrCurrentTrack.sizePolicy().hasHeightForWidth()
)
self.hdrCurrentTrack.setSizePolicy(sizePolicy) self.hdrCurrentTrack.setSizePolicy(sizePolicy)
font = QtGui.QFont() font = QtGui.QFont()
font.setPointSize(20) font.setPointSize(20)
self.hdrCurrentTrack.setFont(font) self.hdrCurrentTrack.setFont(font)
self.hdrCurrentTrack.setStyleSheet("background-color: #d4edda;\n" self.hdrCurrentTrack.setStyleSheet(
"background-color: #d4edda;\n"
"border: 1px solid rgb(85, 87, 83);\n" "border: 1px solid rgb(85, 87, 83);\n"
"text-align: left;\n" "text-align: left;\n"
"padding-left: 8px;\n" "padding-left: 8px;\n"
"") ""
)
self.hdrCurrentTrack.setText("") self.hdrCurrentTrack.setText("")
self.hdrCurrentTrack.setFlat(True) self.hdrCurrentTrack.setFlat(True)
self.hdrCurrentTrack.setObjectName("hdrCurrentTrack") self.hdrCurrentTrack.setObjectName("hdrCurrentTrack")
self.verticalLayout.addWidget(self.hdrCurrentTrack) self.verticalLayout.addWidget(self.hdrCurrentTrack)
self.hdrNextTrack = QtWidgets.QPushButton(parent=self.centralwidget) self.hdrNextTrack = QtWidgets.QPushButton(parent=self.centralwidget)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Preferred, QtWidgets.QSizePolicy.Policy.Preferred) sizePolicy = QtWidgets.QSizePolicy(
QtWidgets.QSizePolicy.Policy.Preferred,
QtWidgets.QSizePolicy.Policy.Preferred,
)
sizePolicy.setHorizontalStretch(0) sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0) sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.hdrNextTrack.sizePolicy().hasHeightForWidth()) sizePolicy.setHeightForWidth(self.hdrNextTrack.sizePolicy().hasHeightForWidth())
@ -122,10 +170,12 @@ class Ui_MainWindow(object):
font = QtGui.QFont() font = QtGui.QFont()
font.setPointSize(20) font.setPointSize(20)
self.hdrNextTrack.setFont(font) self.hdrNextTrack.setFont(font)
self.hdrNextTrack.setStyleSheet("background-color: #fff3cd;\n" self.hdrNextTrack.setStyleSheet(
"background-color: #fff3cd;\n"
"border: 1px solid rgb(85, 87, 83);\n" "border: 1px solid rgb(85, 87, 83);\n"
"text-align: left;\n" "text-align: left;\n"
"padding-left: 8px;") "padding-left: 8px;"
)
self.hdrNextTrack.setText("") self.hdrNextTrack.setText("")
self.hdrNextTrack.setFlat(True) self.hdrNextTrack.setFlat(True)
self.hdrNextTrack.setObjectName("hdrNextTrack") self.hdrNextTrack.setObjectName("hdrNextTrack")
@ -172,7 +222,12 @@ class Ui_MainWindow(object):
self.cartsWidget.setObjectName("cartsWidget") self.cartsWidget.setObjectName("cartsWidget")
self.horizontalLayout_Carts = QtWidgets.QHBoxLayout(self.cartsWidget) self.horizontalLayout_Carts = QtWidgets.QHBoxLayout(self.cartsWidget)
self.horizontalLayout_Carts.setObjectName("horizontalLayout_Carts") self.horizontalLayout_Carts.setObjectName("horizontalLayout_Carts")
spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Minimum) spacerItem = QtWidgets.QSpacerItem(
40,
20,
QtWidgets.QSizePolicy.Policy.Expanding,
QtWidgets.QSizePolicy.Policy.Minimum,
)
self.horizontalLayout_Carts.addItem(spacerItem) self.horizontalLayout_Carts.addItem(spacerItem)
self.gridLayout_4.addWidget(self.cartsWidget, 2, 0, 1, 1) self.gridLayout_4.addWidget(self.cartsWidget, 2, 0, 1, 1)
self.frame_6 = QtWidgets.QFrame(parent=self.centralwidget) self.frame_6 = QtWidgets.QFrame(parent=self.centralwidget)
@ -217,7 +272,11 @@ class Ui_MainWindow(object):
self.btnPreview = QtWidgets.QPushButton(parent=self.FadeStopInfoFrame) self.btnPreview = QtWidgets.QPushButton(parent=self.FadeStopInfoFrame)
self.btnPreview.setMinimumSize(QtCore.QSize(132, 41)) self.btnPreview.setMinimumSize(QtCore.QSize(132, 41))
icon1 = QtGui.QIcon() icon1 = QtGui.QIcon()
icon1.addPixmap(QtGui.QPixmap(":/icons/headphones"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off) icon1.addPixmap(
QtGui.QPixmap(":/icons/headphones"),
QtGui.QIcon.Mode.Normal,
QtGui.QIcon.State.Off,
)
self.btnPreview.setIcon(icon1) self.btnPreview.setIcon(icon1)
self.btnPreview.setIconSize(QtCore.QSize(30, 30)) self.btnPreview.setIconSize(QtCore.QSize(30, 30))
self.btnPreview.setCheckable(True) self.btnPreview.setCheckable(True)
@ -239,8 +298,16 @@ class Ui_MainWindow(object):
self.btnPreviewArm.setMaximumSize(QtCore.QSize(44, 23)) self.btnPreviewArm.setMaximumSize(QtCore.QSize(44, 23))
self.btnPreviewArm.setText("") self.btnPreviewArm.setText("")
icon2 = QtGui.QIcon() icon2 = QtGui.QIcon()
icon2.addPixmap(QtGui.QPixmap(":/icons/record-button.png"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off) icon2.addPixmap(
icon2.addPixmap(QtGui.QPixmap(":/icons/record-red-button.png"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.On) QtGui.QPixmap(":/icons/record-button.png"),
QtGui.QIcon.Mode.Normal,
QtGui.QIcon.State.Off,
)
icon2.addPixmap(
QtGui.QPixmap(":/icons/record-red-button.png"),
QtGui.QIcon.Mode.Normal,
QtGui.QIcon.State.On,
)
self.btnPreviewArm.setIcon(icon2) self.btnPreviewArm.setIcon(icon2)
self.btnPreviewArm.setCheckable(True) self.btnPreviewArm.setCheckable(True)
self.btnPreviewArm.setObjectName("btnPreviewArm") self.btnPreviewArm.setObjectName("btnPreviewArm")
@ -261,8 +328,16 @@ class Ui_MainWindow(object):
self.btnPreviewMark.setMaximumSize(QtCore.QSize(44, 23)) self.btnPreviewMark.setMaximumSize(QtCore.QSize(44, 23))
self.btnPreviewMark.setText("") self.btnPreviewMark.setText("")
icon3 = QtGui.QIcon() icon3 = QtGui.QIcon()
icon3.addPixmap(QtGui.QPixmap(":/icons/star.png"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.On) icon3.addPixmap(
icon3.addPixmap(QtGui.QPixmap(":/icons/star_empty.png"), QtGui.QIcon.Mode.Disabled, QtGui.QIcon.State.Off) QtGui.QPixmap(":/icons/star.png"),
QtGui.QIcon.Mode.Normal,
QtGui.QIcon.State.On,
)
icon3.addPixmap(
QtGui.QPixmap(":/icons/star_empty.png"),
QtGui.QIcon.Mode.Disabled,
QtGui.QIcon.State.Off,
)
self.btnPreviewMark.setIcon(icon3) self.btnPreviewMark.setIcon(icon3)
self.btnPreviewMark.setObjectName("btnPreviewMark") self.btnPreviewMark.setObjectName("btnPreviewMark")
self.btnPreviewFwd = QtWidgets.QPushButton(parent=self.groupBoxIntroControls) self.btnPreviewFwd = QtWidgets.QPushButton(parent=self.groupBoxIntroControls)
@ -363,10 +438,15 @@ class Ui_MainWindow(object):
self.verticalLayout_7.addWidget(self.label_silent_timer) self.verticalLayout_7.addWidget(self.label_silent_timer)
self.horizontalLayout.addWidget(self.frame_silent) self.horizontalLayout.addWidget(self.frame_silent)
self.widgetFadeVolume = PlotWidget(parent=self.InfoFooterFrame) self.widgetFadeVolume = PlotWidget(parent=self.InfoFooterFrame)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Preferred, QtWidgets.QSizePolicy.Policy.Preferred) sizePolicy = QtWidgets.QSizePolicy(
QtWidgets.QSizePolicy.Policy.Preferred,
QtWidgets.QSizePolicy.Policy.Preferred,
)
sizePolicy.setHorizontalStretch(1) sizePolicy.setHorizontalStretch(1)
sizePolicy.setVerticalStretch(0) sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.widgetFadeVolume.sizePolicy().hasHeightForWidth()) sizePolicy.setHeightForWidth(
self.widgetFadeVolume.sizePolicy().hasHeightForWidth()
)
self.widgetFadeVolume.setSizePolicy(sizePolicy) self.widgetFadeVolume.setSizePolicy(sizePolicy)
self.widgetFadeVolume.setMinimumSize(QtCore.QSize(0, 0)) self.widgetFadeVolume.setMinimumSize(QtCore.QSize(0, 0))
self.widgetFadeVolume.setObjectName("widgetFadeVolume") self.widgetFadeVolume.setObjectName("widgetFadeVolume")
@ -383,7 +463,11 @@ class Ui_MainWindow(object):
self.btnFade.setMinimumSize(QtCore.QSize(132, 32)) self.btnFade.setMinimumSize(QtCore.QSize(132, 32))
self.btnFade.setMaximumSize(QtCore.QSize(164, 16777215)) self.btnFade.setMaximumSize(QtCore.QSize(164, 16777215))
icon4 = QtGui.QIcon() icon4 = QtGui.QIcon()
icon4.addPixmap(QtGui.QPixmap(":/icons/fade"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off) icon4.addPixmap(
QtGui.QPixmap(":/icons/fade"),
QtGui.QIcon.Mode.Normal,
QtGui.QIcon.State.Off,
)
self.btnFade.setIcon(icon4) self.btnFade.setIcon(icon4)
self.btnFade.setIconSize(QtCore.QSize(30, 30)) self.btnFade.setIconSize(QtCore.QSize(30, 30))
self.btnFade.setObjectName("btnFade") self.btnFade.setObjectName("btnFade")
@ -391,7 +475,11 @@ class Ui_MainWindow(object):
self.btnStop = QtWidgets.QPushButton(parent=self.frame) self.btnStop = QtWidgets.QPushButton(parent=self.frame)
self.btnStop.setMinimumSize(QtCore.QSize(0, 36)) self.btnStop.setMinimumSize(QtCore.QSize(0, 36))
icon5 = QtGui.QIcon() icon5 = QtGui.QIcon()
icon5.addPixmap(QtGui.QPixmap(":/icons/stopsign"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off) icon5.addPixmap(
QtGui.QPixmap(":/icons/stopsign"),
QtGui.QIcon.Mode.Normal,
QtGui.QIcon.State.Off,
)
self.btnStop.setIcon(icon5) self.btnStop.setIcon(icon5)
self.btnStop.setObjectName("btnStop") self.btnStop.setObjectName("btnStop")
self.verticalLayout_5.addWidget(self.btnStop) self.verticalLayout_5.addWidget(self.btnStop)
@ -417,39 +505,71 @@ class Ui_MainWindow(object):
MainWindow.setStatusBar(self.statusbar) MainWindow.setStatusBar(self.statusbar)
self.actionPlay_next = QtGui.QAction(parent=MainWindow) self.actionPlay_next = QtGui.QAction(parent=MainWindow)
icon6 = QtGui.QIcon() icon6 = QtGui.QIcon()
icon6.addPixmap(QtGui.QPixmap("app/ui/../../../../../../.designer/backup/icon-play.png"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off) icon6.addPixmap(
QtGui.QPixmap("app/ui/../../../../../../.designer/backup/icon-play.png"),
QtGui.QIcon.Mode.Normal,
QtGui.QIcon.State.Off,
)
self.actionPlay_next.setIcon(icon6) self.actionPlay_next.setIcon(icon6)
self.actionPlay_next.setObjectName("actionPlay_next") self.actionPlay_next.setObjectName("actionPlay_next")
self.actionSkipToNext = QtGui.QAction(parent=MainWindow) self.actionSkipToNext = QtGui.QAction(parent=MainWindow)
icon7 = QtGui.QIcon() icon7 = QtGui.QIcon()
icon7.addPixmap(QtGui.QPixmap(":/icons/next"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off) icon7.addPixmap(
QtGui.QPixmap(":/icons/next"),
QtGui.QIcon.Mode.Normal,
QtGui.QIcon.State.Off,
)
self.actionSkipToNext.setIcon(icon7) self.actionSkipToNext.setIcon(icon7)
self.actionSkipToNext.setObjectName("actionSkipToNext") self.actionSkipToNext.setObjectName("actionSkipToNext")
self.actionInsertTrack = QtGui.QAction(parent=MainWindow) self.actionInsertTrack = QtGui.QAction(parent=MainWindow)
icon8 = QtGui.QIcon() icon8 = QtGui.QIcon()
icon8.addPixmap(QtGui.QPixmap("app/ui/../../../../../../.designer/backup/icon_search_database.png"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off) icon8.addPixmap(
QtGui.QPixmap(
"app/ui/../../../../../../.designer/backup/icon_search_database.png"
),
QtGui.QIcon.Mode.Normal,
QtGui.QIcon.State.Off,
)
self.actionInsertTrack.setIcon(icon8) self.actionInsertTrack.setIcon(icon8)
self.actionInsertTrack.setObjectName("actionInsertTrack") self.actionInsertTrack.setObjectName("actionInsertTrack")
self.actionAdd_file = QtGui.QAction(parent=MainWindow) self.actionAdd_file = QtGui.QAction(parent=MainWindow)
icon9 = QtGui.QIcon() icon9 = QtGui.QIcon()
icon9.addPixmap(QtGui.QPixmap("app/ui/../../../../../../.designer/backup/icon_open_file.png"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off) icon9.addPixmap(
QtGui.QPixmap(
"app/ui/../../../../../../.designer/backup/icon_open_file.png"
),
QtGui.QIcon.Mode.Normal,
QtGui.QIcon.State.Off,
)
self.actionAdd_file.setIcon(icon9) self.actionAdd_file.setIcon(icon9)
self.actionAdd_file.setObjectName("actionAdd_file") self.actionAdd_file.setObjectName("actionAdd_file")
self.actionFade = QtGui.QAction(parent=MainWindow) self.actionFade = QtGui.QAction(parent=MainWindow)
icon10 = QtGui.QIcon() icon10 = QtGui.QIcon()
icon10.addPixmap(QtGui.QPixmap("app/ui/../../../../../../.designer/backup/icon-fade.png"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off) icon10.addPixmap(
QtGui.QPixmap("app/ui/../../../../../../.designer/backup/icon-fade.png"),
QtGui.QIcon.Mode.Normal,
QtGui.QIcon.State.Off,
)
self.actionFade.setIcon(icon10) self.actionFade.setIcon(icon10)
self.actionFade.setObjectName("actionFade") self.actionFade.setObjectName("actionFade")
self.actionStop = QtGui.QAction(parent=MainWindow) self.actionStop = QtGui.QAction(parent=MainWindow)
icon11 = QtGui.QIcon() icon11 = QtGui.QIcon()
icon11.addPixmap(QtGui.QPixmap(":/icons/stop"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off) icon11.addPixmap(
QtGui.QPixmap(":/icons/stop"),
QtGui.QIcon.Mode.Normal,
QtGui.QIcon.State.Off,
)
self.actionStop.setIcon(icon11) self.actionStop.setIcon(icon11)
self.actionStop.setObjectName("actionStop") self.actionStop.setObjectName("actionStop")
self.action_Clear_selection = QtGui.QAction(parent=MainWindow) self.action_Clear_selection = QtGui.QAction(parent=MainWindow)
self.action_Clear_selection.setObjectName("action_Clear_selection") self.action_Clear_selection.setObjectName("action_Clear_selection")
self.action_Resume_previous = QtGui.QAction(parent=MainWindow) self.action_Resume_previous = QtGui.QAction(parent=MainWindow)
icon12 = QtGui.QIcon() icon12 = QtGui.QIcon()
icon12.addPixmap(QtGui.QPixmap(":/icons/previous"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off) icon12.addPixmap(
QtGui.QPixmap(":/icons/previous"),
QtGui.QIcon.Mode.Normal,
QtGui.QIcon.State.Off,
)
self.action_Resume_previous.setIcon(icon12) self.action_Resume_previous.setIcon(icon12)
self.action_Resume_previous.setObjectName("action_Resume_previous") self.action_Resume_previous.setObjectName("action_Resume_previous")
self.actionE_xit = QtGui.QAction(parent=MainWindow) self.actionE_xit = QtGui.QAction(parent=MainWindow)
@ -496,7 +616,9 @@ class Ui_MainWindow(object):
self.actionImport = QtGui.QAction(parent=MainWindow) self.actionImport = QtGui.QAction(parent=MainWindow)
self.actionImport.setObjectName("actionImport") self.actionImport.setObjectName("actionImport")
self.actionDownload_CSV_of_played_tracks = QtGui.QAction(parent=MainWindow) self.actionDownload_CSV_of_played_tracks = QtGui.QAction(parent=MainWindow)
self.actionDownload_CSV_of_played_tracks.setObjectName("actionDownload_CSV_of_played_tracks") self.actionDownload_CSV_of_played_tracks.setObjectName(
"actionDownload_CSV_of_played_tracks"
)
self.actionSearch = QtGui.QAction(parent=MainWindow) self.actionSearch = QtGui.QAction(parent=MainWindow)
self.actionSearch.setObjectName("actionSearch") self.actionSearch.setObjectName("actionSearch")
self.actionInsertSectionHeader = QtGui.QAction(parent=MainWindow) self.actionInsertSectionHeader = QtGui.QAction(parent=MainWindow)
@ -524,9 +646,13 @@ class Ui_MainWindow(object):
self.actionResume = QtGui.QAction(parent=MainWindow) self.actionResume = QtGui.QAction(parent=MainWindow)
self.actionResume.setObjectName("actionResume") self.actionResume.setObjectName("actionResume")
self.actionSearch_title_in_Wikipedia = QtGui.QAction(parent=MainWindow) self.actionSearch_title_in_Wikipedia = QtGui.QAction(parent=MainWindow)
self.actionSearch_title_in_Wikipedia.setObjectName("actionSearch_title_in_Wikipedia") self.actionSearch_title_in_Wikipedia.setObjectName(
"actionSearch_title_in_Wikipedia"
)
self.actionSearch_title_in_Songfacts = QtGui.QAction(parent=MainWindow) self.actionSearch_title_in_Songfacts = QtGui.QAction(parent=MainWindow)
self.actionSearch_title_in_Songfacts.setObjectName("actionSearch_title_in_Songfacts") self.actionSearch_title_in_Songfacts.setObjectName(
"actionSearch_title_in_Songfacts"
)
self.actionSelect_duplicate_rows = QtGui.QAction(parent=MainWindow) self.actionSelect_duplicate_rows = QtGui.QAction(parent=MainWindow)
self.actionSelect_duplicate_rows.setObjectName("actionSelect_duplicate_rows") self.actionSelect_duplicate_rows.setObjectName("actionSelect_duplicate_rows")
self.actionReplace_files = QtGui.QAction(parent=MainWindow) self.actionReplace_files = QtGui.QAction(parent=MainWindow)
@ -623,38 +749,58 @@ class Ui_MainWindow(object):
self.actionFade.setShortcut(_translate("MainWindow", "Ctrl+Z")) self.actionFade.setShortcut(_translate("MainWindow", "Ctrl+Z"))
self.actionStop.setText(_translate("MainWindow", "S&top")) self.actionStop.setText(_translate("MainWindow", "S&top"))
self.actionStop.setShortcut(_translate("MainWindow", "Ctrl+Alt+S")) self.actionStop.setShortcut(_translate("MainWindow", "Ctrl+Alt+S"))
self.action_Clear_selection.setText(_translate("MainWindow", "Clear &selection")) self.action_Clear_selection.setText(
_translate("MainWindow", "Clear &selection")
)
self.action_Clear_selection.setShortcut(_translate("MainWindow", "Esc")) self.action_Clear_selection.setShortcut(_translate("MainWindow", "Esc"))
self.action_Resume_previous.setText(_translate("MainWindow", "&Resume previous")) self.action_Resume_previous.setText(
_translate("MainWindow", "&Resume previous")
)
self.actionE_xit.setText(_translate("MainWindow", "E&xit")) self.actionE_xit.setText(_translate("MainWindow", "E&xit"))
self.actionTest.setText(_translate("MainWindow", "&Test")) self.actionTest.setText(_translate("MainWindow", "&Test"))
self.actionOpenPlaylist.setText(_translate("MainWindow", "O&pen...")) self.actionOpenPlaylist.setText(_translate("MainWindow", "O&pen..."))
self.actionNewPlaylist.setText(_translate("MainWindow", "&New...")) self.actionNewPlaylist.setText(_translate("MainWindow", "&New..."))
self.actionTestFunction.setText(_translate("MainWindow", "&Test function")) self.actionTestFunction.setText(_translate("MainWindow", "&Test function"))
self.actionSkipToFade.setText(_translate("MainWindow", "&Skip to start of fade")) self.actionSkipToFade.setText(
_translate("MainWindow", "&Skip to start of fade")
)
self.actionSkipToEnd.setText(_translate("MainWindow", "Skip to &end of track")) self.actionSkipToEnd.setText(_translate("MainWindow", "Skip to &end of track"))
self.actionClosePlaylist.setText(_translate("MainWindow", "&Close")) self.actionClosePlaylist.setText(_translate("MainWindow", "&Close"))
self.actionRenamePlaylist.setText(_translate("MainWindow", "&Rename...")) self.actionRenamePlaylist.setText(_translate("MainWindow", "&Rename..."))
self.actionDeletePlaylist.setText(_translate("MainWindow", "Dele&te...")) self.actionDeletePlaylist.setText(_translate("MainWindow", "Dele&te..."))
self.actionMoveSelected.setText(_translate("MainWindow", "Mo&ve selected tracks to...")) self.actionMoveSelected.setText(
_translate("MainWindow", "Mo&ve selected tracks to...")
)
self.actionExport_playlist.setText(_translate("MainWindow", "E&xport...")) self.actionExport_playlist.setText(_translate("MainWindow", "E&xport..."))
self.actionSetNext.setText(_translate("MainWindow", "Set &next")) self.actionSetNext.setText(_translate("MainWindow", "Set &next"))
self.actionSetNext.setShortcut(_translate("MainWindow", "Ctrl+N")) self.actionSetNext.setShortcut(_translate("MainWindow", "Ctrl+N"))
self.actionSelect_next_track.setText(_translate("MainWindow", "Select next track")) self.actionSelect_next_track.setText(
_translate("MainWindow", "Select next track")
)
self.actionSelect_next_track.setShortcut(_translate("MainWindow", "J")) self.actionSelect_next_track.setShortcut(_translate("MainWindow", "J"))
self.actionSelect_previous_track.setText(_translate("MainWindow", "Select previous track")) self.actionSelect_previous_track.setText(
_translate("MainWindow", "Select previous track")
)
self.actionSelect_previous_track.setShortcut(_translate("MainWindow", "K")) self.actionSelect_previous_track.setShortcut(_translate("MainWindow", "K"))
self.actionSelect_played_tracks.setText(_translate("MainWindow", "Select played tracks")) self.actionSelect_played_tracks.setText(
self.actionMoveUnplayed.setText(_translate("MainWindow", "Move &unplayed tracks to...")) _translate("MainWindow", "Select played tracks")
)
self.actionMoveUnplayed.setText(
_translate("MainWindow", "Move &unplayed tracks to...")
)
self.actionAdd_note.setText(_translate("MainWindow", "Add note...")) self.actionAdd_note.setText(_translate("MainWindow", "Add note..."))
self.actionAdd_note.setShortcut(_translate("MainWindow", "Ctrl+T")) self.actionAdd_note.setShortcut(_translate("MainWindow", "Ctrl+T"))
self.actionEnable_controls.setText(_translate("MainWindow", "Enable controls")) self.actionEnable_controls.setText(_translate("MainWindow", "Enable controls"))
self.actionImport.setText(_translate("MainWindow", "Import track...")) self.actionImport.setText(_translate("MainWindow", "Import track..."))
self.actionImport.setShortcut(_translate("MainWindow", "Ctrl+Shift+I")) self.actionImport.setShortcut(_translate("MainWindow", "Ctrl+Shift+I"))
self.actionDownload_CSV_of_played_tracks.setText(_translate("MainWindow", "Download CSV of played tracks...")) self.actionDownload_CSV_of_played_tracks.setText(
_translate("MainWindow", "Download CSV of played tracks...")
)
self.actionSearch.setText(_translate("MainWindow", "Search...")) self.actionSearch.setText(_translate("MainWindow", "Search..."))
self.actionSearch.setShortcut(_translate("MainWindow", "/")) self.actionSearch.setShortcut(_translate("MainWindow", "/"))
self.actionInsertSectionHeader.setText(_translate("MainWindow", "Insert &section header...")) self.actionInsertSectionHeader.setText(
_translate("MainWindow", "Insert &section header...")
)
self.actionInsertSectionHeader.setShortcut(_translate("MainWindow", "Ctrl+H")) self.actionInsertSectionHeader.setShortcut(_translate("MainWindow", "Ctrl+H"))
self.actionRemove.setText(_translate("MainWindow", "&Remove track")) self.actionRemove.setText(_translate("MainWindow", "&Remove track"))
self.actionFind_next.setText(_translate("MainWindow", "Find next")) self.actionFind_next.setText(_translate("MainWindow", "Find next"))
@ -662,8 +808,12 @@ class Ui_MainWindow(object):
self.actionFind_previous.setText(_translate("MainWindow", "Find previous")) self.actionFind_previous.setText(_translate("MainWindow", "Find previous"))
self.actionFind_previous.setShortcut(_translate("MainWindow", "P")) self.actionFind_previous.setShortcut(_translate("MainWindow", "P"))
self.action_About.setText(_translate("MainWindow", "&About")) self.action_About.setText(_translate("MainWindow", "&About"))
self.actionSave_as_template.setText(_translate("MainWindow", "Save as template...")) self.actionSave_as_template.setText(
self.actionNew_from_template.setText(_translate("MainWindow", "New from template...")) _translate("MainWindow", "Save as template...")
)
self.actionNew_from_template.setText(
_translate("MainWindow", "New from template...")
)
self.actionDebug.setText(_translate("MainWindow", "Debug")) self.actionDebug.setText(_translate("MainWindow", "Debug"))
self.actionAdd_cart.setText(_translate("MainWindow", "Edit cart &1...")) self.actionAdd_cart.setText(_translate("MainWindow", "Edit cart &1..."))
self.actionMark_for_moving.setText(_translate("MainWindow", "Mark for moving")) self.actionMark_for_moving.setText(_translate("MainWindow", "Mark for moving"))
@ -672,11 +822,23 @@ class Ui_MainWindow(object):
self.actionPaste.setShortcut(_translate("MainWindow", "Ctrl+V")) self.actionPaste.setShortcut(_translate("MainWindow", "Ctrl+V"))
self.actionResume.setText(_translate("MainWindow", "Resume")) self.actionResume.setText(_translate("MainWindow", "Resume"))
self.actionResume.setShortcut(_translate("MainWindow", "Ctrl+R")) self.actionResume.setShortcut(_translate("MainWindow", "Ctrl+R"))
self.actionSearch_title_in_Wikipedia.setText(_translate("MainWindow", "Search title in Wikipedia")) self.actionSearch_title_in_Wikipedia.setText(
self.actionSearch_title_in_Wikipedia.setShortcut(_translate("MainWindow", "Ctrl+W")) _translate("MainWindow", "Search title in Wikipedia")
self.actionSearch_title_in_Songfacts.setText(_translate("MainWindow", "Search title in Songfacts")) )
self.actionSearch_title_in_Songfacts.setShortcut(_translate("MainWindow", "Ctrl+S")) self.actionSearch_title_in_Wikipedia.setShortcut(
self.actionSelect_duplicate_rows.setText(_translate("MainWindow", "Select duplicate rows...")) _translate("MainWindow", "Ctrl+W")
self.actionReplace_files.setText(_translate("MainWindow", "Import files...")) )
from infotabs import InfoTabs # type: ignore self.actionSearch_title_in_Songfacts.setText(
_translate("MainWindow", "Search title in Songfacts")
)
self.actionSearch_title_in_Songfacts.setShortcut(
_translate("MainWindow", "Ctrl+S")
)
self.actionSelect_duplicate_rows.setText(
_translate("MainWindow", "Select duplicate rows...")
)
self.actionReplace_files.setText(_translate("MainWindow", "Replace files..."))
from infotabs import InfoTabs
from pyqtgraph import PlotWidget # type: ignore from pyqtgraph import PlotWidget # type: ignore