sane-cast: support casting audio
This commit is contained in:
@@ -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
|
||||
|
||||
|
@@ -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" ]
|
||||
|
@@ -1,7 +1,7 @@
|
||||
{ static-nix-shell }:
|
||||
static-nix-shell.mkPython3 {
|
||||
pname = "sane-cast";
|
||||
pkgs = [ "go2tv" ];
|
||||
pkgs = [ "blast-ugjka" "go2tv" ];
|
||||
srcRoot = ./.;
|
||||
}
|
||||
|
||||
|
@@ -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()
|
||||
|
Reference in New Issue
Block a user