diff mbox series

[FFmpeg-devel,v8,1/1] avformat: Add IPFS protocol support.

Message ID 20220217144904.158764-2-markg85@gmail.com
State New
Headers show
Series Add IPFS protocol support. | expand

Checks

Context Check Description
andriy/make_x86 success Make finished
andriy/make_fate_x86 success Make fate finished
andriy/make_ppc success Make finished
andriy/make_fate_ppc success Make fate finished
yinshiyou/make_loongarch64 success Make finished
yinshiyou/make_fate_loongarch64 success Make fate finished
andriy/make_aarch64_jetson success Make finished
andriy/make_fate_aarch64_jetson success Make fate finished

Commit Message

Mark Gaiser Feb. 17, 2022, 2:49 p.m. UTC
This patch adds support for:
- ffplay ipfs://<cid>
- ffplay ipns://<cid>

IPFS data can be played from so called "ipfs gateways".
A gateway is essentially a webserver that gives access to the
distributed IPFS network.

This protocol support (ipfs and ipns) therefore translates
ipfs:// and ipns:// to a http:// url. This resulting url is
then handled by the http protocol. It could also be https
depending on the gateway provided.

To use this protocol, a gateway must be provided.
If you do nothing it will try to find it in your
$HOME/.ipfs/gateway file. The ways to set it manually are:
1. Define a -gateway <url> to the gateway.
2. Define $IPFS_GATEWAY with the full http link to the gateway.
3. Define $IPFS_PATH and point it to the IPFS data path.
4. Have IPFS running in your local user folder (under $HOME/.ipfs).

Signed-off-by: Mark Gaiser <markg85@gmail.com>
---
 configure                 |   2 +
 doc/protocols.texi        |  30 ++++
 libavformat/Makefile      |   2 +
 libavformat/ipfsgateway.c | 309 ++++++++++++++++++++++++++++++++++++++
 libavformat/protocols.c   |   2 +
 5 files changed, 345 insertions(+)
 create mode 100644 libavformat/ipfsgateway.c

Comments

Mark Gaiser Feb. 17, 2022, 2:57 p.m. UTC | #1
On Thu, Feb 17, 2022 at 3:50 PM Mark Gaiser <markg85@gmail.com> wrote:

