Automatically updating ffmpeg on Windows using Zeranoe’s builds

Building ffmpeg from source on Windows is a royal pain.

For those who came in late, ffmpeg is the best reason for a video nerd to learn the command line.
For every question: “is it possible to automatically do x to some video?”, or I have 10,000 video files in format y, I want to convert them to format z, is that possible in my lifetime?”, the answer is usually: yes, you can do it with ffmpeg.

 

The up side is that you get to learn the command line. The down side is that since ffmpeg is distributed as source code you need to build it. And building it on windows is a royal pain. Just making sure you’ve got the build environment working properly is a full time job, and if you’re paid to be a video nerd you don’t want to be mucking around with minGW and Cygwin and so on. That’s a whole pile of tedium that you don’t need. Fortunately there’s someone who does it for you, for nothing — the wonderful Zeranoe. Aren’t humans the best people?

Machines will do our work for us…

So I was downloading and linking the latest update and making sure it all went into the right place and got to thinking that checking for updates is the kind of task best left to a machine, and given that I’ve been gaining in PowerShell powers for a while I thought it might be a job I could tackle.So below is a function called updateFFMPEG. This script checks for new builds, downloads them, unzips them, and makes a hardlink to the executables — ffmpeg, ffprobe, ffplay — in C:\Program Files\ffmpeg. If you add this folder to your PATH environment variable you can call ffmpeg from the Powershell prompt without having to specify the path to it every time.

The script checks to see if it has been run today, and only checks for new builds if it hasn’t. You could change this to every month if your connection speed is slow, or hour if you’re crazy, by editing the date format string on the second line. Since it’s powershell, you have to save it as a “.ps1” file. You knew that, didn’t you?

function msg($msg)
{
  $Host.UI.RawUI.CursorPosition = New-Object System.Management.Automation.Host.Coordinates 0 , 3
  $Host.UI.Write( "                                                                                                     ")
  $Host.UI.RawUI.CursorPosition = New-Object System.Management.Automation.Host.Coordinates 0 , 3
  Write-Host -Fore Magenta "Updating FFMPEG"
  $Host.UI.RawUI.CursorPosition = New-Object System.Management.Automation.Host.Coordinates 0 , 4
  $Host.UI.Write( "                                                                                                     ")
  $Host.UI.RawUI.CursorPosition = New-Object System.Management.Automation.Host.Coordinates 0 , 4
  Write-Host -Fore Red $msg
}

