diff --git a/Telegram/SourceFiles/app.cpp b/Telegram/SourceFiles/app.cpp
index 256421f30..9d4e55165 100644
--- a/Telegram/SourceFiles/app.cpp
+++ b/Telegram/SourceFiles/app.cpp
@@ -2462,11 +2462,11 @@ namespace {
 		return ::sharedContactItems;
 	}
 
-	void regGifItem(ClipReader *reader, HistoryItem *item) {
+	void regGifItem(Media::Clip::Reader *reader, HistoryItem *item) {
 		::gifItems.insert(reader, item);
 	}
 
-	void unregGifItem(ClipReader *reader) {
+	void unregGifItem(Media::Clip::Reader *reader) {
 		::gifItems.remove(reader);
 	}
 
diff --git a/Telegram/SourceFiles/app.h b/Telegram/SourceFiles/app.h
index d2e06ce10..1c80ba848 100644
--- a/Telegram/SourceFiles/app.h
+++ b/Telegram/SourceFiles/app.h
@@ -39,7 +39,7 @@ typedef QHash<PhotoData*, HistoryItemsMap> PhotoItems;
 typedef QHash<DocumentData*, HistoryItemsMap> DocumentItems;
 typedef QHash<WebPageData*, HistoryItemsMap> WebPageItems;
 typedef QHash<int32, HistoryItemsMap> SharedContactItems;
-typedef QHash<ClipReader*, HistoryItem*> GifItems;
+typedef QHash<Media::Clip::Reader*, HistoryItem*> GifItems;
 
 typedef QHash<PhotoId, PhotoData*> PhotosData;
 typedef QHash<DocumentId, DocumentData*> DocumentsData;
@@ -257,8 +257,8 @@ namespace App {
 	const SharedContactItems &sharedContactItems();
 	QString phoneFromSharedContact(int32 userId);
 
-	void regGifItem(ClipReader *reader, HistoryItem *item);
-	void unregGifItem(ClipReader *reader);
+	void regGifItem(Media::Clip::Reader *reader, HistoryItem *item);
+	void unregGifItem(Media::Clip::Reader *reader);
 	void stopGifItems();
 
 	void regMuted(PeerData *peer, int32 changeIn);
diff --git a/Telegram/SourceFiles/history.cpp b/Telegram/SourceFiles/history.cpp
index ae585def9..ded75552a 100644
--- a/Telegram/SourceFiles/history.cpp
+++ b/Telegram/SourceFiles/history.cpp
@@ -26,6 +26,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
 #include "styles/style_dialogs.h"
 #include "history/history_service_layout.h"
 #include "data/data_drafts.h"
+#include "media/media_clip_reader.h"
 #include "lang.h"
 #include "mainwidget.h"
 #include "application.h"
@@ -2906,15 +2907,17 @@ void HistoryItem::setUnreadBarFreezed() {
 	}
 }
 
-void HistoryItem::clipCallback(ClipReaderNotification notification) {
+void HistoryItem::clipCallback(Media::Clip::Notification notification) {
+	using namespace Media::Clip;
+
 	HistoryMedia *media = getMedia();
 	if (!media) return;
 
-	ClipReader *reader = media ? media->getClipReader() : 0;
+	Reader *reader = media ? media->getClipReader() : 0;
 	if (!reader) return;
 
 	switch (notification) {
-	case ClipReaderReinit: {
+	case NotificationReinit: {
 		bool stopped = false;
 		if (reader->paused()) {
 			if (MainWidget *m = App::main()) {
@@ -2933,7 +2936,7 @@ void HistoryItem::clipCallback(ClipReaderNotification notification) {
 		}
 	} break;
 
-	case ClipReaderRepaint: {
+	case NotificationRepaint: {
 		if (!reader->currentDisplayed()) {
 			Ui::repaintHistoryItem(this);
 		}
@@ -4526,13 +4529,13 @@ void HistoryGif::initDimensions() {
 
 	bool bubble = _parent->hasBubble();
 	int32 tw = 0, th = 0;
-	if (gif() && _gif->state() == ClipError) {
+	if (gif() && _gif->state() == Media::Clip::State::Error) {
 		if (!_gif->autoplay()) {
 			Ui::showLayer(new InformBox(lang(lng_gif_error)));
 		}
 		App::unregGifItem(_gif);
 		delete _gif;
-		_gif = BadClipReader;
+		_gif = Media::Clip::BadReader;
 	}
 
 	if (gif() && _gif->ready()) {
@@ -4637,7 +4640,7 @@ void HistoryGif::draw(Painter &p, const QRect &r, TextSelection selection, uint6
 	bool loaded = _data->loaded(), displayLoading = (_parent->id < 0) || _data->displayLoading();
 	bool selected = (selection == FullSelection);
 
-	if (loaded && !gif() && _gif != BadClipReader && cAutoPlayGif()) {
+	if (loaded && !gif() && _gif != Media::Clip::BadReader && cAutoPlayGif()) {
 		Ui::autoplayMediaInlineAsync(_parent->fullId());
 	}
 
@@ -4684,7 +4687,7 @@ void HistoryGif::draw(Painter &p, const QRect &r, TextSelection selection, uint6
 		App::roundRect(p, rthumb, textstyleCurrent()->selectOverlay, SelectedOverlayCorners);
 	}
 
-	if (radial || (!_gif && ((!loaded && !_data->loading()) || !cAutoPlayGif())) || (_gif == BadClipReader)) {
+	if (radial || (!_gif && ((!loaded && !_data->loading()) || !cAutoPlayGif())) || (_gif == Media::Clip::BadReader)) {
         float64 radialOpacity = (radial && loaded && _parent->id > 0) ? _animation->radial.opacity() : 1;
 		QRect inner(rthumb.x() + (rthumb.width() - st::msgFileSize) / 2, rthumb.y() + (rthumb.height() - st::msgFileSize) / 2, st::msgFileSize, st::msgFileSize);
 		p.setPen(Qt::NoPen);
@@ -4848,7 +4851,7 @@ bool HistoryGif::playInline(bool autoplay) {
 		if (!cAutoPlayGif()) {
 			App::stopGifItems();
 		}
-		_gif = new ClipReader(_data->location(), _data->data(), func(_parent, &HistoryItem::clipCallback));
+		_gif = new Media::Clip::Reader(_data->location(), _data->data(), func(_parent, &HistoryItem::clipCallback));
 		App::regGifItem(_gif, _parent);
 		if (gif()) _gif->setAutoplay();
 	}
diff --git a/Telegram/SourceFiles/history.h b/Telegram/SourceFiles/history.h
index a38257e02..8f867bb09 100644
--- a/Telegram/SourceFiles/history.h
+++ b/Telegram/SourceFiles/history.h
@@ -1412,7 +1412,7 @@ public:
 		return _text.isEmpty() && !_media;
 	}
 
-	void clipCallback(ClipReaderNotification notification);
+	void clipCallback(Media::Clip::Notification notification);
 
 	virtual ~HistoryItem();
 
@@ -1656,7 +1656,7 @@ public:
 	virtual DocumentData *getDocument() {
 		return nullptr;
 	}
-	virtual ClipReader *getClipReader() {
+	virtual Media::Clip::Reader *getClipReader() {
 		return nullptr;
 	}
 
@@ -2140,7 +2140,7 @@ public:
 	DocumentData *getDocument() override {
 		return _data;
 	}
-	ClipReader *getClipReader() override {
+	Media::Clip::Reader *getClipReader() override {
 		return gif();
 	}
 
@@ -2189,12 +2189,12 @@ private:
 	int32 _thumbw, _thumbh;
 	Text _caption;
 
-	ClipReader *_gif;
-	ClipReader *gif() {
-		return (_gif == BadClipReader) ? nullptr : _gif;
+	Media::Clip::Reader *_gif;
+	Media::Clip::Reader *gif() {
+		return (_gif == Media::Clip::BadReader) ? nullptr : _gif;
 	}
-	const ClipReader *gif() const {
-		return (_gif == BadClipReader) ? nullptr : _gif;
+	const Media::Clip::Reader *gif() const {
+		return (_gif == Media::Clip::BadReader) ? nullptr : _gif;
 	}
 
 	void setStatusSize(int32 newSize) const;
@@ -2377,7 +2377,7 @@ public:
 	DocumentData *getDocument() override {
 		return _attach ? _attach->getDocument() : 0;
 	}
-	ClipReader *getClipReader() override {
+	Media::Clip::Reader *getClipReader() override {
 		return _attach ? _attach->getClipReader() : 0;
 	}
 	bool playInline(bool autoplay) override {
diff --git a/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.cpp b/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.cpp
index c3fa01dd6..5b9f7d89b 100644
--- a/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.cpp
+++ b/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.cpp
@@ -23,6 +23,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
 
 #include "styles/style_overview.h"
 #include "inline_bots/inline_bot_result.h"
+#include "media/media_clip_reader.h"
 #include "localstorage.h"
 #include "mainwidget.h"
 #include "lang.h"
@@ -131,9 +132,9 @@ void Gif::paint(Painter &p, const QRect &clip, const PaintContext *context) cons
 	document->automaticLoad(nullptr);
 
 	bool loaded = document->loaded(), loading = document->loading(), displayLoading = document->displayLoading();
-	if (loaded && !gif() && _gif != BadClipReader) {
+	if (loaded && !gif() && _gif != Media::Clip::BadReader) {
 		Gif *that = const_cast<Gif*>(this);
-		that->_gif = new ClipReader(document->location(), document->data(), func(that, &Gif::clipCallback));
+		that->_gif = new Media::Clip::Reader(document->location(), document->data(), func(that, &Gif::clipCallback));
 		if (gif()) _gif->setAutoplay();
 	}
 
@@ -162,7 +163,7 @@ void Gif::paint(Painter &p, const QRect &clip, const PaintContext *context) cons
 		}
 	}
 
-	if (radial || (!_gif && !loaded && !loading) || (_gif == BadClipReader)) {
+	if (radial || (!_gif && !loaded && !loading) || (_gif == Media::Clip::BadReader)) {
 		float64 radialOpacity = (radial && loaded) ? _animation->radial.opacity() : 1;
 		if (_animation && _animation->_a_over.animating(context->ms)) {
 			float64 over = _animation->_a_over.current();
@@ -326,13 +327,14 @@ void Gif::step_radial(uint64 ms, bool timer) {
 	}
 }
 
-void Gif::clipCallback(ClipReaderNotification notification) {
+void Gif::clipCallback(Media::Clip::Notification notification) {
+	using namespace Media::Clip;
 	switch (notification) {
-	case ClipReaderReinit: {
+	case NotificationReinit: {
 		if (gif()) {
-			if (_gif->state() == ClipError) {
+			if (_gif->state() == State::Error) {
 				delete _gif;
-				_gif = BadClipReader;
+				_gif = BadReader;
 				getShownDocument()->forget();
 			} else if (_gif->ready() && !_gif->started()) {
 				int32 height = st::inlineMediaHeight;
@@ -348,7 +350,7 @@ void Gif::clipCallback(ClipReaderNotification notification) {
 		update();
 	} break;
 
-	case ClipReaderRepaint: {
+	case NotificationRepaint: {
 		if (gif() && !_gif->currentDisplayed()) {
 			update();
 		}
diff --git a/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.h b/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.h
index 7095f8828..85a8ccc08 100644
--- a/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.h
+++ b/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.h
@@ -92,10 +92,10 @@ private:
 		return ~StateFlags(flag);
 	}
 
-	ClipReader *_gif = nullptr;
+	Media::Clip::Reader *_gif = nullptr;
 	ClickHandlerPtr _delete;
 	bool gif() const {
-		return (!_gif || _gif == BadClipReader) ? false : true;
+		return (!_gif || _gif == Media::Clip::BadReader) ? false : true;
 	}
 	mutable QPixmap _thumb;
 	void prepareThumb(int32 width, int32 height, const QSize &frame) const;
@@ -104,7 +104,7 @@ private:
 	bool isRadialAnimation(uint64 ms) const;
 	void step_radial(uint64 ms, bool timer);
 
-	void clipCallback(ClipReaderNotification notification);
+	void clipCallback(Media::Clip::Notification notification);
 
 	struct AnimationData {
 		AnimationData(AnimationCallbacks &&callbacks)
diff --git a/Telegram/SourceFiles/layerwidget.cpp b/Telegram/SourceFiles/layerwidget.cpp
index 54d061526..08f840da6 100644
--- a/Telegram/SourceFiles/layerwidget.cpp
+++ b/Telegram/SourceFiles/layerwidget.cpp
@@ -21,6 +21,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
 #include "stdafx.h"
 #include "lang.h"
 
+#include "media/media_clip_reader.h"
 #include "layerwidget.h"
 #include "application.h"
 #include "mainwindow.h"
@@ -346,9 +347,9 @@ QPixmap MediaPreviewWidget::currentImage() const {
 		} else {
 			_document->automaticLoad(nullptr);
 			if (_document->loaded()) {
-				if (!_gif && _gif != BadClipReader) {
-					MediaPreviewWidget *that = const_cast<MediaPreviewWidget*>(this);
-					that->_gif = new ClipReader(_document->location(), _document->data(), func(that, &MediaPreviewWidget::clipCallback));
+				if (!_gif && _gif != Media::Clip::BadReader) {
+					auto that = const_cast<MediaPreviewWidget*>(this);
+					that->_gif = new Media::Clip::Reader(_document->location(), _document->data(), func(that, &MediaPreviewWidget::clipCallback));
 					if (gif()) _gif->setAutoplay();
 				}
 			}
@@ -385,12 +386,13 @@ QPixmap MediaPreviewWidget::currentImage() const {
 	return _cache;
 }
 
-void MediaPreviewWidget::clipCallback(ClipReaderNotification notification) {
+void MediaPreviewWidget::clipCallback(Media::Clip::Notification notification) {
+	using namespace Media::Clip;
 	switch (notification) {
-	case ClipReaderReinit: {
-		if (gif() && _gif->state() == ClipError) {
+	case NotificationReinit: {
+		if (gif() && _gif->state() == State::Error) {
 			delete _gif;
-			_gif = BadClipReader;
+			_gif = BadReader;
 		}
 
 		if (gif() && _gif->ready() && !_gif->started()) {
@@ -401,7 +403,7 @@ void MediaPreviewWidget::clipCallback(ClipReaderNotification notification) {
 		update();
 	} break;
 
-	case ClipReaderRepaint: {
+	case NotificationRepaint: {
 		if (gif() && !_gif->currentDisplayed()) {
 			update();
 		}
diff --git a/Telegram/SourceFiles/layerwidget.h b/Telegram/SourceFiles/layerwidget.h
index e1d408f1e..c5ba79eb7 100644
--- a/Telegram/SourceFiles/layerwidget.h
+++ b/Telegram/SourceFiles/layerwidget.h
@@ -135,12 +135,12 @@ private:
 	Animation _a_shown;
 	DocumentData *_document = nullptr;
 	PhotoData *_photo = nullptr;
-	ClipReader *_gif = nullptr;
+	Media::Clip::Reader *_gif = nullptr;
 	bool gif() const {
-		return (!_gif || _gif == BadClipReader) ? false : true;
+		return (!_gif || _gif == Media::Clip::BadReader) ? false : true;
 	}
 
-	void clipCallback(ClipReaderNotification notification);
+	void clipCallback(Media::Clip::Notification notification);
 
 	enum CacheStatus {
 		CacheNotLoaded,
diff --git a/Telegram/SourceFiles/localimageloader.cpp b/Telegram/SourceFiles/localimageloader.cpp
index f484ccb9b..cec36b9f4 100644
--- a/Telegram/SourceFiles/localimageloader.cpp
+++ b/Telegram/SourceFiles/localimageloader.cpp
@@ -24,6 +24,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
 #include "audio.h"
 
 #include "boxes/photosendbox.h"
+#include "media/media_clip_reader.h"
 #include "mainwidget.h"
 #include "mainwindow.h"
 #include "lang.h"
@@ -330,7 +331,7 @@ void FileLoadTask::process() {
 		}
 		if (filemime == qstr("video/mp4") || filename.endsWith(qstr(".mp4"), Qt::CaseInsensitive) || animated) {
 			QImage cover;
-			MTPDocumentAttribute animatedAttribute = clipReadAnimatedAttributes(_filepath, _content, cover);
+			MTPDocumentAttribute animatedAttribute = Media::Clip::readAttributes(_filepath, _content, cover);
 			if (animatedAttribute.type() == mtpc_documentAttributeVideo) {
 				int32 cw = cover.width(), ch = cover.height();
 				if (cw < 20 * ch && ch < 20 * cw) {
diff --git a/Telegram/SourceFiles/media/media_clip_ffmpeg.cpp b/Telegram/SourceFiles/media/media_clip_ffmpeg.cpp
new file mode 100644
index 000000000..d10568f70
--- /dev/null
+++ b/Telegram/SourceFiles/media/media_clip_ffmpeg.cpp
@@ -0,0 +1,302 @@
+/*
+This file is part of Telegram Desktop,
+the official desktop version of Telegram messaging app, see https://telegram.org
+
+Telegram Desktop is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+It 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 General Public License for more details.
+
+In addition, as a special exception, the copyright holders give permission
+to link the code of portions of this program with the OpenSSL library.
+
+Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
+Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
+*/
+#include "stdafx.h"
+#include "media/media_clip_ffmpeg.h"
+
+namespace Media {
+namespace Clip {
+namespace internal {
+
+FFMpegReaderImplementation::FFMpegReaderImplementation(FileLocation *location, QByteArray *data) : ReaderImplementation(location, data) {
+	_frame = av_frame_alloc();
+	av_init_packet(&_avpkt);
+	_avpkt.data = NULL;
+	_avpkt.size = 0;
+}
+
+bool FFMpegReaderImplementation::readNextFrame() {
+	if (_frameRead) {
+		av_frame_unref(_frame);
+		_frameRead = false;
+	}
+
+	int res;
+	while (true) {
+		if (_avpkt.size > 0) { // previous packet not finished
+			res = 0;
+		} else if ((res = av_read_frame(_fmtContext, &_avpkt)) < 0) {
+			if (res != AVERROR_EOF || !_hadFrame) {
+				char err[AV_ERROR_MAX_STRING_SIZE] = { 0 };
+				LOG(("Gif Error: Unable to av_read_frame() %1, error %2, %3").arg(logData()).arg(res).arg(av_make_error_string(err, sizeof(err), res)));
+				return false;
+			}
+		}
+
+		bool finished = (res < 0);
+		if (finished) {
+			_avpkt.data = NULL;
+			_avpkt.size = 0;
+		} else {
+			rememberPacket();
+		}
+
+		int32 got_frame = 0;
+		int32 decoded = _avpkt.size;
+		if (_avpkt.stream_index == _streamId) {
+			if ((res = avcodec_decode_video2(_codecContext, _frame, &got_frame, &_avpkt)) < 0) {
+				char err[AV_ERROR_MAX_STRING_SIZE] = { 0 };
+				LOG(("Gif Error: Unable to avcodec_decode_video2() %1, error %2, %3").arg(logData()).arg(res).arg(av_make_error_string(err, sizeof(err), res)));
+
+				if (res == AVERROR_INVALIDDATA) { // try to skip bad packet
+					freePacket();
+					_avpkt.data = NULL;
+					_avpkt.size = 0;
+					continue;
+				}
+
+				if (res != AVERROR_EOF || !_hadFrame) { // try to skip end of file
+					return false;
+				}
+				freePacket();
+				_avpkt.data = NULL;
+				_avpkt.size = 0;
+				continue;
+			}
+			if (res > 0) decoded = res;
+		} else if (_audioStreamId >= 0 && _avpkt.stream_index == _audioStreamId) {
+			freePacket();
+			continue;
+		}
+		if (!finished) {
+			_avpkt.data += decoded;
+			_avpkt.size -= decoded;
+			if (_avpkt.size <= 0) freePacket();
+		}
+
+		if (got_frame) {
+			int64 duration = av_frame_get_pkt_duration(_frame);
+			int64 framePts = (_frame->pkt_pts == AV_NOPTS_VALUE) ? _frame->pkt_dts : _frame->pkt_pts;
+			int64 frameMs = (framePts * 1000LL * _fmtContext->streams[_streamId]->time_base.num) / _fmtContext->streams[_streamId]->time_base.den;
+			_currentFrameDelay = _nextFrameDelay;
+			if (_frameMs + _currentFrameDelay < frameMs) {
+				_currentFrameDelay = int32(frameMs - _frameMs);
+			}
+			if (duration == AV_NOPTS_VALUE) {
+				_nextFrameDelay = 0;
+			} else {
+				_nextFrameDelay = (duration * 1000LL * _fmtContext->streams[_streamId]->time_base.num) / _fmtContext->streams[_streamId]->time_base.den;
+			}
+			_frameMs = frameMs;
+
+			_hadFrame = _frameRead = true;
+			return true;
+		}
+
+		if (finished) {
+			if ((res = avformat_seek_file(_fmtContext, _streamId, std::numeric_limits<int64_t>::min(), 0, std::numeric_limits<int64_t>::max(), 0)) < 0) {
+				if ((res = av_seek_frame(_fmtContext, _streamId, 0, AVSEEK_FLAG_BYTE)) < 0) {
+					if ((res = av_seek_frame(_fmtContext, _streamId, 0, AVSEEK_FLAG_FRAME)) < 0) {
+						if ((res = av_seek_frame(_fmtContext, _streamId, 0, 0)) < 0) {
+							char err[AV_ERROR_MAX_STRING_SIZE] = { 0 };
+							LOG(("Gif Error: Unable to av_seek_frame() to the start %1, error %2, %3").arg(logData()).arg(res).arg(av_make_error_string(err, sizeof(err), res)));
+							return false;
+						}
+					}
+				}
+			}
+			avcodec_flush_buffers(_codecContext);
+			_hadFrame = false;
+			_frameMs = 0;
+		}
+	}
+
+	return false;
+}
+
+bool FFMpegReaderImplementation::renderFrame(QImage &to, bool &hasAlpha, const QSize &size) {
+	t_assert(_frameRead);
+	_frameRead = false;
+
+	if (!_width || !_height) {
+		_width = _frame->width;
+		_height = _frame->height;
+		if (!_width || !_height) {
+			LOG(("Gif Error: Bad frame size %1").arg(logData()));
+			return false;
+		}
+	}
+
+	QSize toSize(size.isEmpty() ? QSize(_width, _height) : size);
+	if (to.isNull() || to.size() != toSize) {
+		to = QImage(toSize, QImage::Format_ARGB32);
+	}
+	hasAlpha = (_frame->format == AV_PIX_FMT_BGRA || (_frame->format == -1 && _codecContext->pix_fmt == AV_PIX_FMT_BGRA));
+	if (_frame->width == toSize.width() && _frame->height == toSize.height() && hasAlpha) {
+		int32 sbpl = _frame->linesize[0], dbpl = to.bytesPerLine(), bpl = qMin(sbpl, dbpl);
+		uchar *s = _frame->data[0], *d = to.bits();
+		for (int32 i = 0, l = _frame->height; i < l; ++i) {
+			memcpy(d + i * dbpl, s + i * sbpl, bpl);
+		}
+	} else {
+		if ((_swsSize != toSize) || (_frame->format != -1 && _frame->format != _codecContext->pix_fmt) || !_swsContext) {
+			_swsSize = toSize;
+			_swsContext = sws_getCachedContext(_swsContext, _frame->width, _frame->height, AVPixelFormat(_frame->format), toSize.width(), toSize.height(), AV_PIX_FMT_BGRA, 0, 0, 0, 0);
+		}
+		uint8_t * toData[1] = { to.bits() };
+		int	toLinesize[1] = { to.bytesPerLine() }, res;
+		if ((res = sws_scale(_swsContext, _frame->data, _frame->linesize, 0, _frame->height, toData, toLinesize)) != _swsSize.height()) {
+			LOG(("Gif Error: Unable to sws_scale to good size %1, height %2, should be %3").arg(logData()).arg(res).arg(_swsSize.height()));
+			return false;
+		}
+	}
+
+	av_frame_unref(_frame);
+	return true;
+}
+
+int FFMpegReaderImplementation::nextFrameDelay() {
+	return _currentFrameDelay;
+}
+
+bool FFMpegReaderImplementation::start(bool onlyGifv) {
+	initDevice();
+	if (!_device->open(QIODevice::ReadOnly)) {
+		LOG(("Gif Error: Unable to open device %1").arg(logData()));
+		return false;
+	}
+	_ioBuffer = (uchar*)av_malloc(AVBlockSize);
+	_ioContext = avio_alloc_context(_ioBuffer, AVBlockSize, 0, static_cast<void*>(this), &FFMpegReaderImplementation::_read, 0, &FFMpegReaderImplementation::_seek);
+	_fmtContext = avformat_alloc_context();
+	if (!_fmtContext) {
+		LOG(("Gif Error: Unable to avformat_alloc_context %1").arg(logData()));
+		return false;
+	}
+	_fmtContext->pb = _ioContext;
+
+	int res = 0;
+	char err[AV_ERROR_MAX_STRING_SIZE] = { 0 };
+	if ((res = avformat_open_input(&_fmtContext, 0, 0, 0)) < 0) {
+		_ioBuffer = 0;
+
+		LOG(("Gif Error: Unable to avformat_open_input %1, error %2, %3").arg(logData()).arg(res).arg(av_make_error_string(err, sizeof(err), res)));
+		return false;
+	}
+	_opened = true;
+
+	if ((res = avformat_find_stream_info(_fmtContext, 0)) < 0) {
+		LOG(("Gif Error: Unable to avformat_find_stream_info %1, error %2, %3").arg(logData()).arg(res).arg(av_make_error_string(err, sizeof(err), res)));
+		return false;
+	}
+
+	_streamId = av_find_best_stream(_fmtContext, AVMEDIA_TYPE_VIDEO, -1, -1, 0, 0);
+	if (_streamId < 0) {
+		LOG(("Gif Error: Unable to av_find_best_stream %1, error %2, %3").arg(logData()).arg(_streamId).arg(av_make_error_string(err, sizeof(err), _streamId)));
+		return false;
+	}
+
+	// Get a pointer to the codec context for the audio stream
+	_codecContext = _fmtContext->streams[_streamId]->codec;
+	_codec = avcodec_find_decoder(_codecContext->codec_id);
+
+	_audioStreamId = av_find_best_stream(_fmtContext, AVMEDIA_TYPE_AUDIO, -1, -1, 0, 0);
+	if (onlyGifv) {
+		if (_audioStreamId >= 0) { // should be no audio stream
+			return false;
+		}
+		if (dataSize() > AnimationInMemory) {
+			return false;
+		}
+		if (_codecContext->codec_id != AV_CODEC_ID_H264) {
+			return false;
+		}
+	}
+	av_opt_set_int(_codecContext, "refcounted_frames", 1, 0);
+	if ((res = avcodec_open2(_codecContext, _codec, 0)) < 0) {
+		LOG(("Gif Error: Unable to avcodec_open2 %1, error %2, %3").arg(logData()).arg(res).arg(av_make_error_string(err, sizeof(err), res)));
+		return false;
+	}
+
+	return true;
+}
+
+QString FFMpegReaderImplementation::logData() const {
+	return qsl("for file '%1', data size '%2'").arg(_location ? _location->name() : QString()).arg(_data->size());
+}
+
+int FFMpegReaderImplementation::duration() const {
+	if (_fmtContext->streams[_streamId]->duration == AV_NOPTS_VALUE) return 0;
+	return (_fmtContext->streams[_streamId]->duration * _fmtContext->streams[_streamId]->time_base.num) / _fmtContext->streams[_streamId]->time_base.den;
+}
+
+FFMpegReaderImplementation::~FFMpegReaderImplementation() {
+	if (_frameRead) {
+		av_frame_unref(_frame);
+		_frameRead = false;
+	}
+	if (_ioContext) av_free(_ioContext);
+	if (_codecContext) avcodec_close(_codecContext);
+	if (_swsContext) sws_freeContext(_swsContext);
+	if (_opened) {
+		avformat_close_input(&_fmtContext);
+	} else if (_ioBuffer) {
+		av_free(_ioBuffer);
+	}
+	if (_fmtContext) avformat_free_context(_fmtContext);
+	av_frame_free(&_frame);
+	freePacket();
+}
+
+void FFMpegReaderImplementation::rememberPacket() {
+	if (!_packetWas) {
+		_packetSize = _avpkt.size;
+		_packetData = _avpkt.data;
+		_packetWas = true;
+	}
+}
+
+void FFMpegReaderImplementation::freePacket() {
+	if (_packetWas) {
+		_avpkt.size = _packetSize;
+		_avpkt.data = _packetData;
+		_packetWas = false;
+		av_packet_unref(&_avpkt);
+	}
+}
+
+int FFMpegReaderImplementation::_read(void *opaque, uint8_t *buf, int buf_size) {
+	FFMpegReaderImplementation *l = reinterpret_cast<FFMpegReaderImplementation*>(opaque);
+	return int(l->_device->read((char*)(buf), buf_size));
+}
+
+int64_t FFMpegReaderImplementation::_seek(void *opaque, int64_t offset, int whence) {
+	FFMpegReaderImplementation *l = reinterpret_cast<FFMpegReaderImplementation*>(opaque);
+
+	switch (whence) {
+	case SEEK_SET: return l->_device->seek(offset) ? l->_device->pos() : -1;
+	case SEEK_CUR: return l->_device->seek(l->_device->pos() + offset) ? l->_device->pos() : -1;
+	case SEEK_END: return l->_device->seek(l->_device->size() + offset) ? l->_device->pos() : -1;
+	}
+	return -1;
+}
+
+} // namespace internal
+} // namespace Clip
+} // namespace Media
diff --git a/Telegram/SourceFiles/media/media_clip_ffmpeg.h b/Telegram/SourceFiles/media/media_clip_ffmpeg.h
new file mode 100644
index 000000000..3850ebf49
--- /dev/null
+++ b/Telegram/SourceFiles/media/media_clip_ffmpeg.h
@@ -0,0 +1,89 @@
+/*
+This file is part of Telegram Desktop,
+the official desktop version of Telegram messaging app, see https://telegram.org
+
+Telegram Desktop is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+It 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 General Public License for more details.
+
+In addition, as a special exception, the copyright holders give permission
+to link the code of portions of this program with the OpenSSL library.
+
+Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
+Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
+*/
+#pragma once
+
+extern "C" {
+#include <libavcodec/avcodec.h>
+#include <libavformat/avformat.h>
+#include <libavutil/opt.h>
+#include <libswscale/swscale.h>
+}
+
+#include "media/media_clip_implementation.h"
+
+namespace Media {
+namespace Clip {
+namespace internal {
+
+class FFMpegReaderImplementation : public ReaderImplementation {
+public:
+
+	FFMpegReaderImplementation(FileLocation *location, QByteArray *data);
+
+	bool readNextFrame() override;
+	bool renderFrame(QImage &to, bool &hasAlpha, const QSize &size) override;
+	int nextFrameDelay() override;
+	bool start(bool onlyGifv) override;
+
+	int duration() const;
+	QString logData() const;
+
+	~FFMpegReaderImplementation();
+
+private:
+	void rememberPacket();
+	void freePacket();
+
+	static int _read(void *opaque, uint8_t *buf, int buf_size);
+	static int64_t _seek(void *opaque, int64_t offset, int whence);
+
+	uchar *_ioBuffer = nullptr;
+	AVIOContext *_ioContext = nullptr;
+	AVFormatContext *_fmtContext = nullptr;
+	AVCodec *_codec = nullptr;
+	AVCodecContext *_codecContext = nullptr;
+	int _streamId = 0;
+	AVFrame *_frame = nullptr;
+	bool _opened = false;
+	bool _hadFrame = false;
+	bool _frameRead = false;
+
+	int _audioStreamId = 0;
+
+	AVPacket _avpkt;
+	int _packetSize = 0;
+	uint8_t *_packetData = nullptr;
+	bool _packetWas = false;
+
+	int _width = 0;
+	int _height = 0;
+	SwsContext *_swsContext = nullptr;
+	QSize _swsSize;
+
+	int64 _frameMs = 0;
+	int _nextFrameDelay = 0;
+	int _currentFrameDelay = 0;
+
+};
+
+} // namespace internal
+} // namespace Clip
+} // namespace Media
diff --git a/Telegram/SourceFiles/media/media_clip_implementation.cpp b/Telegram/SourceFiles/media/media_clip_implementation.cpp
new file mode 100644
index 000000000..9526074cb
--- /dev/null
+++ b/Telegram/SourceFiles/media/media_clip_implementation.cpp
@@ -0,0 +1,43 @@
+/*
+This file is part of Telegram Desktop,
+the official desktop version of Telegram messaging app, see https://telegram.org
+
+Telegram Desktop is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+It 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 General Public License for more details.
+
+In addition, as a special exception, the copyright holders give permission
+to link the code of portions of this program with the OpenSSL library.
+
+Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
+Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
+*/
+#include "stdafx.h"
+#include "media/media_clip_implementation.h"
+
+namespace Media {
+namespace Clip {
+namespace internal {
+
+void ReaderImplementation::initDevice() {
+	if (_data->isEmpty()) {
+		if (_file.isOpen()) _file.close();
+		_file.setFileName(_location->name());
+		_dataSize = _file.size();
+	} else {
+		if (_buffer.isOpen()) _buffer.close();
+		_buffer.setBuffer(_data);
+		_dataSize = _data->size();
+	}
+	_device = _data->isEmpty() ? static_cast<QIODevice*>(&_file) : static_cast<QIODevice*>(&_buffer);
+}
+
+} // namespace internal
+} // namespace Clip
+} // namespace Media
diff --git a/Telegram/SourceFiles/media/media_clip_implementation.h b/Telegram/SourceFiles/media/media_clip_implementation.h
new file mode 100644
index 000000000..30f34c4bc
--- /dev/null
+++ b/Telegram/SourceFiles/media/media_clip_implementation.h
@@ -0,0 +1,60 @@
+/*
+This file is part of Telegram Desktop,
+the official desktop version of Telegram messaging app, see https://telegram.org
+
+Telegram Desktop is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+It 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 General Public License for more details.
+
+In addition, as a special exception, the copyright holders give permission
+to link the code of portions of this program with the OpenSSL library.
+
+Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
+Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
+*/
+#pragma once
+
+class FileLocation;
+
+namespace Media {
+namespace Clip {
+namespace internal {
+
+class ReaderImplementation {
+public:
+
+	ReaderImplementation(FileLocation *location, QByteArray *data)
+		: _location(location)
+		, _data(data) {
+	}
+	virtual bool readNextFrame() = 0;
+	virtual bool renderFrame(QImage &to, bool &hasAlpha, const QSize &size) = 0;
+	virtual int nextFrameDelay() = 0;
+	virtual bool start(bool onlyGifv) = 0;
+	virtual ~ReaderImplementation() {
+	}
+	int64 dataSize() const {
+		return _dataSize;
+	}
+
+protected:
+	FileLocation *_location;
+	QByteArray *_data;
+	QFile _file;
+	QBuffer _buffer;
+	QIODevice *_device = nullptr;
+	int64 _dataSize = 0;
+
+	void initDevice();
+
+};
+
+} // namespace internal
+} // namespace Clip
+} // namespace Media
diff --git a/Telegram/SourceFiles/media/media_clip_qtgif.cpp b/Telegram/SourceFiles/media/media_clip_qtgif.cpp
new file mode 100644
index 000000000..2ec76997c
--- /dev/null
+++ b/Telegram/SourceFiles/media/media_clip_qtgif.cpp
@@ -0,0 +1,106 @@
+/*
+This file is part of Telegram Desktop,
+the official desktop version of Telegram messaging app, see https://telegram.org
+
+Telegram Desktop is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+It 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 General Public License for more details.
+
+In addition, as a special exception, the copyright holders give permission
+to link the code of portions of this program with the OpenSSL library.
+
+Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
+Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
+*/
+#include "stdafx.h"
+#include "media/media_clip_qtgif.h"
+
+namespace Media {
+namespace Clip {
+namespace internal {
+
+QtGifReaderImplementation::QtGifReaderImplementation(FileLocation *location, QByteArray *data) : ReaderImplementation(location, data) {
+}
+
+bool QtGifReaderImplementation::readNextFrame() {
+	if (_reader) _frameDelay = _reader->nextImageDelay();
+	if (_framesLeft < 1 && !jumpToStart()) {
+		return false;
+	}
+
+	_frame = QImage(); // QGifHandler always reads first to internal QImage and returns it
+	if (!_reader->read(&_frame) || _frame.isNull()) {
+		return false;
+	}
+	--_framesLeft;
+	return true;
+}
+
+bool QtGifReaderImplementation::renderFrame(QImage &to, bool &hasAlpha, const QSize &size) {
+	t_assert(!_frame.isNull());
+	if (size.isEmpty() || size == _frame.size()) {
+		int32 w = _frame.width(), h = _frame.height();
+		if (to.width() == w && to.height() == h && to.format() == _frame.format()) {
+			if (to.byteCount() != _frame.byteCount()) {
+				int bpl = qMin(to.bytesPerLine(), _frame.bytesPerLine());
+				for (int i = 0; i < h; ++i) {
+					memcpy(to.scanLine(i), _frame.constScanLine(i), bpl);
+				}
+			} else {
+				memcpy(to.bits(), _frame.constBits(), _frame.byteCount());
+			}
+		} else {
+			to = _frame.copy();
+		}
+	} else {
+		to = _frame.scaled(size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
+	}
+	hasAlpha = _frame.hasAlphaChannel();
+	_frame = QImage();
+	return true;
+}
+
+int QtGifReaderImplementation::nextFrameDelay() {
+	return _frameDelay;
+}
+
+bool QtGifReaderImplementation::start(bool onlyGifv) {
+	if (onlyGifv) return false;
+	return jumpToStart();
+}
+
+QtGifReaderImplementation::~QtGifReaderImplementation() {
+	deleteAndMark(_reader);
+}
+
+bool QtGifReaderImplementation::jumpToStart() {
+	if (_reader && _reader->jumpToImage(0)) {
+		_framesLeft = _reader->imageCount();
+		return true;
+	}
+
+	delete _reader;
+	initDevice();
+	_reader = new QImageReader(_device);
+#if QT_VERSION >= QT_VERSION_CHECK(5, 5, 0)
+	_reader->setAutoTransform(true);
+#endif
+	if (!_reader->canRead() || !_reader->supportsAnimation()) {
+		return false;
+	}
+	_framesLeft = _reader->imageCount();
+	if (_framesLeft < 1) {
+		return false;
+	}
+	return true;
+}
+
+} // namespace internal
+} // namespace Clip
+} // namespace Media
diff --git a/Telegram/SourceFiles/media/media_clip_qtgif.h b/Telegram/SourceFiles/media/media_clip_qtgif.h
new file mode 100644
index 000000000..5e8efa063
--- /dev/null
+++ b/Telegram/SourceFiles/media/media_clip_qtgif.h
@@ -0,0 +1,53 @@
+/*
+This file is part of Telegram Desktop,
+the official desktop version of Telegram messaging app, see https://telegram.org
+
+Telegram Desktop is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+It 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 General Public License for more details.
+
+In addition, as a special exception, the copyright holders give permission
+to link the code of portions of this program with the OpenSSL library.
+
+Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
+Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
+*/
+#pragma once
+
+#include "media/media_clip_implementation.h"
+
+namespace Media {
+namespace Clip {
+namespace internal {
+
+class QtGifReaderImplementation : public ReaderImplementation {
+public:
+
+	QtGifReaderImplementation(FileLocation *location, QByteArray *data);
+
+	bool readNextFrame() override;
+	bool renderFrame(QImage &to, bool &hasAlpha, const QSize &size) override;
+	int nextFrameDelay() override;
+	bool start(bool onlyGifv) override;
+
+	~QtGifReaderImplementation();
+
+private:
+	bool jumpToStart();
+
+	QImageReader *_reader = nullptr;
+	int _framesLeft = 0;
+	int _frameDelay = 0;
+	QImage _frame;
+
+};
+
+} // namespace internal
+} // namespace Clip
+} // namespace Media
diff --git a/Telegram/SourceFiles/media/media_clip_reader.cpp b/Telegram/SourceFiles/media/media_clip_reader.cpp
new file mode 100644
index 000000000..ca96eb418
--- /dev/null
+++ b/Telegram/SourceFiles/media/media_clip_reader.cpp
@@ -0,0 +1,731 @@
+/*
+This file is part of Telegram Desktop,
+the official desktop version of Telegram messaging app, see https://telegram.org
+
+Telegram Desktop is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+It 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 General Public License for more details.
+
+In addition, as a special exception, the copyright holders give permission
+to link the code of portions of this program with the OpenSSL library.
+
+Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
+Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
+*/
+#include "stdafx.h"
+#include "media/media_clip_reader.h"
+
+extern "C" {
+#include <libavcodec/avcodec.h>
+#include <libavformat/avformat.h>
+#include <libavutil/opt.h>
+#include <libswscale/swscale.h>
+}
+
+#include "media/media_clip_ffmpeg.h"
+#include "media/media_clip_qtgif.h"
+#include "mainwidget.h"
+#include "mainwindow.h"
+
+namespace Media {
+namespace Clip {
+namespace {
+
+QVector<QThread*> threads;
+QVector<Manager*> managers;
+
+QPixmap _prepareFrame(const FrameRequest &request, const QImage &original, bool hasAlpha, QImage &cache) {
+	bool badSize = (original.width() != request.framew) || (original.height() != request.frameh);
+	bool needOuter = (request.outerw != request.framew) || (request.outerh != request.frameh);
+	if (badSize || needOuter || hasAlpha || request.rounded) {
+		int32 factor(request.factor);
+		bool newcache = (cache.width() != request.outerw || cache.height() != request.outerh);
+		if (newcache) {
+			cache = QImage(request.outerw, request.outerh, QImage::Format_ARGB32_Premultiplied);
+			cache.setDevicePixelRatio(factor);
+		}
+		{
+			Painter p(&cache);
+			if (newcache) {
+				if (request.framew < request.outerw) {
+					p.fillRect(0, 0, (request.outerw - request.framew) / (2 * factor), cache.height() / factor, st::black);
+					p.fillRect((request.outerw - request.framew) / (2 * factor) + (request.framew / factor), 0, (cache.width() / factor) - ((request.outerw - request.framew) / (2 * factor) + (request.framew / factor)), cache.height() / factor, st::black);
+				}
+				if (request.frameh < request.outerh) {
+					p.fillRect(qMax(0, (request.outerw - request.framew) / (2 * factor)), 0, qMin(cache.width(), request.framew) / factor, (request.outerh - request.frameh) / (2 * factor), st::black);
+					p.fillRect(qMax(0, (request.outerw - request.framew) / (2 * factor)), (request.outerh - request.frameh) / (2 * factor) + (request.frameh / factor), qMin(cache.width(), request.framew) / factor, (cache.height() / factor) - ((request.outerh - request.frameh) / (2 * factor) + (request.frameh / factor)), st::black);
+				}
+			}
+			if (hasAlpha) {
+				p.fillRect(qMax(0, (request.outerw - request.framew) / (2 * factor)), qMax(0, (request.outerh - request.frameh) / (2 * factor)), qMin(cache.width(), request.framew) / factor, qMin(cache.height(), request.frameh) / factor, st::white);
+			}
+			QPoint position((request.outerw - request.framew) / (2 * factor), (request.outerh - request.frameh) / (2 * factor));
+			if (badSize) {
+				p.setRenderHint(QPainter::SmoothPixmapTransform);
+				QRect to(position, QSize(request.framew / factor, request.frameh / factor));
+				QRect from(0, 0, original.width(), original.height());
+				p.drawImage(to, original, from, Qt::ColorOnly);
+			} else {
+				p.drawImage(position, original);
+			}
+		}
+		if (request.rounded) {
+			imageRound(cache);
+		}
+		return QPixmap::fromImage(cache, Qt::ColorOnly);
+	}
+	return QPixmap::fromImage(original, Qt::ColorOnly);
+}
+
+} // namespace
+
+Reader::Reader(const FileLocation &location, const QByteArray &data, Callback &&callback, Mode mode)
+: _callback(std_::move(callback))
+, _mode(mode) {
+	if (threads.size() < ClipThreadsCount) {
+		_threadIndex = threads.size();
+		threads.push_back(new QThread());
+		managers.push_back(new Manager(threads.back()));
+		threads.back()->start();
+	} else {
+		_threadIndex = int32(rand_value<uint32>() % threads.size());
+		int32 loadLevel = 0x7FFFFFFF;
+		for (int32 i = 0, l = threads.size(); i < l; ++i) {
+			int32 level = managers.at(i)->loadLevel();
+			if (level < loadLevel) {
+				_threadIndex = i;
+				loadLevel = level;
+			}
+		}
+	}
+	managers.at(_threadIndex)->append(this, location, data);
+}
+
+Reader::Frame *Reader::frameToShow(int32 *index) const { // 0 means not ready
+	int32 step = _step.loadAcquire(), i;
+	if (step == WaitingForDimensionsStep) {
+		if (index) *index = 0;
+		return 0;
+	} else if (step == WaitingForRequestStep) {
+		i = 0;
+	} else if (step == WaitingForFirstFrameStep) {
+		i = 0;
+	} else {
+		i = (step / 2) % 3;
+	}
+	if (index) *index = i;
+	return _frames + i;
+}
+
+Reader::Frame *Reader::frameToWrite(int32 *index) const { // 0 means not ready
+	int32 step = _step.loadAcquire(), i;
+	if (step == WaitingForDimensionsStep) {
+		i = 0;
+	} else if (step == WaitingForRequestStep) {
+		if (index) *index = 0;
+		return 0;
+	} else if (step == WaitingForFirstFrameStep) {
+		i = 0;
+	} else {
+		i = ((step + 2) / 2) % 3;
+	}
+	if (index) *index = i;
+	return _frames + i;
+}
+
+Reader::Frame *Reader::frameToWriteNext(bool checkNotWriting, int32 *index) const {
+	int32 step = _step.loadAcquire(), i;
+	if (step == WaitingForDimensionsStep || step == WaitingForRequestStep || (checkNotWriting && (step % 2))) {
+		if (index) *index = 0;
+		return 0;
+	}
+	i = ((step + 4) / 2) % 3;
+	if (index) *index = i;
+	return _frames + i;
+}
+
+void Reader::moveToNextShow() const {
+	int32 step = _step.loadAcquire();
+	if (step == WaitingForDimensionsStep) {
+	} else if (step == WaitingForRequestStep) {
+		_step.storeRelease(WaitingForFirstFrameStep);
+	} else if (step == WaitingForFirstFrameStep) {
+	} else if (!(step % 2)) {
+		_step.storeRelease(step + 1);
+	}
+}
+
+void Reader::moveToNextWrite() const {
+	int32 step = _step.loadAcquire();
+	if (step == WaitingForDimensionsStep) {
+		_step.storeRelease(WaitingForRequestStep);
+	} else if (step == WaitingForRequestStep) {
+	} else if (step == WaitingForFirstFrameStep) {
+		_step.storeRelease(0);
+	} else if (step % 2) {
+		_step.storeRelease((step + 1) % 6);
+	}
+}
+
+void Reader::callback(Reader *reader, int32 threadIndex, Notification notification) {
+	// check if reader is not deleted already
+	if (managers.size() > threadIndex && managers.at(threadIndex)->carries(reader)) {
+		reader->_callback.call(notification);
+	}
+}
+
+void Reader::start(int32 framew, int32 frameh, int32 outerw, int32 outerh, bool rounded) {
+	if (managers.size() <= _threadIndex) error();
+	if (_state == State::Error) return;
+
+	if (_step.loadAcquire() == WaitingForRequestStep) {
+		int factor = cIntRetinaFactor();
+		FrameRequest request;
+		request.factor = factor;
+		request.framew = framew * factor;
+		request.frameh = frameh * factor;
+		request.outerw = outerw * factor;
+		request.outerh = outerh * factor;
+		request.rounded = rounded;
+		_frames[0].request = _frames[1].request = _frames[2].request = request;
+		moveToNextShow();
+		managers.at(_threadIndex)->start(this);
+	}
+}
+
+QPixmap Reader::current(int32 framew, int32 frameh, int32 outerw, int32 outerh, uint64 ms) {
+	Frame *frame = frameToShow();
+	t_assert(frame != 0);
+
+	if (ms) {
+		frame->displayed.storeRelease(1);
+		if (_paused.loadAcquire()) {
+			_paused.storeRelease(0);
+			if (managers.size() <= _threadIndex) error();
+			if (_state != State::Error) {
+				managers.at(_threadIndex)->update(this);
+			}
+		}
+	} else {
+		frame->displayed.storeRelease(-1); // displayed, but should be paused
+	}
+
+	int32 factor(cIntRetinaFactor());
+	if (frame->pix.width() == outerw * factor && frame->pix.height() == outerh * factor) {
+		moveToNextShow();
+		return frame->pix;
+	}
+
+	frame->request.framew = framew * factor;
+	frame->request.frameh = frameh * factor;
+	frame->request.outerw = outerw * factor;
+	frame->request.outerh = outerh * factor;
+
+	QImage cacheForResize;
+	frame->original.setDevicePixelRatio(factor);
+	frame->pix = QPixmap();
+	frame->pix = _prepareFrame(frame->request, frame->original, true, cacheForResize);
+
+	Frame *other = frameToWriteNext(true);
+	if (other) other->request = frame->request;
+
+	moveToNextShow();
+
+	if (managers.size() <= _threadIndex) error();
+	if (_state != State::Error) {
+		managers.at(_threadIndex)->update(this);
+	}
+
+	return frame->pix;
+}
+
+bool Reader::ready() const {
+	if (_width && _height) return true;
+
+	Frame *frame = frameToShow();
+	if (frame) {
+		_width = frame->original.width();
+		_height = frame->original.height();
+		return true;
+	}
+	return false;
+}
+
+int32 Reader::width() const {
+	return _width;
+}
+
+int32 Reader::height() const {
+	return _height;
+}
+
+State Reader::state() const {
+	return _state;
+}
+
+void Reader::stop() {
+	if (managers.size() <= _threadIndex) error();
+	if (_state != State::Error) {
+		managers.at(_threadIndex)->stop(this);
+		_width = _height = 0;
+	}
+}
+
+void Reader::error() {
+	_state = State::Error;
+}
+
+Reader::~Reader() {
+	stop();
+}
+
+class ReaderPrivate {
+public:
+
+	ReaderPrivate(Reader *reader, const FileLocation &location, const QByteArray &data) : _interface(reader)
+		, _data(data)
+		, _location(_data.isEmpty() ? new FileLocation(location) : 0) {
+		if (_data.isEmpty() && !_location->accessEnable()) {
+			error();
+			return;
+		}
+		_accessed = true;
+	}
+
+	ProcessResult start(uint64 ms) {
+		if (!_implementation && !init()) {
+			return error();
+		}
+		if (frame() && frame()->original.isNull()) {
+			if (!_implementation->readNextFrame()) {
+				return error();
+			}
+			if (!_implementation->renderFrame(frame()->original, frame()->alpha, QSize())) {
+				return error();
+			}
+			_width = frame()->original.width();
+			_height = frame()->original.height();
+			return ProcessResult::Started;
+		}
+		return ProcessResult::Wait;
+	}
+
+	ProcessResult process(uint64 ms) { // -1 - do nothing, 0 - update, 1 - reinit
+		if (_state == State::Error) return ProcessResult::Error;
+
+		if (!_request.valid()) {
+			return start(ms);
+		}
+
+		if (!_paused && ms >= _nextFrameWhen) {
+			return ProcessResult::Repaint;
+		}
+		return ProcessResult::Wait;
+	}
+
+	ProcessResult finishProcess(uint64 ms) {
+		if (!readNextFrame()) {
+			return error();
+		}
+		if (ms >= _nextFrameWhen && !readNextFrame(true)) {
+			return error();
+		}
+		if (!renderFrame()) {
+			return error();
+		}
+		return ProcessResult::CopyFrame;
+	}
+
+	uint64 nextFrameDelay() {
+		int32 delay = _implementation->nextFrameDelay();
+		return qMax(delay, 5);
+	}
+
+	bool readNextFrame(bool keepup = false) {
+		if (!_implementation->readNextFrame()) {
+			return false;
+		}
+		_nextFrameWhen += nextFrameDelay();
+		if (keepup) {
+			_nextFrameWhen = qMax(_nextFrameWhen, getms());
+		}
+		return true;
+	}
+
+	bool renderFrame() {
+		t_assert(frame() != 0 && _request.valid());
+		if (!_implementation->renderFrame(frame()->original, frame()->alpha, QSize(_request.framew, _request.frameh))) {
+			return false;
+		}
+		frame()->original.setDevicePixelRatio(_request.factor);
+		frame()->pix = QPixmap();
+		frame()->pix = _prepareFrame(_request, frame()->original, frame()->alpha, frame()->cache);
+		frame()->when = _nextFrameWhen;
+		return true;
+	}
+
+	bool init() {
+		if (_data.isEmpty() && QFileInfo(_location->name()).size() <= AnimationInMemory) {
+			QFile f(_location->name());
+			if (f.open(QIODevice::ReadOnly)) {
+				_data = f.readAll();
+				if (f.error() != QFile::NoError) {
+					_data = QByteArray();
+				}
+			}
+		}
+
+		_implementation = new internal::FFMpegReaderImplementation(_location, &_data);
+//		_implementation = new QtGifReaderImplementation(_location, &_data);
+		return _implementation->start(false);
+	}
+
+	ProcessResult error() {
+		stop();
+		_state = State::Error;
+		return ProcessResult::Error;
+	}
+
+	void stop() {
+		delete _implementation;
+		_implementation = 0;
+
+		if (_location) {
+			if (_accessed) {
+				_location->accessDisable();
+			}
+			delete _location;
+			_location = 0;
+		}
+		_accessed = false;
+	}
+
+	~ReaderPrivate() {
+		stop();
+		deleteAndMark(_location);
+		deleteAndMark(_implementation);
+		_data.clear();
+	}
+
+private:
+
+	Reader *_interface;
+	State _state = State::Reading;
+
+	QByteArray _data;
+	FileLocation *_location;
+	bool _accessed = false;
+
+	QBuffer _buffer;
+	internal::ReaderImplementation *_implementation = nullptr;
+
+	FrameRequest _request;
+	struct Frame {
+		QPixmap pix;
+		QImage original, cache;
+		bool alpha = true;
+		uint64 when = 0;
+	};
+	Frame _frames[3];
+	int _frame = 0;
+	Frame *frame() {
+		return _frames + _frame;
+	}
+
+	int _width = 0;
+	int _height = 0;
+
+	uint64 _nextFrameWhen = 0;
+
+	bool _paused = false;
+
+	friend class Manager;
+
+};
+
+Manager::Manager(QThread *thread) : _processingInThread(0), _needReProcess(false) {
+	moveToThread(thread);
+	connect(thread, SIGNAL(started()), this, SLOT(process()));
+	connect(thread, SIGNAL(finished()), this, SLOT(finish()));
+	connect(this, SIGNAL(processDelayed()), this, SLOT(process()), Qt::QueuedConnection);
+
+	_timer.setSingleShot(true);
+	_timer.moveToThread(thread);
+	connect(&_timer, SIGNAL(timeout()), this, SLOT(process()));
+
+	anim::registerClipManager(this);
+}
+
+void Manager::append(Reader *reader, const FileLocation &location, const QByteArray &data) {
+	reader->_private = new ReaderPrivate(reader, location, data);
+	_loadLevel.fetchAndAddRelaxed(AverageGifSize);
+	update(reader);
+}
+
+void Manager::start(Reader *reader) {
+	update(reader);
+}
+
+void Manager::update(Reader *reader) {
+	QReadLocker lock(&_readerPointersMutex);
+	ReaderPointers::const_iterator i = _readerPointers.constFind(reader);
+	if (i == _readerPointers.cend()) {
+		lock.unlock();
+
+		QWriteLocker lock(&_readerPointersMutex);
+		_readerPointers.insert(reader, MutableAtomicInt(1));
+	} else {
+		i->v.storeRelease(1);
+	}
+	emit processDelayed();
+}
+
+void Manager::stop(Reader *reader) {
+	if (!carries(reader)) return;
+
+	QWriteLocker lock(&_readerPointersMutex);
+	_readerPointers.remove(reader);
+	emit processDelayed();
+}
+
+bool Manager::carries(Reader *reader) const {
+	QReadLocker lock(&_readerPointersMutex);
+	return _readerPointers.contains(reader);
+}
+
+Manager::ReaderPointers::iterator Manager::unsafeFindReaderPointer(ReaderPrivate *reader) {
+	ReaderPointers::iterator it = _readerPointers.find(reader->_interface);
+
+	// could be a new reader which was realloced in the same address
+	return (it == _readerPointers.cend() || it.key()->_private == reader) ? it : _readerPointers.end();
+}
+
+Manager::ReaderPointers::const_iterator Manager::constUnsafeFindReaderPointer(ReaderPrivate *reader) const {
+	ReaderPointers::const_iterator it = _readerPointers.constFind(reader->_interface);
+
+	// could be a new reader which was realloced in the same address
+	return (it == _readerPointers.cend() || it.key()->_private == reader) ? it : _readerPointers.cend();
+}
+
+bool Manager::handleProcessResult(ReaderPrivate *reader, ProcessResult result, uint64 ms) {
+	QReadLocker lock(&_readerPointersMutex);
+	ReaderPointers::const_iterator it = constUnsafeFindReaderPointer(reader);
+	if (result == ProcessResult::Error) {
+		if (it != _readerPointers.cend()) {
+			it.key()->error();
+			emit callback(it.key(), it.key()->threadIndex(), NotificationReinit);
+
+			lock.unlock();
+			QWriteLocker lock(&_readerPointersMutex);
+			ReaderPointers::iterator i = unsafeFindReaderPointer(reader);
+			if (i != _readerPointers.cend()) _readerPointers.erase(i);
+		}
+		return false;
+	}
+	if (it == _readerPointers.cend()) {
+		return false;
+	}
+
+	if (result == ProcessResult::Started) {
+		_loadLevel.fetchAndAddRelaxed(reader->_width * reader->_height - AverageGifSize);
+	}
+	if (!reader->_paused && result == ProcessResult::Repaint) {
+		int32 ishowing, iprevious;
+		Reader::Frame *showing = it.key()->frameToShow(&ishowing), *previous = it.key()->frameToWriteNext(false, &iprevious);
+		t_assert(previous != 0 && showing != 0 && ishowing >= 0 && iprevious >= 0);
+		if (reader->_frames[ishowing].when > 0 && showing->displayed.loadAcquire() <= 0) { // current frame was not shown
+			if (reader->_frames[ishowing].when + WaitBeforeGifPause < ms || (reader->_frames[iprevious].when && previous->displayed.loadAcquire() <= 0)) {
+				reader->_paused = true;
+				it.key()->_paused.storeRelease(1);
+				result = ProcessResult::Paused;
+			}
+		}
+	}
+	if (result == ProcessResult::Started || result == ProcessResult::CopyFrame) {
+		t_assert(reader->_frame >= 0);
+		Reader::Frame *frame = it.key()->_frames + reader->_frame;
+		frame->clear();
+		frame->pix = reader->frame()->pix;
+		frame->original = reader->frame()->original;
+		frame->displayed.storeRelease(0);
+		if (result == ProcessResult::Started) {
+			reader->_nextFrameWhen = ms;
+			it.key()->moveToNextWrite();
+			emit callback(it.key(), it.key()->threadIndex(), NotificationReinit);
+		}
+	} else if (result == ProcessResult::Paused) {
+		it.key()->moveToNextWrite();
+		emit callback(it.key(), it.key()->threadIndex(), NotificationReinit);
+	} else if (result == ProcessResult::Repaint) {
+		it.key()->moveToNextWrite();
+		emit callback(it.key(), it.key()->threadIndex(), NotificationRepaint);
+	}
+	return true;
+}
+
+Manager::ResultHandleState Manager::handleResult(ReaderPrivate *reader, ProcessResult result, uint64 ms) {
+	if (!handleProcessResult(reader, result, ms)) {
+		_loadLevel.fetchAndAddRelaxed(-1 * (reader->_width > 0 ? reader->_width * reader->_height : AverageGifSize));
+		delete reader;
+		return ResultHandleRemove;
+	}
+
+	_processingInThread->eventDispatcher()->processEvents(QEventLoop::AllEvents);
+	if (_processingInThread->isInterruptionRequested()) {
+		return ResultHandleStop;
+	}
+
+	if (result == ProcessResult::Repaint) {
+		{
+			QReadLocker lock(&_readerPointersMutex);
+			ReaderPointers::const_iterator it = constUnsafeFindReaderPointer(reader);
+			if (it != _readerPointers.cend()) {
+				int32 index = 0;
+				Reader *r = it.key();
+				Reader::Frame *frame = it.key()->frameToWrite(&index);
+				if (frame) {
+					frame->clear();
+				} else {
+					t_assert(!reader->_request.valid());
+				}
+				reader->_frame = index;
+			}
+		}
+		return handleResult(reader, reader->finishProcess(ms), ms);
+	}
+
+	return ResultHandleContinue;
+}
+
+void Manager::process() {
+	if (_processingInThread) {
+		_needReProcess = true;
+		return;
+	}
+
+	_timer.stop();
+	_processingInThread = thread();
+
+	uint64 ms = getms(), minms = ms + 86400 * 1000ULL;
+	{
+		QReadLocker lock(&_readerPointersMutex);
+		for (ReaderPointers::iterator it = _readerPointers.begin(), e = _readerPointers.end(); it != e; ++it) {
+			if (it->v.loadAcquire()) {
+				Readers::iterator i = _readers.find(it.key()->_private);
+				if (i == _readers.cend()) {
+					_readers.insert(it.key()->_private, 0);
+				} else {
+					i.value() = ms;
+					if (i.key()->_paused && !it.key()->_paused.loadAcquire()) {
+						i.key()->_paused = false;
+					}
+				}
+				Reader::Frame *frame = it.key()->frameToWrite();
+				if (frame) it.key()->_private->_request = frame->request;
+				it->v.storeRelease(0);
+			}
+		}
+	}
+
+	for (Readers::iterator i = _readers.begin(), e = _readers.end(); i != e;) {
+		ReaderPrivate *reader = i.key();
+		if (i.value() <= ms) {
+			ResultHandleState state = handleResult(reader, reader->process(ms), ms);
+			if (state == ResultHandleRemove) {
+				i = _readers.erase(i);
+				continue;
+			} else if (state == ResultHandleStop) {
+				_processingInThread = 0;
+				return;
+			}
+			ms = getms();
+			i.value() = reader->_nextFrameWhen ? reader->_nextFrameWhen : (ms + 86400 * 1000ULL);
+		}
+		if (!reader->_paused && i.value() < minms) {
+			minms = i.value();
+		}
+		++i;
+	}
+
+	ms = getms();
+	if (_needReProcess || minms <= ms) {
+		_needReProcess = false;
+		_timer.start(1);
+	} else {
+		_timer.start(minms - ms);
+	}
+
+	_processingInThread = 0;
+}
+
+void Manager::finish() {
+	_timer.stop();
+	clear();
+}
+
+void Manager::clear() {
+	{
+		QWriteLocker lock(&_readerPointersMutex);
+		for (ReaderPointers::iterator it = _readerPointers.begin(), e = _readerPointers.end(); it != e; ++it) {
+			it.key()->_private = 0;
+		}
+		_readerPointers.clear();
+	}
+
+	for (Readers::iterator i = _readers.begin(), e = _readers.end(); i != e; ++i) {
+		delete i.key();
+	}
+	_readers.clear();
+}
+
+Manager::~Manager() {
+	clear();
+}
+
+MTPDocumentAttribute readAttributes(const QString &fname, const QByteArray &data, QImage &cover) {
+	FileLocation localloc(StorageFilePartial, fname);
+	QByteArray localdata(data);
+
+	auto reader = std_::make_unique<internal::FFMpegReaderImplementation>(&localloc, &localdata);
+	if (reader->start(true)) {
+		bool hasAlpha = false;
+		if (reader->readNextFrame() && reader->renderFrame(cover, hasAlpha, QSize())) {
+			if (cover.width() > 0 && cover.height() > 0 && cover.width() < cover.height() * 10 && cover.height() < cover.width() * 10) {
+				if (hasAlpha) {
+					QImage cacheForResize;
+					FrameRequest request;
+					request.framew = request.outerw = cover.width();
+					request.frameh = request.outerh = cover.height();
+					request.factor = 1;
+					cover = _prepareFrame(request, cover, hasAlpha, cacheForResize).toImage();
+				}
+				int duration = reader->duration();
+				return MTP_documentAttributeVideo(MTP_int(duration), MTP_int(cover.width()), MTP_int(cover.height()));
+			}
+		}
+	}
+	return MTP_documentAttributeFilename(MTP_string(fname));
+}
+
+void Finish() {
+	if (!threads.isEmpty()) {
+		for (int32 i = 0, l = threads.size(); i < l; ++i) {
+			threads.at(i)->quit();
+			DEBUG_LOG(("Waiting for clipThread to finish: %1").arg(i));
+			threads.at(i)->wait();
+			delete managers.at(i);
+			delete threads.at(i);
+		}
+		threads.clear();
+		managers.clear();
+	}
+}
+
+} // namespace Clip
+} // namespace Media
diff --git a/Telegram/SourceFiles/media/media_clip_reader.h b/Telegram/SourceFiles/media/media_clip_reader.h
new file mode 100644
index 000000000..a0b160e70
--- /dev/null
+++ b/Telegram/SourceFiles/media/media_clip_reader.h
@@ -0,0 +1,222 @@
+/*
+This file is part of Telegram Desktop,
+the official desktop version of Telegram messaging app, see https://telegram.org
+
+Telegram Desktop is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+It 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 General Public License for more details.
+
+In addition, as a special exception, the copyright holders give permission
+to link the code of portions of this program with the OpenSSL library.
+
+Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
+Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
+*/
+#pragma once
+
+class FileLocation;
+
+namespace Media {
+namespace Clip {
+
+enum class State {
+	Reading,
+	Error,
+};
+
+struct FrameRequest {
+	bool valid() const {
+		return factor > 0;
+	}
+	int factor = 0;
+	int framew = 0;
+	int frameh = 0;
+	int outerw = 0;
+	int outerh = 0;
+	bool rounded = false;
+};
+
+enum ReaderSteps {
+	WaitingForDimensionsStep = -3, // before ReaderPrivate read the first image and got the original frame size
+	WaitingForRequestStep = -2, // before Reader got the original frame size and prepared the frame request
+	WaitingForFirstFrameStep = -1, // before ReaderPrivate got the frame request and started waiting for the 1-2 delay
+};
+
+class ReaderPrivate;
+class Reader {
+public:
+
+	using Callback = Function<void, Notification>;
+	enum class Mode {
+		Gif,
+		Video,
+	};
+
+	Reader(const FileLocation &location, const QByteArray &data, Callback &&callback, Mode mode = Mode::Gif);
+	static void callback(Reader *reader, int threadIndex, Notification notification); // reader can be deleted
+
+	void setAutoplay() {
+		_autoplay = true;
+	}
+	bool autoplay() const {
+		return _autoplay;
+	}
+
+	void start(int framew, int frameh, int outerw, int outerh, bool rounded);
+	QPixmap current(int framew, int frameh, int outerw, int outerh, uint64 ms);
+	QPixmap frameOriginal() const {
+		Frame *frame = frameToShow();
+		if (!frame) return QPixmap();
+		QPixmap result(frame ? QPixmap::fromImage(frame->original) : QPixmap());
+		result.detach();
+		return result;
+	}
+	bool currentDisplayed() const {
+		Frame *frame = frameToShow();
+		return frame ? (frame->displayed.loadAcquire() != 0) : true;
+	}
+	bool paused() const {
+		return _paused.loadAcquire();
+	}
+	int threadIndex() const {
+		return _threadIndex;
+	}
+
+	int width() const;
+	int height() const;
+
+	State state() const;
+	bool started() const {
+		int step = _step.loadAcquire();
+		return (step == WaitingForFirstFrameStep) || (step >= 0);
+	}
+	bool ready() const;
+
+	void stop();
+	void error();
+
+	~Reader();
+
+private:
+
+	Callback _callback;
+	Mode _mode;
+
+	State _state = State::Reading;
+
+	mutable int _width = 0;
+	mutable int _height = 0;
+
+	// -2, -1 - init, 0-5 - work, show ((state + 1) / 2) % 3 state, write ((state + 3) / 2) % 3
+	mutable QAtomicInt _step = WaitingForDimensionsStep;
+	struct Frame {
+		Frame() : displayed(false) {
+		}
+		void clear() {
+			pix = QPixmap();
+			original = QImage();
+		}
+		QPixmap pix;
+		QImage original;
+		FrameRequest request;
+		QAtomicInt displayed;
+	};
+	mutable Frame _frames[3];
+	Frame *frameToShow(int *index = 0) const; // 0 means not ready
+	Frame *frameToWrite(int *index = 0) const; // 0 means not ready
+	Frame *frameToWriteNext(bool check, int *index = 0) const;
+	void moveToNextShow() const;
+	void moveToNextWrite() const;
+
+	QAtomicInt _paused = 0;
+	int32 _threadIndex;
+
+	bool _autoplay = false;
+
+	friend class Manager;
+
+	ReaderPrivate *_private = nullptr;
+
+};
+
+enum class ProcessResult {
+	Error,
+	Started,
+	Paused,
+	Repaint,
+	CopyFrame,
+	Wait,
+};
+
+class Manager : public QObject {
+	Q_OBJECT
+
+public:
+
+	Manager(QThread *thread);
+	int32 loadLevel() const {
+		return _loadLevel.load();
+	}
+	void append(Reader *reader, const FileLocation &location, const QByteArray &data);
+	void start(Reader *reader);
+	void update(Reader *reader);
+	void stop(Reader *reader);
+	bool carries(Reader *reader) const;
+	~Manager();
+
+signals:
+	void processDelayed();
+
+	void callback(Media::Clip::Reader *reader, qint32 threadIndex, qint32 notification);
+
+public slots:
+	void process();
+	void finish();
+
+private:
+
+	void clear();
+
+	QAtomicInt _loadLevel;
+	struct MutableAtomicInt {
+		MutableAtomicInt(int value) : v(value) {
+		}
+		mutable QAtomicInt v;
+	};
+	typedef QMap<Reader*, MutableAtomicInt> ReaderPointers;
+	ReaderPointers _readerPointers;
+	mutable QReadWriteLock _readerPointersMutex;
+
+	ReaderPointers::const_iterator constUnsafeFindReaderPointer(ReaderPrivate *reader) const;
+	ReaderPointers::iterator unsafeFindReaderPointer(ReaderPrivate *reader);
+
+	bool handleProcessResult(ReaderPrivate *reader, ProcessResult result, uint64 ms);
+
+	enum ResultHandleState {
+		ResultHandleRemove,
+		ResultHandleStop,
+		ResultHandleContinue,
+	};
+	ResultHandleState handleResult(ReaderPrivate *reader, ProcessResult result, uint64 ms);
+
+	typedef QMap<ReaderPrivate*, uint64> Readers;
+	Readers _readers;
+
+	QTimer _timer;
+	QThread *_processingInThread;
+	bool _needReProcess;
+
+};
+
+MTPDocumentAttribute readAttributes(const QString &fname, const QByteArray &data, QImage &cover);
+
+void Finish();
+
+} // namespace Clip
+} // namespace Media
diff --git a/Telegram/SourceFiles/mediaview.cpp b/Telegram/SourceFiles/mediaview.cpp
index 9f3d29fab..dae547c5f 100644
--- a/Telegram/SourceFiles/mediaview.cpp
+++ b/Telegram/SourceFiles/mediaview.cpp
@@ -26,52 +26,55 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
 #include "mainwindow.h"
 #include "application.h"
 #include "ui/filedialog.h"
+#include "media/media_clip_reader.h"
 
 namespace {
-	class SaveMsgClickHandler : public ClickHandler {
-	public:
 
-		SaveMsgClickHandler(MediaView *view) : _view(view) {
-		}
+class SaveMsgClickHandler : public ClickHandler {
+public:
 
-		void onClick(Qt::MouseButton button) const {
-			if (button == Qt::LeftButton) {
-				_view->showSaveMsgFile();
-			}
-		}
-
-	private:
-
-		MediaView *_view;
-	};
-
-	TextParseOptions _captionTextOptions = {
-		TextParseLinks | TextParseMentions | TextParseHashtags | TextParseMultiline | TextParseRichText, // flags
-		0, // maxw
-		0, // maxh
-		Qt::LayoutDirectionAuto, // dir
-	};
-	TextParseOptions _captionBotOptions = {
-		TextParseLinks | TextParseMentions | TextParseHashtags | TextParseMultiline | TextParseRichText | TextParseBotCommands, // flags
-		0, // maxw
-		0, // maxh
-		Qt::LayoutDirectionAuto, // dir
-	};
-
-	bool typeHasMediaOverview(MediaOverviewType type) {
-		switch (type) {
-		case OverviewPhotos:
-		case OverviewVideos:
-		case OverviewMusicFiles:
-		case OverviewFiles:
-		case OverviewVoiceFiles:
-		case OverviewLinks: return true;
-		default: break;
-		}
-		return false;
+	SaveMsgClickHandler(MediaView *view) : _view(view) {
 	}
+
+	void onClick(Qt::MouseButton button) const {
+		if (button == Qt::LeftButton) {
+			_view->showSaveMsgFile();
+		}
+	}
+
+private:
+
+	MediaView *_view;
+};
+
+TextParseOptions _captionTextOptions = {
+	TextParseLinks | TextParseMentions | TextParseHashtags | TextParseMultiline | TextParseRichText, // flags
+	0, // maxw
+	0, // maxh
+	Qt::LayoutDirectionAuto, // dir
+};
+TextParseOptions _captionBotOptions = {
+	TextParseLinks | TextParseMentions | TextParseHashtags | TextParseMultiline | TextParseRichText | TextParseBotCommands, // flags
+	0, // maxw
+	0, // maxh
+	Qt::LayoutDirectionAuto, // dir
+};
+
+bool typeHasMediaOverview(MediaOverviewType type) {
+	switch (type) {
+	case OverviewPhotos:
+	case OverviewVideos:
+	case OverviewMusicFiles:
+	case OverviewFiles:
+	case OverviewVoiceFiles:
+	case OverviewLinks: return true;
+	default: break;
+	}
+	return false;
 }
 
+} // namespace
+
 MediaView::MediaView() : TWidget(App::wnd())
 , _animStarted(getms())
 , _docDownload(this, lang(lng_media_download), st::mvDocLink)
@@ -219,7 +222,7 @@ bool MediaView::gifShown() const {
 			_gif->start(_gif->width(), _gif->height(), _gif->width(), _gif->height(), false);
 			const_cast<MediaView*>(this)->_current = QPixmap();
 		}
-		return _gif->state() != ClipError;
+		return _gif->state() != Media::Clip::State::Error;
 	}
 	return false;
 }
@@ -479,13 +482,13 @@ void MediaView::step_radial(uint64 ms, bool timer) {
 	if (timer && _radial.animating()) {
 		update(radialRect());
 	}
-	if (_doc && _doc->loaded() && _doc->size < MediaViewImageSizeLimit && (!_radial.animating() || _doc->isAnimation())) {
-		if (!_doc->data().isEmpty() && _doc->isAnimation()) {
+	if (_doc && _doc->loaded() && _doc->size < MediaViewImageSizeLimit && (!_radial.animating() || _doc->isAnimation() || _doc->isVideo())) {
+		if (!_doc->data().isEmpty() && (_doc->isAnimation() || _doc->isVideo())) {
 			displayDocument(_doc, App::histItemById(_msgmigrated ? 0 : _channel, _msgid));
 		} else {
 			const FileLocation &location(_doc->location(true));
 			if (location.accessEnable()) {
-				if (_doc->isAnimation() || QImageReader(location.name()).canRead()) {
+				if (_doc->isAnimation() || _doc->isVideo() || QImageReader(location.name()).canRead()) {
 					displayDocument(_doc, App::histItemById(_msgmigrated ? 0 : _channel, _msgid));
 				}
 				location.accessDisable();
@@ -654,13 +657,15 @@ void MediaView::onDocClick() {
 	}
 }
 
-void MediaView::clipCallback(ClipReaderNotification notification) {
+void MediaView::clipCallback(Media::Clip::Notification notification) {
+	using namespace Media::Clip;
+
 	if (!_gif) return;
 
 	switch (notification) {
-	case ClipReaderReinit: {
+	case NotificationReinit: {
 		if (HistoryItem *item = App::histItemById(_msgmigrated ? 0 : _channel, _msgid)) {
-			if (_gif->state() == ClipError) {
+			if (_gif->state() == State::Error) {
 				_current = QPixmap();
 			}
 			displayDocument(_doc, item);
@@ -669,7 +674,7 @@ void MediaView::clipCallback(ClipReaderNotification notification) {
 		}
 	} break;
 
-	case ClipReaderRepaint: {
+	case NotificationRepaint: {
 		if (!_gif->currentDisplayed()) {
 			update(_x, _y, _w, _h);
 		}
@@ -1027,7 +1032,7 @@ void MediaView::displayPhoto(PhotoData *photo, HistoryItem *item) {
 }
 
 void MediaView::displayDocument(DocumentData *doc, HistoryItem *item) { // empty messages shown as docs: doc can be NULL
-	if (!doc || !doc->isAnimation() || doc != _doc || (item && (item->id != _msgid || (item->history() != (_msgmigrated ? _migrated : _history))))) {
+	if (!doc || (!doc->isAnimation() && !doc->isVideo()) || doc != _doc || (item && (item->id != _msgid || (item->history() != (_msgmigrated ? _migrated : _history))))) {
 		stopGif();
 	}
 	_doc = doc;
@@ -1049,20 +1054,20 @@ void MediaView::displayDocument(DocumentData *doc, HistoryItem *item) { // empty
 			_doc->automaticLoad(item);
 
 			const FileLocation &location(_doc->location(true));
-			if (!_doc->data().isEmpty() && _doc->isAnimation()) {
+			if (!_doc->data().isEmpty() && (_doc->isAnimation() || _doc->isVideo())) {
 				if (!_gif) {
 					if (_doc->dimensions.width() && _doc->dimensions.height()) {
 						_current = _doc->thumb->pixNoCache(_doc->dimensions.width(), _doc->dimensions.height(), ImagePixSmooth | ImagePixBlurred, _doc->dimensions.width(), _doc->dimensions.height());
 					}
-					_gif = new ClipReader(location, _doc->data(), func(this, &MediaView::clipCallback));
+					_gif = new Media::Clip::Reader(location, _doc->data(), func(this, &MediaView::clipCallback));
 				}
 			} else if (location.accessEnable()) {
-				if (_doc->isAnimation()) {
+				if (_doc->isAnimation() || _doc->isVideo()) {
 					if (!_gif) {
 						if (_doc->dimensions.width() && _doc->dimensions.height()) {
 							_current = _doc->thumb->pixNoCache(_doc->dimensions.width(), _doc->dimensions.height(), ImagePixSmooth | ImagePixBlurred, _doc->dimensions.width(), _doc->dimensions.height());
 						}
-						_gif = new ClipReader(location, _doc->data(), func(this, &MediaView::clipCallback));
+						_gif = new Media::Clip::Reader(location, _doc->data(), func(this, &MediaView::clipCallback));
 					}
 				} else {
 					if (QImageReader(location.name()).canRead()) {
diff --git a/Telegram/SourceFiles/mediaview.h b/Telegram/SourceFiles/mediaview.h
index ae07799ac..904e4bd41 100644
--- a/Telegram/SourceFiles/mediaview.h
+++ b/Telegram/SourceFiles/mediaview.h
@@ -71,7 +71,7 @@ public:
 	void activateControls();
 	void onDocClick();
 
-	void clipCallback(ClipReaderNotification notification);
+	void clipCallback(Media::Clip::Notification notification);
 	PeerData *ui_getPeerForMouseAction();
 
 	~MediaView();
@@ -169,7 +169,7 @@ private:
 	bool _pressed = false;
 	int32 _dragging = 0;
 	QPixmap _current;
-	ClipReader *_gif = nullptr;
+	Media::Clip::Reader *_gif = nullptr;
 	int32 _full = -1; // -1 - thumb, 0 - medium, 1 - full
 
 	bool fileShown() const;
diff --git a/Telegram/SourceFiles/structs.cpp b/Telegram/SourceFiles/structs.cpp
index 4b3e006d7..b5c7eb34f 100644
--- a/Telegram/SourceFiles/structs.cpp
+++ b/Telegram/SourceFiles/structs.cpp
@@ -947,9 +947,10 @@ void DocumentOpenClickHandler::doOpen(DocumentData *data, ActionOnLoad action) {
 	}
 	bool playVoice = data->voice() && audioPlayer();
 	bool playMusic = data->song() && audioPlayer();
+	bool playVideo = data->isVideo() && audioPlayer();
 	bool playAnimation = data->isAnimation() && item && item->getMedia();
 	const FileLocation &location(data->location(true));
-	if (!location.isEmpty() || (!data->data().isEmpty() && (playVoice || playMusic || playAnimation))) {
+	if (!location.isEmpty() || (!data->data().isEmpty() && (playVoice || playMusic || playVideo || playAnimation))) {
 		if (playVoice) {
 			AudioMsgId playing;
 			AudioPlayerState playingState = AudioPlayerStopped;
@@ -975,6 +976,16 @@ void DocumentOpenClickHandler::doOpen(DocumentData *data, ActionOnLoad action) {
 				audioPlayer()->play(song);
 				if (App::main()) App::main()->documentPlayProgress(song);
 			}
+		} else if (playVideo) {
+			if (!data->data().isEmpty()) {
+				App::wnd()->showDocument(data, item);
+			} else if (location.accessEnable()) {
+				App::wnd()->showDocument(data, item);
+				location.accessDisable();
+			} else {
+				psOpenFile(location.name());
+			}
+			if (App::main()) App::main()->mediaMarkRead(data);
 		} else if (data->voice() || data->isVideo()) {
 			psOpenFile(location.name());
 			if (App::main()) App::main()->mediaMarkRead(data);
diff --git a/Telegram/SourceFiles/ui/animation.cpp b/Telegram/SourceFiles/ui/animation.cpp
index 03b194fe9..8fcc98cce 100644
--- a/Telegram/SourceFiles/ui/animation.cpp
+++ b/Telegram/SourceFiles/ui/animation.cpp
@@ -19,99 +19,85 @@ Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
 Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
 */
 #include "stdafx.h"
-
 #include "animation.h"
 
-extern "C" {
-#include <libavcodec/avcodec.h>
-#include <libavformat/avformat.h>
-#include <libavutil/opt.h>
-#include <libswscale/swscale.h>
-}
-
-#include "mainwidget.h"
-#include "mainwindow.h"
+#include "media/media_clip_reader.h"
 
 namespace {
-	AnimationManager *_manager = 0;
-	QVector<QThread*> _clipThreads;
-	QVector<ClipReadManager*> _clipManagers;
-};
+
+AnimationManager *_manager = nullptr;
+
+} // namespace
 
 namespace anim {
 
-    float64 linear(const float64 &delta, const float64 &dt) {
-		return delta * dt;
-	}
+float64 linear(const float64 &delta, const float64 &dt) {
+	return delta * dt;
+}
 
-	float64 sineInOut(const float64 &delta, const float64 &dt) {
-		return -(delta / 2) * (cos(M_PI * dt) - 1);
-	}
+float64 sineInOut(const float64 &delta, const float64 &dt) {
+	return -(delta / 2) * (cos(M_PI * dt) - 1);
+}
 
-    float64 halfSine(const float64 &delta, const float64 &dt) {
-		return delta * sin(M_PI * dt / 2);
-	}
+float64 halfSine(const float64 &delta, const float64 &dt) {
+	return delta * sin(M_PI * dt / 2);
+}
 
-    float64 easeOutBack(const float64 &delta, const float64 &dt) {
-		static const float64 s = 1.70158;
+float64 easeOutBack(const float64 &delta, const float64 &dt) {
+	static const float64 s = 1.70158;
 
-		const float64 t = dt - 1;
-		return delta * (t * t * ((s + 1) * t + s) + 1);
-	}
+	const float64 t = dt - 1;
+	return delta * (t * t * ((s + 1) * t + s) + 1);
+}
 
-    float64 easeInCirc(const float64 &delta, const float64 &dt) {
-		return -delta * (sqrt(1 - dt * dt) - 1);
-	}
+float64 easeInCirc(const float64 &delta, const float64 &dt) {
+	return -delta * (sqrt(1 - dt * dt) - 1);
+}
 
-    float64 easeOutCirc(const float64 &delta, const float64 &dt) {
-		const float64 t = dt - 1;
-		return delta * sqrt(1 - t * t);
-	}
+float64 easeOutCirc(const float64 &delta, const float64 &dt) {
+	const float64 t = dt - 1;
+	return delta * sqrt(1 - t * t);
+}
 
-    float64 easeInCubic(const float64 &delta, const float64 &dt) {
-		return delta * dt * dt * dt;
-	}
+float64 easeInCubic(const float64 &delta, const float64 &dt) {
+	return delta * dt * dt * dt;
+}
 
-	float64 easeOutCubic(const float64 &delta, const float64 &dt) {
-		const float64 t = dt - 1;
-		return delta * (t * t * t + 1);
-	}
+float64 easeOutCubic(const float64 &delta, const float64 &dt) {
+	const float64 t = dt - 1;
+	return delta * (t * t * t + 1);
+}
 
-    float64 easeInQuint(const float64 &delta, const float64 &dt) {
-		const float64 t2 = dt * dt;
-		return delta * t2 * t2 * dt;
-	}
+float64 easeInQuint(const float64 &delta, const float64 &dt) {
+	const float64 t2 = dt * dt;
+	return delta * t2 * t2 * dt;
+}
 
-    float64 easeOutQuint(const float64 &delta, const float64 &dt) {
-		const float64 t = dt - 1, t2 = t * t;
-		return delta * (t2 * t2 * t + 1);
-	}
+float64 easeOutQuint(const float64 &delta, const float64 &dt) {
+	const float64 t = dt - 1, t2 = t * t;
+	return delta * (t2 * t2 * t + 1);
+}
 
-	void startManager() {
-		stopManager();
+void startManager() {
+	stopManager();
 
-		_manager = new AnimationManager();
-
-	}
-
-	void stopManager() {
-		delete _manager;
-		_manager = 0;
-		if (!_clipThreads.isEmpty()) {
-			for (int32 i = 0, l = _clipThreads.size(); i < l; ++i) {
-				_clipThreads.at(i)->quit();
-				DEBUG_LOG(("Waiting for clipThread to finish: %1").arg(i));
-				_clipThreads.at(i)->wait();
-				delete _clipManagers.at(i);
-				delete _clipThreads.at(i);
-			}
-			_clipThreads.clear();
-			_clipManagers.clear();
-		}
-	}
+	_manager = new AnimationManager();
 
 }
 
+void stopManager() {
+	delete _manager;
+	_manager = nullptr;
+
+	Media::Clip::Finish();
+}
+
+void registerClipManager(Media::Clip::Manager *manager) {
+	manager->connect(manager, SIGNAL(callback(Media::Clip::Reader*,qint32,qint32)), _manager, SLOT(clipCallback(Media::Clip::Reader*,qint32,qint32)));
+}
+
+} // anim
+
 void Animation::start() {
 	if (!_manager) return;
 
@@ -190,1137 +176,6 @@ void AnimationManager::timeout() {
 	}
 }
 
-void AnimationManager::clipCallback(ClipReader *reader, qint32 threadIndex, qint32 notification) {
-	ClipReader::callback(reader, threadIndex, ClipReaderNotification(notification));
-}
-
-QPixmap _prepareFrame(const ClipFrameRequest &request, const QImage &original, bool hasAlpha, QImage &cache) {
-	bool badSize = (original.width() != request.framew) || (original.height() != request.frameh);
-	bool needOuter = (request.outerw != request.framew) || (request.outerh != request.frameh);
-	if (badSize || needOuter || hasAlpha || request.rounded) {
-		int32 factor(request.factor);
-		bool newcache = (cache.width() != request.outerw || cache.height() != request.outerh);
-		if (newcache) {
-			cache = QImage(request.outerw, request.outerh, QImage::Format_ARGB32_Premultiplied);
-			cache.setDevicePixelRatio(factor);
-		}
-		{
-			Painter p(&cache);
-			if (newcache) {
-				if (request.framew < request.outerw) {
-					p.fillRect(0, 0, (request.outerw - request.framew) / (2 * factor), cache.height() / factor, st::black);
-					p.fillRect((request.outerw - request.framew) / (2 * factor) + (request.framew / factor), 0, (cache.width() / factor) - ((request.outerw - request.framew) / (2 * factor) + (request.framew / factor)), cache.height() / factor, st::black);
-				}
-				if (request.frameh < request.outerh) {
-					p.fillRect(qMax(0, (request.outerw - request.framew) / (2 * factor)), 0, qMin(cache.width(), request.framew) / factor, (request.outerh - request.frameh) / (2 * factor), st::black);
-					p.fillRect(qMax(0, (request.outerw - request.framew) / (2 * factor)), (request.outerh - request.frameh) / (2 * factor) + (request.frameh / factor), qMin(cache.width(), request.framew) / factor, (cache.height() / factor) - ((request.outerh - request.frameh) / (2 * factor) + (request.frameh / factor)), st::black);
-				}
-			}
-			if (hasAlpha) {
-				p.fillRect(qMax(0, (request.outerw - request.framew) / (2 * factor)), qMax(0, (request.outerh - request.frameh) / (2 * factor)), qMin(cache.width(), request.framew) / factor, qMin(cache.height(), request.frameh) / factor, st::white);
-			}
-			QPoint position((request.outerw - request.framew) / (2 * factor), (request.outerh - request.frameh) / (2 * factor));
-			if (badSize) {
-				p.setRenderHint(QPainter::SmoothPixmapTransform);
-				QRect to(position, QSize(request.framew / factor, request.frameh / factor));
-				QRect from(0, 0, original.width(), original.height());
-				p.drawImage(to, original, from, Qt::ColorOnly);
-			} else {
-				p.drawImage(position, original);
-			}
-		}
-		if (request.rounded) {
-			imageRound(cache);
-		}
-		return QPixmap::fromImage(cache, Qt::ColorOnly);
-	}
-	return QPixmap::fromImage(original, Qt::ColorOnly);
-}
-
-ClipReader::ClipReader(const FileLocation &location, const QByteArray &data, Callback &&callback)
-: _callback(std_::move(callback))
-, _state(ClipReading)
-, _width(0)
-, _height(0)
-, _step(WaitingForDimensionsStep)
-, _paused(0)
-, _autoplay(false)
-, _private(0) {
-	if (_clipThreads.size() < ClipThreadsCount) {
-		_threadIndex = _clipThreads.size();
-		_clipThreads.push_back(new QThread());
-		_clipManagers.push_back(new ClipReadManager(_clipThreads.back()));
-		_clipThreads.back()->start();
-	} else {
-		_threadIndex = int32(rand_value<uint32>() % _clipThreads.size());
-		int32 loadLevel = 0x7FFFFFFF;
-		for (int32 i = 0, l = _clipThreads.size(); i < l; ++i) {
-			int32 level = _clipManagers.at(i)->loadLevel();
-			if (level < loadLevel) {
-				_threadIndex = i;
-				loadLevel = level;
-			}
-		}
-	}
-	_clipManagers.at(_threadIndex)->append(this, location, data);
-}
-
-ClipReader::Frame *ClipReader::frameToShow(int32 *index) const { // 0 means not ready
-	int32 step = _step.loadAcquire(), i;
-	if (step == WaitingForDimensionsStep) {
-		if (index) *index = 0;
-		return 0;
-	} else if (step == WaitingForRequestStep) {
-		i = 0;
-	} else if (step == WaitingForFirstFrameStep) {
-		i = 0;
-	} else {
-		i = (step / 2) % 3;
-	}
-	if (index) *index = i;
-	return _frames + i;
-}
-
-ClipReader::Frame *ClipReader::frameToWrite(int32 *index) const { // 0 means not ready
-	int32 step = _step.loadAcquire(), i;
-	if (step == WaitingForDimensionsStep) {
-		i = 0;
-	} else if (step == WaitingForRequestStep) {
-		if (index) *index = 0;
-		return 0;
-	} else if (step == WaitingForFirstFrameStep) {
-		i = 0;
-	} else {
-		i = ((step + 2) / 2) % 3;
-	}
-	if (index) *index = i;
-	return _frames + i;
-}
-
-ClipReader::Frame *ClipReader::frameToWriteNext(bool checkNotWriting, int32 *index) const {
-	int32 step = _step.loadAcquire(), i;
-	if (step == WaitingForDimensionsStep || step == WaitingForRequestStep || (checkNotWriting && (step % 2))) {
-		if (index) *index = 0;
-		return 0;
-	}
-	i = ((step + 4) / 2) % 3;
-	if (index) *index = i;
-	return _frames + i;
-}
-
-void ClipReader::moveToNextShow() const {
-	int32 step = _step.loadAcquire();
-	if (step == WaitingForDimensionsStep) {
-	} else if (step == WaitingForRequestStep) {
-		_step.storeRelease(WaitingForFirstFrameStep);
-	} else if (step == WaitingForFirstFrameStep) {
-	} else if (!(step % 2)) {
-		_step.storeRelease(step + 1);
-	}
-}
-
-void ClipReader::moveToNextWrite() const {
-	int32 step = _step.loadAcquire();
-	if (step == WaitingForDimensionsStep) {
-		_step.storeRelease(WaitingForRequestStep);
-	} else if (step == WaitingForRequestStep) {
-	} else if (step == WaitingForFirstFrameStep) {
-		_step.storeRelease(0);
-	} else if (step % 2) {
-		_step.storeRelease((step + 1) % 6);
-	}
-}
-
-void ClipReader::callback(ClipReader *reader, int32 threadIndex, ClipReaderNotification notification) {
-	// check if reader is not deleted already
-	if (_clipManagers.size() > threadIndex && _clipManagers.at(threadIndex)->carries(reader)) {
-		reader->_callback.call(notification);
-	}
-}
-
-void ClipReader::start(int32 framew, int32 frameh, int32 outerw, int32 outerh, bool rounded) {
-	if (_clipManagers.size() <= _threadIndex) error();
-	if (_state == ClipError) return;
-
-	if (_step.loadAcquire() == WaitingForRequestStep) {
-		int32 factor(cIntRetinaFactor());
-		ClipFrameRequest request;
-		request.factor = factor;
-		request.framew = framew * factor;
-		request.frameh = frameh * factor;
-		request.outerw = outerw * factor;
-		request.outerh = outerh * factor;
-		request.rounded = rounded;
-		_frames[0].request = _frames[1].request = _frames[2].request = request;
-		moveToNextShow();
-		_clipManagers.at(_threadIndex)->start(this);
-	}
-}
-
-QPixmap ClipReader::current(int32 framew, int32 frameh, int32 outerw, int32 outerh, uint64 ms) {
-	Frame *frame = frameToShow();
-	t_assert(frame != 0);
-
-	if (ms) {
-		frame->displayed.storeRelease(1);
-		if (_paused.loadAcquire()) {
-			_paused.storeRelease(0);
-			if (_clipManagers.size() <= _threadIndex) error();
-			if (_state != ClipError) {
-				_clipManagers.at(_threadIndex)->update(this);
-			}
-		}
-	} else {
-		frame->displayed.storeRelease(-1); // displayed, but should be paused
-	}
-
-	int32 factor(cIntRetinaFactor());
-	if (frame->pix.width() == outerw * factor && frame->pix.height() == outerh * factor) {
-		moveToNextShow();
-		return frame->pix;
-	}
-
-	frame->request.framew = framew * factor;
-	frame->request.frameh = frameh * factor;
-	frame->request.outerw = outerw * factor;
-	frame->request.outerh = outerh * factor;
-
-	QImage cacheForResize;
-	frame->original.setDevicePixelRatio(factor);
-	frame->pix = QPixmap();
-	frame->pix = _prepareFrame(frame->request, frame->original, true, cacheForResize);
-
-	Frame *other = frameToWriteNext(true);
-	if (other) other->request = frame->request;
-
-	moveToNextShow();
-
-	if (_clipManagers.size() <= _threadIndex) error();
-	if (_state != ClipError) {
-		_clipManagers.at(_threadIndex)->update(this);
-	}
-
-	return frame->pix;
-}
-
-bool ClipReader::ready() const {
-	if (_width && _height) return true;
-
-	Frame *frame = frameToShow();
-	if (frame) {
-		_width = frame->original.width();
-		_height = frame->original.height();
-		return true;
-	}
-	return false;
-}
-
-int32 ClipReader::width() const {
-	return _width;
-}
-
-int32 ClipReader::height() const {
-	return _height;
-}
-
-ClipState ClipReader::state() const {
-	return _state;
-}
-
-void ClipReader::stop() {
-	if (_clipManagers.size() <= _threadIndex) error();
-	if (_state != ClipError) {
-		_clipManagers.at(_threadIndex)->stop(this);
-		_width = _height = 0;
-	}
-}
-
-void ClipReader::error() {
-	_state = ClipError;
-}
-
-ClipReader::~ClipReader() {
-	stop();
-}
-
-class ClipReaderImplementation {
-public:
-
-	ClipReaderImplementation(FileLocation *location, QByteArray *data)
-		: _location(location)
-		, _data(data)
-		, _device(0)
-		, _dataSize(0) {
-	}
-	virtual bool readNextFrame() = 0;
-	virtual bool renderFrame(QImage &to, bool &hasAlpha, const QSize &size) = 0;
-	virtual int32 nextFrameDelay() = 0;
-	virtual bool start(bool onlyGifv) = 0;
-	virtual ~ClipReaderImplementation() {
-	}
-	int64 dataSize() const {
-		return _dataSize;
-	}
-
-protected:
-	FileLocation *_location;
-	QByteArray *_data;
-	QFile _file;
-	QBuffer _buffer;
-	QIODevice *_device;
-	int64 _dataSize;
-
-	void initDevice() {
-		if (_data->isEmpty()) {
-			if (_file.isOpen()) _file.close();
-			_file.setFileName(_location->name());
-			_dataSize = _file.size();
-		} else {
-			if (_buffer.isOpen()) _buffer.close();
-			_buffer.setBuffer(_data);
-			_dataSize = _data->size();
-		}
-		_device = _data->isEmpty() ? static_cast<QIODevice*>(&_file) : static_cast<QIODevice*>(&_buffer);
-	}
-
-};
-
-class QtGifReaderImplementation : public ClipReaderImplementation{
-public:
-
-	QtGifReaderImplementation(FileLocation *location, QByteArray *data) : ClipReaderImplementation(location, data)
-	, _reader(0)
-	, _framesLeft(0)
-	, _frameDelay(0) {
-	}
-
-	bool readNextFrame() {
-		if (_reader) _frameDelay = _reader->nextImageDelay();
-		if (_framesLeft < 1 && !jumpToStart()) {
-			return false;
-		}
-
-		_frame = QImage(); // QGifHandler always reads first to internal QImage and returns it
-		if (!_reader->read(&_frame) || _frame.isNull()) {
-			return false;
-		}
-		--_framesLeft;
-		return true;
-	}
-
-	bool renderFrame(QImage &to, bool &hasAlpha, const QSize &size) {
-		t_assert(!_frame.isNull());
-		if (size.isEmpty() || size == _frame.size()) {
-			int32 w = _frame.width(), h = _frame.height();
-			if (to.width() == w && to.height() == h && to.format() == _frame.format()) {
-				if (to.byteCount() != _frame.byteCount()) {
-					int bpl = qMin(to.bytesPerLine(), _frame.bytesPerLine());
-					for (int i = 0; i < h; ++i) {
-						memcpy(to.scanLine(i), _frame.constScanLine(i), bpl);
-					}
-				} else {
-					memcpy(to.bits(), _frame.constBits(), _frame.byteCount());
-				}
-			} else {
-				to = _frame.copy();
-			}
-		} else {
-			to = _frame.scaled(size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
-		}
-		hasAlpha = _frame.hasAlphaChannel();
-		_frame = QImage();
-		return true;
-	}
-
-	int32 nextFrameDelay() {
-		return _frameDelay;
-	}
-
-	bool start(bool onlyGifv) {
-		if (onlyGifv) return false;
-		return jumpToStart();
-	}
-
-	~QtGifReaderImplementation() {
-		deleteAndMark(_reader);
-	}
-
-private:
-	QImageReader *_reader;
-	int32 _framesLeft, _frameDelay;
-	QImage _frame;
-
-	bool jumpToStart() {
-		if (_reader && _reader->jumpToImage(0)) {
-			_framesLeft = _reader->imageCount();
-			return true;
-		}
-
-		delete _reader;
-		initDevice();
-		_reader = new QImageReader(_device);
-#if QT_VERSION >= QT_VERSION_CHECK(5, 5, 0)
-		_reader->setAutoTransform(true);
-#endif
-		if (!_reader->canRead() || !_reader->supportsAnimation()) {
-			return false;
-		}
-		_framesLeft = _reader->imageCount();
-		if (_framesLeft < 1) {
-			return false;
-		}
-		return true;
-	}
-
-};
-
-class FFMpegReaderImplementation : public ClipReaderImplementation {
-public:
-
-	FFMpegReaderImplementation(FileLocation *location, QByteArray *data) : ClipReaderImplementation(location, data)
-		, _ioBuffer(0)
-		, _ioContext(0)
-		, _fmtContext(0)
-		, _codec(0)
-		, _codecContext(0)
-		, _streamId(0)
-		, _frame(0)
-		, _opened(false)
-		, _hadFrame(false)
-		, _frameRead(false)
-		, _packetSize(0)
-		, _packetData(0)
-		, _packetWas(false)
-		, _width(0)
-		, _height(0)
-		, _swsContext(0)
-		, _frameMs(0)
-		, _nextFrameDelay(0)
-		, _currentFrameDelay(0) {
-		_frame = av_frame_alloc();
-		av_init_packet(&_avpkt);
-		_avpkt.data = NULL;
-		_avpkt.size = 0;
-	}
-
-	bool readNextFrame() {
-		if (_frameRead) {
-			av_frame_unref(_frame);
-			_frameRead = false;
-		}
-
-		int res;
-		while (true) {
-			if (_avpkt.size > 0) { // previous packet not finished
-				res = 0;
-			} else if ((res = av_read_frame(_fmtContext, &_avpkt)) < 0) {
-				if (res != AVERROR_EOF || !_hadFrame) {
-					char err[AV_ERROR_MAX_STRING_SIZE] = { 0 };
-					LOG(("Gif Error: Unable to av_read_frame() %1, error %2, %3").arg(logData()).arg(res).arg(av_make_error_string(err, sizeof(err), res)));
-					return false;
-				}
-			}
-
-			bool finished = (res < 0);
-			if (finished) {
-				_avpkt.data = NULL;
-				_avpkt.size = 0;
-			} else {
-				rememberPacket();
-			}
-
-			int32 got_frame = 0;
-			int32 decoded = _avpkt.size;
-			if (_avpkt.stream_index == _streamId) {
-				if ((res = avcodec_decode_video2(_codecContext, _frame, &got_frame, &_avpkt)) < 0) {
-					char err[AV_ERROR_MAX_STRING_SIZE] = { 0 };
-					LOG(("Gif Error: Unable to avcodec_decode_video2() %1, error %2, %3").arg(logData()).arg(res).arg(av_make_error_string(err, sizeof(err), res)));
-
-					if (res == AVERROR_INVALIDDATA) { // try to skip bad packet
-						freePacket();
-						_avpkt.data = NULL;
-						_avpkt.size = 0;
-						continue;
-					}
-
-					if (res != AVERROR_EOF || !_hadFrame) { // try to skip end of file
-						return false;
-					}
-					freePacket();
-					_avpkt.data = NULL;
-					_avpkt.size = 0;
-					continue;
-				}
-				if (res > 0) decoded = res;
-			}
-			if (!finished) {
-				_avpkt.data += decoded;
-				_avpkt.size -= decoded;
-				if (_avpkt.size <= 0) freePacket();
-			}
-
-			if (got_frame) {
-				int64 duration = av_frame_get_pkt_duration(_frame);
-				int64 framePts = (_frame->pkt_pts == AV_NOPTS_VALUE) ? _frame->pkt_dts : _frame->pkt_pts;
-				int64 frameMs = (framePts * 1000LL * _fmtContext->streams[_streamId]->time_base.num) / _fmtContext->streams[_streamId]->time_base.den;
-				_currentFrameDelay = _nextFrameDelay;
-				if (_frameMs + _currentFrameDelay < frameMs) {
-					_currentFrameDelay = int32(frameMs - _frameMs);
-				}
-				if (duration == AV_NOPTS_VALUE) {
-					_nextFrameDelay = 0;
-				} else {
-					_nextFrameDelay = (duration * 1000LL * _fmtContext->streams[_streamId]->time_base.num) / _fmtContext->streams[_streamId]->time_base.den;
-				}
-				_frameMs = frameMs;
-
-				_hadFrame = _frameRead = true;
-				return true;
-			}
-
-			if (finished) {
-				if ((res = avformat_seek_file(_fmtContext, _streamId, std::numeric_limits<int64_t>::min(), 0, std::numeric_limits<int64_t>::max(), 0)) < 0) {
-					if ((res = av_seek_frame(_fmtContext, _streamId, 0, AVSEEK_FLAG_BYTE)) < 0) {
-						if ((res = av_seek_frame(_fmtContext, _streamId, 0, AVSEEK_FLAG_FRAME)) < 0) {
-							if ((res = av_seek_frame(_fmtContext, _streamId, 0, 0)) < 0) {
-								char err[AV_ERROR_MAX_STRING_SIZE] = { 0 };
-								LOG(("Gif Error: Unable to av_seek_frame() to the start %1, error %2, %3").arg(logData()).arg(res).arg(av_make_error_string(err, sizeof(err), res)));
-								return false;
-							}
-						}
-					}
-				}
-				avcodec_flush_buffers(_codecContext);
-				_hadFrame = false;
-				_frameMs = 0;
-			}
-		}
-
-		return false;
-	}
-
-	bool renderFrame(QImage &to, bool &hasAlpha, const QSize &size) {
-		t_assert(_frameRead);
-		_frameRead = false;
-
-		if (!_width || !_height) {
-			_width = _frame->width;
-			_height = _frame->height;
-			if (!_width || !_height) {
-				LOG(("Gif Error: Bad frame size %1").arg(logData()));
-				return false;
-			}
-		}
-
-		QSize toSize(size.isEmpty() ? QSize(_width, _height) : size);
-		if (to.isNull() || to.size() != toSize) {
-			to = QImage(toSize, QImage::Format_ARGB32);
-		}
-		hasAlpha = (_frame->format == AV_PIX_FMT_BGRA || (_frame->format == -1 && _codecContext->pix_fmt == AV_PIX_FMT_BGRA));
-		if (_frame->width == toSize.width() && _frame->height == toSize.height() && hasAlpha) {
-			int32 sbpl = _frame->linesize[0], dbpl = to.bytesPerLine(), bpl = qMin(sbpl, dbpl);
-			uchar *s = _frame->data[0], *d = to.bits();
-			for (int32 i = 0, l = _frame->height; i < l; ++i) {
-				memcpy(d + i * dbpl, s + i * sbpl, bpl);
-			}
-		} else {
-			if ((_swsSize != toSize) || (_frame->format != -1 && _frame->format != _codecContext->pix_fmt) || !_swsContext) {
-				_swsSize = toSize;
-				_swsContext = sws_getCachedContext(_swsContext, _frame->width, _frame->height, AVPixelFormat(_frame->format), toSize.width(), toSize.height(), AV_PIX_FMT_BGRA, 0, 0, 0, 0);
-			}
-			uint8_t * toData[1] = { to.bits() };
-			int	toLinesize[1] = { to.bytesPerLine() }, res;
-			if ((res = sws_scale(_swsContext, _frame->data, _frame->linesize, 0, _frame->height, toData, toLinesize)) != _swsSize.height()) {
-				LOG(("Gif Error: Unable to sws_scale to good size %1, height %2, should be %3").arg(logData()).arg(res).arg(_swsSize.height()));
-				return false;
-			}
-		}
-
-		av_frame_unref(_frame);
-		return true;
-	}
-
-	int32 nextFrameDelay() {
-		return _currentFrameDelay;
-	}
-
-	QString logData() const {
-		return qsl("for file '%1', data size '%2'").arg(_location ? _location->name() : QString()).arg(_data->size());
-	}
-
-	bool start(bool onlyGifv) {
-		initDevice();
-		if (!_device->open(QIODevice::ReadOnly)) {
-			LOG(("Gif Error: Unable to open device %1").arg(logData()));
-			return false;
-		}
-		_ioBuffer = (uchar*)av_malloc(AVBlockSize);
-		_ioContext = avio_alloc_context(_ioBuffer, AVBlockSize, 0, static_cast<void*>(this), &FFMpegReaderImplementation::_read, 0, &FFMpegReaderImplementation::_seek);
-		_fmtContext = avformat_alloc_context();
-		if (!_fmtContext) {
-			LOG(("Gif Error: Unable to avformat_alloc_context %1").arg(logData()));
-			return false;
-		}
-		_fmtContext->pb = _ioContext;
-
-		int res = 0;
-		char err[AV_ERROR_MAX_STRING_SIZE] = { 0 };
-		if ((res = avformat_open_input(&_fmtContext, 0, 0, 0)) < 0) {
-			_ioBuffer = 0;
-
-			LOG(("Gif Error: Unable to avformat_open_input %1, error %2, %3").arg(logData()).arg(res).arg(av_make_error_string(err, sizeof(err), res)));
-			return false;
-		}
-		_opened = true;
-
-		if ((res = avformat_find_stream_info(_fmtContext, 0)) < 0) {
-			LOG(("Gif Error: Unable to avformat_find_stream_info %1, error %2, %3").arg(logData()).arg(res).arg(av_make_error_string(err, sizeof(err), res)));
-			return false;
-		}
-
-		_streamId = av_find_best_stream(_fmtContext, AVMEDIA_TYPE_VIDEO, -1, -1, 0, 0);
-		if (_streamId < 0) {
-			LOG(("Gif Error: Unable to av_find_best_stream %1, error %2, %3").arg(logData()).arg(_streamId).arg(av_make_error_string(err, sizeof(err), _streamId)));
-			return false;
-		}
-
-		// Get a pointer to the codec context for the audio stream
-		_codecContext = _fmtContext->streams[_streamId]->codec;
-		_codec = avcodec_find_decoder(_codecContext->codec_id);
-
-		if (onlyGifv) {
-			if (av_find_best_stream(_fmtContext, AVMEDIA_TYPE_AUDIO, -1, -1, 0, 0) >= 0) { // should be no audio stream
-				return false;
-			}
-			if (dataSize() > AnimationInMemory) {
-				return false;
-			}
-			if (_codecContext->codec_id != AV_CODEC_ID_H264) {
-				return false;
-			}
-		}
-		av_opt_set_int(_codecContext, "refcounted_frames", 1, 0);
-		if ((res = avcodec_open2(_codecContext, _codec, 0)) < 0) {
-			LOG(("Gif Error: Unable to avcodec_open2 %1, error %2, %3").arg(logData()).arg(res).arg(av_make_error_string(err, sizeof(err), res)));
-			return false;
-		}
-
-		return true;
-	}
-
-	int32 duration() const {
-		if (_fmtContext->streams[_streamId]->duration == AV_NOPTS_VALUE) return 0;
-		return (_fmtContext->streams[_streamId]->duration * _fmtContext->streams[_streamId]->time_base.num) / _fmtContext->streams[_streamId]->time_base.den;
-	}
-
-	~FFMpegReaderImplementation() {
-		if (_frameRead) {
-			av_frame_unref(_frame);
-			_frameRead = false;
-		}
-		if (_ioContext) av_free(_ioContext);
-		if (_codecContext) avcodec_close(_codecContext);
-		if (_swsContext) sws_freeContext(_swsContext);
-		if (_opened) {
-			avformat_close_input(&_fmtContext);
-		} else if (_ioBuffer) {
-			av_free(_ioBuffer);
-		}
-		if (_fmtContext) avformat_free_context(_fmtContext);
-		av_frame_free(&_frame);
-		freePacket();
-	}
-
-private:
-	uchar *_ioBuffer;
-	AVIOContext *_ioContext;
-	AVFormatContext *_fmtContext;
-	AVCodec *_codec;
-	AVCodecContext *_codecContext;
-	int32 _streamId;
-	AVFrame *_frame;
-	bool _opened, _hadFrame, _frameRead;
-
-	AVPacket _avpkt;
-	int _packetSize;
-	uint8_t *_packetData;
-	bool _packetWas;
-	void rememberPacket() {
-		if (!_packetWas) {
-			_packetSize = _avpkt.size;
-			_packetData = _avpkt.data;
-			_packetWas = true;
-		}
-	}
-	void freePacket() {
-		if (_packetWas) {
-			_avpkt.size = _packetSize;
-			_avpkt.data = _packetData;
-			_packetWas = false;
-			av_packet_unref(&_avpkt);
-		}
-	}
-
-	int32 _width, _height;
-	SwsContext *_swsContext;
-	QSize _swsSize;
-
-	int64 _frameMs;
-	int32 _nextFrameDelay, _currentFrameDelay;
-
-	static int _read(void *opaque, uint8_t *buf, int buf_size) {
-		FFMpegReaderImplementation *l = reinterpret_cast<FFMpegReaderImplementation*>(opaque);
-		return int(l->_device->read((char*)(buf), buf_size));
-	}
-
-	static int64_t _seek(void *opaque, int64_t offset, int whence) {
-		FFMpegReaderImplementation *l = reinterpret_cast<FFMpegReaderImplementation*>(opaque);
-
-		switch (whence) {
-		case SEEK_SET: return l->_device->seek(offset) ? l->_device->pos() : -1;
-		case SEEK_CUR: return l->_device->seek(l->_device->pos() + offset) ? l->_device->pos() : -1;
-		case SEEK_END: return l->_device->seek(l->_device->size() + offset) ? l->_device->pos() : -1;
-		}
-		return -1;
-	}
-
-};
-
-class ClipReaderPrivate {
-public:
-
-	ClipReaderPrivate(ClipReader *reader, const FileLocation &location, const QByteArray &data) : _interface(reader)
-	, _state(ClipReading)
-	, _data(data)
-	, _location(_data.isEmpty() ? new FileLocation(location) : 0)
-	, _accessed(false)
-	, _implementation(0)
-	, _frame(0)
-	, _width(0)
-	, _height(0)
-	, _nextFrameWhen(0)
-	, _paused(false) {
-		if (_data.isEmpty() && !_location->accessEnable()) {
-			error();
-			return;
-		}
-		_accessed = true;
-	}
-
-	ClipProcessResult start(uint64 ms) {
-		if (!_implementation && !init()) {
-			return error();
-		}
-		if (frame() && frame()->original.isNull()) {
-			if (!_implementation->readNextFrame()) {
-				return error();
-			}
-			if (!_implementation->renderFrame(frame()->original, frame()->alpha, QSize())) {
-				return error();
-			}
-			_width = frame()->original.width();
-			_height = frame()->original.height();
-			return ClipProcessStarted;
-		}
-		return ClipProcessWait;
-	}
-
-	ClipProcessResult process(uint64 ms) { // -1 - do nothing, 0 - update, 1 - reinit
-		if (_state == ClipError) return ClipProcessError;
-
-		if (!_request.valid()) {
-			return start(ms);
-		}
-
-		if (!_paused && ms >= _nextFrameWhen) {
-			return ClipProcessRepaint;
-		}
-		return ClipProcessWait;
-	}
-
-	ClipProcessResult finishProcess(uint64 ms) {
-		if (!readNextFrame()) {
-			return error();
-		}
-		if (ms >= _nextFrameWhen && !readNextFrame(true)) {
-			return error();
-		}
-		if (!renderFrame()) {
-			return error();
-		}
-		return ClipProcessCopyFrame;
-	}
-
-	uint64 nextFrameDelay() {
-		int32 delay = _implementation->nextFrameDelay();
-		return qMax(delay, 5);
-	}
-
-	bool readNextFrame(bool keepup = false) {
-		if (!_implementation->readNextFrame()) {
-			return false;
-		}
-		_nextFrameWhen += nextFrameDelay();
-		if (keepup) {
-			_nextFrameWhen = qMax(_nextFrameWhen, getms());
-		}
-		return true;
-	}
-
-	bool renderFrame() {
-		t_assert(frame() != 0 && _request.valid());
-		if (!_implementation->renderFrame(frame()->original, frame()->alpha, QSize(_request.framew, _request.frameh))) {
-			return false;
-		}
-		frame()->original.setDevicePixelRatio(_request.factor);
-		frame()->pix = QPixmap();
-		frame()->pix = _prepareFrame(_request, frame()->original, frame()->alpha, frame()->cache);
-		frame()->when = _nextFrameWhen;
-		return true;
-	}
-
-	bool init() {
-		if (_data.isEmpty() && QFileInfo(_location->name()).size() <= AnimationInMemory) {
-			QFile f(_location->name());
-			if (f.open(QIODevice::ReadOnly)) {
-				_data = f.readAll();
-				if (f.error() != QFile::NoError) {
-					_data = QByteArray();
-				}
-			}
-		}
-
-		_implementation = new FFMpegReaderImplementation(_location, &_data);
-//		_implementation = new QtGifReaderImplementation(_location, &_data);
-		return _implementation->start(false);
-	}
-
-	ClipProcessResult error() {
-		stop();
-		_state = ClipError;
-		return ClipProcessError;
-	}
-
-	void stop() {
-		delete _implementation;
-		_implementation = 0;
-
-		if (_location) {
-			if (_accessed) {
-				_location->accessDisable();
-			}
-			delete _location;
-			_location = 0;
-		}
-		_accessed = false;
-	}
-
-	~ClipReaderPrivate() {
-		stop();
-		deleteAndMark(_location);
-		deleteAndMark(_implementation);
-		_data.clear();
-	}
-
-private:
-
-	ClipReader *_interface;
-	ClipState _state;
-
-	QByteArray _data;
-	FileLocation *_location;
-	bool _accessed;
-
-	QBuffer _buffer;
-	ClipReaderImplementation *_implementation;
-
-	ClipFrameRequest _request;
-	struct Frame {
-		Frame() : alpha(true), when(0) {
-		}
-		QPixmap pix;
-		QImage original, cache;
-		bool alpha;
-		uint64 when;
-	};
-	Frame _frames[3];
-	int32 _frame;
-	Frame *frame() {
-		return _frames + _frame;
-	}
-
-	int32 _width, _height;
-
-	uint64 _nextFrameWhen;
-
-	bool _paused;
-
-	friend class ClipReadManager;
-
-};
-
-ClipReadManager::ClipReadManager(QThread *thread) : _processingInThread(0), _needReProcess(false) {
-	moveToThread(thread);
-	connect(thread, SIGNAL(started()), this, SLOT(process()));
-    connect(thread, SIGNAL(finished()), this, SLOT(finish()));
-	connect(this, SIGNAL(processDelayed()), this, SLOT(process()), Qt::QueuedConnection);
-
-	_timer.setSingleShot(true);
-	_timer.moveToThread(thread);
-	connect(&_timer, SIGNAL(timeout()), this, SLOT(process()));
-
-	connect(this, SIGNAL(callback(ClipReader*,qint32,qint32)), _manager, SLOT(clipCallback(ClipReader*,qint32,qint32)));
-}
-
-void ClipReadManager::append(ClipReader *reader, const FileLocation &location, const QByteArray &data) {
-	reader->_private = new ClipReaderPrivate(reader, location, data);
-	_loadLevel.fetchAndAddRelaxed(AverageGifSize);
-	update(reader);
-}
-
-void ClipReadManager::start(ClipReader *reader) {
-	update(reader);
-}
-
-void ClipReadManager::update(ClipReader *reader) {
-	QReadLocker lock(&_readerPointersMutex);
-	ReaderPointers::const_iterator i = _readerPointers.constFind(reader);
-	if (i == _readerPointers.cend()) {
-		lock.unlock();
-
-		QWriteLocker lock(&_readerPointersMutex);
-		_readerPointers.insert(reader, MutableAtomicInt(1));
-	} else {
-		i->v.storeRelease(1);
-	}
-	emit processDelayed();
-}
-
-void ClipReadManager::stop(ClipReader *reader) {
-	if (!carries(reader)) return;
-
-	QWriteLocker lock(&_readerPointersMutex);
-	_readerPointers.remove(reader);
-	emit processDelayed();
-}
-
-bool ClipReadManager::carries(ClipReader *reader) const {
-	QReadLocker lock(&_readerPointersMutex);
-	return _readerPointers.contains(reader);
-}
-
-ClipReadManager::ReaderPointers::iterator ClipReadManager::unsafeFindReaderPointer(ClipReaderPrivate *reader) {
-	ReaderPointers::iterator it = _readerPointers.find(reader->_interface);
-
-	// could be a new reader which was realloced in the same address
-	return (it == _readerPointers.cend() || it.key()->_private == reader) ? it : _readerPointers.end();
-}
-
-ClipReadManager::ReaderPointers::const_iterator ClipReadManager::constUnsafeFindReaderPointer(ClipReaderPrivate *reader) const {
-	ReaderPointers::const_iterator it = _readerPointers.constFind(reader->_interface);
-
-	// could be a new reader which was realloced in the same address
-	return (it == _readerPointers.cend() || it.key()->_private == reader) ? it : _readerPointers.cend();
-}
-
-bool ClipReadManager::handleProcessResult(ClipReaderPrivate *reader, ClipProcessResult result, uint64 ms) {
-	QReadLocker lock(&_readerPointersMutex);
-	ReaderPointers::const_iterator it = constUnsafeFindReaderPointer(reader);
-	if (result == ClipProcessError) {
-		if (it != _readerPointers.cend()) {
-			it.key()->error();
-			emit callback(it.key(), it.key()->threadIndex(), ClipReaderReinit);
-
-			lock.unlock();
-			QWriteLocker lock(&_readerPointersMutex);
-			ReaderPointers::iterator i = unsafeFindReaderPointer(reader);
-			if (i != _readerPointers.cend()) _readerPointers.erase(i);
-		}
-		return false;
-	}
-	if (it == _readerPointers.cend()) {
-		return false;
-	}
-
-	if (result == ClipProcessStarted) {
-		_loadLevel.fetchAndAddRelaxed(reader->_width * reader->_height - AverageGifSize);
-	}
-	if (!reader->_paused && result == ClipProcessRepaint) {
-		int32 ishowing, iprevious;
-		ClipReader::Frame *showing = it.key()->frameToShow(&ishowing), *previous = it.key()->frameToWriteNext(false, &iprevious);
-		t_assert(previous != 0 && showing != 0 && ishowing >= 0 && iprevious >= 0);
-		if (reader->_frames[ishowing].when > 0 && showing->displayed.loadAcquire() <= 0) { // current frame was not shown
-			if (reader->_frames[ishowing].when + WaitBeforeGifPause < ms || (reader->_frames[iprevious].when && previous->displayed.loadAcquire() <= 0)) {
-				reader->_paused = true;
-				it.key()->_paused.storeRelease(1);
-				result = ClipProcessPaused;
-			}
-		}
-	}
-	if (result == ClipProcessStarted || result == ClipProcessCopyFrame) {
-		t_assert(reader->_frame >= 0);
-		ClipReader::Frame *frame = it.key()->_frames + reader->_frame;
-		frame->clear();
-		frame->pix = reader->frame()->pix;
-		frame->original = reader->frame()->original;
-		frame->displayed.storeRelease(0);
-		if (result == ClipProcessStarted) {
-			reader->_nextFrameWhen = ms;
-			it.key()->moveToNextWrite();
-			emit callback(it.key(), it.key()->threadIndex(), ClipReaderReinit);
-		}
-	} else if (result == ClipProcessPaused) {
-		it.key()->moveToNextWrite();
-		emit callback(it.key(), it.key()->threadIndex(), ClipReaderReinit);
-	} else if (result == ClipProcessRepaint) {
-		it.key()->moveToNextWrite();
-		emit callback(it.key(), it.key()->threadIndex(), ClipReaderRepaint);
-	}
-	return true;
-}
-
-ClipReadManager::ResultHandleState ClipReadManager::handleResult(ClipReaderPrivate *reader, ClipProcessResult result, uint64 ms) {
-	if (!handleProcessResult(reader, result, ms)) {
-		_loadLevel.fetchAndAddRelaxed(-1 * (reader->_width > 0 ? reader->_width * reader->_height : AverageGifSize));
-		delete reader;
-		return ResultHandleRemove;
-	}
-
-	_processingInThread->eventDispatcher()->processEvents(QEventLoop::AllEvents);
-	if (_processingInThread->isInterruptionRequested()) {
-		return ResultHandleStop;
-	}
-
-	if (result == ClipProcessRepaint) {
-		{
-			QReadLocker lock(&_readerPointersMutex);
-			ReaderPointers::const_iterator it = constUnsafeFindReaderPointer(reader);
-			if (it != _readerPointers.cend()) {
-				int32 index = 0;
-				ClipReader *r = it.key();
-				ClipReader::Frame *frame = it.key()->frameToWrite(&index);
-				if (frame) {
-					frame->clear();
-				} else {
-					t_assert(!reader->_request.valid());
-				}
-				reader->_frame = index;
-			}
-		}
-		return handleResult(reader, reader->finishProcess(ms), ms);
-	}
-
-	return ResultHandleContinue;
-}
-
-void ClipReadManager::process() {
-	if (_processingInThread) {
-		_needReProcess = true;
-		return;
-	}
-
-    _timer.stop();
-	_processingInThread = thread();
-
-	uint64 ms = getms(), minms = ms + 86400 * 1000ULL;
-	{
-		QReadLocker lock(&_readerPointersMutex);
-		for (ReaderPointers::iterator it = _readerPointers.begin(), e = _readerPointers.end(); it != e; ++it) {
-			if (it->v.loadAcquire()) {
-				Readers::iterator i = _readers.find(it.key()->_private);
-				if (i == _readers.cend()) {
-					_readers.insert(it.key()->_private, 0);
-				} else {
-					i.value() = ms;
-					if (i.key()->_paused && !it.key()->_paused.loadAcquire()) {
-						i.key()->_paused = false;
-					}
-				}
-				ClipReader::Frame *frame = it.key()->frameToWrite();
-				if (frame) it.key()->_private->_request = frame->request;
-				it->v.storeRelease(0);
-			}
-		}
-	}
-
-	for (Readers::iterator i = _readers.begin(), e = _readers.end(); i != e;) {
-		ClipReaderPrivate *reader = i.key();
-		if (i.value() <= ms) {
-			ResultHandleState state = handleResult(reader, reader->process(ms), ms);
-			if (state == ResultHandleRemove) {
-				i = _readers.erase(i);
-				continue;
-			} else if (state == ResultHandleStop) {
-				_processingInThread = 0;
-				return;
-			}
-			ms = getms();
-			i.value() = reader->_nextFrameWhen ? reader->_nextFrameWhen : (ms + 86400 * 1000ULL);
-		}
-		if (!reader->_paused && i.value() < minms) {
-			minms = i.value();
-		}
-		++i;
-	}
-
-	ms = getms();
-	if (_needReProcess || minms <= ms) {
-		_needReProcess = false;
-		_timer.start(1);
-	} else {
-		_timer.start(minms - ms);
-	}
-
-	_processingInThread = 0;
-}
-
-void ClipReadManager::finish() 	{
-    _timer.stop();
-    clear();
-}
-
-void ClipReadManager::clear() {
-	{
-		QWriteLocker lock(&_readerPointersMutex);
-		for (ReaderPointers::iterator it = _readerPointers.begin(), e = _readerPointers.end(); it != e; ++it) {
-			it.key()->_private = 0;
-		}
-		_readerPointers.clear();
-	}
-
-    for (Readers::iterator i = _readers.begin(), e = _readers.end(); i != e; ++i) {
-        delete i.key();
-    }
-    _readers.clear();
-}
-
-ClipReadManager::~ClipReadManager() {
-    clear();
-}
-
-MTPDocumentAttribute clipReadAnimatedAttributes(const QString &fname, const QByteArray &data, QImage &cover) {
-	FileLocation localloc(StorageFilePartial, fname);
-	QByteArray localdata(data);
-
-	FFMpegReaderImplementation *reader = new FFMpegReaderImplementation(&localloc, &localdata);
-	if (reader->start(true)) {
-		bool hasAlpha = false;
-		if (reader->readNextFrame() && reader->renderFrame(cover, hasAlpha, QSize())) {
-			if (cover.width() > 0 && cover.height() > 0 && cover.width() < cover.height() * 10 && cover.height() < cover.width() * 10) {
-				if (hasAlpha) {
-					QImage cacheForResize;
-					ClipFrameRequest request;
-					request.framew = request.outerw = cover.width();
-					request.frameh = request.outerh = cover.height();
-					request.factor = 1;
-					cover = _prepareFrame(request, cover, hasAlpha, cacheForResize).toImage();
-				}
-				int32 duration = reader->duration();
-				delete reader;
-				return MTP_documentAttributeVideo(MTP_int(duration), MTP_int(cover.width()), MTP_int(cover.height()));
-			}
-		}
-	}
-	delete reader;
-	return MTP_documentAttributeFilename(MTP_string(fname));
+void AnimationManager::clipCallback(Media::Clip::Reader *reader, qint32 threadIndex, qint32 notification) {
+	Media::Clip::Reader::callback(reader, threadIndex, Media::Clip::Notification(notification));
 }
diff --git a/Telegram/SourceFiles/ui/animation.h b/Telegram/SourceFiles/ui/animation.h
index ff1a944ed..3482ef0e9 100644
--- a/Telegram/SourceFiles/ui/animation.h
+++ b/Telegram/SourceFiles/ui/animation.h
@@ -24,6 +24,22 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
 #include <QtCore/QTimer>
 #include <QtGui/QColor>
 
+namespace Media {
+namespace Clip {
+
+class Reader;
+static Reader * const BadReader = SharedMemoryLocation<Reader, 0>();
+
+class Manager;
+
+enum Notification {
+	NotificationReinit,
+	NotificationRepaint,
+};
+
+} // namespace Clip
+} // namespace Media
+
 namespace anim {
 
 	typedef float64 (*transition)(const float64 &delta, const float64 &dt);
@@ -193,6 +209,7 @@ namespace anim {
 
 	void startManager();
 	void stopManager();
+	void registerClipManager(Media::Clip::Manager *manager);
 
 };
 
@@ -480,8 +497,6 @@ if ((animation).isNull()) { \
 ENSURE_ANIMATION(animation, updateCallback, from); \
 (animation).start((to), (duration), (transition))
 
-class ClipReader;
-
 class AnimationManager : public QObject {
 Q_OBJECT
 
@@ -494,7 +509,7 @@ public:
 public slots:
 	void timeout();
 
-	void clipCallback(ClipReader *reader, qint32 threadIndex, qint32 notification);
+	void clipCallback(Media::Clip::Reader *reader, qint32 threadIndex, qint32 notification);
 
 private:
 	typedef QMap<Animation*, NullType> AnimatingObjects;
@@ -503,198 +518,3 @@ private:
 	bool _iterating;
 
 };
-
-class FileLocation;
-
-enum ClipState {
-	ClipReading,
-	ClipError,
-};
-
-struct ClipFrameRequest {
-	ClipFrameRequest() : factor(0), framew(0), frameh(0), outerw(0), outerh(0), rounded(false) {
-	}
-	bool valid() const {
-		return factor > 0;
-	}
-	int32 factor;
-	int32 framew, frameh;
-	int32 outerw, outerh;
-	bool rounded;
-};
-
-enum ClipReaderNotification {
-	ClipReaderReinit,
-	ClipReaderRepaint,
-};
-
-enum ClipReaderSteps {
-	WaitingForDimensionsStep = -3, // before ClipReaderPrivate read the first image and got the original frame size
-	WaitingForRequestStep = -2, // before ClipReader got the original frame size and prepared the frame request
-	WaitingForFirstFrameStep = -1, // before ClipReaderPrivate got the frame request and started waiting for the 1-2 delay
-};
-
-class ClipReaderPrivate;
-class ClipReader {
-public:
-
-	using Callback = Function<void, ClipReaderNotification>;
-
-	ClipReader(const FileLocation &location, const QByteArray &data, Callback &&callback);
-	static void callback(ClipReader *reader, int32 threadIndex, ClipReaderNotification notification); // reader can be deleted
-
-	void setAutoplay() {
-		_autoplay = true;
-	}
-	bool autoplay() const {
-		return _autoplay;
-	}
-
-	void start(int32 framew, int32 frameh, int32 outerw, int32 outerh, bool rounded);
-	QPixmap current(int32 framew, int32 frameh, int32 outerw, int32 outerh, uint64 ms);
-	QPixmap frameOriginal() const {
-		Frame *frame = frameToShow();
-		if (!frame) return QPixmap();
-		QPixmap result(frame ? QPixmap::fromImage(frame->original) : QPixmap());
-		result.detach();
-		return result;
-	}
-	bool currentDisplayed() const {
-		Frame *frame = frameToShow();
-		return frame ? (frame->displayed.loadAcquire() != 0) : true;
-	}
-	bool paused() const {
-		return _paused.loadAcquire();
-	}
-	int32 threadIndex() const {
-		return _threadIndex;
-	}
-
-	int32 width() const;
-	int32 height() const;
-
-	ClipState state() const;
-	bool started() const {
-		int32 step = _step.loadAcquire();
-		return (step == WaitingForFirstFrameStep) || (step >= 0);
-	}
-	bool ready() const;
-
-	void stop();
-	void error();
-
-	~ClipReader();
-
-private:
-
-	Callback _callback;
-
-	ClipState _state;
-
-	mutable int32 _width, _height;
-
-	mutable QAtomicInt _step; // -2, -1 - init, 0-5 - work, show ((state + 1) / 2) % 3 state, write ((state + 3) / 2) % 3
-	struct Frame {
-		Frame() : displayed(false) {
-		}
-		void clear() {
-			pix = QPixmap();
-			original = QImage();
-		}
-		QPixmap pix;
-		QImage original;
-		ClipFrameRequest request;
-		QAtomicInt displayed;
-	};
-	mutable Frame _frames[3];
-	Frame *frameToShow(int32 *index = 0) const; // 0 means not ready
-	Frame *frameToWrite(int32 *index = 0) const; // 0 means not ready
-	Frame *frameToWriteNext(bool check, int32 *index = 0) const;
-	void moveToNextShow() const;
-	void moveToNextWrite() const;
-
-	QAtomicInt _paused;
-	int32 _threadIndex;
-
-	bool _autoplay;
-
-	friend class ClipReadManager;
-
-	ClipReaderPrivate *_private;
-
-};
-
-static ClipReader * const BadClipReader = SharedMemoryLocation<ClipReader, 0>();
-
-enum ClipProcessResult {
-	ClipProcessError,
-	ClipProcessStarted,
-	ClipProcessPaused,
-	ClipProcessRepaint,
-	ClipProcessCopyFrame,
-	ClipProcessWait,
-};
-
-class ClipReadManager : public QObject {
-	Q_OBJECT
-
-public:
-
-	ClipReadManager(QThread *thread);
-	int32 loadLevel() const {
-		return _loadLevel.load();
-	}
-	void append(ClipReader *reader, const FileLocation &location, const QByteArray &data);
-	void start(ClipReader *reader);
-	void update(ClipReader *reader);
-	void stop(ClipReader *reader);
-	bool carries(ClipReader *reader) const;
-	~ClipReadManager();
-
-signals:
-
-	void processDelayed();
-
-	void callback(ClipReader *reader, qint32 threadIndex, qint32 notification);
-
-public slots:
-
-	void process();
-    void finish();
-
-private:
-
-    void clear();
-
-	QAtomicInt _loadLevel;
-	struct MutableAtomicInt {
-		MutableAtomicInt(int value) : v(value) {
-		}
-		mutable QAtomicInt v;
-	};
-	typedef QMap<ClipReader*, MutableAtomicInt> ReaderPointers;
-	ReaderPointers _readerPointers;
-	mutable QReadWriteLock _readerPointersMutex;
-
-	ReaderPointers::const_iterator constUnsafeFindReaderPointer(ClipReaderPrivate *reader) const;
-	ReaderPointers::iterator unsafeFindReaderPointer(ClipReaderPrivate *reader);
-
-	bool handleProcessResult(ClipReaderPrivate *reader, ClipProcessResult result, uint64 ms);
-
-	enum ResultHandleState {
-		ResultHandleRemove,
-		ResultHandleStop,
-		ResultHandleContinue,
-	};
-	ResultHandleState handleResult(ClipReaderPrivate *reader, ClipProcessResult result, uint64 ms);
-
-	typedef QMap<ClipReaderPrivate*, uint64> Readers;
-	Readers _readers;
-
-	QTimer _timer;
-	QThread *_processingInThread;
-	bool _needReProcess;
-
-};
-
-MTPDocumentAttribute clipReadAnimatedAttributes(const QString &fname, const QByteArray &data, QImage &cover);
diff --git a/Telegram/Telegram.vcxproj b/Telegram/Telegram.vcxproj
index 5b8fc9a82..532a644d2 100644
--- a/Telegram/Telegram.vcxproj
+++ b/Telegram/Telegram.vcxproj
@@ -371,6 +371,10 @@
       <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Deploy|Win32'">true</ExcludedFromBuild>
       <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</ExcludedFromBuild>
     </ClCompile>
+    <ClCompile Include="GeneratedFiles\Debug\moc_media_clip_reader.cpp">
+      <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Deploy|Win32'">true</ExcludedFromBuild>
+      <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</ExcludedFromBuild>
+    </ClCompile>
     <ClCompile Include="GeneratedFiles\Debug\moc_overviewwidget.cpp">
       <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Deploy|Win32'">true</ExcludedFromBuild>
       <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</ExcludedFromBuild>
@@ -701,6 +705,10 @@
       <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</ExcludedFromBuild>
       <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</ExcludedFromBuild>
     </ClCompile>
+    <ClCompile Include="GeneratedFiles\Deploy\moc_media_clip_reader.cpp">
+      <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</ExcludedFromBuild>
+      <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</ExcludedFromBuild>
+    </ClCompile>
     <ClCompile Include="GeneratedFiles\Deploy\moc_overviewwidget.cpp">
       <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</ExcludedFromBuild>
       <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</ExcludedFromBuild>
@@ -1062,6 +1070,10 @@
       <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Deploy|Win32'">true</ExcludedFromBuild>
       <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</ExcludedFromBuild>
     </ClCompile>
+    <ClCompile Include="GeneratedFiles\Release\moc_media_clip_reader.cpp">
+      <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Deploy|Win32'">true</ExcludedFromBuild>
+      <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</ExcludedFromBuild>
+    </ClCompile>
     <ClCompile Include="GeneratedFiles\Release\moc_overviewwidget.cpp">
       <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Deploy|Win32'">true</ExcludedFromBuild>
       <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</ExcludedFromBuild>
@@ -1250,6 +1262,10 @@
     <ClCompile Include="SourceFiles\main.cpp" />
     <ClCompile Include="SourceFiles\mainwidget.cpp" />
     <ClCompile Include="SourceFiles\mediaview.cpp" />
+    <ClCompile Include="SourceFiles\media\media_clip_ffmpeg.cpp" />
+    <ClCompile Include="SourceFiles\media\media_clip_implementation.cpp" />
+    <ClCompile Include="SourceFiles\media\media_clip_qtgif.cpp" />
+    <ClCompile Include="SourceFiles\media\media_clip_reader.cpp" />
     <ClCompile Include="SourceFiles\mtproto\auth_key.cpp" />
     <ClCompile Include="SourceFiles\mtproto\connection.cpp" />
     <ClCompile Include="SourceFiles\mtproto\connection_abstract.cpp" />
@@ -1508,6 +1524,23 @@
     <ClInclude Include="SourceFiles\inline_bots\inline_bot_layout_item.h" />
     <ClInclude Include="SourceFiles\inline_bots\inline_bot_result.h" />
     <ClInclude Include="SourceFiles\inline_bots\inline_bot_send_data.h" />
+    <CustomBuild Include="SourceFiles\media\media_clip_reader.h">
+      <AdditionalInputs Condition="'$(Configuration)|$(Platform)'=='Deploy|Win32'">$(QTDIR)\bin\moc.exe;%(FullPath)</AdditionalInputs>
+      <Message Condition="'$(Configuration)|$(Platform)'=='Deploy|Win32'">Moc%27ing media_clip_reader.h...</Message>
+      <Outputs Condition="'$(Configuration)|$(Platform)'=='Deploy|Win32'">.\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp</Outputs>
+      <Command Condition="'$(Configuration)|$(Platform)'=='Deploy|Win32'">"$(QTDIR)\bin\moc.exe"  "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" "-fstdafx.h" "-f../../SourceFiles/media/media_clip_reader.h"  -DAL_LIBTYPE_STATIC -DCUSTOM_API_ID -DUNICODE -DWIN32 -DWIN64 -DHAVE_STDINT_H -DZLIB_WINAPI -DQT_NO_DEBUG -DNDEBUG -D_SCL_SECURE_NO_WARNINGS "-I.\SourceFiles" "-I.\GeneratedFiles" "-I.\GeneratedFiles\$(ConfigurationName)\." "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtCore\5.6.0\QtCore" "-I$(QTDIR)\include\QtGui\5.6.0\QtGui" "-I.\..\..\Libraries\breakpad\src" "-I.\..\..\Libraries\lzma\C" "-I.\..\..\Libraries\libexif-0.6.20" "-I.\..\..\Libraries\zlib-1.2.8" "-I.\..\..\Libraries\ffmpeg" "-I.\..\..\Libraries\openal-soft\include" "-I.\ThirdParty\minizip" "-I.\..\..\Libraries\openssl\Release\include"</Command>
+      <AdditionalInputs Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">$(QTDIR)\bin\moc.exe;%(FullPath)</AdditionalInputs>
+      <Message Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">Moc%27ing media_clip_reader.h...</Message>
+      <Outputs Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">.\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp</Outputs>
+      <Command Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">"$(QTDIR)\bin\moc.exe"  "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" "-fstdafx.h" "-f../../SourceFiles/media/media_clip_reader.h"  -DAL_LIBTYPE_STATIC -DUNICODE -DWIN32 -DWIN64 -DHAVE_STDINT_H -DZLIB_WINAPI -D_SCL_SECURE_NO_WARNINGS "-I.\SourceFiles" "-I.\GeneratedFiles" "-I.\GeneratedFiles\$(ConfigurationName)\." "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtCore\5.6.0\QtCore" "-I$(QTDIR)\include\QtGui\5.6.0\QtGui" "-I.\..\..\Libraries\breakpad\src" "-I.\..\..\Libraries\lzma\C" "-I.\..\..\Libraries\libexif-0.6.20" "-I.\..\..\Libraries\zlib-1.2.8" "-I.\..\..\Libraries\ffmpeg" "-I.\..\..\Libraries\openal-soft\include" "-I.\ThirdParty\minizip" "-I.\..\..\Libraries\openssl_debug\Debug\include"</Command>
+      <AdditionalInputs Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">$(QTDIR)\bin\moc.exe;%(FullPath)</AdditionalInputs>
+      <Message Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">Moc%27ing media_clip_reader.h...</Message>
+      <Outputs Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">.\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp</Outputs>
+      <Command Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">"$(QTDIR)\bin\moc.exe"  "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" "-fstdafx.h" "-f../../SourceFiles/media/media_clip_reader.h"  -DAL_LIBTYPE_STATIC -DUNICODE -DWIN32 -DWIN64 -DHAVE_STDINT_H -DZLIB_WINAPI -DQT_NO_DEBUG -DNDEBUG -D_SCL_SECURE_NO_WARNINGS "-I.\SourceFiles" "-I.\GeneratedFiles" "-I.\GeneratedFiles\$(ConfigurationName)\." "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtCore\5.6.0\QtCore" "-I$(QTDIR)\include\QtGui\5.6.0\QtGui" "-I.\..\..\Libraries\breakpad\src" "-I.\..\..\Libraries\lzma\C" "-I.\..\..\Libraries\libexif-0.6.20" "-I.\..\..\Libraries\zlib-1.2.8" "-I.\..\..\Libraries\ffmpeg" "-I.\..\..\Libraries\openal-soft\include" "-I.\ThirdParty\minizip" "-I.\..\..\Libraries\openssl\Release\include"</Command>
+    </CustomBuild>
+    <ClInclude Include="SourceFiles\media\media_clip_ffmpeg.h" />
+    <ClInclude Include="SourceFiles\media\media_clip_implementation.h" />
+    <ClInclude Include="SourceFiles\media\media_clip_qtgif.h" />
     <ClInclude Include="SourceFiles\mtproto\auth_key.h" />
     <CustomBuild Include="SourceFiles\mtproto\connection.h">
       <AdditionalInputs Condition="'$(Configuration)|$(Platform)'=='Deploy|Win32'">$(QTDIR)\bin\moc.exe;%(FullPath)</AdditionalInputs>
diff --git a/Telegram/Telegram.vcxproj.filters b/Telegram/Telegram.vcxproj.filters
index 2d1a1f393..64116e19b 100644
--- a/Telegram/Telegram.vcxproj.filters
+++ b/Telegram/Telegram.vcxproj.filters
@@ -112,6 +112,9 @@
     <Filter Include="SourceFiles\platform\winrt">
       <UniqueIdentifier>{385d4cd5-f702-41b7-9e39-707d16b118d5}</UniqueIdentifier>
     </Filter>
+    <Filter Include="SourceFiles\media">
+      <UniqueIdentifier>{a281888a-8b70-4e95-9b03-ebcb02837df4}</UniqueIdentifier>
+    </Filter>
   </ItemGroup>
   <ItemGroup>
     <ClCompile Include="SourceFiles\main.cpp">
@@ -1344,6 +1347,27 @@
     <ClCompile Include="SourceFiles\platform\linux\linux_libs.cpp">
       <Filter>SourceFiles\platform\linux</Filter>
     </ClCompile>
+    <ClCompile Include="GeneratedFiles\Deploy\moc_media_clip_reader.cpp">
+      <Filter>GeneratedFiles\Deploy</Filter>
+    </ClCompile>
+    <ClCompile Include="GeneratedFiles\Debug\moc_media_clip_reader.cpp">
+      <Filter>GeneratedFiles\Debug</Filter>
+    </ClCompile>
+    <ClCompile Include="GeneratedFiles\Release\moc_media_clip_reader.cpp">
+      <Filter>GeneratedFiles\Release</Filter>
+    </ClCompile>
+    <ClCompile Include="SourceFiles\media\media_clip_reader.cpp">
+      <Filter>SourceFiles\media</Filter>
+    </ClCompile>
+    <ClCompile Include="SourceFiles\media\media_clip_implementation.cpp">
+      <Filter>SourceFiles\media</Filter>
+    </ClCompile>
+    <ClCompile Include="SourceFiles\media\media_clip_ffmpeg.cpp">
+      <Filter>SourceFiles\media</Filter>
+    </ClCompile>
+    <ClCompile Include="SourceFiles\media\media_clip_qtgif.cpp">
+      <Filter>SourceFiles\media</Filter>
+    </ClCompile>
   </ItemGroup>
   <ItemGroup>
     <ClInclude Include="SourceFiles\stdafx.h">
@@ -1595,6 +1619,15 @@
     <ClInclude Include="SourceFiles\platform\linux\linux_libs.h">
       <Filter>SourceFiles\platform\linux</Filter>
     </ClInclude>
+    <ClInclude Include="SourceFiles\media\media_clip_implementation.h">
+      <Filter>SourceFiles\media</Filter>
+    </ClInclude>
+    <ClInclude Include="SourceFiles\media\media_clip_ffmpeg.h">
+      <Filter>SourceFiles\media</Filter>
+    </ClInclude>
+    <ClInclude Include="SourceFiles\media\media_clip_qtgif.h">
+      <Filter>SourceFiles\media</Filter>
+    </ClInclude>
   </ItemGroup>
   <ItemGroup>
     <CustomBuild Include="SourceFiles\application.h">
@@ -1885,6 +1918,9 @@
     <CustomBuild Include="SourceFiles\ui\inner_dropdown.h">
       <Filter>SourceFiles\ui</Filter>
     </CustomBuild>
+    <CustomBuild Include="SourceFiles\media\media_clip_reader.h">
+      <Filter>SourceFiles\media</Filter>
+    </CustomBuild>
   </ItemGroup>
   <ItemGroup>
     <None Include="Resources\langs\lang_it.strings">