140 lines
4.9 KiB
C++
140 lines
4.9 KiB
C++
|
/* mcopy
|
||
|
*
|
||
|
* Rename music by specifying a format
|
||
|
* Licensed under the GNU General Public License version 3.
|
||
|
* See included LICENSE file for copyright and license details.
|
||
|
*/
|
||
|
|
||
|
#include <tag.h>
|
||
|
#include <fileref.h>
|
||
|
#include <filesystem>
|
||
|
#include <unordered_map>
|
||
|
#include <fstream>
|
||
|
#include <core.hpp>
|
||
|
|
||
|
std::pair<mcopy::FileStatus, mcopy::FileData> mcopy::get_metadata_from_file(const std::string& file) {
|
||
|
TagLib::FileRef _file = TagLib::FileRef(TagLib::FileName(file.c_str()));
|
||
|
|
||
|
if (_file.isNull() || !_file.audioProperties()) {
|
||
|
return {mcopy::FileStatus::Failure, {}};
|
||
|
}
|
||
|
|
||
|
TagLib::Tag* tag = _file.tag();
|
||
|
|
||
|
const auto get_title = [&tag]() -> std::string { return tag->title().to8Bit(true); };
|
||
|
const auto get_album = [&tag]() -> std::string { return tag->album().to8Bit(true); };
|
||
|
const auto get_artist = [&tag]() -> std::string { return tag->artist().to8Bit(true); };
|
||
|
const auto get_track = [&tag]() -> std::string { return std::to_string(tag->track()); };
|
||
|
const auto get_year = [&tag]() -> std::string { return std::to_string(tag->year()); };
|
||
|
const auto get_genre = [&tag]() -> std::string { return tag->genre().to8Bit(true); };
|
||
|
const auto get_comment = [&tag]() -> std::string { return tag->comment().to8Bit(true); };
|
||
|
const auto get_extension = [&file]() -> std::string { return file.find_last_of(".") + 1 != file.npos ? file.substr(file.find_last_of(".") + 1) : ""; };
|
||
|
|
||
|
mcopy::FileData data{
|
||
|
get_title(),
|
||
|
get_album(),
|
||
|
get_artist(),
|
||
|
get_track(),
|
||
|
get_year(),
|
||
|
get_genre(),
|
||
|
get_comment(),
|
||
|
get_extension(),
|
||
|
};
|
||
|
|
||
|
return {mcopy::FileStatus::Success, data};
|
||
|
}
|
||
|
|
||
|
void mcopy::write_to_file(const mcopy::Settings& settings, const std::string& format, const std::string& file) {
|
||
|
if (!std::filesystem::is_regular_file(file)) {
|
||
|
std::cerr << "mcopy: invalid file '" << file << "' is skipped.\n";
|
||
|
if (settings.strict) {
|
||
|
std::exit(EXIT_FAILURE);
|
||
|
}
|
||
|
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
std::pair<mcopy::FileStatus, mcopy::FileData> pair = mcopy::get_metadata_from_file(file);
|
||
|
if (pair.first == mcopy::FileStatus::Failure) {
|
||
|
std::cerr << "mcopy: invalid file '" << file << "' is skipped.\n";
|
||
|
if (settings.strict) {
|
||
|
std::exit(EXIT_FAILURE);
|
||
|
}
|
||
|
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
mcopy::FileData& data = pair.second;
|
||
|
#ifdef _WIN32
|
||
|
std::vector<char> the_forbidden_characters{'<', '>', ':', '"', '/', '\\', '|', '?', '*'};
|
||
|
#else
|
||
|
std::vector<char> the_forbidden_characters{'/'};
|
||
|
#endif
|
||
|
|
||
|
const std::vector<std::string*> data_members = {&data.title, &data.album, &data.artist, &data.track, &data.year, &data.genre, &data.comment, &data.extension};
|
||
|
for (const auto& it : data_members) {
|
||
|
for (const auto& c : the_forbidden_characters) {
|
||
|
it->erase(std::remove_if(it->begin(), it->end(), [&c](char _c) { return c == _c; }), it->end());
|
||
|
}
|
||
|
}
|
||
|
|
||
|
std::unordered_map<std::string, std::string> delim_map{
|
||
|
{"@t", data.title},
|
||
|
{"@a", data.album},
|
||
|
{"@A", data.artist},
|
||
|
{"@n", data.track},
|
||
|
{"@y", data.year},
|
||
|
{"@g", data.genre},
|
||
|
{"@c", data.comment},
|
||
|
{"@e", data.extension},
|
||
|
};
|
||
|
|
||
|
std::string output_filename = format;
|
||
|
for (auto& it : delim_map) {
|
||
|
while (output_filename.find(it.first) != std::string::npos) {
|
||
|
if (it.second.empty()) {
|
||
|
it.second = "Unknown";
|
||
|
|
||
|
if (settings.ask) {
|
||
|
std::cerr << "mcopy: failed to retrieve data to represent " << it.first << ", enter an appropriate replacement: ";
|
||
|
std::getline(std::cin, it.second);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
output_filename.replace(output_filename.find(it.first), it.first.length(), it.second);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
std::ifstream ef(output_filename);
|
||
|
if (ef.good() && settings.skip_existing) {
|
||
|
std::cerr << "mcopy: file << '" << file << "' already exists, skipping\n";
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
const std::string dir{std::filesystem::path{output_filename}.remove_filename()};
|
||
|
if (dir.empty()) {
|
||
|
std::cerr << "mcopy: failed to retrieve directory: " << output_filename << "\n";
|
||
|
if (settings.strict) {
|
||
|
std::exit(EXIT_FAILURE);
|
||
|
}
|
||
|
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (!std::filesystem::exists(dir) && !std::filesystem::create_directories(dir)) {
|
||
|
std::cerr << "mcopy: failed to create directory '" << dir << "'\n";
|
||
|
if (settings.strict) {
|
||
|
std::exit(EXIT_FAILURE);
|
||
|
}
|
||
|
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (!std::filesystem::copy_file(file, output_filename, std::filesystem::copy_options::overwrite_existing)) {
|
||
|
std::cerr << "mcopy: failed to copy input file '" << file << "' to '" << output_filename << "'\n";
|
||
|
if (settings.strict) std::exit(EXIT_FAILURE);
|
||
|
} else if (!settings.quiet) {
|
||
|
std::cout << "mcopy: copied input file '" << file << "' to '" << output_filename << "'\n";
|
||
|
}
|
||
|
}
|