Audacity 3.2.0
Public Member Functions | Static Private Member Functions | Private Attributes | List of all members
CLExportProcessor Class Reference
Inheritance diagram for CLExportProcessor:
[legend]
Collaboration diagram for CLExportProcessor:
[legend]

Public Member Functions

bool Initialize (AudacityProject &project, const Parameters &parameters, const wxFileNameWrapper &filename, double t0, double t1, bool selectedOnly, double rate, unsigned channels, MixerOptions::Downmix *mixerSpec, const Tags *tags) override
 Called before start processing. More...
 
ExportResult Process (ExportProcessorDelegate &delegate) override
 
- Public Member Functions inherited from ExportProcessor
 ExportProcessor (const ExportProcessor &)=delete
 
ExportProcessoroperator= (const ExportProcessor &)=delete
 
 ExportProcessor ()=default
 
virtual ~ExportProcessor ()
 
virtual bool Initialize (AudacityProject &project, const Parameters &parameters, const wxFileNameWrapper &filename, double t0, double t1, bool selectedOnly, double rate, unsigned channels, MixerOptions::Downmix *mixerSpec=nullptr, const Tags *tags=nullptr)=0
 Called before start processing. More...
 
virtual ExportResult Process (ExportProcessorDelegate &delegate)=0
 

Static Private Member Functions

static std::vector< char > GetMetaChunk (const Tags *metadata)
 

Private Attributes

struct {
   TranslatableString   status
 
   double   t0
 
   double   t1
 
   unsigned   channels
 
   wxString   cmd
 
   bool   showOutput
 
   std::unique_ptr< Mixer >   mixer
 
   wxString   output
 
   std::unique_ptr< ExportCLProcess >   process
 
context
 

Additional Inherited Members

- Public Types inherited from ExportProcessor
using Parameters = std::vector< std::tuple< ExportOptionID, ExportValue > >
 

Detailed Description

Definition at line 418 of file ExportCL.cpp.

Member Function Documentation

◆ GetMetaChunk()

std::vector< char > CLExportProcessor::GetMetaChunk ( const Tags metadata)
staticprivate

Definition at line 748 of file ExportCL.cpp.

749{
750 std::vector<char> buffer;
751
752#ifdef USE_LIBID3TAG
753 struct id3_tag_deleter {
754 void operator () (id3_tag *p) const { if (p) id3_tag_delete(p); }
755 };
756
757 std::unique_ptr<id3_tag, id3_tag_deleter> tp { id3_tag_new() };
758
759 for (const auto &pair : tags->GetRange()) {
760 const auto &n = pair.first;
761 const auto &v = pair.second;
762 const char *name = "TXXX";
763
764 if (n.CmpNoCase(TAG_TITLE) == 0) {
765 name = ID3_FRAME_TITLE;
766 }
767 else if (n.CmpNoCase(TAG_ARTIST) == 0) {
768 name = ID3_FRAME_ARTIST;
769 }
770 else if (n.CmpNoCase(TAG_ALBUM) == 0) {
771 name = ID3_FRAME_ALBUM;
772 }
773 else if (n.CmpNoCase(TAG_YEAR) == 0) {
774 name = ID3_FRAME_YEAR;
775 }
776 else if (n.CmpNoCase(TAG_GENRE) == 0) {
777 name = ID3_FRAME_GENRE;
778 }
779 else if (n.CmpNoCase(TAG_COMMENTS) == 0) {
780 name = ID3_FRAME_COMMENT;
781 }
782 else if (n.CmpNoCase(TAG_TRACK) == 0) {
783 name = ID3_FRAME_TRACK;
784 }
785 else if (n.CmpNoCase(wxT("composer")) == 0) {
786 name = "TCOM";
787 }
788
789 struct id3_frame *frame = id3_frame_new(name);
790
791 if (!n.IsAscii() || !v.IsAscii()) {
792 id3_field_settextencoding(id3_frame_field(frame, 0), ID3_FIELD_TEXTENCODING_UTF_16);
793 }
794 else {
795 id3_field_settextencoding(id3_frame_field(frame, 0), ID3_FIELD_TEXTENCODING_ISO_8859_1);
796 }
797
799 id3_utf8_ucs4duplicate((id3_utf8_t *) (const char *) v.mb_str(wxConvUTF8)) };
800
801 if (strcmp(name, ID3_FRAME_COMMENT) == 0) {
802 // A hack to get around iTunes not recognizing the comment. The
803 // language defaults to XXX and, since it's not a valid language,
804 // iTunes just ignores the tag. So, either set it to a valid language
805 // (which one???) or just clear it. Unfortunately, there's no supported
806 // way of clearing the field, so do it directly.
807 id3_field *f = id3_frame_field(frame, 1);
808 memset(f->immediate.value, 0, sizeof(f->immediate.value));
809 id3_field_setfullstring(id3_frame_field(frame, 3), ucs4.get());
810 }
811 else if (strcmp(name, "TXXX") == 0) {
812 id3_field_setstring(id3_frame_field(frame, 2), ucs4.get());
813
814 ucs4.reset(id3_utf8_ucs4duplicate((id3_utf8_t *) (const char *) n.mb_str(wxConvUTF8)));
815
816 id3_field_setstring(id3_frame_field(frame, 1), ucs4.get());
817 }
818 else {
819 auto addr = ucs4.get();
820 id3_field_setstrings(id3_frame_field(frame, 1), 1, &addr);
821 }
822
823 id3_tag_attachframe(tp.get(), frame);
824 }
825
826 tp->options &= (~ID3_TAG_OPTION_COMPRESSION); // No compression
827
828 // If this version of libid3tag supports it, use v2.3 ID3
829 // tags instead of the newer, but less well supported, v2.4
830 // that libid3tag uses by default.
831#ifdef ID3_TAG_HAS_TAG_OPTION_ID3V2_3
832 tp->options |= ID3_TAG_OPTION_ID3V2_3;
833#endif
834
835 id3_length_t len;
836
837 len = id3_tag_render(tp.get(), 0);
838 if ((len % 2) != 0) {
839 len++; // Length must be even.
840 }
841
842 if (len > 0) {
843 buffer.resize(len);
844 id3_tag_render(tp.get(), (id3_byte_t *) buffer.data());
845 }
846#endif
847
848 return buffer;
849}
wxT("CloseDown"))
std::unique_ptr< Character[], freer > MallocString
Definition: MemoryX.h:148
#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
wxString name
Definition: TagsEditor.cpp:166

