Git Inbox Mirror of the ffmpeg-devel mailing list - see https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
 help / color / mirror / Atom feed
* [FFmpeg-devel] [PATCH] avutil/map: replace passing Compare functions by flags
@ 2025-04-16 20:12 Michael Niedermayer
  2025-04-16 20:22 ` softworkz .
  0 siblings, 1 reply; 3+ messages in thread
From: Michael Niedermayer @ 2025-04-16 20:12 UTC (permalink / raw)
  To: FFmpeg development discussions and patches

This makes the API much more robust because if you receive a map
from some other module you no longer need to know which
compare function is correct for it
Instead you just specify what you need, like
AV_MAP_CMP_CASE_SENSITIVE or AV_MAP_CMP_CASE_INSENSITIVE or 0 if you dont care
and the code will either do what you asked for or cleanly fail if its unable to
previously it would just give you a wrong result sometimes

Signed-off-by: Michael Niedermayer <michael@niedermayer.cc>
---
 libavutil/map.c       | 80 +++++++++++++++++++++++++++++++++++--------
 libavutil/map.h       | 53 +++++++++++++++++++++-------
 libavutil/tests/map.c | 45 ++++++++++++++++--------
 3 files changed, 137 insertions(+), 41 deletions(-)

diff --git a/libavutil/map.c b/libavutil/map.c
index 00dd5a1bd39..950473c2c45 100644
--- a/libavutil/map.c
+++ b/libavutil/map.c
@@ -35,7 +35,8 @@ typedef struct{
 } AVMapInternalEntry;
 
 struct AVMap{
-    AVMapCompareFunc cmp_keyvalue;
+#define CMP_MASK 31
+    AVMapCompareFunc cmp[27];
     AVMapCopyFunc copy;
     AVMapFreeFunc freef;
     int count;
@@ -98,24 +99,71 @@ int av_map_supercmp_keyvalue(const char *a, const char *b)
     return v;
 }
 
-AVMap *av_map_new(AVMapCompareFunc cmp_keyvalue, AVMapCopyFunc copy, AVMapFreeFunc freef)
+int av_map_add_cmp_func(AVMap *m, AVMapCompareFunc cmp, int cmp_flags)
+{
+    static const uint8_t sensitivity[27][3] = {
+        {0,0, 0},{1,0, 0},{2,0, 0}, {0,3, 0},{1,3, 0},{2,3, 0}, {0,6, 0},{1,6, 0},{2,6, 0},
+        {0,0, 9},{1,0, 9},{2,0, 9}, {0,3, 9},{1,3, 9},{2,3, 9}, {0,6, 9},{1,6, 9},{2,6, 9},
+        {0,0,18},{1,0,18},{2,0,18}, {0,3,18},{1,3,18},{2,3,18}, {0,6,18},{1,6,18},{2,6,18},};
+    int case_sensitive       =  sensitivity[cmp_flags][0];
+    int keyvalue_sensitive   =  sensitivity[cmp_flags][1];
+    int truncated_sensitive  =  sensitivity[cmp_flags][2];
+
+    if (!keyvalue_sensitive || !truncated_sensitive || cmp_flags >= 27U)
+        return AVERROR(EINVAL);
+
+    av_assert1(case_sensitive + keyvalue_sensitive + truncated_sensitive == cmp_flags);
+
+    if (     case_sensitive == AV_MAP_CMP_CASE_SENSITIVE && m->cmp[keyvalue_sensitive + AV_MAP_CMP_CASE_INSENSITIVE])
+        return AVERROR(EINVAL);
+    if ( keyvalue_sensitive == AV_MAP_CMP_KEYVALUE       && m->cmp[AV_MAP_CMP_KEY])
+        return AVERROR(EINVAL);
+    if (truncated_sensitive == AV_MAP_CMP_NON_TRUNCATED  && m->cmp[keyvalue_sensitive + AV_MAP_CMP_TRUNCATED])
+        return AVERROR(EINVAL);
+
+    //max functions is KV NT CS -> KV NT CI -> KV T CI (CI/T is about value only) -> K NT CS -> K NT CI -> K T CI
+    //missing is KV T CS and K T CS, with them we can have KV NT CS -> KV T CS -> K NT CS -> K T CS
+
+    for (int i=0; i<8; i++) {
+        int flags = 0;
+        if (i&1) flags += case_sensitive;
+        if (i&2) flags += keyvalue_sensitive;
+        if (i&4) flags += truncated_sensitive;
+
+        if (!m->cmp[flags])
+            m->cmp[flags] = cmp;
+    }
+    return 0;
+}
+
+int av_map_is_cmp_flags_supported(AVMap *m, int cmp_flags)
+{
+    if (cmp_flags >= 27U)
+        return AVERROR(EINVAL);
+    return !!m->cmp[cmp_flags];
+}
+
+AVMap *av_map_new(AVMapCompareFunc cmp_keyvalue, int cmp_flags, AVMapCopyFunc copy, AVMapFreeFunc freef)
 {
     AVMap *s = av_mallocz(sizeof(*s));
     if (!s)
         return NULL;
 
-    s->cmp_keyvalue  = cmp_keyvalue;
     s->copy          = copy;
     s->freef         = freef;
 
+    av_map_add_cmp_func(s, cmp_keyvalue, cmp_flags);
+
     return s;
 }
 
-const AVMapEntry *av_map_get_multiple(const AVMap *s, const AVMapEntry *prev, const char *keyvalue, int (*cmp)(const void *keyvalue, const void *b))
+const AVMapEntry *av_map_get_multiple(const AVMap *s, const AVMapEntry *prev, const char *keyvalue, int flags)
 {
+    AVMapCompareFunc cmp = s->cmp[flags & CMP_MASK];
+
     if (prev) {
         void *next_node[2] = { NULL, NULL };
-        void *prev_keyvalue = av_tree_find2(s->tree_root, prev->key, s->cmp_keyvalue, next_node, 2);
+        void *prev_keyvalue = av_tree_find2(s->tree_root, prev->key, s->cmp[0], next_node, 2);
         av_assert2(prev_keyvalue);
         if (!next_node[1] || cmp(next_node[1], keyvalue))
             return NULL;
@@ -134,8 +182,10 @@ const AVMapEntry *av_map_get_multiple(const AVMap *s, const AVMapEntry *prev, co
     return &keyvalue2internal(keyvalue)->map_entry;
 }
 
-const AVMapEntry *av_map_get(const AVMap *s, const char *keyvalue, int (*cmp)(const void *keyvalue, const void *b))
+const AVMapEntry *av_map_get(const AVMap *s, const char *keyvalue, int flags)
 {
+    AVMapCompareFunc cmp = s->cmp[flags & CMP_MASK];
+
     keyvalue = av_tree_find2(s->tree_root, keyvalue, cmp, NULL, 0);
 
     if (!keyvalue)
@@ -192,15 +242,15 @@ int av_map_add(AVMap *s, const char *key, int keylen, const char *value, int val
     memcpy(entry->key  , key  , keylen);
     memcpy(entry->value, value, valuelen);
 
-    void *elem = av_tree_insert(&s->tree_root, entry->key, s->cmp_keyvalue, &next);
+    void *elem = av_tree_insert(&s->tree_root, entry->key, s->cmp[0], &next);
     int ret = 1;
     if (elem != entry->key && elem) {
         av_assert2(next);
         //we assume that new entries are more common than replacements
         if (flags & AV_MAP_REPLACE) {
-            ret = av_map_del(s, entry->key, s->cmp_keyvalue, flags);
+            ret = av_map_del(s, entry->key, flags & ~CMP_MASK);
             av_assert2(ret == 1);
-            elem = av_tree_insert(&s->tree_root, entry->key, s->cmp_keyvalue, &next);
+            elem = av_tree_insert(&s->tree_root, entry->key, s->cmp[0], &next);
             av_assert2(elem == entry->key || !elem);
             ret = 2;
         } else
@@ -214,12 +264,13 @@ int av_map_add(AVMap *s, const char *key, int keylen, const char *value, int val
     return ret;
 }
 
-int av_map_del(AVMap *s, const char *keyvalue, int (*cmp)(const void *keyvalue, const void *b), int flags)
+int av_map_del(AVMap *s, const char *keyvalue, int flags)
 {
     uint8_t *old_keyvalue;
     AVTreeNode *next = NULL;
+    AVMapCompareFunc cmp = s->cmp[flags & CMP_MASK];
 
-    if (cmp != s->cmp_keyvalue) {
+    if (cmp != s->cmp[0]) {
         // The user asks us to remove a entry with a compare function different from the one used to build the map
         // we need to do 2 calls here, first with the users compare to find the entry she wants to remove
         // and then to remove it while maintaining the correct order within the map
@@ -227,10 +278,10 @@ int av_map_del(AVMap *s, const char *keyvalue, int (*cmp)(const void *keyvalue,
         if (!old_keyvalue)
             return 0;
 
-        av_tree_insert(&s->tree_root, old_keyvalue, s->cmp_keyvalue, &next);
+        av_tree_insert(&s->tree_root, old_keyvalue, s->cmp[0], &next);
         av_assert2(next);
     } else {
-        av_tree_insert(&s->tree_root, (char*)keyvalue, s->cmp_keyvalue, &next);
+        av_tree_insert(&s->tree_root, (char*)keyvalue, s->cmp[0], &next);
         if (!next)
             return 0;
         old_keyvalue = next->elem; //TODO add a API to av_tree() to return the elem of a AVTreeNode
@@ -243,8 +294,9 @@ int av_map_del(AVMap *s, const char *keyvalue, int (*cmp)(const void *keyvalue,
     s->deleted++;
 
     if ((flags & AV_MAP_ALLOW_REBUILD) && s->deleted > s->count) {
-        AVMap *news = av_map_new(s->cmp_keyvalue, s->copy, s->freef);
+        AVMap *news = av_map_new(s->cmp[0], AV_MAP_CMP_KEYVALUE + AV_MAP_CMP_NON_TRUNCATED, s->copy, s->freef);
         if(news) {
+            memcpy(news->cmp, s->cmp, sizeof(news->cmp));
             int ret = av_map_copy(news, s);
             if (ret < 0) {
                 av_map_free(&news);
diff --git a/libavutil/map.h b/libavutil/map.h
index 0c660260017..10f541cc5ba 100644
--- a/libavutil/map.h
+++ b/libavutil/map.h
@@ -46,8 +46,17 @@
  */
 
 enum {
-    AV_MAP_ALLOW_REBUILD  = 1,   ///< when removing entries rebuild the map to reduce memory consumption, note, this invalidates previously retrieved elements and iterate state.
-    AV_MAP_REPLACE        = 2,   ///< replace keyvalue if already in the map
+//use + not | to combine these flags
+    AV_MAP_CMP_CASE_SENSITIVE   = 1,
+    AV_MAP_CMP_CASE_INSENSITIVE = 2,
+    AV_MAP_CMP_KEY              = 3,
+    AV_MAP_CMP_KEYVALUE         = 6,
+    AV_MAP_CMP_TRUNCATED        = 9,
+    AV_MAP_CMP_NON_TRUNCATED    = 18,
+
+    AV_MAP_ALLOW_REBUILD        = 256,   ///< when removing entries rebuild the map to reduce memory consumption, note, this invalidates previously retrieved elements and iterate state.
+    AV_MAP_REPLACE              = 512,   ///< replace keyvalue if already in the map
+
 };
 
 typedef struct AVMapEntry {
@@ -104,7 +113,33 @@ int av_map_supercmp_key(const char *a, const char *b);
  *
  *
  */
-AVMap *av_map_new(AVMapCompareFunc keyvalue_cmp, AVMapCopyFunc clone, AVMapFreeFunc freef);
+AVMap *av_map_new(AVMapCompareFunc keyvalue_cmp, int cmp_flags, AVMapCopyFunc clone, AVMapFreeFunc freef);
+
+
+/**
+ * Add a compatible compare function to the map.
+ * The function will later be selected by using AV_MAP_CMP_* flags
+ *
+ * Functions must be added from most specific to least specific, that is if a AVMap is build
+ * with a case insensitive compare no case sensitive compare functions can be added. This is
+ * to avoid building non functional AVMaps.
+ *
+ * @see av_map_new
+ *
+ * @param cmp_flags a combination of AV_MAP_CMP_*, note key/keyvalue and truncated vs non truncated
+ *                  are mandatory to be specified
+ *
+ * @return a negative error code if the cmp_flags are illegal or unsupported for this AVMap
+ *         If you know your flags are valid, then you dont need to check the return
+ */
+int av_map_add_cmp_func(AVMap *m, AVMapCompareFunc keyvalue_cmp, int cmp_flags);
+
+/**
+ *
+ * @return 1 if the provided AV_MAP_CMP_* flag combination is supported by this map.
+ *         0 otherwise
+ */
+int av_map_is_cmp_flags_supported(AVMap *m, int cmp_flags);
 
 /**
  * realloc internal space to accomodate the specified new elements
@@ -136,13 +171,12 @@ int av_map_add(AVMap *s, const char *key, int keylen, const char *value, int val
  *
  * @param s         Pointer AVMap struct.
  * @param keyvalue  key or concatenated key+value
- * @param cmp       compatible compare function that comapres key or keyvalues
  * @param flags     AV_MAP_ALLOW_REBUILD or 0
  *
  * @return          1 if the entry was deleted, 0 if it was not found in the map
  *                  otherwise an error code <0
  */
-int av_map_del(AVMap *s, const char *keyvalue, int (*cmp)(const void *keyvalue, const void *b), int flags);
+int av_map_del(AVMap *s, const char *keyvalue, int flags);
 
 /**
  * Iterate over possibly multiple matching map entries.
@@ -153,22 +187,17 @@ int av_map_del(AVMap *s, const char *keyvalue, int (*cmp)(const void *keyvalue,
  * @param prev  Set to the previous matching element to find the next.
  *              If set to NULL the first matching element is returned.
  * @param keyvalue Matching key or key + value
- * @param cmp   compare function, this will be passed keyvalue and the concatenated key+value
- *              it must form a total order on all elements, that is a key can occur more than once.
- *              But cmp2 must be a refinement of the cmp order, any disagreement of the 2 compares
- *              must be by cmp returning equal. If this only reads the key part of keyvalue
- *              then keyvalue can be just a key
  *
  * @return      Found entry or NULL in case no matching entry was found in the dictionary
  */
-const AVMapEntry *av_map_get_multiple(const AVMap *s, const AVMapEntry *prev, const char *keyvalue, int (*cmp)(const void *keyvalue, const void *b));
+const AVMapEntry *av_map_get_multiple(const AVMap *s, const AVMapEntry *prev, const char *keyvalue, int flags);
 
 /**
  * Like av_map_get_multiple() but only returns one matching entry
  *
  * The returned entry cannot be used as initial prev entry for av_map_get_multiple()
  */
-const AVMapEntry *av_map_get(const AVMap *s, const char *keyvalue, int (*cmp)(const void *keyvalue, const void *b));
+const AVMapEntry *av_map_get(const AVMap *s, const char *keyvalue, int flags);
 
 /**
  * Iterate over a map
diff --git a/libavutil/tests/map.c b/libavutil/tests/map.c
index 90950769f98..1e3f80a42af 100644
--- a/libavutil/tests/map.c
+++ b/libavutil/tests/map.c
@@ -47,6 +47,13 @@ int main(void)
         av_map_supercmp_keyvalue,
         av_map_supercmp_keyvalue,
     };
+    int our_flags[] = {
+        AV_MAP_CMP_NON_TRUNCATED + AV_MAP_CMP_CASE_SENSITIVE   + AV_MAP_CMP_KEY,
+        AV_MAP_CMP_NON_TRUNCATED + AV_MAP_CMP_CASE_SENSITIVE   + AV_MAP_CMP_KEYVALUE,
+        AV_MAP_CMP_NON_TRUNCATED + AV_MAP_CMP_CASE_INSENSITIVE + AV_MAP_CMP_KEY,
+        AV_MAP_CMP_NON_TRUNCATED + AV_MAP_CMP_CASE_SENSITIVE   + AV_MAP_CMP_KEYVALUE,
+        AV_MAP_CMP_NON_TRUNCATED + AV_MAP_CMP_CASE_SENSITIVE   + AV_MAP_CMP_KEYVALUE,
+    };
     void *our_subcmp[] = {
         strcmp,
         strcmp,
@@ -54,19 +61,27 @@ int main(void)
         av_map_supercmp_key,
         av_strcasecmp,
     };
+    int our_subflags[] = {
+        AV_MAP_CMP_NON_TRUNCATED + AV_MAP_CMP_CASE_SENSITIVE   + AV_MAP_CMP_KEY,
+        AV_MAP_CMP_NON_TRUNCATED + AV_MAP_CMP_CASE_SENSITIVE   + AV_MAP_CMP_KEY,
+        AV_MAP_CMP_NON_TRUNCATED + AV_MAP_CMP_CASE_INSENSITIVE + AV_MAP_CMP_KEY,
+        AV_MAP_CMP_NON_TRUNCATED + AV_MAP_CMP_CASE_SENSITIVE   + AV_MAP_CMP_KEY,
+        AV_MAP_CMP_NON_TRUNCATED + AV_MAP_CMP_CASE_INSENSITIVE + AV_MAP_CMP_KEY,
+    };
 
     for (int settype=0; settype<3; settype++) {
-        AVMap *set = av_map_new(our_cmp[settype], NULL, NULL);
+        AVMap *set = av_map_new(our_cmp[settype], our_flags[settype], NULL, NULL);
+        av_map_add_cmp_func(set, our_subcmp[settype], our_subflags[settype]);
 
         printf("testing empty set\n");
 
-        const AVMapEntry *e = av_map_get(set, "foo", our_subcmp[settype]);
+        const AVMapEntry *e = av_map_get(set, "foo", our_subflags[settype]);
         av_assert0(e == NULL);
 
-        e = av_map_get(set, "foo", our_subcmp[settype]);
+        e = av_map_get(set, "foo", our_subflags[settype]);
         av_assert0(e == NULL);
 
-        int ret = av_map_del(set, "foo", our_subcmp[settype], 0);
+        int ret = av_map_del(set, "foo", our_subflags[settype]);
         av_assert0(ret == 0);
 
         print_set(set);
@@ -79,7 +94,7 @@ int main(void)
         ret = av_map_add(set, "foo", 4, "bear", 5, 0);
         av_assert0(ret == ((int[]){0,1,0})[settype]);
 
-        e = av_map_get(set, "foo", our_subcmp[settype]);
+        e = av_map_get(set, "foo", our_subflags[settype]);
         av_assert0(!strcmp(e->key, "foo"));
         if (settype == 1) {
             av_assert0(!strcmp(e->value, "bear") || !strcmp(e->value, "bar"));
@@ -90,7 +105,7 @@ int main(void)
         ret = av_map_add(set, "foo", 4, "bear", 5, AV_MAP_REPLACE);
         av_assert0(ret == 2);
 
-        e = av_map_get(set, "foo", our_subcmp[settype]);
+        e = av_map_get(set, "foo", our_subflags[settype]);
         av_assert0(!strcmp(e->key, "foo"));
         if (settype == 1) {
             av_assert0(!strcmp(e->value, "bear") || !strcmp(e->value, "bar"));
@@ -98,14 +113,14 @@ int main(void)
             av_assert0(!strcmp(e->value, "bear"));
         }
 
-        e = av_map_get_multiple(set, NULL, "foo", our_subcmp[settype]);
+        e = av_map_get_multiple(set, NULL, "foo", our_subflags[settype]);
         av_assert0(!strcmp(e->key, "foo"));
         if (settype == 1) {
             av_assert0(!strcmp(e->value, "bar"));
         } else {
             av_assert0(!strcmp(e->value, "bear"));
         }
-        e = av_map_get_multiple(set, e, "foo", our_subcmp[settype]);
+        e = av_map_get_multiple(set, e, "foo", our_subflags[settype]);
         if (settype == 1) {
             av_assert0(!strcmp(e->key, "foo"));
             av_assert0(!strcmp(e->value, "bear"));
@@ -113,10 +128,10 @@ int main(void)
             av_assert0(e == NULL);
         }
 
-        ret = av_map_del(set, "foo", our_subcmp[settype], 0);
+        ret = av_map_del(set, "foo", our_subflags[settype]);
         av_assert0(ret == 1);
 
-        e = av_map_get(set, "foo", our_subcmp[settype]);
+        e = av_map_get(set, "foo", our_subflags[settype]);
         if (settype == 1) {
             av_assert0(!strcmp(e->key, "foo"));
             av_assert0(!strcmp(e->value, "bear") || !strcmp(e->value, "bar"));
@@ -124,7 +139,7 @@ int main(void)
             av_assert0(e == NULL);
         }
 
-        ret = av_map_del(set, "foo", our_subcmp[settype], 0);
+        ret = av_map_del(set, "foo", our_subflags[settype]);
         av_assert0(ret == ((int[]){0,1,0})[settype]);
 
 
@@ -157,7 +172,7 @@ int main(void)
             r = r*123 + 7;
             char str[3] = {0};
             str[0] = r;
-            e = av_map_get(set, str, our_subcmp[settype]);
+            e = av_map_get(set, str, our_subflags[settype]);
             if (settype != 2) {
                 av_assert0(!strcmp(e->key, str));
                 av_assert0(!strcmp(e->value, str));
@@ -165,7 +180,7 @@ int main(void)
                 av_assert0(!av_strcasecmp(e->key, str));
                 av_assert0(!av_strcasecmp(e->value, str));
             }
-            e = av_map_get_multiple(set, NULL, str, our_subcmp[settype]);
+            e = av_map_get_multiple(set, NULL, str, our_subflags[settype]);
             if (settype != 2) {
                 av_assert0(!strcmp(e->key, str));
                 av_assert0(!strcmp(e->value, str));
@@ -178,9 +193,9 @@ int main(void)
 
             str[1]='x';
 
-            e = av_map_get(set, str, our_subcmp[settype]);
+            e = av_map_get(set, str, our_subflags[settype]);
             av_assert0(e == NULL);
-            e = av_map_get_multiple(set, NULL, str, our_subcmp[settype]);
+            e = av_map_get_multiple(set, NULL, str, our_subflags[settype]);
             av_assert0(e == NULL);
         }
         print_set(set);
-- 
2.49.0

_______________________________________________
ffmpeg-devel mailing list
ffmpeg-devel@ffmpeg.org
https://ffmpeg.org/mailman/listinfo/ffmpeg-devel

To unsubscribe, visit link above, or email
ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".

^ permalink raw reply	[flat|nested] 3+ messages in thread

* Re: [FFmpeg-devel] [PATCH] avutil/map: replace passing Compare functions by flags
  2025-04-16 20:12 [FFmpeg-devel] [PATCH] avutil/map: replace passing Compare functions by flags Michael Niedermayer
@ 2025-04-16 20:22 ` softworkz .
  2025-04-16 23:07   ` Michael Niedermayer
  0 siblings, 1 reply; 3+ messages in thread
From: softworkz . @ 2025-04-16 20:22 UTC (permalink / raw)
  To: FFmpeg development discussions and patches



> -----Original Message-----
> From: ffmpeg-devel <ffmpeg-devel-bounces@ffmpeg.org> On Behalf Of
> Michael Niedermayer
> Sent: Mittwoch, 16. April 2025 22:12
> To: FFmpeg development discussions and patches <ffmpeg-
> devel@ffmpeg.org>
> Subject: [FFmpeg-devel] [PATCH] avutil/map: replace passing Compare
> functions by flags
> 
> This makes the API much more robust because if you receive a map
> from some other module you no longer need to know which
> compare function is correct for it
> Instead you just specify what you need, like
> AV_MAP_CMP_CASE_SENSITIVE or AV_MAP_CMP_CASE_INSENSITIVE or 0 if you
> dont care
> and the code will either do what you asked for or cleanly fail if its
> unable to
> previously it would just give you a wrong result sometimes
> 
> Signed-off-by: Michael Niedermayer <michael@niedermayer.cc>
> ---
>  libavutil/map.c       | 80 +++++++++++++++++++++++++++++++++++-------
> -
>  libavutil/map.h       | 53 +++++++++++++++++++++-------
>  libavutil/tests/map.c | 45 ++++++++++++++++--------
>  3 files changed, 137 insertions(+), 41 deletions(-)
> 
> diff --git a/libavutil/map.c b/libavutil/map.c
> index 00dd5a1bd39..950473c2c45 100644
> --- a/libavutil/map.c
> +++ b/libavutil/map.c
> @@ -35,7 +35,8 @@ typedef struct{
>  } AVMapInternalEntry;
> 
>  struct AVMap{
> -    AVMapCompareFunc cmp_keyvalue;
> +#define CMP_MASK 31
> +    AVMapCompareFunc cmp[27];
>      AVMapCopyFunc copy;
>      AVMapFreeFunc freef;
>      int count;
> @@ -98,24 +99,71 @@ int av_map_supercmp_keyvalue(const char *a, const
> char *b)
>      return v;
>  }
> 
> -AVMap *av_map_new(AVMapCompareFunc cmp_keyvalue, AVMapCopyFunc copy,
> AVMapFreeFunc freef)
> +int av_map_add_cmp_func(AVMap *m, AVMapCompareFunc cmp, int
> cmp_flags)
> +{
> +    static const uint8_t sensitivity[27][3] = {
> +        {0,0, 0},{1,0, 0},{2,0, 0}, {0,3, 0},{1,3, 0},{2,3, 0}, {0,6,
> 0},{1,6, 0},{2,6, 0},
> +        {0,0, 9},{1,0, 9},{2,0, 9}, {0,3, 9},{1,3, 9},{2,3, 9}, {0,6,
> 9},{1,6, 9},{2,6, 9},
> +        {0,0,18},{1,0,18},{2,0,18}, {0,3,18},{1,3,18},{2,3,18},
> {0,6,18},{1,6,18},{2,6,18},};
> +    int case_sensitive       =  sensitivity[cmp_flags][0];
> +    int keyvalue_sensitive   =  sensitivity[cmp_flags][1];
> +    int truncated_sensitive  =  sensitivity[cmp_flags][2];
> +
> +    if (!keyvalue_sensitive || !truncated_sensitive || cmp_flags >=
> 27U)
> +        return AVERROR(EINVAL);
> +
> +    av_assert1(case_sensitive + keyvalue_sensitive +
> truncated_sensitive == cmp_flags);
> +
> +    if (     case_sensitive == AV_MAP_CMP_CASE_SENSITIVE && m-
> >cmp[keyvalue_sensitive + AV_MAP_CMP_CASE_INSENSITIVE])
> +        return AVERROR(EINVAL);
> +    if ( keyvalue_sensitive == AV_MAP_CMP_KEYVALUE       && m-
> >cmp[AV_MAP_CMP_KEY])
> +        return AVERROR(EINVAL);
> +    if (truncated_sensitive == AV_MAP_CMP_NON_TRUNCATED  && m-
> >cmp[keyvalue_sensitive + AV_MAP_CMP_TRUNCATED])
> +        return AVERROR(EINVAL);
> +
> +    //max functions is KV NT CS -> KV NT CI -> KV T CI (CI/T is about
> value only) -> K NT CS -> K NT CI -> K T CI
> +    //missing is KV T CS and K T CS, with them we can have KV NT CS -
> > KV T CS -> K NT CS -> K T CS
> +
> +    for (int i=0; i<8; i++) {
> +        int flags = 0;
> +        if (i&1) flags += case_sensitive;
> +        if (i&2) flags += keyvalue_sensitive;
> +        if (i&4) flags += truncated_sensitive;
> +
> +        if (!m->cmp[flags])
> +            m->cmp[flags] = cmp;
> +    }
> +    return 0;
> +}
> +
> +int av_map_is_cmp_flags_supported(AVMap *m, int cmp_flags)
> +{
> +    if (cmp_flags >= 27U)
> +        return AVERROR(EINVAL);
> +    return !!m->cmp[cmp_flags];
> +}
> +
> +AVMap *av_map_new(AVMapCompareFunc cmp_keyvalue, int cmp_flags,
> AVMapCopyFunc copy, AVMapFreeFunc freef)
>  {
>      AVMap *s = av_mallocz(sizeof(*s));
>      if (!s)
>          return NULL;
> 
> -    s->cmp_keyvalue  = cmp_keyvalue;
>      s->copy          = copy;
>      s->freef         = freef;
> 
> +    av_map_add_cmp_func(s, cmp_keyvalue, cmp_flags);
> +
>      return s;
>  }
> 
> -const AVMapEntry *av_map_get_multiple(const AVMap *s, const
> AVMapEntry *prev, const char *keyvalue, int (*cmp)(const void
> *keyvalue, const void *b))
> +const AVMapEntry *av_map_get_multiple(const AVMap *s, const
> AVMapEntry *prev, const char *keyvalue, int flags)
>  {
> +    AVMapCompareFunc cmp = s->cmp[flags & CMP_MASK];
> +
>      if (prev) {
>          void *next_node[2] = { NULL, NULL };
> -        void *prev_keyvalue = av_tree_find2(s->tree_root, prev->key,
> s->cmp_keyvalue, next_node, 2);
> +        void *prev_keyvalue = av_tree_find2(s->tree_root, prev->key,
> s->cmp[0], next_node, 2);
>          av_assert2(prev_keyvalue);
>          if (!next_node[1] || cmp(next_node[1], keyvalue))
>              return NULL;
> @@ -134,8 +182,10 @@ const AVMapEntry *av_map_get_multiple(const AVMap
> *s, const AVMapEntry *prev, co
>      return &keyvalue2internal(keyvalue)->map_entry;
>  }
> 
> -const AVMapEntry *av_map_get(const AVMap *s, const char *keyvalue,
> int (*cmp)(const void *keyvalue, const void *b))
> +const AVMapEntry *av_map_get(const AVMap *s, const char *keyvalue,
> int flags)
>  {
> +    AVMapCompareFunc cmp = s->cmp[flags & CMP_MASK];
> +
>      keyvalue = av_tree_find2(s->tree_root, keyvalue, cmp, NULL, 0);
> 
>      if (!keyvalue)
> @@ -192,15 +242,15 @@ int av_map_add(AVMap *s, const char *key, int
> keylen, const char *value, int val
>      memcpy(entry->key  , key  , keylen);
>      memcpy(entry->value, value, valuelen);
> 
> -    void *elem = av_tree_insert(&s->tree_root, entry->key, s-
> >cmp_keyvalue, &next);
> +    void *elem = av_tree_insert(&s->tree_root, entry->key, s->cmp[0],
> &next);
>      int ret = 1;
>      if (elem != entry->key && elem) {
>          av_assert2(next);
>          //we assume that new entries are more common than
> replacements
>          if (flags & AV_MAP_REPLACE) {
> -            ret = av_map_del(s, entry->key, s->cmp_keyvalue, flags);
> +            ret = av_map_del(s, entry->key, flags & ~CMP_MASK);
>              av_assert2(ret == 1);
> -            elem = av_tree_insert(&s->tree_root, entry->key, s-
> >cmp_keyvalue, &next);
> +            elem = av_tree_insert(&s->tree_root, entry->key, s-
> >cmp[0], &next);
>              av_assert2(elem == entry->key || !elem);
>              ret = 2;
>          } else
> @@ -214,12 +264,13 @@ int av_map_add(AVMap *s, const char *key, int
> keylen, const char *value, int val
>      return ret;
>  }
> 
> -int av_map_del(AVMap *s, const char *keyvalue, int (*cmp)(const void
> *keyvalue, const void *b), int flags)
> +int av_map_del(AVMap *s, const char *keyvalue, int flags)
>  {
>      uint8_t *old_keyvalue;
>      AVTreeNode *next = NULL;
> +    AVMapCompareFunc cmp = s->cmp[flags & CMP_MASK];
> 
> -    if (cmp != s->cmp_keyvalue) {
> +    if (cmp != s->cmp[0]) {
>          // The user asks us to remove a entry with a compare function
> different from the one used to build the map
>          // we need to do 2 calls here, first with the users compare
> to find the entry she wants to remove
>          // and then to remove it while maintaining the correct order
> within the map
> @@ -227,10 +278,10 @@ int av_map_del(AVMap *s, const char *keyvalue,
> int (*cmp)(const void *keyvalue,
>          if (!old_keyvalue)
>              return 0;
> 
> -        av_tree_insert(&s->tree_root, old_keyvalue, s->cmp_keyvalue,
> &next);
> +        av_tree_insert(&s->tree_root, old_keyvalue, s->cmp[0],
> &next);
>          av_assert2(next);
>      } else {
> -        av_tree_insert(&s->tree_root, (char*)keyvalue, s-
> >cmp_keyvalue, &next);
> +        av_tree_insert(&s->tree_root, (char*)keyvalue, s->cmp[0],
> &next);
>          if (!next)
>              return 0;
>          old_keyvalue = next->elem; //TODO add a API to av_tree() to
> return the elem of a AVTreeNode
> @@ -243,8 +294,9 @@ int av_map_del(AVMap *s, const char *keyvalue, int
> (*cmp)(const void *keyvalue,
>      s->deleted++;
> 
>      if ((flags & AV_MAP_ALLOW_REBUILD) && s->deleted > s->count) {
> -        AVMap *news = av_map_new(s->cmp_keyvalue, s->copy, s->freef);
> +        AVMap *news = av_map_new(s->cmp[0], AV_MAP_CMP_KEYVALUE +
> AV_MAP_CMP_NON_TRUNCATED, s->copy, s->freef);
>          if(news) {
> +            memcpy(news->cmp, s->cmp, sizeof(news->cmp));
>              int ret = av_map_copy(news, s);
>              if (ret < 0) {
>                  av_map_free(&news);
> diff --git a/libavutil/map.h b/libavutil/map.h
> index 0c660260017..10f541cc5ba 100644
> --- a/libavutil/map.h
> +++ b/libavutil/map.h
> @@ -46,8 +46,17 @@
>   */
> 
>  enum {
> -    AV_MAP_ALLOW_REBUILD  = 1,   ///< when removing entries rebuild
> the map to reduce memory consumption, note, this invalidates
> previously retrieved elements and iterate state.
> -    AV_MAP_REPLACE        = 2,   ///< replace keyvalue if already in
> the map
> +//use + not | to combine these flags
> +    AV_MAP_CMP_CASE_SENSITIVE   = 1,
> +    AV_MAP_CMP_CASE_INSENSITIVE = 2,
> +    AV_MAP_CMP_KEY              = 3,
> +    AV_MAP_CMP_KEYVALUE         = 6,
> +    AV_MAP_CMP_TRUNCATED        = 9,
> +    AV_MAP_CMP_NON_TRUNCATED    = 18,
> +
> +    AV_MAP_ALLOW_REBUILD        = 256,   ///< when removing entries
> rebuild the map to reduce memory consumption, note, this invalidates
> previously retrieved elements and iterate state.
> +    AV_MAP_REPLACE              = 512,   ///< replace keyvalue if
> already in the map
> +
>  };
> 
>  typedef struct AVMapEntry {
> @@ -104,7 +113,33 @@ int av_map_supercmp_key(const char *a, const char
> *b);
>   *
>   *
>   */
> -AVMap *av_map_new(AVMapCompareFunc keyvalue_cmp, AVMapCopyFunc clone,
> AVMapFreeFunc freef);
> +AVMap *av_map_new(AVMapCompareFunc keyvalue_cmp, int cmp_flags,
> AVMapCopyFunc clone, AVMapFreeFunc freef);
> +
> +
> +/**
> + * Add a compatible compare function to the map.
> + * The function will later be selected by using AV_MAP_CMP_* flags
> + *
> + * Functions must be added from most specific to least specific, that
> is if a AVMap is build
> + * with a case insensitive compare no case sensitive compare
> functions can be added. This is
> + * to avoid building non functional AVMaps.
> + *
> + * @see av_map_new
> + *
> + * @param cmp_flags a combination of AV_MAP_CMP_*, note key/keyvalue
> and truncated vs non truncated
> + *                  are mandatory to be specified
> + *
> + * @return a negative error code if the cmp_flags are illegal or
> unsupported for this AVMap
> + *         If you know your flags are valid, then you dont need to
> check the return
> + */
> +int av_map_add_cmp_func(AVMap *m, AVMapCompareFunc keyvalue_cmp, int
> cmp_flags);
> +
> +/**
> + *
> + * @return 1 if the provided AV_MAP_CMP_* flag combination is
> supported by this map.
> + *         0 otherwise
> + */
> +int av_map_is_cmp_flags_supported(AVMap *m, int cmp_flags);
> 
>  /**
>   * realloc internal space to accomodate the specified new elements
> @@ -136,13 +171,12 @@ int av_map_add(AVMap *s, const char *key, int
> keylen, const char *value, int val
>   *
>   * @param s         Pointer AVMap struct.
>   * @param keyvalue  key or concatenated key+value
> - * @param cmp       compatible compare function that comapres key or
> keyvalues
>   * @param flags     AV_MAP_ALLOW_REBUILD or 0
>   *
>   * @return          1 if the entry was deleted, 0 if it was not found
> in the map
>   *                  otherwise an error code <0
>   */
> -int av_map_del(AVMap *s, const char *keyvalue, int (*cmp)(const void
> *keyvalue, const void *b), int flags);
> +int av_map_del(AVMap *s, const char *keyvalue, int flags);
> 
>  /**
>   * Iterate over possibly multiple matching map entries.
> @@ -153,22 +187,17 @@ int av_map_del(AVMap *s, const char *keyvalue,
> int (*cmp)(const void *keyvalue,
>   * @param prev  Set to the previous matching element to find the
> next.
>   *              If set to NULL the first matching element is
> returned.
>   * @param keyvalue Matching key or key + value
> - * @param cmp   compare function, this will be passed keyvalue and
> the concatenated key+value
> - *              it must form a total order on all elements, that is a
> key can occur more than once.
> - *              But cmp2 must be a refinement of the cmp order, any
> disagreement of the 2 compares
> - *              must be by cmp returning equal. If this only reads
> the key part of keyvalue
> - *              then keyvalue can be just a key
>   *
>   * @return      Found entry or NULL in case no matching entry was
> found in the dictionary
>   */
> -const AVMapEntry *av_map_get_multiple(const AVMap *s, const
> AVMapEntry *prev, const char *keyvalue, int (*cmp)(const void
> *keyvalue, const void *b));
> +const AVMapEntry *av_map_get_multiple(const AVMap *s, const
> AVMapEntry *prev, const char *keyvalue, int flags);
> 
>  /**
>   * Like av_map_get_multiple() but only returns one matching entry
>   *
>   * The returned entry cannot be used as initial prev entry for
> av_map_get_multiple()
>   */
> -const AVMapEntry *av_map_get(const AVMap *s, const char *keyvalue,
> int (*cmp)(const void *keyvalue, const void *b));
> +const AVMapEntry *av_map_get(const AVMap *s, const char *keyvalue,
> int flags);
> 
>  /**
>   * Iterate over a map
> diff --git a/libavutil/tests/map.c b/libavutil/tests/map.c
> index 90950769f98..1e3f80a42af 100644
> --- a/libavutil/tests/map.c
> +++ b/libavutil/tests/map.c
> @@ -47,6 +47,13 @@ int main(void)
>          av_map_supercmp_keyvalue,
>          av_map_supercmp_keyvalue,
>      };
> +    int our_flags[] = {
> +        AV_MAP_CMP_NON_TRUNCATED + AV_MAP_CMP_CASE_SENSITIVE   +
> AV_MAP_CMP_KEY,
> +        AV_MAP_CMP_NON_TRUNCATED + AV_MAP_CMP_CASE_SENSITIVE   +
> AV_MAP_CMP_KEYVALUE,
> +        AV_MAP_CMP_NON_TRUNCATED + AV_MAP_CMP_CASE_INSENSITIVE +
> AV_MAP_CMP_KEY,
> +        AV_MAP_CMP_NON_TRUNCATED + AV_MAP_CMP_CASE_SENSITIVE   +
> AV_MAP_CMP_KEYVALUE,
> +        AV_MAP_CMP_NON_TRUNCATED + AV_MAP_CMP_CASE_SENSITIVE   +
> AV_MAP_CMP_KEYVALUE,
> +    };
>      void *our_subcmp[] = {
>          strcmp,
>          strcmp,
> @@ -54,19 +61,27 @@ int main(void)
>          av_map_supercmp_key,
>          av_strcasecmp,
>      };
> +    int our_subflags[] = {
> +        AV_MAP_CMP_NON_TRUNCATED + AV_MAP_CMP_CASE_SENSITIVE   +
> AV_MAP_CMP_KEY,
> +        AV_MAP_CMP_NON_TRUNCATED + AV_MAP_CMP_CASE_SENSITIVE   +
> AV_MAP_CMP_KEY,
> +        AV_MAP_CMP_NON_TRUNCATED + AV_MAP_CMP_CASE_INSENSITIVE +
> AV_MAP_CMP_KEY,
> +        AV_MAP_CMP_NON_TRUNCATED + AV_MAP_CMP_CASE_SENSITIVE   +
> AV_MAP_CMP_KEY,
> +        AV_MAP_CMP_NON_TRUNCATED + AV_MAP_CMP_CASE_INSENSITIVE +
> AV_MAP_CMP_KEY,
> +    };
> 
>      for (int settype=0; settype<3; settype++) {
> -        AVMap *set = av_map_new(our_cmp[settype], NULL, NULL);
> +        AVMap *set = av_map_new(our_cmp[settype], our_flags[settype],
> NULL, NULL);
> +        av_map_add_cmp_func(set, our_subcmp[settype],
> our_subflags[settype]);
> 
>          printf("testing empty set\n");
> 
> -        const AVMapEntry *e = av_map_get(set, "foo",
> our_subcmp[settype]);
> +        const AVMapEntry *e = av_map_get(set, "foo",
> our_subflags[settype]);
>          av_assert0(e == NULL);
> 
> -        e = av_map_get(set, "foo", our_subcmp[settype]);
> +        e = av_map_get(set, "foo", our_subflags[settype]);
>          av_assert0(e == NULL);
> 
> -        int ret = av_map_del(set, "foo", our_subcmp[settype], 0);
> +        int ret = av_map_del(set, "foo", our_subflags[settype]);
>          av_assert0(ret == 0);
> 
>          print_set(set);
> @@ -79,7 +94,7 @@ int main(void)
>          ret = av_map_add(set, "foo", 4, "bear", 5, 0);
>          av_assert0(ret == ((int[]){0,1,0})[settype]);
> 
> -        e = av_map_get(set, "foo", our_subcmp[settype]);
> +        e = av_map_get(set, "foo", our_subflags[settype]);
>          av_assert0(!strcmp(e->key, "foo"));
>          if (settype == 1) {
>              av_assert0(!strcmp(e->value, "bear") || !strcmp(e->value,
> "bar"));
> @@ -90,7 +105,7 @@ int main(void)
>          ret = av_map_add(set, "foo", 4, "bear", 5, AV_MAP_REPLACE);
>          av_assert0(ret == 2);
> 
> -        e = av_map_get(set, "foo", our_subcmp[settype]);
> +        e = av_map_get(set, "foo", our_subflags[settype]);
>          av_assert0(!strcmp(e->key, "foo"));
>          if (settype == 1) {
>              av_assert0(!strcmp(e->value, "bear") || !strcmp(e->value,
> "bar"));
> @@ -98,14 +113,14 @@ int main(void)
>              av_assert0(!strcmp(e->value, "bear"));
>          }
> 
> -        e = av_map_get_multiple(set, NULL, "foo",
> our_subcmp[settype]);
> +        e = av_map_get_multiple(set, NULL, "foo",
> our_subflags[settype]);
>          av_assert0(!strcmp(e->key, "foo"));
>          if (settype == 1) {
>              av_assert0(!strcmp(e->value, "bar"));
>          } else {
>              av_assert0(!strcmp(e->value, "bear"));
>          }
> -        e = av_map_get_multiple(set, e, "foo", our_subcmp[settype]);
> +        e = av_map_get_multiple(set, e, "foo",
> our_subflags[settype]);
>          if (settype == 1) {
>              av_assert0(!strcmp(e->key, "foo"));
>              av_assert0(!strcmp(e->value, "bear"));
> @@ -113,10 +128,10 @@ int main(void)
>              av_assert0(e == NULL);
>          }
> 
> -        ret = av_map_del(set, "foo", our_subcmp[settype], 0);
> +        ret = av_map_del(set, "foo", our_subflags[settype]);
>          av_assert0(ret == 1);
> 
> -        e = av_map_get(set, "foo", our_subcmp[settype]);
> +        e = av_map_get(set, "foo", our_subflags[settype]);
>          if (settype == 1) {
>              av_assert0(!strcmp(e->key, "foo"));
>              av_assert0(!strcmp(e->value, "bear") || !strcmp(e->value,
> "bar"));
> @@ -124,7 +139,7 @@ int main(void)
>              av_assert0(e == NULL);
>          }
> 
> -        ret = av_map_del(set, "foo", our_subcmp[settype], 0);
> +        ret = av_map_del(set, "foo", our_subflags[settype]);
>          av_assert0(ret == ((int[]){0,1,0})[settype]);
> 
> 
> @@ -157,7 +172,7 @@ int main(void)
>              r = r*123 + 7;
>              char str[3] = {0};
>              str[0] = r;
> -            e = av_map_get(set, str, our_subcmp[settype]);
> +            e = av_map_get(set, str, our_subflags[settype]);
>              if (settype != 2) {
>                  av_assert0(!strcmp(e->key, str));
>                  av_assert0(!strcmp(e->value, str));
> @@ -165,7 +180,7 @@ int main(void)
>                  av_assert0(!av_strcasecmp(e->key, str));
>                  av_assert0(!av_strcasecmp(e->value, str));
>              }
> -            e = av_map_get_multiple(set, NULL, str,
> our_subcmp[settype]);
> +            e = av_map_get_multiple(set, NULL, str,
> our_subflags[settype]);
>              if (settype != 2) {
>                  av_assert0(!strcmp(e->key, str));
>                  av_assert0(!strcmp(e->value, str));
> @@ -178,9 +193,9 @@ int main(void)
> 
>              str[1]='x';
> 
> -            e = av_map_get(set, str, our_subcmp[settype]);
> +            e = av_map_get(set, str, our_subflags[settype]);
>              av_assert0(e == NULL);
> -            e = av_map_get_multiple(set, NULL, str,
> our_subcmp[settype]);
> +            e = av_map_get_multiple(set, NULL, str,
> our_subflags[settype]);
>              av_assert0(e == NULL);
>          }
>          print_set(set);
> --
> 2.49.0
> 
> _______________________________________________

That's a good change, because it was in fact confusing.

Another confusing part of the API is the need for specifying the 
lengths of key and value - and I finally figured out why it 
didn't retrieve any elements: those values need to be strlen + 1

I think the final API should not require any length params
(or at least it should have a function that doesn't need it)


Best,
sw








_______________________________________________
ffmpeg-devel mailing list
ffmpeg-devel@ffmpeg.org
https://ffmpeg.org/mailman/listinfo/ffmpeg-devel

To unsubscribe, visit link above, or email
ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".

^ permalink raw reply	[flat|nested] 3+ messages in thread

* Re: [FFmpeg-devel] [PATCH] avutil/map: replace passing Compare functions by flags
  2025-04-16 20:22 ` softworkz .
