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, 3)) #test the thumbnail filename uri = "file://" + pathname2url(s.filename) name = hash.md5(uri).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 __init__(self, name, module, deps, path): self.name = name self.module = module self.path = path self.deps = {} for dep in deps: self.deps[dep] = mtime(dep)
def has_changed(self, dep_paths): if set(self.deps.keys()) != set(dep_paths): return True for path, old_mtime in iteritems(self.deps): if mtime(path) != old_mtime: return True return False
def has_changed(self, dep_paths): if set(self.deps.keys()) != set(dep_paths): return True for path, old_mtime in self.deps.items(): if mtime(path) != old_mtime: return True return False
def copy(self, parent_widget, song): if not self.__pattern: self.__set_pattern() target = strip_win32_incompat_from_path(self.__pattern.format(song)) dirname = os.path.dirname(target) if os.path.exists(target): dialog = ConfirmFileReplace(parent_widget, target) resp = dialog.run() if resp == ConfirmFileReplace.RESPONSE_REPLACE: try: # Remove the current song self.__library.remove([self.__library[target]]) except KeyError: pass else: return False try: if not os.path.isdir(dirname): os.makedirs(dirname) shutil.copyfile(song['~filename'], target) if self['covers']: coverfile = os.path.join(dirname, 'folder.jpg') cover = app.cover_manager.get_cover(song) if cover and mtime(cover.name) > mtime(coverfile): image = GdkPixbuf.Pixbuf.new_from_file_at_size( cover.name, 200, 200) image.savev(coverfile, "jpeg", [], []) song = copy.deepcopy(song) song.sanitize(target) self.__library.add([song]) return song except (OSError, IOError, GLib.GError) as exc: encoding = util.get_locale_encoding() return str(exc).decode(encoding, 'replace')
def save(save_period=None): """Save all registered libraries that have a filename and are marked dirty. If `save_period` (seconds) is given the library will only be saved if it hasn't been in the last `save_period` seconds. """ print_d("Saving all libraries...") librarian = SongFileLibrary.librarian for lib in librarian.libraries.values(): filename = lib.filename if not filename or not lib.dirty: continue if not save_period or abs(time.time() - mtime(filename)) > save_period: lib.save()
def __changed(self, selector, selection, label): model, rows = selection.get_selected_rows() files = [] if len(rows) < 2: count = len(model or []) else: count = len(rows) label.set_text(numeric_phrase("%d song", "%d songs", count)) for row in rows: filename = model[row][0] if not os.path.exists(filename): pass elif filename in self.__library: song = self.__library[filename] if song("~#mtime") + 1. < mtime(filename): try: song.reload() except AudioFileError: pass files.append(song) else: files.append(formats.MusicFile(filename)) files = list(filter(None, files)) if len(files) == 0: self.set_title("Ex Falso") elif len(files) == 1: self.set_title("%s - Ex Falso" % files[0].comma("title")) else: params = ({ 'title': files[0].comma("title"), 'count': format_int_locale(len(files) - 1) }) self.set_title("%s - Ex Falso" % (ngettext( "%(title)s and %(count)s more", "%(title)s and %(count)s more", len(files) - 1) % params)) self.__library.add(files) self.emit('changed', files)
def __changed(self, selector, selection, label): model, rows = selection.get_selected_rows() files = [] if len(rows) < 2: count = len(model or []) else: count = len(rows) label.set_text(numeric_phrase("%d song", "%d songs", count)) for row in rows: filename = model[row][0] if not os.path.exists(filename): pass elif filename in self.__library: song = self.__library[filename] if song("~#mtime") + 1. < mtime(filename): try: song.reload() except AudioFileError: pass files.append(song) else: files.append(formats.MusicFile(filename)) files = list(filter(None, files)) if len(files) == 0: self.set_title("Ex Falso") elif len(files) == 1: self.set_title("%s - Ex Falso" % files[0].comma("title")) else: params = ({'title': files[0].comma("title"), 'count': format_int_locale(len(files) - 1)}) self.set_title( "%s - Ex Falso" % (ngettext("%(title)s and %(count)s more", "%(title)s and %(count)s more", len(files) - 1) % params)) self.__library.add(files) self.emit('changed', files)
def __changed(self, selector, selection, label): model, rows = selection.get_selected_rows() files = [] if len(rows) < 2: count = len(model or []) else: count = len(rows) label.set_text(ngettext("%d song", "%d songs", count) % count) for row in rows: filename = model[row][0] if not os.path.exists(filename): pass elif filename in self.__library: file = self.__library[filename] if file("~#mtime") + 1. < mtime(filename): try: file.reload() except StandardError: pass files.append(file) else: files.append(formats.MusicFile(filename)) files = filter(None, files) if len(files) == 0: self.set_title("Ex Falso") elif len(files) == 1: self.set_title("%s - Ex Falso" % files[0].comma("title")) else: self.set_title( "%s - Ex Falso" % (ngettext("%(title)s and %(count)d more", "%(title)s and %(count)d more", len(files) - 1) % ( {'title': files[0].comma("title"), 'count': len(files) - 1}))) self.__library.add(files) self.emit('changed', files)
def __changed(self, selector, selection, label): model, rows = selection.get_selected_rows() files = [] if len(rows) < 2: count = len(model or []) else: count = len(rows) label.set_text(ngettext("%d song", "%d songs", count) % count) for row in rows: filename = util.fsnative(model[row][0]) if not os.path.exists(filename): pass elif filename in self.__library: file = self.__library[filename] if file("~#mtime") + 1. < mtime(filename): try: file.reload() except StandardError: pass files.append(file) else: files.append(formats.MusicFile(filename)) files = filter(None, files) if len(files) == 0: self.set_title("Ex Falso") elif len(files) == 1: self.set_title("%s - Ex Falso" % files[0].comma("title")) else: self.set_title("%s - Ex Falso" % (ngettext( "%(title)s and %(count)d more", "%(title)s and %(count)d more", len(files) - 1) % ({ 'title': files[0].comma("title"), 'count': len(files) - 1 }))) self.__library.add(files) self.emit('changed', files)
def save(force=False): """Save all registered libraries that have a filename and are marked dirty. If force = True save all of them blocking, else save non-blocking and only if they were last saved more than LIBRARY_SAVE_PERIOD_SECONDS ago. """ print_d("Saving all libraries...") librarian = SongFileLibrary.librarian for lib in librarian.libraries.values(): filename = lib.filename if not filename or not lib.dirty: continue if force: try: lib.save() except EnvironmentError: pass lib.destroy() elif time.time() - mtime(filename) > LIBRARY_SAVE_PERIOD_SECONDS: threading.Thread(target=lib.save).run()
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. """ 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 = "file://" + pathname2url(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 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. """ width, height = boundary # embedded thumbnails come from /tmp/ # and too big thumbnails make no sense if path.startswith(tempfile.gettempdir()) or \ width > 256 or height > 256 or mtime(path) == 0: return GdkPixbuf.Pixbuf.new_from_file_at_size(path, width, height) if width <= 128 and height <= 128: size_name = "normal" thumb_size = 128 else: size_name = "large" thumb_size = 256 thumb_folder = get_thumbnail_folder() cache_dir = os.path.join(thumb_folder, size_name) try: mkdir(cache_dir, 0700) except OSError: return GdkPixbuf.Pixbuf.new_from_file_at_size(path, width, height) bytes_ = path if isinstance(path, unicode): bytes_ = path.encode("utf-8") uri = "file://" + pathname2url(bytes_) thumb_name = hashlib.md5(uri).hexdigest() + ".png" thumb_path = os.path.join(cache_dir, thumb_name) pb = meta_mtime = None if os.path.exists(thumb_path): pb = GdkPixbuf.Pixbuf.new_from_file(thumb_path) meta_mtime = pb.get_option("tEXt::Thumb::MTime") meta_mtime = meta_mtime and int(meta_mtime) if not pb or meta_mtime != int(mtime(path)): pb = GdkPixbuf.Pixbuf.new_from_file(path) #Too small picture, no thumbnail needed if pb.get_width() < thumb_size and pb.get_height() < thumb_size: return scale(pb, boundary) info = GdkPixbuf.Pixbuf.get_file_info(path)[0] mime = info.get_mime_types()[0] options = { "tEXt::Thumb::Image::Width": str(pb.get_width()), "tEXt::Thumb::Image::Height": str(pb.get_height()), "tEXt::Thumb::URI": uri, "tEXt::Thumb::MTime": str(int(mtime(path))), "tEXt::Thumb::Size": str(os.path.getsize(fsnative(path))), "tEXt::Thumb::Mimetype": mime, "tEXt::Software": "QuodLibet" } pb = scale(pb, (thumb_size, thumb_size)) pb.savev(thumb_path, "png", options.keys(), options.values()) try: os.chmod(thumb_path, 0600) except OSError: pass return scale(pb, boundary)
def copy(self, songlist, song): if self.__load_db() is None: return False track = gpod.itdb_track_new() # All values should be utf-8 encoded strings # Filepaths should be encoded with the fs encoding # Either combine tags with comma, or only take the first value if self['all_tags']: tag = song.comma else: tag = lambda key: (song.list(key) or ('', ))[0] title = tag('title') if self['title_version'] and song('version'): title = " - ".join([title, song('version')]) track.title = encode(title) album = tag('album') if self['album_part'] and song('discsubtitle'): album = " - ".join([album, song('discsubtitle')]) track.album = encode(album) # String keys for key in ['artist', 'genre', 'grouping', 'composer', 'albumartist']: if hasattr(track, key): # albumartist since libgpod-0.4.2 setattr(track, key, encode(tag(key))) # Sort keys (since libgpod-0.5.0) for key in ['artist', 'album', 'albumartist']: if hasattr(track, 'sort_' + key): setattr(track, 'sort_' + key, encode(tag(key + 'sort'))) # Numeric keys for key in ['bitrate', 'playcount', 'year']: try: setattr(track, key, int(song('~#' + key))) except ValueError: continue # Numeric keys where the names differ for key, value in { 'cd_nr': song('~#disc'), 'cds': song('~#discs'), 'rating': min(100, song('~#rating') * 100), 'time_added': self.__mactime(time.time()), 'time_modified': self.__mactime(mtime(song('~filename'))), 'track_nr': song('~#track'), 'tracklen': song('~#length') * 1000, 'tracks': song('~#tracks'), 'size': filesize(song('~filename')), 'soundcheck': self.__soundcheck(song), }.items(): try: setattr(track, key, int(value)) except ValueError: continue track.filetype = song('~format') track.comment = encode(fsdecode(song('~filename'))) # Associate a cover with the track if self['covers']: cover = song.find_cover() if cover: # libgpod will copy the file later when the iTunesDB # is saved, so we have to keep a reference around in # case the cover is a temporary file. self.__covers.append(cover) gpod.itdb_track_set_thumbnails(track, fsencode(cover.name)) # Add the track to the master playlist gpod.itdb_track_add(self.__itdb, track, -1) master = gpod.itdb_playlist_mpl(self.__itdb) gpod.itdb_playlist_add_track(master, track, -1) # Copy the actual file if gpod.itdb_cp_track_to_ipod(track, song['~filename'], None) == 1: return IPodSong(track) else: return False
def copy(self, parent_widget, song): if self.__load_db() is None: return False track = gpod.itdb_track_new() # All values should be utf-8 encoded strings # Filepaths should be encoded with the fs encoding # Either combine tags with comma, or only take the first value if self['all_tags']: tag = song.comma else: tag = lambda key: (song.list(key) or ('',))[0] title = tag('title') if self['title_version'] and song('version'): title = " - ".join([title, song('version')]) track.title = encode(title) album = tag('album') if self['album_part'] and song('discsubtitle'): album = " - ".join([album, song('discsubtitle')]) track.album = encode(album) # String keys for key in ['artist', 'genre', 'grouping', 'composer', 'albumartist']: if hasattr(track, key): # albumartist since libgpod-0.4.2 setattr(track, key, encode(tag(key))) # Sort keys (since libgpod-0.5.0) for key in ['artist', 'album', 'albumartist']: if hasattr(track, 'sort_' + key): setattr(track, 'sort_' + key, encode(tag(key + 'sort'))) # Numeric keys for key in ['bitrate', 'playcount', 'year']: try: setattr(track, key, int(song('~#' + key))) except ValueError: continue # Numeric keys where the names differ for key, value in { 'cd_nr': song('~#disc'), 'cds': song('~#discs'), 'rating': min(100, song('~#rating') * 100), 'time_added': self.__mactime(time.time()), 'time_modified': self.__mactime(mtime(song('~filename'))), 'track_nr': song('~#track'), 'tracklen': song('~#length') * 1000, 'tracks': song('~#tracks'), 'size': filesize(song('~filename')), 'soundcheck': self.__soundcheck(song), }.items(): try: setattr(track, key, int(value)) except ValueError: continue track.filetype = encode(song('~format')) track.comment = encode(fsdecode(song('~filename'))) # Associate a cover with the track if self['covers']: cover = app.cover_manager.get_cover(song) if cover: # libgpod will copy the file later when the iTunesDB # is saved, so we have to keep a reference around in # case the cover is a temporary file. self.__covers.append(cover) gpod.itdb_track_set_thumbnails( track, fsnative2glib(cover.name)) # Add the track to the master playlist gpod.itdb_track_add(self.__itdb, track, -1) master = gpod.itdb_playlist_mpl(self.__itdb) gpod.itdb_playlist_add_track(master, track, -1) # Copy the actual file if gpod.itdb_cp_track_to_ipod(track, song['~filename'], None) == 1: return IPodSong(track) else: return False
def valid(self): """Return true if the file cache is up-to-date (checked via mtime), or we can't tell.""" return (bool(self.get("~#mtime", 0)) and self["~#mtime"] == mtime(self["~filename"]))
def test_bad(self): self.failIf(os.path.exists("/dev/doesnotexist")) self.failUnlessEqual(mtime("/dev/doesnotexist"), 0)
def test_equal(self): self.failUnlessEqual(mtime("."), os.path.getmtime("."))
def _execute(self, options, args): if len(args) < 1: raise CommandError(_("Not enough arguments")) elif len(args) > 1: raise CommandError(_("Too many arguments")) song = self.load_song(args[0]) dump = self._song_to_text(song).encode("utf-8") # write to tmp file fd, path = tempfile.mkstemp(suffix=".txt") # XXX: copy mtime here so we can test for changes in tests by # setting a out of date mtime on the source song file copy_mtime(args[0], path) try: try: os.write(fd, dump) finally: os.close(fd) # only parse the result if the editor returns 0 and the mtime has # changed old_mtime = mtime(path) editor_args = get_editor_args() self.log("Using editor: %r" % editor_args) try: subprocess.check_call(editor_args + [path]) except subprocess.CalledProcessError as e: self.log(text_type(e)) raise CommandError(_("Editing aborted")) except OSError as e: self.log(text_type(e)) raise CommandError( _("Starting text editor '%(editor-name)s' failed.") % { "editor-name": editor_args[0]}) was_changed = mtime(path) != old_mtime if not was_changed: raise CommandError(_("No changes detected")) with open(path, "rb") as h: data = h.read() finally: os.unlink(path) try: text = data.decode("utf-8") except ValueError as e: raise CommandError("Invalid data: %r" % e) if options.dry_run: self.verbose = True self._text_to_song(text, song) if not options.dry_run: self.save_songs([song])
def _execute(self, options, args): if len(args) < 1: raise CommandError(_("Not enough arguments")) elif len(args) > 1: raise CommandError(_("Too many arguments")) song = self.load_song(args[0]) dump = self._song_to_text(song).encode("utf-8") # write to tmp file fd, path = tempfile.mkstemp(suffix=".txt") # XXX: copy mtime here so we can test for changes in tests by # setting a out of date mtime on the source song file copy_mtime(args[0], path) try: try: os.write(fd, dump) finally: os.close(fd) # only parse the result if the editor returns 0 and the mtime has # changed old_mtime = mtime(path) editor_args = get_editor_args() self.log("Using editor: %r" % editor_args) try: subprocess.check_call(editor_args + [path]) except subprocess.CalledProcessError as e: self.log(str(e)) raise CommandError(_("Editing aborted")) except OSError as e: self.log(str(e)) raise CommandError( _("Starting text editor '%(editor-name)s' failed.") % {"editor-name": editor_args[0]}) was_changed = mtime(path) != old_mtime if not was_changed: raise CommandError(_("No changes detected")) with open(path, "rb") as h: data = h.read() finally: os.unlink(path) try: text = data.decode("utf-8") except ValueError as e: raise CommandError("Invalid data: %r" % e) if options.dry_run: self.verbose = True self._text_to_song(text, song) if not options.dry_run: self.save_songs([song])
def copy(self, parent_widget, song): if self.__load_db() is None: return False track = gpod.itdb_track_new() # All values should be utf-8 encoded strings # Filepaths should be encoded with the fs encoding # Either combine tags with comma, or only take the first value if self["all_tags"]: tag = song.comma else: tag = lambda key: (song.list(key) or ("",))[0] title = tag("title") if self["title_version"] and song("version"): title = " - ".join([title, song("version")]) track.title = encode(title) album = tag("album") if self["album_part"] and song("discsubtitle"): album = " - ".join([album, song("discsubtitle")]) track.album = encode(album) # String keys for key in ["artist", "genre", "grouping", "composer", "albumartist"]: if hasattr(track, key): # albumartist since libgpod-0.4.2 setattr(track, key, encode(tag(key))) # Sort keys (since libgpod-0.5.0) for key in ["artist", "album", "albumartist"]: if hasattr(track, "sort_" + key): setattr(track, "sort_" + key, encode(tag(key + "sort"))) # Numeric keys for key in ["bitrate", "playcount", "year"]: try: setattr(track, key, int(song("~#" + key))) except ValueError: continue # Numeric keys where the names differ for key, value in { "cd_nr": song("~#disc"), "cds": song("~#discs"), "rating": min(100, song("~#rating") * 100), "time_added": self.__mactime(time.time()), "time_modified": self.__mactime(mtime(song("~filename"))), "track_nr": song("~#track"), "tracklen": song("~#length") * 1000, "tracks": song("~#tracks"), "size": filesize(song("~filename")), "soundcheck": self.__soundcheck(song), }.items(): try: setattr(track, key, int(value)) except ValueError: continue track.filetype = encode(song("~format")) track.comment = encode(fsdecode(song("~filename"))) # Associate a cover with the track if self["covers"]: cover = app.cover_manager.get_cover(song) if cover: # libgpod will copy the file later when the iTunesDB # is saved, so we have to keep a reference around in # case the cover is a temporary file. self.__covers.append(cover) gpod.itdb_track_set_thumbnails(track, fsnative2glib(cover.name)) # Add the track to the master playlist gpod.itdb_track_add(self.__itdb, track, -1) master = gpod.itdb_playlist_mpl(self.__itdb) gpod.itdb_playlist_add_track(master, track, -1) # Copy the actual file if gpod.itdb_cp_track_to_ipod(track, song["~filename"], None) == 1: return IPodSong(track) else: return False