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