Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1c9f8f6abc | |||
| 2ae1ee1c99 | |||
| b73a9a4a67 | |||
| 3dd901ff3e | |||
| e751c65424 | |||
| 22439e0273 |
@@ -91,7 +91,7 @@ Docker Compose file:
|
|||||||
version: '3'
|
version: '3'
|
||||||
services:
|
services:
|
||||||
pinchflat:
|
pinchflat:
|
||||||
image: keglin/pinchflat:latest
|
image: ghcr.io/kieraneglin/pinchflat:latest
|
||||||
environment:
|
environment:
|
||||||
# Set the timezone to your local timezone
|
# Set the timezone to your local timezone
|
||||||
- TZ=America/New_York
|
- TZ=America/New_York
|
||||||
@@ -153,6 +153,7 @@ If you change this setting and it works well for you, please leave a comment on
|
|||||||
| EXPOSE_FEED_ENDPOINTS | No | | See [RSS feed docs](https://github.com/kieraneglin/pinchflat/wiki/Podcast-RSS-Feeds) |
|
| EXPOSE_FEED_ENDPOINTS | No | | See [RSS feed docs](https://github.com/kieraneglin/pinchflat/wiki/Podcast-RSS-Feeds) |
|
||||||
| JOURNAL_MODE | No | `wal` | Set to `delete` if your config directory is stored on a network share (not recommended) |
|
| JOURNAL_MODE | No | `wal` | Set to `delete` if your config directory is stored on a network share (not recommended) |
|
||||||
| TZ_DATA_DIR | No | `/etc/elixir_tzdata_data` | The container path where the timezone database is stored |
|
| TZ_DATA_DIR | No | `/etc/elixir_tzdata_data` | The container path where the timezone database is stored |
|
||||||
|
| BASE_ROUTE_PATH | No | `/` | The base path for route generation. Useful when running behind certain reverse proxies |
|
||||||
|
|
||||||
## EFF donations
|
## EFF donations
|
||||||
|
|
||||||
|
|||||||
+21
-18
@@ -22,8 +22,8 @@
|
|||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'Satoshi';
|
font-family: 'Satoshi';
|
||||||
src: url('/fonts/satoshi/Satoshi-Light.woff2'), url('/fonts/satoshi/Satoshi-Light.woff'),
|
src: url('../fonts/satoshi/Satoshi-Light.woff2'), url('../fonts/satoshi/Satoshi-Light.woff'),
|
||||||
url('/fonts/satoshi/Satoshi-Light.ttf');
|
url('../fonts/satoshi/Satoshi-Light.ttf');
|
||||||
font-weight: 300;
|
font-weight: 300;
|
||||||
font-display: swap;
|
font-display: swap;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
@@ -31,8 +31,9 @@
|
|||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'Satoshi';
|
font-family: 'Satoshi';
|
||||||
src: url('/fonts/satoshi/Satoshi-LightItalic.woff2'),
|
src: url('../fonts/satoshi/Satoshi-LightItalic.woff2'),
|
||||||
url('/fonts/satoshi/Satoshi-LightItalic.woff'), url('/fonts/satoshi/Satoshi-LightItalic.ttf');
|
url('../fonts/satoshi/Satoshi-LightItalic.woff'),
|
||||||
|
url('../fonts/satoshi/Satoshi-LightItalic.ttf');
|
||||||
font-weight: 300;
|
font-weight: 300;
|
||||||
font-display: swap;
|
font-display: swap;
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
@@ -40,8 +41,8 @@
|
|||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'Satoshi';
|
font-family: 'Satoshi';
|
||||||
src: url('/fonts/satoshi/Satoshi-Regular.woff2'), url('/fonts/satoshi/Satoshi-Regular.woff'),
|
src: url('../fonts/satoshi/Satoshi-Regular.woff2'), url('../fonts/satoshi/Satoshi-Regular.woff'),
|
||||||
url('/fonts/satoshi/Satoshi-Regular.ttf');
|
url('../fonts/satoshi/Satoshi-Regular.ttf');
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
font-display: swap;
|
font-display: swap;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
@@ -49,8 +50,8 @@
|
|||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'Satoshi';
|
font-family: 'Satoshi';
|
||||||
src: url('/fonts/satoshi/Satoshi-Italic.woff2'), url('/fonts/satoshi/Satoshi-Italic.woff'),
|
src: url('../fonts/satoshi/Satoshi-Italic.woff2'), url('../fonts/satoshi/Satoshi-Italic.woff'),
|
||||||
url('/fonts/satoshi/Satoshi-Italic.ttf');
|
url('../fonts/satoshi/Satoshi-Italic.ttf');
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
font-display: swap;
|
font-display: swap;
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
@@ -58,8 +59,8 @@
|
|||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'Satoshi';
|
font-family: 'Satoshi';
|
||||||
src: url('/fonts/satoshi/Satoshi-Medium.woff2'), url('/fonts/satoshi/Satoshi-Medium.woff'),
|
src: url('../fonts/satoshi/Satoshi-Medium.woff2'), url('../fonts/satoshi/Satoshi-Medium.woff'),
|
||||||
url('/fonts/satoshi/Satoshi-Medium.ttf');
|
url('../fonts/satoshi/Satoshi-Medium.ttf');
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
font-display: swap;
|
font-display: swap;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
@@ -67,8 +68,9 @@
|
|||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'Satoshi';
|
font-family: 'Satoshi';
|
||||||
src: url('/fonts/satoshi/Satoshi-MediumItalic.woff2'),
|
src: url('../fonts/satoshi/Satoshi-MediumItalic.woff2'),
|
||||||
url('/fonts/satoshi/Satoshi-MediumItalic.woff'), url('/fonts/satoshi/Satoshi-MediumItalic.ttf');
|
url('../fonts/satoshi/Satoshi-MediumItalic.woff'),
|
||||||
|
url('../fonts/satoshi/Satoshi-MediumItalic.ttf');
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
font-display: swap;
|
font-display: swap;
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
@@ -76,8 +78,8 @@
|
|||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'Satoshi';
|
font-family: 'Satoshi';
|
||||||
src: url('/fonts/satoshi/Satoshi-Bold.woff2'), url('/fonts/satoshi/Satoshi-Bold.woff'),
|
src: url('../fonts/satoshi/Satoshi-Bold.woff2'), url('../fonts/satoshi/Satoshi-Bold.woff'),
|
||||||
url('/fonts/satoshi/Satoshi-Bold.ttf');
|
url('../fonts/satoshi/Satoshi-Bold.ttf');
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
font-display: swap;
|
font-display: swap;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
@@ -85,8 +87,8 @@
|
|||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'Satoshi';
|
font-family: 'Satoshi';
|
||||||
src: url('/fonts/satoshi/Satoshi-BoldItalic.woff2'), url('/fonts/satoshi/Satoshi-BoldItalic.woff'),
|
src: url('../fonts/satoshi/Satoshi-BoldItalic.woff2'),
|
||||||
url('/fonts/satoshi/Satoshi-BoldItalic.ttf');
|
url('../fonts/satoshi/Satoshi-BoldItalic.woff'), url('../fonts/satoshi/Satoshi-BoldItalic.ttf');
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
font-display: swap;
|
font-display: swap;
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
@@ -94,8 +96,9 @@
|
|||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'Satoshi';
|
font-family: 'Satoshi';
|
||||||
src: url('/fonts/satoshi/Satoshi-BlackItalic.woff2'),
|
src: url('../fonts/satoshi/Satoshi-BlackItalic.woff2'),
|
||||||
url('/fonts/satoshi/Satoshi-BlackItalic.woff'), url('/fonts/satoshi/Satoshi-BlackItalic.ttf');
|
url('../fonts/satoshi/Satoshi-BlackItalic.woff'),
|
||||||
|
url('../fonts/satoshi/Satoshi-BlackItalic.ttf');
|
||||||
font-weight: 900;
|
font-weight: 900;
|
||||||
font-display: swap;
|
font-display: swap;
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
|
|||||||
+1
-1
@@ -29,7 +29,7 @@ window.Alpine = Alpine
|
|||||||
Alpine.start()
|
Alpine.start()
|
||||||
|
|
||||||
let csrfToken = document.querySelector("meta[name='csrf-token']").getAttribute('content')
|
let csrfToken = document.querySelector("meta[name='csrf-token']").getAttribute('content')
|
||||||
let liveSocket = new LiveSocket('/live', Socket, {
|
let liveSocket = new LiveSocket(document.body.dataset.socketPath, Socket, {
|
||||||
params: { _csrf_token: csrfToken },
|
params: { _csrf_token: csrfToken },
|
||||||
dom: {
|
dom: {
|
||||||
onBeforeElUpdated(from, to) {
|
onBeforeElUpdated(from, to) {
|
||||||
|
|||||||
+2
-1
@@ -26,7 +26,8 @@ config :pinchflat,
|
|||||||
basic_auth_password: "",
|
basic_auth_password: "",
|
||||||
expose_feed_endpoints: false,
|
expose_feed_endpoints: false,
|
||||||
file_watcher_poll_interval: 1000,
|
file_watcher_poll_interval: 1000,
|
||||||
timezone: "UTC"
|
timezone: "UTC",
|
||||||
|
base_route_path: "/"
|
||||||
|
|
||||||
config :pinchflat, Pinchflat.Repo,
|
config :pinchflat, Pinchflat.Repo,
|
||||||
journal_mode: :wal,
|
journal_mode: :wal,
|
||||||
|
|||||||
+5
-3
@@ -46,12 +46,12 @@ if config_env() == :prod do
|
|||||||
log_path = System.get_env("LOG_PATH", Path.join([config_path, "logs", "pinchflat.log"]))
|
log_path = System.get_env("LOG_PATH", Path.join([config_path, "logs", "pinchflat.log"]))
|
||||||
metadata_path = System.get_env("METADATA_PATH", Path.join([config_path, "metadata"]))
|
metadata_path = System.get_env("METADATA_PATH", Path.join([config_path, "metadata"]))
|
||||||
extras_path = System.get_env("EXTRAS_PATH", Path.join([config_path, "extras"]))
|
extras_path = System.get_env("EXTRAS_PATH", Path.join([config_path, "extras"]))
|
||||||
|
|
||||||
# For running PF as a podcast host on self-hosted environments
|
# For running PF as a podcast host on self-hosted environments
|
||||||
expose_feed_endpoints = String.length(System.get_env("EXPOSE_FEED_ENDPOINTS", "")) > 0
|
expose_feed_endpoints = String.length(System.get_env("EXPOSE_FEED_ENDPOINTS", "")) > 0
|
||||||
|
|
||||||
# For testing alternate journal modes (see issue #137)
|
# For testing alternate journal modes (see issue #137)
|
||||||
journal_mode = String.to_existing_atom(System.get_env("JOURNAL_MODE", "wal"))
|
journal_mode = String.to_existing_atom(System.get_env("JOURNAL_MODE", "wal"))
|
||||||
|
# For running PF in a subdirectory via a reverse proxy
|
||||||
|
base_route_path = System.get_env("BASE_ROUTE_PATH", "/")
|
||||||
|
|
||||||
config :logger, level: String.to_existing_atom(System.get_env("LOG_LEVEL", "debug"))
|
config :logger, level: String.to_existing_atom(System.get_env("LOG_LEVEL", "debug"))
|
||||||
|
|
||||||
@@ -65,7 +65,8 @@ if config_env() == :prod do
|
|||||||
dns_cluster_query: System.get_env("DNS_CLUSTER_QUERY"),
|
dns_cluster_query: System.get_env("DNS_CLUSTER_QUERY"),
|
||||||
expose_feed_endpoints: expose_feed_endpoints,
|
expose_feed_endpoints: expose_feed_endpoints,
|
||||||
timezone: System.get_env("TIMEZONE") || System.get_env("TZ") || "UTC",
|
timezone: System.get_env("TIMEZONE") || System.get_env("TZ") || "UTC",
|
||||||
log_path: log_path
|
log_path: log_path,
|
||||||
|
base_route_path: base_route_path
|
||||||
|
|
||||||
config :tzdata, :data_dir, System.get_env("TZ_DATA_DIR", "/etc/elixir_tzdata_data")
|
config :tzdata, :data_dir, System.get_env("TZ_DATA_DIR", "/etc/elixir_tzdata_data")
|
||||||
|
|
||||||
@@ -108,6 +109,7 @@ if config_env() == :prod do
|
|||||||
ip: {0, 0, 0, 0},
|
ip: {0, 0, 0, 0},
|
||||||
port: String.to_integer(System.get_env("PORT") || "4000")
|
port: String.to_integer(System.get_env("PORT") || "4000")
|
||||||
],
|
],
|
||||||
|
url: [path: base_route_path],
|
||||||
secret_key_base: secret_key_base
|
secret_key_base: secret_key_base
|
||||||
|
|
||||||
config :pinchflat, :logger, [
|
config :pinchflat, :logger, [
|
||||||
|
|||||||
@@ -16,14 +16,10 @@ defmodule Pinchflat.Downloading.DownloadingHelpers do
|
|||||||
alias Pinchflat.Media.MediaItem
|
alias Pinchflat.Media.MediaItem
|
||||||
alias Pinchflat.Downloading.MediaDownloadWorker
|
alias Pinchflat.Downloading.MediaDownloadWorker
|
||||||
|
|
||||||
alias Pinchflat.Lifecycle.UserScripts.CommandRunner, as: UserScriptRunner
|
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Starts tasks for downloading media for any of a sources _pending_ media items.
|
Starts tasks for downloading media for any of a sources _pending_ media items.
|
||||||
Jobs are not enqueued if the source is set to not download media. This will return :ok.
|
Jobs are not enqueued if the source is set to not download media. This will return :ok.
|
||||||
|
|
||||||
You can optionally set the `kickoff_delay` option to delay when the jobs are enqueued.
|
|
||||||
|
|
||||||
NOTE: this starts a download for each media item that is pending,
|
NOTE: this starts a download for each media item that is pending,
|
||||||
not just the ones that were indexed in this job run. This should ensure
|
not just the ones that were indexed in this job run. This should ensure
|
||||||
that any stragglers are caught if, for some reason, they weren't enqueued
|
that any stragglers are caught if, for some reason, they weren't enqueued
|
||||||
@@ -31,17 +27,13 @@ defmodule Pinchflat.Downloading.DownloadingHelpers do
|
|||||||
|
|
||||||
Returns :ok
|
Returns :ok
|
||||||
"""
|
"""
|
||||||
def enqueue_pending_download_tasks(source, opts \\ [])
|
def enqueue_pending_download_tasks(%Source{download_media: true} = source) do
|
||||||
|
|
||||||
def enqueue_pending_download_tasks(%Source{download_media: true} = source, opts) do
|
|
||||||
kickoff_delay = Keyword.get(opts, :kickoff_delay, 0)
|
|
||||||
|
|
||||||
source
|
source
|
||||||
|> Media.list_pending_media_items_for()
|
|> Media.list_pending_media_items_for()
|
||||||
|> Enum.each(&MediaDownloadWorker.kickoff_with_task(&1, %{}, schedule_in: kickoff_delay))
|
|> Enum.each(&MediaDownloadWorker.kickoff_with_task/1)
|
||||||
end
|
end
|
||||||
|
|
||||||
def enqueue_pending_download_tasks(%Source{download_media: false}, _opts) do
|
def enqueue_pending_download_tasks(%Source{download_media: false}) do
|
||||||
:ok
|
:ok
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -61,18 +53,15 @@ defmodule Pinchflat.Downloading.DownloadingHelpers do
|
|||||||
downloaded, based on the source's download settings and whether media is
|
downloaded, based on the source's download settings and whether media is
|
||||||
considered pending.
|
considered pending.
|
||||||
|
|
||||||
You can optionally set the `kickoff_delay` option to delay when the jobs are enqueued.
|
|
||||||
|
|
||||||
Returns {:ok, %Task{}} | {:error, :should_not_download} | {:error, any()}
|
Returns {:ok, %Task{}} | {:error, :should_not_download} | {:error, any()}
|
||||||
"""
|
"""
|
||||||
def kickoff_download_if_pending(%MediaItem{} = media_item, opts \\ []) do
|
def kickoff_download_if_pending(%MediaItem{} = media_item) do
|
||||||
kickoff_delay = Keyword.get(opts, :kickoff_delay, 0)
|
|
||||||
media_item = Repo.preload(media_item, :source)
|
media_item = Repo.preload(media_item, :source)
|
||||||
|
|
||||||
if media_item.source.download_media && Media.pending_download?(media_item) do
|
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})")
|
Logger.info("Kicking off download for media item ##{media_item.id} (#{media_item.media_id})")
|
||||||
|
|
||||||
MediaDownloadWorker.kickoff_with_task(media_item, %{}, schedule_in: kickoff_delay)
|
MediaDownloadWorker.kickoff_with_task(media_item)
|
||||||
else
|
else
|
||||||
{:error, :should_not_download}
|
{:error, :should_not_download}
|
||||||
end
|
end
|
||||||
@@ -109,32 +98,4 @@ defmodule Pinchflat.Downloading.DownloadingHelpers do
|
|||||||
|> Repo.all()
|
|> Repo.all()
|
||||||
|> Enum.map(&MediaDownloadWorker.kickoff_with_task/1)
|
|> Enum.map(&MediaDownloadWorker.kickoff_with_task/1)
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc """
|
|
||||||
Creates a media item from the attributes returned by the video backend
|
|
||||||
(read: yt-dlp) and runs the user script with a `media_indexed` event type.
|
|
||||||
|
|
||||||
Only runs the user script if the media item was created successfully and the media item
|
|
||||||
doesn't already exist in the database.
|
|
||||||
|
|
||||||
Returns {:ok, %MediaItem{}} | {:error, any()}
|
|
||||||
"""
|
|
||||||
def create_media_item_and_run_script(%Source{} = source, media_attrs_struct) do
|
|
||||||
media_already_exists =
|
|
||||||
MediaQuery.new()
|
|
||||||
|> where(^dynamic(^MediaQuery.for_source(source) and ^MediaQuery.media_id(media_attrs_struct.media_id)))
|
|
||||||
|> Repo.exists?()
|
|
||||||
|
|
||||||
case Media.create_media_item_from_backend_attrs(source, media_attrs_struct) do
|
|
||||||
{:ok, media_item} ->
|
|
||||||
if !media_already_exists do
|
|
||||||
UserScriptRunner.run(:media_indexed, media_item)
|
|
||||||
end
|
|
||||||
|
|
||||||
{:ok, media_item}
|
|
||||||
|
|
||||||
err ->
|
|
||||||
err
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ defmodule Pinchflat.FastIndexing.FastIndexingHelpers do
|
|||||||
use Pinchflat.Media.MediaQuery
|
use Pinchflat.Media.MediaQuery
|
||||||
|
|
||||||
alias Pinchflat.Repo
|
alias Pinchflat.Repo
|
||||||
|
alias Pinchflat.Media
|
||||||
alias Pinchflat.Sources.Source
|
alias Pinchflat.Sources.Source
|
||||||
alias Pinchflat.FastIndexing.YoutubeRss
|
alias Pinchflat.FastIndexing.YoutubeRss
|
||||||
alias Pinchflat.Downloading.DownloadingHelpers
|
alias Pinchflat.Downloading.DownloadingHelpers
|
||||||
@@ -41,8 +42,7 @@ defmodule Pinchflat.FastIndexing.FastIndexingHelpers do
|
|||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
|
|
||||||
# Wait 5s before enqueuing downloads to give the post-indexing user script a chance to run
|
DownloadingHelpers.enqueue_pending_download_tasks(source)
|
||||||
DownloadingHelpers.enqueue_pending_download_tasks(source, kickoff_delay: 5)
|
|
||||||
|
|
||||||
Enum.filter(maybe_new_media_items, & &1)
|
Enum.filter(maybe_new_media_items, & &1)
|
||||||
end
|
end
|
||||||
@@ -57,8 +57,8 @@ defmodule Pinchflat.FastIndexing.FastIndexingHelpers do
|
|||||||
url = "https://www.youtube.com/watch?v=#{media_id}"
|
url = "https://www.youtube.com/watch?v=#{media_id}"
|
||||||
|
|
||||||
case YtDlpMedia.get_media_attributes(url) do
|
case YtDlpMedia.get_media_attributes(url) do
|
||||||
{:ok, media_attrs_struct} ->
|
{:ok, media_attrs} ->
|
||||||
DownloadingHelpers.create_media_item_and_run_script(source, media_attrs_struct)
|
Media.create_media_item_from_backend_attrs(source, media_attrs)
|
||||||
|
|
||||||
err ->
|
err ->
|
||||||
err
|
err
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ defmodule Pinchflat.Lifecycle.UserScripts.CommandRunner do
|
|||||||
@behaviour UserScriptCommandRunner
|
@behaviour UserScriptCommandRunner
|
||||||
|
|
||||||
@event_types [
|
@event_types [
|
||||||
:media_indexed,
|
|
||||||
:media_downloaded,
|
:media_downloaded,
|
||||||
:media_deleted
|
:media_deleted
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -41,9 +41,10 @@ defmodule Pinchflat.Media do
|
|||||||
Returns a list of media_items that are redownloadable based on the redownload delay
|
Returns a list of media_items that are redownloadable based on the redownload delay
|
||||||
of the media_profile their source belongs to.
|
of the media_profile their source belongs to.
|
||||||
|
|
||||||
The logic is that a media_item is past_redownload_delay if the media_item's
|
The logic is that a media_item is past_redownload_delay if the media_item's uploaded_at is
|
||||||
upload_date is at least redownload_delay_days ago AND
|
at least redownload_delay_days ago AND `media_downloaded_at` - `redownload_delay_days`
|
||||||
`media_downloaded_at` - `redownload_delay_days` is before the media_item's `upload_date`.
|
is before the media_item's `uploaded_at`.
|
||||||
|
|
||||||
This logic grabs media that we've recently downloaded AND is recently uploaded, but
|
This logic grabs media that we've recently downloaded AND is recently uploaded, but
|
||||||
doesn't grab media that we've recently downloaded and was uploaded a long time ago.
|
doesn't grab media that we've recently downloaded and was uploaded a long time ago.
|
||||||
This also makes things work as expected when downloading media from a source for the
|
This also makes things work as expected when downloading media from a source for the
|
||||||
@@ -135,7 +136,7 @@ defmodule Pinchflat.Media do
|
|||||||
|
|
||||||
Unlike `create_media_item`, this will attempt an update if the media_item
|
Unlike `create_media_item`, this will attempt an update if the media_item
|
||||||
already exists. This is so that future indexing can pick up attributes that
|
already exists. This is so that future indexing can pick up attributes that
|
||||||
we may not have asked for in the past (eg: upload_date)
|
we may not have asked for in the past (eg: uploaded_at)
|
||||||
|
|
||||||
Returns {:ok, %MediaItem{}} | {:error, %Ecto.Changeset{}}
|
Returns {:ok, %MediaItem{}} | {:error, %Ecto.Changeset{}}
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ defmodule Pinchflat.Media.MediaItem do
|
|||||||
:livestream,
|
:livestream,
|
||||||
:source_id,
|
:source_id,
|
||||||
:short_form_content,
|
:short_form_content,
|
||||||
:upload_date,
|
:uploaded_at,
|
||||||
:upload_date_index,
|
:upload_date_index,
|
||||||
:duration_seconds,
|
:duration_seconds,
|
||||||
# these fields are captured only on download
|
# these fields are captured only on download
|
||||||
@@ -51,7 +51,7 @@ defmodule Pinchflat.Media.MediaItem do
|
|||||||
livestream
|
livestream
|
||||||
media_id
|
media_id
|
||||||
source_id
|
source_id
|
||||||
upload_date
|
uploaded_at
|
||||||
short_form_content
|
short_form_content
|
||||||
)a
|
)a
|
||||||
|
|
||||||
@@ -69,7 +69,7 @@ defmodule Pinchflat.Media.MediaItem do
|
|||||||
field :short_form_content, :boolean, default: false
|
field :short_form_content, :boolean, default: false
|
||||||
field :media_downloaded_at, :utc_datetime
|
field :media_downloaded_at, :utc_datetime
|
||||||
field :media_redownloaded_at, :utc_datetime
|
field :media_redownloaded_at, :utc_datetime
|
||||||
field :upload_date, :date
|
field :uploaded_at, :utc_datetime
|
||||||
field :upload_date_index, :integer, default: 0
|
field :upload_date_index, :integer, default: 0
|
||||||
field :duration_seconds, :integer
|
field :duration_seconds, :integer
|
||||||
|
|
||||||
@@ -130,7 +130,7 @@ defmodule Pinchflat.Media.MediaItem do
|
|||||||
~w(__meta__ __struct__ metadata tasks media_items_search_index)a
|
~w(__meta__ __struct__ metadata tasks media_items_search_index)a
|
||||||
end
|
end
|
||||||
|
|
||||||
defp update_upload_date_index(%{changes: changes} = changeset) when is_map_key(changes, :upload_date) do
|
defp update_upload_date_index(%{changes: changes} = changeset) when is_map_key(changes, :uploaded_at) do
|
||||||
source_id = get_field(changeset, :source_id)
|
source_id = get_field(changeset, :source_id)
|
||||||
source = Sources.get_source!(source_id)
|
source = Sources.get_source!(source_id)
|
||||||
# Channels should count down from 99, playlists should count up from 0
|
# Channels should count down from 99, playlists should count up from 0
|
||||||
@@ -142,7 +142,7 @@ defmodule Pinchflat.Media.MediaItem do
|
|||||||
|
|
||||||
current_max =
|
current_max =
|
||||||
MediaQuery.new()
|
MediaQuery.new()
|
||||||
|> where(^dynamic([mi], mi.upload_date == ^changes.upload_date and ^MediaQuery.for_source(source)))
|
|> where(^dynamic([mi], ^MediaQuery.upload_date_matches(changes.uploaded_at) and ^MediaQuery.for_source(source)))
|
||||||
|> Repo.aggregate(aggregator, :upload_date_index)
|
|> Repo.aggregate(aggregator, :upload_date_index)
|
||||||
|
|
||||||
case current_max do
|
case current_max do
|
||||||
|
|||||||
@@ -35,11 +35,14 @@ defmodule Pinchflat.Media.MediaQuery do
|
|||||||
def culling_prevented, do: dynamic([mi], mi.prevent_culling == true)
|
def culling_prevented, do: dynamic([mi], mi.prevent_culling == true)
|
||||||
def culled, do: dynamic([mi], not is_nil(mi.culled_at))
|
def culled, do: dynamic([mi], not is_nil(mi.culled_at))
|
||||||
def redownloaded, do: dynamic([mi], not is_nil(mi.media_redownloaded_at))
|
def redownloaded, do: dynamic([mi], not is_nil(mi.media_redownloaded_at))
|
||||||
def media_id(nil), do: dynamic(false)
|
def upload_date_matches(other_date), do: dynamic([mi], fragment("date(?) = date(?)", mi.uploaded_at, ^other_date))
|
||||||
def media_id(media_id), do: dynamic([mi], mi.media_id == ^media_id)
|
|
||||||
|
|
||||||
def upload_date_after_source_cutoff do
|
def upload_date_after_source_cutoff do
|
||||||
dynamic([mi, source], is_nil(source.download_cutoff_date) or mi.upload_date >= source.download_cutoff_date)
|
dynamic(
|
||||||
|
[mi, source],
|
||||||
|
is_nil(source.download_cutoff_date) or
|
||||||
|
fragment("date(?) >= ?", mi.uploaded_at, source.download_cutoff_date)
|
||||||
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
def format_matching_profile_preference do
|
def format_matching_profile_preference do
|
||||||
@@ -86,12 +89,12 @@ defmodule Pinchflat.Media.MediaQuery do
|
|||||||
def past_redownload_delay do
|
def past_redownload_delay do
|
||||||
dynamic(
|
dynamic(
|
||||||
[mi, source, media_profile],
|
[mi, source, media_profile],
|
||||||
# Returns media items where the upload_date is at least redownload_delay_days ago AND
|
# Returns media items where the uploaded_at is at least redownload_delay_days ago AND
|
||||||
# downloaded_at minus the redownload_delay_days is before the upload date
|
# downloaded_at minus the redownload_delay_days is before the upload date
|
||||||
fragment("""
|
fragment("""
|
||||||
IFNULL(redownload_delay_days, 0) > 0 AND
|
IFNULL(redownload_delay_days, 0) > 0 AND
|
||||||
DATETIME('now', '-' || redownload_delay_days || ' day') > upload_date AND
|
DATETIME('now', '-' || redownload_delay_days || ' day') > uploaded_at AND
|
||||||
DATETIME(media_downloaded_at, '-' || redownload_delay_days || ' day') < upload_date
|
DATETIME(media_downloaded_at, '-' || redownload_delay_days || ' day') < uploaded_at
|
||||||
""")
|
""")
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -88,14 +88,17 @@ defmodule Pinchflat.Metadata.MetadataFileHelpers do
|
|||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Parses an upload date from the YYYYMMDD string returned in yt-dlp metadata
|
Parses an upload date from the YYYYMMDD string returned in yt-dlp metadata
|
||||||
and returns a Date struct.
|
and returns a DateTime struct, appending a time of 00:00:00Z.
|
||||||
|
|
||||||
Returns Date.t()
|
Returns DateTime.t()
|
||||||
"""
|
"""
|
||||||
def parse_upload_date(upload_date) do
|
def parse_upload_date(upload_date) do
|
||||||
<<year::binary-size(4)>> <> <<month::binary-size(2)>> <> <<day::binary-size(2)>> = upload_date
|
<<year::binary-size(4)>> <> <<month::binary-size(2)>> <> <<day::binary-size(2)>> = upload_date
|
||||||
|
|
||||||
Date.from_iso8601!("#{year}-#{month}-#{day}")
|
case DateTime.from_iso8601("#{year}-#{month}-#{day}T00:00:00Z") do
|
||||||
|
{:ok, datetime, _} -> datetime
|
||||||
|
_ -> raise "Invalid upload date: #{upload_date}"
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ defmodule Pinchflat.Metadata.MetadataParser do
|
|||||||
and not have it, ya know?
|
and not have it, ya know?
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
alias Pinchflat.YtDlp.Media, as: YtDlpMedia
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Parses the given JSON response from yt-dlp and returns a map of
|
Parses the given JSON response from yt-dlp and returns a map of
|
||||||
the needful media_item attributes, along with anything needed for
|
the needful media_item attributes, along with anything needed for
|
||||||
@@ -24,15 +26,12 @@ defmodule Pinchflat.Metadata.MetadataParser do
|
|||||||
end
|
end
|
||||||
|
|
||||||
defp parse_media_metadata(metadata) do
|
defp parse_media_metadata(metadata) do
|
||||||
%{
|
Map.merge(
|
||||||
media_id: metadata["id"],
|
Map.from_struct(YtDlpMedia.response_to_struct(metadata)),
|
||||||
title: metadata["title"],
|
%{
|
||||||
original_url: metadata["original_url"],
|
media_filepath: metadata["filepath"]
|
||||||
description: metadata["description"],
|
}
|
||||||
media_filepath: metadata["filepath"],
|
)
|
||||||
livestream: !!metadata["was_live"],
|
|
||||||
duration_seconds: metadata["duration"] && round(metadata["duration"])
|
|
||||||
}
|
|
||||||
end
|
end
|
||||||
|
|
||||||
defp parse_subtitle_metadata(metadata) do
|
defp parse_subtitle_metadata(metadata) do
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ defmodule Pinchflat.Podcasts.PodcastHelpers do
|
|||||||
|
|
||||||
MediaQuery.new()
|
MediaQuery.new()
|
||||||
|> where(^dynamic(^MediaQuery.for_source(source) and ^MediaQuery.downloaded()))
|
|> where(^dynamic(^MediaQuery.for_source(source) and ^MediaQuery.downloaded()))
|
||||||
|> order_by(desc: :upload_date)
|
|> order_by(desc: :uploaded_at)
|
||||||
|> Repo.maybe_limit(limit)
|
|> Repo.maybe_limit(limit)
|
||||||
|> Repo.all()
|
|> Repo.all()
|
||||||
|> Enum.filter(fn media_item -> File.exists?(media_item.media_filepath) end)
|
|> Enum.filter(fn media_item -> File.exists?(media_item.media_filepath) end)
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ defmodule Pinchflat.Podcasts.RssFeedBuilder do
|
|||||||
|
|
||||||
import Pinchflat.Utils.XmlUtils, only: [safe: 1]
|
import Pinchflat.Utils.XmlUtils, only: [safe: 1]
|
||||||
|
|
||||||
alias Pinchflat.Utils.DatetimeUtils
|
|
||||||
alias Pinchflat.Podcasts.PodcastHelpers
|
alias Pinchflat.Podcasts.PodcastHelpers
|
||||||
alias PinchflatWeb.Router.Helpers, as: Routes
|
alias PinchflatWeb.Router.Helpers, as: Routes
|
||||||
|
|
||||||
@@ -83,7 +82,7 @@ defmodule Pinchflat.Podcasts.RssFeedBuilder do
|
|||||||
<title>#{safe(media_item.title)}</title>
|
<title>#{safe(media_item.title)}</title>
|
||||||
<link>#{safe(media_item.original_url)}</link>
|
<link>#{safe(media_item.original_url)}</link>
|
||||||
<description>#{safe(media_item.description)}</description>
|
<description>#{safe(media_item.description)}</description>
|
||||||
<pubDate>#{generate_upload_date(media_item)}</pubDate>
|
<pubDate>#{Calendar.strftime(media_item.uploaded_at, @datetime_format)}</pubDate>
|
||||||
<itunes:duration>#{media_item.duration_seconds}</itunes:duration>
|
<itunes:duration>#{media_item.duration_seconds}</itunes:duration>
|
||||||
<enclosure
|
<enclosure
|
||||||
url="#{media_stream_path(url_base, media_item)}"
|
url="#{media_stream_path(url_base, media_item)}"
|
||||||
@@ -133,12 +132,6 @@ defmodule Pinchflat.Podcasts.RssFeedBuilder do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp generate_upload_date(media_item) do
|
|
||||||
media_item.upload_date
|
|
||||||
|> DatetimeUtils.date_to_datetime()
|
|
||||||
|> Calendar.strftime(@datetime_format)
|
|
||||||
end
|
|
||||||
|
|
||||||
defp podcast_route(action, params) do
|
defp podcast_route(action, params) do
|
||||||
Routes.podcast_path(PinchflatWeb.Endpoint, action, params)
|
Routes.podcast_path(PinchflatWeb.Endpoint, action, params)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ defmodule Pinchflat.SlowIndexing.SlowIndexingHelpers do
|
|||||||
require Logger
|
require Logger
|
||||||
|
|
||||||
alias Pinchflat.Repo
|
alias Pinchflat.Repo
|
||||||
|
alias Pinchflat.Media
|
||||||
alias Pinchflat.Tasks
|
alias Pinchflat.Tasks
|
||||||
alias Pinchflat.Sources
|
alias Pinchflat.Sources
|
||||||
alias Pinchflat.Sources.Source
|
alias Pinchflat.Sources.Source
|
||||||
@@ -38,9 +39,6 @@ defmodule Pinchflat.SlowIndexing.SlowIndexingHelpers do
|
|||||||
item belonging to the source. You can't tell me the method name isn't descriptive!
|
item belonging to the source. You can't tell me the method name isn't descriptive!
|
||||||
Returns a list of media items or changesets (if the media item couldn't be created).
|
Returns a list of media items or changesets (if the media item couldn't be created).
|
||||||
|
|
||||||
For each new media item, the method will also run a user script with the `media_indexed`
|
|
||||||
event, if the script is present.
|
|
||||||
|
|
||||||
Indexing is slow and usually returns a list of all media data at once for record creation.
|
Indexing is slow and usually returns a list of all media data at once for record creation.
|
||||||
To help with this, we use a file follower to watch the file that yt-dlp writes to
|
To help with this, we use a file follower to watch the file that yt-dlp writes to
|
||||||
so we can create media items as they come in. This parallelizes the process and adds
|
so we can create media items as they come in. This parallelizes the process and adds
|
||||||
@@ -66,16 +64,15 @@ defmodule Pinchflat.SlowIndexing.SlowIndexingHelpers do
|
|||||||
source = Repo.reload!(source)
|
source = Repo.reload!(source)
|
||||||
|
|
||||||
result =
|
result =
|
||||||
Enum.map(media_attributes, fn media_attrs_struct ->
|
Enum.map(media_attributes, fn media_attrs ->
|
||||||
case DownloadingHelpers.create_media_item_and_run_script(source, media_attrs_struct) do
|
case Media.create_media_item_from_backend_attrs(source, media_attrs) do
|
||||||
{:ok, media_item} -> media_item
|
{:ok, media_item} -> media_item
|
||||||
{:error, changeset} -> changeset
|
{:error, changeset} -> changeset
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
|
|
||||||
Sources.update_source(source, %{last_indexed_at: DateTime.utc_now()})
|
Sources.update_source(source, %{last_indexed_at: DateTime.utc_now()})
|
||||||
# Wait 5s before enqueuing downloads to give the post-indexing user script a chance to run
|
DownloadingHelpers.enqueue_pending_download_tasks(source)
|
||||||
DownloadingHelpers.enqueue_pending_download_tasks(source, kickoff_delay: 5)
|
|
||||||
|
|
||||||
result
|
result
|
||||||
end
|
end
|
||||||
@@ -121,15 +118,14 @@ defmodule Pinchflat.SlowIndexing.SlowIndexingHelpers do
|
|||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
defp create_media_item_and_enqueue_download(source, media_attrs_struct) do
|
defp create_media_item_and_enqueue_download(source, media_attrs) do
|
||||||
# Reload because the source may have been updated during the (long-running) indexing process
|
# Reload because the source may have been updated during the (long-running) indexing process
|
||||||
# and important settings like `download_media` may have changed.
|
# and important settings like `download_media` may have changed.
|
||||||
source = Repo.reload!(source)
|
source = Repo.reload!(source)
|
||||||
|
|
||||||
case DownloadingHelpers.create_media_item_and_run_script(source, media_attrs_struct) do
|
case Media.create_media_item_from_backend_attrs(source, media_attrs) do
|
||||||
{:ok, %MediaItem{} = media_item} ->
|
{:ok, %MediaItem{} = media_item} ->
|
||||||
# Wait 5s before enqueuing downloads to give the post-indexing user script a chance to run
|
DownloadingHelpers.kickoff_download_if_pending(media_item)
|
||||||
DownloadingHelpers.kickoff_download_if_pending(media_item, kickoff_delay: 5)
|
|
||||||
|
|
||||||
{:error, changeset} ->
|
{:error, changeset} ->
|
||||||
changeset
|
changeset
|
||||||
|
|||||||
@@ -1,17 +0,0 @@
|
|||||||
defmodule Pinchflat.Utils.DatetimeUtils do
|
|
||||||
@moduledoc """
|
|
||||||
Utility methods for working with dates and datetimes
|
|
||||||
"""
|
|
||||||
|
|
||||||
@doc """
|
|
||||||
Converts a Date to a DateTime
|
|
||||||
|
|
||||||
Returns %DateTime{}
|
|
||||||
"""
|
|
||||||
def date_to_datetime(date) do
|
|
||||||
date
|
|
||||||
|> Date.to_gregorian_days()
|
|
||||||
|> Kernel.*(86_400)
|
|
||||||
|> DateTime.from_gregorian_seconds()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -10,7 +10,7 @@ defmodule Pinchflat.YtDlp.Media do
|
|||||||
:original_url,
|
:original_url,
|
||||||
:livestream,
|
:livestream,
|
||||||
:short_form_content,
|
:short_form_content,
|
||||||
:upload_date,
|
:uploaded_at,
|
||||||
:duration_seconds
|
:duration_seconds
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -21,7 +21,7 @@ defmodule Pinchflat.YtDlp.Media do
|
|||||||
:original_url,
|
:original_url,
|
||||||
:livestream,
|
:livestream,
|
||||||
:short_form_content,
|
:short_form_content,
|
||||||
:upload_date,
|
:uploaded_at,
|
||||||
:duration_seconds
|
:duration_seconds
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -72,7 +72,7 @@ defmodule Pinchflat.YtDlp.Media do
|
|||||||
Returns the output template for yt-dlp's indexing command.
|
Returns the output template for yt-dlp's indexing command.
|
||||||
"""
|
"""
|
||||||
def indexing_output_template do
|
def indexing_output_template do
|
||||||
"%(.{id,title,was_live,webpage_url,description,aspect_ratio,duration,upload_date})j"
|
"%(.{id,title,was_live,webpage_url,description,aspect_ratio,duration,upload_date,timestamp})j"
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
@@ -90,7 +90,7 @@ defmodule Pinchflat.YtDlp.Media do
|
|||||||
livestream: !!response["was_live"],
|
livestream: !!response["was_live"],
|
||||||
duration_seconds: response["duration"] && round(response["duration"]),
|
duration_seconds: response["duration"] && round(response["duration"]),
|
||||||
short_form_content: response["webpage_url"] && short_form_content?(response),
|
short_form_content: response["webpage_url"] && short_form_content?(response),
|
||||||
upload_date: response["upload_date"] && MetadataFileHelpers.parse_upload_date(response["upload_date"])
|
uploaded_at: response["upload_date"] && parse_uploaded_at(response)
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -110,6 +110,18 @@ defmodule Pinchflat.YtDlp.Media do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp parse_uploaded_at(%{"timestamp" => ts} = response) when is_number(ts) do
|
||||||
|
case DateTime.from_unix(ts) do
|
||||||
|
{:ok, datetime} -> datetime
|
||||||
|
_ -> MetadataFileHelpers.parse_upload_date(response["upload_date"])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# This field is needed before inserting into the database, but absence
|
||||||
|
# of this field should fail at insert-time rather than here
|
||||||
|
defp parse_uploaded_at(%{"upload_date" => nil}), do: nil
|
||||||
|
defp parse_uploaded_at(response), do: MetadataFileHelpers.parse_upload_date(response["upload_date"])
|
||||||
|
|
||||||
defp backend_runner do
|
defp backend_runner do
|
||||||
# This approach lets us mock the command for testing
|
# This approach lets us mock the command for testing
|
||||||
Application.get_env(:pinchflat, :yt_dlp_runner)
|
Application.get_env(:pinchflat, :yt_dlp_runner)
|
||||||
|
|||||||
@@ -2,7 +2,8 @@ defmodule PinchflatWeb.CustomComponents.TableComponents do
|
|||||||
@moduledoc false
|
@moduledoc false
|
||||||
use Phoenix.Component
|
use Phoenix.Component
|
||||||
|
|
||||||
alias PinchflatWeb.CoreComponents
|
import PinchflatWeb.CoreComponents
|
||||||
|
import PinchflatWeb.CustomComponents.TextComponents
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Renders a table component with the given rows and columns.
|
Renders a table component with the given rows and columns.
|
||||||
@@ -76,11 +77,13 @@ defmodule PinchflatWeb.CustomComponents.TableComponents do
|
|||||||
phx-click={@page_number != 1 && "page_change"}
|
phx-click={@page_number != 1 && "page_change"}
|
||||||
phx-value-direction="dec"
|
phx-value-direction="dec"
|
||||||
>
|
>
|
||||||
<CoreComponents.icon name="hero-chevron-left" />
|
<.icon name="hero-chevron-left" />
|
||||||
</span>
|
</span>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<span class="mx-2">Page <%= @page_number %> of <%= @total_pages %></span>
|
<span class="mx-2">
|
||||||
|
Page <.localized_number number={@page_number} /> of <.localized_number number={@total_pages} />
|
||||||
|
</span>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<span
|
<span
|
||||||
@@ -92,7 +95,7 @@ defmodule PinchflatWeb.CustomComponents.TableComponents do
|
|||||||
phx-click={@page_number != @total_pages && "page_change"}
|
phx-click={@page_number != @total_pages && "page_change"}
|
||||||
phx-value-direction="inc"
|
phx-value-direction="inc"
|
||||||
>
|
>
|
||||||
<CoreComponents.icon name="hero-chevron-right" />
|
<.icon name="hero-chevron-right" />
|
||||||
</span>
|
</span>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|||||||
@@ -94,4 +94,28 @@ defmodule PinchflatWeb.CustomComponents.TextComponents do
|
|||||||
<time><%= Calendar.strftime(Timex.Timezone.convert(@datetime, @timezone), @format) %></time>
|
<time><%= Calendar.strftime(Timex.Timezone.convert(@datetime, @timezone), @format) %></time>
|
||||||
"""
|
"""
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Renders a localized number using the Intl.NumberFormat API, falling back to the raw number if needed
|
||||||
|
"""
|
||||||
|
attr :number, :any, required: true
|
||||||
|
|
||||||
|
def localized_number(assigns) do
|
||||||
|
~H"""
|
||||||
|
<span x-data x-text={"Intl.NumberFormat().format(#{@number})"}><%= @number %></span>
|
||||||
|
"""
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Renders a word with a suffix if the count is not 1
|
||||||
|
"""
|
||||||
|
attr :word, :string, required: true
|
||||||
|
attr :count, :integer, required: true
|
||||||
|
attr :suffix, :string, default: "s"
|
||||||
|
|
||||||
|
def pluralize(assigns) do
|
||||||
|
~H"""
|
||||||
|
<%= @word %><%= if @count == 1, do: "", else: @suffix %>
|
||||||
|
"""
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -19,6 +19,7 @@
|
|||||||
onboarding: #{Settings.get!(:onboarding)}
|
onboarding: #{Settings.get!(:onboarding)}
|
||||||
}"}
|
}"}
|
||||||
class="dark text-bodydark bg-boxdark-2"
|
class="dark text-bodydark bg-boxdark-2"
|
||||||
|
data-socket-path={Path.join(Application.get_env(:pinchflat, :base_route_path), "/live")}
|
||||||
>
|
>
|
||||||
<%= @inner_content %>
|
<%= @inner_content %>
|
||||||
|
|
||||||
|
|||||||
@@ -31,7 +31,7 @@
|
|||||||
<.media_preview media_item={@media_item} />
|
<.media_preview media_item={@media_item} />
|
||||||
</div>
|
</div>
|
||||||
<aside class="mt-4 xl:mt-0">
|
<aside class="mt-4 xl:mt-0">
|
||||||
<div>Uploaded: <%= @media_item.upload_date %></div>
|
<div>Uploaded: <%= DateTime.to_date(@media_item.uploaded_at) %></div>
|
||||||
<div>
|
<div>
|
||||||
<span :if={URI.parse(@media_item.original_url).scheme =~ "http"}>
|
<span :if={URI.parse(@media_item.original_url).scheme =~ "http"}>
|
||||||
<.subtle_link href={@media_item.original_url} target="_blank">Open Original</.subtle_link>
|
<.subtle_link href={@media_item.original_url} target="_blank">Open Original</.subtle_link>
|
||||||
@@ -43,7 +43,7 @@
|
|||||||
</.subtle_link>
|
</.subtle_link>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-4 text-bodydark">
|
<div :if={@media_item.description} class="mt-4 text-bodydark">
|
||||||
<.break_on_newline text={@media_item.description} />
|
<.break_on_newline text={@media_item.description} />
|
||||||
</div>
|
</div>
|
||||||
</aside>
|
</aside>
|
||||||
|
|||||||
@@ -5,9 +5,19 @@ defmodule PinchflatWeb.Pages.PageHTML do
|
|||||||
|
|
||||||
embed_templates "page_html/*"
|
embed_templates "page_html/*"
|
||||||
|
|
||||||
def readable_media_filesize(media_filesize) do
|
attr :media_filesize, :integer, required: true
|
||||||
{num, suffix} = NumberUtils.human_byte_size(media_filesize, precision: 1)
|
|
||||||
|
|
||||||
"#{Float.round(num)} #{suffix}"
|
def readable_media_filesize(assigns) do
|
||||||
|
{num, suffix} = NumberUtils.human_byte_size(assigns.media_filesize, precision: 2)
|
||||||
|
|
||||||
|
assigns =
|
||||||
|
Map.merge(assigns, %{
|
||||||
|
num: num,
|
||||||
|
suffix: suffix
|
||||||
|
})
|
||||||
|
|
||||||
|
~H"""
|
||||||
|
<.localized_number number={@num} /> <%= @suffix %>
|
||||||
|
"""
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -22,7 +22,9 @@ defmodule Pinchflat.Pages.HistoryTableLive do
|
|||||||
<div>
|
<div>
|
||||||
<span class="mb-4 flex items-center">
|
<span class="mb-4 flex items-center">
|
||||||
<.icon_button icon_name="hero-arrow-path" class="h-10 w-10" phx-click="reload_page" tooltip="Refresh" />
|
<.icon_button icon_name="hero-arrow-path" class="h-10 w-10" phx-click="reload_page" tooltip="Refresh" />
|
||||||
<span class="ml-2">Showing <%= length(@records) %> of <%= @total_record_count %></span>
|
<span class="ml-2">
|
||||||
|
Showing <.localized_number number={length(@records)} /> of <.localized_number number={@total_record_count} />
|
||||||
|
</span>
|
||||||
</span>
|
</span>
|
||||||
<div class="max-w-full overflow-x-auto">
|
<div class="max-w-full overflow-x-auto">
|
||||||
<.table rows={@records} table_class="text-white">
|
<.table rows={@records} table_class="text-white">
|
||||||
@@ -32,7 +34,7 @@ defmodule Pinchflat.Pages.HistoryTableLive do
|
|||||||
</.subtle_link>
|
</.subtle_link>
|
||||||
</:col>
|
</:col>
|
||||||
<:col :let={media_item} label="Upload Date">
|
<:col :let={media_item} label="Upload Date">
|
||||||
<%= media_item.upload_date %>
|
<%= DateTime.to_date(media_item.uploaded_at) %>
|
||||||
</:col>
|
</:col>
|
||||||
<:col :let={media_item} label="Indexed At">
|
<:col :let={media_item} label="Indexed At">
|
||||||
<%= format_datetime(media_item.inserted_at) %>
|
<%= format_datetime(media_item.inserted_at) %>
|
||||||
|
|||||||
@@ -1,33 +1,39 @@
|
|||||||
<div class="grid grid-cols-1 gap-4 md:grid-cols-4">
|
<div class="grid grid-cols-1 gap-4 md:grid-cols-4">
|
||||||
<div class="rounded-sm border px-7.5 py-6 shadow-default border-strokedark bg-boxdark">
|
<div class="flex flex-col justify-center rounded-sm border px-7.5 py-6 shadow-default border-strokedark bg-boxdark">
|
||||||
<a href={~p"/media_profiles"} class="mt-4 flex flex-col items-center justify-center">
|
<a href={~p"/media_profiles"} class="flex flex-col items-center py-2">
|
||||||
<span class="text-md font-medium">Media Profile(s)</span>
|
<span class="text-md font-medium">
|
||||||
|
Media <.pluralize count={@media_profile_count} word="Profile" />
|
||||||
|
</span>
|
||||||
<h4 class="text-title-md font-bold text-white">
|
<h4 class="text-title-md font-bold text-white">
|
||||||
<%= @media_profile_count %>
|
<.localized_number number={@media_profile_count} />
|
||||||
</h4>
|
</h4>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="rounded-sm border px-7.5 py-6 shadow-default border-strokedark bg-boxdark">
|
<div class="flex flex-col justify-center rounded-sm border px-7.5 py-6 shadow-default border-strokedark bg-boxdark">
|
||||||
<a href={~p"/sources"} class="mt-4 flex flex-col items-center justify-center">
|
<a href={~p"/sources"} class="flex flex-col items-center py-2">
|
||||||
<span class="text-md font-medium">Source(s)</span>
|
<span class="text-md font-medium">
|
||||||
|
<.pluralize count={@source_count} word="Source" />
|
||||||
|
</span>
|
||||||
<h4 class="text-title-md font-bold text-white">
|
<h4 class="text-title-md font-bold text-white">
|
||||||
<%= @source_count %>
|
<.localized_number number={@source_count} />
|
||||||
</h4>
|
</h4>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="rounded-sm border px-7.5 py-6 shadow-default border-strokedark bg-boxdark">
|
<div class="flex flex-col justify-center rounded-sm border px-7.5 py-6 shadow-default border-strokedark bg-boxdark">
|
||||||
<span class="mt-4 flex flex-col items-center justify-center">
|
<span class="flex flex-col items-center py-2">
|
||||||
<span class="text-md font-medium">Downloaded Media</span>
|
<span class="text-md font-medium">
|
||||||
|
<.pluralize count={@media_item_count} word="Download" />
|
||||||
|
</span>
|
||||||
<h4 class="text-title-md font-bold text-white">
|
<h4 class="text-title-md font-bold text-white">
|
||||||
<%= @media_item_count %>
|
<.localized_number number={@media_item_count} />
|
||||||
</h4>
|
</h4>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="rounded-sm border px-7.5 py-6 shadow-default border-strokedark bg-boxdark">
|
<div class="flex flex-col justify-center rounded-sm border px-7.5 py-6 shadow-default border-strokedark bg-boxdark">
|
||||||
<span class="mt-4 flex flex-col items-center justify-center">
|
<span class="flex flex-col items-center py-2">
|
||||||
<span class="text-md font-medium">Library Size</span>
|
<span class="text-md font-medium">Library Size</span>
|
||||||
<h4 class="text-title-md font-bold text-white">
|
<h4 class="text-title-md font-bold text-white">
|
||||||
<%= readable_media_filesize(@media_item_size) %>
|
<.readable_media_filesize media_filesize={@media_item_size} />
|
||||||
</h4>
|
</h4>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -19,11 +19,12 @@
|
|||||||
</.subtle_link>
|
</.subtle_link>
|
||||||
</:col>
|
</:col>
|
||||||
<:col :let={source} label="Type"><%= source.collection_type %></:col>
|
<:col :let={source} label="Type"><%= source.collection_type %></:col>
|
||||||
<:col :let={source} label="Pending"><%= source.pending_count %></:col>
|
<:col :let={source} label="Pending"><.localized_number number={source.pending_count} /></:col>
|
||||||
<:col :let={source} label="Downloaded"><%= source.downloaded_count %></:col>
|
<:col :let={source} label="Downloaded"><.localized_number number={source.downloaded_count} /></:col>
|
||||||
<:col :let={source} label="Retention">
|
<:col :let={source} label="Retention">
|
||||||
<%= if source.retention_period_days && source.retention_period_days > 0 do %>
|
<%= if source.retention_period_days && source.retention_period_days > 0 do %>
|
||||||
<%= source.retention_period_days %> day(s)
|
<.localized_number number={source.retention_period_days} />
|
||||||
|
<.pluralize count={source.retention_period_days} word="day" />
|
||||||
<% else %>
|
<% else %>
|
||||||
<span class="text-lg">∞</span>
|
<span class="text-lg">∞</span>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|||||||
@@ -23,7 +23,9 @@ defmodule Pinchflat.Sources.MediaItemTableLive do
|
|||||||
<header class="flex justify-between items-center mb-4">
|
<header class="flex justify-between items-center mb-4">
|
||||||
<span class="flex items-center">
|
<span class="flex items-center">
|
||||||
<.icon_button icon_name="hero-arrow-path" class="h-10 w-10" phx-click="reload_page" tooltip="Refresh" />
|
<.icon_button icon_name="hero-arrow-path" class="h-10 w-10" phx-click="reload_page" tooltip="Refresh" />
|
||||||
<span class="ml-2">Showing <%= length(@records) %> of <%= @filtered_record_count %></span>
|
<span class="ml-2">
|
||||||
|
Showing <.localized_number number={length(@records)} /> of <.localized_number number={@filtered_record_count} />
|
||||||
|
</span>
|
||||||
</span>
|
</span>
|
||||||
<div class="bg-meta-4 rounded-md">
|
<div class="bg-meta-4 rounded-md">
|
||||||
<div class="relative">
|
<div class="relative">
|
||||||
@@ -49,12 +51,12 @@ defmodule Pinchflat.Sources.MediaItemTableLive do
|
|||||||
<%= StringUtils.truncate(media_item.title, 50) %>
|
<%= StringUtils.truncate(media_item.title, 50) %>
|
||||||
</.subtle_link>
|
</.subtle_link>
|
||||||
</:col>
|
</:col>
|
||||||
<:col :let={media_item} label="Upload Date">
|
|
||||||
<%= media_item.upload_date %>
|
|
||||||
</:col>
|
|
||||||
<:col :let={media_item} :if={@media_state == "other"} label="Manually Ignored?">
|
<:col :let={media_item} :if={@media_state == "other"} label="Manually Ignored?">
|
||||||
<.icon name={if media_item.prevent_download, do: "hero-check", else: "hero-x-mark"} />
|
<.icon name={if media_item.prevent_download, do: "hero-check", else: "hero-x-mark"} />
|
||||||
</:col>
|
</:col>
|
||||||
|
<:col :let={media_item} label="Upload Date">
|
||||||
|
<%= DateTime.to_date(media_item.uploaded_at) %>
|
||||||
|
</:col>
|
||||||
<:col :let={media_item} label="" class="flex justify-end">
|
<:col :let={media_item} label="" class="flex justify-end">
|
||||||
<.icon_link href={~p"/sources/#{@source.id}/media/#{media_item.id}/edit"} icon="hero-pencil-square" class="mr-4" />
|
<.icon_link href={~p"/sources/#{@source.id}/media/#{media_item.id}/edit"} icon="hero-pencil-square" class="mr-4" />
|
||||||
</:col>
|
</:col>
|
||||||
@@ -117,14 +119,40 @@ defmodule Pinchflat.Sources.MediaItemTableLive do
|
|||||||
{:noreply, assign(socket, new_assigns)}
|
{:noreply, assign(socket, new_assigns)}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp fetch_pagination_attributes(base_query, page, ""), do: fetch_pagination_attributes(base_query, page, nil)
|
||||||
|
|
||||||
|
defp fetch_pagination_attributes(base_query, page, nil) do
|
||||||
|
total_record_count = Repo.aggregate(base_query, :count, :id)
|
||||||
|
total_pages = max(ceil(total_record_count / @limit), 1)
|
||||||
|
page = NumberUtils.clamp(page, 1, total_pages)
|
||||||
|
|
||||||
|
records =
|
||||||
|
fetch_records(base_query, page)
|
||||||
|
|> order_by(desc: :uploaded_at)
|
||||||
|
|> Repo.all()
|
||||||
|
|
||||||
|
%{
|
||||||
|
page: page,
|
||||||
|
total_pages: total_pages,
|
||||||
|
records: records,
|
||||||
|
search_term: nil,
|
||||||
|
total_record_count: total_record_count,
|
||||||
|
filtered_record_count: total_record_count
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
defp fetch_pagination_attributes(base_query, page, search_term) do
|
defp fetch_pagination_attributes(base_query, page, search_term) do
|
||||||
filtered_base_query = filter_base_query(base_query, search_term)
|
filtered_base_query = filtered_base_query(base_query, search_term)
|
||||||
|
|
||||||
total_record_count = Repo.aggregate(base_query, :count, :id)
|
total_record_count = Repo.aggregate(base_query, :count, :id)
|
||||||
filtered_record_count = Repo.aggregate(filtered_base_query, :count, :id)
|
filtered_record_count = Repo.aggregate(filtered_base_query, :count, :id)
|
||||||
total_pages = max(ceil(filtered_record_count / @limit), 1)
|
total_pages = max(ceil(filtered_record_count / @limit), 1)
|
||||||
page = NumberUtils.clamp(page, 1, total_pages)
|
page = NumberUtils.clamp(page, 1, total_pages)
|
||||||
records = fetch_records(filtered_base_query, page)
|
|
||||||
|
records =
|
||||||
|
fetch_records(filtered_base_query, page)
|
||||||
|
|> order_by(desc: fragment("rank"), desc: :uploaded_at)
|
||||||
|
|> Repo.all()
|
||||||
|
|
||||||
%{
|
%{
|
||||||
page: page,
|
page: page,
|
||||||
@@ -142,39 +170,41 @@ defmodule Pinchflat.Sources.MediaItemTableLive do
|
|||||||
base_query
|
base_query
|
||||||
|> limit(^@limit)
|
|> limit(^@limit)
|
||||||
|> offset(^offset)
|
|> offset(^offset)
|
||||||
|> Repo.all()
|
|
||||||
end
|
end
|
||||||
|
|
||||||
defp generate_base_query(source, "pending") do
|
defp generate_base_query(source, "pending") do
|
||||||
MediaQuery.new()
|
MediaQuery.new()
|
||||||
|
|> select(^select_fields())
|
||||||
|> MediaQuery.require_assoc(:media_profile)
|
|> MediaQuery.require_assoc(:media_profile)
|
||||||
|> MediaQuery.require_assoc(:media_items_search_index)
|
|
||||||
|> where(^dynamic(^MediaQuery.for_source(source) and ^MediaQuery.pending()))
|
|> where(^dynamic(^MediaQuery.for_source(source) and ^MediaQuery.pending()))
|
||||||
|> order_by(desc: fragment("rank"), desc: :upload_date)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
defp generate_base_query(source, "downloaded") do
|
defp generate_base_query(source, "downloaded") do
|
||||||
MediaQuery.new()
|
MediaQuery.new()
|
||||||
|> MediaQuery.require_assoc(:media_items_search_index)
|
|> select(^select_fields())
|
||||||
|> where(^dynamic(^MediaQuery.for_source(source) and ^MediaQuery.downloaded()))
|
|> where(^dynamic(^MediaQuery.for_source(source) and ^MediaQuery.downloaded()))
|
||||||
|> order_by(desc: fragment("rank"), desc: :upload_date)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
defp generate_base_query(source, "other") do
|
defp generate_base_query(source, "other") do
|
||||||
MediaQuery.new()
|
MediaQuery.new()
|
||||||
|
|> select(^select_fields())
|
||||||
|> MediaQuery.require_assoc(:media_profile)
|
|> MediaQuery.require_assoc(:media_profile)
|
||||||
|> MediaQuery.require_assoc(:media_items_search_index)
|
|
||||||
|> where(
|
|> where(
|
||||||
^dynamic(
|
^dynamic(
|
||||||
^MediaQuery.for_source(source) and
|
^MediaQuery.for_source(source) and
|
||||||
(not (^MediaQuery.downloaded()) and not (^MediaQuery.pending()))
|
(not (^MediaQuery.downloaded()) and not (^MediaQuery.pending()))
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|> order_by(desc: fragment("rank"), desc: :upload_date)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
defp filter_base_query(base_query, search_term) do
|
defp filtered_base_query(base_query, search_term) do
|
||||||
base_query
|
base_query
|
||||||
|
|> MediaQuery.require_assoc(:media_items_search_index)
|
||||||
|> where(^MediaQuery.matches_search_term(search_term))
|
|> where(^MediaQuery.matches_search_term(search_term))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Selecting only what we need GREATLY speeds up queries on large tables
|
||||||
|
defp select_fields do
|
||||||
|
[:id, :title, :uploaded_at, :prevent_download]
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -95,7 +95,8 @@
|
|||||||
type="number"
|
type="number"
|
||||||
label="Retention Period (days)"
|
label="Retention Period (days)"
|
||||||
min="0"
|
min="0"
|
||||||
help="Days between when media is *downloaded* and when it's deleted. Leave blank to keep media indefinitely"
|
help="Days between when media is <em>downloaded</em> and when it's deleted. Leave blank to keep media indefinitely"
|
||||||
|
html_help={true}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<section x-show="advancedMode">
|
<section x-show="advancedMode">
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ defmodule PinchflatWeb.Router do
|
|||||||
plug :fetch_session
|
plug :fetch_session
|
||||||
plug :fetch_live_flash
|
plug :fetch_live_flash
|
||||||
plug :put_root_layout, html: {PinchflatWeb.Layouts, :root}
|
plug :put_root_layout, html: {PinchflatWeb.Layouts, :root}
|
||||||
plug :protect_from_forgery, with: :clear_session
|
plug :protect_from_forgery
|
||||||
plug :put_secure_browser_headers
|
plug :put_secure_browser_headers
|
||||||
plug :allow_iframe_embed
|
plug :allow_iframe_embed
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ defmodule Pinchflat.MixProject do
|
|||||||
def project do
|
def project do
|
||||||
[
|
[
|
||||||
app: :pinchflat,
|
app: :pinchflat,
|
||||||
version: "2024.5.24",
|
version: "2024.5.30",
|
||||||
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,
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 449 KiB After Width: | Height: | Size: 471 KiB |
@@ -0,0 +1,21 @@
|
|||||||
|
defmodule Pinchflat.Repo.Migrations.RenameUploadDateToUploadedAt do
|
||||||
|
use Ecto.Migration
|
||||||
|
|
||||||
|
def up do
|
||||||
|
rename table(:media_items), :upload_date, to: :uploaded_at
|
||||||
|
|
||||||
|
execute """
|
||||||
|
UPDATE media_items
|
||||||
|
SET uploaded_at = uploaded_at || 'T00:00:00'
|
||||||
|
"""
|
||||||
|
end
|
||||||
|
|
||||||
|
def down do
|
||||||
|
rename table(:media_items), :uploaded_at, to: :upload_date
|
||||||
|
|
||||||
|
execute """
|
||||||
|
UPDATE media_items
|
||||||
|
SET upload_date = DATE(upload_date)
|
||||||
|
"""
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
defmodule Pinchflat.Repo.Migrations.ModifyUploadDateIndex do
|
||||||
|
use Ecto.Migration
|
||||||
|
|
||||||
|
def change do
|
||||||
|
drop index("media_items", [:upload_date])
|
||||||
|
create index("media_items", [:uploaded_at])
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
defmodule Pinchflat.Repo.Migrations.AddIndexesForLargeCollections do
|
||||||
|
use Ecto.Migration
|
||||||
|
|
||||||
|
def change do
|
||||||
|
create index(
|
||||||
|
"media_items",
|
||||||
|
[
|
||||||
|
:source_id,
|
||||||
|
:media_filepath,
|
||||||
|
:uploaded_at,
|
||||||
|
:prevent_download,
|
||||||
|
:livestream,
|
||||||
|
:short_form_content,
|
||||||
|
:title
|
||||||
|
],
|
||||||
|
name: "media_items_pending_and_downloaded_index"
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -6,13 +6,9 @@ defmodule Pinchflat.Downloading.DownloadingHelpersTest do
|
|||||||
import Pinchflat.ProfilesFixtures
|
import Pinchflat.ProfilesFixtures
|
||||||
|
|
||||||
alias Pinchflat.Tasks
|
alias Pinchflat.Tasks
|
||||||
alias Pinchflat.Media.MediaItem
|
|
||||||
alias Pinchflat.Utils.FilesystemUtils
|
|
||||||
alias Pinchflat.Downloading.DownloadingHelpers
|
alias Pinchflat.Downloading.DownloadingHelpers
|
||||||
alias Pinchflat.Downloading.MediaDownloadWorker
|
alias Pinchflat.Downloading.MediaDownloadWorker
|
||||||
|
|
||||||
alias Pinchflat.YtDlp.Media, as: YtDlpMedia
|
|
||||||
|
|
||||||
describe "enqueue_pending_download_tasks/1" do
|
describe "enqueue_pending_download_tasks/1" do
|
||||||
test "it enqueues a job for each pending media item" do
|
test "it enqueues a job for each pending media item" do
|
||||||
source = source_fixture()
|
source = source_fixture()
|
||||||
@@ -23,16 +19,6 @@ defmodule Pinchflat.Downloading.DownloadingHelpersTest do
|
|||||||
assert_enqueued(worker: MediaDownloadWorker, args: %{"id" => media_item.id})
|
assert_enqueued(worker: MediaDownloadWorker, args: %{"id" => media_item.id})
|
||||||
end
|
end
|
||||||
|
|
||||||
test "it can optionally delay when those jobs are enqueued" do
|
|
||||||
source = source_fixture()
|
|
||||||
_media_item = media_item_fixture(source_id: source.id, media_filepath: nil)
|
|
||||||
|
|
||||||
assert :ok = DownloadingHelpers.enqueue_pending_download_tasks(source, kickoff_delay: 60)
|
|
||||||
[job] = all_enqueued(worker: MediaDownloadWorker)
|
|
||||||
|
|
||||||
assert_in_delta DateTime.diff(job.scheduled_at, now()), 60, 1
|
|
||||||
end
|
|
||||||
|
|
||||||
test "it does not enqueue a job for media items with a filepath" do
|
test "it does not enqueue a job for media items with a filepath" do
|
||||||
source = source_fixture()
|
source = source_fixture()
|
||||||
_media_item = media_item_fixture(source_id: source.id, media_filepath: "some/filepath.mp4")
|
_media_item = media_item_fixture(source_id: source.id, media_filepath: "some/filepath.mp4")
|
||||||
@@ -98,13 +84,6 @@ defmodule Pinchflat.Downloading.DownloadingHelpersTest do
|
|||||||
assert_enqueued(worker: MediaDownloadWorker, args: %{"id" => media_item.id})
|
assert_enqueued(worker: MediaDownloadWorker, args: %{"id" => media_item.id})
|
||||||
end
|
end
|
||||||
|
|
||||||
test "it can optionally delay when those jobs are enqueued", %{media_item: media_item} do
|
|
||||||
assert {:ok, _} = DownloadingHelpers.kickoff_download_if_pending(media_item, kickoff_delay: 60)
|
|
||||||
[job] = all_enqueued(worker: MediaDownloadWorker)
|
|
||||||
|
|
||||||
assert_in_delta DateTime.diff(job.scheduled_at, now()), 60, 1
|
|
||||||
end
|
|
||||||
|
|
||||||
test "creates and returns a download task record", %{media_item: media_item} do
|
test "creates and returns a download task record", %{media_item: media_item} do
|
||||||
assert {:ok, task} = DownloadingHelpers.kickoff_download_if_pending(media_item)
|
assert {:ok, task} = DownloadingHelpers.kickoff_download_if_pending(media_item)
|
||||||
|
|
||||||
@@ -159,67 +138,4 @@ defmodule Pinchflat.Downloading.DownloadingHelpersTest do
|
|||||||
refute_enqueued(worker: MediaDownloadWorker)
|
refute_enqueued(worker: MediaDownloadWorker)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "create_media_item_and_run_script/2" do
|
|
||||||
setup do
|
|
||||||
FilesystemUtils.write_p!(filepath(), "")
|
|
||||||
File.chmod(filepath(), 0o755)
|
|
||||||
|
|
||||||
on_exit(fn -> File.rm(filepath()) end)
|
|
||||||
|
|
||||||
source = source_fixture()
|
|
||||||
|
|
||||||
media_attrs =
|
|
||||||
media_attributes_return_fixture()
|
|
||||||
|> Phoenix.json_library().decode!()
|
|
||||||
|> YtDlpMedia.response_to_struct()
|
|
||||||
|
|
||||||
{:ok, source: source, media_attrs: media_attrs}
|
|
||||||
end
|
|
||||||
|
|
||||||
test "creates a media item for a given source and attributes", %{source: source, media_attrs: media_attrs} do
|
|
||||||
assert {:ok, %MediaItem{} = media_item} = DownloadingHelpers.create_media_item_and_run_script(source, media_attrs)
|
|
||||||
|
|
||||||
assert media_item.source_id == source.id
|
|
||||||
assert media_item.title == media_attrs.title
|
|
||||||
assert media_item.media_id == media_attrs.media_id
|
|
||||||
assert media_item.original_url == media_attrs.original_url
|
|
||||||
assert media_item.description == media_attrs.description
|
|
||||||
end
|
|
||||||
|
|
||||||
test "returns an error if the media item cannot be created", %{source: source, media_attrs: media_attrs} do
|
|
||||||
media_attrs = %YtDlpMedia{media_attrs | media_id: nil}
|
|
||||||
|
|
||||||
assert {:error, %Ecto.Changeset{}} = DownloadingHelpers.create_media_item_and_run_script(source, media_attrs)
|
|
||||||
end
|
|
||||||
|
|
||||||
test "runs a script if the media item is created", %{source: source, media_attrs: media_attrs} do
|
|
||||||
# We *love* indirectly testing side effects
|
|
||||||
tmp_dir = Application.get_env(:pinchflat, :tmpfile_directory)
|
|
||||||
filename = "#{tmp_dir}/test_file-#{Enum.random(1..1000)}"
|
|
||||||
File.write(filepath(), "#!/bin/bash\ntouch #{filename}\n")
|
|
||||||
|
|
||||||
refute File.exists?(filename)
|
|
||||||
assert {:ok, %MediaItem{}} = DownloadingHelpers.create_media_item_and_run_script(source, media_attrs)
|
|
||||||
assert File.exists?(filename)
|
|
||||||
end
|
|
||||||
|
|
||||||
test "does not run a script if the media item already exists", %{source: source, media_attrs: media_attrs} do
|
|
||||||
{:ok, %MediaItem{}} = DownloadingHelpers.create_media_item_and_run_script(source, media_attrs)
|
|
||||||
|
|
||||||
tmp_dir = Application.get_env(:pinchflat, :tmpfile_directory)
|
|
||||||
filename = "#{tmp_dir}/test_file-#{Enum.random(1..1000)}"
|
|
||||||
File.write(filepath(), "#!/bin/bash\ntouch #{filename}\n")
|
|
||||||
|
|
||||||
refute File.exists?(filename)
|
|
||||||
assert {:ok, %MediaItem{}} = DownloadingHelpers.create_media_item_and_run_script(source, media_attrs)
|
|
||||||
refute File.exists?(filename)
|
|
||||||
end
|
|
||||||
|
|
||||||
defp filepath do
|
|
||||||
base_dir = Application.get_env(:pinchflat, :extras_directory)
|
|
||||||
|
|
||||||
Path.join([base_dir, "user-scripts", "lifecycle"])
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ defmodule Pinchflat.Downloading.MediaQualityUpgradeWorkerTest do
|
|||||||
media_item =
|
media_item =
|
||||||
media_item_fixture(%{
|
media_item_fixture(%{
|
||||||
source_id: source.id,
|
source_id: source.id,
|
||||||
upload_date: now_minus(6, :days),
|
uploaded_at: now_minus(6, :days),
|
||||||
media_downloaded_at: now_minus(5, :days)
|
media_downloaded_at: now_minus(5, :days)
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -32,7 +32,7 @@ defmodule Pinchflat.Downloading.MediaQualityUpgradeWorkerTest do
|
|||||||
_media_item =
|
_media_item =
|
||||||
media_item_fixture(%{
|
media_item_fixture(%{
|
||||||
source_id: source.id,
|
source_id: source.id,
|
||||||
upload_date: now_minus(6, :days),
|
uploaded_at: now_minus(6, :days),
|
||||||
media_downloaded_at: now_minus(1, :day)
|
media_downloaded_at: now_minus(1, :day)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -28,16 +28,6 @@ defmodule Pinchflat.FastIndexing.FastIndexingHelpersTest do
|
|||||||
assert worker.args["id"] == media_item.id
|
assert worker.args["id"] == media_item.id
|
||||||
end
|
end
|
||||||
|
|
||||||
test "enqueues the worker with a small delay", %{source: source} do
|
|
||||||
expect(HTTPClientMock, :get, fn _url -> {:ok, "<yt:videoId>test_1</yt:videoId>"} end)
|
|
||||||
|
|
||||||
FastIndexingHelpers.kickoff_download_tasks_from_youtube_rss_feed(source)
|
|
||||||
|
|
||||||
[job | _] = all_enqueued(worker: MediaDownloadWorker)
|
|
||||||
|
|
||||||
assert_in_delta DateTime.diff(job.scheduled_at, now()), 5, 1
|
|
||||||
end
|
|
||||||
|
|
||||||
test "does not enqueue a new worker for the source's media IDs we already know about", %{source: source} do
|
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)
|
expect(HTTPClientMock, :get, fn _url -> {:ok, "<yt:videoId>test_1</yt:videoId>"} end)
|
||||||
media_item_fixture(source_id: source.id, media_id: "test_1")
|
media_item_fixture(source_id: source.id, media_id: "test_1")
|
||||||
|
|||||||
+147
-177
@@ -34,150 +34,6 @@ defmodule Pinchflat.MediaTest do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "schema when testing upload_date_index and source is a channel" do
|
|
||||||
test "upload_date_index is set to 99 if it's the only video uploaded that day" do
|
|
||||||
upload_date = Date.utc_today()
|
|
||||||
source = source_fixture(%{collection_type: :channel})
|
|
||||||
media_item = media_item_fixture(%{source_id: source.id, upload_date: upload_date})
|
|
||||||
|
|
||||||
assert media_item.upload_date_index == 99
|
|
||||||
end
|
|
||||||
|
|
||||||
test "upload_date_index is set to 98 if it's the second video uploaded that day" do
|
|
||||||
upload_date = Date.utc_today()
|
|
||||||
source = source_fixture(%{collection_type: :channel})
|
|
||||||
|
|
||||||
media_item_one = media_item_fixture(%{source_id: source.id, upload_date: upload_date})
|
|
||||||
media_item_two = media_item_fixture(%{source_id: source.id, upload_date: upload_date})
|
|
||||||
|
|
||||||
assert media_item_one.upload_date_index == 99
|
|
||||||
assert media_item_two.upload_date_index == 98
|
|
||||||
end
|
|
||||||
|
|
||||||
test "upload_date_index doesn't decrement if the video is uploaded on a different day" do
|
|
||||||
today = Date.utc_today()
|
|
||||||
one_day_ago = Date.add(today, -1)
|
|
||||||
source = source_fixture(%{collection_type: :channel})
|
|
||||||
|
|
||||||
media_item_new = media_item_fixture(%{source_id: source.id, upload_date: today})
|
|
||||||
media_item_old = media_item_fixture(%{source_id: source.id, upload_date: one_day_ago})
|
|
||||||
|
|
||||||
assert media_item_new.upload_date_index == 99
|
|
||||||
assert media_item_old.upload_date_index == 99
|
|
||||||
end
|
|
||||||
|
|
||||||
test "recomputes upload_date_index if an upload_date is changed...somehow" do
|
|
||||||
today = Date.utc_today()
|
|
||||||
one_day_ago = Date.add(today, -1)
|
|
||||||
source = source_fixture(%{collection_type: :channel})
|
|
||||||
|
|
||||||
media_item_new = media_item_fixture(%{source_id: source.id, upload_date: today})
|
|
||||||
media_item_old = media_item_fixture(%{source_id: source.id, upload_date: one_day_ago})
|
|
||||||
|
|
||||||
{:ok, updated_media_item} = Media.update_media_item(media_item_old, %{upload_date: today})
|
|
||||||
|
|
||||||
assert media_item_new.upload_date_index == 99
|
|
||||||
assert updated_media_item.upload_date_index == 98
|
|
||||||
end
|
|
||||||
|
|
||||||
test "upload_date_index doesn't decrement if the video is for a different source" do
|
|
||||||
today = Date.utc_today()
|
|
||||||
|
|
||||||
source_one = source_fixture(%{collection_type: :channel})
|
|
||||||
source_two = source_fixture(%{collection_type: :channel})
|
|
||||||
|
|
||||||
media_item_one = media_item_fixture(%{source_id: source_one.id, upload_date: today})
|
|
||||||
media_item_two = media_item_fixture(%{source_id: source_two.id, upload_date: today})
|
|
||||||
|
|
||||||
assert media_item_one.upload_date_index == 99
|
|
||||||
assert media_item_two.upload_date_index == 99
|
|
||||||
end
|
|
||||||
|
|
||||||
test "upload_date_index doesn't decrement if the a video's upload_date is updated but doesn't change" do
|
|
||||||
today = Date.utc_today()
|
|
||||||
source = source_fixture(%{collection_type: :channel})
|
|
||||||
|
|
||||||
media_item_one = media_item_fixture(%{source_id: source.id, upload_date: today})
|
|
||||||
_media_item_two = media_item_fixture(%{source_id: source.id, upload_date: today})
|
|
||||||
|
|
||||||
{:ok, updated_media_item} = Media.update_media_item(media_item_one, %{upload_date: today, title: "New title"})
|
|
||||||
|
|
||||||
assert updated_media_item.upload_date_index == 99
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe "schema when testing upload_date_index and source is a playlist" do
|
|
||||||
test "upload_date_index is set to 0 if it's the only video uploaded that day" do
|
|
||||||
upload_date = Date.utc_today()
|
|
||||||
source = source_fixture(%{collection_type: :playlist})
|
|
||||||
media_item = media_item_fixture(%{source_id: source.id, upload_date: upload_date})
|
|
||||||
|
|
||||||
assert media_item.upload_date_index == 0
|
|
||||||
end
|
|
||||||
|
|
||||||
test "upload_date_index is set to 1 if it's the second video uploaded that day" do
|
|
||||||
upload_date = Date.utc_today()
|
|
||||||
source = source_fixture(%{collection_type: :playlist})
|
|
||||||
|
|
||||||
media_item_one = media_item_fixture(%{source_id: source.id, upload_date: upload_date})
|
|
||||||
media_item_two = media_item_fixture(%{source_id: source.id, upload_date: upload_date})
|
|
||||||
|
|
||||||
assert media_item_one.upload_date_index == 0
|
|
||||||
assert media_item_two.upload_date_index == 1
|
|
||||||
end
|
|
||||||
|
|
||||||
test "upload_date_index doesn't increment if the video is uploaded on a different day" do
|
|
||||||
today = Date.utc_today()
|
|
||||||
one_day_ago = Date.add(today, -1)
|
|
||||||
source = source_fixture(%{collection_type: :playlist})
|
|
||||||
|
|
||||||
media_item_new = media_item_fixture(%{source_id: source.id, upload_date: today})
|
|
||||||
media_item_old = media_item_fixture(%{source_id: source.id, upload_date: one_day_ago})
|
|
||||||
|
|
||||||
assert media_item_new.upload_date_index == 0
|
|
||||||
assert media_item_old.upload_date_index == 0
|
|
||||||
end
|
|
||||||
|
|
||||||
test "recomputes upload_date_index if an upload_date is changed...somehow" do
|
|
||||||
today = Date.utc_today()
|
|
||||||
one_day_ago = Date.add(today, -1)
|
|
||||||
source = source_fixture(%{collection_type: :playlist})
|
|
||||||
|
|
||||||
media_item_new = media_item_fixture(%{source_id: source.id, upload_date: today})
|
|
||||||
media_item_old = media_item_fixture(%{source_id: source.id, upload_date: one_day_ago})
|
|
||||||
|
|
||||||
{:ok, updated_media_item} = Media.update_media_item(media_item_old, %{upload_date: today})
|
|
||||||
|
|
||||||
assert media_item_new.upload_date_index == 0
|
|
||||||
assert updated_media_item.upload_date_index == 1
|
|
||||||
end
|
|
||||||
|
|
||||||
test "upload_date_index doesn't increment if the video is for a different source" do
|
|
||||||
today = Date.utc_today()
|
|
||||||
|
|
||||||
source_one = source_fixture(%{collection_type: :playlist})
|
|
||||||
source_two = source_fixture(%{collection_type: :playlist})
|
|
||||||
|
|
||||||
media_item_one = media_item_fixture(%{source_id: source_one.id, upload_date: today})
|
|
||||||
media_item_two = media_item_fixture(%{source_id: source_two.id, upload_date: today})
|
|
||||||
|
|
||||||
assert media_item_one.upload_date_index == 0
|
|
||||||
assert media_item_two.upload_date_index == 0
|
|
||||||
end
|
|
||||||
|
|
||||||
test "upload_date_index doesn't increment if the a video's upload_date is updated but doesn't change" do
|
|
||||||
today = Date.utc_today()
|
|
||||||
source = source_fixture(%{collection_type: :playlist})
|
|
||||||
|
|
||||||
media_item_one = media_item_fixture(%{source_id: source.id, upload_date: today})
|
|
||||||
_media_item_two = media_item_fixture(%{source_id: source.id, upload_date: today})
|
|
||||||
|
|
||||||
{:ok, updated_media_item} = Media.update_media_item(media_item_one, %{upload_date: today, title: "New title"})
|
|
||||||
|
|
||||||
assert updated_media_item.upload_date_index == 0
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe "list_media_items/0" do
|
describe "list_media_items/0" do
|
||||||
test "it returns all media_items" do
|
test "it returns all media_items" do
|
||||||
media_item = media_item_fixture()
|
media_item = media_item_fixture()
|
||||||
@@ -289,7 +145,7 @@ defmodule Pinchflat.MediaTest do
|
|||||||
media_item =
|
media_item =
|
||||||
media_item_fixture(%{
|
media_item_fixture(%{
|
||||||
source_id: source.id,
|
source_id: source.id,
|
||||||
upload_date: now_minus(6, :days),
|
uploaded_at: now_minus(6, :days),
|
||||||
media_downloaded_at: now_minus(5, :days)
|
media_downloaded_at: now_minus(5, :days)
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -300,7 +156,7 @@ defmodule Pinchflat.MediaTest do
|
|||||||
media_item =
|
media_item =
|
||||||
media_item_fixture(%{
|
media_item_fixture(%{
|
||||||
source_id: source.id,
|
source_id: source.id,
|
||||||
upload_date: now_minus(20, :days),
|
uploaded_at: now_minus(20, :days),
|
||||||
media_downloaded_at: now_minus(19, :days)
|
media_downloaded_at: now_minus(19, :days)
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -311,7 +167,7 @@ defmodule Pinchflat.MediaTest do
|
|||||||
_media_item =
|
_media_item =
|
||||||
media_item_fixture(%{
|
media_item_fixture(%{
|
||||||
source_id: source.id,
|
source_id: source.id,
|
||||||
upload_date: now_minus(5, :days),
|
uploaded_at: now_minus(5, :days),
|
||||||
media_downloaded_at: nil
|
media_downloaded_at: nil
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -322,7 +178,7 @@ defmodule Pinchflat.MediaTest do
|
|||||||
_media_item =
|
_media_item =
|
||||||
media_item_fixture(%{
|
media_item_fixture(%{
|
||||||
source_id: source.id,
|
source_id: source.id,
|
||||||
upload_date: now_minus(5, :days),
|
uploaded_at: now_minus(5, :days),
|
||||||
media_downloaded_at: now(),
|
media_downloaded_at: now(),
|
||||||
prevent_download: true
|
prevent_download: true
|
||||||
})
|
})
|
||||||
@@ -334,7 +190,7 @@ defmodule Pinchflat.MediaTest do
|
|||||||
_media_item =
|
_media_item =
|
||||||
media_item_fixture(%{
|
media_item_fixture(%{
|
||||||
source_id: source.id,
|
source_id: source.id,
|
||||||
upload_date: now_minus(5, :days),
|
uploaded_at: now_minus(5, :days),
|
||||||
media_downloaded_at: now(),
|
media_downloaded_at: now(),
|
||||||
culled_at: now()
|
culled_at: now()
|
||||||
})
|
})
|
||||||
@@ -346,7 +202,7 @@ defmodule Pinchflat.MediaTest do
|
|||||||
_media_item =
|
_media_item =
|
||||||
media_item_fixture(%{
|
media_item_fixture(%{
|
||||||
source_id: source.id,
|
source_id: source.id,
|
||||||
upload_date: now_minus(3, :days),
|
uploaded_at: now_minus(3, :days),
|
||||||
media_downloaded_at: now_minus(3, :days)
|
media_downloaded_at: now_minus(3, :days)
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -357,7 +213,7 @@ defmodule Pinchflat.MediaTest do
|
|||||||
_media_item =
|
_media_item =
|
||||||
media_item_fixture(%{
|
media_item_fixture(%{
|
||||||
source_id: source.id,
|
source_id: source.id,
|
||||||
upload_date: now_minus(5, :days),
|
uploaded_at: now_minus(5, :days),
|
||||||
media_downloaded_at: now(),
|
media_downloaded_at: now(),
|
||||||
media_redownloaded_at: now()
|
media_redownloaded_at: now()
|
||||||
})
|
})
|
||||||
@@ -365,12 +221,12 @@ defmodule Pinchflat.MediaTest do
|
|||||||
assert Media.list_redownloadable_media_items() == []
|
assert Media.list_redownloadable_media_items() == []
|
||||||
end
|
end
|
||||||
|
|
||||||
test "does not return media items that were first downloaded well after the upload_date", %{source: source} do
|
test "does not return media items that were first downloaded well after the uploaded_at", %{source: source} do
|
||||||
_media_item =
|
_media_item =
|
||||||
media_item_fixture(%{
|
media_item_fixture(%{
|
||||||
source_id: source.id,
|
source_id: source.id,
|
||||||
media_downloaded_at: now(),
|
media_downloaded_at: now(),
|
||||||
upload_date: now_minus(20, :days)
|
uploaded_at: now_minus(20, :days)
|
||||||
})
|
})
|
||||||
|
|
||||||
assert Media.list_redownloadable_media_items() == []
|
assert Media.list_redownloadable_media_items() == []
|
||||||
@@ -381,7 +237,7 @@ defmodule Pinchflat.MediaTest do
|
|||||||
media_item_fixture(%{
|
media_item_fixture(%{
|
||||||
source_id: source.id,
|
source_id: source.id,
|
||||||
media_downloaded_at: now(),
|
media_downloaded_at: now(),
|
||||||
upload_date: now_minus(2, :days)
|
uploaded_at: now_minus(2, :days)
|
||||||
})
|
})
|
||||||
|
|
||||||
assert Media.list_redownloadable_media_items() == []
|
assert Media.list_redownloadable_media_items() == []
|
||||||
@@ -394,7 +250,7 @@ defmodule Pinchflat.MediaTest do
|
|||||||
_media_item =
|
_media_item =
|
||||||
media_item_fixture(%{
|
media_item_fixture(%{
|
||||||
source_id: source.id,
|
source_id: source.id,
|
||||||
upload_date: now_minus(6, :days),
|
uploaded_at: now_minus(6, :days),
|
||||||
media_downloaded_at: now_minus(5, :days)
|
media_downloaded_at: now_minus(5, :days)
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -552,9 +408,9 @@ defmodule Pinchflat.MediaTest do
|
|||||||
source = source_fixture(%{download_cutoff_date: now_minus(1, :day)})
|
source = source_fixture(%{download_cutoff_date: now_minus(1, :day)})
|
||||||
|
|
||||||
_old_media_item =
|
_old_media_item =
|
||||||
media_item_fixture(%{source_id: source.id, media_filepath: nil, upload_date: now_minus(2, :days)})
|
media_item_fixture(%{source_id: source.id, media_filepath: nil, uploaded_at: now_minus(2, :days)})
|
||||||
|
|
||||||
new_media_item = media_item_fixture(%{source_id: source.id, media_filepath: nil, upload_date: now()})
|
new_media_item = media_item_fixture(%{source_id: source.id, media_filepath: nil, uploaded_at: now()})
|
||||||
|
|
||||||
assert Media.list_pending_media_items_for(source) == [new_media_item]
|
assert Media.list_pending_media_items_for(source) == [new_media_item]
|
||||||
end
|
end
|
||||||
@@ -563,9 +419,9 @@ defmodule Pinchflat.MediaTest do
|
|||||||
source = source_fixture(%{download_cutoff_date: nil})
|
source = source_fixture(%{download_cutoff_date: nil})
|
||||||
|
|
||||||
old_media_item =
|
old_media_item =
|
||||||
media_item_fixture(%{source_id: source.id, media_filepath: nil, upload_date: now_minus(2, :days)})
|
media_item_fixture(%{source_id: source.id, media_filepath: nil, uploaded_at: now_minus(2, :days)})
|
||||||
|
|
||||||
new_media_item = media_item_fixture(%{source_id: source.id, media_filepath: nil, upload_date: now()})
|
new_media_item = media_item_fixture(%{source_id: source.id, media_filepath: nil, uploaded_at: now()})
|
||||||
|
|
||||||
assert Media.list_pending_media_items_for(source) == [old_media_item, new_media_item]
|
assert Media.list_pending_media_items_for(source) == [old_media_item, new_media_item]
|
||||||
end
|
end
|
||||||
@@ -630,21 +486,21 @@ defmodule Pinchflat.MediaTest do
|
|||||||
|
|
||||||
test "returns true if there is a cutoff date before the media's upload date" do
|
test "returns true if there is a cutoff date before the media's upload date" do
|
||||||
source = source_fixture(%{download_cutoff_date: now_minus(2, :days)})
|
source = source_fixture(%{download_cutoff_date: now_minus(2, :days)})
|
||||||
media_item = media_item_fixture(%{source_id: source.id, media_filepath: nil, upload_date: now_minus(1, :day)})
|
media_item = media_item_fixture(%{source_id: source.id, media_filepath: nil, uploaded_at: now_minus(1, :day)})
|
||||||
|
|
||||||
assert Media.pending_download?(media_item)
|
assert Media.pending_download?(media_item)
|
||||||
end
|
end
|
||||||
|
|
||||||
test "returns false if there is a cutoff date after the media's upload date" do
|
test "returns false if there is a cutoff date after the media's upload date" do
|
||||||
source = source_fixture(%{download_cutoff_date: now_minus(1, :day)})
|
source = source_fixture(%{download_cutoff_date: now_minus(1, :day)})
|
||||||
media_item = media_item_fixture(%{source_id: source.id, media_filepath: nil, upload_date: now_minus(2, :days)})
|
media_item = media_item_fixture(%{source_id: source.id, media_filepath: nil, uploaded_at: now_minus(2, :days)})
|
||||||
|
|
||||||
refute Media.pending_download?(media_item)
|
refute Media.pending_download?(media_item)
|
||||||
end
|
end
|
||||||
|
|
||||||
test "returns true if there is no cutoff date" do
|
test "returns true if there is no cutoff date" do
|
||||||
source = source_fixture(%{download_cutoff_date: nil})
|
source = source_fixture(%{download_cutoff_date: nil})
|
||||||
media_item = media_item_fixture(%{source_id: source.id, media_filepath: nil, upload_date: now_minus(1, :day)})
|
media_item = media_item_fixture(%{source_id: source.id, media_filepath: nil, uploaded_at: now_minus(1, :day)})
|
||||||
|
|
||||||
assert Media.pending_download?(media_item)
|
assert Media.pending_download?(media_item)
|
||||||
end
|
end
|
||||||
@@ -755,7 +611,7 @@ defmodule Pinchflat.MediaTest do
|
|||||||
media_filepath: "/video/#{Faker.File.file_name(:video)}",
|
media_filepath: "/video/#{Faker.File.file_name(:video)}",
|
||||||
source_id: source_fixture().id,
|
source_id: source_fixture().id,
|
||||||
original_url: "https://www.youtube.com/channel/#{Faker.String.base64(12)}",
|
original_url: "https://www.youtube.com/channel/#{Faker.String.base64(12)}",
|
||||||
upload_date: Date.utc_today()
|
uploaded_at: now()
|
||||||
}
|
}
|
||||||
|
|
||||||
assert {:ok, %MediaItem{} = media_item} = Media.create_media_item(valid_attrs)
|
assert {:ok, %MediaItem{} = media_item} = Media.create_media_item(valid_attrs)
|
||||||
@@ -772,7 +628,7 @@ defmodule Pinchflat.MediaTest do
|
|||||||
media_filepath: "/video/#{Faker.File.file_name(:video)}",
|
media_filepath: "/video/#{Faker.File.file_name(:video)}",
|
||||||
source_id: source_fixture().id,
|
source_id: source_fixture().id,
|
||||||
original_url: "https://www.youtube.com/channel/#{Faker.String.base64(12)}",
|
original_url: "https://www.youtube.com/channel/#{Faker.String.base64(12)}",
|
||||||
upload_date: Date.utc_today()
|
uploaded_at: now()
|
||||||
}
|
}
|
||||||
|
|
||||||
assert {:ok, %MediaItem{} = media_item} = Media.create_media_item(valid_attrs)
|
assert {:ok, %MediaItem{} = media_item} = Media.create_media_item(valid_attrs)
|
||||||
@@ -787,7 +643,7 @@ defmodule Pinchflat.MediaTest do
|
|||||||
media_filepath: "/video/#{Faker.File.file_name(:video)}",
|
media_filepath: "/video/#{Faker.File.file_name(:video)}",
|
||||||
source_id: source_fixture().id,
|
source_id: source_fixture().id,
|
||||||
original_url: "https://www.youtube.com/channel/#{Faker.String.base64(12)}",
|
original_url: "https://www.youtube.com/channel/#{Faker.String.base64(12)}",
|
||||||
upload_date: Date.utc_today(),
|
uploaded_at: now(),
|
||||||
uuid: "some-uuid"
|
uuid: "some-uuid"
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -835,18 +691,6 @@ defmodule Pinchflat.MediaTest do
|
|||||||
assert media_item_1.id == media_item_2.id
|
assert media_item_1.id == media_item_2.id
|
||||||
assert media_item_2.title == different_attrs.title
|
assert media_item_2.title == different_attrs.title
|
||||||
end
|
end
|
||||||
|
|
||||||
test "returns an error if the media item cannot be created" do
|
|
||||||
source = source_fixture()
|
|
||||||
|
|
||||||
media_attrs =
|
|
||||||
media_attributes_return_fixture()
|
|
||||||
|> Phoenix.json_library().decode!()
|
|
||||||
|> Map.put("id", nil)
|
|
||||||
|> YtDlpMedia.response_to_struct()
|
|
||||||
|
|
||||||
assert {:error, %Ecto.Changeset{}} = Media.create_media_item_from_backend_attrs(source, media_attrs)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "update_media_item/2" do
|
describe "update_media_item/2" do
|
||||||
@@ -1064,4 +908,130 @@ defmodule Pinchflat.MediaTest do
|
|||||||
assert %Ecto.Changeset{} = Media.change_media_item(media_item)
|
assert %Ecto.Changeset{} = Media.change_media_item(media_item)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe "change_media_item/1 when testing upload_date_index and source is a channel" do
|
||||||
|
test "upload_date_index is set to 99 if it's the only video uploaded that day" do
|
||||||
|
source = source_fixture(%{collection_type: :channel})
|
||||||
|
media_item = media_item_fixture(%{source_id: source.id, uploaded_at: now()})
|
||||||
|
|
||||||
|
assert media_item.upload_date_index == 99
|
||||||
|
end
|
||||||
|
|
||||||
|
test "upload_date_index is set to 98 if it's the second video uploaded that day" do
|
||||||
|
source = source_fixture(%{collection_type: :channel})
|
||||||
|
|
||||||
|
media_item_one = media_item_fixture(%{source_id: source.id, uploaded_at: now()})
|
||||||
|
media_item_two = media_item_fixture(%{source_id: source.id, uploaded_at: now()})
|
||||||
|
|
||||||
|
assert media_item_one.upload_date_index == 99
|
||||||
|
assert media_item_two.upload_date_index == 98
|
||||||
|
end
|
||||||
|
|
||||||
|
test "upload_date_index doesn't decrement if the video is uploaded on a different day" do
|
||||||
|
source = source_fixture(%{collection_type: :channel})
|
||||||
|
|
||||||
|
media_item_new = media_item_fixture(%{source_id: source.id, uploaded_at: now()})
|
||||||
|
media_item_old = media_item_fixture(%{source_id: source.id, uploaded_at: now_minus(1, :day)})
|
||||||
|
|
||||||
|
assert media_item_new.upload_date_index == 99
|
||||||
|
assert media_item_old.upload_date_index == 99
|
||||||
|
end
|
||||||
|
|
||||||
|
test "recomputes upload_date_index if an upload_date is changed...somehow" do
|
||||||
|
source = source_fixture(%{collection_type: :channel})
|
||||||
|
|
||||||
|
media_item_new = media_item_fixture(%{source_id: source.id, uploaded_at: now()})
|
||||||
|
media_item_old = media_item_fixture(%{source_id: source.id, uploaded_at: now_minus(1, :day)})
|
||||||
|
|
||||||
|
{:ok, updated_media_item} = Media.update_media_item(media_item_old, %{uploaded_at: now()})
|
||||||
|
|
||||||
|
assert media_item_new.upload_date_index == 99
|
||||||
|
assert updated_media_item.upload_date_index == 98
|
||||||
|
end
|
||||||
|
|
||||||
|
test "upload_date_index doesn't decrement if the video is for a different source" do
|
||||||
|
source_one = source_fixture(%{collection_type: :channel})
|
||||||
|
source_two = source_fixture(%{collection_type: :channel})
|
||||||
|
|
||||||
|
media_item_one = media_item_fixture(%{source_id: source_one.id, uploaded_at: now()})
|
||||||
|
media_item_two = media_item_fixture(%{source_id: source_two.id, uploaded_at: now()})
|
||||||
|
|
||||||
|
assert media_item_one.upload_date_index == 99
|
||||||
|
assert media_item_two.upload_date_index == 99
|
||||||
|
end
|
||||||
|
|
||||||
|
test "upload_date_index doesn't decrement if the a video's upload_date is updated but doesn't change" do
|
||||||
|
source = source_fixture(%{collection_type: :channel})
|
||||||
|
|
||||||
|
media_item_one = media_item_fixture(%{source_id: source.id, uploaded_at: now()})
|
||||||
|
_media_item_two = media_item_fixture(%{source_id: source.id, uploaded_at: now()})
|
||||||
|
|
||||||
|
{:ok, updated_media_item} = Media.update_media_item(media_item_one, %{uploaded_at: now(), title: "New title"})
|
||||||
|
|
||||||
|
assert updated_media_item.upload_date_index == 99
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "change_media_item/1 when testing upload_date_index and source is a playlist" do
|
||||||
|
test "upload_date_index is set to 0 if it's the only video uploaded that day" do
|
||||||
|
source = source_fixture(%{collection_type: :playlist})
|
||||||
|
media_item = media_item_fixture(%{source_id: source.id, uploaded_at: now()})
|
||||||
|
|
||||||
|
assert media_item.upload_date_index == 0
|
||||||
|
end
|
||||||
|
|
||||||
|
test "upload_date_index is set to 1 if it's the second video uploaded that day" do
|
||||||
|
source = source_fixture(%{collection_type: :playlist})
|
||||||
|
|
||||||
|
media_item_one = media_item_fixture(%{source_id: source.id, uploaded_at: now()})
|
||||||
|
media_item_two = media_item_fixture(%{source_id: source.id, uploaded_at: now()})
|
||||||
|
|
||||||
|
assert media_item_one.upload_date_index == 0
|
||||||
|
assert media_item_two.upload_date_index == 1
|
||||||
|
end
|
||||||
|
|
||||||
|
test "upload_date_index doesn't increment if the video is uploaded on a different day" do
|
||||||
|
source = source_fixture(%{collection_type: :playlist})
|
||||||
|
|
||||||
|
media_item_new = media_item_fixture(%{source_id: source.id, uploaded_at: now()})
|
||||||
|
media_item_old = media_item_fixture(%{source_id: source.id, uploaded_at: now_minus(1, :day)})
|
||||||
|
|
||||||
|
assert media_item_new.upload_date_index == 0
|
||||||
|
assert media_item_old.upload_date_index == 0
|
||||||
|
end
|
||||||
|
|
||||||
|
test "recomputes upload_date_index if an upload_date is changed...somehow" do
|
||||||
|
source = source_fixture(%{collection_type: :playlist})
|
||||||
|
|
||||||
|
media_item_new = media_item_fixture(%{source_id: source.id, uploaded_at: now()})
|
||||||
|
media_item_old = media_item_fixture(%{source_id: source.id, uploaded_at: now_minus(1, :day)})
|
||||||
|
|
||||||
|
{:ok, updated_media_item} = Media.update_media_item(media_item_old, %{uploaded_at: now()})
|
||||||
|
|
||||||
|
assert media_item_new.upload_date_index == 0
|
||||||
|
assert updated_media_item.upload_date_index == 1
|
||||||
|
end
|
||||||
|
|
||||||
|
test "upload_date_index doesn't increment if the video is for a different source" do
|
||||||
|
source_one = source_fixture(%{collection_type: :playlist})
|
||||||
|
source_two = source_fixture(%{collection_type: :playlist})
|
||||||
|
|
||||||
|
media_item_one = media_item_fixture(%{source_id: source_one.id, uploaded_at: now()})
|
||||||
|
media_item_two = media_item_fixture(%{source_id: source_two.id, uploaded_at: now()})
|
||||||
|
|
||||||
|
assert media_item_one.upload_date_index == 0
|
||||||
|
assert media_item_two.upload_date_index == 0
|
||||||
|
end
|
||||||
|
|
||||||
|
test "upload_date_index doesn't increment if the a video's upload_date is updated but doesn't change" do
|
||||||
|
source = source_fixture(%{collection_type: :playlist})
|
||||||
|
|
||||||
|
media_item_one = media_item_fixture(%{source_id: source.id, uploaded_at: now()})
|
||||||
|
_media_item_two = media_item_fixture(%{source_id: source.id, uploaded_at: now()})
|
||||||
|
|
||||||
|
{:ok, updated_media_item} = Media.update_media_item(media_item_one, %{uploaded_at: now(), title: "New title"})
|
||||||
|
|
||||||
|
assert updated_media_item.upload_date_index == 0
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -132,10 +132,10 @@ defmodule Pinchflat.Metadata.MetadataFileHelpersTest do
|
|||||||
end
|
end
|
||||||
|
|
||||||
describe "parse_upload_date/1" do
|
describe "parse_upload_date/1" do
|
||||||
test "returns a date from the given metadata upload date" do
|
test "returns a datetime from the given metadata upload date" do
|
||||||
upload_date = "20210101"
|
upload_date = "20210101"
|
||||||
|
|
||||||
assert Helpers.parse_upload_date(upload_date) == ~D[2021-01-01]
|
assert Helpers.parse_upload_date(upload_date) == ~U[2021-01-01 00:00:00Z]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -26,9 +26,9 @@ defmodule Pinchflat.Podcasts.PodcastHelpersTest do
|
|||||||
test "orders by upload date where newest is first" do
|
test "orders by upload date where newest is first" do
|
||||||
source = source_fixture()
|
source = source_fixture()
|
||||||
|
|
||||||
oldest = media_item_with_attachments(%{source_id: source.id, upload_date: now_minus(2, :day)})
|
oldest = media_item_with_attachments(%{source_id: source.id, uploaded_at: now_minus(2, :day)})
|
||||||
current = media_item_with_attachments(%{source_id: source.id, upload_date: now()})
|
current = media_item_with_attachments(%{source_id: source.id, uploaded_at: now()})
|
||||||
older = media_item_with_attachments(%{source_id: source.id, upload_date: now_minus(1, :days)})
|
older = media_item_with_attachments(%{source_id: source.id, uploaded_at: now_minus(1, :days)})
|
||||||
|
|
||||||
assert [^current, ^older, ^oldest] = PodcastHelpers.persisted_media_items_for(source)
|
assert [^current, ^older, ^oldest] = PodcastHelpers.persisted_media_items_for(source)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -117,8 +117,8 @@ defmodule Pinchflat.Podcasts.RssFeedBuilderTest do
|
|||||||
assert String.contains?(item_xml, ~s(<itunes:summary><![CDATA[#{media_item.description}]]></itunes:summary>))
|
assert String.contains?(item_xml, ~s(<itunes:summary><![CDATA[#{media_item.description}]]></itunes:summary>))
|
||||||
end
|
end
|
||||||
|
|
||||||
test "returns pubDate based off the media's upload_date", %{source: source} do
|
test "returns pubDate based off the media's uploaded_at", %{source: source} do
|
||||||
media_item_with_attachments(%{source_id: source.id, upload_date: ~D[2020-01-01]})
|
media_item_with_attachments(%{source_id: source.id, uploaded_at: ~U[2020-01-01 00:00:00Z]})
|
||||||
|
|
||||||
res = RssFeedBuilder.build(source)
|
res = RssFeedBuilder.build(source)
|
||||||
[_before, item_xml, _after] = String.split(res, ~r(</?item>))
|
[_before, item_xml, _after] = String.split(res, ~r(</?item>))
|
||||||
|
|||||||
@@ -158,16 +158,6 @@ defmodule Pinchflat.SlowIndexing.SlowIndexingHelpersTest do
|
|||||||
assert_enqueued(worker: MediaDownloadWorker, args: %{"id" => media_item.id})
|
assert_enqueued(worker: MediaDownloadWorker, args: %{"id" => media_item.id})
|
||||||
end
|
end
|
||||||
|
|
||||||
test "it enqueues the job with a small delay", %{source: source} do
|
|
||||||
media_item = media_item_fixture(source_id: source.id, media_filepath: nil)
|
|
||||||
|
|
||||||
SlowIndexingHelpers.index_and_enqueue_download_for_media_items(source)
|
|
||||||
|
|
||||||
[job] = all_enqueued(worker: MediaDownloadWorker, args: %{"id" => media_item.id})
|
|
||||||
|
|
||||||
assert_in_delta DateTime.diff(job.scheduled_at, now()), 5, 1
|
|
||||||
end
|
|
||||||
|
|
||||||
test "it does not attach tasks if the source is set to not download" do
|
test "it does not attach tasks if the source is set to not download" do
|
||||||
source = source_fixture(download_media: false)
|
source = source_fixture(download_media: false)
|
||||||
media_item = media_item_fixture(source_id: source.id, media_filepath: nil)
|
media_item = media_item_fixture(source_id: source.id, media_filepath: nil)
|
||||||
@@ -245,26 +235,6 @@ defmodule Pinchflat.SlowIndexing.SlowIndexingHelpersTest do
|
|||||||
assert_enqueued(worker: MediaDownloadWorker)
|
assert_enqueued(worker: MediaDownloadWorker)
|
||||||
end
|
end
|
||||||
|
|
||||||
test "sets a small delay on the download job", %{source: source} do
|
|
||||||
watcher_poll_interval = Application.get_env(:pinchflat, :file_watcher_poll_interval)
|
|
||||||
|
|
||||||
stub(YtDlpRunnerMock, :run, fn _url, _opts, _ot, addl_opts ->
|
|
||||||
filepath = Keyword.get(addl_opts, :output_filepath)
|
|
||||||
File.write(filepath, source_attributes_return_fixture())
|
|
||||||
|
|
||||||
# Need to add a delay to ensure the file watcher has time to read the file
|
|
||||||
:timer.sleep(watcher_poll_interval * 2)
|
|
||||||
# We know we're testing the file watcher since the syncronous call will only
|
|
||||||
# return an empty string (creating no records)
|
|
||||||
{:ok, ""}
|
|
||||||
end)
|
|
||||||
|
|
||||||
SlowIndexingHelpers.index_and_enqueue_download_for_media_items(source)
|
|
||||||
[job | _] = all_enqueued(worker: MediaDownloadWorker)
|
|
||||||
|
|
||||||
assert_in_delta DateTime.diff(job.scheduled_at, now()), 5, 1
|
|
||||||
end
|
|
||||||
|
|
||||||
test "does not enqueue downloads if the source is set to not download" do
|
test "does not enqueue downloads if the source is set to not download" do
|
||||||
watcher_poll_interval = Application.get_env(:pinchflat, :file_watcher_poll_interval)
|
watcher_poll_interval = Application.get_env(:pinchflat, :file_watcher_poll_interval)
|
||||||
source = source_fixture(download_media: false)
|
source = source_fixture(download_media: false)
|
||||||
|
|||||||
@@ -1,14 +0,0 @@
|
|||||||
defmodule Pinchflat.Utils.DatetimeUtilsTest do
|
|
||||||
use Pinchflat.DataCase
|
|
||||||
|
|
||||||
alias Pinchflat.Utils.DatetimeUtils
|
|
||||||
|
|
||||||
describe "date_to_datetime/1" do
|
|
||||||
test "converts a Date to a DateTime" do
|
|
||||||
date = ~D[2022-01-01]
|
|
||||||
datetime = DatetimeUtils.date_to_datetime(date)
|
|
||||||
|
|
||||||
assert datetime == ~U[2022-01-01 00:00:00Z]
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -79,7 +79,7 @@ defmodule Pinchflat.YtDlp.MediaTest do
|
|||||||
|
|
||||||
describe "indexing_output_template/0" do
|
describe "indexing_output_template/0" do
|
||||||
test "contains all the greatest hits" do
|
test "contains all the greatest hits" do
|
||||||
assert "%(.{id,title,was_live,webpage_url,description,aspect_ratio,duration,upload_date})j" ==
|
assert "%(.{id,title,was_live,webpage_url,description,aspect_ratio,duration,upload_date,timestamp})j" ==
|
||||||
Media.indexing_output_template()
|
Media.indexing_output_template()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -94,7 +94,8 @@ defmodule Pinchflat.YtDlp.MediaTest do
|
|||||||
"was_live" => false,
|
"was_live" => false,
|
||||||
"aspect_ratio" => 1.0,
|
"aspect_ratio" => 1.0,
|
||||||
"duration" => 60,
|
"duration" => 60,
|
||||||
"upload_date" => "20210101"
|
"upload_date" => "20210101",
|
||||||
|
"timestamp" => 1_600_000_000
|
||||||
}
|
}
|
||||||
|
|
||||||
assert %Media{
|
assert %Media{
|
||||||
@@ -104,7 +105,7 @@ defmodule Pinchflat.YtDlp.MediaTest do
|
|||||||
original_url: "https://www.youtube.com/watch?v=TiZPUDkDYbk",
|
original_url: "https://www.youtube.com/watch?v=TiZPUDkDYbk",
|
||||||
livestream: false,
|
livestream: false,
|
||||||
short_form_content: false,
|
short_form_content: false,
|
||||||
upload_date: Date.from_iso8601!("2021-01-01"),
|
uploaded_at: ~U[2020-09-13 12:26:40Z],
|
||||||
duration_seconds: 60
|
duration_seconds: 60
|
||||||
} == Media.response_to_struct(response)
|
} == Media.response_to_struct(response)
|
||||||
end
|
end
|
||||||
@@ -146,34 +147,11 @@ defmodule Pinchflat.YtDlp.MediaTest do
|
|||||||
response = %{
|
response = %{
|
||||||
"webpage_url" => nil,
|
"webpage_url" => nil,
|
||||||
"aspect_ratio" => nil,
|
"aspect_ratio" => nil,
|
||||||
"duration" => nil
|
"duration" => nil,
|
||||||
}
|
|
||||||
|
|
||||||
assert %Media{short_form_content: nil} = Media.response_to_struct(response)
|
|
||||||
end
|
|
||||||
|
|
||||||
test "parses the upload date" do
|
|
||||||
response = %{
|
|
||||||
"webpage_url" => "https://www.youtube.com/watch?v=TiZPUDkDYbk",
|
|
||||||
"aspect_ratio" => 1.0,
|
|
||||||
"duration" => 61,
|
|
||||||
"upload_date" => "20210101"
|
"upload_date" => "20210101"
|
||||||
}
|
}
|
||||||
|
|
||||||
expected_date = Date.from_iso8601!("2021-01-01")
|
assert %Media{short_form_content: nil} = Media.response_to_struct(response)
|
||||||
|
|
||||||
assert %Media{upload_date: ^expected_date} = Media.response_to_struct(response)
|
|
||||||
end
|
|
||||||
|
|
||||||
test "doesn't blow up if upload date is missing" do
|
|
||||||
response = %{
|
|
||||||
"webpage_url" => "https://www.youtube.com/watch?v=TiZPUDkDYbk",
|
|
||||||
"aspect_ratio" => 1.0,
|
|
||||||
"duration" => 61,
|
|
||||||
"upload_date" => nil
|
|
||||||
}
|
|
||||||
|
|
||||||
assert %Media{upload_date: nil} = Media.response_to_struct(response)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
test "parses the duration" do
|
test "parses the duration" do
|
||||||
@@ -209,4 +187,58 @@ defmodule Pinchflat.YtDlp.MediaTest do
|
|||||||
assert %Media{livestream: false} = Media.response_to_struct(response)
|
assert %Media{livestream: false} = Media.response_to_struct(response)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe "response_to_struct/1 when testing uploaded_at" do
|
||||||
|
test "parses the upload date from the timestamp if present" do
|
||||||
|
response = %{
|
||||||
|
"webpage_url" => "https://www.youtube.com/watch?v=TiZPUDkDYbk",
|
||||||
|
"aspect_ratio" => 1.0,
|
||||||
|
"duration" => 61,
|
||||||
|
"upload_date" => "20210101",
|
||||||
|
"timestamp" => 1_600_000_000
|
||||||
|
}
|
||||||
|
|
||||||
|
expected_date = ~U[2020-09-13 12:26:40Z]
|
||||||
|
|
||||||
|
assert %Media{uploaded_at: ^expected_date} = Media.response_to_struct(response)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "parses the upload date from the uploaded_at if timestamp is present but nil" do
|
||||||
|
response = %{
|
||||||
|
"webpage_url" => "https://www.youtube.com/watch?v=TiZPUDkDYbk",
|
||||||
|
"aspect_ratio" => 1.0,
|
||||||
|
"duration" => 61,
|
||||||
|
"upload_date" => "20210101",
|
||||||
|
"timestamp" => nil
|
||||||
|
}
|
||||||
|
|
||||||
|
expected_date = ~U[2021-01-01 00:00:00Z]
|
||||||
|
|
||||||
|
assert %Media{uploaded_at: ^expected_date} = Media.response_to_struct(response)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "parses the upload date from the uploaded_at if timestamp absent" do
|
||||||
|
response = %{
|
||||||
|
"webpage_url" => "https://www.youtube.com/watch?v=TiZPUDkDYbk",
|
||||||
|
"aspect_ratio" => 1.0,
|
||||||
|
"duration" => 61,
|
||||||
|
"upload_date" => "20210101"
|
||||||
|
}
|
||||||
|
|
||||||
|
expected_date = ~U[2021-01-01 00:00:00Z]
|
||||||
|
|
||||||
|
assert %Media{uploaded_at: ^expected_date} = Media.response_to_struct(response)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "doesn't blow up if upload date is missing" do
|
||||||
|
response = %{
|
||||||
|
"webpage_url" => "https://www.youtube.com/watch?v=TiZPUDkDYbk",
|
||||||
|
"aspect_ratio" => 1.0,
|
||||||
|
"duration" => 61,
|
||||||
|
"upload_date" => nil
|
||||||
|
}
|
||||||
|
|
||||||
|
assert %Media{uploaded_at: nil} = Media.response_to_struct(response)
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -14,6 +14,13 @@ defmodule PinchflatWeb.MediaItemControllerTest do
|
|||||||
|
|
||||||
assert html_response(conn, 200) =~ "#{media_item.title}"
|
assert html_response(conn, 200) =~ "#{media_item.title}"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "renders the page when the media item has no description", %{conn: conn} do
|
||||||
|
media_item = media_item_with_attachments(%{description: nil})
|
||||||
|
conn = get(conn, ~p"/sources/#{media_item.source_id}/media/#{media_item}")
|
||||||
|
|
||||||
|
assert html_response(conn, 200) =~ "#{media_item.title}"
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "edit media" do
|
describe "edit media" do
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ defmodule PinchflatWeb.Sources.MediaItemTableLiveTest do
|
|||||||
|
|
||||||
{:ok, _view, html} = live_isolated(conn, MediaItemTableLive, session: create_session(source))
|
{:ok, _view, html} = live_isolated(conn, MediaItemTableLive, session: create_session(source))
|
||||||
|
|
||||||
assert html =~ "Showing 1 of 1"
|
assert html =~ "Showing"
|
||||||
assert html =~ "Title"
|
assert html =~ "Title"
|
||||||
assert html =~ media_item.title
|
assert html =~ media_item.title
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -9637,7 +9637,7 @@
|
|||||||
"uploader_url": "https://www.youtube.com/@PinchflatTestChannel",
|
"uploader_url": "https://www.youtube.com/@PinchflatTestChannel",
|
||||||
"upload_date": "20210720",
|
"upload_date": "20210720",
|
||||||
"availability": "public",
|
"availability": "public",
|
||||||
"original_url": "ABC123",
|
"original_url": "https://www.youtube.com/watch?v=ABC123",
|
||||||
"webpage_url_basename": "watch",
|
"webpage_url_basename": "watch",
|
||||||
"webpage_url_domain": "youtube.com",
|
"webpage_url_domain": "youtube.com",
|
||||||
"extractor": "youtube",
|
"extractor": "youtube",
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ defmodule Pinchflat.MediaFixtures do
|
|||||||
short_form_content: false,
|
short_form_content: false,
|
||||||
media_filepath: "/video/#{Faker.File.file_name(:video)}",
|
media_filepath: "/video/#{Faker.File.file_name(:video)}",
|
||||||
source_id: SourcesFixtures.source_fixture().id,
|
source_id: SourcesFixtures.source_fixture().id,
|
||||||
upload_date: DateTime.utc_now()
|
uploaded_at: DateTime.utc_now()
|
||||||
})
|
})
|
||||||
|> Pinchflat.Media.create_media_item()
|
|> Pinchflat.Media.create_media_item()
|
||||||
|
|
||||||
@@ -97,7 +97,8 @@ defmodule Pinchflat.MediaFixtures do
|
|||||||
description: "desc1",
|
description: "desc1",
|
||||||
aspect_ratio: 1.67,
|
aspect_ratio: 1.67,
|
||||||
duration: 123.45,
|
duration: 123.45,
|
||||||
upload_date: "20210101"
|
upload_date: "20210101",
|
||||||
|
timestamp: 1_600_000_000
|
||||||
}
|
}
|
||||||
|
|
||||||
Phoenix.json_library().encode!(media_attributes)
|
Phoenix.json_library().encode!(media_attributes)
|
||||||
|
|||||||
Reference in New Issue
Block a user