diff --git a/doc/lkt.1 b/doc/lkt.1
index d1ce04e5380170d681ef25c6670d445a445a2354..73c36811edd4d8ab56b7b51d306c3c4f0fd5fdb1 100644
--- a/doc/lkt.1
+++ b/doc/lkt.1
@@ -129,6 +129,34 @@ Search and prints the number of karas that match the query
 Search in the queue and prints the karas that match the query
 .PP
 
+\fISTICKERS-COMMANDS\fP
+.PP
+.PD 0
+.TP
+.PD
+\fBsticker get\fP <type> <uri> [ <name> [ <op> <value> ] ]
+List the stickers of an object \fIuri\fP. The object \fItype\fP can be
+\fIkara\fP or \fIplt\fP.
+
+A condition can be defined on the value of the sticker with an operator
+\fIop\fP and an integer value \fIvalue\fP. Supported operators are \fI<\fP,
+\fI=\fP and \fI>\fP. Operations are not strict.
+.TP
+\fBsticker set\fP <type> <uri> <name> <value>
+Set the value of a sticker \fIname\fP to \fIvalue\fP for the object with the
+id \fIuri\fP
+.TP
+\fBsticker delete\fP <type> <uri> [name]
+Delete all the stickers or only one (specified by \fIname\fP) of the object with
+the id \fIuri\fP
+.TP
+\fBksticker\fP ...
+An alias for the sticker commands with the type \fIkara\fP
+.TP
+\fBpsticker\fP ...
+An alias for the sticker commands with the type \fIplt\fP
+.PP
+
 \fIADMIN-COMMANDS\fP
 .PP
 .PD 0
diff --git a/inc/lektor/bufferfd.h b/inc/lektor/bufferfd.h
index ffc4aacd4cf2fc56693cb4bdf501d2840c82fa92..8e03dc79903a51e1391d250ff284290e1ecda014 100644
--- a/inc/lektor/bufferfd.h
+++ b/inc/lektor/bufferfd.h
@@ -42,7 +42,7 @@ ssize_t bufferfd_bytes(struct bufferfd *bf, size_t n, uint8_t *res);
 ssize_t bufferfd_byte(struct bufferfd *bf, uint8_t *res);
 
 /* Consume `n` bytes without returning them. */
