Added support for replaygain option

This commit is contained in:
Sumner Evans
2020-03-18 10:53:24 -06:00
parent 19d616fa13
commit 45ab5c0852
7 changed files with 91 additions and 18 deletions

View File

@@ -11,6 +11,7 @@ gi.require_version('Notify', '0.7')
from gi.repository import Gdk, GdkPixbuf, Gio, GLib, Gtk, Notify from gi.repository import Gdk, GdkPixbuf, Gio, GLib, Gtk, Notify
from .cache_manager import CacheManager from .cache_manager import CacheManager
from .config import ReplayGainType
from .dbus_manager import dbus_propagate, DBusManager from .dbus_manager import dbus_propagate, DBusManager
from .players import ChromecastPlayer, MPVPlayer, PlayerEvent from .players import ChromecastPlayer, MPVPlayer, PlayerEvent
from .server.api_objects import Child, Directory, Playlist from .server.api_objects import Child, Directory, Playlist
@@ -431,6 +432,8 @@ class SublimeMusicApp(Gtk.Application):
'prefetch_amount'].get_value_as_int() 'prefetch_amount'].get_value_as_int()
self.state.config.concurrent_download_limit = dialog.data[ self.state.config.concurrent_download_limit = dialog.data[
'concurrent_download_limit'].get_value_as_int() 'concurrent_download_limit'].get_value_as_int()
self.state.config.replay_gain = ReplayGainType.from_string(
dialog.data['replay_gain'].get_active_id())
self.state.save_config() self.state.save_config()
self.reset_state() self.reset_state()
dialog.destroy() dialog.destroy()

View File

@@ -348,6 +348,7 @@ class CacheManager(metaclass=Singleton):
logging.info('Migrating cache to version 1.') logging.info('Migrating cache to version 1.')
cover_art_re = re.compile(r'(\d+)_(\d+)') cover_art_re = re.compile(r'(\d+)_(\d+)')
abs_path = self.calculate_abs_path('cover_art/') abs_path = self.calculate_abs_path('cover_art/')
abs_path.mkdir(parents=True, exist_ok=True)
for cover_art_file in Path(abs_path).iterdir(): for cover_art_file in Path(abs_path).iterdir():
match = cover_art_re.match(cover_art_file.name) match = cover_art_re.match(cover_art_file.name)
if match: if match:

View File

@@ -1,10 +1,29 @@
import logging import logging
import os import os
from enum import Enum
from typing import Any, Dict, List, Optional from typing import Any, Dict, List, Optional
import keyring import keyring
class ReplayGainType(Enum):
NO = 0
TRACK = 1
ALBUM = 2
def as_string(self) -> str:
return ['no', 'track', 'album'][self.value]
@staticmethod
def from_string(replay_gain_type: str) -> 'ReplayGainType':
return {
'no': ReplayGainType.NO,
'disabled': ReplayGainType.NO,
'track': ReplayGainType.TRACK,
'album': ReplayGainType.ALBUM,
}[replay_gain_type.lower()]
class ServerConfiguration: class ServerConfiguration:
version: int version: int
name: str name: str
@@ -61,19 +80,23 @@ class AppConfiguration:
prefetch_amount: int = 3 prefetch_amount: int = 3
concurrent_download_limit: int = 5 concurrent_download_limit: int = 5
port_number: int = 8282 port_number: int = 8282
version: int = 2 version: int = 3
serve_over_lan: bool = True serve_over_lan: bool = True
replay_gain: ReplayGainType = ReplayGainType.NO
def to_json(self) -> Dict[str, Any]: def to_json(self) -> Dict[str, Any]:
exclude = ('servers') exclude = ('servers', 'replay_gain')
json_object = { json_object = {
k: getattr(self, k) k: getattr(self, k)
for k in self.__annotations__.keys() for k in self.__annotations__.keys()
if k not in exclude if k not in exclude
} }
json_object.update({ json_object.update(
'servers': [s.__dict__ for s in self.servers], {
}) 'servers': [s.__dict__ for s in self.servers],
'replay_gain':
getattr(self, 'replay_gain', ReplayGainType.NO).value,
})
return json_object return json_object
def migrate(self): def migrate(self):
@@ -85,7 +108,12 @@ class AppConfiguration:
logging.info('Setting serve_over_lan to True') logging.info('Setting serve_over_lan to True')
self.serve_over_lan = True self.serve_over_lan = True
self.version = 2 if (getattr(self, 'version') or 0) < 3:
logging.info('Migrating app configuration to version 3.')
logging.info('Setting replay_gain to ReplayGainType.NO')
self.replay_gain = ReplayGainType.NO
self.version = 3
@property @property
def cache_location(self) -> str: def cache_location(self) -> str:

View File

