diff --git a/Dockerfile b/Dockerfile
index 34b8060d385cf837bcedc3c54be768b8edae21c8..e217e3e6b43d3310e9e12e65124b2d6accfa737c 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -11,7 +11,7 @@ RUN \
     pacman -S --noconfirm --noprogressbar --quiet --needed \
         git vim \
         gstreamer gst-plugins-base gst-plugins-good gst-plugins-bad gst-plugins-ugly gst-libav \
-        qrencode zbar ffmpeg make \
+        qrencode zbar make \
         python-setuptools python-pip python-gobject gst-python python-numpy
 
 COPY . /opt/qrlipsync
diff --git a/Makefile b/Makefile
index 67029444f4b2331b5e1166702497d4378397d8a8..ba23549f87e0932ca1817325092d0a993e495b56 100644
--- a/Makefile
+++ b/Makefile
@@ -35,7 +35,7 @@ ifeq (${VIDEO_FOUND}, 1)
 	@echo "${VIDEO} file not found, exiting, run with VIDEO=${VIDEO}"
 	@exit 1
 else
-	docker run -v ${CURDIR}/${VIDEO}:/opt/src/${VIDEO}:ro ${DOCKER_IMAGE_NAME} qr-lipsync-detect.py /opt/src/${VIDEO} ${ARGS}
+	docker run -v ${CURDIR}:/opt/src/ ${DOCKER_IMAGE_NAME} qr-lipsync-detect.py /opt/src/${VIDEO} ${ARGS}
 endif
 
 generate:
diff --git a/bin/qr-lipsync-detect.py b/bin/qr-lipsync-detect.py
index 4297988e2eb4975c7d3125fd31952f9d226f40c4..b8d867c8c825199833740b68afd41881bc832568 100755
--- a/bin/qr-lipsync-detect.py
+++ b/bin/qr-lipsync-detect.py
@@ -101,7 +101,7 @@ if __name__ == "__main__":
         dirname = os.path.dirname(media_file)
         media_prefix = os.path.splitext(os.path.basename(media_file))[0]
         result_file = os.path.join(dirname, "%s_data.txt" % (media_prefix))
-        d = QrLipsyncDetector.create(media_file, result_file, options, mainloop)
+        d = QrLipsyncDetector(media_file, result_file, options, mainloop)
         if d:
             GLib.idle_add(d.start)
             try:
diff --git a/qrlipsync/detect.py b/qrlipsync/detect.py
index 87a9de71ef5953635b720de9002c7ba492e8b9ab..6be7de5547604fe4047c5aaeb203a90fd78c2f86 100644
--- a/qrlipsync/detect.py
+++ b/qrlipsync/detect.py
@@ -1,6 +1,6 @@
 #!/usr/bin/env python
 import os
-import shutil
+import sys
 import time
 import subprocess
 import logging
@@ -10,9 +10,11 @@ from fractions import Fraction
 import gi
 
 gi.require_version("Gst", "1.0")
+gi.require_version("GstPbutils", "1.0")
 # We don't want to use hw accel since it seems to be messing with latency
 os.environ["LIBVA_DRIVER_NAME"] = "fakedriver"
 from gi.repository import Gst  # noqa
+from gi.repository import GstPbutils  # noqa
 
 Gst.init(None)
 
@@ -21,54 +23,46 @@ logger = logging.getLogger("detector")
 QUEUE_OPTS = "max-size-buffers=10 max-size-bytes=0 max-size-time=0"
 
 
-def run_subprocess(cmd, filename):
-    fields = cmd.split(" ")
-    fields.append(filename)
-    result = subprocess.check_output(fields, universal_newlines=True)
-    return result
-
-
 def get_media_info(media_file):
-    result = None
+    uri = Gst.filename_to_uri(os.path.realpath(media_file))
     try:
-        ffprobe = shutil.which("ffprobe")
-    except Exception:
-        # python2
-        from distutils.spawn import find_executable
-
-        ffprobe = find_executable("ffprobe")
-    if ffprobe:
-        cmd = "ffprobe -v error -select_streams v -show_entries stream=width,height,avg_frame_rate,duration -of default=noprint_wrappers=1 -print_format json"
-        try:
-            cmd_result = run_subprocess(cmd, media_file)
-        except Exception:
-            cmd_result = None
-        if cmd_result:
-            vjres = json.loads(cmd_result)["streams"][0]
-            if not vjres.get("duration"):
-                cmd = "ffprobe -v error -select_streams v -show_format_entry duration -of default=noprint_wrappers=1 -print_format json"
-                cmd_result = run_subprocess(cmd, media_file)
-                vjres["duration"] = json.loads(cmd_result)["format"]["duration"]
-            cmd = "ffprobe -v error -select_streams a -show_entries stream=sample_rate,codec_name -of default=noprint_wrappers=1 -print_format json"
-            cmd_result = run_subprocess(cmd, media_file)
-            ajres = json.loads(cmd_result)["streams"]
-            if ajres:
-                ajres = ajres[0]
-                vjres["sample_rate"] = ajres["sample_rate"]
-                vjres["a_codec"] = ajres["codec_name"]
-            else:
-                logger.error("No audio track found, cannot detect sync")
-            result = vjres
+        info = GstPbutils.Discoverer.new(10 * Gst.SECOND).discover_uri(uri)
+    except gi.repository.GLib.Error as e:
+        sys.exit("Could not discover file: %s (%s)" % (media_file, e))
+
+    try:
+        vinfo = info.get_video_streams()[0]
+    except IndexError:
+        sys.exit("File contains no video stream")
+
+    fps_num = vinfo.get_framerate_num()
+    fps_denom = vinfo.get_framerate_denom()
+    if not fps_num or not fps_denom:
+        fps = None
     else:
-        logger.error("ffprobe is required")
+        fps = Fraction(fps_num, fps_denom)
+
+    result = {
+        'width': vinfo.get_width(),
+        'height': vinfo.get_height(),
+        'framerate': fps,
+        'duration': str(info.get_duration() / Gst.SECOND),
+    }
+
+    try:
+        ainfo = info.get_audio_streams()[0]
+        result["sample_rate"] = ainfo.get_sample_rate()
+        result["a_codec"] = GstPbutils.pb_utils_get_codec_description(ainfo.get_caps()).lower().replace("mpeg-4 aac", "aac")
+    except IndexError:
+        logger.warning("File contains no audio stream, cannot detect sync")
     return result
 
 
 class QrLipsyncDetector:
-    def __init__(self, media_file, result_file, options, mainloop, media_info):
+    def __init__(self, media_file, result_file, options, mainloop):
         self.analyze_returncode = None
         self.options = options
-        self.media_info = media_info
+        self.media_info = get_media_info(media_file)
         self._samplerate = int(self.media_info.get("sample_rate", 0))
         self._media_duration = float(self.media_info["duration"])
         self.mainloop = mainloop
@@ -87,20 +81,20 @@ class QrLipsyncDetector:
         self._tick_count = 0
         spectrum_interval_ms = 3
         self.spectrum_interval_ns = spectrum_interval_ms * Gst.MSECOND
-        framerate = self.media_info.get("avg_frame_rate")
 
-        if framerate is not None:
-            # assume audio ticks are at least 1 video frame long
-            self.framerate = Fraction(self.media_info["avg_frame_rate"])
-            frame_dur_ms = float(1000 / self.framerate)
-        else:
-            # assume 60 fps
-            frame_dur_ms = 1000 / 60
+        framerate = self.media_info.get('framerate')
+        if framerate is None:
+            if options.expected_beep_duration:
+                framerate = 1000 / options.expected_beep_duration
+                logger.warning(f'Unable to guess framerate, using --expected-beep-duration to guess framerate: {framerate}')
+            else:
+                framerate = Fraction(60, 1)
+                logger.warning('Unable to guess framerate, assuming {framerate} until we can extract the real value from the first qrcode')
+        self.framerate = framerate
 
-        if options.expected_beep_duration:
-            self.ticks_count_threshold = int(options.expected_beep_duration / spectrum_interval_ms)
-        else:
-            self.ticks_count_threshold = int(frame_dur_ms / spectrum_interval_ms)
+        # assume audio ticks are at least 1 video frame long
+        frame_dur_ms = float(1000 / self.framerate)
+        self.ticks_count_threshold = int(frame_dur_ms / spectrum_interval_ms)
 
         # FIXME: fdk adds 2048 samples of priming samples (silence) which adds 42ms of latency
         # aacenc adds 1024 samples (21ms)
@@ -137,14 +131,6 @@ class QrLipsyncDetector:
         self.pipeline_str = self.get_pipeline(self._uri_media_file)
         self.pipeline = Gst.parse_launch(self.pipeline_str)
 
-    @classmethod
-    def create(qrlipsyncdetector, media_file, result_file, options, mainloop):
-        result = None
-        media_info = get_media_info(media_file)
-        if media_info:
-            result = qrlipsyncdetector(media_file, result_file, options, mainloop, media_info)
-        return result
-
     def exit(self):
         self.pipeline.set_state(Gst.State.NULL)
         self.mainloop.quit()
@@ -304,7 +290,11 @@ class QrLipsyncDetector:
                     real_framerate = float(Fraction(qrcode["FRAMERATE"]))
                     beep_dur = int(1000 / real_framerate)
                     if real_framerate != self.framerate:
-                        logger.warning(f"Input file framerate ({self.framerate}) differs from original sample framerate ({real_framerate}), you should run this with --expected-beep-duration {beep_dur} or beeps won't be detected")
+                        logger.warning(f"Detected real framerate {real_framerate} (different from assumed framerate {self.framerate}, updating values. You should run this with --expected-beep-duration {beep_dur} or (some) beeps may not be detected")
+                        self.framerate = real_framerate
+                        frame_dur_ms = 1000 / real_framerate
+                        spectrum_interval_ms = self.spectrum_interval_ns * 1000
+                        self.ticks_count_threshold = int(frame_dur_ms / spectrum_interval_ms)
                 qrcode["ELEMENTNAME"] = elt_name
                 qrcode["VIDEOTIMESTAMP"] = timestamp
                 if qrcode.get("TICKFREQ"):
@@ -372,6 +362,12 @@ class QrLipsyncDetector:
                     self._tick_count += 1
                     self.write_line(json.dumps(result))
 
+    def run_subprocess(self, cmd, filename):
+        fields = cmd.split(" ")
+        fields.append(filename)
+        result = subprocess.check_output(fields, universal_newlines=True)
+        return result
+
     def disconnect_probes(self):
         logger.debug("Disconnecting probes")
         if self._audio_fakesink_pad: