Initial commit of avi mjpeg decoder
std::streams removed debug code removed whitespaces removed stdint.h added and trailing whitespaces removed trailing whitespaces removed warnings on windows fix warnings on windows fix pt 2 Refactoring performed: AviMjpegStream class introduced. CAP_PROP_FRAME_WIDTH and CAP_PROP_FRAME_HEIGHT properties support added. Windows warnings & uninitialized variable fix Windows warning fix Win32 warning fix
This commit is contained in:
		@@ -552,6 +552,20 @@ static Ptr<IVideoCapture> IVideoCapture_create(int index)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
static Ptr<IVideoCapture> IVideoCapture_create(const String& filename)
 | 
			
		||||
{
 | 
			
		||||
    Ptr<IVideoCapture> capture;
 | 
			
		||||
 | 
			
		||||
    capture = createMotionJpegCapture(filename);
 | 
			
		||||
    if (capture && capture->isOpened())
 | 
			
		||||
    {
 | 
			
		||||
        return capture;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // failed open a camera
 | 
			
		||||
    return Ptr<IVideoCapture>();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static Ptr<IVideoWriter> IVideoWriter_create(const String& filename, int _fourcc, double fps, Size frameSize, bool isColor)
 | 
			
		||||
{
 | 
			
		||||
    Ptr<IVideoWriter> iwriter;
 | 
			
		||||
@@ -582,6 +596,10 @@ VideoCapture::~VideoCapture()
 | 
			
		||||
bool VideoCapture::open(const String& filename)
 | 
			
		||||
{
 | 
			
		||||
    if (isOpened()) release();
 | 
			
		||||
    icap = IVideoCapture_create(filename);
 | 
			
		||||
    if (!icap.empty())
 | 
			
		||||
        return true;
 | 
			
		||||
 | 
			
		||||
    cap.reset(cvCreateFileCapture(filename.c_str()));
 | 
			
		||||
    return isOpened();
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -40,13 +40,881 @@
 | 
			
		||||
//M*/
 | 
			
		||||
 | 
			
		||||
#include "precomp.hpp"
 | 
			
		||||
#include <deque>
 | 
			
		||||
#include <stdint.h>
 | 
			
		||||
 | 
			
		||||
namespace cv
 | 
			
		||||
{
 | 
			
		||||
 | 
			
		||||
Ptr<IVideoCapture> createMotionJpegCapture(const String&)
 | 
			
		||||
const uint32_t RIFF_CC = CV_FOURCC('R','I','F','F');
 | 
			
		||||
const uint32_t LIST_CC = CV_FOURCC('L','I','S','T');
 | 
			
		||||
const uint32_t HDRL_CC = CV_FOURCC('h','d','r','l');
 | 
			
		||||
const uint32_t AVIH_CC = CV_FOURCC('a','v','i','h');
 | 
			
		||||
const uint32_t STRL_CC = CV_FOURCC('s','t','r','l');
 | 
			
		||||
const uint32_t STRH_CC = CV_FOURCC('s','t','r','h');
 | 
			
		||||
const uint32_t VIDS_CC = CV_FOURCC('v','i','d','s');
 | 
			
		||||
const uint32_t MJPG_CC = CV_FOURCC('M','J','P','G');
 | 
			
		||||
const uint32_t MOVI_CC = CV_FOURCC('m','o','v','i');
 | 
			
		||||
const uint32_t IDX1_CC = CV_FOURCC('i','d','x','1');
 | 
			
		||||
const uint32_t AVI_CC  = CV_FOURCC('A','V','I',' ');
 | 
			
		||||
const uint32_t AVIX_CC = CV_FOURCC('A','V','I','X');
 | 
			
		||||
const uint32_t JUNK_CC = CV_FOURCC('J','U','N','K');
 | 
			
		||||
const uint32_t INFO_CC = CV_FOURCC('I','N','F','O');
 | 
			
		||||
 | 
			
		||||
String fourccToString(uint32_t fourcc);
 | 
			
		||||
 | 
			
		||||
String fourccToString(uint32_t fourcc)
 | 
			
		||||
{
 | 
			
		||||
    return Ptr<IVideoCapture>();
 | 
			
		||||
    return format("%c%c%c%c", fourcc & 255, (fourcc >> 8) & 255, (fourcc >> 16) & 255, (fourcc >> 24) & 255);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#ifndef DWORD
 | 
			
		||||
typedef uint32_t DWORD;
 | 
			
		||||
#endif
 | 
			
		||||
#ifndef WORD
 | 
			
		||||
typedef uint16_t WORD;
 | 
			
		||||
#endif
 | 
			
		||||
#ifndef LONG
 | 
			
		||||
typedef int32_t  LONG;
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#pragma pack(push, 1)
 | 
			
		||||
struct AviMainHeader
 | 
			
		||||
{
 | 
			
		||||
    DWORD dwMicroSecPerFrame;    //  The period between video frames
 | 
			
		||||
    DWORD dwMaxBytesPerSec;      //  Maximum data rate of the file
 | 
			
		||||
    DWORD dwReserved1;           // 0
 | 
			
		||||
    DWORD dwFlags;               //  0x10 AVIF_HASINDEX: The AVI file has an idx1 chunk containing an index at the end of the file.
 | 
			
		||||
    DWORD dwTotalFrames;         // Field of the main header specifies the total number of frames of data in file.
 | 
			
		||||
    DWORD dwInitialFrames;       // Is used for interleaved files
 | 
			
		||||
    DWORD dwStreams;             // Specifies the number of streams in the file.
 | 
			
		||||
    DWORD dwSuggestedBufferSize; // Field specifies the suggested buffer size forreading the file
 | 
			
		||||
    DWORD dwWidth;               // Fields specify the width of the AVIfile in pixels.
 | 
			
		||||
    DWORD dwHeight;              // Fields specify the height of the AVIfile in pixels.
 | 
			
		||||
    DWORD dwReserved[4];         // 0, 0, 0, 0
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct AviStreamHeader
 | 
			
		||||
{
 | 
			
		||||
    uint32_t fccType;              // 'vids', 'auds', 'txts'...
 | 
			
		||||
    uint32_t fccHandler;           // "cvid", "DIB "
 | 
			
		||||
    DWORD dwFlags;               // 0
 | 
			
		||||
    DWORD dwPriority;            // 0
 | 
			
		||||
    DWORD dwInitialFrames;       // 0
 | 
			
		||||
    DWORD dwScale;               // 1
 | 
			
		||||
    DWORD dwRate;                // Fps (dwRate - frame rate for video streams)
 | 
			
		||||
    DWORD dwStart;               // 0
 | 
			
		||||
    DWORD dwLength;              // Frames number (playing time of AVI file as defined by scale and rate)
 | 
			
		||||
    DWORD dwSuggestedBufferSize; // For reading the stream
 | 
			
		||||
    DWORD dwQuality;             // -1 (encoding quality. If set to -1, drivers use the default quality value)
 | 
			
		||||
    DWORD dwSampleSize;          // 0 means that each frame is in its own chunk
 | 
			
		||||
    struct {
 | 
			
		||||
        short int left;
 | 
			
		||||
        short int top;
 | 
			
		||||
        short int right;
 | 
			
		||||
        short int bottom;
 | 
			
		||||
    } rcFrame;                // If stream has a different size than dwWidth*dwHeight(unused)
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct AviIndex
 | 
			
		||||
{
 | 
			
		||||
    DWORD ckid;
 | 
			
		||||
    DWORD dwFlags;
 | 
			
		||||
    DWORD dwChunkOffset;
 | 
			
		||||
    DWORD dwChunkLength;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct BitmapInfoHeader
 | 
			
		||||
{
 | 
			
		||||
    DWORD biSize;                // Write header size of BITMAPINFO header structure
 | 
			
		||||
    LONG  biWidth;               // width in pixels
 | 
			
		||||
    LONG  biHeight;              // heigth in pixels
 | 
			
		||||
    WORD  biPlanes;              // Number of color planes in which the data is stored
 | 
			
		||||
    WORD  biBitCount;            // Number of bits per pixel
 | 
			
		||||
    DWORD biCompression;         // Type of compression used (uncompressed: NO_COMPRESSION=0)
 | 
			
		||||
    DWORD biSizeImage;           // Image Buffer. Quicktime needs 3 bytes also for 8-bit png
 | 
			
		||||
                                 //   (biCompression==NO_COMPRESSION)?0:xDim*yDim*bytesPerPixel;
 | 
			
		||||
    LONG  biXPelsPerMeter;       // Horizontal resolution in pixels per meter
 | 
			
		||||
    LONG  biYPelsPerMeter;       // Vertical resolution in pixels per meter
 | 
			
		||||
    DWORD biClrUsed;             // 256 (color table size; for 8-bit only)
 | 
			
		||||
    DWORD biClrImportant;        // Specifies that the first x colors of the color table. Are important to the DIB.
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct RiffChunk
 | 
			
		||||
{
 | 
			
		||||
    uint32_t m_four_cc;
 | 
			
		||||
    uint32_t m_size;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct RiffList
 | 
			
		||||
{
 | 
			
		||||
    uint32_t m_riff_or_list_cc;
 | 
			
		||||
    uint32_t m_size;
 | 
			
		||||
    uint32_t m_list_type_cc;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
#pragma pack(pop)
 | 
			
		||||
 | 
			
		||||
class MjpegInputStream
 | 
			
		||||
{
 | 
			
		||||
public:
 | 
			
		||||
    MjpegInputStream();
 | 
			
		||||
    MjpegInputStream(const String& filename);
 | 
			
		||||
    ~MjpegInputStream();
 | 
			
		||||
    MjpegInputStream& read(char*, uint64_t);
 | 
			
		||||
    MjpegInputStream& seekg(uint64_t);
 | 
			
		||||
    uint64_t tellg();
 | 
			
		||||
    bool isOpened() const;
 | 
			
		||||
    bool open(const String& filename);
 | 
			
		||||
    void close();
 | 
			
		||||
    operator bool();
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
    bool    m_is_valid;
 | 
			
		||||
    FILE*   m_f;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
MjpegInputStream::MjpegInputStream(): m_is_valid(false), m_f(0)
 | 
			
		||||
{
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
MjpegInputStream::MjpegInputStream(const String& filename): m_is_valid(false), m_f(0)
 | 
			
		||||
{
 | 
			
		||||
    open(filename);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool MjpegInputStream::isOpened() const
 | 
			
		||||
{
 | 
			
		||||
    return m_f != 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool MjpegInputStream::open(const String& filename)
 | 
			
		||||
{
 | 
			
		||||
    close();
 | 
			
		||||
 | 
			
		||||
    m_f = fopen(filename.c_str(), "rb");
 | 
			
		||||
 | 
			
		||||
    m_is_valid = isOpened();
 | 
			
		||||
 | 
			
		||||
    return m_is_valid;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void MjpegInputStream::close()
 | 
			
		||||
{
 | 
			
		||||
    if(isOpened())
 | 
			
		||||
    {
 | 
			
		||||
        m_is_valid = false;
 | 
			
		||||
 | 
			
		||||
        fclose(m_f);
 | 
			
		||||
        m_f = 0;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
MjpegInputStream& MjpegInputStream::read(char* buf, uint64_t count)
 | 
			
		||||
{
 | 
			
		||||
    if(isOpened())
 | 
			
		||||
    {
 | 
			
		||||
        m_is_valid = (count == fread((void*)buf, 1, (size_t)count, m_f));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return *this;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
MjpegInputStream& MjpegInputStream::seekg(uint64_t pos)
 | 
			
		||||
{
 | 
			
		||||
    m_is_valid = (fseek(m_f, (long)pos, SEEK_SET) == 0);
 | 
			
		||||
 | 
			
		||||
    return *this;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
uint64_t MjpegInputStream::tellg()
 | 
			
		||||
{
 | 
			
		||||
    return ftell(m_f);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
MjpegInputStream::operator bool()
 | 
			
		||||
{
 | 
			
		||||
    return m_is_valid;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
MjpegInputStream::~MjpegInputStream()
 | 
			
		||||
{
 | 
			
		||||
    close();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
MjpegInputStream& operator >> (MjpegInputStream& is, AviMainHeader& avih);
 | 
			
		||||
MjpegInputStream& operator >> (MjpegInputStream& is, AviStreamHeader& strh);
 | 
			
		||||
MjpegInputStream& operator >> (MjpegInputStream& is, BitmapInfoHeader& bmph);
 | 
			
		||||
MjpegInputStream& operator >> (MjpegInputStream& is, RiffList& riff_list);
 | 
			
		||||
MjpegInputStream& operator >> (MjpegInputStream& is, RiffChunk& riff_chunk);
 | 
			
		||||
MjpegInputStream& operator >> (MjpegInputStream& is, AviIndex& idx1);
 | 
			
		||||
 | 
			
		||||
MjpegInputStream& operator >> (MjpegInputStream& is, AviMainHeader& avih)
 | 
			
		||||
{
 | 
			
		||||
    is.read((char*)(&avih), sizeof(AviMainHeader));
 | 
			
		||||
    return is;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
MjpegInputStream& operator >> (MjpegInputStream& is, AviStreamHeader& strh)
 | 
			
		||||
{
 | 
			
		||||
    is.read((char*)(&strh), sizeof(AviStreamHeader));
 | 
			
		||||
    return is;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
MjpegInputStream& operator >> (MjpegInputStream& is, BitmapInfoHeader& bmph)
 | 
			
		||||
{
 | 
			
		||||
    is.read((char*)(&bmph), sizeof(BitmapInfoHeader));
 | 
			
		||||
    return is;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
MjpegInputStream& operator >> (MjpegInputStream& is, RiffList& riff_list)
 | 
			
		||||
{
 | 
			
		||||
    is.read((char*)(&riff_list), sizeof(riff_list));
 | 
			
		||||
    return is;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
MjpegInputStream& operator >> (MjpegInputStream& is, RiffChunk& riff_chunk)
 | 
			
		||||
{
 | 
			
		||||
    is.read((char*)(&riff_chunk), sizeof(riff_chunk));
 | 
			
		||||
    return is;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
MjpegInputStream& operator >> (MjpegInputStream& is, AviIndex& idx1)
 | 
			
		||||
{
 | 
			
		||||
    is.read((char*)(&idx1), sizeof(idx1));
 | 
			
		||||
    return is;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
AVI struct:
 | 
			
		||||
 | 
			
		||||
RIFF ('AVI '
 | 
			
		||||
      LIST ('hdrl'
 | 
			
		||||
            'avih'(<Main AVI Header>)
 | 
			
		||||
            LIST ('strl'
 | 
			
		||||
                  'strh'(<Stream header>)
 | 
			
		||||
                  'strf'(<Stream format>)
 | 
			
		||||
                  [ 'strd'(<Additional header data>) ]
 | 
			
		||||
                  [ 'strn'(<Stream name>) ]
 | 
			
		||||
                  [ 'indx'(<Odml index data>) ]
 | 
			
		||||
                  ...
 | 
			
		||||
                 )
 | 
			
		||||
            [LIST ('strl' ...)]
 | 
			
		||||
            [LIST ('strl' ...)]
 | 
			
		||||
            ...
 | 
			
		||||
            [LIST ('odml'
 | 
			
		||||
                  'dmlh'(<ODML header data>)
 | 
			
		||||
                  ...
 | 
			
		||||
                 )
 | 
			
		||||
            ]
 | 
			
		||||
            ...
 | 
			
		||||
           )
 | 
			
		||||
      [LIST ('INFO' ...)]
 | 
			
		||||
      [JUNK]
 | 
			
		||||
      LIST ('movi'
 | 
			
		||||
            {{xxdb|xxdc|xxpc|xxwb}(<Data>) | LIST ('rec '
 | 
			
		||||
                              {xxdb|xxdc|xxpc|xxwb}(<Data>)
 | 
			
		||||
                              {xxdb|xxdc|xxpc|xxwb}(<Data>)
 | 
			
		||||
                              ...
 | 
			
		||||
                             )
 | 
			
		||||
               ...
 | 
			
		||||
            }
 | 
			
		||||
            ...
 | 
			
		||||
           )
 | 
			
		||||
      ['idx1' (<AVI Index>) ]
 | 
			
		||||
     )
 | 
			
		||||
 | 
			
		||||
     {xxdb|xxdc|xxpc|xxwb}
 | 
			
		||||
     xx - stream number: 00, 01, 02, ...
 | 
			
		||||
     db - uncompressed video frame
 | 
			
		||||
     dc - commpressed video frame
 | 
			
		||||
     pc - palette change
 | 
			
		||||
     wb - audio frame
 | 
			
		||||
 | 
			
		||||
     JUNK section may pad any data section and must be ignored
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
typedef std::deque< std::pair<uint64_t, uint32_t> > frame_list;
 | 
			
		||||
typedef frame_list::iterator frame_iterator;
 | 
			
		||||
 | 
			
		||||
//Represents single MJPEG video stream within single AVI/AVIX entry
 | 
			
		||||
//Multiple video streams within single AVI/AVIX entry are not supported
 | 
			
		||||
//ODML index is not supported
 | 
			
		||||
class AviMjpegStream
 | 
			
		||||
{
 | 
			
		||||
public:
 | 
			
		||||
    AviMjpegStream();
 | 
			
		||||
    //stores founded frames in m_frame_list which be accessed via getFrames
 | 
			
		||||
    bool parseAvi(MjpegInputStream& in_str);
 | 
			
		||||
    //stores founded frames in in_frame_list. getFrames() would return empty list
 | 
			
		||||
    bool parseAvi(MjpegInputStream& in_str, frame_list& in_frame_list);
 | 
			
		||||
    size_t getFramesCount();
 | 
			
		||||
    frame_list& getFrames();
 | 
			
		||||
    uint32_t getWidth();
 | 
			
		||||
    uint32_t getHeight();
 | 
			
		||||
    double getFps();
 | 
			
		||||
 | 
			
		||||
protected:
 | 
			
		||||
 | 
			
		||||
    bool parseAviWithFrameList(MjpegInputStream& in_str, frame_list& in_frame_list);
 | 
			
		||||
    void skipJunk(RiffChunk& chunk, MjpegInputStream& in_str);
 | 
			
		||||
    void skipJunk(RiffList& list, MjpegInputStream& in_str);
 | 
			
		||||
    bool parseHdrlList(MjpegInputStream& in_str);
 | 
			
		||||
    bool parseIndex(MjpegInputStream& in_str, uint32_t index_size, frame_list& in_frame_list);
 | 
			
		||||
    bool parseMovi(MjpegInputStream& in_str, frame_list& in_frame_list);
 | 
			
		||||
    bool parseStrl(MjpegInputStream& in_str, uint8_t stream_id);
 | 
			
		||||
    bool parseInfo(MjpegInputStream& in_str);
 | 
			
		||||
    void printError(MjpegInputStream& in_str, RiffList& list, uint32_t expected_fourcc);
 | 
			
		||||
    void printError(MjpegInputStream& in_str, RiffChunk& chunk, uint32_t expected_fourcc);
 | 
			
		||||
 | 
			
		||||
    uint32_t   m_stream_id;
 | 
			
		||||
    uint64_t   m_movi_start;
 | 
			
		||||
    uint64_t   m_movi_end;
 | 
			
		||||
    frame_list m_frame_list;
 | 
			
		||||
    uint32_t   m_width;
 | 
			
		||||
    uint32_t   m_height;
 | 
			
		||||
    double     m_fps;
 | 
			
		||||
    bool       m_is_indx_present;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
AviMjpegStream::AviMjpegStream(): m_stream_id(0), m_movi_end(0), m_width(0), m_height(0), m_fps(0), m_is_indx_present(false)
 | 
			
		||||
{
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
size_t AviMjpegStream::getFramesCount()
 | 
			
		||||
{
 | 
			
		||||
    return m_frame_list.size();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
frame_list& AviMjpegStream::getFrames()
 | 
			
		||||
{
 | 
			
		||||
    return m_frame_list;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
uint32_t AviMjpegStream::getWidth()
 | 
			
		||||
{
 | 
			
		||||
    return m_width;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
uint32_t AviMjpegStream::getHeight()
 | 
			
		||||
{
 | 
			
		||||
    return m_height;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
double AviMjpegStream::getFps()
 | 
			
		||||
{
 | 
			
		||||
    return m_fps;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AviMjpegStream::printError(MjpegInputStream& in_str, RiffList& list, uint32_t expected_fourcc)
 | 
			
		||||
{
 | 
			
		||||
    if(!in_str)
 | 
			
		||||
    {
 | 
			
		||||
        fprintf(stderr, "Unexpected end of file while searching for %s list\n", fourccToString(expected_fourcc).c_str());
 | 
			
		||||
    }
 | 
			
		||||
    else if(list.m_riff_or_list_cc != LIST_CC)
 | 
			
		||||
    {
 | 
			
		||||
        fprintf(stderr, "Unexpected element. Expected: %s. Got: %s.\n", fourccToString(LIST_CC).c_str(), fourccToString(list.m_riff_or_list_cc).c_str());
 | 
			
		||||
    }
 | 
			
		||||
    else
 | 
			
		||||
    {
 | 
			
		||||
        fprintf(stderr, "Unexpected list type. Expected: %s. Got: %s.\n", fourccToString(expected_fourcc).c_str(), fourccToString(list.m_list_type_cc).c_str());
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AviMjpegStream::printError(MjpegInputStream& in_str, RiffChunk& chunk, uint32_t expected_fourcc)
 | 
			
		||||
{
 | 
			
		||||
    if(!in_str)
 | 
			
		||||
    {
 | 
			
		||||
        fprintf(stderr, "Unexpected end of file while searching for %s chunk\n", fourccToString(expected_fourcc).c_str());
 | 
			
		||||
    }
 | 
			
		||||
    else
 | 
			
		||||
    {
 | 
			
		||||
        fprintf(stderr, "Unexpected element. Expected: %s. Got: %s.\n", fourccToString(expected_fourcc).c_str(), fourccToString(chunk.m_four_cc).c_str());
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
bool AviMjpegStream::parseMovi(MjpegInputStream&, frame_list&)
 | 
			
		||||
{
 | 
			
		||||
    //not implemented
 | 
			
		||||
    return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool AviMjpegStream::parseInfo(MjpegInputStream&)
 | 
			
		||||
{
 | 
			
		||||
    //not implemented
 | 
			
		||||
    return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool AviMjpegStream::parseIndex(MjpegInputStream& in_str, uint32_t index_size, frame_list& in_frame_list)
 | 
			
		||||
{
 | 
			
		||||
    uint64_t index_end = in_str.tellg();
 | 
			
		||||
    index_end += index_size;
 | 
			
		||||
    bool result = false;
 | 
			
		||||
 | 
			
		||||
    while(in_str && (in_str.tellg() < index_end))
 | 
			
		||||
    {
 | 
			
		||||
        AviIndex idx1;
 | 
			
		||||
        in_str >> idx1;
 | 
			
		||||
 | 
			
		||||
        if(idx1.ckid == m_stream_id)
 | 
			
		||||
        {
 | 
			
		||||
            uint64_t absolute_pos = m_movi_start + idx1.dwChunkOffset;
 | 
			
		||||
 | 
			
		||||
            if(absolute_pos < m_movi_end)
 | 
			
		||||
            {
 | 
			
		||||
                in_frame_list.push_back(std::make_pair(absolute_pos, idx1.dwChunkLength));
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
                //unsupported case
 | 
			
		||||
                fprintf(stderr, "Frame offset points outside movi section.\n");
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        result = true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return result;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool AviMjpegStream::parseStrl(MjpegInputStream& in_str, uint8_t stream_id)
 | 
			
		||||
{
 | 
			
		||||
    RiffChunk strh;
 | 
			
		||||
    in_str >> strh;
 | 
			
		||||
 | 
			
		||||
    if(in_str && strh.m_four_cc == STRH_CC)
 | 
			
		||||
    {
 | 
			
		||||
        uint64_t next_strl_list = in_str.tellg();
 | 
			
		||||
        next_strl_list += strh.m_size;
 | 
			
		||||
 | 
			
		||||
        AviStreamHeader strm_hdr;
 | 
			
		||||
        in_str >> strm_hdr;
 | 
			
		||||
 | 
			
		||||
        if(strm_hdr.fccType == VIDS_CC && strm_hdr.fccHandler == MJPG_CC)
 | 
			
		||||
        {
 | 
			
		||||
            uint8_t first_digit = (stream_id/10) + '0';
 | 
			
		||||
            uint8_t second_digit = (stream_id%10) + '0';
 | 
			
		||||
 | 
			
		||||
            if(m_stream_id == 0)
 | 
			
		||||
            {
 | 
			
		||||
                m_stream_id = CV_FOURCC(first_digit, second_digit, 'd', 'c');
 | 
			
		||||
                m_fps = double(strm_hdr.dwRate)/strm_hdr.dwScale;
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
                //second mjpeg video stream found which is not supported
 | 
			
		||||
                fprintf(stderr, "More than one video stream found within AVI/AVIX list. Stream %c%cdc would be ignored\n", first_digit, second_digit);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return true;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AviMjpegStream::skipJunk(RiffChunk& chunk, MjpegInputStream& in_str)
 | 
			
		||||
{
 | 
			
		||||
    if(chunk.m_four_cc == JUNK_CC)
 | 
			
		||||
    {
 | 
			
		||||
        in_str.seekg(in_str.tellg() + chunk.m_size);
 | 
			
		||||
        in_str >> chunk;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AviMjpegStream::skipJunk(RiffList& list, MjpegInputStream& in_str)
 | 
			
		||||
{
 | 
			
		||||
    if(list.m_riff_or_list_cc == JUNK_CC)
 | 
			
		||||
    {
 | 
			
		||||
        //JUNK chunk is 4 bytes less than LIST
 | 
			
		||||
        in_str.seekg(in_str.tellg() + list.m_size - 4);
 | 
			
		||||
        in_str >> list;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool AviMjpegStream::parseHdrlList(MjpegInputStream& in_str)
 | 
			
		||||
{
 | 
			
		||||
    bool result = false;
 | 
			
		||||
 | 
			
		||||
    RiffChunk avih;
 | 
			
		||||
    in_str >> avih;
 | 
			
		||||
 | 
			
		||||
    if(in_str && avih.m_four_cc == AVIH_CC)
 | 
			
		||||
    {
 | 
			
		||||
        uint64_t next_strl_list = in_str.tellg();
 | 
			
		||||
        next_strl_list += avih.m_size;
 | 
			
		||||
 | 
			
		||||
        AviMainHeader avi_hdr;
 | 
			
		||||
        in_str >> avi_hdr;
 | 
			
		||||
 | 
			
		||||
        if(in_str)
 | 
			
		||||
        {
 | 
			
		||||
            m_is_indx_present = ((avi_hdr.dwFlags & 0x10) != 0);
 | 
			
		||||
            DWORD number_of_streams = avi_hdr.dwStreams;
 | 
			
		||||
            m_width = avi_hdr.dwWidth;
 | 
			
		||||
            m_height = avi_hdr.dwWidth;
 | 
			
		||||
 | 
			
		||||
            //the number of strl lists must be equal to number of streams specified in main avi header
 | 
			
		||||
            for(DWORD i = 0; i < number_of_streams; ++i)
 | 
			
		||||
            {
 | 
			
		||||
                in_str.seekg(next_strl_list);
 | 
			
		||||
                RiffList strl_list;
 | 
			
		||||
                in_str >> strl_list;
 | 
			
		||||
 | 
			
		||||
                if( in_str && strl_list.m_riff_or_list_cc == LIST_CC && strl_list.m_list_type_cc == STRL_CC )
 | 
			
		||||
                {
 | 
			
		||||
                    next_strl_list = in_str.tellg();
 | 
			
		||||
                    //RiffList::m_size includes fourCC field which we have already read
 | 
			
		||||
                    next_strl_list += (strl_list.m_size - 4);
 | 
			
		||||
 | 
			
		||||
                    result = parseStrl(in_str, (uint8_t)i);
 | 
			
		||||
                }
 | 
			
		||||
                else
 | 
			
		||||
                {
 | 
			
		||||
                    printError(in_str, strl_list, STRL_CC);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    else
 | 
			
		||||
    {
 | 
			
		||||
        printError(in_str, avih, AVIH_CC);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return result;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool AviMjpegStream::parseAviWithFrameList(MjpegInputStream& in_str, frame_list& in_frame_list)
 | 
			
		||||
{
 | 
			
		||||
    RiffList hdrl_list;
 | 
			
		||||
    in_str >> hdrl_list;
 | 
			
		||||
 | 
			
		||||
    if( in_str && hdrl_list.m_riff_or_list_cc == LIST_CC && hdrl_list.m_list_type_cc == HDRL_CC )
 | 
			
		||||
    {
 | 
			
		||||
        uint64_t next_list = in_str.tellg();
 | 
			
		||||
        //RiffList::m_size includes fourCC field which we have already read
 | 
			
		||||
        next_list += (hdrl_list.m_size - 4);
 | 
			
		||||
        //parseHdrlList sets m_is_indx_present flag which would be used later
 | 
			
		||||
        if(parseHdrlList(in_str))
 | 
			
		||||
        {
 | 
			
		||||
            in_str.seekg(next_list);
 | 
			
		||||
 | 
			
		||||
            RiffList some_list;
 | 
			
		||||
            in_str >> some_list;
 | 
			
		||||
 | 
			
		||||
            //an optional section INFO
 | 
			
		||||
            if(in_str && some_list.m_riff_or_list_cc == LIST_CC && some_list.m_list_type_cc == INFO_CC)
 | 
			
		||||
            {
 | 
			
		||||
                next_list = in_str.tellg();
 | 
			
		||||
                //RiffList::m_size includes fourCC field which we have already read
 | 
			
		||||
                next_list += (some_list.m_size - 4);
 | 
			
		||||
                parseInfo(in_str);
 | 
			
		||||
 | 
			
		||||
                in_str.seekg(next_list);
 | 
			
		||||
                in_str >> some_list;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            //an optional section JUNK
 | 
			
		||||
            skipJunk(some_list, in_str);
 | 
			
		||||
 | 
			
		||||
            //we are expecting to find here movi list. Must present in avi
 | 
			
		||||
            if(in_str && some_list.m_riff_or_list_cc == LIST_CC && some_list.m_list_type_cc == MOVI_CC)
 | 
			
		||||
            {
 | 
			
		||||
                bool is_index_found = false;
 | 
			
		||||
 | 
			
		||||
                m_movi_start = in_str.tellg();
 | 
			
		||||
                m_movi_start -= 4;
 | 
			
		||||
 | 
			
		||||
                m_movi_end = m_movi_start + some_list.m_size;
 | 
			
		||||
                //if m_is_indx_present is set to true we should find index
 | 
			
		||||
                if(m_is_indx_present)
 | 
			
		||||
                {
 | 
			
		||||
                    //we are expecting to find index section after movi list
 | 
			
		||||
                    uint32_t indx_pos = (uint32_t)m_movi_start + 4;
 | 
			
		||||
                    indx_pos += (some_list.m_size - 4);
 | 
			
		||||
                    in_str.seekg(indx_pos);
 | 
			
		||||
 | 
			
		||||
                    RiffChunk index_chunk;
 | 
			
		||||
                    in_str >> index_chunk;
 | 
			
		||||
 | 
			
		||||
                    if(in_str && index_chunk.m_four_cc == IDX1_CC)
 | 
			
		||||
                    {
 | 
			
		||||
                        is_index_found = parseIndex(in_str, index_chunk.m_size, in_frame_list);
 | 
			
		||||
                        //we are not going anywhere else
 | 
			
		||||
                    }
 | 
			
		||||
                    else
 | 
			
		||||
                    {
 | 
			
		||||
                        printError(in_str, index_chunk, IDX1_CC);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                //index not present or we were not able to find it
 | 
			
		||||
                //parsing movi list
 | 
			
		||||
                if(!is_index_found)
 | 
			
		||||
                {
 | 
			
		||||
                    //not implemented
 | 
			
		||||
                    parseMovi(in_str, in_frame_list);
 | 
			
		||||
 | 
			
		||||
                    fprintf(stderr, "Failed to parse avi: index was not found\n");
 | 
			
		||||
                    //we are not going anywhere else
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
                printError(in_str, some_list, MOVI_CC);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    else
 | 
			
		||||
    {
 | 
			
		||||
        printError(in_str, hdrl_list, HDRL_CC);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return in_frame_list.size() > 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool AviMjpegStream::parseAvi(MjpegInputStream& in_str, frame_list& in_frame_list)
 | 
			
		||||
{
 | 
			
		||||
    return parseAviWithFrameList(in_str, in_frame_list);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool AviMjpegStream::parseAvi(MjpegInputStream& in_str)
 | 
			
		||||
{
 | 
			
		||||
    return parseAviWithFrameList(in_str, m_frame_list);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class MotionJpegCapture: public IVideoCapture
 | 
			
		||||
{
 | 
			
		||||
public:
 | 
			
		||||
    virtual ~MotionJpegCapture();
 | 
			
		||||
    virtual double getProperty(int) const;
 | 
			
		||||
    virtual bool setProperty(int, double);
 | 
			
		||||
    virtual bool grabFrame();
 | 
			
		||||
    virtual bool retrieveFrame(int, OutputArray);
 | 
			
		||||
    virtual bool isOpened() const;
 | 
			
		||||
    virtual int getCaptureDomain() { return CAP_ANY; } // Return the type of the capture object: CAP_VFW, etc...
 | 
			
		||||
    MotionJpegCapture(const String&);
 | 
			
		||||
 | 
			
		||||
    bool open(const String&);
 | 
			
		||||
    void close();
 | 
			
		||||
protected:
 | 
			
		||||
 | 
			
		||||
    bool parseRiff(MjpegInputStream& in_str);
 | 
			
		||||
 | 
			
		||||
    inline uint64_t getFramePos() const;
 | 
			
		||||
    std::vector<char> readFrame(frame_iterator it);
 | 
			
		||||
 | 
			
		||||
    MjpegInputStream m_file_stream;
 | 
			
		||||
    bool             m_is_first_frame;
 | 
			
		||||
    frame_list       m_mjpeg_frames;
 | 
			
		||||
 | 
			
		||||
    frame_iterator   m_frame_iterator;
 | 
			
		||||
    Mat              m_current_frame;
 | 
			
		||||
 | 
			
		||||
    //frame width/height and fps could be different for
 | 
			
		||||
    //each frame/stream. At the moment we suppose that they
 | 
			
		||||
    //stays the same within single avi file.
 | 
			
		||||
    uint32_t         m_frame_width;
 | 
			
		||||
    uint32_t         m_frame_height;
 | 
			
		||||
    double           m_fps;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
uint64_t MotionJpegCapture::getFramePos() const
 | 
			
		||||
{
 | 
			
		||||
    if(m_is_first_frame)
 | 
			
		||||
        return 0;
 | 
			
		||||
 | 
			
		||||
    if(m_frame_iterator == m_mjpeg_frames.end())
 | 
			
		||||
        return m_mjpeg_frames.size();
 | 
			
		||||
 | 
			
		||||
    return m_frame_iterator - m_mjpeg_frames.begin() + 1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool MotionJpegCapture::setProperty(int property, double value)
 | 
			
		||||
{
 | 
			
		||||
    if(property == CAP_PROP_POS_FRAMES)
 | 
			
		||||
    {
 | 
			
		||||
        if(int(value) == 0)
 | 
			
		||||
        {
 | 
			
		||||
            m_is_first_frame = true;
 | 
			
		||||
            m_frame_iterator = m_mjpeg_frames.end();
 | 
			
		||||
            return true;
 | 
			
		||||
        }
 | 
			
		||||
        else if(m_mjpeg_frames.size() > value)
 | 
			
		||||
        {
 | 
			
		||||
            m_frame_iterator = m_mjpeg_frames.begin() + int(value - 1);
 | 
			
		||||
            m_is_first_frame = false;
 | 
			
		||||
            return true;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
double MotionJpegCapture::getProperty(int property) const
 | 
			
		||||
{
 | 
			
		||||
    switch(property)
 | 
			
		||||
    {
 | 
			
		||||
        case CAP_PROP_POS_FRAMES:
 | 
			
		||||
            return (double)getFramePos();
 | 
			
		||||
        case CAP_PROP_POS_AVI_RATIO:
 | 
			
		||||
            return double(getFramePos())/m_mjpeg_frames.size();
 | 
			
		||||
        case CAP_PROP_FRAME_WIDTH:
 | 
			
		||||
            return (double)m_frame_width;
 | 
			
		||||
        case CAP_PROP_FRAME_HEIGHT:
 | 
			
		||||
            return (double)m_frame_height;
 | 
			
		||||
        case CAP_PROP_FPS:
 | 
			
		||||
            return m_fps;
 | 
			
		||||
        case CAP_PROP_FOURCC:
 | 
			
		||||
            return (double)CV_FOURCC('M','J','P','G');
 | 
			
		||||
        case CAP_PROP_FRAME_COUNT:
 | 
			
		||||
            return (double)m_mjpeg_frames.size();
 | 
			
		||||
        case CAP_PROP_FORMAT:
 | 
			
		||||
            return 0;
 | 
			
		||||
        default:
 | 
			
		||||
            return 0;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::vector<char> MotionJpegCapture::readFrame(frame_iterator it)
 | 
			
		||||
{
 | 
			
		||||
    m_file_stream.seekg(it->first);
 | 
			
		||||
 | 
			
		||||
    RiffChunk chunk;
 | 
			
		||||
    m_file_stream >> chunk;
 | 
			
		||||
 | 
			
		||||
    std::vector<char> result;
 | 
			
		||||
 | 
			
		||||
    result.reserve(chunk.m_size);
 | 
			
		||||
    result.resize(chunk.m_size);
 | 
			
		||||
 | 
			
		||||
    m_file_stream.read(result.data(), chunk.m_size);
 | 
			
		||||
 | 
			
		||||
    return result;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool MotionJpegCapture::grabFrame()
 | 
			
		||||
{
 | 
			
		||||
    if(isOpened())
 | 
			
		||||
    {
 | 
			
		||||
        if(m_is_first_frame)
 | 
			
		||||
        {
 | 
			
		||||
            m_is_first_frame = false;
 | 
			
		||||
            m_frame_iterator = m_mjpeg_frames.begin();
 | 
			
		||||
        }
 | 
			
		||||
        else
 | 
			
		||||
        {
 | 
			
		||||
            ++m_frame_iterator;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return m_frame_iterator != m_mjpeg_frames.end();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool MotionJpegCapture::retrieveFrame(int, OutputArray output_frame)
 | 
			
		||||
{
 | 
			
		||||
    if(m_frame_iterator != m_mjpeg_frames.end())
 | 
			
		||||
    {
 | 
			
		||||
        std::vector<char> data = readFrame(m_frame_iterator);
 | 
			
		||||
 | 
			
		||||
        if(data.size())
 | 
			
		||||
        {
 | 
			
		||||
            m_current_frame = imdecode(data, CV_LOAD_IMAGE_ANYDEPTH | CV_LOAD_IMAGE_COLOR);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        m_current_frame.copyTo(output_frame);
 | 
			
		||||
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
MotionJpegCapture::~MotionJpegCapture()
 | 
			
		||||
{
 | 
			
		||||
    close();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
MotionJpegCapture::MotionJpegCapture(const String& filename)
 | 
			
		||||
{
 | 
			
		||||
    open(filename);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool MotionJpegCapture::isOpened() const
 | 
			
		||||
{
 | 
			
		||||
    return m_mjpeg_frames.size() > 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void MotionJpegCapture::close()
 | 
			
		||||
{
 | 
			
		||||
    m_file_stream.close();
 | 
			
		||||
    m_frame_iterator = m_mjpeg_frames.end();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool MotionJpegCapture::open(const String& filename)
 | 
			
		||||
{
 | 
			
		||||
    close();
 | 
			
		||||
 | 
			
		||||
    m_file_stream.open(filename);
 | 
			
		||||
 | 
			
		||||
    m_frame_iterator = m_mjpeg_frames.end();
 | 
			
		||||
    m_is_first_frame = true;
 | 
			
		||||
 | 
			
		||||
    if(!parseRiff(m_file_stream))
 | 
			
		||||
    {
 | 
			
		||||
        close();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return isOpened();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
bool MotionJpegCapture::parseRiff(MjpegInputStream& in_str)
 | 
			
		||||
{
 | 
			
		||||
    bool result = false;
 | 
			
		||||
    while(in_str)
 | 
			
		||||
    {
 | 
			
		||||
        RiffList riff_list;
 | 
			
		||||
 | 
			
		||||
        in_str >> riff_list;
 | 
			
		||||
 | 
			
		||||
        if( in_str && riff_list.m_riff_or_list_cc == RIFF_CC &&
 | 
			
		||||
            ((riff_list.m_list_type_cc == AVI_CC) | (riff_list.m_list_type_cc == AVIX_CC)) )
 | 
			
		||||
        {
 | 
			
		||||
            uint64_t next_riff = in_str.tellg();
 | 
			
		||||
            //RiffList::m_size includes fourCC field which we have already read
 | 
			
		||||
            next_riff += (riff_list.m_size - 4);
 | 
			
		||||
 | 
			
		||||
            AviMjpegStream mjpeg_video_stream;
 | 
			
		||||
            bool is_parsed = mjpeg_video_stream.parseAvi(in_str, m_mjpeg_frames);
 | 
			
		||||
            result = result || is_parsed;
 | 
			
		||||
 | 
			
		||||
            if(is_parsed)
 | 
			
		||||
            {
 | 
			
		||||
                m_frame_width = mjpeg_video_stream.getWidth();
 | 
			
		||||
                m_frame_height = mjpeg_video_stream.getHeight();
 | 
			
		||||
                m_fps = mjpeg_video_stream.getFps();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            in_str.seekg(next_riff);
 | 
			
		||||
        }
 | 
			
		||||
        else
 | 
			
		||||
        {
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return result;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
Ptr<IVideoCapture> createMotionJpegCapture(const String& filename)
 | 
			
		||||
{
 | 
			
		||||
    Ptr<MotionJpegCapture> mjdecoder(new MotionJpegCapture(filename));
 | 
			
		||||
    if( mjdecoder->isOpened() )
 | 
			
		||||
        return mjdecoder;
 | 
			
		||||
    return Ptr<MotionJpegCapture>();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user