File indexing completed on 2024-04-28 12:37:42
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 }