1
0
mirror of https://github.com/algora-io/tv.git synced 2025-03-17 20:17:45 +02:00
algora-tv/lib/algora/admin/generate_blurb_thumbnails.ex

134 lines
3.9 KiB
Elixir
Raw Normal View History

defmodule Algora.Admin.GenerateBlurbThumbnails do
import Ecto.Query
alias Algora.Ads.ProductReview
alias Algora.{Clipper, Repo}
def run do
for product_review <- fetch_product_reviews() do
:ok = process_product_review(product_review)
end
end
defp fetch_product_reviews do
Repo.all(
from pr in ProductReview,
join: v in assoc(pr, :video),
where: is_nil(pr.thumbnail_url),
preload: [video: v]
)
end
defp process_product_review(product_review) do
with {:ok, thumbnail_path} <- create_thumbnail(product_review),
{:ok, thumbnail_url} <- upload_thumbnail(thumbnail_path),
{:ok, _updated_review} <- update_product_review(product_review, thumbnail_url) do
IO.puts("Successfully processed ProductReview #{product_review.id}")
else
{:error, step, reason} ->
IO.puts(
"Error processing ProductReview #{product_review.id} at step #{step}: #{inspect(reason)}"
)
:error
end
end
defp create_thumbnail(product_review) do
video_path = generate_thumbnails(product_review)
output_path = "/tmp/thumbnail_#{product_review.id}.jpg"
case Thumbnex.create_thumbnail(video_path, output_path, time: product_review.clip_from) do
:ok -> {:ok, output_path}
error -> {:error, :create_thumbnail, error}
end
end
defp upload_thumbnail(file_path) do
uuid = Ecto.UUID.generate()
remote_path = "blurbs/#{uuid}.jpg"
case Algora.Storage.upload_from_filename(file_path, remote_path, fn _ -> nil end,
content_type: "image/jpeg"
) do
{:ok, _} ->
bucket = Algora.config([:buckets, :media])
%{scheme: scheme, host: host} = Application.fetch_env!(:ex_aws, :s3) |> Enum.into(%{})
thumbnail_url = "#{scheme}#{host}/#{bucket}/#{remote_path}"
{:ok, thumbnail_url}
error ->
{:error, :upload_thumbnail, error}
end
end
defp update_product_review(product_review, thumbnail_url) do
product_review
|> Ecto.Changeset.change(thumbnail_url: thumbnail_url)
|> Repo.update()
|> case do
{:ok, updated_review} -> {:ok, updated_review}
error -> {:error, :update_product_review, error}
end
end
def generate_thumbnails(product_review) do
# Generate clipped manifest
%{playlist: playlist, ss: _ss} =
Clipper.clip(product_review.video, product_review.clip_from, product_review.clip_to)
# Find MediaInit and first Segment
{init_tag, segment_tag} = find_init_and_segment(playlist.timeline)
# Download and concatenate files
video_path = download_and_concatenate(init_tag, segment_tag, product_review.video)
video_path
end
defp find_init_and_segment(timeline) do
init_tag = Enum.find(timeline, &match?(%ExM3U8.Tags.MediaInit{}, &1))
segments = Enum.filter(timeline, &match?(%ExM3U8.Tags.Segment{}, &1))
segment_tag =
case segments do
[_, second | _] -> second
[first | _] -> first
_ -> nil
end
{init_tag, segment_tag}
end
defp download_and_concatenate(init_tag, segment_tag, video) do
temp_dir = Path.join(System.tmp_dir!(), video.uuid)
File.mkdir_p!(temp_dir)
output_path = Path.join(temp_dir, "output.mp4")
init_url = maybe_to_absolute(init_tag.uri, video)
segment_url = maybe_to_absolute(segment_tag.uri, video)
init_path = download_file(init_url, Path.join(temp_dir, "init.mp4"))
segment_path = download_file(segment_url, Path.join(temp_dir, "segment.m4s"))
ffmpeg_command = "ffmpeg -y -i \"concat:#{init_path}|#{segment_path}\" -c copy #{output_path}"
System.cmd("sh", ["-c", ffmpeg_command])
output_path
end
defp download_file(url, path) do
{:ok, %{body: body}} = HTTPoison.get(url)
File.write!(path, body)
path
end
defp maybe_to_absolute(uri, video) do
if URI.parse(uri).scheme do
uri
else
Algora.Clipper.to_absolute(:video, video.uuid, uri)
end
end
end