From 14427f01672a0fb2e0a2d9e9bccb9f84c55bcea0 Mon Sep 17 00:00:00 2001
From: Thulinma <jaron@vietors.com>
Date: Fri, 20 Jul 2018 14:23:34 +0200
Subject: [PATCH] EBML updates: - AV1 support - Support for outputting
 fragments longer than 30 seconds in duration - Fixed FireFox support for Opus
 audio tracks - Added support for stdin live input of EBML - Fixed broken
 timestamps when seeking in VoD EBML files - Analyser now calculates offsets
 for (manual) double-checking - Added JSON track support to EBML input and
 output - Added basic input support for SRT/ASS/SSA subtitles - Opus
 CODECDELAY now actually calculated. - Fixed Opus in Firefox - Improved MP3
 support, more robust handling of corruption, support for non-standard
 timescale sources

# Conflicts:
#	src/output/output_ebml.cpp
---
 lib/ebml.cpp                    |  54 +++++-----
 lib/ebml.h                      |   5 +
 src/analysers/analyser_ebml.cpp |  57 ++++++++++-
 src/analysers/analyser_ebml.h   |   4 +
 src/input/input_ebml.cpp        | 174 ++++++++++++++++++++++++++++----
 src/input/input_ebml.h          |  61 ++++++++---
 src/output/output_ebml.cpp      |  82 ++++++++++-----
 src/output/output_ebml.h        |   2 +-
 8 files changed, 348 insertions(+), 91 deletions(-)

diff --git a/lib/ebml.cpp b/lib/ebml.cpp
index b3cc9332..c865f841 100644
--- a/lib/ebml.cpp
+++ b/lib/ebml.cpp
@@ -16,7 +16,7 @@ namespace EBML{
     if (p[0] & 0x04){return 6;}
     if (p[0] & 0x02){return 7;}
     if (p[0] & 0x01){return 8;}
-    return 0;
+    return 1;
   }
 
   /// Returns the size of an EBML-encoded integer for a given numerical value
