From: Ubuntu Developers <ubuntu-devel-discuss@lists.ubuntu.com>
Date: Tue, 1 Feb 2022 14:56:55 -0500
Subject: This is a copy of the jpegformat plugin from gst-plugins-good. It's
 needed for Ubuntu main, so we copy it.

===================================================================
---
 gst/jpegformat/codecparsers-prelude.h |   35 +
 gst/jpegformat/gstjpegparser.c        |  763 ++++++++++++++++++++++++
 gst/jpegformat/gstjpegparser.h        |  412 +++++++++++++
 gst/jpegformat/gstjifmux.c            |  766 ++++++++++++++++++++++++
 gst/jpegformat/gstjifmux.h            |   66 ++
 gst/jpegformat/gstjpegformat.c        |   45 +
 gst/jpegformat/gstjpegparse.c         | 1043 +++++++++++++++++++++++++++++++++
 gst/jpegformat/gstjpegparse.h         |   97 +++
 gst/jpegformat/meson.build            |   17 +
 gst/meson.build                       |    2 +-
 meson_options.txt                     |    2 +
 11 files changed, 3247 insertions(+), 1 deletion(-)
 create mode 100644 gst/jpegformat/codecparsers-prelude.h
 create mode 100644 gst/jpegformat/gstjpegparser.c
 create mode 100644 gst/jpegformat/gstjpegparser.h
 create mode 100644 gst/jpegformat/gstjifmux.c
 create mode 100644 gst/jpegformat/gstjifmux.h
 create mode 100644 gst/jpegformat/gstjpegformat.c
 create mode 100644 gst/jpegformat/gstjpegparse.c
 create mode 100644 gst/jpegformat/gstjpegparse.h
 create mode 100644 gst/jpegformat/meson.build

