4

I am trying to get video details (length, height, width, and content type) from a video based on a URL. By using the nuget package https://www.nuget.org/packages/NReco.VideoInfo.LT I was able to create an Azure Function running locally pointing to my local install of FFProbe rather easily. However, the trick is now making it compatible with an Azure Function and, to the best of my knowledge, the easiest way to do that is by providing a Docker image that contains everything needed (this would be running on a Linux machine).

So, let's get into the code. Previously, this is what I had working if I ran the function locally:

   var ffProbe = new NReco.VideoInfo.FFProbe();
   ffProbe.FFProbeExeName = "ffprobe.exe";  // just "ffprobe" for Linux/OS-X
   ffProbe.ToolPath = "C:\\tools\\ffmpeg\\bin";
   var videoInfo = ffProbe.GetMediaInfo(videoUrl);

Now, (and again, this is the best to my knowledge), to get it to run in a Linux environment using Docker, I've run into several issues.

So, the first attempt was the easy way I suppose and try to do a apt-get install ffmpeg. This was the Dockerfile (Sorry, this is going to be a long post... but better to show everything I suppose):

   FROM mcr.microsoft.com/dotnet/core/sdk:3.1 AS installer-env
   
   # 0. Install essential packages
   RUN apt-get -y update
   RUN apt-get -y upgrade
   RUN apt-get install -y ffmpeg
   
   ENV AzureWebJobsStorage=""
   
   COPY . /src/dotnet-function-app
   RUN cd /src/dotnet-function-app && \
   mkdir -p /home/site/wwwroot && \
   dotnet publish *.csproj --output /home/site/wwwroot
    
   RUN mkdir -p /home/site/wwwroot/bin
   RUN mv /usr/bin/* /home/site/wwwroot/bin
   
   # To enable ssh & remote debugging on app service change the base image to the one below
   # FROM mcr.microsoft.com/azure-functions/dotnet:3.0-appservice
   FROM mcr.microsoft.com/azure-functions/dotnet:3.0
   ENV AzureWebJobsScriptRoot=/home/site/wwwroot \
   AzureFunctionsJobHost__Logging__Console__IsEnabled=true

   COPY --from=installer-env ["/home/site/wwwroot", "/home/site/wwwroot"]

I change the code to

   var ffProbe = new NReco.VideoInfo.FFProbe();
   ffProbe.FFProbeExeName = "ffprobe";  // just "ffprobe" for Linux/OS-X
   ffProbe.ToolPath = "/home/site/wwwroot/bin";
   var videoInfo = ffProbe.GetMediaInfo(videoUrl);

That builds, but when I run it, I get an error: error while loading shared libraries: libavdevice.so.58

So, I do some digging and it looks like I have to go the manual build route. Which isn't ideal, but I give it a go and adjust the Dockerfile:

   FROM mcr.microsoft.com/dotnet/core/sdk:3.1 AS installer-env

   # 0. Install essential packages
   RUN apt-get update -qq && apt-get -y install \
       autoconf \
       automake \
       build-essential \
       cmake \
       git-core \
       libass-dev \
       libfreetype6-dev \
       libsdl2-dev \
       libtool \
       libva-dev \
       libvdpau-dev \
       libvorbis-dev \
       libxcb1-dev \
       libxcb-shm0-dev \
       libxcb-xfixes0-dev \
       pkg-config \
       texinfo \
       wget \
       zlib1g-dev \
       nasm \
       yasm \
       libx265-dev \
       libnuma-dev \
       libvpx-dev \
       libmp3lame-dev \
       libopus-dev \
       libx264-dev
   
   RUN mkdir -p /home/site/wwwroot
   
   RUN mkdir -p /home/site/wwwroot/ffmpeg_sources /home/site/wwwroot/bin && cd /home/site/wwwroot/ffmpeg_sources && \
       wget -O ffmpeg-4.3.1.tar.bz2 https://ffmpeg.org/releases/ffmpeg-4.3.1.tar.bz2 && \
       tar xjvf ffmpeg-4.3.1.tar.bz2 && \
       cd ffmpeg-4.3.1 && \
       PATH="/home/site/wwwroot/bin:$PATH" PKG_CONFIG_PATH="/home/site/wwwroot/ffmpeg_build/lib/pkgconfig" ./configure \
       --prefix="/home/site/wwwroot/ffmpeg_build" \
       --pkg-config-flags="--static" \
       --extra-cflags="-I/home/site/wwwroot/ffmpeg_build/include" \
       --extra-ldflags="-L/home/site/wwwroot/ffmpeg_build/lib" \
       --extra-libs="-lpthread -lm" \
       --bindir="/home/site/wwwroot/bin" \
       --enable-gpl \
       --enable-libass \
       --enable-libfreetype \
       --enable-libmp3lame \
       --enable-libopus \
       --enable-libvorbis \
       --enable-libvpx \
       --enable-libx264 \
       --enable-libx265 \
       --enable-nonfree && \
       PATH="/home/site/wwwroot/bin:$PATH" make -j8 && \
       make install -j8 && \
       hash -r
   
   ENV AzureWebJobsStorage=""
   
   COPY . /src/dotnet-function-app
   RUN cd /src/dotnet-function-app && \
   dotnet publish *.csproj --output /home/site/wwwroot
   
   FROM mcr.microsoft.com/azure-functions/dotnet:3.0
   ENV AzureWebJobsScriptRoot=/home/site/wwwroot \
       AzureFunctionsJobHost__Logging__Console__IsEnabled=true
   
   COPY --from=installer-env ["/home/site/wwwroot", "/home/site/wwwroot"]

Again, that builds and then I try to run, this time getting the same error but for a different file: error while loading shared libraries: libxcb.so.1

So, I should mention that Docker is somewhat new to me, so I could be missing something easy. But I've tried to figure this out all night until I decided that I needed some advice. My main app is also hosted on a linux machine, so that would be a no go there. Besides the Azure Function approach, the other crazy idea is spinning up a low-priority Windows VM for like $20 a month... but then I have to configure IIS and all that stuff. So, it would be great to figure out what I'm doing wrong or if it's even feasible. I've gotten Python/Selenium to work using Docker and Azure Functions before based off of https://github.com/rebremer/azure-function-selenium... so I'm hoping it's just me not being familiar with Docker enough.

Any help to get this working for me would be greatly appreciated. If you have any questions, please don't hesitate to ask.

Thanks!

3
  • For Docker, you need to install all the dependencies if the base image does not include.
    – Charles Xu
    Commented Aug 7, 2020 at 8:12
  • Could you expand on this a little more? I've tried including "FROM jrottenberg/ffmpeg" in various parts of the docker file, but at the end of the day it still can't find ffprobe. I changed the path to look in /usr/local/bin, but I think the FROM mcr.microsoft.com/azure-functions/dotnet overwrites whatever is pulled in from the other Docker image. As said, I'm relatively new to Docker.... so any more information you can provide would be helpful. Thank you for your help!
    – AJ Tatum
    Commented Aug 7, 2020 at 17:59
  • Any more questions or updates?
    – Charles Xu
    Commented Aug 11, 2020 at 1:21

3 Answers 3

7

My solution ended up not using another Docker image once I realized that basically the image for Azure Functions in Docker starts off with Ubuntu. Realizing that, my Dockerfile came out to be

FROM mcr.microsoft.com/dotnet/core/sdk:3.1 AS installer-env

COPY . /src/dotnet-function-app
RUN cd /src/dotnet-function-app && \
    mkdir -p /home/site/wwwroot && \
    dotnet publish *.csproj --output /home/site/wwwroot

FROM mcr.microsoft.com/azure-functions/dotnet:3.0
ENV AzureWebJobsScriptRoot=/home/site/wwwroot \
    AzureFunctionsJobHost__Logging__Console__IsEnabled=true \
    AzureWebJobsStorage=""

COPY --from=installer-env ["/home/site/wwwroot", "/home/site/wwwroot"]

RUN apt-get update && apt-get install -y ffmpeg

So, it ended up being a really simple solution. Have Azure build the image, copy the website to wwwroot and then install ffmpeg like you would normally on Ubuntu! Then, it was a matter of just updating the code to:

var ffProbe = new NReco.VideoInfo.FFProbe();
ffProbe.FFProbeExeName = "ffprobe";  // just "ffprobe" for Linux/OS-X
ffProbe.ToolPath = "/usr/bin";
var mediaInfo = ffProbe.GetMediaInfo(videoUrl);

And that's it!

1
  • Thank you! This helped me. I had pretty much the same dockerfile as you and had the ffmpeg install sandwiched between the install of the two sdks, and not as the last step. Moving to the last step made available in the final stage. :) Commented Aug 19, 2020 at 22:49
0

Since you are new to Docker. Here I will show you something I have known.

You Dockerfile is to create an image with multiple stages:

With multi-stage builds, you use multiple FROM statements in your Dockerfile. Each FROM instruction can use a different base, and each of them begins a new stage of the build. You can selectively copy artifacts from one stage to another, leaving behind everything you don’t want in the final image.

From the description, you need to know all the FROM except the last one is the build pattern, only the complete the application will be copied to the last base image and run in it. So it is not the overwrite, it's a new running environment. You need to install all the dependencies for your application in the last base image if it does not already have.

0

I am newbie, so my solution may not have efficieny. I was also facing problem to use FFMpegCore in dotnet core docker container to generate thumbnail from a video. This is what I came up with in my Dockerfile. I am pointing out important lines in it.

FROM mcr.microsoft.com/dotnet/core/aspnet:3.1-alpine
RUN apk add libgdiplus --update-cache --repository http://dl-3.alpinelinux.org/alpine/edge/testing/ --allow-untrusted
RUN apk update && apk add ffmpeg
... [onwards]

Added package reference to FFMpegCore

<PackageReference Include="FFMpegCore" Version="2.2.6"/>

Now my code to generate thumbnail:

public async Task<Stream> CreateVideoThumbnailAsync(IFormFile file, string thumbnailName)
    {
        // Creating a temporary folder in project directory
        string path = Directory.GetCurrentDirectory() + Path.GetTempPath();
        Directory.CreateDirectory("tmp");
        var filePath = Path.Combine(path, file.FileName);

        using (var fileStream = new FileStream(filePath, FileMode.Create)) {
            // Copying file to temporary folder
            await file.CopyToAsync(fileStream);
            
            // Generating thumbnail
            var mediaFileAnalysis = await FFProbe.AnalyseAsync(filePath);
            var bitmap = await FFMpeg.SnapshotAsync(mediaFileAnalysis, new Size(), TimeSpan.FromMinutes(1));
            
            // Cleaning project directory
            Directory.Delete("tmp", true);

            return bitmap.ToStream(ImageFormat.Jpeg);
        }
    }

public static Stream ToStream(this Image image, ImageFormat format) {
        var stream = new System.IO.MemoryStream();
        image.Save(stream, format);
        stream.Position = 0;
        return stream;
    }

I needed to upload it to Azurite (emulator for local Azure Storage Development) by using the stream returned. You can look to documentation of FFMpegCore.

Not the answer you're looking for? Browse other questions tagged or ask your own question.