diff --git a/lcd b/lcd new file mode 100755 index 0000000000000000000000000000000000000000..ba18c4d185125f27886964f28c1e9b351ace4897 --- /dev/null +++ b/lcd @@ -0,0 +1,847 @@ +#!/usr/bin/env bash +## This script for bash 4.x on linux displays the way an audio file is +## streamed from storage, through mpd, to alsa (the DAC). +## +## Copyright (C) 2017 Ronald van Engelen +## This program is free software: you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation, either version 3 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program. If not, see . +## +## Source: https://github.com/ronalde/mpd-configure +## See also: https://lacocina.nl/detect-alsa-output-capabilities + +LANG=C + +app_name_mm="mpd-monitor" +app_version="0.1.0" +app_info_url="https://lacocina.nl/mpd-display-status" + +def_mpd_host="localhost" +def_mpd_port="6600" +def_ssh_user="root" + +## output formatting +bold="$(tput bold)" +dim="$(tput dim)" +std="$(tput sgr0)" +red=$(tput setaf 1) +green=$(tput setaf 2) +blue=$(tput setaf 4) +white=$(tput setaf 7) + +arg_mpd_host="" +arg_mpd_port="" +arg_ssh_user="" +arg_mpd_music_dir="" + +function display_usageinfo() { + + msg_usage="\ +Usage: +${app_name_mm} -d|--mpd-music-dir PATH [optional arguments] +-or- +${app_name_mm} PATH + +PATH should be the path to the directory used as 'music_directory' by +mpd running on MPDHOST. + +Optional arguments: + -m|--mpd-host MPDHOST The IP address or hostname on which mpd + runs. + Defaults to 'localhost'. + -p|--mpd-port MPDPORT The port on which mpd on MPDHOST + listens. + Defaults to '6600'. + -x|--mpd-password PASSWD The password for mpd. + -u|--ssh-user SSHUSER The username for the ssh connection to + MPDHOST used to get realtime alsa + information on MPDHOST. + (Only used when MPDHOST is remote, ie not + 'localhost' or '127.0.x.y'.) + +Background information: +${app_info_url} +" + printf 1>&2 "%s\n" "${msg_usage}" +} + + +function analyze_command_line() { + ## parse command line arguments using the `manual loop` method + ## described in http://mywiki.wooledge.org/BashFAQ/035. + while :; do + case "${1:-}" in + -m|--mpd-host) + if [[ "${2}x" == "x" ]]; then + die "argument \`$1' requires the dns host name or ip address for the host running mpd." + else + arg_mpd_host="$2" + shift 2 + continue + fi + ;; + -x|--mpd-password) + if [[ "${2}x" == "x" ]]; then + die "argument \`$1' requires a non-empty value." + else + arg_mpd_password="${2}" + shift 2 + continue + fi + ;; + -u|--ssh-user) + if [[ "${2}x" == "x" ]]; then + die "argument \`$1' requires a non-empty value." + else + arg_ssh_user="${2}" + shift 2 + continue + fi + ;; + -d|--mpd-music-dir) + if [[ "${2}x" == "x" ]]; then + die "argument \`$1' requires the path to the directory where the music for mpd is stored." + break + else + arg_mpd_music_dir="$2" + mpd_music_dir="${arg_mpd_music_dir}" + shift 2 + continue + fi + ;; + -h|-\?|--help) + display_usageinfo + exit + ;; + --) + shift + break + ;; + -?*) + printf "Notice: unknown option \`%s' ignored\n\n." "$1" 1>&2 + display_usageinfo + exit + ;; + *) + break + esac + done + + ## shift the options index to get the remainder of the command + ## line arguments containing the font descriptions + ## and return them as 0-separated strings. + #printf 1>&2 "OPTIND: \`%s'\n" "${OPTIND}" + shift $((OPTIND - 1)) + if [[ "${arg_mpd_music_dir}x" == "x" ]]; then + arg_single="$@" + arg_mpd_music_dir="${arg_single}" + if [[ ! -d "${arg_mpd_music_dir}" ]]; then + printf 1>&2 "error: invalid mpd music directory \`%s' specified.\n\n" \ + "${arg_mpd_music_dir}" + display_usageinfo + else + ## single argument, use in defaults + mpd_music_dir="${arg_mpd_music_dir}" + mpd_host="${def_mpd_host}" + mpd_port="${def_mpd_port}" + mpd_password="" + ssh_host="" + fi + else + [[ ${DEBUG} ]] && \ + declare -p arg_mpd_music_dir \ + arg_mpd_host arg_mpd_port arg_mpd_password \ + arg_ssh_user 1>&2 + fi + +} + +function die() { + printf "%s\n" "$@" 1>&2 + exit 1 +} + +function human_bytes() { + bytes="${1##*.}" + if [[ "${bytes}x" == "x" ]]; then + printf "%s" "0" + else + numfmt --to=iec-i --suffix=B --format="%.1f" "${bytes}" + fi +} + +function check_commands() { + err=() + cmd_mpc="$(type -p mpc || err+=("mpc not found") )" + cmd_soxi="$(type -p soxi || err+=("soxi not found"))" + cmd_exiftool="$(type -p exiftool || err+=("exiftool not found"))" + cmd_netcat="$(type -p nc || err+=("netcat (nc) not found"))" + if [[ ${#err} -gt 0 ]]; then + printf 1>&2 "error: %s\n" "${err[@]}" + return 1 + fi +} + +function get_csv_value() { + data="$1" + IFS=\; read type field value <<<"${data}" + printf "%s" "${value//\"}" +} + +function get_soxi_value() { + fieldname="$1" + printf 1>&2 "%s: fieldname='%s', value='%s'\n" \ + "${FUNCNAME[0]}" "${fieldname}" "${soxi_info["${fieldname}"]}" + declare vals="${soxi_info["${fieldname}"]}" + #declare -p vals 1>&2 + value="$(get_csv_value "${vals[0]}")" + printf "%s" "${value}" +} + +function filter_soxi_duration() { + ## expects: hh:mm:ss.xxx where xxx are the subseconds (milliseconds?) + duration_fields=($1) + IFS=":" read hours minutes seconds subseconds <<<"${duration_fields[0]//./:}" + minutes=$(echo "${minutes} + ( ${hours} * 60 )" | bc) + printf "%s:%s" "${minutes}" "${seconds}" +} + +function run_func() { + func="$1" + shift + args="$@" + [[ ${DEBUG} ]] && declare -p 1>&2 func args + if [[ "${alsa_ssh_userhost}x" == "x" ]]; then + [[ ${DEBUG} ]] && \ + printf 1>&2 "running on local host.\n" + "${func}" + else + [[ ${DEBUG} ]] && \ + printf 1>&2 "running through ssh://using %s.\n" \ + "${alsa_ssh_userhost}" + ssh ${alsa_ssh_userhost} -- \ + "$(declare -f ${func}); ${func} "${args}"" + fi +} + +function ret_mpdconf_contents() { + ## return the contents of the mpd configuration file + alsa_ssh_userhost="${alsa_ssh_userhost:-}" + mpdconf_path="$1" + #declare -p mpdconf_path 1>&2 + #xargs -0 echo < "${mpd_conf}" + if [[ -f "${mpdconf_path}" ]]; then + cat "${mpdconf_path}" + else + printf 1>&2 "error: mpd configuration file %s does not exist on host %s.\n" \ + "${mpdconf_path}" "$(hostname)" + return 1 + fi +} + + +function ret_mpdconf_commandline() { + ## return the command line arguments for running mpd + alsa_ssh_userhost="${alsa_ssh_userhost:-}" + mpd_pid="$(pidof mpd | awk '{print $1}')" + xargs -0 echo < /proc/${mpd_pid}/cmdline +} + + +function ret_mpdconf_default() { + ## function + for conffile in $XDG_CONFIG_HOME/mpd/mpd.conf \ + ~/.mpdconf \ + ~/.mpd/mpd.conf \ + /etc/mpd.conf; do + if [[ "${conffile}x" != "x" ]]; then + if [[ -f ${conffile} ]]; then + [[ ${DEBUG} ]] && \ + printf 1>&2 "using mpd configuration file %s\n" \ + "${conffile}" + echo ${conffile} + return 0 + fi + fi + done + printf 1>&2 "warning: no mpd configuration found.\n" + return 1 +} + + +function ret_mpdconf_path() { + ## returns the name of the mpd.conf file in use by mpd (either + ## local or remote) + ## all possible mpd command line arguments + ## the command mpd itself (including a possible path) gets + ## stripped before + mpd_args=(--no-config) + mpd_args+=(--no-daemon) + mpd_args+=(-v) + mpd_args+=(--verbose) + mpd_args+=(--stderr) + ## strip each command line argument from all possible arguments + ## the remainder is the mpd conf file + mpd_real_args=($(run_func ret_mpdconf_commandline)) + mpd_rest_args=(${mpd_real_args[@]:1}) + for arg in ${mpd_args[@]}; do + mpd_rest_args=(${mpd_rest_args[@]//${arg}}) + done + mpd_conf_file="${mpd_rest_args[0]}" + if [[ "${mpd_conf_file}x" == "x" ]]; then + [[ ${DEBUG} ]] && \ + printf 1>&2 "no conffile specified on mpd command line, \ +test default locations\n" + run_func ret_mpdconf_default + else + printf "%s" "${mpd_conf_file}" + fi +} + +function get_mpdconf_contents() { + mpdconf_path="$1" + run_func ret_mpdconf_contents "${mpdconf_path}" +} + + +function get_mpd_alsa_outputs() { + ## TODO + # while read -r line; do + # done< <(ssh ${alsa_ssh_userhost} -- "cat /ec/mpd.conf") + mpdconf_path=$(ret_mpdconf_path) + #mpdconf_contents="$(get_mpdconf_contents "${mpdconf_path}")" + #declare -p mpdconf_path mpdconf_contents 1>&2 + start_audio_output_re="^[[:space:]]*audio_output[[:space:]]*\{" + end_audio_output_re="[[:space:]]*\}" + ## only handle alsa audio outputs + type_re="^[[:space:]]*type[[:space:]]*[\"'](.*)[\"']" + device_re="^[[:space:]]*device[[:space:]]*[\"']hw:([0-9]+),([0-9]+)[\"']" + audio_outputs=0 + inside_audio_output= + inside_alsa_output= + declare -a devices + while read -r line; do + # declare -p line inside_audio_output inside_alsa_output 1>&2 + if [[ "${inside_audio_output}x" == "x" ]]; then + if [[ "${line}" =~ ${start_audio_output_re} ]]; then + ## new audio_output started + ((audio_outputs++)) + inside_audio_output=true + [[ ${DEBUG} ]] && \ + printf 1>&2 "\n *** starting audio_output\n" + elif [[ "${line}" =~ ${end_audio_output_re} ]]; then + ## end of audio output + inside_audio_output= + fi + else + if [[ "${line}" =~ ${type_re} ]]; then + type="${BASH_REMATCH[1]}" + [[ ${DEBUG} ]] && \ + printf 1>&2 " ** audio_output type: %s\n" "${type}" + if [[ "${type}x" == "alsax" ]]; then + inside_alsa_output=true + else + inside_alsa_output= + fi + elif [[ "${line}" =~ ${device_re} ]] && \ + [[ "${inside_alsa_output}x" != "x" ]]; then + alsa_dev="${BASH_REMATCH[1]}" + alsa_int="${BASH_REMATCH[2]}" + [[ ${DEBUG} ]] && \ + printf 1>&2 " ** audio_output device: hw:%s,%s\n" \ + "${alsa_dev}" "${alsa_int}" + devices+=("hw:${alsa_dev},${alsa_int}") + inside_audio_output= + inside_alsa_output= + fi + fi + done< <(get_mpdconf_contents "${mpdconf_path}") + # declare -p audio_outputs devices 1>&2 + ## TODO: handle multiple devices + for device in ${devices[0]}; do + #printf 1>&2 "alsa device: " + printf "%s" "${device}" + #printf 1>&2 "\n" + done +} + +function fill_arrays() { + ret_mpc_info || return 1 + #ret_mpc_now + ret_mpd_now + ret_exif_info "${mpc_vals[file]}" + ret_soxi_info "${mpc_vals[file]}" + ## TODO get right card and interface numbers + alsa_hw="$(get_mpd_alsa_outputs)" + [[ ${DEBUG} ]] && declare -p alsa_hw 1>&2 + ret_alsa_info "${alsa_hw}" + #[[ "${exif_vals[*]}x" == "x" ]] || ret_exif_info "${mpc_vals[file]}" + #[[ "${soxi_vals[*]}x" == "x" ]] || ret_soxi_info "${mpc_vals[file]}" + #[[ "${alsa_vals[*]}x" == "x" ]] || ret_alsa_info "1" "0" "root@sonida" +} + +function info_to_csv() { + fill_arrays + + for f in "${!mpc_vals[@]}"; do + printf '"%s";"%s";"%s";\n' "mpc" "${f}" "${mpc_vals[${f}]}" + done + for f in "${!soxi_vals[@]}"; do + printf '"%s";"%s";"%s";\n' "soxi" "${f}" "${soxi_vals[${f}]}" + done + for f in "${!exif_vals[@]}"; do + printf '"%s";"%s";"%s";\n' "exif" "${f}" "${exif_vals[${f}]}" + done + for f in "${!alsa_vals[@]}"; do + printf '"%s";"%s";"%s";\n' "alsa" "${f}" "${alsa_vals[${f}]}" + done + +} +function soxi_filesize_to_bytes() { + soxi_filesize="$1" # ${soxi_vals[FileSize]} + unit="${soxi_filesize:$(( ${#soxi_filesize} -1 )):1}" + size="${soxi_filesize:0:-1}" + case "${unit}" in + G) value="$(echo "scale=0; ${size} * 1000000" | bc)" ;; + M) value="$(echo "scale=0; ${size} * 1000" | bc)" ;; + K) value="${size}" ;; + *) printf 1>&2 \ + "ANOMALITY: unknown unit '%s' in soxi reported file size ('%s')" \ + "${unit}" "${soxi_filesize}" + return 1 + esac + printf "%s" "${value}" +} + +function alsa_format_outputencoding() { + + alsa_encoding="$1" # ${alsa_vals[format]} + encoding_re="([SU])([0-9]+)_([LB]E)" + if [[ "${alsa_encoding}" =~ ${encoding_re} ]]; then + signedness="${BASH_REMATCH[1]}" + bitdepth="${BASH_REMATCH[2]}" + alsa_encoding[bitdepth]="${bitdepth}" + endianness="${BASH_REMATCH[3]}" + case "${signedness}" in + U) alsa_encoding[signedness]="unsigned" ;; + S) alsa_encoding[signedness]="signed" ;; + esac + case "${endianness}" in + LE) alsa_encoding[endianness]="little endian" ;; + BE) alsa_encoding[endianness]="big endian" ;; + esac + else + printf "ANOMALITY in %s: alsa_encoding '%s' does not match regex.\n" \ + "${FUNCNAME[0]}" "${alsa_encoding}" + return 1 + fi +} + + +function get_info() { + + ## parse command line arguments + analyze_command_line "$@" + + ## check presence of needed commands + check_commands || return 1 + + if [[ ! -d "${mpd_music_dir}" ]]; then + die "error: can't access directory ${mpd_music_dir}." + fi + mpd_host="${arg_mpd_host:-${def_mpd_host}}" + mpd_port="${arg_mpd_port:-${def_mpd_port}}" + ssh_user="${arg_ssh_user:-${def_ssh_user}}" + mpd_password="${arg_mpd_password:-}" + ssh_host="${mpd_host:-}" + localhost_re="127.0.[0-9]+.[0-9]+|localhost" + if [[ "${mpd_host}" =~ ${localhost_re} ]]; then + ## mpd runs local, no need for ssh to get alsa properties + alsa_ssh_userhost="" + else + aalsa_ssh_host="${mpd_host}" + ## mpd runs remote, use ssh to get alsa properties + alsa_ssh_userhost="${ssh_user}@${ssh_host}" + fi + [[ ${DEBUG} ]] && declare -p mpd_host ssh_host ssh_user mpd_music_dir 1>&2 + + fill_arrays + #|| return 1 + [[ ${DEBUG} ]] && declare -p mpc_vals mpd_vals 1>&2 + + main_playing_tracktitle="${mpc_vals[title]}" + main_playing_trackartist="${mpc_vals[artist]}" + main_playing_trackperformers="${exif_vals[Performer]}" + main_rt_tracknumber_album="${mpc_vals[track]}" + main_rt_tracknumber_playlist="${mpc_vals[position]}" + main_rt_playlistlength="${mpd_vals[rt_raw_playlistlength]}" + main_playing_trackalbumname="${mpc_vals[album]}" + main_playing_trackalbumnumber="${mpc_vals[position]}" + main_playing_trackalbumartist="${mpc_vals[albumartist]}" + main_playing_trackduration="$(filter_soxi_duration "${soxi_vals[Duration]}")" + main_file_sizebytes="$(soxi_filesize_to_bytes ${soxi_vals[FileSize]})" + main_file_bitrate_raw="${soxi_vals[BitRate]}" + main_file_bitrate_value="${main_file_bitrate_raw:0:-1}" + main_file_bitrate_unit="${main_file_bitrate_raw:$(( ${#main_file_bitrate_raw} - 1 )):1}" + main_file_bitrate_kvalue= + if [[ "${main_file_bitrate_unit}x" == "Mx" ]]; then + main_file_bitrate_kvalue="$(echo "${main_file_bitrate_value} * 1000" | bc)" + else + main_file_bitrate_kvalue="${main_file_bitrate_value}" + fi + main_file_bitrate_kvalue="${main_file_bitrate_kvalue%%.00}" + main_file_bitrate="${main_file_bitrate_kvalue}" + main_file_bitdepth="${soxi_vals[Precision]//-bit}" + main_file_samplerate="${soxi_vals[SampleRate]}" + #declare -p alsa_vals 1>&2 + main_output_samplerate_raw=(${alsa_vals[rate]}) + main_output_samplerate="${main_output_samplerate_raw[0]}" + alsa_format_outputencoding "${alsa_vals[format]}" + main_output_bitdepth="${alsa_encoding[bitdepth]}" + main_output_sampleencoding="${alsa_encoding[bitdepth]} bit (${alsa_encoding[signedness]} ${alsa_encoding[endianness]})" + main_output_samplechannelcount="${alsa_vals[channels]}" + + main_playing_trackplayingtime="${mpc_vals[rt_time_elapsed_minutes]}:${mpc_vals[rt_time_elapsed_seconds]}" + main_playing_trackplayingpercentage="${mpc_vals[rt_percentage_played]}%" + #declare -p main_playing_trackplayingpercentage 1>&2 + main_mpd_bitdepth="${mpd_vals[rt_audio_bitdepth]}" + main_mpd_samplerate="${mpd_vals[rt_audio_samplerate]}" + main_mpd_bitrate="${mpd_vals[rt_audio_bitrate]}" + + main_file_sizehbytes="$(human_bytes "${main_file_sizebytes}")" + msg_filesize="$(printf "%-12s: %-13s (%s)" \ +"File size" "${main_file_sizebytes} bytes" "${main_file_sizehbytes}")" + + color_bitdepth_file_mpd="${red}" + color_bitdepth_mpd_output="${red}" + if [[ ${main_mpd_bitdepth} -ge ${main_file_bitdepth} ]]; then + color_bitdepth_file_mpd="${green}" + if [[ ${main_output_bitdepth} -ge ${main_mpd_bitdepth} ]]; then + color_bitdepth_mpd_output="${green}" + fi + fi + color_sample_rate_file_mpd="${red}" + color_sample_rate_mpd_output="${red}" + if [[ ${main_mpd_samplerate} -ge ${main_file_samplerate} ]]; then + color_samplerate_file_mpd="${green}" + if [[ ${main_output_samplerate} -ge ${main_mpd_samplerate} ]]; then + color_samplerate_mpd_output="${green}" + fi + fi + + + msg_header="$(printf "%-11s %-18s ${white}%8s${std} > ${white}%8s${std} > ${white}%8s${std}" " " " " "File" "MPD" "DAC")" + msg_bitrate="$(printf "%-11s %-18s: ${dim}${white}%8s${std} > ${white}%8s${std} > ${dim}${white}%8s${std}" \ +" " "Bit rate (kbit/s)" "${main_file_bitrate}" "${main_mpd_bitrate}" "(n/a)")" + msg_bitdepth="$(printf "%-11s %-18s: %8s ${color_bitdepth_file_mpd}> %8s ${color_bitdepth_mpd_output}> %8s${std}" \ +" " "Bit depth (bit)" "${main_file_bitdepth}" "${main_mpd_bitdepth}" "${main_output_bitdepth}")" + msg_samplerate="$(printf "%-11s %-18s: %8s ${color_samplerate_file_mpd}> %8s${std} ${color_samplerate_mpd_output}> %8s${std}" \ +" " "Sample rate (Hz)" "${main_file_samplerate}" "${main_mpd_samplerate}" "${main_output_samplerate}")" + + msg_tracktitle="${bold}${white}${main_playing_tracktitle}${std}" + msg_trackartist="${blue}${main_playing_trackartist}${std}" + msg_tracknumber="$(printf "%-4s of %-3s" "#${bold}${blue}${main_rt_tracknumber_playlist}${std}" "${main_rt_playlistlength}")" + msg_nowplaying="${msg_tracknumber}: ${msg_tracktitle}" + msg_byline="$(printf "%-11s-- by %s" " " "${msg_trackartist}")" + msg_albumline="$(printf "%-11strack %s from album %s" \ +" " "${bold}${main_rt_tracknumber_album}${std}" "${bold}${blue}${main_playing_trackalbumname}${std}")" + msg_playingtime="$(printf "%-13s playing %s from %s (%s)" \ +" " "${bold}${std}${main_playing_trackplayingtime}${std}" "${main_playing_trackduration}" "${main_playing_trackplayingpercentage}")" + + echo -en "\ +${msg_nowplaying} +${msg_albumline} +${msg_byline} + +${msg_header} +${msg_bitdepth} +${msg_samplerate} +${msg_bitrate} + +${msg_filesize} +\r" +} + +function ret_mpd_now() { + ## echo status command to mpd host on mpd port using netcat, to + ## fill mpc_vals array with rt_ fields + + [[ ${DEBUG} ]] && \ + declare -p cmd_netcat mpd_host mpd_port 1>&2 + mpd_status="$(echo "status" | ${cmd_netcat} -N ${mpd_host} ${mpd_port})" + [[ ${DEBUG} ]] && \ + declare -p mpd_status 1>&2 + linecounter=1 + prefix="rt_raw" + key_val_re="^([^\:]+):[[:space:]]+(.*)" + while read -r line; do + if [[ "${line}" =~ ${key_val_re} ]]; then + field="${BASH_REMATCH[1]}" + value="${BASH_REMATCH[2]}" + mpd_vals["${prefix}_${field}"]="${value}" + fi + done<<<"${mpd_status}" + IFS=: read audio_samplerate audio_bitdepth audio_channelcount <<< "${mpd_vals[${prefix}_audio]}" + mpd_vals["rt_audio_samplerate"]="${audio_samplerate}" + mpd_vals["rt_audio_bitdepth"]="${audio_bitdepth}" + mpd_vals["rt_audio_channelcount"]="${audio_channelcount}" + mpd_vals["rt_audio_bitrate"]="${mpd_vals[${prefix}_bitrate]}" + + IFS=: read time_elapsed_rounded time_tracktotal <<< "${mpd_vals[${prefix}_time]}" + mpd_vals["rt_time_tracktotal"]="${time_tracktotal}" + + # for k in "${!mpd_vals[@]}"; do + # if [[ "${k}" =~ ^rt ]]; then + # printf 1>&2 "mpd_val %s: '%s'\n" "${k}" "${mpd_vals[${k}]}" + # fi + # done +} + +function ret_mpc_info() { + ## get information from mpd using mpc with the 'current' command + ## and a format string. + if [[ "${mpd_password}x" == "x" ]]; then + mpc_args=(-h "${mpd_host}") + else + mpc_args=(-h "${mpd_password}@${mpd_host}") + fi + mpc_args+=(-p "${mpd_port}") + mpc_args+=(current) + mpc_args+=(-f "${mpc_format_string}") + [[ ${DEBUG} ]] && \ + declare -p mpc_format_string 1>&2 + res="$(${cmd_mpc} "${mpc_args[@]}" &)" + if [[ $? -ne 0 ]]; then + die "${FUNCNAME[0]}: mpd on %s is not running." + else + if [[ "${res}x" == "x" ]]; then + printf 1>&2 "mpd on %s is not currently playing anything.\n" \ + "${mpd_host}" + return 1 + fi + fi + field_val_re="^([^\:]+):(.*)" + while read -r line; do + if [[ "${line}" =~ ${field_val_re} ]]; then + field="${BASH_REMATCH[1]}" + val="${BASH_REMATCH[2]}" + mpc_vals["${field}"]="${val}" + fi + done<<<"${res}" +} + +function ret_soxi_info() { + rel_file_path="${1}" + file_path="${arg_mpd_music_dir}/${rel_file_path}" + if [[ -f "${file_path}" ]]; then + res="$(${cmd_soxi} "${file_path}")" + field_val_re='([^\:]+):[[:space:]]*(.*)' + while read -r line; do + if [[ "${line}" =~ ${field_val_re} ]]; then + field="${BASH_REMATCH[1]}" + field="${field// /}" + val="${BASH_REMATCH[2]}" + soxi_vals["${field}"]="${val}" + if [[ "${field}x" == "Sample Encodingx" ]]; then + break + fi + fi + done<<<"${res}" + fi +} + +function ret_exif_info() { + rel_file_path="${1}" + file_path="${arg_mpd_music_dir}/${rel_file_path}" + if [[ -f "${file_path}" ]]; then + res="$(${cmd_exiftool} -j "${file_path}")" + field_val_re='"([^\:]+)":(.*)' + while read -r line; do + if [[ "${line}" =~ ${field_val_re} ]]; then + field="${BASH_REMATCH[1]}" + val="${BASH_REMATCH[2]}" + exif_vals["${field}"]="${val}" + fi + done<<<"${res}" + fi +} + +function get_hwparams() { + alsa_card_no="$1" + alsa_dev_no="$2" + hwparams_file="/proc/asound/card${alsa_card_no}/pcm${alsa_dev_no}p/sub0/hw_params" + if [[ -f "${hwparams_file}" ]]; then + ## return the contents of the file + xargs -0 echo < "${hwparams_file}" + else + printf 1>&2 "error accessing file: '%s' on host '%s'\n" \ + "${hwparams_file}" "$(hostname)" + return 1 + fi +} + +function ret_alsa_info() { + ## TODO: fix handling of mpd output(s) + ## expects "hw:x,y" + alsa_hw="${1//hw:/}" + alsa_card_no="${alsa_hw##*,}" + alsa_dev_no="${alsa_hw%%,*}" + alsa_ssh_userhost="${alsa_ssh_userhost:-}" + [[ ${DEBUG} ]] && \ + declare -p alsa_hw alsa_card_no alsa_dev_no alsa_ssh_userhost + declare -a mpc_args + if [[ "${mpd_password}x" == "x" ]]; then + mpc_args=(-h "${mpd_host}") + else + mpc_args=(-h "${mpd_password}@${mpd_host}") + fi + mpc_args+=(-p "${mpd_port}") + mpc_args+=(output) + [[ ${DEBUG} ]] && \ + declare -p mpd_host mpd_port mpc_args 1>&2 + mpc_output_re="Output[[:space:]]([0-9]+)[[:space:]]\(([^\(]+)\)" + while read -r line; do + if [[ "${line}" =~ ${mpc_output_re} ]]; then + mpd_outputs+=("${BASH_REMATCH[2]}") + fi + done< <(${cmd_mpc} "${mpc_args[@]}") + [[ ${DEBUG} ]] && \ + declare -p mpd_outputs 1>&2 + + #hw_params_file="/proc/asound/card${alsa_card_no}/pcm${alsa_dev_no}p/sub0/hw_params" + #cmd="cat ${hw_params_file}" + #if [[ "${alsa_ssh_userhost}x" == "x" ]]; then + ## use local alsa + # xargs -0 echo < /proc/${mpd_pid}/cmdline + # read res < ${hw_params_file} + #else + ## use alsa on remote host + #res="$(ssh ${alsa_ssh_userhost} -- "${cmd}")" + #fi +# if [[ $? -ne 0 ]]; then +# printf 1>&2 "error accessing file: '%s' on host '%s'\n" \ +# "${hw_params_file}" "${alsa_ssh_userhost}" +# return 1 + # else + key_val_re="^([^\:]+):[[:space:]]+(.*)" + while read -r line; do + if [[ "${line}" =~ ${key_val_re} ]]; then + field="${BASH_REMATCH[1]}" + val="${BASH_REMATCH[2]}" + alsa_vals["${field}"]="${val}" + [[ ${DEBUG} ]] && declare -p line field val 1>&2 + else + [[ ${DEBUG} ]] && declare -p line 1>&2 + return 1 + fi + + done< <(run_func get_hwparams "${alsa_card_no} ${alsa_dev_no}") +# fi +} + +function terminal_size() { # Calculate the size of the terminal + terminal_cols="$(tput cols)" + terminal_rows="$(tput lines)" +} + + +function main_loop() { + mpc_args=(-h "${mpd_host}") + counter=0 + while : ; do + #"${cmd_mpc}" "${mpc_args[@]}" idle + sleep 0.5 + get_info + fill_arrays + # \r" + ((counter++)) + echo -en "bla die bla $counter\ntwee bla die bla $counter\r" + + #tclsh /etc/shairport/smartie/smartie-cat.tcl -tty /dev/ttyUSB0 + done + printf 1>&2 "main_loop done.\n" +} + +unset mpc_vals +unset mpd_vals +unset alsa_vals +unset exif_vals +unset soxi_vals + +declare -A alsa_vals +declare -A mpc_vals +declare -A mpd_vals +declare -A exif_vals +declare -A soxi_vals + +## input | nfs://srv/media/music/.../track11.aiff +## | AIFF (Little Endian) PCM in 24bit/192kHz @ 4.503bit/s +main_file_bitdepth= +main_file_bitrate= +main_file_bitrate_kvalue= +main_file_bitrate_raw= +main_file_bitrate_unit= +main_file_bitrate_value= +main_file_samplerate= +main_file_sizebytes= +main_mpd_bitdepth= +main_mpd_bitrate= +main_mpd_samplerate= +main_output_bitdepth= +main_output_samplechannelcount= +main_output_sampleencoding= +main_output_samplerate= +main_output_samplerate_raw= +main_playing_trackalbumartist= +main_playing_trackalbumname= +main_playing_trackalbumnumber= +main_playing_trackartist= +main_playing_trackduration= +main_playing_trackperformers= +main_playing_trackplayingpercentage= +main_playing_trackplayingtime= +main_playing_tracktitle= +main_rt_tracknumber_album= +main_rt_tracknumber_playlist= +main_rt_playlistlength= + +declare -A alsa_encoding +declare -a mpc_fields=( + album + albumartist + artist + comment + composer + date + disc + file + genre + mdate + mtime + name + performer + position + time + title + length + track +) + +mpc_format_string= +for mpc_field in ${mpc_fields[@]}; do + mpc_format_string+="${mpc_field}:[%${mpc_field}%]\n" +done + +## if the script is not sourced by another script but run within its +## own shell call function `analyze_commandline_args' +[[ "${BASH_SOURCE[0]:-}" != "${0}" ]] || \ + get_info "$@" diff --git a/mpd-monitor b/mpd-monitor index 543b03ad096602d3515845eb74505363c7bd0c82..5d6f43d461fc3649c6d690fed25402fd8db3c748 100755 --- a/mpd-monitor +++ b/mpd-monitor @@ -191,13 +191,14 @@ function does_mpd_haveoutputs() { if [[ ${#MPD_OUTPUTS[@]} -lt 1 ]]; then ## no outputs found - debug "bla" + debug "no outputs found" + declare -p MPD_OUTPUTS 1>&2 printf "%s" "${MSG_MPDCONF_NO_AUDIOOUTOUTS}" return 1 else msg="$(printf "\`%s' " "${MPD_OUTPUTS[@]}")" [[ ! -z ${DEBUG} ]] && \ - debug "using outputs: ${msg}" + declare -p MPD_OUTPUTS 1>&2 printf "output: %s (total: %s)\n" \ "${MPD_OUTPUTS[@]}" "${#MPD_OUTPUTS[@]}" 1>&2; return 0 @@ -263,7 +264,6 @@ function get_mpd_alsa_audio_outputs() { ## audio_output defined in the mpd configuration. [[ ! -z "${DEBUG}" ]] && \ debug "entering \`${FUNCNAME}' with arguments \`$*'" - ## be sure not to match global parameter `audio_output_format' audio_output_started_regexp="^[[:space:]]*audio_output[[:space:]]" audio_output_ended_regexp="}" @@ -274,7 +274,6 @@ function get_mpd_alsa_audio_outputs() { audio_output_comment_regexp="^[[:space:]]*#" while read -r line; do - if [[ ! -z "${output_started}" ]]; then ## inside output? if [[ "${line}" =~ ${audio_output_ended_regexp} ]]; then @@ -305,8 +304,7 @@ function get_mpd_alsa_audio_outputs() { fi fi - - done < ${MPD_CONFIGURATIONFILE} + done<"${MPD_CONFIGURATIONFILE}" if [[ ${output_counter} -gt 0 ]]; then [[ ! -z "${DEBUG}" ]] && \ @@ -337,13 +335,11 @@ function get_mpd_device() { ## return it. [[ ! -z "${DEBUG}" ]] && \ debug "entering \`${FUNCNAME}' with arguments \`$*'" - mpd_device="" mpd_staticoutput="${mpd_staticoutput}" mpd_fixed_samplerate="" mpd_fixed_channelcount="" mpd_fixed_sampleencoding="" - alsa_outputs="$(get_mpd_alsa_audio_outputs)" if [[ ${alsa_outputs} -lt 1 ]]; then die "no alsa outputs found in the ${MSG_MPDCONF}" @@ -725,15 +721,12 @@ function get_alsa_streamfile() { ## configuration file (needed for `get_alsa_props'). [[ ! -z "${DEBUG}" ]] && \ debug "entering \`${FUNCNAME}' with arguments \`$*'" - - #monitor file = /proc/asound/card1/stream0 alsa_streamfile="" alsa_streamfile_regexp="monitor[[:space:]]file[[:space:]]*=[[:space:]]*(.*)" - alsa_hwaddress_value=$(get_mpd_device) [[ ! -z "${DEBUG}" ]] && \ - debug "got alsa hwaddress: \`${alsa_hwaddress_value}'" + declare -p alsa_hwaddress_value 1>&2 alsa_hwaddress="${alsa_hwaddress_value/hw:/}" alsa_dev_nr="${alsa_hwaddress%%,*}" @@ -974,8 +967,9 @@ function main() { if [[ -z "${MPD_CONFIGURATIONFILE}" ]]; then MPD_CONFIGURATIONFILE="$(get_mpdconf_filepath)" [[ $? -ne 0 ]] && die "${MPD_CONFIGURATIONFILE}" + else + declare -p MPD_CONFIGURATIONFILE 1>&2 fi - ## check if mpd is configured globally for a static audio output MPDCONF_GLOBAL_STATICFORMAT="$(get_mpdconf_parameter "audio_output_format")" @@ -1001,7 +995,9 @@ function main() { ## mpd configuration file alsa_streamfile="$(get_alsa_streamfile)" if [[ $? -ne 0 ]]; then - die "could not get current file from alsa" + die "could not get current file from alsa" + else + declare -p alsa_streamfile 1>&2 fi ## get the path to the (parent) music directory from the mpd