Audacity  2.2.2
ExportPCM.cpp
Go to the documentation of this file.
1 /**********************************************************************
2 
3  Audacity: A Digital Audio Editor
4 
5  ExportPCM.cpp
6 
7  Dominic Mazzoni
8 
9 **********************************************************************/
10 
11 #include "../Audacity.h"
12 #include "ExportPCM.h"
13 
14 #include <wx/defs.h>
15 
16 #include <wx/choice.h>
17 #include <wx/dynlib.h>
18 #include <wx/filename.h>
19 #include <wx/intl.h>
20 #include <wx/timer.h>
21 #include <wx/progdlg.h>
22 #include <wx/string.h>
23 #include <wx/textctrl.h>
24 #include <wx/window.h>
25 
26 #include "sndfile.h"
27 
28 #include "../FileFormats.h"
29 #include "../Internat.h"
30 #include "../MemoryX.h"
31 #include "../Mix.h"
32 #include "../Prefs.h"
33 #include "../Project.h"
34 #include "../ShuttleGui.h"
35 #include "../Tags.h"
36 #include "../Track.h"
37 #include "../ondemand/ODManager.h"
38 #include "../widgets/ErrorDialog.h"
39 
40 #include "Export.h"
41 
42 #ifdef USE_LIBID3TAG
43  #include <id3tag.h>
44  // DM: the following functions were supposed to have been
45  // included in id3tag.h - should be fixed in the next release
46  // of mad.
47  extern "C" {
48  struct id3_frame *id3_frame_new(char const *);
49  id3_length_t id3_latin1_length(id3_latin1_t const *);
50  void id3_latin1_decode(id3_latin1_t const *, id3_ucs4_t *);
51  }
52 #endif
53 
54 struct
55 {
56  int format;
57  const wxChar *name;
58  const wxChar *desc;
59 }
60 static const kFormats[] =
61 {
62 #if defined(__WXMAC__)
63  { SF_FORMAT_AIFF | SF_FORMAT_PCM_16, wxT("AIFF"), XO("AIFF (Apple) signed 16-bit PCM") },
64 #endif
65  { SF_FORMAT_WAV | SF_FORMAT_PCM_16, wxT("WAV"), XO("WAV (Microsoft) signed 16-bit PCM") },
66  { SF_FORMAT_WAV | SF_FORMAT_PCM_24, wxT("WAV24"), XO("WAV (Microsoft) signed 24-bit PCM") },
67  { SF_FORMAT_WAV | SF_FORMAT_FLOAT, wxT("WAVFLT"), XO("WAV (Microsoft) 32-bit float PCM") },
68 // { SF_FORMAT_WAV | SF_FORMAT_GSM610, wxT("GSM610"), XO("GSM 6.10 WAV (mobile)") },
69 };
70 
71 //----------------------------------------------------------------------------
72 // Statics
73 //----------------------------------------------------------------------------
74 
76 {
77 #if defined(__WXMAC__)
78  return gPrefs->Read(wxT("/FileFormats/ExportFormat_SF1"),
79  (long int)(SF_FORMAT_AIFF | SF_FORMAT_PCM_16));
80 #else
81  return gPrefs->Read(wxT("/FileFormats/ExportFormat_SF1"),
82  (long int)(SF_FORMAT_WAV | SF_FORMAT_PCM_16));
83 #endif
84 }
85 
86 static void WriteExportFormatPref(int format)
87 {
88  gPrefs->Write(wxT("/FileFormats/ExportFormat_SF1"), (long int)format);
89  gPrefs->Flush();
90 }
91 
92 //----------------------------------------------------------------------------
93 // ExportPCMOptions Class
94 //----------------------------------------------------------------------------
95 
96 #define ID_HEADER_CHOICE 7102
97 #define ID_ENCODING_CHOICE 7103
98 
99 class ExportPCMOptions final : public wxPanelWrapper
100 {
101 public:
102 
103  ExportPCMOptions(wxWindow *parent, int format);
104  virtual ~ExportPCMOptions();
105 
106  void PopulateOrExchange(ShuttleGui & S);
107  bool TransferDataToWindow() override;
108  bool TransferDataFromWindow() override;
109 
110  void OnHeaderChoice(wxCommandEvent & evt);
111 
112 private:
113 
114  bool ValidatePair(int format);
115  int GetFormat();
116 
117 private:
118 
119  wxArrayString mHeaderNames;
120  wxArrayString mEncodingNames;
121  wxChoice *mHeaderChoice;
122  wxChoice *mEncodingChoice;
125  std::vector<int> mEncodingFormats;
126 
127  DECLARE_EVENT_TABLE()
128 };
129 
130 BEGIN_EVENT_TABLE(ExportPCMOptions, wxPanelWrapper)
131  EVT_CHOICE(ID_HEADER_CHOICE, ExportPCMOptions::OnHeaderChoice)
133 
134 ExportPCMOptions::ExportPCMOptions(wxWindow *parent, int selformat)
135 : wxPanelWrapper(parent, wxID_ANY)
136 {
137  int format;
138 
139  if (selformat < 0 || static_cast<unsigned int>(selformat) >= WXSIZEOF(kFormats))
140  {
141  format = ReadExportFormatPref();
142  }
143  else
144  {
145  format = kFormats[selformat].format;
146  }
147 
148  mHeaderFromChoice = 0;
149  for (int i = 0, num = sf_num_headers(); i < num; i++) {
150  mHeaderNames.Add(sf_header_index_name(i));
151  if ((format & SF_FORMAT_TYPEMASK) == (int)sf_header_index_to_type(i))
152  mHeaderFromChoice = i;
153  }
154 
155  mEncodingFromChoice = 0;
156  for (int i = 0, sel = 0, num = sf_num_encodings(); i < num; i++) {
157  int enc = sf_encoding_index_to_subtype(i);
158  int fmt = (format & SF_FORMAT_TYPEMASK) | enc;
159  bool valid = ValidatePair(fmt);
160  if (valid)
161  {
162  mEncodingNames.Add(sf_encoding_index_name(i));
163  mEncodingFormats.push_back(enc);
164  if ((format & SF_FORMAT_SUBMASK) == (int)sf_encoding_index_to_subtype(i))
165  mEncodingFromChoice = sel;
166  else
167  sel++;
168  }
169  }
170 
172  PopulateOrExchange(S);
173 
174  TransferDataToWindow();
175  TransferDataFromWindow();
176 }
177 
179 {
181 }
182 
184 {
185  S.StartVerticalLay();
186  {
187  S.StartHorizontalLay(wxCENTER);
188  {
189  S.StartMultiColumn(2, wxCENTER);
190  {
191  S.SetStretchyCol(1);
193  .AddChoice(_("Header:"),
195  &mHeaderNames);
197  .AddChoice(_("Encoding:"),
199  &mEncodingNames);
200  }
201  S.EndMultiColumn();
202  }
203  S.EndHorizontalLay();
204  }
205  S.EndVerticalLay();
206 
207  return;
208 }
209 
213 {
214  return true;
215 }
216 
220 {
221  ShuttleGui S(this, eIsSavingToPrefs);
223 
224  gPrefs->Flush();
225 
227 
228  return true;
229 }
230 
231 void ExportPCMOptions::OnHeaderChoice(wxCommandEvent & WXUNUSED(evt))
232 {
233  int format = sf_header_index_to_type(mHeaderChoice->GetSelection());
234  // Fix for Bug 1218 - AIFF with no option should default to 16 bit.
235  if( format == SF_FORMAT_AIFF )
236  format = SF_FORMAT_AIFF | SF_FORMAT_PCM_16;
237 
238  mEncodingNames.Clear();
239  mEncodingChoice->Clear();
240  mEncodingFormats.clear();
241  int sel = wxNOT_FOUND;
242  int i,j;
243 
244  int sfnum = sf_num_simple_formats();
245  std::vector<int> sfs;
246 
247  for (i = 0; i < sfnum; i++)
248  {
249  SF_FORMAT_INFO *fi = sf_simple_format(i);
250  sfs.push_back(fi->format);
251  }
252 
253  int num = sf_num_encodings();
254  for (i = 0; i < num; i++)
255  {
256  int enc = sf_encoding_index_to_subtype(i);
257  int fmt = format | enc;
258  bool valid = ValidatePair(fmt);
259  if (valid)
260  {
261  const auto name = sf_encoding_index_name(i);
262  mEncodingNames.Add(name);
263  mEncodingChoice->Append(name);
264  mEncodingFormats.push_back(enc);
265  for (j = 0; j < sfnum; j++)
266  {
267  int enc = sfs[j];
268  if ((sel == wxNOT_FOUND) && (fmt == enc))
269  {
270  sel = mEncodingFormats.size() - 1;
271  break;
272  }
273  }
274  }
275  }
276 
277  if (sel == wxNOT_FOUND) sel = 0;
278  mEncodingFromChoice = sel;
279  mEncodingChoice->SetSelection(sel);
281 
283 }
284 
286 {
287  int hdr = sf_header_index_to_type(mHeaderChoice->GetSelection());
288  int sel = mEncodingChoice->GetSelection();
289  int enc = mEncodingFormats[sel];
290  return hdr | enc;
291 }
292 
298 {
299  SF_INFO info;
300  memset(&info, 0, sizeof(info));
301  info.frames = 0;
302  info.samplerate = 44100;
303  info.channels = 1;
304  info.format = format;
305  info.sections = 1;
306  info.seekable = 0;
307 
308  return sf_format_check(&info) != 0 ? true : false;
309 }
310 
311 //----------------------------------------------------------------------------
312 // ExportPCM Class
313 //----------------------------------------------------------------------------
314 
315 class ExportPCM final : public ExportPlugin
316 {
317 public:
318 
319  ExportPCM();
320 
321  // Required
322 
323  wxWindow *OptionsCreate(wxWindow *parent, int format) override;
325  std::unique_ptr<ProgressDialog> &pDialog,
326  unsigned channels,
327  const wxString &fName,
328  bool selectedOnly,
329  double t0,
330  double t1,
331  MixerSpec *mixerSpec = NULL,
332  const Tags *metadata = NULL,
333  int subformat = 0) override;
334  // optional
335  wxString GetExtension(int index) override;
336  bool CheckFileName(wxFileName &filename, int format) override;
337 
338 private:
339 
340  ArrayOf<char> AdjustString(const wxString & wxStr, int sf_format);
341  bool AddStrings(AudacityProject *project, SNDFILE *sf, const Tags *tags, int sf_format);
342  bool AddID3Chunk(wxString fName, const Tags *tags, int sf_format);
343 
344 };
345 
347 : ExportPlugin()
348 {
349 
350  SF_INFO si;
351 
352  si.samplerate = 0;
353  si.channels = 0;
354 
355  int format; // the index of the format we are setting up at the moment
356 
357  // Add the "special" formats first
358  for (size_t i = 0; i < WXSIZEOF(kFormats); i++)
359  {
360  format = AddFormat() - 1;
361 
362  si.format = kFormats[i].format;
363  for (si.channels = 1; sf_format_check(&si); si.channels++)
364  ;
365  wxString ext = sf_header_extension(si.format);
366 
367  SetFormat(kFormats[i].name, format);
368  SetCanMetaData(true, format);
369  SetDescription(wxGetTranslation(kFormats[i].desc), format);
370  AddExtension(ext, format);
371  SetMaxChannels(si.channels - 1, format);
372  }
373 
374  // Then add the generic libsndfile formats
375  format = AddFormat() - 1; // store the index = 1 less than the count
376  SetFormat(wxT("LIBSNDFILE"), format);
377  SetCanMetaData(true, format);
378  SetDescription(_("Other uncompressed files"), format);
379  wxArrayString allext = sf_get_all_extensions();
380  wxString wavext = sf_header_extension(SF_FORMAT_WAV); // get WAV ext.
381 #if defined(wxMSW)
382  // On Windows make sure WAV is at the beginning of the list of all possible
383  // extensions for this format
384  allext.Remove(wavext);
385  allext.Insert(wavext, 0);
386 #endif
387  SetExtensions(allext, format);
388  SetMaxChannels(255, format);
389 }
390 
397  std::unique_ptr<ProgressDialog> &pDialog,
398  unsigned numChannels,
399  const wxString &fName,
400  bool selectionOnly,
401  double t0,
402  double t1,
403  MixerSpec *mixerSpec,
404  const Tags *metadata,
405  int subformat)
406 {
407  double rate = project->GetRate();
408  const TrackList *tracks = project->GetTracks();
409  int sf_format;
410 
411  if (subformat < 0 || static_cast<unsigned int>(subformat) >= WXSIZEOF(kFormats))
412  {
413  sf_format = ReadExportFormatPref();
414  }
415  else
416  {
417  sf_format = kFormats[subformat].format;
418  }
419 
420  auto updateResult = ProgressResult::Success;
421  {
422  wxFile f; // will be closed when it goes out of scope
423  SFFile sf; // wraps f
424 
425  wxString formatStr;
426  SF_INFO info;
427  //int err;
428 
429  //This whole operation should not occur while a file is being loaded on OD,
430  //(we are worried about reading from a file being written to,) so we block.
431  //Furthermore, we need to do this because libsndfile is not threadsafe.
432  formatStr = SFCall<wxString>(sf_header_name, sf_format & SF_FORMAT_TYPEMASK);
433 
434  // Use libsndfile to export file
435 
436  info.samplerate = (unsigned int)(rate + 0.5);
437  info.frames = (unsigned int)((t1 - t0)*rate + 0.5);
438  info.channels = numChannels;
439  info.format = sf_format;
440  info.sections = 1;
441  info.seekable = 0;
442 
443  // If we can't export exactly the format they requested,
444  // try the default format for that header type...
445  if (!sf_format_check(&info))
446  info.format = (info.format & SF_FORMAT_TYPEMASK);
447  if (!sf_format_check(&info)) {
448  AudacityMessageBox(_("Cannot export audio in this format."));
450  }
451 
452  if (f.Open(fName, wxFile::write)) {
453  // Even though there is an sf_open() that takes a filename, use the one that
454  // takes a file descriptor since wxWidgets can open a file with a Unicode name and
455  // libsndfile can't (under Windows).
456  sf.reset(SFCall<SNDFILE*>(sf_open_fd, f.fd(), SFM_WRITE, &info, FALSE));
457  //add clipping for integer formats. We allow floats to clip.
458  sf_command(sf.get(), SFC_SET_CLIPPING, NULL, sf_subtype_is_integer(sf_format)?SF_TRUE:SF_FALSE) ;
459  }
460 
461  if (!sf) {
462  AudacityMessageBox(wxString::Format(_("Cannot export audio to %s"),
463  fName));
465  }
466  // Retrieve tags if not given a set
467  if (metadata == NULL)
468  metadata = project->GetTags();
469 
470  // Install the metata at the beginning of the file (except for
471  // WAV and WAVEX formats)
472  if ((sf_format & SF_FORMAT_TYPEMASK) != SF_FORMAT_WAV &&
473  (sf_format & SF_FORMAT_TYPEMASK) != SF_FORMAT_WAVEX) {
474  if (!AddStrings(project, sf.get(), metadata, sf_format)) {
476  }
477  }
478 
480  if (sf_subtype_more_than_16_bits(info.format))
481  format = floatSample;
482  else
483  format = int16Sample;
484 
485  size_t maxBlockLen = 44100 * 5;
486 
487  const WaveTrackConstArray waveTracks =
488  tracks->GetWaveTrackConstArray(selectionOnly, false);
489  {
490  wxASSERT(info.channels >= 0);
491  auto mixer = CreateMixer(waveTracks,
492  tracks->GetTimeTrack(),
493  t0, t1,
494  info.channels, maxBlockLen, true,
495  rate, format, true, mixerSpec);
496 
497  InitProgress( pDialog, wxFileName(fName).GetName(),
498  selectionOnly
499  ? wxString::Format(_("Exporting the selected audio as %s"),
500  formatStr)
501  : wxString::Format(_("Exporting the audio as %s"),
502  formatStr) );
503  auto &progress = *pDialog;
504 
505  while (updateResult == ProgressResult::Success) {
506  sf_count_t samplesWritten;
507  size_t numSamples = mixer->Process(maxBlockLen);
508 
509  if (numSamples == 0)
510  break;
511 
512  samplePtr mixed = mixer->GetBuffer();
513 
514  if (format == int16Sample)
515  samplesWritten = SFCall<sf_count_t>(sf_writef_short, sf.get(), (short *)mixed, numSamples);
516  else
517  samplesWritten = SFCall<sf_count_t>(sf_writef_float, sf.get(), (float *)mixed, numSamples);
518 
519  if (static_cast<size_t>(samplesWritten) != numSamples) {
520  char buffer2[1000];
521  sf_error_str(sf.get(), buffer2, 1000);
522  AudacityMessageBox(wxString::Format(
523  /* i18n-hint: %s will be the error message from libsndfile, which
524  * is usually something unhelpful (and untranslated) like "system
525  * error" */
526  _("Error while writing %s file (disk full?).\nLibsndfile says \"%s\""),
527  formatStr,
528  wxString::FromAscii(buffer2)));
529  updateResult = ProgressResult::Cancelled;
530  break;
531  }
532 
533  updateResult = progress.Update(mixer->MixGetCurrentTime() - t0, t1 - t0);
534  }
535  }
536 
537  // Install the WAV metata in a "LIST" chunk at the end of the file
538  if (updateResult == ProgressResult::Success ||
539  updateResult == ProgressResult::Stopped) {
540  if ((sf_format & SF_FORMAT_TYPEMASK) == SF_FORMAT_WAV ||
541  (sf_format & SF_FORMAT_TYPEMASK) == SF_FORMAT_WAVEX) {
542  if (!AddStrings(project, sf.get(), metadata, sf_format)) {
543  // TODO: more precise message
544  AudacityMessageBox(_("Unable to export"));
546  }
547  }
548  if (0 != sf.close()) {
549  // TODO: more precise message
550  AudacityMessageBox(_("Unable to export"));
552  }
553  }
554  }
555 
556  if (updateResult == ProgressResult::Success ||
557  updateResult == ProgressResult::Stopped)
558  if (((sf_format & SF_FORMAT_TYPEMASK) == SF_FORMAT_AIFF) ||
559  ((sf_format & SF_FORMAT_TYPEMASK) == SF_FORMAT_WAV))
560  // Note: file has closed, and gets reopened and closed again here:
561  if (!AddID3Chunk(fName, metadata, sf_format) ) {
562  // TODO: more precise message
563  AudacityMessageBox(_("Unable to export"));
565  }
566 
567  return updateResult;
568 }
569 
570 ArrayOf<char> ExportPCM::AdjustString(const wxString & wxStr, int sf_format)
571 {
572  bool b_aiff = false;
573  if ((sf_format & SF_FORMAT_TYPEMASK) == SF_FORMAT_AIFF)
574  b_aiff = true; // Apple AIFF file
575 
576  // We must convert the string to 7 bit ASCII
577  size_t sz = wxStr.length();
578  if(sz == 0)
579  return {};
580  // Size for secure allocation in case of local wide char usage
581  size_t sr = (sz+4) * 2;
582 
583  ArrayOf<char> pDest{ sr, true };
584  if (!pDest)
585  return {};
586  ArrayOf<char> pSrc{ sr, true };
587  if (!pSrc)
588  return {};
589 
590  if(wxStr.mb_str(wxConvISO8859_1))
591  strncpy(pSrc.get(), wxStr.mb_str(wxConvISO8859_1), sz);
592  else if(wxStr.mb_str())
593  strncpy(pSrc.get(), wxStr.mb_str(), sz);
594  else
595  return {};
596 
597  char *pD = pDest.get();
598  char *pS = pSrc.get();
599  unsigned char c;
600 
601  // ISO Latin to 7 bit ascii conversion table (best approximation)
602  static char aASCII7Table[256] = {
603  0x00, 0x5f, 0x5f, 0x5f, 0x5f, 0x5f, 0x5f, 0x5f,
604  0x5f, 0x09, 0x0a, 0x5f, 0x0d, 0x5f, 0x5f, 0x5f,
605  0x5f, 0x5f, 0x5f, 0x5f, 0x5f, 0x5f, 0x5f, 0x5f,
606  0x5f, 0x5f, 0x5f, 0x5f, 0x5f, 0x5f, 0x5f, 0x5f,
607  0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27,
608  0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f,
609  0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37,
610  0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f,
611  0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47,
612  0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f,
613  0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57,
614  0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f,
615  0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67,
616  0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f,
617  0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77,
618  0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f,
619  0x45, 0x20, 0x2c, 0x53, 0x22, 0x2e, 0x2b, 0x2b,
620  0x5e, 0x25, 0x53, 0x28, 0x4f, 0x20, 0x5a, 0x20,
621  0x20, 0x27, 0x27, 0x22, 0x22, 0x2e, 0x2d, 0x5f,
622  0x22, 0x54, 0x73, 0x29, 0x6f, 0x20, 0x7a, 0x59,
623  0x20, 0x21, 0x63, 0x4c, 0x6f, 0x59, 0x7c, 0x53,
624  0x22, 0x43, 0x61, 0x22, 0x5f, 0x2d, 0x43, 0x2d,
625  0x6f, 0x7e, 0x32, 0x33, 0x27, 0x75, 0x50, 0x27,
626  0x2c, 0x31, 0x6f, 0x22, 0x5f, 0x5f, 0x5f, 0x3f,
627  0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x43,
628  0x45, 0x45, 0x45, 0x45, 0x49, 0x49, 0x49, 0x49,
629  0x44, 0x4e, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x78,
630  0x4f, 0x55, 0x55, 0x55, 0x55, 0x59, 0x70, 0x53,
631  0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x63,
632  0x65, 0x65, 0x65, 0x65, 0x69, 0x69, 0x69, 0x69,
633  0x64, 0x6e, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x2f,
634  0x6f, 0x75, 0x75, 0x75, 0x75, 0x79, 0x70, 0x79
635  };
636 
637  size_t i;
638  for(i = 0; i < sr; i++) {
639  c = (unsigned char) *pS++;
640  *pD++ = aASCII7Table[c];
641  if(c == 0)
642  break;
643  }
644  *pD = '\0';
645 
646  if(b_aiff) {
647  int len = (int)strlen(pDest.get());
648  if((len % 2) != 0) {
649  // In case of an odd length string, add a space char
650  strcat(pDest.get(), " ");
651  }
652  }
653 
654  return pDest;
655 }
656 
657 bool ExportPCM::AddStrings(AudacityProject * WXUNUSED(project), SNDFILE *sf, const Tags *tags, int sf_format)
658 {
659  if (tags->HasTag(TAG_TITLE)) {
660  auto ascii7Str = AdjustString(tags->GetTag(TAG_TITLE), sf_format);
661  if (ascii7Str) {
662  sf_set_string(sf, SF_STR_TITLE, ascii7Str.get());
663  }
664  }
665 
666  if (tags->HasTag(TAG_ALBUM)) {
667  auto ascii7Str = AdjustString(tags->GetTag(TAG_ALBUM), sf_format);
668  if (ascii7Str) {
669  sf_set_string(sf, SF_STR_ALBUM, ascii7Str.get());
670  }
671  }
672 
673  if (tags->HasTag(TAG_ARTIST)) {
674  auto ascii7Str = AdjustString(tags->GetTag(TAG_ARTIST), sf_format);
675  if (ascii7Str) {
676  sf_set_string(sf, SF_STR_ARTIST, ascii7Str.get());
677  }
678  }
679 
680  if (tags->HasTag(TAG_COMMENTS)) {
681  auto ascii7Str = AdjustString(tags->GetTag(TAG_COMMENTS), sf_format);
682  if (ascii7Str) {
683  sf_set_string(sf, SF_STR_COMMENT, ascii7Str.get());
684  }
685  }
686 
687  if (tags->HasTag(TAG_YEAR)) {
688  auto ascii7Str = AdjustString(tags->GetTag(TAG_YEAR), sf_format);
689  if (ascii7Str) {
690  sf_set_string(sf, SF_STR_DATE, ascii7Str.get());
691  }
692  }
693 
694  if (tags->HasTag(TAG_GENRE)) {
695  auto ascii7Str = AdjustString(tags->GetTag(TAG_GENRE), sf_format);
696  if (ascii7Str) {
697  sf_set_string(sf, SF_STR_GENRE, ascii7Str.get());
698  }
699  }
700 
701  if (tags->HasTag(TAG_COPYRIGHT)) {
702  auto ascii7Str = AdjustString(tags->GetTag(TAG_COPYRIGHT), sf_format);
703  if (ascii7Str) {
704  sf_set_string(sf, SF_STR_COPYRIGHT, ascii7Str.get());
705  }
706  }
707 
708  if (tags->HasTag(TAG_SOFTWARE)) {
709  auto ascii7Str = AdjustString(tags->GetTag(TAG_SOFTWARE), sf_format);
710  if (ascii7Str) {
711  sf_set_string(sf, SF_STR_SOFTWARE, ascii7Str.get());
712  }
713  }
714 
715  if (tags->HasTag(TAG_TRACK)) {
716  auto ascii7Str = AdjustString(tags->GetTag(TAG_TRACK), sf_format);
717  if (ascii7Str) {
718  sf_set_string(sf, SF_STR_TRACKNUMBER, ascii7Str.get());
719  }
720  }
721 
722  return true;
723 }
724 
725 #ifdef USE_LIBID3TAG
726 struct id3_tag_deleter {
727  void operator () (id3_tag *p) const { if (p) id3_tag_delete(p); }
728 };
729 using id3_tag_holder = std::unique_ptr<id3_tag, id3_tag_deleter>;
730 #endif
731 
732 bool ExportPCM::AddID3Chunk(wxString fName, const Tags *tags, int sf_format)
733 {
734 #ifdef USE_LIBID3TAG
735  id3_tag_holder tp { id3_tag_new() };
736 
737  for (const auto &pair : tags->GetRange()) {
738  const auto &n = pair.first;
739  const auto &v = pair.second;
740  const char *name = "TXXX";
741 
742  if (n.CmpNoCase(TAG_TITLE) == 0) {
743  name = ID3_FRAME_TITLE;
744  }
745  else if (n.CmpNoCase(TAG_ARTIST) == 0) {
746  name = ID3_FRAME_ARTIST;
747  }
748  else if (n.CmpNoCase(TAG_ALBUM) == 0) {
749  name = ID3_FRAME_ALBUM;
750  }
751  else if (n.CmpNoCase(TAG_YEAR) == 0) {
752  name = ID3_FRAME_YEAR;
753  }
754  else if (n.CmpNoCase(TAG_GENRE) == 0) {
755  name = ID3_FRAME_GENRE;
756  }
757  else if (n.CmpNoCase(TAG_COMMENTS) == 0) {
758  name = ID3_FRAME_COMMENT;
759  }
760  else if (n.CmpNoCase(TAG_TRACK) == 0) {
761  name = ID3_FRAME_TRACK;
762  }
763  else if (n.CmpNoCase(wxT("composer")) == 0) {
764  name = "TCOM";
765  }
766 
767  struct id3_frame *frame = id3_frame_new(name);
768 
769  if (!n.IsAscii() || !v.IsAscii()) {
770  id3_field_settextencoding(id3_frame_field(frame, 0), ID3_FIELD_TEXTENCODING_UTF_16);
771  }
772  else {
773  id3_field_settextencoding(id3_frame_field(frame, 0), ID3_FIELD_TEXTENCODING_ISO_8859_1);
774  }
775 
777  id3_utf8_ucs4duplicate((id3_utf8_t *) (const char *) v.mb_str(wxConvUTF8)) };
778 
779  if (strcmp(name, ID3_FRAME_COMMENT) == 0) {
780  // A hack to get around iTunes not recognizing the comment. The
781  // language defaults to XXX and, since it's not a valid language,
782  // iTunes just ignores the tag. So, either set it to a valid language
783  // (which one???) or just clear it. Unfortunately, there's no supported
784  // way of clearing the field, so do it directly.
785  id3_field *f = id3_frame_field(frame, 1);
786  memset(f->immediate.value, 0, sizeof(f->immediate.value));
787  id3_field_setfullstring(id3_frame_field(frame, 3), ucs4.get());
788  }
789  else if (strcmp(name, "TXXX") == 0) {
790  id3_field_setstring(id3_frame_field(frame, 2), ucs4.get());
791 
792  ucs4.reset(id3_utf8_ucs4duplicate((id3_utf8_t *) (const char *) n.mb_str(wxConvUTF8)));
793 
794  id3_field_setstring(id3_frame_field(frame, 1), ucs4.get());
795  }
796  else {
797  auto addr = ucs4.get();
798  id3_field_setstrings(id3_frame_field(frame, 1), 1, &addr);
799  }
800 
801  id3_tag_attachframe(tp.get(), frame);
802  }
803 
804  tp->options &= (~ID3_TAG_OPTION_COMPRESSION); // No compression
805 
806  // If this version of libid3tag supports it, use v2.3 ID3
807  // tags instead of the newer, but less well supported, v2.4
808  // that libid3tag uses by default.
809 #ifdef ID3_TAG_HAS_TAG_OPTION_ID3V2_3
810  tp->options |= ID3_TAG_OPTION_ID3V2_3;
811 #endif
812 
813  id3_length_t len;
814 
815  len = id3_tag_render(tp.get(), 0);
816  if (len == 0)
817  return true;
818 
819  if ((len % 2) != 0) len++; // Length must be even.
820  ArrayOf<id3_byte_t> buffer { len, true };
821  if (buffer == NULL)
822  return false;
823 
824  // Zero all locations, for ending odd UTF16 content
825  // correctly, i.e., two '\0's at the end.
826 
827  id3_tag_render(tp.get(), buffer.get());
828 
829  wxFFile f(fName, wxT("r+b"));
830  if (f.IsOpened()) {
831  wxUint32 sz;
832 
833  sz = (wxUint32) len;
834  if (!f.SeekEnd(0))
835  return false;
836  if ((sf_format & SF_FORMAT_TYPEMASK) == SF_FORMAT_WAV)
837  {
838  if (4 != f.Write("id3 ", 4))// Must be lower case for foobar2000.
839  return false ;
840  }
841  else {
842  if (4 != f.Write("ID3 ", 4))
843  return false;
844  sz = wxUINT32_SWAP_ON_LE(sz);
845  }
846  if (4 != f.Write(&sz, 4))
847  return false;
848 
849  if (len != f.Write(buffer.get(), len))
850  return false;
851 
852  sz = (wxUint32) f.Tell() - 8;
853  if ((sf_format & SF_FORMAT_TYPEMASK) == SF_FORMAT_AIFF)
854  sz = wxUINT32_SWAP_ON_LE(sz);
855 
856  if (!f.Seek(4))
857  return false;
858  if (4 != f.Write(&sz, 4))
859  return false;
860 
861  if (!f.Flush())
862  return false;
863 
864  if (!f.Close())
865  return false;
866  }
867  else
868  return false;
869 #endif
870  return true;
871 }
872 
873 wxWindow *ExportPCM::OptionsCreate(wxWindow *parent, int format)
874 {
875  wxASSERT(parent); // to justify safenew
876  // default, full user control
877  if (format < 0 || static_cast<unsigned int>(format) >= WXSIZEOF(kFormats))
878  {
879  return safenew ExportPCMOptions(parent, format);
880  }
881 
882  return ExportPlugin::OptionsCreate(parent, format);
883 }
884 
885 wxString ExportPCM::GetExtension(int index)
886 {
887  if (index == WXSIZEOF(kFormats)) {
888  // get extension libsndfile thinks is correct for currently selected format
890  }
891  else {
892  // return the default
893  return ExportPlugin::GetExtension(index);
894  }
895 }
896 
897 bool ExportPCM::CheckFileName(wxFileName &filename, int format)
898 {
899  if (format == WXSIZEOF(kFormats) &&
900  IsExtension(filename.GetExt(), format)) {
901  // PRL: Bug1217
902  // If the user left the extension blank, then the
903  // file dialog will have defaulted the extension, beyond our control,
904  // to the first in the wildcard list or (Linux) the last-saved extension,
905  // ignoring what we try to do with the additional drop-down mHeaderChoice.
906  // Here we can intercept file name processing and impose the correct default.
907  // However this has the consequence that in case an explicit extension was typed,
908  // we override it without asking.
909  filename.SetExt(GetExtension(format));
910  }
911 
912  return ExportPlugin::CheckFileName(filename, format);
913 }
914 
915 std::unique_ptr<ExportPlugin> New_ExportPCM()
916 {
917  return std::make_unique<ExportPCM>();
918 }
virtual bool CheckFileName(wxFileName &filename, int format=0)
Definition: Export.cpp:85
AudacityPrefs * gPrefs
Definition: Prefs.cpp:73
A list of TrackListNode items.
Definition: Track.h:618
virtual bool IsExtension(const wxString &ext, int index)
Definition: Export.cpp:196
ProgressResult
wxString sf_encoding_index_name(int i)
Get the string name of the data encoding of the requested format.
Definition: FileFormats.cpp:84
#define TAG_TRACK
Definition: Tags.h:63
ExportPCMOptions(wxWindow *parent, int format)
Definition: ExportPCM.cpp:134
static void WriteExportFormatPref(int format)
Definition: ExportPCM.cpp:86
Derived from ShuttleGuiBase, an Audacity specific class for shuttling data to and from GUI...
Definition: ShuttleGui.h:409
wxString sf_header_index_name(int format)
Get the name of a container format from libsndfile.
Definition: FileFormats.cpp:47
wxString GetExtension(int index) override
Return the (first) file name extension for the sub-format.
Definition: ExportPCM.cpp:885
const wxChar * desc
Definition: ExportPCM.cpp:58
bool CheckFileName(wxFileName &filename, int format) override
Definition: ExportPCM.cpp:897
wxChoice * mEncodingChoice
Definition: ExportPCM.cpp:122
bool sf_subtype_more_than_16_bits(unsigned int format)
void SetDescription(const wxString &description, int index)
Definition: Export.cpp:118
wxArrayString mEncodingNames
Definition: ExportPCM.cpp:120
SF_FORMAT_INFO * sf_simple_format(int i)
bool AddID3Chunk(wxString fName, const Tags *tags, int sf_format)
Definition: ExportPCM.cpp:732
void EndMultiColumn()
#define TAG_TITLE
Definition: Tags.h:60
bool TransferDataFromWindow() override
Definition: ExportPCM.cpp:219
virtual wxWindow * OptionsCreate(wxWindow *parent, int format)=0
Definition: Export.cpp:215
#define XO(s)
Definition: Internat.h:33
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
#define TAG_ARTIST
Definition: Tags.h:61
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
#define safenew
Definition: Audacity.h:230
std::unique_ptr< Character[], freer > MallocString
Definition: MemoryX.h:417
void EndHorizontalLay()
void SetExtensions(const wxArrayString &extensions, int index)
Definition: Export.cpp:128
void OnHeaderChoice(wxCommandEvent &evt)
Definition: ExportPCM.cpp:231
#define TAG_SOFTWARE
Definition: Tags.h:67
struct @41 kFormats[]
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
ArrayOf< char > AdjustString(const wxString &wxStr, int sf_format)
Definition: ExportPCM.cpp:570
virtual ~ExportPCMOptions()
Definition: ExportPCM.cpp:178
wxArrayString mHeaderNames
Definition: ExportPCM.cpp:119
int sf_num_encodings()
Get the number of data encodings libsndfile supports (in any container or none.
Definition: FileFormats.cpp:75
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
wxChoice * AddChoice(const wxString &Prompt, const wxString &Selected, const wxArrayString *pChoices)
Definition: ShuttleGui.cpp:371
int close()
Definition: FileFormats.h:139
wxString sf_header_name(int format)
Get the string name of the specified container format.
sampleFormat
Definition: Types.h:188
bool sf_subtype_is_integer(unsigned int format)
std::vector< int > mEncodingFormats
Definition: ExportPCM.cpp:125
void PopulateOrExchange(ShuttleGui &S)
Definition: ExportPCM.cpp:183
char * samplePtr
Definition: Types.h:203
ShuttleGui & Id(int id)
wxString sf_header_extension(int format)
Get the most common file extension for the given format.
bool AddStrings(AudacityProject *project, SNDFILE *sf, const Tags *tags, int sf_format)
Definition: ExportPCM.cpp:657
bool TransferDataToWindow() override
Definition: ExportPCM.cpp:212
unsigned int sf_encoding_index_to_subtype(int i)
Definition: FileFormats.cpp:95
virtual wxString GetExtension(int index=0)
Return the (first) file name extension for the sub-format.
Definition: Export.cpp:158
ID3 Tags (for MP3)
Definition: Tags.h:70
bool ValidatePair(int format)
Definition: ExportPCM.cpp:297
wxString GetTag(const wxString &name) const
Definition: Tags.cpp:424
_("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
wxArrayString sf_get_all_extensions()
#define TAG_COPYRIGHT
Definition: Tags.h:68
int AddFormat()
Add a NEW entry to the list of formats this plug-in can export.
Definition: Export.cpp:97
wxChoice * mHeaderChoice
Definition: ExportPCM.cpp:121
void SetCanMetaData(bool canmetadata, int index)
Definition: Export.cpp:143
void AddExtension(const wxString &extension, int index)
Definition: Export.cpp:123
#define TAG_COMMENTS
Definition: Tags.h:66
const Tags * GetTags()
Definition: Project.cpp:1453
#define ID_HEADER_CHOICE
Definition: ExportPCM.cpp:96
#define TAG_GENRE
Definition: Tags.h:65
static int ReadExportFormatPref()
Definition: ExportPCM.cpp:75
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
Definition: ExportPCM.cpp:396
unsigned int sf_header_index_to_type(int i)
Definition: FileFormats.cpp:59
double GetRate() const
Definition: Project.h:199
std::unique_ptr< ExportPlugin > New_ExportPCM()
Definition: ExportPCM.cpp:915
END_EVENT_TABLE()
int sf_num_simple_formats()
TrackList * GetTracks()
Definition: Project.h:192
int sf_num_headers()
Get the number of container formats supported by libsndfile.
Definition: FileFormats.cpp:37
wxWindow * OptionsCreate(wxWindow *parent, int format) override
Definition: ExportPCM.cpp:873
#define ID_ENCODING_CHOICE
Definition: ExportPCM.cpp:97
static void InitProgress(std::unique_ptr< ProgressDialog > &pDialog, const wxString &title, const wxString &message)
Definition: Export.cpp:253
Iterators GetRange() const
Definition: Tags.cpp:444
const wxChar * name
Definition: ExportPCM.cpp:57
void SetStretchyCol(int i)
Used to modify an already placed FlexGridSizer to make a column stretchy.
Definition: ShuttleGui.cpp:203
#define TAG_ALBUM
Definition: Tags.h:62
#define TAG_YEAR
Definition: Tags.h:64
Class used with Mixer.
Definition: Mix.h:58
bool HasTag(const wxString &name) const
Definition: Tags.cpp:415
void StartVerticalLay(int iProp=1)