Compare commits

..

No commits in common. "0e3e30391b02e3726221ae97e379ac7774e6dfba" and "94e7508a248efd00a7792f4e8184c91e138d847b" have entirely different histories.

8 changed files with 161 additions and 382 deletions

View File

@ -1,16 +1,9 @@
from datetime import datetime
from PyQt5.QtWidgets import QMessageBox
from datetime import datetime, date
def get_relative_date(past_date, reference_date=None):
"""
Return how long before reference_date past_date is as string.
Params:
@past_date: datetime
@reference_date: datetime, defaults to current date and time
@return: string
Return relative date as string.
"""
if not past_date:
@ -26,21 +19,7 @@ def get_relative_date(past_date, reference_date=None):
if weeks == days == 0:
# Played today, so return time instead
return past_date.strftime("%H:%M")
if weeks == 1:
weeks_str = "week"
else:
weeks_str = "weeks"
if days == 1:
days_str = "day"
else:
days_str = "days"
return f"{weeks} {weeks_str}, {days} {days_str} ago"
def show_warning(title, msg):
"Display a warning to user"
QMessageBox.warning(None, title, msg, buttons=QMessageBox.Cancel)
return f"{weeks} weeks, {days} days ago"
def ms_to_mmss(ms, decimals=0, negative=False):

View File

