The JUCE cross-platform C++ framework, with DISTRHO/KXStudio specific changes
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

229 lines
7.1KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2022 - Raw Material Software Limited
  5. JUCE is an open source library subject to commercial or open-source
  6. licensing.
  7. By using JUCE, you agree to the terms of both the JUCE 7 End-User License
  8. Agreement and JUCE Privacy Policy.
  9. End User License Agreement: www.juce.com/juce-7-licence
  10. Privacy Policy: www.juce.com/juce-privacy-policy
  11. Or: You may also use this code under the terms of the GPL v3 (see
  12. www.gnu.org/licenses).
  13. JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
  14. EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
  15. DISCLAIMED.
  16. ==============================================================================
  17. */
  18. namespace juce
  19. {
  20. #if JUCE_USE_LAME_AUDIO_FORMAT
  21. class LAMEEncoderAudioFormat::Writer final : public AudioFormatWriter
  22. {
  23. public:
  24. Writer (OutputStream* destStream, const String& formatName,
  25. const File& appFile, int vbr, int cbr,
  26. double sampleRateIn, unsigned int numberOfChannels,
  27. int bitsPerSampleIn, const StringPairArray& metadata)
  28. : AudioFormatWriter (destStream, formatName, sampleRateIn,
  29. numberOfChannels, (unsigned int) bitsPerSampleIn),
  30. vbrLevel (vbr), cbrBitrate (cbr)
  31. {
  32. WavAudioFormat wavFormat;
  33. if (auto out = tempWav.getFile().createOutputStream())
  34. {
  35. writer.reset (wavFormat.createWriterFor (out.release(), sampleRateIn, numChannels,
  36. bitsPerSampleIn, metadata, 0));
  37. args.add (appFile.getFullPathName());
  38. args.add ("--quiet");
  39. if (cbrBitrate == 0)
  40. {
  41. args.add ("--vbr-new");
  42. args.add ("-V");
  43. args.add (String (vbrLevel));
  44. }
  45. else
  46. {
  47. args.add ("--cbr");
  48. args.add ("-b");
  49. args.add (String (cbrBitrate));
  50. }
  51. addMetadataArg (metadata, "id3title", "--tt");
  52. addMetadataArg (metadata, "id3artist", "--ta");
  53. addMetadataArg (metadata, "id3album", "--tl");
  54. addMetadataArg (metadata, "id3comment", "--tc");
  55. addMetadataArg (metadata, "id3date", "--ty");
  56. addMetadataArg (metadata, "id3genre", "--tg");
  57. addMetadataArg (metadata, "id3trackNumber", "--tn");
  58. }
  59. }
  60. void addMetadataArg (const StringPairArray& metadata, const char* key, const char* lameFlag)
  61. {
  62. auto value = metadata.getValue (key, {});
  63. if (value.isNotEmpty())
  64. {
  65. args.add (lameFlag);
  66. args.add (value);
  67. }
  68. }
  69. ~Writer()
  70. {
  71. if (writer != nullptr)
  72. {
  73. writer = nullptr;
  74. if (! convertToMP3())
  75. convertToMP3(); // try again
  76. }
  77. }
  78. bool write (const int** samplesToWrite, int numSamples)
  79. {
  80. return writer != nullptr && writer->write (samplesToWrite, numSamples);
  81. }
  82. private:
  83. int vbrLevel, cbrBitrate;
  84. TemporaryFile tempWav { ".wav" };
  85. std::unique_ptr<AudioFormatWriter> writer;
  86. StringArray args;
  87. bool runLameChildProcess (const TemporaryFile& tempMP3, const StringArray& processArgs) const
  88. {
  89. ChildProcess cp;
  90. if (cp.start (processArgs))
  91. {
  92. [[maybe_unused]] auto childOutput = cp.readAllProcessOutput();
  93. DBG (childOutput);
  94. cp.waitForProcessToFinish (10000);
  95. return tempMP3.getFile().getSize() > 0;
  96. }
  97. return false;
  98. }
  99. bool convertToMP3() const
  100. {
  101. TemporaryFile tempMP3 (".mp3");
  102. StringArray args2 (args);
  103. args2.add (tempWav.getFile().getFullPathName());
  104. args2.add (tempMP3.getFile().getFullPathName());
  105. DBG (args2.joinIntoString (" "));
  106. if (runLameChildProcess (tempMP3, args2))
  107. {
  108. FileInputStream fis (tempMP3.getFile());
  109. if (fis.openedOk() && output->writeFromInputStream (fis, -1) > 0)
  110. {
  111. output->flush();
  112. return true;
  113. }
  114. }
  115. return false;
  116. }
  117. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Writer)
  118. };
  119. //==============================================================================
  120. LAMEEncoderAudioFormat::LAMEEncoderAudioFormat (const File& lameApplication)
  121. : AudioFormat ("MP3 file", ".mp3"),
  122. lameApp (lameApplication)
  123. {
  124. }
  125. LAMEEncoderAudioFormat::~LAMEEncoderAudioFormat()
  126. {
  127. }
  128. bool LAMEEncoderAudioFormat::canHandleFile (const File&)
  129. {
  130. return false;
  131. }
  132. Array<int> LAMEEncoderAudioFormat::getPossibleSampleRates()
  133. {
  134. return { 32000, 44100, 48000 };
  135. }
  136. Array<int> LAMEEncoderAudioFormat::getPossibleBitDepths()
  137. {
  138. return { 16 };
  139. }
  140. bool LAMEEncoderAudioFormat::canDoStereo() { return true; }
  141. bool LAMEEncoderAudioFormat::canDoMono() { return true; }
  142. bool LAMEEncoderAudioFormat::isCompressed() { return true; }
  143. StringArray LAMEEncoderAudioFormat::getQualityOptions()
  144. {
  145. static const char* vbrOptions[] = { "VBR quality 0 (best)", "VBR quality 1", "VBR quality 2", "VBR quality 3",
  146. "VBR quality 4 (normal)", "VBR quality 5", "VBR quality 6", "VBR quality 7",
  147. "VBR quality 8", "VBR quality 9 (smallest)", nullptr };
  148. StringArray opts (vbrOptions);
  149. const int cbrRates[] = { 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320 };
  150. for (int i = 0; i < numElementsInArray (cbrRates); ++i)
  151. opts.add (String (cbrRates[i]) + " Kb/s CBR");
  152. return opts;
  153. }
  154. AudioFormatReader* LAMEEncoderAudioFormat::createReaderFor (InputStream*, const bool)
  155. {
  156. return nullptr;
  157. }
  158. AudioFormatWriter* LAMEEncoderAudioFormat::createWriterFor (OutputStream* streamToWriteTo,
  159. double sampleRateToUse,
  160. unsigned int numberOfChannels,
  161. int bitsPerSample,
  162. const StringPairArray& metadataValues,
  163. int qualityOptionIndex)
  164. {
  165. if (streamToWriteTo == nullptr)
  166. return nullptr;
  167. int vbr = 4;
  168. int cbr = 0;
  169. const String qual (getQualityOptions() [qualityOptionIndex]);
  170. if (qual.contains ("VBR"))
  171. vbr = qual.retainCharacters ("0123456789").getIntValue();
  172. else
  173. cbr = qual.getIntValue();
  174. return new Writer (streamToWriteTo, getFormatName(), lameApp, vbr, cbr,
  175. sampleRateToUse, numberOfChannels, bitsPerSample, metadataValues);
  176. }
  177. #endif
  178. } // namespace juce