diff --git a/cfg/encoder_lowdelay_P_vtm.cfg b/cfg/encoder_lowdelay_P_vtm.cfg
index d2001e4a5691fac2b250977f20b840615d27b24d..9ec4a5d13cf00e9d8e1da1bfc47971a6f37dc64c 100644
--- a/cfg/encoder_lowdelay_P_vtm.cfg
+++ b/cfg/encoder_lowdelay_P_vtm.cfg
@@ -56,6 +56,11 @@ TransformSkipFast             : 1           # Fast Transform skipping (0: OFF, 1
 TransformSkipLog2MaxSize      : 5
 SAOLcuBoundary                : 0           # SAOLcuBoundary using non-deblocked pixels (0: OFF, 1: ON)
 
+#=========== TemporalFilter =================
+TemporalFilter                : 0           # Enable/disable GOP Based Temporal Filter
+TemporalFilterFutureReference : 0           # Enable/disable reading future frames
+TemporalFilterStrengthFrame4  : 0.4         # Enable filter at every 4th frame with strength
+
 #============ Slices ================
 SliceMode                : 0                # 0: Disable all slice options.
                                             # 1: Enforce maximum number of LCU in an slice,
diff --git a/cfg/encoder_lowdelay_vtm.cfg b/cfg/encoder_lowdelay_vtm.cfg
index 0125529b61d6ecedf749e5189460431c2fa1f420..c8899e2e74339001db4bce7515fd2c23997dd3a0 100644
--- a/cfg/encoder_lowdelay_vtm.cfg
+++ b/cfg/encoder_lowdelay_vtm.cfg
@@ -56,6 +56,11 @@ TransformSkipFast             : 1           # Fast Transform skipping (0: OFF, 1
 TransformSkipLog2MaxSize      : 5
 SAOLcuBoundary                : 0           # SAOLcuBoundary using non-deblocked pixels (0: OFF, 1: ON)
 
+#=========== TemporalFilter =================
+TemporalFilter                : 0           # Enable/disable GOP Based Temporal Filter
+TemporalFilterFutureReference : 0           # Enable/disable reading future frames
+TemporalFilterStrengthFrame4  : 0.4         # Enable filter at every 4th frame with strength
+
 #============ Slices ================
 SliceMode                : 0                # 0: Disable all slice options.
                                             # 1: Enforce maximum number of LCU in an slice,
diff --git a/cfg/encoder_randomaccess_vtm.cfg b/cfg/encoder_randomaccess_vtm.cfg
index 9548bc8e66469f0d5df536deb5ef98aca23a73b1..db7fb3002833aad1c47835327a046115a23ddd31 100644
--- a/cfg/encoder_randomaccess_vtm.cfg
+++ b/cfg/encoder_randomaccess_vtm.cfg
@@ -70,6 +70,12 @@ TransformSkipFast             : 1           # Fast Transform skipping (0: OFF, 1
 TransformSkipLog2MaxSize      : 5
 SAOLcuBoundary                : 0           # SAOLcuBoundary using non-deblocked pixels (0: OFF, 1: ON)
 
+#=========== TemporalFilter =================
+TemporalFilter                : 0           # Enable/disable GOP Based Temporal Filter
+TemporalFilterFutureReference : 1           # Enable/disable reading future frames
+TemporalFilterStrengthFrame8  : 0.95        # Enable filter at every 8th frame with given strength
+TemporalFilterStrengthFrame16 : 1.5         # Enable filter at every 16th frame with given strength, longer intervals has higher priority
+
 #============ Slices ================
 SliceMode                : 0                # 0: Disable all slice options.
                                             # 1: Enforce maximum number of LCU in an slice,
diff --git a/doc/software-manual.tex b/doc/software-manual.tex
old mode 100755
new mode 100644
index 820df1b14e1df0497b45e0d89078b692af8037b6..db557a0b809463dd14c99ac25c6c777c8e84ae11
--- a/doc/software-manual.tex
+++ b/doc/software-manual.tex
@@ -890,6 +890,32 @@ Picture output options: output upscaled (2), decoded but in full resolution buff
 
 \end{OptionTableNoShorthand}
 
+%%
+%% GOP based temporal filter parameters
+%%
+
+\begin{OptionTableNoShorthand}{GOP based temporal filter paramters}{tab:gop-based-temporal-filter}
+
+\Option{TemporalFilter} &
+%\ShortOption{\None} &
+\Default{false} &
+Enables or disables GOP based temporal filter.
+\\
+\Option{TemporalFilterFutureReference} &
+%\ShortOption{\None} &
+\Default{true} &
+Enables or disable referencing future frames in the GOP based temporal filter. Can be used to disable future referencing for
+low delay configurations.
+\\
+\Option{TemporalFilterStrengthFrame*} &
+%\ShortOption{\None} &
+\Default{} &
+Strength for every * frame in GOP based temporal filter, where * is an integer. E.g. --TemporalFilterStrengthFrame8 0.95 will
+enable GOP based temporal filter at every 8th frame with strength 0.95. Longer intervals overrides shorter when there are
+multiple matches.
+\\
+\end{OptionTableNoShorthand}
+
 %%
 %% profile, level and conformance options
 %%
diff --git a/source/App/EncoderApp/EncApp.cpp b/source/App/EncoderApp/EncApp.cpp
index 8646b6e48aebee4a43add86685dce75ede798091..7c8b5e3ec1a9ccd11e90e310060707973ea4b328 100644
--- a/source/App/EncoderApp/EncApp.cpp
+++ b/source/App/EncoderApp/EncApp.cpp
@@ -48,6 +48,10 @@
 #include "AppEncHelper360/TExt360AppEncTop.h"
 #endif
 
+#if JVET_O0549_ENCODER_ONLY_FILTER
+#include "EncoderLib/EncTemporalFilter.h"
+#endif
+
 using namespace std;
 
 //! \ingroup EncoderApp
@@ -692,6 +696,9 @@ void EncApp::xInitLibCfg()
   m_cEncLib.setCropOffsetBottom                                  (m_cropOffsetBottom);
   m_cEncLib.setCalculateHdrMetrics                               (m_calculateHdrMetrics);
 #endif
