+ The best available codec will be used if your preferred codecs are not found
+
+
+ <.input
+ id="video_codec_preference_string"
+ name="setting[video_codec_preference_string]"
+ value={Enum.join(f[:video_codec_preference].value, ">")}
+ placeholder="avc>vp9>av01"
+ type="text"
+ label="Video Codec Preference"
+ help="Order of preference for video codecs. Separate with >. Will be remuxed into an MP4 container. See below for available codecs"
+ inputclass="font-mono text-sm mr-4"
+ />
+
+ <.input
+ id="audio_codec_preference_string"
+ name="setting[audio_codec_preference_string]"
+ value={Enum.join(f[:audio_codec_preference].value, ">")}
+ placeholder="mp4a>opus>aac"
+ type="text"
+ label="Audio Codec Preference"
+ help="Order of preference for audio codecs. Separate with >. See below for available codecs"
+ inputclass="font-mono text-sm mr-4"
+ />
+
+
+
+ <.codec_settings_help />
+
+
+
<.button class="mt-10 mb-4 sm:mb-8 w-full sm:w-auto" rounding="rounded-lg">Save Settings
diff --git a/priv/repo/migrations/20240521193719_add_codec_preferences_to_settings.exs b/priv/repo/migrations/20240521193719_add_codec_preferences_to_settings.exs
new file mode 100644
index 0000000..64c34aa
--- /dev/null
+++ b/priv/repo/migrations/20240521193719_add_codec_preferences_to_settings.exs
@@ -0,0 +1,10 @@
+defmodule Pinchflat.Repo.Migrations.AddCodecPreferencesToSettings do
+ use Ecto.Migration
+
+ def change do
+ alter table(:settings) do
+ add :video_codec_preference, {:array, :string}, default: []
+ add :audio_codec_preference, {:array, :string}, default: []
+ end
+ end
+end
diff --git a/test/pinchflat/downloading/codec_parser_test.exs b/test/pinchflat/downloading/codec_parser_test.exs
new file mode 100644
index 0000000..c7b856a
--- /dev/null
+++ b/test/pinchflat/downloading/codec_parser_test.exs
@@ -0,0 +1,70 @@
+defmodule Pinchflat.Downloading.CodecParserTest do
+ use Pinchflat.DataCase
+
+ alias Pinchflat.Settings
+ alias Pinchflat.Downloading.CodecParser
+
+ describe "generate_vcodec_string_from_settings/1" do
+ test "returns a default vcodec string when setting isn't set" do
+ Settings.set(video_codec_preference: [])
+
+ assert "bestvideo[vcodec~='^avc']/bestvideo" == CodecParser.generate_vcodec_string_from_settings()
+ end
+
+ test "generates a vcodec string" do
+ Settings.set(video_codec_preference: ["av01"])
+
+ assert "bestvideo[vcodec~='^av01']/bestvideo" == CodecParser.generate_vcodec_string_from_settings()
+ end
+ end
+
+ describe "generate_acodec_string_from_settings/1" do
+ test "returns a default acodec string when setting isn't set" do
+ Settings.set(audio_codec_preference: [])
+
+ assert "bestaudio[acodec~='^mp4a']/bestaudio" == CodecParser.generate_acodec_string_from_settings()
+ end
+
+ test "generates an acodec string" do
+ Settings.set(audio_codec_preference: ["mp3"])
+
+ assert "bestaudio[acodec~='^mp3']/bestaudio" == CodecParser.generate_acodec_string_from_settings()
+ end
+ end
+
+ describe "generate_vcodec_string/1" do
+ test "returns a default vcodec string when nil" do
+ assert "bestvideo[vcodec~='^avc']/bestvideo" == CodecParser.generate_vcodec_string(nil)
+ end
+
+ test "returns a default vcodec string when empty" do
+ assert "bestvideo[vcodec~='^avc']/bestvideo" == CodecParser.generate_vcodec_string([])
+ end
+
+ test "generates a vcodec string" do
+ assert "bestvideo[vcodec~='^av01']/bestvideo" == CodecParser.generate_vcodec_string(["av01"])
+ end
+
+ test "ignores options that don't exist" do
+ assert "bestvideo[vcodec~='^av01']/bestvideo" == CodecParser.generate_vcodec_string(["av01", "foo"])
+ end
+ end
+
+ describe "generate_acodec_string/1" do
+ test "returns a default acodec string when nil" do
+ assert "bestaudio[acodec~='^mp4a']/bestaudio" == CodecParser.generate_acodec_string(nil)
+ end
+
+ test "returns a default acodec string when empty" do
+ assert "bestaudio[acodec~='^mp4a']/bestaudio" == CodecParser.generate_acodec_string([])
+ end
+
+ test "generates an acodec string" do
+ assert "bestaudio[acodec~='^mp3']/bestaudio" == CodecParser.generate_acodec_string(["mp3"])
+ end
+
+ test "ignores options that don't exist" do
+ assert "bestaudio[acodec~='^mp3']/bestaudio" == CodecParser.generate_acodec_string(["mp3", "foo"])
+ end
+ end
+end
diff --git a/test/pinchflat/downloading/download_option_builder_test.exs b/test/pinchflat/downloading/download_option_builder_test.exs
index 735e72d..a22e11c 100644
--- a/test/pinchflat/downloading/download_option_builder_test.exs
+++ b/test/pinchflat/downloading/download_option_builder_test.exs
@@ -6,6 +6,7 @@ defmodule Pinchflat.Downloading.DownloadOptionBuilderTest do
alias Pinchflat.Sources
alias Pinchflat.Profiles
+ alias Pinchflat.Settings
alias Pinchflat.Utils.FilesystemUtils
alias Pinchflat.Downloading.DownloadOptionBuilder
@@ -244,7 +245,7 @@ defmodule Pinchflat.Downloading.DownloadOptionBuilderTest do
end
describe "build/1 when testing quality options" do
- test "it includes quality options" do
+ test "includes quality options" do
resolutions = ["360", "480", "720", "1080", "2160", "4320"]
Enum.each(resolutions, fn resolution ->
@@ -255,21 +256,35 @@ defmodule Pinchflat.Downloading.DownloadOptionBuilderTest do
media_item = Repo.preload(media_item_fixture(source_id: source.id), source: :media_profile)
assert {:ok, res} = DownloadOptionBuilder.build(media_item)
- assert {:format_sort, "res:#{resolution},+codec:avc:m4a"} in res
+ assert {:format_sort, "res:#{resolution}"} in res
+
+ assert {:format, "((bestvideo[vcodec~='^avc']/bestvideo)+(bestaudio[acodec~='^mp4a']/bestaudio))/best"} in res
+
assert {:remux_video, "mp4"} in res
end)
end
- test "it includes quality options for audio only", %{media_item: media_item} do
+ test "includes quality options for audio only", %{media_item: media_item} do
media_item = update_media_profile_attribute(media_item, %{preferred_resolution: :audio})
assert {:ok, res} = DownloadOptionBuilder.build(media_item)
assert :extract_audio in res
- assert {:format, "bestaudio[ext=m4a]/bestaudio[ext=mp3]/bestaudio/best[ext=m4a]/best[ext=mp3]/best"} in res
+ assert {:format, "bestaudio[acodec~='^mp4a']/bestaudio/best"} in res
refute {:remux_video, "mp4"} in res
end
+
+ test "includes custom quality options if specified", %{media_item: media_item} do
+ Settings.set(video_codec_preference: ["av01"])
+ Settings.set(audio_codec_preference: ["aac"])
+
+ media_item = update_media_profile_attribute(media_item, %{preferred_resolution: :"1080p"})
+
+ assert {:ok, res} = DownloadOptionBuilder.build(media_item)
+
+ assert {:format, "((bestvideo[vcodec~='^av01']/bestvideo)+(bestaudio[acodec~='^aac']/bestaudio))/best"} in res
+ end
end
describe "build/1 when testing sponsorblock options" do
diff --git a/test/pinchflat/settings_test.exs b/test/pinchflat/settings_test.exs
index 944eaf1..3c4d653 100644
--- a/test/pinchflat/settings_test.exs
+++ b/test/pinchflat/settings_test.exs
@@ -78,4 +78,64 @@ defmodule Pinchflat.SettingsTest do
assert %Ecto.Changeset{} = Settings.change_setting(setting, %{onboarding: true})
end
end
+
+ describe "change_setting/2 when testing codec preferences" do
+ test "converts (video|audio)_codec_preference_string to an array" do
+ setting = Settings.record()
+
+ new_setting = %{
+ video_codec_preference_string: "avc>vp9",
+ audio_codec_preference_string: "aac>opus"
+ }
+
+ changeset = Settings.change_setting(setting, new_setting)
+
+ assert ["avc", "vp9"] = changeset.changes.video_codec_preference
+ assert ["aac", "opus"] = changeset.changes.audio_codec_preference
+ end
+
+ test "removes whitespace from (video|audio)_codec_preference" do
+ setting = Settings.record()
+
+ new_setting = %{
+ video_codec_preference_string: " avc > > vp9 ",
+ audio_codec_preference_string: "aac> opus "
+ }
+
+ changeset = Settings.change_setting(setting, new_setting)
+
+ assert ["avc", "vp9"] = changeset.changes.video_codec_preference
+ assert ["aac", "opus"] = changeset.changes.audio_codec_preference
+ end
+
+ test "downcases (video|audio)_codec_preference" do
+ setting = Settings.record()
+
+ new_setting = %{
+ video_codec_preference_string: "AVC>VP9",
+ audio_codec_preference_string: "AAC>OPUS"
+ }
+
+ changeset = Settings.change_setting(setting, new_setting)
+
+ assert ["avc", "vp9"] = changeset.changes.video_codec_preference
+ assert ["aac", "opus"] = changeset.changes.audio_codec_preference
+ end
+
+ test "an empty value will remove the codec settings" do
+ Settings.set(video_codec_preference: ["avc", "vp9"])
+ Settings.set(audio_codec_preference: ["aac", "opus"])
+ setting = Settings.record()
+
+ new_setting = %{
+ video_codec_preference_string: "",
+ audio_codec_preference_string: ""
+ }
+
+ changeset = Settings.change_setting(setting, new_setting)
+
+ assert [] = changeset.changes.video_codec_preference
+ assert [] = changeset.changes.audio_codec_preference
+ end
+ end
end