[Enhancement] Use cookies on a per-source basis (#354)
* Added 'use_cookies' column to source * Added cookies to the source form * Updated every command to optionally include cookies * LOTS of tests * Made YT cookies off by default
This commit is contained in:
@@ -106,8 +106,9 @@ defmodule Pinchflat.Downloading.MediaDownloader do
|
||||
|
||||
defp download_with_options(url, item_with_preloads, output_filepath, override_opts) do
|
||||
{:ok, options} = DownloadOptionBuilder.build(item_with_preloads, override_opts)
|
||||
runner_opts = [output_filepath: output_filepath, use_cookies: item_with_preloads.source.use_cookies]
|
||||
|
||||
YtDlpMedia.download(url, options, output_filepath: output_filepath)
|
||||
YtDlpMedia.download(url, options, runner_opts)
|
||||
end
|
||||
|
||||
defp recoverable_errors do
|
||||
|
||||
@@ -68,7 +68,7 @@ defmodule Pinchflat.FastIndexing.FastIndexingHelpers do
|
||||
defp create_media_item_from_media_id(source, media_id) do
|
||||
url = "https://www.youtube.com/watch?v=#{media_id}"
|
||||
|
||||
case YtDlpMedia.get_media_attributes(url) do
|
||||
case YtDlpMedia.get_media_attributes(url, use_cookies: source.use_cookies) do
|
||||
{:ok, media_attrs} ->
|
||||
Media.create_media_item_from_backend_attrs(source, media_attrs)
|
||||
|
||||
|
||||
@@ -62,11 +62,13 @@ defmodule Pinchflat.Metadata.MetadataFileHelpers do
|
||||
|
||||
Returns binary() | nil
|
||||
"""
|
||||
def download_and_store_thumbnail_for(database_record) do
|
||||
yt_dlp_filepath = generate_filepath_for(database_record, "thumbnail.%(ext)s")
|
||||
real_filepath = generate_filepath_for(database_record, "thumbnail.jpg")
|
||||
def download_and_store_thumbnail_for(media_item_with_preloads) do
|
||||
yt_dlp_filepath = generate_filepath_for(media_item_with_preloads, "thumbnail.%(ext)s")
|
||||
real_filepath = generate_filepath_for(media_item_with_preloads, "thumbnail.jpg")
|
||||
command_opts = [output: yt_dlp_filepath]
|
||||
addl_opts = [use_cookies: media_item_with_preloads.source.use_cookies]
|
||||
|
||||
case YtDlpMedia.download_thumbnail(database_record.original_url, output: yt_dlp_filepath) do
|
||||
case YtDlpMedia.download_thumbnail(media_item_with_preloads.original_url, command_opts, addl_opts) do
|
||||
{:ok, _} -> real_filepath
|
||||
_ -> nil
|
||||
end
|
||||
|
||||
@@ -92,7 +92,9 @@ defmodule Pinchflat.Metadata.SourceMetadataStorageWorker do
|
||||
|
||||
defp determine_series_directory(source) do
|
||||
output_path = DownloadOptionBuilder.build_output_path_for(source)
|
||||
{:ok, %{filepath: filepath}} = MediaCollection.get_source_details(source.original_url, output: output_path)
|
||||
runner_opts = [output: output_path]
|
||||
addl_opts = [use_cookies: source.use_cookies]
|
||||
{:ok, %{filepath: filepath}} = MediaCollection.get_source_details(source.original_url, runner_opts, addl_opts)
|
||||
|
||||
case MetadataFileHelpers.series_directory_from_media_filepath(filepath) do
|
||||
{:ok, series_directory} -> series_directory
|
||||
@@ -119,7 +121,7 @@ defmodule Pinchflat.Metadata.SourceMetadataStorageWorker do
|
||||
base_opts ++ [:write_thumbnail, playlist_items: 1]
|
||||
end
|
||||
|
||||
MediaCollection.get_source_metadata(source.original_url, opts)
|
||||
MediaCollection.get_source_metadata(source.original_url, opts, use_cookies: source.use_cookies)
|
||||
end
|
||||
|
||||
defp tmp_directory do
|
||||
|
||||
@@ -94,7 +94,8 @@ defmodule Pinchflat.SlowIndexing.SlowIndexingHelpers do
|
||||
{:ok, pid} = FileFollowerServer.start_link()
|
||||
|
||||
handler = fn filepath -> setup_file_follower_watcher(pid, filepath, source) end
|
||||
result = MediaCollection.get_media_attributes_for_collection(source.original_url, file_listener_handler: handler)
|
||||
runner_opts = [file_listener_handler: handler, use_cookies: source.use_cookies]
|
||||
result = MediaCollection.get_media_attributes_for_collection(source.original_url, runner_opts)
|
||||
|
||||
FileFollowerServer.stop(pid)
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@ defmodule Pinchflat.Sources.Source do
|
||||
series_directory
|
||||
index_frequency_minutes
|
||||
fast_index
|
||||
use_cookies
|
||||
download_media
|
||||
last_indexed_at
|
||||
original_url
|
||||
@@ -73,6 +74,7 @@ defmodule Pinchflat.Sources.Source do
|
||||
field :collection_type, Ecto.Enum, values: [:channel, :playlist]
|
||||
field :index_frequency_minutes, :integer, default: 60 * 24
|
||||
field :fast_index, :boolean, default: false
|
||||
field :use_cookies, :boolean, default: false
|
||||
field :download_media, :boolean, default: true
|
||||
field :last_indexed_at, :utc_datetime
|
||||
# Only download media items that were published after this date
|
||||
|
||||
@@ -180,7 +180,9 @@ defmodule Pinchflat.Sources do
|
||||
end
|
||||
|
||||
defp add_source_details_to_changeset(source, changeset) do
|
||||
case MediaCollection.get_source_details(changeset.changes.original_url) do
|
||||
use_cookies = Ecto.Changeset.get_field(changeset, :use_cookies)
|
||||
|
||||
case MediaCollection.get_source_details(changeset.changes.original_url, [], use_cookies: use_cookies) do
|
||||
{:ok, source_details} ->
|
||||
add_source_details_by_collection_type(source, changeset, source_details)
|
||||
|
||||
|
||||
@@ -18,6 +18,8 @@ defmodule Pinchflat.YtDlp.CommandRunner do
|
||||
- :output_filepath - the path to save the output to. If not provided, a temporary
|
||||
file will be created and used. Useful for if you need a reference to the file
|
||||
for a file watcher.
|
||||
- :use_cookies - if true, will add a cookie file to the command options. Will not
|
||||
attach a cookie file if the user hasn't set one up.
|
||||
|
||||
Returns {:ok, binary()} | {:error, output, status}.
|
||||
"""
|
||||
@@ -28,7 +30,7 @@ defmodule Pinchflat.YtDlp.CommandRunner do
|
||||
|
||||
output_filepath = generate_output_filepath(addl_opts)
|
||||
print_to_file_opts = [{:print_to_file, output_template}, output_filepath]
|
||||
user_configured_opts = cookie_file_options()
|
||||
user_configured_opts = cookie_file_options(addl_opts)
|
||||
# These must stay in exactly this order, hence why I'm giving it its own variable.
|
||||
all_opts = command_opts ++ print_to_file_opts ++ user_configured_opts ++ global_options()
|
||||
formatted_command_opts = [url] ++ CliUtils.parse_options(all_opts)
|
||||
@@ -78,7 +80,14 @@ defmodule Pinchflat.YtDlp.CommandRunner do
|
||||
]
|
||||
end
|
||||
|
||||
defp cookie_file_options do
|
||||
defp cookie_file_options(addl_opts) do
|
||||
case Keyword.get(addl_opts, :use_cookies) do
|
||||
true -> add_cookie_file()
|
||||
_ -> []
|
||||
end
|
||||
end
|
||||
|
||||
defp add_cookie_file do
|
||||
base_dir = Application.get_env(:pinchflat, :extras_directory)
|
||||
filename_options_map = %{cookies: "cookies.txt"}
|
||||
|
||||
|
||||
@@ -37,9 +37,9 @@ defmodule Pinchflat.YtDlp.Media do
|
||||
Returns {:ok, map()} | {:error, any, ...}.
|
||||
"""
|
||||
def download(url, command_opts \\ [], addl_opts \\ []) do
|
||||
opts = [:no_simulate] ++ command_opts
|
||||
all_command_opts = [:no_simulate] ++ command_opts
|
||||
|
||||
with {:ok, output} <- backend_runner().run(url, opts, "after_move:%()j", addl_opts),
|
||||
with {:ok, output} <- backend_runner().run(url, all_command_opts, "after_move:%()j", addl_opts),
|
||||
{:ok, parsed_json} <- Phoenix.json_library().decode(output) do
|
||||
{:ok, parsed_json}
|
||||
else
|
||||
@@ -53,12 +53,12 @@ defmodule Pinchflat.YtDlp.Media do
|
||||
|
||||
Returns {:ok, ""} | {:error, any, ...}.
|
||||
"""
|
||||
def download_thumbnail(url, command_opts \\ []) do
|
||||
opts = [:no_simulate, :skip_download, :write_thumbnail, convert_thumbnail: "jpg"] ++ command_opts
|
||||
def download_thumbnail(url, command_opts \\ [], addl_opts \\ []) do
|
||||
all_command_opts = [:no_simulate, :skip_download, :write_thumbnail, convert_thumbnail: "jpg"] ++ command_opts
|
||||
|
||||
# NOTE: it doesn't seem like this command actually returns anything in `after_move` since
|
||||
# we aren't downloading the main media file
|
||||
backend_runner().run(url, opts, "after_move:%()j")
|
||||
backend_runner().run(url, all_command_opts, "after_move:%()j", addl_opts)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -66,12 +66,12 @@ defmodule Pinchflat.YtDlp.Media do
|
||||
|
||||
Returns {:ok, %Media{}} | {:error, any, ...}.
|
||||
"""
|
||||
def get_media_attributes(url) do
|
||||
def get_media_attributes(url, addl_opts \\ []) do
|
||||
runner = Application.get_env(:pinchflat, :yt_dlp_runner)
|
||||
command_opts = [:simulate, :skip_download]
|
||||
output_template = indexing_output_template()
|
||||
|
||||
case runner.run(url, command_opts, output_template) do
|
||||
case runner.run(url, command_opts, output_template, addl_opts) do
|
||||
{:ok, output} ->
|
||||
output
|
||||
|> Phoenix.json_library().decode!()
|
||||
|
||||
@@ -25,15 +25,17 @@ defmodule Pinchflat.YtDlp.MediaCollection do
|
||||
# the first video has not released yet (ie: is a premier). We don't care about
|
||||
# available formats since we're just getting the media details
|
||||
command_opts = [:simulate, :skip_download, :ignore_no_formats_error, :no_warnings]
|
||||
use_cookies = Keyword.get(addl_opts, :use_cookies, false)
|
||||
output_template = YtDlpMedia.indexing_output_template()
|
||||
output_filepath = FilesystemUtils.generate_metadata_tmpfile(:json)
|
||||
file_listener_handler = Keyword.get(addl_opts, :file_listener_handler, false)
|
||||
runner_opts = [output_filepath: output_filepath, use_cookies: use_cookies]
|
||||
|
||||
if file_listener_handler do
|
||||
file_listener_handler.(output_filepath)
|
||||
end
|
||||
|
||||
case runner.run(url, command_opts, output_template, output_filepath: output_filepath) do
|
||||
case runner.run(url, command_opts, output_template, runner_opts) do
|
||||
{:ok, output} ->
|
||||
parsed_lines =
|
||||
output
|
||||
@@ -64,7 +66,7 @@ defmodule Pinchflat.YtDlp.MediaCollection do
|
||||
|
||||
Returns {:ok, map()} | {:error, any, ...}.
|
||||
"""
|
||||
def get_source_details(source_url, addl_opts \\ []) do
|
||||
def get_source_details(source_url, command_opts \\ [], addl_opts \\ []) do
|
||||
# `ignore_no_formats_error` is necessary because yt-dlp will error out if
|
||||
# the first video has not released yet (ie: is a premier). We don't care about
|
||||
# available formats since we're just getting the source details
|
||||
@@ -75,10 +77,10 @@ defmodule Pinchflat.YtDlp.MediaCollection do
|
||||
playlist_end: 1
|
||||
]
|
||||
|
||||
command_opts = default_opts ++ addl_opts
|
||||
all_command_opts = default_opts ++ command_opts
|
||||
output_template = "%(.{channel,channel_id,playlist_id,playlist_title,filename})j"
|
||||
|
||||
with {:ok, output} <- backend_runner().run(source_url, command_opts, output_template),
|
||||
with {:ok, output} <- backend_runner().run(source_url, all_command_opts, output_template, addl_opts),
|
||||
{:ok, parsed_json} <- Phoenix.json_library().decode(output) do
|
||||
{:ok, format_source_details(parsed_json)}
|
||||
else
|
||||
@@ -109,14 +111,14 @@ defmodule Pinchflat.YtDlp.MediaCollection do
|
||||
|
||||
Returns {:ok, map()} | {:error, any, ...}.
|
||||
"""
|
||||
def get_source_metadata(source_url, addl_opts \\ [playlist_items: 0]) do
|
||||
def get_source_metadata(source_url, command_opts, addl_opts \\ []) do
|
||||
# This only validates that the `playlist_items` key is present. It's otherwise unused
|
||||
_playlist_items = Keyword.fetch!(addl_opts, :playlist_items)
|
||||
_playlist_items = Keyword.fetch!(command_opts, :playlist_items)
|
||||
|
||||
opts = [:skip_download] ++ addl_opts
|
||||
all_command_opts = [:skip_download] ++ command_opts
|
||||
output_template = "playlist:%()j"
|
||||
|
||||
with {:ok, output} <- backend_runner().run(source_url, opts, output_template),
|
||||
with {:ok, output} <- backend_runner().run(source_url, all_command_opts, output_template, addl_opts),
|
||||
{:ok, parsed_json} <- Phoenix.json_library().decode(output) do
|
||||
{:ok, parsed_json}
|
||||
else
|
||||
|
||||
Reference in New Issue
Block a user