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