diff --git a/database/init.sql b/database/init.sql
index 93f4069db6b6aeb9cea0b9cc6ccf58ac22560cd0..c131ebded8cc8b632a818ec14ff01be6d4828fa1 100644
--- a/database/init.sql
+++ b/database/init.sql
@@ -42,7 +42,7 @@ CREATE TABLE IF NOT EXISTS language
   );
 
 INSERT INTO language (name) VALUES
-  ('jp'), ('fr'), ('en'), ('ru'), ('sp'), ('it'), ('ch');
+  ('jp'), ('fr'), ('en'), ('ru'), ('sp'), ('it'), ('ch'), ('multi'), ('undefined');
 
 -- Playlists are sets of karas that are meant to be played together, kinda like
 -- M3U except it's stored in a SQL database.
diff --git a/player/db/update.c b/player/db/update.c
index db428e1b67a5541d80e05bcffdf533f049c18dc1..1cf6848d562cfb15692607b834265fa15900111d 100644
--- a/player/db/update.c
+++ b/player/db/update.c
@@ -11,238 +11,267 @@
 
 static const char SQL_LAST_UPDATE[] = "SELECT last_update FROM misc;";
 static const char SQL_INSERT_OLD_KARA[] =
-        "INSERT INTO "
-        "kara (song_name, source_name, category, language, file_path, is_new) "
-        "VALUES (?, ?, ?, ?, ?, 0)";
+  "INSERT INTO "
+  "kara (song_name, source_name, category, language, file_path, is_new) "
+  "VALUES (?, ?, ?, ?, ?, 0)";
 static const char SQL_INSERT_NEW_KARA[] =
-        "INSERT INTO "
-        "kara (song_name, source_name, category, language, file_path,"
-        "      is_new, author_name) "
-        "VALUES (?, ?, ?, ?, ?, 1, ?)";
+  "INSERT INTO "
+  "kara (song_name, source_name, category, language, file_path,"
+  "      is_new, author_name) "
+  "VALUES (?, ?, ?, ?, ?, 1, ?)";
 
-static inline void *
-mallocf(size_t n)
+static inline void * mallocf(size_t n)
 {
-    void *res = malloc(n);
-    if (!res)
-        fprintf(stderr, "Failed to allocate %ld bytes.\n", n);
-    return res;
-}
+  void * res = malloc(n);
 
-static inline void *
-reallocf(void *ptr, size_t n)
-{
-    void *res = realloc(ptr, n);
-    if (!res)
-        fprintf(stderr, "Failed to allocate %ld bytes.\n", n);
-    return res;
-}
+  if (!res)
+  { fprintf(stderr, "Failed to allocate %ld bytes.\n", n); }
 
-static int
-directories_and_files(const struct dirent *entry)
-{
-    return (entry->d_type == DT_REG || entry->d_type == DT_DIR) &&
-            entry->d_name[0] != '.';
+  return res;
 }
 
