Hooked up the backend portion of the codec preference work
This commit is contained in:
parent
14742d442a
commit
df62c54d61
8 changed files with 177 additions and 47 deletions
|
|
@ -1,5 +1,35 @@
|
||||||
defmodule Pinchflat.Downloading.CodecParser do
|
defmodule Pinchflat.Downloading.CodecParser do
|
||||||
# TODO: test
|
@moduledoc """
|
||||||
|
Functions for generating yt-dlp codec strings
|
||||||
|
"""
|
||||||
|
|
||||||
|
alias Pinchflat.Settings
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Generate a video codec string based on the value of the video_codec_preference setting.
|
||||||
|
|
||||||
|
Returns binary()
|
||||||
|
"""
|
||||||
|
def generate_vcodec_string_from_settings do
|
||||||
|
generate_vcodec_string(Settings.get!(:video_codec_preference))
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Generate an audio codec string based on the value of the audio_codec_preference setting.
|
||||||
|
|
||||||
|
Returns binary()
|
||||||
|
"""
|
||||||
|
def generate_acodec_string_from_settings do
|
||||||
|
generate_acodec_string(Settings.get!(:audio_codec_preference))
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Generate a video codec string from a list of video codecs.
|
||||||
|
|
||||||
|
If the list is nil or empty, the default video codec is AVC.
|
||||||
|
|
||||||
|
Returns binary()
|
||||||
|
"""
|
||||||
def generate_vcodec_string(nil), do: "bestvideo[vcodec~='^avc']/bestvideo"
|
def generate_vcodec_string(nil), do: "bestvideo[vcodec~='^avc']/bestvideo"
|
||||||
def generate_vcodec_string([]), do: generate_vcodec_string(nil)
|
def generate_vcodec_string([]), do: generate_vcodec_string(nil)
|
||||||
|
|
||||||
|
|
@ -8,12 +38,18 @@ defmodule Pinchflat.Downloading.CodecParser do
|
||||||
|> Enum.map(&video_codec_map()[&1])
|
|> Enum.map(&video_codec_map()[&1])
|
||||||
|> Enum.reject(&is_nil/1)
|
|> Enum.reject(&is_nil/1)
|
||||||
|> Enum.map(&"bestvideo[vcodec~='^#{&1}']")
|
|> Enum.map(&"bestvideo[vcodec~='^#{&1}']")
|
||||||
|> Enum.concat(["bestvideo", "best"])
|
|> Enum.concat(["bestvideo"])
|
||||||
|> Enum.join("/")
|
|> Enum.join("/")
|
||||||
end
|
end
|
||||||
|
|
||||||
# TODO: test
|
@doc """
|
||||||
def generate_acodec_string(nil), do: "bestaudio[acodec~='^mp4a']/bestaudio[acodec~='^mp3']/bestaudio"
|
Generate an audio codec string from a list of audio codecs.
|
||||||
|
|
||||||
|
If the list is nil or empty, the default audio codec is MP4A.
|
||||||
|
|
||||||
|
Returns binary()
|
||||||
|
"""
|
||||||
|
def generate_acodec_string(nil), do: "bestaudio[acodec~='^mp4a']/bestaudio"
|
||||||
def generate_acodec_string([]), do: generate_acodec_string(nil)
|
def generate_acodec_string([]), do: generate_acodec_string(nil)
|
||||||
|
|
||||||
def generate_acodec_string(audio_codecs) do
|
def generate_acodec_string(audio_codecs) do
|
||||||
|
|
@ -21,7 +57,7 @@ defmodule Pinchflat.Downloading.CodecParser do
|
||||||
|> Enum.map(&audio_codec_map()[&1])
|
|> Enum.map(&audio_codec_map()[&1])
|
||||||
|> Enum.reject(&is_nil/1)
|
|> Enum.reject(&is_nil/1)
|
||||||
|> Enum.map(&"bestaudio[acodec~='^#{&1}']")
|
|> Enum.map(&"bestaudio[acodec~='^#{&1}']")
|
||||||
|> Enum.concat(["bestaudio", "best"])
|
|> Enum.concat(["bestaudio"])
|
||||||
|> Enum.join("/")
|
|> Enum.join("/")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -35,15 +71,10 @@ defmodule Pinchflat.Downloading.CodecParser do
|
||||||
|
|
||||||
defp audio_codec_map do
|
defp audio_codec_map do
|
||||||
%{
|
%{
|
||||||
"flac" => "flac",
|
|
||||||
"alac" => "alac",
|
|
||||||
"wav" => "wav",
|
|
||||||
"aiff" => "aiff",
|
|
||||||
"aac" => "aac",
|
"aac" => "aac",
|
||||||
"mp4a" => "mp4a",
|
"mp4a" => "mp4a",
|
||||||
"mp3" => "mp3",
|
"mp3" => "mp3",
|
||||||
"opus" => "opus",
|
"opus" => "opus"
|
||||||
"vorbis" => "vorbis"
|
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -121,38 +121,27 @@ defmodule Pinchflat.Downloading.DownloadOptionBuilder do
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
# TODO: test
|
|
||||||
defp quality_options(media_profile) do
|
defp quality_options(media_profile) do
|
||||||
vcodec_string = CodecParser.generate_vcodec_string(["vp9", "av01"])
|
vcodec_string = CodecParser.generate_vcodec_string_from_settings()
|
||||||
acodec_string = CodecParser.generate_acodec_string(["opus", "aac", "mp4a"])
|
acodec_string = CodecParser.generate_acodec_string_from_settings()
|
||||||
|
|
||||||
video_codec_option = fn res ->
|
|
||||||
[
|
|
||||||
format_sort: "res:#{res}",
|
|
||||||
# Since Plex doesn't support reading metadata from MKV
|
|
||||||
remux_video: "mp4",
|
|
||||||
format: "((#{vcodec_string})+(#{acodec_string}))/best"
|
|
||||||
]
|
|
||||||
end
|
|
||||||
|
|
||||||
# audio_format_precedence = [
|
|
||||||
# "bestaudio[ext=m4a]",
|
|
||||||
# "bestaudio[ext=mp3]",
|
|
||||||
# "bestaudio",
|
|
||||||
# "best[ext=m4a]",
|
|
||||||
# "best[ext=mp3]",
|
|
||||||
# "best"
|
|
||||||
# ]
|
|
||||||
|
|
||||||
case media_profile.preferred_resolution do
|
case media_profile.preferred_resolution do
|
||||||
# Also be aware that :audio disabled all embedding options for subtitles
|
# Also be aware that :audio disabled all embedding options for subtitles
|
||||||
:audio -> [:extract_audio, format: "#{acodec_string}/best"]
|
:audio ->
|
||||||
:"360p" -> video_codec_option.("360")
|
[:extract_audio, format: "#{acodec_string}/best"]
|
||||||
:"480p" -> video_codec_option.("480")
|
|
||||||
:"720p" -> video_codec_option.("720")
|
resolution_atom ->
|
||||||
:"1080p" -> video_codec_option.("1080")
|
{resolution_string, _} =
|
||||||
:"2160p" -> video_codec_option.("2160")
|
resolution_atom
|
||||||
:"4320p" -> video_codec_option.("4320")
|
|> Atom.to_string()
|
||||||
|
|> Integer.parse()
|
||||||
|
|
||||||
|
[
|
||||||
|
format_sort: "res:#{resolution_string}",
|
||||||
|
# Since Plex doesn't support reading metadata from MKV
|
||||||
|
remux_video: "mp4",
|
||||||
|
format: "((#{vcodec_string})+(#{acodec_string}))/best"
|
||||||
|
]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,6 @@ defmodule Pinchflat.Settings.Setting do
|
||||||
|> validate_required(@required_fields)
|
|> validate_required(@required_fields)
|
||||||
end
|
end
|
||||||
|
|
||||||
# TODO: test
|
|
||||||
defp convert_codec_preference_strings(changeset) do
|
defp convert_codec_preference_strings(changeset) do
|
||||||
fields = [
|
fields = [
|
||||||
video_codec_preference_string: :video_codec_preference,
|
video_codec_preference_string: :video_codec_preference,
|
||||||
|
|
|
||||||
|
|
@ -15,11 +15,10 @@ defmodule PinchflatWeb.MediaItems.MediaItemHTML do
|
||||||
!!media_item.media_filepath and File.exists?(media_item.media_filepath)
|
!!media_item.media_filepath and File.exists?(media_item.media_filepath)
|
||||||
end
|
end
|
||||||
|
|
||||||
# TODO: update for new format types
|
|
||||||
def media_type(media_item) do
|
def media_type(media_item) do
|
||||||
case Path.extname(media_item.media_filepath) do
|
case Path.extname(media_item.media_filepath) do
|
||||||
ext when ext in [".mp4", ".webm", ".mkv"] -> :video
|
ext when ext in [".mp4", ".webm", ".mkv"] -> :video
|
||||||
ext when ext in [".mp3", ".m4a"] -> :audio
|
ext when ext in [".mp3", ".m4a", ".opus"] -> :audio
|
||||||
_ -> :unknown
|
_ -> :unknown
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,6 @@ defmodule PinchflatWeb.Settings.SettingController do
|
||||||
|
|
||||||
def update(conn, %{"setting" => setting_params}) do
|
def update(conn, %{"setting" => setting_params}) do
|
||||||
setting = Settings.record()
|
setting = Settings.record()
|
||||||
IO.inspect(setting_params)
|
|
||||||
|
|
||||||
case Settings.update_setting(setting, setting_params) do
|
case Settings.update_setting(setting, setting_params) do
|
||||||
{:ok, _} ->
|
{:ok, _} ->
|
||||||
|
|
|
||||||
70
test/pinchflat/downloading/codec_parser_test.exs
Normal file
70
test/pinchflat/downloading/codec_parser_test.exs
Normal file
|
|
@ -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
|
||||||
|
|
@ -6,6 +6,7 @@ defmodule Pinchflat.Downloading.DownloadOptionBuilderTest do
|
||||||
|
|
||||||
alias Pinchflat.Sources
|
alias Pinchflat.Sources
|
||||||
alias Pinchflat.Profiles
|
alias Pinchflat.Profiles
|
||||||
|
alias Pinchflat.Settings
|
||||||
alias Pinchflat.Utils.FilesystemUtils
|
alias Pinchflat.Utils.FilesystemUtils
|
||||||
alias Pinchflat.Downloading.DownloadOptionBuilder
|
alias Pinchflat.Downloading.DownloadOptionBuilder
|
||||||
|
|
||||||
|
|
@ -244,7 +245,7 @@ defmodule Pinchflat.Downloading.DownloadOptionBuilderTest do
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "build/1 when testing quality options" do
|
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"]
|
resolutions = ["360", "480", "720", "1080", "2160", "4320"]
|
||||||
|
|
||||||
Enum.each(resolutions, fn resolution ->
|
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)
|
media_item = Repo.preload(media_item_fixture(source_id: source.id), source: :media_profile)
|
||||||
|
|
||||||
assert {:ok, res} = DownloadOptionBuilder.build(media_item)
|
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
|
assert {:remux_video, "mp4"} in res
|
||||||
end)
|
end)
|
||||||
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})
|
media_item = update_media_profile_attribute(media_item, %{preferred_resolution: :audio})
|
||||||
|
|
||||||
assert {:ok, res} = DownloadOptionBuilder.build(media_item)
|
assert {:ok, res} = DownloadOptionBuilder.build(media_item)
|
||||||
|
|
||||||
assert :extract_audio in res
|
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
|
refute {:remux_video, "mp4"} in res
|
||||||
end
|
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
|
end
|
||||||
|
|
||||||
describe "build/1 when testing sponsorblock options" do
|
describe "build/1 when testing sponsorblock options" do
|
||||||
|
|
|
||||||
|
|
@ -77,5 +77,33 @@ 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 "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
|
||||||
|
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
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue