def get_example_xml(song_path, rating, lastplayed): song_uri = fsn2uri(song_path) mount_uri = fsn2uri(find_mount_point(song_path)) return (u"""\ <?xml version="1.0" standalone="yes"?> <rhythmdb version="1.9"> <entry type="song"> <title>Music</title> <genre>Unknown</genre> <track-number>7</track-number> <duration>199</duration> <file-size>4799124</file-size> <location>%s</location> <mountpoint>%s</mountpoint> <mtime>1378717158</mtime> <first-seen>1339576187</first-seen> <last-seen>1409855394</last-seen> <last-played>%d</last-played> <play-count>1</play-count> <bitrate>191</bitrate> <rating>%d</rating> <date>731881</date> <media-type>audio/mpeg</media-type> <composer>Unknown</composer> </entry> </rhythmdb>\ """ % (song_uri, mount_uri, lastplayed, rating)).encode("utf-8")
def test_fsn2uri(self): if os.name != "nt": uri = fsn2uri(fsnative(u"/öäü.txt")) self.assertEqual(uri, u"file:///%C3%B6%C3%A4%C3%BC.txt") else: uri = fsn2uri(fsnative(u"C:\\öäü.txt")) self.assertEqual(uri, "file:///C:/%C3%B6%C3%A4%C3%BC.txt") self.assertEqual(fsn2uri(u"C:\\SomeDir\xe4"), "file:///C:/SomeDir%C3%A4")
def test_fsn2uri(self): if os.name != "nt": uri = fsn2uri(fsnative(u"/öäü.txt")) self.assertEqual(uri, u"file:///%C3%B6%C3%A4%C3%BC.txt") else: uri = fsn2uri(fsnative(u"C:\\öäü.txt")) self.assertEqual( uri, "file:///C:/%C3%B6%C3%A4%C3%BC.txt") self.assertEqual( fsn2uri(u"C:\\SomeDir\xe4"), "file:///C:/SomeDir%C3%A4")
def test_uri_roundtrip(): if os.name == "nt": for path in [ u"C:\\foo-\u1234", u"C:\\bla\\quux ha", u"\\\\\u1234\\foo\\\u1423", u"\\\\foo;\\f" ]: path = fsnative(path) assert uri2fsn(fsn2uri(path)) == path assert isinstance(uri2fsn(fsn2uri(path)), fsnative) else: path = path2fsn(b"/foo-\xe1\x88\xb4") assert uri2fsn(fsn2uri(fsnative(u"/foo"))) == "/foo" assert uri2fsn(fsn2uri(path)) == path assert isinstance(uri2fsn(fsn2uri(path)), fsnative)
def test_thumb(s): thumb = thumbnails.get_thumbnail(s.filename, (50, 60), ignore_temp=False) #check for right scaling s.failUnless(thumb) s.failUnlessEqual((thumb.get_width(), thumb.get_height()), (50, 25)) #test the thumbnail filename uri = fsn2uri(s.filename) name = hash.md5(uri.encode("ascii")).hexdigest() + ".png" path = thumbnails.get_thumbnail_folder() path = os.path.join(path, "normal", name) s.failUnless(os.path.isfile(path)) #check for metadata thumb_pb = GdkPixbuf.Pixbuf.new_from_file(path) meta_mtime = thumb_pb.get_option("tEXt::Thumb::MTime") meta_uri = thumb_pb.get_option("tEXt::Thumb::URI") s.failUnlessEqual(int(meta_mtime), int(mtime(s.filename))) s.failUnlessEqual(meta_uri, uri) #check rights if os.name != "nt": s.failUnlessEqual(os.stat(path).st_mode, 33152)
def test_any_pathnames(path): fsn = path2fsn(path) abspath = os.path.abspath(fsn) if os.path.isabs(abspath): if is_wine: # FIXME: fails on native Windows assert uri2fsn(fsn2uri(abspath)) == abspath
def get_example_db(song_path, rating, playcount, skipcount, lastplayed, dateadded): # create a temporary database in memory db = sqlite3.connect(':memory:') # create a simplified version of a banshee track table csr = db.cursor() csr.execute('''CREATE TABLE CoreTracks( ArtistID INTEGER, AlbumID INTEGER, Uri TEXT, Title TEXT, Rating INTEGER, PlayCount INTEGER, SkipCount INTEGER, LastPlayedStamp INTEGER, DateAddedStamp INTEGER ) ''') # insert song and save song_uri = fsn2uri(song_path) csr.execute('INSERT INTO CoreTracks VALUES (?,?,?,?,?,?,?,?,?)', (1, 1, song_uri, 'Music', rating, playcount, skipcount, lastplayed, dateadded)) db.commit() # give the user the in-memory database return db
def test_thumb(s): thumb = thumbnails.get_thumbnail(s.filename, (50, 60)) #check for right scaling s.failUnless(thumb) s.failUnlessEqual((thumb.get_width(), thumb.get_height()), (50, 25)) #test the thumbnail filename uri = fsn2uri(s.filename) name = hash.md5(uri.encode("ascii")).hexdigest() + ".png" path = thumbnails.get_thumbnail_folder() path = os.path.join(path, "normal", name) s.failUnless(os.path.isfile(path)) #check for metadata thumb_pb = GdkPixbuf.Pixbuf.new_from_file(path) meta_mtime = thumb_pb.get_option("tEXt::Thumb::MTime") meta_uri = thumb_pb.get_option("tEXt::Thumb::URI") s.failUnlessEqual(int(meta_mtime), int(mtime(s.filename))) s.failUnlessEqual(meta_uri, uri) #check rights if os.name != "nt": s.failUnlessEqual(os.stat(path).st_mode, 33152)
def get_cache_info(path, boundary): """For an image at `path` return (cache_path, thumb_size) cache_path points to a potential cache file thumb size is either 128 or 256 """ assert isinstance(path, fsnative) width, height = boundary if width <= ThumbSize.NORMAL and height <= ThumbSize.NORMAL: size_name = "normal" thumb_size = ThumbSize.NORMAL else: size_name = "large" thumb_size = ThumbSize.LARGE thumb_folder = get_thumbnail_folder() cache_dir = os.path.join(thumb_folder, size_name) uri = fsn2uri(path) thumb_name = hashlib.md5(uri).hexdigest() + ".png" thumb_path = os.path.join(cache_dir, thumb_name) return (thumb_path, thumb_size)
def _get_image_uri(self, song): """A unicode file URI or an empty string""" fileobj = app.cover_manager.get_cover(song) self._set_image_fileobj(fileobj) if fileobj: return fsn2uri(fileobj.name) return u""
def _show_files_thunar(dirname, entries): # https://git.xfce.org/xfce/thunar/tree/thunar/thunar-dbus-service-infos.xml XFCE_PATH = "/org/xfce/FileManager" XFCE_NAME = "org.xfce.FileManager" XFCE_IFACE = "org.xfce.FileManager" try: dbus_proxy = _get_dbus_proxy(XFCE_NAME, XFCE_PATH, XFCE_IFACE) if not entries: dbus_proxy.DisplayFolder('(sss)', fsn2uri(dirname), "", _get_startup_id()) else: dbus_proxy.DisplayFolderAndSelect( '(ssss)', fsn2uri(dirname), entries[0], "", _get_startup_id()) except GLib.Error as e: raise BrowseError(e)
def _show_files_fdo(dirname, entries): # https://www.freedesktop.org/wiki/Specifications/file-manager-interface/ FDO_PATH = "/org/freedesktop/FileManager1" FDO_NAME = "org.freedesktop.FileManager1" FDO_IFACE = "org.freedesktop.FileManager1" try: dbus_proxy = _get_dbus_proxy(FDO_NAME, FDO_PATH, FDO_IFACE) if not entries: dbus_proxy.ShowFolders('(ass)', [fsn2uri(dirname)], _get_startup_id()) else: item_uri = fsn2uri(os.path.join(dirname, entries[0])) dbus_proxy.ShowItems('(ass)', [item_uri], _get_startup_id()) except GLib.Error as e: raise BrowseError(e)
def label_path(path): l = Gtk.Label(label="<a href='%s'>%s</a>" % ( fsn2uri(path), escape(fsn2text(unexpand(path)))), use_markup=True, ellipsize=Pango.EllipsizeMode.MIDDLE, xalign=0, selectable=True) l.connect("activate-link", show_uri) return l
def test_roundtrip(self): if os.name == "nt": paths = [u"C:\\öäü.txt"] else: paths = [u"/öäü.txt", u"/a/foo/bar", u"/a/b/foo/bar"] for source in paths: path = uri2fsn(fsn2uri(fsnative(source))) self.assertTrue(isinstance(path, fsnative)) self.assertEqual(path, fsnative(source))
def test_roundtrip(self): if os.name == "nt": paths = [u"C:\\öäü.txt"] else: paths = [u"/öäü.txt", u"//foo/bar", u"///foo/bar"] for source in paths: path = uri2fsn(fsn2uri(fsnative(source))) self.assertTrue(is_fsnative(path)) self.assertEqual(path, fsnative(source))
def test_feed(self): fn = get_data_path('valid_feed.xml') feed = Feed(fsn2uri(fn)) result = feed.parse() # Assume en_US / en_GB locale here in tests self.failIfEqual(feed.name, "Unknown", msg="Didn't find feed name") # Do this after the above, as many exceptions can be swallowed self.failUnless(result) self.failUnlessEqual(len(feed), 2) self.failUnlessEqual(feed[0]('title'), 'Full Episode: Tuesday, November 28, 2017')
def test_parse_onesong_uri(self): target = get_data_path("silence-44-s.ogg") target = fsn2uri(target).encode("ascii") target = self.prefix + target with temp_filename() as name: with open(name, "wb") as f: f.write(target) with open(name, "rb") as f: pl = self.Parse(f, name, pl_lib=self.pl_lib) self.failUnlessEqual(len(pl), 1) self.failUnlessEqual(pl[0]("title"), "Silence") pl.delete()
def _show_files_fdo(dirname, entries): # http://www.freedesktop.org/wiki/Specifications/file-manager-interface FDO_PATH = "/org/freedesktop/FileManager1" FDO_NAME = "org.freedesktop.FileManager1" FDO_IFACE = "org.freedesktop.FileManager1" if not dbus: raise BrowseError("no dbus") try: bus = dbus.SessionBus() bus_object = bus.get_object(FDO_NAME, FDO_PATH) bus_iface = dbus.Interface(bus_object, dbus_interface=FDO_IFACE) if not entries: bus_iface.ShowFolders([fsn2uri(dirname)], _get_startup_id()) else: item_uri = fsn2uri(os.path.join(dirname, entries[0])) bus_iface.ShowItems([item_uri], _get_startup_id()) except dbus.DBusException as e: raise BrowseError(e)
def test_parse_onesong_uri(self): target = get_data_path("silence-44-s.ogg") target = fsn2uri(target).encode("ascii") target = self.prefix + target with temp_filename() as name: with open(name, "wb") as f: f.write(target) with open(name, "rb") as f: pl = self.Parse(f, name) self.failUnlessEqual(len(pl), 1) self.failUnlessEqual(pl[0]("title"), "Silence") pl.delete()
def _show_files_thunar(dirname, entries): # http://git.xfce.org/xfce/thunar/tree/thunar/thunar-dbus-service-infos.xml XFCE_PATH = "/org/xfce/FileManager" XFCE_NAME = "org.xfce.FileManager" XFCE_IFACE = "org.xfce.FileManager" if not dbus: raise BrowseError("no dbus") try: bus = dbus.SessionBus() bus_object = bus.get_object(XFCE_NAME, XFCE_PATH) bus_iface = dbus.Interface(bus_object, dbus_interface=XFCE_IFACE) if not entries: bus_iface.DisplayFolder(fsn2uri(dirname), "", _get_startup_id()) else: item_name = os.path.join(dirname, entries[0]) bus_iface.DisplayFolderAndSelect( fsn2uri(dirname), item_name, "", _get_startup_id()) except dbus.DBusException as e: raise BrowseError(e)
def test_parse_onesong_uri(self): h, name = mkstemp() os.close(h) target = get_data_path("silence-44-s.ogg") target = fsn2uri(target) target = self.prefix + target with open(name, "w") as f: f.write(target) list = self.Parse(name) os.unlink(name) self.failUnlessEqual(len(list), 1) self.failUnlessEqual(list[0]("title"), "Silence") list.delete()
def _show_files_thunar(dirname, entries): # http://git.xfce.org/xfce/thunar/tree/thunar/thunar-dbus-service-infos.xml XFCE_PATH = "/org/xfce/FileManager" XFCE_NAME = "org.xfce.FileManager" XFCE_IFACE = "org.xfce.FileManager" if not dbus: raise BrowseError("no dbus") try: bus = dbus.SessionBus() bus_object = bus.get_object(XFCE_NAME, XFCE_PATH) bus_iface = dbus.Interface(bus_object, dbus_interface=XFCE_IFACE) if not entries: bus_iface.DisplayFolder(fsn2uri(dirname), "", _get_startup_id()) else: item_name = os.path.join(dirname, entries[0]) bus_iface.DisplayFolderAndSelect(fsn2uri(dirname), item_name, "", _get_startup_id()) except dbus.DBusException as e: raise BrowseError(e)
def test_parse_onesong_uri(self): h, name = mkstemp() os.close(h) target = get_data_path("silence-44-s.ogg") target = fsn2uri(target).encode("ascii") target = self.prefix + target with open(name, "wb") as f: f.write(target) list = self.Parse(name) os.unlink(name) self.failUnlessEqual(len(list), 1) self.failUnlessEqual(list[0]("title"), "Silence") list.delete()
def test_parse_onesong_uri(self): h, name = mkstemp() os.close(h) target = os.path.join(DATA_DIR, "silence-44-s.ogg") target = fsn2uri(target) target = self.prefix + target with open(name, "w") as f: f.write(target) list = self.Parse(name) os.unlink(name) self.failUnlessEqual(len(list), 1) self.failUnlessEqual(list[0]("title"), "Silence") list.delete()
def test_surrogates(): if os.name == "nt": assert fsn2bytes(u"\ud83d", "utf-16-le") == b"=\xd8" assert bytes2fsn(b"\xd8=", "utf-16-be") == u"\ud83d" with pytest.raises(ValueError): bytes2fsn(b"\xd8=a", "utf-16-be") with pytest.raises(ValueError): bytes2fsn(b"=\xd8a", "utf-16-le") # for utf-16-le we have a workaround assert bytes2fsn(b"=\xd8", "utf-16-le") == u"\ud83d" assert bytes2fsn(b"=\xd8=\xd8", "utf-16-le") == u"\ud83d\ud83d" with pytest.raises(ValueError): bytes2fsn(b"=\xd8\x00\x00", "utf-16-le") # 4 byte code point assert fsn2bytes(u"\U0001f600", "utf-16-le") == b"=\xd8\x00\xde" assert bytes2fsn(b"=\xd8\x00\xde", "utf-16-le") == u"\U0001f600" # 4 byte codepoint + lone surrogate assert bytes2fsn(b"=\xd8\x00\xde=\xd8", "utf-16-le") == \ u"\U0001f600\ud83d" with pytest.raises(UnicodeDecodeError): bytes2fsn(b"a", "utf-16-le") assert fsn2bytes(u"\ud83d", "utf-8") == b"\xed\xa0\xbd" assert bytes2fsn(b"\xed\xa0\xbd", "utf-8") == u"\ud83d" assert fsnative(u"\ud83d") == u"\ud83d" assert fsn2text(u"\ud83d") == u"\ufffd" # at least don't fail... assert fsn2uri(u"C:\\\ud83d") == u"file:///C:/%ED%A0%BD" else: # this shouldn't fail and produce the same result on py2/3 at least. assert fsn2bytes(fsnative(u"\ud83d"), None) == b"\xed\xa0\xbd" text2fsn(fsn2text(fsnative(u"\ud83d"))) if PY2 and isunicodeencoding(): # under Python 2 we get surrogates, but we can't do anything about # it since most codecs don't treat that as an error assert fsn2text(fsnative(u"\ud83d")) == u"\ud83d" else: # under Python 3 the decoder don't allow surrogates assert fsn2text(fsnative(u"\ud83d")) == u"\ufffd\ufffd\ufffd"
def write(self): track_list = Element("trackList") # TODO: ditch for proper indent, once we have Python 3.9 track_list.text = "\n" for song in self._list: if isinstance(song, str): track = {"location": fsn2uri(song)} else: creator = self.CREATOR_PATTERN.format(song) mbid = song("musicbrainz_trackid") track = { "location": song("~uri"), "identifier": (f"https://musicbrainz.org/recording/{mbid}" if mbid else None), "title": song("title"), "creator": creator, "album": song("album"), "trackNum": song("~#track"), "duration": int(song("~#length") * 1000.) } track_list.append(self._element_from("track", track, True)) playlist = Element("playlist", attrib={ "version": "1", "xmlns": XSPF_NS }) # Be kind to cat, git, editors etc. by leaving a final newline playlist.tail = "\n" playlist.append(self._version_tag()) playlist.append(self._text_element("title", self.name)) playlist.append(self._text_element("date", datetime.now().isoformat())) playlist.append(track_list) tree = ElementTree(playlist) ET.register_namespace("", XSPF_NS) path = self.path print_d(f"Writing {path !r}") tree.write(path, encoding="utf-8", xml_declaration=True) if self._last_fn != path: self._delete_file(self._last_fn) self._last_fn = path
def get_property(self, interface, name): if interface == MediaObject.IFACE: if name == "Parent": return EntryObject.PATH elif name == "Type": return "image" elif name == "Path": return Icon.PATH elif name == "DisplayName": return "I'm an icon \o/" elif interface == MediaItem.IFACE: if name == "URLs": return [fsn2uri(self.__f.name)] elif name == "MIMEType": return "image/png" elif name == "Width" or name == "Height": return Icon.SIZE elif name == "ColorDepth": return self.__depth
def browse_folders_thunar(songs, display=""): # http://git.xfce.org/xfce/thunar/tree/thunar/thunar-dbus-service-infos.xml XFCE_PATH = "/org/xfce/FileManager" XFCE_NAME = "org.xfce.FileManager" XFCE_IFACE = "org.xfce.FileManager" if not dbus: raise BrowseError("no dbus") try: bus = dbus.SessionBus() bus_object = bus.get_object(XFCE_NAME, XFCE_PATH) bus_iface = dbus.Interface(bus_object, dbus_interface=XFCE_IFACE) # open each folder and select the first file we have selected for dirname, sub_songs in group_songs(songs).items(): bus_iface.DisplayFolderAndSelect(fsn2uri(dirname), sub_songs[0]("~basename"), display, get_startup_id()) except dbus.DBusException as e: raise BrowseError(e)
def browse_folders_thunar(songs, display=""): # http://git.xfce.org/xfce/thunar/tree/thunar/thunar-dbus-service-infos.xml XFCE_PATH = "/org/xfce/FileManager" XFCE_NAME = "org.xfce.FileManager" XFCE_IFACE = "org.xfce.FileManager" if not dbus: raise BrowseError("no dbus") try: bus = dbus.SessionBus() bus_object = bus.get_object(XFCE_NAME, XFCE_PATH) bus_iface = dbus.Interface(bus_object, dbus_interface=XFCE_IFACE) # open each folder and select the first file we have selected for dirname, sub_songs in group_songs(songs).items(): bus_iface.DisplayFolderAndSelect( fsn2uri(dirname), sub_songs[0]("~basename"), display, get_startup_id()) except dbus.DBusException as e: raise BrowseError(e)
def __drag_data_get(self, view, ctx, sel, tid, etime): model, rows = view.get_selection().get_selected_rows() dirs = [model[row][0] for row in rows] for songs in self.__find_songs(view.get_selection()): pass if tid == self.TARGET_QL: cant_add = filter(lambda s: not s.can_add, songs) if cant_add: qltk.ErrorMessage( qltk.get_top_parent(self), _("Unable to copy songs"), _("The files selected cannot be copied to other " "song lists or the queue.")).run() ctx.drag_abort(etime) return to_add = filter(self.__library.__contains__, songs) self.__add_songs(view, to_add) qltk.selection_set_songs(sel, songs) else: # External target (app) is delivered a list of URIS of songs uris = list({fsn2uri(dir) for dir in dirs}) print_d("Directories to drop: %s" % dirs) sel.set_uris(uris)
def test_fsn2uri(): with pytest.raises(TypeError): fsn2uri(object()) if os.name == "nt": with pytest.raises(TypeError): fsn2uri(u"\x00") assert fsn2uri(fsnative(u"C:\\foo")) == "file:///C:/foo" assert fsn2uri(u"C:\\ö ä%") == "file:///C:/%C3%B6%20%C3%A4%25" assert (fsn2uri(u"C:\\foo-\u1234") == "file:///C:/foo-%E1%88%B4") assert isinstance(fsn2uri(u"C:\\foo-\u1234"), text_type) assert fsn2uri(u"\\\serv\\share\\") == "file://serv/share/" assert fsn2uri(u"\\\serv\\\u1234\\") == "file://serv/%E1%88%B4/" assert \ fsn2uri(fsnative(u"\\\\UNC\\foo\\bar")) == u"file://UNC/foo/bar" # winapi can't handle too large paths. make sure we raise properly with pytest.raises(ValueError): fsn2uri(u"C:\\" + 4000 * u"a") assert fsn2uri(u"C:\\\uD800\uDC01") == u"file:///C:/%F0%90%80%81" else: with pytest.raises(TypeError): fsn2uri(b"\x00") if PY2: path = "/foo-\xe1\x88\xb4" else: path = fsnative(u"/foo-\u1234") assert fsn2uri(path) == "file:///foo-%E1%88%B4" assert isinstance(fsn2uri(path), text_type)
def __get_metadata_real(self): """ https://www.freedesktop.org/wiki/Specifications/mpris-spec/metadata/ """ metadata = {} metadata["mpris:trackid"] = self.__get_current_track_id() def ignore_overflow(dbus_type, value): try: return dbus_type(value) except OverflowError: return 0 song = app.player.info if not song: return metadata metadata["mpris:length"] = ignore_overflow( dbus.Int64, song("~#length") * 10 ** 6) if self.__cover is not None: self.__cover.close() self.__cover = None cover = app.cover_manager.get_cover(song) if cover: is_temp = cover.name.startswith(tempfile.gettempdir()) if is_temp: self.__cover = cover metadata["mpris:artUrl"] = fsn2uri(cover.name) # All list values list_val = {"artist": "artist", "albumArtist": "albumartist", "comment": "comment", "composer": "composer", "genre": "genre", "lyricist": "lyricist"} for xesam, tag in iteritems(list_val): vals = song.list(tag) if vals: metadata["xesam:" + xesam] = listmap(unival, vals) # All single values sing_val = {"album": "album", "title": "title", "asText": "~lyrics"} for xesam, tag in iteritems(sing_val): vals = song.comma(tag) if vals: metadata["xesam:" + xesam] = unival(vals) # URI metadata["xesam:url"] = song("~uri") # Integers num_val = {"audioBPM": "bpm", "discNumber": "disc", "trackNumber": "track", "useCount": "playcount"} for xesam, tag in iteritems(num_val): val = song("~#" + tag, None) if val is not None: metadata["xesam:" + xesam] = ignore_overflow(dbus.Int32, val) # Rating metadata["xesam:userRating"] = ignore_overflow( dbus.Double, song("~#rating")) # Dates ISO_8601_format = "%Y-%m-%dT%H:%M:%S" tuple_time = time.gmtime(song("~#lastplayed")) iso_time = time.strftime(ISO_8601_format, tuple_time) metadata["xesam:lastUsed"] = iso_time year = song("~year") if year: try: tuple_time = time.strptime(year, "%Y") iso_time = time.strftime(ISO_8601_format, tuple_time) except ValueError: pass else: metadata["xesam:contentCreated"] = iso_time return metadata
def test_win_unc_path(self): if os.name == "nt": self.assertEqual(fsn2uri(u"\\\\server\\share\\path"), u"file://server/share/path")
def __get_metadata(self): """http://xmms2.org/wiki/MPRIS_Metadata""" metadata = {} metadata["mpris:trackid"] = self.__get_current_track_id() song = app.player.info if not song: return metadata metadata["mpris:length"] = dbus.Int64(song("~#length") * 10 ** 6) self.__cover = cover = app.cover_manager.get_cover(song) is_temp = False if cover: name = cover.name is_temp = name.startswith(tempfile.gettempdir()) # This doesn't work for embedded images.. the file gets unlinked # after loosing the file handle metadata["mpris:artUrl"] = fsn2uri(name) if not is_temp: self.__cover = None # All list values list_val = {"artist": "artist", "albumArtist": "albumartist", "comment": "comment", "composer": "composer", "genre": "genre", "lyricist": "lyricist"} for xesam, tag in iteritems(list_val): vals = song.list(tag) if vals: metadata["xesam:" + xesam] = listmap(unival, vals) # All single values sing_val = {"album": "album", "title": "title", "asText": "~lyrics"} for xesam, tag in iteritems(sing_val): vals = song.comma(tag) if vals: metadata["xesam:" + xesam] = unival(vals) # URI metadata["xesam:url"] = song("~uri") # Integers num_val = {"audioBPM": "bpm", "discNumber": "disc", "trackNumber": "track", "useCount": "playcount"} for xesam, tag in iteritems(num_val): val = song("~#" + tag, None) if val is not None: metadata["xesam:" + xesam] = int(val) # Rating metadata["xesam:userRating"] = float(song("~#rating")) # Dates ISO_8601_format = "%Y-%m-%dT%H:%M:%S" tuple_time = time.gmtime(song("~#lastplayed")) iso_time = time.strftime(ISO_8601_format, tuple_time) metadata["xesam:lastUsed"] = iso_time year = song("~year") if year: try: tuple_time = time.strptime(year, "%Y") iso_time = time.strftime(ISO_8601_format, tuple_time) except ValueError: pass else: metadata["xesam:contentCreated"] = iso_time return metadata
def __get_metadata(self): """http://xmms2.org/wiki/MPRIS_Metadata""" metadata = {} metadata["mpris:trackid"] = self.__get_current_track_id() song = app.player.info if not song: return metadata metadata["mpris:length"] = dbus.Int64(song("~#length") * 10**6) self.__cover = cover = app.cover_manager.get_cover(song) is_temp = False if cover: name = cover.name is_temp = name.startswith(tempfile.gettempdir()) # This doesn't work for embedded images.. the file gets unlinked # after loosing the file handle metadata["mpris:artUrl"] = fsn2uri(name) if not is_temp: self.__cover = None # All list values list_val = { "artist": "artist", "albumArtist": "albumartist", "comment": "comment", "composer": "composer", "genre": "genre", "lyricist": "lyricist" } for xesam, tag in iteritems(list_val): vals = song.list(tag) if vals: metadata["xesam:" + xesam] = listmap(unival, vals) # All single values sing_val = {"album": "album", "title": "title", "asText": "~lyrics"} for xesam, tag in iteritems(sing_val): vals = song.comma(tag) if vals: metadata["xesam:" + xesam] = unival(vals) # URI metadata["xesam:url"] = song("~uri") # Integers num_val = { "audioBPM": "bpm", "discNumber": "disc", "trackNumber": "track", "useCount": "playcount" } for xesam, tag in iteritems(num_val): val = song("~#" + tag, None) if val is not None: metadata["xesam:" + xesam] = int(val) # Rating metadata["xesam:userRating"] = float(song("~#rating")) # Dates ISO_8601_format = "%Y-%m-%dT%H:%M:%S" tuple_time = time.gmtime(song("~#lastplayed")) iso_time = time.strftime(ISO_8601_format, tuple_time) metadata["xesam:lastUsed"] = iso_time year = song("~year") if year: try: tuple_time = time.strptime(year, "%Y") iso_time = time.strftime(ISO_8601_format, tuple_time) except ValueError: pass else: metadata["xesam:contentCreated"] = iso_time return metadata
def test_win_unc_path(self): if os.name == "nt": self.assertEqual( fsn2uri(u"\\\\server\\share\\path"), u"file://server/share/path")
def get_thumbnail(path, boundary): """Get a thumbnail pixbuf of an image at `path`. Will create/use a thumbnail in the user's thumbnail directory if possible. Follows the Free Desktop specification: http://specifications.freedesktop.org/thumbnail-spec/ Can raise GLib.GError. Thread-safe. """ assert isinstance(path, fsnative) width, height = boundary new_from_file_at_size = GdkPixbuf.Pixbuf.new_from_file_at_size # larger than thumbnails, load directly if width > ThumbSize.LARGEST or height > ThumbSize.LARGEST: return new_from_file_at_size(path, width, height) path_mtime = mtime(path) if path_mtime == 0: return new_from_file_at_size(path, width, height) # embedded thumbnails come from /tmp/ # FIXME: move this to another layer if path.startswith(tempfile.gettempdir()): return new_from_file_at_size(path, width, height) thumb_path, thumb_size = get_cache_info(path, boundary) cache_dir = os.path.dirname(thumb_path) try: mkdir(cache_dir, 0o700) except OSError: return new_from_file_at_size(path, width, height) try: pb = new_from_file_at_size(thumb_path, width, height) except GLib.GError: # in case it fails to load, we recreate it pass else: meta_mtime = pb.get_option("tEXt::Thumb::MTime") if meta_mtime is not None: try: meta_mtime = int(meta_mtime) except ValueError: pass else: if meta_mtime == int(path_mtime): return pb info, pw, ph = GdkPixbuf.Pixbuf.get_file_info(path) # Too small picture, no thumbnail needed if pw < thumb_size and ph < thumb_size: return new_from_file_at_size(path, width, height) thumb_pb = new_from_file_at_size(path, thumb_size, thumb_size) uri = fsn2uri(path) mime = info.get_mime_types()[0] options = { "tEXt::Thumb::Image::Width": str(pw), "tEXt::Thumb::Image::Height": str(ph), "tEXt::Thumb::URI": uri, "tEXt::Thumb::MTime": str(int(path_mtime)), "tEXt::Thumb::Size": str(os.path.getsize(path)), "tEXt::Thumb::Mimetype": mime, "tEXt::Software": "QuodLibet" } thumb_pb.savev(thumb_path, "png", options.keys(), options.values()) try: os.chmod(thumb_path, 0o600) except OSError: pass return scale(thumb_pb, boundary)
def __call__(self, key, default=u"", connector=" - ", joiner=', '): """Return the value(s) for a key, synthesizing if necessary. Multiple values for a key are delimited by newlines. A default value may be given (like `dict.get`); the default default is an empty unicode string (even if the tag is numeric). If a tied tag ('a~b') is requested, the `connector` keyword argument may be used to specify what it is tied with. In case the tied tag contains numeric and file path tags, the result will still be a unicode string. The `joiner` keyword specifies how multiple *values* will be joined within that tied tag output, e.g. ~people~title = "Kanye West, Jay Z - New Day" For details on tied tags, see the documentation for `util.tagsplit`. """ if key[:1] == "~": key = key[1:] if "~" in key: real_key = "~" + key values = [] sub_tags = util.tagsplit(real_key) # If it's genuinely a tied tag (not ~~people etc), we want # to delimit the multi-values separately from the tying j = joiner if len(sub_tags) > 1 else "\n" for t in sub_tags: vs = [decode_value(real_key, v) for v in (self.list(t))] v = j.join(vs) if v: values.append(v) return connector.join(values) or default elif key == "#track": try: return int(self["tracknumber"].split("/")[0]) except (ValueError, TypeError, KeyError): return default elif key == "#disc": try: return int(self["discnumber"].split("/")[0]) except (ValueError, TypeError, KeyError): return default elif key == "length": length = self.get("~#length") if length is None: return default else: return util.format_time_display(length) elif key == "#rating": return dict.get(self, "~" + key, config.RATINGS.default) elif key == "rating": return util.format_rating(self("~#rating")) elif key == "people": return "\n".join(self.list_unique(PEOPLE)) or default elif key == "people:real": # Issue 1034: Allow removal of V.A. if others exist. unique = self.list_unique(PEOPLE) # Order is important, for (unlikely case): multiple removals for val in VARIOUS_ARTISTS_VALUES: if len(unique) > 1 and val in unique: unique.remove(val) return "\n".join(unique) or default elif key == "people:roles": return (self._role_call("performer", PEOPLE) or default) elif key == "peoplesort": return ("\n".join(self.list_unique(PEOPLE_SORT)) or self("~people", default, connector)) elif key == "peoplesort:roles": # Ignores non-sort tags if there are any sort tags (e.g. just # returns "B" for {artist=A, performersort=B}). # TODO: figure out the "correct" behavior for mixed sort tags return (self._role_call("performersort", PEOPLE_SORT) or self("~peoplesort", default, connector)) elif key in ("performers", "performer"): return self._prefixvalue("performer") or default elif key in ("performerssort", "performersort"): return (self._prefixvalue("performersort") or self("~" + key[-4:], default, connector)) elif key in ("performers:roles", "performer:roles"): return (self._role_call("performer") or default) elif key in ("performerssort:roles", "performersort:roles"): return (self._role_call("performersort") or self("~" + key.replace("sort", ""), default, connector)) elif key == "basename": return os.path.basename(self["~filename"]) or self["~filename"] elif key == "dirname": return os.path.dirname(self["~filename"]) or self["~filename"] elif key == "uri": try: return self["~uri"] except KeyError: return fsn2uri(self["~filename"]) elif key == "format": return self.get("~format", text_type(self.format)) elif key == "codec": codec = self.get("~codec") if codec is None: return self("~format") return codec elif key == "encoding": parts = filter(None, [self.get("~encoding"), self.get("encodedby")]) encoding = u"\n".join(parts) return encoding or default elif key == "language": codes = self.list("language") if not codes: return default return u"\n".join(iso639.translate(c) or c for c in codes) elif key == "bitrate": return util.format_bitrate(self("~#bitrate")) elif key == "#date": date = self.get("date") if date is None: return default return util.date_key(date) elif key == "year": return self.get("date", default)[:4] elif key == "#year": try: return int(self.get("date", default)[:4]) except (ValueError, TypeError, KeyError): return default elif key == "originalyear": return self.get("originaldate", default)[:4] elif key == "#originalyear": try: return int(self.get("originaldate", default)[:4]) except (ValueError, TypeError, KeyError): return default elif key == "#tracks": try: return int(self["tracknumber"].split("/")[1]) except (ValueError, IndexError, TypeError, KeyError): return default elif key == "#discs": try: return int(self["discnumber"].split("/")[1]) except (ValueError, IndexError, TypeError, KeyError): return default elif key == "lyrics": # First, try the embedded lyrics. try: return self[key] except KeyError: pass # If there are no embedded lyrics, try to read them from # the external file. try: fileobj = open(self.lyric_filename, "rU") except EnvironmentError: return default else: return fileobj.read().decode("utf-8", "replace") elif key == "filesize": return util.format_size(self("~#filesize", 0)) elif key == "playlists": # See Issue 876 # Avoid circular references from formats/__init__.py from quodlibet.util.collection import Playlist playlists = Playlist.playlists_featuring(self) return "\n".join([s.name for s in playlists]) or default elif key.startswith("#replaygain_"): try: val = self.get(key[1:], default) return round(float(val.split(" ")[0]), 2) except (ValueError, TypeError, AttributeError): return default elif key[:1] == "#": key = "~" + key if key in self: return self[key] elif key in NUMERIC_ZERO_DEFAULT: return 0 else: try: val = self[key[2:]] except KeyError: return default try: return int(val) except ValueError: try: return float(val) except ValueError: return default else: return dict.get(self, "~" + key, default) elif key == "title": title = dict.get(self, "title") if title is None: basename = self("~basename") return "%s [%s]" % ( decode_value("~basename", basename), _("Unknown")) else: return title elif key in SORT_TO_TAG: try: return self[key] except KeyError: key = SORT_TO_TAG[key] return dict.get(self, key, default)
def __call__(self, key, default=u"", connector=" - "): """Return a key, synthesizing it if necessary. A default value may be given (like dict.get); the default default is an empty unicode string (even if the tag is numeric). If a tied tag ('a~b') is requested, the 'connector' keyword argument may be used to specify what it is tied with. In case the tied tag contains numeric and file path tags, the result will still be a unicode string. For details on tied tags, see the documentation for util.tagsplit. """ if key[:1] == "~": key = key[1:] if "~" in key: real_key = "~" + key values = [] for v in map(self.__call__, util.tagsplit(real_key)): v = decode_value(real_key, v) if v: values.append(v) return connector.join(values) or default elif key == "#track": try: return int(self["tracknumber"].split("/")[0]) except (ValueError, TypeError, KeyError): return default elif key == "#disc": try: return int(self["discnumber"].split("/")[0]) except (ValueError, TypeError, KeyError): return default elif key == "length": length = self.get("~#length") if length is None: return default else: return util.format_time_display(length) elif key == "#rating": return dict.get(self, "~" + key, config.RATINGS.default) elif key == "rating": return util.format_rating(self("~#rating")) elif key == "people": return "\n".join(self.list_unique(PEOPLE)) or default elif key == "people:real": # Issue 1034: Allow removal of V.A. if others exist. unique = self.list_unique(PEOPLE) # Order is important, for (unlikely case): multiple removals for val in VARIOUS_ARTISTS_VALUES: if len(unique) > 1 and val in unique: unique.remove(val) return "\n".join(unique) or default elif key == "people:roles": return (self._role_call("performer", PEOPLE) or default) elif key == "peoplesort": return ("\n".join(self.list_unique(PEOPLE_SORT)) or self("~people", default, connector)) elif key == "peoplesort:roles": # Ignores non-sort tags if there are any sort tags (e.g. just # returns "B" for {artist=A, performersort=B}). # TODO: figure out the "correct" behavior for mixed sort tags return (self._role_call("performersort", PEOPLE_SORT) or self("~peoplesort", default, connector)) elif key in ("performers", "performer"): return self._prefixvalue("performer") or default elif key in ("performerssort", "performersort"): return (self._prefixvalue("performersort") or self("~" + key[-4:], default, connector)) elif key in ("performers:roles", "performer:roles"): return (self._role_call("performer") or default) elif key in ("performerssort:roles", "performersort:roles"): return (self._role_call("performersort") or self( "~" + key.replace("sort", ""), default, connector)) elif key == "basename": return os.path.basename(self["~filename"]) or self["~filename"] elif key == "dirname": return os.path.dirname(self["~filename"]) or self["~filename"] elif key == "uri": try: return self["~uri"] except KeyError: return text_type(fsn2uri(self["~filename"])) elif key == "format": return self.get("~format", self.format) elif key == "codec": codec = self.get("~codec") if codec is None: return self("~format") return codec elif key == "encoding": parts = filter(None, [self.get("~encoding"), self.get("encodedby")]) encoding = u"\n".join(parts) return encoding or default elif key == "language": codes = self.list("language") if not codes: return default return u"\n".join(iso639.translate(c) or c for c in codes) elif key == "bitrate": return util.format_bitrate(self("~#bitrate")) elif key == "#date": date = self.get("date") if date is None: return default return util.date_key(date) elif key == "year": return self.get("date", default)[:4] elif key == "#year": try: return int(self.get("date", default)[:4]) except (ValueError, TypeError, KeyError): return default elif key == "originalyear": return self.get("originaldate", default)[:4] elif key == "#originalyear": try: return int(self.get("originaldate", default)[:4]) except (ValueError, TypeError, KeyError): return default elif key == "#tracks": try: return int(self["tracknumber"].split("/")[1]) except (ValueError, IndexError, TypeError, KeyError): return default elif key == "#discs": try: return int(self["discnumber"].split("/")[1]) except (ValueError, IndexError, TypeError, KeyError): return default elif key == "lyrics": # First, try the embedded lyrics. try: return self[key] except KeyError: pass # If there are no embedded lyrics, try to read them from # the external file. try: fileobj = open(self.lyric_filename, "rU") except EnvironmentError: return default else: return fileobj.read().decode("utf-8", "replace") elif key == "filesize": return util.format_size(self("~#filesize", 0)) elif key == "playlists": # See Issue 876 # Avoid circular references from formats/__init__.py from quodlibet.util.collection import Playlist playlists = Playlist.playlists_featuring(self) return "\n".join([s.name for s in playlists]) or default elif key.startswith("#replaygain_"): try: val = self.get(key[1:], default) return round(float(val.split(" ")[0]), 2) except (ValueError, TypeError, AttributeError): return default elif key[:1] == "#": key = "~" + key if key in self: return self[key] elif key in NUMERIC_ZERO_DEFAULT: return 0 else: try: val = self[key[2:]] except KeyError: return default try: return int(val) except ValueError: try: return float(val) except ValueError: return default else: return dict.get(self, "~" + key, default) elif key == "title": title = dict.get(self, "title") if title is None: basename = self("~basename") return "%s [%s]" % (decode_value("~basename", basename), _("Unknown")) else: return title elif key in SORT_TO_TAG: try: return self[key] except KeyError: key = SORT_TO_TAG[key] return dict.get(self, key, default)
def __call__(self, key, default: Any = u"", connector=" - ", joiner=', '): """Return the value(s) for a key, synthesizing if necessary. Multiple values for a key are delimited by newlines. A default value may be given (like `dict.get`); the default default is an empty unicode string (even if the tag is numeric). If a tied tag ('a~b') is requested, the `connector` keyword argument may be used to specify what it is tied with. In case the tied tag contains numeric and file path tags, the result will still be a unicode string. The `joiner` keyword specifies how multiple *values* will be joined within that tied tag output, e.g. ~people~title = "Kanye West, Jay Z - New Day" For details on tied tags, see the documentation for `util.tagsplit`. """ if key[:1] == "~": key = key[1:] if "~" in key: real_key = "~" + key values = [] sub_tags = util.tagsplit(real_key) # If it's genuinely a tied tag (not ~~people etc), we want # to delimit the multi-values separately from the tying j = joiner if len(sub_tags) > 1 else "\n" for t in sub_tags: vs = [decode_value(real_key, v) for v in (self.list(t))] v = j.join(vs) if v: values.append(v) return connector.join(values) or default elif key == "#track": try: return int(self["tracknumber"].split("/")[0]) except (ValueError, TypeError, KeyError): return default elif key == "#disc": try: return int(self["discnumber"].split("/")[0]) except (ValueError, TypeError, KeyError): return default elif key == "length": length = self.get("~#length") if length is None: return default else: return util.format_time_display(length) elif key == "#rating": return dict.get(self, "~" + key, config.RATINGS.default) elif key == "rating": return util.format_rating(self("~#rating")) elif key == "people": return "\n".join(self.list_unique(PEOPLE)) or default elif key == "people:real": # Issue 1034: Allow removal of V.A. if others exist. unique = self.list_unique(PEOPLE) # Order is important, for (unlikely case): multiple removals for val in VARIOUS_ARTISTS_VALUES: if len(unique) > 1 and val in unique: unique.remove(val) return "\n".join(unique) or default elif key == "people:roles": return (self._role_call("performer", PEOPLE) or default) elif key == "peoplesort": return ("\n".join(self.list_unique(PEOPLE_SORT)) or self("~people", default, connector)) elif key == "peoplesort:roles": # Ignores non-sort tags if there are any sort tags (e.g. just # returns "B" for {artist=A, performersort=B}). # TODO: figure out the "correct" behavior for mixed sort tags return (self._role_call("performersort", PEOPLE_SORT) or self("~peoplesort", default, connector)) elif key in ("performers", "performer"): return self._prefixvalue("performer") or default elif key in ("performerssort", "performersort"): return (self._prefixvalue("performersort") or self("~" + key[-4:], default, connector)) elif key in ("performers:roles", "performer:roles"): return (self._role_call("performer") or default) elif key in ("performerssort:roles", "performersort:roles"): return (self._role_call("performersort") or self( "~" + key.replace("sort", ""), default, connector)) elif key == "basename": return os.path.basename(self["~filename"]) or self["~filename"] elif key == "dirname": return os.path.dirname(self["~filename"]) or self["~filename"] elif key == "uri": try: return self["~uri"] except KeyError: return fsn2uri(self["~filename"]) elif key == "format": return self.get("~format", str(self.format)) elif key == "codec": codec = self.get("~codec") if codec is None: return self("~format") return codec elif key == "encoding": encoding = "\n".join( part for part in [self.get("~encoding"), self.get("encodedby")] if part) return encoding or default elif key == "language": codes = self.list("language") if not codes: return default return u"\n".join(iso639.translate(c) or c for c in codes) elif key == "bitrate": return util.format_bitrate(self("~#bitrate")) elif key == "#date": date = self.get("date") if date is None: return default return util.date_key(date) elif key == "year": return self.get("date", default)[:4] elif key == "#year": try: return int(self.get("date", default)[:4]) except (ValueError, TypeError, KeyError): return default elif key == "originalyear": return self.get("originaldate", default)[:4] elif key == "#originalyear": try: return int(self.get("originaldate", default)[:4]) except (ValueError, TypeError, KeyError): return default elif key == "#tracks": try: return int(self["tracknumber"].split("/")[1]) except (ValueError, IndexError, TypeError, KeyError): return default elif key == "#discs": try: return int(self["discnumber"].split("/")[1]) except (ValueError, IndexError, TypeError, KeyError): return default elif key == "lyrics": # First, try the embedded lyrics. try: return self["lyrics"] except KeyError: pass try: return self["unsyncedlyrics"] except KeyError: pass # If there are no embedded lyrics, try to read them from # the external file. lyric_filename = self.lyric_filename if not lyric_filename: return default try: with open(lyric_filename, "rb") as fileobj: print_d(f"Reading lyrics from {lyric_filename!r}") text = fileobj.read().decode("utf-8", "replace") # try to skip binary files if "\0" in text: return default return text except (EnvironmentError, UnicodeDecodeError): return default elif key == "filesize": return util.format_size(self("~#filesize", 0)) elif key == "playlists": # TODO: avoid static dependency here... somehow from quodlibet import app lib = app.library if not lib: return "" playlists = lib.playlists.playlists_featuring(self) return "\n".join(s.name for s in playlists) or default elif key.startswith("#replaygain_"): try: val = self.get(key[1:], default) return round(float(val.split(" ")[0]), 2) except (ValueError, TypeError, AttributeError): return default elif key[:1] == "#": key = "~" + key if key in self: return self[key] elif key in NUMERIC_ZERO_DEFAULT: return 0 else: try: val = self[key[2:]] except KeyError: return default try: return int(val) except ValueError: try: return float(val) except ValueError: return default else: return dict.get(self, "~" + key, default) elif key == "title": title = dict.get(self, "title") if title is None: # build a title with missing_title_template option unknown_track_template = _( config.gettext("browsers", "missing_title_template")) from quodlibet.pattern import Pattern try: pattern = Pattern(unknown_track_template) except ValueError: title = decode_value("~basename", self("~basename")) else: title = pattern % self return title elif key in SORT_TO_TAG: try: return self[key] except KeyError: key = SORT_TO_TAG[key] return dict.get(self, key, default)