diff --git a/python/.gitignore b/python/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..7e99e367f8443d86e5e8825b9fda39dfbb39630d
--- /dev/null
+++ b/python/.gitignore
@@ -0,0 +1 @@
+*.pyc
\ No newline at end of file
diff --git a/python/ftl/ftlstream.py b/python/ftl/ftlstream.py
index 7ab436e0063b9e30e505398f359efd688aad1f68..2d6674e80d421f46e5768e72ea6adfc87384c8f0 100644
--- a/python/ftl/ftlstream.py
+++ b/python/ftl/ftlstream.py
@@ -2,6 +2,8 @@ import msgpack
 
 import numpy as np
 
+import struct
+
 from enum import IntEnum
 from collections import namedtuple
 from . libde265 import Decoder
@@ -16,7 +18,8 @@ except ImportError:
     def _ycrcb2rgb(img):
         ''' YCrCb to RGB, based on OpenCV documentation definition.
         
-        Note: It seems this implementation is not perfectly equivalent to OpenCV's
+        Note: It seems this implementation is not perfectly equivalent to
+        OpenCV's
         '''
         
         rgb = np.zeros(img.shape, np.float)
@@ -34,22 +37,43 @@ except ImportError:
 
 # FTL definitions
 
-_packet = namedtuple("Packet", ["codec", "definition", "block_total", "block_number", "flags", "data"])
-_stream_packet = namedtuple("StreamPacket", ["timestamp", "streamID", "chanel_count", "channel"])
+# components/rgbd-sources/include/ftl/rgbd/camera.hpp
+_Camera = namedtuple("Camera", ["fx", "fy", "cx", "cy", "width", "height",
+                                "min_depth", "max_depth", "baseline", "doffs"])
+
+# components/codecs/include/ftl/codecs/packet.hpp
+_packet = namedtuple("Packet", ["codec", "definition", "block_total",
+                                "block_number", "flags", "data"])
+
+_stream_packet = namedtuple("StreamPacket", ["timestamp", "streamID",
+                                             "chanel_count", "channel"])
+
+# components/codecs/include/ftl/codecs/bitrates.hpp
+class _codec_t(IntEnum):
+    JPG = 0
+    PNG = 1
+    H264 = 2
+    HEVC = 3
+    WAV = 4
+    JSON = 100
+    CALIBRATION = 101
+    POSE = 102
+    RAW = 103
 
 _definition_t = {
-    0 : (),
-    1 : (),
+    0 : (7680, 4320),
+    1 : (2160, 3840),
     2 : (1080, 1920),
     3 : (720, 1280),
-    4 : (),
-    5 : (),
-    6 : (),
-    7 : (),
-    8 : ()
+    4 : (576, 1024),
+    5 : (480, 854),
+    6 : (360, 640),
+    7 : (0, 0),
+    8 : (2056, 1852)
 }
 
-class NALType(IntEnum):
+# components/codecs/include/ftl/codecs/hevc.hpp
+class _NALType(IntEnum):
     CODED_SLICE_TRAIL_N = 0
     CODED_SLICE_TRAIL_R = 1
 
@@ -129,14 +153,19 @@ def get_NAL_type(data):
     if not isinstance(data, bytes):
         raise ValueError("expected bytes")
     
-    return NALType((data[4] >> 1) & 0x3f)
+    return _NALType((data[4] >> 1) & 0x3f)
 
 class FTLStream:
+    ''' FTL file reader '''
+    
     def __init__(self, file):
         self._file = open(file, "br")
         self._decoders = {}
         self._frames = {}
-        
+        self._calibration = {}
+        self._pose = {}
+        self._ts = -1
+
         try:
             magic = self._file.read(5)
             if magic[:4] != bytearray(ord(c) for c in "FTLF"):
@@ -158,14 +187,30 @@ class FTLStream:
         return _stream_packet._make(v1), _packet._make(v2)
     
     def _update_calib(self, sp, p):
-        ''' Update calibration '''
-        pass
-    
+        ''' Update calibration.
+        
+        todo: fix endianess
+        '''
+
+        calibration = struct.unpack("@ddddIIdddd", p.data[:(4*8+2*4+4*8)])
+        self._calibration[sp.streamID] = _Camera._make(calibration)
+
     def _update_pose(self, sp, p):
