Ejemplo n.º 1
0
    def restore(self):
        text = config.get("browsers", "query_text").decode("utf-8")
        entry = self.__search
        entry.set_text(text)

        # update_filter expects a parsable query
        if Query.is_parsable(text):
            self.__update_filter(entry, text, scroll_up=False, restore=True)

        keys = config.get("browsers", "albums").split("\n")

        # FIXME: If albums is "" then it could be either all albums or
        # no albums. If it's "" and some other stuff, assume no albums,
        # otherwise all albums.
        self.__inhibit()
        if keys == [""]:
            self.view.set_cursor((0,))
        else:

            def select_fun(row):
                album = row[0]
                if not album:  # all
                    return False
                return album.str_key in keys
            self.view.select_by_func(select_fun)
        self.__uninhibit()
Ejemplo n.º 2
0
 def test_get_columns_migrated(self):
     self.failIf(config.get("settings", "headers", None))
     columns = "~album,~#replaygain_track_gain,foobar"
     config.set("settings", "columns", columns)
     self.failUnlessEqual(get_columns(),
                          ["~album", "~#replaygain_track_gain", "foobar"])
     self.failIf(config.get("settings", "headers", None))
Ejemplo n.º 3
0
 def refresh_cb(button):
     from quodlibet.library import library
     from quodlibet.util import copool
     paths = util.split_scan_dirs(config.get("settings", "scan"))
     exclude = config.get("library", "exclude").split(":")
     copool.add(library.rebuild,
        paths, False, exclude, cofuncid="library", funcid="library")
Ejemplo n.º 4
0
        def __init__(self):
            super(PreferencesWindow.Tagging, self).__init__(spacing=12)
            self.set_border_width(12)
            self.title = _("Tags")

            vbox = gtk.VBox(spacing=6)

            cb = ConfigCheckButton(_("Auto-save tag changes"),
                                   'editing', 'auto_save_changes',
                                   populate=True)
            cb.set_tooltip_text(_("Save changes to tags without confirmation "
                                  "when editing multiple files"))
            vbox.pack_start(cb, expand=False)

            cb = ConfigCheckButton(_("Show _programmatic tags"),
                                   'editing', 'alltags', populate=True)
            cb.set_tooltip_text(
                    _("Access all tags, including machine-generated ones "
                      "e.g. MusicBrainz or Replay Gain tags"))
            vbox.pack_start(cb, expand=False)

            hb = gtk.HBox(spacing=6)
            e = UndoEntry()
            e.set_text(config.get("editing", "split_on"))
            e.connect('changed', self.__changed, 'editing', 'split_on')
            e.set_tooltip_text(
                    _("A list of separators to use when splitting tag values. "
                      "The list is space-separated"))
            l = gtk.Label(_("Split _on:"))
            l.set_use_underline(True)
            l.set_mnemonic_widget(e)
            hb.pack_start(l, expand=False)
            hb.pack_start(e)
            vbox.pack_start(hb, expand=False)

            vb2 = gtk.VBox(spacing=6)
            cb = ConfigCheckButton(_("Save ratings and play _counts"),
                                   "editing", "save_to_songs", populate=True)
            vb2.pack_start(cb)
            hb = gtk.HBox(spacing=6)
            lab = gtk.Label(_("_Email:"))
            entry = UndoEntry()
            entry.set_tooltip_text(_("Ratings and play counts will be set "
                                     "for this email address"))
            entry.set_text(config.get("editing", "save_email"))
            entry.connect('changed', self.__changed, 'editing', 'save_email')
            hb.pack_start(lab, expand=False)
            hb.pack_start(entry)
            lab.set_mnemonic_widget(entry)
            lab.set_use_underline(True)
            vb2.pack_start(hb)

            f = qltk.Frame(_("Tag Editing"), child=vbox)
            self.pack_start(f, expand=False)

            f = qltk.Frame(_("Ratings"), child=vb2)
            self.pack_start(f, expand=False)

            self.show_all()
Ejemplo n.º 5
0
 def __init__(self):
     print_d("Starting Soundcloud API...")
     super(SoundcloudApiClient, self).__init__(self.API_ROOT)
     self.access_token = config.get("browsers", "soundcloud_token", None)
     self.online = bool(self.access_token)
     self.user_id = config.get("browsers", "soundcloud_user_id", None)
     if not self.user_id:
         self._get_me()
     self.username = None
Ejemplo n.º 6
0
    def test_get_columns_migrates(self):
        self.failIf(config.get("settings", "headers", None))
        self.failIf(config.get("settings", "columns", None))

        headers = "~album ~#replaygain_track_gain foobar"
        config.set("settings", "headers", headers)
        columns = get_columns()
        self.failUnlessEqual(columns, ["~album", "~#replaygain_track_gain",
                                       "foobar"])
        self.failIf(config.get("settings", "headers", None))
Ejemplo n.º 7
0
 def test_get_set_columns(self):
     self.failIf(config.get("settings", "headers", None))
     self.failIf(config.get("settings", "columns", None))
     columns = ["first", "won't", "two words", "4"]
     set_columns(columns)
     self.failUnlessEqual(columns, get_columns())
     columns += ["~~another~one"]
     set_columns(columns)
     self.failUnlessEqual(columns, get_columns())
     self.failIf(config.get("settings", "headers", None))
Ejemplo n.º 8
0
 def restore(self):
     try:
         name = config.get("browsers", "playlist")
     except config.Error as e:
         print_d("Couldn't get last playlist from config: %s" % e)
     else:
         self.__view.select_by_func(lambda r: r[0].name == name, one=True)
     try:
         text = config.get("browsers", "query_text")
     except config.Error as e:
         print_d("Couldn't get last search string from config: %s" % e)
     else:
         self._set_text(text)
Ejemplo n.º 9
0
    def connect(self):
        '''Moved from __init__ to ensure we always read the current
            configuration data'''
        self._host = config.get("connection", "hostname")
        self._port = config.get("connection", "port")
        self._password = config.get("connection", "password")
        try:
            self._client.connect(self._host, self._port)

        # Catch socket errors
        except IOError as (errno, strerror):
            raise PollerError("Could not connect to '%s': %s" %
                              (self._host, strerror))
Ejemplo n.º 10
0
    def _get_order(self, shuffle):
        """Get the active order for shuffle/inorder mode"""

        first_matching = None
        if shuffle:
            name = config.get("memory", "order_shuffle")
        else:
            name = config.get("memory", "order")
        for order in ORDERS:
            if order.is_shuffle == shuffle:
                first_matching = first_matching or order
                if order.name == name:
                    return order
        return first_matching
Ejemplo n.º 11
0
    def restore(self):
        try:
            text = config.get("browsers", "query_text")
        except config.Error:
            return

        self._set_text(text)
Ejemplo n.º 12
0
 def __init__(self, tag, value):
     super(SplitPerson, self).__init__(label=self.title, use_underline=True)
     self.set_image(Gtk.Image.new_from_icon_name(
         Icons.EDIT_FIND_REPLACE, Gtk.IconSize.MENU))
     spls = config.get("editing", "split_on").decode(
         'utf-8', 'replace').split()
     self.set_sensitive(bool(split_people(value, spls)[1]))
Ejemplo n.º 13
0
    def __post_read(self):
        email = config.get("editing", "save_email").strip()
        maps = {"rating": float, "playcount": int}
        for keyed_key, func in maps.items():
            for subkey in ["", ":" + const.EMAIL, ":" + email]:
                key = keyed_key + subkey
                if key in self:
                    try:
                        self["~#" + keyed_key] = func(self[key])
                    except ValueError:
                        pass
                    del(self[key])

        if "metadata_block_picture" in self:
            self.has_images = True
            del(self["metadata_block_picture"])

        if "coverart" in self:
            self.has_images = True
            del(self["coverart"])

        if "coverartmime" in self:
            del(self["coverartmime"])

        self.__post_read_total("tracktotal", "totaltracks", "tracknumber")
        self.__post_read_total("disctotal", "totaldiscs", "discnumber")
Ejemplo n.º 14
0
    def plugin_on_song_started(self, song):
        if (song is None and config.get("memory", "order") != "onesong" and
            not app.player.paused):
            browser = app.window.browser

            if not browser.can_filter('album'):
                return

            albumlib = app.library.albums
            albumlib.load()

            if browser.can_filter_albums():
                keys = browser.list_albums()
                values = [albumlib[k] for k in keys]
            else:
                keys = set(browser.list("album"))
                values = [a for a in albumlib if a("album") in keys]

            if self.use_weights:
                # Select 3% of albums, or at least 3 albums
                nr_albums = int(min(len(values), max(0.03 * len(values), 3)))
                chosen_albums = random.sample(values, nr_albums)
                album_scores = sorted(self._score(chosen_albums))
                for score, album in album_scores:
                    print_d("%0.2f scored by %s" % (score, album("album")))
                album = max(album_scores)[1]
            else:
                album = random.choice(values)

            if album is not None:
                self.schedule_change(album)
Ejemplo n.º 15
0
    def refresh(self):
        """Reread the current pipeline config and refresh the list"""

        self.handler_block(self.__sig)

        model = self.get_model()
        model.clear()
        self.__fill_model(model)

        if not len(model):
            self.handler_unblock(self.__sig)
            # Translators: Unknown audio output device.
            model.append(row=["", _("Unknown")])
            self.set_active(0)
            self.set_sensitive(False)
            return

        self.set_sensitive(True)

        # Translators: Default audio output device.
        model.insert(0, row=["", _("Default")])

        dev = config.get("player", "gst_device")
        for row in model:
            if row[self.DEVICE] == dev:
                self.set_active_iter(row.iter)
                break

        self.handler_unblock(self.__sig)

        # If no dev was found, change to default so the config gets reset
        if self.get_active() == -1:
            self.set_active(0)
Ejemplo n.º 16
0
        def tag_editing_vbox(self):
            """Returns a new VBox containing all tag editing widgets"""
            vbox = Gtk.VBox(spacing=6)
            cb = CCB(_("Auto-save tag changes"), 'editing',
                     'auto_save_changes', populate=True,
                     tooltip=_("Save changes to tags without confirmation "
                               "when editing multiple files"))
            vbox.pack_start(cb, False, True, 0)
            hb = Gtk.HBox(spacing=6)
            e = UndoEntry()
            e.set_text(config.get("editing", "split_on"))
            e.connect('changed', self.__changed, 'editing', 'split_on')
            e.set_tooltip_text(
                _("A list of separators to use when splitting tag values. "
                  "The list is space-separated"))

            def do_revert_split(button, section, option):
                config.reset(section, option)
                e.set_text(config.get(section, option))

            split_revert = Button(_("_Revert"), Icons.DOCUMENT_REVERT)
            split_revert.connect("clicked", do_revert_split, "editing",
                                 "split_on")
            l = Gtk.Label(label=_("Split _on:"))
            l.set_use_underline(True)
            l.set_mnemonic_widget(e)
            hb.pack_start(l, False, True, 0)
            hb.pack_start(e, True, True, 0)
            hb.pack_start(split_revert, False, True, 0)
            vbox.pack_start(hb, False, True, 0)
            return vbox
