Connect the UI for Jellyfin
* Add Jellyfin media server type * Add and use `forms_dict` and `long_types` methods * Add `JellyfinMediaServerForm` * Use `JellyfinMediaServer` * Add and use `MediaServerType.handlers_dict` method * Do not hard-code `plex` * Also map backwards in `MediaServerType.long_types` * Use the long server type for URLs * Loop over media server type names * Add `MediaServerType.long_type` property * Add `media_server_types` to template context * Use the `MediaServerType.members_list` class method * Port is already part of the URL
This commit is contained in:
1 parent
e25a9cd228
commit
976101781d
8 files changed
+137
-28
No files matched your search
@@ -65,8 +65,55 @@ class IndexSchedule(models.IntegerChoices):
|
||||
|
||||
|
||||
class MediaServerType(models.TextChoices):
|
||||
JELLYFIN = 'j', _('Jellyfin')
|
||||
PLEX = 'p', _('Plex')
|
||||
|
||||
@classmethod
|
||||
def forms_dict(cls):
|
||||
from .forms import (
|
||||
JellyfinMediaServerForm,
|
||||
PlexMediaServerForm,
|
||||
)
|
||||
return dict(zip(
|
||||
cls.values,
|
||||
(
|
||||
JellyfinMediaServerForm,
|
||||
PlexMediaServerForm,
|
||||
),
|
||||
))
|
||||
|
||||
@classmethod
|
||||
def handlers_dict(cls):
|
||||
from .mediaservers import (
|
||||
JellyfinMediaServer,
|
||||
PlexMediaServer,
|
||||
)
|
||||
return dict(zip(
|
||||
cls.values,
|
||||
(
|
||||
JellyfinMediaServer,
|
||||
PlexMediaServer,
|
||||
),
|
||||
))
|
||||
|
||||
@property
|
||||
def long_type(self):
|
||||
return self.long_types().get(self.value)
|
||||
|
||||
@classmethod
|
||||
def long_types(cls):
|
||||
d = dict(zip(
|
||||
list(map(cls.lower, cls.names)),
|
||||
cls.values,
|
||||
))
|
||||
rd = dict(zip( d.values(), d.keys() ))
|
||||
rd.update(d)
|
||||
return rd
|
||||
|
||||
@classmethod
|
||||
def members_list(cls):
|
||||
return list(cls.__members__.values())
|
||||
|
||||
|
||||
class MediaState(models.TextChoices):
|
||||
UNKNOWN = 'unknown'
|
||||
|
||||
@@ -48,7 +48,39 @@ class ConfirmDeleteMediaServerForm(forms.Form):
|
||||
|
||||
pass
|
||||
|
||||
_media_server_type_label = 'Jellyfin'
|
||||
class JellyfinMediaServerForm(forms.Form):
|
||||
|
||||
host = forms.CharField(
|
||||
label=_(f'Host name or IP address of the {_media_server_type_label} server'),
|
||||
required=True,
|
||||
)
|
||||
port = forms.IntegerField(
|
||||
label=_(f'Port number of the {_media_server_type_label} server'),
|
||||
required=True,
|
||||
initial=8096,
|
||||
)
|
||||
use_https = forms.BooleanField(
|
||||
label=_('Connect over HTTPS'),
|
||||
required=False,
|
||||
initial=False,
|
||||
)
|
||||
verify_https = forms.BooleanField(
|
||||
label=_('Verify the HTTPS certificate is valid if connecting over HTTPS'),
|
||||
required=False,
|
||||
initial=True,
|
||||
)
|
||||
token = forms.CharField(
|
||||
label=_(f'{_media_server_type_label} token'),
|
||||
required=True,
|
||||
)
|
||||
libraries = forms.CharField(
|
||||
label=_(f'Comma-separated list of {_media_server_type_label} library IDs to update'),
|
||||
required=False,
|
||||
)
|
||||
|
||||
|
||||
_media_server_type_label = 'Plex'
|
||||
class PlexMediaServerForm(forms.Form):
|
||||
|
||||
host = forms.CharField(
|
||||
|
||||
@@ -180,9 +180,8 @@ class JellyfinMediaServer(MediaServer):
|
||||
'User-Agent': 'TubeSync',
|
||||
'X-Emby-Token': self.object.loaded_options['token'] # Jellyfin uses the same `X-Emby-Token` header as Emby
|
||||
}
|
||||
base_url = f"{self.object.url}:{self.object.port}" if self.object.port else self.object.url
|
||||
url = f"{base_url}{uri}"
|
||||
|
||||
url = f'{self.object.url}{uri}'
|
||||
log.debug(f'[jellyfin media server] Making HTTP GET request to: {url}')
|
||||
|
||||
return requests.get(url, headers=headers, verify=self.object.verify_https, timeout=self.TIMEOUT)
|
||||
@@ -234,19 +233,18 @@ class JellyfinMediaServer(MediaServer):
|
||||
remote_libraries_desc.append(f'"{remote_library_name}" with ID '
|
||||
f'"{remote_library_id}"')
|
||||
remote_libraries_str = ', '.join(remote_libraries_desc)
|
||||
for library_id in libraries:
|
||||
library_id = library_id.strip()
|
||||
libraries = options.get('libraries', '').split(',')
|
||||
for library_id in map(str.strip, libraries):
|
||||
if library_id not in remote_libraries:
|
||||
raise ValidationError(f'One or more of your specified library IDs do '
|
||||
f'not exist on your Plex Media Server. Your '
|
||||
f'not exist on your Jellyfin Media Server. Your '
|
||||
f'valid libraries are: {remote_libraries_str}')
|
||||
|
||||
return True
|
||||
|
||||
def update(self):
|
||||
libraries = self.object.loaded_options.get('libraries', '').split(',')
|
||||
for library_id in libraries:
|
||||
library_id = library_id.strip()
|
||||
for library_id in map(str.strip, libraries):
|
||||
uri = f'/Library/{library_id}/Refresh'
|
||||
response = self.make_request(uri)
|
||||
if response.status_code != 204: # 204 No Content is expected for successful refresh
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
# Generated by Django 3.2.25 on 2025-02-22 03:52
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('sync', '0028_alter_source_source_resolution'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='mediaserver',
|
||||
name='options',
|
||||
field=models.TextField(help_text='JSON encoded options for the media server', null=True, verbose_name='options'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='mediaserver',
|
||||
name='server_type',
|
||||
field=models.CharField(choices=[('j', 'Jellyfin'), ('p', 'Plex')], db_index=True, default='p', help_text='Server type', max_length=1, verbose_name='server type'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='mediaserver',
|
||||
name='use_https',
|
||||
field=models.BooleanField(default=False, help_text='Connect to the media server over HTTPS', verbose_name='use https'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='mediaserver',
|
||||
name='verify_https',
|
||||
field=models.BooleanField(default=True, help_text='If connecting over HTTPS, verify the SSL certificate is valid', verbose_name='verify https'),
|
||||
),
|
||||
]
|
||||
@@ -24,7 +24,6 @@ from .utils import (seconds_to_timestr, parse_media_format, filter_response,
|
||||
write_text_file, mkdir_p, directory_and_stem, glob_quote)
|
||||
from .matching import (get_best_combined_format, get_best_audio_format,
|
||||
get_best_video_format)
|
||||
from .mediaservers import PlexMediaServer
|
||||
from .fields import CommaSepChoiceField
|
||||
from .choices import (Val, CapChoices, Fallback, FileExtension,
|
||||
FilterSeconds, IndexSchedule, MediaServerType,
|
||||
@@ -1553,11 +1552,10 @@ class MediaServer(models.Model):
|
||||
'''
|
||||
|
||||
ICONS = {
|
||||
Val(MediaServerType.JELLYFIN): '<i class="fas fa-server"></i>',
|
||||
Val(MediaServerType.PLEX): '<i class="fas fa-server"></i>',
|
||||
}
|
||||
HANDLERS = {
|
||||
Val(MediaServerType.PLEX): PlexMediaServer,
|
||||
}
|
||||
HANDLERS = MediaServerType.handlers_dict()
|
||||
|
||||
server_type = models.CharField(
|
||||
_('server type'),
|
||||
@@ -1580,17 +1578,17 @@ class MediaServer(models.Model):
|
||||
)
|
||||
use_https = models.BooleanField(
|
||||
_('use https'),
|
||||
default=True,
|
||||
default=False,
|
||||
help_text=_('Connect to the media server over HTTPS')
|
||||
)
|
||||
verify_https = models.BooleanField(
|
||||
_('verify https'),
|
||||
default=False,
|
||||
default=True,
|
||||
help_text=_('If connecting over HTTPS, verify the SSL certificate is valid')
|
||||
)
|
||||
options = models.TextField(
|
||||
_('options'),
|
||||
blank=True,
|
||||
blank=False, # valid JSON only
|
||||
null=True,
|
||||
help_text=_('JSON encoded options for the media server')
|
||||
)
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<form method="post" action="{% url 'sync:add-mediaserver' server_type='plex' %}" class="col s12 simpleform">
|
||||
<form method="post" action="{% url 'sync:add-mediaserver' server_type=server_type_long %}" class="col s12 simpleform">
|
||||
{% csrf_token %}
|
||||
{% include 'simpleform.html' with form=form %}
|
||||
<div class="row no-margin-bottom padding-top">
|
||||
|
||||
@@ -10,16 +10,18 @@
|
||||
Media servers are services like Plex which you may be running on your network. If
|
||||
you add your media server TubeSync will notify your media server to rescan or
|
||||
refresh its libraries every time media is successfully downloaded. Currently,
|
||||
TubeSync only supports Plex.
|
||||
TubeSync only supports Jellyfin and Plex.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
{% include 'infobox.html' with message=message %}
|
||||
{% for mst in media_server_types %}
|
||||
<div class="row">
|
||||
<div class="col s12 margin-bottom">
|
||||
<a href="{% url 'sync:add-mediaserver' server_type='plex' %}" class="btn">Add a Plex media server <i class="fas fa-server"></i></a>
|
||||
<a href="{% url 'sync:add-mediaserver' server_type=mst.long_type %}" class="btn">Add a {{ mst.label }} media server <i class="fas fa-server"></i></a>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
<div class="row no-margin-bottom">
|
||||
<div class="col s12">
|
||||
<div class="collection">
|
||||
|
||||
+10
-11
@@ -25,7 +25,7 @@ from common.utils import append_uri_params
|
||||
from background_task.models import Task, CompletedTask
|
||||
from .models import Source, Media, MediaServer
|
||||
from .forms import (ValidateSourceForm, ConfirmDeleteSourceForm, RedownloadMediaForm,
|
||||
SkipMediaForm, EnableMediaForm, ResetTasksForm, PlexMediaServerForm,
|
||||
SkipMediaForm, EnableMediaForm, ResetTasksForm,
|
||||
ConfirmDeleteMediaServerForm)
|
||||
from .utils import validate_url, delete_file
|
||||
from .tasks import (map_task_to_instance, get_error_message,
|
||||
@@ -879,6 +879,7 @@ class MediaServersView(ListView):
|
||||
|
||||
template_name = 'sync/mediaservers.html'
|
||||
context_object_name = 'mediaservers'
|
||||
types_object = MediaServerType
|
||||
messages = {
|
||||
'deleted': _('Your selected media server has been deleted.'),
|
||||
}
|
||||
@@ -893,11 +894,12 @@ class MediaServersView(ListView):
|
||||
return super().dispatch(request, *args, **kwargs)
|
||||
|
||||
def get_queryset(self):
|
||||
return MediaServer.objects.all().order_by('host')
|
||||
return MediaServer.objects.all().order_by('host', 'port')
|
||||
|
||||
def get_context_data(self, *args, **kwargs):
|
||||
data = super().get_context_data(*args, **kwargs)
|
||||
data['message'] = self.message
|
||||
data['media_server_types'] = self.types_object.members_list()
|
||||
return data
|
||||
|
||||
|
||||
@@ -908,13 +910,9 @@ class AddMediaServerView(FormView):
|
||||
'''
|
||||
|
||||
template_name = 'sync/mediaserver-add.html'
|
||||
server_types = {
|
||||
'plex': Val(MediaServerType.PLEX),
|
||||
}
|
||||
server_types = MediaServerType.long_types()
|
||||
server_type_names = dict(MediaServerType.choices)
|
||||
forms = {
|
||||
Val(MediaServerType.PLEX): PlexMediaServerForm,
|
||||
}
|
||||
forms = MediaServerType.forms_dict()
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.server_type = None
|
||||
@@ -928,6 +926,8 @@ class AddMediaServerView(FormView):
|
||||
if not self.server_type:
|
||||
raise Http404
|
||||
self.form_class = self.forms.get(self.server_type)
|
||||
if not self.form_class:
|
||||
raise Http404
|
||||
self.model_class = MediaServer(server_type=self.server_type)
|
||||
return super().dispatch(request, *args, **kwargs)
|
||||
|
||||
@@ -969,6 +969,7 @@ class AddMediaServerView(FormView):
|
||||
def get_context_data(self, *args, **kwargs):
|
||||
data = super().get_context_data(*args, **kwargs)
|
||||
data['server_type'] = self.server_type
|
||||
data['server_type_long'] = self.server_types.get(self.server_type)
|
||||
data['server_type_name'] = self.server_type_names.get(self.server_type)
|
||||
data['server_help'] = self.model_class.get_help_html()
|
||||
return data
|
||||
@@ -1029,9 +1030,7 @@ class UpdateMediaServerView(FormView, SingleObjectMixin):
|
||||
|
||||
template_name = 'sync/mediaserver-update.html'
|
||||
model = MediaServer
|
||||
forms = {
|
||||
Val(MediaServerType.PLEX): PlexMediaServerForm,
|
||||
}
|
||||
forms = MediaServerType.forms_dict()
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.object = None
|
||||
|
||||
Reference in new issue
Block a user