aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJason A. Donenfeld <Jason@zx2c4.com>2012-08-20 17:16:23 +0200
committerJason A. Donenfeld <Jason@zx2c4.com>2012-08-20 17:16:23 +0200
commit6d16ed0b09f6bffa1633c521a0d32ce96108a8c5 (patch)
treea2584e5e1fb85043fa9d1615a40ec666528e3603
parentRearrange headers. (diff)
downloadmusic-file-organizer-6d16ed0b09f6bffa1633c521a0d32ce96108a8c5.tar.xz
music-file-organizer-6d16ed0b09f6bffa1633c521a0d32ce96108a8c5.zip
Nice comments.
-rw-r--r--organizemusic.cpp48
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] == '/')