-ssize_t bufferfd_skip(struct bufferfd *bf, size_t n);
+ssize_t bufferfd_skip(struct bufferfd *bf, const size_t n);
 
 /* Move the file cursor to the absolute position `pos` (0 is the beginning of
  * the file).
diff --git a/inc/lektor/mkv.h b/inc/lektor/mkv.h
index 21b3808c0b7ea7d3d27f99d101787729fafa5de2..e4479001a9e14d8f768074f7087bd483919ab623 100644
--- a/inc/lektor/mkv.h
+++ b/inc/lektor/mkv.h
@@ -37,15 +37,7 @@ struct kara_metadata {
     int song_number;
 };
 
-enum kara_action {
-    kara_action_none    = 0,        /* Do nothing               */
-    kara_action_add     = (1 << 1), /* Add to database          */
-    kara_action_delete  = (1 << 2), /* Delete from database     */
-    kara_action_unavail = (1 << 3), /* Mark kara as unavailable */
-};
-
 struct kara {
-    enum kara_action action;    /* What to do with this kara.       */
     size_t id;                  /* Should never be NULL. NEVER!!    */
     struct kara_metadata mdt;   /* The metadata of the kara.        */
     char filename[PATH_MAX];    /* The path to the matroska file.   */
@@ -55,9 +47,14 @@ struct kara {
    Returns 0 on success and -1 on error. */
 int kara_metadata_read(struct kara_metadata *dst, const char *filename);
 
-/* Write metadata to a mkv file */
+/* Write metadata to a mkv file. Returns 0 on success and -1 on error */
 int kara_metadata_write(struct kara_metadata *mdt, const char *filename, const char *mkvpropedit);
 
+/* Check if the mdt of the kara `filename` is the same as the `mdt`.
+   Returns 0 on success and -1 on error or if the metadata are
+   not equals. */
+int kara_metadata_equals(struct kara_metadata *mdt, const char *filename);
+
 /* Set the metadata for the file according to its path. */
 int metadata_set_file(char *karapath, const char *mkvpropedit);
 
diff --git a/src/main/lkt.c b/src/main/lkt.c
index 87282296a414035f9f0bcbd0674441680e2e2348..267a94b8779921b6539a536b2db46abf370b424d 100644
--- a/src/main/lkt.c
+++ b/src/main/lkt.c
@@ -795,11 +795,19 @@ static struct lkt_cmd_opt options_admin[] = {
     LKT_OPT_NULL,
 };
 
+static struct lkt_cmd_opt options_stickers[] = {
+    { .name = "get",        .call = stickers_get__      },
+    { .name = "set",        .call = stickers_set__      },
+    { .name = "delete",     .call = stickers_delete__   },
+    LKT_OPT_NULL,
+};
+
 #define sub_command(name) /* Create sub-commands here */                            \
     noreturn void name ## __(struct lkt_cmd_args *args) {                           \
         fail_if(!args->argc, "Invalid command, specify a sub command for " #name);  \
         lkt_cmd_parse(options_ ## name, args->argc, args->argv);                    \
     }
+sub_command(stickers)
 sub_command(search)
 sub_command(plt)
 sub_command(admin)
@@ -825,6 +833,7 @@ static struct lkt_cmd_opt options_[] = {
     { .name = "plt",      .call = plt__      },
     { .name = "search",   .call = search__   },
     { .name = "admin",    .call = admin__    },
+    { .name = "stickers", .call = stickers__ },
     LKT_OPT_NULL,
 };
 
diff --git a/src/mkv/bufferfd.c b/src/mkv/bufferfd.c
index f41eefd501c3a9ad9f5b504e003a8359a6032849..3de8ac4eee492bdc2ebc81b5aea142969e4639f1 100644
--- a/src/mkv/bufferfd.c
+++ b/src/mkv/bufferfd.c
@@ -5,8 +5,8 @@
 
 #include <lektor/bufferfd.h>
 
-static ssize_t
-bufferfd_fill(struct bufferfd *bf)
+static inline ssize_t
+__fill(struct bufferfd *bf)
 {
     if (bf->len <= bf->pos) {
         ssize_t n = read(bf->fd, bf->buffer, BUFFER_MAX);
@@ -24,8 +24,7 @@ bufferfd_fill(struct bufferfd *bf)
 ssize_t
 bufferfd_bytes(struct bufferfd *bf, size_t n, uint8_t *res)
 {
-    // TODO repeat until n is reached
-    ssize_t r = bufferfd_fill(bf);
+    ssize_t r = __fill(bf); /* TODO repeat until n is reached */
 
     if (r < (ssize_t) n)
         return -1;
@@ -41,14 +40,19 @@ bufferfd_byte(struct bufferfd *bf, uint8_t *res)
     return bufferfd_bytes(bf, 1, res);
 }
 
-ssize_t
-bufferfd_skip(struct bufferfd *bf, size_t n)
+static inline ssize_t
+__skip(struct bufferfd *bf, const size_t n, const size_t b_size)
 {
-    // TODO less dirty bomb
-    uint8_t b[4096];
+    uint8_t b[b_size];
     return bufferfd_bytes(bf, n, b);
 }
 
+ssize_t
+bufferfd_skip(struct bufferfd *bf, const size_t n)
+{
+    return __skip(bf, n, n > BUFFER_MAX ? BUFFER_MAX : n);
+}
+
 ssize_t
 bufferfd_seek(struct bufferfd *bf, size_t pos)
 {
diff --git a/src/mkv/mkv.c b/src/mkv/mkv.c
index fe1b6fff80086c99968c79fb866793a318fc5800..6b0623bbd9d833a9e9c89e674b8539a7cd18feb5 100644
--- a/src/mkv/mkv.c
+++ b/src/mkv/mkv.c
@@ -23,14 +23,14 @@
 #define EBML_MKV_TAGS        0x1254c367
 #define EBML_MKV_TAG         0x00007373
 #define EBML_MKV_TAG_TARGETS 0x000063c0
-#define EBML_MKV_TAG_TTV     0x000068ca   // TargetTypeValue
-#define EBML_MKV_TAG_SIMPLE  0x000067c8   // SimpleTag
+#define EBML_MKV_TAG_TTV     0x000068ca   /* TargetTypeValue */
+#define EBML_MKV_TAG_SIMPLE  0x000067c8   /* SimpleTag */
 #define EBML_MKV_TAG_NAME    0x000045a3
 #define EBML_MKV_TAG_STRING  0x00004487
 #define EBML_MKV_TAG_BINARY  0x00004485
 
-#define EBML_MKV_CRC32      0x0000000bf   // CRC-32, they skip it in mpv (demux/ebml.c:463 aprox)
-#define EBML_MKV_VOID       0x0000000ec   // VOID element
+#define EBML_MKV_CRC32      0x0000000bf   /* CRC-32, they skip it in mpv (demux/ebml.c:463 aprox) */
+#define EBML_MKV_VOID       0x0000000ec   /* VOID element */
 
 /* mkv_read_* functions take data from the bufferfd and parse the next "*" (one
  * of the following):
@@ -537,3 +537,19 @@ error:
     close(file.fd);
     return status_code;
 }
+
+int
+kara_metadata_equals(struct kara_metadata *mdt, const char *filename)
+{
+    struct kara_metadata kara_mdt;  /* FIXME: Less dirty bomb */
+    if (kara_metadata_read(&kara_mdt, filename))
+        return -1;
+    return ! (STR_MATCH(kara_mdt.song_name, mdt->song_name)     &&
+              STR_MATCH(kara_mdt.source_name, mdt->source_name) &&
+              STR_MATCH(kara_mdt.category, mdt->category)       &&
+              STR_MATCH(kara_mdt.language, mdt->language)       &&
+              STR_MATCH(kara_mdt.author_name, mdt->author_name) &&
+              STR_MATCH(kara_mdt.song_type, mdt->song_type)     &&
+              kara_mdt.song_number == mdt->song_number);
+}
+
diff --git a/src/mkv/write.c b/src/mkv/write.c
index d366c6bf3dc86999c5bba1357d72b5e771efb711..7faa39996cb8a41d77b0de36ca0004f032e48fbf 100644
--- a/src/mkv/write.c
+++ b/src/mkv/write.c
@@ -108,8 +108,7 @@ kara_metadata_write(struct kara_metadata *mdt, const char *filename, const char
     char *const metadafilepath = &tmpfilepath[4];
     const char *const args1[] = { mkvpropedit, "-t", "all:", filename, NULL };
     const char *const args2[] = { mkvpropedit, "-t", tmpfilepath, filename, NULL };
-    int fd;
-    bool sta = false;
+    int fd, sta = -1;
 
     memset(tmpfilepath, 0, PATH_MAX);
     strncat(tmpfilepath, "all:/tmp/lektor.metadata.XXXXXX", PATH_MAX - 1);
@@ -128,7 +127,7 @@ kara_metadata_write(struct kara_metadata *mdt, const char *filename, const char
     if (!mkvpropedit__(args1) || !mkvpropedit__(args2))
         goto error;
 
-    sta = true;
+    sta = 0;
 error:
     close(fd);
     unlink(metadafilepath);
@@ -207,7 +206,7 @@ metadata_set_directory(const char *kara_dir, const char *mkvpropedit)
 
         if (dir->d_type == DT_REG               &&
             metadata_from_path(path, &meta)     &&
-            kara_metadata_write(&meta, path, mkvpropedit))
+            ! kara_metadata_write(&meta, path, mkvpropedit))
             continue;
         else if (dir->d_type == DT_DIR          &&
                  !STR_MATCH(dir->d_name, ".")   &&
@@ -226,6 +225,6 @@ metadata_set_file(char *karapath, const char *mkvpropedit)
     struct kara_metadata meta;
 
     return metadata_from_path(karapath, &meta) &&
-           kara_metadata_write(&meta, karapath, mkvpropedit);
+           ! kara_metadata_write(&meta, karapath, mkvpropedit);
 }
 
diff --git a/src/net/downloader.c b/src/net/downloader.c
index b25ebac890df8e24b9ce60e3f6a43c0010beda0b..a076deef22967bc8079f39795a66b0689b4b90b8 100644
--- a/src/net/downloader.c
+++ b/src/net/downloader.c
@@ -325,7 +325,10 @@ __handle_got_json(volatile sqlite3 *db, struct lkt_repo *repo, struct json_objec
     struct kara kara;
     char url[URL_MAX_LEN];
     long filestamp, timestamp;
+    char mkvpropedit[PATH_MAX];
     RETURN_UNLESS(len > 0 && json_object_get_array(json), "Json invalid or array empty", NOTHING);
+    RETURN_UNLESS(database_config_get_text(db, "externals", "mkvpropedit", mkvpropedit, PATH_MAX - 1),
+                  "Can't get the mkvpropedit executable path", NOTHING);
 
     LOG_INFO_SCT("REPO", "Starting to process json for repo %s", repo->name);
     for (i = 0; i < len; ++i) {
@@ -346,25 +349,29 @@ __handle_got_json(volatile sqlite3 *db, struct lkt_repo *repo, struct json_objec
         }
         safe_snprintf(kara.filename + kara_dir_len, PATH_MAX - kara_dir_len, "%d.mkv", integer);
 
+        /* Reads the json */
+        err |= safe_json_get_string(kara_json, "song_name",   kara.mdt.song_name,   LEKTOR_TAG_MAX);
+        err |= safe_json_get_string(kara_json, "source_name", kara.mdt.source_name, LEKTOR_TAG_MAX);
+        err |= safe_json_get_string(kara_json, "category",    kara.mdt.category,    LEKTOR_TAG_MAX);
+        err |= safe_json_get_string(kara_json, "language",    kara.mdt.language,    LEKTOR_TAG_MAX);
+        err |= safe_json_get_string(kara_json, "author_name", kara.mdt.author_name, LEKTOR_TAG_MAX);
+        err |= safe_json_get_string(kara_json, "song_type",   kara.mdt.song_type,   LEKTOR_TAG_MAX);
+        if (err || safe_json_get_int32(kara_json, "song_number", &kara.mdt.song_number)) {
+            LOG_WARN_SCT("REPO", "Json is invalid for kara '%ld', skip it", kara.id);
+            continue;
+        }
+
         /* Timestamp verification */
         if (safe_json_get_long(kara_json, "unix_timestamp", &timestamp))
             continue;
         filestamp = get_mtime(kara.filename);
-        if (database_get_timestamp(db) >= filestamp && filestamp > timestamp) {
+        if (database_get_timestamp(db) >= filestamp && filestamp > timestamp &&
+            ! kara_metadata_equals(&kara.mdt, kara.filename)) {
             LOG_INFO_SCT("REPO", "Ignore kara '%ld' with path '%s'", kara.id, kara.filename);
             continue;
         }
         LOG_WARN_SCT("REPO", "Download kara '%ld' with path '%s'", kara.id, kara.filename);
 
-        err |= safe_json_get_string(kara_json, "song_name",   kara.mdt.song_name,   LEKTOR_TAG_MAX);
-        err |= safe_json_get_string(kara_json, "source_name", kara.mdt.source_name, LEKTOR_TAG_MAX);
-        err |= safe_json_get_string(kara_json, "category",    kara.mdt.category,    LEKTOR_TAG_MAX);
-        err |= safe_json_get_string(kara_json, "language",    kara.mdt.language,    LEKTOR_TAG_MAX);
-        err |= safe_json_get_string(kara_json, "author_name", kara.mdt.author_name, LEKTOR_TAG_MAX);
-        err |= safe_json_get_string(kara_json, "song_type",   kara.mdt.song_type,   LEKTOR_TAG_MAX);
-        RETURN_IF(err || safe_json_get_int32(kara_json, "song_number", &kara.mdt.song_number),
-                  "Invalid json", NOTHING);
-
         if (!database_update_add((sqlite3 *) db, kara.filename, &kara.mdt, kara.id, false)) {
             LOG_ERROR_SCT("REPO", "Could not add unavailable kara %ld to db", kara.id);
             continue;
@@ -377,6 +384,11 @@ __handle_got_json(volatile sqlite3 *db, struct lkt_repo *repo, struct json_objec
             continue;
         }
 
+        if (kara_metadata_write(&kara.mdt, kara.filename, mkvpropedit)) {
+            LOG_WARN_SCT("REPO", "Could not write metadata to kara '%ld' with path '%s'", kara.id, kara.filename);
+            continue;
+        }
+
         if (!database_update_set_available((sqlite3 *) db, kara.id)) {
             LOG_WARN_SCT("REPO", "Could not set kara %ld available", kara.id);
             continue;