import os
import re
from io import BytesIO
import base64
import hashlib
import mutagen
from PIL import Image
import util
import variables as var
from media.item import BaseItem, item_builders, item_loaders, item_id_generators, ValidationFailedError
from constants import tr_cli as tr
'''
type : file
id
path
title
artist
duration
thumbnail
user
'''
def file_item_builder(**kwargs):
return FileItem(kwargs['path'])
def file_item_loader(_dict):
return FileItem("", _dict)
def file_item_id_generator(**kwargs):
return hashlib.md5(kwargs['path'].encode()).hexdigest()
item_builders['file'] = file_item_builder
item_loaders['file'] = file_item_loader
item_id_generators['file'] = file_item_id_generator
class FileItem(BaseItem):
def __init__(self, path, from_dict=None):
if not from_dict:
super().__init__()
self.path = path
self.title = ""
self.artist = ""
self.thumbnail = None
self.id = hashlib.md5(path.encode()).hexdigest()
if os.path.exists(self.uri()):
self._get_info_from_tag()
self.ready = "yes"
self.duration = util.get_media_duration(self.uri())
self.keywords = self.title + " " + self.artist
else:
super().__init__(from_dict)
self.artist = from_dict['artist']
self.thumbnail = from_dict['thumbnail']
try:
self.validate()
except ValidationFailedError:
self.ready = "failed"
self.type = "file"
def uri(self):
return var.music_folder + self.path if self.path[0] != "/" else self.path
def is_ready(self):
return True
def validate(self):
if not os.path.exists(self.uri()):
self.log.info(
"file: music file missed for %s" % self.format_debug_string())
raise ValidationFailedError(tr('file_missed', file=self.path))
if self.duration == 0:
self.duration = util.get_media_duration(self.uri())
self.version += 1 # 0 -> 1, notify the wrapper to save me
self.ready = "yes"
return True
def _get_info_from_tag(self):
match = re.search(r"(.+)\.(.+)", self.uri())
assert match is not None
file_no_ext = match[1]
ext = match[2]
try:
im = None
path_thumbnail = file_no_ext + ".jpg"
if os.path.isfile(path_thumbnail):
im = Image.open(path_thumbnail)
if ext == "mp3":
# title: TIT2
# artist: TPE1, TPE2
# album: TALB
# cover artwork: APIC:
tags = mutagen.File(self.uri())
if 'TIT2' in tags:
self.title = tags['TIT2'].text[0]
if 'TPE1' in tags: # artist
self.artist = tags['TPE1'].text[0]
if im is None:
if "APIC:" in tags:
im = Image.open(BytesIO(tags["APIC:"].data))
elif ext == "m4a" or ext == "m4b" or ext == "mp4" or ext == "m4p":
# title: ©nam (\xa9nam)
# artist: ©ART
# album: ©alb
# cover artwork: covr
tags = mutagen.File(self.uri())
if '©nam' in tags:
self.title = tags['©nam'][0]
if '©ART' in tags: # artist
self.artist = tags['©ART'][0]
if im is None:
if "covr" in tags:
im = Image.open(BytesIO(tags["covr"][0]))
elif ext == "opus":
# title: 'title'
# artist: 'artist'
# album: 'album'
# cover artwork: 'metadata_block_picture', and then:
## |
## |
## v
## Decode string as base64 binary
## |
## v
## Open that binary as a mutagen.flac.Picture
## |
## v
## Extract binary image data
tags = mutagen.File(self.uri())
if 'title' in tags:
self.title = tags['title'][0]
if 'artist' in tags:
self.artist = tags['artist'][0]
if im is None:
if 'metadata_block_picture' in tags:
pic_as_base64 = tags['metadata_block_picture'][0]
as_flac_picture = mutagen.flac.Picture(base64.b64decode(pic_as_base64))
im = Image.open(BytesIO(as_flac_picture.data))
if im:
self.thumbnail = self._prepare_thumbnail(im)
except:
pass
if not self.title:
self.title = os.path.basename(file_no_ext)
@staticmethod
def _prepare_thumbnail(im):
im.thumbnail((100, 100), Image.ANTIALIAS)
buffer = BytesIO()
im = im.convert('RGB')
im.save(buffer, format="JPEG")
return base64.b64encode(buffer.getvalue()).decode('utf-8')
def to_dict(self):
dict = super().to_dict()
dict['type'] = 'file'
dict['path'] = self.path
dict['title'] = self.title
dict['artist'] = self.artist
dict['thumbnail'] = self.thumbnail
return dict
def format_debug_string(self):
return "[file] {descrip} ({path})".format(
descrip=self.format_title(),
path=self.path
)
def format_song_string(self, user):
return tr("file_item",
title=self.title,
artist=self.artist if self.artist else '??',
user=user
)
def format_current_playing(self, user):
display = tr("now_playing", item=self.format_song_string(user))
if self.thumbnail:
thumbnail_html = ''
display += "
" + thumbnail_html
return display
def format_title(self):
title = self.title if self.title else self.path
if self.artist:
return self.artist + " - " + title
else:
return title
def display_type(self):
return tr("file")