Compare commits
10 Commits
ad3e9704c0
...
7b37ea2dfa
Author | SHA1 | Date |
---|---|---|
|
7b37ea2dfa | |
|
1bd68fc0ba | |
|
9b8393f27c | |
|
43151cd1a8 | |
|
9b3cf7c5eb | |
|
5177abd11f | |
|
2396d6fb62 | |
|
7572f594e7 | |
|
10cc0f3604 | |
|
be84376c04 |
|
@ -0,0 +1,10 @@
|
|||
module git.icedream.tech/icedream/auto-restart-voicemeeter
|
||||
|
||||
go 1.20
|
||||
|
||||
require (
|
||||
github.com/JamesDunne/go-asio v0.0.0-20150322061733-6c4b099ca927
|
||||
github.com/davecgh/go-spew v1.1.1
|
||||
)
|
||||
|
||||
require github.com/cenkalti/backoff/v4 v4.2.0 // indirect
|
|
@ -0,0 +1,6 @@
|
|||
github.com/JamesDunne/go-asio v0.0.0-20150322061733-6c4b099ca927 h1:qcTT3RNLm21eamlM+G/AJrh1KG9VcyW8AwOsbzVRW04=
|
||||
github.com/JamesDunne/go-asio v0.0.0-20150322061733-6c4b099ca927/go.mod h1:dRYieepeZSO8hlYXKnpnn6WqR7Myv+kb+Y+w14NxfFw=
|
||||
github.com/cenkalti/backoff/v4 v4.2.0 h1:HN5dHm3WBOgndBH6E8V0q2jIYIR3s9yglV8k/+MN3u4=
|
||||
github.com/cenkalti/backoff/v4 v4.2.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
|
@ -0,0 +1,243 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"math"
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/signal"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
asio "github.com/JamesDunne/go-asio"
|
||||
"github.com/cenkalti/backoff/v4"
|
||||
)
|
||||
|
||||
func reverse(s string) string {
|
||||
r := []rune(s)
|
||||
for i, j := 0, len(r)-1; i < len(r)/2; i, j = i+1, j-1 {
|
||||
r[i], r[j] = r[j], r[i]
|
||||
}
|
||||
return string(r)
|
||||
}
|
||||
|
||||
func main() {
|
||||
// TODO - handle interrupt signal here rather than in sample loop
|
||||
|
||||
bo := backoff.NewConstantBackOff(time.Second)
|
||||
err := backoff.Retry(run, bo)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func run() error {
|
||||
fmt.Printf("CoInitialize(0)\n")
|
||||
asio.CoInitialize(0)
|
||||
defer fmt.Printf("CoUninitialize()\n")
|
||||
defer asio.CoUninitialize()
|
||||
|
||||
drivers, err := asio.ListDrivers()
|
||||
if err != nil {
|
||||
return backoff.Permanent(err)
|
||||
}
|
||||
|
||||
var mainOutDriver *asio.ASIODriver
|
||||
for _, driver := range drivers {
|
||||
if driver.Name != "Voicemeeter Virtual ASIO" {
|
||||
continue
|
||||
}
|
||||
mainOutDriver = driver
|
||||
break
|
||||
}
|
||||
|
||||
if mainOutDriver == nil {
|
||||
return backoff.Permanent(errors.New("could not find main Voicemeeter ASIO output"))
|
||||
}
|
||||
|
||||
log.Println("ASIO driver:", mainOutDriver.GUID, mainOutDriver.CLSID, mainOutDriver.Name)
|
||||
|
||||
if err := mainOutDriver.Open(); err != nil {
|
||||
return err
|
||||
}
|
||||
defer mainOutDriver.Close()
|
||||
|
||||
drv := mainOutDriver.ASIO
|
||||
fmt.Printf("getDriverName(): '%s'\n", drv.GetDriverName())
|
||||
fmt.Printf("getDriverVersion(): %d\n", drv.GetDriverVersion())
|
||||
|
||||
// mainOutUnknown := drv.AsIUnknown()
|
||||
// mainOutUnknown.AddRef()
|
||||
// defer mainOutUnknown.Release()
|
||||
|
||||
// getChannels
|
||||
in, out, err := drv.GetChannels()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Printf("getChannels(): %d, %d\n", in, out)
|
||||
|
||||
// getBufferSize
|
||||
minSize, maxSize, preferredSize, granularity, err := drv.GetBufferSize()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Printf("getBufferSize(): %d, %d, %d, %d\n", minSize, maxSize, preferredSize, granularity)
|
||||
|
||||
// getSampleRate
|
||||
srate, err := drv.GetSampleRate()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Printf("getSampleRate(): %v\n", srate)
|
||||
|
||||
// canSampleRate
|
||||
var sampleRate float64
|
||||
// for _, canSampleRate := range []int{
|
||||
// 48000,
|
||||
// 44100,
|
||||
// } {
|
||||
// sampleRateF64 := float64(canSampleRate)
|
||||
// err = drv.CanSampleRate(sampleRateF64)
|
||||
// fmt.Printf("canSampleRate(%q): %v\n", sampleRateF64, err)
|
||||
// if err != nil {
|
||||
// continue
|
||||
// }
|
||||
// sampleRate = canSampleRate
|
||||
// break
|
||||
// }
|
||||
// if sampleRate == 0 {
|
||||
// // log.Fatal("Could not negotiate a compatible samplerate")
|
||||
// // return
|
||||
// fmt.Println("WARNING: Defaulting to 48000 Hz")
|
||||
// sampleRate = 48000
|
||||
// }
|
||||
sampleRate = srate
|
||||
// sampleRate = 48000
|
||||
|
||||
// SetSampleRate
|
||||
err = drv.SetSampleRate(sampleRate)
|
||||
fmt.Printf("setSampleRate(%v): %v\n", float64(sampleRate), err)
|
||||
if err != nil {
|
||||
fmt.Println("WARNING: setSampleRate failed, ignoring:", err)
|
||||
}
|
||||
|
||||
// outputReady
|
||||
fmt.Printf("outputReady(): %v\n", drv.OutputReady())
|
||||
|
||||
// open control panel:
|
||||
// drv.ControlPanel()
|
||||
|
||||
bufferDescriptors := make([]asio.BufferInfo, 0, in+out)
|
||||
for i := 0; i < in; i++ {
|
||||
bufferDescriptors = append(bufferDescriptors, asio.BufferInfo{
|
||||
Channel: i,
|
||||
IsInput: true,
|
||||
})
|
||||
cinfo, err := drv.GetChannelInfo(i, true)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
continue
|
||||
}
|
||||
fmt.Printf(" IN%-2d: active=%v, group=%d, type=%d, name=%s\n", i+1, cinfo.IsActive, cinfo.ChannelGroup, cinfo.SampleType, cinfo.Name)
|
||||
}
|
||||
for i := 0; i < out; i++ {
|
||||
bufferDescriptors = append(bufferDescriptors, asio.BufferInfo{
|
||||
Channel: i,
|
||||
IsInput: false,
|
||||
})
|
||||
cinfo, err := drv.GetChannelInfo(i, false)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
continue
|
||||
}
|
||||
fmt.Printf("OUT%-2d: active=%v, group=%d, type=%d, name=%s\n", i+1, cinfo.IsActive, cinfo.ChannelGroup, cinfo.SampleType, cinfo.Name)
|
||||
}
|
||||
|
||||
err = drv.CreateBuffers(bufferDescriptors, 512, asio.Callbacks{
|
||||
Message: func(selector, value int32, message uintptr, opt *float64) int32 {
|
||||
log.Println("Message:", selector, value, message, opt)
|
||||
return 0
|
||||
},
|
||||
BufferSwitch: func(doubleBufferIndex int, directProcess bool) {
|
||||
log.Println("Buffer switch:", doubleBufferIndex, directProcess)
|
||||
},
|
||||
BufferSwitchTimeInfo: func(params *asio.ASIOTime, doubleBufferIndex int32, directProcess bool) *asio.ASIOTime {
|
||||
log.Println("Buffer switch time info:", params, doubleBufferIndex, directProcess)
|
||||
return params
|
||||
},
|
||||
SampleRateDidChange: func(rate float64) {
|
||||
log.Println("Sample rate did change:", rate)
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer fmt.Printf("disposeBuffers()\n")
|
||||
defer drv.DisposeBuffers()
|
||||
fmt.Printf("createBuffers()\n")
|
||||
|
||||
// getLatencies
|
||||
latin, latout, err := drv.GetLatencies()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Printf("getLatencies(): %d, %d\n", latin, latout)
|
||||
|
||||
err = drv.Start()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer drv.Stop()
|
||||
|
||||
c := make(chan os.Signal, 1)
|
||||
go signal.Notify(c, os.Interrupt, syscall.SIGTERM)
|
||||
|
||||
output := new(strings.Builder)
|
||||
ladder := " ▁▂▃▄▅▆▇█"
|
||||
chars := []rune(reverse(ladder) + ladder)
|
||||
grace := 5 * time.Second
|
||||
lastSignalTime := time.Now()
|
||||
for {
|
||||
select {
|
||||
case <-time.After(33 * time.Millisecond):
|
||||
output.Reset()
|
||||
for _, desc := range bufferDescriptors {
|
||||
for _, buf := range desc.Buffers {
|
||||
output.WriteString("|")
|
||||
if buf == nil {
|
||||
output.WriteString("?")
|
||||
continue
|
||||
}
|
||||
if *buf != 0 {
|
||||
lastSignalTime = time.Now()
|
||||
}
|
||||
output.WriteString(fmt.Sprintf("%c",
|
||||
chars[len(chars)/2+
|
||||
int(float64(len(chars))/2*
|
||||
float64(*buf)/float64(math.MaxInt32))]))
|
||||
}
|
||||
}
|
||||
os.Stdout.WriteString(output.String() + "\r")
|
||||
if time.Now().Sub(lastSignalTime) > time.Second {
|
||||
os.Stdout.WriteString("Silence!\r")
|
||||
}
|
||||
if time.Now().Sub(lastSignalTime) > grace {
|
||||
os.Stdout.WriteString("\n")
|
||||
log.Println("Restarting audio engine...")
|
||||
cmd := exec.Command(`C:\Program Files (x86)\VB\Voicemeeter\voicemeeterpro.exe`, "-r")
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
log.Printf("Restart audio engine result: %v", cmd.Run())
|
||||
// time.Sleep(3 * time.Second)
|
||||
// lastSignalTime = time.Now()
|
||||
return errors.New("audio engine restarted, retry")
|
||||
}
|
||||
case <-c:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
|
@ -26,7 +26,7 @@ then
|
|||
rm winfsp.zip
|
||||
fi
|
||||
|
||||
for bin in foobar2000 tunadish tunaposter prime4
|
||||
for bin in auto-restart-voicemeeter foobar2000 tunadish tunaposter prime4
|
||||
do
|
||||
cd "$SCRIPT_DIR/$bin"
|
||||
go build -ldflags "-s -w" -o "$GOBIN/$bin$ext" -v
|
||||
|
|
|
@ -11,8 +11,8 @@ require (
|
|||
github.com/billziss-gh/cgofuse v1.5.0
|
||||
github.com/dhowden/tag v0.0.0-20220618230019-adf36e896086
|
||||
github.com/gin-gonic/gin v1.9.0
|
||||
github.com/icedream/livestream-tools/icedreammusic/metacollector v0.0.0-20230302092605-950fba55448e
|
||||
github.com/icedream/livestream-tools/icedreammusic/tuna v0.0.0-20230302092605-950fba55448e
|
||||
github.com/icedream/livestream-tools/icedreammusic/metacollector v0.0.0-20230302143311-ad3e9704c029
|
||||
github.com/icedream/livestream-tools/icedreammusic/tuna v0.0.0-20230302143311-ad3e9704c029
|
||||
gopkg.in/alecthomas/kingpin.v3-unstable v3.0.0-20191105091915-95d230a53780
|
||||
)
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
# ARG IMAGE=savonet/liquidsoap:master
|
||||
ARG IMAGE=savonet/liquidsoap-ci-build:v2.1.4_alpine_amd64
|
||||
ARG IMAGE=savonet/liquidsoap-ci-build:rolling-release-v2.2.x@sha256:d38c0580b4b1490bfd679fdacadd20352558c8ac214beb2a8aed3d7017275e15
|
||||
|
||||
# FROM $IMAGE
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
# Our Denon StagelinQ receiver will send the metadata to this interface.
|
||||
# For fun, I tested this code with the port set to 1608 to emulate the OBS Tuna plugin's HTTP interface, and that works well!
|
||||
|
||||
metadata_api_hostname = getenv(default="icedream-bitwave", "METADATA_API_HOSTNAME")
|
||||
metadata_api_hostname = environment.get(default="icedream-bitwave", "METADATA_API_HOSTNAME")
|
||||
|
||||
def http_export_meta(%argsof(json.stringify), m) =
|
||||
j = json()
|
||||
|
@ -16,7 +16,10 @@ def setup_harbor_metadata_api(~metadata_api_port=21338, s) =
|
|||
s = insert_metadata(s)
|
||||
|
||||
# Handler for fetching metadata
|
||||
def on_http_get_metadata(~protocol, ~data, ~headers, uri) =
|
||||
def on_http_get_metadata(request) =
|
||||
http_version = request.http_version
|
||||
headers = request.headers
|
||||
|
||||
m = s.last_metadata() ?? []
|
||||
|
||||
# remove cover info and link to it instead if existing
|
||||
|
@ -35,7 +38,7 @@ def setup_harbor_metadata_api(~metadata_api_port=21338, s) =
|
|||
m = metadata.cover.remove(m)
|
||||
data = http_export_meta(compact=true, m)
|
||||
|
||||
http.response(protocol=protocol, code=200, headers=[
|
||||
http.response(http_version=http_version, status_code=200, headers=[
|
||||
("access-control-allow-origin","*"),
|
||||
("access-control-allow-credentials","true"),
|
||||
("access-control-allow-methods","GET,POST"),
|
||||
|
@ -47,11 +50,14 @@ def setup_harbor_metadata_api(~metadata_api_port=21338, s) =
|
|||
end
|
||||
|
||||
# Handler for fetching current cover art
|
||||
def on_http_get_cover(~protocol, ~data, ~headers, uri) =
|
||||
def on_http_get_cover(request) =
|
||||
http_version = request.http_version
|
||||
headers = request.headers
|
||||
|
||||
m = s.last_metadata() ?? []
|
||||
cover = metadata.cover(m) ?? "".{mime="text/plain"}
|
||||
if string.length(cover) > 0 then
|
||||
http.response(protocol=protocol, code=200, headers=[
|
||||
http.response(http_version=http_version, status_code=200, headers=[
|
||||
("access-control-allow-origin","*"),
|
||||
("access-control-allow-credentials","true"),
|
||||
("access-control-allow-methods","GET,POST"),
|
||||
|
@ -61,7 +67,7 @@ def setup_harbor_metadata_api(~metadata_api_port=21338, s) =
|
|||
("content-type", cover.mime),
|
||||
], data=string_of(cover))
|
||||
else
|
||||
http.response(protocol=protocol, code=404, headers=[
|
||||
http.response(http_version=http_version, status_code=404, headers=[
|
||||
("access-control-allow-origin","*"),
|
||||
("access-control-allow-credentials","true"),
|
||||
("access-control-allow-methods","GET,POST"),
|
||||
|
@ -73,7 +79,11 @@ def setup_harbor_metadata_api(~metadata_api_port=21338, s) =
|
|||
end
|
||||
|
||||
# Handler for receiving metadata
|
||||
def on_http_metadata(~protocol, ~data, ~headers, uri) =
|
||||
def on_http_metadata(request) =
|
||||
http_version = request.http_version
|
||||
data = request.data()
|
||||
headers = request.headers
|
||||
|
||||
let json.parse (data : {
|
||||
data: [(string * string)] as json.object
|
||||
}) = data
|
||||
|
@ -108,7 +118,7 @@ def setup_harbor_metadata_api(~metadata_api_port=21338, s) =
|
|||
# set metadata on stream
|
||||
s.insert_metadata(new_track=new_track, m)
|
||||
|
||||
http.response(protocol=protocol, code=200, headers=[
|
||||
http.response(http_version=http_version, status_code=200, headers=[
|
||||
("allow","POST"),
|
||||
("access-control-allow-origin","*"),
|
||||
("access-control-allow-credentials","true"),
|
||||
|
@ -119,8 +129,11 @@ def setup_harbor_metadata_api(~metadata_api_port=21338, s) =
|
|||
end
|
||||
|
||||
# Just in case we use a browser to send data to this (for example while emulating Tuna)
|
||||
def on_http_metadata_cors(~protocol, ~data, ~headers, uri) =
|
||||
http.response(protocol=protocol, code=200, headers=[
|
||||
def on_http_metadata_cors(request) =
|
||||
http_version = request.http_version
|
||||
headers = request.headers
|
||||
|
||||
http.response(http_version=http_version, status_code=200, headers=[
|
||||
("allow","POST"),
|
||||
("access-control-allow-origin","*"),
|
||||
("access-control-allow-credentials","true"),
|
||||
|
@ -130,17 +143,17 @@ def setup_harbor_metadata_api(~metadata_api_port=21338, s) =
|
|||
], data="POST")
|
||||
end
|
||||
|
||||
harbor.http.register(port=metadata_api_port, method="GET", "/#{id}/meta", on_http_get_metadata)
|
||||
harbor.http.register(port=metadata_api_port, method="OPTIONS", "/#{id}/meta", on_http_metadata_cors)
|
||||
harbor.http.register.simple(port=metadata_api_port, method="GET", "/#{id}/meta", on_http_get_metadata)
|
||||
harbor.http.register.simple(port=metadata_api_port, method="OPTIONS", "/#{id}/meta", on_http_metadata_cors)
|
||||
|
||||
harbor.http.register(port=metadata_api_port, method="GET", "/#{id}/cover", on_http_get_cover)
|
||||
harbor.http.register(port=metadata_api_port, method="OPTIONS", "/#{id}/cover", on_http_metadata_cors)
|
||||
harbor.http.register.simple(port=metadata_api_port, method="GET", "/#{id}/cover", on_http_get_cover)
|
||||
harbor.http.register.simple(port=metadata_api_port, method="OPTIONS", "/#{id}/cover", on_http_metadata_cors)
|
||||
|
||||
harbor.http.register(port=metadata_api_port, method="POST", "/#{id}", on_http_metadata)
|
||||
harbor.http.register(port=metadata_api_port, method="OPTIONS", "/#{id}", on_http_metadata_cors)
|
||||
harbor.http.register.simple(port=metadata_api_port, method="POST", "/#{id}", on_http_metadata)
|
||||
harbor.http.register.simple(port=metadata_api_port, method="OPTIONS", "/#{id}", on_http_metadata_cors)
|
||||
|
||||
harbor.http.register(port=metadata_api_port, method="POST", "/", on_http_metadata)
|
||||
harbor.http.register(port=metadata_api_port, method="OPTIONS", "/", on_http_metadata_cors)
|
||||
harbor.http.register.simple(port=metadata_api_port, method="POST", "/", on_http_metadata)
|
||||
harbor.http.register.simple(port=metadata_api_port, method="OPTIONS", "/", on_http_metadata_cors)
|
||||
|
||||
s
|
||||
end
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
stream_name=getenv(default="Spontaneously live in the mix", "STREAM_NAME")
|
||||
stream_description=getenv(default="", "STREAM_DESCRIPTION")
|
||||
stream_name=environment.get(default="Spontaneously live in the mix", "STREAM_NAME")
|
||||
stream_description=environment.get(default="", "STREAM_DESCRIPTION")
|
||||
|
||||
internal_icecast_username=getenv(default="source", "INTERNAL_ICECAST_USERNAME")
|
||||
internal_icecast_password=getenv(default="source", "INTERNAL_ICECAST_PASSWORD")
|
||||
internal_icecast_username=environment.get(default="source", "INTERNAL_ICECAST_USERNAME")
|
||||
internal_icecast_password=environment.get(default="source", "INTERNAL_ICECAST_PASSWORD")
|
||||
|
||||
rektfm_username=getenv("REKTNETWORK_USERNAME")
|
||||
rektfm_password=getenv("REKTNETWORK_PASSWORD")
|
||||
rektfm_username=environment.get("REKTNETWORK_USERNAME")
|
||||
rektfm_password=environment.get("REKTNETWORK_PASSWORD")
|
||||
|
|
|
@ -22,18 +22,32 @@ set("sandbox", "disabled")
|
|||
s = input.http(id="input_ice_main", max_buffer=4., "http://127.0.0.1:61120/main")
|
||||
|
||||
# Split audio off to be handled specially
|
||||
a = drop_video(s)
|
||||
# NOTE - drop_video causes a weird error during script validation, we assume audio-only here
|
||||
# a = drop_video(s)
|
||||
a = s
|
||||
a = mksafe_soft(a)
|
||||
output.dummy(a)
|
||||
|
||||
def append_encoder_meta(m) =
|
||||
[
|
||||
def append_encoder_meta(_) =
|
||||
new_meta = [
|
||||
("encoder", "Liquidsoap #{liquidsoap.version}"),
|
||||
("stream_name", stream_name),
|
||||
("stream_description", stream_description),
|
||||
]
|
||||
|
||||
new_meta = if null.defined(stream_name) then
|
||||
[...new_meta, ("stream_name", null.get(stream_name))]
|
||||
else
|
||||
new_meta
|
||||
end
|
||||
|
||||
new_meta = if null.defined(stream_description) then
|
||||
[...new_meta, ("stream_description", null.get(stream_description))]
|
||||
else
|
||||
new_meta
|
||||
end
|
||||
|
||||
new_meta
|
||||
end
|
||||
a = map_metadata(id="main", append_encoder_meta, a)
|
||||
a = metadata.map(id="main", append_encoder_meta, a)
|
||||
|
||||
a = setup_harbor_metadata_api(a)
|
||||
|
||||
|
@ -55,10 +69,10 @@ def internal_icecast(
|
|||
headers=[],
|
||||
port=61120,
|
||||
host="127.0.0.1",
|
||||
user=internal_icecast_username,
|
||||
password=internal_icecast_password,
|
||||
name=stream_name,
|
||||
description=stream_description,
|
||||
user=null.get(internal_icecast_username),
|
||||
password=null.get(internal_icecast_password),
|
||||
name=null.get(stream_name),
|
||||
description=null.get(stream_description),
|
||||
e, s)
|
||||
end
|
||||
|
||||
|
@ -123,7 +137,7 @@ setup_harbor_stream_api(internal_icecast(
|
|||
))
|
||||
|
||||
# REKT.fm
|
||||
if string.length(rektfm_username) > 0 and string.length(rektfm_password) > 0 then
|
||||
if null.defined(rektfm_username) and null.defined(rektfm_password) then
|
||||
setup_harbor_stream_api(output.icecast(
|
||||
id="out_a_rekt",
|
||||
# %ogg(%flac),
|
||||
|
@ -133,10 +147,10 @@ if string.length(rektfm_username) > 0 and string.length(rektfm_password) > 0 the
|
|||
port=60000,
|
||||
host="stream.rekt.network",
|
||||
# host="stream.rekt.fm",
|
||||
user=rektfm_username,
|
||||
name=stream_name,
|
||||
description=stream_description,
|
||||
password=rektfm_password,
|
||||
user=null.get(rektfm_username),
|
||||
name=null.get(stream_name),
|
||||
description=null.get(stream_description),
|
||||
password=null.get(rektfm_password),
|
||||
start=false,
|
||||
%ffmpeg(
|
||||
format="ogg",
|
||||
|
|
|
@ -3,35 +3,34 @@ stream_api_port=21336
|
|||
interactive.harbor(port=stream_api_port, uri="/interactive") # expose through stream API port
|
||||
|
||||
# list of stream IDs that have been set up by setup_harbor_stream_api
|
||||
stream_api_streams=[]
|
||||
stream_api_streams=ref([])
|
||||
|
||||
def setup_harbor_stream_api_general()
|
||||
def on_list(~protocol, ~data, ~headers, uri) =
|
||||
data = stream_api_streams
|
||||
http.response(protocol=protocol, code=200, headers=[
|
||||
def on_list(_) =
|
||||
http.response(status_code=200, headers=[
|
||||
("content-type","application/json"),
|
||||
], data=json.stringify(data))
|
||||
], data=json.stringify(stream_api_streams()))
|
||||
end
|
||||
|
||||
harbor.http.register(port=stream_api_port, method="GET", "/streams/", on_list)
|
||||
harbor.http.register.simple(port=stream_api_port, method="GET", "/streams/", on_list)
|
||||
end
|
||||
|
||||
def setup_harbor_stream_api(s) =
|
||||
def on_start(~protocol, ~data, ~headers, uri) =
|
||||
def on_start(_) =
|
||||
s.start()
|
||||
http.response(protocol=protocol, code=200, headers=[
|
||||
http.response(status_code=200, headers=[
|
||||
("content-type","application/json"),
|
||||
], data=json.stringify([]))
|
||||
end
|
||||
|
||||
def on_stop(~protocol, ~data, ~headers, uri) =
|
||||
def on_stop(_) =
|
||||
s.stop()
|
||||
http.response(protocol=protocol, code=200, headers=[
|
||||
http.response(status_code=200, headers=[
|
||||
("content-type","application/json"),
|
||||
], data=json.stringify([]))
|
||||
end
|
||||
|
||||
def on_info(~protocol, ~data, ~headers, uri) =
|
||||
def on_info(_) =
|
||||
data = [
|
||||
("id", s.id()),
|
||||
("last_metadata", json.stringify(s.last_metadata())),
|
||||
|
@ -40,16 +39,16 @@ def setup_harbor_stream_api(s) =
|
|||
("is_ready", json.stringify(s.is_ready())),
|
||||
("is_active", json.stringify(s.is_active())),
|
||||
]
|
||||
http.response(protocol=protocol, code=200, headers=[
|
||||
http.response(status_code=200, headers=[
|
||||
("content-type","application/json"),
|
||||
], data=json.stringify(data))
|
||||
end
|
||||
|
||||
s.id()::stream_api_streams
|
||||
stream_api_streams := [...stream_api_streams(), s.id()]
|
||||
|
||||
harbor.http.register(port=stream_api_port, method="POST", "/streams/#{s.id()}/start", on_start)
|
||||
harbor.http.register(port=stream_api_port, method="POST", "/streams/#{s.id()}/stop", on_stop)
|
||||
harbor.http.register(port=stream_api_port, method="GET", "/streams/#{s.id()}", on_info)
|
||||
harbor.http.register.simple(port=stream_api_port, method="POST", "/streams/#{s.id()}/start", on_start)
|
||||
harbor.http.register.simple(port=stream_api_port, method="POST", "/streams/#{s.id()}/stop", on_stop)
|
||||
harbor.http.register.simple(port=stream_api_port, method="GET", "/streams/#{s.id()}", on_info)
|
||||
|
||||
s
|
||||
end
|
||||
|
|
|
@ -1,11 +1,102 @@
|
|||
FROM busybox
|
||||
# syntax=docker/dockerfile:1.2.1
|
||||
|
||||
WORKDIR /target/usr/local/bin/
|
||||
COPY *.sh .
|
||||
RUN dos2unix *.sh
|
||||
RUN chmod -v +x *.sh
|
||||
FROM alpine AS rootfs
|
||||
|
||||
RUN apk add --no-cache gnupg
|
||||
|
||||
RUN wget -O- https://raw.githubusercontent.com/archlinuxarm/archlinuxarm-keyring/master/archlinuxarm.gpg | gpg --import
|
||||
|
||||
WORKDIR /target/
|
||||
ARG ALARM_ROOTFS_URL=http://os.archlinuxarm.org/os/ArchLinuxARM-rpi-2-latest.tar.gz
|
||||
RUN wget "${ALARM_ROOTFS_URL}" -O/tmp/rootfs.tar.gz
|
||||
RUN wget "${ALARM_ROOTFS_URL}.sig" -O/tmp/rootfs.tar.gz.sig
|
||||
RUN gpg --verify /tmp/rootfs.tar.gz.sig
|
||||
RUN tar -xvpzf /tmp/rootfs.tar.gz
|
||||
|
||||
###
|
||||
# PREPARE LAYER FOR UPDATES AND GENERAL PACKAGE INSTALLATION
|
||||
|
||||
# FROM scratch AS image-base
|
||||
|
||||
# COPY --from=rootfs /target/ /
|
||||
|
||||
FROM archlinux AS image-base
|
||||
|
||||
# Make powerpill not act up later, placing this early for validation consistency
|
||||
RUN sed -i 's,SigLevel\s\+=\s\+Required,SigLevel = PackageRequired,' /etc/pacman.conf
|
||||
|
||||
RUN pacman -Sy --noconfirm
|
||||
RUN pacman-key --init
|
||||
|
||||
# Install core keyring (https://archlinuxarm.org/about/package-signing)
|
||||
# RUN pacman -S --needed --noconfirm archlinuxarm-keyring
|
||||
# RUN pacman-key --populate archlinuxarm
|
||||
RUN pacman-key --populate archlinux
|
||||
RUN pacman -S --needed --noconfirm archlinux-keyring
|
||||
RUN pacman-key --populate archlinux
|
||||
|
||||
# ###
|
||||
# # INSTALL FILESYSTEM PACKAGE UPDATES
|
||||
# # We have to do this with an alternative root since /etc/{hosts,resolv.conf}
|
||||
# # are mounted read-only by Docker.
|
||||
|
||||
# FROM image-base AS updated-filesystem-base
|
||||
|
||||
# COPY --from=image-base / /target/
|
||||
|
||||
FROM image-base AS updated-filesystem-base
|
||||
|
||||
# RUN \
|
||||
# --mount=type=cache,target=/var/cache/pacman/pkg,sharing=locked \
|
||||
# --mount=type=cache,target=/tmp/build/.cache \
|
||||
# pacman -r /target/ -S --noconfirm --needed filesystem
|
||||
|
||||
# ###
|
||||
# # LAYER USED FOR INSTALLING UPDATES AND ADDITIONAL PACKAGES USED IN FINAL IMAGE
|
||||
|
||||
# FROM scratch AS base
|
||||
|
||||
# COPY --from=updated-filesystem-base /target/ /
|
||||
|
||||
FROM updated-filesystem-base AS base
|
||||
|
||||
# # Install updates
|
||||
# # NOTE - we install fsck helpers for fat and ext4 in this stage to save on time spent on /boot updates
|
||||
# RUN \
|
||||
# --mount=type=cache,target=/var/cache/pacman/pkg,sharing=locked \
|
||||
# --mount=type=cache,target=/tmp/build/.cache \
|
||||
# pacman -Suu --noconfirm --needed dosfstools e2fsprogs
|
||||
|
||||
###
|
||||
# LAYER USED TO COMPILE STUFF
|
||||
|
||||
FROM image-base AS base-devel
|
||||
|
||||
RUN pacman -S --noconfirm base-devel git
|
||||
|
||||
#RUN pacman -S --noconfirm --needed sudo
|
||||
RUN (echo "" && echo "%wheel ALL=(ALL) NOPASSWD: ALL") >> /etc/sudoers
|
||||
|
||||
RUN useradd -r -N -m -G wheel -d /tmp/build -k /var/empty build
|
||||
|
||||
RUN sed -i \
|
||||
-e 's,#MAKEFLAGS=.*,MAKEFLAGS="-j$(getconf _NPROCESSORS_ONLN)",g' \
|
||||
/etc/makepkg.conf
|
||||
|
||||
RUN \
|
||||
--mount=type=cache,target=/tmp/build/.cache \
|
||||
chown -Rv build /tmp/build /tmp/build/.cache
|
||||
|
||||
RUN echo "ParallelDownloads = 5" >>/etc/pacman.conf
|
||||
|
||||
USER build
|
||||
|
||||
# Needed for anything commits
|
||||
RUN git config --global user.email "$(whoami)@localhost"
|
||||
RUN git config --global user.name "Build"
|
||||
|
||||
###
|
||||
# FAKESILENCE
|
||||
|
||||
FROM golang:1 AS fakesilence
|
||||
|
||||
|
@ -15,27 +106,209 @@ RUN go install -v -ldflags "-s -w" github.com/icedream/fakesilence@"${FAKESILENC
|
|||
RUN cp -v "$GOPATH"/bin/* /usr/local/bin
|
||||
|
||||
###
|
||||
# YAY
|
||||
|
||||
# yay build
|
||||
FROM base-devel AS yay
|
||||
|
||||
FROM archlinux
|
||||
WORKDIR /usr/src/yay
|
||||
RUN git clone --recursive https://aur.archlinux.org/yay.git .
|
||||
RUN \
|
||||
--mount=type=cache,target=/var/cache/pacman/pkg,sharing=locked \
|
||||
--mount=type=cache,target=/tmp/build/.cache \
|
||||
makepkg -sr --noconfirm --nocheck
|
||||
|
||||
WORKDIR /usr/src/ndi-feeder/
|
||||
RUN pacman --noconfirm -Sy git sudo make binutils fakeroot base-devel
|
||||
RUN echo "" && echo "%wheel ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers
|
||||
RUN useradd -UMr -d /usr/src/ndi-feeder/ -G wheel app
|
||||
RUN chown -R app .
|
||||
###
|
||||
# BASE DEVEL (YAY)
|
||||
|
||||
USER app
|
||||
RUN git clone --recursive https://aur.archlinux.org/yay.git yay/
|
||||
RUN cd yay && makepkg --noconfirm -si && cd .. && rm -r yay
|
||||
RUN yay --noconfirm -S pod2man && sudo rm -r ~/.cache /var/cache/pacman/*
|
||||
RUN yay --noconfirm -S ndi-advanced-sdk && sudo rm -r ~/.cache /var/cache/pacman/*
|
||||
RUN yay --noconfirm -S ffmpeg-ndi && sudo rm -r ~/.cache /var/cache/pacman/*
|
||||
FROM base-devel AS base-devel-yay
|
||||
|
||||
USER root
|
||||
COPY --from=yay /usr/src/yay/*.pkg.* /tmp/
|
||||
RUN \
|
||||
--mount=type=cache,target=/var/cache/pacman/pkg,sharing=locked \
|
||||
--mount=type=cache,target=/tmp/build/.cache \
|
||||
pacman --noconfirm -U /tmp/*.pkg.* && rm /tmp/*.pkg.*
|
||||
|
||||
USER build
|
||||
|
||||
###
|
||||
# POD2MAN
|
||||
|
||||
FROM base-devel-yay AS pod2man
|
||||
|
||||
WORKDIR /usr/src/pod2man
|
||||
|
||||
RUN git clone --recursive https://aur.archlinux.org/pod2man.git .
|
||||
|
||||
RUN \
|
||||
--mount=type=cache,target=/var/cache/pacman/pkg,sharing=locked \
|
||||
--mount=type=cache,target=/tmp/build/.cache \
|
||||
(. ./PKGBUILD && yay -S --noconfirm --asdeps --provides --needed $(yay -T "${depends[@]}") && (mv -v ~/.cache/yay/*/*.pkg.* . || true))
|
||||
RUN makepkg -sr --noconfirm
|
||||
|
||||
###
|
||||
# NDI-SDK-EMBEDDED
|
||||
|
||||
FROM base-devel-yay AS ndi-sdk-embedded
|
||||
|
||||
WORKDIR /usr/src/ndi-sdk-embedded
|
||||
|
||||
RUN git clone --recursive https://aur.archlinux.org/ndi-sdk-embedded.git .
|
||||
|
||||
RUN \
|
||||
--mount=type=cache,target=/var/cache/pacman/pkg,sharing=locked \
|
||||
--mount=type=cache,target=/tmp/build/.cache \
|
||||
(. ./PKGBUILD && yay -S --noconfirm --asdeps --provides --needed $(yay -T "${depends[@]}") && (mv -v ~/.cache/yay/*/*.pkg.* . || true))
|
||||
RUN makepkg -sr --noconfirm
|
||||
|
||||
###
|
||||
# NDI-SDK
|
||||
|
||||
FROM base-devel-yay AS ndi-sdk
|
||||
|
||||
WORKDIR /usr/src/ndi-sdk
|
||||
RUN git clone --recursive https://aur.archlinux.org/ndi-sdk.git .
|
||||
|
||||
RUN \
|
||||
--mount=type=cache,target=/var/cache/pacman/pkg,sharing=locked \
|
||||
--mount=type=cache,target=/tmp/build/.cache \
|
||||
(. ./PKGBUILD && yay -S --noconfirm --asdeps --provides --needed $(yay -T "${depends[@]}") && (mv -v ~/.cache/yay/*/*.pkg.* . || true))
|
||||
RUN \
|
||||
--mount=type=cache,target=/var/cache/pacman/pkg,sharing=locked \
|
||||
--mount=type=cache,target=/tmp/build/.cache \
|
||||
(. ./PKGBUILD && yay -S --noconfirm --asdeps --provides --needed $(yay -T "${makedepends[@]}"))
|
||||
RUN \
|
||||
--mount=type=cache,target=/var/cache/pacman/pkg,sharing=locked \
|
||||
--mount=type=cache,target=/tmp/build/.cache \
|
||||
makepkg -sr --noconfirm
|
||||
|
||||
###
|
||||
# NDI-ADVANCED-SDK
|
||||
|
||||
FROM base-devel-yay AS ndi-advanced-sdk
|
||||
|
||||
WORKDIR /usr/src/ndi-advanced-sdk
|
||||
RUN git clone --recursive https://aur.archlinux.org/ndi-advanced-sdk.git .
|
||||
|
||||
RUN \
|
||||
--mount=type=cache,target=/var/cache/pacman/pkg,sharing=locked \
|
||||
--mount=type=cache,target=/tmp/build/.cache \
|
||||
(. ./PKGBUILD && yay -S --noconfirm --asdeps --provides --needed $(yay -T "${depends[@]}") && (mv -v ~/.cache/yay/*/*.pkg.* . || true))
|
||||
RUN \
|
||||
--mount=type=cache,target=/var/cache/pacman/pkg,sharing=locked \
|
||||
--mount=type=cache,target=/tmp/build/.cache \
|
||||
(. ./PKGBUILD && yay -S --noconfirm --asdeps --provides --needed $(yay -T "${makedepends[@]}"))
|
||||
RUN \
|
||||
--mount=type=cache,target=/var/cache/pacman/pkg,sharing=locked \
|
||||
--mount=type=cache,target=/tmp/build/.cache \
|
||||
makepkg -sr --noconfirm
|
||||
|
||||
###
|
||||
# FFMPEG-NDI
|
||||
|
||||
FROM base-devel-yay AS ffmpeg-ndi
|
||||
|
||||
WORKDIR /usr/src/ffmpeg-ndi
|
||||
|
||||
USER root
|
||||
# COPY --from=ndi-sdk-embedded /usr/src/ndi-sdk-embedded/*.pkg.* /tmp/
|
||||
COPY --from=ndi-sdk /usr/src/ndi-sdk/*.pkg.* /tmp/
|
||||
COPY --from=pod2man /usr/src/pod2man/*.pkg.* /tmp/
|
||||
RUN \
|
||||
--mount=type=cache,target=/var/cache/pacman/pkg,sharing=locked \
|
||||
--mount=type=cache,target=/tmp/build/.cache \
|
||||
yay --noconfirm -U /tmp/*.pkg.* && rm /tmp/*.pkg.*
|
||||
|
||||
USER build
|
||||
RUN git clone --recursive https://aur.archlinux.org/ffmpeg-ndi.git .
|
||||
RUN \
|
||||
--mount=type=cache,target=/var/cache/pacman/pkg,sharing=locked \
|
||||
--mount=type=cache,target=/tmp/build/.cache \
|
||||
(\
|
||||
. ./PKGBUILD &&\
|
||||
if [ "${#depends[@]}" -eq 0 ]; then exit; fi &&\
|
||||
packages=$(yay -T "${depends[@]}" 2>/dev/null|| true) &&\
|
||||
if [ -z "$packages" ]; then exit; fi &&\
|
||||
yay -S --noconfirm --asdeps --provides --needed $packages &&\
|
||||
find ~/.cache/yay/ -mindepth 2 -maxdepth 2 -name \*.pkg.\* -exec mv {} . \;\
|
||||
)
|
||||
# RUN (. ./PKGBUILD && yay -S --noconfirm --asdeps --provides --needed $(yay -T "${optdepends[@]}") && (mv -v ~/.cache/yay/*/*.pkg.* . || true))
|
||||
RUN \
|
||||
--mount=type=cache,target=/var/cache/pacman/pkg,sharing=locked \
|
||||
--mount=type=cache,target=/tmp/build/.cache \
|
||||
(\
|
||||
. ./PKGBUILD &&\
|
||||
if [ "${#makedepends[@]}" -eq 0 ]; then exit; fi &&\
|
||||
packages=$(yay -T "${makedepends[@]}" 2>/dev/null|| true) &&\
|
||||
if [ -z "$packages" ]; then exit; fi &&\
|
||||
yay -S --noconfirm --asdeps --provides --needed $packages \
|
||||
)
|
||||
RUN \
|
||||
--mount=type=cache,target=/var/cache/pacman/pkg,sharing=locked \
|
||||
--mount=type=cache,target=/tmp/build/.cache \
|
||||
makepkg -sr --noconfirm --nocheck
|
||||
|
||||
###
|
||||
# PERMISSIONS FOR FINAL IMAGE FILES
|
||||
|
||||
FROM busybox AS files
|
||||
|
||||
WORKDIR /target/usr/local/bin/
|
||||
COPY *.sh .
|
||||
RUN dos2unix *.sh
|
||||
RUN chmod -v +x *.sh
|
||||
|
||||
###
|
||||
# PACKAGES
|
||||
|
||||
FROM scratch as packages
|
||||
|
||||
COPY --from=ndi-sdk /usr/src/ndi-sdk/*.pkg.* /packages/
|
||||
COPY --from=ffmpeg-ndi /usr/src/ffmpeg-ndi/*.pkg.* /packages/
|
||||
COPY --from=fakesilence /usr/local/bin/fakesilence /target/usr/local/bin/
|
||||
|
||||
###
|
||||
# PACKAGE INSTALL
|
||||
|
||||
FROM base AS install
|
||||
|
||||
USER root
|
||||
# COPY --from=powerpill /usr/src/powerpill/*.pkg.* /tmp/
|
||||
# RUN \
|
||||
# --mount=type=cache,target=/var/cache/pacman/pkg,sharing=locked \
|
||||
# --mount=type=cache,target=/tmp/build/.cache \
|
||||
# pacman --noconfirm -U /tmp/*.pkg.*; rm /tmp/*.pkg.*
|
||||
|
||||
#COPY --from=yay /usr/src/yay/*.pkg.* /tmp/
|
||||
COPY --from=ndi-sdk /usr/src/ndi-sdk/*.pkg.* /tmp/
|
||||
COPY --from=ffmpeg-ndi /usr/src/ffmpeg-ndi/*.pkg.* /tmp/
|
||||
RUN \
|
||||
--mount=type=cache,target=/var/cache/pacman/pkg,sharing=locked \
|
||||
--mount=type=cache,target=/tmp/build/.cache \
|
||||
rm -f /var/cache/pacman/pkg/cache.lck; pacman --noconfirm -U /tmp/*.pkg.*; rm /tmp/*.pkg.*
|
||||
|
||||
RUN \
|
||||
--mount=type=cache,target=/var/cache/pacman/pkg,sharing=locked \
|
||||
--mount=type=cache,target=/tmp/build/.cache \
|
||||
rm -f /var/cache/pacman/pkg/cache.lck; pacman -S --noconfirm --needed sudo realtime-privileges
|
||||
|
||||
COPY --from=fakesilence /usr/local/bin/fakesilence /usr/local/bin/
|
||||
|
||||
COPY --from=0 /target/ /
|
||||
CMD ["ndi-feeder.sh"]
|
||||
COPY --from=files /target/ /
|
||||
|
||||
RUN rm -rf /var/cache/pacman/pkg/*
|
||||
|
||||
###
|
||||
# FINAL IMAGE
|
||||
|
||||
FROM base AS final-image
|
||||
|
||||
# squash all the package installation into a single
|
||||
COPY --from=install / /
|
||||
|
||||
RUN useradd -m -u 1000 -G wheel,realtime,audio,video ndi-feeder
|
||||
RUN echo "ndi-feeder:ndi-feeder" | chpasswd
|
||||
|
||||
USER ndi-feeder
|
||||
CMD ["ndi-feeder.sh"]
|
||||
STOPSIGNAL SIGTERM
|
||||
|
|
|
@ -9,8 +9,8 @@ replace (
|
|||
|
||||
require (
|
||||
github.com/icedream/go-stagelinq v0.0.1
|
||||
github.com/icedream/livestream-tools/icedreammusic/metacollector e27172751086
|
||||
github.com/icedream/livestream-tools/icedreammusic/tuna d83cb4af0567
|
||||
github.com/icedream/livestream-tools/icedreammusic/metacollector v0.0.0-20221208055945-e27172751086
|
||||
github.com/icedream/livestream-tools/icedreammusic/tuna v0.0.0-20221205042012-d83cb4af0567
|
||||
)
|
||||
|
||||
require golang.org/x/text v0.4.0 // indirect
|
||||
require golang.org/x/text v0.7.0 // indirect
|
||||
|
|
|
@ -10,10 +10,8 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
|
|||
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg=
|
||||
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
|
|
@ -5,7 +5,7 @@ go 1.19
|
|||
require (
|
||||
github.com/gin-contrib/cors v1.4.0
|
||||
github.com/gin-gonic/gin v1.9.0
|
||||
github.com/icedream/livestream-tools/icedreammusic/tuna v0.0.0-20230302092605-950fba55448e
|
||||
github.com/icedream/livestream-tools/icedreammusic/tuna v0.0.0-20230302143311-ad3e9704c029
|
||||
)
|
||||
|
||||
require (
|
||||
|
|
|
@ -3,7 +3,7 @@ module github.com/icedream/livestream-tools/icedreammusic/tunaposter
|
|||
go 1.19
|
||||
|
||||
require (
|
||||
github.com/icedream/livestream-tools/icedreammusic/tuna d83cb4af0567
|
||||
github.com/icedream/livestream-tools/icedreammusic/tuna v0.0.0-20221205042012-d83cb4af0567
|
||||
gopkg.in/alecthomas/kingpin.v3-unstable v3.0.0-20191105091915-95d230a53780
|
||||
)
|
||||
|
||||
|
|
|
@ -3,21 +3,6 @@
|
|||
"config:base"
|
||||
],
|
||||
"packageRules": [
|
||||
{
|
||||
"matchPackagePatterns": [
|
||||
"github.com/icedream/livestream-tools/icedreammusic/*"
|
||||
],
|
||||
"automerge": false
|
||||
},
|
||||
{
|
||||
"matchUpdateTypes": [
|
||||
"minor",
|
||||
"patch",
|
||||
"pin",
|
||||
"digest"
|
||||
],
|
||||
"automerge": true
|
||||
},
|
||||
{
|
||||
"matchPackageNames": [
|
||||
"savonet/liquidsoap-ci-build"
|
||||
|
@ -39,4 +24,4 @@
|
|||
"packageNameTemplate": "homebrew/{{{ lowercase depNameUppercase }}}"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue