Commit c1b3b086e4 for ffmpeg

commit c1b3b086e4bba744a57613d101a7b861b1dd20af
Author: Niklas Haas <git@haasn.dev>
Date:   Sun Nov 8 14:59:25 2026 +0100

    avformat/shared: add -cache_key option

    There are a number of cases in which deriving the filename from a hash of
    the URL is not ideal: For example, when the URL embeds parameters that
    don't affect the underlying file data, or when the user wants to be able to
    clean up or access the cache files directly, e.g. by associating them with a
    database.

    Sponsored-by: nxtedition AB
    Signed-off-by: Niklas Haas <git@haasn.dev>

diff --git a/doc/protocols.texi b/doc/protocols.texi
index ab2ec92d80..e802c9121b 100644
--- a/doc/protocols.texi
+++ b/doc/protocols.texi
@@ -1788,6 +1788,8 @@ This includes content selected by request options such as headers, cookies or
 credentials: data cached for one user is returned to every other user of the
 same cache directory.

+See also the @option{cache_key} option.
+
 The accepted options are:
 @table @option

@@ -1801,6 +1803,11 @@ writable by untrusted users.
 Path to the directory where metadata files are stored. Optional. If not
 specified, metadata is stored in the same directory as the cache file.

+@item cache_key
+Override the hash used to identify the cached content. Note that this is also
+used verbatim as the base name of the cache and metadata files, so it must be
+a valid file name.
+
 @item block_shift
 Shift factor (log2) of the block size used for internal reads/writes. Defaults
 to 15, i.e. 32KB blocks. If this does not match the value that was specified
diff --git a/libavformat/shared.c b/libavformat/shared.c
index 38a64412a7..ecea2f0340 100644
--- a/libavformat/shared.c
+++ b/libavformat/shared.c
@@ -166,6 +166,7 @@ typedef struct SharedContext {
     /* options */
     char *cache_dir;
     char *metadata_dir;
+    char *cache_key;
     int block_shift; ///< requested shift; updated on init if it disagrees
     int read_only;
     int64_t timeout;
@@ -297,15 +298,31 @@ static int shared_open(URLContext *h, const char *arg, int flags, AVDictionary *
     } else if (ret < 0)
         goto fail;

+    const char *cache_key = s->cache_key;
+    if (cache_key) {
+        if (strchr(cache_key, '/') || strchr(cache_key, '\\')) {
+            av_log(h, AV_LOG_ERROR, "Invalid cache key '%s': must not contain "
+                   "path separators\n", cache_key);
+            ret = AVERROR(EINVAL);
+            goto fail;
+        } else if (!cache_key[0])
+            cache_key = NULL;
+    }
+
     uint8_t hash[HASH_SIZE];
-    ret = hash_uri(hash, arg);
+    ret = hash_uri(hash, cache_key ? cache_key : arg);
     if (ret < 0)
         goto fail;

     /* 128 bits is enough for collision resistance; we already store the full
      * hash inside the header for verification */
-    char filename[2 * 16 + 1];
-    ff_data_to_hex(filename, hash, sizeof(filename)/2, 0);
+    const char *filename = cache_key;
+    char buf[2 * 16 + 1];
+    if (!filename) {
+        ff_data_to_hex(buf, hash, sizeof(buf)/2, 0);
+        filename = buf;
+    }
+
     s->cache_path = av_asprintf("%s/%s.cache",    s->cache_dir, filename);
     s->map_path   = av_asprintf("%s/%s.spacemap", spacemap_dir, filename);
     if (!s->cache_path || !s->map_path) {
@@ -952,6 +969,7 @@ static const AVOption options[] = {
     { "retry_corrupt",  "Re-request blocks that fail the CRC check",        OFFSET(retry_corrupt),  AV_OPT_TYPE_BOOL, {.i64 = 1}, 0, 1, .flags = D },
     { "cache_size_max", "Limit the maximum amount of data cached",          OFFSET(cache_size_max), AV_OPT_TYPE_INT64, {.i64 = 0}, 0, INT64_MAX, .flags = D },
     { "offset",         "Initial byte offset",                              OFFSET(offset),         AV_OPT_TYPE_INT64, {.i64 = 0}, 0, INT64_MAX, .flags = D },
+    { "cache_key",      "Override the hash used to identify the cache file",OFFSET(cache_key),      AV_OPT_TYPE_STRING, {.str = NULL}, .flags = D },
     {0},
 };