diff --git a/cms/settings.py b/cms/settings.py index 69d8cf05..49c92f66 100644 --- a/cms/settings.py +++ b/cms/settings.py @@ -528,3 +528,5 @@ DJANGO_ADMIN_URL = "admin/" # CSRF_COOKIE_SECURE = True # SESSION_COOKIE_SECURE = True + +PYSUBS_COMMAND = "pysubs2" diff --git a/files/forms.py b/files/forms.py index a36c0ca6..26ce4cac 100644 --- a/files/forms.py +++ b/files/forms.py @@ -68,6 +68,8 @@ class SubtitleForm(forms.ModelForm): def __init__(self, media_item, *args, **kwargs): super(SubtitleForm, self).__init__(*args, **kwargs) self.instance.media = media_item + self.fields["subtitle_file"].help_text = "SubRip (.srt) and WebVTT (.vtt) are supported file formats." + self.fields["subtitle_file"].label = "Subtitle or Closed Caption File" def save(self, *args, **kwargs): self.instance.user = self.instance.media.user @@ -75,6 +77,14 @@ class SubtitleForm(forms.ModelForm): return media +class EditSubtitleForm(forms.Form): + subtitle = forms.CharField(widget=forms.Textarea, required=True) + + def __init__(self, subtitle, *args, **kwargs): + super(EditSubtitleForm, self).__init__(*args, **kwargs) + self.fields["subtitle"].initial = subtitle.subtitle_file.read().decode("utf-8") + + class ContactForm(forms.Form): from_email = forms.EmailField(required=True) name = forms.CharField(required=False) diff --git a/files/models.py b/files/models.py index 85f606bc..bd77dfc4 100644 --- a/files/models.py +++ b/files/models.py @@ -1210,9 +1210,36 @@ class Subtitle(models.Model): user = models.ForeignKey("users.User", on_delete=models.CASCADE) + class Meta: + ordering = ["language__title"] + def __str__(self): return "{0}-{1}".format(self.media.title, self.language.title) + def get_absolute_url(self): + return f"{reverse('edit_subtitle')}?id={self.id}" + + @property + def url(self): + return self.get_absolute_url() + + def convert_to_srt(self): + input_path = self.subtitle_file.path + with tempfile.TemporaryDirectory(dir=settings.TEMP_DIRECTORY) as tmpdirname: + pysub = settings.PYSUBS_COMMAND + + cmd = [pysub, input_path, "--to", "vtt", "-o", tmpdirname] + stdout = helpers.run_command(cmd) + + list_of_files = os.listdir(tmpdirname) + if list_of_files: + subtitles_file = os.path.join(tmpdirname, list_of_files[0]) + cmd = ["cp", subtitles_file, input_path] + stdout = helpers.run_command(cmd) # noqa + else: + raise Exception("Could not convert to srt") + return True + class RatingCategory(models.Model): """Rating Category diff --git a/files/urls.py b/files/urls.py index 500f6bbf..654190e5 100644 --- a/files/urls.py +++ b/files/urls.py @@ -12,6 +12,7 @@ urlpatterns = [ re_path(r"^about", views.about, name="about"), re_path(r"^setlanguage", views.setlanguage, name="setlanguage"), re_path(r"^add_subtitle", views.add_subtitle, name="add_subtitle"), + re_path(r"^edit_subtitle", views.edit_subtitle, name="edit_subtitle"), re_path(r"^categories$", views.categories, name="categories"), re_path(r"^contact$", views.contact, name="contact"), re_path(r"^edit", views.edit_media, name="edit_media"), diff --git a/files/views.py b/files/views.py index 5e22e2e8..191bc0d5 100644 --- a/files/views.py +++ b/files/views.py @@ -6,7 +6,7 @@ from django.contrib.auth.decorators import login_required from django.contrib.postgres.search import SearchQuery from django.core.mail import EmailMessage from django.db.models import Q -from django.http import HttpResponseRedirect +from django.http import HttpResponse, HttpResponseRedirect from django.shortcuts import get_object_or_404, render from drf_yasg import openapi as openapi from drf_yasg.utils import swagger_auto_schema @@ -32,7 +32,7 @@ from cms.permissions import ( ) from users.models import User -from .forms import ContactForm, MediaForm, SubtitleForm +from .forms import ContactForm, EditSubtitleForm, MediaForm, SubtitleForm from .frontend_translations import translate_string from .helpers import clean_query, get_alphanumeric_only, produce_ffmpeg_commands from .methods import ( @@ -54,6 +54,7 @@ from .models import ( Media, Playlist, PlaylistMedia, + Subtitle, Tag, ) from .serializers import ( @@ -105,12 +106,68 @@ def add_subtitle(request): form = SubtitleForm(media, request.POST, request.FILES) if form.is_valid(): subtitle = form.save() - messages.add_message(request, messages.INFO, translate_string(request.LANGUAGE_CODE, "Subtitle was added")) + new_subtitle = Subtitle.objects.filter(id=subtitle.id).first() + try: + new_subtitle.convert_to_srt() + messages.add_message(request, messages.INFO, "Subtitle was added!") + return HttpResponseRedirect(subtitle.media.get_absolute_url()) + except: # noqa: E722 + new_subtitle.delete() + error_msg = "Invalid subtitle format. Use SubRip (.srt) or WebVTT (.vtt) files." + form.add_error("subtitle_file", error_msg) - return HttpResponseRedirect(subtitle.media.get_absolute_url()) else: form = SubtitleForm(media_item=media) - return render(request, "cms/add_subtitle.html", {"form": form}) + subtitles = media.subtitles.all() + context = {"media": media, "form": form, "subtitles": subtitles} + return render(request, "cms/add_subtitle.html", context) + + +@login_required +def edit_subtitle(request): + subtitle_id = request.GET.get("id", "").strip() + action = request.GET.get("action", "").strip() + if not subtitle_id: + return HttpResponseRedirect("/") + subtitle = Subtitle.objects.filter(id=subtitle_id).first() + + if not subtitle: + return HttpResponseRedirect("/") + + if not (request.user == subtitle.user or is_mediacms_editor(request.user) or is_mediacms_manager(request.user)): + return HttpResponseRedirect("/") + + context = {"subtitle": subtitle, "action": action} + + if action == "download": + response = HttpResponse(subtitle.subtitle_file.read(), content_type="text/vtt") + filename = subtitle.subtitle_file.name.split("/")[-1] + + if not filename.endswith(".vtt"): + filename = f"{filename}.vtt" + + response["Content-Disposition"] = f"attachment; filename={filename}" + + return response + + if request.method == "GET": + form = EditSubtitleForm(subtitle) + context["form"] = form + elif request.method == "POST": + confirm = request.GET.get("confirm", "").strip() + if confirm == "true": + messages.add_message(request, messages.INFO, "Subtitle was deleted") + redirect_url = subtitle.media.get_absolute_url() + subtitle.delete() + return HttpResponseRedirect(redirect_url) + form = EditSubtitleForm(subtitle, request.POST) + subtitle_text = form.data["subtitle"] + with open(subtitle.subtitle_file.path, "w") as ff: + ff.write(subtitle_text) + + messages.add_message(request, messages.INFO, "Subtitle was edited") + return HttpResponseRedirect(subtitle.media.get_absolute_url()) + return render(request, "cms/edit_subtitle.html", context) def categories(request): diff --git a/requirements.txt b/requirements.txt index 44526f4a..065c0259 100644 --- a/requirements.txt +++ b/requirements.txt @@ -19,3 +19,4 @@ m3u8==6.0.0 django-debug-toolbar==5.0.1 django-login-required-middleware==0.9.0 pre-commit==4.1.0 +pysubs2==1.8.0 diff --git a/templates/cms/add_subtitle.html b/templates/cms/add_subtitle.html index bcf9c914..d1cbec3e 100644 --- a/templates/cms/add_subtitle.html +++ b/templates/cms/add_subtitle.html @@ -7,11 +7,23 @@

Add subtitle

+ Media: {{media.title}} +
{% csrf_token %} {{ form|crispy }}
+ + {% if subtitles %} +

View/Edit Existing Subtitles

+ + {% endif %} +
{% endblock innercontent %} diff --git a/templates/cms/edit_subtitle.html b/templates/cms/edit_subtitle.html new file mode 100644 index 00000000..bc43b6c5 --- /dev/null +++ b/templates/cms/edit_subtitle.html @@ -0,0 +1,49 @@ +{% extends "base.html" %} +{% load crispy_forms_tags %} + +{% block headtitle %}Edit subtitle - {{PORTAL_NAME}}{% endblock headtitle %} + +{% block headermeta %}{% endblock headermeta %} + +{% block innercontent %} + + {% if action == 'delete' %} + +
+

Confirm deletion

+ +
+ are you sure you want to delete the subtitle? + +
+ {% csrf_token %} + +
+
+ +
+ + + + {% else %} + +
+

Edit {{subtitle.language.title}} subtitle

+
+ Media: {{subtitle.media.title}} +
+ {% csrf_token %} + {{ form|crispy }} + + + + +
+
+ +
+ + {% endif %} +{% endblock innercontent %} + +