Compare commits
No commits in common. "683e76f9a03f12a17d14a625ab69a14724753e3d" and "6a2bcfff195d13e20bad0ac1b0bd0e7aea80e4c1" have entirely different histories.
683e76f9a0
...
6a2bcfff19
@ -4,9 +4,11 @@ from typing import List, Optional
|
|||||||
|
|
||||||
|
|
||||||
class Config(object):
|
class Config(object):
|
||||||
|
AUDACITY_COMMAND = "/usr/bin/audacity"
|
||||||
AUDIO_SEGMENT_CHUNK_SIZE = 10
|
AUDIO_SEGMENT_CHUNK_SIZE = 10
|
||||||
BITRATE_LOW_THRESHOLD = 192
|
BITRATE_LOW_THRESHOLD = 192
|
||||||
BITRATE_OK_THRESHOLD = 300
|
BITRATE_OK_THRESHOLD = 300
|
||||||
|
CHECK_AUDACITY_AT_STARTUP = True
|
||||||
CART_DIRECTORY = "/home/kae/radio/CartTracks"
|
CART_DIRECTORY = "/home/kae/radio/CartTracks"
|
||||||
CARTS_COUNT = 10
|
CARTS_COUNT = 10
|
||||||
CARTS_HIDE = True
|
CARTS_HIDE = True
|
||||||
@ -19,16 +21,19 @@ class Config(object):
|
|||||||
COLOUR_CART_PROGRESSBAR = "#000000"
|
COLOUR_CART_PROGRESSBAR = "#000000"
|
||||||
COLOUR_CART_READY = "#ffc107"
|
COLOUR_CART_READY = "#ffc107"
|
||||||
COLOUR_CART_UNCONFIGURED = "#f2f2f2"
|
COLOUR_CART_UNCONFIGURED = "#f2f2f2"
|
||||||
|
COLOUR_CURRENT_HEADER = "#d4edda"
|
||||||
COLOUR_CURRENT_PLAYLIST = "#7eca8f"
|
COLOUR_CURRENT_PLAYLIST = "#7eca8f"
|
||||||
COLOUR_CURRENT_TAB = "#248f24"
|
COLOUR_CURRENT_TAB = "#248f24"
|
||||||
COLOUR_ENDING_TIMER = "#dc3545"
|
COLOUR_ENDING_TIMER = "#dc3545"
|
||||||
COLOUR_EVEN_PLAYLIST = "#d9d9d9"
|
COLOUR_EVEN_PLAYLIST = "#d9d9d9"
|
||||||
COLOUR_LONG_START = "#dc3545"
|
COLOUR_LONG_START = "#dc3545"
|
||||||
|
COLOUR_NEXT_HEADER = "#fff3cd"
|
||||||
COLOUR_NEXT_PLAYLIST = "#ffc107"
|
COLOUR_NEXT_PLAYLIST = "#ffc107"
|
||||||
COLOUR_NEXT_TAB = "#b38600"
|
COLOUR_NEXT_TAB = "#b38600"
|
||||||
COLOUR_NORMAL_TAB = "#000000"
|
COLOUR_NORMAL_TAB = "#000000"
|
||||||
COLOUR_NOTES_PLAYLIST = "#b8daff"
|
COLOUR_NOTES_PLAYLIST = "#b8daff"
|
||||||
COLOUR_ODD_PLAYLIST = "#f2f2f2"
|
COLOUR_ODD_PLAYLIST = "#f2f2f2"
|
||||||
|
COLOUR_PREVIOUS_HEADER = "#f8d7da"
|
||||||
COLOUR_UNREADABLE = "#dc3545"
|
COLOUR_UNREADABLE = "#dc3545"
|
||||||
COLOUR_WARNING_TIMER = "#ffc107"
|
COLOUR_WARNING_TIMER = "#ffc107"
|
||||||
COLUMN_NAME_ARTIST = "Artist"
|
COLUMN_NAME_ARTIST = "Artist"
|
||||||
@ -41,10 +46,13 @@ class Config(object):
|
|||||||
COLUMN_NAME_NOTES = "Notes"
|
COLUMN_NAME_NOTES = "Notes"
|
||||||
COLUMN_NAME_START_TIME = "Start"
|
COLUMN_NAME_START_TIME = "Start"
|
||||||
COLUMN_NAME_TITLE = "Title"
|
COLUMN_NAME_TITLE = "Title"
|
||||||
|
DBFS_FADE = -12
|
||||||
DBFS_SILENCE = -50
|
DBFS_SILENCE = -50
|
||||||
DEBUG_FUNCTIONS: List[Optional[str]] = []
|
DEBUG_FUNCTIONS: List[Optional[str]] = []
|
||||||
DEBUG_MODULES: List[Optional[str]] = ['dbconfig']
|
DEBUG_MODULES: List[Optional[str]] = ['dbconfig']
|
||||||
DEFAULT_COLUMN_WIDTH = 200
|
DEFAULT_COLUMN_WIDTH = 200
|
||||||
|
DEFAULT_IMPORT_DIRECTORY = "/home/kae/Nextcloud/tmp"
|
||||||
|
DEFAULT_OUTPUT_DIRECTORY = "/home/kae/music/Singles"
|
||||||
DISPLAY_SQL = False
|
DISPLAY_SQL = False
|
||||||
ERRORS_FROM = ['noreply@midnighthax.com']
|
ERRORS_FROM = ['noreply@midnighthax.com']
|
||||||
ERRORS_TO = ['kae@midnighthax.com']
|
ERRORS_TO = ['kae@midnighthax.com']
|
||||||
@ -70,6 +78,7 @@ class Config(object):
|
|||||||
ROOT = os.environ.get('ROOT') or "/home/kae/music"
|
ROOT = os.environ.get('ROOT') or "/home/kae/music"
|
||||||
IMPORT_DESTINATION = os.path.join(ROOT, "Singles")
|
IMPORT_DESTINATION = os.path.join(ROOT, "Singles")
|
||||||
SCROLL_TOP_MARGIN = 3
|
SCROLL_TOP_MARGIN = 3
|
||||||
|
TESTMODE = True
|
||||||
TEXT_NO_TRACK_NO_NOTE = "[Section header]"
|
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
|
||||||
@ -77,3 +86,6 @@ class Config(object):
|
|||||||
VOLUME_VLC_DEFAULT = 75
|
VOLUME_VLC_DEFAULT = 75
|
||||||
VOLUME_VLC_DROP3db = 65
|
VOLUME_VLC_DROP3db = 65
|
||||||
WEB_ZOOM_FACTOR = 1.2
|
WEB_ZOOM_FACTOR = 1.2
|
||||||
|
|
||||||
|
|
||||||
|
config = Config
|
||||||
|
|||||||
@ -235,7 +235,7 @@ def normalise_track(path):
|
|||||||
stats = os.stat(path)
|
stats = os.stat(path)
|
||||||
try:
|
try:
|
||||||
# Copy original file
|
# Copy original file
|
||||||
_, temp_path = tempfile.mkstemp()
|
fd, temp_path = tempfile.mkstemp()
|
||||||
shutil.copyfile(path, temp_path)
|
shutil.copyfile(path, temp_path)
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
log.debug(
|
log.debug(
|
||||||
|
|||||||
39
app/log.py
39
app/log.py
@ -2,8 +2,7 @@
|
|||||||
|
|
||||||
import logging
|
import logging
|
||||||
import logging.handlers
|
import logging.handlers
|
||||||
import os
|
import stackprinter
|
||||||
import stackprinter # type: ignore
|
|
||||||
import sys
|
import sys
|
||||||
import traceback
|
import traceback
|
||||||
|
|
||||||
@ -56,30 +55,40 @@ syslog.addFilter(local_filter)
|
|||||||
stderr.addFilter(local_filter)
|
stderr.addFilter(local_filter)
|
||||||
stderr.addFilter(debug_filter)
|
stderr.addFilter(debug_filter)
|
||||||
|
|
||||||
stderr_fmt = logging.Formatter('[%(asctime)s] %(leveltag)s: %(message)s',
|
|
||||||
datefmt='%H:%M:%S')
|
|
||||||
syslog_fmt = logging.Formatter(
|
|
||||||
'[%(name)s] %(module)s.%(funcName)s - %(leveltag)s: %(message)s'
|
|
||||||
)
|
|
||||||
stderr.setFormatter(stderr_fmt)
|
|
||||||
syslog.setFormatter(syslog_fmt)
|
|
||||||
|
|
||||||
|
class VerboseExceptionFormatter(logging.Formatter):
|
||||||
|
def formatException(self, exc_info):
|
||||||
|
msg = stackprinter.format(exc_info)
|
||||||
|
lines = msg.split('\n')
|
||||||
|
lines_indented = [" ┆ " + line + "\n" for line in lines]
|
||||||
|
msg_indented = "".join(lines_indented)
|
||||||
|
return msg_indented
|
||||||
|
|
||||||
|
|
||||||
|
stderr_fmt = '[%(asctime)s] %(leveltag)s: %(message)s'
|
||||||
|
stderr_formatter = VerboseExceptionFormatter(stderr_fmt, datefmt='%H:%M:%S')
|
||||||
|
stderr.setFormatter(stderr_formatter)
|
||||||
|
|
||||||
|
syslog_fmt = '[%(name)s] %(module)s.%(funcName)s - %(leveltag)s: %(message)s'
|
||||||
|
syslog_formatter = VerboseExceptionFormatter(syslog_fmt)
|
||||||
|
syslog.setFormatter(syslog_formatter)
|
||||||
|
|
||||||
|
# add the handlers to the log
|
||||||
log.addHandler(stderr)
|
log.addHandler(stderr)
|
||||||
log.addHandler(syslog)
|
log.addHandler(syslog)
|
||||||
|
|
||||||
|
|
||||||
def log_uncaught_exceptions(_ex_cls, ex, tb):
|
def log_uncaught_exceptions(ex_cls, ex, tb):
|
||||||
|
|
||||||
from helpers import send_mail
|
from helpers import send_mail
|
||||||
|
|
||||||
print("\033[1;31;47m")
|
print("\033[1;31;47m")
|
||||||
logging.critical(''.join(traceback.format_tb(tb)))
|
logging.critical(''.join(traceback.format_tb(tb)))
|
||||||
print("\033[1;37;40m")
|
print("\033[1;37;40m")
|
||||||
print(stackprinter.format(ex, style="darkbg2", add_summary=True))
|
stackprinter.show(style="lightbg")
|
||||||
if os.environ["MM_ENV"] != "DEVELOPMENT":
|
msg = stackprinter.format(ex)
|
||||||
msg = stackprinter.format(ex)
|
send_mail(Config.ERRORS_TO, Config.ERRORS_FROM,
|
||||||
send_mail(Config.ERRORS_TO, Config.ERRORS_FROM,
|
"Exception from musicmuster", msg)
|
||||||
"Exception from musicmuster", msg)
|
|
||||||
|
|
||||||
|
|
||||||
sys.excepthook = log_uncaught_exceptions
|
sys.excepthook = log_uncaught_exceptions
|
||||||
|
|||||||
139
app/models.py
139
app/models.py
@ -1,16 +1,16 @@
|
|||||||
#!/usr/bin/python3
|
#!/usr/bin/python3
|
||||||
|
#
|
||||||
import os.path
|
import os.path
|
||||||
import re
|
import re
|
||||||
import stackprinter # type: ignore
|
#
|
||||||
|
|
||||||
from dbconfig import Session
|
from dbconfig import Session
|
||||||
|
#
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from typing import List, Optional
|
from typing import List, Optional
|
||||||
|
#
|
||||||
|
# from pydub import AudioSegment
|
||||||
from sqlalchemy.ext.associationproxy import association_proxy
|
from sqlalchemy.ext.associationproxy import association_proxy
|
||||||
|
# from sqlalchemy.ext.declarative import declarative_base, DeclarativeMeta
|
||||||
from sqlalchemy import (
|
from sqlalchemy import (
|
||||||
Boolean,
|
Boolean,
|
||||||
Column,
|
Column,
|
||||||
@ -22,16 +22,22 @@ from sqlalchemy import (
|
|||||||
Integer,
|
Integer,
|
||||||
select,
|
select,
|
||||||
String,
|
String,
|
||||||
|
UniqueConstraint,
|
||||||
update,
|
update,
|
||||||
)
|
)
|
||||||
|
# from sqlalchemy.exc import IntegrityError
|
||||||
from sqlalchemy.orm import (
|
from sqlalchemy.orm import (
|
||||||
|
backref,
|
||||||
declarative_base,
|
declarative_base,
|
||||||
relationship,
|
relationship,
|
||||||
|
RelationshipProperty
|
||||||
)
|
)
|
||||||
|
from sqlalchemy.orm.collections import attribute_mapped_collection
|
||||||
from sqlalchemy.orm.exc import (
|
from sqlalchemy.orm.exc import (
|
||||||
|
# MultipleResultsFound,
|
||||||
NoResultFound
|
NoResultFound
|
||||||
)
|
)
|
||||||
|
#
|
||||||
from config import Config
|
from config import Config
|
||||||
from helpers import (
|
from helpers import (
|
||||||
fade_point,
|
fade_point,
|
||||||
@ -41,6 +47,7 @@ from helpers import (
|
|||||||
trailing_silence,
|
trailing_silence,
|
||||||
)
|
)
|
||||||
from log import log
|
from log import log
|
||||||
|
#
|
||||||
Base = declarative_base()
|
Base = declarative_base()
|
||||||
|
|
||||||
|
|
||||||
@ -62,7 +69,7 @@ class Carts(Base):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def __init__(self, session: Session, cart_number: int, name: str = None,
|
def __init__(self, session: Session, cart_number: int, name: str = None,
|
||||||
duration: int = None, path: str = None,
|
duration: int = None, path: str = None,
|
||||||
enabled: bool = True) -> None:
|
enabled: bool = True) -> None:
|
||||||
"""Create new cart"""
|
"""Create new cart"""
|
||||||
|
|
||||||
@ -75,6 +82,23 @@ class Carts(Base):
|
|||||||
session.add(self)
|
session.add(self)
|
||||||
session.commit()
|
session.commit()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_or_create(cls, session: Session, cart_number: int) -> "Carts":
|
||||||
|
"""
|
||||||
|
Return cart with passed cart number, or create a record if
|
||||||
|
none exists.
|
||||||
|
"""
|
||||||
|
|
||||||
|
try:
|
||||||
|
return (
|
||||||
|
session.execute(
|
||||||
|
select(Carts)
|
||||||
|
.where(Carts.cart_number == cart_number)
|
||||||
|
).scalar_one()
|
||||||
|
)
|
||||||
|
except NoResultFound:
|
||||||
|
return Carts(session, cart_number)
|
||||||
|
|
||||||
|
|
||||||
class NoteColours(Base):
|
class NoteColours(Base):
|
||||||
__tablename__ = 'notecolours'
|
__tablename__ = 'notecolours'
|
||||||
@ -263,17 +287,8 @@ class Playlists(Base):
|
|||||||
def close(self, session: Session) -> None:
|
def close(self, session: Session) -> None:
|
||||||
"""Mark playlist as unloaded"""
|
"""Mark playlist as unloaded"""
|
||||||
|
|
||||||
# Closing this tab will mean all higher-number tabs have moved
|
|
||||||
# down by one
|
|
||||||
closed_idx = self.tab
|
|
||||||
self.tab = None
|
self.tab = None
|
||||||
|
|
||||||
session.execute(
|
|
||||||
update(Playlists)
|
|
||||||
.where(Playlists.tab > closed_idx)
|
|
||||||
.values(tab=Playlists.tab - 1)
|
|
||||||
)
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def create_playlist_from_template(cls,
|
def create_playlist_from_template(cls,
|
||||||
session: Session,
|
session: Session,
|
||||||
@ -402,7 +417,7 @@ class PlaylistRows(Base):
|
|||||||
return (
|
return (
|
||||||
f"<PlaylistRow(id={self.id}, playlist_id={self.playlist_id}, "
|
f"<PlaylistRow(id={self.id}, playlist_id={self.playlist_id}, "
|
||||||
f"track_id={self.track_id}, "
|
f"track_id={self.track_id}, "
|
||||||
f"note={self.note}, row_number={self.row_number}>"
|
f"note={self.note} row_number={self.row_number}>"
|
||||||
)
|
)
|
||||||
|
|
||||||
def __init__(self,
|
def __init__(self,
|
||||||
@ -437,19 +452,41 @@ class PlaylistRows(Base):
|
|||||||
plr.note)
|
plr.note)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def delete_plrids_not_in_list(session: Session, playlist_id: int,
|
def delete_higher_rows(session: Session, playlist_id: int, row: int) \
|
||||||
plrids: List["PlaylistRows"]) -> None:
|
-> None:
|
||||||
"""
|
"""
|
||||||
Delete rows in given playlist that have a higher row number
|
Delete rows in given playlist that have a higher row number
|
||||||
than 'maxrow'
|
than 'row'
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Log the rows to be deleted
|
||||||
|
rows_to_go = session.execute(
|
||||||
|
select(PlaylistRows)
|
||||||
|
.where(PlaylistRows.playlist_id == playlist_id,
|
||||||
|
PlaylistRows.row_number > row)
|
||||||
|
).scalars().all()
|
||||||
|
if not rows_to_go:
|
||||||
|
return
|
||||||
|
|
||||||
|
for row in rows_to_go:
|
||||||
|
log.debug(f"Should delete: {row}")
|
||||||
|
# If needed later:
|
||||||
|
# session.delete(row)
|
||||||
|
rows_to_go = session.execute(
|
||||||
|
select(PlaylistRows)
|
||||||
|
.where(PlaylistRows.playlist_id == playlist_id,
|
||||||
|
PlaylistRows.row_number > row)
|
||||||
|
).scalars().all()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def delete_rows(session: Session, ids: List[int]) -> None:
|
||||||
|
"""
|
||||||
|
Delete passed ids
|
||||||
"""
|
"""
|
||||||
|
|
||||||
session.execute(
|
session.execute(
|
||||||
delete(PlaylistRows)
|
delete(PlaylistRows)
|
||||||
.where(
|
.where(PlaylistRows.id.in_(ids))
|
||||||
PlaylistRows.playlist_id == playlist_id,
|
|
||||||
PlaylistRows.id.not_in(plrids)
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
# Delete won't take effect until commit()
|
# Delete won't take effect until commit()
|
||||||
session.commit()
|
session.commit()
|
||||||
@ -472,29 +509,6 @@ class PlaylistRows(Base):
|
|||||||
# Ensure new row numbers are available to the caller
|
# Ensure new row numbers are available to the caller
|
||||||
session.commit()
|
session.commit()
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def get_track_plr(session: Session, track_id: int,
|
|
||||||
playlist_id: int) -> Optional["PlaylistRows"]:
|
|
||||||
"""Return first matching PlaylistRows object or None"""
|
|
||||||
|
|
||||||
return session.scalars(
|
|
||||||
select(PlaylistRows)
|
|
||||||
.where(
|
|
||||||
PlaylistRows.track_id == track_id,
|
|
||||||
PlaylistRows.playlist_id == playlist_id
|
|
||||||
)
|
|
||||||
.limit(1)
|
|
||||||
).first()
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def get_last_used_row(session: Session, playlist_id: int) -> Optional[int]:
|
|
||||||
"""Return the last used row for playlist, or None if no rows"""
|
|
||||||
|
|
||||||
return session.execute(
|
|
||||||
select(func.max(PlaylistRows.row_number))
|
|
||||||
.where(PlaylistRows.playlist_id == playlist_id)
|
|
||||||
).scalar_one()
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_played_rows(cls, session: Session,
|
def get_played_rows(cls, session: Session,
|
||||||
playlist_id: int) -> List[int]:
|
playlist_id: int) -> List[int]:
|
||||||
@ -533,6 +547,15 @@ class PlaylistRows(Base):
|
|||||||
|
|
||||||
return plrs
|
return plrs
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_last_used_row(session: Session, playlist_id: int) -> Optional[int]:
|
||||||
|
"""Return the last used row for playlist, or None if no rows"""
|
||||||
|
|
||||||
|
return session.execute(
|
||||||
|
select(func.max(PlaylistRows.row_number))
|
||||||
|
.where(PlaylistRows.playlist_id == playlist_id)
|
||||||
|
).scalar_one()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_unplayed_rows(cls, session: Session,
|
def get_unplayed_rows(cls, session: Session,
|
||||||
playlist_id: int) -> List[int]:
|
playlist_id: int) -> List[int]:
|
||||||
@ -570,26 +593,6 @@ class PlaylistRows(Base):
|
|||||||
.values(row_number=PlaylistRows.row_number + move_by)
|
.values(row_number=PlaylistRows.row_number + move_by)
|
||||||
)
|
)
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def indexed_by_id(session: Session, plr_ids: List[int]) -> dict:
|
|
||||||
"""
|
|
||||||
Return a dictionary of playlist_rows indexed by their plr id from
|
|
||||||
the passed plr_id list.
|
|
||||||
"""
|
|
||||||
|
|
||||||
plrs = session.execute(
|
|
||||||
select(PlaylistRows)
|
|
||||||
.where(
|
|
||||||
PlaylistRows.id.in_(plr_ids)
|
|
||||||
)
|
|
||||||
).scalars().all()
|
|
||||||
|
|
||||||
result = {}
|
|
||||||
for plr in plrs:
|
|
||||||
result[plr.id] = plr
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
||||||
class Settings(Base):
|
class Settings(Base):
|
||||||
"""Manage settings"""
|
"""Manage settings"""
|
||||||
|
|||||||
@ -19,8 +19,11 @@ class Music:
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
|
# self.current_track_start_time = None
|
||||||
|
# self.fading = 0
|
||||||
self.VLC = vlc.Instance()
|
self.VLC = vlc.Instance()
|
||||||
self.player = None
|
self.player = None
|
||||||
|
# self.track_path = None
|
||||||
self.max_volume = Config.VOLUME_VLC_DEFAULT
|
self.max_volume = Config.VOLUME_VLC_DEFAULT
|
||||||
|
|
||||||
def fade(self) -> None:
|
def fade(self) -> None:
|
||||||
@ -106,6 +109,7 @@ class Music:
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
status = -1
|
status = -1
|
||||||
|
self.track_path = path
|
||||||
|
|
||||||
if Config.COLON_IN_PATH_FIX:
|
if Config.COLON_IN_PATH_FIX:
|
||||||
media = self.VLC.media_new_path(path)
|
media = self.VLC.media_new_path(path)
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
from log import log
|
from log import log
|
||||||
import argparse
|
import argparse
|
||||||
import stackprinter # type: ignore
|
import stackprinter
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
import threading
|
import threading
|
||||||
@ -42,6 +42,7 @@ from models import (
|
|||||||
)
|
)
|
||||||
from config import Config
|
from config import Config
|
||||||
from playlists import PlaylistTab
|
from playlists import PlaylistTab
|
||||||
|
from sqlalchemy.orm.exc import DetachedInstanceError
|
||||||
from ui.dlg_cart_ui import Ui_DialogCartEdit # type: ignore
|
from ui.dlg_cart_ui import Ui_DialogCartEdit # type: ignore
|
||||||
from ui.dlg_search_database_ui import Ui_Dialog # type: ignore
|
from ui.dlg_search_database_ui import Ui_Dialog # type: ignore
|
||||||
from ui.dlg_SelectPlaylist_ui import Ui_dlgSelectPlaylist # type: ignore
|
from ui.dlg_SelectPlaylist_ui import Ui_dlgSelectPlaylist # type: ignore
|
||||||
@ -358,7 +359,7 @@ class Window(QMainWindow, Ui_MainWindow):
|
|||||||
|
|
||||||
def close_tab(self, tab_index: int) -> None:
|
def close_tab(self, tab_index: int) -> None:
|
||||||
"""
|
"""
|
||||||
Close playlist tab unless it holds the current or next track.
|
Close active playlist tab unless it holds the curren or next track.
|
||||||
Called from close_playlist_tab() or by clicking close button on tab.
|
Called from close_playlist_tab() or by clicking close button on tab.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@ -375,12 +376,6 @@ class Window(QMainWindow, Ui_MainWindow):
|
|||||||
"Can't close next track playlist", 5000)
|
"Can't close next track playlist", 5000)
|
||||||
return
|
return
|
||||||
|
|
||||||
# Record playlist as closed and update remaining playlist tabs
|
|
||||||
with Session() as session:
|
|
||||||
playlist_id = self.tabPlaylist.widget(tab_index).playlist_id
|
|
||||||
playlist = session.get(Playlists, playlist_id)
|
|
||||||
playlist.close(session)
|
|
||||||
|
|
||||||
# Close playlist and remove tab
|
# Close playlist and remove tab
|
||||||
self.tabPlaylist.widget(tab_index).close()
|
self.tabPlaylist.widget(tab_index).close()
|
||||||
self.tabPlaylist.removeTab(tab_index)
|
self.tabPlaylist.removeTab(tab_index)
|
||||||
@ -426,7 +421,8 @@ class Window(QMainWindow, Ui_MainWindow):
|
|||||||
self.btnStop.clicked.connect(self.stop)
|
self.btnStop.clicked.connect(self.stop)
|
||||||
self.hdrCurrentTrack.clicked.connect(self.show_current)
|
self.hdrCurrentTrack.clicked.connect(self.show_current)
|
||||||
self.hdrNextTrack.clicked.connect(self.show_next)
|
self.hdrNextTrack.clicked.connect(self.show_next)
|
||||||
self.tabPlaylist.currentChanged.connect(self.tab_change)
|
self.tabPlaylist.currentChanged.connect(
|
||||||
|
lambda: self.tabPlaylist.currentWidget().tab_visible())
|
||||||
self.tabPlaylist.tabCloseRequested.connect(self.close_tab)
|
self.tabPlaylist.tabCloseRequested.connect(self.close_tab)
|
||||||
self.tabBar = self.tabPlaylist.tabBar()
|
self.tabBar = self.tabPlaylist.tabBar()
|
||||||
self.tabBar.tabMoved.connect(self.move_tab)
|
self.tabBar.tabMoved.connect(self.move_tab)
|
||||||
@ -441,8 +437,6 @@ class Window(QMainWindow, Ui_MainWindow):
|
|||||||
|
|
||||||
if not playlist_name:
|
if not playlist_name:
|
||||||
playlist_name = self.solicit_playlist_name()
|
playlist_name = self.solicit_playlist_name()
|
||||||
if not playlist_name:
|
|
||||||
return
|
|
||||||
|
|
||||||
playlist = Playlists(session, playlist_name)
|
playlist = Playlists(session, playlist_name)
|
||||||
return playlist
|
return playlist
|
||||||
@ -452,8 +446,7 @@ class Window(QMainWindow, Ui_MainWindow):
|
|||||||
|
|
||||||
with Session() as session:
|
with Session() as session:
|
||||||
playlist = self.create_playlist(session)
|
playlist = self.create_playlist(session)
|
||||||
if playlist:
|
self.create_playlist_tab(session, playlist)
|
||||||
self.create_playlist_tab(session, playlist)
|
|
||||||
|
|
||||||
def create_playlist_tab(self, session: Session,
|
def create_playlist_tab(self, session: Session,
|
||||||
playlist: Playlists) -> int:
|
playlist: Playlists) -> int:
|
||||||
@ -743,7 +736,6 @@ class Window(QMainWindow, Ui_MainWindow):
|
|||||||
helpers.set_track_metadata(session, track)
|
helpers.set_track_metadata(session, track)
|
||||||
helpers.normalise_track(track.path)
|
helpers.normalise_track(track.path)
|
||||||
self.visible_playlist_tab().insert_track(session, track)
|
self.visible_playlist_tab().insert_track(session, track)
|
||||||
self.visible_playlist_tab().save_playlist(session)
|
|
||||||
|
|
||||||
def insert_header(self) -> None:
|
def insert_header(self) -> None:
|
||||||
"""Show dialog box to enter header text and add to playlist"""
|
"""Show dialog box to enter header text and add to playlist"""
|
||||||
@ -763,7 +755,6 @@ class Window(QMainWindow, Ui_MainWindow):
|
|||||||
if ok:
|
if ok:
|
||||||
with Session() as session:
|
with Session() as session:
|
||||||
playlist_tab.insert_header(session, dlg.textValue())
|
playlist_tab.insert_header(session, dlg.textValue())
|
||||||
playlist_tab.save_playlist(session)
|
|
||||||
|
|
||||||
def insert_track(self) -> None:
|
def insert_track(self) -> None:
|
||||||
"""Show dialog box to select and add track from database"""
|
"""Show dialog box to select and add track from database"""
|
||||||
@ -801,8 +792,6 @@ class Window(QMainWindow, Ui_MainWindow):
|
|||||||
# Identify destination playlist
|
# Identify destination playlist
|
||||||
visible_tab = self.visible_playlist_tab()
|
visible_tab = self.visible_playlist_tab()
|
||||||
source_playlist = visible_tab.playlist_id
|
source_playlist = visible_tab.playlist_id
|
||||||
|
|
||||||
# Get destination playlist id
|
|
||||||
playlists = []
|
playlists = []
|
||||||
for playlist in Playlists.get_all(session):
|
for playlist in Playlists.get_all(session):
|
||||||
if playlist.id == source_playlist:
|
if playlist.id == source_playlist:
|
||||||
@ -810,17 +799,17 @@ class Window(QMainWindow, Ui_MainWindow):
|
|||||||
else:
|
else:
|
||||||
playlists.append(playlist)
|
playlists.append(playlist)
|
||||||
|
|
||||||
|
# Get destination playlist id
|
||||||
dlg = SelectPlaylistDialog(self, playlists=playlists, session=session)
|
dlg = SelectPlaylistDialog(self, playlists=playlists, session=session)
|
||||||
dlg.exec()
|
dlg.exec()
|
||||||
if not dlg.playlist:
|
if not dlg.playlist:
|
||||||
return
|
return
|
||||||
destination_playlist_id = dlg.playlist.id
|
destination_playlist_id = dlg.playlist.id
|
||||||
|
|
||||||
# Remove moved rows from display and save
|
# Remove moved rows from display
|
||||||
visible_tab.remove_rows([plr.row_number for plr in playlistrows])
|
visible_tab.remove_rows([plr.row_number for plr in playlistrows])
|
||||||
visible_tab.save_playlist(session)
|
|
||||||
|
|
||||||
# Update destination playlist in the database
|
# Update playlist for the rows in the database
|
||||||
last_row = PlaylistRows.get_last_used_row(session,
|
last_row = PlaylistRows.get_last_used_row(session,
|
||||||
destination_playlist_id)
|
destination_playlist_id)
|
||||||
if last_row is not None:
|
if last_row is not None:
|
||||||
@ -836,13 +825,13 @@ class Window(QMainWindow, Ui_MainWindow):
|
|||||||
|
|
||||||
# Update destination playlist_tab if visible (if not visible, it
|
# Update destination playlist_tab if visible (if not visible, it
|
||||||
# will be re-populated when it is opened)
|
# will be re-populated when it is opened)
|
||||||
destination_playlist_tab = None
|
destionation_playlist_tab = None
|
||||||
for tab in range(self.tabPlaylist.count()):
|
for tab in range(self.tabPlaylist.count()):
|
||||||
if self.tabPlaylist.widget(tab).playlist_id == dlg.playlist.id:
|
if self.tabPlaylist.widget(tab).playlist_id == dlg.playlist.id:
|
||||||
destination_playlist_tab = self.tabPlaylist.widget(tab)
|
destionation_playlist_tab = self.tabPlaylist.widget(tab)
|
||||||
break
|
break
|
||||||
if destination_playlist_tab:
|
if destionation_playlist_tab:
|
||||||
destination_playlist_tab.populate_display(session, dlg.playlist.id)
|
destionation_playlist_tab.populate(session, dlg.playlist.id)
|
||||||
|
|
||||||
def move_selected(self) -> None:
|
def move_selected(self) -> None:
|
||||||
"""
|
"""
|
||||||
@ -924,17 +913,17 @@ class Window(QMainWindow, Ui_MainWindow):
|
|||||||
|
|
||||||
playlist_tab = self.visible_playlist_tab()
|
playlist_tab = self.visible_playlist_tab()
|
||||||
dst_playlist_id = playlist_tab.playlist_id
|
dst_playlist_id = playlist_tab.playlist_id
|
||||||
dst_row = self.visible_playlist_tab().get_new_row_number()
|
|
||||||
|
|
||||||
with Session() as session:
|
with Session() as session:
|
||||||
# Create space in destination playlist
|
# Create space in destination playlist
|
||||||
PlaylistRows.move_rows_down(session, dst_playlist_id,
|
if playlist_tab.selectionModel().hasSelection():
|
||||||
dst_row, len(self.selected_plrs))
|
row = playlist_tab.currentRow()
|
||||||
session.commit()
|
PlaylistRows.move_rows_down(session, dst_playlist_id,
|
||||||
|
row, len(self.selected_plrs))
|
||||||
|
session.commit()
|
||||||
|
|
||||||
# Update plrs
|
|
||||||
row = dst_row
|
|
||||||
src_playlist_id = None
|
src_playlist_id = None
|
||||||
|
dst_row = row
|
||||||
for plr in self.selected_plrs:
|
for plr in self.selected_plrs:
|
||||||
# Update moved rows
|
# Update moved rows
|
||||||
session.add(plr)
|
session.add(plr)
|
||||||
@ -947,8 +936,8 @@ class Window(QMainWindow, Ui_MainWindow):
|
|||||||
session.commit()
|
session.commit()
|
||||||
|
|
||||||
# Update display
|
# Update display
|
||||||
self.visible_playlist_tab().populate_display(
|
self.visible_playlist_tab().populate(session, dst_playlist_id,
|
||||||
session, dst_playlist_id, scroll_to_top=False)
|
scroll_to_top=False)
|
||||||
|
|
||||||
# If source playlist is not destination playlist, fixup row
|
# If source playlist is not destination playlist, fixup row
|
||||||
# numbers and update display
|
# numbers and update display
|
||||||
@ -963,8 +952,8 @@ class Window(QMainWindow, Ui_MainWindow):
|
|||||||
source_playlist_tab = self.tabPlaylist.widget(tab)
|
source_playlist_tab = self.tabPlaylist.widget(tab)
|
||||||
break
|
break
|
||||||
if source_playlist_tab:
|
if source_playlist_tab:
|
||||||
source_playlist_tab.populate_display(
|
source_playlist_tab.populate(session, src_playlist_id,
|
||||||
session, src_playlist_id, scroll_to_top=False)
|
scroll_to_top=False)
|
||||||
|
|
||||||
# Reset so rows can't be repasted
|
# Reset so rows can't be repasted
|
||||||
self.selected_plrs = None
|
self.selected_plrs = None
|
||||||
@ -1043,6 +1032,7 @@ class Window(QMainWindow, Ui_MainWindow):
|
|||||||
)
|
)
|
||||||
fade_at = self.current_track.fade_at
|
fade_at = self.current_track.fade_at
|
||||||
silence_at = self.current_track.silence_at
|
silence_at = self.current_track.silence_at
|
||||||
|
length = self.current_track.duration
|
||||||
self.label_fade_length.setText(
|
self.label_fade_length.setText(
|
||||||
helpers.ms_to_mmss(silence_at - fade_at))
|
helpers.ms_to_mmss(silence_at - fade_at))
|
||||||
self.label_start_time.setText(
|
self.label_start_time.setText(
|
||||||
@ -1212,15 +1202,6 @@ 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 tab_change(self):
|
|
||||||
"""Called when active tab changed"""
|
|
||||||
|
|
||||||
try:
|
|
||||||
self.tabPlaylist.currentWidget().tab_visible()
|
|
||||||
except AttributeError:
|
|
||||||
# May also be called when last tab is closed
|
|
||||||
pass
|
|
||||||
|
|
||||||
def this_is_the_next_track(self, session: Session,
|
def this_is_the_next_track(self, session: Session,
|
||||||
playlist_tab: PlaylistTab,
|
playlist_tab: PlaylistTab,
|
||||||
track: Tracks) -> None:
|
track: Tracks) -> None:
|
||||||
@ -1516,8 +1497,8 @@ class DbDialog(QDialog):
|
|||||||
|
|
||||||
self.parent().visible_playlist_tab().insert_track(
|
self.parent().visible_playlist_tab().insert_track(
|
||||||
self.session, track, note=self.ui.txtNote.text())
|
self.session, track, note=self.ui.txtNote.text())
|
||||||
# Save to database (which will also commit changes)
|
# Commit session to get correct row numbers if more tracks added
|
||||||
self.parent().visible_playlist_tab().save_playlist(self.session)
|
self.session.commit()
|
||||||
# Clear note field and select search text to make it easier for
|
# Clear note field and select search text to make it easier for
|
||||||
# next search
|
# next search
|
||||||
self.ui.txtNote.clear()
|
self.ui.txtNote.clear()
|
||||||
@ -1603,6 +1584,7 @@ class SelectPlaylistDialog(QDialog):
|
|||||||
self.ui.buttonBox.rejected.connect(self.close)
|
self.ui.buttonBox.rejected.connect(self.close)
|
||||||
self.session = session
|
self.session = session
|
||||||
self.playlist = None
|
self.playlist = None
|
||||||
|
self.plid = None
|
||||||
|
|
||||||
record = Settings.get_int_settings(
|
record = Settings.get_int_settings(
|
||||||
self.session, "select_playlist_dialog_width")
|
self.session, "select_playlist_dialog_width")
|
||||||
@ -1681,6 +1663,6 @@ if __name__ == "__main__":
|
|||||||
send_mail(Config.ERRORS_TO, Config.ERRORS_FROM,
|
send_mail(Config.ERRORS_TO, Config.ERRORS_FROM,
|
||||||
"Exception from musicmuster", msg)
|
"Exception from musicmuster", msg)
|
||||||
|
|
||||||
print("\033[1;31;47mUnhandled exception starts")
|
print("\033[1;31;47mUnhandled exception starts\033[1;37;40m")
|
||||||
stackprinter.show(style="darkbg")
|
stackprinter.show(style="darkbg2")
|
||||||
print("Unhandled exception ends\033[1;37;40m")
|
print("\033[1;31;47mUnhandled exception ends\033[1;37;40m")
|
||||||
|
|||||||
351
app/playlists.py
351
app/playlists.py
@ -1,5 +1,5 @@
|
|||||||
import re
|
import re
|
||||||
import stackprinter # type: ignore
|
import stackprinter
|
||||||
import subprocess
|
import subprocess
|
||||||
import threading
|
import threading
|
||||||
|
|
||||||
@ -34,6 +34,7 @@ from PyQt5.QtWidgets import (
|
|||||||
QStyledItemDelegate,
|
QStyledItemDelegate,
|
||||||
QTableWidget,
|
QTableWidget,
|
||||||
QTableWidgetItem,
|
QTableWidgetItem,
|
||||||
|
QTextEdit,
|
||||||
QWidget
|
QWidget
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -64,6 +65,8 @@ MINIMUM_ROW_HEIGHT = 30
|
|||||||
|
|
||||||
|
|
||||||
class RowMeta:
|
class RowMeta:
|
||||||
|
CLEAR = 0
|
||||||
|
NOTE = 1
|
||||||
UNREADABLE = 2
|
UNREADABLE = 2
|
||||||
NEXT = 3
|
NEXT = 3
|
||||||
CURRENT = 4
|
CURRENT = 4
|
||||||
@ -196,13 +199,23 @@ class PlaylistTab(QTableWidget):
|
|||||||
# self.setSortingEnabled(True)
|
# self.setSortingEnabled(True)
|
||||||
|
|
||||||
# Now load our tracks and notes
|
# Now load our tracks and notes
|
||||||
self.populate_display(session, self.playlist_id)
|
self.populate(session, self.playlist_id)
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return f"<PlaylistTab(id={self.playlist_id}>"
|
return f"<PlaylistTab(id={self.playlist_id}>"
|
||||||
|
|
||||||
# ########## Events other than cell editing ##########
|
# ########## Events other than cell editing ##########
|
||||||
|
|
||||||
|
def closeEvent(self, event) -> None:
|
||||||
|
"""Handle closing playist tab"""
|
||||||
|
|
||||||
|
with Session() as session:
|
||||||
|
# Record playlist as closed
|
||||||
|
playlist = session.get(Playlists, self.playlist_id)
|
||||||
|
playlist.close(session)
|
||||||
|
|
||||||
|
event.accept()
|
||||||
|
|
||||||
def dropEvent(self, event: QDropEvent) -> None:
|
def dropEvent(self, event: QDropEvent) -> None:
|
||||||
"""
|
"""
|
||||||
Handle drag/drop of rows
|
Handle drag/drop of rows
|
||||||
@ -314,8 +327,8 @@ class PlaylistTab(QTableWidget):
|
|||||||
act_setnext.triggered.connect(
|
act_setnext.triggered.connect(
|
||||||
lambda: self._set_next(session, row_number))
|
lambda: self._set_next(session, row_number))
|
||||||
|
|
||||||
|
# Open in Audacity
|
||||||
if not current:
|
if not current:
|
||||||
# Open in Audacity
|
|
||||||
act_audacity = self.menu.addAction(
|
act_audacity = self.menu.addAction(
|
||||||
"Open in Audacity")
|
"Open in Audacity")
|
||||||
act_audacity.triggered.connect(
|
act_audacity.triggered.connect(
|
||||||
@ -544,17 +557,6 @@ class PlaylistTab(QTableWidget):
|
|||||||
self.clearSelection()
|
self.clearSelection()
|
||||||
self.setDragEnabled(False)
|
self.setDragEnabled(False)
|
||||||
|
|
||||||
def get_new_row_number(self) -> int:
|
|
||||||
"""
|
|
||||||
Return the selected row or the row count if no row selected
|
|
||||||
(ie, new row will be appended)
|
|
||||||
"""
|
|
||||||
|
|
||||||
if self.selectionModel().hasSelection():
|
|
||||||
return self.currentRow()
|
|
||||||
else:
|
|
||||||
return self.rowCount()
|
|
||||||
|
|
||||||
def get_selected_playlistrow_ids(self) -> Optional[List]:
|
def get_selected_playlistrow_ids(self) -> Optional[List]:
|
||||||
"""
|
"""
|
||||||
Return a list of PlaylistRow ids of the selected rows
|
Return a list of PlaylistRow ids of the selected rows
|
||||||
@ -582,49 +584,63 @@ class PlaylistTab(QTableWidget):
|
|||||||
to do the heavy lifing.
|
to do the heavy lifing.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
row_number = self.get_new_row_number()
|
# PlaylistRows object requires a row number, but that number
|
||||||
|
# can be reset by calling PlaylistRows.fixup_rownumbers() later,
|
||||||
|
# so just fudge a row number for now.
|
||||||
|
row_number = 0
|
||||||
plr = PlaylistRows(session, self.playlist_id, None, row_number, note)
|
plr = PlaylistRows(session, self.playlist_id, None, row_number, note)
|
||||||
self.insert_row(session, plr, repaint)
|
self.insert_row(session, plr)
|
||||||
self.save_playlist(session)
|
PlaylistRows.fixup_rownumbers(session, self.playlist_id)
|
||||||
|
if repaint:
|
||||||
|
self.update_display(session, clear_selection=False)
|
||||||
|
|
||||||
def insert_row(self, session: Session, plr: PlaylistRows,
|
def insert_row(self, session: Session, row_data: PlaylistRows,
|
||||||
repaint: bool = True) -> None:
|
repaint: bool = True) -> None:
|
||||||
"""
|
"""
|
||||||
Insert passed playlist row (plr) into playlist tab.
|
Insert a row into playlist tab.
|
||||||
|
|
||||||
|
If playlist has a row selected, add new row above. Otherwise,
|
||||||
|
add to end of playlist.
|
||||||
|
|
||||||
|
Note: we ignore the row number in the PlaylistRows record. That is
|
||||||
|
used only to order the query that generates the records.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
row = plr.row_number
|
if self.selectionModel().hasSelection():
|
||||||
|
row = self.currentRow()
|
||||||
|
else:
|
||||||
|
row = self.rowCount()
|
||||||
self.insertRow(row)
|
self.insertRow(row)
|
||||||
|
|
||||||
# Add row metadata to userdata column
|
# Add row metadata to userdata column
|
||||||
userdata_item = QTableWidgetItem()
|
userdata_item = QTableWidgetItem()
|
||||||
userdata_item.setData(self.ROW_FLAGS, 0)
|
userdata_item.setData(self.ROW_FLAGS, 0)
|
||||||
userdata_item.setData(self.PLAYLISTROW_ID, plr.id)
|
userdata_item.setData(self.PLAYLISTROW_ID, row_data.id)
|
||||||
userdata_item.setData(self.ROW_TRACK_ID, plr.track_id)
|
userdata_item.setData(self.ROW_TRACK_ID, row_data.track_id)
|
||||||
self.setItem(row, USERDATA, userdata_item)
|
self.setItem(row, USERDATA, userdata_item)
|
||||||
|
|
||||||
if plr.track_id:
|
if row_data.track_id:
|
||||||
# Add track details to items
|
# Add track details to items
|
||||||
try:
|
try:
|
||||||
start_gap = plr.track.start_gap
|
start_gap = row_data.track.start_gap
|
||||||
except AttributeError:
|
except:
|
||||||
return
|
return
|
||||||
start_gap_item = QTableWidgetItem(str(start_gap))
|
start_gap_item = QTableWidgetItem(str(start_gap))
|
||||||
if start_gap and start_gap >= 500:
|
if start_gap and start_gap >= 500:
|
||||||
start_gap_item.setBackground(QColor(Config.COLOUR_LONG_START))
|
start_gap_item.setBackground(QColor(Config.COLOUR_LONG_START))
|
||||||
self.setItem(row, START_GAP, start_gap_item)
|
self.setItem(row, START_GAP, start_gap_item)
|
||||||
|
|
||||||
title_item = QTableWidgetItem(plr.track.title)
|
title_item = QTableWidgetItem(row_data.track.title)
|
||||||
log.debug(f"KAE: insert_row:619, {title_item.text()=}")
|
log.debug(f"KAE: insert_row:619, {title_item.text()=}")
|
||||||
self.setItem(row, TITLE, title_item)
|
self.setItem(row, TITLE, title_item)
|
||||||
|
|
||||||
artist_item = QTableWidgetItem(plr.track.artist)
|
artist_item = QTableWidgetItem(row_data.track.artist)
|
||||||
self.setItem(row, ARTIST, artist_item)
|
self.setItem(row, ARTIST, artist_item)
|
||||||
|
|
||||||
duration_item = QTableWidgetItem(
|
duration_item = QTableWidgetItem(
|
||||||
ms_to_mmss(plr.track.duration))
|
ms_to_mmss(row_data.track.duration))
|
||||||
self.setItem(row, DURATION, duration_item)
|
self.setItem(row, DURATION, duration_item)
|
||||||
self._set_row_duration(row, plr.track.duration)
|
self._set_row_duration(row, row_data.track.duration)
|
||||||
|
|
||||||
start_item = QTableWidgetItem()
|
start_item = QTableWidgetItem()
|
||||||
self.setItem(row, START_TIME, start_item)
|
self.setItem(row, START_TIME, start_item)
|
||||||
@ -632,8 +648,8 @@ class PlaylistTab(QTableWidget):
|
|||||||
end_item = QTableWidgetItem()
|
end_item = QTableWidgetItem()
|
||||||
self.setItem(row, END_TIME, end_item)
|
self.setItem(row, END_TIME, end_item)
|
||||||
|
|
||||||
if plr.track.bitrate:
|
if row_data.track.bitrate:
|
||||||
bitrate = str(plr.track.bitrate)
|
bitrate = str(row_data.track.bitrate)
|
||||||
else:
|
else:
|
||||||
bitrate = ""
|
bitrate = ""
|
||||||
bitrate_item = QTableWidgetItem(bitrate)
|
bitrate_item = QTableWidgetItem(bitrate)
|
||||||
@ -641,23 +657,23 @@ class PlaylistTab(QTableWidget):
|
|||||||
|
|
||||||
# As we have track info, any notes should be contained in
|
# As we have track info, any notes should be contained in
|
||||||
# the notes column
|
# the notes column
|
||||||
notes_item = QTableWidgetItem(plr.note)
|
notes_item = QTableWidgetItem(row_data.note)
|
||||||
self.setItem(row, ROW_NOTES, notes_item)
|
self.setItem(row, ROW_NOTES, notes_item)
|
||||||
|
|
||||||
last_playtime = Playdates.last_played(session, plr.track.id)
|
last_playtime = Playdates.last_played(session, row_data.track.id)
|
||||||
last_played_str = get_relative_date(last_playtime)
|
last_played_str = get_relative_date(last_playtime)
|
||||||
last_played_item = QTableWidgetItem(last_played_str)
|
last_played_item = QTableWidgetItem(last_played_str)
|
||||||
self.setItem(row, LASTPLAYED, last_played_item)
|
self.setItem(row, LASTPLAYED, last_played_item)
|
||||||
|
|
||||||
# Mark track if file is unreadable
|
# Mark track if file is unreadable
|
||||||
if not file_is_readable(plr.track.path):
|
if not file_is_readable(row_data.track.path):
|
||||||
self._set_unreadable_row(row)
|
self._set_unreadable_row(row)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
# This is a section header so it must have note text
|
# This is a section header so it must have note text
|
||||||
if plr.note is None:
|
if row_data.note is None:
|
||||||
log.debug(
|
log.debug(
|
||||||
f"insert_row({plr=}) with no track_id and no note"
|
f"insert_row({row_data=}) with no track_id and no note"
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
@ -671,16 +687,17 @@ class PlaylistTab(QTableWidget):
|
|||||||
continue
|
continue
|
||||||
self.setItem(row, i, QTableWidgetItem())
|
self.setItem(row, i, QTableWidgetItem())
|
||||||
self.setSpan(row, HEADER_NOTES_COLUMN, 1, len(columns) - 1)
|
self.setSpan(row, HEADER_NOTES_COLUMN, 1, len(columns) - 1)
|
||||||
notes_item = QTableWidgetItem(plr.note)
|
notes_item = QTableWidgetItem(row_data.note)
|
||||||
self.setItem(row, HEADER_NOTES_COLUMN, notes_item)
|
self.setItem(row, HEADER_NOTES_COLUMN, notes_item)
|
||||||
|
|
||||||
# Save (no) track_id
|
# Save (no) track_id
|
||||||
userdata_item.setData(self.ROW_TRACK_ID, 0)
|
userdata_item.setData(self.ROW_TRACK_ID, 0)
|
||||||
|
|
||||||
if repaint:
|
if repaint:
|
||||||
|
self.save_playlist(session)
|
||||||
self.update_display(session, clear_selection=False)
|
self.update_display(session, clear_selection=False)
|
||||||
|
|
||||||
def insert_track(self, session: Session, track: Tracks,
|
def insert_track(self, session: Session, track: Optional[Tracks],
|
||||||
note: str = None, repaint: bool = True) -> None:
|
note: str = None, repaint: bool = True) -> None:
|
||||||
"""
|
"""
|
||||||
Insert track into playlist tab.
|
Insert track into playlist tab.
|
||||||
@ -692,30 +709,20 @@ class PlaylistTab(QTableWidget):
|
|||||||
to do the heavy lifing.
|
to do the heavy lifing.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if not track and track.id:
|
# PlaylistRows object requires a row number, but that number
|
||||||
log.debug(
|
# can be reset by calling PlaylistRows.fixup_rownumbers() later,
|
||||||
f"insert_track({session=}, {track=}, {note=}, {repaint=}"
|
# so just fudge a row number for now.
|
||||||
" called with either no track or no track.id"
|
row_number = 0
|
||||||
)
|
if track:
|
||||||
return
|
track_id = track.id
|
||||||
|
else:
|
||||||
row_number = self.get_new_row_number()
|
track_id = None
|
||||||
|
plr = PlaylistRows(session, self.playlist_id,
|
||||||
# Check to see whether track is already in playlist
|
track_id, row_number, note)
|
||||||
existing_plr = PlaylistRows.get_track_plr(session, track.id,
|
self.insert_row(session, plr)
|
||||||
self.playlist_id)
|
PlaylistRows.fixup_rownumbers(session, self.playlist_id)
|
||||||
if existing_plr and ask_yes_no("Duplicate row",
|
if repaint:
|
||||||
"Track already in playlist. "
|
self.update_display(session, clear_selection=False)
|
||||||
"Move to new location?"):
|
|
||||||
# Yes it is and we shoudl reuse it
|
|
||||||
return self._move_row(session, existing_plr, row_number)
|
|
||||||
|
|
||||||
# Build playlist_row object
|
|
||||||
plr = PlaylistRows(session, self.playlist_id, track.id,
|
|
||||||
row_number, note)
|
|
||||||
self.insert_row(session, plr, repaint)
|
|
||||||
# Let display update, then save playlist
|
|
||||||
QTimer.singleShot(0, lambda: self.save_playlist(session))
|
|
||||||
|
|
||||||
def play_started(self, session: Session) -> None:
|
def play_started(self, session: Session) -> None:
|
||||||
"""
|
"""
|
||||||
@ -764,10 +771,10 @@ class PlaylistTab(QTableWidget):
|
|||||||
self._clear_current_track_row()
|
self._clear_current_track_row()
|
||||||
self.current_track_start_time = None
|
self.current_track_start_time = None
|
||||||
|
|
||||||
def populate_display(self, session: Session, playlist_id: int,
|
def populate(self, session: Session, playlist_id: int,
|
||||||
scroll_to_top: bool = True) -> None:
|
scroll_to_top: bool = True) -> None:
|
||||||
"""
|
"""
|
||||||
Populate display from the associated playlist ID
|
Populate from the associated playlist ID
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Sanity check row numbering before we load
|
# Sanity check row numbering before we load
|
||||||
@ -778,8 +785,8 @@ class PlaylistTab(QTableWidget):
|
|||||||
|
|
||||||
# Add the rows
|
# Add the rows
|
||||||
playlist = session.get(Playlists, playlist_id)
|
playlist = session.get(Playlists, playlist_id)
|
||||||
for plr in playlist.rows:
|
for row in playlist.rows:
|
||||||
self.insert_row(session, plr, repaint=False)
|
self.insert_row(session, row, repaint=False)
|
||||||
|
|
||||||
# Scroll to top
|
# Scroll to top
|
||||||
if scroll_to_top:
|
if scroll_to_top:
|
||||||
@ -815,39 +822,22 @@ class PlaylistTab(QTableWidget):
|
|||||||
|
|
||||||
def save_playlist(self, session: Session) -> None:
|
def save_playlist(self, session: Session) -> None:
|
||||||
"""
|
"""
|
||||||
Get the PlaylistRow objects for each row in the display. Correct
|
All playlist rows have a PlaylistRows id. Check that that id points
|
||||||
the row_number and playlist_id if necessary. Remove any row
|
to this playlist (in case track has been moved from other) and that
|
||||||
numbers in the database that are higher than the last row in
|
the row number is correct (in case tracks have been reordered).
|
||||||
the display.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Build a dictionary of
|
|
||||||
# {display_row_number: display_row_plr_id}
|
|
||||||
display_plr_ids = {row_number: self._get_playlistrow_id(row_number)
|
|
||||||
for row_number in range(self.rowCount())}
|
|
||||||
|
|
||||||
# Now build a dictionary of
|
|
||||||
# {display_row_number: display_row_plr}
|
|
||||||
plr_dict_by_id = PlaylistRows.indexed_by_id(session,
|
|
||||||
display_plr_ids.values())
|
|
||||||
|
|
||||||
# Finally a dictionary of
|
|
||||||
# {display_row_number: plr}
|
|
||||||
row_plr = {row_number: plr_dict_by_id[display_plr_ids[row_number]]
|
|
||||||
for row_number in range(self.rowCount())}
|
|
||||||
|
|
||||||
# Ensure all row plrs have correct row number and playlist_id
|
|
||||||
for row in range(self.rowCount()):
|
for row in range(self.rowCount()):
|
||||||
row_plr[row].row_number = row
|
plr = session.get(PlaylistRows, self._get_playlistrow_id(row))
|
||||||
row_plr[row].playlist_id = self.playlist_id
|
# Set the row number and playlist id (even if correct)
|
||||||
|
plr.row_number = row
|
||||||
|
plr.playlist_id = self.playlist_id
|
||||||
|
|
||||||
# Any rows in the database for this playlist that have a plr id
|
# Any rows in the database with a row_number higher that the
|
||||||
# that's not in the displayed playlist need to be deleted.
|
# current value of 'row' should not be there. Commit session
|
||||||
|
# first to ensure any changes made above are committed.
|
||||||
# Ensure changes flushed
|
|
||||||
session.commit()
|
session.commit()
|
||||||
PlaylistRows.delete_plrids_not_in_list(session, self.playlist_id,
|
PlaylistRows.delete_higher_rows(session, self.playlist_id, row)
|
||||||
display_plr_ids.values())
|
|
||||||
|
|
||||||
def scroll_current_to_top(self) -> None:
|
def scroll_current_to_top(self) -> None:
|
||||||
"""Scroll currently-playing row to top"""
|
"""Scroll currently-playing row to top"""
|
||||||
@ -870,6 +860,65 @@ class PlaylistTab(QTableWidget):
|
|||||||
return
|
return
|
||||||
self._search(next=True)
|
self._search(next=True)
|
||||||
|
|
||||||
|
def _search(self, next: bool = True) -> None:
|
||||||
|
"""
|
||||||
|
Select next/previous row containg self.search_string. Start from
|
||||||
|
top selected row if there is one, else from top.
|
||||||
|
|
||||||
|
Wrap at last/first row.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if not self.search_text:
|
||||||
|
return
|
||||||
|
|
||||||
|
selected_row = self._get_selected_row()
|
||||||
|
if next:
|
||||||
|
if selected_row is not None and selected_row < self.rowCount() - 1:
|
||||||
|
starting_row = selected_row + 1
|
||||||
|
else:
|
||||||
|
starting_row = 0
|
||||||
|
else:
|
||||||
|
if selected_row is not None and selected_row > 0:
|
||||||
|
starting_row = selected_row - 1
|
||||||
|
else:
|
||||||
|
starting_row = self.rowCount() - 1
|
||||||
|
|
||||||
|
wrapped = False
|
||||||
|
match_row = None
|
||||||
|
row = starting_row
|
||||||
|
needle = self.search_text.lower()
|
||||||
|
while True:
|
||||||
|
# Check for match in title, artist or notes
|
||||||
|
title = self._get_row_title(row)
|
||||||
|
if title and needle in title.lower():
|
||||||
|
match_row = row
|
||||||
|
break
|
||||||
|
artist = self._get_row_artist(row)
|
||||||
|
if artist and needle in artist.lower():
|
||||||
|
match_row = row
|
||||||
|
break
|
||||||
|
note = self._get_row_note(row)
|
||||||
|
if note and needle in note.lower():
|
||||||
|
match_row = row
|
||||||
|
break
|
||||||
|
if next:
|
||||||
|
row += 1
|
||||||
|
if wrapped and row >= starting_row:
|
||||||
|
break
|
||||||
|
if row >= self.rowCount():
|
||||||
|
row = 0
|
||||||
|
wrapped = True
|
||||||
|
else:
|
||||||
|
row -= 1
|
||||||
|
if wrapped and row <= starting_row:
|
||||||
|
break
|
||||||
|
if row < 0:
|
||||||
|
row = self.rowCount() - 1
|
||||||
|
wrapped = True
|
||||||
|
|
||||||
|
if match_row is not None:
|
||||||
|
self.selectRow(row)
|
||||||
|
|
||||||
def search_next(self) -> None:
|
def search_next(self) -> None:
|
||||||
"""
|
"""
|
||||||
Select next row containg self.search_string.
|
Select next row containg self.search_string.
|
||||||
@ -960,6 +1009,12 @@ class PlaylistTab(QTableWidget):
|
|||||||
|
|
||||||
self.selectRow(row)
|
self.selectRow(row)
|
||||||
|
|
||||||
|
def set_searchtext(self, text: Optional[str]) -> None:
|
||||||
|
"""Set the search text and find first match"""
|
||||||
|
|
||||||
|
self.search_text = text
|
||||||
|
self._find_next_match()
|
||||||
|
|
||||||
def set_selected_as_next(self) -> None:
|
def set_selected_as_next(self) -> None:
|
||||||
"""Sets the select track as next to play"""
|
"""Sets the select track as next to play"""
|
||||||
|
|
||||||
@ -999,6 +1054,7 @@ class PlaylistTab(QTableWidget):
|
|||||||
p.row_number for p in PlaylistRows.get_played_rows(
|
p.row_number for p in PlaylistRows.get_played_rows(
|
||||||
session, self.playlist_id)
|
session, self.playlist_id)
|
||||||
]
|
]
|
||||||
|
unreadable: List[int] = self._get_unreadable_track_rows()
|
||||||
|
|
||||||
next_start_time = None
|
next_start_time = None
|
||||||
section_start_plr = None
|
section_start_plr = None
|
||||||
@ -1288,8 +1344,9 @@ class PlaylistTab(QTableWidget):
|
|||||||
Delete mutliple rows
|
Delete mutliple rows
|
||||||
|
|
||||||
Actions required:
|
Actions required:
|
||||||
|
- Delete the rows from the PlaylistRows table
|
||||||
|
- Correct the row numbers in the PlaylistRows table
|
||||||
- Remove the rows from the display
|
- Remove the rows from the display
|
||||||
- Save the playlist
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Delete rows from database
|
# Delete rows from database
|
||||||
@ -1302,10 +1359,13 @@ class PlaylistTab(QTableWidget):
|
|||||||
f"Really delete {row_count} row{plural}?"):
|
f"Really delete {row_count} row{plural}?"):
|
||||||
return
|
return
|
||||||
|
|
||||||
self.remove_selected_rows()
|
|
||||||
|
|
||||||
with Session() as session:
|
with Session() as session:
|
||||||
QTimer.singleShot(0, lambda: self.save_playlist(session))
|
PlaylistRows.delete_rows(session, plr_ids)
|
||||||
|
|
||||||
|
# Fix up row numbers left in this playlist
|
||||||
|
PlaylistRows.fixup_rownumbers(session, self.playlist_id)
|
||||||
|
# Remove selected rows from display
|
||||||
|
self.remove_selected_rows()
|
||||||
|
|
||||||
def _drop_on(self, event):
|
def _drop_on(self, event):
|
||||||
"""
|
"""
|
||||||
@ -1319,6 +1379,16 @@ class PlaylistTab(QTableWidget):
|
|||||||
return (index.row() + 1 if self._is_below(event.pos(), index)
|
return (index.row() + 1 if self._is_below(event.pos(), index)
|
||||||
else index.row())
|
else index.row())
|
||||||
|
|
||||||
|
def _find_next_match(self) -> None:
|
||||||
|
"""
|
||||||
|
Find next match of search_text. Start at first highlighted row
|
||||||
|
if there is one, else from top of playlist.
|
||||||
|
"""
|
||||||
|
|
||||||
|
start_row = self._get_selected_row()
|
||||||
|
if start_row is None:
|
||||||
|
start_row = 0
|
||||||
|
|
||||||
def _find_next_track_row(self, session: Session,
|
def _find_next_track_row(self, session: Session,
|
||||||
starting_row: int = None) -> Optional[int]:
|
starting_row: int = None) -> Optional[int]:
|
||||||
"""
|
"""
|
||||||
@ -1470,6 +1540,16 @@ class PlaylistTab(QTableWidget):
|
|||||||
[row for row in set([a.row() for a in self.selectedItems()])]
|
[row for row in set([a.row() for a in self.selectedItems()])]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def _get_unreadable_track_rows(self) -> List[int]:
|
||||||
|
"""Return rows marked as unreadable, or None"""
|
||||||
|
|
||||||
|
return self._meta_search(RowMeta.UNREADABLE, one=False)
|
||||||
|
|
||||||
|
# def _header_click(self, index: int) -> None:
|
||||||
|
# """Handle playlist header click"""
|
||||||
|
|
||||||
|
# print(f"_header_click({index=})")
|
||||||
|
|
||||||
def _info_row(self, track_id: int) -> None:
|
def _info_row(self, track_id: int) -> None:
|
||||||
"""Display popup with info re row"""
|
"""Display popup with info re row"""
|
||||||
|
|
||||||
@ -1578,20 +1658,6 @@ class PlaylistTab(QTableWidget):
|
|||||||
new_metadata = self._meta_get(row) | (1 << attribute)
|
new_metadata = self._meta_get(row) | (1 << attribute)
|
||||||
self.item(row, USERDATA).setData(self.ROW_FLAGS, new_metadata)
|
self.item(row, USERDATA).setData(self.ROW_FLAGS, new_metadata)
|
||||||
|
|
||||||
def _move_row(self, session: Session, plr: PlaylistRows,
|
|
||||||
new_row_number: int) -> None:
|
|
||||||
"""Move playlist row to new_row_number using parent copy/paste"""
|
|
||||||
|
|
||||||
# Remove source row
|
|
||||||
self.removeRow(plr.row_number)
|
|
||||||
# Fixup plr row number
|
|
||||||
if plr.row_number < new_row_number:
|
|
||||||
plr.row_number = new_row_number - 1
|
|
||||||
else:
|
|
||||||
plr.row_number = new_row_number
|
|
||||||
self.insert_row(session, plr)
|
|
||||||
self.save_playlist(session)
|
|
||||||
|
|
||||||
def _mplayer_play(self, track_id: int) -> None:
|
def _mplayer_play(self, track_id: int) -> None:
|
||||||
"""Play track with mplayer"""
|
"""Play track with mplayer"""
|
||||||
|
|
||||||
@ -1703,65 +1769,6 @@ class PlaylistTab(QTableWidget):
|
|||||||
scroll_item = self.item(top_row, 0)
|
scroll_item = self.item(top_row, 0)
|
||||||
self.scrollToItem(scroll_item, QAbstractItemView.PositionAtTop)
|
self.scrollToItem(scroll_item, QAbstractItemView.PositionAtTop)
|
||||||
|
|
||||||
def _search(self, next: bool = True) -> None:
|
|
||||||
"""
|
|
||||||
Select next/previous row containg self.search_string. Start from
|
|
||||||
top selected row if there is one, else from top.
|
|
||||||
|
|
||||||
Wrap at last/first row.
|
|
||||||
"""
|
|
||||||
|
|
||||||
if not self.search_text:
|
|
||||||
return
|
|
||||||
|
|
||||||
selected_row = self._get_selected_row()
|
|
||||||
if next:
|
|
||||||
if selected_row is not None and selected_row < self.rowCount() - 1:
|
|
||||||
starting_row = selected_row + 1
|
|
||||||
else:
|
|
||||||
starting_row = 0
|
|
||||||
else:
|
|
||||||
if selected_row is not None and selected_row > 0:
|
|
||||||
starting_row = selected_row - 1
|
|
||||||
else:
|
|
||||||
starting_row = self.rowCount() - 1
|
|
||||||
|
|
||||||
wrapped = False
|
|
||||||
match_row = None
|
|
||||||
row = starting_row
|
|
||||||
needle = self.search_text.lower()
|
|
||||||
while True:
|
|
||||||
# Check for match in title, artist or notes
|
|
||||||
title = self._get_row_title(row)
|
|
||||||
if title and needle in title.lower():
|
|
||||||
match_row = row
|
|
||||||
break
|
|
||||||
artist = self._get_row_artist(row)
|
|
||||||
if artist and needle in artist.lower():
|
|
||||||
match_row = row
|
|
||||||
break
|
|
||||||
note = self._get_row_note(row)
|
|
||||||
if note and needle in note.lower():
|
|
||||||
match_row = row
|
|
||||||
break
|
|
||||||
if next:
|
|
||||||
row += 1
|
|
||||||
if wrapped and row >= starting_row:
|
|
||||||
break
|
|
||||||
if row >= self.rowCount():
|
|
||||||
row = 0
|
|
||||||
wrapped = True
|
|
||||||
else:
|
|
||||||
row -= 1
|
|
||||||
if wrapped and row <= starting_row:
|
|
||||||
break
|
|
||||||
if row < 0:
|
|
||||||
row = self.rowCount() - 1
|
|
||||||
wrapped = True
|
|
||||||
|
|
||||||
if match_row is not None:
|
|
||||||
self.selectRow(row)
|
|
||||||
|
|
||||||
def _select_event(self) -> None:
|
def _select_event(self) -> None:
|
||||||
"""
|
"""
|
||||||
Called when item selection changes.
|
Called when item selection changes.
|
||||||
|
|||||||
@ -4,6 +4,7 @@
|
|||||||
# the current directory contains a "better" version of the file than the
|
# the current directory contains a "better" version of the file than the
|
||||||
# parent (eg, bettet bitrate).
|
# parent (eg, bettet bitrate).
|
||||||
|
|
||||||
|
import glob
|
||||||
import os
|
import os
|
||||||
import pydymenu # type: ignore
|
import pydymenu # type: ignore
|
||||||
import shutil
|
import shutil
|
||||||
@ -20,6 +21,7 @@ from helpers import (
|
|||||||
|
|
||||||
from models import Tracks
|
from models import Tracks
|
||||||
from dbconfig import Session
|
from dbconfig import Session
|
||||||
|
from thefuzz import process # type: ignore
|
||||||
from sqlalchemy.exc import IntegrityError
|
from sqlalchemy.exc import IntegrityError
|
||||||
from typing import List
|
from typing import List
|
||||||
|
|
||||||
@ -33,6 +35,15 @@ source_dir = '/home/kae/music/Singles/tmp'
|
|||||||
parent_dir = os.path.dirname(source_dir)
|
parent_dir = os.path.dirname(source_dir)
|
||||||
# #########################################################
|
# #########################################################
|
||||||
|
|
||||||
|
|
||||||
|
def insensitive_glob(pattern):
|
||||||
|
"""Helper for case insensitive glob.glob()"""
|
||||||
|
|
||||||
|
def either(c):
|
||||||
|
return '[%s%s]' % (c.lower(), c.upper()) if c.isalpha() else c
|
||||||
|
return glob.glob(''.join(map(either, pattern)))
|
||||||
|
|
||||||
|
|
||||||
name_and_tags: List[str] = []
|
name_and_tags: List[str] = []
|
||||||
tags_not_name: List[str] = []
|
tags_not_name: List[str] = []
|
||||||
multiple_similar: List[str] = []
|
multiple_similar: List[str] = []
|
||||||
@ -126,6 +137,10 @@ def main():
|
|||||||
|
|
||||||
# Try to find a near match
|
# Try to find a near match
|
||||||
|
|
||||||
|
# stem = new_fname.split(".")[0]
|
||||||
|
# matches = insensitive_glob(os.path.join(parent_dir, stem) + '*')
|
||||||
|
# match_count = len(matches)
|
||||||
|
# if match_count == 0:
|
||||||
if process_no_matches:
|
if process_no_matches:
|
||||||
prompt = f"\n file={new_fname}\n title={new_title}\n artist={new_artist}: "
|
prompt = f"\n file={new_fname}\n title={new_title}\n artist={new_artist}: "
|
||||||
# Use fzf to search
|
# Use fzf to search
|
||||||
|
|||||||
@ -16,6 +16,20 @@ from log import log
|
|||||||
from models import Tracks
|
from models import Tracks
|
||||||
|
|
||||||
|
|
||||||
|
def create_track(session, path, normalise=None):
|
||||||
|
"""
|
||||||
|
Create track in database from passed path.
|
||||||
|
|
||||||
|
Return track.
|
||||||
|
"""
|
||||||
|
|
||||||
|
track = Tracks(session, path)
|
||||||
|
|
||||||
|
set_track_metadata(session, track)
|
||||||
|
if normalise or normalise is None and Config.NORMALISE_ON_IMPORT:
|
||||||
|
normalise_track(path)
|
||||||
|
|
||||||
|
|
||||||
def check_db(session):
|
def check_db(session):
|
||||||
"""
|
"""
|
||||||
Database consistency check.
|
Database consistency check.
|
||||||
@ -30,7 +44,7 @@ def check_db(session):
|
|||||||
db_paths = set([a.path for a in Tracks.get_all(session)])
|
db_paths = set([a.path for a in Tracks.get_all(session)])
|
||||||
|
|
||||||
os_paths_list = []
|
os_paths_list = []
|
||||||
for root, _dirs, files in os.walk(Config.ROOT):
|
for root, dirs, files in os.walk(Config.ROOT):
|
||||||
for f in files:
|
for f in files:
|
||||||
path = os.path.join(root, f)
|
path = os.path.join(root, f)
|
||||||
ext = os.path.splitext(f)[1]
|
ext = os.path.splitext(f)[1]
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user