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