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