Take the 2-minute tour ×
Ask Ubuntu is a question and answer site for Ubuntu users and developers. It's 100% free, no registration required.

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 "$@"
share|improve this question

3 Answers 3

up vote 4 down vote accepted

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 "$@"
share|improve this answer
    
This works perfectly as well –  era878 Aug 26 '12 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. –  glenn jackman Aug 27 '12 at 0:16
2  
nice awk skills :) –  hakermania Aug 27 '12 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 '12 at 10:47

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.

share|improve this answer

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.

share|improve this answer
    
Perfect script, works as expected –  era878 Aug 26 '12 at 18:56

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.