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

mix format

This commit is contained in:
zafer 2024-10-28 19:44:32 +02:00
parent fcef5653ee
commit d81e457b61
2 changed files with 307 additions and 189 deletions

View File

@ -12,7 +12,8 @@ defmodule Algora.Clipper do
%{ %{
acc acc
| timeline: [ | timeline: [
%ExM3U8.Tags.MediaInit{uri: Storage.to_absolute(:video, video.uuid, uri)} | acc.timeline %ExM3U8.Tags.MediaInit{uri: Storage.to_absolute(:video, video.uuid, uri)}
| acc.timeline
] ]
} }
@ -87,60 +88,82 @@ defmodule Algora.Clipper do
end end
def create_combined_local_clips(video, clips_params) do def create_combined_local_clips(video, clips_params) do
# Generate a unique filename for the combined clip # Generate a unique filename for the combined clip
filename = generate_combined_clip_filename(video, clips_params) filename = generate_combined_clip_filename(video, clips_params)
output_path = Path.join(System.tmp_dir(), "#{filename}.mp4") output_path = Path.join(System.tmp_dir(), "#{filename}.mp4")
# Create a temporary file for the complex filter # Create a temporary file for the complex filter
filter_path = Path.join(System.tmp_dir(), "#{filename}_filter.txt") filter_path = Path.join(System.tmp_dir(), "#{filename}_filter.txt")
File.write!(filter_path, create_filter_complex(clips_params)) File.write!(filter_path, create_filter_complex(clips_params))
# Construct the FFmpeg command # Construct the FFmpeg command
ffmpeg_cmd = [ ffmpeg_cmd = [
"-y", "-y",
"-i", video.url, "-i",
"-filter_complex_script", filter_path, video.url,
"-map", "[v]", "-filter_complex_script",
"-map", "[a]", filter_path,
"-c:v", "libx264", "-map",
"-c:a", "aac", "[v]",
output_path "-map",
] "[a]",
"-c:v",
"libx264",
"-c:a",
"aac",
output_path
]
# Execute the FFmpeg command # Execute the FFmpeg command
case System.cmd("ffmpeg", ffmpeg_cmd, stderr_to_stdout: true) do case System.cmd("ffmpeg", ffmpeg_cmd, stderr_to_stdout: true) do
{_, 0} -> {_, 0} ->
File.rm(filter_path) File.rm(filter_path)
{:ok, output_path} {:ok, output_path}
{error, _} ->
File.rm(filter_path) {error, _} ->
{:error, "FFmpeg error: #{error}"} File.rm(filter_path)
end {:error, "FFmpeg error: #{error}"}
end end
end
defp generate_combined_clip_filename(video, clips_params) do defp generate_combined_clip_filename(video, clips_params) do
clip_count = map_size(clips_params) clip_count = map_size(clips_params)
total_duration = Enum.sum(Enum.map(clips_params, fn {_, clip} ->
Library.from_hhmmss(clip["clip_to"]) - Library.from_hhmmss(clip["clip_from"])
end))
Slug.slugify("#{video.title}-#{clip_count}clips-#{total_duration}s")
end
defp create_filter_complex(clips_params) do total_duration =
{filter_complex, _} = clips_params Enum.sum(
Enum.map(clips_params, fn {_, clip} ->
Library.from_hhmmss(clip["clip_to"]) - Library.from_hhmmss(clip["clip_from"])
end)
)
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.sort_by(fn {key, _} -> key end)
|> Enum.reduce({"", 0}, fn {_, clip}, {acc, index} -> |> Enum.reduce({"", 0}, fn {_, clip}, {acc, index} ->
from = Library.from_hhmmss(clip["clip_from"]) from = Library.from_hhmmss(clip["clip_from"])
to = Library.from_hhmmss(clip["clip_to"]) 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" 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} {acc <> clip_filter, index + 1}
end) end)
clip_count = map_size(clips_params) 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 video_concat =
end 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 end

View File