Ejemplo n.º 17
0
def config_get(key, default=''):
    """Returns value for 'key' from config. If key is missing *or empty*,
    return default."""
    try:
        return (config.get("plugins", "scrobbler_%s" % key) or default)
    except config.Error:
        return default
Ejemplo n.º 18
0
 def restore(self):
     try:
         names = config.get("browsers", "audiofeeds").split("\t")
     except:
         pass
     else:
         self.__view.select_by_func(lambda r: r[0].name in names)
Ejemplo n.º 19
0
 def restore(self):
     try:
         paths = config.get("browsers", "filesystem").split("\n")
     except config.Error:
         pass
     else:
         self.__select_paths(paths)
Ejemplo n.º 20
0
def get_init_select_dir():
    scandirs = util.split_scan_dirs(config.get("settings", "scan"))
    if scandirs and os.path.isdir(scandirs[-1]):
        # start with last added directory
        return scandirs[-1]
    else:
        return const.HOME
Ejemplo n.º 21
0
    def restore(self):
        try:
            text = config.get("browsers", "query_text")
        except Exception:
            return

        self._text = text
Ejemplo n.º 22
0
    def __init__(self):
        super(ScanBox, self).__init__(spacing=6)

        self.model = model = gtk.ListStore(str)
        view = RCMHintedTreeView(model)
        view.set_fixed_height_mode(True)
        view.set_headers_visible(False)

        view.set_tooltip_text(_("Songs in the listed folders will be "
            "added to the library during a library refresh"))

        menu = gtk.Menu()
        remove_item = gtk.ImageMenuItem(gtk.STOCK_REMOVE)
        menu.append(remove_item)
        menu.show_all()
        view.connect('popup-menu', self.__popup, menu)
        remove_item.connect_object('activate', self.__remove, view)

        sw = gtk.ScrolledWindow()
        sw.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
        sw.set_shadow_type(gtk.SHADOW_IN)
        sw.add(view)
        sw.set_size_request(-1, max(sw.size_request()[1], 100))

        render = gtk.CellRendererText()
        render.set_property('ellipsize', pango.ELLIPSIZE_END)

        def cdf(column, cell, model, iter):
            row = model[iter]
            cell.set_property('text', util.unexpand(row[0]))

        column = gtk.TreeViewColumn(None, render)
        column.set_cell_data_func(render, cdf)
        column.set_sizing(gtk.TREE_VIEW_COLUMN_FIXED)
        view.append_column(column)

        add = gtk.Button(stock=gtk.STOCK_ADD)
        add.connect("clicked", self.__add)
        remove = gtk.Button(stock=gtk.STOCK_REMOVE)

        selection = view.get_selection()
        selection.set_mode(gtk.SELECTION_MULTIPLE)
        selection.connect("changed", self.__select_changed, remove)
        selection.emit("changed")

        remove.connect_object("clicked", self.__remove, view)

        vbox = gtk.VBox(spacing=6)
        vbox.pack_start(add, expand=False)
        vbox.pack_start(remove, expand=False)

        self.pack_start(sw)
        self.pack_start(vbox, expand=False)
        self.show_all()

        paths = util.split_scan_dirs(config.get("settings", "scan"))
        paths = map(util.fsdecode, paths)
        for path in paths:
            model.append(row=[path])
Ejemplo n.º 23
0
def init(librarian):
    """May raise PlayerError"""

    try:
        driver = config.get("settings", "xine_driver")
    except:
        driver = None
    return XinePlaylistPlayer(driver, librarian)
Ejemplo n.º 24
0
def background_filter():
    bg = config.get("browsers", "background").decode('utf-8')
    if not bg:
        return
    try:
        return Query(bg, SongList.star).search
    except Query.error:
        pass
Ejemplo n.º 25
0
 def __init__(self, tag, value):
     super(SplitValues, self).__init__(
         label=_("Split into _Multiple Values"), use_underline=True)
     self.set_image(Gtk.Image.new_from_icon_name(
         Icons.EDIT_FIND_REPLACE, Gtk.IconSize.MENU))
     spls = config.get("editing", "split_on").decode(
         'utf-8', 'replace').split()
     self.set_sensitive(len(split_value(value, spls)) > 1)
Ejemplo n.º 26
0
 def __cols_changed(self, songlist):
     headers = [col.header_name for col in songlist.get_columns()]
     try: headers.remove('~current')
     except ValueError: pass
     if len(headers) == len(config.get("settings", "headers").split()):
         # Not an addition or removal (handled separately)
         config.set("settings", "headers", " ".join(headers))
         SongList.headers = headers
Ejemplo n.º 27
0
def get_headers():
    # QL <= 2.1 saved the headers tab-separated, but had a space-separated
    # default value, so check for that.
    headers = config.get("browsers", "panes")
    if headers == "~people album":
        return headers.split()
    else:
        return headers.split("\t")
Ejemplo n.º 28
0
 def __init__(self):
     try:
         self._times = config.get("plugins", self._pref_name).split(' ')[:7]
     except:
         pass
     else:
         self._times = (self._times + ["HH:MM"] * 7)[:7]
     GLib.timeout_add(30000, self._check)
Ejemplo n.º 29
0
    def enabled(self):
        self.__enabled = True
        self.__init_defaults()

        theme = config.get("plugins", self.CONFIG_THEME, None)
        self.__set_theme(theme)

        self.__set_dark(self.__get_dark())
Ejemplo n.º 30
0
    def restore(cls, library, player):
        """restore saved browser windows"""

        value = config.get("memory", "open_browsers", "")
        for name in value.split():
            kind = browsers.get(name)
            browser = cls(kind, library, player)
            browser.show_maybe()
Ejemplo n.º 31
0
    def __create_menu(self, player, library):
        def add_view_items(ag):
            act = Action(name="Information",
                         label=_('_Information'),
                         icon_name=Icons.DIALOG_INFORMATION)
            act.connect('activate', self.__current_song_info)
            ag.add_action(act)

            act = Action(name="Jump",
                         label=_('_Jump to Playing Song'),
                         icon_name=Icons.GO_JUMP)
            self.__jump_to_current(True, None, True)
            act.connect('activate', self.__jump_to_current)
            ag.add_action_with_accel(act, "<Primary>J")

        def add_top_level_items(ag):
            ag.add_action(Action(name="File", label=_("_File")))
            ag.add_action(Action(name="Song", label=_("_Song")))
            ag.add_action(Action(name="View", label=_('_View')))
            ag.add_action(Action(name="Browse", label=_("_Browse")))
            ag.add_action(Action(name="Control", label=_('_Control')))
            ag.add_action(Action(name="Help", label=_('_Help')))

        ag = Gtk.ActionGroup.new('QuodLibetWindowActions')
        add_top_level_items(ag)
        add_view_items(ag)

        act = Action(name="AddFolders",
                     label=_(u'_Add a Folder…'),
                     icon_name=Icons.LIST_ADD)
        act.connect('activate', self.open_chooser)
        ag.add_action_with_accel(act, "<Primary>O")

        act = Action(name="AddFiles",
                     label=_(u'_Add a File…'),
                     icon_name=Icons.LIST_ADD)
        act.connect('activate', self.open_chooser)
        ag.add_action(act)

        act = Action(name="AddLocation",
                     label=_(u'_Add a Location…'),
                     icon_name=Icons.LIST_ADD)
        act.connect('activate', self.open_location)
        ag.add_action(act)

        act = Action(name="BrowseLibrary",
                     label=_('Open _Browser'),
                     icon_name=Icons.EDIT_FIND)
        ag.add_action(act)

        act = Action(name="Preferences",
                     label=_('_Preferences'),
                     icon_name=Icons.PREFERENCES_SYSTEM)
        act.connect('activate', self.__preferences)
        ag.add_action(act)

        act = Action(name="Plugins",
                     label=_('_Plugins'),
                     icon_name=Icons.SYSTEM_RUN)
        act.connect('activate', self.__plugins)
        ag.add_action(act)

        act = Action(name="Quit",
                     label=_('_Quit'),
                     icon_name=Icons.APPLICATION_EXIT)
        act.connect('activate', lambda *x: self.destroy())
        ag.add_action_with_accel(act, "<Primary>Q")

        act = Action(name="EditTags",
                     label=_('Edit _Tags'),
                     icon_name=Icons.DOCUMENT_PROPERTIES)
        act.connect('activate', self.__current_song_prop)
        ag.add_action(act)

        act = Action(name="EditBookmarks", label=_(u"Edit Bookmarks…"))
        connect_obj(act, 'activate', self.__edit_bookmarks, library.librarian,
                    player)
        ag.add_action_with_accel(act, "<Primary>B")

        act = Action(name="Previous",
                     label=_('Pre_vious'),
                     icon_name=Icons.MEDIA_SKIP_BACKWARD)
        act.connect('activate', self.__previous_song)
        ag.add_action_with_accel(act, "<Primary>comma")

        act = Action(name="PlayPause",
                     label=_('_Play'),
                     icon_name=Icons.MEDIA_PLAYBACK_START)
        act.connect('activate', self.__play_pause)
        ag.add_action_with_accel(act, "<Primary>space")

        act = Action(name="Next",
                     label=_('_Next'),
                     icon_name=Icons.MEDIA_SKIP_FORWARD)
        act.connect('activate', self.__next_song)
        ag.add_action_with_accel(act, "<Primary>period")

        act = ToggleAction(name="StopAfter", label=_("Stop After This Song"))
        ag.add_action_with_accel(act, "<shift>space")

        # access point for the tray icon
        self.stop_after = act

        act = Action(name="Shortcuts", label=_("_Keyboard Shortcuts"))
        act.connect('activate', self.__keyboard_shortcuts)
        ag.add_action_with_accel(act, "<Primary>question")

        act = Action(name="About",
                     label=_("_About"),
                     icon_name=Icons.HELP_ABOUT)
        act.connect('activate', self.__show_about)
        ag.add_action_with_accel(act, None)

        act = Action(name="OnlineHelp",
                     label=_("Online Help"),
                     icon_name=Icons.HELP_BROWSER)

        def website_handler(*args):
            util.website(const.ONLINE_HELP)

        act.connect('activate', website_handler)
        ag.add_action_with_accel(act, "F1")

        act = Action(name="SearchHelp", label=_("Search Help"))

        def search_help_handler(*args):
            util.website(const.SEARCH_HELP)

        act.connect('activate', search_help_handler)
        ag.add_action_with_accel(act, None)

        act = Action(name="CheckUpdates",
                     label=_("_Check for Updates…"),
                     icon_name=Icons.NETWORK_SERVER)

        def check_updates_handler(*args):
            d = UpdateDialog(self)
            d.run()
            d.destroy()

        act.connect('activate', check_updates_handler)
        ag.add_action_with_accel(act, None)

        act = Action(name="RefreshLibrary",
                     label=_("_Scan Library"),
                     icon_name=Icons.VIEW_REFRESH)
        act.connect('activate', self.__rebuild, False)
        ag.add_action(act)

        current = config.get("memory", "browser")
        try:
            browsers.get(current)
        except ValueError:
            current = browsers.name(browsers.default)

        first_action = None
        for Kind in browsers.browsers:
            name = browsers.name(Kind)
            index = browsers.index(name)
            action_name = "View" + Kind.__name__
            act = RadioAction(name=action_name,
                              label=Kind.accelerated_name,
                              value=index)
            act.join_group(first_action)
            first_action = first_action or act
            if name == current:
                act.set_active(True)
            ag.add_action_with_accel(act, "<Primary>%d" % ((index + 1) % 10, ))
        assert first_action
        self._browser_action = first_action

        def action_callback(view_action, current_action):
            current = browsers.name(
                browsers.get(current_action.get_current_value()))
            self._select_browser(view_action, current, library, player)

        first_action.connect("changed", action_callback)

        for Kind in browsers.browsers:
            action = "Browser" + Kind.__name__
            label = Kind.accelerated_name
            name = browsers.name(Kind)
            index = browsers.index(name)
            act = Action(name=action, label=label)

            def browser_activate(action, Kind):
                LibraryBrowser.open(Kind, library, player)

            act.connect('activate', browser_activate, Kind)
            ag.add_action_with_accel(act,
                                     "<Primary><alt>%d" % ((index + 1) % 10, ))

        ui = Gtk.UIManager()
        ui.insert_action_group(ag, -1)

        menustr = MENU % {
            "views": browser_menu_items(),
            "browsers": secondary_browser_menu_items(),
            "filters_menu": FilterMenu.MENU
        }
        ui.add_ui_from_string(menustr)
        self._filter_menu = FilterMenu(library, player, ui)

        # Cute. So. UIManager lets you attach tooltips, but when they're
        # for menu items, they just get ignored. So here I get to actually
        # attach them.
        ui.get_widget("/Menu/File/RefreshLibrary").set_tooltip_text(
            _("Check for changes in your library"))

        return ui
