mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2026-04-07 21:57:51 +00:00
Fixes
fix localeWithStrings globally (#30)
Fix badge on zoomed devices. closes #9
Hide channel bottom panel closes #27
Another attempt to fix badge on some Zoomed devices
Force System Share sheet tg://sg/debug
fixes for device badge
New Crowdin updates (#34)
* New translations sglocalizable.strings (Chinese Traditional)
* New translations sglocalizable.strings (Chinese Simplified)
* New translations sglocalizable.strings (Chinese Traditional)
Fix input panel hidden on selection (#31)
* added if check for selectionState != nil
* same order of subnodes
Revert "Fix input panel hidden on selection (#31)"
This reverts commit e8a8bb1496.
Fix input panel for channels Closes #37
Quickly share links with system's share menu
force tabbar when editing
increase height for correct animation
New translations sglocalizable.strings (Ukrainian) (#38)
Hide Post Story button
Fix 10.15.1
Fix archive option for long-tap
Enable in-app Safari
Disable some unsupported purchases
disableDeleteChatSwipeOption + refactor restart alert
Hide bot in suggestions list
Fix merge v11.0
Fix exceptions for safari webview controller
New Crowdin updates (#47)
* New translations sglocalizable.strings (Romanian)
* New translations sglocalizable.strings (French)
* New translations sglocalizable.strings (Spanish)
* New translations sglocalizable.strings (Afrikaans)
* New translations sglocalizable.strings (Arabic)
* New translations sglocalizable.strings (Catalan)
* New translations sglocalizable.strings (Czech)
* New translations sglocalizable.strings (Danish)
* New translations sglocalizable.strings (German)
* New translations sglocalizable.strings (Greek)
* New translations sglocalizable.strings (Finnish)
* New translations sglocalizable.strings (Hebrew)
* New translations sglocalizable.strings (Hungarian)
* New translations sglocalizable.strings (Italian)
* New translations sglocalizable.strings (Japanese)
* New translations sglocalizable.strings (Korean)
* New translations sglocalizable.strings (Dutch)
* New translations sglocalizable.strings (Norwegian)
* New translations sglocalizable.strings (Polish)
* New translations sglocalizable.strings (Portuguese)
* New translations sglocalizable.strings (Serbian (Cyrillic))
* New translations sglocalizable.strings (Swedish)
* New translations sglocalizable.strings (Turkish)
* New translations sglocalizable.strings (Vietnamese)
* New translations sglocalizable.strings (Indonesian)
* New translations sglocalizable.strings (Hindi)
* New translations sglocalizable.strings (Uzbek)
New Crowdin updates (#49)
* New translations sglocalizable.strings (Arabic)
* New translations sglocalizable.strings (Arabic)
New translations sglocalizable.strings (Russian) (#51)
Call confirmation
WIP Settings search
Settings Search
Localize placeholder
Update AccountUtils.swift
mark mutual contact
Align back context action to left
New Crowdin updates (#54)
* New translations sglocalizable.strings (Chinese Simplified)
* New translations sglocalizable.strings (Chinese Traditional)
* New translations sglocalizable.strings (Ukrainian)
Independent Playground app for simulator
New translations sglocalizable.strings (Ukrainian) (#55)
Playground UIKit base and controllers
Inject SwiftUI view with overflow to AsyncDisplayKit
Launch Playgound project on simulator
Create .swiftformat
Move Playground to example
Update .swiftformat
Init SwiftUIViewController
wip
New translations sglocalizable.strings (Chinese Traditional) (#57)
Xcode 16 fixes
Fix
New translations sglocalizable.strings (Italian) (#59)
New translations sglocalizable.strings (Chinese Simplified) (#63)
Force disable CallKit integration due to missing NSE Entitlement
Fix merge
Fix whole chat translator
Sweetpad config
Bump version
11.3.1 fixes
Mutual contact placement fix
Disable Video PIP swipe
Update versions.json
Fix PIP crash
1841 lines
100 KiB
Swift
1841 lines
100 KiB
Swift
import Foundation
|
|
import UIKit
|
|
import Display
|
|
import TelegramCore
|
|
import AVFoundation
|
|
import VideoToolbox
|
|
|
|
public enum EditorToolKey: Int32, CaseIterable {
|
|
case enhance
|
|
case brightness
|
|
case contrast
|
|
case saturation
|
|
case warmth
|
|
case fade
|
|
case highlights
|
|
case shadows
|
|
case vignette
|
|
case grain
|
|
case sharpen
|
|
case shadowsTint
|
|
case highlightsTint
|
|
case blur
|
|
case curves
|
|
case stickerOutline
|
|
|
|
static let adjustmentToolsKeys: [EditorToolKey] = [
|
|
.enhance,
|
|
.brightness,
|
|
.contrast,
|
|
.saturation,
|
|
.warmth,
|
|
.fade,
|
|
.highlights,
|
|
.shadows,
|
|
.vignette,
|
|
.grain,
|
|
.sharpen
|
|
]
|
|
}
|
|
|
|
public struct VideoPositionChange: Codable, Equatable {
|
|
private enum CodingKeys: String, CodingKey {
|
|
case additional
|
|
case timestamp
|
|
}
|
|
|
|
public let additional: Bool
|
|
public let timestamp: Double
|
|
|
|
public init(
|
|
additional: Bool,
|
|
timestamp: Double
|
|
) {
|
|
self.additional = additional
|
|
self.timestamp = timestamp
|
|
}
|
|
}
|
|
|
|
public struct MediaAudioTrack: Codable, Equatable {
|
|
private enum CodingKeys: String, CodingKey {
|
|
case path
|
|
case artist
|
|
case title
|
|
case duration
|
|
}
|
|
|
|
public let path: String
|
|
public let artist: String?
|
|
public let title: String?
|
|
public let duration: Double
|
|
|
|
public init(
|
|
path: String,
|
|
artist: String?,
|
|
title: String?,
|
|
duration: Double
|
|
) {
|
|
self.path = path
|
|
self.artist = artist
|
|
self.title = title
|
|
self.duration = duration
|
|
}
|
|
}
|
|
|
|
public struct MediaAudioTrackSamples: Equatable {
|
|
private enum CodingKeys: String, CodingKey {
|
|
case samples
|
|
case peak
|
|
}
|
|
|
|
public let samples: Data
|
|
public let peak: Int32
|
|
|
|
public init(
|
|
samples: Data,
|
|
peak: Int32
|
|
) {
|
|
self.samples = samples
|
|
self.peak = peak
|
|
}
|
|
}
|
|
|
|
public enum MediaQualityPreset: Int32 {
|
|
case compressedDefault
|
|
case compressedVeryLow
|
|
case compressedLow
|
|
case compressedMedium
|
|
case compressedHigh
|
|
case compressedVeryHigh
|
|
case animation
|
|
case videoMessage
|
|
case videoMessageHD
|
|
case profileLow
|
|
case profile
|
|
case profileHigh
|
|
case profileVeryHigh
|
|
case sticker
|
|
case passthrough
|
|
|
|
var hasAudio: Bool {
|
|
switch self {
|
|
case .animation, .profileLow, .profile, .profileHigh, .profileVeryHigh, .sticker:
|
|
return false
|
|
default:
|
|
return true
|
|
}
|
|
}
|
|
|
|
var maximumDimensions: CGFloat {
|
|
switch self {
|
|
case .compressedVeryLow:
|
|
return 480.0
|
|
case .compressedLow:
|
|
return 640.0
|
|
case .compressedMedium:
|
|
return 848.0
|
|
case .compressedHigh:
|
|
return 1280.0
|
|
case .compressedVeryHigh:
|
|
return 1920.0
|
|
case .videoMessageHD:
|
|
return 384.0
|
|
case .videoMessage:
|
|
return 400.0
|
|
case .profileLow:
|
|
return 720.0
|
|
case .profile, .profileHigh, .profileVeryHigh:
|
|
return 800.0
|
|
case .sticker:
|
|
return 512.0
|
|
default:
|
|
return 848.0
|
|
}
|
|
}
|
|
|
|
var videoBitrateKbps: Int {
|
|
switch self {
|
|
case .compressedVeryLow:
|
|
return 400
|
|
case .compressedLow:
|
|
return 700
|
|
case .compressedMedium:
|
|
return 1600
|
|
case .compressedHigh:
|
|
return 3000
|
|
case .compressedVeryHigh:
|
|
return 6600
|
|
case .videoMessageHD:
|
|
return 2000
|
|
case .videoMessage:
|
|
return 1000
|
|
case .profileLow:
|
|
return 1100
|
|
case .profile:
|
|
return 1500
|
|
case .profileHigh:
|
|
return 2000
|
|
case .profileVeryHigh:
|
|
return 2400
|
|
case .sticker:
|
|
return 1000
|
|
default:
|
|
return 900
|
|
}
|
|
}
|
|
|
|
var audioBitrateKbps: Int {
|
|
switch self {
|
|
case .compressedVeryLow, .compressedLow:
|
|
return 32
|
|
case .compressedMedium, .compressedHigh, .compressedVeryHigh, .videoMessage:
|
|
return 64
|
|
default:
|
|
return 0
|
|
}
|
|
}
|
|
|
|
var audioChannelsCount: Int {
|
|
switch self {
|
|
case .compressedVeryLow, .compressedLow:
|
|
return 1
|
|
default:
|
|
return 2
|
|
}
|
|
}
|
|
}
|
|
|
|
public enum MediaCropOrientation: Int32 {
|
|
case up
|
|
case down
|
|
case left
|
|
case right
|
|
|
|
var rotation: CGFloat {
|
|
switch self {
|
|
case .up:
|
|
return 0.0
|
|
case .down:
|
|
return .pi
|
|
case .left:
|
|
return .pi / 2.0
|
|
case .right:
|
|
return -.pi / 2.0
|
|
}
|
|
}
|
|
|
|
var isSideward: Bool {
|
|
switch self {
|
|
case .left, .right:
|
|
return true
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
}
|
|
|
|
public final class MediaEditorValues: Codable, Equatable {
|
|
public static func == (lhs: MediaEditorValues, rhs: MediaEditorValues) -> Bool {
|
|
if lhs.peerId != rhs.peerId {
|
|
return false
|
|
}
|
|
if lhs.originalDimensions != rhs.originalDimensions {
|
|
return false
|
|
}
|
|
if lhs.cropOffset != rhs.cropOffset {
|
|
return false
|
|
}
|
|
if lhs.cropRect != rhs.cropRect {
|
|
return false
|
|
}
|
|
if lhs.cropScale != rhs.cropScale {
|
|
return false
|
|
}
|
|
if lhs.cropRotation != rhs.cropRotation {
|
|
return false
|
|
}
|
|
if lhs.cropMirroring != rhs.cropMirroring {
|
|
return false
|
|
}
|
|
if lhs.cropOrientation != rhs.cropOrientation {
|
|
return false
|
|
}
|
|
if lhs.gradientColors != rhs.gradientColors {
|
|
return false
|
|
}
|
|
if lhs.videoTrimRange != rhs.videoTrimRange {
|
|
return false
|
|
}
|
|
if lhs.videoIsMuted != rhs.videoIsMuted {
|
|
return false
|
|
}
|
|
if lhs.videoIsFullHd != rhs.videoIsFullHd {
|
|
return false
|
|
}
|
|
if lhs.videoIsMirrored != rhs.videoIsMirrored {
|
|
return false
|
|
}
|
|
if lhs.videoVolume != rhs.videoVolume {
|
|
return false
|
|
}
|
|
if lhs.additionalVideoPath != rhs.additionalVideoPath {
|
|
return false
|
|
}
|
|
if lhs.additionalVideoIsDual != rhs.additionalVideoIsDual {
|
|
return false
|
|
}
|
|
if lhs.additionalVideoPosition != rhs.additionalVideoPosition {
|
|
return false
|
|
}
|
|
if lhs.additionalVideoScale != rhs.additionalVideoScale {
|
|
return false
|
|
}
|
|
if lhs.additionalVideoRotation != rhs.additionalVideoRotation {
|
|
return false
|
|
}
|
|
if lhs.additionalVideoPositionChanges != rhs.additionalVideoPositionChanges {
|
|
return false
|
|
}
|
|
if lhs.additionalVideoTrimRange != rhs.additionalVideoTrimRange {
|
|
return false
|
|
}
|
|
if lhs.additionalVideoOffset != rhs.additionalVideoOffset {
|
|
return false
|
|
}
|
|
if lhs.additionalVideoVolume != rhs.additionalVideoVolume {
|
|
return false
|
|
}
|
|
if lhs.collage != rhs.collage {
|
|
return false
|
|
}
|
|
if lhs.drawing !== rhs.drawing {
|
|
return false
|
|
}
|
|
if lhs.maskDrawing !== rhs.maskDrawing {
|
|
return false
|
|
}
|
|
if lhs.entities != rhs.entities {
|
|
return false
|
|
}
|
|
if lhs.audioTrack != rhs.audioTrack {
|
|
return false
|
|
}
|
|
if lhs.audioTrackTrimRange != rhs.audioTrackTrimRange {
|
|
return false
|
|
}
|
|
if lhs.audioTrackOffset != rhs.audioTrackOffset {
|
|
return false
|
|
}
|
|
if lhs.audioTrackVolume != rhs.audioTrackVolume {
|
|
return false
|
|
}
|
|
if lhs.audioTrackSamples != rhs.audioTrackSamples {
|
|
return false
|
|
}
|
|
if lhs.collageTrackSamples != rhs.collageTrackSamples {
|
|
return false
|
|
}
|
|
if lhs.coverImageTimestamp != rhs.coverImageTimestamp {
|
|
return false
|
|
}
|
|
if lhs.nightTheme != rhs.nightTheme {
|
|
return false
|
|
}
|
|
|
|
for key in EditorToolKey.allCases {
|
|
let lhsToolValue = lhs.toolValues[key]
|
|
let rhsToolValue = rhs.toolValues[key]
|
|
if (lhsToolValue == nil) != (rhsToolValue == nil) {
|
|
return false
|
|
}
|
|
if let lhsToolValue = lhsToolValue as? Float, let rhsToolValue = rhsToolValue as? Float {
|
|
if lhsToolValue != rhsToolValue {
|
|
return false
|
|
}
|
|
}
|
|
if let lhsToolValue = lhsToolValue as? BlurValue, let rhsToolValue = rhsToolValue as? BlurValue {
|
|
if lhsToolValue != rhsToolValue {
|
|
return false
|
|
}
|
|
}
|
|
if let lhsToolValue = lhsToolValue as? TintValue, let rhsToolValue = rhsToolValue as? TintValue {
|
|
if lhsToolValue != rhsToolValue {
|
|
return false
|
|
}
|
|
}
|
|
if let lhsToolValue = lhsToolValue as? CurvesValue, let rhsToolValue = rhsToolValue as? CurvesValue {
|
|
if lhsToolValue != rhsToolValue {
|
|
return false
|
|
}
|
|
}
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
private enum CodingKeys: String, CodingKey {
|
|
case peerId
|
|
case originalWidth
|
|
case originalHeight
|
|
case cropOffset
|
|
case cropRect
|
|
case cropScale
|
|
case cropRotation
|
|
case cropMirroring
|
|
case cropOrientation
|
|
case gradientColors
|
|
case videoTrimRange
|
|
case videoIsMuted
|
|
case videoIsFullHd
|
|
case videoIsMirrored
|
|
case videoVolume
|
|
case additionalVideoPath
|
|
case additionalVideoIsDual
|
|
case additionalVideoPosition
|
|
case additionalVideoScale
|
|
case additionalVideoRotation
|
|
case additionalVideoPositionChanges
|
|
case additionalVideoTrimRange
|
|
case additionalVideoOffset
|
|
case additionalVideoVolume
|
|
case collage
|
|
|
|
case nightTheme
|
|
case drawing
|
|
case maskDrawing
|
|
case entities
|
|
case toolValues
|
|
case audioTrack
|
|
case audioTrackTrimRange
|
|
case audioTrackOffset
|
|
case audioTrackVolume
|
|
case coverImageTimestamp
|
|
case qualityPreset
|
|
}
|
|
|
|
public struct VideoCollageItem: Codable, Equatable {
|
|
enum DecodingError: Error {
|
|
case generic
|
|
}
|
|
|
|
private enum CodingKeys: String, CodingKey {
|
|
case contentType
|
|
case contentValue
|
|
case isVideo
|
|
case frame
|
|
case videoTrimRange
|
|
case videoOffset
|
|
case videoVolume
|
|
}
|
|
|
|
public enum Content: Equatable {
|
|
case main
|
|
case imageFile(path: String)
|
|
case videoFile(path: String)
|
|
case asset(localIdentifier: String, isVideo: Bool)
|
|
|
|
public var isVideo: Bool {
|
|
switch self {
|
|
case .videoFile, .asset(_, true):
|
|
return true
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
}
|
|
|
|
public let content: Content
|
|
public let frame: CGRect
|
|
|
|
public let videoTrimRange: Range<Double>?
|
|
public let videoOffset: Double?
|
|
public let videoVolume: CGFloat?
|
|
|
|
public init(
|
|
content: Content,
|
|
frame: CGRect,
|
|
videoTrimRange: Range<Double>?,
|
|
videoOffset: Double?,
|
|
videoVolume: CGFloat?
|
|
) {
|
|
self.content = content
|
|
self.frame = frame
|
|
self.videoTrimRange = videoTrimRange
|
|
self.videoOffset = videoOffset
|
|
self.videoVolume = videoVolume
|
|
}
|
|
|
|
public init(from decoder: Decoder) throws {
|
|
let container = try decoder.container(keyedBy: CodingKeys.self)
|
|
switch try container.decode(Int32.self, forKey: .contentType) {
|
|
case 0:
|
|
self.content = .main
|
|
case 1:
|
|
self.content = .imageFile(path: try container.decode(String.self, forKey: .contentValue))
|
|
case 2:
|
|
self.content = .videoFile(path: try container.decode(String.self, forKey: .contentValue))
|
|
case 3:
|
|
self.content = .asset(localIdentifier: try container.decode(String.self, forKey: .contentValue), isVideo: try container.decode(Bool.self, forKey: .isVideo))
|
|
default:
|
|
throw DecodingError.generic
|
|
}
|
|
self.frame = try container.decode(CGRect.self, forKey: .frame)
|
|
self.videoTrimRange = try container.decodeIfPresent(Range<Double>.self, forKey: .videoTrimRange)
|
|
self.videoOffset = try container.decodeIfPresent(Double.self, forKey: .videoOffset)
|
|
self.videoVolume = try container.decodeIfPresent(CGFloat.self, forKey: .videoVolume)
|
|
}
|
|
|
|
public func encode(to encoder: any Encoder) throws {
|
|
var container = encoder.container(keyedBy: CodingKeys.self)
|
|
switch self.content {
|
|
case .main:
|
|
try container.encode(Int32(0), forKey: .contentType)
|
|
case let .imageFile(value):
|
|
try container.encode(Int32(1), forKey: .contentType)
|
|
try container.encode(value, forKey: .contentValue)
|
|
case let .videoFile(value):
|
|
try container.encode(Int32(2), forKey: .contentType)
|
|
try container.encode(value, forKey: .contentValue)
|
|
case let .asset(value, isVideo):
|
|
try container.encode(Int32(3), forKey: .contentType)
|
|
try container.encode(value, forKey: .contentValue)
|
|
try container.encode(isVideo, forKey: .isVideo)
|
|
}
|
|
try container.encode(self.frame, forKey: .frame)
|
|
try container.encodeIfPresent(self.videoTrimRange, forKey: .videoTrimRange)
|
|
try container.encodeIfPresent(self.videoOffset, forKey: .videoOffset)
|
|
try container.encodeIfPresent(self.videoVolume, forKey: .videoVolume)
|
|
}
|
|
|
|
func withUpdatedVideoTrimRange(_ videoTrimRange: Range<Double>?) -> VideoCollageItem {
|
|
return VideoCollageItem(
|
|
content: self.content,
|
|
frame: self.frame,
|
|
videoTrimRange: videoTrimRange,
|
|
videoOffset: self.videoOffset,
|
|
videoVolume: self.videoVolume
|
|
)
|
|
}
|
|
|
|
func withUpdatedVideoOffset(_ videoOffset: Double?) -> VideoCollageItem {
|
|
return VideoCollageItem(
|
|
content: self.content,
|
|
frame: self.frame,
|
|
videoTrimRange: self.videoTrimRange,
|
|
videoOffset: videoOffset,
|
|
videoVolume: self.videoVolume
|
|
)
|
|
}
|
|
|
|
func withUpdatedVideoVolume(_ videoVolume: CGFloat?) -> VideoCollageItem {
|
|
return VideoCollageItem(
|
|
content: self.content,
|
|
frame: self.frame,
|
|
videoTrimRange: self.videoTrimRange,
|
|
videoOffset: self.videoOffset,
|
|
videoVolume: videoVolume
|
|
)
|
|
}
|
|
}
|
|
|
|
public let peerId: EnginePeer.Id
|
|
|
|
public let originalDimensions: PixelDimensions
|
|
public let cropOffset: CGPoint
|
|
public let cropRect: CGRect?
|
|
public let cropScale: CGFloat
|
|
public let cropRotation: CGFloat
|
|
public let cropMirroring: Bool
|
|
public let cropOrientation: MediaCropOrientation?
|
|
|
|
public let gradientColors: [UIColor]?
|
|
|
|
public let videoTrimRange: Range<Double>?
|
|
public let videoIsMuted: Bool
|
|
public let videoIsFullHd: Bool
|
|
public let videoIsMirrored: Bool
|
|
public let videoVolume: CGFloat?
|
|
|
|
public let additionalVideoPath: String?
|
|
public let additionalVideoIsDual: Bool
|
|
public let additionalVideoPosition: CGPoint?
|
|
public let additionalVideoScale: CGFloat?
|
|
public let additionalVideoRotation: CGFloat?
|
|
public let additionalVideoPositionChanges: [VideoPositionChange]
|
|
|
|
public let additionalVideoTrimRange: Range<Double>?
|
|
public let additionalVideoOffset: Double?
|
|
public let additionalVideoVolume: CGFloat?
|
|
|
|
public let collage: [VideoCollageItem]
|
|
|
|
public let nightTheme: Bool
|
|
public let drawing: UIImage?
|
|
public let maskDrawing: UIImage?
|
|
public let entities: [CodableDrawingEntity]
|
|
public let toolValues: [EditorToolKey: Any]
|
|
|
|
public let audioTrack: MediaAudioTrack?
|
|
public let audioTrackTrimRange: Range<Double>?
|
|
public let audioTrackOffset: Double?
|
|
public let audioTrackVolume: CGFloat?
|
|
public let audioTrackSamples: MediaAudioTrackSamples?
|
|
|
|
public let collageTrackSamples: MediaAudioTrackSamples?
|
|
|
|
public let coverImageTimestamp: Double?
|
|
|
|
public let qualityPreset: MediaQualityPreset?
|
|
|
|
var isStory: Bool {
|
|
return self.qualityPreset == nil
|
|
}
|
|
|
|
var isSticker: Bool {
|
|
return self.qualityPreset == .sticker
|
|
}
|
|
|
|
public var cropValues: (offset: CGPoint, rotation: CGFloat, scale: CGFloat) {
|
|
return (self.cropOffset, self.cropRotation, self.cropScale)
|
|
}
|
|
|
|
public init(
|
|
peerId: EnginePeer.Id,
|
|
originalDimensions: PixelDimensions,
|
|
cropOffset: CGPoint,
|
|
cropRect: CGRect?,
|
|
cropScale: CGFloat,
|
|
cropRotation: CGFloat,
|
|
cropMirroring: Bool,
|
|
cropOrientation: MediaCropOrientation?,
|
|
gradientColors: [UIColor]?,
|
|
videoTrimRange: Range<Double>?,
|
|
videoIsMuted: Bool,
|
|
videoIsFullHd: Bool,
|
|
videoIsMirrored: Bool,
|
|
videoVolume: CGFloat?,
|
|
additionalVideoPath: String?,
|
|
additionalVideoIsDual: Bool,
|
|
additionalVideoPosition: CGPoint?,
|
|
additionalVideoScale: CGFloat?,
|
|
additionalVideoRotation: CGFloat?,
|
|
additionalVideoPositionChanges: [VideoPositionChange],
|
|
additionalVideoTrimRange: Range<Double>?,
|
|
additionalVideoOffset: Double?,
|
|
additionalVideoVolume: CGFloat?,
|
|
collage: [VideoCollageItem],
|
|
nightTheme: Bool,
|
|
drawing: UIImage?,
|
|
maskDrawing: UIImage?,
|
|
entities: [CodableDrawingEntity],
|
|
toolValues: [EditorToolKey: Any],
|
|
audioTrack: MediaAudioTrack?,
|
|
audioTrackTrimRange: Range<Double>?,
|
|
audioTrackOffset: Double?,
|
|
audioTrackVolume: CGFloat?,
|
|
audioTrackSamples: MediaAudioTrackSamples?,
|
|
collageTrackSamples: MediaAudioTrackSamples?,
|
|
coverImageTimestamp: Double?,
|
|
qualityPreset: MediaQualityPreset?
|
|
) {
|
|
self.peerId = peerId
|
|
self.originalDimensions = originalDimensions
|
|
self.cropOffset = cropOffset
|
|
self.cropRect = cropRect
|
|
self.cropScale = cropScale
|
|
self.cropRotation = cropRotation
|
|
self.cropMirroring = cropMirroring
|
|
self.cropOrientation = cropOrientation
|
|
self.gradientColors = gradientColors
|
|
self.videoTrimRange = videoTrimRange
|
|
self.videoIsMuted = videoIsMuted
|
|
self.videoIsFullHd = videoIsFullHd
|
|
self.videoIsMirrored = videoIsMirrored
|
|
self.videoVolume = videoVolume
|
|
self.additionalVideoPath = additionalVideoPath
|
|
self.additionalVideoIsDual = additionalVideoIsDual
|
|
self.additionalVideoPosition = additionalVideoPosition
|
|
self.additionalVideoScale = additionalVideoScale
|
|
self.additionalVideoRotation = additionalVideoRotation
|
|
self.additionalVideoPositionChanges = additionalVideoPositionChanges
|
|
self.additionalVideoTrimRange = additionalVideoTrimRange
|
|
self.additionalVideoOffset = additionalVideoOffset
|
|
self.additionalVideoVolume = additionalVideoVolume
|
|
self.collage = collage
|
|
self.nightTheme = nightTheme
|
|
self.drawing = drawing
|
|
self.maskDrawing = maskDrawing
|
|
self.entities = entities
|
|
self.toolValues = toolValues
|
|
self.audioTrack = audioTrack
|
|
self.audioTrackTrimRange = audioTrackTrimRange
|
|
self.audioTrackOffset = audioTrackOffset
|
|
self.audioTrackVolume = audioTrackVolume
|
|
self.audioTrackSamples = audioTrackSamples
|
|
self.collageTrackSamples = collageTrackSamples
|
|
self.coverImageTimestamp = coverImageTimestamp
|
|
self.qualityPreset = qualityPreset
|
|
}
|
|
|
|
public init(from decoder: Decoder) throws {
|
|
let container = try decoder.container(keyedBy: CodingKeys.self)
|
|
|
|
self.peerId = EnginePeer.Id(try container.decodeIfPresent(Int64.self, forKey: .peerId) ?? 0)
|
|
|
|
let width = try container.decode(Int32.self, forKey: .originalWidth)
|
|
let height = try container.decode(Int32.self, forKey: .originalHeight)
|
|
self.originalDimensions = PixelDimensions(width: width, height: height)
|
|
|
|
self.cropOffset = try container.decode(CGPoint.self, forKey: .cropOffset)
|
|
self.cropRect = try container.decodeIfPresent(CGRect.self, forKey: .cropRect)
|
|
self.cropScale = try container.decode(CGFloat.self, forKey: .cropScale)
|
|
self.cropRotation = try container.decode(CGFloat.self, forKey: .cropRotation)
|
|
self.cropMirroring = try container.decode(Bool.self, forKey: .cropMirroring)
|
|
self.cropOrientation = (try container.decodeIfPresent(Int32.self, forKey: .cropOrientation)).flatMap { MediaCropOrientation(rawValue: $0) }
|
|
|
|
if let gradientColors = try container.decodeIfPresent([DrawingColor].self, forKey: .gradientColors) {
|
|
self.gradientColors = gradientColors.map { $0.toUIColor() }
|
|
} else {
|
|
self.gradientColors = nil
|
|
}
|
|
|
|
self.videoTrimRange = try container.decodeIfPresent(Range<Double>.self, forKey: .videoTrimRange)
|
|
self.videoIsMuted = try container.decode(Bool.self, forKey: .videoIsMuted)
|
|
self.videoIsFullHd = try container.decodeIfPresent(Bool.self, forKey: .videoIsFullHd) ?? false
|
|
self.videoIsMirrored = try container.decodeIfPresent(Bool.self, forKey: .videoIsMirrored) ?? false
|
|
self.videoVolume = try container.decodeIfPresent(CGFloat.self, forKey: .videoVolume) ?? 1.0
|
|
|
|
self.additionalVideoPath = try container.decodeIfPresent(String.self, forKey: .additionalVideoPath)
|
|
self.additionalVideoIsDual = try container.decodeIfPresent(Bool.self, forKey: .additionalVideoIsDual) ?? false
|
|
self.additionalVideoPosition = try container.decodeIfPresent(CGPoint.self, forKey: .additionalVideoPosition)
|
|
self.additionalVideoScale = try container.decodeIfPresent(CGFloat.self, forKey: .additionalVideoScale)
|
|
self.additionalVideoRotation = try container.decodeIfPresent(CGFloat.self, forKey: .additionalVideoRotation)
|
|
self.additionalVideoPositionChanges = try container.decodeIfPresent([VideoPositionChange].self, forKey: .additionalVideoPositionChanges) ?? []
|
|
self.additionalVideoTrimRange = try container.decodeIfPresent(Range<Double>.self, forKey: .additionalVideoTrimRange)
|
|
self.additionalVideoOffset = try container.decodeIfPresent(Double.self, forKey: .additionalVideoOffset)
|
|
self.additionalVideoVolume = try container.decodeIfPresent(CGFloat.self, forKey: .additionalVideoVolume)
|
|
|
|
self.collage = try container.decodeIfPresent([VideoCollageItem].self, forKey: .collage) ?? []
|
|
|
|
self.nightTheme = try container.decodeIfPresent(Bool.self, forKey: .nightTheme) ?? false
|
|
if let drawingData = try container.decodeIfPresent(Data.self, forKey: .drawing), let image = UIImage(data: drawingData) {
|
|
self.drawing = image
|
|
} else {
|
|
self.drawing = nil
|
|
}
|
|
if let drawingData = try container.decodeIfPresent(Data.self, forKey: .maskDrawing), let image = UIImage(data: drawingData) {
|
|
self.maskDrawing = image
|
|
} else {
|
|
self.maskDrawing = nil
|
|
}
|
|
|
|
self.entities = try container.decode([CodableDrawingEntity].self, forKey: .entities)
|
|
|
|
let values = try container.decode([CodableToolValue].self, forKey: .toolValues)
|
|
var toolValues: [EditorToolKey: Any] = [:]
|
|
for value in values {
|
|
let (key, value) = value.keyAndValue
|
|
toolValues[key] = value
|
|
}
|
|
self.toolValues = toolValues
|
|
|
|
self.audioTrack = try container.decodeIfPresent(MediaAudioTrack.self, forKey: .audioTrack)
|
|
self.audioTrackTrimRange = try container.decodeIfPresent(Range<Double>.self, forKey: .audioTrackTrimRange)
|
|
self.audioTrackOffset = try container.decodeIfPresent(Double.self, forKey: .audioTrackOffset)
|
|
self.audioTrackVolume = try container.decodeIfPresent(CGFloat.self, forKey: .audioTrackVolume)
|
|
|
|
self.audioTrackSamples = nil
|
|
self.collageTrackSamples = nil
|
|
|
|
self.coverImageTimestamp = try container.decodeIfPresent(Double.self, forKey: .coverImageTimestamp)
|
|
|
|
self.qualityPreset = (try container.decodeIfPresent(Int32.self, forKey: .qualityPreset)).flatMap { MediaQualityPreset(rawValue: $0) }
|
|
}
|
|
|
|
public func encode(to encoder: Encoder) throws {
|
|
var container = encoder.container(keyedBy: CodingKeys.self)
|
|
|
|
try container.encode(self.peerId.toInt64(), forKey: .peerId)
|
|
|
|
try container.encode(self.originalDimensions.width, forKey: .originalWidth)
|
|
try container.encode(self.originalDimensions.height, forKey: .originalHeight)
|
|
|
|
try container.encode(self.cropOffset, forKey: .cropOffset)
|
|
try container.encode(self.cropRect, forKey: .cropRect)
|
|
try container.encode(self.cropScale, forKey: .cropScale)
|
|
try container.encode(self.cropRotation, forKey: .cropRotation)
|
|
try container.encode(self.cropMirroring, forKey: .cropMirroring)
|
|
try container.encodeIfPresent(self.cropOrientation?.rawValue, forKey: .cropOrientation)
|
|
|
|
if let gradientColors = self.gradientColors {
|
|
try container.encode(gradientColors.map { DrawingColor(color: $0) }, forKey: .gradientColors)
|
|
}
|
|
|
|
try container.encodeIfPresent(self.videoTrimRange, forKey: .videoTrimRange)
|
|
try container.encode(self.videoIsMuted, forKey: .videoIsMuted)
|
|
try container.encode(self.videoIsFullHd, forKey: .videoIsFullHd)
|
|
try container.encode(self.videoIsMirrored, forKey: .videoIsMirrored)
|
|
try container.encode(self.videoVolume, forKey: .videoVolume)
|
|
|
|
try container.encodeIfPresent(self.additionalVideoPath, forKey: .additionalVideoPath)
|
|
try container.encodeIfPresent(self.additionalVideoIsDual, forKey: .additionalVideoIsDual)
|
|
try container.encodeIfPresent(self.additionalVideoPosition, forKey: .additionalVideoPosition)
|
|
try container.encodeIfPresent(self.additionalVideoScale, forKey: .additionalVideoScale)
|
|
try container.encodeIfPresent(self.additionalVideoRotation, forKey: .additionalVideoRotation)
|
|
try container.encodeIfPresent(self.additionalVideoPositionChanges, forKey: .additionalVideoPositionChanges)
|
|
try container.encodeIfPresent(self.additionalVideoTrimRange, forKey: .additionalVideoTrimRange)
|
|
try container.encodeIfPresent(self.additionalVideoOffset, forKey: .additionalVideoOffset)
|
|
try container.encodeIfPresent(self.additionalVideoVolume, forKey: .additionalVideoVolume)
|
|
|
|
try container.encode(self.collage, forKey: .collage)
|
|
|
|
try container.encode(self.nightTheme, forKey: .nightTheme)
|
|
if let drawing = self.drawing, let pngDrawingData = drawing.pngData() {
|
|
try container.encode(pngDrawingData, forKey: .drawing)
|
|
}
|
|
if let drawing = self.maskDrawing, let pngDrawingData = drawing.pngData() {
|
|
try container.encode(pngDrawingData, forKey: .maskDrawing)
|
|
}
|
|
|
|
try container.encode(self.entities, forKey: .entities)
|
|
|
|
var values: [CodableToolValue] = []
|
|
for (key, value) in self.toolValues {
|
|
if let toolValue = CodableToolValue(key: key, value: value) {
|
|
values.append(toolValue)
|
|
}
|
|
}
|
|
try container.encode(values, forKey: .toolValues)
|
|
|
|
try container.encodeIfPresent(self.audioTrack, forKey: .audioTrack)
|
|
try container.encodeIfPresent(self.audioTrackTrimRange, forKey: .audioTrackTrimRange)
|
|
try container.encodeIfPresent(self.audioTrackOffset, forKey: .audioTrackOffset)
|
|
try container.encodeIfPresent(self.audioTrackVolume, forKey: .audioTrackVolume)
|
|
|
|
try container.encodeIfPresent(self.coverImageTimestamp, forKey: .coverImageTimestamp)
|
|
|
|
try container.encodeIfPresent(self.qualityPreset?.rawValue, forKey: .qualityPreset)
|
|
}
|
|
|
|
public func makeCopy() -> MediaEditorValues {
|
|
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, qualityPreset: self.qualityPreset)
|
|
}
|
|
|
|
func withUpdatedCrop(offset: CGPoint, scale: CGFloat, rotation: CGFloat, mirroring: Bool) -> MediaEditorValues {
|
|
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: offset, cropRect: self.cropRect, cropScale: scale, cropRotation: rotation, cropMirroring: mirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, qualityPreset: self.qualityPreset)
|
|
}
|
|
|
|
public func withUpdatedCropRect(cropRect: CGRect, rotation: CGFloat, mirroring: Bool) -> MediaEditorValues {
|
|
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: .zero, cropRect: cropRect, cropScale: 1.0, cropRotation: rotation, cropMirroring: mirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, qualityPreset: self.qualityPreset)
|
|
}
|
|
|
|
func withUpdatedGradientColors(gradientColors: [UIColor]) -> MediaEditorValues {
|
|
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: gradientColors, videoTrimRange: self.videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, qualityPreset: self.qualityPreset)
|
|
}
|
|
|
|
func withUpdatedVideoIsMuted(_ videoIsMuted: Bool) -> MediaEditorValues {
|
|
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoIsMuted: videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, qualityPreset: self.qualityPreset)
|
|
}
|
|
|
|
func withUpdatedVideoIsFullHd(_ videoIsFullHd: Bool) -> MediaEditorValues {
|
|
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, qualityPreset: self.qualityPreset)
|
|
}
|
|
|
|
|
|
func withUpdatedVideoIsMirrored(_ videoIsMirrored: Bool) -> MediaEditorValues {
|
|
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, qualityPreset: self.qualityPreset)
|
|
}
|
|
|
|
func withUpdatedVideoVolume(_ videoVolume: CGFloat?) -> MediaEditorValues {
|
|
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoIsMuted: videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, qualityPreset: self.qualityPreset)
|
|
}
|
|
|
|
func withUpdatedAdditionalVideo(path: String?, isDual: Bool, positionChanges: [VideoPositionChange]) -> MediaEditorValues {
|
|
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: path, additionalVideoIsDual: isDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: positionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, qualityPreset: self.qualityPreset)
|
|
}
|
|
|
|
func withUpdatedAdditionalVideo(position: CGPoint, scale: CGFloat, rotation: CGFloat) -> MediaEditorValues {
|
|
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: position, additionalVideoScale: scale, additionalVideoRotation: rotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, qualityPreset: self.qualityPreset)
|
|
}
|
|
|
|
func withUpdatedAdditionalVideoTrimRange(_ additionalVideoTrimRange: Range<Double>?) -> MediaEditorValues {
|
|
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, qualityPreset: self.qualityPreset)
|
|
}
|
|
|
|
|
|
func withUpdatedAdditionalVideoOffset(_ additionalVideoOffset: Double?) -> MediaEditorValues {
|
|
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, qualityPreset: self.qualityPreset)
|
|
}
|
|
|
|
func withUpdatedAdditionalVideoVolume(_ additionalVideoVolume: CGFloat?) -> MediaEditorValues {
|
|
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, qualityPreset: self.qualityPreset)
|
|
}
|
|
|
|
func withUpdatedCollage(_ collage: [VideoCollageItem]) -> MediaEditorValues {
|
|
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, qualityPreset: self.qualityPreset)
|
|
}
|
|
|
|
func withUpdatedVideoTrimRange(_ videoTrimRange: Range<Double>) -> MediaEditorValues {
|
|
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, qualityPreset: self.qualityPreset)
|
|
}
|
|
|
|
func withUpdatedDrawingAndEntities(drawing: UIImage?, entities: [CodableDrawingEntity]) -> MediaEditorValues {
|
|
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: drawing, maskDrawing: self.maskDrawing, entities: entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, qualityPreset: self.qualityPreset)
|
|
}
|
|
|
|
public func withUpdatedMaskDrawing(maskDrawing: UIImage?) -> MediaEditorValues {
|
|
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, qualityPreset: self.qualityPreset)
|
|
}
|
|
|
|
func withUpdatedToolValues(_ toolValues: [EditorToolKey: Any]) -> MediaEditorValues {
|
|
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, qualityPreset: self.qualityPreset)
|
|
}
|
|
|
|
func withUpdatedAudioTrack(_ audioTrack: MediaAudioTrack?) -> MediaEditorValues {
|
|
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, qualityPreset: self.qualityPreset)
|
|
}
|
|
|
|
func withUpdatedAudioTrackTrimRange(_ audioTrackTrimRange: Range<Double>?) -> MediaEditorValues {
|
|
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, qualityPreset: self.qualityPreset)
|
|
}
|
|
|
|
func withUpdatedAudioTrackOffset(_ audioTrackOffset: Double?) -> MediaEditorValues {
|
|
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, qualityPreset: self.qualityPreset)
|
|
}
|
|
|
|
func withUpdatedAudioTrackVolume(_ audioTrackVolume: CGFloat?) -> MediaEditorValues {
|
|
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, qualityPreset: self.qualityPreset)
|
|
}
|
|
|
|
func withUpdatedAudioTrackSamples(_ audioTrackSamples: MediaAudioTrackSamples?) -> MediaEditorValues {
|
|
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, qualityPreset: self.qualityPreset)
|
|
}
|
|
|
|
func withUpdatedCollageTrackSamples(_ collageTrackSamples: MediaAudioTrackSamples?) -> MediaEditorValues {
|
|
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, qualityPreset: self.qualityPreset)
|
|
}
|
|
|
|
func withUpdatedNightTheme(_ nightTheme: Bool) -> MediaEditorValues {
|
|
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, qualityPreset: self.qualityPreset)
|
|
}
|
|
|
|
public func withUpdatedEntities(_ entities: [CodableDrawingEntity]) -> MediaEditorValues {
|
|
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, qualityPreset: self.qualityPreset)
|
|
}
|
|
|
|
public func withUpdatedCoverImageTimestamp(_ coverImageTimestamp: Double?) -> MediaEditorValues {
|
|
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: coverImageTimestamp, qualityPreset: self.qualityPreset)
|
|
}
|
|
|
|
public func withUpdatedQualityPreset(_ qualityPreset: MediaQualityPreset?) -> MediaEditorValues {
|
|
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, qualityPreset: qualityPreset)
|
|
}
|
|
|
|
public var resultDimensions: PixelDimensions {
|
|
if self.videoIsFullHd {
|
|
return PixelDimensions(width: 1080, height: 1920)
|
|
} else {
|
|
return PixelDimensions(width: 720, height: 1280)
|
|
}
|
|
}
|
|
|
|
public var hasChanges: Bool {
|
|
if self.cropOffset != .zero {
|
|
return true
|
|
}
|
|
if self.cropScale != 1.0 {
|
|
return true
|
|
}
|
|
if self.cropRotation != 0.0 {
|
|
return true
|
|
}
|
|
if self.cropMirroring {
|
|
return true
|
|
}
|
|
if (self.cropOrientation ?? .up) != .up {
|
|
return true
|
|
}
|
|
if self.videoTrimRange != nil {
|
|
return true
|
|
}
|
|
if self.drawing != nil {
|
|
return true
|
|
}
|
|
if !self.entities.isEmpty {
|
|
return true
|
|
}
|
|
if !self.toolValues.isEmpty {
|
|
return true
|
|
}
|
|
if self.audioTrack != nil {
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
}
|
|
|
|
public struct TintValue: Equatable, Codable {
|
|
private enum CodingKeys: String, CodingKey {
|
|
case color
|
|
case intensity
|
|
}
|
|
|
|
public static let initial = TintValue(
|
|
color: .clear,
|
|
intensity: 0.5
|
|
)
|
|
|
|
public let color: UIColor
|
|
public let intensity: Float
|
|
|
|
public init(
|
|
color: UIColor,
|
|
intensity: Float
|
|
) {
|
|
self.color = color
|
|
self.intensity = intensity
|
|
}
|
|
|
|
public init(from decoder: Decoder) throws {
|
|
let container = try decoder.container(keyedBy: CodingKeys.self)
|
|
|
|
self.color = try container.decode(DrawingColor.self, forKey: .color).toUIColor()
|
|
self.intensity = try container.decode(Float.self, forKey: .intensity)
|
|
}
|
|
|
|
public func encode(to encoder: Encoder) throws {
|
|
var container = encoder.container(keyedBy: CodingKeys.self)
|
|
|
|
try container.encode(DrawingColor(color: self.color), forKey: .color)
|
|
try container.encode(self.intensity, forKey: .intensity)
|
|
}
|
|
|
|
public func withUpdatedColor(_ color: UIColor) -> TintValue {
|
|
return TintValue(color: color, intensity: self.intensity)
|
|
}
|
|
|
|
public func withUpdatedIntensity(_ intensity: Float) -> TintValue {
|
|
return TintValue(color: self.color, intensity: intensity)
|
|
}
|
|
}
|
|
|
|
public struct BlurValue: Equatable, Codable {
|
|
private enum CodingKeys: String, CodingKey {
|
|
case mode
|
|
case intensity
|
|
case position
|
|
case size
|
|
case falloff
|
|
case rotation
|
|
}
|
|
|
|
public static let initial = BlurValue(
|
|
mode: .off,
|
|
intensity: 0.5,
|
|
position: CGPoint(x: 0.5, y: 0.5),
|
|
size: 0.24,
|
|
falloff: 0.12,
|
|
rotation: 0.0
|
|
)
|
|
|
|
public enum Mode: Int32, Equatable {
|
|
case off
|
|
case radial
|
|
case linear
|
|
case portrait
|
|
}
|
|
|
|
public let mode: Mode
|
|
public let intensity: Float
|
|
public let position: CGPoint
|
|
public let size: Float
|
|
public let falloff: Float
|
|
public let rotation: Float
|
|
|
|
public init(
|
|
mode: Mode,
|
|
intensity: Float,
|
|
position: CGPoint,
|
|
size: Float,
|
|
falloff: Float,
|
|
rotation: Float
|
|
) {
|
|
self.mode = mode
|
|
self.intensity = intensity
|
|
self.position = position
|
|
self.size = size
|
|
self.falloff = falloff
|
|
self.rotation = rotation
|
|
}
|
|
|
|
public init(from decoder: Decoder) throws {
|
|
let container = try decoder.container(keyedBy: CodingKeys.self)
|
|
|
|
self.mode = try BlurValue.Mode(rawValue: container.decode(Int32.self, forKey: .mode)) ?? .off
|
|
self.intensity = try container.decode(Float.self, forKey: .intensity)
|
|
self.position = try container.decode(CGPoint.self, forKey: .position)
|
|
self.size = try container.decode(Float.self, forKey: .size)
|
|
self.falloff = try container.decode(Float.self, forKey: .falloff)
|
|
self.rotation = try container.decode(Float.self, forKey: .rotation)
|
|
}
|
|
|
|
public func encode(to encoder: Encoder) throws {
|
|
var container = encoder.container(keyedBy: CodingKeys.self)
|
|
|
|
try container.encode(self.mode.rawValue, forKey: .mode)
|
|
try container.encode(self.intensity, forKey: .intensity)
|
|
try container.encode(self.position, forKey: .position)
|
|
try container.encode(self.size, forKey: .size)
|
|
try container.encode(self.falloff, forKey: .falloff)
|
|
try container.encode(self.rotation, forKey: .rotation)
|
|
}
|
|
|
|
public func withUpdatedMode(_ mode: Mode) -> BlurValue {
|
|
return BlurValue(
|
|
mode: mode,
|
|
intensity: self.intensity,
|
|
position: self.position,
|
|
size: self.size,
|
|
falloff: self.falloff,
|
|
rotation: self.rotation
|
|
)
|
|
}
|
|
|
|
public func withUpdatedIntensity(_ intensity: Float) -> BlurValue {
|
|
return BlurValue(
|
|
mode: self.mode,
|
|
intensity: intensity,
|
|
position: self.position,
|
|
size: self.size,
|
|
falloff: self.falloff,
|
|
rotation: self.rotation
|
|
)
|
|
}
|
|
|
|
public func withUpdatedPosition(_ position: CGPoint) -> BlurValue {
|
|
return BlurValue(
|
|
mode: self.mode,
|
|
intensity: self.intensity,
|
|
position: position,
|
|
size: self.size,
|
|
falloff: self.falloff,
|
|
rotation: self.rotation
|
|
)
|
|
}
|
|
|
|
public func withUpdatedSize(_ size: Float) -> BlurValue {
|
|
return BlurValue(
|
|
mode: self.mode,
|
|
intensity: self.intensity,
|
|
position: self.position,
|
|
size: size,
|
|
falloff: self.falloff,
|
|
rotation: self.rotation
|
|
)
|
|
}
|
|
|
|
public func withUpdatedFalloff(_ falloff: Float) -> BlurValue {
|
|
return BlurValue(
|
|
mode: self.mode,
|
|
intensity: self.intensity,
|
|
position: self.position,
|
|
size: self.size,
|
|
falloff: falloff,
|
|
rotation: self.rotation
|
|
)
|
|
}
|
|
|
|
public func withUpdatedRotation(_ rotation: Float) -> BlurValue {
|
|
return BlurValue(
|
|
mode: self.mode,
|
|
intensity: self.intensity,
|
|
position: self.position,
|
|
size: self.size,
|
|
falloff: self.falloff,
|
|
rotation: rotation
|
|
)
|
|
}
|
|
}
|
|
|
|
public struct CurvesValue: Equatable, Codable {
|
|
private enum CodingKeys: String, CodingKey {
|
|
case all
|
|
case red
|
|
case green
|
|
case blue
|
|
}
|
|
|
|
public struct CurveValue: Equatable, Codable {
|
|
private enum CodingKeys: String, CodingKey {
|
|
case blacks
|
|
case shadows
|
|
case midtones
|
|
case highlights
|
|
case whites
|
|
}
|
|
|
|
public static let initial = CurveValue(
|
|
blacks: 0.0,
|
|
shadows: 0.25,
|
|
midtones: 0.5,
|
|
highlights: 0.75,
|
|
whites: 1.0
|
|
)
|
|
|
|
public let blacks: Float
|
|
public let shadows: Float
|
|
public let midtones: Float
|
|
public let highlights: Float
|
|
public let whites: Float
|
|
|
|
lazy var dataPoints: [Float] = {
|
|
let points: [Float] = [
|
|
self.blacks,
|
|
self.blacks,
|
|
self.shadows,
|
|
self.midtones,
|
|
self.highlights,
|
|
self.whites,
|
|
self.whites
|
|
]
|
|
|
|
let (_, dataPoints) = curveThroughPoints(
|
|
count: points.count,
|
|
valueAtIndex: { index in
|
|
return points[index]
|
|
},
|
|
positionAtIndex: { index, _ in
|
|
switch index {
|
|
case 0:
|
|
return -0.001
|
|
case 1:
|
|
return 0.0
|
|
case 2:
|
|
return 0.25
|
|
case 3:
|
|
return 0.5
|
|
case 4:
|
|
return 0.75
|
|
case 5:
|
|
return 1.0
|
|
default:
|
|
return 1.001
|
|
}
|
|
},
|
|
size: CGSize(width: 1.0, height: 1.0),
|
|
type: .line,
|
|
granularity: 100,
|
|
floor: false
|
|
)
|
|
return dataPoints
|
|
}()
|
|
|
|
public init(
|
|
blacks: Float,
|
|
shadows: Float,
|
|
midtones: Float,
|
|
highlights: Float,
|
|
whites: Float
|
|
) {
|
|
self.blacks = blacks
|
|
self.shadows = shadows
|
|
self.midtones = midtones
|
|
self.highlights = highlights
|
|
self.whites = whites
|
|
}
|
|
|
|
public init(from decoder: Decoder) throws {
|
|
let container = try decoder.container(keyedBy: CodingKeys.self)
|
|
|
|
self.blacks = try container.decode(Float.self, forKey: .blacks)
|
|
self.shadows = try container.decode(Float.self, forKey: .shadows)
|
|
self.midtones = try container.decode(Float.self, forKey: .midtones)
|
|
self.highlights = try container.decode(Float.self, forKey: .highlights)
|
|
self.whites = try container.decode(Float.self, forKey: .whites)
|
|
}
|
|
|
|
public func encode(to encoder: Encoder) throws {
|
|
var container = encoder.container(keyedBy: CodingKeys.self)
|
|
|
|
try container.encode(self.blacks, forKey: .blacks)
|
|
try container.encode(self.shadows, forKey: .shadows)
|
|
try container.encode(self.midtones, forKey: .midtones)
|
|
try container.encode(self.highlights, forKey: .highlights)
|
|
try container.encode(self.whites, forKey: .whites)
|
|
}
|
|
|
|
public func withUpdatedBlacks(_ blacks: Float) -> CurveValue {
|
|
return CurveValue(blacks: blacks, shadows: self.shadows, midtones: self.midtones, highlights: self.highlights, whites: self.whites)
|
|
}
|
|
|
|
public func withUpdatedShadows(_ shadows: Float) -> CurveValue {
|
|
return CurveValue(blacks: self.blacks, shadows: shadows, midtones: self.midtones, highlights: self.highlights, whites: self.whites)
|
|
}
|
|
|
|
public func withUpdatedMidtones(_ midtones: Float) -> CurveValue {
|
|
return CurveValue(blacks: self.blacks, shadows: self.shadows, midtones: midtones, highlights: self.highlights, whites: self.whites)
|
|
}
|
|
|
|
public func withUpdatedHighlights(_ highlights: Float) -> CurveValue {
|
|
return CurveValue(blacks: self.blacks, shadows: self.shadows, midtones: self.midtones, highlights: highlights, whites: self.whites)
|
|
}
|
|
|
|
public func withUpdatedWhites(_ whites: Float) -> CurveValue {
|
|
return CurveValue(blacks: self.blacks, shadows: self.shadows, midtones: self.midtones, highlights: self.highlights, whites: whites)
|
|
}
|
|
}
|
|
|
|
public static let initial = CurvesValue(
|
|
all: CurveValue.initial,
|
|
red: CurveValue.initial,
|
|
green: CurveValue.initial,
|
|
blue: CurveValue.initial
|
|
)
|
|
|
|
public var all: CurveValue
|
|
public var red: CurveValue
|
|
public var green: CurveValue
|
|
public var blue: CurveValue
|
|
|
|
public init(
|
|
all: CurveValue,
|
|
red: CurveValue,
|
|
green: CurveValue,
|
|
blue: CurveValue
|
|
) {
|
|
self.all = all
|
|
self.red = red
|
|
self.green = green
|
|
self.blue = blue
|
|
}
|
|
|
|
public init(from decoder: Decoder) throws {
|
|
let container = try decoder.container(keyedBy: CodingKeys.self)
|
|
|
|
self.all = try container.decode(CurveValue.self, forKey: .all)
|
|
self.red = try container.decode(CurveValue.self, forKey: .red)
|
|
self.green = try container.decode(CurveValue.self, forKey: .green)
|
|
self.blue = try container.decode(CurveValue.self, forKey: .blue)
|
|
}
|
|
|
|
public func encode(to encoder: Encoder) throws {
|
|
var container = encoder.container(keyedBy: CodingKeys.self)
|
|
|
|
try container.encode(self.all, forKey: .all)
|
|
try container.encode(self.red, forKey: .red)
|
|
try container.encode(self.green, forKey: .green)
|
|
try container.encode(self.blue, forKey: .blue)
|
|
}
|
|
|
|
public func withUpdatedAll(_ all: CurveValue) -> CurvesValue {
|
|
return CurvesValue(all: all, red: self.red, green: self.green, blue: self.blue)
|
|
}
|
|
|
|
public func withUpdatedRed(_ red: CurveValue) -> CurvesValue {
|
|
return CurvesValue(all: self.all, red: red, green: self.green, blue: self.blue)
|
|
}
|
|
|
|
public func withUpdatedGreen(_ green: CurveValue) -> CurvesValue {
|
|
return CurvesValue(all: self.all, red: self.red, green: green, blue: self.blue)
|
|
}
|
|
|
|
public func withUpdatedBlue(_ blue: CurveValue) -> CurvesValue {
|
|
return CurvesValue(all: self.all, red: self.red, green: self.green, blue: blue)
|
|
}
|
|
}
|
|
|
|
|
|
private let toolEpsilon: Float = 0.005
|
|
public extension MediaEditorValues {
|
|
var hasAdjustments: Bool {
|
|
for key in EditorToolKey.adjustmentToolsKeys {
|
|
if let value = self.toolValues[key] as? Float, abs(value) > toolEpsilon {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
var hasTint: Bool {
|
|
if let tintValue = self.toolValues[.shadowsTint] as? TintValue, tintValue.color != .clear && tintValue.intensity > toolEpsilon {
|
|
return true
|
|
} else if let tintValue = self.toolValues[.highlightsTint] as? TintValue, tintValue.color != .clear && tintValue.intensity > toolEpsilon {
|
|
return true
|
|
} else {
|
|
return false
|
|
}
|
|
}
|
|
|
|
var hasBlur: Bool {
|
|
if let blurValue = self.toolValues[.blur] as? BlurValue, blurValue.mode != .off && blurValue.intensity > toolEpsilon {
|
|
return true
|
|
} else {
|
|
return false
|
|
}
|
|
}
|
|
|
|
var hasCurves: Bool {
|
|
if let curvesValue = self.toolValues[.curves] as? CurvesValue, curvesValue != CurvesValue.initial {
|
|
return true
|
|
} else {
|
|
return false
|
|
}
|
|
}
|
|
|
|
var requiresComposing: Bool {
|
|
if abs(1.0 - self.cropScale) > 0.0 {
|
|
return true
|
|
}
|
|
if self.cropRect != nil {
|
|
return true
|
|
}
|
|
if self.cropOffset != .zero {
|
|
return true
|
|
}
|
|
if abs(self.cropRotation) > 0.0 {
|
|
return true
|
|
}
|
|
if self.cropMirroring {
|
|
return true
|
|
}
|
|
if self.hasAdjustments {
|
|
return true
|
|
}
|
|
if self.hasTint {
|
|
return true
|
|
}
|
|
if self.hasBlur {
|
|
return true
|
|
}
|
|
if self.hasCurves {
|
|
return true
|
|
}
|
|
if self.drawing != nil {
|
|
return true
|
|
}
|
|
if !self.entities.isEmpty {
|
|
return true
|
|
}
|
|
if self.additionalVideoPath != nil {
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
}
|
|
|
|
public class MediaEditorHistogram: Equatable {
|
|
public class HistogramBins: Equatable {
|
|
public static func == (lhs: HistogramBins, rhs: HistogramBins) -> Bool {
|
|
if lhs.count != rhs.count {
|
|
return false
|
|
}
|
|
if lhs.max != rhs.max {
|
|
return false
|
|
}
|
|
if lhs.values != rhs.values {
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
let values: [UInt32]
|
|
let max: UInt32
|
|
|
|
public var count: Int {
|
|
return self.values.count
|
|
}
|
|
|
|
init(values: [UInt32], max: UInt32) {
|
|
self.values = values
|
|
self.max = max
|
|
}
|
|
|
|
public func valueAtIndex(_ index: Int, mirrored: Bool = false) -> Float {
|
|
if index >= 0 && index < values.count, self.max > 0 {
|
|
let value = Float(self.values[index]) / Float(self.max)
|
|
return mirrored ? 1.0 - value : value
|
|
} else {
|
|
return 0.0
|
|
}
|
|
}
|
|
}
|
|
|
|
public static func == (lhs: MediaEditorHistogram, rhs: MediaEditorHistogram) -> Bool {
|
|
if lhs.luminance != rhs.luminance {
|
|
return false
|
|
}
|
|
if lhs.red != rhs.red {
|
|
return false
|
|
}
|
|
if lhs.green != rhs.green {
|
|
return false
|
|
}
|
|
if lhs.blue != rhs.blue {
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
public let luminance: HistogramBins
|
|
public let red: HistogramBins
|
|
public let green: HistogramBins
|
|
public let blue: HistogramBins
|
|
|
|
public init(data: Data) {
|
|
let count = 256
|
|
|
|
var maxRed: UInt32 = 0
|
|
var redValues: [UInt32] = []
|
|
var maxGreen: UInt32 = 0
|
|
var greenValues: [UInt32] = []
|
|
var maxBlue: UInt32 = 0
|
|
var blueValues: [UInt32] = []
|
|
var maxLuma: UInt32 = 0
|
|
var lumaValues: [UInt32] = []
|
|
|
|
data.withUnsafeBytes { pointer in
|
|
if let red = pointer.baseAddress?.assumingMemoryBound(to: UInt32.self) {
|
|
for i in 0 ..< count {
|
|
redValues.append(red[i])
|
|
if red[i] > maxRed {
|
|
maxRed = red[i]
|
|
}
|
|
}
|
|
}
|
|
|
|
if let green = pointer.baseAddress?.assumingMemoryBound(to: UInt32.self).advanced(by: count) {
|
|
for i in 0 ..< count {
|
|
greenValues.append(green[i])
|
|
if green[i] > maxGreen {
|
|
maxGreen = green[i]
|
|
}
|
|
}
|
|
}
|
|
|
|
if let blue = pointer.baseAddress?.assumingMemoryBound(to: UInt32.self).advanced(by: count * 2) {
|
|
for i in 0 ..< count {
|
|
blueValues.append(blue[i])
|
|
if blue[i] > maxBlue {
|
|
maxBlue = blue[i]
|
|
}
|
|
}
|
|
}
|
|
|
|
if let luma = pointer.baseAddress?.assumingMemoryBound(to: UInt32.self).advanced(by: count * 3) {
|
|
for i in 0 ..< count {
|
|
lumaValues.append(luma[i])
|
|
if luma[i] > maxLuma {
|
|
maxLuma = luma[i]
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
self.luminance = HistogramBins(values: lumaValues, max: maxLuma)
|
|
self.red = HistogramBins(values: redValues, max: maxRed)
|
|
self.green = HistogramBins(values: greenValues, max: maxGreen)
|
|
self.blue = HistogramBins(values: blueValues, max: maxBlue)
|
|
}
|
|
|
|
init(
|
|
luminance: HistogramBins,
|
|
red: HistogramBins,
|
|
green: HistogramBins,
|
|
blue: HistogramBins
|
|
) {
|
|
self.luminance = luminance
|
|
self.red = red
|
|
self.green = green
|
|
self.blue = blue
|
|
}
|
|
}
|
|
|
|
public enum MediaEditorCurveType {
|
|
case filled
|
|
case line
|
|
}
|
|
|
|
public func curveThroughPoints(count: Int, valueAtIndex: (Int) -> Float, positionAtIndex: (Int, CGFloat) -> CGFloat, size: CGSize, type: MediaEditorCurveType, granularity: Int, floor: Bool) -> (UIBezierPath, [Float]) {
|
|
let path = UIBezierPath()
|
|
var dataPoints: [Float] = []
|
|
|
|
let firstValue = valueAtIndex(0)
|
|
switch type {
|
|
case .filled:
|
|
path.move(to: CGPoint(x: -1.0, y: size.height))
|
|
path.addLine(to: CGPoint(x: -1.0, y: CGFloat(firstValue) * size.height))
|
|
case .line:
|
|
path.move(to: CGPoint(x: -1.0, y: CGFloat(firstValue) * size.height))
|
|
}
|
|
|
|
let step = size.width / CGFloat(count)
|
|
func pointAtIndex(_ index: Int) -> CGPoint {
|
|
if floor {
|
|
return CGPoint(x: floorToScreenPixels(positionAtIndex(index, step)), y: floorToScreenPixels(CGFloat(valueAtIndex(index)) * size.height))
|
|
} else {
|
|
return CGPoint(x: positionAtIndex(index, step), y: CGFloat(valueAtIndex(index)) * size.height)
|
|
}
|
|
}
|
|
|
|
for index in 1 ..< count - 2 {
|
|
let point0 = pointAtIndex(index - 1)
|
|
let point1 = pointAtIndex(index)
|
|
let point2 = pointAtIndex(index + 1)
|
|
let point3 = pointAtIndex(index + 2)
|
|
|
|
for j in 1 ..< granularity {
|
|
let t = CGFloat(j) * (1.0 / CGFloat(granularity))
|
|
let tt = t * t
|
|
let ttt = tt * t
|
|
|
|
var point = CGPoint(
|
|
x: 0.5 * (2 * point1.x + (point2.x - point0.x) * t + (2 * point0.x - 5 * point1.x + 4 * point2.x - point3.x) * tt + (3 * point1.x - point0.x - 3 * point2.x + point3.x) * ttt),
|
|
y: 0.5 * (2 * point1.y + (point2.y - point0.y) * t + (2 * point0.y - 5 * point1.y + 4 * point2.y - point3.y) * tt + (3 * point1.y - point0.y - 3 * point2.y + point3.y) * ttt)
|
|
)
|
|
point.y = max(0.0, min(size.height, point.y))
|
|
if point.x > point0.x {
|
|
path.addLine(to: point)
|
|
}
|
|
|
|
if ((j - 1) % 2 == 0) {
|
|
dataPoints.append(Float(point.y))
|
|
}
|
|
}
|
|
path.addLine(to: point2)
|
|
}
|
|
|
|
let lastValue = valueAtIndex(count - 1)
|
|
path.addLine(to: CGPoint(x: size.width + 1.0, y: CGFloat(lastValue) * size.height))
|
|
|
|
if case .filled = type {
|
|
path.addLine(to: CGPoint(x: size.width + 1.0, y: size.height))
|
|
path.close()
|
|
}
|
|
|
|
return (path, dataPoints)
|
|
}
|
|
|
|
public enum CodableToolValue {
|
|
case float(EditorToolKey, Float)
|
|
case tint(EditorToolKey, TintValue)
|
|
case blur(EditorToolKey, BlurValue)
|
|
case curves(EditorToolKey, CurvesValue)
|
|
|
|
public init?(key: EditorToolKey, value: Any) {
|
|
if let toolValue = value as? Float {
|
|
self = .float(key, toolValue)
|
|
} else if let toolValue = value as? TintValue {
|
|
self = .tint(key, toolValue)
|
|
} else if let toolValue = value as? BlurValue {
|
|
self = .blur(key, toolValue)
|
|
} else if let toolValue = value as? CurvesValue {
|
|
self = .curves(key, toolValue)
|
|
} else {
|
|
return nil
|
|
}
|
|
}
|
|
|
|
public var keyAndValue: (EditorToolKey, Any) {
|
|
switch self {
|
|
case let .float(key, value):
|
|
return (key, value)
|
|
case let .tint(key, value):
|
|
return (key, value)
|
|
case let .blur(key, value):
|
|
return (key, value)
|
|
case let .curves(key, value):
|
|
return (key, value)
|
|
}
|
|
}
|
|
}
|
|
|
|
extension CodableToolValue: Codable {
|
|
private enum CodingKeys: String, CodingKey {
|
|
case key
|
|
case type
|
|
case value
|
|
}
|
|
|
|
private enum ToolType: Int, Codable {
|
|
case float
|
|
case tint
|
|
case blur
|
|
case curves
|
|
}
|
|
|
|
public init(from decoder: Decoder) throws {
|
|
let container = try decoder.container(keyedBy: CodingKeys.self)
|
|
let type = try container.decode(ToolType.self, forKey: .type)
|
|
let key = EditorToolKey(rawValue: try container.decode(Int32.self, forKey: .key))!
|
|
switch type {
|
|
case .float:
|
|
self = .float(key, try container.decode(Float.self, forKey: .value))
|
|
case .tint:
|
|
self = .tint(key, try container.decode(TintValue.self, forKey: .value))
|
|
case .blur:
|
|
self = .blur(key, try container.decode(BlurValue.self, forKey: .value))
|
|
case .curves:
|
|
self = .curves(key, try container.decode(CurvesValue.self, forKey: .value))
|
|
}
|
|
}
|
|
|
|
public func encode(to encoder: Encoder) throws {
|
|
var container = encoder.container(keyedBy: CodingKeys.self)
|
|
switch self {
|
|
case let .float(key, value):
|
|
try container.encode(key.rawValue, forKey: .key)
|
|
try container.encode(ToolType.float, forKey: .type)
|
|
try container.encode(value, forKey: .value)
|
|
case let .tint(key, value):
|
|
try container.encode(key.rawValue, forKey: .key)
|
|
try container.encode(ToolType.tint, forKey: .type)
|
|
try container.encode(value, forKey: .value)
|
|
case let .blur(key, value):
|
|
try container.encode(key.rawValue, forKey: .key)
|
|
try container.encode(ToolType.blur, forKey: .type)
|
|
try container.encode(value, forKey: .value)
|
|
case let .curves(key, value):
|
|
try container.encode(key.rawValue, forKey: .key)
|
|
try container.encode(ToolType.curves, forKey: .type)
|
|
try container.encode(value, forKey: .value)
|
|
}
|
|
}
|
|
}
|
|
|
|
private let hasHEVCHardwareEncoder: Bool = {
|
|
let spec: [CFString: Any] = [:]
|
|
var outID: CFString?
|
|
var properties: CFDictionary?
|
|
let result = VTCopySupportedPropertyDictionaryForEncoder(width: 1920, height: 1080, codecType: kCMVideoCodecType_HEVC, encoderSpecification: spec as CFDictionary, encoderIDOut: &outID, supportedPropertiesOut: &properties)
|
|
if result == kVTCouldNotFindVideoEncoderErr {
|
|
return false
|
|
}
|
|
return result == noErr
|
|
}()
|
|
|
|
|
|
func targetSize(cropSize: CGSize, rotateSideward: Bool = false) -> CGSize {
|
|
let blockSize: CGFloat = 16.0
|
|
|
|
var adjustedCropSize = cropSize
|
|
if rotateSideward {
|
|
adjustedCropSize = CGSize(width: cropSize.height, height: cropSize.width)
|
|
}
|
|
|
|
let renderWidth = (adjustedCropSize.width / blockSize).rounded(.down) * blockSize
|
|
let renderHeight = (adjustedCropSize.height * renderWidth / adjustedCropSize.width).rounded(.down)
|
|
|
|
// if fmod(renderHeight, blockSize) != 0 {
|
|
// renderHeight = (adjustedCropSize.height / blockSize).rounded(.down) * blockSize
|
|
// }
|
|
|
|
return CGSize(width: renderWidth, height: renderHeight)
|
|
}
|
|
|
|
public func recommendedVideoExportConfiguration(values: MediaEditorValues, duration: Double, image: Bool = false, forceFullHd: Bool = false, frameRate: Float, isSticker: Bool = false) -> MediaEditorVideoExport.Configuration {
|
|
let compressionProperties: [String: Any]
|
|
let codecType: Any
|
|
|
|
var values = values
|
|
|
|
var videoBitrate: Int = 3700
|
|
var audioBitrate: Int = 64
|
|
var audioNumberOfChannels = 2
|
|
if image {
|
|
videoBitrate = 5000
|
|
} else {
|
|
if duration < 10 {
|
|
videoBitrate = 5800
|
|
} else if duration < 20 {
|
|
videoBitrate = 5500
|
|
} else if duration < 30 {
|
|
videoBitrate = 5000
|
|
}
|
|
}
|
|
|
|
let width: Int
|
|
let height: Int
|
|
|
|
var frameRate = frameRate
|
|
|
|
var useHEVC = hasHEVCHardwareEncoder
|
|
var useVP9 = false
|
|
if let qualityPreset = values.qualityPreset {
|
|
let maxSize = CGSize(width: qualityPreset.maximumDimensions, height: qualityPreset.maximumDimensions)
|
|
var resultSize = values.originalDimensions.cgSize
|
|
if let cropRect = values.cropRect, !cropRect.isEmpty {
|
|
resultSize = targetSize(cropSize: cropRect.size.aspectFitted(maxSize), rotateSideward: values.cropOrientation?.isSideward ?? false)
|
|
} else {
|
|
resultSize = targetSize(cropSize: resultSize.aspectFitted(maxSize), rotateSideward: values.cropOrientation?.isSideward ?? false)
|
|
}
|
|
|
|
width = Int(resultSize.width)
|
|
height = Int(resultSize.height)
|
|
|
|
videoBitrate = qualityPreset.videoBitrateKbps
|
|
audioBitrate = qualityPreset.audioBitrateKbps
|
|
audioNumberOfChannels = qualityPreset.audioChannelsCount
|
|
|
|
useHEVC = false
|
|
} else {
|
|
if isSticker {
|
|
width = 512
|
|
height = 512
|
|
useVP9 = true
|
|
frameRate = 30
|
|
values = values.withUpdatedQualityPreset(.sticker)
|
|
} else if values.videoIsFullHd {
|
|
width = 1080
|
|
height = 1920
|
|
} else {
|
|
width = 720
|
|
height = 1280
|
|
}
|
|
}
|
|
|
|
if useVP9 {
|
|
codecType = "VP9"
|
|
compressionProperties = [:]
|
|
} else if useHEVC {
|
|
codecType = AVVideoCodecType.hevc
|
|
compressionProperties = [
|
|
AVVideoAverageBitRateKey: videoBitrate * 1000,
|
|
AVVideoProfileLevelKey: kVTProfileLevel_HEVC_Main_AutoLevel
|
|
]
|
|
} else {
|
|
codecType = AVVideoCodecType.h264
|
|
compressionProperties = [
|
|
AVVideoAverageBitRateKey: videoBitrate * 1000,
|
|
AVVideoProfileLevelKey: AVVideoProfileLevelH264HighAutoLevel,
|
|
AVVideoH264EntropyModeKey: AVVideoH264EntropyModeCABAC
|
|
]
|
|
}
|
|
|
|
let videoSettings: [String: Any] = [
|
|
AVVideoCodecKey: codecType,
|
|
AVVideoCompressionPropertiesKey: compressionProperties,
|
|
AVVideoWidthKey: width,
|
|
AVVideoHeightKey: height
|
|
]
|
|
|
|
let audioSettings: [String: Any]
|
|
if isSticker {
|
|
audioSettings = [:]
|
|
} else {
|
|
audioSettings = [
|
|
AVFormatIDKey: kAudioFormatMPEG4AAC,
|
|
AVSampleRateKey: 44100,
|
|
AVEncoderBitRateKey: audioBitrate * 1000,
|
|
AVNumberOfChannelsKey: audioNumberOfChannels
|
|
]
|
|
}
|
|
|
|
return MediaEditorVideoExport.Configuration(
|
|
videoSettings: videoSettings,
|
|
audioSettings: audioSettings,
|
|
values: values,
|
|
frameRate: frameRate,
|
|
preferredDuration: isSticker ? duration: nil
|
|
)
|
|
}
|