Avoid having similar filenames when organising songs (number them instead)
This commit is contained in:
parent
96075faf88
commit
a349a96f5a
|
@ -36,7 +36,7 @@ const int Organise::kTranscodeProgressInterval = 500;
|
||||||
Organise::Organise(TaskManager* task_manager,
|
Organise::Organise(TaskManager* task_manager,
|
||||||
boost::shared_ptr<MusicStorage> destination,
|
boost::shared_ptr<MusicStorage> destination,
|
||||||
const OrganiseFormat &format, bool copy, bool overwrite,
|
const OrganiseFormat &format, bool copy, bool overwrite,
|
||||||
const SongList& songs, bool eject_after)
|
const NewSongInfoList& songs_info, bool eject_after)
|
||||||
: thread_(NULL),
|
: thread_(NULL),
|
||||||
task_manager_(task_manager),
|
task_manager_(task_manager),
|
||||||
transcoder_(new Transcoder(this)),
|
transcoder_(new Transcoder(this)),
|
||||||
|
@ -45,7 +45,7 @@ Organise::Organise(TaskManager* task_manager,
|
||||||
copy_(copy),
|
copy_(copy),
|
||||||
overwrite_(overwrite),
|
overwrite_(overwrite),
|
||||||
eject_after_(eject_after),
|
eject_after_(eject_after),
|
||||||
task_count_(songs.count()),
|
task_count_(songs_info.count()),
|
||||||
transcode_suffix_(1),
|
transcode_suffix_(1),
|
||||||
tasks_complete_(0),
|
tasks_complete_(0),
|
||||||
started_(false),
|
started_(false),
|
||||||
|
@ -54,8 +54,8 @@ Organise::Organise(TaskManager* task_manager,
|
||||||
{
|
{
|
||||||
original_thread_ = thread();
|
original_thread_ = thread();
|
||||||
|
|
||||||
for (const Song& song : songs) {
|
for (const NewSongInfo& song_info : songs_info) {
|
||||||
tasks_pending_ << Task(song);
|
tasks_pending_ << Task(song_info);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -81,7 +81,7 @@ void Organise::ProcessSomeFiles() {
|
||||||
if (!destination_->StartCopy(&supported_filetypes_)) {
|
if (!destination_->StartCopy(&supported_filetypes_)) {
|
||||||
// Failed to start - mark everything as failed :(
|
// Failed to start - mark everything as failed :(
|
||||||
for (const Task& task : tasks_pending_)
|
for (const Task& task : tasks_pending_)
|
||||||
files_with_errors_ << task.song_.url().toLocalFile();
|
files_with_errors_ << task.song_info_.song_.url().toLocalFile();
|
||||||
tasks_pending_.clear();
|
tasks_pending_.clear();
|
||||||
}
|
}
|
||||||
started_ = true;
|
started_ = true;
|
||||||
|
@ -124,10 +124,10 @@ void Organise::ProcessSomeFiles() {
|
||||||
break;
|
break;
|
||||||
|
|
||||||
Task task = tasks_pending_.takeFirst();
|
Task task = tasks_pending_.takeFirst();
|
||||||
qLog(Info) << "Processing" << task.song_.url().toLocalFile();
|
qLog(Info) << "Processing" << task.song_info_.song_.url().toLocalFile();
|
||||||
|
|
||||||
// Use a Song instead of a tag reader
|
// Use a Song instead of a tag reader
|
||||||
Song song = task.song_;
|
Song song = task.song_info_.song_;
|
||||||
if (!song.is_valid())
|
if (!song.is_valid())
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
@ -157,14 +157,14 @@ void Organise::ProcessSomeFiles() {
|
||||||
QString::number(transcode_suffix_++);
|
QString::number(transcode_suffix_++);
|
||||||
task.new_extension_ = preset.extension_;
|
task.new_extension_ = preset.extension_;
|
||||||
task.new_filetype_ = dest_type;
|
task.new_filetype_ = dest_type;
|
||||||
tasks_transcoding_[task.song_.url().toLocalFile()] = task;
|
tasks_transcoding_[task.song_info_.song_.url().toLocalFile()] = task;
|
||||||
|
|
||||||
qLog(Debug) << "Transcoding to" << task.transcoded_filename_;
|
qLog(Debug) << "Transcoding to" << task.transcoded_filename_;
|
||||||
|
|
||||||
// Start the transcoding - this will happen in the background and
|
// Start the transcoding - this will happen in the background and
|
||||||
// FileTranscoded() will get called when it's done. At that point the
|
// FileTranscoded() will get called when it's done. At that point the
|
||||||
// task will get re-added to the pending queue with the new filename.
|
// task will get re-added to the pending queue with the new filename.
|
||||||
transcoder_->AddJob(task.song_.url().toLocalFile(), preset, task.transcoded_filename_);
|
transcoder_->AddJob(task.song_info_.song_.url().toLocalFile(), preset, task.transcoded_filename_);
|
||||||
transcoder_->Start();
|
transcoder_->Start();
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -172,8 +172,8 @@ void Organise::ProcessSomeFiles() {
|
||||||
|
|
||||||
MusicStorage::CopyJob job;
|
MusicStorage::CopyJob job;
|
||||||
job.source_ = task.transcoded_filename_.isEmpty() ?
|
job.source_ = task.transcoded_filename_.isEmpty() ?
|
||||||
task.song_.url().toLocalFile() : task.transcoded_filename_;
|
task.song_info_.song_.url().toLocalFile() : task.transcoded_filename_;
|
||||||
job.destination_ = format_.GetFilenameForSong(song);
|
job.destination_ = task.song_info_.new_filename_;
|
||||||
job.metadata_ = song;
|
job.metadata_ = song;
|
||||||
job.overwrite_ = overwrite_;
|
job.overwrite_ = overwrite_;
|
||||||
job.remove_original_ = !copy_;
|
job.remove_original_ = !copy_;
|
||||||
|
@ -181,7 +181,7 @@ void Organise::ProcessSomeFiles() {
|
||||||
this, _1, !task.transcoded_filename_.isEmpty());
|
this, _1, !task.transcoded_filename_.isEmpty());
|
||||||
|
|
||||||
if (!destination_->CopyToStorage(job)) {
|
if (!destination_->CopyToStorage(job)) {
|
||||||
files_with_errors_ << task.song_.basefilename();
|
files_with_errors_ << task.song_info_.song_.basefilename();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clean up the temporary transcoded file
|
// Clean up the temporary transcoded file
|
||||||
|
|
|
@ -34,10 +34,19 @@ class Organise : public QObject {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
|
struct NewSongInfo {
|
||||||
|
NewSongInfo(const Song& song = Song(), const QString& new_filename = QString())
|
||||||
|
: song_(song), new_filename_(new_filename) {}
|
||||||
|
Song song_;
|
||||||
|
QString new_filename_;
|
||||||
|
};
|
||||||
|
typedef QList<NewSongInfo> NewSongInfoList;
|
||||||
|
|
||||||
Organise(TaskManager* task_manager,
|
Organise(TaskManager* task_manager,
|
||||||
boost::shared_ptr<MusicStorage> destination,
|
boost::shared_ptr<MusicStorage> destination,
|
||||||
const OrganiseFormat& format, bool copy, bool overwrite,
|
const OrganiseFormat& format, bool copy, bool overwrite,
|
||||||
const SongList& songs, bool eject_after);
|
const NewSongInfoList& songs, bool eject_after);
|
||||||
|
|
||||||
static const int kBatchSize;
|
static const int kBatchSize;
|
||||||
static const int kTranscodeProgressInterval;
|
static const int kTranscodeProgressInterval;
|
||||||
|
@ -61,10 +70,10 @@ private:
|
||||||
|
|
||||||
private:
|
private:
|
||||||
struct Task {
|
struct Task {
|
||||||
explicit Task(const Song& song = Song())
|
explicit Task(const NewSongInfo& song_info = NewSongInfo())
|
||||||
: song_(song), transcode_progress_(0.0) {}
|
: song_info_(song_info), transcode_progress_(0.0) {}
|
||||||
|
|
||||||
Song song_;
|
NewSongInfo song_info_;
|
||||||
|
|
||||||
float transcode_progress_;
|
float transcode_progress_;
|
||||||
QString transcoded_filename_;
|
QString transcoded_filename_;
|
||||||
|
|
|
@ -161,8 +161,7 @@ QString OrganiseFormat::TagValue(const QString &tag, const Song &song) const {
|
||||||
QString::number(song.length_nanosec() / kNsecPerSec);
|
QString::number(song.length_nanosec() / kNsecPerSec);
|
||||||
else if (tag == "bitrate") value = QString::number(song.bitrate());
|
else if (tag == "bitrate") value = QString::number(song.bitrate());
|
||||||
else if (tag == "samplerate") value = QString::number(song.samplerate());
|
else if (tag == "samplerate") value = QString::number(song.samplerate());
|
||||||
else if (tag == "extension") value =
|
else if (tag == "extension") value = QFileInfo(song.url().toLocalFile()).suffix();
|
||||||
song.url().toLocalFile().section('.', -1, -1);
|
|
||||||
else if (tag == "artistinitial") {
|
else if (tag == "artistinitial") {
|
||||||
value = song.effective_albumartist().trimmed();
|
value = song.effective_albumartist().trimmed();
|
||||||
if (replace_the_ && !value.isEmpty())
|
if (replace_the_ && !value.isEmpty())
|
||||||
|
|
|
@ -22,9 +22,11 @@
|
||||||
#include "core/musicstorage.h"
|
#include "core/musicstorage.h"
|
||||||
#include "core/organise.h"
|
#include "core/organise.h"
|
||||||
#include "core/tagreaderclient.h"
|
#include "core/tagreaderclient.h"
|
||||||
|
#include "core/utilities.h"
|
||||||
|
|
||||||
#include <QDir>
|
#include <QDir>
|
||||||
#include <QFileInfo>
|
#include <QFileInfo>
|
||||||
|
#include <QHash>
|
||||||
#include <QMenu>
|
#include <QMenu>
|
||||||
#include <QPushButton>
|
#include <QPushButton>
|
||||||
#include <QResizeEvent>
|
#include <QResizeEvent>
|
||||||
|
@ -162,6 +164,29 @@ void OrganiseDialog::InsertTag(const QString &tag) {
|
||||||
ui_->naming->insertPlainText("%" + tag);
|
ui_->naming->insertPlainText("%" + tag);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Organise::NewSongInfoList OrganiseDialog::ComputeNewSongsFilenames(
|
||||||
|
const SongList& songs, const OrganiseFormat& format) {
|
||||||
|
|
||||||
|
// Check if we will have multiple files with the same name.
|
||||||
|
// If so, they will erase each other if the overwrite flag is set.
|
||||||
|
// Better to rename them: e.g. foo.bar -> foo(2).bar
|
||||||
|
QHash<QString, int> filenames;
|
||||||
|
Organise::NewSongInfoList new_songs_info;
|
||||||
|
|
||||||
|
for (const Song& song : songs) {
|
||||||
|
QString new_filename = format.GetFilenameForSong(song);
|
||||||
|
if (filenames.contains(new_filename)) {
|
||||||
|
QString song_number = QString::number(++filenames[new_filename]);
|
||||||
|
new_filename =
|
||||||
|
Utilities::PathWithoutFilenameExtension(new_filename) +
|
||||||
|
"(" + song_number + ")." + QFileInfo(new_filename).suffix();
|
||||||
|
}
|
||||||
|
filenames.insert(new_filename, 1);
|
||||||
|
new_songs_info << Organise::NewSongInfo(song, new_filename);
|
||||||
|
}
|
||||||
|
return new_songs_info;
|
||||||
|
}
|
||||||
|
|
||||||
void OrganiseDialog::UpdatePreviews() {
|
void OrganiseDialog::UpdatePreviews() {
|
||||||
const QModelIndex destination = ui_->destination->model()->index(
|
const QModelIndex destination = ui_->destination->model()->index(
|
||||||
ui_->destination->currentIndex(), 0);
|
ui_->destination->currentIndex(), 0);
|
||||||
|
@ -205,14 +230,16 @@ void OrganiseDialog::UpdatePreviews() {
|
||||||
if (!format_valid)
|
if (!format_valid)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
new_songs_info_ = ComputeNewSongsFilenames(songs_, format_);
|
||||||
|
|
||||||
// Update the previews
|
// Update the previews
|
||||||
ui_->preview->clear();
|
ui_->preview->clear();
|
||||||
ui_->preview_group->setVisible(has_local_destination);
|
ui_->preview_group->setVisible(has_local_destination);
|
||||||
ui_->naming_group->setVisible(has_local_destination);
|
ui_->naming_group->setVisible(has_local_destination);
|
||||||
if (has_local_destination) {
|
if (has_local_destination) {
|
||||||
for (const Song& song : songs_) {
|
for (const Organise::NewSongInfo& song_info : new_songs_info_) {
|
||||||
QString filename = storage->LocalPath() + "/" +
|
QString filename = storage->LocalPath() + "/" +
|
||||||
format_.GetFilenameForSong(song);
|
song_info.new_filename_;
|
||||||
ui_->preview->addItem(QDir::toNativeSeparators(filename));
|
ui_->preview->addItem(QDir::toNativeSeparators(filename));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -278,7 +305,7 @@ void OrganiseDialog::accept() {
|
||||||
const bool copy = ui_->aftercopying->currentIndex() == 0;
|
const bool copy = ui_->aftercopying->currentIndex() == 0;
|
||||||
Organise* organise = new Organise(
|
Organise* organise = new Organise(
|
||||||
task_manager_, storage, format_, copy, ui_->overwrite->isChecked(),
|
task_manager_, storage, format_, copy, ui_->overwrite->isChecked(),
|
||||||
songs_, ui_->eject_after->isChecked());
|
new_songs_info_, ui_->eject_after->isChecked());
|
||||||
connect(organise, SIGNAL(Finished(QStringList)), SLOT(OrganiseFinished(QStringList)));
|
connect(organise, SIGNAL(Finished(QStringList)), SLOT(OrganiseFinished(QStringList)));
|
||||||
organise->Start();
|
organise->Start();
|
||||||
|
|
||||||
|
|
|
@ -23,6 +23,9 @@
|
||||||
#include <QMap>
|
#include <QMap>
|
||||||
#include <QUrl>
|
#include <QUrl>
|
||||||
|
|
||||||
|
#include "gtest/gtest_prod.h"
|
||||||
|
|
||||||
|
#include "core/organise.h"
|
||||||
#include "core/organiseformat.h"
|
#include "core/organiseformat.h"
|
||||||
#include "core/song.h"
|
#include "core/song.h"
|
||||||
|
|
||||||
|
@ -68,17 +71,24 @@ private slots:
|
||||||
void OrganiseFinished(const QStringList& files_with_errors);
|
void OrganiseFinished(const QStringList& files_with_errors);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
static Organise::NewSongInfoList ComputeNewSongsFilenames(
|
||||||
|
const SongList& songs,
|
||||||
|
const OrganiseFormat& format);
|
||||||
|
|
||||||
Ui_OrganiseDialog* ui_;
|
Ui_OrganiseDialog* ui_;
|
||||||
TaskManager* task_manager_;
|
TaskManager* task_manager_;
|
||||||
|
|
||||||
OrganiseFormat format_;
|
OrganiseFormat format_;
|
||||||
|
|
||||||
SongList songs_;
|
SongList songs_;
|
||||||
|
Organise::NewSongInfoList new_songs_info_;
|
||||||
quint64 total_size_;
|
quint64 total_size_;
|
||||||
|
|
||||||
std::unique_ptr<OrganiseErrorDialog> error_dialog_;
|
std::unique_ptr<OrganiseErrorDialog> error_dialog_;
|
||||||
|
|
||||||
bool resized_by_user_;
|
bool resized_by_user_;
|
||||||
|
|
||||||
|
FRIEND_TEST(OrganiseDialogTest, ComputeNewSongsFilenamesTest);
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // ORGANISEDIALOG_H
|
#endif // ORGANISEDIALOG_H
|
||||||
|
|
|
@ -132,6 +132,7 @@ add_test_file(fmpsparser_test.cpp false)
|
||||||
#add_test_file(m3uparser_test.cpp false)
|
#add_test_file(m3uparser_test.cpp false)
|
||||||
add_test_file(mergedproxymodel_test.cpp false)
|
add_test_file(mergedproxymodel_test.cpp false)
|
||||||
add_test_file(organiseformat_test.cpp false)
|
add_test_file(organiseformat_test.cpp false)
|
||||||
|
add_test_file(organisedialog_test.cpp false)
|
||||||
#add_test_file(playlist_test.cpp true)
|
#add_test_file(playlist_test.cpp true)
|
||||||
#add_test_file(plsparser_test.cpp false)
|
#add_test_file(plsparser_test.cpp false)
|
||||||
add_test_file(scopedtransaction_test.cpp false)
|
add_test_file(scopedtransaction_test.cpp false)
|
||||||
|
|
|
@ -0,0 +1,71 @@
|
||||||
|
/* This file is part of Clementine.
|
||||||
|
Copyright 2014, David Sansome <me@davidsansome.com>
|
||||||
|
|
||||||
|
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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "gtest/gtest.h"
|
||||||
|
#include "test_utils.h"
|
||||||
|
|
||||||
|
#include "core/song.h"
|
||||||
|
#include "core/organiseformat.h"
|
||||||
|
#include "ui/organisedialog.h"
|
||||||
|
|
||||||
|
|
||||||
|
TEST(OrganiseDialogTest, ComputeNewSongsFilenamesTest) {
|
||||||
|
// Create some songs, with multiple similar songs
|
||||||
|
SongList songs;
|
||||||
|
{
|
||||||
|
Song song;
|
||||||
|
song.set_title("Test1");
|
||||||
|
song.set_album("Album");
|
||||||
|
songs << song;
|
||||||
|
}
|
||||||
|
// Empty song
|
||||||
|
{
|
||||||
|
Song song;
|
||||||
|
song.set_basefilename("filename.mp3");
|
||||||
|
songs << song;
|
||||||
|
}
|
||||||
|
// Without extension
|
||||||
|
for (int i = 0; i < 2; i++) {
|
||||||
|
Song song;
|
||||||
|
song.set_title("Test2");
|
||||||
|
song.set_url(QUrl("file://test" + QString::number(i)));
|
||||||
|
songs << song;
|
||||||
|
}
|
||||||
|
|
||||||
|
// With file extension
|
||||||
|
for (int i = 0; i < 3; i++) {
|
||||||
|
Song song;
|
||||||
|
song.set_artist("Foo");
|
||||||
|
song.set_title("Bar");
|
||||||
|
song.set_url(QUrl("file://foobar" + QString::number(i) + ".mp3"));
|
||||||
|
songs << song;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate new filenames
|
||||||
|
OrganiseFormat format;
|
||||||
|
format.set_format(OrganiseDialog::kDefaultFormat);
|
||||||
|
Organise::NewSongInfoList new_songs_info = OrganiseDialog::ComputeNewSongsFilenames(songs, format);
|
||||||
|
|
||||||
|
EXPECT_EQ("/Album/Test1.", new_songs_info[0].new_filename_);
|
||||||
|
EXPECT_EQ("//filename.mp3", new_songs_info[1].new_filename_);
|
||||||
|
EXPECT_EQ("//Test2.", new_songs_info[2].new_filename_);
|
||||||
|
EXPECT_EQ("//Test2(2).", new_songs_info[3].new_filename_);
|
||||||
|
EXPECT_EQ("Foo//Bar.mp3", new_songs_info[4].new_filename_);
|
||||||
|
EXPECT_EQ("Foo//Bar(2).mp3", new_songs_info[5].new_filename_);
|
||||||
|
EXPECT_EQ("Foo//Bar(3).mp3", new_songs_info[6].new_filename_);
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue