Webcam Adventure

It has absolutely nothing to do with pr0n sadly. I build a Web dashboard with some statistics for hardware testing. Nothing crazy just a bit flask and a bit of python magic. Anyway at some point I thought it would be cool to add a tab where you can see your test. Since we have IP cameras anyway. Sounds like an fairly easy task to do, or at least I thought that.

So my first step was to figure out what model of cameras we have and how they work. Of course you don’t find a company name or model number in the web Interface. And the “Quick Installation Guide” is a joke. It’s amazing how many pages you can fill with useless informations. At least on the Camera itself there is a sticker with a model number. At least something you can type in Google and with a bit searching it turned out that this camera is no longer for sale, but at least I found out how made it. Which is not as helpful as I thought because Fitivision Technology Inc. are a bit useless. But thanks to that I found a great blog post about a Zonet ZVC7610 network camera which looks almost identical to the one I have. The post mentions two urls http://admin:admin@[camera IP address]/cgi/mjpg/mjpeg.cgi and http://admin:admin@[camera IP address]/cgi/jpg/image.cgi the image url worked fine. But I didn’t got the MJPG stream to display it just started to download a file.

So I looked up how this M-JPEG works. Starting with the wikipedia article, by reading it I found out that it’s probably M-JPEG over HTTP which sounds simple:

In response to a GET request for a MJPEG file or stream, the server streams the sequence of JPEG frames over HTTP. A special mime-type content type multipart/x-mixed-replace;boundary= informs the client to expect several parts (frames) as an answer delimited by . This boundary name is expressly disclosed within the MIME-type declaration itself. The TCP connection is not closed as long as the client wants to receive new frames and the server wants to provide new frames.

Well didn’t worked for me, so the next thing I tried was to use the image url and reload it with javascript.

<!DOCTYPE html>
		function updateImage() {
		    var image = document.getElementById("img");
		    image.src = image.src.split("#")[0] + "#" + new Date().getTime();
		setInterval(updateImage, 800);
    <img id="img" src="http://admin:admin@[camera IP address]/cgi/jpg/image.cgi#date">

Which works fine. The downside of this is for each request a TCP connection get created, the images is downloaded. Which is very slow. But cool is that it works cross browser (Firefox/Chrome) at least I thought it would. More on this topic what could go wrong later.

My next step was to analyse how the Camera itself is able to show more FPS than my javascript solution. It’s a java applet. But on the other hand it’s really easy to find out what it does. Just download the jar file and open it with JD-GUI. For fun I created a swing GUI. All the code is on my bitbucket ultracam. It helped me a lot to figure out how you can decode and display a M-JPG stream. Based on that research I started building a small python parser.

# -*- coding: latin-1 -*-

import requests

url = 'http://[camera IP address]/cgi/mjpg/mjpeg.cgi'
r = requests.get(url, auth=('admin', 'admin'), stream = True)

with open("wat", 'wb') as f:
    for chunk in r.iter_content(chunk_size=600):
        if chunk: # filter out keep-alive new chunks

            #print(str(chunk).find("--myboundary")) # aka --myboundary .index("2d2d6d79626f756e64617279") 
            cl_header = int(str(chunk).find("Content-Length: "))
            cl_header = int(cl_header) + len("Content-Length: ") + 5

            shift = 0
            foundFF = False
            foundD8 = False

            for item in str(chunk)[cl_header:cl_header+45]:
                shift += 1

                hexhex = item.encode("hex")

                if hexhex is "ff":
                    foundFF = True

                if hexhex is "d8":
                    foundD8 = True

                if foundFF and foundD8:

            print(cl_header + shift - 2)


It’s not really finished but it was fun to play around and extract the jpg images from the stream. In the process I learned that there is a tool called ffplay which worked fine with the M-JPG stream.

ffplay -f mjpeg -probesize 32 -i http://[camera IP address]/cgi/mjpg/mjpeg.cgi

This got me thinking why does this not work in a browser which lead me to the conclusion that I’m doing something wrong. With a bit Google magic I found out that you can put a M-JPG stream in a img tag.

<img src="http://admin:admin@[camera IP address]/cgi/mjpg/mjpeg.cgi" />

Well that was too easy. This works fine in Chrome / Chromium (almost). In Firefox it works for a few seconds and then the entire Firefox crashes, reproducible. And Chrome / Chromium doesn’t send the basic auth information if the url is embedded in the img tag.

So for now I use the Firefox with the JavaScript image refreshing method until I figure out what the problem in Chrome is. In conclusion: you can waste many hours for a simple idea.

Setkeycode Lenovo Yoga 13

I have a problem with my touchpad on my laptop. And to figure out what is wrong I checked dmesg and found this unrelated problem. And fixing this problem should not hurt.

[ 3290.177993] atkbd serio0: Unknown key released (translated set 2, code 0xbe on isa0060/serio0).
[ 3290.178007] atkbd serio0: Use 'setkeycodes e03e <keycode>' to make it known.

As you can see it sends a unknown keycode which we can map with setkeycodes e03e. According to the Internet TM this is send every second and tells the OS the orientation of the Screen. So I mapped it to 255 to do nothing like this: sudo setkeycodes e03e 255. This solves the problem until the next reboot which is not good enough.

So we create a systemd service file

$ cat /etc/systemd/system/setkeycodes.service 
Description=Change keycodes at boot

ExecStart=/usr/bin/setkeycodes e03e 255


And enable the service:

sudo systemctl enable setkeycodes 

Logitech F710 Windows 10 Driver

This Blogpost describes how you can use your Logitech F710 controller on a Windows 10. For some reasons the offical Logitech driver doesn’t work on Windows 10. But you can just use the Xbox driver, here is how:

device manager

Right click -> Update Driver Software… -> Browse my computer for driver software -> Let me pick from a list of device drivers on my computer -> Xbox 360 Peripherals -> Xbox 360 Wireless Receiver for Windows.

I would have made a short video how this works but Nvidia Shadowplay also doesn’t work.

Rebuild Windows 10 Efi

My Windows 8 had some weired problems today. Since I migrated this specific installation over three different hardware configurations, I didn’t try to solve it. Instead I just reinstalled a Windows 10. The installation of Windows 10 went smooth, even the creation of the USB stick worked at the first try.

But I wouldn’t write a blog post if everything went flawless. Grub was unable to boot my new Windows 10. And a simple rebuild didn’t fix the problem.

sudo grub2-mkconfig -o /boot/efi/EFI/fedora/grub.cfg

It made it even worse after that the Windows option was vanished from my boot menu.

So to fix this you need to recreate the Windows EFI files. To do this, start diskpart and find your EFI partition. It’s the one which is FAT formated and around 200MB large.

DISKPART> list vol

  Volume ###  Ltr  Label        Fs     Type        Size     Status     Info
  ----------  ---  -----------  -----  ----------  -------  ---------  --------
  Volume 0                      FAT    Partition    200 MB  Healthy    System
  Volume 1                      RAW    Partition    585 GB  Healthy
  Volume 2                      NTFS   Partition    345 GB  Healthy
  Volume 3     E   System Rese  NTFS   Partition    500 MB  Healthy
  Volume 4     C                NTFS   Partition    223 GB  Healthy    Boot

DISKPART> sel vol 0

Volume 0 is the selected volume.

DISKPART> assign letter=b:

DiskPart successfully assigned the drive letter or mount point.


This mounts the EFI partition to the letter b.

Then open a cmd as admin and create the Microsoft\Boot directory and create the EFI boot files:

md b:\EFI\Microsoft\Boot
cd /d b:\EFI\Microsoft\Boot
bcdboot c:\Windows /s b: /f ALL

Now it’s time to restart linux and recreate our grub config with:

sudo grub2-mkconfig -o /boot/efi/EFI/fedora/grub.cfg

Now everything works again.

Build A Dns Server On Debian

Perhaps you read the blog post Build a DNS server on NetBSD. This is essentially the same thing except I use debian this time. The idea behind this is if one system goes down the other one should be running. So basically diversity for zero DNS downtime. (There will be a third blog post with images of the hardware I used)

I used the raspbian lite image from the official raspberrypi site.

So as always unzip it and dd it to the right sd card.

$ unzip
$ sudo dd if=2016-05-27-raspbian-jessie-lite.img of=/dev/sde
2709504+0 records in
2709504+0 records out
1387266048 bytes (1.4 GB) copied, 169.065 s, 8.2 MB/s

Now you can start your pi and login in with user:pi & password:raspberry. It’s highly recommended to change your password with passwd. Also always a good idea is to upgrade your system.

apt-get update && apt-get upgrade

Configure a static IP on your interface:

$ cat /etc/dhcpcd.conf 
# A sample configuration for dhcpcd.
# See dhcpcd.conf(5) for details.


interface eth0
static ip_address=	
static routers=

Now we are ready to install dnsmasq

sudo apt-get install dnsmasq

We can use now the exact same config files as in the previous post.


# Change this line if you want dns to get its upstream servers from
# somewhere other that /etc/resolv.conf

# Add other name servers here, with domain specs if they are for
# non-public domains.

# Add local-only domains here, queries in these domains are answered
# from /etc/hosts or DHCP only.

# Set the cachesize here.




#  = IP =       =  Domainname =               = PC name =          pandora            janus            atlas

The only thing that slightly changes is the path of the first config file.

Side note since I use a resolv-file you need for some reasons also edit /etc/default/dnsmasq and uncomment this line: IGNORE_RESOLVCONF=yes.

Now you can restart dnsmasq and your DNS server is ready to use.