Adds options + option builder + metadata parsing for media thumbnails

This commit is contained in:
Kieran Eglin 2024-02-05 12:22:08 -08:00
parent ca01f17b58
commit 3e5faa0503
No known key found for this signature in database
GPG key ID: 193984967FCF432D
9 changed files with 281 additions and 48 deletions

View file

@ -11,12 +11,13 @@ defmodule Pinchflat.Media.MediaItem do
alias Pinchflat.Media.MediaMetadata
@required_fields ~w(media_id source_id)a
@allowed_fields ~w(title media_id media_filepath source_id subtitle_filepaths)a
@allowed_fields ~w(title media_id media_filepath source_id subtitle_filepaths thumbnail_filepath)a
schema "media_items" do
field :title, :string
field :media_id, :string
field :media_filepath, :string
field :thumbnail_filepath, :string
# This is an array of [iso-2 language, filepath] pairs. Probably could
# be an associated record, but I don't see the benefit right now.
# Will very likely revisit because I can't leave well-enough alone.

View file

@ -25,6 +25,7 @@ defmodule Pinchflat.MediaClient.Backends.YtDlp.MetadataParser do
metadata_attrs
|> Map.merge(parse_media_metadata(metadata))
|> Map.merge(parse_subtitle_metadata(metadata))
|> Map.merge(parse_thumbnail_metadata(metadata))
end
defp parse_media_metadata(metadata) do
@ -35,10 +36,9 @@ defmodule Pinchflat.MediaClient.Backends.YtDlp.MetadataParser do
end
defp parse_subtitle_metadata(metadata) do
subtitle_map = metadata["requested_subtitles"] || %{}
# IDEA: if needed, consider filtering out subtitles that don't exist on-disk
subtitle_filepaths =
subtitle_map
(metadata["requested_subtitles"] || %{})
|> Enum.map(fn {lang, attrs} -> [lang, attrs["filepath"]] end)
|> Enum.sort(fn [lang_a, _], [lang_b, _] -> lang_a < lang_b end)
@ -46,4 +46,18 @@ defmodule Pinchflat.MediaClient.Backends.YtDlp.MetadataParser do
subtitle_filepaths: subtitle_filepaths
}
end
defp parse_thumbnail_metadata(metadata) do
thumbnail_filepath =
(metadata["thumbnails"] || %{})
# Reverse so that higher resolution thumbnails come first.
# This _shouldn't_ matter yet, but I'd rather default to the best
# in case I'm wrong.
|> Enum.reverse()
|> Enum.find_value(fn attrs -> attrs["filepath"] end)
%{
thumbnail_filepath: thumbnail_filepath
}
end
end

View file

@ -15,6 +15,8 @@ defmodule Pinchflat.Profiles.MediaProfile do
download_auto_subs
embed_subs
sub_langs
download_thumbnail
embed_thumbnail
)a
@required_fields ~w(name output_path_template)a
@ -22,11 +24,15 @@ defmodule Pinchflat.Profiles.MediaProfile do
schema "media_profiles" do
field :name, :string
field :output_path_template, :string
field :download_subs, :boolean, default: true
field :download_auto_subs, :boolean, default: true
field :embed_subs, :boolean, default: true
field :sub_langs, :string, default: "en"
field :download_thumbnail, :boolean, default: true
field :embed_thumbnail, :boolean, default: true
has_many :sources, Source
timestamps(type: :utc_datetime)

View file

@ -24,6 +24,7 @@ defmodule Pinchflat.Profiles.Options.YtDlp.OptionBuilder do
built_options =
default_options() ++
subtitle_options(media_profile) ++
thumbnail_options(media_profile) ++
output_options(media_profile)
{:ok, built_options}
@ -33,7 +34,6 @@ defmodule Pinchflat.Profiles.Options.YtDlp.OptionBuilder do
defp default_options do
[
:embed_metadata,
:embed_thumbnail,
:no_progress
]
end
@ -65,6 +65,18 @@ defmodule Pinchflat.Profiles.Options.YtDlp.OptionBuilder do
end)
end
defp thumbnail_options(media_profile) do
mapped_struct = Map.from_struct(media_profile)
Enum.reduce(mapped_struct, [], fn attr, acc ->
case attr do
{:download_thumbnail, true} -> acc ++ [:write_thumbnail]
{:embed_thumbnail, true} -> acc ++ [:embed_thumbnail]
_ -> acc
end
end)
end
defp output_options(media_profile) do
{:ok, output_path} = OutputPathBuilder.build(media_profile.output_path_template)

View file

@ -0,0 +1,14 @@
defmodule Pinchflat.Repo.Migrations.AddThumbnailOptionsToMediaProfiles do
use Ecto.Migration
def change do
alter table(:media_profiles) do
add :download_thumbnail, :boolean, default: true, null: false
add :embed_thumbnail, :boolean, default: true, null: false
end
alter table(:media_items) do
add :thumbnail_filepath, :string
end
end
end

View file

@ -83,4 +83,28 @@ defmodule Pinchflat.MediaClient.Backends.YtDlp.MediaParserTest do
assert result.subtitle_filepaths == []
end
end
describe "parse_for_media_item/1 when testing thumbnail metadata" do
test "extracts the thumbnail filepath", %{metadata: metadata} do
result = Parser.parse_for_media_item(metadata)
assert String.ends_with?(result.thumbnail_filepath, ".webp")
end
test "doesn't freak out if the video has no thumbnails", %{metadata: metadata} do
metadata = Map.put(metadata, "thumbnails", %{})
result = Parser.parse_for_media_item(metadata)
assert result.thumbnail_filepath == nil
end
test "doesn't freak out if the thumbnails key is missing", %{metadata: metadata} do
metadata = Map.delete(metadata, "thumbnails")
result = Parser.parse_for_media_item(metadata)
assert result.thumbnail_filepath == nil
end
end
end

View file

@ -33,11 +33,18 @@ defmodule Pinchflat.MediaClient.VideoDownloaderTest do
{:ok, render_metadata(:media_metadata)}
end)
assert %{media_filepath: nil, title: nil, subtitle_filepaths: []} = media_item
assert %{
media_filepath: nil,
title: nil,
subtitle_filepaths: [],
thumbnail_filepath: nil
} = media_item
assert {:ok, updated_media_item} = VideoDownloader.download_for_media_item(media_item)
assert updated_media_item.media_filepath
assert updated_media_item.title
assert length(updated_media_item.subtitle_filepaths) > 0
assert updated_media_item.thumbnail_filepath
end
test "it saves the metadata to the database", %{media_item: media_item} do

View file

@ -94,4 +94,35 @@ defmodule Pinchflat.Profiles.Options.YtDlp.OptionBuilderTest do
refute {:id, -1} in res
end
end
describe "build/1 when testing thumbnail options" do
test "includes :write_thumbnail option when specified" do
media_profile = %MediaProfile{@media_profile | download_thumbnail: true}
assert {:ok, res} = OptionBuilder.build(media_profile)
assert :write_thumbnail in res
end
test "includes :embed_thumbnail option when specified" do
media_profile = %MediaProfile{@media_profile | embed_thumbnail: true}
assert {:ok, res} = OptionBuilder.build(media_profile)
assert :embed_thumbnail in res
end
test "doesn't include these options when not specified" do
media_profile = %MediaProfile{
@media_profile
| embed_thumbnail: false,
download_thumbnail: false
}
assert {:ok, res} = OptionBuilder.build(media_profile)
refute :write_thumbnail in res
refute :embed_thumbnail in res
end
end
end

View file

@ -1389,42 +1389,138 @@
}
],
"thumbnails": [
{ "url": "https://i.ytimg.com/vi/bwRHIkYqYJo/3.jpg", "preference": -37, "id": "0" },
{ "url": "https://i.ytimg.com/vi_webp/bwRHIkYqYJo/3.webp", "preference": -36, "id": "1" },
{ "url": "https://i.ytimg.com/vi/bwRHIkYqYJo/2.jpg", "preference": -35, "id": "2" },
{ "url": "https://i.ytimg.com/vi_webp/bwRHIkYqYJo/2.webp", "preference": -34, "id": "3" },
{ "url": "https://i.ytimg.com/vi/bwRHIkYqYJo/1.jpg", "preference": -33, "id": "4" },
{ "url": "https://i.ytimg.com/vi_webp/bwRHIkYqYJo/1.webp", "preference": -32, "id": "5" },
{ "url": "https://i.ytimg.com/vi/bwRHIkYqYJo/mq3.jpg", "preference": -31, "id": "6" },
{ "url": "https://i.ytimg.com/vi_webp/bwRHIkYqYJo/mq3.webp", "preference": -30, "id": "7" },
{ "url": "https://i.ytimg.com/vi/bwRHIkYqYJo/mq2.jpg", "preference": -29, "id": "8" },
{ "url": "https://i.ytimg.com/vi_webp/bwRHIkYqYJo/mq2.webp", "preference": -28, "id": "9" },
{ "url": "https://i.ytimg.com/vi/bwRHIkYqYJo/mq1.jpg", "preference": -27, "id": "10" },
{ "url": "https://i.ytimg.com/vi_webp/bwRHIkYqYJo/mq1.webp", "preference": -26, "id": "11" },
{ "url": "https://i.ytimg.com/vi/bwRHIkYqYJo/hq3.jpg", "preference": -25, "id": "12" },
{ "url": "https://i.ytimg.com/vi_webp/bwRHIkYqYJo/hq3.webp", "preference": -24, "id": "13" },
{ "url": "https://i.ytimg.com/vi/bwRHIkYqYJo/hq2.jpg", "preference": -23, "id": "14" },
{ "url": "https://i.ytimg.com/vi_webp/bwRHIkYqYJo/hq2.webp", "preference": -22, "id": "15" },
{ "url": "https://i.ytimg.com/vi/bwRHIkYqYJo/hq1.jpg", "preference": -21, "id": "16" },
{ "url": "https://i.ytimg.com/vi_webp/bwRHIkYqYJo/hq1.webp", "preference": -20, "id": "17" },
{ "url": "https://i.ytimg.com/vi/bwRHIkYqYJo/sd3.jpg", "preference": -19, "id": "18" },
{ "url": "https://i.ytimg.com/vi_webp/bwRHIkYqYJo/sd3.webp", "preference": -18, "id": "19" },
{ "url": "https://i.ytimg.com/vi/bwRHIkYqYJo/sd2.jpg", "preference": -17, "id": "20" },
{ "url": "https://i.ytimg.com/vi_webp/bwRHIkYqYJo/sd2.webp", "preference": -16, "id": "21" },
{ "url": "https://i.ytimg.com/vi/bwRHIkYqYJo/sd1.jpg", "preference": -15, "id": "22" },
{ "url": "https://i.ytimg.com/vi_webp/bwRHIkYqYJo/sd1.webp", "preference": -14, "id": "23" },
{
"url": "https://i.ytimg.com/vi/bwRHIkYqYJo/3.jpg",
"preference": -37,
"id": "0"
},
{
"url": "https://i.ytimg.com/vi_webp/bwRHIkYqYJo/3.webp",
"preference": -36,
"id": "1"
},
{
"url": "https://i.ytimg.com/vi/bwRHIkYqYJo/2.jpg",
"preference": -35,
"id": "2"
},
{
"url": "https://i.ytimg.com/vi_webp/bwRHIkYqYJo/2.webp",
"preference": -34,
"id": "3"
},
{
"url": "https://i.ytimg.com/vi/bwRHIkYqYJo/1.jpg",
"preference": -33,
"id": "4"
},
{
"url": "https://i.ytimg.com/vi_webp/bwRHIkYqYJo/1.webp",
"preference": -32,
"id": "5"
},
{
"url": "https://i.ytimg.com/vi/bwRHIkYqYJo/mq3.jpg",
"preference": -31,
"id": "6"
},
{
"url": "https://i.ytimg.com/vi_webp/bwRHIkYqYJo/mq3.webp",
"preference": -30,
"id": "7"
},
{
"url": "https://i.ytimg.com/vi/bwRHIkYqYJo/mq2.jpg",
"preference": -29,
"id": "8"
},
{
"url": "https://i.ytimg.com/vi_webp/bwRHIkYqYJo/mq2.webp",
"preference": -28,
"id": "9"
},
{
"url": "https://i.ytimg.com/vi/bwRHIkYqYJo/mq1.jpg",
"preference": -27,
"id": "10"
},
{
"url": "https://i.ytimg.com/vi_webp/bwRHIkYqYJo/mq1.webp",
"preference": -26,
"id": "11"
},
{
"url": "https://i.ytimg.com/vi/bwRHIkYqYJo/hq3.jpg",
"preference": -25,
"id": "12"
},
{
"url": "https://i.ytimg.com/vi_webp/bwRHIkYqYJo/hq3.webp",
"preference": -24,
"id": "13"
},
{
"url": "https://i.ytimg.com/vi/bwRHIkYqYJo/hq2.jpg",
"preference": -23,
"id": "14"
},
{
"url": "https://i.ytimg.com/vi_webp/bwRHIkYqYJo/hq2.webp",
"preference": -22,
"id": "15"
},
{
"url": "https://i.ytimg.com/vi/bwRHIkYqYJo/hq1.jpg",
"preference": -21,
"id": "16"
},
{
"url": "https://i.ytimg.com/vi_webp/bwRHIkYqYJo/hq1.webp",
"preference": -20,
"id": "17"
},
{
"url": "https://i.ytimg.com/vi/bwRHIkYqYJo/sd3.jpg",
"preference": -19,
"id": "18"
},
{
"url": "https://i.ytimg.com/vi_webp/bwRHIkYqYJo/sd3.webp",
"preference": -18,
"id": "19"
},
{
"url": "https://i.ytimg.com/vi/bwRHIkYqYJo/sd2.jpg",
"preference": -17,
"id": "20"
},
{
"url": "https://i.ytimg.com/vi_webp/bwRHIkYqYJo/sd2.webp",
"preference": -16,
"id": "21"
},
{
"url": "https://i.ytimg.com/vi/bwRHIkYqYJo/sd1.jpg",
"preference": -15,
"id": "22"
},
{
"url": "https://i.ytimg.com/vi_webp/bwRHIkYqYJo/sd1.webp",
"preference": -14,
"id": "23"
},
{
"url": "https://i.ytimg.com/vi/bwRHIkYqYJo/default.jpg",
"height": 90,
"width": 120,
"preference": -13,
"id": "24",
"resolution": "120x90"
"id": "24"
},
{
"url": "https://i.ytimg.com/vi_webp/bwRHIkYqYJo/default.webp",
"height": 90,
"width": 120,
"preference": -12,
"id": "25"
"id": "25",
"resolution": "120x90"
},
{
"url": "https://i.ytimg.com/vi/bwRHIkYqYJo/mqdefault.jpg",
@ -1436,13 +1532,24 @@
},
{
"url": "https://i.ytimg.com/vi_webp/bwRHIkYqYJo/mqdefault.webp",
"height": 180,
"width": 320,
"preference": -10,
"id": "27"
"id": "27",
"resolution": "320x180"
},
{ "url": "https://i.ytimg.com/vi/bwRHIkYqYJo/0.jpg", "preference": -9, "id": "28" },
{ "url": "https://i.ytimg.com/vi_webp/bwRHIkYqYJo/0.webp", "preference": -8, "id": "29" },
{
"url": "https://i.ytimg.com/vi/bwRHIkYqYJo/hqdefault.jpg?sqp=-oaymwEbCKgBEF5IVfKriqkDDggBFQAAiEIYAXABwAEG&rs=AOn4CLCfnqZqzf3SpMWSBlr5eEkYrRbeOw",
"url": "https://i.ytimg.com/vi/bwRHIkYqYJo/0.jpg",
"preference": -9,
"id": "28"
},
{
"url": "https://i.ytimg.com/vi_webp/bwRHIkYqYJo/0.webp",
"preference": -8,
"id": "29"
},
{
"url": "https://i.ytimg.com/vi/bwRHIkYqYJo/hqdefault.jpg?sqp=-oaymwEbCKgBEF5IVfKriqkDDggBFQAAiEIYAXABwAEG&rs=AOn4CLDmfPp_bFp43fLeDiG7JmFAldoFLA",
"height": 94,
"width": 168,
"preference": -7,
@ -1450,7 +1557,7 @@
"resolution": "168x94"
},
{
"url": "https://i.ytimg.com/vi/bwRHIkYqYJo/hqdefault.jpg?sqp=-oaymwEbCMQBEG5IVfKriqkDDggBFQAAiEIYAXABwAEG&rs=AOn4CLAnj5wtnbYGzZX9gerMKQ-FS5sSeA",
"url": "https://i.ytimg.com/vi/bwRHIkYqYJo/hqdefault.jpg?sqp=-oaymwEbCMQBEG5IVfKriqkDDggBFQAAiEIYAXABwAEG&rs=AOn4CLCGAiCuOy3Tmz9U9iGo3YF5dM57bQ",
"height": 110,
"width": 196,
"preference": -7,
@ -1458,7 +1565,7 @@
"resolution": "196x110"
},
{
"url": "https://i.ytimg.com/vi/bwRHIkYqYJo/hqdefault.jpg?sqp=-oaymwEcCPYBEIoBSFXyq4qpAw4IARUAAIhCGAFwAcABBg==&rs=AOn4CLC5fmbFoduOVcifLAMu006pXlcicQ",
"url": "https://i.ytimg.com/vi/bwRHIkYqYJo/hqdefault.jpg?sqp=-oaymwEcCPYBEIoBSFXyq4qpAw4IARUAAIhCGAFwAcABBg==&rs=AOn4CLDcSQOIb-t1Aug8y0A-_M2jNehcRw",
"height": 138,
"width": 246,
"preference": -7,
@ -1466,7 +1573,7 @@
"resolution": "246x138"
},
{
"url": "https://i.ytimg.com/vi/bwRHIkYqYJo/hqdefault.jpg?sqp=-oaymwEcCNACELwBSFXyq4qpAw4IARUAAIhCGAFwAcABBg==&rs=AOn4CLDISoJqnSXFGgqsSCMRd9NCiV5Dyg",
"url": "https://i.ytimg.com/vi/bwRHIkYqYJo/hqdefault.jpg?sqp=-oaymwEcCNACELwBSFXyq4qpAw4IARUAAIhCGAFwAcABBg==&rs=AOn4CLAT65DjLaxTpghDUrIAQEclNUUJog",
"height": 188,
"width": 336,
"preference": -7,
@ -1483,8 +1590,11 @@
},
{
"url": "https://i.ytimg.com/vi_webp/bwRHIkYqYJo/hqdefault.webp",
"height": 360,
"width": 480,
"preference": -6,
"id": "35"
"id": "35",
"resolution": "480x360"
},
{
"url": "https://i.ytimg.com/vi/bwRHIkYqYJo/sddefault.jpg",
@ -1496,23 +1606,37 @@
},
{
"url": "https://i.ytimg.com/vi_webp/bwRHIkYqYJo/sddefault.webp",
"height": 480,
"width": 640,
"preference": -4,
"id": "37"
"id": "37",
"resolution": "640x480"
},
{
"url": "https://i.ytimg.com/vi/bwRHIkYqYJo/hq720.jpg",
"preference": -3,
"id": "38"
},
{
"url": "https://i.ytimg.com/vi_webp/bwRHIkYqYJo/hq720.webp",
"preference": -2,
"id": "39"
},
{ "url": "https://i.ytimg.com/vi/bwRHIkYqYJo/hq720.jpg", "preference": -3, "id": "38" },
{ "url": "https://i.ytimg.com/vi_webp/bwRHIkYqYJo/hq720.webp", "preference": -2, "id": "39" },
{
"url": "https://i.ytimg.com/vi/bwRHIkYqYJo/maxresdefault.jpg",
"height": 1080,
"width": 1920,
"height": 720,
"width": 1280,
"preference": -1,
"id": "40",
"resolution": "1920x1080"
"resolution": "1280x720"
},
{
"url": "https://i.ytimg.com/vi_webp/bwRHIkYqYJo/maxresdefault.webp",
"height": 1080,
"width": 1920,
"preference": 0,
"id": "41",
"resolution": "1920x1080",
"filepath": "/app/tmp/videos/bwRHIkYqYJo/Trying to Wheelie Without the Rear Brake-bwRHIkYqYJo.webp"
}
],