@ -9,103 +9,165 @@ defmodule AlgoraWeb.VideoClipperLive do
def render(assigns) do def render(assigns) do
~H""" ~H"""
<div class="min-h-[90vh]"> <div class="min-h-[90vh]">
<div class="max-w-6xl mx-auto pt-2 pb-6 px-4 sm:px-6 space-y-6"> <div class="max-w-6xl mx-auto pt-2 pb-6 px-4 sm:px-6 space-y-6">
<h1 class="text-lg font-semibold leading-8 text-gray-100 focus:outline-none">Clip editor</h1> <h1 class="text-lg font-semibold leading-8 text-gray-100 focus:outline-none">Clip editor</h1>
<.simple_form for={@form} phx-change="update_form" phx-submit="create_video"> <.simple_form for={@form} phx-change="update_form" phx-submit="create_video">
<div class="grid grid-cols-1 md:grid-cols-2 gap-6"> <div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<div class="space-y-4"> <div class="space-y-4">
<div class="relative"> <div class="relative">
<.input <.input
field={@form[:livestream_id]} field={@form[:livestream_id]}
type="select" type="select"
options={Enum.map(@livestreams, &{&1.title, &1.id})} options={Enum.map(@livestreams, &{&1.title, &1.id})}
value={@selected_livestream && @selected_livestream.id} value={@selected_livestream && @selected_livestream.id}
prompt="Select livestream" prompt="Select livestream"
class="w-full bg-white/5 border border-white/15 rounded p-2 appearance-none" class="w-full bg-white/5 border border-white/15 rounded p-2 appearance-none"
/> />
<.error :for={error <- @form[:livestream_id].errors}> <.error :for={error <- @form[:livestream_id].errors}>
<%=error %> <%= error %>
</.error> </.error>
</div>
<div class="aspect-video bg-white/5 rounded flex items-center justify-center">
<%= if @selected_livestream do %>
<div id="preview-player-container" phx-update="ignore">
<.live_component
module={PlayerComponent}
id="preview-player"
video={@selected_livestream}
current_time={@preview_clip && @preview_clip.start || 0}
end_time={@preview_clip && @preview_clip.end || nil}
current_user={@current_user}
/>
</div>
<% else %>
<Heroicons.play solid class="w-16 h-16" />
<% end %>
</div>
<.input field={@form[:title]} type="text" label="Title" class="w-full bg-white/5 border border-white/15 rounded p-2" />
<.input field={@form[:description]} type="textarea" label="Description" class="w-full bg-white/5 border border-white/15 rounded p-2 h-24" />
</div> </div>
<div class="space-y-4 max-h-[calc(100vh-16rem)] overflow-y-auto p-2"> <div class="aspect-video bg-white/5 rounded flex items-center justify-center">
<%= if @selected_livestream do %>
<div id="preview-player-container" phx-update="ignore">
<.live_component
module={PlayerComponent}
id="preview-player"
video={@selected_livestream}
current_time={(@preview_clip && @preview_clip.start) || 0}
end_time={(@preview_clip && @preview_clip.end) || nil}
current_user={@current_user}
/>
</div>
<% else %>
<Heroicons.play solid class="w-16 h-16" />
<% end %>
</div>
<.input
field={@form[:title]}
type="text"
label="Title"
class="w-full bg-white/5 border border-white/15 rounded p-2"
/>
<.input
field={@form[:description]}
type="textarea"
label="Description"
class="w-full bg-white/5 border border-white/15 rounded p-2 h-24"
/>
</div>
<div class="space-y-4 max-h-[calc(100vh-16rem)] overflow-y-auto p-2">
<%= for {clip, index} <- Enum.with_index(@clips) do %> <%= for {clip, index} <- Enum.with_index(@clips) do %>
<div class="bg-white/5 rounded-lg p-4 space-y-2 ring-1 ring-white/15 relative"> <div class="bg-white/5 rounded-lg p-4 space-y-2 ring-1 ring-white/15 relative">
<div class="flex justify-between items-center"> <div class="flex justify-between items-center">
<h3 class="font-semibold">Clip <%= index + 1 %></h3> <h3 class="font-semibold">Clip <%= index + 1 %></h3>
<button type="button" phx-click="clip_action" phx-value-action="remove"phx-value-index={index} class="absolute top-2 right-2 text-gray-400 hover:text-gray-200"> <button
type="button"
phx-click="clip_action"
phx-value-action="remove"
phx-value-index={index}
class="absolute top-2 right-2 text-gray-400 hover:text-gray-200"
>
<Heroicons.x_mark solid class="w-5 h-5" /> <Heroicons.x_mark solid class="w-5 h-5" />
</button> </button>
</div> </div>
<div class="grid grid-cols-2 gap-2"> <div class="grid grid-cols-2 gap-2">
<div> <div>
<label class="block text-xs mb-1">From</label> <label class="block text-xs mb-1">From</label>
<.input type="text" name={"video_clipper[clips][#{index}][clip_from]"} value={clip.clip_from} class="w-full bg-white/5 border border-white/15 rounded p-1 text-sm" phx-debounce="300" /> <.input
type="text"
name={"video_clipper[clips][#{index}][clip_from]"}
value={clip.clip_from}
class="w-full bg-white/5 border border-white/15 rounded p-1 text-sm"
phx-debounce="300"
/>
<%= for error <- (clip.errors[:clip_from] || []) do %> <%= for error <- (clip.errors[:clip_from] || []) do %>
<.error><%= error %></.error> <.error><%= error %></.error>
<% end %> <% end %>
</div> </div>
<div> <div>
<label class="block text-xs mb-1">To</label> <label class="block text-xs mb-1">To</label>
<.input type="text" name={"video_clipper[clips][#{index}][clip_to]"} value={clip.clip_to} class="w-full bg-white/5 border border-white/15 rounded p-1 text-sm" phx-debounce="300" /> <.input
type="text"
name={"video_clipper[clips][#{index}][clip_to]"}
value={clip.clip_to}
class="w-full bg-white/5 border border-white/15 rounded p-1 text-sm"
phx-debounce="300"
/>
<%= for error <- (clip.errors[:clip_to] || []) do %> <%= for error <- (clip.errors[:clip_to] || []) do %>
<.error><%= error %></.error> <.error><%= error %></.error>
<% end %> <% end %>
</div> </div>
</div> </div>
<div class="flex py-2 space-x-2"> <div class="flex py-2 space-x-2">
<.button type="button" phx-click="preview_clip" phx-value-index={index} class="flex-grow" disabled={clip.errors != %{}}>Preview Clip <%= index + 1 %></.button> <.button
type="button"
phx-click="preview_clip"
phx-value-index={index}
class="flex-grow"
disabled={clip.errors != %{}}
>
Preview Clip <%= index + 1 %>
</.button>
<div class="flex gap-2"> <div class="flex gap-2">
<.button type="button" phx-click="clip_action" phx-value-action="move_up"phx-value-index={index}> <.button
type="button"
phx-click="clip_action"
phx-value-action="move_up"
phx-value-index={index}
>
<Heroicons.chevron_up solid class="w-4 h-4" /> <Heroicons.chevron_up solid class="w-4 h-4" />
</.button> </.button>
<.button type="button" phx-click="clip_action" phx-value-action="move_down" phx-value-index={index}> <.button
type="button"
phx-click="clip_action"
phx-value-action="move_down"
phx-value-index={index}
>
<Heroicons.chevron_down solid class="w-4 h-4" /> <Heroicons.chevron_down solid class="w-4 h-4" />
</.button> </.button>
</div> </div>
</div> </div>
</div> </div>
<% end %> <% end %>
<.button type="button" phx-click="clip_action" phx-value-action="add" phx-value-index={-1} class="w-full">+ Add new clip</.button> <.button
</div> type="button"
phx-click="clip_action"
phx-value-action="add"
phx-value-index={-1}
class="w-full"
>
+ Add new clip
</.button>
</div> </div>
<:actions>
<div class="w-full">
<.button type="submit" disabled={@processing} class="w-full rounded-xl p-3 font-semibold">
<%= if @processing, do: "Creating video..", else: "Create video" %>
</.button>
<%= if @processing do %>
<div class="mt-4 text-center text-sm text-gray-400">
<%= @progress.stage %> (<%= @progress.current %>/<%= @progress.total %>)
</div>
<div class="w-full bg-white/5 rounded-full h-2.5 mt-2">
<div class="bg-blue-600 h-2.5 rounded-full" style={"width: #{@progress.current / @progress.total * 100}%"}></div>
</div>
<% end %>
</div>
</:actions>
</.simple_form>
</div> </div>
<:actions>
<div class="w-full">
<.button
type="submit"
disabled={@processing}
class="w-full rounded-xl p-3 font-semibold"
>
<%= if @processing, do: "Creating video..", else: "Create video" %>
</.button>
<%= if @processing do %>
<div class="mt-4 text-center text-sm text-gray-400">
<%= @progress.stage %> (<%= @progress.current %>/<%= @progress.total %>)
</div>
<div class="w-full bg-white/5 rounded-full h-2.5 mt-2">
<div
class="bg-blue-600 h-2.5 rounded-full"
style={"width: #{@progress.current / @progress.total * 100}%"}
>
</div>
</div>
<% end %>
</div>
</:actions>
</.simple_form>
</div> </div>
</div>
""" """
end end
@ -132,40 +194,48 @@ defmodule AlgoraWeb.VideoClipperLive do
end end
def handle_event("update_form", %{"video_clipper" => params}, socket) do def handle_event("update_form", %{"video_clipper" => params}, socket) do
socket =
if params["livestream_id"] &&
params["livestream_id"] != to_string(socket.assigns.selected_livestream.id) do
new_livestream =
Enum.find(
socket.assigns.livestreams,
&(&1.id == String.to_integer(params["livestream_id"]))
)
socket = if params["livestream_id"] && params["livestream_id"] != to_string(socket.assigns.selected_livestream.id) do if new_livestream do
new_livestream = Enum.find(socket.assigns.livestreams, &(&1.id == String.to_integer(params["livestream_id"]))) send_update(PlayerComponent,
id: "preview-player",
video: new_livestream,
current_time: 0,
end_time: nil,
current_user: socket.assigns.current_user
)
if new_livestream do socket
send_update(PlayerComponent,
id: "preview-player",
video: new_livestream,
current_time: 0,
end_time: nil,
current_user: socket.assigns.current_user
)
socket
|> assign(selected_livestream: new_livestream) |> assign(selected_livestream: new_livestream)
|> assign(preview_clip: nil) |> assign(preview_clip: nil)
|> assign(clips: []) # Clear the clips # Clear the clips
|> assign(clips: [])
else
socket
end
else else
socket socket
end end
else
socket
end
# Update clips only if the livestream hasn't changed # Update clips only if the livestream hasn't changed
updated_clips = if params["livestream_id"] == to_string(socket.assigns.selected_livestream.id) do updated_clips =
update_clips_from_params(params["clips"] || %{}) if params["livestream_id"] == to_string(socket.assigns.selected_livestream.id) do
else update_clips_from_params(params["clips"] || %{})
[] else
end []
end
changeset = params |> change_video_clipper() |> Map.put(:action, :validate) changeset = params |> change_video_clipper() |> Map.put(:action, :validate)
socket = socket socket =
socket
|> assign(clips: updated_clips) |> assign(clips: updated_clips)
|> assign_form(changeset) |> assign_form(changeset)
@ -177,6 +247,7 @@ defmodule AlgoraWeb.VideoClipperLive do
|> Enum.sort_by(fn {key, _} -> key end) |> Enum.sort_by(fn {key, _} -> key end)
|> Enum.map(fn {_, clip} -> |> Enum.map(fn {_, clip} ->
errors = validate_clip_times(clip["clip_from"], clip["clip_to"]) errors = validate_clip_times(clip["clip_from"], clip["clip_to"])
%{ %{
clip_from: clip["clip_from"] || "", clip_from: clip["clip_from"] || "",
clip_to: clip["clip_to"] || "", clip_to: clip["clip_to"] || "",
@ -204,12 +275,13 @@ defmodule AlgoraWeb.VideoClipperLive do
defp validate_clips(changeset) do defp validate_clips(changeset) do
clips = Ecto.Changeset.get_field(changeset, :clips) || %{} clips = Ecto.Changeset.get_field(changeset, :clips) || %{}
errors = Enum.reduce(clips, [], fn {_index, clip}, acc -> errors =
case validate_clip_times(clip["clip_from"], clip["clip_to"]) do Enum.reduce(clips, [], fn {_index, clip}, acc ->
%{} -> acc case validate_clip_times(clip["clip_from"], clip["clip_to"]) do
errors -> [errors | acc] %{} -> acc
end errors -> [errors | acc]
end) end
end)
case errors do case errors do
[] -> changeset [] -> changeset
@ -221,10 +293,13 @@ defmodule AlgoraWeb.VideoClipperLive do
case {parse_seconds(from), parse_seconds(to)} do case {parse_seconds(from), parse_seconds(to)} do
{{:ok, from_seconds}, {:ok, to_seconds}} when from_seconds < to_seconds -> {{:ok, from_seconds}, {:ok, to_seconds}} when from_seconds < to_seconds ->
%{} %{}
{{:ok, _}, {:ok, _}} -> {{:ok, _}, {:ok, _}} ->
%{clip_to: ["End time must be after start time"]} %{clip_to: ["End time must be after start time"]}
{{:error, _}, _} -> {{:error, _}, _} ->
%{clip_from: ["Invalid time. Use HH:MM:SS"]} %{clip_from: ["Invalid time. Use HH:MM:SS"]}
{_, {:error, _}} -> {_, {:error, _}} ->
%{clip_to: ["Invalid time. Use HH:MM:SS"]} %{clip_to: ["Invalid time. Use HH:MM:SS"]}
end end
@ -259,17 +334,25 @@ defmodule AlgoraWeb.VideoClipperLive do
index = String.to_integer(index) index = String.to_integer(index)
clips = socket.assigns.clips clips = socket.assigns.clips
updated_clips = case action do updated_clips =
"add" -> clips ++ [%{clip_from: "", clip_to: "", errors: %{}}] case action do
"remove" -> List.delete_at(clips, index) "add" ->
"move_up" when index > 0 -> clips ++ [%{clip_from: "", clip_to: "", errors: %{}}]
{clip, clips} = List.pop_at(clips, index)
List.insert_at(clips, index - 1, clip) "remove" ->
"move_down" when index < length(clips) - 1 -> List.delete_at(clips, index)
{clip, clips} = List.pop_at(clips, index)
List.insert_at(clips, index + 1, clip) "move_up" when index > 0 ->
_ -> clips {clip, clips} = List.pop_at(clips, index)
end List.insert_at(clips, index - 1, clip)
"move_down" when index < length(clips) - 1 ->
{clip, clips} = List.pop_at(clips, index)
List.insert_at(clips, index + 1, clip)
_ ->
clips
end
{:noreply, assign(socket, clips: updated_clips)} {:noreply, assign(socket, clips: updated_clips)}
end end
@ -285,8 +368,10 @@ defmodule AlgoraWeb.VideoClipperLive do
video = socket.assigns.selected_livestream video = socket.assigns.selected_livestream
update_player(socket, video, start, end_time, "Previewing Clip #{index + 1}") update_player(socket, video, start, end_time, "Previewing Clip #{index + 1}")
{:noreply, assign(socket, preview_clip: %{start: start, end: end_time})} {:noreply, assign(socket, preview_clip: %{start: start, end: end_time})}
_ -> _ ->
{:noreply, put_flash(socket, :error, "An unexpected error occurred while previewing the clip")} {:noreply,
put_flash(socket, :error, "An unexpected error occurred while previewing the clip")}
end end
else else
{:noreply, put_flash(socket, :error, "Cannot preview an invalid clip")} {:noreply, put_flash(socket, :error, "Cannot preview an invalid clip")}
@ -300,10 +385,11 @@ defmodule AlgoraWeb.VideoClipperLive do
@impl true @impl true
def handle_info({:processing_complete, video}, socket) do def handle_info({:processing_complete, video}, socket) do
{:noreply, socket {:noreply,
|> put_flash(:info, "Video created successfully!") socket
|> assign(processing: false, progress: nil) |> put_flash(:info, "Video created successfully!")
|> push_redirect(to: ~p"/#{video.channel_handle}/#{video.id}")} |> assign(processing: false, progress: nil)
|> push_redirect(to: ~p"/#{video.channel_handle}/#{video.id}")}
end end
@impl true @impl true
@ -312,13 +398,16 @@ defmodule AlgoraWeb.VideoClipperLive do
if changeset.valid? do if changeset.valid? do
# Set initial processing state # Set initial processing state
socket = assign(socket, socket =
processing: true, assign(socket,
progress: %{stage: "Initializing", current: 0, total: 100} processing: true,
) progress: %{stage: "Initializing", current: 0, total: 100}
)
# Start the video creation process in a separate process # Start the video creation process in a separate process
lv = self() # capture the LiveView PID # capture the LiveView PID
lv = self()
Task.start(fn -> Task.start(fn ->
case create_video(params, socket, lv) do case create_video(params, socket, lv) do
{:ok, video} -> send(lv, {:processing_complete, video}) {:ok, video} -> send(lv, {:processing_complete, video})
@ -338,9 +427,10 @@ defmodule AlgoraWeb.VideoClipperLive do
current_user = socket.assigns.current_user current_user = socket.assigns.current_user
# Check for clip errors # Check for clip errors
clip_errors = clips clip_errors =
|> update_clips_from_params() clips
|> Enum.flat_map(fn clip -> Map.to_list(clip.errors) end) |> update_clips_from_params()
|> Enum.flat_map(fn clip -> Map.to_list(clip.errors) end)
if Enum.empty?(clip_errors) do if Enum.empty?(clip_errors) do
# Send initial progress update # Send initial progress update
@ -360,32 +450,37 @@ defmodule AlgoraWeb.VideoClipperLive do
new_video = Library.init_mp4!(upload_entry, combined_clip_path, current_user) new_video = Library.init_mp4!(upload_entry, combined_clip_path, current_user)
send(lv, {:progress_update, %{stage: "Updating video", current: 3, total: 6}}) send(lv, {:progress_update, %{stage: "Updating video", current: 3, total: 6}})
{:ok, updated_video} = Library.update_video(new_video, %{
title: params["title"] || "New Video", {:ok, updated_video} =
description: params["description"], Library.update_video(new_video, %{
visibility: :unlisted title: params["title"] || "New Video",
}) description: params["description"],
visibility: :unlisted
})
# Handle transmux progress updates with all three stages # Handle transmux progress updates with all three stages
processed_video = Library.transmux_to_hls(updated_video, fn progress_info -> processed_video =
progress = case progress_info do Library.transmux_to_hls(updated_video, fn progress_info ->
%{stage: :transmuxing, done: done, total: total} -> progress =
# Transmuxing is stage 4 case progress_info do
current = 3 + (done / total) %{stage: :transmuxing, done: done, total: total} ->
%{stage: "Transmuxing", current: trunc(current), total: 6} # Transmuxing is stage 4
current = 3 + done / total
%{stage: "Transmuxing", current: trunc(current), total: 6}
%{stage: :persisting, done: done, total: total} -> %{stage: :persisting, done: done, total: total} ->
# Persisting is stage 5 # Persisting is stage 5
current = 4 + (done / total) current = 4 + done / total
%{stage: "Persisting", current: trunc(current), total: 6} %{stage: "Persisting", current: trunc(current), total: 6}
%{stage: :generating_thumbnail, done: done, total: total} -> %{stage: :generating_thumbnail, done: done, total: total} ->
# Generating thumbnail is stage 6 # Generating thumbnail is stage 6
current = 5 + (done / total) current = 5 + done / total
%{stage: "Generating thumbnail", current: trunc(current), total: 6} %{stage: "Generating thumbnail", current: trunc(current), total: 6}
end end
send(lv, {:progress_update, progress})
end) send(lv, {:progress_update, progress})
end)
# Clean up temporary file # Clean up temporary file
File.rm(combined_clip_path) File.rm(combined_clip_path)
@ -406,13 +501,13 @@ defmodule AlgoraWeb.VideoClipperLive do
end end
defp update_player(socket, video, current_time, end_time \\ nil, title \\ nil) do defp update_player(socket, video, current_time, end_time \\ nil, title \\ nil) do
send_update(PlayerComponent, send_update(PlayerComponent,
id: "preview-player", id: "preview-player",
video: video, video: video,
current_time: current_time, current_time: current_time,
end_time: end_time, end_time: end_time,
title: title, title: title,
current_user: socket.assigns.current_user current_user: socket.assigns.current_user
) )
end end
end end