mirror of
https://github.com/mediacms-io/mediacms.git
synced 2025-11-06 07:28:53 -05:00
m
This commit is contained in:
parent
f39de968c8
commit
1c9aff252e
@ -14,7 +14,6 @@ from django.core.files import File
|
|||||||
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.utils import timezone
|
from django.utils import timezone
|
||||||
from contextlib import contextmanager
|
|
||||||
|
|
||||||
from cms import celery_app
|
from cms import celery_app
|
||||||
|
|
||||||
@ -23,15 +22,6 @@ from .helpers import mask_ip
|
|||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@contextmanager
|
|
||||||
def disable_signal(signal, receiver, sender):
|
|
||||||
"""Context manager to temporarily disable a signal"""
|
|
||||||
signal.disconnect(receiver, sender=sender)
|
|
||||||
try:
|
|
||||||
yield
|
|
||||||
finally:
|
|
||||||
signal.connect(receiver, sender=sender)
|
|
||||||
|
|
||||||
|
|
||||||
def get_user_or_session(request):
|
def get_user_or_session(request):
|
||||||
"""Return a dictionary with user info
|
"""Return a dictionary with user info
|
||||||
@ -459,7 +449,7 @@ def copy_video(original_media, copy_encodings=True, title_suffix="(Trimmed)"):
|
|||||||
add_date=timezone.now()
|
add_date=timezone.now()
|
||||||
)
|
)
|
||||||
models.Media.objects.bulk_create([new_media])
|
models.Media.objects.bulk_create([new_media])
|
||||||
# avoids calling signals
|
# avoids calling signals since signals will call media_init and we don't want that
|
||||||
|
|
||||||
|
|
||||||
if copy_encodings:
|
if copy_encodings:
|
||||||
@ -477,7 +467,7 @@ def copy_video(original_media, copy_encodings=True, title_suffix="(Trimmed)"):
|
|||||||
logs=f"Copied from encoding {encoding.id}"
|
logs=f"Copied from encoding {encoding.id}"
|
||||||
)
|
)
|
||||||
models.Encoding.objects.bulk_create([new_encoding])
|
models.Encoding.objects.bulk_create([new_encoding])
|
||||||
# avoids calling signals
|
# avoids calling signals as this is still not ready
|
||||||
|
|
||||||
# Copy categories and tags
|
# Copy categories and tags
|
||||||
for category in original_media.category.all():
|
for category in original_media.category.all():
|
||||||
|
|||||||
@ -526,6 +526,8 @@ class Media(models.Model):
|
|||||||
with open(self.media_file.path, "rb") as f:
|
with open(self.media_file.path, "rb") as f:
|
||||||
myfile = File(f)
|
myfile = File(f)
|
||||||
thumbnail_name = helpers.get_file_name(self.media_file.path) + ".jpg"
|
thumbnail_name = helpers.get_file_name(self.media_file.path) + ".jpg"
|
||||||
|
# avoid saving the whole object, because something might have been changed
|
||||||
|
# on the meanwhile
|
||||||
self.thumbnail.save(content=myfile, name=thumbnail_name, save=False)
|
self.thumbnail.save(content=myfile, name=thumbnail_name, save=False)
|
||||||
self.poster.save(content=myfile, name=thumbnail_name, save=False)
|
self.poster.save(content=myfile, name=thumbnail_name, save=False)
|
||||||
self.save(update_fields=["thumbnail", "poster"])
|
self.save(update_fields=["thumbnail", "poster"])
|
||||||
@ -563,6 +565,8 @@ class Media(models.Model):
|
|||||||
with open(tf, "rb") as f:
|
with open(tf, "rb") as f:
|
||||||
myfile = File(f)
|
myfile = File(f)
|
||||||
thumbnail_name = helpers.get_file_name(self.media_file.path) + ".jpg"
|
thumbnail_name = helpers.get_file_name(self.media_file.path) + ".jpg"
|
||||||
|
# avoid saving the whole object, because something might have been changed
|
||||||
|
# on the meanwhile
|
||||||
self.thumbnail.save(content=myfile, name=thumbnail_name, save=False)
|
self.thumbnail.save(content=myfile, name=thumbnail_name, save=False)
|
||||||
self.poster.save(content=myfile, name=thumbnail_name, save=False)
|
self.poster.save(content=myfile, name=thumbnail_name, save=False)
|
||||||
self.save(update_fields=["thumbnail", "poster"])
|
self.save(update_fields=["thumbnail", "poster"])
|
||||||
|
|||||||
@ -62,9 +62,11 @@ ERRORS_LIST = [
|
|||||||
def handle_pending_running_encodings(media):
|
def handle_pending_running_encodings(media):
|
||||||
"""Handle pending and running encodings for a media object.
|
"""Handle pending and running encodings for a media object.
|
||||||
|
|
||||||
we are trimming the original file. If there are encodings in success, this means that the encoding has run
|
we are trimming the original file. If there are encodings in success state, this means that the encoding has run
|
||||||
and has succeeded, so we can keep them (they will be trimmed). However for encodings that are in pending
|
and has succeeded, so we can keep them (they will be trimmed) or if we dont keep them we dont have to delete them
|
||||||
or running phase,
|
here
|
||||||
|
|
||||||
|
However for encodings that are in pending or running phase, just delete them
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
media: The media object to handle encodings for
|
media: The media object to handle encodings for
|
||||||
@ -86,15 +88,23 @@ def handle_pending_running_encodings(media):
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
def pre_trim_actions(media):
|
def pre_trim_video_actions(media):
|
||||||
# avoid re-running unnecessary encodings (or chunkize_media, which is the first step for them)
|
# the reason for this function is to perform tasks before trimming a video
|
||||||
# if the video is already completed. however if it is a new video (user uploded the video and starts trimming
|
|
||||||
# before the video is processed), this is necessary, so encode has to be called
|
# avoid re-running unnecessary encodings (or chunkize_media, which is the first step for them)
|
||||||
|
# if the video is already completed
|
||||||
|
# however if it is a new video (user uploded the video and starts trimming
|
||||||
|
# before the video is processed), this is necessary, so encode has to be called to give it a chance to encode
|
||||||
|
|
||||||
|
# if a video is fully processed (all encodings are success), or if a video is new, then things are clear
|
||||||
|
|
||||||
|
# HOWEVER there is a race condition and this is that some encodings are success and some are pending/running
|
||||||
|
# Since we are making speed cutting, we will perform an ffmpeg -c copy on all of them and the result will be
|
||||||
|
# that they will end up differently cut, because ffmpeg checks for I-frames
|
||||||
|
# The result is fine if playing the video but is bad in case of HLS
|
||||||
|
# So we need to delete all encodings inevitably to produce same results, if there are some that are success and some that
|
||||||
|
# are still not finished.
|
||||||
|
|
||||||
# also since we are making speed cutting, if a video resolution (say 720 and 360) has been ffmpeg copied by the
|
|
||||||
# original file, it has specificy information as I-frames. Now the original file was trimmed too. So now if we attempt
|
|
||||||
# to trim it for a missing resolution (eg 240), it will pick different I-frames and the result will be different
|
|
||||||
# while playing the video in HLS. Thus we need to re-encode the video for all resolutions to ensure they have the same information
|
|
||||||
profiles = EncodeProfile.objects.filter(active=True, extension='mp4', resolution__lte=media.video_height)
|
profiles = EncodeProfile.objects.filter(active=True, extension='mp4', resolution__lte=media.video_height)
|
||||||
media_encodings = EncodeProfile.objects.filter(
|
media_encodings = EncodeProfile.objects.filter(
|
||||||
encoding__in=media.encodings.filter(
|
encoding__in=media.encodings.filter(
|
||||||
@ -113,8 +123,8 @@ def pre_trim_actions(media):
|
|||||||
|
|
||||||
|
|
||||||
if picked:
|
if picked:
|
||||||
|
# by calling encode will re-encode all. The logic is explained above...
|
||||||
logger.info(f"Encoding media {media.friendly_token} will have to be performed for all profiles")
|
logger.info(f"Encoding media {media.friendly_token} will have to be performed for all profiles")
|
||||||
|
|
||||||
media.encode()
|
media.encode()
|
||||||
|
|
||||||
return True
|
return True
|
||||||
@ -244,6 +254,9 @@ def encode_media(
|
|||||||
"""Encode a media to given profile, using ffmpeg, storing progress"""
|
"""Encode a media to given profile, using ffmpeg, storing progress"""
|
||||||
|
|
||||||
logger.info(f"encode_media for {friendly_token}/{profile_id}/{encoding_id}/{force}/{chunk}")
|
logger.info(f"encode_media for {friendly_token}/{profile_id}/{encoding_id}/{force}/{chunk}")
|
||||||
|
# TODO: this is new behavior, check whether it performs well. Before that check it would end up saving the Encoding
|
||||||
|
# at some point below. Now it exits the task. Could it be that before it would give it a chance to re-run? Or it was
|
||||||
|
# not being used at all?
|
||||||
if not Encoding.objects.filter(id=encoding_id).exists():
|
if not Encoding.objects.filter(id=encoding_id).exists():
|
||||||
logger.info(f"Exiting for {friendly_token}/{profile_id}/{encoding_id}/{force} since encoding id not found")
|
logger.info(f"Exiting for {friendly_token}/{profile_id}/{encoding_id}/{force} since encoding id not found")
|
||||||
return False
|
return False
|
||||||
@ -390,6 +403,9 @@ def encode_media(
|
|||||||
logger.info("Saved {0}".format(round(percent, 2)))
|
logger.info("Saved {0}".format(round(percent, 2)))
|
||||||
n_times += 1
|
n_times += 1
|
||||||
except DatabaseError:
|
except DatabaseError:
|
||||||
|
# primary reason for this is that the encoding has been deleted, because
|
||||||
|
# the media file was deleted, or also that there was a trim video request
|
||||||
|
# so it would be redundant to let it complete the encoding
|
||||||
kill_ffmpeg_process(encoding.temp_file)
|
kill_ffmpeg_process(encoding.temp_file)
|
||||||
kill_ffmpeg_process(encoding.chunk_file_path)
|
kill_ffmpeg_process(encoding.chunk_file_path)
|
||||||
return False
|
return False
|
||||||
@ -539,7 +555,6 @@ def create_hls(friendly_token):
|
|||||||
if os.path.exists(pp):
|
if os.path.exists(pp):
|
||||||
if media.hls_file != pp:
|
if media.hls_file != pp:
|
||||||
Media.objects.filter(pk=media.pk).update(hls_file=pp)
|
Media.objects.filter(pk=media.pk).update(hls_file=pp)
|
||||||
hlsfile = Media.objects.filter(pk=media.pk).first().hls_file
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
@ -882,6 +897,8 @@ def update_encoding_size(encoding_id):
|
|||||||
|
|
||||||
@task(name="produce_video_chapters", queue="short_tasks")
|
@task(name="produce_video_chapters", queue="short_tasks")
|
||||||
def produce_video_chapters(chapter_id):
|
def produce_video_chapters(chapter_id):
|
||||||
|
# this is not used
|
||||||
|
return False
|
||||||
chapter_object = VideoChapterData.objects.filter(id=chapter_id).first()
|
chapter_object = VideoChapterData.objects.filter(id=chapter_id).first()
|
||||||
if not chapter_object:
|
if not chapter_object:
|
||||||
return False
|
return False
|
||||||
@ -999,14 +1016,15 @@ def video_trim_task(self, trim_request_id):
|
|||||||
trim_request.media = new_media
|
trim_request.media = new_media
|
||||||
trim_request.save(update_fields=["media"])
|
trim_request.save(update_fields=["media"])
|
||||||
|
|
||||||
# processing timestamps differently on encodings and original file, since they have different I-frames
|
# processing timestamps differently on encodings and original file, in case we do accuracy trimming (currently not)
|
||||||
# the cut is made based on the I-frames
|
# these have different I-frames and the cut is made based on the I-frames
|
||||||
|
|
||||||
original_trim_result = trim_video_method(target_media.media_file.path, timestamps_original)
|
original_trim_result = trim_video_method(target_media.media_file.path, timestamps_original)
|
||||||
if not original_trim_result:
|
if not original_trim_result:
|
||||||
logger.info(f"Failed to trim original file for media {target_media.friendly_token}")
|
logger.info(f"Failed to trim original file for media {target_media.friendly_token}")
|
||||||
|
|
||||||
deleted_encodings = handle_pending_running_encodings(target_media)
|
deleted_encodings = handle_pending_running_encodings(target_media)
|
||||||
|
# the following could be un-necessary, read commend in pre_trim_video_actions to see why
|
||||||
encodings = target_media.encodings.filter(status="success", profile__extension='mp4', chunk=False)
|
encodings = target_media.encodings.filter(status="success", profile__extension='mp4', chunk=False)
|
||||||
for encoding in encodings:
|
for encoding in encodings:
|
||||||
trim_result = trim_video_method(encoding.media_file.path, timestamps_encodings)
|
trim_result = trim_video_method(encoding.media_file.path, timestamps_encodings)
|
||||||
@ -1014,7 +1032,7 @@ def video_trim_task(self, trim_request_id):
|
|||||||
logger.info(f"Failed to trim encoding {encoding.id} for media {target_media.friendly_token}")
|
logger.info(f"Failed to trim encoding {encoding.id} for media {target_media.friendly_token}")
|
||||||
encoding.delete()
|
encoding.delete()
|
||||||
|
|
||||||
pre_trim_actions(target_media)
|
pre_trim_video_actions(target_media)
|
||||||
post_trim_action.delay(target_media.friendly_token)
|
post_trim_action.delay(target_media.friendly_token)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
@ -1034,6 +1052,7 @@ def video_trim_task(self, trim_request_id):
|
|||||||
|
|
||||||
original_trim_result = trim_video_method(target_media.media_file.path, [timestamp])
|
original_trim_result = trim_video_method(target_media.media_file.path, [timestamp])
|
||||||
deleted_encodings = handle_pending_running_encodings(target_media)
|
deleted_encodings = handle_pending_running_encodings(target_media)
|
||||||
|
# the following could be un-necessary, read commend in pre_trim_video_actions to see why
|
||||||
encodings = target_media.encodings.filter(status="success", profile__extension='mp4', chunk=False)
|
encodings = target_media.encodings.filter(status="success", profile__extension='mp4', chunk=False)
|
||||||
for encoding in encodings:
|
for encoding in encodings:
|
||||||
trim_result = trim_video_method(encoding.media_file.path, [timestamp])
|
trim_result = trim_video_method(encoding.media_file.path, [timestamp])
|
||||||
@ -1041,7 +1060,7 @@ def video_trim_task(self, trim_request_id):
|
|||||||
logger.info(f"Failed to trim encoding {encoding.id} for media {target_media.friendly_token}")
|
logger.info(f"Failed to trim encoding {encoding.id} for media {target_media.friendly_token}")
|
||||||
encoding.delete()
|
encoding.delete()
|
||||||
|
|
||||||
pre_trim_actions(target_media)
|
pre_trim_video_actions(target_media)
|
||||||
post_trim_action.delay(target_media.friendly_token)
|
post_trim_action.delay(target_media.friendly_token)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -246,6 +246,8 @@ def history(request):
|
|||||||
@csrf_exempt
|
@csrf_exempt
|
||||||
@login_required
|
@login_required
|
||||||
def video_chapters(request, friendly_token):
|
def video_chapters(request, friendly_token):
|
||||||
|
# this is not ready...
|
||||||
|
return False
|
||||||
if not request.method == "POST":
|
if not request.method == "POST":
|
||||||
return HttpResponseRedirect("/")
|
return HttpResponseRedirect("/")
|
||||||
|
|
||||||
@ -354,7 +356,8 @@ def publish_media(request):
|
|||||||
@login_required
|
@login_required
|
||||||
def edit_chapters(request):
|
def edit_chapters(request):
|
||||||
"""Edit chapters"""
|
"""Edit chapters"""
|
||||||
|
# not implemented yet
|
||||||
|
return False
|
||||||
friendly_token = request.GET.get("m", "").strip()
|
friendly_token = request.GET.get("m", "").strip()
|
||||||
if not friendly_token:
|
if not friendly_token:
|
||||||
return HttpResponseRedirect("/")
|
return HttpResponseRedirect("/")
|
||||||
@ -614,7 +617,8 @@ def view_media(request):
|
|||||||
context["CAN_EDIT_MEDIA"] = True
|
context["CAN_EDIT_MEDIA"] = True
|
||||||
context["CAN_DELETE_COMMENTS"] = True
|
context["CAN_DELETE_COMMENTS"] = True
|
||||||
|
|
||||||
# TODO: explaim
|
# in case media is video and is processing (eg the case a video was just uploaded)
|
||||||
|
# attempt to show it (rather than showing a blank video player)
|
||||||
if media.media_type == 'video':
|
if media.media_type == 'video':
|
||||||
video_msg = None
|
video_msg = None
|
||||||
if media.encoding_status == "pending":
|
if media.encoding_status == "pending":
|
||||||
|
|||||||
@ -7,7 +7,6 @@ A modern browser-based video editing tool built with React and TypeScript that i
|
|||||||
- ⏱️ Trim video start and end points
|
- ⏱️ Trim video start and end points
|
||||||
- ✂️ Split videos into multiple segments
|
- ✂️ Split videos into multiple segments
|
||||||
- 👁️ Preview individual segments or the full edited video
|
- 👁️ Preview individual segments or the full edited video
|
||||||
- 🔍 Zoom timeline for precise editing
|
|
||||||
- 🔄 Undo/redo support for all editing operations
|
- 🔄 Undo/redo support for all editing operations
|
||||||
- 🔊 Audio mute controls
|
- 🔊 Audio mute controls
|
||||||
- 💾 Save edits directly to MediaCMS
|
- 💾 Save edits directly to MediaCMS
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
@ -9,8 +9,7 @@
|
|||||||
<link href="{% static "video_editor/video-editor.css" %}" rel="stylesheet">
|
<link href="{% static "video_editor/video-editor.css" %}" rel="stylesheet">
|
||||||
<script>
|
<script>
|
||||||
window.MEDIA_DATA = {
|
window.MEDIA_DATA = {
|
||||||
// videoUrl: "{{ media_file_path }}", // "http://temp.web357.com/SampleVideo_1280x720_30mb.mp4",
|
videoUrl: "",
|
||||||
videoUrl: "http://temp.web357.com/SampleVideo_1280x720_30mb.mp4",
|
|
||||||
mediaId: "{{ media_id }}",
|
mediaId: "{{ media_id }}",
|
||||||
chapters: [
|
chapters: [
|
||||||
{
|
{
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user