mirror of
https://github.com/cookiengineer/audacity
synced 2025-06-26 09:08:44 +02:00
Correct string formatting for: 2/15 %d + enum => %d + int 3/15 %lld + int64_t => %lld + long long 4/15 %d + int64_t => %lld + long long 5/15 %d + double => %f + double 6/15 %d + int32_t => %d + int 7/15 %d + intptr_t => %p + void* 8/15 gint, guint 9/15 %d + long => %ld + long 10/15 %n + int => %d + int 11/15 %x + int => %x + unsigned int 12/15 %f + int => %d + int 13/15 %S + wxChar* => %s + wxChar* 14/15 %d + size_t => %d + int 15/15 %d + size_t => %lld + long long "The functions wxString::Format, wxString::Printf (and others indirectly) have become stricter about parameter types that don't match (format specifier vs. function parameters). So the bugs (that were already present in audacity before) become visible in wx3.0 as error message dialogs. I've checked all occurrences of Printf, wxPrintf, PrintfV, Format, FormatV, wxLogDebug and wxLogError systematically and made the type match." Note (9/15): In TrackPanel.cpp, ExportMP2.cpp and CompareAudioCommand.cpp this patch supersedes related change done in r13466 because the new solution requires fewer casts and therefore simplifies the code. Note: Many .po files are affected, and we need to be very careful about this. Incorrect "%d" and similar in translation files may lead to crashes in those languages (only). This is something we should actually have been more careful about in the past. We need to write a script to check that the "%d" and similar format specifiers match between English and translation.
457 lines
12 KiB
C++
457 lines
12 KiB
C++
/**********************************************************************
|
|
|
|
Audacity: A Digital Audio Editor
|
|
|
|
ExportMP2.cpp
|
|
|
|
Joshua Haberman
|
|
Markus Meyer
|
|
|
|
Copyright 2002, 2003 Joshua Haberman.
|
|
Copyright 2006 Markus Meyer
|
|
Some portions may be Copyright 2003 Paolo Patruno.
|
|
|
|
This program is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation; either version 2 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program 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.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program; if not, write to the Free Software
|
|
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
|
|
*******************************************************************//**
|
|
|
|
\class MP2Exporter
|
|
\brief Class used to export MP2 files
|
|
|
|
*/
|
|
|
|
#include "../Audacity.h"
|
|
|
|
#ifdef USE_LIBTWOLAME
|
|
|
|
#include <wx/defs.h>
|
|
#include <wx/textctrl.h>
|
|
#include <wx/dynlib.h>
|
|
#include <wx/msgdlg.h>
|
|
#include <wx/utils.h>
|
|
#include <wx/timer.h>
|
|
#include <wx/window.h>
|
|
#include <wx/log.h>
|
|
#include <wx/intl.h>
|
|
|
|
#include "Export.h"
|
|
#include "ExportMP2.h"
|
|
#include "../FileIO.h"
|
|
#include "../Internat.h"
|
|
#include "../Mix.h"
|
|
#include "../Prefs.h"
|
|
#include "../Project.h"
|
|
#include "../Tags.h"
|
|
#include "../WaveTrack.h"
|
|
|
|
#define LIBTWOLAME_STATIC
|
|
#include "twolame.h"
|
|
|
|
#ifdef USE_LIBID3TAG
|
|
#include <id3tag.h>
|
|
// DM: the following functions were supposed to have been
|
|
// included in id3tag.h - should be fixed in the next release
|
|
// of mad.
|
|
extern "C" {
|
|
struct id3_frame *id3_frame_new(char const *);
|
|
id3_length_t id3_latin1_length(id3_latin1_t const *);
|
|
void id3_latin1_decode(id3_latin1_t const *, id3_ucs4_t *);
|
|
}
|
|
#endif
|
|
|
|
//----------------------------------------------------------------------------
|
|
// ExportMP2Options
|
|
//----------------------------------------------------------------------------
|
|
|
|
static int iBitrates[] = {
|
|
16, 24, 32, 40, 48, 56, 64,
|
|
80, 96, 112, 128, 160,
|
|
192, 224, 256, 320, 384
|
|
};
|
|
|
|
class ExportMP2Options : public wxDialog
|
|
{
|
|
public:
|
|
|
|
///
|
|
///
|
|
ExportMP2Options(wxWindow *parent);
|
|
void PopulateOrExchange(ShuttleGui & S);
|
|
void OnOK(wxCommandEvent& event);
|
|
|
|
private:
|
|
wxArrayString mBitRateNames;
|
|
wxArrayInt mBitRateLabels;
|
|
|
|
DECLARE_EVENT_TABLE()
|
|
};
|
|
|
|
BEGIN_EVENT_TABLE(ExportMP2Options, wxDialog)
|
|
EVT_BUTTON(wxID_OK, ExportMP2Options::OnOK)
|
|
END_EVENT_TABLE()
|
|
|
|
///
|
|
///
|
|
ExportMP2Options::ExportMP2Options(wxWindow *parent)
|
|
: wxDialog(parent, wxID_ANY,
|
|
wxString(_("Specify MP2 Options")))
|
|
{
|
|
ShuttleGui S(this, eIsCreatingFromPrefs);
|
|
|
|
for (unsigned int i=0; i < (sizeof(iBitrates)/sizeof(int)); i++)
|
|
{
|
|
mBitRateNames.Add(wxString::Format(_("%i kbps"),iBitrates[i]));
|
|
mBitRateLabels.Add(iBitrates[i]);
|
|
}
|
|
|
|
PopulateOrExchange(S);
|
|
}
|
|
|
|
///
|
|
///
|
|
void ExportMP2Options::PopulateOrExchange(ShuttleGui & S)
|
|
{
|
|
S.StartHorizontalLay(wxEXPAND, 0);
|
|
{
|
|
S.StartStatic(_("MP2 Export Setup"), 0);
|
|
{
|
|
S.StartTwoColumn();
|
|
{
|
|
S.TieChoice(_("Bit Rate:"), wxT("/FileFormats/MP2Bitrate"),
|
|
160, mBitRateNames, mBitRateLabels);
|
|
}
|
|
S.EndTwoColumn();
|
|
}
|
|
S.EndStatic();
|
|
}
|
|
S.EndHorizontalLay();
|
|
|
|
S.AddStandardButtons();
|
|
|
|
Layout();
|
|
Fit();
|
|
SetMinSize(GetSize());
|
|
Center();
|
|
|
|
return;
|
|
}
|
|
|
|
///
|
|
///
|
|
void ExportMP2Options::OnOK(wxCommandEvent& WXUNUSED(event))
|
|
{
|
|
ShuttleGui S(this, eIsSavingToPrefs);
|
|
PopulateOrExchange(S);
|
|
|
|
EndModal(wxID_OK);
|
|
|
|
return;
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
// ExportMP2
|
|
//----------------------------------------------------------------------------
|
|
|
|
class ExportMP2 : public ExportPlugin
|
|
{
|
|
public:
|
|
|
|
ExportMP2();
|
|
void Destroy();
|
|
|
|
// Required
|
|
|
|
bool DisplayOptions(wxWindow *parent, int format = 0);
|
|
int Export(AudacityProject *project,
|
|
int channels,
|
|
wxString fName,
|
|
bool selectedOnly,
|
|
double t0,
|
|
double t1,
|
|
MixerSpec *mixerSpec = NULL,
|
|
Tags *metadata = NULL,
|
|
int subformat = 0);
|
|
|
|
private:
|
|
|
|
int AddTags(AudacityProject *project, char **buffer, bool *endOfFile, Tags *tags);
|
|
#ifdef USE_LIBID3TAG
|
|
void AddFrame(struct id3_tag *tp, const wxString & n, const wxString & v, const char *name);
|
|
#endif
|
|
|
|
};
|
|
|
|
ExportMP2::ExportMP2()
|
|
: ExportPlugin()
|
|
{
|
|
AddFormat();
|
|
SetFormat(wxT("MP2"),0);
|
|
AddExtension(wxT("mp2"),0);
|
|
SetMaxChannels(2,0);
|
|
SetCanMetaData(true,0);
|
|
SetDescription(_("MP2 Files"),0);
|
|
}
|
|
|
|
void ExportMP2::Destroy()
|
|
{
|
|
delete this;
|
|
}
|
|
|
|
int ExportMP2::Export(AudacityProject *project,
|
|
int channels, wxString fName,
|
|
bool selectionOnly, double t0, double t1, MixerSpec *mixerSpec, Tags *metadata,
|
|
int WXUNUSED(subformat))
|
|
{
|
|
bool stereo = (channels == 2);
|
|
long bitrate = gPrefs->Read(wxT("/FileFormats/MP2Bitrate"), 160);
|
|
double rate = project->GetRate();
|
|
TrackList *tracks = project->GetTracks();
|
|
|
|
wxLogNull logNo; /* temporarily disable wxWidgets error messages */
|
|
|
|
twolame_options *encodeOptions;
|
|
encodeOptions = twolame_init();
|
|
|
|
twolame_set_in_samplerate(encodeOptions, (int)(rate + 0.5));
|
|
twolame_set_out_samplerate(encodeOptions, (int)(rate + 0.5));
|
|
twolame_set_bitrate(encodeOptions, bitrate);
|
|
twolame_set_num_channels(encodeOptions, stereo ? 2:1);
|
|
|
|
if (twolame_init_params(encodeOptions) != 0)
|
|
{
|
|
wxMessageBox(_("Cannot export MP2 with this sample rate and bit rate"),
|
|
_("Error"), wxICON_STOP);
|
|
twolame_close(&encodeOptions);
|
|
return false;
|
|
}
|
|
|
|
// Put ID3 tags at beginning of file
|
|
if (metadata == NULL)
|
|
metadata = project->GetTags();
|
|
|
|
FileIO outFile(fName, FileIO::Output);
|
|
if (!outFile.IsOpened()) {
|
|
wxMessageBox(_("Unable to open target file for writing"));
|
|
twolame_close(&encodeOptions);
|
|
return false;
|
|
}
|
|
|
|
char *id3buffer = NULL;
|
|
int id3len;
|
|
bool endOfFile;
|
|
id3len = AddTags(project, &id3buffer, &endOfFile, metadata);
|
|
if (id3len && !endOfFile)
|
|
outFile.Write(id3buffer, id3len);
|
|
|
|
// Values taken from the twolame simple encoder sample
|
|
const int pcmBufferSize = 9216 / 2; // number of samples
|
|
const int mp2BufferSize = 16384 ; // bytes
|
|
|
|
// We allocate a buffer which is twice as big as the
|
|
// input buffer, which should always be enough.
|
|
// We have to multiply by 4 because one sample is 2 bytes wide!
|
|
unsigned char* mp2Buffer = new unsigned char[mp2BufferSize];
|
|
|
|
int numWaveTracks;
|
|
WaveTrack **waveTracks;
|
|
tracks->GetWaveTracks(selectionOnly, &numWaveTracks, &waveTracks);
|
|
Mixer *mixer = CreateMixer(numWaveTracks, waveTracks,
|
|
tracks->GetTimeTrack(),
|
|
t0, t1,
|
|
stereo? 2: 1, pcmBufferSize, true,
|
|
rate, int16Sample, true, mixerSpec);
|
|
delete [] waveTracks;
|
|
|
|
ProgressDialog *progress = new ProgressDialog(wxFileName(fName).GetName(),
|
|
selectionOnly ?
|
|
wxString::Format(_("Exporting selected audio at %ld kbps"), bitrate) :
|
|
wxString::Format(_("Exporting entire file at %ld kbps"), bitrate));
|
|
|
|
int updateResult = eProgressSuccess;
|
|
while(updateResult == eProgressSuccess) {
|
|
sampleCount pcmNumSamples = mixer->Process(pcmBufferSize);
|
|
|
|
if (pcmNumSamples == 0)
|
|
break;
|
|
|
|
short *pcmBuffer = (short *)mixer->GetBuffer();
|
|
|
|
int mp2BufferNumBytes = twolame_encode_buffer_interleaved(
|
|
encodeOptions,
|
|
pcmBuffer,
|
|
pcmNumSamples,
|
|
mp2Buffer,
|
|
mp2BufferSize);
|
|
|
|
outFile.Write(mp2Buffer, mp2BufferNumBytes);
|
|
|
|
updateResult = progress->Update(mixer->MixGetCurrentTime()-t0, t1-t0);
|
|
}
|
|
|
|
delete progress;
|
|
|
|
delete mixer;
|
|
|
|
int mp2BufferNumBytes = twolame_encode_flush(
|
|
encodeOptions,
|
|
mp2Buffer,
|
|
mp2BufferSize);
|
|
|
|
if (mp2BufferNumBytes > 0)
|
|
outFile.Write(mp2Buffer, mp2BufferNumBytes);
|
|
|
|
twolame_close(&encodeOptions);
|
|
|
|
delete[] mp2Buffer;
|
|
|
|
/* Write ID3 tag if it was supposed to be at the end of the file */
|
|
|
|
if (id3len && endOfFile)
|
|
outFile.Write(id3buffer, id3len);
|
|
|
|
if (id3buffer) {
|
|
free(id3buffer);
|
|
}
|
|
|
|
/* Close file */
|
|
|
|
outFile.Close();
|
|
|
|
return updateResult;
|
|
}
|
|
|
|
bool ExportMP2::DisplayOptions(wxWindow *parent, int WXUNUSED(format))
|
|
{
|
|
ExportMP2Options od(parent);
|
|
|
|
od.ShowModal();
|
|
|
|
return true;
|
|
}
|
|
|
|
// returns buffer len; caller frees
|
|
int ExportMP2::AddTags(AudacityProject * WXUNUSED(project), char **buffer, bool *endOfFile, Tags *tags)
|
|
{
|
|
#ifdef USE_LIBID3TAG
|
|
struct id3_tag *tp = id3_tag_new();
|
|
|
|
wxString n, v;
|
|
for (bool cont = tags->GetFirst(n, v); cont; cont = tags->GetNext(n, v)) {
|
|
const char *name = "TXXX";
|
|
|
|
if (n.CmpNoCase(TAG_TITLE) == 0) {
|
|
name = ID3_FRAME_TITLE;
|
|
}
|
|
else if (n.CmpNoCase(TAG_ARTIST) == 0) {
|
|
name = ID3_FRAME_ARTIST;
|
|
}
|
|
else if (n.CmpNoCase(TAG_ALBUM) == 0) {
|
|
name = ID3_FRAME_ALBUM;
|
|
}
|
|
else if (n.CmpNoCase(TAG_YEAR) == 0) {
|
|
// LLL: Some apps do not like the newer frame ID (ID3_FRAME_YEAR),
|
|
// so we add old one as well.
|
|
AddFrame(tp, n, v, "TYER");
|
|
name = ID3_FRAME_YEAR;
|
|
}
|
|
else if (n.CmpNoCase(TAG_GENRE) == 0) {
|
|
name = ID3_FRAME_GENRE;
|
|
}
|
|
else if (n.CmpNoCase(TAG_COMMENTS) == 0) {
|
|
name = ID3_FRAME_COMMENT;
|
|
}
|
|
else if (n.CmpNoCase(TAG_TRACK) == 0) {
|
|
name = ID3_FRAME_TRACK;
|
|
}
|
|
|
|
AddFrame(tp, n, v, name);
|
|
}
|
|
|
|
tp->options &= (~ID3_TAG_OPTION_COMPRESSION); // No compression
|
|
|
|
// If this version of libid3tag supports it, use v2.3 ID3
|
|
// tags instead of the newer, but less well supported, v2.4
|
|
// that libid3tag uses by default.
|
|
#ifdef ID3_TAG_HAS_TAG_OPTION_ID3V2_3
|
|
tp->options |= ID3_TAG_OPTION_ID3V2_3;
|
|
#endif
|
|
|
|
*endOfFile = false;
|
|
|
|
id3_length_t len;
|
|
|
|
len = id3_tag_render(tp, 0);
|
|
*buffer = (char *)malloc(len);
|
|
len = id3_tag_render(tp, (id3_byte_t *)*buffer);
|
|
|
|
id3_tag_delete(tp);
|
|
|
|
return len;
|
|
#else //ifdef USE_LIBID3TAG
|
|
return 0;
|
|
#endif
|
|
}
|
|
|
|
#ifdef USE_LIBID3TAG
|
|
void ExportMP2::AddFrame(struct id3_tag *tp, const wxString & n, const wxString & v, const char *name)
|
|
{
|
|
struct id3_frame *frame = id3_frame_new(name);
|
|
|
|
if (!n.IsAscii() || !v.IsAscii()) {
|
|
id3_field_settextencoding(id3_frame_field(frame, 0), ID3_FIELD_TEXTENCODING_UTF_16);
|
|
}
|
|
else {
|
|
id3_field_settextencoding(id3_frame_field(frame, 0), ID3_FIELD_TEXTENCODING_ISO_8859_1);
|
|
}
|
|
|
|
id3_ucs4_t *ucs4 =
|
|
id3_utf8_ucs4duplicate((id3_utf8_t *) (const char *) v.mb_str(wxConvUTF8));
|
|
|
|
if (strcmp(name, ID3_FRAME_COMMENT) == 0) {
|
|
// A hack to get around iTunes not recognizing the comment. The
|
|
// language defaults to XXX and, since it's not a valid language,
|
|
// iTunes just ignores the tag. So, either set it to a valid language
|
|
// (which one???) or just clear it. Unfortunately, there's no supported
|
|
// way of clearing the field, so do it directly.
|
|
id3_field *f = id3_frame_field(frame, 1);
|
|
memset(f->immediate.value, 0, sizeof(f->immediate.value));
|
|
id3_field_setfullstring(id3_frame_field(frame, 3), ucs4);
|
|
}
|
|
else if (strcmp(name, "TXXX") == 0) {
|
|
id3_field_setstring(id3_frame_field(frame, 2), ucs4);
|
|
free(ucs4);
|
|
|
|
ucs4 = id3_utf8_ucs4duplicate((id3_utf8_t *) (const char *) n.mb_str(wxConvUTF8));
|
|
|
|
id3_field_setstring(id3_frame_field(frame, 1), ucs4);
|
|
}
|
|
else {
|
|
id3_field_setstrings(id3_frame_field(frame, 1), 1, &ucs4);
|
|
}
|
|
|
|
free(ucs4);
|
|
|
|
id3_tag_attachframe(tp, frame);
|
|
}
|
|
#endif
|
|
|
|
ExportPlugin *New_ExportMP2()
|
|
{
|
|
return new ExportMP2();
|
|
}
|
|
|
|
#endif // #ifdef USE_LIBTWOLAME
|
|
|