Files
beets/test/plugins/test_thumbnails.py
T

247 lines
8.5 KiB
Python

import os.path
from unittest.mock import Mock, call, patch
import pytest
from beets.test.helper import BeetsTestCase
from beets.util import syspath
from beetsplug.thumbnails import (
LARGE_DIR,
NORMAL_DIR,
GioURI,
PathlibURI,
ThumbnailsPlugin,
)
class ThumbnailsTest(BeetsTestCase):
@patch("beetsplug.thumbnails.ArtResizer")
@patch("beetsplug.thumbnails.ThumbnailsPlugin._check_local_ok", Mock())
@patch("beetsplug.thumbnails.os.stat")
def test_add_tags(self, mock_stat, mock_artresizer):
plugin = ThumbnailsPlugin()
plugin.get_uri = Mock(
side_effect={b"/path/to/cover": "COVER_URI"}.__getitem__
)
album = Mock(artpath=b"/path/to/cover")
mock_stat.return_value.st_mtime = 12345
plugin.add_tags(album, b"/path/to/thumbnail")
metadata = {"Thumb::URI": "COVER_URI", "Thumb::MTime": "12345"}
mock_artresizer.shared.write_metadata.assert_called_once_with(
b"/path/to/thumbnail", metadata
)
mock_stat.assert_called_once_with(syspath(album.artpath))
@patch("beetsplug.thumbnails.os")
@patch("beetsplug.thumbnails.ArtResizer")
@patch("beetsplug.thumbnails.GioURI")
def test_check_local_ok(self, mock_giouri, mock_artresizer, mock_os):
# test local resizing capability
mock_artresizer.shared.local = False
mock_artresizer.shared.can_write_metadata = False
plugin = ThumbnailsPlugin()
assert not plugin._check_local_ok()
# test dirs creation
mock_artresizer.shared.local = True
mock_artresizer.shared.can_write_metadata = True
def exists(path):
if path == syspath(NORMAL_DIR):
return False
if path == syspath(LARGE_DIR):
return True
raise ValueError(f"unexpected path {path!r}")
mock_os.path.exists = exists
plugin = ThumbnailsPlugin()
mock_os.makedirs.assert_called_once_with(syspath(NORMAL_DIR))
assert plugin._check_local_ok()
# test metadata writer function
mock_os.path.exists = lambda _: True
mock_artresizer.shared.local = True
mock_artresizer.shared.can_write_metadata = False
with pytest.raises(RuntimeError):
ThumbnailsPlugin()
mock_artresizer.shared.local = True
mock_artresizer.shared.can_write_metadata = True
assert ThumbnailsPlugin()._check_local_ok()
# test URI getter function
giouri_inst = mock_giouri.return_value
giouri_inst.available = True
assert ThumbnailsPlugin().get_uri == giouri_inst.uri
giouri_inst.available = False
assert ThumbnailsPlugin().get_uri.__self__.__class__ == PathlibURI
@patch("beetsplug.thumbnails.ThumbnailsPlugin._check_local_ok", Mock())
@patch("beetsplug.thumbnails.ArtResizer")
@patch("beets.util.syspath", Mock(side_effect=lambda x: x))
@patch("beetsplug.thumbnails.os")
@patch("beetsplug.thumbnails.shutil")
def test_make_cover_thumbnail(self, mock_shutils, mock_os, mock_artresizer):
thumbnail_dir = os.path.normpath(b"/thumbnail/dir")
md5_file = os.path.join(thumbnail_dir, b"md5")
path_to_art = os.path.normpath(b"/path/to/art")
path_to_resized_art = os.path.normpath(b"/path/to/resized/artwork")
mock_os.path.join = os.path.join # don't mock that function
plugin = ThumbnailsPlugin()
plugin.add_tags = Mock()
album = Mock(artpath=path_to_art)
plugin.thumbnail_file_name = Mock(return_value=b"md5")
mock_os.path.exists.return_value = False
mock_resize = mock_artresizer.shared.resize
mock_resize.return_value = path_to_resized_art
plugin.make_cover_thumbnail(album, 12345, thumbnail_dir)
mock_os.path.exists.assert_called_once_with(syspath(md5_file))
mock_resize.assert_called_once_with(12345, path_to_art, md5_file)
plugin.add_tags.assert_called_once_with(album, path_to_resized_art)
mock_shutils.move.assert_called_once_with(
syspath(path_to_resized_art), syspath(md5_file)
)
# now test with recent thumbnail & with force
mock_os.path.exists.return_value = True
plugin.force = False
mock_resize.reset_mock()
def os_stat(target):
if target == syspath(md5_file):
return Mock(st_mtime=3)
if target == syspath(path_to_art):
return Mock(st_mtime=2)
raise ValueError(f"invalid target {target}")
mock_os.stat.side_effect = os_stat
plugin.make_cover_thumbnail(album, 12345, thumbnail_dir)
assert mock_resize.call_count == 0
# and with force
plugin.config["force"] = True
plugin.make_cover_thumbnail(album, 12345, thumbnail_dir)
mock_resize.assert_called_once_with(12345, path_to_art, md5_file)
@patch("beetsplug.thumbnails.ThumbnailsPlugin._check_local_ok", Mock())
def test_make_dolphin_cover_thumbnail(self):
plugin = ThumbnailsPlugin()
tmp = self.temp_path
album = Mock(
path=os.fsencode(tmp), artpath=os.fsencode(tmp / "cover.jpg")
)
plugin.make_dolphin_cover_thumbnail(album)
filename = tmp / ".directory"
assert filename.read_text() == "[Desktop Entry]\nIcon=./cover.jpg"
# not rewritten when it already exists (yup that's a big limitation)
album.artpath = b"/my/awesome/art.tiff"
plugin.make_dolphin_cover_thumbnail(album)
assert filename.read_text() == "[Desktop Entry]\nIcon=./cover.jpg"
@patch("beetsplug.thumbnails.ThumbnailsPlugin._check_local_ok", Mock())
@patch("beetsplug.thumbnails.ArtResizer")
def test_process_album(self, mock_artresizer):
get_size = mock_artresizer.shared.get_size
plugin = ThumbnailsPlugin()
make_cover = plugin.make_cover_thumbnail = Mock(return_value=True)
make_dolphin = plugin.make_dolphin_cover_thumbnail = Mock()
# no art
album = Mock(artpath=None)
plugin.process_album(album)
assert get_size.call_count == 0
assert make_dolphin.call_count == 0
# cannot get art size
album.artpath = b"/path/to/art"
get_size.return_value = None
plugin.process_album(album)
get_size.assert_called_once_with(b"/path/to/art")
assert make_cover.call_count == 0
# dolphin tests
plugin.config["dolphin"] = False
plugin.process_album(album)
assert make_dolphin.call_count == 0
plugin.config["dolphin"] = True
plugin.process_album(album)
make_dolphin.assert_called_once_with(album)
# small art
get_size.return_value = 200, 200
plugin.process_album(album)
make_cover.assert_called_once_with(album, 128, NORMAL_DIR)
# big art
make_cover.reset_mock()
get_size.return_value = 500, 500
plugin.process_album(album)
make_cover.assert_has_calls(
[call(album, 128, NORMAL_DIR), call(album, 256, LARGE_DIR)],
any_order=True,
)
@patch("beetsplug.thumbnails.ThumbnailsPlugin._check_local_ok", Mock())
def test_invokations(self):
plugin = ThumbnailsPlugin()
plugin.process_album = Mock()
album = Mock()
plugin.process_album.reset_mock()
lib = Mock()
album2 = Mock()
lib.albums.return_value = [album, album2]
plugin.process_query(lib, Mock(), None)
plugin.process_album.assert_has_calls(
[call(album), call(album2)], any_order=True
)
@patch("beetsplug.thumbnails.BaseDirectory")
def test_thumbnail_file_name(self, mock_basedir):
plug = ThumbnailsPlugin()
plug.get_uri = Mock(return_value="file:///my/uri")
assert (
plug.thumbnail_file_name(b"idontcare")
== b"9488f5797fbe12ffb316d607dfd93d04.png"
)
def test_uri(self):
gio = GioURI()
if not gio.available:
self.skipTest("GIO library not found")
import ctypes
with pytest.raises(ctypes.ArgumentError):
gio.uri("/foo")
assert gio.uri(b"/foo") == "file:///foo"
assert gio.uri(b"/foo!") == "file:///foo!"
assert (
gio.uri(b"/music/\xec\x8b\xb8\xec\x9d\xb4")
== "file:///music/%EC%8B%B8%EC%9D%B4"
)
class TestPathlibURI:
"""Test PathlibURI class"""
def test_uri(self):
test_uri = PathlibURI()
# test it won't break if we pass it bytes for a path
test_uri.uri(b"/")