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