-static int
-update_legacy_file(sqlite3 *db, const char *filename, size_t prefix, time_t last_update)
+static inline void * reallocf(void * ptr, size_t n)
 {
-    struct stat attrs;
-    if (stat(filename, &attrs) < 0)
-        return -1;
-    if (attrs.st_mtim.tv_sec < last_update)
-        return 0;
-
-    const char *pseudo = 0,
-               *category = 0,
-               *name = 0,
-               *type = 0,
-               *title = 0;
-    size_t pseudo_len = 0,
-           category_len = 0,
-           name_len = 0,
-           type_len = 0,
-           title_len = 0;
-
-    const char *file_path = filename + prefix;
-    const char *f = file_path;
-    category_len = strcspn(f, "/");
-    if (!strncmp(f, "nouveau", category_len)) {
-        // "Nouveau" kara, in nouveaux/{pseudo}/{category}/...
-        f += category_len + 1;  // Skip "nouveaux/"
-        pseudo_len = strcspn(f, "/");
-        pseudo = f;
-        f += pseudo_len + 1;  // Skip "{pseudo}/"
-        category_len = strcspn(f, "/");
-    }
-
-    category = f;
-    f += category_len + 1;  // Skip "{category}/"
-    name_len = strcspn(f, "-");
-    name = f;
-    f += name_len + 1;
-    type_len = strcspn(f, "-");
-    type = f;
-    f += type_len + 1;
-    title_len = strcspn(f, ".");
-    title = f;
-
-    if (!title) {
-        fprintf(stderr, "Bad file path: '%s'.\n", filename);
-        return -1;
-    }
-
-    int status_code = -1;
-    sqlite3_stmt *stmt = 0;
-    const char *sql = pseudo ? SQL_INSERT_NEW_KARA : SQL_INSERT_OLD_KARA;
-
-    if (sqlite3_prepare_v2(db, sql, -1, &stmt, 0) != SQLITE_OK) {
-        serror(db, "Failed to prepare statement");
-        goto error;
-    }
-
-    if (sqlite3_bind_text(stmt, 0, name, name_len, 0) != SQLITE_OK) {
-        serror(db, "Failed to bind song_name");
-        goto error;
-    }
-
-    if (sqlite3_bind_text(stmt, 0, title, title_len, 0) != SQLITE_OK) {
-        serror(db, "Failed to bind song_source");
-        goto error;
-    }
+  void * res = realloc(ptr, n);
 
-    if (sqlite3_bind_text(stmt, 0, category, -1, 0) != SQLITE_OK) {
-        serror(db, "Failed to bind category");
-        goto error;
-    }
-
-    if (sqlite3_bind_text(stmt, 0, type, -1, 0) != SQLITE_OK) {
-        serror(db, "Failed to bind type");
-        goto error;
-    }
-
-    if (sqlite3_bind_text(stmt, 0, file_path, -1, 0) != SQLITE_OK) {
-        serror(db, "Failed to bind file_path");
-        goto error;
-    }
+  if (!res)
+  { fprintf(stderr, "Failed to allocate %ld bytes.\n", n); }
 
-    if (sqlite3_step(stmt) != SQLITE_DONE) {
-        // TODO handle SQLITE_BUSY (should rollback)
-        serror(db, "Failed to execute insert statement");
-        goto error;
-    }
-
-    status_code = 0;
-error:
-    sqlite3_finalize(stmt);
-    return status_code;
+  return res;
 }
 
-static int
-update_directory(sqlite3 *db, const char *root, time_t last_update)
+static int directories_and_files(const struct dirent * entry)
 {
-    int status_code = -1;
-    const size_t root_len = strlen(root) + 1;
-    char **queue = 0;
-    size_t queue_max = 128;
-    size_t queue_len = 0;
-
-    queue = (char**) mallocf(queue_max * sizeof(char *));
-    if (!queue)
-        goto error;
-
-    queue[0] = strdup(root);
-    queue_len++;
-
-    while (queue_len) {
-        struct dirent **children;
-        int n;
-        char *dir = queue[--queue_len];
-
-        n = scandir(dir, &children, directories_and_files, alphasort);
-        if (n < 0)
-            fprintf(stderr, "Failed to open '%s': %s.\n", dir, strerror(errno));
-
-        for (int i = 0; i < n; i++) {
-            size_t dir_len = strlen(dir);
-
-            // child length (256) + null byte + slash
-            char *child = (char*) mallocf(dir_len + 258);
-            if (!child)
-                goto error;
-
-            memcpy(child, dir, dir_len);
-            child[dir_len] = '/';
-            strcpy(child + dir_len + 1, children[i]->d_name);
-
-            if (children[i]->d_type == DT_REG) {
-                if (update_legacy_file(db, child, root_len, last_update) < 0)
-                    fprintf(stderr, "Failed to add '%s'.\n", child);
-                free(child);
-            } else if (queue_len == queue_max) {
-                queue_max *= 2;
-                char **new_queue = (char**) reallocf(queue, queue_max * sizeof(char *));
-                if (!new_queue)
-                    goto error;
-                queue = new_queue;
-                queue[queue_len++] = child;
-            } else {
-                queue[queue_len++] = child;
-            }
-        }
-    }
+  return (entry->d_type == DT_REG || entry->d_type == DT_DIR) &&
+         entry->d_name[0] != '.';
+}
 
