refactor how media objects parse metadata fields
This commit is contained in:
parent
f3ab3b97f5
commit
78e59a4630
|
@ -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
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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)
|
||||||
|
|
Loading…
Reference in New Issue