References Tags::GetRange(), name, TAG_ALBUM, TAG_ARTIST, TAG_COMMENTS, TAG_GENRE, TAG_TITLE, TAG_TRACK, TAG_YEAR, and wxT().

Referenced by Initialize().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ Initialize()

bool CLExportProcessor::Initialize ( AudacityProject project,
const Parameters parameters,
const wxFileNameWrapper filename,
double  t0,
double  t1,
bool  selectedOnly,
double  rate,
unsigned  channels,
MixerOptions::Downmix mixerSpec,
const Tags tags 
)
overridevirtual

Called before start processing.

Parameters
projectProcessor may access project data, take care to exclude any data race
parametersA format-dependent set of parameters used in exporting
selectedOnlySet to true if all tracks should be mixed, to false if only the selected tracks should be mixed and exported.
tagsA Tags object that will over-ride the one in *project and be used to tag the file that is exported. @retern Implementations may simply return false without any error reporting. This is to temporarily preserve old behavior, which is to be removed in the nearest future.

Implements ExportProcessor.

Definition at line 491 of file ExportCL.cpp.

498{
499 context.t0 = t0;
500 context.t1 = t1;
501 context.channels = channels;
502
503 ExtendPath ep;
504 long rc;
505
506 const auto path = fName.GetFullPath();
507
508 context.cmd = wxString::FromUTF8(ExportPluginHelpers::GetParameterValue<std::string>(parameters, CLOptionIDCommand));
510
511 // Bug 2178 - users who don't know what they are doing will
512 // now get a file extension of .wav appended to their ffmpeg filename
513 // and therefore ffmpeg will be able to choose a file type.
514 if( context.cmd == wxT("ffmpeg -i - \"%f\"") && !fName.HasExt())
515 context.cmd.Replace( "%f", "%f.wav" );
516 context.cmd.Replace(wxT("%f"), path);
517
518 // Kick off the command
519 context.process = std::make_unique<ExportCLProcess>(&context.output);
520 auto& process = *context.process;
521
522 rc = wxExecute(context.cmd, wxEXEC_ASYNC, &process);
523 if (!rc) {
524 process.Detach();
525 process.CloseOutput();
526 throw ExportException(XO("Cannot export audio to %s")
527 .Format( path )
528 .Translation());
529 }
530
531 // Turn off logging to prevent broken pipe messages
532 wxLogNull nolog;
533
534 // establish parameters
535 int rate = lrint(sampleRate);
536 const size_t maxBlockLen = 44100 * 5;
537 unsigned long totalSamples = lrint((t1 - t0) * rate);
538 unsigned long sampleBytes = totalSamples * channels * SAMPLE_SIZE(floatSample);
539
540 wxOutputStream *os = process.GetOutputStream();
541
542 // RIFF header
543 struct {
544 char riffID[4]; // "RIFF"
545 wxUint32 riffLen; // basically the file len - 8
546 char riffType[4]; // "WAVE"
547 } riff;
548
549 // format chunk */
550 struct {
551 char fmtID[4]; // "fmt " */
552 wxUint32 formatChunkLen; // (format chunk len - first two fields) 16 in our case
553 wxUint16 formatTag; // 1 for PCM
554 wxUint16 channels;
555 wxUint32 sampleRate;
556 wxUint32 avgBytesPerSec; // sampleRate * blockAlign
557 wxUint16 blockAlign; // bitsPerSample * channels (assume bps % 8 = 0)
558 wxUint16 bitsPerSample;
559 } fmt;
560
561 // id3 chunk header
562 struct {
563 char id3ID[4]; // "id3 "
564 wxUint32 id3Len; // length of metadata in bytes
565 } id3;
566
567 // data chunk header
568 struct {
569 char dataID[4]; // "data"
570 wxUint32 dataLen; // length of all samples in bytes
571 } data;
572
573 riff.riffID[0] = 'R';
574 riff.riffID[1] = 'I';
575 riff.riffID[2] = 'F';
576 riff.riffID[3] = 'F';
577 riff.riffLen = wxUINT32_SWAP_ON_BE(sizeof(riff) +
578 sizeof(fmt) +
579 sizeof(data) +
580 sampleBytes -
581 8);
582 riff.riffType[0] = 'W';
583 riff.riffType[1] = 'A';
584 riff.riffType[2] = 'V';
585 riff.riffType[3] = 'E';
586
587 fmt.fmtID[0] = 'f';
588 fmt.fmtID[1] = 'm';
589 fmt.fmtID[2] = 't';
590 fmt.fmtID[3] = ' ';
591 fmt.formatChunkLen = wxUINT32_SWAP_ON_BE(16);
592 fmt.formatTag = wxUINT16_SWAP_ON_BE(3);
593 fmt.channels = wxUINT16_SWAP_ON_BE(channels);
594 fmt.sampleRate = wxUINT32_SWAP_ON_BE(rate);
595 fmt.bitsPerSample = wxUINT16_SWAP_ON_BE(SAMPLE_SIZE(floatSample) * 8);
596 fmt.blockAlign = wxUINT16_SWAP_ON_BE(fmt.bitsPerSample * fmt.channels / 8);
597 fmt.avgBytesPerSec = wxUINT32_SWAP_ON_BE(fmt.sampleRate * fmt.blockAlign);
598
599 // Retrieve tags if not given a set
600 if (metadata == nullptr) {
601 metadata = &Tags::Get(project);
602 }
603 const auto metachunk = GetMetaChunk(metadata);
604
605 if (!metachunk.empty()) {
606
607 id3.id3ID[0] = 'i';
608 id3.id3ID[1] = 'd';
609 id3.id3ID[2] = '3';
610 id3.id3ID[3] = ' ';
611 id3.id3Len = wxUINT32_SWAP_ON_BE(metachunk.size());
612 riff.riffLen += sizeof(id3) + metachunk.size();
613 }
614
615 data.dataID[0] = 'd';
616 data.dataID[1] = 'a';
617 data.dataID[2] = 't';
618 data.dataID[3] = 'a';
619 data.dataLen = wxUINT32_SWAP_ON_BE(sampleBytes);
620
621 // write the headers and metadata
622 os->Write(&riff, sizeof(riff));
623 os->Write(&fmt, sizeof(fmt));
624 if (!metachunk.empty()) {
625 os->Write(&id3, sizeof(id3));
626 os->Write(metachunk.data(), metachunk.size());
627 }
628 os->Write(&data, sizeof(data));
629
630 // Mix 'em up
632 project, selectionOnly, t0, t1, channels, maxBlockLen, true, rate,
633 floatSample, mixerSpec);
634
635 context.status = selectionOnly
636 ? XO("Exporting the selected audio using command-line encoder")
637 : XO("Exporting the audio using command-line encoder");
638
639 return true;
640}
@ CLOptionIDShowOutput
Definition: ExportCL.cpp:162
@ CLOptionIDCommand
Definition: ExportCL.cpp:161
XO("Cut/Copy/Paste")
#define SAMPLE_SIZE(SampleFormat)
Definition: SampleFormat.h:52
const auto project
struct CLExportProcessor::@143 context
unsigned channels
Definition: ExportCL.cpp:425
static std::vector< char > GetMetaChunk(const Tags *metadata)
Definition: ExportCL.cpp:748
std::unique_ptr< ExportCLProcess > process
Definition: ExportCL.cpp:430
static T GetParameterValue(const ExportProcessor::Parameters &parameters, int id, T defaultValue=T())
static std::unique_ptr< Mixer > CreateMixer(const AudacityProject &project, bool selectionOnly, double startTime, double stopTime, unsigned numOutChannels, size_t outBufferSize, bool outInterleaved, double outRate, sampleFormat outFormat, MixerOptions::Downmix *mixerSpec)
Abstract base class used in importing a file.
static Tags & Get(AudacityProject &project)
Definition: Tags.cpp:214
#define lrint(dbl)
Definition: float_cast.h:169

