1
0
Fork 0

Compare commits

..

10 Commits

16 changed files with 645 additions and 104 deletions

View File

@ -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

View File

@ -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=

View File

@ -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
}
}
}

View File

@ -26,7 +26,7 @@ then
rm winfsp.zip rm winfsp.zip
fi fi
for bin in foobar2000 tunadish tunaposter prime4 for bin in auto-restart-voicemeeter foobar2000 tunadish tunaposter prime4
do do
cd "$SCRIPT_DIR/$bin" cd "$SCRIPT_DIR/$bin"
go build -ldflags "-s -w" -o "$GOBIN/$bin$ext" -v go build -ldflags "-s -w" -o "$GOBIN/$bin$ext" -v

View File

@ -11,8 +11,8 @@ require (
github.com/billziss-gh/cgofuse v1.5.0 github.com/billziss-gh/cgofuse v1.5.0
github.com/dhowden/tag v0.0.0-20220618230019-adf36e896086 github.com/dhowden/tag v0.0.0-20220618230019-adf36e896086
github.com/gin-gonic/gin v1.9.0 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/metacollector v0.0.0-20230302143311-ad3e9704c029
github.com/icedream/livestream-tools/icedreammusic/tuna v0.0.0-20230302092605-950fba55448e github.com/icedream/livestream-tools/icedreammusic/tuna v0.0.0-20230302143311-ad3e9704c029
gopkg.in/alecthomas/kingpin.v3-unstable v3.0.0-20191105091915-95d230a53780 gopkg.in/alecthomas/kingpin.v3-unstable v3.0.0-20191105091915-95d230a53780
) )

View File

@ -1,5 +1,5 @@
# ARG IMAGE=savonet/liquidsoap:master # 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 # FROM $IMAGE

View File

