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