#!/usr/bin/env python3 from __future__ import print_function #Released under GNU General Public License version 3, see the file COPYING for details. #An attempt at a speech-friendly alsa mixer which runs in the console (no X-windows) import alsaaudio, sys, os """ The commands module was dropped in python 3, but its getstatusoutput function was added to the subprocess module in python3 getstatusoutput however is not in subprocess module of python2 and it does not seem to be importable from __future__. We therefore test the major python version and then import getstatusoutput from commands in python2 and from subprocess in python3 """ major_version=sys.version_info.major if major_version==2: from commands import getstatusoutput if major_version==3: from subprocess import getstatusoutput def cls(): #run the shell command, clear to clear the screen cl=os.system("clear") return def set_increment(vol_range): if vol_range[1]<16: increment=int(float(100)/float(vol_range[1])) else: increment=5 return increment def contain_vol(vol): if vol<0: vol=0 sys.stdout.write(chr(7)) if vol>100: vol=100 sys.stdout.write(chr(7)) return vol def new_volume(mixer, voltype): current_vol=mixer.getvolume(voltype) vol=current_vol[0] if isinstance(vol, str): vol=int(vol.rstrip('L')) return vol def vol_value(vol): volval=vol[0] if isinstance(volval, str): volval=volval.rstrip('L') return int(volval) def get_mixerlist(cardnumber): mixers=[] mixer_names=[] mixer_ids=[] try: mixers=alsaaudio.mixers(cardnumber) except Exception as e: err=prompt('Failed to get mixer list for card '+str(cardnumber)+' Press enter to continue') if len(mixers)==0: err=prompt('Failed to get mixer list for card '+str(cardnumber)+' Press enter to continue') return False for i in mixers: dups=mixers.count(i) if dups==1: #There is only one of these, so copy its name and give it a mixer id of 0 and go on. mixer_names.append(i) mixer_ids.append(0) continue mid=0 while dups>0: try: m=alsaaudio.Mixer(i, mid, cardnumber) name=i+' index '+str(mid) if name not in mixer_names: mixer_names.append(name) mixer_ids.append(mid) except alsaaudio.ALSAAudioError: pass mid=mid+1 dups=dups-1 return (mixers, mixer_names, mixer_ids) def change_mixer(mixerlist, m): mixers=mixerlist[0] mixer_names=mixerlist[1] mixer_ids=mixerlist[2] cls() try: mixer=alsaaudio.Mixer(mixers[m], mixer_ids[m], card) except Exception as e: err=prompt("Failed to get the details for "+mixers[m]+". \nThe error was: "+str(e)+"\n Press enter to continue") return False extra_info='' choices=[] instruction='Pick a control to change on '+mixer_names[m]+':' #can mixer change volume? v=mixer.volumecap() if len(v)>0: #We know there is volume capability, but not if it is Playback or Capture or both #Get the allowed range so that we can change the increment if required #First try specificly for playback volume by getting its range. try: pr=mixer.getrange(0) except alsaaudio.ALSAAudioError: pr=False #Now try specificly for capture volume by getting its range. try: cr=mixer.getrange(1) except alsaaudio.ALSAAudioError: cr=False if pr: #set playback increment p_increment=set_increment(pr) pv=mixer.getvolume(0) pv=vol_value(pv) extra_info=extra_info+'\n can change playback volume' choices.append('playback volume down from '+str(pv)+' percent') choices.append('playback volume up from '+str(pv)+' percent') if cr: c_increment=set_increment(cr) cv=mixer.getvolume(1) cv=vol_value(cv) extra_info=extra_info+'\n can change capture volume' choices.append('capture volume down from '+str(cv)+' percent') choices.append('capture volume up from '+str(cv)+' percent') capabilities=' '.join(mixer.switchcap()) try: mute=mixer.getmute() except alsaaudio.ALSAAudioError: mute=[] # if 'Capture' in capabilities: # #There is a chance that recording can be switched on/off try: record=mixer.getrec() can_record=record[0] extra_info=extra_info+'\ncan toggle recording on/off' if can_record==0: choices.append('recording off') else: choices.append('recording on') except alsaaudio.ALSAAudioError: record=[] can_record='n' if len(mute)>0: extra_info=extra_info+'\ncan mute/unmute ' if mute[0]==0: choices.append('mute '+mixer_names[m]) if mute[0]==1: choices.append('unmute '+mixer_names[m]) #See if the mixer can be set to different options (enum) enums=mixer.getenum() if len(enums)==2: options=enums[1] opt_selected=enums[0] opt=options.index(opt_selected) instruction=instruction+'\n '+mixer_names[m]+' can be set to one of '+str(len(options))+' options\n' can_be_set=True choices.append('set '+mixer_names[m]+' to another value, now '+opt_selected) else: can_be_set=False choice=0 while choice!='q': choice=select_from(choices, instruction, choice, extra_info) if choice=='q': continue if choices[choice].startswith('playback volume down'): pv=pv-p_increment pv=contain_vol(pv) mixer.setvolume(pv, -1, 0) pv=new_volume(mixer, 0) choices[choice]='playback volume down from '+str(pv)+' percent' choices[choice+1]='playback volume up from '+str(pv)+' percent' continue if choices[choice].startswith('playback volume up'): pv=pv+p_increment pv=contain_vol(pv) mixer.setvolume(pv, -1, 0) pv=new_volume(mixer, 0) choices[choice-1]='playback volume down from '+str(pv)+' percent' choices[choice]='playback volume up from '+str(pv)+' percent' continue if choices[choice].startswith('capture volume down'): cv=cv-c_increment cv=contain_vol(cv) mixer.setvolume(cv, -1, 1) cv=new_volume(mixer, 1) choices[choice]='capture volume down from '+str(cv)+' percent' choices[choice+1]='capture volume up from '+str(cv)+' percent' continue if choices[choice].startswith('capture volume up'): cv=cv+c_increment cv=contain_vol(cv) mixer.setvolume(cv, -1, 1) cv=new_volume(mixer, 1) choices[choice-1]='capture volume down from '+str(cv)+' percent' choices[choice]='capture volume up from '+str(cv)+' percent' continue if choices[choice]=='recording on': mixer.setrec(0) choices[choice]='recording off' continue if choices[choice]=='recording off': mixer.setrec(1) choices[choice]='recording on' continue if choices[choice].startswith('mute '): mixer.setmute(1) choices[choice]='unmute '+mixer_names[m] continue if choices[choice].startswith('unmute '): mixer.setmute(0) choices[choice]='mute '+mixer_names[m] continue if can_be_set and choices[choice].startswith('set '): opt=select_from(options, 'Select another option to set '+mixer_names[m]+' to', opt) if opt!='q': mixer.setenum(opt) opt_selected=options[opt] choices[choice]='set '+mixer_names[m]+' to another value, now '+opt_selected continue return def prompt(output): sys.stdout.write(str(output)) sys.stdout.flush() key=getstatusoutput("catchkey")[1] print ('') return key def select_from(entries, instruction='', current_entry=0, extra_info='No extra information available.'): tot_entries=len(entries) key='' active_keys=['enter', 'up', 'down', 'f1', 'q', 'Q'] last_key='' have_seen=0 result_list=[] cls() instruction=instruction+'\nThere are '+str(len(entries))+' choices:\n f1 for more info,\nup/down arrows to scroll, enter to select, q to quit.' print (instruction) while key!='q': last_key=key key=prompt (entries[current_entry]) if key=='Q': print ('Bye-bye') exit() if key not in active_keys and key!=last_key: result_list=[] have_seen=0 for j in entries: if key.lower()==j[0].lower(): result_list.append(entries.index(j)) if have_seen>=len(result_list): have_seen=0 if len(result_list)>0 and key not in active_keys: current_entry=result_list[have_seen] have_seen=have_seen+1 continue if key=='f1': print(extra_info) if key=='enter': return current_entry if key=='down': current_entry=current_entry+1 if key=='up': current_entry=current_entry-1 if current_entry==len(entries): current_entry=0 cls() print (instruction) if current_entry<0: current_entry=len(entries)-1 return 'q' def get_cards(): #returns a tuple with a list of card names and a list with thecorresponding card numbers cards=[] cardnumbers=[] try: cardindex=alsaaudio.card_indexes() except Exception as e: err=prompt("Failed to find the details for your soundcards. \nThe error was: "+str(e)+"\n Press enter to continue") return False indexlen=len(cardindex) for c in cardindex: cards.append(alsaaudio.card_name(c)[0]+' card number '+str(c)) cardnumbers.append(c) return (cards, cardnumbers) #main program starts here card_details=get_cards() cards=card_details[0] cardnumbers=card_details[1] if not cards: exit(1) card=0 if len(cards)>1: card=select_from(cards, 'pick a sound card') else: card=0 while card!='q': mixerlist=get_mixerlist(cardnumbers[card]) if mixerlist==False: err=prompt("Failed to find the mixers for "+cards[card]+". \n Press enter to continue") card=select_from(cards, 'pick a sound card', card) continue else: mixers=mixerlist[0] mixer_names=mixerlist[1] mixer_ids=mixerlist[2] if len(mixers)==0: err=prompt('There are no mixers available on card '+cards[card]+' with card index '+str(cardnumbers[card])+' press enter to continue') card=select_from(cards, 'pick a sound card', card) continue mix=0 while mix!='q': mix=select_from(mixer_names, 'Pick a mixer on '+cards[card], mix) if mix=='q': continue change_mixer(mixerlist,mix) card_details=get_cards() cards=card_details[0] cardnumbers=card_details[1] if len(cards)>1: if card>=len(cards): card=0 print('The available soundcards must have changed!') card=select_from(cards, 'pick a sound card', card) else: card='q' print ("Bye-bye!")