Added new sleep setting to yt-dlp runner and added tests
This commit is contained in:
parent
e5dea6066d
commit
7f2ec1e802
8 changed files with 165 additions and 24 deletions
|
|
@ -45,5 +45,6 @@ defmodule Pinchflat.Settings.Setting do
|
||||||
setting
|
setting
|
||||||
|> cast(attrs, @allowed_fields)
|
|> cast(attrs, @allowed_fields)
|
||||||
|> validate_required(@required_fields)
|
|> validate_required(@required_fields)
|
||||||
|
|> validate_number(:extractor_sleep_interval, greater_than: 0)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -180,9 +180,12 @@ defmodule Pinchflat.Sources do
|
||||||
end
|
end
|
||||||
|
|
||||||
defp add_source_details_to_changeset(source, changeset) do
|
defp add_source_details_to_changeset(source, changeset) do
|
||||||
|
original_url = changeset.changes.original_url
|
||||||
use_cookies = Ecto.Changeset.get_field(changeset, :use_cookies)
|
use_cookies = Ecto.Changeset.get_field(changeset, :use_cookies)
|
||||||
|
# Skipping sleep interval since this is UI blocking and we want to keep this as fast as possible
|
||||||
|
addl_opts = [use_cookies: use_cookies, skip_sleep_interval: true]
|
||||||
|
|
||||||
case MediaCollection.get_source_details(changeset.changes.original_url, [], use_cookies: use_cookies) do
|
case MediaCollection.get_source_details(original_url, [], addl_opts) do
|
||||||
{:ok, source_details} ->
|
{:ok, source_details} ->
|
||||||
add_source_details_by_collection_type(source, changeset, source_details)
|
add_source_details_by_collection_type(source, changeset, source_details)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -36,4 +36,18 @@ defmodule Pinchflat.Utils.NumberUtils do
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Adds jitter to a number based on a percentage. Returns 0 if the number is less than or equal to 0.
|
||||||
|
|
||||||
|
Returns integer()
|
||||||
|
"""
|
||||||
|
def add_jitter(num, jitter_percentage \\ 0.5)
|
||||||
|
def add_jitter(num, _jitter_percentage) when num <= 0, do: 0
|
||||||
|
|
||||||
|
def add_jitter(num, jitter_percentage) do
|
||||||
|
jitter = :rand.uniform(round(num * jitter_percentage))
|
||||||
|
|
||||||
|
round(num + jitter)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,9 @@ defmodule Pinchflat.YtDlp.CommandRunner do
|
||||||
|
|
||||||
require Logger
|
require Logger
|
||||||
|
|
||||||
|
alias Pinchflat.Settings
|
||||||
alias Pinchflat.Utils.CliUtils
|
alias Pinchflat.Utils.CliUtils
|
||||||
|
alias Pinchflat.Utils.NumberUtils
|
||||||
alias Pinchflat.YtDlp.YtDlpCommandRunner
|
alias Pinchflat.YtDlp.YtDlpCommandRunner
|
||||||
alias Pinchflat.Utils.FilesystemUtils, as: FSUtils
|
alias Pinchflat.Utils.FilesystemUtils, as: FSUtils
|
||||||
|
|
||||||
|
|
@ -22,23 +24,23 @@ defmodule Pinchflat.YtDlp.CommandRunner do
|
||||||
for a file watcher.
|
for a file watcher.
|
||||||
- :use_cookies - if true, will add a cookie file to the command options. Will not
|
- :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.
|
attach a cookie file if the user hasn't set one up.
|
||||||
|
- :skip_sleep_interval - if true, will not add the sleep interval options to the command.
|
||||||
|
Usually only used for commands that would be UI-blocking
|
||||||
|
|
||||||
Returns {:ok, binary()} | {:error, output, status}.
|
Returns {:ok, binary()} | {:error, output, status}.
|
||||||
"""
|
"""
|
||||||
@impl YtDlpCommandRunner
|
@impl YtDlpCommandRunner
|
||||||
def run(url, action_name, command_opts, output_template, addl_opts \\ []) do
|
def run(url, action_name, command_opts, output_template, addl_opts \\ []) do
|
||||||
Logger.debug("Running yt-dlp command for action: #{action_name}")
|
Logger.debug("Running yt-dlp command for action: #{action_name}")
|
||||||
# This approach lets us mock the command for testing
|
|
||||||
command = backend_executable()
|
|
||||||
|
|
||||||
output_filepath = generate_output_filepath(addl_opts)
|
output_filepath = generate_output_filepath(addl_opts)
|
||||||
print_to_file_opts = [{:print_to_file, output_template}, output_filepath]
|
print_to_file_opts = [{:print_to_file, output_template}, output_filepath]
|
||||||
user_configured_opts = cookie_file_options(addl_opts)
|
user_configured_opts = cookie_file_options(addl_opts) ++ sleep_interval_opts(addl_opts)
|
||||||
# These must stay in exactly this order, hence why I'm giving it its own variable.
|
# 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()
|
all_opts = command_opts ++ print_to_file_opts ++ user_configured_opts ++ global_options()
|
||||||
formatted_command_opts = [url] ++ CliUtils.parse_options(all_opts)
|
formatted_command_opts = [url] ++ CliUtils.parse_options(all_opts)
|
||||||
|
|
||||||
case CliUtils.wrap_cmd(command, formatted_command_opts, stderr_to_stdout: true) do
|
case CliUtils.wrap_cmd(backend_executable(), formatted_command_opts, stderr_to_stdout: true) do
|
||||||
# yt-dlp exit codes:
|
# yt-dlp exit codes:
|
||||||
# 0 = Everything is successful
|
# 0 = Everything is successful
|
||||||
# 100 = yt-dlp must restart for update to complete
|
# 100 = yt-dlp must restart for update to complete
|
||||||
|
|
@ -96,6 +98,20 @@ defmodule Pinchflat.YtDlp.CommandRunner do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp sleep_interval_opts(addl_opts) do
|
||||||
|
sleep_interval = Settings.get!(:extractor_sleep_interval)
|
||||||
|
|
||||||
|
if sleep_interval <= 0 || Keyword.get(addl_opts, :skip_sleep_interval) do
|
||||||
|
[]
|
||||||
|
else
|
||||||
|
[
|
||||||
|
sleep_requests: NumberUtils.add_jitter(sleep_interval),
|
||||||
|
sleep_interval: NumberUtils.add_jitter(sleep_interval),
|
||||||
|
sleep_subtitles: NumberUtils.add_jitter(sleep_interval)
|
||||||
|
]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
defp add_cookie_file do
|
defp add_cookie_file do
|
||||||
base_dir = Application.get_env(:pinchflat, :extras_directory)
|
base_dir = Application.get_env(:pinchflat, :extras_directory)
|
||||||
filename_options_map = %{cookies: "cookies.txt"}
|
filename_options_map = %{cookies: "cookies.txt"}
|
||||||
|
|
|
||||||
|
|
@ -77,5 +77,13 @@ defmodule Pinchflat.SettingsTest do
|
||||||
|
|
||||||
assert %Ecto.Changeset{} = Settings.change_setting(setting, %{onboarding: true})
|
assert %Ecto.Changeset{} = Settings.change_setting(setting, %{onboarding: true})
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "ensures the extractor sleep interval is positive" do
|
||||||
|
setting = Settings.record()
|
||||||
|
|
||||||
|
assert %Ecto.Changeset{valid?: true} = Settings.change_setting(setting, %{extractor_sleep_interval: 1})
|
||||||
|
assert %Ecto.Changeset{valid?: true} = Settings.change_setting(setting, %{extractor_sleep_interval: 0})
|
||||||
|
assert %Ecto.Changeset{valid?: false} = Settings.change_setting(setting, %{extractor_sleep_interval: -1})
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -354,7 +354,56 @@ defmodule Pinchflat.SourcesTest do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "create_source/2 when testing options" do
|
describe "create_source/2 when testing yt-dlp options" do
|
||||||
|
test "sets use_cookies to true if the source has been set to use cookies" do
|
||||||
|
expect(YtDlpRunnerMock, :run, fn _url, :get_source_details, _opts, _ot, addl ->
|
||||||
|
assert Keyword.get(addl, :use_cookies)
|
||||||
|
|
||||||
|
{:ok, playlist_return()}
|
||||||
|
end)
|
||||||
|
|
||||||
|
valid_attrs = %{
|
||||||
|
media_profile_id: media_profile_fixture().id,
|
||||||
|
original_url: "https://www.youtube.com/channel/abc123",
|
||||||
|
use_cookies: true
|
||||||
|
}
|
||||||
|
|
||||||
|
assert {:ok, %Source{}} = Sources.create_source(valid_attrs)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "does not set use_cookies to false if the source has not been set to use cookies" do
|
||||||
|
expect(YtDlpRunnerMock, :run, fn _url, :get_source_details, _opts, _ot, addl ->
|
||||||
|
refute Keyword.get(addl, :use_cookies)
|
||||||
|
|
||||||
|
{:ok, playlist_return()}
|
||||||
|
end)
|
||||||
|
|
||||||
|
valid_attrs = %{
|
||||||
|
media_profile_id: media_profile_fixture().id,
|
||||||
|
original_url: "https://www.youtube.com/channel/abc123",
|
||||||
|
use_cookies: false
|
||||||
|
}
|
||||||
|
|
||||||
|
assert {:ok, %Source{}} = Sources.create_source(valid_attrs)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "skips sleep interval" do
|
||||||
|
expect(YtDlpRunnerMock, :run, fn _url, :get_source_details, _opts, _ot, addl ->
|
||||||
|
assert Keyword.get(addl, :skip_sleep_interval)
|
||||||
|
|
||||||
|
{:ok, playlist_return()}
|
||||||
|
end)
|
||||||
|
|
||||||
|
valid_attrs = %{
|
||||||
|
media_profile_id: media_profile_fixture().id,
|
||||||
|
original_url: "https://www.youtube.com/channel/abc123"
|
||||||
|
}
|
||||||
|
|
||||||
|
assert {:ok, %Source{}} = Sources.create_source(valid_attrs)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "create_source/2 when testing its options" do
|
||||||
test "run_post_commit_tasks: false won't enqueue post-commit tasks" do
|
test "run_post_commit_tasks: false won't enqueue post-commit tasks" do
|
||||||
expect(YtDlpRunnerMock, :run, &channel_mock/5)
|
expect(YtDlpRunnerMock, :run, &channel_mock/5)
|
||||||
|
|
||||||
|
|
@ -902,28 +951,30 @@ defmodule Pinchflat.SourcesTest do
|
||||||
end
|
end
|
||||||
|
|
||||||
defp playlist_mock(_url, :get_source_details, _opts, _ot, _addl) do
|
defp playlist_mock(_url, :get_source_details, _opts, _ot, _addl) do
|
||||||
{
|
{:ok, playlist_return()}
|
||||||
:ok,
|
|
||||||
Phoenix.json_library().encode!(%{
|
|
||||||
channel: nil,
|
|
||||||
channel_id: nil,
|
|
||||||
playlist_id: "some_playlist_id_#{:rand.uniform(1_000_000)}",
|
|
||||||
playlist_title: "some playlist name"
|
|
||||||
})
|
|
||||||
}
|
|
||||||
end
|
end
|
||||||
|
|
||||||
defp channel_mock(_url, :get_source_details, _opts, _ot, _addl) do
|
defp channel_mock(_url, :get_source_details, _opts, _ot, _addl) do
|
||||||
|
{:ok, channel_return()}
|
||||||
|
end
|
||||||
|
|
||||||
|
defp playlist_return do
|
||||||
|
Phoenix.json_library().encode!(%{
|
||||||
|
channel: nil,
|
||||||
|
channel_id: nil,
|
||||||
|
playlist_id: "some_playlist_id_#{:rand.uniform(1_000_000)}",
|
||||||
|
playlist_title: "some playlist name"
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
defp channel_return do
|
||||||
channel_id = "some_channel_id_#{:rand.uniform(1_000_000)}"
|
channel_id = "some_channel_id_#{:rand.uniform(1_000_000)}"
|
||||||
|
|
||||||
{
|
Phoenix.json_library().encode!(%{
|
||||||
:ok,
|
channel: "some channel name",
|
||||||
Phoenix.json_library().encode!(%{
|
channel_id: channel_id,
|
||||||
channel: "some channel name",
|
playlist_id: channel_id,
|
||||||
channel_id: channel_id,
|
playlist_title: "some channel name - videos"
|
||||||
playlist_id: channel_id,
|
})
|
||||||
playlist_title: "some channel name - videos"
|
|
||||||
})
|
|
||||||
}
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -47,4 +47,21 @@ defmodule Pinchflat.Utils.NumberUtilsTest do
|
||||||
assert NumberUtils.human_byte_size(nil) == {0, "B"}
|
assert NumberUtils.human_byte_size(nil) == {0, "B"}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe "add_jitter/2" do
|
||||||
|
test "returns 0 when the number is less than or equal to 0" do
|
||||||
|
assert NumberUtils.add_jitter(0) == 0
|
||||||
|
assert NumberUtils.add_jitter(-1) == 0
|
||||||
|
end
|
||||||
|
|
||||||
|
test "returns the number with jitter added" do
|
||||||
|
assert NumberUtils.add_jitter(100) in 100..150
|
||||||
|
end
|
||||||
|
|
||||||
|
test "optionally takes a jitter percentage" do
|
||||||
|
assert NumberUtils.add_jitter(100, 0.1) in 90..110
|
||||||
|
assert NumberUtils.add_jitter(100, 0.5) in 50..150
|
||||||
|
assert NumberUtils.add_jitter(100, 1) in 0..200
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
defmodule Pinchflat.YtDlp.CommandRunnerTest do
|
defmodule Pinchflat.YtDlp.CommandRunnerTest do
|
||||||
use Pinchflat.DataCase
|
use Pinchflat.DataCase
|
||||||
|
|
||||||
|
alias Pinchflat.Settings
|
||||||
alias Pinchflat.Utils.FilesystemUtils
|
alias Pinchflat.Utils.FilesystemUtils
|
||||||
|
|
||||||
alias Pinchflat.YtDlp.CommandRunner, as: Runner
|
alias Pinchflat.YtDlp.CommandRunner, as: Runner
|
||||||
|
|
@ -95,6 +96,36 @@ defmodule Pinchflat.YtDlp.CommandRunnerTest do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe "run/4 when testing sleep interval options" do
|
||||||
|
test "includes sleep interval options by default" do
|
||||||
|
Settings.set(extractor_sleep_interval: 5)
|
||||||
|
|
||||||
|
assert {:ok, output} = Runner.run(@media_url, :foo, [], "")
|
||||||
|
|
||||||
|
assert String.contains?(output, "--sleep-interval")
|
||||||
|
assert String.contains?(output, "--sleep-requests")
|
||||||
|
assert String.contains?(output, "--sleep-subtitles")
|
||||||
|
end
|
||||||
|
|
||||||
|
test "doesn't include sleep interval options when skip_sleep_interval is true" do
|
||||||
|
assert {:ok, output} = Runner.run(@media_url, :foo, [], "", skip_sleep_interval: true)
|
||||||
|
|
||||||
|
refute String.contains?(output, "--sleep-interval")
|
||||||
|
refute String.contains?(output, "--sleep-requests")
|
||||||
|
refute String.contains?(output, "--sleep-subtitles")
|
||||||
|
end
|
||||||
|
|
||||||
|
test "doesn't include sleep interval options when extractor_sleep_interval is 0" do
|
||||||
|
Settings.set(extractor_sleep_interval: 0)
|
||||||
|
|
||||||
|
assert {:ok, output} = Runner.run(@media_url, :foo, [], "")
|
||||||
|
|
||||||
|
refute String.contains?(output, "--sleep-interval")
|
||||||
|
refute String.contains?(output, "--sleep-requests")
|
||||||
|
refute String.contains?(output, "--sleep-subtitles")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe "run/4 when testing global options" do
|
describe "run/4 when testing global options" do
|
||||||
test "creates windows-safe filenames" do
|
test "creates windows-safe filenames" do
|
||||||
assert {:ok, output} = Runner.run(@media_url, :foo, [], "")
|
assert {:ok, output} = Runner.run(@media_url, :foo, [], "")
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue