mirror of https://github.com/procxx/kepka.git
				
				
				
			ClipReader was moved to a separate namespace and different files.
This commit is contained in:
		
							parent
							
								
									8fc38d9ac2
								
							
						
					
					
						commit
						d64892584d
					
				| 
						 | 
				
			
			@ -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);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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();
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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 {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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();
 | 
			
		||||
		}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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();
 | 
			
		||||
		}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			@ -26,8 +26,10 @@ 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:
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -70,7 +72,8 @@ namespace {
 | 
			
		|||
	}
 | 
			
		||||
	return false;
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
} // namespace
 | 
			
		||||
 | 
			
		||||
MediaView::MediaView() : TWidget(App::wnd())
 | 
			
		||||
, _animStarted(getms())
 | 
			
		||||
| 
						 | 
				
			
			@ -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()) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| 
						 | 
				
			
			@ -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);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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">
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue