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 mCommandBox->SetMaxSize(wxSize(50,400));
221
222 S.AddButton(XXO("Browse..."), wxALIGN_CENTER_VERTICAL)
223 ->Bind(wxEVT_BUTTON, &ExportOptionsCLEditor::OnBrowse, this);
224
225 S.AddFixedText( {} );
226 S.TieCheckBox(XXO("Show output"), mShowOutput);
227 }
228 S.EndMultiColumn();
229 }
230 S.EndHorizontalLay();
231
232 S.AddTitle(XO(
233 /* i18n-hint: Some programmer-oriented terminology here:
234 "Data" refers to the sound to be exported, "piped" means sent,
235 and "standard in" means the default input stream that the external program,
236 named by %f, will read. And yes, it's %f, not %s -- this isn't actually used
237 in the program as a format string. Keep %f unchanged. */
238 "Data will be piped to standard in. \"%f\" uses the file name in the export window."), 250);
239 }
240 S.EndVerticalLay();
241 }
242
243 static bool IsValidCommand(const wxString& command)
244 {
245 wxArrayString argv = wxCmdLineParser::ConvertStringToArgs(command,
246#if defined(__WXMSW__)
247 wxCMD_LINE_SPLIT_DOS
248#else
249 wxCMD_LINE_SPLIT_UNIX
250#endif
251 );
252
253 if (argv.empty()) {
255 XO("Warning"),
256 XO("Program name appears to be missing."),//":745"
257 true);
258 return false;
259 }
260
261 // Normalize the path (makes absolute and resolves variables)
262 wxFileName cmd(argv[0]);
263 cmd.Normalize(wxPATH_NORM_ALL & ~wxPATH_NORM_ABSOLUTE);
264
265 // Just verify the given path exists if it is absolute.
266 if (cmd.IsAbsolute()) {
267 if (!cmd.Exists()) {
268 BasicUI::ShowMessageBox(XO("\"%s\" couldn't be found.").Format(cmd.GetFullPath()),
271 .Caption(XO("Warning")));
272 return false;
273 }
274
275 return true;
276 }
277
278 // Search for the command in the PATH list
279 wxPathList pathlist;
280 pathlist.AddEnvList(wxT("PATH"));
281 wxString path = pathlist.FindAbsoluteValidPath(argv[0]);
282
283 #if defined(__WXMSW__)
284 if (path.empty()) {
285 path = pathlist.FindAbsoluteValidPath(argv[0] + wxT(".exe"));
286 }
287 #endif
288
289 if (path.empty()) {
290 BasicUI::ShowMessageBox(XO("Unable to locate \"%s\" in your path.").Format(cmd.GetFullPath()),
293 .Caption(XO("Warning")));
294 return false;
295 }
296
297 return true;
298 }
299
301 {
303 {
307 return true;
308 }
309 return false;
310 }
311
313 {
314 return {};
315 }
316
317 int GetOptionsCount() const override
318 {
319 return static_cast<int>(CLOptions.size());
320 }
321
322 bool GetOption(int index, ExportOption& option) const override
323 {
324 if(index >= 0 && index < static_cast<int>(CLOptions.size()))
325 {
326 option = CLOptions[index];
327 return true;
328 }
329 return false;
330 }
331
332 bool GetValue(int id, ExportValue& value) const override
333 {
334 if(id == CLOptionIDCommand)
335 {
336 value = std::string(mCommand.ToUTF8());
337 return true;
338 }
339 if(id == CLOptionIDShowOutput)
340 {
341 value = mShowOutput;
342 return true;
343 }
344 return false;
345 }
346
347 bool SetValue(int id, const ExportValue& value) override
348 {
349 if(id == CLOptionIDCommand && std::holds_alternative<std::string>(value))
350 {
351 mCommand = wxString::FromUTF8(*std::get_if<std::string>(&value));
352 return true;
353 }
354 if(id == CLOptionIDShowOutput && std::holds_alternative<bool>(value))
355 {
356 mShowOutput = *std::get_if<bool>(&value);
357 return true;
358 }
359 return false;
360 }
361
362 void Load(const audacity::BasicSettings& config) override
363 {
364 mCommand = config.Read(wxT("/FileFormats/ExternalProgramExportCommand"), mCommand);
365 mShowOutput = config.Read(wxT("/FileFormats/ExternalProgramShowOutput"), mShowOutput);
366 }
367
368 void Store(audacity::BasicSettings& config) const override
369 {
370 config.Write(wxT("/FileFormats/ExternalProgramExportCommand"), mCommand);
371 config.Write(wxT("/FileFormats/ExternalProgramShowOutput"), mShowOutput);
372 }
373
374private:
375
376 void OnBrowse(const wxCommandEvent&)
377 {
378 wxString path;
379 FileExtension ext;
381
382 #if defined(__WXMSW__)
383 ext = wxT("exe");
384 /* i18n-hint files that can be run as programs */
385 type = { XO("Executables"), { ext } };
386 #endif
387
388 path = SelectFile(FileNames::Operation::Open,
389 XO("Find path to command"),
390 wxEmptyString,
391 wxEmptyString,
392 ext,
393 { type },
394 wxFD_OPEN | wxRESIZE_BORDER,
395 mParent);
396 if (path.empty()) {
397 return;
398 }
399
400 if (path.Find(wxT(' ')) != wxNOT_FOUND)
401 path = wxT('"') + path + wxT('"');
402
403 mCommandBox->SetValue(path);
404 mCommandBox->SetInsertionPointEnd();
405 }
406
407 wxWindow* mParent{nullptr};
408 wxComboBox* mCommandBox{nullptr};
409
410 //Caches latest value in mCommandBox.
411 //Currently mCommandBox isn't available from
412 //`TransferDataFromWindow` since parent window is destroyed.
413 wxString mLastCommand;
414
416};
417
419{
420 struct
421 {
423 double t0;
424 double t1;
425 unsigned channels;
426 wxString cmd;
428 std::unique_ptr<Mixer> mixer;
429 wxString output;
430 std::unique_ptr<ExportCLProcess> process;
432public:
433
435 const Parameters& parameters,
436 const wxFileNameWrapper& filename,
437 double t0, double t1, bool selectedOnly,
438 double rate, unsigned channels,
439 MixerOptions::Downmix* mixerSpec,
440 const Tags* tags) override;
441
442 ExportResult Process(ExportProcessorDelegate& delegate) override;
443
444private:
445
446 static std::vector<char> GetMetaChunk(const Tags *metadata);
447};
448
449class ExportCL final
450 : public ExportPlugin
451{
452public:
453
454 ExportCL() = default;
455
456 int GetFormatCount() const override;
457 FormatInfo GetFormatInfo(int) const override;
458
459 // Required
460
461 std::unique_ptr<ExportOptionsEditor>
463
464 std::unique_ptr<ExportProcessor> CreateProcessor(int format) const override;
465
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
632 project, selectionOnly, t0, t1, channels, maxBlockLen, true, rate,
633 floatSample, mixerSpec);
634
635 context.status = selectionOnly
636 ? XO("Exporting the selected audio using command-line encoder")
637 : XO("Exporting the audio using command-line encoder");
638
639 return true;
640}
641
643{
644 delegate.SetStatusString(context.status);
645 auto& process = *context.process;
646 auto exportResult = ExportResult::Success;
647 {
648 size_t numBytes = 0;
649 constSamplePtr mixed = nullptr;
650
651 wxOutputStream *os = process.GetOutputStream();
652 auto closeIt = finally ( [&] {
653 // Should make the process die, before propagating any exception
654 process.CloseOutput();
655 } );
656
657 // Start piping the mixed data to the command
658 while (exportResult == ExportResult::Success && process.IsActive() && os->IsOk()) {
659 // Capture any stdout and stderr from the command
660 Drain(process.GetInputStream(), &context.output);
661 Drain(process.GetErrorStream(), &context.output);
662
663 // Need to mix another block
664 if (numBytes == 0) {
665 auto numSamples = context.mixer->Process();
666 if (numSamples == 0)
667 break;
668
669 mixed = context.mixer->GetBuffer();
670 numBytes = numSamples * context.channels;
671
672 // Byte-swapping is necessary on big-endian machines, since
673 // WAV files are little-endian
674#if wxBYTE_ORDER == wxBIG_ENDIAN
675 auto buffer = (const float *) mixed;
676 for (int i = 0; i < numBytes; i++) {
677 buffer[i] = wxUINT32_SWAP_ON_BE(buffer[i]);
678 }
679#endif
680 numBytes *= SAMPLE_SIZE(floatSample);
681 }
682
683 // Don't write too much at once...pipes may not be able to handle it
684 size_t bytes = wxMin(numBytes, 4096);
685 numBytes -= bytes;
686
687 while (bytes > 0) {
688 os->Write(mixed, bytes);
689 if (!os->IsOk()) {
690 exportResult = ExportResult::Error;
691 break;
692 }
693 bytes -= os->LastWrite();
694 mixed += os->LastWrite();
695 }
696
697 if(exportResult == ExportResult::Success)
699 delegate, *context.mixer, context.t0, context.t1);
700 }
701 // Done with the progress display
702 }
703
704 // Wait for process to terminate
705 while (process.IsActive()) {
706 using namespace std::chrono;
707 std::this_thread::sleep_for(10ms);
709 }
710
711 // Display output on error or if the user wants to see it
712 if (process.GetStatus() != 0 || context.showOutput) {
713 // TODO use ShowInfoDialog() instead.
714 BasicUI::CallAfter([cmd = context.cmd, output = std::move(context.output)]
715 {
716 wxDialogWrapper dlg(nullptr,
717 wxID_ANY,
718 XO("Command Output"),
719 wxDefaultPosition,
720 wxSize(600, 400),
721 wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER);
722 dlg.SetName();
723
724 ShuttleGui S(&dlg, eIsCreating);
725 S
726 .Style( wxTE_MULTILINE | wxTE_READONLY | wxTE_RICH )
727 .AddTextWindow(cmd + wxT("\n\n") + output);
728 S.StartHorizontalLay(wxALIGN_CENTER, false);
729 {
730 S.Id(wxID_OK).AddButton(XXO("&OK"), wxALIGN_CENTER, true);
731 }
732 dlg.GetSizer()->AddSpacer(5);
733 dlg.Layout();
734 dlg.SetMinSize(dlg.GetSize());
735 dlg.Center();
736
737 dlg.ShowModal();
738 });
739
740 if (process.GetStatus() != 0)
741 exportResult = ExportResult::Error;
742 }
743
744 return exportResult;
745}
746
747
748std::vector<char> CLExportProcessor::GetMetaChunk(const Tags *tags)
749{
750 std::vector<char> buffer;
751
752#ifdef USE_LIBID3TAG
753 struct id3_tag_deleter {
754 void operator () (id3_tag *p) const { if (p) id3_tag_delete(p); }
755 };
756
757 std::unique_ptr<id3_tag, id3_tag_deleter> tp { id3_tag_new() };
758
759 for (const auto &pair : tags->GetRange()) {
760 const auto &n = pair.first;
761 const auto &v = pair.second;
762 const char *name = "TXXX";
763
764 if (n.CmpNoCase(TAG_TITLE) == 0) {
765 name = ID3_FRAME_TITLE;
766 }
767 else if (n.CmpNoCase(TAG_ARTIST) == 0) {
768 name = ID3_FRAME_ARTIST;
769 }
770 else if (n.CmpNoCase(TAG_ALBUM) == 0) {
771 name = ID3_FRAME_ALBUM;
772 }
773 else if (n.CmpNoCase(TAG_YEAR) == 0) {
774 name = ID3_FRAME_YEAR;
775 }
776 else if (n.CmpNoCase(TAG_GENRE) == 0) {
777 name = ID3_FRAME_GENRE;
778 }
779 else if (n.CmpNoCase(TAG_COMMENTS) == 0) {
780 name = ID3_FRAME_COMMENT;
781 }
782 else if (n.CmpNoCase(TAG_TRACK) == 0) {
783 name = ID3_FRAME_TRACK;
784 }
785 else if (n.CmpNoCase(wxT("composer")) == 0) {
786 name = "TCOM";
787 }
788
789 struct id3_frame *frame = id3_frame_new(name);
790
791 if (!n.IsAscii() || !v.IsAscii()) {
792 id3_field_settextencoding(id3_frame_field(frame, 0), ID3_FIELD_TEXTENCODING_UTF_16);
793 }
794 else {
795 id3_field_settextencoding(id3_frame_field(frame, 0), ID3_FIELD_TEXTENCODING_ISO_8859_1);
796 }
797
799 id3_utf8_ucs4duplicate((id3_utf8_t *) (const char *) v.mb_str(wxConvUTF8)) };
800
801 if (strcmp(name, ID3_FRAME_COMMENT) == 0) {
802 // A hack to get around iTunes not recognizing the comment. The
803 // language defaults to XXX and, since it's not a valid language,
804 // iTunes just ignores the tag. So, either set it to a valid language
805 // (which one???) or just clear it. Unfortunately, there's no supported
806 // way of clearing the field, so do it directly.
807 id3_field *f = id3_frame_field(frame, 1);
808 memset(f->immediate.value, 0, sizeof(f->immediate.value));
809 id3_field_setfullstring(id3_frame_field(frame, 3), ucs4.get());
810 }
811 else if (strcmp(name, "TXXX") == 0) {
812 id3_field_setstring(id3_frame_field(frame, 2), ucs4.get());
813
814 ucs4.reset(id3_utf8_ucs4duplicate((id3_utf8_t *) (const char *) n.mb_str(wxConvUTF8)));
815
816 id3_field_setstring(id3_frame_field(frame, 1), ucs4.get());
817 }
818 else {
819 auto addr = ucs4.get();
820 id3_field_setstrings(id3_frame_field(frame, 1), 1, &addr);
821 }
822
823 id3_tag_attachframe(tp.get(), frame);
824 }
825
826 tp->options &= (~ID3_TAG_OPTION_COMPRESSION); // No compression
827
828 // If this version of libid3tag supports it, use v2.3 ID3
829 // tags instead of the newer, but less well supported, v2.4
830 // that libid3tag uses by default.
831#ifdef ID3_TAG_HAS_TAG_OPTION_ID3V2_3
832 tp->options |= ID3_TAG_OPTION_ID3V2_3;
833#endif
834
835 id3_length_t len;
836
837 len = id3_tag_render(tp.get(), 0);
838 if ((len % 2) != 0) {
839 len++; // Length must be even.
840 }
841
842 if (len > 0) {
843 buffer.resize(len);
844 id3_tag_render(tp.get(), (id3_byte_t *) buffer.data());
845 }
846#endif
847
848 return buffer;
849}
850
852 []{ return std::make_unique< ExportCL >(); }
853};
wxT("CloseDown"))
Toolkit-neutral facade for basic user interface services.
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:851
@ 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:148
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
wxString name
Definition: TagsEditor.cpp:166
const auto project
#define S(N)
Definition: ToChars.cpp:64
declares abstract base class Track, TrackList, and iterators over TrackList
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:642
struct CLExportProcessor::@143 context
TranslatableString status
Definition: ExportCL.cpp:422
unsigned channels
Definition: ExportCL.cpp:425
static std::vector< char > GetMetaChunk(const Tags *metadata)
Definition: ExportCL.cpp:748
std::unique_ptr< Mixer > mixer
Definition: ExportCL.cpp:428
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:430
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
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:408
ExportOptionsCLEditor()=default
bool GetValue(int id, ExportValue &value) const override
Definition: ExportCL.cpp:332
int GetOptionsCount() const override
Definition: ExportCL.cpp:317
bool SetValue(int id, const ExportValue &value) override
Definition: ExportCL.cpp:347
SampleRateList GetSampleRateList() const override
Definition: ExportCL.cpp:312
void PopulateUI(ShuttleGui &S) override
Definition: ExportCL.cpp:181
static bool IsValidCommand(const wxString &command)
Definition: ExportCL.cpp:243
void Store(audacity::BasicSettings &config) const override
Definition: ExportCL.cpp:368
bool TransferDataFromWindow() override
Definition: ExportCL.cpp:300
FileHistory mHistory
Definition: ExportCL.cpp:415
bool GetOption(int index, ExportOption &option) const override
Definition: ExportCL.cpp:322
void Load(const audacity::BasicSettings &config) override
Definition: ExportCL.cpp:362
void OnBrowse(const wxCommandEvent &)
Definition: ExportCL.cpp:376
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 AudacityProject &project, 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:640
ID3 Tags (for MP3)
Definition: Tags.h:73
Iterators GetRange() const
Definition: Tags.cpp:426
static Tags & Get(AudacityProject &project)
Definition: Tags.cpp:214
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:214
void Yield()
Dispatch waiting events, including actions enqueued by CallAfter.
Definition: BasicUI.cpp:225
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:287
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