diff options
author | Jason A. Donenfeld <Jason@zx2c4.com> | 2012-08-20 02:53:24 +0200 |
---|---|---|
committer | Jason A. Donenfeld <Jason@zx2c4.com> | 2012-08-20 02:53:24 +0200 |
commit | 43538809de8ebb49f5e6cea9a19bcbdd933da44e (patch) | |
tree | 4dac9081f8bcc27b45bb5132df554cc87caa0b97 /organizemusic.cpp | |
parent | Support audio properties too. (diff) | |
download | music-file-organizer-43538809de8ebb49f5e6cea9a19bcbdd933da44e.tar.xz music-file-organizer-43538809de8ebb49f5e6cea9a19bcbdd933da44e.zip |
Make fundumentals work decently.
Diffstat (limited to 'organizemusic.cpp')
-rw-r--r-- | organizemusic.cpp | 189 |
1 files changed, 189 insertions, 0 deletions
diff --git a/organizemusic.cpp b/organizemusic.cpp new file mode 100644 index 0000000..b885ebe --- /dev/null +++ b/organizemusic.cpp @@ -0,0 +1,189 @@ +#include "AudioFile.h" + +#include <unicode/translit.h> +#include <unicode/unistr.h> + +#include <iostream> +#include <iomanip> +#include <sstream> +#include <algorithm> + +#include <cstdio> +#include <cstring> +#include <cerrno> + +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> +#include <dirent.h> + +using namespace std; + +static string base_destination = "/home/zx2c4/Music/"; +static Transliterator *transliterator; + +void process_file(const char *filename, ino_t inode); +void process_directory(const char *directory); +void process_path(const char *path); + +string generate_path(const AudioFile &audio); +string truncated(const string &str, const string &ext); +string transliterated(const string &str); +void rename_path(const string &source, const string &stem, ino_t inode); +void strip_slash(string &name); +void disc_track(unsigned int disc, unsigned int track, ostringstream &path); + +string strip_slash(const string &name) +{ + string ret(name); + replace(ret.begin(), ret.end(), '/', '-'); + return ret; +} +void disc_track(unsigned int disc, unsigned int track, ostringstream &path) +{ + if (disc > 0) { + path << disc; + if (track > 0) + path << '-'; + else + path << ' '; + } + if (track > 0) + path << setw(2) << setfill('0') << track << setw(0) << ' '; +} +string generate_path(const AudioFile &audio) +{ + ostringstream path; + if (audio.compilation()) { + path << "Various Artists/"; + if (audio.album().length() == 0) + path << "Unknown Album/"; + else + path << strip_slash(audio.album()) << '/'; + disc_track(audio.disc(), audio.track(), path); + if (audio.artist().length() > 0) + path << strip_slash(audio.artist()) << " - "; + path << strip_slash(audio.title()); + } else { + if (audio.artist().length() == 0) + path << "Unknown Artist/"; + else + path << strip_slash(audio.artist()) << '/'; + if (audio.album().length() > 0) + path << strip_slash(audio.album()) << '/'; + disc_track(audio.disc(), audio.track(), path); + path << strip_slash(audio.title()); + } + return transliterated(path.str()); +} +string truncated(const string &str, const string &ext) +{ + stringstream stream; + size_t current, next = -1; + for (;;) { + current = next + 1; + next = str.find_first_of('/', current); + if (current) + stream << '/'; + if (next == string::npos) { + stream << str.substr(current, static_cast<size_t>(255 - ext.length())) << ext; + break; + } + stream << str.substr(current, min(static_cast<size_t>(255), next - current)); + } + return stream.str(); +} +string transliterated(const string &str) +{ + UnicodeString in(str.c_str(), "UTF-8"); + transliterator->transliterate(in); + char out[in.length() + 1]; + out[in.extract(0, in.length(), out, in.length(), "ASCII")] = '\0'; + return out; +} +void rename_path(const string &source, const string &stem, ino_t inode) +{ + size_t lastdot = source.find_last_of('.'); + string ext; + if (lastdot != string::npos) { + ext = source.substr(lastdot); + transform(ext.begin(), ext.end(), ext.begin(), ::tolower); + } + string destination = truncated(base_destination + stem, ext); + unsigned long counter = 0; + for (;;) { + struct stat sbuf; + if (stat(destination.c_str(), &sbuf)) { + if (errno == ENOENT) + break; + } + if (sbuf.st_ino == inode) + return; + + ostringstream stream; + stream << base_destination << stem << ' ' << ++counter; + destination = truncated(stream.str(), ext); + + if (counter == 0) + return; + } + cout << source << endl << "\t-> " << destination << endl; +} +void process_file(const char *filename, ino_t inode) +{ + AudioFile audio(filename); + if (!audio.isValid()) { + cerr << filename << ": not a valid audio file" << endl; + return; + } + rename_path(filename, generate_path(audio), inode); +} +void process_directory(const char *directory) +{ + DIR *dir = opendir(directory); + if (!dir) + return; + while (struct dirent *entry = readdir(dir)) { + if (entry->d_name[0] == '.' && (entry->d_name[1] == '\0' || + (entry->d_name[1] == '.' && strlen(entry->d_name) == 2))) + continue; + + char joined[strlen(directory) + strlen(entry->d_name) + 2]; + sprintf(joined, "%s/%s", directory, entry->d_name); + process_path(joined); + } + closedir(dir); +} +void process_path(const char *path) +{ + struct stat sbuf; + if (stat(path, &sbuf)) { + perror(path); + return; + } + if (S_ISREG(sbuf.st_mode)) + process_file(path, sbuf.st_ino); + else if (S_ISDIR(sbuf.st_mode)) + process_directory(path); + else + cerr << path << ": not a file nor a directory" << endl; +} + +int main(int argc, char *argv[]) +{ + UErrorCode status = U_ZERO_ERROR; + transliterator = Transliterator::createInstance("Latin; NFD; [:Nonspacing Mark:] Remove; NFC; [:^ASCII:] Remove; [\\:;*?\"<>|\\\\] Remove", UTRANS_FORWARD, status); + if (!transliterator || status != U_ZERO_ERROR) { + cerr << "Fatal: Could not initialize transliterator." << endl; + return EXIT_FAILURE; + } + + for (int i = 1; i < argc; ++i) { + size_t len = strlen(argv[i]); + if (argv[i][len - 1] == '/') + argv[i][len - 1] = '\0'; + process_path(argv[i]); + } + + return 0; +} |