Script para silenciar una aplicación

14

Mi objetivo es poder silenciar la aplicación Spotify, no todo el sistema. Usando el comando: ps -C spotify -o pid=Puedo encontrar la ID de proceso de Spotify, en este caso la ID es "22981". Con ese proceso de identificación me gustaría buscar de esta lista: pacmd list-sink-inputs. Ese comando devuelve una lista como esta:

eric@eric-desktop:~$ pacmd list-sink-inputs
Welcome to PulseAudio! Use "help" for usage information.
>>> 1 sink input(s) available.
    index: 0
    driver: <protocol-native.c>
    flags: START_CORKED 
    state: RUNNING
    sink: 1 <alsa_output.pci-0000_00_1b.0.analog-stereo>
    volume: 0: 100% 1: 100%
            0: -0.00 dB 1: -0.00 dB
            balance 0.00
    muted: no
    current latency: 1019.80 ms
    requested latency: 371.52 ms
    sample spec: s16le 2ch 44100Hz
    channel map: front-left,front-right
                 Stereo
    resample method: (null)
    module: 8
    client: 10 <Spotify>
    properties:
        media.role = "music"
        media.name = "Spotify"
        application.name = "Spotify"
        native-protocol.peer = "UNIX socket client"
        native-protocol.version = "26"
        application.process.id = "22981"
        application.process.user = "eric"
        application.process.host = "eric-desktop"
        application.process.binary = "spotify"
        window.x11.display = ":0"
        application.language = "en_US.UTF-8"
        application.process.machine_id = "058c89ad77c15e1ce0dd5a7800000012"
        application.process.session_id = "058c89ad77c15e1ce0dd5a7800000012-1345692739.486413-85297109"
        application.icon_name = "spotify-linux-512x512"
        module-stream-restore.id = "sink-input-by-media-role:music"

Ahora me gustaría correlacionar el application.process.id = "22981"índice de entrada al sumidero que en este caso es index: 0. Ahora, con ese número de índice, necesitaría ejecutar este comando: pacmd set-sink-input-mute 0 1silenciar Spotify y pacmd set-sink-input-mute 0 0activar Silenciar Spotify. Para esos comandos, el primer número necesitaría ser reemplazado por el número de índice encontrado anteriormente, y el siguiente número es el booleano para activar o desactivar el silencio. ¿Cómo puedo poner todo esto en un script, para que pueda obtener un comando para silenciar / activar la aplicación de Spotify?

EDITAR: Ambos scripts a continuación funcionan como se esperaba, ¿alguien puede agregar un conmutador que verifique muted: yeso muted: noluego, silenciar o activar el silencio en consecuencia?

EDITAR: pude modificar el script de glenn jackman para agregar la palanca:

#!/bin/bash

main() {
    local action=toggle
    while getopts :mu option; do 
        case "$option" in 
            m) action=mute ;;
            u) action=unmute ;;
            ?) usage 1 "invalid option: -$OPTARG" ;;
        esac
    done
    shift $((OPTIND - 1))
    local pid=$(pidof "$1")
    if [[ -z "$pid" ]]; then
        echo "error: no running processes for: $1" >&2
    elif [[ "$1" ]]; then
        $action "$1"
    else
        usage 1 "specify an application name" 
    fi
}

usage() {
    [[ "$2" ]] && echo "error: $2"
    echo "Usage: $0 [-m | -u] appname"
    echo "Default: toggle mute"
    echo "Arguments:"
    echo "-m = mute application"
    echo "-u = unmute application"
    exit $1
}

toggle() {
    local status=$(get_status "$1")
    if [[ "$status" == "yes" ]]; then
      unmute "$1"
    elif [[ "$status" == "no" ]]; then
      mute "$1"
    fi
}

mute()   { adjust_muteness "$1" 1; }
unmute() { adjust_muteness "$1" 0; }

adjust_muteness() { 
    local index=$(get_index "$1")
    local status=$(get_status "$1")
    [[ "$index" ]] && pacmd set-sink-input-mute "$index" $2 >/dev/null 
}

get_index() {
    local pid=$(pidof "$1")
    pacmd list-sink-inputs | 
    awk -v pid=$pid '
    $1 == "index:" {idx = $2} 
    $1 == "application.process.id" && $3 == "\"" pid "\"" {print idx; exit}
    '
}

