Skip to content

Commit 61f6084

Browse files
authored
Build binaries using burrito (#17)
* Build binaries using burrito * Add release github action * Add more tests * make credo happy
1 parent d5d29f7 commit 61f6084

7 files changed

Lines changed: 289 additions & 136 deletions

File tree

.github/workflows/release.yml

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
name: Release
2+
3+
on:
4+
push:
5+
tags:
6+
- "v*"
7+
8+
env:
9+
MIX_ENV: prod
10+
11+
jobs:
12+
release:
13+
runs-on: ubuntu-latest
14+
steps:
15+
- uses: actions/checkout@v4
16+
17+
- name: Set up Elixir
18+
uses: erlef/setup-elixir@v1
19+
with:
20+
elixir-version: "1.18.4"
21+
otp-version: "27.3.4.1"
22+
23+
- uses: mlugg/setup-zig@v2
24+
with:
25+
version: "0.15.2"
26+
27+
- name: Install dependencies
28+
run: mix deps.get
29+
30+
- name: Build release
31+
run: mix release
32+
33+
- name: Create Checksum
34+
run: |
35+
chmod +x ./burrito_out/*
36+
shasum -a 256 ./burrito_out/* > shinkai_checksums.txt
37+
38+
- name: Create GitHub Release
39+
id: create_release
40+
uses: actions/create-release@v1
41+
with:
42+
tag_name: ${{ github.ref_name }}
43+
release_name: Release ${{ github.ref_name }}
44+
draft: true
45+
46+
- name: Publish archives and packages
47+
uses: softprops/action-gh-release@v1
48+
with:
49+
draft: true
50+
generate_release_notes: true
51+
files: |
52+
./burrito_out/*
53+
./shinkai_checksums.txt

lib/shinkai/sources/rtmp/media_processor.ex

Lines changed: 58 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ defmodule Shinkai.Sources.RTMP.MediaProcessor do
55

66
require Logger
77

8+
alias MediaCodecs.MPEG4
89
alias Phoenix.PubSub
910
alias Shinkai.{Packet, Track}
1011

@@ -37,38 +38,21 @@ defmodule Shinkai.Sources.RTMP.MediaProcessor do
3738

3839
@spec handle_video_data(tuple(), state()) :: state()
3940
def handle_video_data({:codec, codec, init_data}, state) do
40-
track = Track.new(id: 1, type: :video, codec: codec, timescale: @timescale)
41-
4241
track =
43-
case codec do
44-
:h264 ->
45-
avcc = ExMP4.Box.parse(%ExMP4.Box.Avcc{}, init_data)
46-
%{track | codec: :h264, priv_data: {List.first(avcc.sps), avcc.pps}}
47-
48-
:h265 ->
49-
hvcc = ExMP4.Box.parse(%ExMP4.Box.Hvcc{}, init_data)
50-
51-
%{
52-
track
53-
| codec: :h265,
54-
priv_data: {List.first(hvcc.vps), List.first(hvcc.sps), hvcc.pps}
55-
}
56-
57-
:av1 ->
58-
av1c = ExMP4.Box.parse(%ExMP4.Box.Av1c{}, init_data)
59-
priv_data = if av1c.config_obus != <<>>, do: av1c.config_obus
60-
%{track | codec: :av1, priv_data: priv_data}
61-
62-
_ ->
63-
track
64-
end
42+
Track.new(
43+
id: 1,
44+
type: :video,
45+
codec: codec,
46+
timescale: 90_000,
47+
priv_data: track_priv_data(codec, init_data)
48+
)
6549

6650
state = %{state | video_track: track}
6751
if state.audio_track, do: unbuffer(state), else: state
6852
end
6953

7054
def handle_video_data(sample, %{buffer?: false} = state) do
71-
packet = packet_from_sample(state.video_track.id, sample)
55+
packet = packet_from_sample(state.video_track, sample)
7256
PubSub.broadcast(Shinkai.PubSub, state.packets_topic, {:packet, packet})
7357
state
7458
end
@@ -80,16 +64,28 @@ defmodule Shinkai.Sources.RTMP.MediaProcessor do
8064
def handle_video_data(sample, state) do
8165
%{
8266
state
83-
| packets: [packet_from_sample(state.video_track.id, sample) | state.packets],
67+
| packets: [packet_from_sample(state.video_track, sample) | state.packets],
8468
buffer_len: state.buffer_len + 1
8569
}
8670
end
8771

8872
@spec handle_audio_data(tuple(), state()) :: state()
8973
def handle_audio_data({:codec, codec, init_data}, state) do
90-
track = Track.new(id: 2, type: :audio, codec: codec, timescale: @timescale)
74+
track =
75+
Track.new(
76+
id: 2,
77+
type: :audio,
78+
codec: codec,
79+
timescale: @timescale,
80+
priv_data: track_priv_data(codec, init_data)
81+
)
9182

92-
track = if codec == :aac, do: %{track | priv_data: init_data}, else: track
83+
track =
84+
case track.codec do
85+
:aac -> %{track | timescale: track.priv_data.sampling_frequency}
86+
:opus -> %{track | timescale: 48_000}
87+
_codec -> track
88+
end
9389

9490
state = %{state | audio_track: track}
9591
if state.video_track, do: unbuffer(state), else: state
@@ -99,7 +95,7 @@ defmodule Shinkai.Sources.RTMP.MediaProcessor do
9995
PubSub.broadcast(
10096
Shinkai.PubSub,
10197
state.packets_topic,
102-
{:packet, packet_from_sample(state.audio_track.id, sample)}
98+
{:packet, packet_from_sample(state.audio_track, sample)}
10399
)
104100

105101
state
@@ -112,7 +108,7 @@ defmodule Shinkai.Sources.RTMP.MediaProcessor do
112108
def handle_audio_data(sample, state) do
113109
%{
114110
state
115-
| packets: [packet_from_sample(state.audio_track.id, sample) | state.packets],
111+
| packets: [packet_from_sample(state.audio_track, sample) | state.packets],
116112
buffer_len: state.buffer_len + 1
117113
}
118114
end
@@ -135,20 +131,46 @@ defmodule Shinkai.Sources.RTMP.MediaProcessor do
135131
%{state | buffer?: false, packets: [], buffer_len: 0}
136132
end
137133

134+
defp track_priv_data(:h264, init_data) do
135+
avcc = ExMP4.Box.parse(%ExMP4.Box.Avcc{}, init_data)
136+
{List.first(avcc.sps), avcc.pps}
137+
end
138+
139+
defp track_priv_data(:h265, init_data) do
140+
hvcc = ExMP4.Box.parse(%ExMP4.Box.Hvcc{}, init_data)
141+
{List.first(hvcc.vps), List.first(hvcc.sps), hvcc.pps}
142+
end
143+
144+
defp track_priv_data(:av1, init_data) do
145+
av1c = ExMP4.Box.parse(%ExMP4.Box.Av1c{}, init_data)
146+
147+
if av1c.config_obus != <<>>, do: av1c.config_obus
148+
end
149+
150+
defp track_priv_data(:aac, init_data) do
151+
MPEG4.AudioSpecificConfig.parse(init_data)
152+
end
153+
154+
defp track_priv_data(:opus, _init_data), do: nil
155+
156+
defp track_priv_data(_codec, init_data), do: init_data
157+
138158
@compile {:inline, packet_from_sample: 2}
139-
defp packet_from_sample(track_id, {:sample, payload, dts, pts, sync?}) do
159+
defp packet_from_sample(track, {:sample, payload, dts, pts, sync?}) do
140160
%Packet{
141-
track_id: track_id,
161+
track_id: track.id,
142162
data: payload,
143-
dts: dts,
144-
pts: pts,
163+
dts: div(dts * track.timescale, @timescale),
164+
pts: div(pts * track.timescale, @timescale),
145165
sync?: sync?
146166
}
147167
end
148168

149-
defp packet_from_sample(track_id, {:sample, payload, pts}) do
169+
defp packet_from_sample(track, {:sample, payload, pts}) do
170+
pts = div(pts * track.timescale, @timescale)
171+
150172
%Packet{
151-
track_id: track_id,
173+
track_id: track.id,
152174
data: payload,
153175
dts: pts,
154176
pts: pts,

mix.exs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ defmodule Shinkai.MixProject do
1212
elixirc_paths: elixirc_paths(Mix.env()),
1313
start_permanent: Mix.env() == :prod,
1414
deps: deps(),
15+
releases: releases(),
1516
# hex
1617
description: "Media server for Elixir",
1718
package: package(),
@@ -36,6 +37,7 @@ defmodule Shinkai.MixProject do
3637
{:hlx, "~> 0.5.0"},
3738
{:ex_rtmp, "~> 0.4.1"},
3839
{:yaml_elixir, "~> 2.12"},
40+
{:burrito, "~> 1.5.0"},
3941
{:plug, "~> 1.19", optional: true},
4042
{:bandit, "~> 1.8", optional: true},
4143
{:ex_doc, "~> 0.30", only: :dev, runtime: false},
@@ -46,6 +48,23 @@ defmodule Shinkai.MixProject do
4648
defp elixirc_paths(:test), do: ["lib", "test/support"]
4749
defp elixirc_paths(_), do: ["lib"]
4850

51+
def releases do
52+
[
53+
shinkai: [
54+
steps: [:assemble, &Burrito.wrap/1],
55+
burrito: [
56+
targets: [
57+
macos: [os: :darwin, cpu: :x86_64],
58+
macos_silicon: [os: :darwin, cpu: :aarch64],
59+
linux: [os: :linux, cpu: :x86_64],
60+
linux_arm: [os: :linux, cpu: :aarch64],
61+
windows: [os: :windows, cpu: :x86_64]
62+
]
63+
]
64+
]
65+
]
66+
end
67+
4968
defp package do
5069
[
5170
maintainers: ["Billal Ghilas"],

mix.lock

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
"bandit": {:hex, :bandit, "1.10.2", "d15ea32eb853b5b42b965b24221eb045462b2ba9aff9a0bda71157c06338cbff", [:mix], [{:hpax, "~> 1.0", [hex: :hpax, repo: "hexpm", optional: false]}, {:plug, "~> 1.18", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:thousand_island, "~> 1.0", [hex: :thousand_island, repo: "hexpm", optional: false]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "27b2a61b647914b1726c2ced3601473be5f7aa6bb468564a688646a689b3ee45"},
33
"bunch": {:hex, :bunch, "1.6.1", "5393d827a64d5f846092703441ea50e65bc09f37fd8e320878f13e63d410aec7", [:mix], [], "hexpm", "286cc3add551628b30605efbe2fca4e38cc1bea89bcd0a1a7226920b3364fe4a"},
44
"bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"},
5+
"burrito": {:hex, :burrito, "1.5.0", "d68ec01df2871f1d5bc603b883a78546c75761ac73c1bec1b7ae2cc74790fcd1", [:mix], [{:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:req, ">= 0.5.0", [hex: :req, repo: "hexpm", optional: false]}, {:typed_struct, "~> 0.2.0 or ~> 0.3.0", [hex: :typed_struct, repo: "hexpm", optional: false]}], "hexpm", "3861abda7bffa733862b48da3e03df0b4cd41abf6fd24b91745f5c16d971e5fa"},
56
"coerce": {:hex, :coerce, "1.0.2", "5ef791040c92baaa5dd344887563faaeac6e6742573a167493294f8af3672bbe", [:mix], [], "hexpm", "0b3451c729571234fdac478636c298e71d1f2ce1243abed5fa43fa3181b980eb"},
67
"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"},
78
"earmark_parser": {:hex, :earmark_parser, "1.4.44", "f20830dd6b5c77afe2b063777ddbbff09f9759396500cdbe7523efd58d7a339c", [:mix], [], "hexpm", "4778ac752b4701a5599215f7030989c989ffdc4f6df457c5f36938cc2d2a2750"},
@@ -16,6 +17,7 @@
1617
"ex_sdp": {:hex, :ex_sdp, "1.1.2", "7e7465cb13b557cc76ef3e854bad7626b73cc1d1f480d38b5fbcf539c7d8a45d", [:mix], [{:bunch, "~> 1.3", [hex: :bunch, repo: "hexpm", optional: false]}, {:elixir_uuid, "~> 1.2", [hex: :elixir_uuid, repo: "hexpm", optional: false]}], "hexpm", "50a27c2d745924679acca32b3d5499d0b35d135a180b83422df82c289afce564"},
1718
"file_system": {:hex, :file_system, "1.1.1", "31864f4685b0148f25bd3fbef2b1228457c0c89024ad67f7a81a3ffbc0bbad3a", [:mix], [], "hexpm", "7a15ff97dfe526aeefb090a7a9d3d03aa907e100e262a0f8f7746b78f8f87a5d"},
1819
"hlx": {:hex, :hlx, "0.5.0", "22542ba77c4fd9a50d4566e546ff49b19d9b22d5110bf3b920d3d1f5f61ca9c1", [:mix], [{:ex_m3u8, "~> 0.15.0", [hex: :ex_m3u8, repo: "hexpm", optional: false]}, {:ex_mp4, "~> 0.14.0", [hex: :ex_mp4, repo: "hexpm", optional: false]}, {:media_codecs, "~> 0.10.0", [hex: :media_codecs, repo: "hexpm", optional: false]}, {:mpeg_ts, "~> 3.3.5", [hex: :mpeg_ts, repo: "hexpm", optional: false]}, {:qex, "~> 0.5.1", [hex: :qex, repo: "hexpm", optional: false]}], "hexpm", "74bcb44fda7a2407b37c125385b2389b7922438aba05b13bbc6ab01f8ba42a70"},
20+
"finch": {:hex, :finch, "0.20.0", "5330aefb6b010f424dcbbc4615d914e9e3deae40095e73ab0c1bb0968933cadf", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.6.2 or ~> 1.7", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 1.1", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "2658131a74d051aabfcba936093c903b8e89da9a1b63e430bee62045fa9b2ee2"},
1921
"hpax": {:hex, :hpax, "1.0.3", "ed67ef51ad4df91e75cc6a1494f851850c0bd98ebc0be6e81b026e765ee535aa", [:mix], [], "hexpm", "8eab6e1cfa8d5918c2ce4ba43588e894af35dbd8e91e6e55c817bca5847df34a"},
2022
"jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"},
2123
"makeup": {:hex, :makeup, "1.2.1", "e90ac1c65589ef354378def3ba19d401e739ee7ee06fb47f94c687016e3713d1", [:mix], [{:nimble_parsec, "~> 1.4", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "d36484867b0bae0fea568d10131197a4c2e47056a6fbe84922bf6ba71c8d17ce"},
@@ -24,17 +26,20 @@
2426
"media_codecs": {:hex, :media_codecs, "0.10.0", "dcc64779c3b287202fd8083fe49bf11b37f7b6bbd8edf3a9bd756370ee4417c5", [:mix], [], "hexpm", "8ea233ae378acfae3ab95a90f6f5c99711d55f15d0c5fac244d46b42f6a9ca04"},
2527
"membrane_rtsp": {:hex, :membrane_rtsp, "0.11.0", "887b1c0cd4f40f6ce93880bfa1a1e8c9e250aabb24810a8fe2a7556bb54c29c4", [:mix], [{:bunch, "~> 1.6", [hex: :bunch, repo: "hexpm", optional: false]}, {:ex_sdp, "~> 0.17.0 or ~> 1.0", [hex: :ex_sdp, repo: "hexpm", optional: false]}, {:mockery, "~> 2.3", [hex: :mockery, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.4.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "69252d77ad3df48e6cb21fc16b0c5730607709714ad7849b7635813f9741ee2f"},
2628
"mime": {:hex, :mime, "2.0.7", "b8d739037be7cd402aee1ba0306edfdef982687ee7e9859bee6198c1e7e2f128", [:mix], [], "hexpm", "6171188e399ee16023ffc5b76ce445eb6d9672e2e241d2df6050f3c771e80ccd"},
29+
"mint": {:hex, :mint, "1.7.1", "113fdb2b2f3b59e47c7955971854641c61f378549d73e829e1768de90fc1abf1", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1 or ~> 0.2.0 or ~> 1.0", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "fceba0a4d0f24301ddee3024ae116df1c3f4bb7a563a731f45fdfeb9d39a231b"},
2730
"mockery": {:hex, :mockery, "2.5.0", "a87acd74fd733aa3b9cb5663d6f690178b056608f2652f18e4ec423ddd5496ed", [:mix], [], "hexpm", "52492b2eba61055df1c626e894663b624b5e6fdfaaaba1d9a8596236fbf4da69"},
2831
"mpeg_ts": {:hex, :mpeg_ts, "3.3.11", "77d69c5599fcd6eadef926b03cf6fe990dd76301ec41ce77de71bc84ad53412c", [:mix], [], "hexpm", "e1554e7b2ffe5692effca19173200fdee0959bd40d201ec920a950054c27cb76"},
2932
"nimble_options": {:hex, :nimble_options, "1.1.1", "e3a492d54d85fc3fd7c5baf411d9d2852922f66e69476317787a7b2bb000a61b", [:mix], [], "hexpm", "821b2470ca9442c4b6984882fe9bb0389371b8ddec4d45a9504f00a66f650b44"},
3033
"nimble_parsec": {:hex, :nimble_parsec, "1.4.2", "8efba0122db06df95bfaa78f791344a89352ba04baedd3849593bfce4d0dc1c6", [:mix], [], "hexpm", "4b21398942dda052b403bbe1da991ccd03a053668d147d53fb8c4e0efe09c973"},
34+
"nimble_pool": {:hex, :nimble_pool, "1.1.0", "bf9c29fbdcba3564a8b800d1eeb5a3c58f36e1e11d7b7fb2e084a643f645f06b", [:mix], [], "hexpm", "af2e4e6b34197db81f7aad230c1118eac993acc0dae6bc83bac0126d4ae0813a"},
3135
"numbers": {:hex, :numbers, "5.2.4", "f123d5bb7f6acc366f8f445e10a32bd403c8469bdbce8ce049e1f0972b607080", [:mix], [{:coerce, "~> 1.0", [hex: :coerce, repo: "hexpm", optional: false]}, {:decimal, "~> 1.9 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "eeccf5c61d5f4922198395bf87a465b6f980b8b862dd22d28198c5e6fab38582"},
3236
"phoenix_pubsub": {:hex, :phoenix_pubsub, "2.2.0", "ff3a5616e1bed6804de7773b92cbccfc0b0f473faf1f63d7daf1206c7aeaaa6f", [:mix], [], "hexpm", "adc313a5bf7136039f63cfd9668fde73bba0765e0614cba80c06ac9460ff3e96"},
3337
"plug": {:hex, :plug, "1.19.1", "09bac17ae7a001a68ae393658aa23c7e38782be5c5c00c80be82901262c394c0", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "560a0017a8f6d5d30146916862aaf9300b7280063651dd7e532b8be168511e62"},
3438
"plug_crypto": {:hex, :plug_crypto, "2.1.1", "19bda8184399cb24afa10be734f84a16ea0a2bc65054e23a62bb10f06bc89491", [:mix], [], "hexpm", "6470bce6ffe41c8bd497612ffde1a7e4af67f36a15eea5f921af71cf3e11247c"},
3539
"qex": {:hex, :qex, "0.5.1", "0d82c0f008551d24fffb99d97f8299afcb8ea9cf99582b770bd004ed5af63fd6", [:mix], [], "hexpm", "935a39fdaf2445834b95951456559e9dc2063d0a055742c558a99987b38d6bab"},
3640
"ratio": {:hex, :ratio, "4.0.1", "3044166f2fc6890aa53d3aef0c336f84b2bebb889dc57d5f95cc540daa1912f8", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}, {:numbers, "~> 5.2.0", [hex: :numbers, repo: "hexpm", optional: false]}], "hexpm", "c60cbb3ccdff9ffa56e7d6d1654b5c70d9f90f4d753ab3a43a6bf40855b881ce"},
3741
"rtsp": {:hex, :rtsp, "0.8.1", "4bffebfcb0e1354283567178c040bbf40a85c4fbbde6d23addbbc7672cb3c700", [:mix], [{:ex_mp4, "~> 0.14.0", [hex: :ex_mp4, repo: "hexpm", optional: true]}, {:ex_rtcp, "~> 0.4.0", [hex: :ex_rtcp, repo: "hexpm", optional: false]}, {:ex_rtp, "~> 0.4.0", [hex: :ex_rtp, repo: "hexpm", optional: false]}, {:ex_sdp, "~> 1.0", [hex: :ex_sdp, repo: "hexpm", optional: false]}, {:media_codecs, "~> 0.10.0", [hex: :media_codecs, repo: "hexpm", optional: false]}, {:membrane_rtsp, "~> 0.11.0", [hex: :membrane_rtsp, repo: "hexpm", optional: false]}, {:nimble_options, "~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}], "hexpm", "b4af3c30b8f79dd642940452c6ad6727bfd1df492e5ddbc1eb705f25df6f4053"},
42+
"req": {:hex, :req, "0.5.16", "99ba6a36b014458e52a8b9a0543bfa752cb0344b2a9d756651db1281d4ba4450", [:mix], [{:brotli, "~> 0.3.1", [hex: :brotli, repo: "hexpm", optional: true]}, {:ezstd, "~> 1.0", [hex: :ezstd, repo: "hexpm", optional: true]}, {:finch, "~> 0.17", [hex: :finch, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mime, "~> 2.0.6 or ~> 2.1", [hex: :mime, repo: "hexpm", optional: false]}, {:nimble_csv, "~> 1.0", [hex: :nimble_csv, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "974a7a27982b9b791df84e8f6687d21483795882a7840e8309abdbe08bb06f09"},
3843
"telemetry": {:hex, :telemetry, "1.3.0", "fedebbae410d715cf8e7062c96a1ef32ec22e764197f70cda73d82778d61e7a2", [:rebar3], [], "hexpm", "7015fc8919dbe63764f4b4b87a95b7c0996bd539e0d499be6ec9d7f3875b79e6"},
3944
"thousand_island": {:hex, :thousand_island, "1.4.3", "2158209580f633be38d43ec4e3ce0a01079592b9657afff9080d5d8ca149a3af", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "6e4ce09b0fd761a58594d02814d40f77daff460c48a7354a15ab353bb998ea0b"},
4045
"typed_struct": {:hex, :typed_struct, "0.3.0", "939789e3c1dca39d7170c87f729127469d1315dcf99fee8e152bb774b17e7ff7", [:mix], [], "hexpm", "c50bd5c3a61fe4e198a8504f939be3d3c85903b382bde4865579bc23111d1b6d"},

0 commit comments

Comments
 (0)