diff --git a/gst/jpegformat/codecparsers-prelude.h b/gst/jpegformat/codecparsers-prelude.h
new file mode 100644
index 0000000..b056c64
--- /dev/null
+++ b/gst/jpegformat/codecparsers-prelude.h
@@ -0,0 +1,35 @@
+/* GStreamer Codec Parsers Library
+ * Copyright (C) 2018 GStreamer developers
+ *
+ * codecparsers-prelude.h: prelude include header for gst-codecparsers library
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef __GST_CODEC_PARSERS_PRELUDE_H__
+#define __GST_CODEC_PARSERS_PRELUDE_H__
+
+#include <gst/gst.h>
+
+#ifndef GST_CODEC_PARSERS_API
+# ifdef BUILDING_GST_CODEC_PARSERS
+#  define GST_CODEC_PARSERS_API GST_API_EXPORT         /* from config.h */
+# else
+#  define GST_CODEC_PARSERS_API GST_API_IMPORT
+# endif
+#endif
+
+#endif /* __GST_CODEC_PARSERS_PRELUDE_H__ */
diff --git a/gst/jpegformat/gstjpegparser.c b/gst/jpegformat/gstjpegparser.c
new file mode 100644
index 0000000..6411076
--- /dev/null
+++ b/gst/jpegformat/gstjpegparser.c
@@ -0,0 +1,763 @@
+/*  GStreamer JPEG parser
+ *  Copyright (C) 2011-2012 Intel Corporation
+ *  Copyright (C) 2015 Tim-Philipp Müller <tim@centricular.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1
+ *  of the License, or (at your option) any later version.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free
+ *  Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ *  Boston, MA 02110-1301 USA
+ */
+
+/**
+ * SECTION:gstjpegparser
+ * @title: GstJpegParser
+ * @short_description: Convenience library for JPEG bitstream parsing.
+ *
+ * Provides useful functions for parsing JPEG images
+ *
+ */
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <string.h>
+#include <stdlib.h>
+#include <gst/base/gstbytereader.h>
+#include "gstjpegparser.h"
+
+#ifndef GST_DISABLE_GST_DEBUG
+
+#define GST_CAT_DEFAULT ensure_debug_category()
+
+static GstDebugCategory *
+ensure_debug_category (void)
+{
+  static gsize cat_gonce = 0;
+
+  if (g_once_init_enter (&cat_gonce)) {
+    gsize cat_done;
+
+    cat_done = (gsize) _gst_debug_category_new ("codecparsers_jpeg", 0,
+        "GstJpegCodecParser");
+
+    g_once_init_leave (&cat_gonce, cat_done);
+  }
+
+  return (GstDebugCategory *) cat_gonce;
+}
+#else
+
+#define ensure_debug_category() /* NOOP */
+
+#endif /* GST_DISABLE_GST_DEBUG */
+
+#define DEBUG_PRINT_COMMENT 0
+
+#define READ_UINT8(reader, val) G_STMT_START {                  \
+    if (!gst_byte_reader_get_uint8 ((reader), &(val))) {        \
+      GST_WARNING ("failed to read uint8");                     \
+      goto failed;                                              \
+    }                                                           \
+  } G_STMT_END
+
+#define READ_UINT16(reader, val) G_STMT_START {                 \
+    if (!gst_byte_reader_get_uint16_be ((reader), &(val))) {    \
+      GST_WARNING ("failed to read uint16");                    \
+      goto failed;                                              \
+    }                                                           \
+  } G_STMT_END
+
+#define READ_BYTES(reader, buf, length) G_STMT_START {          \
+    const guint8 *vals;                                         \
+    if (!gst_byte_reader_get_data (reader, length, &vals)) {    \
+      GST_WARNING ("failed to read bytes, size:%d", length);    \
+      goto failed;                                              \
+    }                                                           \
+    memcpy (buf, vals, length);                                 \
+  } G_STMT_END
+
+#define U_READ_UINT8(reader, val) G_STMT_START {                \
+    (val) = gst_byte_reader_get_uint8_unchecked(reader);        \
+  } G_STMT_END
+
+#define U_READ_UINT16(reader, val) G_STMT_START {               \
+    (val) = gst_byte_reader_get_uint16_be_unchecked(reader);    \
+  } G_STMT_END
+
+
+/* Table used to address an 8x8 matrix in zig-zag order */
+/* *INDENT-OFF* */
+static const guint8 zigzag_index[64] = {
+  0,   1,  8, 16,  9,  2,  3, 10,
+  17, 24, 32, 25, 18, 11,  4,  5,
+  12, 19, 26, 33, 40, 48, 41, 34,
+  27, 20, 13,  6,  7, 14, 21, 28,
+  35, 42, 49, 56, 57, 50, 43, 36,
+  29, 22, 15, 23, 30, 37, 44, 51,
+  58, 59, 52, 45, 38, 31, 39, 46,
+  53, 60, 61, 54, 47, 55, 62, 63
+};
+/* *INDENT-ON* */
+
+/* Table K.1 - Luminance quantization table */
+/* *INDENT-OFF* */
+static const guint8 default_luminance_quant_table[64] = {
+  16,  11,  10,  16,  24,  40,  51,  61,
+  12,  12,  14,  19,  26,  58,  60,  55,
+  14,  13,  16,  24,  40,  57,  69,  56,
+  14,  17,  22,  29,  51,  87,  80,  62,
+  18,  22,  37,  56,  68, 109, 103,  77,
+  24,  35,  55,  64,  81, 104, 113,  92,
+  49,  64,  78,  87, 103, 121, 120, 101,
+  72,  92,  95,  98, 112, 100, 103,  99
+};
+/* *INDENT-ON* */
+
+/* Table K.2 - Chrominance quantization table */
+/* *INDENT-OFF* */
+static const guint8 default_chrominance_quant_table[64] = {
+  17,  18,  24,  47,  99,  99,  99,  99,
+  18,  21,  26,  66,  99,  99,  99,  99,
+  24,  26,  56,  99,  99,  99,  99,  99,
+  47,  66,  99,  99,  99,  99,  99,  99,
+  99,  99,  99,  99,  99,  99,  99,  99,
+  99,  99,  99,  99,  99,  99,  99,  99,
+  99,  99,  99,  99,  99,  99,  99,  99,
+  99,  99,  99,  99,  99,  99,  99,  99
+};
+/* *INDENT-ON* */
+
+typedef struct _GstJpegHuffmanTableEntry GstJpegHuffmanTableEntry;
+struct _GstJpegHuffmanTableEntry
+{
+  guint8 value;                 /* category */
+  guint8 length;                /* code length in bits */
+};
+
+/* Table K.3 - Table for luminance DC coefficient differences */
+static const GstJpegHuffmanTableEntry default_luminance_dc_table[] = {
+  {0x00, 2}, {0x01, 3}, {0x02, 3}, {0x03, 3}, {0x04, 3}, {0x05, 3},
+  {0x06, 4}, {0x07, 5}, {0x08, 6}, {0x09, 7}, {0x0a, 8}, {0x0b, 9}
+};
+
+/* Table K.4 - Table for chrominance DC coefficient differences */
+static const GstJpegHuffmanTableEntry default_chrominance_dc_table[] = {
+  {0x00, 2}, {0x01, 2}, {0x02, 2}, {0x03, 3}, {0x04, 4}, {0x05, 5},
+  {0x06, 6}, {0x07, 7}, {0x08, 8}, {0x09, 9}, {0x0a, 10}, {0x0b, 11}
+};
+
+/* Table K.5 - Table for luminance AC coefficients */
+/* *INDENT-OFF* */
+static const GstJpegHuffmanTableEntry default_luminance_ac_table[] = {
+  {0x00,  4}, {0x01,  2}, {0x02,  2}, {0x03,  3}, {0x04,  4}, {0x05,  5},
+  {0x06,  7}, {0x07,  8}, {0x08, 10}, {0x09, 16}, {0x0a, 16}, {0x11,  4},
+  {0x12,  5}, {0x13,  7}, {0x14,  9}, {0x15, 11}, {0x16, 16}, {0x17, 16},
+  {0x18, 16}, {0x19, 16}, {0x1a, 16}, {0x21,  5}, {0x22,  8}, {0x23, 10},
+  {0x24, 12}, {0x25, 16}, {0x26, 16}, {0x27, 16}, {0x28, 16}, {0x29, 16},
+  {0x2a, 16}, {0x31,  6}, {0x32,  9}, {0x33, 12}, {0x34, 16}, {0x35, 16},
+  {0x36, 16}, {0x37, 16}, {0x38, 16}, {0x39, 16}, {0x3a, 16}, {0x41,  6},
+  {0x42, 10}, {0x43, 16}, {0x44, 16}, {0x45, 16}, {0x46, 16}, {0x47, 16},
+  {0x48, 16}, {0x49, 16}, {0x4a, 16}, {0x51,  7}, {0x52, 11}, {0x53, 16},
+  {0x54, 16}, {0x55, 16}, {0x56, 16}, {0x57, 16}, {0x58, 16}, {0x59, 16},
+  {0x5a, 16}, {0x61,  7}, {0x62, 12}, {0x63, 16}, {0x64, 16}, {0x65, 16},
+  {0x66, 16}, {0x67, 16}, {0x68, 16}, {0x69, 16}, {0x6a, 16}, {0x71,  8},
+  {0x72, 12}, {0x73, 16}, {0x74, 16}, {0x75, 16}, {0x76, 16}, {0x77, 16},
+  {0x78, 16}, {0x79, 16}, {0x7a, 16}, {0x81,  9}, {0x82, 15}, {0x83, 16},
+  {0x84, 16}, {0x85, 16}, {0x86, 16}, {0x87, 16}, {0x88, 16}, {0x89, 16},
+  {0x8a, 16}, {0x91,  9}, {0x92, 16}, {0x93, 16}, {0x94, 16}, {0x95, 16},
+  {0x96, 16}, {0x97, 16}, {0x98, 16}, {0x99, 16}, {0x9a, 16}, {0xa1,  9},
+  {0xa2, 16}, {0xa3, 16}, {0xa4, 16}, {0xa5, 16}, {0xa6, 16}, {0xa7, 16},
+  {0xa8, 16}, {0xa9, 16}, {0xaa, 16}, {0xb1, 10}, {0xb2, 16}, {0xb3, 16},
+  {0xb4, 16}, {0xb5, 16}, {0xb6, 16}, {0xb7, 16}, {0xb8, 16}, {0xb9, 16},
+  {0xba, 16}, {0xc1, 10}, {0xc2, 16}, {0xc3, 16}, {0xc4, 16}, {0xc5, 16},
+  {0xc6, 16}, {0xc7, 16}, {0xc8, 16}, {0xc9, 16}, {0xca, 16}, {0xd1, 11},
+  {0xd2, 16}, {0xd3, 16}, {0xd4, 16}, {0xd5, 16}, {0xd6, 16}, {0xd7, 16},
+  {0xd8, 16}, {0xd9, 16}, {0xda, 16}, {0xe1, 16}, {0xe2, 16}, {0xe3, 16},
+  {0xe4, 16}, {0xe5, 16}, {0xe6, 16}, {0xe7, 16}, {0xe8, 16}, {0xe9, 16},
+  {0xea, 16}, {0xf0, 11}, {0xf1, 16}, {0xf2, 16}, {0xf3, 16}, {0xf4, 16},
+  {0xf5, 16}, {0xf6, 16}, {0xf7, 16}, {0xf8, 16}, {0xf9, 16}, {0xfa, 16}
+};
+/* *INDENT-ON* */
+
+/* Table K.6 - Table for chrominance AC coefficients */
+/* *INDENT-OFF* */
+static const GstJpegHuffmanTableEntry default_chrominance_ac_table[] = {
+  {0x00,  2}, {0x01,  2}, {0x02,  3}, {0x03,  4}, {0x04,  5}, {0x05,  5},
+  {0x06,  6}, {0x07,  7}, {0x08,  9}, {0x09, 10}, {0x0a, 12}, {0x11,  4},
+  {0x12,  6}, {0x13,  8}, {0x14,  9}, {0x15, 11}, {0x16, 12}, {0x17, 16},
+  {0x18, 16}, {0x19, 16}, {0x1a, 16}, {0x21,  5}, {0x22,  8}, {0x23, 10},
+  {0x24, 12}, {0x25, 15}, {0x26, 16}, {0x27, 16}, {0x28, 16}, {0x29, 16},
+  {0x2a, 16}, {0x31,  5}, {0x32,  8}, {0x33, 10}, {0x34, 12}, {0x35, 16},
+  {0x36, 16}, {0x37, 16}, {0x38, 16}, {0x39, 16}, {0x3a, 16}, {0x41,  6},
+  {0x42,  9}, {0x43, 16}, {0x44, 16}, {0x45, 16}, {0x46, 16}, {0x47, 16},
+  {0x48, 16}, {0x49, 16}, {0x4a, 16}, {0x51,  6}, {0x52, 10}, {0x53, 16},
+  {0x54, 16}, {0x55, 16}, {0x56, 16}, {0x57, 16}, {0x58, 16}, {0x59, 16},
+  {0x5a, 16}, {0x61,  7}, {0x62, 11}, {0x63, 16}, {0x64, 16}, {0x65, 16},
+  {0x66, 16}, {0x67, 16}, {0x68, 16}, {0x69, 16}, {0x6a, 16}, {0x71,  7},
+  {0x72, 11}, {0x73, 16}, {0x74, 16}, {0x75, 16}, {0x76, 16}, {0x77, 16},
+  {0x78, 16}, {0x79, 16}, {0x7a, 16}, {0x81,  8}, {0x82, 16}, {0x83, 16},
+  {0x84, 16}, {0x85, 16}, {0x86, 16}, {0x87, 16}, {0x88, 16}, {0x89, 16},
+  {0x8a, 16}, {0x91,  9}, {0x92, 16}, {0x93, 16}, {0x94, 16}, {0x95, 16},
+  {0x96, 16}, {0x97, 16}, {0x98, 16}, {0x99, 16}, {0x9a, 16}, {0xa1,  9},
+  {0xa2, 16}, {0xa3, 16}, {0xa4, 16}, {0xa5, 16}, {0xa6, 16}, {0xa7, 16},
+  {0xa8, 16}, {0xa9, 16}, {0xaa, 16}, {0xb1,  9}, {0xb2, 16}, {0xb3, 16},
+  {0xb4, 16}, {0xb5, 16}, {0xb6, 16}, {0xb7, 16}, {0xb8, 16}, {0xb9, 16},
+  {0xba, 16}, {0xc1,  9}, {0xc2, 16}, {0xc3, 16}, {0xc4, 16}, {0xc5, 16},
+  {0xc6, 16}, {0xc7, 16}, {0xc8, 16}, {0xc9, 16}, {0xca, 16}, {0xd1, 11},
+  {0xd2, 16}, {0xd3, 16}, {0xd4, 16}, {0xd5, 16}, {0xd6, 16}, {0xd7, 16},
+  {0xd8, 16}, {0xd9, 16}, {0xda, 16}, {0xe1, 14}, {0xe2, 16}, {0xe3, 16},
+  {0xe4, 16}, {0xe5, 16}, {0xe6, 16}, {0xe7, 16}, {0xe8, 16}, {0xe9, 16},
+  {0xea, 16}, {0xf0, 10}, {0xf1, 15}, {0xf2, 16}, {0xf3, 16}, {0xf4, 16},
+  {0xf5, 16}, {0xf6, 16}, {0xf7, 16}, {0xf8, 16}, {0xf9, 16}, {0xfa, 16}
+};
+/* *INDENT-ON* */
+
+static gint gst_jpeg_scan_for_marker_code (const guint8 * data, gsize size,
+    guint offset);
+
+static inline gboolean
+jpeg_parse_to_next_marker (GstByteReader * br, GstJpegMarker * marker)
+{
+  gint ofs;
+
+  ofs = gst_jpeg_scan_for_marker_code (br->data, br->size, br->byte);
+  if (ofs < 0)
+    return FALSE;
+
+  if (marker)
+    *marker = (GstJpegMarker) br->data[ofs + 1];
+
+  gst_byte_reader_skip_unchecked (br, ofs - br->byte);
+  return TRUE;
+}
+
+/* gst_jpeg_scan_for_marker_code:
+ * @data: The data to parse
+ * @size: The size of @data
+ * @offset: The offset from which to start parsing
+ *
+ * Scans the JPEG bitstream contained in @data for the next marker
+ * code. If found, the function returns an offset to the marker code,
+ * including the 0xff prefix code but excluding any extra fill bytes.
+ *
+ * Returns: offset to the marker code if found, or -1 if not found.
+ */
+static gint
+gst_jpeg_scan_for_marker_code (const guint8 * data, gsize size, guint offset)
+{
+  guint i;
+
+  i = offset + 1;
+  while (i < size) {
+    const guint8 v = data[i];
+    if (v < 0xc0)
+      i += 2;
+    else if (v < 0xff && data[i - 1] == 0xff)
+      return i - 1;
+    else
+      i++;
+  }
+  return -1;
+}
+
+/**
+ * gst_jpeg_segment_parse_frame_header:
+ * @segment: the JPEG segment
+ * @frame_hdr: (out): The #GstJpegFrameHdr structure to fill in
+ *
+ * Parses the @frame_hdr JPEG frame header structure members from @segment.
+ *
+ * The caller must make sure there is enough data for the whole segment
+ * available.
+ *
+ * Returns: TRUE if the frame header was correctly parsed.
+ *
+ * Since: 1.6
+ */
+gboolean
+gst_jpeg_segment_parse_frame_header (const GstJpegSegment * segment,
+    GstJpegFrameHdr * frame_hdr)
+{
+  GstByteReader br;
+  guint8 val;
+  guint i;
+
+  g_return_val_if_fail (segment != NULL, FALSE);
+  g_return_val_if_fail (frame_hdr != NULL, FALSE);
+
+  if (segment->size < 8)
+    return FALSE;
+
+  gst_byte_reader_init (&br, segment->data + segment->offset, segment->size);
+  gst_byte_reader_skip_unchecked (&br, 2);
+
+  U_READ_UINT8 (&br, frame_hdr->sample_precision);
+  U_READ_UINT16 (&br, frame_hdr->height);
+  U_READ_UINT16 (&br, frame_hdr->width);
+  U_READ_UINT8 (&br, frame_hdr->num_components);
+
+  if (frame_hdr->num_components > GST_JPEG_MAX_SCAN_COMPONENTS)
+    return FALSE;
+
+  if (gst_byte_reader_get_remaining (&br) < 3 * frame_hdr->num_components)
+    return FALSE;
+
+  for (i = 0; i < frame_hdr->num_components; i++) {
+    U_READ_UINT8 (&br, frame_hdr->components[i].identifier);
+    U_READ_UINT8 (&br, val);
+    frame_hdr->components[i].horizontal_factor = (val >> 4) & 0x0F;
+    frame_hdr->components[i].vertical_factor = (val & 0x0F);
+    U_READ_UINT8 (&br, frame_hdr->components[i].quant_table_selector);
+    if (frame_hdr->components[i].horizontal_factor > 4
+        || frame_hdr->components[i].vertical_factor > 4
+        || frame_hdr->components[i].quant_table_selector >= 4)
+      return FALSE;
+  }
+
+  if (gst_byte_reader_get_remaining (&br) > 0)
+    GST_DEBUG ("data left at end of frame header segment");
+
+  return TRUE;
+}
+
+/**
+ * gst_jpeg_segment_parse_scan_header:
+ * @segment: the JPEG segment
+ * @scan_hdr: (out): The #GstJpegScanHdr structure to fill in
+ *
+ * Parses the @scan_hdr JPEG scan header structure members from @segment.
+ *
+ * The caller must make sure there is enough data for the whole segment
+ * available.
+ *
+ * Returns: TRUE if the scan header was correctly parsed
+ *
+ * Since: 1.6
+ */
+gboolean
+gst_jpeg_segment_parse_scan_header (const GstJpegSegment * segment,
+    GstJpegScanHdr * scan_hdr)
+{
+  GstByteReader br;
+  guint8 val;
+  guint i;
+
+  g_return_val_if_fail (segment != NULL, FALSE);
+  g_return_val_if_fail (scan_hdr != NULL, FALSE);
+
+  gst_byte_reader_init (&br, segment->data + segment->offset, segment->size);
+
+  if (segment->size < 3)
+    return FALSE;
+
+  gst_byte_reader_skip_unchecked (&br, 2);
+
+  U_READ_UINT8 (&br, scan_hdr->num_components);
+
+  if (scan_hdr->num_components > GST_JPEG_MAX_SCAN_COMPONENTS)
+    return FALSE;
+
+  if (gst_byte_reader_get_remaining (&br) < 2 * scan_hdr->num_components)
+    return FALSE;
+
+  for (i = 0; i < scan_hdr->num_components; i++) {
+    U_READ_UINT8 (&br, scan_hdr->components[i].component_selector);
+    U_READ_UINT8 (&br, val);
+    scan_hdr->components[i].dc_selector = (val >> 4) & 0x0F;
+    scan_hdr->components[i].ac_selector = val & 0x0F;
+    if (scan_hdr->components[i].dc_selector >= 4
+        || scan_hdr->components[i].ac_selector >= 4)
+      return FALSE;
+  }
+
+  if (gst_byte_reader_get_remaining (&br) < 3)
+    return FALSE;
+
+  /* FIXME: Ss, Se, Ah, Al */
+  gst_byte_reader_skip_unchecked (&br, 3);
+
+  if (gst_byte_reader_get_remaining (&br) > 0)
+    GST_DEBUG ("data left at end of scan header segment");
+
+  return TRUE;
+}
+
+/**
+ * gst_jpeg_segment_parse_huffman_table:
+ * @segment: the JPEG segment
+ * @huff_tables: (out): The #GstJpegHuffmanTables structure to fill in
+ *
+ * Parses the JPEG Huffman table structure members from @segment.
+ *
+ * The caller must make sure there is enough data for the whole segment
+ * available.
+ *
+ * Note: @huf_tables represents the complete set of possible Huffman
+ * tables. However, the parser will only write to the Huffman table
+ * specified by the table destination identifier (Th). While doing so,
+ * the @valid flag of the specified Huffman table will also be set to
+ * %TRUE;
+ *
+ * Returns: TRUE if the Huffman table was correctly parsed.
+ *
+ * Since: 1.6
+ */
+gboolean
+gst_jpeg_segment_parse_huffman_table (const GstJpegSegment * segment,
+    GstJpegHuffmanTables * huff_tables)
+{
+  GstByteReader br;
+  GstJpegHuffmanTable *huf_table;
+  guint8 val, table_class, table_index;
+  guint32 value_count;
+  guint i;
+
+  g_return_val_if_fail (segment != NULL, FALSE);
+  g_return_val_if_fail (huff_tables != NULL, FALSE);
+
+  if (segment->size < 2)
+    return FALSE;
+
+  gst_byte_reader_init (&br, segment->data + segment->offset, segment->size);
+
+  gst_byte_reader_skip_unchecked (&br, 2);
+
+  while (gst_byte_reader_get_remaining (&br) > 0) {
+    U_READ_UINT8 (&br, val);
+    table_class = ((val >> 4) & 0x0F);
+    table_index = (val & 0x0F);
+    if (table_index >= GST_JPEG_MAX_SCAN_COMPONENTS)
+      return FALSE;
+    if (table_class == 0) {
+      huf_table = &huff_tables->dc_tables[table_index];
+    } else {
+      huf_table = &huff_tables->ac_tables[table_index];
+    }
+    READ_BYTES (&br, huf_table->huf_bits, 16);
+    value_count = 0;
+    for (i = 0; i < 16; i++)
+      value_count += huf_table->huf_bits[i];
+    READ_BYTES (&br, huf_table->huf_values, value_count);
+    huf_table->valid = TRUE;
+  }
+  return TRUE;
+
+failed:
+  return FALSE;
+}
+
+/**
+ * gst_jpeg_segment_parse_quantization_table:
+ * @segment: the JPEG segment
+ * @quant_tables: (out): The #GstJpegQuantTables structure to fill in
+ *
+ * Parses the JPEG quantization table structure members from @segment.
+ *
+ * The caller must make sure there is enough data for the whole segment
+ * available.
+ *
+ * Note: @quant_tables represents the complete set of possible
+ * quantization tables. However, the parser will only write to the
+ * quantization table specified by the table destination identifier
+ * (Tq). While doing so, the @valid flag of the specified quantization
+ * table will also be set to %TRUE.
+ *
+ * Returns: TRUE if the quantization table was correctly parsed.
+ *
+ * Since: 1.6
+ */
+gboolean
+gst_jpeg_segment_parse_quantization_table (const GstJpegSegment * segment,
+    GstJpegQuantTables * quant_tables)
+{
+  GstByteReader br;
+  GstJpegQuantTable *quant_table;
+  guint8 val, table_index;
+  guint i;
+
+  g_return_val_if_fail (segment != NULL, FALSE);
+  g_return_val_if_fail (quant_tables != NULL, FALSE);
+
+  if (segment->size < 2)
+    return FALSE;
+
+  gst_byte_reader_init (&br, segment->data + segment->offset, segment->size);
+
+  gst_byte_reader_skip_unchecked (&br, 2);
+
+  while (gst_byte_reader_get_remaining (&br) > 0) {
+    guint8 element_size;
+
+    U_READ_UINT8 (&br, val);
+    table_index = (val & 0x0f);
+    if (table_index >= GST_JPEG_MAX_SCAN_COMPONENTS)
+      return FALSE;
+    quant_table = &quant_tables->quant_tables[table_index];
+    quant_table->quant_precision = ((val >> 4) & 0x0f);
+
+    element_size = (quant_table->quant_precision == 0) ? 1 : 2;
+    if (gst_byte_reader_get_remaining (&br) <
+        GST_JPEG_MAX_QUANT_ELEMENTS * element_size)
+      return FALSE;
+    for (i = 0; i < GST_JPEG_MAX_QUANT_ELEMENTS; i++) {
+      if (!quant_table->quant_precision) {      /* 8-bit values */
+        U_READ_UINT8 (&br, val);
+        quant_table->quant_table[i] = val;
+      } else {                  /* 16-bit values */
+        U_READ_UINT16 (&br, quant_table->quant_table[i]);
+      }
+    }
+    quant_table->valid = TRUE;
+  }
+  return TRUE;
+}
+
+/**
+ * gst_jpeg_segment_parse_restart_interval:
+ * @segment: the JPEG segment
+ * @interval: (out): The parsed restart interval value
+ *
+ * The caller must make sure there is enough data for the whole segment
+ * available.
+ *
+ * Returns: TRUE if the restart interval value was correctly parsed.
+ *
+ * Since: 1.6
+ */
+gboolean
+gst_jpeg_segment_parse_restart_interval (const GstJpegSegment * segment,
+    guint * interval)
+{
+  GstByteReader br;
+  guint16 val;
+
+  g_return_val_if_fail (segment != NULL, FALSE);
+  g_return_val_if_fail (interval != NULL, FALSE);
+
+  if (segment->size < 4)
+    return FALSE;
+
+  gst_byte_reader_init (&br, segment->data + segment->offset, segment->size);
+  gst_byte_reader_skip_unchecked (&br, 2);
+
+  U_READ_UINT16 (&br, val);
+  *interval = val;
+  return TRUE;
+}
+
+static int
+compare_huffman_table_entry (const void *a, const void *b)
+{
+  const GstJpegHuffmanTableEntry *const e1 = *(GstJpegHuffmanTableEntry **) a;
+  const GstJpegHuffmanTableEntry *const e2 = *(GstJpegHuffmanTableEntry **) b;
+
+  if (e1->length == e2->length)
+    return (gint) e1->value - (gint) e2->value;
+  return (gint) e1->length - (gint) e2->length;
+}
+
+static void
+build_huffman_table (GstJpegHuffmanTable * huf_table,
+    const GstJpegHuffmanTableEntry * entries, guint num_entries)
+{
+  const GstJpegHuffmanTableEntry *sorted_entries[256];
+  guint i, j, n;
+
+  g_assert (num_entries <= G_N_ELEMENTS (sorted_entries));
+
+  for (i = 0; i < num_entries; i++)
+    sorted_entries[i] = &entries[i];
+  qsort ((void *) sorted_entries, num_entries, sizeof (sorted_entries[0]),
+      compare_huffman_table_entry);
+
+  for (i = 0, j = 1, n = 0; i < num_entries; i++) {
+    const GstJpegHuffmanTableEntry *const e = sorted_entries[i];
+    if (e->length != j) {
+      huf_table->huf_bits[j++ - 1] = n;
+      for (; j < e->length; j++)
+        huf_table->huf_bits[j - 1] = 0;
+      n = 0;
+    }
+    huf_table->huf_values[i] = e->value;
+    n++;
+  }
+  huf_table->huf_bits[j - 1] = n;
+
+  for (; j < G_N_ELEMENTS (huf_table->huf_bits); j++)
+    huf_table->huf_bits[j] = 0;
+  for (; i < G_N_ELEMENTS (huf_table->huf_values); i++)
+    huf_table->huf_values[i] = 0;
+  huf_table->valid = TRUE;
+}
+
+/**
+ * gst_jpeg_get_default_huffman_tables:
+ * @huf_tables: (out): The default DC/AC Huffman tables to fill in
+ *
+ * Fills in @huf_tables with the default AC/DC Huffman tables, as
+ * specified by the JPEG standard.
+ *
+ * Since: 1.6
+ */
+void
+gst_jpeg_get_default_huffman_tables (GstJpegHuffmanTables * huf_tables)
+{
+  g_return_if_fail (huf_tables != NULL);
+
+  /* Build DC tables */
+  build_huffman_table (&huf_tables->dc_tables[0], default_luminance_dc_table,
+      G_N_ELEMENTS (default_luminance_dc_table));
+  build_huffman_table (&huf_tables->dc_tables[1], default_chrominance_dc_table,
+      G_N_ELEMENTS (default_chrominance_dc_table));
+  memcpy (&huf_tables->dc_tables[2], &huf_tables->dc_tables[1],
+      sizeof (huf_tables->dc_tables[2]));
+
+  /* Build AC tables */
+  build_huffman_table (&huf_tables->ac_tables[0], default_luminance_ac_table,
+      G_N_ELEMENTS (default_luminance_ac_table));
+  build_huffman_table (&huf_tables->ac_tables[1], default_chrominance_ac_table,
+      G_N_ELEMENTS (default_chrominance_ac_table));
+  memcpy (&huf_tables->ac_tables[2], &huf_tables->ac_tables[1],
+      sizeof (huf_tables->ac_tables[2]));
+}
+
+static void
+build_quant_table (GstJpegQuantTable * quant_table, const guint8 values[64])
+{
+  guint i;
+
+  for (i = 0; i < 64; i++)
+    quant_table->quant_table[i] = values[zigzag_index[i]];
+  quant_table->quant_precision = 0;     /* Pq = 0 (8-bit precision) */
+  quant_table->valid = TRUE;
+}
+
+/**
+ * gst_jpeg_get_default_quantization_table:
+ * @quant_tables: (out): The default luma/chroma quant-tables in zigzag mode
+ *
+ * Fills in @quant_tables with the default quantization tables, as
+ * specified by the JPEG standard.
+ *
+ * Since: 1.6
+ */
+void
+gst_jpeg_get_default_quantization_tables (GstJpegQuantTables * quant_tables)
+{
+  g_return_if_fail (quant_tables != NULL);
+
+  build_quant_table (&quant_tables->quant_tables[0],
+      default_luminance_quant_table);
+  build_quant_table (&quant_tables->quant_tables[1],
+      default_chrominance_quant_table);
+  build_quant_table (&quant_tables->quant_tables[2],
+      default_chrominance_quant_table);
+}
+
+/**
+ * gst_jpeg_parse:
+ * @segment: (out): pointer to a #GstJpegSegment structure to fill in
+ * @data: The data to parse
+ * @size: The size of @data
+ * @offset: The offset from which to start parsing
+ *
+ * Parses the JPEG bitstream contained in @data, and returns the
+ * detected segment as a #GstJpegSegment.
+ *
+ * Note that a valid segment may be returned with a length that exceeds
+ * the available data. It is up to the caller to make sure there's enough
+ * data available when parsing the segment.
+ *
+ * Returns: TRUE if a packet start code was found.
+ *
+ * Since: 1.6
+ */
+gboolean
+gst_jpeg_parse (GstJpegSegment * segment,
+    const guint8 * data, gsize size, guint offset)
+{
+  GstJpegSegment *seg = segment;
+  GstByteReader br;
+  guint16 length;
+
+  g_return_val_if_fail (seg != NULL, FALSE);
+  g_return_val_if_fail (data != NULL, FALSE);
+
+  if (size <= offset) {
+    GST_DEBUG ("failed to parse from offset %u, buffer is too small", offset);
+    return FALSE;
+  }
+
+  size -= offset;
+  gst_byte_reader_init (&br, &data[offset], size);
+
+  if (!jpeg_parse_to_next_marker (&br, &seg->marker)) {
+    GST_DEBUG ("failed to find marker code");
+    return FALSE;
+  }
+
+  gst_byte_reader_skip_unchecked (&br, 2);
+  seg->offset = offset + gst_byte_reader_get_pos (&br);
+  seg->size = -1;
+
+  /* Try to find end of segment */
+  switch (seg->marker) {
+    case GST_JPEG_MARKER_SOI:
+    case GST_JPEG_MARKER_EOI:
+    fixed_size_segment:
+      seg->size = 0;
+      break;
+
+    case (GST_JPEG_MARKER_SOF_MIN + 0):        /* Lf */
+    case (GST_JPEG_MARKER_SOF_MIN + 1):        /* Lf */
+    case (GST_JPEG_MARKER_SOF_MIN + 2):        /* Lf */
+    case (GST_JPEG_MARKER_SOF_MIN + 3):        /* Lf */
+    case (GST_JPEG_MARKER_SOF_MIN + 9):        /* Lf */
+    case (GST_JPEG_MARKER_SOF_MIN + 10):       /* Lf */
+    case (GST_JPEG_MARKER_SOF_MIN + 11):       /* Lf */
+    case GST_JPEG_MARKER_SOS:  /* Ls */
+    case GST_JPEG_MARKER_DQT:  /* Lq */
+    case GST_JPEG_MARKER_DHT:  /* Lh */
+    case GST_JPEG_MARKER_DAC:  /* La */
+    case GST_JPEG_MARKER_DRI:  /* Lr */
+    case GST_JPEG_MARKER_COM:  /* Lc */
+    case GST_JPEG_MARKER_DNL:  /* Ld */
+    variable_size_segment:
+      READ_UINT16 (&br, length);
+      seg->size = length;
+      break;
+
+    default:
+      /* Application data segment length (Lp) */
+      if (seg->marker >= GST_JPEG_MARKER_APP_MIN &&
+          seg->marker <= GST_JPEG_MARKER_APP_MAX)
+        goto variable_size_segment;
+
+      /* Restart markers (fixed size, two bytes only) */
+      if (seg->marker >= GST_JPEG_MARKER_RST_MIN &&
+          seg->marker <= GST_JPEG_MARKER_RST_MAX)
+        goto fixed_size_segment;
+
+      /* Fallback: scan for next marker */
+      if (!jpeg_parse_to_next_marker (&br, NULL))
+        goto failed;
+      seg->size = gst_byte_reader_get_pos (&br) - seg->offset;
+      break;
+  }
+
+  seg->data = data;
+  return TRUE;
+
+failed:
+  return FALSE;
+}
diff --git a/gst/jpegformat/gstjpegparser.h b/gst/jpegformat/gstjpegparser.h
new file mode 100644
index 0000000..cbdf17e
--- /dev/null
+++ b/gst/jpegformat/gstjpegparser.h
@@ -0,0 +1,412 @@
+/*  GStreamer JPEG parser
+ *  Copyright (C) 2011-2012 Intel Corporation
+ *  Copyright (C) 2015 Tim-Philipp Müller <tim@centricular.com>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1
+ *  of the License, or (at your option) any later version.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free
+ *  Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ *  Boston, MA 02110-1301 USA
+ */
+
+#ifndef GST_JPEG_PARSER_H
+#define GST_JPEG_PARSER_H
+
+#ifndef GST_USE_UNSTABLE_API
+#  warning "The JPEG parsing library is unstable API and may change in future."
+#  warning "You can define GST_USE_UNSTABLE_API to avoid this warning."
+#endif
+
+#include <gst/gst.h>
+#include "codecparsers-prelude.h"
+
+G_BEGIN_DECLS
+
+/**
+ * GST_JPEG_MAX_FRAME_COMPONENTS:
+ *
+ * Maximum number of image components in a frame (Nf).
+ *
+ * Since: 1.6
+ */
+#define GST_JPEG_MAX_FRAME_COMPONENTS   256
+
+/**
+ * GST_JPEG_MAX_SCAN_COMPONENTS:
+ *
+ * Maximum number of image components in a scan (Ns).
+ *
+ * Since: 1.6
+ */
+#define GST_JPEG_MAX_SCAN_COMPONENTS    4
+
+/**
+ * GST_JPEG_MAX_QUANT_ELEMENTS:
+ *
+ * Number of elements in the quantization table.
+ *
+ * Since: 1.6
+ */
+#define GST_JPEG_MAX_QUANT_ELEMENTS     64
+
+typedef struct _GstJpegQuantTable       GstJpegQuantTable;
+typedef struct _GstJpegQuantTables      GstJpegQuantTables;
+typedef struct _GstJpegHuffmanTable     GstJpegHuffmanTable;
+typedef struct _GstJpegHuffmanTables    GstJpegHuffmanTables;
+typedef struct _GstJpegScanComponent    GstJpegScanComponent;
+typedef struct _GstJpegScanHdr          GstJpegScanHdr;
+typedef struct _GstJpegFrameComponent   GstJpegFrameComponent;
+typedef struct _GstJpegFrameHdr         GstJpegFrameHdr;
+typedef struct _GstJpegSegment          GstJpegSegment;
+
+/**
+ * GstJpegMarker:
+ * @GST_JPEG_MARKER_SOF0: Start of frame marker code (Baseline)
+ * @GST_JPEG_MARKER_SOF1: Start of frame marker code (Extended Sequential, Huffman)
+ * @GST_JPEG_MARKER_SOF2: Start of frame marker code (Progressive, Huffman)
+ * @GST_JPEG_MARKER_SOF3: Start of frame marker code (Lossless, Huffman)
+ * @GST_JPEG_MARKER_SOF5: Start of frame marker code (Differential Sequential, Huffman)
+ * @GST_JPEG_MARKER_SOF6: Start of frame marker code (Differential Progressive, Huffman)
+ * @GST_JPEG_MARKER_SOF7: Start of frame marker code (Differential Lossless, Huffman)
+ * @GST_JPEG_MARKER_SOF9: Start of frame marker code (Extended Sequential, Arithmetic)
+ * @GST_JPEG_MARKER_SOF10: Start of frame marker code (Progressive, Arithmetic)
+ * @GST_JPEG_MARKER_SOF11: Start of frame marker code (Lossless, Arithmetic)
+ * @GST_JPEG_MARKER_SOF13: Start of frame marker code (Differential Sequential, Arithmetic)
+ * @GST_JPEG_MARKER_SOF14: Start of frame marker code (Differential Progressive, Arithmetic)
+ * @GST_JPEG_MARKER_SOF15: Start of frame marker code (Differential Lossless, Arithmetic)
+ * @GST_JPEG_MARKER_DHT: Huffman table marker code
+ * @GST_JPEG_MARKER_DAC: Arithmetic coding marker code
+ * @GST_JPEG_MARKER_RST_MIN: Restart interval min marker code
+ * @GST_JPEG_MARKER_RST_MAX: Restart interval max marker code
+ * @GST_JPEG_MARKER_SOI: Start of image marker code
+ * @GST_JPEG_MARKER_EOI: End of image marker code
+ * @GST_JPEG_MARKER_SOS: Start of scan marker code
+ * @GST_JPEG_MARKER_DQT: Define quantization table marker code
+ * @GST_JPEG_MARKER_DNL: Define number of lines marker code
+ * @GST_JPEG_MARKER_DRI: Define restart interval marker code
+ * @GST_JPEG_MARKER_APP0: Application segment 0 marker code
+ * @GST_JPEG_MARKER_APP1: Application segment 1 marker code
+ * @GST_JPEG_MARKER_APP2: Application segment 2 marker code
+ * @GST_JPEG_MARKER_APP3: Application segment 3 marker code
+ * @GST_JPEG_MARKER_APP4: Application segment 4 marker code
+ * @GST_JPEG_MARKER_APP5: Application segment 5 marker code
+ * @GST_JPEG_MARKER_APP6: Application segment 6 marker code
+ * @GST_JPEG_MARKER_APP7: Application segment 7 marker code
+ * @GST_JPEG_MARKER_APP8: Application segment 8 marker code
+ * @GST_JPEG_MARKER_APP9: Application segment 9 marker code
+ * @GST_JPEG_MARKER_APP10: Application segment 10 marker code
+ * @GST_JPEG_MARKER_APP11: Application segment 11 marker code
+ * @GST_JPEG_MARKER_APP12: Application segment 12 marker code
+ * @GST_JPEG_MARKER_APP13: Application segment 13 marker code
+ * @GST_JPEG_MARKER_APP14: Application segment 14 marker code
+ * @GST_JPEG_MARKER_APP15: Application segment 15 marker code
+ * @GST_JPEG_MARKER_COM: Comment marker code
+ *
+ * Indicates the type of JPEG segment.
+ *
+ * Since: 1.6
+ */
+typedef enum {
+  GST_JPEG_MARKER_SOF0          = 0xC0,
+  GST_JPEG_MARKER_SOF1          = 0xC1,
+  GST_JPEG_MARKER_SOF2          = 0xC2,
+  GST_JPEG_MARKER_SOF3          = 0xC3,
+  /* 0xC4 = DHT see below */
+  GST_JPEG_MARKER_SOF5          = 0xC5,
+  GST_JPEG_MARKER_SOF6          = 0xC6,
+  GST_JPEG_MARKER_SOF7          = 0xC7,
+  /* 0xC8 = reserved */
+  GST_JPEG_MARKER_SOF9          = 0xC9,
+  GST_JPEG_MARKER_SOF10         = 0xCA,
+  GST_JPEG_MARKER_SOF11         = 0xCB,
+  /* 0xCC = DAC see below */
+  GST_JPEG_MARKER_SOF13         = 0xCD,
+  GST_JPEG_MARKER_SOF14         = 0xCE,
+  GST_JPEG_MARKER_SOF15         = 0xCF,
+  GST_JPEG_MARKER_DHT           = 0xC4,
+  GST_JPEG_MARKER_DAC           = 0xCC,
+  GST_JPEG_MARKER_RST0          = 0xD0,
+  GST_JPEG_MARKER_RST1          = 0xD1,
+  GST_JPEG_MARKER_RST2          = 0xD2,
+  GST_JPEG_MARKER_RST3          = 0xD3,
+  GST_JPEG_MARKER_RST4          = 0xD4,
+  GST_JPEG_MARKER_RST5          = 0xD5,
+  GST_JPEG_MARKER_RST6          = 0xD6,
+  GST_JPEG_MARKER_RST7          = 0xD7,
+  GST_JPEG_MARKER_SOI           = 0xD8,
+  GST_JPEG_MARKER_EOI           = 0xD9,
+  GST_JPEG_MARKER_SOS           = 0xDA,
+  GST_JPEG_MARKER_DQT           = 0xDB,
+  GST_JPEG_MARKER_DNL           = 0xDC,
+  GST_JPEG_MARKER_DRI           = 0xDD,
+  GST_JPEG_MARKER_APP0          = 0xE0,
+  GST_JPEG_MARKER_APP1          = 0xE1,
+  GST_JPEG_MARKER_APP2          = 0xE2,
+  GST_JPEG_MARKER_APP3          = 0xE3,
+  GST_JPEG_MARKER_APP4          = 0xE4,
+  GST_JPEG_MARKER_APP5          = 0xE5,
+  GST_JPEG_MARKER_APP6          = 0xE6,
+  GST_JPEG_MARKER_APP7          = 0xE7,
+  GST_JPEG_MARKER_APP8          = 0xE8,
+  GST_JPEG_MARKER_APP9          = 0xE9,
+  GST_JPEG_MARKER_APP10         = 0xEA,
+  GST_JPEG_MARKER_APP11         = 0xEB,
+  GST_JPEG_MARKER_APP12         = 0xEC,
+  GST_JPEG_MARKER_APP13         = 0xED,
+  GST_JPEG_MARKER_APP14         = 0xEE,
+  GST_JPEG_MARKER_APP15         = 0xEF,
+  GST_JPEG_MARKER_COM           = 0xFE,
+} GstJpegMarker;
+
+#define GST_JPEG_MARKER_SOF_MIN GST_JPEG_MARKER_SOF0
+#define GST_JPEG_MARKER_SOF_MAX GST_JPEG_MARKER_SOF15
+
+#define GST_JPEG_MARKER_APP_MIN GST_JPEG_MARKER_APP0
+#define GST_JPEG_MARKER_APP_MAX GST_JPEG_MARKER_APP15
+
+#define GST_JPEG_MARKER_RST_MIN GST_JPEG_MARKER_RST0
+#define GST_JPEG_MARKER_RST_MAX GST_JPEG_MARKER_RST7
+
+/**
+ * GstJpegProfile:
+ * @GST_JPEG_PROFILE_BASELINE: Baseline DCT
+ * @GST_JPEG_PROFILE_EXTENDED: Extended sequential DCT
+ * @GST_JPEG_PROFILE_PROGRESSIVE: Progressive DCT
+ * @GST_JPEG_PROFILE_LOSSLESS: Lossless (sequential)
+ *
+ * JPEG encoding processes.
+ *
+ * Since: 1.6
+ */
+typedef enum {
+  GST_JPEG_PROFILE_BASELINE     = 0x00,
+  GST_JPEG_PROFILE_EXTENDED     = 0x01,
+  GST_JPEG_PROFILE_PROGRESSIVE  = 0x02,
+  GST_JPEG_PROFILE_LOSSLESS     = 0x03,
+} GstJpegProfile;
+
+/**
+ * GstJpegEntropyCodingMode:
+ * @GST_JPEG_ENTROPY_CODING_HUFFMAN: Huffman coding
+ * @GST_JPEG_ENTROPY_CODING_ARITHMETIC: arithmetic coding
+ *
+ * JPEG entropy coding mode.
+ *
+ * Since: 1.6
+ */
+typedef enum {
+  GST_JPEG_ENTROPY_CODING_HUFFMAN       = 0x00,
+  GST_JPEG_ENTROPY_CODING_ARITHMETIC    = 0x08
+} GstJpegEntropyCodingMode;
+
+/**
+ * GstJpegQuantTable:
+ * @quant_precision: Quantization table element precision (Pq)
+ * @quant_table: Quantization table elements (Qk)
+ * @valid: If the quantization table is valid, which means it has
+ *   already been parsed
+ *
+ * Quantization table.
+ *
+ * Since: 1.6
+ */
+struct _GstJpegQuantTable
+{
+  guint8 quant_precision;
+  guint16 quant_table[GST_JPEG_MAX_QUANT_ELEMENTS];
+  gboolean valid;
+};
+
+/**
+ * GstJpegQuantTables:
+ * @quant_tables: All quantization tables
+ *
+ * Helper data structure that holds all quantization tables used to
+ * decode an image.
+ *
+ * Since: 1.6
+ */
+struct _GstJpegQuantTables
+{
+  GstJpegQuantTable quant_tables[GST_JPEG_MAX_SCAN_COMPONENTS];
+};
+
+/**
+ * GstJpegHuffmanTable:
+ * @huf_bits: Number of Huffman codes of length i + 1 (Li)
+ * @huf_vales: Value associated with each Huffman code (Vij)
+ * @valid: If the Huffman table is valid, which means it has already
+ *   been parsed
+ *
+ * Huffman table.
+ *
+ * Since: 1.6
+ */
+struct _GstJpegHuffmanTable
+{
+  guint8 huf_bits[16];
+  guint8 huf_values[256];
+  gboolean valid;
+};
+
+/**
+ * GstJpegHuffmanTables:
+ * @dc_tables: DC Huffman tables
+ * @ac_tables: AC Huffman tables
+ *
+ * Helper data structure that holds all AC/DC Huffman tables used to
+ * decode an image.
+ *
+ * Since: 1.6
+ */
+struct _GstJpegHuffmanTables
+{
+  GstJpegHuffmanTable dc_tables[GST_JPEG_MAX_SCAN_COMPONENTS];
+  GstJpegHuffmanTable ac_tables[GST_JPEG_MAX_SCAN_COMPONENTS];
+};
+
+/**
+ * GstJpegScanComponent:
+ * @component_selector: Scan component selector (Csj)
+ * @dc_selector: DC entropy coding table destination selector (Tdj)
+ * @ac_selector: AC entropy coding table destination selector (Taj)
+
+ * Component-specification parameters.
+ *
+ * Since: 1.6
+ */
+struct _GstJpegScanComponent
+{
+    guint8 component_selector;          /* 0 .. 255     */
+    guint8 dc_selector;                 /* 0 .. 3       */
+    guint8 ac_selector;                 /* 0 .. 3       */
+};
+
+/**
+ * GstJpegScanHdr:
+ * @num_components: Number of image components in scan (Ns)
+ * @components: Image components
+ *
+ * Scan header.
+ *
+ * Since: 1.6
+ */
+struct _GstJpegScanHdr
+{
+  guint8 num_components;                /* 1 .. 4       */
+  GstJpegScanComponent components[GST_JPEG_MAX_SCAN_COMPONENTS];
+
+  /*< private >*/
+  guint8 _reserved1; /* Ss */
+  guint8 _reserved2; /* Se */
+  guint8 _reserved3; /* Al */
+  guint8 _reserved4; /* Ah */
+};
+
+/**
+ * GstJpegFrameComponent:
+ * @identifier: Component identifier (Ci)
+ * @horizontal_factor: Horizontal sampling factor (Hi)
+ * @vertical_factor: Vertical sampling factor (Vi)
+ * @quant_table_selector: Quantization table destination selector (Tqi)
+ *
+ * Component-specification parameters.
+ *
+ * Since: 1.6
+ */
+struct _GstJpegFrameComponent
+{
+  guint8 identifier;                    /* 0 .. 255     */
+  guint8 horizontal_factor;             /* 1 .. 4       */
+  guint8 vertical_factor;               /* 1 .. 4       */
+  guint8 quant_table_selector;          /* 0 .. 3       */
+};
+
+/**
+ * GstJpegFrameHdr:
+ * @sample_precision: Sample precision (P)
+ * @height: Number of lines (Y)
+ * @width: Number of samples per line (X)
+ * @num_components: Number of image components in frame (Nf)
+ * @components: Image components
+ * @restart_interval: Number of MCU in the restart interval (Ri)
+ *
+ * Frame header.
+ *
+ * Since: 1.6
+ */
+struct _GstJpegFrameHdr
+{
+  guint8 sample_precision;              /* 2 .. 16      */
+  guint16 width;                        /* 1 .. 65535   */
+  guint16 height;                       /* 0 .. 65535   */
+  guint8 num_components;                /* 1 .. 255     */
+  GstJpegFrameComponent components[GST_JPEG_MAX_FRAME_COMPONENTS];
+};
+
+/**
+ * GstJpegSegment:
+ * @marker: The type of the segment that starts at @offset
+ * @data: the data containing the jpeg segment starting at @offset
+ * @offset: The offset to the segment start in bytes. This is the
+ *   exact start of the segment, no marker code included
+ * @size: The size of the segment in bytes, or -1 if the end was not
+ *   found. It is the exact size of the segment, without the sync byte and
+ *   marker code but including any length bytes.
+ *
+ * A structure that contains the type of a segment, its offset and its size.
+ *
+ * Since: 1.6
+ */
+struct _GstJpegSegment
+{
+  GstJpegMarker marker;
+  const guint8 *data;
+  guint offset;
+  gssize size;
+};
+
+GST_CODEC_PARSERS_API
+gboolean  gst_jpeg_parse (GstJpegSegment * seg,
+                          const guint8   * data,
+                          gsize            size,
+                          guint            offset);
+
+GST_CODEC_PARSERS_API
+gboolean  gst_jpeg_segment_parse_frame_header  (const GstJpegSegment  * segment,
+                                                GstJpegFrameHdr       * frame_hdr);
+
+GST_CODEC_PARSERS_API
+gboolean  gst_jpeg_segment_parse_scan_header   (const GstJpegSegment * segment,
+                                                GstJpegScanHdr       * scan_hdr);
+
+GST_CODEC_PARSERS_API
+gboolean  gst_jpeg_segment_parse_huffman_table (const GstJpegSegment * segment,
+                                                GstJpegHuffmanTables * huff_tables);
+
+GST_CODEC_PARSERS_API
+gboolean  gst_jpeg_segment_parse_restart_interval (const GstJpegSegment * segment,
+                                                   guint                * interval);
+
+GST_CODEC_PARSERS_API
+gboolean  gst_jpeg_segment_parse_quantization_table (const GstJpegSegment * segment,
+                                                     GstJpegQuantTables   * quant_tables);
+
+GST_CODEC_PARSERS_API
+void      gst_jpeg_get_default_quantization_tables (GstJpegQuantTables * quant_tables);
+
+GST_CODEC_PARSERS_API
+void      gst_jpeg_get_default_huffman_tables (GstJpegHuffmanTables * huff_tables);
+
+G_END_DECLS
+
+#endif /* GST_JPEG_PARSER_H */
diff --git a/gst/jpegformat/gstjifmux.c b/gst/jpegformat/gstjifmux.c
new file mode 100644
index 0000000..ab89d03
--- /dev/null
+++ b/gst/jpegformat/gstjifmux.c
@@ -0,0 +1,766 @@
+/* GStreamer
+ *
+ * jifmux: JPEG interchange format muxer
+ *
+ * Copyright (C) 2010 Stefan Kost <stefan.kost@nokia.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+/**
+ * SECTION:element-jifmux
+ * @title: jifmux
+ * @short_description: JPEG interchange format writer
+ *
+ * Writes a JPEG image as JPEG/EXIF or JPEG/JFIF including various metadata. The
+ * jpeg image received on the sink pad should be minimal (e.g. should not
+ * contain metadata already).
+ *
+ * ## Example launch line
+ * |[
+ * gst-launch-1.0 -v videotestsrc num-buffers=1 ! jpegenc ! jifmux ! filesink location=...
+ * ]|
+ * The above pipeline renders a frame, encodes to jpeg, adds metadata and writes
+ * it to disk.
+ *
+ */
+/*
+jpeg interchange format:
+file header : SOI, APPn{JFIF,EXIF,...}
+frame header: DQT, SOF
+scan header : {DAC,DHT},DRI,SOS
+<scan data>
+file trailer: EOI
+
+tests:
+gst-launch-1.0 videotestsrc num-buffers=1 ! jpegenc ! jifmux ! filesink location=test1.jpeg
+gst-launch-1.0 videotestsrc num-buffers=1 ! jpegenc ! taginject tags="comment=test image" ! jifmux ! filesink location=test2.jpeg
+*/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+#include <gst/base/gstbytereader.h>
+#include <gst/base/gstbytewriter.h>
+#include "gstjpegparser.h"
+#include <gst/tag/tag.h>
+#include <gst/tag/xmpwriter.h>
+
+#include "gstjifmux.h"
+
+static GstStaticPadTemplate gst_jif_mux_src_pad_template =
+GST_STATIC_PAD_TEMPLATE ("src",
+    GST_PAD_SRC,
+    GST_PAD_ALWAYS,
+    GST_STATIC_CAPS ("image/jpeg")
+    );
+
+static GstStaticPadTemplate gst_jif_mux_sink_pad_template =
+GST_STATIC_PAD_TEMPLATE ("sink",
+    GST_PAD_SINK,
+    GST_PAD_ALWAYS,
+    GST_STATIC_CAPS ("image/jpeg")
+    );
+
+GST_DEBUG_CATEGORY_STATIC (jif_mux_debug);
+#define GST_CAT_DEFAULT jif_mux_debug
+
+#define COLORSPACE_UNKNOWN         (0 << 0)
+#define COLORSPACE_GRAYSCALE       (1 << 0)
+#define COLORSPACE_YUV             (1 << 1)
+#define COLORSPACE_RGB             (1 << 2)
+#define COLORSPACE_CMYK            (1 << 3)
+#define COLORSPACE_YCCK            (1 << 4)
+
+typedef struct _GstJifMuxMarker
+{
+  guint8 marker;
+  guint16 size;
+
+  const guint8 *data;
+  gboolean owned;
+} GstJifMuxMarker;
+
+static void gst_jif_mux_finalize (GObject * object);
+
+static void gst_jif_mux_reset (GstJifMux * self);
+static gboolean gst_jif_mux_sink_setcaps (GstJifMux * self, GstCaps * caps);
+static gboolean gst_jif_mux_sink_event (GstPad * pad, GstObject * parent,
+    GstEvent * event);
+static GstFlowReturn gst_jif_mux_chain (GstPad * pad, GstObject * parent,
+    GstBuffer * buffer);
+static GstStateChangeReturn gst_jif_mux_change_state (GstElement * element,
+    GstStateChange transition);
+
+#define gst_jif_mux_parent_class parent_class
+G_DEFINE_TYPE_WITH_CODE (GstJifMux, gst_jif_mux, GST_TYPE_ELEMENT,
+    G_IMPLEMENT_INTERFACE (GST_TYPE_TAG_SETTER, NULL);
+    G_IMPLEMENT_INTERFACE (GST_TYPE_TAG_XMP_WRITER, NULL));
+GST_ELEMENT_REGISTER_DEFINE (jifmux, "jifmux", GST_RANK_SECONDARY,
+    GST_TYPE_JIF_MUX);
+
+static void
+gst_jif_mux_class_init (GstJifMuxClass * klass)
+{
+  GObjectClass *gobject_class;
+  GstElementClass *gstelement_class;
+
+  gobject_class = (GObjectClass *) klass;
+  gstelement_class = (GstElementClass *) klass;
+
+  gobject_class->finalize = gst_jif_mux_finalize;
+
+  gstelement_class->change_state = GST_DEBUG_FUNCPTR (gst_jif_mux_change_state);
+
+  gst_element_class_add_static_pad_template (gstelement_class,
+      &gst_jif_mux_src_pad_template);
+  gst_element_class_add_static_pad_template (gstelement_class,
+      &gst_jif_mux_sink_pad_template);
+
+  gst_element_class_set_static_metadata (gstelement_class,
+      "JPEG stream muxer",
+      "Video/Formatter",
+      "Remuxes JPEG images with markers and tags",
+      "Arnout Vandecappelle (Essensium/Mind) <arnout@mind.be>");
+
+  GST_DEBUG_CATEGORY_INIT (jif_mux_debug, "jifmux", 0,
+      "JPEG interchange format muxer");
+}
+
+static void
+gst_jif_mux_init (GstJifMux * self)
+{
+  GstPad *sinkpad;
+
+  /* create the sink and src pads */
+  sinkpad = gst_pad_new_from_static_template (&gst_jif_mux_sink_pad_template,
+      "sink");
+  gst_pad_set_chain_function (sinkpad, GST_DEBUG_FUNCPTR (gst_jif_mux_chain));
+  gst_pad_set_event_function (sinkpad,
+      GST_DEBUG_FUNCPTR (gst_jif_mux_sink_event));
+  gst_element_add_pad (GST_ELEMENT (self), sinkpad);
+
+  self->srcpad =
+      gst_pad_new_from_static_template (&gst_jif_mux_src_pad_template, "src");
+  gst_element_add_pad (GST_ELEMENT (self), self->srcpad);
+}
+
+static void
+gst_jif_mux_finalize (GObject * object)
+{
+  GstJifMux *self = GST_JIF_MUX (object);
+
+  gst_jif_mux_reset (self);
+  G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static gboolean
+gst_jif_mux_sink_setcaps (GstJifMux * self, GstCaps * caps)
+{
+  GstStructure *s = gst_caps_get_structure (caps, 0);
+  const gchar *variant;
+
+  /* should be {combined (default), EXIF, JFIF} */
+  if ((variant = gst_structure_get_string (s, "variant")) != NULL) {
+    GST_INFO_OBJECT (self, "muxing to '%s'", variant);
+    /* FIXME: do we want to switch it like this or use a gobject property ? */
+  }
+
+  return gst_pad_set_caps (self->srcpad, caps);
+}
+
+static gboolean
+gst_jif_mux_sink_event (GstPad * pad, GstObject * parent, GstEvent * event)
+{
+  GstJifMux *self = GST_JIF_MUX (parent);
+  gboolean ret;
+
+  switch (GST_EVENT_TYPE (event)) {
+    case GST_EVENT_CAPS:
+    {
+      GstCaps *caps;
+
+      gst_event_parse_caps (event, &caps);
+      ret = gst_jif_mux_sink_setcaps (self, caps);
+      gst_event_unref (event);
+      break;
+    }
+    case GST_EVENT_TAG:{
+      GstTagList *list;
+      GstTagSetter *setter = GST_TAG_SETTER (self);
+      const GstTagMergeMode mode = gst_tag_setter_get_tag_merge_mode (setter);
+
+      gst_event_parse_tag (event, &list);
+
+      gst_tag_setter_merge_tags (setter, list, mode);
+
+      ret = gst_pad_event_default (pad, parent, event);
+      break;
+    }
+    default:
+      ret = gst_pad_event_default (pad, parent, event);
+      break;
+  }
+  return ret;
+}
+
+static void
+gst_jif_mux_marker_free (GstJifMuxMarker * m)
+{
+  if (m->owned)
+    g_free ((gpointer) m->data);
+
+  g_free (m);
+}
+
+static void
+gst_jif_mux_reset (GstJifMux * self)
+{
+  GList *node;
+  GstJifMuxMarker *m;
+
+  for (node = self->markers; node; node = g_list_next (node)) {
+    m = (GstJifMuxMarker *) node->data;
+    gst_jif_mux_marker_free (m);
+  }
+  g_list_free (self->markers);
+  self->markers = NULL;
+}
+
+static GstJifMuxMarker *
+gst_jif_mux_new_marker (guint8 marker, guint16 size, const guint8 * data,
+    gboolean owned)
+{
+  GstJifMuxMarker *m = g_new (GstJifMuxMarker, 1);
+
+  m->marker = marker;
+  m->size = size;
+  m->data = data;
+  m->owned = owned;
+
+  return m;
+}
+
+static gboolean
+gst_jif_mux_parse_image (GstJifMux * self, GstBuffer * buf)
+{
+  GstByteReader reader;
+  GstJifMuxMarker *m;
+  guint8 marker = 0;
+  guint16 size = 0;
+  const guint8 *data = NULL;
+  GstMapInfo map;
+
+  gst_buffer_map (buf, &map, GST_MAP_READ);
+  gst_byte_reader_init (&reader, map.data, map.size);
+
+  GST_LOG_OBJECT (self, "Received buffer of size: %" G_GSIZE_FORMAT, map.size);
+
+  if (!gst_byte_reader_peek_uint8 (&reader, &marker))
+    goto error;
+
+  while (marker == 0xff) {
+    if (!gst_byte_reader_skip (&reader, 1))
+      goto error;
+
+    if (!gst_byte_reader_get_uint8 (&reader, &marker))
+      goto error;
+
+    switch (marker) {
+      case GST_JPEG_MARKER_RST0:
+      case GST_JPEG_MARKER_RST1:
+      case GST_JPEG_MARKER_RST2:
+      case GST_JPEG_MARKER_RST3:
+      case GST_JPEG_MARKER_RST4:
+      case GST_JPEG_MARKER_RST5:
+      case GST_JPEG_MARKER_RST6:
+      case GST_JPEG_MARKER_RST7:
+      case GST_JPEG_MARKER_SOI:
+        GST_DEBUG_OBJECT (self, "marker = %x", marker);
+        m = gst_jif_mux_new_marker (marker, 0, NULL, FALSE);
+        self->markers = g_list_prepend (self->markers, m);
+        break;
+      case GST_JPEG_MARKER_EOI:
+        GST_DEBUG_OBJECT (self, "marker = %x", marker);
+        m = gst_jif_mux_new_marker (marker, 0, NULL, FALSE);
+        self->markers = g_list_prepend (self->markers, m);
+        goto done;
+      default:
+        if (!gst_byte_reader_get_uint16_be (&reader, &size))
+          goto error;
+        if (!gst_byte_reader_get_data (&reader, size - 2, &data))
+          goto error;
+
+        m = gst_jif_mux_new_marker (marker, size - 2, data, FALSE);
+        self->markers = g_list_prepend (self->markers, m);
+
+        GST_DEBUG_OBJECT (self, "marker = %2x, size = %u", marker, size);
+        break;
+    }
+
+    if (marker == GST_JPEG_MARKER_SOS) {
+      gint eoi_pos = -1;
+      gint i;
+
+      /* search the last 5 bytes for the EOI marker */
+      g_assert (map.size >= 5);
+      for (i = 5; i >= 2; i--) {
+        if (map.data[map.size - i] == 0xFF
+            && map.data[map.size - i + 1] == GST_JPEG_MARKER_EOI) {
+          eoi_pos = map.size - i;
+          break;
+        }
+      }
+      if (eoi_pos == -1) {
+        GST_WARNING_OBJECT (self, "Couldn't find an EOI marker");
+        eoi_pos = map.size;
+      }
+
+      /* remaining size except EOI is scan data */
+      self->scan_size = eoi_pos - gst_byte_reader_get_pos (&reader);
+      if (!gst_byte_reader_get_data (&reader, self->scan_size,
+              &self->scan_data))
+        goto error;
+
+      GST_DEBUG_OBJECT (self, "scan data, size = %u", self->scan_size);
+    }
+
+    if (!gst_byte_reader_peek_uint8 (&reader, &marker))
+      goto error;
+  }
+  GST_INFO_OBJECT (self, "done parsing at 0x%x / 0x%x",
+      gst_byte_reader_get_pos (&reader), (guint) map.size);
+
+done:
+  self->markers = g_list_reverse (self->markers);
+  gst_buffer_unmap (buf, &map);
+
+  return TRUE;
+
+  /* ERRORS */
+error:
+  {
+    GST_WARNING_OBJECT (self,
+        "Error parsing image header (need more that %u bytes available)",
+        gst_byte_reader_get_remaining (&reader));
+    gst_buffer_unmap (buf, &map);
+    return FALSE;
+  }
+}
+
+static gboolean
+gst_jif_mux_mangle_markers (GstJifMux * self)
+{
+  gboolean modified = FALSE;
+  GstTagList *tags = NULL;
+  gboolean cleanup_tags;
+  GstJifMuxMarker *m;
+  GList *node, *file_hdr = NULL, *frame_hdr = NULL, *scan_hdr = NULL;
+  GList *app0_jfif = NULL, *app1_exif = NULL, *app1_xmp = NULL, *com = NULL;
+  GstBuffer *xmp_data;
+  gchar *str = NULL;
+  gint colorspace = COLORSPACE_UNKNOWN;
+
+  /* update the APP markers
+   * - put any JFIF APP0 first
+   * - the Exif APP1 next,
+   * - the XMP APP1 next,
+   * - the PSIR APP13 next,
+   * - followed by all other marker segments
+   */
+
+  /* find some reference points where we insert before/after */
+  file_hdr = self->markers;
+  for (node = self->markers; node; node = g_list_next (node)) {
+    m = (GstJifMuxMarker *) node->data;
+
+    switch (m->marker) {
+      case GST_JPEG_MARKER_APP0:
+        if (m->size > 5 && !memcmp (m->data, "JFIF\0", 5)) {
+          GST_DEBUG_OBJECT (self, "found APP0 JFIF");
+          colorspace |= COLORSPACE_GRAYSCALE | COLORSPACE_YUV;
+          if (!app0_jfif)
+            app0_jfif = node;
+        }
+        break;
+      case GST_JPEG_MARKER_APP1:
+        if (m->size > 6 && (!memcmp (m->data, "EXIF\0\0", 6) ||
+                !memcmp (m->data, "Exif\0\0", 6))) {
+          GST_DEBUG_OBJECT (self, "found APP1 EXIF");
+          if (!app1_exif)
+            app1_exif = node;
+        } else if (m->size > 29
+            && !memcmp (m->data, "http://ns.adobe.com/xap/1.0/\0", 29)) {
+          GST_INFO_OBJECT (self, "found APP1 XMP, will be replaced");
+          if (!app1_xmp)
+            app1_xmp = node;
+        }
+        break;
+      case GST_JPEG_MARKER_APP14:
+        /* check if this contains RGB */
+        /*
+         * This marker should have:
+         * - 'Adobe\0'
+         * - 2 bytes DCTEncodeVersion
+         * - 2 bytes flags0
+         * - 2 bytes flags1
+         * - 1 byte  ColorTransform
+         *             - 0 means unknown (RGB or CMYK)
+         *             - 1 YCbCr
+         *             - 2 YCCK
+         */
+
+        if ((m->size >= 14)
+            && (strncmp ((gchar *) m->data, "Adobe\0", 6) == 0)) {
+          switch (m->data[11]) {
+            case 0:
+              colorspace |= COLORSPACE_RGB | COLORSPACE_CMYK;
+              break;
+            case 1:
+              colorspace |= COLORSPACE_YUV;
+              break;
+            case 2:
+              colorspace |= COLORSPACE_YCCK;
+              break;
+            default:
+              break;
+          }
+        }
+
+        break;
+      case GST_JPEG_MARKER_COM:
+        GST_INFO_OBJECT (self, "found COM, will be replaced");
+        if (!com)
+          com = node;
+        break;
+      case GST_JPEG_MARKER_DQT:
+      case GST_JPEG_MARKER_SOF0:
+      case GST_JPEG_MARKER_SOF1:
+      case GST_JPEG_MARKER_SOF2:
+      case GST_JPEG_MARKER_SOF3:
+      case GST_JPEG_MARKER_SOF5:
+      case GST_JPEG_MARKER_SOF6:
+      case GST_JPEG_MARKER_SOF7:
+      case GST_JPEG_MARKER_SOF9:
+      case GST_JPEG_MARKER_SOF10:
+      case GST_JPEG_MARKER_SOF11:
+      case GST_JPEG_MARKER_SOF13:
+      case GST_JPEG_MARKER_SOF14:
+      case GST_JPEG_MARKER_SOF15:
+        if (!frame_hdr)
+          frame_hdr = node;
+        break;
+      case GST_JPEG_MARKER_DAC:
+      case GST_JPEG_MARKER_DHT:
+      case GST_JPEG_MARKER_DRI:
+      case GST_JPEG_MARKER_SOS:
+        if (!scan_hdr)
+          scan_hdr = node;
+        break;
+    }
+  }
+
+  /* if we want combined or JFIF */
+  /* check if we don't have JFIF APP0 */
+  if (!app0_jfif && (colorspace & (COLORSPACE_GRAYSCALE | COLORSPACE_YUV))) {
+    /* build jfif header */
+    /* *INDENT-OFF* */
+    static const struct
+    {
+      gchar id[5];
+      guint8 ver[2];
+      guint8 du;
+      guint8 xd[2], yd[2];
+      guint8 tw, th;
+    } jfif_data = { "JFIF",
+        { 1, 2 },
+        0,
+        { 0, 1 }, /* FIXME: check pixel-aspect from caps */
+        { 0, 1 },
+        0, 0
+    };
+    /* *INDENT-ON* */
+
+    m = gst_jif_mux_new_marker (GST_JPEG_MARKER_APP0, sizeof (jfif_data),
+        (const guint8 *) &jfif_data, FALSE);
+    /* insert into self->markers list */
+    self->markers = g_list_insert (self->markers, m, 1);
+    app0_jfif = g_list_nth (self->markers, 1);
+  }
+  /* else */
+  /* remove JFIF if exists */
+
+  /* Existing exif tags will be removed and our own will be added */
+  if (!tags) {
+    tags = (GstTagList *) gst_tag_setter_get_tag_list (GST_TAG_SETTER (self));
+    cleanup_tags = FALSE;
+  }
+  if (!tags) {
+    tags = gst_tag_list_new_empty ();
+    cleanup_tags = TRUE;
+  }
+
+  GST_DEBUG_OBJECT (self, "Tags to be serialized %" GST_PTR_FORMAT, tags);
+
+  /* FIXME: not happy with those
+   * - else where we would use VIDEO_CODEC = "Jpeg"
+   gst_tag_list_add (tags, GST_TAG_MERGE_REPLACE,
+   GST_TAG_VIDEO_CODEC, "image/jpeg", NULL);
+   */
+
+  /* Add EXIF */
+  {
+    GstBuffer *exif_data;
+    gsize exif_size;
+    guint8 *data;
+    GstJifMuxMarker *m;
+    GList *pos;
+
+    /* insert into self->markers list */
+    exif_data = gst_tag_list_to_exif_buffer_with_tiff_header (tags);
+    exif_size = exif_data ? gst_buffer_get_size (exif_data) : 0;
+
+    if (exif_data && exif_size + 8 >= G_GUINT64_CONSTANT (65536)) {
+      GST_WARNING_OBJECT (self, "Exif tags data size exceed maximum size");
+      gst_buffer_unref (exif_data);
+      exif_data = NULL;
+    }
+    if (exif_data) {
+      data = g_malloc0 (exif_size + 6);
+      memcpy (data, "Exif", 4);
+      gst_buffer_extract (exif_data, 0, data + 6, exif_size);
+      m = gst_jif_mux_new_marker (GST_JPEG_MARKER_APP1, exif_size + 6, data,
+          TRUE);
+      gst_buffer_unref (exif_data);
+
+      if (app1_exif) {
+        gst_jif_mux_marker_free ((GstJifMuxMarker *) app1_exif->data);
+        app1_exif->data = m;
+      } else {
+        pos = file_hdr;
+        if (app0_jfif)
+          pos = app0_jfif;
+        pos = g_list_next (pos);
+
+        self->markers = g_list_insert_before (self->markers, pos, m);
+        if (pos) {
+          app1_exif = g_list_previous (pos);
+        } else {
+          app1_exif = g_list_last (self->markers);
+        }
+      }
+      modified = TRUE;
+    }
+  }
+
+  /* add xmp */
+  xmp_data =
+      gst_tag_xmp_writer_tag_list_to_xmp_buffer (GST_TAG_XMP_WRITER (self),
+      tags, FALSE);
+  if (xmp_data) {
+    guint8 *data;
+    gsize size;
+    GList *pos;
+
+    size = gst_buffer_get_size (xmp_data);
+    data = g_malloc (size + 29);
+    memcpy (data, "http://ns.adobe.com/xap/1.0/\0", 29);
+    gst_buffer_extract (xmp_data, 0, &data[29], size);
+    m = gst_jif_mux_new_marker (GST_JPEG_MARKER_APP1, size + 29, data, TRUE);
+
+    /*
+     * Replace the old xmp marker and not add a new one.
+     * There shouldn't be a xmp packet in the input, but it is better
+     * to be safe than add another one and end up with 2 packets.
+     */
+    if (app1_xmp) {
+      gst_jif_mux_marker_free ((GstJifMuxMarker *) app1_xmp->data);
+      app1_xmp->data = m;
+    } else {
+
+      pos = file_hdr;
+      if (app1_exif)
+        pos = app1_exif;
+      else if (app0_jfif)
+        pos = app0_jfif;
+      pos = g_list_next (pos);
+
+      self->markers = g_list_insert_before (self->markers, pos, m);
+
+    }
+    gst_buffer_unref (xmp_data);
+    modified = TRUE;
+  }
+
+  /* add jpeg comment from any of those */
+  (void) (gst_tag_list_get_string (tags, GST_TAG_COMMENT, &str) ||
+      gst_tag_list_get_string (tags, GST_TAG_DESCRIPTION, &str) ||
+      gst_tag_list_get_string (tags, GST_TAG_TITLE, &str));
+
+  if (str) {
+    GST_DEBUG_OBJECT (self, "set COM marker to '%s'", str);
+    /* insert new marker into self->markers list */
+    m = gst_jif_mux_new_marker (GST_JPEG_MARKER_COM, strlen (str) + 1,
+        (const guint8 *) str, TRUE);
+    /* FIXME: if we have one already, replace */
+    /* this should go before SOS, maybe at the end of file-header */
+    self->markers = g_list_insert_before (self->markers, frame_hdr, m);
+
+    modified = TRUE;
+  }
+
+  if (tags && cleanup_tags)
+    gst_tag_list_unref (tags);
+  return modified;
+}
+
+static GstFlowReturn
+gst_jif_mux_recombine_image (GstJifMux * self, GstBuffer ** new_buf,
+    GstBuffer * old_buf)
+{
+  GstBuffer *buf;
+  GstByteWriter *writer;
+  GstJifMuxMarker *m;
+  GList *node;
+  guint size = self->scan_size;
+  gboolean writer_status = TRUE;
+  GstMapInfo map;
+
+  /* iterate list and collect size */
+  for (node = self->markers; node; node = g_list_next (node)) {
+    m = (GstJifMuxMarker *) node->data;
+
+    /* some markers like e.g. SOI are empty */
+    if (m->size) {
+      size += 2 + m->size;
+    }
+    /* 0xff <marker> */
+    size += 2;
+  }
+  GST_INFO_OBJECT (self, "old size: %" G_GSIZE_FORMAT ", new size: %u",
+      gst_buffer_get_size (old_buf), size);
+
+  /* allocate new buffer */
+  buf = gst_buffer_new_allocate (NULL, size, NULL);
+
+  /* copy buffer metadata */
+  gst_buffer_copy_into (buf, old_buf,
+      GST_BUFFER_COPY_FLAGS | GST_BUFFER_COPY_TIMESTAMPS, 0, -1);
+
+  /* memcopy markers */
+  gst_buffer_map (buf, &map, GST_MAP_WRITE);
+  writer = gst_byte_writer_new_with_data (map.data, map.size, TRUE);
+
+  for (node = self->markers; node && writer_status; node = g_list_next (node)) {
+    m = (GstJifMuxMarker *) node->data;
+
+    writer_status &= gst_byte_writer_put_uint8 (writer, 0xff);
+    writer_status &= gst_byte_writer_put_uint8 (writer, m->marker);
+
+    GST_DEBUG_OBJECT (self, "marker = %2x, size = %u", m->marker, m->size + 2);
+
+    if (m->size) {
+      writer_status &= gst_byte_writer_put_uint16_be (writer, m->size + 2);
+      writer_status &= gst_byte_writer_put_data (writer, m->data, m->size);
+    }
+
+    if (m->marker == GST_JPEG_MARKER_SOS) {
+      GST_DEBUG_OBJECT (self, "scan data, size = %u", self->scan_size);
+      writer_status &=
+          gst_byte_writer_put_data (writer, self->scan_data, self->scan_size);
+    }
+  }
+  gst_buffer_unmap (buf, &map);
+  gst_byte_writer_free (writer);
+
+  if (!writer_status) {
+    GST_WARNING_OBJECT (self, "Failed to write to buffer, calculated size "
+        "was probably too short");
+    g_assert_not_reached ();
+  }
+
+  *new_buf = buf;
+  return GST_FLOW_OK;
+}
+
+static GstFlowReturn
+gst_jif_mux_chain (GstPad * pad, GstObject * parent, GstBuffer * buf)
+{
+  GstJifMux *self = GST_JIF_MUX (parent);
+  GstFlowReturn fret = GST_FLOW_OK;
+
+#if 0
+  GST_MEMDUMP ("jpeg beg", GST_BUFFER_DATA (buf), 64);
+  GST_MEMDUMP ("jpeg end", GST_BUFFER_DATA (buf) + GST_BUFFER_SIZE (buf) - 64,
+      64);
+#endif
+
+  /* we should have received a whole picture from SOI to EOI
+   * build a list of markers */
+  if (gst_jif_mux_parse_image (self, buf)) {
+    /* modify marker list */
+    if (gst_jif_mux_mangle_markers (self)) {
+      /* the list was changed, remux */
+      GstBuffer *old = buf;
+      fret = gst_jif_mux_recombine_image (self, &buf, old);
+      gst_buffer_unref (old);
+    }
+  }
+
+  /* free the marker list */
+  gst_jif_mux_reset (self);
+
+  if (fret == GST_FLOW_OK) {
+    fret = gst_pad_push (self->srcpad, buf);
+  }
+  return fret;
+}
+
+static GstStateChangeReturn
+gst_jif_mux_change_state (GstElement * element, GstStateChange transition)
+{
+  GstStateChangeReturn ret;
+  GstJifMux *self = GST_JIF_MUX_CAST (element);
+
+  switch (transition) {
+    case GST_STATE_CHANGE_NULL_TO_READY:
+      break;
+    case GST_STATE_CHANGE_READY_TO_PAUSED:
+      break;
+    case GST_STATE_CHANGE_PAUSED_TO_PLAYING:
+      break;
+    default:
+      break;
+  }
+
+  ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
+
+  switch (transition) {
+    case GST_STATE_CHANGE_PLAYING_TO_PAUSED:
+      break;
+    case GST_STATE_CHANGE_PAUSED_TO_READY:
+      gst_tag_setter_reset_tags (GST_TAG_SETTER (self));
+      break;
+    case GST_STATE_CHANGE_READY_TO_NULL:
+      break;
+    default:
+      break;
+  }
+
+  return ret;
+}
diff --git a/gst/jpegformat/gstjifmux.h b/gst/jpegformat/gstjifmux.h
new file mode 100644
index 0000000..e65c873
--- /dev/null
+++ b/gst/jpegformat/gstjifmux.h
@@ -0,0 +1,66 @@
+/* GStreamer
+ *
+ * jifmux: JPEG interchange format muxer
+ *
+ * Copyright (C) 2010 Stefan Kost <stefan.kost@nokia.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef __GST_JFIF_MUX_H__
+#define __GST_JFIF_MUX_H__
+
+#include <gst/gst.h>
+
+G_BEGIN_DECLS
+
+#define GST_TYPE_JIF_MUX \
+  (gst_jif_mux_get_type())
+#define GST_JIF_MUX(obj) \
+  (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_JIF_MUX,GstJifMux))
+#define GST_JIF_MUX_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_JIF_MUX,GstJifMuxClass))
+#define GST_IS_JIF_MUX(obj) \
+  (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_JIF_MUX))
+#define GST_IS_JIF_MUX_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_JIF_MUX))
+#define GST_JIF_MUX_CAST(obj) ((GstJifMux *) (obj))
+
+typedef struct _GstJifMux           GstJifMux;
+typedef struct _GstJifMuxClass      GstJifMuxClass;
+
+struct _GstJifMux {
+  GstElement element;
+
+  GstPad *srcpad;
+
+  /* list of GstJifMuxMarker */
+  GList *markers;
+  guint scan_size;
+  const guint8 *scan_data;
+};
+
+struct _GstJifMuxClass {
+  GstElementClass  parent_class;
+};
+
+GType gst_jif_mux_get_type (void);
+
+GST_ELEMENT_REGISTER_DECLARE (jifmux);
+
+G_END_DECLS
+
+#endif /* __GST_JFIF_MUX_H__ */
diff --git a/gst/jpegformat/gstjpegformat.c b/gst/jpegformat/gstjpegformat.c
new file mode 100644
index 0000000..bc37045
--- /dev/null
+++ b/gst/jpegformat/gstjpegformat.c
@@ -0,0 +1,45 @@
+/* GStreamer
+ *
+ * jpegformat: a plugin for JPEG Interchange Format
+ *
+ * Copyright (C) <2010> Stefan Kost <ensonic@users.sf.net>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "gstjpegparse.h"
+#include "gstjifmux.h"
+
+static gboolean
+plugin_init (GstPlugin * plugin)
+{
+  gboolean ret = FALSE;
+
+  ret |= GST_ELEMENT_REGISTER (jpegparse, plugin);
+  ret |= GST_ELEMENT_REGISTER (jifmux, plugin);
+
+  return ret;
+}
+
+GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
+    GST_VERSION_MINOR,
+    jpegformat,
+    "JPEG interchange format plugin",
+    plugin_init, VERSION, "LGPL", GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN)
diff --git a/gst/jpegformat/gstjpegparse.c b/gst/jpegformat/gstjpegparse.c
new file mode 100644
index 0000000..52d549d
--- /dev/null
+++ b/gst/jpegformat/gstjpegparse.c
@@ -0,0 +1,1043 @@
+/* GStreamer
+ *
+ * jpegparse: a parser for JPEG streams
+ *
+ * Copyright (C) <2009> Arnout Vandecappelle (Essensium/Mind) <arnout@mind.be>
+ *               <2022> Víctor Manuel Jáquez Leal <vjaquez@igalia.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+/**
+ * SECTION:element-jpegparse
+ * @title: jpegparse
+ * @short_description: JPEG parser
+ *
+ * Parses a JPEG stream into JPEG images.  It looks for EOI boundaries to
+ * split a continuous stream into single-frame buffers. Also reads the
+ * image header searching for image properties such as width and height
+ * among others. Jpegparse can also extract metadata (e.g. xmp).
+ *
+ * ## Example launch line
+ * |[
+ * gst-launch-1.0 -v souphttpsrc location=... ! jpegparse ! matroskamux ! filesink location=...
+ * ]|
+ * The above pipeline fetches a motion JPEG stream from an IP camera over
+ * HTTP and stores it in a matroska file.
+ *
+ */
+/* FIXME: output plain JFIF APP marker only. This provides best code reuse.
+ * JPEG decoders would not need to handle this part anymore. Also when remuxing
+ * (... ! jpegparse ! ... ! jifmux ! ...) metadata consolidation would be
+ * easier.
+ */
+
+/* TODO:
+ *  + APP2 -- ICC color profile
+ *  + APP3 -- meta (same as exif)
+ *  + APP12 -- Photoshop Save for Web: Ducky / Picture info
+ *  + APP13 -- Adobe IRB
+ *  + check for interlaced mjpeg
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+#include <gst/base/gstbytereader.h>
+#include "gstjpegparser.h"
+#include <gst/tag/tag.h>
+
+#include "gstjpegparse.h"
+
+enum ParserState
+{
+  GST_JPEG_PARSER_STATE_GOT_SOI = 1 << 0,
+  GST_JPEG_PARSER_STATE_GOT_SOF = 1 << 1,
+  GST_JPEG_PARSER_STATE_GOT_SOS = 1 << 2,
+  GST_JPEG_PARSER_STATE_GOT_JFIF = 1 << 3,
+  GST_JPEG_PARSER_STATE_GOT_ADOBE = 1 << 4,
+
+  GST_JPEG_PARSER_STATE_VALID_PICTURE = (GST_JPEG_PARSER_STATE_GOT_SOI |
+      GST_JPEG_PARSER_STATE_GOT_SOF | GST_JPEG_PARSER_STATE_GOT_SOS),
+};
+
+static GstStaticPadTemplate gst_jpeg_parse_src_pad_template =
+GST_STATIC_PAD_TEMPLATE ("src",
+    GST_PAD_SRC,
+    GST_PAD_ALWAYS,
+    GST_STATIC_CAPS ("image/jpeg, "
+        "framerate = (fraction) [ 0/1, MAX ], " "parsed = (boolean) true")
+    );
+
+static GstStaticPadTemplate gst_jpeg_parse_sink_pad_template =
+GST_STATIC_PAD_TEMPLATE ("sink",
+    GST_PAD_SINK,
+    GST_PAD_ALWAYS,
+    GST_STATIC_CAPS ("image/jpeg")
+    );
+
+GST_DEBUG_CATEGORY_STATIC (jpeg_parse_debug);
+#define GST_CAT_DEFAULT jpeg_parse_debug
+
+static GstFlowReturn
+gst_jpeg_parse_handle_frame (GstBaseParse * bparse, GstBaseParseFrame * frame,
+    gint * skipsize);
+static gboolean gst_jpeg_parse_set_sink_caps (GstBaseParse * parse,
+    GstCaps * caps);
+static gboolean gst_jpeg_parse_sink_event (GstBaseParse * parse,
+    GstEvent * event);
+static gboolean gst_jpeg_parse_start (GstBaseParse * parse);
+static gboolean gst_jpeg_parse_stop (GstBaseParse * parse);
+
+#define gst_jpeg_parse_parent_class parent_class
+G_DEFINE_TYPE (GstJpegParse, gst_jpeg_parse, GST_TYPE_BASE_PARSE);
+GST_ELEMENT_REGISTER_DEFINE (jpegparse, "jpegparse", GST_RANK_PRIMARY,
+    GST_TYPE_JPEG_PARSE);
+
+enum GstJPEGColorspace
+{
+  GST_JPEG_COLORSPACE_NONE,
+  GST_JPEG_COLORSPACE_RGB,
+  GST_JPEG_COLORSPACE_YUV,
+  GST_JPEG_COLORSPACE_GRAY,
+  GST_JPEG_COLORSPACE_CMYK,
+  GST_JPEG_COLORSPACE_YCCK,
+};
+
+static const gchar *gst_jpeg_colorspace_strings[] = {
+  [GST_JPEG_COLORSPACE_NONE] = NULL,
+  [GST_JPEG_COLORSPACE_RGB] = "sRGB",
+  [GST_JPEG_COLORSPACE_YUV] = "sYUV",
+  [GST_JPEG_COLORSPACE_GRAY] = "GRAY",
+  [GST_JPEG_COLORSPACE_CMYK] = "CMYK",
+  [GST_JPEG_COLORSPACE_YCCK] = "YCCK",
+};
+
+enum GstJPEGSampling
+{
+  GST_JPEG_SAMPLING_NONE,
+  GST_JPEG_SAMPLING_RGB,
+  GST_JPEG_SAMPLING_BGR,
+  GST_JPEG_SAMPLING_YBR444,
+  GST_JPEG_SAMPLING_YBR422,
+  GST_JPEG_SAMPLING_YBR420,
+  GST_JPEG_SAMPLING_YBR440,
+  GST_JPEG_SAMPLING_YBR410,
+  GST_JPEG_SAMPLING_YBR411,
+  GST_JPEG_SAMPLING_GRAYSCALE,
+};
+
+static const gchar *gst_jpeg_sampling_strings[] = {
+  [GST_JPEG_SAMPLING_NONE] = NULL,
+  [GST_JPEG_SAMPLING_RGB] = "RGB",
+  [GST_JPEG_SAMPLING_BGR] = "BGR",
+  [GST_JPEG_SAMPLING_YBR444] = "YCbCr-4:4:4",
+  [GST_JPEG_SAMPLING_YBR422] = "YCbCr-4:2:2",
+  [GST_JPEG_SAMPLING_YBR420] = "YCbCr-4:2:0",
+  [GST_JPEG_SAMPLING_YBR440] = "YCbCr-4:4:0",
+  [GST_JPEG_SAMPLING_YBR410] = "YCbCr-4:1:0",
+  [GST_JPEG_SAMPLING_YBR411] = "YCbCr-4:1:1",
+  [GST_JPEG_SAMPLING_GRAYSCALE] = "GRAYSCALE",
+};
+
+static void
+gst_jpeg_parse_class_init (GstJpegParseClass * klass)
+{
+  GstBaseParseClass *gstbaseparse_class;
+  GstElementClass *gstelement_class;
+
+  gstbaseparse_class = (GstBaseParseClass *) klass;
+  gstelement_class = (GstElementClass *) klass;
+
+  gstbaseparse_class->start = gst_jpeg_parse_start;
+  gstbaseparse_class->stop = gst_jpeg_parse_stop;
+  gstbaseparse_class->set_sink_caps = gst_jpeg_parse_set_sink_caps;
+  gstbaseparse_class->sink_event = gst_jpeg_parse_sink_event;
+  gstbaseparse_class->handle_frame = gst_jpeg_parse_handle_frame;
+
+  gst_element_class_add_static_pad_template (gstelement_class,
+      &gst_jpeg_parse_src_pad_template);
+  gst_element_class_add_static_pad_template (gstelement_class,
+      &gst_jpeg_parse_sink_pad_template);
+
+  gst_element_class_set_static_metadata (gstelement_class,
+      "JPEG stream parser",
+      "Codec/Parser/Image",
+      "Parse JPEG images into single-frame buffers",
+      "Víctor Jáquez <vjaquez@igalia.com>");
+
+  GST_DEBUG_CATEGORY_INIT (jpeg_parse_debug, "jpegparse", 0, "JPEG parser");
+}
+
+static void
+gst_jpeg_parse_init (GstJpegParse * parse)
+{
+  parse->sof = -1;
+}
+
+static void
+parse_avid (GstJpegParse * parse, const guint8 * data, guint16 len)
+{
+  parse->avid = 1;
+  if (len > 14 && data[12] == 1)        /* 1 - NTSC */
+    parse->field_order = GST_VIDEO_FIELD_ORDER_BOTTOM_FIELD_FIRST;
+  if (len > 14 && data[12] == 2)        /* 2 - PAL */
+    parse->field_order = GST_VIDEO_FIELD_ORDER_TOP_FIELD_FIRST;
+  GST_INFO_OBJECT (parse, "AVID: %s",
+      gst_video_field_order_to_string (parse->field_order));
+}
+
+static gboolean
+gst_jpeg_parse_set_sink_caps (GstBaseParse * bparse, GstCaps * caps)
+{
+  GstJpegParse *parse = GST_JPEG_PARSE_CAST (bparse);
+  GstStructure *s = gst_caps_get_structure (caps, 0);
+  const GValue *codec_data;
+  const char *interlace_mode, *field_order;
+
+  GST_DEBUG_OBJECT (parse, "get sink caps %" GST_PTR_FORMAT, caps);
+
+  gst_structure_get_fraction (s, "framerate",
+      &parse->framerate_numerator, &parse->framerate_denominator);
+
+  gst_structure_get_int (s, "height", &parse->orig_height);
+  gst_structure_get_int (s, "width", &parse->orig_width);
+
+  gst_structure_get_fraction (s, "pixel-aspect-ration", &parse->par_num,
+      &parse->par_den);
+
+  codec_data = gst_structure_get_value (s, "codec_data");
+  if (codec_data && G_VALUE_TYPE (codec_data) == GST_TYPE_BUFFER) {
+    GstMapInfo map;
+
+    gst_clear_buffer (&parse->codec_data);
+
+    parse->codec_data = GST_BUFFER (g_value_dup_boxed (codec_data));
+    if (gst_buffer_map (parse->codec_data, &map, GST_MAP_READ)) {
+      if (map.size > 8 && map.data[0] == 0x2c && map.data[4] == 0x18)
+        parse_avid (parse, map.data, map.size);
+      gst_buffer_unmap (parse->codec_data, &map);
+    }
+  }
+
+  interlace_mode = gst_structure_get_string (s, "interlace-mode");
+  if (interlace_mode) {
+    parse->interlace_mode =
+        gst_video_interlace_mode_from_string (interlace_mode);
+  }
+
+  if (parse->interlace_mode != GST_VIDEO_INTERLACE_MODE_PROGRESSIVE) {
+    field_order = gst_structure_get_string (s, "field-order");
+    if (field_order)
+      parse->field_order = gst_video_field_order_from_string (field_order);
+  }
+
+  g_clear_pointer (&parse->colorimetry, g_free);
+  parse->colorimetry = g_strdup (gst_structure_get_string (s, "colorimetry"));
+
+  return TRUE;
+}
+
+static inline gboolean
+valid_state (guint state, guint ref_state)
+{
+  return (state & ref_state) == ref_state;
+}
+
+/* https://zpl.fi/chroma-subsampling-and-jpeg-sampling-factors/ */
+/* *INDENT-OFF* */
+static const struct
+{
+  gint h[3];
+  gint v[3];
+  enum GstJPEGSampling sampling;
+} subsampling_map[] = {
+  {{1, 1, 1}, {1, 1, 1}, GST_JPEG_SAMPLING_YBR444},
+  {{2, 2, 2}, {1, 1, 1}, GST_JPEG_SAMPLING_YBR444},
+  {{3, 3, 3}, {1, 1, 1}, GST_JPEG_SAMPLING_YBR444},
+  {{1, 1, 1}, {2, 2, 2}, GST_JPEG_SAMPLING_YBR444},
+  {{1, 1, 1}, {3, 3, 3}, GST_JPEG_SAMPLING_YBR444},
+  {{1, 1, 1}, {2, 1, 1}, GST_JPEG_SAMPLING_YBR440},
+  {{2, 2, 2}, {2, 1, 1}, GST_JPEG_SAMPLING_YBR440},
+  {{1, 1, 1}, {4, 2, 2}, GST_JPEG_SAMPLING_YBR440},
+  {{2, 1, 1}, {1, 1, 1}, GST_JPEG_SAMPLING_YBR422},
+  {{2, 1, 1}, {2, 2, 2}, GST_JPEG_SAMPLING_YBR422},
+  {{4, 2, 2}, {1, 1, 1}, GST_JPEG_SAMPLING_YBR422},
+  {{2, 1, 1}, {2, 1, 1}, GST_JPEG_SAMPLING_YBR420},
+  {{4, 1, 1}, {1, 1, 1}, GST_JPEG_SAMPLING_YBR411},
+  {{4, 1, 1}, {2, 1, 1}, GST_JPEG_SAMPLING_YBR410},
+};
+/* *INDENT-ON* */
+
+static guint16
+yuv_sampling (GstJpegFrameHdr * frame_hdr)
+{
+  int i, h0, h1, h2, v0, v1, v2;
+
+  g_return_val_if_fail (frame_hdr->num_components == 3, GST_JPEG_SAMPLING_NONE);
+
+  h0 = frame_hdr->components[0].horizontal_factor;
+  h1 = frame_hdr->components[1].horizontal_factor;
+  h2 = frame_hdr->components[2].horizontal_factor;
+  v0 = frame_hdr->components[0].vertical_factor;
+  v1 = frame_hdr->components[1].vertical_factor;
+  v2 = frame_hdr->components[2].vertical_factor;
+
+  for (i = 0; i < G_N_ELEMENTS (subsampling_map); i++) {
+    if (subsampling_map[i].h[0] == h0
+        && subsampling_map[i].h[1] == h1 && subsampling_map[i].h[2] == h2
+        && subsampling_map[i].v[0] == v0
+        && subsampling_map[i].v[1] == v1 && subsampling_map[i].v[2] == v2)
+      return subsampling_map[i].sampling;
+  }
+
+  return GST_JPEG_SAMPLING_NONE;
+}
+
+static const gchar *
+colorspace_to_string (enum GstJPEGColorspace colorspace)
+{
+  return gst_jpeg_colorspace_strings[colorspace];
+}
+
+/* https://entropymine.wordpress.com/2018/10/22/how-is-a-jpeg-images-color-type-determined/ */
+/* T-REC-T.872-201206  6.1 Colour encodings and associated values to define white and black */
+static gboolean
+gst_jpeg_parse_sof (GstJpegParse * parse, GstJpegSegment * seg)
+{
+  GstJpegFrameHdr hdr = { 0, };
+  guint colorspace;
+  guint sampling;
+
+  if (!gst_jpeg_segment_parse_frame_header (seg, &hdr)) {
+    return FALSE;
+  }
+
+  colorspace = GST_JPEG_COLORSPACE_NONE;
+  sampling = GST_JPEG_SAMPLING_NONE;
+
+  switch (hdr.num_components) {
+    case 1:
+      colorspace = GST_JPEG_COLORSPACE_GRAY;
+      sampling = GST_JPEG_SAMPLING_GRAYSCALE;
+      break;
+    case 3:
+      if (valid_state (parse->state, GST_JPEG_PARSER_STATE_GOT_JFIF)) {
+        colorspace = GST_JPEG_COLORSPACE_YUV;
+        sampling = yuv_sampling (&hdr);
+      } else {
+        if (valid_state (parse->state, GST_JPEG_PARSER_STATE_GOT_ADOBE)) {
+          if (parse->adobe_transform == 0) {
+            colorspace = GST_JPEG_COLORSPACE_RGB;
+            sampling = GST_JPEG_SAMPLING_RGB;
+          } else if (parse->adobe_transform == 1) {
+            colorspace = GST_JPEG_COLORSPACE_YUV;;
+            sampling = yuv_sampling (&hdr);
+          } else {
+            GST_DEBUG_OBJECT (parse, "Unknown Adobe color transform code");
+            colorspace = GST_JPEG_COLORSPACE_YUV;;
+            sampling = yuv_sampling (&hdr);
+          }
+        } else {
+          int cid0, cid1, cid2;
+
+          cid0 = hdr.components[0].identifier;
+          cid1 = hdr.components[1].identifier;
+          cid2 = hdr.components[2].identifier;
+
+          if (cid0 == 1 && cid1 == 2 && cid2 == 3) {
+            colorspace = GST_JPEG_COLORSPACE_YUV;
+            sampling = yuv_sampling (&hdr);
+          } else if (cid0 == 'R' && cid1 == 'G' && cid2 == 'B') {
+            colorspace = GST_JPEG_COLORSPACE_RGB;
+            sampling = GST_JPEG_SAMPLING_RGB;
+          } else {
+            GST_DEBUG_OBJECT (parse, "Unrecognized component IDs");
+            colorspace = GST_JPEG_COLORSPACE_YUV;
+            sampling = yuv_sampling (&hdr);
+          }
+        }
+      }
+      break;
+    case 4:
+      if (valid_state (parse->state, GST_JPEG_PARSER_STATE_GOT_ADOBE)) {
+        if (parse->adobe_transform == 0) {
+          colorspace = GST_JPEG_COLORSPACE_CMYK;
+        } else if (parse->adobe_transform == 2) {
+          colorspace = GST_JPEG_COLORSPACE_YCCK;
+        } else {
+          GST_DEBUG_OBJECT (parse, "Unknown Adobe color transform code");
+          colorspace = GST_JPEG_COLORSPACE_YCCK;
+        }
+      } else {
+        colorspace = GST_JPEG_COLORSPACE_CMYK;
+      }
+      break;
+    default:
+      GST_WARNING_OBJECT (parse, "Unknown color space");
+      break;
+  }
+
+  if (hdr.width != parse->width || hdr.height != parse->height
+      || colorspace != parse->colorspace || sampling != parse->sampling) {
+    parse->width = hdr.width;
+    parse->height = hdr.height;
+    parse->colorspace = colorspace;
+    parse->sampling = sampling;
+
+    if (parse->first_picture && !parse->multiscope) {
+      if (parse->orig_height > 0
+          && parse->height < ((parse->orig_height * 3) / 4)) {
+        parse->interlace_mode = GST_VIDEO_INTERLACE_MODE_INTERLEAVED;
+      } else if (parse->avid) {
+        /* if no container info, let's suppose it doubles its height */
+        if (parse->orig_height == 0)
+          parse->orig_height = 2 * hdr.height;
+        parse->interlace_mode = GST_VIDEO_INTERLACE_MODE_INTERLEAVED;
+      }
+    }
+
+    parse->first_picture = FALSE;
+    parse->renegotiate = TRUE;
+  }
+
+  GST_INFO_OBJECT (parse, "SOF [%dx%d] %d comp - %s", parse->width,
+      parse->height, hdr.num_components,
+      GST_STR_NULL (colorspace_to_string (parse->colorspace)));
+  return TRUE;
+}
+
+static inline GstTagList *
+get_tag_list (GstJpegParse * parse)
+{
+  if (!parse->tags)
+    parse->tags = gst_tag_list_new_empty ();
+  return parse->tags;
+}
+
+static gboolean
+gst_jpeg_parse_app0 (GstJpegParse * parse, GstJpegSegment * seg)
+{
+  GstByteReader reader;
+  guint16 xd, yd;
+  guint8 unit, xt, yt;
+  guint32 id;
+
+  if (seg->size < 6)            /* less than 6 means no id string */
+    return FALSE;
+
+  gst_byte_reader_init (&reader, seg->data + seg->offset, seg->size);
+  gst_byte_reader_skip_unchecked (&reader, 2);
+
+#if G_BYTE_ORDER == G_LITTLE_ENDIAN
+  if (!gst_byte_reader_get_uint32_le (&reader, &id))
+    return FALSE;
+#else
+  if (!gst_byte_reader_get_uint32_be (&reader, &id))
+    return FALSE;
+#endif
+
+  if (!valid_state (parse->state, GST_JPEG_PARSER_STATE_GOT_JFIF)
+      && GST_MAKE_FOURCC ('J', 'F', 'I', 'F') == id) {
+
+    parse->state |= GST_JPEG_PARSER_STATE_GOT_JFIF;
+
+    /* trailing zero-byte */
+    gst_byte_reader_skip_unchecked (&reader, 1);
+
+    /* version */
+    gst_byte_reader_skip_unchecked (&reader, 2);
+
+    /* units */
+    if (!gst_byte_reader_get_uint8 (&reader, &unit))
+      return FALSE;
+
+    /* x density */
+#if G_BYTE_ORDER == G_LITTLE_ENDIAN
+    if (!gst_byte_reader_get_uint16_le (&reader, &xd))
+      return FALSE;
+    /* y density */
+    if (!gst_byte_reader_get_uint16_le (&reader, &yd))
+      return FALSE;
+#else
+    if (!gst_byte_reader_get_uint16_be (&reader, &xd))
+      return FALSE;
+    /* y density */
+    if (!gst_byte_reader_get_uint16_be (&reader, &yd))
+      return FALSE;
+#endif
+
+    /* x thumbnail */
+    if (!gst_byte_reader_get_uint8 (&reader, &xt))
+      return FALSE;
+    /* y thumbnail */
+    if (!gst_byte_reader_get_uint8 (&reader, &yt))
+      return FALSE;
+
+    if (unit == 0) {
+      /* no units, X and Y specify the pixel aspect ratio */
+      if (parse->par_num != xd || parse->par_den != yd) {
+        parse->renegotiate = TRUE;
+        parse->par_num = xd;
+        parse->par_den = yd;
+      }
+    } else if (unit == 1 || unit == 2) {
+      /* tag pixel per inches */
+      double hppi = xd, vppi = yd;
+
+      /* cm to in */
+      if (unit == 2) {
+        hppi *= 2.54;
+        vppi *= 2.54;
+      }
+
+      gst_tag_register_musicbrainz_tags ();
+      gst_tag_list_add (get_tag_list (parse), GST_TAG_MERGE_REPLACE,
+          GST_TAG_IMAGE_HORIZONTAL_PPI, hppi, GST_TAG_IMAGE_VERTICAL_PPI, vppi,
+          NULL);
+    }
+
+    if (xt > 0 && yt > 0)
+      GST_FIXME_OBJECT (parse, "embedded thumbnail ignored");
+
+    return TRUE;
+  }
+
+  /* JFIF  Extension  */
+  if (GST_MAKE_FOURCC ('J', 'F', 'X', 'X') == id) {
+    if (!valid_state (parse->state, GST_JPEG_PARSER_STATE_GOT_JFIF))
+      return FALSE;
+
+    return TRUE;
+  }
+
+  /* https://exiftool.org/TagNames/JPEG.html#AVI1 */
+  if (GST_MAKE_FOURCC ('A', 'V', 'I', '1') == id) {
+    /* polarity */
+    if (!gst_byte_reader_get_uint8 (&reader, &unit))
+      return FALSE;
+
+    parse->avid = (unit > 0);   /* otherwise is not interleaved */
+
+    /* TODO: update caps for interlaced MJPEG */
+    GST_DEBUG_OBJECT (parse, "MJPEG interleaved field: %s", unit == 0 ?
+        "not interleaved" : unit % 2 ? "Odd" : "Even");
+
+    return TRUE;
+  }
+
+  GST_MEMDUMP_OBJECT (parse, "Unhandled app0", seg->data + seg->offset,
+      seg->size);
+
+  return TRUE;
+}
+
+/* *INDENT-OFF* */
+static const struct
+{
+  const gchar *suffix;
+  GstTagList *(*tag_func) (GstBuffer * buff);
+} TagMap[] = {
+  {"Exif", gst_tag_list_from_exif_buffer_with_tiff_header},
+  {"http://ns.adobe.com/xap/1.0/", gst_tag_list_from_xmp_buffer},
+};
+/* *INDENT-ON* */
+
+static gboolean
+gst_jpeg_parse_app1 (GstJpegParse * parse, GstJpegSegment * seg)
+{
+  GstByteReader reader;
+  GstBuffer *buf;
+  guint16 size = 0;
+  const gchar *id_str;
+  const guint8 *data;
+  gint i;
+
+  if (seg->size < 6)            /* less than 6 means no id string */
+    return FALSE;
+
+  gst_byte_reader_init (&reader, seg->data + seg->offset, seg->size);
+  gst_byte_reader_skip_unchecked (&reader, 2);
+
+  if (!gst_byte_reader_get_string_utf8 (&reader, &id_str))
+    return FALSE;
+
+  for (i = 0; i < G_N_ELEMENTS (TagMap); i++) {
+    if (!g_str_has_suffix (id_str, TagMap[i].suffix))
+      continue;
+
+    /* skip NUL only for Exif */
+    if (i == 0) {
+      if (!gst_byte_reader_skip (&reader, 1))
+        return FALSE;
+    }
+
+    size = gst_byte_reader_get_remaining (&reader);
+
+    if (!gst_byte_reader_get_data (&reader, size, &data))
+      return FALSE;
+
+    buf = gst_buffer_new_wrapped_full (GST_MEMORY_FLAG_READONLY,
+        (gpointer) data, size, 0, size, NULL, NULL);
+
+    if (buf) {
+      GstTagList *tags;
+
+      tags = TagMap[i].tag_func (buf);
+      gst_buffer_unref (buf);
+
+      if (tags) {
+        GST_LOG_OBJECT (parse, "parsed marker %x: '%s' %" GST_PTR_FORMAT,
+            GST_JPEG_MARKER_APP1, id_str, tags);
+        gst_tag_list_insert (get_tag_list (parse), tags, GST_TAG_MERGE_REPLACE);
+        gst_tag_list_unref (tags);
+      } else {
+        GST_INFO_OBJECT (parse, "failed to parse %s: %s", id_str, data);
+        return FALSE;
+      }
+    }
+
+    return TRUE;
+  }
+
+  GST_MEMDUMP_OBJECT (parse, "Unhandled app1", seg->data + seg->offset,
+      seg->size);
+
+  return TRUE;
+}
+
+static gboolean
+gst_jpeg_parse_app14 (GstJpegParse * parse, GstJpegSegment * seg)
+{
+  GstByteReader reader;
+  const gchar *id_str;
+  guint8 transform;
+
+  if (seg->size < 6)            /* less than 6 means no id string */
+    return FALSE;
+
+  gst_byte_reader_init (&reader, seg->data + seg->offset, seg->size);
+  gst_byte_reader_skip_unchecked (&reader, 2);
+
+  if (!gst_byte_reader_get_string_utf8 (&reader, &id_str))
+    return FALSE;
+
+  if (!g_str_has_prefix (id_str, "Adobe")) {
+    GST_DEBUG_OBJECT (parse, "Unhandled app14: %s", id_str);
+    return TRUE;
+  }
+
+  /* skip version and flags */
+  if (!gst_byte_reader_skip (&reader, 6))
+    return FALSE;
+
+  parse->state |= GST_JPEG_PARSER_STATE_GOT_ADOBE;
+
+  /* transform bit might not exist  */
+  if (!gst_byte_reader_get_uint8 (&reader, &transform))
+    return TRUE;
+
+  parse->adobe_transform = transform;
+  return TRUE;
+}
+
+static inline gchar *
+get_utf8_from_data (const guint8 * data, guint16 size)
+{
+  const gchar *env_vars[] = { "GST_JPEG_TAG_ENCODING",
+    "GST_TAG_ENCODING", NULL
+  };
+  const char *str = (gchar *) data;
+  char *ret;
+
+  ret = gst_tag_freeform_string_to_utf8 (str, size, env_vars);
+  if (!ret)
+    GST_MEMDUMP ("non-parsed marker data", data, size);
+
+  return ret;
+}
+
+/* read comment and post as tag */
+static inline gboolean
+gst_jpeg_parse_com (GstJpegParse * parse, GstJpegSegment * seg)
+{
+  GstByteReader reader;
+  const guint8 *data = NULL;
+  guint16 size;
+  const gchar *buf;
+
+  gst_byte_reader_init (&reader, seg->data + seg->offset, seg->size);
+  gst_byte_reader_skip_unchecked (&reader, 2);
+
+  size = gst_byte_reader_get_remaining (&reader);
+  if (!gst_byte_reader_get_data (&reader, size, &data))
+    return FALSE;
+
+  buf = (const gchar *) data;
+  /* buggy avid, it puts EOI only at every 10th frame */
+  if (g_str_has_prefix (buf, "AVID")) {
+    parse_avid (parse, data, size);
+  } else if (g_str_has_prefix (buf, "MULTISCOPE II")) {
+    parse->par_num = 1;
+    parse->par_den = 2;
+    parse->multiscope = TRUE;
+  } else {
+    gchar *comment;
+
+    comment = get_utf8_from_data (data, size);
+    if (!comment)
+      return FALSE;
+
+    GST_INFO_OBJECT (parse, "comment found: %s", comment);
+    gst_tag_list_add (get_tag_list (parse), GST_TAG_MERGE_REPLACE,
+        GST_TAG_COMMENT, comment, NULL);
+    g_free (comment);
+  }
+
+  return TRUE;
+}
+
+/* reset per image */
+static void
+gst_jpeg_parse_reset (GstJpegParse * parse)
+{
+  parse->last_offset = 0;
+  parse->state = 0;
+  parse->adobe_transform = 0;
+  parse->field = 0;
+
+  if (parse->tags) {
+    gst_tag_list_unref (parse->tags);
+    parse->tags = NULL;
+  }
+}
+
+static const gchar *
+sampling_to_string (enum GstJPEGSampling sampling)
+{
+  return gst_jpeg_sampling_strings[sampling];
+}
+
+static gboolean
+gst_jpeg_parse_set_new_caps (GstJpegParse * parse)
+{
+  GstCaps *caps;
+  GstEvent *event;
+  gboolean res;
+
+  if (!parse->renegotiate)
+    return TRUE;
+
+  caps = gst_caps_new_simple ("image/jpeg", "parsed", G_TYPE_BOOLEAN, TRUE,
+      NULL);
+
+  if (parse->width > 0)
+    gst_caps_set_simple (caps, "width", G_TYPE_INT, parse->width, NULL);
+  if (parse->orig_height > 0 && parse->orig_height > parse->height)
+    gst_caps_set_simple (caps, "height", G_TYPE_INT, parse->orig_height, NULL);
+  else if (parse->height > 0)
+    gst_caps_set_simple (caps, "height", G_TYPE_INT, parse->height, NULL);
+  if (parse->sof >= 0)
+    gst_caps_set_simple (caps, "sof-marker", G_TYPE_INT, parse->sof, NULL);
+  if (parse->colorspace != GST_JPEG_COLORSPACE_NONE) {
+    gst_caps_set_simple (caps, "colorspace", G_TYPE_STRING,
+        colorspace_to_string (parse->colorspace), NULL);
+  }
+  if (parse->sampling != GST_JPEG_SAMPLING_NONE) {
+    gst_caps_set_simple (caps, "sampling", G_TYPE_STRING,
+        sampling_to_string (parse->sampling), NULL);
+  }
+
+  if (parse->colorimetry) {
+    gst_caps_set_simple (caps, "colorimetry", G_TYPE_STRING, parse->colorimetry,
+        NULL);
+  }
+
+  gst_caps_set_simple (caps, "interlace-mode", G_TYPE_STRING,
+      gst_video_interlace_mode_to_string (parse->interlace_mode), NULL);
+
+  if (parse->interlace_mode == GST_VIDEO_INTERLACE_MODE_INTERLEAVED) {
+    gst_caps_set_simple (caps, "field-order", G_TYPE_STRING,
+        gst_video_field_order_to_string (parse->field_order), NULL);
+  }
+
+  gst_caps_set_simple (caps, "framerate", GST_TYPE_FRACTION,
+      parse->framerate_numerator, parse->framerate_denominator, NULL);
+
+  if (parse->par_num > 0 && parse->par_den > 0) {
+    gst_caps_set_simple (caps, "pixel-aspect-ratio", GST_TYPE_FRACTION,
+        parse->par_num, parse->par_den, NULL);
+  }
+
+  if (parse->codec_data) {
+    gst_caps_set_simple (caps, "codec_data", GST_TYPE_BUFFER, parse->codec_data,
+        NULL);
+  }
+
+  parse->renegotiate = FALSE;
+
+  GST_DEBUG_OBJECT (parse,
+      "setting downstream caps on %s:%s to %" GST_PTR_FORMAT,
+      GST_DEBUG_PAD_NAME (GST_BASE_PARSE_SRC_PAD (parse)), caps);
+
+  event = gst_event_new_caps (caps);
+  res = gst_pad_push_event (GST_BASE_PARSE_SRC_PAD (parse), event);
+  gst_caps_unref (caps);
+
+  return res;
+}
+
+static GstFlowReturn
+gst_jpeg_parse_finish_frame (GstJpegParse * parse, GstBaseParseFrame * frame,
+    gint size)
+{
+  GstBaseParse *bparse = GST_BASE_PARSE (parse);
+  GstFlowReturn ret;
+
+  if (parse->tags)
+    gst_base_parse_merge_tags (bparse, parse->tags, GST_TAG_MERGE_REPLACE);
+
+  if (!gst_jpeg_parse_set_new_caps (parse))
+    return GST_FLOW_ERROR;
+
+  if (!valid_state (parse->state, GST_JPEG_PARSER_STATE_VALID_PICTURE)) {
+    /* this validation breaks unit tests */
+    /* frame->flags |= GST_BASE_PARSE_FRAME_FLAG_DROP; */
+    GST_WARNING_OBJECT (parse, "Potentially invalid picture");
+  }
+
+  GST_TRACE_OBJECT (parse, "Finish frame %" GST_PTR_FORMAT, frame->buffer);
+  ret = gst_base_parse_finish_frame (bparse, frame, size);
+
+  gst_jpeg_parse_reset (parse);
+
+  return ret;
+}
+
+static GstFlowReturn
+gst_jpeg_parse_handle_frame (GstBaseParse * bparse, GstBaseParseFrame * frame,
+    gint * skipsize)
+{
+  GstJpegParse *parse = GST_JPEG_PARSE_CAST (bparse);
+  GstMapInfo mapinfo;
+  GstJpegMarker marker;
+  GstJpegSegment seg;
+  guint offset;
+
+  GST_TRACE_OBJECT (parse, "frame %" GST_PTR_FORMAT, frame->buffer);
+
+  if (!gst_buffer_map (frame->buffer, &mapinfo, GST_MAP_READ))
+    return GST_FLOW_ERROR;
+
+  offset = parse->last_offset;
+  if (offset > 0)
+    offset -= 1;                /* it migth be in the middle marker */
+
+  while (offset < mapinfo.size) {
+    if (!gst_jpeg_parse (&seg, mapinfo.data, mapinfo.size, offset)) {
+      if (!valid_state (parse->state, GST_JPEG_PARSER_STATE_GOT_SOI)) {
+        /* Skip any garbage until SOI */
+        *skipsize = mapinfo.size;
+        GST_INFO_OBJECT (parse, "skipping %d bytes", *skipsize);
+      } else {
+        /* Accept anything after SOI */
+        parse->last_offset = mapinfo.size;
+      }
+      goto beach;
+    }
+
+    offset = seg.offset;
+    marker = seg.marker;
+
+    if (!valid_state (parse->state, GST_JPEG_PARSER_STATE_GOT_SOI)
+        && marker != GST_JPEG_MARKER_SOI)
+      continue;
+
+    /* check if the whole segment is available */
+    if (offset + seg.size > mapinfo.size) {
+      GST_DEBUG_OBJECT (parse, "incomplete segment: %x [offset %d]", marker,
+          offset);
+      parse->last_offset = offset - 2;
+      goto beach;
+    }
+
+    offset += seg.size;
+
+    GST_LOG_OBJECT (parse, "marker found: %x [offset %d / size %"
+        G_GSSIZE_FORMAT "]", marker, seg.offset, seg.size);
+
+    switch (marker) {
+      case GST_JPEG_MARKER_SOI:
+        /* This means that new SOI comes without an previous EOI. */
+        if (offset > 2
+            && (parse->interlace_mode == GST_VIDEO_INTERLACE_MODE_PROGRESSIVE
+                || parse->field == 0)) {
+          /* If already some data segment parsed, push it as a frame. */
+          if (valid_state (parse->state, GST_JPEG_PARSER_STATE_GOT_SOS)) {
+            gst_buffer_unmap (frame->buffer, &mapinfo);
+
+            frame->out_buffer = gst_buffer_copy_region (frame->buffer,
+                GST_BUFFER_COPY_ALL, 0, seg.offset - 2);
+            GST_MINI_OBJECT_FLAGS (frame->out_buffer) |=
+                GST_BUFFER_FLAG_CORRUPTED;
+
+            GST_WARNING_OBJECT (parse, "Push a frame without EOI, size %d",
+                seg.offset - 2);
+            return gst_jpeg_parse_finish_frame (parse, frame, seg.offset - 2);
+          }
+
+          gst_jpeg_parse_reset (parse);
+          parse->state |= GST_JPEG_PARSER_STATE_GOT_SOI;
+          /* unset tags */
+          gst_base_parse_merge_tags (bparse, NULL, GST_TAG_MERGE_UNDEFINED);
+
+          *skipsize = offset - 2;
+          GST_DEBUG_OBJECT (parse, "skipping %d bytes before SOI", *skipsize);
+          parse->last_offset = 2;
+          goto beach;
+        }
+
+        /* unset tags */
+        gst_base_parse_merge_tags (bparse, NULL, GST_TAG_MERGE_UNDEFINED);
+        parse->state |= GST_JPEG_PARSER_STATE_GOT_SOI;
+        break;
+      case GST_JPEG_MARKER_EOI:
+        if (parse->interlace_mode == GST_VIDEO_INTERLACE_MODE_PROGRESSIVE
+            || parse->field == 1) {
+          gst_buffer_unmap (frame->buffer, &mapinfo);
+          return gst_jpeg_parse_finish_frame (parse, frame, seg.offset);
+        } else if (parse->interlace_mode == GST_VIDEO_INTERLACE_MODE_INTERLEAVED
+            && parse->field == 0) {
+          parse->field = 1;
+          parse->state = 0;
+        }
+        break;
+      case GST_JPEG_MARKER_SOS:
+        if (!valid_state (parse->state, GST_JPEG_PARSER_STATE_GOT_SOF))
+          GST_WARNING_OBJECT (parse, "SOS marker without SOF one");
+        parse->state |= GST_JPEG_PARSER_STATE_GOT_SOS;
+        break;
+      case GST_JPEG_MARKER_COM:
+        if (!gst_jpeg_parse_com (parse, &seg))
+          GST_WARNING_OBJECT (parse, "Failed to parse com segment");
+        break;
+      case GST_JPEG_MARKER_APP0:
+        if (!gst_jpeg_parse_app0 (parse, &seg))
+          GST_WARNING_OBJECT (parse, "Failed to parse app0 segment");
+        break;
+      case GST_JPEG_MARKER_APP1:
+        if (!gst_jpeg_parse_app1 (parse, &seg))
+          GST_WARNING_OBJECT (parse, "Failed to parse app1 segment");
+        break;
+      case GST_JPEG_MARKER_APP14:
+        if (!gst_jpeg_parse_app14 (parse, &seg))
+          GST_WARNING_OBJECT (parse, "Failed to parse app14 segment");
+        break;
+      case GST_JPEG_MARKER_DHT:
+      case GST_JPEG_MARKER_DAC:
+        /* to avoid break the below SOF interval */
+        break;
+      default:
+        /* SOFn segments */
+        if (marker >= GST_JPEG_MARKER_SOF_MIN &&
+            marker <= GST_JPEG_MARKER_SOF_MAX) {
+          if (!valid_state (parse->state, GST_JPEG_PARSER_STATE_GOT_SOF)
+              && gst_jpeg_parse_sof (parse, &seg)) {
+            gint8 sof;
+
+            parse->state |= GST_JPEG_PARSER_STATE_GOT_SOF;
+            sof = marker - 0xc0;
+            if (parse->sof != sof) {
+              parse->sof = sof;
+              parse->renegotiate = TRUE;
+            }
+          } else {
+            GST_ELEMENT_ERROR (parse, STREAM, FORMAT,
+                ("Invalid data"), ("Duplicated or bad SOF marker"));
+            gst_buffer_unmap (frame->buffer, &mapinfo);
+            gst_jpeg_parse_reset (parse);
+            return GST_FLOW_ERROR;
+          }
+        }
+        break;
+    }
+  }
+
+  parse->last_offset = offset;
+
+beach:
+  gst_buffer_unmap (frame->buffer, &mapinfo);
+  return GST_FLOW_OK;
+}
+
+static gboolean
+gst_jpeg_parse_sink_event (GstBaseParse * bparse, GstEvent * event)
+{
+  GstJpegParse *parse = GST_JPEG_PARSE_CAST (bparse);
+
+  GST_DEBUG_OBJECT (parse, "event : %s", GST_EVENT_TYPE_NAME (event));
+
+  switch (GST_EVENT_TYPE (event)) {
+    case GST_EVENT_FLUSH_STOP:
+      gst_jpeg_parse_reset (parse);
+      break;
+    default:
+      break;
+  }
+
+  return GST_BASE_PARSE_CLASS (parent_class)->sink_event (bparse, event);
+}
+
+static gboolean
+gst_jpeg_parse_start (GstBaseParse * bparse)
+{
+  GstJpegParse *parse = GST_JPEG_PARSE_CAST (bparse);
+
+  parse->framerate_numerator = 0;
+  parse->framerate_denominator = 1;
+
+  parse->first_picture = TRUE;
+  parse->renegotiate = TRUE;
+
+  parse->par_num = parse->par_den = 1;
+
+  parse->interlace_mode = GST_VIDEO_INTERLACE_MODE_PROGRESSIVE;
+  parse->field_order = GST_VIDEO_FIELD_ORDER_TOP_FIELD_FIRST;
+
+  gst_jpeg_parse_reset (parse);
+
+  gst_base_parse_set_min_frame_size (bparse, 2);
+
+  return TRUE;
+}
+
+static gboolean
+gst_jpeg_parse_stop (GstBaseParse * bparse)
+{
+  GstJpegParse *parse = GST_JPEG_PARSE_CAST (bparse);
+
+  if (parse->tags) {
+    gst_tag_list_unref (parse->tags);
+    parse->tags = NULL;
+  }
+  gst_clear_buffer (&parse->codec_data);
+  gst_clear_caps (&parse->prev_caps);
+  g_clear_pointer (&parse->colorimetry, g_free);
+
+  return TRUE;
+}
diff --git a/gst/jpegformat/gstjpegparse.h b/gst/jpegformat/gstjpegparse.h
new file mode 100644
index 0000000..d6a88fd
--- /dev/null
+++ b/gst/jpegformat/gstjpegparse.h
@@ -0,0 +1,97 @@
+/* GStreamer
+ *
+ * jpegparse: a parser for JPEG streams
+ *
+ * Copyright (C) <2009> Arnout Vandecappelle (Essensium/Mind) <arnout@mind.be>
+ *               <2022> Víctor Manuel Jáquez Leal <vjaquez@igalia.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef __GST_JPEG_PARSE_H__
+#define __GST_JPEG_PARSE_H__
+
+#include <gst/gst.h>
+#include <gst/base/gstbaseparse.h>
+#include <gst/video/video.h>
+
+G_BEGIN_DECLS
+
+#define GST_TYPE_JPEG_PARSE \
+  (gst_jpeg_parse_get_type())
+#define GST_JPEG_PARSE(obj) \
+  (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_JPEG_PARSE,GstJpegParse))
+#define GST_JPEG_PARSE_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_JPEG_PARSE,GstJpegParseClass))
+#define GST_IS_JPEG_PARSE(obj) \
+  (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_JPEG_PARSE))
+#define GST_IS_JPEG_PARSE_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_JPEG_PARSE))
+#define GST_JPEG_PARSE_CAST(obj) ((GstJpegParse *)obj)
+
+typedef struct _GstJpegParse           GstJpegParse;
+typedef struct _GstJpegParseClass      GstJpegParseClass;
+
+struct _GstJpegParse {
+  GstBaseParse parse;
+
+  guint last_offset;
+  gint state;
+
+  gboolean first_picture;
+  gboolean multiscope;
+  gboolean avid;
+  gboolean renegotiate;
+
+  gint8 sof;
+  gint8 adobe_transform;
+
+  /* the parsed frame size */
+  guint16 width, height;
+  gint orig_width, orig_height;
+
+  GstBuffer *codec_data;
+  char *colorimetry;
+  GstVideoInterlaceMode interlace_mode;
+  GstVideoFieldOrder field_order;
+  guint field;
+
+  /* format color space */
+  guint colorspace;
+  guint sampling;
+  gint par_num;
+  gint par_den;
+  GstCaps *prev_caps;
+
+  /* fps */
+  gint framerate_numerator;
+  gint framerate_denominator;
+
+  /* tags */
+  GstTagList *tags;
+};
+
+struct _GstJpegParseClass {
+  GstBaseParseClass  parent_class;
+};
+
+GType gst_jpeg_parse_get_type (void);
+
+GST_ELEMENT_REGISTER_DECLARE (jpegparse);
+
+G_END_DECLS
+
+#endif /* __GST_JPEG_PARSE_H__ */
diff --git a/gst/jpegformat/meson.build b/gst/jpegformat/meson.build
new file mode 100644
index 0000000..bf95f21
--- /dev/null
+++ b/gst/jpegformat/meson.build
@@ -0,0 +1,17 @@
+jpegf_sources = [
+  'gstjpegparser.c',
+  'gstjpegformat.c',
+  'gstjpegparse.c',
+  'gstjifmux.c',
+]
+
+gstjpegformat = library('gstjpegformat',
+  jpegf_sources,
+  c_args : gst_plugins_bad_args,
+  include_directories : [configinc],
+  dependencies : [gstbase_dep, gstvideo_dep, gsttag_dep],
+  install : true,
+  install_dir : plugins_install_dir,
+)
+pkgconfig.generate(gstjpegformat, install_dir : plugins_pkgconfig_install_dir)
+plugins += [gstjpegformat]
diff --git a/gst/meson.build b/gst/meson.build
index 18d033f..6ec3dcc 100644
--- a/gst/meson.build
+++ b/gst/meson.build
@@ -2,7 +2,7 @@ foreach plugin : ['alpha', 'apetag', 'audiofx', 'audioparsers', 'auparse',
                   'autodetect', 'avi', 'camerabin2', 'cutter', 'debugutils', 'deinterlace',
                   'dtmf', 'effectv', 'equalizer', 'flv', 'flx', 'goom',
                   'goom2k1', 'icydemux', 'id3demux', 'imagefreeze',
-                  'interleave', 'isomp4', 'law', 'level', 'matroska',
+                  'interleave', 'isomp4', 'jpegformat', 'law', 'level', 'matroska',
                   'monoscope', 'multifile', 'multipart', 'replaygain', 'rtp',
                   'rtpmanager', 'rtsp', 'shapewipe', 'smpte', 'spectrum',
                   'udp', 'videobox', 'videocrop', 'videofilter', 'videomixer',
diff --git a/meson_options.txt b/meson_options.txt
index e3cc2a4..af2dc04 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -22,6 +22,7 @@ option('id3demux', type : 'feature', value : 'auto')
 option('imagefreeze', type : 'feature', value : 'auto')
 option('interleave', type : 'feature', value : 'auto')
 option('isomp4', type : 'feature', value : 'auto')
+option('jpegformat', type : 'feature', value : 'auto')
 option('law', type : 'feature', value : 'auto')
 option('level', type : 'feature', value : 'auto')
 option('matroska', type : 'feature', value : 'auto')
@@ -113,6 +114,7 @@ option('v4l2-gudev', type : 'feature', value : 'auto', description : 'Use libgud
 # Common feature options
 option('examples', type : 'feature', value : 'auto', yield : true)
 option('tests', type : 'feature', value : 'auto', yield : true)
+option('introspection', type : 'feature', value : 'auto', yield : true, description : 'Generate gobject-introspection bindings')
 option('nls', type : 'feature', value : 'auto', yield: true, description : 'Enable native language support (translations)')
 option('orc', type : 'feature', value : 'auto', yield : true)
 option('gobject-cast-checks', type : 'feature', value : 'auto', yield : true,
