mirror of
https://github.com/azlux/botamusique
synced 2024-11-23 13:56:17 +00:00
212 lines
6.5 KiB
Python
212 lines
6.5 KiB
Python
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 = '<img width="80" src="data:image/jpge;base64,' + \
|
|
self.thumbnail + '"/>'
|
|
display += "<br />" + 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")
|