mirror of
https://github.com/fergalmoran/picard.git
synced 2025-12-22 09:18:18 +00:00
398 lines
18 KiB
Python
398 lines
18 KiB
Python
# -*- coding: utf-8 -*-
|
|
#
|
|
# Picard, the next-generation MusicBrainz tagger
|
|
#
|
|
# Copyright (C) 2019, 2021-2024 Philipp Wolfer
|
|
# Copyright (C) 2019-2021 Laurent Monin
|
|
#
|
|
# This program is free software; you can redistribute it and/or
|
|
# modify it under the terms of the GNU General Public License
|
|
# as published by the Free Software Foundation; either version 2
|
|
# of the License, or (at your option) any later version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with this program; if not, write to the Free Software
|
|
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
|
|
|
|
from collections import Counter
|
|
import os.path
|
|
from tempfile import TemporaryDirectory
|
|
import unittest
|
|
|
|
from test.picardtestcase import (
|
|
PicardTestCase,
|
|
create_fake_png,
|
|
)
|
|
|
|
from picard.const.defaults import DEFAULT_COVER_IMAGE_FILENAME
|
|
from picard.const.sys import IS_WIN
|
|
from picard.coverart.image import (
|
|
CoverArtImage,
|
|
LocalFileCoverArtImage,
|
|
TagCoverArtImage,
|
|
)
|
|
from picard.coverart.utils import (
|
|
Id3ImageType,
|
|
types_from_id3,
|
|
)
|
|
from picard.file import File
|
|
from picard.metadata import Metadata
|
|
from picard.util import encode_filename
|
|
from picard.util.filenaming import WinPathTooLong
|
|
|
|
|
|
def create_image(extra_data, types=None, support_types=False,
|
|
support_multi_types=False, comment=None, id3_type=None):
|
|
return CoverArtImage(
|
|
data=create_fake_png(extra_data),
|
|
types=types,
|
|
comment=comment,
|
|
support_types=support_types,
|
|
support_multi_types=support_multi_types,
|
|
id3_type=id3_type,
|
|
)
|
|
|
|
|
|
class TagCoverArtImageTest(PicardTestCase):
|
|
def test_repr_str_1(self):
|
|
image_type = Id3ImageType.COVER_FRONT
|
|
image = TagCoverArtImage(
|
|
file='testfilename',
|
|
tag='tag',
|
|
types=types_from_id3(image_type),
|
|
comment='description',
|
|
support_types=True,
|
|
data=None,
|
|
id3_type=image_type,
|
|
is_front=True,
|
|
)
|
|
expected = "TagCoverArtImage('testfilename', tag='tag', types=['front'], support_types=True, support_multi_types=False, is_front=True, comment='description')"
|
|
self.assertEqual(expected, repr(image))
|
|
expected = "TagCoverArtImage from 'testfilename' of type front and comment 'description'"
|
|
self.assertEqual(expected, str(image))
|
|
|
|
|
|
class CoverArtImageTest(PicardTestCase):
|
|
def test_repr_str_1(self):
|
|
image = CoverArtImage(
|
|
url='url', types=["booklet", "front"],
|
|
comment='comment', support_types=True,
|
|
support_multi_types=True
|
|
)
|
|
expected = "CoverArtImage(url='url', types=['booklet', 'front'], support_types=True, support_multi_types=True, comment='comment')"
|
|
self.assertEqual(expected, repr(image))
|
|
expected = "CoverArtImage from url of type booklet,front and comment 'comment'"
|
|
self.assertEqual(expected, str(image))
|
|
|
|
def test_repr_str_2(self):
|
|
image = CoverArtImage()
|
|
expected = "CoverArtImage(support_types=False, support_multi_types=False)"
|
|
self.assertEqual(expected, repr(image))
|
|
expected = "CoverArtImage"
|
|
self.assertEqual(expected, str(image))
|
|
|
|
def test_is_front_image_no_types(self):
|
|
image = create_image(b'a')
|
|
self.assertTrue(image.is_front_image())
|
|
self.assertEqual(Id3ImageType.COVER_FRONT, image.id3_type)
|
|
image.can_be_saved_to_metadata = False
|
|
self.assertFalse(image.is_front_image())
|
|
|
|
def test_is_front_image_types_supported(self):
|
|
image = create_image(b'a', types=["booklet", "front"], support_types=True)
|
|
self.assertTrue(image.is_front_image())
|
|
image.is_front = False
|
|
self.assertFalse(image.is_front_image())
|
|
image = create_image(b'a', support_types=True)
|
|
self.assertFalse(image.is_front_image())
|
|
|
|
def test_is_front_image_no_types_supported(self):
|
|
image = create_image(b'a', types=["back"], support_types=False)
|
|
self.assertTrue(image.is_front_image())
|
|
self.assertEqual(Id3ImageType.COVER_FRONT, image.id3_type)
|
|
|
|
def test_maintype(self):
|
|
self.assertEqual("front", create_image(b'a').maintype)
|
|
self.assertEqual("front", create_image(b'a', support_types=True).maintype)
|
|
self.assertEqual("front", create_image(b'a', types=["back", "front"], support_types=True).maintype)
|
|
self.assertEqual("back", create_image(b'a', types=["back", "medium"], support_types=True).maintype)
|
|
self.assertEqual("front", create_image(b'a', types=["back", "medium"], support_types=False).maintype)
|
|
|
|
def test_normalized_types(self):
|
|
self.assertEqual(("front",), create_image(b'a').normalized_types())
|
|
self.assertEqual(("-",), create_image(b'a', support_types=True).normalized_types())
|
|
self.assertEqual(("front",), create_image(b'a', types=["front"], support_types=True).normalized_types())
|
|
self.assertEqual(("front", "back",), create_image(b'a', types=["back", "front"], support_types=True).normalized_types())
|
|
self.assertEqual(("back", "medium",), create_image(b'a', types=["medium", "back"], support_types=True).normalized_types())
|
|
self.assertEqual(("front",), create_image(b'a', types=["back", "medium"], support_types=False).normalized_types())
|
|
|
|
def test_id3_type_derived(self):
|
|
self.assertEqual(Id3ImageType.COVER_FRONT, create_image(b'a').id3_type)
|
|
self.assertEqual(Id3ImageType.COVER_FRONT, create_image(b'a', support_types=True).id3_type)
|
|
self.assertEqual(Id3ImageType.COVER_FRONT, create_image(b'a', types=["back", "front"], support_types=True).id3_type)
|
|
self.assertEqual(Id3ImageType.COVER_BACK, create_image(b'a', types=["back", "medium"], support_types=True).id3_type)
|
|
self.assertEqual(Id3ImageType.COVER_FRONT, create_image(b'a', types=["back", "medium"], support_types=False).id3_type)
|
|
self.assertEqual(Id3ImageType.MEDIA, create_image(b'a', types=["medium"], support_types=True).id3_type)
|
|
self.assertEqual(Id3ImageType.LEAFLET_PAGE, create_image(b'a', types=["booklet"], support_types=True).id3_type)
|
|
self.assertEqual(Id3ImageType.OTHER, create_image(b'a', types=["spine"], support_types=True).id3_type)
|
|
self.assertEqual(Id3ImageType.OTHER, create_image(b'a', types=["sticker"], support_types=True).id3_type)
|
|
|
|
def test_id3_type_explicit(self):
|
|
image = create_image(b'a', types=["back"], support_types=True)
|
|
for id3_type in Id3ImageType:
|
|
image.id3_type = id3_type
|
|
self.assertEqual(id3_type, image.id3_type)
|
|
image.id3_type = None
|
|
self.assertEqual(Id3ImageType.COVER_BACK, image.id3_type)
|
|
|
|
def test_id3_type_value_error(self):
|
|
image = create_image(b'a')
|
|
for invalid_value in ('foo', 200, -1):
|
|
with self.assertRaises(ValueError):
|
|
image.id3_type = invalid_value
|
|
|
|
def test_init_invalid_id3_type(self):
|
|
cases = (
|
|
(CoverArtImage, []),
|
|
(TagCoverArtImage, [File('test.mp3')]),
|
|
)
|
|
for image_class, args in cases:
|
|
image = image_class(*args, id3_type=255)
|
|
self.assertEqual(image.id3_type, Id3ImageType.OTHER)
|
|
|
|
def test_compare_without_type(self):
|
|
image1 = create_image(b'a', types=["front"])
|
|
image2 = create_image(b'a', types=["back"])
|
|
image3 = create_image(b'a', types=["back"], support_types=True)
|
|
image4 = create_image(b'b', types=["front"])
|
|
|
|
self.assertEqual(image1, image2)
|
|
self.assertEqual(image1, image3)
|
|
self.assertNotEqual(image1, image4)
|
|
|
|
def test_compare_with_primary_type(self):
|
|
image1 = create_image(b'a', types=["front"], support_types=True)
|
|
image2 = create_image(b'a', types=["front", "booklet"], support_types=True, support_multi_types=True)
|
|
image3 = create_image(b'a', types=["back"], support_types=True)
|
|
image4 = create_image(b'b', types=["front"], support_types=True)
|
|
image5 = create_image(b'a', types=[], support_types=True)
|
|
image6 = create_image(b'a', types=[], support_types=True)
|
|
|
|
self.assertEqual(image1, image2)
|
|
self.assertNotEqual(image1, image3)
|
|
self.assertNotEqual(image1, image4)
|
|
self.assertNotEqual(image3, image5)
|
|
self.assertEqual(image5, image6)
|
|
|
|
def test_compare_with_multiple_types(self):
|
|
image1 = create_image(b'a', types=["front"], support_types=True, support_multi_types=True)
|
|
image2 = create_image(b'a', types=["front", "booklet"], support_types=True, support_multi_types=True)
|
|
image3 = create_image(b'a', types=["front", "booklet"], support_types=True, support_multi_types=True)
|
|
image4 = create_image(b'b', types=["front", "booklet"], support_types=True, support_multi_types=True)
|
|
|
|
self.assertNotEqual(image1, image2)
|
|
self.assertEqual(image2, image3)
|
|
self.assertNotEqual(image2, image4)
|
|
|
|
def test_lt_type1(self):
|
|
image1 = create_image(b'a', types=["front"], support_types=True, support_multi_types=True)
|
|
image2 = create_image(b'b', types=["booklet"], support_types=True, support_multi_types=True)
|
|
self.assertLess(image1, image2)
|
|
|
|
def test_lt_type2(self):
|
|
image1 = create_image(b'a', types=["front"], support_types=True, support_multi_types=True)
|
|
image2 = create_image(b'b', types=["booklet", "front"], support_types=True, support_multi_types=True)
|
|
self.assertLess(image1, image2)
|
|
|
|
def test_lt_type3(self):
|
|
image1 = create_image(b'a', types=["back"], support_types=True, support_multi_types=True)
|
|
image2 = create_image(b'b', types=["-"], support_types=True, support_multi_types=True)
|
|
self.assertLess(image1, image2)
|
|
|
|
def test_lt_comment1(self):
|
|
image1 = create_image(b'a', types=["front"], support_types=True, support_multi_types=True)
|
|
image2 = create_image(b'b', types=["front"], support_types=True, support_multi_types=True, comment='b')
|
|
self.assertLess(image1, image2)
|
|
|
|
def test_lt_comment2(self):
|
|
image1 = create_image(b'a', types=["front"], support_types=True, support_multi_types=True, comment='a')
|
|
image2 = create_image(b'b', types=["front"], support_types=True, support_multi_types=True, comment='b')
|
|
self.assertLess(image1, image2)
|
|
|
|
def test_lt_comment3(self):
|
|
image1 = create_image(b'b', types=["back"], support_types=False, comment='a') # not supporting types = front
|
|
image2 = create_image(b'a', types=["front"], support_types=True, support_multi_types=True, comment='b')
|
|
self.assertLess(image1, image2)
|
|
|
|
def test_lt_datahash(self):
|
|
image1 = create_image(b'zz', types=["front"], support_types=True, support_multi_types=True, comment='a')
|
|
image2 = create_image(b'xx', types=["front"], support_types=True, support_multi_types=True, comment='a')
|
|
self.assertLess(image1, image2)
|
|
|
|
def test_sorted_images(self):
|
|
image1 = create_image(b'a', types=["front"], support_types=True, support_multi_types=True)
|
|
image2 = create_image(b'a', types=["booklet"], support_types=True, support_multi_types=True)
|
|
image3 = create_image(b'a', types=["front", "booklet"], support_types=True, support_multi_types=True, comment='a')
|
|
image4 = create_image(b'b', types=["front", "booklet"], support_types=True, support_multi_types=True, comment='b')
|
|
|
|
result = sorted([image4, image3, image2, image1])
|
|
self.maxDiff = None
|
|
self.assertEqual(result, [image1, image3, image4, image2])
|
|
|
|
def test_set_data(self):
|
|
imgdata = create_fake_png(b'a')
|
|
imgdata2 = create_fake_png(b'xxx')
|
|
# set data once
|
|
coverartimage = CoverArtImage(data=imgdata2)
|
|
tmp_file = coverartimage.tempfile_filename
|
|
filesize = os.path.getsize(tmp_file)
|
|
# ensure file was written, and check its length
|
|
self.assertEqual(filesize, len(imgdata2))
|
|
self.assertEqual(coverartimage.data, imgdata2)
|
|
|
|
# set data again, with another payload
|
|
coverartimage.set_tags_data(imgdata)
|
|
|
|
tmp_file = coverartimage.tempfile_filename
|
|
filesize = os.path.getsize(tmp_file)
|
|
# check file length again
|
|
self.assertEqual(filesize, len(imgdata))
|
|
self.assertEqual(coverartimage.data, imgdata)
|
|
|
|
def test_save(self):
|
|
self.set_config_values({
|
|
'image_type_as_filename': True,
|
|
'windows_compatibility': True,
|
|
'win_compat_replacements': {},
|
|
'windows_long_paths': False,
|
|
'replace_spaces_with_underscores': False,
|
|
'replace_dir_separator': '_',
|
|
'enabled_plugins': [],
|
|
'ascii_filenames': False,
|
|
'save_images_overwrite': False,
|
|
})
|
|
metadata = Metadata()
|
|
counters = Counter()
|
|
with TemporaryDirectory() as d:
|
|
image1 = create_image(b'a', types=['back'], support_types=True)
|
|
expected_filename = os.path.join(d, 'back.png')
|
|
counter_filename = encode_filename(os.path.join(d, 'back'))
|
|
image1.save(d, metadata, counters)
|
|
self.assertTrue(os.path.exists(expected_filename))
|
|
self.assertEqual(len(image1.data), os.path.getsize(expected_filename))
|
|
self.assertEqual(1, counters[counter_filename])
|
|
image2 = create_image(b'bb', types=['back'], support_types=True)
|
|
image2.save(d, metadata, counters)
|
|
expected_filename_2 = os.path.join(d, 'back (1).png')
|
|
self.assertTrue(os.path.exists(expected_filename_2))
|
|
self.assertEqual(len(image2.data), os.path.getsize(expected_filename_2))
|
|
self.assertEqual(2, counters[counter_filename])
|
|
|
|
|
|
class CoverArtImageMakeFilenameTest(PicardTestCase):
|
|
def setUp(self):
|
|
super().setUp()
|
|
self.image = create_image(b'a', types=['back'], support_types=True)
|
|
self.metadata = Metadata()
|
|
self.set_config_values({
|
|
'windows_compatibility': False,
|
|
'win_compat_replacements': {},
|
|
'enabled_plugins': [],
|
|
'ascii_filenames': False,
|
|
'replace_spaces_with_underscores': False,
|
|
'replace_dir_separator': '_',
|
|
})
|
|
|
|
def compare_paths(self, path1, path2):
|
|
self.assertEqual(
|
|
encode_filename(os.path.normpath(path1)),
|
|
encode_filename(os.path.normpath(path2)),
|
|
)
|
|
|
|
def test_make_image_filename(self):
|
|
filename = self.image._make_image_filename('AlbumArt', '/music/albumart',
|
|
self.metadata, win_compat=False, win_shorten_path=False)
|
|
self.compare_paths('/music/albumart/AlbumArt', filename)
|
|
|
|
def test_make_image_filename_default(self):
|
|
filename = self.image._make_image_filename('$noop()', '/music/albumart',
|
|
self.metadata, win_compat=False, win_shorten_path=False)
|
|
self.compare_paths(
|
|
os.path.join('/music/albumart/', DEFAULT_COVER_IMAGE_FILENAME), filename)
|
|
|
|
def test_make_image_filename_relative_path(self):
|
|
self.metadata['album'] = 'TheAlbum'
|
|
filename = self.image._make_image_filename("../covers/%album%", "/music/album",
|
|
self.metadata, win_compat=False, win_shorten_path=False)
|
|
self.compare_paths('/music/covers/TheAlbum', filename)
|
|
|
|
def test_make_image_filename_absolute_path(self):
|
|
filename = self.image._make_image_filename('/foo/bar/AlbumArt', '/music/albumart',
|
|
self.metadata, win_compat=False, win_shorten_path=False)
|
|
self.compare_paths('/foo/bar/AlbumArt', filename)
|
|
|
|
@unittest.skipUnless(IS_WIN, "windows test")
|
|
def test_make_image_filename_absolute_path_no_common_base(self):
|
|
filename = self.image._make_image_filename('D:/foo/AlbumArt', 'C:/music',
|
|
self.metadata, win_compat=False, win_shorten_path=False)
|
|
self.compare_paths('D:\\foo\\AlbumArt', filename)
|
|
|
|
def test_make_image_filename_script(self):
|
|
cover_script = '%album%-$if($eq(%coverart_maintype%,front),cover,%coverart_maintype%)'
|
|
self.metadata['album'] = 'TheAlbum'
|
|
filename = self.image._make_image_filename(cover_script, "/music/",
|
|
self.metadata, win_compat=False, win_shorten_path=False)
|
|
self.compare_paths('/music/TheAlbum-back', filename)
|
|
|
|
def test_make_image_filename_save_path(self):
|
|
self.set_config_values({
|
|
'windows_compatibility': True,
|
|
})
|
|
filename = self.image._make_image_filename(".co:ver", "/music/albumart",
|
|
self.metadata, win_compat=True, win_shorten_path=False)
|
|
self.compare_paths('/music/albumart/_co_ver', filename)
|
|
|
|
def test_make_image_filename_win_shorten_path(self):
|
|
requested_path = "/" + 300 * "a" + "/cover"
|
|
expected_path = "/" + 226 * "a" + "/cover"
|
|
filename = self.image._make_image_filename(requested_path, "/music/albumart",
|
|
self.metadata, win_compat=False, win_shorten_path=True)
|
|
self.compare_paths(expected_path, filename)
|
|
|
|
def test_make_image_filename_win_shorten_path_too_long_base_path(self):
|
|
base_path = '/' + 244*'a'
|
|
with self.assertRaises(WinPathTooLong):
|
|
self.image._make_image_filename("cover", base_path,
|
|
self.metadata, win_compat=False, win_shorten_path=True)
|
|
|
|
|
|
class LocalFileCoverArtImageTest(PicardTestCase):
|
|
def test_set_file_url(self):
|
|
path = '/some/path/image.jpeg'
|
|
image = LocalFileCoverArtImage(path)
|
|
self.assertEqual(image.url.toString(), 'file://' + path)
|
|
|
|
def test_support_types(self):
|
|
path = '/some/path/image.jpeg'
|
|
image = LocalFileCoverArtImage(path)
|
|
self.assertFalse(image.support_types)
|
|
self.assertFalse(image.support_multi_types)
|
|
image = LocalFileCoverArtImage(path, support_types=True)
|
|
self.assertTrue(image.support_types)
|
|
self.assertFalse(image.support_multi_types)
|
|
image = LocalFileCoverArtImage(path, support_multi_types=True)
|
|
self.assertFalse(image.support_types)
|
|
self.assertTrue(image.support_multi_types)
|
|
|
|
@unittest.skipUnless(IS_WIN, "windows test")
|
|
def test_windows_path(self):
|
|
path = 'C:\\Music\\somefile.mp3'
|
|
image = LocalFileCoverArtImage(path)
|
|
self.assertEqual(image.url.toLocalFile(), 'C:/Music/somefile.mp3')
|