Audacity 3.2.0
ImportMP3_MPG123.cpp
Go to the documentation of this file.
1/* SPDX-License-Identifier: GPL-2.0-or-later */
2/*!********************************************************************
3
4 Audacity: A Digital Audio Editor
5
6 ImportMP3_MPG123.cpp
7
8 Dmitry Vedenko
9
10*/
11
12#include <wx/defs.h>
13#include <cstddef>
14#include <cstring>
15
16#include "Import.h"
17#include "BasicUI.h"
18#include "ImportPlugin.h"
19#include "Project.h"
20
21#define DESC XO("MP3 files")
22
23#include <wx/file.h>
24#include <wx/string.h>
25#include <wx/log.h>
26
27#include <mpg123.h>
28
29#include "Prefs.h"
30#include "Tags.h"
31#include "WaveTrack.h"
32#include "AudacityMessageBox.h"
33#include "ProgressDialog.h"
34
35#include "CodeConversions.h"
36#include "FromChars.h"
37
38namespace
39{
40
41static const auto exts = { wxT("mp3"), wxT("mp2"), wxT("mpa") };
42
43// ID2V2 genre can be quite complex:
44// (from https://id3.org/id3v2.3.0)
45// References to the ID3v1 genres can be made by, as first byte, enter
46// "(" followed by a number from the genres list (appendix A) and ended
47// with a ")" character. This is optionally followed by a refinement,
48// e.g. "(21)" or "(4)Eurodisco". Several references can be made in the
49// same frame, e.g. "(51)(39)". However, Audacity only supports one
50// genre, so we just skip ( a parse the number afterwards.
51wxString GetId3v2Genre(Tags& tags, const char* genre)
52{
53 if (genre == nullptr)
54 return {};
55
56 // It was observed, however, that Genre can use a different format
57 if (genre[0] != '(')
58 // We consider the string to be a genre name
59 return audacity::ToWXString(genre);
60
61 auto it = genre;
62 auto end = it + std::strlen(it);
63
64 while (*it == '(')
65 {
66 int tagValue;
67 auto result = FromChars(++it, end, tagValue);
68
69 // Parsing failed, consider it to be the genre
70 if (result.ec != std::errc {})
71 break;
72
73 const auto parsedGenre = tags.GetGenre(tagValue);
74
75 if (!parsedGenre.empty())
76 return parsedGenre;
77
78 it = result.ptr;
79
80 // Nothing left to parse
81 if (it == end)
82 break;
83
84 // Unexpected symbol in the tag
85 if (*it != ')')
86 break;
87
88 ++it;
89 }
90
91 if (it != end)
92 return audacity::ToWXString(it);
93
94 return audacity::ToWXString(genre);
95}
96
97class MP3ImportPlugin final : public ImportPlugin
98{
99public:
101 : ImportPlugin(
103 {
104#if MPG123_API_VERSION < 46
105 // Newer versions of the library don't need that anymore, but it is safe
106 // to have the no-op call present for compatibility with old versions.
107 mpg123_init();
108#endif
109 }
110
111 wxString GetPluginStringID() override
112 {
113 return wxT("libmpg123");
114 }
115
117 {
118 return DESC;
119 }
120
121 std::unique_ptr<ImportFileHandle> Open(const FilePath &Filename, AudacityProject*) override;
122}; // class MP3ImportPlugin
123
124using NewChannelGroup = std::vector< std::shared_ptr<WaveTrack> >;
125
127{
128public:
129 MP3ImportFileHandle(const FilePath &filename);
131
134 ProgressResult Import(WaveTrackFactory *trackFactory, TrackHolders &outTracks, Tags *tags) override;
135
136 bool SetupOutputFormat();
137
138 void ReadTags(Tags* tags);
139
140 wxInt32 GetStreamCount() override;
141 const TranslatableStrings &GetStreamInfo() override;
142 void SetStreamUsage(wxInt32 StreamID, bool Use) override;
143
144private:
145 bool Open();
146
147private:
148 static ptrdiff_t ReadCallback(void* handle, void* buffer, size_t size);
149 static off_t SeekCallback(void* handle, off_t offset, int whence);
150
151 wxFile mFile;
152 wxFileOffset mFileLen { 0 };
153
156 unsigned mNumChannels { 0 };
157
159
160 mpg123_handle* mHandle { nullptr };
161
162 bool mFloat64Output {};
163
165}; // class MP3ImportFileHandle
166
167std::unique_ptr<ImportFileHandle> MP3ImportPlugin::Open(
168 const FilePath &Filename, AudacityProject *)
169{
170 auto handle = std::make_unique<MP3ImportFileHandle>(Filename);
171
172 if (!handle->Open())
173 return nullptr;
174
175 return handle;
176}
177
179{
180 "MP3",
181 std::make_unique<MP3ImportPlugin>()
182};
183
184// ============================================================================
185// MP3ImportFileHandle
186// ============================================================================
187
189 : ImportFileHandle(filename)
190{
191 int errorCode = MPG123_OK;
192 mHandle = mpg123_new(nullptr, &errorCode);
193
194 if (errorCode != MPG123_OK)
195 {
196 wxLogError(
197 "Failed to create MPG123 handle: %s",
198 mpg123_plain_strerror(errorCode));
199
200 mHandle = nullptr;
201
202 return;
203 }
204
205 errorCode = mpg123_replace_reader_handle(
207
208 if (errorCode != MPG123_OK)
209 {
210 wxLogError(
211 "Failed to set reader on the MPG123 handle: %s",
212 mpg123_plain_strerror(errorCode));
213
214 mpg123_delete(mHandle);
215 mHandle = nullptr;
216 }
217
218 // We force mpg123 to decode into floats
219 mpg123_param(mHandle, MPG123_FLAGS, MPG123_FORCE_FLOAT, 0.0);
220
221 if (errorCode != MPG123_OK)
222 {
223 wxLogError(
224 "Failed to set options on the MPG123 handle",
225 mpg123_plain_strerror(errorCode));
226
227 mpg123_delete(mHandle);
228 mHandle = nullptr;
229 }
230}
231
233{
234 // nullptr is a valid input for the mpg123_delete
235 mpg123_delete(mHandle);
236}
237
239{
240 return DESC;
241}
242
244{
245 // We have to parse the file first using mpg123_scan,
246 // we do not want to do that before the import starts.
247 return 0;
248}
249
251{
252 return 1;
253}
254
256{
257 static TranslatableStrings empty;
258 return empty;
259}
260
261void MP3ImportFileHandle::SetStreamUsage(wxInt32 WXUNUSED(StreamID), bool WXUNUSED(Use))
262{
263}
264
266 TrackHolders &outTracks,
267 Tags *tags)
268{
269 auto finalAction = finally([handle = mHandle]() { mpg123_close(handle); });
270
271 outTracks.clear();
272 mTrackFactory = trackFactory;
273
275
276 long long framesCount = mpg123_framelength(mHandle);
277
278 mUpdateResult = mProgress->Update(0ll, framesCount);
279
282
283 if (!SetupOutputFormat())
284 return ProgressResult::Failed;
285
286 off_t frameIndex { 0 };
287 unsigned char* data { nullptr };
288 size_t dataSize { 0 };
289
290 std::vector<float> conversionBuffer;
291
292 int ret = MPG123_OK;
293
294 while ((ret = mpg123_decode_frame(mHandle, &frameIndex, &data, &dataSize)) ==
295 MPG123_OK)
296 {
297 mUpdateResult = mProgress->Update(
298 static_cast<long long>(frameIndex), framesCount);
299
302
303 constSamplePtr samples = reinterpret_cast<constSamplePtr>(data);
304 const size_t samplesCount = dataSize / sizeof(float) / mNumChannels;
305
306 // libmpg123 picks up the format based on some "internal" precision.
307 // This case is not expected to happen
308 if (mFloat64Output)
309 {
310 conversionBuffer.resize(samplesCount * mNumChannels);
311
312 for (size_t sampleIndex = 0; sampleIndex < conversionBuffer.size();
313 ++sampleIndex)
314 {
315 conversionBuffer[sampleIndex] = static_cast<float>(
316 reinterpret_cast<const double*>(data)[sampleIndex]);
317 }
318
319 samples = reinterpret_cast<constSamplePtr>(conversionBuffer.data());
320 }
321 // Just copy the interleaved data to the channels
322 for (unsigned channel = 0; channel < mNumChannels; ++channel)
323 {
324 mChannels[channel]->Append(
325 samples + sizeof(float) * channel, floatSample, samplesCount,
327 }
328 }
329
330 if (ret != MPG123_DONE)
331 {
332 wxLogError(
333 "Failed to decode MP3 file: %s", mpg123_plain_strerror(ret));
334
335 return ProgressResult::Failed;
336 }
337
338 // Flush and trim the channels
339 for (const auto &channel : mChannels)
340 channel->Flush();
341
342 // Copy the WaveTrack pointers into the Track pointer list that
343 // we are expected to fill
344 outTracks.push_back(std::move(mChannels));
345
346 ReadTags(tags);
347
348 return mUpdateResult;
349}
350
352{
353 long rate;
354 int channels;
355 int encoding = MPG123_ENC_FLOAT_32;
356 mpg123_getformat(mHandle, &rate, &channels, &encoding);
357
358 mNumChannels = channels == MPG123_MONO ? 1 : 2;
359 mChannels.resize(mNumChannels);
360
361 if (encoding != MPG123_ENC_FLOAT_32 && encoding != MPG123_ENC_FLOAT_64)
362 {
363 wxLogError("MPG123 returned unexpected encoding");
364
365 return false;
366 }
367
368 mFloat64Output = encoding == MPG123_ENC_FLOAT_64;
369
370 for (unsigned i = 0; i < mNumChannels; ++i)
372
373 return true;
374}
375
377{
378 mpg123_id3v1* v1;
379 mpg123_id3v2* v2;
380 int meta;
381
382 meta = mpg123_meta_check(mHandle);
383
384 if (meta & MPG123_ID3 && mpg123_id3(mHandle, &v1, &v2) == MPG123_OK)
385 {
386 if (v2 != nullptr && v2->title != nullptr && v2->title->fill > 0)
387 tags->SetTag(TAG_TITLE, audacity::ToWXString(v2->title->p));
388 else if (v1 != nullptr && v1->title[0] != '\0')
389 tags->SetTag(TAG_TITLE, audacity::ToWXString(v1->title));
390
391 if (v2 != nullptr && v2->artist != nullptr && v2->artist->fill > 0)
392 tags->SetTag(TAG_ARTIST, audacity::ToWXString(v2->artist->p));
393 else if (v1 != nullptr && v1->artist[0] != '\0')
394 tags->SetTag(TAG_ARTIST, audacity::ToWXString(v1->artist));
395
396 if (v2 != nullptr && v2->album != nullptr && v2->album->fill > 0)
397 tags->SetTag(TAG_ALBUM, audacity::ToWXString(v2->album->p));
398 else if (v1 != nullptr && v1->album[0] != '\0')
399 tags->SetTag(TAG_ALBUM, audacity::ToWXString(v1->album));
400
401 if (v2 != nullptr && v2->year != nullptr && v2->year->fill > 0)
402 tags->SetTag(TAG_YEAR, audacity::ToWXString(v2->year->p));
403 else if (v1 != nullptr && v1->year[0] != '\0')
404 tags->SetTag(TAG_YEAR, audacity::ToWXString(std::string(v1->year, 4)));
405
406 if (v2 != nullptr && v2->genre != nullptr && v2->genre->fill > 0)
407 tags->SetTag(TAG_GENRE, GetId3v2Genre(*tags, v2->genre->p));
408 else if (v1 != nullptr)
409 tags->SetTag(TAG_GENRE, tags->GetGenre(v1->genre));
410
411 if (v2 != nullptr && v2->comment != nullptr && v2->comment->fill > 0)
412 tags->SetTag(TAG_COMMENTS, audacity::ToWXString(v2->comment->p));
413 else if (v1 != nullptr && v1->comment[0] != '\0')
414 tags->SetTag(TAG_COMMENTS, audacity::ToWXString(v1->comment));
415
416 if (v2 != nullptr)
417 {
418 for (size_t i = 0; i < v2->comments; ++i)
419 {
420 if (v2->comment_list[i].text.fill == 0)
421 continue;
422
423 tags->SetTag(
424 audacity::ToWXString(std::string(v2->comment_list[i].id, 4)),
425 audacity::ToWXString(v2->comment_list[i].text.p));
426 }
427
428 for (size_t i = 0; i < v2->extras; ++i)
429 {
430 if (v2->extra[i].text.fill == 0)
431 continue;
432
433 tags->SetTag(
434 audacity::ToWXString(std::string(v2->extra[i].id, 4)),
435 audacity::ToWXString(v2->extra[i].text.p));
436 }
437
438 // libmpg123 does not parse TRCK tag, we have to do it ourselves
439 for (size_t i = 0; i < v2->texts; ++i)
440 {
441 if (memcmp(v2->text[i].id, "TRCK", 4) == 0)
442 {
443 tags->SetTag(
444 TAG_TRACK, audacity::ToWXString(v2->text[i].text.p));
445 }
446 }
447 }
448 }
449}
450
452{
453 if (mHandle == nullptr)
454 return false;
455
456 // Open the file
457 if (!mFile.Open(mFilename))
458 {
459 return false;
460 }
461
462 // Get the length of the file
463 mFileLen = mFile.Seek(0, wxFromEnd);
464
465 if (mFileLen == wxInvalidOffset || mFile.Error())
466 {
467 mFile.Close();
468 return false;
469 }
470
471 if (mFile.Seek(0, wxFromStart) == wxInvalidOffset || mFile.Error())
472 {
473 mFile.Close();
474 return false;
475 }
476
477 // Check if file is an MP3
478 auto errorCode = mpg123_open_handle(mHandle, this);
479
480 if (errorCode != MPG123_OK)
481 return false;
482
483 // Scan the file
484 errorCode = mpg123_scan(mHandle);
485
486 if (errorCode != MPG123_OK)
487 return false;
488
489 // Read the output format
490 errorCode = mpg123_decode_frame(mHandle, nullptr, nullptr, nullptr);
491
492 // First decode should read the format
493 if (errorCode != MPG123_NEW_FORMAT)
494 return false;
495
496 return true;
497}
498
500 void* handle, void* buffer, size_t size)
501{
502 return static_cast<MP3ImportFileHandle*>(handle)->mFile.Read(buffer, size);
503}
504
505wxSeekMode GetWXSeekMode(int whence)
506{
507 switch (whence)
508 {
509 case SEEK_SET:
510 return wxFromStart;
511 case SEEK_CUR:
512 return wxFromCurrent;
513 case SEEK_END:
514 return wxFromEnd;
515 default:
516 // We have covered all the lseek whence modes defined by POSIX
517 // so this branch should not be reachable
518 assert(false);
519 return wxFromStart;
520 }
521}
522
524 void* handle, off_t offset, int whence)
525{
526 return static_cast<MP3ImportFileHandle*>(handle)->mFile.Seek(
527 offset, GetWXSeekMode(whence));
528}
529
530} // namespace
wxT("CloseDown"))
Toolkit-neutral facade for basic user interface services.
Declare functions to perform UTF-8 to std::wstring conversions.
FromCharsResult FromChars(const char *buffer, const char *last, float &value) noexcept
Parse a string into a single precision floating point value, always uses the dot as decimal.
Definition: FromChars.cpp:153
Declare functions to convert numeric types to string representation.
std::vector< std::vector< std::shared_ptr< WaveTrack > > > TrackHolders
Definition: Import.h:39
#define DESC
The interface that all file import "plugins" (if you want to call them that) must implement....
wxString FilePath
Definition: Project.h:21
constexpr sampleFormat floatSample
Definition: SampleFormat.h:43
const char * constSamplePtr
Definition: SampleFormat.h:56
#define TAG_TRACK
Definition: Tags.h:61
#define TAG_COMMENTS
Definition: Tags.h:64
#define TAG_GENRE
Definition: Tags.h:63
#define TAG_ALBUM
Definition: Tags.h:60
#define TAG_YEAR
Definition: Tags.h:62
#define TAG_TITLE
Definition: Tags.h:58
#define TAG_ARTIST
Definition: Tags.h:59
std::vector< TranslatableString > TranslatableStrings
The top-level handle to an Audacity project. It serves as a source of events that other objects can b...
Definition: Project.h:90
An ImportFileHandle for data.
Definition: ImportPlugin.h:112
FilePath mFilename
Definition: ImportPlugin.h:163
unsigned long long ByteCount
Definition: ImportPlugin.h:132
std::unique_ptr< ProgressDialog > mProgress
Definition: ImportPlugin.h:164
std::shared_ptr< WaveTrack > NewWaveTrack(WaveTrackFactory &trackFactory, sampleFormat effectiveFormat, double rate)
Build a wave track with appropriate format, which will not be narrower than the specified one.
Base class for FlacImportPlugin, LOFImportPlugin, MP3ImportPlugin, OggImportPlugin and PCMImportPlugi...
Definition: ImportPlugin.h:68
ProgressResult Import(WaveTrackFactory *trackFactory, TrackHolders &outTracks, Tags *tags) override
ByteCount GetFileUncompressedBytes() override
MP3ImportFileHandle(const FilePath &filename)
const TranslatableStrings & GetStreamInfo() override
TranslatableString GetFileDescription() override
wxInt32 GetStreamCount() override
ProgressResult mUpdateResult
WaveTrackFactory * mTrackFactory
std::unique_ptr< ImportFileHandle > Open(const FilePath &Filename, AudacityProject *) override
ID3 Tags (for MP3)
Definition: Tags.h:73
wxString GetGenre(int value)
Definition: Tags.cpp:383
void SetTag(const wxString &name, const wxString &value, const bool bSpecialTag=false)
Definition: Tags.cpp:441
Holds a msgid for the translation catalog; may also bind format arguments.
Used to create or clone a WaveTrack, with appropriate context from the project that will own the trac...
Definition: WaveTrack.h:565
ProgressResult Import(WaveTrackFactory *trackFactory, TrackHolders &outTracks, Tags *tags) override
static ptrdiff_t ReadCallback(void *handle, void *buffer, size_t size)
static off_t SeekCallback(void *handle, off_t offset, int whence)
void SetStreamUsage(wxInt32 StreamID, bool Use) override
Extend wxArrayString with move operations and construction and insertion fromstd::initializer_list.
ProgressResult
Definition: BasicUI.h:147
auto end(const Ptr< Type, BaseDeleter > &p)
Enables range-for.
Definition: PackedArray.h:159
auto begin(const Ptr< Type, BaseDeleter > &p)
Enables range-for.
Definition: PackedArray.h:150
wxString GetId3v2Genre(Tags &tags, const char *genre)
static Importer::RegisteredImportPlugin registered
std::vector< std::shared_ptr< WaveTrack > > NewChannelGroup
wxString ToWXString(const std::string &str)