1
0
mirror of https://github.com/cookiengineer/audacity synced 2025-05-01 16:19:43 +02:00

Bug 1302 - Metadata Editor should be disabled for (external program) in Export Multiple, as in straight export

This commit is contained in:
Leland Lucius 2020-03-26 01:21:49 -05:00
parent 1e12997d30
commit 3f57884766

View File

@ -33,6 +33,7 @@
#include "../Mix.h"
#include "../Prefs.h"
#include "../ShuttleGui.h"
#include "../Tags.h"
#include "../Track.h"
#include "../float_cast.h"
#include "../widgets/FileHistory.h"
@ -40,6 +41,12 @@
#include "../widgets/ProgressDialog.h"
#include "../wxFileNameWrapper.h"
#ifdef USE_LIBID3TAG
#include <id3tag.h>
extern "C" {
struct id3_frame *id3_frame_new(char const *);
}
#endif
//----------------------------------------------------------------------------
// ExportCLOptions
@ -268,29 +275,6 @@ private:
// ExportCL
//----------------------------------------------------------------------------
/* this structure combines the RIFF header, the format chunk, and the data
* chunk header */
struct wav_header {
/* RIFF header */
char riffID[4]; /* "RIFF" */
wxUint32 lenAfterRiff; /* basically the file len - 8, or samples len + 36 */
char riffType[4]; /* "WAVE" */
/* format chunk */
char fmtID[4]; /* "fmt " */
wxUint32 formatChunkLen; /* (format chunk len - first two fields) 16 in our case */
wxUint16 formatTag; /* 1 for PCM */
wxUint16 channels;
wxUint32 sampleRate;
wxUint32 avgBytesPerSec; /* sampleRate * blockAlign */
wxUint16 blockAlign; /* bitsPerSample * channels (assume bps % 8 = 0) */
wxUint16 bitsPerSample;
/* data chunk header */
char dataID[4]; /* "data" */
wxUint32 dataLen; /* length of all samples in bytes */
};
class ExportCL final : public ExportPlugin
{
public:
@ -301,15 +285,18 @@ public:
void OptionsCreate(ShuttleGui &S, int format) override;
ProgressResult Export(AudacityProject *project,
std::unique_ptr<ProgressDialog> &pDialog,
unsigned channels,
const wxFileNameWrapper &fName,
bool selectedOnly,
double t0,
double t1,
MixerSpec *mixerSpec = NULL,
const Tags *metadata = NULL,
int subformat = 0) override;
std::unique_ptr<ProgressDialog> &pDialog,
unsigned channels,
const wxFileNameWrapper &fName,
bool selectedOnly,
double t0,
double t1,
MixerSpec *mixerSpec = NULL,
const Tags *metadata = NULL,
int subformat = 0) override;
private:
std::vector<char> GetMetaChunk(const Tags *metadata);
};
ExportCL::ExportCL()
@ -319,20 +306,20 @@ ExportCL::ExportCL()
SetFormat(wxT("CL"),0);
AddExtension(wxT(""),0);
SetMaxChannels(255,0);
SetCanMetaData(false,0);
SetCanMetaData(true,0);
SetDescription(XO("(external program)"),0);
}
ProgressResult ExportCL::Export(AudacityProject *project,
std::unique_ptr<ProgressDialog> &pDialog,
unsigned channels,
const wxFileNameWrapper &fName,
bool selectionOnly,
double t0,
double t1,
MixerSpec *mixerSpec,
const Tags *WXUNUSED(metadata),
int WXUNUSED(subformat))
std::unique_ptr<ProgressDialog> &pDialog,
unsigned channels,
const wxFileNameWrapper &fName,
bool selectionOnly,
double t0,
double t1,
MixerSpec *mixerSpec,
const Tags *metadata,
int WXUNUSED(subformat))
{
wxString output;
wxString cmd;
@ -409,38 +396,95 @@ ProgressResult ExportCL::Export(AudacityProject *project,
unsigned long totalSamples = lrint((t1 - t0) * rate);
unsigned long sampleBytes = totalSamples * channels * SAMPLE_SIZE(floatSample);
// fill up the wav header
wav_header header;
header.riffID[0] = 'R';
header.riffID[1] = 'I';
header.riffID[2] = 'F';
header.riffID[3] = 'F';
header.lenAfterRiff = wxUINT32_SWAP_ON_BE(sampleBytes + 36);
header.riffType[0] = 'W';
header.riffType[1] = 'A';
header.riffType[2] = 'V';
header.riffType[3] = 'E';
header.fmtID[0] = 'f';
header.fmtID[1] = 'm';
header.fmtID[2] = 't';
header.fmtID[3] = ' ';
header.formatChunkLen = wxUINT32_SWAP_ON_BE(16);
header.formatTag = wxUINT16_SWAP_ON_BE(3);
header.channels = wxUINT16_SWAP_ON_BE(channels);
header.sampleRate = wxUINT32_SWAP_ON_BE(rate);
header.bitsPerSample = wxUINT16_SWAP_ON_BE(SAMPLE_SIZE(floatSample) * 8);
header.blockAlign = wxUINT16_SWAP_ON_BE(header.bitsPerSample * header.channels / 8);
header.avgBytesPerSec = wxUINT32_SWAP_ON_BE(header.sampleRate * header.blockAlign);
header.dataID[0] = 'd';
header.dataID[1] = 'a';
header.dataID[2] = 't';
header.dataID[3] = 'a';
header.dataLen = wxUINT32_SWAP_ON_BE(sampleBytes);
// write the header
wxOutputStream *os = process.GetOutputStream();
os->Write(&header, sizeof(wav_header));
// RIFF header
struct {
char riffID[4]; // "RIFF"
wxUint32 riffLen; // basically the file len - 8
char riffType[4]; // "WAVE"
} riff;
// format chunk */
struct {
char fmtID[4]; // "fmt " */
wxUint32 formatChunkLen; // (format chunk len - first two fields) 16 in our case
wxUint16 formatTag; // 1 for PCM
wxUint16 channels;
wxUint32 sampleRate;
wxUint32 avgBytesPerSec; // sampleRate * blockAlign
wxUint16 blockAlign; // bitsPerSample * channels (assume bps % 8 = 0)
wxUint16 bitsPerSample;
} fmt;
// id3 chunk header
struct {
char id3ID[4]; // "id3 "
wxUint32 id3Len; // length of metadata in bytes
} id3;
// data chunk header
struct {
char dataID[4]; // "data"
wxUint32 dataLen; // length of all samples in bytes
} data;
riff.riffID[0] = 'R';
riff.riffID[1] = 'I';
riff.riffID[2] = 'F';
riff.riffID[3] = 'F';
riff.riffLen = wxUINT32_SWAP_ON_BE(sizeof(riff) +
sizeof(fmt) +
sizeof(data) +
sampleBytes -
8);
riff.riffType[0] = 'W';
riff.riffType[1] = 'A';
riff.riffType[2] = 'V';
riff.riffType[3] = 'E';
fmt.fmtID[0] = 'f';
fmt.fmtID[1] = 'm';
fmt.fmtID[2] = 't';
fmt.fmtID[3] = ' ';
fmt.formatChunkLen = wxUINT32_SWAP_ON_BE(16);
fmt.formatTag = wxUINT16_SWAP_ON_BE(3);
fmt.channels = wxUINT16_SWAP_ON_BE(channels);
fmt.sampleRate = wxUINT32_SWAP_ON_BE(rate);
fmt.bitsPerSample = wxUINT16_SWAP_ON_BE(SAMPLE_SIZE(floatSample) * 8);
fmt.blockAlign = wxUINT16_SWAP_ON_BE(fmt.bitsPerSample * fmt.channels / 8);
fmt.avgBytesPerSec = wxUINT32_SWAP_ON_BE(fmt.sampleRate * fmt.blockAlign);
// Retrieve tags if not given a set
if (metadata == NULL) {
metadata = &Tags::Get(*project);
}
auto metachunk = GetMetaChunk(metadata);
if (metachunk.size()) {
id3.id3ID[0] = 'i';
id3.id3ID[1] = 'd';
id3.id3ID[2] = '3';
id3.id3ID[3] = ' ';
id3.id3Len = wxUINT32_SWAP_ON_BE(metachunk.size());
riff.riffLen += sizeof(id3) + metachunk.size();
}
data.dataID[0] = 'd';
data.dataID[1] = 'a';
data.dataID[2] = 't';
data.dataID[3] = 'a';
data.dataLen = wxUINT32_SWAP_ON_BE(sampleBytes);
// write the headers and metadata
os->Write(&riff, sizeof(riff));
os->Write(&fmt, sizeof(fmt));
if (metachunk.size()) {
os->Write(&id3, sizeof(id3));
os->Write(metachunk.data(), metachunk.size());
}
os->Write(&data, sizeof(data));
// Mix 'em up
const auto &tracks = TrackList::Get( *project );
@ -560,6 +604,109 @@ ProgressResult ExportCL::Export(AudacityProject *project,
return updateResult;
}
std::vector<char> ExportCL::GetMetaChunk(const Tags *tags)
{
std::vector<char> buffer;
#ifdef USE_LIBID3TAG
struct id3_tag_deleter {
void operator () (id3_tag *p) const { if (p) id3_tag_delete(p); }
};
std::unique_ptr<id3_tag, id3_tag_deleter> tp { id3_tag_new() };
for (const auto &pair : tags->GetRange()) {
const auto &n = pair.first;
const auto &v = pair.second;
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) {
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;
}
else if (n.CmpNoCase(wxT("composer")) == 0) {
name = "TCOM";
}
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);
}
MallocString<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.get());
}
else if (strcmp(name, "TXXX") == 0) {
id3_field_setstring(id3_frame_field(frame, 2), ucs4.get());
ucs4.reset(id3_utf8_ucs4duplicate((id3_utf8_t *) (const char *) n.mb_str(wxConvUTF8)));
id3_field_setstring(id3_frame_field(frame, 1), ucs4.get());
}
else {
auto addr = ucs4.get();
id3_field_setstrings(id3_frame_field(frame, 1), 1, &addr);
}
id3_tag_attachframe(tp.get(), frame);
}
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
id3_length_t len;
len = id3_tag_render(tp.get(), 0);
if ((len % 2) != 0) {
len++; // Length must be even.
}
if (len > 0) {
buffer.resize(len);
id3_tag_render(tp.get(), (id3_byte_t *) buffer.data());
}
#endif
return buffer;
}
void ExportCL::OptionsCreate(ShuttleGui &S, int format)
{
S.AddWindow( safenew ExportCLOptions{ S.GetParent(), format } );