Ejemplo n.º 32
0
 def CODECS(self):
     codecs = ["utf-8"]
     codecs_conf = config.get("editing", "id3encoding")
     codecs.extend(codecs_conf.strip().split())
     codecs.append("iso-8859-1")
     return codecs
Ejemplo n.º 33
0
        def __init__(self):
            super(PreferencesWindow.Browsers, self).__init__(spacing=12)
            self.set_border_width(12)
            self.title = _("Browsers")

            # Search
            vb = Gtk.VBox(spacing=6)
            hb = Gtk.HBox(spacing=6)
            l = Gtk.Label(label=_("_Global filter:"))
            l.set_use_underline(True)
            e = ValidatingEntry(QueryValidator)
            e.set_text(config.get("browsers", "background"))
            e.connect('changed', self._entry, 'background', 'browsers')
            e.set_tooltip_text(_("Apply this query in addition to all others"))
            l.set_mnemonic_widget(e)
            hb.pack_start(l, False, True, 0)
            hb.pack_start(e, True, True, 0)
            vb.pack_start(hb, False, True, 0)

            # Translators: The heading of the preference group, no action
            f = qltk.Frame(C_("heading", "Search"), child=vb)
            self.pack_start(f, False, True, 0)

            # Ratings
            vb = Gtk.VBox(spacing=6)
            c1 = CCB(_("Confirm _multiple ratings"),
                     'browsers',
                     'rating_confirm_multiple',
                     populate=True,
                     tooltip=_("Ask for confirmation before changing the "
                               "rating of multiple songs at once"))

            c2 = CCB(_("Enable _one-click ratings"),
                     'browsers',
                     'rating_click',
                     populate=True,
                     tooltip=_("Enable rating by clicking on the rating "
                               "column in the song list"))

            vbox = Gtk.VBox(spacing=6)
            vbox.pack_start(c1, False, True, 0)
            vbox.pack_start(c2, False, True, 0)
            f = qltk.Frame(_("Ratings"), child=vbox)
            self.pack_start(f, False, True, 0)

            # Album Art
            vb = Gtk.VBox(spacing=6)
            c = CCB(_("_Use rounded corners on thumbnails"),
                    'albumart',
                    'round',
                    populate=True,
                    tooltip=_("Round the corners of album artwork thumbnail "
                              "images."))
            c.connect('toggled', self.__toggle_round_corners)
            vb.pack_start(c, False, True, 0)

            # Filename choice algorithm config
            cb = CCB(_("Prefer _embedded art"),
                     'albumart',
                     'prefer_embedded',
                     populate=True,
                     tooltip=_("Choose to use artwork embedded in the audio "
                               "(where available) over other sources"))
            vb.pack_start(cb, False, True, 0)

            hb = Gtk.HBox(spacing=3)
            cb = CCB(_("_Fixed image filename:"),
                     'albumart',
                     'force_filename',
                     populate=True,
                     tooltip=_("The single image filename to use if "
                               "selected"))
            hb.pack_start(cb, False, True, 0)

            entry = UndoEntry()
            entry.set_tooltip_text(
                _("The album art image file to use when forced"))
            entry.set_text(config.get("albumart", "filename"))
            entry.connect('changed', self.__changed_text, 'filename')
            # Disable entry when not forcing
            entry.set_sensitive(cb.get_active())
            cb.connect('toggled', self.__toggled_force_filename, entry)
            hb.pack_start(entry, True, True, 0)
            vb.pack_start(hb, False, True, 0)

            f = qltk.Frame(_("Album Art"), child=vb)
            self.pack_start(f, False, True, 0)

            for child in self.get_children():
                child.show_all()
Ejemplo n.º 34
0
def main(argv=None):
    if argv is None:
        argv = sys_argv

    import quodlibet

    config_file = os.path.join(quodlibet.get_user_dir(), "config")
    quodlibet.init_cli(config_file=config_file)

    try:
        # we want basic commands not to import gtk (doubles process time)
        assert "gi.repository.Gtk" not in sys.modules
        sys.modules["gi.repository.Gtk"] = None
        startup_actions, cmds_todo = process_arguments(argv)
    finally:
        sys.modules.pop("gi.repository.Gtk", None)

    quodlibet.init()

    from quodlibet import app
    from quodlibet.qltk import add_signal_watch
    add_signal_watch(app.quit)

    import quodlibet.player
    import quodlibet.library
    from quodlibet import config
    from quodlibet import browsers
    from quodlibet import util

    app.name = "Quod Libet"
    app.description = _("Music player and music library manager")
    app.id = "io.github.quodlibet.QuodLibet"
    app.process_name = "quodlibet"
    quodlibet.set_application_info(app)

    library_path = os.path.join(quodlibet.get_user_dir(), "songs")

    print_d("Initializing main library (%s)" % (
            quodlibet.util.path.unexpand(library_path)))

    library = quodlibet.library.init(library_path)
    app.library = library

    # this assumes that nullbe will always succeed
    from quodlibet.player import PlayerError
    wanted_backend = environ.get(
        "QUODLIBET_BACKEND", config.get("player", "backend"))

    try:
        player = quodlibet.player.init_player(wanted_backend, app.librarian)
    except PlayerError:
        print_exc()
        player = quodlibet.player.init_player("nullbe", app.librarian)

    app.player = player

    environ["PULSE_PROP_media.role"] = "music"
    environ["PULSE_PROP_application.icon_name"] = app.icon_name

    browsers.init()

    from quodlibet.qltk.songlist import SongList, get_columns

    headers = get_columns()
    SongList.set_all_column_headers(headers)

    for opt in config.options("header_maps"):
        val = config.get("header_maps", opt)
        util.tags.add(opt, val)

    in_all = ("~filename ~uri ~#lastplayed ~#rating ~#playcount ~#skipcount "
              "~#added ~#bitrate ~current ~#laststarted ~basename "
              "~dirname").split()
    for Kind in browsers.browsers:
        if Kind.headers is not None:
            Kind.headers.extend(in_all)
        Kind.init(library)

    pm = quodlibet.init_plugins("no-plugins" in startup_actions)

    if hasattr(player, "init_plugins"):
        player.init_plugins()

    from quodlibet.qltk import unity
    unity.init("io.github.quodlibet.QuodLibet.desktop", player)

    from quodlibet.qltk.songsmenu import SongsMenu
    SongsMenu.init_plugins()

    from quodlibet.util.cover import CoverManager
    app.cover_manager = CoverManager()
    app.cover_manager.init_plugins()

    from quodlibet.plugins.playlist import PLAYLIST_HANDLER
    PLAYLIST_HANDLER.init_plugins()

    from quodlibet.plugins.query import QUERY_HANDLER
    QUERY_HANDLER.init_plugins()

    from gi.repository import GLib

    from quodlibet.commands import registry as cmd_registry, CommandError

    def exec_commands(*args):
        for cmd in cmds_todo:
            try:
                resp = cmd_registry.run(app, *cmd)
            except CommandError:
                pass
            else:
                if resp is not None:
                    print_(resp, end="", flush=True)

    from quodlibet.qltk.quodlibetwindow import QuodLibetWindow, PlayerOptions
    # Call exec_commands after the window is restored, but make sure
    # it's after the mainloop has started so everything is set up.

    app.window = window = QuodLibetWindow(
        library, player,
        restore_cb=lambda:
            GLib.idle_add(exec_commands, priority=GLib.PRIORITY_HIGH))

    app.player_options = PlayerOptions(window)

    from quodlibet.qltk.window import Window

    from quodlibet.plugins.events import EventPluginHandler
    from quodlibet.plugins.gui import UserInterfacePluginHandler
    pm.register_handler(EventPluginHandler(library.librarian, player,
                                           app.window.songlist))
    pm.register_handler(UserInterfacePluginHandler())

    from quodlibet.mmkeys import MMKeysHandler
    from quodlibet.remote import Remote, RemoteError
    from quodlibet.qltk.tracker import SongTracker, FSInterface
    try:
        from quodlibet.qltk.dbus_ import DBusHandler
    except ImportError:
        DBusHandler = None

    mmkeys_handler = MMKeysHandler(app)
    mmkeys_handler.start()

    current_path = os.path.join(quodlibet.get_user_dir(), "current")
    fsiface = FSInterface(current_path, player)
    remote = Remote(app, cmd_registry)
    try:
        remote.start()
    except RemoteError:
        exit_(1, True)

    if DBusHandler is not None:
        DBusHandler(player, library)
    tracker = SongTracker(library.librarian, player, window.playlist)

    from quodlibet import session
    session_client = session.init(app)

    quodlibet.enable_periodic_save(save_library=True)

    if ("start-playing" in startup_actions or
            (config.getboolean("player", "restore_playing", False) and
                config.getboolean("player", "is_playing", False))):
        player.paused = False

    if "start-hidden" in startup_actions:
        Window.prevent_inital_show(True)

    # restore browser windows
    from quodlibet.qltk.browser import LibraryBrowser
    GLib.idle_add(LibraryBrowser.restore, library, player,
                  priority=GLib.PRIORITY_HIGH)

    def before_quit():
        print_d("Saving active browser state")
        try:
            app.browser.save()
        except NotImplementedError:
            pass

        print_d("Shutting down player device %r." % player.version_info)
        player.destroy()

    quodlibet.run(window, before_quit=before_quit)

    app.player_options.destroy()
    quodlibet.finish_first_session("quodlibet")
    mmkeys_handler.quit()
    remote.stop()
    fsiface.destroy()

    tracker.destroy()
    quodlibet.library.save()

    config.save()

    session_client.close()

    print_d("Finished shutdown.")

    if app.is_restarting:
        os.execv(sys.executable, [sys.executable] + sys.argv)
