feat: better support for subtitles (#1215)

This commit is contained in:
Markos Gogoulos 2025-03-09 20:29:26 +02:00 committed by GitHub
parent 0b9a203123
commit 50e9f3103f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 164 additions and 5 deletions

View File

@ -528,3 +528,5 @@ DJANGO_ADMIN_URL = "admin/"
# CSRF_COOKIE_SECURE = True # CSRF_COOKIE_SECURE = True
# SESSION_COOKIE_SECURE = True # SESSION_COOKIE_SECURE = True
PYSUBS_COMMAND = "pysubs2"

View File

@ -68,6 +68,8 @@ class SubtitleForm(forms.ModelForm):
def __init__(self, media_item, *args, **kwargs): def __init__(self, media_item, *args, **kwargs):
super(SubtitleForm, self).__init__(*args, **kwargs) super(SubtitleForm, self).__init__(*args, **kwargs)
self.instance.media = media_item 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): def save(self, *args, **kwargs):
self.instance.user = self.instance.media.user self.instance.user = self.instance.media.user
@ -75,6 +77,14 @@ class SubtitleForm(forms.ModelForm):
return media 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): class ContactForm(forms.Form):
from_email = forms.EmailField(required=True) from_email = forms.EmailField(required=True)
name = forms.CharField(required=False) name = forms.CharField(required=False)

View File

@ -1210,9 +1210,36 @@ class Subtitle(models.Model):
user = models.ForeignKey("users.User", on_delete=models.CASCADE) user = models.ForeignKey("users.User", on_delete=models.CASCADE)
class Meta:
ordering = ["language__title"]
def __str__(self): def __str__(self):
return "{0}-{1}".format(self.media.title, self.language.title) 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): class RatingCategory(models.Model):
"""Rating Category """Rating Category

View File

@ -12,6 +12,7 @@ urlpatterns = [
re_path(r"^about", views.about, name="about"), re_path(r"^about", views.about, name="about"),
re_path(r"^setlanguage", views.setlanguage, name="setlanguage"), re_path(r"^setlanguage", views.setlanguage, name="setlanguage"),
re_path(r"^add_subtitle", views.add_subtitle, name="add_subtitle"), 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"^categories$", views.categories, name="categories"),
re_path(r"^contact$", views.contact, name="contact"), re_path(r"^contact$", views.contact, name="contact"),
re_path(r"^edit", views.edit_media, name="edit_media"), re_path(r"^edit", views.edit_media, name="edit_media"),

View File

@ -6,7 +6,7 @@ from django.contrib.auth.decorators import login_required
from django.contrib.postgres.search import SearchQuery from django.contrib.postgres.search import SearchQuery
from django.core.mail import EmailMessage from django.core.mail import EmailMessage
from django.db.models import Q 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 django.shortcuts import get_object_or_404, render
from drf_yasg import openapi as openapi from drf_yasg import openapi as openapi
from drf_yasg.utils import swagger_auto_schema from drf_yasg.utils import swagger_auto_schema
@ -32,7 +32,7 @@ from cms.permissions import (
) )
from users.models import User 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 .frontend_translations import translate_string
from .helpers import clean_query, get_alphanumeric_only, produce_ffmpeg_commands from .helpers import clean_query, get_alphanumeric_only, produce_ffmpeg_commands
from .methods import ( from .methods import (
@ -54,6 +54,7 @@ from .models import (
Media, Media,
Playlist, Playlist,
PlaylistMedia, PlaylistMedia,
Subtitle,
Tag, Tag,
) )
from .serializers import ( from .serializers import (
@ -105,12 +106,68 @@ def add_subtitle(request):
form = SubtitleForm(media, request.POST, request.FILES) form = SubtitleForm(media, request.POST, request.FILES)
if form.is_valid(): if form.is_valid():
subtitle = form.save() 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()) 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)
else: else:
form = SubtitleForm(media_item=media) 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): def categories(request):

View File

@ -19,3 +19,4 @@ m3u8==6.0.0
django-debug-toolbar==5.0.1 django-debug-toolbar==5.0.1
django-login-required-middleware==0.9.0 django-login-required-middleware==0.9.0
pre-commit==4.1.0 pre-commit==4.1.0
pysubs2==1.8.0

View File

@ -7,11 +7,23 @@
<div class="user-action-form-wrap"> <div class="user-action-form-wrap">
<div class="user-action-form-inner"> <div class="user-action-form-inner">
<h1>Add subtitle</h1> <h1>Add subtitle</h1>
Media: <a href="{{media.get_absolute_url}}">{{media.title}}</a>
<form enctype="multipart/form-data" action="" method="post" class="post-form"> <form enctype="multipart/form-data" action="" method="post" class="post-form">
{% csrf_token %} {% csrf_token %}
{{ form|crispy }} {{ form|crispy }}
<button class="primaryAction" type="submit">Add</button> <button class="primaryAction" type="submit">Add</button>
</form> </form>
{% if subtitles %}
<h3>View/Edit Existing Subtitles</h3>
<ul>
{% for subtitle in subtitles %}
<li><a href="{{subtitle.url}}">{{subtitle.language.title}}</a></li>
{% endfor %}
</ul>
{% endif %}
</div> </div>
</div> </div>
{% endblock innercontent %} {% endblock innercontent %}

View File

@ -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' %}
<div class="user-action-form-wrap">
<h1>Confirm deletion</h1>
<div class="user-action-form-inner">
are you sure you want to delete the subtitle?
<form action="{{subtitle.url}}&action=delete&confirm=true" method="post" class="post-form">
{% csrf_token %}
<button class="secondaryAction" type="submit">DELETE SUBTITLE</button>
</form>
</div>
</div>
{% else %}
<div class="user-action-form-wrap">
<h1>Edit {{subtitle.language.title}} subtitle</h1>
<div class="user-action-form-inner">
Media: <a href="{{subtitle.media.get_absolute_url}}">{{subtitle.media.title}}</a>
<form action="" method="post" class="post-form">
{% csrf_token %}
{{ form|crispy }}
<button class="primaryAction" style="margin-right: 10px" type="submit">SAVE</button>
<button class="primaryAction" style="margin-right: 10px" type="button" onclick="window.location.href='{{subtitle.url}}&action=download';">DOWNLOAD</button>
<button class="primaryAction" type="button" onclick="window.location.href='{{subtitle.url}}&action=delete';">DELETE</button>
</form>
</div>
</div>
{% endif %}
{% endblock innercontent %}