1
0
mirror of https://github.com/cookiengineer/audacity synced 2025-06-15 15:49:36 +02:00

Script work

Add Envelope script
Add Clips and Boundaries Scripts.
Add docimages_oddments.py for the weirder cases.
Add make html script.
Add more spectral images
Add Dark versions of spectrograms (paul's comment)
Clean up oddments script.

- This commit also adds an option NOT to bring Audacity to the top when screenshotting.
-- That's because QuickPlay will disappear if you do -because you lose mouse capture.
- Also allow negative window IDs in the drag command.

- Registration names of align commands sorted.  They had &, : and / in them.
- Fixed bug in SetTrackInfo where bFocused was being ignored
- Fixed bug where command manager ignored multi-commands.
- Commit docimages_oddments.py again, to fix line endings.
This commit is contained in:
James Crook 2018-02-17 16:08:38 +00:00 committed by Paul Licameli
parent 4724c6a131
commit fa49d94530
22 changed files with 951 additions and 48 deletions

View File

@ -5,8 +5,42 @@
# Make sure Audacity is running first and that mod-script-pipe is enabled
# before running this script.
import time
# records time, name of file and returns contents.
def inner( name ) :
global old_name
global start_time
global results
result = old_name + ' took ' + str( time.time() - start_time )
results.append( result )
print( result )
start_time = time.time()
if not name :
return ""
old_name = name
return open("docimages_" + name + ".py" ).read()
#initialise timing
start_time = time.time()
old_name = 'startup'
results = []
#do the different files...
exec( inner( 'tracks' ) )
exec( inner( 'labels' ) )
exec( inner( 'spectro' ) )
exec( inner( 'after' ) )
exec( inner( 'envelopes' ) )
exec( inner( 'cut_n_paste' ) )
exec( inner( 'clip_boundaries' ) )
exec( inner( 'oddments' ) )
#report on timing.
inner( "" )
print( "\n\nSummary:" )
print( "\n".join( results ) )
exec( open("docimages_tracks.py" ).read() )
exec( open("docimages_labels.py" ).read() )
exec( open("docimages_spectro.py" ).read() )
exec( open("docimages_after.py" ).read() )

View File

@ -0,0 +1,100 @@
# docimages_arrange.py
# Sends commands to get images for the manual.
# These ones arrange tracks and do alignment.
# Make sure Audacity is running first and that mod-script-pipe is enabled
# before running this script.
#load and run the common core.
exec( open("docimages_core.py" ).read() )
import time
def loadFourColours() :
loadMonoTracks( 4 )
do( 'SetTrack: Track=0 Name="Claire" Height=60 Color=Color0')
do( 'SetTrack: Track=1 Name="Ann" Height=60 Color=Color1')
do( 'SetTrack: Track=2 Name="Bob" Height=60 Color=Color2')
do( 'SetTrack: Track=3 Name="David" Height=60 Color=Color3')
do( 'SetClip: Track=0 At=1 Start=25')
do( 'SetClip: Track=1 At=1 Start=15')
do( 'SetClip: Track=2 At=1 Start=20')
do( 'SetClip: Track=3 At=1 Start=10')
do( 'Select: First=0 Last=100 Mode=Remove' )
def loadFourColoursSelected() :
loadFourColours()
do( 'ZoomOut' )
do( 'Select: Start=90 End=135 First=0 Last=100' )
def blockMoves( name ):
# These are the align commands that move tracks 'en block'.
loadFourColoursSelected()
capture( name + '001.png', 'All_Tracks_Plus' )
do( 'Align_StarttoZero' )
capture( name + '002.png', 'All_Tracks_Plus' )
loadFourColoursSelected()
do( 'Align_StarttoCursorSelectionStart' )
capture( name + '003.png', 'All_Tracks_Plus' )
loadFourColoursSelected()
do( 'Align_StarttoSelectionEnd' )
capture( name + '004.png', 'All_Tracks_Plus' )
loadFourColoursSelected()
do( 'Align_EndtoCursorSelectionStart' )
capture( name + '005.png', 'All_Tracks_Plus' )
loadFourColoursSelected()
do( 'Align_EndtoSelectionEnd' )
capture( name + '006.png', 'All_Tracks_Plus' )
def track_moves( type ) :
loadFourColours()
# Sorting tracks into order
do( 'SetTrack: Track=1 Focused=1')
capture( 'TrackOrder002.png', 'All_Tracks' )
def arrange_imagesA() :
loadFourColours()
# Moving tracks up and down.
capture( 'TrackOrder001.png', 'All_Tracks' )
do( 'SetTrack: Track=1 Focused=1')
# ToTop=0 to show the focus...
capture( 'TrackOrder002.png', 'All_Tracks ToTop=0' )
do( 'TrackMoveUp' )
capture( 'TrackUp.png', 'All_Tracks ToTop=0' )
do( 'TrackMoveDown' ) # undo
do( 'TrackMoveDown' )
capture( 'TrackDown.png', 'All_Tracks ToTop=0' )
do( 'TrackMoveTop' )
capture( 'TrackTop.png', 'All_Tracks ToTop=0' )
do( 'TrackMoveBottom' )
capture( 'TrackBottom.png', 'All_Tracks ToTop=0' )
# Sorting tracks into order
do( 'SortByName')
capture( 'TrackOrder003.png', 'All_Tracks' )
do( 'SortByTime')
capture( 'TrackOrder004.png', 'All_Tracks' )
# Aligning tracks
do( 'Select: First=0 Last=100 From=0 To=0')
do( 'Align_AlignTogether' )
capture( 'TrackAlign001.png', 'All_Tracks' )
do( 'Align_AlignEndtoEnd' )
do( 'FitInWindow' )
capture( 'TrackAlign002.png', 'All_Tracks' )
def arrange_imagesB() :
blockMoves( 'BlockMoves' )
do( 'MoveSelectionWithTracks')
blockMoves( 'BlockAndCursorMoves' )
do( 'MoveSelectionWithTracks')
#quickTest()
arrange_imagesA()
#arrange_imagesB()

View File

@ -0,0 +1,69 @@
# docimages_clip_boundariess.py
# Sends commands to get images for the manual.
# Images for clip boundary manipulation, per that chapter in the manual
# Make sure Audacity is running first and that mod-script-pipe is enabled
# before running this script.
#load and run the common core.
exec( open("docimages_core.py" ).read() )
def gappyTrack2() :
loadMonoTracks(1)
# A mono track
do( 'Select: Start=0 End=10')
do( 'SplitCut' )
do( 'Select: Start=30 End=45')
do( 'SplitCut' )
do( 'Select: Start=90 End=100')
do( 'SplitCut' )
do( 'Select: Start=120 End=135')
do( 'SplitCut' )
do( 'Select: Start=0 End=0')
def clipb_imagesA():
gappyTrack2()
# clip bound left
capture( 'ClipBounds001.png', 'All_Tracks' )
do( 'Select: Start=60 End=60')
capture( 'ClipBounds002.png', 'All_Tracks' )
do( 'SelPrevClipBoundaryToCursor' )
capture( 'ClipBounds003.png', 'All_Tracks' )
do( 'SelPrevClipBoundaryToCursor' )
capture( 'ClipBounds004.png', 'All_Tracks' )
do( 'SelPrevClipBoundaryToCursor' )
capture( 'ClipBounds005.png', 'All_Tracks' )
# clip bound right
gappyTrack2()
capture( 'ClipBounds006.png', 'All_Tracks' )
do( 'Select: Start=60 End=60')
capture( 'ClipBounds007.png', 'All_Tracks' )
do( 'SelCursorToNextClipBoundary' )
capture( 'ClipBounds008.png', 'All_Tracks' )
do( 'SelCursorToNextClipBoundary' )
capture( 'ClipBounds009.png', 'All_Tracks' )
do( 'SelCursorToNextClipBoundary' )
capture( 'ClipBounds010.png', 'All_Tracks' )
# clip left
gappyTrack2()
capture( 'ClipBounds011.png', 'All_Tracks' )
do( 'Select: Start=60 End=60')
capture( 'ClipBounds012.png', 'All_Tracks' )
do( 'SelPrevClip' )
capture( 'ClipBounds013.png', 'All_Tracks' )
do( 'SelPrevClip' )
capture( 'ClipBounds014.png', 'All_Tracks' )
# clip right
gappyTrack2()
capture( 'ClipBounds015.png', 'All_Tracks' )
do( 'Select: Start=60 End=60')
capture( 'ClipBounds016.png', 'All_Tracks' )
do( 'SelNextClip' )
capture( 'ClipBounds017.png', 'All_Tracks' )
do( 'SelNextClip' )
capture( 'ClipBounds018.png', 'All_Tracks' )
clipb_imagesA()

View File

@ -72,9 +72,13 @@ def quickTest() :
def setup() :
global path
global sample_path
global sample
global sample2
global postfix
postfix = ''
path = 'C:\\Users\\James Crook\\'
sample_path ='C:\\Users\\James Crook\\Music\\'
sample ='C:\\Users\\James Crook\\Music\\The Poodle Podcast.wav'
sample2 ='C:\\Users\\James Crook\\Music\\PoodlePodStereo.wav'
startPipes()
@ -89,8 +93,17 @@ def makeWayForTracks( ) :
def capture( name, what ) :
global path
global postfix
name = name.split( '.png' )[0] + postfix + '.png'
do( 'Screenshot: Path="'+path+name+'" CaptureWhat=' + what )
def loadExample( name ):
global sample_path
makeWayForTracks( )
do( 'Import2: Filename="'+sample_path+name+'"' )
do( 'Select: First=0 Last=0 Start=0 End=0')
do( 'FitInWindow' )
def loadMonoTrack():
global sample
makeWayForTracks( )
@ -112,6 +125,7 @@ def loadMonoTracks( num ) :
loadMonoTrack()
do( 'SetTrack: Track=0 Name="Foxy Lady"')
for i in range( 0, num-1 ):
do( 'Select: First=0 Last=0')
do( 'Duplicate' )
do( 'FitInWindow' )
do( 'Select: Start=55 End=70')
@ -121,6 +135,7 @@ def loadStereoTracks( num ) :
loadStereoTrack()
do( 'SetTrack: Track=0 Name="Foxy Lady"')
for i in range( 0, num-1 ):
do( 'Select: First=0 Last=0')
do( 'Duplicate' )
do( 'FitInWindow' )
do( 'Select: Start=55 End=70 First=0 Last=' + str(num*2-1) )

View File

@ -0,0 +1,109 @@
# docimages_cut_n_pastes.py
# Sends commands to get images for the manual.
# Images for cut_n_paste manipulation.
# Make sure Audacity is running first and that mod-script-pipe is enabled
# before running this script.
#load and run the common core.
exec( open("docimages_core.py" ).read() )
def gappyTrack() :
loadMonoTracks(1)
# A mono track
do( 'Select: Start=0 End=10')
do( 'SplitCut' )
do( 'Select: Start=60 End=100')
do( 'SplitCut' )
do( 'Select: Start=30 End=50')
def cut_n_paste_imagesA() :
# Split and move
gappyTrack()
capture( 'CutAndPaste001.png', 'All_Tracks' )
do( 'Split' )
capture( 'CutAndPaste002.png', 'All_Tracks' )
do( 'SetClip: At=55 Start=60')
capture( 'CutAndPaste003.png', 'All_Tracks' )
do( 'Select: Start=0 End=0')
capture( 'CutAndPaste004.png', 'All_Tracks' )
def cut_n_paste_imagesB() :
# SplitNew
gappyTrack()
capture( 'CutAndPaste005.png', 'All_Tracks' )
do( 'SplitNew' )
capture( 'CutAndPaste006.png', 'All_Tracks' )
do( 'Select: Start=0 End=0')
capture( 'CutAndPaste007.png', 'All_Tracks' )
def cut_n_paste_imagesC() :
# Join
gappyTrack()
do( 'Select: Start=45 End=75')
do( 'Split' )
do( 'Select: Start=39 End=129')
capture( 'CutAndPaste008.png', 'All_Tracks' )
do( 'Join' )
capture( 'CutAndPaste009.png', 'All_Tracks' )
do( 'Select: Start=0 End=0')
capture( 'CutAndPaste010.png', 'All_Tracks' )
# Detach at silences
do( 'Select: Start=0 End=150')
do( 'Join' )
capture( 'CutAndPaste011.png', 'All_Tracks' )
do( 'Select: Start=0 End=150')
do( 'Disjoin' )
capture( 'CutAndPaste012.png', 'All_Tracks' )
do( 'Select: Start=0 End=0')
capture( 'CutAndPaste013.png', 'All_Tracks' )
def cut_n_paste_imagesD() :
#Copy and Paste
gappyTrack()
do( 'Select: Start=15 End=20')
do( 'Copy' )
capture( 'CutAndPaste014.png', 'All_Tracks' )
#Pasting into
do( 'Select: Start=45 End=45')
capture( 'CutAndPaste015.png', 'All_Tracks' )
do( 'Paste' )
capture( 'CutAndPaste016.png', 'All_Tracks' )
do( 'Select: Start=0 End=0')
capture( 'CutAndPaste017.png', 'All_Tracks' )
gappyTrack()
do( 'Select: Start=15 End=20')
do( 'Copy' )
#Pasting before
do( 'Select: Start=5 End=5')
capture( 'CutAndPaste018.png', 'All_Tracks' )
do( 'Paste' )
capture( 'CutAndPaste019.png', 'All_Tracks' )
do( 'Select: Start=0 End=0')
capture( 'CutAndPaste030.png', 'All_Tracks' )
#pasting before with no movement (cheat)
do( 'Select: Start=11 End=16')
do( 'Cut' )
do( 'Select: Start=0 End=0')
capture( 'CutAndPaste031.png', 'All_Tracks' )
do( 'Select: Start=5 End=10')
capture( 'CutAndPaste032.png', 'All_Tracks' )
do( 'Select: Start=0 End=0')
def cut_n_paste_imagesE() :
# Duplicate
gappyTrack()
capture( 'CutAndPaste033.png', 'All_Tracks' )
do( 'Duplicate' )
capture( 'CutAndPaste034.png', 'All_Tracks' )
do( 'Select: Start=0 End=0')
capture( 'CutAndPaste035.png', 'All_Tracks' )
cut_n_paste_imagesA()
cut_n_paste_imagesB()
cut_n_paste_imagesC()
cut_n_paste_imagesD()
cut_n_paste_imagesE()

View File

@ -0,0 +1,45 @@
# docimages_envelopes.py
# Sends commands to get images for the manual.
# Images for envelope manipulation.
# Make sure Audacity is running first and that mod-script-pipe is enabled
# before running this script.
#load and run the common core.
exec( open("docimages_core.py" ).read() )
def env_images() :
loadMonoTracks(1)
do( 'Select: Start=0 End=0')
# A mono track
capture( 'Envelope001.png', 'All_Tracks' )
do( 'EnvelopeTool' )
# As spectrogram.
capture( 'Envelope002.png', 'All_Tracks' )
do( 'SetEnvelope: Time=55 Value=0.9');
capture( 'Envelope003.png', 'All_Tracks' )
do( 'SetEnvelope: Time=120 Value=0.4');
capture( 'Envelope004.png', 'All_Tracks' )
do( 'SetEnvelope: Time=125 Value=0.9');
capture( 'Envelope005.png', 'All_Tracks' )
do( 'SetEnvelope: Time=45 Value=0.85');
capture( 'Envelope006.png', 'All_Tracks' )
do( 'SetEnvelope: Time=25 Value=1.85');
capture( 'Envelope007.png', 'All_Tracks' )
do( 'SetEnvelope: Time=0 Value=0.85');
capture( 'Envelope008.png', 'All_Tracks' )
do( 'SetTrack: VZoom=Times2' )
capture( 'Envelope009.png', 'All_Tracks' )
do( 'SetTrack: VZoom=HalfWave' )
capture( 'Envelope010.png', 'All_Tracks' )
do( 'SelectTool' )
capture( 'Envelope011.png', 'All_Tracks' )
do( 'SetTrack: VZoom=Reset' )
capture( 'Envelope012.png', 'All_Tracks' )
env_images()

View File

@ -0,0 +1,50 @@
# docimages_oddments.py
# Sends commands to get images for the manual.
# Image oddments that don't fit the other categories.
# Make sure Audacity is running first and that mod-script-pipe is enabled
# before running this script.
# Historically this file has had peculiar problems with line endings.
#load and run the common core.
exec( open("docimages_core.py" ).read() )
import time
def oddments_imagesA():
for name in ["Select","Envelope","Draw","Zoom","TimeShift","Multi"] :
do( name + "Tool" )
capture( name + "Tool.png" , 'Tools' );
#A track is needed for the buttons to be active.
loadMonoTracks(1)
do( 'SetPreference: Name="/GUI/Theme" Value="high-contrast"')
for id in range( 11000, 11006 ):
do( "Drag: Id="+str( id) + " FromX=10 FromY=10" )
capture( "Button" + str(id) +"Hover.png", "Transport" )
do( "Drag: Id="+str( id) + " FromX=1000 FromY=10" )
for id in range( 11200, 11206 ):
do( "Drag: Id="+str( id) + " FromX=10 FromY=10" )
capture( "Button" + str(id) +"Hover.png", "Tools" )
do( "Drag: Id="+str( id) + " FromX=1000 FromY=10" )
for id in range( 11300, 11312 ):
do( "Drag: Id="+str( id) + " FromX=10 FromY=10" )
capture( "Button" + str(id) +"Hover.png", "Edit" )
do( "Drag: Id="+str( id) + " FromX=1000 FromY=10" )
do( 'SetPreference: Name="/GUI/Theme" Value="light"')
def oddments_imagesB():
loadMonoTracks(1)
#Bring window to top now
capture( "Dummy.png", "Ruler" )
#We hope nothing else gets focus before the next capture, so
#that we actually get to see something!
do( "Drag: Window=Timeline FromX=200 FromY=10 ToX=600 ToY=10" )
time.sleep(3.0)
#Disable bringing to top, so as not to destroy quick play.
capture( "QuikPlay001.png", "First_Track_Plus ToTop=0" )
oddments_imagesA()
oddments_imagesB()

View File

@ -8,6 +8,8 @@
#load and run the common core.
exec( open("docimages_core.py" ).read() )
import math
import time
# 11 2KHz tones, of decreasing amplitude.
@ -32,29 +34,139 @@ def makeStepper():
do( 'Select: Start=0 End=0')
def spectro_image1and2() :
def spectro_imagesA() :
loadStereoTracks(1)
# A stereo track
capture( 'Spectral001.png', 'First_Track' )
# As spectrogram.
do( 'SetTrack: Track=0 Display=Spectrogram')
do( 'Select: Start=55 End=70 First=0 Last=1')
capture( 'Spectral002.png', 'First_Track' )
capture( 'Spectral002.png', 'All_Tracks' )
# Half spectrogram, half wave.
do( 'SetTrack: Channel=1 Display=Waveform')
capture( 'MixedMode.png', 'First_Track' )
capture( 'MixedMode.png', 'All_Tracks' )
def spectro_image3and4():
def spectro_imagesB():
makeStepper();
# Stepper tone, viewed in dB.
do( 'SetTrack: Scale=dB')
capture( 'Spectral003.png', 'First_Two_Tracks' )
capture( 'Spectral003.png', 'All_Tracks' )
# As spectrogram.
do( 'SetTrack: Display=Spectrogram')
capture( 'Spectral004.png', 'First_Two_Tracks' )
capture( 'Spectral004.png', 'All_Tracks' )
def spectro_imagesC():
# A chirp and the word 'Audacity'
loadExample( 'AudacitySpectral.wav' )
capture( 'Spectral005.png', 'All_Tracks' )
do( 'SetTrack: Scale=dB')
capture( 'Spectral006.png', 'All_Tracks' )
do( 'SetTrack: Display=Spectrogram')
capture( 'Spectral007.png', 'All_Tracks' )
do( 'Select: Start=1.5 End=2.1 Low=3000 High=6000')
capture( 'Spectral008.png', 'All_Tracks' )
do( 'Select: Start=1.1 End=2.5' )
do( 'ZoomSel' )
do( 'Select: Start=1.5 End=2.1 Low=3000 High=6000')
do( 'SetTrack: Height=400' )
multiWindow( "SpectralVocal" )
def setWindow( name, value ):
do( 'SetTrack: SpecPrefs=1 Name="Window Size '+value+'"' )
do( 'SetPreference: Name="/Spectrum/FFTSize" Reload=1 Value='+value )
do( 'SetTrack: Track=0 Display=Spectrogram' )
capture( name + postfix + value + '.png', 'All_Tracks' )
def multiWindow( name ) :
setWindow( name, "256" )
setWindow( name, "512" )
setWindow( name, "2048" )
setWindow( name, "4096" )
setWindow( name, "8192" )
setWindow( name, "1024" ) # done last so we restore the default.
def spectro_imagesD():
makeWayForTracks()
do( 'NewMonoTrack' )
do( 'Select: Start=0.7 End=1.3')
do( 'Silence' ) #Just so we can watch.
do( 'Select: Start=0.8 End=1.2')
do( 'ZoomSel')
do( 'Select: Start=0.7 End=1.3')
do( 'Tone: Frequency=3000 Amplitude=0.8' )
do( 'Select: Start=0.99 End=0.99005' )
do( 'Tone: Frequency=12000 Amplitude=0.9' )
do( 'Select: Start=1.01 End=1.01005' )
do( 'Tone: Frequency=12000 Amplitude=0.9' )
do( 'Select: Start=0 End=0' )
multiWindow( 'SpectralAt' )
def spectro_imagesE():
makeWayForTracks()
do( 'NewMonoTrack' )
do( 'Select: Start=0 End=1.2')
do( 'ZoomSel')
do( 'Pluck' )
do( 'Select: Start=0.1 End=1.0')
do( 'ZoomSel')
multiWindow( 'SpectralNote' )
def makeScale( start, end, count ) :
a = math.exp( math.log( end/start) /(count-1 ))
makeWayForTracks()
do( 'NewMonoTrack' )
do( 'Select: Start=0 End=' + str( count/10 ))
do( 'Silence' ) #To see it happen...
do( 'SetTrack: Track=0 SpecPrefs=1 Display=Spectrogram' )
do( 'ZoomSel' )
#do( 'SetTrack: Track=0 Display=Spectrogram' )
for i in range( 0 , count , 2 ):
note = start * ( a ** i )
#print( note )
do( 'Select: Start=' + str( i / 10) + ' End=' + str( (i+1)/10 ))
do( 'Tone: Frequency=' +str( note ) )
do( 'Select: Start=' + str( i / 10) + ' End=' + str( ( i / 10) +0.05))
do( 'FadeIn' )
do( 'Select: Start=' + str( (i+1) / 10 -0.05) + ' End=' + str( (i+1) / 10 ))
do( 'FadeOut' )
do( 'FitInWindow' )
#do( 'Select: Start=0 End=' + str( count/10 ))
#do( 'Join' )
do( 'Select: Start=0 End=0')
def spectro_imagesF():
makeScale( 200, 4000, 100 )
do( 'SetTrack: Track=0 Display=Spectrogram' )
capture( 'ScaleLin.png', 'All_Tracks' )
do( 'SetPreference: Name=/Spectrum/ScaleType Value=1 Reload=1')
capture( 'ScaleLog.png', 'All_Tracks' )
do( 'SetPreference: Name=/Spectrum/ScaleType Value=0 Reload=1')
#quickTest()
spectro_image1and2()
spectro_image3and4()
do( 'SetPreference: Name="/GUI/Theme" Value=light Reload=1' )
postfix = ''
spectro_imagesA()
spectro_imagesB()
spectro_imagesC()
spectro_imagesD()
spectro_imagesE()
spectro_imagesF()
do( 'SetPreference: Name="/GUI/Theme" Value=dark Reload=1' )
postfix = 'Dark'
spectro_imagesA()
spectro_imagesB()
spectro_imagesC()
spectro_imagesD()
spectro_imagesE()
spectro_imagesF()
do( 'SetPreference: Name="/GUI/Theme" Value=light Reload=1' )

View File

@ -0,0 +1,17 @@
# Takes an image file directory and makes the web page that lists them.
# They are listed in creation date/time order.
import glob
import os
def getFiles() :
files = glob.glob("C:\\OpenSourceGit\\AudacityTeamTools\\wit-html\\auto_images\\*.png")
files.sort(key=os.path.getmtime)
return [ os.path.basename( name ) for name in files ]
#print("\n".join(files))
def oneItem( name ) :
return "<img src='./auto_images/"+name+"'><br><em>"+name+"</em><br>"
files = getFiles()
print( "\n".join( [ oneItem(name) for name in files ] ) )

View File

@ -0,0 +1,296 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""Automate Audacity via mod-script-pipe.
Pipe Client may be used as a command-line script to send commands to
Audacity via the mod-script-pipe interface, or loaded as a module.
Requires Python 2.7 or later. Python 3 recommended.
======================
Command Line Interface
======================
usage: pipeclient.py [-h] [-t] [-s ] [-d]
Arguments
---------
-h,--help: optional
show short help and exit
-t, --timeout: float, optional
timeout for reply in seconds (default: 10)
-s, --show-time: bool, optional
show command execution time (default: True)
-d, --docs: optional
show this documentation and exit
Example
-------
$ python3 pipeclient.py -t 20 -s False
Launches command line interface with 20 second time-out for
returned message, and don't show the execution time.
When prompted, enter the command to send (not quoted), or 'Q' to quit.
$ Enter command or 'Q' to quit: GetInfo: Type=Tracks Format=LISP
============
Module Usage
============
Note that on a critical error (such as broken pipe), the module just exits.
If a more graceful shutdown is required, replace the sys.exit()'s with
exceptions.
Example
-------
# Import the module:
>>> import pipeclient
# Create a client instance:
>>> client = pipeclient.PipeClient()
# Send a command:
>>> client.write("Command", timer=True)
# Read the last reply:
>>> print(client.read())
See Also
--------
PipeClient.write : Write a command to _write_pipe.
PipeClient.read : Read Audacity's reply from pipe.
Copyright Steve Daulton 2018
Released under terms of the GNU General Public License version 2:
<http://www.gnu.org/licenses/old-licenses/gpl-2.0.html />
"""
import os
import sys
import threading
import time
import errno
import argparse
if sys.version_info[0] < 3 and sys.version_info[1] < 7:
sys.exit('PipeClient Error: Python 2.7 or later required')
# Platform specific constants
if sys.platform == 'win32':
WRITE_NAME = '\\\\.\\pipe\\ToSrvPipe'
READ_NAME = '\\\\.\\pipe\\FromSrvPipe'
EOL = '\r\n\0'
else:
# Linux or Mac
PIPE_BASE = '/tmp/audacity_script_pipe.'
WRITE_NAME = PIPE_BASE + 'to.' + str(os.getuid())
READ_NAME = PIPE_BASE + 'from.' + str(os.getuid())
EOL = '\n'
class PipeClient(object):
"""Write / read client access to Audacity via named pipes.
Normally there should be just one instance of this class. If
more instances are created, they all share the same state.
__init__ calls _write_thread_start() and _read_thread_start() on
first instantiation.
Parameters
----------
None
Attributes
----------
reader_pipe_broken : event object
Set if pipe reader fails. Audacity may have crashed
reply_ready : event object
flag cleared when command sent and set when response received
timer : bool
When true, time the command execution (default False)
reply : string
message received when Audacity completes the command
See Also
--------
write : Write a command to _write_pipe.
read : Read Audacity's reply from pipe.
"""
reader_pipe_broken = threading.Event()
reply_ready = threading.Event()
_shared_state = {}
def __new__(cls, *p, **k):
self = object.__new__(cls, *p, **k)
self.__dict__ = cls._shared_state
return self
def __init__(self):
self.timer = False
self._start_time = 0
self._write_pipe = None
self.reply = ''
if not self._write_pipe:
self._write_thread_start()
self._read_thread_start()
def _write_thread_start(self):
"""Start _write_pipe thread"""
# Pipe is opened in a new thread so that we don't
# freeze if Audacity is not running.
write_thread = threading.Thread(target=self._write_pipe_open)
write_thread.daemon = True
write_thread.start()
# Allow a little time for connection to be made.
time.sleep(0.1)
if not self._write_pipe:
sys.exit('PipeClientError: Write pipe cannot be opened.')
def _write_pipe_open(self):
"""Open _write_pipe."""
self._write_pipe = open(WRITE_NAME, 'w')
def _read_thread_start(self):
"""Start read_pipe thread."""
read_thread = threading.Thread(target=self._reader)
read_thread.daemon = True
read_thread.start()
def write(self, command, timer=False):
"""Write a command to _write_pipe.
Parameters
----------
command : string
The command to send to Audacity
timer : bool, optional
If true, time the execution of the command
Example
-------
write("GetInfo: Type=Labels", timer=True):
"""
self.timer = timer
print('Sending command:', command)
self._write_pipe.write(command + EOL)
# Check that read pipe is alive
if PipeClient.reader_pipe_broken.isSet():
sys.exit('PipeClient: Read-pipe error.')
try:
self._write_pipe.flush()
if self.timer:
self._start_time = time.time()
self.reply = ''
PipeClient.reply_ready.clear()
except IOError as err:
if err.errno == errno.EPIPE:
sys.exit('PipeClient: Write-pipe error.')
else:
raise
def _reader(self):
"""Read FIFO in worker thread."""
# Thread will wait at this read until it connects.
# Connection should occur as soon as _write_pipe has connected.
read_pipe = open(READ_NAME, 'r')
message = ''
while True:
line = read_pipe.readline()
# Stop timer as soon as we get first line of response.
stop_time = time.time()
while line != EOL:
message += line
line = read_pipe.readline()
if line == '':
# No data in read_pipe indicates that the pipe is broken
# (Audacity may have crashed).
PipeClient.reader_pipe_broken.set()
if self.timer:
xtime = (stop_time - self._start_time) * 1000
message += 'Execution time: {0:.2f}ms'.format(xtime)
self.reply = message
PipeClient.reply_ready.set()
message = ''
read_pipe.close()
def read(self):
"""Read Audacity's reply from pipe.
Returns
-------
string
The reply from the last command sent to Audacity, or null string
if reply not received. Null string usually indicates that Audacity
is still processing the last command.
"""
if not PipeClient.reply_ready.isSet():
return ''
else:
return self.reply
def bool_from_string(strval):
"""Return boolean value from string"""
if strval.lower() in ('true', 't', '1', 'yes', 'y'):
return True
elif strval.lower() in ('false', 'f', '0', 'no', 'n'):
return False
else:
raise argparse.ArgumentTypeError('Boolean value expected.')
def main():
"""Interactive command-line for PipeClient"""
parser = argparse.ArgumentParser()
parser.add_argument('-t', '--timeout', type=float, metavar='', default=10,
help="timeout for reply in seconds (default: 10")
parser.add_argument('-s', '--show-time', metavar='True/False',
nargs='?', type=bool_from_string,
const='t', default='t', dest='show',
help='show command execution time (default: True)')
parser.add_argument('-d', '--docs', action='store_true',
help='show documentation and exit')
args = parser.parse_args()
if args.docs:
print(__doc__)
sys.exit(0)
client = PipeClient()
while True:
reply = ''
if sys.version_info[0] < 3:
#pylint: disable=undefined-variable
message = raw_input("\nEnter command or 'Q' to quit: ")
else:
message = input( #pylint: disable=bad-builtin
"\nEnter command or 'Q' to quit: ")
start = time.time()
if message.upper() == 'Q':
sys.exit(0)
elif message == '':
pass
else:
client.write(message, timer=args.show)
while reply == '':
time.sleep(0.1) # allow time for reply
if time.time() - start > args.timeout:
reply = 'PipeClient: Reply timed-out.'
else:
reply = client.read()
print(reply)
if __name__ == '__main__':
main()

View File

@ -947,7 +947,7 @@ CommandListEntry *CommandManager::NewIdentifier(const wxString & name,
count, {});
}
CommandListEntry *CommandManager::NewIdentifier(const wxString & name,
CommandListEntry *CommandManager::NewIdentifier(const wxString & nameIn,
const wxString & label,
const wxString & accel,
wxMenu *menu,
@ -958,6 +958,8 @@ CommandListEntry *CommandManager::NewIdentifier(const wxString & name,
int count,
const CommandParameter &parameter)
{
wxString name = nameIn;
// If we have the identifier already, reuse it.
CommandListEntry *prev = mCommandNameHash[name];
if (!prev);
@ -975,6 +977,18 @@ CommandListEntry *CommandManager::NewIdentifier(const wxString & name,
labelPrefix = mSubMenuList.back()->name;
}
// For key bindings for commands with a list, such as align,
// the name in prefs is the category name plus the effect name.
// This feature is not used for built-in effects.
if (multi) {
// The name needs to be clean for use by automation.
wxString cleanedName = wxString::Format(wxT("%s_%s"), name, label);
cleanedName.Replace( "/", "" );
cleanedName.Replace( "&", "" );
cleanedName.Replace( " ", "" );
name = cleanedName;
}
// wxMac 2.5 and higher will do special things with the
// Preferences, Exit (Quit), and About menu items,
// if we give them the right IDs.
@ -1024,13 +1038,6 @@ CommandListEntry *CommandManager::NewIdentifier(const wxString & name,
if( mMaxListOnly.Index( entry->key ) !=-1)
entry->key = wxT("");
// For key bindings for commands with a list, such as effects,
// the name in prefs is the category name plus the effect name.
if (multi) {
entry->name = wxString::Format(wxT("%s:%s"), name, label);
}
// Key from preferences overridse the default key given
gPrefs->SetPath(wxT("/NewKeys"));
if (gPrefs->HasEntry(entry->name)) {
@ -1551,6 +1558,14 @@ bool CommandManager::HandleTextualCommand(const wxString & Str, const CommandCon
return HandleCommandEntry( entry.get(), flags, mask);
}
}
else
{
// Handle multis too...
if( Str.IsSameAs( entry->name, false ) )
{
return HandleCommandEntry( entry.get(), flags, mask);
}
}
}
// Not one of the singleton commands.
// We could/should try all the list-style commands.

View File

@ -49,7 +49,7 @@ static const wxString kCoordTypeStrings[nCoordTypes] =
bool DragCommand::DefineParams( ShuttleParams & S ){
wxArrayString coords( nCoordTypes, kCoordTypeStrings );
S.OptionalN( bHasId ).Define( mId, wxT("Id"), 0.0, 11000.0, 1000000.0);
S.OptionalN( bHasId ).Define( mId, wxT("Id"), 11000.0, -100000.0, 1000000.0);
S.OptionalY( bHasWinName ).Define( mWinName, wxT("Window"), "Timeline");
S.OptionalY( bHasFromX ).Define( mFromX, wxT("FromX"), 200.0, 0.0, 1000000.0);
S.OptionalY( bHasFromY ).Define( mFromY, wxT("FromY"), 10.0, 0.0, 1000000.0);

View File

@ -142,6 +142,7 @@ bool ScreenshotCommand::DefineParams( ShuttleParams & S ){
S.Define( mPath, wxT("Path"), wxT(""));
S.DefineEnum( mWhat, wxT("CaptureWhat"), wxT("Window"), whats );
S.OptionalN(bHasBackground).DefineEnum( mBack, wxT("Background"), wxT("None"), backs );
S.OptionalN(bHasBringToTop).Define( mbBringToTop, wxT("ToTop"), true );
return true;
};
@ -156,6 +157,7 @@ void ScreenshotCommand::PopulateOrExchange(ShuttleGui & S)
S.TieTextBox( _("Path:"), mPath);
S.TieChoice( _("Capture What:"), mWhat, &whats);
S.TieChoice( _("Background:"), mBack, &backs);
S.TieCheckBox( _("Bring To Top:"), mbBringToTop);
}
S.EndMultiColumn();
}
@ -248,15 +250,17 @@ bool ScreenshotCommand::Capture(
if( r.width == 0 )
return false;
if (window ) {
if (window->IsTopLevel()) {
window->Raise();
}
else {
wxGetTopLevelParent(window)->Raise();
wxWindow * win = window;
wxTopLevelWindow * top_win= nullptr;
if( !window->IsTopLevel())
win = wxGetTopLevelParent(window);
top_win = dynamic_cast<wxTopLevelWindow*>( win );
if( (!bHasBringToTop || mbBringToTop) && (!top_win || !top_win->IsActive()) ){
win->Raise();
Yield();
}
}
Yield();
int screenW, screenH;
wxDisplaySize(&screenW, &screenH);

View File

@ -33,6 +33,7 @@ class CommandContext;
class ScreenshotCommand : public AudacityCommand
{
public:
ScreenshotCommand(){ mbBringToTop=true;};
// CommandDefinitionInterface overrides
wxString GetSymbol() override {return SCREENSHOT_PLUGIN_SYMBOL;};
wxString GetDescription() override {return _("Takes screenshots.");};
@ -46,7 +47,9 @@ private:
wxString mWhat;
wxString mBack;
wxString mPath;
bool mbBringToTop;
bool bHasBackground;
bool bHasBringToTop;
friend class ScreenshotCommand;
friend class ScreenFrame;

View File

@ -35,7 +35,7 @@ bool SetEnvelopeCommand::DefineParams( ShuttleParams & S ){
S.OptionalY( bHasTrackIndex ).Define( mTrackIndex, wxT("Track"), 0, 0, 100 );
S.OptionalN( bHasChannelIndex ).Define( mChannelIndex, wxT("Channel"), 0, 0, 100 );
S.OptionalY( bHasT ).Define( mT, wxT("Time"), 0.0, 0.0, 100000.0);
S.OptionalY( bHasV ).Define( mV, wxT("Value"), 0.0, 0.0, 2.0);
S.OptionalY( bHasV ).Define( mV, wxT("Value"), 1.0, 0.0, 2.0);
S.OptionalN( bHasDelete ).Define( mbDelete, wxT("Delete"), false );
return true;
};

View File

@ -77,10 +77,27 @@ static const wxString kScaleTypeStrings[nScaleTypes] =
};
enum kZoomTypes
{
kReset,
kTimes2,
kHalfWave,
nZoomTypes
};
static const wxString kZoomTypeStrings[nZoomTypes] =
{
XO("Reset"),
XO("Times2"),
XO("HalfWave"),
};
bool SetTrackCommand::DefineParams( ShuttleParams & S ){
wxArrayString colours( nColours, kColourStrings );
wxArrayString displays( nDisplayTypes, kDisplayTypeStrings );
wxArrayString scales( nScaleTypes, kScaleTypeStrings );
wxArrayString vzooms( nZoomTypes, kZoomTypeStrings );
S.OptionalY( bHasTrackIndex ).Define( mTrackIndex, wxT("Track"), 0, 0, 100 );
S.OptionalN( bHasChannelIndex ).Define( mChannelIndex, wxT("Channel"), 0, 0, 100 );
@ -92,6 +109,7 @@ bool SetTrackCommand::DefineParams( ShuttleParams & S ){
S.OptionalN( bHasScaleType ).DefineEnum( mScaleType, wxT("Scale"), kLinear, scales );
S.OptionalN( bHasColour ).DefineEnum( mColour, wxT("Color"), kColour0, colours );
S.OptionalN( bHasUseSpecPrefs ).Define( bUseSpecPrefs, wxT("SpecPrefs"), false );
S.OptionalN( bHasVZoom ).DefineEnum( mVZoom, wxT("VZoom"), kReset, vzooms );
S.OptionalN( bHasSpectralSelect ).Define( bSpectralSelect, wxT("SpectralSel"),true );
S.OptionalN( bHasGrayScale ).Define( bGrayScale, wxT("GrayScale"), false );
@ -108,6 +126,7 @@ void SetTrackCommand::PopulateOrExchange(ShuttleGui & S)
wxArrayString colours( nColours, kColourStrings );
wxArrayString displays( nDisplayTypes, kDisplayTypeStrings );
wxArrayString scales( nScaleTypes, kScaleTypeStrings );
wxArrayString vzooms( nZoomTypes, kZoomTypeStrings );
S.AddSpace(0, 5);
@ -122,6 +141,7 @@ void SetTrackCommand::PopulateOrExchange(ShuttleGui & S)
S.Optional( bHasColour ).TieChoice( _("Colour:"), mColour, &colours );
S.Optional( bHasDisplayType ).TieChoice( _("Display:"), mDisplayType, &displays );
S.Optional( bHasScaleType ).TieChoice( _("Scale:"), mScaleType, &scales );
S.Optional( bHasVZoom ).TieChoice( _("VZoom:"), mVZoom, &vzooms );
}
S.EndMultiColumn();
S.StartMultiColumn(2, wxALIGN_CENTER);
@ -188,11 +208,19 @@ bool SetTrackCommand::Apply(const CommandContext & context)
wt->GetSpectrogramSettings().spectralSelection = bSpectralSelect;
if( wt && bHasGrayScale )
wt->GetSpectrogramSettings().isGrayscale = bGrayScale;
if( wt && bHasVZoom ){
switch( mVZoom ){
default:
case kReset: wt->SetDisplayBounds(-1,1); break;
case kTimes2: wt->SetDisplayBounds(-2,2); break;
case kHalfWave: wt->SetDisplayBounds(0,1); break;
}
}
// These ones don't make sense on the second channel of a stereo track.
if( !bIsSecondChannel ){
if( bHasSelected )
t->SetSelected(bSelected);
if( bHasFocused )
if( bHasFocused && bFocused)
{
TrackPanel *panel = context.GetProject()->GetTrackPanel();
panel->SetFocusedTrack( t );

View File

@ -47,6 +47,7 @@ public:
int mHeight;
int mDisplayType;
int mScaleType;
int mVZoom;
bool bUseSpecPrefs;
bool bSpectralSelect;
bool bGrayScale;
@ -65,6 +66,7 @@ public:
bool bHasHeight;
bool bHasDisplayType;
bool bHasScaleType;
bool bHasVZoom;
bool bHasUseSpecPrefs;
bool bHasSpectralSelect;
bool bHasGrayScale;

View File

@ -141,12 +141,12 @@ class ControlToolBar final : public ToolBar {
enum
{
ID_PLAY_BUTTON = 11000,
ID_RECORD_BUTTON,
ID_PAUSE_BUTTON,
ID_PAUSE_BUTTON = 11000,
ID_PLAY_BUTTON,
ID_STOP_BUTTON,
ID_FF_BUTTON,
ID_REW_BUTTON,
ID_RECORD_BUTTON,
BUTTON_COUNT,
};

View File

@ -69,8 +69,8 @@ const int SEPARATOR_WIDTH = 14;
////////////////////////////////////////////////////////////
BEGIN_EVENT_TABLE( EditToolBar, ToolBar )
EVT_COMMAND_RANGE( ETBCutID,
ETBCutID + ETBNumButtons - 1,
EVT_COMMAND_RANGE( ETBCutID+first_ETB_ID,
ETBCutID+first_ETB_ID + ETBNumButtons - 1,
wxEVT_COMMAND_BUTTON_CLICKED,
EditToolBar::OnButton )
END_EVENT_TABLE()
@ -110,7 +110,7 @@ AButton *EditToolBar::AddButton(
r = ToolBar::MakeButton(pBar,
bmpRecoloredUpSmall, bmpRecoloredDownSmall, bmpRecoloredUpHiliteSmall, bmpRecoloredHiliteSmall,
eEnabledUp, eEnabledDown, eDisabled,
wxWindowID(id),
wxWindowID(id+first_ETB_ID),
wxDefaultPosition,
toggle,
theTheme.ImageSize( bmpRecoloredUpSmall ));
@ -291,7 +291,7 @@ void EditToolBar::ForAllButtons(int Action)
void EditToolBar::OnButton(wxCommandEvent &event)
{
int id = event.GetId();
int id = event.GetId()-first_ETB_ID;
// Be sure the pop-up happens even if there are exceptions, except for buttons which toggle.
auto cleanup = finally( [&] { mButtons[id]->InteractionOver();});

View File

@ -62,6 +62,8 @@ enum {
ETBNumButtons
};
const int first_ETB_ID = 11300;
// flags so 1,2,4,8 etc.
enum {
ETBActTooltips = 1,

View File

@ -67,8 +67,8 @@ IMPLEMENT_CLASS(ToolsToolBar, ToolBar);
////////////////////////////////////////////////////////////
BEGIN_EVENT_TABLE(ToolsToolBar, ToolBar)
EVT_COMMAND_RANGE(firstTool,
lastTool,
EVT_COMMAND_RANGE(firstTool+FirstToolID,
lastTool+FirstToolID,
wxEVT_COMMAND_BUTTON_CLICKED,
ToolsToolBar::OnTool)
END_EVENT_TABLE()
@ -165,7 +165,7 @@ AButton * ToolsToolBar::MakeTool(
bmpRecoloredUpHiliteSmall,
bmpRecoloredDownSmall, // Not bmpRecoloredHiliteSmall as down is inactive.
eTool, eTool, eTool,
wxWindowID(id),
wxWindowID(id + FirstToolID),
wxDefaultPosition, true,
theTheme.ImageSize( bmpRecoloredUpSmall ));
button->SetLabel( label );
@ -250,7 +250,7 @@ int ToolsToolBar::GetDownTool()
void ToolsToolBar::OnTool(wxCommandEvent & evt)
{
mCurrentTool = evt.GetId() - firstTool;
mCurrentTool = evt.GetId() - firstTool - FirstToolID;
for (int i = 0; i < numTools; i++)
if (i == mCurrentTool)
mTool[i]->PushDown();

View File

@ -41,9 +41,11 @@ enum {
numTools,
firstTool = selectTool,
lastTool = multiTool
lastTool = multiTool,
};
const int FirstToolID = 11200;
class ToolsToolBar final : public ToolBar {
public: