Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
signalapp
GitHub Repository: signalapp/Signal-iOS
Path: blob/main/SignalServiceKit/Messages/Attachments/OWSMediaUtils.swift
1 views
//
// Copyright 2018 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
//

public import AVFoundation
import Foundation
public import UIKit

public enum OWSMediaError: Error {
    case failure(description: String)
}

public enum OWSMediaUtils {

    public static func thumbnail(forImage image: UIImage, maxDimensionPixels: CGFloat) throws -> UIImage {
        if
            image.pixelSize.width <= maxDimensionPixels,
            image.pixelSize.height <= maxDimensionPixels
        {
            let result = image.withNativeScale
            return result
        }
        guard let thumbnailImage = image.resized(maxDimensionPixels: maxDimensionPixels) else {
            throw OWSMediaError.failure(description: "Could not thumbnail image.")
        }
        guard nil != thumbnailImage.cgImage else {
            throw OWSMediaError.failure(description: "Missing cgImage.")
        }
        let result = thumbnailImage.withNativeScale
        return result
    }

    public static let videoStillFrameMimeType = MimeType.imageJpeg

    public static func generateThumbnail(forVideo asset: AVAsset, maxSizePixels: CGSize) throws -> UIImage {
        try validateVideoAsset(asset)
        let generator = AVAssetImageGenerator(asset: asset)
        generator.maximumSize = maxSizePixels
        generator.appliesPreferredTrackTransform = true
        let time: CMTime = CMTimeMake(value: 1, timescale: 60)
        let cgImage = try generator.copyCGImage(at: time, actualTime: nil)
        return UIImage(cgImage: cgImage, scale: UIScreen.main.scale, orientation: .up)
    }

    public static func validateVideoExtension(ofPath path: String) throws {
        guard let contentType = MimeTypeUtil.mimeTypeForFileExtension((path as NSString).pathExtension) else {
            throw OWSGenericError("video file has unknown content type")
        }
        guard MimeTypeUtil.isSupportedVideoMimeType(contentType) else {
            throw OWSGenericError("video file has invalid content type")
        }
    }

    public static func validateVideoAsset(atPath path: String) throws {
        let asset = AVURLAsset(url: URL(fileURLWithPath: path), options: nil)
        try validateVideoAsset(asset)
    }

    public static func validateVideoAsset(_ asset: AVAsset) throws {
        var maxTrackSize = CGSize.zero
        for track: AVAssetTrack in asset.tracks(withMediaType: .video) {
            let trackSize: CGSize = track.naturalSize
            maxTrackSize.width = max(maxTrackSize.width, trackSize.width)
            maxTrackSize.height = max(maxTrackSize.height, trackSize.height)
        }
        if maxTrackSize.width < 1.0 || maxTrackSize.height < 1.0 {
            throw OWSGenericError("invalid video size: \(maxTrackSize)")
        }
        if maxTrackSize.width > kMaxVideoDimensions || maxTrackSize.height > kMaxVideoDimensions {
            throw OWSGenericError("invalid video dimensions: \(maxTrackSize)")
        }
    }

    // MARK: Constants

    /**
     * Media Size constraints from Signal-Android
     *
     * https://github.com/signalapp/Signal-Android/blob/c4bc2162f23e0fd6bc25941af8fb7454d91a4a35/app/src/main/java/org/thoughtcrime/securesms/mms/PushMediaConstraints.java
     */
    public static let kMaxFileSizeAnimatedImage: UInt64 = 25 * 1024 * 1024
    public static let kMaxFileSizeImage: UInt64 = 8 * 1024 * 1024

    public static let kMaxVideoDimensions: CGFloat = 4096 // 4k video width
    public static let kMaxImageDimensions: CGFloat = 12 * 1024

    /// Text past this size on send (excluding forwarding) is truncated to this length and the rest
    /// is sent as an oversize text attachment.
    /// Text past this side on receive is considered an invalid message and will be dropped.
    public static let kOversizeTextMessageSizeThresholdBytes = 2 * 1024
    /// Oversize text attachments past this size will be truncated on send.
    public static let kMaxOversizeTextMessageSendSizeBytes = 64 * 1024
    /// Oversize text attachments past this size will be rejected on receive. (Larger than send
    /// to support legacy cases)
    public static let kMaxOversizeTextMessageReceiveSizeBytes = 128 * 1024
}