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