Пример #1
0
ProgressResult LOFImportFileHandle::Import(TrackFactory * WXUNUSED(trackFactory), TrackHolders &outTracks,
                                Tags * WXUNUSED(tags))
{
   outTracks.clear();

   wxASSERT(mTextFile->IsOpened());

   if(mTextFile->Eof())
   {
      mTextFile->Close();
      return ProgressResult::Failed;
   }

   wxString line = mTextFile->GetFirstLine();

   while (!mTextFile->Eof())
   {
      lofOpenFiles(&line);
      line = mTextFile->GetNextLine();
   }

   // for last line
   lofOpenFiles(&line);

   // set any duration/offset factors for last window, as all files were called
   doDuration();
   doScrollOffset();

   // exited ok
   if(mTextFile->Close())
      return ProgressResult::Success;

   return ProgressResult::Failed;
}
Пример #2
0
int MP3ImportFileHandle::Import(TrackFactory *trackFactory, TrackHolders &outTracks,
                                Tags *tags)
{
   outTracks.clear();

   CreateProgress();

   /* Prepare decoder data, initialize decoder */

   mPrivateData.file        = mFile;
   mPrivateData.inputBuffer = new unsigned char [INPUT_BUFFER_SIZE];
   mPrivateData.progress    = mProgress.get();
   mPrivateData.updateResult= eProgressSuccess;
   mPrivateData.id3checked  = false;
   mPrivateData.numChannels = 0;
   mPrivateData.trackFactory= trackFactory;

   mad_decoder_init(&mDecoder, &mPrivateData, input_cb, 0, 0, output_cb, error_cb, 0);

   /* and send the decoder on its way! */

   bool res = (mad_decoder_run(&mDecoder, MAD_DECODER_MODE_SYNC) == 0) &&
              (mPrivateData.numChannels > 0) &&
              !(mPrivateData.updateResult == eProgressCancelled) &&
              !(mPrivateData.updateResult == eProgressFailed);

   mad_decoder_finish(&mDecoder);

   delete[] mPrivateData.inputBuffer;

   if (!res) {
      /* failure */
      /* printf("failure\n"); */
      return (mPrivateData.updateResult);
   }

   /* success */
   /* printf("success\n"); */

      /* copy the WaveTrack pointers into the Track pointer list that
       * we are expected to fill */
   for(const auto &channel : mPrivateData.channels) {
      channel->Flush();
   }
   outTracks.swap(mPrivateData.channels);

   /* Read in any metadata */
   ImportID3(tags);

   return mPrivateData.updateResult;
}
Пример #3
0
ProgressResult LOFImportFileHandle::Import(
   TrackFactory * WXUNUSED(trackFactory), TrackHolders &outTracks,
   Tags * WXUNUSED(tags))
{
   // Unlike other ImportFileHandle subclasses, this one never gives any tracks
   // back to the caller.
   // Instead, it recursively calls AudacityProject::Import for each file listed
   // in the .lof file.
   // Each importation creates a NEW undo state.
   // If there is an error or exception during one of them, only that one's
   // side effects are rolled back, and the rest of the import list is skipped.
   // The file may have "window" directives that cause NEW AudacityProjects
   // to be created, and the undo states are pushed onto the latest project.
   // If a project is created but the first file import into it fails, destroy
   // the project.

   outTracks.clear();

   wxASSERT(mTextFile->IsOpened());

   if(mTextFile->Eof())
   {
      mTextFile->Close();
      return ProgressResult::Failed;
   }

   wxString line = mTextFile->GetFirstLine();

   while (!mTextFile->Eof())
   {
      lofOpenFiles(&line);
      line = mTextFile->GetNextLine();
   }

   // for last line
   lofOpenFiles(&line);

   if(!mTextFile->Close())
      return ProgressResult::Failed;

   // set any duration/offset factors for last window, as all files were called
   doDurationAndScrollOffset();

   return ProgressResult::Success;
}
Пример #4
0
ProgressResult QTImportFileHandle::Import(TrackFactory *trackFactory,
                               TrackHolders &outTracks,
                               Tags *tags)
{
   outTracks.clear();

   OSErr err = noErr;
   MovieAudioExtractionRef maer = NULL;
   auto updateResult = ProgressResult::Success;
   auto totSamples =
      (sampleCount) GetMovieDuration(mMovie); // convert from TimeValue
   decltype(totSamples) numSamples = 0;
   Boolean discrete = true;
   UInt32 quality = kQTAudioRenderQuality_Max;
   AudioStreamBasicDescription desc;
   UInt32 maxSampleSize;
   bool res = false;

   auto cleanup = finally( [&] {
      if (maer) {
         MovieAudioExtractionEnd(maer);
      }
   } );

   CreateProgress();

   do
   {
      err = MovieAudioExtractionBegin(mMovie, 0, &maer);
      if (err != noErr) {
         AudacityMessageBox(_("Unable to start QuickTime extraction"));
         break;
      }
   
      err = MovieAudioExtractionSetProperty(maer,
                                            kQTPropertyClass_MovieAudioExtraction_Audio,
                                            kQTMovieAudioExtractionAudioPropertyID_RenderQuality,
                                            sizeof(quality),
                                            &quality);
      if (err != noErr) {
         AudacityMessageBox(_("Unable to set QuickTime render quality"));
         break;
      }
   
      err = MovieAudioExtractionSetProperty(maer,
                                            kQTPropertyClass_MovieAudioExtraction_Movie,
                                            kQTMovieAudioExtractionMoviePropertyID_AllChannelsDiscrete,
                                            sizeof(discrete),
                                            &discrete);
      if (err != noErr) {
         AudacityMessageBox(_("Unable to set QuickTime discrete channels property"));
         break;
      }
   
      err = MovieAudioExtractionGetProperty(maer,
                                            kQTPropertyClass_MovieAudioExtraction_Audio,
                                            kQTAudioPropertyID_MaxAudioSampleSize,
                                            sizeof(maxSampleSize),
                                            &maxSampleSize,
                                            NULL);
      if (err != noErr) {
         AudacityMessageBox(_("Unable to get QuickTime sample size property"));
         break;
      }
   
      err = MovieAudioExtractionGetProperty(maer,
                                            kQTPropertyClass_MovieAudioExtraction_Audio,
                                            kQTMovieAudioExtractionAudioPropertyID_AudioStreamBasicDescription,
                                            sizeof(desc),
                                            &desc,
                                            NULL);
      if (err != noErr) {
         AudacityMessageBox(_("Unable to retrieve stream description"));
         break;
      }
   
      auto numchan = desc.mChannelsPerFrame;
      const size_t bufsize = 5 * desc.mSampleRate;
   
      // determine sample format
      sampleFormat format;
      switch (maxSampleSize)
      {
         case 16:
            format = int16Sample;
            break;
   
         case 24:
            format = int24Sample;
            break;
   
         default:
            format = floatSample;
            break;
      }

      // Allocate an array of pointers, whose size is not known statically,
      // and prefixed with the AudioBufferList structure.
      MallocPtr< AudioBufferList > abl{
         static_cast< AudioBufferList * >(
            calloc( 1, offsetof( AudioBufferList, mBuffers ) +
               (sizeof(AudioBuffer) * numchan))) };
      abl->mNumberBuffers = numchan;
   
      TrackHolders channels{ numchan };

      const auto size = sizeof(float) * bufsize;
      ArraysOf<unsigned char> holders{ numchan, size };
      for (size_t c = 0; c < numchan; c++) {
         auto &buffer = abl->mBuffers[c];
         auto &holder = holders[c];
         auto &channel = channels[c];

         buffer.mNumberChannels = 1;
         buffer.mDataByteSize = size;

         buffer.mData = holder.get();
   
         channel = trackFactory->NewWaveTrack( format );
         channel->SetRate( desc.mSampleRate );
   
         if (numchan == 2) {
            if (c == 0) {
               channel->SetChannel(Track::LeftChannel);
               channel->SetLinked(true);
            }
            else if (c == 1) {
               channel->SetChannel(Track::RightChannel);
            }
         }
      }
   
      do {
         UInt32 flags = 0;
         UInt32 numFrames = bufsize;
   
         err = MovieAudioExtractionFillBuffer(maer,
                                              &numFrames,
                                              abl.get(),
                                              &flags);
         if (err != noErr) {
            AudacityMessageBox(_("Unable to get fill buffer"));
            break;
         }
   
         for (size_t c = 0; c < numchan; c++) {
            channels[c]->Append((char *) abl->mBuffers[c].mData, floatSample, numFrames);
         }
   
         numSamples += numFrames;
   
         updateResult = mProgress->Update(
            numSamples.as_long_long(),
            totSamples.as_long_long() );
   
         if (numFrames == 0 || flags & kQTMovieAudioExtractionComplete) {
            break;
         }
      } while (updateResult == ProgressResult::Success);
   
      res = (updateResult == ProgressResult::Success && err == noErr);
   
      if (res) {
         for (const auto &channel: channels) {
            channel->Flush();
         }
   
         outTracks.swap(channels);
      }

      //
      // Extract any metadata
      //
      if (res) {
         AddMetadata(tags);
      }
   } while (false);

// done:

   return (res ? ProgressResult::Success : ProgressResult::Failed);
}
Пример #5
0
int FFmpegImportFileHandle::Import(TrackFactory *trackFactory,
              TrackHolders &outTracks,
              Tags *tags)
{
   outTracks.clear();

   CreateProgress();

   // Remove stream contexts which are not marked for importing and adjust mScs and mNumStreams accordingly
   const auto scs = mScs->get();
   for (int i = 0; i < mNumStreams;)
   {
      if (!scs[i]->m_use)
      {
         for (int j = i; j < mNumStreams - 1; j++)
         {
            scs[j] = std::move(scs[j+1]);
         }
         mNumStreams--;
      }
      else i++;
   }

   mChannels.resize(mNumStreams);

   int s = -1;
   for (auto &stream : mChannels)
   {
      ++s;

      auto sc = scs[s].get();
      switch (sc->m_stream->codec->sample_fmt)
      {
         case AV_SAMPLE_FMT_U8:
         case AV_SAMPLE_FMT_S16:
         case AV_SAMPLE_FMT_U8P:
         case AV_SAMPLE_FMT_S16P:
            sc->m_osamplesize = sizeof(int16_t);
            sc->m_osamplefmt = int16Sample;
         break;
         default:
            sc->m_osamplesize = sizeof(float);
            sc->m_osamplefmt = floatSample;
         break;
      }

      // There is a possibility that number of channels will change over time, but we do not have WaveTracks for NEW channels. Remember the number of channels and stick to it.
      sc->m_initialchannels = sc->m_stream->codec->channels;
      stream.resize(sc->m_stream->codec->channels);
      int c = -1;
      for (auto &channel : stream)
      {
         ++c;

         channel = trackFactory->NewWaveTrack(sc->m_osamplefmt, sc->m_stream->codec->sample_rate);

         if (sc->m_stream->codec->channels == 2)
         {
            switch (c)
            {
            case 0:
               channel->SetChannel(Track::LeftChannel);
               channel->SetLinked(true);
               break;
            case 1:
               channel->SetChannel(Track::RightChannel);
               break;
            }
         }
         else
         {
            channel->SetChannel(Track::MonoChannel);
         }
      }
   }

   // Handles the start_time by creating silence. This may or may not be correct.
   // There is a possibility that we should ignore first N milliseconds of audio instead. I do not know.
   /// TODO: Nag FFmpeg devs about start_time until they finally say WHAT is this and HOW to handle it.
   s = -1;
   for (auto &stream : mChannels)
   {
      ++s;

      int64_t stream_delay = 0;
      auto sc = scs[s].get();
      if (sc->m_stream->start_time != int64_t(AV_NOPTS_VALUE) && sc->m_stream->start_time > 0)
      {
         stream_delay = sc->m_stream->start_time;
         wxLogDebug(wxT("Stream %d start_time = %lld, that would be %f milliseconds."), s, (long long) sc->m_stream->start_time, double(sc->m_stream->start_time)/AV_TIME_BASE*1000);
      }
      if (stream_delay != 0)
      {
         int c = -1;
         for (auto &channel : stream)
         {
            ++c;

            WaveTrack *t = channel.get();
            t->InsertSilence(0,double(stream_delay)/AV_TIME_BASE);
         }
      }
   }
   // This is the heart of the importing process
   // The result of Import() to be returend. It will be something other than zero if user canceled or some error appears.
   int res = eProgressSuccess;

#ifdef EXPERIMENTAL_OD_FFMPEG
   mUsingOD = false;
   gPrefs->Read(wxT("/Library/FFmpegOnDemand"), &mUsingOD);
   //at this point we know the file is good and that we have to load the number of channels in mScs[s]->m_stream->codec->channels;
   //so for OD loading we create the tracks and releasee the modal lock after starting the ODTask.
   if (mUsingOD) {
      std::vector<ODDecodeFFmpegTask*> tasks;
      //append blockfiles to each stream and add an individual ODDecodeTask for each one.
      s = -1;
      for (const auto &stream : mChannels) {
         ++s;
         ODDecodeFFmpegTask* odTask =
            new ODDecodeFFmpegTask(mScs, ODDecodeFFmpegTask::FromList(mChannels), mFormatContext, s);
         odTask->CreateFileDecoder(mFilename);

         //each stream has different duration.  We need to know it if seeking is to be allowed.
         sampleCount sampleDuration = 0;
         auto sc = scs[s].get();
         if (sc->m_stream->duration > 0)
            sampleDuration = ((sampleCount)sc->m_stream->duration * sc->m_stream->time_base.num), sc->m_stream->codec->sample_rate / sc->m_stream->time_base.den;
         else
            sampleDuration = ((sampleCount)mFormatContext->duration *sc->m_stream->codec->sample_rate) / AV_TIME_BASE;

         //      printf(" OD duration samples %qi, sr %d, secs %d\n",sampleDuration, (int)sc->m_stream->codec->sample_rate, (int)sampleDuration/sc->m_stream->codec->sample_rate);

         //for each wavetrack within the stream add coded blockfiles
         for (int c = 0; c < sc->m_stream->codec->channels; c++) {
            WaveTrack *t = stream[c].get();
            odTask->AddWaveTrack(t);

            sampleCount maxBlockSize = t->GetMaxBlockSize();
            //use the maximum blockfile size to divide the sections (about 11secs per blockfile at 44.1khz)
            for (sampleCount i = 0; i < sampleDuration; i += maxBlockSize) {
               sampleCount blockLen = maxBlockSize;
               if (i + blockLen > sampleDuration)
                  blockLen = sampleDuration - i;

               t->AppendCoded(mFilename, i, blockLen, c, ODTask::eODFFMPEG);

               // This only works well for single streams since we assume
               // each stream is of the same duration and channels
               res = mProgress->Update(i+sampleDuration*c+ sampleDuration*sc->m_stream->codec->channels*s,
                                       sampleDuration*sc->m_stream->codec->channels*mNumStreams);
               if (res != eProgressSuccess)
                  break;
            }
         }
         tasks.push_back(odTask);
      }
      //Now we add the tasks and let them run, or DELETE them if the user cancelled
      for(int i=0; i < (int)tasks.size(); i++) {
         if(res==eProgressSuccess)
            ODManager::Instance()->AddNewTask(tasks[i]);
         else
            {
               delete tasks[i];
            }
      }
   } else {
#endif

   // Read next frame.
   for (streamContext *sc; (sc = ReadNextFrame()) != NULL && (res == eProgressSuccess);)
   {
      // ReadNextFrame returns 1 if stream is not to be imported
      if (sc != (streamContext*)1)
      {
         // Decode frame until it is not possible to decode any further
         while (sc->m_pktRemainingSiz > 0 && (res == eProgressSuccess || res == eProgressStopped))
         {
            if (DecodeFrame(sc,false) < 0)
               break;

            // If something useable was decoded - write it to mChannels
            if (sc->m_frameValid)
               res = WriteData(sc);
         }

         // Cleanup after frame decoding
         if (sc->m_pktValid)
         {
            av_free_packet(&sc->m_pkt);
            sc->m_pktValid = 0;
         }
      }
   }

   // Flush the decoders.
   if ((mNumStreams != 0) && (res == eProgressSuccess || res == eProgressStopped))
   {
      for (int i = 0; i < mNumStreams; i++)
      {
         auto sc = scs[i].get();
         if (DecodeFrame(sc, true) == 0)
         {
            WriteData(sc);

            if (sc->m_pktValid)
            {
               av_free_packet(&sc->m_pkt);
               sc->m_pktValid = 0;
            }
         }
      }
   }
#ifdef EXPERIMENTAL_OD_FFMPEG
   } // else -- !mUsingOD == true
#endif   //EXPERIMENTAL_OD_FFMPEG

   // Something bad happened - destroy everything!
   if (res == eProgressCancelled || res == eProgressFailed)
      return res;
   //else if (res == 2), we just stop the decoding as if the file has ended

   // Copy audio from mChannels to newly created tracks (destroying mChannels elements in process)
   for (auto &stream : mChannels)
   {
      for(auto &channel : stream)
      {
         channel->Flush();
         outTracks.push_back(std::move(channel));
      }
   }

   // Save metadata
   WriteMetadata(tags);

   return res;
}
Пример #6
0
int OggImportFileHandle::Import(TrackFactory *trackFactory, TrackHolders &outTracks,
                                Tags *tags)
{
   outTracks.clear();

   wxASSERT(mFile->IsOpened());

   CreateProgress();

   //Number of streams used may be less than mVorbisFile->links,
   //but this way bitstream matches array index.
   mChannels.resize(mVorbisFile->links);

   int i = -1;
   for (auto &link: mChannels)
   {
      ++i;

      //Stream is not used
      if (mStreamUsage[i] == 0)
      {
         //This is just a padding to keep bitstream number and
         //array indices matched.
         continue;
      }

      vorbis_info *vi = ov_info(mVorbisFile, i);

      link.resize(vi->channels);

      int c = - 1;
      for (auto &channel : link) {
         ++c;

         channel = trackFactory->NewWaveTrack(mFormat, vi->rate);

         if (vi->channels == 2) {
            switch (c) {
         case 0:
            channel->SetChannel(Track::LeftChannel);
            channel->SetLinked(true);
            break;
         case 1:
            channel->SetChannel(Track::RightChannel);
            break;
            }
         }
         else {
            channel->SetChannel(Track::MonoChannel);
         }
      }
   }

/* The number of bytes to get from the codec in each run */
#define CODEC_TRANSFER_SIZE 4096

/* The number of samples to read between calls to the callback.
 * Balance between responsiveness of the GUI and throughput of import. */
#define SAMPLES_PER_CALLBACK 100000

   short *mainBuffer = new short[CODEC_TRANSFER_SIZE];

   /* determine endianness (clever trick courtesy of Nicholas Devillard,
    * (http://www.eso.org/~ndevilla/endian/) */
   int testvar = 1, endian;
   if(*(char *)&testvar)
      endian = 0;  // little endian
   else
      endian = 1;  // big endian

   /* number of samples currently in each channel's buffer */
   int updateResult = eProgressSuccess;
   long bytesRead = 0;
   long samplesRead = 0;
   int bitstream = 0;
   int samplesSinceLastCallback = 0;

   // You would think that the stream would already be seeked to 0, and
   // indeed it is if the file is legit.  But I had several ogg files on
   // my hard drive that have malformed headers, and this added call
   // causes them to be read correctly.  Otherwise they have lots of
   // zeros inserted at the beginning
   ov_pcm_seek(mVorbisFile, 0);

   do {
      /* get data from the decoder */
      bytesRead = ov_read(mVorbisFile, (char *) mainBuffer,
                          CODEC_TRANSFER_SIZE,
                          endian,
                          2,    // word length (2 for 16 bit samples)
                          1,    // signed
                          &bitstream);

      if (bytesRead == OV_HOLE) {
         wxFileName ff(mFilename);
         wxLogError(wxT("Ogg Vorbis importer: file %s is malformed, ov_read() reported a hole"),
                    ff.GetFullName().c_str());
         /* http://lists.xiph.org/pipermail/vorbis-dev/2001-February/003223.html
          * is the justification for doing this - best effort for malformed file,
          * hence the message.
          */
         continue;
      } else if (bytesRead < 0) {
         /* Malformed Ogg Vorbis file. */
         /* TODO: Return some sort of meaningful error. */
         wxLogError(wxT("Ogg Vorbis importer: ov_read() returned error %i"),
                    bytesRead);
         break;
      }

      samplesRead = bytesRead / mVorbisFile->vi[bitstream].channels / sizeof(short);

      /* give the data to the wavetracks */
      auto iter = mChannels.begin();
      std::advance(iter, bitstream);
      if (mStreamUsage[bitstream] != 0)
      {
         auto iter2 = iter->begin();
         for (int c = 0; c < mVorbisFile->vi[bitstream].channels; ++iter2, ++c)
            iter2->get()->Append((char *)(mainBuffer + c),
            int16Sample,
            samplesRead,
            mVorbisFile->vi[bitstream].channels);
      }

      samplesSinceLastCallback += samplesRead;
      if (samplesSinceLastCallback > SAMPLES_PER_CALLBACK) {
          updateResult = mProgress->Update(ov_time_tell(mVorbisFile),
                                         ov_time_total(mVorbisFile, bitstream));
          samplesSinceLastCallback -= SAMPLES_PER_CALLBACK;

      }
   } while (updateResult == eProgressSuccess && bytesRead != 0);

   delete[]mainBuffer;

   int res = updateResult;
   if (bytesRead < 0)
     res = eProgressFailed;

   if (res == eProgressFailed || res == eProgressCancelled) {
      return res;
   }

   for (auto &link : mChannels)
   {
      for (auto &channel : link) {
         channel->Flush();
         outTracks.push_back(std::move(channel));
      }
   }

   //\todo { Extract comments from each stream? }
   if (mVorbisFile->vc[0].comments > 0) {
      tags->Clear();
      for (int c = 0; c < mVorbisFile->vc[0].comments; c++) {
         wxString comment = UTF8CTOWX(mVorbisFile->vc[0].user_comments[c]);
         wxString name = comment.BeforeFirst(wxT('='));
         wxString value = comment.AfterFirst(wxT('='));
         if (name.Upper() == wxT("DATE") && !tags->HasTag(TAG_YEAR)) {
            long val;
            if (value.Length() == 4 && value.ToLong(&val)) {
               name = TAG_YEAR;
            }
         }
         tags->SetTag(name, value);
      }
   }

   return res;
}
Пример #7
0
int FLACImportFileHandle::Import(TrackFactory *trackFactory,
                                 TrackHolders &outTracks,
                                 Tags *tags)
{
   outTracks.clear();

   wxASSERT(mStreamInfoDone);

   CreateProgress();

   mChannels.resize(mNumChannels);

   auto iter = mChannels.begin();
   for (int c = 0; c < mNumChannels; ++iter, ++c) {
      *iter = trackFactory->NewWaveTrack(mFormat, mSampleRate);

      if (mNumChannels == 2) {
         switch (c) {
         case 0:
            iter->get()->SetChannel(Track::LeftChannel);
            iter->get()->SetLinked(true);
            break;
         case 1:
            iter->get()->SetChannel(Track::RightChannel);
            break;
         }
      }
      else {
         iter->get()->SetChannel(Track::MonoChannel);
      }
   }


//Start OD
   bool useOD = false;
#ifdef EXPERIMENTAL_OD_FLAC
   useOD=true;
#endif

   // TODO: Vigilant Sentry: Variable res unused after assignment (error code DA1)
   //    Should check the result.
   #ifdef LEGACY_FLAC
      bool res = (mFile->process_until_end_of_file() != 0);
   #else
      bool res = true;
      if(!useOD)
         res = (mFile->process_until_end_of_stream() != 0);
   #endif
      wxUnusedVar(res);

   //add the task to the ODManager
   if(useOD)
   {
      sampleCount fileTotalFrames = mNumSamples;
      sampleCount maxBlockSize = mChannels.begin()->get()->GetMaxBlockSize();
      for (sampleCount i = 0; i < fileTotalFrames; i += maxBlockSize) {
         sampleCount blockLen = maxBlockSize;
         if (i + blockLen > fileTotalFrames)
            blockLen = fileTotalFrames - i;

         auto iter = mChannels.begin();
         for (int c = 0; c < mNumChannels; ++c, ++iter)
            iter->get()->AppendCoded(mFilename, i, blockLen, c, ODTask::eODFLAC);

         mUpdateResult = mProgress->Update(i, fileTotalFrames);
         if (mUpdateResult != eProgressSuccess)
            break;
      }

      bool moreThanStereo = mNumChannels>2;
      for (const auto &channel : mChannels)
      {
         mDecoderTask->AddWaveTrack(channel.get());
         if(moreThanStereo)
         {
            //if we have 3 more channels, they get imported on seperate tracks, so we add individual tasks for each.
            ODManager::Instance()->AddNewTask(mDecoderTask);
            mDecoderTask=new ODDecodeFlacTask; //TODO: see if we need to use clone to keep the metadata.
         }
      }
      //if we have mono or a linked track (stereo), we add ONE task for the one linked wave track
      if(!moreThanStereo)
         ODManager::Instance()->AddNewTask(mDecoderTask);
   }
//END OD


   if (mUpdateResult == eProgressFailed || mUpdateResult == eProgressCancelled) {
      return mUpdateResult;
   }

   for (const auto &channel : mChannels) {
      channel->Flush();
   }
   outTracks.swap(mChannels);

   tags->Clear();
   size_t cnt = mFile->mComments.GetCount();
   for (int c = 0; c < cnt; c++) {
      wxString name = mFile->mComments[c].BeforeFirst(wxT('='));
      wxString value = mFile->mComments[c].AfterFirst(wxT('='));
      if (name.Upper() == wxT("DATE") && !tags->HasTag(TAG_YEAR)) {
         long val;
         if (value.Length() == 4 && value.ToLong(&val)) {
            name = TAG_YEAR;
         }
      }
      tags->SetTag(name, value);
   }

   return mUpdateResult;
}