#!/usr/bin/bash

# heos-volume  Copyright 2025-2026  Norman Carver

# Script program to print (to stdout) or change the current Volume of a HEOS player/group.
#
# A player is specified with:
# (1) a (sub)string of the player's HEOS name, or
# (2) its IP num (i.e., host portion of its dotted quad IP address).
#
# With the -g option, a group is specified:
# (1) a (sub)string of the group's HEOS name, or
# (2) its Group ID (GID).
#
# Note: volume settings are meaningless for players with Fixed Output set!
#
# Sends commands to the default HEOS master device, but can select alternative
# using the MASTER_IPNUM argument option.


# Maximum Set Level to accept without asking if correct:
volmax=75
# Default volume level change with just +/-:
delta=5


function print_usage()
{
    echo -e "Print out or change the Volume of a HEOS Player/Group.\n" >&2
    echo    "Usage: heos-volume [-g] PLAYER [ACTION [MASTER_IPNUM]]" >&2
    echo    "where:" >&2
    echo    "  PLAYER -- a HEOS Player's Name (substring) or its IPNUM;" >&2
    echo    "  ACTION -- action to take with player's Volume, one of:" >&2
    echo    "          -- default: print out current volume level;" >&2
    echo    "     N    -- Set volume to level, 0<=N<=100;" >&2
    echo    "     +[N] -- Increase volume by N steps, 0<N<=25 (blank N means 5);" >&2
    echo    "     -[N] -- Decrease volume by N steps, 0<N<=100 (blank N means 5);" >&2
    echo    "     ?    -- print out current volume level (allows MASTER_IPNUM);" >&2
    echo    "  MASTER_IPNUM -- use a non-default HEOS master device;" >&2
    echo -e "  (IPNUM means the last quad of an IP address only: i.e., 0--255.)\n" >&2
    echo    "Options:" >&2
    echo    "  -g -- group: PLAYER is a group Name or GID;" >&2
}


if [[ "$1" == --help ]]; then
    print_usage
    exit 0
fi

# Process supplied options:
pgopt=--player
pidopt=""
debugopt=""
while [[ "$1" == -* ]]; do
    if [[ "$1" == -g ]]; then
        pgopt=--group
    elif [[ "$1" == --pid=?* ]]; then
        pidopt=$1
    elif [[ "$1" == --debug ]]; then
        debugopt=--debug
    elif [[ "$1" == -- ]]; then
        shift;break
    else
        echo "Error: invalid option '$1'" >&2
        echo "(use '--' to signal end of options, with -* arguments)" >&2
        exit 1
    fi
    shift
done

