diff --git a/beetsplug/thumbnails.py b/beetsplug/thumbnails.py new file mode 100644 index 000000000..6963c76d7 --- /dev/null +++ b/beetsplug/thumbnails.py @@ -0,0 +1,96 @@ +# -*- coding: utf-8 -*- +# This file is part of beets. +# Copyright 2015, Bruno Cauet +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. + +"""Create freedesktop.org-compliant thumnails for album folders""" + +from __future__ import (division, absolute_import, print_function, + unicode_literals) + +from hashlib import md5 +import os +import shutil +from pathlib import PurePosixPath + +from xdg import BaseDirectory + +from beets.plugins import BeetsPlugin +from beets.ui import Subcommand, decargs +from beets.util import syspath, displayable_path +from beets.util.artresizer import ArtResizer + + +class ThumbnailsPlugin(BeetsPlugin): + def __init__(self): + super(ThumbnailsPlugin, self).__init__() + if self._check_local_ok(): + self.register_listener('album_imported', self.imported) + + def commands(self): + thumbnails_command = Subcommand("thumbnails", + help="Create album thumbnails") + thumbnails_command.func = self.process_query + return [thumbnails_command] + + def imported(self, lib, album): + self.process_album(album) + + def process_query(self, lib, opts, args): + if self._check_local_ok(): + for album in lib.albums(decargs(args)): + self.process_album(album) + + def _check_local_ok(self): + if not ArtResizer.local: + self._log.warning("No local image resizing capabilities, " + "cannot generate thumbnails") + return False + return True + + def process_album(self, album): + """Produce a thumbnail for the album folder + + The thumbnail is a PNG of resolution either 256x256 or lower than + 128x128 + """ + if not album.artpath: + self._log.info(u'album {0} has no art', album) + return + + # FIXME should create dirs if needed? + + # FIXME should handle covers smaller than 256x256 + # see http://standards.freedesktop.org/thumbnail-spec/latest/x122.html + + target = os.path.join(BaseDirectory.xdg_cache_home, + "thumbnails", "large", + self.thumbnail_file_name(album.path)) + + resized = ArtResizer.shared.resize(256, + syspath(album.artpath), + syspath(target)) + + # FIXME should add tags + # see http://standards.freedesktop.org/thumbnail-spec/latest/x142.html + + shutil.move(resized, target) + self._log.info(u'wrote thumbnail for {0}', album) + self._log.debug(u'thumbnail is at {0}', displayable_path(target)) + + @staticmethod + def thumbnail_file_name(path): + # http://standards.freedesktop.org/thumbnail-spec/latest/x227.html + uri = PurePosixPath(path).as_uri() + hash = md5(uri).hexdigest() + return "{0}.png".format(hash) diff --git a/setup.py b/setup.py index 76669c62c..215593146 100755 --- a/setup.py +++ b/setup.py @@ -106,6 +106,7 @@ setup( 'mpdstats': ['python-mpd'], 'web': ['flask', 'flask-cors'], 'import': ['rarfile'], + 'thumbnails': ['pathlib'], }, # Non-Python/non-PyPI plugin dependencies: # replaygain: mp3gain || aacgain