@ 2025-04-16 23:07   ` Michael Niedermayer
  0 siblings, 0 replies; 3+ messages in thread
From: Michael Niedermayer @ 2025-04-16 23:07 UTC (permalink / raw)
  To: FFmpeg development discussions and patches


[-- Attachment #1.1: Type: text/plain, Size: 929 bytes --]

On Wed, Apr 16, 2025 at 08:22:18PM +0000, softworkz . wrote:
[...]
>
> That's a good change, because it was in fact confusing.
> 
> Another confusing part of the API is the need for specifying the 
> lengths of key and value - and I finally figured out why it 
> didn't retrieve any elements: those values need to be strlen + 1
> 

> I think the final API should not require any length params
> (or at least it should have a function that doesn't need it)

its there because people asked for support for non string data
ill add a
int av_map_add_strings(AVMap *s, const char *key, const char *value, int flags);

thx

[...]
-- 
Michael     GnuPG fingerprint: 9FF2128B147EF6730BADF133611EC787040B0FAB

If you fake or manipulate statistics in a paper in physics you will never
get a job again.
If you fake or manipulate statistics in a paper in medicin you will get
a job for life at the pharma industry.

[-- Attachment #1.2: signature.asc --]
[-- Type: application/pgp-signature, Size: 195 bytes --]

[-- Attachment #2: Type: text/plain, Size: 251 bytes --]

_______________________________________________
ffmpeg-devel mailing list
ffmpeg-devel@ffmpeg.org
https://ffmpeg.org/mailman/listinfo/ffmpeg-devel

To unsubscribe, visit link above, or email
ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".

^ permalink raw reply	[flat|nested] 3+ messages in thread

end of thread, other threads:[~2025-04-16 23:08 UTC | newest]

Thread overview: 3+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2025-04-16 20:12 [FFmpeg-devel] [PATCH] avutil/map: replace passing Compare functions by flags Michael Niedermayer
2025-04-16 20:22 ` softworkz .
2025-04-16 23:07   ` Michael Niedermayer

Git Inbox Mirror of the ffmpeg-devel mailing list - see https://ffmpeg.org/mailman/listinfo/ffmpeg-devel

This inbox may be cloned and mirrored by anyone:

	git clone --mirror https://master.gitmailbox.com/ffmpegdev/0 ffmpegdev/git/0.git

	# If you have public-inbox 1.1+ installed, you may
	# initialize and index your mirror using the following commands:
	public-inbox-init -V2 ffmpegdev ffmpegdev/ https://master.gitmailbox.com/ffmpegdev \
		ffmpegdev@gitmailbox.com
	public-inbox-index ffmpegdev

Example config snippet for mirrors.


AGPL code for this site: git clone https://public-inbox.org/public-inbox.git