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 757 of file ExportCL.cpp.

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

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
631 const auto &tracks = TrackList::Get( project );
633 tracks,
634 selectionOnly,
635 t0,
636 t1,
637 channels,
638 maxBlockLen,
639 true,
640 rate,
642 mixerSpec);
643
644 context.status = selectionOnly
645 ? XO("Exporting the selected audio using command-line encoder")
646 : XO("Exporting the audio using command-line encoder");
647
648 return true;
649}
@ CLOptionIDShowOutput
Definition: ExportCL.cpp:162
@ CLOptionIDCommand
Definition: ExportCL.cpp:161
XO("Cut/Copy/Paste")
#define SAMPLE_SIZE(SampleFormat)
Definition: SampleFormat.h:52
const auto tracks
const auto project
struct CLExportProcessor::@144 context
unsigned channels
Definition: ExportCL.cpp:425
static std::vector< char > GetMetaChunk(const Tags *metadata)
Definition: ExportCL.cpp:757
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 TrackList &tracks, 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
static TrackList & Get(AudacityProject &project)
Definition: Track.cpp:314
#define lrint(dbl)
Definition: float_cast.h:169

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

Here is the call graph for this function:

◆ Process()

ExportResult CLExportProcessor::Process ( ExportProcessorDelegate delegate)
overridevirtual

Implements ExportProcessor.

Definition at line 651 of file ExportCL.cpp.

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