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