Cleanup the codebase significantly.
This commit is contained in:
parent
1ebba43dc0
commit
b1b8f6917c
195
mcopy.cpp
195
mcopy.cpp
|
@ -1,195 +0,0 @@
|
|||
/* 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 <iostream>
|
||||
#include <filesystem>
|
||||
#include <string>
|
||||
#include <fstream>
|
||||
#include <regex>
|
||||
#include <cstdlib>
|
||||
#include <tag.h>
|
||||
#include <fileref.h>
|
||||
|
||||
#ifndef VERSION
|
||||
#define VERSION "0.1"
|
||||
#endif
|
||||
|
||||
bool ask = true;
|
||||
bool quiet = false;
|
||||
|
||||
void help() {
|
||||
std::cout <<
|
||||
"mcopy " << VERSION << "\n\n" <<
|
||||
"mcopy -f, --format Specify a format to output files using\n" <<
|
||||
"mcopy -a, --ask Ask the user if metadata cannot be retrieved from file\n" <<
|
||||
"mcopy -na, --no-ask Don't ask if metadata cannot be retrieved from file\n" <<
|
||||
"mcopy -q, --quiet Don't print status messages. Warnings will still be printed\n" <<
|
||||
"mcopy -nq, --no-quiet Print status messages\n" <<
|
||||
"mcopy -h, --help Display help\n" <<
|
||||
"mcopy -v, --version Display version\n" <<
|
||||
"\n" <<
|
||||
"Example: mcopy --format \"/home/anon/Music/Albums/@A/@a/@n. @A - @t.flac\" \"~/Downloads/myflac1.flac\" \"~/Downloads/myflac2.flac\n" <<
|
||||
"\n" <<
|
||||
"Formats:\n" <<
|
||||
"\n" <<
|
||||
"@A Artist\n" <<
|
||||
"@a Album name\n" <<
|
||||
"@t Title\n" <<
|
||||
"@n Track number\n";
|
||||
}
|
||||
|
||||
TagLib::FileRef getFile(std::string str) {
|
||||
const char *cstr = str.c_str();
|
||||
TagLib::FileName fn(cstr);
|
||||
TagLib::FileRef file(fn);
|
||||
|
||||
return file;
|
||||
}
|
||||
|
||||
void getMetadataFromFile(std::string format, std::string str) {
|
||||
TagLib::FileRef file = getFile(str);
|
||||
TagLib::Tag *tag = file.tag();
|
||||
|
||||
if (file.isNull() || !file.audioProperties()) {
|
||||
std::cerr << "File '" << str << "' is not valid. Skipping.\n";
|
||||
return;
|
||||
}
|
||||
|
||||
std::string title = tag->title().to8Bit(true);
|
||||
std::string album = tag->album().to8Bit(true);
|
||||
std::string artist = tag->artist().to8Bit(true);
|
||||
std::string track = std::to_string(tag->track());
|
||||
|
||||
// If information is missing, ask for the missing details
|
||||
if (title.empty()) {
|
||||
title = "Unknown";
|
||||
|
||||
if (ask) {
|
||||
std::cerr << "mcopy: Could not retrieve title; please specify\n> ";
|
||||
std::getline(std::cin, title);
|
||||
}
|
||||
}
|
||||
|
||||
if (album.empty()) {
|
||||
album = "Unknown";
|
||||
|
||||
if (ask) {
|
||||
std::cerr << "mcopy: Could not retrieve album; please specify\n> ";
|
||||
std::getline(std::cin, album);
|
||||
}
|
||||
}
|
||||
|
||||
if (artist.empty()) {
|
||||
artist = "Unknown";
|
||||
|
||||
if (ask) {
|
||||
std::cerr << "mcopy: Could not retrieve artist; please specify\n> ";
|
||||
std::getline(std::cin, artist);
|
||||
}
|
||||
}
|
||||
|
||||
if (track.empty()) {
|
||||
track = "Unknown";
|
||||
|
||||
if (ask) {
|
||||
std::cerr << "mcopy: Could not retrieve track; please specify\n> ";
|
||||
std::getline(std::cin, track);
|
||||
}
|
||||
}
|
||||
|
||||
if (!std::cin) // Failed to get details, skipping
|
||||
return;
|
||||
|
||||
std::string filename = format;
|
||||
|
||||
// TODO: Replace this junk
|
||||
filename = std::regex_replace(filename, std::regex("@t"), title);
|
||||
filename = std::regex_replace(filename, std::regex("@a"), album);
|
||||
filename = std::regex_replace(filename, std::regex("@A"), artist);
|
||||
filename = std::regex_replace(filename, std::regex("@n"), track);
|
||||
|
||||
std::ifstream ef(filename);
|
||||
|
||||
if (static_cast<bool>(ef.good())) {
|
||||
std::cerr << "mcopy: File already exists, skipping.\n";
|
||||
return;
|
||||
}
|
||||
|
||||
std::filesystem::path fs;
|
||||
std::string dir = (fs = filename).remove_filename();
|
||||
|
||||
if (dir.empty()) {
|
||||
std::cerr << "mcopy: Was not able to get a directory.\n";
|
||||
return;
|
||||
}
|
||||
|
||||
if (!std::filesystem::exists(fs.remove_filename())) {
|
||||
if (!std::filesystem::create_directories(dir)) {
|
||||
std::cerr << "mcopy: Failed to create directory '" << dir << "'\n";
|
||||
}
|
||||
}
|
||||
|
||||
if (!std::filesystem::copy_file(str, filename)) {
|
||||
std::cerr << "mcopy: Failed to copy file " << str << " to " << filename << "\n";
|
||||
std::exit(1);
|
||||
} else if (!quiet) {
|
||||
std::cout << "mcopy: Copied file " << str << " to " << filename << "\n";
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
bool set{false};
|
||||
std::string format;
|
||||
|
||||
for (int i{1}; i < argc; i++) {
|
||||
std::string arg = argv[i];
|
||||
|
||||
if (!arg.compare("-h") || !arg.compare("--help")) {
|
||||
help();
|
||||
} else if (!arg.compare("-v") || !arg.compare("--version")) {
|
||||
std::cout << "mcopy " << VERSION << '\n';
|
||||
return 0;
|
||||
} else if (!arg.compare("-a") || !arg.compare("--ask")) {
|
||||
ask = true;
|
||||
} else if (!arg.compare("-na") || !arg.compare("--no-ask")) {
|
||||
ask = false;
|
||||
} else if (!arg.compare("-q") || !arg.compare("--quiet")) {
|
||||
quiet = true;
|
||||
} else if (!arg.compare("-nq") || !arg.compare("--no-quiet")) {
|
||||
quiet = false;
|
||||
} else if (!arg.compare("-f") || !arg.compare("--format")) {
|
||||
if (argc > i+1) {
|
||||
format = argv[++i];
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (!format.compare("")) {
|
||||
std::cerr << "mcopy: You must specify a format.\n";
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (argc < 4) {
|
||||
std::cerr << "mcopy: You must specify a file to copy.\n";
|
||||
return 1;
|
||||
}
|
||||
|
||||
for (int i{1}; i < argc; i++) {
|
||||
std::ifstream f(argv[i]);
|
||||
|
||||
if (static_cast<bool>(f.good())) {
|
||||
set = true;
|
||||
getMetadataFromFile(format, argv[i]);
|
||||
}
|
||||
}
|
||||
|
||||
if (!set) {
|
||||
std::cerr << "mcopy: File not found.\n";
|
||||
return 1;
|
||||
}
|
||||
}
|
|
@ -1,13 +1,11 @@
|
|||
project(
|
||||
'mcopy',
|
||||
'cpp',
|
||||
version : '"0.1"',
|
||||
version : '"0.0.1"',
|
||||
)
|
||||
|
||||
cc = meson.get_compiler('cpp')
|
||||
|
||||
project_source_files = [
|
||||
'mcopy.cpp',
|
||||
'src/mcopy.cpp',
|
||||
]
|
||||
|
||||
project_dependencies = [
|
||||
|
@ -15,7 +13,7 @@ project_dependencies = [
|
|||
]
|
||||
|
||||
build_args = [
|
||||
'-DVERSION=' + meson.project_version(),
|
||||
'-DMCOPY_VERSION=' + meson.project_version(),
|
||||
]
|
||||
|
||||
project_target = executable(
|
||||
|
|
203
src/mcopy.cpp
Normal file
203
src/mcopy.cpp
Normal file
|
@ -0,0 +1,203 @@
|
|||
/* 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 <iostream>
|
||||
#include <filesystem>
|
||||
#include <string>
|
||||
#include <fstream>
|
||||
#include <unordered_map>
|
||||
#include <cstdlib>
|
||||
#include <tag.h>
|
||||
#include <fileref.h>
|
||||
|
||||
#ifndef MCOPY_VERSION
|
||||
#define MCOPY_VERSION "0.0.1"
|
||||
#endif
|
||||
|
||||
struct Settings {
|
||||
bool ask{true};
|
||||
bool quiet{false};
|
||||
bool strict{false};
|
||||
bool skip_existing{false};
|
||||
};
|
||||
|
||||
void help() {
|
||||
std::cout <<
|
||||
"mcopy [options]\n" <<
|
||||
" -f, --format Specify a format to output files using\n" <<
|
||||
" -a, --ask Ask the user if metadata cannot be retrieved from file\n" <<
|
||||
" -na, --no-ask Don't ask if metadata cannot be retrieved from file\n" <<
|
||||
" -q, --quiet Don't print status messages. Warnings will still be printed\n" <<
|
||||
" -nq, --no-quiet Print status messages\n" <<
|
||||
" -s, --strict Error on any invalid files\n" <<
|
||||
" -ns, --no-strict Don't error on any invalid files\n" <<
|
||||
" -sk, --skip-existing Skip existing files\n" <<
|
||||
" -ow, --overwrite-existing Overwrite any existing files\n" <<
|
||||
" -h, --help Display help\n" <<
|
||||
" -v, --version Display version\n" <<
|
||||
"\n" <<
|
||||
"Example: mcopy --format \"~/Music/Albums/@A/@a/@n. @A - @t.flac\" \"~/Downloads/myflac1.flac\" \"~/Downloads/myflac2.flac\n" <<
|
||||
"\n" <<
|
||||
"Formats:\n" <<
|
||||
"\n" <<
|
||||
"@A Artist\n" <<
|
||||
"@a Album name\n" <<
|
||||
"@t Title\n" <<
|
||||
"@n Track number\n";
|
||||
|
||||
std::exit(EXIT_SUCCESS);
|
||||
}
|
||||
|
||||
TagLib::FileRef get_file(const std::string& str) {
|
||||
return TagLib::FileRef(TagLib::FileName(str.c_str()));
|
||||
}
|
||||
|
||||
void get_metadata_from_file(const Settings& settings, const std::string& format, const std::string& file) {
|
||||
TagLib::FileRef _file = get_file(file);
|
||||
TagLib::Tag* tag = _file.tag();
|
||||
|
||||
if (_file.isNull() || !_file.audioProperties()) {
|
||||
std::cerr << "mcopy: invalid file: '" << file << "' is skipped.\n";
|
||||
return;
|
||||
}
|
||||
|
||||
struct FileData {
|
||||
std::string title{};
|
||||
std::string album{};
|
||||
std::string artist{};
|
||||
std::string track{};
|
||||
};
|
||||
|
||||
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()); };
|
||||
|
||||
FileData data{get_title(), get_album(), get_artist(), get_track()};
|
||||
|
||||
#ifdef _WIN32
|
||||
std::vector<char> the_forbidden_characters{'<', '>', ':', '"', '/', '\\', '|', '?', '*'};
|
||||
#else
|
||||
std::vector<char> the_forbidden_characters{'/'};
|
||||
#endif
|
||||
|
||||
for (auto& it : std::vector<std::string*>{&data.title, &data.album, &data.artist, &data.track}) {
|
||||
for (const auto& c : the_forbidden_characters)
|
||||
static_cast<void>(std::remove_if(it->begin(), it->end(), [&c](char _c) { return c == _c; }));
|
||||
}
|
||||
|
||||
for (const auto& it : std::unordered_map<std::string, std::string&>{{"title", data.title}, {"album", data.album}, {"artist", data.artist}, {"track", data.track}}) {
|
||||
if (it.second.empty()) {
|
||||
it.second = "Unknown";
|
||||
|
||||
if (settings.ask) {
|
||||
std::cerr << "mcopy: failed to retrieve " << it.first << ", enter an appropriate replacement: ";
|
||||
std::getline(std::cin, it.second);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::string output_filename = format;
|
||||
for (const auto& it : std::unordered_map<std::string, std::string>{{"@t", data.title}, {"@a", data.album}, {"@A", data.artist}, {"@n", data.track}}) {
|
||||
while (output_filename.find(it.first) != std::string::npos) {
|
||||
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\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";
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
Settings settings{};
|
||||
std::string format{};
|
||||
|
||||
std::vector<std::string> unknown_args{};
|
||||
for (int i{1}; i < argc; i++) {
|
||||
std::string arg = argv[i];
|
||||
|
||||
if (!arg.compare("-h") || !arg.compare("--help")) {
|
||||
help();
|
||||
} else if (!arg.compare("-v") || !arg.compare("--version")) {
|
||||
std::cout << "mcopy " << MCOPY_VERSION << '\n';
|
||||
return 0;
|
||||
} else if (!arg.compare("-a") || !arg.compare("--ask")) {
|
||||
settings.ask = true;
|
||||
} else if (!arg.compare("-na") || !arg.compare("--no-ask")) {
|
||||
settings.ask = false;
|
||||
} else if (!arg.compare("-q") || !arg.compare("--quiet")) {
|
||||
settings.quiet = true;
|
||||
} else if (!arg.compare("-nq") || !arg.compare("--no-quiet")) {
|
||||
settings.quiet = false;
|
||||
} else if (!arg.compare("-s") || !arg.compare("--strict")) {
|
||||
settings.strict = true;
|
||||
} else if (!arg.compare("-ns") || !arg.compare("--no-strict")) {
|
||||
settings.strict = false;
|
||||
} else if (!arg.compare("-sk") || !arg.compare("--skip-existing")) {
|
||||
settings.skip_existing = true;
|
||||
} else if (!arg.compare("-ow") || !arg.compare("--overwrite-existing")) {
|
||||
settings.skip_existing = false;
|
||||
} else if (!arg.compare("-f") || !arg.compare("--format")) {
|
||||
if (argc > i+1) format = argv[++i];
|
||||
continue;
|
||||
} else {
|
||||
unknown_args.push_back(argv[i]);
|
||||
}
|
||||
}
|
||||
|
||||
if (!format.compare("")) {
|
||||
std::cerr << "mcopy: format parameter not specified\n";
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (argc < 4) {
|
||||
std::cerr << "mcopy: input file(s) not specified\n";
|
||||
return 1;
|
||||
}
|
||||
|
||||
for (const auto& it : unknown_args) {
|
||||
std::ifstream f(it);
|
||||
|
||||
if (f.good()) {
|
||||
get_metadata_from_file(settings, format, it);
|
||||
} else {
|
||||
std::cerr << "mcopy: input file '" << it << "' not found\n";
|
||||
|
||||
if (settings.strict) std::exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue