Audacity  2.2.2
BatchCommands.cpp
Go to the documentation of this file.
1 /**********************************************************************
2 
3  Audacity: A Digital Audio Editor
4 
5  MacroCommands.cpp
6 
7  Dominic Mazzoni
8  James Crook
9 
10 ********************************************************************//*******************************************************************/
17 
18 
19 #include "Audacity.h"
20 #include "BatchCommands.h"
21 
22 #include <wx/defs.h>
23 #include <wx/dir.h>
24 #include <wx/filedlg.h>
25 #include <wx/textfile.h>
26 
27 #include "AudacityApp.h"
28 #include "Project.h"
30 #include "effects/EffectManager.h"
31 #include "FileNames.h"
32 #include "Internat.h"
33 #include "PluginManager.h"
34 #include "Prefs.h"
35 #include "Shuttle.h"
36 #include "export/ExportFLAC.h"
37 #include "export/ExportMP3.h"
38 #include "export/ExportOGG.h"
39 #include "export/ExportPCM.h"
40 
41 #include "Theme.h"
42 #include "AllThemeResources.h"
43 
44 #include "Track.h"
45 #include "widgets/ErrorDialog.h"
46 
49 
50 // KLUDGE: All commands should be on the same footing
51 // however, for historical reasons we distinguish between
52 // - Effects (which are looked up in effects lists)
53 // - Menu commands (which are held in command manager)
54 // - Specials (which we deal with specially here)
56 
57 // TIDY-ME: Not currently translated,
58 // but there are issues to address if we do.
59 // CLEANSPEECH remnant
60 static const std::pair<const wxChar*, const wxChar*> SpecialCommands[] = {
61  // Use translations of the first members, some other day.
62  // For 2.2.2 we'll get them into the catalog at least.
63 
64  { XO("No Action"), wxT("NoAction") },
65 
66  // { wxT("Import"), wxT("Import") }, // non-functioning
67  /* i18n-hint: before is adverb; MP3 names an audio file format */
68  { XO("Export as MP3 56k before"), wxT("ExportMP3_56k_before") },
69 
70  /* i18n-hint: after is adverb; MP3 names an audio file format */
71  { XO("Export as MP3 56k after"), wxT("ExportMP3_56k_after") },
72 
73  /* i18n-hint: FLAC names an audio file format */
74  { XO("Export as FLAC"), wxT("ExportFLAC") },
75 
76 // MP3 OGG and WAV already handled by menu items.
77 #if 0
78  /* i18n-hint: MP3 names an audio file format */
79  { XO("Export as MP3"), wxT("ExportMP3") },
80 
81  /* i18n-hint: Ogg names an audio file format */
82  { XO("Export as Ogg"), wxT("ExportOgg") },
83 
84  /* i18n-hint: WAV names an audio file format */
85  { XO("Export as WAV"), wxT("ExportWAV") },
86 #endif
87 };
88 // end CLEANSPEECH remnant
89 
91 {
92  mMessage = "";
93  ResetMacro();
94 
95  wxArrayString names = GetNames();
96  wxArrayString defaults = GetNamesOfDefaultMacros();
97 
98  for( size_t i = 0;i<defaults.Count();i++){
99  wxString name = defaults[i];
100  if (names.Index(name) == wxNOT_FOUND) {
101  AddMacro(name);
102  RestoreMacro(name);
103  WriteMacro(name);
104  }
105  }
106 }
107 
108 static const wxString MP3Conversion = XO("MP3 Conversion");
109 static const wxString FadeEnds = XO("Fade Ends");
110 static const wxString SelectToEnds = XO("Select to Ends");
111 
112 
114 {
115  wxArrayString defaults;
116  defaults.Add( GetCustomTranslation( MP3Conversion ) );
117  defaults.Add( GetCustomTranslation( FadeEnds ) );
118  //Don't add this one anymore, as there is a new menu command for it.
119  //defaults.Add( GetCustomTranslation( SelectToEnds ) );
120  return defaults;
121 }
122 
123 void MacroCommands::RestoreMacro(const wxString & name)
124 {
125 // TIDY-ME: Effects change their name with localisation.
126 // Commands (at least currently) don't. Messy.
127  ResetMacro();
128  if (name == GetCustomTranslation( MP3Conversion ) ){
129  AddToMacro( wxT("Normalize") );
130  AddToMacro( wxT("ExportMP3") );
131  } else if (name == GetCustomTranslation( FadeEnds ) ){
132  AddToMacro( wxT("Select"), wxT("Start=\"0\" End=\"1\"") );
133  AddToMacro( wxT("FadeIn") );
134  AddToMacro( wxT("Select"), wxT("Start=\"0\" End=\"1\" RelativeTo=\"ProjectEnd\"") );
135  AddToMacro( wxT("FadeOut") );
136  AddToMacro( wxT("Select"), wxT("Start=\"0\" End=\"0\"") );
137  } else if (name == GetCustomTranslation( SelectToEnds ) ){
138  AddToMacro( wxT("SelCursorEnd") );
139  AddToMacro( wxT("SelStartCursor") );
140  }
141 }
142 
143 wxString MacroCommands::GetCommand(int index)
144 {
145  if (index < 0 || index >= (int)mCommandMacro.GetCount()) {
146  return wxT("");
147  }
148 
149  return mCommandMacro[index];
150 }
151 
152 wxString MacroCommands::GetParams(int index)
153 {
154  if (index < 0 || index >= (int)mParamsMacro.GetCount()) {
155  return wxT("");
156  }
157 
158  return mParamsMacro[index];
159 }
160 
162 {
163  return (int)mCommandMacro.GetCount();
164 }
165 
166 bool MacroCommands::ReadMacro(const wxString & macro)
167 {
168  // Clear any previous macro
169  ResetMacro();
170 
171  // Build the filename
172  wxFileName name(FileNames::MacroDir(), macro, wxT("txt"));
173 
174  // Set the file name
175  wxTextFile tf(name.GetFullPath());
176 
177  // Open and check
178  tf.Open();
179  if (!tf.IsOpened()) {
180  // wxTextFile will display any errors
181  return false;
182  }
183 
184  // Load commands from the file
185  int lines = tf.GetLineCount();
186  if (lines > 0) {
187  for (int i = 0; i < lines; i++) {
188 
189  // Find the command name terminator...ingore line if not found
190  int splitAt = tf[i].Find(wxT(':'));
191  if (splitAt < 0) {
192  continue;
193  }
194 
195  // Parse and clean
196  wxString cmd = tf[i].Left(splitAt).Strip(wxString::both);
197  wxString parm = tf[i].Mid(splitAt + 1).Strip(wxString::trailing);
198 
199  // Add to lists
200  mCommandMacro.Add(cmd);
201  mParamsMacro.Add(parm);
202  }
203  }
204 
205  // Done with the file
206  tf.Close();
207 
208  return true;
209 }
210 
211 
212 bool MacroCommands::WriteMacro(const wxString & macro)
213 {
214  // Build the filename
215  wxFileName name(FileNames::MacroDir(), macro, wxT("txt"));
216 
217  // Set the file name
218  wxTextFile tf(name.GetFullPath());
219 
220  // Create the file (Create() doesn't leave the file open)
221  if (!tf.Exists()) {
222  tf.Create();
223  }
224 
225  // Open it
226  tf.Open();
227 
228  if (!tf.IsOpened()) {
229  // wxTextFile will display any errors
230  return false;
231  }
232 
233  // Start with a clean slate
234  tf.Clear();
235 
236  // Copy over the commands
237  int lines = mCommandMacro.GetCount();
238  for (int i = 0; i < lines; i++) {
239  tf.AddLine(mCommandMacro[i] + wxT(":") + mParamsMacro[ i ]);
240  }
241 
242  // Write the macro
243  tf.Write();
244 
245  // Done with the file
246  tf.Close();
247 
248  return true;
249 }
250 
251 bool MacroCommands::AddMacro(const wxString & macro)
252 {
253  // Build the filename
254  wxFileName name(FileNames::MacroDir(), macro, wxT("txt"));
255 
256  // Set the file name
257  wxTextFile tf(name.GetFullPath());
258 
259  // Create it..Create will display errors
260  return tf.Create();
261 }
262 
263 bool MacroCommands::DeleteMacro(const wxString & macro)
264 {
265  // Build the filename
266  wxFileName name(FileNames::MacroDir(), macro, wxT("txt"));
267 
268  // Delete it...wxRemoveFile will display errors
269  auto result = wxRemoveFile(name.GetFullPath());
270 
271  // Delete any legacy chain that it shadowed
272  auto oldPath = wxFileName{ FileNames::LegacyChainDir(), macro, wxT("txt") };
273  wxRemoveFile(oldPath.GetFullPath()); // Don't care about this return value
274 
275  return result;
276 }
277 
278 bool MacroCommands::RenameMacro(const wxString & oldmacro, const wxString & newmacro)
279 {
280  // Build the filenames
281  wxFileName oname(FileNames::MacroDir(), oldmacro, wxT("txt"));
282  wxFileName nname(FileNames::MacroDir(), newmacro, wxT("txt"));
283 
284  // Rename it...wxRenameFile will display errors
285  return wxRenameFile(oname.GetFullPath(), nname.GetFullPath());
286 }
287 
288 // Gets all commands that are valid for this mode.
290 {
291  if (!project)
292  return;
293 
294  // CLEANSPEECH remnant
295  Entries commands;
296  for( const auto &command : SpecialCommands )
297  commands.push_back( {
298  { command.second, GetCustomTranslation( command.first ) },
299  _("Special Command")
300  } );
301 
302  // end CLEANSPEECH remnant
303 
306  {
308  while (plug)
309  {
310  auto command = em.GetCommandIdentifier(plug->GetID());
311  if (!command.IsEmpty())
312  commands.push_back( {
313  { command, plug->GetSymbol().Translation() },
314  plug->GetPluginType() == PluginTypeEffect ?
315  _("Effect") : _("Menu Command (With Parameters)")
316  } );
318  }
319  }
320 
321  auto mManager = project->GetCommandManager();
322  wxArrayString mLabels;
323  wxArrayString mNames;
324  std::vector<bool> vHasDialog;
325  mLabels.Clear();
326  mNames.Clear();
327  mManager->GetAllCommandLabels(mLabels, vHasDialog, true);
328  mManager->GetAllCommandNames(mNames, true);
329 
330  const bool english = wxGetLocale()->GetCanonicalName().StartsWith(wxT("en"));
331 
332  for(size_t i=0; i<mNames.GetCount(); i++) {
333  wxString label = mLabels[i];
334  if( !vHasDialog[i] ){
335  label.Replace( "&", "" );
336  bool suffix;
337  if (!english)
338  suffix = false;
339  else {
340  // We'll disambiguate if the squashed name is short and shorter than the internal name.
341  // Otherwise not.
342  // This means we won't have repetitive items like "Cut (Cut)"
343  // But we will show important disambiguation like "All (SelectAll)" and "By Date (SortByDate)"
344  // Disambiguation is no longer essential as the details box will show it.
345  // PRL: I think this reasoning applies only when locale is English.
346  // For other locales, show the (CamelCaseCodeName) always. Or, never?
347  wxString squashed = label;
348  squashed.Replace( " ", "" );
349 
350  suffix = squashed.Length() < wxMin( 18, mNames[i].Length());
351  }
352 
353  if( suffix )
354  label = label + " (" + mNames[i] + ")";
355 
356  commands.push_back(
357  {
358  {
359  mNames[i], // Internal name.
360  label // User readable name
361  },
362  _("Menu Command (No Parameters)")
363  }
364  );
365  }
366  }
367 
368  // Sort commands by their user-visible names.
369  // PRL: What exactly should happen if first members of pairs are not unique?
370  // I'm not sure, but at least I can sort stably for a better defined result,
371  // keeping specials before effects and menu items, and lastly commands.
372  auto less =
373  [](const Entry &a, const Entry &b)
374  { return a.name.Translated() < b.name.Translated(); };
375  std::stable_sort(commands.begin(), commands.end(), less);
376 
377  // Now uniquify by friendly name
378  auto equal =
379  [](const Entry &a, const Entry &b)
380  { return a.name.Translated() == b.name.Translated(); };
381  std::unique_copy(
382  commands.begin(), commands.end(), std::back_inserter(mCommands), equal);
383 }
384 
385 // binary search
386 auto MacroCommandsCatalog::ByFriendlyName( const wxString &friendlyName ) const
387  -> Entries::const_iterator
388 {
389  const auto less = [](const Entry &entryA, const Entry &entryB)
390  { return entryA.name.Translated() < entryB.name.Translated(); };
391  auto range = std::equal_range(
392  begin(), end(), Entry{ { {}, friendlyName }, {} }, less
393  );
394  if (range.first != range.second) {
395  wxASSERT_MSG( range.first + 1 == range.second,
396  "Non-unique user-visible command name" );
397  return range.first;
398  }
399  else
400  return end();
401 }
402 
403 // linear search
404 auto MacroCommandsCatalog::ByCommandId( const wxString &commandId ) const
405  -> Entries::const_iterator
406 {
407  // Maybe this too should have a uniqueness check?
408  return std::find_if( begin(), end(),
409  [&](const Entry &entry)
410  { return entry.name.Internal() == commandId; });
411 }
412 
413 
414 
415 wxString MacroCommands::GetCurrentParamsFor(const wxString & command)
416 {
417  const PluginID & ID = EffectManager::Get().GetEffectByIdentifier(command);
418  if (ID.empty())
419  {
420  return wxEmptyString; // effect not found.
421  }
422 
424 }
425 
426 wxString MacroCommands::PromptForParamsFor(const wxString & command, const wxString & params, wxWindow *parent)
427 {
428  const PluginID & ID = EffectManager::Get().GetEffectByIdentifier(command);
429  if (ID.empty())
430  {
431  return wxEmptyString; // effect not found
432  }
433 
434  wxString res = params;
435 
436  auto cleanup = EffectManager::Get().SetBatchProcessing(ID);
437 
438  if (EffectManager::Get().SetEffectParameters(ID, params))
439  {
440  if (EffectManager::Get().PromptUser(ID, parent))
441  {
443  }
444  }
445 
446  return res;
447 }
448 
449 wxString MacroCommands::PromptForPresetFor(const wxString & command, const wxString & params, wxWindow *parent)
450 {
451  const PluginID & ID = EffectManager::Get().GetEffectByIdentifier(command);
452  if (ID.empty())
453  {
454  return wxEmptyString; // effect not found.
455  }
456 
457  wxString preset = EffectManager::Get().GetPreset(ID, params, parent);
458 
459  // Preset will be empty if the user cancelled the dialog, so return the original
460  // parameter value.
461  if (preset.IsEmpty())
462  {
463  return params;
464  }
465 
466  return preset;
467 }
468 
470 {
471  AudacityProject *project = GetActiveProject();
472  if( project == NULL )
473  {
474  //AudacityMessageBox( _("No project to process!") );
475  return -1.0;
476  }
477  TrackList * tracks = project->GetTracks();
478  if( tracks == NULL )
479  {
480  //AudacityMessageBox( _("No tracks to process!") );
481  return -1.0;
482  }
483 
484  double endTime = tracks->GetEndTime();
485  return endTime;
486 }
487 
489 {
490  AudacityProject *project = GetActiveProject();
491  if( project == NULL )
492  {
493  //AudacityMessageBox( _("No project and no Audio to process!") );
494  return false;
495  }
496 
497  TrackList * tracks = project->GetTracks();
498  if( tracks == NULL )
499  {
500  //AudacityMessageBox( _("No tracks to process!") );
501  return false;
502  }
503 
504  TrackListIterator iter(tracks);
505  Track *t = iter.First();
506  bool mono = true;
507  while (t) {
508  if (t->GetLinked()) {
509  mono = false;
510  break;
511  }
512  t = iter.Next();
513  }
514 
515  return mono;
516 }
517 
518 wxString MacroCommands::BuildCleanFileName(const wxString &fileName, const wxString &extension)
519 {
520  const wxFileName newFileName{ fileName };
521  wxString justName = newFileName.GetName();
522  wxString pathName = newFileName.GetPath(wxPATH_GET_VOLUME | wxPATH_GET_SEPARATOR);
523  const auto cleanedString = _("cleaned");
524 
525  if (justName.empty()) {
526  wxDateTime now = wxDateTime::Now();
527  int year = now.GetYear();
528  wxDateTime::Month month = now.GetMonth();
529  wxString monthName = now.GetMonthName(month);
530  int dom = now.GetDay();
531  int hour = now.GetHour();
532  int minute = now.GetMinute();
533  int second = now.GetSecond();
534  justName = wxString::Format(wxT("%d-%s-%02d-%02d-%02d-%02d"),
535  year, monthName, dom, hour, minute, second);
536 
537 // SetName(cleanedFileName);
538 // bool isStereo;
539 // double endTime = project->mTracks->GetEndTime();
540 // double startTime = 0.0;
541  //OnSelectAll();
543  ::AudacityMessageBox(wxString::Format(_("Export recording to %s\n/%s/%s%s"),
544  pathName, cleanedString, justName, extension),
545  _("Export recording"),
546  wxOK | wxCENTRE);
547  pathName += wxFileName::GetPathSeparator();
548  }
549  wxString cleanedName = pathName;
550  cleanedName += cleanedString;
551  bool flag = ::wxFileName::FileExists(cleanedName);
552  if (flag == true) {
553  ::AudacityMessageBox(_("Cannot create directory 'cleaned'. \nFile already exists that is not a directory"));
554  return wxString{};
555  }
556  ::wxFileName::Mkdir(cleanedName, 0777, wxPATH_MKDIR_FULL); // make sure it exists
557 
558  cleanedName += wxFileName::GetPathSeparator();
559  cleanedName += justName;
560  cleanedName += extension;
561  wxGetApp().AddFileToHistory(cleanedName);
562 
563  return cleanedName;
564 }
565 
566 // TODO Move this out of Batch Commands
567 bool MacroCommands::WriteMp3File( const wxString & Name, int bitrate )
568 { //check if current project is mono or stereo
569  unsigned numChannels = 2;
570  if (IsMono()) {
571  numChannels = 1;
572  }
573 
574  double endTime = GetEndTime();
575  if( endTime <= 0.0f )
576  return false;
577  AudacityProject *project = GetActiveProject();
578  if( bitrate <=0 )
579  {
580  // 'No' bitrate given, use the current default.
581  // Use Mp3Stereo to control if export is to a stereo or mono file
582  return mExporter.Process(project, numChannels, wxT("MP3"), Name, false, 0.0, endTime);
583  }
584 
585 
586  bool rc;
587  long prevBitRate = gPrefs->Read(wxT("/FileFormats/MP3Bitrate"), 128);
588  gPrefs->Write(wxT("/FileFormats/MP3Bitrate"), bitrate);
589 
590  auto cleanup = finally( [&] {
591  gPrefs->Write(wxT("/FileFormats/MP3Bitrate"), prevBitRate);
592  gPrefs->Flush();
593  } );
594 
595  // Use Mp3Stereo to control if export is to a stereo or mono file
596  rc = mExporter.Process(project, numChannels, wxT("MP3"), Name, false, 0.0, endTime);
597  return rc;
598 }
599 
600 // TIDY-ME: Get rid of special commands and make them part of the
601 // 'menu' system (but not showing on the menu)
602 //
603 // ======= IMPORTANT ========
604 // Special Commands are a KLUDGE whilst we wait for a better system to handle the menu
605 // commands from batch mode.
606 //
607 // Really we should be using a similar (or same) system to that used for effects
608 // so that parameters can be passed to the commands. Many of the menu
609 // commands take a selection as their parameter.
610 //
611 // If you find yourself adding lots of existing commands from the menus here, STOP
612 // and think again.
613 // ======= IMPORTANT ========
614 // CLEANSPEECH remnant
616  int WXUNUSED(iCommand), const wxString &friendlyCommand,
617  const wxString & command, const wxString & params)
618 {
619  if (ReportAndSkip(friendlyCommand, params))
620  return true;
621 
622  AudacityProject *project = GetActiveProject();
623 
624  unsigned numChannels = 1; //used to switch between mono and stereo export
625  if (IsMono()) {
626  numChannels = 1; //export in mono
627  } else {
628  numChannels = 2; //export in stereo
629  }
630 
631  wxString filename;
632  wxString extension; // required for correct message
633  if (command == wxT("ExportWAV"))
634  extension = wxT(".wav");
635  else if (command == wxT("ExportOgg"))
636  extension = wxT(".ogg");
637  else if (command == wxT("ExportFLAC"))
638  extension = wxT(".flac");
639  else extension = wxT(".mp3");
640 
641  if (mFileName.IsEmpty()) {
642  filename = BuildCleanFileName(project->GetFileName(), extension);
643  }
644  else {
645  filename = BuildCleanFileName(mFileName, extension);
646  }
647 
648  // We have a command index, but we don't use it!
649  // TODO: Make this special-batch-command code use the menu item code....
650  // FIXME: TRAP_ERR No error reporting on write file failure in batch mode.
651  if (command == wxT("NoAction")) {
652  return true;
653  } else if (!mFileName.IsEmpty() && command == wxT("Import")) {
654  // historically this was in use, now ignored if there
655  return true;
656  } else if (command == wxT("ExportMP3_56k_before")) {
657  filename.Replace(wxT("cleaned/"), wxT("cleaned/MasterBefore_"), false);
658  return WriteMp3File(filename, 56);
659  } else if (command == wxT("ExportMP3_56k_after")) {
660  filename.Replace(wxT("cleaned/"), wxT("cleaned/MasterAfter_"), false);
661  return WriteMp3File(filename, 56);
662  } else if (command == wxT("ExportMP3")) {
663  return WriteMp3File(filename, 0); // 0 bitrate means use default/current
664  } else if (command == wxT("ExportWAV")) {
665  filename.Replace(wxT(".mp3"), wxT(".wav"), false);
666  double endTime = GetEndTime();
667  if (endTime <= 0.0f) {
668  return false;
669  }
670  return mExporter.Process(project, numChannels, wxT("WAV"), filename, false, 0.0, endTime);
671  } else if (command == wxT("ExportOgg")) {
672 #ifdef USE_LIBVORBIS
673  filename.Replace(wxT(".mp3"), wxT(".ogg"), false);
674  double endTime = GetEndTime();
675  if (endTime <= 0.0f) {
676  return false;
677  }
678  return mExporter.Process(project, numChannels, wxT("OGG"), filename, false, 0.0, endTime);
679 #else
680  AudacityMessageBox(_("Ogg Vorbis support is not included in this build of Audacity"));
681  return false;
682 #endif
683  } else if (command == wxT("ExportFLAC")) {
684 #ifdef USE_LIBFLAC
685  filename.Replace(wxT(".mp3"), wxT(".flac"), false);
686  double endTime = GetEndTime();
687  if (endTime <= 0.0f) {
688  return false;
689  }
690  return mExporter.Process(project, numChannels, wxT("FLAC"), filename, false, 0.0, endTime);
691 #else
692  AudacityMessageBox(_("FLAC support is not included in this build of Audacity"));
693  return false;
694 #endif
695  }
697  wxString::Format(_("Command %s not implemented yet"), friendlyCommand));
698  return false;
699 }
700 // end CLEANSPEECH remnant
701 
703  const PluginID & ID, const wxString &friendlyCommand,
704  const wxString & command, const wxString & params,
705  const CommandContext & Context)
706 {
707  static_cast<void>(command);//compiler food.
708 
709  //Possibly end processing here, if in batch-debug
710  if( ReportAndSkip(friendlyCommand, params))
711  return true;
712 
713  const PluginDescriptor *plug = PluginManager::Get().GetPlugin(ID);
714  if (!plug)
715  return false;
716 
717  AudacityProject *project = GetActiveProject();
718 
719  // FIXME: for later versions may want to not select-all in batch mode.
720  // IF nothing selected, THEN select everything
721  // (most effects require that you have something selected).
723  project->SelectAllIfNone();
724 
725  bool res = false;
726 
727  auto cleanup = EffectManager::Get().SetBatchProcessing(ID);
728 
729  // transfer the parameters to the effect...
730  if (EffectManager::Get().SetEffectParameters(ID, params))
731  {
733  // and apply the effect...
734  res = project->DoAudacityCommand(ID,
735  Context,
739  else
740  // and apply the effect...
741  res = project->DoEffect(ID,
742  Context,
746  }
747 
748  return res;
749 }
750 
751 bool MacroCommands::ApplyCommand( const wxString &friendlyCommand,
752  const wxString & command, const wxString & params,
753  CommandContext const * pContext)
754 {
755 
756  unsigned int i;
757  // Test for a special command.
758  // CLEANSPEECH remnant
759  for( i = 0; i < sizeof(SpecialCommands)/sizeof(*SpecialCommands); ++i ) {
760  if( command.IsSameAs( SpecialCommands[i].second, false) )
761  return ApplySpecialCommand( i, friendlyCommand, command, params );
762  }
763  // end CLEANSPEECH remnant
764 
765  // Test for an effect.
766  const PluginID & ID = EffectManager::Get().GetEffectByIdentifier( command );
767  if (!ID.empty())
768  {
769  if( pContext )
770  return ApplyEffectCommand(
771  ID, friendlyCommand, command, params, *pContext);
772  const CommandContext context( *GetActiveProject() );
773  return ApplyEffectCommand(
774  ID, friendlyCommand, command, params, context);
775  }
776 
777  AudacityProject *project = GetActiveProject();
778  CommandManager * pManager = project->GetCommandManager();
779  if( pContext ){
780  if( pManager->HandleTextualCommand( command, *pContext, AlwaysEnabledFlag, AlwaysEnabledFlag ) )
781  return true;
782  pContext->Status( wxString::Format(
783  _("Your batch command of %s was not recognized."), friendlyCommand ));
784  return false;
785  }
786  else
787  {
788  const CommandContext context( *GetActiveProject() );
789  if( pManager->HandleTextualCommand( command, context, AlwaysEnabledFlag, AlwaysEnabledFlag ) )
790  return true;
791  }
792 
794  wxString::Format(
795  _("Your batch command of %s was not recognized."), friendlyCommand ));
796 
797  return false;
798 }
799 
800 bool MacroCommands::ApplyCommandInBatchMode( const wxString &friendlyCommand,
801  const wxString & command, const wxString &params)
802 {
803  AudacityProject *project = GetActiveProject();
804  // Recalc flags and enable items that may have become enabled.
805  project->UpdateMenus(false);
806  // enter batch mode...
807  bool prevShowMode = project->GetShowId3Dialog();
808  auto cleanup = finally( [&] {
809  // exit batch mode...
810  project->SetShowId3Dialog(prevShowMode);
811  } );
812 
813  return ApplyCommand( friendlyCommand, command, params );
814 }
815 
816 static int MacroReentryCount = 0;
817 // ApplyMacro returns true on success, false otherwise.
818 // Any error reporting to the user in setting up the macro
819 // has already been done.
821  const MacroCommandsCatalog &catalog, const wxString & filename)
822 {
823  // Check for reentrant ApplyMacro commands.
824  // We'll allow 1 level of reentry, but not more.
825  // And we treat ignoring deeper levels as a success.
826  if( MacroReentryCount > 1 )
827  return true;
828 
829  // Restore the reentry counter (to zero) when we exit.
830  auto cleanup1 = valueRestorer( MacroReentryCount);
831  MacroReentryCount++;
832 
833  mFileName = filename;
834 
836  bool res = false;
837  auto cleanup2 = finally( [&] {
838  if (!res) {
839  if(proj) {
840  // Macro failed or was cancelled; revert to the previous state
841  proj->RollbackState();
842  }
843  }
844  } );
845 
846  mAbort = false;
847 
848  size_t i = 0;
849  for (; i < mCommandMacro.size(); i++) {
850  const auto &command = mCommandMacro[i];
851  auto iter = catalog.ByCommandId(command);
852  auto friendly = (iter == catalog.end())
853  ? command // Expose internal name to user, in default of a better one!
854  : iter->name.Translated();
855  if (!ApplyCommandInBatchMode(friendly, command, mParamsMacro[i]) || mAbort)
856  break;
857  }
858 
859  res = (i == mCommandMacro.size());
860  if (!res)
861  return false;
862 
863  mFileName.Empty();
864 
865  // Macro was successfully applied; save the NEW project state
866  wxString longDesc, shortDesc;
867  wxString name = gPrefs->Read(wxT("/Batch/ActiveMacro"), wxEmptyString);
868  if (name.IsEmpty())
869  {
870  /* i18n-hint: active verb in past tense */
871  longDesc = _("Applied Macro");
872  shortDesc = _("Apply Macro");
873  }
874  else
875  {
876  /* i18n-hint: active verb in past tense */
877  longDesc = wxString::Format(_("Applied Macro '%s'"), name);
878  shortDesc = wxString::Format(_("Apply '%s'"), name);
879  }
880 
881  if (!proj)
882  return false;
883  if( MacroReentryCount <= 1 )
884  proj->PushState(longDesc, shortDesc);
885  return true;
886 }
887 
888 // AbortBatch() allows a premature terminatation of a batch.
890 {
891  mAbort = true;
892 }
893 
894 void MacroCommands::AddToMacro(const wxString &command, int before)
895 {
896  AddToMacro(command, GetCurrentParamsFor(command), before);
897 }
898 
899 void MacroCommands::AddToMacro(const wxString &command, const wxString &params, int before)
900 {
901  if (before == -1) {
902  before = (int)mCommandMacro.GetCount();
903  }
904 
905  mCommandMacro.Insert(command, before);
906  mParamsMacro.Insert(params, before);
907 }
908 
910 {
911  if (index < 0 || index >= (int)mCommandMacro.GetCount()) {
912  return;
913  }
914 
915  mCommandMacro.RemoveAt(index);
916  mParamsMacro.RemoveAt(index);
917 }
918 
920 {
921  mCommandMacro.Clear();
922  mParamsMacro.Clear();
923 }
924 
925 // ReportAndSkip() is a diagnostic function that avoids actually
926 // applying the requested effect if in batch-debug mode.
928  const wxString & friendlyCommand, const wxString & params)
929 {
930  int bDebug;
931  gPrefs->Read(wxT("/Batch/Debug"), &bDebug, false);
932  if( bDebug == 0 )
933  return false;
934 
935  //TODO: Add a cancel button to these, and add the logic so that we can abort.
936  if( params != wxT("") )
937  {
938  AudacityMessageBox( wxString::Format(_("Apply %s with parameter(s)\n\n%s"),friendlyCommand, params),
939  _("Test Mode"));
940  }
941  else
942  {
943  AudacityMessageBox( wxString::Format(_("Apply %s"), friendlyCommand),
944  _("Test Mode"));
945  }
946  return true;
947 }
948 
950 {
951  static bool done = false;
952  if (!done) {
953  // Check once per session at most
954 
955  // Copy chain files from the old Chains into the new Macros directory,
956  // but only if like-named files are not already present in Macros.
957 
958  // Leave the old copies in place, in case a user wants to go back to
959  // an old Audacity version. They will have their old chains intact, but
960  // won't have any edits they made to the copy that now lives in Macros
961  // which old Audacity will not read.
962 
963  const auto oldDir = FileNames::LegacyChainDir();
964  wxArrayString files;
965  wxDir::GetAllFiles(oldDir, &files, wxT("*.txt"), wxDIR_FILES);
966 
967  // add a dummy path component to be overwritten by SetFullName
968  wxFileName newDir{ FileNames::MacroDir(), wxT("x") };
969 
970  for (const auto &file : files) {
971  auto name = wxFileName{file}.GetFullName();
972  newDir.SetFullName(name);
973  const auto newPath = newDir.GetFullPath();
974  if (!wxFileExists(newPath))
975  FileNames::CopyFile(file, newPath);
976  }
977  done = true;
978  }
979  // To do: use std::once
980 }
981 
982 wxArrayString MacroCommands::GetNames()
983 {
985 
986  wxArrayString names;
987  wxArrayString files;
988  wxDir::GetAllFiles(FileNames::MacroDir(), &files, wxT("*.txt"), wxDIR_FILES);
989  size_t i;
990 
991  wxFileName ff;
992  for (i = 0; i < files.GetCount(); i++) {
993  ff = (files[i]);
994  names.Add(ff.GetName());
995  }
996 
997  return names;
998 }
999 
1000 bool MacroCommands::IsFixed(const wxString & name)
1001 {
1002  wxArrayString defaults = GetNamesOfDefaultMacros();
1003  if( defaults.Index( name ) != wxNOT_FOUND )
1004  return true;
1005  return false;
1006 }
1007 
1008 void MacroCommands::Split(const wxString & str, wxString & command, wxString & param)
1009 {
1010  int splitAt;
1011 
1012  command.Empty();
1013  param.Empty();
1014 
1015  if (str.IsEmpty()) {
1016  return;
1017  }
1018 
1019  splitAt = str.Find(wxT(':'));
1020  if (splitAt < 0) {
1021  return;
1022  }
1023 
1024  command = str.Mid(0, splitAt);
1025  param = str.Mid(splitAt + 1);
1026 
1027  return;
1028 }
1029 
1030 wxString MacroCommands::Join(const wxString & command, const wxString & param)
1031 {
1032  return command + wxT(": ") + param;
1033 }
void UpdateMenus(bool checkActive=true)
Definition: Menus.cpp:2434
bool AddMacro(const wxString &macro)
static wxString FindDefaultPath(Operation op)
Definition: FileNames.cpp:390
void AddToMacro(const wxString &command, int before=-1)
static const std::pair< const wxChar *, const wxChar * > SpecialCommands[]
static wxArrayString names()
Definition: Tags.cpp:697
AudacityPrefs * gPrefs
Definition: Prefs.cpp:73
A list of TrackListNode items.
Definition: Track.h:618
const PluginDescriptor * GetFirstPlugin(int type)
wxString GetParams(int index)
static const int kConfigured
Definition: Project.h:489
static const wxString MP3Conversion
Definition: BatchCommands.h:27
static const int kDontRepeatLast
Definition: Project.h:493
wxString mFileName
wxString mMessage
static const wxString SelectToEnds
wxString BuildCleanFileName(const wxString &fileName, const wxString &extension)
static wxString PromptForPresetFor(const wxString &command, const wxString &params, wxWindow *parent)
bool DoEffect(const PluginID &ID, const CommandContext &context, int flags)
Definition: Menus.cpp:4512
wxString GetCommand(int index)
double GetEndTime() const
Definition: Track.cpp:1418
const PluginID & GetEffectByIdentifier(const wxString &strTarget)
wxString label
Definition: Tags.cpp:727
bool Process(AudacityProject *project, bool selectedOnly, double t0, double t1)
Definition: Export.cpp:338
virtual void Status(const wxString &message, bool bFlush=false) const
bool HandleTextualCommand(const wxString &Str, const CommandContext &context, CommandFlag flags, CommandMask mask)
bool GetLinked() const
Definition: Track.h:278
static wxArrayString GetNames()
wxString PluginID
Definition: Types.h:209
static wxString MacroDir()
Definition: FileNames.cpp:199
void SetShowId3Dialog(bool flag)
Definition: Project.h:317
bool RenameMacro(const wxString &oldmacro, const wxString &newmacro)
const PluginDescriptor * GetNextPlugin(int type)
#define XO(s)
Definition: Internat.h:33
int AudacityMessageBox(const wxString &message, const wxString &caption=AudacityMessageBoxCaptionStr(), long style=wxOK|wxCENTRE, wxWindow *parent=NULL, int x=wxDefaultCoord, int y=wxDefaultCoord)
Definition: ErrorDialog.h:92
bool ApplyMacro(const MacroCommandsCatalog &catalog, const wxString &filename=wxT(""))
CommandContext provides addiitonal information to an 'Apply()' command. It provides the project...
CommandManager * GetCommandManager()
Definition: Project.h:346
TranslatedInternalString name
Definition: BatchCommands.h:28
static const wxString FadeEnds
bool ApplySpecialCommand(int iCommand, const wxString &friendlyCommand, const wxString &command, const wxString &params)
const wxString & Internal() const
Definition: Internat.h:210
const wxString & Translation() const
Entries::const_iterator ByFriendlyName(const wxString &friendlyName) const
void RestoreMacro(const wxString &name)
void AddFileToHistory(const wxString &name)
bool ReportAndSkip(const wxString &friendlyCommand, const wxString &params)
AudacityProject provides the main window, with tools and tracks contained within it.
Definition: Project.h:176
bool ApplyCommand(const wxString &friendlyCommand, const wxString &command, const wxString &params, CommandContext const *pContext=NULL)
const wxString Translated() const
Definition: Internat.h:211
void RollbackState()
Definition: Project.cpp:4735
std::vector< Entry > Entries
Definition: BatchCommands.h:31
static const int kSkipState
Definition: Project.h:491
bool GetShowId3Dialog()
Definition: Project.h:316
bool DeleteMacro(const wxString &name)
Fundamental data object of Audacity, placed in the TrackPanel. Classes derived form it include the Wa...
Definition: Track.h:101
CommandManager implements a system for organizing all user-callable commands.
static bool CopyFile(const wxString &file1, const wxString &file2, bool overwrite=true)
Definition: FileNames.cpp:46
EffectManager is the class that handles effects and effect categories.
Definition: EffectManager.h:45
static wxString PromptForParamsFor(const wxString &command, const wxString &params, wxWindow *parent)
virtual Track * First(TrackList *val=nullptr)
Definition: Track.cpp:418
const IdentInterfaceSymbol & GetSymbol() const
static EffectManager & Get()
wxString GetPreset(const PluginID &ID, const wxString &params, wxWindow *parent)
PluginManager maintains a list of all plug ins. That covers modules, effects, generators, analysis-effects, commands. It also has functions for shared and private configs - which need to move out.
static wxArrayString GetNamesOfDefaultMacros()
bool WriteMp3File(const wxString &Name, int bitrate)
bool ReadMacro(const wxString &macro)
An iterator for a TrackList.
Definition: Track.h:401
wxArrayString mParamsMacro
bool WriteMacro(const wxString &macro)
_("Move Track &Down")+wxT("\t")+(GetActiveProject() -> GetCommandManager() ->GetKeyFromName(wxT("TrackMoveDown")).Raw()), OnMoveTrack) POPUP_MENU_ITEM(OnMoveTopID, _("Move Track to &Top")+wxT("\t")+(GetActiveProject() ->GetCommandManager() ->GetKeyFromName(wxT("TrackMoveTop")).Raw()), OnMoveTrack) POPUP_MENU_ITEM(OnMoveBottomID, _("Move Track to &Bottom")+wxT("\t")+(GetActiveProject() ->GetCommandManager() ->GetKeyFromName(wxT("TrackMoveBottom")).Raw()), OnMoveTrack)#define SET_TRACK_NAME_PLUGIN_SYMBOLclass SetTrackNameCommand:public AudacityCommand
wxString Join(const wxString &command, const wxString &param)
wxString GetEffectParameters(const PluginID &ID)
ValueRestorer< T > valueRestorer(T &var)
Definition: MemoryX.h:494
AUDACITY_DLL_API const wxString & GetCustomTranslation(const wxString &str1)
Definition: Internat.cpp:75
Entries::const_iterator ByCommandId(const wxString &commandId) const
void DeleteFromMacro(int index)
void PushState(const wxString &desc, const wxString &shortDesc)
Definition: Project.cpp:4695
const wxChar * name
Definition: Distortion.cpp:94
const wxString & GetID() const
virtual Track * Next(bool skiplinked=false)
Definition: Track.cpp:460
AUDACITY_DLL_API AudacityProject * GetActiveProject()
Definition: Project.cpp:308
wxArrayString mCommandMacro
wxString GetCommandIdentifier(const PluginID &ID)
Entries::const_iterator end() const
Definition: BatchCommands.h:44
Exporter mExporter
double GetEndTime()
PluginType GetPluginType() const
AudacityApp & wxGetApp()
TrackList * GetTracks()
Definition: Project.h:192
bool ApplyCommandInBatchMode(const wxString &friendlyCommand, const wxString &command, const wxString &params)
static PluginManager & Get()
eCommandType
EffectDistortion::Params params
Definition: Distortion.cpp:95
MacroCommandsCatalog(const AudacityProject *project)
static void MigrateLegacyChains()
bool DoAudacityCommand(const PluginID &ID, const CommandContext &, int flags)
Definition: Menus.cpp:4466
const wxString & GetFileName()
Definition: Project.h:302
const PluginDescriptor * GetPlugin(const PluginID &ID)
void Split(const wxString &str, wxString &command, wxString &param)
static wxString GetCurrentParamsFor(const wxString &command)
void SetBatchProcessing(const PluginID &ID, bool start)
static wxString LegacyChainDir()
Definition: FileNames.cpp:193
bool IsFixed(const wxString &name)
void SelectAllIfNone()
Definition: Menus.cpp:2340
static int MacroReentryCount
bool ApplyEffectCommand(const PluginID &ID, const wxString &friendlyCommand, const wxString &command, const wxString &params, const CommandContext &Context)