File indexing completed on 2024-04-28 04:50:21
0001 /* 0002 SPDX-FileCopyrightText: 2003-2009 Sebastian Trueg <trueg@k3b.org> 0003 SPDX-FileCopyrightText: 2010 Michal Malek <michalm@jabster.pl> 0004 SPDX-FileCopyrightText: 1998-2009 Sebastian Trueg <trueg@k3b.org> 0005 0006 SPDX-License-Identifier: GPL-2.0-or-later 0007 */ 0008 0009 0010 #include "k3baudiorippingdialog.h" 0011 #include "k3baudioripjob.h" 0012 #include "k3bpatternparser.h" 0013 #include "k3bcddbpatternwidget.h" 0014 #include "k3baudioconvertingoptionwidget.h" 0015 0016 #include "k3bjobprogressdialog.h" 0017 #include "k3bcore.h" 0018 #include "k3bglobals.h" 0019 #include "k3btrack.h" 0020 #include "k3bstdguiitems.h" 0021 #include "k3bfilesysteminfo.h" 0022 #include "k3bpluginmanager.h" 0023 #include "k3baudioencoder.h" 0024 #include "k3bmediacache.h" 0025 0026 #include <KComboBox> 0027 #include <KConfig> 0028 #include <KLocalizedString> 0029 #include <KIO/Global> 0030 #include <KUrlRequester> 0031 #include <KMessageBox> 0032 #include <KUrlLabel> 0033 0034 #include <QDebug> 0035 #include <QDir> 0036 #include <QHash> 0037 #include <QList> 0038 #include <QPair> 0039 #include <QStringList> 0040 #include <QVariant> 0041 #include <QFont> 0042 #include <QValidator> 0043 #include <QGridLayout> 0044 #include <QGroupBox> 0045 #include <QCheckBox> 0046 #include <QHeaderView> 0047 #include <QLabel> 0048 #include <QLayout> 0049 #include <QMessageBox> 0050 #include <QPushButton> 0051 #include <QTabWidget> 0052 #include <QToolButton> 0053 #include <QToolTip> 0054 #include <QTreeWidget> 0055 #include <QSpinBox> 0056 0057 0058 0059 class K3b::AudioRippingDialog::Private 0060 { 0061 public: 0062 Private(); 0063 void addTrack( const QString& name, const QString& length, const QString& size, const QString& type ); 0064 0065 QVector<QString> filenames; 0066 QString playlistFilename; 0067 K3b::FileSystemInfo fsInfo; 0068 0069 QTreeWidget* viewTracks; 0070 }; 0071 0072 0073 K3b::AudioRippingDialog::Private::Private() 0074 : viewTracks( 0 ) 0075 { 0076 } 0077 0078 0079 void K3b::AudioRippingDialog::Private::addTrack( const QString& name, const QString& length, const QString& size, const QString& type ) 0080 { 0081 QTreeWidgetItem* item = new QTreeWidgetItem( viewTracks ); 0082 item->setText( 0, name ); 0083 item->setText( 1, length ); 0084 item->setText( 2, size ); 0085 item->setText( 3, type ); 0086 } 0087 0088 0089 K3b::AudioRippingDialog::AudioRippingDialog( const K3b::Medium& medium, 0090 const KCDDB::CDInfo& entry, 0091 const QList<int>& tracks, 0092 QWidget *parent ) 0093 : K3b::InteractionDialog( parent, 0094 QString(), 0095 QString(), 0096 START_BUTTON|CANCEL_BUTTON, 0097 START_BUTTON, 0098 "Audio Ripping" ), // config group 0099 m_medium( medium ), 0100 m_cddbEntry( entry ), 0101 m_trackNumbers( tracks ) 0102 { 0103 d = new Private(); 0104 0105 setupGui(); 0106 setupContextHelp(); 0107 0108 K3b::Msf length; 0109 K3b::Device::Toc toc = medium.toc(); 0110 for( QList<int>::const_iterator it = m_trackNumbers.constBegin(); 0111 it != m_trackNumbers.constEnd(); ++it ) { 0112 length += toc[*it].length(); 0113 } 0114 setTitle( i18n("CD Ripping"), 0115 i18np("1 track (%2)", "%1 tracks (%2)", 0116 m_trackNumbers.count(),length.toString()) ); 0117 } 0118 0119 0120 K3b::AudioRippingDialog::~AudioRippingDialog() 0121 { 0122 delete d; 0123 } 0124 0125 0126 void K3b::AudioRippingDialog::setupGui() 0127 { 0128 QWidget *frame = mainWidget(); 0129 QGridLayout* Form1Layout = new QGridLayout( frame ); 0130 Form1Layout->setContentsMargins( 0, 0, 0, 0 ); 0131 0132 QTreeWidgetItem* header = new QTreeWidgetItem; 0133 header->setText( 0, i18n( "Filename") ); 0134 header->setText( 1, i18n( "Length") ); 0135 header->setText( 2, i18n( "File Size") ); 0136 header->setText( 3, i18n( "Type") ); 0137 0138 d->viewTracks = new QTreeWidget( frame ); 0139 d->viewTracks->setSortingEnabled( false ); 0140 d->viewTracks->setAllColumnsShowFocus( true ); 0141 d->viewTracks->setHeaderItem( header ); 0142 d->viewTracks->setRootIsDecorated( false ); 0143 d->viewTracks->setSelectionMode( QAbstractItemView::NoSelection ); 0144 d->viewTracks->setFocusPolicy( Qt::NoFocus ); 0145 d->viewTracks->header()->setStretchLastSection( false ); 0146 d->viewTracks->header()->setSectionResizeMode( 0, QHeaderView::Stretch ); 0147 d->viewTracks->header()->setSectionResizeMode( 1, QHeaderView::ResizeToContents ); 0148 d->viewTracks->header()->setSectionResizeMode( 2, QHeaderView::ResizeToContents ); 0149 d->viewTracks->header()->setSectionResizeMode( 3, QHeaderView::ResizeToContents ); 0150 0151 QTabWidget* mainTab = new QTabWidget( frame ); 0152 0153 m_optionWidget = new K3b::AudioConvertingOptionWidget( mainTab ); 0154 mainTab->addTab( m_optionWidget, i18n("Settings") ); 0155 0156 0157 // setup filename pattern page 0158 // ------------------------------------------------------------------------------------------- 0159 m_patternWidget = new K3b::CddbPatternWidget( mainTab ); 0160 mainTab->addTab( m_patternWidget, i18n("File Naming") ); 0161 connect( m_patternWidget, SIGNAL(changed()), this, SLOT(refresh()) ); 0162 0163 0164 // setup advanced page 0165 // ------------------------------------------------------------------------------------------- 0166 QWidget* advancedPage = new QWidget( mainTab ); 0167 QGridLayout* advancedPageLayout = new QGridLayout( advancedPage ); 0168 mainTab->addTab( advancedPage, i18n("Advanced") ); 0169 0170 m_comboParanoiaMode = K3b::StdGuiItems::paranoiaModeComboBox( advancedPage ); 0171 m_spinRetries = new QSpinBox( advancedPage ); 0172 m_checkIgnoreReadErrors = new QCheckBox( i18n("Ignore read errors"), advancedPage ); 0173 m_checkUseIndex0 = new QCheckBox( i18n("Do not read pregaps"), advancedPage ); 0174 0175 advancedPageLayout->addWidget( new QLabel( i18n("Paranoia mode:"), advancedPage ), 0, 0 ); 0176 advancedPageLayout->addWidget( m_comboParanoiaMode, 0, 1 ); 0177 advancedPageLayout->addWidget( new QLabel( i18n("Read retries:"), advancedPage ), 1, 0 ); 0178 advancedPageLayout->addWidget( m_spinRetries, 1, 1 ); 0179 advancedPageLayout->addWidget( m_checkIgnoreReadErrors, 2, 0, 0, 1 ); 0180 advancedPageLayout->addWidget( m_checkUseIndex0, 3, 0, 0, 1 ); 0181 advancedPageLayout->setRowStretch( 4, 1 ); 0182 advancedPageLayout->setColumnStretch( 2, 1 ); 0183 0184 // ------------------------------------------------------------------------------------------- 0185 0186 0187 Form1Layout->addWidget( d->viewTracks, 0, 0 ); 0188 Form1Layout->addWidget( mainTab, 1, 0 ); 0189 Form1Layout->setRowStretch( 0, 1 ); 0190 0191 setStartButtonText( i18n( "Start Ripping" ), i18n( "Starts copying the selected tracks") ); 0192 0193 connect( m_checkUseIndex0, SIGNAL(toggled(bool)), this, SLOT(refresh()) ); 0194 connect( m_optionWidget, SIGNAL(changed()), this, SLOT(refresh()) ); 0195 } 0196 0197 0198 void K3b::AudioRippingDialog::setupContextHelp() 0199 { 0200 m_spinRetries->setToolTip( i18n("Maximal number of read retries") ); 0201 m_spinRetries->setWhatsThis( i18n("<p>This specifies the maximum number of retries to " 0202 "read a sector of audio data from the cd. After that " 0203 "K3b will either skip the sector if the <em>Ignore Read Errors</em> " 0204 "option is enabled or stop the process.") ); 0205 m_checkUseIndex0->setToolTip( i18n("Do not read the pregaps at the end of every track") ); 0206 m_checkUseIndex0->setWhatsThis( i18n("<p>If this option is checked K3b will not rip the audio " 0207 "data in the pregaps. Most audio tracks contain an empty " 0208 "pregap which does not belong to the track itself.</p>" 0209 "<p>Although the default behavior of nearly all ripping " 0210 "software is to include the pregaps for most CDs, it makes more " 0211 "sense to ignore them. In any case, when creating a K3b audio " 0212 "project, the pregaps will be regenerated.</p>") ); 0213 } 0214 0215 0216 void K3b::AudioRippingDialog::init() 0217 { 0218 refresh(); 0219 } 0220 0221 0222 void K3b::AudioRippingDialog::slotStartClicked() 0223 { 0224 // check if all filenames differ 0225 if( d->filenames.count() > 1 ) { 0226 bool differ = true; 0227 // the most stupid version to compare but most cds have about 12 tracks 0228 // that's a size where algorithms do not need any optimization! ;) 0229 for( int i = 0; i < d->filenames.count(); ++i ) { 0230 for( int j = i+1; j < d->filenames.count(); ++j ) 0231 if( d->filenames[i] == d->filenames[j] ) { 0232 differ = false; 0233 break; 0234 } 0235 } 0236 0237 if( !differ ) { 0238 KMessageBox::error( this, i18n("Please check the naming pattern. All filenames need to be unique.") ); 0239 return; 0240 } 0241 } 0242 0243 // check if we need to overwrite some files... 0244 QStringList filesToOverwrite; 0245 for( int i = 0; i < d->filenames.count(); ++i ) { 0246 if( QFile::exists( d->filenames[i] ) ) 0247 filesToOverwrite.append( d->filenames[i] ); 0248 } 0249 0250 if( m_optionWidget->createPlaylist() && QFile::exists( d->playlistFilename ) ) 0251 filesToOverwrite.append( d->playlistFilename ); 0252 0253 if( !filesToOverwrite.isEmpty() ) 0254 if( KMessageBox::questionTwoActionsList( this, 0255 i18n("Do you want to overwrite these files?"), 0256 filesToOverwrite, 0257 i18n("Files Exist"), KStandardGuiItem::overwrite(), KStandardGuiItem::cancel() ) == KMessageBox::SecondaryAction ) 0258 return; 0259 0260 0261 // prepare list of tracks to rip 0262 AudioRipJob::Tracks tracksToRip; 0263 if( m_optionWidget->createSingleFile() && !d->filenames.isEmpty() ) { 0264 // Since QMultiMap stores multiple values "from most recently to least recently inserted" 0265 // we will add it in reverse order to rip in ascending order 0266 for( int i = m_trackNumbers.count()-1; i >= 0; --i ) { 0267 tracksToRip.insert( d->filenames.first(), m_trackNumbers[i]+1 ); 0268 } 0269 } 0270 else { 0271 for( int i = 0; i < m_trackNumbers.count() && i < d->filenames.count(); ++i ) { 0272 tracksToRip.insert( d->filenames[ i ], m_trackNumbers[i]+1 ); 0273 } 0274 } 0275 0276 K3b::JobProgressDialog ripDialog( parentWidget(), "Ripping" ); 0277 0278 K3b::AudioEncoder* encoder = m_optionWidget->encoder(); 0279 K3b::AudioRipJob* job = new K3b::AudioRipJob( &ripDialog, this ); 0280 job->setDevice( m_medium.device() ); 0281 job->setCddbEntry( m_cddbEntry ); 0282 job->setTrackList( tracksToRip ); 0283 job->setParanoiaMode( m_comboParanoiaMode->currentText().toInt() ); 0284 job->setMaxRetries( m_spinRetries->value() ); 0285 job->setNeverSkip( !m_checkIgnoreReadErrors->isChecked() ); 0286 job->setEncoder( encoder ); 0287 job->setUseIndex0( m_checkUseIndex0->isChecked() ); 0288 job->setWriteCueFile( m_optionWidget->createSingleFile() && m_optionWidget->createCueFile() ); 0289 if( m_optionWidget->createPlaylist() ) 0290 job->setWritePlaylist( d->playlistFilename, m_optionWidget->playlistRelativePath() ); 0291 if( encoder ) 0292 job->setFileType( m_optionWidget->extension() ); 0293 0294 hide(); 0295 ripDialog.startJob(job); 0296 0297 qDebug() << "(K3b::AudioRippingDialog) deleting ripjob."; 0298 delete job; 0299 0300 close(); 0301 } 0302 0303 0304 void K3b::AudioRippingDialog::refresh() 0305 { 0306 d->viewTracks->clear(); 0307 d->filenames.clear(); 0308 0309 QString baseDir = K3b::prepareDir( m_optionWidget->baseDir() ); 0310 d->fsInfo.setPath( baseDir ); 0311 0312 KIO::filesize_t overallSize = 0; 0313 0314 K3b::Device::Toc toc = m_medium.toc(); 0315 0316 if( m_optionWidget->createSingleFile() ) { 0317 long length = 0; 0318 for( QList<int>::const_iterator it = m_trackNumbers.constBegin(); 0319 it != m_trackNumbers.constEnd(); ++it ) { 0320 length += ( m_checkUseIndex0->isChecked() 0321 ? toc[*it].realAudioLength().lba() 0322 : toc[*it].length().lba() ); 0323 } 0324 0325 QString filename; 0326 QString extension; 0327 long long fileSize = 0; 0328 if( m_optionWidget->encoder() == 0 ) { 0329 extension = "wav"; 0330 fileSize = length * 2352 + 44; 0331 } 0332 else { 0333 extension = m_optionWidget->extension(); 0334 fileSize = m_optionWidget->encoder()->fileSize( extension, length ); 0335 } 0336 0337 if( fileSize > 0 ) 0338 overallSize = fileSize; 0339 0340 filename = d->fsInfo.fixupPath( K3b::PatternParser::parsePattern( m_cddbEntry, 1, 0341 extension, 0342 m_patternWidget->playlistPattern(), 0343 m_patternWidget->replaceBlanks(), 0344 m_patternWidget->blankReplaceString() ) ); 0345 0346 d->addTrack( filename, 0347 K3b::Msf(length).toString(), 0348 fileSize < 0 ? i18n("unknown") : KIO::convertSize( fileSize ), 0349 i18n("Audio") ); 0350 0351 d->filenames.append( baseDir + filename ); 0352 0353 if( m_optionWidget->createCueFile() ) { 0354 QString cueFileName = d->fsInfo.fixupPath( K3b::PatternParser::parsePattern( m_cddbEntry, 1, 0355 QLatin1String( "cue" ), 0356 m_patternWidget->playlistPattern(), 0357 m_patternWidget->replaceBlanks(), 0358 m_patternWidget->blankReplaceString() ) ); 0359 d->addTrack( cueFileName, "-", "-", i18n("Cue-file") ); 0360 } 0361 } 0362 else { 0363 for( int i = 0; i < m_trackNumbers.count(); ++i ) { 0364 int trackIndex = m_trackNumbers[i]; 0365 0366 QString extension; 0367 long long fileSize = 0; 0368 K3b::Msf trackLength = ( m_checkUseIndex0->isChecked() 0369 ? toc[trackIndex].realAudioLength() 0370 : toc[trackIndex].length() ); 0371 if( m_optionWidget->encoder() == 0 ) { 0372 extension = "wav"; 0373 fileSize = trackLength.audioBytes() + 44; 0374 } 0375 else { 0376 extension = m_optionWidget->extension(); 0377 fileSize = m_optionWidget->encoder()->fileSize( extension, trackLength ); 0378 } 0379 0380 if( fileSize > 0 ) 0381 overallSize += fileSize; 0382 0383 if( toc[trackIndex].type() == K3b::Device::Track::TYPE_DATA ) { 0384 extension = ".iso"; 0385 continue; // TODO: find out how to rip the iso data 0386 } 0387 0388 0389 QString filename; 0390 0391 filename = K3b::PatternParser::parsePattern( m_cddbEntry, trackIndex+1, 0392 extension, 0393 m_patternWidget->filenamePattern(), 0394 m_patternWidget->replaceBlanks(), 0395 m_patternWidget->blankReplaceString() ); 0396 if ( filename.isEmpty() ){ 0397 filename = i18n("Track%1", QString::number( trackIndex+1 ).rightJustified( 2, '0' ) ) + '.' + extension; 0398 } 0399 filename = d->fsInfo.fixupPath( filename ); 0400 0401 d->addTrack( filename, 0402 trackLength.toString(), 0403 fileSize < 0 ? i18n("unknown") : KIO::convertSize( fileSize ), 0404 toc[trackIndex].type() == K3b::Device::Track::TYPE_AUDIO ? i18n("Audio") : i18n("Data") ); 0405 0406 d->filenames.append( baseDir + filename ); 0407 } 0408 } 0409 0410 // create playlist item 0411 if( m_optionWidget->createPlaylist() ) { 0412 QString filename = K3b::PatternParser::parsePattern( m_cddbEntry, 1, 0413 QLatin1String( "m3u" ), 0414 m_patternWidget->playlistPattern(), 0415 m_patternWidget->replaceBlanks(), 0416 m_patternWidget->blankReplaceString() ); 0417 0418 d->addTrack( filename, "-", "-", i18n("Playlist") ); 0419 0420 d->playlistFilename = d->fsInfo.fixupPath( baseDir + '/' + filename ); 0421 } 0422 0423 if( overallSize > 0 ) 0424 m_optionWidget->setNeededSize( overallSize ); 0425 else 0426 m_optionWidget->setNeededSize( 0 ); 0427 } 0428 0429 0430 void K3b::AudioRippingDialog::setStaticDir( const QString& path ) 0431 { 0432 m_optionWidget->setBaseDir( path ); 0433 } 0434 0435 0436 void K3b::AudioRippingDialog::loadSettings( const KConfigGroup& c ) 0437 { 0438 m_comboParanoiaMode->setCurrentIndex( c.readEntry( "paranoia_mode", 0 ) ); 0439 m_spinRetries->setValue( c.readEntry( "read_retries", 5 ) ); 0440 m_checkIgnoreReadErrors->setChecked( !c.readEntry( "never_skip", true ) ); 0441 m_checkUseIndex0->setChecked( c.readEntry( "use_index0", false ) ); 0442 0443 m_optionWidget->loadConfig( c ); 0444 m_patternWidget->loadConfig( c ); 0445 0446 refresh(); 0447 } 0448 0449 0450 void K3b::AudioRippingDialog::saveSettings( KConfigGroup c ) 0451 { 0452 c.writeEntry( "paranoia_mode", m_comboParanoiaMode->currentText().toInt() ); 0453 c.writeEntry( "read_retries", m_spinRetries->value() ); 0454 c.writeEntry( "never_skip", !m_checkIgnoreReadErrors->isChecked() ); 0455 c.writeEntry( "use_index0", m_checkUseIndex0->isChecked() ); 0456 0457 m_optionWidget->saveConfig( c ); 0458 m_patternWidget->saveConfig( c ); 0459 } 0460 0461 #include "moc_k3baudiorippingdialog.cpp"