diff --git a/files/methods.py b/files/methods.py
index f579d256..11667c9d 100644
--- a/files/methods.py
+++ b/files/methods.py
@@ -14,7 +14,6 @@ from django.core.files import File
from django.core.mail import EmailMessage
from django.db.models import Q
from django.utils import timezone
-from contextlib import contextmanager
from cms import celery_app
@@ -23,15 +22,6 @@ from .helpers import mask_ip
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):
"""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()
)
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:
@@ -477,7 +467,7 @@ def copy_video(original_media, copy_encodings=True, title_suffix="(Trimmed)"):
logs=f"Copied from encoding {encoding.id}"
)
models.Encoding.objects.bulk_create([new_encoding])
- # avoids calling signals
+ # avoids calling signals as this is still not ready
# Copy categories and tags
for category in original_media.category.all():
diff --git a/files/models.py b/files/models.py
index 61dc0746..fae9445d 100644
--- a/files/models.py
+++ b/files/models.py
@@ -526,6 +526,8 @@ class Media(models.Model):
with open(self.media_file.path, "rb") as f:
myfile = File(f)
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.poster.save(content=myfile, name=thumbnail_name, save=False)
self.save(update_fields=["thumbnail", "poster"])
@@ -563,6 +565,8 @@ class Media(models.Model):
with open(tf, "rb") as f:
myfile = File(f)
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.poster.save(content=myfile, name=thumbnail_name, save=False)
self.save(update_fields=["thumbnail", "poster"])
diff --git a/files/tasks.py b/files/tasks.py
index 9aef1744..7529bfd3 100644
--- a/files/tasks.py
+++ b/files/tasks.py
@@ -62,9 +62,11 @@ ERRORS_LIST = [
def handle_pending_running_encodings(media):
"""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
- and has succeeded, so we can keep them (they will be trimmed). However for encodings that are in pending
- or running phase,
+ 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) or if we dont keep them we dont have to delete them
+ here
+
+ However for encodings that are in pending or running phase, just delete them
Args:
media: The media object to handle encodings for
@@ -86,15 +88,23 @@ def handle_pending_running_encodings(media):
-def pre_trim_actions(media):
- # 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
+def pre_trim_video_actions(media):
+ # the reason for this function is to perform tasks before trimming a video
+
+ # 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)
media_encodings = EncodeProfile.objects.filter(
encoding__in=media.encodings.filter(
@@ -113,8 +123,8 @@ def pre_trim_actions(media):
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")
-
media.encode()
return True
@@ -244,6 +254,9 @@ def encode_media(
"""Encode a media to given profile, using ffmpeg, storing progress"""
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():
logger.info(f"Exiting for {friendly_token}/{profile_id}/{encoding_id}/{force} since encoding id not found")
return False
@@ -390,6 +403,9 @@ def encode_media(
logger.info("Saved {0}".format(round(percent, 2)))
n_times += 1
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.chunk_file_path)
return False
@@ -539,7 +555,6 @@ def create_hls(friendly_token):
if os.path.exists(pp):
if media.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
@@ -882,6 +897,8 @@ def update_encoding_size(encoding_id):
@task(name="produce_video_chapters", queue="short_tasks")
def produce_video_chapters(chapter_id):
+ # this is not used
+ return False
chapter_object = VideoChapterData.objects.filter(id=chapter_id).first()
if not chapter_object:
return False
@@ -999,14 +1016,15 @@ def video_trim_task(self, trim_request_id):
trim_request.media = new_media
trim_request.save(update_fields=["media"])
- # processing timestamps differently on encodings and original file, since they have different I-frames
- # the cut is made based on the I-frames
+ # processing timestamps differently on encodings and original file, in case we do accuracy trimming (currently not)
+ # 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)
if not original_trim_result:
logger.info(f"Failed to trim original file for media {target_media.friendly_token}")
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)
for encoding in 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}")
encoding.delete()
- pre_trim_actions(target_media)
+ pre_trim_video_actions(target_media)
post_trim_action.delay(target_media.friendly_token)
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])
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)
for encoding in encodings:
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}")
encoding.delete()
- pre_trim_actions(target_media)
+ pre_trim_video_actions(target_media)
post_trim_action.delay(target_media.friendly_token)
diff --git a/files/views.py b/files/views.py
index 363c2651..3555580a 100644
--- a/files/views.py
+++ b/files/views.py
@@ -246,6 +246,8 @@ def history(request):
@csrf_exempt
@login_required
def video_chapters(request, friendly_token):
+ # this is not ready...
+ return False
if not request.method == "POST":
return HttpResponseRedirect("/")
@@ -354,7 +356,8 @@ def publish_media(request):
@login_required
def edit_chapters(request):
"""Edit chapters"""
-
+ # not implemented yet
+ return False
friendly_token = request.GET.get("m", "").strip()
if not friendly_token:
return HttpResponseRedirect("/")
@@ -614,7 +617,8 @@ def view_media(request):
context["CAN_EDIT_MEDIA"] = 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':
video_msg = None
if media.encoding_status == "pending":
diff --git a/frontend-tools/video-editor/README.md b/frontend-tools/video-editor/README.md
index 8418289b..ddb91455 100644
--- a/frontend-tools/video-editor/README.md
+++ b/frontend-tools/video-editor/README.md
@@ -7,7 +7,6 @@ A modern browser-based video editing tool built with React and TypeScript that i
- ⏱️ Trim video start and end points
- ✂️ Split videos into multiple segments
- 👁️ Preview individual segments or the full edited video
-- 🔍 Zoom timeline for precise editing
- 🔄 Undo/redo support for all editing operations
- 🔊 Audio mute controls
- 💾 Save edits directly to MediaCMS
diff --git a/frontend-tools/video-editor/client/public/videos/sample-video-30s.mp4 b/frontend-tools/video-editor/client/public/videos/sample-video-30s.mp4
deleted file mode 100644
index 851c175c..00000000
Binary files a/frontend-tools/video-editor/client/public/videos/sample-video-30s.mp4 and /dev/null differ
diff --git a/frontend-tools/video-editor/client/public/videos/sample-video-37s.mp4 b/frontend-tools/video-editor/client/public/videos/sample-video-37s.mp4
deleted file mode 100644
index 819250e7..00000000
Binary files a/frontend-tools/video-editor/client/public/videos/sample-video-37s.mp4 and /dev/null differ
diff --git a/templates/cms/edit_chapters.html b/templates/cms/edit_chapters.html
index 31c9829f..c9c6c557 100644
--- a/templates/cms/edit_chapters.html
+++ b/templates/cms/edit_chapters.html
@@ -9,8 +9,7 @@