Audacity  2.2.2
ExportMultiple.cpp
Go to the documentation of this file.
1 /**********************************************************************
2 
3  Audacity: A Digital Audio Editor
4 
5  ExportMultiple.cpp
6 
7  Dominic Mazzoni
8 
9 *******************************************************************//********************************************************************/
17 
18 #include "../Audacity.h"
19 #include "ExportMultiple.h"
20 
21 #include <wx/defs.h>
22 #include <wx/button.h>
23 #include <wx/checkbox.h>
24 #include <wx/choice.h>
25 #include <wx/dialog.h>
26 #include <wx/dirdlg.h>
27 #include <wx/dynarray.h>
28 #include <wx/event.h>
29 #include <wx/filedlg.h>
30 #include <wx/filefn.h>
31 #include <wx/filename.h>
32 #include <wx/intl.h>
33 #include <wx/radiobut.h>
34 #include <wx/sizer.h>
35 #include <wx/statbox.h>
36 #include <wx/stattext.h>
37 #include <wx/textctrl.h>
38 #include <wx/textdlg.h>
39 
40 #include "Export.h"
41 
42 #include "../Internat.h"
43 #include "../FileFormats.h"
44 #include "../FileNames.h"
45 #include "../LabelTrack.h"
46 #include "../Project.h"
47 #include "../Prefs.h"
48 #include "../ShuttleGui.h"
49 #include "../Tags.h"
50 #include "../WaveTrack.h"
51 #include "../widgets/HelpSystem.h"
52 #include "../widgets/ErrorDialog.h"
53 
54 
55 /* define our dynamic array of export settings */
56 #include <wx/arrimpl.cpp> // much hackery
57 WX_DEFINE_OBJARRAY( ExportKitArray )
58 
59 enum {
60  FormatID = 10001,
74 };
75 
76 //
77 // ExportMultiple methods
78 //
79 
80 BEGIN_EVENT_TABLE(ExportMultiple, wxDialogWrapper)
81  EVT_CHOICE(FormatID, ExportMultiple::OnFormat)
82 // EVT_BUTTON(OptionsID, ExportMultiple::OnOptions)
86  EVT_BUTTON(wxID_CANCEL, ExportMultiple::OnCancel)
87  EVT_RADIOBUTTON(LabelID, ExportMultiple::OnLabel)
88  EVT_RADIOBUTTON(TrackID, ExportMultiple::OnTrack)
89  EVT_RADIOBUTTON(ByNameAndNumberID, ExportMultiple::OnByName)
90  EVT_RADIOBUTTON(ByNameID, ExportMultiple::OnByName)
91  EVT_RADIOBUTTON(ByNumberID, ExportMultiple::OnByNumber)
92  EVT_CHECKBOX(FirstID, ExportMultiple::OnFirst)
93  EVT_TEXT(FirstFileNameID, ExportMultiple::OnFirstFileName)
94  EVT_TEXT(PrefixID, ExportMultiple::OnPrefix)
96 
97 BEGIN_EVENT_TABLE(SuccessDialog, wxDialogWrapper)
98  EVT_LIST_KEY_DOWN(wxID_ANY, SuccessDialog::OnKeyDown)
99  EVT_LIST_ITEM_ACTIVATED(wxID_ANY, SuccessDialog::OnItemActivated) // happens when <enter> is pressed with list item having focus
101 
102 BEGIN_EVENT_TABLE(MouseEvtHandler, wxEvtHandler)
103  EVT_LEFT_DCLICK(MouseEvtHandler::OnMouse)
105 
107 : wxDialogWrapper(project, wxID_ANY, wxString(_("Export Multiple")))
108 , mSelectionState{ project->GetSelectionState() }
109 {
110  SetName(GetTitle());
111 
112  mProject = project;
113  mTracks = project->GetTracks();
114  // Construct an array of non-owning pointers
115  for (const auto &plugin : mExporter.GetPlugins())
116  mPlugins.push_back(plugin.get());
117 
118  this->CountTracksAndLabels();
119 
120  mBook = NULL;
121 
123 
124  // Creating some of the widgets cause events to fire
125  // and we don't want that until after we're completely
126  // created. (Observed on Windows)
127  mInitialized = false;
128  PopulateOrExchange(S);
129  mInitialized = true;
130 
131  Layout();
132  Fit();
133  SetMinSize(GetSize());
134  Center();
135 
136  EnableControls();
137 }
138 
140 {
141 }
142 
144 {
145  mLabels = NULL;
146  mNumLabels = 0;
147  mNumWaveTracks = 0;
148 
149  const Track* pTrack;
151  for (pTrack = iter.First(mTracks); pTrack != NULL; pTrack = iter.Next())
152  {
153  switch (pTrack->GetKind())
154  {
155  // Count WaveTracks, and for linked pairs, count only the second of the pair.
156  case Track::Wave:
157  {
158  auto wt = static_cast<const WaveTrack *>(pTrack);
159  if (!wt->GetMute() && !pTrack->GetLinked()) // Don't count muted tracks.
160  mNumWaveTracks++;
161  break;
162  }
163 
164  // Only support one label track???
165  case Track::Label:
166  {
167  // Supports only one LabelTrack.
168  if (mLabels == NULL) {
169  mLabels = (LabelTrack*)pTrack;
171  }
172  break;
173  }
174  }
175  }
176 }
177 
179 {
180  // Cannot export if all audio tracks are muted.
181  if (mNumWaveTracks == 0)
182  {
183  ::AudacityMessageBox(_("All audio is muted."),
184  _("Cannot Export Multiple"),
185  wxOK | wxCENTRE, this);
186  return wxID_CANCEL;
187  }
188 
189  if ((mNumWaveTracks == 1) && (mNumLabels < 1))
190  {
192 "You have only one unmuted Audio Track and no applicable \
193 \nlabels, so you cannot export to separate audio files."),
194  _("Cannot Export Multiple"),
195  wxOK | wxCENTRE, this);
196  return wxID_CANCEL;
197  }
198 
199  if (mNumLabels < 1) {
200  mLabel->Enable(false);
201  mTrack->SetValue(true);
202  mLabel->SetValue(false);
203  }
204 
205  if (mNumWaveTracks < 2) {
206  mTrack->Enable(false);
207  mLabel->SetValue(true);
208  mTrack->SetValue(false);
209  }
210 
211  EnableControls();
212 
213  return wxDialogWrapper::ShowModal();
214 }
215 
217 {
218  wxString name = mProject->GetName();
219  wxString defaultFormat = gPrefs->Read(wxT("/Export/Format"), wxT("WAV"));
220 
221  wxArrayString formats;
222  mPluginIndex = -1;
223  mFilterIndex = 0;
224 
225  {
226  int i = -1;
227  for (const auto &pPlugin : mPlugins)
228  {
229  ++i;
230  for (int j = 0; j < pPlugin->GetFormatCount(); j++)
231  {
232  formats.Add(mPlugins[i]->GetDescription(j));
233  if (mPlugins[i]->GetFormat(j) == defaultFormat) {
234  mPluginIndex = i;
235  mSubFormatIndex = j;
236  }
237  if (mPluginIndex == -1) mFilterIndex++;
238  }
239  }
240  }
241 
242 
243  // Bug 1304: Set the default file path. It's used if none stored in config.
244  auto filename = FileNames::DefaultToDocumentsFolder(wxT("/Export/Path"));
245  wxString DefaultPath = filename.GetPath();
246 
247  if (mPluginIndex == -1)
248  {
249  mPluginIndex = 0;
250  mFilterIndex = 0;
251  mSubFormatIndex = 0;
252  }
253 
254  S.SetBorder(5);
255  S.StartHorizontalLay(wxEXPAND, true);
256  {
257  S.SetBorder(5);
258  S.StartStatic(_("Export files to:"), true);
259  {
260  S.StartMultiColumn(4, true);
261  {
262  mDir = S.Id(DirID)
263  .TieTextBox(_("Folder:"),
264  wxT("/Export/MultiplePath"),
265  DefaultPath,
266  64);
267  S.Id(ChooseID).AddButton(_("Choose..."));
268  S.Id(CreateID).AddButton(_("Create"));
269 
270  mFormat = S.Id(FormatID)
271  .TieChoice(_("Format:"),
272  wxT("/Export/MultipleFormat"),
273  formats[mFilterIndex],
274  formats,
275  formats);
276  S.AddVariableText( {}, false);
277  S.AddVariableText( {}, false);
278 
279  S.AddPrompt(_("Options:"));
280  if (!mBook)
281  {
282  mBook = safenew wxSimplebook(S.GetParent(), OptionsID, wxDefaultPosition, wxDefaultSize, wxBORDER_STATIC);
283  for (const auto &pPlugin : mPlugins)
284  {
285  for (int j = 0; j < pPlugin->GetFormatCount(); j++)
286  {
287  mBook->AddPage(pPlugin->OptionsCreate(mBook, j), wxEmptyString);
288  }
289  }
290  mBook->ChangeSelection(mFormat->GetSelection());
291  }
292  S.AddWindow(mBook);
293  S.AddVariableText( {}, false);
294  S.AddVariableText( {}, false);
295  }
296  S.EndMultiColumn();
297  }
298  S.EndStatic();
299  }
300  S.EndHorizontalLay();
301 
302  S.StartHorizontalLay(wxEXPAND, false);
303  {
304  S.SetBorder(5);
305  S.StartStatic(_("Split files based on:"), 1);
306  {
307  // Row 1
308  S.SetBorder(1);
309  mTrack = S.Id(TrackID)
310  .AddRadioButton(_("Tracks"));
311  mTrack->SetName(_("Tracks"));
312 
313  // Row 2
314  S.SetBorder(1);
315  mLabel = S.Id(LabelID).AddRadioButtonToGroup(_("Labels"));
316  mLabel->SetName(_("Labels"));
317  S.SetBorder(3);
318 
319  S.StartMultiColumn(2, wxEXPAND);
320  S.SetStretchyCol(1);
321  {
322  // Row 3 (indented)
323  S.AddVariableText(wxT(" "), false);
324  mFirst = S.Id(FirstID)
325  .AddCheckBox(_("Include audio before first label"), wxT("false"));
326 
327  // Row 4
328  S.AddVariableText( {}, false);
329  S.StartMultiColumn(2, wxEXPAND);
330  S.SetStretchyCol(1);
331  {
332  mFirstFileLabel = S.AddVariableText(_("First file name:"), false);
334  .Prop(1).TieTextBox( {},
335  name,
336  30);
337  mFirstFileName->SetName(_("First file name"));
338  }
339  S.EndMultiColumn();
340  }
341  S.EndMultiColumn();
342 
343  S.SetBorder(3);
344  }
345  S.EndStatic();
346 
347  S.SetBorder(5);
348  S.StartStatic(_("Name files:"), 1);
349  {
350  S.SetBorder(2);
351  S.StartRadioButtonGroup(wxT("/Export/TrackNameWithOrWithoutNumbers"), wxT("labelTrack"));
352  {
353  mByName = S.Id(ByNameID)
354  .TieRadioButton(_("Using Label/Track Name"), wxT("labelTrack"));
355 
357  .TieRadioButton(_("Numbering before Label/Track Name"), wxT("numberBefore"));
358 
359  mByNumber = S.Id(ByNumberID)
360  .TieRadioButton(_("Numbering after File name prefix"), wxT("numberAfter"));
361  }
363 
364  S.StartMultiColumn(3, wxEXPAND);
365  S.SetStretchyCol(2);
366  {
367  // Row 3 (indented)
368  S.AddVariableText(wxT(" "), false);
369  mPrefixLabel = S.AddVariableText(_("File name prefix:"), false);
370  mPrefix = S.Id(PrefixID)
371  .TieTextBox( {},
372  name,
373  30);
374  mPrefix->SetName(_("File name prefix"));
375  }
376  S.EndMultiColumn();
377  }
378  S.EndStatic();
379  }
380  S.EndHorizontalLay();
381 
382  S.SetBorder(5);
383  S.StartHorizontalLay(wxEXPAND, false);
384  {
385  mOverwrite = S.Id(OverwriteID).TieCheckBox(_("Overwrite existing files"),
386  wxT("/Export/OverwriteExisting"),
387  false);
388  }
389  S.EndHorizontalLay();
390 
391  S.AddStandardButtons();
392  mExport = (wxButton *)wxWindow::FindWindowById(wxID_OK, this);
393  mExport->SetLabel(_("Export"));
394 
395 }
396 
398 {
399  bool enable;
400 
401  if (!mInitialized) {
402  return;
403  }
404 
405  mFirst->Enable(mLabel->GetValue());
406 
407  enable = mLabel->GetValue() &&
408  (mByName->GetValue() || mByNumberAndName->GetValue()) &&
409  mFirst->GetValue();
410  mFirstFileLabel->Enable(enable);
411  mFirstFileName->Enable(enable);
412 
413  enable = mByNumber->GetValue();
414  mPrefixLabel->Enable(enable);
415  mPrefix->Enable(enable);
416 
417  bool ok = true;
418 
419  if (mLabel->GetValue() && mFirst->GetValue() &&
420  mFirstFileName->GetValue() == wxT("") &&
421  mPrefix->GetValue() == wxT(""))
422  ok = false;
423 
424  if (mByNumber->GetValue() &&
425  mPrefix->GetValue() == wxT(""))
426  ok = false;
427 
428  mExport->Enable(ok);
429 }
430 
431 void ExportMultiple::OnFormat(wxCommandEvent& WXUNUSED(event))
432 {
433  mBook->ChangeSelection(mFormat->GetSelection());
434 
435  EnableControls();
436 }
437 
438 void ExportMultiple::OnOptions(wxCommandEvent& WXUNUSED(event))
439 {
440  const int sel = mFormat->GetSelection();
441  if (sel != wxNOT_FOUND)
442  {
443  size_t c = 0;
444  int i = -1;
445  for (const auto &pPlugin : mPlugins)
446  {
447  ++i;
448  for (int j = 0; j < pPlugin->GetFormatCount(); j++)
449  {
450  if ((size_t)sel == c)
451  {
452  mPluginIndex = i;
453  mSubFormatIndex = j;
454  }
455  c++;
456  }
457  }
458  }
459  mPlugins[mPluginIndex]->DisplayOptions(this,mSubFormatIndex);
460 }
461 
462 void ExportMultiple::OnCreate(wxCommandEvent& WXUNUSED(event))
463 {
464  wxFileName fn;
465 
466  fn.AssignDir(mDir->GetValue());
467 
468  bool ok = fn.Mkdir(0777, wxPATH_MKDIR_FULL);
469 
470  if (!ok) {
471  // Mkdir will produce an error dialog
472  return;
473  }
474 
475  ::AudacityMessageBox(wxString::Format(_("\"%s\" successfully created."),
476  fn.GetPath()),
477  _("Export Multiple"),
478  wxOK | wxCENTRE, this);
479 }
480 
481 void ExportMultiple::OnChoose(wxCommandEvent& WXUNUSED(event))
482 {
483  wxDirDialogWrapper dlog(this,
484  _("Choose a location to save the exported files"),
485  mDir->GetValue());
486  dlog.ShowModal();
487  if (dlog.GetPath() != wxT(""))
488  mDir->SetValue(dlog.GetPath());
489 }
490 
491 void ExportMultiple::OnLabel(wxCommandEvent& WXUNUSED(event))
492 {
493  EnableControls();
494 }
495 
496 void ExportMultiple::OnFirst(wxCommandEvent& WXUNUSED(event))
497 {
498  EnableControls();
499 }
500 
501 void ExportMultiple::OnFirstFileName(wxCommandEvent& WXUNUSED(event))
502 {
503  EnableControls();
504 }
505 
506 void ExportMultiple::OnTrack(wxCommandEvent& WXUNUSED(event))
507 {
508  EnableControls();
509 }
510 
511 void ExportMultiple::OnByName(wxCommandEvent& WXUNUSED(event))
512 {
513  EnableControls();
514 }
515 
516 void ExportMultiple::OnByNumber(wxCommandEvent& WXUNUSED(event))
517 {
518  EnableControls();
519 }
520 
521 void ExportMultiple::OnPrefix(wxCommandEvent& WXUNUSED(event))
522 {
523  EnableControls();
524 }
525 
526 void ExportMultiple::OnCancel(wxCommandEvent& WXUNUSED(event))
527 {
528  EndModal(0);
529 }
530 
531 void ExportMultiple::OnExport(wxCommandEvent& WXUNUSED(event))
532 {
533  ShuttleGui S(this, eIsSavingToPrefs);
535 
536  gPrefs->Flush();
537 
538  // Make sure the output directory is in good shape
539  if (!DirOk()) {
540  return;
541  }
542 
543  mFilterIndex = mFormat->GetSelection();
544  if (mFilterIndex != wxNOT_FOUND)
545  {
546  size_t c = 0;
547  int i = -1;
548  for (const auto &pPlugin : mPlugins)
549  {
550  ++i;
551  for (int j = 0; j < pPlugin->GetFormatCount(); j++, c++)
552  {
553  if ((size_t)mFilterIndex == c)
554  { // this is the selected format. Store the plug-in and sub-format
555  // needed to acheive it.
556  mPluginIndex = i;
557  mSubFormatIndex = j;
558  mBook->GetPage(mFilterIndex)->TransferDataFromWindow();
559  }
560  }
561  }
562  }
563 
564 // bool overwrite = mOverwrite->GetValue();
566  mExported.Empty();
567 
568  // Give 'em the result
569  auto cleanup = finally( [&]
570  {
571  wxString msg;
572  msg.Printf(
573  ok == ProgressResult::Success ? _("Successfully exported the following %lld file(s).")
574  : (ok == ProgressResult::Failed ? _("Something went wrong after exporting the following %lld file(s).")
575  : (ok == ProgressResult::Cancelled ? _("Export canceled after exporting the following %lld file(s).")
576  : (ok == ProgressResult::Stopped ? _("Export stopped after exporting the following %lld file(s).")
577  : _("Something went really wrong after exporting the following %lld file(s).")
578  )
579  )
580  ), (long long) mExported.GetCount());
581 
582  wxString FileList;
583  for (size_t i = 0; i < mExported.GetCount(); i++) {
584  FileList += mExported[i];
585  FileList += '\n';
586  }
587 
588  // TODO: give some warning dialog first, when only some files exported
589  // successfully.
590 
591  GuardedCall( [&] {
592  // This results dialog is a child of this dialog.
594  _("Export Multiple"),
595  msg,
596  FileList,
597  450,400);
598  } );
599  } );
600 
601  if (mLabel->GetValue()) {
602  ok = ExportMultipleByLabel(mByName->GetValue() || mByNumberAndName->GetValue(),
603  mPrefix->GetValue(),
604  mByNumberAndName->GetValue());
605  }
606  else {
607  ok = ExportMultipleByTrack(mByName->GetValue() || mByNumberAndName->GetValue(),
608  mPrefix->GetValue(),
609  mByNumberAndName->GetValue());
610  }
611 
613  EndModal(1);
614  }
615 }
616 
618 {
619  wxFileName fn;
620 
621  fn.AssignDir(mDir->GetValue());
622 
623  if (fn.DirExists()) {
624  return true;
625  }
626 
627  wxString prompt;
628 
629  prompt.Printf(_("\"%s\" doesn't exist.\n\nWould you like to create it?"),
630  fn.GetFullPath());
631 
632  int action = AudacityMessageBox(prompt,
633  wxT("Warning"),
634  wxYES_NO | wxICON_EXCLAMATION);
635  if (action != wxYES) {
636  return false;
637  }
638 
639  return fn.Mkdir(0777, wxPATH_MKDIR_FULL);
640 }
641 
642 // TODO: JKC July2016: Merge labels/tracks duplicated export code.
644  const wxString &prefix, bool addNumber)
645 {
646  wxASSERT(mProject);
647  bool tagsPrompt = mProject->GetShowId3Dialog();
648  int numFiles = mNumLabels;
649  int l = 0; // counter for files done
650  ExportKitArray exportSettings; // dynamic array for settings.
651  exportSettings.Alloc(numFiles); // Allocate some guessed space to use.
652 
653  // Account for exporting before first label
654  if( mFirst->GetValue() ) {
655  l--;
656  numFiles++;
657  }
658 
659  // Figure out how many channels we should export.
660  auto channels = mTracks->GetNumExportChannels(false);
661 
662  wxArrayString otherNames; // keep track of file names we will use, so we
663  // don't duplicate them
664  ExportKit setting; // the current batch of settings
665  setting.destfile.SetPath(mDir->GetValue());
666  setting.destfile.SetExt(mPlugins[mPluginIndex]->GetExtension(mSubFormatIndex));
667  wxLogDebug(wxT("Plug-in index = %d, Sub-format = %d"), mPluginIndex, mSubFormatIndex);
668  wxLogDebug(wxT("File extension is %s"), setting.destfile.GetExt());
669  wxString name; // used to hold file name whilst we mess with it
670  wxString title; // un-messed-with title of file for tagging with
671 
672  const LabelStruct *info = NULL;
673  /* Examine all labels a first time, sort out all data but don't do any
674  * exporting yet (so this run is quick but interactive) */
675  while( l < mNumLabels ) {
676 
677  // Get file name and starting time
678  if( l < 0 ) {
679  // create wxFileName for output file
680  name = (mFirstFileName->GetValue());
681  setting.t0 = 0.0;
682  } else {
683  info = mLabels->GetLabel(l);
684  name = (info->title);
685  setting.t0 = info->selectedRegion.t0();
686  }
687 
688  // Figure out the ending time
689  if( info && !info->selectedRegion.isPoint() ) {
690  setting.t1 = info->selectedRegion.t1();
691  } else if( l < mNumLabels-1 ) {
692  // Use start of next label as end
693  const LabelStruct *info1 = mLabels->GetLabel(l+1);
694  setting.t1 = info1->selectedRegion.t0();
695  } else {
696  setting.t1 = mTracks->GetEndTime();
697  }
698 
699  if( name.IsEmpty() )
700  name = _("untitled");
701 
702  // store title of label to use in tags
703  title = name;
704 
705  // Numbering files...
706  if( !byName ) {
707  name.Printf(wxT("%s-%02d"), prefix, l+1);
708  } else if( addNumber ) {
709  // Following discussion with GA, always have 2 digits
710  // for easy file-name sorting (on Windows)
711  name.Prepend(wxString::Format(wxT("%02d-"), l+1));
712  }
713 
714  // store sanitised and user checked name in object
715  setting.destfile.SetName(MakeFileName(name));
716  if( setting.destfile.GetName().IsEmpty() )
717  { // user cancelled dialogue, or deleted everything in field.
718  // or maybe the label was empty??
719  // So we ignore this one and keep going.
720  }
721  else
722  {
723  // FIXME: TRAP_ERR User could have given an illegal filename prefix.
724  // in that case we should tell them, not fail silently.
725  wxASSERT(setting.destfile.IsOk()); // burp if file name is broke
726 
727  // Make sure the (final) file name is unique within the set of exports
728  FileNames::MakeNameUnique(otherNames, setting.destfile);
729 
730  /* do the metadata for this file */
731  // copy project metadata to start with
732  setting.filetags = *(mProject->GetTags());
733  // over-ride with values
734  setting.filetags.SetTag(TAG_TITLE, title);
735  setting.filetags.SetTag(TAG_TRACK, l+1);
736  // let the user have a crack at editing it, exit if cancelled
737  if( !setting.filetags.ShowEditDialog(mProject, _("Edit Metadata Tags"), tagsPrompt) )
739  }
740 
741  /* add the settings to the array of settings to be used for export */
742  exportSettings.Add(setting);
743 
744  l++; // next label, count up one
745  }
746 
747  auto ok = ProgressResult::Success; // did it work?
748  int count = 0; // count the number of sucessful runs
749  ExportKit activeSetting; // pointer to the settings in use for this export
750  /* Go round again and do the exporting (so this run is slow but
751  * non-interactive) */
752  std::unique_ptr<ProgressDialog> pDialog;
753  for (count = 0; count < numFiles; count++) {
754  /* get the settings to use for the export from the array */
755  activeSetting = exportSettings[count];
756  // Bug 1440 fix.
757  if( activeSetting.destfile.GetName().IsEmpty() )
758  continue;
759 
760  // Export it
761  ok = DoExport(pDialog, channels, activeSetting.destfile, false,
762  activeSetting.t0, activeSetting.t1, activeSetting.filetags);
764  break;
765  }
766  }
767 
768  return ok;
769 }
770 
772  const wxString &prefix, bool addNumber)
773 {
774  wxASSERT(mProject);
775  bool tagsPrompt = mProject->GetShowId3Dialog();
776  Track *tr, *tr2;
777  int l = 0; // track counter
778  auto ok = ProgressResult::Success;
779  wxArrayString otherNames;
780  ExportKitArray exportSettings; // dynamic array we will use to store the
781  // settings needed to do the exports with in
782  exportSettings.Alloc(mNumWaveTracks); // Allocate some guessed space to use.
783  ExportKit setting; // the current batch of settings
784  setting.destfile.SetPath(mDir->GetValue());
785  setting.destfile.SetExt(mPlugins[mPluginIndex]->GetExtension(mSubFormatIndex));
786 
787  wxString name; // used to hold file name whilst we mess with it
788  wxString title; // un-messed-with title of file for tagging with
789 
790  /* Remember which tracks were selected, and set them to unselected */
792  TrackListIterator iter;
793  for (tr = iter.First(mTracks); tr != NULL; tr = iter.Next()) {
794  if (tr->GetKind() != Track::Wave) {
795  continue;
796  }
797 
798  if (tr->GetSelected())
799  tr->SetSelected(false);
800  }
801 
802  /* Examine all tracks in turn, collecting export information */
803  for (tr = iter.First(mTracks); tr != NULL; tr = iter.Next()) {
804 
805  // Want only non-muted wave tracks.
806  auto wt = static_cast<const WaveTrack *>(tr);
807  if ((tr->GetKind() != Track::Wave) ||
808  wt->GetMute())
809  continue;
810 
811  // Get the times for the track
812  setting.t0 = tr->GetStartTime();
813  setting.t1 = tr->GetEndTime();
814 
815  // Check for a linked track
816  tr2 = NULL;
817  if (tr->GetLinked()) {
818  tr2 = iter.Next();
819  if (tr2) {
820 
821  // Make sure it gets included
822  if (tr2->GetStartTime() < setting.t0) {
823  setting.t0 = tr2->GetStartTime();
824  }
825 
826  if (tr2->GetEndTime() > setting.t1) {
827  setting.t1 = tr2->GetEndTime();
828  }
829  }
830  }
831 
832  // number of export channels?
833  // Needs to be per track.
834  if (tr2 == NULL && tr->GetChannel() == WaveTrack::MonoChannel &&
835  ((WaveTrack *)tr)->GetPan() == 0.0)
836  setting.channels = 1;
837  else
838  setting.channels = 2;
839 
840  // Get name and title
841  title = tr->GetName();
842  if( title.IsEmpty() )
843  title = _("untitled");
844 
845  if (byName) {
846  name = title;
847  if (addNumber) {
848  name.Prepend(
849  wxString::Format(wxT("%02d-"), l+1));
850  }
851  }
852  else {
853  name = (wxString::Format(wxT("%s-%02d"), prefix, l+1));
854  }
855 
856  // store sanitised and user checked name in object
857  setting.destfile.SetName(MakeFileName(name));
858 
859  if (setting.destfile.GetName().IsEmpty())
860  { // user cancelled dialogue, or deleted everything in field.
861  // So we ignore this one and keep going.
862  }
863  else
864  {
865 
866  // FIXME: TRAP_ERR User could have given an illegal track name.
867  // in that case we should tell them, not fail silently.
868  wxASSERT(setting.destfile.IsOk()); // burp if file name is broke
869 
870  // Make sure the (final) file name is unique within the set of exports
871  FileNames::MakeNameUnique(otherNames, setting.destfile);
872 
873  /* do the metadata for this file */
874  // copy project metadata to start with
875  setting.filetags = *(mProject->GetTags());
876  // over-ride with values
877  setting.filetags.SetTag(TAG_TITLE, title);
878  setting.filetags.SetTag(TAG_TRACK, l+1);
879  // let the user have a crack at editing it, exit if cancelled
880  if (!setting.filetags.ShowEditDialog(mProject,_("Edit Metadata Tags"), tagsPrompt))
882  }
883  /* add the settings to the array of settings to be used for export */
884  exportSettings.Add(setting);
885 
886  l++; // next track, count up one
887  }
888  // end of user-interactive data gathering loop, start of export processing
889  // loop
890  int count = 0; // count the number of sucessful runs
891  ExportKit activeSetting; // pointer to the settings in use for this export
892  std::unique_ptr<ProgressDialog> pDialog;
893  for (tr = iter.First(mTracks); tr != NULL; tr = iter.Next()) {
894 
895  // Want only non-muted wave tracks.
896  auto wt = static_cast<const WaveTrack *>(tr);
897  if ((tr->GetKind() != Track::Wave) || (wt->GetMute())) {
898  continue;
899  }
900 
901  // Bug 1510 possibly increment iter, before deciding whether to export.
902  // Check for a linked track
903  tr2 = NULL;
904  if (tr->GetLinked()) {
905  tr2 = iter.Next();
906  }
907 
908  wxLogDebug( "Get setting %i", count );
909  /* get the settings to use for the export from the array */
910  activeSetting = exportSettings[count];
911  if( activeSetting.destfile.GetName().IsEmpty() ){
912  count++;
913  continue;
914  }
915 
916  /* Select the track */
918  tr->SetSelected(true);
919  if (tr2)
920  // Select it also
921  tr2->SetSelected(true);
922 
923  // Export the data. "channels" are per track.
924  ok = DoExport(pDialog,
925  activeSetting.channels, activeSetting.destfile, true,
926  activeSetting.t0, activeSetting.t1, activeSetting.filetags);
927 
928  // Stop if an error occurred
930  break;
931  }
932  // increment export counter
933  count++;
934 
935  }
936 
937  return ok ;
938 }
939 
940 ProgressResult ExportMultiple::DoExport(std::unique_ptr<ProgressDialog> &pDialog,
941  unsigned channels,
942  const wxFileName &inName,
943  bool selectedOnly,
944  double t0,
945  double t1,
946  const Tags &tags)
947 {
948  wxFileName name;
949 
950  wxLogDebug(wxT("Doing multiple Export: File name \"%s\""), (inName.GetFullName()));
951  wxLogDebug(wxT("Channels: %i, Start: %lf, End: %lf "), channels, t0, t1);
952  if (selectedOnly)
953  wxLogDebug(wxT("Selected Region Only"));
954  else
955  wxLogDebug(wxT("Whole Project"));
956 
957  wxFileName backup;
958  if (mOverwrite->GetValue()) {
959  // Make sure we don't overwrite (corrupt) alias files
960  if (!mProject->GetDirManager()->EnsureSafeFilename(inName)) {
962  }
963  name = inName;
964  backup.Assign(name);
965 
966  int suffix = 0;
967  do {
968  backup.SetName(name.GetName() +
969  wxString::Format(wxT("%d"), suffix));
970  ++suffix;
971  }
972  while (backup.FileExists());
973  ::wxRenameFile(inName.GetFullPath(), backup.GetFullPath());
974  }
975  else {
976  name = inName;
977  int i = 2;
978  wxString base(name.GetName());
979  while (name.FileExists()) {
980  name.SetName(wxString::Format(wxT("%s-%d"), base, i++));
981  }
982  }
983 
985  const wxString fullPath{name.GetFullPath()};
986 
987  auto cleanup = finally( [&] {
988  bool ok =
989  success == ProgressResult::Stopped ||
990  success == ProgressResult::Success;
991  if (backup.IsOk()) {
992  if ( ok )
993  // Remove backup
994  ::wxRemoveFile(backup.GetFullPath());
995  else {
996  // Restore original
997  ::wxRemoveFile(fullPath);
998  ::wxRenameFile(backup.GetFullPath(), fullPath);
999  }
1000  }
1001  else {
1002  if ( ! ok )
1003  // Remove any new, and only partially written, file.
1004  ::wxRemoveFile(fullPath);
1005  }
1006  } );
1007 
1008  // Call the format export routine
1009  success = mPlugins[mPluginIndex]->Export(mProject,
1010  pDialog,
1011  channels,
1012  fullPath,
1013  selectedOnly,
1014  t0,
1015  t1,
1016  NULL,
1017  &tags,
1018  mSubFormatIndex);
1019 
1020  if (success == ProgressResult::Success || success == ProgressResult::Stopped) {
1021  mExported.Add(fullPath);
1022  }
1023 
1024  Refresh();
1025  Update();
1026 
1027  return success;
1028 }
1029 
1030 wxString ExportMultiple::MakeFileName(const wxString &input)
1031 {
1032  wxString newname = input; // name we are generating
1033 
1034  // strip out anything that isn't allowed in file names on this platform
1035  auto changed = Internat::SanitiseFilename(newname, wxT("_"));
1036 
1037  if(changed)
1038  { // need to get user to fix file name
1039  // build the dialog
1040  wxString msg;
1041  wxString excluded = ::wxJoin( Internat::GetExcludedCharacters(), wxChar(' ') );
1042  // TODO: For Russian langauge we should have separate cases for 2 and more than 2 letters.
1043  if( excluded.Length() > 1 ){
1044  // i18n-hint: The second %s gives some letters that can't be used.
1045  msg.Printf(_("Label or track \"%s\" is not a legal file name. You cannot use any of: %s\nUse..."), input,
1046  excluded);
1047  } else {
1048  // i18n-hint: The second %s gives a letter that can't be used.
1049  msg.Printf(_("Label or track \"%s\" is not a legal file name. You cannot use \"%s\".\nUse..."), input,
1050  excluded);
1051  }
1052 
1053  AudacityTextEntryDialog dlg( this, msg, _("Save As..."), newname );
1054 
1055 
1056  // And tell the validator about excluded chars
1057  dlg.SetTextValidator( wxFILTER_EXCLUDE_CHAR_LIST );
1058  wxTextValidator *tv = dlg.GetTextValidator();
1059  tv->SetExcludes(Internat::GetExcludedCharacters());
1060 
1061  // Show the dialog and bail if the user cancels
1062  if( dlg.ShowModal() == wxID_CANCEL )
1063  {
1064  return wxEmptyString;
1065  }
1066  // Extract the name from the dialog
1067  newname = dlg.GetValue();
1068  } // phew - end of file name sanitisation procedure
1069  return newname;
1070 }
1071 
1072 void SuccessDialog::OnKeyDown(wxListEvent& event)
1073 {
1074  if (event.GetKeyCode() == WXK_RETURN)
1075  EndModal(1);
1076  else
1077  event.Skip(); // allow standard behaviour
1078 }
1079 
1080 void SuccessDialog::OnItemActivated(wxListEvent& WXUNUSED(event))
1081 {
1082  EndModal(1);
1083 }
1084 
1085 void MouseEvtHandler::OnMouse(wxMouseEvent& event)
1086 {
1087  event.Skip(false);
1088 }
void OnByNumber(wxCommandEvent &event)
static void ShowInfoDialog(wxWindow *parent, const wxString &dlogTitle, const wxString &shortMsg, const wxString &message, const int xSize, const int ySize)
Displays cutable information in a text ctrl, with an OK button.
Definition: HelpSystem.cpp:54
void OnExport(const wxString &Format)
wxChoice * TieChoice(const wxString &Prompt, WrappedType &WrappedRef, const wxArrayString *pChoices)
wxRadioButton * mByNumberAndName
TrackList * mTracks
void SetTag(const wxString &name, const wxString &value)
Definition: Tags.cpp:449
LabelTrack * mLabels
ProgressResult
wxRadioButton * mTrack
double t0() const
#define TAG_TRACK
Definition: Tags.h:65
SelectionState & mSelectionState
void OnFormat(wxCommandEvent &event)
bool isPoint() const
Derived from ShuttleGuiBase, an Audacity specific class for shuttling data to and from GUI...
Definition: ShuttleGui.h:366
void EndRadioButtonGroup()
wxRadioButton * mLabel
wxTextCtrl * mPrefix
bool GetSelected() const
Definition: Track.h:217
wxWindow * AddWindow(wxWindow *pWindow, int Flags=wxALIGN_CENTRE|wxALL)
Definition: ShuttleGui.cpp:257
static wxFileNameWrapper DefaultToDocumentsFolder(const wxString &preference)
Definition: FileNames.cpp:348
const Track * First(const TrackList *val=NULL)
Definition: Track.h:402
void OnKeyDown(wxListEvent &event)
void OnOptions(wxCommandEvent &event)
virtual double GetEndTime() const =0
wxTextCtrl * mDir
double GetEndTime() const
Definition: Track.cpp:1334
AudacityProject * mProject
wxString MakeFileName(const wxString &input)
Takes an arbitrary text string and converts it to a form that can be used as a file name...
void EndMultiColumn()
wxString title
Definition: LabelTrack.h:93
WX_DEFINE_OBJARRAY(IdMapArray)
wxSimplebook * mBook
#define TAG_TITLE
Definition: Tags.h:62
virtual int GetChannel() const
Definition: Track.h:223
static const wxArrayString & GetExcludedCharacters()
Definition: Internat.h:134
bool GetLinked() const
Definition: Track.h:218
static bool SanitiseFilename(wxString &name, const wxString &sub)
Protect against Unicode to multi-byte conversion failures on Windows.
Definition: Internat.cpp:266
void OnByName(wxCommandEvent &event)
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
Main class to control the export function.
int GetNumLabels() const
Definition: ErrorDialog.h:105
virtual double GetStartTime() const =0
wxButton * mExport
void OnMouse(wxMouseEvent &event)
double t1() const
void OnCreate(wxCommandEvent &event)
void OnPrefix(wxCommandEvent &event)
void CountTracksAndLabels()
#define safenew
Definition: Audacity.h:223
virtual void SetSelected(bool s)
Definition: Track.cpp:97
virtual int GetKind() const
Definition: Track.h:267
void EndHorizontalLay()
Definition: ShuttleGui.cpp:975
A LabelTrack is a Track that holds labels (LabelStruct).
Definition: LabelTrack.h:114
const Track * Next(bool skiplinked=false)
Definition: Track.h:406
wxRadioButton * mByName
void AddPrompt(const wxString &Prompt)
Right aligned text string.
Definition: ShuttleGui.cpp:215
const std::shared_ptr< DirManager > & GetDirManager()
Definition: Project.cpp:1407
AudacityProject provides the main window, with tools and tracks contained within it.
Definition: Project.h:161
A LabelStruct holds information for ONE label in a LabelTrack.
Definition: LabelTrack.h:45
wxStaticText * mPrefixLabel
wxTextCtrl * mFirstFileName
void OnLabel(wxCommandEvent &event)
wxFileConfig * gPrefs
Definition: Prefs.cpp:72
ProgressResult ExportMultipleByTrack(bool byName, const wxString &prefix, bool addNumber)
Export each track in the project to a separate file.
wxFileName destfile
wxCheckBox * AddCheckBox(const wxString &Prompt, const wxString &Selected)
Definition: ShuttleGui.cpp:267
virtual ~ExportMultiple()
wxWindow * GetParent()
Definition: ShuttleGui.h:259
void StartHorizontalLay(int PositionFlags=wxALIGN_CENTRE, int iProp=1)
Definition: ShuttleGui.cpp:966
wxRadioButton * mByNumber
void StartMultiColumn(int nCols, int PositionFlags=wxALIGN_LEFT)
Definition: ShuttleGui.cpp:998
const LabelStruct * GetLabel(int index) const
void OnCancel(wxCommandEvent &event)
wxArrayString mExported
bool GetShowId3Dialog()
Definition: Project.h:301
A Track that contains audio waveform data.
Definition: WaveTrack.h:60
ShuttleGui & Id(int id)
wxCheckBox * mFirst
Fundamental data object of Audacity, placed in the TrackPanel. Classes derived form it include the Wa...
Definition: Track.h:67
void PopulateOrExchange(ShuttleGui &S)
unsigned GetNumExportChannels(bool selectionOnly) const
Find out how many channels this track list mixes to.
Definition: Track.cpp:1176
void OnItemActivated(wxListEvent &event)
virtual Track * First(TrackList *val=nullptr)
Definition: Track.cpp:355
EVT_LIST_ITEM_ACTIVATED(wxID_ANY, SuccessDialog::OnItemActivated) ExportMultiple
R GuardedCall(const F1 &body, const F2 &handler=F2::Default(), const F3 &delayedHandler={})
wxChoice * mFormat
void OnChoose(wxCommandEvent &event)
wxRadioButton * TieRadioButton(const wxString &Prompt, WrappedType &WrappedRef)
EVT_BUTTON(wxID_NO, DependencyDialog::OnNo) EVT_BUTTON(wxID_YES
ID3 Tags (for MP3)
Definition: Tags.h:72
An iterator for a TrackList.
Definition: Track.h:339
std::vector< ExportPlugin * > mPlugins
_("Move Track &Down")+wxT("\t")+(GetActiveProject() -> GetCommandManager() ->GetKeyFromName(wxT("TrackMoveDown"))), OnMoveTrack) POPUP_MENU_ITEM(OnMoveTopID, _("Move Track to &Top")+wxT("\t")+(GetActiveProject() ->GetCommandManager() ->GetKeyFromName(wxT("TrackMoveTop"))), OnMoveTrack) POPUP_MENU_ITEM(OnMoveBottomID, _("Move Track to &Bottom")+wxT("\t")+(GetActiveProject() ->GetCommandManager() ->GetKeyFromName(wxT("TrackMoveBottom"))), OnMoveTrack) void TrackMenuTable::OnSetName(wxCommandEvent &)
A private class used to store the information needed to do an export.
void OnFirst(wxCommandEvent &event)
wxCheckBox * TieCheckBox(const wxString &Prompt, WrappedType &WrappedRef)
const wxChar * name
Definition: Distortion.cpp:94
Presents a dialog box allowing the user to export multiple files either by exporting each track as a ...
virtual Track * Next(bool skiplinked=false)
Definition: Track.cpp:396
SelectedRegion selectedRegion
Definition: LabelTrack.h:92
wxStaticText * AddVariableText(const wxString &Str, bool bCenter=false, int PositionFlags=0)
Definition: ShuttleGui.cpp:373
wxStaticBox * StartStatic(const wxString &Str, int iProp=0)
Definition: ShuttleGui.cpp:701
wxString GetName()
Definition: Project.cpp:1443
wxStaticText * mFirstFileLabel
void OnExport(wxCommandEvent &event)
ProgressResult DoExport(std::unique_ptr< ProgressDialog > &pDialog, unsigned channels, const wxFileName &name, bool selectedOnly, double t0, double t1, const Tags &tags)
const Tags * GetTags()
Definition: Project.cpp:1438
ShuttleGui & Prop(int iProp)
Definition: ShuttleGui.h:374
wxRadioButton * AddRadioButtonToGroup(const wxString &Prompt)
Definition: ShuttleGui.cpp:443
void AddStandardButtons(long buttons=eOkButton|eCancelButton, wxButton *extra=NULL)
END_EVENT_TABLE()
unsigned channels
void SetBorder(int Border)
Definition: ShuttleGui.h:251
void OnTrack(wxCommandEvent &event)
void StartRadioButtonGroup(const wxString &SettingName)
void OnFirstFileName(wxCommandEvent &event)
wxButton * AddButton(const wxString &Text, int PositionFlags=wxALIGN_CENTRE)
Definition: ShuttleGui.cpp:301
void SetStretchyCol(int i)
Used to modify an already placed FlexGridSizer to make a column stretchy.
Definition: ShuttleGui.cpp:192
wxRadioButton * AddRadioButton(const wxString &Prompt)
Definition: ShuttleGui.cpp:427
wxTextCtrl * TieTextBox(const wxString &Prompt, WrappedType &WrappedRef, const int nChars)
bool ShowEditDialog(wxWindow *parent, const wxString &title, bool force=false)
Definition: Tags.cpp:570
static void MakeNameUnique(wxArrayString &otherNames, wxFileName &newName)
Definition: FileNames.cpp:92
wxCheckBox * mOverwrite
ProgressResult ExportMultipleByLabel(bool byName, const wxString &prefix, bool addNumber)
Export multiple labeled regions of the project to separate files.