/* ============================================================================== This file is part of the JUCE library. Copyright (c) 2013 - Raw Material Software Ltd. Permission is granted to use this software under the terms of either: a) the GPL v2 (or any later version) b) the Affero GPL v3 Details of these licenses can be found at: www.gnu.org/licenses JUCE is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. ------------------------------------------------------------------------------ To release a closed-source product which uses JUCE, commercial licenses are available: visit www.juce.com for more information. ============================================================================== */ #if JUCE_USE_LAME_AUDIO_FORMAT class LAMEEncoderAudioFormat::Writer : public AudioFormatWriter { public: Writer (OutputStream* destStream, const String& formatName, const File& lameApp, int vbr, int cbr, double sampleRate, unsigned int numberOfChannels, unsigned int bitsPerSample, const StringPairArray& metadata) : AudioFormatWriter (destStream, formatName, sampleRate, numberOfChannels, bitsPerSample), vbrLevel (vbr), cbrBitrate (cbr), tempWav (".wav") { WavAudioFormat wavFormat; if (FileOutputStream* out = tempWav.getFile().createOutputStream()) { writer = wavFormat.createWriterFor (out, sampleRate, numChannels, bitsPerSample, metadata, 0); args.add (lameApp.getFullPathName()); args.add ("--quiet"); if (cbrBitrate == 0) { args.add ("--vbr-new"); args.add ("-V"); args.add (String (vbrLevel)); } else { args.add ("--cbr"); args.add ("-b"); args.add (String (cbrBitrate)); } addMetadataArg (metadata, "id3title", "--tt"); addMetadataArg (metadata, "id3artist", "--ta"); addMetadataArg (metadata, "id3album", "--tl"); addMetadataArg (metadata, "id3comment", "--tc"); addMetadataArg (metadata, "id3date", "--ty"); addMetadataArg (metadata, "id3genre", "--tg"); addMetadataArg (metadata, "id3trackNumber", "--tn"); } } void addMetadataArg (const StringPairArray& metadata, const char* key, const char* lameFlag) { const String value (metadata.getValue (key, String::empty)); if (value.isNotEmpty()) { args.add (lameFlag); args.add (value); } } ~Writer() { if (writer != nullptr) { writer = nullptr; if (! convertToMP3()) convertToMP3(); // try again } } bool write (const int** samplesToWrite, int numSamples) { return writer != nullptr && writer->write (samplesToWrite, numSamples); } private: int vbrLevel, cbrBitrate; TemporaryFile tempWav; ScopedPointer writer; StringArray args; bool runLameChildProcess (const TemporaryFile& tempMP3, const StringArray& args) const { ChildProcess cp; if (cp.start (args)) { const String childOutput (cp.readAllProcessOutput()); DBG (childOutput); (void) childOutput; cp.waitForProcessToFinish (10000); return tempMP3.getFile().getSize() > 0; } return false; } bool convertToMP3() const { TemporaryFile tempMP3 (".mp3"); StringArray args2 (args); args2.add (tempWav.getFile().getFullPathName()); args2.add (tempMP3.getFile().getFullPathName()); DBG (args2.joinIntoString (" ")); if (runLameChildProcess (tempMP3, args2)) { FileInputStream fis (tempMP3.getFile()); if (fis.openedOk() && output->writeFromInputStream (fis, -1) > 0) { output->flush(); return true; } } return false; } JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Writer) }; //============================================================================== static const char* const lameFormatName = "MP3 file"; static const char* const lameExtensions[] = { ".mp3", nullptr }; LAMEEncoderAudioFormat::LAMEEncoderAudioFormat (const File& lameApplication) : AudioFormat (TRANS (lameFormatName), StringArray (lameExtensions)), lameApp (lameApplication) { } LAMEEncoderAudioFormat::~LAMEEncoderAudioFormat() { } bool LAMEEncoderAudioFormat::canHandleFile (const File&) { return false; } Array LAMEEncoderAudioFormat::getPossibleSampleRates() { const int rates[] = { 32000, 44100, 48000, 0 }; return Array (rates); } Array LAMEEncoderAudioFormat::getPossibleBitDepths() { const int depths[] = { 16, 0 }; return Array (depths); } bool LAMEEncoderAudioFormat::canDoStereo() { return true; } bool LAMEEncoderAudioFormat::canDoMono() { return true; } bool LAMEEncoderAudioFormat::isCompressed() { return true; } StringArray LAMEEncoderAudioFormat::getQualityOptions() { const char* vbrOptions[] = { "VBR quality 0 (best)", "VBR quality 1", "VBR quality 2", "VBR quality 3", "VBR quality 4 (normal)", "VBR quality 5", "VBR quality 6", "VBR quality 7", "VBR quality 8", "VBR quality 9 (smallest)", nullptr }; StringArray opts (vbrOptions); const int cbrRates[] = { 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320 }; for (int i = 0; i < numElementsInArray (cbrRates); ++i) opts.add (String (cbrRates[i]) + " Kb/s CBR"); return opts; } AudioFormatReader* LAMEEncoderAudioFormat::createReaderFor (InputStream*, const bool) { return nullptr; } AudioFormatWriter* LAMEEncoderAudioFormat::createWriterFor (OutputStream* streamToWriteTo, double sampleRateToUse, unsigned int numberOfChannels, int bitsPerSample, const StringPairArray& metadataValues, int qualityOptionIndex) { int vbr = 4; int cbr = 0; const String qual (getQualityOptions() [qualityOptionIndex]); if (qual.contains ("VBR")) vbr = qual.retainCharacters ("0123456789").getIntValue(); else cbr = qual.getIntValue(); return new Writer (streamToWriteTo, getFormatName(), lameApp, vbr, cbr, sampleRateToUse, numberOfChannels, bitsPerSample, metadataValues); } #endif