mirror of
https://github.com/kieraneglin/pinchflat.git
synced 2026-01-23 02:24:24 +00:00
Source attributes in output template (#45)
* Updated download options to take a media item; allowed for specifying custom output path template options * Fixed bug where indexing job wouldn't run for the first time * Renamed friendly_name to custom_name * Added new options to default template and UI * Improved explainer UI
This commit is contained in:
parent
9e75092876
commit
09448e3fcc
18 changed files with 200 additions and 139 deletions
|
|
@ -11,7 +11,6 @@ defmodule Pinchflat.MediaClient.VideoDownloader do
|
|||
alias Pinchflat.Repo
|
||||
alias Pinchflat.Media
|
||||
alias Pinchflat.Media.MediaItem
|
||||
alias Pinchflat.Profiles.MediaProfile
|
||||
|
||||
alias Pinchflat.MediaClient.Backends.YtDlp.Video, as: YtDlpVideo
|
||||
alias Pinchflat.Profiles.Options.YtDlp.DownloadOptionBuilder, as: YtDlpDownloadOptionBuilder
|
||||
|
|
@ -31,9 +30,8 @@ defmodule Pinchflat.MediaClient.VideoDownloader do
|
|||
"""
|
||||
def download_for_media_item(%MediaItem{} = media_item, backend \\ :yt_dlp) do
|
||||
item_with_preloads = Repo.preload(media_item, [:metadata, source: :media_profile])
|
||||
media_profile = item_with_preloads.source.media_profile
|
||||
|
||||
case download_for_media_profile(media_item.original_url, media_profile, backend) do
|
||||
case download_with_options(media_item.original_url, item_with_preloads, backend) do
|
||||
{:ok, parsed_json} ->
|
||||
{parser, helpers} = metadata_parsers(backend)
|
||||
|
||||
|
|
@ -57,10 +55,10 @@ defmodule Pinchflat.MediaClient.VideoDownloader do
|
|||
end
|
||||
end
|
||||
|
||||
defp download_for_media_profile(url, %MediaProfile{} = media_profile, backend) do
|
||||
defp download_with_options(url, item_with_preloads, backend) do
|
||||
option_builder = option_builder(backend)
|
||||
video_backend = video_backend(backend)
|
||||
{:ok, options} = option_builder.build(media_profile)
|
||||
{:ok, options} = option_builder.build(item_with_preloads)
|
||||
|
||||
video_backend.download(url, options)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ defmodule Pinchflat.Sources.Source do
|
|||
collection_name
|
||||
collection_id
|
||||
collection_type
|
||||
friendly_name
|
||||
custom_name
|
||||
index_frequency_minutes
|
||||
download_media
|
||||
last_indexed_at
|
||||
|
|
@ -27,7 +27,7 @@ defmodule Pinchflat.Sources.Source do
|
|||
collection_name
|
||||
collection_id
|
||||
collection_type
|
||||
friendly_name
|
||||
custom_name
|
||||
index_frequency_minutes
|
||||
download_media
|
||||
original_url
|
||||
|
|
@ -35,7 +35,7 @@ defmodule Pinchflat.Sources.Source do
|
|||
)a
|
||||
|
||||
schema "sources" do
|
||||
field :friendly_name, :string
|
||||
field :custom_name, :string
|
||||
field :collection_name, :string
|
||||
field :collection_id, :string
|
||||
field :collection_type, Ecto.Enum, values: [:channel, :playlist]
|
||||
|
|
@ -58,7 +58,7 @@ defmodule Pinchflat.Sources.Source do
|
|||
def changeset(source, attrs) do
|
||||
source
|
||||
|> cast(attrs, @allowed_fields)
|
||||
|> dynamic_default(:friendly_name, fn cs -> get_field(cs, :collection_name) end)
|
||||
|> dynamic_default(:custom_name, fn cs -> get_field(cs, :collection_name) end)
|
||||
|> validate_required(@required_fields)
|
||||
|> unique_constraint([:collection_id, :media_profile_id])
|
||||
end
|
||||
|
|
|
|||
|
|
@ -29,7 +29,8 @@ defmodule Pinchflat.Profiles.MediaProfile do
|
|||
schema "media_profiles" do
|
||||
field :name, :string
|
||||
|
||||
field :output_path_template, :string, default: "/{{ channel }}/{{ title }}/{{ title }} [{{ id }}].{{ ext }}"
|
||||
field :output_path_template, :string,
|
||||
default: "/{{ source_custom_name }}/{{ title }}/{{ title }} [{{ id }}].{{ ext }}"
|
||||
|
||||
field :download_subs, :boolean, default: true
|
||||
field :download_auto_subs, :boolean, default: true
|
||||
|
|
|
|||
|
|
@ -5,21 +5,17 @@ defmodule Pinchflat.Profiles.Options.YtDlp.DownloadOptionBuilder do
|
|||
IDEA: consider making this a behaviour so I can add other backends later
|
||||
"""
|
||||
|
||||
alias Pinchflat.Profiles.MediaProfile
|
||||
alias Pinchflat.Media.MediaItem
|
||||
alias Pinchflat.Profiles.Options.YtDlp.OutputPathBuilder
|
||||
|
||||
@doc """
|
||||
Builds the options for yt-dlp to download media based on the given media profile.
|
||||
Builds the options for yt-dlp to download media based on the given media's profile.
|
||||
|
||||
IDEA: consider adding the ability to pass in a second argument to override
|
||||
these options
|
||||
"""
|
||||
def build(%MediaProfile{} = media_profile) do
|
||||
# NOTE: I'll be hardcoding most things for now (esp. options to help me test) -
|
||||
# add more configuration later as I build out the models. Walk before you can run!
|
||||
|
||||
# NOTE: Looks like you can put different media types in different directories.
|
||||
# see: https://github.com/yt-dlp/yt-dlp#output-template
|
||||
def build(%MediaItem{} = media_item_with_preloads) do
|
||||
media_profile = media_item_with_preloads.source.media_profile
|
||||
|
||||
built_options =
|
||||
default_options() ++
|
||||
|
|
@ -27,12 +23,11 @@ defmodule Pinchflat.Profiles.Options.YtDlp.DownloadOptionBuilder do
|
|||
thumbnail_options(media_profile) ++
|
||||
metadata_options(media_profile) ++
|
||||
quality_options(media_profile) ++
|
||||
output_options(media_profile)
|
||||
output_options(media_item_with_preloads)
|
||||
|
||||
{:ok, built_options}
|
||||
end
|
||||
|
||||
# This will be updated a lot as I add new options to profiles
|
||||
defp default_options do
|
||||
[:no_progress, :windows_filenames]
|
||||
end
|
||||
|
|
@ -101,14 +96,25 @@ defmodule Pinchflat.Profiles.Options.YtDlp.DownloadOptionBuilder do
|
|||
end
|
||||
end
|
||||
|
||||
defp output_options(media_profile) do
|
||||
{:ok, output_path} = OutputPathBuilder.build(media_profile.output_path_template)
|
||||
defp output_options(media_item_with_preloads) do
|
||||
media_profile = media_item_with_preloads.source.media_profile
|
||||
additional_options_map = output_options_map(media_item_with_preloads)
|
||||
{:ok, output_path} = OutputPathBuilder.build(media_profile.output_path_template, additional_options_map)
|
||||
|
||||
[
|
||||
output: Path.join(base_directory(), output_path)
|
||||
]
|
||||
end
|
||||
|
||||
defp output_options_map(media_item_with_preloads) do
|
||||
source = media_item_with_preloads.source
|
||||
|
||||
%{
|
||||
"source_custom_name" => source.custom_name,
|
||||
"source_collection_type" => source.collection_type
|
||||
}
|
||||
end
|
||||
|
||||
defp base_directory do
|
||||
Application.get_env(:pinchflat, :media_directory)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -8,13 +8,16 @@ defmodule Pinchflat.Profiles.Options.YtDlp.OutputPathBuilder do
|
|||
alias Pinchflat.RenderedString.Parser, as: TemplateParser
|
||||
|
||||
@doc """
|
||||
Builds the actual final filepath from a given template.
|
||||
Builds the actual final filepath from a given template. Optionally, you can pass in
|
||||
a map of additional options to be used in the template.
|
||||
|
||||
Translates liquid-style templates into yt-dlp-style templates,
|
||||
leaving yt-dlp syntax intact.
|
||||
"""
|
||||
def build(template_string) do
|
||||
TemplateParser.parse(template_string, custom_yt_dlp_option_map(), &identifier_fn/2)
|
||||
def build(template_string, additional_template_options \\ %{}) do
|
||||
combined_options = Map.merge(custom_yt_dlp_option_map(), additional_template_options)
|
||||
|
||||
TemplateParser.parse(template_string, combined_options, &identifier_fn/2)
|
||||
end
|
||||
|
||||
# The `nil` case simply wraps the identifier in yt-dlp-style syntax. This assumes that
|
||||
|
|
@ -30,6 +33,9 @@ defmodule Pinchflat.Profiles.Options.YtDlp.OutputPathBuilder do
|
|||
end
|
||||
end
|
||||
|
||||
# This isn't the only source for custom options, since they can be passed in my the caller.
|
||||
# `download_option_builder` is the most likely place for other custom options to be added,
|
||||
# but if in doubt just search the codebase for `OutputPathBuilder.build`.
|
||||
defp custom_yt_dlp_option_map do
|
||||
%{
|
||||
# Individual parts of the upload date
|
||||
|
|
|
|||
|
|
@ -15,8 +15,9 @@ defmodule Pinchflat.Workers.MediaIndexingWorker do
|
|||
@doc """
|
||||
The ID is that of a source _record_, not a YouTube channel/playlist ID. Indexes
|
||||
the provided source, kicks off downloads for each new MediaItem, and
|
||||
reschedules the job to run again in the future (as determined by the
|
||||
souce's `index_frequency_minutes` field).
|
||||
reschedules the job to run again in the future. It will ALWAYS index a source
|
||||
if it's never been indexed before, but rescheduling is determined by the
|
||||
`index_frequency_minutes` field.
|
||||
|
||||
README: Re-scheduling here works a little different than you may expect.
|
||||
The reschedule time is relative to the time the job has actually _completed_.
|
||||
|
|
@ -39,18 +40,31 @@ defmodule Pinchflat.Workers.MediaIndexingWorker do
|
|||
def perform(%Oban.Job{args: %{"id" => source_id}}) do
|
||||
source = Sources.get_source!(source_id)
|
||||
|
||||
if source.index_frequency_minutes <= 0 do
|
||||
:ok
|
||||
else
|
||||
index_media_and_reschedule(source)
|
||||
case {source.index_frequency_minutes, source.last_indexed_at} do
|
||||
{index_freq, _} when index_freq > 0 ->
|
||||
# If the indexing is on a schedule simply run indexing and reschedule
|
||||
index_media(source)
|
||||
reschedule_indexing(source)
|
||||
|
||||
{_, nil} ->
|
||||
# If the source has never been indexed, index it once
|
||||
# even if it's not meant to reschedule
|
||||
index_media(source)
|
||||
|
||||
_ ->
|
||||
# If the source HAS been indexed and is not meant to reschedule,
|
||||
# perform a no-op
|
||||
:ok
|
||||
end
|
||||
end
|
||||
|
||||
defp index_media_and_reschedule(source) do
|
||||
defp index_media(source) do
|
||||
SourceTasks.index_media_items(source)
|
||||
# This method handles the case where a source is set to not download media
|
||||
SourceTasks.enqueue_pending_media_tasks(source)
|
||||
end
|
||||
|
||||
defp reschedule_indexing(source) do
|
||||
source
|
||||
|> Map.take([:id])
|
||||
|> MediaIndexingWorker.new(schedule_in: source.index_frequency_minutes * 60)
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@
|
|||
<section>
|
||||
<strong>Source:</strong>
|
||||
<.inline_link href={~p"/sources/#{@media_item.source_id}"}>
|
||||
<%= @media_item.source.friendly_name %>
|
||||
<%= @media_item.source.custom_name %>
|
||||
</.inline_link>
|
||||
</section>
|
||||
|
||||
|
|
|
|||
|
|
@ -30,7 +30,13 @@ defmodule PinchflatWeb.MediaProfiles.MediaProfileHTML do
|
|||
end
|
||||
|
||||
def custom_output_template_options do
|
||||
~w(upload_day upload_month upload_year)a
|
||||
%{
|
||||
upload_day: nil,
|
||||
upload_month: nil,
|
||||
upload_year: nil,
|
||||
source_custom_name: "the name of the sources that use this profile",
|
||||
source_collection_type: "the collection type of the sources that use this profile. Either 'channel' or 'playlist'"
|
||||
}
|
||||
end
|
||||
|
||||
def common_output_template_options do
|
||||
|
|
|
|||
|
|
@ -46,8 +46,8 @@
|
|||
</section>
|
||||
|
||||
<h2 class="text-xl font-bold mb-2">Template Options</h2>
|
||||
<section class="ml-2 md:ml-4 mb-4 max-w-prose">
|
||||
<p>
|
||||
<section class="ml-2 md:ml-4 mb-4">
|
||||
<p class="max-w-prose">
|
||||
Any single-word <code class="text-sm">yt-dlp</code>
|
||||
option
|
||||
<.inline_link href="https://github.com/yt-dlp/yt-dlp?tab=readme-ov-file#output-template">
|
||||
|
|
@ -58,8 +58,9 @@
|
|||
</p>
|
||||
<h3 class="text-lg font-bold mb-2">Custom Aliases</h3>
|
||||
<ul class="list-disc list-inside ml-2 md:ml-5">
|
||||
<li :for={opt <- custom_output_template_options()}>
|
||||
<.inline_code>{{ <%= opt %> }}</.inline_code>
|
||||
<li :for={{k, v} <- custom_output_template_options()}>
|
||||
<.inline_code>{{ <%= k %> }}</.inline_code>
|
||||
<span :if={v}>- <%= v %></span>
|
||||
</li>
|
||||
</ul>
|
||||
<h3 class="text-lg font-bold mb-2">Common Options</h3>
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@
|
|||
<:tab title="Sources">
|
||||
<.table rows={@media_profile.sources} table_class="text-black dark:text-white">
|
||||
<:col :let={source} label="Name">
|
||||
<%= source.friendly_name || source.collection_name %>
|
||||
<%= source.custom_name || source.collection_name %>
|
||||
</:col>
|
||||
<:col :let={source} label="Type"><%= source.collection_type %></:col>
|
||||
<:col :let={source} label="Should Download?">
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@
|
|||
<div class="flex flex-col gap-10 min-w-max">
|
||||
<.table rows={@sources} table_class="text-black dark:text-white">
|
||||
<:col :let={source} label="Name">
|
||||
<%= source.friendly_name || source.collection_name %>
|
||||
<%= source.custom_name || source.collection_name %>
|
||||
</:col>
|
||||
<:col :let={source} label="Type"><%= source.collection_type %></:col>
|
||||
<:col :let={source} label="Should Download?">
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
</.error>
|
||||
|
||||
<.input
|
||||
field={f[:friendly_name]}
|
||||
field={f[:custom_name]}
|
||||
type="text"
|
||||
label="Custom Name"
|
||||
help="Something descriptive. Does not impact indexing or downloading"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,7 @@
|
|||
defmodule Pinchflat.Repo.Migrations.RenameFriendlyNameToCustomName do
|
||||
use Ecto.Migration
|
||||
|
||||
def change do
|
||||
rename table(:sources), :friendly_name, to: :custom_name
|
||||
end
|
||||
end
|
||||
|
|
@ -3,7 +3,7 @@ defmodule Pinchflat.MediaClient.Backends.YtDlp.OutputPathBuilderTest do
|
|||
|
||||
alias Pinchflat.Profiles.Options.YtDlp.OutputPathBuilder
|
||||
|
||||
describe "build/1" do
|
||||
describe "build/2" do
|
||||
test "it expands 'standard' curly brace variables in the template" do
|
||||
assert {:ok, res} = OutputPathBuilder.build("/videos/{{ title }}.{{ ext }}")
|
||||
|
||||
|
|
@ -16,6 +16,12 @@ defmodule Pinchflat.MediaClient.Backends.YtDlp.OutputPathBuilderTest do
|
|||
assert res == "/videos/%(upload_date>%Y)S.%(ext)S"
|
||||
end
|
||||
|
||||
test "it respects additional options" do
|
||||
assert {:ok, res} = OutputPathBuilder.build("/videos/{{ custom }}.{{ ext }}", %{"custom" => "test"})
|
||||
|
||||
assert res == "/videos/test.%(ext)S"
|
||||
end
|
||||
|
||||
test "it leaves yt-dlp variables alone" do
|
||||
assert {:ok, res} = OutputPathBuilder.build("/videos/%(title)s.%(ext)s")
|
||||
|
||||
|
|
|
|||
|
|
@ -1,24 +1,40 @@
|
|||
defmodule Pinchflat.Profiles.Options.YtDlp.DownloadOptionBuilderTest do
|
||||
use ExUnit.Case, async: true
|
||||
use Pinchflat.DataCase
|
||||
import Pinchflat.MediaFixtures
|
||||
import Pinchflat.ProfilesFixtures
|
||||
import Pinchflat.SourcesFixtures
|
||||
|
||||
alias Pinchflat.Profiles.MediaProfile
|
||||
alias Pinchflat.Profiles
|
||||
alias Pinchflat.Profiles.Options.YtDlp.DownloadOptionBuilder
|
||||
|
||||
@media_profile %MediaProfile{
|
||||
output_path_template: "{{ title }}.%(ext)s"
|
||||
}
|
||||
setup do
|
||||
media_profile = media_profile_fixture(%{output_path_template: "{{ title }}.%(ext)s"})
|
||||
source = source_fixture(%{media_profile_id: media_profile.id, custom_name: "my source"})
|
||||
media_item = Repo.preload(media_item_fixture(source_id: source.id), source: :media_profile)
|
||||
|
||||
describe "build/1" do
|
||||
test "it generates an expanded output path based on the given template" do
|
||||
assert {:ok, res} = DownloadOptionBuilder.build(@media_profile)
|
||||
{:ok, media_item: media_item}
|
||||
end
|
||||
|
||||
describe "build/1 when testing output options" do
|
||||
test "it generates an expanded output path based on the given template", %{media_item: media_item} do
|
||||
assert {:ok, res} = DownloadOptionBuilder.build(media_item)
|
||||
|
||||
assert {:output, "/tmp/test/videos/%(title)S.%(ext)s"} in res
|
||||
end
|
||||
|
||||
test "it respects custom output path options", %{media_item: media_item} do
|
||||
media_item =
|
||||
update_media_profile_attribute(media_item, %{output_path_template: "{{ source_custom_name }}.%(ext)s"})
|
||||
|
||||
assert {:ok, res} = DownloadOptionBuilder.build(media_item)
|
||||
|
||||
assert {:output, "/tmp/test/videos/#{media_item.source.custom_name}.%(ext)s"} in res
|
||||
end
|
||||
end
|
||||
|
||||
describe "build/1 when testing default options" do
|
||||
test "it includes default options" do
|
||||
assert {:ok, res} = DownloadOptionBuilder.build(@media_profile)
|
||||
test "it includes default options", %{media_item: media_item} do
|
||||
assert {:ok, res} = DownloadOptionBuilder.build(media_item)
|
||||
|
||||
assert :no_progress in res
|
||||
assert :windows_filenames in res
|
||||
|
|
@ -26,109 +42,93 @@ defmodule Pinchflat.Profiles.Options.YtDlp.DownloadOptionBuilderTest do
|
|||
end
|
||||
|
||||
describe "build/1 when testing subtitle options" do
|
||||
test "includes :write_subs option when specified" do
|
||||
media_profile = %MediaProfile{@media_profile | download_subs: true}
|
||||
test "includes :write_subs option when specified", %{media_item: media_item} do
|
||||
media_item = update_media_profile_attribute(media_item, %{download_subs: true})
|
||||
|
||||
assert {:ok, res} = DownloadOptionBuilder.build(media_profile)
|
||||
assert {:ok, res} = DownloadOptionBuilder.build(media_item)
|
||||
|
||||
assert :write_subs in res
|
||||
end
|
||||
|
||||
test "forces SRT format when download_subs is true" do
|
||||
media_profile = %MediaProfile{@media_profile | download_subs: true}
|
||||
test "forces SRT format when download_subs is true", %{media_item: media_item} do
|
||||
media_item = update_media_profile_attribute(media_item, %{download_subs: true})
|
||||
|
||||
assert {:ok, res} = DownloadOptionBuilder.build(media_profile)
|
||||
assert {:ok, res} = DownloadOptionBuilder.build(media_item)
|
||||
|
||||
assert {:convert_subs, "srt"} in res
|
||||
end
|
||||
|
||||
test "includes :write_auto_subs option when specified" do
|
||||
media_profile = %MediaProfile{@media_profile | download_subs: true, download_auto_subs: true}
|
||||
test "includes :write_auto_subs option when specified", %{media_item: media_item} do
|
||||
media_item = update_media_profile_attribute(media_item, %{download_subs: true, download_auto_subs: true})
|
||||
|
||||
assert {:ok, res} = DownloadOptionBuilder.build(media_profile)
|
||||
assert {:ok, res} = DownloadOptionBuilder.build(media_item)
|
||||
|
||||
assert :write_auto_subs in res
|
||||
end
|
||||
|
||||
test "doesn't include :write_auto_subs option when download_subs is false" do
|
||||
media_profile = %MediaProfile{@media_profile | download_subs: false, download_auto_subs: true}
|
||||
test "doesn't include :write_auto_subs option when download_subs is false", %{media_item: media_item} do
|
||||
media_item = update_media_profile_attribute(media_item, %{download_subs: false, download_auto_subs: true})
|
||||
|
||||
assert {:ok, res} = DownloadOptionBuilder.build(media_profile)
|
||||
assert {:ok, res} = DownloadOptionBuilder.build(media_item)
|
||||
|
||||
refute :write_auto_subs in res
|
||||
end
|
||||
|
||||
test "includes :embed_subs option when specified" do
|
||||
media_profile = %MediaProfile{@media_profile | embed_subs: true}
|
||||
test "includes :embed_subs option when specified", %{media_item: media_item} do
|
||||
media_item = update_media_profile_attribute(media_item, %{embed_subs: true})
|
||||
|
||||
assert {:ok, res} = DownloadOptionBuilder.build(media_profile)
|
||||
assert {:ok, res} = DownloadOptionBuilder.build(media_item)
|
||||
|
||||
assert :embed_subs in res
|
||||
end
|
||||
|
||||
test "includes sub_langs option when download_subs is true" do
|
||||
media_profile = %MediaProfile{@media_profile | download_subs: true, sub_langs: "en"}
|
||||
test "includes sub_langs option when download_subs is true", %{media_item: media_item} do
|
||||
media_item = update_media_profile_attribute(media_item, %{download_subs: true, sub_langs: "en"})
|
||||
|
||||
assert {:ok, res} = DownloadOptionBuilder.build(media_profile)
|
||||
assert {:ok, res} = DownloadOptionBuilder.build(media_item)
|
||||
|
||||
assert {:sub_langs, "en"} in res
|
||||
end
|
||||
|
||||
test "includes sub_langs option when embed_subs is true" do
|
||||
media_profile = %MediaProfile{@media_profile | embed_subs: true, sub_langs: "en"}
|
||||
test "includes sub_langs option when embed_subs is true", %{media_item: media_item} do
|
||||
media_item = update_media_profile_attribute(media_item, %{embed_subs: true, sub_langs: "en"})
|
||||
|
||||
assert {:ok, res} = DownloadOptionBuilder.build(media_profile)
|
||||
assert {:ok, res} = DownloadOptionBuilder.build(media_item)
|
||||
|
||||
assert {:sub_langs, "en"} in res
|
||||
end
|
||||
|
||||
test "doesn't include sub_langs option when neither downloading nor embedding" do
|
||||
media_profile = %MediaProfile{
|
||||
@media_profile
|
||||
| embed_subs: false,
|
||||
download_subs: false,
|
||||
sub_langs: "en"
|
||||
}
|
||||
test "doesn't include sub_langs option when neither downloading nor embedding", %{media_item: media_item} do
|
||||
media_item =
|
||||
update_media_profile_attribute(media_item, %{embed_subs: false, download_subs: false, sub_langs: "en"})
|
||||
|
||||
assert {:ok, res} = DownloadOptionBuilder.build(media_profile)
|
||||
assert {:ok, res} = DownloadOptionBuilder.build(media_item)
|
||||
|
||||
refute {:sub_langs, "en"} in res
|
||||
end
|
||||
|
||||
test "other struct attributes are ignored" do
|
||||
media_profile = %MediaProfile{@media_profile | id: -1}
|
||||
|
||||
assert {:ok, res} = DownloadOptionBuilder.build(media_profile)
|
||||
|
||||
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}
|
||||
test "includes :write_thumbnail option when specified", %{media_item: media_item} do
|
||||
media_item = update_media_profile_attribute(media_item, %{download_thumbnail: true})
|
||||
|
||||
assert {:ok, res} = DownloadOptionBuilder.build(media_profile)
|
||||
assert {:ok, res} = DownloadOptionBuilder.build(media_item)
|
||||
|
||||
assert :write_thumbnail in res
|
||||
end
|
||||
|
||||
test "includes :embed_thumbnail option when specified" do
|
||||
media_profile = %MediaProfile{@media_profile | embed_thumbnail: true}
|
||||
test "includes :embed_thumbnail option when specified", %{media_item: media_item} do
|
||||
media_item = update_media_profile_attribute(media_item, %{embed_thumbnail: true})
|
||||
|
||||
assert {:ok, res} = DownloadOptionBuilder.build(media_profile)
|
||||
assert {:ok, res} = DownloadOptionBuilder.build(media_item)
|
||||
|
||||
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
|
||||
}
|
||||
test "doesn't include these options when not specified", %{media_item: media_item} do
|
||||
media_item = update_media_profile_attribute(media_item, %{embed_thumbnail: false, download_thumbnail: false})
|
||||
|
||||
assert {:ok, res} = DownloadOptionBuilder.build(media_profile)
|
||||
assert {:ok, res} = DownloadOptionBuilder.build(media_item)
|
||||
|
||||
refute :write_thumbnail in res
|
||||
refute :embed_thumbnail in res
|
||||
|
|
@ -136,31 +136,27 @@ defmodule Pinchflat.Profiles.Options.YtDlp.DownloadOptionBuilderTest do
|
|||
end
|
||||
|
||||
describe "build/1 when testing metadata options" do
|
||||
test "includes :write_info_json option when specified" do
|
||||
media_profile = %MediaProfile{@media_profile | download_metadata: true}
|
||||
test "includes :write_info_json option when specified", %{media_item: media_item} do
|
||||
media_item = update_media_profile_attribute(media_item, %{download_metadata: true})
|
||||
|
||||
assert {:ok, res} = DownloadOptionBuilder.build(media_profile)
|
||||
assert {:ok, res} = DownloadOptionBuilder.build(media_item)
|
||||
|
||||
assert :write_info_json in res
|
||||
assert :clean_info_json in res
|
||||
end
|
||||
|
||||
test "includes :embed_metadata option when specified" do
|
||||
media_profile = %MediaProfile{@media_profile | embed_metadata: true}
|
||||
test "includes :embed_metadata option when specified", %{media_item: media_item} do
|
||||
media_item = update_media_profile_attribute(media_item, %{embed_metadata: true})
|
||||
|
||||
assert {:ok, res} = DownloadOptionBuilder.build(media_profile)
|
||||
assert {:ok, res} = DownloadOptionBuilder.build(media_item)
|
||||
|
||||
assert :embed_metadata in res
|
||||
end
|
||||
|
||||
test "doesn't include these options when not specified" do
|
||||
media_profile = %MediaProfile{
|
||||
@media_profile
|
||||
| embed_metadata: false,
|
||||
download_metadata: false
|
||||
}
|
||||
test "doesn't include these options when not specified", %{media_item: media_item} do
|
||||
media_item = update_media_profile_attribute(media_item, %{embed_metadata: false, download_metadata: false})
|
||||
|
||||
assert {:ok, res} = DownloadOptionBuilder.build(media_profile)
|
||||
assert {:ok, res} = DownloadOptionBuilder.build(media_item)
|
||||
|
||||
refute :write_info_json in res
|
||||
refute :clean_info_json in res
|
||||
|
|
@ -169,10 +165,22 @@ defmodule Pinchflat.Profiles.Options.YtDlp.DownloadOptionBuilderTest do
|
|||
end
|
||||
|
||||
describe "build/1 when testing quality options" do
|
||||
test "it includes quality options" do
|
||||
assert {:ok, res} = DownloadOptionBuilder.build(@media_profile)
|
||||
test "it includes quality options", %{media_item: media_item} do
|
||||
media_item = update_media_profile_attribute(media_item, %{preferred_resolution: :"1080p"})
|
||||
|
||||
assert {:ok, res} = DownloadOptionBuilder.build(media_item)
|
||||
|
||||
assert {:format_sort, "res:1080,+codec:avc:m4a"} in res
|
||||
end
|
||||
end
|
||||
|
||||
defp update_media_profile_attribute(media_item_with_preloads, attrs) do
|
||||
media_item_with_preloads.source.media_profile
|
||||
|> Profiles.change_media_profile(attrs)
|
||||
|> Repo.update!()
|
||||
|
||||
media_item_with_preloads
|
||||
|> Repo.reload()
|
||||
|> Repo.preload(source: :media_profile)
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -66,18 +66,18 @@ defmodule Pinchflat.SourcesTest do
|
|||
assert String.starts_with?(source.collection_id, "some_playlist_id_")
|
||||
end
|
||||
|
||||
test "you can specify a custom friendly_name" do
|
||||
test "you can specify a custom custom_name" do
|
||||
expect(YtDlpRunnerMock, :run, &channel_mock/3)
|
||||
|
||||
valid_attrs = %{
|
||||
media_profile_id: media_profile_fixture().id,
|
||||
original_url: "https://www.youtube.com/channel/abc123",
|
||||
friendly_name: "some custom name"
|
||||
custom_name: "some custom name"
|
||||
}
|
||||
|
||||
assert {:ok, %Source{} = source} = Sources.create_source(valid_attrs)
|
||||
|
||||
assert source.friendly_name == "some custom name"
|
||||
assert source.custom_name == "some custom name"
|
||||
end
|
||||
|
||||
test "friendly name is pulled from collection_name if not specified" do
|
||||
|
|
@ -90,7 +90,7 @@ defmodule Pinchflat.SourcesTest do
|
|||
|
||||
assert {:ok, %Source{} = source} = Sources.create_source(valid_attrs)
|
||||
|
||||
assert source.friendly_name == "some channel name"
|
||||
assert source.custom_name == "some channel name"
|
||||
end
|
||||
|
||||
test "collection_type is inferred from source details" do
|
||||
|
|
|
|||
|
|
@ -12,23 +12,6 @@ defmodule Pinchflat.Workers.MediaIndexingWorkerTest do
|
|||
setup :verify_on_exit!
|
||||
|
||||
describe "perform/1" do
|
||||
test "it does not do any indexing if the source shouldn't be indexed" do
|
||||
expect(YtDlpRunnerMock, :run, 0, fn _url, _opts, _ot -> {:ok, ""} end)
|
||||
|
||||
source = source_fixture(index_frequency_minutes: -1)
|
||||
|
||||
perform_job(MediaIndexingWorker, %{id: source.id})
|
||||
end
|
||||
|
||||
test "it does not reschedule if the source shouldn't be indexed" do
|
||||
expect(YtDlpRunnerMock, :run, 0, fn _url, _opts, _ot -> {:ok, ""} end)
|
||||
|
||||
source = source_fixture(index_frequency_minutes: -1)
|
||||
perform_job(MediaIndexingWorker, %{id: source.id})
|
||||
|
||||
refute_enqueued(worker: MediaIndexingWorker, args: %{"id" => source.id})
|
||||
end
|
||||
|
||||
test "it indexes the source if it should be indexed" do
|
||||
expect(YtDlpRunnerMock, :run, fn _url, _opts, _ot -> {:ok, ""} end)
|
||||
|
||||
|
|
@ -37,6 +20,31 @@ defmodule Pinchflat.Workers.MediaIndexingWorkerTest do
|
|||
perform_job(MediaIndexingWorker, %{id: source.id})
|
||||
end
|
||||
|
||||
test "it indexes the source no matter what if the source has never been indexed before" do
|
||||
expect(YtDlpRunnerMock, :run, fn _url, _opts, _ot -> {:ok, ""} end)
|
||||
|
||||
source = source_fixture(index_frequency_minutes: 0, last_indexed_at: nil)
|
||||
|
||||
perform_job(MediaIndexingWorker, %{id: source.id})
|
||||
end
|
||||
|
||||
test "it does not do any indexing if the source has been indexed and shouldn't be rescheduled" do
|
||||
expect(YtDlpRunnerMock, :run, 0, fn _url, _opts, _ot -> {:ok, ""} end)
|
||||
|
||||
source = source_fixture(index_frequency_minutes: -1, last_indexed_at: DateTime.utc_now())
|
||||
|
||||
perform_job(MediaIndexingWorker, %{id: source.id})
|
||||
end
|
||||
|
||||
test "it does not reschedule if the source shouldn't be indexed" do
|
||||
stub(YtDlpRunnerMock, :run, fn _url, _opts, _ot -> {:ok, ""} end)
|
||||
|
||||
source = source_fixture(index_frequency_minutes: -1)
|
||||
perform_job(MediaIndexingWorker, %{id: source.id})
|
||||
|
||||
refute_enqueued(worker: MediaIndexingWorker, args: %{"id" => source.id})
|
||||
end
|
||||
|
||||
test "it kicks off a download job for each pending media item" do
|
||||
expect(YtDlpRunnerMock, :run, fn _url, _opts, _ot -> {:ok, source_attributes_return_fixture()} end)
|
||||
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ defmodule Pinchflat.SourcesFixtures do
|
|||
collection_name: "Source ##{:rand.uniform(1_000_000)}",
|
||||
collection_id: Base.encode16(:crypto.hash(:md5, "#{:rand.uniform(1_000_000)}")),
|
||||
collection_type: "channel",
|
||||
friendly_name: "Cool and good internal name!",
|
||||
custom_name: "Cool and good internal name!",
|
||||
original_url: "https://www.youtube.com/channel/#{Faker.String.base64(12)}",
|
||||
media_profile_id: ProfilesFixtures.media_profile_fixture().id,
|
||||
index_frequency_minutes: 60
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue