1 /*
2     SPDX-FileCopyrightText: 2003-2009 Sebastian Trueg <trueg@k3b.org>
3     SPDX-FileCopyrightText: 2010 Michal Malek <michalm@jabster.pl>
4     SPDX-FileCopyrightText: 1998-2009 Sebastian Trueg <trueg@k3b.org>
5 
6     SPDX-License-Identifier: GPL-2.0-or-later
7 */
8 
9 #include "k3bsoxencoder.h"
10 #include "k3bsoxencoderdefaults.h"
11 
12 #include <config-k3b.h>
13 
14 #include "k3bprocess.h"
15 #include "k3bcore.h"
16 #include "k3bexternalbinmanager.h"
17 #include "k3bplugin_i18n.h"
18 
19 #include <KConfig>
20 #include <KSharedConfig>
21 
22 #include <QDebug>
23 #include <QFile>
24 #include <QFileInfo>
25 
26 #include <sys/types.h>
27 
28 
29 K_PLUGIN_CLASS_WITH_JSON(K3bSoxEncoder , "k3bsoxencoder.json")
30 
31 
32 namespace {
33 
34 // the sox external program
35 class SoxProgram : public K3b::ExternalProgram
36 {
37 public:
SoxProgram()38     SoxProgram()
39         : K3b::ExternalProgram( "sox" ) {
40     }
41 
scan(const QString & p)42     bool scan( const QString& p ) override {
43         if( p.isEmpty() )
44             return false;
45 
46         QString path = p;
47         QFileInfo fi( path );
48         if( fi.isDir() ) {
49             path = buildProgramPath( path, "sox" );
50         }
51 
52         if( !QFile::exists( path ) )
53             return false;
54 
55         K3b::ExternalBin* bin = 0;
56 
57         // probe version
58         KProcess vp;
59         vp.setOutputChannelMode( KProcess::MergedChannels );
60 
61         vp << path << "--version";
62         vp.start();
63         if( vp.waitForFinished( -1 ) ) {
64             QByteArray out = vp.readAll();
65             int pos = out.indexOf( "sox: SoX Version" );
66             if ( pos >= 0 ) {
67                 pos += 17;
68             }
69             else if ( ( pos = out.indexOf( "sox:      SoX v" ) ) >= 0 ) {
70                 pos += 15;
71             }
72             else if ( ( pos = out.indexOf( "sox: SoX v" ) ) >= 0 ) {
73                 pos += 10;
74             }
75             else if ( ( pos = out.indexOf( "sox: Version" ) ) >= 0 ) {
76                 pos += 13;
77             }
78             int endPos = out.indexOf( '\n', pos );
79             if( pos > 0 && endPos > 0 ) {
80                 bin = new K3b::ExternalBin( *this, path );
81                 bin->setVersion( K3b::Version( out.mid( pos, endPos-pos ) ) );
82 
83                 addBin( bin );
84 
85                 return true;
86             }
87         }
88 
89         return false;
90     }
91 };
92 
93 } // namespace
94 
95 class K3bSoxEncoder::Private
96 {
97 public:
98     K3b::Process* process;
99     QString fileName;
100 };
101 
102 
K3bSoxEncoder(QObject * parent,const QVariantList &)103 K3bSoxEncoder::K3bSoxEncoder( QObject* parent, const QVariantList& )
104     : K3b::AudioEncoder( parent )
105 {
106     if( k3bcore->externalBinManager()->program( "sox" ) == 0 )
107         k3bcore->externalBinManager()->addProgram( new SoxProgram() );
108 
109     d = new Private();
110     d->process = 0;
111 }
112 
113 
~K3bSoxEncoder()114 K3bSoxEncoder::~K3bSoxEncoder()
115 {
116     delete d->process;
117     delete d;
118 }
119 
120 
finishEncoderInternal()121 void K3bSoxEncoder::finishEncoderInternal()
122 {
123     if( d->process && d->process->isRunning() ) {
124         d->process->closeWriteChannel();
125 
126         // this is kind of evil...
127         // but we need to be sure the process exited when this method returns
128         d->process->waitForFinished(-1);
129     }
130 }
131 
132 
slotSoxFinished(int exitCode,QProcess::ExitStatus exitStatus)133 void K3bSoxEncoder::slotSoxFinished( int exitCode, QProcess::ExitStatus exitStatus )
134 {
135     if( (exitStatus != QProcess::NormalExit) || (exitCode != 0) )
136         qDebug() << "(K3bSoxEncoder) sox exited with error.";
137 }
138 
139 
openFile(const QString & extension,const QString & filename,const K3b::Msf & length,const MetaData & metaData)140 bool K3bSoxEncoder::openFile( const QString& extension, const QString& filename, const K3b::Msf& length, const MetaData& metaData )
141 {
142     d->fileName = filename;
143     return initEncoderInternal( extension, length, metaData );
144 }
145 
146 
isOpen() const147 bool K3bSoxEncoder::isOpen() const
148 {
149     return d->process && d->process->isRunning();
150 }
151 
152 
closeFile()153 void K3bSoxEncoder::closeFile()
154 {
155     finishEncoderInternal();
156 }
157 
158 
initEncoderInternal(const QString & extension,const K3b::Msf &,const MetaData &)159 bool K3bSoxEncoder::initEncoderInternal( const QString& extension, const K3b::Msf& /*length*/, const MetaData& /*metaData*/ )
160 {
161     const K3b::ExternalBin* soxBin = k3bcore->externalBinManager()->binObject( "sox" );
162     if( soxBin ) {
163         // we want to be thread-safe
164         delete d->process;
165         d->process = new K3b::Process();
166         d->process->setSplitStdout(true);
167 
168         connect( d->process, SIGNAL(finished(int,QProcess::ExitStatus)),
169                  this, SLOT(slotSoxFinished(int,QProcess::ExitStatus)) );
170         connect( d->process, SIGNAL(stdoutLine(QString)),
171                  this, SLOT(slotSoxOutputLine(QString)) );
172 
173         // input settings
174         *d->process << soxBin->path()
175                     << "-t" << "raw"    // raw samples
176                     << "-r" << "44100"  // samplerate
177                     << "-s";            // signed linear
178         if ( soxBin->version() >= K3b::Version( 13, 0, 0 ) )
179             *d->process << "-2";
180         else
181             *d->process << "-w";        // 16-bit words
182         *d->process << "-c" << "2"      // stereo
183                     << "-";             // read from stdin
184 
185         // output settings
186         *d->process << "-t" << extension;
187 
188         KSharedConfig::Ptr c = KSharedConfig::openConfig();
189         KConfigGroup grp(c,"K3bSoxEncoderPlugin" );
190         if( grp.readEntry( "manual settings", DEFAULT_MANUAL_SETTINGS ) ) {
191             *d->process << "-r" << QString::number( grp.readEntry( "samplerate", DEFAULT_SAMPLE_RATE ) )
192                         << "-c" << QString::number( grp.readEntry( "channels", DEFAULT_CHANNELS ) );
193 
194             int size = grp.readEntry( "data size", DEFAULT_DATA_SIZE );
195             *d->process << ( size == 8 ? QString("-b") : ( size == 32 ? QString("-l") : QString("-w") ) );
196 
197             QString encoding = grp.readEntry( "data encoding", DEFAULT_DATA_ENCODING );
198             if( encoding == "unsigned" )
199                 *d->process << "-u";
200             else if( encoding == "u-law" )
201                 *d->process << "-U";
202             else if( encoding == "A-law" )
203                 *d->process << "-A";
204             else if( encoding == "ADPCM" )
205                 *d->process << "-a";
206             else if( encoding == "IMA_ADPCM" )
207                 *d->process << "-i";
208             else if( encoding == "GSM" )
209                 *d->process << "-g";
210             else if( encoding == "Floating-point" )
211                 *d->process << "-f";
212             else
213                 *d->process << "-s";
214         }
215 
216         *d->process << d->fileName;
217 
218         qDebug() << "***** sox parameters:";
219         QString s = d->process->joinedArgs();
220         qDebug() << s << flush;
221 
222         return d->process->start( KProcess::MergedChannels );
223     }
224     else {
225         qDebug() << "(K3bSoxEncoder) could not find sox bin.";
226         return false;
227     }
228 }
229 
230 
encodeInternal(const char * data,qint64 len)231 qint64 K3bSoxEncoder::encodeInternal( const char* data, qint64 len )
232 {
233     if( d->process && d->process->isRunning() )
234         return d->process->write( data, len );
235     else
236         return -1;
237 }
238 
239 
slotSoxOutputLine(const QString & line)240 void K3bSoxEncoder::slotSoxOutputLine( const QString& line )
241 {
242     qDebug() << "(sox) " << line;
243 }
244 
245 
extensions() const246 QStringList K3bSoxEncoder::extensions() const
247 {
248     static QStringList s_extensions;
249     if( s_extensions.isEmpty() ) {
250         s_extensions << "au"
251                      << "8svx"
252                      << "aiff"
253                      << "avr"
254                      << "cdr"
255                      << "cvs"
256                      << "dat"
257                      << "gsm"
258                      << "hcom"
259                      << "maud"
260                      << "sf"
261                      << "sph"
262                      << "smp"
263                      << "txw"
264                      << "vms"
265                      << "voc"
266                      << "wav"
267                      << "wve"
268                      << "raw";
269     }
270 
271     if( k3bcore->externalBinManager()->foundBin( "sox" ) )
272         return s_extensions;
273     else
274         return QStringList(); // no sox -> no encoding
275 }
276 
277 
fileTypeComment(const QString & ext) const278 QString K3bSoxEncoder::fileTypeComment( const QString& ext ) const
279 {
280     if( ext == "au" )
281         return i18n("Sun AU");
282     else if( ext == "8svx" )
283         return i18n("Amiga 8SVX");
284     else if( ext == "aiff" )
285         return i18n("AIFF");
286     else if( ext == "avr" )
287         return i18n("Audio Visual Research");
288     else if( ext == "cdr" )
289         return i18n("CD-R");
290     else if( ext == "cvs" )
291         return i18n("CVS");
292     else if( ext == "dat" )
293         return i18n("Text Data");
294     else if( ext == "gsm" )
295         return i18n("GSM Speech");
296     else if( ext == "hcom" )
297         return i18n("Macintosh HCOM");
298     else if( ext == "maud" )
299         return i18n("Maud (Amiga)");
300     else if( ext == "sf" )
301         return i18n("IRCAM");
302     else if( ext == "sph" )
303         return i18n("SPHERE");
304     else if( ext == "smp" )
305         return i18n("Turtle Beach SampleVision");
306     else if( ext == "txw" )
307         return i18n("Yamaha TX-16W");
308     else if( ext == "vms" )
309         return i18n("VMS");
310     else if( ext == "voc" )
311         return i18n("Sound Blaster VOC");
312     else if( ext == "wav" )
313         return i18n("Wave (SoX)");
314     else if( ext == "wve" )
315         return i18n("Psion 8-bit A-law");
316     else if( ext == "raw" )
317         return i18n("Raw");
318     else
319         return i18n("Error");
320 }
321 
322 
fileSize(const QString &,const K3b::Msf & msf) const323 long long K3bSoxEncoder::fileSize( const QString&, const K3b::Msf& msf ) const
324 {
325     // for now we make a rough assumption based on the settings
326     KSharedConfig::Ptr c = KSharedConfig::openConfig();
327     KConfigGroup grp(c, "K3bSoxEncoderPlugin" );
328     if( grp.readEntry( "manual settings", DEFAULT_MANUAL_SETTINGS ) ) {
329         int sr =  grp.readEntry( "samplerate", DEFAULT_SAMPLE_RATE );
330         int ch = grp.readEntry( "channels", DEFAULT_CHANNELS );
331         int wsize = grp.readEntry( "data size", DEFAULT_DATA_SIZE );
332 
333         return msf.totalFrames()*sr*ch*wsize/75;
334     }
335     else {
336         // fallback to raw
337         return msf.audioBytes();
338     }
339 }
340 
341 #include "k3bsoxencoder.moc"
342