# Process command arguments:
if [[ $# == 0 || $# -gt 3 ]]; then
    print_usage
    exit 1
fi

player=$1
action=print
value=$delta
master=""
if [[ $# -ge 2 ]]; then
    case "$2" in
        ?) action=print ;;
        [0-9]*) action=set; value=$2 ;;
        +) action=increase; ;;
        +?*) action=increase; value=${2#+} ;;
        -) action=decrease; ;;
        -?*) action=decrease; value=${2#-} ;;
        *) echo "Error: invalid ACTION '$2'!" >&2; exit 1;;
    esac
    if [[ $# == 3 ]]; then
        master=${3}
    fi
fi
heosdir=$(dirname "$0")


# heosutil-run-command error checking function (one json output):
# args: 1) command result variable; 2) error message; [3) error action]
# (default action on error is 'exit 1', use $3 of 'return 1' to continue execution)
function error_check_run_command()
{
    cmdresult=${!1}
    if [[ ($? != 0) || ("$cmdresult" != *'"result": "success"'*) ]]; then
        if [[ "$cmdresult" == Error:* || ($debugopt == --debug && "$cmdresult" == ERROR:*) ]]; then
            echo "$cmdresult" >&2
        fi
        if [[ -n "$2" ]]; then echo "$2" >&2; fi
        if [[ $# == 3 ]]; then $3; else exit 1; fi
    else
        return 0
    fi
}


if [[ "$action" == print ]]; then
    # Get volume of player/group and print out:
    if [[ "$pgopt" == "--player" ]]; then
        command="heos://player/get_volume?pid=\${pid}"
    else
        command="heos://group/get_volume?gid=\${gid}"
    fi
    result=$("$heosdir"/heosutils/heosutil-run-command ${pgopt}="$player" $pidopt $debugopt "$command" $master 2>&1)
    error_check_run_command result "Error: failed getting Volume level!"
    vol=$(grep -Eo 'level=[^"&]+' <<<"$result")
    vol=${vol#level=}
    fixed=""
    if [[ "$vol" == 0 && "$pgopt" == "--player" ]]; then
        # Check if player output is fixed:
        info=$("$heosdir"/heosutils/heosutil-run-command ${pgopt}="$player" $pidopt "heos://player/get_player_info?pid=\${pid}" $master)
        if error_check_run_command info "" "return 1"; then
            lineout=$(grep -Eo '"lineout": [0-9]' <<<"$info")
            lineout=${lineout#\"lineout\":\ }
            if [[ "$lineout" == 2 ]]; then
                fixed=" [Fixed Level Line Out]"
            fi
        fi
    fi
    echo "Volume:  ${vol}${fixed}"

elif [[ "$action" == set ]]; then
    # Set volume of player/group:
    level=$value
    if [[ ! ("$level" =~ ^[0-9]{1,3}$) || ("$level" -gt 100) ]]; then
        echo "Error: Level must be from 0--100 ('$level')!" >&2
        exit 1
    fi
    if [[ "$level" -ge $volmax ]]; then
        echo "LEVEL ('$level') is above the specified safe level ('$volmax')." >&2
        echo -n "Really set volume to LEVEL (y/N)? "
        read response
        if [[ ! "$response" =~ ^[yY] ]]; then
            exit 1
        fi
    fi

    # Set volume of player/group:
    if [[ "$pgopt" == "--player" ]]; then
        command="heos://player/set_volume?pid=\${pid}&level=${level}"
    else
        command="heos://group/set_volume?gid=\${gid}&level=${level}"
    fi
    result=$("$heosdir"/heosutils/heosutil-run-command ${pgopt}="$player" $pidopt $debugopt "$command" $master 2>&1)
    error_check_run_command result "Error: failed to set Volume!" "exit 1"
    echo "Volume Set to $level..." >&2

elif [[ "$action" == increase ]]; then
    # Increase volume of player/group:
    steps=$value
    if [[ ! ("$steps" =~ ^[0-9]+$ && "$steps" -gt 0 && "$steps" -le 25) ]]; then
        echo "Error: Steps must be from 1--25 ('$steps')!" >&2
        exit 1
    fi

    if [[ "$pgopt" == "--player" ]]; then
        command="heos://player/volume_up?pid=\${pid}&step=${steps}"
    else
        command="heos://group/volume_up?gid=\${gid}&step=${steps}"
    fi
    result=$("$heosdir"/heosutils/heosutil-run-command ${pgopt}="$player" $pidopt $debugopt "$command" $master 2>&1)
    error_check_run_command result "Error: failed to increase Volume!" "exit 1"
    echo "Volume Increased by $steps..." >&2

elif [[ "$action" == decrease ]]; then
    # Decrease volume of player/group:
    steps=$value
    if [[ ! ("$steps" =~ ^[0-9]+$ && "$steps" -gt 0 && "$steps" -le 100) ]]; then
        echo "Error: Steps must be from 1--100 ('$steps')!" >&2
        exit 1
    fi

    if [[ "$pgopt" == "--player" ]]; then
        command="heos://player/volume_down?pid=\${pid}&step=${steps}"
    else
        command="heos://group/volume_down?gid=\${gid}&step=${steps}"
    fi
    result=$("$heosdir"/heosutils/heosutil-run-command ${pgopt}="$player" $pidopt $debugopt "$command" $master 2>&1)
    error_check_run_command result "Error: failed to decrease Volume!" "exit 1"
    echo "Volume Decreased by $steps..." >&2

fi

#EOF
