mirror of
https://github.com/beetbox/beets.git
synced 2025-12-06 08:39:17 +01:00
295 lines
11 KiB
Python
295 lines
11 KiB
Python
# -*- coding: utf-8 -*-
|
|
# This file is part of beets.
|
|
# Copyright 2016, 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.
|
|
|
|
from __future__ import division, absolute_import, print_function
|
|
|
|
import os.path
|
|
from mock import Mock, patch, call
|
|
from tempfile import mkdtemp
|
|
from shutil import rmtree
|
|
|
|
from test._common import unittest
|
|
from test.helper import TestHelper
|
|
|
|
from beetsplug.thumbnails import (ThumbnailsPlugin, NORMAL_DIR, LARGE_DIR,
|
|
write_metadata_im, write_metadata_pil,
|
|
PathlibURI, GioURI)
|
|
|
|
|
|
class ThumbnailsTest(unittest.TestCase, TestHelper):
|
|
def setUp(self):
|
|
self.setup_beets()
|
|
|
|
def tearDown(self):
|
|
self.teardown_beets()
|
|
|
|
@patch('beetsplug.thumbnails.util')
|
|
def test_write_metadata_im(self, mock_util):
|
|
metadata = {"a": u"A", "b": u"B"}
|
|
write_metadata_im("foo", metadata)
|
|
try:
|
|
command = u"convert foo -set a A -set b B foo".split(' ')
|
|
mock_util.command_output.assert_called_once_with(command)
|
|
except AssertionError:
|
|
command = u"convert foo -set b B -set a A foo".split(' ')
|
|
mock_util.command_output.assert_called_once_with(command)
|
|
|
|
@patch('beetsplug.thumbnails.ThumbnailsPlugin._check_local_ok')
|
|
@patch('beetsplug.thumbnails.os.stat')
|
|
def test_add_tags(self, mock_stat, _):
|
|
plugin = ThumbnailsPlugin()
|
|
plugin.write_metadata = Mock()
|
|
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": b"COVER_URI",
|
|
"Thumb::MTime": u"12345"}
|
|
plugin.write_metadata.assert_called_once_with(b"/path/to/thumbnail",
|
|
metadata)
|
|
mock_stat.assert_called_once_with(album.artpath)
|
|
|
|
@patch('beetsplug.thumbnails.os')
|
|
@patch('beetsplug.thumbnails.ArtResizer')
|
|
@patch('beetsplug.thumbnails.get_im_version')
|
|
@patch('beetsplug.thumbnails.get_pil_version')
|
|
@patch('beetsplug.thumbnails.GioURI')
|
|
def test_check_local_ok(self, mock_giouri, mock_pil, mock_im,
|
|
mock_artresizer, mock_os):
|
|
# test local resizing capability
|
|
mock_artresizer.shared.local = False
|
|
plugin = ThumbnailsPlugin()
|
|
self.assertFalse(plugin._check_local_ok())
|
|
|
|
# test dirs creation
|
|
mock_artresizer.shared.local = True
|
|
|
|
def exists(path):
|
|
if path == NORMAL_DIR:
|
|
return False
|
|
if path == LARGE_DIR:
|
|
return True
|
|
raise ValueError(u"unexpected path {0!r}".format(path))
|
|
mock_os.path.exists = exists
|
|
plugin = ThumbnailsPlugin()
|
|
mock_os.makedirs.assert_called_once_with(NORMAL_DIR)
|
|
self.assertTrue(plugin._check_local_ok())
|
|
|
|
# test metadata writer function
|
|
mock_os.path.exists = lambda _: True
|
|
mock_pil.return_value = False
|
|
mock_im.return_value = False
|
|
with self.assertRaises(AssertionError):
|
|
ThumbnailsPlugin()
|
|
|
|
mock_pil.return_value = True
|
|
self.assertEqual(ThumbnailsPlugin().write_metadata, write_metadata_pil)
|
|
|
|
mock_im.return_value = True
|
|
self.assertEqual(ThumbnailsPlugin().write_metadata, write_metadata_im)
|
|
|
|
mock_pil.return_value = False
|
|
self.assertEqual(ThumbnailsPlugin().write_metadata, write_metadata_im)
|
|
|
|
self.assertTrue(ThumbnailsPlugin()._check_local_ok())
|
|
|
|
# test URI getter function
|
|
giouri_inst = mock_giouri.return_value
|
|
giouri_inst.available = True
|
|
self.assertEqual(ThumbnailsPlugin().get_uri, giouri_inst.uri)
|
|
|
|
giouri_inst.available = False
|
|
self.assertEqual(ThumbnailsPlugin().get_uri.__self__.__class__,
|
|
PathlibURI)
|
|
|
|
@patch('beetsplug.thumbnails.ThumbnailsPlugin._check_local_ok')
|
|
@patch('beetsplug.thumbnails.ArtResizer')
|
|
@patch('beetsplug.thumbnails.util')
|
|
@patch('beetsplug.thumbnails.os')
|
|
@patch('beetsplug.thumbnails.shutil')
|
|
def test_make_cover_thumbnail(self, mock_shutils, mock_os, mock_util,
|
|
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")
|
|
|
|
mock_os.path.join = os.path.join # don't mock that function
|
|
plugin = ThumbnailsPlugin()
|
|
plugin.add_tags = Mock()
|
|
|
|
album = Mock(artpath=path_to_art)
|
|
mock_util.syspath.side_effect = lambda x: x
|
|
plugin.thumbnail_file_name = Mock(return_value="md5")
|
|
mock_os.path.exists.return_value = False
|
|
|
|
def os_stat(target):
|
|
if target == md5_file:
|
|
return Mock(st_mtime=1)
|
|
elif target == path_to_art:
|
|
return Mock(st_mtime=2)
|
|
else:
|
|
raise ValueError(u"invalid target {0}".format(target))
|
|
mock_os.stat.side_effect = os_stat
|
|
|
|
plugin.make_cover_thumbnail(album, 12345, thumbnail_dir)
|
|
|
|
mock_os.path.exists.assert_called_once_with(md5_file)
|
|
mock_os.stat.has_calls([call(md5_file), call(path_to_art)],
|
|
any_order=True)
|
|
|
|
resize = mock_artresizer.shared.resize
|
|
resize.assert_called_once_with(12345, path_to_art, md5_file)
|
|
plugin.add_tags.assert_called_once_with(album, resize.return_value)
|
|
mock_shutils.move.assert_called_once_with(resize.return_value,
|
|
md5_file)
|
|
|
|
# now test with recent thumbnail & with force
|
|
mock_os.path.exists.return_value = True
|
|
plugin.force = False
|
|
resize.reset_mock()
|
|
|
|
def os_stat(target):
|
|
if target == md5_file:
|
|
return Mock(st_mtime=3)
|
|
elif target == path_to_art:
|
|
return Mock(st_mtime=2)
|
|
else:
|
|
raise ValueError(u"invalid target {0}".format(target))
|
|
mock_os.stat.side_effect = os_stat
|
|
|
|
plugin.make_cover_thumbnail(album, 12345, thumbnail_dir)
|
|
self.assertEqual(resize.call_count, 0)
|
|
|
|
# and with force
|
|
plugin.config['force'] = True
|
|
plugin.make_cover_thumbnail(album, 12345, thumbnail_dir)
|
|
resize.assert_called_once_with(12345, path_to_art, md5_file)
|
|
|
|
@patch('beetsplug.thumbnails.ThumbnailsPlugin._check_local_ok')
|
|
def test_make_dolphin_cover_thumbnail(self, _):
|
|
plugin = ThumbnailsPlugin()
|
|
tmp = mkdtemp()
|
|
album = Mock(path=tmp,
|
|
artpath=os.path.join(tmp, b"cover.jpg"))
|
|
plugin.make_dolphin_cover_thumbnail(album)
|
|
with open(os.path.join(tmp, b".directory"), "rb") as f:
|
|
self.assertEqual(
|
|
f.read().splitlines(),
|
|
[b"[Desktop Entry]", b"Icon=./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)
|
|
with open(os.path.join(tmp, b".directory"), "rb") as f:
|
|
self.assertEqual(
|
|
f.read().splitlines(),
|
|
[b"[Desktop Entry]", b"Icon=./cover.jpg"]
|
|
)
|
|
|
|
rmtree(tmp)
|
|
|
|
@patch('beetsplug.thumbnails.ThumbnailsPlugin._check_local_ok')
|
|
@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)
|
|
self.assertEqual(get_size.call_count, 0)
|
|
self.assertEqual(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")
|
|
self.assertEqual(make_cover.call_count, 0)
|
|
|
|
# dolphin tests
|
|
plugin.config['dolphin'] = False
|
|
plugin.process_album(album)
|
|
self.assertEqual(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.has_calls([call(album, 128, NORMAL_DIR),
|
|
call(album, 256, LARGE_DIR)], any_order=True)
|
|
|
|
@patch('beetsplug.thumbnails.ThumbnailsPlugin._check_local_ok')
|
|
@patch('beetsplug.thumbnails.decargs')
|
|
def test_invokations(self, mock_decargs, _):
|
|
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)
|
|
lib.albums.assert_called_once_with(mock_decargs.return_value)
|
|
plugin.process_album.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=u"file:///my/uri")
|
|
self.assertEqual(plug.thumbnail_file_name("idontcare"),
|
|
b"9488f5797fbe12ffb316d607dfd93d04.png")
|
|
|
|
def test_uri(self):
|
|
gio = GioURI()
|
|
plib = PathlibURI()
|
|
if not gio.available:
|
|
self.skipTest(u"GIO library not found")
|
|
|
|
self.assertEqual(gio.uri(u"/foo"), b"file:///") # silent fail
|
|
self.assertEqual(gio.uri(b"/foo"), b"file:///foo")
|
|
self.assertEqual(gio.uri(b"/foo!"), b"file:///foo!")
|
|
self.assertEqual(plib.uri(b"/foo!"), b"file:///foo%21")
|
|
self.assertEqual(
|
|
gio.uri(b'/music/\xec\x8b\xb8\xec\x9d\xb4'),
|
|
b'file:///music/%EC%8B%B8%EC%9D%B4')
|
|
self.assertEqual(
|
|
plib.uri(b'/music/\xec\x8b\xb8\xec\x9d\xb4'),
|
|
b'file:///music/%EC%8B%B8%EC%9D%B4')
|
|
|
|
|
|
def suite():
|
|
return unittest.TestLoader().loadTestsFromName(__name__)
|
|
|
|
if __name__ == b'__main__':
|
|
unittest.main(defaultTest='suite')
|