+#if JVET_O0549_ENCODER_ONLY_FILTER
+  m_cEncLib.setGopBasedTemporalFilterEnabled(m_gopBasedTemporalFilterEnabled);
+#endif
 }
 
 void EncApp::xCreateLib( std::list<PelUnitBuf*>& recBufList
@@ -792,6 +799,17 @@ void EncApp::encode()
   TExt360AppEncTop           ext360(*this, m_cEncLib.getGOPEncoder()->getExt360Data(), *(m_cEncLib.getGOPEncoder()), orgPic);
 #endif
 
+#if JVET_O0549_ENCODER_ONLY_FILTER
+  EncTemporalFilter temporalFilter;
+  if (m_gopBasedTemporalFilterEnabled)
+  {
+    temporalFilter.init(m_FrameSkip, m_inputBitDepth, m_MSBExtendedBitDepth, m_internalBitDepth, m_iSourceWidth, m_iSourceHeight,
+      m_aiPad, m_bClipInputVideoToRec709Range, m_inputFileName, m_chromaFormatIDC,
+      m_inputColourSpaceConvert, m_iQP, m_gopBasedTemporalFilterStrengths,
+      m_gopBasedTemporalFilterFutureReference);
+  }
+#endif
+
   while ( !bEos )
   {
     // read input YUV file
@@ -808,6 +826,13 @@ void EncApp::encode()
     m_cVideoIOYuvInputFile.read( orgPic, trueOrgPic, ipCSC, m_aiPad, m_InputChromaFormatIDC, m_bClipInputVideoToRec709Range );
 #endif
 
+#if JVET_O0549_ENCODER_ONLY_FILTER
+    if (m_gopBasedTemporalFilterEnabled)
+    {
+      temporalFilter.filter(&orgPic, m_iFrameRcvd);
+    }
+#endif
+
     // increase number of received frames
     m_iFrameRcvd++;
 
diff --git a/source/App/EncoderApp/EncAppCfg.cpp b/source/App/EncoderApp/EncAppCfg.cpp
index 56a3813a0871afd3bd6f68222f68830c60b633f8..ae932b1b23687763ac4241fa36011265d20497b5 100644
--- a/source/App/EncoderApp/EncAppCfg.cpp
+++ b/source/App/EncoderApp/EncAppCfg.cpp
@@ -605,6 +605,27 @@ static inline istream& operator >> (std::istream &in, EncAppCfg::OptionalValue<T
 }
 #endif
 
+#if JVET_O0549_ENCODER_ONLY_FILTER
+template <class T1, class T2>
+static inline istream& operator >> (std::istream& in, std::map<T1, T2>& map)
+{
+  T1 key;
+  T2 value;
+  try
+  {
+    in >> key;
+    in >> value;
+  }
+  catch (...)
+  {
+    in.setstate(ios::failbit);
+  }
+
+  map[key] = value;
+  return in;
+}
+#endif
+
 static void
 automaticallySelectRExtProfile(const bool bUsingGeneralRExtTools,
                                const bool bUsingChromaQPAdjustment,
@@ -1422,6 +1443,14 @@ bool EncAppCfg::parseCfg( int argc, char* argv[] )
 #endif
     ;
 
+#if JVET_O0549_ENCODER_ONLY_FILTER
+  opts.addOptions()
+    ("TemporalFilter",                                m_gopBasedTemporalFilterEnabled,          false,            "Enable GOP based temporal filter. Disabled per default")
+    ("TemporalFilterFutureReference",                 m_gopBasedTemporalFilterFutureReference,   true,            "Enable referencing of future frames in the GOP based temporal filter. This is typically disabled for Low Delay configurations.")
+    ("TemporalFilterStrengthFrame*",                  m_gopBasedTemporalFilterStrengths, std::map<int, double>(), "Strength for every * frame in GOP based temporal filter, where * is an integer."
+                                                                                                                  " E.g. --TemporalFilterStrengthFrame8 0.95 will enable GOP based temporal filter at every 8th frame with strength 0.95");
+#endif
+
 #if EXTENSION_360_VIDEO
   TExt360AppEncCfg::TExt360AppEncCfgContext ext360CfgContext;
   m_ext360.addOptions(opts, ext360CfgContext);
@@ -3474,6 +3503,12 @@ bool EncAppCfg::xCheckParameter()
   xConfirmPara( m_decodeBitstreams[0] == m_bitstreamFileName, "Debug bitstream and the output bitstream cannot be equal.\n" );
   xConfirmPara( m_decodeBitstreams[1] == m_bitstreamFileName, "Decode2 bitstream and the output bitstream cannot be equal.\n" );
   xConfirmPara(unsigned(m_LMChroma) > 1, "LMMode exceeds range (0 to 1)");
+#if JVET_O0549_ENCODER_ONLY_FILTER
+  if (m_gopBasedTemporalFilterEnabled)
+  {
+    xConfirmPara(m_temporalSubsampleRatio != 1, "GOP Based Temporal Filter only support Temporal sub-sample ratio 1");
+  }
+#endif
 #if EXTENSION_360_VIDEO
   check_failed |= m_ext360.verifyParameters();
 #endif
@@ -3837,7 +3872,9 @@ void EncAppCfg::xPrintParameter()
 #if EXTENSION_360_VIDEO
   m_ext360.outputConfigurationSummary();
 #endif
-
+#if JVET_O0549_ENCODER_ONLY_FILTER
+  msg(VERBOSE, "TemporalFilter:%d ", m_gopBasedTemporalFilterEnabled);
+#endif
   msg( VERBOSE, "\n\n");
 
   msg( NOTICE, "\n");
diff --git a/source/App/EncoderApp/EncAppCfg.h b/source/App/EncoderApp/EncAppCfg.h
index 4c67d85fade0bb654ffee889981ea7a14a0aa670..a1aa89cba1fbadf294983666831a90b978bd2a44 100644
--- a/source/App/EncoderApp/EncAppCfg.h
+++ b/source/App/EncoderApp/EncAppCfg.h
@@ -39,6 +39,9 @@
 #define __ENCAPPCFG__
 
 #include "CommonLib/CommonDef.h"
+#if JVET_O0549_ENCODER_ONLY_FILTER
+#include "Utilities/program_options_lite.h"
+#endif
 
 #include "EncoderLib/EncCfg.h"
 #if EXTENSION_360_VIDEO
@@ -48,6 +51,9 @@
 #if JVET_O0756_CALCULATE_HDRMETRICS
 #include "HDRLib/inc/DistortionMetric.H"
 #endif
+#if JVET_O0549_ENCODER_ONLY_FILTER
+namespace po = df::program_options_lite;
+#endif
 
 #include <sstream>
 #include <vector>
@@ -656,6 +662,12 @@ protected:
   int         m_upscaledOutput;                               ////< Output upscaled (2), decoded cropped but in full resolution buffer (1) or decoded cropped (0, default) picture for RPR.
 #endif
 
+#if JVET_O0549_ENCODER_ONLY_FILTER
+  bool                  m_gopBasedTemporalFilterEnabled;               ///< GOP-based Temporal Filter enable/disable
+  bool                  m_gopBasedTemporalFilterFutureReference;       ///< Enable/disable future frame references in the GOP-based Temporal Filter
+  std::map<int, double> m_gopBasedTemporalFilterStrengths;             ///< Filter strength per frame for the GOP-based Temporal Filter
+#endif
+
 #if EXTENSION_360_VIDEO
   TExt360AppEncCfg m_ext360;
   friend class TExt360AppEncCfg;
diff --git a/source/Lib/CommonLib/Buffer.h b/source/Lib/CommonLib/Buffer.h
index e76cdd7a55ad73002fdf1f9e197290312a13548e..e2c16f95512c29203dea0d5269bc3a6fe085c614 100644
--- a/source/Lib/CommonLib/Buffer.h
+++ b/source/Lib/CommonLib/Buffer.h
@@ -126,6 +126,9 @@ struct AreaBuf : public Size
 #endif
   void extendSingleBorderPel();
   void extendBorderPel      (  unsigned margin );
+#if JVET_O0549_ENCODER_ONLY_FILTER
+  void extendBorderPel(unsigned marginX, unsigned marginY);
+#endif
   void addWeightedAvg       ( const AreaBuf<const T> &other1, const AreaBuf<const T> &other2, const ClpRng& clpRng, const int8_t gbiIdx);
   void removeWeightHighFreq ( const AreaBuf<T>& other, const bool bClip, const ClpRng& clpRng, const int8_t iGbiWeight);
   void addAvg               ( const AreaBuf<const T> &other1, const AreaBuf<const T> &other2, const ClpRng& clpRng );
@@ -579,6 +582,46 @@ void AreaBuf<T>::updateHistogram( std::vector<int32_t>& hist ) const
   }
 }
 
+#if JVET_O0549_ENCODER_ONLY_FILTER
+template<typename T>
+void AreaBuf<T>::extendBorderPel(unsigned marginX, unsigned marginY)
+{
+  T* p = buf;
+  int h = height;
+  int w = width;
+  int s = stride;
+
+  CHECK((w + 2 * marginX) > s, "Size of buffer too small to extend");
+  // do left and right margins
+  for (int y = 0; y < h; y++)
+  {
+    for (int x = 0; x < marginX; x++)
+    {
+      *(p - marginX + x) = p[0];
+      p[w + x] = p[w - 1];
+    }
+    p += s;
+  }
+
+  // p is now the (0,height) (bottom left of image within bigger picture
+  p -= (s + marginX);
+  // p is now the (-margin, height-1)
+  for (int y = 0; y < marginY; y++)
+  {
+    ::memcpy(p + (y + 1) * s, p, sizeof(T) * (w + (marginX << 1)));
+  }
+
+  // p is still (-marginX, height-1)
+  p -= ((h - 1) * s);
+  // p is now (-marginX, 0)
+  for (int y = 0; y < marginY; y++)
+  {
+    ::memcpy(p - (y + 1) * s, p, sizeof(T) * (w + (marginX << 1)));
+  }
+}
+#endif
+
+
 template<typename T>
 void AreaBuf<T>::extendBorderPel( unsigned margin )
 {
@@ -746,6 +789,9 @@ struct UnitBuf
   void addWeightedAvg       ( const UnitBuf<const T> &other1, const UnitBuf<const T> &other2, const ClpRngs& clpRngs, const uint8_t gbiIdx = GBI_DEFAULT, const bool chromaOnly = false, const bool lumaOnly = false);
   void addAvg               ( const UnitBuf<const T> &other1, const UnitBuf<const T> &other2, const ClpRngs& clpRngs, const bool chromaOnly = false, const bool lumaOnly = false);
   void extendSingleBorderPel();
+#if JVET_O0549_ENCODER_ONLY_FILTER
+  void extendBorderPel(unsigned marginX, unsigned marginY);
+#endif
   void extendBorderPel      ( unsigned margin );
   void removeHighFreq       ( const UnitBuf<T>& other, const bool bClip, const ClpRngs& clpRngs
                             , const int8_t gbiWeight = g_GbiWeights[GBI_DEFAULT]
@@ -855,6 +901,17 @@ void UnitBuf<T>::extendSingleBorderPel()
   }
 }
 
+#if JVET_O0549_ENCODER_ONLY_FILTER
+template<typename T>
+void UnitBuf<T>::extendBorderPel(unsigned marginX, unsigned marginY)
+{
+  for (unsigned i = 0; i < bufs.size(); i++)
+  {
+    bufs[i].extendBorderPel(marginX >> getComponentScaleX(ComponentID(i), chromaFormat), marginY >> getComponentScaleY(ComponentID(i), chromaFormat));
+  }
+}
+#endif
+
 template<typename T>
 void UnitBuf<T>::extendBorderPel( unsigned margin )
 {
diff --git a/source/Lib/CommonLib/TypeDef.h b/source/Lib/CommonLib/TypeDef.h
index 44aa44e3ec759f0f138a27e03a423f42c41b05f5..44d23329064bc9934d30e6b53eabdf71aa174f65 100644
--- a/source/Lib/CommonLib/TypeDef.h
+++ b/source/Lib/CommonLib/TypeDef.h
@@ -50,6 +50,8 @@
 #include <assert.h>
 #include <cassert>
 
+#define JVET_O0549_ENCODER_ONLY_FILTER                    1 // JVET-O0549: Encoder-only temporal filter, no decoder changes
+
 #define JVET_O0143_BOTTOM_RIGHT_BRICK_IDX_DELTA           1 // JVET-O0143: Remove signaling of top_right_brick_idx
 
 #define JVET_O0236_PPS_PARSING_DEPENDENCY                 1 // JVET-O0236: Resolves a PPS parsing dependency
diff --git a/source/Lib/EncoderLib/EncCfg.h b/source/Lib/EncoderLib/EncCfg.h
index 5a430caa73dff7e3217346ec695e7d710a90ace9..8a5d92ec6768040d6494cf8bf98c2497b6079a80 100644
--- a/source/Lib/EncoderLib/EncCfg.h
+++ b/source/Lib/EncoderLib/EncCfg.h
@@ -485,6 +485,9 @@ protected:
   int       m_PCMBitDepth[MAX_NUM_CHANNEL_TYPE];
   uint32_t      m_pcmLog2MaxSize;
   uint32_t      m_uiPCMLog2MinSize;
+#endif
+#if JVET_O0549_ENCODER_ONLY_FILTER
+  bool      m_gopBasedTemporalFilterEnabled;
 #endif
   //====== Slice ========
   SliceConstraint m_sliceMode;
@@ -1291,6 +1294,10 @@ public:
   uint32_t      getPCMLog2MaxSize               ()      { return m_pcmLog2MaxSize;  }
   uint32_t      getPCMLog2MinSize               ()      { return  m_uiPCMLog2MinSize;  }
 #endif
+#if JVET_O0549_ENCODER_ONLY_FILTER
+  void  setGopBasedTemporalFilterEnabled(bool flag) { m_gopBasedTemporalFilterEnabled = flag; }
+  bool  getGopBasedTemporalFilterEnabled()          { return m_gopBasedTemporalFilterEnabled; }
+#endif
 
   bool      getCrossComponentPredictionEnabledFlag     ()                const { return m_crossComponentPredictionEnabledFlag;   }
   void      setCrossComponentPredictionEnabledFlag     (const bool value)      { m_crossComponentPredictionEnabledFlag = value;  }
diff --git a/source/Lib/EncoderLib/EncGOP.cpp b/source/Lib/EncoderLib/EncGOP.cpp
index c3c1348c62af595cede8e2ce8609ecd59cc89655..e66074dafdfde7cf8e4cfbc8fc22407a4dca6c0d 100644
--- a/source/Lib/EncoderLib/EncGOP.cpp
+++ b/source/Lib/EncoderLib/EncGOP.cpp
@@ -3779,7 +3779,11 @@ void EncGOP::xCalculateAddPSNR(Picture* pcPic, PelUnitBuf cPicD, const AccessUni
   const CPelUnitBuf& pic = cPicD;
   CHECK(!(conversion == IPCOLOURSPACE_UNCHANGED), "Unspecified error");
 //  const CPelUnitBuf& org = (conversion != IPCOLOURSPACE_UNCHANGED) ? pcPic->getPicYuvTrueOrg()->getBuf() : pcPic->getPicYuvOrg()->getBuf();
+#if JVET_O0549_ENCODER_ONLY_FILTER
+  const CPelUnitBuf& org = (sps.getUseReshaper() || m_pcCfg->getGopBasedTemporalFilterEnabled()) ? pcPic->getTrueOrigBuf() : pcPic->getOrigBuf();
+#else
   const CPelUnitBuf& org = sps.getUseReshaper() ? pcPic->getTrueOrigBuf() : pcPic->getOrigBuf();
+#endif
 #if ENABLE_QPA
   const bool    useWPSNR = m_pcEncLib->getUseWPSNR();
 #endif
diff --git a/source/Lib/EncoderLib/EncTemporalFilter.cpp b/source/Lib/EncoderLib/EncTemporalFilter.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..5c19ef9aec3e665601684f48abb3d109c5c8cf5a
--- /dev/null
+++ b/source/Lib/EncoderLib/EncTemporalFilter.cpp
@@ -0,0 +1,628 @@
+/* The copyright in this software is being made available under the BSD
+* License, included below. This software may be subject to other third party
+* and contributor rights, including patent rights, and no such rights are
+* granted under this license.
+*
+* Copyright (c) 2010-2019, ITU/ISO/IEC
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following conditions are met:
+*
+*  * Redistributions of source code must retain the above copyright notice,
+*    this list of conditions and the following disclaimer.
+*  * Redistributions in binary form must reproduce the above copyright notice,
+*    this list of conditions and the following disclaimer in the documentation
+*    and/or other materials provided with the distribution.
+*  * Neither the name of the ITU/ISO/IEC nor the names of its contributors may
+*    be used to endorse or promote products derived from this software without
+*    specific prior written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS
+* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+* THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+/** \file     EncTemporalFilter.cpp
+\brief    EncTemporalFilter class
+*/
+
+#include "EncTemporalFilter.h"
+#include <math.h>
+
+#if JVET_O0549_ENCODER_ONLY_FILTER
+
+// ====================================================================================================================
+// Constructor / destructor / initialization / destroy
+// ====================================================================================================================
+
+const int EncTemporalFilter::s_range = 2;
+const double EncTemporalFilter::s_chromaFactor = 0.55;
+const double EncTemporalFilter::s_sigmaMultiplier = 9.0;
+const double EncTemporalFilter::s_sigmaZeroPoint = 10.0;
+const int EncTemporalFilter::s_motionVectorFactor = 16;
+const int EncTemporalFilter::s_padding = 128;
+const int EncTemporalFilter::s_interpolationFilter[16][8] =
+{
+  {   0,   0,   0,  64,   0,   0,   0,   0 },   //0
+  {   0,   1,  -3,  64,   4,  -2,   0,   0 },   //1 -->-->
+  {   0,   1,  -6,  62,   9,  -3,   1,   0 },   //2 -->
+  {   0,   2,  -8,  60,  14,  -5,   1,   0 },   //3 -->-->
+  {   0,   2,  -9,  57,  19,  -7,   2,   0 },   //4
+  {   0,   3, -10,  53,  24,  -8,   2,   0 },   //5 -->-->
+  {   0,   3, -11,  50,  29,  -9,   2,   0 },   //6 -->
+  {   0,   3, -11,  44,  35, -10,   3,   0 },   //7 -->-->
+  {   0,   1,  -7,  38,  38,  -7,   1,   0 },   //8
+  {   0,   3, -10,  35,  44, -11,   3,   0 },   //9 -->-->
+  {   0,   2,  -9,  29,  50, -11,   3,   0 },   //10-->
+  {   0,   2,  -8,  24,  53, -10,   3,   0 },   //11-->-->
+  {   0,   2,  -7,  19,  57,  -9,   2,   0 },   //12
+  {   0,   1,  -5,  14,  60,  -8,   2,   0 },   //13-->-->
+  {   0,   1,  -3,   9,  62,  -6,   1,   0 },   //14-->
+  {   0,   0,  -2,   4,  64,  -3,   1,   0 }    //15-->-->
+};
+
+const double EncTemporalFilter::s_refStrengths[3][2] =
+{ // abs(POC offset)
+  //  1,    2
+  {0.85, 0.60},  // s_range * 2
+  {1.20, 1.00},  // s_range
+  {0.30, 0.30}   // otherwise
+};
+
+EncTemporalFilter::EncTemporalFilter() :
+  m_FrameSkip(0),
+  m_chromaFormatIDC(NUM_CHROMA_FORMAT),
+  m_sourceWidth(0),
+  m_sourceHeight(0),
+  m_QP(0),
+  m_clipInputVideoToRec709Range(false),
+  m_inputColourSpaceConvert(NUMBER_INPUT_COLOUR_SPACE_CONVERSIONS)
+{}
+
+void EncTemporalFilter::init(const int frameSkip,
+  const int inputBitDepth[MAX_NUM_CHANNEL_TYPE],
+  const int MSBExtendedBitDepth[MAX_NUM_CHANNEL_TYPE],
+  const int internalBitDepth[MAX_NUM_CHANNEL_TYPE],
+  const int width,
+  const int height,
+  const int *pad,
+  const bool Rec709,
+  const std::string &filename,
+  const ChromaFormat inputChromaFormatIDC,
+  const InputColourSpaceConversion colorSpaceConv,
+  const int QP,
+  const std::map<int, double> &temporalFilterStrengths,
+  const bool gopBasedTemporalFilterFutureReference)
+{
+  m_FrameSkip = frameSkip;
+  for (int i = 0; i < MAX_NUM_CHANNEL_TYPE; i++)
+  {
+    m_inputBitDepth[i] = inputBitDepth[i];
+    m_MSBExtendedBitDepth[i] = MSBExtendedBitDepth[i];
+    m_internalBitDepth[i] = internalBitDepth[i];
+  }
+
+  m_sourceWidth = width;
+  m_sourceHeight = height;
+  for (int i = 0; i < 2; i++)
+  {
+    m_pad[i] = pad[i];
+  }
+  m_clipInputVideoToRec709Range = Rec709;
+  m_inputFileName = filename;
+  m_chromaFormatIDC = inputChromaFormatIDC;
+  m_inputColourSpaceConvert = colorSpaceConv;
+  m_area = Area(0, 0, width, height);
+  m_QP = QP;
+  m_temporalFilterStrengths = temporalFilterStrengths;
+  m_gopBasedTemporalFilterFutureReference = gopBasedTemporalFilterFutureReference;
+}
+
+// ====================================================================================================================
+// Public member functions
+// ====================================================================================================================
+
+bool EncTemporalFilter::filter(PelStorage *orgPic, int receivedPoc)
+{
+  bool isFilterThisFrame = false;
+  if (m_QP >= 17)  // disable filter for QP < 17
+  {
+    for (map<int, double>::iterator it = m_temporalFilterStrengths.begin(); it != m_temporalFilterStrengths.end(); ++it)
+    {
+      int filteredFrame = it->first;
+      if (receivedPoc % filteredFrame == 0)
+      {
+        isFilterThisFrame = true;
+        break;
+      }
+    }
+  }
+
+  if (isFilterThisFrame)
+  {
+    int offset = m_FrameSkip;
+    VideoIOYuv yuvFrames;
+    yuvFrames.open(m_inputFileName, false, m_inputBitDepth, m_MSBExtendedBitDepth, m_internalBitDepth);
+    yuvFrames.skipFrames(std::max(offset + receivedPoc - s_range, 0), m_sourceWidth - m_pad[0], m_sourceHeight - m_pad[1], m_chromaFormatIDC);
+
+
+    std::deque<TemporalFilterSourcePicInfo> srcFrameInfo;
+
+    int firstFrame = receivedPoc + offset - s_range;
+    int lastFrame = receivedPoc + offset + s_range;
+    if (!m_gopBasedTemporalFilterFutureReference)
+    {
+      lastFrame = receivedPoc + offset - 1;
+    }
+    int origOffset = -s_range;
+
+    // subsample original picture so it only needs to be done once
+    PelStorage origPadded;
+
+    origPadded.create(m_chromaFormatIDC, m_area, 0, s_padding);
+    origPadded.copyFrom(*orgPic);
+    origPadded.extendBorderPel(s_padding, s_padding);
+
+    PelStorage origSubsampled2;
+    PelStorage origSubsampled4;
+
+    subsampleLuma(origPadded, origSubsampled2);
+    subsampleLuma(origSubsampled2, origSubsampled4);
+
+    // determine motion vectors
+    for (int poc = firstFrame; poc <= lastFrame; poc++)
+    {
+      if (poc < 0)
+      {
+        origOffset++;
+        continue; // frame not available
+      }
+      else if (poc == offset + receivedPoc)
+      { // hop over frame that will be filtered
+        yuvFrames.skipFrames(1, m_sourceWidth - m_pad[0], m_sourceHeight - m_pad[1], m_chromaFormatIDC);
+        origOffset++;
+        continue;
+      }
+      srcFrameInfo.push_back(TemporalFilterSourcePicInfo());
+      TemporalFilterSourcePicInfo &srcPic=srcFrameInfo.back();
+
+      PelStorage dummyPicBufferTO; // Only used temporary in yuvFrames.read
+      srcPic.picBuffer.create(m_chromaFormatIDC, m_area, 0, s_padding);
+      dummyPicBufferTO.create(m_chromaFormatIDC, m_area, 0, s_padding);
+      if (!yuvFrames.read(srcPic.picBuffer, dummyPicBufferTO, m_inputColourSpaceConvert, m_pad, m_chromaFormatIDC, m_clipInputVideoToRec709Range))
+      {
+        return false; // eof or read fail
+      }
+      srcPic.picBuffer.extendBorderPel(s_padding, s_padding);
+      srcPic.mvs.allocate(m_sourceWidth / 4, m_sourceHeight / 4);
+
+      motionEstimation(srcPic.mvs, origPadded, srcPic.picBuffer, origSubsampled2, origSubsampled4);
+      srcPic.origOffset = origOffset;
+      origOffset++;
+    }
+
+    // filter
+    PelStorage newOrgPic;
+    newOrgPic.create(m_chromaFormatIDC, m_area, 0, s_padding);
+    double overallStrength = -1.0;
+    for (map<int, double>::iterator it = m_temporalFilterStrengths.begin(); it != m_temporalFilterStrengths.end(); ++it)
+    {
+      int frame = it->first;
+      double strength = it->second;
+      if (receivedPoc % frame == 0)
+      {
+        overallStrength = strength;
+      }
+    }
+
+    bilateralFilter(origPadded, srcFrameInfo, newOrgPic, overallStrength);
+
+    // move filtered to orgPic
+    orgPic->copyFrom(newOrgPic);
+
+    yuvFrames.close();
+    return true;
+  }
+  return false;
+}
+
+// ====================================================================================================================
+// Private member functions
+// ====================================================================================================================
+
+void EncTemporalFilter::subsampleLuma(const PelStorage &input, PelStorage &output, const int factor) const
+{
+  const int newWidth = input.Y().width / factor;
+  const int newHeight = input.Y().height / factor;
+  output.create(m_chromaFormatIDC, Area(0, 0, newWidth, newHeight), 0, s_padding);
+
+  const Pel* srcRow = input.Y().buf;
+  const int srcStride = input.Y().stride;
+  Pel *dstRow = output.Y().buf;
+  const int dstStride = output.Y().stride;
+
+  for (int y = 0; y < newHeight; y++, srcRow+=factor*srcStride, dstRow+=dstStride)
+  {
+    const Pel *inRow      = srcRow;
+    const Pel *inRowBelow = srcRow+srcStride;
+    Pel *target     = dstRow;
+
+    for (int x = 0; x < newWidth; x++)
+    {
+      target[x] = (inRow[0] + inRowBelow[0] + inRow[1] + inRowBelow[1] + 2) >> 2;
+      inRow += 2;
+      inRowBelow += 2;
+    }
+  }
+  output.extendBorderPel(s_padding, s_padding);
+}
+
+int EncTemporalFilter::motionErrorLuma(const PelStorage &orig,
+  const PelStorage &buffer,
+  const int x,
+  const int y,
+  int dx,
+  int dy,
+  const int bs,
+  const int besterror = 8 * 8 * 1024 * 1024) const
+{
+  const Pel* origOrigin = orig.Y().buf;
+  const int origStride  = orig.Y().stride;
+  const Pel *buffOrigin = buffer.Y().buf;
+  const int buffStride  = buffer.Y().stride;
+
+  int error = 0;// dx * 10 + dy * 10;
+  if (((dx | dy) & 0xF) == 0)
+  {
+    dx /= s_motionVectorFactor;
+    dy /= s_motionVectorFactor;
+    for (int y1 = 0; y1 < bs; y1++)
+    {
+      const Pel* origRowStart = origOrigin + (y+y1)*origStride + x;
+      const Pel* bufferRowStart = buffOrigin + (y+y1+dy)*buffStride + (x+dx);
+      for (int x1 = 0; x1 < bs; x1 += 2)
+      {
+        int diff = origRowStart[x1] - bufferRowStart[x1];
+        error += diff * diff;
+        diff = origRowStart[x1 + 1] - bufferRowStart[x1 + 1];
+        error += diff * diff;
+      }
+      if (error > besterror)
+      {
+        return error;
+      }
+    }
+  }
+  else
+  {
+    const int *xFilter = s_interpolationFilter[dx & 0xF];
+    const int *yFilter = s_interpolationFilter[dy & 0xF];
+    int tempArray[64 + 8][64];
+
+    int sum, base;
+    for (int y1 = 1; y1 < bs + 7; y1++)
+    {
+      const int yOffset = y + y1 + (dy >> 4) - 3;
+      const Pel *sourceRow = buffOrigin + (yOffset)*buffStride + 0;
+      for (int x1 = 0; x1 < bs; x1++)
+      {
+        sum = 0;
+        base = x + x1 + (dx >> 4) - 3;
+        const Pel *rowStart = sourceRow + base;
+
+        sum += xFilter[1] * rowStart[1];
+        sum += xFilter[2] * rowStart[2];
+        sum += xFilter[3] * rowStart[3];
+        sum += xFilter[4] * rowStart[4];
+        sum += xFilter[5] * rowStart[5];
+        sum += xFilter[6] * rowStart[6];
+
+        tempArray[y1][x1] = sum;
+      }
+    }
+
+    const Pel maxSampleValue = (1<<m_internalBitDepth[CHANNEL_TYPE_LUMA])-1;
+    for (int y1 = 0; y1 < bs; y1++)
+    {
+      const Pel *origRow = origOrigin + (y+y1)*origStride + 0;
+      for (int x1 = 0; x1 < bs; x1++)
+      {
+        sum = 0;
+        sum += yFilter[1] * tempArray[y1 + 1][x1];
+        sum += yFilter[2] * tempArray[y1 + 2][x1];
+        sum += yFilter[3] * tempArray[y1 + 3][x1];
+        sum += yFilter[4] * tempArray[y1 + 4][x1];
+        sum += yFilter[5] * tempArray[y1 + 5][x1];
+        sum += yFilter[6] * tempArray[y1 + 6][x1];
+
+        sum = (sum + (1 << 11)) >> 12;
+        sum = sum < 0 ? 0 : (sum > maxSampleValue ? maxSampleValue : sum);
+
+        error += (sum - origRow[x + x1]) * (sum - origRow[x + x1]);
+      }
+      if (error > besterror)
+      {
+        return error;
+      }
+    }
+  }
+  return error;
+}
+
+void EncTemporalFilter::motionEstimationLuma(Array2D<MotionVector> &mvs, const PelStorage &orig, const PelStorage &buffer, const int blockSize,
+  const Array2D<MotionVector> *previous, const int factor, const bool doubleRes) const
+{
+  int range = 5;
+  const int stepSize = blockSize;
+
+  const int origWidth  = orig.Y().width;
+  const int origHeight = orig.Y().height;
+
+  for (int blockY = 0; blockY + blockSize < origHeight; blockY += stepSize)
+  {
+    for (int blockX = 0; blockX + blockSize < origWidth; blockX += stepSize)
+    {
+      MotionVector best;
+
+      if (previous == NULL)
+      {
+        range = 8;
+      }
+      else
+      {
+        for (int py = -2; py <= 2; py++)
+        {
+          int testy = blockY / (2 * blockSize) + py;
+          for (int px = -2; px <= 2; px++)
+          {
+            int testx = blockX / (2 * blockSize) + px;
+            if ((testx >= 0) && (testx < origWidth / (2 * blockSize)) && (testy >= 0) && (testy < origHeight / (2 * blockSize)))
+            {
+              MotionVector old = previous->get(testx, testy);
+              int error = motionErrorLuma(orig, buffer, blockX, blockY, old.x * factor, old.y * factor, blockSize, best.error);
+              if (error < best.error)
+              {
+                best.set(old.x * factor, old.y * factor, error);
+              }
+            }
+          }
+        }
+      }
+      MotionVector prevBest = best;
+      for (int y2 = prevBest.y / s_motionVectorFactor - range; y2 <= prevBest.y / s_motionVectorFactor + range; y2++)
+      {
+        for (int x2 = prevBest.x / s_motionVectorFactor - range; x2 <= prevBest.x / s_motionVectorFactor + range; x2++)
+        {
+          int error = motionErrorLuma(orig, buffer, blockX, blockY, x2 * s_motionVectorFactor, y2 * s_motionVectorFactor, blockSize, best.error);
+          if (error < best.error)
+          {
+            best.set(x2 * s_motionVectorFactor, y2 * s_motionVectorFactor, error);
+          }
+        }
+      }
+      if (doubleRes)
+      { // merge into one loop, probably with precision array (here [12, 3] or maybe [4, 1]) with setable number of iterations
+        prevBest = best;
+        int doubleRange = 3 * 4;
+        for (int y2 = prevBest.y - doubleRange; y2 <= prevBest.y + doubleRange; y2 += 4)
+        {
+          for (int x2 = prevBest.x - doubleRange; x2 <= prevBest.x + doubleRange; x2 += 4)
+          {
+            int error = motionErrorLuma(orig, buffer, blockX, blockY, x2, y2, blockSize, best.error);
+            if (error < best.error)
+            {
+              best.set(x2, y2, error);
+            }
+
+          }
+        }
+
+        prevBest = best;
+        doubleRange = 3;
+        for (int y2 = prevBest.y - doubleRange; y2 <= prevBest.y + doubleRange; y2++)
+        {
+          for (int x2 = prevBest.x - doubleRange; x2 <= prevBest.x + doubleRange; x2++)
+          {
+            int error = motionErrorLuma(orig, buffer, blockX, blockY, x2, y2, blockSize, best.error);
+            if (error < best.error)
+            {
+              best.set(x2, y2, error);
+            }
+
+          }
+        }
+
+      }
+      mvs.get(blockX / stepSize, blockY / stepSize) = best;
+    }
+  }
+}
+
+void EncTemporalFilter::motionEstimation(Array2D<MotionVector> &mv, const PelStorage &orgPic, const PelStorage &buffer, const PelStorage &origSubsampled2, const PelStorage &origSubsampled4) const
+{
+  const int width = m_sourceWidth;
+  const int height = m_sourceHeight;
+  Array2D<MotionVector> mv_0(width / 16, height / 16);
+  Array2D<MotionVector> mv_1(width / 16, height / 16);
+  Array2D<MotionVector> mv_2(width / 16, height / 16);
+
+  PelStorage bufferSub2;
+  PelStorage bufferSub4;
+
+  subsampleLuma(buffer, bufferSub2);
+  subsampleLuma(bufferSub2, bufferSub4);
+
+  motionEstimationLuma(mv_0, origSubsampled4, bufferSub4, 16);
+  motionEstimationLuma(mv_1, origSubsampled2, bufferSub2, 16, &mv_0, 2);
+  motionEstimationLuma(mv_2, orgPic, buffer, 16, &mv_1, 2);
+
+  motionEstimationLuma(mv, orgPic, buffer, 8, &mv_2, 1, true);
+}
+
+void EncTemporalFilter::applyMotion(const Array2D<MotionVector> &mvs, const PelStorage &input, PelStorage &output) const
+{
+  static const int lumaBlockSize=8;
+
+  for(int c=0; c< getNumberValidComponents(m_chromaFormatIDC); c++)
+  {
+    const ComponentID compID=(ComponentID)c;
+    const int csx=getComponentScaleX(compID, m_chromaFormatIDC);
+    const int csy=getComponentScaleY(compID, m_chromaFormatIDC);
+    const int blockSizeX = lumaBlockSize>>csx;
+    const int blockSizeY = lumaBlockSize>>csy;
+    const int height = input.bufs[c].height;
+    const int width  = input.bufs[c].width;
+
+    const Pel maxValue = (1<<m_internalBitDepth[toChannelType(compID)])-1;
+
+    const Pel *srcImage = input.bufs[c].buf;
+    const int srcStride  = input.bufs[c].stride;
+
+    Pel *dstImage = output.bufs[c].buf;
+    int dstStride  = output.bufs[c].stride;
+
+    for (int y = 0, blockNumY = 0; y + blockSizeY <= height; y += blockSizeY, blockNumY++)
+    {
+      for (int x = 0, blockNumX = 0; x + blockSizeX <= width; x += blockSizeX, blockNumX++)
+      {
+        const MotionVector &mv = mvs.get(blockNumX,blockNumY);
+        const int dx = mv.x >> csx ;
+        const int dy = mv.y >> csy ;
+        const int xInt = mv.x >> (4+csx) ;
+        const int yInt = mv.y >> (4+csy) ;
+
+        const int *xFilter = s_interpolationFilter[dx & 0xf];
+        const int *yFilter = s_interpolationFilter[dy & 0xf]; // will add 6 bit.
+        const int numFilterTaps=7;
+        const int centreTapOffset=3;
+
+        int tempArray[lumaBlockSize + numFilterTaps][lumaBlockSize];
+
+        for (int by = 1; by < blockSizeY + numFilterTaps; by++)
+        {
+          const int yOffset = y + by + yInt - centreTapOffset;
+          const Pel *sourceRow = srcImage+yOffset*srcStride;
+          for (int bx = 0; bx < blockSizeX; bx++)
+          {
+            int base = x + bx + xInt - centreTapOffset;
+            const Pel *rowStart = sourceRow + base;
+
+            int sum = 0;
+            sum += xFilter[1] * rowStart[1];
+            sum += xFilter[2] * rowStart[2];
+            sum += xFilter[3] * rowStart[3];
+            sum += xFilter[4] * rowStart[4];
+            sum += xFilter[5] * rowStart[5];
+            sum += xFilter[6] * rowStart[6];
+
+            tempArray[by][bx] = sum;
+          }
+        }
+
+        Pel *dstRow = dstImage+y*dstStride;
+        for (int by = 0; by < blockSizeY; by++, dstRow+=dstStride)
+        {
+          Pel *dstPel=dstRow+x;
+          for (int bx = 0; bx < blockSizeX; bx++, dstPel++)
+          {
+            int sum = 0;
+
+            sum += yFilter[1] * tempArray[by + 1][bx];
+            sum += yFilter[2] * tempArray[by + 2][bx];
+            sum += yFilter[3] * tempArray[by + 3][bx];
+            sum += yFilter[4] * tempArray[by + 4][bx];
+            sum += yFilter[5] * tempArray[by + 5][bx];
+            sum += yFilter[6] * tempArray[by + 6][bx];
+
+            sum = (sum + (1 << 11)) >> 12;
+            sum = sum < 0 ? 0 : (sum > maxValue ? maxValue : sum);
+            *dstPel = sum;
+          }
+        }
+      }
+    }
+  }
+}
+
+void EncTemporalFilter::bilateralFilter(const PelStorage &orgPic,
+  const std::deque<TemporalFilterSourcePicInfo> &srcFrameInfo,
+  PelStorage &newOrgPic,
+  double overallStrength) const
+{
+  const int numRefs = int(srcFrameInfo.size());
+  std::vector<PelStorage> correctedPics(numRefs);
+  for (int i = 0; i < numRefs; i++)
+  {
+    correctedPics[i].create(m_chromaFormatIDC, m_area, 0, s_padding);
+    applyMotion(srcFrameInfo[i].mvs, srcFrameInfo[i].picBuffer, correctedPics[i]);
+  }
+
+  int refStrengthRow = 2;
+  if (numRefs == s_range*2)
+  {
+    refStrengthRow = 0;
+  }
+  else if (numRefs == s_range)
+  {
+    refStrengthRow = 1;
+  }
+
+  const double lumaSigmaSq = (m_QP - s_sigmaZeroPoint) * (m_QP - s_sigmaZeroPoint) * s_sigmaMultiplier;
+  const double chromaSigmaSq = 30 * 30;
+
+  for(int c=0; c< getNumberValidComponents(m_chromaFormatIDC); c++)
+  {
+    const ComponentID compID=(ComponentID)c;
+    const int height = orgPic.bufs[c].height;
+    const int width  = orgPic.bufs[c].width;
+    const Pel *srcPelRow = orgPic.bufs[c].buf;
+    const int srcStride = orgPic.bufs[c].stride;
+    Pel *dstPelRow = newOrgPic.bufs[c].buf;
+    const int dstStride = newOrgPic.bufs[c].stride;
+    const double sigmaSq = isChroma(compID)? chromaSigmaSq : lumaSigmaSq;
+    const double weightScaling = overallStrength * (isChroma(compID) ? s_chromaFactor : 0.4);
+    const Pel maxSampleValue = (1<<m_internalBitDepth[toChannelType(compID)])-1;
+    const double bitDepthDiffWeighting=1024.0 / (maxSampleValue+1);
+
+    for (int y = 0; y < height; y++, srcPelRow+=srcStride, dstPelRow+=dstStride)
+    {
+      const Pel *srcPel=srcPelRow;
+      Pel *dstPel=dstPelRow;
+      for (int x = 0; x < width; x++, srcPel++, dstPel++)
+      {
+        const int orgVal = (int) *srcPel;
+        double temporalWeightSum = 1.0;
+        double newVal = (double) orgVal;
+        for (int i = 0; i < numRefs; i++)
+        {
+          const Pel *pCorrectedPelPtr=correctedPics[i].bufs[c].buf+(y*correctedPics[i].bufs[c].stride+x);
+          const int refVal = (int) *pCorrectedPelPtr;
+          double diff = (double)(refVal - orgVal);
+          diff *= bitDepthDiffWeighting;
+          double diffSq = diff * diff;
+          const int index = std::min(1, std::abs(srcFrameInfo[i].origOffset) - 1);
+          const double weight = weightScaling * s_refStrengths[refStrengthRow][index] * exp(-diffSq / (2 * sigmaSq));
+          newVal += weight * refVal;
+          temporalWeightSum += weight;
+        }
+        newVal /= temporalWeightSum;
+        Pel sampleVal = (Pel)round(newVal);
+        sampleVal=(sampleVal<0?0 : (sampleVal>maxSampleValue ? maxSampleValue : sampleVal));
+        *dstPel = sampleVal;
+      }
+    }
+  }
+}
+
+//! \}
+
+#endif
diff --git a/source/Lib/EncoderLib/EncTemporalFilter.h b/source/Lib/EncoderLib/EncTemporalFilter.h
new file mode 100644
index 0000000000000000000000000000000000000000..0a44eefa01b5c8d955c4b9e46cf982c974f21db5
--- /dev/null
+++ b/source/Lib/EncoderLib/EncTemporalFilter.h
@@ -0,0 +1,170 @@
+/* The copyright in this software is being made available under the BSD
+* License, included below. This software may be subject to other third party
+* and contributor rights, including patent rights, and no such rights are
+* granted under this license.
+*
+* Copyright (c) 2010-2019, ITU/ISO/IEC
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following conditions are met:
+*
+*  * Redistributions of source code must retain the above copyright notice,
+*    this list of conditions and the following disclaimer.
+*  * Redistributions in binary form must reproduce the above copyright notice,
+*    this list of conditions and the following disclaimer in the documentation
+*    and/or other materials provided with the distribution.
+*  * Neither the name of the ITU/ISO/IEC nor the names of its contributors may
+*    be used to endorse or promote products derived from this software without
+*    specific prior written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS
+* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+* THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+/** \file     EncTemporalFilter.h
+\brief    EncTemporalFilter class (header)
+*/
+
+#ifndef __TEMPORAL_FILTER__
+#define __TEMPORAL_FILTER__
+#include "EncLib.h"
+#include "CommonLib/Buffer.h"
+#include <sstream>
+#include <map>
+#include <deque>
+
+#if JVET_O0549_ENCODER_ONLY_FILTER
+
+//! \ingroup EncoderLib
+//! \{
+
+struct MotionVector
+{
+  int x, y;
+  int error;
+  MotionVector() : x(0), y(0), error(INT_LEAST32_MAX) {}
+  void set(int nx, int ny, int ne) { x = nx; y = ny; error = ne; }
+};
+
+template <class T>
+struct Array2D
+{
+private:
+  int m_width, m_height;
+  std::vector< T > v;
+public:
+  Array2D() : m_width(0), m_height(0), v() { }
+  Array2D(int width, int height, const T& value=T()) : m_width(0), m_height(0), v() { allocate(width, height, value); }
+
+  void allocate(int width, int height, const T& value=T())
+  {
+    m_width=width;
+    m_height=height;
+    v.resize(std::size_t(m_width*m_height), value);
+  }
+
+  T& get(int x, int y)
+  {
+    assert(x<m_width && y<m_height);
+    return v[y*m_width+x];
+  }
+
+  const T& get(int x, int y) const
+  {
+    assert(x<m_width && y<m_height);
+    return v[y*m_width+x];
+  }
+};
+
+struct TemporalFilterSourcePicInfo
+{
+  TemporalFilterSourcePicInfo() : picBuffer(), mvs(), origOffset(0) { }
+  PelStorage            picBuffer;
+  Array2D<MotionVector> mvs;
+  int                   origOffset;
+};
+
+// ====================================================================================================================
+// Class definition
+// ====================================================================================================================
+
+class EncTemporalFilter
+{
+public:
+  EncTemporalFilter();
+  ~EncTemporalFilter() {}
+
+  void init(const int frameSkip,
+    const int inputBitDepth[MAX_NUM_CHANNEL_TYPE],
+    const int MSBExtendedBitDepth[MAX_NUM_CHANNEL_TYPE], const int InternalBitDepth[MAX_NUM_CHANNEL_TYPE],
+    const int width,
+    const int height,
+    const int *pad,
+    const bool Rec709,
+    const std::string &filename,
+    const ChromaFormat inputChroma,
+    const InputColourSpaceConversion colorSpaceConv,
+    const int qp,
+    const std::map<int, double> &temporalFilterStrengths,
+    const bool gopBasedTemporalFilterFutureReference);
+
+  bool filter(PelStorage *orgPic, int frame);
+
+private:
+  // Private static member variables
+  static const int s_range;
+  static const double s_chromaFactor;
+  static const double s_sigmaMultiplier;
+  static const double s_sigmaZeroPoint;
+  static const int s_motionVectorFactor;
+  static const int s_padding;
+  static const int s_interpolationFilter[16][8];
+  static const double s_refStrengths[3][2];
+
+  // Private member variables
+  int m_FrameSkip;
+  std::string m_inputFileName;
+  int m_inputBitDepth[MAX_NUM_CHANNEL_TYPE];
+  int m_MSBExtendedBitDepth[MAX_NUM_CHANNEL_TYPE];
+  int m_internalBitDepth[MAX_NUM_CHANNEL_TYPE];
+  ChromaFormat m_chromaFormatIDC;
+  int m_sourceWidth;
+  int m_sourceHeight;
+  int m_QP;
+  std::map<int, double> m_temporalFilterStrengths;
+  int m_pad[2];
+  bool m_clipInputVideoToRec709Range;
+  InputColourSpaceConversion m_inputColourSpaceConvert;
+  Area m_area;
+  bool m_gopBasedTemporalFilterFutureReference;
+
+  int m_maxCUWidth;
+  int m_maxCUHeight;
+  int m_maxTotalCUDepth;
+
+  // Private functions
+  void subsampleLuma(const PelStorage &input, PelStorage &output, const int factor = 2) const;
+  int motionErrorLuma(const PelStorage &orig, const PelStorage &buffer, const int x, const int y, int dx, int dy, const int bs, const int besterror) const;
+  void motionEstimationLuma(Array2D<MotionVector> &mvs, const PelStorage &orig, const PelStorage &buffer, const int bs,
+    const Array2D<MotionVector> *previous=0, const int factor = 1, const bool doubleRes = false) const;
+  void motionEstimation(Array2D<MotionVector> &mvs, const PelStorage &orgPic, const PelStorage &buffer, const PelStorage &origSubsampled2, const PelStorage &origSubsampled4) const;
+
+  void bilateralFilter(const PelStorage &orgPic, const std::deque<TemporalFilterSourcePicInfo> &srcFrameInfo, PelStorage &newOrgPic, double overallStrength) const;
+  void applyMotion(const Array2D<MotionVector> &mvs, const PelStorage &input, PelStorage &output) const;
+}; // END CLASS DEFINITION EncTemporalFilter
+
+   //! \}
+
+#endif
+
+#endif // __TEMPORAL_FILTER__
diff --git a/source/Lib/Utilities/program_options_lite.cpp b/source/Lib/Utilities/program_options_lite.cpp
index 0c4bba0502cc08c2caa01b4cd61f66554dfe30ab..4a380e04cc1b64761d11aed3852252fc47addca4 100644
--- a/source/Lib/Utilities/program_options_lite.cpp
+++ b/source/Lib/Utilities/program_options_lite.cpp
@@ -96,8 +96,22 @@ namespace df
         }
         else
         {
+#if JVET_O0549_ENCODER_ONLY_FILTER_POL
+          if (opt_name.size() > 0 && opt_name.back() == '*')
+          {
+            string prefix_name = opt_name.substr(0, opt_name.size() - 1);
+            names->opt_prefix.push_back(prefix_name);
+            opt_prefix_map[prefix_name].push_back(names);
+          }
+          else
+          {
+            names->opt_long.push_back(opt_name);
+            opt_long_map[opt_name].push_back(names);
+          }
+#else
           names->opt_long.push_back(opt_name);
           opt_long_map[opt_name].push_back(names);
+#endif
         }
         opt_start += opt_end + 1;
       }
@@ -150,6 +164,12 @@ namespace df
       {
         out << "--" << entry.opt_long.front();
       }
+#if JVET_O0549_ENCODER_ONLY_FILTER_POL
+      else if (!entry.opt_prefix.empty())
+      {
+      out << "--" << entry.opt_prefix.front() << "*";
+      }
+#endif
     }
 
     /* format the help text */