@@ -123,11 +123,19 @@ class Player:
class MPVPlayer(Player): class MPVPlayer(Player):
def __init__(self, *args): def __init__(
super().__init__(*args) self,
on_timepos_change: Callable[[Optional[float]], None],
on_track_end: Callable[[], None],
on_player_event: Callable[[PlayerEvent], None],
config: AppConfiguration,
):
super().__init__(
on_timepos_change, on_track_end, on_player_event, config)
self.mpv = mpv.MPV() self.mpv = mpv.MPV()
self.mpv.audio_client_name = 'sublime-music' self.mpv.audio_client_name = 'sublime-music'
self.mpv.replaygain = config.replay_gain.as_string()
self.progress_value_lock = threading.Lock() self.progress_value_lock = threading.Lock()
self.progress_value_count = 0 self.progress_value_count = 0
self._muted = False self._muted = False
@@ -301,11 +309,7 @@ class ChromecastPlayer(Player):
config: AppConfiguration, config: AppConfiguration,
): ):
super().__init__( super().__init__(
on_timepos_change, on_timepos_change, on_track_end, on_player_event, config)
on_track_end,
on_player_event,
config,
)
self._timepos = 0.0 self._timepos = 0.0
self.time_incrementor_running = False self.time_incrementor_running = False
self._can_hotswap_source = False self._can_hotswap_source = False

View File

@@ -160,16 +160,22 @@ class ApplicationState:
os.makedirs(os.path.dirname(self.state_filename), exist_ok=True) os.makedirs(os.path.dirname(self.state_filename), exist_ok=True)
# Save the state # Save the state
state_json = json.dumps(self.to_json(), indent=2, sort_keys=True)
if not state_json:
return
with open(self.state_filename, 'w+') as f: with open(self.state_filename, 'w+') as f:
f.write(json.dumps(self.to_json(), indent=2, sort_keys=True)) f.write(state_json)
def save_config(self): def save_config(self):
# Make the necessary directories before writing the config. # Make the necessary directories before writing the config.
os.makedirs(os.path.dirname(self.config_file), exist_ok=True) os.makedirs(os.path.dirname(self.config_file), exist_ok=True)
config_json = json.dumps(
self.config.to_json(), indent=2, sort_keys=True)
if not config_json:
return
with open(self.config_file, 'w+') as f: with open(self.config_file, 'w+') as f:
f.write( f.write(config_json)
json.dumps(self.config.to_json(), indent=2, sort_keys=True))
def get_config(self, filename: str) -> AppConfiguration: def get_config(self, filename: str) -> AppConfiguration:
if not os.path.exists(filename): if not os.path.exists(filename):

View File

@@ -4,16 +4,20 @@ import gi
gi.require_version('Gtk', '3.0') gi.require_version('Gtk', '3.0')
from gi.repository import Gtk from gi.repository import Gtk
TextFieldDescription = Tuple[str, str, bool]
BooleanFieldDescription = Tuple[str, str]
NumericFieldDescription = Tuple[str, str, Tuple[int, int, int], int] NumericFieldDescription = Tuple[str, str, Tuple[int, int, int], int]
OptionFieldDescription = Tuple[str, str, Tuple[str, ...]]
class EditFormDialog(Gtk.Dialog): class EditFormDialog(Gtk.Dialog):
entity_name: str entity_name: str
title: str title: str
initial_size: Tuple[int, int] initial_size: Tuple[int, int]
text_fields: List[Tuple[str, str, bool]] = [] text_fields: List[TextFieldDescription] = []
boolean_fields: List[Tuple[str, str]] = [] boolean_fields: List[BooleanFieldDescription] = []
numeric_fields: List[NumericFieldDescription] = [] numeric_fields: List[NumericFieldDescription] = []
option_fields: List[OptionFieldDescription] = []
extra_label: Optional[str] = None extra_label: Optional[str] = None
extra_buttons: List[Gtk.Button] = [] extra_buttons: List[Gtk.Button] = []
@@ -75,6 +79,30 @@ class EditFormDialog(Gtk.Dialog):
i += 1 i += 1
for label, value_field_name, options in self.option_fields:
entry_label = Gtk.Label(label=label + ':')
entry_label.set_halign(Gtk.Align.START)
content_grid.attach(entry_label, 0, i, 1, 1)
options_store = Gtk.ListStore(str)
for option in options:
options_store.append([option])
combo = Gtk.ComboBox.new_with_model(options_store)
combo.set_id_column(0)
renderer_text = Gtk.CellRendererText()
combo.pack_start(renderer_text, True)
combo.add_attribute(renderer_text, "text", 0)
field_value = getattr(existing_object, value_field_name)
if field_value:
combo.set_active(field_value.value)
content_grid.attach(combo, 1, i, 1, 1)
self.data[value_field_name] = combo
i += 1
# Add the boolean entries to the content area. # Add the boolean entries to the content area.
for label, value_field_name in self.boolean_fields: for label, value_field_name in self.boolean_fields:
entry_label = Gtk.Label(label=label + ':') entry_label = Gtk.Label(label=label + ':')

View File

@@ -41,6 +41,9 @@ class SettingsDialog(EditFormDialog):
5, 5,
), ),
] ]
option_fields = [
('Replay Gain', 'replay_gain', ('Disabled', 'Track', 'Album')),
]
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
self.extra_label = Gtk.Label( self.extra_label = Gtk.Label(