get_status() {
   local pid=$(pidof "$1")
   pacmd list-sink-inputs | 
   awk -v pid=$pid '
   $1 == "muted:" {idx = $2} 
   $1 == "application.process.id" && $3 == "\"" pid "\"" {print idx; exit}
   '
}

main "$@"
era878
fuente
¿Por qué no usar pactl list sink-inputs? entonces funcionará a través de la red.
Janus Troelsen

Respuestas:

13

Aquí está mi opinión sobre su interesante desafío:

#!/bin/bash

main() {
    local action=mute
    while getopts :hu option; do 
        case "$option" in 
            h) usage 0 ;;
            u) action=unmute ;;
            ?) usage 1 "invalid option: -$OPTARG" ;;
        esac
    done
    shift $((OPTIND - 1))

    if [[ "$1" ]]; then
        $action "$1"
    else
        usage 1 "specify an application name" 
    fi
}

usage() {
    [[ "$2" ]] && echo "error: $2"
    echo "usage: $0 [-h] [-u] appname"
    echo "where: -u = ummute application (default action is to mute)"
    exit $1
}

mute()   { adjust_muteness "$1" 1; }
unmute() { adjust_muteness "$1" 0; }

adjust_muteness() { 
    local index=$(get_index "$1")
    [[ "$index" ]] && pacmd set-sink-input-mute "$index" $2 >/dev/null 
}

get_index() {
    local pid=$(pidof "$1")
    if [[ -z "$pid" ]]; then
        echo "error: no running processes for: $1" >&2
    else
        pacmd list-sink-inputs | 
        awk -v pid=$pid '
            $1 == "index:" {idx = $2} 
            $1 == "application.process.id" && $3 == "\"" pid "\"" {print idx; exit}
        '
    fi
}

main "$@"
Glenn Jackman
fuente
Esto funciona perfectamente también
era878
@ era878, me gusta la idea de alternar ser la acción predeterminada. Sin embargo, su get_statusfunción solo encontrará las líneas "silenciadas" sin verificar que el estado pertenece a la aplicación adecuada. Vuelva a leer mi get_indexfunción para los detalles.
Glenn Jackman
3
nice awk skills :)
hytromo
@glennjackman, sí, lo descubrí después de un tiempo. Creo que el script que acabo de publicar funciona correctamente ahora.
era878
1
Detalles: awk -v var=val. Awk recorre las líneas, 1 por 1, intenta hacer coincidir cualquiera de las $1 == ...declaraciones, ejecuta el código entre paréntesis en la coincidencia y continúa. La primera declaración coincide en las líneas cuya primera palabra es index:, y almacena la segunda palabra (INDICE SINK) en idxvariable. Por lo tanto, idxse sobrescribe en la siguiente index: <SINK INDEX>línea hasta que awk coincida con la segunda instrucción ( $1= application.process.id, $2= =, $3= <expected pid val>). Cuando esta segunda declaración coincide, awk imprime idx(que es la última línea que coincide con la primera declaración index:) y sale.
KrisWebDev
7

gracias por la solucion! Logré usar los scripts provistos aquí para solucionar mi problema. Como tuve que modificarlos un poco, aquí me uno a la versión mejorada.

La razón por la que los scripts originales no funcionaron para mí es porque algunas aplicaciones pueden tener varias instancias, es decir, varios PID, pero tal vez solo una de ellas produce sonido y, por lo tanto, está realmente conectada a Pulseaudio. Dado que el script solo usaba el primer PID encontrado, normalmente / no / silenciaría la aplicación deseada.

Así que aquí hay una versión donde el argumento es el nombre de la aplicación registrada en PulseAudio. Puede encontrar este nombre ejecutando el pacmd list-sink-inputscomando y buscando el application.namecampo.

Una solución alternativa sería activar / desactivar todos los PID que tienen el mismo nombre de aplicación.

#!/bin/bash

# Adapter from glenn jackman on http://askubuntu.com/questions/180612/script-to-mute-an-application
# to depend directly on the name of the PulseAudio client
# rather than the application name (several instances of one application could
# run while only one is connected to PulseAudio)

# Possible further improvement: it could be useful to also mute all clients having
# the specified name. Here, only the first one is muted.

#!/bin/bash

main() {
    local action=mute
    while getopts :hu option; do
        case "$option" in
            h) usage 0 ;;
            u) action=unmute ;;
            ?) usage 1 "invalid option: -$OPTARG" ;;
        esac
    done
    shift $((OPTIND - 1))

    if [[ "$1" ]]; then
        $action "$1"
    else
        usage 1 "specify the name of a PulseAudio client"
    fi
}

usage() {
    [[ "$2" ]] && echo "error: $2"
    echo "usage: $0 [-h] [-u] appname"
    echo "where: -u = ummute application (default action is to mute)"
    exit $1
}

mute()   { adjust_muteness "$1" 1; }
unmute() { adjust_muteness "$1" 0; }

adjust_muteness() {
    local index=$(get_index "$1")
    if [[ -z "$index" ]]; then
        echo "error: no PulseAudio sink named $1 was found" >&2
    else
        [[ "$index" ]] && pacmd set-sink-input-mute "$index" $2 >/dev/null
    fi
}

get_index() {
#    local pid=$(pidof "$1")
#    if [[ -z "$pid" ]]; then
#        echo "error: no running processes for: $1" >&2
#    else
        pacmd list-sink-inputs |
        awk -v name=$1 '
            $1 == "index:" {idx = $2}
            $1 == "application.name" && $3 == "\"" name "\"" {print idx; exit}
        '
#    fi
}

main "$@"
Punta
fuente
6

Aunque la pregunta es pedir un guión, quería dejar esto aquí.

He escrito una aplicación C que hace esto en Ubuntu. Aún mejor, se sienta en la bandeja del indicador (usando libappindicator) y comprueba lo que está reproduciendo Spotify, en intervalos cortos. Si está reproduciendo un anuncio (se compara con una lista negra), silencia Spotify. Si se está reproduciendo un nuevo anuncio, simplemente haga clic en Silencio en el menú indicador y lo agrega a la lista negra.

Lo que hace es buscar una ventana X, para la cual XFetchNameregresa Spotify - Linux Preview. Luego llama XGetWindowPropertypara consultar la _NET_WM_ICON_NAMEpropiedad de esa ventana, que devuelve una cadena en el "Spotify – <Artist> – <Song>"formato. Al reproducir anuncios, devuelve algo como esto:

"Spotify – Spotify – Premium Free Trial Cancel Any Time"

Mantiene un Árbol de búsqueda ternario de la lista de anuncios, para verificar de manera eficiente si el título actual está en la lista.

También utiliza la API asincrónica PulseAudio para consultar sink-inputsy set-mute:

pa_context_get_sink_input_info_list()
pa_context_set_sink_input_mute()

Dado que es solo un código C simple, es liviano. Consulte el código fuente y el .debpaquete de Ubuntu en: indicator-muteads . Probablemente superaría un script de shell en 2-3 órdenes de magnitud.

mkayaalp
fuente
no funciona con la versión 1.0.11
Janus Troelsen
4

En primer lugar, la forma "más correcta" de encontrar el PID de una aplicación como spotify, es usar:

pidof spotify

He creado un script que hace el trabajo, no sé si es la mejor manera de hacerlo, pero funciona perfectamente:

#!/bin/bash
# Script to mute an application using PulseAudio, depending solely on
# process name, constructed as answer on askubuntu.com: 
# http://askubuntu.com/questions/180612/script-to-mute-an-application

#It works as: mute_application.sh vlc mute OR mute_application.sh vlc unmute

if [ -z "$1" ]; then
   echo "Please provide me with an application name"
   exit 1
fi

if [ -z "$2" ]; then
   echo "Please provide me with an action mute/unmute after the application name"
   exit 1
fi

if ! [[ "$2" == "mute" || "$2" == "unmute" ]]; then
   echo "The 2nd argument must be mute/unmute"
   exit 1
fi

process_id=$(pidof "$1")

if [ $? -ne 0 ]; then
   echo "There is no such process as "$1""
   exit 1
fi

temp=$(mktemp)

pacmd list-sink-inputs > $temp

inputs_found=0;
current_index=-1;

while read line; do
   if [ $inputs_found -eq 0 ]; then
      inputs=$(echo -ne "$line" | awk '{print $2}')
      if [[ "$inputs" == "to" ]]; then
         continue
      fi
      inputs_found=1
   else
      if [[ "${line:0:6}" == "index:" ]]; then
         current_index="${line:7}"
      elif [[ "${line:0:25}" == "application.process.id = " ]]; then
         if [[ "${line:25}" == "\"$process_id\"" ]]; then
            #index found...
            break;
         fi
      fi
   fi
done < $temp

rm -f $temp

if [ $current_index -eq -1 ]; then
   echo "Could not find "$1" in the processes that output sound."
   exit 1
fi

#muting...
if [[ "$2" == "mute" ]]; then
   pacmd set-sink-input-mute "$current_index" 1 > /dev/null 2>&1
else
   pacmd set-sink-input-mute "$current_index" 0 > /dev/null 2>&1
fi

exit 0

Puede trabajar con es como:

./mute_application.sh spotify mute

o

./mute_application.sh spotify unmute

Probado con Audacious y Vlc ejecutando y silenciando / activando solo uno de ellos.

hytromo
fuente
Script perfecto, funciona como se esperaba
era878
1

Realmente no puedo escribir, pero he modificado el script de hakermania para crear otro.

Este aumentará o disminuirá el volumen de la aplicación específica en incrementos del 5%:

editar: en realidad, está funcionando siempre cambiando la última aplicación abierta. Ideas?

#!/bin/bash
# Script to increase or decrease an individual application's volume using PulseAudio, depending solely on
# process name, based on another script by hakermania, constructed as answer on askubuntu.com: 
# http://askubuntu.com/questions/180612/script-to-mute-an-application

# It works as: change_app_volume.sh vlc increase OR change_app_volume.sh vlc decrease
# Set desired increments in lines #66 and #68

if [ -z "$1" ]; then
   echo "Please provide me with an application name"
   exit 1
fi

if [ -z "$2" ]; then
   echo "Please provide me with an action increase/decrease after the application name"
   exit 1
fi

if ! [[ "$2" == "increase" || "$2" == "decrease" ]]; then
   echo "The 2nd argument must be increase/decrease"
   exit 1
fi

process_id=$(pidof "$1")

if [ $? -ne 0 ]; then
   echo "There is no such process as "$1""
   exit 1
fi

temp=$(mktemp)

pacmd list-sink-inputs > $temp

inputs_found=0;
current_index=-1;

while read line; do
   if [ $inputs_found -eq 0 ]; then
      inputs=$(echo -ne "$line" | awk '{print $2}')
      if [[ "$inputs" == "to" ]]; then
         continue
      fi
      inputs_found=1
   else
      if [[ "${line:0:6}" == "index:" ]]; then
         current_index="${line:7}"
      elif [[ "${line:0:25}" == "application.process.id = " ]]; then
         if [[ "${line:25}" == "\"$process_id\"" ]]; then
            #index found...
            break;
         fi
      fi
   fi
done < $temp

rm -f $temp

if [ $current_index -eq -1 ]; then
   echo "Could not find "$1" in the processes that output sound."
   exit 1
fi

#increase/decrease...
if [[ "$2" == "increase" ]]; then
   pactl set-sink-input-volume "$current_index" +5% > /dev/null 2>&1
else
   pactl set-sink-input-volume "$current_index" -5% > /dev/null 2>&1
fi

exit 0
RomuNe
fuente
0

Script editado para silenciar todas las entradas de una aplicación (procesos múltiples) y valores predeterminados para alternar:

#!/bin/bash

main() {
    local action=toggle
    while getopts :hu option; do
        case "$option" in
            h) usage 0 ;;
            m) action=mute ;;
            u) action=unmute ;;
            ?) usage 1 "invalid option: -$OPTARG" ;;
        esac
    done
    shift $((OPTIND - 1))

    if [[ "$1" ]]; then
        $action "$1"
    else
        usage 1 "specify an application name"
    fi
}

usage() {
    [[ "$2" ]] && echo "error: $2"
    echo "usage: $0 [-h] [-u] appname"
    echo "where: -u = ummute , -m = mute (default action is to toggle)"
    exit $1
}

mute()   { adjust_muteness "$1" 1; }
unmute() { adjust_muteness "$1" 0; }
toggle() { adjust_muteness "$1" toggle; }

adjust_muteness() {
    clients=$(pactl list clients short | awk '/[0-9]+.*'$1'.*/{print $1}')
    inputs=$(pactl list sink-inputs short)
    for c in $clients; do
        for i in $(printf '%s' "$inputs" | awk '/[0-9]+\s[0-9]+\s'$c'/{print $1}'); do
            pactl set-sink-input-mute $i $2 &
        done
    done
}

main "$@"
deshacer
fuente