Right-click menu mostly working
Still to implement: - Move to playlist - Remove row
This commit is contained in:
parent
89781c0a94
commit
99409e8626
@ -60,6 +60,7 @@ class Config(object):
|
|||||||
IMPORT_DESTINATION = os.path.join(ROOT, "Singles")
|
IMPORT_DESTINATION = os.path.join(ROOT, "Singles")
|
||||||
SCROLL_TOP_MARGIN = 3
|
SCROLL_TOP_MARGIN = 3
|
||||||
TESTMODE = True
|
TESTMODE = True
|
||||||
|
TEXT_NO_TRACK_NO_NOTE = "[Section header]"
|
||||||
TOD_TIME_FORMAT = "%H:%M:%S"
|
TOD_TIME_FORMAT = "%H:%M:%S"
|
||||||
TIMER_MS = 500
|
TIMER_MS = 500
|
||||||
TRACK_TIME_FORMAT = "%H:%M:%S"
|
TRACK_TIME_FORMAT = "%H:%M:%S"
|
||||||
|
|||||||
258
app/helpers.py
258
app/helpers.py
@ -1,11 +1,12 @@
|
|||||||
import os
|
import os
|
||||||
# import psutil
|
import psutil
|
||||||
#
|
|
||||||
# from config import Config
|
from config import Config
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
# from pydub import AudioSegment
|
from pydub import AudioSegment
|
||||||
# from PyQt5.QtWidgets import QMessageBox
|
# from PyQt5.QtWidgets import QMessageBox
|
||||||
# from tinytag import TinyTag
|
# from tinytag import TinyTag
|
||||||
|
from typing import Optional
|
||||||
# from typing import Dict, Optional, Union
|
# from typing import Dict, Optional, Union
|
||||||
#
|
#
|
||||||
#
|
#
|
||||||
@ -15,35 +16,35 @@ from datetime import datetime
|
|||||||
# button_reply: bool = QMessageBox.question(None, title, question)
|
# button_reply: bool = QMessageBox.question(None, title, question)
|
||||||
#
|
#
|
||||||
# return button_reply == QMessageBox.Yes
|
# return button_reply == QMessageBox.Yes
|
||||||
#
|
|
||||||
#
|
|
||||||
# def fade_point(
|
def fade_point(
|
||||||
# audio_segment: AudioSegment, fade_threshold: int = 0,
|
audio_segment: AudioSegment, fade_threshold: float = 0.0,
|
||||||
# chunk_size: int = Config.AUDIO_SEGMENT_CHUNK_SIZE) -> int:
|
chunk_size: int = Config.AUDIO_SEGMENT_CHUNK_SIZE) -> int:
|
||||||
# """
|
"""
|
||||||
# Returns the millisecond/index of the point where the volume drops below
|
Returns the millisecond/index of the point where the volume drops below
|
||||||
# the maximum and doesn't get louder again.
|
the maximum and doesn't get louder again.
|
||||||
# audio_segment - the sdlg_search_database_uiegment to find silence in
|
audio_segment - the sdlg_search_database_uiegment to find silence in
|
||||||
# fade_threshold - the upper bound for how quiet is silent in dFBS
|
fade_threshold - the upper bound for how quiet is silent in dFBS
|
||||||
# chunk_size - chunk size for interating over the segment in ms
|
chunk_size - chunk size for interating over the segment in ms
|
||||||
# """
|
"""
|
||||||
#
|
|
||||||
# assert chunk_size > 0 # to avoid infinite loop
|
assert chunk_size > 0 # to avoid infinite loop
|
||||||
#
|
|
||||||
# segment_length: int = audio_segment.duration_seconds * 1000 # ms
|
segment_length: int = audio_segment.duration_seconds * 1000 # ms
|
||||||
# trim_ms: int = segment_length - chunk_size
|
trim_ms = segment_length - chunk_size
|
||||||
# max_vol: int = audio_segment.dBFS
|
max_vol = audio_segment.dBFS
|
||||||
# if fade_threshold == 0:
|
if fade_threshold == 0:
|
||||||
# fade_threshold = max_vol
|
fade_threshold = max_vol
|
||||||
#
|
|
||||||
# while (
|
while (
|
||||||
# audio_segment[trim_ms:trim_ms + chunk_size].dBFS < fade_threshold
|
audio_segment[trim_ms:trim_ms + chunk_size].dBFS < fade_threshold
|
||||||
# and trim_ms > 0): # noqa W503
|
and trim_ms > 0): # noqa W503
|
||||||
# trim_ms -= chunk_size
|
trim_ms -= chunk_size
|
||||||
#
|
|
||||||
# # if there is no trailing silence, return lenght of track (it's less
|
# if there is no trailing silence, return lenght of track (it's less
|
||||||
# # the chunk_size, but for chunk_size = 10ms, this may be ignored)
|
# the chunk_size, but for chunk_size = 10ms, this may be ignored)
|
||||||
# return int(trim_ms)
|
return int(trim_ms)
|
||||||
|
|
||||||
|
|
||||||
def file_is_readable(path: str, check_colon: bool = True) -> bool:
|
def file_is_readable(path: str, check_colon: bool = True) -> bool:
|
||||||
@ -60,16 +61,19 @@ def file_is_readable(path: str, check_colon: bool = True) -> bool:
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
return False
|
return False
|
||||||
#
|
|
||||||
# def get_audio_segment(path: str) -> Optional[AudioSegment]:
|
|
||||||
# try:
|
def get_audio_segment(path: str) -> Optional[AudioSegment]:
|
||||||
# if path.endswith('.mp3'):
|
try:
|
||||||
# return AudioSegment.from_mp3(path)
|
if path.endswith('.mp3'):
|
||||||
# elif path.endswith('.flac'):
|
return AudioSegment.from_mp3(path)
|
||||||
# return AudioSegment.from_file(path, "flac")
|
elif path.endswith('.flac'):
|
||||||
# except AttributeError:
|
return AudioSegment.from_file(path, "flac")
|
||||||
# return None
|
except AttributeError:
|
||||||
#
|
return None
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
#
|
#
|
||||||
# def get_tags(path: str) -> Dict[str, Union[str, int]]:
|
# def get_tags(path: str) -> Dict[str, Union[str, int]]:
|
||||||
# """
|
# """
|
||||||
@ -126,32 +130,32 @@ def get_relative_date(past_date: datetime, reference_date: datetime = None) \
|
|||||||
else:
|
else:
|
||||||
days_str = "days"
|
days_str = "days"
|
||||||
return f"{weeks} {weeks_str}, {days} {days_str} ago"
|
return f"{weeks} {weeks_str}, {days} {days_str} ago"
|
||||||
#
|
|
||||||
#
|
|
||||||
# def leading_silence(
|
def leading_silence(
|
||||||
# audio_segment: AudioSegment,
|
audio_segment: AudioSegment,
|
||||||
# silence_threshold: int = Config.DBFS_SILENCE,
|
silence_threshold: int = Config.DBFS_SILENCE,
|
||||||
# chunk_size: int = Config.AUDIO_SEGMENT_CHUNK_SIZE) -> int:
|
chunk_size: int = Config.AUDIO_SEGMENT_CHUNK_SIZE) -> int:
|
||||||
# """
|
"""
|
||||||
# Returns the millisecond/index that the leading silence ends.
|
Returns the millisecond/index that the leading silence ends.
|
||||||
# audio_segment - the segment to find silence in
|
audio_segment - the segment to find silence in
|
||||||
# silence_threshold - the upper bound for how quiet is silent in dFBS
|
silence_threshold - the upper bound for how quiet is silent in dFBS
|
||||||
# chunk_size - chunk size for interating over the segment in ms
|
chunk_size - chunk size for interating over the segment in ms
|
||||||
#
|
|
||||||
# https://github.com/jiaaro/pydub/blob/master/pydub/silence.py
|
https://github.com/jiaaro/pydub/blob/master/pydub/silence.py
|
||||||
# """
|
"""
|
||||||
#
|
|
||||||
# trim_ms: int = 0 # ms
|
trim_ms: int = 0 # ms
|
||||||
# assert chunk_size > 0 # to avoid infinite loop
|
assert chunk_size > 0 # to avoid infinite loop
|
||||||
# while (
|
while (
|
||||||
# audio_segment[trim_ms:trim_ms + chunk_size].dBFS < # noqa W504
|
audio_segment[trim_ms:trim_ms + chunk_size].dBFS < # noqa W504
|
||||||
# silence_threshold and trim_ms < len(audio_segment)):
|
silence_threshold and trim_ms < len(audio_segment)):
|
||||||
# trim_ms += chunk_size
|
trim_ms += chunk_size
|
||||||
#
|
|
||||||
# # if there is no end it should return the length of the segment
|
# if there is no end it should return the length of the segment
|
||||||
# return min(trim_ms, len(audio_segment))
|
return min(trim_ms, len(audio_segment))
|
||||||
#
|
|
||||||
#
|
|
||||||
def ms_to_mmss(ms: int, decimals: int = 0, negative: bool = False) -> str:
|
def ms_to_mmss(ms: int, decimals: int = 0, negative: bool = False) -> str:
|
||||||
"""Convert milliseconds to mm:ss"""
|
"""Convert milliseconds to mm:ss"""
|
||||||
|
|
||||||
@ -177,66 +181,68 @@ def ms_to_mmss(ms: int, decimals: int = 0, negative: bool = False) -> str:
|
|||||||
seconds = 59.0
|
seconds = 59.0
|
||||||
|
|
||||||
return f"{sign}{minutes:.0f}:{seconds:02.{decimals}f}"
|
return f"{sign}{minutes:.0f}:{seconds:02.{decimals}f}"
|
||||||
#
|
|
||||||
#
|
|
||||||
# def open_in_audacity(path: str) -> Optional[bool]:
|
def open_in_audacity(path: str) -> bool:
|
||||||
# """
|
"""
|
||||||
# Open passed file in Audacity
|
Open passed file in Audacity
|
||||||
#
|
|
||||||
# Return True if apparently opened successfully, else False
|
Return True if apparently opened successfully, else False
|
||||||
# """
|
"""
|
||||||
#
|
|
||||||
# # Return if audacity not running
|
# Return if audacity not running
|
||||||
# if "audacity" not in [i.name() for i in psutil.process_iter()]:
|
if "audacity" not in [i.name() for i in psutil.process_iter()]:
|
||||||
# return False
|
return False
|
||||||
#
|
|
||||||
# # Return if path not given
|
# Return if path not given
|
||||||
# if not path:
|
if not path:
|
||||||
# return False
|
return False
|
||||||
#
|
|
||||||
# to_pipe: str = '/tmp/audacity_script_pipe.to.' + str(os.getuid())
|
to_pipe: str = '/tmp/audacity_script_pipe.to.' + str(os.getuid())
|
||||||
# from_pipe: str = '/tmp/audacity_script_pipe.from.' + str(os.getuid())
|
from_pipe: str = '/tmp/audacity_script_pipe.from.' + str(os.getuid())
|
||||||
# eol: str = '\n'
|
eol: str = '\n'
|
||||||
#
|
|
||||||
# def send_command(command: str) -> None:
|
def send_command(command: str) -> None:
|
||||||
# """Send a single command."""
|
"""Send a single command."""
|
||||||
# to_audacity.write(command + eol)
|
to_audacity.write(command + eol)
|
||||||
# to_audacity.flush()
|
to_audacity.flush()
|
||||||
#
|
|
||||||
# def get_response() -> str:
|
def get_response() -> str:
|
||||||
# """Return the command response."""
|
"""Return the command response."""
|
||||||
#
|
|
||||||
# result: str = ''
|
result: str = ''
|
||||||
# line: str = ''
|
line: str = ''
|
||||||
#
|
|
||||||
# while True:
|
while True:
|
||||||
# result += line
|
result += line
|
||||||
# line = from_audacity.readline()
|
line = from_audacity.readline()
|
||||||
# if line == '\n' and len(result) > 0:
|
if line == '\n' and len(result) > 0:
|
||||||
# break
|
break
|
||||||
# return result
|
return result
|
||||||
#
|
|
||||||
# def do_command(command: str) -> str:
|
def do_command(command: str) -> str:
|
||||||
# """Send one command, and return the response."""
|
"""Send one command, and return the response."""
|
||||||
#
|
|
||||||
# send_command(command)
|
send_command(command)
|
||||||
# response = get_response()
|
response = get_response()
|
||||||
# return response
|
return response
|
||||||
#
|
|
||||||
# with open(to_pipe, 'w') as to_audacity, open(
|
with open(to_pipe, 'w') as to_audacity, open(
|
||||||
# from_pipe, 'rt') as from_audacity:
|
from_pipe, 'rt') as from_audacity:
|
||||||
# do_command(f'Import2: Filename="{path}"')
|
do_command(f'Import2: Filename="{path}"')
|
||||||
|
|
||||||
|
return True
|
||||||
#
|
#
|
||||||
#
|
#
|
||||||
# def show_warning(title: str, msg: str) -> None:
|
# def show_warning(title: str, msg: str) -> None:
|
||||||
# """Display a warning to user"""
|
# """Display a warning to user"""
|
||||||
#
|
#
|
||||||
# QMessageBox.warning(None, title, msg, buttons=QMessageBox.Cancel)
|
# QMessageBox.warning(None, title, msg, buttons=QMessageBox.Cancel)
|
||||||
#
|
|
||||||
#
|
|
||||||
# def trailing_silence(
|
def trailing_silence(
|
||||||
# audio_segment: AudioSegment, silence_threshold: int = -50,
|
audio_segment: AudioSegment, silence_threshold: int = -50,
|
||||||
# chunk_size: int = Config.AUDIO_SEGMENT_CHUNK_SIZE) -> int:
|
chunk_size: int = Config.AUDIO_SEGMENT_CHUNK_SIZE) -> int:
|
||||||
# """Return fade point from start in milliseconds"""
|
"""Return fade point from start in milliseconds"""
|
||||||
#
|
|
||||||
# return fade_point(audio_segment, silence_threshold, chunk_size)
|
return fade_point(audio_segment, silence_threshold, chunk_size)
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
#!/usr/bin/python3
|
#!/usr/bin/python3
|
||||||
#
|
#
|
||||||
# import os.path
|
import os.path
|
||||||
import re
|
import re
|
||||||
#
|
#
|
||||||
from dbconfig import Session
|
from dbconfig import Session
|
||||||
@ -36,13 +36,13 @@ from sqlalchemy.orm.exc import (
|
|||||||
NoResultFound
|
NoResultFound
|
||||||
)
|
)
|
||||||
#
|
#
|
||||||
# from config import Config
|
from config import Config
|
||||||
# from helpers import (
|
from helpers import (
|
||||||
# fade_point,
|
fade_point,
|
||||||
# get_audio_segment,
|
get_audio_segment,
|
||||||
# leading_silence,
|
leading_silence,
|
||||||
# trailing_silence,
|
trailing_silence,
|
||||||
# )
|
)
|
||||||
from log import log
|
from log import log
|
||||||
#
|
#
|
||||||
Base = declarative_base()
|
Base = declarative_base()
|
||||||
@ -669,22 +669,22 @@ class Tracks(Base):
|
|||||||
# except NoResultFound:
|
# except NoResultFound:
|
||||||
# log.error(f"get_track({track_id}): not found")
|
# log.error(f"get_track({track_id}): not found")
|
||||||
# return None
|
# return None
|
||||||
#
|
|
||||||
# def rescan(self, session: Session) -> None:
|
def rescan(self, session: Session) -> None:
|
||||||
# """
|
"""
|
||||||
# Update audio metadata for passed track.
|
Update audio metadata for passed track.
|
||||||
# """
|
"""
|
||||||
#
|
|
||||||
# audio: AudioSegment = get_audio_segment(self.path)
|
audio: AudioSegment = get_audio_segment(self.path)
|
||||||
# self.duration = len(audio)
|
self.duration = len(audio)
|
||||||
# self.fade_at = round(fade_point(audio) / 1000,
|
self.fade_at = round(fade_point(audio) / 1000,
|
||||||
# Config.MILLISECOND_SIGFIGS) * 1000
|
Config.MILLISECOND_SIGFIGS) * 1000
|
||||||
# self.mtime = os.path.getmtime(self.path)
|
self.mtime = os.path.getmtime(self.path)
|
||||||
# self.silence_at = round(trailing_silence(audio) / 1000,
|
self.silence_at = round(trailing_silence(audio) / 1000,
|
||||||
# Config.MILLISECOND_SIGFIGS) * 1000
|
Config.MILLISECOND_SIGFIGS) * 1000
|
||||||
# self.start_gap = leading_silence(audio)
|
self.start_gap = leading_silence(audio)
|
||||||
# session.add(self)
|
session.add(self)
|
||||||
# session.flush()
|
session.flush()
|
||||||
#
|
#
|
||||||
# @staticmethod
|
# @staticmethod
|
||||||
# def remove_by_path(session: Session, path: str) -> None:
|
# def remove_by_path(session: Session, path: str) -> None:
|
||||||
|
|||||||
@ -37,7 +37,7 @@ from models import (
|
|||||||
# Playdates,
|
# Playdates,
|
||||||
Playlists,
|
Playlists,
|
||||||
# Settings,
|
# Settings,
|
||||||
# Tracks
|
Tracks
|
||||||
)
|
)
|
||||||
from playlists import PlaylistTab
|
from playlists import PlaylistTab
|
||||||
# from sqlalchemy.orm.exc import DetachedInstanceError
|
# from sqlalchemy.orm.exc import DetachedInstanceError
|
||||||
@ -501,10 +501,13 @@ class Window(QMainWindow, Ui_MainWindow):
|
|||||||
for playlist in Playlists.get_open(session):
|
for playlist in Playlists.get_open(session):
|
||||||
self.create_playlist_tab(session, playlist)
|
self.create_playlist_tab(session, playlist)
|
||||||
playlist.mark_open(session)
|
playlist.mark_open(session)
|
||||||
#
|
|
||||||
# def move_selected(self) -> None:
|
def move_selected(self) -> None:
|
||||||
# """Move selected rows to another playlist"""
|
"""Move selected rows to another playlist"""
|
||||||
#
|
|
||||||
|
# ***KAE
|
||||||
|
pass
|
||||||
|
|
||||||
# with Session() as session:
|
# with Session() as session:
|
||||||
# visible_tab = self.visible_playlist_tab()
|
# visible_tab = self.visible_playlist_tab()
|
||||||
# visible_tab_id = visible_tab.playlist_id
|
# visible_tab_id = visible_tab.playlist_id
|
||||||
@ -819,56 +822,59 @@ class Window(QMainWindow, Ui_MainWindow):
|
|||||||
#
|
#
|
||||||
# # Run end-of-track actions
|
# # Run end-of-track actions
|
||||||
# self.end_of_track_actions()
|
# self.end_of_track_actions()
|
||||||
#
|
|
||||||
# def this_is_the_next_track(self, playlist_tab: PlaylistTab,
|
def this_is_the_next_track(self, playlist_tab: PlaylistTab,
|
||||||
# track: Tracks, session) -> None:
|
track: Tracks, session) -> None:
|
||||||
# """
|
"""
|
||||||
# This is notification from a playlist tab that it holds the next
|
This is notification from a playlist tab that it holds the next
|
||||||
# track to be played.
|
track to be played.
|
||||||
#
|
|
||||||
# Actions required:
|
Actions required:
|
||||||
# - Clear next track if on other tab
|
- Clear next track if on other tab
|
||||||
# - Reset tab colour if on other tab
|
- Reset tab colour if on other tab
|
||||||
# - Note next playlist tab
|
- Note next playlist tab
|
||||||
# - Set next playlist_tab tab colour
|
- Set next playlist_tab tab colour
|
||||||
# - Note next track
|
- Note next track
|
||||||
# - Update headers
|
- Update headers
|
||||||
# - Populate ‘info’ tabs
|
- Populate ‘info’ tabs
|
||||||
#
|
|
||||||
# """
|
"""
|
||||||
#
|
|
||||||
# # Clear next track if on another tab
|
# ***kae
|
||||||
# if self.next_track_playlist_tab != playlist_tab:
|
return
|
||||||
# # We need to reset the ex-next-track playlist
|
|
||||||
# if self.next_track_playlist_tab:
|
# Clear next track if on another tab
|
||||||
# self.next_track_playlist_tab.clear_next(session)
|
if self.next_track_playlist_tab != playlist_tab:
|
||||||
#
|
# We need to reset the ex-next-track playlist
|
||||||
# # Reset tab colour if on other tab
|
if self.next_track_playlist_tab:
|
||||||
# if (self.next_track_playlist_tab !=
|
self.next_track_playlist_tab.clear_next(session)
|
||||||
# self.current_track_playlist_tab):
|
|
||||||
# self.set_tab_colour(
|
# Reset tab colour if on other tab
|
||||||
# self.next_track_playlist_tab,
|
if (self.next_track_playlist_tab !=
|
||||||
# QColor(Config.COLOUR_NORMAL_TAB))
|
self.current_track_playlist_tab):
|
||||||
#
|
self.set_tab_colour(
|
||||||
# # Note next playlist tab
|
self.next_track_playlist_tab,
|
||||||
# self.next_track_playlist_tab = playlist_tab
|
QColor(Config.COLOUR_NORMAL_TAB))
|
||||||
#
|
|
||||||
# # Set next playlist_tab tab colour if it isn't the
|
# Note next playlist tab
|
||||||
# # currently-playing tab
|
self.next_track_playlist_tab = playlist_tab
|
||||||
# if (self.next_track_playlist_tab !=
|
|
||||||
# self.current_track_playlist_tab):
|
# Set next playlist_tab tab colour if it isn't the
|
||||||
# self.set_tab_colour(
|
# currently-playing tab
|
||||||
# self.next_track_playlist_tab,
|
if (self.next_track_playlist_tab !=
|
||||||
# QColor(Config.COLOUR_NEXT_TAB))
|
self.current_track_playlist_tab):
|
||||||
#
|
self.set_tab_colour(
|
||||||
# # Note next track
|
self.next_track_playlist_tab,
|
||||||
# self.next_track = TrackData(track)
|
QColor(Config.COLOUR_NEXT_TAB))
|
||||||
#
|
|
||||||
# # Update headers
|
# Note next track
|
||||||
# self.update_headers()
|
self.next_track = TrackData(track)
|
||||||
#
|
|
||||||
# # Populate 'info' tabs
|
# Update headers
|
||||||
# self.open_info_tabs()
|
self.update_headers()
|
||||||
|
|
||||||
|
# Populate 'info' tabs
|
||||||
|
self.open_info_tabs()
|
||||||
#
|
#
|
||||||
# def tick(self) -> None:
|
# def tick(self) -> None:
|
||||||
# """
|
# """
|
||||||
|
|||||||
185
app/playlists.py
185
app/playlists.py
@ -15,9 +15,9 @@ from PyQt5.QtWidgets import (
|
|||||||
# QInputDialog,
|
# QInputDialog,
|
||||||
# QLineEdit,
|
# QLineEdit,
|
||||||
QMainWindow,
|
QMainWindow,
|
||||||
# QMenu,
|
QMenu,
|
||||||
# QStyledItemDelegate,
|
# QStyledItemDelegate,
|
||||||
# QMessageBox,
|
QMessageBox,
|
||||||
QTableWidget,
|
QTableWidget,
|
||||||
QTableWidgetItem,
|
QTableWidgetItem,
|
||||||
)
|
)
|
||||||
@ -25,14 +25,14 @@ from PyQt5.QtWidgets import (
|
|||||||
import helpers
|
import helpers
|
||||||
# import os
|
# import os
|
||||||
import re
|
import re
|
||||||
# import subprocess
|
import subprocess
|
||||||
# import threading
|
import threading
|
||||||
#
|
#
|
||||||
from config import Config
|
from config import Config
|
||||||
from datetime import datetime # , timedelta
|
from datetime import datetime # , timedelta
|
||||||
from helpers import (
|
from helpers import (
|
||||||
get_relative_date,
|
get_relative_date,
|
||||||
# open_in_audacity
|
open_in_audacity
|
||||||
)
|
)
|
||||||
from log import log
|
from log import log
|
||||||
from models import (
|
from models import (
|
||||||
@ -102,7 +102,7 @@ class PlaylistTab(QTableWidget):
|
|||||||
self.musicmuster: QMainWindow = musicmuster
|
self.musicmuster: QMainWindow = musicmuster
|
||||||
self.playlist_id: int = playlist_id
|
self.playlist_id: int = playlist_id
|
||||||
|
|
||||||
# self.menu: Optional[QMenu] = None
|
self.menu: Optional[QMenu] = None
|
||||||
# self.current_track_start_time: Optional[datetime] = None
|
# self.current_track_start_time: Optional[datetime] = None
|
||||||
#
|
#
|
||||||
# # Don't select text on edit
|
# # Don't select text on edit
|
||||||
@ -293,7 +293,7 @@ class PlaylistTab(QTableWidget):
|
|||||||
# Rescan
|
# Rescan
|
||||||
act_rescan = self.menu.addAction("Rescan")
|
act_rescan = self.menu.addAction("Rescan")
|
||||||
act_rescan.triggered.connect(
|
act_rescan.triggered.connect(
|
||||||
lambda: self._rescan(track_id)
|
lambda: self._rescan(row_number, track_id)
|
||||||
)
|
)
|
||||||
|
|
||||||
self.menu.addSeparator()
|
self.menu.addSeparator()
|
||||||
@ -557,7 +557,7 @@ class PlaylistTab(QTableWidget):
|
|||||||
# self._set_row_content(row, track.id)
|
# self._set_row_content(row, track.id)
|
||||||
#
|
#
|
||||||
# # Mark track if file is unreadable
|
# # Mark track if file is unreadable
|
||||||
# if not self._file_is_readable(track.path):
|
# if not helpers.file_is_readable(track.path):
|
||||||
# self._set_unreadable_row(row)
|
# self._set_unreadable_row(row)
|
||||||
# # Scroll to new row
|
# # Scroll to new row
|
||||||
# self.scrollToItem(title_item, QAbstractItemView.PositionAtCenter)
|
# self.scrollToItem(title_item, QAbstractItemView.PositionAtCenter)
|
||||||
@ -1242,10 +1242,13 @@ class PlaylistTab(QTableWidget):
|
|||||||
# """Clear played status on row"""
|
# """Clear played status on row"""
|
||||||
#
|
#
|
||||||
# self._meta_clear_attribute(row, RowMeta.PLAYED)
|
# self._meta_clear_attribute(row, RowMeta.PLAYED)
|
||||||
#
|
|
||||||
# def _delete_rows(self) -> None:
|
def _delete_rows(self) -> None:
|
||||||
# """Delete mutliple rows"""
|
"""Delete mutliple rows"""
|
||||||
#
|
|
||||||
|
# ***KAE
|
||||||
|
pass
|
||||||
|
|
||||||
# log.debug("playlist._delete_rows()")
|
# log.debug("playlist._delete_rows()")
|
||||||
#
|
#
|
||||||
# selected_rows: List[int] = sorted(
|
# selected_rows: List[int] = sorted(
|
||||||
@ -1444,18 +1447,18 @@ class PlaylistTab(QTableWidget):
|
|||||||
# notes_rows: Set[int] = set(self._get_notes_rows())
|
# notes_rows: Set[int] = set(self._get_notes_rows())
|
||||||
#
|
#
|
||||||
# return list(unplayed_rows - notes_rows)
|
# return list(unplayed_rows - notes_rows)
|
||||||
#
|
|
||||||
# def _get_row_start_time(self, row: int) -> Optional[datetime]:
|
def _get_row_start_time(self, row: int) -> Optional[datetime]:
|
||||||
# try:
|
try:
|
||||||
# if self.item(row, self.COL_START_TIME):
|
if self.item(row, columns['start_time'].idx):
|
||||||
# return datetime.strptime(self.item(
|
return datetime.strptime(self.item(
|
||||||
# row, self.COL_START_TIME).text(),
|
row, columns['start_time'].idx).text(),
|
||||||
# Config.NOTE_TIME_FORMAT
|
Config.NOTE_TIME_FORMAT
|
||||||
# )
|
)
|
||||||
# else:
|
else:
|
||||||
# return None
|
return None
|
||||||
# except ValueError:
|
except ValueError:
|
||||||
# return None
|
return None
|
||||||
#
|
#
|
||||||
# def _get_row_track_object(self, row: int, session: Session) \
|
# def _get_row_track_object(self, row: int, session: Session) \
|
||||||
# -> Optional[Tracks]:
|
# -> Optional[Tracks]:
|
||||||
@ -1578,15 +1581,15 @@ class PlaylistTab(QTableWidget):
|
|||||||
# new_metadata: int = self._meta_get(row) & ~(1 << attribute)
|
# new_metadata: int = self._meta_get(row) & ~(1 << attribute)
|
||||||
# self.item(row, self.COL_USERDATA).setData(
|
# self.item(row, self.COL_USERDATA).setData(
|
||||||
# self.ROW_FLAGS, new_metadata)
|
# self.ROW_FLAGS, new_metadata)
|
||||||
#
|
|
||||||
# def _meta_clear_next(self) -> None:
|
def _meta_clear_next(self) -> None:
|
||||||
# """
|
"""
|
||||||
# Clear next row if there is one.
|
Clear next row if there is one.
|
||||||
# """
|
"""
|
||||||
#
|
|
||||||
# next_row: Optional[int] = self._get_next_track_row()
|
next_row: Optional[int] = self._get_next_track_row()
|
||||||
# if next_row is not None:
|
if next_row is not None:
|
||||||
# self._meta_clear_attribute(next_row, RowMeta.NEXT)
|
self._meta_clear_attribute(next_row, RowMeta.NEXT)
|
||||||
|
|
||||||
def _meta_get(self, row: int) -> int:
|
def _meta_get(self, row: int) -> int:
|
||||||
"""Return row metadata"""
|
"""Return row metadata"""
|
||||||
@ -1637,20 +1640,20 @@ class PlaylistTab(QTableWidget):
|
|||||||
f"in rows: {', '.join([str(x) for x in matches])}"
|
f"in rows: {', '.join([str(x) for x in matches])}"
|
||||||
)
|
)
|
||||||
raise AttributeError(f"Multiple '{metadata}' metadata {matches}")
|
raise AttributeError(f"Multiple '{metadata}' metadata {matches}")
|
||||||
#
|
|
||||||
# def _meta_set_attribute(self, row: int, attribute: int) -> None:
|
def _meta_set_attribute(self, row: int, attribute: int) -> None:
|
||||||
# """Set row metadata"""
|
"""Set row metadata"""
|
||||||
#
|
|
||||||
# if row is None:
|
if row is None:
|
||||||
# raise ValueError(f"_meta_set_attribute({row=}, {attribute=})")
|
raise ValueError(f"_meta_set_attribute({row=}, {attribute=})")
|
||||||
#
|
|
||||||
# current_metadata: int = self._meta_get(row)
|
current_metadata: int = self._meta_get(row)
|
||||||
# if not current_metadata:
|
if not current_metadata:
|
||||||
# new_metadata: int = (1 << attribute)
|
new_metadata: int = (1 << attribute)
|
||||||
# else:
|
else:
|
||||||
# new_metadata = self._meta_get(row) | (1 << attribute)
|
new_metadata = self._meta_get(row) | (1 << attribute)
|
||||||
# self.item(row, self.COL_USERDATA).setData(
|
self.item(row, columns['userdata'].idx).setData(
|
||||||
# self.ROW_FLAGS, new_metadata)
|
self.ROW_FLAGS, new_metadata)
|
||||||
|
|
||||||
def _mplayer_play(self, track_id: int) -> None:
|
def _mplayer_play(self, track_id: int) -> None:
|
||||||
"""Play track with mplayer"""
|
"""Play track with mplayer"""
|
||||||
@ -1673,21 +1676,30 @@ class PlaylistTab(QTableWidget):
|
|||||||
"""Remove track from row, making it a section header"""
|
"""Remove track from row, making it a section header"""
|
||||||
|
|
||||||
# Update playlist_rows record
|
# Update playlist_rows record
|
||||||
|
with Session() as session:
|
||||||
plr = session.get(PlaylistRows, self._get_playlistrow_id(row))
|
plr = session.get(PlaylistRows, self._get_playlistrow_id(row))
|
||||||
plr.track_id = None
|
plr.track_id = None
|
||||||
plr.save()
|
# We can't have null text
|
||||||
|
if not plr.note:
|
||||||
|
plr.note = Config.TEXT_NO_TRACK_NO_NOTE
|
||||||
|
session.commit()
|
||||||
|
|
||||||
# Clear track text items
|
# Clear track text items
|
||||||
for i in range(2, len(columns) - 1):
|
for i in range(2, len(columns)):
|
||||||
self.item(row, i).setText("")
|
self.item(row, i).setText("")
|
||||||
# Set note text in correct column for section head
|
# Set note text in correct column for section head
|
||||||
self.item(row, 1).setText(plr.note)
|
self.item(row, 1).setText(plr.note)
|
||||||
# Remove row duration
|
# Remove row duration
|
||||||
self._set_row_duration(row, 0)
|
self._set_row_duration(row, 0)
|
||||||
|
# Remote track_id from row
|
||||||
|
self.item(row, columns['userdata'].idx).setData(
|
||||||
|
self.ROW_TRACK_ID, 0)
|
||||||
|
# Span the rows
|
||||||
|
self.setSpan(row, 1, 1, len(columns))
|
||||||
# And refresh display
|
# And refresh display
|
||||||
self.update_display()
|
self.update_display(session)
|
||||||
|
|
||||||
def _rescan(self, track_id: int) -> None:
|
def _rescan(self, row: int, track_id: int) -> None:
|
||||||
"""Rescan track"""
|
"""Rescan track"""
|
||||||
|
|
||||||
with Session() as session:
|
with Session() as session:
|
||||||
@ -1702,10 +1714,10 @@ class PlaylistTab(QTableWidget):
|
|||||||
track.rescan(session)
|
track.rescan(session)
|
||||||
self._update_row(session, row, track)
|
self._update_row(session, row, track)
|
||||||
|
|
||||||
# def _run_subprocess(self, args):
|
def _run_subprocess(self, args):
|
||||||
# """Run args in subprocess"""
|
"""Run args in subprocess"""
|
||||||
#
|
|
||||||
# subprocess.call(args)
|
subprocess.call(args)
|
||||||
#
|
#
|
||||||
# def _set_current_track_row(self, row: int) -> None:
|
# def _set_current_track_row(self, row: int) -> None:
|
||||||
# """Mark this row as current track"""
|
# """Mark this row as current track"""
|
||||||
@ -1796,7 +1808,7 @@ class PlaylistTab(QTableWidget):
|
|||||||
else:
|
else:
|
||||||
self.setColumnWidth(idx, Config.DEFAULT_COLUMN_WIDTH)
|
self.setColumnWidth(idx, Config.DEFAULT_COLUMN_WIDTH)
|
||||||
|
|
||||||
def _set_next(self, session: Session, row: int) -> None:
|
def _set_next(self, session: Session, row_number: int) -> None:
|
||||||
"""
|
"""
|
||||||
Set passed row as next track to play.
|
Set passed row as next track to play.
|
||||||
|
|
||||||
@ -1810,21 +1822,23 @@ class PlaylistTab(QTableWidget):
|
|||||||
|
|
||||||
track_id = self._get_row_track_id(row_number)
|
track_id = self._get_row_track_id(row_number)
|
||||||
if not track_id:
|
if not track_id:
|
||||||
log.error(f"playlists._set_next({row=}) has no track associated")
|
log.error(
|
||||||
|
f"playlists._set_next({row_number=}) has no track associated"
|
||||||
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
track = session.get(Tracks, track_id)
|
track = session.get(Tracks, track_id)
|
||||||
if not track:
|
if not track:
|
||||||
log.error(f"playlists._set_next({row=}): Track not found")
|
log.error(f"playlists._set_next({row_number=}): Track not found")
|
||||||
return
|
return
|
||||||
|
|
||||||
# Check track is readable
|
# Check track is readable
|
||||||
if not self._file_is_readable(track.path):
|
if not helpers.file_is_readable(track.path):
|
||||||
self._set_unreadable_row(row)
|
self._set_unreadable_row(row_number)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
# Mark as next track
|
# Mark as next track
|
||||||
self._set_next_track_row(row)
|
self._set_next_track_row(row_number)
|
||||||
|
|
||||||
# Update display
|
# Update display
|
||||||
self.update_display(session)
|
self.update_display(session)
|
||||||
@ -1904,28 +1918,27 @@ class PlaylistTab(QTableWidget):
|
|||||||
# display_text = note_text + ' [' + duration + caveat + ']'
|
# display_text = note_text + ' [' + duration + caveat + ']'
|
||||||
# item = self.item(start_row, self.COL_TITLE)
|
# item = self.item(start_row, self.COL_TITLE)
|
||||||
# item.setText(display_text)
|
# item.setText(display_text)
|
||||||
#
|
|
||||||
# def _update_row(self, session, row: int, track: Tracks) -> None:
|
def _update_row(self, session, row: int, track: Tracks) -> None:
|
||||||
# """
|
"""
|
||||||
# Update the passed row with info from the passed track.
|
Update the passed row with info from the passed track.
|
||||||
# """
|
"""
|
||||||
#
|
|
||||||
# log.debug(f"_update_row({row=}, {track=}")
|
columns['start_time'].idx
|
||||||
#
|
item_startgap = self.item(row, columns['start_gap'].idx)
|
||||||
# item_startgap: QTableWidgetItem = self.item(row, self.COL_MSS)
|
item_startgap.setText(str(track.start_gap))
|
||||||
# item_startgap.setText(str(track.start_gap))
|
if track.start_gap >= 500:
|
||||||
# if track.start_gap >= 500:
|
item_startgap.setBackground(QColor(Config.COLOUR_LONG_START))
|
||||||
# item_startgap.setBackground(QColor(Config.COLOUR_LONG_START))
|
else:
|
||||||
# else:
|
item_startgap.setBackground(QColor("white"))
|
||||||
# item_startgap.setBackground(QColor("white"))
|
|
||||||
#
|
item_title = self.item(row, columns['title'].idx)
|
||||||
# item_title: QTableWidgetItem = self.item(row, self.COL_TITLE)
|
item_title.setText(track.title)
|
||||||
# item_title.setText(track.title)
|
|
||||||
#
|
item_artist = self.item(row, columns['artist'].idx)
|
||||||
# item_artist: QTableWidgetItem = self.item(row, self.COL_ARTIST)
|
item_artist.setText(track.artist)
|
||||||
# item_artist.setText(track.artist)
|
|
||||||
#
|
item_duration = self.item(row, columns['duration'].idx)
|
||||||
# item_duration: QTableWidgetItem = self.item(row, self.COL_DURATION)
|
item_duration.setText(helpers.ms_to_mmss(track.duration))
|
||||||
# item_duration.setText(helpers.ms_to_mmss(track.duration))
|
|
||||||
#
|
self.update_display(session)
|
||||||
# self.update_display(session)
|
|
||||||
|
|||||||
11
poetry.lock
generated
11
poetry.lock
generated
@ -358,6 +358,14 @@ category = "main"
|
|||||||
optional = false
|
optional = false
|
||||||
python-versions = "*"
|
python-versions = "*"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pydub-stubs"
|
||||||
|
version = "0.25.1.0"
|
||||||
|
description = "Stub-only package containing type information for pydub"
|
||||||
|
category = "dev"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.8,<4.0"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pygments"
|
name = "pygments"
|
||||||
version = "2.11.2"
|
version = "2.11.2"
|
||||||
@ -614,7 +622,7 @@ python-versions = "*"
|
|||||||
[metadata]
|
[metadata]
|
||||||
lock-version = "1.1"
|
lock-version = "1.1"
|
||||||
python-versions = "^3.9"
|
python-versions = "^3.9"
|
||||||
content-hash = "32b91fc8cb421cc92689db4fc1a4647044714d6e2a72194fe7caf2e25c821b55"
|
content-hash = "7754808d801630b110a46869b849a6ce205784f587d3c1d4ed2097553e4368c4"
|
||||||
|
|
||||||
[metadata.files]
|
[metadata.files]
|
||||||
alembic = [
|
alembic = [
|
||||||
@ -886,6 +894,7 @@ pydub = [
|
|||||||
{file = "pydub-0.25.1-py2.py3-none-any.whl", hash = "sha256:65617e33033874b59d87db603aa1ed450633288aefead953b30bded59cb599a6"},
|
{file = "pydub-0.25.1-py2.py3-none-any.whl", hash = "sha256:65617e33033874b59d87db603aa1ed450633288aefead953b30bded59cb599a6"},
|
||||||
{file = "pydub-0.25.1.tar.gz", hash = "sha256:980a33ce9949cab2a569606b65674d748ecbca4f0796887fd6f46173a7b0d30f"},
|
{file = "pydub-0.25.1.tar.gz", hash = "sha256:980a33ce9949cab2a569606b65674d748ecbca4f0796887fd6f46173a7b0d30f"},
|
||||||
]
|
]
|
||||||
|
pydub-stubs = []
|
||||||
pygments = [
|
pygments = [
|
||||||
{file = "Pygments-2.11.2-py3-none-any.whl", hash = "sha256:44238f1b60a76d78fc8ca0528ee429702aae011c265fe6a8dd8b63049ae41c65"},
|
{file = "Pygments-2.11.2-py3-none-any.whl", hash = "sha256:44238f1b60a76d78fc8ca0528ee429702aae011c265fe6a8dd8b63049ae41c65"},
|
||||||
{file = "Pygments-2.11.2.tar.gz", hash = "sha256:4e426f72023d88d03b2fa258de560726ce890ff3b630f88c21cbb8b2503b8c6a"},
|
{file = "Pygments-2.11.2.tar.gz", hash = "sha256:4e426f72023d88d03b2fa258de560726ce890ff3b630f88c21cbb8b2503b8c6a"},
|
||||||
|
|||||||
@ -26,6 +26,7 @@ PyQt5-stubs = "^5.15.2"
|
|||||||
mypy = "^0.931"
|
mypy = "^0.931"
|
||||||
pytest = "^7.0.1"
|
pytest = "^7.0.1"
|
||||||
pytest-qt = "^4.0.2"
|
pytest-qt = "^4.0.2"
|
||||||
|
pydub-stubs = "^0.25.1"
|
||||||
|
|
||||||
[build-system]
|
[build-system]
|
||||||
requires = ["poetry-core>=1.0.0"]
|
requires = ["poetry-core>=1.0.0"]
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user