Compare commits
11 Commits
a86ce6165d
...
f307c92edb
Author | SHA1 | Date |
---|---|---|
Anna Schumaker | f307c92edb | |
Anna Schumaker | 8afd1a6240 | |
Anna Schumaker | 84fbd94aa1 | |
Anna Schumaker | 7155fa9db5 | |
Anna Schumaker | 0e40e6a4e8 | |
Anna Schumaker | deea9caa37 | |
Anna Schumaker | a626a1f3c4 | |
Anna Schumaker | dae588bfaf | |
Anna Schumaker | 03e5b9ad1b | |
Anna Schumaker | 5b0a0f54e4 | |
Anna Schumaker | dd9d6268ff |
|
@ -8,13 +8,13 @@ other playlists run out of tracks.
|
|||
* MPRIS2
|
||||
* ReplayGain
|
||||
* Gapless playback
|
||||
* Background listening mode
|
||||
* Automatically pause after a user-configured number of tracks
|
||||
* Playlist creation and management
|
||||
* Automatic playlists based on Artists, Albums, Genres, Decades, and Years
|
||||
* Multiple library path support
|
||||
* Plays all audio formats supported by GStreamer
|
||||
* Renamed track detection (using MusicBrainzIDs)
|
||||
* Updated tag detection
|
||||
* Renamed and updated tracks detection (using MusicBrainzIDs)
|
||||
|
||||
## Dependencies
|
||||
* Python3
|
||||
|
|
|
@ -127,8 +127,11 @@ class Application(Adw.Application):
|
|||
def build_header(self) -> header.Header:
|
||||
"""Build a new header instance."""
|
||||
hdr = header.Header(sql=self.db, title=VERSION_STRING)
|
||||
hdr.bind_property("volume", self.player, "volume")
|
||||
for prop in ["bg-enabled", "bg-volume", "volume"]:
|
||||
hdr.bind_property(prop, self.player, prop)
|
||||
for (setting, property) in [("audio.volume", "volume"),
|
||||
("audio.background.enabled", "bg-enabled"),
|
||||
("audio.background.volume", "bg-volume"),
|
||||
("audio.replaygain.enabled", "rg-enabled"),
|
||||
("audio.replaygain.mode", "rg-mode")]:
|
||||
self.db.settings.bind_setting(setting, hdr, property)
|
||||
|
@ -250,7 +253,7 @@ class Application(Adw.Application):
|
|||
"""Handle any command line options."""
|
||||
if opts.contains("version"):
|
||||
print(VERSION_STRING)
|
||||
gsetup.print_versions()
|
||||
gsetup.print_env()
|
||||
return 0
|
||||
return -1
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@ import pathlib
|
|||
from gi.repository import GObject
|
||||
from gi.repository import GLib
|
||||
from gi.repository import Gst
|
||||
from . import replaygain
|
||||
from . import filter
|
||||
from .. import path
|
||||
from .. import tmpdir
|
||||
|
||||
|
@ -35,16 +35,18 @@ class Player(GObject.GObject):
|
|||
playtime = GObject.Property(type=float)
|
||||
savedtime = GObject.Property(type=float)
|
||||
|
||||
bg_enabled = GObject.Property(type=bool, default=False)
|
||||
bg_volume = GObject.Property(type=float, default=0.5)
|
||||
pause_on_load = GObject.Property(type=bool, default=False)
|
||||
|
||||
def __init__(self):
|
||||
"""Initialize the audio Player."""
|
||||
super().__init__()
|
||||
self._replaygain = replaygain.Filter()
|
||||
self._filter = filter.Filter()
|
||||
self._timeout = None
|
||||
|
||||
self._playbin = Gst.ElementFactory.make("playbin")
|
||||
self._playbin.set_property("audio-filter", self._replaygain)
|
||||
self._playbin.set_property("audio-filter", self._filter)
|
||||
self._playbin.set_property("video-sink",
|
||||
Gst.ElementFactory.make("fakesink"))
|
||||
self._playbin.set_state(Gst.State.READY)
|
||||
|
@ -58,6 +60,8 @@ class Player(GObject.GObject):
|
|||
bus.connect("message::tag", self.__msg_tags)
|
||||
|
||||
self.bind_property("volume", self._playbin, "volume")
|
||||
self.bind_property("bg-enabled", self._filter, "bg-enabled")
|
||||
self.bind_property("bg-volume", self._filter, "bg-volume")
|
||||
|
||||
self.connect("notify::file", self.__notify_file)
|
||||
|
||||
|
@ -163,7 +167,7 @@ class Player(GObject.GObject):
|
|||
|
||||
def get_replaygain(self) -> tuple[bool, str | None]:
|
||||
"""Get the current ReplayGain mode."""
|
||||
mode = self._replaygain.mode
|
||||
mode = self._filter.rg_mode
|
||||
return (False, None) if mode == "disabled" else (True, mode)
|
||||
|
||||
def get_state(self) -> Gst.State:
|
||||
|
@ -191,7 +195,7 @@ class Player(GObject.GObject):
|
|||
|
||||
def set_replaygain(self, enabled: bool, mode: str) -> None:
|
||||
"""Set the ReplayGain mode."""
|
||||
self._replaygain.mode = mode if enabled else "disabled"
|
||||
self._filter.rg_mode = mode if enabled else "disabled"
|
||||
|
||||
def set_state_sync(self, state: Gst.State) -> None:
|
||||
"""Set the state of the playbin, and wait for it to change."""
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
# Copyright 2023 (c) Anna Schumaker.
|
||||
"""A custom Gst.Bin with our audio filter effects."""
|
||||
from gi.repository import GObject
|
||||
from gi.repository import Gst
|
||||
from . import replaygain
|
||||
|
||||
|
||||
class Filter(Gst.Bin):
|
||||
"""The audio filter element."""
|
||||
|
||||
bg_enabled = GObject.Property(type=bool, default=False)
|
||||
bg_volume = GObject.Property(type=float, default=0.5)
|
||||
rg_mode = GObject.Property(type=str, default="disabled")
|
||||
|
||||
def __init__(self):
|
||||
"""Initialize the audio filter."""
|
||||
super().__init__()
|
||||
self._replaygain = replaygain.Filter()
|
||||
self._volume = Gst.ElementFactory.make("volume")
|
||||
|
||||
self.add(self._replaygain)
|
||||
self.add(self._volume)
|
||||
|
||||
rg_pad = self._replaygain.get_static_pad("src")
|
||||
rg_pad.link(self._volume.get_static_pad("sink"))
|
||||
|
||||
self.__add_ghost_pad("sink", self._replaygain)
|
||||
self.__add_ghost_pad("src", self._volume)
|
||||
|
||||
self.connect("notify", self.__notify)
|
||||
|
||||
def __add_ghost_pad(self, pad: str, elm: Gst.Element) -> None:
|
||||
self.add_pad(Gst.GhostPad.new(pad, elm.get_static_pad(pad)))
|
||||
|
||||
def __notify(self, filter: Gst.Bin, param: GObject.ParamSpec) -> None:
|
||||
match param.name:
|
||||
case "bg-enabled" | "bg-volume":
|
||||
vol = self.bg_volume if self.bg_enabled else 1.0
|
||||
if vol != self._volume.get_property("volume"):
|
||||
vs = f"{round(vol * 100)}%" if self.bg_enabled else "off"
|
||||
print(f"audio: setting background listening to {vs}")
|
||||
self._volume.set_property("volume", vol)
|
||||
case "rg-mode":
|
||||
if self.rg_mode != self._replaygain.mode:
|
||||
self._replaygain.mode = self.rg_mode
|
|
@ -40,26 +40,31 @@ def add_style():
|
|||
CSS_PROVIDER, CSS_PRIORITY)
|
||||
|
||||
|
||||
def __print_version(subsystem, major, minor, micro):
|
||||
print(f" ⋅ {subsystem} {major}.{minor}.{micro}")
|
||||
def __version_string(subsystem, major, minor, micro):
|
||||
return f" ⋅ {subsystem} {major}.{minor}.{micro}"
|
||||
|
||||
|
||||
def print_versions():
|
||||
"""Print version information for libraries we use."""
|
||||
__print_version("Python", sys.version_info.major, sys.version_info.minor,
|
||||
sys.version_info.micro)
|
||||
__print_version("Gtk", gi.repository.Gtk.MAJOR_VERSION,
|
||||
gi.repository.Gtk.MINOR_VERSION,
|
||||
gi.repository.Gtk.MICRO_VERSION)
|
||||
__print_version("Libadwaita", gi.repository.Adw.MAJOR_VERSION,
|
||||
gi.repository.Adw.MINOR_VERSION,
|
||||
gi.repository.Adw.MICRO_VERSION)
|
||||
__print_version("GStreamer", gi.repository.Gst.version().major,
|
||||
gi.repository.Gst.version().minor,
|
||||
gi.repository.Gst.version().micro)
|
||||
__print_version("Pango", gi.repository.Pango.VERSION_MAJOR,
|
||||
gi.repository.Pango.VERSION_MINOR,
|
||||
gi.repository.Pango.VERSION_MICRO)
|
||||
__print_version("SQLite", sqlite3.sqlite_version_info[0],
|
||||
sqlite3.sqlite_version_info[1],
|
||||
sqlite3.sqlite_version_info[2])
|
||||
def env_string() -> str:
|
||||
"""Return a string with the version numbers of our dependencies."""
|
||||
gst = gi.repository.Gst.version()
|
||||
strs = [__version_string("Python", sys.version_info.major,
|
||||
sys.version_info.minor, sys.version_info.micro),
|
||||
__version_string("Gtk", gi.repository.Gtk.MAJOR_VERSION,
|
||||
gi.repository.Gtk.MINOR_VERSION,
|
||||
gi.repository.Gtk.MICRO_VERSION),
|
||||
__version_string("Libadwaita", gi.repository.Adw.MAJOR_VERSION,
|
||||
gi.repository.Adw.MINOR_VERSION,
|
||||
gi.repository.Adw.MICRO_VERSION),
|
||||
__version_string("GStreamer", gst.major, gst.minor, gst.micro),
|
||||
__version_string("Pango", gi.repository.Pango.VERSION_MAJOR,
|
||||
gi.repository.Pango.VERSION_MINOR,
|
||||
gi.repository.Pango.VERSION_MICRO),
|
||||
__version_string("SQLite", sqlite3.sqlite_version_info[0],
|
||||
sqlite3.sqlite_version_info[1],
|
||||
sqlite3.sqlite_version_info[2])]
|
||||
return "\n".join(strs)
|
||||
|
||||
|
||||
def print_env() -> None:
|
||||
"""Print the environment versions to stdout."""
|
||||
print(env_string())
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
# Copyright 2022 (c) Anna Schumaker.
|
||||
"""A custom Gtk.HeaderBar configured for our application."""
|
||||
import pathlib
|
||||
import typing
|
||||
from gi.repository import GObject
|
||||
from gi.repository import Gtk
|
||||
from gi.repository import Adw
|
||||
from .. import db
|
||||
from .. import buttons
|
||||
from .. import gsetup
|
||||
from . import open
|
||||
from . import replaygain
|
||||
from . import volume
|
||||
|
@ -31,6 +33,8 @@ class Header(Gtk.HeaderBar):
|
|||
sql = GObject.Property(type=db.Connection)
|
||||
title = GObject.Property(type=str)
|
||||
subtitle = GObject.Property(type=str)
|
||||
bg_enabled = GObject.Property(type=bool, default=False)
|
||||
bg_volume = GObject.Property(type=float, default=0.5)
|
||||
rg_enabled = GObject.Property(type=bool, default=False)
|
||||
rg_mode = GObject.Property(type=str, default="auto")
|
||||
volume = GObject.Property(type=float, default=1.0)
|
||||
|
@ -39,21 +43,34 @@ class Header(Gtk.HeaderBar):
|
|||
"""Initialize the HeaderBar."""
|
||||
super().__init__(title=title, subtitle=SUBTITLE, sql=sql)
|
||||
self._open = open.Button()
|
||||
self._title = Adw.WindowTitle(title=self.title, subtitle=self.subtitle)
|
||||
self._volume = volume.Controls()
|
||||
self._replaygain = replaygain.Selector()
|
||||
self._title = Adw.WindowTitle(title=self.title, subtitle=self.subtitle,
|
||||
tooltip_text=gsetup.env_string())
|
||||
self._volume = volume.VolumeRow()
|
||||
self._volume_icon = Gtk.Image(icon_name=_volume_icon(self.volume))
|
||||
self._background = volume.BackgroundRow()
|
||||
self._background_icon = Gtk.Image(icon_name="sound-wave")
|
||||
self._replaygain = replaygain.ReplayGainRow()
|
||||
|
||||
self._box = Gtk.Box.new(Gtk.Orientation.VERTICAL, 0)
|
||||
self._icons = Gtk.Box.new(Gtk.Orientation.HORIZONTAL, 6)
|
||||
self._icons.append(self._volume_icon)
|
||||
self._icons.append(self._background_icon)
|
||||
|
||||
self._box = Gtk.ListBox(selection_mode=Gtk.SelectionMode.NONE)
|
||||
self._box.add_css_class("boxed-list")
|
||||
self._box.append(self._volume)
|
||||
self._box.append(Gtk.Separator())
|
||||
self._box.append(self._background)
|
||||
self._box.append(self._replaygain)
|
||||
|
||||
icon = _volume_icon(self.volume)
|
||||
self._button = buttons.PopoverButton(popover_child=self._box,
|
||||
icon_name=icon)
|
||||
child=self._icons,
|
||||
has_frame=False, margin_end=6)
|
||||
|
||||
self.bind_property("title", self._title, "title")
|
||||
self.bind_property("subtitle", self._title, "subtitle")
|
||||
self.bind_property("bg-enabled", self._background, "enabled",
|
||||
GObject.BindingFlags.BIDIRECTIONAL)
|
||||
self.bind_property("bg-volume", self._background, "volume",
|
||||
GObject.BindingFlags.BIDIRECTIONAL)
|
||||
self.bind_property("rg-enabled", self._replaygain, "enabled",
|
||||
GObject.BindingFlags.BIDIRECTIONAL)
|
||||
self.bind_property("rg-mode", self._replaygain, "mode",
|
||||
|
@ -64,7 +81,8 @@ class Header(Gtk.HeaderBar):
|
|||
self.pack_start(self._open)
|
||||
if __debug__:
|
||||
self._window = settings.Window(sql)
|
||||
self._settings = Gtk.Button.new_from_icon_name("settings-symbolic")
|
||||
self._settings = Gtk.Button(icon_name="settings-symbolic",
|
||||
tooltip_text="open settings editor")
|
||||
self._settings.connect("clicked", self.__run_settings)
|
||||
self.pack_start(self._settings)
|
||||
|
||||
|
@ -72,14 +90,29 @@ class Header(Gtk.HeaderBar):
|
|||
self.set_title_widget(self._title)
|
||||
|
||||
self._open.connect("track-requested", self.__track_requested)
|
||||
self.connect("notify::volume", self.__notify_volume)
|
||||
self.connect("notify", self.__notify)
|
||||
|
||||
def __run_settings(self, button: Gtk.Button) -> None:
|
||||
if __debug__:
|
||||
self._window.present()
|
||||
|
||||
def __notify_volume(self, header, param) -> None:
|
||||
self._button.set_icon_name(_volume_icon(self.volume))
|
||||
def __notify(self, header: typing.Self, param: GObject.ParamSpec) -> None:
|
||||
match param.name:
|
||||
case "bg-enabled":
|
||||
icon = "sound-wave-alt" if self.bg_enabled else "sound-wave"
|
||||
self._background_icon.set_from_icon_name(icon)
|
||||
case "volume":
|
||||
self._volume_icon.set_from_icon_name(_volume_icon(self.volume))
|
||||
|
||||
bg_status = "off"
|
||||
if self.bg_enabled:
|
||||
bg_status = f"{round(self.bg_volume * 100)}%"
|
||||
|
||||
rg_status = f"{self.rg_mode} mode" if self.rg_enabled else "off"
|
||||
status = (f"volume: {round(self.volume * 100)}%\n"
|
||||
f"background listening: {bg_status}\n"
|
||||
f"normalizing: {rg_status}")
|
||||
self._button.set_tooltip_text(status)
|
||||
|
||||
def __track_requested(self, button: open.Button,
|
||||
path: pathlib.Path) -> None:
|
||||
|
|
|
@ -12,7 +12,8 @@ class Button(Gtk.Button):
|
|||
|
||||
def __init__(self):
|
||||
"""Initialize our open button."""
|
||||
super().__init__(icon_name="document-open-symbolic")
|
||||
super().__init__(icon_name="document-open-symbolic",
|
||||
tooltip_text="open a file for playback")
|
||||
self._filters = Gio.ListStore()
|
||||
self._filter = Gtk.FileFilter(name="Audio Files",
|
||||
mime_types=["inode/directory",
|
||||
|
|
|
@ -2,9 +2,36 @@
|
|||
"""A widget for selecting ReplayGain mode."""
|
||||
from gi.repository import GObject
|
||||
from gi.repository import Gtk
|
||||
from gi.repository import Adw
|
||||
|
||||
|
||||
class Selector(Gtk.Grid):
|
||||
class CheckRow(Adw.ActionRow):
|
||||
"""A custom Adw.ActionRow displaying a Check Button."""
|
||||
|
||||
active = GObject.Property(type=bool, default=False)
|
||||
group = GObject.Property(type=Adw.ActionRow)
|
||||
mode = GObject.Property(type=str)
|
||||
|
||||
def __init__(self, mode: str, active: bool = False,
|
||||
group: Adw.ActionRow | None = None, **kwargs):
|
||||
"""Initialize the Check Row."""
|
||||
super().__init__(mode=mode, active=active, group=group, **kwargs)
|
||||
self._prefix = Gtk.CheckButton(active=active,
|
||||
group=group._prefix if group else None)
|
||||
|
||||
self.bind_property("active", self._prefix, "active",
|
||||
GObject.BindingFlags.BIDIRECTIONAL)
|
||||
|
||||
self.set_activatable_widget(self._prefix)
|
||||
self.add_prefix(self._prefix)
|
||||
|
||||
def set_active(self, newval: bool) -> None:
|
||||
"""Set the active property."""
|
||||
if self.active != newval:
|
||||
self.active = newval
|
||||
|
||||
|
||||
class ReplayGainRow(Adw.ExpanderRow):
|
||||
"""Build up a widget for configuring ReplayGain settings."""
|
||||
|
||||
enabled = GObject.Property(type=bool, default=False)
|
||||
|
@ -12,42 +39,40 @@ class Selector(Gtk.Grid):
|
|||
|
||||
def __init__(self):
|
||||
"""Initialize the ReplayGain selector."""
|
||||
super().__init__(column_spacing=6, margin_top=8)
|
||||
self._title = Gtk.Label(label="Volume Normalization", yalign=0.8,
|
||||
hexpand=True, vexpand=True)
|
||||
self._switch = Gtk.Switch()
|
||||
self._auto = Gtk.CheckButton(label="Decide automatically",
|
||||
sensitive=False, active=True)
|
||||
self._album = Gtk.CheckButton(label="Albums have the same volume",
|
||||
sensitive=False, group=self._auto)
|
||||
self._track = Gtk.CheckButton(label="Tracks have the same volume",
|
||||
sensitive=False, group=self._auto)
|
||||
super().__init__(title="Volume Normalization",
|
||||
subtitle="Configure ReplayGain normalizing")
|
||||
self._switch = Gtk.Switch(valign=Gtk.Align.CENTER)
|
||||
self._automatic = CheckRow(title="Automatic Mode",
|
||||
subtitle="Emmental decides automatically",
|
||||
mode="auto", active=True)
|
||||
self._album = CheckRow(title="Album Mode",
|
||||
subtitle="Albums have the same volume",
|
||||
mode="album", group=self._automatic)
|
||||
self._track = CheckRow(title="Track Mode",
|
||||
subtitle="Tracks have the same volume",
|
||||
mode="track", group=self._automatic)
|
||||
|
||||
self.attach(self._title, 0, 0, 1, 1)
|
||||
self.attach(self._switch, 1, 0, 1, 1)
|
||||
self.attach(self._auto, 0, 1, 2, 1)
|
||||
self.attach(self._album, 0, 2, 2, 1)
|
||||
self.attach(self._track, 0, 3, 2, 1)
|
||||
self.add_prefix(self._switch)
|
||||
self.add_row(self._automatic)
|
||||
self.add_row(self._album)
|
||||
self.add_row(self._track)
|
||||
|
||||
self.connect("notify::mode", self.__notify_mode)
|
||||
self._auto.connect("toggled", self.__mode_toggled, "auto")
|
||||
self._album.connect("toggled", self.__mode_toggled, "album")
|
||||
self._track.connect("toggled", self.__mode_toggled, "track")
|
||||
self._automatic.connect("notify::active", self.__row_activated)
|
||||
self._album.connect("notify::active", self.__row_activated)
|
||||
self._track.connect("notify::active", self.__row_activated)
|
||||
|
||||
self._switch.bind_property("state", self._auto, "sensitive")
|
||||
self._switch.bind_property("state", self._album, "sensitive")
|
||||
self._switch.bind_property("state", self._track, "sensitive")
|
||||
self.bind_property("enabled", self._switch, "state",
|
||||
self._switch.bind_property("active", self, "expanded",
|
||||
GObject.BindingFlags.BIDIRECTIONAL)
|
||||
self.bind_property("enabled", self._switch, "active",
|
||||
GObject.BindingFlags.BIDIRECTIONAL)
|
||||
|
||||
self._title.add_css_class("title-4")
|
||||
|
||||
def __notify_mode(self, selector: Gtk.Grid, param) -> None:
|
||||
match selector.get_property("mode"):
|
||||
def __notify_mode(self, row: Adw.ExpanderRow, param) -> None:
|
||||
match self.mode:
|
||||
case "album": self._album.set_active(True)
|
||||
case "track": self._track.set_active(True)
|
||||
case _: self._auto.set_active(True)
|
||||
case _: self._automatic.set_active(True)
|
||||
|
||||
def __mode_toggled(self, check: Gtk.CheckButton, new_mode: str) -> None:
|
||||
if check.get_active():
|
||||
self.mode = new_mode
|
||||
def __row_activated(self, row: CheckRow, param: GObject.ParamSpec) -> None:
|
||||
if row.active:
|
||||
self.mode = row.mode
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
"""A custom Gtk.Box with controls for adjusting the volume."""
|
||||
from gi.repository import GObject
|
||||
from gi.repository import Gtk
|
||||
from gi.repository import Adw
|
||||
|
||||
STEP_SIZE = 0.05
|
||||
|
||||
|
@ -11,29 +12,34 @@ def format_value_func(scale, value: float) -> str:
|
|||
return f"{round(value*100)} %"
|
||||
|
||||
|
||||
class Controls(Gtk.Box):
|
||||
class VolumeRow(Gtk.ListBoxRow):
|
||||
"""A Gtk.Box containing widgets for adjusting the volume."""
|
||||
|
||||
volume = GObject.Property(type=float, default=1.0)
|
||||
|
||||
def __init__(self):
|
||||
def __init__(self, volume: float = 1.0):
|
||||
"""Initialize our volume controls."""
|
||||
super().__init__()
|
||||
super().__init__(volume=volume)
|
||||
self._box = Gtk.Box()
|
||||
self._decrement = Gtk.Button(icon_name="list-remove-symbolic",
|
||||
tooltip_text="reduce the volume",
|
||||
valign=Gtk.Align.END, has_frame=False,
|
||||
margin_bottom=6)
|
||||
self._adjustment = Gtk.Adjustment.new(1.0, 0.0, 1.0, STEP_SIZE, 0, 0)
|
||||
margin_bottom=5)
|
||||
self._adjustment = Gtk.Adjustment.new(volume, 0.0, 1.0,
|
||||
STEP_SIZE, 0, 0)
|
||||
self._scale = Gtk.Scale(adjustment=self._adjustment, draw_value=True,
|
||||
valign=Gtk.Align.END, hexpand=True)
|
||||
self._increment = Gtk.Button(icon_name="list-add-symbolic",
|
||||
tooltip_text="increase the volume",
|
||||
valign=Gtk.Align.END, has_frame=False,
|
||||
margin_bottom=6)
|
||||
margin_bottom=5)
|
||||
|
||||
self._scale.set_format_value_func(format_value_func)
|
||||
|
||||
self.append(self._decrement)
|
||||
self.append(self._scale)
|
||||
self.append(self._increment)
|
||||
self._box.append(self._decrement)
|
||||
self._box.append(self._scale)
|
||||
self._box.append(self._increment)
|
||||
self.set_child(self._box)
|
||||
|
||||
self._decrement.connect("clicked", self.__decrement)
|
||||
self._scale.connect("value-changed", self.__value_changed)
|
||||
|
@ -50,3 +56,27 @@ class Controls(Gtk.Box):
|
|||
|
||||
def __value_changed(self, range: Gtk.Range) -> None:
|
||||
self.volume = range.get_value()
|
||||
|
||||
|
||||
class BackgroundRow(Adw.ExpanderRow):
|
||||
"""A VolumeRow for setting Background Listening volume."""
|
||||
|
||||
enabled = GObject.Property(type=bool, default=False)
|
||||
volume = GObject.Property(type=float, default=0.5)
|
||||
|
||||
def __init__(self):
|
||||
"""Initialize the BackgroundRow."""
|
||||
super().__init__(title="Background Listening",
|
||||
subtitle="Decrease the volume to help focus")
|
||||
self._switch = Gtk.Switch(valign=Gtk.Align.CENTER)
|
||||
self._volume = VolumeRow(volume=self.volume)
|
||||
|
||||
self.add_prefix(self._switch)
|
||||
self.add_row(self._volume)
|
||||
|
||||
self._switch.bind_property("active", self, "expanded",
|
||||
GObject.BindingFlags.BIDIRECTIONAL)
|
||||
self.bind_property("enabled", self._switch, "active",
|
||||
GObject.BindingFlags.BIDIRECTIONAL)
|
||||
self.bind_property("volume", self._volume, "volume",
|
||||
GObject.BindingFlags.BIDIRECTIONAL)
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="16px" viewBox="0 0 16 16" width="16px"><path d="m 5.5 4 c -0.277344 0 -0.5 0.222656 -0.5 0.5 v 8 c 0 0.277344 0.222656 0.5 0.5 0.5 s 0.5 -0.222656 0.5 -0.5 v -8 c 0 -0.277344 -0.222656 -0.5 -0.5 -0.5 z m 4 1 c -0.277344 0 -0.5 0.222656 -0.5 0.5 v 6 c 0 0.277344 0.222656 0.5 0.5 0.5 s 0.5 -0.222656 0.5 -0.5 v -6 c 0 -0.277344 -0.222656 -0.5 -0.5 -0.5 z m -2 1 c -0.277344 0 -0.5 0.222656 -0.5 0.5 v 4 c 0 0.277344 0.222656 0.5 0.5 0.5 s 0.5 -0.222656 0.5 -0.5 v -4 c 0 -0.277344 -0.222656 -0.5 -0.5 -0.5 z m 4 0 c -0.277344 0 -0.5 0.222656 -0.5 0.5 v 4 c 0 0.277344 0.222656 0.5 0.5 0.5 s 0.5 -0.222656 0.5 -0.5 v -4 c 0 -0.277344 -0.222656 -0.5 -0.5 -0.5 z m -8 1 c -0.277344 0 -0.5 0.222656 -0.5 0.5 v 3 c 0 0.277344 0.222656 0.5 0.5 0.5 s 0.5 -0.222656 0.5 -0.5 v -3 c 0 -0.277344 -0.222656 -0.5 -0.5 -0.5 z m -2.027344 1 c -0.261718 0.011719 -0.472656 0.230469 -0.472656 0.5 v 1 c 0 0.277344 0.222656 0.5 0.5 0.5 s 0.5 -0.222656 0.5 -0.5 v -1 c 0 -0.277344 -0.222656 -0.5 -0.5 -0.5 c -0.007812 0 -0.015625 0 -0.027344 0 z m 12 0 c -0.261718 0.011719 -0.472656 0.230469 -0.472656 0.5 v 1 c 0 0.277344 0.222656 0.5 0.5 0.5 s 0.5 -0.222656 0.5 -0.5 v -1 c 0 -0.277344 -0.222656 -0.5 -0.5 -0.5 c -0.007812 0 -0.015625 0 -0.027344 0 z m 0 0"/></svg>
|
After Width: | Height: | Size: 1.3 KiB |
|
@ -0,0 +1,2 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="16px" viewBox="0 0 16 16" width="16px"><path d="m 8 2 c -0.554688 0 -1 0.445312 -1 1 v 10 c 0 0.554688 0.445312 1 1 1 s 1 -0.445312 1 -1 v -10 c 0 -0.554688 -0.445312 -1 -1 -1 z m 6 2 c -0.554688 0 -1 0.445312 -1 1 v 6 c 0 0.554688 0.445312 1 1 1 s 1 -0.445312 1 -1 v -6 c 0 -0.554688 -0.445312 -1 -1 -1 z m -9 1 c -0.554688 0 -1 0.445312 -1 1 v 4 c 0 0.554688 0.445312 1 1 1 s 1 -0.445312 1 -1 v -4 c 0 -0.554688 -0.445312 -1 -1 -1 z m 6 1 c -0.554688 0 -1 0.445312 -1 1 v 2 c 0 0.554688 0.445312 1 1 1 s 1 -0.445312 1 -1 v -2 c 0 -0.554688 -0.445312 -1 -1 -1 z m -9 1 c -0.554688 0 -1 0.445312 -1 1 s 0.445312 1 1 1 s 1 -0.445312 1 -1 s -0.445312 -1 -1 -1 z m 0 0"/></svg>
|
After Width: | Height: | Size: 762 B |
|
@ -53,6 +53,27 @@ class TestAudio(unittest.TestCase):
|
|||
self.assertRegex(self.player._playbin.get_property("video-sink").name,
|
||||
r"fakesink\d+")
|
||||
|
||||
@unittest.mock.patch("sys.stdout", new_callable=io.StringIO)
|
||||
def test_filter(self, mock_stdout: io.StringIO):
|
||||
"""Test the filter element added to the playbin."""
|
||||
self.assertIsInstance(self.player._filter,
|
||||
emmental.audio.filter.Filter)
|
||||
self.assertEqual(self.player._playbin.get_property("audio-filter"),
|
||||
self.player._filter)
|
||||
|
||||
self.assertFalse(self.player.bg_enabled)
|
||||
self.assertEqual(self.player.bg_volume, 0.5)
|
||||
|
||||
self.player.bg_enabled = True
|
||||
self.player.bg_volume = 0.75
|
||||
self.assertTrue(self.player._filter.bg_enabled)
|
||||
self.assertEqual(self.player._filter.bg_volume, 0.75)
|
||||
|
||||
self.player.bg_enabled = False
|
||||
self.player.bg_volume = 0.5
|
||||
self.assertFalse(self.player._filter.bg_enabled)
|
||||
self.assertEqual(self.player.bg_volume, 0.5)
|
||||
|
||||
@unittest.mock.patch("sys.stdout", new_callable=io.StringIO)
|
||||
def test_eos(self, mock_stdout: io.StringIO):
|
||||
"""Test handling an EOS message."""
|
||||
|
@ -297,22 +318,17 @@ class TestAudio(unittest.TestCase):
|
|||
@unittest.mock.patch("sys.stdout", new_callable=io.StringIO)
|
||||
def test_replaygain(self, mock_stdout: io.StringIO):
|
||||
"""Test that ReplayGain functions work as expected."""
|
||||
self.assertIsInstance(self.player._replaygain,
|
||||
emmental.audio.replaygain.Filter)
|
||||
self.assertEqual(self.player._playbin.get_property("audio-filter"),
|
||||
self.player._replaygain)
|
||||
|
||||
self.assertEqual(self.player._replaygain.mode, "disabled")
|
||||
self.assertEqual(self.player._filter.rg_mode, "disabled")
|
||||
self.assertEqual(self.player.get_replaygain(), (False, None))
|
||||
|
||||
self.player.set_replaygain(True, "album")
|
||||
self.assertEqual(self.player._replaygain.mode, "album")
|
||||
self.assertEqual(self.player._filter.rg_mode, "album")
|
||||
self.assertEqual(self.player.get_replaygain(), (True, "album"))
|
||||
self.assertRegex(mock_stdout.getvalue(),
|
||||
r"audio: setting ReplayGain mode to 'album'")
|
||||
|
||||
self.player.set_replaygain(False, "track")
|
||||
self.assertEqual(self.player._replaygain.mode, "disabled")
|
||||
self.assertEqual(self.player._filter.rg_mode, "disabled")
|
||||
self.assertEqual(self.player.get_replaygain(), (False, None))
|
||||
self.assertRegex(mock_stdout.getvalue(),
|
||||
r"audio: setting ReplayGain mode to 'disabled'")
|
||||
|
|
|
@ -0,0 +1,72 @@
|
|||
# Copyright 2023 (c) Anna Schumaker.
|
||||
"""Tests our combined Filter element."""
|
||||
import io
|
||||
import unittest
|
||||
import emmental.audio.filter
|
||||
from gi.repository import Gst
|
||||
|
||||
|
||||
class TestFilter(unittest.TestCase):
|
||||
"""Tests our custom Filter element."""
|
||||
|
||||
def setUp(self):
|
||||
"""Set up common variables."""
|
||||
self.filter = emmental.audio.filter.Filter()
|
||||
|
||||
def test_init(self):
|
||||
"""Test that the filter is set up correctly."""
|
||||
self.assertIsInstance(self.filter, Gst.Bin)
|
||||
self.assertIsInstance(self.filter.get_static_pad("src"), Gst.GhostPad)
|
||||
self.assertIsInstance(self.filter.get_static_pad("sink"), Gst.GhostPad)
|
||||
|
||||
@unittest.mock.patch("sys.stdout", new_callable=io.StringIO)
|
||||
def test_replaygain(self, mock_stdout: io.StringIO):
|
||||
"""Test the ReplayGain element in the filter."""
|
||||
self.assertIsInstance(self.filter._replaygain,
|
||||
emmental.audio.replaygain.Filter)
|
||||
|
||||
self.assertEqual(self.filter.rg_mode, "disabled")
|
||||
self.filter.rg_mode = "track"
|
||||
self.assertEqual(self.filter._replaygain.mode, "track")
|
||||
self.filter.rg_mode = "track"
|
||||
self.assertEqual(mock_stdout.getvalue(),
|
||||
"audio: setting ReplayGain mode to 'track'\n")
|
||||
|
||||
@unittest.mock.patch("sys.stdout", new_callable=io.StringIO)
|
||||
def test_volume(self, mock_stdout: io.StringIO):
|
||||
"""Test the Volume element in the filter."""
|
||||
self.assertIsInstance(self.filter._volume, Gst.Element)
|
||||
self.assertRegex(self.filter._volume.name, r"volume\d+")
|
||||
self.assertEqual(self.filter._volume.get_property("volume"), 1.0)
|
||||
|
||||
self.assertFalse(self.filter.bg_enabled)
|
||||
self.assertEqual(self.filter.bg_volume, 0.5)
|
||||
|
||||
self.filter.bg_enabled = True
|
||||
self.assertEqual(self.filter._volume.get_property("volume"), 0.5)
|
||||
self.assertEqual(mock_stdout.getvalue(),
|
||||
"audio: setting background listening to 50%\n")
|
||||
|
||||
self.filter.bg_volume = 0.50
|
||||
self.assertEqual(mock_stdout.getvalue(),
|
||||
"audio: setting background listening to 50%\n")
|
||||
|
||||
self.filter.bg_volume = 0.75
|
||||
self.assertEqual(self.filter._volume.get_property("volume"), 0.75)
|
||||
self.assertEqual(mock_stdout.getvalue(),
|
||||
"audio: setting background listening to 50%\n"
|
||||
"audio: setting background listening to 75%\n")
|
||||
|
||||
self.filter.bg_enabled = False
|
||||
self.assertEqual(self.filter._volume.get_property("volume"), 1.0)
|
||||
self.assertEqual(mock_stdout.getvalue(),
|
||||
"audio: setting background listening to 50%\n"
|
||||
"audio: setting background listening to 75%\n"
|
||||
"audio: setting background listening to off\n")
|
||||
|
||||
self.filter.bg_volume = 0.5
|
||||
self.assertEqual(self.filter._volume.get_property("volume"), 1.0)
|
||||
self.assertEqual(mock_stdout.getvalue(),
|
||||
"audio: setting background listening to 50%\n"
|
||||
"audio: setting background listening to 75%\n"
|
||||
"audio: setting background listening to off\n")
|
|
@ -33,6 +33,9 @@ class TestHeader(tests.util.TestCase):
|
|||
self.assertEqual(self.header._title.get_subtitle(),
|
||||
emmental.header.SUBTITLE)
|
||||
|
||||
self.assertEqual(self.header._title.get_tooltip_text(),
|
||||
emmental.gsetup.env_string())
|
||||
|
||||
def test_open(self):
|
||||
"""Check that the Open button works as expected."""
|
||||
self.assertIsInstance(self.header._open, emmental.header.open.Button)
|
||||
|
@ -51,16 +54,31 @@ class TestHeader(tests.util.TestCase):
|
|||
self.assertEqual(self.header.sql, self.sql)
|
||||
self.assertEqual(self.header._settings.get_icon_name(),
|
||||
"settings-symbolic")
|
||||
self.assertEqual(self.header._settings.get_tooltip_text(),
|
||||
"open settings editor")
|
||||
|
||||
with unittest.mock.patch.object(self.header._window,
|
||||
"present") as mock_present:
|
||||
self.header._settings.emit("clicked")
|
||||
mock_present.assert_called()
|
||||
|
||||
def test_volume_icons(self):
|
||||
"""Check that the volume icons box is set up properly."""
|
||||
self.assertIsInstance(self.header._icons, Gtk.Box)
|
||||
self.assertIsInstance(self.header._volume_icon, Gtk.Image)
|
||||
self.assertIsInstance(self.header._background_icon, Gtk.Image)
|
||||
|
||||
self.assertEqual(self.header._icons.get_spacing(), 6)
|
||||
|
||||
self.assertEqual(self.header._icons.get_first_child(),
|
||||
self.header._volume_icon)
|
||||
self.assertEqual(self.header._volume_icon.get_next_sibling(),
|
||||
self.header._background_icon)
|
||||
|
||||
def test_volume(self):
|
||||
"""Check that volume widgets work as expected."""
|
||||
self.assertIsInstance(self.header._volume,
|
||||
emmental.header.volume.Controls)
|
||||
emmental.header.volume.VolumeRow)
|
||||
self.assertEqual(self.header.volume, 1.0)
|
||||
|
||||
for i, vol in [(x, x/10) for x in range(11)]:
|
||||
|
@ -75,13 +93,51 @@ class TestHeader(tests.util.TestCase):
|
|||
widget.volume = vol
|
||||
self.assertEqual(self.header.volume, vol)
|
||||
self.assertEqual(self.header._volume.volume, vol)
|
||||
self.assertEqual(self.header._button.get_icon_name(),
|
||||
self.assertEqual(self.header._volume_icon.get_icon_name(),
|
||||
f"audio-volume-{icon}-symbolic")
|
||||
self.assertEqual(self.header._button.get_tooltip_text(),
|
||||
f"volume: {i*10}%\n"
|
||||
"background listening: off\nnormalizing: off")
|
||||
|
||||
def test_background_listening(self):
|
||||
"""Test the background listening mode."""
|
||||
self.assertIsInstance(self.header._background,
|
||||
emmental.header.volume.BackgroundRow)
|
||||
self.assertEqual(self.header._background_icon.get_icon_name(),
|
||||
"sound-wave")
|
||||
|
||||
self.assertFalse(self.header.bg_enabled)
|
||||
self.assertEqual(self.header.bg_volume, 0.5)
|
||||
|
||||
self.header.bg_enabled = True
|
||||
self.assertTrue(self.header._background.enabled)
|
||||
self.assertEqual(self.header._background_icon.get_icon_name(),
|
||||
"sound-wave-alt")
|
||||
self.assertEqual(self.header._button.get_tooltip_text(),
|
||||
"volume: 100%\nbackground listening: 50%\n"
|
||||
"normalizing: off")
|
||||
|
||||
self.header.bg_volume = 0.75
|
||||
self.assertEqual(self.header._background.volume, 0.75)
|
||||
self.assertEqual(self.header._button.get_tooltip_text(),
|
||||
"volume: 100%\nbackground listening: 75%\n"
|
||||
"normalizing: off")
|
||||
|
||||
self.header._background.volume = 0.25
|
||||
self.assertEqual(self.header.bg_volume, 0.25)
|
||||
self.assertEqual(self.header._button.get_tooltip_text(),
|
||||
"volume: 100%\nbackground listening: 25%\n"
|
||||
"normalizing: off")
|
||||
|
||||
self.header._background.enabled = False
|
||||
self.assertFalse(self.header.bg_enabled)
|
||||
self.assertEqual(self.header._background_icon.get_icon_name(),
|
||||
"sound-wave")
|
||||
|
||||
def test_replaygain(self):
|
||||
"""Test that we can configure ReplayGain as expected."""
|
||||
self.assertIsInstance(self.header._replaygain,
|
||||
emmental.header.replaygain.Selector)
|
||||
emmental.header.replaygain.ReplayGainRow)
|
||||
self.assertFalse(self.header.rg_enabled)
|
||||
self.assertEqual(self.header.rg_mode, "auto")
|
||||
|
||||
|
@ -89,29 +145,38 @@ class TestHeader(tests.util.TestCase):
|
|||
self.header.rg_mode = "track"
|
||||
self.assertTrue(self.header._replaygain.enabled)
|
||||
self.assertEqual(self.header._replaygain.mode, "track")
|
||||
self.assertEqual(self.header._button.get_tooltip_text(),
|
||||
"volume: 100%\nbackground listening: off\n"
|
||||
"normalizing: track mode")
|
||||
|
||||
self.header._replaygain.enabled = False
|
||||
self.header._replaygain.mode = "album"
|
||||
self.assertFalse(self.header.rg_enabled)
|
||||
self.assertEqual(self.header.rg_mode, "album")
|
||||
self.assertEqual(self.header._button.get_tooltip_text(),
|
||||
"volume: 100%\nbackground listening: off\n"
|
||||
"normalizing: off")
|
||||
|
||||
def test_popover(self):
|
||||
"""Check that the menu popover was set up correctly."""
|
||||
def test_popover_button(self):
|
||||
"""Check that the menu popover button was set up correctly."""
|
||||
self.assertIsInstance(self.header._button,
|
||||
emmental.buttons.PopoverButton)
|
||||
self.assertIsInstance(self.header._box, Gtk.Box)
|
||||
|
||||
self.assertEqual(self.header._box.get_orientation(),
|
||||
Gtk.Orientation.VERTICAL)
|
||||
self.assertEqual(self.header._box.get_spacing(), 0)
|
||||
|
||||
self.assertEqual(self.header._button.get_icon_name(),
|
||||
"audio-volume-high-symbolic")
|
||||
self.assertEqual(self.header._button.popover_child, self.header._box)
|
||||
self.assertEqual(self.header._box.get_first_child(),
|
||||
self.header._volume)
|
||||
|
||||
sep = self.header._volume.get_next_sibling()
|
||||
self.assertIsInstance(sep, Gtk.Separator)
|
||||
self.assertEqual(sep.get_orientation(), Gtk.Orientation.HORIZONTAL)
|
||||
self.assertEqual(sep.get_next_sibling(), self.header._replaygain)
|
||||
self.assertEqual(self.header._button.get_child(), self.header._icons)
|
||||
self.assertEqual(self.header._button.get_margin_end(), 6)
|
||||
self.assertFalse(self.header._button.get_has_frame())
|
||||
|
||||
def test_popover_child(self):
|
||||
"""Check that the menu popover button child was set up correctly."""
|
||||
self.assertIsInstance(self.header._box, Gtk.ListBox)
|
||||
self.assertEqual(self.header._box.get_selection_mode(),
|
||||
Gtk.SelectionMode.NONE)
|
||||
self.assertTrue(self.header._box.has_css_class("boxed-list"))
|
||||
|
||||
self.assertEqual(self.header._box.get_row_at_index(0),
|
||||
self.header._volume)
|
||||
self.assertEqual(self.header._box.get_row_at_index(1),
|
||||
self.header._background)
|
||||
self.assertEqual(self.header._box.get_row_at_index(2),
|
||||
self.header._replaygain)
|
||||
|
|
|
@ -18,6 +18,8 @@ class TestButton(unittest.TestCase):
|
|||
"""Check that the button was set up properly."""
|
||||
self.assertIsInstance(self.button, Gtk.Button)
|
||||
self.assertEqual(self.button.get_icon_name(), "document-open-symbolic")
|
||||
self.assertEqual(self.button.get_tooltip_text(),
|
||||
"open a file for playback")
|
||||
|
||||
def test_filter(self):
|
||||
"""Check that the file filter is set up properly."""
|
||||
|
|
|
@ -1,119 +1,159 @@
|
|||
# Copyright 2022 (c) Anna Schumaker
|
||||
"""Tests our ReplayGain selector."""
|
||||
import unittest
|
||||
import unittest.mock
|
||||
import emmental.header.replaygain
|
||||
from gi.repository import Gtk
|
||||
from gi.repository import Adw
|
||||
|
||||
|
||||
class TestSelector(unittest.TestCase):
|
||||
"""Test case for our custom ReplayGain Selector."""
|
||||
class TestCheckRow(unittest.TestCase):
|
||||
"""Test case for our custom CheckRow ListBox row."""
|
||||
|
||||
def setUp(self):
|
||||
"""Set up common variables."""
|
||||
self.selector = emmental.header.replaygain.Selector()
|
||||
self.checkrow = emmental.header.replaygain.CheckRow("test mode",
|
||||
title="My Title")
|
||||
|
||||
def test_selector(self):
|
||||
"""Check that the Selector is set up properly."""
|
||||
self.assertIsInstance(self.selector, Gtk.Grid)
|
||||
self.assertEqual(self.selector.get_column_spacing(), 6)
|
||||
self.assertEqual(self.selector.get_margin_top(), 8)
|
||||
def test_init(self):
|
||||
"""Test that the CheckRow is set up properly."""
|
||||
self.assertIsInstance(self.checkrow, Adw.ActionRow)
|
||||
self.assertIsInstance(self.checkrow._prefix, Gtk.CheckButton)
|
||||
|
||||
self.assertEqual(self.selector.mode, "auto")
|
||||
self.assertFalse(self.selector.enabled)
|
||||
self.assertEqual(self.checkrow.mode, "test mode")
|
||||
self.assertEqual(self.checkrow.get_title(), "My Title")
|
||||
self.assertEqual(self.checkrow.get_activatable_widget(),
|
||||
self.checkrow._prefix)
|
||||
|
||||
self.selector.enabled = True
|
||||
self.assertTrue(self.selector._switch.get_state())
|
||||
self.selector.enabled = False
|
||||
self.assertFalse(self.selector._switch.get_state())
|
||||
def test_active(self):
|
||||
"""Test the CheckRow active property."""
|
||||
self.assertFalse(self.checkrow.active)
|
||||
|
||||
def test_title(self):
|
||||
"""Check that the Selector title label is set up properly."""
|
||||
self.assertIsInstance(self.selector._title, Gtk.Label)
|
||||
self.assertEqual(self.selector.get_child_at(0, 0),
|
||||
self.selector._title)
|
||||
self.checkrow.active = True
|
||||
self.assertTrue(self.checkrow._prefix.get_active())
|
||||
self.checkrow._prefix.set_active(False)
|
||||
self.assertFalse(self.checkrow.active)
|
||||
|
||||
self.assertEqual(self.selector._title.get_text(),
|
||||
"Volume Normalization")
|
||||
self.assertAlmostEqual(self.selector._title.get_yalign(), 0.8)
|
||||
self.assertTrue(self.selector._title.has_css_class("title-4"))
|
||||
self.assertTrue(self.selector._title.get_hexpand())
|
||||
self.assertTrue(self.selector._title.get_vexpand())
|
||||
checkrow2 = emmental.header.replaygain.CheckRow("other", active=True)
|
||||
self.assertTrue(checkrow2.active)
|
||||
self.assertTrue(checkrow2._prefix.get_active())
|
||||
|
||||
def test_group(self):
|
||||
"""Test the CheckRow group property."""
|
||||
self.assertIsNone(self.checkrow.group)
|
||||
checkrow2 = emmental.header.replaygain.CheckRow("other",
|
||||
group=self.checkrow)
|
||||
self.assertEqual(checkrow2.group, self.checkrow)
|
||||
|
||||
def test_set_active(self):
|
||||
"""Test the set_active() property."""
|
||||
notify = unittest.mock.Mock()
|
||||
self.checkrow.connect("notify::active", notify)
|
||||
|
||||
self.checkrow.set_active(True)
|
||||
self.assertTrue(self.checkrow.active)
|
||||
notify.assert_called()
|
||||
|
||||
notify.reset_mock()
|
||||
self.checkrow.set_active(True)
|
||||
notify.assert_not_called()
|
||||
|
||||
|
||||
class TestReplayGainRow(unittest.TestCase):
|
||||
"""Test case for our custom ReplayGain ListBox row."""
|
||||
|
||||
def setUp(self):
|
||||
"""Set up common variables."""
|
||||
self.replaygain = emmental.header.replaygain.ReplayGainRow()
|
||||
|
||||
def test_init(self):
|
||||
"""Check that the ReplayGainRow is set up properly."""
|
||||
self.assertIsInstance(self.replaygain, Adw.ExpanderRow)
|
||||
self.assertEqual(self.replaygain.get_title(), "Volume Normalization")
|
||||
self.assertEqual(self.replaygain.get_subtitle(),
|
||||
"Configure ReplayGain normalizing")
|
||||
|
||||
self.assertEqual(self.replaygain.mode, "auto")
|
||||
self.assertFalse(self.replaygain.enabled)
|
||||
|
||||
self.replaygain.set_expanded(True)
|
||||
self.assertTrue(self.replaygain._switch.get_active())
|
||||
self.replaygain.set_expanded(False)
|
||||
self.assertFalse(self.replaygain._switch.get_active())
|
||||
|
||||
def test_switch(self):
|
||||
"""Check that the Selector switch works as intended."""
|
||||
self.assertIsInstance(self.selector._switch, Gtk.Switch)
|
||||
self.assertEqual(self.selector.get_child_at(1, 0),
|
||||
self.selector._switch)
|
||||
self.assertFalse(self.selector._switch.get_active())
|
||||
"""Check that the ReplayGainRow switch works as intended."""
|
||||
self.assertIsInstance(self.replaygain._switch, Gtk.Switch)
|
||||
self.assertEqual(self.replaygain._switch.get_valign(),
|
||||
Gtk.Align.CENTER)
|
||||
self.assertFalse(self.replaygain._switch.get_active())
|
||||
|
||||
self.selector._switch.set_active(True)
|
||||
self.assertTrue(self.selector.enabled)
|
||||
self.assertTrue(self.selector._auto.get_sensitive())
|
||||
self.assertTrue(self.selector._album.get_sensitive())
|
||||
self.assertTrue(self.selector._track.get_sensitive())
|
||||
|
||||
self.selector._switch.set_active(False)
|
||||
self.assertFalse(self.selector.enabled)
|
||||
self.assertFalse(self.selector._auto.get_sensitive())
|
||||
self.assertFalse(self.selector._album.get_sensitive())
|
||||
self.assertFalse(self.selector._track.get_sensitive())
|
||||
self.replaygain._switch.set_active(True)
|
||||
self.assertTrue(self.replaygain.get_expanded())
|
||||
self.replaygain._switch.set_active(False)
|
||||
self.assertFalse(self.replaygain.get_expanded())
|
||||
|
||||
def test_automatic_mode(self):
|
||||
"""Test the Selector automatic mode button."""
|
||||
self.assertIsInstance(self.selector._auto, Gtk.CheckButton)
|
||||
self.assertEqual(self.selector.get_child_at(0, 1), self.selector._auto)
|
||||
"""Test the ReplayGainRow automatic mode option."""
|
||||
self.assertIsInstance(self.replaygain._automatic,
|
||||
emmental.header.replaygain.CheckRow)
|
||||
self.assertEqual(self.replaygain._automatic.get_title(),
|
||||
"Automatic Mode")
|
||||
self.assertEqual(self.replaygain._automatic.get_subtitle(),
|
||||
"Emmental decides automatically")
|
||||
|
||||
self.assertEqual(self.selector._auto.get_label(),
|
||||
"Decide automatically")
|
||||
self.assertFalse(self.selector._auto.get_sensitive())
|
||||
self.assertTrue(self.selector._auto.get_active())
|
||||
self.assertEqual(self.replaygain._automatic.mode, "auto")
|
||||
self.assertTrue(self.replaygain._automatic.active)
|
||||
|
||||
self.selector._track.set_active(True)
|
||||
self.selector._auto.set_active(True)
|
||||
self.assertEqual(self.selector.mode, "auto")
|
||||
self.replaygain._track.active = True
|
||||
self.replaygain._automatic.active = True
|
||||
self.assertEqual(self.replaygain.mode, "auto")
|
||||
self.assertFalse(self.replaygain._track.active)
|
||||
|
||||
def test_album_mode(self):
|
||||
"""Test the Selector album mode button."""
|
||||
self.assertIsInstance(self.selector._album, Gtk.CheckButton)
|
||||
self.assertEqual(self.selector.get_child_at(0, 2),
|
||||
self.selector._album)
|
||||
|
||||
self.assertEqual(self.selector._album.get_label(),
|
||||
"""Test the ReplayGainRow album mode option."""
|
||||
self.assertIsInstance(self.replaygain._album,
|
||||
emmental.header.replaygain.CheckRow)
|
||||
self.assertEqual(self.replaygain._album.get_title(), "Album Mode")
|
||||
self.assertEqual(self.replaygain._album.get_subtitle(),
|
||||
"Albums have the same volume")
|
||||
self.assertFalse(self.selector._album.get_sensitive())
|
||||
self.assertFalse(self.selector._album.get_active())
|
||||
|
||||
self.selector._album.set_active(True)
|
||||
self.assertEqual(self.selector.mode, "album")
|
||||
self.assertEqual(self.replaygain._album.mode, "album")
|
||||
self.assertEqual(self.replaygain._album.group,
|
||||
self.replaygain._automatic)
|
||||
|
||||
self.replaygain._album.active = True
|
||||
self.assertEqual(self.replaygain.mode, "album")
|
||||
|
||||
def test_track_mode(self):
|
||||
"""Test the Selector album mode button."""
|
||||
self.assertIsInstance(self.selector._track, Gtk.CheckButton)
|
||||
self.assertEqual(self.selector.get_child_at(0, 3),
|
||||
self.selector._track)
|
||||
|
||||
self.assertEqual(self.selector._track.get_label(),
|
||||
"""Test the ReplayGainRow track mode option."""
|
||||
self.assertIsInstance(self.replaygain._track,
|
||||
emmental.header.replaygain.CheckRow)
|
||||
self.assertEqual(self.replaygain._track.get_title(), "Track Mode")
|
||||
self.assertEqual(self.replaygain._track.get_subtitle(),
|
||||
"Tracks have the same volume")
|
||||
self.assertFalse(self.selector._track.get_sensitive())
|
||||
self.assertFalse(self.selector._track.get_active())
|
||||
|
||||
self.selector._track.set_active(True)
|
||||
self.assertEqual(self.selector.mode, "track")
|
||||
self.assertEqual(self.replaygain._track.mode, "track")
|
||||
self.assertEqual(self.replaygain._track.group,
|
||||
self.replaygain._automatic)
|
||||
|
||||
self.replaygain._track.active = True
|
||||
self.assertEqual(self.replaygain.mode, "track")
|
||||
|
||||
def test_mode_property(self):
|
||||
"""Test that the mode property is set correctly."""
|
||||
self.selector.mode = "album"
|
||||
self.assertTrue(self.selector._album.get_active())
|
||||
self.assertFalse(self.selector._auto.get_active())
|
||||
self.assertFalse(self.selector._track.get_active())
|
||||
self.replaygain.mode = "album"
|
||||
self.assertTrue(self.replaygain._album.active)
|
||||
self.assertFalse(self.replaygain._automatic.active)
|
||||
self.assertFalse(self.replaygain._track.active)
|
||||
|
||||
self.selector.mode = "track"
|
||||
self.assertTrue(self.selector._track.get_active())
|
||||
self.assertFalse(self.selector._auto.get_active())
|
||||
self.assertFalse(self.selector._album.get_active())
|
||||
self.replaygain.mode = "track"
|
||||
self.assertTrue(self.replaygain._track.active)
|
||||
self.assertFalse(self.replaygain._automatic.active)
|
||||
self.assertFalse(self.replaygain._album.active)
|
||||
|
||||
self.selector.mode = "anything else"
|
||||
self.assertTrue(self.selector._auto.get_active())
|
||||
self.assertFalse(self.selector._album.get_active())
|
||||
self.assertFalse(self.selector._track.get_active())
|
||||
self.assertEqual(self.selector.mode, "auto")
|
||||
self.replaygain.mode = "anything else"
|
||||
self.assertTrue(self.replaygain._automatic.active)
|
||||
self.assertFalse(self.replaygain._album.active)
|
||||
self.assertFalse(self.replaygain._track.active)
|
||||
self.assertEqual(self.replaygain.mode, "auto")
|
||||
|
|
|
@ -4,14 +4,15 @@ import unittest
|
|||
import emmental.header.volume
|
||||
import tests.util
|
||||
from gi.repository import Gtk
|
||||
from gi.repository import Adw
|
||||
|
||||
|
||||
class TestControls(unittest.TestCase):
|
||||
class TestVolumeRow(unittest.TestCase):
|
||||
"""Test case for our custom volume controls."""
|
||||
|
||||
def setUp(self):
|
||||
"""Set up common variables."""
|
||||
self.vol = emmental.header.volume.Controls()
|
||||
self.vol = emmental.header.volume.VolumeRow()
|
||||
self.value = tests.util.FloatObject(value=1.0)
|
||||
self.vol.bind_property("volume", self.value, "value")
|
||||
|
||||
|
@ -21,9 +22,12 @@ class TestControls(unittest.TestCase):
|
|||
|
||||
def test_volume(self):
|
||||
"""Check that the volume Controls are set up properly."""
|
||||
self.assertIsInstance(self.vol, Gtk.Box)
|
||||
self.assertEqual(self.vol.get_spacing(), 0)
|
||||
self.assertEqual(self.vol.get_orientation(),
|
||||
self.assertIsInstance(self.vol, Gtk.ListBoxRow)
|
||||
self.assertIsInstance(self.vol._box, Gtk.Box)
|
||||
|
||||
self.assertEqual(self.vol.get_child(), self.vol._box)
|
||||
self.assertEqual(self.vol._box.get_spacing(), 0)
|
||||
self.assertEqual(self.vol._box.get_orientation(),
|
||||
Gtk.Orientation.HORIZONTAL)
|
||||
self.assertEqual(self.vol.volume, 1.0)
|
||||
|
||||
|
@ -31,15 +35,21 @@ class TestControls(unittest.TestCase):
|
|||
self.assertAlmostEqual(self.vol._scale.get_value(), 0.85)
|
||||
self.assertAlmostEqual(self.value.value, 0.85)
|
||||
|
||||
vol2 = emmental.header.volume.VolumeRow(volume=0.5)
|
||||
self.assertEqual(vol2.volume, 0.5)
|
||||
self.assertEqual(vol2._adjustment.get_value(), 0.5)
|
||||
|
||||
def test_decrement_button(self):
|
||||
"""Test the decrement button."""
|
||||
self.assertIsInstance(self.vol._decrement, Gtk.Button)
|
||||
self.assertEqual(self.vol.get_first_child(), self.vol._decrement)
|
||||
self.assertEqual(self.vol._box.get_first_child(), self.vol._decrement)
|
||||
|
||||
self.assertEqual(self.vol._decrement.get_tooltip_text(),
|
||||
"reduce the volume")
|
||||
self.assertEqual(self.vol._decrement.get_icon_name(),
|
||||
"list-remove-symbolic")
|
||||
self.assertEqual(self.vol._decrement.get_valign(), Gtk.Align.END)
|
||||
self.assertEqual(self.vol._decrement.get_margin_bottom(), 6)
|
||||
self.assertEqual(self.vol._decrement.get_margin_bottom(), 5)
|
||||
self.assertFalse(self.vol._decrement.get_has_frame())
|
||||
|
||||
self.vol._decrement.emit("clicked")
|
||||
|
@ -79,10 +89,12 @@ class TestControls(unittest.TestCase):
|
|||
self.assertEqual(self.vol._scale.get_next_sibling(),
|
||||
self.vol._increment)
|
||||
|
||||
self.assertEqual(self.vol._increment.get_tooltip_text(),
|
||||
"increase the volume")
|
||||
self.assertEqual(self.vol._increment.get_icon_name(),
|
||||
"list-add-symbolic")
|
||||
self.assertEqual(self.vol._increment.get_valign(), Gtk.Align.END)
|
||||
self.assertEqual(self.vol._increment.get_margin_bottom(), 6)
|
||||
self.assertEqual(self.vol._increment.get_margin_bottom(), 5)
|
||||
self.assertFalse(self.vol._increment.get_has_frame())
|
||||
|
||||
self.vol.volume = 0.9
|
||||
|
@ -98,3 +110,58 @@ class TestControls(unittest.TestCase):
|
|||
with self.subTest(value=value):
|
||||
self.assertEqual(format_value(self.vol._scale, value/100),
|
||||
f"{value} %")
|
||||
|
||||
|
||||
class TestBackgroundRow(unittest.TestCase):
|
||||
"""Test case for our Background Listening volume row."""
|
||||
|
||||
def setUp(self):
|
||||
"""Set up common variables."""
|
||||
self.background = emmental.header.volume.BackgroundRow()
|
||||
|
||||
def test_init(self):
|
||||
"""Test that the Background Listening row was set up properly."""
|
||||
self.assertIsInstance(self.background, Adw.ExpanderRow)
|
||||
self.assertEqual(self.background.get_title(), "Background Listening")
|
||||
self.assertEqual(self.background.get_subtitle(),
|
||||
"Decrease the volume to help focus")
|
||||
|
||||
self.background.set_expanded(True)
|
||||
self.assertTrue(self.background._switch.get_active())
|
||||
self.background.set_expanded(False)
|
||||
self.assertFalse(self.background._switch.get_active())
|
||||
|
||||
def test_switch(self):
|
||||
"""Check that the BackgroundRow switch works as intended."""
|
||||
self.assertIsInstance(self.background._switch, Gtk.Switch)
|
||||
self.assertEqual(self.background._switch.get_valign(),
|
||||
Gtk.Align.CENTER)
|
||||
self.assertFalse(self.background._switch.get_active())
|
||||
|
||||
self.background._switch.set_active(True)
|
||||
self.assertTrue(self.background.get_expanded())
|
||||
self.background._switch.set_active(False)
|
||||
self.assertFalse(self.background.get_expanded())
|
||||
|
||||
def test_volume(self):
|
||||
"""Check the VolumeRow instance inside the Expander."""
|
||||
self.assertIsInstance(self.background._volume,
|
||||
emmental.header.volume.VolumeRow)
|
||||
self.assertEqual(self.background.volume, 0.5)
|
||||
self.assertEqual(self.background._volume.volume, 0.5)
|
||||
|
||||
self.background.volume = 0.75
|
||||
self.assertEqual(self.background._volume.volume, 0.75)
|
||||
|
||||
self.background._volume.volume = 0.25
|
||||
self.assertEqual(self.background.volume, 0.25)
|
||||
|
||||
def test_enabled(self):
|
||||
"""Check the BackgroundRow enabled property."""
|
||||
self.assertFalse(self.background.enabled)
|
||||
|
||||
self.background.enabled = True
|
||||
self.assertTrue(self.background._switch.get_active())
|
||||
|
||||
self.background._switch.set_active(False)
|
||||
self.assertFalse(self.background.enabled)
|
||||
|
|
|
@ -220,3 +220,18 @@ class TestEmmental(unittest.TestCase):
|
|||
self.assertEqual(player.get_replaygain(), (True, "track"))
|
||||
win.header.rg_mode = "album"
|
||||
self.assertEqual(player.get_replaygain(), (True, "album"))
|
||||
|
||||
@unittest.mock.patch("sys.stdout", new_callable=io.StringIO)
|
||||
def test_background_mode(self, mock_stdout: io.StringIO):
|
||||
"""Test setting background mode."""
|
||||
self.application.db = emmental.db.Connection()
|
||||
self.application.factory = emmental.playlist.Factory(
|
||||
self.application.db)
|
||||
self.application.player = emmental.audio.Player()
|
||||
win = self.application.build_window()
|
||||
player = self.application.player
|
||||
|
||||
win.header.bg_enabled = True
|
||||
win.header.bg_volume = 0.5
|
||||
self.assertTrue(player.bg_enabled)
|
||||
self.assertEqual(player.bg_volume, 0.5)
|
||||
|
|
|
@ -4,6 +4,7 @@ import unittest
|
|||
import pathlib
|
||||
import emmental
|
||||
import gi
|
||||
import io
|
||||
import xdg.BaseDirectory
|
||||
|
||||
|
||||
|
@ -65,3 +66,20 @@ class TestGSetup(unittest.TestCase):
|
|||
"""Check that the DATA_DIR points to the right place."""
|
||||
data_path = xdg.BaseDirectory.save_data_path("emmental")
|
||||
self.assertEqual(emmental.gsetup.DATA_DIR, pathlib.Path(data_path))
|
||||
|
||||
def test_env_string(self):
|
||||
"""Check that the env_string() function works as expected."""
|
||||
self.assertRegex(emmental.gsetup.env_string(),
|
||||
r" ⋅ Python \d+\.\d+\.\d+\n"
|
||||
r" ⋅ Gtk \d+\.\d+\.\d+\n"
|
||||
r" ⋅ Libadwaita \d+\.\d+\.\d+\n"
|
||||
r" ⋅ GStreamer \d+\.\d+\.\d+\n"
|
||||
r" ⋅ Pango \d+\.\d+\.\d+\n"
|
||||
r" ⋅ SQLite \d+\.\d+\.\d+$")
|
||||
|
||||
@unittest.mock.patch("sys.stdout", new_callable=io.StringIO)
|
||||
def test_print_env(self, mock_stdout: io.StringIO):
|
||||
"""Check that the print_env() function prints the env_string()."""
|
||||
emmental.gsetup.print_env()
|
||||
self.assertEqual(mock_stdout.getvalue(),
|
||||
emmental.gsetup.env_string() + "\n")
|
||||
|
|
|
@ -51,6 +51,18 @@ class TestSettings(unittest.TestCase):
|
|||
self.assertEqual(self.app.build_header().volume, 0.5)
|
||||
self.assertEqual(self.player.volume, 0.5)
|
||||
|
||||
def test_save_background_mode(self, mock_stdout: io.StringIO):
|
||||
"""Check saving and loading background mode from the database."""
|
||||
self.assertFalse(self.settings["audio.background.enabled"])
|
||||
self.assertEqual(self.settings["audio.background.volume"], 0.5)
|
||||
|
||||
self.win.header.bg_enabled = True
|
||||
self.win.header.bg_volume = 0.75
|
||||
|
||||
win = self.app.build_window()
|
||||
self.assertTrue(win.header.bg_enabled)
|
||||
self.assertEqual(win.header.bg_volume, 0.75)
|
||||
|
||||
def test_save_replaygain(self, mock_stdout: io.StringIO):
|
||||
"""Check saving and loading replaygain state from the database."""
|
||||
self.assertFalse(self.settings["audio.replaygain.enabled"])
|
||||
|
|
Loading…
Reference in New Issue