Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

9:16 export #114

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 31 additions & 29 deletions Classes/Editor/EditorViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -635,7 +635,7 @@ public final class EditorViewController: UIViewController, MediaPlayerController
// MARK: - EditorViewDelegate

func didTapSaveButton() {
startExporting(action: .save)
startExporting(action: .save, size: exportSize ?? .zero)
analyticsProvider?.logSaveFromDashboard()
}

Expand All @@ -645,20 +645,20 @@ public final class EditorViewController: UIViewController, MediaPlayerController

func didTapPostButton() {
if delegate?.shouldExport() ?? true {
startExporting(action: .post)
startExporting(action: .post, size: exportSize ?? .zero)
}
analyticsProvider?.logPostFromDashboard()
}

func didTapConfirmButton() {
if delegate?.shouldExport() ?? true {
startExporting(action: .confirm)
startExporting(action: .confirm, size: exportSize ?? .zero)
}
analyticsProvider?.logOpenComposeFromDashboard()
}

func didTapPostOptionsButton() {
startExporting(action: .postOptions)
startExporting(action: .postOptions, size: exportSize ?? .zero)
analyticsProvider?.logAdvancedOptionsOpen(page: Constants.pageName)
}

Expand Down Expand Up @@ -741,7 +741,7 @@ public final class EditorViewController: UIViewController, MediaPlayerController

// MARK: - Media Exporting

private func startExporting(action: KanvasExportAction) {
private func startExporting(action: KanvasExportAction, size: CGSize) {
player.stop()
showLoading()
let archive: Data
Expand All @@ -758,20 +758,20 @@ public final class EditorViewController: UIViewController, MediaPlayerController
assetsHandler.ensureAllImagesHaveVideo(segments: segments) { segments in
guard let videoURL = segments.first?.videoURL else { return }
DispatchQueue.main.async {
self.createFinalVideo(videoURL: videoURL, mediaInfo: firstSegment.mediaInfo, archive: archive, exportAction: action)
self.createFinalVideo(videoURL: videoURL, size: size, mediaInfo: firstSegment.mediaInfo, archive: archive, exportAction: action)
}
}
}
else {
createFinalImage(image: image, mediaInfo: firstSegment.mediaInfo, archive: archive, exportAction: action)
createFinalImage(image: image, size: size, mediaInfo: firstSegment.mediaInfo, archive: archive, exportAction: action)
}
}
else if shouldExportMediaAsGIF {
if segments.count == 1, let segment = segments.first, let url = segment.videoURL {
self.createFinalGIF(videoURL: url, framesPerSecond: KanvasTimes.gifPreferredFramesPerSecond, mediaInfo: segment.mediaInfo, archive: archive, exportAction: action)
self.createFinalGIF(videoURL: url, size: size, framesPerSecond: KanvasTimes.gifPreferredFramesPerSecond, mediaInfo: segment.mediaInfo, archive: archive, exportAction: action)
}
else if assetsHandler.containsOnlyImages(segments: segments) {
self.createFinalGIF(segments: segments, mediaInfo: segments.first?.mediaInfo ?? MediaInfo(source: .kanvas_camera), archive: archive, exportAction: action)
self.createFinalGIF(segments: segments, size: size, mediaInfo: segments.first?.mediaInfo ?? MediaInfo(source: .kanvas_camera), archive: archive, exportAction: action)
}
else {
// Segments are not all frames, so we need to generate a full video first, and then convert that to a GIF.
Expand All @@ -786,7 +786,7 @@ public final class EditorViewController: UIViewController, MediaPlayerController
}
let fps = Int(CMTime(seconds: 1.0, preferredTimescale: KanvasTimes.stopMotionFrameTimescale).seconds / KanvasTimes.onlyImagesFrameTime.seconds)
DispatchQueue.main.async {
self.createFinalGIF(videoURL: url, framesPerSecond: fps, mediaInfo: mediaInfo, archive: archive, exportAction: action)
self.createFinalGIF(videoURL: url, size: size, framesPerSecond: fps, mediaInfo: mediaInfo, archive: archive, exportAction: action)
}
}
}
Expand All @@ -798,33 +798,33 @@ public final class EditorViewController: UIViewController, MediaPlayerController
return
}
DispatchQueue.main.async {
self?.createFinalVideo(videoURL: url, mediaInfo: mediaInfo ?? MediaInfo(source: .media_library), archive: archive, exportAction: action)
self?.createFinalVideo(videoURL: url, size: size, mediaInfo: mediaInfo ?? MediaInfo(source: .media_library), archive: archive, exportAction: action)
}
}
}
}

