Audacity  2.2.2
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 "../Audacity.h"
15 #include "ExportCL.h"
16 #include "../Project.h"
17 
18 #include <wx/app.h>
19 #include <wx/button.h>
20 #include <wx/combobox.h>
21 #include <wx/filedlg.h>
22 #include <wx/log.h>
23 #include <wx/process.h>
24 #include <wx/sizer.h>
25 #include <wx/textctrl.h>
26 #include "../FileNames.h"
27 #include "Export.h"
28 
29 #include "../Mix.h"
30 #include "../Prefs.h"
31 #include "../ShuttleGui.h"
32 #include "../Internat.h"
33 #include "../float_cast.h"
34 #include "../widgets/FileHistory.h"
35 #include "../widgets/ErrorDialog.h"
36 
37 #include "../Track.h"
38 
39 
40 //----------------------------------------------------------------------------
41 // ExportCLOptions
42 //----------------------------------------------------------------------------
43 
44 class ExportCLOptions final : public wxPanelWrapper
45 {
46 public:
47  ExportCLOptions(wxWindow *parent, int format);
48  virtual ~ExportCLOptions();
49 
51  bool TransferDataToWindow() override;
52  bool TransferDataFromWindow() override;
53 
54  void OnBrowse(wxCommandEvent & event);
55 
56 private:
57  wxComboBox *mCmd;
59 
60  DECLARE_EVENT_TABLE()
61 };
62 
63 #define ID_BROWSE 5000
64 
65 BEGIN_EVENT_TABLE(ExportCLOptions, wxPanelWrapper)
68 
71 ExportCLOptions::ExportCLOptions(wxWindow *parent, int WXUNUSED(format))
72 : wxPanelWrapper(parent, wxID_ANY)
73 {
74  mHistory.Load(*gPrefs, wxT("/FileFormats/ExternalProgramHistory"));
75 
76  if (mHistory.GetCount() == 0) {
77  mHistory.AddFileToHistory(wxT("ffmpeg -i - \"%f\""), false);
78  mHistory.AddFileToHistory(wxT("lame - \"%f\""), false);
79  }
80 
81  mHistory.AddFileToHistory(gPrefs->Read(wxT("/FileFormats/ExternalProgramExportCommand"),
82  mHistory.GetHistoryFile(0)),
83  false);
84 
86  PopulateOrExchange(S);
87 
88  TransferDataToWindow();
89 
90  parent->Layout();
91 }
92 
94 {
96 }
97 
101 {
102  wxArrayString cmds;
103  wxString cmd;
104 
105  for (size_t i = 0; i < mHistory.GetCount(); i++) {
106  cmds.Add(mHistory.GetHistoryFile(i));
107  }
108  cmd = cmds[0];
109 
110  S.StartVerticalLay();
111  {
112  S.StartHorizontalLay(wxEXPAND);
113  {
114  S.SetSizerProportion(1);
115  S.StartMultiColumn(3, wxEXPAND);
116  {
117  S.SetStretchyCol(1);
118  mCmd = S.AddCombo(_("Command:"),
119  cmd,
120  &cmds);
121  S.Id(ID_BROWSE).AddButton(_("Browse..."),
122  wxALIGN_CENTER_VERTICAL);
123  S.AddFixedText( {} );
124  S.TieCheckBox(_("Show output"),
125  wxT("/FileFormats/ExternalProgramShowOutput"),
126  false);
127  }
128  S.EndMultiColumn();
129  }
130  S.EndHorizontalLay();
131 
132  S.AddTitle(_("Data will be piped to standard in. \"%f\" uses the file name in the export window."));
133  }
134  S.EndVerticalLay();
135 }
136 
140 {
141  return true;
142 }
143 
147 {
148  ShuttleGui S(this, eIsSavingToPrefs);
150 
151  wxString cmd = mCmd->GetValue();
152 
153  mHistory.AddFileToHistory(cmd, false);
154  mHistory.Save(*gPrefs, wxT("/FileFormats/ExternalProgramHistory"));
155 
156  gPrefs->Write(wxT("/FileFormats/ExternalProgramExportCommand"), cmd);
157  gPrefs->Flush();
158 
159  return true;
160 }
161 
164 void ExportCLOptions::OnBrowse(wxCommandEvent& WXUNUSED(event))
165 {
166  wxString path;
167  wxString ext;
168 
169 #if defined(__WXMSW__)
170  ext = wxT(".exe");
171 #endif
172 
174  _("Find path to command"),
175  wxEmptyString,
176  wxEmptyString,
177  ext,
178  wxT("*") + ext,
179  wxFD_OPEN | wxRESIZE_BORDER,
180  this);
181  if (path.IsEmpty()) {
182  return;
183  }
184 
185  if (path.Find(wxT(' ')) == wxNOT_FOUND) {
186  mCmd->SetValue(path);
187  }
188  else {
189  mCmd->SetValue(wxT('"') + path + wxT('"'));
190  }
191 
192  mCmd->SetInsertionPointEnd();
193 
194  return;
195 }
196 
197 //----------------------------------------------------------------------------
198 // ExportCLProcess
199 //----------------------------------------------------------------------------
200 
201 static void Drain(wxInputStream *s, wxString *o)
202 {
203  while (s->CanRead()) {
204  char buffer[4096];
205 
206  s->Read(buffer, WXSIZEOF(buffer) - 1);
207  buffer[s->LastRead()] = wxT('\0');
208  *o += LAT1CTOWX(buffer);
209  }
210 }
211 
212 class ExportCLProcess final : public wxProcess
213 {
214 public:
215  ExportCLProcess(wxString *output)
216  {
217 #if defined(__WXMAC__)
218  // Don't want to crash on broken pipe
219  signal(SIGPIPE, SIG_IGN);
220 #endif
221 
222  mOutput = output;
223  mActive = true;
224  mStatus = -555;
225  Redirect();
226  }
227 
228  bool IsActive()
229  {
230  return mActive;
231  }
232 
233  void OnTerminate(int WXUNUSED( pid ), int status)
234  {
235  Drain(GetInputStream(), mOutput);
236  Drain(GetErrorStream(), mOutput);
237 
238  mStatus = status;
239  mActive = false;
240  }
241 
242  int GetStatus()
243  {
244  return mStatus;
245  }
246 
247 private:
248  wxString *mOutput;
249  bool mActive;
250  int mStatus;
251 };
252 
253 //----------------------------------------------------------------------------
254 // ExportCL
255 //----------------------------------------------------------------------------
256 
257 /* this structure combines the RIFF header, the format chunk, and the data
258  * chunk header */
259 struct wav_header {
260  /* RIFF header */
261  char riffID[4]; /* "RIFF" */
262  wxUint32 lenAfterRiff; /* basically the file len - 8, or samples len + 36 */
263  char riffType[4]; /* "WAVE" */
264 
265  /* format chunk */
266  char fmtID[4]; /* "fmt " */
267  wxUint32 formatChunkLen; /* (format chunk len - first two fields) 16 in our case */
268  wxUint16 formatTag; /* 1 for PCM */
269  wxUint16 channels;
270  wxUint32 sampleRate;
271  wxUint32 avgBytesPerSec; /* sampleRate * blockAlign */
272  wxUint16 blockAlign; /* bitsPerSample * channels (assume bps % 8 = 0) */
273  wxUint16 bitsPerSample;
274 
275  /* data chunk header */
276  char dataID[4]; /* "data" */
277  wxUint32 dataLen; /* length of all samples in bytes */
278 };
279 
280 class ExportCL final : public ExportPlugin
281 {
282 public:
283 
284  ExportCL();
285 
286  // Required
287  wxWindow *OptionsCreate(wxWindow *parent, int format) override;
288 
290  std::unique_ptr<ProgressDialog> &pDialog,
291  unsigned channels,
292  const wxString &fName,
293  bool selectedOnly,
294  double t0,
295  double t1,
296  MixerSpec *mixerSpec = NULL,
297  const Tags *metadata = NULL,
298  int subformat = 0) override;
299 };
300 
302 : ExportPlugin()
303 {
304  AddFormat();
305  SetFormat(wxT("CL"),0);
306  AddExtension(wxT(""),0);
307  SetMaxChannels(255,0);
308  SetCanMetaData(false,0);
309  SetDescription(_("(external program)"),0);
310 }
311 
313  std::unique_ptr<ProgressDialog> &pDialog,
314  unsigned channels,
315  const wxString &fName,
316  bool selectionOnly,
317  double t0,
318  double t1,
319  MixerSpec *mixerSpec,
320  const Tags *WXUNUSED(metadata),
321  int WXUNUSED(subformat))
322 {
323  wxString output;
324  wxString cmd;
325  bool show;
326  long rc;
327 
328  // Retrieve settings
329  gPrefs->Read(wxT("/FileFormats/ExternalProgramShowOutput"), &show, false);
330  cmd = gPrefs->Read(wxT("/FileFormats/ExternalProgramExportCommand"), wxT("lame - \"%f.mp3\""));
331  cmd.Replace(wxT("%f"), fName);
332 
333 #if defined(__WXMSW__)
334  // Give Windows a chance at finding lame command in the default location.
335  wxString paths[] = {wxT("HKEY_LOCAL_MACHINE\\Software\\Lame for Audacity"),
336  wxT("HKEY_LOCAL_MACHINE\\Software\\FFmpeg for Audacity")};
337  wxString opath;
338  wxString npath;
339  wxRegKey reg;
340 
341  wxGetEnv(wxT("PATH"), &opath);
342  npath = opath;
343 
344  for (int i = 0; i < WXSIZEOF(paths); i++) {
345  reg.SetName(paths[i]);
346 
347  if (reg.Exists()) {
348  wxString ipath;
349  reg.QueryValue(wxT("InstallPath"), ipath);
350  if (!ipath.IsEmpty()) {
351  npath += wxPATH_SEP + ipath;
352  }
353  }
354  }
355 
356  wxSetEnv(wxT("PATH"),npath);
357 #endif
358 
359  // Kick off the command
360  ExportCLProcess process(&output);
361 
362  {
363 #if defined(__WXMSW__)
364  auto cleanup = finally( [&] {
365  if (!opath.IsEmpty()) {
366  wxSetEnv(wxT("PATH"),opath);
367  }
368  } );
369 #endif
370 
371  rc = wxExecute(cmd, wxEXEC_ASYNC, &process);
372  }
373 
374  if (!rc) {
375  AudacityMessageBox(wxString::Format(_("Cannot export audio to %s"),
376  fName));
377  process.Detach();
378  process.CloseOutput();
379 
381  }
382 
383  // Turn off logging to prevent broken pipe messages
384  wxLogNull nolog;
385 
386  // establish parameters
387  int rate = lrint(project->GetRate());
388  const size_t maxBlockLen = 44100 * 5;
389  unsigned long totalSamples = lrint((t1 - t0) * rate);
390  unsigned long sampleBytes = totalSamples * channels * SAMPLE_SIZE(int16Sample);
391 
392  // fill up the wav header
393  wav_header header;
394  header.riffID[0] = 'R';
395  header.riffID[1] = 'I';
396  header.riffID[2] = 'F';
397  header.riffID[3] = 'F';
398  header.lenAfterRiff = wxUINT32_SWAP_ON_BE(sampleBytes + 36);
399  header.riffType[0] = 'W';
400  header.riffType[1] = 'A';
401  header.riffType[2] = 'V';
402  header.riffType[3] = 'E';
403 
404  header.fmtID[0] = 'f';
405  header.fmtID[1] = 'm';
406  header.fmtID[2] = 't';
407  header.fmtID[3] = ' ';
408  header.formatChunkLen = wxUINT32_SWAP_ON_BE(16);
409  header.formatTag = wxUINT16_SWAP_ON_BE(1);
410  header.channels = wxUINT16_SWAP_ON_BE(channels);
411  header.sampleRate = wxUINT32_SWAP_ON_BE(rate);
412  header.bitsPerSample = wxUINT16_SWAP_ON_BE(SAMPLE_SIZE(int16Sample) * 8);
413  header.blockAlign = wxUINT16_SWAP_ON_BE(header.bitsPerSample * header.channels / 8);
414  header.avgBytesPerSec = wxUINT32_SWAP_ON_BE(header.sampleRate * header.blockAlign);
415  header.dataID[0] = 'd';
416  header.dataID[1] = 'a';
417  header.dataID[2] = 't';
418  header.dataID[3] = 'a';
419  header.dataLen = wxUINT32_SWAP_ON_BE(sampleBytes);
420 
421  // write the header
422  wxOutputStream *os = process.GetOutputStream();
423  os->Write(&header, sizeof(wav_header));
424 
425  // Mix 'em up
426  const TrackList *tracks = project->GetTracks();
427  const WaveTrackConstArray waveTracks =
428  tracks->GetWaveTrackConstArray(selectionOnly, false);
429  auto mixer = CreateMixer(
430  waveTracks,
431  tracks->GetTimeTrack(),
432  t0,
433  t1,
434  channels,
435  maxBlockLen,
436  true,
437  rate,
438  int16Sample,
439  true,
440  mixerSpec);
441 
442  size_t numBytes = 0;
443  samplePtr mixed = NULL;
444  auto updateResult = ProgressResult::Success;
445 
446  {
447  auto closeIt = finally ( [&] {
448  // Should make the process die, before propagating any exception
449  process.CloseOutput();
450  } );
451 
452  // Prepare the progress display
453  InitProgress( pDialog, _("Export"),
454  selectionOnly
455  ? _("Exporting the selected audio using command-line encoder")
456  : _("Exporting the audio using command-line encoder") );
457  auto &progress = *pDialog;
458 
459  // Start piping the mixed data to the command
460  while (updateResult == ProgressResult::Success && process.IsActive() && os->IsOk()) {
461  // Capture any stdout and stderr from the command
462  Drain(process.GetInputStream(), &output);
463  Drain(process.GetErrorStream(), &output);
464 
465  // Need to mix another block
466  if (numBytes == 0) {
467  auto numSamples = mixer->Process(maxBlockLen);
468  if (numSamples == 0) {
469  break;
470  }
471 
472  mixed = mixer->GetBuffer();
473  numBytes = numSamples * channels;
474 
475  // Byte-swapping is neccesary on big-endian machines, since
476  // WAV files are little-endian
477 #if wxBYTE_ORDER == wxBIG_ENDIAN
478  wxUint16 *buffer = (wxUint16 *) mixed;
479  for (int i = 0; i < numBytes; i++) {
480  buffer[i] = wxUINT16_SWAP_ON_BE(buffer[i]);
481  }
482 #endif
483  numBytes *= SAMPLE_SIZE(int16Sample);
484  }
485 
486  // Don't write too much at once...pipes may not be able to handle it
487  size_t bytes = wxMin(numBytes, 4096);
488  numBytes -= bytes;
489 
490  while (bytes > 0) {
491  os->Write(mixed, bytes);
492  if (!os->IsOk()) {
493  updateResult = ProgressResult::Cancelled;
494  break;
495  }
496  bytes -= os->LastWrite();
497  mixed += os->LastWrite();
498  }
499 
500  // Update the progress display
501  updateResult = progress.Update(mixer->MixGetCurrentTime() - t0, t1 - t0);
502  }
503  // Done with the progress display
504  }
505 
506  // Wait for process to terminate
507  while (process.IsActive()) {
508  wxMilliSleep(10);
509  wxTheApp->Yield();
510  }
511 
512  // Display output on error or if the user wants to see it
513  if (process.GetStatus() != 0 || show) {
514  // TODO use ShowInfoDialog() instead.
515  wxDialogWrapper dlg(nullptr,
516  wxID_ANY,
517  wxString(_("Command Output")),
518  wxDefaultPosition,
519  wxSize(600, 400),
520  wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER);
521  dlg.SetName(dlg.GetTitle());
522 
523  ShuttleGui S(&dlg, eIsCreating);
524  S.AddTextWindow(cmd + wxT("\n\n") + output);
525  S.StartHorizontalLay(wxALIGN_CENTER, false);
526  {
527  S.Id(wxID_OK).AddButton(_("&OK"))->SetDefault();
528  }
529  dlg.GetSizer()->AddSpacer(5);
530  dlg.Layout();
531  dlg.SetMinSize(dlg.GetSize());
532  dlg.Center();
533 
534  dlg.ShowModal();
535 
536  updateResult = ProgressResult::Failed;
537  }
538 
539  return updateResult;
540 }
541 
542 wxWindow *ExportCL::OptionsCreate(wxWindow *parent, int format)
543 {
544  wxASSERT(parent); // to justify safenew
545  return safenew ExportCLOptions(parent, format);
546 }
547 
548 std::unique_ptr<ExportPlugin> New_ExportCL()
549 {
550  return std::make_unique<ExportCL>();
551 }
552 
bool TransferDataFromWindow() override
Definition: ExportCL.cpp:146
AudacityPrefs * gPrefs
Definition: Prefs.cpp:73
A list of TrackListNode items.
Definition: Track.h:618
virtual ~ExportCLOptions()
Definition: ExportCL.cpp:93
ProgressResult
Derived from ShuttleGuiBase, an Audacity specific class for shuttling data to and from GUI...
Definition: ShuttleGui.h:409
void SetDescription(const wxString &description, int index)
Definition: Export.cpp:118
#define SAMPLE_SIZE(SampleFormat)
Definition: Types.h:198
void EndMultiColumn()
FileHistory mHistory
Definition: ExportCL.cpp:58
int AudacityMessageBox(const wxString &message, const wxString &caption=AudacityMessageBoxCaptionStr(), long style=wxOK|wxCENTRE, wxWindow *parent=NULL, int x=wxDefaultCoord, int y=wxDefaultCoord)
Definition: ErrorDialog.h:92
wxUint16 bitsPerSample
Definition: ExportCL.cpp:273
wxUint32 dataLen
Definition: ExportCL.cpp:277
std::unique_ptr< Mixer > CreateMixer(const WaveTrackConstArray &inputTracks, const TimeTrack *timeTrack, double startTime, double stopTime, unsigned numOutChannels, size_t outBufferSize, bool outInterleaved, double outRate, sampleFormat outFormat, bool highQuality=true, MixerSpec *mixerSpec=NULL)
Definition: Export.cpp:235
TimeTrack * GetTimeTrack()
Definition: Track.cpp:1244
void SetFormat(const wxString &format, int index)
Definition: Export.cpp:113
void SetSizerProportion(int iProp)
Definition: ShuttleGui.h:289
#define safenew
Definition: Audacity.h:230
wxUint16 blockAlign
Definition: ExportCL.cpp:272
const wxString & GetHistoryFile(size_t i) const
Definition: FileHistory.cpp:85
void EndHorizontalLay()
AudacityProject provides the main window, with tools and tracks contained within it.
Definition: Project.h:176
void EndVerticalLay()
int format
Definition: ExportPCM.cpp:56
WaveTrackConstArray GetWaveTrackConstArray(bool selectionOnly, bool includeMuted=true) const
Definition: Track.cpp:1349
void SetMaxChannels(unsigned maxchannels, unsigned index)
Definition: Export.cpp:138
void StartHorizontalLay(int PositionFlags=wxALIGN_CENTRE, int iProp=1)
void StartMultiColumn(int nCols, int PositionFlags=wxALIGN_LEFT)
std::vector< std::shared_ptr< const WaveTrack > > WaveTrackConstArray
Definition: AudioIO.h:66
char riffType[4]
Definition: ExportCL.cpp:263
bool TransferDataToWindow() override
Definition: ExportCL.cpp:139
#define lrint(dbl)
Definition: float_cast.h:136
char * samplePtr
Definition: Types.h:203
ShuttleGui & Id(int id)
wxWindow * OptionsCreate(wxWindow *parent, int format) override
Definition: ExportCL.cpp:542
char fmtID[4]
Definition: ExportCL.cpp:266
void Save(wxConfigBase &config, const wxString &group)
ExportCLOptions(wxWindow *parent, int format)
Definition: ExportCL.cpp:71
void AddFixedText(const wxString &Str, bool bCenter=false)
Definition: ShuttleGui.cpp:397
#define LAT1CTOWX(X)
Definition: Internat.h:180
void AddTitle(const wxString &Prompt)
Centred text string.
Definition: ShuttleGui.cpp:274
std::unique_ptr< ExportPlugin > New_ExportCL()
Definition: ExportCL.cpp:548
char dataID[4]
Definition: ExportCL.cpp:276
wxComboBox * mCmd
Definition: ExportCL.cpp:57
EVT_BUTTON(wxID_NO, DependencyDialog::OnNo) EVT_BUTTON(wxID_YES
ID3 Tags (for MP3)
Definition: Tags.h:70
void OnBrowse(wxCommandEvent &event)
Definition: ExportCL.cpp:164
static wxString SelectFile(Operation op, const wxString &message, const wxString &default_path, const wxString &default_filename, const wxString &default_extension, const wxString &wildcard, int flags, wxWindow *parent)
Definition: FileNames.cpp:411
_("Move Track &Down")+wxT("\t")+(GetActiveProject() -> GetCommandManager() ->GetKeyFromName(wxT("TrackMoveDown")).Raw()), OnMoveTrack) POPUP_MENU_ITEM(OnMoveTopID, _("Move Track to &Top")+wxT("\t")+(GetActiveProject() ->GetCommandManager() ->GetKeyFromName(wxT("TrackMoveTop")).Raw()), OnMoveTrack) POPUP_MENU_ITEM(OnMoveBottomID, _("Move Track to &Bottom")+wxT("\t")+(GetActiveProject() ->GetCommandManager() ->GetKeyFromName(wxT("TrackMoveBottom")).Raw()), OnMoveTrack)#define SET_TRACK_NAME_PLUGIN_SYMBOLclass SetTrackNameCommand:public AudacityCommand
wxComboBox * AddCombo(const wxString &Prompt, const wxString &Selected, const wxArrayString *pChoices, long style=0)
Definition: ShuttleGui.cpp:440
char riffID[4]
Definition: ExportCL.cpp:261
int AddFormat()
Add a NEW entry to the list of formats this plug-in can export.
Definition: Export.cpp:97
wxUint32 sampleRate
Definition: ExportCL.cpp:270
wxCheckBox * TieCheckBox(const wxString &Prompt, WrappedType &WrappedRef)
wxTextCtrl * AddTextWindow(const wxString &Value)
Multiline text box that grows.
Definition: ShuttleGui.cpp:608
wxUint32 lenAfterRiff
Definition: ExportCL.cpp:262
wxUint32 avgBytesPerSec
Definition: ExportCL.cpp:271
void SetCanMetaData(bool canmetadata, int index)
Definition: Export.cpp:143
void AddExtension(const wxString &extension, int index)
Definition: Export.cpp:123
wxUint16 channels
Definition: ExportCL.cpp:269
wxUint32 formatChunkLen
Definition: ExportCL.cpp:267
void OnTerminate(int WXUNUSED(pid), int status)
Definition: ExportCL.cpp:233
double GetRate() const
Definition: Project.h:199
END_EVENT_TABLE()
TrackList * GetTracks()
Definition: Project.h:192
wxUint16 formatTag
Definition: ExportCL.cpp:268
ExportCLProcess(wxString *output)
Definition: ExportCL.cpp:215
static void InitProgress(std::unique_ptr< ProgressDialog > &pDialog, const wxString &title, const wxString &message)
Definition: Export.cpp:253
void PopulateOrExchange(ShuttleGui &S)
Definition: ExportCL.cpp:100
#define ID_BROWSE
Definition: ExportCL.cpp:63
static void Drain(wxInputStream *s, wxString *o)
Definition: ExportCL.cpp:201
wxButton * AddButton(const wxString &Text, int PositionFlags=wxALIGN_CENTRE)
Definition: ShuttleGui.cpp:341
wxString * mOutput
Definition: ExportCL.cpp:248
void AddFileToHistory(const wxString &file, bool update=true)
Definition: FileHistory.cpp:36
size_t GetCount()
Definition: FileHistory.cpp:97
void SetStretchyCol(int i)
Used to modify an already placed FlexGridSizer to make a column stretchy.
Definition: ShuttleGui.cpp:203
ProgressResult Export(AudacityProject *project, std::unique_ptr< ProgressDialog > &pDialog, unsigned channels, const wxString &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.
Definition: ExportCL.cpp:312
Similar to FileHistory, but customized to our needs.
Definition: FileHistory.h:23
Class used with Mixer.
Definition: Mix.h:58
void StartVerticalLay(int iProp=1)