function update-FFMPEG {
  # change this to -Format MM-yyy for once a month,
  # or HH-dd-MM-yyyy for once an hour
  $theDate = (Get-Date -Format dd-MM-yyyy)
  # this will be the ffmpeg executable directory:
  $ffmpegDir = 'C:\Program Files\ffmpeg\'
  mkdir $ffmpegDir -ErrorAction SilentlyContinue;
  #I like to put all my command line shit in here
  $commandLineDir = 'C:\usr\local\bin\'
  mkdir $commandLineDir -ErrorAction SilentlyContinue;
  # Zeranoe's latest build:
  $proxy = 'http://proxymv:3128'
  $URL = "https://ffmpeg.zeranoe.com/builds/win64/static/ffmpeg-latest-win64-static.7z"
  echo "Updating FFMPEG"
  # check to see if it has been updated today
  if (test-Path ($ffmpegDir + "last_update-" + $theDate)){
    Write-Host "already updated ffmpeg today" -f "Green"
  } else {
    rm ($ffmpegDir + "last_update-*") -ErrorAction SilentlyContinue
    New-Item ($ffmpegDir + "last_update-" + $theDate) -type file 2>&1 1>$null
    echo( "Checking online for new FFMPEG version")
    $downloadPath = ($ffmpegDir + 'latest.7z')

    # check to see if ImageMagick has been installed
    $IMVersion = (ls 'C:\Program Files\ImageMagick*\ffmpeg.exe')

    # delete any old downloads
    echo( "deleting old downloads")
    rm $downloadPath -ErrorAction SilentlyContinue
    # look in the ffmpeg directory for latest current versions
    $f=(ls $ffmpegDir -filter "ffmpeg-*"| ?{ $_.PSIsContainer }| sort lastWriteTime)
    if ($f.length -gt 0) {
      # there are current versions locally
      # get the last write time of the latest version
      $D = (get-date $f[-1].LastWriteTime -format "yyyyMMdd HH:mm:ss")
      echo( "last version was $D")
      # download a newer version if it exists (--time-cond)
      #curl.exe -x $proxy --time-cond $D $URL -o $downloadPath #2>&1 1>$null
      Invoke-WebRequest $URL -OutFile $downloadPath
    } else {
      # no current versions
      echo( "downloading for the first time")
      #curl.exe -x $proxy  $URL -o $downloadPath
      Invoke-WebRequest $URL -OutFile $downloadPath
    }
    if (test-Path $downloadPath){
      # there was a new version available
      echo( "New build of FFMPEG found, installing")
      # unpack it to the ffmpeg program dir
      #(silently, remove "2>&1 1>$null" if you want to know what it's doing)
      &"7z.exe" x -y -o"$ffmpegDir" $downloadPath # 2>&1 1>$null
      # delete the old links
      ls $ffmpegDir -file -filter "ff*.exe"|%{rm $_.fullname}
      if (test-path $commandLineDir -ErrorAction SilentlyContinue){
        ls $commandLineDir -file -filter "ff*.exe"|%{rm $_.fullname}
      }
      # update the latest version
      $f=(ls $ffmpegDir -directory -filter "ffmpeg-*"|sort lastWriteTime)
      # make new symlinks, er hardlinks, whateverr
      ls ($f[-1].fullname + "\bin")|%{
        New-Hardlink ($ffmpegDir + $_.name) $_.FullName
        if (test-path $commandLineDir -ErrorAction SilentlyContinue){
          New-Hardlink ($commandLineDir + $_.name) $_.FullName
        }
      }
      # Imagemagick brings its own version of ffmpeg,
      # which ends up on the PATH, so replace it with a hardlink to this one
      #-------if you don't want this cut here ------
      if ($IMVersion.length -gt 0) {
        echo ( "replacing the Image Magick version of FFMPEG")
        if (Test-Path ($IMVersion.fullname + ".dist")) {
          rm $IMVersion #already made a backup
        } else {
          mv $IMVersion ($IMVersion.fullname + ".dist")
        }
        New-Hardlink $IMVersion.fullname ($ffmpegDir + "ffmpeg.exe")`
        -ErrorAction SilentlyContinue
      }
      #-------to here-------------------------
      rm $downloadPath 2>&1 1>$null
      #-------Update Path variable
            $p=(("C:\usr\local\bin;" + (ls Env:\Path).value).split(";"))
      Set-Content -path Env:\Path -value (($p|Get-Unique) -join ";")

    } else {
      echo( "Current build of FFMPEG is up to date.")
    }
  }
}

Because it writes to c:\program files you’ll need to open a powershell prompt as administrator to run it, then type the command

Update-FFMPEG

I used to have it in my profile file, but it was a bit of overkill.

You might notice the ImageMagick palaver in the code. I added it because ImageMagick installs its own version of ffmpeg, and if ImageMagick is in the $Path then that version could get used instead. So this swaps the new build for ImageMagick’s version. You can safely cut this section out of the code if you don’t have ImageMagick, or if you don’t want some random script from the internet faffing about wi’ it.

Oh, and it relies on 7z.exe being in your PATH. You can download 7z here, and for easily editing the PATH environment variable I recommend Rapid Environment Editor.

6 comments

  1. Hi Thanks for your post and time.
    I have tried your solution but seems to not work properly as it only download an empty file to the created directory .\ffmpeg that the latest version is up to date but nothing else.
    there’s also a PS error on line 32 curl –silent –time-cond $D $URL -o $downloadPath
    it says: invoke-Webrequest : parameter cannot be processed because the parameter name ‘o’ is ambiguous. possible matches include: -Outfile -OutVariable -outBuffer

    Thanks for your help!.

    Reply

    1. I think powershell is using its own get-webrequest instead of using actual curl to do the downloading. I’ve updated the script so it should work now.

      Reply

  2. So did you wanted people to guess what extension the file was supposed to be save? Maybe you don’t even have to save it with extension, maybe it’s part of your profile? who knows because you didn’t say anything, maybe it’s a .bat or .ps1, but who knows you didn’t say anything ? #nerdism

    Reply

    1. Uhhh, ok. It’s Powershell, like it says in the article, so .ps1. I’ve updated the text to be more specific

      Reply

  3. First of all, Thank you for your update, I’ve modified the previous script to my needs and it used to work fine for the .7z releases, now I’ve noticed there’s a whole new ones coming as “.Zip” instead which makes me figure it out another way and also update your current ps1 file and used as a psm1 which exclude “ImageMagick” as I do not use it.
    Also, I excluded the proxy

    Here it is:

    function global:updateFFMPEG (){
    # change this to -Format MM-yyy for once a month,
    # or HH-dd-MM-yyyy for once an hour
    $theDate = (Get-Date -Format dd-MM-yyyy)
    # this will be the ffmpeg executable directory:
    $ffmpegDir = “$home\documents\tools\”
    mkdir $ffmpegDir -ErrorAction SilentlyContinue;
    #I like to put all my command line shit in here
    $commandLineDir = ‘C:\usr\local\bin\’
    mkdir $commandLineDir -ErrorAction SilentlyContinue;
    # Zeranoe’s latest build:
    # $proxy = ‘http://proxymv:3128’ commented as is not being used
    $URL = “https://ffmpeg.zeranoe.com/builds/win64/static/ffmpeg-latest-win64-static.zip”
    Write-Output “Updating FFMPEG”
    # check to see if it has been updated today
    if (test-Path ($ffmpegDir + “last_update-” + $theDate)) {
    Write-Host “already updated ffmpeg today” -f “Green”
    }
    else {
    Remove-Item ($ffmpegDir + “last_update-*”) -ErrorAction SilentlyContinue
    New-Item ($ffmpegDir + “last_update-” + $theDate) -type file 2>&1 1>$null
    Write-Output( “Checking online for new FFMPEG version”)
    $downloadPath = ($ffmpegDir + ‘latest.zip’)
    # delete any old downloads
    Write-Output( “deleting old downloads”)
    Remove-Item $downloadPath -ErrorAction SilentlyContinue
    # look in the ffmpeg directory for latest current versions
    $f = (Get-ChildItem $ffmpegDir -filter “ffmpeg-*”| Where-Object { $_.PSIsContainer }| Sort-Object lastWriteTime)
    if ($f.length -gt 0) {
    # there are current versions locally
    # get the last write time of the latest version
    $D = (get-date $f[-1].LastWriteTime -format “yyyyMMdd HH:mm:ss”)
    Write-Output( “last version was $D”)
    # download a newer version if it exists (–time-cond)
    #curl.exe -x $proxy –time-cond $D $URL -o $downloadPath #2>&1 1>$null
    Invoke-WebRequest $URL -OutFile $downloadPath
    }
    else {
    # no current versions
    Write-Output( “downloading for the first time”)
    #curl.exe -x $proxy $URL -o $downloadPath
    Invoke-WebRequest $URL -OutFile $downloadPath
    }
    if (test-Path $downloadPath) {
    # there was a new version available
    Write-Output( “New build of FFMPEG found, installing”)
    # unpack it to the ffmpeg program dir
    #(silently, remove “2>&1 1>$null” if you want to know what it’s doing)
    &”7z.exe” x -y -o”$ffmpegDir” $downloadPath # 2>&1 1>$null
    # delete the old links
    Get-ChildItem $ffmpegDir -file -filter “ff*.exe”| ForEach-Object {Remove-Item $_.fullname}
    if (test-path $commandLineDir -ErrorAction SilentlyContinue) {
    Get-ChildItem $commandLineDir -file -filter “ff*.exe”| ForEach-Object {Remove-Item $_.fullname}
    }
    # update the latest version
    $f = (Get-ChildItem $ffmpegDir -directory -filter “ffmpeg-*”|Sort-Object lastWriteTime)
    # make new symlinks, er hardlinks, whateverr
    Get-ChildItem ($f[-1].fullname + “\bin”)
    Remove-Item $downloadPath #2>&1 1>$null
    #——-Update Path variable
    $p = ((“C:\usr\local\bin;” + (Get-ChildItem Env:\Path).value).split(“;”))
    Set-Content -path Env:\Path -value (($p|Get-Unique) -join “;”)

    }
    else {
    Write-Output( “Current build of FFMPEG is up to date.”)
    }
    }
    }

    Reply

    1. Thanks for this. I’ll have a proper look at it once I get back to my Windows (work) machine.

      Reply

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.