2018-10-11 19:52:47 +02:00
|
|
|
import mimetypes
|
|
|
|
import os
|
2018-03-25 16:17:41 +02:00
|
|
|
import shutil
|
2018-10-11 19:52:47 +02:00
|
|
|
import tempfile
|
2023-01-28 16:57:20 +01:00
|
|
|
from typing import Union, Dict
|
2018-03-25 16:17:41 +02:00
|
|
|
|
|
|
|
import mutagen
|
|
|
|
|
2020-01-01 17:34:42 +01:00
|
|
|
from . import abstract, parser_factory, video
|
2018-03-25 16:17:41 +02:00
|
|
|
|
2018-04-04 23:21:48 +02:00
|
|
|
|
2018-03-25 16:17:41 +02:00
|
|
|
class MutagenParser(abstract.AbstractParser):
|
2018-06-22 21:16:55 +02:00
|
|
|
def __init__(self, filename):
|
|
|
|
super().__init__(filename)
|
|
|
|
try:
|
2021-12-13 19:00:41 +01:00
|
|
|
if mutagen.File(self.filename) is None:
|
|
|
|
raise ValueError
|
2018-07-02 00:22:05 +02:00
|
|
|
except mutagen.MutagenError:
|
2018-06-22 21:16:55 +02:00
|
|
|
raise ValueError
|
|
|
|
|
2023-01-28 16:57:20 +01:00
|
|
|
def get_meta(self) -> Dict[str, Union[str, Dict]]:
|
2018-03-25 16:17:41 +02:00
|
|
|
f = mutagen.File(self.filename)
|
|
|
|
if f.tags:
|
2023-01-28 16:57:20 +01:00
|
|
|
return {k: ', '.join(map(str, v)) for k, v in f.tags.items()}
|
2018-03-25 16:17:41 +02:00
|
|
|
return {}
|
|
|
|
|
2018-10-12 11:58:01 +02:00
|
|
|
def remove_all(self) -> bool:
|
2018-03-25 16:17:41 +02:00
|
|
|
shutil.copy(self.filename, self.output_filename)
|
|
|
|
f = mutagen.File(self.output_filename)
|
2021-12-19 22:33:28 +01:00
|
|
|
try:
|
|
|
|
f.delete()
|
|
|
|
f.save()
|
|
|
|
except mutagen.MutagenError:
|
|
|
|
raise ValueError
|
2018-03-25 16:17:41 +02:00
|
|
|
return True
|
|
|
|
|
2018-04-04 23:21:48 +02:00
|
|
|
|
2018-03-25 16:17:41 +02:00
|
|
|
class MP3Parser(MutagenParser):
|
|
|
|
mimetypes = {'audio/mpeg', }
|
|
|
|
|
2023-01-28 16:57:20 +01:00
|
|
|
def get_meta(self) -> Dict[str, Union[str, Dict]]:
|
2023-05-03 22:28:02 +02:00
|
|
|
metadata: Dict[str, Union[str, Dict]] = dict()
|
2018-04-04 21:59:46 +02:00
|
|
|
meta = mutagen.File(self.filename).tags
|
2020-07-22 15:47:35 +02:00
|
|
|
if not meta:
|
|
|
|
return metadata
|
2018-03-25 16:31:21 +02:00
|
|
|
for key in meta:
|
2021-12-18 19:43:21 +01:00
|
|
|
if isinstance(key, tuple):
|
|
|
|
metadata[key[0]] = key[1]
|
|
|
|
continue
|
2019-03-23 00:41:23 +01:00
|
|
|
if not hasattr(meta[key], 'text'): # pragma: no cover
|
2019-03-22 16:33:59 +01:00
|
|
|
continue
|
2018-04-04 21:59:46 +02:00
|
|
|
metadata[key.rstrip(' \t\r\n\0')] = ', '.join(map(str, meta[key].text))
|
2018-03-25 16:17:41 +02:00
|
|
|
return metadata
|
|
|
|
|
2018-04-04 23:21:48 +02:00
|
|
|
|
2018-03-25 16:17:41 +02:00
|
|
|
class OGGParser(MutagenParser):
|
|
|
|
mimetypes = {'audio/ogg', }
|
2018-03-25 16:20:45 +02:00
|
|
|
|
2018-04-04 23:21:48 +02:00
|
|
|
|
2018-03-25 16:20:45 +02:00
|
|
|
class FLACParser(MutagenParser):
|
2018-07-02 00:22:05 +02:00
|
|
|
mimetypes = {'audio/flac', 'audio/x-flac'}
|
2018-10-11 18:15:11 +02:00
|
|
|
|
2018-10-12 11:58:01 +02:00
|
|
|
def remove_all(self) -> bool:
|
2018-10-11 18:15:11 +02:00
|
|
|
shutil.copy(self.filename, self.output_filename)
|
|
|
|
f = mutagen.File(self.output_filename)
|
|
|
|
f.clear_pictures()
|
|
|
|
f.delete()
|
|
|
|
f.save(deleteid3=True)
|
|
|
|
return True
|
|
|
|
|
2023-01-28 16:57:20 +01:00
|
|
|
def get_meta(self) -> Dict[str, Union[str, Dict]]:
|
2018-10-11 18:15:11 +02:00
|
|
|
meta = super().get_meta()
|
2018-10-11 19:52:47 +02:00
|
|
|
for num, picture in enumerate(mutagen.File(self.filename).pictures):
|
|
|
|
name = picture.desc if picture.desc else 'Cover %d' % num
|
2018-10-12 11:58:01 +02:00
|
|
|
extension = mimetypes.guess_extension(picture.mime)
|
2023-01-28 16:57:20 +01:00
|
|
|
if extension is None: # pragma: no cover
|
2018-10-12 11:58:01 +02:00
|
|
|
meta[name] = 'harmful data'
|
|
|
|
continue
|
|
|
|
|
2018-10-11 19:52:47 +02:00
|
|
|
_, fname = tempfile.mkstemp()
|
2018-10-12 11:58:01 +02:00
|
|
|
fname = fname + extension
|
2018-10-11 19:52:47 +02:00
|
|
|
with open(fname, 'wb') as f:
|
|
|
|
f.write(picture.data)
|
2018-10-12 11:58:01 +02:00
|
|
|
p, _ = parser_factory.get_parser(fname) # type: ignore
|
2023-11-13 15:03:42 +01:00
|
|
|
if p is None:
|
|
|
|
raise ValueError
|
2023-11-13 13:11:35 +01:00
|
|
|
p.sandbox = self.sandbox
|
2018-10-12 11:58:01 +02:00
|
|
|
# Mypy chokes on ternaries :/
|
|
|
|
meta[name] = p.get_meta() if p else 'harmful data' # type: ignore
|
|
|
|
os.remove(fname)
|
2018-10-11 18:15:11 +02:00
|
|
|
return meta
|
2020-01-01 17:34:42 +01:00
|
|
|
|
|
|
|
|
|
|
|
class WAVParser(video.AbstractFFmpegParser):
|
|
|
|
mimetypes = {'audio/x-wav', }
|
|
|
|
meta_allowlist = {'AvgBytesPerSec', 'BitsPerSample', 'Directory',
|
|
|
|
'Duration', 'Encoding', 'ExifToolVersion',
|
|
|
|
'FileAccessDate', 'FileInodeChangeDate',
|
|
|
|
'FileModifyDate', 'FileName', 'FilePermissions',
|
|
|
|
'FileSize', 'FileType', 'FileTypeExtension',
|
|
|
|
'MIMEType', 'NumChannels', 'SampleRate', 'SourceFile',
|
|
|
|
}
|
2021-04-24 17:26:38 +02:00
|
|
|
|
2023-01-28 16:57:20 +01:00
|
|
|
|
2021-04-24 17:26:38 +02:00
|
|
|
class AIFFParser(video.AbstractFFmpegParser):
|
|
|
|
mimetypes = {'audio/aiff', 'audio/x-aiff'}
|
|
|
|
meta_allowlist = {'AvgBytesPerSec', 'BitsPerSample', 'Directory',
|
|
|
|
'Duration', 'Encoding', 'ExifToolVersion',
|
|
|
|
'FileAccessDate', 'FileInodeChangeDate',
|
|
|
|
'FileModifyDate', 'FileName', 'FilePermissions',
|
|
|
|
'FileSize', 'FileType', 'FileTypeExtension',
|
|
|
|
'MIMEType', 'NumChannels', 'SampleRate', 'SourceFile',
|
|
|
|
'NumSampleFrames', 'SampleSize',
|
|
|
|
}
|