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