Audacity 3.2.0
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
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/listbase.h>
29#include <wx/filefn.h>
30#include <wx/filename.h>
31#include <wx/intl.h>
32#include <wx/log.h>
33#include <wx/radiobut.h>
34#include <wx/simplebook.h>
35#include <wx/sizer.h>
36#include <wx/statbox.h>
37#include <wx/stattext.h>
38#include <wx/textctrl.h>
39#include <wx/textdlg.h>
40
41#include "FileNames.h"
42#include "LabelTrack.h"
43#include "Project.h"
44#include "ProjectSettings.h"
45#include "ProjectWindow.h"
46#include "ProjectWindows.h"
47#include "Prefs.h"
48#include "../SelectionState.h"
49#include "../ShuttleGui.h"
50#include "../TagsEditor.h"
51#include "../WaveTrack.h"
52#include "../widgets/HelpSystem.h"
53#include "../widgets/AudacityMessageBox.h"
54#include "../widgets/AudacityTextEntryDialog.h"
55#include "../widgets/ProgressDialog.h"
56
57
58namespace {
65 {
66 public:
69 double t0;
70 double t1;
71 unsigned channels;
72 }; // end of ExportKit declaration
73 /* we are going to want an set of these kits, and don't know how many until
74 * runtime. I would dearly like to use a std::vector, but it seems that
75 * this isn't done anywhere else in Audacity, presumably for a reason?, so
76 * I'm stuck with wxArrays, which are much harder, as well as non-standard.
77 */
78}
79
80/* define our dynamic array of export settings */
81
82enum {
83 FormatID = 10001,
97};
98
99//
100// ExportMultipleDialog methods
101//
102
103BEGIN_EVENT_TABLE(ExportMultipleDialog, wxDialogWrapper)
105// EVT_BUTTON(OptionsID, ExportMultipleDialog::OnOptions)
111 EVT_RADIOBUTTON(LabelID, ExportMultipleDialog::OnLabel)
112 EVT_RADIOBUTTON(TrackID, ExportMultipleDialog::OnTrack)
120
121BEGIN_EVENT_TABLE(SuccessDialog, wxDialogWrapper)
122 EVT_LIST_KEY_DOWN(wxID_ANY, SuccessDialog::OnKeyDown)
123 EVT_LIST_ITEM_ACTIVATED(wxID_ANY, SuccessDialog::OnItemActivated) // happens when <enter> is pressed with list item having focus
125
126BEGIN_EVENT_TABLE(MouseEvtHandler, wxEvtHandler)
127 EVT_LEFT_DCLICK(MouseEvtHandler::OnMouse)
129
131: wxDialogWrapper( &GetProjectFrame( *project ),
132 wxID_ANY, XO("Export Multiple") )
133, mExporter{ *project }
134, mSelectionState{ SelectionState::Get( *project ) }
135{
136 SetName();
137
138 mProject = project;
139 mTracks = &TrackList::Get( *project );
140 // Construct an array of non-owning pointers
141 for (const auto &plugin : mExporter.GetPlugins())
142 mPlugins.push_back(plugin.get());
143
144 this->CountTracksAndLabels();
145
146 mBook = NULL;
147
149
150 // Creating some of the widgets cause events to fire
151 // and we don't want that until after we're completely
152 // created. (Observed on Windows)
153 mInitialized = false;
154 PopulateOrExchange(S);
155 mInitialized = true;
156
157 Layout();
158 Fit();
159 SetMinSize(GetSize());
160 Center();
161
162 EnableControls();
163}
164
166{
167}
168
170{
171 bool anySolo = !(( mTracks->Any<const WaveTrack>() + &WaveTrack::GetSolo ).empty());
172
174 (mTracks->Leaders< const WaveTrack >() -
176
177 // only the first label track
178 mLabels = *mTracks->Any< const LabelTrack >().begin();
180}
181
183{
184 // Cannot export if all audio tracks are muted.
185 if (mNumWaveTracks == 0)
186 {
188 XO("All audio is muted."),
189 XO("Cannot Export Multiple"),
190 wxOK | wxCENTRE,
191 this);
192 return wxID_CANCEL;
193 }
194
195 if ((mNumWaveTracks < 1) && (mNumLabels < 1))
196 {
198 XO(
199"You have no unmuted Audio Tracks and no applicable \
200\nlabels, so you cannot export to separate audio files."),
201 XO("Cannot Export Multiple"),
202 wxOK | wxCENTRE,
203 this);
204 return wxID_CANCEL;
205 }
206
207 bool bHasLabels = (mNumLabels > 0);
208 bool bHasTracks = (mNumWaveTracks > 0);
209
210 mLabel->Enable(bHasLabels && bHasTracks);
211 mTrack->Enable(bHasTracks);
212
213 // If you have 2 or more tracks, then it is export by tracks.
214 // If you have no labels, then it is export by tracks.
215 // Otherwise it is export by labels, by default.
216 bool bPreferByLabels = bHasLabels && (mNumWaveTracks < 2);
217 mLabel->SetValue(bPreferByLabels);
218 mTrack->SetValue(!bPreferByLabels);
219
221
222 // This is a work around for issue #2909, and ensures that
223 // when the dialog opens, the first control is the focus.
224 // The work around is only needed on Windows.
225#if defined(__WXMSW__)
226 mDir->SetFocus();
227#endif
228
229 return wxDialogWrapper::ShowModal();
230}
231
233{
234 ChoiceSetting NumberingSetting{
235 wxT("/Export/TrackNameWithOrWithoutNumbers"),
236 {
237 { wxT("labelTrack"), XXO("Using Label/Track Name") },
238 { wxT("numberBefore"), XXO("Numbering before Label/Track Name") },
239 { wxT("numberAfter"), XXO("Numbering after File name prefix") },
240 },
241 0 // labelTrack
242 };
243
244 wxString name = mProject->GetProjectName();
245 wxString defaultFormat = gPrefs->Read(wxT("/Export/Format"), wxT("WAV"));
246
247 TranslatableStrings visibleFormats;
248 wxArrayStringEx formats;
249 mPluginIndex = -1;
250 mFilterIndex = 0;
251
252 {
253 int i = -1;
254 for (const auto &pPlugin : mPlugins)
255 {
256 ++i;
257 for (int j = 0; j < pPlugin->GetFormatCount(); j++)
258 {
259 auto format = mPlugins[i]->GetDescription(j);
260 visibleFormats.push_back( format );
261 // use MSGID of description as a value too, written into config file
262 // This is questionable. A change in the msgid can make the
263 // preference stored in old config files inapplicable
264 formats.push_back( format.MSGID().GET() );
265 if (mPlugins[i]->GetFormat(j) == defaultFormat) {
266 mPluginIndex = i;
267 mSubFormatIndex = j;
268 }
269 if (mPluginIndex == -1) mFilterIndex++;
270 }
271 }
272 }
273
274 ChoiceSetting FormatSetting{ wxT("/Export/MultipleFormat"),
275 {
276 ByColumns,
277 visibleFormats,
278 formats
279 },
281 };
282
283 // Bug 1304: Set the default file path. It's used if none stored in config.
284 auto DefaultPath = FileNames::FindDefaultPath(FileNames::Operation::Export);
285
286 if (mPluginIndex == -1)
287 {
288 mPluginIndex = 0;
289 mFilterIndex = 0;
290 mSubFormatIndex = 0;
291 }
292
293 S.SetBorder(5);
294 S.StartHorizontalLay(wxEXPAND, true);
295 {
296 S.SetBorder(5);
297 S.StartStatic(XO("Export files to:"), true);
298 {
299 S.StartMultiColumn(4, true);
300 {
301 mDir = S.Id(DirID)
302 .AddTextBox(XXO("Folder:"),
303 DefaultPath,
304 64);
305 S.Id(ChooseID).AddButton(XXO("Choose..."));
306 S.Id(CreateID).AddButton(XXO("Create"));
307
308 mFormat = S.Id(FormatID)
309 .TieChoice( XXO("Format:"),
310 FormatSetting
311 );
312 S.AddVariableText( {}, false);
313 S.AddVariableText( {}, false);
314
315 S.AddPrompt(XXO("Options:"));
316
317 mBook = S.Id(OptionsID)
318 .Style(wxBORDER_STATIC)
319 .StartSimplebook();
320 if (S.GetMode() == eIsCreating)
321 {
322 for (const auto &pPlugin : mPlugins)
323 {
324 for (int j = 0; j < pPlugin->GetFormatCount(); j++)
325 {
326 // Name of simple book page is not displayed
327 S.StartNotebookPage( {} );
328 pPlugin->OptionsCreate(S, j);
329 S.EndNotebookPage();
330 }
331 }
332 mBook->ChangeSelection(mFormat->GetSelection());
333 }
334 S.EndSimplebook();
335 S.AddVariableText( {}, false);
336 S.AddVariableText( {}, false);
337 }
338 S.EndMultiColumn();
339 }
340 S.EndStatic();
341 }
342 S.EndHorizontalLay();
343
344 S.StartHorizontalLay(wxEXPAND, false);
345 {
346 S.SetBorder(5);
347 S.StartStatic(XO("Split files based on:"), 1);
348 {
349 // Row 1
350 S.SetBorder(1);
351
352 // Bug 2692: Place button group in panel so tabbing will work and,
353 // on the Mac, VoiceOver will announce as radio buttons.
354 S.StartPanel();
355 {
356 mTrack = S.Id(TrackID)
357 .AddRadioButton(XXO("Tracks"));
358
359 // Row 2
360 S.SetBorder(1);
361 mLabel = S.Id(LabelID)
362 .AddRadioButtonToGroup(XXO("Labels"));
363 }
364 S.EndPanel();
365
366 S.SetBorder(3);
367 S.StartMultiColumn(2, wxEXPAND);
368 S.SetStretchyCol(1);
369 {
370 // Row 3 (indented)
371 S.AddVariableText(Verbatim(" "), false);
372 mFirst = S.Id(FirstID)
373 .AddCheckBox(XXO("Include audio before first label"), false);
374
375 // Row 4
376 S.AddVariableText( {}, false);
377 S.StartMultiColumn(2, wxEXPAND);
378 S.SetStretchyCol(1);
379 {
381 S.AddVariableText(XO("First file name:"), false);
383 .Prop(1)
384 .Name(XO("First file name"))
385 .TieTextBox( {},
386 name,
387 30);
388 }
389 S.EndMultiColumn();
390 }
391 S.EndMultiColumn();
392
393 S.SetBorder(3);
394 }
395 S.EndStatic();
396
397 S.SetBorder(5);
398 S.StartStatic(XO("Name files:"), 1);
399 {
400 S.SetBorder(2);
401
402 // Bug 2692: Place button group in panel so tabbing will work and,
403 // on the Mac, VoiceOver will announce as radio buttons.
404 S.StartPanel();
405 {
406 S.StartRadioButtonGroup(NumberingSetting);
407 {
408 mByName = S.Id(ByNameID).TieRadioButton();
409
410 mByNumberAndName = S.Id(ByNameAndNumberID).TieRadioButton();
411
412 mByNumber = S.Id(ByNumberID).TieRadioButton();
413 }
414 S.EndRadioButtonGroup();
415 }
416 S.EndPanel();
417
418 S.StartMultiColumn(3, wxEXPAND);
419 S.SetStretchyCol(2);
420 {
421 // Row 3 (indented)
422 S.AddVariableText(Verbatim(" "), false);
423 mPrefixLabel = S.AddVariableText(XO("File name prefix:"), false);
424 mPrefix = S.Id(PrefixID)
425 .Name(XO("File name prefix"))
426 .TieTextBox( {},
427 name,
428 30);
429 }
430 S.EndMultiColumn();
431 }
432 S.EndStatic();
433 }
434 S.EndHorizontalLay();
435
436 S.SetBorder(5);
437 S.StartHorizontalLay(wxEXPAND, false);
438 {
439 mOverwrite = S.Id(OverwriteID).TieCheckBox(XXO("Overwrite existing files"),
440 {wxT("/Export/OverwriteExisting"),
441 false});
442 }
443 S.EndHorizontalLay();
444
445 S.AddStandardButtons(eOkButton | eCancelButton | eHelpButton);
446 mExport = (wxButton *)wxWindow::FindWindowById(wxID_OK, this);
447 mExport->SetLabel(_("Export"));
448
449}
450
452{
453 bool enable;
454
455 if (!mInitialized) {
456 return;
457 }
458
459 mFirst->Enable(mLabel->GetValue());
460
461 enable = mLabel->GetValue() &&
462 (mByName->GetValue() || mByNumberAndName->GetValue()) &&
463 mFirst->GetValue();
464 mFirstFileLabel->Enable(enable);
465 mFirstFileName->Enable(enable);
466
467 enable = mByNumber->GetValue();
468 mPrefixLabel->Enable(enable);
469 mPrefix->Enable(enable);
470
471 bool ok = true;
472
473 if (mLabel->GetValue() && mFirst->GetValue() &&
474 mFirstFileName->GetValue().empty() &&
475 mPrefix->GetValue().empty())
476 ok = false;
477
478 if (mByNumber->GetValue() &&
479 mPrefix->GetValue().empty())
480 ok = false;
481
482 mExport->Enable(ok);
483}
484
485void ExportMultipleDialog::OnFormat(wxCommandEvent& WXUNUSED(event))
486{
487 mBook->ChangeSelection(mFormat->GetSelection());
488
490}
491
492void ExportMultipleDialog::OnOptions(wxCommandEvent& WXUNUSED(event))
493{
494 const int sel = mFormat->GetSelection();
495 if (sel != wxNOT_FOUND)
496 {
497 size_t c = 0;
498 int i = -1;
499 for (const auto &pPlugin : mPlugins)
500 {
501 ++i;
502 for (int j = 0; j < pPlugin->GetFormatCount(); j++)
503 {
504 if ((size_t)sel == c)
505 {
506 mPluginIndex = i;
507 mSubFormatIndex = j;
508 }
509 c++;
510 }
511 }
512 }
513 mPlugins[mPluginIndex]->DisplayOptions(this,mSubFormatIndex);
514}
515
516void ExportMultipleDialog::OnCreate(wxCommandEvent& WXUNUSED(event))
517{
518 wxFileName fn;
519
520 fn.AssignDir(mDir->GetValue());
521
522 bool ok = fn.Mkdir(0777, wxPATH_MKDIR_FULL);
523
524 if (!ok) {
525 // Mkdir will produce an error dialog
526 return;
527 }
528
530 XO("\"%s\" successfully created.").Format( fn.GetPath() ),
531 XO("Export Multiple"),
532 wxOK | wxCENTRE,
533 this);
534}
535
536void ExportMultipleDialog::OnChoose(wxCommandEvent& WXUNUSED(event))
537{
538 wxDirDialogWrapper dlog(this,
539 XO("Choose a location to save the exported files"),
540 mDir->GetValue());
541 dlog.ShowModal();
542 if (!dlog.GetPath().empty())
543 mDir->SetValue(dlog.GetPath());
544}
545
546void ExportMultipleDialog::OnLabel(wxCommandEvent& WXUNUSED(event))
547{
549}
550
551void ExportMultipleDialog::OnFirst(wxCommandEvent& WXUNUSED(event))
552{
554}
555
556void ExportMultipleDialog::OnFirstFileName(wxCommandEvent& WXUNUSED(event))
557{
559}
560
561void ExportMultipleDialog::OnTrack(wxCommandEvent& WXUNUSED(event))
562{
564}
565
566void ExportMultipleDialog::OnByName(wxCommandEvent& WXUNUSED(event))
567{
569}
570
571void ExportMultipleDialog::OnByNumber(wxCommandEvent& WXUNUSED(event))
572{
574}
575
576void ExportMultipleDialog::OnPrefix(wxCommandEvent& WXUNUSED(event))
577{
579}
580
581void ExportMultipleDialog::OnCancel(wxCommandEvent& WXUNUSED(event))
582{
583 EndModal(0);
584}
585
586void ExportMultipleDialog::OnHelp(wxCommandEvent& WXUNUSED(event))
587{
588 HelpSystem::ShowHelp(this, L"Export_Multiple", true);
589}
590
591void ExportMultipleDialog::OnExport(wxCommandEvent& WXUNUSED(event))
592{
595
596 gPrefs->Flush();
597
598 FileNames::UpdateDefaultPath(FileNames::Operation::Export, mDir->GetValue());
599
600 // Make sure the output directory is in good shape
601 if (!DirOk()) {
602 return;
603 }
604
605 mFilterIndex = mFormat->GetSelection();
606 if (mFilterIndex != wxNOT_FOUND)
607 {
608 size_t c = 0;
609 int i = -1;
610 for (const auto &pPlugin : mPlugins)
611 {
612 ++i;
613 for (int j = 0; j < pPlugin->GetFormatCount(); j++, c++)
614 {
615 if ((size_t)mFilterIndex == c)
616 { // this is the selected format. Store the plug-in and sub-format
617 // needed to achieve it.
618 mPluginIndex = i;
619 mSubFormatIndex = j;
620 mBook->GetPage(mFilterIndex)->TransferDataFromWindow();
621 }
622 }
623 }
624 }
625
626// bool overwrite = mOverwrite->GetValue();
627 ProgressResult ok = ProgressResult::Failed;
628 mExported.clear();
629
630 // Give 'em the result
631 auto cleanup = finally( [&]
632 {
633 auto msg = (ok == ProgressResult::Success
634 ? XO("Successfully exported the following %lld file(s).")
635 : ok == ProgressResult::Failed
636 ? XO("Something went wrong after exporting the following %lld file(s).")
638 ? XO("Export canceled after exporting the following %lld file(s).")
639 : ok == ProgressResult::Stopped
640 ? XO("Export stopped after exporting the following %lld file(s).")
641 : XO("Something went really wrong after exporting the following %lld file(s).")
642 ).Format((long long) mExported.size());
643
644 wxString FileList;
645 for (size_t i = 0; i < mExported.size(); i++) {
646 FileList += mExported[i];
647 FileList += '\n';
648 }
649
650 // TODO: give some warning dialog first, when only some files exported
651 // successfully.
652
653 GuardedCall( [&] {
654 // This results dialog is a child of this dialog.
656 XO("Export Multiple"),
657 msg,
658 FileList,
659 450,400);
660 } );
661 } );
662
663 if (mLabel->GetValue()) {
664 ok = ExportMultipleByLabel(mByName->GetValue() || mByNumberAndName->GetValue(),
665 mPrefix->GetValue(),
666 mByNumberAndName->GetValue());
667 }
668 else {
669 ok = ExportMultipleByTrack(mByName->GetValue() || mByNumberAndName->GetValue(),
670 mPrefix->GetValue(),
671 mByNumberAndName->GetValue());
672 }
673
674 if (ok == ProgressResult::Success || ok == ProgressResult::Stopped) {
675 EndModal(1);
676 }
677}
678
680{
681 wxFileName fn;
682
683 fn.AssignDir(mDir->GetValue());
684
685 if (fn.DirExists()) {
686 return true;
687 }
688
689 auto prompt = XO("\"%s\" doesn't exist.\n\nWould you like to create it?")
690 .Format( fn.GetFullPath() );
691
692 int action = AudacityMessageBox(
693 prompt,
694 XO("Warning"),
695 wxYES_NO | wxICON_EXCLAMATION);
696 if (action != wxYES) {
697 return false;
698 }
699
700 return fn.Mkdir(0777, wxPATH_MKDIR_FULL);
701}
702
703static unsigned GetNumExportChannels( const TrackList &tracks )
704{
705 /* counters for tracks panned different places */
706 int numLeft = 0;
707 int numRight = 0;
708 //int numMono = 0;
709 /* track iteration kit */
710
711 bool anySolo = !(( tracks.Any<const WaveTrack>() + &WaveTrack::GetSolo ).empty());
712
713 // Want only unmuted wave tracks.
714 for (auto tr :
715 tracks.Any< const WaveTrack >() -
717 ) {
718 // Found a left channel
719 if (tr->GetChannel() == Track::LeftChannel) {
720 numLeft++;
721 }
722
723 // Found a right channel
724 else if (tr->GetChannel() == Track::RightChannel) {
725 numRight++;
726 }
727
728 // Found a mono channel, but it may be panned
729 else if (tr->GetChannel() == Track::MonoChannel) {
730 float pan = tr->GetPan();
731
732 // Figure out what kind of channel it should be
733 if (pan == -1.0) { // panned hard left
734 numLeft++;
735 }
736 else if (pan == 1.0) { // panned hard right
737 numRight++;
738 }
739 else if (pan == 0) { // panned dead center
740 // numMono++;
741 }
742 else { // panned somewhere else
743 numLeft++;
744 numRight++;
745 }
746 }
747 }
748
749 // if there is stereo content, report 2, else report 1
750 if (numRight > 0 || numLeft > 0) {
751 return 2;
752 }
753
754 return 1;
755}
756
757// TODO: JKC July2016: Merge labels/tracks duplicated export code.
758// TODO: JKC Apr2019: Doubly so merge these! Too much duplication.
760 const wxString &prefix, bool addNumber)
761{
762 wxASSERT(mProject);
763 int numFiles = mNumLabels;
764 int l = 0; // counter for files done
765 std::vector<ExportKit> exportSettings; // dynamic array for settings.
766 exportSettings.reserve(numFiles); // Allocate some guessed space to use.
767
768 // Account for exporting before first label
769 if( mFirst->GetValue() ) {
770 l--;
771 numFiles++;
772 }
773
774 // Figure out how many channels we should export.
775 auto channels = GetNumExportChannels( *mTracks );
776
777 FilePaths otherNames; // keep track of file names we will use, so we
778 // don't duplicate them
779 ExportKit setting; // the current batch of settings
780 setting.destfile.SetPath(mDir->GetValue());
781 setting.destfile.SetExt(mPlugins[mPluginIndex]->GetExtension(mSubFormatIndex));
782 wxLogDebug(wxT("Plug-in index = %d, Sub-format = %d"), mPluginIndex, mSubFormatIndex);
783 wxLogDebug(wxT("File extension is %s"), setting.destfile.GetExt());
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 const LabelStruct *info = NULL;
788 /* Examine all labels a first time, sort out all data but don't do any
789 * exporting yet (so this run is quick but interactive) */
790 while( l < mNumLabels ) {
791
792 // Get file name and starting time
793 if( l < 0 ) {
794 // create wxFileName for output file
795 name = (mFirstFileName->GetValue());
796 setting.t0 = 0.0;
797 } else {
798 info = mLabels->GetLabel(l);
799 name = (info->title);
800 setting.t0 = info->selectedRegion.t0();
801 }
802
803 // Figure out the ending time
804 if( info && !info->selectedRegion.isPoint() ) {
805 setting.t1 = info->selectedRegion.t1();
806 } else if( l < mNumLabels-1 ) {
807 // Use start of next label as end
808 const LabelStruct *info1 = mLabels->GetLabel(l+1);
809 setting.t1 = info1->selectedRegion.t0();
810 } else {
811 setting.t1 = mTracks->GetEndTime();
812 }
813
814 if( name.empty() )
815 name = _("untitled");
816
817 // store title of label to use in tags
818 title = name;
819
820 // Numbering files...
821 if( !byName ) {
822 name.Printf(wxT("%s-%02d"), prefix, l+1);
823 } else if( addNumber ) {
824 // Following discussion with GA, always have 2 digits
825 // for easy file-name sorting (on Windows)
826 name.Prepend(wxString::Format(wxT("%02d-"), l+1));
827 }
828
829 // store sanitised and user checked name in object
830 setting.destfile.SetName(MakeFileName(name));
831 if( setting.destfile.GetName().empty() )
832 { // user cancelled dialogue, or deleted everything in field.
833 // or maybe the label was empty??
834 // So we ignore this one and keep going.
835 }
836 else
837 {
838 // FIXME: TRAP_ERR User could have given an illegal filename prefix.
839 // in that case we should tell them, not fail silently.
840 wxASSERT(setting.destfile.IsOk()); // burp if file name is broke
841
842 // Make sure the (final) file name is unique within the set of exports
843 FileNames::MakeNameUnique(otherNames, setting.destfile);
844
845 /* do the metadata for this file */
846 // copy project metadata to start with
847 setting.filetags = Tags::Get( *mProject );
848 setting.filetags.LoadDefaults();
849 if (exportSettings.size()) {
850 setting.filetags = exportSettings.back().filetags;
851 }
852 // over-ride with values
853 setting.filetags.SetTag(TAG_TITLE, title);
854 setting.filetags.SetTag(TAG_TRACK, l+1);
855 // let the user have a crack at editing it, exit if cancelled
857 bool bShowTagsDialog = settings.GetShowId3Dialog();
858
859 bShowTagsDialog = bShowTagsDialog && mPlugins[mPluginIndex]->GetCanMetaData(mSubFormatIndex);
860
861 if( bShowTagsDialog ){
862 bool bCancelled = !TagsEditorDialog::ShowEditDialog(setting.filetags,
864 XO("Edit Metadata Tags"), bShowTagsDialog);
865 gPrefs->Read(wxT("/AudioFiles/ShowId3Dialog"), &bShowTagsDialog, true);
866 settings.SetShowId3Dialog( bShowTagsDialog );
867 if( bCancelled )
869 }
870 }
871
872 /* add the settings to the array of settings to be used for export */
873 exportSettings.push_back(setting);
874
875 l++; // next label, count up one
876 }
877
878 auto ok = ProgressResult::Success; // did it work?
879 int count = 0; // count the number of successful runs
880 ExportKit activeSetting; // pointer to the settings in use for this export
881 /* Go round again and do the exporting (so this run is slow but
882 * non-interactive) */
883 std::unique_ptr<BasicUI::ProgressDialog> pDialog;
884 for (count = 0; count < numFiles; count++) {
885 /* get the settings to use for the export from the array */
886 activeSetting = exportSettings[count];
887 // Bug 1440 fix.
888 if( activeSetting.destfile.GetName().empty() )
889 continue;
890
891 // Export it
892 ok = DoExport(pDialog, channels, activeSetting.destfile, false,
893 activeSetting.t0, activeSetting.t1, activeSetting.filetags);
894 if (ok == ProgressResult::Stopped) {
895 AudacityMessageDialog dlgMessage(
896 nullptr,
897 XO("Continue to export remaining files?"),
898 XO("Export"),
899 wxYES_NO | wxNO_DEFAULT | wxICON_WARNING);
900 if (dlgMessage.ShowModal() != wxID_YES ) {
901 // User decided not to continue - bail out!
902 break;
903 }
904 }
905 else if (ok != ProgressResult::Success) {
906 break;
907 }
908 }
909
910 return ok;
911}
912
914 const wxString &prefix, bool addNumber)
915{
916 wxASSERT(mProject);
917 int l = 0; // track counter
918 auto ok = ProgressResult::Success;
919 FilePaths otherNames;
920 std::vector<ExportKit> exportSettings; // dynamic array we will use to store the
921 // settings needed to do the exports with in
922 exportSettings.reserve(mNumWaveTracks); // Allocate some guessed space to use.
923 ExportKit setting; // the current batch of settings
924 setting.destfile.SetPath(mDir->GetValue());
925 setting.destfile.SetExt(mPlugins[mPluginIndex]->GetExtension(mSubFormatIndex));
926
927 wxString name; // used to hold file name whilst we mess with it
928 wxString title; // un-messed-with title of file for tagging with
929
930 /* Remember which tracks were selected, and set them to deselected */
932 for (auto tr : mTracks->Selected<WaveTrack>())
933 tr->SetSelected(false);
934
935 bool anySolo = !(( mTracks->Any<const WaveTrack>() + &WaveTrack::GetSolo ).empty());
936
937 bool skipSilenceAtBeginning;
938 gPrefs->Read(wxT("/AudioFiles/SkipSilenceAtBeginning"), &skipSilenceAtBeginning, false);
939
940 /* Examine all tracks in turn, collecting export information */
941 for (auto tr : mTracks->Leaders<WaveTrack>() -
943
944 // Get the times for the track
945 auto channels = TrackList::Channels(tr);
946 setting.t0 = skipSilenceAtBeginning ? channels.min(&Track::GetStartTime) : 0;
947 setting.t1 = channels.max( &Track::GetEndTime );
948
949 // number of export channels?
950 setting.channels = channels.size();
951 if (setting.channels == 1 &&
952 !(tr->GetChannel() == WaveTrack::MonoChannel &&
953 tr->GetPan() == 0.0))
954 setting.channels = 2;
955
956 // Get name and title
957 title = tr->GetName();
958 if( title.empty() )
959 title = _("untitled");
960
961 if (byName) {
962 name = title;
963 if (addNumber) {
964 name.Prepend(
965 wxString::Format(wxT("%02d-"), l+1));
966 }
967 }
968 else {
969 name = (wxString::Format(wxT("%s-%02d"), prefix, l+1));
970 }
971
972 // store sanitised and user checked name in object
973 setting.destfile.SetName(MakeFileName(name));
974
975 if (setting.destfile.GetName().empty())
976 { // user cancelled dialogue, or deleted everything in field.
977 // So we ignore this one and keep going.
978 }
979 else
980 {
981
982 // FIXME: TRAP_ERR User could have given an illegal track name.
983 // in that case we should tell them, not fail silently.
984 wxASSERT(setting.destfile.IsOk()); // burp if file name is broke
985
986 // Make sure the (final) file name is unique within the set of exports
987 FileNames::MakeNameUnique(otherNames, setting.destfile);
988
989 /* do the metadata for this file */
990 // copy project metadata to start with
991 setting.filetags = Tags::Get( *mProject );
992 setting.filetags.LoadDefaults();
993 if (exportSettings.size()) {
994 setting.filetags = exportSettings.back().filetags;
995 }
996 // over-ride with values
997 setting.filetags.SetTag(TAG_TITLE, title);
998 setting.filetags.SetTag(TAG_TRACK, l+1);
999 // let the user have a crack at editing it, exit if cancelled
1001 bool bShowTagsDialog = settings.GetShowId3Dialog();
1002
1003 bShowTagsDialog = bShowTagsDialog && mPlugins[mPluginIndex]->GetCanMetaData(mSubFormatIndex);
1004
1005 if( bShowTagsDialog ){
1006 bool bCancelled = !TagsEditorDialog::ShowEditDialog(setting.filetags,
1008 XO("Edit Metadata Tags"), bShowTagsDialog);
1009 gPrefs->Read(wxT("/AudioFiles/ShowId3Dialog"), &bShowTagsDialog, true);
1010 settings.SetShowId3Dialog( bShowTagsDialog );
1011 if( bCancelled )
1013 }
1014 }
1015 /* add the settings to the array of settings to be used for export */
1016 exportSettings.push_back(setting);
1017
1018 l++; // next track, count up one
1019 }
1020 // end of user-interactive data gathering loop, start of export processing
1021 // loop
1022 int count = 0; // count the number of successful runs
1023 ExportKit activeSetting; // pointer to the settings in use for this export
1024 std::unique_ptr<BasicUI::ProgressDialog> pDialog;
1025
1026 for (auto tr : mTracks->Leaders<WaveTrack>() -
1027 (anySolo ? &WaveTrack::GetNotSolo : &WaveTrack::GetMute)) {
1028
1029 wxLogDebug( "Get setting %i", count );
1030 /* get the settings to use for the export from the array */
1031 activeSetting = exportSettings[count];
1032 if( activeSetting.destfile.GetName().empty() ){
1033 count++;
1034 continue;
1035 }
1036
1037 /* Select the track */
1039 const auto range = TrackList::Channels(tr);
1040 for (auto channel : range)
1041 channel->SetSelected(true);
1042
1043 // Export the data. "channels" are per track.
1044 ok = DoExport(pDialog,
1045 activeSetting.channels, activeSetting.destfile, true,
1046 activeSetting.t0, activeSetting.t1, activeSetting.filetags);
1047 if (ok == ProgressResult::Stopped) {
1048 AudacityMessageDialog dlgMessage(
1049 nullptr,
1050 XO("Continue to export remaining files?"),
1051 XO("Export"),
1052 wxYES_NO | wxNO_DEFAULT | wxICON_WARNING);
1053 if (dlgMessage.ShowModal() != wxID_YES ) {
1054 // User decided not to continue - bail out!
1055 break;
1056 }
1057 }
1058 else if (ok != ProgressResult::Success) {
1059 break;
1060 }
1061 // increment export counter
1062 count++;
1063
1064 }
1065
1066 return ok ;
1067}
1068
1069ProgressResult ExportMultipleDialog::DoExport(std::unique_ptr<BasicUI::ProgressDialog> &pDialog,
1070 unsigned channels,
1071 const wxFileName &inName,
1072 bool selectedOnly,
1073 double t0,
1074 double t1,
1075 const Tags &tags)
1076{
1077 wxFileName name;
1078
1079 wxLogDebug(wxT("Doing multiple Export: File name \"%s\""), (inName.GetFullName()));
1080 wxLogDebug(wxT("Channels: %i, Start: %lf, End: %lf "), channels, t0, t1);
1081 if (selectedOnly)
1082 wxLogDebug(wxT("Selected Region Only"));
1083 else
1084 wxLogDebug(wxT("Whole Project"));
1085
1086 wxFileName backup;
1087 if (mOverwrite->GetValue()) {
1088 name = inName;
1089 backup.Assign(name);
1090
1091 int suffix = 0;
1092 do {
1093 backup.SetName(name.GetName() +
1094 wxString::Format(wxT("%d"), suffix));
1095 ++suffix;
1096 }
1097 while (backup.FileExists());
1098 ::wxRenameFile(inName.GetFullPath(), backup.GetFullPath());
1099 }
1100 else {
1101 name = inName;
1102 int i = 2;
1103 wxString base(name.GetName());
1104 while (name.FileExists()) {
1105 name.SetName(wxString::Format(wxT("%s-%d"), base, i++));
1106 }
1107 }
1108
1110 const wxString fullPath{name.GetFullPath()};
1111
1112 auto cleanup = finally( [&] {
1113 bool ok =
1114 success == ProgressResult::Stopped ||
1115 success == ProgressResult::Success;
1116 if (backup.IsOk()) {
1117 if ( ok )
1118 // Remove backup
1119 ::wxRemoveFile(backup.GetFullPath());
1120 else {
1121 // Restore original
1122 ::wxRemoveFile(fullPath);
1123 ::wxRenameFile(backup.GetFullPath(), fullPath);
1124 }
1125 }
1126 else {
1127 if ( ! ok )
1128 // Remove any new, and only partially written, file.
1129 ::wxRemoveFile(fullPath);
1130 }
1131 } );
1132
1133 // Call the format export routine
1134 success = mPlugins[mPluginIndex]->Export(mProject,
1135 pDialog,
1136 channels,
1137 fullPath,
1138 selectedOnly,
1139 t0,
1140 t1,
1141 NULL,
1142 &tags,
1143 mSubFormatIndex);
1144
1145 if (success == ProgressResult::Success || success == ProgressResult::Stopped) {
1146 mExported.push_back(fullPath);
1147 }
1148
1149 Refresh();
1150 Update();
1151
1152 return success;
1153}
1154
1155wxString ExportMultipleDialog::MakeFileName(const wxString &input)
1156{
1157 wxString newname = input; // name we are generating
1158
1159 // strip out anything that isn't allowed in file names on this platform
1160 auto changed = Internat::SanitiseFilename(newname, wxT("_"));
1161
1162 if(changed)
1163 { // need to get user to fix file name
1164 // build the dialog
1166 wxString excluded = ::wxJoin( Internat::GetExcludedCharacters(), wxT(' '), wxT('\0') );
1167 // TODO: For Russian language we should have separate cases for 2 and more than 2 letters.
1168 if( excluded.length() > 1 ){
1169 msg = XO(
1170// i18n-hint: The second %s gives some letters that can't be used.
1171"Label or track \"%s\" is not a legal file name.\nYou cannot use any of these characters:\n\n%s\n\nSuggested replacement:")
1172 .Format( input, excluded );
1173 } else {
1174 msg = XO(
1175// i18n-hint: The second %s gives a letter that can't be used.
1176"Label or track \"%s\" is not a legal file name. You cannot use \"%s\".\n\nSuggested replacement:")
1177 .Format( input, excluded );
1178 }
1179
1180 AudacityTextEntryDialog dlg( this, msg, XO("Save As..."), newname );
1181
1182
1183 // And tell the validator about excluded chars
1184 dlg.SetTextValidator( wxFILTER_EXCLUDE_CHAR_LIST );
1185 wxTextValidator *tv = dlg.GetTextValidator();
1186 tv->SetExcludes(Internat::GetExcludedCharacters());
1187
1188 // Show the dialog and bail if the user cancels
1189 if( dlg.ShowModal() == wxID_CANCEL )
1190 {
1191 return wxEmptyString;
1192 }
1193 // Extract the name from the dialog
1194 newname = dlg.GetValue();
1195 } // phew - end of file name sanitisation procedure
1196 return newname;
1197}
1198
1199void SuccessDialog::OnKeyDown(wxListEvent& event)
1200{
1201 if (event.GetKeyCode() == WXK_RETURN)
1202 EndModal(1);
1203 else
1204 event.Skip(); // allow standard behaviour
1205}
1206
1207void SuccessDialog::OnItemActivated(wxListEvent& WXUNUSED(event))
1208{
1209 EndModal(1);
1210}
1211
1212void MouseEvtHandler::OnMouse(wxMouseEvent& event)
1213{
1214 event.Skip(false);
1215}
wxT("CloseDown"))
R GuardedCall(const F1 &body, const F2 &handler=F2::Default(), F3 delayedHandler=DefaultDelayedHandlerAction) noexcept(noexcept(handler(std::declval< AudacityException * >())) &&noexcept(handler(nullptr)) &&noexcept(std::function< void(AudacityException *)>{std::move(delayedHandler)}))
Execute some code on any thread; catch any AudacityException; enqueue error report on the main thread...
int AudacityMessageBox(const TranslatableString &message, const TranslatableString &caption, long style, wxWindow *parent, int x, int y)
END_EVENT_TABLE()
EVT_BUTTON(wxID_NO, DependencyDialog::OnNo) EVT_BUTTON(wxID_YES
const TranslatableString name
Definition: Distortion.cpp:82
@ LabelID
@ CreateID
@ TrackID
@ PrefixID
@ ByNumberID
@ ChooseID
@ OverwriteID
@ DirID
@ FormatID
@ ByNameAndNumberID
@ OptionsID
@ FirstFileNameID
@ ByNameID
@ FirstID
static unsigned GetNumExportChannels(const TrackList &tracks)
EVT_LIST_ITEM_ACTIVATED(wxID_ANY, SuccessDialog::OnItemActivated) ExportMultipleDialog
int format
Definition: ExportPCM.cpp:56
#define XXO(s)
Definition: Internat.h:44
#define XO(s)
Definition: Internat.h:31
#define _(s)
Definition: Internat.h:75
static const auto title
FileConfig * gPrefs
Definition: Prefs.cpp:71
ByColumns_t ByColumns
Definition: Prefs.cpp:474
AUDACITY_DLL_API wxFrame & GetProjectFrame(AudacityProject &project)
Get the top-level window associated with the project (as a wxFrame only, when you do not need to use ...
accessors for certain important windows associated with each project
@ eIsCreating
Definition: ShuttleGui.h:39
@ eIsCreatingFromPrefs
Definition: ShuttleGui.h:48
@ eIsSavingToPrefs
Definition: ShuttleGui.h:49
@ eOkButton
Definition: ShuttleGui.h:597
@ eCancelButton
Definition: ShuttleGui.h:598
@ eHelpButton
Definition: ShuttleGui.h:601
#define TAG_TRACK
Definition: Tags.h:61
#define TAG_TITLE
Definition: Tags.h:58
#define S(N)
Definition: ToChars.cpp:64
static Settings & settings()
Definition: TrackInfo.cpp:87
TranslatableString Verbatim(wxString str)
Require calls to the one-argument constructor to go through this distinct global function name.
std::vector< TranslatableString > TranslatableStrings
static const auto fn
Wrap wxMessageDialog so that caption IS translatable.
The top-level handle to an Audacity project. It serves as a source of events that other objects can b...
Definition: Project.h:89
const wxString & GetProjectName() const
Definition: Project.cpp:100
Wrap wxTextEntryDialog so that caption IS translatable.
Main class to control the export function.
Presents a dialog box allowing the user to export multiple files either by exporting each track as a ...
ProgressResult ExportMultipleByTrack(bool byName, const wxString &prefix, bool addNumber)
Export each track in the project to a separate file.
void OnCreate(wxCommandEvent &event)
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 OnFormat(wxCommandEvent &event)
wxSimplebook * mBook
void OnChoose(wxCommandEvent &event)
void OnByName(wxCommandEvent &event)
void OnByNumber(wxCommandEvent &event)
std::vector< ExportPlugin * > mPlugins
void OnHelp(wxCommandEvent &event)
wxTextCtrl * mFirstFileName
void OnCancel(wxCommandEvent &event)
wxRadioButton * mByName
ProgressResult ExportMultipleByLabel(bool byName, const wxString &prefix, bool addNumber)
Export multiple labeled regions of the project to separate files.
wxCheckBox * mOverwrite
const LabelTrack * mLabels
wxRadioButton * mTrack
wxRadioButton * mLabel
void OnTrack(wxCommandEvent &event)
ProgressResult DoExport(std::unique_ptr< BasicUI::ProgressDialog > &pDialog, unsigned channels, const wxFileName &name, bool selectedOnly, double t0, double t1, const Tags &tags)
wxStaticText * mFirstFileLabel
SelectionState & mSelectionState
void OnFirstFileName(wxCommandEvent &event)
void OnLabel(wxCommandEvent &event)
wxRadioButton * mByNumberAndName
wxStaticText * mPrefixLabel
void OnFirst(wxCommandEvent &event)
void OnExport(wxCommandEvent &event)
wxRadioButton * mByNumber
void PopulateOrExchange(ShuttleGui &S)
AudacityProject * mProject
void OnPrefix(wxCommandEvent &event)
void OnOptions(wxCommandEvent &event)
virtual bool Flush(bool bCurrentOnly=false) wxOVERRIDE
Definition: FileConfig.cpp:143
Abstract base class used in importing a file.
static void ShowHelp(wxWindow *parent, const FilePath &localFileName, const URLString &remoteURL, bool bModal=false, bool alwaysDefaultBrowser=false)
Definition: HelpSystem.cpp:239
static void ShowInfoDialog(wxWindow *parent, const TranslatableString &dlogTitle, const TranslatableString &shortMsg, const wxString &message, const int xSize, const int ySize)
Displays cuttable information in a text ctrl, with an OK button.
Definition: HelpSystem.cpp:92
static bool SanitiseFilename(wxString &name, const wxString &sub)
Check a proposed file name string for illegal characters and remove them return true iff name is "vis...
Definition: Internat.cpp:240
static const wxArrayString & GetExcludedCharacters()
Definition: Internat.h:149
A LabelStruct holds information for ONE label in a LabelTrack.
Definition: LabelTrack.h:31
wxString title
Definition: LabelTrack.h:72
SelectedRegion selectedRegion
Definition: LabelTrack.h:71
A LabelTrack is a Track that holds labels (LabelStruct).
Definition: LabelTrack.h:89
int GetNumLabels() const
Definition: LabelTrack.cpp:942
const LabelStruct * GetLabel(int index) const
Definition: LabelTrack.cpp:947
void OnMouse(wxMouseEvent &event)
bool GetSolo() const
Definition: Track.h:924
bool GetNotSolo() const
Definition: Track.h:926
bool GetMute() const
Definition: Track.h:923
static ProjectSettings & Get(AudacityProject &project)
static ProjectWindow * Find(AudacityProject *pProject)
double t1() const
bool isPoint() const
double t0() const
static SelectionState & Get(AudacityProject &project)
Derived from ShuttleGuiBase, an Audacity specific class for shuttling data to and from GUI.
Definition: ShuttleGui.h:628
void OnKeyDown(wxListEvent &event)
void OnItemActivated(wxListEvent &event)
static AUDACITY_DLL_API bool ShowEditDialog(Tags &tags, wxWindow *parent, const TranslatableString &title, bool force=false)
Definition: TagsEditor.cpp:23
ID3 Tags (for MP3)
Definition: Tags.h:73
static Tags & Get(AudacityProject &project)
Definition: Tags.cpp:214
@ LeftChannel
Definition: Track.h:282
@ RightChannel
Definition: Track.h:283
@ MonoChannel
Definition: Track.h:284
virtual double GetStartTime() const =0
virtual double GetEndTime() const =0
A flat linked list of tracks supporting Add, Remove, Clear, and Contains, serialization of the list o...
Definition: Track.h:1338
double GetEndTime() const
Definition: Track.cpp:1052
auto Leaders() -> TrackIterRange< TrackType >
Definition: Track.h:1471
auto Any() -> TrackIterRange< TrackType >
Definition: Track.h:1437
static TrackList & Get(AudacityProject &project)
Definition: Track.cpp:486
auto Selected() -> TrackIterRange< TrackType >
Definition: Track.h:1454
static auto Channels(TrackType *pTrack) -> TrackIterRange< TrackType >
Definition: Track.h:1541
Holds a msgid for the translation catalog; may also bind format arguments.
A Track that contains audio waveform data.
Definition: WaveTrack.h:57
A private class used to store the information needed to do an export.
Extend wxArrayString with move operations and construction and insertion fromstd::initializer_list.
ProgressResult
Definition: BasicUI.h:145
FILES_API void UpdateDefaultPath(Operation op, const FilePath &path)
FILES_API void MakeNameUnique(FilePaths &otherNames, wxFileName &newName)
FILES_API FilePath FindDefaultPath(Operation op)
auto begin(const Ptr< Type, BaseDeleter > &p)
Enables range-for.
Definition: PackedArray.h:150