17

My goal is to be able to mute the Spotify application, not the entire system. Using the command: ps -C spotify -o pid= I am able to find the process ID of Spotify, in this case the ID is "22981". With that process ID I would like to search from this list: pacmd list-sink-inputs. That command returns a list like this:

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"

Now I would like to correlate the application.process.id = "22981" to the sink input index which in this case is index: 0. Now with that index number I would then need to to run this command: pacmd set-sink-input-mute 0 1 to mute Spotify and pacmd set-sink-input-mute 0 0 to unmute Spotify. For those commands, the first number would need to be replaced with the index number found earlier, and the next number is the boolean to turn on or off the mute. How can I put this altogether into a script, so I can get a command to mute/unmute the Spotify application?

EDIT: Both of the scripts below work as expected, can somebody add a toggle which would check muted: yes or muted: no and then mute or unmute accordingly?

EDIT: I was able to modify glenn jackman's script to add the toggle:

#!/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 "$@"
2

7 Answers 7

15

Here's my take on your interesting challenge:

#!/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 "$@"
5
  • This works perfectly as well
    – era878
    Aug 26, 2012 at 19:05
  • @era878, I like the idea of toggle being the default action. However your get_status function will only find the "muted" lines without cross-checking that status belongs to the appropriate application. Re-read my get_index function for the details. Aug 27, 2012 at 0:16
  • 3
    nice awk skills :)
    – hytromo
    Aug 27, 2012 at 4:07
  • @glennjackman, Yup, I figured that out after a while. I believe that the script that I have just posted works correctly now.
    – era878
    Aug 27, 2012 at 10:47
  • 1
    Details: awk -v var=val. Awk loops over the lines, 1 by 1, tries to match any of the $1 == ... statements, executes the code in brackets on match, and continues. The first statement matches on lines whose first word is index:, and stores the second word (SINK INDEX) in idx variable. So idx gets overwritten by the next index: <SINK INDEX> line until awk matches the second statement ($1=application.process.id, $2==, $3=<expected pid val>). When this 2nd statement matches, awk prints idx (which is the last line that matched the first statement index:) and exits.
    – KrisWebDev
    Oct 22, 2016 at 17:55
8

thanks for the solution! I managed to use the scripts provided here to fix my problem. Since I had to modify them a bit, here I join the improved version.

The reason the original scripts didn't work for me is because some applications can have several instances, i.e. several PID, but maybe only one of them is producing sound, and is thus actually connected to Pulseaudio. Since the script only used the first PID found, it would typically /not/ mute the desired application.

So here is a version where the argument is the application name as registered in PulseAudio. You can find this name by running the pacmd list-sink-inputs command and look for application.name field.

An alternative solution would be to mute/unmute all PIDs which have the same application name.

#!/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 "$@"
6

Even though the question is asking for a script, I wanted to leave this here.

I have written a C application that does this on Ubuntu. Even better, it sits on the indicator tray (using libappindicator) and checks what Spotify is playing, on short intervals. If it is playing an ad (checks against a blacklist) it mutes the Spotify. If a new ad is being played, you simply click Mute on the indicator menu and it adds it to the blacklist.

What it does, is looks for an X window, for which XFetchName returns Spotify - Linux Preview. Then it calls XGetWindowProperty to query the _NET_WM_ICON_NAME property of that window, which returns a string in the "Spotify – <Artist> – <Song>" format. When playing ads, it returns something like this:

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

It maintains a Ternary Search Tree of the list of ads, to efficiently check whether the current title is in the list.

It also uses the PulseAudio Asynchronous API to query the sink-inputs and set-mute:

pa_context_get_sink_input_info_list()
pa_context_set_sink_input_mute()

Since it is just simple C code, it is light-weight. Check out the source code and Ubuntu .deb package at: indicator-muteads. It would probably beat a shell script by 2-3 orders of magnitude.

2
  • does not work with version 1.0.11 Aug 3, 2015 at 14:31
  • I wrote a similar program to mute spotify ads. In my case, I used the dbus-monitor command to detect when an ad starts or finishes (it literally says "ad" or "track" ;) ). This avoids the need for a database.
    – DBear
    Oct 12, 2021 at 20:13
4

First of all, the "more correct" way to find an application's PID like spotify, is to use:

pidof spotify

I've built up a script that does the job, I don't know if it is the best way to do it, but it works perfectly:

#!/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

You can work with is as:

./mute_application.sh spotify mute

or

./mute_application.sh spotify unmute

Tested with both Audacious and Vlc running and muting/unmuting only one of them.

2
  • Perfect script, works as expected
    – era878
    Aug 26, 2012 at 18:56
  • pidof doesn't work well with chromium-browser though
    – Subin
    Aug 17, 2020 at 6:16
2

I modified Jag's script to do a toggle of muting. I assigned this to a keyboard shortcut to mute/unmute Chromium. pidof doesn't work with Chromium apparently. So this uses Pulseaudio client.

#!/bin/bash

# Adapter from glenn Jag on http://askubuntu.com/questions/180612/script-to-mute-an-application
# Toggles muting of all audio streams of an app

main() {
    if [[ "$1" ]]; then
        prev=""; prev2="";
        while read line; do
            if [[ $line = "end" ]]; then
                adjust_muteness $1 $(echo "$prev2 $prev" | tr -d '\n')
                prev=""; prev2="";
            else
                if [[ $prev2 = "" ]]; then prev2=$line; else prev=$line; fi
            fi
        done < <(get_info "$1")
    else
        usage 1 "specify the name of a PulseAudio client"
    fi
}

usage() {
    [[ "$2" ]] && echo "error: $2"
    echo "usage: $0 [-h] [-u] appname"
    exit $1
}

adjust_muteness() {
    # muted or not
    muted=$2
    # process ID
    index=$3

    # muteness value
    if [[ $muted = "yes" ]]; then
        mute=0 # unmute
    else
        mute=1 # mute
    fi

    echo $muted

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

get_info() {
    pacmd list-sink-inputs |
    awk -v name=$1 '
        $1 == "index:" {idx = $2}
        $1 == "muted:" {idm = $2}
        $1 == "application.name" && $3 == "\"" name "\"" {print idm; print idx; print "end";}
    '
}

main "$@"

Usage :

./mute-app.sh Chromium

You can get the name of apps with pacmd list-sink-inputs

1

I really can't script, but I have modified hakermania's script to create another.

This one will increase or decrease the specific application's volume at increments of 5%:

edit: actually, it's working in always changing last opened app. 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
1

Edited script to mute all the inputs of an app (multiple processes) and defaults to toggle:

#!/bin/bash

main() {
    local action=toggle
    while getopts :hum 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 | -m] appname"
    echo "where: -u = ummute , -m = mute (default action is to toggle)"
    echo "Usage examples:"
    echo "  toggle mute: $0 appname"
    echo "  to mute an app: $0 -m appname"
    echo "  to unmute an app: $0 -u appname"
    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 "$@"

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .