2025-07-11 - mjpeg and basic auth on client side

some time ago, as a part of my Voron activities, i decided to add a camera to monitor the print. it was an interesting quest to find nice, and easy to use options, while minimizing external dependencies. finally i've settled for MJPEG + reverse proxy on nginx… but that's for another blog entry. :)

here, the interesting part was client side. since i used nginx it was very natural to just do HTTPS + basic auth for auth and encryption.

take 1: ffmpeg

ffmpeg has a built-in support for both MJPEG and basic auth, so it was supposed to be a smooth sailing… however there was a small catch – basic auth is passed as a part of the URI, e.g.:

https://user:mysecret@camera.address/some_stream

combining this with ffmpeg was straight forward:

ffmpeg \
  -f mjpeg -framerate 25 -r 25 -i https://user:mysecret@camera.address/some_stream \
  -vcodec copy -an \
  output.avi

…and guess what happens when you run ps command? yes – the password is in clear text. it turns out that ffmpeg does not sanitize the input secret. it also does not have any option

take 2: curl + ffmpeg

the next, natural option was to use curl:

curl https://user:mysecret@camera.address/some_stream | ffmpeg -i - ...

then ps… and still the same – secret is there. :/

interestingly enough – there was no way to read a series of links from a file in curl. i guess the assumption was that one can always do a for loop in shell, or similar. it however does not cover my case of hiding basic auth details.

take 3: wget + ffmpeg

the next natural take was:

curl -O - https://user:mysecret@camera.address/some_stream | ffmpeg -i - ...

as you probably already expect – password was still visible in process list. this time however, wget was an ace upon its sleeve – it can read links from file!

it's now enough to prepare a file:

touch ~/.my_camera.cfg
chmod $_
vim $_

and put our magic string here:

https://user:mysecret@camera.address/some_stream

now to run it just use the input file, instead of a direct link:

wget -O - -i ~/.my_camera.cfg | ffmpeg -i - ...

and voila! stream is being recorded, while password is ~secure in a config file.

full script

to make thins complete we add 2 more things on top:

  • H.264 compression
  • file segmentation (10min each)
#!/bin/bash
set -eu -o pipefail
app=$(basename "$0")
crt="$HOME/.config/camera/mycam.crt"
link="$HOME/.config/camera/mycam.link" # << secret password lives here
outdir_base="$HOME/recordings"
fps=25
outdir="$outdir_base/$(date +%Y-%m-%d_%H%M%d)"
 
mkdir -p "$outdir"
echo "$app: recording to $outdir"
 
wget_cmd=(wget)
wget_cmd+=(--quiet)
wget_cmd+=(--timeout=5)
wget_cmd+=(--ca-certificate "$crt")
wget_cmd+=(-O -)
wget_cmd+=(-i "$link")
 
ffmpeg_cmd=(ffmpeg)
ffmpeg_cmd+=(-f mjpeg)
ffmpeg_cmd+=(-framerate "$fps") # input frame rate - apparently this is what source provides atm...
ffmpeg_cmd+=(-i -)
ffmpeg_cmd+=(-r "$fps") # output frame rate
# compress input
ffmpeg_cmd+=(-vcodec h264 -preset fast -crf 23)
# disable audio (if present)
ffmpeg_cmd+=(-an)
# segment output
ffmpeg_cmd+=(-f segment)
ffmpeg_cmd+=(-segment_time 600) # seconds
ffmpeg_cmd+=(-reset_timestamps 1)
ffmpeg_cmd+=("$outdir/%06d.avi")
 
"${wget_cmd[@]}" | "${ffmpeg_cmd[@]}"

final notes

while the need to combine ffmpeg with downloader was anyway a must have, due to custom HTTPS certificates used for the local connection, the lack of easy basic auth covering up was a bit of a surprise to me. while it's kinda ok for app not overwriting password in its argv (it can give a false sense of security, as there's still a race when starting application – password is briefly visible then), not being able to pass it via some env or a secrets file was a bit of a head-scratcher for me.

the good news is that FLOSS and the UNIX way of combining things with pipes once again shined and saved the day! :)

blog/2025/07/11/2025-07-11_-_mjpeg_and_basic_auth_on_client_side.txt · Last modified: 2025/07/11 07:09 by basz
Back to top
Valid CSS Driven by DokuWiki Recent changes RSS feed Valid XHTML 1.0