sending gifv done

This commit is contained in:
John Preston 2015-12-28 18:34:45 +03:00
parent d966eebb7c
commit 2e853f9338
11 changed files with 140 additions and 34 deletions

View File

@ -938,7 +938,7 @@ namespace App {
if (!item->toHistoryForwarded() && item->out()) { if (!item->toHistoryForwarded() && item->out()) {
if (HistoryMedia *media = item->getMedia()) { if (HistoryMedia *media = item->getMedia()) {
if (DocumentData *doc = media->getDocument()) { if (DocumentData *doc = media->getDocument()) {
if (doc->type == AnimatedDocument && doc->mime.toLower() == qstr("video/mp4")) { if (doc->isGifv()) {
addSavedGif(doc); addSavedGif(doc);
} }
} }
@ -1623,6 +1623,9 @@ namespace App {
} }
convert->id = document; convert->id = document;
convert->status = FileReady; convert->status = FileReady;
if (cSavedGifs().indexOf(convert) >= 0) { // id changed
Local::writeSavedGifs();
}
sentSticker = !!convert->sticker(); sentSticker = !!convert->sticker();
} }
convert->access = access; convert->access = access;

View File

@ -23,7 +23,7 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org
static const int32 AppVersion = 9015; static const int32 AppVersion = 9015;
static const wchar_t *AppVersionStr = L"0.9.15"; static const wchar_t *AppVersionStr = L"0.9.15";
static const bool DevVersion = false; 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 *AppNameOld = L"Telegram Win (Unofficial)";
static const wchar_t *AppName = L"Telegram Desktop"; static const wchar_t *AppName = L"Telegram Desktop";

View File

@ -358,13 +358,20 @@ ClipReader::~ClipReader() {
class ClipReaderImplementation { class ClipReaderImplementation {
public: 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 bool readNextFrame(QImage &to, bool &hasAlpha, const QSize &size) = 0;
virtual int32 nextFrameDelay() = 0; virtual int32 nextFrameDelay() = 0;
virtual bool start() = 0; virtual bool start(bool onlyGifv) = 0;
virtual ~ClipReaderImplementation() { virtual ~ClipReaderImplementation() {
} }
int64 dataSize() const {
return _dataSize;
}
protected: protected:
FileLocation *_location; FileLocation *_location;
@ -372,14 +379,17 @@ protected:
QFile _file; QFile _file;
QBuffer _buffer; QBuffer _buffer;
QIODevice *_device; QIODevice *_device;
int64 _dataSize;
void initDevice() { void initDevice() {
if (_data->isEmpty()) { if (_data->isEmpty()) {
if (_file.isOpen()) _file.close(); if (_file.isOpen()) _file.close();
_file.setFileName(_location->name()); _file.setFileName(_location->name());
_dataSize = _file.size();
} else { } else {
if (_buffer.isOpen()) _buffer.close(); if (_buffer.isOpen()) _buffer.close();
_buffer.setBuffer(_data); _buffer.setBuffer(_data);
_dataSize = _data->size();
} }
_device = _data->isEmpty() ? static_cast<QIODevice*>(&_file) : static_cast<QIODevice*>(&_buffer); _device = _data->isEmpty() ? static_cast<QIODevice*>(&_file) : static_cast<QIODevice*>(&_buffer);
} }
@ -432,7 +442,8 @@ public:
return _frameDelay; return _frameDelay;
} }
bool start() { bool start(bool onlyGifv) {
if (onlyGifv) return false;
return jumpToStart(); return jumpToStart();
} }
@ -632,7 +643,7 @@ public:
return qsl("for file '%1', data size '%2'").arg(_location ? _location->name() : QString()).arg(_data->size()); return qsl("for file '%1', data size '%2'").arg(_location ? _location->name() : QString()).arg(_data->size());
} }
bool start() { bool start(bool onlyGifv) {
initDevice(); initDevice();
if (!_device->open(QIODevice::ReadOnly)) { if (!_device->open(QIODevice::ReadOnly)) {
LOG(("Gif Error: Unable to open device %1").arg(logData())); 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 // Get a pointer to the codec context for the audio stream
_codecContext = _fmtContext->streams[_streamId]->codec; _codecContext = _fmtContext->streams[_streamId]->codec;
_codec = avcodec_find_decoder(_codecContext->codec_id); _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); av_opt_set_int(_codecContext, "refcounted_frames", 1, 0);
if ((res = avcodec_open2(_codecContext, _codec, 0)) < 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))); 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; 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() { ~FFMpegReaderImplementation() {
if (_ioContext) av_free(_ioContext); if (_ioContext) av_free(_ioContext);
if (_codecContext) avcodec_close(_codecContext); if (_codecContext) avcodec_close(_codecContext);
@ -864,7 +892,7 @@ public:
_implementation = new FFMpegReaderImplementation(_location, &_data); _implementation = new FFMpegReaderImplementation(_location, &_data);
// _implementation = new QtGifReaderImplementation(_location, &_data); // _implementation = new QtGifReaderImplementation(_location, &_data);
return _implementation->start(); return _implementation->start(false);
} }
ClipProcessResult error() { ClipProcessResult error() {
@ -1108,3 +1136,30 @@ void ClipReadManager::clear() {
ClipReadManager::~ClipReadManager() { ClipReadManager::~ClipReadManager() {
clear(); 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));
}

View File

@ -646,3 +646,5 @@ private:
bool _needReProcess; bool _needReProcess;
}; };
MTPDocumentAttribute clipReadAnimatedAttributes(const QString &fname, const QByteArray &data, QImage &cover);

View File

@ -944,7 +944,7 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
if (doc->loading()) { if (doc->loading()) {
_menu->addAction(lang(lng_context_cancel_download), this, SLOT(cancelContextDownload()))->setEnabled(true); _menu->addAction(lang(lng_context_cancel_download), this, SLOT(cancelContextDownload()))->setEnabled(true);
} else { } 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); _menu->addAction(lang(lng_context_save_gif), this, SLOT(saveContextGif()))->setEnabled(true);
} }
if (!doc->already(true).isEmpty()) { if (!doc->already(true).isEmpty()) {
@ -3181,10 +3181,8 @@ void HistoryWidget::savedGifsGot(const MTPmessages_SavedGifs &gifs) {
} }
void HistoryWidget::saveGif(DocumentData *doc) { void HistoryWidget::saveGif(DocumentData *doc) {
if (doc->mime.toLower() == qstr("video/mp4") && doc->type == AnimatedDocument) { if (doc->isGifv() && cSavedGifs().indexOf(doc) != 0) {
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));
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<MTPDocumentAttribute> _composeDocumentAttributes(DocumentData *document) { MTPVector<MTPDocumentAttribute> _composeDocumentAttributes(DocumentData *document) {
QVector<MTPDocumentAttribute> attributes(1, MTP_documentAttributeFilename(MTP_string(document->name))); QVector<MTPDocumentAttribute> attributes(1, MTP_documentAttributeFilename(MTP_string(document->name)));
if (document->dimensions.width() > 0 && document->dimensions.height() > 0) { 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) { if (document->type == AnimatedDocument) {
attributes.push_back(MTP_documentAttributeAnimated()); attributes.push_back(MTP_documentAttributeAnimated());

View File

@ -294,13 +294,12 @@ void FileLoadTask::process() {
MTPDocument document(MTP_documentEmpty(MTP_long(0))); MTPDocument document(MTP_documentEmpty(MTP_long(0)));
MTPAudio audio(MTP_audioEmpty(MTP_long(0))); MTPAudio audio(MTP_audioEmpty(MTP_long(0)));
bool song = false; bool song = false, gif = false;
if (_type != PrepareAudio) { if (_type != PrepareAudio) {
if (filemime == qstr("audio/mp3") || filemime == qstr("audio/m4a") || filemime == qstr("audio/aac") || filemime == qstr("audio/ogg") || filemime == qstr("audio/flac") || 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(".mp3"), Qt::CaseInsensitive) || filename.endsWith(qstr(".m4a"), Qt::CaseInsensitive) ||
filename.endsWith(qstr(".aac"), Qt::CaseInsensitive) || filename.endsWith(qstr(".ogg"), Qt::CaseInsensitive) || filename.endsWith(qstr(".aac"), Qt::CaseInsensitive) || filename.endsWith(qstr(".ogg"), Qt::CaseInsensitive) ||
filename.endsWith(qstr(".flac"), Qt::CaseInsensitive)) { filename.endsWith(qstr(".flac"), Qt::CaseInsensitive)) {
QImage cover; QImage cover;
QByteArray coverBytes, coverFormat; QByteArray coverBytes, coverFormat;
MTPDocumentAttribute audioAttribute = audioReadSongAttributes(_filepath, _content, cover, 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<uint64>();
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(); int32 w = fullimage.width(), h = fullimage.height();
attributes.push_back(MTP_documentAttributeImageSize(MTP_int(w), MTP_int(h))); attributes.push_back(MTP_documentAttributeImageSize(MTP_int(w), MTP_int(h)));

View File

@ -552,7 +552,8 @@ namespace {
lskStickers = 0x0b, // no data lskStickers = 0x0b, // no data
lskSavedPeers = 0x0c, // no data lskSavedPeers = 0x0c, // no data
lskReportSpamStatuses = 0x0d, // no data lskReportSpamStatuses = 0x0d, // no data
lskSavedGifs = 0x0e, lskSavedGifsOld = 0x0e,
lskSavedGifs = 0x0f,
}; };
typedef QMap<PeerId, FileKey> DraftsMap; typedef QMap<PeerId, FileKey> DraftsMap;
@ -569,7 +570,7 @@ namespace {
FileLocationAliases _fileLocationAliases; FileLocationAliases _fileLocationAliases;
FileKey _locationsKey = 0, _reportSpamStatusesKey = 0; FileKey _locationsKey = 0, _reportSpamStatusesKey = 0;
FileKey _recentStickersKeyOld = 0, _stickersKey = 0, _savedGifsKey; FileKey _recentStickersKeyOld = 0, _stickersKey = 0, _savedGifsKey = 0;
FileKey _backgroundKey = 0; FileKey _backgroundKey = 0;
bool _backgroundWasRead = false; bool _backgroundWasRead = false;
@ -1720,6 +1721,10 @@ namespace {
case lskStickers: { case lskStickers: {
map.stream >> stickersKey; map.stream >> stickersKey;
} break; } break;
case lskSavedGifsOld: {
quint64 key;
map.stream >> key;
} break;
case lskSavedGifs: { case lskSavedGifs: {
map.stream >> savedGifsKey; map.stream >> savedGifsKey;
} break; } break;
@ -2974,8 +2979,8 @@ namespace Local {
for (SavedGifs::const_iterator i = saved.cbegin(), e = saved.cend(); i != e; ++i) { for (SavedGifs::const_iterator i = saved.cbegin(), e = saved.cend(); i != e; ++i) {
DocumentData *doc = *i; DocumentData *doc = *i;
// id + access + date + namelen + name + mimelen + mime + dc + size + width + height + type // 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); 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 // thumb
size += _storageImageLocationSize(); size += _storageImageLocationSize();
@ -2991,7 +2996,7 @@ namespace Local {
for (SavedGifs::const_iterator i = saved.cbegin(), e = saved.cend(); i != e; ++i) { for (SavedGifs::const_iterator i = saved.cbegin(), e = saved.cend(); i != e; ++i) {
DocumentData *doc = *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()); _writeStorageImageLocation(data.stream, doc->thumb->location());
} }
FileWriteDescriptor file(_savedGifsKey); FileWriteDescriptor file(_savedGifsKey);
@ -3020,8 +3025,8 @@ namespace Local {
for (uint32 i = 0; i < cnt; ++i) { for (uint32 i = 0; i < cnt; ++i) {
quint64 id, access; quint64 id, access;
QString name, mime; QString name, mime;
qint32 date, 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; gifs.stream >> id >> access >> date >> name >> mime >> dc >> size >> width >> height >> type >> duration;
StorageImageLocation thumb(_readStorageImageLocation(gifs)); StorageImageLocation thumb(_readStorageImageLocation(gifs));
@ -3034,7 +3039,11 @@ namespace Local {
attributes.push_back(MTP_documentAttributeAnimated()); attributes.push_back(MTP_documentAttributeAnimated());
} }
if (width > 0 && height > 0) { 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); DocumentData *doc = App::documentSet(id, 0, access, date, attributes, mime, thumb.isNull() ? ImagePtr() : ImagePtr(thumb), dc, size, thumb);

View File

@ -1490,7 +1490,7 @@ DocumentData::DocumentData(const DocumentId &id, const uint64 &access, int32 dat
, status(FileReady) , status(FileReady)
, uploadOffset(0) , uploadOffset(0)
, _additional(0) , _additional(0)
, _isImage(false) , _duration(-1)
, _actionOnLoad(ActionOnLoadNone) , _actionOnLoad(ActionOnLoadNone)
, _loader(0) { , _loader(0) {
_location = Local::readFileLocation(mediaKey(DocumentFileLocation, dc, id)); _location = Local::readFileLocation(mediaKey(DocumentFileLocation, dc, id));
@ -1524,7 +1524,7 @@ void DocumentData::setattributes(const QVector<MTPDocumentAttribute> &attributes
if (type == FileDocument) { if (type == FileDocument) {
type = VideoDocument; type = VideoDocument;
} }
// duration = d.vduration.v; _duration = d.vduration.v;
dimensions = QSize(d.vw.v, d.vh.v); dimensions = QSize(d.vw.v, d.vh.v);
} break; } break;
case mtpc_documentAttributeAudio: { case mtpc_documentAttributeAudio: {
@ -1812,7 +1812,8 @@ bool fileIsImage(const QString &name, const QString &mime) {
} }
void DocumentData::recountIsImage() { void DocumentData::recountIsImage() {
_isImage = fileIsImage(name, mime); if (isAnimation() || type == VideoDocument) return;
_duration = fileIsImage(name, mime) ? 1 : -1; // hack
} }
DocumentData::~DocumentData() { DocumentData::~DocumentData() {

View File

@ -1132,8 +1132,14 @@ public:
bool isAnimation() const { bool isAnimation() const {
return (type == AnimatedDocument) || !mime.compare(qstr("image/gif"), Qt::CaseInsensitive); 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 { bool isImage() const {
return _isImage; return !isAnimation() && (type != VideoDocument) && (_duration > 0);
} }
void recountIsImage(); void recountIsImage();
@ -1159,7 +1165,7 @@ private:
FileLocation _location; FileLocation _location;
QByteArray _data; QByteArray _data;
DocumentAdditionalData *_additional; DocumentAdditionalData *_additional;
bool _isImage; int32 _duration;
ActionOnLoad _actionOnLoad; ActionOnLoad _actionOnLoad;
FullMsgId _actionOnLoadMsgId; FullMsgId _actionOnLoadMsgId;

View File

@ -34,8 +34,8 @@ IDI_ICON1 ICON "SourceFiles\\art\\icon256.ico"
// //
VS_VERSION_INFO VERSIONINFO VS_VERSION_INFO VERSIONINFO
FILEVERSION 0,9,15,4 FILEVERSION 0,9,15,5
PRODUCTVERSION 0,9,15,4 PRODUCTVERSION 0,9,15,5
FILEFLAGSMASK 0x3fL FILEFLAGSMASK 0x3fL
#ifdef _DEBUG #ifdef _DEBUG
FILEFLAGS 0x1L FILEFLAGS 0x1L
@ -51,10 +51,10 @@ BEGIN
BLOCK "040904b0" BLOCK "040904b0"
BEGIN BEGIN
VALUE "CompanyName", "Telegram Messenger LLP" VALUE "CompanyName", "Telegram Messenger LLP"
VALUE "FileVersion", "0.9.15.4" VALUE "FileVersion", "0.9.15.5"
VALUE "LegalCopyright", "Copyright (C) 2013" VALUE "LegalCopyright", "Copyright (C) 2013"
VALUE "ProductName", "Telegram Desktop" VALUE "ProductName", "Telegram Desktop"
VALUE "ProductVersion", "0.9.15.4" VALUE "ProductVersion", "0.9.15.5"
END END
END END
BLOCK "VarFileInfo" BLOCK "VarFileInfo"

View File

@ -3,4 +3,4 @@ AppVersionStrMajor 0.9
AppVersionStrSmall 0.9.15 AppVersionStrSmall 0.9.15
AppVersionStr 0.9.15 AppVersionStr 0.9.15
DevChannel 0 DevChannel 0
BetaVersion 9015004 BetaVersion 9015005