@ -2,7 +2,7 @@
# Our Denon StagelinQ receiver will send the metadata to this interface. # 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! # 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) = def http_export_meta(%argsof(json.stringify), m) =
j = json() j = json()
@ -16,7 +16,10 @@ def setup_harbor_metadata_api(~metadata_api_port=21338, s) =
s = insert_metadata(s) s = insert_metadata(s)
# Handler for fetching metadata # 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() ?? [] m = s.last_metadata() ?? []
# remove cover info and link to it instead if existing # 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) m = metadata.cover.remove(m)
data = http_export_meta(compact=true, 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-origin","*"),
("access-control-allow-credentials","true"), ("access-control-allow-credentials","true"),
("access-control-allow-methods","GET,POST"), ("access-control-allow-methods","GET,POST"),
@ -47,11 +50,14 @@ def setup_harbor_metadata_api(~metadata_api_port=21338, s) =
end end
# Handler for fetching current cover art # 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() ?? [] m = s.last_metadata() ?? []
cover = metadata.cover(m) ?? "".{mime="text/plain"} cover = metadata.cover(m) ?? "".{mime="text/plain"}
if string.length(cover) > 0 then 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-origin","*"),
("access-control-allow-credentials","true"), ("access-control-allow-credentials","true"),
("access-control-allow-methods","GET,POST"), ("access-control-allow-methods","GET,POST"),
@ -61,7 +67,7 @@ def setup_harbor_metadata_api(~metadata_api_port=21338, s) =
("content-type", cover.mime), ("content-type", cover.mime),
], data=string_of(cover)) ], data=string_of(cover))
else 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-origin","*"),
("access-control-allow-credentials","true"), ("access-control-allow-credentials","true"),
("access-control-allow-methods","GET,POST"), ("access-control-allow-methods","GET,POST"),
@ -73,7 +79,11 @@ def setup_harbor_metadata_api(~metadata_api_port=21338, s) =
end end
# Handler for receiving metadata # 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 : { let json.parse (data : {
data: [(string * string)] as json.object data: [(string * string)] as json.object
}) = data }) = data
@ -108,7 +118,7 @@ def setup_harbor_metadata_api(~metadata_api_port=21338, s) =
# set metadata on stream # set metadata on stream
s.insert_metadata(new_track=new_track, m) 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"), ("allow","POST"),
("access-control-allow-origin","*"), ("access-control-allow-origin","*"),
("access-control-allow-credentials","true"), ("access-control-allow-credentials","true"),
@ -119,8 +129,11 @@ def setup_harbor_metadata_api(~metadata_api_port=21338, s) =
end end
# Just in case we use a browser to send data to this (for example while emulating Tuna) # 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) = def on_http_metadata_cors(request) =
http.response(protocol=protocol, code=200, headers=[ http_version = request.http_version
headers = request.headers
http.response(http_version=http_version, status_code=200, headers=[
("allow","POST"), ("allow","POST"),
("access-control-allow-origin","*"), ("access-control-allow-origin","*"),
("access-control-allow-credentials","true"), ("access-control-allow-credentials","true"),
@ -130,17 +143,17 @@ def setup_harbor_metadata_api(~metadata_api_port=21338, s) =
], data="POST") ], data="POST")
end end
harbor.http.register(port=metadata_api_port, method="GET", "/#{id}/meta", on_http_get_metadata) harbor.http.register.simple(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="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.simple(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="OPTIONS", "/#{id}/cover", on_http_metadata_cors)
harbor.http.register(port=metadata_api_port, method="POST", "/#{id}", on_http_metadata) harbor.http.register.simple(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="OPTIONS", "/#{id}", on_http_metadata_cors)
harbor.http.register(port=metadata_api_port, method="POST", "/", on_http_metadata) harbor.http.register.simple(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="OPTIONS", "/", on_http_metadata_cors)
s s
end end

View File

@ -1,8 +1,8 @@
stream_name=getenv(default="Spontaneously live in the mix", "STREAM_NAME") stream_name=environment.get(default="Spontaneously live in the mix", "STREAM_NAME")
stream_description=getenv(default="", "STREAM_DESCRIPTION") stream_description=environment.get(default="", "STREAM_DESCRIPTION")
internal_icecast_username=getenv(default="source", "INTERNAL_ICECAST_USERNAME") internal_icecast_username=environment.get(default="source", "INTERNAL_ICECAST_USERNAME")
internal_icecast_password=getenv(default="source", "INTERNAL_ICECAST_PASSWORD") internal_icecast_password=environment.get(default="source", "INTERNAL_ICECAST_PASSWORD")
rektfm_username=getenv("REKTNETWORK_USERNAME") rektfm_username=environment.get("REKTNETWORK_USERNAME")
rektfm_password=getenv("REKTNETWORK_PASSWORD") rektfm_password=environment.get("REKTNETWORK_PASSWORD")

View File

@ -22,18 +22,32 @@ set("sandbox", "disabled")
s = input.http(id="input_ice_main", max_buffer=4., "http://127.0.0.1:61120/main") s = input.http(id="input_ice_main", max_buffer=4., "http://127.0.0.1:61120/main")
# Split audio off to be handled specially # 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) a = mksafe_soft(a)
output.dummy(a) output.dummy(a)
def append_encoder_meta(m) = def append_encoder_meta(_) =
[ new_meta = [
("encoder", "Liquidsoap #{liquidsoap.version}"), ("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 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) a = setup_harbor_metadata_api(a)
@ -55,10 +69,10 @@ def internal_icecast(
headers=[], headers=[],
port=61120, port=61120,
host="127.0.0.1", host="127.0.0.1",
user=internal_icecast_username, user=null.get(internal_icecast_username),
password=internal_icecast_password, password=null.get(internal_icecast_password),
name=stream_name, name=null.get(stream_name),
description=stream_description, description=null.get(stream_description),
e, s) e, s)
end end
@ -123,7 +137,7 @@ setup_harbor_stream_api(internal_icecast(
)) ))
# REKT.fm # 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( setup_harbor_stream_api(output.icecast(
id="out_a_rekt", id="out_a_rekt",
# %ogg(%flac), # %ogg(%flac),
@ -133,10 +147,10 @@ if string.length(rektfm_username) > 0 and string.length(rektfm_password) > 0 the
port=60000, port=60000,
host="stream.rekt.network", host="stream.rekt.network",
# host="stream.rekt.fm", # host="stream.rekt.fm",
user=rektfm_username, user=null.get(rektfm_username),
name=stream_name, name=null.get(stream_name),
description=stream_description, description=null.get(stream_description),
password=rektfm_password, password=null.get(rektfm_password),
start=false, start=false,
%ffmpeg( %ffmpeg(
format="ogg", format="ogg",

View File

@ -3,35 +3,34 @@ stream_api_port=21336
interactive.harbor(port=stream_api_port, uri="/interactive") # expose through stream API port 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 # 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 setup_harbor_stream_api_general()
def on_list(~protocol, ~data, ~headers, uri) = def on_list(_) =
data = stream_api_streams http.response(status_code=200, headers=[
http.response(protocol=protocol, code=200, headers=[
("content-type","application/json"), ("content-type","application/json"),
], data=json.stringify(data)) ], data=json.stringify(stream_api_streams()))
end 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 end
def setup_harbor_stream_api(s) = def setup_harbor_stream_api(s) =
def on_start(~protocol, ~data, ~headers, uri) = def on_start(_) =
s.start() s.start()
http.response(protocol=protocol, code=200, headers=[ http.response(status_code=200, headers=[
("content-type","application/json"), ("content-type","application/json"),
], data=json.stringify([])) ], data=json.stringify([]))
end end
def on_stop(~protocol, ~data, ~headers, uri) = def on_stop(_) =
s.stop() s.stop()
http.response(protocol=protocol, code=200, headers=[ http.response(status_code=200, headers=[
("content-type","application/json"), ("content-type","application/json"),
], data=json.stringify([])) ], data=json.stringify([]))
end end
def on_info(~protocol, ~data, ~headers, uri) = def on_info(_) =
data = [ data = [
("id", s.id()), ("id", s.id()),
("last_metadata", json.stringify(s.last_metadata())), ("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_ready", json.stringify(s.is_ready())),
("is_active", json.stringify(s.is_active())), ("is_active", json.stringify(s.is_active())),
] ]
http.response(protocol=protocol, code=200, headers=[ http.response(status_code=200, headers=[
("content-type","application/json"), ("content-type","application/json"),
], data=json.stringify(data)) ], data=json.stringify(data))
end 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.simple(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.simple(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="GET", "/streams/#{s.id()}", on_info)
s s
end end

View File

@ -1,11 +1,102 @@
FROM busybox # syntax=docker/dockerfile:1.2.1
WORKDIR /target/usr/local/bin/ FROM alpine AS rootfs
COPY *.sh .
RUN dos2unix *.sh RUN apk add --no-cache gnupg
RUN chmod -v +x *.sh
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 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 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 # BASE DEVEL (YAY)
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 .
USER app FROM base-devel AS base-devel-yay
RUN git clone --recursive https://aur.archlinux.org/yay.git yay/
RUN cd yay && makepkg --noconfirm -si && cd .. && rm -r yay USER root
RUN yay --noconfirm -S pod2man && sudo rm -r ~/.cache /var/cache/pacman/* COPY --from=yay /usr/src/yay/*.pkg.* /tmp/
RUN yay --noconfirm -S ndi-advanced-sdk && sudo rm -r ~/.cache /var/cache/pacman/* RUN \
RUN yay --noconfirm -S ffmpeg-ndi && sudo rm -r ~/.cache /var/cache/pacman/* --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=fakesilence /usr/local/bin/fakesilence /usr/local/bin/
COPY --from=0 /target/ / COPY --from=files /target/ /
CMD ["ndi-feeder.sh"]
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 STOPSIGNAL SIGTERM

View File

@ -9,8 +9,8 @@ replace (
require ( require (
github.com/icedream/go-stagelinq v0.0.1 github.com/icedream/go-stagelinq v0.0.1
github.com/icedream/livestream-tools/icedreammusic/metacollector e27172751086 github.com/icedream/livestream-tools/icedreammusic/metacollector v0.0.0-20221208055945-e27172751086
github.com/icedream/livestream-tools/icedreammusic/tuna d83cb4af0567 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

View File

@ -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 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 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.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
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/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 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/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= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@ -5,7 +5,7 @@ go 1.19
require ( require (
github.com/gin-contrib/cors v1.4.0 github.com/gin-contrib/cors v1.4.0
github.com/gin-gonic/gin v1.9.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 ( require (

View File

@ -3,7 +3,7 @@ module github.com/icedream/livestream-tools/icedreammusic/tunaposter
go 1.19 go 1.19
require ( 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 gopkg.in/alecthomas/kingpin.v3-unstable v3.0.0-20191105091915-95d230a53780
) )

View File

@ -3,21 +3,6 @@
"config:base" "config:base"
], ],
"packageRules": [ "packageRules": [
{
"matchPackagePatterns": [
"github.com/icedream/livestream-tools/icedreammusic/*"
],
"automerge": false
},
{
"matchUpdateTypes": [
"minor",
"patch",
"pin",
"digest"
],
"automerge": true
},
{ {
"matchPackageNames": [ "matchPackageNames": [
"savonet/liquidsoap-ci-build" "savonet/liquidsoap-ci-build"
@ -39,4 +24,4 @@
"packageNameTemplate": "homebrew/{{{ lowercase depNameUppercase }}}" "packageNameTemplate": "homebrew/{{{ lowercase depNameUppercase }}}"
} }
] ]
} }