Compare commits
16 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| db859411dd | |||
| 0aa4b5b119 | |||
| 17f4bd70f7 | |||
| 2c717e8e7e | |||
| 3acdb5ccba | |||
| 0ba74264c4 | |||
| 72c82ee72f | |||
| 3018a9c46b | |||
| 314118341e | |||
| b4438c1ba5 | |||
| 725edc3fd8 | |||
| 1994ea5b08 | |||
| f9e4e44b0c | |||
| f2ee3d77a2 | |||
| 7fc70da14a | |||
| 8a0ae89bc0 |
@@ -36,17 +36,6 @@ let liveSocket = new LiveSocket('/live', Socket, {
|
|||||||
window.Alpine.clone(from, to)
|
window.Alpine.clone(from, to)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
|
||||||
hooks: {
|
|
||||||
supressEnterSubmission: {
|
|
||||||
mounted() {
|
|
||||||
this.el.addEventListener('keypress', (event) => {
|
|
||||||
if (event.key === 'Enter') {
|
|
||||||
event.preventDefault()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -29,9 +29,6 @@ RUN yt-dlp -U
|
|||||||
# Download Apprise
|
# Download Apprise
|
||||||
RUN python3 -m pip install -U apprise --break-system-packages
|
RUN python3 -m pip install -U apprise --break-system-packages
|
||||||
|
|
||||||
# Download Mutagen for music thumbnail generation
|
|
||||||
RUN python3 -m pip install -U mutagen --break-system-packages
|
|
||||||
|
|
||||||
# Set the locale
|
# Set the locale
|
||||||
RUN sed -i '/en_US.UTF-8/s/^# //g' /etc/locale.gen && locale-gen
|
RUN sed -i '/en_US.UTF-8/s/^# //g' /etc/locale.gen && locale-gen
|
||||||
ENV LANG en_US.UTF-8
|
ENV LANG en_US.UTF-8
|
||||||
|
|||||||
@@ -42,14 +42,8 @@ defmodule Pinchflat.Downloading.DownloadOptionBuilder do
|
|||||||
end
|
end
|
||||||
|
|
||||||
defp default_options do
|
defp default_options do
|
||||||
[
|
# Add force-overwrites to make sure redownloading works
|
||||||
:no_progress,
|
[:no_progress, :windows_filenames, :force_overwrites]
|
||||||
:windows_filenames,
|
|
||||||
# Add force-overwrites to make sure redownloading works
|
|
||||||
:force_overwrites,
|
|
||||||
# This makes the date metadata conform to what jellyfin expects
|
|
||||||
parse_metadata: "%(upload_date>%Y-%m-%d)s:(?P<meta_date>.+)"
|
|
||||||
]
|
|
||||||
end
|
end
|
||||||
|
|
||||||
defp subtitle_options(media_profile) do
|
defp subtitle_options(media_profile) do
|
||||||
|
|||||||
@@ -7,11 +7,9 @@ defmodule Pinchflat.Downloading.DownloadingHelpers do
|
|||||||
|
|
||||||
require Logger
|
require Logger
|
||||||
|
|
||||||
alias Pinchflat.Repo
|
|
||||||
alias Pinchflat.Media
|
alias Pinchflat.Media
|
||||||
alias Pinchflat.Tasks
|
alias Pinchflat.Tasks
|
||||||
alias Pinchflat.Sources.Source
|
alias Pinchflat.Sources.Source
|
||||||
alias Pinchflat.Media.MediaItem
|
|
||||||
alias Pinchflat.Downloading.MediaDownloadWorker
|
alias Pinchflat.Downloading.MediaDownloadWorker
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
@@ -45,23 +43,4 @@ defmodule Pinchflat.Downloading.DownloadingHelpers do
|
|||||||
|> Media.list_pending_media_items_for()
|
|> Media.list_pending_media_items_for()
|
||||||
|> Enum.each(&Tasks.delete_pending_tasks_for/1)
|
|> Enum.each(&Tasks.delete_pending_tasks_for/1)
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc """
|
|
||||||
Takes a single media item and enqueues a download job if the media should be
|
|
||||||
downloaded, based on the source's download settings and whether media is
|
|
||||||
considered pending.
|
|
||||||
|
|
||||||
Returns {:ok, %Task{}} | {:error, :should_not_download} | {:error, any()}
|
|
||||||
"""
|
|
||||||
def kickoff_download_if_pending(%MediaItem{} = media_item) do
|
|
||||||
media_item = Repo.preload(media_item, :source)
|
|
||||||
|
|
||||||
if media_item.source.download_media && Media.pending_download?(media_item) do
|
|
||||||
Logger.info("Kicking off download for media item ##{media_item.id} (#{media_item.media_id})")
|
|
||||||
|
|
||||||
MediaDownloadWorker.kickoff_with_task(media_item)
|
|
||||||
else
|
|
||||||
{:error, :should_not_download}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -5,45 +5,64 @@ defmodule Pinchflat.FastIndexing.FastIndexingHelpers do
|
|||||||
Many of these methods are made to be kickoff or be consumed by workers.
|
Many of these methods are made to be kickoff or be consumed by workers.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
require Logger
|
|
||||||
|
|
||||||
alias Pinchflat.Repo
|
alias Pinchflat.Repo
|
||||||
alias Pinchflat.Media
|
alias Pinchflat.Media
|
||||||
alias Pinchflat.Sources.Source
|
alias Pinchflat.Sources.Source
|
||||||
alias Pinchflat.Media.MediaQuery
|
alias Pinchflat.Media.MediaQuery
|
||||||
alias Pinchflat.FastIndexing.YoutubeRss
|
alias Pinchflat.FastIndexing.YoutubeRss
|
||||||
alias Pinchflat.Downloading.DownloadingHelpers
|
alias Pinchflat.Downloading.MediaDownloadWorker
|
||||||
|
alias Pinchflat.FastIndexing.MediaIndexingWorker
|
||||||
|
|
||||||
alias Pinchflat.YtDlp.Media, as: YtDlpMedia
|
alias Pinchflat.YtDlp.Media, as: YtDlpMedia
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Fetches new media IDs from a source's YouTube RSS feed, indexes them, and kicks off downloading
|
Fetches new media IDs from a source's YouTube RSS feed and kicks off indexing tasks
|
||||||
tasks for any pending media items. See comments in `FastIndexingWorker` for more info on the
|
for any new media items. See comments in `MediaIndexingWorker` for more info on the
|
||||||
order of operations and how this fits into the indexing process.
|
order of operations and how this fits into the indexing process.
|
||||||
|
|
||||||
Returns [%MediaItem{}] where each item is a new media item that was created _but not necessarily
|
Despite the similar name to `kickoff_fast_indexing_task`, this does work differently.
|
||||||
downloaded_.
|
`kickoff_fast_indexing_task` starts a task that _calls_ this function whereas this
|
||||||
|
function starts individual indexing tasks for each new media item. I think it does
|
||||||
|
make sense grammatically, but I could see how that's confusing.
|
||||||
|
|
||||||
|
Returns [binary()] where each binary is the media ID of a new media item.
|
||||||
"""
|
"""
|
||||||
def kickoff_download_tasks_from_youtube_rss_feed(%Source{} = source) do
|
def kickoff_indexing_tasks_from_youtube_rss_feed(%Source{} = source) do
|
||||||
{:ok, media_ids} = YoutubeRss.get_recent_media_ids_from_rss(source)
|
{:ok, media_ids} = YoutubeRss.get_recent_media_ids_from_rss(source)
|
||||||
existing_media_items = list_media_items_by_media_id_for(source, media_ids)
|
existing_media_items = list_media_items_by_media_id_for(source, media_ids)
|
||||||
new_media_ids = media_ids -- Enum.map(existing_media_items, & &1.media_id)
|
new_media_ids = media_ids -- Enum.map(existing_media_items, & &1.media_id)
|
||||||
|
|
||||||
maybe_new_media_items =
|
Enum.each(new_media_ids, fn media_id ->
|
||||||
Enum.map(new_media_ids, fn media_id ->
|
url = "https://www.youtube.com/watch?v=#{media_id}"
|
||||||
case create_media_item_from_media_id(source, media_id) do
|
|
||||||
{:ok, media_item} ->
|
|
||||||
media_item
|
|
||||||
|
|
||||||
err ->
|
MediaIndexingWorker.kickoff_with_task(source, url)
|
||||||
Logger.error("Error creating media item '#{media_id}' from URL: #{inspect(err)}")
|
end)
|
||||||
nil
|
|
||||||
|
new_media_ids
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Indexes a single media item for a source and enqueues a download job if the
|
||||||
|
media should be downloaded. This method creates the media item record so it's
|
||||||
|
the one-stop-shop for adding a media item (and possibly downloading it) just
|
||||||
|
by a URL and source.
|
||||||
|
|
||||||
|
Returns {:ok, media_item} | {:error, any()}
|
||||||
|
"""
|
||||||
|
def index_and_enqueue_download_for_media_item(%Source{} = source, url) do
|
||||||
|
maybe_media_item = create_media_item_from_url(source, url)
|
||||||
|
|
||||||
|
case maybe_media_item do
|
||||||
|
{:ok, media_item} ->
|
||||||
|
if source.download_media && Media.pending_download?(media_item) do
|
||||||
|
MediaDownloadWorker.kickoff_with_task(media_item)
|
||||||
end
|
end
|
||||||
end)
|
|
||||||
|
|
||||||
DownloadingHelpers.enqueue_pending_download_tasks(source)
|
{:ok, media_item}
|
||||||
|
|
||||||
Enum.filter(maybe_new_media_items, & &1)
|
err ->
|
||||||
|
err
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp list_media_items_by_media_id_for(source, media_ids) do
|
defp list_media_items_by_media_id_for(source, media_ids) do
|
||||||
@@ -53,15 +72,9 @@ defmodule Pinchflat.FastIndexing.FastIndexingHelpers do
|
|||||||
|> Repo.all()
|
|> Repo.all()
|
||||||
end
|
end
|
||||||
|
|
||||||
defp create_media_item_from_media_id(source, media_id) do
|
defp create_media_item_from_url(source, url) do
|
||||||
url = "https://www.youtube.com/watch?v=#{media_id}"
|
{:ok, media_attrs} = YtDlpMedia.get_media_attributes(url)
|
||||||
|
|
||||||
case YtDlpMedia.get_media_attributes(url) do
|
Media.create_media_item_from_backend_attrs(source, media_attrs)
|
||||||
{:ok, media_attrs} ->
|
|
||||||
Media.create_media_item_from_backend_attrs(source, media_attrs)
|
|
||||||
|
|
||||||
err ->
|
|
||||||
err
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ defmodule Pinchflat.FastIndexing.FastIndexingWorker do
|
|||||||
|
|
||||||
alias __MODULE__
|
alias __MODULE__
|
||||||
alias Pinchflat.Tasks
|
alias Pinchflat.Tasks
|
||||||
alias Pinchflat.Media
|
|
||||||
alias Pinchflat.Sources
|
alias Pinchflat.Sources
|
||||||
alias Pinchflat.Settings
|
alias Pinchflat.Settings
|
||||||
alias Pinchflat.Sources.Source
|
alias Pinchflat.Sources.Source
|
||||||
@@ -29,21 +28,9 @@ defmodule Pinchflat.FastIndexing.FastIndexingWorker do
|
|||||||
end
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Similar to `MediaCollectionIndexingWorker`, but for working with RSS feeds.
|
Kicks off the fast indexing process for a source, reschedules the job to run again
|
||||||
`MediaCollectionIndexingWorker` should be preferred in general, but this is
|
once complete. See `MediaCollectionIndexingWorker` and `MediaIndexingWorker` comments
|
||||||
useful for downloading small batches of media items via fast indexing.
|
for more
|
||||||
|
|
||||||
Only kicks off downloads for media that _should_ be downloaded
|
|
||||||
(ie: the source is set to download and the media matches the profile's format preferences)
|
|
||||||
|
|
||||||
Order of operations:
|
|
||||||
1. FastIndexingWorker (this module) periodically checks the YouTube RSS feed for new media.
|
|
||||||
with `FastIndexingHelpers.kickoff_download_tasks_from_youtube_rss_feed`
|
|
||||||
2. If the above `kickoff_download_tasks_from_youtube_rss_feed` finds new media items in the RSS feed,
|
|
||||||
it indexes them with a yt-dlp call to create the media item records then kicks off downloading
|
|
||||||
tasks (MediaDownloadWorker) for any new media items _that should be downloaded_.
|
|
||||||
3. Once downloads are kicked off, this worker sends a notification to the apprise server if applicable
|
|
||||||
then reschedules itself to run again in the future.
|
|
||||||
|
|
||||||
Returns :ok | {:ok, :job_exists} | {:ok, %Task{}}
|
Returns :ok | {:ok, :job_exists} | {:ok, %Task{}}
|
||||||
"""
|
"""
|
||||||
@@ -52,7 +39,7 @@ defmodule Pinchflat.FastIndexing.FastIndexingWorker do
|
|||||||
source = Sources.get_source!(source_id)
|
source = Sources.get_source!(source_id)
|
||||||
|
|
||||||
if source.fast_index do
|
if source.fast_index do
|
||||||
perform_indexing_and_send_notification(source)
|
perform_indexing_and_notification(source)
|
||||||
reschedule_indexing(source)
|
reschedule_indexing(source)
|
||||||
else
|
else
|
||||||
:ok
|
:ok
|
||||||
@@ -62,17 +49,11 @@ defmodule Pinchflat.FastIndexing.FastIndexingWorker do
|
|||||||
Ecto.StaleEntryError -> Logger.info("#{__MODULE__} discarded: source #{source_id} stale")
|
Ecto.StaleEntryError -> Logger.info("#{__MODULE__} discarded: source #{source_id} stale")
|
||||||
end
|
end
|
||||||
|
|
||||||
defp perform_indexing_and_send_notification(source) do
|
defp perform_indexing_and_notification(source) do
|
||||||
apprise_server = Settings.get!(:apprise_server)
|
apprise_server = Settings.get!(:apprise_server)
|
||||||
|
new_media_items = FastIndexingHelpers.kickoff_indexing_tasks_from_youtube_rss_feed(source)
|
||||||
|
|
||||||
new_media_items =
|
SourceNotifications.send_new_media_notification(apprise_server, source, length(new_media_items))
|
||||||
source
|
|
||||||
|> FastIndexingHelpers.kickoff_download_tasks_from_youtube_rss_feed()
|
|
||||||
|> Enum.filter(&Media.pending_download?(&1))
|
|
||||||
|
|
||||||
if source.download_media do
|
|
||||||
SourceNotifications.send_new_media_notification(apprise_server, source, length(new_media_items))
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
defp reschedule_indexing(source) do
|
defp reschedule_indexing(source) do
|
||||||
|
|||||||
@@ -0,0 +1,69 @@
|
|||||||
|
defmodule Pinchflat.FastIndexing.MediaIndexingWorker do
|
||||||
|
@moduledoc false
|
||||||
|
|
||||||
|
use Oban.Worker,
|
||||||
|
queue: :media_indexing,
|
||||||
|
unique: [period: :infinity, states: [:available, :scheduled, :retryable]],
|
||||||
|
tags: ["media_source", "media_indexing"]
|
||||||
|
|
||||||
|
require Logger
|
||||||
|
|
||||||
|
alias __MODULE__
|
||||||
|
alias Pinchflat.Tasks
|
||||||
|
alias Pinchflat.Sources
|
||||||
|
alias Pinchflat.FastIndexing.FastIndexingHelpers
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Starts the fast media indexing worker and creates a task for the source.
|
||||||
|
|
||||||
|
Returns {:ok, %Task{}} | {:error, :duplicate_job} | {:error, %Ecto.Changeset{}}
|
||||||
|
"""
|
||||||
|
def kickoff_with_task(source, media_url, opts \\ []) do
|
||||||
|
%{id: source.id, media_url: media_url}
|
||||||
|
|> MediaIndexingWorker.new(opts)
|
||||||
|
|> Tasks.create_job_with_task(source)
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Similar to `MediaCollectionIndexingWorker`, but for individual media items.
|
||||||
|
Does not reschedule or check anything to do with a source's indexing
|
||||||
|
frequency - only collects initial metadata then kicks off a download.
|
||||||
|
`MediaCollectionIndexingWorker` should be preferred in general, but this is
|
||||||
|
useful for downloading one-off media items based on a URL (like for fast indexing).
|
||||||
|
|
||||||
|
Only downloads media that _should_ be downloaded (ie: the source is set to download
|
||||||
|
and the media matches the profile's format preferences)
|
||||||
|
|
||||||
|
Order of operations:
|
||||||
|
1. FastIndexingHelpers.kickoff_indexing_tasks_from_youtube_rss_feed/1 (which is running
|
||||||
|
in its own worker) periodically checks the YouTube RSS feed for new media
|
||||||
|
2. If new media is found, it enqueues a MediaIndexingWorker (this module) for each new media
|
||||||
|
item
|
||||||
|
3. This worker fetches the media metadata and uses that to determine if it should be
|
||||||
|
downloaded. If so, it enqueues a MediaDownloadWorker
|
||||||
|
|
||||||
|
Each is a worker because they all either need to be scheduled periodically or call out to
|
||||||
|
an external service and will be long-running. They're split into different jobs to separate
|
||||||
|
retry logic for each step and allow us to better optimize various queues (eg: the indexing
|
||||||
|
steps can keep running while the slow download steps are worked through).
|
||||||
|
|
||||||
|
Returns :ok
|
||||||
|
"""
|
||||||
|
@impl Oban.Worker
|
||||||
|
def perform(%Oban.Job{args: %{"id" => source_id, "media_url" => media_url}}) do
|
||||||
|
source = Sources.get_source!(source_id)
|
||||||
|
|
||||||
|
case FastIndexingHelpers.index_and_enqueue_download_for_media_item(source, media_url) do
|
||||||
|
{:ok, media_item} ->
|
||||||
|
Logger.debug("Indexed and enqueued download for url: #{media_url} (media item: #{media_item.id})")
|
||||||
|
|
||||||
|
{:error, reason} ->
|
||||||
|
Logger.debug("Failed to index and enqueue download for url: #{media_url} (reason: #{inspect(reason)})")
|
||||||
|
end
|
||||||
|
|
||||||
|
:ok
|
||||||
|
rescue
|
||||||
|
Ecto.NoResultsError -> Logger.info("#{__MODULE__} discarded: source #{source_id} not found")
|
||||||
|
Ecto.StaleEntryError -> Logger.info("#{__MODULE__} discarded: source #{source_id} stale")
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -54,18 +54,11 @@ defmodule Pinchflat.Metadata.MetadataFileHelpers do
|
|||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Downloads and stores a thumbnail for a media item, returning the filepath.
|
Downloads and stores a thumbnail for a media item, returning the filepath.
|
||||||
Chooses the highest quality jpg thumbnail available.
|
|
||||||
|
|
||||||
Returns binary()
|
Returns binary()
|
||||||
"""
|
"""
|
||||||
def download_and_store_thumbnail_for(database_record, metadata_map) do
|
def download_and_store_thumbnail_for(database_record, metadata_map) do
|
||||||
thumbnail_url =
|
thumbnail_url = metadata_map["thumbnail"]
|
||||||
metadata_map["thumbnails"]
|
|
||||||
|> Enum.filter(&(&1["preference"] && String.ends_with?(&1["url"], ".jpg")))
|
|
||||||
|> Enum.sort(&(&1["preference"] >= &2["preference"]))
|
|
||||||
|> List.first()
|
|
||||||
|> Map.get("url")
|
|
||||||
|
|
||||||
filepath = generate_filepath_for(database_record, Path.basename(thumbnail_url))
|
filepath = generate_filepath_for(database_record, Path.basename(thumbnail_url))
|
||||||
thumbnail_blob = fetch_thumbnail_from_url(thumbnail_url)
|
thumbnail_blob = fetch_thumbnail_from_url(thumbnail_url)
|
||||||
|
|
||||||
|
|||||||
@@ -61,8 +61,9 @@ defmodule Pinchflat.SlowIndexing.MediaCollectionIndexingWorker do
|
|||||||
5. If the source uses fast indexing, that job is kicked off as well. It
|
5. If the source uses fast indexing, that job is kicked off as well. It
|
||||||
uses RSS to run a smaller, faster, and more frequent index. That job
|
uses RSS to run a smaller, faster, and more frequent index. That job
|
||||||
handles rescheduling itself but largely has a similar behaviour to this
|
handles rescheduling itself but largely has a similar behaviour to this
|
||||||
job in that it runs and index and maybe kicks off media download jobs.
|
job in that it kicks off index and maybe download jobs. The biggest difference
|
||||||
Check out `FastIndexingWorker` comments for more.
|
is that an index job is kicked off _for each new media item_ as opposed
|
||||||
|
to one larger index job. Check out `MediaIndexingWorker` comments for more.
|
||||||
6. If the job reschedules, the cycle from step 3 repeats until the heat death
|
6. If the job reschedules, the cycle from step 3 repeats until the heat death
|
||||||
of the universe. The user changing things like the index frequency can
|
of the universe. The user changing things like the index frequency can
|
||||||
dequeue or reschedule jobs as well
|
dequeue or reschedule jobs as well
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ defmodule Pinchflat.SlowIndexing.SlowIndexingHelpers do
|
|||||||
alias Pinchflat.YtDlp.MediaCollection
|
alias Pinchflat.YtDlp.MediaCollection
|
||||||
alias Pinchflat.Downloading.DownloadingHelpers
|
alias Pinchflat.Downloading.DownloadingHelpers
|
||||||
alias Pinchflat.SlowIndexing.FileFollowerServer
|
alias Pinchflat.SlowIndexing.FileFollowerServer
|
||||||
|
alias Pinchflat.Downloading.MediaDownloadWorker
|
||||||
alias Pinchflat.SlowIndexing.MediaCollectionIndexingWorker
|
alias Pinchflat.SlowIndexing.MediaCollectionIndexingWorker
|
||||||
|
|
||||||
alias Pinchflat.YtDlp.Media, as: YtDlpMedia
|
alias Pinchflat.YtDlp.Media, as: YtDlpMedia
|
||||||
@@ -28,6 +29,7 @@ defmodule Pinchflat.SlowIndexing.SlowIndexingHelpers do
|
|||||||
"""
|
"""
|
||||||
def kickoff_indexing_task(%Source{} = source, job_args \\ %{}, job_opts \\ []) do
|
def kickoff_indexing_task(%Source{} = source, job_args \\ %{}, job_opts \\ []) do
|
||||||
Tasks.delete_pending_tasks_for(source, "FastIndexingWorker")
|
Tasks.delete_pending_tasks_for(source, "FastIndexingWorker")
|
||||||
|
Tasks.delete_pending_tasks_for(source, "MediaIndexingWorker")
|
||||||
Tasks.delete_pending_tasks_for(source, "MediaCollectionIndexingWorker")
|
Tasks.delete_pending_tasks_for(source, "MediaCollectionIndexingWorker")
|
||||||
|
|
||||||
MediaCollectionIndexingWorker.kickoff_with_task(source, job_args, job_opts)
|
MediaCollectionIndexingWorker.kickoff_with_task(source, job_args, job_opts)
|
||||||
@@ -125,7 +127,11 @@ defmodule Pinchflat.SlowIndexing.SlowIndexingHelpers do
|
|||||||
|
|
||||||
case Media.create_media_item_from_backend_attrs(source, media_attrs) do
|
case Media.create_media_item_from_backend_attrs(source, media_attrs) do
|
||||||
{:ok, %MediaItem{} = media_item} ->
|
{:ok, %MediaItem{} = media_item} ->
|
||||||
DownloadingHelpers.kickoff_download_if_pending(media_item)
|
if source.download_media && Media.pending_download?(media_item) do
|
||||||
|
Logger.debug("FileFollowerServer Handler: Enqueuing download task for #{inspect(media_attrs)}")
|
||||||
|
|
||||||
|
MediaDownloadWorker.kickoff_with_task(media_item)
|
||||||
|
end
|
||||||
|
|
||||||
{:error, changeset} ->
|
{:error, changeset} ->
|
||||||
changeset
|
changeset
|
||||||
|
|||||||
@@ -288,6 +288,7 @@ defmodule Pinchflat.Sources do
|
|||||||
|
|
||||||
%{index_frequency_minutes: _} ->
|
%{index_frequency_minutes: _} ->
|
||||||
Tasks.delete_pending_tasks_for(source, "FastIndexingWorker")
|
Tasks.delete_pending_tasks_for(source, "FastIndexingWorker")
|
||||||
|
Tasks.delete_pending_tasks_for(source, "MediaIndexingWorker")
|
||||||
Tasks.delete_pending_tasks_for(source, "MediaCollectionIndexingWorker")
|
Tasks.delete_pending_tasks_for(source, "MediaCollectionIndexingWorker")
|
||||||
|
|
||||||
_ ->
|
_ ->
|
||||||
|
|||||||
@@ -3,11 +3,10 @@ defmodule Pinchflat.UpgradeButtonLive do
|
|||||||
|
|
||||||
def render(assigns) do
|
def render(assigns) do
|
||||||
~H"""
|
~H"""
|
||||||
<form id="upgradeForm" phx-change="check_matching_text" phx-hook="supressEnterSubmission">
|
<form phx-change="check_matching_text">
|
||||||
<.input type="text" name="unlock-pro-textbox" value="" />
|
<.input type="text" name="unlock-pro-textbox" value="" />
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<%!-- The setTimeout is so the modal has time to disappear before it's removed --%>
|
|
||||||
<.button
|
<.button
|
||||||
class="w-full mt-4"
|
class="w-full mt-4"
|
||||||
type="button"
|
type="button"
|
||||||
|
|||||||
@@ -50,13 +50,7 @@ defmodule PinchflatWeb.MediaItems.MediaItemController do
|
|||||||
|
|
||||||
def force_download(conn, %{"media_item_id" => id}) do
|
def force_download(conn, %{"media_item_id" => id}) do
|
||||||
media_item = Media.get_media_item!(id)
|
media_item = Media.get_media_item!(id)
|
||||||
|
{:ok, _} = MediaDownloadWorker.kickoff_with_task(media_item, %{force: true})
|
||||||
:ok =
|
|
||||||
case MediaDownloadWorker.kickoff_with_task(media_item, %{force: true}) do
|
|
||||||
{:ok, _} -> :ok
|
|
||||||
{:error, :duplicate_job} -> :ok
|
|
||||||
err -> err
|
|
||||||
end
|
|
||||||
|
|
||||||
conn
|
conn
|
||||||
|> put_flash(:info, "Download task enqueued.")
|
|> put_flash(:info, "Download task enqueued.")
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ defmodule Pinchflat.MixProject do
|
|||||||
def project do
|
def project do
|
||||||
[
|
[
|
||||||
app: :pinchflat,
|
app: :pinchflat,
|
||||||
version: "0.1.14",
|
version: "0.1.13",
|
||||||
elixir: "~> 1.16",
|
elixir: "~> 1.16",
|
||||||
elixirc_paths: elixirc_paths(Mix.env()),
|
elixirc_paths: elixirc_paths(Mix.env()),
|
||||||
start_permanent: Mix.env() == :prod,
|
start_permanent: Mix.env() == :prod,
|
||||||
|
|||||||
@@ -90,9 +90,6 @@ RUN yt-dlp -U
|
|||||||
# Download Apprise
|
# Download Apprise
|
||||||
RUN python3 -m pip install -U apprise --break-system-packages
|
RUN python3 -m pip install -U apprise --break-system-packages
|
||||||
|
|
||||||
# Download Mutagen for music thumbnail generation
|
|
||||||
RUN python3 -m pip install -U mutagen --break-system-packages
|
|
||||||
|
|
||||||
# Set the locale
|
# Set the locale
|
||||||
RUN sed -i '/en_US.UTF-8/s/^# //g' /etc/locale.gen && locale-gen
|
RUN sed -i '/en_US.UTF-8/s/^# //g' /etc/locale.gen && locale-gen
|
||||||
ENV LANG en_US.UTF-8
|
ENV LANG en_US.UTF-8
|
||||||
|
|||||||
@@ -55,7 +55,6 @@ defmodule Pinchflat.Downloading.DownloadOptionBuilderTest do
|
|||||||
assert :no_progress in res
|
assert :no_progress in res
|
||||||
assert :windows_filenames in res
|
assert :windows_filenames in res
|
||||||
assert :force_overwrites in res
|
assert :force_overwrites in res
|
||||||
assert {:parse_metadata, "%(upload_date>%Y-%m-%d)s:(?P<meta_date>.+)"} in res
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ defmodule Pinchflat.Downloading.DownloadingHelpersTest do
|
|||||||
import Mox
|
import Mox
|
||||||
import Pinchflat.MediaFixtures
|
import Pinchflat.MediaFixtures
|
||||||
import Pinchflat.SourcesFixtures
|
import Pinchflat.SourcesFixtures
|
||||||
import Pinchflat.ProfilesFixtures
|
|
||||||
|
|
||||||
alias Pinchflat.Tasks
|
alias Pinchflat.Tasks
|
||||||
alias Pinchflat.Downloading.DownloadingHelpers
|
alias Pinchflat.Downloading.DownloadingHelpers
|
||||||
@@ -73,44 +72,4 @@ defmodule Pinchflat.Downloading.DownloadingHelpersTest do
|
|||||||
assert [] = Tasks.list_tasks_for(media_item)
|
assert [] = Tasks.list_tasks_for(media_item)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "kickoff_download_if_pending/1" do
|
|
||||||
setup do
|
|
||||||
media_item = media_item_fixture(media_filepath: nil)
|
|
||||||
|
|
||||||
{:ok, media_item: media_item}
|
|
||||||
end
|
|
||||||
|
|
||||||
test "enqueues a download job", %{media_item: media_item} do
|
|
||||||
assert {:ok, _} = DownloadingHelpers.kickoff_download_if_pending(media_item)
|
|
||||||
|
|
||||||
assert_enqueued(worker: MediaDownloadWorker, args: %{"id" => media_item.id})
|
|
||||||
end
|
|
||||||
|
|
||||||
test "creates and returns a download task record", %{media_item: media_item} do
|
|
||||||
assert {:ok, task} = DownloadingHelpers.kickoff_download_if_pending(media_item)
|
|
||||||
|
|
||||||
assert [found_task] = Tasks.list_tasks_for(media_item, "MediaDownloadWorker")
|
|
||||||
assert task.id == found_task.id
|
|
||||||
end
|
|
||||||
|
|
||||||
test "does not enqueue a download job if the source does not allow it" do
|
|
||||||
source = source_fixture(%{download_media: false})
|
|
||||||
media_item = media_item_fixture(source_id: source.id, media_filepath: nil)
|
|
||||||
|
|
||||||
assert {:error, :should_not_download} = DownloadingHelpers.kickoff_download_if_pending(media_item)
|
|
||||||
|
|
||||||
refute_enqueued(worker: MediaDownloadWorker)
|
|
||||||
end
|
|
||||||
|
|
||||||
test "does not enqueue a download job if the media item does not match the format rules" do
|
|
||||||
profile = media_profile_fixture(%{livestream_behaviour: :exclude})
|
|
||||||
source = source_fixture(%{media_profile_id: profile.id})
|
|
||||||
media_item = media_item_fixture(source_id: source.id, media_filepath: nil, livestream: true)
|
|
||||||
|
|
||||||
assert {:error, :should_not_download} = DownloadingHelpers.kickoff_download_if_pending(media_item)
|
|
||||||
|
|
||||||
refute_enqueued(worker: MediaDownloadWorker)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -9,11 +9,45 @@ defmodule Pinchflat.FastIndexing.FastIndexingHelpersTest do
|
|||||||
alias Pinchflat.Tasks
|
alias Pinchflat.Tasks
|
||||||
alias Pinchflat.Media.MediaItem
|
alias Pinchflat.Media.MediaItem
|
||||||
alias Pinchflat.Downloading.MediaDownloadWorker
|
alias Pinchflat.Downloading.MediaDownloadWorker
|
||||||
|
alias Pinchflat.FastIndexing.MediaIndexingWorker
|
||||||
alias Pinchflat.FastIndexing.FastIndexingHelpers
|
alias Pinchflat.FastIndexing.FastIndexingHelpers
|
||||||
|
|
||||||
setup :verify_on_exit!
|
setup :verify_on_exit!
|
||||||
|
|
||||||
describe "kickoff_download_tasks_from_youtube_rss_feed/1" do
|
@media_url "https://www.youtube.com/watch?v=test_1"
|
||||||
|
|
||||||
|
describe "kickoff_indexing_tasks_from_youtube_rss_feed/1" do
|
||||||
|
setup do
|
||||||
|
{:ok, [source: source_fixture()]}
|
||||||
|
end
|
||||||
|
|
||||||
|
test "enqueues a new worker for each new media_id in the source's RSS feed", %{source: source} do
|
||||||
|
expect(HTTPClientMock, :get, fn _url -> {:ok, "<yt:videoId>test_1</yt:videoId>"} end)
|
||||||
|
|
||||||
|
assert [_] = FastIndexingHelpers.kickoff_indexing_tasks_from_youtube_rss_feed(source)
|
||||||
|
|
||||||
|
assert [worker] = all_enqueued(worker: MediaIndexingWorker)
|
||||||
|
assert worker.args["id"] == source.id
|
||||||
|
assert worker.args["media_url"] == "https://www.youtube.com/watch?v=test_1"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "does not enqueue a new worker for the source's media IDs we already know about", %{source: source} do
|
||||||
|
expect(HTTPClientMock, :get, fn _url -> {:ok, "<yt:videoId>test_1</yt:videoId>"} end)
|
||||||
|
media_item_fixture(source_id: source.id, media_id: "test_1")
|
||||||
|
|
||||||
|
assert [] = FastIndexingHelpers.kickoff_indexing_tasks_from_youtube_rss_feed(source)
|
||||||
|
|
||||||
|
refute_enqueued(worker: MediaIndexingWorker)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "returns the IDs of the found media items", %{source: source} do
|
||||||
|
expect(HTTPClientMock, :get, fn _url -> {:ok, "<yt:videoId>test_1</yt:videoId>"} end)
|
||||||
|
|
||||||
|
assert ["test_1"] = FastIndexingHelpers.kickoff_indexing_tasks_from_youtube_rss_feed(source)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "index_and_enqueue_download_for_media_item/2" do
|
||||||
setup do
|
setup do
|
||||||
stub(YtDlpRunnerMock, :run, fn _url, _opts, _ot ->
|
stub(YtDlpRunnerMock, :run, fn _url, _opts, _ot ->
|
||||||
{:ok, media_attributes_return_fixture()}
|
{:ok, media_attributes_return_fixture()}
|
||||||
@@ -22,50 +56,41 @@ defmodule Pinchflat.FastIndexing.FastIndexingHelpersTest do
|
|||||||
{:ok, [source: source_fixture()]}
|
{:ok, [source: source_fixture()]}
|
||||||
end
|
end
|
||||||
|
|
||||||
test "enqueues a new worker for each new media_id in the source's RSS feed", %{source: source} do
|
test "creates a new media item based on the URL", %{source: source} do
|
||||||
expect(HTTPClientMock, :get, fn _url -> {:ok, "<yt:videoId>test_1</yt:videoId>"} end)
|
assert Repo.aggregate(MediaItem, :count) == 0
|
||||||
|
assert {:ok, _} = FastIndexingHelpers.index_and_enqueue_download_for_media_item(source, @media_url)
|
||||||
assert [media_item] = FastIndexingHelpers.kickoff_download_tasks_from_youtube_rss_feed(source)
|
assert Repo.aggregate(MediaItem, :count) == 1
|
||||||
|
|
||||||
assert [worker] = all_enqueued(worker: MediaDownloadWorker)
|
|
||||||
assert worker.args["id"] == media_item.id
|
|
||||||
end
|
end
|
||||||
|
|
||||||
test "does not enqueue a new worker for the source's media IDs we already know about", %{source: source} do
|
test "won't duplicate media_items based on media_id and source", %{source: source} do
|
||||||
expect(HTTPClientMock, :get, fn _url -> {:ok, "<yt:videoId>test_1</yt:videoId>"} end)
|
assert {:ok, mi_1} = FastIndexingHelpers.index_and_enqueue_download_for_media_item(source, @media_url)
|
||||||
media_item_fixture(source_id: source.id, media_id: "test_1")
|
assert {:ok, mi_2} = FastIndexingHelpers.index_and_enqueue_download_for_media_item(source, @media_url)
|
||||||
|
|
||||||
assert [] = FastIndexingHelpers.kickoff_download_tasks_from_youtube_rss_feed(source)
|
assert Repo.aggregate(MediaItem, :count) == 1
|
||||||
|
assert mi_1.id == mi_2.id
|
||||||
refute_enqueued(worker: MediaDownloadWorker)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
test "returns the found media items", %{source: source} do
|
test "enqueues a download job", %{source: source} do
|
||||||
expect(HTTPClientMock, :get, fn _url -> {:ok, "<yt:videoId>test_1</yt:videoId>"} end)
|
assert {:ok, media_item} = FastIndexingHelpers.index_and_enqueue_download_for_media_item(source, @media_url)
|
||||||
|
|
||||||
assert [%MediaItem{}] = FastIndexingHelpers.kickoff_download_tasks_from_youtube_rss_feed(source)
|
assert_enqueued(worker: MediaDownloadWorker, args: %{"id" => media_item.id})
|
||||||
end
|
|
||||||
|
|
||||||
test "does not enqueue a download job if the source does not allow it" do
|
|
||||||
expect(HTTPClientMock, :get, fn _url -> {:ok, "<yt:videoId>test_1</yt:videoId>"} end)
|
|
||||||
source = source_fixture(%{download_media: false})
|
|
||||||
|
|
||||||
assert [%MediaItem{}] = FastIndexingHelpers.kickoff_download_tasks_from_youtube_rss_feed(source)
|
|
||||||
|
|
||||||
refute_enqueued(worker: MediaDownloadWorker)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
test "creates a download task record", %{source: source} do
|
test "creates a download task record", %{source: source} do
|
||||||
expect(HTTPClientMock, :get, fn _url -> {:ok, "<yt:videoId>test_1</yt:videoId>"} end)
|
assert {:ok, media_item} = FastIndexingHelpers.index_and_enqueue_download_for_media_item(source, @media_url)
|
||||||
|
|
||||||
assert [media_item] = FastIndexingHelpers.kickoff_download_tasks_from_youtube_rss_feed(source)
|
|
||||||
|
|
||||||
assert [_] = Tasks.list_tasks_for(media_item, "MediaDownloadWorker")
|
assert [_] = Tasks.list_tasks_for(media_item, "MediaDownloadWorker")
|
||||||
end
|
end
|
||||||
|
|
||||||
test "does not enqueue a download job if the media item does not match the format rules" do
|
test "does not enqueue a download job if the source does not allow it" do
|
||||||
expect(HTTPClientMock, :get, fn _url -> {:ok, "<yt:videoId>test_1</yt:videoId>"} end)
|
source = source_fixture(%{download_media: false})
|
||||||
|
|
||||||
|
assert {:ok, _} = FastIndexingHelpers.index_and_enqueue_download_for_media_item(source, @media_url)
|
||||||
|
|
||||||
|
refute_enqueued(worker: MediaDownloadWorker)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "does not enqueue a download job if the media item does not match the format rules" do
|
||||||
profile = media_profile_fixture(%{shorts_behaviour: :exclude})
|
profile = media_profile_fixture(%{shorts_behaviour: :exclude})
|
||||||
source = source_fixture(%{media_profile_id: profile.id})
|
source = source_fixture(%{media_profile_id: profile.id})
|
||||||
|
|
||||||
@@ -85,8 +110,7 @@ defmodule Pinchflat.FastIndexing.FastIndexingHelpersTest do
|
|||||||
{:ok, output}
|
{:ok, output}
|
||||||
end)
|
end)
|
||||||
|
|
||||||
assert [%MediaItem{}] = FastIndexingHelpers.kickoff_download_tasks_from_youtube_rss_feed(source)
|
assert {:ok, _media_item} = FastIndexingHelpers.index_and_enqueue_download_for_media_item(source, @media_url)
|
||||||
|
|
||||||
refute_enqueued(worker: MediaDownloadWorker)
|
refute_enqueued(worker: MediaDownloadWorker)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -87,7 +87,6 @@ defmodule Pinchflat.FastIndexing.FastIndexingWorkerTest do
|
|||||||
source = source_fixture(fast_index: true)
|
source = source_fixture(fast_index: true)
|
||||||
|
|
||||||
expect(HTTPClientMock, :get, fn _url -> {:ok, "<yt:videoId>test_1</yt:videoId>"} end)
|
expect(HTTPClientMock, :get, fn _url -> {:ok, "<yt:videoId>test_1</yt:videoId>"} end)
|
||||||
expect(YtDlpRunnerMock, :run, fn _url, _opts, _ot -> {:ok, render_metadata(:media_metadata)} end)
|
|
||||||
|
|
||||||
expect(AppriseRunnerMock, :run, fn servers, opts ->
|
expect(AppriseRunnerMock, :run, fn servers, opts ->
|
||||||
assert "server_1" = servers
|
assert "server_1" = servers
|
||||||
@@ -99,34 +98,5 @@ defmodule Pinchflat.FastIndexing.FastIndexingWorkerTest do
|
|||||||
|
|
||||||
perform_job(FastIndexingWorker, %{id: source.id})
|
perform_job(FastIndexingWorker, %{id: source.id})
|
||||||
end
|
end
|
||||||
|
|
||||||
test "doesn't send a notification if new media is not found" do
|
|
||||||
source = source_fixture(fast_index: true)
|
|
||||||
|
|
||||||
expect(HTTPClientMock, :get, fn _url -> {:ok, ""} end)
|
|
||||||
expect(AppriseRunnerMock, :run, 0, fn _servers, _opts -> {:ok, ""} end)
|
|
||||||
|
|
||||||
perform_job(FastIndexingWorker, %{id: source.id})
|
|
||||||
end
|
|
||||||
|
|
||||||
test "doesn't send a notification if the source doesn't download media" do
|
|
||||||
source = source_fixture(fast_index: true, download_media: false)
|
|
||||||
|
|
||||||
expect(HTTPClientMock, :get, fn _url -> {:ok, "<yt:videoId>test_1</yt:videoId>"} end)
|
|
||||||
expect(YtDlpRunnerMock, :run, fn _url, _opts, _ot -> {:ok, render_metadata(:media_metadata)} end)
|
|
||||||
expect(AppriseRunnerMock, :run, 0, fn _servers, _opts -> {:ok, ""} end)
|
|
||||||
|
|
||||||
perform_job(FastIndexingWorker, %{id: source.id})
|
|
||||||
end
|
|
||||||
|
|
||||||
test "doesn't send a notification if the media isn't pending download" do
|
|
||||||
source = source_fixture(fast_index: true, title_filter_regex: "foobar")
|
|
||||||
|
|
||||||
expect(HTTPClientMock, :get, fn _url -> {:ok, "<yt:videoId>test_1</yt:videoId>"} end)
|
|
||||||
expect(YtDlpRunnerMock, :run, fn _url, _opts, _ot -> {:ok, render_metadata(:media_metadata)} end)
|
|
||||||
expect(AppriseRunnerMock, :run, 0, fn _servers, _opts -> {:ok, ""} end)
|
|
||||||
|
|
||||||
perform_job(FastIndexingWorker, %{id: source.id})
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -0,0 +1,61 @@
|
|||||||
|
defmodule Pinchflat.FastIndexing.MediaIndexingWorkerTest do
|
||||||
|
use Pinchflat.DataCase
|
||||||
|
|
||||||
|
import Mox
|
||||||
|
import Pinchflat.MediaFixtures
|
||||||
|
import Pinchflat.SourcesFixtures
|
||||||
|
|
||||||
|
alias Pinchflat.Media.MediaItem
|
||||||
|
alias Pinchflat.Downloading.MediaDownloadWorker
|
||||||
|
alias Pinchflat.FastIndexing.MediaIndexingWorker
|
||||||
|
|
||||||
|
@media_url "https://www.youtube.com/watch?v=1234567890"
|
||||||
|
|
||||||
|
setup :verify_on_exit!
|
||||||
|
|
||||||
|
setup do
|
||||||
|
source = source_fixture()
|
||||||
|
|
||||||
|
{:ok, source: source}
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "kickoff_with_task/2" do
|
||||||
|
test "starts the worker", %{source: source} do
|
||||||
|
assert [] = all_enqueued(worker: MediaIndexingWorker)
|
||||||
|
assert {:ok, _} = MediaIndexingWorker.kickoff_with_task(source, @media_url)
|
||||||
|
assert [_] = all_enqueued(worker: MediaIndexingWorker)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "attaches a task", %{source: source} do
|
||||||
|
assert {:ok, task} = MediaIndexingWorker.kickoff_with_task(source, @media_url)
|
||||||
|
assert task.source_id == source.id
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "perform/1" do
|
||||||
|
test "indexes the media item and saves it to the database", %{source: source} do
|
||||||
|
expect(YtDlpRunnerMock, :run, fn _url, _opts, _ot ->
|
||||||
|
{:ok, media_attributes_return_fixture()}
|
||||||
|
end)
|
||||||
|
|
||||||
|
before = Repo.aggregate(MediaItem, :count, :id)
|
||||||
|
perform_job(MediaIndexingWorker, %{id: source.id, media_url: @media_url})
|
||||||
|
|
||||||
|
assert Repo.aggregate(MediaItem, :count, :id) == before + 1
|
||||||
|
end
|
||||||
|
|
||||||
|
test "enqueues a download job for the media item", %{source: source} do
|
||||||
|
expect(YtDlpRunnerMock, :run, fn _url, _opts, _ot ->
|
||||||
|
{:ok, media_attributes_return_fixture()}
|
||||||
|
end)
|
||||||
|
|
||||||
|
perform_job(MediaIndexingWorker, %{id: source.id, media_url: @media_url})
|
||||||
|
|
||||||
|
assert [_] = all_enqueued(worker: MediaDownloadWorker)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "does not blow up if the record doesn't exist" do
|
||||||
|
assert :ok = perform_job(MediaDownloadWorker, %{id: 0, media_url: @media_url})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -731,7 +731,9 @@ defmodule Pinchflat.MediaTest do
|
|||||||
metadata: %{
|
metadata: %{
|
||||||
metadata_filepath: MetadataFileHelpers.compress_and_store_metadata_for(media_item, %{}),
|
metadata_filepath: MetadataFileHelpers.compress_and_store_metadata_for(media_item, %{}),
|
||||||
thumbnail_filepath:
|
thumbnail_filepath:
|
||||||
MetadataFileHelpers.download_and_store_thumbnail_for(media_item, render_parsed_metadata(:media_metadata))
|
MetadataFileHelpers.download_and_store_thumbnail_for(media_item, %{
|
||||||
|
"thumbnail" => "https://example.com/thumbnail.jpg"
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -758,7 +760,9 @@ defmodule Pinchflat.MediaTest do
|
|||||||
metadata: %{
|
metadata: %{
|
||||||
metadata_filepath: MetadataFileHelpers.compress_and_store_metadata_for(media_item, %{}),
|
metadata_filepath: MetadataFileHelpers.compress_and_store_metadata_for(media_item, %{}),
|
||||||
thumbnail_filepath:
|
thumbnail_filepath:
|
||||||
MetadataFileHelpers.download_and_store_thumbnail_for(media_item, render_parsed_metadata(:media_metadata))
|
MetadataFileHelpers.download_and_store_thumbnail_for(media_item, %{
|
||||||
|
"thumbnail" => "https://example.com/thumbnail.jpg"
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -827,7 +831,9 @@ defmodule Pinchflat.MediaTest do
|
|||||||
metadata: %{
|
metadata: %{
|
||||||
metadata_filepath: MetadataFileHelpers.compress_and_store_metadata_for(media_item, %{}),
|
metadata_filepath: MetadataFileHelpers.compress_and_store_metadata_for(media_item, %{}),
|
||||||
thumbnail_filepath:
|
thumbnail_filepath:
|
||||||
MetadataFileHelpers.download_and_store_thumbnail_for(media_item, render_parsed_metadata(:media_metadata))
|
MetadataFileHelpers.download_and_store_thumbnail_for(media_item, %{
|
||||||
|
"thumbnail" => "https://example.com/thumbnail.jpg"
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -54,11 +54,13 @@ defmodule Pinchflat.Metadata.MetadataFileHelpersTest do
|
|||||||
describe "download_and_store_thumbnail_for/2" do
|
describe "download_and_store_thumbnail_for/2" do
|
||||||
setup do
|
setup do
|
||||||
# This tests that the HTTP endpoint is being called with every test
|
# This tests that the HTTP endpoint is being called with every test
|
||||||
expect(HTTPClientMock, :get, fn _url, _headers, _opts ->
|
expect(HTTPClientMock, :get, fn url, _headers, _opts ->
|
||||||
|
assert url =~ "example.com"
|
||||||
|
|
||||||
{:ok, "thumbnail data"}
|
{:ok, "thumbnail data"}
|
||||||
end)
|
end)
|
||||||
|
|
||||||
metadata = render_parsed_metadata(:media_metadata)
|
metadata = %{"thumbnail" => "example.com/thumbnail.jpg"}
|
||||||
|
|
||||||
{:ok, %{metadata: metadata}}
|
{:ok, %{metadata: metadata}}
|
||||||
end
|
end
|
||||||
@@ -66,7 +68,7 @@ defmodule Pinchflat.Metadata.MetadataFileHelpersTest do
|
|||||||
test "returns the filepath", %{media_item: media_item, metadata: metadata} do
|
test "returns the filepath", %{media_item: media_item, metadata: metadata} do
|
||||||
filepath = Helpers.download_and_store_thumbnail_for(media_item, metadata)
|
filepath = Helpers.download_and_store_thumbnail_for(media_item, metadata)
|
||||||
|
|
||||||
assert filepath =~ ~r{/media_items/#{media_item.id}/maxresdefault.jpg}
|
assert filepath =~ ~r{/media_items/#{media_item.id}/thumbnail.jpg}
|
||||||
end
|
end
|
||||||
|
|
||||||
test "creates folder structure based on passed record", %{media_item: media_item, metadata: metadata} do
|
test "creates folder structure based on passed record", %{media_item: media_item, metadata: metadata} do
|
||||||
@@ -75,19 +77,11 @@ defmodule Pinchflat.Metadata.MetadataFileHelpersTest do
|
|||||||
assert File.exists?(Path.dirname(filepath))
|
assert File.exists?(Path.dirname(filepath))
|
||||||
end
|
end
|
||||||
|
|
||||||
test "chooses the highest preference jpg thumbnail available", %{media_item: media_item} do
|
test "the filename and extension is based on the URL", %{media_item: media_item} do
|
||||||
metadata = %{
|
metadata = %{"thumbnail" => "example.com/maxres.webp"}
|
||||||
"thumbnails" => [
|
|
||||||
%{"url" => "https://i.ytimg.com/vi/ABC123/img_1.jpg", "preference" => -1},
|
|
||||||
%{"url" => "https://i.ytimg.com/vi/ABC123/img_2.jpg", "preference" => 1},
|
|
||||||
%{"url" => "https://i.ytimg.com/vi/ABC123/img_3.jpg", "preference" => -10},
|
|
||||||
%{"url" => "https://i.ytimg.com/vi/ABC123/img_4.webp", "preference" => 10}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
filepath = Helpers.download_and_store_thumbnail_for(media_item, metadata)
|
filepath = Helpers.download_and_store_thumbnail_for(media_item, metadata)
|
||||||
|
|
||||||
assert filepath =~ ~r{/media_items/#{media_item.id}/img_2.jpg}
|
assert Path.basename(filepath) == "maxres.webp"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ defmodule Pinchflat.SlowIndexing.SlowIndexingHelpersTest do
|
|||||||
alias Pinchflat.Media.MediaItem
|
alias Pinchflat.Media.MediaItem
|
||||||
alias Pinchflat.FastIndexing.FastIndexingWorker
|
alias Pinchflat.FastIndexing.FastIndexingWorker
|
||||||
alias Pinchflat.Downloading.MediaDownloadWorker
|
alias Pinchflat.Downloading.MediaDownloadWorker
|
||||||
|
alias Pinchflat.FastIndexing.MediaIndexingWorker
|
||||||
alias Pinchflat.SlowIndexing.SlowIndexingHelpers
|
alias Pinchflat.SlowIndexing.SlowIndexingHelpers
|
||||||
alias Pinchflat.SlowIndexing.MediaCollectionIndexingWorker
|
alias Pinchflat.SlowIndexing.MediaCollectionIndexingWorker
|
||||||
|
|
||||||
@@ -46,7 +47,7 @@ defmodule Pinchflat.SlowIndexing.SlowIndexingHelpersTest do
|
|||||||
|
|
||||||
test "it deletes any pending media tasks for the source" do
|
test "it deletes any pending media tasks for the source" do
|
||||||
source = source_fixture()
|
source = source_fixture()
|
||||||
{:ok, job} = Oban.insert(FastIndexingWorker.new(%{"id" => source.id}))
|
{:ok, job} = Oban.insert(MediaIndexingWorker.new(%{"id" => source.id}))
|
||||||
task = task_fixture(source_id: source.id, job_id: job.id)
|
task = task_fixture(source_id: source.id, job_id: job.id)
|
||||||
|
|
||||||
assert {:ok, _} = SlowIndexingHelpers.kickoff_indexing_task(source)
|
assert {:ok, _} = SlowIndexingHelpers.kickoff_indexing_task(source)
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ defmodule Pinchflat.SourcesTest do
|
|||||||
alias Pinchflat.Downloading.DownloadingHelpers
|
alias Pinchflat.Downloading.DownloadingHelpers
|
||||||
alias Pinchflat.FastIndexing.FastIndexingWorker
|
alias Pinchflat.FastIndexing.FastIndexingWorker
|
||||||
alias Pinchflat.Downloading.MediaDownloadWorker
|
alias Pinchflat.Downloading.MediaDownloadWorker
|
||||||
|
alias Pinchflat.FastIndexing.MediaIndexingWorker
|
||||||
alias Pinchflat.Metadata.SourceMetadataStorageWorker
|
alias Pinchflat.Metadata.SourceMetadataStorageWorker
|
||||||
alias Pinchflat.SlowIndexing.MediaCollectionIndexingWorker
|
alias Pinchflat.SlowIndexing.MediaCollectionIndexingWorker
|
||||||
|
|
||||||
@@ -414,13 +415,16 @@ defmodule Pinchflat.SourcesTest do
|
|||||||
|
|
||||||
{:ok, job_1} = Oban.insert(FastIndexingWorker.new(%{"id" => source.id}))
|
{:ok, job_1} = Oban.insert(FastIndexingWorker.new(%{"id" => source.id}))
|
||||||
task_1 = task_fixture(source_id: source.id, job_id: job_1.id)
|
task_1 = task_fixture(source_id: source.id, job_id: job_1.id)
|
||||||
{:ok, job_2} = Oban.insert(MediaCollectionIndexingWorker.new(%{"id" => source.id}))
|
{:ok, job_2} = Oban.insert(MediaIndexingWorker.new(%{"id" => source.id}))
|
||||||
task_2 = task_fixture(source_id: source.id, job_id: job_2.id)
|
task_2 = task_fixture(source_id: source.id, job_id: job_2.id)
|
||||||
|
{:ok, job_3} = Oban.insert(MediaCollectionIndexingWorker.new(%{"id" => source.id}))
|
||||||
|
task_3 = task_fixture(source_id: source.id, job_id: job_3.id)
|
||||||
|
|
||||||
assert {:ok, %Source{}} = Sources.update_source(source, update_attrs)
|
assert {:ok, %Source{}} = Sources.update_source(source, update_attrs)
|
||||||
|
|
||||||
assert_raise Ecto.NoResultsError, fn -> Repo.reload!(task_1) end
|
assert_raise Ecto.NoResultsError, fn -> Repo.reload!(task_1) end
|
||||||
assert_raise Ecto.NoResultsError, fn -> Repo.reload!(task_2) end
|
assert_raise Ecto.NoResultsError, fn -> Repo.reload!(task_2) end
|
||||||
|
assert_raise Ecto.NoResultsError, fn -> Repo.reload!(task_3) end
|
||||||
end
|
end
|
||||||
|
|
||||||
test "not updating the index frequency will not re-schedule the indexing task or delete tasks" do
|
test "not updating the index frequency will not re-schedule the indexing task or delete tasks" do
|
||||||
|
|||||||
@@ -97,15 +97,6 @@ defmodule PinchflatWeb.MediaItemControllerTest do
|
|||||||
assert [_] = all_enqueued(worker: MediaDownloadWorker)
|
assert [_] = all_enqueued(worker: MediaDownloadWorker)
|
||||||
end
|
end
|
||||||
|
|
||||||
test "doesn't freak out if the task is a duplicate", %{conn: conn} do
|
|
||||||
media_item = media_item_fixture()
|
|
||||||
|
|
||||||
MediaDownloadWorker.kickoff_with_task(media_item, %{force: true})
|
|
||||||
|
|
||||||
post(conn, ~p"/sources/#{media_item.source_id}/media/#{media_item.id}/force_download")
|
|
||||||
assert [_] = all_enqueued(worker: MediaDownloadWorker)
|
|
||||||
end
|
|
||||||
|
|
||||||
test "forces a download even if one wouldn't normally run", %{conn: conn} do
|
test "forces a download even if one wouldn't normally run", %{conn: conn} do
|
||||||
media_item = media_item_fixture(%{media_filepath: nil})
|
media_item = media_item_fixture(%{media_filepath: nil})
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user