Audacity 3.2.0
ExportCL.cpp
Go to the documentation of this file.
1/**********************************************************************
2
3 Audacity: A Digital Audio Editor
4
5 ExportCL.cpp
6
7 Joshua Haberman
8
9 This code allows Audacity to export data by piping it to an external
10 program.
11
12**********************************************************************/
13
14#include "ProjectRate.h"
15
16#include <thread>
17
18#include <wx/app.h>
19#include <wx/cmdline.h>
20#include <wx/combobox.h>
21#include <wx/button.h>
22#include <wx/log.h>
23#include <wx/process.h>
24#include <wx/sizer.h>
25#include <wx/textctrl.h>
26#if defined(__WXMSW__)
27#include <wx/msw/registry.h> // for wxRegKey
28#endif
29
30#include "FileNames.h"
31#include "Export.h"
32
33#include "Mix.h"
34#include "Prefs.h"
35#include "SelectFile.h"
36#include "ShuttleGui.h"
37#include "Tags.h"
38#include "Track.h"
39#include "float_cast.h"
40#include "widgets/FileHistory.h"
41#include "wxPanelWrapper.h"
42#include "widgets/Warning.h"
43#include "wxFileNameWrapper.h"
44#include "BasicUI.h"
45
46#include "ExportOptionsEditor.h"
48#include "ExportPluginHelpers.h"
50
51#ifdef USE_LIBID3TAG
52 #include <id3tag.h>
53 extern "C" {
54 struct id3_frame *id3_frame_new(char const *);
55 }
56#endif
57
58namespace
59{
60
61void Drain(wxInputStream *s, wxString *o)
62{
63 while (s->CanRead()) {
64 char buffer[4096];
65
66 s->Read(buffer, WXSIZEOF(buffer) - 1);
67 buffer[s->LastRead()] = wxT('\0');
68 *o += LAT1CTOWX(buffer);
69 }
70}
71
73{
74#if defined(__WXMSW__)
75 wxString opath;
76
78 {
79 // Give Windows a chance at finding lame command in the default location.
80 wxString paths[] = {wxT("HKEY_LOCAL_MACHINE\\Software\\Lame for Audacity"),
81 wxT("HKEY_LOCAL_MACHINE\\Software\\FFmpeg for Audacity")};
82 wxString npath;
83 wxRegKey reg;
84
85 wxGetEnv(wxT("PATH"), &opath);
86 npath = opath;
87
88 for (int i = 0; i < WXSIZEOF(paths); i++) {
89 reg.SetName(paths[i]);
90
91 if (reg.Exists()) {
92 wxString ipath;
93 reg.QueryValue(wxT("InstallPath"), ipath);
94 if (!ipath.empty()) {
95 npath += wxPATH_SEP + ipath;
96 }
97 }
98 }
99
100 wxSetEnv(wxT("PATH"),npath);
101 };
102
104 {
105 if (!opath.empty())
106 {
107 wxSetEnv(wxT("PATH"),opath);
108 }
109 }
110#endif
111};
112
113//----------------------------------------------------------------------------
114// ExportCLProcess
115//----------------------------------------------------------------------------
116
117class ExportCLProcess final : public wxProcess
118{
119public:
120 ExportCLProcess(wxString *output)
121 {
122#if defined(__WXMAC__)
123 // Don't want to crash on broken pipe
124 signal(SIGPIPE, SIG_IGN);
125#endif
126
127 mOutput = output;
128 mActive = true;
129 mStatus = -555;
130 Redirect();
131 }
132
133 bool IsActive() const
134 {
135 return mActive;
136 }
137
138 void OnTerminate(int WXUNUSED( pid ), int status) override
139 {
140 Drain(GetInputStream(), mOutput);
141 Drain(GetErrorStream(), mOutput);
142
143 mStatus = status;
144 mActive = false;
145 }
146
147 int GetStatus() const
148 {
149 return mStatus;
150 }
151
152private:
153 wxString *mOutput;
156};
157
158}
159
160enum : int {
164
165const std::vector<ExportOption> CLOptions {
166 { CLOptionIDCommand, {}, std::string() },
167 { CLOptionIDShowOutput, {}, false }
168};
169
171 : public ExportOptionsEditor
173{
174 wxString mCommand {wxT("lame - \"%f\"")};
175 bool mShowOutput {false};
176 bool mInitialized {false};
177public:
178
180
181 void PopulateUI(ShuttleGui& S) override
182 {
183 if(!mInitialized)
184 {
185 mHistory.Load(*gPrefs, wxT("/FileFormats/ExternalProgramHistory"));
186
187 if (mHistory.empty()) {
188 mHistory.Append(wxT("ffmpeg -i - \"%f.opus\""));
189 mHistory.Append(wxT("ffmpeg -i - \"%f.wav\""));
190 mHistory.Append(wxT("ffmpeg -i - \"%f\""));
191 mHistory.Append(wxT("lame - \"%f\""));
192 }
193
194 if(!mCommand.empty())
196
197 mInitialized = true;
198 }
199
200 mParent = wxGetTopLevelParent(S.GetParent());
201
203 auto cmd = cmds[0];
204
205 S.StartVerticalLay();
206 {
207 S.StartHorizontalLay(wxEXPAND);
208 {
209 S.SetSizerProportion(1);
210 S.StartMultiColumn(3, wxEXPAND);
211 {
212 S.SetStretchyCol(1);
213 mCommandBox = S.AddCombo(XXO("Command:"),
214 cmd,
215 cmds);
216 mCommandBox->Bind(wxEVT_TEXT, [this](wxCommandEvent& event) {
217 mLastCommand = event.GetString();
218 });
219 mLastCommand = mCommandBox->GetValue();
220
221 S.AddButton(XXO("Browse..."), wxALIGN_CENTER_VERTICAL)
222 ->Bind(wxEVT_BUTTON, &ExportOptionsCLEditor::OnBrowse, this);
223
224 S.AddFixedText( {} );
225 S.TieCheckBox(XXO("Show output"), mShowOutput);
226 }
227 S.EndMultiColumn();
228 }
229 S.EndHorizontalLay();
230
231 S.AddTitle(XO(
232 /* i18n-hint: Some programmer-oriented terminology here:
233 "Data" refers to the sound to be exported, "piped" means sent,
234 and "standard in" means the default input stream that the external program,
235 named by %f, will read. And yes, it's %f, not %s -- this isn't actually used
236 in the program as a format string. Keep %f unchanged. */
237 "Data will be piped to standard in. \"%f\" uses the file name in the export window."), 250);
238 }
239 S.EndVerticalLay();
240 }
241
242 static bool IsValidCommand(const wxString& command)
243 {
244 wxArrayString argv = wxCmdLineParser::ConvertStringToArgs(command,
245#if defined(__WXMSW__)
246 wxCMD_LINE_SPLIT_DOS
247#else
248 wxCMD_LINE_SPLIT_UNIX
249#endif
250 );
251
252 if (argv.empty()) {
254 XO("Warning"),
255 XO("Program name appears to be missing."),//":745"
256 true);
257 return false;
258 }
259
260 // Normalize the path (makes absolute and resolves variables)
261 wxFileName cmd(argv[0]);
262 cmd.Normalize(wxPATH_NORM_ALL & ~wxPATH_NORM_ABSOLUTE);
263
264 // Just verify the given path exists if it is absolute.
265 if (cmd.IsAbsolute()) {
266 if (!cmd.Exists()) {
267 BasicUI::ShowMessageBox(XO("\"%s\" couldn't be found.").Format(cmd.GetFullPath()),
270 .Caption(XO("Warning")));
271 return false;
272 }
273
274 return true;
275 }
276
277 // Search for the command in the PATH list
278 wxPathList pathlist;
279 pathlist.AddEnvList(wxT("PATH"));
280 wxString path = pathlist.FindAbsoluteValidPath(argv[0]);
281
282 #if defined(__WXMSW__)
283 if (path.empty()) {
284 path = pathlist.FindAbsoluteValidPath(argv[0] + wxT(".exe"));
285 }
286 #endif
287
288 if (path.empty()) {
289 BasicUI::ShowMessageBox(XO("Unable to locate \"%s\" in your path.").Format(cmd.GetFullPath()),
292 .Caption(XO("Warning")));
293 return false;
294 }
295
296 return true;
297 }
298
300 {
302 {
306 return true;
307 }
308 return false;
309 }
310
312 {
313 return {};
314 }
315
316 int GetOptionsCount() const override
317 {
318 return static_cast<int>(CLOptions.size());
319 }
320
321 bool GetOption(int index, ExportOption& option) const override
322 {
323 if(index >= 0 && index < static_cast<int>(CLOptions.size()))
324 {
325 option = CLOptions[index];
326 return true;
327 }
328 return false;
329 }
330
331 bool GetValue(int id, ExportValue& value) const override
332 {
333 if(id == CLOptionIDCommand)
334 {
335 value = std::string(mCommand.ToUTF8());
336 return true;
337 }
338 if(id == CLOptionIDShowOutput)
339 {
340 value = mShowOutput;
341 return true;
342 }
343 return false;
344 }
345
346 bool SetValue(int id, const ExportValue& value) override
347 {
348 if(id == CLOptionIDCommand && std::holds_alternative<std::string>(value))
349 {
350 mCommand = wxString::FromUTF8(*std::get_if<std::string>(&value));
351 return true;
352 }
353 if(id == CLOptionIDShowOutput && std::holds_alternative<bool>(value))
354 {
355 mShowOutput = *std::get_if<bool>(&value);
356 return true;
357 }
358 return false;
359 }
360
361 void Load(const audacity::BasicSettings& config) override
362 {
363 (void)config.Read(wxT("/FileFormats/ExternalProgramExportCommand"), mCommand);
364 (void)config.Read(wxT("/FileFormats/ExternalProgramShowOutput"), mShowOutput);
365 }
366
367 void Store(audacity::BasicSettings& config) const override
368 {
369 config.Write(wxT("/FileFormats/ExternalProgramExportCommand"), mCommand);
370 config.Write(wxT("/FileFormats/ExternalProgramShowOutput"), mShowOutput);
371 }
372
373private:
374
375 void OnBrowse(const wxCommandEvent&)
376 {
377 wxString path;
378 FileExtension ext;
380
381 #if defined(__WXMSW__)
382 ext = wxT("exe");
383 /* i18n-hint files that can be run as programs */
384 type = { XO("Executables"), { ext } };
385 #endif
386
387 path = SelectFile(FileNames::Operation::Open,
388 XO("Find path to command"),
389 wxEmptyString,
390 wxEmptyString,
391 ext,
392 { type },
393 wxFD_OPEN | wxRESIZE_BORDER,
394 mParent);
395 if (path.empty()) {
396 return;
397 }
398
399 if (path.Find(wxT(' ')) != wxNOT_FOUND)
400 path = wxT('"') + path + wxT('"');
401
402 mCommandBox->SetValue(path);
403 mCommandBox->SetInsertionPointEnd();
404 }
405
406 wxWindow* mParent{nullptr};
407 wxComboBox* mCommandBox{nullptr};
408
409 //Caches latest value in mCommandBox.
410 //Currently mCommandBox isn't available from
411 //`TransferDataFromWindow` since parent window is destroyed.
412 wxString mLastCommand;
413
415};
416
418{
419 struct
420 {
422 double t0;
423 double t1;
424 unsigned channels;
425 wxString cmd;
427 std::unique_ptr<Mixer> mixer;
428 wxString output;
429 std::unique_ptr<ExportCLProcess> process;
431public:
432
434 const Parameters& parameters,
435 const wxFileNameWrapper& filename,
436 double t0, double t1, bool selectedOnly,
437 double rate, unsigned channels,
438 MixerOptions::Downmix* mixerSpec,
439 const Tags* tags) override;
440
441 ExportResult Process(ExportProcessorDelegate& delegate) override;
442
443private:
444
445 static std::vector<char> GetMetaChunk(const Tags *metadata);
446};
447
448class ExportCL final
449 : public ExportPlugin
450{
451public:
452
453 ExportCL() = default;
454
455 int GetFormatCount() const override;
456 FormatInfo GetFormatInfo(int) const override;
457
458 // Required
459
460 std::unique_ptr<ExportOptionsEditor>
462
463 std::unique_ptr<ExportProcessor> CreateProcessor(int format) const override;
464
465 bool CheckFileName(wxFileName &filename, int format) const override;
466};
467
469{
470 return 1;
471}
472
474{
475 return {
476 wxT("CL"), XO("(external program)"), {""}, 255, false
477 };
478}
479
480std::unique_ptr<ExportOptionsEditor>
482{
483 return std::make_unique<ExportOptionsCLEditor>();
484}
485
486std::unique_ptr<ExportProcessor> ExportCL::CreateProcessor(int format) const
487{
488 return std::make_unique<CLExportProcessor>();
489}
490
492 const Parameters& parameters,
493 const wxFileNameWrapper& fName,
494 double t0, double t1, bool selectionOnly,
495 double sampleRate, unsigned channels,
496 MixerOptions::Downmix* mixerSpec,
497 const Tags* metadata)
498{
499 context.t0 = t0;
500 context.t1 = t1;
501 context.channels = channels;
502
503 ExtendPath ep;
504 long rc;
505
506 const auto path = fName.GetFullPath();
507
508 context.cmd = wxString::FromUTF8(ExportPluginHelpers::GetParameterValue<std::string>(parameters, CLOptionIDCommand));
510
511 // Bug 2178 - users who don't know what they are doing will
512 // now get a file extension of .wav appended to their ffmpeg filename
513 // and therefore ffmpeg will be able to choose a file type.
514 if( context.cmd == wxT("ffmpeg -i - \"%f\"") && !fName.HasExt())
515 context.cmd.Replace( "%f", "%f.wav" );
516 context.cmd.Replace(wxT("%f"), path);
517
518 // Kick off the command
519 context.process = std::make_unique<ExportCLProcess>(&context.output);
520 auto& process = *context.process;
521
522 rc = wxExecute(context.cmd, wxEXEC_ASYNC, &process);
523 if (!rc) {
524 process.Detach();
525 process.CloseOutput();
526 throw ExportException(XO("Cannot export audio to %s")
527 .Format( path )
528 .Translation());
529 }
530
531 // Turn off logging to prevent broken pipe messages
532 wxLogNull nolog;
533
534 // establish parameters
535 int rate = lrint(sampleRate);
536 const size_t maxBlockLen = 44100 * 5;
537 unsigned long totalSamples = lrint((t1 - t0) * rate);
538 unsigned long sampleBytes = totalSamples * channels * SAMPLE_SIZE(floatSample);
539
540 wxOutputStream *os = process.GetOutputStream();
541
542 // RIFF header
543 struct {
544 char riffID[4]; // "RIFF"
545 wxUint32 riffLen; // basically the file len - 8
546 char riffType[4]; // "WAVE"
547 } riff;
548
549 // format chunk */
550 struct {
551 char fmtID[4]; // "fmt " */
552 wxUint32 formatChunkLen; // (format chunk len - first two fields) 16 in our case
553 wxUint16 formatTag; // 1 for PCM
554 wxUint16 channels;
555 wxUint32 sampleRate;
556 wxUint32 avgBytesPerSec; // sampleRate * blockAlign
557 wxUint16 blockAlign; // bitsPerSample * channels (assume bps % 8 = 0)
558 wxUint16 bitsPerSample;
559 } fmt;
560
561 // id3 chunk header
562 struct {
563 char id3ID[4]; // "id3 "
564 wxUint32 id3Len; // length of metadata in bytes
565 } id3;
566
567 // data chunk header
568 struct {
569 char dataID[4]; // "data"
570 wxUint32 dataLen; // length of all samples in bytes
571 } data;
572
573 riff.riffID[0] = 'R';
574 riff.riffID[1] = 'I';
575 riff.riffID[2] = 'F';
576 riff.riffID[3] = 'F';
577 riff.riffLen = wxUINT32_SWAP_ON_BE(sizeof(riff) +
578 sizeof(fmt) +
579 sizeof(data) +
580 sampleBytes -
581 8);
582 riff.riffType[0] = 'W';
583 riff.riffType[1] = 'A';
584 riff.riffType[2] = 'V';
585 riff.riffType[3] = 'E';
586
587 fmt.fmtID[0] = 'f';
588 fmt.fmtID[1] = 'm';
589 fmt.fmtID[2] = 't';
590 fmt.fmtID[3] = ' ';
591 fmt.formatChunkLen = wxUINT32_SWAP_ON_BE(16);
592 fmt.formatTag = wxUINT16_SWAP_ON_BE(3);
593 fmt.channels = wxUINT16_SWAP_ON_BE(channels);
594 fmt.sampleRate = wxUINT32_SWAP_ON_BE(rate);
595 fmt.bitsPerSample = wxUINT16_SWAP_ON_BE(SAMPLE_SIZE(floatSample) * 8);
596 fmt.blockAlign = wxUINT16_SWAP_ON_BE(fmt.bitsPerSample * fmt.channels / 8);
597 fmt.avgBytesPerSec = wxUINT32_SWAP_ON_BE(fmt.sampleRate * fmt.blockAlign);
598
599 // Retrieve tags if not given a set
600 if (metadata == nullptr) {
601 metadata = &Tags::Get(project);
602 }
603 const auto metachunk = GetMetaChunk(metadata);
604
605 if (!metachunk.empty()) {
606
607 id3.id3ID[0] = 'i';
608 id3.id3ID[1] = 'd';
609 id3.id3ID[2] = '3';
610 id3.id3ID[3] = ' ';
611 id3.id3Len = wxUINT32_SWAP_ON_BE(metachunk.size());
612 riff.riffLen += sizeof(id3) + metachunk.size();
613 }
614
615 data.dataID[0] = 'd';
616 data.dataID[1] = 'a';
617 data.dataID[2] = 't';
618 data.dataID[3] = 'a';
619 data.dataLen = wxUINT32_SWAP_ON_BE(sampleBytes);
620
621 // write the headers and metadata
622 os->Write(&riff, sizeof(riff));
623 os->Write(&fmt, sizeof(fmt));
624 if (!metachunk.empty()) {
625 os->Write(&id3, sizeof(id3));
626 os->Write(metachunk.data(), metachunk.size());
627 }
628 os->Write(&data, sizeof(data));
629
630 // Mix 'em up
631 const auto &tracks = TrackList::Get( project );
633 tracks,
634 selectionOnly,
635 t0,
636 t1,
637 channels,
638 maxBlockLen,
639 true,
640 rate,
642 mixerSpec);
643
644 context.status = selectionOnly
645 ? XO("Exporting the selected audio using command-line encoder")
646 : XO("Exporting the audio using command-line encoder");
647
648 return true;
649}
650
652{
653 delegate.SetStatusString(context.status);
654 auto& process = *context.process;
655 auto exportResult = ExportResult::Success;
656 {
657 size_t numBytes = 0;
658 constSamplePtr mixed = nullptr;
659
660 wxOutputStream *os = process.GetOutputStream();
661 auto closeIt = finally ( [&] {
662 // Should make the process die, before propagating any exception
663 process.CloseOutput();
664 } );
665
666 // Start piping the mixed data to the command
667 while (exportResult == ExportResult::Success && process.IsActive() && os->IsOk()) {
668 // Capture any stdout and stderr from the command
669 Drain(process.GetInputStream(), &context.output);
670 Drain(process.GetErrorStream(), &context.output);
671
672 // Need to mix another block
673 if (numBytes == 0) {
674 auto numSamples = context.mixer->Process();
675 if (numSamples == 0)
676 break;
677
678 mixed = context.mixer->GetBuffer();
679 numBytes = numSamples * context.channels;
680
681 // Byte-swapping is necessary on big-endian machines, since
682 // WAV files are little-endian
683#if wxBYTE_ORDER == wxBIG_ENDIAN
684 auto buffer = (const float *) mixed;
685 for (int i = 0; i < numBytes; i++) {
686 buffer[i] = wxUINT32_SWAP_ON_BE(buffer[i]);
687 }
688#endif
689 numBytes *= SAMPLE_SIZE(floatSample);
690 }
691
692 // Don't write too much at once...pipes may not be able to handle it
693 size_t bytes = wxMin(numBytes, 4096);
694 numBytes -= bytes;
695
696 while (bytes > 0) {
697 os->Write(mixed, bytes);
698 if (!os->IsOk()) {
699 exportResult = ExportResult::Error;
700 break;
701 }
702 bytes -= os->LastWrite();
703 mixed += os->LastWrite();
704 }
705
706 if(exportResult == ExportResult::Success)
708 delegate, *context.mixer, context.t0, context.t1);
709 }
710 // Done with the progress display
711 }
712
713 // Wait for process to terminate
714 while (process.IsActive()) {
715 using namespace std::chrono;
716 std::this_thread::sleep_for(10ms);
718 }
719
720 // Display output on error or if the user wants to see it
721 if (process.GetStatus() != 0 || context.showOutput) {
722 // TODO use ShowInfoDialog() instead.
723 BasicUI::CallAfter([cmd = context.cmd, output = std::move(context.output)]
724 {
725 wxDialogWrapper dlg(nullptr,
726 wxID_ANY,
727 XO("Command Output"),
728 wxDefaultPosition,
729 wxSize(600, 400),
730 wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER);
731 dlg.SetName();
732
733 ShuttleGui S(&dlg, eIsCreating);
734 S
735 .Style( wxTE_MULTILINE | wxTE_READONLY | wxTE_RICH )
736 .AddTextWindow(cmd + wxT("\n\n") + output);
737 S.StartHorizontalLay(wxALIGN_CENTER, false);
738 {
739 S.Id(wxID_OK).AddButton(XXO("&OK"), wxALIGN_CENTER, true);
740 }
741 dlg.GetSizer()->AddSpacer(5);
742 dlg.Layout();
743 dlg.SetMinSize(dlg.GetSize());
744 dlg.Center();
745
746 dlg.ShowModal();
747 });
748
749 if (process.GetStatus() != 0)
750 exportResult = ExportResult::Error;
751 }
752
753 return exportResult;
754}
755
756
757std::vector<char> CLExportProcessor::GetMetaChunk(const Tags *tags)
758{
759 std::vector<char> buffer;
760
761#ifdef USE_LIBID3TAG
762 struct id3_tag_deleter {
763 void operator () (id3_tag *p) const { if (p) id3_tag_delete(p); }
764 };
765
766 std::unique_ptr<id3_tag, id3_tag_deleter> tp { id3_tag_new() };
767
768 for (const auto &pair : tags->GetRange()) {
769 const auto &n = pair.first;
770 const auto &v = pair.second;
771 const char *name = "TXXX";
772
773 if (n.CmpNoCase(TAG_TITLE) == 0) {
774 name = ID3_FRAME_TITLE;
775 }
776 else if (n.CmpNoCase(TAG_ARTIST) == 0) {
777 name = ID3_FRAME_ARTIST;
778 }
779 else if (n.CmpNoCase(TAG_ALBUM) == 0) {
780 name = ID3_FRAME_ALBUM;
781 }
782 else if (n.CmpNoCase(TAG_YEAR) == 0) {
783 name = ID3_FRAME_YEAR;
784 }
785 else if (n.CmpNoCase(TAG_GENRE) == 0) {
786 name = ID3_FRAME_GENRE;
787 }
788 else if (n.CmpNoCase(TAG_COMMENTS) == 0) {
789 name = ID3_FRAME_COMMENT;
790 }
791 else if (n.CmpNoCase(TAG_TRACK) == 0) {
792 name = ID3_FRAME_TRACK;
793 }
794 else if (n.CmpNoCase(wxT("composer")) == 0) {
795 name = "TCOM";
796 }
797
798 struct id3_frame *frame = id3_frame_new(name);
799
800 if (!n.IsAscii() || !v.IsAscii()) {
801 id3_field_settextencoding(id3_frame_field(frame, 0), ID3_FIELD_TEXTENCODING_UTF_16);
802 }
803 else {
804 id3_field_settextencoding(id3_frame_field(frame, 0), ID3_FIELD_TEXTENCODING_ISO_8859_1);
805 }
806
808 id3_utf8_ucs4duplicate((id3_utf8_t *) (const char *) v.mb_str(wxConvUTF8)) };
809
810 if (strcmp(name, ID3_FRAME_COMMENT) == 0) {
811 // A hack to get around iTunes not recognizing the comment. The
812 // language defaults to XXX and, since it's not a valid language,
813 // iTunes just ignores the tag. So, either set it to a valid language
814 // (which one???) or just clear it. Unfortunately, there's no supported
815 // way of clearing the field, so do it directly.
816 id3_field *f = id3_frame_field(frame, 1);
817 memset(f->immediate.value, 0, sizeof(f->immediate.value));
818 id3_field_setfullstring(id3_frame_field(frame, 3), ucs4.get());
819 }
820 else if (strcmp(name, "TXXX") == 0) {
821 id3_field_setstring(id3_frame_field(frame, 2), ucs4.get());
822
823 ucs4.reset(id3_utf8_ucs4duplicate((id3_utf8_t *) (const char *) n.mb_str(wxConvUTF8)));
824
825 id3_field_setstring(id3_frame_field(frame, 1), ucs4.get());
826 }
827 else {
828 auto addr = ucs4.get();
829 id3_field_setstrings(id3_frame_field(frame, 1), 1, &addr);
830 }
831
832 id3_tag_attachframe(tp.get(), frame);
833 }
834
835 tp->options &= (~ID3_TAG_OPTION_COMPRESSION); // No compression
836
837 // If this version of libid3tag supports it, use v2.3 ID3
838 // tags instead of the newer, but less well supported, v2.4
839 // that libid3tag uses by default.
840#ifdef ID3_TAG_HAS_TAG_OPTION_ID3V2_3
841 tp->options |= ID3_TAG_OPTION_ID3V2_3;
842#endif
843
844 id3_length_t len;
845
846 len = id3_tag_render(tp.get(), 0);
847 if ((len % 2) != 0) {
848 len++; // Length must be even.
849 }
850
851 if (len > 0) {
852 buffer.resize(len);
853 id3_tag_render(tp.get(), (id3_byte_t *) buffer.data());
854 }
855#endif
856
857 return buffer;
858}
859
860bool ExportCL::CheckFileName(wxFileName &filename, int WXUNUSED(format)) const
861{
862 ExtendPath ep;
863
864 if (filename.GetExt().empty()) {
865 if (ShowWarningDialog(NULL,
866 wxT("MissingExtension"),
867 XO("You've specified a file name without an extension. Are you sure?"),
868 true) == wxID_CANCEL) {
869 return false;
870 }
871 }
872 return true;
873}
874
876 []{ return std::make_unique< ExportCL >(); }
877};
wxT("CloseDown"))
Toolkit-neutral facade for basic user interface services.
const TranslatableString name
Definition: Distortion.cpp:76
void ShowExportErrorDialog(const TranslatableString &message, const TranslatableString &caption, bool allowReporting)
Definition: Export.cpp:144
const std::vector< ExportOption > CLOptions
Definition: ExportCL.cpp:165
static ExportPluginRegistry::RegisteredPlugin sRegisteredPlugin
Definition: ExportCL.cpp:875
@ CLOptionIDShowOutput
Definition: ExportCL.cpp:162
@ CLOptionIDCommand
Definition: ExportCL.cpp:161
std::variant< bool, int, double, std::string > ExportValue
A type of option values (parameters) used by exporting plugins.
Definition: ExportTypes.h:38
ExportResult
Definition: ExportTypes.h:24
XO("Cut/Copy/Paste")
XXO("&Cut/Copy/Paste Toolbar")
wxString FileExtension
File extension, not including any leading dot.
Definition: Identifier.h:224
#define LAT1CTOWX(X)
Definition: Internat.h:158
std::unique_ptr< Character[], freer > MallocString
Definition: MemoryX.h:146
audacity::BasicSettings * gPrefs
Definition: Prefs.cpp:68
an object holding per-project preferred sample rate
#define SAMPLE_SIZE(SampleFormat)
Definition: SampleFormat.h:52
const char * constSamplePtr
Definition: SampleFormat.h:58
FilePath SelectFile(FileNames::Operation op, const TranslatableString &message, const FilePath &default_path, const FilePath &default_filename, const FileExtension &default_extension, const FileTypes &fileTypes, int flags, wxWindow *parent)
Definition: SelectFile.cpp:17
#define TAG_TRACK
Definition: Tags.h:61
#define TAG_COMMENTS
Definition: Tags.h:64
#define TAG_GENRE
Definition: Tags.h:63
#define TAG_ALBUM
Definition: Tags.h:60
#define TAG_YEAR
Definition: Tags.h:62
#define TAG_TITLE
Definition: Tags.h:58
#define TAG_ARTIST
Definition: Tags.h:59
const auto tracks
const auto project
#define S(N)
Definition: ToChars.cpp:64
declares abstract base class Track, TrackList, and iterators over TrackList
int ShowWarningDialog(wxWindow *parent, const wxString &internalDialogName, const TranslatableString &message, bool showCancelButton, const TranslatableString &footer)
Definition: Warning.cpp:90
The top-level handle to an Audacity project. It serves as a source of events that other objects can b...
Definition: Project.h:90
ExportResult Process(ExportProcessorDelegate &delegate) override
Definition: ExportCL.cpp:651
TranslatableString status
Definition: ExportCL.cpp:421
unsigned channels
Definition: ExportCL.cpp:424
static std::vector< char > GetMetaChunk(const Tags *metadata)
Definition: ExportCL.cpp:757
std::unique_ptr< Mixer > mixer
Definition: ExportCL.cpp:427
bool Initialize(AudacityProject &project, const Parameters &parameters, const wxFileNameWrapper &filename, double t0, double t1, bool selectedOnly, double rate, unsigned channels, MixerOptions::Downmix *mixerSpec, const Tags *tags) override
Called before start processing.
Definition: ExportCL.cpp:491
std::unique_ptr< ExportCLProcess > process
Definition: ExportCL.cpp:429
struct CLExportProcessor::@141 context
int GetFormatCount() const override
Definition: ExportCL.cpp:468
FormatInfo GetFormatInfo(int) const override
Returns FormatInfo structure for given index if it's valid, or a default one. FormatInfo::format isn'...
Definition: ExportCL.cpp:473
ExportCL()=default
bool CheckFileName(wxFileName &filename, int format) const override
Definition: ExportCL.cpp:860
std::unique_ptr< ExportOptionsEditor > CreateOptionsEditor(int, ExportOptionsEditor::Listener *) const override
Creates format-dependent options editor, that is used to create a valid set of parameters to be used ...
Definition: ExportCL.cpp:481
std::unique_ptr< ExportProcessor > CreateProcessor(int format) const override
Definition: ExportCL.cpp:486
wxComboBox * mCommandBox
Definition: ExportCL.cpp:407
ExportOptionsCLEditor()=default
bool GetValue(int id, ExportValue &value) const override
Definition: ExportCL.cpp:331
int GetOptionsCount() const override
Definition: ExportCL.cpp:316
bool SetValue(int id, const ExportValue &value) override
Definition: ExportCL.cpp:346
SampleRateList GetSampleRateList() const override
Definition: ExportCL.cpp:311
void PopulateUI(ShuttleGui &S) override
Definition: ExportCL.cpp:181
static bool IsValidCommand(const wxString &command)
Definition: ExportCL.cpp:242
void Store(audacity::BasicSettings &config) const override
Definition: ExportCL.cpp:367
bool TransferDataFromWindow() override
Definition: ExportCL.cpp:299
FileHistory mHistory
Definition: ExportCL.cpp:414
bool GetOption(int index, ExportOption &option) const override
Definition: ExportCL.cpp:321
void Load(const audacity::BasicSettings &config) override
Definition: ExportCL.cpp:361
void OnBrowse(const wxCommandEvent &)
Definition: ExportCL.cpp:375
Listener object that is used to report on option changes.
Editor objects are used to retrieve a set of export options, and configure exporting parameters accor...
std::vector< int > SampleRateList
static T GetParameterValue(const ExportProcessor::Parameters &parameters, int id, T defaultValue=T())
static ExportResult UpdateProgress(ExportProcessorDelegate &delegate, Mixer &mixer, double t0, double t1)
Sends progress update to delegate and retrieves state update from it. Typically used inside each expo...
static std::unique_ptr< Mixer > CreateMixer(const TrackList &tracks, bool selectionOnly, double startTime, double stopTime, unsigned numOutChannels, size_t outBufferSize, bool outInterleaved, double outRate, sampleFormat outFormat, MixerOptions::Downmix *mixerSpec)
virtual void SetStatusString(const TranslatableString &str)=0
std::vector< std::tuple< ExportOptionID, ExportValue > > Parameters
Definition: ExportPlugin.h:93
Similar to wxFileHistory, but customized to our needs.
Definition: FileHistory.h:30
const_iterator begin() const
Definition: FileHistory.h:60
bool empty() const
Definition: FileHistory.h:63
void Load(audacity::BasicSettings &config, const wxString &group=wxEmptyString)
void Append(const FilePath &file)
Definition: FileHistory.h:46
const_iterator end() const
Definition: FileHistory.h:61
void Save(audacity::BasicSettings &config)
FILES_API const FileType AllFiles
Definition: FileNames.h:70
Abstract base class used in importing a file.
A matrix of booleans, one row per input channel, column per output.
Definition: MixerOptions.h:32
Derived from ShuttleGuiBase, an Audacity specific class for shuttling data to and from GUI.
Definition: ShuttleGui.h:630
ID3 Tags (for MP3)
Definition: Tags.h:73
Iterators GetRange() const
Definition: Tags.cpp:431
static Tags & Get(AudacityProject &project)
Definition: Tags.cpp:214
static TrackList & Get(AudacityProject &project)
Definition: Track.cpp:347
Holds a msgid for the translation catalog; may also bind format arguments.
void OnTerminate(int WXUNUSED(pid), int status) override
Definition: ExportCL.cpp:138
Base class for objects that provide facility to store data persistently, and access it with string ke...
Definition: BasicSettings.h:31
virtual bool Write(const wxString &key, bool value)=0
virtual bool Read(const wxString &key, bool *value) const =0
Extend wxArrayString with move operations and construction and insertion fromstd::initializer_list.
#define lrint(dbl)
Definition: float_cast.h:169
void CallAfter(Action action)
Schedule an action to be done later, and in the main thread.
Definition: BasicUI.cpp:208
void Yield()
Dispatch waiting events, including actions enqueued by CallAfter.
Definition: BasicUI.cpp:219
MessageBoxResult ShowMessageBox(const TranslatableString &message, MessageBoxOptions options={})
Show a modal message box with either Ok or Yes and No, and optionally Cancel.
Definition: BasicUI.h:277
UTILITY_API const char *const * argv
A copy of argv; responsibility of application startup to assign it.
BuiltinCommandsModule::Registration< CompareAudioCommand > reg
void Drain(wxInputStream *s, wxString *o)
Definition: ExportCL.cpp:61
MessageBoxOptions && Caption(TranslatableString caption_) &&
Definition: BasicUI.h:101
MessageBoxOptions && IconStyle(Icon style) &&
Definition: BasicUI.h:104
A type that provides a description of an exporting option. Isn't allowed to change except non-type re...
Definition: ExportTypes.h:43