Git Inbox Mirror of the ffmpeg-devel mailing list - see https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
 help / color / mirror / Atom feed
From: Thilo Borgmann via ffmpeg-devel <ffmpeg-devel@ffmpeg.org>
To: ffmpeg-devel@ffmpeg.org
Cc: Thilo Borgmann <thilo.borgmann@mail.de>
Subject: [FFmpeg-devel] [PATCH 2/5] fftools/ffmpeg: move parsing of -stats_* specifiers to lavu/parseutils
Date: Mon, 11 Dec 2023 16:07:22 +0100
Message-ID: <20231211150725.46473-3-thilo.borgmann@mail.de> (raw)
In-Reply-To: <20231211150725.46473-1-thilo.borgmann@mail.de>

---
 fftools/ffmpeg.h          |  31 +------
 fftools/ffmpeg_enc.c      |   3 +-
 fftools/ffmpeg_mux_init.c | 152 +++-----------------------------
 libavutil/parseutils.c    | 176 ++++++++++++++++++++++++++++++++++++++
 libavutil/parseutils.h    | 102 ++++++++++++++++++++++
 libavutil/version.h       |   2 +-
 6 files changed, 296 insertions(+), 170 deletions(-)

diff --git a/fftools/ffmpeg.h b/fftools/ffmpeg.h
index 1f11a2f002..cb4d90c7b2 100644
--- a/fftools/ffmpeg.h
+++ b/fftools/ffmpeg.h
@@ -42,6 +42,7 @@
 #include "libavutil/eval.h"
 #include "libavutil/fifo.h"
 #include "libavutil/hwcontext.h"
+#include "libavutil/parseutils.h"
 #include "libavutil/pixfmt.h"
 #include "libavutil/rational.h"
 #include "libavutil/thread.h"
@@ -437,36 +438,8 @@ enum forced_keyframes_const {
 #define ABORT_ON_FLAG_EMPTY_OUTPUT        (1 <<  0)
 #define ABORT_ON_FLAG_EMPTY_OUTPUT_STREAM (1 <<  1)
 
-enum EncStatsType {
-    ENC_STATS_LITERAL = 0,
-    ENC_STATS_FILE_IDX,
-    ENC_STATS_STREAM_IDX,
-    ENC_STATS_FRAME_NUM,
-    ENC_STATS_FRAME_NUM_IN,
-    ENC_STATS_TIMEBASE,
-    ENC_STATS_TIMEBASE_IN,
-    ENC_STATS_PTS,
-    ENC_STATS_PTS_TIME,
-    ENC_STATS_PTS_IN,
-    ENC_STATS_PTS_TIME_IN,
-    ENC_STATS_DTS,
-    ENC_STATS_DTS_TIME,
-    ENC_STATS_SAMPLE_NUM,
-    ENC_STATS_NB_SAMPLES,
-    ENC_STATS_PKT_SIZE,
-    ENC_STATS_BITRATE,
-    ENC_STATS_AVG_BITRATE,
-};
-
-typedef struct EncStatsComponent {
-    enum EncStatsType type;
-
-    uint8_t *str;
-    size_t   str_len;
-} EncStatsComponent;
-
 typedef struct EncStats {
-    EncStatsComponent  *components;
+    AVEncStatsComponent  *components;
     int              nb_components;
 
     AVIOContext        *io;
diff --git a/fftools/ffmpeg_enc.c b/fftools/ffmpeg_enc.c
index fa4539664f..a499bc0c81 100644
--- a/fftools/ffmpeg_enc.c
+++ b/fftools/ffmpeg_enc.c
@@ -30,6 +30,7 @@
 #include "libavutil/frame.h"
 #include "libavutil/intreadwrite.h"
 #include "libavutil/log.h"
+#include "libavutil/parseutils.h"
 #include "libavutil/pixdesc.h"
 #include "libavutil/rational.h"
 #include "libavutil/timestamp.h"
@@ -499,7 +500,7 @@ void enc_stats_write(OutputStream *ost, EncStats *es,
     }
 
     for (size_t i = 0; i < es->nb_components; i++) {
-        const EncStatsComponent *c = &es->components[i];
+        const AVEncStatsComponent *c = &es->components[i];
 
         switch (c->type) {
         case ENC_STATS_LITERAL:         avio_write (io, c->str,     c->str_len);                    continue;
diff --git a/fftools/ffmpeg_mux_init.c b/fftools/ffmpeg_mux_init.c
index 6c473a8f09..6acdf92c2c 100644
--- a/fftools/ffmpeg_mux_init.c
+++ b/fftools/ffmpeg_mux_init.c
@@ -242,152 +242,26 @@ void of_enc_stats_close(void)
     nb_enc_stats_files = 0;
 }
 
-static int unescape(char **pdst, size_t *dst_len,
-                    const char **pstr, char delim)
-{
-    const char *str = *pstr;
-    char *dst;
-    size_t len, idx;
-
-    *pdst = NULL;
-
-    len = strlen(str);
-    if (!len)
-        return 0;
-
-    dst = av_malloc(len + 1);
-    if (!dst)
-        return AVERROR(ENOMEM);
-
-    for (idx = 0; *str; idx++, str++) {
-        if (str[0] == '\\' && str[1])
-            str++;
-        else if (*str == delim)
-            break;
-
-        dst[idx] = *str;
-    }
-    if (!idx) {
-        av_freep(&dst);
-        return 0;
-    }
-
-    dst[idx] = 0;
-
-    *pdst    = dst;
-    *dst_len = idx;
-    *pstr    = str;
-
-    return 0;
-}
-
 static int enc_stats_init(OutputStream *ost, EncStats *es, int pre,
                           const char *path, const char *fmt_spec)
 {
-    static const struct {
-        enum EncStatsType  type;
-        const char        *str;
-        int                pre_only:1;
-        int                post_only:1;
-        int                need_input_data:1;
-    } fmt_specs[] = {
-        { ENC_STATS_FILE_IDX,       "fidx"                      },
-        { ENC_STATS_STREAM_IDX,     "sidx"                      },
-        { ENC_STATS_FRAME_NUM,      "n"                         },
-        { ENC_STATS_FRAME_NUM_IN,   "ni",       0, 0, 1         },
-        { ENC_STATS_TIMEBASE,       "tb"                        },
-        { ENC_STATS_TIMEBASE_IN,    "tbi",      0, 0, 1         },
-        { ENC_STATS_PTS,            "pts"                       },
-        { ENC_STATS_PTS_TIME,       "t"                         },
-        { ENC_STATS_PTS_IN,         "ptsi",     0, 0, 1         },
-        { ENC_STATS_PTS_TIME_IN,    "ti",       0, 0, 1         },
-        { ENC_STATS_DTS,            "dts",      0, 1            },
-        { ENC_STATS_DTS_TIME,       "dt",       0, 1            },
-        { ENC_STATS_SAMPLE_NUM,     "sn",       1               },
-        { ENC_STATS_NB_SAMPLES,     "samp",     1               },
-        { ENC_STATS_PKT_SIZE,       "size",     0, 1            },
-        { ENC_STATS_BITRATE,        "br",       0, 1            },
-        { ENC_STATS_AVG_BITRATE,    "abr",      0, 1            },
-    };
-    const char *next = fmt_spec;
-
     int ret;
 
-    while (*next) {
-        EncStatsComponent *c;
-        char *val;
-        size_t val_len;
-
-        // get the sequence up until next opening brace
-        ret = unescape(&val, &val_len, &next, '{');
-        if (ret < 0)
-            return ret;
-
-        if (val) {
-            ret = GROW_ARRAY(es->components, es->nb_components);
-            if (ret < 0) {
-                av_freep(&val);
-                return ret;
-            }
-
-            c          = &es->components[es->nb_components - 1];
-            c->type    = ENC_STATS_LITERAL;
-            c->str     = val;
-            c->str_len = val_len;
-        }
-
-        if (!*next)
-            break;
-        next++;
-
-        // get the part inside braces
-        ret = unescape(&val, &val_len, &next, '}');
-        if (ret < 0)
-            return ret;
-
-        if (!val) {
-            av_log(NULL, AV_LOG_ERROR,
-                   "Empty formatting directive in: %s\n", fmt_spec);
-            return AVERROR(EINVAL);
-        }
-
-        if (!*next) {
-            av_log(NULL, AV_LOG_ERROR,
-                   "Missing closing brace in: %s\n", fmt_spec);
-            ret = AVERROR(EINVAL);
-            goto fail;
-        }
-        next++;
-
-        ret = GROW_ARRAY(es->components, es->nb_components);
-        if (ret < 0)
-            goto fail;
-
-        c = &es->components[es->nb_components - 1];
-
-        for (size_t i = 0; i < FF_ARRAY_ELEMS(fmt_specs); i++) {
-            if (!strcmp(val, fmt_specs[i].str)) {
-                c->type = fmt_specs[i].type;
-                c->str  = val;
-                c->str_len = val_len;
-                break;
-            }
-        }
-
-        if (!c->type) {
-            av_log(NULL, AV_LOG_ERROR, "Invalid format directive: %s\n", val);
-            ret = AVERROR(EINVAL);
-            goto fail;
-        }
-    }
+    ret = av_parse_enc_stats_components(&es->components, &es->nb_components, fmt_spec);
+    if (ret < 0)
+        goto fail;
 
     for (int j = 0; j < es->nb_components; j++) {
-        EncStatsComponent *c = &es->components[j];
+        AVEncStatsComponent *c = &es->components[j];
         char *val = c->str;
 
-        for (size_t i = 0; i < FF_ARRAY_ELEMS(fmt_specs); i++) {
-            if (!strcmp(val, fmt_specs[i].str)) {
-                if ((pre && fmt_specs[i].post_only) || (!pre && fmt_specs[i].pre_only)) {
+        for (size_t i = 0; i < av_get_nb_stats_fmt_specs(); i++) {
+            AVEncStatsFormatSpecifier f;
+            if (av_get_stats_fmt_spec(&f, i))
+                goto fail;
+
+            if (!strcmp(val, f.str)) {
+                if ((pre && f.post_only) || (!pre && f.pre_only)) {
                     av_log(NULL, AV_LOG_ERROR,
                            "Format directive '%s' may only be used %s-encoding\n",
                            val, pre ? "post" : "pre");
@@ -395,7 +269,7 @@ static int enc_stats_init(OutputStream *ost, EncStats *es, int pre,
                     goto fail;
                 }
 
-                if (fmt_specs[i].need_input_data && !ost->ist) {
+                if (f.need_input_data && !ost->ist) {
                     av_log(ost, AV_LOG_WARNING,
                            "Format directive '%s' is unavailable, because "
                            "this output stream has no associated input stream\n",
@@ -410,7 +284,7 @@ static int enc_stats_init(OutputStream *ost, EncStats *es, int pre,
     ret = enc_stats_get_file(&es->io, path);
 fail:
     for (int j = 0; j < es->nb_components; j++) {
-        EncStatsComponent *c = &es->components[j];
+        AVEncStatsComponent *c = &es->components[j];
         if (c->type != ENC_STATS_LITERAL) {
             av_freep(&c->str);
         }
diff --git a/libavutil/parseutils.c b/libavutil/parseutils.c
index 94e88e0a79..796811aa6e 100644
--- a/libavutil/parseutils.c
+++ b/libavutil/parseutils.c
@@ -788,3 +788,179 @@ int av_find_info_tag(char *arg, int arg_size, const char *tag1, const char *info
     }
     return 0;
 }
+
+static const AVEncStatsFormatSpecifier fmt_specs[] = {
+    { ENC_STATS_FILE_IDX,       "fidx"                      },
+    { ENC_STATS_STREAM_IDX,     "sidx"                      },
+    { ENC_STATS_FRAME_NUM,      "n"                         },
+    { ENC_STATS_FRAME_NUM_IN,   "ni",       0, 0, 1         },
+    { ENC_STATS_TIMEBASE,       "tb"                        },
+    { ENC_STATS_TIMEBASE_IN,    "tbi",      0, 0, 1         },
+    { ENC_STATS_PTS,            "pts"                       },
+    { ENC_STATS_PTS_TIME,       "t"                         },
+    { ENC_STATS_PTS_IN,         "ptsi",     0, 0, 1         },
+    { ENC_STATS_PTS_TIME_IN,    "ti",       0, 0, 1         },
+    { ENC_STATS_DTS,            "dts",      0, 1            },
+    { ENC_STATS_DTS_TIME,       "dt",       0, 1            },
+    { ENC_STATS_SAMPLE_NUM,     "sn",       1               },
+    { ENC_STATS_NB_SAMPLES,     "samp",     1               },
+    { ENC_STATS_PKT_SIZE,       "size",     0, 1            },
+    { ENC_STATS_BITRATE,        "br",       0, 1            },
+    { ENC_STATS_AVG_BITRATE,    "abr",      0, 1            },
+};
+
+size_t av_get_nb_stats_fmt_specs(void)
+{
+    return sizeof(fmt_specs) / sizeof(*fmt_specs);
+}
+
+int av_get_stats_fmt_spec(AVEncStatsFormatSpecifier *fmt_spec, int idx)
+{
+    if (!fmt_spec || idx >= av_get_nb_stats_fmt_specs())
+        return AVERROR(EINVAL);
+
+    *fmt_spec = fmt_specs[idx];
+    return 0;
+}
+
+static int unescape(char **pdst, size_t *dst_len,
+                    const char **pstr, char delim)
+{
+    const char *str = *pstr;
+    char *dst;
+    size_t len, idx;
+
+    *pdst = NULL;
+
+    len = strlen(str);
+    if (!len)
+        return 0;
+
+    dst = av_malloc(len + 1);
+    if (!dst)
+        return AVERROR(ENOMEM);
+
+    for (idx = 0; *str; idx++, str++) {
+        if (str[0] == '\\' && str[1])
+            str++;
+        else if (*str == delim)
+            break;
+
+        dst[idx] = *str;
+    }
+    if (!idx) {
+        av_freep(&dst);
+        return 0;
+    }
+
+    dst[idx] = 0;
+
+    *pdst    = dst;
+    *dst_len = idx;
+    *pstr    = str;
+
+    return 0;
+}
+
+static int grow_array(void **array, int elem_size, int *size, int new_size)
+{
+    if (new_size >= INT_MAX / elem_size) {
+        av_log(NULL, AV_LOG_ERROR, "Array too big.\n");
+        return AVERROR(ERANGE);
+    }
+    if (*size < new_size) {
+        uint8_t *tmp = av_realloc_array(*array, new_size, elem_size);
+        if (!tmp)
+            return AVERROR(ENOMEM);
+        memset(tmp + *size*elem_size, 0, (new_size-*size) * elem_size);
+        *size = new_size;
+        *array = tmp;
+        return 0;
+    }
+    return 0;
+}
+
+#define GROW_ARRAY(array, nb_elems)\
+    grow_array((void**)&array, sizeof(*array), &nb_elems, nb_elems + 1)
+
+int av_parse_enc_stats_components(AVEncStatsComponent **components, int *nb_components, const char *fmt_spec)
+{
+    const char *next = fmt_spec;
+    int ret;
+
+    while (*next) {
+        AVEncStatsComponent *c;
+        char *val;
+        size_t val_len;
+
+        // get the sequence up until next opening brace
+        ret = unescape(&val, &val_len, &next, '{');
+        if (ret < 0)
+            return ret;
+
+        if (val) {
+            ret = GROW_ARRAY(*components, *nb_components);
+            if (ret < 0) {
+                av_freep(&val);
+                return ret;
+            }
+
+            c          = &((*components)[*nb_components - 1]);
+            c->type    = ENC_STATS_LITERAL;
+            c->str     = val;
+            c->str_len = val_len;
+        }
+
+        if (!*next)
+            break;
+        next++;
+
+        // get the part inside braces
+        ret = unescape(&val, &val_len, &next, '}');
+        if (ret < 0)
+            return ret;
+
+        if (!val) {
+            av_log(NULL, AV_LOG_ERROR,
+                   "Empty formatting directive in: %s\n", fmt_spec);
+            return AVERROR(EINVAL);
+        }
+
+        if (!*next) {
+            av_log(NULL, AV_LOG_ERROR,
+                   "Missing closing brace in: %s\n", fmt_spec);
+            ret = AVERROR(EINVAL);
+            goto fail;
+        }
+        next++;
+
+        ret = GROW_ARRAY(*components, *nb_components);
+        if (ret < 0)
+            goto fail;
+
+        c = &(*components)[*nb_components - 1];
+
+        for (int i = 0; i < FF_ARRAY_ELEMS(fmt_specs); i++) {
+            if (!strcmp(val, fmt_specs[i].str)) {
+                c->type    = fmt_specs[i].type;
+                c->str     = val;
+                c->str_len = val_len;
+                break;
+            }
+        }
+
+        if (!c->type) {
+            av_log(NULL, AV_LOG_ERROR, "Invalid format directive: %s\n", val);
+            ret = AVERROR(EINVAL);
+            goto fail;
+        }
+
+        continue;
+fail:
+        av_freep(&val);
+        if (ret < 0)
+            return ret;
+    }
+
+    return 0;
+}
diff --git a/libavutil/parseutils.h b/libavutil/parseutils.h
index dad5c2775b..d546d77de0 100644
--- a/libavutil/parseutils.h
+++ b/libavutil/parseutils.h
@@ -22,6 +22,7 @@
 #include <time.h>
 
 #include "rational.h"
+#include "mem.h"
 
 /**
  * @file
@@ -194,4 +195,105 @@ char *av_small_strptime(const char *p, const char *fmt, struct tm *dt);
  */
 time_t av_timegm(struct tm *tm);
 
+typedef enum AVEncStatsType {
+    ENC_STATS_LITERAL = 0,
+    ENC_STATS_FILE_IDX,
+    ENC_STATS_STREAM_IDX,
+    ENC_STATS_FRAME_NUM,
+    ENC_STATS_FRAME_NUM_IN,
+    ENC_STATS_TIMEBASE,
+    ENC_STATS_TIMEBASE_IN,
+    ENC_STATS_PTS,
+    ENC_STATS_PTS_TIME,
+    ENC_STATS_PTS_IN,
+    ENC_STATS_PTS_TIME_IN,
+    ENC_STATS_DTS,
+    ENC_STATS_DTS_TIME,
+    ENC_STATS_SAMPLE_NUM,
+    ENC_STATS_NB_SAMPLES,
+    ENC_STATS_PKT_SIZE,
+    ENC_STATS_BITRATE,
+    ENC_STATS_AVG_BITRATE,
+} AVEncStatsType;
+
+/**
+ * Structure describing an encoding stats component.
+ */
+typedef struct AVEncStatsComponent {
+    /**
+     * The type of this component.
+     */
+    AVEncStatsType type;
+    /**
+     * The string representation of this component.
+     */
+    uint8_t *str;
+    /**
+     * The length of the string.
+     */
+    size_t   str_len;
+} AVEncStatsComponent;
+
+/**
+ * Structure describing an encoding stats format specifier.
+ */
+typedef struct AVEncStatsFormatSpecifier {
+    /**
+     * The type of this format specifier.
+     */
+    enum AVEncStatsType  type;
+    /**
+     * The string representation of this format specifier.
+     */
+    const char          *str;
+    /**
+     * Flag if this format specifier is only valid before encoding.
+     */
+    int                  pre_only:1;
+    /**
+     * Flag if this format specifier is only valid after encoding.
+     */
+    int                  post_only:1;
+    /**
+     * Flag if this format specifier requires an associated input
+     * stream for it to be processed.
+     */
+    int                  need_input_data:1;
+} AVEncStatsFormatSpecifier;
+
+/**
+ * Get the number of available enc stats format specifiers.
+ */
+size_t av_get_nb_stats_fmt_specs(void);
+
+/**
+ * Get a copy of an enc stats format specifier.
+ *
+ * @param *fmt_spec Destination for the copy of the format specifier.
+ *                  Has to be previously allocated.
+ *
+ * @param idx       Index to the table of format specifiers.
+ *
+ * @return          Return 0 on success, a negative value corresponding
+ *                  to an AVERROR code otherwise.
+ */
+int av_get_stats_fmt_spec(AVEncStatsFormatSpecifier *fmt_spec, int idx);
+
+/**
+ * Parse an enc stats format string into an array of AVEncStatsComponent.
+ *
+ * @param components Pointer to the array of AVEncStatsComponent to store
+ *                   the parsed elements. The arrary will be reallocated
+ *                   in the process if any elements are found.
+ *
+ * @param nb_components Pointer to the number of components in the array.
+ *
+ * @param fmt_spec   The format string to parse for components.
+ *
+ * @return          Return 0 on success, a negative value corresponding
+ *                  to an AVERROR code otherwise.
+ *
+ */
+int av_parse_enc_stats_components(AVEncStatsComponent **components, int *nb_components, const char *fmt_spec);
+
 #endif /* AVUTIL_PARSEUTILS_H */
diff --git a/libavutil/version.h b/libavutil/version.h
index c5fa7c3692..0684996bf2 100644
--- a/libavutil/version.h
+++ b/libavutil/version.h
@@ -79,7 +79,7 @@
  */
 
 #define LIBAVUTIL_VERSION_MAJOR  58
-#define LIBAVUTIL_VERSION_MINOR  32
+#define LIBAVUTIL_VERSION_MINOR  33
 #define LIBAVUTIL_VERSION_MICRO 100
 
 #define LIBAVUTIL_VERSION_INT   AV_VERSION_INT(LIBAVUTIL_VERSION_MAJOR, \
-- 
2.37.1 (Apple Git-137.1)

_______________________________________________
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".

  parent reply	other threads:[~2023-12-11 15:07 UTC|newest]

Thread overview: 20+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2023-12-11 15:07 [FFmpeg-devel] [PATCH 0/5] avfilter: Add fsync filter Thilo Borgmann via ffmpeg-devel
2023-12-11 15:07 ` [FFmpeg-devel] [PATCH 1/5] fftools/ffmpeg: split loop for parsing and validation of -stats_* specifiers Thilo Borgmann via ffmpeg-devel
2023-12-11 15:07 ` Thilo Borgmann via ffmpeg-devel [this message]
2023-12-13 12:00   ` [FFmpeg-devel] [PATCH 2/5] fftools/ffmpeg: move parsing of -stats_* specifiers to lavu/parseutils Anton Khirnov
2023-12-13 12:05     ` Thilo Borgmann via ffmpeg-devel
2023-12-13 12:08       ` Anton Khirnov
2023-12-13 12:15         ` Thilo Borgmann via ffmpeg-devel
2023-12-13 12:39           ` Anton Khirnov
2023-12-13 12:50             ` Thilo Borgmann via ffmpeg-devel
2023-12-13 16:28               ` Anton Khirnov
2023-12-13 18:17                 ` Thilo Borgmann via ffmpeg-devel
2023-12-14  5:23                   ` Anton Khirnov
2023-12-14 10:34                     ` Thilo Borgmann via ffmpeg-devel
2023-12-14 17:51                       ` Anton Khirnov
2023-12-14 18:42                         ` Thilo Borgmann via ffmpeg-devel
2023-12-13 12:10   ` Nicolas George
2023-12-11 15:07 ` [FFmpeg-devel] [PATCH 3/5] reindent after last commit Thilo Borgmann via ffmpeg-devel
2023-12-11 15:07 ` [FFmpeg-devel] [PATCH 4/5] avfilter: Add fsync filter Thilo Borgmann via ffmpeg-devel
2023-12-11 15:14   ` Thilo Borgmann via ffmpeg-devel
2023-12-11 15:07 ` [FFmpeg-devel] [PATCH 5/5] fate: Add fsync filter tests Thilo Borgmann via ffmpeg-devel

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20231211150725.46473-3-thilo.borgmann@mail.de \
    --to=ffmpeg-devel@ffmpeg.org \
    --cc=thilo.borgmann@mail.de \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link

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