File indexing completed on 2024-04-14 04:43:11

0001 /* AUDEX CDDA EXTRACTOR
0002  * SPDX-FileCopyrightText: Copyright (C) 2007 Marco Nelles
0003  * <https://userbase.kde.org/Audex>
0004  *
0005  * SPDX-License-Identifier: GPL-3.0-or-later
0006  */
0007 
0008 #include "audex.h"
0009 
0010 /* The heart of audex */
0011 
0012 Audex::Audex(QWidget *parent, ProfileModel *profile_model, CDDAModel *cdda_model)
0013     : QObject(parent)
0014 {
0015     Q_UNUSED(parent);
0016 
0017     this->profile_model = profile_model;
0018     this->cdda_model = cdda_model;
0019 
0020     p_profile_name = profile_model->data(profile_model->index(profile_model->currentProfileRow(), PROFILE_MODEL_COLUMN_NAME_INDEX)).toString();
0021     p_suffix = profile_model->getSelectedEncoderSuffixFromCurrentIndex();
0022     p_single_file = profile_model->data(profile_model->index(profile_model->currentProfileRow(), PROFILE_MODEL_COLUMN_SF_INDEX)).toBool();
0023 
0024     encoder_wrapper = new EncoderWrapper(this,
0025                                          profile_model->getSelectedEncoderSchemeFromCurrentIndex(),
0026                                          profile_model->getSelectedEncoderNameAndVersion(),
0027                                          Preferences::deletePartialFiles());
0028 
0029     if (!encoder_wrapper) {
0030         qDebug() << "PANIC ERROR. Could not load object EncoderWrapper. Low mem?";
0031         return;
0032     }
0033 
0034     cdda_extract_thread = new CDDAExtractThread(this, cdda_model->cdio());
0035     if (!cdda_extract_thread) {
0036         qDebug() << "PANIC ERROR. Could not load object CDDAExtractThread. Low mem?";
0037         return;
0038     }
0039     cdda_extract_thread->setParanoiaFullMode(Preferences::fullParanoiaMode());
0040     cdda_extract_thread->setSkipReadingErrors(Preferences::skipReadingErrors());
0041     cdda_extract_thread->setSampleOffset(Preferences::sampleOffset());
0042 
0043     jobs = new AudexJobs();
0044     connect(jobs, SIGNAL(newJobAvailable()), this, SLOT(start_encode()));
0045 
0046     wave_file_writer = new WaveFileWriter();
0047 
0048     last_measuring_point_sector = -1;
0049     timer_extract = new QTimer(this);
0050     connect(timer_extract, SIGNAL(timeout()), this, SLOT(calculate_speed_extract()));
0051     timer_extract->start(4000);
0052 
0053     last_measuring_point_encoder_percent = -1;
0054     timer_encode = new QTimer(this);
0055     connect(timer_encode, SIGNAL(timeout()), this, SLOT(calculate_speed_encode()));
0056     timer_encode->start(2000);
0057 
0058     connect(encoder_wrapper, SIGNAL(progress(int)), this, SLOT(progress_encode(int)));
0059     connect(encoder_wrapper, SIGNAL(finished()), this, SLOT(finish_encode()));
0060     connect(encoder_wrapper, SIGNAL(info(const QString &)), this, SLOT(slot_info(const QString &)));
0061     connect(encoder_wrapper, SIGNAL(warning(const QString &)), this, SLOT(slot_warning(const QString &)));
0062     connect(encoder_wrapper, SIGNAL(error(const QString &, const QString &)), this, SLOT(slot_error(const QString &, const QString &)));
0063 
0064     connect(cdda_extract_thread, SIGNAL(progress(int, int, int)), this, SLOT(progress_extract(int, int, int)));
0065     connect(cdda_extract_thread, SIGNAL(output(const QByteArray &)), this, SLOT(write_to_wave(const QByteArray &)));
0066     connect(cdda_extract_thread, SIGNAL(finished()), this, SLOT(finish_extract()));
0067     connect(cdda_extract_thread, SIGNAL(info(const QString &)), this, SLOT(slot_info(const QString &)));
0068     connect(cdda_extract_thread, SIGNAL(warning(const QString &)), this, SLOT(slot_warning(const QString &)));
0069     connect(cdda_extract_thread, SIGNAL(error(const QString &, const QString &)), this, SLOT(slot_error(const QString &, const QString &)));
0070 
0071     process_counter = 0;
0072     timeout_done = false;
0073     timeout_counter = 0;
0074     p_finished = false;
0075     p_finished_successful = false;
0076 
0077     en_track_index = 0;
0078     en_track_count = 0;
0079 
0080     ex_track_index = 0;
0081     ex_track_count = 0;
0082 
0083     current_sector = 0;
0084     current_encoder_percent = 0;
0085 
0086     overall_frames = 0;
0087 }
0088 
0089 Audex::~Audex()
0090 {
0091     delete encoder_wrapper;
0092     delete cdda_extract_thread;
0093     delete wave_file_writer;
0094     delete jobs;
0095 }
0096 
0097 bool Audex::prepare()
0098 {
0099     if (profile_model->currentProfileIndex() < 0) {
0100         slot_error(i18n("No profile selected. Operation abort."));
0101         return false;
0102     }
0103 
0104     qDebug() << "Using profile with index" << profile_model->currentProfileIndex();
0105 
0106     return true;
0107 }
0108 
0109 void Audex::start()
0110 {
0111     Q_EMIT changedEncodeTrack(0, 0, "");
0112     Q_EMIT info(i18n("Start ripping and encoding with profile \"%1\"...", p_profile_name));
0113     if (check())
0114         start_extract();
0115     else
0116         request_finish(false);
0117 }
0118 
0119 void Audex::cancel()
0120 {
0121     request_finish(false);
0122 }
0123 
0124 const QStringList &Audex::extractLog()
0125 {
0126     return cdda_extract_thread->log();
0127 }
0128 
0129 const QStringList &Audex::encoderLog()
0130 {
0131     return encoder_wrapper->log();
0132 }
0133 
0134 void Audex::start_extract()
0135 {
0136     if (p_finished)
0137         return;
0138 
0139     if (p_single_file) {
0140         if (ex_track_count >= 1) {
0141             if (!jobs->jobInProgress() && !jobs->pendingJobs())
0142                 request_finish(true);
0143             return;
0144         }
0145 
0146         ex_track_index++;
0147 
0148         QString artist = cdda_model->artist();
0149         QString title = cdda_model->title();
0150         QString year = cdda_model->year();
0151         QString genre = cdda_model->genre();
0152         QString suffix = p_suffix;
0153         QString basepath = Preferences::basePath();
0154         int cdnum;
0155         if (!cdda_model->isMultiCD())
0156             cdnum = 0;
0157         else
0158             cdnum = cdda_model->cdNum();
0159         int nooftracks = cdda_model->numOfAudioTracks();
0160         bool overwrite = Preferences::overwriteExistingFiles();
0161 
0162         QString targetFilename;
0163         if (!construct_target_filename_for_singlefile(targetFilename, cdnum, nooftracks, artist, title, year, genre, suffix, basepath, overwrite)) {
0164             request_finish(false);
0165             return;
0166         }
0167         ex_track_target_filename = targetFilename;
0168 
0169         cdda_model->setCustomData("filename", targetFilename);
0170 
0171         // if empty (maybe because it already exists) skip
0172         if (!targetFilename.isEmpty()) {
0173             Q_EMIT changedExtractTrack(ex_track_index, 1, artist, title);
0174 
0175             QString sourceFilename = tmp_dir.path() + '/' + "tracks-all.wav";
0176             ex_track_source_filename = sourceFilename;
0177             wave_file_writer->open(sourceFilename);
0178 
0179             cdda_extract_thread->setTrackToRip(0);
0180             cdda_extract_thread->start();
0181             process_counter++;
0182 
0183             ex_track_count++;
0184 
0185         } else {
0186             if (!jobs->jobInProgress() && !jobs->pendingJobs())
0187                 request_finish(true);
0188         }
0189 
0190     } else {
0191         if (ex_track_count >= cdda_model->numOfAudioTracksInSelection()) {
0192             if (!jobs->jobInProgress() && !jobs->pendingJobs())
0193                 request_finish(true);
0194             return;
0195         }
0196 
0197         ex_track_index++;
0198 
0199         bool skip = !cdda_model->isTrackInSelection(ex_track_index);
0200 
0201         if (!cdda_model->isAudioTrack(ex_track_index))
0202             skip = true;
0203 
0204         if (!skip) {
0205             QString artist = cdda_model->artist();
0206             QString title = cdda_model->title();
0207             QString tartist = cdda_model->data(cdda_model->index(ex_track_index - 1, CDDA_MODEL_COLUMN_ARTIST_INDEX)).toString();
0208             QString ttitle = cdda_model->data(cdda_model->index(ex_track_index - 1, CDDA_MODEL_COLUMN_TITLE_INDEX)).toString();
0209             QString year = cdda_model->year();
0210             QString genre = cdda_model->genre();
0211             QString isrc = cdda_model->cdio()->getISRC(ex_track_index);
0212             QString suffix = p_suffix;
0213             QString basepath = Preferences::basePath();
0214             bool fat32_compatible =
0215                 profile_model->data(profile_model->index(profile_model->currentProfileRow(), PROFILE_MODEL_COLUMN_FAT32COMPATIBLE_INDEX)).toBool();
0216             bool replacespaceswithunderscores =
0217                 profile_model->data(profile_model->index(profile_model->currentProfileRow(), PROFILE_MODEL_COLUMN_UNDERSCORE_INDEX)).toBool();
0218             bool _2digitstracknum =
0219                 profile_model->data(profile_model->index(profile_model->currentProfileRow(), PROFILE_MODEL_COLUMN_2DIGITSTRACKNUM_INDEX)).toBool();
0220             int cdnum;
0221             if (!cdda_model->isMultiCD())
0222                 cdnum = 0;
0223             else
0224                 cdnum = cdda_model->cdNum();
0225             int trackoffset = cdda_model->trackOffset();
0226             int nooftracks = cdda_model->numOfAudioTracks();
0227             bool overwrite = Preferences::overwriteExistingFiles();
0228 
0229             QString targetFilename;
0230             if (cdda_model->isVarious()) {
0231                 if (!construct_target_filename(targetFilename,
0232                                                ex_track_index,
0233                                                cdnum,
0234                                                nooftracks,
0235                                                trackoffset,
0236                                                artist,
0237                                                title,
0238                                                tartist,
0239                                                ttitle,
0240                                                year,
0241                                                genre,
0242                                                isrc,
0243                                                suffix,
0244                                                basepath,
0245                                                fat32_compatible,
0246                                                replacespaceswithunderscores,
0247                                                _2digitstracknum,
0248                                                overwrite,
0249                                                (ex_track_index == 1))) {
0250                     request_finish(false);
0251                     return;
0252                 }
0253             } else {
0254                 if (!construct_target_filename(targetFilename,
0255                                                ex_track_index,
0256                                                cdnum,
0257                                                nooftracks,
0258                                                trackoffset,
0259                                                artist,
0260                                                title,
0261                                                artist,
0262                                                ttitle,
0263                                                year,
0264                                                genre,
0265                                                isrc,
0266                                                suffix,
0267                                                basepath,
0268                                                fat32_compatible,
0269                                                replacespaceswithunderscores,
0270                                                _2digitstracknum,
0271                                                overwrite,
0272                                                (ex_track_index == 1))) {
0273                     request_finish(false);
0274                     return;
0275                 }
0276             }
0277             ex_track_target_filename = targetFilename;
0278 
0279             // if empty (maybe because it already exists) skip
0280             if (!targetFilename.isEmpty()) {
0281                 Q_EMIT changedExtractTrack(ex_track_index, cdda_model->numOfAudioTracks(), tartist, ttitle);
0282 
0283                 QString sourceFilename = tmp_dir.path() + '/' + QString("track-%1").arg(ex_track_index) + ".wav";
0284                 ex_track_source_filename = sourceFilename;
0285                 wave_file_writer->open(sourceFilename);
0286 
0287                 cdda_extract_thread->setTrackToRip(ex_track_index);
0288                 cdda_extract_thread->start();
0289                 process_counter++;
0290 
0291                 ex_track_count++;
0292 
0293             } else {
0294                 en_track_count++;
0295                 ex_track_count++; // important to check for finish
0296                 cdda_extract_thread->skipTrack(ex_track_index);
0297                 start_extract();
0298             }
0299 
0300         } else {
0301             start_extract();
0302         }
0303     }
0304 }
0305 
0306 void Audex::finish_extract()
0307 {
0308     process_counter--;
0309 
0310     wave_file_writer->close();
0311     if (p_finished) {
0312         QFile file(ex_track_source_filename);
0313         file.remove();
0314         if (!process_counter)
0315             execute_finish();
0316         return;
0317     }
0318     jobs->addNewJob(ex_track_source_filename, ex_track_target_filename, ex_track_index);
0319     start_extract();
0320 }
0321 
0322 void Audex::start_encode()
0323 {
0324     if (p_finished)
0325         return;
0326 
0327     if (p_single_file) {
0328         if (en_track_count >= 1) {
0329             request_finish(true);
0330             return;
0331         }
0332 
0333         if (encoder_wrapper->isProcessing())
0334             return;
0335 
0336         AudexJob *job = jobs->orderJob();
0337         if (!job)
0338             return;
0339 
0340         int cdnum = cdda_model->cdNum();
0341         int nooftracks = cdda_model->numOfAudioTracks();
0342         QString artist = cdda_model->artist();
0343         QString title = cdda_model->title();
0344         QString year = cdda_model->year();
0345         QString genre = cdda_model->genre();
0346         QString suffix = p_suffix;
0347 
0348         QString targetFilename = job->targetFilename();
0349         en_track_target_filename = targetFilename;
0350 
0351         Q_EMIT changedEncodeTrack(job->trackNo(), 1, targetFilename);
0352         en_track_count++;
0353 
0354         en_track_filename = job->sourceFilename();
0355         en_track_index = job->trackNo();
0356         if (!encoder_wrapper->encode(job->trackNo(),
0357                                      cdnum,
0358                                      0,
0359                                      nooftracks,
0360                                      artist,
0361                                      title,
0362                                      artist,
0363                                      title,
0364                                      genre,
0365                                      year,
0366                                      cdda_model->cdio()->getISRC(job->trackNo()),
0367                                      suffix,
0368                                      cdda_model->cover(),
0369                                      tmp_dir.path(),
0370                                      job->sourceFilename(),
0371                                      targetFilename)) {
0372             request_finish(false);
0373         }
0374         process_counter++;
0375 
0376     } else {
0377         if (en_track_count >= cdda_model->numOfAudioTracksInSelection()) {
0378             request_finish(true);
0379             return;
0380         }
0381 
0382         if (encoder_wrapper->isProcessing())
0383             return;
0384 
0385         AudexJob *job = jobs->orderJob();
0386         if (!job)
0387             return;
0388 
0389         int cdnum = cdda_model->cdNum();
0390         int trackoffset = cdda_model->trackOffset();
0391         int nooftracks = cdda_model->numOfAudioTracks();
0392         QString artist = cdda_model->artist();
0393         QString title = cdda_model->title();
0394         QString tartist = cdda_model->data(cdda_model->index(job->trackNo() - 1, CDDA_MODEL_COLUMN_ARTIST_INDEX)).toString();
0395         QString ttitle = cdda_model->data(cdda_model->index(job->trackNo() - 1, CDDA_MODEL_COLUMN_TITLE_INDEX)).toString();
0396         QString year = cdda_model->year();
0397         QString genre = cdda_model->genre();
0398         QString suffix = p_suffix;
0399 
0400         QString targetFilename = job->targetFilename();
0401         en_track_target_filename = targetFilename;
0402 
0403         Q_EMIT changedEncodeTrack(job->trackNo(), cdda_model->numOfAudioTracks(), targetFilename);
0404         en_track_count++;
0405 
0406         en_track_filename = job->sourceFilename();
0407         en_track_index = job->trackNo();
0408         if (cdda_model->isVarious()) {
0409             if (!encoder_wrapper->encode(job->trackNo(),
0410                                          cdnum,
0411                                          trackoffset,
0412                                          nooftracks,
0413                                          artist,
0414                                          title,
0415                                          tartist,
0416                                          ttitle,
0417                                          genre,
0418                                          year,
0419                                          cdda_model->cdio()->getISRC(job->trackNo()),
0420                                          suffix,
0421                                          cdda_model->cover(),
0422                                          tmp_dir.path(),
0423                                          job->sourceFilename(),
0424                                          targetFilename)) {
0425                 request_finish(false);
0426             }
0427         } else {
0428             if (!encoder_wrapper->encode(job->trackNo(),
0429                                          cdnum,
0430                                          trackoffset,
0431                                          nooftracks,
0432                                          artist,
0433                                          title,
0434                                          artist,
0435                                          ttitle,
0436                                          genre,
0437                                          year,
0438                                          cdda_model->cdio()->getISRC(job->trackNo()),
0439                                          suffix,
0440                                          cdda_model->cover(),
0441                                          tmp_dir.path(),
0442                                          job->sourceFilename(),
0443                                          targetFilename)) {
0444                 request_finish(false);
0445             }
0446         }
0447         process_counter++;
0448     }
0449 }
0450 
0451 void Audex::finish_encode()
0452 {
0453     process_counter--;
0454     jobs->reportJobFinished();
0455 
0456     cdda_model->setCustomDataPerTrack(en_track_index, "filename", en_track_target_filename);
0457     cdda_model->setCustomDataPerTrack(en_track_index, "ripped", true);
0458 
0459     QFile file(en_track_filename);
0460     file.remove();
0461 
0462     if (p_finished) {
0463         if (!process_counter)
0464             execute_finish();
0465         return;
0466     }
0467     Q_EMIT changedEncodeTrack(0, 0, "");
0468     progress_encode(0);
0469     start_encode();
0470 }
0471 
0472 void Audex::calculate_speed_extract()
0473 {
0474     if ((last_measuring_point_sector > -1) && (cdda_extract_thread->isProcessing())) {
0475         double new_value = (double)(current_sector - last_measuring_point_sector) / (2.0f * (double)SECTORS_PER_SECOND);
0476         if (new_value < 0.0f)
0477             new_value = 0.0f;
0478         if ((new_value < 0.2f) && (!timeout_done)) {
0479             timeout_counter += 2;
0480             if (timeout_counter >= 300) {
0481                 timeout_done = true;
0482                 Q_EMIT timeout();
0483             }
0484         }
0485         Q_EMIT speedExtract(new_value);
0486     } else {
0487         Q_EMIT speedExtract(0.0f);
0488     }
0489     last_measuring_point_sector = current_sector;
0490 }
0491 
0492 void Audex::calculate_speed_encode()
0493 {
0494     if ((last_measuring_point_encoder_percent > -1) && (encoder_wrapper->isProcessing()) && (current_encoder_percent > 0)) {
0495         int song_length = cdda_model->data(cdda_model->index(en_track_index - 1, CDDA_MODEL_COLUMN_LENGTH_INDEX), CDDA_MODEL_INTERNAL_ROLE).toInt();
0496         double new_value = (double)((double)song_length / 100.0f) * ((double)current_encoder_percent - (double)last_measuring_point_encoder_percent);
0497         if (new_value < 0.0f)
0498             new_value = 0.0f;
0499         Q_EMIT speedEncode(new_value);
0500     } else {
0501         Q_EMIT speedEncode(0.0f);
0502     }
0503     last_measuring_point_encoder_percent = current_encoder_percent;
0504 }
0505 
0506 void Audex::progress_extract(int percent_of_track, int sector, int overall_sectors_read)
0507 {
0508     if (overall_frames == 0) {
0509         QSet<int> sel = cdda_model->selectedTracks();
0510         QSet<int>::ConstIterator it(sel.begin()), end(sel.end());
0511         for (; it != end; ++it) {
0512             if ((*it < 0) || (*it > cdda_extract_thread->cdio()->numOfTracks()) || (!cdda_extract_thread->cdio()->isAudioTrack((*it)))) {
0513                 continue;
0514             }
0515             overall_frames += cdda_extract_thread->cdio()->numOfFramesOfTrack((*it));
0516         }
0517     }
0518 
0519     float fraction = 0.0f;
0520     if (overall_frames > 0)
0521         fraction = (float)overall_sectors_read / (float)overall_frames;
0522 
0523     Q_EMIT progressExtractTrack(percent_of_track);
0524     Q_EMIT progressExtractOverall((int)(fraction * 100.0f));
0525 
0526     current_sector = sector;
0527 }
0528 
0529 void Audex::progress_encode(int percent)
0530 {
0531     Q_EMIT progressEncodeTrack(percent);
0532     if (percent > 0) {
0533         Q_EMIT progressEncodeOverall(((en_track_count > 0 ? ((en_track_count - 1) * 100.0f) : 0) + (percent * 1.0f))
0534                                      / (float)cdda_model->numOfAudioTracksInSelection());
0535     }
0536     current_encoder_percent = percent;
0537 }
0538 
0539 void Audex::write_to_wave(const QByteArray &data)
0540 {
0541     wave_file_writer->write(data);
0542 }
0543 
0544 void Audex::slot_error(const QString &description, const QString &solution)
0545 {
0546     Q_EMIT error(description, solution);
0547     request_finish(false);
0548 }
0549 
0550 void Audex::slot_warning(const QString &description)
0551 {
0552     Q_EMIT warning(description);
0553 }
0554 
0555 void Audex::slot_info(const QString &description)
0556 {
0557     Q_EMIT info(description);
0558 }
0559 
0560 void Audex::check_if_thread_still_running()
0561 {
0562     if (cdda_extract_thread->isRunning()) {
0563         // this could happen if the thread is stuck in paranoia_read
0564         // because of an unreadable cd
0565         cdda_extract_thread->terminate();
0566         qDebug() << "Terminate extracting thread.";
0567     }
0568 }
0569 
0570 bool Audex::construct_target_filename(QString &targetFilename,
0571                                       int trackno,
0572                                       int cdno,
0573                                       int nooftracks,
0574                                       int gindex,
0575                                       const QString &artist,
0576                                       const QString &title,
0577                                       const QString &tartist,
0578                                       const QString &ttitle,
0579                                       const QString &year,
0580                                       const QString &genre,
0581                                       const QString &isrc,
0582                                       const QString &ext,
0583                                       const QString &basepath,
0584                                       bool fat32_compatible,
0585                                       bool replacespaceswithunderscores,
0586                                       bool _2digitstracknum,
0587                                       bool overwrite_existing_files,
0588                                       bool is_first_track)
0589 {
0590     Q_UNUSED(is_first_track);
0591 
0592     SchemeParser schemeparser;
0593     targetFilename = ((basepath.right(1) == "/") ? basepath : basepath + "/")
0594         + schemeparser.parsePerTrackFilenameScheme(
0595             profile_model->data(profile_model->index(profile_model->currentProfileRow(), PROFILE_MODEL_COLUMN_SCHEME_INDEX)).toString(),
0596             trackno,
0597             cdno,
0598             gindex,
0599             nooftracks,
0600             artist,
0601             title,
0602             tartist,
0603             ttitle,
0604             year,
0605             genre,
0606             isrc,
0607             ext,
0608             fat32_compatible,
0609             replacespaceswithunderscores,
0610             _2digitstracknum);
0611 
0612     int lastSlash = targetFilename.lastIndexOf("/", -1);
0613     if (lastSlash == -1) {
0614         Q_EMIT error(i18n("Can't find path \"%1\".", targetFilename), i18n("Please check your path (write access?)"));
0615         return false;
0616     }
0617     QString targetPath = targetFilename.mid(0, lastSlash);
0618     QString targetStrippedFilename = targetFilename.mid(lastSlash + 1);
0619     target_dir = targetPath;
0620 
0621     if (!p_mkdir(targetPath))
0622         return false;
0623 
0624     QStorageInfo diskfreespace(targetPath);
0625     quint64 free = diskfreespace.bytesAvailable() / 1024;
0626     if (free < 200 * 1024) {
0627         Q_EMIT warning(i18n("Free space on \"%1\" is less than 200 MiB.", targetPath));
0628     }
0629 
0630     auto *file = new QFile(targetFilename);
0631     if (file->exists()) {
0632         if (overwrite_existing_files) {
0633             Q_EMIT warning(i18n("Warning! File \"%1\" already exists. Overwriting.", targetStrippedFilename));
0634         } else {
0635             Q_EMIT warning(i18n("Warning! File \"%1\" already exists. Skipping.", targetStrippedFilename));
0636 
0637             cdda_model->setCustomDataPerTrack(trackno, "filename", targetFilename);
0638             cdda_model->setCustomDataPerTrack(trackno, "ripped", true);
0639 
0640             targetFilename.clear();
0641         }
0642     }
0643     delete file;
0644 
0645     return true;
0646 }
0647 
0648 bool Audex::construct_target_filename_for_singlefile(QString &targetFilename,
0649                                                      int cdno,
0650                                                      int nooftracks,
0651                                                      const QString &artist,
0652                                                      const QString &title,
0653                                                      const QString &date,
0654                                                      const QString &genre,
0655                                                      const QString &ext,
0656                                                      const QString &basepath,
0657                                                      bool overwrite_existing_files)
0658 {
0659     SchemeParser schemeparser;
0660     targetFilename = ((basepath.right(1) == "/") ? basepath : basepath + "/")
0661         + schemeparser.parseFilenameScheme(
0662             profile_model->data(profile_model->index(profile_model->currentProfileRow(), PROFILE_MODEL_COLUMN_SF_NAME_INDEX)).toString(),
0663             cdno,
0664             nooftracks,
0665             artist,
0666             title,
0667             date,
0668             genre,
0669             ext,
0670             false);
0671 
0672     int lastSlash = targetFilename.lastIndexOf("/", -1);
0673     if (lastSlash == -1) {
0674         Q_EMIT error(i18n("Can't find path \"%1\".", targetFilename), i18n("Please check your path (write access?)"));
0675         return false;
0676     }
0677     QString targetPath = targetFilename.mid(0, lastSlash);
0678     target_dir = targetPath;
0679     QString targetStrippedFilename = targetFilename.mid(lastSlash + 1);
0680     QDir *dir = new QDir(targetPath);
0681     if (!dir->exists()) {
0682         if (!dir->mkpath(targetPath)) {
0683             Q_EMIT error(i18n("Unable to create folder \"%1\".", targetPath), i18n("Please check your path (write access?)"));
0684             return false;
0685         } else {
0686             Q_EMIT info(i18n("Folder \"%1\" successfully created.", targetPath));
0687         }
0688     } else {
0689         Q_EMIT warning(i18n("Folder \"%1\" already exists.", targetPath));
0690     }
0691     delete dir;
0692 
0693     QStorageInfo diskfreespace(targetPath);
0694     quint64 free = diskfreespace.bytesAvailable() / 1024;
0695     if (free < 800 * 1024) {
0696         Q_EMIT warning(i18n("Free space on \"%1\" is less than 800 MiB.", targetPath));
0697     }
0698 
0699     auto *file = new QFile(targetFilename);
0700     if (file->exists()) {
0701         if (overwrite_existing_files) {
0702             Q_EMIT warning(i18n("Warning! File \"%1\" already exists. Overwriting.", targetStrippedFilename));
0703         } else {
0704             Q_EMIT warning(i18n("Warning! File \"%1\" already exists. Skipping.", targetStrippedFilename));
0705 
0706             cdda_model->setCustomData("filename", targetFilename);
0707 
0708             targetFilename.clear();
0709         }
0710     }
0711     delete file;
0712 
0713     return true;
0714 }
0715 
0716 bool Audex::check()
0717 {
0718     if (!tmp_dir.isValid()) {
0719         slot_error(i18n("Temporary folder \"%1\" error (%2).", tmp_dir.path(), tmp_dir.errorString()), i18n("Please check."));
0720         return false;
0721     }
0722 
0723     QStorageInfo diskfreespace(tmp_dir.path());
0724     quint64 free = diskfreespace.bytesAvailable() / (1024 * 1024);
0725 
0726     if (free < 800) {
0727         slot_warning(i18n("Free space on temporary folder \"%1\" is less than 800 MiB.", tmp_dir.path()));
0728     } else if (free < 200) {
0729         slot_error(i18n("Temporary folder \"%1\" needs at least 200 MiB of free space.", tmp_dir.path()), i18n("Please free space or set another path."));
0730         return false;
0731     }
0732 
0733     return true;
0734 }
0735 
0736 void Audex::request_finish(bool successful)
0737 {
0738     if (!p_finished) {
0739         p_finished = true;
0740         p_finished_successful = successful;
0741     } else {
0742         return;
0743     }
0744 
0745     if (process_counter > 0) {
0746         encoder_wrapper->cancel();
0747         cdda_extract_thread->cancel();
0748         QTimer::singleShot(2000, this, SLOT(check_if_thread_still_running()));
0749 
0750     } else {
0751         execute_finish();
0752     }
0753 }
0754 
0755 void Audex::execute_finish()
0756 {
0757     if (Preferences::ejectCDTray()) {
0758         Q_EMIT info(i18n("Eject CD tray"));
0759         cdda_model->eject();
0760     }
0761 
0762     bool overwrite = Preferences::overwriteExistingFiles();
0763 
0764     QStringList target_filename_list;
0765     for (int i = 0; i < cdda_model->rowCount(); ++i) {
0766         if (!cdda_model->isAudioTrack(i + 1))
0767             continue;
0768         if (!cdda_model->isTrackInSelection(i + 1))
0769             continue;
0770         if (!cdda_model->getCustomDataPerTrack(i + 1, "ripped").toBool())
0771             continue;
0772         target_filename_list.append(cdda_model->getCustomDataPerTrack(i + 1, "filename").toString());
0773     }
0774     QString target_single_filename;
0775     if (p_single_file) {
0776         target_single_filename = cdda_model->customData("filename").toString();
0777     }
0778 
0779     QString cover_file;
0780     if ((p_finished_successful) && (profile_model->data(profile_model->index(profile_model->currentProfileRow(), PROFILE_MODEL_COLUMN_SC_INDEX)).toBool())) {
0781         // store the cover
0782         if (!cdda_model->isCoverEmpty()) {
0783             QImage image(cdda_model->cover());
0784             if (profile_model->data(profile_model->index(profile_model->currentProfileRow(), PROFILE_MODEL_COLUMN_SC_SCALE_INDEX)).toBool()) {
0785                 QSize size = profile_model->data(profile_model->index(profile_model->currentProfileRow(), PROFILE_MODEL_COLUMN_SC_SIZE_INDEX)).toSize();
0786                 image = image.scaled(size, Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation);
0787                 qDebug() << QString("Cover scaled to %1x%2.").arg(size.width()).arg(size.height());
0788             }
0789             QString scheme = profile_model->data(profile_model->index(profile_model->currentProfileRow(), PROFILE_MODEL_COLUMN_SC_NAME_INDEX)).toString();
0790             QString format = profile_model->data(profile_model->index(profile_model->currentProfileRow(), PROFILE_MODEL_COLUMN_SC_FORMAT_INDEX)).toString();
0791 
0792             SchemeParser schemeparser;
0793             QString filename = schemeparser.parseFilenameScheme(
0794                 scheme,
0795                 cdda_model->cdNum(),
0796                 cdda_model->numOfAudioTracks(),
0797                 cdda_model->artist(),
0798                 cdda_model->title(),
0799                 QString("%1").arg(cdda_model->year()),
0800                 cdda_model->genre(),
0801                 format.toLower(),
0802                 profile_model->data(profile_model->index(profile_model->currentProfileRow(), PROFILE_MODEL_COLUMN_FAT32COMPATIBLE_INDEX)).toBool());
0803 
0804             if (p_prepare_dir(filename, target_dir, overwrite)) {
0805                 if (image.save(filename, format.toLatin1().data())) {
0806                     Q_EMIT info(i18n("Cover \"%1\" successfully saved.", QFileInfo(filename).fileName()));
0807                     cover_file = filename;
0808                 } else {
0809                     Q_EMIT error(i18n("Unable to save cover \"%1\".", QFileInfo(filename).fileName()), i18n("Please check your path and permissions"));
0810                 }
0811             }
0812         }
0813     }
0814 
0815     QString playlist_file;
0816     if ((p_finished_successful) && (profile_model->data(profile_model->index(profile_model->currentProfileRow(), PROFILE_MODEL_COLUMN_PL_INDEX)).toBool())
0817         && (target_filename_list.count() > 0) && (!p_single_file)) {
0818         // create the playlist
0819         Playlist playlist;
0820 
0821         QString scheme = profile_model->data(profile_model->index(profile_model->currentProfileRow(), PROFILE_MODEL_COLUMN_PL_NAME_INDEX)).toString();
0822         QString format = profile_model->data(profile_model->index(profile_model->currentProfileRow(), PROFILE_MODEL_COLUMN_PL_FORMAT_INDEX)).toString();
0823         bool is_absFilePath =
0824             profile_model->data(profile_model->index(profile_model->currentProfileRow(), PROFILE_MODEL_COLUMN_PL_ABS_FILE_PATH_INDEX)).toBool();
0825         bool is_utf8 = profile_model->data(profile_model->index(profile_model->currentProfileRow(), PROFILE_MODEL_COLUMN_PL_UTF8_INDEX)).toBool();
0826 
0827         SchemeParser schemeparser;
0828         QString filename = schemeparser.parseFilenameScheme(
0829             scheme,
0830             cdda_model->cdNum(),
0831             cdda_model->numOfAudioTracks(),
0832             cdda_model->artist(),
0833             cdda_model->title(),
0834             QString("%1").arg(cdda_model->year()),
0835             cdda_model->genre(),
0836             format.toLower(),
0837             profile_model->data(profile_model->index(profile_model->currentProfileRow(), PROFILE_MODEL_COLUMN_FAT32COMPATIBLE_INDEX)).toBool());
0838 
0839         if (p_prepare_dir(filename, target_dir, (overwrite && !cdda_model->isMultiCD() && (cdda_model->cdNum() < 1)))) {
0840             QFile file(filename);
0841 
0842             if (file.exists() && cdda_model->isMultiCD() && (cdda_model->cdNum() > 0)) {
0843                 if (file.open(QFile::ReadOnly)) {
0844                     QByteArray ba = file.readAll();
0845                     playlist.addPlaylist(ba);
0846                     file.close();
0847                 }
0848             }
0849 
0850             if (file.open(QFile::WriteOnly | QFile::Truncate)) {
0851                 for (int i = 0; i < cdda_model->rowCount(); ++i) {
0852                     if (!cdda_model->isAudioTrack(i + 1))
0853                         continue;
0854                     if (!cdda_model->isTrackInSelection(i + 1))
0855                         continue;
0856                     if (!cdda_model->getCustomDataPerTrack(i + 1, "ripped").toBool())
0857                         continue;
0858                     PlaylistItem item;
0859                     item.setFilename(cdda_model->getCustomDataPerTrack(i + 1, "filename").toString());
0860                     item.setArtist(cdda_model->data(cdda_model->index(i, CDDA_MODEL_COLUMN_ARTIST_INDEX)).toString());
0861                     item.setTitle(cdda_model->data(cdda_model->index(i, CDDA_MODEL_COLUMN_TITLE_INDEX)).toString());
0862                     item.setLength(cdda_model->lengthOfTrack(i + 1));
0863                     playlist.appendItem(item);
0864                 }
0865 
0866                 QString relFilePath;
0867                 if (!is_absFilePath) {
0868                     relFilePath = QFileInfo(filename).absoluteDir().absolutePath();
0869                 }
0870 
0871                 if (format == "M3U") {
0872                     file.write(playlist.toM3U(relFilePath, is_utf8));
0873                 } else if (format == "PLS") {
0874                     file.write(playlist.toPLS(relFilePath, is_utf8));
0875                 } else if (format == "XSPF") {
0876                     file.write(playlist.toXSPF());
0877                 }
0878                 file.close();
0879                 Q_EMIT info(i18n("Playlist \"%1\" successfully created.", QFileInfo(filename).fileName()));
0880                 playlist_file = filename;
0881 
0882             } else {
0883                 Q_EMIT error(i18n("Unable to save playlist \"%1\".", QFileInfo(filename).fileName()), i18n("Please check your path and permissions"));
0884             }
0885         }
0886     }
0887 
0888     QString info_file;
0889     if ((p_finished_successful) && (profile_model->data(profile_model->index(profile_model->currentProfileRow(), PROFILE_MODEL_COLUMN_INF_INDEX)).toBool())) {
0890         SchemeParser schemeparser;
0891         QString filename = schemeparser.parseFilenameScheme(
0892             profile_model->data(profile_model->index(profile_model->currentProfileRow(), PROFILE_MODEL_COLUMN_INF_NAME_INDEX)).toString(),
0893             cdda_model->cdNum(),
0894             cdda_model->numOfAudioTracks(),
0895             cdda_model->artist(),
0896             cdda_model->title(),
0897             QString("%1").arg(cdda_model->year()),
0898             cdda_model->genre(),
0899             profile_model->data(profile_model->index(profile_model->currentProfileRow(), PROFILE_MODEL_COLUMN_INF_SUFFIX_INDEX)).toString(),
0900             profile_model->data(profile_model->index(profile_model->currentProfileRow(), PROFILE_MODEL_COLUMN_FAT32COMPATIBLE_INDEX)).toBool());
0901 
0902         if (p_prepare_dir(filename, target_dir, overwrite)) {
0903             QFile file(filename);
0904             if (file.open(QFile::WriteOnly | QFile::Truncate)) {
0905                 QTextStream out(&file);
0906                 QStringList text =
0907                     profile_model->data(profile_model->index(profile_model->currentProfileRow(), PROFILE_MODEL_COLUMN_INF_TEXT_INDEX)).toStringList();
0908                 schemeparser.parseInfoTextScheme(text,
0909                                                  cdda_model->artist(),
0910                                                  cdda_model->title(),
0911                                                  QString("%1").arg(cdda_model->year()),
0912                                                  cdda_model->genre(),
0913                                                  cdda_model->cdio()->getMCN(),
0914                                                  DiscIDCalculator::CDDBId(cdda_model->discSignature()),
0915                                                  p_size_of_all_files(target_filename_list),
0916                                                  cdda_model->lengthOfAudioTracksInSelection(),
0917                                                  cdda_model->numOfAudioTracksInSelection());
0918                 out << text.join("\n");
0919                 file.close();
0920                 Q_EMIT info(i18n("Info file \"%1\" successfully created.", QFileInfo(filename).fileName()));
0921                 info_file = filename;
0922             } else {
0923                 Q_EMIT error(i18n("Unable to save info file \"%1\".", QFileInfo(filename).fileName()), i18n("Please check your path and permissions"));
0924             }
0925         }
0926     }
0927 
0928     QString hash_list;
0929     if ((p_finished_successful) && (profile_model->data(profile_model->index(profile_model->currentProfileRow(), PROFILE_MODEL_COLUMN_HL_INDEX)).toBool())
0930         && (target_filename_list.count() > 0)) {
0931         QString scheme = profile_model->data(profile_model->index(profile_model->currentProfileRow(), PROFILE_MODEL_COLUMN_HL_NAME_INDEX)).toString();
0932         QString format = profile_model->data(profile_model->index(profile_model->currentProfileRow(), PROFILE_MODEL_COLUMN_HL_FORMAT_INDEX)).toString();
0933 
0934         SchemeParser schemeparser;
0935         QString filename = schemeparser.parseFilenameScheme(
0936             scheme,
0937             cdda_model->cdNum(),
0938             cdda_model->numOfAudioTracks(),
0939             cdda_model->artist(),
0940             cdda_model->title(),
0941             QString("%1").arg(cdda_model->year()),
0942             cdda_model->genre(),
0943             format.toLower(),
0944             profile_model->data(profile_model->index(profile_model->currentProfileRow(), PROFILE_MODEL_COLUMN_FAT32COMPATIBLE_INDEX)).toBool());
0945 
0946         if (p_prepare_dir(filename, target_dir, (overwrite && !cdda_model->isMultiCD() && (cdda_model->cdNum() < 1)))) {
0947             QFile file(filename);
0948             bool fexists = file.exists() && cdda_model->isMultiCD() && (cdda_model->cdNum() > 0);
0949             bool success;
0950             if (fexists)
0951                 success = file.open(QFile::WriteOnly | QFile::Append);
0952             else
0953                 success = file.open(QFile::WriteOnly | QFile::Truncate);
0954             if (success) {
0955                 QTextStream out(&file);
0956                 if (fexists)
0957                     out << "\n";
0958                 Hashlist hashlist;
0959                 if (format == "SFV") {
0960                     out << hashlist.getSFV(target_filename_list).join("\n");
0961                 } else if (format == "MD5") {
0962                     out << hashlist.getMD5(target_filename_list).join("\n");
0963                 } else if (format == "SHA-256") {
0964                     out << hashlist.getSHA256(target_filename_list).join("\n");
0965                 }
0966                 file.close();
0967                 Q_EMIT info(i18n("Hashlist \"%1\" successfully created.", QFileInfo(filename).fileName()));
0968                 hash_list = filename;
0969             } else {
0970                 Q_EMIT error(i18n("Unable to save hashlist \"%1\".", QFileInfo(filename).fileName()), i18n("Please check your path and permissions"));
0971             }
0972         }
0973     }
0974 
0975     QString cue_sheet;
0976     if ((p_finished_successful) && (profile_model->data(profile_model->index(profile_model->currentProfileRow(), PROFILE_MODEL_COLUMN_CUE_INDEX)).toBool())
0977         && (((target_filename_list.count() > 0) && !p_single_file) || p_single_file)) {
0978         QString scheme = profile_model->data(profile_model->index(profile_model->currentProfileRow(), PROFILE_MODEL_COLUMN_CUE_NAME_INDEX)).toString();
0979         bool writeMCNAndISRC =
0980             profile_model->data(profile_model->index(profile_model->currentProfileRow(), PROFILE_MODEL_COLUMN_CUE_ADD_MCN_AND_ISRC_INDEX)).toBool();
0981 
0982         SchemeParser schemeparser;
0983         QString filename = schemeparser.parseFilenameScheme(
0984             scheme,
0985             cdda_model->cdNum(),
0986             cdda_model->numOfAudioTracks(),
0987             cdda_model->artist(),
0988             cdda_model->title(),
0989             QString("%1").arg(cdda_model->year()),
0990             cdda_model->genre(),
0991             "cue",
0992             profile_model->data(profile_model->index(profile_model->currentProfileRow(), PROFILE_MODEL_COLUMN_FAT32COMPATIBLE_INDEX)).toBool());
0993 
0994         if (p_prepare_dir(filename, target_dir, overwrite)) {
0995             QFile file(filename);
0996             bool success = file.open(QFile::WriteOnly | QFile::Truncate);
0997             if (success) {
0998                 QTextStream out(&file);
0999                 CueSheetWriter cuesheetwriter(cdda_model);
1000                 if (p_single_file) {
1001                     out << cuesheetwriter.cueSheet(target_single_filename, Preferences::sampleOffset() / CD_FRAMESIZE_SAMPLES, writeMCNAndISRC, writeMCNAndISRC)
1002                                .join("\n");
1003                 } else {
1004                     out << cuesheetwriter.cueSheet(target_filename_list, Preferences::sampleOffset() / CD_FRAMESIZE_SAMPLES, writeMCNAndISRC, writeMCNAndISRC)
1005                                .join("\n");
1006                 }
1007                 file.close();
1008                 Q_EMIT info(i18n("Cue sheet \"%1\" successfully created.", QFileInfo(filename).fileName()));
1009                 cue_sheet = filename;
1010             } else {
1011                 Q_EMIT error(i18n("Unable to save cue sheet \"%1\".", QFileInfo(filename).fileName()), i18n("Please check your path and permissions"));
1012             }
1013         }
1014     }
1015 
1016     QString log_file;
1017     if ((p_finished_successful) && (profile_model->data(profile_model->index(profile_model->currentProfileRow(), PROFILE_MODEL_COLUMN_LOG_INDEX)).toBool())) {
1018         SchemeParser schemeparser;
1019         QString filename = schemeparser.parseFilenameScheme(
1020             profile_model->data(profile_model->index(profile_model->currentProfileRow(), PROFILE_MODEL_COLUMN_LOG_NAME_INDEX)).toString(),
1021             cdda_model->cdNum(),
1022             cdda_model->numOfAudioTracks(),
1023             cdda_model->artist(),
1024             cdda_model->title(),
1025             QString("%1").arg(cdda_model->year()),
1026             cdda_model->genre(),
1027             "log",
1028             profile_model->data(profile_model->index(profile_model->currentProfileRow(), PROFILE_MODEL_COLUMN_FAT32COMPATIBLE_INDEX)).toBool());
1029 
1030         if (p_prepare_dir(filename, target_dir, overwrite)) {
1031             QFile file(filename);
1032             if (file.open(QFile::WriteOnly | QFile::Truncate)) {
1033                 QTextStream out(&file);
1034                 out << cdda_extract_thread->log().join("\n");
1035                 file.close();
1036                 Q_EMIT info(i18n("Log file \"%1\" successfully stored.", QFileInfo(filename).fileName()));
1037                 info_file = filename;
1038             } else {
1039                 Q_EMIT error(i18n("Unable to save log file \"%1\".", QFileInfo(filename).fileName()), i18n("Please check your path and permissions"));
1040             }
1041         }
1042     }
1043 
1044     if ((p_finished_successful) && (Preferences::upload()) && (target_filename_list.count() > 0)) {
1045         QString targetpath = QFileInfo(target_filename_list.at(0)).absolutePath().mid(Preferences::basePath().length());
1046 
1047         QStringList files_to_transfer = target_filename_list;
1048         if (!cover_file.isEmpty())
1049             files_to_transfer << cover_file;
1050         if (!playlist_file.isEmpty())
1051             files_to_transfer << playlist_file;
1052         if (!info_file.isEmpty())
1053             files_to_transfer << info_file;
1054         if (!hash_list.isEmpty())
1055             files_to_transfer << hash_list;
1056         if (!cue_sheet.isEmpty())
1057             files_to_transfer << cue_sheet;
1058 
1059         Upload upload(Preferences::url(), this);
1060         connect(&upload, SIGNAL(info(const QString &)), this, SLOT(slot_info(const QString &)));
1061         connect(&upload, SIGNAL(error(const QString &, const QString &)), this, SLOT(slot_error(const QString &, const QString &)));
1062         upload.upload(targetpath, files_to_transfer);
1063     }
1064 
1065     // flush temporary path
1066     tmp_dir.remove();
1067 
1068     Q_EMIT finished(p_finished_successful);
1069 }
1070 
1071 bool Audex::p_prepare_dir(QString &filename, const QString &targetDirIfRelative, const bool overwrite)
1072 {
1073     QString result;
1074 
1075     QFileInfo fileinfo(filename);
1076     if (fileinfo.isAbsolute()) {
1077         if (!p_mkdir(fileinfo.dir().absolutePath())) {
1078             return false;
1079         } else {
1080             result = filename;
1081         }
1082     } else {
1083         if (!targetDirIfRelative.isEmpty()) {
1084             QDir dir(targetDirIfRelative);
1085             if (!dir.isReadable()) {
1086                 Q_EMIT error(i18n("Unable to open folder \"%1\".", targetDirIfRelative), i18n("Please check your path and permissions"));
1087                 return false;
1088             }
1089             result = targetDirIfRelative + '/' + filename;
1090         } else {
1091             result = filename;
1092         }
1093     }
1094 
1095     if (!overwrite) {
1096         QFileInfo info(result);
1097         if (info.exists()) {
1098             Q_EMIT warning(i18n("Warning! File \"%1\" already exists. Skipping.", info.fileName()));
1099             return false;
1100         }
1101     }
1102 
1103     filename = result;
1104 
1105     return true;
1106 }
1107 
1108 bool Audex::p_mkdir(const QString &absoluteFilePath)
1109 {
1110     QDir dir(absoluteFilePath);
1111     if (dir.exists()) {
1112         if (!dir.isReadable()) {
1113             Q_EMIT error(i18n("Unable to open folder \"%1\".", absoluteFilePath), i18n("Please check your path and permissions"));
1114             return false;
1115         }
1116     } else {
1117         if (!dir.mkpath(absoluteFilePath)) {
1118             Q_EMIT error(i18n("Unable to create folder \"%1\".", absoluteFilePath), i18n("Please check your path (write access?)"));
1119             return false;
1120         } else {
1121             Q_EMIT info(i18n("Folder \"%1\" successfully created.", absoluteFilePath));
1122         }
1123     }
1124 
1125     return true;
1126 }
1127 
1128 qreal Audex::p_size_of_all_files(const QStringList &filenames) const
1129 {
1130     qreal size = .0f;
1131     for (int i = 0; i < filenames.count(); ++i) {
1132         QFileInfo info(filenames.at(i));
1133         size += info.size();
1134     }
1135     return size;
1136 }