Merge commit '7621e2f8dec938cf48181c8b10afc9b01f444e68' into beta

This commit is contained in:
Ilya Laktyushin
2025-12-06 02:17:48 +04:00
commit 8344b97e03
28070 changed files with 7995182 additions and 0 deletions
+34
View File
@@ -0,0 +1,34 @@
objc_library(
name = "ShareItemsImpl",
enable_modules = True,
module_name = "ShareItemsImpl",
srcs = glob([
"Sources/**/*.m",
"Sources/**/*.h",
], allow_empty=True),
hdrs = glob([
"PublicHeaders/**/*.h",
]),
includes = [
"PublicHeaders",
],
deps = [
"//submodules/MtProtoKit:MtProtoKit",
"//submodules/PhoneNumberFormat:PhoneNumberFormat",
"//submodules/MimeTypes:MimeTypes",
],
sdk_frameworks = [
"Foundation",
"UIKit",
"MobileCoreServices",
"AddressBook",
"AVFoundation",
],
weak_sdk_frameworks = [
"PassKit",
],
visibility = [
"//visibility:public",
],
)
@@ -0,0 +1,6 @@
#import <UIKit/UIKit.h>
#import <ShareItemsImpl/TGItemProviderSignals.h>
#import <ShareItemsImpl/TGShareLocationSignals.h>
@@ -0,0 +1,10 @@
#import <Foundation/Foundation.h>
@class MTSignal;
@interface TGItemProviderSignals : NSObject
+ (NSArray<MTSignal *> *)itemSignalsForInputItems:(NSArray *)inputItems;
+ (NSData *)audioWaveform:(NSURL *)url;
@end
@@ -0,0 +1,24 @@
#import <Foundation/Foundation.h>
@class MTSignal;
@interface TGShareLocationResult : NSObject
@property (nonatomic, readonly) double latitude;
@property (nonatomic, readonly) double longitude;
@property (nonatomic, readonly) NSString *title;
@property (nonatomic, readonly) NSString *address;
@property (nonatomic, readonly) NSString *provider;
@property (nonatomic, readonly) NSString *venueId;
@property (nonatomic, readonly) NSString *venueType;
- (instancetype)initWithLatitude:(double)latitude longitude:(double)longitude title:(NSString *)title address:(NSString *)address provider:(NSString *)provider venueId:(NSString *)venueId venueType:(NSString *)venueType;
@end
@interface TGShareLocationSignals : NSObject
+ (MTSignal *)locationMessageContentForURL:(NSURL *)url;
+ (bool)isLocationURL:(NSURL *)url;
@end
@@ -0,0 +1,708 @@
#import <ShareItemsImpl/TGItemProviderSignals.h>
#import <MtProtoKit/MtProtoKit.h>
#import <UIKit/UIKit.h>
#import <MobileCoreServices/MobileCoreServices.h>
#import <AddressBook/AddressBook.h>
#import <AVFoundation/AVFoundation.h>
#import <PassKit/PassKit.h>
#import <MimeTypes/MimeTypes.h>
@implementation TGItemProviderSignals
+ (NSArray<MTSignal *> *)itemSignalsForInputItems:(NSArray *)inputItems
{
NSMutableArray *itemSignals = [[NSMutableArray alloc] init];
NSMutableArray *providers = [[NSMutableArray alloc] init];
for (NSExtensionItem *item in inputItems)
{
for (NSItemProvider *provider in item.attachments)
{
if ([provider hasItemConformingToTypeIdentifier:(NSString *)kUTTypeMovie])
[providers addObject:provider];
else if ([provider hasItemConformingToTypeIdentifier:(NSString *)kUTTypeAudio])
[providers addObject:provider];
else if ([provider hasItemConformingToTypeIdentifier:(NSString *)kUTTypeImage])
[providers addObject:provider];
else if ([provider hasItemConformingToTypeIdentifier:(NSString *)kUTTypeFileURL])
[providers addObject:provider];
else if ([provider hasItemConformingToTypeIdentifier:(NSString *)kUTTypeURL]) {
[providers removeAllObjects];
[providers addObject:provider];
break;
}
else if ([provider hasItemConformingToTypeIdentifier:(NSString *)kUTTypeVCard])
[providers addObject:provider];
else if ([provider hasItemConformingToTypeIdentifier:(NSString *)kUTTypeText])
[providers addObject:provider];
else if ([provider hasItemConformingToTypeIdentifier:(NSString *)kUTTypeData])
[providers addObject:provider];
else if ([provider hasItemConformingToTypeIdentifier:@"com.apple.pkpass"])
[providers addObject:provider];
}
}
__unused NSInteger providerIndex = -1;
for (NSItemProvider *provider in providers)
{
providerIndex++;
MTSignal *dataSignal = nil;
if ([provider hasItemConformingToTypeIdentifier:(NSString *)kUTTypeAudio])
dataSignal = [self signalForAudioItemProvider:provider];
else if ([provider hasItemConformingToTypeIdentifier:(NSString *)kUTTypeMovie])
dataSignal = [self signalForVideoItemProvider:provider];
else if ([provider hasItemConformingToTypeIdentifier:(NSString *)kUTTypeGIF])
dataSignal = [self signalForDataItemProvider:provider];
else if ([provider hasItemConformingToTypeIdentifier:(NSString *)kUTTypeImage])
dataSignal = [self signalForImageItemProvider:provider];
else if ([provider hasItemConformingToTypeIdentifier:(NSString *)kUTTypeFileURL])
{
dataSignal = [[self signalForUrlItemProvider:provider] mapToSignal:^MTSignal *(NSURL *url)
{
NSData *data = [[NSData alloc] initWithContentsOfURL:url options:NSDataReadingMappedIfSafe error:nil];
if (data == nil)
return [MTSignal fail:nil];
NSString *fileName = [[url pathComponents] lastObject];
if (fileName.length == 0)
fileName = @"file.bin";
NSString *extension = [fileName pathExtension];
NSString *mimeType = [TGMimeTypeMap mimeTypeForExtension:[extension lowercaseString]];
if (mimeType == nil)
mimeType = @"application/octet-stream";
return [MTSignal single:@{@"data": data, @"fileName": fileName, @"mimeType": mimeType}];
}];
}
else if ([provider hasItemConformingToTypeIdentifier:(NSString *)kUTTypeVCard])
dataSignal = [self signalForVCardItemProvider:provider];
else if ([provider hasItemConformingToTypeIdentifier:(NSString *)kUTTypeText])
dataSignal = [self signalForTextItemProvider:provider];
else if ([provider hasItemConformingToTypeIdentifier:(NSString *)kUTTypeURL])
dataSignal = [self signalForTextUrlItemProvider:provider];
else if ([provider hasItemConformingToTypeIdentifier:(NSString *)kUTTypeData])
{
dataSignal = [[self signalForDataItemProvider:provider] map:^id(NSDictionary *dict)
{
if (dict[@"fileName"] == nil)
{
NSMutableDictionary *updatedDict = [[NSMutableDictionary alloc] initWithDictionary:dict];
for (NSString *typeIdentifier in provider.registeredTypeIdentifiers)
{
NSString *extension = [TGMimeTypeMap extensionForMimeType:typeIdentifier];
if (extension == nil)
extension = [TGMimeTypeMap extensionForMimeType:[@"application/" stringByAppendingString:typeIdentifier]];
if (extension != nil) {
updatedDict[@"fileName"] = [@"file" stringByAppendingPathExtension:extension];
updatedDict[@"mimeType"] = [TGMimeTypeMap mimeTypeForExtension:extension];
}
}
return updatedDict;
}
else
{
return dict;
}
}];
}
else if ([provider hasItemConformingToTypeIdentifier:@"com.apple.pkpass"])
{
dataSignal = [self signalForPassKitItemProvider:provider];
}
if (dataSignal != nil)
[itemSignals addObject:dataSignal];
}
return itemSignals;
}
+ (MTSignal *)signalForDataItemProvider:(NSItemProvider *)itemProvider
{
return [[MTSignal alloc] initWithGenerator:^id<MTDisposable>(MTSubscriber *subscriber)
{
[itemProvider loadItemForTypeIdentifier:(NSString *)kUTTypeData options:nil completionHandler:^(NSData *data, NSError *error)
{
if (error != nil)
[subscriber putError:nil];
else
{
[subscriber putNext:@{@"data": data}];
[subscriber putCompletion];
}
}];
return nil;
}];
}
__unused static UIImage *TGScaleImageToPixelSize(UIImage *image, CGSize size) {
UIGraphicsBeginImageContextWithOptions(size, true, 1.0f);
[image drawInRect:CGRectMake(0, 0, size.width, size.height) blendMode:kCGBlendModeCopy alpha:1.0f];
UIImage *result = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return result;
}
__unused static CGSize TGFitSize(CGSize size, CGSize maxSize) {
if (size.width < 1)
size.width = 1;
if (size.height < 1)
size.height = 1;
if (size.width > maxSize.width)
{
size.height = floor((size.height * maxSize.width / size.width));
size.width = maxSize.width;
}
if (size.height > maxSize.height)
{
size.width = floor((size.width * maxSize.height / size.height));
size.height = maxSize.height;
}
return size;
}
+ (MTSignal *)signalForImageItemProvider:(NSItemProvider *)itemProvider
{
return [[MTSignal alloc] initWithGenerator:^id<MTDisposable>(MTSubscriber *subscriber)
{
bool preferAsFile = false;
CGSize maxSize = CGSizeMake(1280.0, 1280.0);
NSDictionary *imageOptions = @{
NSItemProviderPreferredImageSizeKey: [NSValue valueWithCGSize:maxSize]
};
if (preferAsFile) {
imageOptions = nil;
}
if ([itemProvider hasItemConformingToTypeIdentifier:(NSString *)kUTTypeImage]) {
[itemProvider loadItemForTypeIdentifier:(NSString *)kUTTypeImage options:imageOptions completionHandler:^(id<NSSecureCoding> _Nullable item, NSError * _Null_unspecified error) {
if (error != nil && ![(NSObject *)item respondsToSelector:@selector(CGImage)] && ![(NSObject *)item respondsToSelector:@selector(absoluteString)]) {
[itemProvider loadItemForTypeIdentifier:(NSString *)kUTTypeData options:nil completionHandler:^(UIImage *image, NSError *error)
{
if (error != nil)
[subscriber putError:nil];
else
{
[subscriber putNext:@{@"image": image}];
[subscriber putCompletion];
}
}];
} else {
if ([(NSObject *)item respondsToSelector:@selector(absoluteString)]) {
NSURL *url = (NSURL *)item;
if (preferAsFile) {
NSData *data = [[NSData alloc] initWithContentsOfURL:url options:NSDataReadingMappedIfSafe error:nil];
if (data == nil) {
[subscriber putError:nil];
return;
}
NSString *fileName = [[url pathComponents] lastObject];
if (fileName.length == 0) {
fileName = @"file.bin";
}
NSString *extension = [fileName pathExtension];
NSString *mimeType = [TGMimeTypeMap mimeTypeForExtension:[extension lowercaseString]];
if (mimeType == nil) {
mimeType = @"application/octet-stream";
}
[subscriber putNext:@{@"data": data, @"fileName": fileName, @"mimeType": mimeType, @"treatAsFile": @true}];
[subscriber putCompletion];
} else {
CGImageSourceRef src = CGImageSourceCreateWithURL((__bridge CFURLRef) url, NULL);
CFDictionaryRef options = (__bridge CFDictionaryRef) @{
(id) kCGImageSourceCreateThumbnailWithTransform : @YES,
(id) kCGImageSourceCreateThumbnailFromImageAlways : @YES,
(id) kCGImageSourceThumbnailMaxPixelSize : @(maxSize.width)
};
CGImageRef image = CGImageSourceCreateThumbnailAtIndex(src, 0, options);
CFRelease(src);
if (image == nil) {
[subscriber putError:nil];
return;
}
NSString *tempPath = [NSTemporaryDirectory() stringByAppendingPathComponent:[[NSString alloc] initWithFormat:@"img%d", (int)arc4random()]];
CFURLRef tempUrl = (__bridge CFURLRef)[NSURL fileURLWithPath:tempPath];
CGImageDestinationRef destination = CGImageDestinationCreateWithURL(tempUrl, kUTTypeJPEG, 1, NULL);
NSDictionary *properties = @{ (__bridge NSString *)kCGImageDestinationLossyCompressionQuality: @(0.52)};
CGImageDestinationSetProperties(destination, (__bridge CFDictionaryRef)properties);
CGImageDestinationAddImage(destination, image, nil);
if (!CGImageDestinationFinalize(destination)) {
CFRelease(destination);
[subscriber putError:nil];
return;
}
CFRelease(destination);
NSData *resultData = [[NSData alloc] initWithContentsOfFile:tempPath options:NSDataReadingMappedIfSafe error:nil];
if (resultData != nil) {
[subscriber putNext:@{@"scaledImageData": resultData, @"scaledImageDimensions": [NSValue valueWithCGSize:CGSizeMake(CGImageGetWidth(image), CGImageGetHeight(image))]}];
[subscriber putCompletion];
} else {
[subscriber putError:nil];
}
}
} else {
[subscriber putNext:@{@"image": item}];
[subscriber putCompletion];
}
}
}];
} else {
[itemProvider loadItemForTypeIdentifier:(NSString *)kUTTypeData options:nil completionHandler:^(UIImage *image, NSError *error)
{
if (error != nil)
[subscriber putError:nil];
else
{
[subscriber putNext:@{@"image": image}];
[subscriber putCompletion];
}
}];
}
return nil;
}];
}
+ (MTSignal *)signalForAudioItemProvider:(NSItemProvider *)itemProvider
{
MTSignal *itemSignal = [[MTSignal alloc] initWithGenerator:^id<MTDisposable>(MTSubscriber *subscriber)
{
[itemProvider loadItemForTypeIdentifier:(NSString *)kUTTypeAudio options:nil completionHandler:^(NSURL *url, NSError *error)
{
if (error != nil)
[subscriber putError:nil];
else
{
[subscriber putNext:url];
[subscriber putCompletion];
}
}];
return nil;
}];
return [itemSignal map:^id(NSURL *url)
{
AVAsset *asset = [AVURLAsset URLAssetWithURL:url options:nil];
if (asset == nil)
return [MTSignal fail:nil];
NSString *extension = url.pathExtension;
NSString *mimeType = [TGMimeTypeMap mimeTypeForExtension:[extension lowercaseString]];
if (mimeType == nil)
mimeType = @"application/octet-stream";
NSString *title = (NSString *)[[AVMetadataItem metadataItemsFromArray:asset.commonMetadata withKey:AVMetadataCommonKeyTitle keySpace:AVMetadataKeySpaceCommon] firstObject];
NSString *artist = (NSString *)[[AVMetadataItem metadataItemsFromArray:asset.commonMetadata withKey:AVMetadataCommonKeyArtist keySpace:AVMetadataKeySpaceCommon] firstObject];
NSString *software = nil;
AVMetadataItem *softwareItem = [[AVMetadataItem metadataItemsFromArray:asset.commonMetadata withKey:AVMetadataCommonKeySoftware keySpace:AVMetadataKeySpaceCommon] firstObject];
if ([softwareItem isKindOfClass:[AVMetadataItem class]] && ([softwareItem.value isKindOfClass:[NSString class]]))
software = (NSString *)[softwareItem value];
bool isVoice = [software hasPrefix:@"com.apple.VoiceMemos"];
NSTimeInterval duration = CMTimeGetSeconds(asset.duration);
NSMutableDictionary *result = [[NSMutableDictionary alloc] init];
result[@"audio"] = url;
result[@"mimeType"] = mimeType;
result[@"duration"] = @(duration);
result[@"isVoice"] = @(isVoice);
NSString *artistString = @"";
if ([artist respondsToSelector:@selector(characterAtIndex:)]) {
artistString = artist;
} else if ([artist isKindOfClass:[AVMetadataItem class]]) {
artistString = [(AVMetadataItem *)artist stringValue];
}
NSString *titleString = @"";
if ([artist respondsToSelector:@selector(characterAtIndex:)]) {
titleString = title;
} else if ([title isKindOfClass:[AVMetadataItem class]]) {
titleString = [(AVMetadataItem *)title stringValue];
}
if (artistString.length > 0)
result[@"artist"] = artistString;
if (titleString.length > 0)
result[@"title"] = titleString;
return result;
}];
}
+ (MTSignal *)detectRoundVideo:(AVAsset *)asset
{
MTSignal *imageSignal = [[MTSignal alloc] initWithGenerator:^id<MTDisposable>(MTSubscriber *subsriber)
{
AVAssetImageGenerator *imageGenerator = [[AVAssetImageGenerator alloc] initWithAsset:asset];
imageGenerator.appliesPreferredTrackTransform = true;
[imageGenerator generateCGImagesAsynchronouslyForTimes:@[ [NSValue valueWithCMTime:kCMTimeZero] ] completionHandler:^(CMTime requestedTime, CGImageRef _Nullable image, CMTime actualTime, AVAssetImageGeneratorResult result, NSError * _Nullable error)
{
if (error != nil)
{
[subsriber putError:nil];
}
else
{
[subsriber putNext:[UIImage imageWithCGImage:image]];
[subsriber putCompletion];
}
}];
return [[MTBlockDisposable alloc] initWithBlock:^
{
[imageGenerator cancelAllCGImageGeneration];
}];
}];
return [imageSignal map:^NSNumber *(UIImage *image)
{
CFDataRef pixelData = CGDataProviderCopyData(CGImageGetDataProvider(image.CGImage));
const UInt8 *data = CFDataGetBytePtr(pixelData);
bool (^isWhitePixel)(NSInteger, NSInteger) = ^bool(NSInteger x, NSInteger y)
{
int pixelInfo = ((image.size.width * y) + x ) * 4;
UInt8 red = data[pixelInfo];
UInt8 green = data[(pixelInfo + 1)];
UInt8 blue = data[pixelInfo + 2];
return (red > 250 && green > 250 && blue > 250);
};
CFRelease(pixelData);
return @(isWhitePixel(0, 0) && isWhitePixel(image.size.width - 1, 0) && isWhitePixel(0, image.size.height - 1) && isWhitePixel(image.size.width - 1, image.size.height - 1));
}];
}
+ (MTSignal *)signalForVideoItemProvider:(NSItemProvider *)itemProvider
{
MTSignal *assetSignal = [[MTSignal alloc] initWithGenerator:^id<MTDisposable>(MTSubscriber *subscriber)
{
[itemProvider loadItemForTypeIdentifier:(NSString *)kUTTypeMovie options:nil completionHandler:^(NSURL *url, NSError *error)
{
if (error != nil)
{
[subscriber putError:nil];
}
else
{
AVURLAsset *asset = [[AVURLAsset alloc] initWithURL:url options:nil];
[subscriber putNext:asset];
[subscriber putCompletion];
}
}];
return nil;
}];
return [assetSignal mapToSignal:^MTSignal *(AVURLAsset *asset)
{
AVAssetTrack *videoTrack = [[asset tracksWithMediaType:AVMediaTypeVideo] firstObject];
if (videoTrack == nil)
{
return [MTSignal fail:nil];
}
else
{
CGSize dimensions = CGRectApplyAffineTransform((CGRect){CGPointZero, videoTrack.naturalSize}, videoTrack.preferredTransform).size;
NSString *extension = asset.URL.pathExtension;
NSString *mimeType = [TGMimeTypeMap mimeTypeForExtension:[extension lowercaseString]];
if (mimeType == nil)
mimeType = @"application/octet-stream";
NSString *software = nil;
AVMetadataItem *softwareItem = [[AVMetadataItem metadataItemsFromArray:asset.metadata withKey:AVMetadataCommonKeySoftware keySpace:AVMetadataKeySpaceCommon] firstObject];
if ([softwareItem isKindOfClass:[AVMetadataItem class]] && ([softwareItem.value isKindOfClass:[NSString class]]))
software = (NSString *)[softwareItem value];
bool isAnimation = false;
if ([software hasPrefix:@"Boomerang"])
isAnimation = true;
if (isAnimation || fabs(dimensions.width - dimensions.height) > FLT_EPSILON)
{
return [MTSignal single:@{@"video": asset, @"mimeType": mimeType, @"isAnimation": @(isAnimation), @"width": @(dimensions.width), @"height": @(dimensions.height)}];
}
else
{
return [[self detectRoundVideo:asset] mapToSignal:^MTSignal *(NSNumber *isRoundVideo)
{
return [MTSignal single:@{@"video": asset, @"mimeType": mimeType, @"isAnimation": @(isAnimation), @"width": @(dimensions.width), @"height": @(dimensions.height), @"isRoundMessage": isRoundVideo}];
}];
}
}
}];
}
+ (MTSignal *)signalForUrlItemProvider:(NSItemProvider *)itemProvider
{
return [[MTSignal alloc] initWithGenerator:^id<MTDisposable>(MTSubscriber *subscriber)
{
[itemProvider loadItemForTypeIdentifier:(NSString *)kUTTypeFileURL options:nil completionHandler:^(NSURL *url, NSError *error)
{
if (error != nil)
[subscriber putError:nil];
else
{
[subscriber putNext:url];
[subscriber putCompletion];
}
}];
return nil;
}];
}
+ (MTSignal *)signalForTextItemProvider:(NSItemProvider *)itemProvider
{
return [[MTSignal alloc] initWithGenerator:^id<MTDisposable>(MTSubscriber *subscriber)
{
[itemProvider loadItemForTypeIdentifier:(NSString *)kUTTypeText options:nil completionHandler:^(NSString *text, NSError *error)
{
if (error != nil)
[subscriber putError:nil];
else
{
[subscriber putNext:@{@"text": text}];
[subscriber putCompletion];
}
}];
return nil;
}];
}
+ (MTSignal *)signalForTextUrlItemProvider:(NSItemProvider *)itemProvider
{
return [[MTSignal alloc] initWithGenerator:^id<MTDisposable>(MTSubscriber *subscriber)
{
[itemProvider loadItemForTypeIdentifier:(NSString *)kUTTypeURL options:nil completionHandler:^(NSURL *url, NSError *error)
{
if (error != nil)
[subscriber putError:nil];
else
{
[subscriber putNext:@{@"url": url}];
[subscriber putCompletion];
}
}];
return nil;
}];
}
+ (MTSignal *)signalForVCardItemProvider:(NSItemProvider *)itemProvider
{
return [[MTSignal alloc] initWithGenerator:^id<MTDisposable>(MTSubscriber *subscriber)
{
[itemProvider loadItemForTypeIdentifier:(NSString *)kUTTypeVCard options:nil completionHandler:^(NSData *vcard, NSError *error)
{
if (error != nil)
[subscriber putError:nil];
else
{
[subscriber putNext:@{@"contact": vcard}];
[subscriber putCompletion];
}
}];
return nil;
}];
}
+ (MTSignal *)signalForPassKitItemProvider:(NSItemProvider *)itemProvider
{
return [[MTSignal alloc] initWithGenerator:^id<MTDisposable>(MTSubscriber *subscriber)
{
[itemProvider loadItemForTypeIdentifier:@"com.apple.pkpass" options:nil completionHandler:^(id data, NSError *error)
{
if (error != nil)
{
[subscriber putError:nil];
}
else
{
NSError *parseError;
PKPass *pass = [[PKPass alloc] initWithData:data error:&parseError];
if (parseError != nil)
{
[subscriber putError:nil];
}
else
{
NSString *fileName = [NSString stringWithFormat:@"%@.pkpass", pass.serialNumber];
[subscriber putNext:@{@"data": data, @"fileName": fileName, @"mimeType": @"application/vnd.apple.pkpass"}];
[subscriber putCompletion];
}
}
}];
return nil;
}];
}
static void set_bits(uint8_t *bytes, int32_t bitOffset, int32_t numBits, int32_t value) {
numBits = (unsigned int)pow(2, numBits) - 1; //this will only work up to 32 bits, of course
uint8_t *data = bytes;
data += bitOffset / 8;
bitOffset %= 8;
*((int32_t *)data) |= ((value) << bitOffset);
}
+ (NSData *)audioWaveform:(NSURL *)url {
NSDictionary *outputSettings = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithInt:kAudioFormatLinearPCM], AVFormatIDKey,
[NSNumber numberWithFloat:44100.0], AVSampleRateKey,
[NSNumber numberWithInt:16], AVLinearPCMBitDepthKey,
[NSNumber numberWithBool:NO], AVLinearPCMIsNonInterleaved,
[NSNumber numberWithBool:NO], AVLinearPCMIsFloatKey,
[NSNumber numberWithBool:NO], AVLinearPCMIsBigEndianKey,
nil];
AVURLAsset *asset = [AVURLAsset URLAssetWithURL:url options:nil];
if (asset == nil) {
NSLog(@"asset is not defined!");
return nil;
}
NSError *assetError = nil;
AVAssetReader *iPodAssetReader = [AVAssetReader assetReaderWithAsset:asset error:&assetError];
if (assetError) {
NSLog (@"error: %@", assetError);
return nil;
}
AVAssetReaderOutput *readerOutput = [AVAssetReaderAudioMixOutput assetReaderAudioMixOutputWithAudioTracks:asset.tracks audioSettings:outputSettings];
if (! [iPodAssetReader canAddOutput: readerOutput]) {
NSLog (@"can't add reader output... die!");
return nil;
}
// add output reader to reader
[iPodAssetReader addOutput: readerOutput];
if (![iPodAssetReader startReading]) {
NSLog(@"Unable to start reading!");
return nil;
}
NSMutableData *_waveformSamples = [[NSMutableData alloc] init];
int16_t _waveformPeak = 0;
int _waveformPeakCount = 0;
while (iPodAssetReader.status == AVAssetReaderStatusReading) {
CMSampleBufferRef nextBuffer = [readerOutput copyNextSampleBuffer];
if (nextBuffer) {
AudioBufferList abl;
CMBlockBufferRef blockBuffer = NULL;
CMSampleBufferGetAudioBufferListWithRetainedBlockBuffer(nextBuffer, NULL, &abl, sizeof(abl), NULL, NULL, kCMSampleBufferFlag_AudioBufferList_Assure16ByteAlignment, &blockBuffer);
UInt64 size = CMSampleBufferGetTotalSampleSize(nextBuffer);
if (size != 0) {
int16_t *samples = (int16_t *)(abl.mBuffers[0].mData);
int count = (int)size / 2;
for (int i = 0; i < count; i++) {
int16_t sample = samples[i];
if (sample < 0) {
sample = -sample;
}
if (_waveformPeak < sample) {
_waveformPeak = sample;
}
_waveformPeakCount++;
if (_waveformPeakCount >= 100) {
[_waveformSamples appendBytes:&_waveformPeak length:2];
_waveformPeak = 0;
_waveformPeakCount = 0;
}
}
}
CFRelease(nextBuffer);
if (blockBuffer) {
CFRelease(blockBuffer);
}
}
else {
break;
}
}
int16_t scaledSamples[100];
memset(scaledSamples, 0, 100 * 2);
int16_t *samples = _waveformSamples.mutableBytes;
int count = (int)_waveformSamples.length / 2;
for (int i = 0; i < count; i++) {
int16_t sample = samples[i];
int index = i * 100 / count;
if (scaledSamples[index] < sample) {
scaledSamples[index] = sample;
}
}
int16_t peak = 0;
int64_t sumSamples = 0;
for (int i = 0; i < 100; i++) {
int16_t sample = scaledSamples[i];
if (peak < sample) {
peak = sample;
}
sumSamples += sample;
}
uint16_t calculatedPeak = 0;
calculatedPeak = (uint16_t)(sumSamples * 1.8f / 100);
if (calculatedPeak < 2500) {
calculatedPeak = 2500;
}
for (int i = 0; i < 100; i++) {
uint16_t sample = (uint16_t)((int64_t)samples[i]);
if (sample > calculatedPeak) {
scaledSamples[i] = calculatedPeak;
}
}
int numSamples = 100;
int bitstreamLength = (numSamples * 5) / 8 + (((numSamples * 5) % 8) == 0 ? 0 : 1);
NSMutableData *result = [[NSMutableData alloc] initWithLength:bitstreamLength];
{
int32_t maxSample = peak;
uint16_t const *samples = (uint16_t *)scaledSamples;
uint8_t *bytes = result.mutableBytes;
for (int i = 0; i < numSamples; i++) {
int32_t value = MIN(31, ABS((int32_t)samples[i]) * 31 / maxSample);
set_bits(bytes, i * 5, 5, value & 31);
}
}
return result;
}
@end
@@ -0,0 +1,373 @@
#import <ShareItemsImpl/TGShareLocationSignals.h>
#import <MtProtoKit/MtProtoKit.h>
NSString *const TGShareAppleMapsHost = @"maps.apple.com";
NSString *const TGShareAppleMapsPath = @"/maps";
NSString *const TGShareAppleMapsLatLonKey = @"ll";
NSString *const TGShareAppleMapsNameKey = @"q";
NSString *const TGShareAppleMapsAddressKey = @"address";
NSString *const TGShareAppleMapsIdKey = @"auid";
NSString *const TGShareAppleMapsProvider = @"apple";
NSString *const TGShareFoursquareHost = @"foursquare.com";
NSString *const TGShareFoursquareVenuePath = @"/v";
NSString *const TGShareFoursquareVenueEndpointUrl = @"https://api.foursquare.com/v2/venues/";
NSString *const TGShareFoursquareClientId = @"BN3GWQF1OLMLKKQTFL0OADWD1X1WCDNISPPOT1EMMUYZTQV1";
NSString *const TGShareFoursquareClientSecret = @"WEEZHCKI040UVW2KWW5ZXFAZ0FMMHKQ4HQBWXVSX4WXWBWYN";
NSString *const TGShareFoursquareVersion = @"20150326";
NSString *const TGShareFoursquareVenuesCountLimit = @"25";
NSString *const TGShareFoursquareLocale = @"en";
NSString *const TGShareFoursquareProvider = @"foursquare";
NSString *const TGShareGoogleShortenerEndpointUrl = @"https://www.googleapis.com/urlshortener/v1/url";
NSString *const TGShareGoogleAPIKey = @"AIzaSyBCTH4aAdvi0MgDGlGNmQAaFS8GTNBrfj4";
NSString *const TGShareGoogleMapsShortHost = @"goo.gl";
NSString *const TGShareGoogleMapsShortPath = @"/maps";
NSString *const TGShareGoogleMapsHost = @"google.com";
NSString *const TGShareGoogleMapsSearchPath = @"maps/search";
NSString *const TGShareGoogleMapsPlacePath = @"maps/place";
NSString *const TGShareGoogleProvider = @"google";
@implementation TGShareLocationResult
- (instancetype)initWithLatitude:(double)latitude longitude:(double)longitude title:(NSString *)title address:(NSString *)address provider:(NSString *)provider venueId:(NSString *)venueId venueType:(NSString *)venueType {
self = [super init];
if (self != nil) {
_latitude = latitude;
_longitude = longitude;
_title = title;
_address = address;
_provider = provider;
_venueId = venueId;
_venueType = venueType;
}
return self;
}
@end
@interface TGQueryStringComponent : NSObject {
@private
NSString *_key;
NSString *_value;
}
@property (readwrite, nonatomic, retain) id key;
@property (readwrite, nonatomic, retain) id value;
- (id)initWithKey:(id)key value:(id)value;
- (NSString *)URLEncodedStringValueWithEncoding:(NSStringEncoding)stringEncoding;
@end
NSString * TGURLEncodedStringFromStringWithEncoding(NSString *string, NSStringEncoding encoding) {
static NSString * const kAFLegalCharactersToBeEscaped = @"?!@#$^&%*+=,:;'\"`<>()[]{}/\\|~ ";
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
NSString *unescapedString = [string stringByReplacingPercentEscapesUsingEncoding:encoding];
if (unescapedString) {
string = unescapedString;
}
return (__bridge NSString *)CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault, (__bridge CFStringRef)string, NULL, (__bridge CFStringRef)kAFLegalCharactersToBeEscaped, CFStringConvertNSStringEncodingToEncoding(encoding));
#pragma clang diagnostic pop
}
@implementation TGQueryStringComponent
@synthesize key = _key;
@synthesize value = _value;
- (id)initWithKey:(id)key value:(id)value {
self = [super init];
if (!self) {
return nil;
}
self.key = key;
self.value = value;
return self;
}
- (NSString *)URLEncodedStringValueWithEncoding:(NSStringEncoding)stringEncoding {
return [NSString stringWithFormat:@"%@=%@", self.key, TGURLEncodedStringFromStringWithEncoding([self.value description], stringEncoding)];
}
@end
static NSString * TGQueryStringFromParametersWithEncoding(NSDictionary *parameters, NSStringEncoding stringEncoding);
static NSArray * TGQueryStringComponentsFromKeyAndValue(NSString *key, id value);
NSArray * TGQueryStringComponentsFromKeyAndDictionaryValue(NSString *key, NSDictionary *value);
NSArray * TGQueryStringComponentsFromKeyAndArrayValue(NSString *key, NSArray *value);
static NSString * TGQueryStringFromParametersWithEncoding(NSDictionary *parameters, NSStringEncoding stringEncoding) {
NSMutableArray *mutableComponents = [NSMutableArray array];
for (TGQueryStringComponent *component in TGQueryStringComponentsFromKeyAndValue(nil, parameters)) {
[mutableComponents addObject:[component URLEncodedStringValueWithEncoding:stringEncoding]];
}
return [mutableComponents componentsJoinedByString:@"&"];
}
static NSArray * TGQueryStringComponentsFromKeyAndValue(NSString *key, id value) {
NSMutableArray *mutableQueryStringComponents = [NSMutableArray array];
if([value isKindOfClass:[NSDictionary class]]) {
[mutableQueryStringComponents addObjectsFromArray:TGQueryStringComponentsFromKeyAndDictionaryValue(key, value)];
} else if([value isKindOfClass:[NSArray class]]) {
[mutableQueryStringComponents addObjectsFromArray:TGQueryStringComponentsFromKeyAndArrayValue(key, value)];
} else {
[mutableQueryStringComponents addObject:[[TGQueryStringComponent alloc] initWithKey:key value:value]];
}
return mutableQueryStringComponents;
}
NSArray * TGQueryStringComponentsFromKeyAndDictionaryValue(NSString *key, NSDictionary *value){
NSMutableArray *mutableQueryStringComponents = [NSMutableArray array];
[value enumerateKeysAndObjectsUsingBlock:^(id nestedKey, id nestedValue, __unused BOOL *stop) {
[mutableQueryStringComponents addObjectsFromArray:TGQueryStringComponentsFromKeyAndValue((key ? [NSString stringWithFormat:@"%@[%@]", key, nestedKey] : nestedKey), nestedValue)];
}];
return mutableQueryStringComponents;
}
NSArray * TGQueryStringComponentsFromKeyAndArrayValue(NSString *key, NSArray *value) {
NSMutableArray *mutableQueryStringComponents = [NSMutableArray array];
[value enumerateObjectsUsingBlock:^(id nestedValue, __unused NSUInteger idx, __unused BOOL *stop) {
[mutableQueryStringComponents addObjectsFromArray:TGQueryStringComponentsFromKeyAndValue([NSString stringWithFormat:@"%@[]", key], nestedValue)];
}];
return mutableQueryStringComponents;
}
@implementation TGShareLocationSignals
+ (MTSignal *)locationMessageContentForURL:(NSURL *)url
{
if ([self isAppleMapsURL:url])
return [self _appleMapsLocationContentForURL:url];
else if ([self isFoursquareURL:url])
return [self _foursquareLocationForURL:url];
return [MTSignal single:nil];
}
+ (MTSignal *)_appleMapsLocationContentForURL:(NSURL *)url
{
NSURLComponents *urlComponents = [NSURLComponents componentsWithURL:url resolvingAgainstBaseURL:false];
NSArray *queryItems = urlComponents.queryItems;
NSString *latLon = nil;
NSString *name = nil;
NSString *address = nil;
NSString *venueId = nil;
for (NSURLQueryItem *queryItem in queryItems)
{
if ([queryItem.name isEqualToString:TGShareAppleMapsLatLonKey])
{
latLon = queryItem.value;
}
else if ([queryItem.name isEqualToString:TGShareAppleMapsNameKey])
{
if (![queryItem.value isEqualToString:latLon])
name = queryItem.value;
}
else if ([queryItem.name isEqualToString:TGShareAppleMapsAddressKey])
{
address = queryItem.value;
}
else if ([queryItem.name isEqualToString:TGShareAppleMapsIdKey])
{
venueId = queryItem.value;
}
}
if (latLon == nil)
return [MTSignal fail:nil];
NSArray *coordComponents = [latLon componentsSeparatedByString:@","];
if (coordComponents.count != 2)
return [MTSignal fail:nil];
double latitude = [coordComponents.firstObject floatValue];
double longitude = [coordComponents.lastObject floatValue];
return [MTSignal single:[[TGShareLocationResult alloc] initWithLatitude:latitude longitude:longitude title:name address:address provider:TGShareAppleMapsProvider venueId:venueId venueType:@""]];
}
+ (MTSignal *)_foursquareLocationForURL:(NSURL *)url
{
NSArray *pathComponents = url.pathComponents;
NSString *venueId = nil;
for (NSString *component in pathComponents)
{
if (component.length == 24)
{
venueId = component;
break;
}
}
if (venueId == nil)
return [MTSignal fail:nil];
NSString *urlString = [NSString stringWithFormat:@"%@?%@", [TGShareFoursquareVenueEndpointUrl stringByAppendingPathComponent:venueId], TGQueryStringFromParametersWithEncoding([self _defaultParametersForFoursquare], NSUTF8StringEncoding)];
return [[MTHttpRequestOperation dataForHttpUrl:[NSURL URLWithString:urlString]] mapToSignal:^id(MTHttpResponse *response)
{
NSData *data = response.data;
id json = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
if (![json respondsToSelector:@selector(objectForKey:)])
return nil;
NSDictionary *venue = json[@"response"][@"venue"];
if (![venue respondsToSelector:@selector(objectForKey:)])
return nil;
NSString *name = venue[@"name"];
NSDictionary *location = venue[@"location"];
NSString *address = location[@"address"];
if (address.length == 0)
address = location[@"crossStreet"];
if (address.length == 0)
address = location[@"city"];
if (address.length == 0)
address = location[@"country"];
if (address.length == 0)
address = @"";
double latitude = [location[@"lat"] doubleValue];
double longitude = [location[@"lng"] doubleValue];
if (name.length == 0)
return [MTSignal fail:nil];
return [MTSignal single:[[TGShareLocationResult alloc] initWithLatitude:latitude longitude:longitude title:name address:address provider:TGShareFoursquareProvider venueId:venueId venueType:@""]];
}];
}
+ (MTSignal *)_googleMapsLocationForURL:(NSURL *)url
{
NSString *shortenerUrl = [NSString stringWithFormat:@"%@?fields=longUrl,status&shortUrl=%@&key=%@", TGShareGoogleShortenerEndpointUrl, TGURLEncodedStringFromStringWithEncoding(url.absoluteString, NSUTF8StringEncoding), TGShareGoogleAPIKey];
MTSignal *shortenerSignal = [[MTHttpRequestOperation dataForHttpUrl:[NSURL URLWithString:shortenerUrl]] mapToSignal:^MTSignal *(MTHttpResponse *response)
{
NSData *data = response.data;
id json = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
if (![json respondsToSelector:@selector(objectForKey:)])
return [MTSignal fail:nil];
NSString *status = json[@"status"];
if (![status isEqualToString:@"OK"])
return [MTSignal fail:nil];
return [MTSignal single:[NSURL URLWithString:json[@"longUrl"]]];
}];
MTSignal *(^processLongUrl)(NSURL *) = ^MTSignal *(NSURL *longUrl)
{
NSArray *pathComponents = longUrl.pathComponents;
bool isSearch = false;
double latitude = 0.0;
double longitude = 0.0;
for (NSString *component in pathComponents)
{
if ([component isEqualToString:@"search"])
{
isSearch = true;
}
else if ([component isEqualToString:@"place"])
{
return [MTSignal fail:nil];
}
else if (isSearch && [component containsString:@","])
{
NSArray *coordinates = [component componentsSeparatedByString:@","];
if (coordinates.count == 2)
{
latitude = [coordinates.firstObject doubleValue];
longitude = [coordinates.lastObject doubleValue];
break;
}
}
}
if (fabs(latitude) < DBL_EPSILON && fabs(longitude) < DBL_EPSILON)
return [MTSignal fail:nil];
return [MTSignal single:[[TGShareLocationResult alloc] initWithLatitude:latitude longitude:longitude title:nil address:nil provider:nil venueId:nil venueType:nil]];
};
MTSignal *signal = nil;
if ([self _isShortGoogleMapsURL:url])
{
signal = [shortenerSignal mapToSignal:^MTSignal *(NSURL *longUrl)
{
return processLongUrl(longUrl);
}];
}
else
{
signal = processLongUrl(url);
}
return [signal catch:^MTSignal *(id error)
{
return [MTSignal single:url.absoluteString];
}];
}
+ (NSDictionary *)_defaultParametersForFoursquare
{
return @
{
@"v": TGShareFoursquareVersion,
@"locale": TGShareFoursquareLocale,
@"client_id": TGShareFoursquareClientId,
@"client_secret" :TGShareFoursquareClientSecret
};
}
+ (bool)isLocationURL:(NSURL *)url
{
return [self isAppleMapsURL:url] || [self isFoursquareURL:url];
}
+ (bool)isAppleMapsURL:(NSURL *)url
{
return ([url.host isEqualToString:TGShareAppleMapsHost] && [url.path isEqualToString:TGShareAppleMapsPath]);
}
+ (bool)isFoursquareURL:(NSURL *)url
{
return ([url.host isEqualToString:TGShareFoursquareHost] && [url.path hasPrefix:TGShareFoursquareVenuePath]);
}
+ (bool)_isShortGoogleMapsURL:(NSURL *)url
{
return ([url.host isEqualToString:TGShareGoogleMapsShortHost] && [url.path hasPrefix:TGShareGoogleMapsShortPath]);
}
+ (bool)_isLongGoogleMapsURL:(NSURL *)url
{
return ([url.host isEqualToString:TGShareGoogleMapsHost] && ([url.path hasPrefix:TGShareGoogleMapsSearchPath] || [url.path hasPrefix:TGShareGoogleMapsPlacePath]));
}
+ (bool)isGoogleMapsURL:(NSURL *)url
{
return [self _isShortGoogleMapsURL:url] || [self _isLongGoogleMapsURL:url];
}
@end