@@ -271,6 +291,9 @@ namespace df
     bool OptionWriter::storePair(bool allow_long, bool allow_short, const string& name, const string& value)
     {
       bool found = false;
+#if JVET_O0549_ENCODER_ONLY_FILTER_POL
+      std::string val = value;
+#endif
       Options::NamesMap::iterator opt_it;
       if (allow_long)
       {
@@ -290,15 +313,34 @@ namespace df
           found = true;
         }
       }
-
+#if JVET_O0549_ENCODER_ONLY_FILTER_POL
+      bool allow_prefix = allow_long;
+      if (allow_prefix && !found)
+      {
+        for (opt_it = opts.opt_prefix_map.begin(); opt_it != opts.opt_prefix_map.end(); opt_it++)
+        {
+          std::string name_prefix = name.substr(0, opt_it->first.size());
+          if (name_prefix == opt_it->first)
+          {
+            // prepend value matching *
+            val = name.substr(name_prefix.size()) + std::string(" ") + val;
+            found = true;
+            break;
+          }
+        }
+      }
+#endif
       if (!found)
       {
         error_reporter.error(where())
           << "Unknown option `" << name << "' (value:`" << value << "')\n";
         return false;
       }
-
+#if JVET_O0549_ENCODER_ONLY_FILTER_POL
+      setOptions((*opt_it).second, val, error_reporter);
+#else
       setOptions((*opt_it).second, value, error_reporter);