public func export(_ completion: @escaping (Result<ExportResult, Error>) -> Void) {
public func export(size: CGSize, _ completion: @escaping (Result<ExportResult, Error>) -> Void) {
exportCompletion = completion
startExporting(action: .post)
startExporting(action: .post, size: size)
}

var edit: Edit {
return Edit(canvas: editorView.movableViewCanvas, isMuted: isMuted)
}

private var exportSize: CGSize? {
var exportSize: CGSize? {
let exportSize = editorView.exportSize
return settings.features.scaleMediaToFill ? exportSize : nil
}

private func createFinalGIF(segments: [CameraSegment], mediaInfo: MediaInfo, archive: Data, exportAction: KanvasExportAction) {
private func createFinalGIF(segments: [CameraSegment], size: CGSize, mediaInfo: MediaInfo, archive: Data, exportAction: KanvasExportAction) {
let exporter = exporterClass.init(settings: settings)
exporter.filterType = filterType ?? .passthrough
exporter.imageOverlays = imageOverlays()
exporter.imageOverlays = imageOverlays(size: size)
let segments = gifMakerHandler.trimmedSegments(segments)
let frames = segments.compactMap { $0.mediaFrame(defaultTimeInterval: getDefaultTimeIntervalForImageSegments()) }
exporter.export(frames: frames, toSize: exportSize) { orderedFrames in
exporter.export(frames: frames, toSize: size) { orderedFrames in
let playbackFrames = self.gifMakerHandler.framesForPlayback(orderedFrames)
self.gifEncoderClass.init().encode(frames: playbackFrames, loopCount: 0) { gifURL in
guard let gifURL = gifURL else {
Expand All @@ -841,11 +841,11 @@ public final class EditorViewController: UIViewController, MediaPlayerController
}
}

private func createFinalGIF(videoURL: URL, framesPerSecond: Int, mediaInfo: MediaInfo, archive: Data, exportAction: KanvasExportAction) {
private func createFinalGIF(videoURL: URL, size: CGSize, framesPerSecond: Int, mediaInfo: MediaInfo, archive: Data, exportAction: KanvasExportAction) {
let exporter = exporterClass.init(settings: settings)
exporter.filterType = filterType ?? .passthrough
exporter.imageOverlays = imageOverlays()
exporter.export(video: videoURL, mediaInfo: mediaInfo, toSize: exportSize) { (exportedVideoURL, _) in
exporter.imageOverlays = imageOverlays(size: size)
exporter.export(video: videoURL, mediaInfo: mediaInfo, toSize: size) { (exportedVideoURL, _) in
guard let exportedVideoURL = exportedVideoURL else {
performUIUpdate {
self.handleExportError()
Expand All @@ -867,10 +867,10 @@ public final class EditorViewController: UIViewController, MediaPlayerController
}
}

private func createFinalVideo(videoURL: URL, mediaInfo: MediaInfo, archive: Data, exportAction: KanvasExportAction) {
private func createFinalVideo(videoURL: URL, size: CGSize, mediaInfo: MediaInfo, archive: Data, exportAction: KanvasExportAction) {
let exporter = exporterClass.init(settings: settings)
exporter.imageOverlays = imageOverlays()
exporter.export(video: videoURL, mediaInfo: mediaInfo, toSize: exportSize) { (exportedVideoURL, error) in
exporter.imageOverlays = imageOverlays(size: size)
exporter.export(video: videoURL, mediaInfo: mediaInfo, toSize: size) { (exportedVideoURL, error) in
performUIUpdate {
guard let url = exportedVideoURL else {
if let error = error, let exportCompletion = self.exportCompletion {
Expand All @@ -887,11 +887,11 @@ public final class EditorViewController: UIViewController, MediaPlayerController
}
}

private func createFinalImage(image: UIImage, mediaInfo: MediaInfo, archive: Data, exportAction: KanvasExportAction) {
private func createFinalImage(image: UIImage, size: CGSize, mediaInfo: MediaInfo, archive: Data, exportAction: KanvasExportAction) {
let exporter = exporterClass.init(settings: settings)
exporter.filterType = filterType ?? .passthrough
exporter.imageOverlays = imageOverlays()
exporter.export(image: image, time: player.lastStillFilterTime, toSize: exportSize) { (exportedImage, error) in
exporter.imageOverlays = imageOverlays(size: size)
exporter.export(image: image, time: player.lastStillFilterTime, toSize: size) { (exportedImage, error) in
let originalImage = image
performUIUpdate {
guard let unwrappedImage = exportedImage else {
Expand All @@ -909,14 +909,16 @@ public final class EditorViewController: UIViewController, MediaPlayerController
}
}

private func imageOverlays() -> [CGImage] {
editorView.layoutIfNeeded()
private func imageOverlays(size: CGSize) -> [CGImage] {
// Remove this automatic sizing once export logic is removed from EditorViewController
editorView.movableViewCanvas.frame = CGRect(origin: .zero, size: size)
var imageOverlays: [CGImage] = []
if let drawingLayer = drawingController.drawingLayer, let drawingOverlayImage = drawingLayer.cgImage() {
imageOverlays.append(drawingOverlayImage)
}

if let movableViewsOverlayImage = editorView.movableViewCanvas.layer.cgImage() {
print("Frame: \(editorView.movableViewCanvas.frame) Image: \(movableViewsOverlayImage.width), \(movableViewsOverlayImage.height)")
imageOverlays.append(movableViewsOverlayImage)
}
return imageOverlays
Expand Down Expand Up @@ -1222,7 +1224,7 @@ public final class EditorViewController: UIViewController, MediaPlayerController
}

func onQuickPostButtonSubmitted() {
startExporting(action: .confirmPostOptions)
startExporting(action: .confirmPostOptions, size: exportSize ?? .zero)
}

public func onQuickPostOptionsShown(visible: Bool, hintText: String?, view: UIView) {
Expand Down
30 changes: 14 additions & 16 deletions Classes/Editor/MultiEditor/MultiEditorViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -127,13 +127,11 @@ class MultiEditorViewController: UIViewController {
clipsController.select(index: selected ?? 0)
}

func loadEditor(for index: Int, current: Bool = true) {
func loadEditor(for index: Int) {
let frame = frames[index]
if let editor = delegate?.editor(segment: frame.segment, edit: frame.edit) {
if current {
currentEditor?.stopPlayback()
currentEditor?.unloadFromParentViewController()
}
currentEditor?.stopPlayback()
currentEditor?.unloadFromParentViewController()
let additionalPadding: CGFloat = 10 // Extra padding for devices that don't have safe areas (which provide some padding by default).
let bottom: CGFloat
if view.safeAreaInsets.bottom > 0 {
Expand All @@ -147,11 +145,7 @@ class MultiEditorViewController: UIViewController {
self?.clipsController.removeDraggingClip()
}
load(childViewController: editor, into: editorContainer)
if current {
currentEditor = editor
} else {
editor.view.alpha = 0.0
}
currentEditor = editor
}
}

Expand Down Expand Up @@ -383,13 +377,17 @@ extension MultiEditorViewController: EditorControllerDelegate {

frames.enumerated().forEach({ (idx, frame) in
autoreleasepool {
let editor = delegate.editor(segment: frame.segment, edit: frame.edit)
editor.export { [weak self, editor] result in
let _ = editor // strong reference until the export completes
self?.exportHandler.handleExport(result, for: idx)
if let selected = self?.selected {
self?.loadEditor(for: selected, current: false)
do {
let data = try NSKeyedArchiver.archivedData(withRootObject: frame.edit, requiringSecureCoding: true)
let edit = try NSKeyedUnarchiver.unarchivedObject(ofClass: EditorViewController.Edit.self, from: data)
let editor = delegate.editor(segment: frame.segment, edit: edit)
editor.export(size: currentEditor?.exportSize ?? .zero) { [weak self, editor] result in
let _ = editor // strong reference until the export completes

self?.exportHandler.handleExport(result, for: idx)
}
} catch let error {
exportHandler.handleExport(MultiEditorExportHandler.ExportResult.failure(error), for: idx)
}
}
})
Expand Down
19 changes: 11 additions & 8 deletions Classes/Rendering/MediaPlayer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -312,21 +312,24 @@ final class MediaPlayer {
let heightFactor = 4

let bufferAspectRatio = bufferWidth.f / bufferHeight.f
let screenAspectRatio = Device.screenWidth.f / Device.screenHeight.f

let targetSize = CGSize(width: playerView.frame.width * playerView.contentScaleFactor, height: playerView.frame.height * playerView.contentScaleFactor)

let targetAspectRatio = targetSize.width / targetSize.height

let x,y: CGFloat

if bufferAspectRatio > screenAspectRatio {
let croppedSpace = bufferWidth - (CGFloat(Device.screenWidth) * bufferHeight / CGFloat(Device.screenHeight))
if CGFloat(bufferAspectRatio) > targetAspectRatio {
let croppedSpace = bufferWidth - (targetSize.width * bufferHeight / targetSize.height)
let visibleBufferWidth = bufferWidth - croppedSpace
x = croppedSpace / 2 + point.x * visibleBufferWidth / CGFloat(Device.screenWidth)
y = point.y * bufferHeight / (CGFloat(Device.screenHeight))
x = croppedSpace / 2 + point.x * visibleBufferWidth / targetSize.width
y = point.y * bufferHeight / (targetSize.height)
}
else {
let croppedSpace = bufferHeight - (CGFloat(Device.screenHeight) * bufferWidth / CGFloat(Device.screenWidth))
let croppedSpace = bufferHeight - (targetSize.height * bufferWidth / targetSize.width)
let visibleBufferHeight = bufferHeight - croppedSpace
y = croppedSpace / 2 + point.y * visibleBufferHeight / CGFloat(Device.screenHeight)
x = point.x * bufferWidth / CGFloat(Device.screenWidth)
y = croppedSpace / 2 + point.y * visibleBufferHeight / targetSize.height
x = point.x * bufferWidth / targetSize.width
}

let luma = int32Buffer[Int(y) * int32PerRow / heightFactor + Int(x)]
Expand Down