-        ''' Update pose '''
-        pass
+        ''' Update pose
+        
+        todo: fix endianess
+        '''
+
+        pose = np.asarray(struct.unpack("@16d", p.data[:(16*8)]),
+                          dtype=np.float64)
+        pose = pose.reshape((4, 4), order='F') # Eigen
+
+        self._pose[sp.streamID] = pose
     
-    def _decode_frame_hevc(self, sp, p):
+    def _process_json(self, sp, p):
+        raise NotImplementedError("json decoding not implemented")
+
+    def _push_data_hevc(self, sp, p):
         ''' Decode HEVC frame '''
         
         k = (sp.streamID, sp.channel)
@@ -174,19 +219,29 @@ class FTLStream:
             self._decoders[k] = Decoder(_definition_t[p.definition])
         
         decoder = self._decoders[k]
-        
         decoder.push_data(p.data)
-        decoder.decode()
-        
-        img = decoder.get_next_picture()
-        
-        if img is not None:
-            self._frames[k] = _ycrcb2rgb(img)
     
+    def _decode_hevc(self):
+        for stream, decoder in self._decoders.items():
+            decoder.decode()
+            img = decoder.get_next_picture()
+            
+            if img is not None:
+                self._frames[stream] = _ycrcb2rgb(img)
+
     def _flush_decoders(self):
         for decoder in self._decoders.values():
             decoder.flush_data()
-    
+
+    def seek(self, ts):
+        ''' Read until timestamp reached '''
+        if self._ts >= ts:
+            raise Exception("trying to seek to earlier timestamp")
+        
+        while self.read():
+            if self._ts >= ts:
+                break
+
     def read(self):
         '''
         Reads data for until the next timestamp. Returns False if there is no
@@ -198,29 +253,30 @@ class FTLStream:
             
         self._frames = {}
         
-        ts = self._sp.timestamp
-        ex = None
+        self._ts = self._sp.timestamp
+        ex = []
         
-        while self._sp.timestamp == ts:
+        while self._sp.timestamp == self._ts:
+            # TODO: Can source be removed?
+            
             try:
-                if self._p.codec == 100: # JSON
-                    NotImplementedError("json decoding not implemented")
+                if self._p.codec == _codec_t.JSON:
+                    self._process_json(self._sp, self._p)
 
-                elif self._p.codec == 101: # CALIBRATION
+                elif self._p.codec == _codec_t.CALIBRATION:
                     self._update_calib(self._sp, self._p)
 
-                elif self._p.codec == 102: # POSE
+                elif self._p.codec == _codec_t.POSE:
                     self._update_pose(self._sp, self._p)
 
-                elif self._p.codec == 3: # HEVC
-                    self._decode_frame_hevc(self._sp, self._p)
+                elif self._p.codec == _codec_t.HEVC:
+                    self._push_data_hevc(self._sp, self._p)
 
                 else:
-                    raise ValueError("unkowno codec %i" % p.codec)
+                    raise ValueError("unkowno codec %i" % self._p.codec)
             
             except Exception as e:
-                # TODO: Multiple exceptions possible. Re-design read()?
-                ex = e
+                ex.append(e)
             
             try:
                 self._sp, self._p = self._read_next()
@@ -229,11 +285,37 @@ class FTLStream:
             except msgpack.OutOfData:
                 return False
             
-        if ex is not None:
-            raise ex
-            
+        if len(ex) > 0:
+            raise Exception(ex)
+        
+        self._decode_hevc()
+
         return True
     
+    def get_timestamp(self):
+        return self._ts
+
+    def get_pose(self, source):
+        try:
+            return self._pose[source]
+        except KeyError:
+            raise ValueError("source id %i not found" % source)
+
+    def get_camera_matrix(self, source):
+        calib = self.get_calibration(source)
+        K = np.identity(3, dtype=np.float64)
+        K[0,0] = calib.fx
+        K[1,1] = calib.fy
+        K[0,2] = calib.cx
+        K[1,2] = calib.cy
+        return K
+
+    def get_calibration(self, source):
+        try:
+            return self._calibration[source]
+        except KeyError:
+            raise ValueError("source id %i not found" % source)
+
     def get_frames(self):
         ''' Returns all frames '''
         return self._frames
@@ -243,4 +325,12 @@ class FTLStream:
         if k in self._frames:
             return self._frames[k]
         else:
+            # raise an exception instead?
             return None
+
+    def get_sources(self):
+        ''' Get list of sources
+        
+        todo: Is there a better way?
+        '''
+        return list(self._calibration.keys())
\ No newline at end of file