sane-cast: support casting audio

This commit is contained in:
2024-09-21 08:29:23 +00:00
parent f7b4e5ba98
commit 9a4635c93b
4 changed files with 43 additions and 7 deletions

View File

@@ -1079,7 +1079,8 @@ in
sane-cast.sandbox.method = "bunpen";
sane-cast.sandbox.net = "clearnet";
sane-cast.sandbox.autodetectCliPaths = "existingFile";
sane-cast.suggestedPrograms = [ "go2tv" ];
sane-cast.sandbox.whitelistAudio = true; #< for blast audio casting
sane-cast.suggestedPrograms = [ "blast-ugjka" "go2tv" ];
sane-die-with-parent.sandbox.enable = false; #< it's a launcher; can't sandbox

View File

@@ -7,6 +7,11 @@
# based on: <repo:pipewire/pipewire:src/daemon/filter-chain/source-duplicate-FL.conf>
# but modified:
# - duplicate both FL *and* FR
# - don't pipe inputs into outputs
# this effectively creates a filter who's output is always silence.
# blast still works because it grabs from the monitor, not the output.
# better would be to make a sink (a "virtual sink", a "null sink", or a "loopback"), which has no output at *all*,
# but i couldn't get that to actually work (e.g. it doesn't show up in PavuControl, or it does but still forwards audio)
context.modules = [
{ name = libpipewire-module-filter-chain
@@ -40,8 +45,8 @@ context.modules = [
links = [
# we can only tee from nodes, not inputs so we need
# to copy the inputs and then tee.
{ output = "copyIL:Out" input = "copyOL:In" }
{ output = "copyIR:Out" input = "copyOR:In" }
# { output = "copyIL:Out" input = "copyOL:In" }
# { output = "copyIR:Out" input = "copyOR:In" }
]
inputs = [ "copyIL:In" "copyIR:In" ]
outputs = [ "copyOL:Out" "copyOR:Out" ]

View File

@@ -1,7 +1,7 @@
{ static-nix-shell }:
static-nix-shell.mkPython3 {
pname = "sane-cast";
pkgs = [ "go2tv" ];
pkgs = [ "blast-ugjka" "go2tv" ];
srcRoot = ./.;
}

View File

@@ -1,5 +1,5 @@
#!/usr/bin/env nix-shell
#!nix-shell -i python3 -p go2tv -p python3
#!nix-shell -i python3 -p blast-ugjka -p go2tv -p python3
# vim: set filetype=python :
"""
cast media (local video or audio files) to a device on the same network
@@ -13,6 +13,7 @@ from enum import Enum
import argparse
import logging
import os
import socket
import subprocess
import tempfile
@@ -125,6 +126,31 @@ class Go2TvDriver:
os.execvp("go2tv", cli_args)
class BlastDriver:
def cast_to(self, dev: Device) -> None:
blast_args = [
"blast",
# "blast.monitor" source will create a new output, or we can do that in pipewire config for better predictability.
"-source", "effect_input.virtual.monitor",
"-device", dev.model,
"-ip", get_ranked_ip_addrs()[0],
]
if dev.compat != Compat.RenameToMp4:
blast_args += [ "-usewav" ];
os.execvp("blast", blast_args)
def get_ranked_ip_addrs():
"""
return the IP addresses most likely to be LAN addresses
based on: <https://stackoverflow.com/a/1267524>
"""
_name, _aliases, static_addrs = socket.gethostbyname_ex(socket.gethostname())
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.connect(("1", 53))
con_addr, _port = s.getsockname()
return sorted(set(static_addrs + [ con_addr ]), key=lambda a: (a.startswith("127"), a))
def filter_devices(devices: list[Device], filter: str) -> list[Device]:
return [d for d in devices if d.matches(filter)]
@@ -179,13 +205,14 @@ def main():
parser.add_argument("--verbose", action="store_true", help="more logging")
parser.add_argument("--always-ask", action="store_true", help="always ask which device to cast to, regardless how many are available")
parser.add_argument("--device", help="filter devices based on if this string is contained in their name (case-insensitive)")
parser.add_argument("media", help="file or URL to send to the DLNA device")
parser.add_argument("media", nargs="?", help="file or URL to send to the DLNA device. empty to case just an audio stream")
args = parser.parse_args()
if args.verbose:
logger.setLevel(logging.DEBUG)
blast = BlastDriver()
go2tv = Go2TvDriver()
devices = go2tv.scan_devices()
@@ -197,7 +224,10 @@ def main():
if dev is None or args.always_ask:
dev = ask_device(devices)
if dev:
go2tv.cast_to(dev, args.media)
if args.media:
go2tv.cast_to(dev, args.media)
else:
blast.cast_to(dev)
if __name__ == "__main__":
main()