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:
parent
fcef5653ee
commit
d81e457b61
@ -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
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -98,12 +99,18 @@ defmodule Algora.Clipper do
|
|||||||
# 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]",
|
||||||
|
"-map",
|
||||||
|
"[a]",
|
||||||
|
"-c:v",
|
||||||
|
"libx264",
|
||||||
|
"-c:a",
|
||||||
|
"aac",
|
||||||
output_path
|
output_path
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -112,6 +119,7 @@ defmodule Algora.Clipper do
|
|||||||
{_, 0} ->
|
{_, 0} ->
|
||||||
File.rm(filter_path)
|
File.rm(filter_path)
|
||||||
{:ok, output_path}
|
{:ok, output_path}
|
||||||
|
|
||||||
{error, _} ->
|
{error, _} ->
|
||||||
File.rm(filter_path)
|
File.rm(filter_path)
|
||||||
{:error, "FFmpeg error: #{error}"}
|
{:error, "FFmpeg error: #{error}"}
|
||||||
@ -120,26 +128,41 @@ defmodule Algora.Clipper do
|
|||||||
|
|
||||||
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} ->
|
|
||||||
|
total_duration =
|
||||||
|
Enum.sum(
|
||||||
|
Enum.map(clips_params, fn {_, clip} ->
|
||||||
Library.from_hhmmss(clip["clip_to"]) - Library.from_hhmmss(clip["clip_from"])
|
Library.from_hhmmss(clip["clip_to"]) - Library.from_hhmmss(clip["clip_from"])
|
||||||
end))
|
end)
|
||||||
|
)
|
||||||
|
|
||||||
Slug.slugify("#{video.title}-#{clip_count}clips-#{total_duration}s")
|
Slug.slugify("#{video.title}-#{clip_count}clips-#{total_duration}s")
|
||||||
end
|
end
|
||||||
|
|
||||||
defp create_filter_complex(clips_params) do
|
defp create_filter_complex(clips_params) do
|
||||||
{filter_complex, _} = clips_params
|
{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}]; " <>
|
|
||||||
|
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"
|
"[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]"
|
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
|
filter_complex <> video_concat <> audio_concat
|
||||||
end
|
end
|
||||||
|
@ -34,8 +34,8 @@ defmodule AlgoraWeb.VideoClipperLive do
|
|||||||
module={PlayerComponent}
|
module={PlayerComponent}
|
||||||
id="preview-player"
|
id="preview-player"
|
||||||
video={@selected_livestream}
|
video={@selected_livestream}
|
||||||
current_time={@preview_clip && @preview_clip.start || 0}
|
current_time={(@preview_clip && @preview_clip.start) || 0}
|
||||||
end_time={@preview_clip && @preview_clip.end || nil}
|
end_time={(@preview_clip && @preview_clip.end) || nil}
|
||||||
current_user={@current_user}
|
current_user={@current_user}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -43,53 +43,111 @@ defmodule AlgoraWeb.VideoClipperLive do
|
|||||||
<Heroicons.play solid class="w-16 h-16" />
|
<Heroicons.play solid class="w-16 h-16" />
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
<.input field={@form[:title]} type="text" label="Title" class="w-full bg-white/5 border border-white/15 rounded p-2" />
|
<.input
|
||||||
<.input field={@form[:description]} type="textarea" label="Description" class="w-full bg-white/5 border border-white/15 rounded p-2 h-24" />
|
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="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
|
||||||
|
type="button"
|
||||||
|
phx-click="clip_action"
|
||||||
|
phx-value-action="add"
|
||||||
|
phx-value-index={-1}
|
||||||
|
class="w-full"
|
||||||
|
>
|
||||||
|
+ Add new clip
|
||||||
|
</.button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<:actions>
|
<:actions>
|
||||||
<div class="w-full">
|
<div class="w-full">
|
||||||
<.button type="submit" disabled={@processing} class="w-full rounded-xl p-3 font-semibold">
|
<.button
|
||||||
|
type="submit"
|
||||||
|
disabled={@processing}
|
||||||
|
class="w-full rounded-xl p-3 font-semibold"
|
||||||
|
>
|
||||||
<%= if @processing, do: "Creating video..", else: "Create video" %>
|
<%= if @processing, do: "Creating video..", else: "Create video" %>
|
||||||
</.button>
|
</.button>
|
||||||
|
|
||||||
@ -98,7 +156,11 @@ defmodule AlgoraWeb.VideoClipperLive do
|
|||||||
<%= @progress.stage %> (<%= @progress.current %>/<%= @progress.total %>)
|
<%= @progress.stage %> (<%= @progress.current %>/<%= @progress.total %>)
|
||||||
</div>
|
</div>
|
||||||
<div class="w-full bg-white/5 rounded-full h-2.5 mt-2">
|
<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
|
||||||
|
class="bg-blue-600 h-2.5 rounded-full"
|
||||||
|
style={"width: #{@progress.current / @progress.total * 100}%"}
|
||||||
|
>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
@ -132,9 +194,14 @@ 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 =
|
||||||
socket = if params["livestream_id"] && params["livestream_id"] != to_string(socket.assigns.selected_livestream.id) do
|
if params["livestream_id"] &&
|
||||||
new_livestream = Enum.find(socket.assigns.livestreams, &(&1.id == String.to_integer(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"]))
|
||||||
|
)
|
||||||
|
|
||||||
if new_livestream do
|
if new_livestream do
|
||||||
send_update(PlayerComponent,
|
send_update(PlayerComponent,
|
||||||
@ -148,7 +215,8 @@ defmodule AlgoraWeb.VideoClipperLive do
|
|||||||
socket
|
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
|
else
|
||||||
socket
|
socket
|
||||||
end
|
end
|
||||||
@ -157,7 +225,8 @@ defmodule AlgoraWeb.VideoClipperLive do
|
|||||||
end
|
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 =
|
||||||
|
if params["livestream_id"] == to_string(socket.assigns.selected_livestream.id) do
|
||||||
update_clips_from_params(params["clips"] || %{})
|
update_clips_from_params(params["clips"] || %{})
|
||||||
else
|
else
|
||||||
[]
|
[]
|
||||||
@ -165,7 +234,8 @@ defmodule AlgoraWeb.VideoClipperLive do
|
|||||||
|
|
||||||
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,7 +275,8 @@ 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 =
|
||||||
|
Enum.reduce(clips, [], fn {_index, clip}, acc ->
|
||||||
case validate_clip_times(clip["clip_from"], clip["clip_to"]) do
|
case validate_clip_times(clip["clip_from"], clip["clip_to"]) do
|
||||||
%{} -> acc
|
%{} -> acc
|
||||||
errors -> [errors | acc]
|
errors -> [errors | acc]
|
||||||
@ -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,16 +334,24 @@ 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" ->
|
||||||
|
clips ++ [%{clip_from: "", clip_to: "", errors: %{}}]
|
||||||
|
|
||||||
|
"remove" ->
|
||||||
|
List.delete_at(clips, index)
|
||||||
|
|
||||||
"move_up" when index > 0 ->
|
"move_up" when index > 0 ->
|
||||||
{clip, clips} = List.pop_at(clips, index)
|
{clip, clips} = List.pop_at(clips, index)
|
||||||
List.insert_at(clips, index - 1, clip)
|
List.insert_at(clips, index - 1, clip)
|
||||||
|
|
||||||
"move_down" when index < length(clips) - 1 ->
|
"move_down" when index < length(clips) - 1 ->
|
||||||
{clip, clips} = List.pop_at(clips, index)
|
{clip, clips} = List.pop_at(clips, index)
|
||||||
List.insert_at(clips, index + 1, clip)
|
List.insert_at(clips, index + 1, clip)
|
||||||
_ -> clips
|
|
||||||
|
_ ->
|
||||||
|
clips
|
||||||
end
|
end
|
||||||
|
|
||||||
{:noreply, assign(socket, clips: updated_clips)}
|
{:noreply, assign(socket, clips: updated_clips)}
|
||||||
@ -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,7 +385,8 @@ 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,
|
||||||
|
socket
|
||||||
|> put_flash(:info, "Video created successfully!")
|
|> put_flash(:info, "Video created successfully!")
|
||||||
|> assign(processing: false, progress: nil)
|
|> assign(processing: false, progress: nil)
|
||||||
|> push_redirect(to: ~p"/#{video.channel_handle}/#{video.id}")}
|
|> push_redirect(to: ~p"/#{video.channel_handle}/#{video.id}")}
|
||||||
@ -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 =
|
||||||
|
assign(socket,
|
||||||
processing: true,
|
processing: true,
|
||||||
progress: %{stage: "Initializing", current: 0, total: 100}
|
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,7 +427,8 @@ 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 =
|
||||||
|
clips
|
||||||
|> update_clips_from_params()
|
|> update_clips_from_params()
|
||||||
|> Enum.flat_map(fn clip -> Map.to_list(clip.errors) end)
|
|> Enum.flat_map(fn clip -> Map.to_list(clip.errors) end)
|
||||||
|
|
||||||
@ -360,30 +450,35 @@ 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, %{
|
|
||||||
|
{:ok, updated_video} =
|
||||||
|
Library.update_video(new_video, %{
|
||||||
title: params["title"] || "New Video",
|
title: params["title"] || "New Video",
|
||||||
description: params["description"],
|
description: params["description"],
|
||||||
visibility: :unlisted
|
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 ->
|
||||||
|
progress =
|
||||||
|
case progress_info do
|
||||||
%{stage: :transmuxing, done: done, total: total} ->
|
%{stage: :transmuxing, done: done, total: total} ->
|
||||||
# Transmuxing is stage 4
|
# Transmuxing is stage 4
|
||||||
current = 3 + (done / total)
|
current = 3 + done / total
|
||||||
%{stage: "Transmuxing", current: trunc(current), total: 6}
|
%{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})
|
send(lv, {:progress_update, progress})
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user