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"