@ -53,8 +53,6 @@ class Notes(Base):
@staticmethod
def add_note(session, playlist_id, row, text):
"Add note"
DEBUG(f"add_note(playlist_id={playlist_id}, row={row}, text={text})")
note = Notes()
note.playlist_id = playlist_id
@ -66,13 +64,16 @@ class Notes(Base):
@staticmethod
def delete_note(session, id):
"Delete note"
DEBUG(f"delete_note(id={id}")
session.query(Notes).filter(Notes.id == id).delete()
session.commit()
# Not currently used 1 June 2021
# @staticmethod
# def get_note(session, id):
# return session.query(Notes).filter(Notes.id == id).one()
@classmethod
def update_note(cls, session, id, row, text=None):
"""
@ -98,8 +99,6 @@ class Playdates(Base):
@staticmethod
def add_playdate(session, track):
"Record that track was played"
DEBUG(f"add_playdate(track={track})")
pd = Playdates()
pd.lastplayed = datetime.now()
@ -120,17 +119,6 @@ class Playdates(Base):
else:
return None
@staticmethod
def remove_track(session, track_id):
"""
Remove all records of track_id
"""
session.query(Playdates).filter(
Playdates.track_id == track_id,
).delete()
session.commit()
class Playlists(Base):
"""
@ -305,11 +293,18 @@ class PlaylistTracks(Base):
session.commit()
@staticmethod
def get_track_playlists(session, track_id):
"Return all PlaylistTracks objects with this track_id"
def get_playlists_containing_track_id(session, track_id):
return session.query(PlaylistTracks).filter(
playlists = []
playlist_ids = session.query(PlaylistTracks.playlist_id).filter(
PlaylistTracks.track_id == track_id).all()
for p in playlist_ids:
playlist = session.query(Playlists).filter(
Playlists.id == p[0]).first()
if playlist:
playlists.append(playlist)
return playlists
@staticmethod
def move_track(session, from_playlist_id, row, to_playlist_id):
@ -452,22 +447,6 @@ class Tracks(Base):
f"<Track(id={self.id}, title={self.title}, "
f"artist={self.artist}, path={self.path}>"
)
# Not currently used 1 June 2021
# @staticmethod
# def get_note(session, id):
# return session.query(Notes).filter(Notes.id == id).one()
@staticmethod
def get_all_paths(session):
"Return a list of paths of all tracks"
return [a[0] for a in session.query(Tracks.path).all()]
@staticmethod
def get_all_tracks(session):
"Return a list of all tracks"
return session.query(Tracks).all()
@classmethod
def get_or_create(cls, session, path):
@ -489,6 +468,30 @@ class Tracks(Base):
ERROR(f"Can't find track id {id}")
return None
@staticmethod
def get_all_paths(session):
"Return a list of paths of all tracks"
return [a[0] for a in session.query(Tracks.path).all()]
@staticmethod
def get_path(session, id):
try:
return session.query(Tracks.path).filter(Tracks.id == id).one()[0]
except NoResultFound:
ERROR(f"Can't find track id {id}")
return None
@staticmethod
def get_track(session, id):
try:
DEBUG(f"Tracks.get_track(track_id={id})")
track = session.query(Tracks).filter(Tracks.id == id).one()
return track
except NoResultFound:
ERROR(f"get_track({id}): not found")
return None
@staticmethod
def get_track_from_filename(session, filename):
"""
@ -516,29 +519,6 @@ class Tracks(Base):
return session.query(Tracks).filter(Tracks.path == path).first()
@staticmethod
def get_path(session, track_id):
"Return path of passed track_id, or None"
try:
return session.query(Tracks.path).filter(
Tracks.id == track_id).one()[0]
except NoResultFound:
ERROR(f"Can't find track id {track_id}")
return None
@staticmethod
def get_track(session, track_id):
"Return track or None"
try:
DEBUG(f"Tracks.get_track(track_id={track_id})")
track = session.query(Tracks).filter(Tracks.id == track_id).one()
return track
except NoResultFound:
ERROR(f"get_track({track_id}): not found")
return None
@staticmethod
def remove_path(session, path):
"Remove track with passed path from database"
@ -547,33 +527,9 @@ class Tracks(Base):
try:
session.query(Tracks).filter(Tracks.path == path).delete()
session.commit()
except IntegrityError as exception:
ERROR(f"Can't remove track with {path=} ({exception=})")
@staticmethod
def search(session, title=None, artist=None, duration=None):
"""
Return any tracks matching passed criteria
"""
DEBUG(
f"Tracks.search({title=}, {artist=}), {duration=})"
)
if not title and not artist and not duration:
return None
q = session.query(Tracks).filter(False)
if title:
q = q.filter(Tracks.title == title)
if artist:
q = q.filter(Tracks.artist == artist)
if duration:
q = q.filter(Tracks.duration == duration)
return q.all()
@staticmethod
def search_titles(session, text):
return (

View File

@ -8,17 +8,17 @@ import urllib.parse
from datetime import datetime, timedelta
from log import DEBUG, EXCEPTION
from slugify import slugify
from PyQt5.QtCore import Qt, QTimer
from PyQt5.QtGui import QFontMetrics, QPainter
from PyQt5.QtWidgets import (
QApplication,
QDialog,
QFileDialog,
QInputDialog,
QLabel,
QListWidgetItem,
QMainWindow,
QMessageBox,
)
import helpers
@ -34,21 +34,6 @@ from ui.dlg_SelectPlaylist_ui import Ui_dlgSelectPlaylist
from ui.main_window_ui import Ui_MainWindow
class ElideLabel(QLabel):
"""
From https://stackoverflow.com/questions/11446478/
pyside-pyqt-truncate-text-in-qlabel-based-on-minimumsize
"""
def paintEvent(self, event):
painter = QPainter(self)
metrics = QFontMetrics(self.font())
elided = metrics.elidedText(self.text(), Qt.ElideRight, self.width())
painter.drawText(self.rect(), self.alignment(), elided)
class Window(QMainWindow, Ui_MainWindow):
def __init__(self, parent=None):
super().__init__(parent)
@ -154,10 +139,10 @@ class Window(QMainWindow, Ui_MainWindow):
self.actionOpenPlaylist.triggered.connect(self.open_playlist)
self.actionPlay_next.triggered.connect(self.play_next)
self.actionSearch_database.triggered.connect(self.search_database)
self.actionSelect_next_track.triggered.connect(self.select_next_row)
self.actionSelect_next_track.triggered.connect(self.select_next_track)
self.actionSelect_played_tracks.triggered.connect(self.select_played)
self.actionSelect_previous_track.triggered.connect(
self.select_previous_row)
self.select_previous_track)
self.actionSelect_unplayed_tracks.triggered.connect(
self.select_unplayed)
self.actionSetNext.triggered.connect(self.set_next_track)
@ -239,17 +224,13 @@ class Window(QMainWindow, Ui_MainWindow):
def end_of_track_actions(self):
"Clean up after track played"
# Set self.playing to False so that tick() doesn't see
# player=None and kick off end-of-track actions
self.playing = False
# Clean up metadata
self.previous_track = self.current_track
if self.current_track_playlist_tab:
self.current_track_playlist_tab.play_stopped()
self.current_track_playlist_tab.clear_current()
self.current_track_playlist_tab = None
self.current_track_playlist_tab.play_stopped()
self.current_track_playlist_tab.clear_current()
self.current_track_playlist_tab = None
self.current_track = None
self.playing = False
# Clean up display
self.label_end_timer.setText("00:00")
@ -257,9 +238,6 @@ class Window(QMainWindow, Ui_MainWindow):
self.frame_fade.setStyleSheet("")
self.update_headers()
# Enable controls
self.enable_play_next_controls()
def export_playlist_tab(self):
"Export the current playlist to an m3u file"
@ -306,6 +284,10 @@ class Window(QMainWindow, Ui_MainWindow):
if not self.current_track:
return
# Set self.playing to False so that tick() doesn't see
# player=None and kick off end-of-track actions
self.playing = False
self.enable_play_next_controls()
self.previous_track_position = self.music.fade()
self.end_of_track_actions()
@ -432,15 +414,17 @@ class Window(QMainWindow, Ui_MainWindow):
self.music.play(self.current_track.path)
# Update metadata
# Get next track for this playlist. May be None if there is
# no automatic next track, and may later be overriden by
# user selecting a different track on this or another
# playlist.
# TODO is this valid if next track is on different playlist?
next_track_id = self.current_track_playlist_tab.play_started()
if next_track_id is not None:
self.next_track = Tracks.get_track(session, next_track_id)
self.next_track_playlist_tab = self.current_track_playlist_tab
# Check we can read it
if not self.file_is_readable(self.next_track.path):
self.show_warning(
"Can't read next track",
self.next_track.path)
else:
self.next_track = self.next_track_playlist_tab = None
@ -481,20 +465,20 @@ class Window(QMainWindow, Ui_MainWindow):
playlist_db = Playlists.open(session, dlg.plid)
self.load_playlist(session, playlist_db)
def select_next_row(self):
"Select next or first row in playlist"
def select_next_track(self):
"Select next or first track in playlist"
self.visible_playlist_tab().select_next_row()
self.visible_playlist_tab().select_next_track()
def select_played(self):
"Select all played tracks in playlist"
self.visible_playlist_tab().select_played_tracks()
def select_previous_row(self):
"Select previous or first row in playlist"
def select_previous_track(self):
"Select previous or first track in playlist"
self.visible_playlist_tab().select_previous_row()
self.visible_playlist_tab().select_previous_track()
def set_next_track(self, next_track_id=None):
"Set selected track as next"
@ -516,6 +500,11 @@ class Window(QMainWindow, Ui_MainWindow):
self.visible_playlist_tab().select_unplayed_tracks()
def show_warning(self, title, msg):
"Display a warning to user"
QMessageBox.warning(None, title, msg, buttons=QMessageBox.Cancel)
def song_info_search(self):
"""
Open browser tabs for Wikipedia, searching for
@ -544,15 +533,15 @@ class Window(QMainWindow, Ui_MainWindow):
DEBUG("musicmuster.stop()")
self.stop_playing(fade=False)
self.enable_play_next_controls()
def stop_playing(self, fade=True):
"Stop playing current track"
DEBUG(f"musicmuster.stop_playing({fade=})", True)
DEBUG("musicmuster.stop_playing()", True)
if not self.music.playing():
DEBUG("musicmuster.stop_playing(): not playing", True)
self.end_of_track_actions()
return
self.previous_track_position = self.music.get_position()
@ -560,8 +549,8 @@ class Window(QMainWindow, Ui_MainWindow):
DEBUG("musicmuster.stop_playing(): fading music", True)
self.music.fade()
else:
DEBUG("musicmuster.stop_playing(): stopping music", True)
self.music.stop()
DEBUG("musicmuster.stop_playing(): stopping music", True)
self.end_of_track_actions()
@ -629,20 +618,16 @@ class Window(QMainWindow, Ui_MainWindow):
# Time to fade
self.label_fade_timer.setText(helpers.ms_to_mmss(time_to_fade))
# If silent in the next 5 seconds, put warning colour on
# time to silence box and enable play controls
# Time to silence
if time_to_silence <= 5500:
self.frame_silent.setStyleSheet(
f"background: {Config.COLOUR_ENDING_TIMER}"
)
self.enable_play_next_controls()
# Set warning colour on time to silence box when fade starts
elif time_to_fade <= 500:
self.frame_silent.setStyleSheet(
f"background: {Config.COLOUR_WARNING_TIMER}"
)
# Five seconds before fade starts, set warning colour on
# time to silence box and enable play controls
elif time_to_fade <= 5500:
self.frame_fade.setStyleSheet(
f"background: {Config.COLOUR_WARNING_TIMER}"

View File

@ -16,7 +16,7 @@ import os
from config import Config
from datetime import datetime, timedelta
from helpers import get_relative_date, show_warning
from helpers import get_relative_date
from log import DEBUG, ERROR
from model import (
Notes, Playdates, Playlists, PlaylistTracks, Session, Settings, Tracks
@ -155,8 +155,8 @@ class PlaylistTab(QTableWidget):
self.menu.addSeparator()
act_delete = self.menu.addAction('Delete')
act_delete.triggered.connect(lambda: self._delete_row(row))
act_info = self.menu.addAction('Info')
act_info.triggered.connect(lambda: self._info_row(row))
act_delete = self.menu.addAction('Info')
act_delete.triggered.connect(lambda: self._info_row(row))
return super(PlaylistTab, self).eventFilter(source, event)
@ -357,10 +357,7 @@ class PlaylistTab(QTableWidget):
# Scroll to put current track in centre
scroll_to = self.item(current_row, self.COL_INDEX)
self.scrollToItem(scroll_to, QAbstractItemView.PositionAtCenter)
# Get next track
next_track_row = self._find_next_track_row()
next_track_id = self._set_next(next_track_row)
next_track_id = self._mark_next_track()
self._repaint()
return next_track_id
@ -410,9 +407,9 @@ class PlaylistTab(QTableWidget):
# Called when we change tabs
self._repaint()
def select_next_row(self):
def select_next_track(self):
"""
Select next or first row. Don't select notes. Wrap at last row.
Select next or first track. Don't select notes. Wrap at last row.
"""
selected_rows = [row for row in
@ -457,7 +454,7 @@ class PlaylistTab(QTableWidget):
# Reset extended selection
self.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection)
def select_previous_row(self):
def select_previous_track(self):
"""
Select previous or last track. Don't select notes. Wrap at first row.
"""
@ -517,8 +514,7 @@ class PlaylistTab(QTableWidget):
if not self.selectionModel().hasSelection():
return
self._set_next(self.currentRow())
self._repaint()
return self._set_next(self.currentRow())
# ########## Internally called functions ##########
@ -534,11 +530,6 @@ class PlaylistTab(QTableWidget):
duration = Tracks.get_duration(session, self._get_row_id(row))
return start + timedelta(milliseconds=duration)
def _can_read_track(self, track):
"Check track file is readable"
return os.access(track.path, os.R_OK)
def _context_menu(self, pos):
self.menu.exec_(self.mapToGlobal(pos))
@ -549,10 +540,12 @@ class PlaylistTab(QTableWidget):
DEBUG(f"playlist._delete_row({row})")
if row == self._meta_get_current():
show_warning("Silly", "Can't delete playing track")
# TODO
DEBUG("playlist._delete_row(): Can't delete playing track")
return
elif row == self._meta_get_next():
show_warning("Safety", "Can't delete next track")
# TODO
DEBUG("playlist._delete_row(): Can't delete next track")
return
with Session() as session:
@ -624,12 +617,12 @@ class PlaylistTab(QTableWidget):
if not track:
txt = f"Track not found (track.id={id})"
else:
txt = (
f"Title: {track.title}\n"
f"Artist: {track.artist}\n"
f"Path: {track.path}\n"
f"Track ID: {track.id}"
)
txt = f"""
Title: {track.title}
Artist: {track.artist}
Path: {track.path}
Track ID: {track.id}
"""
info = QMessageBox(self)
info.setIcon(QMessageBox.Information)
info.setText(txt)
@ -650,13 +643,13 @@ class PlaylistTab(QTableWidget):
and pos.y() >= rect.center().y() # noqa W503
)
def _find_next_track_row(self):
def _mark_next_track(self):
"""
Find next track to play.
If not found, return None.
If found, return row number.
If found, mark row with metadata and return track_id.
"""
found_next_track = False
@ -669,14 +662,16 @@ class PlaylistTab(QTableWidget):
for row in range(start, self.rowCount()):
if row in notes_rows:
continue
self._meta_set_next(row)
found_next_track = True
break
if found_next_track:
return row
else:
if not found_next_track:
return None
track_id = self._get_row_id(row)
return track_id
def _meta_clear(self, row):
"Clear metadata for row"
@ -813,12 +808,11 @@ class PlaylistTab(QTableWidget):
track_id = self._get_row_id(row)
if track_id:
if self._track_path_is_readable(track_id):
self._meta_set_next(row)
self._meta_set_next(self.currentRow())
self.master_process.set_next_track(track_id)
else:
self._meta_set_unreadable(row)
track_id = None
return track_id
self._meta_set_unreadable(self.currentRow())
self._repaint()
def _repaint(self, clear_selection=True):
"Set row colours, fonts, etc"

View File

@ -7,7 +7,7 @@ import tempfile
from config import Config
from log import DEBUG, INFO
from model import Notes, Playdates, PlaylistTracks, Session, Tracks
from model import Tracks, Playdates, PlaylistTracks, Session
from mutagen.flac import FLAC
from mutagen.mp3 import MP3
from pydub import AudioSegment, effects
@ -115,75 +115,10 @@ def create_track_from_file(session, path):
return track
def full_update_db(session):
def full_update_db():
"Rescan all entries in database"
def log(msg):
INFO(f"full_update_db(): {msg}")
def check_change(track_id, title, attribute, old, new):
if new > (old * 1.1) or new < (old * 0.9):
log(
"\n"
f"track[{track_id}] ({title}) "
f"{attribute} updated from {old} to {new}"
)
# Start with normal update to add new tracks and remove any missing
# files
log("update_db()")
update_db(session)
# Now update track length, silence and fade for every track in
# database
tracks = Tracks.get_all_tracks(session)
total_tracks = len(tracks)
log(f"Processing {total_tracks} tracks")
track_count = 0
for track in tracks:
track_count += 1
print(f"\rTrack {track_count} of {total_tracks}", end='')
# Sanity check
tag = get_music_info(track.path)
if not tag['title']:
log(f"track[{track.id}] {track.title=}: No tag title")
continue
if not tag['artist']:
log(f"track[{track.id}] {track.artist=}: No tag artist")
continue
# Update title and artist
if track.title != tag['title']:
track.title = tag['title']
if track.artist != tag['artist']:
track.artist = tag['artist']
# Update numbers; log if more than 10% different
duration = int(round(
tag['duration'], Config.MILLISECOND_SIGFIGS) * 1000)
check_change(track.id, track.title, "duration", track.duration,
duration)
track.duration = duration
audio = get_audio_segment(track.path)
start_gap = leading_silence(audio)
check_change(track.id, track.title, "start_gap", track.start_gap,
start_gap)
track.start_gap = start_gap
fade_at = fade_point(audio)
check_change(track.id, track.title, "fade_at", track.fade_at,
fade_at)
track.fade_at = fade_at
silence_at = trailing_silence(audio)
check_change(track.id, track.title, "silence_at", track.silence_at,
silence_at)
track.silence_at = silence_at
session.commit()
print("Full scan not yet implemented")
def get_audio_segment(path):
@ -233,11 +168,11 @@ def leading_silence(audio_segment, silence_threshold=Config.DBFS_SILENCE,
return min(trim_ms, len(audio_segment))
def fade_point(audio_segment, fade_threshold=0,
def fade_point(audio_segment, fade_threshold=Config.DBFS_FADE,
chunk_size=Config.AUDIO_SEGMENT_CHUNK_SIZE):
"""
Returns the millisecond/index of the point where the volume drops below
the maximum and doesn't get louder again.
Returns the millisecond/index of the point where the fade is down to
fade_threshold and doesn't get louder again.
audio_segment - the sdlg_search_database_uiegment to find silence in
fade_threshold - the upper bound for how quiet is silent in dFBS
chunk_size - chunk size for interating over the segment in ms
@ -247,10 +182,6 @@ def fade_point(audio_segment, fade_threshold=0,
segment_length = audio_segment.duration_seconds * 1000 # ms
trim_ms = segment_length - chunk_size
max_vol = audio_segment.dBFS
if fade_threshold == 0:
fade_threshold = max_vol
while (
audio_segment[trim_ms:trim_ms + chunk_size].dBFS < fade_threshold
and trim_ms > 0): # noqa W503
@ -261,6 +192,22 @@ def fade_point(audio_segment, fade_threshold=0,
return int(trim_ms)
# Current unused (1 June 2021)
# def rescan_database(session):
#
# tracks = Tracks.get_all_tracks(session)
# total_tracks = len(tracks)
# track_count = 0
# for track in tracks:
# track_count += 1
# print(f"Track {track_count} of {total_tracks}")
# audio = get_audio_segment(track.path)
# track.start_gap = leading_silence(audio)
# track.fade_at = fade_point(audio)
# track.silence_at = trailing_silence(audio)
# session.commit()
def trailing_silence(audio_segment, silence_threshold=-50.0,
chunk_size=Config.AUDIO_SEGMENT_CHUNK_SIZE):
return fade_point(audio_segment, silence_threshold, chunk_size)
@ -294,7 +241,7 @@ def update_db(session):
# is filename in database?
track = Tracks.get_track_from_filename(session, os.path.basename(path))
if not track:
INFO(f"songdb.update_db: Adding to database: {path}")
DEBUG(f"songdb.update_db: Adding to database: {path}")
create_track_from_file(session, path)
else:
# Check track info matches found track
@ -308,26 +255,23 @@ def update_db(session):
db_paths = set(Tracks.get_all_paths(session))
# Remote any tracks from database whose paths don't exist
for path in list(db_paths - os_paths):
# Manage tracks listed in database but where path is invalid
track = Tracks.get_track_from_path(session, path)
INFO(f"songdb.update_db(): remove from database: {path=} {track=}")
# Remove references from Playdates
Playdates.remove_track(session, track.id)
# Replace playlist entries with a note
note_txt = (
f"File removed: {track.title=}, {track.artist=}, "
f"{track.path=}"
)
for pt in PlaylistTracks.get_track_playlists(session, track.id):
# Create note
Notes.add_note(session, pt.playlist_id, pt.row, note_txt)
# Remove playlist entry
PlaylistTracks.remove_track(session, pt.playlist_id, pt.row)
# Remove Track entry pointing to invalid path
Tracks.remove_path(session, path)
DEBUG(f"songdb.update_db(): remove from database: {path=} {track=}")
played = Playdates.last_played(session, track)
playlists = PlaylistTracks.get_playlists_containing_track_id(
session, track.id)
if played:
INFO(
f"songdb.update_db: Can't remove {track.id=} ({track.path=}) "
f"as it's in playdates.id={played.id}"
)
elif playlists:
INFO(
f"songdb.update_db: Can't remove {track.id=} ({track.path=} "
f"as it's in playlists {[p.name for p in playlists]}"
)
else:
Tracks.remove_path(session, path)
if __name__ == '__main__' and '__file__' in globals():

View File

@ -149,9 +149,6 @@ border: 1px solid rgb(85, 87, 83);</string>
<property name="text">
<string/>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
@ -169,25 +166,10 @@ border: 1px solid rgb(85, 87, 83);</string>
<property name="text">
<string/>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="ElideLabel" name="hdrNextTrack">
<property name="minimumSize">
<size>
<width>0</width>
<height>39</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>39</height>
</size>
</property>
<widget class="QLabel" name="hdrNextTrack">
<property name="font">
<font>
<family>Sans</family>
@ -201,9 +183,6 @@ border: 1px solid rgb(85, 87, 83);</string>
<property name="text">
<string/>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
@ -413,13 +392,13 @@ border: 1px solid rgb(85, 87, 83);</string>
</spacer>
</item>
<item>
<widget class="QPushButton" name="btnStop">
<widget class="QPushButton" name="btnFade">
<property name="text">
<string>Stop</string>
<string>Fade</string>
</property>
<property name="icon">
<iconset resource="icons.qrc">
<normaloff>:/icons/stopsign</normaloff>:/icons/stopsign</iconset>
<normaloff>:/icons/fade</normaloff>:/icons/fade</iconset>
</property>
<property name="iconSize">
<size>
@ -430,13 +409,13 @@ border: 1px solid rgb(85, 87, 83);</string>
</widget>
</item>
<item>
<widget class="QPushButton" name="btnFade">
<widget class="QPushButton" name="btnStop">
<property name="text">
<string>Fade</string>
<string>Stop</string>
</property>
<property name="icon">
<iconset resource="icons.qrc">
<normaloff>:/icons/fade</normaloff>:/icons/fade</iconset>
<normaloff>:/icons/stopsign</normaloff>:/icons/stopsign</iconset>
</property>
<property name="iconSize">
<size>
@ -1005,13 +984,6 @@ border: 1px solid rgb(85, 87, 83);</string>
</property>
</action>
</widget>
<customwidgets>
<customwidget>
<class>ElideLabel</class>
<extends>QLabel</extends>
<header>musicmuster</header>
</customwidget>
</customwidgets>
<resources>
<include location="icons.qrc"/>
</resources>

View File

@ -85,7 +85,6 @@ class Ui_MainWindow(object):
self.hdrPreviousTrack.setStyleSheet("background-color: #f8d7da;\n"
"border: 1px solid rgb(85, 87, 83);")
self.hdrPreviousTrack.setText("")
self.hdrPreviousTrack.setWordWrap(True)
self.hdrPreviousTrack.setObjectName("hdrPreviousTrack")
self.verticalLayout.addWidget(self.hdrPreviousTrack)
self.hdrCurrentTrack = QtWidgets.QLabel(self.centralwidget)
@ -96,12 +95,9 @@ class Ui_MainWindow(object):
self.hdrCurrentTrack.setStyleSheet("background-color: #d4edda;\n"
"border: 1px solid rgb(85, 87, 83);")
self.hdrCurrentTrack.setText("")
self.hdrCurrentTrack.setWordWrap(True)
self.hdrCurrentTrack.setObjectName("hdrCurrentTrack")
self.verticalLayout.addWidget(self.hdrCurrentTrack)
self.hdrNextTrack = ElideLabel(self.centralwidget)
self.hdrNextTrack.setMinimumSize(QtCore.QSize(0, 39))
self.hdrNextTrack.setMaximumSize(QtCore.QSize(16777215, 39))
self.hdrNextTrack = QtWidgets.QLabel(self.centralwidget)
font = QtGui.QFont()
font.setFamily("Sans")
font.setPointSize(20)
@ -109,7 +105,6 @@ class Ui_MainWindow(object):
self.hdrNextTrack.setStyleSheet("background-color: #fff3cd;\n"
"border: 1px solid rgb(85, 87, 83);")
self.hdrNextTrack.setText("")
self.hdrNextTrack.setWordWrap(True)
self.hdrNextTrack.setObjectName("hdrNextTrack")
self.verticalLayout.addWidget(self.hdrNextTrack)
self.horizontalLayout_3.addLayout(self.verticalLayout)
@ -186,20 +181,20 @@ class Ui_MainWindow(object):
self.horizontalLayout.addWidget(self.btnSetNext)
spacerItem3 = QtWidgets.QSpacerItem(69, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
self.horizontalLayout.addItem(spacerItem3)
self.btnStop = QtWidgets.QPushButton(self.frame_5)
icon6 = QtGui.QIcon()
icon6.addPixmap(QtGui.QPixmap(":/icons/stopsign"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
self.btnStop.setIcon(icon6)
self.btnStop.setIconSize(QtCore.QSize(30, 30))
self.btnStop.setObjectName("btnStop")
self.horizontalLayout.addWidget(self.btnStop)
self.btnFade = QtWidgets.QPushButton(self.frame_5)
icon7 = QtGui.QIcon()
icon7.addPixmap(QtGui.QPixmap(":/icons/fade"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
self.btnFade.setIcon(icon7)
icon6 = QtGui.QIcon()
icon6.addPixmap(QtGui.QPixmap(":/icons/fade"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
self.btnFade.setIcon(icon6)
self.btnFade.setIconSize(QtCore.QSize(30, 30))
self.btnFade.setObjectName("btnFade")
self.horizontalLayout.addWidget(self.btnFade)
self.btnStop = QtWidgets.QPushButton(self.frame_5)
icon7 = QtGui.QIcon()
icon7.addPixmap(QtGui.QPixmap(":/icons/stopsign"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
self.btnStop.setIcon(icon7)
self.btnStop.setIconSize(QtCore.QSize(30, 30))
self.btnStop.setObjectName("btnStop")
self.horizontalLayout.addWidget(self.btnStop)
self.spnVolume = QtWidgets.QSpinBox(self.frame_5)
self.spnVolume.setMaximum(100)
self.spnVolume.setProperty("value", 100)
@ -491,8 +486,8 @@ class Ui_MainWindow(object):
self.btnAddFile.setText(_translate("MainWindow", "Add file"))
self.btnAddNote.setText(_translate("MainWindow", "Add note"))
self.btnSetNext.setText(_translate("MainWindow", "Set next"))
self.btnStop.setText(_translate("MainWindow", "Stop"))
self.btnFade.setText(_translate("MainWindow", "Fade"))
self.btnStop.setText(_translate("MainWindow", "Stop"))
self.label_2.setText(_translate("MainWindow", "Started at:"))
self.label_start_tod.setText(_translate("MainWindow", "00:00:00"))
self.label_3.setText(_translate("MainWindow", "Silent at:"))
@ -545,5 +540,4 @@ class Ui_MainWindow(object):
self.actionSelect_previous_track.setShortcut(_translate("MainWindow", "K"))
self.actionSelect_played_tracks.setText(_translate("MainWindow", "Select played tracks"))
self.actionSelect_unplayed_tracks.setText(_translate("MainWindow", "Select unplayed tracks"))
from musicmuster import ElideLabel
import icons_rc

View File

@ -1,45 +0,0 @@
#!/usr/bin/env python
from pydub import AudioSegment
def fade_point(audio_segment, fade_threshold=-12, chunk_size=10):
"""
Returns the millisecond/index of the point where the fade is down to
fade_threshold and doesn't get louder again.
audio_segment - the sdlg_search_database_uiegment to find silence in
fade_threshold - the upper bound for how quiet is silent in dFBS
chunk_size - chunk size for interating over the segment in ms
"""
assert chunk_size > 0 # to avoid infinite loop
segment_length = audio_segment.duration_seconds * 1000 # ms
print(f"segment_length={int(segment_length/1000)}")
trim_ms = segment_length - chunk_size
max_vol = audio_segment.dBFS
print(f"{max_vol=}")
fade_threshold = max_vol
while (
audio_segment[trim_ms:trim_ms + chunk_size].dBFS < fade_threshold
and trim_ms > 0): # noqa W503
trim_ms -= chunk_size
# 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)
print(f"Fade last {int(segment_length - trim_ms)/1000} seconds")
# print("Shout:")
# segment = AudioSegment.from_mp3("../archive/shout.mp3")
# fade_point(segment)
# print("Champagne:")
# segment = AudioSegment.from_mp3("../archive/champ.mp3")
# fade_point(segment)
# print("Be good:")
# segment = AudioSegment.from_mp3("../archive/wibg.mp3")
# fade_point(segment)
print("Be good:")
segment = AudioSegment.from_file("/tmp/bia.flac", "flac")
fade_point(segment)