Compare commits

..

3 Commits

Author SHA1 Message Date
Kieran Eglin c5bf51dfb3 Hooked up script runner for indexing 2024-05-28 10:43:54 -07:00
Kieran Eglin 5d624f545b [WIP] added delay between indexing and downloading in some cases 2024-05-27 11:54:06 -07:00
Kieran Eglin 1305a485a0 Updated CSRF plug to not throw an exception 2024-05-27 11:41:05 -07:00
48 changed files with 543 additions and 498 deletions
+1 -2
View File
@@ -91,7 +91,7 @@ Docker Compose file:
version: '3'
services:
pinchflat:
image: ghcr.io/kieraneglin/pinchflat:latest
image: keglin/pinchflat:latest
environment:
# Set the timezone to your local timezone
- TZ=America/New_York
@@ -153,7 +153,6 @@ 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) |
| 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 |
| BASE_ROUTE_PATH | No | `/` | The base path for route generation. Useful when running behind certain reverse proxies |
## EFF donations
+18 -21
View File
@@ -22,8 +22,8 @@
@font-face {
font-family: 'Satoshi';
src: url('../fonts/satoshi/Satoshi-Light.woff2'), url('../fonts/satoshi/Satoshi-Light.woff'),
url('../fonts/satoshi/Satoshi-Light.ttf');
src: url('/fonts/satoshi/Satoshi-Light.woff2'), url('/fonts/satoshi/Satoshi-Light.woff'),
url('/fonts/satoshi/Satoshi-Light.ttf');
font-weight: 300;
font-display: swap;
font-style: normal;
@@ -31,9 +31,8 @@
@font-face {
font-family: 'Satoshi';
src: url('../fonts/satoshi/Satoshi-LightItalic.woff2'),
url('../fonts/satoshi/Satoshi-LightItalic.woff'),
url('../fonts/satoshi/Satoshi-LightItalic.ttf');
src: url('/fonts/satoshi/Satoshi-LightItalic.woff2'),
url('/fonts/satoshi/Satoshi-LightItalic.woff'), url('/fonts/satoshi/Satoshi-LightItalic.ttf');
font-weight: 300;
font-display: swap;
font-style: italic;
@@ -41,8 +40,8 @@
@font-face {
font-family: 'Satoshi';
src: url('../fonts/satoshi/Satoshi-Regular.woff2'), url('../fonts/satoshi/Satoshi-Regular.woff'),
url('../fonts/satoshi/Satoshi-Regular.ttf');
src: url('/fonts/satoshi/Satoshi-Regular.woff2'), url('/fonts/satoshi/Satoshi-Regular.woff'),
url('/fonts/satoshi/Satoshi-Regular.ttf');
font-weight: 400;
font-display: swap;
font-style: normal;
@@ -50,8 +49,8 @@
@font-face {
font-family: 'Satoshi';
src: url('../fonts/satoshi/Satoshi-Italic.woff2'), url('../fonts/satoshi/Satoshi-Italic.woff'),
url('../fonts/satoshi/Satoshi-Italic.ttf');
src: url('/fonts/satoshi/Satoshi-Italic.woff2'), url('/fonts/satoshi/Satoshi-Italic.woff'),
url('/fonts/satoshi/Satoshi-Italic.ttf');
font-weight: 400;
font-display: swap;
font-style: italic;
@@ -59,8 +58,8 @@
@font-face {
font-family: 'Satoshi';
src: url('../fonts/satoshi/Satoshi-Medium.woff2'), url('../fonts/satoshi/Satoshi-Medium.woff'),
url('../fonts/satoshi/Satoshi-Medium.ttf');
src: url('/fonts/satoshi/Satoshi-Medium.woff2'), url('/fonts/satoshi/Satoshi-Medium.woff'),
url('/fonts/satoshi/Satoshi-Medium.ttf');
font-weight: 500;
font-display: swap;
font-style: normal;
@@ -68,9 +67,8 @@
@font-face {
font-family: 'Satoshi';
src: url('../fonts/satoshi/Satoshi-MediumItalic.woff2'),
url('../fonts/satoshi/Satoshi-MediumItalic.woff'),
url('../fonts/satoshi/Satoshi-MediumItalic.ttf');
src: url('/fonts/satoshi/Satoshi-MediumItalic.woff2'),
url('/fonts/satoshi/Satoshi-MediumItalic.woff'), url('/fonts/satoshi/Satoshi-MediumItalic.ttf');
font-weight: 500;
font-display: swap;
font-style: italic;
@@ -78,8 +76,8 @@
@font-face {
font-family: 'Satoshi';
src: url('../fonts/satoshi/Satoshi-Bold.woff2'), url('../fonts/satoshi/Satoshi-Bold.woff'),
url('../fonts/satoshi/Satoshi-Bold.ttf');
src: url('/fonts/satoshi/Satoshi-Bold.woff2'), url('/fonts/satoshi/Satoshi-Bold.woff'),
url('/fonts/satoshi/Satoshi-Bold.ttf');
font-weight: 700;
font-display: swap;
font-style: normal;
@@ -87,8 +85,8 @@
@font-face {
font-family: 'Satoshi';
src: url('../fonts/satoshi/Satoshi-BoldItalic.woff2'),
url('../fonts/satoshi/Satoshi-BoldItalic.woff'), url('../fonts/satoshi/Satoshi-BoldItalic.ttf');
src: url('/fonts/satoshi/Satoshi-BoldItalic.woff2'), url('/fonts/satoshi/Satoshi-BoldItalic.woff'),
url('/fonts/satoshi/Satoshi-BoldItalic.ttf');
font-weight: 700;
font-display: swap;
font-style: italic;
@@ -96,9 +94,8 @@
@font-face {
font-family: 'Satoshi';
src: url('../fonts/satoshi/Satoshi-BlackItalic.woff2'),
url('../fonts/satoshi/Satoshi-BlackItalic.woff'),
url('../fonts/satoshi/Satoshi-BlackItalic.ttf');
src: url('/fonts/satoshi/Satoshi-BlackItalic.woff2'),
url('/fonts/satoshi/Satoshi-BlackItalic.woff'), url('/fonts/satoshi/Satoshi-BlackItalic.ttf');
font-weight: 900;
font-display: swap;
font-style: italic;
+1 -1
View File
@@ -29,7 +29,7 @@ window.Alpine = Alpine
Alpine.start()
let csrfToken = document.querySelector("meta[name='csrf-token']").getAttribute('content')
let liveSocket = new LiveSocket(document.body.dataset.socketPath, Socket, {
let liveSocket = new LiveSocket('/live', Socket, {
params: { _csrf_token: csrfToken },
dom: {
onBeforeElUpdated(from, to) {
+1 -2
View File
@@ -26,8 +26,7 @@ config :pinchflat,
basic_auth_password: "",
expose_feed_endpoints: false,
file_watcher_poll_interval: 1000,
timezone: "UTC",
base_route_path: "/"
timezone: "UTC"
config :pinchflat, Pinchflat.Repo,
journal_mode: :wal,
+3 -5
View File
@@ -46,12 +46,12 @@ if config_env() == :prod do
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"]))
extras_path = System.get_env("EXTRAS_PATH", Path.join([config_path, "extras"]))
# For running PF as a podcast host on self-hosted environments
expose_feed_endpoints = String.length(System.get_env("EXPOSE_FEED_ENDPOINTS", "")) > 0
# For testing alternate journal modes (see issue #137)
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"))
@@ -65,8 +65,7 @@ if config_env() == :prod do
dns_cluster_query: System.get_env("DNS_CLUSTER_QUERY"),
expose_feed_endpoints: expose_feed_endpoints,
timezone: System.get_env("TIMEZONE") || System.get_env("TZ") || "UTC",
log_path: log_path,
base_route_path: base_route_path
log_path: log_path
config :tzdata, :data_dir, System.get_env("TZ_DATA_DIR", "/etc/elixir_tzdata_data")
@@ -109,7 +108,6 @@ if config_env() == :prod do
ip: {0, 0, 0, 0},
port: String.to_integer(System.get_env("PORT") || "4000")
],
url: [path: base_route_path],
secret_key_base: secret_key_base
config :pinchflat, :logger, [
@@ -16,10 +16,14 @@ defmodule Pinchflat.Downloading.DownloadingHelpers do
alias Pinchflat.Media.MediaItem
alias Pinchflat.Downloading.MediaDownloadWorker
alias Pinchflat.Lifecycle.UserScripts.CommandRunner, as: UserScriptRunner
@doc """
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.
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,
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
@@ -27,13 +31,17 @@ defmodule Pinchflat.Downloading.DownloadingHelpers do
Returns :ok
"""
def enqueue_pending_download_tasks(%Source{download_media: true} = source) do
def enqueue_pending_download_tasks(source, opts \\ [])
def enqueue_pending_download_tasks(%Source{download_media: true} = source, opts) do
kickoff_delay = Keyword.get(opts, :kickoff_delay, 0)
source
|> Media.list_pending_media_items_for()
|> Enum.each(&MediaDownloadWorker.kickoff_with_task/1)
|> Enum.each(&MediaDownloadWorker.kickoff_with_task(&1, %{}, schedule_in: kickoff_delay))
end
def enqueue_pending_download_tasks(%Source{download_media: false}) do
def enqueue_pending_download_tasks(%Source{download_media: false}, _opts) do
:ok
end
@@ -53,15 +61,18 @@ defmodule Pinchflat.Downloading.DownloadingHelpers do
downloaded, based on the source's download settings and whether media is
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()}
"""
def kickoff_download_if_pending(%MediaItem{} = media_item) do
def kickoff_download_if_pending(%MediaItem{} = media_item, opts \\ []) do
kickoff_delay = Keyword.get(opts, :kickoff_delay, 0)
media_item = Repo.preload(media_item, :source)
if media_item.source.download_media && Media.pending_download?(media_item) do
Logger.info("Kicking off download for media item ##{media_item.id} (#{media_item.media_id})")
MediaDownloadWorker.kickoff_with_task(media_item)
MediaDownloadWorker.kickoff_with_task(media_item, %{}, schedule_in: kickoff_delay)
else
{:error, :should_not_download}
end
@@ -98,4 +109,32 @@ defmodule Pinchflat.Downloading.DownloadingHelpers do
|> Repo.all()
|> Enum.map(&MediaDownloadWorker.kickoff_with_task/1)
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
@@ -10,7 +10,6 @@ defmodule Pinchflat.FastIndexing.FastIndexingHelpers do
use Pinchflat.Media.MediaQuery
alias Pinchflat.Repo
alias Pinchflat.Media
alias Pinchflat.Sources.Source
alias Pinchflat.FastIndexing.YoutubeRss
alias Pinchflat.Downloading.DownloadingHelpers
@@ -42,7 +41,8 @@ defmodule Pinchflat.FastIndexing.FastIndexingHelpers do
end
end)
DownloadingHelpers.enqueue_pending_download_tasks(source)
# Wait 5s before enqueuing downloads to give the post-indexing user script a chance to run
DownloadingHelpers.enqueue_pending_download_tasks(source, kickoff_delay: 5)
Enum.filter(maybe_new_media_items, & &1)
end
@@ -57,8 +57,8 @@ defmodule Pinchflat.FastIndexing.FastIndexingHelpers do
url = "https://www.youtube.com/watch?v=#{media_id}"
case YtDlpMedia.get_media_attributes(url) do
{:ok, media_attrs} ->
Media.create_media_item_from_backend_attrs(source, media_attrs)
{:ok, media_attrs_struct} ->
DownloadingHelpers.create_media_item_and_run_script(source, media_attrs_struct)
err ->
err
@@ -12,6 +12,7 @@ defmodule Pinchflat.Lifecycle.UserScripts.CommandRunner do
@behaviour UserScriptCommandRunner
@event_types [
:media_indexed,
:media_downloaded,
:media_deleted
]
+4 -5
View File
@@ -41,10 +41,9 @@ defmodule Pinchflat.Media do
Returns a list of media_items that are redownloadable based on the redownload delay
of the media_profile their source belongs to.
The logic is that a media_item is past_redownload_delay if the media_item's uploaded_at is
at least redownload_delay_days ago AND `media_downloaded_at` - `redownload_delay_days`
is before the media_item's `uploaded_at`.
The logic is that a media_item is past_redownload_delay if the media_item's
upload_date is at least redownload_delay_days ago AND
`media_downloaded_at` - `redownload_delay_days` is before the media_item's `upload_date`.
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.
This also makes things work as expected when downloading media from a source for the
@@ -136,7 +135,7 @@ defmodule Pinchflat.Media do
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
we may not have asked for in the past (eg: uploaded_at)
we may not have asked for in the past (eg: upload_date)
Returns {:ok, %MediaItem{}} | {:error, %Ecto.Changeset{}}
"""
+5 -5
View File
@@ -26,7 +26,7 @@ defmodule Pinchflat.Media.MediaItem do
:livestream,
:source_id,
:short_form_content,
:uploaded_at,
:upload_date,
:upload_date_index,
:duration_seconds,
# these fields are captured only on download
@@ -51,7 +51,7 @@ defmodule Pinchflat.Media.MediaItem do
livestream
media_id
source_id
uploaded_at
upload_date
short_form_content
)a
@@ -69,7 +69,7 @@ defmodule Pinchflat.Media.MediaItem do
field :short_form_content, :boolean, default: false
field :media_downloaded_at, :utc_datetime
field :media_redownloaded_at, :utc_datetime
field :uploaded_at, :utc_datetime
field :upload_date, :date
field :upload_date_index, :integer, default: 0
field :duration_seconds, :integer
@@ -130,7 +130,7 @@ defmodule Pinchflat.Media.MediaItem do
~w(__meta__ __struct__ metadata tasks media_items_search_index)a
end
defp update_upload_date_index(%{changes: changes} = changeset) when is_map_key(changes, :uploaded_at) do
defp update_upload_date_index(%{changes: changes} = changeset) when is_map_key(changes, :upload_date) do
source_id = get_field(changeset, :source_id)
source = Sources.get_source!(source_id)
# Channels should count down from 99, playlists should count up from 0
@@ -142,7 +142,7 @@ defmodule Pinchflat.Media.MediaItem do
current_max =
MediaQuery.new()
|> where(^dynamic([mi], ^MediaQuery.upload_date_matches(changes.uploaded_at) and ^MediaQuery.for_source(source)))
|> where(^dynamic([mi], mi.upload_date == ^changes.upload_date and ^MediaQuery.for_source(source)))
|> Repo.aggregate(aggregator, :upload_date_index)
case current_max do
+6 -9
View File
@@ -35,14 +35,11 @@ defmodule Pinchflat.Media.MediaQuery do
def culling_prevented, do: dynamic([mi], mi.prevent_culling == true)
def culled, do: dynamic([mi], not is_nil(mi.culled_at))
def redownloaded, do: dynamic([mi], not is_nil(mi.media_redownloaded_at))
def upload_date_matches(other_date), do: dynamic([mi], fragment("date(?) = date(?)", mi.uploaded_at, ^other_date))
def media_id(nil), do: dynamic(false)
def media_id(media_id), do: dynamic([mi], mi.media_id == ^media_id)
def upload_date_after_source_cutoff do
dynamic(
[mi, source],
is_nil(source.download_cutoff_date) or
fragment("date(?) >= ?", mi.uploaded_at, source.download_cutoff_date)
)
dynamic([mi, source], is_nil(source.download_cutoff_date) or mi.upload_date >= source.download_cutoff_date)
end
def format_matching_profile_preference do
@@ -89,12 +86,12 @@ defmodule Pinchflat.Media.MediaQuery do
def past_redownload_delay do
dynamic(
[mi, source, media_profile],
# Returns media items where the uploaded_at is at least redownload_delay_days ago AND
# Returns media items where the upload_date is at least redownload_delay_days ago AND
# downloaded_at minus the redownload_delay_days is before the upload date
fragment("""
IFNULL(redownload_delay_days, 0) > 0 AND
DATETIME('now', '-' || redownload_delay_days || ' day') > uploaded_at AND
DATETIME(media_downloaded_at, '-' || redownload_delay_days || ' day') < uploaded_at
DATETIME('now', '-' || redownload_delay_days || ' day') > upload_date AND
DATETIME(media_downloaded_at, '-' || redownload_delay_days || ' day') < upload_date
""")
)
end
@@ -88,17 +88,14 @@ defmodule Pinchflat.Metadata.MetadataFileHelpers do
@doc """
Parses an upload date from the YYYYMMDD string returned in yt-dlp metadata
and returns a DateTime struct, appending a time of 00:00:00Z.
and returns a Date struct.
Returns DateTime.t()
Returns Date.t()
"""
def parse_upload_date(upload_date) do
<<year::binary-size(4)>> <> <<month::binary-size(2)>> <> <<day::binary-size(2)>> = upload_date
case DateTime.from_iso8601("#{year}-#{month}-#{day}T00:00:00Z") do
{:ok, datetime, _} -> datetime
_ -> raise "Invalid upload date: #{upload_date}"
end
Date.from_iso8601!("#{year}-#{month}-#{day}")
end
@doc """
+9 -8
View File
@@ -8,8 +8,6 @@ defmodule Pinchflat.Metadata.MetadataParser do
and not have it, ya know?
"""
alias Pinchflat.YtDlp.Media, as: YtDlpMedia
@doc """
Parses the given JSON response from yt-dlp and returns a map of
the needful media_item attributes, along with anything needed for
@@ -26,12 +24,15 @@ defmodule Pinchflat.Metadata.MetadataParser do
end
defp parse_media_metadata(metadata) do
Map.merge(
Map.from_struct(YtDlpMedia.response_to_struct(metadata)),
%{
media_filepath: metadata["filepath"]
}
)
%{
media_id: metadata["id"],
title: metadata["title"],
original_url: metadata["original_url"],
description: metadata["description"],
media_filepath: metadata["filepath"],
livestream: !!metadata["was_live"],
duration_seconds: metadata["duration"] && round(metadata["duration"])
}
end
defp parse_subtitle_metadata(metadata) do
+1 -1
View File
@@ -28,7 +28,7 @@ defmodule Pinchflat.Podcasts.PodcastHelpers do
MediaQuery.new()
|> where(^dynamic(^MediaQuery.for_source(source) and ^MediaQuery.downloaded()))
|> order_by(desc: :uploaded_at)
|> order_by(desc: :upload_date)
|> Repo.maybe_limit(limit)
|> Repo.all()
|> Enum.filter(fn media_item -> File.exists?(media_item.media_filepath) end)
+8 -1
View File
@@ -7,6 +7,7 @@ defmodule Pinchflat.Podcasts.RssFeedBuilder do
import Pinchflat.Utils.XmlUtils, only: [safe: 1]
alias Pinchflat.Utils.DatetimeUtils
alias Pinchflat.Podcasts.PodcastHelpers
alias PinchflatWeb.Router.Helpers, as: Routes
@@ -82,7 +83,7 @@ defmodule Pinchflat.Podcasts.RssFeedBuilder do
<title>#{safe(media_item.title)}</title>
<link>#{safe(media_item.original_url)}</link>
<description>#{safe(media_item.description)}</description>
<pubDate>#{Calendar.strftime(media_item.uploaded_at, @datetime_format)}</pubDate>
<pubDate>#{generate_upload_date(media_item)}</pubDate>
<itunes:duration>#{media_item.duration_seconds}</itunes:duration>
<enclosure
url="#{media_stream_path(url_base, media_item)}"
@@ -132,6 +133,12 @@ defmodule Pinchflat.Podcasts.RssFeedBuilder do
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
Routes.podcast_path(PinchflatWeb.Endpoint, action, params)
end
@@ -8,7 +8,6 @@ defmodule Pinchflat.SlowIndexing.SlowIndexingHelpers do
require Logger
alias Pinchflat.Repo
alias Pinchflat.Media
alias Pinchflat.Tasks
alias Pinchflat.Sources
alias Pinchflat.Sources.Source
@@ -39,6 +38,9 @@ defmodule Pinchflat.SlowIndexing.SlowIndexingHelpers do
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).
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.
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
@@ -64,15 +66,16 @@ defmodule Pinchflat.SlowIndexing.SlowIndexingHelpers do
source = Repo.reload!(source)
result =
Enum.map(media_attributes, fn media_attrs ->
case Media.create_media_item_from_backend_attrs(source, media_attrs) do
Enum.map(media_attributes, fn media_attrs_struct ->
case DownloadingHelpers.create_media_item_and_run_script(source, media_attrs_struct) do
{:ok, media_item} -> media_item
{:error, changeset} -> changeset
end
end)
Sources.update_source(source, %{last_indexed_at: DateTime.utc_now()})
DownloadingHelpers.enqueue_pending_download_tasks(source)
# Wait 5s before enqueuing downloads to give the post-indexing user script a chance to run
DownloadingHelpers.enqueue_pending_download_tasks(source, kickoff_delay: 5)
result
end
@@ -118,14 +121,15 @@ defmodule Pinchflat.SlowIndexing.SlowIndexingHelpers do
end)
end
defp create_media_item_and_enqueue_download(source, media_attrs) do
defp create_media_item_and_enqueue_download(source, media_attrs_struct) do
# Reload because the source may have been updated during the (long-running) indexing process
# and important settings like `download_media` may have changed.
source = Repo.reload!(source)
case Media.create_media_item_from_backend_attrs(source, media_attrs) do
case DownloadingHelpers.create_media_item_and_run_script(source, media_attrs_struct) do
{:ok, %MediaItem{} = media_item} ->
DownloadingHelpers.kickoff_download_if_pending(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, kickoff_delay: 5)
{:error, changeset} ->
changeset
+17
View File
@@ -0,0 +1,17 @@
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
+4 -16
View File
@@ -10,7 +10,7 @@ defmodule Pinchflat.YtDlp.Media do
:original_url,
:livestream,
:short_form_content,
:uploaded_at,
:upload_date,
:duration_seconds
]
@@ -21,7 +21,7 @@ defmodule Pinchflat.YtDlp.Media do
:original_url,
:livestream,
:short_form_content,
:uploaded_at,
:upload_date,
:duration_seconds
]
@@ -72,7 +72,7 @@ defmodule Pinchflat.YtDlp.Media do
Returns the output template for yt-dlp's indexing command.
"""
def indexing_output_template do
"%(.{id,title,was_live,webpage_url,description,aspect_ratio,duration,upload_date,timestamp})j"
"%(.{id,title,was_live,webpage_url,description,aspect_ratio,duration,upload_date})j"
end
@doc """
@@ -90,7 +90,7 @@ defmodule Pinchflat.YtDlp.Media do
livestream: !!response["was_live"],
duration_seconds: response["duration"] && round(response["duration"]),
short_form_content: response["webpage_url"] && short_form_content?(response),
uploaded_at: response["upload_date"] && parse_uploaded_at(response)
upload_date: response["upload_date"] && MetadataFileHelpers.parse_upload_date(response["upload_date"])
}
end
@@ -110,18 +110,6 @@ defmodule Pinchflat.YtDlp.Media do
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
# This approach lets us mock the command for testing
Application.get_env(:pinchflat, :yt_dlp_runner)
@@ -2,8 +2,7 @@ defmodule PinchflatWeb.CustomComponents.TableComponents do
@moduledoc false
use Phoenix.Component
import PinchflatWeb.CoreComponents
import PinchflatWeb.CustomComponents.TextComponents
alias PinchflatWeb.CoreComponents
@doc """
Renders a table component with the given rows and columns.
@@ -77,13 +76,11 @@ defmodule PinchflatWeb.CustomComponents.TableComponents do
phx-click={@page_number != 1 && "page_change"}
phx-value-direction="dec"
>
<.icon name="hero-chevron-left" />
<CoreComponents.icon name="hero-chevron-left" />
</span>
</li>
<li>
<span class="mx-2">
Page <.localized_number number={@page_number} /> of <.localized_number number={@total_pages} />
</span>
<span class="mx-2">Page <%= @page_number %> of <%= @total_pages %></span>
</li>
<li>
<span
@@ -95,7 +92,7 @@ defmodule PinchflatWeb.CustomComponents.TableComponents do
phx-click={@page_number != @total_pages && "page_change"}
phx-value-direction="inc"
>
<.icon name="hero-chevron-right" />
<CoreComponents.icon name="hero-chevron-right" />
</span>
</li>
</ul>
@@ -94,28 +94,4 @@ defmodule PinchflatWeb.CustomComponents.TextComponents do
<time><%= Calendar.strftime(Timex.Timezone.convert(@datetime, @timezone), @format) %></time>
"""
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
@@ -19,7 +19,6 @@
onboarding: #{Settings.get!(:onboarding)}
}"}
class="dark text-bodydark bg-boxdark-2"
data-socket-path={Path.join(Application.get_env(:pinchflat, :base_route_path), "/live")}
>
<%= @inner_content %>
@@ -31,7 +31,7 @@
<.media_preview media_item={@media_item} />
</div>
<aside class="mt-4 xl:mt-0">
<div>Uploaded: <%= DateTime.to_date(@media_item.uploaded_at) %></div>
<div>Uploaded: <%= @media_item.upload_date %></div>
<div>
<span :if={URI.parse(@media_item.original_url).scheme =~ "http"}>
<.subtle_link href={@media_item.original_url} target="_blank">Open Original</.subtle_link>
@@ -43,7 +43,7 @@
</.subtle_link>
</span>
</div>
<div :if={@media_item.description} class="mt-4 text-bodydark">
<div class="mt-4 text-bodydark">
<.break_on_newline text={@media_item.description} />
</div>
</aside>
@@ -5,19 +5,9 @@ defmodule PinchflatWeb.Pages.PageHTML do
embed_templates "page_html/*"
attr :media_filesize, :integer, required: true
def readable_media_filesize(media_filesize) do
{num, suffix} = NumberUtils.human_byte_size(media_filesize, precision: 1)
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 %>
"""
"#{Float.round(num)} #{suffix}"
end
end
@@ -22,9 +22,7 @@ defmodule Pinchflat.Pages.HistoryTableLive do
<div>
<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" />
<span class="ml-2">
Showing <.localized_number number={length(@records)} /> of <.localized_number number={@total_record_count} />
</span>
<span class="ml-2">Showing <%= length(@records) %> of <%= @total_record_count %></span>
</span>
<div class="max-w-full overflow-x-auto">
<.table rows={@records} table_class="text-white">
@@ -34,7 +32,7 @@ defmodule Pinchflat.Pages.HistoryTableLive do
</.subtle_link>
</:col>
<:col :let={media_item} label="Upload Date">
<%= DateTime.to_date(media_item.uploaded_at) %>
<%= media_item.upload_date %>
</:col>
<:col :let={media_item} label="Indexed At">
<%= format_datetime(media_item.inserted_at) %>
@@ -1,39 +1,33 @@
<div class="grid grid-cols-1 gap-4 md:grid-cols-4">
<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="flex flex-col items-center py-2">
<span class="text-md font-medium">
Media <.pluralize count={@media_profile_count} word="Profile" />
</span>
<div class="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">
<span class="text-md font-medium">Media Profile(s)</span>
<h4 class="text-title-md font-bold text-white">
<.localized_number number={@media_profile_count} />
<%= @media_profile_count %>
</h4>
</a>
</div>
<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="flex flex-col items-center py-2">
<span class="text-md font-medium">
<.pluralize count={@source_count} word="Source" />
</span>
<div class="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">
<span class="text-md font-medium">Source(s)</span>
<h4 class="text-title-md font-bold text-white">
<.localized_number number={@source_count} />
<%= @source_count %>
</h4>
</a>
</div>
<div class="flex flex-col justify-center rounded-sm border px-7.5 py-6 shadow-default border-strokedark bg-boxdark">
<span class="flex flex-col items-center py-2">
<span class="text-md font-medium">
<.pluralize count={@media_item_count} word="Download" />
</span>
<div class="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="text-md font-medium">Downloaded Media</span>
<h4 class="text-title-md font-bold text-white">
<.localized_number number={@media_item_count} />
<%= @media_item_count %>
</h4>
</span>
</div>
<div class="flex flex-col justify-center rounded-sm border px-7.5 py-6 shadow-default border-strokedark bg-boxdark">
<span class="flex flex-col items-center py-2">
<div class="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="text-md font-medium">Library Size</span>
<h4 class="text-title-md font-bold text-white">
<.readable_media_filesize media_filesize={@media_item_size} />
<%= readable_media_filesize(@media_item_size) %>
</h4>
</span>
</div>
@@ -19,12 +19,11 @@
</.subtle_link>
</:col>
<:col :let={source} label="Type"><%= source.collection_type %></:col>
<:col :let={source} label="Pending"><.localized_number number={source.pending_count} /></:col>
<:col :let={source} label="Downloaded"><.localized_number number={source.downloaded_count} /></:col>
<:col :let={source} label="Pending"><%= source.pending_count %></:col>
<:col :let={source} label="Downloaded"><%= source.downloaded_count %></:col>
<:col :let={source} label="Retention">
<%= if source.retention_period_days && source.retention_period_days > 0 do %>
<.localized_number number={source.retention_period_days} />
<.pluralize count={source.retention_period_days} word="day" />
<%= source.retention_period_days %> day(s)
<% else %>
<span class="text-lg">∞</span>
<% end %>
@@ -23,9 +23,7 @@ defmodule Pinchflat.Sources.MediaItemTableLive do
<header class="flex justify-between items-center mb-4">
<span class="flex items-center">
<.icon_button icon_name="hero-arrow-path" class="h-10 w-10" phx-click="reload_page" tooltip="Refresh" />
<span class="ml-2">
Showing <.localized_number number={length(@records)} /> of <.localized_number number={@filtered_record_count} />
</span>
<span class="ml-2">Showing <%= length(@records) %> of <%= @filtered_record_count %></span>
</span>
<div class="bg-meta-4 rounded-md">
<div class="relative">
@@ -51,12 +49,12 @@ defmodule Pinchflat.Sources.MediaItemTableLive do
<%= StringUtils.truncate(media_item.title, 50) %>
</.subtle_link>
</:col>
<:col :let={media_item} label="Upload Date">
<%= media_item.upload_date %>
</:col>
<: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"} />
</: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">
<.icon_link href={~p"/sources/#{@source.id}/media/#{media_item.id}/edit"} icon="hero-pencil-square" class="mr-4" />
</:col>
@@ -119,40 +117,14 @@ defmodule Pinchflat.Sources.MediaItemTableLive do
{:noreply, assign(socket, new_assigns)}
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
filtered_base_query = filtered_base_query(base_query, search_term)
filtered_base_query = filter_base_query(base_query, search_term)
total_record_count = Repo.aggregate(base_query, :count, :id)
filtered_record_count = Repo.aggregate(filtered_base_query, :count, :id)
total_pages = max(ceil(filtered_record_count / @limit), 1)
page = NumberUtils.clamp(page, 1, total_pages)
records =
fetch_records(filtered_base_query, page)
|> order_by(desc: fragment("rank"), desc: :uploaded_at)
|> Repo.all()
records = fetch_records(filtered_base_query, page)
%{
page: page,
@@ -170,41 +142,39 @@ defmodule Pinchflat.Sources.MediaItemTableLive do
base_query
|> limit(^@limit)
|> offset(^offset)
|> Repo.all()
end
defp generate_base_query(source, "pending") do
MediaQuery.new()
|> select(^select_fields())
|> MediaQuery.require_assoc(:media_profile)
|> MediaQuery.require_assoc(:media_items_search_index)
|> where(^dynamic(^MediaQuery.for_source(source) and ^MediaQuery.pending()))
|> order_by(desc: fragment("rank"), desc: :upload_date)
end
defp generate_base_query(source, "downloaded") do
MediaQuery.new()
|> select(^select_fields())
|> MediaQuery.require_assoc(:media_items_search_index)
|> where(^dynamic(^MediaQuery.for_source(source) and ^MediaQuery.downloaded()))
|> order_by(desc: fragment("rank"), desc: :upload_date)
end
defp generate_base_query(source, "other") do
MediaQuery.new()
|> select(^select_fields())
|> MediaQuery.require_assoc(:media_profile)
|> MediaQuery.require_assoc(:media_items_search_index)
|> where(
^dynamic(
^MediaQuery.for_source(source) and
(not (^MediaQuery.downloaded()) and not (^MediaQuery.pending()))
)
)
|> order_by(desc: fragment("rank"), desc: :upload_date)
end
defp filtered_base_query(base_query, search_term) do
defp filter_base_query(base_query, search_term) do
base_query
|> MediaQuery.require_assoc(:media_items_search_index)
|> where(^MediaQuery.matches_search_term(search_term))
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
@@ -95,8 +95,7 @@
type="number"
label="Retention Period (days)"
min="0"
help="Days between when media is <em>downloaded</em> and when it's deleted. Leave blank to keep media indefinitely"
html_help={true}
help="Days between when media is *downloaded* and when it's deleted. Leave blank to keep media indefinitely"
/>
<section x-show="advancedMode">
+1 -1
View File
@@ -10,7 +10,7 @@ defmodule PinchflatWeb.Router do
plug :fetch_session
plug :fetch_live_flash
plug :put_root_layout, html: {PinchflatWeb.Layouts, :root}
plug :protect_from_forgery
plug :protect_from_forgery, with: :clear_session
plug :put_secure_browser_headers
plug :allow_iframe_embed
end
+1 -1
View File
@@ -4,7 +4,7 @@ defmodule Pinchflat.MixProject do
def project do
[
app: :pinchflat,
version: "2024.5.30",
version: "2024.5.24",
elixir: "~> 1.16",
elixirc_paths: elixirc_paths(Mix.env()),
start_permanent: Mix.env() == :prod,
BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 471 KiB

After

Width:  |  Height:  |  Size: 449 KiB

@@ -1,21 +0,0 @@
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
@@ -1,8 +0,0 @@
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
@@ -1,19 +0,0 @@
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,9 +6,13 @@ defmodule Pinchflat.Downloading.DownloadingHelpersTest do
import Pinchflat.ProfilesFixtures
alias Pinchflat.Tasks
alias Pinchflat.Media.MediaItem
alias Pinchflat.Utils.FilesystemUtils
alias Pinchflat.Downloading.DownloadingHelpers
alias Pinchflat.Downloading.MediaDownloadWorker
alias Pinchflat.YtDlp.Media, as: YtDlpMedia
describe "enqueue_pending_download_tasks/1" do
test "it enqueues a job for each pending media item" do
source = source_fixture()
@@ -19,6 +23,16 @@ defmodule Pinchflat.Downloading.DownloadingHelpersTest do
assert_enqueued(worker: MediaDownloadWorker, args: %{"id" => media_item.id})
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
source = source_fixture()
_media_item = media_item_fixture(source_id: source.id, media_filepath: "some/filepath.mp4")
@@ -84,6 +98,13 @@ defmodule Pinchflat.Downloading.DownloadingHelpersTest do
assert_enqueued(worker: MediaDownloadWorker, args: %{"id" => media_item.id})
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
assert {:ok, task} = DownloadingHelpers.kickoff_download_if_pending(media_item)
@@ -138,4 +159,67 @@ defmodule Pinchflat.Downloading.DownloadingHelpersTest do
refute_enqueued(worker: MediaDownloadWorker)
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
@@ -16,7 +16,7 @@ defmodule Pinchflat.Downloading.MediaQualityUpgradeWorkerTest do
media_item =
media_item_fixture(%{
source_id: source.id,
uploaded_at: now_minus(6, :days),
upload_date: now_minus(6, :days),
media_downloaded_at: now_minus(5, :days)
})
@@ -32,7 +32,7 @@ defmodule Pinchflat.Downloading.MediaQualityUpgradeWorkerTest do
_media_item =
media_item_fixture(%{
source_id: source.id,
uploaded_at: now_minus(6, :days),
upload_date: now_minus(6, :days),
media_downloaded_at: now_minus(1, :day)
})
@@ -28,6 +28,16 @@ defmodule Pinchflat.FastIndexing.FastIndexingHelpersTest do
assert worker.args["id"] == media_item.id
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
expect(HTTPClientMock, :get, fn _url -> {:ok, "<yt:videoId>test_1</yt:videoId>"} end)
media_item_fixture(source_id: source.id, media_id: "test_1")
+177 -147
View File
@@ -34,6 +34,150 @@ defmodule Pinchflat.MediaTest do
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
test "it returns all media_items" do
media_item = media_item_fixture()
@@ -145,7 +289,7 @@ defmodule Pinchflat.MediaTest do
media_item =
media_item_fixture(%{
source_id: source.id,
uploaded_at: now_minus(6, :days),
upload_date: now_minus(6, :days),
media_downloaded_at: now_minus(5, :days)
})
@@ -156,7 +300,7 @@ defmodule Pinchflat.MediaTest do
media_item =
media_item_fixture(%{
source_id: source.id,
uploaded_at: now_minus(20, :days),
upload_date: now_minus(20, :days),
media_downloaded_at: now_minus(19, :days)
})
@@ -167,7 +311,7 @@ defmodule Pinchflat.MediaTest do
_media_item =
media_item_fixture(%{
source_id: source.id,
uploaded_at: now_minus(5, :days),
upload_date: now_minus(5, :days),
media_downloaded_at: nil
})
@@ -178,7 +322,7 @@ defmodule Pinchflat.MediaTest do
_media_item =
media_item_fixture(%{
source_id: source.id,
uploaded_at: now_minus(5, :days),
upload_date: now_minus(5, :days),
media_downloaded_at: now(),
prevent_download: true
})
@@ -190,7 +334,7 @@ defmodule Pinchflat.MediaTest do
_media_item =
media_item_fixture(%{
source_id: source.id,
uploaded_at: now_minus(5, :days),
upload_date: now_minus(5, :days),
media_downloaded_at: now(),
culled_at: now()
})
@@ -202,7 +346,7 @@ defmodule Pinchflat.MediaTest do
_media_item =
media_item_fixture(%{
source_id: source.id,
uploaded_at: now_minus(3, :days),
upload_date: now_minus(3, :days),
media_downloaded_at: now_minus(3, :days)
})
@@ -213,7 +357,7 @@ defmodule Pinchflat.MediaTest do
_media_item =
media_item_fixture(%{
source_id: source.id,
uploaded_at: now_minus(5, :days),
upload_date: now_minus(5, :days),
media_downloaded_at: now(),
media_redownloaded_at: now()
})
@@ -221,12 +365,12 @@ defmodule Pinchflat.MediaTest do
assert Media.list_redownloadable_media_items() == []
end
test "does not return media items that were first downloaded well after the uploaded_at", %{source: source} do
test "does not return media items that were first downloaded well after the upload_date", %{source: source} do
_media_item =
media_item_fixture(%{
source_id: source.id,
media_downloaded_at: now(),
uploaded_at: now_minus(20, :days)
upload_date: now_minus(20, :days)
})
assert Media.list_redownloadable_media_items() == []
@@ -237,7 +381,7 @@ defmodule Pinchflat.MediaTest do
media_item_fixture(%{
source_id: source.id,
media_downloaded_at: now(),
uploaded_at: now_minus(2, :days)
upload_date: now_minus(2, :days)
})
assert Media.list_redownloadable_media_items() == []
@@ -250,7 +394,7 @@ defmodule Pinchflat.MediaTest do
_media_item =
media_item_fixture(%{
source_id: source.id,
uploaded_at: now_minus(6, :days),
upload_date: now_minus(6, :days),
media_downloaded_at: now_minus(5, :days)
})
@@ -408,9 +552,9 @@ defmodule Pinchflat.MediaTest do
source = source_fixture(%{download_cutoff_date: now_minus(1, :day)})
_old_media_item =
media_item_fixture(%{source_id: source.id, media_filepath: nil, uploaded_at: now_minus(2, :days)})
media_item_fixture(%{source_id: source.id, media_filepath: nil, upload_date: now_minus(2, :days)})
new_media_item = media_item_fixture(%{source_id: source.id, media_filepath: nil, uploaded_at: now()})
new_media_item = media_item_fixture(%{source_id: source.id, media_filepath: nil, upload_date: now()})
assert Media.list_pending_media_items_for(source) == [new_media_item]
end
@@ -419,9 +563,9 @@ defmodule Pinchflat.MediaTest do
source = source_fixture(%{download_cutoff_date: nil})
old_media_item =
media_item_fixture(%{source_id: source.id, media_filepath: nil, uploaded_at: now_minus(2, :days)})
media_item_fixture(%{source_id: source.id, media_filepath: nil, upload_date: now_minus(2, :days)})
new_media_item = media_item_fixture(%{source_id: source.id, media_filepath: nil, uploaded_at: now()})
new_media_item = media_item_fixture(%{source_id: source.id, media_filepath: nil, upload_date: now()})
assert Media.list_pending_media_items_for(source) == [old_media_item, new_media_item]
end
@@ -486,21 +630,21 @@ defmodule Pinchflat.MediaTest 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)})
media_item = media_item_fixture(%{source_id: source.id, media_filepath: nil, uploaded_at: now_minus(1, :day)})
media_item = media_item_fixture(%{source_id: source.id, media_filepath: nil, upload_date: now_minus(1, :day)})
assert Media.pending_download?(media_item)
end
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)})
media_item = media_item_fixture(%{source_id: source.id, media_filepath: nil, uploaded_at: now_minus(2, :days)})
media_item = media_item_fixture(%{source_id: source.id, media_filepath: nil, upload_date: now_minus(2, :days)})
refute Media.pending_download?(media_item)
end
test "returns true if there is no cutoff date" do
source = source_fixture(%{download_cutoff_date: nil})
media_item = media_item_fixture(%{source_id: source.id, media_filepath: nil, uploaded_at: now_minus(1, :day)})
media_item = media_item_fixture(%{source_id: source.id, media_filepath: nil, upload_date: now_minus(1, :day)})
assert Media.pending_download?(media_item)
end
@@ -611,7 +755,7 @@ defmodule Pinchflat.MediaTest do
media_filepath: "/video/#{Faker.File.file_name(:video)}",
source_id: source_fixture().id,
original_url: "https://www.youtube.com/channel/#{Faker.String.base64(12)}",
uploaded_at: now()
upload_date: Date.utc_today()
}
assert {:ok, %MediaItem{} = media_item} = Media.create_media_item(valid_attrs)
@@ -628,7 +772,7 @@ defmodule Pinchflat.MediaTest do
media_filepath: "/video/#{Faker.File.file_name(:video)}",
source_id: source_fixture().id,
original_url: "https://www.youtube.com/channel/#{Faker.String.base64(12)}",
uploaded_at: now()
upload_date: Date.utc_today()
}
assert {:ok, %MediaItem{} = media_item} = Media.create_media_item(valid_attrs)
@@ -643,7 +787,7 @@ defmodule Pinchflat.MediaTest do
media_filepath: "/video/#{Faker.File.file_name(:video)}",
source_id: source_fixture().id,
original_url: "https://www.youtube.com/channel/#{Faker.String.base64(12)}",
uploaded_at: now(),
upload_date: Date.utc_today(),
uuid: "some-uuid"
}
@@ -691,6 +835,18 @@ defmodule Pinchflat.MediaTest do
assert media_item_1.id == media_item_2.id
assert media_item_2.title == different_attrs.title
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
describe "update_media_item/2" do
@@ -908,130 +1064,4 @@ defmodule Pinchflat.MediaTest do
assert %Ecto.Changeset{} = Media.change_media_item(media_item)
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
@@ -132,10 +132,10 @@ defmodule Pinchflat.Metadata.MetadataFileHelpersTest do
end
describe "parse_upload_date/1" do
test "returns a datetime from the given metadata upload date" do
test "returns a date from the given metadata upload date" do
upload_date = "20210101"
assert Helpers.parse_upload_date(upload_date) == ~U[2021-01-01 00:00:00Z]
assert Helpers.parse_upload_date(upload_date) == ~D[2021-01-01]
end
end
@@ -26,9 +26,9 @@ defmodule Pinchflat.Podcasts.PodcastHelpersTest do
test "orders by upload date where newest is first" do
source = source_fixture()
oldest = media_item_with_attachments(%{source_id: source.id, uploaded_at: now_minus(2, :day)})
current = media_item_with_attachments(%{source_id: source.id, uploaded_at: now()})
older = media_item_with_attachments(%{source_id: source.id, uploaded_at: now_minus(1, :days)})
oldest = media_item_with_attachments(%{source_id: source.id, upload_date: now_minus(2, :day)})
current = media_item_with_attachments(%{source_id: source.id, upload_date: now()})
older = media_item_with_attachments(%{source_id: source.id, upload_date: now_minus(1, :days)})
assert [^current, ^older, ^oldest] = PodcastHelpers.persisted_media_items_for(source)
end
@@ -117,8 +117,8 @@ defmodule Pinchflat.Podcasts.RssFeedBuilderTest do
assert String.contains?(item_xml, ~s(<itunes:summary><![CDATA[#{media_item.description}]]></itunes:summary>))
end
test "returns pubDate based off the media's uploaded_at", %{source: source} do
media_item_with_attachments(%{source_id: source.id, uploaded_at: ~U[2020-01-01 00:00:00Z]})
test "returns pubDate based off the media's upload_date", %{source: source} do
media_item_with_attachments(%{source_id: source.id, upload_date: ~D[2020-01-01]})
res = RssFeedBuilder.build(source)
[_before, item_xml, _after] = String.split(res, ~r(</?item>))
@@ -158,6 +158,16 @@ defmodule Pinchflat.SlowIndexing.SlowIndexingHelpersTest do
assert_enqueued(worker: MediaDownloadWorker, args: %{"id" => media_item.id})
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
source = source_fixture(download_media: false)
media_item = media_item_fixture(source_id: source.id, media_filepath: nil)
@@ -235,6 +245,26 @@ defmodule Pinchflat.SlowIndexing.SlowIndexingHelpersTest do
assert_enqueued(worker: MediaDownloadWorker)
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
watcher_poll_interval = Application.get_env(:pinchflat, :file_watcher_poll_interval)
source = source_fixture(download_media: false)
@@ -0,0 +1,14 @@
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
+28 -60
View File
@@ -79,7 +79,7 @@ defmodule Pinchflat.YtDlp.MediaTest do
describe "indexing_output_template/0" do
test "contains all the greatest hits" do
assert "%(.{id,title,was_live,webpage_url,description,aspect_ratio,duration,upload_date,timestamp})j" ==
assert "%(.{id,title,was_live,webpage_url,description,aspect_ratio,duration,upload_date})j" ==
Media.indexing_output_template()
end
end
@@ -94,8 +94,7 @@ defmodule Pinchflat.YtDlp.MediaTest do
"was_live" => false,
"aspect_ratio" => 1.0,
"duration" => 60,
"upload_date" => "20210101",
"timestamp" => 1_600_000_000
"upload_date" => "20210101"
}
assert %Media{
@@ -105,7 +104,7 @@ defmodule Pinchflat.YtDlp.MediaTest do
original_url: "https://www.youtube.com/watch?v=TiZPUDkDYbk",
livestream: false,
short_form_content: false,
uploaded_at: ~U[2020-09-13 12:26:40Z],
upload_date: Date.from_iso8601!("2021-01-01"),
duration_seconds: 60
} == Media.response_to_struct(response)
end
@@ -147,13 +146,36 @@ defmodule Pinchflat.YtDlp.MediaTest do
response = %{
"webpage_url" => nil,
"aspect_ratio" => nil,
"duration" => nil,
"upload_date" => "20210101"
"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"
}
expected_date = Date.from_iso8601!("2021-01-01")
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
test "parses the duration" do
response = %{
"webpage_url" => "https://www.youtube.com/watch?v=TiZPUDkDYbk",
@@ -187,58 +209,4 @@ defmodule Pinchflat.YtDlp.MediaTest do
assert %Media{livestream: false} = Media.response_to_struct(response)
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
@@ -14,13 +14,6 @@ defmodule PinchflatWeb.MediaItemControllerTest do
assert html_response(conn, 200) =~ "#{media_item.title}"
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
describe "edit media" do
@@ -27,7 +27,7 @@ defmodule PinchflatWeb.Sources.MediaItemTableLiveTest do
{:ok, _view, html} = live_isolated(conn, MediaItemTableLive, session: create_session(source))
assert html =~ "Showing"
assert html =~ "Showing 1 of 1"
assert html =~ "Title"
assert html =~ media_item.title
end
+1 -1
View File
@@ -9637,7 +9637,7 @@
"uploader_url": "https://www.youtube.com/@PinchflatTestChannel",
"upload_date": "20210720",
"availability": "public",
"original_url": "https://www.youtube.com/watch?v=ABC123",
"original_url": "ABC123",
"webpage_url_basename": "watch",
"webpage_url_domain": "youtube.com",
"extractor": "youtube",
+2 -3
View File
@@ -23,7 +23,7 @@ defmodule Pinchflat.MediaFixtures do
short_form_content: false,
media_filepath: "/video/#{Faker.File.file_name(:video)}",
source_id: SourcesFixtures.source_fixture().id,
uploaded_at: DateTime.utc_now()
upload_date: DateTime.utc_now()
})
|> Pinchflat.Media.create_media_item()
@@ -97,8 +97,7 @@ defmodule Pinchflat.MediaFixtures do
description: "desc1",
aspect_ratio: 1.67,
duration: 123.45,
upload_date: "20210101",
timestamp: 1_600_000_000
upload_date: "20210101"
}
Phoenix.json_library().encode!(media_attributes)