> This patch adds support for:
> - ffplay ipfs://<cid>
> - ffplay ipns://<cid>
>
> IPFS data can be played from so called "ipfs gateways".
> A gateway is essentially a webserver that gives access to the
> distributed IPFS network.
>
> This protocol support (ipfs and ipns) therefore translates
> ipfs:// and ipns:// to a http:// url. This resulting url is
> then handled by the http protocol. It could also be https
> depending on the gateway provided.
>
> To use this protocol, a gateway must be provided.
> If you do nothing it will try to find it in your
> $HOME/.ipfs/gateway file. The ways to set it manually are:
> 1. Define a -gateway <url> to the gateway.
> 2. Define $IPFS_GATEWAY with the full http link to the gateway.
> 3. Define $IPFS_PATH and point it to the IPFS data path.
> 4. Have IPFS running in your local user folder (under $HOME/.ipfs).
>
> Signed-off-by: Mark Gaiser <markg85@gmail.com>
> ---
>  configure                 |   2 +
>  doc/protocols.texi        |  30 ++++
>  libavformat/Makefile      |   2 +
>  libavformat/ipfsgateway.c | 309 ++++++++++++++++++++++++++++++++++++++
>  libavformat/protocols.c   |   2 +
>  5 files changed, 345 insertions(+)
>  create mode 100644 libavformat/ipfsgateway.c
>
> diff --git a/configure b/configure
> index 5b19a35f59..6ff09e7974 100755
> --- a/configure
> +++ b/configure
> @@ -3585,6 +3585,8 @@ udp_protocol_select="network"
>  udplite_protocol_select="network"
>  unix_protocol_deps="sys_un_h"
>  unix_protocol_select="network"
> +ipfs_protocol_select="https_protocol"
> +ipns_protocol_select="https_protocol"
>
>  # external library protocols
>  libamqp_protocol_deps="librabbitmq"
> diff --git a/doc/protocols.texi b/doc/protocols.texi
> index d207df0b52..7c9c0a4808 100644
> --- a/doc/protocols.texi
> +++ b/doc/protocols.texi
> @@ -2025,5 +2025,35 @@ decoding errors.
>
>  @end table
>
> +@section ipfs
> +
> +InterPlanetary File System (IPFS) protocol support. One can access files
> stored
> +on the IPFS network through so called gateways. Those are http(s)
> endpoints.
> +This protocol wraps the IPFS native protocols (ipfs:// and ipns://) to be
> send
> +to such a gateway. Users can (and should) host their own node which means
> this
> +protocol will use your local machine gateway to access files on the IPFS
> network.
> +
> +If a user doesn't have a node of their own then the public gateway
> dweb.link is
> +used by default.
> +
> +You can use this protocol in 2 ways. Using IPFS:
> +@example
> +ffplay ipfs://QmbGtJg23skhvFmu9mJiePVByhfzu5rwo74MEkVDYAmF5T
> +@end example
> +
> +Or the IPNS protocol (IPNS is mutable IPFS):
> +@example
> +ffplay ipns://QmbGtJg23skhvFmu9mJiePVByhfzu5rwo74MEkVDYAmF5T
> +@end example
> +
> +You can also change the gateway to be used:
> +
> +@table @option
> +
> +@item gateway
> +Defines the gateway to use. When nothing is provided the protocol will
> first try
> +your local gateway. If that fails dweb.link will be used.
> +
> +@end table
>
>  @c man end PROTOCOLS
> diff --git a/libavformat/Makefile b/libavformat/Makefile
> index 3dc6a479cc..4edce8420f 100644
> --- a/libavformat/Makefile
> +++ b/libavformat/Makefile
> @@ -656,6 +656,8 @@ OBJS-$(CONFIG_SRTP_PROTOCOL)             +=
> srtpproto.o srtp.o
>  OBJS-$(CONFIG_SUBFILE_PROTOCOL)          += subfile.o
>  OBJS-$(CONFIG_TEE_PROTOCOL)              += teeproto.o tee_common.o
>  OBJS-$(CONFIG_TCP_PROTOCOL)              += tcp.o
> +OBJS-$(CONFIG_IPFS_PROTOCOL)             += ipfsgateway.o
> +OBJS-$(CONFIG_IPNS_PROTOCOL)             += ipfsgateway.o
>  TLS-OBJS-$(CONFIG_GNUTLS)                += tls_gnutls.o
>  TLS-OBJS-$(CONFIG_LIBTLS)                += tls_libtls.o
>  TLS-OBJS-$(CONFIG_MBEDTLS)               += tls_mbedtls.o
> diff --git a/libavformat/ipfsgateway.c b/libavformat/ipfsgateway.c
> new file mode 100644
> index 0000000000..7dfa56871d
> --- /dev/null
> +++ b/libavformat/ipfsgateway.c
> @@ -0,0 +1,309 @@
> +/*
> + * IPFS and IPNS protocol support through IPFS Gateway.
> + * Copyright (c) 2022 Mark Gaiser
> + *
> + * This file is part of FFmpeg.
> + *
> + * FFmpeg is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU Lesser General Public
> + * License as published by the Free Software Foundation; either
> + * version 2.1 of the License, or (at your option) any later version.
> + *
> + * FFmpeg is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> + * Lesser General Public License for more details.
> + *
> + * You should have received a copy of the GNU Lesser General Public
> + * License along with FFmpeg; if not, write to the Free Software
> + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
> 02110-1301 USA
> + */
> +
> +#include "avformat.h"
> +#include "libavutil/avassert.h"
> +#include "libavutil/avstring.h"
> +#include "libavutil/internal.h"
> +#include "libavutil/opt.h"
> +#include "libavutil/tree.h"
> +#include <fcntl.h>
> +#if HAVE_IO_H
> +#include <io.h>
> +#endif
> +#if HAVE_UNISTD_H
> +#include <unistd.h>
> +#endif
> +#include "os_support.h"
> +#include "url.h"
> +#include <stdlib.h>
> +#include <sys/stat.h>
> +
> +typedef struct IPFSGatewayContext {
> +    AVClass *class;
> +    URLContext *inner;
> +    // Is filled by the -gateway argument and not changed after.
> +    char *gateway;
> +    // If the above gateway is non null, it will be copied into this
> buffer.
> +    // Else this buffer will contain the auto detected gateway.
> +    // In either case, the gateway to use will be in this buffer.
> +    char gateway_buffer[PATH_MAX];
> +} IPFSGatewayContext;
> +
> +// A best-effort way to find the IPFS gateway.
> +// Only the most appropiate gateway is set. It's not actually requested
> +// (http call) to prevent a potential slowdown in startup. A potential
> timeout
> +// is handled by the HTTP protocol.
> +static int populate_ipfs_gateway(URLContext *h)
> +{
> +    IPFSGatewayContext *c = h->priv_data;
> +    char ipfs_full_data_folder[PATH_MAX];
> +    char ipfs_gateway_file[PATH_MAX];
> +    struct stat st;
> +    int stat_ret = 0;
> +    int ret = AVERROR(EINVAL);
> +    FILE *gateway_file = NULL;
> +
> +    // Test $IPFS_GATEWAY.
> +    if (getenv("IPFS_GATEWAY") != NULL) {
> +        if (snprintf(c->gateway_buffer, sizeof(c->gateway_buffer), "%s",
> +                     getenv("IPFS_GATEWAY")) >=
> sizeof(c->gateway_buffer)) {
> +            av_log(h, AV_LOG_ERROR, "The IPFS_GATEWAY environment
> variable exceeds the maximum length. We allow a max of %zu characters\n",
> sizeof(c->gateway_buffer));
> +            ret = AVERROR(EINVAL);
> +            goto err;
> +        }
> +
> +        ret = 1;
> +        goto err;
> +    } else
> +        av_log(h, AV_LOG_DEBUG, "$IPFS_GATEWAY is empty.\n");
> +
> +    // We need to know the IPFS folder to - eventually - read the
> contents of
> +    // the "gateway" file which would tell us the gateway to use.
> +    if (getenv("IPFS_PATH") == NULL) {
> +        av_log(h, AV_LOG_DEBUG, "$IPFS_PATH is empty.\n");
> +
> +        // Try via the home folder.
> +        if (getenv("HOME") == NULL) {
> +            av_log(h, AV_LOG_ERROR, "$HOME appears to be empty.\n");
> +            ret = AVERROR(EINVAL);
> +            goto err;
> +        }
> +
> +        // Verify the composed path fits.
> +        if (snprintf(ipfs_full_data_folder, sizeof(ipfs_full_data_folder),
> +                     "%s/.ipfs/", getenv("HOME")) >=
> sizeof(ipfs_full_data_folder)) {
> +            av_log(h, AV_LOG_ERROR, "The IPFS data path exceeds the max
> path length (%zu)\n", sizeof(ipfs_full_data_folder));
> +            ret = AVERROR(EINVAL);
> +            goto err;
> +        }
> +
> +        // Stat the folder.
> +        // It should exist in a default IPFS setup when run as local user.
> +#ifndef _WIN32
> +        stat_ret = stat(ipfs_full_data_folder, &st);
> +#else
> +        stat_ret = win32_stat(ipfs_full_data_folder, &st);
> +#endif
> +        if (stat_ret < 0) {
> +            av_log(h, AV_LOG_INFO, "Unable to find IPFS folder. We
> tried:\n");
> +            av_log(h, AV_LOG_INFO, "- $IPFS_PATH, which was empty.\n");
> +            av_log(h, AV_LOG_INFO, "- $HOME/.ipfs (full uri: %s) which
> doesn't exist.\n", ipfs_full_data_folder);
> +            ret = AVERROR(ENOENT);
> +            goto err;
> +        }
> +    } else {
> +        if (snprintf(ipfs_full_data_folder,
> sizeof(ipfs_full_data_folder), "%s",
> +                 getenv("IPFS_PATH")) >= sizeof(ipfs_full_data_folder)) {
> +            av_log(h, AV_LOG_ERROR, "The IPFS_PATH environment variable
> exceeds the maximum length. We allow a max of %zu characters\n",
> sizeof(c->gateway_buffer));
> +            ret = AVERROR(EINVAL);
> +            goto err;
> +        }
> +
> +    }
> +
> +    // Copy the fully composed gateway path into ipfs_gateway_file.
> +    if (snprintf(ipfs_gateway_file, sizeof(ipfs_gateway_file),
> "%sgateway",
> +                 ipfs_full_data_folder) >= sizeof(ipfs_gateway_file)) {
> +        av_log(h, AV_LOG_ERROR, "The IPFS gateway file path exceeds the
> max path length (%zu)\n", sizeof(ipfs_gateway_file));
> +        ret = AVERROR(ENOENT);
> +        goto err;
> +    }
> +
> +    // Get the contents of the gateway file.
> +    gateway_file = av_fopen_utf8(ipfs_gateway_file, "r");
> +    if (!gateway_file) {
> +        av_log(h, AV_LOG_ERROR, "The IPFS gateway file (full uri: %s)
> doesn't exist. Is the gateway enabled?\n", ipfs_gateway_file);
> +        ret = AVERROR(ENOENT);
> +        goto err;
> +    }
> +
> +    // Read a single line (fgets stops at new line mark).
> +    fgets(c->gateway_buffer, sizeof(c->gateway_buffer) - 1, gateway_file);
> +
> +    // Replace the last char with \0
> +    c->gateway_buffer[sizeof(c->gateway_buffer) - 1] = 0;
> +
> +    // Replace first occurence of end of line with \0
> +    c->gateway_buffer[strcspn(c->gateway_buffer, "\r")] = 0;
> +    c->gateway_buffer[strcspn(c->gateway_buffer, "\n")] = 0;
> +
> +    // If strlen finds anything longer then 0 characters then we have a
> +    // potential gateway url.
> +    if (strlen(c->gateway_buffer) < 1) {
> +        av_log(h, AV_LOG_ERROR, "The IPFS gateway file (full uri: %s)
> appears to be empty. Is the gateway started?\n", ipfs_gateway_file);
> +        ret = AVERROR(EILSEQ);
> +        goto err;
> +    } else {
> +        // We're done, the c->gateway_buffer has something that looks
> valid.
> +        ret = 1;
> +        goto err;
> +    }
> +
> +err:
> +    if (gateway_file)
> +        fclose(gateway_file);
> +
> +    return ret;
> +}
> +
> +static int translate_ipfs_to_http(URLContext *h, const char *uri,
> +                                  int flags, AVDictionary **options)
> +{
> +    const char *ipfs_cid;
> +    char *fulluri = NULL;
> +    int ret;
> +    IPFSGatewayContext *c = h->priv_data;
> +
> +    // Test for ipfs://, ipfs:, ipns:// and ipns:. This prefix is
> stripped from
> +    // the string leaving just the CID in ipfs_cid.
> +    int is_ipfs = av_stristart(uri, "ipfs://", &ipfs_cid);
> +    int is_ipns = av_stristart(uri, "ipns://", &ipfs_cid);
> +
> +    // We must have either ipns or ipfs.
> +    if (!is_ipfs && !is_ipns) {
> +        ret = AVERROR(EINVAL);
> +        av_log(h, AV_LOG_ERROR, "Unsupported url %s\n", uri);
> +        goto err;
> +    }
> +
> +    // If the CID has a length greater then 0 then we assume we have a
> proper working one.
> +    // It could still be wrong but in that case the gateway should save
> us and
> +    // ruturn a 403 error. The http protocol handles this.
> +    if (strlen(ipfs_cid) < 1) {
> +        av_log(h, AV_LOG_ERROR, "A CID must be provided.\n");
> +        ret = AVERROR(EILSEQ);
> +        goto err;
> +    }
> +
> +    // Populate c->gateway_buffer with whatever is in c->gateway
> +    if (c->gateway != NULL) {
> +        if (snprintf(c->gateway_buffer, sizeof(c->gateway_buffer), "%s",
> +                     c->gateway) >= sizeof(c->gateway_buffer)) {
> +            av_log(h, AV_LOG_ERROR, "The -gateway parameter is too long.
> We allow a max of %zu characters\n", sizeof(c->gateway_buffer));
> +            ret = AVERROR(EINVAL);
> +            goto err;
> +        }
> +    } else {
> +        // Populate the IPFS gateway if we have any.
> +        // If not, inform the user how to properly set one.
> +        ret = populate_ipfs_gateway(h);
> +
> +        if (ret < 1) {
> +            av_log(h, AV_LOG_ERROR, "No IPFS gateway was set. Make sure a
> local IPFS instance is running.\n");
> +            av_log(h, AV_LOG_INFO, "There are multiple options to define
> this gateway. The below options are in order of precedence:\n");
> +            av_log(h, AV_LOG_INFO, "1. Define a -gateway <url> to the
> gateway without trailing forward slash.\n");
> +            av_log(h, AV_LOG_INFO, "2. Define $IPFS_GATEWAY with the full
> http link to the gateway without trailing forward slash.\n");
> +            av_log(h, AV_LOG_INFO, "3. Define $IPFS_PATH and point it to
> the IPFS data path.\n");
> +            av_log(h, AV_LOG_INFO, "4. Have IPFS running in your local
> user folder (under $HOME/.ipfs).\n");
> +            av_log(h, AV_LOG_INFO, "In all path cases, a file named
> gateway is expected. See https://github.com/ipfs/specs/issues/261 for
> more information.\n");
> +            goto err;
> +        }
> +    }
> +
> +    // Test if the gateway starts with either http:// or https://
> +    if (av_stristart(c->gateway_buffer, "http://", NULL) == 0
> +        && av_stristart(c->gateway_buffer, "https://", NULL) == 0) {
> +        av_log(h, AV_LOG_ERROR, "The gateway URL didn't start with http://
> or https:// and is therefore invalid.\n");
> +        ret = AVERROR(EILSEQ);
> +        goto err;
> +    }
> +
> +    // Concatenate the url.
> +    // This ends up with something like: http://localhost:8080/ipfs/Qm...
> ..
> +    // The format of "%s%s%s%s" is the following:
> +    // 1st %s = The gateway.
> +    // 2nd %s = If the gateway didn't end in a slash, add a "/".
> Otherwise it's an empty string
> +    // 3rd %s = Either ipns/ or ipfs/.
> +    // 4th %s = The IPFS CID (Qm..., bafy..., ...).
> +    fulluri = av_asprintf("%s%s%s%s",
> +                          c->gateway_buffer,
> +                          (c->gateway_buffer[strlen(c->gateway_buffer) -
> 1] == '/') ? "" : "/",
> +                          (is_ipns) ? "ipns/" : "ipfs/",
> +                          ipfs_cid);
> +
> +    // Pass the URL back to FFMpeg's protocol handler.
> +    if ((ret = ffurl_open_whitelist(&c->inner, fulluri, flags,
> +                                    &h->interrupt_callback, options,
> +                                    h->protocol_whitelist,
> +                                    h->protocol_blacklist, h))
> +        < 0) {
> +        av_log(h, AV_LOG_ERROR, "Unable to open resource: %s\n", fulluri);
> +        goto err;
> +    }
> +
> +err:
> +    av_free(fulluri);
> +    return ret;
> +}
> +
> +static int ipfs_read(URLContext *h, unsigned char *buf, int size)
> +{
> +    IPFSGatewayContext *c = h->priv_data;
> +    return ffurl_read(c->inner, buf, size);
> +}
> +
> +static int64_t ipfs_seek(URLContext *h, int64_t pos, int whence)
> +{
> +    IPFSGatewayContext *c = h->priv_data;
> +    return ffurl_seek(c->inner, pos, whence);
> +}
> +
> +static int ipfs_close(URLContext *h)
> +{
> +    IPFSGatewayContext *c = h->priv_data;
> +    av_free(c->gateway);
> +    return ffurl_closep(&c->inner);
> +}
> +
> +#define OFFSET(x) offsetof(IPFSGatewayContext, x)
> +
> +static const AVOption options[] = {
> +    {"gateway", "The gateway to ask for IPFS data.", OFFSET(gateway),
> AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, AV_OPT_FLAG_DECODING_PARAM},
> +    {NULL},
> +};
> +
> +static const AVClass ipfs_context_class = {
> +    .class_name   = "IPFS",
> +    .item_name    = av_default_item_name,
> +    .option       = options,
> +    .version      = LIBAVUTIL_VERSION_INT,
> +};
> +
> +const URLProtocol ff_ipfs_protocol = {
> +    .name             = "ipfs",
> +    .url_open2        = translate_ipfs_to_http,
> +    .url_read         = ipfs_read,
> +    .url_seek         = ipfs_seek,
> +    .url_close        = ipfs_close,
> +    .priv_data_size   = sizeof(IPFSGatewayContext),
> +    .priv_data_class  = &ipfs_context_class,
> +};
> +
> +const URLProtocol ff_ipns_protocol = {
> +    .name             = "ipns",
> +    .url_open2        = translate_ipfs_to_http,
> +    .url_read         = ipfs_read,
> +    .url_seek         = ipfs_seek,
> +    .url_close        = ipfs_close,
> +    .priv_data_size   = sizeof(IPFSGatewayContext),
> +    .priv_data_class  = &ipfs_context_class,
> +};
> diff --git a/libavformat/protocols.c b/libavformat/protocols.c
> index 948fae411f..675b684bd3 100644
> --- a/libavformat/protocols.c
> +++ b/libavformat/protocols.c
> @@ -73,6 +73,8 @@ extern const URLProtocol ff_libsrt_protocol;
>  extern const URLProtocol ff_libssh_protocol;
>  extern const URLProtocol ff_libsmbclient_protocol;
>  extern const URLProtocol ff_libzmq_protocol;
> +extern const URLProtocol ff_ipfs_protocol;
> +extern const URLProtocol ff_ipns_protocol;
>
>  #include "libavformat/protocol_list.c"
>
> --
> 2.35.1
>
>
There we go, V8. Would this be the final one? I hope so! :)
Just in case this is the magic final version that is allowed to be merged.
How do I proceed in that case? I don't have merge powers...

A small note on the log lines.
I do prefer to keep them on one line. This seems to be in line with other
ffmpeg code files (i only looked at a couple other protocols like crypto,
http and avio).
Splitting them looks - imho - quite ugly.
Mark Gaiser Feb. 21, 2022, 11:32 a.m. UTC | #2
On Thu, Feb 17, 2022 at 3:57 PM Mark Gaiser <markg85@gmail.com> wrote:

> On Thu, Feb 17, 2022 at 3:50 PM Mark Gaiser <markg85@gmail.com> wrote:
>
>> This patch adds support for:
>> - ffplay ipfs://<cid>
>> - ffplay ipns://<cid>
>>
>> IPFS data can be played from so called "ipfs gateways".
>> A gateway is essentially a webserver that gives access to the
>> distributed IPFS network.
>>
>> This protocol support (ipfs and ipns) therefore translates
>> ipfs:// and ipns:// to a http:// url. This resulting url is
>> then handled by the http protocol. It could also be https
>> depending on the gateway provided.
>>
>> To use this protocol, a gateway must be provided.
>> If you do nothing it will try to find it in your
>> $HOME/.ipfs/gateway file. The ways to set it manually are:
>> 1. Define a -gateway <url> to the gateway.
>> 2. Define $IPFS_GATEWAY with the full http link to the gateway.
>> 3. Define $IPFS_PATH and point it to the IPFS data path.
>> 4. Have IPFS running in your local user folder (under $HOME/.ipfs).
>>
>> Signed-off-by: Mark Gaiser <markg85@gmail.com>
>> ---
>>  configure                 |   2 +
>>  doc/protocols.texi        |  30 ++++
>>  libavformat/Makefile      |   2 +
>>  libavformat/ipfsgateway.c | 309 ++++++++++++++++++++++++++++++++++++++
>>  libavformat/protocols.c   |   2 +
>>  5 files changed, 345 insertions(+)
>>  create mode 100644 libavformat/ipfsgateway.c
>>
>> diff --git a/configure b/configure
>> index 5b19a35f59..6ff09e7974 100755
>> --- a/configure
>> +++ b/configure
>> @@ -3585,6 +3585,8 @@ udp_protocol_select="network"
>>  udplite_protocol_select="network"
>>  unix_protocol_deps="sys_un_h"
>>  unix_protocol_select="network"
>> +ipfs_protocol_select="https_protocol"
>> +ipns_protocol_select="https_protocol"
>>
>>  # external library protocols
>>  libamqp_protocol_deps="librabbitmq"
>> diff --git a/doc/protocols.texi b/doc/protocols.texi
>> index d207df0b52..7c9c0a4808 100644
>> --- a/doc/protocols.texi
>> +++ b/doc/protocols.texi
>> @@ -2025,5 +2025,35 @@ decoding errors.
>>
>>  @end table
>>
>> +@section ipfs
>> +
>> +InterPlanetary File System (IPFS) protocol support. One can access files
>> stored
>> +on the IPFS network through so called gateways. Those are http(s)
>> endpoints.
>> +This protocol wraps the IPFS native protocols (ipfs:// and ipns://) to
>> be send
>> +to such a gateway. Users can (and should) host their own node which
>> means this
>> +protocol will use your local machine gateway to access files on the IPFS
>> network.
>> +
>> +If a user doesn't have a node of their own then the public gateway
>> dweb.link is
>> +used by default.
>> +
>> +You can use this protocol in 2 ways. Using IPFS:
>> +@example
>> +ffplay ipfs://QmbGtJg23skhvFmu9mJiePVByhfzu5rwo74MEkVDYAmF5T
>> +@end example
>> +
>> +Or the IPNS protocol (IPNS is mutable IPFS):
>> +@example
>> +ffplay ipns://QmbGtJg23skhvFmu9mJiePVByhfzu5rwo74MEkVDYAmF5T
>> +@end example
>> +
>> +You can also change the gateway to be used:
>> +
>> +@table @option
>> +
>> +@item gateway
>> +Defines the gateway to use. When nothing is provided the protocol will
>> first try
>> +your local gateway. If that fails dweb.link will be used.
>> +
>> +@end table
>>
>>  @c man end PROTOCOLS
>> diff --git a/libavformat/Makefile b/libavformat/Makefile
>> index 3dc6a479cc..4edce8420f 100644
>> --- a/libavformat/Makefile
>> +++ b/libavformat/Makefile
>> @@ -656,6 +656,8 @@ OBJS-$(CONFIG_SRTP_PROTOCOL)             +=
>> srtpproto.o srtp.o
>>  OBJS-$(CONFIG_SUBFILE_PROTOCOL)          += subfile.o
>>  OBJS-$(CONFIG_TEE_PROTOCOL)              += teeproto.o tee_common.o
>>  OBJS-$(CONFIG_TCP_PROTOCOL)              += tcp.o
>> +OBJS-$(CONFIG_IPFS_PROTOCOL)             += ipfsgateway.o
>> +OBJS-$(CONFIG_IPNS_PROTOCOL)             += ipfsgateway.o
>>  TLS-OBJS-$(CONFIG_GNUTLS)                += tls_gnutls.o
>>  TLS-OBJS-$(CONFIG_LIBTLS)                += tls_libtls.o
>>  TLS-OBJS-$(CONFIG_MBEDTLS)               += tls_mbedtls.o
>> diff --git a/libavformat/ipfsgateway.c b/libavformat/ipfsgateway.c
>> new file mode 100644
>> index 0000000000..7dfa56871d
>> --- /dev/null
>> +++ b/libavformat/ipfsgateway.c
>> @@ -0,0 +1,309 @@
>> +/*
>> + * IPFS and IPNS protocol support through IPFS Gateway.
>> + * Copyright (c) 2022 Mark Gaiser
>> + *
>> + * This file is part of FFmpeg.
>> + *
>> + * FFmpeg is free software; you can redistribute it and/or
>> + * modify it under the terms of the GNU Lesser General Public
>> + * License as published by the Free Software Foundation; either
>> + * version 2.1 of the License, or (at your option) any later version.
>> + *
>> + * FFmpeg is distributed in the hope that it will be useful,
>> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
>> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
>> + * Lesser General Public License for more details.
>> + *
>> + * You should have received a copy of the GNU Lesser General Public
>> + * License along with FFmpeg; if not, write to the Free Software
>> + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
>> 02110-1301 USA
>> + */
>> +
>> +#include "avformat.h"
>> +#include "libavutil/avassert.h"
>> +#include "libavutil/avstring.h"
>> +#include "libavutil/internal.h"
>> +#include "libavutil/opt.h"
>> +#include "libavutil/tree.h"
>> +#include <fcntl.h>
>> +#if HAVE_IO_H
>> +#include <io.h>
>> +#endif
>> +#if HAVE_UNISTD_H
>> +#include <unistd.h>
>> +#endif
>> +#include "os_support.h"
>> +#include "url.h"
>> +#include <stdlib.h>
>> +#include <sys/stat.h>
>> +
>> +typedef struct IPFSGatewayContext {
>> +    AVClass *class;
>> +    URLContext *inner;
>> +    // Is filled by the -gateway argument and not changed after.
>> +    char *gateway;
>> +    // If the above gateway is non null, it will be copied into this
>> buffer.
>> +    // Else this buffer will contain the auto detected gateway.
>> +    // In either case, the gateway to use will be in this buffer.
>> +    char gateway_buffer[PATH_MAX];
>> +} IPFSGatewayContext;
>> +
>> +// A best-effort way to find the IPFS gateway.
>> +// Only the most appropiate gateway is set. It's not actually requested
>> +// (http call) to prevent a potential slowdown in startup. A potential
>> timeout
>> +// is handled by the HTTP protocol.
>> +static int populate_ipfs_gateway(URLContext *h)
>> +{
>> +    IPFSGatewayContext *c = h->priv_data;
>> +    char ipfs_full_data_folder[PATH_MAX];
>> +    char ipfs_gateway_file[PATH_MAX];
>> +    struct stat st;
>> +    int stat_ret = 0;
>> +    int ret = AVERROR(EINVAL);
>> +    FILE *gateway_file = NULL;
>> +
>> +    // Test $IPFS_GATEWAY.
>> +    if (getenv("IPFS_GATEWAY") != NULL) {
>> +        if (snprintf(c->gateway_buffer, sizeof(c->gateway_buffer), "%s",
>> +                     getenv("IPFS_GATEWAY")) >=
>> sizeof(c->gateway_buffer)) {
>> +            av_log(h, AV_LOG_ERROR, "The IPFS_GATEWAY environment
>> variable exceeds the maximum length. We allow a max of %zu characters\n",
>> sizeof(c->gateway_buffer));
>> +            ret = AVERROR(EINVAL);
>> +            goto err;
>> +        }
>> +
>> +        ret = 1;
>> +        goto err;
>> +    } else
>> +        av_log(h, AV_LOG_DEBUG, "$IPFS_GATEWAY is empty.\n");
>> +
>> +    // We need to know the IPFS folder to - eventually - read the
>> contents of
>> +    // the "gateway" file which would tell us the gateway to use.
>> +    if (getenv("IPFS_PATH") == NULL) {
>> +        av_log(h, AV_LOG_DEBUG, "$IPFS_PATH is empty.\n");
>> +
>> +        // Try via the home folder.
>> +        if (getenv("HOME") == NULL) {
>> +            av_log(h, AV_LOG_ERROR, "$HOME appears to be empty.\n");
>> +            ret = AVERROR(EINVAL);
>> +            goto err;
>> +        }
>> +
>> +        // Verify the composed path fits.
>> +        if (snprintf(ipfs_full_data_folder,
>> sizeof(ipfs_full_data_folder),
>> +                     "%s/.ipfs/", getenv("HOME")) >=
>> sizeof(ipfs_full_data_folder)) {
>> +            av_log(h, AV_LOG_ERROR, "The IPFS data path exceeds the max
>> path length (%zu)\n", sizeof(ipfs_full_data_folder));
>> +            ret = AVERROR(EINVAL);
>> +            goto err;
>> +        }
>> +
>> +        // Stat the folder.
>> +        // It should exist in a default IPFS setup when run as local
>> user.
>> +#ifndef _WIN32
>> +        stat_ret = stat(ipfs_full_data_folder, &st);
>> +#else
>> +        stat_ret = win32_stat(ipfs_full_data_folder, &st);
>> +#endif
>> +        if (stat_ret < 0) {
>> +            av_log(h, AV_LOG_INFO, "Unable to find IPFS folder. We
>> tried:\n");
>> +            av_log(h, AV_LOG_INFO, "- $IPFS_PATH, which was empty.\n");
>> +            av_log(h, AV_LOG_INFO, "- $HOME/.ipfs (full uri: %s) which
>> doesn't exist.\n", ipfs_full_data_folder);
>> +            ret = AVERROR(ENOENT);
>> +            goto err;
>> +        }
>> +    } else {
>> +        if (snprintf(ipfs_full_data_folder,
>> sizeof(ipfs_full_data_folder), "%s",
>> +                 getenv("IPFS_PATH")) >= sizeof(ipfs_full_data_folder)) {
>> +            av_log(h, AV_LOG_ERROR, "The IPFS_PATH environment variable
>> exceeds the maximum length. We allow a max of %zu characters\n",
>> sizeof(c->gateway_buffer));
>> +            ret = AVERROR(EINVAL);
>> +            goto err;
>> +        }
>> +
>> +    }
>> +
>> +    // Copy the fully composed gateway path into ipfs_gateway_file.
>> +    if (snprintf(ipfs_gateway_file, sizeof(ipfs_gateway_file),
>> "%sgateway",
>> +                 ipfs_full_data_folder) >= sizeof(ipfs_gateway_file)) {
>> +        av_log(h, AV_LOG_ERROR, "The IPFS gateway file path exceeds the
>> max path length (%zu)\n", sizeof(ipfs_gateway_file));
>> +        ret = AVERROR(ENOENT);
>> +        goto err;
>> +    }
>> +
>> +    // Get the contents of the gateway file.
>> +    gateway_file = av_fopen_utf8(ipfs_gateway_file, "r");
>> +    if (!gateway_file) {
>> +        av_log(h, AV_LOG_ERROR, "The IPFS gateway file (full uri: %s)
>> doesn't exist. Is the gateway enabled?\n", ipfs_gateway_file);
>> +        ret = AVERROR(ENOENT);
>> +        goto err;
>> +    }
>> +
>> +    // Read a single line (fgets stops at new line mark).
>> +    fgets(c->gateway_buffer, sizeof(c->gateway_buffer) - 1,
>> gateway_file);
>> +
>> +    // Replace the last char with \0
>> +    c->gateway_buffer[sizeof(c->gateway_buffer) - 1] = 0;
>> +
>> +    // Replace first occurence of end of line with \0
>> +    c->gateway_buffer[strcspn(c->gateway_buffer, "\r")] = 0;
>> +    c->gateway_buffer[strcspn(c->gateway_buffer, "\n")] = 0;
>> +
>> +    // If strlen finds anything longer then 0 characters then we have a
>> +    // potential gateway url.
>> +    if (strlen(c->gateway_buffer) < 1) {
>> +        av_log(h, AV_LOG_ERROR, "The IPFS gateway file (full uri: %s)
>> appears to be empty. Is the gateway started?\n", ipfs_gateway_file);
>> +        ret = AVERROR(EILSEQ);
>> +        goto err;
>> +    } else {
>> +        // We're done, the c->gateway_buffer has something that looks
>> valid.
>> +        ret = 1;
>> +        goto err;
>> +    }
>> +
>> +err:
>> +    if (gateway_file)
>> +        fclose(gateway_file);
>> +
>> +    return ret;
>> +}
>> +
>> +static int translate_ipfs_to_http(URLContext *h, const char *uri,
>> +                                  int flags, AVDictionary **options)
>> +{
>> +    const char *ipfs_cid;
>> +    char *fulluri = NULL;
>> +    int ret;
>> +    IPFSGatewayContext *c = h->priv_data;
>> +
>> +    // Test for ipfs://, ipfs:, ipns:// and ipns:. This prefix is
>> stripped from
>> +    // the string leaving just the CID in ipfs_cid.
>> +    int is_ipfs = av_stristart(uri, "ipfs://", &ipfs_cid);
>> +    int is_ipns = av_stristart(uri, "ipns://", &ipfs_cid);
>> +
>> +    // We must have either ipns or ipfs.
>> +    if (!is_ipfs && !is_ipns) {
>> +        ret = AVERROR(EINVAL);
>> +        av_log(h, AV_LOG_ERROR, "Unsupported url %s\n", uri);
>> +        goto err;
>> +    }
>> +
>> +    // If the CID has a length greater then 0 then we assume we have a
>> proper working one.
>> +    // It could still be wrong but in that case the gateway should save
>> us and
>> +    // ruturn a 403 error. The http protocol handles this.
>> +    if (strlen(ipfs_cid) < 1) {
>> +        av_log(h, AV_LOG_ERROR, "A CID must be provided.\n");
>> +        ret = AVERROR(EILSEQ);
>> +        goto err;
>> +    }
>> +
>> +    // Populate c->gateway_buffer with whatever is in c->gateway
>> +    if (c->gateway != NULL) {
>> +        if (snprintf(c->gateway_buffer, sizeof(c->gateway_buffer), "%s",
>> +                     c->gateway) >= sizeof(c->gateway_buffer)) {
>> +            av_log(h, AV_LOG_ERROR, "The -gateway parameter is too long.
>> We allow a max of %zu characters\n", sizeof(c->gateway_buffer));
>> +            ret = AVERROR(EINVAL);
>> +            goto err;
>> +        }
>> +    } else {
>> +        // Populate the IPFS gateway if we have any.
>> +        // If not, inform the user how to properly set one.
>> +        ret = populate_ipfs_gateway(h);
>> +
>> +        if (ret < 1) {
>> +            av_log(h, AV_LOG_ERROR, "No IPFS gateway was set. Make sure
>> a local IPFS instance is running.\n");
>> +            av_log(h, AV_LOG_INFO, "There are multiple options to define
>> this gateway. The below options are in order of precedence:\n");
>> +            av_log(h, AV_LOG_INFO, "1. Define a -gateway <url> to the
>> gateway without trailing forward slash.\n");
>> +            av_log(h, AV_LOG_INFO, "2. Define $IPFS_GATEWAY with the
>> full http link to the gateway without trailing forward slash.\n");
>> +            av_log(h, AV_LOG_INFO, "3. Define $IPFS_PATH and point it to
>> the IPFS data path.\n");
>> +            av_log(h, AV_LOG_INFO, "4. Have IPFS running in your local
>> user folder (under $HOME/.ipfs).\n");
>> +            av_log(h, AV_LOG_INFO, "In all path cases, a file named
>> gateway is expected. See https://github.com/ipfs/specs/issues/261 for
>> more information.\n");
>> +            goto err;
>> +        }
>> +    }
>> +
>> +    // Test if the gateway starts with either http:// or https://
>> +    if (av_stristart(c->gateway_buffer, "http://", NULL) == 0
>> +        && av_stristart(c->gateway_buffer, "https://", NULL) == 0) {
>> +        av_log(h, AV_LOG_ERROR, "The gateway URL didn't start with
>> http:// or https:// and is therefore invalid.\n");
>> +        ret = AVERROR(EILSEQ);
>> +        goto err;
>> +    }
>> +
>> +    // Concatenate the url.
>> +    // This ends up with something like:
>> http://localhost:8080/ipfs/Qm.....
>> +    // The format of "%s%s%s%s" is the following:
>> +    // 1st %s = The gateway.
>> +    // 2nd %s = If the gateway didn't end in a slash, add a "/".
>> Otherwise it's an empty string
>> +    // 3rd %s = Either ipns/ or ipfs/.
>> +    // 4th %s = The IPFS CID (Qm..., bafy..., ...).
>> +    fulluri = av_asprintf("%s%s%s%s",
>> +                          c->gateway_buffer,
>> +                          (c->gateway_buffer[strlen(c->gateway_buffer) -
>> 1] == '/') ? "" : "/",
>> +                          (is_ipns) ? "ipns/" : "ipfs/",
>> +                          ipfs_cid);
>> +
>> +    // Pass the URL back to FFMpeg's protocol handler.
>> +    if ((ret = ffurl_open_whitelist(&c->inner, fulluri, flags,
>> +                                    &h->interrupt_callback, options,
>> +                                    h->protocol_whitelist,
>> +                                    h->protocol_blacklist, h))
>> +        < 0) {
>> +        av_log(h, AV_LOG_ERROR, "Unable to open resource: %s\n",
>> fulluri);
>> +        goto err;
>> +    }
>> +
>> +err:
>> +    av_free(fulluri);
>> +    return ret;
>> +}
>> +
>> +static int ipfs_read(URLContext *h, unsigned char *buf, int size)
>> +{
>> +    IPFSGatewayContext *c = h->priv_data;
>> +    return ffurl_read(c->inner, buf, size);
>> +}
>> +
>> +static int64_t ipfs_seek(URLContext *h, int64_t pos, int whence)
>> +{
>> +    IPFSGatewayContext *c = h->priv_data;
>> +    return ffurl_seek(c->inner, pos, whence);
>> +}
>> +
>> +static int ipfs_close(URLContext *h)
>> +{
>> +    IPFSGatewayContext *c = h->priv_data;
>> +    av_free(c->gateway);
>> +    return ffurl_closep(&c->inner);
>> +}
>> +
>> +#define OFFSET(x) offsetof(IPFSGatewayContext, x)
>> +
>> +static const AVOption options[] = {
>> +    {"gateway", "The gateway to ask for IPFS data.", OFFSET(gateway),
>> AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, AV_OPT_FLAG_DECODING_PARAM},
>> +    {NULL},
>> +};
>> +
>> +static const AVClass ipfs_context_class = {
>> +    .class_name   = "IPFS",
>> +    .item_name    = av_default_item_name,
>> +    .option       = options,
>> +    .version      = LIBAVUTIL_VERSION_INT,
>> +};
>> +
>> +const URLProtocol ff_ipfs_protocol = {
>> +    .name             = "ipfs",
>> +    .url_open2        = translate_ipfs_to_http,
>> +    .url_read         = ipfs_read,
>> +    .url_seek         = ipfs_seek,
>> +    .url_close        = ipfs_close,
>> +    .priv_data_size   = sizeof(IPFSGatewayContext),
>> +    .priv_data_class  = &ipfs_context_class,
>> +};
>> +
>> +const URLProtocol ff_ipns_protocol = {
>> +    .name             = "ipns",
>> +    .url_open2        = translate_ipfs_to_http,
>> +    .url_read         = ipfs_read,
>> +    .url_seek         = ipfs_seek,
>> +    .url_close        = ipfs_close,
>> +    .priv_data_size   = sizeof(IPFSGatewayContext),
>> +    .priv_data_class  = &ipfs_context_class,
>> +};
>> diff --git a/libavformat/protocols.c b/libavformat/protocols.c
>> index 948fae411f..675b684bd3 100644
>> --- a/libavformat/protocols.c
>> +++ b/libavformat/protocols.c
>> @@ -73,6 +73,8 @@ extern const URLProtocol ff_libsrt_protocol;
>>  extern const URLProtocol ff_libssh_protocol;
>>  extern const URLProtocol ff_libsmbclient_protocol;
>>  extern const URLProtocol ff_libzmq_protocol;
>> +extern const URLProtocol ff_ipfs_protocol;
>> +extern const URLProtocol ff_ipns_protocol;
>>
>>  #include "libavformat/protocol_list.c"
>>
>> --
>> 2.35.1
>>
>>
> There we go, V8. Would this be the final one? I hope so! :)
> Just in case this is the magic final version that is allowed to be merged.
> How do I proceed in that case? I don't have merge powers...
>
> A small note on the log lines.
> I do prefer to keep them on one line. This seems to be in line with other
> ffmpeg code files (i only looked at a couple other protocols like crypto,
> http and avio).
> Splitting them looks - imho - quite ugly.
>

ping
Mark Gaiser Feb. 27, 2022, 2:29 p.m. UTC | #3
Ping 2....

I'd really like to get this merged!
This kinda blocks me right now from proceeding with IPFS integration in
Kodi, MPV and VLC. Implementations in those (who rely on ffmpeg) are
significantly easier once this patch is finally landed in ffmpeg.

On Mon, Feb 21, 2022 at 12:32 PM Mark Gaiser <markg85@gmail.com> wrote:

> On Thu, Feb 17, 2022 at 3:57 PM Mark Gaiser <markg85@gmail.com> wrote:
>
>> On Thu, Feb 17, 2022 at 3:50 PM Mark Gaiser <markg85@gmail.com> wrote:
>>
>>> This patch adds support for:
>>> - ffplay ipfs://<cid>
>>> - ffplay ipns://<cid>
>>>
>>> IPFS data can be played from so called "ipfs gateways".
>>> A gateway is essentially a webserver that gives access to the
>>> distributed IPFS network.
>>>
>>> This protocol support (ipfs and ipns) therefore translates
>>> ipfs:// and ipns:// to a http:// url. This resulting url is
>>> then handled by the http protocol. It could also be https
>>> depending on the gateway provided.
>>>
>>> To use this protocol, a gateway must be provided.
>>> If you do nothing it will try to find it in your
>>> $HOME/.ipfs/gateway file. The ways to set it manually are:
>>> 1. Define a -gateway <url> to the gateway.
>>> 2. Define $IPFS_GATEWAY with the full http link to the gateway.
>>> 3. Define $IPFS_PATH and point it to the IPFS data path.
>>> 4. Have IPFS running in your local user folder (under $HOME/.ipfs).
>>>
>>> Signed-off-by: Mark Gaiser <markg85@gmail.com>
>>> ---
>>>  configure                 |   2 +
>>>  doc/protocols.texi        |  30 ++++
>>>  libavformat/Makefile      |   2 +
>>>  libavformat/ipfsgateway.c | 309 ++++++++++++++++++++++++++++++++++++++
>>>  libavformat/protocols.c   |   2 +
>>>  5 files changed, 345 insertions(+)
>>>  create mode 100644 libavformat/ipfsgateway.c
>>>
>>> diff --git a/configure b/configure
>>> index 5b19a35f59..6ff09e7974 100755
>>> --- a/configure
>>> +++ b/configure
>>> @@ -3585,6 +3585,8 @@ udp_protocol_select="network"
>>>  udplite_protocol_select="network"
>>>  unix_protocol_deps="sys_un_h"
>>>  unix_protocol_select="network"
>>> +ipfs_protocol_select="https_protocol"
>>> +ipns_protocol_select="https_protocol"
>>>
>>>  # external library protocols
>>>  libamqp_protocol_deps="librabbitmq"
>>> diff --git a/doc/protocols.texi b/doc/protocols.texi
>>> index d207df0b52..7c9c0a4808 100644
>>> --- a/doc/protocols.texi
>>> +++ b/doc/protocols.texi
>>> @@ -2025,5 +2025,35 @@ decoding errors.
>>>
>>>  @end table
>>>
>>> +@section ipfs
>>> +
>>> +InterPlanetary File System (IPFS) protocol support. One can access
>>> files stored
>>> +on the IPFS network through so called gateways. Those are http(s)
>>> endpoints.
>>> +This protocol wraps the IPFS native protocols (ipfs:// and ipns://) to
>>> be send
>>> +to such a gateway. Users can (and should) host their own node which
>>> means this
>>> +protocol will use your local machine gateway to access files on the
>>> IPFS network.
>>> +
>>> +If a user doesn't have a node of their own then the public gateway
>>> dweb.link is
>>> +used by default.
>>> +
>>> +You can use this protocol in 2 ways. Using IPFS:
>>> +@example
>>> +ffplay ipfs://QmbGtJg23skhvFmu9mJiePVByhfzu5rwo74MEkVDYAmF5T
>>> +@end example
>>> +
>>> +Or the IPNS protocol (IPNS is mutable IPFS):
>>> +@example
>>> +ffplay ipns://QmbGtJg23skhvFmu9mJiePVByhfzu5rwo74MEkVDYAmF5T
>>> +@end example
>>> +
>>> +You can also change the gateway to be used:
>>> +
>>> +@table @option
>>> +
>>> +@item gateway
>>> +Defines the gateway to use. When nothing is provided the protocol will
>>> first try
>>> +your local gateway. If that fails dweb.link will be used.
>>> +
>>> +@end table
>>>
>>>  @c man end PROTOCOLS
>>> diff --git a/libavformat/Makefile b/libavformat/Makefile
>>> index 3dc6a479cc..4edce8420f 100644
>>> --- a/libavformat/Makefile
>>> +++ b/libavformat/Makefile
>>> @@ -656,6 +656,8 @@ OBJS-$(CONFIG_SRTP_PROTOCOL)             +=
>>> srtpproto.o srtp.o
>>>  OBJS-$(CONFIG_SUBFILE_PROTOCOL)          += subfile.o
>>>  OBJS-$(CONFIG_TEE_PROTOCOL)              += teeproto.o tee_common.o
>>>  OBJS-$(CONFIG_TCP_PROTOCOL)              += tcp.o
>>> +OBJS-$(CONFIG_IPFS_PROTOCOL)             += ipfsgateway.o
>>> +OBJS-$(CONFIG_IPNS_PROTOCOL)             += ipfsgateway.o
>>>  TLS-OBJS-$(CONFIG_GNUTLS)                += tls_gnutls.o
>>>  TLS-OBJS-$(CONFIG_LIBTLS)                += tls_libtls.o
>>>  TLS-OBJS-$(CONFIG_MBEDTLS)               += tls_mbedtls.o
>>> diff --git a/libavformat/ipfsgateway.c b/libavformat/ipfsgateway.c
>>> new file mode 100644
>>> index 0000000000..7dfa56871d
>>> --- /dev/null
>>> +++ b/libavformat/ipfsgateway.c
>>> @@ -0,0 +1,309 @@
>>> +/*
>>> + * IPFS and IPNS protocol support through IPFS Gateway.
>>> + * Copyright (c) 2022 Mark Gaiser
>>> + *
>>> + * This file is part of FFmpeg.
>>> + *
>>> + * FFmpeg is free software; you can redistribute it and/or
>>> + * modify it under the terms of the GNU Lesser General Public
>>> + * License as published by the Free Software Foundation; either
>>> + * version 2.1 of the License, or (at your option) any later version.
>>> + *
>>> + * FFmpeg is distributed in the hope that it will be useful,
>>> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
>>> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
>>> + * Lesser General Public License for more details.
>>> + *
>>> + * You should have received a copy of the GNU Lesser General Public
>>> + * License along with FFmpeg; if not, write to the Free Software
>>> + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
>>> 02110-1301 USA
>>> + */
>>> +
>>> +#include "avformat.h"
>>> +#include "libavutil/avassert.h"
>>> +#include "libavutil/avstring.h"
>>> +#include "libavutil/internal.h"
>>> +#include "libavutil/opt.h"
>>> +#include "libavutil/tree.h"
>>> +#include <fcntl.h>
>>> +#if HAVE_IO_H
>>> +#include <io.h>
>>> +#endif
>>> +#if HAVE_UNISTD_H
>>> +#include <unistd.h>
>>> +#endif
>>> +#include "os_support.h"
>>> +#include "url.h"
>>> +#include <stdlib.h>
>>> +#include <sys/stat.h>
>>> +
>>> +typedef struct IPFSGatewayContext {
>>> +    AVClass *class;
>>> +    URLContext *inner;
>>> +    // Is filled by the -gateway argument and not changed after.
>>> +    char *gateway;
>>> +    // If the above gateway is non null, it will be copied into this
>>> buffer.
>>> +    // Else this buffer will contain the auto detected gateway.
>>> +    // In either case, the gateway to use will be in this buffer.
>>> +    char gateway_buffer[PATH_MAX];
>>> +} IPFSGatewayContext;
>>> +
>>> +// A best-effort way to find the IPFS gateway.
>>> +// Only the most appropiate gateway is set. It's not actually requested
>>> +// (http call) to prevent a potential slowdown in startup. A potential
>>> timeout
>>> +// is handled by the HTTP protocol.
>>> +static int populate_ipfs_gateway(URLContext *h)
>>> +{
>>> +    IPFSGatewayContext *c = h->priv_data;
>>> +    char ipfs_full_data_folder[PATH_MAX];
>>> +    char ipfs_gateway_file[PATH_MAX];
>>> +    struct stat st;
>>> +    int stat_ret = 0;
>>> +    int ret = AVERROR(EINVAL);
>>> +    FILE *gateway_file = NULL;
>>> +
>>> +    // Test $IPFS_GATEWAY.
>>> +    if (getenv("IPFS_GATEWAY") != NULL) {
>>> +        if (snprintf(c->gateway_buffer, sizeof(c->gateway_buffer), "%s",
>>> +                     getenv("IPFS_GATEWAY")) >=
>>> sizeof(c->gateway_buffer)) {
>>> +            av_log(h, AV_LOG_ERROR, "The IPFS_GATEWAY environment
>>> variable exceeds the maximum length. We allow a max of %zu characters\n",
>>> sizeof(c->gateway_buffer));
>>> +            ret = AVERROR(EINVAL);
>>> +            goto err;
>>> +        }
>>> +
>>> +        ret = 1;
>>> +        goto err;
>>> +    } else
>>> +        av_log(h, AV_LOG_DEBUG, "$IPFS_GATEWAY is empty.\n");
>>> +
>>> +    // We need to know the IPFS folder to - eventually - read the
>>> contents of
>>> +    // the "gateway" file which would tell us the gateway to use.
>>> +    if (getenv("IPFS_PATH") == NULL) {
>>> +        av_log(h, AV_LOG_DEBUG, "$IPFS_PATH is empty.\n");
>>> +
>>> +        // Try via the home folder.
>>> +        if (getenv("HOME") == NULL) {
>>> +            av_log(h, AV_LOG_ERROR, "$HOME appears to be empty.\n");
>>> +            ret = AVERROR(EINVAL);
>>> +            goto err;
>>> +        }
>>> +
>>> +        // Verify the composed path fits.
>>> +        if (snprintf(ipfs_full_data_folder,
>>> sizeof(ipfs_full_data_folder),
>>> +                     "%s/.ipfs/", getenv("HOME")) >=
>>> sizeof(ipfs_full_data_folder)) {
>>> +            av_log(h, AV_LOG_ERROR, "The IPFS data path exceeds the max
>>> path length (%zu)\n", sizeof(ipfs_full_data_folder));
>>> +            ret = AVERROR(EINVAL);
>>> +            goto err;
>>> +        }
>>> +
>>> +        // Stat the folder.
>>> +        // It should exist in a default IPFS setup when run as local
>>> user.
>>> +#ifndef _WIN32
>>> +        stat_ret = stat(ipfs_full_data_folder, &st);
>>> +#else
>>> +        stat_ret = win32_stat(ipfs_full_data_folder, &st);
>>> +#endif
>>> +        if (stat_ret < 0) {
>>> +            av_log(h, AV_LOG_INFO, "Unable to find IPFS folder. We
>>> tried:\n");
>>> +            av_log(h, AV_LOG_INFO, "- $IPFS_PATH, which was empty.\n");
>>> +            av_log(h, AV_LOG_INFO, "- $HOME/.ipfs (full uri: %s) which
>>> doesn't exist.\n", ipfs_full_data_folder);
>>> +            ret = AVERROR(ENOENT);
>>> +            goto err;
>>> +        }
>>> +    } else {
>>> +        if (snprintf(ipfs_full_data_folder,
>>> sizeof(ipfs_full_data_folder), "%s",
>>> +                 getenv("IPFS_PATH")) >= sizeof(ipfs_full_data_folder))
>>> {
>>> +            av_log(h, AV_LOG_ERROR, "The IPFS_PATH environment variable
>>> exceeds the maximum length. We allow a max of %zu characters\n",
>>> sizeof(c->gateway_buffer));
>>> +            ret = AVERROR(EINVAL);
>>> +            goto err;
>>> +        }
>>> +
>>> +    }
>>> +
>>> +    // Copy the fully composed gateway path into ipfs_gateway_file.
>>> +    if (snprintf(ipfs_gateway_file, sizeof(ipfs_gateway_file),
>>> "%sgateway",
>>> +                 ipfs_full_data_folder) >= sizeof(ipfs_gateway_file)) {
>>> +        av_log(h, AV_LOG_ERROR, "The IPFS gateway file path exceeds the
>>> max path length (%zu)\n", sizeof(ipfs_gateway_file));
>>> +        ret = AVERROR(ENOENT);
>>> +        goto err;
>>> +    }
>>> +
>>> +    // Get the contents of the gateway file.
>>> +    gateway_file = av_fopen_utf8(ipfs_gateway_file, "r");
>>> +    if (!gateway_file) {
>>> +        av_log(h, AV_LOG_ERROR, "The IPFS gateway file (full uri: %s)
>>> doesn't exist. Is the gateway enabled?\n", ipfs_gateway_file);
>>> +        ret = AVERROR(ENOENT);
>>> +        goto err;
>>> +    }
>>> +
>>> +    // Read a single line (fgets stops at new line mark).
>>> +    fgets(c->gateway_buffer, sizeof(c->gateway_buffer) - 1,
>>> gateway_file);
>>> +
>>> +    // Replace the last char with \0
>>> +    c->gateway_buffer[sizeof(c->gateway_buffer) - 1] = 0;
>>> +
>>> +    // Replace first occurence of end of line with \0
>>> +    c->gateway_buffer[strcspn(c->gateway_buffer, "\r")] = 0;
>>> +    c->gateway_buffer[strcspn(c->gateway_buffer, "\n")] = 0;
>>> +
>>> +    // If strlen finds anything longer then 0 characters then we have a
>>> +    // potential gateway url.
>>> +    if (strlen(c->gateway_buffer) < 1) {
>>> +        av_log(h, AV_LOG_ERROR, "The IPFS gateway file (full uri: %s)
>>> appears to be empty. Is the gateway started?\n", ipfs_gateway_file);
>>> +        ret = AVERROR(EILSEQ);
>>> +        goto err;
>>> +    } else {
>>> +        // We're done, the c->gateway_buffer has something that looks
>>> valid.
>>> +        ret = 1;
>>> +        goto err;
>>> +    }
>>> +
>>> +err:
>>> +    if (gateway_file)
>>> +        fclose(gateway_file);
>>> +
>>> +    return ret;
>>> +}
>>> +
>>> +static int translate_ipfs_to_http(URLContext *h, const char *uri,
>>> +                                  int flags, AVDictionary **options)
>>> +{
>>> +    const char *ipfs_cid;
>>> +    char *fulluri = NULL;
>>> +    int ret;
>>> +    IPFSGatewayContext *c = h->priv_data;
>>> +
>>> +    // Test for ipfs://, ipfs:, ipns:// and ipns:. This prefix is
>>> stripped from
>>> +    // the string leaving just the CID in ipfs_cid.
>>> +    int is_ipfs = av_stristart(uri, "ipfs://", &ipfs_cid);
>>> +    int is_ipns = av_stristart(uri, "ipns://", &ipfs_cid);
>>> +
>>> +    // We must have either ipns or ipfs.
>>> +    if (!is_ipfs && !is_ipns) {
>>> +        ret = AVERROR(EINVAL);
>>> +        av_log(h, AV_LOG_ERROR, "Unsupported url %s\n", uri);
>>> +        goto err;
>>> +    }
>>> +
>>> +    // If the CID has a length greater then 0 then we assume we have a
>>> proper working one.
>>> +    // It could still be wrong but in that case the gateway should save
>>> us and
>>> +    // ruturn a 403 error. The http protocol handles this.
>>> +    if (strlen(ipfs_cid) < 1) {
>>> +        av_log(h, AV_LOG_ERROR, "A CID must be provided.\n");
>>> +        ret = AVERROR(EILSEQ);
>>> +        goto err;
>>> +    }
>>> +
>>> +    // Populate c->gateway_buffer with whatever is in c->gateway
>>> +    if (c->gateway != NULL) {
>>> +        if (snprintf(c->gateway_buffer, sizeof(c->gateway_buffer), "%s",
>>> +                     c->gateway) >= sizeof(c->gateway_buffer)) {
>>> +            av_log(h, AV_LOG_ERROR, "The -gateway parameter is too
>>> long. We allow a max of %zu characters\n", sizeof(c->gateway_buffer));
>>> +            ret = AVERROR(EINVAL);
>>> +            goto err;
>>> +        }
>>> +    } else {
>>> +        // Populate the IPFS gateway if we have any.
>>> +        // If not, inform the user how to properly set one.
>>> +        ret = populate_ipfs_gateway(h);
>>> +
>>> +        if (ret < 1) {
>>> +            av_log(h, AV_LOG_ERROR, "No IPFS gateway was set. Make sure
>>> a local IPFS instance is running.\n");
>>> +            av_log(h, AV_LOG_INFO, "There are multiple options to
>>> define this gateway. The below options are in order of precedence:\n");
>>> +            av_log(h, AV_LOG_INFO, "1. Define a -gateway <url> to the
>>> gateway without trailing forward slash.\n");
>>> +            av_log(h, AV_LOG_INFO, "2. Define $IPFS_GATEWAY with the
>>> full http link to the gateway without trailing forward slash.\n");
>>> +            av_log(h, AV_LOG_INFO, "3. Define $IPFS_PATH and point it
>>> to the IPFS data path.\n");
>>> +            av_log(h, AV_LOG_INFO, "4. Have IPFS running in your local
>>> user folder (under $HOME/.ipfs).\n");
>>> +            av_log(h, AV_LOG_INFO, "In all path cases, a file named
>>> gateway is expected. See https://github.com/ipfs/specs/issues/261 for
>>> more information.\n");
>>> +            goto err;
>>> +        }
>>> +    }
>>> +
>>> +    // Test if the gateway starts with either http:// or https://
>>> +    if (av_stristart(c->gateway_buffer, "http://", NULL) == 0
>>> +        && av_stristart(c->gateway_buffer, "https://", NULL) == 0) {
>>> +        av_log(h, AV_LOG_ERROR, "The gateway URL didn't start with
>>> http:// or https:// and is therefore invalid.\n");
>>> +        ret = AVERROR(EILSEQ);
>>> +        goto err;
>>> +    }
>>> +
>>> +    // Concatenate the url.
>>> +    // This ends up with something like:
>>> http://localhost:8080/ipfs/Qm.....
>>> +    // The format of "%s%s%s%s" is the following:
>>> +    // 1st %s = The gateway.
>>> +    // 2nd %s = If the gateway didn't end in a slash, add a "/".
>>> Otherwise it's an empty string
>>> +    // 3rd %s = Either ipns/ or ipfs/.
>>> +    // 4th %s = The IPFS CID (Qm..., bafy..., ...).
>>> +    fulluri = av_asprintf("%s%s%s%s",
>>> +                          c->gateway_buffer,
>>> +                          (c->gateway_buffer[strlen(c->gateway_buffer)
>>> - 1] == '/') ? "" : "/",
>>> +                          (is_ipns) ? "ipns/" : "ipfs/",
>>> +                          ipfs_cid);
>>> +
>>> +    // Pass the URL back to FFMpeg's protocol handler.
>>> +    if ((ret = ffurl_open_whitelist(&c->inner, fulluri, flags,
>>> +                                    &h->interrupt_callback, options,
>>> +                                    h->protocol_whitelist,
>>> +                                    h->protocol_blacklist, h))
>>> +        < 0) {
>>> +        av_log(h, AV_LOG_ERROR, "Unable to open resource: %s\n",
>>> fulluri);
>>> +        goto err;
>>> +    }
>>> +
>>> +err:
>>> +    av_free(fulluri);
>>> +    return ret;
>>> +}
>>> +
>>> +static int ipfs_read(URLContext *h, unsigned char *buf, int size)
>>> +{
>>> +    IPFSGatewayContext *c = h->priv_data;
>>> +    return ffurl_read(c->inner, buf, size);
>>> +}
>>> +
>>> +static int64_t ipfs_seek(URLContext *h, int64_t pos, int whence)
>>> +{
>>> +    IPFSGatewayContext *c = h->priv_data;
>>> +    return ffurl_seek(c->inner, pos, whence);
>>> +}
>>> +
>>> +static int ipfs_close(URLContext *h)
>>> +{
>>> +    IPFSGatewayContext *c = h->priv_data;
>>> +    av_free(c->gateway);
>>> +    return ffurl_closep(&c->inner);
>>> +}
>>> +
>>> +#define OFFSET(x) offsetof(IPFSGatewayContext, x)
>>> +
>>> +static const AVOption options[] = {
>>> +    {"gateway", "The gateway to ask for IPFS data.", OFFSET(gateway),
>>> AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, AV_OPT_FLAG_DECODING_PARAM},
>>> +    {NULL},
>>> +};
>>> +
>>> +static const AVClass ipfs_context_class = {
>>> +    .class_name   = "IPFS",
>>> +    .item_name    = av_default_item_name,
>>> +    .option       = options,
>>> +    .version      = LIBAVUTIL_VERSION_INT,
>>> +};
>>> +
>>> +const URLProtocol ff_ipfs_protocol = {
>>> +    .name             = "ipfs",
>>> +    .url_open2        = translate_ipfs_to_http,
>>> +    .url_read         = ipfs_read,
>>> +    .url_seek         = ipfs_seek,
>>> +    .url_close        = ipfs_close,
>>> +    .priv_data_size   = sizeof(IPFSGatewayContext),
>>> +    .priv_data_class  = &ipfs_context_class,
>>> +};
>>> +
>>> +const URLProtocol ff_ipns_protocol = {
>>> +    .name             = "ipns",
>>> +    .url_open2        = translate_ipfs_to_http,
>>> +    .url_read         = ipfs_read,
>>> +    .url_seek         = ipfs_seek,
>>> +    .url_close        = ipfs_close,
>>> +    .priv_data_size   = sizeof(IPFSGatewayContext),
>>> +    .priv_data_class  = &ipfs_context_class,
>>> +};
>>> diff --git a/libavformat/protocols.c b/libavformat/protocols.c
>>> index 948fae411f..675b684bd3 100644
>>> --- a/libavformat/protocols.c
>>> +++ b/libavformat/protocols.c
>>> @@ -73,6 +73,8 @@ extern const URLProtocol ff_libsrt_protocol;
>>>  extern const URLProtocol ff_libssh_protocol;
>>>  extern const URLProtocol ff_libsmbclient_protocol;
>>>  extern const URLProtocol ff_libzmq_protocol;
>>> +extern const URLProtocol ff_ipfs_protocol;
>>> +extern const URLProtocol ff_ipns_protocol;
>>>
>>>  #include "libavformat/protocol_list.c"
>>>
>>> --
>>> 2.35.1
>>>
>>>
>> There we go, V8. Would this be the final one? I hope so! :)
>> Just in case this is the magic final version that is allowed to be merged.
>> How do I proceed in that case? I don't have merge powers...
>>
>> A small note on the log lines.
>> I do prefer to keep them on one line. This seems to be in line with other
>> ffmpeg code files (i only looked at a couple other protocols like crypto,
>> http and avio).
>> Splitting them looks - imho - quite ugly.
>>
>
> ping
>
Tomas Härdin Feb. 28, 2022, 1:09 p.m. UTC | #4
sön 2022-02-27 klockan 15:29 +0100 skrev Mark Gaiser:
> Ping 2....
> 
> I'd really like to get this merged!
> This kinda blocks me right now from proceeding with IPFS integration
> in
> Kodi, MPV and VLC. Implementations in those (who rely on ffmpeg) are
> significantly easier once this patch is finally landed in ffmpeg.

I'd like to hear at least one other dev chime in on this one

/Tomas
Michael Niedermayer March 1, 2022, 10:01 p.m. UTC | #5
On Mon, Feb 28, 2022 at 02:09:15PM +0100, Tomas Härdin wrote:
> sön 2022-02-27 klockan 15:29 +0100 skrev Mark Gaiser:
> > Ping 2....
> > 
> > I'd really like to get this merged!
> > This kinda blocks me right now from proceeding with IPFS integration
> > in
> > Kodi, MPV and VLC. Implementations in those (who rely on ffmpeg) are
> > significantly easier once this patch is finally landed in ffmpeg.
> 
> I'd like to hear at least one other dev chime in on this one

what exactly are you not sure about ? 
what exactly needs a 2nd look ?

thx

[...]
Mark Gaiser March 3, 2022, 2:58 p.m. UTC | #6
On Tue, Mar 1, 2022 at 11:01 PM Michael Niedermayer <michael@niedermayer.cc>
wrote:

> On Mon, Feb 28, 2022 at 02:09:15PM +0100, Tomas Härdin wrote:
> > sön 2022-02-27 klockan 15:29 +0100 skrev Mark Gaiser:
> > > Ping 2....
> > >
> > > I'd really like to get this merged!
> > > This kinda blocks me right now from proceeding with IPFS integration
> > > in
> > > Kodi, MPV and VLC. Implementations in those (who rely on ffmpeg) are
> > > significantly easier once this patch is finally landed in ffmpeg.
> >
> > I'd like to hear at least one other dev chime in on this one
>
> what exactly are you not sure about ?
> what exactly needs a 2nd look ?
>

My assumption.
In general just a second look by someone other than Tomas.
And, as he was skeptical about this patch at first, likely another opinion
if this makes sense to add in ffmpeg.
To me it does very much but i'm biased :)

In an effort to get this patch done, I'm adding the people to the CC that
have commented on the earlier versions (v0, 1 and 2) of this patch.

@Michael or Thomas, if no reply is here in the next 2 days, could this be
merged?
As I assume no reply after this much pinging and raising awareness means an
implicit OK.


> thx
>
> [...]
> --
> Michael     GnuPG fingerprint: 9FF2128B147EF6730BADF133611EC787040B0FAB
>
> The real ebay dictionary, page 1
> "Used only once"    - "Some unspecified defect prevented a second use"
> "In good condition" - "Can be repaird by experienced expert"
> "As is" - "You wouldnt want it even if you were payed for it, if you knew
> ..."
> _______________________________________________
> 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".
>
Michael Niedermayer March 4, 2022, 6:09 p.m. UTC | #7
On Thu, Mar 03, 2022 at 03:58:53PM +0100, Mark Gaiser wrote:
> On Tue, Mar 1, 2022 at 11:01 PM Michael Niedermayer <michael@niedermayer.cc>
> wrote:
> 
> > On Mon, Feb 28, 2022 at 02:09:15PM +0100, Tomas Härdin wrote:
> > > sön 2022-02-27 klockan 15:29 +0100 skrev Mark Gaiser:
> > > > Ping 2....
> > > >
> > > > I'd really like to get this merged!
> > > > This kinda blocks me right now from proceeding with IPFS integration
> > > > in
> > > > Kodi, MPV and VLC. Implementations in those (who rely on ffmpeg) are
> > > > significantly easier once this patch is finally landed in ffmpeg.
> > >
> > > I'd like to hear at least one other dev chime in on this one
> >
> > what exactly are you not sure about ?
> > what exactly needs a 2nd look ?
> >
> 
> My assumption.
> In general just a second look by someone other than Tomas.
> And, as he was skeptical about this patch at first, likely another opinion
> if this makes sense to add in ffmpeg.
> To me it does very much but i'm biased :)

ipfs support makes sense to be added to ffmpeg. ive seen ipfs urls and ive
already been annoyed that some tools dont "just" work with them.
While if i compare this to many other formats which i have never seen
outside the context of FFmpeg. So from this biased single sample that i
am, ipfs seems more widespread and thats why iam in favor of its support

thx

[...]
Mark Gaiser March 8, 2022, 12:49 p.m. UTC | #8
On Fri, Mar 4, 2022 at 7:09 PM Michael Niedermayer <michael@niedermayer.cc>
wrote:

> On Thu, Mar 03, 2022 at 03:58:53PM +0100, Mark Gaiser wrote:
> > On Tue, Mar 1, 2022 at 11:01 PM Michael Niedermayer <
> michael@niedermayer.cc>
> > wrote:
> >
> > > On Mon, Feb 28, 2022 at 02:09:15PM +0100, Tomas Härdin wrote:
> > > > sön 2022-02-27 klockan 15:29 +0100 skrev Mark Gaiser:
> > > > > Ping 2....
> > > > >
> > > > > I'd really like to get this merged!
> > > > > This kinda blocks me right now from proceeding with IPFS
> integration
> > > > > in
> > > > > Kodi, MPV and VLC. Implementations in those (who rely on ffmpeg)
> are
> > > > > significantly easier once this patch is finally landed in ffmpeg.
> > > >
> > > > I'd like to hear at least one other dev chime in on this one
> > >
> > > what exactly are you not sure about ?
> > > what exactly needs a 2nd look ?
> > >
> >
> > My assumption.
> > In general just a second look by someone other than Tomas.
> > And, as he was skeptical about this patch at first, likely another
> opinion
> > if this makes sense to add in ffmpeg.
> > To me it does very much but i'm biased :)
>
> ipfs support makes sense to be added to ffmpeg. ive seen ipfs urls and ive
> already been annoyed that some tools dont "just" work with them.
> While if i compare this to many other formats which i have never seen
> outside the context of FFmpeg. So from this biased single sample that i
> am, ipfs seems more widespread and thats why iam in favor of its support
>
> thx
>
> Great to have your support :)
Reading that is quite motivating to work on it, no joke!

Just to be clear here. Having this in ffmpeg won't make it "just work" yet.
For a minimal feeling of "hey, it works out of the box" you'd need:
- The next or version after the next IPFS.
- MPV support which relies on this patch to even be supported in mpv
- Have a node running locally

Once you have those then a "mpv ipfs://<cid>" works without any additional
configuration.
We're getting there and this is one (admittedly very significant) step in
that direction.

Lastly. merge time?
Could you do the honors? As I don't have the required permissions.



> [...]
> --
> Michael     GnuPG fingerprint: 9FF2128B147EF6730BADF133611EC787040B0FAB
>
> Everything should be made as simple as possible, but not simpler.
> -- Albert Einstein
> _______________________________________________
> 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".
>
Michael Niedermayer March 8, 2022, 11:45 p.m. UTC | #9
On Tue, Mar 08, 2022 at 01:49:22PM +0100, Mark Gaiser wrote:
> On Fri, Mar 4, 2022 at 7:09 PM Michael Niedermayer <michael@niedermayer.cc>
> wrote:
> 
> > On Thu, Mar 03, 2022 at 03:58:53PM +0100, Mark Gaiser wrote:
> > > On Tue, Mar 1, 2022 at 11:01 PM Michael Niedermayer <
> > michael@niedermayer.cc>
> > > wrote:
> > >
> > > > On Mon, Feb 28, 2022 at 02:09:15PM +0100, Tomas Härdin wrote:
> > > > > sön 2022-02-27 klockan 15:29 +0100 skrev Mark Gaiser:
> > > > > > Ping 2....
> > > > > >
> > > > > > I'd really like to get this merged!
> > > > > > This kinda blocks me right now from proceeding with IPFS
> > integration
> > > > > > in
> > > > > > Kodi, MPV and VLC. Implementations in those (who rely on ffmpeg)
> > are
> > > > > > significantly easier once this patch is finally landed in ffmpeg.
> > > > >
> > > > > I'd like to hear at least one other dev chime in on this one
> > > >
> > > > what exactly are you not sure about ?
> > > > what exactly needs a 2nd look ?
> > > >
> > >
> > > My assumption.
> > > In general just a second look by someone other than Tomas.
> > > And, as he was skeptical about this patch at first, likely another
> > opinion
> > > if this makes sense to add in ffmpeg.
> > > To me it does very much but i'm biased :)
> >
> > ipfs support makes sense to be added to ffmpeg. ive seen ipfs urls and ive
> > already been annoyed that some tools dont "just" work with them.
> > While if i compare this to many other formats which i have never seen
> > outside the context of FFmpeg. So from this biased single sample that i
> > am, ipfs seems more widespread and thats why iam in favor of its support
> >
> > thx
> >
> > Great to have your support :)
> Reading that is quite motivating to work on it, no joke!
> 
> Just to be clear here. Having this in ffmpeg won't make it "just work" yet.
> For a minimal feeling of "hey, it works out of the box" you'd need:
> - The next or version after the next IPFS.
> - MPV support which relies on this patch to even be supported in mpv
> - Have a node running locally

if theres no local node it should fallback to a public node
ATM
IPFS_GATEWAY=https://dweb.link ./ffplay ipfs://...
works
so such a fallback is all thats needed for it to just work

thx

[...]
Mark Gaiser March 9, 2022, 12:30 a.m. UTC | #10
On Wed, Mar 9, 2022 at 12:45 AM Michael Niedermayer <michael@niedermayer.cc>
wrote:

> On Tue, Mar 08, 2022 at 01:49:22PM +0100, Mark Gaiser wrote:
> > On Fri, Mar 4, 2022 at 7:09 PM Michael Niedermayer <
> michael@niedermayer.cc>
> > wrote:
> >
> > > On Thu, Mar 03, 2022 at 03:58:53PM +0100, Mark Gaiser wrote:
> > > > On Tue, Mar 1, 2022 at 11:01 PM Michael Niedermayer <
> > > michael@niedermayer.cc>
> > > > wrote:
> > > >
> > > > > On Mon, Feb 28, 2022 at 02:09:15PM +0100, Tomas Härdin wrote:
> > > > > > sön 2022-02-27 klockan 15:29 +0100 skrev Mark Gaiser:
> > > > > > > Ping 2....
> > > > > > >
> > > > > > > I'd really like to get this merged!
> > > > > > > This kinda blocks me right now from proceeding with IPFS
> > > integration
> > > > > > > in
> > > > > > > Kodi, MPV and VLC. Implementations in those (who rely on
> ffmpeg)
> > > are
> > > > > > > significantly easier once this patch is finally landed in
> ffmpeg.
> > > > > >
> > > > > > I'd like to hear at least one other dev chime in on this one
> > > > >
> > > > > what exactly are you not sure about ?
> > > > > what exactly needs a 2nd look ?
> > > > >
> > > >
> > > > My assumption.
> > > > In general just a second look by someone other than Tomas.
> > > > And, as he was skeptical about this patch at first, likely another
> > > opinion
> > > > if this makes sense to add in ffmpeg.
> > > > To me it does very much but i'm biased :)
> > >
> > > ipfs support makes sense to be added to ffmpeg. ive seen ipfs urls and
> ive
> > > already been annoyed that some tools dont "just" work with them.
> > > While if i compare this to many other formats which i have never seen
> > > outside the context of FFmpeg. So from this biased single sample that i
> > > am, ipfs seems more widespread and thats why iam in favor of its
> support
> > >
> > > thx
> > >
> > > Great to have your support :)
> > Reading that is quite motivating to work on it, no joke!
> >
> > Just to be clear here. Having this in ffmpeg won't make it "just work"
> yet.
> > For a minimal feeling of "hey, it works out of the box" you'd need:
> > - The next or version after the next IPFS.
> > - MPV support which relies on this patch to even be supported in mpv
> > - Have a node running locally
>
> if theres no local node it should fallback to a public node
> ATM
> IPFS_GATEWAY=https://dweb.link ./ffplay ipfs://...
> works
> so such a fallback is all thats needed for it to just work
>

Yes, the beauty of gateways.

Are you suggesting that I update the patch to add this default?
I would prefer not to add that even though it would give a feeling of "just
works".
I'm mostly concerned about the bandwidth usage it could cause on that site.
But also about potential hacks. If this is a default and well used then it
becomes quite appealing for hackers to take control of dweb.link and send
back data that wasn't requested.

If you insist this would be really better to add then I'll need to go find
the ones managing that site (paying for it) to ask permission if this would
be allowed.


> thx
>
> [...]
> --
> Michael     GnuPG fingerprint: 9FF2128B147EF6730BADF133611EC787040B0FAB
>
> If the United States is serious about tackling the national security
> threats
> related to an insecure 5G network, it needs to rethink the extent to which
> it
> values corporate profits and government espionage over security.-Bruce
> Schneier
> _______________________________________________
> 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".
>
Michael Niedermayer March 9, 2022, 9:35 a.m. UTC | #11
On Wed, Mar 09, 2022 at 01:30:30AM +0100, Mark Gaiser wrote:
> On Wed, Mar 9, 2022 at 12:45 AM Michael Niedermayer <michael@niedermayer.cc>
> wrote:
> 
> > On Tue, Mar 08, 2022 at 01:49:22PM +0100, Mark Gaiser wrote:
> > > On Fri, Mar 4, 2022 at 7:09 PM Michael Niedermayer <
> > michael@niedermayer.cc>
> > > wrote:
> > >
> > > > On Thu, Mar 03, 2022 at 03:58:53PM +0100, Mark Gaiser wrote:
> > > > > On Tue, Mar 1, 2022 at 11:01 PM Michael Niedermayer <
> > > > michael@niedermayer.cc>
> > > > > wrote:
> > > > >
> > > > > > On Mon, Feb 28, 2022 at 02:09:15PM +0100, Tomas Härdin wrote:
> > > > > > > sön 2022-02-27 klockan 15:29 +0100 skrev Mark Gaiser:
> > > > > > > > Ping 2....
> > > > > > > >
> > > > > > > > I'd really like to get this merged!
> > > > > > > > This kinda blocks me right now from proceeding with IPFS
> > > > integration
> > > > > > > > in
> > > > > > > > Kodi, MPV and VLC. Implementations in those (who rely on
> > ffmpeg)
> > > > are
> > > > > > > > significantly easier once this patch is finally landed in
> > ffmpeg.
> > > > > > >
> > > > > > > I'd like to hear at least one other dev chime in on this one
> > > > > >
> > > > > > what exactly are you not sure about ?
> > > > > > what exactly needs a 2nd look ?
> > > > > >
> > > > >
> > > > > My assumption.
> > > > > In general just a second look by someone other than Tomas.
> > > > > And, as he was skeptical about this patch at first, likely another
> > > > opinion
> > > > > if this makes sense to add in ffmpeg.
> > > > > To me it does very much but i'm biased :)
> > > >
> > > > ipfs support makes sense to be added to ffmpeg. ive seen ipfs urls and
> > ive
> > > > already been annoyed that some tools dont "just" work with them.
> > > > While if i compare this to many other formats which i have never seen
> > > > outside the context of FFmpeg. So from this biased single sample that i
> > > > am, ipfs seems more widespread and thats why iam in favor of its
> > support
> > > >
> > > > thx
> > > >
> > > > Great to have your support :)
> > > Reading that is quite motivating to work on it, no joke!
> > >
> > > Just to be clear here. Having this in ffmpeg won't make it "just work"
> > yet.
> > > For a minimal feeling of "hey, it works out of the box" you'd need:
> > > - The next or version after the next IPFS.
> > > - MPV support which relies on this patch to even be supported in mpv
> > > - Have a node running locally
> >
> > if theres no local node it should fallback to a public node
> > ATM
> > IPFS_GATEWAY=https://dweb.link ./ffplay ipfs://...
> > works
> > so such a fallback is all thats needed for it to just work
> >
> 
> Yes, the beauty of gateways.
> 
> Are you suggesting that I update the patch to add this default?

Iam not sure


> I would prefer not to add that even though it would give a feeling of "just
> works".

> I'm mostly concerned about the bandwidth usage it could cause on that site.

you could add more than one
you could point people to https://ipfs.io/ipns/ipnso.com/ to let them select
their own or maybe theres a better way


> But also about potential hacks. If this is a default and well used then it
> becomes quite appealing for hackers to take control of dweb.link and send
> back data that wasn't requested.

Thats a valid concern but not adding a default is not really solving this
Because what do most people do then ?
They google for a gateway and pick the first that works. 
Thats plausibly not going to give them the fastest nor the most secure nor 
even not the same as the last million people searching.

To setup a ipfs node on ones own machiene, first it would be needed that this
is VERY simple and clean
if i run "apt search ipfs", theres none, so that already fails here
but for the fun, i tried to search for ipfs node on the app store on my iphone
no, also nothing.

so i dont think "install an ipfs node" is really a viable solution for the
average joe.

we have a wide range of platforms, linux, windows, android, ios just to name
the major ones. This protocol should work on all of them.
I think a default gateway is the easy way to make that happen, asking the
user to set a gateway will already leave android and iphone users probably
wondering how to do that. Is there a way ?

So really iam not saying "add a default", iam really saying "make it work
for everyone", its ok if the user has to choose one or set some default but
it really has to work on all platforms and with all user apps using
libavformat. It should not be specific to mpv or windows/linux

you can also print a big nasty warning that a default is used and the user
should really setup their own node and why that is better.

thx

> 
> If you insist this would be really better to add then I'll need to go find
> the ones managing that site (paying for it) to ask permission if this would
> be allowed.

[...]
Mark Gaiser March 11, 2022, 1:45 p.m. UTC | #12
On Wed, Mar 9, 2022 at 10:36 AM Michael Niedermayer <michael@niedermayer.cc>
wrote:

> On Wed, Mar 09, 2022 at 01:30:30AM +0100, Mark Gaiser wrote:
> > On Wed, Mar 9, 2022 at 12:45 AM Michael Niedermayer <
> michael@niedermayer.cc>
> > wrote:
> >
> > > On Tue, Mar 08, 2022 at 01:49:22PM +0100, Mark Gaiser wrote:
> > > > On Fri, Mar 4, 2022 at 7:09 PM Michael Niedermayer <
> > > michael@niedermayer.cc>
> > > > wrote:
> > > >
> > > > > On Thu, Mar 03, 2022 at 03:58:53PM +0100, Mark Gaiser wrote:
> > > > > > On Tue, Mar 1, 2022 at 11:01 PM Michael Niedermayer <
> > > > > michael@niedermayer.cc>
> > > > > > wrote:
> > > > > >
> > > > > > > On Mon, Feb 28, 2022 at 02:09:15PM +0100, Tomas Härdin wrote:
> > > > > > > > sön 2022-02-27 klockan 15:29 +0100 skrev Mark Gaiser:
> > > > > > > > > Ping 2....
> > > > > > > > >
> > > > > > > > > I'd really like to get this merged!
> > > > > > > > > This kinda blocks me right now from proceeding with IPFS
> > > > > integration
> > > > > > > > > in
> > > > > > > > > Kodi, MPV and VLC. Implementations in those (who rely on
> > > ffmpeg)
> > > > > are
> > > > > > > > > significantly easier once this patch is finally landed in
> > > ffmpeg.
> > > > > > > >
> > > > > > > > I'd like to hear at least one other dev chime in on this one
> > > > > > >
> > > > > > > what exactly are you not sure about ?
> > > > > > > what exactly needs a 2nd look ?
> > > > > > >
> > > > > >
> > > > > > My assumption.
> > > > > > In general just a second look by someone other than Tomas.
> > > > > > And, as he was skeptical about this patch at first, likely
> another
> > > > > opinion
> > > > > > if this makes sense to add in ffmpeg.
> > > > > > To me it does very much but i'm biased :)
> > > > >
> > > > > ipfs support makes sense to be added to ffmpeg. ive seen ipfs urls
> and
> > > ive
> > > > > already been annoyed that some tools dont "just" work with them.
> > > > > While if i compare this to many other formats which i have never
> seen
> > > > > outside the context of FFmpeg. So from this biased single sample
> that i
> > > > > am, ipfs seems more widespread and thats why iam in favor of its
> > > support
> > > > >
> > > > > thx
> > > > >
> > > > > Great to have your support :)
> > > > Reading that is quite motivating to work on it, no joke!
> > > >
> > > > Just to be clear here. Having this in ffmpeg won't make it "just
> work"
> > > yet.
> > > > For a minimal feeling of "hey, it works out of the box" you'd need:
> > > > - The next or version after the next IPFS.
> > > > - MPV support which relies on this patch to even be supported in mpv
> > > > - Have a node running locally
> > >
> > > if theres no local node it should fallback to a public node
> > > ATM
> > > IPFS_GATEWAY=https://dweb.link ./ffplay ipfs://...
> > > works
> > > so such a fallback is all thats needed for it to just work
> > >
> >
> > Yes, the beauty of gateways.
> >
> > Are you suggesting that I update the patch to add this default?
>
> Iam not sure
>
>
> > I would prefer not to add that even though it would give a feeling of
> "just
> > works".
>
> > I'm mostly concerned about the bandwidth usage it could cause on that
> site.
>
> you could add more than one
> you could point people to https://ipfs.io/ipns/ipnso.com/ to let them
> select
> their own or maybe theres a better way
>
>
> > But also about potential hacks. If this is a default and well used then
> it
> > becomes quite appealing for hackers to take control of dweb.link and send
> > back data that wasn't requested.
>
> Thats a valid concern but not adding a default is not really solving this
> Because what do most people do then ?
> They google for a gateway and pick the first that works.
> Thats plausibly not going to give them the fastest nor the most secure nor
> even not the same as the last million people searching.
>
> To setup a ipfs node on ones own machiene, first it would be needed that
> this
> is VERY simple and clean
> if i run "apt search ipfs", theres none, so that already fails here
> but for the fun, i tried to search for ipfs node on the app store on my
> iphone
> no, also nothing.
>
> so i dont think "install an ipfs node" is really a viable solution for the
> average joe.
>
> we have a wide range of platforms, linux, windows, android, ios just to
> name
> the major ones. This protocol should work on all of them.
> I think a default gateway is the easy way to make that happen, asking the
> user to set a gateway will already leave android and iphone users probably
> wondering how to do that. Is there a way ?
>
> So really iam not saying "add a default", iam really saying "make it work
> for everyone", its ok if the user has to choose one or set some default but
> it really has to work on all platforms and with all user apps using
> libavformat. It should not be specific to mpv or windows/linux
>
> you can also print a big nasty warning that a default is used and the user
> should really setup their own node and why that is better.
>

Sorry for the belayed response but i had to discuss this with someone from
Protocol Labs (they manage IPFS).

I'd like to propose the following:

1. First and foremost, the gateway detection stuff is used. If all of that
still evaluates to no gateway then a fallback on dweb.link will be used. I
have asked them for their approval. This would be the most ideal gateway as
it is managed by the same company as IPFS (Protocol Labs). Approval on
their part is already a near certain yes but i'm still waiting on an
explicit OK from their end with the knowledge that this might become a
bandwidth beast once i complete specifically the KODI side of ipfs handling
(which would use this IPFS path).
2. There won't be any other fallback gateway
3. The above mentioned fallback gateway will be hardcoded. So no extra
command argument to change it. Changing it later would be a patch to ffmpeg.
4. When the fallback gateway is used, print a warning that a user should
install a local gateway instead. Something along those lines. Just to
motivate the use of local gateways.

On the bright, and that's awesome, it will mean that ipfs url's will just
work!
That includes mobile platforms too! Which is a really big win imho because
mobile/embedded might not be able to change ffmpeg settings/flags at all.
Also for mpv, vlc and kodi (my next projects after it lands here) it will
give an impression of "ipfs urls work out of the box"

The downside is the reliance on dweb.link.
Therefore i'd like to ask your explicit permission that a reliance on -
effectively another service - is OK!

I'll send a mail with a new patch once your approval and that of protocol
labs are known to me.


>
> thx
>
> >
> > If you insist this would be really better to add then I'll need to go
> find
> > the ones managing that site (paying for it) to ask permission if this
> would
> > be allowed.
>
> [...]
> --
> Michael     GnuPG fingerprint: 9FF2128B147EF6730BADF133611EC787040B0FAB
>
> Complexity theory is the science of finding the exact solution to an
> approximation. Benchmarking OTOH is finding an approximation of the exact
> _______________________________________________
> 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".
>
Michael Niedermayer March 12, 2022, 3:14 p.m. UTC | #13
On Fri, Mar 11, 2022 at 02:45:17PM +0100, Mark Gaiser wrote:
> On Wed, Mar 9, 2022 at 10:36 AM Michael Niedermayer <michael@niedermayer.cc>
> wrote:
> 
> > On Wed, Mar 09, 2022 at 01:30:30AM +0100, Mark Gaiser wrote:
> > > On Wed, Mar 9, 2022 at 12:45 AM Michael Niedermayer <
> > michael@niedermayer.cc>
> > > wrote:
> > >
> > > > On Tue, Mar 08, 2022 at 01:49:22PM +0100, Mark Gaiser wrote:
> > > > > On Fri, Mar 4, 2022 at 7:09 PM Michael Niedermayer <
> > > > michael@niedermayer.cc>
> > > > > wrote:
> > > > >
> > > > > > On Thu, Mar 03, 2022 at 03:58:53PM +0100, Mark Gaiser wrote:
> > > > > > > On Tue, Mar 1, 2022 at 11:01 PM Michael Niedermayer <
> > > > > > michael@niedermayer.cc>
> > > > > > > wrote:
> > > > > > >
> > > > > > > > On Mon, Feb 28, 2022 at 02:09:15PM +0100, Tomas Härdin wrote:
> > > > > > > > > sön 2022-02-27 klockan 15:29 +0100 skrev Mark Gaiser:
> > > > > > > > > > Ping 2....
> > > > > > > > > >
> > > > > > > > > > I'd really like to get this merged!
> > > > > > > > > > This kinda blocks me right now from proceeding with IPFS
> > > > > > integration
> > > > > > > > > > in
> > > > > > > > > > Kodi, MPV and VLC. Implementations in those (who rely on
> > > > ffmpeg)
> > > > > > are
> > > > > > > > > > significantly easier once this patch is finally landed in
> > > > ffmpeg.
> > > > > > > > >
> > > > > > > > > I'd like to hear at least one other dev chime in on this one
> > > > > > > >
> > > > > > > > what exactly are you not sure about ?
> > > > > > > > what exactly needs a 2nd look ?
> > > > > > > >
> > > > > > >
> > > > > > > My assumption.
> > > > > > > In general just a second look by someone other than Tomas.
> > > > > > > And, as he was skeptical about this patch at first, likely
> > another
> > > > > > opinion
> > > > > > > if this makes sense to add in ffmpeg.
> > > > > > > To me it does very much but i'm biased :)
> > > > > >
> > > > > > ipfs support makes sense to be added to ffmpeg. ive seen ipfs urls
> > and
> > > > ive
> > > > > > already been annoyed that some tools dont "just" work with them.
> > > > > > While if i compare this to many other formats which i have never
> > seen
> > > > > > outside the context of FFmpeg. So from this biased single sample
> > that i
> > > > > > am, ipfs seems more widespread and thats why iam in favor of its
> > > > support
> > > > > >
> > > > > > thx
> > > > > >
> > > > > > Great to have your support :)
> > > > > Reading that is quite motivating to work on it, no joke!
> > > > >
> > > > > Just to be clear here. Having this in ffmpeg won't make it "just
> > work"
> > > > yet.
> > > > > For a minimal feeling of "hey, it works out of the box" you'd need:
> > > > > - The next or version after the next IPFS.
> > > > > - MPV support which relies on this patch to even be supported in mpv
> > > > > - Have a node running locally
> > > >
> > > > if theres no local node it should fallback to a public node
> > > > ATM
> > > > IPFS_GATEWAY=https://dweb.link ./ffplay ipfs://...
> > > > works
> > > > so such a fallback is all thats needed for it to just work
> > > >
> > >
> > > Yes, the beauty of gateways.
> > >
> > > Are you suggesting that I update the patch to add this default?
> >
> > Iam not sure
> >
> >
> > > I would prefer not to add that even though it would give a feeling of
> > "just
> > > works".
> >
> > > I'm mostly concerned about the bandwidth usage it could cause on that
> > site.
> >
> > you could add more than one
> > you could point people to https://ipfs.io/ipns/ipnso.com/ to let them
> > select
> > their own or maybe theres a better way
> >
> >
> > > But also about potential hacks. If this is a default and well used then
> > it
> > > becomes quite appealing for hackers to take control of dweb.link and send
> > > back data that wasn't requested.
> >
> > Thats a valid concern but not adding a default is not really solving this
> > Because what do most people do then ?
> > They google for a gateway and pick the first that works.
> > Thats plausibly not going to give them the fastest nor the most secure nor
> > even not the same as the last million people searching.
> >
> > To setup a ipfs node on ones own machiene, first it would be needed that
> > this
> > is VERY simple and clean
> > if i run "apt search ipfs", theres none, so that already fails here
> > but for the fun, i tried to search for ipfs node on the app store on my
> > iphone
> > no, also nothing.
> >
> > so i dont think "install an ipfs node" is really a viable solution for the
> > average joe.
> >
> > we have a wide range of platforms, linux, windows, android, ios just to
> > name
> > the major ones. This protocol should work on all of them.
> > I think a default gateway is the easy way to make that happen, asking the
> > user to set a gateway will already leave android and iphone users probably
> > wondering how to do that. Is there a way ?
> >
> > So really iam not saying "add a default", iam really saying "make it work
> > for everyone", its ok if the user has to choose one or set some default but
> > it really has to work on all platforms and with all user apps using
> > libavformat. It should not be specific to mpv or windows/linux
> >
> > you can also print a big nasty warning that a default is used and the user
> > should really setup their own node and why that is better.
> >
> 
> Sorry for the belayed response but i had to discuss this with someone from
> Protocol Labs (they manage IPFS).
> 
> I'd like to propose the following:
> 
> 1. First and foremost, the gateway detection stuff is used. If all of that
> still evaluates to no gateway then a fallback on dweb.link will be used. I
> have asked them for their approval. This would be the most ideal gateway as
> it is managed by the same company as IPFS (Protocol Labs). Approval on
> their part is already a near certain yes but i'm still waiting on an
> explicit OK from their end with the knowledge that this might become a
> bandwidth beast once i complete specifically the KODI side of ipfs handling
> (which would use this IPFS path).
> 2. There won't be any other fallback gateway
> 3. The above mentioned fallback gateway will be hardcoded. So no extra
> command argument to change it. Changing it later would be a patch to ffmpeg.
> 4. When the fallback gateway is used, print a warning that a user should
> install a local gateway instead. Something along those lines. Just to
> motivate the use of local gateways.

ok


> 
> On the bright, and that's awesome, it will mean that ipfs url's will just
> work!
> That includes mobile platforms too! Which is a really big win imho because
> mobile/embedded might not be able to change ffmpeg settings/flags at all.
> Also for mpv, vlc and kodi (my next projects after it lands here) it will
> give an impression of "ipfs urls work out of the box"
> 
> The downside is the reliance on dweb.link.
> Therefore i'd like to ask your explicit permission that a reliance on -
> effectively another service - is OK!

ok with me


> 
> I'll send a mail with a new patch once your approval and that of protocol
> labs are known to me.

[...]
diff mbox series

Patch

diff --git a/configure b/configure
index 5b19a35f59..6ff09e7974 100755
--- a/configure
+++ b/configure
@@ -3585,6 +3585,8 @@  udp_protocol_select="network"
 udplite_protocol_select="network"
 unix_protocol_deps="sys_un_h"
 unix_protocol_select="network"
+ipfs_protocol_select="https_protocol"
+ipns_protocol_select="https_protocol"
 
 # external library protocols
 libamqp_protocol_deps="librabbitmq"
diff --git a/doc/protocols.texi b/doc/protocols.texi
index d207df0b52..7c9c0a4808 100644
--- a/doc/protocols.texi
+++ b/doc/protocols.texi
@@ -2025,5 +2025,35 @@  decoding errors.
 
 @end table
 
+@section ipfs
+
+InterPlanetary File System (IPFS) protocol support. One can access files stored 
+on the IPFS network through so called gateways. Those are http(s) endpoints.
+This protocol wraps the IPFS native protocols (ipfs:// and ipns://) to be send 
+to such a gateway. Users can (and should) host their own node which means this 
+protocol will use your local machine gateway to access files on the IPFS network.
+
+If a user doesn't have a node of their own then the public gateway dweb.link is 
+used by default.
+
+You can use this protocol in 2 ways. Using IPFS:
+@example
+ffplay ipfs://QmbGtJg23skhvFmu9mJiePVByhfzu5rwo74MEkVDYAmF5T
+@end example
+
+Or the IPNS protocol (IPNS is mutable IPFS):
+@example
+ffplay ipns://QmbGtJg23skhvFmu9mJiePVByhfzu5rwo74MEkVDYAmF5T
+@end example
+
+You can also change the gateway to be used:
+
+@table @option
+
+@item gateway
+Defines the gateway to use. When nothing is provided the protocol will first try 
+your local gateway. If that fails dweb.link will be used.
+
+@end table
 
 @c man end PROTOCOLS
diff --git a/libavformat/Makefile b/libavformat/Makefile
index 3dc6a479cc..4edce8420f 100644
--- a/libavformat/Makefile
+++ b/libavformat/Makefile
@@ -656,6 +656,8 @@  OBJS-$(CONFIG_SRTP_PROTOCOL)             += srtpproto.o srtp.o
 OBJS-$(CONFIG_SUBFILE_PROTOCOL)          += subfile.o
 OBJS-$(CONFIG_TEE_PROTOCOL)              += teeproto.o tee_common.o
 OBJS-$(CONFIG_TCP_PROTOCOL)              += tcp.o
+OBJS-$(CONFIG_IPFS_PROTOCOL)             += ipfsgateway.o
+OBJS-$(CONFIG_IPNS_PROTOCOL)             += ipfsgateway.o
 TLS-OBJS-$(CONFIG_GNUTLS)                += tls_gnutls.o
 TLS-OBJS-$(CONFIG_LIBTLS)                += tls_libtls.o
 TLS-OBJS-$(CONFIG_MBEDTLS)               += tls_mbedtls.o
diff --git a/libavformat/ipfsgateway.c b/libavformat/ipfsgateway.c
new file mode 100644
index 0000000000..7dfa56871d
--- /dev/null
+++ b/libavformat/ipfsgateway.c
@@ -0,0 +1,309 @@ 
+/*
+ * IPFS and IPNS protocol support through IPFS Gateway.
+ * Copyright (c) 2022 Mark Gaiser
+ *
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "avformat.h"
+#include "libavutil/avassert.h"
+#include "libavutil/avstring.h"
+#include "libavutil/internal.h"
+#include "libavutil/opt.h"
+#include "libavutil/tree.h"
+#include <fcntl.h>
+#if HAVE_IO_H
+#include <io.h>
+#endif
+#if HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#include "os_support.h"
+#include "url.h"
+#include <stdlib.h>
+#include <sys/stat.h>
+
+typedef struct IPFSGatewayContext {
+    AVClass *class;
+    URLContext *inner;
+    // Is filled by the -gateway argument and not changed after.
+    char *gateway;
+    // If the above gateway is non null, it will be copied into this buffer.
+    // Else this buffer will contain the auto detected gateway.
+    // In either case, the gateway to use will be in this buffer.
+    char gateway_buffer[PATH_MAX];
+} IPFSGatewayContext;
+
+// A best-effort way to find the IPFS gateway.
+// Only the most appropiate gateway is set. It's not actually requested
+// (http call) to prevent a potential slowdown in startup. A potential timeout
+// is handled by the HTTP protocol.
+static int populate_ipfs_gateway(URLContext *h)
+{
+    IPFSGatewayContext *c = h->priv_data;
+    char ipfs_full_data_folder[PATH_MAX];
+    char ipfs_gateway_file[PATH_MAX];
+    struct stat st;
+    int stat_ret = 0;
+    int ret = AVERROR(EINVAL);
+    FILE *gateway_file = NULL;
+
+    // Test $IPFS_GATEWAY.
+    if (getenv("IPFS_GATEWAY") != NULL) {
+        if (snprintf(c->gateway_buffer, sizeof(c->gateway_buffer), "%s",
+                     getenv("IPFS_GATEWAY")) >= sizeof(c->gateway_buffer)) {
+            av_log(h, AV_LOG_ERROR, "The IPFS_GATEWAY environment variable exceeds the maximum length. We allow a max of %zu characters\n", sizeof(c->gateway_buffer));
+            ret = AVERROR(EINVAL);
+            goto err;
+        }
+
+        ret = 1;
+        goto err;
+    } else
+        av_log(h, AV_LOG_DEBUG, "$IPFS_GATEWAY is empty.\n");
+
+    // We need to know the IPFS folder to - eventually - read the contents of
+    // the "gateway" file which would tell us the gateway to use.
+    if (getenv("IPFS_PATH") == NULL) {
+        av_log(h, AV_LOG_DEBUG, "$IPFS_PATH is empty.\n");
+
+        // Try via the home folder.
+        if (getenv("HOME") == NULL) {
+            av_log(h, AV_LOG_ERROR, "$HOME appears to be empty.\n");
+            ret = AVERROR(EINVAL);
+            goto err;
+        }
+
+        // Verify the composed path fits.
+        if (snprintf(ipfs_full_data_folder, sizeof(ipfs_full_data_folder),
+                     "%s/.ipfs/", getenv("HOME")) >= sizeof(ipfs_full_data_folder)) {
+            av_log(h, AV_LOG_ERROR, "The IPFS data path exceeds the max path length (%zu)\n", sizeof(ipfs_full_data_folder));
+            ret = AVERROR(EINVAL);
+            goto err;
+        }
+
+        // Stat the folder.
+        // It should exist in a default IPFS setup when run as local user.
+#ifndef _WIN32
+        stat_ret = stat(ipfs_full_data_folder, &st);
+#else
+        stat_ret = win32_stat(ipfs_full_data_folder, &st);
+#endif
+        if (stat_ret < 0) {
+            av_log(h, AV_LOG_INFO, "Unable to find IPFS folder. We tried:\n");
+            av_log(h, AV_LOG_INFO, "- $IPFS_PATH, which was empty.\n");
+            av_log(h, AV_LOG_INFO, "- $HOME/.ipfs (full uri: %s) which doesn't exist.\n", ipfs_full_data_folder);
+            ret = AVERROR(ENOENT);
+            goto err;
+        }
+    } else {
+        if (snprintf(ipfs_full_data_folder, sizeof(ipfs_full_data_folder), "%s",
+                 getenv("IPFS_PATH")) >= sizeof(ipfs_full_data_folder)) {
+            av_log(h, AV_LOG_ERROR, "The IPFS_PATH environment variable exceeds the maximum length. We allow a max of %zu characters\n", sizeof(c->gateway_buffer));
+            ret = AVERROR(EINVAL);
+            goto err;
+        }
+
+    }
+
+    // Copy the fully composed gateway path into ipfs_gateway_file.
+    if (snprintf(ipfs_gateway_file, sizeof(ipfs_gateway_file), "%sgateway",
+                 ipfs_full_data_folder) >= sizeof(ipfs_gateway_file)) {
+        av_log(h, AV_LOG_ERROR, "The IPFS gateway file path exceeds the max path length (%zu)\n", sizeof(ipfs_gateway_file));
+        ret = AVERROR(ENOENT);
+        goto err;
+    }
+
+    // Get the contents of the gateway file.
+    gateway_file = av_fopen_utf8(ipfs_gateway_file, "r");
+    if (!gateway_file) {
+        av_log(h, AV_LOG_ERROR, "The IPFS gateway file (full uri: %s) doesn't exist. Is the gateway enabled?\n", ipfs_gateway_file);
+        ret = AVERROR(ENOENT);
+        goto err;
+    }
+
+    // Read a single line (fgets stops at new line mark).
+    fgets(c->gateway_buffer, sizeof(c->gateway_buffer) - 1, gateway_file);
+
+    // Replace the last char with \0
+    c->gateway_buffer[sizeof(c->gateway_buffer) - 1] = 0;
+
+    // Replace first occurence of end of line with \0
+    c->gateway_buffer[strcspn(c->gateway_buffer, "\r")] = 0;
+    c->gateway_buffer[strcspn(c->gateway_buffer, "\n")] = 0;
+
+    // If strlen finds anything longer then 0 characters then we have a
+    // potential gateway url.
+    if (strlen(c->gateway_buffer) < 1) {
+        av_log(h, AV_LOG_ERROR, "The IPFS gateway file (full uri: %s) appears to be empty. Is the gateway started?\n", ipfs_gateway_file);
+        ret = AVERROR(EILSEQ);
+        goto err;
+    } else {
+        // We're done, the c->gateway_buffer has something that looks valid.
+        ret = 1;
+        goto err;
+    }
+
+err:
+    if (gateway_file)
+        fclose(gateway_file);
+
+    return ret;
+}
+
+static int translate_ipfs_to_http(URLContext *h, const char *uri,
+                                  int flags, AVDictionary **options)
+{
+    const char *ipfs_cid;
+    char *fulluri = NULL;
+    int ret;
+    IPFSGatewayContext *c = h->priv_data;
+
+    // Test for ipfs://, ipfs:, ipns:// and ipns:. This prefix is stripped from
+    // the string leaving just the CID in ipfs_cid.
+    int is_ipfs = av_stristart(uri, "ipfs://", &ipfs_cid);
+    int is_ipns = av_stristart(uri, "ipns://", &ipfs_cid);
+
+    // We must have either ipns or ipfs.
+    if (!is_ipfs && !is_ipns) {
+        ret = AVERROR(EINVAL);
+        av_log(h, AV_LOG_ERROR, "Unsupported url %s\n", uri);
+        goto err;
+    }
+
+    // If the CID has a length greater then 0 then we assume we have a proper working one.
+    // It could still be wrong but in that case the gateway should save us and
+    // ruturn a 403 error. The http protocol handles this.
+    if (strlen(ipfs_cid) < 1) {
+        av_log(h, AV_LOG_ERROR, "A CID must be provided.\n");
+        ret = AVERROR(EILSEQ);
+        goto err;
+    }
+
+    // Populate c->gateway_buffer with whatever is in c->gateway
+    if (c->gateway != NULL) {
+        if (snprintf(c->gateway_buffer, sizeof(c->gateway_buffer), "%s",
+                     c->gateway) >= sizeof(c->gateway_buffer)) {
+            av_log(h, AV_LOG_ERROR, "The -gateway parameter is too long. We allow a max of %zu characters\n", sizeof(c->gateway_buffer));
+            ret = AVERROR(EINVAL);
+            goto err;
+        }
+    } else {
+        // Populate the IPFS gateway if we have any.
+        // If not, inform the user how to properly set one.
+        ret = populate_ipfs_gateway(h);
+
+        if (ret < 1) {
+            av_log(h, AV_LOG_ERROR, "No IPFS gateway was set. Make sure a local IPFS instance is running.\n");
+            av_log(h, AV_LOG_INFO, "There are multiple options to define this gateway. The below options are in order of precedence:\n");
+            av_log(h, AV_LOG_INFO, "1. Define a -gateway <url> to the gateway without trailing forward slash.\n");
+            av_log(h, AV_LOG_INFO, "2. Define $IPFS_GATEWAY with the full http link to the gateway without trailing forward slash.\n");
+            av_log(h, AV_LOG_INFO, "3. Define $IPFS_PATH and point it to the IPFS data path.\n");
+            av_log(h, AV_LOG_INFO, "4. Have IPFS running in your local user folder (under $HOME/.ipfs).\n");
+            av_log(h, AV_LOG_INFO, "In all path cases, a file named gateway is expected. See https://github.com/ipfs/specs/issues/261 for more information.\n");
+            goto err;
+        }
+    }
+
+    // Test if the gateway starts with either http:// or https://
+    if (av_stristart(c->gateway_buffer, "http://", NULL) == 0
+        && av_stristart(c->gateway_buffer, "https://", NULL) == 0) {
+        av_log(h, AV_LOG_ERROR, "The gateway URL didn't start with http:// or https:// and is therefore invalid.\n");
+        ret = AVERROR(EILSEQ);
+        goto err;
+    }
+
+    // Concatenate the url.
+    // This ends up with something like: http://localhost:8080/ipfs/Qm.....
+    // The format of "%s%s%s%s" is the following:
+    // 1st %s = The gateway.
+    // 2nd %s = If the gateway didn't end in a slash, add a "/". Otherwise it's an empty string
+    // 3rd %s = Either ipns/ or ipfs/.
+    // 4th %s = The IPFS CID (Qm..., bafy..., ...).
+    fulluri = av_asprintf("%s%s%s%s",
+                          c->gateway_buffer,
+                          (c->gateway_buffer[strlen(c->gateway_buffer) - 1] == '/') ? "" : "/",
+                          (is_ipns) ? "ipns/" : "ipfs/",
+                          ipfs_cid);
+
+    // Pass the URL back to FFMpeg's protocol handler.
+    if ((ret = ffurl_open_whitelist(&c->inner, fulluri, flags,
+                                    &h->interrupt_callback, options,
+                                    h->protocol_whitelist,
+                                    h->protocol_blacklist, h))
+        < 0) {
+        av_log(h, AV_LOG_ERROR, "Unable to open resource: %s\n", fulluri);
+        goto err;
+    }
+
+err:
+    av_free(fulluri);
+    return ret;
+}
+
+static int ipfs_read(URLContext *h, unsigned char *buf, int size)
+{
+    IPFSGatewayContext *c = h->priv_data;
+    return ffurl_read(c->inner, buf, size);
+}
+
+static int64_t ipfs_seek(URLContext *h, int64_t pos, int whence)
+{
+    IPFSGatewayContext *c = h->priv_data;
+    return ffurl_seek(c->inner, pos, whence);
+}
+
+static int ipfs_close(URLContext *h)
+{
+    IPFSGatewayContext *c = h->priv_data;
+    av_free(c->gateway);
+    return ffurl_closep(&c->inner);
+}
+
+#define OFFSET(x) offsetof(IPFSGatewayContext, x)
+
+static const AVOption options[] = {
+    {"gateway", "The gateway to ask for IPFS data.", OFFSET(gateway), AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, AV_OPT_FLAG_DECODING_PARAM},
+    {NULL},
+};
+
+static const AVClass ipfs_context_class = {
+    .class_name   = "IPFS",
+    .item_name    = av_default_item_name,
+    .option       = options,
+    .version      = LIBAVUTIL_VERSION_INT,
+};
+
+const URLProtocol ff_ipfs_protocol = {
+    .name             = "ipfs",
+    .url_open2        = translate_ipfs_to_http,
+    .url_read         = ipfs_read,
+    .url_seek         = ipfs_seek,
+    .url_close        = ipfs_close,
+    .priv_data_size   = sizeof(IPFSGatewayContext),
+    .priv_data_class  = &ipfs_context_class,
+};
+
+const URLProtocol ff_ipns_protocol = {
+    .name             = "ipns",
+    .url_open2        = translate_ipfs_to_http,
+    .url_read         = ipfs_read,
+    .url_seek         = ipfs_seek,
+    .url_close        = ipfs_close,
+    .priv_data_size   = sizeof(IPFSGatewayContext),
+    .priv_data_class  = &ipfs_context_class,
+};
diff --git a/libavformat/protocols.c b/libavformat/protocols.c
index 948fae411f..675b684bd3 100644
--- a/libavformat/protocols.c
+++ b/libavformat/protocols.c
@@ -73,6 +73,8 @@  extern const URLProtocol ff_libsrt_protocol;
 extern const URLProtocol ff_libssh_protocol;
 extern const URLProtocol ff_libsmbclient_protocol;
 extern const URLProtocol ff_libzmq_protocol;
+extern const URLProtocol ff_ipfs_protocol;
+extern const URLProtocol ff_ipns_protocol;
 
 #include "libavformat/protocol_list.c"