diff --git a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGMediaVideoConverter.h b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGMediaVideoConverter.h index 6f9446c8c7..ad993b9001 100644 --- a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGMediaVideoConverter.h +++ b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGMediaVideoConverter.h @@ -20,6 +20,8 @@ + (SSignal *)convertAVAsset:(AVAsset *)avAsset adjustments:(TGMediaVideoEditAdjustments *)adjustments watcher:(TGMediaVideoFileWatcher *)watcher inhibitAudio:(bool)inhibitAudio entityRenderer:(id)entityRenderer; + (SSignal *)hashForAVAsset:(AVAsset *)avAsset adjustments:(TGMediaVideoEditAdjustments *)adjustments; ++ (SSignal *)renderUIImage:(UIImage *)image adjustments:(TGMediaVideoEditAdjustments *)adjustments watcher:(TGMediaVideoFileWatcher *)watcher entityRenderer:(id)entityRenderer; + + (NSUInteger)estimatedSizeForPreset:(TGMediaVideoConversionPreset)preset duration:(NSTimeInterval)duration hasAudio:(bool)hasAudio; + (TGMediaVideoConversionPreset)bestAvailablePresetForDimensions:(CGSize)dimensions; + (CGSize)_renderSizeWithCropSize:(CGSize)cropSize; diff --git a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGVideoEditAdjustments.h b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGVideoEditAdjustments.h index 4e6fc32d7e..0979089e32 100644 --- a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGVideoEditAdjustments.h +++ b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGVideoEditAdjustments.h @@ -1,6 +1,8 @@ #import #import +@class PGPhotoEditorValues; + typedef enum { TGMediaVideoConversionPresetCompressedDefault, @@ -29,7 +31,7 @@ typedef enum - (instancetype)editAdjustmentsWithPreset:(TGMediaVideoConversionPreset)preset maxDuration:(NSTimeInterval)maxDuration; + (instancetype)editAdjustmentsWithOriginalSize:(CGSize)originalSize preset:(TGMediaVideoConversionPreset)preset; - ++ (instancetype)editAdjustmentsWithPhotoEditorValues:(PGPhotoEditorValues *)values; + (instancetype)editAdjustmentsWithDictionary:(NSDictionary *)dictionary; + (instancetype)editAdjustmentsWithOriginalSize:(CGSize)originalSize diff --git a/submodules/LegacyComponents/Resources/LegacyComponentsResources.bundle/blank_1080p.mp4 b/submodules/LegacyComponents/Resources/LegacyComponentsResources.bundle/blank_1080p.mp4 new file mode 100644 index 0000000000..17bacbefd3 Binary files /dev/null and b/submodules/LegacyComponents/Resources/LegacyComponentsResources.bundle/blank_1080p.mp4 differ diff --git a/submodules/LegacyComponents/Sources/TGMediaAssetsController.m b/submodules/LegacyComponents/Sources/TGMediaAssetsController.m index 5ec5042c07..094e30a538 100644 --- a/submodules/LegacyComponents/Sources/TGMediaAssetsController.m +++ b/submodules/LegacyComponents/Sources/TGMediaAssetsController.m @@ -821,10 +821,48 @@ NSMutableDictionary *dict = [[NSMutableDictionary alloc] init]; dict[@"type"] = @"editedPhoto"; dict[@"image"] = image; - if (adjustments.paintingData.stickers.count > 0) dict[@"stickers"] = adjustments.paintingData.stickers; + bool animated = false; + for (TGPhotoPaintEntity *entity in adjustments.paintingData.entities) { + if (entity.animated) { + animated = true; + break; + } + } + + if (animated) { + dict[@"isAnimation"] = @true; + if ([adjustments isKindOfClass:[PGPhotoEditorValues class]]) { + dict[@"adjustments"] = [TGVideoEditAdjustments editAdjustmentsWithPhotoEditorValues:(PGPhotoEditorValues *)adjustments]; + } else { + dict[@"adjustments"] = adjustments; + } + + NSString *filePath = [NSTemporaryDirectory() stringByAppendingPathComponent:[[NSString alloc] initWithFormat:@"gifvideo_%x.jpg", (int)arc4random()]]; + NSData *data = UIImageJPEGRepresentation(image, 0.8); + [data writeToFile:filePath atomically:true]; + dict[@"url"] = [NSURL fileURLWithPath:filePath]; + + + if ([adjustments cropAppliedForAvatar:false] || adjustments.hasPainting || adjustments.toolsApplied) + { + CGRect scaledCropRect = CGRectMake(adjustments.cropRect.origin.x * image.size.width / adjustments.originalSize.width, adjustments.cropRect.origin.y * image.size.height / adjustments.originalSize.height, adjustments.cropRect.size.width * image.size.width / adjustments.originalSize.width, adjustments.cropRect.size.height * image.size.height / adjustments.originalSize.height); + UIImage *paintingImage = adjustments.paintingData.stillImage; + if (paintingImage == nil) { + paintingImage = adjustments.paintingData.image; + } + if (adjustments.toolsApplied) { + image = [PGPhotoEditor resultImageForImage:image adjustments:adjustments]; + } + UIImage *thumbnailImage = TGPhotoEditorCrop(image, paintingImage, adjustments.cropOrientation, 0, scaledCropRect, adjustments.cropMirrored, TGScaleToFill(asset.dimensions, CGSizeMake(384, 384)), asset.dimensions, true); + if (thumbnailImage != nil) { + dict[@"previewImage"] = thumbnailImage; + } + } + } + if (timer != nil) dict[@"timer"] = timer; else if (groupedId != nil && !hasAnyTimers) @@ -902,7 +940,7 @@ CGSize imageSize = TGFillSize(asset.dimensions, CGSizeMake(384, 384)); return [[TGMediaAssetImageSignals videoThumbnailForAVAsset:avAsset size:imageSize timestamp:CMTimeMakeWithSeconds(adjustments.trimStartValue, NSEC_PER_SEC)] map:^UIImage *(UIImage *image) { - return cropVideoThumbnail(image, TGScaleToFill(asset.dimensions, CGSizeMake(256, 256)), asset.dimensions, true); + return cropVideoThumbnail(image, TGScaleToFill(asset.dimensions, CGSizeMake(384, 384)), asset.dimensions, true); }]; }]; diff --git a/submodules/LegacyComponents/Sources/TGMediaVideoConverter.m b/submodules/LegacyComponents/Sources/TGMediaVideoConverter.m index 052c8f7a0a..07ae33cc5a 100644 --- a/submodules/LegacyComponents/Sources/TGMediaVideoConverter.m +++ b/submodules/LegacyComponents/Sources/TGMediaVideoConverter.m @@ -42,6 +42,7 @@ @property (nonatomic, readonly) bool succeed; - (instancetype)initWithAssetReaderOutput:(AVAssetReaderOutput *)assetReaderOutput assetWriterInput:(AVAssetWriterInput *)assetWriterInput; +- (instancetype)initWithUIImage:(UIImage *)image duration:(NSTimeInterval)duration assetWriterInput:(AVAssetWriterInput *)assetWriterInput; - (void)startWithTimeRange:(CMTimeRange)timeRange progressBlock:(void (^)(CGFloat progress))progressBlock completionBlock:(void (^)(void))completionBlock; - (void)cancel; @@ -153,7 +154,7 @@ } } - if (![self setupAssetReaderWriterForItem:avAsset outputURL:outputUrl preset:preset entityRenderer:entityRenderer adjustments:adjustments inhibitAudio:inhibitAudio conversionContext:context error:&error]) + if (![self setupAssetReaderWriterForAVAsset:avAsset image:nil outputURL:outputUrl preset:preset entityRenderer:entityRenderer adjustments:adjustments inhibitAudio:inhibitAudio conversionContext:context error:&error]) { [subscriber putError:error]; return; @@ -220,65 +221,72 @@ SAtomic *context = [[SAtomic alloc] initWithValue:[TGMediaVideoConversionContext contextWithQueue:queue subscriber:subscriber]]; NSURL *outputUrl = [self _randomTemporaryURL]; - [queue dispatch:^ + NSString *path = TGComponentsPathForResource(@"blank_1080p", @"mp4"); + AVAsset *avAsset = [[AVURLAsset alloc] initWithURL:[NSURL fileURLWithPath:path] options:nil]; + + NSArray *requiredKeys = @[ @"tracks", @"duration", @"playable" ]; + [avAsset loadValuesAsynchronouslyForKeys:requiredKeys completionHandler:^ { - if (((TGMediaVideoConversionContext *)context.value).cancelled) - return; - - TGMediaVideoConversionPreset preset = TGMediaVideoConversionPresetAnimation; - - NSError *error = nil; - - NSString *outputPath = outputUrl.path; - NSFileManager *fileManager = [NSFileManager defaultManager]; - if ([fileManager fileExistsAtPath:outputPath]) + [queue dispatch:^ { - [fileManager removeItemAtPath:outputPath error:&error]; - if (error != nil) + if (((TGMediaVideoConversionContext *)context.value).cancelled) + return; + + TGMediaVideoConversionPreset preset = TGMediaVideoConversionPresetAnimation; + + NSError *error = nil; + + NSString *outputPath = outputUrl.path; + NSFileManager *fileManager = [NSFileManager defaultManager]; + if ([fileManager fileExistsAtPath:outputPath]) + { + [fileManager removeItemAtPath:outputPath error:&error]; + if (error != nil) + { + [subscriber putError:error]; + return; + } + } + + if (![self setupAssetReaderWriterForAVAsset:avAsset image:image outputURL:outputUrl preset:preset entityRenderer:entityRenderer adjustments:adjustments inhibitAudio:true conversionContext:context error:&error]) { [subscriber putError:error]; return; } - } - - if (![self setupAssetReaderWriterForItem:image outputURL:outputUrl preset:preset entityRenderer:entityRenderer adjustments:adjustments inhibitAudio:true conversionContext:context error:&error]) - { - [subscriber putError:error]; - return; - } - - TGDispatchAfter(1.0, queue._dispatch_queue, ^ - { - if (watcher != nil) - [watcher setupWithFileURL:outputUrl]; - }); - - [self processWithConversionContext:context completionBlock:^ - { - TGMediaVideoConversionContext *resultContext = context.value; - [resultContext.imageGenerator generateCGImagesAsynchronouslyForTimes:@[ [NSValue valueWithCMTime:kCMTimeZero] ] completionHandler:^(__unused CMTime requestedTime, CGImageRef _Nullable image, __unused CMTime actualTime, AVAssetImageGeneratorResult result, __unused NSError * _Nullable error) + + TGDispatchAfter(1.0, queue._dispatch_queue, ^ { - UIImage *coverImage = nil; - if (result == AVAssetImageGeneratorSucceeded) - coverImage = [UIImage imageWithCGImage:image]; - - __block TGMediaVideoConversionResult *contextResult = nil; - [context modify:^id(TGMediaVideoConversionContext *resultContext) + if (watcher != nil) + [watcher setupWithFileURL:outputUrl]; + }); + + [self processWithConversionContext:context completionBlock:^ + { + TGMediaVideoConversionContext *resultContext = context.value; + [resultContext.imageGenerator generateCGImagesAsynchronouslyForTimes:@[ [NSValue valueWithCMTime:kCMTimeZero] ] completionHandler:^(__unused CMTime requestedTime, CGImageRef _Nullable image, __unused CMTime actualTime, AVAssetImageGeneratorResult result, __unused NSError * _Nullable error) { - id liveUploadData = nil; - if (watcher != nil) - liveUploadData = [watcher fileUpdated:true]; + UIImage *coverImage = nil; + if (result == AVAssetImageGeneratorSucceeded) + coverImage = [UIImage imageWithCGImage:image]; - contextResult = [TGMediaVideoConversionResult resultWithFileURL:outputUrl fileSize:0 duration:CMTimeGetSeconds(resultContext.timeRange.duration) dimensions:resultContext.dimensions coverImage:coverImage liveUploadData:liveUploadData]; - return [resultContext finishedContext]; + __block TGMediaVideoConversionResult *contextResult = nil; + [context modify:^id(TGMediaVideoConversionContext *resultContext) + { + id liveUploadData = nil; + if (watcher != nil) + liveUploadData = [watcher fileUpdated:true]; + + contextResult = [TGMediaVideoConversionResult resultWithFileURL:outputUrl fileSize:0 duration:CMTimeGetSeconds(resultContext.timeRange.duration) dimensions:resultContext.dimensions coverImage:coverImage liveUploadData:liveUploadData]; + return [resultContext finishedContext]; + }]; + + [subscriber putNext:contextResult]; + [subscriber putCompletion]; }]; - - [subscriber putNext:contextResult]; - [subscriber putCompletion]; }]; }]; }]; - + return [[SBlockDisposable alloc] initWithBlock:^ { [queue dispatch:^ @@ -314,7 +322,7 @@ return outputDimensions; } -+ (AVAssetReaderVideoCompositionOutput *)setupVideoCompositionOutputWithAVAsset:(AVAsset *)avAsset composition:(AVMutableComposition *)composition videoTrack:(AVAssetTrack *)videoTrack preset:(TGMediaVideoConversionPreset)preset entityRenderer:(id)entityRenderer adjustments:(TGMediaVideoEditAdjustments *)adjustments timeRange:(CMTimeRange)timeRange outputSettings:(NSDictionary **)outputSettings dimensions:(CGSize *)dimensions conversionContext:(SAtomic *)conversionContext ++ (AVAssetReaderVideoCompositionOutput *)setupVideoCompositionOutputWithAVAsset:(AVAsset *)avAsset image:(UIImage *)image composition:(AVMutableComposition *)composition videoTrack:(AVAssetTrack *)videoTrack preset:(TGMediaVideoConversionPreset)preset entityRenderer:(id)entityRenderer adjustments:(TGMediaVideoEditAdjustments *)adjustments timeRange:(CMTimeRange)timeRange outputSettings:(NSDictionary **)outputSettings dimensions:(CGSize *)dimensions conversionContext:(SAtomic *)conversionContext { CGSize transformedSize = CGRectApplyAffineTransform((CGRect){CGPointZero, videoTrack.naturalSize}, videoTrack.preferredTransform).size;; CGRect transformedRect = CGRectMake(0, 0, transformedSize.width, transformedSize.height); @@ -325,6 +333,8 @@ CGRect cropRect = hasCropping ? CGRectIntegral(adjustments.cropRect) : transformedRect; if (cropRect.size.width < FLT_EPSILON || cropRect.size.height < FLT_EPSILON) cropRect = transformedRect; + if (image != nil) + cropRect = CGRectMake(0.0f, 0.0f, image.size.width, image.size.height); CGSize maxDimensions = [TGMediaVideoConversionPresetSettings maximumSizeForPreset:preset]; CGSize outputDimensions = TGFitSizeF(cropRect.size, maxDimensions); @@ -361,11 +371,22 @@ editor.standalone = true; ciContext = [CIContext contextWithEAGLContext:[[GPUImageContext sharedImageProcessingContext] context]]; } - + + CIImage *backgroundCIImage = nil; + if (image != nil) { + backgroundCIImage = [[CIImage alloc] initWithImage:image]; + } + __block CIImage *overlayCIImage = nil; videoComposition = [AVMutableVideoComposition videoCompositionWithAsset:avAsset applyingCIFiltersWithHandler:^(AVAsynchronousCIImageFilteringRequest * _Nonnull request) { __block CIImage *resultImage = request.sourceImage; + if (backgroundCIImage != nil) { + resultImage = backgroundCIImage; + } + + CGSize size = resultImage.extent.size; + if (editor != nil) { [editor setCIImage:resultImage]; resultImage = editor.currentResultCIImage; @@ -374,14 +395,14 @@ if (overlayImage != nil && overlayImage.size.width > 0.0) { if (overlayCIImage == nil) { overlayCIImage = [[CIImage alloc] initWithImage:overlayImage]; - CGFloat scale = request.sourceImage.extent.size.width / overlayCIImage.extent.size.width; + CGFloat scale = size.width / overlayCIImage.extent.size.width; overlayCIImage = [overlayCIImage imageByApplyingTransform:CGAffineTransformMakeScale(scale, scale)]; } resultImage = [overlayCIImage imageByCompositingOverImage:resultImage]; } if (entityRenderer != nil) { - [entityRenderer entitiesForTime:request.compositionTime size:request.sourceImage.extent.size completion:^(NSArray *images) { + [entityRenderer entitiesForTime:request.compositionTime size:size completion:^(NSArray *images) { for (CIImage *image in images) { resultImage = [image imageByCompositingOverImage:resultImage]; } @@ -422,10 +443,13 @@ if (!CMTIME_IS_VALID(videoComposition.frameDuration)) videoComposition.frameDuration = CMTimeMake(1, 30); + if (image != nil) + videoComposition.frameDuration = CMTimeMake(1, 30); + videoComposition.renderSize = [self _renderSizeWithCropSize:cropRect.size rotateSideward:TGOrientationIsSideward(adjustments.cropOrientation, NULL)]; if (videoComposition.renderSize.width < FLT_EPSILON || videoComposition.renderSize.height < FLT_EPSILON) return nil; - + if (overlayImage != nil && entityRenderer == nil) { CALayer *parentLayer = [CALayer layer]; @@ -481,14 +505,12 @@ return output; } -+ (bool)setupAssetReaderWriterForItem:(id)item outputURL:(NSURL *)outputURL preset:(TGMediaVideoConversionPreset)preset entityRenderer:(id)entityRenderer adjustments:(TGMediaVideoEditAdjustments *)adjustments inhibitAudio:(bool)inhibitAudio conversionContext:(SAtomic *)outConversionContext error:(NSError **)error ++ (bool)setupAssetReaderWriterForAVAsset:(AVAsset *)avAsset image:(UIImage *)image outputURL:(NSURL *)outputURL preset:(TGMediaVideoConversionPreset)preset entityRenderer:(id)entityRenderer adjustments:(TGMediaVideoEditAdjustments *)adjustments inhibitAudio:(bool)inhibitAudio conversionContext:(SAtomic *)outConversionContext error:(NSError **)error { - if ([item isKindOfClass:[AVAsset class]]) { + if (image == nil) { TGMediaSampleBufferProcessor *videoProcessor = nil; TGMediaSampleBufferProcessor *audioProcessor = nil; - AVAsset *avAsset = (AVAsset *)item; - AVAssetTrack *audioTrack = [[avAsset tracksWithMediaType:AVMediaTypeAudio] firstObject]; AVAssetTrack *videoTrack = [[avAsset tracksWithMediaType:AVMediaTypeVideo] firstObject]; if (videoTrack == nil) @@ -512,7 +534,7 @@ NSDictionary *outputSettings = nil; AVMutableComposition *composition = [AVMutableComposition composition]; - AVAssetReaderVideoCompositionOutput *output = [self setupVideoCompositionOutputWithAVAsset:avAsset composition:composition videoTrack:videoTrack preset:preset entityRenderer:entityRenderer adjustments:adjustments timeRange:timeRange outputSettings:&outputSettings dimensions:&dimensions conversionContext:outConversionContext]; + AVAssetReaderVideoCompositionOutput *output = [self setupVideoCompositionOutputWithAVAsset:avAsset image:nil composition:composition videoTrack:videoTrack preset:preset entityRenderer:entityRenderer adjustments:adjustments timeRange:timeRange outputSettings:&outputSettings dimensions:&dimensions conversionContext:outConversionContext]; if (output == nil) return false; @@ -553,15 +575,26 @@ }]; return true; - } else if ([item isKindOfClass:[UIImage class]]) { + } else { TGMediaSampleBufferProcessor *videoProcessor = nil; CGSize dimensions = CGSizeZero; NSDictionary *outputSettings = nil; CMTimeRange timeRange = CMTimeRangeMake(CMTimeMakeWithSeconds(0.0, NSEC_PER_SEC), CMTimeMakeWithSeconds(4.0, NSEC_PER_SEC)); AVMutableComposition *composition = [AVMutableComposition composition]; - AVAssetTrack *videoTrack = [composition addMutableTrackWithMediaType:AVMediaTypeVideo preferredTrackID:kCMPersistentTrackID_Invalid]; - AVAssetReaderVideoCompositionOutput *output = [self setupVideoCompositionOutputWithAVAsset:composition composition:composition videoTrack:videoTrack preset:preset entityRenderer:entityRenderer adjustments:adjustments timeRange:timeRange outputSettings:&outputSettings dimensions:&dimensions conversionContext:outConversionContext]; + + AVAssetTrack *videoTrack = [[avAsset tracksWithMediaType:AVMediaTypeVideo] firstObject]; + if (videoTrack == nil) + return false; + + AVMutableCompositionTrack *mutableCompositionVideoTrack = [composition addMutableTrackWithMediaType:AVMediaTypeVideo preferredTrackID:kCMPersistentTrackID_Invalid]; + [mutableCompositionVideoTrack insertTimeRange:timeRange ofTrack:videoTrack atTime:kCMTimeZero error:nil]; + + AVMutableComposition *mock = [AVMutableComposition composition]; + AVMutableCompositionTrack *mockTrack = [mock addMutableTrackWithMediaType:AVMediaTypeVideo preferredTrackID:kCMPersistentTrackID_Invalid]; + [mockTrack insertTimeRange:timeRange ofTrack:videoTrack atTime:kCMTimeZero error:nil]; + + AVAssetReaderVideoCompositionOutput *output = [self setupVideoCompositionOutputWithAVAsset:mock image:image composition:composition videoTrack:videoTrack preset:preset entityRenderer:entityRenderer adjustments:adjustments timeRange:timeRange outputSettings:&outputSettings dimensions:&dimensions conversionContext:outConversionContext]; if (output == nil) return false; @@ -1176,9 +1209,8 @@ static CGFloat progressOfSampleBufferInTimeRange(CMSampleBufferRef sampleBuffer, { return (CGSize){ 240.0f, 240.0f }; } - default: - return (CGSize){ 640.0f, 640.0f }; + return (CGSize){ 848.0f, 848.0f }; } } @@ -1224,11 +1256,7 @@ static CGFloat progressOfSampleBufferInTimeRange(CMSampleBufferRef sampleBuffer, NSDictionary *codecSettings = @ { -#if DEBUG - AVVideoAverageBitRateKey: @([self _videoBitrateKbpsForPreset:preset] * 500), -#else AVVideoAverageBitRateKey: @([self _videoBitrateKbpsForPreset:preset] * 1000), -#endif AVVideoCleanApertureKey: videoCleanApertureSettings, AVVideoPixelAspectRatioKey: videoAspectRatioSettings }; @@ -1265,7 +1293,7 @@ static CGFloat progressOfSampleBufferInTimeRange(CMSampleBufferRef sampleBuffer, return 300; default: - return 700; + return 500; } } diff --git a/submodules/LegacyComponents/Sources/TGVideoEditAdjustments.m b/submodules/LegacyComponents/Sources/TGVideoEditAdjustments.m index d787723785..d135fb39c7 100644 --- a/submodules/LegacyComponents/Sources/TGVideoEditAdjustments.m +++ b/submodules/LegacyComponents/Sources/TGVideoEditAdjustments.m @@ -4,6 +4,8 @@ #import "TGPaintingData.h" +#import "PGPhotoEditorValues.h" + #import "TGPhotoPaintStickerEntity.h" #import "TGPhotoPaintTextEntity.h" @@ -129,6 +131,26 @@ const NSTimeInterval TGVideoEditMaximumGifDuration = 30.5; return adjustments; } ++ (instancetype)editAdjustmentsWithPhotoEditorValues:(PGPhotoEditorValues *)values { + TGVideoEditAdjustments *adjustments = [[[self class] alloc] init]; + adjustments->_originalSize = values.originalSize; + CGRect cropRect = values.cropRect; + if (CGRectIsEmpty(cropRect)) { + cropRect = CGRectMake(0.0f, 0.0f, values.originalSize.width, values.originalSize.height); + } + adjustments->_cropRect = cropRect; + adjustments->_cropOrientation = values.cropOrientation; + adjustments->_cropLockedAspectRatio = values.cropLockedAspectRatio; + adjustments->_cropMirrored = values.cropMirrored; + adjustments->_toolValues = values.toolValues; + adjustments->_paintingData = values.paintingData; + adjustments->_sendAsGif = true; + adjustments->_preset = TGMediaVideoConversionPresetAnimation; + adjustments->_toolValues = values.toolValues; + + return adjustments; +} + - (instancetype)editAdjustmentsWithPreset:(TGMediaVideoConversionPreset)preset maxDuration:(NSTimeInterval)maxDuration { TGVideoEditAdjustments *adjustments = [[[self class] alloc] init]; diff --git a/submodules/LegacyMediaPickerUI/Sources/LegacyMediaPickers.swift b/submodules/LegacyMediaPickerUI/Sources/LegacyMediaPickers.swift index 8eee272e26..57096e96dc 100644 --- a/submodules/LegacyMediaPickerUI/Sources/LegacyMediaPickers.swift +++ b/submodules/LegacyMediaPickerUI/Sources/LegacyMediaPickers.swift @@ -128,22 +128,27 @@ private final class LegacyAssetItemWrapper: NSObject { public func legacyAssetPickerItemGenerator() -> ((Any?, String?, [Any]?, String?) -> [AnyHashable : Any]?) { return { anyDict, caption, entities, hash in let dict = anyDict as! NSDictionary + let stickers = (dict["stickers"] as? [TGDocumentMediaAttachment])?.compactMap { document -> FileMediaReference? in + if let sticker = stickerFromLegacyDocument(document) { + return FileMediaReference.standalone(media: sticker) + } else { + return nil + } + } ?? [] if (dict["type"] as! NSString) == "editedPhoto" || (dict["type"] as! NSString) == "capturedPhoto" { let image = dict["image"] as! UIImage let thumbnail = dict["previewImage"] as? UIImage - (dict["stickers"] as? Array).map { element in - } - - let stickers = (dict["stickers"] as? [TGDocumentMediaAttachment])?.compactMap { document -> FileMediaReference? in - if let sticker = stickerFromLegacyDocument(document) { - return FileMediaReference.standalone(media: sticker) - } else { - return nil - } - } ?? [] var result: [AnyHashable : Any] = [:] - result["item" as NSString] = LegacyAssetItemWrapper(item: .image(data: .image(image), thumbnail: thumbnail, caption: caption, stickers: stickers), timer: (dict["timer"] as? NSNumber)?.intValue, groupedId: (dict["groupedId"] as? NSNumber)?.int64Value) + if let isAnimation = dict["isAnimation"] as? NSNumber, isAnimation.boolValue { + let url: String? = (dict["url"] as? String) ?? (dict["url"] as? URL)?.path + if let url = url { + let dimensions = image.size + result["item" as NSString] = LegacyAssetItemWrapper(item: .video(data: .tempFile(path: url, dimensions: dimensions, duration: 4.0), thumbnail: thumbnail, adjustments: dict["adjustments"] as? TGVideoEditAdjustments, caption: caption, asFile: false, asAnimation: true, stickers: stickers), timer: (dict["timer"] as? NSNumber)?.intValue, groupedId: (dict["groupedId"] as? NSNumber)?.int64Value) + } + } else { + result["item" as NSString] = LegacyAssetItemWrapper(item: .image(data: .image(image), thumbnail: thumbnail, caption: caption, stickers: stickers), timer: (dict["timer"] as? NSNumber)?.intValue, groupedId: (dict["groupedId"] as? NSNumber)?.int64Value) + } return result } else if (dict["type"] as! NSString) == "cloudPhoto" { let asset = dict["asset"] as! TGMediaAsset @@ -203,13 +208,13 @@ public func legacyAssetPickerItemGenerator() -> ((Any?, String?, [Any]?, String? if let asset = dict["asset"] as? TGMediaAsset { var result: [AnyHashable: Any] = [:] - result["item" as NSString] = LegacyAssetItemWrapper(item: .video(data: .asset(asset), thumbnail: thumbnail, adjustments: dict["adjustments"] as? TGVideoEditAdjustments, caption: caption, asFile: asFile, asAnimation: false, stickers: []), timer: (dict["timer"] as? NSNumber)?.intValue, groupedId: (dict["groupedId"] as? NSNumber)?.int64Value) + result["item" as NSString] = LegacyAssetItemWrapper(item: .video(data: .asset(asset), thumbnail: thumbnail, adjustments: dict["adjustments"] as? TGVideoEditAdjustments, caption: caption, asFile: asFile, asAnimation: false, stickers: stickers), timer: (dict["timer"] as? NSNumber)?.intValue, groupedId: (dict["groupedId"] as? NSNumber)?.int64Value) return result } else if let url = (dict["url"] as? String) ?? (dict["url"] as? URL)?.absoluteString { let dimensions = (dict["dimensions"]! as AnyObject).cgSizeValue! let duration = (dict["duration"]! as AnyObject).doubleValue! var result: [AnyHashable: Any] = [:] - result["item" as NSString] = LegacyAssetItemWrapper(item: .video(data: .tempFile(path: url, dimensions: dimensions, duration: duration), thumbnail: thumbnail, adjustments: dict["adjustments"] as? TGVideoEditAdjustments, caption: caption, asFile: asFile, asAnimation: false, stickers: []), timer: (dict["timer"] as? NSNumber)?.intValue, groupedId: (dict["groupedId"] as? NSNumber)?.int64Value) + result["item" as NSString] = LegacyAssetItemWrapper(item: .video(data: .tempFile(path: url, dimensions: dimensions, duration: duration), thumbnail: thumbnail, adjustments: dict["adjustments"] as? TGVideoEditAdjustments, caption: caption, asFile: asFile, asAnimation: false, stickers: stickers), timer: (dict["timer"] as? NSNumber)?.intValue, groupedId: (dict["groupedId"] as? NSNumber)?.int64Value) return result } } else if (dict["type"] as! NSString) == "cameraVideo" { @@ -225,7 +230,7 @@ public func legacyAssetPickerItemGenerator() -> ((Any?, String?, [Any]?, String? let dimensions = previewImage.pixelSize() let duration = (dict["duration"]! as AnyObject).doubleValue! var result: [AnyHashable: Any] = [:] - result["item" as NSString] = LegacyAssetItemWrapper(item: .video(data: .tempFile(path: url, dimensions: dimensions, duration: duration), thumbnail: thumbnail, adjustments: dict["adjustments"] as? TGVideoEditAdjustments, caption: caption, asFile: asFile, asAnimation: false, stickers: []), timer: (dict["timer"] as? NSNumber)?.intValue, groupedId: (dict["groupedId"] as? NSNumber)?.int64Value) + result["item" as NSString] = LegacyAssetItemWrapper(item: .video(data: .tempFile(path: url, dimensions: dimensions, duration: duration), thumbnail: thumbnail, adjustments: dict["adjustments"] as? TGVideoEditAdjustments, caption: caption, asFile: asFile, asAnimation: false, stickers: stickers), timer: (dict["timer"] as? NSNumber)?.intValue, groupedId: (dict["groupedId"] as? NSNumber)?.int64Value) return result } } @@ -434,7 +439,7 @@ public func legacyAssetPickerEnqueueMessages(account: Account, signals: [Any]) - } resource = VideoLibraryMediaResource(localIdentifier: asset.backingAsset.localIdentifier, conversion: asFile ? .passthrough : .compress(resourceAdjustments)) case let .tempFile(path, _, _): - if asFile || asAnimation { + if asFile || (asAnimation && !path.contains(".jpg")) { if let size = fileSize(path) { resource = LocalFileMediaResource(fileId: arc4random64(), size: size) account.postbox.mediaBox.moveResourceData(resource.id, fromTempPath: path) diff --git a/submodules/LegacyMediaPickerUI/Sources/LegacyPaintStickersContext.swift b/submodules/LegacyMediaPickerUI/Sources/LegacyPaintStickersContext.swift index 78df4321f2..a8be5baec7 100644 --- a/submodules/LegacyMediaPickerUI/Sources/LegacyPaintStickersContext.swift +++ b/submodules/LegacyMediaPickerUI/Sources/LegacyPaintStickersContext.swift @@ -262,8 +262,9 @@ public final class LegacyPaintEntityRenderer: NSObject, TGPhotoPaintEntityRender } entity.image(for: time, completion: { image in if var image = image { - let paintingScale = max(size.width, size.height) / 1920.0 - + let maxSide = max(size.width, size.height) + let paintingScale = maxSide / 1920.0 + var transform = CGAffineTransform(translationX: -image.extent.midX, y: -image.extent.midY) image = image.transformed(by: transform) diff --git a/submodules/TelegramUI/Sources/FetchVideoMediaResource.swift b/submodules/TelegramUI/Sources/FetchVideoMediaResource.swift index 8a31c87ffb..d1896c6994 100644 --- a/submodules/TelegramUI/Sources/FetchVideoMediaResource.swift +++ b/submodules/TelegramUI/Sources/FetchVideoMediaResource.swift @@ -380,20 +380,43 @@ func fetchLocalFileVideoMediaResource(account: Account, resource: LocalFileVideo } let updatedSize = Atomic(value: 0) let entityRenderer = adjustments.flatMap { LegacyPaintEntityRenderer(account: account, adjustments: $0) } - let signal = TGMediaVideoConverter.convert(avAsset, adjustments: adjustments, watcher: VideoConversionWatcher(update: { path, size in - var value = stat() - if stat(path, &value) == 0 { - if let data = try? Data(contentsOf: URL(fileURLWithPath: path), options: [.mappedRead]) { - var range: Range? - let _ = updatedSize.modify { updatedSize in - range = updatedSize ..< Int(value.st_size) - return Int(value.st_size) + let signal: SSignal + if filteredPath.contains(".jpg") { + if let data = try? Data(contentsOf: URL(fileURLWithPath: filteredPath), options: [.mappedRead]), let image = UIImage(data: data) { + signal = TGMediaVideoConverter.renderUIImage(image, adjustments: adjustments, watcher: VideoConversionWatcher(update: { path, size in + var value = stat() + if stat(path, &value) == 0 { + if let data = try? Data(contentsOf: URL(fileURLWithPath: path), options: [.mappedRead]) { + var range: Range? + let _ = updatedSize.modify { updatedSize in + range = updatedSize ..< Int(value.st_size) + return Int(value.st_size) + } + //print("size = \(Int(value.st_size)), range: \(range!)") + subscriber.putNext(.dataPart(resourceOffset: range!.lowerBound, data: data, range: range!, complete: false)) + } } - //print("size = \(Int(value.st_size)), range: \(range!)") - subscriber.putNext(.dataPart(resourceOffset: range!.lowerBound, data: data, range: range!, complete: false)) - } + }), entityRenderer: entityRenderer)! + } else { + signal = SSignal.single(nil) } - }), entityRenderer: entityRenderer)! + } else { + signal = TGMediaVideoConverter.convert(avAsset, adjustments: adjustments, watcher: VideoConversionWatcher(update: { path, size in + var value = stat() + if stat(path, &value) == 0 { + if let data = try? Data(contentsOf: URL(fileURLWithPath: path), options: [.mappedRead]) { + var range: Range? + let _ = updatedSize.modify { updatedSize in + range = updatedSize ..< Int(value.st_size) + return Int(value.st_size) + } + //print("size = \(Int(value.st_size)), range: \(range!)") + subscriber.putNext(.dataPart(resourceOffset: range!.lowerBound, data: data, range: range!, complete: false)) + } + } + }), entityRenderer: entityRenderer)! + } + let signalDisposable = signal.start(next: { next in if let result = next as? TGMediaVideoConversionResult { var value = stat()