Ejemplo n.º 35
0
    def __init_pipeline(self):
        """Creates a gstreamer pipeline. Returns True on success."""

        if self.bin:
            return True

        pipeline = config.get("player", "gst_pipeline")
        try:
            pipeline, self._pipeline_desc = GStreamerSink(pipeline)
        except PlayerError as e:
            self._error(e)
            return False

        if self._use_eq and Gst.ElementFactory.find('equalizer-10bands'):
            # The equalizer only operates on 16-bit ints or floats, and
            # will only pass these types through even when inactive.
            # We push floats through to this point, then let the second
            # audioconvert handle pushing to whatever the rest of the
            # pipeline supports. As a bonus, this seems to automatically
            # select the highest-precision format supported by the
            # rest of the chain.
            filt = Gst.ElementFactory.make('capsfilter', None)
            filt.set_property('caps',
                              Gst.Caps.from_string('audio/x-raw,format=F32LE'))
            eq = Gst.ElementFactory.make('equalizer-10bands', None)
            self._eq_element = eq
            self.update_eq_values()
            conv = Gst.ElementFactory.make('audioconvert', None)
            resample = Gst.ElementFactory.make('audioresample', None)
            pipeline = [filt, eq, conv, resample] + pipeline

        # playbin2 has started to control the volume through pulseaudio,
        # which means the volume property can change without us noticing.
        # Use our own volume element for now until this works with PA.
        self._vol_element = Gst.ElementFactory.make('volume', None)
        pipeline.insert(0, self._vol_element)

        # Get all plugin elements and append audio converters.
        # playbin already includes one at the end
        plugin_pipeline = []
        for plugin in self._get_plugin_elements():
            plugin_pipeline.append(plugin)
            plugin_pipeline.append(
                Gst.ElementFactory.make('audioconvert', None))
            plugin_pipeline.append(
                Gst.ElementFactory.make('audioresample', None))
        pipeline = plugin_pipeline + pipeline

        bufbin = Gst.Bin()
        for element in pipeline:
            assert element is not None, pipeline
            bufbin.add(element)

        PIPELINE_ERROR = PlayerError(_("Could not create GStreamer pipeline"))

        if len(pipeline) > 1:
            if not link_many(pipeline):
                print_w("Linking the GStreamer pipeline failed")
                self._error(PIPELINE_ERROR)
                return False

        # Test to ensure output pipeline can preroll
        bufbin.set_state(Gst.State.READY)
        result, state, pending = bufbin.get_state(timeout=STATE_CHANGE_TIMEOUT)
        if result == Gst.StateChangeReturn.FAILURE:
            bufbin.set_state(Gst.State.NULL)
            print_w("Prerolling the GStreamer pipeline failed")
            self._error(PIPELINE_ERROR)
            return False

        # Make the sink of the first element the sink of the bin
        gpad = Gst.GhostPad.new('sink', pipeline[0].get_static_pad('sink'))
        bufbin.add_pad(gpad)

        self.bin = Gst.ElementFactory.make('playbin', None)
        assert self.bin

        bus = self.bin.get_bus()
        bus.add_signal_watch()
        self.__bus_id = bus.connect('message', self.__message, self._librarian)

        self.bin = BufferingWrapper(self.bin, self)
        self.__atf_id = self.bin.connect('about-to-finish',
                                         self.__about_to_finish)

        # set buffer duration
        duration = config.getfloat("player", "gst_buffer")
        self._set_buffer_duration(int(duration * 1000))

        # connect playbin to our pluing/volume/eq pipeline
        self.bin.set_property('audio-sink', bufbin)

        # by default playbin will render video -> suppress using fakesink
        fakesink = Gst.ElementFactory.make('fakesink', None)
        self.bin.set_property('video-sink', fakesink)

        # disable all video/text decoding in playbin
        GST_PLAY_FLAG_VIDEO = 1 << 0
        GST_PLAY_FLAG_TEXT = 1 << 2
        flags = self.bin.get_property("flags")
        flags &= ~(GST_PLAY_FLAG_VIDEO | GST_PLAY_FLAG_TEXT)
        self.bin.set_property("flags", flags)

        # find the (uri)decodebin after setup and use autoplug-sort
        # to sort elements like decoders
        def source_setup(*args):
            def autoplug_sort(decode, pad, caps, factories):
                def set_prio(x):
                    i, f = x
                    i = {"mad": -1, "mpg123audiodec": -2}.get(f.get_name(), i)
                    return (i, f)

                return zip(*sorted(map(set_prio, enumerate(factories))))[1]

            for e in iter_to_list(self.bin.iterate_recurse):
                try:
                    e.connect("autoplug-sort", autoplug_sort)
                except TypeError:
                    pass
                else:
                    break

        self.__source_setup_id = self.bin.connect("source-setup", source_setup)

        # ReplayGain information gets lost when destroying
        self.volume = self.volume

        if self.song:
            self.bin.set_property('uri', self.song("~uri"))

        return True
Ejemplo n.º 36
0
    def cover(self):
        # TODO: Deserves some refactoring
        if not self.song.is_file:
            return None

        base = self.song('~dirname')
        images = []

        # Issue 374: Specify artwork filename
        if config.getboolean("albumart", "force_filename"):
            path = os.path.join(base, config.get("albumart", "filename"))
            if os.path.isfile(path):
                images = [(100, path)]
        else:
            get_ext = lambda s: os.path.splitext(s)[1].lstrip('.')

            entries = []
            try:
                entries = os.listdir(base)
            except EnvironmentError:
                pass

            fns = []
            for entry in entries:
                lentry = entry.lower()
                if get_ext(lentry) in self.cover_exts:
                    fns.append((None, entry))
                if lentry in self.cover_subdirs:
                    subdir = os.path.join(base, entry)
                    sub_entries = []
                    try:
                        sub_entries = os.listdir(subdir)
                    except EnvironmentError:
                        pass
                    for sub_entry in sub_entries:
                        lsub_entry = sub_entry.lower()
                        if get_ext(lsub_entry) in self.cover_exts:
                            fns.append((entry, sub_entry))

            for sub, fn in fns:
                dec_lfn = fsdecode(fn, False).lower()

                score = 0
                # check for the album label number
                labelid = self.song.get("labelid", "").lower()
                if labelid and labelid in dec_lfn:
                    score += 20

                # Track-related keywords
                keywords = [
                    k.lower().strip() for k in [
                        self.song("artist"),
                        self.song("albumartist"),
                        self.song("album")
                    ] if len(k) > 1
                ]
                score += 2 * sum(map(dec_lfn.__contains__, keywords))

                # Generic keywords
                score += 3 * sum(
                    r.search(dec_lfn) is not None
                    for r in self.cover_positive_regexes)

                negs = sum(
                    r.search(dec_lfn) is not None
                    for r in self.cover_negative_regexes)
                score -= 2 * negs
                #print("[%s - %s]: Album art \"%s\" scores %d (%s neg)." % (
                #        self("artist"), self("title"), fn, score, negs))
                if score > 0:
                    if sub is not None:
                        fn = os.path.join(sub, fn)
                    images.append((score, os.path.join(base, fn)))
            images.sort(reverse=True)

        for score, path in images:
            # could be a directory
            if not os.path.isfile(path):
                continue
            try:
                return file(path, "rb")
            except IOError:
                print_w("Failed reading album art \"%s\"" % path)

        return None