-    status_code = 0;
+static int update_legacy_file(sqlite3 * db, const char * filename, size_t prefix, time_t last_update)
+{
+  struct stat attrs;
+
+  if (stat(filename, &attrs) < 0)
+  { return -1; }
+
+  if (attrs.st_mtim.tv_sec < last_update)
+  { return 0; }
+
+  const char * pseudo = 0;
+  const char * category = 0;
+  const char * name = 0;
+  const char * type = 0;
+  const char * title = 0;
+  size_t pseudo_len = 0,
+         category_len = 0,
+         name_len = 0,
+         type_len = 0,
+         title_len = 0;
+  const char * file_path = filename + prefix;
+  const char * f = file_path;
+  category_len = strcspn(f, "/");
+
+  if (!strncmp(f, "nouveau", category_len))
+  {
+    // "Nouveau" kara, in nouveaux/{pseudo}/{category}/...
+    f += category_len + 1;  // Skip "nouveaux/"
+    pseudo_len = strcspn(f, "/");
+    pseudo = f;
+    f += pseudo_len + 1;  // Skip "{pseudo}/"
+    category_len = strcspn(f, "/");
+  }
+
+  category = f;
+  f += category_len + 1;  // Skip "{category}/"
+  name_len = strcspn(f, "-");
+  name = f;
+  f += name_len + 1;
+  type_len = strcspn(f, "-");
+  type = f;
+  f += type_len + 1;
+  title_len = strcspn(f, ".");
+  title = f;
+
+  if (!title)
+  {
+    fprintf(stderr, "Bad file path: '%s'.\n", filename);
+    return -1;
+  }
+
+  int status_code = -1;
+  sqlite3_stmt * stmt = 0;
+  const char * sql = pseudo ? SQL_INSERT_NEW_KARA : SQL_INSERT_OLD_KARA;
+
+  if (sqlite3_prepare_v2(db, sql, -1, &stmt, 0) != SQLITE_OK)
+  {
+    serror(db, "Failed to prepare statement");
+    goto error;
+  }
+
+  if (sqlite3_bind_text(stmt, 0, name, name_len, 0) != SQLITE_OK)
+  {
+    serror(db, "Failed to bind song_name");
+    goto error;
+  }
+
+  if (sqlite3_bind_text(stmt, 0, title, title_len, 0) != SQLITE_OK)
+  {
+    serror(db, "Failed to bind song_source");
+    goto error;
+  }
+
+  if (sqlite3_bind_text(stmt, 0, category, -1, 0) != SQLITE_OK)
+  {
+    serror(db, "Failed to bind category");
+    goto error;
+  }
+
+  if (sqlite3_bind_text(stmt, 0, type, -1, 0) != SQLITE_OK)
+  {
+    serror(db, "Failed to bind type");
+    goto error;
+  }
+
+  if (sqlite3_bind_text(stmt, 0, file_path, -1, 0) != SQLITE_OK)
+  {
+    serror(db, "Failed to bind file_path");
+    goto error;
+  }
+
+  status_code = sqlite3_step(stmt);
+  if (status_code != SQLITE_DONE)
+  {
+    // TODO handle SQLITE_BUSY (should rollback)
+    serror(db, "Failed to execute insert statement");
+    goto error;
+  }
+
+  status_code = 0;
 error:
-    for (size_t i = 0; i < queue_len; i++)
-        free(queue[i]);
-    free(queue);
-    return status_code;
+  sqlite3_finalize(stmt);
+  return status_code;
 }
 
-int
-lektor_db_update(sqlite3 *db, const char *directory)
+static int update_directory(sqlite3 * db, const char * root, time_t last_update)
 {
-    if (sqlite3_exec(db, "BEGIN TRANSACTION;", 0, 0, 0) != SQLITE_OK) {
-        serror(db, "Failed to start transaction");
-        return -1;
+  int status_code = -1;
+  const size_t root_len = strlen(root) + 1;
+  char ** queue = 0;
+  size_t queue_max = 128;
+  size_t queue_len = 0;
+  queue = (char **) mallocf(queue_max * sizeof(char *));
+
+  if (!queue)
+  { goto error; }
+
+  queue[0] = strdup(root);
+  queue_len++;
+
+  while (queue_len)
+  {
+    struct dirent ** children;
+    int n;
+    char * dir = queue[--queue_len];
+    n = scandir(dir, &children, directories_and_files, alphasort);
+
+    if (n < 0)
+    { fprintf(stderr, "Failed to open '%s': %s.\n", dir, strerror(errno)); }
+
+    for (int i = 0; i < n; i++)
+    {
+      size_t dir_len = strlen(dir);
+      // child length (256) + null byte + slash
+      char * child = (char *) mallocf(dir_len + 258);
+
+      if (!child)
+      { goto error; }
+
+      memcpy(child, dir, dir_len);
+      child[dir_len] = '/';
+      strcpy(child + dir_len + 1, children[i]->d_name);
+
+      if (children[i]->d_type == DT_REG)
+      {
+        if (update_legacy_file(db, child, root_len, last_update) < 0)
+        { fprintf(stderr, "Failed to add '%s'.\n", child); }
+
+        free(child);
+      }
+      else if (queue_len == queue_max)
+      {
+        queue_max *= 2;
+        char ** new_queue = (char **) reallocf(queue, queue_max * sizeof(char *));
+
+        if (!new_queue)
+        { goto error; }
+
+        queue = new_queue;
+        queue[queue_len++] = child;
+      }
+      else
+      {
+        queue[queue_len++] = child;
+      }
     }
+  }
 
-    sqlite3_stmt *stmt = 0;
-    time_t last_update = 0;
-
-    if (sqlite3_prepare_v2(db, SQL_LAST_UPDATE, -1, &stmt, 0) != SQLITE_OK) {
-        serror(db, "Failed to get last update time");
-        goto error;
-    }
-    switch (sqlite3_step(stmt)) {
-    case SQLITE_ROW:
-        last_update = (time_t) sqlite3_column_int(stmt, 0);
-        break;
-    case SQLITE_DONE:
-        fprintf(stderr, "Failed to get last update time: table misc is empty");
-        goto error;
-    default:
-        serror(db, "Failed to get last update time");
-        goto error;
-    }
+  status_code = 0;
+error:
 
-    if (update_directory(db, directory, last_update) < 0)
-        goto error;
+  for (size_t i = 0; i < queue_len; i++)
+  { free(queue[i]); }
 
-    if (sqlite3_exec(db, "COMMIT;", 0, 0, 0) != SQLITE_OK) {
-        serror(db, "Failed to commit transaction");
-        goto error;
-    }
-
-    sqlite3_finalize(stmt);
-    return 0;
+  free(queue);
+  return status_code;
+}
 
+int lektor_db_update(sqlite3 * db, const char * directory)
+{
+  if (sqlite3_exec(db, "BEGIN TRANSACTION;", 0, 0, 0) != SQLITE_OK)
+  {
+    serror(db, "Failed to start transaction");
+    return -1;
+  }
+
+  sqlite3_stmt * stmt = 0;
+  time_t last_update = 0;
+
+  if (sqlite3_prepare_v2(db, SQL_LAST_UPDATE, -1, &stmt, 0) != SQLITE_OK)
+  {
+    serror(db, "Failed to get last update time");
+    goto error;
+  }
+
+  switch (sqlite3_step(stmt))
+  {
+  case SQLITE_ROW:
+    last_update = (time_t) sqlite3_column_int(stmt, 0);
+    break;
+
+  case SQLITE_DONE:
+    fprintf(stderr, "Failed to get last update time: table misc is empty");
+    goto error;
+
+  default:
+    serror(db, "Failed to get last update time");
+    goto error;
+  }
+
+  if (update_directory(db, directory, last_update) < 0)
+  { goto error; }
+
+  if (sqlite3_exec(db, "COMMIT;", 0, 0, 0) != SQLITE_OK)
+  {
+    serror(db, "Failed to commit transaction");
+    goto error;
+  }
+
+  sqlite3_finalize(stmt);
+  return 0;
 error:
+  sqlite3_finalize(stmt);
 
-    sqlite3_finalize(stmt);
-    if (sqlite3_exec(db, "ROLLBACK;", 0, 0, 0) != SQLITE_OK)
-        serror(db, "Failed to rollback transaction, database is corrupted");
-    return -1;
+  if (sqlite3_exec(db, "ROLLBACK;", 0, 0, 0) != SQLITE_OK)
+  { serror(db, "Failed to rollback transaction, database is corrupted"); }
+
+  return -1;
 }
diff --git a/player/db/utils.c b/player/db/utils.c
index 0683fd22b0483b31b677577f84d50687613f0240..83f04df2b1d16ae4032be421e7047ebdeec435c1 100644
--- a/player/db/utils.c
+++ b/player/db/utils.c
@@ -1,7 +1,6 @@
 #include "utils.h"
 
-void
-serror(sqlite3 *db, const char *msg)
+void serror(sqlite3 * db, const char * msg)
 {
-    fprintf(stderr, "%s: %s\n", msg, sqlite3_errmsg(db));
+  fprintf(stderr, "%s: %s\n", msg, sqlite3_errmsg(db));
 }