Add Id3Encoding enum

This commit is contained in:
Philipp Wolfer
2021-10-04 09:14:09 +02:00
parent b1fc2f204e
commit c059631723
3 changed files with 45 additions and 31 deletions

View File

@@ -33,6 +33,7 @@
from collections import defaultdict
from enum import IntEnum
import re
from urllib.parse import urlparse
@@ -82,12 +83,25 @@ __ID3_IMAGE_TYPE_MAP = dict(__IMAGE_TYPES)
__ID3_REVERSE_IMAGE_TYPE_MAP = dict([(v, k) for k, v in __IMAGE_TYPES])
class Id3Encoding(IntEnum):
LATIN1 = 0
UTF16 = 1
UTF16BE = 2
UTF8 = 3
def from_config(id3v2_encoding):
return {
'utf-8': Id3Encoding.UTF8,
'utf-16': Id3Encoding.UTF16
}.get(id3v2_encoding, Id3Encoding.LATIN1)
def id3text(text, encoding):
"""Returns a string which only contains code points which can
be encododed with the given numeric id3 encoding.
"""
if encoding == 0:
if encoding == Id3Encoding.LATIN1:
return text.encode("latin1", "replace").decode("latin1")
return text
@@ -259,7 +273,7 @@ class ID3File(File):
tags = file.tags or {}
config = get_config()
itunes_compatible = config.setting['itunes_compatible_grouping']
rating_user_email = id3text(config.setting['rating_user_email'], 0)
rating_user_email = id3text(config.setting['rating_user_email'], Id3Encoding.LATIN1)
rating_steps = config.setting['rating_steps']
# upgrade custom 2.3 frames to 2.4
for old, new in self.__upgrade.items():
@@ -391,28 +405,28 @@ class ID3File(File):
if images_to_save:
tags.delall('APIC')
encoding = {'utf-8': 3, 'utf-16': 1}.get(config.setting['id3v2_encoding'], 0)
encoding = Id3Encoding.from_config(config.setting['id3v2_encoding'])
if 'tracknumber' in metadata:
if 'totaltracks' in metadata:
text = '%s/%s' % (metadata['tracknumber'], metadata['totaltracks'])
else:
text = metadata['tracknumber']
tags.add(id3.TRCK(encoding=0, text=id3text(text, 0)))
tags.add(id3.TRCK(encoding=Id3Encoding.LATIN1, text=id3text(text, Id3Encoding.LATIN1)))
if 'discnumber' in metadata:
if 'totaldiscs' in metadata:
text = '%s/%s' % (metadata['discnumber'], metadata['totaldiscs'])
else:
text = metadata['discnumber']
tags.add(id3.TPOS(encoding=0, text=id3text(text, 0)))
tags.add(id3.TPOS(encoding=Id3Encoding.LATIN1, text=id3text(text, Id3Encoding.LATIN1)))
if 'movementnumber' in metadata:
if 'movementtotal' in metadata:
text = '%s/%s' % (metadata['movementnumber'], metadata['movementtotal'])
else:
text = metadata['movementnumber']
tags.add(id3.MVIN(encoding=0, text=id3text(text, 0)))
tags.add(id3.MVIN(encoding=Id3Encoding.LATIN1, text=id3text(text, Id3Encoding.LATIN1)))
# This is necessary because mutagens HashKey for APIC frames only
# includes the FrameID (APIC) and description - it's basically
@@ -427,10 +441,10 @@ class ID3File(File):
else:
desctag = "(%i)" % counters[desc]
counters[desc] += 1
tags.add(id3.APIC(encoding=0,
tags.add(id3.APIC(encoding=Id3Encoding.LATIN1,
mime=image.mimetype,
type=image_type_as_id3_num(image.maintype),
desc=id3text(desctag, 0),
desc=id3text(desctag, Id3Encoding.LATIN1),
data=image.data))
tmcl = mutagen.id3.TMCL(encoding=encoding, people=[])
@@ -457,7 +471,7 @@ class ID3File(File):
(lang, desc) = parse_comment_tag(name)
if desc.lower()[:4] == 'itun':
tags.delall('COMM:' + desc)
tags.add(id3.COMM(encoding=0, desc=desc, lang='eng', text=[v + '\x00' for v in values]))
tags.add(id3.COMM(encoding=Id3Encoding.LATIN1, desc=desc, lang='eng', text=[v + '\x00' for v in values]))
else:
tags.add(id3.COMM(encoding=encoding, desc=desc, lang=lang, text=values))
elif name.startswith('lyrics:') or name == 'lyrics':
@@ -473,7 +487,7 @@ class ID3File(File):
elif name == 'musicbrainz_recordingid':
tags.add(id3.UFID(owner='http://musicbrainz.org', data=bytes(values[0], 'ascii')))
elif name == '~rating':
rating_user_email = id3text(config.setting['rating_user_email'], 0)
rating_user_email = id3text(config.setting['rating_user_email'], Id3Encoding.LATIN1)
# Search for an existing POPM frame to get the current playcount
for frame in tags.values():
if frame.FrameID == 'POPM' and frame.email == rating_user_email:
@@ -597,7 +611,7 @@ class ID3File(File):
tags.delall(real_name)
tags.delall('TXXX:' + self.__rtranslate_freetext[name])
elif real_name == 'POPM':
rating_user_email = id3text(config.setting['rating_user_email'], 0)
rating_user_email = id3text(config.setting['rating_user_email'], Id3Encoding.LATIN1)
for key, frame in list(tags.items()):
if frame.FrameID == 'POPM' and frame.email == rating_user_email:
del tags[key]

View File

@@ -644,11 +644,17 @@ class AIFFTest(CommonId3Tests.Id3TestCase):
class Id3UtilTest(PicardTestCase):
def test_id3encoding_from_config(self):
self.assertEqual(id3.Id3Encoding.LATIN1, id3.Id3Encoding.from_config('iso-8859-1'))
self.assertEqual(id3.Id3Encoding.UTF16, id3.Id3Encoding.from_config('utf-16'))
self.assertEqual(id3.Id3Encoding.UTF8, id3.Id3Encoding.from_config('utf-8'))
def test_id3text(self):
teststring = '日本語testÖäß'
self.assertEqual(id3.id3text(teststring, 0), '???testÖäß')
self.assertEqual(id3.id3text(teststring, 1), teststring)
self.assertEqual(id3.id3text(teststring, 3), teststring)
self.assertEqual(id3.id3text(teststring, id3.Id3Encoding.LATIN1), '???testÖäß')
self.assertEqual(id3.id3text(teststring, id3.Id3Encoding.UTF16), teststring)
self.assertEqual(id3.id3text(teststring, id3.Id3Encoding.UTF16BE), teststring)
self.assertEqual(id3.id3text(teststring, id3.Id3Encoding.UTF8), teststring)
def test_image_type_from_id3_num(self):
self.assertEqual(id3.image_type_from_id3_num(0), 'other')

View File

@@ -6,7 +6,7 @@
# Copyright (C) 2013, 2018 Laurent Monin
# Copyright (C) 2016 Christoph Reiter
# Copyright (C) 2018 Wieland Hoffmann
# Copyright (C) 2019 Philipp Wolfer
# Copyright (C) 2019, 2021 Philipp Wolfer
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@@ -27,23 +27,17 @@ from mutagen import id3
from test.picardtestcase import PicardTestCase
from picard.formats.id3 import id3text
from picard.formats.id3 import Id3Encoding
from picard.formats.mutagenext import compatid3
class UpdateToV23Test(PicardTestCase):
def test_id3text(self):
self.assertEqual(id3text("\u1234", 0), "?")
self.assertEqual(id3text("\u1234", 1), "\u1234")
self.assertEqual(id3text("\u1234", 2), "\u1234")
self.assertEqual(id3text("\u1234", 3), "\u1234")
def test_keep_some_v24_tag(self):
tags = compatid3.CompatID3()
tags.add(id3.TSOP(encoding=0, text=["foo"]))
tags.add(id3.TSOA(encoding=0, text=["foo"]))
tags.add(id3.TSOT(encoding=0, text=["foo"]))
tags.add(id3.TSOP(encoding=Id3Encoding.LATIN1, text=["foo"]))
tags.add(id3.TSOA(encoding=Id3Encoding.LATIN1, text=["foo"]))
tags.add(id3.TSOT(encoding=Id3Encoding.LATIN1, text=["foo"]))
tags.update_to_v23()
self.assertEqual(tags["TSOP"].text, ["foo"])
self.assertEqual(tags["TSOA"].text, ["foo"])
@@ -51,7 +45,7 @@ class UpdateToV23Test(PicardTestCase):
def test_tdrc(self):
tags = compatid3.CompatID3()
tags.add(id3.TDRC(encoding=1, text="2003-04-05 12:03"))
tags.add(id3.TDRC(encoding=Id3Encoding.UTF16, text="2003-04-05 12:03"))
tags.update_to_v23()
self.assertEqual(tags["TYER"].text, ["2003"])
self.assertEqual(tags["TDAT"].text, ["0504"])
@@ -59,30 +53,30 @@ class UpdateToV23Test(PicardTestCase):
def test_tdor(self):
tags = compatid3.CompatID3()
tags.add(id3.TDOR(encoding=1, text="2003-04-05 12:03"))
tags.add(id3.TDOR(encoding=Id3Encoding.UTF16, text="2003-04-05 12:03"))
tags.update_to_v23()
self.assertEqual(tags["TORY"].text, ["2003"])
def test_genre_from_v24_1(self):
tags = compatid3.CompatID3()
tags.add(id3.TCON(encoding=1, text=["4", "Rock"]))
tags.add(id3.TCON(encoding=Id3Encoding.UTF16, text=["4", "Rock"]))
tags.update_to_v23()
self.assertEqual(tags["TCON"].text, ["Disco", "Rock"])
def test_genre_from_v24_2(self):
tags = compatid3.CompatID3()
tags.add(id3.TCON(encoding=1, text=["RX", "3", "CR"]))
tags.add(id3.TCON(encoding=Id3Encoding.UTF16, text=["RX", "3", "CR"]))
tags.update_to_v23()
self.assertEqual(tags["TCON"].text, ["Remix", "Dance", "Cover"])
def test_genre_from_v23_1(self):
tags = compatid3.CompatID3()
tags.add(id3.TCON(encoding=1, text=["(4)Rock"]))
tags.add(id3.TCON(encoding=Id3Encoding.UTF16, text=["(4)Rock"]))
tags.update_to_v23()
self.assertEqual(tags["TCON"].text, ["Disco", "Rock"])
def test_genre_from_v23_2(self):
tags = compatid3.CompatID3()
tags.add(id3.TCON(encoding=1, text=["(RX)(3)(CR)"]))
tags.add(id3.TCON(encoding=Id3Encoding.UTF16, text=["(RX)(3)(CR)"]))
tags.update_to_v23()
self.assertEqual(tags["TCON"].text, ["Remix", "Dance", "Cover"])