diff options
author | Jason A. Donenfeld <Jason@zx2c4.com> | 2012-08-20 17:16:23 +0200 |
---|---|---|
committer | Jason A. Donenfeld <Jason@zx2c4.com> | 2012-08-20 17:16:23 +0200 |
commit | 6d16ed0b09f6bffa1633c521a0d32ce96108a8c5 (patch) | |
tree | a2584e5e1fb85043fa9d1615a40ec666528e3603 | |
parent | Rearrange headers. (diff) | |
download | music-file-organizer-6d16ed0b09f6bffa1633c521a0d32ce96108a8c5.tar.xz music-file-organizer-6d16ed0b09f6bffa1633c521a0d32ce96108a8c5.zip |
Nice comments.
-rw-r--r-- | organizemusic.cpp | 48 |
1 files changed, 48 insertions, 0 deletions
diff --git a/organizemusic.cpp b/organizemusic.cpp index 2419d87..0363e47 100644 --- a/organizemusic.cpp +++ b/organizemusic.cpp @@ -19,6 +19,8 @@ using namespace std; +/* TODO: we could benefit from having these dynamic, and making them + * no-ops when the output isn't an xterm. */ #define RED "\x1b[31m" #define GREEN "\x1b[32m" #define YELLOW "\x1b[33m" @@ -42,12 +44,15 @@ void disc_track(unsigned int disc, unsigned int track, ostringstream &path); void rename_path(const string &source, const string &stem, ino_t inode); void make_parents(const string &filepath); +/* Converts forward slashes to hyphens. */ string strip_slash(const string &name) { string ret(name); replace(ret.begin(), ret.end(), '/', '-'); return ret; } + +/* {disc}-{0-padded track}{space} with reasonable fallbacks. */ void disc_track(unsigned int disc, unsigned int track, ostringstream &path) { if (disc > 0) { @@ -60,6 +65,12 @@ void disc_track(unsigned int disc, unsigned int track, ostringstream &path) if (track > 0) path << setw(2) << setfill('0') << track << setw(0) << ' '; } + +/* For compilations: + * Various Artists/{Album Name}/{Disc/Track} {Artist} - {Title} + * Otherwise: + * {Artist}/{Album}/{Disc/Track} {Title} + * With reasonable fallbacks in case the data is partially. */ string generate_path(const AudioFile &audio) { ostringstream path; @@ -85,6 +96,10 @@ string generate_path(const AudioFile &audio) } return transliterated(path.str()); } + +/* Ensures that no component of the path name is bigger than 255 characters. + * This truncates any components larger than the maximum, taking into account + * the file extension. */ string truncated(const string &str, const string &ext) { stringstream stream; @@ -102,6 +117,10 @@ string truncated(const string &str, const string &ext) } return stream.str(); } + +/* Uses the libicu transliterator to remove dangerous characters. Unfortuantely, + * this requires some somewhat ugly string copying because libicu uses its own + * type of string container. */ string transliterated(const string &str) { UnicodeString in(str.c_str(), "UTF-8"); @@ -110,6 +129,15 @@ string transliterated(const string &str) out[in.extract(0, in.length(), out, in.length(), "ASCII")] = '\0'; return out; } + +/* 1. Determine the extension of the source file and convert it to lower case. + * 2. Form destination file name from base destination, generated stem, and extension. + * 3. If the destination file does not exist, great. Otherwise, see if it exists. + * 4. If it exists, and the source file has the same inode as the destination, we're done. + * Otherwise, tag on a counter number and see if that file exists, etc. + * 5. If we exhaust all possible counters and wrap around to 0, give up. + * 6. After establishing a non-existing file name destination, make parent directories, + * then attempt to rename the source to the destination. */ void rename_path(const string &source, const string &stem, ino_t inode) { size_t pos = source.find_last_of('.'); @@ -146,6 +174,9 @@ void rename_path(const string &source, const string &stem, ino_t inode) cerr << RESET; } } + +/* Break a file path down into components and try to mkdir for each component, + * except for the last one, ensuring that filepath's enclosing directories exist. */ void make_parents(const string &filepath) { string path; @@ -162,6 +193,8 @@ void make_parents(const string &filepath) cout << MAGENTA << "mkdir" << RESET << ": " << path.c_str() << endl; } } + +/* Read tags using AudioFile, and then rename the file to a generated path. */ void process_file(const char *filename, ino_t inode) { AudioFile audio(filename); @@ -171,6 +204,9 @@ void process_file(const char *filename, ino_t inode) } rename_path(filename, generate_path(audio), inode); } + +/* Iterate through all items in given directory, skipping . and .., and process + * each path. After, try to remove directory if it's empty. */ void process_directory(const char *directory) { DIR *dir = opendir(directory); @@ -189,6 +225,8 @@ void process_directory(const char *directory) if (!rmdir(directory)) cout << BLUE << "rmdir" << RESET << ": " << directory << endl; } + +/* Process files as files, directories as directories, and skip everything else. */ void process_path(const char *path) { struct stat sbuf; @@ -210,12 +248,21 @@ int main(int argc, char *argv[]) { size_t len; UErrorCode status = U_ZERO_ERROR; + /* This libicu transliterator does the following: + * 1. Switches to Latin. + * 2. Decomposes unicode. + * 3. Removes any special marks that aren't spaces. + * 4. Recomposes unicode. + * 5. Removes any non-ASCII chars. + * 6. Removes dangerous file name characters: :;*?"<>|\ */ transliterator = Transliterator::createInstance("Latin; NFD; [:Nonspacing Mark:] Remove; NFC; [:^ASCII:] Remove; [\\:;*?\"<>|\\\\] Remove", UTRANS_FORWARD, status); if (!transliterator || status != U_ZERO_ERROR) { cerr << RED << "Fatal: Could not initialize transliterator." << RESET << endl; return EXIT_FAILURE; } + /* We prefer MUSICDIR, but if it isn't set, we fallback to $HOME/Music/. + * We also take care that it ends in a / and doesn't have repeated / chars. */ if (char *base = getenv("MUSICDIR")) { base_destination = base; if (base_destination[base_destination.length() - 1] != '/') @@ -230,6 +277,7 @@ int main(int argc, char *argv[]) return EXIT_FAILURE; } + /* Process all arguments to program as input paths. */ for (int i = 1; i < argc; ++i) { size_t len = strlen(argv[i]); if (argv[i][len - 1] == '/') |