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