1 /*
2     SPDX-FileCopyrightText: 2003-2009 Sebastian Trueg <trueg@k3b.org>
3     SPDX-FileCopyrightText: 2009-2011 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 
10 #include "k3baudioripjob.h"
11 
12 #include "k3bcdparanoialib.h"
13 #include "k3bcore.h"
14 #include "k3bdevice.h"
15 #include "k3btoc.h"
16 #include "k3btrack.h"
17 
18 #include <KCddb/Cdinfo>
19 
20 #include <KLocalizedString>
21 
22 
23 namespace K3b {
24 
25 
26 class AudioRipJob::Private
27 {
28 public:
Private()29     Private()
30         : paranoiaRetries(5),
31           neverSkip(false),
32           paranoiaLib(0),
33           device(0),
34           useIndex0(false) {
35     }
36     int paranoiaMode;
37     int paranoiaRetries;
38     int neverSkip;
39 
40     CdparanoiaLib* paranoiaLib;
41 
42     Device::Toc toc;
43     Device::Device* device;
44 
45     bool useIndex0;
46 };
47 
48 
49 namespace {
50 
51 class AudioCdReader : public QIODevice
52 {
53 public:
54     AudioCdReader( int trackIndex, AudioRipJob::Private* priv, QObject* parent = 0 );
55     bool open( OpenMode mode ) override;
56     bool isSequential() const override;
57     qint64 size() const override;
58 
59 protected:
60     qint64 writeData( const char* data, qint64 len ) override;
61     qint64 readData( char* data, qint64 maxlen ) override;
62 
63 private:
64     int m_trackIndex;
65     AudioRipJob::Private* d;
66 };
67 
68 
AudioCdReader(int trackIndex,AudioRipJob::Private * priv,QObject * parent)69 AudioCdReader::AudioCdReader( int trackIndex, AudioRipJob::Private* priv, QObject* parent )
70     : QIODevice( parent ),
71       m_trackIndex( trackIndex ),
72       d( priv )
73 {
74 }
75 
76 
open(OpenMode mode)77 bool AudioCdReader::open( OpenMode mode )
78 {
79     if( !mode.testFlag( QIODevice::WriteOnly ) ) {
80 
81         const Device::Track& tt = d->toc[m_trackIndex-1];
82 
83         long endSec = ( (d->useIndex0 && tt.index0() > 0)
84                         ? tt.firstSector().lba() + tt.index0().lba() - 1
85                         : tt.lastSector().lba() );
86 
87         if( d->paranoiaLib->initReading( tt.firstSector().lba(), endSec ) ) {
88             return QIODevice::open( mode );
89         }
90         else {
91             setErrorString( i18n("Error while initializing audio ripping.") );
92             return false;
93         }
94     }
95     else {
96         return false;
97     }
98 }
99 
100 
isSequential() const101 bool AudioCdReader::isSequential() const
102 {
103     return false;
104 }
105 
106 
size() const107 qint64 AudioCdReader::size() const
108 {
109     return d->toc[m_trackIndex-1].length().audioBytes();
110 }
111 
112 
writeData(const char *,qint64)113 qint64 AudioCdReader::writeData( const char* /*data*/, qint64 /*len*/ )
114 {
115     return -1;
116 }
117 
118 
readData(char * data,qint64)119 qint64 AudioCdReader::readData( char* data, qint64 /*maxlen*/ )
120 {
121     int status = 0;
122     char* buf = d->paranoiaLib->read( &status );
123     if( status == CdparanoiaLib::S_OK ) {
124         if( buf == 0 ) {
125             return -1;
126         }
127         else {
128             ::memcpy( data, buf, CD_FRAMESIZE_RAW );
129             return CD_FRAMESIZE_RAW;
130         }
131     }
132     else {
133         setErrorString( i18n("Unrecoverable error while ripping track %1.",m_trackIndex) );
134         return -1;
135     }
136 }
137 
138 } // namespace
139 
140 
AudioRipJob(JobHandler * hdl,QObject * parent)141 AudioRipJob::AudioRipJob( JobHandler* hdl, QObject* parent )
142     :  MassAudioEncodingJob( false, hdl, parent ),
143        d( new Private )
144 {
145 }
146 
147 
~AudioRipJob()148 AudioRipJob::~AudioRipJob()
149 {
150     delete d->paranoiaLib;
151 }
152 
153 
setParanoiaMode(int mode)154 void AudioRipJob::setParanoiaMode( int mode )
155 {
156     d->paranoiaMode = mode;
157 }
158 
159 
setMaxRetries(int r)160 void AudioRipJob::setMaxRetries( int r )
161 {
162     d->paranoiaRetries = r;
163 }
164 
165 
setNeverSkip(bool b)166 void AudioRipJob::setNeverSkip( bool b )
167 {
168     d->neverSkip = b;
169 }
170 
171 
setUseIndex0(bool b)172 void AudioRipJob::setUseIndex0( bool b )
173 {
174     d->useIndex0 = b;
175 }
176 
177 
setDevice(Device::Device * device)178 void AudioRipJob::setDevice( Device::Device* device )
179 {
180     d->device = device;
181 }
182 
183 
jobDescription() const184 QString AudioRipJob::jobDescription() const
185 {
186     if( cddbEntry().get( KCDDB::Title ).toString().isEmpty() )
187         return i18n( "Ripping Audio Tracks" );
188     else
189         return i18n( "Ripping Audio Tracks From '%1'",
190                      cddbEntry().get( KCDDB::Title ).toString() );
191 }
192 
193 
jobSource() const194 QString AudioRipJob::jobSource() const
195 {
196     if( d->device )
197         return d->device->vendor() + ' ' + d->device->description();
198     else
199         return QString();
200 }
201 
202 
start()203 void AudioRipJob::start()
204 {
205     k3bcore->blockDevice( d->device );
206     MassAudioEncodingJob::start();
207 }
208 
209 
jobFinished(bool success)210 void AudioRipJob::jobFinished( bool success )
211 {
212     k3bcore->unblockDevice( d->device );
213     MassAudioEncodingJob::jobFinished( success );
214 }
215 
216 
init()217 bool AudioRipJob::init()
218 {
219     emit newTask( i18n("Extracting Digital Audio")  );
220 
221     if( !d->paranoiaLib ) {
222         d->paranoiaLib = CdparanoiaLib::create();
223     }
224 
225     if( !d->paranoiaLib ) {
226         emit infoMessage( i18n("Could not load libcdparanoia."), Job::MessageError );
227         return false;
228     }
229 
230     // try to open the device
231     if( !d->device ) {
232         return false;
233     }
234 
235     d->device->block(true);
236 
237     emit infoMessage( i18n("Reading CD table of contents."), Job::MessageInfo );
238     d->toc = d->device->readToc();
239 
240     if( !d->paranoiaLib->initParanoia( d->device, d->toc ) ) {
241         emit infoMessage( i18n("Could not open device %1",d->device->blockDeviceName()),
242                           Job::MessageError );
243         d->device->block(false);
244 
245         return false;
246     }
247 
248     d->paranoiaLib->setParanoiaMode( d->paranoiaMode );
249     d->paranoiaLib->setNeverSkip( d->neverSkip );
250     d->paranoiaLib->setMaxRetries( d->paranoiaRetries );
251 
252 
253     if( d->useIndex0 ) {
254         emit newSubTask( i18n("Searching index 0 for all tracks") );
255         d->device->indexScan( d->toc );
256     }
257 
258     emit infoMessage( i18n("Starting digital audio extraction (ripping)."), Job::MessageInfo );
259     return true;
260 }
261 
262 
cleanup()263 void AudioRipJob::cleanup()
264 {
265     d->paranoiaLib->close();
266     d->device->block(false);
267 }
268 
269 
trackLength(int trackIndex) const270 Msf AudioRipJob::trackLength( int trackIndex ) const
271 {
272     if( d->useIndex0 )
273         return d->toc[trackIndex-1].realAudioLength();
274     else
275         return d->toc[trackIndex-1].length();
276 }
277 
278 
createReader(int trackIndex) const279 QIODevice* AudioRipJob::createReader( int trackIndex ) const
280 {
281     return new AudioCdReader( trackIndex, d.data() );
282 }
283 
284 
trackStarted(int trackIndex)285 void AudioRipJob::trackStarted(int trackIndex)
286 {
287     if (!cddbEntry().track(trackIndex - 1).get(KCDDB::Artist).toString().isEmpty() &&
288         !cddbEntry().track(trackIndex - 1).get(KCDDB::Title).toString().isEmpty()) {
289         emit newSubTask(i18n("Ripping track %1 (%2 - %3)",
290                         trackIndex,
291                         cddbEntry().track(trackIndex - 1).get(KCDDB::Artist).toString(),
292                         cddbEntry().track(trackIndex - 1).get(KCDDB::Title).toString().trimmed()));
293     } else
294         emit newSubTask(i18n("Ripping track %1", trackIndex));
295 }
296 
297 
trackFinished(int trackIndex,const QString & filename)298 void AudioRipJob::trackFinished( int trackIndex, const QString& filename )
299 {
300     emit infoMessage( i18n("Successfully ripped track %1 to %2.", trackIndex, filename), Job::MessageInfo );
301 }
302 
303 
304 } // namespace K3b
305 
306 
307