Files
resourcespace/plugins/video_splice/include/splice_functions.php
2025-07-18 16:20:14 +07:00

192 lines
9.6 KiB
PHP
Executable File

<?php
use Montala\ResourceSpace\CommandPlaceholderArg;
function generate_merged_video($videos, $video_splice_type, $target_video_command, $target_video_extension, $target_audio_command, $target_width, $target_height, $target_frame_rate, $description, $auto_populate_video_info, $offline)
{
include_once __DIR__ . "/../../../include/image_processing.php";
global $ffmpeg_global_options, $videosplice_resourcetype, $videosplice_description_field, $videosplice_video_bitrate_field, $videosplice_video_size_field, $videosplice_frame_rate_field, $videosplice_aspect_ratio_field, $username, $scramble_key, $ffmpeg_std_video_options, $ffmpeg_std_audio_options, $ffmpeg_std_frame_rate_options, $video_export_folder, $download_chunk_size, $userref, $date_field, $lang;
// Grab ffmpeg and ffprobe paths
$ffmpeg_fullpath = get_utility_path("ffmpeg");
$ffprobe_fullpath = get_utility_path("ffprobe");
// Generate random string for filename usage
$randstring=md5(rand() . microtime());
$target_order_count = 0;
$target_completed_locations = array();
$video_refs = array_column($videos, 'ref');
// Generate temp location to do re encoding, adding blank audio and splicing
$target_temp_location = get_temp_dir(false,"splice/" . implode("-", $video_refs) . "_" . md5($username . $randstring . $scramble_key));
// Loop through original videos creating new ones encoding as per options
foreach ($videos as $video) {
$source_video_file = get_resource_path($video["ref"],true,"",false,get_resource_data($video["ref"])["file_extension"]);
$shell_exec_cmd = $ffprobe_fullpath . " -i [SOURCE_VIDEO_FILE] -show_streams -select_streams a -loglevel error";
$shell_exec_params = [
'[SOURCE_VIDEO_FILE]' => new CommandPlaceholderArg($source_video_file, 'is_valid_rs_path'),
];
$audiocheck = run_command($shell_exec_cmd, false, $shell_exec_params);
$has_no_audio = empty($audiocheck) ? "_noaudio" : "";
$target_completed_location = $target_temp_location . "/" . $target_order_count . $has_no_audio . "." . $target_video_extension;
// SECURITY NOTE: The target_video_command and target_audio_command are not escaped as they come from config and contain multiple option/value combinations thus cannot be escaped
$validcommandregex = '/^[a-zA-Z0-9\s\-:]*$/';
if (!preg_match($validcommandregex, $target_video_command)) {
debug("Invalid video command: " . $target_video_command);
return false;
}
if (!preg_match($validcommandregex, $target_audio_command)) {
debug("Invalid audio command: " . $target_audio_command);
return false;
}
$video_splice_options = ' -f ' . $target_video_command . " " . $target_audio_command . ' -vf "fps=[TARGET_FRAME_RATE],scale=[TARGET_WIDTH]:[TARGET_HEIGHT]:force_original_aspect_ratio=decrease,pad=[TARGET_WIDTH]:[TARGET_HEIGHT]:(ow-iw)/2:(oh-ih)/2" -sws_flags lanczos';
$video_splice_command = $ffmpeg_fullpath . " " . $ffmpeg_global_options . " -i [SOURCE_VIDEO_FILE] " . $video_splice_options . " [TARGET_COMPLETED_LOCATION]";
$video_splice_params = [
'[SOURCE_VIDEO_FILE]' => new CommandPlaceholderArg($source_video_file, 'is_valid_rs_path'),
'[TARGET_FRAME_RATE]' => new CommandPlaceholderArg($target_frame_rate, 'is_numeric'),
'[TARGET_WIDTH]' => (int) $target_width,
'[TARGET_HEIGHT]' => (int) $target_height,
'[TARGET_COMPLETED_LOCATION]' => new CommandPlaceholderArg($target_completed_location, 'is_valid_rs_path'),
];
run_command($video_splice_command, true, $video_splice_params);
// If file has no audio channels create blank audio channel to ensure all video files contain audio thus wont loose audio on videos that have it
if (empty($audiocheck)) {
$no_audio_command = $ffmpeg_fullpath . " -i [NO_AUDIO_LOCATION] -f lavfi -i anullsrc -vcodec copy " . $target_audio_command . " -shortest [WITH_AUDIO_LOCATION]";
$with_audio_location = str_replace("_noaudio", "", $target_completed_location);
$no_audio_params = [
'[NO_AUDIO_LOCATION]' => new CommandPlaceholderArg($target_completed_location, 'is_valid_rs_path'),
'[WITH_AUDIO_LOCATION]' => new CommandPlaceholderArg($with_audio_location, 'is_valid_rs_path'),
];
run_command($no_audio_command, false, $no_audio_params);
unlink($target_completed_location);
$target_completed_location = $with_audio_location;
}
$target_completed_locations[] = $target_completed_location;
$target_order_count++;
}
$list_file_command = "";
foreach ($target_completed_locations as $target_completed_location) {
// Build list file contents (easiest way to merge videos)
$list_file_command .= "file '" . $target_completed_location . "'\n";
}
file_put_contents($target_temp_location . "/list.txt", $list_file_command);
// Merge video files using list file
$mergedfile = $target_temp_location . "/merged." . $target_video_extension;
$merge_command = $ffmpeg_fullpath . " -f concat -safe 0 -i [LIST_LOCATION] -c copy [OUTPUT_FILE]";
$merge_params = [
'[LIST_LOCATION]' => new CommandPlaceholderArg($target_temp_location . "/list.txt", 'is_valid_rs_path'),
'[OUTPUT_FILE]' => new CommandPlaceholderArg($mergedfile, 'is_valid_rs_path'),
];
run_command($merge_command, false, $merge_params);
// Tidy up as we go along now final file created
unlink($target_temp_location . "/list.txt");
foreach ($target_completed_locations as $target_completed_location) {
unlink($target_completed_location);
}
if ($video_splice_type == "video_splice_save_new") {
// Create new blank resource with the type specified in the config
$ref = create_resource($videosplice_resourcetype, 0,-1,$lang["video_splice_createdfromvideosplice"]);
$resource_location = get_resource_path(
$ref,
true,
"",
true,
$target_video_extension,
-1,
1,
false,
"",
-1,
false
);
// Place new merged file in resource location
rename($mergedfile, $resource_location);
rmdir($target_temp_location);
create_previews($ref,false,$target_video_extension);
// Add file extension to db so front end recognises the file
ps_query("update resource set file_extension=? where ref=?",array("s",$target_video_extension,"i",$ref));
// Add current date to date field
update_field($ref, $date_field, date("Y-m-d H:i:s"));
// If description provided add it
if (!empty($description)) {
update_field($ref, $videosplice_description_field, $description);
}
// If user wants video information auto populated then add it
if ($auto_populate_video_info) {
update_field($ref, $videosplice_video_bitrate_field, array_keys($ffmpeg_std_video_options)[array_search($target_video_command, array_column($ffmpeg_std_video_options, 'command'))]);
update_field($ref, $videosplice_video_size_field, $target_width . "x" . $target_height);
update_field($ref, $videosplice_frame_rate_field, array_keys($ffmpeg_std_frame_rate_options)[array_search($target_frame_rate, array_column($ffmpeg_std_frame_rate_options, 'value'))]);
// Quick way to figure out standard ratios
$ratio = substr($target_width > $target_height?$target_width / $target_height:$target_height / $target_width, 0, 3);
switch ($ratio) {
case 1.3:
$aspect_ratio = "4:3";
break;
case 1.6:
$aspect_ratio = "16:10";
break;
case 1.7:
$aspect_ratio = "16:9";
break;
}
if (isset($aspect_ratio)) {
update_field($ref, $videosplice_aspect_ratio_field, $aspect_ratio);
}
}
return $ref;
} elseif ($video_splice_type == "video_splice_download") {
// Move to download directory
$download_filename = $userref . "_" . md5($username . $randstring . $scramble_key) . "." . $target_video_extension;
$download_file_location = get_temp_dir(false,"user_downloads") . DIRECTORY_SEPARATOR . $download_filename;
rename($mergedfile, $download_file_location);
rmdir($target_temp_location);
if ($offline) {
// For offline we want to return the location of the file so it can be added as a link in the user message
return $userref . "_" . $randstring . "." . $target_video_extension;
} else {
// Download file
$filesize = filesize_unlimited($download_file_location);
ob_flush();
header(sprintf('Content-Disposition: attachment; filename="%s"', $download_filename));
header("Content-Length: " . $filesize);
set_time_limit(0);
$sent = 0;
$handle = fopen($download_file_location, "r");
// Now we need to loop through the file and echo out chunks of file data
while ($sent < $filesize) {
echo fread($handle, $download_chunk_size);
ob_flush();
$sent += $download_chunk_size;
}
// Delete File
fclose($handle);
unlink($download_file_location);
}
return true;
}
// if one of the above options has not worked then return false
return false;
}