diff --git a/Telegram/SourceFiles/app.cpp b/Telegram/SourceFiles/app.cpp index 677da9d2d..984630f1b 100644 --- a/Telegram/SourceFiles/app.cpp +++ b/Telegram/SourceFiles/app.cpp @@ -938,7 +938,7 @@ namespace App { if (!item->toHistoryForwarded() && item->out()) { if (HistoryMedia *media = item->getMedia()) { if (DocumentData *doc = media->getDocument()) { - if (doc->type == AnimatedDocument && doc->mime.toLower() == qstr("video/mp4")) { + if (doc->isGifv()) { addSavedGif(doc); } } @@ -1623,6 +1623,9 @@ namespace App { } convert->id = document; convert->status = FileReady; + if (cSavedGifs().indexOf(convert) >= 0) { // id changed + Local::writeSavedGifs(); + } sentSticker = !!convert->sticker(); } convert->access = access; diff --git a/Telegram/SourceFiles/config.h b/Telegram/SourceFiles/config.h index 718d2fb87..2f0b05474 100644 --- a/Telegram/SourceFiles/config.h +++ b/Telegram/SourceFiles/config.h @@ -23,7 +23,7 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org static const int32 AppVersion = 9015; static const wchar_t *AppVersionStr = L"0.9.15"; static const bool DevVersion = false; -#define BETA_VERSION (9015004ULL) // just comment this line to build public version +#define BETA_VERSION (9015005ULL) // just comment this line to build public version static const wchar_t *AppNameOld = L"Telegram Win (Unofficial)"; static const wchar_t *AppName = L"Telegram Desktop"; diff --git a/Telegram/SourceFiles/gui/animation.cpp b/Telegram/SourceFiles/gui/animation.cpp index dc442243b..5968718b9 100644 --- a/Telegram/SourceFiles/gui/animation.cpp +++ b/Telegram/SourceFiles/gui/animation.cpp @@ -358,13 +358,20 @@ ClipReader::~ClipReader() { class ClipReaderImplementation { public: - ClipReaderImplementation(FileLocation *location, QByteArray *data) : _location(location), _data(data), _device(0) { + ClipReaderImplementation(FileLocation *location, QByteArray *data) + : _location(location) + , _data(data) + , _device(0) + , _dataSize(0) { } virtual bool readNextFrame(QImage &to, bool &hasAlpha, const QSize &size) = 0; virtual int32 nextFrameDelay() = 0; - virtual bool start() = 0; + virtual bool start(bool onlyGifv) = 0; virtual ~ClipReaderImplementation() { } + int64 dataSize() const { + return _dataSize; + } protected: FileLocation *_location; @@ -372,14 +379,17 @@ protected: QFile _file; QBuffer _buffer; QIODevice *_device; + int64 _dataSize; void initDevice() { if (_data->isEmpty()) { if (_file.isOpen()) _file.close(); _file.setFileName(_location->name()); + _dataSize = _file.size(); } else { if (_buffer.isOpen()) _buffer.close(); _buffer.setBuffer(_data); + _dataSize = _data->size(); } _device = _data->isEmpty() ? static_cast(&_file) : static_cast(&_buffer); } @@ -432,7 +442,8 @@ public: return _frameDelay; } - bool start() { + bool start(bool onlyGifv) { + if (onlyGifv) return false; return jumpToStart(); } @@ -632,7 +643,7 @@ public: return qsl("for file '%1', data size '%2'").arg(_location ? _location->name() : QString()).arg(_data->size()); } - bool start() { + bool start(bool onlyGifv) { initDevice(); if (!_device->open(QIODevice::ReadOnly)) { LOG(("Gif Error: Unable to open device %1").arg(logData())); @@ -671,6 +682,18 @@ public: // Get a pointer to the codec context for the audio stream _codecContext = _fmtContext->streams[_streamId]->codec; _codec = avcodec_find_decoder(_codecContext->codec_id); + + if (onlyGifv) { + if (av_find_best_stream(_fmtContext, AVMEDIA_TYPE_AUDIO, -1, -1, 0, 0) >= 0) { // should be no audio stream + return false; + } + if (dataSize() > AnimationInMemory) { + return false; + } + if (_codecContext->codec_id != AV_CODEC_ID_H264) { + return false; + } + } av_opt_set_int(_codecContext, "refcounted_frames", 1, 0); if ((res = avcodec_open2(_codecContext, _codec, 0)) < 0) { LOG(("Gif Error: Unable to avcodec_open2 %1, error %2, %3").arg(logData()).arg(res).arg(av_make_error_string(err, sizeof(err), res))); @@ -680,6 +703,11 @@ public: return true; } + int32 duration() const { + if (_fmtContext->streams[_streamId]->duration == AV_NOPTS_VALUE) return 0; + return (_fmtContext->streams[_streamId]->duration * _fmtContext->streams[_streamId]->time_base.num) / _fmtContext->streams[_streamId]->time_base.den; + } + ~FFMpegReaderImplementation() { if (_ioContext) av_free(_ioContext); if (_codecContext) avcodec_close(_codecContext); @@ -864,7 +892,7 @@ public: _implementation = new FFMpegReaderImplementation(_location, &_data); // _implementation = new QtGifReaderImplementation(_location, &_data); - return _implementation->start(); + return _implementation->start(false); } ClipProcessResult error() { @@ -1108,3 +1136,30 @@ void ClipReadManager::clear() { ClipReadManager::~ClipReadManager() { clear(); } + +MTPDocumentAttribute clipReadAnimatedAttributes(const QString &fname, const QByteArray &data, QImage &cover) { + FileLocation localloc(StorageFilePartial, fname); + QByteArray localdata(data); + + FFMpegReaderImplementation *reader = new FFMpegReaderImplementation(&localloc, &localdata); + if (reader->start(true)) { + bool hasAlpha = false; + if (reader->readNextFrame(cover, hasAlpha, QSize())) { + if (cover.width() > 0 && cover.height() > 0 && cover.width() < cover.height() * 10 && cover.height() < cover.width() * 10) { + if (hasAlpha) { + QImage cache; + ClipFrameRequest request; + request.framew = request.outerw = cover.width(); + request.frameh = request.outerh = cover.height(); + request.factor = 1; + cover = _prepareFrame(request, cover, cache, hasAlpha).toImage(); + } + int32 duration = reader->duration(); + delete reader; + return MTP_documentAttributeVideo(MTP_int(duration), MTP_int(cover.width()), MTP_int(cover.height())); + } + } + } + delete reader; + return MTP_documentAttributeFilename(MTP_string(fname)); +} diff --git a/Telegram/SourceFiles/gui/animation.h b/Telegram/SourceFiles/gui/animation.h index 60c4a1cfc..59c948bf2 100644 --- a/Telegram/SourceFiles/gui/animation.h +++ b/Telegram/SourceFiles/gui/animation.h @@ -646,3 +646,5 @@ private: bool _needReProcess; }; + +MTPDocumentAttribute clipReadAnimatedAttributes(const QString &fname, const QByteArray &data, QImage &cover); diff --git a/Telegram/SourceFiles/historywidget.cpp b/Telegram/SourceFiles/historywidget.cpp index 50d62fb1a..3d50f141c 100644 --- a/Telegram/SourceFiles/historywidget.cpp +++ b/Telegram/SourceFiles/historywidget.cpp @@ -944,7 +944,7 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) { if (doc->loading()) { _menu->addAction(lang(lng_context_cancel_download), this, SLOT(cancelContextDownload()))->setEnabled(true); } else { - if (doc->mime.toLower() == qstr("video/mp4") && doc->type == AnimatedDocument) { + if (doc->isGifv()) { _menu->addAction(lang(lng_context_save_gif), this, SLOT(saveContextGif()))->setEnabled(true); } if (!doc->already(true).isEmpty()) { @@ -3181,10 +3181,8 @@ void HistoryWidget::savedGifsGot(const MTPmessages_SavedGifs &gifs) { } void HistoryWidget::saveGif(DocumentData *doc) { - if (doc->mime.toLower() == qstr("video/mp4") && doc->type == AnimatedDocument) { - if (cSavedGifs().indexOf(doc) != 0) { - MTP::send(MTPmessages_SaveGif(MTP_inputDocument(MTP_long(doc->id), MTP_long(doc->access)), MTP_bool(false)), rpcDone(&HistoryWidget::saveGifDone, doc)); - } + if (doc->isGifv() && cSavedGifs().indexOf(doc) != 0) { + MTP::send(MTPmessages_SaveGif(MTP_inputDocument(MTP_long(doc->id), MTP_long(doc->access)), MTP_bool(false)), rpcDone(&HistoryWidget::saveGifDone, doc)); } } @@ -5468,7 +5466,12 @@ namespace { MTPVector _composeDocumentAttributes(DocumentData *document) { QVector attributes(1, MTP_documentAttributeFilename(MTP_string(document->name))); if (document->dimensions.width() > 0 && document->dimensions.height() > 0) { - attributes.push_back(MTP_documentAttributeImageSize(MTP_int(document->dimensions.width()), MTP_int(document->dimensions.height()))); + int32 duration = document->duration(); + if (duration >= 0) { + attributes.push_back(MTP_documentAttributeVideo(MTP_int(duration), MTP_int(document->dimensions.width()), MTP_int(document->dimensions.height()))); + } else { + attributes.push_back(MTP_documentAttributeImageSize(MTP_int(document->dimensions.width()), MTP_int(document->dimensions.height()))); + } } if (document->type == AnimatedDocument) { attributes.push_back(MTP_documentAttributeAnimated()); diff --git a/Telegram/SourceFiles/localimageloader.cpp b/Telegram/SourceFiles/localimageloader.cpp index 64ae1e592..e2596a7c2 100644 --- a/Telegram/SourceFiles/localimageloader.cpp +++ b/Telegram/SourceFiles/localimageloader.cpp @@ -294,13 +294,12 @@ void FileLoadTask::process() { MTPDocument document(MTP_documentEmpty(MTP_long(0))); MTPAudio audio(MTP_audioEmpty(MTP_long(0))); - bool song = false; + bool song = false, gif = false; if (_type != PrepareAudio) { if (filemime == qstr("audio/mp3") || filemime == qstr("audio/m4a") || filemime == qstr("audio/aac") || filemime == qstr("audio/ogg") || filemime == qstr("audio/flac") || filename.endsWith(qstr(".mp3"), Qt::CaseInsensitive) || filename.endsWith(qstr(".m4a"), Qt::CaseInsensitive) || filename.endsWith(qstr(".aac"), Qt::CaseInsensitive) || filename.endsWith(qstr(".ogg"), Qt::CaseInsensitive) || filename.endsWith(qstr(".flac"), Qt::CaseInsensitive)) { - QImage cover; QByteArray coverBytes, coverFormat; MTPDocumentAttribute audioAttribute = audioReadSongAttributes(_filepath, _content, cover, coverBytes, coverFormat); @@ -327,9 +326,37 @@ void FileLoadTask::process() { } } } + if (filemime == qstr("video/mp4") || filename.endsWith(qstr(".mp4"), Qt::CaseInsensitive)) { + QImage cover; + MTPDocumentAttribute animatedAttribute = clipReadAnimatedAttributes(_filepath, _content, cover); + if (animatedAttribute.type() == mtpc_documentAttributeVideo) { + int32 cw = cover.width(), ch = cover.height(); + if (cw < 20 * ch && ch < 20 * cw) { + attributes.push_back(MTP_documentAttributeAnimated()); + attributes.push_back(animatedAttribute); + gif = true; + + QPixmap full = (cw > 90 || ch > 90) ? QPixmap::fromImage(cover.scaled(90, 90, Qt::KeepAspectRatio, Qt::SmoothTransformation), Qt::ColorOnly) : QPixmap::fromImage(cover, Qt::ColorOnly); + { + QByteArray thumbFormat = "JPG"; + int32 thumbQuality = 87; + + QBuffer buffer(&thumbdata); + full.save(&buffer, thumbFormat, thumbQuality); + } + + thumb = full; + thumbSize = MTP_photoSize(MTP_string(""), MTP_fileLocationUnavailable(MTP_long(0), MTP_int(0), MTP_long(0)), MTP_int(full.width()), MTP_int(full.height()), MTP_int(0)); + + thumbId = MTP::nonce(); + + filemime = qstr("video/mp4"); + } + } + } } - if (!fullimage.isNull() && fullimage.width() > 0 && !song) { + if (!fullimage.isNull() && fullimage.width() > 0 && !song && !gif) { int32 w = fullimage.width(), h = fullimage.height(); attributes.push_back(MTP_documentAttributeImageSize(MTP_int(w), MTP_int(h))); diff --git a/Telegram/SourceFiles/localstorage.cpp b/Telegram/SourceFiles/localstorage.cpp index 8d2d80968..5d4109c07 100644 --- a/Telegram/SourceFiles/localstorage.cpp +++ b/Telegram/SourceFiles/localstorage.cpp @@ -552,7 +552,8 @@ namespace { lskStickers = 0x0b, // no data lskSavedPeers = 0x0c, // no data lskReportSpamStatuses = 0x0d, // no data - lskSavedGifs = 0x0e, + lskSavedGifsOld = 0x0e, + lskSavedGifs = 0x0f, }; typedef QMap DraftsMap; @@ -569,7 +570,7 @@ namespace { FileLocationAliases _fileLocationAliases; FileKey _locationsKey = 0, _reportSpamStatusesKey = 0; - FileKey _recentStickersKeyOld = 0, _stickersKey = 0, _savedGifsKey; + FileKey _recentStickersKeyOld = 0, _stickersKey = 0, _savedGifsKey = 0; FileKey _backgroundKey = 0; bool _backgroundWasRead = false; @@ -1720,6 +1721,10 @@ namespace { case lskStickers: { map.stream >> stickersKey; } break; + case lskSavedGifsOld: { + quint64 key; + map.stream >> key; + } break; case lskSavedGifs: { map.stream >> savedGifsKey; } break; @@ -2974,8 +2979,8 @@ namespace Local { for (SavedGifs::const_iterator i = saved.cbegin(), e = saved.cend(); i != e; ++i) { DocumentData *doc = *i; - // id + access + date + namelen + name + mimelen + mime + dc + size + width + height + type - size += sizeof(quint64) + sizeof(quint64) + sizeof(qint32) + _stringSize(doc->name) + _stringSize(doc->mime) + sizeof(qint32) + sizeof(qint32) + sizeof(qint32) + sizeof(qint32) + sizeof(qint32); + // id + access + date + namelen + name + mimelen + mime + dc + size + width + height + type + duration + size += sizeof(quint64) + sizeof(quint64) + sizeof(qint32) + _stringSize(doc->name) + _stringSize(doc->mime) + sizeof(qint32) + sizeof(qint32) + sizeof(qint32) + sizeof(qint32) + sizeof(qint32) + sizeof(qint32); // thumb size += _storageImageLocationSize(); @@ -2991,7 +2996,7 @@ namespace Local { for (SavedGifs::const_iterator i = saved.cbegin(), e = saved.cend(); i != e; ++i) { DocumentData *doc = *i; - data.stream << quint64(doc->id) << quint64(doc->access) << qint32(doc->date) << doc->name << doc->mime << qint32(doc->dc) << qint32(doc->size) << qint32(doc->dimensions.width()) << qint32(doc->dimensions.height()) << qint32(doc->type); + data.stream << quint64(doc->id) << quint64(doc->access) << qint32(doc->date) << doc->name << doc->mime << qint32(doc->dc) << qint32(doc->size) << qint32(doc->dimensions.width()) << qint32(doc->dimensions.height()) << qint32(doc->type) << qint32(doc->duration()); _writeStorageImageLocation(data.stream, doc->thumb->location()); } FileWriteDescriptor file(_savedGifsKey); @@ -3020,8 +3025,8 @@ namespace Local { for (uint32 i = 0; i < cnt; ++i) { quint64 id, access; QString name, mime; - qint32 date, dc, size, width, height, type; - gifs.stream >> id >> access >> date >> name >> mime >> dc >> size >> width >> height >> type; + qint32 date, dc, size, width, height, type, duration; + gifs.stream >> id >> access >> date >> name >> mime >> dc >> size >> width >> height >> type >> duration; StorageImageLocation thumb(_readStorageImageLocation(gifs)); @@ -3034,7 +3039,11 @@ namespace Local { attributes.push_back(MTP_documentAttributeAnimated()); } if (width > 0 && height > 0) { - attributes.push_back(MTP_documentAttributeImageSize(MTP_int(width), MTP_int(height))); + if (duration >= 0) { + attributes.push_back(MTP_documentAttributeVideo(MTP_int(duration), MTP_int(width), MTP_int(height))); + } else { + attributes.push_back(MTP_documentAttributeImageSize(MTP_int(width), MTP_int(height))); + } } DocumentData *doc = App::documentSet(id, 0, access, date, attributes, mime, thumb.isNull() ? ImagePtr() : ImagePtr(thumb), dc, size, thumb); diff --git a/Telegram/SourceFiles/structs.cpp b/Telegram/SourceFiles/structs.cpp index b93801220..493ce9d99 100644 --- a/Telegram/SourceFiles/structs.cpp +++ b/Telegram/SourceFiles/structs.cpp @@ -1490,7 +1490,7 @@ DocumentData::DocumentData(const DocumentId &id, const uint64 &access, int32 dat , status(FileReady) , uploadOffset(0) , _additional(0) -, _isImage(false) +, _duration(-1) , _actionOnLoad(ActionOnLoadNone) , _loader(0) { _location = Local::readFileLocation(mediaKey(DocumentFileLocation, dc, id)); @@ -1524,7 +1524,7 @@ void DocumentData::setattributes(const QVector &attributes if (type == FileDocument) { type = VideoDocument; } -// duration = d.vduration.v; + _duration = d.vduration.v; dimensions = QSize(d.vw.v, d.vh.v); } break; case mtpc_documentAttributeAudio: { @@ -1812,7 +1812,8 @@ bool fileIsImage(const QString &name, const QString &mime) { } void DocumentData::recountIsImage() { - _isImage = fileIsImage(name, mime); + if (isAnimation() || type == VideoDocument) return; + _duration = fileIsImage(name, mime) ? 1 : -1; // hack } DocumentData::~DocumentData() { diff --git a/Telegram/SourceFiles/structs.h b/Telegram/SourceFiles/structs.h index 0efbce5d8..fa9d14905 100644 --- a/Telegram/SourceFiles/structs.h +++ b/Telegram/SourceFiles/structs.h @@ -1132,8 +1132,14 @@ public: bool isAnimation() const { return (type == AnimatedDocument) || !mime.compare(qstr("image/gif"), Qt::CaseInsensitive); } + bool isGifv() const { + return (type == AnimatedDocument) && !mime.compare(qstr("video/mp4"), Qt::CaseInsensitive); + } + int32 duration() const { + return (isAnimation() || type == VideoDocument) ? _duration : -1; + } bool isImage() const { - return _isImage; + return !isAnimation() && (type != VideoDocument) && (_duration > 0); } void recountIsImage(); @@ -1159,7 +1165,7 @@ private: FileLocation _location; QByteArray _data; DocumentAdditionalData *_additional; - bool _isImage; + int32 _duration; ActionOnLoad _actionOnLoad; FullMsgId _actionOnLoadMsgId; diff --git a/Telegram/Telegram.rc b/Telegram/Telegram.rc index 1392f2ddd..af8a70d02 100644 --- a/Telegram/Telegram.rc +++ b/Telegram/Telegram.rc @@ -34,8 +34,8 @@ IDI_ICON1 ICON "SourceFiles\\art\\icon256.ico" // VS_VERSION_INFO VERSIONINFO - FILEVERSION 0,9,15,4 - PRODUCTVERSION 0,9,15,4 + FILEVERSION 0,9,15,5 + PRODUCTVERSION 0,9,15,5 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -51,10 +51,10 @@ BEGIN BLOCK "040904b0" BEGIN VALUE "CompanyName", "Telegram Messenger LLP" - VALUE "FileVersion", "0.9.15.4" + VALUE "FileVersion", "0.9.15.5" VALUE "LegalCopyright", "Copyright (C) 2013" VALUE "ProductName", "Telegram Desktop" - VALUE "ProductVersion", "0.9.15.4" + VALUE "ProductVersion", "0.9.15.5" END END BLOCK "VarFileInfo" diff --git a/Telegram/Version b/Telegram/Version index 178d0d71b..06a61db83 100644 --- a/Telegram/Version +++ b/Telegram/Version @@ -3,4 +3,4 @@ AppVersionStrMajor 0.9 AppVersionStrSmall 0.9.15 AppVersionStr 0.9.15 DevChannel 0 -BetaVersion 9015004 +BetaVersion 9015005