mirror of
https://github.com/mediacms-io/mediacms.git
synced 2025-10-06 22:32:41 +07:00
feat: Video Trimmer and more
This commit is contained in:
committed by
GitHub
parent
ab4d9d67df
commit
a833b606f1
+155
-2
@@ -17,6 +17,8 @@ from django.conf import settings
|
||||
from django.core.cache import cache
|
||||
from django.core.files import File
|
||||
from django.db.models import Q
|
||||
from django.db.models.signals import post_save
|
||||
from contextlib import contextmanager
|
||||
|
||||
from actions.models import USER_MEDIA_ACTIONS, MediaAction
|
||||
from users.models import User
|
||||
@@ -28,14 +30,27 @@ from .helpers import (
|
||||
create_temp_file,
|
||||
get_file_name,
|
||||
get_file_type,
|
||||
get_trim_timestamps,
|
||||
media_file_info,
|
||||
produce_ffmpeg_commands,
|
||||
produce_friendly_token,
|
||||
rm_file,
|
||||
run_command,
|
||||
trim_video_method,
|
||||
)
|
||||
from . import helpers
|
||||
from .methods import list_tasks, notify_users, pre_save_action, copy_video
|
||||
from .models import (
|
||||
Category,
|
||||
EncodeProfile,
|
||||
Encoding,
|
||||
Media,
|
||||
Rating,
|
||||
Tag,
|
||||
VideoChapterData,
|
||||
VideoTrimRequest,
|
||||
media_save
|
||||
)
|
||||
from .methods import list_tasks, notify_users, pre_save_action
|
||||
from .models import Category, EncodeProfile, Encoding, Media, Rating, Tag
|
||||
|
||||
logger = get_task_logger(__name__)
|
||||
|
||||
@@ -48,6 +63,15 @@ ERRORS_LIST = [
|
||||
]
|
||||
|
||||
|
||||
@contextmanager
|
||||
def disable_signal(signal, receiver, sender):
|
||||
signal.disconnect(receiver, sender=sender)
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
signal.connect(receiver, sender=sender)
|
||||
|
||||
|
||||
@task(name="chunkize_media", bind=True, queue="short_tasks", soft_time_limit=60 * 30 * 4)
|
||||
def chunkize_media(self, friendly_token, profiles, force=True):
|
||||
"""Break media in chunks and start encoding tasks"""
|
||||
@@ -793,6 +817,135 @@ def remove_media_file(media_file=None):
|
||||
return True
|
||||
|
||||
|
||||
@task(name="produce_video_chapters", queue="short_tasks")
|
||||
def produce_video_chapters(chapter_id):
|
||||
chapter_object = VideoChapterData.objects.filter(id=chapter_id).first()
|
||||
if not chapter_object:
|
||||
return False
|
||||
|
||||
media = chapter_object.media
|
||||
video_path = media.media_file.path
|
||||
output_folder = media.video_chapters_folder
|
||||
|
||||
chapters = chapter_object.data
|
||||
|
||||
width = 336
|
||||
height = 188
|
||||
|
||||
if not os.path.exists(output_folder):
|
||||
os.makedirs(output_folder)
|
||||
|
||||
results = []
|
||||
|
||||
for i, chapter in enumerate(chapters):
|
||||
timestamp = chapter["start"]
|
||||
title = chapter["title"]
|
||||
|
||||
output_filename = f"thumbnail_{i:02d}.jpg"
|
||||
output_path = os.path.join(output_folder, output_filename)
|
||||
|
||||
command = [settings.FFMPEG_COMMAND, "-y", "-ss", str(timestamp), "-i", video_path, "-vframes", "1", "-q:v", "2", "-s", f"{width}x{height}", output_path]
|
||||
ret = run_command(command) # noqa
|
||||
if os.path.exists(output_path) and get_file_type(output_path) == "image":
|
||||
results.append({"start": timestamp, "title": title, "thumbnail": output_path})
|
||||
|
||||
chapter_object.data = results
|
||||
chapter_object.save(update_fields=["data"])
|
||||
return True
|
||||
|
||||
|
||||
@task(name="video_trim_task", bind=True, queue="short_tasks")
|
||||
def video_trim_task(self, trim_request_id):
|
||||
# SOS: if at some point we move from ffmpeg copy, then this need be changed
|
||||
# to long_tasks
|
||||
try:
|
||||
trim_request = VideoTrimRequest.objects.get(id=trim_request_id)
|
||||
except VideoTrimRequest.DoesNotExist:
|
||||
logger.info(f"VideoTrimRequest with ID {trim_request_id} not found")
|
||||
return False
|
||||
|
||||
trim_request.status = "running"
|
||||
trim_request.save(update_fields=["status"])
|
||||
|
||||
timestamps_encodings = get_trim_timestamps(trim_request.media.trim_video_path, trim_request.timestamps)
|
||||
timestamps_original = get_trim_timestamps(trim_request.media.media_file.path, trim_request.timestamps)
|
||||
|
||||
if not timestamps_encodings:
|
||||
trim_request.status = "fail"
|
||||
trim_request.save(update_fields=["status"])
|
||||
return False
|
||||
|
||||
target_media = trim_request.media
|
||||
original_media = trim_request.media
|
||||
|
||||
# splitting the logic for single file and multiple files
|
||||
if trim_request.video_action in ["save_new", "replace"]:
|
||||
proceed_with_single_file = True
|
||||
if trim_request.video_action == "create_segments":
|
||||
if len(timestamps_encodings) == 1:
|
||||
proceed_with_single_file = True
|
||||
else:
|
||||
proceed_with_single_file = False
|
||||
|
||||
|
||||
if proceed_with_single_file:
|
||||
|
||||
if trim_request.video_action == "save_new" or trim_request.video_action == "create_segments" and len(timestamps_encodings) == 1:
|
||||
with disable_signal(post_save, media_save, Media):
|
||||
new_media = copy_video(original_media, copy_encodings=True)
|
||||
|
||||
target_media = new_media
|
||||
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
|
||||
|
||||
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)
|
||||
if not trim_result:
|
||||
logger.info(f"Failed to trim encoding {encoding.id} for media {target_media.friendly_token}")
|
||||
|
||||
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}")
|
||||
|
||||
target_media.set_media_type()
|
||||
create_hls(target_media.friendly_token)
|
||||
|
||||
trim_request.status = "success"
|
||||
trim_request.save(update_fields=["status"])
|
||||
logger.info(f"Successfully processed video trim request {trim_request_id} for media {target_media.friendly_token}")
|
||||
|
||||
target_media.produce_thumbnails_from_video()
|
||||
target_media.produce_sprite_from_video()
|
||||
target_media.update_search_vector()
|
||||
|
||||
else:
|
||||
for i, timestamp in enumerate(timestamps_encodings, start=1):
|
||||
with disable_signal(post_save, media_save, Media):
|
||||
new_media = copy_video(original_media, title_suffix=f"(Trimmed) {i}", copy_encodings=True)
|
||||
|
||||
original_trim_result = trim_video_method(new_media.media_file.path, [timestamp])
|
||||
encodings = new_media.encodings.filter(status="success", profile__extension='mp4', chunk=False)
|
||||
for encoding in encodings:
|
||||
trim_result = trim_video_method(encoding.media_file.path, [timestamp])
|
||||
|
||||
new_media.set_media_type()
|
||||
create_hls(new_media.friendly_token)
|
||||
|
||||
new_media.produce_thumbnails_from_video()
|
||||
new_media.produce_sprite_from_video()
|
||||
new_media.update_search_vector()
|
||||
|
||||
trim_request.status = "success"
|
||||
trim_request.save(update_fields=["status"])
|
||||
logger.info(f"Successfully processed video trim request {trim_request_id} for media {target_media.friendly_token}")
|
||||
|
||||
return True
|
||||
|
||||
|
||||
# TODO LIST
|
||||
# 1 chunks are deleted from original server when file is fully encoded.
|
||||
# however need to enter this logic in cases of fail as well
|
||||
|
||||
Reference in New Issue
Block a user