1
0
mirror of https://github.com/algora-io/tv.git synced 2024-11-16 00:58:59 +02:00

perf(clipper): download segments instead of full video (#114)

This commit is contained in:
Zafer Cesur 2024-10-28 20:21:00 +02:00 committed by GitHub
parent d81e457b61
commit c4ab44ebe9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -60,7 +60,7 @@ defmodule Algora.Clipper do
%{playlist: %{playlists.video | timeline: timeline}, ss: ss}
end
def create_clip(video, from, to) do
def trim_manifest(video, from, to) do
uuid = Ecto.UUID.generate()
%{playlist: playlist, ss: ss} = clip(video, from, to)
@ -81,48 +81,93 @@ defmodule Algora.Clipper do
)
|> ExAws.request()
url = Storage.to_absolute(:clip, uuid, "index.m3u8")
%{url: Storage.to_absolute(:clip, uuid, "index.m3u8"), ss: ss}
end
def create_clip(video, from, to) do
%{url: url, ss: ss} = trim_manifest(video, from, to)
filename = Slug.slugify("#{video.title}-#{Library.to_hhmmss(from)}-#{Library.to_hhmmss(to)}")
"ffmpeg -i \"#{url}\" -ss #{ss} -t #{to - from} \"#{filename}.mp4\""
end
def create_combined_local_clips(video, clips_params) do
# Generate a unique filename for the combined clip
# Generate base filename for the combined clip
filename = generate_combined_clip_filename(video, clips_params)
output_path = Path.join(System.tmp_dir(), "#{filename}.mp4")
final_output_path = Path.join(System.tmp_dir(), "#{filename}.mp4")
# Create a temporary file for the complex filter
filter_path = Path.join(System.tmp_dir(), "#{filename}_filter.txt")
File.write!(filter_path, create_filter_complex(clips_params))
# Download individual clips
clip_paths =
clips_params
|> Enum.sort_by(fn {key, _} -> key end)
|> Enum.map(fn {_, clip} ->
from = Library.from_hhmmss(clip["clip_from"])
to = Library.from_hhmmss(clip["clip_to"])
# Construct the FFmpeg command
ffmpeg_cmd = [
"-y",
"-i",
video.url,
"-filter_complex_script",
filter_path,
"-map",
"[v]",
"-map",
"[a]",
"-c:v",
"libx264",
"-c:a",
"aac",
output_path
]
# Get trimmed manifest for this clip
%{url: url, ss: ss} = trim_manifest(video, from, to)
temp_path = Path.join(System.tmp_dir(), "#{filename}_part#{System.unique_integer()}.mp4")
# Execute the FFmpeg command
case System.cmd("ffmpeg", ffmpeg_cmd, stderr_to_stdout: true) do
{_, 0} ->
File.rm(filter_path)
{:ok, output_path}
# Download this clip segment
ffmpeg_cmd = [
"-y",
"-i",
url,
# TODO: Use -ss in input position, see https://superuser.com/a/1845442
"-ss",
"#{ss}",
"-t",
"#{to - from}",
temp_path
]
{error, _} ->
File.rm(filter_path)
{:error, "FFmpeg error: #{error}"}
case System.cmd("ffmpeg", ffmpeg_cmd, stderr_to_stdout: true) do
{_, 0} -> {:ok, temp_path}
{error, _} -> {:error, "FFmpeg error downloading clip: #{error}"}
end
end)
# Check if all clips downloaded successfully
case Enum.all?(clip_paths, &match?({:ok, _}, &1)) do
true ->
clip_paths = Enum.map(clip_paths, fn {:ok, path} -> path end)
# Create concat file
concat_file = Path.join(System.tmp_dir(), "#{filename}_concat.txt")
concat_content = Enum.map_join(clip_paths, "\n", &"file '#{&1}'")
File.write!(concat_file, concat_content)
# Concatenate all clips
concat_cmd = [
"-y",
"-f",
"concat",
"-safe",
"0",
"-i",
concat_file,
"-c",
"copy",
final_output_path
]
case System.cmd("ffmpeg", concat_cmd, stderr_to_stdout: true) do
{_, 0} ->
# Cleanup temporary files
File.rm(concat_file)
Enum.each(clip_paths, &File.rm/1)
{:ok, final_output_path}
{error, _} ->
# Cleanup on error
File.rm(concat_file)
Enum.each(clip_paths, &File.rm/1)
{:error, "FFmpeg error concatenating: #{error}"}
end
false ->
# If any clip failed to download, return the first error
{:error, Enum.find(clip_paths, &match?({:error, _}, &1))}
end
end
@ -138,32 +183,4 @@ defmodule Algora.Clipper do
Slug.slugify("#{video.title}-#{clip_count}clips-#{total_duration}s")
end
defp create_filter_complex(clips_params) do
{filter_complex, _} =
clips_params
|> Enum.sort_by(fn {key, _} -> key end)
|> Enum.reduce({"", 0}, fn {_, clip}, {acc, index} ->
from = Library.from_hhmmss(clip["clip_from"])
to = Library.from_hhmmss(clip["clip_to"])
clip_filter =
"[0:v]trim=start=#{from}:end=#{to},setpts=PTS-STARTPTS[v#{index}]; " <>
"[0:a]atrim=start=#{from}:end=#{to},asetpts=PTS-STARTPTS[a#{index}];\n"
{acc <> clip_filter, index + 1}
end)
clip_count = map_size(clips_params)
video_concat =
Enum.map_join(0..(clip_count - 1), "", fn i -> "[v#{i}]" end) <>
"concat=n=#{clip_count}:v=1:a=0[v];\n"
audio_concat =
Enum.map_join(0..(clip_count - 1), "", fn i -> "[a#{i}]" end) <>
"concat=n=#{clip_count}:v=0:a=1[a]"
filter_complex <> video_concat <> audio_concat
end
end