[WIP] Stickers editor

This commit is contained in:
Ilya Laktyushin 2024-04-06 18:08:20 +04:00
parent 698a74b6b6
commit 7c6651db34
7 changed files with 63 additions and 19 deletions

View File

@ -103,6 +103,10 @@ public final class UniversalVideoNode: ASDisplayNode {
public private(set) var ownsContentNode: Bool = false public private(set) var ownsContentNode: Bool = false
public var ownsContentNodeUpdated: ((Bool) -> Void)? public var ownsContentNodeUpdated: ((Bool) -> Void)?
public var duration: Double {
return self.content.duration
}
private let _status = Promise<MediaPlayerStatus?>() private let _status = Promise<MediaPlayerStatus?>()
public var status: Signal<MediaPlayerStatus?, NoError> { public var status: Signal<MediaPlayerStatus?, NoError> {
return self._status.get() return self._status.get()

View File

@ -201,6 +201,7 @@ public final class DefaultAnimatedStickerNodeImpl: ASDisplayNode, AnimatedSticke
public var frameUpdated: (Int, Int) -> Void = { _, _ in } public var frameUpdated: (Int, Int) -> Void = { _, _ in }
public private(set) var currentFrameIndex: Int = 0 public private(set) var currentFrameIndex: Int = 0
public private(set) var currentFrameCount: Int = 0 public private(set) var currentFrameCount: Int = 0
public private(set) var currentFrameRate: Int = 0
private var playFromIndex: Int? private var playFromIndex: Int?
public var frameColorUpdated: ((UIColor) -> Void)? public var frameColorUpdated: ((UIColor) -> Void)?
@ -537,6 +538,7 @@ public final class DefaultAnimatedStickerNodeImpl: ASDisplayNode, AnimatedSticke
strongSelf.frameUpdated(frame.index, frame.totalFrames) strongSelf.frameUpdated(frame.index, frame.totalFrames)
strongSelf.currentFrameIndex = frame.index strongSelf.currentFrameIndex = frame.index
strongSelf.currentFrameCount = frame.totalFrames strongSelf.currentFrameCount = frame.totalFrames
strongSelf.currentFrameRate = frameRate
if frame.isLastFrame { if frame.isLastFrame {
var stopped = false var stopped = false
@ -652,6 +654,7 @@ public final class DefaultAnimatedStickerNodeImpl: ASDisplayNode, AnimatedSticke
strongSelf.frameUpdated(frame.index, frame.totalFrames) strongSelf.frameUpdated(frame.index, frame.totalFrames)
strongSelf.currentFrameIndex = frame.index strongSelf.currentFrameIndex = frame.index
strongSelf.currentFrameCount = frame.totalFrames; strongSelf.currentFrameCount = frame.totalFrames;
strongSelf.currentFrameRate = frameRate
if frame.isLastFrame { if frame.isLastFrame {
var stopped = false var stopped = false

View File

@ -86,6 +86,16 @@ public class DrawingStickerEntityView: DrawingEntityView {
var currentSize: CGSize? var currentSize: CGSize?
public var updated: () -> Void = {} public var updated: () -> Void = {}
public var duration: Double? {
if let animationNode = self.animationNode, animationNode.currentFrameCount > 1 {
return Double(animationNode.currentFrameCount) / Double(animationNode.currentFrameRate)
} else if let videoNode = self.videoNode {
return videoNode.duration
} else {
return nil
}
}
init(context: AccountContext, entity: DrawingStickerEntity) { init(context: AccountContext, entity: DrawingStickerEntity) {
self.imageNode = TransformImageNode() self.imageNode = TransformImageNode()

View File

@ -1637,6 +1637,7 @@ public func recommendedVideoExportConfiguration(values: MediaEditorValues, durat
videoSettings: videoSettings, videoSettings: videoSettings,
audioSettings: audioSettings, audioSettings: audioSettings,
values: values, values: values,
frameRate: frameRate frameRate: frameRate,
preferredDuration: isSticker ? duration: nil
) )
} }

View File

@ -56,17 +56,20 @@ public final class MediaEditorVideoExport {
public var audioSettings: [String: Any] public var audioSettings: [String: Any]
public var values: MediaEditorValues public var values: MediaEditorValues
public var frameRate: Float public var frameRate: Float
public var preferredDuration: Double?
public init( public init(
videoSettings: [String: Any], videoSettings: [String: Any],
audioSettings: [String: Any], audioSettings: [String: Any],
values: MediaEditorValues, values: MediaEditorValues,
frameRate: Float frameRate: Float,
preferredDuration: Double? = nil
) { ) {
self.videoSettings = videoSettings self.videoSettings = videoSettings
self.audioSettings = audioSettings self.audioSettings = audioSettings
self.values = values self.values = values
self.frameRate = frameRate self.frameRate = frameRate
self.preferredDuration = preferredDuration
} }
var isSticker: Bool { var isSticker: Bool {
@ -284,7 +287,7 @@ public final class MediaEditorVideoExport {
let duration: CMTime let duration: CMTime
if self.configuration.isSticker { if self.configuration.isSticker {
duration = CMTime(seconds: 3.0, preferredTimescale: CMTimeScale(NSEC_PER_SEC)) duration = CMTime(seconds: self.configuration.preferredDuration ?? 3.0, preferredTimescale: CMTimeScale(NSEC_PER_SEC))
} else if let mainAsset { } else if let mainAsset {
if let trimmedDuration = self.configuration.timeRange?.duration { if let trimmedDuration = self.configuration.timeRange?.duration {
duration = trimmedDuration duration = trimmedDuration

View File

@ -3,6 +3,7 @@ import UIKit
import CoreMedia import CoreMedia
import FFMpegBinding import FFMpegBinding
import ImageDCT import ImageDCT
import Accelerate
final class MediaEditorVideoFFMpegWriter: MediaEditorVideoExportWriter { final class MediaEditorVideoFFMpegWriter: MediaEditorVideoExportWriter {
public static let registerFFMpegGlobals: Void = { public static let registerFFMpegGlobals: Void = {
@ -13,6 +14,15 @@ final class MediaEditorVideoFFMpegWriter: MediaEditorVideoExportWriter {
let ffmpegWriter = FFMpegVideoWriter() let ffmpegWriter = FFMpegVideoWriter()
var pool: CVPixelBufferPool? var pool: CVPixelBufferPool?
let conversionInfo: vImage_ARGBToYpCbCr
init() {
var pixelRange = vImage_YpCbCrPixelRange( Yp_bias: 16, CbCr_bias: 128, YpRangeMax: 235, CbCrRangeMax: 240, YpMax: 235, YpMin: 16, CbCrMax: 240, CbCrMin: 16)
var conversionInfo = vImage_ARGBToYpCbCr()
let _ = vImageConvert_ARGBToYpCbCr_GenerateConversion(kvImage_ARGBToYpCbCrMatrix_ITU_R_709_2, &pixelRange, &conversionInfo, kvImageARGB8888, kvImage420Yp8_Cb8_Cr8, vImage_Flags(kvImageNoFlags))
self.conversionInfo = conversionInfo
}
func setup(configuration: MediaEditorVideoExport.Configuration, outputPath: String) { func setup(configuration: MediaEditorVideoExport.Configuration, outputPath: String) {
let _ = MediaEditorVideoFFMpegWriter.registerFFMpegGlobals let _ = MediaEditorVideoFFMpegWriter.registerFFMpegGlobals
@ -91,27 +101,26 @@ final class MediaEditorVideoFFMpegWriter: MediaEditorVideoExportWriter {
} }
func appendPixelBuffer(_ buffer: CVPixelBuffer, at time: CMTime) -> Bool { func appendPixelBuffer(_ buffer: CVPixelBuffer, at time: CMTime) -> Bool {
let width = Int32(CVPixelBufferGetWidth(buffer)) let width = CVPixelBufferGetWidth(buffer)
let height = Int32(CVPixelBufferGetHeight(buffer)) let height = CVPixelBufferGetHeight(buffer)
let bytesPerRow = Int32(CVPixelBufferGetBytesPerRow(buffer)) let bytesPerRow = CVPixelBufferGetBytesPerRow(buffer)
let frame = FFMpegAVFrame(pixelFormat: .YUVA, width: width, height: height) let frame = FFMpegAVFrame(pixelFormat: .YUVA, width: Int32(width), height: Int32(height))
CVPixelBufferLockBaseAddress(buffer, CVPixelBufferLockFlags.readOnly) CVPixelBufferLockBaseAddress(buffer, CVPixelBufferLockFlags.readOnly)
let src = CVPixelBufferGetBaseAddress(buffer) let src = CVPixelBufferGetBaseAddress(buffer)
splitRGBAIntoYUVAPlanes(
src, var srcBuffer = vImage_Buffer(data: src, height: vImagePixelCount(height), width: vImagePixelCount(width), rowBytes: bytesPerRow)
frame.data[0],
frame.data[1], var yBuffer = vImage_Buffer(data: frame.data[0], height: vImagePixelCount(height), width: vImagePixelCount(width), rowBytes: width)
frame.data[2], var uBuffer = vImage_Buffer(data: frame.data[1], height: vImagePixelCount(height / 2), width: vImagePixelCount(width / 2), rowBytes: width / 2)
frame.data[3], var vBuffer = vImage_Buffer(data: frame.data[2], height: vImagePixelCount(height / 2), width: vImagePixelCount(width / 2), rowBytes: width / 2)
width, var aBuffer = vImage_Buffer(data: frame.data[3], height: vImagePixelCount(height), width: vImagePixelCount(width), rowBytes: width)
height,
bytesPerRow, var outInfo = self.conversionInfo
true, let _ = vImageConvert_ARGB8888To420Yp8_Cb8_Cr8(&srcBuffer, &yBuffer, &uBuffer, &vBuffer, &outInfo, [ 3, 2, 1, 0 ], vImage_Flags(kvImageDoNotTile))
true vImageExtractChannel_ARGB8888(&srcBuffer, &aBuffer, 3, vImage_Flags(kvImageDoNotTile))
)
CVPixelBufferUnlockBaseAddress(buffer, CVPixelBufferLockFlags.readOnly) CVPixelBufferUnlockBaseAddress(buffer, CVPixelBufferLockFlags.readOnly)

View File

@ -6847,6 +6847,20 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
if case let .video(video, _) = exportSubject { if case let .video(video, _) = exportSubject {
duration = video.duration.seconds duration = video.duration.seconds
} }
if isSticker {
duration = 3.0
var stickerDurations: [Double] = []
self.node.entitiesView.eachView { entityView in
if let stickerEntityView = entityView as? DrawingStickerEntityView {
if let duration = stickerEntityView.duration, duration > 0.0 {
stickerDurations.append(duration)
}
}
}
if !stickerDurations.isEmpty {
duration = stickerDurations.max() ?? 3.0
}
}
let configuration = recommendedVideoExportConfiguration(values: mediaEditor.values, duration: duration, forceFullHd: true, frameRate: 60.0, isSticker: isSticker) let configuration = recommendedVideoExportConfiguration(values: mediaEditor.values, duration: duration, forceFullHd: true, frameRate: 60.0, isSticker: isSticker)
let outputPath = NSTemporaryDirectory() + "\(Int64.random(in: 0 ..< .max)).\(fileExtension)" let outputPath = NSTemporaryDirectory() + "\(Int64.random(in: 0 ..< .max)).\(fileExtension)"
let videoExport = MediaEditorVideoExport(postbox: self.context.account.postbox, subject: exportSubject, configuration: configuration, outputPath: outputPath, textScale: 2.0) let videoExport = MediaEditorVideoExport(postbox: self.context.account.postbox, subject: exportSubject, configuration: configuration, outputPath: outputPath, textScale: 2.0)