Compare commits

...

7 Commits

12 changed files with 76 additions and 36 deletions
+4 -4
View File
@@ -53,7 +53,7 @@ If it doesn't work for your use case, please make a feature request! You can als
- Self-contained - just one Docker container with no external dependencies
- Powerful naming system so content is stored where and how you want it
- Easy-to-use web interface with presets to get you started right away
- First-class support for media center apps like Plex, Jellyfin, and Kodi
- First-class support for media center apps like Plex, Jellyfin, and Kodi ([docs](https://github.com/kieraneglin/pinchflat/wiki/Frequently-Asked-Questions#how-do-i-get-media-into-plexjellyfinkodi))
- Supports serving RSS feeds to your favourite podcast app ([docs](https://github.com/kieraneglin/pinchflat/wiki/Podcast-RSS-Feeds))
- Automatically downloads new content from channels and playlists
- Uses a novel approach to download new content more quickly than other apps
@@ -63,7 +63,7 @@ If it doesn't work for your use case, please make a feature request! You can als
- Allows automatically redownloading new media after a set period
- This can help improve the download quality of new content or improve SponsorBlock tags
- Optionally automatically delete old content ([docs](https://github.com/kieraneglin/pinchflat/wiki/Automatically-Delete-Media))
- Advanced options like setting cutoff dates and filtering by title
- Advanced options like setting cutoff dates and filtering by title ([docs](https://github.com/kieraneglin/pinchflat/wiki/Frequently-Asked-Questions#i-only-want-certain-videos-from-a-source---how-can-i-only-download-those))
- Reliable hands-off operation
- Can pass cookies to YouTube to download your private playlists ([docs](https://github.com/kieraneglin/pinchflat/wiki/YouTube-Cookies))
- Sponsorblock integration
@@ -161,9 +161,9 @@ Prior to 2024-05-10, a portion of all donations were given to the [Electronic Fr
The EFF defends your online liberties and [backed](https://github.com/github/dmca/blob/9a85e0f021f7967af80e186b890776a50443f06c/2020/11/2020-11-16-RIAA-reversal-effletter.pdf) `youtube-dl` when Google took them down.
## Pre-release disclaimer
## Stability disclaimer
This is pre-release software and anything can break at any time. I make not guarantees about the stability of this software, forward-compatibility of updates, or integrity (both related to and independent of Pinchflat). Essentially, use at your own risk and expect there will be rough edges for now.
This software is in active development and anything can break at any time. I make no guarantees about the stability of this software, forward-compatibility of updates, or integrity (both related to and independent of Pinchflat).
## License
+3 -2
View File
@@ -79,11 +79,12 @@ if config_env() == :prod do
tmpfile_directory: Path.join([System.tmp_dir!(), "pinchflat", "data"]),
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",
# This is configured in application.ex
timezone: "UTC",
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", "/config/extras/elixir_tz_data")
config :pinchflat, Pinchflat.Repo,
database: db_path,
+19
View File
@@ -4,9 +4,12 @@ defmodule Pinchflat.Application do
@moduledoc false
use Application
require Logger
@impl true
def start(_type, _args) do
check_and_update_timezone()
children = [
PinchflatWeb.Telemetry,
Pinchflat.Repo,
@@ -47,4 +50,20 @@ defmodule Pinchflat.Application do
:ok = Oban.Telemetry.attach_default_logger()
:telemetry.attach_many("job-telemetry-broadcast", events, &PinchflatWeb.Telemetry.job_state_change_broadcast/4, [])
end
# This has to be here (rather than runtime.exs) since the `tzdata` application
# has to be started before we can check the timezone
defp check_and_update_timezone do
attempted_timezone = System.get_env("TIMEZONE") || System.get_env("TZ") || "UTC"
valid_timezone =
if Tzdata.zone_exists?(attempted_timezone) do
attempted_timezone
else
Logger.warning("Invalid timezone #{attempted_timezone}, defaulting to UTC")
"UTC"
end
Application.put_env(:pinchflat, :timezone, valid_timezone)
end
end
+7 -4
View File
@@ -15,6 +15,9 @@ defmodule Pinchflat.Media do
alias Pinchflat.Lifecycle.UserScripts.CommandRunner, as: UserScriptRunner
# Some fields should only be set on insert and not on update.
@fields_to_drop_on_update [:playlist_index]
@doc """
Returns the list of media_items.
@@ -131,8 +134,6 @@ defmodule Pinchflat.Media do
"""
def create_media_item_from_backend_attrs(source, media_attrs_struct) do
attrs = Map.merge(%{source_id: source.id}, Map.from_struct(media_attrs_struct))
# Some fields should only be set on insert and not on update.
fields_to_drop_on_update = [:playlist_index]
%MediaItem{}
|> MediaItem.changeset(attrs)
@@ -140,7 +141,7 @@ defmodule Pinchflat.Media do
on_conflict: [
set:
attrs
|> Map.drop(fields_to_drop_on_update)
|> Map.drop(@fields_to_drop_on_update)
|> Map.to_list()
],
conflict_target: [:source_id, :media_id]
@@ -153,8 +154,10 @@ defmodule Pinchflat.Media do
Returns {:ok, %MediaItem{}} | {:error, %Ecto.Changeset{}}
"""
def update_media_item(%MediaItem{} = media_item, attrs) do
update_attrs = Map.drop(attrs, @fields_to_drop_on_update)
media_item
|> MediaItem.changeset(attrs)
|> MediaItem.changeset(update_attrs)
|> Repo.update()
end
+6 -4
View File
@@ -87,9 +87,11 @@ defmodule Pinchflat.YtDlp.Media do
Returns the output template for yt-dlp's indexing command.
NOTE: playlist_index is really only useful for playlists that will never change their order.
NOTE: I've switched back to `original_url` (from `webpage_url`) since it's started indicating
if something is a short via the URL again
"""
def indexing_output_template do
"%(.{id,title,live_status,webpage_url,description,aspect_ratio,duration,upload_date,timestamp,playlist_index})j"
"%(.{id,title,live_status,original_url,description,aspect_ratio,duration,upload_date,timestamp,playlist_index})j"
end
@doc """
@@ -103,17 +105,17 @@ defmodule Pinchflat.YtDlp.Media do
media_id: response["id"],
title: response["title"],
description: response["description"],
original_url: response["webpage_url"],
original_url: response["original_url"],
livestream: !!response["live_status"] && response["live_status"] != "not_live",
duration_seconds: response["duration"] && round(response["duration"]),
short_form_content: response["webpage_url"] && short_form_content?(response),
short_form_content: response["original_url"] && short_form_content?(response),
uploaded_at: response["upload_date"] && parse_uploaded_at(response),
playlist_index: response["playlist_index"] || 0
}
end
defp short_form_content?(response) do
if String.contains?(response["webpage_url"], "/shorts/") do
if String.contains?(response["original_url"], "/shorts/") do
true
else
# Sometimes shorts are returned without /shorts/ in the URL,
+1 -1
View File
@@ -4,7 +4,7 @@ defmodule Pinchflat.MixProject do
def project do
[
app: :pinchflat,
version: "2024.10.25",
version: "2024.10.30",
elixir: "~> 1.17",
elixirc_paths: elixirc_paths(Mix.env()),
start_permanent: Mix.env() == :prod,
@@ -100,7 +100,7 @@ defmodule Pinchflat.FastIndexing.FastIndexingHelpersTest do
Phoenix.json_library().encode!(%{
id: "video2",
title: "Video 2",
webpage_url: "https://example.com/shorts/video2",
original_url: "https://example.com/shorts/video2",
live_status: "is_live",
description: "desc2",
aspect_ratio: 1.67,
+15
View File
@@ -712,6 +712,21 @@ defmodule Pinchflat.MediaTest do
assert media_item.media_filepath == update_attrs.media_filepath
end
test "updating strips playlist_index from the provided attrs" do
media_item = media_item_fixture(playlist_index: 5)
update_attrs = %{
media_id: Faker.String.base64(12),
title: Faker.Commerce.product_name(),
media_filepath: "/video/#{Faker.File.file_name(:video)}",
source_id: source_fixture().id,
playlist_index: 1
}
assert {:ok, %MediaItem{} = media_item} = Media.update_media_item(media_item, update_attrs)
assert media_item.playlist_index == 5
end
test "updating with invalid data returns error changeset" do
media_item = media_item_fixture()
assert {:error, %Ecto.Changeset{}} = Media.update_media_item(media_item, @invalid_attrs)
@@ -188,7 +188,7 @@ defmodule Pinchflat.SlowIndexing.SlowIndexingHelpersTest do
description: "desc3",
# Only focusing on these because these are passed to functions that
# could fail if they're not present
webpage_url: nil,
original_url: nil,
aspect_ratio: nil,
duration: nil,
upload_date: nil
@@ -299,7 +299,7 @@ defmodule Pinchflat.SlowIndexing.SlowIndexingHelpersTest do
Phoenix.json_library().encode!(%{
id: "video2",
title: "Video 2",
webpage_url: "https://example.com/shorts/video2",
original_url: "https://example.com/shorts/video2",
live_status: "is_live",
description: "desc2",
aspect_ratio: 1.67,
+14 -14
View File
@@ -139,7 +139,7 @@ defmodule Pinchflat.YtDlp.MediaTest do
describe "indexing_output_template/0" do
test "contains all the greatest hits" do
attrs =
~w(id title live_status webpage_url description aspect_ratio duration upload_date timestamp playlist_index)a
~w(id title live_status original_url description aspect_ratio duration upload_date timestamp playlist_index)a
formatted_attrs = "%(.{#{Enum.join(attrs, ",")}})j"
@@ -153,7 +153,7 @@ defmodule Pinchflat.YtDlp.MediaTest do
"id" => "TiZPUDkDYbk",
"title" => "Trying to Wheelie Without the Rear Brake",
"description" => "I'm not sure what I expected.",
"webpage_url" => "https://www.youtube.com/watch?v=TiZPUDkDYbk",
"original_url" => "https://www.youtube.com/watch?v=TiZPUDkDYbk",
"live_status" => "not_live",
"aspect_ratio" => 1.0,
"duration" => 60,
@@ -177,7 +177,7 @@ defmodule Pinchflat.YtDlp.MediaTest do
test "sets short_form_content to true if the URL contains /shorts/" do
response = %{
"webpage_url" => "https://www.youtube.com/shorts/TiZPUDkDYbk",
"original_url" => "https://www.youtube.com/shorts/TiZPUDkDYbk",
"aspect_ratio" => 1.0,
"duration" => 61,
"upload_date" => "20210101"
@@ -188,7 +188,7 @@ defmodule Pinchflat.YtDlp.MediaTest do
test "sets short_form_content to true if the aspect ratio are duration are right" do
response = %{
"webpage_url" => "https://www.youtube.com/watch?v=TiZPUDkDYbk",
"original_url" => "https://www.youtube.com/watch?v=TiZPUDkDYbk",
"aspect_ratio" => 0.5,
"duration" => 59,
"upload_date" => "20210101"
@@ -199,7 +199,7 @@ defmodule Pinchflat.YtDlp.MediaTest do
test "sets short_form_content to false otherwise" do
response = %{
"webpage_url" => "https://www.youtube.com/watch?v=TiZPUDkDYbk",
"original_url" => "https://www.youtube.com/watch?v=TiZPUDkDYbk",
"aspect_ratio" => 1.0,
"duration" => 61,
"upload_date" => "20210101"
@@ -210,7 +210,7 @@ defmodule Pinchflat.YtDlp.MediaTest do
test "doesn't blow up if short form content-related fields are missing" do
response = %{
"webpage_url" => nil,
"original_url" => nil,
"aspect_ratio" => nil,
"duration" => nil,
"upload_date" => "20210101"
@@ -221,7 +221,7 @@ defmodule Pinchflat.YtDlp.MediaTest do
test "parses the duration" do
response = %{
"webpage_url" => "https://www.youtube.com/watch?v=TiZPUDkDYbk",
"original_url" => "https://www.youtube.com/watch?v=TiZPUDkDYbk",
"aspect_ratio" => 1.0,
"duration" => 60.4,
"upload_date" => "20210101"
@@ -232,7 +232,7 @@ defmodule Pinchflat.YtDlp.MediaTest do
test "doesn't blow up if duration is missing" do
response = %{
"webpage_url" => "https://www.youtube.com/watch?v=TiZPUDkDYbk",
"original_url" => "https://www.youtube.com/watch?v=TiZPUDkDYbk",
"aspect_ratio" => 1.0,
"duration" => nil,
"upload_date" => "20210101"
@@ -243,7 +243,7 @@ defmodule Pinchflat.YtDlp.MediaTest do
test "sets livestream to false if the live_status field isn't present" do
response = %{
"webpage_url" => "https://www.youtube.com/watch?v=TiZPUDkDYbk",
"original_url" => "https://www.youtube.com/watch?v=TiZPUDkDYbk",
"aspect_ratio" => 1.0,
"duration" => 60,
"upload_date" => "20210101"
@@ -254,7 +254,7 @@ defmodule Pinchflat.YtDlp.MediaTest do
test "doesn't blow up if playlist_index is missing" do
response = %{
"webpage_url" => "https://www.youtube.com/watch?v=TiZPUDkDYbk",
"original_url" => "https://www.youtube.com/watch?v=TiZPUDkDYbk",
"aspect_ratio" => 1.0,
"duration" => nil,
"upload_date" => "20210101"
@@ -267,7 +267,7 @@ defmodule Pinchflat.YtDlp.MediaTest do
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",
"original_url" => "https://www.youtube.com/watch?v=TiZPUDkDYbk",
"aspect_ratio" => 1.0,
"duration" => 61,
"upload_date" => "20210101",
@@ -281,7 +281,7 @@ defmodule Pinchflat.YtDlp.MediaTest do
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",
"original_url" => "https://www.youtube.com/watch?v=TiZPUDkDYbk",
"aspect_ratio" => 1.0,
"duration" => 61,
"upload_date" => "20210101",
@@ -295,7 +295,7 @@ defmodule Pinchflat.YtDlp.MediaTest do
test "parses the upload date from the uploaded_at if timestamp absent" do
response = %{
"webpage_url" => "https://www.youtube.com/watch?v=TiZPUDkDYbk",
"original_url" => "https://www.youtube.com/watch?v=TiZPUDkDYbk",
"aspect_ratio" => 1.0,
"duration" => 61,
"upload_date" => "20210101"
@@ -308,7 +308,7 @@ defmodule Pinchflat.YtDlp.MediaTest do
test "doesn't blow up if upload date is missing" do
response = %{
"webpage_url" => "https://www.youtube.com/watch?v=TiZPUDkDYbk",
"original_url" => "https://www.youtube.com/watch?v=TiZPUDkDYbk",
"aspect_ratio" => 1.0,
"duration" => 61,
"upload_date" => nil
+1 -1
View File
@@ -95,7 +95,7 @@ defmodule Pinchflat.MediaFixtures do
media_attributes = %{
id: "video1",
title: "Video 1",
webpage_url: "https://example.com/video1",
original_url: "https://example.com/video1",
live_status: "not_live",
description: "desc1",
aspect_ratio: 1.67,
+3 -3
View File
@@ -80,7 +80,7 @@ defmodule Pinchflat.SourcesFixtures do
%{
id: "video1",
title: "Video 1",
webpage_url: "https://example.com/video1",
original_url: "https://example.com/video1",
live_status: "not_live",
description: "desc1",
aspect_ratio: 1.67,
@@ -90,7 +90,7 @@ defmodule Pinchflat.SourcesFixtures do
%{
id: "video2",
title: "Video 2",
webpage_url: "https://example.com/video2",
original_url: "https://example.com/video2",
live_status: "is_live",
description: "desc2",
aspect_ratio: 1.67,
@@ -100,7 +100,7 @@ defmodule Pinchflat.SourcesFixtures do
%{
id: "video3",
title: "Video 3",
webpage_url: "https://example.com/video3",
original_url: "https://example.com/video3",
live_status: "not_live",
description: "desc3",
aspect_ratio: 1.0,