Audacity  2.2.2
ImportPCM.cpp
Go to the documentation of this file.
1 /**********************************************************************
2 
3  Audacity: A Digital Audio Editor
4 
5  ImportPCM.cpp
6 
7  Dominic Mazzoni
8  Leland Lucius
9 
10 *//****************************************************************//****************************************************************//*******************************************************************/
21 
22 #include "../Audacity.h"
23 #include "ImportPCM.h"
24 #include "../Internat.h"
25 #include "../Tags.h"
26 
27 #include <wx/wx.h>
28 #include <wx/string.h>
29 #include <wx/utils.h>
30 #include <wx/intl.h>
31 #include <wx/ffile.h>
32 #include <wx/sizer.h>
33 #include <wx/checkbox.h>
34 #include <wx/button.h>
35 #include <wx/stattext.h>
36 
37 #include "sndfile.h"
38 
39 #include "../ondemand/ODManager.h"
40 #include "../ondemand/ODComputeSummaryTask.h"
41 #include "../prefs/QualityPrefs.h"
42 
43 //If OD is enabled, he minimum number of samples a file has to use it.
44 //Otherwise, we use the older PCMAliasBlockFile method since it should be fast enough.
45 #define kMinimumODFileSampleSize 44100*30
46 
47 #ifndef SNDFILE_1
48 #error Requires libsndfile 1.0 or higher
49 #endif
50 
51 #include "../FileFormats.h"
52 #include "../Prefs.h"
53 #include "../WaveTrack.h"
54 #include "ImportPlugin.h"
55 
56 #include <algorithm>
57 
58 #ifdef USE_LIBID3TAG
59  #include <id3tag.h>
60  // DM: the following functions were supposed to have been
61  // included in id3tag.h - should be fixed in the next release
62  // of mad.
63  extern "C" {
64  struct id3_frame *id3_frame_new(char const *);
65  id3_length_t id3_latin1_length(id3_latin1_t const *);
66  void id3_latin1_decode(id3_latin1_t const *, id3_ucs4_t *);
67  }
68 #endif
69 
70 #define DESC _("WAV, AIFF, and other uncompressed types")
71 
72 class PCMImportPlugin final : public ImportPlugin
73 {
74 public:
76  : ImportPlugin(wxArrayString())
77  {
79  }
80 
82 
83  wxString GetPluginStringID() override { return wxT("libsndfile"); }
84  wxString GetPluginFormatDescription() override;
85  std::unique_ptr<ImportFileHandle> Open(const wxString &Filename) override;
86 };
87 
88 
90 {
91 public:
92  PCMImportFileHandle(wxString name, SFFile &&file, SF_INFO info);
94 
95  wxString GetFileDescription() override;
97  ProgressResult Import(TrackFactory *trackFactory, TrackHolders &outTracks,
98  Tags *tags) override;
99 
100  wxInt32 GetStreamCount() override { return 1; }
101 
102  const wxArrayString &GetStreamInfo() override
103  {
104  static wxArrayString empty;
105  return empty;
106  }
107 
108  void SetStreamUsage(wxInt32 WXUNUSED(StreamID), bool WXUNUSED(Use)) override
109  {}
110 
111 private:
113  const SF_INFO mInfo;
115 };
116 
117 void GetPCMImportPlugin(ImportPluginList & importPluginList,
118  UnusableImportPluginList & WXUNUSED(unusableImportPluginList))
119 {
120  importPluginList.push_back( std::make_unique<PCMImportPlugin>() );
121 }
122 
124 {
125  return DESC;
126 }
127 
128 std::unique_ptr<ImportFileHandle> PCMImportPlugin::Open(const wxString &filename)
129 {
130  SF_INFO info;
131  wxFile f; // will be closed when it goes out of scope
132  SFFile file;
133 
134  memset(&info, 0, sizeof(info));
135 
136 
137 #ifdef __WXGTK__
138  if (filename.Lower().EndsWith(wxT("mp3"))) {
139  // There is a bug in libsndfile where mp3s with duplicated metadata tags
140  // will crash libsndfile and thus audacity.
141  // We have patched the lib-src version of libsndfile, but
142  // for linux the user can build against the system libsndfile which
143  // still has this bug.
144  // This happens in sf_open_fd, which is the very first point of
145  // interaction with libsndfile, so the only workaround is to hardcode
146  // ImportPCM to not handle .mp3. Of couse, this will still fail for mp3s
147  // that are mislabeled with a .wav or other extension.
148  // So, in the future we may want to write a simple parser to detect mp3s here.
149  return NULL;
150  }
151 #endif
152 
153 
154  if (f.Open(filename)) {
155  // Even though there is an sf_open() that takes a filename, use the one that
156  // takes a file descriptor since wxWidgets can open a file with a Unicode name and
157  // libsndfile can't (under Windows).
158  file.reset(SFCall<SNDFILE*>(sf_open_fd, f.fd(), SFM_READ, &info, TRUE));
159  }
160 
161  // The file descriptor is now owned by "file", so we must tell "f" to leave
162  // it alone. The file descriptor is closed by the destructor of file even if an error
163  // occurs.
164  f.Detach();
165 
166  if (!file) {
167  // TODO: Handle error
168  //char str[1000];
169  //sf_error_str((SNDFILE *)NULL, str, 1000);
170 
171  return nullptr;
172  } else if (file &&
173  (info.format & SF_FORMAT_TYPEMASK) == SF_FORMAT_OGG) {
174  // mchinen 15.1.2012 - disallowing libsndfile to handle
175  // ogg files because seeking is broken at this date (very slow,
176  // seeks from beginning of file each seek).
177  // This was said by Erik (libsndfile maintainer).
178  // Note that this won't apply to our local libsndfile, so only
179  // linux builds that use --with-libsndfile=system are affected,
180  // as our local libsndfile doesn't do OGG.
181  // In particular ubuntu 10.10 and 11.04 are known to be affected
182  // When the bug is fixed, we can check version to avoid only
183  // the broken builds.
184 
185  return nullptr;
186  }
187 
188  // Success, so now transfer the duty to close the file from "file".
189  return std::make_unique<PCMImportFileHandle>(filename, std::move(file), info);
190 }
191 
193  SFFile &&file, SF_INFO info)
194 : ImportFileHandle(name),
195  mFile(std::move(file)),
196  mInfo(info)
197 {
198  wxASSERT(info.channels >= 0);
199 
200  //
201  // Figure out the format to use.
202  //
203  // In general, go with the user's preferences. However, if
204  // the file is higher-quality, go with a format which preserves
205  // the quality of the original file.
206  //
207 
209 
210  if (mFormat != floatSample &&
213 }
214 
216 {
217  return SFCall<wxString>(sf_header_name, mInfo.format);
218 }
219 
221 {
222  return mInfo.frames * mInfo.channels * SAMPLE_SIZE(mFormat);
223 }
224 
225 // returns "copy" or "edit" (aliased) as the user selects.
226 // if the cancel button is hit then "cancel" is returned.
227 static wxString AskCopyOrEdit()
228 {
229  wxString oldCopyPref = gPrefs->Read(wxT("/FileFormats/CopyOrEditUncompressedData"), wxT("copy"));
230  bool firstTimeAsk = gPrefs->Read(wxT("/Warnings/CopyOrEditUncompressedDataFirstAsk"), true)?true:false;
231  bool oldAskPref = gPrefs->Read(wxT("/Warnings/CopyOrEditUncompressedDataAsk"), true)?true:false;
232 
233  // The first time the user is asked we force it to 'copy'.
234  // This effectively does a one-time change to the preferences.
235  if (firstTimeAsk) {
236  if (oldCopyPref != wxT("copy")) {
237  gPrefs->Write(wxT("/FileFormats/CopyOrEditUncompressedData"), wxT("copy"));
238  oldCopyPref = wxT("copy");
239  }
240  gPrefs->Write(wxT("/Warnings/CopyOrEditUncompressedDataFirstAsk"), (long) false);
241  gPrefs->Flush();
242  }
243 
244  // check the current preferences for whether or not we should ask the user about this.
245  if (oldAskPref) {
246  wxString newCopyPref = wxT("copy");
247  wxDialogWrapper dialog(nullptr, -1, wxString(_("Warning")));
248  dialog.SetName(dialog.GetTitle());
249 
250  wxBoxSizer *vbox;
251  dialog.SetSizer(vbox = safenew wxBoxSizer(wxVERTICAL));
252 
253  wxString clause1 = _(
254 "When importing uncompressed audio files you can either copy them into the project,"
255 " or read them directly from their current location (without copying).\n\n"
256  );
257 
258  wxString clause2 = oldCopyPref == wxT("copy")
259  ? _("Your current preference is set to copy in.\n\n")
260  : _("Your current preference is set to read directly.\n\n")
261  ;
262 
263  wxString clause3 = _(
264 "Reading the files directly allows you to play or edit them almost immediately. "
265 "This is less safe than copying in, because you must retain the files with their "
266 "original names in their original locations.\n"
267 "Help > Diagnostics > Check Dependencies will show the original names and locations of any files "
268 "that you are reading directly.\n\n"
269 "How do you want to import the current file(s)?"
270  );
271 
272  wxStaticText *message =
273  safenew wxStaticText(&dialog, -1, clause1 + clause2 + clause3);
274 
275  message->Wrap(500);
276  message->SetName(message->GetLabel());
277 
278  vbox->Add(message, 1, wxALL | wxEXPAND, 10);
279 
280  wxStaticBox *box = safenew wxStaticBox(&dialog, -1, _("Choose an import method"));
281  box->SetName(box->GetLabel());
282 
283  wxRadioButton *aliasRadio;
284  wxRadioButton *copyRadio;
285  wxCheckBox *dontAskNextTimeBox;
286 
287  {
288  auto boxsizer = std::make_unique<wxStaticBoxSizer>(box, wxVERTICAL);
289 
290  copyRadio = safenew wxRadioButton(&dialog, -1, _("Make a &copy of the files before editing (safer)"), wxDefaultPosition, wxDefaultSize, wxRB_GROUP);
291  boxsizer->Add(copyRadio, 0, wxALL);
292  copyRadio->SetName(wxStripMenuCodes(copyRadio->GetLabel()));
293 
294  aliasRadio = safenew wxRadioButton(&dialog, -1, _("Read the files &directly from the original (faster)"));
295  boxsizer->Add(aliasRadio, 0, wxALL);
296  aliasRadio->SetName(wxStripMenuCodes(aliasRadio->GetLabel()));
297 
298  dontAskNextTimeBox = safenew wxCheckBox(&dialog, -1, _("Don't &warn again and always use my choice above"));
299  boxsizer->Add(dontAskNextTimeBox, 0, wxALL);
300  vbox->Add(boxsizer.release(), 0, wxALL, 10);
301  }
302 
303  dontAskNextTimeBox->SetName(wxStripMenuCodes(dontAskNextTimeBox->GetLabel()));
304 
305 
306  wxRadioButton *prefsRadio = oldCopyPref == wxT("copy") ? copyRadio : aliasRadio;
307  prefsRadio->SetValue(true);
308 
309  wxSizer *buttonSizer = dialog.CreateButtonSizer(wxOK | wxCANCEL);
310  vbox->Add(buttonSizer, 0, wxALL | wxEXPAND, 10);
311 
312  dialog.SetSize(dialog.GetBestSize());
313  dialog.Layout();
314  dialog.Center();
315 
316  if (dialog.ShowModal() == wxID_OK) {
317  if (aliasRadio->GetValue()) {
318  newCopyPref = wxT("edit");
319  }
320  if (dontAskNextTimeBox->IsChecked()) {
321  gPrefs->Write(wxT("/Warnings/CopyOrEditUncompressedDataAsk"), (long) false);
322  gPrefs->Flush();
323  }
324  } else {
325  return wxT("cancel");
326  }
327 
328  // if the preference changed, save it.
329  if (newCopyPref != oldCopyPref) {
330  gPrefs->Write(wxT("/FileFormats/CopyOrEditUncompressedData"), newCopyPref);
331  gPrefs->Flush();
332  }
333  oldCopyPref = newCopyPref;
334  }
335  return oldCopyPref;
336 }
337 
338 #ifdef USE_LIBID3TAG
339 struct id3_tag_deleter {
340  void operator () (id3_tag *p) const { if (p) id3_tag_delete(p); }
341 };
342 using id3_tag_holder = std::unique_ptr<id3_tag, id3_tag_deleter>;
343 #endif
344 
346  TrackHolders &outTracks,
347  Tags *tags)
348 {
349  outTracks.clear();
350 
351  wxASSERT(mFile.get());
352 
353  // Get the preference / warn the user about aliased files.
354  wxString copyEdit = AskCopyOrEdit();
355 
356  if (copyEdit == wxT("cancel"))
358 
359  // Fall back to "copy" if it doesn't match anything else, since it is safer
360  bool doEdit = false;
361  if (copyEdit.IsSameAs(wxT("edit"), false))
362  doEdit = true;
363 
364 
365  CreateProgress();
366 
367  TrackHolders channels(mInfo.channels);
368 
369  auto iter = channels.begin();
370  for (int c = 0; c < mInfo.channels; ++iter, ++c) {
371  *iter = trackFactory->NewWaveTrack(mFormat, mInfo.samplerate);
372 
373  if (mInfo.channels > 1)
374  switch (c) {
375  case 0:
376  iter->get()->SetChannel(Track::LeftChannel);
377  break;
378  case 1:
379  iter->get()->SetChannel(Track::RightChannel);
380  break;
381  default:
382  iter->get()->SetChannel(Track::MonoChannel);
383  }
384  }
385 
386  if (mInfo.channels == 2) {
387  channels.begin()->get()->SetLinked(true);
388  }
389 
390  auto fileTotalFrames =
391  (sampleCount)mInfo.frames; // convert from sf_count_t
392  auto maxBlockSize = channels.begin()->get()->GetMaxBlockSize();
393  auto updateResult = ProgressResult::Cancelled;
394 
395  // If the format is not seekable, we must use 'copy' mode,
396  // because 'edit' mode depends on the ability to seek to an
397  // arbitrary location in the file.
398  if (!mInfo.seekable)
399  doEdit = false;
400 
401  if (doEdit) {
402  // If this mode has been selected, we form the tracks as
403  // aliases to the files we're editing, i.e. ("foo.wav", 12000-18000)
404  // instead of actually making fresh copies of the samples.
405 
406  // lets use OD only if the file is longer than 30 seconds. Otherwise, why wake up extra threads.
407  //todo: make this a user pref.
408  bool useOD =fileTotalFrames>kMinimumODFileSampleSize;
409  int updateCounter = 0;
410 
411  for (decltype(fileTotalFrames) i = 0; i < fileTotalFrames; i += maxBlockSize) {
412 
413  const auto blockLen =
414  limitSampleBufferSize( maxBlockSize, fileTotalFrames - i );
415 
416  auto iter = channels.begin();
417  for (int c = 0; c < mInfo.channels; ++iter, ++c)
418  iter->get()->AppendAlias(mFilename, i, blockLen, c,useOD);
419 
420  if (++updateCounter == 50) {
421  updateResult = mProgress->Update(
422  i.as_long_long(),
423  fileTotalFrames.as_long_long()
424  );
425  updateCounter = 0;
426  if (updateResult != ProgressResult::Success)
427  break;
428  }
429  }
430 
431  // One last update for completion
432  updateResult = mProgress->Update(
433  fileTotalFrames.as_long_long(),
434  fileTotalFrames.as_long_long()
435  );
436 
437  if(useOD)
438  {
439  auto computeTask = std::make_unique<ODComputeSummaryTask>();
440  bool moreThanStereo = mInfo.channels>2;
441  for (const auto &channel : channels)
442  {
443  computeTask->AddWaveTrack(channel.get());
444  if(moreThanStereo)
445  {
446  //if we have 3 more channels, they get imported on seperate tracks, so we add individual tasks for each.
447  ODManager::Instance()->AddNewTask(std::move(computeTask));
448  computeTask = std::make_unique<ODComputeSummaryTask>();
449  }
450  }
451  //if we have a linked track, we add ONE task.
452  if(!moreThanStereo)
453  ODManager::Instance()->AddNewTask(std::move(computeTask));
454  }
455  }
456  else {
457  // Otherwise, we're in the "copy" mode, where we read in the actual
458  // samples from the file and store our own local copy of the
459  // samples in the tracks.
460 
461  // PRL: guard against excessive memory buffer allocation in case of many channels
462  using type = decltype(maxBlockSize);
463  if (mInfo.channels < 1)
464  return ProgressResult::Failed;
465  auto maxBlock = std::min(maxBlockSize,
466  std::numeric_limits<type>::max() /
467  (mInfo.channels * SAMPLE_SIZE(mFormat))
468  );
469  if (maxBlock < 1)
470  return ProgressResult::Failed;
471 
472  SampleBuffer srcbuffer, buffer;
473  wxASSERT(mInfo.channels >= 0);
474  while (NULL == srcbuffer.Allocate(maxBlock * mInfo.channels, mFormat).ptr() ||
475  NULL == buffer.Allocate(maxBlock, mFormat).ptr())
476  {
477  maxBlock /= 2;
478  if (maxBlock < 1)
479  return ProgressResult::Failed;
480  }
481 
482  decltype(fileTotalFrames) framescompleted = 0;
483 
484  long block;
485  do {
486  block = maxBlock;
487 
488  if (mFormat == int16Sample)
489  block = SFCall<sf_count_t>(sf_readf_short, mFile.get(), (short *)srcbuffer.ptr(), block);
490  //import 24 bit int as float and have the append function convert it. This is how PCMAliasBlockFile works too.
491  else
492  block = SFCall<sf_count_t>(sf_readf_float, mFile.get(), (float *)srcbuffer.ptr(), block);
493 
494  if(block < 0 || block > (long)maxBlock) {
495  wxASSERT(false);
496  block = maxBlock;
497  }
498 
499  if (block) {
500  auto iter = channels.begin();
501  for(int c=0; c<mInfo.channels; ++iter, ++c) {
502  if (mFormat==int16Sample) {
503  for(int j=0; j<block; j++)
504  ((short *)buffer.ptr())[j] =
505  ((short *)srcbuffer.ptr())[mInfo.channels*j+c];
506  }
507  else {
508  for(int j=0; j<block; j++)
509  ((float *)buffer.ptr())[j] =
510  ((float *)srcbuffer.ptr())[mInfo.channels*j+c];
511  }
512 
513  iter->get()->Append(buffer.ptr(), (mFormat == int16Sample)?int16Sample:floatSample, block);
514  }
515  framescompleted += block;
516  }
517 
518  updateResult = mProgress->Update(
519  framescompleted.as_long_long(),
520  fileTotalFrames.as_long_long()
521  );
522  if (updateResult != ProgressResult::Success)
523  break;
524 
525  } while (block > 0);
526  }
527 
528  if (updateResult == ProgressResult::Failed || updateResult == ProgressResult::Cancelled) {
529  return updateResult;
530  }
531 
532  for(const auto &channel : channels) {
533  channel->Flush();
534  }
535  outTracks.swap(channels);
536 
537  const char *str;
538 
539  str = sf_get_string(mFile.get(), SF_STR_TITLE);
540  if (str) {
541  tags->SetTag(TAG_TITLE, UTF8CTOWX(str));
542  }
543 
544  str = sf_get_string(mFile.get(), SF_STR_ALBUM);
545  if (str) {
546  tags->SetTag(TAG_ALBUM, UTF8CTOWX(str));
547  }
548 
549  str = sf_get_string(mFile.get(), SF_STR_ARTIST);
550  if (str) {
551  tags->SetTag(TAG_ARTIST, UTF8CTOWX(str));
552  }
553 
554  str = sf_get_string(mFile.get(), SF_STR_COMMENT);
555  if (str) {
556  tags->SetTag(TAG_COMMENTS, UTF8CTOWX(str));
557  }
558 
559  str = sf_get_string(mFile.get(), SF_STR_DATE);
560  if (str) {
561  tags->SetTag(TAG_YEAR, UTF8CTOWX(str));
562  }
563 
564  str = sf_get_string(mFile.get(), SF_STR_COPYRIGHT);
565  if (str) {
566  tags->SetTag(TAG_COPYRIGHT, UTF8CTOWX(str));
567  }
568 
569  str = sf_get_string(mFile.get(), SF_STR_SOFTWARE);
570  if (str) {
571  tags->SetTag(TAG_SOFTWARE, UTF8CTOWX(str));
572  }
573 
574  str = sf_get_string(mFile.get(), SF_STR_TRACKNUMBER);
575  if (str) {
576  tags->SetTag(TAG_TRACK, UTF8CTOWX(str));
577  }
578 
579  str = sf_get_string(mFile.get(), SF_STR_GENRE);
580  if (str) {
581  tags->SetTag(TAG_GENRE, UTF8CTOWX(str));
582  }
583 
584 #if defined(USE_LIBID3TAG)
585  if (((mInfo.format & SF_FORMAT_TYPEMASK) == SF_FORMAT_AIFF) ||
586  ((mInfo.format & SF_FORMAT_TYPEMASK) == SF_FORMAT_WAV)) {
587  wxFFile f(mFilename, wxT("rb"));
588  if (f.IsOpened()) {
589  char id[5];
590  wxUint32 len;
591 
592  id[4] = '\0';
593 
594  f.Seek(12); // Skip filetype, length, and formtype
595 
596  while (!f.Error()) {
597  f.Read(id, 4); // Get chunk type
598  if (f.Eof()) {
599  break;
600  }
601  f.Read(&len, 4);
602  if((mInfo.format & SF_FORMAT_TYPEMASK) == SF_FORMAT_AIFF)
603  len = wxUINT32_SWAP_ON_LE(len);
604 
605  if (wxStricmp(id, "ID3 ") != 0) { // must be case insensitive
606  f.Seek(len + (len & 0x01), wxFromCurrent);
607  continue;
608  }
609 
610 
611  id3_tag_holder tp;
612  {
613  ArrayOf<id3_byte_t> buffer{ len };
614  if (!buffer) {
615  break;
616  }
617 
618  f.Read(buffer.get(), len);
619  tp.reset( id3_tag_parse(buffer.get(), len) );
620  }
621 
622  if (!tp) {
623  break;
624  }
625 
626  // Loop through all frames
627  bool have_year = false;
628  for (int i = 0; i < (int) tp->nframes; i++) {
629  struct id3_frame *frame = tp->frames[i];
630 
631  // wxPrintf("ID: %08x '%4s'\n", (int) *(int *)frame->id, frame->id);
632  // wxPrintf("Desc: %s\n", frame->description);
633  // wxPrintf("Num fields: %d\n", frame->nfields);
634 
635  // for (int j = 0; j < (int) frame->nfields; j++) {
636  // wxPrintf("field %d type %d\n", j, frame->fields[j].type );
637  // if (frame->fields[j].type == ID3_FIELD_TYPE_STRINGLIST) {
638  // wxPrintf("num strings %d\n", frame->fields[j].stringlist.nstrings);
639  // }
640  // }
641 
642  wxString n, v;
643 
644  // Determine the tag name
645  if (strcmp(frame->id, ID3_FRAME_TITLE) == 0) {
646  n = TAG_TITLE;
647  }
648  else if (strcmp(frame->id, ID3_FRAME_ARTIST) == 0) {
649  n = TAG_ARTIST;
650  }
651  else if (strcmp(frame->id, ID3_FRAME_ALBUM) == 0) {
652  n = TAG_ALBUM;
653  }
654  else if (strcmp(frame->id, ID3_FRAME_TRACK) == 0) {
655  n = TAG_TRACK;
656  }
657  else if (strcmp(frame->id, ID3_FRAME_YEAR) == 0) {
658  // LLL: When libid3tag encounters the "TYER" tag, it converts it to a
659  // "ZOBS" (obsolete) tag and adds a "TDRC" tag at the end of the
660  // list of tags using the first 4 characters of the "TYER" tag.
661  // Since we write both the "TDRC" and "TYER" tags, the "TDRC" tag
662  // will always be encountered first in the list. We want use it
663  // since the converted "TYER" tag may have been truncated.
664  if (have_year) {
665  continue;
666  }
667  n = TAG_YEAR;
668  have_year = true;
669  }
670  else if (strcmp(frame->id, ID3_FRAME_COMMENT) == 0) {
671  n = TAG_COMMENTS;
672  }
673  else if (strcmp(frame->id, ID3_FRAME_GENRE) == 0) {
674  n = TAG_GENRE;
675  }
676  else {
677  // Use frame description as default tag name. The descriptions
678  // may include several "meanings" separated by "/" characters, so
679  // we just use the first meaning
680  n = UTF8CTOWX(frame->description).BeforeFirst(wxT('/'));
681  }
682 
683  const id3_ucs4_t *ustr = NULL;
684 
685  if (n == TAG_COMMENTS) {
686  ustr = id3_field_getfullstring(&frame->fields[3]);
687  }
688  else if (frame->nfields == 3) {
689  ustr = id3_field_getstring(&frame->fields[1]);
690  if (ustr) {
691  // Is this duplication really needed?
692  MallocString<> str{ (char *)id3_ucs4_utf8duplicate(ustr) };
693  n = UTF8CTOWX(str.get());
694  }
695 
696  ustr = id3_field_getstring(&frame->fields[2]);
697  }
698  else if (frame->nfields >= 2) {
699  ustr = id3_field_getstrings(&frame->fields[1], 0);
700  }
701 
702  if (ustr) {
703  // Is this duplication really needed?
704  MallocString<> str{ (char *)id3_ucs4_utf8duplicate(ustr) };
705  v = UTF8CTOWX(str.get());
706  }
707 
708  if (!n.IsEmpty() && !v.IsEmpty()) {
709  tags->SetTag(n, v);
710  }
711  }
712 
713  // Convert v1 genre to name
714  if (tags->HasTag(TAG_GENRE)) {
715  long g = -1;
716  if (tags->GetTag(TAG_GENRE).ToLong(&g)) {
717  tags->SetTag(TAG_GENRE, tags->GetGenre(g));
718  }
719  }
720 
721  break;
722  }
723  }
724  }
725 #endif
726 
727  return updateResult;
728 }
729 
731 {
732 }
void SetTag(const wxString &name, const wxString &value)
Definition: Tags.cpp:449
AudacityPrefs * gPrefs
Definition: Prefs.cpp:73
wxString GetPluginFormatDescription() override
Definition: ImportPCM.cpp:123
static ODManager *(* Instance)()
Definition: ODManager.h:49
ProgressResult
#define TAG_TRACK
Definition: Tags.h:63
An ImportFileHandle for data.
Definition: ImportPlugin.h:119
bool sf_subtype_more_than_16_bits(unsigned int format)
unsigned long long ByteCount
Definition: ImportPlugin.h:151
#define UTF8CTOWX(X)
Definition: Internat.h:179
wxInt32 GetStreamCount() override
Definition: ImportPCM.cpp:100
#define SAMPLE_SIZE(SampleFormat)
Definition: Types.h:198
SampleBuffer & Allocate(size_t count, sampleFormat format)
Definition: SampleFormat.h:67
#define TAG_TITLE
Definition: Tags.h:60
static wxString AskCopyOrEdit()
Definition: ImportPCM.cpp:227
#define TAG_ARTIST
Definition: Tags.h:61
wxString GetGenre(int value)
Definition: Tags.cpp:391
wxString GetPluginStringID() override
Definition: ImportPCM.cpp:83
#define safenew
Definition: Audacity.h:230
ProgressResult Import(TrackFactory *trackFactory, TrackHolders &outTracks, Tags *tags) override
Definition: ImportPCM.cpp:345
void AddNewTask(std::unique_ptr< ODTask > &&mtask, bool lockMutex=true)
Adds a wavetrack, creates a queue member.
Definition: ODManager.cpp:145
std::unique_ptr< Character[], freer > MallocString
Definition: MemoryX.h:417
size_t limitSampleBufferSize(size_t bufferSize, sampleCount limit)
Definition: Types.h:178
Used to create a WaveTrack, or a LabelTrack.. Implementation of the functions of this class are dispe...
Definition: Track.h:862
#define DESC
Definition: ImportPCM.cpp:70
#define TAG_SOFTWARE
Definition: Tags.h:67
std::unique_ptr< ImportFileHandle > Open(const wxString &Filename) override
Definition: ImportPCM.cpp:128
An ImportFileHandle for PCM data.
Definition: ImportPCM.cpp:89
void GetPCMImportPlugin(ImportPluginList &importPluginList, UnusableImportPluginList &WXUNUSED(unusableImportPluginList))
Definition: ImportPCM.cpp:117
sampleFormat mFormat
Definition: ImportPCM.cpp:114
wxString sf_header_name(int format)
Get the string name of the specified container format.
sampleFormat
Definition: Types.h:188
ProgressResult Update(int value, const wxString &message=wxEmptyString)
The interface that all file import "plugins" (if you want to call them that) must implement...
#define kMinimumODFileSampleSize
Definition: ImportPCM.cpp:45
int min(int a, int b)
samplePtr ptr() const
Definition: SampleFormat.h:81
Maybe< ProgressDialog > mProgress
Definition: ImportPlugin.h:174
ID3 Tags (for MP3)
Definition: Tags.h:70
void SetStreamUsage(wxInt32 WXUNUSED(StreamID), bool WXUNUSED(Use)) override
Definition: ImportPCM.cpp:108
std::unique_ptr< WaveTrack > NewWaveTrack(sampleFormat format=(sampleFormat) 0, double rate=0)
Definition: WaveTrack.cpp:78
wxString GetTag(const wxString &name) const
Definition: Tags.cpp:424
Base class for FlacImportPlugin, LOFImportPlugin, MP3ImportPlugin, OggImportPlugin and PCMImportPlugi...
Definition: ImportPlugin.h:73
_("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
ByteCount GetFileUncompressedBytes() override
Definition: ImportPCM.cpp:220
PCMImportFileHandle(wxString name, SFFile &&file, SF_INFO info)
Definition: ImportPCM.cpp:192
wxArrayString sf_get_all_extensions()
#define TAG_COPYRIGHT
Definition: Tags.h:68
wxString GetFileDescription() override
Definition: ImportPCM.cpp:215
const wxChar * name
Definition: Distortion.cpp:94
Memory.h template class for making an array of float, bool, etc.
Definition: MemoryX.h:86
#define TAG_COMMENTS
Definition: Tags.h:66
const wxArrayString & GetStreamInfo() override
Definition: ImportPCM.cpp:102
#define TAG_GENRE
Definition: Tags.h:65
An UnusableImportPlugin list.
An ImportPlugin list.
static sampleFormat SampleFormatChoice()
std::vector< std::unique_ptr< WaveTrack >> TrackHolders
Definition: ImportRaw.h:24
An ImportPlugin for PCM data.
Definition: ImportPCM.cpp:72
const SF_INFO mInfo
Definition: ImportPCM.cpp:113
wxString mFilename
Definition: ImportPlugin.h:173
#define TAG_ALBUM
Definition: Tags.h:62
#define TAG_YEAR
Definition: Tags.h:64
bool HasTag(const wxString &name) const
Definition: Tags.cpp:415
wxArrayString mExtensions
Definition: ImportPlugin.h:115