References channels, CLOptionIDCommand, CLOptionIDShowOutput, context, ExportPluginHelpers::CreateMixer(), floatSample, Tags::Get(), GetMetaChunk(), ExportPluginHelpers::GetParameterValue(), lrint, process, project, SAMPLE_SIZE, anonymous_namespace{ClipSegmentTest.cpp}::sampleRate, t0, t1, wxT(), and XO().

Here is the call graph for this function:

◆ Process()

ExportResult CLExportProcessor::Process ( ExportProcessorDelegate delegate)
overridevirtual

Implements ExportProcessor.

Definition at line 642 of file ExportCL.cpp.

643{
644 delegate.SetStatusString(context.status);
645 auto& process = *context.process;
646 auto exportResult = ExportResult::Success;
647 {
648 size_t numBytes = 0;
649 constSamplePtr mixed = nullptr;
650
651 wxOutputStream *os = process.GetOutputStream();
652 auto closeIt = finally ( [&] {
653 // Should make the process die, before propagating any exception
654 process.CloseOutput();
655 } );
656
657 // Start piping the mixed data to the command
658 while (exportResult == ExportResult::Success && process.IsActive() && os->IsOk()) {
659 // Capture any stdout and stderr from the command
660 Drain(process.GetInputStream(), &context.output);
661 Drain(process.GetErrorStream(), &context.output);
662
663 // Need to mix another block
664 if (numBytes == 0) {
665 auto numSamples = context.mixer->Process();
666 if (numSamples == 0)
667 break;
668
669 mixed = context.mixer->GetBuffer();
670 numBytes = numSamples * context.channels;
671
672 // Byte-swapping is necessary on big-endian machines, since
673 // WAV files are little-endian
674#if wxBYTE_ORDER == wxBIG_ENDIAN
675 auto buffer = (const float *) mixed;
676 for (int i = 0; i < numBytes; i++) {
677 buffer[i] = wxUINT32_SWAP_ON_BE(buffer[i]);
678 }
679#endif
680 numBytes *= SAMPLE_SIZE(floatSample);
681 }
682
683 // Don't write too much at once...pipes may not be able to handle it
684 size_t bytes = wxMin(numBytes, 4096);
685 numBytes -= bytes;
686
687 while (bytes > 0) {
688 os->Write(mixed, bytes);
689 if (!os->IsOk()) {
690 exportResult = ExportResult::Error;
691 break;
692 }
693 bytes -= os->LastWrite();
694 mixed += os->LastWrite();
695 }
696
697 if(exportResult == ExportResult::Success)
699 delegate, *context.mixer, context.t0, context.t1);
700 }
701 // Done with the progress display
702 }
703
704 // Wait for process to terminate
705 while (process.IsActive()) {
706 using namespace std::chrono;
707 std::this_thread::sleep_for(10ms);
709 }
710
711 // Display output on error or if the user wants to see it
712 if (process.GetStatus() != 0 || context.showOutput) {
713 // TODO use ShowInfoDialog() instead.
714 BasicUI::CallAfter([cmd = context.cmd, output = std::move(context.output)]
715 {
716 wxDialogWrapper dlg(nullptr,
717 wxID_ANY,
718 XO("Command Output"),
719 wxDefaultPosition,
720 wxSize(600, 400),
721 wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER);
722 dlg.SetName();
723
724 ShuttleGui S(&dlg, eIsCreating);
725 S
726 .Style( wxTE_MULTILINE | wxTE_READONLY | wxTE_RICH )
727 .AddTextWindow(cmd + wxT("\n\n") + output);
728 S.StartHorizontalLay(wxALIGN_CENTER, false);
729 {
730 S.Id(wxID_OK).AddButton(XXO("&OK"), wxALIGN_CENTER, true);
731 }
732 dlg.GetSizer()->AddSpacer(5);
733 dlg.Layout();
734 dlg.SetMinSize(dlg.GetSize());
735 dlg.Center();
736
737 dlg.ShowModal();
738 });
739
740 if (process.GetStatus() != 0)
741 exportResult = ExportResult::Error;
742 }
743
744 return exportResult;
745}
const char * constSamplePtr
Definition: SampleFormat.h:58
static ExportResult UpdateProgress(ExportProcessorDelegate &delegate, Mixer &mixer, double t0, double t1)
Sends progress update to delegate and retrieves state update from it. Typically used inside each expo...
virtual void SetStatusString(const TranslatableString &str)=0
void CallAfter(Action action)
Schedule an action to be done later, and in the main thread.
Definition: BasicUI.cpp:214
void Yield()
Dispatch waiting events, including actions enqueued by CallAfter.
Definition: BasicUI.cpp:225
void Drain(wxInputStream *s, wxString *o)
Definition: ExportCL.cpp:61