@@ -149,8 +149,8 @@ namespace EBML{
     case EID_PIXELWIDTH: return "PixelWidth";
     case EID_PIXELHEIGHT: return "PixelHeight";
     case 0x1A: return "FlagInterlaced";
-    case 0x14B0: return "DisplayWidth";
-    case 0x14BA: return "DisplayHeight";
+    case EID_DISPLAYWIDTH: return "DisplayWidth";
+    case EID_DISPLAYHEIGHT: return "DisplayHeight";
     case 0x15B0: return "Colour";
     case 0x15B7: return "ChromaSitingHorz";
     case 0x15B8: return "ChromaSitingVert";
@@ -164,8 +164,8 @@ namespace EBML{
     case EID_CHANNELS: return "Channels";
     case EID_SAMPLINGFREQUENCY: return "SamplingFrequency";
     case EID_BITDEPTH: return "BitDepth";
-    case 0x16AA: return "CodecDelay";
-    case 0x16BB: return "SeekPreRoll";
+    case EID_CODECDELAY: return "CodecDelay";
+    case EID_SEEKPREROLL: return "SeekPreRoll";
     case EID_CODECPRIVATE: return "CodecPrivate";
     case EID_DEFAULTDURATION: return "DefaultDuration";
     case EID_EBMLVERSION: return "EBMLVersion";
@@ -185,7 +185,7 @@ namespace EBML{
     case 0x6C: return "Void";
     case 0x3F: return "CRC-32";
     case 0x33A4: return "SegmentUID";
-    case 0x254c367: return "Tags";
+    case EID_TAGS: return "Tags";
     case 0x3373: return "Tag";
     case 0x23C0: return "Targets";
     case 0x27C8: return "SimpleTag";
@@ -271,7 +271,7 @@ namespace EBML{
     case EID_CUEPOINT:
     case EID_CUETRACKPOSITIONS:
     case 0x15B0:
-    case 0x254c367:
+    case EID_TAGS:
     case 0x3373:
     case 0x23C0:
     case 0x43a770:
@@ -296,8 +296,8 @@ namespace EBML{
     case EID_FLAGLACING:
     case EID_TRACKTYPE:
     case EID_DEFAULTDURATION:
-    case 0x16AA:
-    case 0x16BB:
+    case EID_CODECDELAY:
+    case EID_SEEKPREROLL:
     case EID_CUETIME:
     case EID_CUETRACK:
     case EID_CUECLUSTERPOSITION:
@@ -305,8 +305,8 @@ namespace EBML{
     case EID_PIXELWIDTH:
     case EID_PIXELHEIGHT:
     case 0x1A:
-    case 0x14B0:
-    case 0x14BA:
+    case EID_DISPLAYWIDTH:
+    case EID_DISPLAYHEIGHT:
     case EID_CHANNELS:
     case EID_BITDEPTH:
     case 0x15B7:
@@ -652,26 +652,28 @@ namespace EBML{
     case 3: ret << " [Lacing: EMBL]"; break;
     case 2: ret << " [Lacing: Fixed]"; break;
     }
-    if (detail < 8){
-      ret << std::endl;
-      return ret.str();
+    ret << std::endl;
+    if (detail >= 4){
+      for (uint32_t frameNo = 0; frameNo < getFrameCount(); ++frameNo){
+        const char *payDat = getFrameData(frameNo);
+        const uint64_t payLen = getFrameSize(frameNo);
+        ret << std::dec << std::string(indent + 4, ' ') << "Frame " << (frameNo+1) << " (" << payLen << "b):";
+        if (!payDat || !payLen || detail < 6){
+          ret << std::endl;
+          continue;
+        }
+        for (uint64_t i = 0; i < payLen; ++i){
+          if ((i % 32) == 0){ret << std::endl << std::string(indent + 6, ' ');}
+          ret << std::hex << std::setw(2) << std::setfill('0') << (unsigned int)payDat[i];
+        }
+        ret << std::endl;
+      }
     }
-    ret << ":";
     if (detail >= 10){
       uint32_t extraStuff = (UniInt::readSize(getPayload()) + 3);
       const char *payDat = getPayload() + extraStuff;
       const uint64_t payLen = getPayloadLen() - extraStuff;
-      ret << std::endl << std::dec << std::string(indent + 4, ' ') << "Raw data:";
-      for (uint64_t i = 0; i < payLen; ++i){
-        if ((i % 32) == 0){ret << std::endl << std::string(indent + 6, ' ');}
-        ret << std::hex << std::setw(2) << std::setfill('0') << (unsigned int)payDat[i];
-      }
-    }
-    for (uint32_t frameNo = 0; frameNo < getFrameCount(); ++frameNo){
-      const char *payDat = getFrameData(frameNo);
-      const uint64_t payLen = getFrameSize(frameNo);
-      ret << std::endl << std::dec << std::string(indent + 4, ' ') << "Frame " << (frameNo+1) << " (" << payLen << "b):";
-      if (!payDat || !payLen){continue;}
+      ret << std::dec << std::string(indent + 4, ' ') << "Raw data:";
       for (uint64_t i = 0; i < payLen; ++i){
         if ((i % 32) == 0){ret << std::endl << std::string(indent + 6, ' ');}
         ret << std::hex << std::setw(2) << std::setfill('0') << (unsigned int)payDat[i];
diff --git a/lib/ebml.h b/lib/ebml.h
index 1597d8a6..972fe071 100644
--- a/lib/ebml.h
+++ b/lib/ebml.h
@@ -48,6 +48,8 @@ namespace EBML{
     EID_PIXELWIDTH = 0x30,
     EID_FLAGLACING = 0x1C,
     EID_PIXELHEIGHT = 0x3A,
+    EID_DISPLAYWIDTH = 0x14B0,
+    EID_DISPLAYHEIGHT = 0x14BA,
     EID_TRACKNUMBER = 0x57,
     EID_CODECPRIVATE = 0x23A2,
     EID_LANGUAGE = 0x2B59C,
@@ -72,6 +74,9 @@ namespace EBML{
     EID_CUETRACKPOSITIONS = 0x37,
     EID_CUETIME = 0x33,
     EID_CUEPOINT = 0x3B,
+    EID_TAGS = 0x254c367,
+    EID_CODECDELAY = 0x16AA,
+    EID_SEEKPREROLL = 0x16BB,
     EID_UNKNOWN = 0
   };
 
diff --git a/src/analysers/analyser_ebml.cpp b/src/analysers/analyser_ebml.cpp
index 22e183ee..e643caba 100644
--- a/src/analysers/analyser_ebml.cpp
+++ b/src/analysers/analyser_ebml.cpp
@@ -8,6 +8,8 @@ void AnalyserEBML::init(Util::Config &conf){
 
 AnalyserEBML::AnalyserEBML(Util::Config &conf) : Analyser(conf){
   curPos = prePos = 0;
+  lastSeekId = 0;
+  lastSeekPos = 0;
 }
 
 bool AnalyserEBML::parsePacket(){
@@ -15,11 +17,18 @@ bool AnalyserEBML::parsePacket(){
   // Read in smart bursts until we have enough data
   while (isOpen() && dataBuffer.size() < neededBytes()){
     uint64_t needed = neededBytes();
+    if (needed > 1024*1024){
+      dataBuffer.erase(0, 1);
+      continue;
+    }
     dataBuffer.reserve(needed);
     for (uint64_t i = dataBuffer.size(); i < needed; ++i){
       dataBuffer += std::cin.get();
       ++curPos;
-      if (!std::cin.good()){dataBuffer.erase(dataBuffer.size() - 1, 1);}
+      if (!std::cin.good()){
+        dataBuffer.erase(dataBuffer.size() - 1, 1);
+        return false;
+      }
     }
   }
 
@@ -28,6 +37,33 @@ bool AnalyserEBML::parsePacket(){
   EBML::Element E(dataBuffer.data(), true);
   HIGH_MSG("Read an element at position %d", prePos);
   if (detail >= 2){std::cout << E.toPrettyString(depthStash.size() * 2, detail);}
+  switch (E.getID()){
+    case EBML::EID_SEGMENT:
+      segmentOffset = prePos + E.getHeaderLen();
+      std::cout << "[OFFSET INFORMATION] Segment offset is " << segmentOffset << std::endl;
+      break;
+    case EBML::EID_CLUSTER:
+      std::cout << "[OFFSET INFORMATION] Cluster at " << (prePos-segmentOffset) << std::endl;
+      break;
+    case EBML::EID_SEEKID:
+      lastSeekId = E.getValUInt();
+      break;
+    case EBML::EID_SEEKPOSITION:
+      lastSeekPos = E.getValUInt();
+      break;
+    case EBML::EID_INFO:
+    case EBML::EID_TRACKS:
+    case EBML::EID_TAGS:
+    case EBML::EID_CUES:
+      {
+        uint32_t sID = E.getID();
+        std::cout << "Encountered " << sID << std::endl;
+        if (seekChecks.count(sID)){
+          std::cout << "[OFFSET INFORMATION] Segment " << EBML::Element::getIDString(sID) << " is at " << prePos << ", expected was " << seekChecks[sID] << std::endl;
+        }
+      }
+      break;
+  }
   if (depthStash.size()){
     depthStash.front() -= E.getOuterLen();
   }
@@ -36,6 +72,25 @@ bool AnalyserEBML::parsePacket(){
   }
   while (depthStash.size() && !depthStash.front()){
     depthStash.pop_front();
+    if (lastSeekId){
+      if (lastSeekId > 0xFFFFFF){
+        lastSeekId &= 0xFFFFFFF;
+      }else{
+        if (lastSeekId > 0xFFFF){
+          lastSeekId &= 0x1FFFFF;
+        }else{
+          if (lastSeekId > 0xFF){
+            lastSeekId &= 0x3FFF;
+          }else{
+            lastSeekId &= 0x7F;
+          }
+        }
+      }
+      seekChecks[lastSeekId] = segmentOffset+lastSeekPos;
+      std::cout << "[OFFSET INFORMATION] Segment offset for " << EBML::Element::getIDString(lastSeekId) << " (" << lastSeekId << ") is " << (segmentOffset+lastSeekPos) << std::endl;
+      lastSeekId = 0;
+      lastSeekPos = 0;
+    }
   }
   ///\TODO update mediaTime with the current timestamp
   dataBuffer.erase(0, E.getOuterLen());
diff --git a/src/analysers/analyser_ebml.h b/src/analysers/analyser_ebml.h
index ba5ac9c4..4934204e 100644
--- a/src/analysers/analyser_ebml.h
+++ b/src/analysers/analyser_ebml.h
@@ -12,6 +12,10 @@ private:
   std::string dataBuffer;
   uint64_t curPos;
   uint64_t prePos;
+  uint64_t segmentOffset;
+  uint32_t lastSeekId;
+  uint64_t lastSeekPos;
+  std::map<uint32_t, uint64_t> seekChecks;
   std::deque<uint64_t> depthStash;///<Contains bytes to read to go up a level in the element depth.
 };
 
diff --git a/src/input/input_ebml.cpp b/src/input/input_ebml.cpp
index 85c3ba2e..b7b88fd6 100644
--- a/src/input/input_ebml.cpp
+++ b/src/input/input_ebml.cpp
@@ -4,9 +4,14 @@
 #include <mist/bitfields.h>
 
 namespace Mist{
+
+  uint16_t maxEBMLFrameOffset = 0;
+  bool frameOffsetKnown = false;
+
   InputEBML::InputEBML(Util::Config *cfg) : Input(cfg){
+    timeScale = 1.0;
     capa["name"] = "EBML";
-    capa["desc"] = "Allows loading MKV, MKA, MK3D, MKS and WebM files for Video on Demand.";
+    capa["desc"] = "Allows loading MKV, MKA, MK3D, MKS and WebM files for Video on Demand, or accepts live streams in those formats over standard input.";
     capa["source_match"].append("/*.mkv");
     capa["source_match"].append("/*.mka");
     capa["source_match"].append("/*.mk3d");
@@ -17,6 +22,7 @@ namespace Mist{
     capa["codecs"].append("HEVC");
     capa["codecs"].append("VP8");
     capa["codecs"].append("VP9");
+    capa["codecs"].append("AV1");
     capa["codecs"].append("opus");
     capa["codecs"].append("vorbis");
     capa["codecs"].append("theora");
@@ -29,16 +35,40 @@ namespace Mist{
     capa["codecs"].append("MP3");
     capa["codecs"].append("AC3");
     capa["codecs"].append("FLOAT");
+    capa["codecs"].append("JSON");
+    capa["codecs"].append("subtitle");
     lastClusterBPos = 0;
     lastClusterTime = 0;
     bufferedPacks = 0;
   }
 
-  bool InputEBML::checkArguments(){
-    if (config->getString("input") == "-"){
-      std::cerr << "Input from stdin not yet supported" << std::endl;
-      return false;
+  std::string ASStoSRT(const char * ptr, uint32_t len){
+    uint16_t commas = 0;
+    uint16_t brackets = 0;
+    std::string tmpStr;
+    tmpStr.reserve(len);
+    for (uint32_t i = 0; i < len; ++i){
+      //Skip everything until the 8th comma
+      if (commas < 8){
+        if (ptr[i] == ','){commas++;}
+        continue;
+      }
+      if (ptr[i] == '{'){brackets++; continue;}
+      if (ptr[i] == '}'){brackets--; continue;}
+      if (!brackets){
+        if (ptr[i] == '\\' && i < len-1 && (ptr[i+1] == 'N' || ptr[i+1] == 'n')){
+          tmpStr += '\n';
+          ++i;
+          continue;
+        }
+        tmpStr += ptr[i];
+      }
     }
+    return tmpStr;
+  }
+
+
+  bool InputEBML::checkArguments(){
     if (!config->getString("streamname").size()){
       if (config->getString("output") == "-"){
         std::cerr << "Output to stdout not yet supported" << std::endl;
@@ -53,10 +83,23 @@ namespace Mist{
     return true;
   }
 
+  bool InputEBML::needsLock() {
+    //Standard input requires no lock, everything else does.
+    if (config->getString("input") != "-"){
+      return true;
+    }else{
+      return false;
+    }
+  }
+
   bool InputEBML::preRun(){
-    // open File
-    inFile = fopen(config->getString("input").c_str(), "r");
-    if (!inFile){return false;}
+    if (config->getString("input") == "-"){
+      inFile = stdin;
+    }else{
+      // open File
+      inFile = fopen(config->getString("input").c_str(), "r");
+      if (!inFile){return false;}
+    }
     return true;
   }
 
@@ -67,7 +110,10 @@ namespace Mist{
     while (ptr.size() < needed){
       if (!ptr.allocate(needed)){return false;}
       if (!fread(ptr + ptr.size(), needed - ptr.size(), 1, inFile)){
-        FAIL_MSG("Could not read more data!");
+        //We assume if there is no current data buffered, that we are at EOF and don't print a warning
+        if (ptr.size()){
+          FAIL_MSG("Could not read more data! (have %lu, need %lu)", ptr.size(), needed);
+        }
         return false;
       }
       ptr.size() = needed;
@@ -81,8 +127,18 @@ namespace Mist{
       }
     }
     EBML::Element E(ptr);
-    if (E.getID() == EBML::EID_CLUSTER){lastClusterBPos = Util::ftell(inFile);}
-    if (E.getID() == EBML::EID_TIMECODE){lastClusterTime = E.getValUInt();}
+    if (E.getID() == EBML::EID_CLUSTER){
+      if (inFile == stdin){
+        lastClusterBPos = 0;
+      }else{
+        lastClusterBPos = Util::ftell(inFile);
+      }
+      DONTEVEN_MSG("Found a cluster at position %llu", lastClusterBPos);
+    }
+    if (E.getID() == EBML::EID_TIMECODE){
+      lastClusterTime = E.getValUInt();
+      DONTEVEN_MSG("Cluster time %llu ms", lastClusterTime);
+    }
     return true;
   }
 
@@ -95,6 +151,13 @@ namespace Mist{
         swapEndianness.insert(it->first);
       }
     }
+    if (myMeta.inputLocalVars.isMember("timescale")){
+        timeScale = ((double)myMeta.inputLocalVars["timescale"].asInt()) / 1000000.0;
+    }
+    if (myMeta.inputLocalVars.isMember("maxframeoffset")){
+      maxEBMLFrameOffset = myMeta.inputLocalVars["maxframeoffset"].asInt();
+      frameOffsetKnown = true;
+    }
     return true;
   }
 
@@ -130,6 +193,10 @@ namespace Mist{
           tmpElem = E.findChild(EBML::EID_CODECPRIVATE);
           if (tmpElem){init = tmpElem.getValString();}
         }
+        if (codec == "V_AV1"){
+          trueCodec = "AV1";
+          trueType = "video";
+        }
         if (codec == "V_VP9"){
           trueCodec = "VP9";
           trueType = "video";
@@ -190,6 +257,20 @@ namespace Mist{
           trueCodec = "FLOAT";
           trueType = "audio";
         }
+        if (codec == "M_JSON"){
+          trueCodec = "JSON";
+          trueType = "meta";
+        }
+        if (codec == "S_TEXT/UTF8"){
+          trueCodec = "subtitle";
+          trueType = "meta";
+        }
+        if (codec == "S_TEXT/ASS" || codec == "S_TEXT/SSA"){
+          trueCodec = "subtitle";
+          trueType = "meta";
+          tmpElem = E.findChild(EBML::EID_CODECPRIVATE);
+          if (tmpElem){init = tmpElem.getValString();}
+        }
         if (codec == "A_MS/ACM"){
           tmpElem = E.findChild(EBML::EID_CODECPRIVATE);
           if (tmpElem){
@@ -247,6 +328,13 @@ namespace Mist{
         }
         INFO_MSG("Detected track: %s", Trk.getIdentifier().c_str());
       }
+      if (E.getID() == EBML::EID_TIMECODESCALE){
+        uint64_t timeScaleVal = E.getValUInt();
+        myMeta.inputLocalVars["timescale"] = (long long)timeScaleVal;
+        timeScale = ((double)timeScaleVal) / 1000000.0;
+      }
+      //Live streams stop parsing the header as soon as the first Cluster is encountered
+      if (E.getID() == EBML::EID_CLUSTER && !needsLock()){return true;}
       if (E.getType() == EBML::ELEM_BLOCK){
         EBML::Block B(ptr);
         uint64_t tNum = B.getTrackNum();
@@ -254,21 +342,32 @@ namespace Mist{
         trackPredictor &TP = packBuf[tNum];
         DTSC::Track &Trk = myMeta.tracks[tNum];
         bool isVideo = (Trk.type == "video");
+        bool isAudio = (Trk.type == "audio");
+        bool isASS = (Trk.codec == "subtitle" && Trk.init.size());
         for (uint64_t frameNo = 0; frameNo < B.getFrameCount(); ++frameNo){
           if (frameNo){
             if (Trk.codec == "AAC"){
-              newTime += 1000000 / Trk.rate;//assume ~1000 samples per frame
+              newTime += (1000000 / Trk.rate)/timeScale;//assume ~1000 samples per frame
+            } else if (Trk.codec == "MP3"){
+              newTime += (1152000 / Trk.rate)/timeScale;//1152 samples per frame
             }else{
+              newTime += 1/timeScale;
               ERROR_MSG("Unknown frame duration for codec %s - timestamps WILL be wrong!", Trk.codec.c_str());
             }
           }
           uint32_t frameSize = B.getFrameSize(frameNo);
+          if (isASS){
+            char * ptr = (char *)B.getFrameData(frameNo);
+            std::string assStr = ASStoSRT(ptr, frameSize);
+            frameSize = assStr.size();
+          }
           if (frameSize){
-            TP.add(newTime, 0, tNum, frameSize, lastClusterBPos,
-                 B.isKeyframe() && isVideo);
+            TP.add(newTime*timeScale, 0, tNum, frameSize, lastClusterBPos,
+                 B.isKeyframe() && !isAudio, isVideo);
           }
         }
-        while (TP.hasPackets()){
+        while (TP.hasPackets() && (isVideo || frameOffsetKnown)){
+          frameOffsetKnown = true;
           packetData &C = TP.getPacketData(isVideo);
           myMeta.update(C.time, C.offset, C.track, C.dsize, C.bpos, C.key);
           TP.remove();
@@ -288,6 +387,8 @@ namespace Mist{
       }
     }
 
+    myMeta.inputLocalVars["maxframeoffset"] = (long long)maxEBMLFrameOffset;
+
     bench = Util::getMicros(bench);
     INFO_MSG("Header generated in %llu ms", bench / 1000);
     packBuf.clear();
@@ -385,19 +486,31 @@ namespace Mist{
     trackPredictor &TP = packBuf[tNum];
     DTSC::Track & Trk = myMeta.tracks[tNum];
     bool isVideo = (Trk.type == "video");
+    bool isAudio = (Trk.type == "audio");
+    bool isASS = (Trk.codec == "subtitle" && Trk.init.size());
     for (uint64_t frameNo = 0; frameNo < B.getFrameCount(); ++frameNo){
       if (frameNo){
         if (Trk.codec == "AAC"){
-          newTime += 1000000 / Trk.rate;//assume ~1000 samples per frame
+          newTime += (1000000 / Trk.rate)/timeScale;//assume ~1000 samples per frame
+        } else if (Trk.codec == "MP3"){
+          newTime += (1152000 / Trk.rate)/timeScale;//1152 samples per frame
         }else{
           ERROR_MSG("Unknown frame duration for codec %s - timestamps WILL be wrong!", Trk.codec.c_str());
         }
       }
       uint32_t frameSize = B.getFrameSize(frameNo);
       if (frameSize){
-        TP.add(newTime, 0, tNum, frameSize, lastClusterBPos,
-          B.isKeyframe() && isVideo, (void *)B.getFrameData(frameNo));
-        ++bufferedPacks;
+        char * ptr = (char *)B.getFrameData(frameNo);
+        if (isASS){
+          std::string assStr = ASStoSRT(ptr, frameSize);
+          frameSize = assStr.size();
+          memcpy(ptr, assStr.data(), frameSize);
+        }
+        if (frameSize){
+          TP.add(newTime*timeScale, 0, tNum, frameSize, lastClusterBPos,
+          B.isKeyframe() && !isAudio, isVideo, (void *)ptr);
+          ++bufferedPacks;
+        }
       }
     }
     if (TP.hasPackets()){
@@ -415,10 +528,29 @@ namespace Mist{
   void InputEBML::seek(int seekTime){
     packBuf.clear();
     bufferedPacks = 0;
-    DTSC::Track Trk = myMeta.tracks[getMainSelectedTrack()];
+    uint64_t mainTrack = getMainSelectedTrack();
+    DTSC::Track Trk = myMeta.tracks[mainTrack];
+    bool isVideo = (Trk.type == "video");
     uint64_t seekPos = Trk.keys[0].getBpos();
+    // Replay the parts of the previous keyframe, so the timestaps match up
+    uint64_t partCount = 0;
     for (unsigned int i = 0; i < Trk.keys.size(); i++){
-      if (Trk.keys[i].getTime() > seekTime){break;}
+      if (Trk.keys[i].getTime() > seekTime){
+        if (i > 1){
+          partCount -= Trk.keys[i-1].getParts() + Trk.keys[i-2].getParts();
+          uint64_t partEnd = partCount + Trk.keys[i-2].getParts();
+          uint64_t partTime = Trk.keys[i-2].getTime();
+          for (uint64_t prt = partCount; prt < partEnd; ++prt){
+            INSANE_MSG("Replay part %llu, timestamp: %llu+%llu", prt, partTime, Trk.parts[prt].getOffset());
+            packBuf[mainTrack].add(partTime, Trk.parts[prt].getOffset(), mainTrack, 0, 0, false, isVideo, (void *)0);
+            packBuf[mainTrack].remove();
+            partTime += Trk.parts[prt].getDuration();
+          }
+        }
+        break;
+      }
+      partCount += Trk.keys[i].getParts();
+      DONTEVEN_MSG("Seeking to %lu, found %llu...", seekTime, Trk.keys[i].getTime());
       seekPos = Trk.keys[i].getBpos();
     }
     Util::fseek(inFile, seekPos, SEEK_SET);
diff --git a/src/input/input_ebml.h b/src/input/input_ebml.h
index 7b5d725a..10f3a700 100644
--- a/src/input/input_ebml.h
+++ b/src/input/input_ebml.h
@@ -3,6 +3,11 @@
 
 namespace Mist{
 
+
+  extern uint16_t maxEBMLFrameOffset;
+  extern bool frameOffsetKnown;
+#define PKT_COUNT 64
+
   class packetData{
     public:
     uint64_t time, offset, track, dsize, bpos;
@@ -33,7 +38,7 @@ namespace Mist{
   };
   class trackPredictor{
     public:
-      packetData pkts[16];
+      packetData pkts[PKT_COUNT];
       uint16_t smallestFrame;
       uint64_t lastTime;
       uint64_t ctr;
@@ -48,31 +53,49 @@ namespace Mist{
         if (finished){
           return (ctr - rem > 0);
         }else{
-          return (ctr - rem > 8);
+          return (ctr - rem > 12);
         }
       }
       packetData & getPacketData(bool mustCalcOffsets){
-        packetData & p = pkts[rem % 16];
-        if (rem && mustCalcOffsets){
-          if (p.time > lastTime + smallestFrame){
-            while (p.time - (lastTime + smallestFrame) > smallestFrame * 8){
-              lastTime += smallestFrame;
-            }
-            p.offset = p.time - (lastTime + smallestFrame);
-            p.time = lastTime + smallestFrame;
+        frameOffsetKnown = true;
+        //grab the next packet to output
+        packetData & p = pkts[rem % PKT_COUNT];
+        //Substract the max frame offset, so we know all offsets are positive, no matter what.
+        //if it's not the first and we're calculating offsets, see if we need an offset
+        if (!mustCalcOffsets){
+          p.time += maxEBMLFrameOffset;
+          DONTEVEN_MSG("Outputting %llu + %llu (%llu -> %llu)", p.time, maxEBMLFrameOffset, rem, rem % PKT_COUNT);
+          return p;
+        }else{
+          if (rem && !p.key){
+            p.offset = p.time + maxEBMLFrameOffset - (lastTime + smallestFrame);
+            //If we calculate an offset less than a frame away,
+            //we assume it's just time stamp drift due to lack of precision.
+            p.time = (lastTime + smallestFrame);
+          }else{
+            p.offset = maxEBMLFrameOffset;
           }
         }
         lastTime = p.time;
         return p;
       }
-      void add(uint64_t packTime, uint64_t packOffset, uint64_t packTrack, uint64_t packDataSize, uint64_t packBytePos, bool isKeyframe, void * dataPtr = 0){
-        if (ctr && ctr > rem){
-          if ((pkts[(ctr-1)%16].time < packTime - 2) && (!smallestFrame || packTime - pkts[(ctr-1)%16].time < smallestFrame)){
-            smallestFrame = packTime - pkts[(ctr-1)%16].time;
+      void add(uint64_t packTime, uint64_t packOffset, uint64_t packTrack, uint64_t packDataSize, uint64_t packBytePos, bool isKeyframe, bool isVideo, void * dataPtr = 0){
+        if (isVideo && ctr && ctr >= rem){
+          int32_t currOffset = packTime - pkts[(ctr-1)%PKT_COUNT].time;
+          if (currOffset < 0){currOffset *= -1;}
+          if (!smallestFrame || currOffset < smallestFrame){
+            smallestFrame = currOffset;
+            HIGH_MSG("Smallest frame is now %u", smallestFrame);
+          }
+          if (!frameOffsetKnown && currOffset < 8*smallestFrame && currOffset*2 > maxEBMLFrameOffset && ctr < PKT_COUNT/2){
+            maxEBMLFrameOffset = currOffset*2;
+            INFO_MSG("Max frame offset is now %u", maxEBMLFrameOffset);
           }
         }
-        pkts[ctr % 16].set(packTime, packOffset, packTrack, packDataSize, packBytePos, isKeyframe, dataPtr);
+        DONTEVEN_MSG("Ingesting %llu (%llu -> %llu)", packTime, ctr, ctr % PKT_COUNT);
+        pkts[ctr % PKT_COUNT].set(packTime, packOffset, packTrack, packDataSize, packBytePos, isKeyframe, dataPtr);
         ++ctr;
+        if (ctr == PKT_COUNT-1){frameOffsetKnown = true;}
       }
       void remove(){
         ++rem;
@@ -83,7 +106,7 @@ namespace Mist{
   class InputEBML : public Input{
   public:
     InputEBML(Util::Config *cfg);
-
+    bool needsLock();
   protected:
     void fillPacket(packetData & C);
     bool checkArguments();
@@ -101,6 +124,12 @@ namespace Mist{
     std::map<uint64_t, trackPredictor> packBuf;
     std::set<uint64_t> swapEndianness;
     bool readExistingHeader();
+    void parseStreamHeader(){
+      readHeader();
+    }
+    bool openStreamSource(){return true;}
+    bool needHeader(){return needsLock();}
+    double timeScale;
   };
 }
 
diff --git a/src/output/output_ebml.cpp b/src/output/output_ebml.cpp
index 3f3aad96..e353202a 100644
--- a/src/output/output_ebml.cpp
+++ b/src/output/output_ebml.cpp
@@ -1,6 +1,7 @@
 #include "output_ebml.h"
 #include <mist/ebml_socketglue.h>
 #include <mist/riff.h>
+#include <mist/opus.h>
 
 namespace Mist{
   OutEBML::OutEBML(Socket::Connection &conn) : HTTPOutput(conn){
@@ -27,6 +28,7 @@ namespace Mist{
     capa["codecs"][0u][0u].append("VP9");
     capa["codecs"][0u][0u].append("theora");
     capa["codecs"][0u][0u].append("MPEG2");
+    capa["codecs"][0u][0u].append("AV1");
     capa["codecs"][0u][1u].append("AAC");
     capa["codecs"][0u][1u].append("vorbis");
     capa["codecs"][0u][1u].append("opus");
@@ -37,6 +39,7 @@ namespace Mist{
     capa["codecs"][0u][1u].append("MP3");
     capa["codecs"][0u][1u].append("FLOAT");
     capa["codecs"][0u][1u].append("AC3");
+    capa["codecs"][0u][2u].append("+JSON");
     capa["methods"][0u]["handler"] = "http";
     capa["methods"][0u]["type"] = "html5/video/webm";
     capa["methods"][0u]["priority"] = 11ll;
@@ -92,12 +95,13 @@ namespace Mist{
       currentClusterTime = thisPacket.getTime();
       if (myMeta.vod){
         //In case of VoD, clusters are aligned with the main track fragments
+        //EXCEPT when they are more than 30 seconds long, because clusters are limited to -32 to 32 seconds.
         DTSC::Track &Trk = myMeta.tracks[getMainSelectedTrack()];
         uint32_t fragIndice = Trk.timeToFragnum(currentClusterTime);
         newClusterTime = Trk.getKey(Trk.fragments[fragIndice].getNumber()).getTime() + Trk.fragments[fragIndice].getDuration();
-        //The last fragment should run until the end of time
-        if (fragIndice == Trk.fragments.size() - 1){
-          newClusterTime = 0xFFFFFFFFFFFFFFFFull;
+        //Limit clusters to 30s, and the last fragment should always be 30s, just in case.
+        if ((newClusterTime - currentClusterTime > 30000) || (fragIndice == Trk.fragments.size() - 1)){
+          newClusterTime = currentClusterTime + 30000;
         }
         EXTREME_MSG("Cluster: %llu - %llu (%lu/%lu) = %llu", currentClusterTime, newClusterTime, fragIndice, Trk.fragments.size(), clusterSize(currentClusterTime, newClusterTime));
       }else{
@@ -118,6 +122,7 @@ namespace Mist{
     if (Trk.codec == "HEVC"){return "V_MPEGH/ISO/HEVC";}
     if (Trk.codec == "VP8"){return "V_VP8";}
     if (Trk.codec == "VP9"){return "V_VP9";}
+    if (Trk.codec == "AV1"){return "V_AV1";}
     if (Trk.codec == "AAC"){return "A_AAC";}
     if (Trk.codec == "vorbis"){return "A_VORBIS";}
     if (Trk.codec == "theora"){return "V_THEORA";}
@@ -129,6 +134,7 @@ namespace Mist{
     if (Trk.codec == "ALAW"){return "A_MS/ACM";}
     if (Trk.codec == "ULAW"){return "A_MS/ACM";}
     if (Trk.codec == "FLOAT"){return "A_PCM/FLOAT/IEEE";}
+    if (Trk.codec == "JSON"){return "M_JSON";}
     return "E_UNKNOWN";
   }
 
@@ -146,10 +152,16 @@ namespace Mist{
     }else{
       if (Trk.init.size()){sendLen += EBML::sizeElemStr(EBML::EID_CODECPRIVATE, Trk.init);}
     }
+    if (Trk.codec == "opus" && Trk.init.size() > 11){
+      sendLen += EBML::sizeElemUInt(EBML::EID_CODECDELAY, Opus::getPreSkip(Trk.init.data())*1000000/48);
+      sendLen += EBML::sizeElemUInt(EBML::EID_SEEKPREROLL, 80000000);
+    }
     if (Trk.type == "video"){
       sendLen += EBML::sizeElemUInt(EBML::EID_TRACKTYPE, 1);
       subLen += EBML::sizeElemUInt(EBML::EID_PIXELWIDTH, Trk.width);
       subLen += EBML::sizeElemUInt(EBML::EID_PIXELHEIGHT, Trk.height);
+      subLen += EBML::sizeElemUInt(EBML::EID_DISPLAYWIDTH, Trk.width);
+      subLen += EBML::sizeElemUInt(EBML::EID_DISPLAYHEIGHT, Trk.height);
       sendLen += EBML::sizeElemHead(EBML::EID_VIDEO, subLen);
     }
     if (Trk.type == "audio"){
@@ -159,6 +171,9 @@ namespace Mist{
       subLen += EBML::sizeElemUInt(EBML::EID_BITDEPTH, Trk.size);
       sendLen += EBML::sizeElemHead(EBML::EID_AUDIO, subLen);
     }
+    if (Trk.type == "meta"){
+      sendLen += EBML::sizeElemUInt(EBML::EID_TRACKTYPE, 3);
+    }
     sendLen += subLen;
 
     // Now actually send.
@@ -176,11 +191,17 @@ namespace Mist{
     }else{
       if (Trk.init.size()){EBML::sendElemStr(myConn, EBML::EID_CODECPRIVATE, Trk.init);}
     }
+    if (Trk.codec == "opus"){
+      EBML::sendElemUInt(myConn, EBML::EID_CODECDELAY, Opus::getPreSkip(Trk.init.data())*1000000/48);
+      EBML::sendElemUInt(myConn, EBML::EID_SEEKPREROLL, 80000000);
+    }
     if (Trk.type == "video"){
       EBML::sendElemUInt(myConn, EBML::EID_TRACKTYPE, 1);
       EBML::sendElemHead(myConn, EBML::EID_VIDEO, subLen);
       EBML::sendElemUInt(myConn, EBML::EID_PIXELWIDTH, Trk.width);
       EBML::sendElemUInt(myConn, EBML::EID_PIXELHEIGHT, Trk.height);
+      EBML::sendElemUInt(myConn, EBML::EID_DISPLAYWIDTH, Trk.width);
+      EBML::sendElemUInt(myConn, EBML::EID_DISPLAYHEIGHT, Trk.height);
     }
     if (Trk.type == "audio"){
       EBML::sendElemUInt(myConn, EBML::EID_TRACKTYPE, 2);
@@ -189,6 +210,9 @@ namespace Mist{
       EBML::sendElemDbl(myConn, EBML::EID_SAMPLINGFREQUENCY, Trk.rate);
       EBML::sendElemUInt(myConn, EBML::EID_BITDEPTH, Trk.size);
     }
+    if (Trk.type == "meta"){
+      EBML::sendElemUInt(myConn, EBML::EID_TRACKTYPE, 3);
+    }
   }
 
   uint32_t OutEBML::sizeElemTrackEntry(const DTSC::Track &Trk){
@@ -205,10 +229,16 @@ namespace Mist{
     }else{
       if (Trk.init.size()){sendLen += EBML::sizeElemStr(EBML::EID_CODECPRIVATE, Trk.init);}
     }
+    if (Trk.codec == "opus"){
+      sendLen += EBML::sizeElemUInt(EBML::EID_CODECDELAY, Opus::getPreSkip(Trk.init.data())*1000000/48);
+      sendLen += EBML::sizeElemUInt(EBML::EID_SEEKPREROLL, 80000000);
+    }
     if (Trk.type == "video"){
       sendLen += EBML::sizeElemUInt(EBML::EID_TRACKTYPE, 1);
       subLen += EBML::sizeElemUInt(EBML::EID_PIXELWIDTH, Trk.width);
       subLen += EBML::sizeElemUInt(EBML::EID_PIXELHEIGHT, Trk.height);
+      subLen += EBML::sizeElemUInt(EBML::EID_DISPLAYWIDTH, Trk.width);
+      subLen += EBML::sizeElemUInt(EBML::EID_DISPLAYHEIGHT, Trk.height);
       sendLen += EBML::sizeElemHead(EBML::EID_VIDEO, subLen);
     }
     if (Trk.type == "audio"){
@@ -218,6 +248,9 @@ namespace Mist{
       subLen += EBML::sizeElemUInt(EBML::EID_BITDEPTH, Trk.size);
       sendLen += EBML::sizeElemHead(EBML::EID_AUDIO, subLen);
     }
+    if (Trk.type == "meta"){
+      sendLen += EBML::sizeElemUInt(EBML::EID_TRACKTYPE, 3);
+    }
     sendLen += subLen;
     return EBML::sizeElemHead(EBML::EID_TRACKENTRY, sendLen) + sendLen;
   }
@@ -257,14 +290,9 @@ namespace Mist{
     if (myMeta.vod){
       EBML::sendElemHead(myConn, EBML::EID_CUES, cuesSize);
       uint64_t tmpsegSize = infoSize + tracksSize + seekheadSize + cuesSize + EBML::sizeElemHead(EBML::EID_CUES, cuesSize);
-      uint32_t fragNo = 0;
-      for (std::deque<DTSC::Fragment>::iterator it = Trk.fragments.begin(); it != Trk.fragments.end(); ++it){
-        uint64_t clusterStart = Trk.getKey(it->getNumber()).getTime();
-        //The first fragment always starts at time 0, even if the main track does not.
-        if (!fragNo){clusterStart = 0;}
-        EBML::sendElemCuePoint(myConn, clusterStart, Trk.trackID, tmpsegSize, 0);
-        tmpsegSize += clusterSizes[fragNo];
-        ++fragNo;
+      for (std::map<uint64_t, uint64_t>::iterator it = clusterSizes.begin(); it != clusterSizes.end(); ++it){
+        EBML::sendElemCuePoint(myConn, it->first, Trk.trackID, tmpsegSize, 0);
+        tmpsegSize += it->second;
       }
     }
     sentHeader = true;
@@ -292,10 +320,10 @@ namespace Mist{
     for (std::map<uint64_t, uint64_t>::iterator it = clusterSizes.begin(); it != clusterSizes.end(); ++it){
       VERYHIGH_MSG("Cluster %llu (%llu bytes) -> %llu to go", it->first, it->second, startPos);
       if (startPos < it->second){
-        HIGH_MSG("Seek to fragment %llu (%llu ms)", it->first, Trk.getKey(Trk.fragments[it->first].getNumber()).getTime());
+        HIGH_MSG("Seek to fragment at %llu ms", it->first);
         myConn.skipBytes(startPos);
-        seek(Trk.getKey(Trk.fragments[it->first].getNumber()).getTime());
-        newClusterTime = Trk.getKey(Trk.fragments[it->first].getNumber()).getTime();
+        seek(it->first);
+        newClusterTime = it->first;
         return;
       }
       startPos -= it->second;
@@ -445,10 +473,17 @@ namespace Mist{
       uint64_t clusterEnd = clusterStart + it->getDuration();
       //The first fragment always starts at time 0, even if the main track does not.
       if (!fragNo){clusterStart = 0;}
-      //The last fragment always ends at the end, even if the main track does not.
-      if (fragNo == Trk.fragments.size() - 1){clusterEnd = 0xFFFFFFFFFFFFFFFFull;}
-      uint64_t cSize = clusterSize(clusterStart, clusterEnd);
-      clusterSizes[fragNo] = cSize + EBML::sizeElemHead(EBML::EID_CLUSTER, cSize);
+      uint64_t clusterTmpEnd = clusterEnd;
+      do {
+        clusterTmpEnd = clusterEnd;
+        //The last fragment always ends at the end, even if the main track does not.
+        if (fragNo == Trk.fragments.size() - 1){clusterTmpEnd = clusterStart + 30000;}
+        //Limit clusters to 30 seconds.
+        if (clusterTmpEnd - clusterStart > 30000){clusterTmpEnd = clusterStart + 30000;}
+        uint64_t cSize = clusterSize(clusterStart, clusterTmpEnd);
+        clusterSizes[clusterStart] = cSize + EBML::sizeElemHead(EBML::EID_CLUSTER, cSize);
+        clusterStart = clusterTmpEnd;//Continue at the end of this cluster, if continuing.
+      }while(clusterTmpEnd < clusterEnd);
       ++fragNo;
     }
     //Calculating Cues size
@@ -461,14 +496,9 @@ namespace Mist{
       oldcuesSize = cuesSize;
       segmentSize = infoSize + tracksSize + seekheadSize + cuesSize + EBML::sizeElemHead(EBML::EID_CUES, cuesSize);
       uint32_t cuesInside = 0;
-      fragNo = 0;
-      for (std::deque<DTSC::Fragment>::iterator it = Trk.fragments.begin(); it != Trk.fragments.end(); ++it){
-        uint64_t clusterStart = Trk.getKey(it->getNumber()).getTime();
-        //The first fragment always starts at time 0, even if the main track does not.
-        if (!fragNo){clusterStart = 0;}
-        cuesInside += EBML::sizeElemCuePoint(clusterStart, Trk.trackID, segmentSize, 0);
-        segmentSize += clusterSizes[fragNo];
-        ++fragNo;
+      for (std::map<uint64_t, uint64_t>::iterator it = clusterSizes.begin(); it != clusterSizes.end(); ++it){
+        cuesInside += EBML::sizeElemCuePoint(it->first, Trk.trackID, segmentSize, 0);
+        segmentSize += it->second;
       }
       cuesSize = cuesInside;
     }while(cuesSize != oldcuesSize);
diff --git a/src/output/output_ebml.h b/src/output/output_ebml.h
index 5e977be2..37242bdf 100644
--- a/src/output/output_ebml.h
+++ b/src/output/output_ebml.h
@@ -25,7 +25,7 @@ namespace Mist{
     uint32_t cuesSize;//size of Cues (excl. header)
     uint32_t seekheadSize;//size of SeekHead (incl. header)
     uint32_t seekSize;//size of contents of SeekHead (excl. header)
-    std::map<uint64_t, uint64_t> clusterSizes;//sizes of Clusters (incl. header)
+    std::map<uint64_t, uint64_t> clusterSizes;//sizes of Clusters by start time (incl. header)
     void byteSeek(uint64_t startPos);
   };
 }