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