References BasicUI::CallAfter(), cmd, context, anonymous_namespace{ExportCL.cpp}::Drain(), Error, floatSample, output, process, SAMPLE_SIZE, ExportProcessorDelegate::SetStatusString(), Success, ExportPluginHelpers::UpdateProgress(), and BasicUI::Yield().

Here is the call graph for this function:

Member Data Documentation

◆ channels

unsigned CLExportProcessor::channels

Definition at line 425 of file ExportCL.cpp.

Referenced by Initialize().

◆ cmd

wxString CLExportProcessor::cmd

Definition at line 426 of file ExportCL.cpp.

Referenced by Process().

◆ 

struct { ... } CLExportProcessor::context

Referenced by Initialize(), and Process().

◆ mixer

std::unique_ptr<Mixer> CLExportProcessor::mixer

Definition at line 428 of file ExportCL.cpp.

◆ output

wxString CLExportProcessor::output

Definition at line 429 of file ExportCL.cpp.

Referenced by Process().

◆ process

std::unique_ptr<ExportCLProcess> CLExportProcessor::process

Definition at line 430 of file ExportCL.cpp.

Referenced by Initialize(), and Process().

◆ showOutput

bool CLExportProcessor::showOutput

Definition at line 427 of file ExportCL.cpp.

◆ status

TranslatableString CLExportProcessor::status

Definition at line 422 of file ExportCL.cpp.

◆ t0

double CLExportProcessor::t0

Definition at line 423 of file ExportCL.cpp.

Referenced by Initialize().

◆ t1

double CLExportProcessor::t1

Definition at line 424 of file ExportCL.cpp.

Referenced by Initialize().


The documentation for this class was generated from the following file: