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:
Kieran 2024-03-02 12:15:10 -08:00 committed by GitHub
parent 9e75092876
commit 09448e3fcc
18 changed files with 200 additions and 139 deletions

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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)

View file

@ -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>

View file

@ -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

View file

@ -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>

View file

@ -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?">

View file

@ -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?">

View file

@ -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"

View file

@ -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

View file

@ -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")

View file

@ -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

View file

@ -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

View file

@ -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)

View file

@ -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