19#include <wx/cmdline.h>
20#include <wx/combobox.h>
22#include <wx/process.h>
24#include <wx/textctrl.h>
26#include <wx/msw/registry.h>
39#include "../widgets/FileHistory.h"
42#include "../widgets/Warning.h"
48 struct id3_frame *id3_frame_new(
char const *);
66 void OnBrowse(wxCommandEvent & event);
86 mHistory.Load(*
gPrefs,
wxT(
"/FileFormats/ExternalProgramHistory"));
88 if (mHistory.empty()) {
89 mHistory.Append(
wxT(
"ffmpeg -i - \"%f.opus\""));
90 mHistory.Append(
wxT(
"ffmpeg -i - \"%f.wav\""));
91 mHistory.Append(
wxT(
"ffmpeg -i - \"%f\""));
92 mHistory.Append(
wxT(
"lame - \"%f\""));
95 mHistory.Append(
gPrefs->Read(
wxT(
"/FileFormats/ExternalProgramExportCommand"),
99 PopulateOrExchange(
S);
101 TransferDataToWindow();
118 S.StartVerticalLay();
120 S.StartHorizontalLay(wxEXPAND);
122 S.SetSizerProportion(1);
123 S.StartMultiColumn(3, wxEXPAND);
130 wxALIGN_CENTER_VERTICAL);
131 S.AddFixedText( {} );
132 S.TieCheckBox(
XXO(
"Show output"),
133 {
wxT(
"/FileFormats/ExternalProgramShowOutput"),
138 S.EndHorizontalLay();
146"Data will be piped to standard in. \"%f\" uses the file name in the export window."), 250);
165 wxString cmd =
mCmd->GetValue();
170 gPrefs->Write(
wxT(
"/FileFormats/ExternalProgramExportCommand"), cmd);
184#if defined(__WXMSW__)
187 type = {
XO(
"Executables"), { ext } };
191 XO(
"Find path to command"),
196 wxFD_OPEN | wxRESIZE_BORDER,
202 if (path.Find(
wxT(
' ')) == wxNOT_FOUND) {
203 mCmd->SetValue(path);
209 mCmd->SetInsertionPointEnd();
218static void Drain(wxInputStream *s, wxString *o)
220 while (s->CanRead()) {
223 s->Read(buffer, WXSIZEOF(buffer) - 1);
224 buffer[s->LastRead()] =
wxT(
'\0');
234#if defined(__WXMAC__)
236 signal(SIGPIPE, SIG_IGN);
284 std::unique_ptr<BasicUI::ProgressDialog> &pDialog,
291 const Tags *metadata = NULL,
292 int subformat = 0)
override;
306#if defined(__WXMSW__)
312 wxString paths[] = {
wxT(
"HKEY_LOCAL_MACHINE\\Software\\Lame for Audacity"),
313 wxT(
"HKEY_LOCAL_MACHINE\\Software\\FFmpeg for Audacity")};
320 for (
int i = 0; i < WXSIZEOF(paths); i++) {
321 reg.SetName(paths[i]);
325 reg.QueryValue(
wxT(
"InstallPath"), ipath);
326 if (!ipath.empty()) {
327 npath += wxPATH_SEP + ipath;
332 wxSetEnv(
wxT(
"PATH"),npath);
364 const Tags *metadata,
365 int WXUNUSED(subformat))
371 const auto path = fName.GetFullPath();
378 if(
mCmd ==
wxT(
"ffmpeg -i - \"%f\"") && !fName.HasExt())
379 mCmd.Replace(
"%f",
"%f.wav" );
385 rc = wxExecute(
mCmd, wxEXEC_ASYNC, &process);
389 process.CloseOutput();
399 const size_t maxBlockLen = 44100 * 5;
400 unsigned long totalSamples =
lrint((t1 - t0) * rate);
403 wxOutputStream *os = process.GetOutputStream();
415 wxUint32 formatChunkLen;
419 wxUint32 avgBytesPerSec;
421 wxUint16 bitsPerSample;
436 riff.riffID[0] =
'R';
437 riff.riffID[1] =
'I';
438 riff.riffID[2] =
'F';
439 riff.riffID[3] =
'F';
440 riff.riffLen = wxUINT32_SWAP_ON_BE(
sizeof(riff) +
445 riff.riffType[0] =
'W';
446 riff.riffType[1] =
'A';
447 riff.riffType[2] =
'V';
448 riff.riffType[3] =
'E';
454 fmt.formatChunkLen = wxUINT32_SWAP_ON_BE(16);
455 fmt.formatTag = wxUINT16_SWAP_ON_BE(3);
456 fmt.channels = wxUINT16_SWAP_ON_BE(channels);
457 fmt.sampleRate = wxUINT32_SWAP_ON_BE(rate);
459 fmt.blockAlign = wxUINT16_SWAP_ON_BE(fmt.bitsPerSample * fmt.channels / 8);
460 fmt.avgBytesPerSec = wxUINT32_SWAP_ON_BE(fmt.sampleRate * fmt.blockAlign);
463 if (metadata == NULL) {
468 if (metachunk.size()) {
474 id3.id3Len = wxUINT32_SWAP_ON_BE(metachunk.size());
475 riff.riffLen +=
sizeof(id3) + metachunk.size();
478 data.dataID[0] =
'd';
479 data.dataID[1] =
'a';
480 data.dataID[2] =
't';
481 data.dataID[3] =
'a';
482 data.dataLen = wxUINT32_SWAP_ON_BE(sampleBytes);
485 os->Write(&riff,
sizeof(riff));
486 os->Write(&fmt,
sizeof(fmt));
487 if (metachunk.size()) {
488 os->Write(&id3,
sizeof(id3));
489 os->Write(metachunk.data(), metachunk.size());
491 os->Write(&data,
sizeof(data));
512 auto closeIt =
finally ( [&] {
514 process.CloseOutput();
520 ?
XO(
"Exporting the selected audio using command-line encoder")
521 :
XO(
"Exporting the audio using command-line encoder") );
522 auto &progress = *pDialog;
527 Drain(process.GetInputStream(), &output);
528 Drain(process.GetErrorStream(), &output);
532 auto numSamples = mixer->Process();
536 mixed = mixer->GetBuffer();
537 numBytes = numSamples * channels;
541#if wxBYTE_ORDER == wxBIG_ENDIAN
542 auto buffer = (
const float *) mixed;
543 for (
int i = 0; i < numBytes; i++) {
544 buffer[i] = wxUINT32_SWAP_ON_BE(buffer[i]);
551 size_t bytes = wxMin(numBytes, 4096);
555 os->Write(mixed, bytes);
560 bytes -= os->LastWrite();
561 mixed += os->LastWrite();
565 updateResult = progress.Poll(mixer->MixGetCurrentTime() - t0, t1 - t0);
572 using namespace std::chrono;
573 std::this_thread::sleep_for(10ms);
582 XO(
"Command Output"),
585 wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER);
590 .Style( wxTE_MULTILINE | wxTE_READONLY | wxTE_RICH )
591 .AddTextWindow(
mCmd +
wxT(
"\n\n") + output);
592 S.StartHorizontalLay(wxALIGN_CENTER,
false);
594 S.Id(wxID_OK).AddButton(
XXO(
"&OK"), wxALIGN_CENTER,
true);
596 dlg.GetSizer()->AddSpacer(5);
598 dlg.SetMinSize(dlg.GetSize());
604 updateResult = ProgressResult::Failed;
612 std::vector<char> buffer;
615 struct id3_tag_deleter {
616 void operator () (id3_tag *p)
const {
if (p) id3_tag_delete(p); }
619 std::unique_ptr<id3_tag, id3_tag_deleter> tp { id3_tag_new() };
621 for (
const auto &pair : tags->
GetRange()) {
622 const auto &n = pair.first;
623 const auto &v = pair.second;
624 const char *
name =
"TXXX";
627 name = ID3_FRAME_TITLE;
630 name = ID3_FRAME_ARTIST;
633 name = ID3_FRAME_ALBUM;
635 else if (n.CmpNoCase(
TAG_YEAR) == 0) {
636 name = ID3_FRAME_YEAR;
639 name = ID3_FRAME_GENRE;
642 name = ID3_FRAME_COMMENT;
645 name = ID3_FRAME_TRACK;
647 else if (n.CmpNoCase(
wxT(
"composer")) == 0) {
651 struct id3_frame *frame = id3_frame_new(
name);
653 if (!n.IsAscii() || !v.IsAscii()) {
654 id3_field_settextencoding(id3_frame_field(frame, 0), ID3_FIELD_TEXTENCODING_UTF_16);
657 id3_field_settextencoding(id3_frame_field(frame, 0), ID3_FIELD_TEXTENCODING_ISO_8859_1);
661 id3_utf8_ucs4duplicate((id3_utf8_t *) (
const char *) v.mb_str(wxConvUTF8)) };
663 if (strcmp(
name, ID3_FRAME_COMMENT) == 0) {
669 id3_field *f = id3_frame_field(frame, 1);
670 memset(f->immediate.value, 0,
sizeof(f->immediate.value));
671 id3_field_setfullstring(id3_frame_field(frame, 3), ucs4.get());
673 else if (strcmp(
name,
"TXXX") == 0) {
674 id3_field_setstring(id3_frame_field(frame, 2), ucs4.get());
676 ucs4.reset(id3_utf8_ucs4duplicate((id3_utf8_t *) (
const char *) n.mb_str(wxConvUTF8)));
678 id3_field_setstring(id3_frame_field(frame, 1), ucs4.get());
681 auto addr = ucs4.get();
682 id3_field_setstrings(id3_frame_field(frame, 1), 1, &addr);
685 id3_tag_attachframe(tp.get(), frame);
688 tp->options &= (~ID3_TAG_OPTION_COMPRESSION);
693#ifdef ID3_TAG_HAS_TAG_OPTION_ID3V2_3
694 tp->options |= ID3_TAG_OPTION_ID3V2_3;
699 len = id3_tag_render(tp.get(), 0);
700 if ((len % 2) != 0) {
706 id3_tag_render(tp.get(), (id3_byte_t *) buffer.data());
722 if (filename.GetExt().empty()) {
724 wxT(
"MissingExtension"),
725 XO(
"You've specified a file name without an extension. Are you sure?"),
726 true) == wxID_CANCEL) {
733 wxArrayString
argv = wxCmdLineParser::ConvertStringToArgs(
mCmd,
734#
if defined(__WXMSW__)
737 wxCMD_LINE_SPLIT_UNIX
741 if (
argv.size() == 0) {
744 XO(
"Program name appears to be missing."));
749 wxFileName cmd(
argv[0]);
750 cmd.Normalize(wxPATH_NORM_ALL & ~wxPATH_NORM_ABSOLUTE);
753 if (cmd.IsAbsolute()) {
756 XO(
"\"%s\" couldn't be found.").
Format(cmd.GetFullPath()),
758 wxOK | wxICON_EXCLAMATION);
768 pathlist.AddEnvList(
wxT(
"PATH"));
769 wxString path = pathlist.FindAbsoluteValidPath(
argv[0]);
771#if defined(__WXMSW__)
773 path = pathlist.FindAbsoluteValidPath(
argv[0] +
wxT(
".exe"));
779 XO(
"Unable to locate \"%s\" in your path.").
Format(cmd.GetFullPath()),
781 wxOK | wxICON_EXCLAMATION);
792 gPrefs->Read(
wxT(
"/FileFormats/ExternalProgramShowOutput"), &
mShow,
false);
793 mCmd =
gPrefs->Read(
wxT(
"/FileFormats/ExternalProgramExportCommand"),
wxT(
"lame - \"%f.mp3\""));
797 []{
return std::make_unique< ExportCL >(); }
int AudacityMessageBox(const TranslatableString &message, const TranslatableString &caption, long style, wxWindow *parent, int x, int y)
EVT_BUTTON(wxID_NO, DependencyDialog::OnNo) EVT_BUTTON(wxID_YES
const TranslatableString name
void ShowExportErrorDialog(wxString ErrorCode, TranslatableString message, const TranslatableString &caption, bool allowReporting)
static void Drain(wxInputStream *s, wxString *o)
static Exporter::RegisteredExportPlugin sRegisteredPlugin
XXO("&Cut/Copy/Paste Toolbar")
wxString FileExtension
File extension, not including any leading dot.
std::unique_ptr< Character[], freer > MallocString
an object holding per-project preferred sample rate
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)
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)
The top-level handle to an Audacity project. It serves as a source of events that other objects can b...
ProgressResult Export(AudacityProject *project, std::unique_ptr< BasicUI::ProgressDialog > &pDialog, unsigned channels, const wxFileNameWrapper &fName, bool selectedOnly, double t0, double t1, MixerSpec *mixerSpec=NULL, const Tags *metadata=NULL, int subformat=0) override
called to export audio into a file.
void OptionsCreate(ShuttleGui &S, int format) override
std::vector< char > GetMetaChunk(const Tags *metadata)
bool CheckFileName(wxFileName &filename, int format=0) override
void PopulateOrExchange(ShuttleGui &S)
virtual ~ExportCLOptions()
void OnBrowse(wxCommandEvent &event)
ExportCLOptions(wxWindow *parent, int format)
bool TransferDataFromWindow() override
bool TransferDataToWindow() override
void OnTerminate(int WXUNUSED(pid), int status)
ExportCLProcess(wxString *output)
void AddExtension(const FileExtension &extension, int index)
int AddFormat()
Add a NEW entry to the list of formats this plug-in can export.
static void InitProgress(std::unique_ptr< BasicUI::ProgressDialog > &pDialog, const TranslatableString &title, const TranslatableString &message)
void SetFormat(const wxString &format, int index)
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, MixerSpec *mixerSpec)
void SetDescription(const TranslatableString &description, int index)
void SetCanMetaData(bool canmetadata, int index)
void SetMaxChannels(unsigned maxchannels, unsigned index)
virtual bool Flush(bool bCurrentOnly=false) wxOVERRIDE
Similar to wxFileHistory, but customized to our needs.
const_iterator begin() const
void Append(const FilePath &file)
void Save(wxConfigBase &config)
const_iterator end() const
FILES_API const FileType AllFiles
A matrix of booleans, one row per input channel, column per output.
static ProjectRate & Get(AudacityProject &project)
Derived from ShuttleGuiBase, an Audacity specific class for shuttling data to and from GUI.
static TrackList & Get(AudacityProject &project)
Extend wxArrayString with move operations and construction and insertion fromstd::initializer_list.
void SetName(const TranslatableString &title)
UTILITY_API const char *const * argv
A copy of argv; responsibility of application startup to assign it.
BuiltinCommandsModule::Registration< CompareAudioCommand > reg