diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 917db286a..37faa3f7c 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1035,14 +1035,16 @@ optional_source(HAVE_AUDIOCD devices/cddadevice.cpp devices/cddalister.cpp devices/cddasongloader.cpp - ui/ripcd.cpp + ripper/ripcddialog.cpp + ripper/ripper.cpp HEADERS devices/cddadevice.h devices/cddalister.h devices/cddasongloader.h - ui/ripcd.h + ripper/ripcddialog.h + ripper/ripper.h UI - ui/ripcd.ui + ripper/ripcddialog.ui ) # mtp device diff --git a/src/ripper/ripcddialog.cpp b/src/ripper/ripcddialog.cpp new file mode 100644 index 000000000..ca112bc02 --- /dev/null +++ b/src/ripper/ripcddialog.cpp @@ -0,0 +1,296 @@ +/* This file is part of Clementine. + Copyright 2014, Andre Siviero + + Clementine is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Clementine is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Clementine. If not, see . + */ + +#include "ripper/ripcddialog.h" + +#include +#include +#include +#include +#include +#include + +#include "config.h" +#include "core/logging.h" +#include "core/tagreaderclient.h" +#include "core/utilities.h" +#include "ripper/ripper.h" +#include "ui_ripcddialog.h" +#include "transcoder/transcoder.h" +#include "transcoder/transcoderoptionsdialog.h" +#include "ui/iconloader.h" + +namespace { +bool ComparePresetsByName(const TranscoderPreset& left, + const TranscoderPreset& right) { + return left.name_ < right.name_; +} + +const int kCheckboxColumn = 0; +const int kTrackNumberColumn = 1; +const int kTrackTitleColumn = 2; +} + +const char* RipCDDialog::kSettingsGroup = "Transcoder"; +const int RipCDDialog::kMaxDestinationItems = 10; + +RipCDDialog::RipCDDialog(QWidget* parent) + : QDialog(parent), + ui_(new Ui_RipCDDialog), + ripper_(new Ripper(this)), + working_(false) { + // Init + ui_->setupUi(this); + + // Set column widths in the QTableWidget. + ui_->tableWidget->horizontalHeader()->setResizeMode( + kCheckboxColumn, QHeaderView::ResizeToContents); + ui_->tableWidget->horizontalHeader()->setResizeMode( + kTrackNumberColumn, QHeaderView::ResizeToContents); + ui_->tableWidget->horizontalHeader()->setResizeMode(kTrackTitleColumn, + QHeaderView::Stretch); + + // Add a rip button + rip_button_ = ui_->button_box->addButton(tr("Start ripping"), + QDialogButtonBox::ActionRole); + cancel_button_ = ui_->button_box->button(QDialogButtonBox::Cancel); + close_button_ = ui_->button_box->button(QDialogButtonBox::Close); + + // Hide elements + cancel_button_->hide(); + ui_->progress_group->hide(); + + connect(ui_->select_all_button, SIGNAL(clicked()), SLOT(SelectAll())); + connect(ui_->select_none_button, SIGNAL(clicked()), SLOT(SelectNone())); + connect(ui_->invert_selection_button, SIGNAL(clicked()), + SLOT(InvertSelection())); + connect(rip_button_, SIGNAL(clicked()), SLOT(ClickedRipButton())); + connect(cancel_button_, SIGNAL(clicked()), ripper_, SLOT(Cancel())); + connect(close_button_, SIGNAL(clicked()), SLOT(hide())); + + connect(ui_->options, SIGNAL(clicked()), SLOT(Options())); + connect(ui_->select, SIGNAL(clicked()), SLOT(AddDestination())); + + connect(ripper_, SIGNAL(Finished()), SLOT(Finished())); + connect(ripper_, SIGNAL(Cancelled()), SLOT(Cancelled())); + connect(ripper_, SIGNAL(ProgressInterval(int, int)), + SLOT(SetupProgressBarLimits(int, int))); + connect(ripper_, SIGNAL(Progress(int)), SLOT(UpdateProgressBar(int))); + + setWindowTitle(tr("Rip CD")); + AddDestinationDirectory(QDir::homePath()); + + // Get presets + QList presets = Transcoder::GetAllPresets(); + qSort(presets.begin(), presets.end(), ComparePresetsByName); + for (const TranscoderPreset& preset : presets) { + ui_->format->addItem( + QString("%1 (.%2)").arg(preset.name_).arg(preset.extension_), + QVariant::fromValue(preset)); + } + + // Load settings + QSettings s; + s.beginGroup(kSettingsGroup); + last_add_dir_ = s.value("last_add_dir", QDir::homePath()).toString(); + + QString last_output_format = s.value("last_output_format", "ogg").toString(); + for (int i = 0; i < ui_->format->count(); ++i) { + if (last_output_format == + ui_->format->itemData(i).value().extension_) { + ui_->format->setCurrentIndex(i); + break; + } + } +} + +RipCDDialog::~RipCDDialog() {} + +bool RipCDDialog::CheckCDIOIsValid() { return ripper_->CheckCDIOIsValid(); } + +void RipCDDialog::showEvent(QShowEvent* event) { + BuildTrackListTable(); + if (!working_) { + ui_->progress_group->hide(); + } +} + +void RipCDDialog::ClickedRipButton() { + if (ripper_->MediaChanged()) { + QMessageBox cdio_fail(QMessageBox::Critical, tr("Error Ripping CD"), + tr("Media has changed. Reloading")); + cdio_fail.exec(); + if (CheckCDIOIsValid()) { + BuildTrackListTable(); + } else { + ui_->tableWidget->clearContents(); + } + return; + } + + // Add tracks and album information to the ripper. + ripper_->ClearTracks(); + TranscoderPreset preset = ui_->format->itemData(ui_->format->currentIndex()) + .value(); + for (int i = 1; i <= ui_->tableWidget->rowCount(); ++i) { + if (!checkboxes_.value(i - 1)->isChecked()) { + continue; + } + QString transcoded_filename = GetOutputFileName( + ParseFileFormatString(ui_->format_filename->text(), i)); + QString title = track_names_.value(i - 1)->text(); + ripper_->AddTrack(i, title, transcoded_filename, preset); + } + ripper_->SetAlbumInformation( + ui_->albumLineEdit->text(), ui_->artistLineEdit->text(), + ui_->genreLineEdit->text(), ui_->yearLineEdit->text().toInt(), + ui_->discLineEdit->text().toInt(), preset.type_); + + SetWorking(true); + ripper_->Start(); +} + +void RipCDDialog::Options() { + TranscoderPreset preset = ui_->format->itemData(ui_->format->currentIndex()) + .value(); + + TranscoderOptionsDialog dialog(preset.type_, this); + if (dialog.is_valid()) { + dialog.exec(); + } +} + +// Adds a folder to the destination box. +void RipCDDialog::AddDestination() { + int index = ui_->destination->currentIndex(); + QString initial_dir = (!ui_->destination->itemData(index).isNull() + ? ui_->destination->itemData(index).toString() + : QDir::homePath()); + QString dir = + QFileDialog::getExistingDirectory(this, tr("Add folder"), initial_dir); + + if (!dir.isEmpty()) { + // Keep only a finite number of items in the box. + while (ui_->destination->count() >= kMaxDestinationItems) { + ui_->destination->removeItem(0); // The oldest item. + } + AddDestinationDirectory(dir); + } +} + +// Adds a directory to the 'destination' combo box. +void RipCDDialog::AddDestinationDirectory(QString dir) { + QIcon icon = IconLoader::Load("folder"); + QVariant data = QVariant::fromValue(dir); + // Do not insert duplicates. + int duplicate_index = ui_->destination->findData(data); + if (duplicate_index == -1) { + ui_->destination->addItem(icon, dir, data); + ui_->destination->setCurrentIndex(ui_->destination->count() - 1); + } else { + ui_->destination->setCurrentIndex(duplicate_index); + } +} + +void RipCDDialog::SelectAll() { + for (QCheckBox* checkbox : checkboxes_) { + checkbox->setCheckState(Qt::Checked); + } +} + +void RipCDDialog::SelectNone() { + for (QCheckBox* checkbox : checkboxes_) { + checkbox->setCheckState(Qt::Unchecked); + } +} + +void RipCDDialog::InvertSelection() { + for (QCheckBox* checkbox : checkboxes_) { + checkbox->setCheckState(checkbox->isChecked() ? Qt::Unchecked + : Qt::Checked); + } +} + +void RipCDDialog::Finished() { SetWorking(false); } + +void RipCDDialog::Cancelled() { + ui_->progress_bar->setValue(0); + SetWorking(false); +} + +void RipCDDialog::SetupProgressBarLimits(int min, int max) { + ui_->progress_bar->setRange(min, max); +} + +void RipCDDialog::UpdateProgressBar(int progress) { + ui_->progress_bar->setValue(progress); +} + +void RipCDDialog::SetWorking(bool working) { + working_ = working; + rip_button_->setVisible(!working); + cancel_button_->setVisible(working); + close_button_->setVisible(!working); + ui_->input_group->setEnabled(!working); + ui_->output_group->setEnabled(!working); + ui_->progress_group->setVisible(true); +} + +void RipCDDialog::BuildTrackListTable() { + checkboxes_.clear(); + track_names_.clear(); + + int tracks = ripper_->TracksOnDisc(); + + ui_->tableWidget->setRowCount(tracks); + for (int i = 1; i <= tracks; i++) { + QCheckBox* checkbox_i = new QCheckBox(ui_->tableWidget); + checkbox_i->setCheckState(Qt::Checked); + checkboxes_.append(checkbox_i); + ui_->tableWidget->setCellWidget(i - 1, kCheckboxColumn, checkbox_i); + ui_->tableWidget->setCellWidget(i - 1, kTrackNumberColumn, + new QLabel(QString::number(i))); + QString track_title = QString("Track %1").arg(i); + QLineEdit* line_edit_track_title_i = + new QLineEdit(track_title, ui_->tableWidget); + track_names_.append(line_edit_track_title_i); + ui_->tableWidget->setCellWidget(i - 1, kTrackTitleColumn, + line_edit_track_title_i); + } +} + +QString RipCDDialog::GetOutputFileName(const QString& basename) const { + QString path = + ui_->destination->itemData(ui_->destination->currentIndex()).toString(); + QString extension = ui_->format->itemData(ui_->format->currentIndex()) + .value() + .extension_; + return path + '/' + basename + '.' + extension; +} + +QString RipCDDialog::ParseFileFormatString(const QString& file_format, + int track_no) const { + QString to_return = file_format; + to_return.replace(QString("%artist%"), ui_->artistLineEdit->text()); + to_return.replace(QString("%album%"), ui_->albumLineEdit->text()); + to_return.replace(QString("%genre%"), ui_->genreLineEdit->text()); + to_return.replace(QString("%year%"), ui_->yearLineEdit->text()); + to_return.replace(QString("%tracknum%"), QString::number(track_no)); + to_return.replace(QString("%track%"), + track_names_.value(track_no - 1)->text()); + return to_return; +} diff --git a/src/ripper/ripcddialog.h b/src/ripper/ripcddialog.h new file mode 100644 index 000000000..ab6f607aa --- /dev/null +++ b/src/ripper/ripcddialog.h @@ -0,0 +1,80 @@ +/* This file is part of Clementine. + Copyright 2014, Andre Siviero + + Clementine is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Clementine is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Clementine. If not, see . + */ + +#ifndef SRC_RIPPER_RIPCDDIALOG_H_ +#define SRC_RIPPER_RIPCDDIALOG_H_ + +#include +#include +#include + +#include "core/song.h" +#include "core/tagreaderclient.h" + +class QCheckBox; +class QLineEdit; + +class Ripper; +class Ui_RipCDDialog; + +class RipCDDialog : public QDialog { + Q_OBJECT + + public: + explicit RipCDDialog(QWidget* parent = nullptr); + ~RipCDDialog(); + bool CheckCDIOIsValid(); + + protected: + void showEvent(QShowEvent* event); + + private slots: + void ClickedRipButton(); + void Options(); + void AddDestination(); + void SelectAll(); + void SelectNone(); + void InvertSelection(); + void Finished(); + void Cancelled(); + void SetupProgressBarLimits(int min, int max); + void UpdateProgressBar(int progress); + + private: + static const char* kSettingsGroup; + static const int kMaxDestinationItems; + + // Constructs a filename from the given base name with a path taken + // from the ui dialog and an extension that corresponds to the audio + // format chosen in the ui. + void AddDestinationDirectory(QString dir); + void BuildTrackListTable(); + QString GetOutputFileName(const QString& basename) const; + QString ParseFileFormatString(const QString& file_format, int track_no) const; + void SetWorking(bool working); + + QList checkboxes_; + QList track_names_; + QString last_add_dir_; + QPushButton* cancel_button_; + QPushButton* close_button_; + QPushButton* rip_button_; + std::unique_ptr ui_; + Ripper* ripper_; + bool working_; +}; +#endif // SRC_RIPPER_RIPCDDIALOG_H_ diff --git a/src/ui/ripcd.ui b/src/ripper/ripcddialog.ui similarity index 99% rename from src/ui/ripcd.ui rename to src/ripper/ripcddialog.ui index a6831d95b..040f7f54b 100644 --- a/src/ui/ripcd.ui +++ b/src/ripper/ripcddialog.ui @@ -1,7 +1,7 @@ - RipCD - + RipCDDialog + Qt::NonModal diff --git a/src/ripper/ripper.cpp b/src/ripper/ripper.cpp new file mode 100644 index 000000000..405442c85 --- /dev/null +++ b/src/ripper/ripper.cpp @@ -0,0 +1,311 @@ +/* This file is part of Clementine. + Copyright 2014, Andre Siviero + + Clementine is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Clementine is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Clementine. If not, see . + */ + +#include "ripper.h" + +#include +#include +#include + +#include "core/closure.h" +#include "core/logging.h" +#include "core/tagreaderclient.h" +#include "transcoder/transcoder.h" +#include "core/utilities.h" + +// winspool.h defines this :( +#ifdef AddJob +#undef AddJob +#endif + +namespace { +const char kWavHeaderRiffMarker[] = "RIFF"; +const char kWavFileTypeFormatChunk[] = "WAVEfmt "; +const char kWavDataString[] = "data"; +} // namespace + +Ripper::Ripper(QObject* parent) + : QObject(parent), + transcoder_(new Transcoder(this)), + cancel_requested_(false), + finished_success_(0), + finished_failed_(0), + files_tagged_(0) { + cdio_ = cdio_open(NULL, DRIVER_UNKNOWN); + + connect(this, SIGNAL(RippingComplete()), transcoder_, SLOT(Start())); + connect(transcoder_, SIGNAL(JobComplete(QString, QString, bool)), + SLOT(TranscodingJobComplete(QString, QString, bool))); + connect(transcoder_, SIGNAL(AllJobsComplete()), + SLOT(AllTranscodingJobsComplete())); + connect(transcoder_, SIGNAL(LogLine(QString)), SLOT(LogLine(QString))); +} + +Ripper::~Ripper() { cdio_destroy(cdio_); } + +void Ripper::AddTrack(int track_number, const QString& title, + const QString& transcoded_filename, + const TranscoderPreset& preset) { + if (track_number < 1 || track_number > TracksOnDisc()) { + qLog(Warning) << "Invalid track number:" << track_number << "Ignoring"; + return; + } + TrackInformation track(track_number, title, transcoded_filename, preset); + tracks_.append(track); +} + +void Ripper::SetAlbumInformation(const QString& album, const QString& artist, + const QString& genre, int year, int disc, + Song::FileType type) { + album_.album = album; + album_.artist = artist; + album_.genre = genre; + album_.year = year; + album_.disc = disc; + album_.type = type; +} + +int Ripper::TracksOnDisc() const { + int number_of_tracks = cdio_get_num_tracks(cdio_); + // Return zero tracks if there is an error, e.g. no medium found. + if (number_of_tracks == CDIO_INVALID_TRACK) number_of_tracks = 0; + return number_of_tracks; +} + +int Ripper::AddedTracks() const { return tracks_.length(); } + +void Ripper::ClearTracks() { tracks_.clear(); } + +bool Ripper::CheckCDIOIsValid() { + if (cdio_) { + cdio_destroy(cdio_); + } + cdio_ = cdio_open(NULL, DRIVER_UNKNOWN); + // Refresh the status of the cd media. This will prevent unnecessary + // rebuilds of the track list table. + if (cdio_) { + cdio_get_media_changed(cdio_); + } + return cdio_; +} + +bool Ripper::MediaChanged() const { + if (cdio_ && cdio_get_media_changed(cdio_)) + return true; + else + return false; +} + +void Ripper::Start() { + { + QMutexLocker l(&mutex_); + cancel_requested_ = false; + } + SetupProgressInterval(); + + qLog(Debug) << "Ripping" << AddedTracks() << "tracks."; + QtConcurrent::run(this, &Ripper::Rip); +} + +void Ripper::Cancel() { + { + QMutexLocker l(&mutex_); + cancel_requested_ = true; + } + transcoder_->Cancel(); + RemoveTemporaryDirectory(); + emit(Cancelled()); +} + +void Ripper::TranscodingJobComplete(const QString& input, const QString& output, + bool success) { + if (success) + finished_success_++; + else + finished_failed_++; + UpdateProgress(); + + // The the transcoder does not overwrite files. Instead, it changes + // the name of the output file. We need to update the transcoded + // filename for the corresponding track so that we tag the correct + // file later on. + for (QList::iterator it = tracks_.begin(); + it != tracks_.end(); ++it) { + if (it->temporary_filename == input) { + it->transcoded_filename = output; + } + } +} + +void Ripper::AllTranscodingJobsComplete() { + RemoveTemporaryDirectory(); + TagFiles(); +} + +void Ripper::LogLine(const QString& message) { qLog(Debug) << message; } + +/* + * WAV Header documentation + * as taken from: + * http://www.topherlee.com/software/pcm-tut-wavformat.html + * Pos Value Description + * 0-3 | "RIFF" | Marks the file as a riff file. + * | Characters are each 1 byte long. + * 4-7 | File size (integer) | Size of the overall file - 8 bytes, + * | in bytes (32-bit integer). + * 8-11 | "WAVE" | File Type Header. For our purposes, + * | it always equals "WAVE". + * 13-16 | "fmt " | Format chunk marker. Includes trailing null. + * 17-20 | 16 | Length of format data as listed above + * 21-22 | 1 | Type of format (1 is PCM) - 2 byte integer + * 23-24 | 2 | Number of Channels - 2 byte integer + * 25-28 | 44100 | Sample Rate - 32 byte integer. Common values + * | are 44100 (CD), 48000 (DAT). + * | Sample Rate = Number of Samples per second, or Hertz. + * 29-32 | 176400 | (Sample Rate * BitsPerSample * Channels) / 8. + * 33-34 | 4 | (BitsPerSample * Channels) / 8.1 - 8 bit mono2 - 8 bit stereo/16 + * bit mono4 - 16 bit stereo + * 35-36 | 16 | Bits per sample + * 37-40 | "data" | "data" chunk header. + * | Marks the beginning of the data section. + * 41-44 | File size (data) | Size of the data section. + */ +void Ripper::WriteWAVHeader(QFile* stream, int32_t i_bytecount) { + QDataStream data_stream(stream); + data_stream.setByteOrder(QDataStream::LittleEndian); + // sizeof() - 1 to avoid including "\0" in the file too + data_stream.writeRawData(kWavHeaderRiffMarker, + sizeof(kWavHeaderRiffMarker) - 1); /* 0-3 */ + data_stream << qint32(i_bytecount + 44 - 8); /* 4-7 */ + data_stream.writeRawData(kWavFileTypeFormatChunk, + sizeof(kWavFileTypeFormatChunk) - 1); /* 8-15 */ + data_stream << (qint32)16; /* 16-19 */ + data_stream << (qint16)1; /* 20-21 */ + data_stream << (qint16)2; /* 22-23 */ + data_stream << (qint32)44100; /* 24-27 */ + data_stream << (qint32)(44100 * 2 * 2); /* 28-31 */ + data_stream << (qint16)4; /* 32-33 */ + data_stream << (qint16)16; /* 34-35 */ + data_stream.writeRawData(kWavDataString, + sizeof(kWavDataString) - 1); /* 36-39 */ + data_stream << (qint32)i_bytecount; /* 40-43 */ +} + +void Ripper::Rip() { + temporary_directory_ = Utilities::MakeTempDir() + "/"; + finished_success_ = 0; + finished_failed_ = 0; + + // Set up progress bar + UpdateProgress(); + + for (QList::iterator it = tracks_.begin(); + it != tracks_.end(); ++it) { + QString filename = + QString("%1%2.wav").arg(temporary_directory_).arg(it->track_number); + QFile destination_file(filename); + destination_file.open(QIODevice::WriteOnly); + + lsn_t i_first_lsn = cdio_get_track_lsn(cdio_, it->track_number); + lsn_t i_last_lsn = cdio_get_track_last_lsn(cdio_, it->track_number); + WriteWAVHeader(&destination_file, + (i_last_lsn - i_first_lsn + 1) * CDIO_CD_FRAMESIZE_RAW); + + QByteArray buffered_input_bytes(CDIO_CD_FRAMESIZE_RAW, '\0'); + for (lsn_t i_cursor = i_first_lsn; i_cursor <= i_last_lsn; i_cursor++) { + { + QMutexLocker l(&mutex_); + if (cancel_requested_) { + qLog(Debug) << "CD ripping canceled."; + return; + } + } + if (cdio_read_audio_sector(cdio_, buffered_input_bytes.data(), + i_cursor) == DRIVER_OP_SUCCESS) { + destination_file.write(buffered_input_bytes.data(), + buffered_input_bytes.size()); + } else { + qLog(Error) << "CD read error"; + break; + } + } + finished_success_++; + UpdateProgress(); + + it->temporary_filename = filename; + transcoder_->AddJob(it->temporary_filename, it->preset, + it->transcoded_filename); + } + emit(RippingComplete()); +} + +// The progress interval is [0, 200*AddedTracks()], where the first +// half corresponds to the CD ripping and the second half corresponds +// to the transcoding. +void Ripper::SetupProgressInterval() { + int max = AddedTracks() * 2 * 100; + emit ProgressInterval(0, max); +} + +void Ripper::UpdateProgress() { + int progress = (finished_success_ + finished_failed_) * 100; + QMap current_jobs = transcoder_->GetProgress(); + for (float value : current_jobs.values()) { + progress += qBound(0, static_cast(value * 100), 99); + } + emit Progress(progress); + qLog(Debug) << "Progress:" << progress; +} + +void Ripper::RemoveTemporaryDirectory() { + if (!temporary_directory_.isEmpty()) + Utilities::RemoveRecursive(temporary_directory_); + temporary_directory_.clear(); +} + +void Ripper::TagFiles() { + files_tagged_ = 0; + for (const TrackInformation& track : tracks_) { + Song song; + song.InitFromFilePartial(track.transcoded_filename); + song.set_track(track.track_number); + song.set_title(track.title); + song.set_album(album_.album); + song.set_artist(album_.artist); + song.set_genre(album_.genre); + song.set_year(album_.year); + song.set_disc(album_.disc); + song.set_filetype(album_.type); + + TagReaderReply* reply = + TagReaderClient::Instance()->SaveFile(song.url().toLocalFile(), song); + NewClosure(reply, SIGNAL(Finished(bool)), this, + SLOT(FileTagged(TagReaderReply*)), reply); + } +} + +void Ripper::FileTagged(TagReaderReply* reply) { + files_tagged_++; + qLog(Debug) << "Tagged" << files_tagged_ << "of" << tracks_.length() + << "files"; + if (files_tagged_ == tracks_.length()) { + qLog(Debug) << "CD ripper finished."; + emit(Finished()); + } + + reply->deleteLater(); +} diff --git a/src/ripper/ripper.h b/src/ripper/ripper.h new file mode 100644 index 000000000..74ce752f6 --- /dev/null +++ b/src/ripper/ripper.h @@ -0,0 +1,132 @@ +/* This file is part of Clementine. + Copyright 2014, Andre Siviero + + Clementine is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Clementine is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Clementine. If not, see . + */ + +#ifndef SRC_RIPPER_RIPPER_H_ +#define SRC_RIPPER_RIPPER_H_ + +#include +#include +#include + +#include "core/song.h" +#include "core/tagreaderclient.h" +#include "transcoder/transcoder.h" + +class QFile; + +// Rips selected tracks from an audio CD, transcodes them to a chosen +// format, and finally tags the files with the supplied metadata. +// +// Usage: Add tracks with AddTrack() and album metadata with +// SetAlbumInformation(). Then start the ripper with Start(). The ripper +// emits the Finished() signal when it's done or the Cancelled() +// signal if the ripping has been cancelled. +class Ripper : public QObject { + Q_OBJECT + + public: + explicit Ripper(QObject* parent = nullptr); + ~Ripper(); + + // Adds a track to the rip list if the track number corresponds to a + // track on the audio cd. The track will transcoded according to the + // chosen TranscoderPreset. + void AddTrack(int track_number, const QString& title, + const QString& transcoded_filename, + const TranscoderPreset& preset); + // Sets album metadata. This information is used when tagging the + // final files. + void SetAlbumInformation(const QString& album, const QString& artist, + const QString& genre, int year, int disc, + Song::FileType type); + // Returns the number of audio tracks on the disc. + int TracksOnDisc() const; + // Returns the number of tracks added to the rip list. + int AddedTracks() const; + // Clears the rip list. + void ClearTracks(); + // Returns true if a cd device was successfully opened. + bool CheckCDIOIsValid(); + // Returns true if the cd media has changed. + bool MediaChanged() const; + +signals: + void Finished(); + void Cancelled(); + void ProgressInterval(int min, int max); + void Progress(int progress); + void RippingComplete(); + + public slots: + void Start(); + void Cancel(); + + private slots: + void TranscodingJobComplete(const QString& input, const QString& output, + bool success); + void AllTranscodingJobsComplete(); + void LogLine(const QString& message); + void FileTagged(TagReaderReply* reply); + + private: + struct TrackInformation { + TrackInformation(int track_number, const QString& title, + const QString& transcoded_filename, + const TranscoderPreset& preset) + : track_number(track_number), + title(title), + transcoded_filename(transcoded_filename), + preset(preset) {} + + int track_number; + QString title; + QString transcoded_filename; + TranscoderPreset preset; + QString temporary_filename; + }; + + struct AlbumInformation { + AlbumInformation() : year(0), disc(0), type(Song::Type_Unknown) {} + + QString album; + QString artist; + QString genre; + int year; + int disc; + Song::FileType type; + }; + + void WriteWAVHeader(QFile* stream, int32_t i_bytecount); + void Rip(); + void SetupProgressInterval(); + void UpdateProgress(); + void RemoveTemporaryDirectory(); + void TagFiles(); + + CdIo_t* cdio_; + Transcoder* transcoder_; + QString temporary_directory_; + bool cancel_requested_; + QMutex mutex_; + int finished_success_; + int finished_failed_; + int files_tagged_; + QList tracks_; + AlbumInformation album_; +}; + +#endif // SRC_RIPPER_RIPPER_H_ diff --git a/src/ui/mainwindow.cpp b/src/ui/mainwindow.cpp index 736762b2a..fd81eaa75 100644 --- a/src/ui/mainwindow.cpp +++ b/src/ui/mainwindow.cpp @@ -95,6 +95,9 @@ #include "playlist/songplaylistitem.h" #include "playlistparsers/playlistparser.h" #include "internet/podcasts/podcastservice.h" +#ifdef HAVE_AUDIOCD +#include "ripper/ripcddialog.h" +#endif #include "smartplaylists/generator.h" #include "smartplaylists/generatormimedata.h" #include "songinfo/artistinfoview.h" @@ -110,9 +113,6 @@ #include "ui/organisedialog.h" #include "ui/organiseerrordialog.h" #include "ui/qtsystemtrayicon.h" -#ifdef HAVE_AUDIOCD -#include "ui/ripcd.h" -#endif #include "ui/settingsdialog.h" #include "ui/systemtrayicon.h" #include "ui/trackselectiondialog.h" @@ -312,6 +312,7 @@ MainWindow::MainWindow(Application* app, SystemTrayIcon* tray_icon, OSD* osd, ui_->action_quit->setIcon(IconLoader::Load("application-exit")); ui_->action_remove_from_playlist->setIcon(IconLoader::Load("list-remove")); ui_->action_repeat_mode->setIcon(IconLoader::Load("media-playlist-repeat")); + ui_->action_rip_audio_cd->setIcon(IconLoader::Load("media-optical")); ui_->action_shuffle->setIcon(IconLoader::Load("x-clementine-shuffle")); ui_->action_shuffle_mode->setIcon(IconLoader::Load("media-playlist-shuffle")); ui_->action_stop->setIcon(IconLoader::Load("media-playback-stop")); @@ -389,7 +390,8 @@ MainWindow::MainWindow(Application* app, SystemTrayIcon* tray_icon, OSD* osd, connect(ui_->action_open_media, SIGNAL(triggered()), SLOT(AddFile())); connect(ui_->action_open_cd, SIGNAL(triggered()), SLOT(AddCDTracks())); #ifdef HAVE_AUDIOCD - connect(ui_->action_rip_audio_cd, SIGNAL(triggered()), SLOT(OpenRipCD())); + connect(ui_->action_rip_audio_cd, SIGNAL(triggered()), + SLOT(OpenRipCDDialog())); #else ui_->action_rip_audio_cd->setVisible(false); #endif @@ -1883,13 +1885,13 @@ void MainWindow::AddStreamAccepted() { AddToPlaylist(data); } -void MainWindow::OpenRipCD() { +void MainWindow::OpenRipCDDialog() { #ifdef HAVE_AUDIOCD - if (!rip_cd_) { - rip_cd_.reset(new RipCD); + if (!rip_cd_dialog_) { + rip_cd_dialog_.reset(new RipCDDialog); } - if (rip_cd_->CheckCDIOIsValid()) { - rip_cd_->show(); + if (rip_cd_dialog_->CheckCDIOIsValid()) { + rip_cd_dialog_->show(); } else { QMessageBox cdio_fail(QMessageBox::Critical, tr("Error"), tr("Failed reading CD drive")); diff --git a/src/ui/mainwindow.h b/src/ui/mainwindow.h index 71e286259..b1dd0b660 100644 --- a/src/ui/mainwindow.h +++ b/src/ui/mainwindow.h @@ -68,7 +68,7 @@ class InternetItem; class InternetModel; class InternetViewContainer; class Remote; -class RipCD; +class RipCDDialog; class Song; class SongInfoBase; class SongInfoView; @@ -218,7 +218,7 @@ signals: void AddFolder(); void AddStream(); void AddStreamAccepted(); - void OpenRipCD(); + void OpenRipCDDialog(); void AddCDTracks(); void AddPodcast(); @@ -297,7 +297,7 @@ signals: LibraryViewContainer* library_view_; FileView* file_view_; #ifdef HAVE_AUDIOCD - std::unique_ptr rip_cd_; + std::unique_ptr rip_cd_dialog_; #endif PlaylistListContainer* playlist_list_; InternetViewContainer* internet_view_; diff --git a/src/ui/mainwindow.ui b/src/ui/mainwindow.ui index 84b819bb9..343c24c89 100644 --- a/src/ui/mainwindow.ui +++ b/src/ui/mainwindow.ui @@ -411,7 +411,6 @@ - @@ -473,6 +472,7 @@ + @@ -847,7 +847,7 @@ - Rip audio CD... + Rip audio CD diff --git a/src/ui/ripcd.cpp b/src/ui/ripcd.cpp deleted file mode 100644 index bf8a329a8..000000000 --- a/src/ui/ripcd.cpp +++ /dev/null @@ -1,520 +0,0 @@ -/* This file is part of Clementine. - Copyright 2014, Andre Siviero - - Clementine is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Clementine is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with Clementine. If not, see . - */ - -#include "ripcd.h" -#include "config.h" -#include "ui_ripcd.h" -#include "transcoder/transcoder.h" -#include "transcoder/transcoderoptionsdialog.h" -#include "ui/iconloader.h" -#include "core/closure.h" -#include "core/logging.h" -#include "core/tagreaderclient.h" -#include "core/utilities.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -// winspool.h defines this :( -#ifdef AddJob -#undef AddJob -#endif - -namespace { -bool ComparePresetsByName(const TranscoderPreset& left, - const TranscoderPreset& right) { - return left.name_ < right.name_; -} - -const char kWavHeaderRiffMarker[] = "RIFF"; -const char kWavFileTypeFormatChunk[] = "WAVEfmt "; -const char kWavDataString[] = "data"; - -const int kCheckboxColumn = 0; -const int kTrackNumberColumn = 1; -const int kTrackTitleColumn = 2; -} -const char* RipCD::kSettingsGroup = "Transcoder"; -const int RipCD::kProgressInterval = 500; -const int RipCD::kMaxDestinationItems = 10; - -RipCD::RipCD(QWidget* parent) - : QDialog(parent), - transcoder_(new Transcoder(this)), - queued_(0), - finished_success_(0), - finished_failed_(0), - ui_(new Ui_RipCD), - cancel_requested_(false), - files_tagged_(0) { - cdio_ = cdio_open(NULL, DRIVER_UNKNOWN); - // Init - ui_->setupUi(this); - - // Set column widths in the QTableWidget. - ui_->tableWidget->horizontalHeader()->setResizeMode( - kCheckboxColumn, QHeaderView::ResizeToContents); - ui_->tableWidget->horizontalHeader()->setResizeMode( - kTrackNumberColumn, QHeaderView::ResizeToContents); - ui_->tableWidget->horizontalHeader()->setResizeMode(kTrackTitleColumn, - QHeaderView::Stretch); - - // Add a rip button - rip_button_ = ui_->button_box->addButton(tr("Start ripping"), - QDialogButtonBox::ActionRole); - cancel_button_ = ui_->button_box->button(QDialogButtonBox::Cancel); - close_button_ = ui_->button_box->button(QDialogButtonBox::Close); - - // Hide elements - cancel_button_->hide(); - ui_->progress_group->hide(); - - connect(ui_->select_all_button, SIGNAL(clicked()), SLOT(SelectAll())); - connect(ui_->select_none_button, SIGNAL(clicked()), SLOT(SelectNone())); - connect(ui_->invert_selection_button, SIGNAL(clicked()), - SLOT(InvertSelection())); - connect(rip_button_, SIGNAL(clicked()), SLOT(ClickedRipButton())); - connect(cancel_button_, SIGNAL(clicked()), SLOT(Cancel())); - connect(close_button_, SIGNAL(clicked()), SLOT(hide())); - - connect(transcoder_, SIGNAL(JobComplete(QString, QString, bool)), - SLOT(TranscodingJobComplete(QString, QString, bool))); - connect(transcoder_, SIGNAL(AllJobsComplete()), - SLOT(AllTranscodingJobsComplete())); - connect(transcoder_, SIGNAL(LogLine(QString)), SLOT(LogLine(QString))); - connect(this, SIGNAL(RippingComplete()), SLOT(ThreadedTranscoding())); - connect(this, SIGNAL(SignalUpdateProgress()), SLOT(UpdateProgress())); - - connect(ui_->options, SIGNAL(clicked()), SLOT(Options())); - connect(ui_->select, SIGNAL(clicked()), SLOT(AddDestination())); - - setWindowTitle(tr("Rip CD")); - AddDestinationDirectory(QDir::homePath()); - - // Get presets - QList presets = Transcoder::GetAllPresets(); - qSort(presets.begin(), presets.end(), ComparePresetsByName); - for (const TranscoderPreset& preset : presets) { - ui_->format->addItem( - QString("%1 (.%2)").arg(preset.name_, preset.extension_), - QVariant::fromValue(preset)); - } - - // Load settings - QSettings s; - s.beginGroup(kSettingsGroup); - last_add_dir_ = s.value("last_add_dir", QDir::homePath()).toString(); - - QString last_output_format = s.value("last_output_format", "ogg").toString(); - for (int i = 0; i < ui_->format->count(); ++i) { - if (last_output_format == - ui_->format->itemData(i).value().extension_) { - ui_->format->setCurrentIndex(i); - break; - } - } - - ui_->progress_bar->setValue(0); - ui_->progress_bar->setMaximum(100); -} - -RipCD::~RipCD() { cdio_destroy(cdio_); } - -/* - * WAV Header documentation - * as taken from: - * http://www.topherlee.com/software/pcm-tut-wavformat.html - * Pos Value Description - * 0-3 | "RIFF" | Marks the file as a riff file. - * | Characters are each 1 byte long. - * 4-7 | File size (integer) | Size of the overall file - 8 bytes, - * | in bytes (32-bit integer). - * 8-11 | "WAVE" | File Type Header. For our purposes, - * | it always equals "WAVE". - * 13-16 | "fmt " | Format chunk marker. Includes trailing null. - * 17-20 | 16 | Length of format data as listed above - * 21-22 | 1 | Type of format (1 is PCM) - 2 byte integer - * 23-24 | 2 | Number of Channels - 2 byte integer - * 25-28 | 44100 | Sample Rate - 32 byte integer. Common values - * | are 44100 (CD), 48000 (DAT). - * | Sample Rate = Number of Samples per second, or Hertz. - * 29-32 | 176400 | (Sample Rate * BitsPerSample * Channels) / 8. - * 33-34 | 4 | (BitsPerSample * Channels) / 8.1 - 8 bit mono2 - 8 bit stereo/16 - * bit mono4 - 16 bit stereo - * 35-36 | 16 | Bits per sample - * 37-40 | "data" | "data" chunk header. - * | Marks the beginning of the data section. - * 41-44 | File size (data) | Size of the data section. - */ -void RipCD::WriteWAVHeader(QFile* stream, int32_t i_bytecount) { - QDataStream data_stream(stream); - data_stream.setByteOrder(QDataStream::LittleEndian); - // sizeof() - 1 to avoid including "\0" in the file too - data_stream.writeRawData(kWavHeaderRiffMarker, - sizeof(kWavHeaderRiffMarker) - 1); /* 0-3 */ - data_stream << qint32(i_bytecount + 44 - 8); /* 4-7 */ - data_stream.writeRawData(kWavFileTypeFormatChunk, - sizeof(kWavFileTypeFormatChunk) - 1); /* 8-15 */ - data_stream << (qint32)16; /* 16-19 */ - data_stream << (qint16)1; /* 20-21 */ - data_stream << (qint16)2; /* 22-23 */ - data_stream << (qint32)44100; /* 24-27 */ - data_stream << (qint32)(44100 * 2 * 2); /* 28-31 */ - data_stream << (qint16)4; /* 32-33 */ - data_stream << (qint16)16; /* 34-35 */ - data_stream.writeRawData(kWavDataString, - sizeof(kWavDataString) - 1); /* 36-39 */ - data_stream << (qint32)i_bytecount; /* 40-43 */ -} - -int RipCD::NumTracksToRip() { - int k = 0; - for (int i = 0; i < checkboxes_.length(); i++) { - if (checkboxes_.value(i)->isChecked()) { - k++; - } - } - return k; -} - -void RipCD::ThreadClickedRipButton() { - temporary_directory_ = Utilities::MakeTempDir() + "/"; - finished_success_ = 0; - finished_failed_ = 0; - ui_->progress_bar->setMaximum(NumTracksToRip() * 2 * 100); - - // Set up progress bar - emit(SignalUpdateProgress()); - - for (const TrackInformation& track : tracks_) { - QString filename = - QString("%1%2.wav").arg(temporary_directory_).arg(track.track_number); - QFile* destination_file = new QFile(filename); - destination_file->open(QIODevice::WriteOnly); - - lsn_t i_first_lsn = cdio_get_track_lsn(cdio_, track.track_number); - lsn_t i_last_lsn = cdio_get_track_last_lsn(cdio_, track.track_number); - WriteWAVHeader(destination_file, - (i_last_lsn - i_first_lsn + 1) * CDIO_CD_FRAMESIZE_RAW); - - QByteArray buffered_input_bytes(CDIO_CD_FRAMESIZE_RAW, '\0'); - for (lsn_t i_cursor = i_first_lsn; i_cursor <= i_last_lsn; i_cursor++) { - { - QMutexLocker l(&mutex_); - if (cancel_requested_) { - qLog(Debug) << "CD ripping canceled."; - return; - } - } - if (cdio_read_audio_sector(cdio_, buffered_input_bytes.data(), - i_cursor) == DRIVER_OP_SUCCESS) { - destination_file->write(buffered_input_bytes.data(), - buffered_input_bytes.size()); - } else { - qLog(Error) << "CD read error"; - break; - } - } - finished_success_++; - emit(SignalUpdateProgress()); - TranscoderPreset preset = ui_->format->itemData(ui_->format->currentIndex()) - .value(); - - transcoder_->AddJob(filename, preset, track.transcoded_filename); - } - emit(RippingComplete()); -} - -QString RipCD::GetOutputFileName(const QString& basename) const { - QString path = - ui_->destination->itemData(ui_->destination->currentIndex()).toString(); - QString extension = ui_->format->itemData(ui_->format->currentIndex()) - .value() - .extension_; - return path + '/' + basename + '.' + extension; -} - -QString RipCD::ParseFileFormatString(const QString& file_format, - int track_no) const { - QString to_return = file_format; - to_return.replace(QString("%artist%"), ui_->artistLineEdit->text()); - to_return.replace(QString("%album%"), ui_->albumLineEdit->text()); - to_return.replace(QString("%genre%"), ui_->genreLineEdit->text()); - to_return.replace(QString("%year%"), ui_->yearLineEdit->text()); - to_return.replace(QString("%tracknum%"), QString::number(track_no)); - to_return.replace(QString("%track%"), - track_names_.value(track_no - 1)->text()); - return to_return; -} - -void RipCD::UpdateProgress() { - int progress = (finished_success_ + finished_failed_) * 100; - QMap current_jobs = transcoder_->GetProgress(); - for (float value : current_jobs.values()) { - progress += qBound(0, static_cast(value * 100), 99); - } - - ui_->progress_bar->setValue(progress); -} - -void RipCD::ThreadedTranscoding() { - transcoder_->Start(); - TranscoderPreset preset = ui_->format->itemData(ui_->format->currentIndex()) - .value(); - // Save the last output format - QSettings s; - s.beginGroup(kSettingsGroup); - s.setValue("last_output_format", preset.extension_); -} - -void RipCD::ClickedRipButton() { - if (cdio_ && cdio_get_media_changed(cdio_)) { - QMessageBox cdio_fail(QMessageBox::Critical, tr("Error Ripping CD"), - tr("Media has changed. Reloading")); - cdio_fail.exec(); - if (CheckCDIOIsValid()) { - BuildTrackListTable(); - } else { - ui_->tableWidget->clearContents(); - } - return; - } - - // Add tracks to the rip list. - tracks_.clear(); - for (int i = 1; i <= i_tracks_; ++i) { - if (!checkboxes_.value(i - 1)->isChecked()) { - continue; - } - QString transcoded_filename = GetOutputFileName( - ParseFileFormatString(ui_->format_filename->text(), i)); - QString title = track_names_.value(i - 1)->text(); - AddTrack(i, title, transcoded_filename); - } - - // Do nothing if no tracks are selected. - if (tracks_.isEmpty()) - return; - - // Start ripping. - SetWorking(true); - { - QMutexLocker l(&mutex_); - cancel_requested_ = false; - } - QtConcurrent::run(this, &RipCD::ThreadClickedRipButton); -} - -void RipCD::AddTrack(int track_number, const QString& title, - const QString& transcoded_filename) { - TrackInformation track(track_number, title, transcoded_filename); - tracks_.append(track); -} - -void RipCD::TranscodingJobComplete(const QString& input, const QString& output, bool success) { - (*(success ? &finished_success_ : &finished_failed_))++; - emit(SignalUpdateProgress()); -} - -void RipCD::AllTranscodingJobsComplete() { - RemoveTemporaryDirectory(); - - // Save tags. - TranscoderPreset preset = ui_->format->itemData(ui_->format->currentIndex()) - .value(); - AlbumInformation album( - ui_->albumLineEdit->text(), ui_->artistLineEdit->text(), - ui_->genreLineEdit->text(), ui_->yearLineEdit->text().toInt(), - ui_->discLineEdit->text().toInt(), preset.type_); - TagFiles(album, tracks_); -} - -void RipCD::TagFiles(const AlbumInformation& album, - const QList& tracks) { - files_tagged_ = 0; - for (const TrackInformation& track : tracks_) { - Song song; - song.InitFromFilePartial(track.transcoded_filename); - song.set_track(track.track_number); - song.set_title(track.title); - song.set_album(album.album); - song.set_artist(album.artist); - song.set_genre(album.genre); - song.set_year(album.year); - song.set_disc(album.disc); - song.set_filetype(album.type); - - TagReaderReply* reply = - TagReaderClient::Instance()->SaveFile(song.url().toLocalFile(), song); - NewClosure(reply, SIGNAL(Finished(bool)), this, - SLOT(FileTagged(TagReaderReply*)), reply); - } -} - -void RipCD::FileTagged(TagReaderReply* reply) { - files_tagged_++; - qLog(Debug) << "Tagged" << files_tagged_ << "of" << tracks_.length() - << "files"; - - // Stop working if all files are tagged. - if (files_tagged_ == tracks_.length()) { - qLog(Debug) << "CD ripper finished."; - SetWorking(false); - } - - reply->deleteLater(); -} - -void RipCD::Options() { - TranscoderPreset preset = ui_->format->itemData(ui_->format->currentIndex()) - .value(); - - TranscoderOptionsDialog dialog(preset.type_, this); - if (dialog.is_valid()) { - dialog.exec(); - } -} - -// Adds a folder to the destination box. -void RipCD::AddDestination() { - int index = ui_->destination->currentIndex(); - QString initial_dir = (!ui_->destination->itemData(index).isNull() - ? ui_->destination->itemData(index).toString() - : QDir::homePath()); - QString dir = - QFileDialog::getExistingDirectory(this, tr("Add folder"), initial_dir); - - if (!dir.isEmpty()) { - // Keep only a finite number of items in the box. - while (ui_->destination->count() >= kMaxDestinationItems) { - ui_->destination->removeItem(0); // The oldest item. - } - AddDestinationDirectory(dir); - } -} - -// Adds a directory to the 'destination' combo box. -void RipCD::AddDestinationDirectory(QString dir) { - QIcon icon = IconLoader::Load("folder"); - QVariant data = QVariant::fromValue(dir); - // Do not insert duplicates. - int duplicate_index = ui_->destination->findData(data); - if (duplicate_index == -1) { - ui_->destination->addItem(icon, dir, data); - ui_->destination->setCurrentIndex(ui_->destination->count() - 1); - } else { - ui_->destination->setCurrentIndex(duplicate_index); - } -} - -void RipCD::Cancel() { - { - QMutexLocker l(&mutex_); - cancel_requested_ = true; - } - ui_->progress_bar->setValue(0); - transcoder_->Cancel(); - RemoveTemporaryDirectory(); - SetWorking(false); -} - -bool RipCD::CheckCDIOIsValid() { - if (cdio_) { - cdio_destroy(cdio_); - } - cdio_ = cdio_open(NULL, DRIVER_UNKNOWN); - // Refresh the status of the cd media. This will prevent unnecessary - // rebuilds of the track list table. - cdio_get_media_changed(cdio_); - return cdio_; -} - -void RipCD::SetWorking(bool working) { - rip_button_->setVisible(!working); - cancel_button_->setVisible(working); - close_button_->setVisible(!working); - ui_->input_group->setEnabled(!working); - ui_->output_group->setEnabled(!working); - ui_->progress_group->setVisible(true); -} - -void RipCD::SelectAll() { - for (QCheckBox* checkbox : checkboxes_) { - checkbox->setCheckState(Qt::Checked); - } -} - -void RipCD::SelectNone() { - for (QCheckBox* checkbox : checkboxes_) { - checkbox->setCheckState(Qt::Unchecked); - } -} - -void RipCD::InvertSelection() { - for (QCheckBox* checkbox : checkboxes_) { - if (checkbox->isChecked()) { - checkbox->setCheckState(Qt::Unchecked); - } else { - checkbox->setCheckState(Qt::Checked); - } - } -} - -void RipCD::RemoveTemporaryDirectory() { - if (!temporary_directory_.isEmpty()) - Utilities::RemoveRecursive(temporary_directory_); - temporary_directory_.clear(); -} - -void RipCD::BuildTrackListTable() { - checkboxes_.clear(); - track_names_.clear(); - - i_tracks_ = cdio_get_num_tracks(cdio_); - // Build an empty table if there is an error, e.g. no medium found. - if (i_tracks_ == CDIO_INVALID_TRACK) - i_tracks_ = 0; - - ui_->tableWidget->setRowCount(i_tracks_); - for (int i = 1; i <= i_tracks_; i++) { - QCheckBox* checkbox_i = new QCheckBox(ui_->tableWidget); - checkbox_i->setCheckState(Qt::Checked); - checkboxes_.append(checkbox_i); - ui_->tableWidget->setCellWidget(i - 1, kCheckboxColumn, checkbox_i); - ui_->tableWidget->setCellWidget(i - 1, kTrackNumberColumn, - new QLabel(QString::number(i))); - QString track_title = QString("Track %1").arg(i); - QLineEdit* line_edit_track_title_i = - new QLineEdit(track_title, ui_->tableWidget); - track_names_.append(line_edit_track_title_i); - ui_->tableWidget->setCellWidget(i - 1, kTrackTitleColumn, - line_edit_track_title_i); - } -} - -void RipCD::LogLine(const QString& message) { qLog(Debug) << message; } - -void RipCD::showEvent(QShowEvent* event) { BuildTrackListTable(); } diff --git a/src/ui/ripcd.h b/src/ui/ripcd.h deleted file mode 100644 index 71bb40746..000000000 --- a/src/ui/ripcd.h +++ /dev/null @@ -1,139 +0,0 @@ -/* This file is part of Clementine. - Copyright 2014, Andre Siviero - - Clementine is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Clementine is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with Clementine. If not, see . - */ - -#ifndef SRC_UI_RIPCD_H_ -#define SRC_UI_RIPCD_H_ - -#include -#include -#include -#include -#include -#include -#include "core/song.h" -#include "core/tagreaderclient.h" -#include "ui_ripcd.h" -#include - -class Ui_RipCD; -class Transcoder; - -struct TranscoderPreset; - -class RipCD : public QDialog { - Q_OBJECT - - public: - explicit RipCD(QWidget* parent = nullptr); - ~RipCD(); - bool CheckCDIOIsValid(); - void BuildTrackListTable(); - - protected: - void showEvent(QShowEvent* event); - - private: - struct TrackInformation { - TrackInformation(int track_number, const QString& title, - const QString& transcoded_filename) - : track_number(track_number), - title(title), - transcoded_filename(transcoded_filename) {} - - int track_number; - QString title; - QString transcoded_filename; - }; - - struct AlbumInformation { - AlbumInformation(const QString& album, const QString& artist, - const QString& genre, int year, int disc, - Song::FileType type) - : album(album), - artist(artist), - genre(genre), - year(year), - disc(disc), - type(type) {} - - QString album; - QString artist; - QString genre; - int year; - int disc; - Song::FileType type; - }; - - static const char* kSettingsGroup; - static const int kProgressInterval; - static const int kMaxDestinationItems; - Transcoder* transcoder_; - int queued_; - int finished_success_; - int finished_failed_; - track_t i_tracks_; - std::unique_ptr ui_; - CdIo_t* cdio_; - QList checkboxes_; - QList track_names_; - QList tracks_; - QString last_add_dir_; - QPushButton* cancel_button_; - QPushButton* close_button_; - QPushButton* rip_button_; - QString temporary_directory_; - bool cancel_requested_; - QMutex mutex_; - int files_tagged_; - - void WriteWAVHeader(QFile* stream, int32_t i_bytecount); - int NumTracksToRip(); - void AddTrack(int track_number, const QString& title, - const QString& transcoded_filename); - void ThreadClickedRipButton(); - // Constructs a filename from the given base name with a path taken - // from the ui dialog and an extension that corresponds to the audio - // format chosen in the ui. - QString GetOutputFileName(const QString& basename) const; - QString ParseFileFormatString(const QString& file_format, int track_no) const; - void SetWorking(bool working); - void AddDestinationDirectory(QString dir); - void RemoveTemporaryDirectory(); - void TagFiles(const AlbumInformation& album, - const QList& tracks); - -signals: - void RippingComplete(); - void SignalUpdateProgress(); - - private slots: - void UpdateProgress(); - void ThreadedTranscoding(); - void ClickedRipButton(); - void TranscodingJobComplete(const QString& input, const QString& output, bool success); - void AllTranscodingJobsComplete(); - void FileTagged(TagReaderReply* reply); - void Options(); - void AddDestination(); - void Cancel(); - void SelectAll(); - void SelectNone(); - void InvertSelection(); - void LogLine(const QString& message); -}; - -#endif // SRC_UI_RIPCD_H_