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; }
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; }
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; }
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); }
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; }
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; }
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; }