refactor how media objects parse metadata fields

This commit is contained in:
meeb 2020-12-06 20:09:48 +11:00
parent f3ab3b97f5
commit 78e59a4630
3 changed files with 78 additions and 18 deletions

View File

@ -7,6 +7,7 @@ from django.db import models
from django.utils.text import slugify from django.utils.text import slugify
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from .youtube import get_media_info as get_youtube_media_info from .youtube import get_media_info as get_youtube_media_info
from .utils import seconds_to_timestr
class Source(models.Model): class Source(models.Model):
@ -69,36 +70,27 @@ class Source(models.Model):
(FALLBACK_NEXT_HD, _('Get next best HD media or codec instead')), (FALLBACK_NEXT_HD, _('Get next best HD media or codec instead')),
) )
# Fontawesome icons used for the source on the front end
ICONS = { ICONS = {
SOURCE_TYPE_YOUTUBE_CHANNEL: '<i class="fab fa-youtube"></i>', SOURCE_TYPE_YOUTUBE_CHANNEL: '<i class="fab fa-youtube"></i>',
SOURCE_TYPE_YOUTUBE_PLAYLIST: '<i class="fab fa-youtube"></i>', SOURCE_TYPE_YOUTUBE_PLAYLIST: '<i class="fab fa-youtube"></i>',
} }
# Format to use to display a URL for the source
URLS = { URLS = {
SOURCE_TYPE_YOUTUBE_CHANNEL: 'https://www.youtube.com/c/{key}', SOURCE_TYPE_YOUTUBE_CHANNEL: 'https://www.youtube.com/c/{key}',
SOURCE_TYPE_YOUTUBE_PLAYLIST: 'https://www.youtube.com/playlist?list={key}', SOURCE_TYPE_YOUTUBE_PLAYLIST: 'https://www.youtube.com/playlist?list={key}',
} }
# Callback functions to get a list of media from the source
INDEXERS = { INDEXERS = {
SOURCE_TYPE_YOUTUBE_CHANNEL: get_youtube_media_info, SOURCE_TYPE_YOUTUBE_CHANNEL: get_youtube_media_info,
SOURCE_TYPE_YOUTUBE_PLAYLIST: get_youtube_media_info, SOURCE_TYPE_YOUTUBE_PLAYLIST: get_youtube_media_info,
} }
# Field names to find the media ID used as the key when storing media
KEY_FIELD = { # Field returned by indexing which contains a unique key KEY_FIELD = {
SOURCE_TYPE_YOUTUBE_CHANNEL: 'id', SOURCE_TYPE_YOUTUBE_CHANNEL: 'id',
SOURCE_TYPE_YOUTUBE_PLAYLIST: 'id', SOURCE_TYPE_YOUTUBE_PLAYLIST: 'id',
} }
PUBLISHED_FIELD = { # Field returned by indexing which contains the published date
SOURCE_TYPE_YOUTUBE_CHANNEL: 'upload_date',
SOURCE_TYPE_YOUTUBE_PLAYLIST: 'upload_date',
}
TITLE_FIELD = { # Field returned by indexing which contains the media title
SOURCE_TYPE_YOUTUBE_CHANNEL: 'title',
SOURCE_TYPE_YOUTUBE_PLAYLIST: 'title',
}
uuid = models.UUIDField( uuid = models.UUIDField(
_('uuid'), _('uuid'),
primary_key=True, primary_key=True,
@ -313,10 +305,34 @@ class Media(models.Model):
Source. Source.
''' '''
# Format to use to display a URL for the media
URLS = { URLS = {
Source.SOURCE_TYPE_YOUTUBE_CHANNEL: 'https://www.youtube.com/watch?v={key}', Source.SOURCE_TYPE_YOUTUBE_CHANNEL: 'https://www.youtube.com/watch?v={key}',
Source.SOURCE_TYPE_YOUTUBE_PLAYLIST: 'https://www.youtube.com/watch?v={key}', Source.SOURCE_TYPE_YOUTUBE_PLAYLIST: 'https://www.youtube.com/watch?v={key}',
} }
# Maps standardised names to names used in source metdata
METADATA_FIELDS = {
'upload_date': {
Source.SOURCE_TYPE_YOUTUBE_CHANNEL: 'upload_date',
Source.SOURCE_TYPE_YOUTUBE_PLAYLIST: 'upload_date',
},
'title': {
Source.SOURCE_TYPE_YOUTUBE_CHANNEL: 'title',
Source.SOURCE_TYPE_YOUTUBE_PLAYLIST: 'title',
},
'description': {
Source.SOURCE_TYPE_YOUTUBE_CHANNEL: 'description',
Source.SOURCE_TYPE_YOUTUBE_PLAYLIST: 'description',
},
'duration': {
Source.SOURCE_TYPE_YOUTUBE_CHANNEL: 'duration',
Source.SOURCE_TYPE_YOUTUBE_PLAYLIST: 'duration',
},
'formats': {
Source.SOURCE_TYPE_YOUTUBE_CHANNEL: 'formats',
Source.SOURCE_TYPE_YOUTUBE_PLAYLIST: 'formats',
}
}
uuid = models.UUIDField( uuid = models.UUIDField(
_('uuid'), _('uuid'),
@ -435,6 +451,10 @@ class Media(models.Model):
verbose_name = _('Media') verbose_name = _('Media')
verbose_name_plural = _('Media') verbose_name_plural = _('Media')
def get_metadata_field(self, field):
fields = self.METADATA_FIELDS.get(field, {})
return fields.get(self.source.source_type, '')
@property @property
def loaded_metadata(self): def loaded_metadata(self):
if self.pk in _metadata_cache: if self.pk in _metadata_cache:
@ -453,8 +473,8 @@ class Media(models.Model):
@property @property
def title(self): def title(self):
k = self.source.TITLE_FIELD.get(self.source.source_type, '') field = self.get_metadata_field('title')
return self.loaded_metadata.get(k, '').strip() return self.loaded_metadata.get(field, '').strip()
@property @property
def name(self): def name(self):
@ -463,13 +483,30 @@ class Media(models.Model):
@property @property
def upload_date(self): def upload_date(self):
k = self.source.PUBLISHED_FIELD.get(self.source.source_type, '') field = self.get_metadata_field('upload_date')
upload_date_str = self.loaded_metadata.get(k, '').strip() upload_date_str = self.loaded_metadata.get(field, '').strip()
try: try:
return datetime.strptime(upload_date_str, '%Y%m%d') return datetime.strptime(upload_date_str, '%Y%m%d')
except (AttributeError, ValueError) as e: except (AttributeError, ValueError) as e:
return None return None
@property
def duration(self):
field = self.get_metadata_field('duration')
return int(self.loaded_metadata.get(field, 0))
@property
def duration_formatted(self):
duration = self.duration
if duration > 0:
return seconds_to_timestr(duration)
return '??:??:??'
@property
def formats(self):
field = self.get_metadata_field('formats')
return self.loaded_metadata.get(field, [])
@property @property
def filename(self): def filename(self):
upload_date = self.upload_date upload_date = self.upload_date

View File

@ -21,6 +21,10 @@
<td class="hide-on-small-only">Title</td> <td class="hide-on-small-only">Title</td>
<td><span class="hide-on-med-and-up">Title<br></span><strong>{{ media.title }}</strong></td> <td><span class="hide-on-med-and-up">Title<br></span><strong>{{ media.title }}</strong></td>
</tr> </tr>
<tr title="The media duration">
<td class="hide-on-small-only">Duration</td>
<td><span class="hide-on-med-and-up">Duration<br></span><strong>{{ media.duration_formatted }}</strong></td>
</tr>
<tr title="The filename the media will be downloaded as"> <tr title="The filename the media will be downloaded as">
<td class="hide-on-small-only">Filename</td> <td class="hide-on-small-only">Filename</td>
<td><span class="hide-on-med-and-up">Filename<br></span><strong>{{ media.filename }}</strong></td> <td><span class="hide-on-med-and-up">Filename<br></span><strong>{{ media.filename }}</strong></td>
@ -33,6 +37,16 @@
<td class="hide-on-small-only">Downloaded</td> <td class="hide-on-small-only">Downloaded</td>
<td><span class="hide-on-med-and-up">Downloaded<br></span><strong>{% if media.downloaded %}<i class="fas fa-check"></i>{% else %}<i class="fas fa-times"></i>{% endif %}</strong></td> <td><span class="hide-on-med-and-up">Downloaded<br></span><strong>{% if media.downloaded %}<i class="fas fa-check"></i>{% else %}<i class="fas fa-times"></i>{% endif %}</strong></td>
</tr> </tr>
<tr title="The available media formats">
<td class="hide-on-small-only">Available formats</td>
<td><span class="hide-on-med-and-up">Available formats<br></span>
{% for format in media.formats %}
<strong>{{ format.format }}</strong><br>
{% empty %}
Media has no detected available formats
{% endfor %}
</td>
</tr>
</table> </table>
</div> </div>
</div> </div>

View File

@ -107,3 +107,12 @@ def delete_file(filepath):
if file_is_editable(filepath): if file_is_editable(filepath):
return os.remove(filepath) return os.remove(filepath)
return False return False
def seconds_to_timestr(seconds):
seconds = seconds % (24 * 3600)
hour = seconds // 3600
seconds %= 3600
minutes = seconds // 60
seconds %= 60
return '{:02d}:{:02d}:{:02d}'.format(hour, minutes, seconds)