+#endif
       return true;
     }
 
diff --git a/source/Lib/Utilities/program_options_lite.h b/source/Lib/Utilities/program_options_lite.h
index 2ce2bd26ed80c6066ec93401034513b2b4b71b4a..dfd082cb73f9ca67f58d04a6ce1a2fd97b51910c 100644
--- a/source/Lib/Utilities/program_options_lite.h
+++ b/source/Lib/Utilities/program_options_lite.h
@@ -36,6 +36,8 @@
 #include <list>
 #include <map>
 
+#define JVET_O0549_ENCODER_ONLY_FILTER_POL 1 // JVET-O0549: Encoder-only GOP-based temporal filter. Program Options Lite related changes.
+
 #ifndef __PROGRAM_OPTIONS_LITE__
 #define __PROGRAM_OPTIONS_LITE__
 
@@ -196,6 +198,9 @@ namespace df
         }
         std::list<std::string> opt_long;
         std::list<std::string> opt_short;
+#if JVET_O0549_ENCODER_ONLY_FILTER_POL
+        std::list<std::string> opt_prefix;
+#endif
         OptionBase* opt;
       };
 
@@ -207,6 +212,9 @@ namespace df
       typedef std::map<std::string, NamesPtrList> NamesMap;
       NamesMap opt_long_map;
       NamesMap opt_short_map;
+#if JVET_O0549_ENCODER_ONLY_FILTER_POL
+      NamesMap opt_prefix_map;
+#endif
     };
 
     /* Class with templated overloaded operator(), for use by Options::addOptions() */