Audacity 3.2.0
ExportAudioDialog.cpp
Go to the documentation of this file.
1/**********************************************************************
2
3 Audacity: A Digital Audio Editor
4
5 ExportAudioDialog.cpp
6
7 Dominic Mazzoni
8
9 Vitaly Sverchinsky split from ExportMultiple.cpp and ExportAudioDialog.cpp
10
11**********************************************************************/
12
13#include "ExportAudioDialog.h"
14
15#include <numeric>
16
17#include <wx/frame.h>
18
19#include "Export.h"
20#include "ExportUtils.h"
21#include "WaveTrack.h"
22#include "LabelTrack.h"
23#include "Mix.h"
24#include "Prefs.h"
25#include "ViewInfo.h"
26#include "Project.h"
27#include "SelectionState.h"
28
29#include <wx/log.h>
30#include <wx/checkbox.h>
31#include <wx/textctrl.h>
32#include <wx/button.h>
33#include <wx/radiobut.h>
34#include <wx/stattext.h>
35#include <wx/event.h>
36
37#include "ShuttleGui.h"
38#include "AudacityMessageBox.h"
39#include "ProjectWindows.h"
40#include "Theme.h"
41#include "HelpSystem.h"
42#include "TagsEditor.h"
43#include "ExportFilePanel.h"
44#include "ExportProgressUI.h"
45#include "ImportExport.h"
46#include "WindowAccessible.h"
47
48#if wxUSE_ACCESSIBILITY
49#include "WindowAccessible.h"
50#endif
51
52namespace
53{
54const bool hookRegistered = [] {
56 [](AudacityProject& project, const FileExtension& format, bool selectedOnly)
57 {
59 project.GetProjectName(), format,
60 selectedOnly
63 };
64
65 dialog.ShowModal();
67 });
68 return true;
69}();
70
71ChoiceSetting ExportAudioExportRange { L"/ExportAudioDialog/ExportRange",
72 {
73 { "project", XO("Entire &Project") },
74 { "split", XO("M&ultiple Files") },
75 { "selection", XO("Curren&t Selection") }
76
77 },
78 0, //project
79};
80
81ChoiceSetting ExportAudioSplitMode { L"/ExportAudioDialog/SplitMode",
82 {
83 { "tracks", XO("Tracks") },
84 { "labels", XO("Labels") }
85 },
86 0
87};
88
89ChoiceSetting ExportAudioSplitNamePolicy { L"/ExportAudioDialog/SplitNamePolicy",
90 {
91 { "name", XO("Using Label/Track Name") },//->"label_track"
92 { "num_and_name", XO("Numbering before Label/Track Name") },//->"number_before"
93 { "num_and_prefix", XO("Numbering after File name prefix") }//->"number_after"
94 },
95 0
96};
97
98BoolSetting ExportAudioIncludeAudioBeforeFirstLabel { L"/ExportAudioDialog/IncludeAudioBeforeFirstLabel", false };
99
100BoolSetting ExportAudioOverwriteExisting { L"/ExportAudioDialog/OverwriteExisting", false };
101
102BoolSetting ExportAudioSkipSilenceAtBeginning { L"/ExportAudioDialog/SkipSilenceAtBeginning", false };
103
104StringSetting ExportAudioDefaultFormat{ L"/ExportAudioDialog/Format", L"WAV" };
105
106StringSetting ExportAudioDefaultPath{ L"ExportAudioDialog/DefaultPath", L"" };
107
108enum {
109 ExportFilePanelID = 10000,//to avoid IDs collision with ExportFilePanel items
110
114
116
119
121
125
127
129
131};
132
133}
134
135BEGIN_EVENT_TABLE(ExportAudioDialog, wxDialogWrapper)
136 EVT_COMMAND(ExportFilePanelID, AUDACITY_EXPORT_FORMAT_CHANGE_EVENT, ExportAudioDialog::OnFormatChange)
137
141
144
148
151
153
156
159
162 const wxString& defaultName,
163 const wxString& defaultFormat,
164 ExportMode mode)
165 : wxDialogWrapper(parent, wxID_ANY, XO("Export Audio"))
166 , mProject(project)
167{
169
171 PopulateOrExchange(S);
172
173 SetMinSize({GetBestSize().GetWidth(), -1});
174
175 wxFileName filename;
176 auto exportPath = ExportAudioDefaultPath.Read();
177 if(exportPath.empty())
178 exportPath = FileNames::FindDefaultPath(FileNames::Operation::Export);
179 filename.SetPath(exportPath);
180
181 //extension will be set in `ChangeFormat`
182 filename.SetEmptyExt();
183 if(defaultName.empty())
184 //i18n-hint: default exported file name when exporting from unsaved project
185 filename.SetName(_("untitled"));
186 else
187 filename.SetName(defaultName);
188
191 {
193 for(const auto track : tracks.Any<WaveTrack>())
194 sampleRate = std::max(sampleRate, track->GetRate());
195 }
196
197 wxString format = defaultFormat;
198 if(format.empty())
200
201 mExportOptionsPanel->Init(filename, sampleRate, format);
202
203 auto& tracks = TrackList::Get(mProject);
204 const auto labelTracks = tracks.Any<LabelTrack>();
205 const auto hasLabels = !labelTracks.empty() &&
206 (*labelTracks.begin())->GetNumLabels() > 0;
207 const auto hasMultipleWaveTracks = tracks.Any<WaveTrack>().size() > 1;
208 const auto hasSelectedAudio = ExportUtils::HasSelectedAudio(mProject);
209
210 mRangeSelection->Enable(hasSelectedAudio);
211 mRangeSplit->Enable(hasLabels || hasMultipleWaveTracks);
212
213 mSplitByLabels->Enable(hasLabels);
214 mSplitByTracks->Enable(hasMultipleWaveTracks);
215
216 if(mRangeSelection->IsEnabled() && mode == ExportMode::SelectedOnly)
217 {
218 mRangeSelection->SetValue(true);
219 }
220 else
221 {
222 if(!mRangeSelection->IsEnabled() && ExportAudioExportRange.Read() == "selection")
223 mRangeProject->SetValue(true);
224
225 if(!mRangeSplit->IsEnabled() && ExportAudioExportRange.Read() == "split")
226 mRangeProject->SetValue(true);
227
228 if(!hasLabels && hasMultipleWaveTracks)
229 mSplitByTracks->SetValue(true);
230 if (!hasMultipleWaveTracks && hasLabels)
231 mSplitByLabels->SetValue(true);
232 }
233
234 if (mRangeSelection->IsEnabled() && !hasLabels && !hasMultipleWaveTracks)
235 mRangeSelection->MoveAfterInTabOrder(mRangeProject);
236
237 if (ExportAudioExportRange.Read() != "split" || (!hasLabels && !hasMultipleWaveTracks))
238 mSplitsPanel->Hide();
239
240 mExportOptionsPanel->SetCustomMappingEnabled(!mRangeSplit->GetValue());
241
242 mIncludeAudioBeforeFirstLabel->Enable(mSplitByLabels->GetValue());
243
244 if(ExportAudioSplitNamePolicy.Read() != "num_and_prefix")
245 mSplitFileNamePrefix->Disable();
246
247 Layout();
248 Fit();
249 Center();
250}
251
253
254// Fix for issue #4960, which only affects Windows
256{
257 bool ret = wxDialogWrapper::Show(show);
258
259#if defined(__WXMSW__)
260 if (show)
262#endif
263
264 return ret;
265}
266
268{
269 S.SetBorder(5);
270 S.StartVerticalLay();
271 {
272 if(S.GetMode() == eIsCreating)
273 {
275 S.Id(ExportFilePanelID).AddWindow(mExportOptionsPanel, wxEXPAND);
276 }
277
278 S.StartPanel();
279 {
280 S.SetBorder(5);
281 S.StartTwoColumn();
282 {
283 S.StartHorizontalLay(wxSHRINK | wxALIGN_TOP);
284 {
285 if(auto prompt = S.AddPrompt(XO("Export Range:")))
286 prompt->SetMinSize({145, -1});
287 }
288 S.EndHorizontalLay();
289
290 S.StartVerticalLay();
291 {
292 S.StartRadioButtonGroup(ExportAudioExportRange);
293 {
295 .Name(XO("Export entire project"))
296 .TieRadioButton();
298 .Name(XO("Export multiple files"))
299 .TieRadioButton();
301 .Name(XO("Export current selection"))
302 .TieRadioButton();
303#if wxUSE_ACCESSIBILITY
307#endif
308 }
309 S.EndRadioButtonGroup();
310 }
311 S.EndVerticalLay();
312 }
313 S.EndTwoColumn();
314
315 S.AddSpace(10);
316
317 S.StartTwoColumn();
318 {
319 S.AddSpace(155, 1);
322 .TieCheckBox(XO("Trim blank space before first clip"), ExportAudioSkipSilenceAtBeginning);
323 }
324 S.EndTwoColumn();
325 }
326 S.EndPanel();
327
328 S.SetBorder(5);
329 mSplitsPanel = S.StartPanel();
330 {
331 S.StartMultiColumn(2);
332 {
333 S.StartStatic(XO("Split files based on:"));
334 {
335 S.StartVerticalLay();
336 {
337 S.StartRadioButtonGroup(ExportAudioSplitMode);
338 {
339 mSplitByTracks = S.Id(ExportModeTracksID).TieRadioButton();
340 mSplitByLabels = S.Id(ExportModeLabelsID).TieRadioButton();
341 }
342 S.EndRadioButtonGroup();
343
344 S.StartHorizontalLay(wxALIGN_TOP);
345 {
346 S.AddSpace(10, 1);
349 .TieCheckBox(XO("Include audio before first label"), ExportAudioIncludeAudioBeforeFirstLabel);
350 }
351 S.EndHorizontalLay();
352 }
353 S.EndVerticalLay();
354 }
355 S.EndStatic();
356
357 S.StartStatic(XO("Name files:"));
358 {
359 S.StartVerticalLay();
360 {
361 S.StartRadioButtonGroup(ExportAudioSplitNamePolicy);
362 {
363 mSplitUseName = S.Id(ExportSplitNamePolicyTrackNameID).TieRadioButton();
366 }
367 S.EndRadioButtonGroup();
368 S.StartHorizontalLay(wxALIGN_TOP);
369 {
370 S.AddSpace(10, 1);
373 .AddTextBox(XO("File name prefix:"), {}, 0);
374 }
375 S.EndHorizontalLay();
376 }
377 S.EndVerticalLay();
378 }
379 S.EndStatic();
380 }
381 S.EndMultiColumn();
382
385 .TieCheckBox(XO("Overwrite existing files"), ExportAudioOverwriteExisting);
386 }
387 S.EndPanel();
388
389 S.AddSpace(1, 10);
390
391 S.SetBorder(5);
392
393 S.SetBorder(5);
394 S.StartHorizontalLay(wxEXPAND);
395 {
396 mEditMetadata = S.Id(EditMetadataID).AddButton(XO("Edit &Metadata..."), wxLEFT | wxBOTTOM);
397 S.AddSpace(1, 1, wxEXPAND);
398 S.Id(wxID_CANCEL).AddButton(XO("&Cancel"), wxBOTTOM);
399 S.Id(wxID_OK).AddButton(XO("&Export"), wxRIGHT | wxBOTTOM, true);
400 }
401 S.EndHorizontalLay();
402 }
403 S.EndVerticalLay();
404}
405
406
407void ExportAudioDialog::OnExportRangeChange(wxCommandEvent& event)
408{
409 const auto enableSplits = event.GetId() == ExportRangeSplitID;
411 if(mSplitsPanel->IsShown() != enableSplits)
412 {
414 mSplitsPanel->Show(enableSplits);
415
416 Layout();
417 Fit();
418 }
419}
420void ExportAudioDialog::OnSplitModeChange(wxCommandEvent& event)
421{
422 mIncludeAudioBeforeFirstLabel->Enable(event.GetId() == ExportModeLabelsID);
424}
425
427{
430}
431
433{
435}
436
438{
440}
441
443{
445}
446
447void ExportAudioDialog::OnEditMetadata(wxCommandEvent &event)
448{
449 if(mRangeSplit->GetValue())
450 {
452
453 std::vector<Tags*> tags;
454 std::vector<wxString> names;
455 tags.reserve(mExportSettings.size());
456 names.reserve(mExportSettings.size());
457 for(auto& spec : mExportSettings)
458 {
459 tags.push_back(&spec.tags);
460 names.push_back(spec.filename.GetFullName());
461 }
462 TagsEditorDialog dialog(this, XO("Edit Metadata Tags"), tags, names, true, true);
463 dialog.ShowModal();
464 }
465 else
466 {
468 XO("Edit Metadata Tags"), XO("Exported Tags"));
469 }
470}
471
472void ExportAudioDialog::OnHelp(wxCommandEvent &event)
473{
474 HelpSystem::ShowHelp(wxGetTopLevelParent(this), L"File_Export_Dialog", true);
475}
476
477void ExportAudioDialog::OnExport(wxCommandEvent &event)
478{
479 auto selectedPlugin = mExportOptionsPanel->GetPlugin();
480 if(selectedPlugin == nullptr)
481 return;
482 auto selectedFormat = mExportOptionsPanel->GetFormat();
483 auto parameters = mExportOptionsPanel->GetParameters();
484 if(!parameters.has_value())
485 return;
486
487 const auto path = mExportOptionsPanel->GetPath();
488
489 if(!wxDirExists(path))
490 wxFileName::Mkdir(path, wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL);
491
492 if(!wxDirExists(path))
493 {
494 AudacityMessageBox(XO("Unable to create destination folder"),
495 XO("Export Audio"),
496 wxOK | wxCENTER,
497 this);
498 return;
499 }
500
501 auto result = ExportResult::Error;
502
503 if(mRangeSplit->GetValue())
504 {
505 FilePaths exportedFiles;
506
508
509 if(mSplitByLabels->GetValue())
510 result = DoExportSplitByLabels(*selectedPlugin, selectedFormat, *parameters, exportedFiles);
511 else if(mSplitByTracks->GetValue())
512 result = DoExportSplitByTracks(*selectedPlugin, selectedFormat, *parameters, exportedFiles);
513
514 auto msg = (result == ExportResult::Success
515 ? XO("Successfully exported the following %lld file(s).")
516 : result == ExportResult::Error
517 ? XO("Something went wrong after exporting the following %lld file(s).")
518 : result == ExportResult::Cancelled
519 ? XO("Export canceled after exporting the following %lld file(s).")
520 : result == ExportResult::Stopped
521 ? XO("Export stopped after exporting the following %lld file(s).")
522 : XO("Something went really wrong after exporting the following %lld file(s).")
523 ).Format((long long) exportedFiles.size());
524
525 wxString fileList;
526 for (auto& path : exportedFiles)
527 fileList += path + '\n';
528
529 // TODO: give some warning dialog first, when only some files exported
530 // successfully.
531
533 XO("Export Audio"),
534 msg,
535 fileList,
536 450,400);
537 }
538 else
539 {
540 wxFileName filename(path, mExportOptionsPanel->GetFullName());
541
542 if (filename.FileExists()) {
543 auto result = AudacityMessageBox(
544 XO("A file named \"%s\" already exists. Replace?")
545 .Format( filename.GetFullPath() ),
546 XO("Export Audio"),
547 wxYES_NO | wxICON_EXCLAMATION);
548 if (result != wxYES) {
549 return;
550 }
551 }
552
553 ExportTaskBuilder builder;
554 builder.SetFileName(filename)
555 .SetPlugin(selectedPlugin, selectedFormat)
556 .SetParameters(*parameters)
558
559 const auto& viewInfo = ViewInfo::Get(mProject);
560
561 const auto selectedOnly = mRangeSelection->GetValue();
562
563 auto t0 = selectedOnly
564 ? std::max(.0, viewInfo.selectedRegion.t0())
565 : .0;
566
567 auto t1 = selectedOnly
568 ? std::min(TrackList::Get(mProject).GetEndTime(), viewInfo.selectedRegion.t1())
570
571 auto exportedTracks = ExportUtils::FindExportWaveTracks(TrackList::Get(mProject), selectedOnly);
572 if(exportedTracks.empty())
573 {
575 selectedOnly ? XO("All selected audio is muted.") : XO("All audio is muted."), //":576"
576 XO("Warning"),
577 false);
578 return;
579 }
580
581 if(mSkipSilenceAtBeginning->GetValue())
582 t0 = std::max(t0, exportedTracks.min(&Track::GetStartTime));
583
584 builder.SetRange(t0, t1, selectedOnly);
585
586 std::unique_ptr<MixerOptions::Downmix> tempMixerSpec;
587 const auto channels = mExportOptionsPanel->GetChannels();
588 if(channels == 0)
589 {
590 //Figure out the final channel mapping: mixer dialog shows
591 //all tracks regardless of their mute/solo state, but
592 //muted channels should not be present in exported file -
593 //apply channel mask to exclude them
595 std::vector<bool> channelMask(
596 tracks.sum([](const auto track) { return track->NChannels(); }),
597 false);
598 unsigned trackIndex = 0;
599 for(const auto track : tracks)
600 {
601 if(track->GetSolo())
602 {
603 channelMask.assign(channelMask.size(), false);
604 for(unsigned i = 0; i < track->NChannels(); ++i)
605 channelMask[trackIndex++] = true;
606 break;
607 }
608 if(!track->GetMute() && (!selectedOnly || track->GetSelected()))
609 {
610 for(unsigned i = 0; i < track->NChannels(); ++i)
611 channelMask[trackIndex++] = true;
612 }
613 else
614 trackIndex += track->NChannels();
615 }
616
617 tempMixerSpec = std::make_unique<MixerOptions::Downmix>(*mExportOptionsPanel->GetMixerSpec(), channelMask);
618 builder.SetMixerSpec(tempMixerSpec.get());
619 }
620 else
621 builder.SetNumChannels(channels);
622
624 {
625 result = ExportProgressUI::Show(builder.Build(mProject));
626 });
627 }
628
629 if(result == ExportResult::Success || result == ExportResult::Stopped)
630 {
632
633 ExportAudioDefaultFormat.Write(selectedPlugin->GetFormatInfo(selectedFormat).format);
635
638 event.Skip();
639 }
640}
641
642void ExportAudioDialog::OnFormatChange(wxCommandEvent& event)
643{
644 Layout();
645 Fit();
646
647 auto enableMeta = false;
648 if(auto plugin = mExportOptionsPanel->GetPlugin())
649 enableMeta = plugin->GetFormatInfo(mExportOptionsPanel->GetFormat()).canMetaData;
650 mEditMetadata->Enable(enableMeta);
652}
653
655{
657 return;
658
659 const auto selectedPlugin = mExportOptionsPanel->GetPlugin();
660 if(selectedPlugin == nullptr)
661 return;
662
663 const auto selectedFormat = mExportOptionsPanel->GetFormat();
664
665 if(mRangeSplit->GetValue())
666 {
667 const auto byName = mSplitUseName->GetValue() || mSplitUseNumAndName->GetValue();
668 const auto addNumber = mSplitUseNumAndName->GetValue();
669 const auto prefix = mSplitFileNamePrefix->GetValue();
670
671 if(mSplitByLabels->GetValue())
672 UpdateLabelExportSettings(*selectedPlugin, selectedFormat, byName, addNumber, prefix);
673 else if(mSplitByTracks->GetValue())
674 UpdateTrackExportSettings(*selectedPlugin, selectedFormat, byName, addNumber, prefix);
675
676 mExportSettingsDirty = false;
677 }
678}
679
680void ExportAudioDialog::UpdateLabelExportSettings(const ExportPlugin& plugin, int formatIndex, bool byName, bool addNumber, const wxString& prefix)
681{
682 const auto& tracks = TrackList::Get(mProject);
683 const auto& labels = (*tracks.Any<const LabelTrack>().begin());
684 const auto numLabels = labels->GetNumLabels();
685
686 auto numFiles = numLabels;
687 auto fileIndex = 0; // counter for files done
688 std::vector<ExportSetting> exportSettings; // dynamic array for settings.
689 exportSettings.reserve(numFiles); // Allocate some guessed space to use.
690
691 // Account for exporting before first label
692 if( mIncludeAudioBeforeFirstLabel->GetValue() ) {
693 fileIndex = -1;
694 numFiles++;
695 }
696
697 const auto formatInfo = plugin.GetFormatInfo(formatIndex);
698
699 FilePaths otherNames; // keep track of file names we will use, so we
700 // don't duplicate them
701 ExportSetting setting; // the current batch of settings
702 setting.filename.SetPath(mExportOptionsPanel->GetPath());
704 setting.filename.SetFullName(mExportOptionsPanel->GetFullName());
705
706 wxString name; // used to hold file name whilst we mess with it
707 wxString title; // un-messed-with title of file for tagging with
708
709 const LabelStruct *info = NULL;
710 /* Examine all labels a first time, sort out all data but don't do any
711 * exporting yet (so this run is quick but interactive) */
712 while( fileIndex < numLabels ) {
713
714 // Get file name and starting time
715 if( fileIndex < 0 ) {
716 // create wxFileName for output file
717 name = setting.filename.GetName();
718 setting.t0 = 0.0;
719 } else {
720 info = labels->GetLabel(fileIndex);
721 name = (info->title);
722 setting.t0 = info->selectedRegion.t0();
723 }
724
725 // Figure out the ending time
726 if( info && !info->selectedRegion.isPoint() ) {
727 setting.t1 = info->selectedRegion.t1();
728 } else if( fileIndex < numLabels - 1 ) {
729 // Use start of next label as end
730 const LabelStruct *info1 = labels->GetLabel(fileIndex+1);
731 setting.t1 = info1->selectedRegion.t0();
732 } else {
733 setting.t1 = tracks.GetEndTime();
734 }
735
736 if( name.empty() )
737 name = _("untitled");
738
739 // store title of label to use in tags
740 title = name;
741
742 // Numbering files...
743 if( !byName ) {
744 name.Printf(wxT("%s-%02d"), prefix, fileIndex + 1);
745 } else if( addNumber ) {
746 // Following discussion with GA, always have 2 digits
747 // for easy file-name sorting (on Windows)
748 name.Prepend(wxString::Format(wxT("%02d-"), fileIndex + 1));
749 }
751
752 setting.filename.SetName(name);
753 {
754 // FIXME: TRAP_ERR User could have given an illegal filename prefix.
755 // in that case we should tell them, not fail silently.
756 wxASSERT(setting.filename.IsOk()); // burp if file name is broke
757
758 // Make sure the (final) file name is unique within the set of exports
759 FileNames::MakeNameUnique(otherNames, setting.filename);
760
761 /* do the metadata for this file */
762 // copy project metadata to start with
763 if (exportSettings.empty())
764 {
765 setting.tags = Tags::Get( mProject );
766 setting.tags.LoadDefaults();
767 }
768 else
769 setting.tags = exportSettings.back().tags;
770 // over-ride with values
771 setting.tags.SetTag(TAG_TITLE, title);
772 setting.tags.SetTag(TAG_TRACK, fileIndex+1);
773 }
774
775 /* add the settings to the array of settings to be used for export */
776 exportSettings.push_back(setting);
777
778 fileIndex++; // next label, count up one
779 }
780 std::swap(mExportSettings, exportSettings);
781}
782
783void ExportAudioDialog::UpdateTrackExportSettings(const ExportPlugin& plugin, int formatIndex, bool byName, bool addNumber,
784 const wxString& prefix)
785{
787
788 bool anySolo = !(( tracks.Any<const WaveTrack>() + &WaveTrack::GetSolo ).empty());
789
790 auto waveTracks = tracks.Any<WaveTrack>() -
792
793 const auto numWaveTracks = waveTracks.size();
794
795 auto fileIndex = 0; // track counter
796 FilePaths otherNames;
797 auto formatInfo = plugin.GetFormatInfo(formatIndex);
798 std::vector<ExportSetting> exportSettings; // dynamic array we will use to store the
799 // settings needed to do the exports with in
800 exportSettings.reserve(numWaveTracks); // Allocate some guessed space to use.
801 ExportSetting setting; // the current batch of settings
802 setting.filename.SetPath(mExportOptionsPanel->GetPath());
803 setting.filename.SetExt(wxFileName{mExportOptionsPanel->GetFullName()}.GetExt());
804
805 wxString name; // used to hold file name whilst we mess with it
806 wxString title; // un-messed-with title of file for tagging with
807
808 const auto skipSilenceAtBeginning = mSkipSilenceAtBeginning->GetValue();
809
810 /* Examine all tracks in turn, collecting export information */
811 for (auto tr : waveTracks) {
812
813 // Get the times for the track
814 setting.t0 = skipSilenceAtBeginning ? tr->GetStartTime() : 0;
815 setting.t1 = tr->GetEndTime();
816
818 // Get name and title
819 title = tr->GetName();
820 if( title.empty() )
821 title = _("untitled");
822
823 if (byName) {
824 name = title;
825 if (addNumber) {
826 name.Prepend(
827 wxString::Format(wxT("%02d-"), fileIndex+1));
828 }
829 }
830 else {
831 name = (wxString::Format(wxT("%s-%02d"), prefix, fileIndex+1));
832 }
833
835 // store sanitised and user checked name in object
836 setting.filename.SetName(name);
837
838 // FIXME: TRAP_ERR User could have given an illegal track name.
839 // in that case we should tell them, not fail silently.
840 wxASSERT(setting.filename.IsOk()); // burp if file name is broke
841
842 // Make sure the (final) file name is unique within the set of exports
843 FileNames::MakeNameUnique(otherNames, setting.filename);
844
845 /* do the metadata for this file */
846 // copy project metadata to start with
847
848 if(exportSettings.empty())
849 {
850 setting.tags = Tags::Get( mProject );
851 setting.tags.LoadDefaults();
852 }
853 else
854 setting.tags = exportSettings.back().tags;
855
856 // over-ride with values
857 setting.tags.SetTag(TAG_TITLE, title);
858 setting.tags.SetTag(TAG_TRACK, fileIndex + 1);
859
860 exportSettings.push_back(setting);
861
862 fileIndex++; // next track, count up one
863 }
864 std::swap(mExportSettings, exportSettings);
865}
866
868 int formatIndex,
869 const ExportProcessor::Parameters& parameters,
870 FilePaths& exporterFiles)
871{
872 auto ok = ExportResult::Success; // did it work?
873 /* Go round again and do the exporting (so this run is slow but
874 * non-interactive) */
875 for(auto& activeSetting : mExportSettings)
876 {
877 /* get the settings to use for the export from the array */
878 // Bug 1440 fix.
879 if( activeSetting.filename.GetName().empty() )
880 continue;
881
882 // Export it
883 ok = DoExport(plugin, formatIndex, parameters, activeSetting.filename, activeSetting.channels,
884 activeSetting.t0, activeSetting.t1, false, activeSetting.tags, exporterFiles);
885
886 if (ok == ExportResult::Stopped) {
887 AudacityMessageDialog dlgMessage(
888 nullptr,
889 XO("Continue to export remaining files?"),
890 XO("Export"),
891 wxYES_NO | wxNO_DEFAULT | wxICON_WARNING);
892 if (dlgMessage.ShowModal() != wxID_YES ) {
893 // User decided not to continue - bail out!
894 break;
895 }
896 }
897 else if (ok != ExportResult::Success) {
898 break;
899 }
900 }
901
902 return ok;
903}
904
906 int formatIndex,
907 const ExportProcessor::Parameters& parameters,
908 FilePaths& exporterFiles)
909{
911
912 bool anySolo =
913 !((tracks.Any<const WaveTrack>() + &WaveTrack::GetSolo).empty());
914
915 auto waveTracks = tracks.Any<WaveTrack>() -
917
918 auto& selectionState = SelectionState::Get( mProject );
919
920 /* Remember which tracks were selected, and set them to deselected */
921 SelectionStateChanger changer{ selectionState, tracks };
922 for (auto tr : tracks.Selected<WaveTrack>())
923 tr->SetSelected(false);
924
925 auto ok = ExportResult::Success;
926
927 int count = 0;
928 for (auto tr : waveTracks) {
929
930 wxLogDebug( "Get setting %i", count );
931 /* get the settings to use for the export from the array */
932 auto& activeSetting = mExportSettings[count];
933 if( activeSetting.filename.GetName().empty() ){
934 count++;
935 continue;
936 }
937
938 /* Select the track */
939 SelectionStateChanger changer2{ selectionState, tracks };
940 tr->SetSelected(true);
941
942 // Export the data. "channels" are per track.
943 ok = DoExport(plugin, formatIndex, parameters, activeSetting.filename, activeSetting.channels,
944 activeSetting.t0, activeSetting.t1, true, activeSetting.tags, exporterFiles);
945
946 if (ok == ExportResult::Stopped) {
947 AudacityMessageDialog dlgMessage(
948 nullptr,
949 XO("Continue to export remaining files?"),
950 XO("Export"),
951 wxYES_NO | wxNO_DEFAULT | wxICON_WARNING);
952 if (dlgMessage.ShowModal() != wxID_YES ) {
953 // User decided not to continue - bail out!
954 break;
955 }
956 }
957
958 else if (ok != ExportResult::Success) {
959 break;
960 }
961 // increment export counter
962 count++;
963 }
964
965
966 return ok ;
967}
968
970 int formatIndex,
971 const ExportProcessor::Parameters& parameters,
972 const wxFileName& filename,
973 int channels,
974 double t0, double t1, bool selectedOnly,
975 const Tags& tags,
976 FilePaths& exportedFiles)
977{
978 wxFileName name;
979
980 wxLogDebug(wxT("Doing multiple Export: File name \"%s\""), (filename.GetFullName()));
981 wxLogDebug(wxT("Channels: %i, Start: %lf, End: %lf "), channels, t0, t1);
982 if (selectedOnly)
983 wxLogDebug(wxT("Selected Region Only"));
984 else
985 wxLogDebug(wxT("Whole Project"));
986
987 wxFileName backup;
988 if (mOverwriteExisting->GetValue()) {
989 name = filename;
990 backup.Assign(name);
991
992 int suffix = 0;
993 do {
994 backup.SetName(name.GetName() +
995 wxString::Format(wxT("%d"), suffix));
996 ++suffix;
997 }
998 while (backup.FileExists());
999 ::wxRenameFile(filename.GetFullPath(), backup.GetFullPath());
1000 }
1001 else {
1002 name = filename;
1003 int i = 2;
1004 wxString base(name.GetName());
1005 while (name.FileExists()) {
1006 name.SetName(wxString::Format(wxT("%s-%d"), base, i++));
1007 }
1008 }
1009
1010 bool success{false};
1011 const wxString fullPath{name.GetFullPath()};
1012
1013 auto cleanup = finally( [&] {
1014 if (backup.IsOk()) {
1015 if ( success )
1016 // Remove backup
1017 ::wxRemoveFile(backup.GetFullPath());
1018 else {
1019 // Restore original
1020 ::wxRemoveFile(fullPath);
1021 ::wxRenameFile(backup.GetFullPath(), fullPath);
1022 }
1023 }
1024 else {
1025 if ( ! success )
1026 // Remove any new, and only partially written, file.
1027 ::wxRemoveFile(fullPath);
1028 }
1029 } );
1030
1031 auto result = ExportResult::Error;
1033 {
1034 result = ExportProgressUI::Show(ExportTaskBuilder{}.SetPlugin(&plugin, formatIndex)
1035 .SetParameters(parameters)
1036 .SetRange(t0, t1, selectedOnly)
1037 .SetTags(&tags)
1038 .SetNumChannels(channels)
1039 .SetFileName(fullPath)
1040 .SetSampleRate(mExportOptionsPanel->GetSampleRate())
1041 .Build(mProject));
1042 });
1043
1044 success = result == ExportResult::Success || result == ExportResult::Stopped;
1045
1046 if(success)
1047 exportedFiles.push_back(fullPath);
1048
1049 return result;
1050}
1051
1052
wxT("CloseDown"))
int AudacityMessageBox(const TranslatableString &message, const TranslatableString &caption, long style, wxWindow *parent, int x, int y)
END_EVENT_TABLE()
int min(int a, int b)
EVT_BUTTON(wxID_NO, DependencyDialog::OnNo) EVT_BUTTON(wxID_YES
const TranslatableString name
Definition: Distortion.cpp:76
void ShowExportErrorDialog(const TranslatableString &message, const TranslatableString &caption, bool allowReporting)
Definition: Export.cpp:144
ExportResult
Definition: ExportTypes.h:24
XO("Cut/Copy/Paste")
wxString FileExtension
File extension, not including any leading dot.
Definition: Identifier.h:224
#define _(s)
Definition: Internat.h:73
EVT_COMMAND(wxID_ANY, EVT_FREQUENCYTEXTCTRL_UPDATED, LabelDialog::OnFreqUpdate) LabelDialog
Definition: LabelDialog.cpp:89
#define safenew
Definition: MemoryX.h:10
static const auto title
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
#define TAG_TRACK
Definition: Tags.h:61
#define TAG_TITLE
Definition: Tags.h:58
static TranslatableStrings names
Definition: TagsEditor.cpp:153
const auto tracks
const auto project
#define S(N)
Definition: ToChars.cpp:64
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
This specialization of Setting for bool adds a Toggle method to negate the saved value.
Definition: Prefs.h:346
double GetStartTime() const
Get the minimum of Start() values of intervals, or 0 when none.
Definition: Channel.cpp:50
wxString Read() const
Definition: Prefs.cpp:388
ExportResult DoExportSplitByTracks(const ExportPlugin &plugin, int formatIndex, const ExportProcessor::Parameters &parameters, FilePaths &exporterFiles)
void OnEditMetadata(wxCommandEvent &event)
void OnIncludeAudioBeforeFirstLabelChange(wxCommandEvent &)
wxRadioButton * mRangeSplit
wxCheckBox * mSkipSilenceAtBeginning
wxCheckBox * mOverwriteExisting
void UpdateLabelExportSettings(const ExportPlugin &plugin, int formatIndex, bool byName, bool addNumber, const wxString &prefix)
void OnHelp(wxCommandEvent &event)
wxRadioButton * mRangeSelection
void OnExport(wxCommandEvent &event)
void OnTrimBlankSpaceBeforeFirstClip(wxCommandEvent &)
void PopulateOrExchange(ShuttleGui &S)
ExportResult DoExport(const ExportPlugin &plugin, int formatIndex, const ExportProcessor::Parameters &parameters, const wxFileName &filename, int channels, double t0, double t1, bool selectedOnly, const Tags &tags, FilePaths &exportedFiles)
void OnSplitNamePolicyChange(wxCommandEvent &event)
void OnExportRangeChange(wxCommandEvent &event)
void OnFileNamePrefixChange(wxCommandEvent &)
wxRadioButton * mSplitUseName
bool Show(bool show=true) override
ExportResult DoExportSplitByLabels(const ExportPlugin &plugin, int formatIndex, const ExportProcessor::Parameters &parameters, FilePaths &exporterFiles)
wxRadioButton * mSplitUseNumAndName
wxRadioButton * mSplitByTracks
ExportFilePanel * mExportOptionsPanel
~ExportAudioDialog() override
wxRadioButton * mSplitByLabels
void UpdateTrackExportSettings(const ExportPlugin &plugin, int formatIndex, bool byName, bool addNumber, const wxString &prefix)
wxTextCtrl * mSplitFileNamePrefix
void OnFormatChange(wxCommandEvent &event)
AudacityProject & mProject
void OnSplitModeChange(wxCommandEvent &event)
wxCheckBox * mIncludeAudioBeforeFirstLabel
std::vector< ExportSetting > mExportSettings
wxRadioButton * mRangeProject
wxRadioButton * mSplitUseNumAndPrefix
const ExportPlugin * GetPlugin() const
int GetSampleRate() const
wxString GetPath() const
void SetCustomMappingEnabled(bool enabled)
wxString GetFullName()
MixerOptions::Downmix * GetMixerSpec() const
int GetFormat() const
int GetChannels() const
std::optional< ExportProcessor::Parameters > GetParameters() const
Main class to control the export function.
virtual FormatInfo GetFormatInfo(int index) const =0
Returns FormatInfo structure for given index if it's valid, or a default one. FormatInfo::format isn'...
std::vector< std::tuple< ExportOptionID, ExportValue > > Parameters
Definition: ExportPlugin.h:93
ExportTaskBuilder & SetPlugin(const ExportPlugin *plugin, int format=0) noexcept
Definition: Export.cpp:59
ExportTaskBuilder & SetMixerSpec(MixerOptions::Downmix *mixerSpec) noexcept
Definition: Export.cpp:66
ExportTask Build(AudacityProject &project)
Definition: Export.cpp:84
ExportTaskBuilder & SetParameters(ExportProcessor::Parameters parameters) noexcept
Definition: Export.cpp:47
ExportTaskBuilder & SetNumChannels(unsigned numChannels) noexcept
Definition: Export.cpp:53
ExportTaskBuilder & SetSampleRate(double sampleRate) noexcept
Definition: Export.cpp:72
ExportTaskBuilder & SetFileName(const wxFileName &filename)
Definition: Export.cpp:33
ExportTaskBuilder & SetRange(double t0, double t1, bool selectedOnly=false) noexcept
Definition: Export.cpp:39
static TrackIterRange< const WaveTrack > FindExportWaveTracks(const TrackList &tracks, bool selectedOnly)
Definition: ExportUtils.cpp:23
static bool HasSelectedAudio(const AudacityProject &project)
Definition: ExportUtils.cpp:33
static void RegisterExportHook(ExportHook hook, Priority=DEFAULT_EXPORT_HOOK_PRIORITY)
Definition: ExportUtils.cpp:67
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:231
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:81
static ImportExport & Get(AudacityProject &project)
void SetPreferredExportRate(double rate)
double GetPreferredExportRate() const
static constexpr double InvalidRate
Definition: ImportExport.h:21
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:215
A LabelStruct holds information for ONE label in a LabelTrack.
Definition: LabelTrack.h:37
wxString title
Definition: LabelTrack.h:78
SelectedRegion selectedRegion
Definition: LabelTrack.h:77
A LabelTrack is a Track that holds labels (LabelStruct).
Definition: LabelTrack.h:95
bool GetNotSolo() const
Definition: PlayableTrack.h:50
double t1() const
bool isPoint() const
double t0() const
static SelectionState & Get(AudacityProject &project)
bool Write(const T &value)
Write value to config and return true if successful.
Definition: Prefs.h:259
bool Read(T *pVar) const
overload of Read returning a boolean that is true if the value was previously defined *‍/
Definition: Prefs.h:207
Derived from ShuttleGuiBase, an Audacity specific class for shuttling data to and from GUI.
Definition: ShuttleGui.h:640
Specialization of Setting for strings.
Definition: Prefs.h:370
Derived from ExpandingToolBar, this dialog allows editing of Tags.
Definition: TagsEditor.h:22
static AUDACITY_DLL_API bool EditProjectMetadata(AudacityProject &project, const TranslatableString &title, const TranslatableString &shortUndoDescription)
ID3 Tags (for MP3)
Definition: Tags.h:73
static Tags & Get(AudacityProject &project)
Definition: Tags.cpp:214
void SetTag(const wxString &name, const wxString &value, const bool bSpecialTag=false)
Definition: Tags.cpp:431
void LoadDefaults()
Definition: Tags.cpp:266
double GetEndTime() const
Return the greatest end time of the tracks, or 0 when no tracks.
Definition: Track.cpp:784
auto Any() -> TrackIterRange< TrackType >
Definition: Track.h:950
static TrackList & Get(AudacityProject &project)
Definition: Track.cpp:314
static ViewInfo & Get(AudacityProject &project)
Definition: ViewInfo.cpp:235
A Track that contains audio waveform data.
Definition: WaveTrack.h:203
bool GetMute() const override
May vary asynchronously.
Definition: WaveTrack.cpp:2318
bool GetSolo() const override
May vary asynchronously.
Definition: WaveTrack.cpp:2323
An alternative to using wxWindowAccessible, which in wxWidgets 3.1.1 contained GetParent() which was ...
Extend wxArrayString with move operations and construction and insertion fromstd::initializer_list.
IMPORT_EXPORT_API ExportResult Show(ExportTask exportTask)
void ExceptionWrappedCall(Callable callable)
FILES_API void MakeNameUnique(FilePaths &otherNames, wxFileName &newName)
FILES_API FilePath FindDefaultPath(Operation op)
void swap(std::unique_ptr< Alg_seq > &a, std::unique_ptr< Alg_seq > &b)
Definition: NoteTrack.cpp:628
const char * begin(const char *str) noexcept
Definition: StringUtils.h:101
A private class used to store the information needed to do an export.