diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..663a121 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,51 @@ +name: Elixir ci + +on: + push: + branches: + - master + pull_request: + +env: + MIX_ENV: test + +jobs: + test: + runs-on: ubuntu-latest + + strategy: + matrix: + include: + - otp_version: 25.3 + elixir_version: 1.16 + + - otp_version: 27.2 + elixir_version: 1.18 + + steps: + - uses: actions/checkout@v3 + + - uses: erlef/setup-beam@v1 + with: + elixir-version: ${{ matrix.elixir_version }} + otp-version: ${{ matrix.otp_version }} + + - name: Install dependencies + run: mix deps.get + - name: Compile dependencies + run: mix deps.compile + - name: Run tests + run: mix test --warnings-as-errors + - name: Run Credo + run: mix credo --strict + + format: + runs-on: ubuntu-latest + name: mix format + steps: + - uses: actions/checkout@v4 + - uses: erlef/setup-beam@v1 + with: + otp-version: 27.2 + elixir-version: 1.18 + - run: mix format --check-formatted diff --git a/lib/hlx/track.ex b/lib/hlx/track.ex index 2024dc2..3411aeb 100644 --- a/lib/hlx/track.ex +++ b/lib/hlx/track.ex @@ -18,8 +18,8 @@ defmodule HLX.Track do """ alias ExMP4.Box - alias MediaCodecs.{MPEG4, H264, H265} alias MediaCodecs.AV1.OBU + alias MediaCodecs.{H264, H265, MPEG4} @codecs [:h264, :h265, :hevc, :aac, :av1] diff --git a/lib/hlx/writer.ex b/lib/hlx/writer.ex index e2dc0f0..190510b 100644 --- a/lib/hlx/writer.ex +++ b/lib/hlx/writer.ex @@ -177,7 +177,7 @@ defmodule HLX.Writer do queues = Map.new(independant_variants, fn variant -> - extra_variants = if variant.id == lead_variant_id, do: dependant_variants, else: [] + extra_variants = (variant.id == lead_variant_id && dependant_variants) || [] {variant.id, create_queues(writer, variant, extra_variants)} end) @@ -266,6 +266,7 @@ defmodule HLX.Writer do end defp do_add_variant(%{config: config} = writer, name, options) do + # credo:disable-for-next-line # TODO: validate options muxer_options = [segment_type: config[:segment_type]] @@ -449,10 +450,9 @@ defmodule HLX.Writer do rendition_reports = if preload_hint? do - Enum.reduce(rendition_reports, [], fn - %{uri: ^id}, acc -> acc - report, acc -> [%{report | uri: "#{report.uri}.m3u8"} | acc] - end) + rendition_reports + |> Enum.reject(&(&1.uri == id)) + |> Enum.map(&%{&1 | uri: &1.uri <> ".m3u8"}) else [] end diff --git a/lib/hlx/writer/variant.ex b/lib/hlx/writer/variant.ex index 7496be7..a82da38 100644 --- a/lib/hlx/writer/variant.ex +++ b/lib/hlx/writer/variant.ex @@ -172,37 +172,8 @@ defmodule HLX.Writer.Variant do _ -> referenced_renditions = Map.values(referenced_renditions) - - referenced_codecs = - referenced_renditions - |> List.flatten() - |> Stream.flat_map(&TracksMuxer.tracks(&1.tracks_muxer)) - |> Enum.map(& &1.mime) - - tracks = TracksMuxer.tracks(variant.tracks_muxer) - - codecs = - tracks - |> Stream.map(& &1.mime) - |> Stream.concat(referenced_codecs) - |> Stream.uniq() - |> Enum.join(",") - - resolution = - Enum.find_value(tracks, fn - %{width: nil} -> nil - %{width: width, height: height} -> {width, height} - end) - - {avg_bitrates, max_bitrates} = - Stream.map(referenced_renditions, fn variants -> - variants - |> Stream.map(&bandwidth/1) - |> Enum.unzip() - |> then(fn {a, m} -> {Enum.max(a), Enum.max(m)} end) - end) - |> Enum.unzip() - + {codecs, resolution} = codecs_resolution(variant, referenced_renditions) + {avg_bitrates, max_bitrates} = avg_max_bandwidths(referenced_renditions) {avg_band, max_band} = bandwidth(variant) %{ @@ -239,6 +210,16 @@ defmodule HLX.Writer.Variant do } end + defp avg_max_bandwidths(renditions) do + Stream.map(renditions, fn variants -> + variants + |> Stream.map(&bandwidth/1) + |> Enum.unzip() + |> then(fn {a, m} -> {Enum.max(a), Enum.max(m)} end) + end) + |> Enum.unzip() + end + defp bandwidth(%{playlist: playlist}), do: MediaPlaylist.bandwidth(playlist) defp calculate_timestamp(%{base_timestamp: nil}), do: nil @@ -255,4 +236,29 @@ defmodule HLX.Writer.Variant do duration = div((first_dts - base_dts) * 1000, timescale) DateTime.from_unix!(variant.base_timestamp + duration, :millisecond) end + + defp codecs_resolution(variant, referenced_renditions) do + referenced_codecs = + referenced_renditions + |> List.flatten() + |> Stream.flat_map(&TracksMuxer.tracks(&1.tracks_muxer)) + |> Enum.map(& &1.mime) + + tracks = TracksMuxer.tracks(variant.tracks_muxer) + + codecs = + tracks + |> Stream.map(& &1.mime) + |> Stream.concat(referenced_codecs) + |> Stream.uniq() + |> Enum.join(",") + + resolution = + Enum.find_value(tracks, fn + %{width: nil} -> nil + %{width: width, height: height} -> {width, height} + end) + + {codecs, resolution} + end end diff --git a/mix.exs b/mix.exs index 213c21c..9849ef9 100644 --- a/mix.exs +++ b/mix.exs @@ -8,7 +8,7 @@ defmodule HLX.MixProject do [ app: :hlx, version: @version, - elixir: "~> 1.15", + elixir: "~> 1.16", start_permanent: Mix.env() == :prod, deps: deps(), @@ -36,7 +36,8 @@ defmodule HLX.MixProject do {:ex_m3u8, "~> 0.15.0"}, {:mpeg_ts, "~> 3.3.5"}, {:qex, "~> 0.5.1"}, - {:ex_doc, ">= 0.0.0", only: :dev, runtime: false} + {:ex_doc, ">= 0.0.0", only: :dev, runtime: false}, + {:credo, "~> 1.7", only: [:dev, :test], runtime: false} ] end diff --git a/mix.lock b/mix.lock index d60ef4d..558c70d 100644 --- a/mix.lock +++ b/mix.lock @@ -1,10 +1,14 @@ %{ "bunch": {:hex, :bunch, "1.6.1", "5393d827a64d5f846092703441ea50e65bc09f37fd8e320878f13e63d410aec7", [:mix], [], "hexpm", "286cc3add551628b30605efbe2fca4e38cc1bea89bcd0a1a7226920b3364fe4a"}, + "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, "coerce": {:hex, :coerce, "1.0.2", "5ef791040c92baaa5dd344887563faaeac6e6742573a167493294f8af3672bbe", [:mix], [], "hexpm", "0b3451c729571234fdac478636c298e71d1f2ce1243abed5fa43fa3181b980eb"}, + "credo": {:hex, :credo, "1.7.15", "283da72eeb2fd3ccf7248f4941a0527efb97afa224bcdef30b4b580bc8258e1c", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "291e8645ea3fea7481829f1e1eb0881b8395db212821338e577a90bf225c5607"}, "earmark_parser": {:hex, :earmark_parser, "1.4.44", "f20830dd6b5c77afe2b063777ddbbff09f9759396500cdbe7523efd58d7a339c", [:mix], [], "hexpm", "4778ac752b4701a5599215f7030989c989ffdc4f6df457c5f36938cc2d2a2750"}, "ex_doc": {:hex, :ex_doc, "0.39.3", "519c6bc7e84a2918b737aec7ef48b96aa4698342927d080437f61395d361dcee", [:mix], [{:earmark_parser, "~> 1.4.44", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.0", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1 or ~> 1.0", [hex: :makeup_erlang, repo: "hexpm", optional: false]}, {:makeup_html, ">= 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: true]}], "hexpm", "0590955cf7ad3b625780ee1c1ea627c28a78948c6c0a9b0322bd976a079996e1"}, "ex_m3u8": {:hex, :ex_m3u8, "0.15.4", "66f6ec7e4fb7372c48032db1c2d4a3e6c2bbbde2d1d9a1098986e3caa0ab7a55", [:mix], [{:nimble_parsec, "~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}, {:typed_struct, "~> 0.3.0", [hex: :typed_struct, repo: "hexpm", optional: false]}], "hexpm", "ec03aa516919e0c8ec202da55f609b763bd7960195a3388900090fcad270c873"}, "ex_mp4": {:hex, :ex_mp4, "0.14.1", "6152b9b981d5d6d5069d8dccd3d60f4f4be135f758f9b4bae039018119fdf089", [:mix], [{:media_codecs, "~> 0.10.0", [hex: :media_codecs, repo: "hexpm", optional: true]}, {:ratio, "~> 4.0", [hex: :ratio, repo: "hexpm", optional: false]}, {:table_rex, "~> 4.0", [hex: :table_rex, repo: "hexpm", optional: true]}], "hexpm", "f9f9630406371d19691673acea83f378b96e5c1961eb28436aa5d8ded148188c"}, + "file_system": {:hex, :file_system, "1.1.1", "31864f4685b0148f25bd3fbef2b1228457c0c89024ad67f7a81a3ffbc0bbad3a", [:mix], [], "hexpm", "7a15ff97dfe526aeefb090a7a9d3d03aa907e100e262a0f8f7746b78f8f87a5d"}, + "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"}, "makeup": {:hex, :makeup, "1.2.1", "e90ac1c65589ef354378def3ba19d401e739ee7ee06fb47f94c687016e3713d1", [:mix], [{:nimble_parsec, "~> 1.4", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "d36484867b0bae0fea568d10131197a4c2e47056a6fbe84922bf6ba71c8d17ce"}, "makeup_elixir": {:hex, :makeup_elixir, "1.0.1", "e928a4f984e795e41e3abd27bfc09f51db16ab8ba1aebdba2b3a575437efafc2", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "7284900d412a3e5cfd97fdaed4f5ed389b8f2b4cb49efc0eb3bd10e2febf9507"}, "makeup_erlang": {:hex, :makeup_erlang, "1.0.2", "03e1804074b3aa64d5fad7aa64601ed0fb395337b982d9bcf04029d68d51b6a7", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "af33ff7ef368d5893e4a267933e7744e46ce3cf1f61e2dccf53a111ed3aa3727"}, diff --git a/test/hlx/writer_test.exs b/test/hlx/writer_test.exs index 385e6c8..154f19d 100644 --- a/test/hlx/writer_test.exs +++ b/test/hlx/writer_test.exs @@ -17,7 +17,7 @@ defmodule HLX.WriterTest do id: 2, type: :audio, codec: :aac, - timescale: 44800 + timescale: 44_800 } setup do @@ -95,6 +95,7 @@ defmodule HLX.WriterTest do assert String.ends_with?(segment.uri, extension) end + # credo:disable-for-next-line # TODO: check the actual segment data end @@ -375,7 +376,7 @@ defmodule HLX.WriterTest do partial_segments = Enum.filter(playlist.timeline, &is_struct(&1, ExM3U8.Tags.Part)) assert length(segments) == 4 - assert length(partial_segments) > 0 + assert partial_segments != [] # rendition reports uri = if name == "video", do: "audio.m3u8", else: "video.m3u8"