Ejemplo n.º 37
0
def main(argv):
    import quodlibet

    quodlibet.init_cli()

    try:
        # we want basic commands not to import gtk (doubles process time)
        assert "gi.repository.Gtk" not in sys.modules
        sys.modules["gi.repository.Gtk"] = None
        startup_actions, cmds_todo = process_arguments(argv)
    finally:
        sys.modules.pop("gi.repository.Gtk", None)

    quodlibet.init()

    from quodlibet import app
    from quodlibet.qltk import add_signal_watch, Icons
    add_signal_watch(app.quit)

    import quodlibet.player
    import quodlibet.library
    from quodlibet import config
    from quodlibet import browsers
    from quodlibet import util

    app.name = "Quod Libet"
    app.id = "quodlibet"
    quodlibet.set_application_info(Icons.QUODLIBET, app.id, app.name)

    config.init(os.path.join(quodlibet.get_user_dir(), "config"))

    library_path = os.path.join(quodlibet.get_user_dir(), "songs")

    print_d("Initializing main library (%s)" %
            (quodlibet.util.path.unexpand(library_path)))

    library = quodlibet.library.init(library_path)
    app.library = library

    # this assumes that nullbe will always succeed
    from quodlibet.player import PlayerError
    wanted_backend = os.environ.get("QUODLIBET_BACKEND",
                                    config.get("player", "backend"))
    backend_traceback = None
    for backend in [wanted_backend, "nullbe"]:
        try:
            player = quodlibet.player.init_player(backend, app.librarian)
        except PlayerError:
            backend_traceback = format_exc()
        else:
            break
    app.player = player

    os.environ["PULSE_PROP_media.role"] = "music"
    os.environ["PULSE_PROP_application.icon_name"] = "quodlibet"

    browsers.init()

    from quodlibet.qltk.songlist import SongList, get_columns

    headers = get_columns()
    SongList.set_all_column_headers(headers)

    for opt in config.options("header_maps"):
        val = config.get("header_maps", opt)
        util.tags.add(opt, val)

    in_all = ("~filename ~uri ~#lastplayed ~#rating ~#playcount ~#skipcount "
              "~#added ~#bitrate ~current ~#laststarted ~basename "
              "~dirname").split()
    for Kind in browsers.browsers:
        if Kind.headers is not None:
            Kind.headers.extend(in_all)
        Kind.init(library)

    pm = quodlibet.init_plugins("no-plugins" in startup_actions)

    if hasattr(player, "init_plugins"):
        player.init_plugins()

    from quodlibet.qltk import unity
    unity.init("quodlibet.desktop", player)

    from quodlibet.qltk.songsmenu import SongsMenu
    SongsMenu.init_plugins()

    from quodlibet.util.cover import CoverManager
    app.cover_manager = CoverManager()
    app.cover_manager.init_plugins()

    from quodlibet.plugins.playlist import PLAYLIST_HANDLER
    PLAYLIST_HANDLER.init_plugins()

    from quodlibet.plugins.query import QUERY_HANDLER
    QUERY_HANDLER.init_plugins()

    from gi.repository import GLib

    def exec_commands(*args):
        for cmd in cmds_todo:
            try:
                resp = cmd_registry.run(app, *cmd)
            except CommandError:
                pass
            else:
                if resp is not None:
                    print_(resp, end="")

    from quodlibet.qltk.quodlibetwindow import QuodLibetWindow, PlayerOptions
    # Call exec_commands after the window is restored, but make sure
    # it's after the mainloop has started so everything is set up.
    app.window = window = QuodLibetWindow(
        library,
        player,
        restore_cb=lambda: GLib.idle_add(exec_commands,
                                         priority=GLib.PRIORITY_HIGH))

    app.player_options = PlayerOptions(window)

    from quodlibet.qltk.debugwindow import MinExceptionDialog
    from quodlibet.qltk.window import on_first_map
    if backend_traceback is not None:

        def show_backend_error(window):
            d = MinExceptionDialog(
                window, _("Audio Backend Failed to Load"),
                _("Loading the audio backend '%(name)s' failed. "
                  "Audio playback will be disabled.") %
                {"name": wanted_backend}, backend_traceback)
            d.run()

        # so we show the main window first
        on_first_map(app.window, show_backend_error, app.window)

    from quodlibet.plugins.events import EventPluginHandler
    pm.register_handler(EventPluginHandler(library.librarian, player))

    from quodlibet.mmkeys import MMKeysHandler
    from quodlibet.remote import Remote, RemoteError
    from quodlibet.commands import registry as cmd_registry, CommandError
    from quodlibet.qltk.tracker import SongTracker, FSInterface
    try:
        from quodlibet.qltk.dbus_ import DBusHandler
    except ImportError:
        DBusHandler = lambda player, library: None

    mmkeys_handler = MMKeysHandler(app)
    mmkeys_handler.start()

    current_path = os.path.join(quodlibet.get_user_dir(), "current")
    fsiface = FSInterface(current_path, player)
    remote = Remote(app, cmd_registry)
    try:
        remote.start()
    except RemoteError:
        exit_(1, True)

    DBusHandler(player, library)
    tracker = SongTracker(library.librarian, player, window.playlist)

    from quodlibet.qltk import session
    session.init("quodlibet")

    quodlibet.enable_periodic_save(save_library=True)

    if "start-playing" in startup_actions:
        player.paused = False

    # restore browser windows
    from quodlibet.qltk.browser import LibraryBrowser
    GLib.idle_add(LibraryBrowser.restore,
                  library,
                  player,
                  priority=GLib.PRIORITY_HIGH)

    def before_quit():
        print_d("Saving active browser state")
        try:
            app.browser.save()
        except NotImplementedError:
            pass

        print_d("Shutting down player device %r." % player.version_info)
        player.destroy()

    quodlibet.main(window, before_quit=before_quit)

    app.player_options.destroy()
    quodlibet.finish_first_session(app.id)
    mmkeys_handler.quit()
    remote.stop()
    fsiface.destroy()

    tracker.destroy()
    quodlibet.library.save()

    config.save()

    print_d("Finished shutdown.")
Ejemplo n.º 38
0
 def scan_dirs(self):
     return config.get('settings', 'scan')
Ejemplo n.º 39
0
def config_get(key, default=None):
    return config.get('plugins', f"lastfmsync_{key}", default)
Ejemplo n.º 40
0
    def PluginPreferences(self, parent):  # pylint: disable=C0103
        """Set and unset preferences from gui or config file."""
        def bool_changed(widget):
            """Boolean setting changed."""
            if widget.get_active():
                setattr(self.configuration, widget.get_name(), True)
            else:
                setattr(self.configuration, widget.get_name(), False)
            config.set('plugins', 'autoqueue_%s' % widget.get_name(),
                       widget.get_active() and 'true' or 'false')

        def str_changed(entry, key):
            """String setting changed."""
            value = entry.get_text()
            config.set('plugins', 'autoqueue_%s' % key, value)
            setattr(self.configuration, key, value)

        def int_changed(entry, key):
            """Integer setting changed."""
            value = entry.get_text()
            if value:
                config.set('plugins', 'autoqueue_%s' % key, value)
                setattr(self.configuration, key, int(value))

        table = Gtk.Table()
        table.set_col_spacings(3)
        i = 0
        j = 0
        for setting in BOOL_SETTINGS:
            button = Gtk.CheckButton(label=BOOL_SETTINGS[setting]['label'])
            button.set_name(setting)
            button.set_active(
                config.get("plugins", "autoqueue_%s" %
                           setting).lower() == 'true')
            button.connect('toggled', bool_changed)
            table.attach(button, i, i + 1, j, j + 1)
            if i == 1:
                i = 0
                j += 1
            else:
                i += 1
        for setting in INT_SETTINGS:
            j += 1
            label = Gtk.Label('%s:' % INT_SETTINGS[setting]['label'])
            entry = Gtk.Entry()
            table.attach(label,
                         0,
                         1,
                         j,
                         j + 1,
                         xoptions=Gtk.AttachOptions.FILL
                         | Gtk.AttachOptions.SHRINK)
            table.attach(entry,
                         1,
                         2,
                         j,
                         j + 1,
                         xoptions=Gtk.AttachOptions.FILL
                         | Gtk.AttachOptions.SHRINK)
            entry.connect('changed', int_changed, setting)
            try:
                entry.set_text(config.get('plugins', 'autoqueue_%s' % setting))
            except:
                pass
        for setting in STR_SETTINGS:
            j += 1
            label = Gtk.Label('%s:' % STR_SETTINGS[setting]['label'])
            entry = ValidatingEntry()
            table.attach(label,
                         0,
                         1,
                         j,
                         j + 1,
                         xoptions=Gtk.AttachOptions.FILL
                         | Gtk.AttachOptions.SHRINK)
            table.attach(entry,
                         1,
                         2,
                         j,
                         j + 1,
                         xoptions=Gtk.AttachOptions.FILL
                         | Gtk.AttachOptions.SHRINK)
            entry.connect('changed', str_changed, setting)
            try:
                entry.set_text(config.get('plugins', 'autoqueue_%s' % setting))
            except:
                pass

        return table
Ejemplo n.º 41
0
    def __init__(self, player, debug=False):
        super(GstPlayerPreferences, self).__init__(spacing=6)

        e = UndoEntry()
        e.set_tooltip_text(
            _("The GStreamer output pipeline used for "
              "playback. Leave blank for the default pipeline. "
              "In case the pipeline contains a sink, "
              "it will be used instead of the default one."))

        e.set_text(config.get('player', 'gst_pipeline'))

        def changed(entry):
            config.set('player', 'gst_pipeline', entry.get_text())

        e.connect('changed', changed)

        pipe_label = Gtk.Label(label=_('_Output pipeline:'))
        pipe_label.set_use_underline(True)
        pipe_label.set_mnemonic_widget(e)

        apply_button = Button(_("_Apply"))

        def format_buffer(scale, value):
            return _("%.1f seconds") % value

        def scale_changed(scale):
            duration_msec = int(scale.get_value() * 1000)
            player._set_buffer_duration(duration_msec)

        duration = config.getfloat("player", "gst_buffer")
        scale = Gtk.HScale.new(
            Gtk.Adjustment(value=duration, lower=0.2, upper=10))
        scale.set_value_pos(Gtk.PositionType.RIGHT)
        scale.set_show_fill_level(True)
        scale.connect('format-value', format_buffer)
        scale.connect('value-changed', scale_changed)

        buffer_label = Gtk.Label(label=_('_Buffer duration:'))
        buffer_label.set_use_underline(True)
        buffer_label.set_mnemonic_widget(scale)

        def rebuild_pipeline(*args):
            player._rebuild_pipeline()

        apply_button.connect('clicked', rebuild_pipeline)

        gapless_button = ConfigCheckButton(_('Disable _gapless playback'),
                                           "player",
                                           "gst_disable_gapless",
                                           populate=True)
        gapless_button.set_alignment(0.0, 0.5)
        gapless_button.set_tooltip_text(
            _("Disabling gapless playback can avoid track changing problems "
              "with some GStreamer versions"))

        widgets = [
            (pipe_label, e, apply_button),
            (buffer_label, scale, None),
        ]

        table = Gtk.Table(n_rows=len(widgets), n_columns=3)
        table.set_col_spacings(6)
        table.set_row_spacings(6)
        for i, (left, middle, right) in enumerate(widgets):
            left.set_alignment(0.0, 0.5)
            table.attach(left,
                         0,
                         1,
                         i,
                         i + 1,
                         xoptions=Gtk.AttachOptions.FILL
                         | Gtk.AttachOptions.SHRINK)
            if right:
                table.attach(middle, 1, 2, i, i + 1)
                table.attach(right,
                             2,
                             3,
                             i,
                             i + 1,
                             xoptions=Gtk.AttachOptions.FILL
                             | Gtk.AttachOptions.SHRINK)
            else:
                table.attach(middle, 1, 3, i, i + 1)

        table.attach(gapless_button, 0, 3, 2, 3)

        self.pack_start(table, True, True, 0)

        if debug:

            def print_bin(player):
                player._print_pipeline()

            b = Button("Print Pipeline", Icons.DIALOG_INFORMATION)
            connect_obj(b, 'clicked', print_bin, player)
            self.pack_start(b, True, True, 0)
Ejemplo n.º 42
0
    def __init__(self, library, player, headless=False, restore_cb=None):
        super(QuodLibetWindow, self).__init__(dialog=False)

        self.__destroyed = False
        self.__update_title(player)
        self.set_default_size(600, 480)

        main_box = Gtk.VBox()
        self.add(main_box)
        self.side_book = qltk.Notebook()

        # get the playlist up before other stuff
        self.songlist = MainSongList(library, player)
        self.songlist.connect("key-press-event", self.__songlist_key_press)
        self.songlist.connect_after('drag-data-received',
                                    self.__songlist_drag_data_recv)
        self.song_scroller = ScrolledWindow()
        self.song_scroller.set_policy(Gtk.PolicyType.AUTOMATIC,
                                      Gtk.PolicyType.AUTOMATIC)
        self.song_scroller.set_shadow_type(Gtk.ShadowType.IN)
        self.song_scroller.add(self.songlist)

        self.qexpander = QueueExpander(library, player)
        self.qexpander.set_no_show_all(True)
        self.qexpander.set_visible(config.getboolean("memory", "queue"))

        def on_queue_visible(qex, param):
            config.set("memory", "queue", str(qex.get_visible()))

        self.qexpander.connect("notify::visible", on_queue_visible)

        self.playlist = PlaylistMux(player, self.qexpander.model,
                                    self.songlist.model)

        self.__player = player
        # create main menubar, load/restore accelerator groups
        self.__library = library
        ui = self.__create_menu(player, library)
        accel_group = ui.get_accel_group()
        self.add_accel_group(accel_group)

        def scroll_and_jump(*args):
            self.__jump_to_current(True, None, True)

        keyval, mod = Gtk.accelerator_parse("<Primary><shift>J")
        accel_group.connect(keyval, mod, 0, scroll_and_jump)

        # custom accel map
        accel_fn = os.path.join(quodlibet.get_user_dir(), "accels")
        Gtk.AccelMap.load(accel_fn)
        # save right away so we fill the file with example comments of all
        # accels
        Gtk.AccelMap.save(accel_fn)

        menubar = ui.get_widget("/Menu")

        # Since https://git.gnome.org/browse/gtk+/commit/?id=b44df22895c79
        # toplevel menu items show an empty 16x16 image. While we don't
        # need image items there UIManager creates them by default.
        # Work around by removing the empty GtkImages
        for child in menubar.get_children():
            if isinstance(child, Gtk.ImageMenuItem):
                child.set_image(None)

        main_box.pack_start(menubar, False, True, 0)

        top_bar = TopBar(self, player, library)
        main_box.pack_start(top_bar, False, True, 0)
        self.top_bar = top_bar

        self.__browserbox = Align(bottom=3)
        self.__paned = paned = ConfigRHPaned("memory", "sidebar_pos", 0.25)
        paned.pack1(self.__browserbox, resize=True)
        # We'll pack2 when necessary (when the first sidebar plugin is set up)

        main_box.pack_start(paned, True, True, 0)

        play_order = PlayOrderWidget(self.songlist.model, player)
        statusbox = StatusBarBox(play_order, self.qexpander)
        self.order = play_order
        self.statusbar = statusbox.statusbar

        main_box.pack_start(Align(statusbox, border=3, top=-3), False, True, 0)

        self.songpane = SongListPaned(self.song_scroller, self.qexpander)
        self.songpane.show_all()

        try:
            orders = []
            for e in config.getstringlist('memory', 'sortby', []):
                orders.append((e[1:], int(e[0])))
        except ValueError:
            pass
        else:
            self.songlist.set_sort_orders(orders)

        self.browser = None
        self.ui = ui

        main_box.show_all()

        self._playback_error_dialog = None
        connect_destroy(player, 'song-started', self.__song_started)
        connect_destroy(player, 'paused', self.__update_paused, True)
        connect_destroy(player, 'unpaused', self.__update_paused, False)
        # make sure we redraw all error indicators before opening
        # a dialog (blocking the main loop), so connect after default handlers
        connect_after_destroy(player, 'error', self.__player_error)
        # connect after to let SongTracker update stats
        connect_after_destroy(player, "song-ended", self.__song_ended)

        # set at least the playlist. the song should be restored
        # after the browser emits the song list
        player.setup(self.playlist, None, 0)
        self.__restore_cb = restore_cb
        self.__first_browser_set = True

        restore_browser = not headless
        try:
            self._select_browser(self, config.get("memory", "browser"),
                                 library, player, restore_browser)
        except:
            config.set("memory", "browser", browsers.name(browsers.default))
            config.save()
            raise

        self.songlist.connect('popup-menu', self.__songs_popup_menu)
        self.songlist.connect('columns-changed', self.__cols_changed)
        self.songlist.connect('columns-changed', self.__hide_headers)
        self.songlist.info.connect("changed", self.__set_totals)

        lib = library.librarian
        connect_destroy(lib, 'changed', self.__song_changed, player)

        targets = [("text/uri-list", Gtk.TargetFlags.OTHER_APP, DND_URI_LIST)]
        targets = [Gtk.TargetEntry.new(*t) for t in targets]

        self.drag_dest_set(Gtk.DestDefaults.ALL, targets, Gdk.DragAction.COPY)
        self.connect('drag-data-received', self.__drag_data_received)

        if not headless:
            on_first_map(self, self.__configure_scan_dirs, library)

        if config.getboolean('library', 'refresh_on_start'):
            self.__rebuild(None, False)

        self.connect("key-press-event", self.__key_pressed, player)

        self.connect("destroy", self.__destroy)

        self.enable_window_tracking("quodlibet")
Ejemplo n.º 43
0
 def restore(self):
     try:
         name = config.get("browsers", "playlist")
     except Exception:
         return
     self.__view.select_by_func(lambda r: r[0].name == name, one=True)
Ejemplo n.º 44
0
 def test_raw_config_is_standard(s):
     s.assertEquals(config.get('display', 'duration_format'),
                    DurationFormat.STANDARD)
Ejemplo n.º 45
0
def get_path():
    default = os.path.join(quodlibet.get_user_dir(), "current.cover")
    return config.get("plugins", __name__, default=default)
Ejemplo n.º 46
0
    def __init__(self, activator):
        super(Preferences, self).__init__(spacing=12)

        self.set_border_width(6)

        ccb = ConfigCheckButton(_("Hide main window on close"), 'plugins',
                                'trayicon_window_hide')
        ccb.set_active(get_hide_window())
        self.pack_start(ccb, False, True, 0)

        combo = Gtk.ComboBoxText()
        combo.append_text(
            _("Scroll wheel adjusts volume\n"
              "Shift and scroll wheel changes song"))
        combo.append_text(
            _("Scroll wheel changes song\n"
              "Shift and scroll wheel adjusts volume"))
        combo.set_active(
            int(config.getboolean("plugins", "icon_modifier_swap", False)))
        combo.connect('changed', self.__changed_combo)

        self.pack_start(qltk.Frame(_("Scroll _Wheel"), child=combo), True,
                        True, 0)

        box = Gtk.VBox(spacing=12)
        table = Gtk.Table(n_rows=2, n_columns=4)
        table.set_row_spacings(6)
        table.set_col_spacings(12)

        cbs = []
        for i, tag in enumerate([
                "genre", "artist", "album", "discnumber", "part",
                "tracknumber", "title", "version"
        ]):
            cb = Gtk.CheckButton(label=util.tag(tag))
            cb.tag = tag
            cbs.append(cb)
            table.attach(cb, i % 3, i % 3 + 1, i // 3, i // 3 + 1)
        box.pack_start(table, True, True, 0)

        entry = Gtk.Entry()
        box.pack_start(entry, False, True, 0)

        preview = Gtk.Label()
        preview.set_ellipsize(Pango.EllipsizeMode.END)
        ev = Gtk.EventBox()
        ev.add(preview)
        box.pack_start(ev, False, True, 0)

        frame = qltk.Frame(_("Tooltip Display"), child=box)
        frame.get_label_widget().set_mnemonic_widget(entry)
        self.pack_start(frame, True, True, 0)

        for cb in cbs:
            cb.connect('toggled', self.__changed_cb, cbs, entry)
        entry.connect('changed', self.__changed_entry, cbs, preview)
        try:
            entry.set_text(config.get("plugins", "icon_tooltip"))
        except:
            entry.set_text(
                "<album|<album~discnumber~part~tracknumber~title~version>|"
                "<artist~title~version>>")

        for child in self.get_children():
            child.show_all()
Ejemplo n.º 47
0
 def restore(self):
     text = config.get("browsers", "query_text").decode("utf-8")
     self.__searchbar.set_text(text)
     self.__query_changed(None, text, restore=True)
Ejemplo n.º 48
0
    def write(self):
        with translate_errors():
            audio = self.Kind(self['~filename'])

        if audio.tags is None:
            audio.add_tags()
        tag = audio.tags

        # prefill TMCL with the ones we can't read
        mcl = tag.get("TMCL", mutagen.id3.TMCL(encoding=3, people=[]))
        mcl.people = [(r, n) for (r, n) in mcl.people
                      if not self.__validate_name(r)]

        # delete all TXXX/COMM we can read except empty COMM
        for frame in ["COMM:", "TXXX:"]:
            for t in tag.getall(frame + "QuodLibet:"):
                if t.desc and self.__validate_name(t.desc):
                    del tag[t.HashKey]

        for key in ["UFID:http://musicbrainz.org",
                    "TMCL",
                    "POPM:%s" % const.EMAIL,
                    "POPM:%s" % config.get("editing", "save_email")]:
            if key in tag:
                del(tag[key])

        for key, id3name in self.SDI.items():
            tag.delall(id3name)
            if key not in self:
                continue
            enc = encoding_for(self[key])
            Kind = mutagen.id3.Frames[id3name]
            text = self[key].split("\n")
            if id3name == "WOAR":
                for t in text:
                    tag.add(Kind(url=t))
            else:
                tag.add(Kind(encoding=enc, text=text))

        dont_write = (RG_KEYS
                      | set(self.TXXX_MAP.values())
                      | {"genre", "comment", "musicbrainz_trackid", "lyrics"})

        if "musicbrainz_trackid" in self.realkeys():
            f = mutagen.id3.UFID(
                owner="http://musicbrainz.org",
                data=self["musicbrainz_trackid"].encode("utf-8"))
            tag.add(f)

        # Issue 439 - Only write valid ISO 639-2 codes to TLAN (else TXXX)
        tag.delall("TLAN")
        if "language" in self:
            langs = self["language"].split("\n")
            if all([lang in ISO_639_2 for lang in langs]):
                # Save value(s) to TLAN tag. Guaranteed to be ASCII here
                tag.add(mutagen.id3.TLAN(encoding=3, text=langs))
                dont_write.add("language")
            else:
                print_w(
                    f"Not using invalid language {self['language']!r} in TLAN")

        # Filter out known keys, and ones set not to write [generically].
        dont_write |= self.SDI.keys()
        keys_to_write = (k for k in self.realkeys()
                         if k not in dont_write)
        for key in keys_to_write:
            enc = encoding_for(self[key])
            if key.startswith("performer:"):
                mcl.people.append((key.split(":", 1)[1], self[key]))
                continue

            f = mutagen.id3.TXXX(
                encoding=enc, text=self[key].split("\n"),
                desc=u"QuodLibet::%s" % key)
            tag.add(f)

        if mcl.people:
            tag.add(mcl)

        if "genre" in self:
            enc = encoding_for(self["genre"])
            t = self["genre"].split("\n")
            tag.add(mutagen.id3.TCON(encoding=enc, text=t))
        else:
            try:
                del(tag["TCON"])
            except KeyError:
                pass

        tag.delall("COMM:")
        if "comment" in self:
            enc = encoding_for(self["comment"])
            t = self["comment"].split("\n")
            tag.add(mutagen.id3.COMM(encoding=enc, text=t, desc=u"",
                                     lang="\x00\x00\x00"))

        tag.delall("USLT")
        if "lyrics" in self:
            enc = encoding_for(self["lyrics"])
            if not ("~lyricslanguage" in self and
                    # language has to be a 3 byte ISO 639-2 code
                    self["~lyricslanguage"] in ISO_639_2):
                self["~lyricslanguage"] = "und" # undefined
            # lyrics are single string, not array
            tag.add(mutagen.id3.USLT(encoding=enc, text=self["lyrics"],
                                     desc=self.get("~lyricsdescription", ""),
                                     lang=self["~lyricslanguage"]))

        # Delete old foobar replaygain ..
        for frame in tag.getall("TXXX"):
            if frame.desc.lower() in RG_KEYS:
                del tag[frame.HashKey]

        # .. write new one
        for k in RG_KEYS:
            # Add new ones
            if k in self:
                value = self[k]
                tag.add(mutagen.id3.TXXX(encoding=encoding_for(value),
                                         text=value.split("\n"),
                                         desc=k.upper()))

        # we shouldn't delete all, but we use unknown ones as fallback, so make
        # sure they don't come back after reloading
        for t in tag.getall("RVA2"):
            if t.channel == 1:
                del tag[t.HashKey]

        for k in ["track", "album"]:
            if ('replaygain_%s_gain' % k) in self:
                try:
                    gain = float(self["replaygain_%s_gain" % k].split()[0])
                except (ValueError, IndexError):
                    gain = 0
                try:
                    peak = float(self["replaygain_%s_peak" % k])
                except (ValueError, KeyError):
                    peak = 0
                # https://github.com/quodlibet/quodlibet/issues/1027
                peak = max(min(1.9, peak), 0)
                gain = max(min(63.9, gain), -64)
                f = mutagen.id3.RVA2(desc=k, channel=1, gain=gain, peak=peak)
                tag.add(f)

        for key in self.TXXX_MAP:
            try:
                del(tag["TXXX:" + key])
            except KeyError:
                pass
        for key in self.PAM_XXXT:
            if key in self.SDI:
                # we already write it back using non-TXXX frames
                continue
            if key in self:
                value = self[key]
                f = mutagen.id3.TXXX(encoding=encoding_for(value),
                                     text=value.split("\n"),
                                     desc=self.PAM_XXXT[key])
                tag.add(f)

        if (config.getboolean("editing", "save_to_songs") and
                (self.has_rating or self.get("~#playcount", 0) != 0)):
            email = config.get("editing", "save_email").strip()
            email = email or const.EMAIL
            t = mutagen.id3.POPM(email=email,
                                 rating=int(255 * self("~#rating")),
                                 count=self.get("~#playcount", 0))
            tag.add(t)

        with translate_errors():
            audio.save()
        self.sanitize()
Ejemplo n.º 49
0
def config_get(key, default=None):
    return config.get('plugins', 'spotify_%s' % key, default)
Ejemplo n.º 50
0
def config_get(key, default=None):
    return config.get('plugins', 'lastfmsync_%s' % key, default)
Ejemplo n.º 51
0
    def __init__(self, parent=None):
        if self.is_not_unique():
            return
        super(PluginWindow, self).__init__()
        self.set_title(_("Plugins"))
        self.set_default_size(700, 500)
        self.set_transient_for(parent)
        self.enable_window_tracking("plugin_prefs")

        paned = Paned()
        vbox = Gtk.VBox()
        vbox.set_property("margin-top", 3)

        sw = ScrolledWindow()
        sw.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.ALWAYS)

        model = ObjectStore()
        filter_model = ObjectModelFilter(child_model=model)

        self._list_view = tv = PluginListView()
        tv.set_model(filter_model)
        tv.set_rules_hint(True)

        tv.connect("plugin-toggled", self.__plugin_toggled)

        fb = Gtk.HBox(spacing=6)

        enabled_combo = PluginEnabledFilterCombo()
        enabled_combo.connect("changed", lambda s: filter_model.refilter())
        enabled_combo.set_tooltip_text("Filter by plugin state / tag")
        fb.pack_start(enabled_combo, True, True, 0)
        self._enabled_combo = enabled_combo

        type_combo = PluginTypeFilterCombo()
        type_combo.connect("changed", lambda s: filter_model.refilter())
        type_combo.set_tooltip_text("Filter by plugin type")
        fb.pack_start(type_combo, True, True, 0)
        self._type_combo = type_combo

        filter_entry = UndoSearchEntry()
        filter_entry.set_tooltip_text("Filter by plugin name or description")
        filter_entry.connect("changed", lambda s: filter_model.refilter())
        self._filter_entry = filter_entry

        sw.add(tv)
        sw.set_shadow_type(Gtk.ShadowType.IN)

        bbox = Gtk.HBox(homogeneous=True, spacing=6)

        errors = qltk.Button(_("Show _Errors"), Icons.DIALOG_WARNING)
        errors.set_focus_on_click(False)
        errors.connect('clicked', self.__show_errors)
        errors.set_no_show_all(True)
        bbox.pack_start(errors, True, True, 0)

        pref_box = PluginPreferencesContainer()

        if const.DEBUG:
            refresh = qltk.Button(_("_Refresh"), Icons.VIEW_REFRESH)
            refresh.set_focus_on_click(False)
            refresh.connect('clicked', self.__refresh, tv, pref_box, errors,
                            enabled_combo)
            bbox.pack_start(refresh, True, True, 0)

        filter_box = Gtk.VBox(spacing=6)
        filter_box.pack_start(fb, False, True, 0)
        filter_box.pack_start(filter_entry, False, True, 0)
        vbox.pack_start(Align(filter_box, border=6, right=-6), False, False, 0)
        vbox.pack_start(sw, True, True, 0)
        vbox.pack_start(Align(bbox, border=6, right=-6), False, True, 0)
        paned.pack1(vbox, False, False)

        close = qltk.Button(_("_Close"), Icons.WINDOW_CLOSE)
        close.connect('clicked', lambda *x: self.destroy())
        bb_align = Align(halign=Gtk.Align.END, valign=Gtk.Align.END)
        bb = Gtk.HButtonBox()
        bb.set_layout(Gtk.ButtonBoxStyle.END)
        bb.pack_start(close, True, True, 0)
        bb_align.add(bb)

        selection = tv.get_selection()
        selection.connect('changed', self.__selection_changed, pref_box)
        selection.emit('changed')

        right_box = Gtk.VBox(spacing=12)
        right_box.pack_start(pref_box, True, True, 0)
        self.use_header_bar()
        if not self.has_close_button():
            right_box.pack_start(bb_align, True, True, 0)

        paned.pack2(Align(right_box, border=12), True, False)
        paned.set_position(275)

        self.add(paned)

        self.__refill(tv, pref_box, errors, enabled_combo)

        self.connect('destroy', self.__destroy)
        filter_model.set_visible_func(
            self.__filter, (filter_entry, enabled_combo, type_combo))

        self.get_child().show_all()
        filter_entry.grab_focus()

        restore_id = config.get("memory", "plugin_selection")
        tv.select_by_plugin_id(restore_id)
Ejemplo n.º 52
0
 def __get_repeat_class(self):
     name = config.get("memory", "repeat_mode", None)
     return self._repeat_orders.by_name(name) or RepeatSongForever
Ejemplo n.º 53
0
 def do_revert_split(button, section, option):
     config.reset(section, option)
     e.set_text(config.get(section, option))
Ejemplo n.º 54
0
 def __get_shuffle_class(self):
     name = config.get("memory", "shuffle_mode", None)
     return self._shuffle_orders.by_name(name) or OrderShuffle
Ejemplo n.º 55
0
        def ratings_vbox(self):
            """Returns a new VBox containing all ratings widgets"""
            vb = Gtk.VBox(spacing=6)

            # Default Rating
            model = Gtk.ListStore(float)
            default_combo = Gtk.ComboBox(model=model)
            default_lab = Gtk.Label(label=_("_Default rating:"))
            default_lab.set_use_underline(True)
            default_lab.set_alignment(0, 0.5)

            def draw_rating(column, cell, model, it, data):
                num = model[it][0]
                text = "%0.2f: %s" % (num, util.format_rating(num))
                cell.set_property('text', text)

            def default_rating_changed(combo, model):
                it = combo.get_active_iter()
                if it is None:
                    return
                RATINGS.default = model[it][0]
                qltk.redraw_all_toplevels()

            def populate_default_rating_model(combo, num):
                model = combo.get_model()
                model.clear()
                deltas = []
                default = RATINGS.default
                precision = RATINGS.precision
                for i in range(0, num + 1):
                    r = i * precision
                    model.append(row=[r])
                    deltas.append((abs(default - r), i))
                active = sorted(deltas)[0][1]
                print_d("Choosing #%d (%.2f), closest to current %.2f" %
                        (active, precision * active, default))
                combo.set_active(active)

            cell = Gtk.CellRendererText()
            default_combo.pack_start(cell, True)
            default_combo.set_cell_data_func(cell, draw_rating, None)
            default_combo.connect('changed', default_rating_changed, model)
            default_lab.set_mnemonic_widget(default_combo)

            def refresh_default_combo(num):
                populate_default_rating_model(default_combo, num)

            # Rating Scale
            model = Gtk.ListStore(int)
            scale_combo = Gtk.ComboBox(model=model)
            scale_lab = Gtk.Label(label=_("Rating _scale:"))
            scale_lab.set_use_underline(True)
            scale_lab.set_mnemonic_widget(scale_combo)

            cell = Gtk.CellRendererText()
            scale_combo.pack_start(cell, False)
            num = RATINGS.number
            for i in [1, 2, 3, 4, 5, 6, 8, 10]:
                it = model.append(row=[i])
                if i == num:
                    scale_combo.set_active_iter(it)

            def draw_rating_scale(column, cell, model, it, data):
                num_stars = model[it][0]
                text = "%d: %s" % (num_stars, RATINGS.full_symbol * num_stars)
                cell.set_property('text', text)

            def rating_scale_changed(combo, model):
                it = combo.get_active_iter()
                if it is None:
                    return
                RATINGS.number = num = model[it][0]
                refresh_default_combo(num)

            refresh_default_combo(RATINGS.number)
            scale_combo.set_cell_data_func(cell, draw_rating_scale, None)
            scale_combo.connect('changed', rating_scale_changed, model)

            default_align = Align(halign=Gtk.Align.START)
            default_align.add(default_lab)
            scale_align = Align(halign=Gtk.Align.START)
            scale_align.add(scale_lab)

            grid = Gtk.Grid(column_spacing=6, row_spacing=6)
            grid.add(scale_align)
            grid.add(scale_combo)
            grid.attach(default_align, 0, 1, 1, 1)
            grid.attach(default_combo, 1, 1, 1, 1)
            vb.pack_start(grid, False, False, 6)

            # Bayesian Factor
            bayesian_factor = config.getfloat("settings",
                                              "bayesian_rating_factor", 0.0)
            adj = Gtk.Adjustment.new(bayesian_factor, 0.0, 10.0, 0.5, 0.5, 0.0)
            bayes_spin = Gtk.SpinButton(adjustment=adj, numeric=True)
            bayes_spin.set_digits(1)
            bayes_spin.connect('changed', self.__changed_and_signal_library,
                               'settings', 'bayesian_rating_factor')
            bayes_spin.set_tooltip_text(
                _("Bayesian Average factor (C) for aggregated ratings.\n"
                  "0 means a conventional average, higher values mean that "
                  "albums with few tracks will have less extreme ratings. "
                  "Changing this value triggers a re-calculation for all "
                  "albums."))
            bayes_label = Gtk.Label(label=_("_Bayesian averaging amount:"))
            bayes_label.set_use_underline(True)
            bayes_label.set_mnemonic_widget(bayes_spin)

            # Save Ratings
            hb = Gtk.HBox(spacing=6)
            hb.pack_start(bayes_label, False, True, 0)
            hb.pack_start(bayes_spin, False, True, 0)
            vb.pack_start(hb, True, True, 0)
            cb = CCB(_("Save ratings and play _counts"),
                     "editing",
                     "save_to_songs",
                     populate=True)
            vb.pack_start(cb, True, True, 0)
            hb = Gtk.HBox(spacing=6)
            lab = Gtk.Label(label=_("_Email:"))
            entry = UndoEntry()
            entry.set_tooltip_text(
                _("Ratings and play counts will be set "
                  "for this email address"))
            entry.set_text(config.get("editing", "save_email"))
            entry.connect('changed', self.__changed, 'editing', 'save_email')
            hb.pack_start(lab, False, True, 0)
            hb.pack_start(entry, True, True, 0)
            lab.set_mnemonic_widget(entry)
            lab.set_use_underline(True)
            vb.pack_start(hb, True, True, 0)

            return vb
Ejemplo n.º 56
0
 def config_get(cls, name, default=""):
     """Gets a config string value for this plugin"""
     return config.get(PM.CONFIG_SECTION, cls._config_key(name), default)
Ejemplo n.º 57
0
 def getter(section, option):
     return decode(config.get(section, option))
Ejemplo n.º 58
0
    def __init__(self, filename):
        with translate_errors():
            audio = self.Kind(filename)
        if audio.tags is None:
            audio.add_tags()
        tag = audio.tags

        self._parse_info(audio.info)

        for frame in tag.values():
            if frame.FrameID == "APIC" and len(frame.data):
                self.has_images = True
                continue
            elif frame.FrameID == "TCON":
                self["genre"] = "\n".join(frame.genres)
                continue
            elif (frame.FrameID == "UFID" and
                  frame.owner == "http://musicbrainz.org"):
                self["musicbrainz_trackid"] = frame.data.decode("utf-8",
                                                                "replace")
                continue
            elif frame.FrameID == "POPM":
                rating = frame.rating / 255.0
                if frame.email == const.EMAIL:
                    try:
                        self.setdefault("~#playcount", frame.count)
                    except AttributeError:
                        pass
                    self.setdefault("~#rating", rating)
                elif frame.email == config.get("editing", "save_email"):
                    try:
                        self["~#playcount"] = frame.count
                    except AttributeError:
                        pass
                    self["~#rating"] = rating
                continue
            elif frame.FrameID == "COMM" and frame.desc == "":
                name = "comment"
            elif frame.FrameID in ["COMM", "TXXX"]:
                if frame.desc.startswith("QuodLibet::"):
                    name = frame.desc[11:]
                elif frame.desc in self.TXXX_MAP:
                    name = self.TXXX_MAP[frame.desc]
                else:
                    continue
            elif frame.FrameID == "RVA2":
                self.__process_rg(frame)
                continue
            elif frame.FrameID == "TMCL":
                for role, name in frame.people:
                    key = self.__validate_name("performer:" + role)
                    if key:
                        self.add(key, name)
                continue
            elif frame.FrameID == "TLAN":
                self["language"] = "\n".join(frame.text)
                continue
            elif frame.FrameID == "USLT":
                name = "lyrics"
            else:
                name = self.IDS.get(frame.FrameID, "").lower()

            name = self.__validate_name(name)
            if not name:
                continue
            name = name.lower()

            id3id = frame.FrameID
            if id3id.startswith("T"):
                text = "\n".join(map(str, frame.text))
            elif id3id == "COMM":
                text = "\n".join(frame.text)
            elif id3id == "USLT":
                # lyrics are single string, not list
                text = frame.text
                self["~lyricsdescription"] = frame.desc
                self["~lyricslanguage"] = frame.lang
            elif id3id.startswith("W"):
                text = frame.url
                frame.encoding = 0
            else:
                continue

            if not text:
                continue
            text = self.__distrust_latin1(text, frame.encoding)
            if text is None:
                continue

            if name in self:
                self[name] += "\n" + text
            else:
                self[name] = text
            self[name] = self[name].strip()

            # to catch a missing continue above
            del name

        # foobar2000 writes long dates in a TXXX DATE tag, leaving the TDRC
        # tag out. Read the TXXX DATE, but only if the TDRC tag doesn't exist
        # to avoid reverting or duplicating tags in existing libraries.
        if audio.tags and "date" not in self:
            for frame in tag.getall('TXXX:DATE'):
                self["date"] = "\n".join(map(str, frame.text))

        # Read TXXX replaygain and replace previously read values from RVA2
        for frame in tag.getall("TXXX"):
            k = frame.desc.lower()
            if k in RG_KEYS:
                self[str(k)] = u"\n".join(map(str, frame.text))

        self.sanitize(filename)
Ejemplo n.º 59
0
 def test_mapping(self):
     c = PluginConfig("some")
     c.set("foo", "bar")
     self.assertEqual(config.get("plugins", "some_foo"), "bar")
Ejemplo n.º 60
0
    def __init_pipeline(self):
        """Creates a gstreamer pipeline. Returns True on success."""

        if self.bin:
            return True

        # reset error state
        self.error = False

        pipeline = config.get("player", "gst_pipeline")
        try:
            pipeline, self._pipeline_desc = GStreamerSink(pipeline)
        except PlayerError as e:
            self._error(e)
            return False

        if self._use_eq and Gst.ElementFactory.find('equalizer-10bands'):
            # The equalizer only operates on 16-bit ints or floats, and
            # will only pass these types through even when inactive.
            # We push floats through to this point, then let the second
            # audioconvert handle pushing to whatever the rest of the
            # pipeline supports. As a bonus, this seems to automatically
            # select the highest-precision format supported by the
            # rest of the chain.
            filt = Gst.ElementFactory.make('capsfilter', None)
            filt.set_property('caps',
                              Gst.Caps.from_string('audio/x-raw,format=F32LE'))
            eq = Gst.ElementFactory.make('equalizer-10bands', None)
            self._eq_element = eq
            self.update_eq_values()
            conv = Gst.ElementFactory.make('audioconvert', None)
            resample = Gst.ElementFactory.make('audioresample', None)
            pipeline = [filt, eq, conv, resample] + pipeline

        # playbin2 has started to control the volume through pulseaudio,
        # which means the volume property can change without us noticing.
        # Use our own volume element for now until this works with PA.
        self._int_vol_element = Gst.ElementFactory.make('volume', None)
        pipeline.insert(0, self._int_vol_element)

        # Get all plugin elements and append audio converters.
        # playbin already includes one at the end
        plugin_pipeline = []
        for plugin in self._get_plugin_elements():
            plugin_pipeline.append(plugin)
            plugin_pipeline.append(
                Gst.ElementFactory.make('audioconvert', None))
            plugin_pipeline.append(
                Gst.ElementFactory.make('audioresample', None))
        pipeline = plugin_pipeline + pipeline

        bufbin = Gst.Bin()
        for element in pipeline:
            assert element is not None, pipeline
            bufbin.add(element)

        if len(pipeline) > 1:
            if not link_many(pipeline):
                print_w("Linking the GStreamer pipeline failed")
                self._error(
                    PlayerError(_("Could not create GStreamer pipeline")))
                return False

        # see if the sink provides a volume property, if yes, use it
        sink_element = pipeline[-1]
        if isinstance(sink_element, Gst.Bin):
            sink_element = iter_to_list(sink_element.iterate_recurse)[-1]

        self._ext_vol_element = None
        if hasattr(sink_element.props, "volume"):
            self._ext_vol_element = sink_element

            # In case we use the sink volume directly we can increase buffering
            # without affecting the volume change delay too much and safe some
            # CPU time... (2x default for now).
            if hasattr(sink_element.props, "buffer_time"):
                sink_element.set_property("buffer-time", 400000)

            def ext_volume_notify(*args):
                # gets called from a thread
                GLib.idle_add(self.notify, "volume")

            self._ext_vol_element.connect("notify::volume", ext_volume_notify)

        self._ext_mute_element = None
        if hasattr(sink_element.props, "mute") and \
                sink_element.get_factory().get_name() != "directsoundsink":
            # directsoundsink has a mute property but it doesn't work
            # https://bugzilla.gnome.org/show_bug.cgi?id=755106
            self._ext_mute_element = sink_element

            def mute_notify(*args):
                # gets called from a thread
                GLib.idle_add(self.notify, "mute")

            self._ext_mute_element.connect("notify::mute", mute_notify)

        # Make the sink of the first element the sink of the bin
        gpad = Gst.GhostPad.new('sink', pipeline[0].get_static_pad('sink'))
        bufbin.add_pad(gpad)

        bin_ = Gst.ElementFactory.make('playbin', None)
        assert bin_

        self.bin = BufferingWrapper(bin_, self)
        self._seeker = Seeker(self.bin, self)

        bus = bin_.get_bus()
        bus.add_signal_watch()
        self.__bus_id = bus.connect('message', self.__message, self._librarian)

        self.__atf_id = self.bin.connect('about-to-finish',
                                         self.__about_to_finish)

        # set buffer duration
        duration = config.getfloat("player", "gst_buffer")
        self._set_buffer_duration(int(duration * 1000))

        # connect playbin to our pluing/volume/eq pipeline
        self.bin.set_property('audio-sink', bufbin)

        # by default playbin will render video -> suppress using fakesink
        fakesink = Gst.ElementFactory.make('fakesink', None)
        self.bin.set_property('video-sink', fakesink)

        # disable all video/text decoding in playbin
        GST_PLAY_FLAG_VIDEO = 1 << 0
        GST_PLAY_FLAG_TEXT = 1 << 2
        flags = self.bin.get_property("flags")
        flags &= ~(GST_PLAY_FLAG_VIDEO | GST_PLAY_FLAG_TEXT)
        self.bin.set_property("flags", flags)

        if not self.has_external_volume:
            # Restore volume/ReplayGain and mute state
            self.props.volume = self._volume
            self.mute = self._mute

        # ReplayGain information gets lost when destroying
        self._reset_replaygain()

        if self.song:
            self.bin.set_property('uri', self.song("~uri"))

        return True