1 /**********************************************************************
2 
3   Audacity: A Digital Audio Editor
4 
5   ExportMP2.cpp
6 
7   Joshua Haberman
8   Markus Meyer
9 
10   Copyright 2002, 2003 Joshua Haberman.
11   Copyright 2006 Markus Meyer
12   Some portions may be Copyright 2003 Paolo Patruno.
13 
14   This program is free software; you can redistribute it and/or modify
15   it under the terms of the GNU General Public License as published by
16   the Free Software Foundation; either version 2 of the License, or
17   (at your option) any later version.
18 
19   This program is distributed in the hope that it will be useful,
20   but WITHOUT ANY WARRANTY; without even the implied warranty of
21   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
22   GNU General Public License for more details.
23 
24   You should have received a copy of the GNU General Public License
25   along with this program; if not, write to the Free Software
26   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
27 
28 *******************************************************************//**
29 
30 \class MP2Exporter
31 \brief Class used to export MP2 files
32 
33 */
34 
35 
36 
37 #ifdef USE_LIBTWOLAME
38 
39 #include <wx/defs.h>
40 #include <wx/textctrl.h>
41 #include <wx/dynlib.h>
42 #include <wx/utils.h>
43 #include <wx/timer.h>
44 #include <wx/window.h>
45 #include <wx/log.h>
46 #include <wx/intl.h>
47 #include <wx/stream.h>
48 
49 #include "Export.h"
50 #include "FileIO.h"
51 #include "../Mix.h"
52 #include "Prefs.h"
53 #include "ProjectRate.h"
54 #include "../ShuttleGui.h"
55 #include "../Tags.h"
56 #include "../Track.h"
57 #include "../widgets/AudacityMessageBox.h"
58 #include "../widgets/ProgressDialog.h"
59 
60 #define LIBTWOLAME_STATIC
61 #include "twolame.h"
62 
63 #ifdef USE_LIBID3TAG
64    #include <id3tag.h>
65    // DM: the following functions were supposed to have been
66    // included in id3tag.h - should be fixed in the next release
67    // of mad.
68    extern "C" {
69       struct id3_frame *id3_frame_new(char const *);
70       id3_length_t id3_latin1_length(id3_latin1_t const *);
71       void id3_latin1_decode(id3_latin1_t const *, id3_ucs4_t *);
72    }
73 #endif
74 
75 //----------------------------------------------------------------------------
76 // ExportMP2Options
77 //----------------------------------------------------------------------------
78 
79 namespace {
80 
81 // i18n-hint kbps abbreviates "thousands of bits per second"
n_kbps(int n)82 inline TranslatableString n_kbps( int n ) { return XO("%d kbps").Format( n ); }
83 
84 const TranslatableStrings BitRateNames {
85    n_kbps(16),
86    n_kbps(24),
87    n_kbps(32),
88    n_kbps(40),
89    n_kbps(48),
90    n_kbps(56),
91    n_kbps(64),
92    n_kbps(80),
93    n_kbps(96),
94    n_kbps(112),
95    n_kbps(128),
96    n_kbps(160),
97    n_kbps(192),
98    n_kbps(224),
99    n_kbps(256),
100    n_kbps(320),
101    n_kbps(384),
102 };
103 
104 const std::vector< int > BitRateValues {
105    16,
106    24,
107    32,
108    40,
109    48,
110    56,
111    64,
112    80,
113    96,
114    112,
115    128,
116    160,
117    192,
118    224,
119    256,
120    320,
121    384,
122 };
123 
124 }
125 
126 class ExportMP2Options final : public wxPanelWrapper
127 {
128 public:
129    ExportMP2Options(wxWindow *parent, int format);
130    virtual ~ExportMP2Options();
131 
132    void PopulateOrExchange(ShuttleGui & S);
133    bool TransferDataToWindow() override;
134    bool TransferDataFromWindow() override;
135 };
136 
137 ///
138 ///
ExportMP2Options(wxWindow * parent,int WXUNUSED (format))139 ExportMP2Options::ExportMP2Options(wxWindow *parent, int WXUNUSED(format))
140 :  wxPanelWrapper(parent, wxID_ANY)
141 {
142    ShuttleGui S(this, eIsCreatingFromPrefs);
143    PopulateOrExchange(S);
144 
145    TransferDataToWindow();
146 }
147 
148 ///
149 ///
~ExportMP2Options()150 ExportMP2Options::~ExportMP2Options()
151 {
152    TransferDataFromWindow();
153 }
154 
155 ///
156 ///
PopulateOrExchange(ShuttleGui & S)157 void ExportMP2Options::PopulateOrExchange(ShuttleGui & S)
158 {
159    S.StartVerticalLay();
160    {
161       S.StartHorizontalLay(wxCENTER);
162       {
163          S.StartMultiColumn(2, wxCENTER);
164          {
165             S.TieNumberAsChoice(
166                XXO("Bit Rate:"),
167                {wxT("/FileFormats/MP2Bitrate"),
168                 160},
169                BitRateNames,
170                &BitRateValues
171             );
172          }
173          S.EndMultiColumn();
174       }
175       S.EndHorizontalLay();
176    }
177    S.EndVerticalLay();
178 }
179 
180 ///
181 ///
TransferDataToWindow()182 bool ExportMP2Options::TransferDataToWindow()
183 {
184    return true;
185 }
186 
187 ///
188 ///
TransferDataFromWindow()189 bool ExportMP2Options::TransferDataFromWindow()
190 {
191    ShuttleGui S(this, eIsSavingToPrefs);
192    PopulateOrExchange(S);
193 
194    gPrefs->Flush();
195 
196    return true;
197 }
198 
199 //----------------------------------------------------------------------------
200 // ExportMP2
201 //----------------------------------------------------------------------------
202 
203 class ExportMP2 final : public ExportPlugin
204 {
205 public:
206 
207    ExportMP2();
208 
209    // Required
210 
211    void OptionsCreate(ShuttleGui &S, int format) override;
212    ProgressResult Export(AudacityProject *project,
213                std::unique_ptr<ProgressDialog> &pDialog,
214                unsigned channels,
215                const wxFileNameWrapper &fName,
216                bool selectedOnly,
217                double t0,
218                double t1,
219                MixerSpec *mixerSpec = NULL,
220                const Tags *metadata = NULL,
221                int subformat = 0) override;
222 
223 private:
224 
225    int AddTags(AudacityProject *project, ArrayOf<char> &buffer, bool *endOfFile, const Tags *tags);
226 #ifdef USE_LIBID3TAG
227    void AddFrame(struct id3_tag *tp, const wxString & n, const wxString & v, const char *name);
228 #endif
229 
230 };
231 
ExportMP2()232 ExportMP2::ExportMP2()
233 :  ExportPlugin()
234 {
235    AddFormat();
236    SetFormat(wxT("MP2"),0);
237    AddExtension(wxT("mp2"),0);
238    SetMaxChannels(2,0);
239    SetCanMetaData(true,0);
240    SetDescription(XO("MP2 Files"),0);
241 }
242 
Export(AudacityProject * project,std::unique_ptr<ProgressDialog> & pDialog,unsigned channels,const wxFileNameWrapper & fName,bool selectionOnly,double t0,double t1,MixerSpec * mixerSpec,const Tags * metadata,int WXUNUSED (subformat))243 ProgressResult ExportMP2::Export(AudacityProject *project,
244    std::unique_ptr<ProgressDialog> &pDialog,
245    unsigned channels, const wxFileNameWrapper &fName,
246    bool selectionOnly, double t0, double t1, MixerSpec *mixerSpec, const Tags *metadata,
247    int WXUNUSED(subformat))
248 {
249    bool stereo = (channels == 2);
250    long bitrate = gPrefs->Read(wxT("/FileFormats/MP2Bitrate"), 160);
251    double rate = ProjectRate::Get(*project).GetRate();
252    const auto &tracks = TrackList::Get( *project );
253 
254    wxLogNull logNo;             /* temporarily disable wxWidgets error messages */
255 
256    twolame_options *encodeOptions;
257    encodeOptions = twolame_init();
258    auto cleanup = finally( [&] { twolame_close(&encodeOptions); } );
259 
260    twolame_set_in_samplerate(encodeOptions, (int)(rate + 0.5));
261    twolame_set_out_samplerate(encodeOptions, (int)(rate + 0.5));
262    twolame_set_bitrate(encodeOptions, bitrate);
263    twolame_set_num_channels(encodeOptions, stereo ? 2 : 1);
264 
265    if (twolame_init_params(encodeOptions) != 0)
266    {
267       AudacityMessageBox(
268          XO("Cannot export MP2 with this sample rate and bit rate"),
269          XO("Error"),
270          wxICON_STOP);
271       return ProgressResult::Cancelled;
272    }
273 
274    // Put ID3 tags at beginning of file
275    if (metadata == NULL)
276       metadata = &Tags::Get( *project );
277 
278    FileIO outFile(fName, FileIO::Output);
279    if (!outFile.IsOpened()) {
280       AudacityMessageBox( XO("Unable to open target file for writing") );
281       return ProgressResult::Cancelled;
282    }
283 
284    ArrayOf<char> id3buffer;
285    int id3len;
286    bool endOfFile;
287    id3len = AddTags(project, id3buffer, &endOfFile, metadata);
288    if (id3len && !endOfFile) {
289       if ( outFile.Write(id3buffer.get(), id3len).GetLastError() ) {
290          // TODO: more precise message
291          ShowExportErrorDialog("MP2:292");
292          return ProgressResult::Cancelled;
293       }
294    }
295 
296    // Values taken from the twolame simple encoder sample
297    const size_t pcmBufferSize = 9216 / 2; // number of samples
298    const size_t mp2BufferSize = 16384u; // bytes
299 
300    // We allocate a buffer which is twice as big as the
301    // input buffer, which should always be enough.
302    // We have to multiply by 4 because one sample is 2 bytes wide!
303    ArrayOf<unsigned char> mp2Buffer{ mp2BufferSize };
304 
305    auto updateResult = ProgressResult::Success;
306    {
307       auto mixer = CreateMixer(tracks, selectionOnly,
308          t0, t1,
309          stereo ? 2 : 1, pcmBufferSize, true,
310          rate, int16Sample, mixerSpec);
311 
312       InitProgress( pDialog, fName,
313          selectionOnly
314             ? XO("Exporting selected audio at %ld kbps")
315                  .Format( bitrate )
316             : XO("Exporting the audio at %ld kbps")
317                  .Format( bitrate ) );
318       auto &progress = *pDialog;
319 
320       while (updateResult == ProgressResult::Success) {
321          auto pcmNumSamples = mixer->Process(pcmBufferSize);
322 
323          if (pcmNumSamples == 0)
324             break;
325 
326          short *pcmBuffer = (short *)mixer->GetBuffer();
327 
328          int mp2BufferNumBytes = twolame_encode_buffer_interleaved(
329             encodeOptions,
330             pcmBuffer,
331             pcmNumSamples,
332             mp2Buffer.get(),
333             mp2BufferSize);
334 
335          if (mp2BufferNumBytes < 0) {
336             // TODO: more precise message
337             ShowExportErrorDialog("MP2:339");
338             updateResult = ProgressResult::Cancelled;
339             break;
340          }
341 
342          if ( outFile.Write(mp2Buffer.get(), mp2BufferNumBytes).GetLastError() ) {
343             // TODO: more precise message
344             ShowDiskFullExportErrorDialog(fName);
345             return ProgressResult::Cancelled;
346          }
347 
348          updateResult = progress.Update(mixer->MixGetCurrentTime() - t0, t1 - t0);
349       }
350    }
351 
352    int mp2BufferNumBytes = twolame_encode_flush(
353       encodeOptions,
354       mp2Buffer.get(),
355       mp2BufferSize);
356 
357    if (mp2BufferNumBytes > 0)
358       if ( outFile.Write(mp2Buffer.get(), mp2BufferNumBytes).GetLastError() ) {
359          // TODO: more precise message
360          ShowExportErrorDialog("MP2:362");
361          return ProgressResult::Cancelled;
362       }
363 
364    /* Write ID3 tag if it was supposed to be at the end of the file */
365 
366    if (id3len && endOfFile)
367       if ( outFile.Write(id3buffer.get(), id3len).GetLastError() ) {
368          // TODO: more precise message
369          ShowExportErrorDialog("MP2:371");
370          return ProgressResult::Cancelled;
371       }
372 
373    if ( !outFile.Close() ) {
374       // TODO: more precise message
375       ShowExportErrorDialog("MP2:377");
376       return ProgressResult::Cancelled;
377    }
378 
379    return updateResult;
380 }
381 
OptionsCreate(ShuttleGui & S,int format)382 void ExportMP2::OptionsCreate(ShuttleGui &S, int format)
383 {
384    S.AddWindow( safenew ExportMP2Options{ S.GetParent(), format } );
385 }
386 
387 
388 #ifdef USE_LIBID3TAG
389 struct id3_tag_deleter {
operator ()id3_tag_deleter390    void operator () (id3_tag *p) const { if (p) id3_tag_delete(p); }
391 };
392 using id3_tag_holder = std::unique_ptr<id3_tag, id3_tag_deleter>;
393 #endif
394 
395 // returns buffer len; caller frees
AddTags(AudacityProject * WXUNUSED (project),ArrayOf<char> & buffer,bool * endOfFile,const Tags * tags)396 int ExportMP2::AddTags(
397    AudacityProject * WXUNUSED(project), ArrayOf< char > &buffer,
398    bool *endOfFile, const Tags *tags)
399 {
400 #ifdef USE_LIBID3TAG
401    id3_tag_holder tp { id3_tag_new() };
402 
403    for (const auto &pair : tags->GetRange()) {
404       const auto &n = pair.first;
405       const auto &v = pair.second;
406       const char *name = "TXXX";
407 
408       if (n.CmpNoCase(TAG_TITLE) == 0) {
409          name = ID3_FRAME_TITLE;
410       }
411       else if (n.CmpNoCase(TAG_ARTIST) == 0) {
412          name = ID3_FRAME_ARTIST;
413       }
414       else if (n.CmpNoCase(TAG_ALBUM) == 0) {
415          name = ID3_FRAME_ALBUM;
416       }
417       else if (n.CmpNoCase(TAG_YEAR) == 0) {
418          // LLL:  Some apps do not like the newer frame ID (ID3_FRAME_YEAR),
419          //       so we add old one as well.
420          AddFrame(tp.get(), n, v, "TYER");
421          name = ID3_FRAME_YEAR;
422       }
423       else if (n.CmpNoCase(TAG_GENRE) == 0) {
424          name = ID3_FRAME_GENRE;
425       }
426       else if (n.CmpNoCase(TAG_COMMENTS) == 0) {
427          name = ID3_FRAME_COMMENT;
428       }
429       else if (n.CmpNoCase(TAG_TRACK) == 0) {
430          name = ID3_FRAME_TRACK;
431       }
432 
433       AddFrame(tp.get(), n, v, name);
434    }
435 
436    tp->options &= (~ID3_TAG_OPTION_COMPRESSION); // No compression
437 
438    // If this version of libid3tag supports it, use v2.3 ID3
439    // tags instead of the newer, but less well supported, v2.4
440    // that libid3tag uses by default.
441    #ifdef ID3_TAG_HAS_TAG_OPTION_ID3V2_3
442    tp->options |= ID3_TAG_OPTION_ID3V2_3;
443    #endif
444 
445    *endOfFile = false;
446 
447    id3_length_t len;
448 
449    len = id3_tag_render(tp.get(), 0);
450    buffer.reinit(len);
451    len = id3_tag_render(tp.get(), (id3_byte_t *)buffer.get());
452 
453 
454    return len;
455 #else //ifdef USE_LIBID3TAG
456    return 0;
457 #endif
458 }
459 
460 #ifdef USE_LIBID3TAG
AddFrame(struct id3_tag * tp,const wxString & n,const wxString & v,const char * name)461 void ExportMP2::AddFrame(struct id3_tag *tp, const wxString & n, const wxString & v, const char *name)
462 {
463    struct id3_frame *frame = id3_frame_new(name);
464 
465    if (!n.IsAscii() || !v.IsAscii()) {
466       id3_field_settextencoding(id3_frame_field(frame, 0), ID3_FIELD_TEXTENCODING_UTF_16);
467    }
468    else {
469       id3_field_settextencoding(id3_frame_field(frame, 0), ID3_FIELD_TEXTENCODING_ISO_8859_1);
470    }
471 
472    MallocString<id3_ucs4_t> ucs4 {
473       id3_utf8_ucs4duplicate((id3_utf8_t *) (const char *) v.mb_str(wxConvUTF8)) };
474 
475    if (strcmp(name, ID3_FRAME_COMMENT) == 0) {
476       // A hack to get around iTunes not recognizing the comment.  The
477       // language defaults to XXX and, since it's not a valid language,
478       // iTunes just ignores the tag.  So, either set it to a valid language
479       // (which one???) or just clear it.  Unfortunately, there's no supported
480       // way of clearing the field, so do it directly.
481       id3_field *f = id3_frame_field(frame, 1);
482       memset(f->immediate.value, 0, sizeof(f->immediate.value));
483       id3_field_setfullstring(id3_frame_field(frame, 3), ucs4.get());
484    }
485    else if (strcmp(name, "TXXX") == 0) {
486       id3_field_setstring(id3_frame_field(frame, 2), ucs4.get());
487 
488       ucs4.reset(id3_utf8_ucs4duplicate((id3_utf8_t *) (const char *) n.mb_str(wxConvUTF8)));
489 
490       id3_field_setstring(id3_frame_field(frame, 1), ucs4.get());
491    }
492    else {
493       auto addr = ucs4.get();
494       id3_field_setstrings(id3_frame_field(frame, 1), 1, &addr);
495    }
496 
497    id3_tag_attachframe(tp, frame);
498 }
499 #endif
500 
501 static Exporter::RegisteredExportPlugin sRegisteredPlugin{ "MP2",
__anon18789a1c0302null502    []{ return std::make_unique< ExportMP2 >(); }
503 };
504 
505 #endif // #ifdef USE_LIBTWOLAME
506 
507