Skip to content

Commit

Permalink
Rewrite audio.py to jive with image.py (#91886)
Browse files Browse the repository at this point in the history
Similar to the rewrite of email/mime/image.py and associated test after the
deprecation of imghdr.py, thisrewrites email/mime/audio.py and associated
tests after the deprecation of sndhdr.py.

Closes #91885
  • Loading branch information
warsaw authored Apr 24, 2022
1 parent e93d2fb commit 4403320
Show file tree
Hide file tree
Showing 6 changed files with 78 additions and 64 deletions.
108 changes: 55 additions & 53 deletions Lib/email/mime/audio.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,58 +11,6 @@
from email.mime.nonmultipart import MIMENonMultipart


_tests = []

def _test_aifc_aiff(h, f):
if not h.startswith(b'FORM'):
return None
if h[8:12] in {b'AIFC', b'AIFF'}:
return 'x-aiff'
else:
return None

_tests.append(_test_aifc_aiff)


def _test_au(h, f):
if h.startswith(b'.snd'):
return 'basic'
else:
return None

_tests.append(_test_au)


def _test_wav(h, f):
import wave
# 'RIFF' <len> 'WAVE' 'fmt ' <len>
if not h.startswith(b'RIFF') or h[8:12] != b'WAVE' or h[12:16] != b'fmt ':
return None
else:
return "x-wav"

_tests.append(_test_wav)


# There are others in sndhdr that don't have MIME types. :(
# Additional ones to be added to sndhdr? midi, mp3, realaudio, wma??
def _whatsnd(data):
"""Try to identify a sound file type.
sndhdr.what() has a pretty cruddy interface, unfortunately. This is why
we re-do it here. It would be easier to reverse engineer the Unix 'file'
command and use the standard 'magic' file, as shipped with a modern Unix.
"""
hdr = data[:512]
fakefile = BytesIO(hdr)
for testfn in _tests:
res = testfn(hdr, fakefile)
if res is not None:
return res
else:
return None


class MIMEAudio(MIMENonMultipart):
"""Class for generating audio/* MIME documents."""

Expand All @@ -89,10 +37,64 @@ def __init__(self, _audiodata, _subtype=None,
header.
"""
if _subtype is None:
_subtype = _whatsnd(_audiodata)
_subtype = _what(_audiodata)
if _subtype is None:
raise TypeError('Could not find audio MIME subtype')
MIMENonMultipart.__init__(self, 'audio', _subtype, policy=policy,
**_params)
self.set_payload(_audiodata)
_encoder(self)


_rules = []


# Originally from the sndhdr module.
#
# There are others in sndhdr that don't have MIME types. :(
# Additional ones to be added to sndhdr? midi, mp3, realaudio, wma??
def _what(data):
# Try to identify a sound file type.
#
# sndhdr.what() had a pretty cruddy interface, unfortunately. This is why
# we re-do it here. It would be easier to reverse engineer the Unix 'file'
# command and use the standard 'magic' file, as shipped with a modern Unix.
hdr = data[:512]
fakefile = BytesIO(hdr)
for testfn in _rules:
if res := testfn(hdr, fakefile):
return res
else:
return None


def rule(rulefunc):
_rules.append(rulefunc)
return rulefunc


@rule
def _aiff(h, f):
if not h.startswith(b'FORM'):
return None
if h[8:12] in {b'AIFC', b'AIFF'}:
return 'x-aiff'
else:
return None


@rule
def _au(h, f):
if h.startswith(b'.snd'):
return 'basic'
else:
return None


@rule
def _wav(h, f):
# 'RIFF' <len> 'WAVE' 'fmt ' <len>
if not h.startswith(b'RIFF') or h[8:12] != b'WAVE' or h[12:16] != b'fmt ':
return None
else:
return "x-wav"
Binary file added Lib/test/test_email/data/sndhdr.aifc
Binary file not shown.
Binary file added Lib/test/test_email/data/sndhdr.aiff
Binary file not shown.
File renamed without changes.
Binary file added Lib/test/test_email/data/sndhdr.wav
Binary file not shown.
34 changes: 23 additions & 11 deletions Lib/test/test_email/test_email.py
Original file line number Diff line number Diff line change
Expand Up @@ -1515,37 +1515,49 @@ def test_multipart_with_bad_bytes_in_cte(self):

# Test the basic MIMEAudio class
class TestMIMEAudio(unittest.TestCase):
def setUp(self):
with openfile('audiotest.au', 'rb') as fp:
def _make_audio(self, ext):
with openfile(f'sndhdr.{ext}', 'rb') as fp:
self._audiodata = fp.read()
self._au = MIMEAudio(self._audiodata)

def test_guess_minor_type(self):
self.assertEqual(self._au.get_content_type(), 'audio/basic')
for ext, subtype in {
'aifc': 'x-aiff',
'aiff': 'x-aiff',
'wav': 'x-wav',
'au': 'basic',
}.items():
self._make_audio(ext)
subtype = ext if subtype is None else subtype
self.assertEqual(self._au.get_content_type(), f'audio/{subtype}')

def test_encoding(self):
self._make_audio('au')
payload = self._au.get_payload()
self.assertEqual(base64.decodebytes(bytes(payload, 'ascii')),
self._audiodata)
self._audiodata)

def test_checkSetMinor(self):
self._make_audio('au')
au = MIMEAudio(self._audiodata, 'fish')
self.assertEqual(au.get_content_type(), 'audio/fish')

def test_add_header(self):
self._make_audio('au')
eq = self.assertEqual
self._au.add_header('Content-Disposition', 'attachment',
filename='audiotest.au')
filename='sndhdr.au')
eq(self._au['content-disposition'],
'attachment; filename="audiotest.au"')
'attachment; filename="sndhdr.au"')
eq(self._au.get_params(header='content-disposition'),
[('attachment', ''), ('filename', 'audiotest.au')])
[('attachment', ''), ('filename', 'sndhdr.au')])
eq(self._au.get_param('filename', header='content-disposition'),
'audiotest.au')
'sndhdr.au')
missing = []
eq(self._au.get_param('attachment', header='content-disposition'), '')
self.assertIs(self._au.get_param('foo', failobj=missing,
header='content-disposition'), missing)
self.assertIs(self._au.get_param(
'foo', failobj=missing,
header='content-disposition'), missing)
# Try some missing stuff
self.assertIs(self._au.get_param('foobar', missing), missing)
self.assertIs(self._au.get_param('attachment', missing,
Expand Down Expand Up @@ -3462,7 +3474,7 @@ def test_BytesGenerator_linend_with_non_ascii(self):
self.assertEqual(s.getvalue(), msgtxt)

def test_mime_classes_policy_argument(self):
with openfile('audiotest.au', 'rb') as fp:
with openfile('sndhdr.au', 'rb') as fp:
audiodata = fp.read()
with openfile('python.gif', 'rb') as fp:
bindata = fp.read()
Expand Down

0 comments on commit 4403320

Please sign in to comment.