--- a
+++ b/app/resources/virb.py
@@ -0,0 +1,267 @@
+#!/usr/bin/python3
+# -*- coding: utf8 -*-
+"""Python 3 Module to interact with Garmin Virb cameras over wifi / mass storage
+This module also allows you to interact with the Garmin website and
+fetch firmware updates when you don't feel like using the Garmin supplied
+windows or Mac software.
+
+I can't give you any warranty that any of these things will work,
+especially the firmware upgrades could be dangerous.
+"""
+__author__ = 'Jan KLopper (jan@underdark.nl)'
+__version__ = 0.2
+
+import os
+import simplejson
+import requests
+
+class Virb(object):
+  """Class to interact with Garmin Virb cameras over wifi / http"""
+  def __init__(self, host=('192.168.0.1', 80)):
+    """Sets up the connection with the Virb device
+
+    Accepts an ip and port which should be routable from the device running
+    the code.
+
+    host = ('192.168.0.1', 80)
+    """
+    self.host = host
+    self.requestcount = 0
+
+  def status(self):
+    """Returns the current camera status"""
+    command = 'status'
+    data = {'command': command}
+    return self._do_post(data=data)
+
+  def features(self):
+    """Returns the features"""
+    command = 'features'
+    data = {'command': command}
+    return self._do_post(data=data)[command]
+
+  def get_features(self):
+    """Returns the features list as a dictionary"""
+    features = self.features()
+    results = {'enabled': {},
+               'disabled': {}}
+    for feature in features:
+      name = str(feature['feature'])
+      value = feature['value']
+      try:
+        value = int(value)
+      except ValueError:
+        value = str(value)
+      if feature['enabled']:
+        results['enabled'][name] = value
+      else:
+        results['disabled'][name] = value
+    return results
+
+  def set_features(self, feature, value):
+    """Update a features"""
+    command = 'updateFeature'
+    data = {'command': command,
+            'feature': feature,
+            'value': value}
+    return self._do_post(data=data)['features']
+
+  def sensors(self):
+    """Returns the current camera sensor readings"""
+    command = 'sensors'
+    data = {'command': command}
+    sensors = self._do_post(data=data)
+    if not sensors:
+      raise VirbNoSensors('no Sensors are currently available')
+
+  def device_info(self):
+    """Returns the cameras device info"""
+    command = 'deviceInfo'
+    data = {'command': command}
+    return self._do_post(data=data)[command]
+
+  def locate(self):
+    """Starts the camera emmiting its lost sound/flash"""
+    command = 'locate'
+    data = {'command':command}
+    return bool(int(self._do_post(data=data)['result']))
+
+  def found(self):
+    """Stops the camera emmiting its lost sound/flash"""
+    command = 'found'
+    data = {'command':command}
+    return bool(int(self._do_post(data=data)['result']))
+
+  def media_dir_list(self):
+    """Returns the list of media directories on the device"""
+    command = 'mediaDirList'
+    data = {'command':command}
+    return self._do_post(data=data)#['mediaDirs']
+
+  def media_list(self, path=None):
+    """Returns the list of media directories on the device"""
+    command = "mediaList"
+    data = {'command':command}
+    if path:
+      data['path'] = path
+    return self._do_post(data=data)['media']
+
+
+  def live_preview(self, streamtype="rtp"):
+    """Returns the cameras live preview url"""
+    command = 'livePreview'
+    data = {'command':command,
+            'streamType':streamtype}
+    return self._do_post(data=data)['url']
+
+  def snap_picture(self, timer=0):
+    """Take a picture"""
+    command = 'snapPicture'
+    data = {'command':command,
+            'selfTimer':timer}
+    return self._do_post(data=data)
+
+  def start_recording(self):
+    """Start recording"""
+    command = "startRecording"
+    data = {'command':command}
+    return bool(int(self._do_post(data=data)['result']))
+
+  def stop_recording(self):
+    """Stop recording"""
+    command = 'stopRecording'
+    data = {'command':command}
+    return bool(int(self._do_post(data=data)['result']))
+
+  def stop_stil_tecording(self):
+    """Stop recording still images"""
+    command = 'stopStillRecording'
+    data = {'command':command}
+    return bool(int(self._do_post(data=data)['result']))
+
+  def _do_post(self, url='virb', data=None):
+    url = 'http://%s:%d/%s' % (self.host[0], self.host[1], url)
+    request = requests.post(url, data=simplejson.dumps(data))
+    self.requestcount += 1
+    try:
+      return request.json()
+    except simplejson.scanner.JSONDecodeError:
+      return request.text
+
+
+class VirbUsb(object):
+  """Class to interact with Garmin Virb devices over USB"""
+  def __init__(self, device):
+    """Sets the USB device path as seen on the filesystem
+
+    For example:
+    device = /media/garmin-virb"""
+    self.device = device
+
+  def get_log(self):
+    """Yields a list of log entries on the device"""
+    logfile = open('%s/Garmin/elog.txt' % self.device, 'r')
+    logentry = []
+    for line in logfile.readline():
+      if line:
+        logentry.append(line)
+      if line == '-----------------------------------------':
+        yield logentry
+        logentry = []
+
+  def clear_log(self):
+    """Clears the log entries on the device"""
+    logfile = open('%s/Garmin/elog.txt' % self.device, 'w')
+    logfile.close()
+    return True
+
+  def get_tracks(self):
+    """Lists all the gpx tracks on the devices"""
+    trackpath = '%s/Garmin/GPX' % self.device
+    fileslist = os.listdir(trackpath)
+    return [filename for filename in fileslist
+            if os.path.isfile(os.path.join(trackpath, filename))]
+
+  def get_activity(self):
+    """Lists all FIT activity files on the device
+
+    Use https://github.com/dtcooper/python-fitparse to parse these files into
+    something meaningfull. Or install fitparse using pip: `pip install fitparse`
+    """
+    activitypath = '%s/Garmin/Activity' % self.device
+    fileslist = os.listdir(activitypath)
+    return [filename for filename in fileslist
+            if os.path.isfile(os.path.join(activitypath, filename))]
+
+  def get_media(self, extensions=('jpg', 'mp4')):
+    """Lists all pictures/videos on the devices
+
+    Possibly filter on extensions (lowercase) using the extensions argument as
+      a tuple
+    """
+    mediapath = '%s/DCIM/100_VIRB' % self.device
+    fileslist = os.listdir(mediapath)
+    return [filename for filename in fileslist
+            if (os.path.isfile(os.path.join(mediapath, filename)) and
+                filename[-3:].lower() in extensions)]
+
+  def update_firmware(self, version=None):
+    """This Upgrades the firmware on the Virb
+
+    It follows the known update procedure but handles everything on its own.
+
+    GCD Update Procedure
+
+    Use the links in Firmware History to download the zipped file for the
+    version you need.
+    (https://www8.garmin.com/support/download_details.jsp?id=6565)
+    Unzip the downloaded archive and extract the gcd file
+    Rename the gcd file to gupdate.gcd
+    Connect the VIRB to your computer via usb
+    Copy the gupdate.gcd file to [µSD]/Garmin/gupdate.gcd
+    Disconnect and reboot the VIRB
+    Once the update is completed, the VIRB will delete the gupdate.gcd file"""
+    if version:
+      garmin = Garmin()
+      firmware = garmin.get_firmware(version=version)
+      targetpath = '%s/Garmin/gupdate-.gcd' % self.device
+      print('Writing out data to device, do not reboot / power down')
+      targetfile = open(targetpath, 'w')
+      targetfile.write(firmware)
+      print('Finishing writing out data to device, do not reboot / power down')
+      os.fsync(targetfile)
+      targetfile.close()
+      print('All Done. Please reboot the Virb')
+      return True
+    return False
+
+
+class Garmin(object):
+  """Class to namespace any Garmin specific funtions and methods that have no
+  direct use for any specific device information"""
+  @staticmethod
+  def get_firmware(device="VIRB", version=4.00):
+    """Version as float"""
+    return requests.get("https://download.garmin.com/software/%s_%d.gcd" % (
+        device, int(version*100)))
+
+class VirbError(Exception):
+  """General exception class for Virb camera class"""
+
+
+class VirbNoSensors(VirbError):
+  """No sensors where located / enabled / connected to the Virb"""
+
+
+if __name__ == '__main__':
+  camera = Virb()
+  print(repr(camera.status()))
+  print(repr(camera.device_info()))
+  print(repr(camera.features()))
+  print(repr(camera.get_features()))
+  try:
+    print(repr(camera.sensors()))
+  except VirbNoSensors:
+    print('no sensors connected')
+  print(repr(camera.media_dir_list()))
+  #print(repr(camera.SnapPicture()))