├── .gitignore ├── Example ├── AppDelegate.swift ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ └── Contents.json ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard ├── Info.plist ├── SceneDelegate.swift ├── ViewController.swift ├── overlay.png └── sample.mp4 ├── LICENSE ├── README.md ├── VideoOverlayProcessor.podspec ├── VideoOverlayProcessor.xcodeproj ├── project.pbxproj └── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ └── IDEWorkspaceChecks.plist ├── VideoOverlayProcessor ├── Classes │ ├── BaseOverlay.swift │ ├── ImageOverlay.swift │ ├── TextOverlay.swift │ └── VideoOverlayProcessor.swift ├── Info.plist └── VideoOverlayProcessor.h └── VideoOverlayProcessorTests ├── Info.plist └── VideoOverlayProcessorTests.swift /.gitignore: -------------------------------------------------------------------------------- 1 | # Mac OS X 2 | .DS_Store 3 | 4 | # Xcode 5 | 6 | ## Build generated 7 | build/ 8 | DerivedData 9 | 10 | ## Various settings 11 | *.pbxuser 12 | !default.pbxuser 13 | *.mode1v3 14 | !default.mode1v3 15 | *.mode2v3 16 | !default.mode2v3 17 | *.perspectivev3 18 | !default.perspectivev3 19 | xcuserdata 20 | 21 | ## Other 22 | *.xccheckout 23 | *.moved-aside 24 | *.xcuserstate 25 | *.xcscmblueprint 26 | 27 | ## Obj-C/Swift specific 28 | *.hmap 29 | *.ipa 30 | 31 | ## Playgrounds 32 | timeline.xctimeline 33 | playground.xcworkspace 34 | 35 | # Swift Package Manager 36 | .build/ 37 | 38 | # Carthage 39 | Carthage/Build 40 | -------------------------------------------------------------------------------- /Example/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // Example 4 | // 5 | // Created by Dawid Płatek on 30/10/2019. 6 | // Copyright © 2019 Inspace Labs. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | 15 | 16 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 17 | // Override point for customization after application launch. 18 | return true 19 | } 20 | 21 | // MARK: UISceneSession Lifecycle 22 | 23 | func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { 24 | // Called when a new scene session is being created. 25 | // Use this method to select a configuration to create the new scene with. 26 | return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) 27 | } 28 | 29 | func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) { 30 | // Called when the user discards a scene session. 31 | // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. 32 | // Use this method to release any resources that were specific to the discarded scenes, as they will not return. 33 | } 34 | 35 | 36 | } 37 | 38 | -------------------------------------------------------------------------------- /Example/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "size" : "1024x1024", 91 | "scale" : "1x" 92 | } 93 | ], 94 | "info" : { 95 | "version" : 1, 96 | "author" : "xcode" 97 | } 98 | } -------------------------------------------------------------------------------- /Example/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Example/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /Example/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 28 | 35 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /Example/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UIApplicationSceneManifest 24 | 25 | UIApplicationSupportsMultipleScenes 26 | 27 | UISceneConfigurations 28 | 29 | UIWindowSceneSessionRoleApplication 30 | 31 | 32 | UISceneConfigurationName 33 | Default Configuration 34 | UISceneDelegateClassName 35 | $(PRODUCT_MODULE_NAME).SceneDelegate 36 | UISceneStoryboardFile 37 | Main 38 | 39 | 40 | 41 | 42 | UILaunchStoryboardName 43 | LaunchScreen 44 | UIMainStoryboardFile 45 | Main 46 | UIRequiredDeviceCapabilities 47 | 48 | armv7 49 | 50 | UISupportedInterfaceOrientations 51 | 52 | UIInterfaceOrientationPortrait 53 | UIInterfaceOrientationLandscapeLeft 54 | UIInterfaceOrientationLandscapeRight 55 | 56 | UISupportedInterfaceOrientations~ipad 57 | 58 | UIInterfaceOrientationPortrait 59 | UIInterfaceOrientationPortraitUpsideDown 60 | UIInterfaceOrientationLandscapeLeft 61 | UIInterfaceOrientationLandscapeRight 62 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /Example/SceneDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SceneDelegate.swift 3 | // Example 4 | // 5 | // Created by Dawid Płatek on 30/10/2019. 6 | // Copyright © 2019 Inspace Labs. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class SceneDelegate: UIResponder, UIWindowSceneDelegate { 12 | 13 | var window: UIWindow? 14 | 15 | 16 | func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { 17 | // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`. 18 | // If using a storyboard, the `window` property will automatically be initialized and attached to the scene. 19 | // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead). 20 | guard let _ = (scene as? UIWindowScene) else { return } 21 | } 22 | 23 | func sceneDidDisconnect(_ scene: UIScene) { 24 | // Called as the scene is being released by the system. 25 | // This occurs shortly after the scene enters the background, or when its session is discarded. 26 | // Release any resources associated with this scene that can be re-created the next time the scene connects. 27 | // The scene may re-connect later, as its session was not neccessarily discarded (see `application:didDiscardSceneSessions` instead). 28 | } 29 | 30 | func sceneDidBecomeActive(_ scene: UIScene) { 31 | // Called when the scene has moved from an inactive state to an active state. 32 | // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive. 33 | } 34 | 35 | func sceneWillResignActive(_ scene: UIScene) { 36 | // Called when the scene will move from an active state to an inactive state. 37 | // This may occur due to temporary interruptions (ex. an incoming phone call). 38 | } 39 | 40 | func sceneWillEnterForeground(_ scene: UIScene) { 41 | // Called as the scene transitions from the background to the foreground. 42 | // Use this method to undo the changes made on entering the background. 43 | } 44 | 45 | func sceneDidEnterBackground(_ scene: UIScene) { 46 | // Called as the scene transitions from the foreground to the background. 47 | // Use this method to save data, release shared resources, and store enough scene-specific state information 48 | // to restore the scene back to its current state. 49 | } 50 | 51 | 52 | } 53 | 54 | -------------------------------------------------------------------------------- /Example/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // Example 4 | // 5 | // Created by Dawid Płatek on 30/10/2019. 6 | // Copyright © 2019 Inspace Labs. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import AVKit 11 | 12 | class ViewController: UIViewController { 13 | 14 | override func viewDidLoad() { 15 | super.viewDidLoad() 16 | } 17 | 18 | 19 | @IBAction func playSampleVideoButtonTapped(_ sender: Any) { 20 | guard let url = Bundle.main.url(forResource: "sample", withExtension: "mp4") else { return } 21 | 22 | showPlayerViewController(for: url) 23 | } 24 | 25 | @IBAction func addWatermarkButtonTapped(_ sender: Any) { 26 | guard let inputURL = Bundle.main.url(forResource: "sample", withExtension: "mp4") else { return } 27 | 28 | let outputURL = FileManager.default.temporaryDirectory.appendingPathComponent("output-\(Int(Date().timeIntervalSince1970)).mp4") 29 | 30 | let processor = VideoOverlayProcessor(inputURL: inputURL, outputURL: outputURL) 31 | 32 | let videoSize = processor.videoSize 33 | let videoDuration = processor.videoDuration 34 | 35 | guard let image = UIImage(named: "overlay") else { return } 36 | let margin: CGFloat = 100 37 | let imageOverlay = ImageOverlay(image: image, frame: CGRect(x: videoSize.width-image.size.width-margin, y: videoSize.height-image.size.height/2-margin, width: image.size.width/2, height: image.size.height/2), delay: 0.0, duration: videoDuration) 38 | processor.addOverlay(imageOverlay) 39 | 40 | processor.process { [weak self] (exportSession) in 41 | guard let exportSession = exportSession else { return } 42 | 43 | if (exportSession.status == .completed) { 44 | DispatchQueue.main.async { [weak self] in 45 | self?.showPlayerViewController(for: outputURL) 46 | } 47 | } 48 | } 49 | } 50 | 51 | @IBAction func addSubtitlesButtonTapped(_ sender: Any) { 52 | guard let inputURL = Bundle.main.url(forResource: "sample", withExtension: "mp4") else { return } 53 | 54 | let outputURL = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent("output-\(Int(Date().timeIntervalSince1970)).mp4") 55 | 56 | let processor = VideoOverlayProcessor(inputURL: inputURL, outputURL: outputURL) 57 | 58 | let videoSize = processor.videoSize 59 | let videoDuration = processor.videoDuration 60 | 61 | let textOverlay = TextOverlay(text: "Hello ;) I hope you like this library", frame: CGRect(x: 0, y: 0, width: videoSize.width, height: videoSize.height/12), delay: 0.0, duration: videoDuration, backgroundColor: UIColor.black.withAlphaComponent(0.3), textColor: UIColor.white) 62 | processor.addOverlay(textOverlay) 63 | 64 | processor.process { [weak self] (exportSession) in 65 | guard let exportSession = exportSession else { return } 66 | 67 | if (exportSession.status == .completed) { 68 | DispatchQueue.main.async { [weak self] in 69 | self?.showPlayerViewController(for: outputURL) 70 | } 71 | } 72 | } 73 | } 74 | 75 | private func showPlayerViewController(for url: URL) { 76 | let videoPlayer = AVPlayer(url: url) 77 | let playerViewController = AVPlayerViewController() 78 | playerViewController.player = videoPlayer 79 | 80 | present(playerViewController, animated: true) { 81 | if let player = playerViewController.player { 82 | player.play() 83 | } 84 | } 85 | } 86 | } 87 | 88 | -------------------------------------------------------------------------------- /Example/overlay.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inspace-io/VideoOverlayProcessor/41c8bed25cab60bfe9c6f3a1891763cb6adf0af1/Example/overlay.png -------------------------------------------------------------------------------- /Example/sample.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inspace-io/VideoOverlayProcessor/41c8bed25cab60bfe9c6f3a1891763cb6adf0af1/Example/sample.mp4 -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2019 Inspace Labs 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![](http://inspace.io/github-cover.jpg)](http://inspace.io) 2 | 3 | # Introduction 4 | 5 | **VideoOverlayProcessor** was written by **[Dawid Płatek](https://github.com/dader)** for **[inspace.io](http://inspace.io)** 6 | 7 | # VideoOverlayProcessor 8 | 9 | **VideoOverlayProcessor** is a clean and easy-to-use library responsible for adding image and text overlays to video. 10 | 11 | 12 | ## Features 13 | 14 | * **Solid performance**:
`VideoOverlayProcessor` is using `AVFoundation` framework under the hood. The whole logic is based on `AVMutableComposition` and the input video is processed only once to keep it as efficient as possible. 15 | 16 | * **Time range support**
Visibility of the overlays can be specified by defining exact time range. 17 | 18 | * **Positioning and sizing**
The position and the size of overlay can be be easily configured by defined `frame`. 19 | 20 | ## Getting Started 21 | 22 | ### Creating a processor 23 | 24 | The base class responsible for video processing is called `VideoOverlayProcessor`. To start using the library you have to just create a new object of this class and pass `inputURL` and `outputURL` parameters. The process can give you some additional information about the input file i.e. to calculate the frame of overlay correctly or to set the time range (`videoSize` and `videoDuration` properties respectively). 25 | 26 | ```Swift 27 | let inputURL = ... 28 | let outputURL = ... 29 | 30 | let processor = VideoOverlayProcessor(inputURL: inputURL, outputURL: inputURL) 31 | ``` 32 | 33 | ### Adding overlays 34 | 35 | There are two classes responsible for adding text and image overlays to video: `TextOverlay` and `ImageOverlay` You should use them to constructed hierarchy of your overlay structure. To defined positioning and sizing you have to set `frame` property. Beside that, you can specify the time range which indicates when the overlay is visible. To achive it you have to use `delay` and `duration` properties. When the overlay object is configured accordingly the last thing you have to do before start processing is adding the object to processor (you should use `addOverlay` method of processing in this case). 36 | 37 | ```Swift 38 | 39 | let videoSize = processor.videoSize 40 | let videoDuration = processor.videoDuration 41 | 42 | let textOverlay = TextOverlay(text: "Welcome on github.com!", frame: CGRect(x: 0, y: 0, width: videoSize.width, height: videoSize.height/4), delay: 0.0, duration: videoDuration) 43 | processor.addOverlay(textOverlay) 44 | 45 | let image = UIImage(named: ...) 46 | let imageOverlay = ImageOverlay(image: image, frame: CGRect(x: videoSize.width/2-image.size.width/2, y: videoSize.height/2-image.size.height/2, width: image.size.width, height: image.size.height), delay: 0.0, duration: videoDuration) 47 | processor.addOverlay(imageOverlay) 48 | 49 | ``` 50 | 51 | ### Starting processing 52 | 53 | You can start processing by calling `process` method. 54 | 55 | ## Use cases 56 | 57 | **VideoOverlayProcessor** can simplify the whole process of adding overlays to video. There are some common use cases which may give an idea how potentially you use this library. 58 | 59 | ### Adding a watermark to video 60 | 61 | ```Swift 62 | 63 | guard let inputURL = Bundle.main.url(forResource: "sample", withExtension: "mp4") else { return } 64 | 65 | let outputURL = FileManager.default.temporaryDirectory.appendingPathComponent("output-\(Int(Date().timeIntervalSince1970)).mp4") 66 | 67 | let processor = VideoOverlayProcessor(inputURL: inputURL, outputURL: outputURL) 68 | 69 | let videoSize = processor.videoSize 70 | let videoDuration = processor.videoDuration 71 | 72 | guard let image = UIImage(named: "overlay") else { return } 73 | let margin: CGFloat = 100 74 | let imageOverlay = ImageOverlay(image: image, frame: CGRect(x: videoSize.width-image.size.width-margin, y: videoSize.height-image.size.height/2-margin, width: image.size.width/2, height: image.size.height/2), delay: 0.0, duration: videoDuration) 75 | processor.addOverlay(imageOverlay) 76 | 77 | processor.process { [weak self] (exportSession) in 78 | guard let exportSession = exportSession else { return } 79 | 80 | if (exportSession.status == .completed) { 81 | DispatchQueue.main.async { [weak self] in 82 | self?.showPlayerViewController(for: outputURL) 83 | } 84 | } 85 | } 86 | 87 | ``` 88 | 89 | ### Adding subtitles to video 90 | 91 | ```Swift 92 | 93 | guard let inputURL = Bundle.main.url(forResource: "sample", withExtension: "mp4") else { return } 94 | 95 | let outputURL = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent("output-\(Int(Date().timeIntervalSince1970)).mp4") 96 | 97 | let processor = VideoOverlayProcessor(inputURL: inputURL, outputURL: outputURL) 98 | 99 | let videoSize = processor.videoSize 100 | let videoDuration = processor.videoDuration 101 | 102 | let textOverlay = TextOverlay(text: "Hello ;) I hope you like this library", frame: CGRect(x: 0, y: 0, width: videoSize.width, height: videoSize.height/12), delay: 0.0, duration: videoDuration, backgroundColor: UIColor.black.withAlphaComponent(0.3), textColor: UIColor.white) 103 | processor.addOverlay(textOverlay) 104 | 105 | processor.process { [weak self] (exportSession) in 106 | guard let exportSession = exportSession else { return } 107 | 108 | if (exportSession.status == .completed) { 109 | DispatchQueue.main.async { [weak self] in 110 | self?.showPlayerViewController(for: outputURL) 111 | } 112 | } 113 | } 114 | 115 | ``` 116 | 117 | ## Installation 118 | 119 | **VideoOverlayProcessor** will be compatible with the lastest public release of Swift. 120 | 121 | ### CocoaPods 122 | 123 | **VideoOverlayProcessor** is available through [CocoaPods](https://cocoapods.org). To install it, add the following to your `Podfile`: 124 | 125 | `pod 'VideoOverlayProcessor'` 126 | 127 | ## Requirements 128 | 129 | * iOS 8.0+ 130 | * Xcode 8.0+ 131 | 132 | ## Licence 133 | 134 | VideoOverlayProcessor is released under the MIT license. See LICENSE for details. 135 | -------------------------------------------------------------------------------- /VideoOverlayProcessor.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = 'VideoOverlayProcessor' 3 | s.version = '1.0.0' 4 | s.license = { type: 'MIT', file: 'LICENSE' } 5 | s.summary = 'A library to simplify adding overlays to video' 6 | s.description = 'VideoOverlayProcessor is a clean and easy-to-use library responsible for adding image and text overlays to video.' 7 | s.homepage = 'https://github.com/inspace-io/VideoOverlayProcessor' 8 | s.author = { "Dawid Płatek" => "dawid@inspace.io" } 9 | s.source = { git: 'https://github.com/inspace-io/VideoOverlayProcessor.git', tag: s.version.to_s } 10 | 11 | s.source_files = 'VideoOverlayProcessor/Classes/**/*' 12 | 13 | s.platform = :ios, '8.0' 14 | s.frameworks = 'UIKit', 'Foundation', 'AVFoundation' 15 | s.swift_version = '5.0' 16 | end 17 | -------------------------------------------------------------------------------- /VideoOverlayProcessor.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 50; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 08D02B422369D2890092AAF1 /* VideoOverlayProcessor.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 08D02B382369D2890092AAF1 /* VideoOverlayProcessor.framework */; }; 11 | 08D02B472369D2890092AAF1 /* VideoOverlayProcessorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08D02B462369D2890092AAF1 /* VideoOverlayProcessorTests.swift */; }; 12 | 08D02B492369D2890092AAF1 /* VideoOverlayProcessor.h in Headers */ = {isa = PBXBuildFile; fileRef = 08D02B3B2369D2890092AAF1 /* VideoOverlayProcessor.h */; settings = {ATTRIBUTES = (Public, ); }; }; 13 | 08D02B542369D5690092AAF1 /* LICENSE in Resources */ = {isa = PBXBuildFile; fileRef = 08D02B532369D5690092AAF1 /* LICENSE */; }; 14 | 08D02B572369D5C00092AAF1 /* BaseOverlay.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08D02B562369D5C00092AAF1 /* BaseOverlay.swift */; }; 15 | 08D02B592369D5EC0092AAF1 /* TextOverlay.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08D02B582369D5EC0092AAF1 /* TextOverlay.swift */; }; 16 | 08D02B5B2369D6000092AAF1 /* ImageOverlay.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08D02B5A2369D6000092AAF1 /* ImageOverlay.swift */; }; 17 | 08D02B5D2369D60F0092AAF1 /* VideoOverlayProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08D02B5C2369D60F0092AAF1 /* VideoOverlayProcessor.swift */; }; 18 | 1CDC7F91236A36990019F02E /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1CDC7F90236A36990019F02E /* AppDelegate.swift */; }; 19 | 1CDC7F93236A36990019F02E /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1CDC7F92236A36990019F02E /* SceneDelegate.swift */; }; 20 | 1CDC7F95236A36990019F02E /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1CDC7F94236A36990019F02E /* ViewController.swift */; }; 21 | 1CDC7F98236A36990019F02E /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 1CDC7F96236A36990019F02E /* Main.storyboard */; }; 22 | 1CDC7F9A236A369B0019F02E /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 1CDC7F99236A369B0019F02E /* Assets.xcassets */; }; 23 | 1CDC7F9D236A369B0019F02E /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 1CDC7F9B236A369B0019F02E /* LaunchScreen.storyboard */; }; 24 | 1CDC7FA3236A39B20019F02E /* sample.mp4 in Resources */ = {isa = PBXBuildFile; fileRef = 1CDC7FA2236A39B20019F02E /* sample.mp4 */; }; 25 | 1CDC7FA4236A3B830019F02E /* BaseOverlay.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08D02B562369D5C00092AAF1 /* BaseOverlay.swift */; }; 26 | 1CDC7FA5236A3B830019F02E /* TextOverlay.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08D02B582369D5EC0092AAF1 /* TextOverlay.swift */; }; 27 | 1CDC7FA6236A3B830019F02E /* ImageOverlay.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08D02B5A2369D6000092AAF1 /* ImageOverlay.swift */; }; 28 | 1CDC7FA7236A3B830019F02E /* VideoOverlayProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08D02B5C2369D60F0092AAF1 /* VideoOverlayProcessor.swift */; }; 29 | 1CDC7FA9236A3C890019F02E /* overlay.png in Resources */ = {isa = PBXBuildFile; fileRef = 1CDC7FA8236A3C890019F02E /* overlay.png */; }; 30 | /* End PBXBuildFile section */ 31 | 32 | /* Begin PBXContainerItemProxy section */ 33 | 08D02B432369D2890092AAF1 /* PBXContainerItemProxy */ = { 34 | isa = PBXContainerItemProxy; 35 | containerPortal = 08D02B2F2369D2890092AAF1 /* Project object */; 36 | proxyType = 1; 37 | remoteGlobalIDString = 08D02B372369D2890092AAF1; 38 | remoteInfo = VideoOverlayProcessor; 39 | }; 40 | /* End PBXContainerItemProxy section */ 41 | 42 | /* Begin PBXFileReference section */ 43 | 08D02B382369D2890092AAF1 /* VideoOverlayProcessor.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = VideoOverlayProcessor.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 44 | 08D02B3B2369D2890092AAF1 /* VideoOverlayProcessor.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = VideoOverlayProcessor.h; sourceTree = ""; }; 45 | 08D02B3C2369D2890092AAF1 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 46 | 08D02B412369D2890092AAF1 /* VideoOverlayProcessorTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = VideoOverlayProcessorTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 47 | 08D02B462369D2890092AAF1 /* VideoOverlayProcessorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoOverlayProcessorTests.swift; sourceTree = ""; }; 48 | 08D02B482369D2890092AAF1 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 49 | 08D02B522369D5440092AAF1 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; 50 | 08D02B532369D5690092AAF1 /* LICENSE */ = {isa = PBXFileReference; lastKnownFileType = text; path = LICENSE; sourceTree = ""; }; 51 | 08D02B562369D5C00092AAF1 /* BaseOverlay.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseOverlay.swift; sourceTree = ""; }; 52 | 08D02B582369D5EC0092AAF1 /* TextOverlay.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextOverlay.swift; sourceTree = ""; }; 53 | 08D02B5A2369D6000092AAF1 /* ImageOverlay.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageOverlay.swift; sourceTree = ""; }; 54 | 08D02B5C2369D60F0092AAF1 /* VideoOverlayProcessor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoOverlayProcessor.swift; sourceTree = ""; }; 55 | 1CDC7F8E236A36990019F02E /* Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Example.app; sourceTree = BUILT_PRODUCTS_DIR; }; 56 | 1CDC7F90236A36990019F02E /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 57 | 1CDC7F92236A36990019F02E /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; 58 | 1CDC7F94236A36990019F02E /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 59 | 1CDC7F97236A36990019F02E /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 60 | 1CDC7F99236A369B0019F02E /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 61 | 1CDC7F9C236A369B0019F02E /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 62 | 1CDC7F9E236A369B0019F02E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 63 | 1CDC7FA2236A39B20019F02E /* sample.mp4 */ = {isa = PBXFileReference; lastKnownFileType = file; path = sample.mp4; sourceTree = ""; }; 64 | 1CDC7FA8236A3C890019F02E /* overlay.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = overlay.png; sourceTree = ""; }; 65 | /* End PBXFileReference section */ 66 | 67 | /* Begin PBXFrameworksBuildPhase section */ 68 | 08D02B352369D2890092AAF1 /* Frameworks */ = { 69 | isa = PBXFrameworksBuildPhase; 70 | buildActionMask = 2147483647; 71 | files = ( 72 | ); 73 | runOnlyForDeploymentPostprocessing = 0; 74 | }; 75 | 08D02B3E2369D2890092AAF1 /* Frameworks */ = { 76 | isa = PBXFrameworksBuildPhase; 77 | buildActionMask = 2147483647; 78 | files = ( 79 | 08D02B422369D2890092AAF1 /* VideoOverlayProcessor.framework in Frameworks */, 80 | ); 81 | runOnlyForDeploymentPostprocessing = 0; 82 | }; 83 | 1CDC7F8B236A36990019F02E /* Frameworks */ = { 84 | isa = PBXFrameworksBuildPhase; 85 | buildActionMask = 2147483647; 86 | files = ( 87 | ); 88 | runOnlyForDeploymentPostprocessing = 0; 89 | }; 90 | /* End PBXFrameworksBuildPhase section */ 91 | 92 | /* Begin PBXGroup section */ 93 | 08D02B2E2369D2890092AAF1 = { 94 | isa = PBXGroup; 95 | children = ( 96 | 08D02B522369D5440092AAF1 /* README.md */, 97 | 08D02B532369D5690092AAF1 /* LICENSE */, 98 | 08D02B3A2369D2890092AAF1 /* VideoOverlayProcessor */, 99 | 08D02B452369D2890092AAF1 /* VideoOverlayProcessorTests */, 100 | 1CDC7F8F236A36990019F02E /* Example */, 101 | 08D02B392369D2890092AAF1 /* Products */, 102 | ); 103 | sourceTree = ""; 104 | }; 105 | 08D02B392369D2890092AAF1 /* Products */ = { 106 | isa = PBXGroup; 107 | children = ( 108 | 08D02B382369D2890092AAF1 /* VideoOverlayProcessor.framework */, 109 | 08D02B412369D2890092AAF1 /* VideoOverlayProcessorTests.xctest */, 110 | 1CDC7F8E236A36990019F02E /* Example.app */, 111 | ); 112 | name = Products; 113 | sourceTree = ""; 114 | }; 115 | 08D02B3A2369D2890092AAF1 /* VideoOverlayProcessor */ = { 116 | isa = PBXGroup; 117 | children = ( 118 | 08D02B552369D5AC0092AAF1 /* Classes */, 119 | 08D02B3B2369D2890092AAF1 /* VideoOverlayProcessor.h */, 120 | 08D02B3C2369D2890092AAF1 /* Info.plist */, 121 | ); 122 | path = VideoOverlayProcessor; 123 | sourceTree = ""; 124 | }; 125 | 08D02B452369D2890092AAF1 /* VideoOverlayProcessorTests */ = { 126 | isa = PBXGroup; 127 | children = ( 128 | 08D02B462369D2890092AAF1 /* VideoOverlayProcessorTests.swift */, 129 | 08D02B482369D2890092AAF1 /* Info.plist */, 130 | ); 131 | path = VideoOverlayProcessorTests; 132 | sourceTree = ""; 133 | }; 134 | 08D02B552369D5AC0092AAF1 /* Classes */ = { 135 | isa = PBXGroup; 136 | children = ( 137 | 08D02B562369D5C00092AAF1 /* BaseOverlay.swift */, 138 | 08D02B582369D5EC0092AAF1 /* TextOverlay.swift */, 139 | 08D02B5A2369D6000092AAF1 /* ImageOverlay.swift */, 140 | 08D02B5C2369D60F0092AAF1 /* VideoOverlayProcessor.swift */, 141 | ); 142 | path = Classes; 143 | sourceTree = ""; 144 | }; 145 | 1CDC7F8F236A36990019F02E /* Example */ = { 146 | isa = PBXGroup; 147 | children = ( 148 | 1CDC7FA8236A3C890019F02E /* overlay.png */, 149 | 1CDC7FA2236A39B20019F02E /* sample.mp4 */, 150 | 1CDC7F90236A36990019F02E /* AppDelegate.swift */, 151 | 1CDC7F92236A36990019F02E /* SceneDelegate.swift */, 152 | 1CDC7F94236A36990019F02E /* ViewController.swift */, 153 | 1CDC7F96236A36990019F02E /* Main.storyboard */, 154 | 1CDC7F99236A369B0019F02E /* Assets.xcassets */, 155 | 1CDC7F9B236A369B0019F02E /* LaunchScreen.storyboard */, 156 | 1CDC7F9E236A369B0019F02E /* Info.plist */, 157 | ); 158 | path = Example; 159 | sourceTree = ""; 160 | }; 161 | /* End PBXGroup section */ 162 | 163 | /* Begin PBXHeadersBuildPhase section */ 164 | 08D02B332369D2890092AAF1 /* Headers */ = { 165 | isa = PBXHeadersBuildPhase; 166 | buildActionMask = 2147483647; 167 | files = ( 168 | 08D02B492369D2890092AAF1 /* VideoOverlayProcessor.h in Headers */, 169 | ); 170 | runOnlyForDeploymentPostprocessing = 0; 171 | }; 172 | /* End PBXHeadersBuildPhase section */ 173 | 174 | /* Begin PBXNativeTarget section */ 175 | 08D02B372369D2890092AAF1 /* VideoOverlayProcessor */ = { 176 | isa = PBXNativeTarget; 177 | buildConfigurationList = 08D02B4C2369D2890092AAF1 /* Build configuration list for PBXNativeTarget "VideoOverlayProcessor" */; 178 | buildPhases = ( 179 | 08D02B332369D2890092AAF1 /* Headers */, 180 | 08D02B342369D2890092AAF1 /* Sources */, 181 | 08D02B352369D2890092AAF1 /* Frameworks */, 182 | 08D02B362369D2890092AAF1 /* Resources */, 183 | ); 184 | buildRules = ( 185 | ); 186 | dependencies = ( 187 | ); 188 | name = VideoOverlayProcessor; 189 | productName = VideoOverlayProcessor; 190 | productReference = 08D02B382369D2890092AAF1 /* VideoOverlayProcessor.framework */; 191 | productType = "com.apple.product-type.framework"; 192 | }; 193 | 08D02B402369D2890092AAF1 /* VideoOverlayProcessorTests */ = { 194 | isa = PBXNativeTarget; 195 | buildConfigurationList = 08D02B4F2369D2890092AAF1 /* Build configuration list for PBXNativeTarget "VideoOverlayProcessorTests" */; 196 | buildPhases = ( 197 | 08D02B3D2369D2890092AAF1 /* Sources */, 198 | 08D02B3E2369D2890092AAF1 /* Frameworks */, 199 | 08D02B3F2369D2890092AAF1 /* Resources */, 200 | ); 201 | buildRules = ( 202 | ); 203 | dependencies = ( 204 | 08D02B442369D2890092AAF1 /* PBXTargetDependency */, 205 | ); 206 | name = VideoOverlayProcessorTests; 207 | productName = VideoOverlayProcessorTests; 208 | productReference = 08D02B412369D2890092AAF1 /* VideoOverlayProcessorTests.xctest */; 209 | productType = "com.apple.product-type.bundle.unit-test"; 210 | }; 211 | 1CDC7F8D236A36990019F02E /* Example */ = { 212 | isa = PBXNativeTarget; 213 | buildConfigurationList = 1CDC7FA1236A369B0019F02E /* Build configuration list for PBXNativeTarget "Example" */; 214 | buildPhases = ( 215 | 1CDC7F8A236A36990019F02E /* Sources */, 216 | 1CDC7F8B236A36990019F02E /* Frameworks */, 217 | 1CDC7F8C236A36990019F02E /* Resources */, 218 | ); 219 | buildRules = ( 220 | ); 221 | dependencies = ( 222 | ); 223 | name = Example; 224 | productName = Example; 225 | productReference = 1CDC7F8E236A36990019F02E /* Example.app */; 226 | productType = "com.apple.product-type.application"; 227 | }; 228 | /* End PBXNativeTarget section */ 229 | 230 | /* Begin PBXProject section */ 231 | 08D02B2F2369D2890092AAF1 /* Project object */ = { 232 | isa = PBXProject; 233 | attributes = { 234 | LastSwiftUpdateCheck = 1110; 235 | LastUpgradeCheck = 1110; 236 | ORGANIZATIONNAME = "Inspace Labs"; 237 | TargetAttributes = { 238 | 08D02B372369D2890092AAF1 = { 239 | CreatedOnToolsVersion = 11.1; 240 | LastSwiftMigration = 1110; 241 | }; 242 | 08D02B402369D2890092AAF1 = { 243 | CreatedOnToolsVersion = 11.1; 244 | }; 245 | 1CDC7F8D236A36990019F02E = { 246 | CreatedOnToolsVersion = 11.1; 247 | }; 248 | }; 249 | }; 250 | buildConfigurationList = 08D02B322369D2890092AAF1 /* Build configuration list for PBXProject "VideoOverlayProcessor" */; 251 | compatibilityVersion = "Xcode 9.3"; 252 | developmentRegion = en; 253 | hasScannedForEncodings = 0; 254 | knownRegions = ( 255 | en, 256 | Base, 257 | ); 258 | mainGroup = 08D02B2E2369D2890092AAF1; 259 | productRefGroup = 08D02B392369D2890092AAF1 /* Products */; 260 | projectDirPath = ""; 261 | projectRoot = ""; 262 | targets = ( 263 | 08D02B372369D2890092AAF1 /* VideoOverlayProcessor */, 264 | 08D02B402369D2890092AAF1 /* VideoOverlayProcessorTests */, 265 | 1CDC7F8D236A36990019F02E /* Example */, 266 | ); 267 | }; 268 | /* End PBXProject section */ 269 | 270 | /* Begin PBXResourcesBuildPhase section */ 271 | 08D02B362369D2890092AAF1 /* Resources */ = { 272 | isa = PBXResourcesBuildPhase; 273 | buildActionMask = 2147483647; 274 | files = ( 275 | 08D02B542369D5690092AAF1 /* LICENSE in Resources */, 276 | ); 277 | runOnlyForDeploymentPostprocessing = 0; 278 | }; 279 | 08D02B3F2369D2890092AAF1 /* Resources */ = { 280 | isa = PBXResourcesBuildPhase; 281 | buildActionMask = 2147483647; 282 | files = ( 283 | ); 284 | runOnlyForDeploymentPostprocessing = 0; 285 | }; 286 | 1CDC7F8C236A36990019F02E /* Resources */ = { 287 | isa = PBXResourcesBuildPhase; 288 | buildActionMask = 2147483647; 289 | files = ( 290 | 1CDC7F9D236A369B0019F02E /* LaunchScreen.storyboard in Resources */, 291 | 1CDC7F9A236A369B0019F02E /* Assets.xcassets in Resources */, 292 | 1CDC7F98236A36990019F02E /* Main.storyboard in Resources */, 293 | 1CDC7FA3236A39B20019F02E /* sample.mp4 in Resources */, 294 | 1CDC7FA9236A3C890019F02E /* overlay.png in Resources */, 295 | ); 296 | runOnlyForDeploymentPostprocessing = 0; 297 | }; 298 | /* End PBXResourcesBuildPhase section */ 299 | 300 | /* Begin PBXSourcesBuildPhase section */ 301 | 08D02B342369D2890092AAF1 /* Sources */ = { 302 | isa = PBXSourcesBuildPhase; 303 | buildActionMask = 2147483647; 304 | files = ( 305 | 08D02B572369D5C00092AAF1 /* BaseOverlay.swift in Sources */, 306 | 08D02B5B2369D6000092AAF1 /* ImageOverlay.swift in Sources */, 307 | 08D02B592369D5EC0092AAF1 /* TextOverlay.swift in Sources */, 308 | 08D02B5D2369D60F0092AAF1 /* VideoOverlayProcessor.swift in Sources */, 309 | ); 310 | runOnlyForDeploymentPostprocessing = 0; 311 | }; 312 | 08D02B3D2369D2890092AAF1 /* Sources */ = { 313 | isa = PBXSourcesBuildPhase; 314 | buildActionMask = 2147483647; 315 | files = ( 316 | 08D02B472369D2890092AAF1 /* VideoOverlayProcessorTests.swift in Sources */, 317 | ); 318 | runOnlyForDeploymentPostprocessing = 0; 319 | }; 320 | 1CDC7F8A236A36990019F02E /* Sources */ = { 321 | isa = PBXSourcesBuildPhase; 322 | buildActionMask = 2147483647; 323 | files = ( 324 | 1CDC7F95236A36990019F02E /* ViewController.swift in Sources */, 325 | 1CDC7FA4236A3B830019F02E /* BaseOverlay.swift in Sources */, 326 | 1CDC7FA7236A3B830019F02E /* VideoOverlayProcessor.swift in Sources */, 327 | 1CDC7F91236A36990019F02E /* AppDelegate.swift in Sources */, 328 | 1CDC7FA5236A3B830019F02E /* TextOverlay.swift in Sources */, 329 | 1CDC7FA6236A3B830019F02E /* ImageOverlay.swift in Sources */, 330 | 1CDC7F93236A36990019F02E /* SceneDelegate.swift in Sources */, 331 | ); 332 | runOnlyForDeploymentPostprocessing = 0; 333 | }; 334 | /* End PBXSourcesBuildPhase section */ 335 | 336 | /* Begin PBXTargetDependency section */ 337 | 08D02B442369D2890092AAF1 /* PBXTargetDependency */ = { 338 | isa = PBXTargetDependency; 339 | target = 08D02B372369D2890092AAF1 /* VideoOverlayProcessor */; 340 | targetProxy = 08D02B432369D2890092AAF1 /* PBXContainerItemProxy */; 341 | }; 342 | /* End PBXTargetDependency section */ 343 | 344 | /* Begin PBXVariantGroup section */ 345 | 1CDC7F96236A36990019F02E /* Main.storyboard */ = { 346 | isa = PBXVariantGroup; 347 | children = ( 348 | 1CDC7F97236A36990019F02E /* Base */, 349 | ); 350 | name = Main.storyboard; 351 | sourceTree = ""; 352 | }; 353 | 1CDC7F9B236A369B0019F02E /* LaunchScreen.storyboard */ = { 354 | isa = PBXVariantGroup; 355 | children = ( 356 | 1CDC7F9C236A369B0019F02E /* Base */, 357 | ); 358 | name = LaunchScreen.storyboard; 359 | sourceTree = ""; 360 | }; 361 | /* End PBXVariantGroup section */ 362 | 363 | /* Begin XCBuildConfiguration section */ 364 | 08D02B4A2369D2890092AAF1 /* Debug */ = { 365 | isa = XCBuildConfiguration; 366 | buildSettings = { 367 | ALWAYS_SEARCH_USER_PATHS = NO; 368 | CLANG_ANALYZER_NONNULL = YES; 369 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 370 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 371 | CLANG_CXX_LIBRARY = "libc++"; 372 | CLANG_ENABLE_MODULES = YES; 373 | CLANG_ENABLE_OBJC_ARC = YES; 374 | CLANG_ENABLE_OBJC_WEAK = YES; 375 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 376 | CLANG_WARN_BOOL_CONVERSION = YES; 377 | CLANG_WARN_COMMA = YES; 378 | CLANG_WARN_CONSTANT_CONVERSION = YES; 379 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 380 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 381 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 382 | CLANG_WARN_EMPTY_BODY = YES; 383 | CLANG_WARN_ENUM_CONVERSION = YES; 384 | CLANG_WARN_INFINITE_RECURSION = YES; 385 | CLANG_WARN_INT_CONVERSION = YES; 386 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 387 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 388 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 389 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 390 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 391 | CLANG_WARN_STRICT_PROTOTYPES = YES; 392 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 393 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 394 | CLANG_WARN_UNREACHABLE_CODE = YES; 395 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 396 | COPY_PHASE_STRIP = NO; 397 | CURRENT_PROJECT_VERSION = 1; 398 | DEBUG_INFORMATION_FORMAT = dwarf; 399 | ENABLE_STRICT_OBJC_MSGSEND = YES; 400 | ENABLE_TESTABILITY = YES; 401 | GCC_C_LANGUAGE_STANDARD = gnu11; 402 | GCC_DYNAMIC_NO_PIC = NO; 403 | GCC_NO_COMMON_BLOCKS = YES; 404 | GCC_OPTIMIZATION_LEVEL = 0; 405 | GCC_PREPROCESSOR_DEFINITIONS = ( 406 | "DEBUG=1", 407 | "$(inherited)", 408 | ); 409 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 410 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 411 | GCC_WARN_UNDECLARED_SELECTOR = YES; 412 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 413 | GCC_WARN_UNUSED_FUNCTION = YES; 414 | GCC_WARN_UNUSED_VARIABLE = YES; 415 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 416 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 417 | MTL_FAST_MATH = YES; 418 | ONLY_ACTIVE_ARCH = YES; 419 | SDKROOT = iphoneos; 420 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 421 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 422 | VERSIONING_SYSTEM = "apple-generic"; 423 | VERSION_INFO_PREFIX = ""; 424 | }; 425 | name = Debug; 426 | }; 427 | 08D02B4B2369D2890092AAF1 /* Release */ = { 428 | isa = XCBuildConfiguration; 429 | buildSettings = { 430 | ALWAYS_SEARCH_USER_PATHS = NO; 431 | CLANG_ANALYZER_NONNULL = YES; 432 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 433 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 434 | CLANG_CXX_LIBRARY = "libc++"; 435 | CLANG_ENABLE_MODULES = YES; 436 | CLANG_ENABLE_OBJC_ARC = YES; 437 | CLANG_ENABLE_OBJC_WEAK = YES; 438 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 439 | CLANG_WARN_BOOL_CONVERSION = YES; 440 | CLANG_WARN_COMMA = YES; 441 | CLANG_WARN_CONSTANT_CONVERSION = YES; 442 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 443 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 444 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 445 | CLANG_WARN_EMPTY_BODY = YES; 446 | CLANG_WARN_ENUM_CONVERSION = YES; 447 | CLANG_WARN_INFINITE_RECURSION = YES; 448 | CLANG_WARN_INT_CONVERSION = YES; 449 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 450 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 451 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 452 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 453 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 454 | CLANG_WARN_STRICT_PROTOTYPES = YES; 455 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 456 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 457 | CLANG_WARN_UNREACHABLE_CODE = YES; 458 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 459 | COPY_PHASE_STRIP = NO; 460 | CURRENT_PROJECT_VERSION = 1; 461 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 462 | ENABLE_NS_ASSERTIONS = NO; 463 | ENABLE_STRICT_OBJC_MSGSEND = YES; 464 | GCC_C_LANGUAGE_STANDARD = gnu11; 465 | GCC_NO_COMMON_BLOCKS = YES; 466 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 467 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 468 | GCC_WARN_UNDECLARED_SELECTOR = YES; 469 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 470 | GCC_WARN_UNUSED_FUNCTION = YES; 471 | GCC_WARN_UNUSED_VARIABLE = YES; 472 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 473 | MTL_ENABLE_DEBUG_INFO = NO; 474 | MTL_FAST_MATH = YES; 475 | SDKROOT = iphoneos; 476 | SWIFT_COMPILATION_MODE = wholemodule; 477 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 478 | VALIDATE_PRODUCT = YES; 479 | VERSIONING_SYSTEM = "apple-generic"; 480 | VERSION_INFO_PREFIX = ""; 481 | }; 482 | name = Release; 483 | }; 484 | 08D02B4D2369D2890092AAF1 /* Debug */ = { 485 | isa = XCBuildConfiguration; 486 | buildSettings = { 487 | CLANG_ENABLE_MODULES = YES; 488 | CODE_SIGN_STYLE = Automatic; 489 | DEFINES_MODULE = YES; 490 | DEVELOPMENT_TEAM = 3G6PMVPLSF; 491 | DYLIB_COMPATIBILITY_VERSION = 1; 492 | DYLIB_CURRENT_VERSION = 1; 493 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 494 | INFOPLIST_FILE = VideoOverlayProcessor/Info.plist; 495 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 496 | LD_RUNPATH_SEARCH_PATHS = ( 497 | "$(inherited)", 498 | "@executable_path/Frameworks", 499 | "@loader_path/Frameworks", 500 | ); 501 | MARKETING_VERSION = 1.0.0; 502 | PRODUCT_BUNDLE_IDENTIFIER = io.inspace.VideoOverlayProcessor; 503 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; 504 | SKIP_INSTALL = YES; 505 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 506 | SWIFT_VERSION = 5.0; 507 | TARGETED_DEVICE_FAMILY = "1,2"; 508 | }; 509 | name = Debug; 510 | }; 511 | 08D02B4E2369D2890092AAF1 /* Release */ = { 512 | isa = XCBuildConfiguration; 513 | buildSettings = { 514 | CLANG_ENABLE_MODULES = YES; 515 | CODE_SIGN_STYLE = Automatic; 516 | DEFINES_MODULE = YES; 517 | DEVELOPMENT_TEAM = 3G6PMVPLSF; 518 | DYLIB_COMPATIBILITY_VERSION = 1; 519 | DYLIB_CURRENT_VERSION = 1; 520 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 521 | INFOPLIST_FILE = VideoOverlayProcessor/Info.plist; 522 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 523 | LD_RUNPATH_SEARCH_PATHS = ( 524 | "$(inherited)", 525 | "@executable_path/Frameworks", 526 | "@loader_path/Frameworks", 527 | ); 528 | MARKETING_VERSION = 1.0.0; 529 | PRODUCT_BUNDLE_IDENTIFIER = io.inspace.VideoOverlayProcessor; 530 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; 531 | SKIP_INSTALL = YES; 532 | SWIFT_VERSION = 5.0; 533 | TARGETED_DEVICE_FAMILY = "1,2"; 534 | }; 535 | name = Release; 536 | }; 537 | 08D02B502369D2890092AAF1 /* Debug */ = { 538 | isa = XCBuildConfiguration; 539 | buildSettings = { 540 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 541 | CODE_SIGN_STYLE = Automatic; 542 | DEVELOPMENT_TEAM = 3G6PMVPLSF; 543 | INFOPLIST_FILE = VideoOverlayProcessorTests/Info.plist; 544 | LD_RUNPATH_SEARCH_PATHS = ( 545 | "$(inherited)", 546 | "@executable_path/Frameworks", 547 | "@loader_path/Frameworks", 548 | ); 549 | PRODUCT_BUNDLE_IDENTIFIER = io.inspace.VideoOverlayProcessorTests; 550 | PRODUCT_NAME = "$(TARGET_NAME)"; 551 | SWIFT_VERSION = 5.0; 552 | TARGETED_DEVICE_FAMILY = "1,2"; 553 | }; 554 | name = Debug; 555 | }; 556 | 08D02B512369D2890092AAF1 /* Release */ = { 557 | isa = XCBuildConfiguration; 558 | buildSettings = { 559 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 560 | CODE_SIGN_STYLE = Automatic; 561 | DEVELOPMENT_TEAM = 3G6PMVPLSF; 562 | INFOPLIST_FILE = VideoOverlayProcessorTests/Info.plist; 563 | LD_RUNPATH_SEARCH_PATHS = ( 564 | "$(inherited)", 565 | "@executable_path/Frameworks", 566 | "@loader_path/Frameworks", 567 | ); 568 | PRODUCT_BUNDLE_IDENTIFIER = io.inspace.VideoOverlayProcessorTests; 569 | PRODUCT_NAME = "$(TARGET_NAME)"; 570 | SWIFT_VERSION = 5.0; 571 | TARGETED_DEVICE_FAMILY = "1,2"; 572 | }; 573 | name = Release; 574 | }; 575 | 1CDC7F9F236A369B0019F02E /* Debug */ = { 576 | isa = XCBuildConfiguration; 577 | buildSettings = { 578 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 579 | CODE_SIGN_STYLE = Automatic; 580 | DEVELOPMENT_TEAM = 3G6PMVPLSF; 581 | INFOPLIST_FILE = Example/Info.plist; 582 | IPHONEOS_DEPLOYMENT_TARGET = 13.1; 583 | LD_RUNPATH_SEARCH_PATHS = ( 584 | "$(inherited)", 585 | "@executable_path/Frameworks", 586 | ); 587 | PRODUCT_BUNDLE_IDENTIFIER = io.inspace.Example; 588 | PRODUCT_NAME = "$(TARGET_NAME)"; 589 | SWIFT_VERSION = 5.0; 590 | TARGETED_DEVICE_FAMILY = "1,2"; 591 | }; 592 | name = Debug; 593 | }; 594 | 1CDC7FA0236A369B0019F02E /* Release */ = { 595 | isa = XCBuildConfiguration; 596 | buildSettings = { 597 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 598 | CODE_SIGN_STYLE = Automatic; 599 | DEVELOPMENT_TEAM = 3G6PMVPLSF; 600 | INFOPLIST_FILE = Example/Info.plist; 601 | IPHONEOS_DEPLOYMENT_TARGET = 13.1; 602 | LD_RUNPATH_SEARCH_PATHS = ( 603 | "$(inherited)", 604 | "@executable_path/Frameworks", 605 | ); 606 | PRODUCT_BUNDLE_IDENTIFIER = io.inspace.Example; 607 | PRODUCT_NAME = "$(TARGET_NAME)"; 608 | SWIFT_VERSION = 5.0; 609 | TARGETED_DEVICE_FAMILY = "1,2"; 610 | }; 611 | name = Release; 612 | }; 613 | /* End XCBuildConfiguration section */ 614 | 615 | /* Begin XCConfigurationList section */ 616 | 08D02B322369D2890092AAF1 /* Build configuration list for PBXProject "VideoOverlayProcessor" */ = { 617 | isa = XCConfigurationList; 618 | buildConfigurations = ( 619 | 08D02B4A2369D2890092AAF1 /* Debug */, 620 | 08D02B4B2369D2890092AAF1 /* Release */, 621 | ); 622 | defaultConfigurationIsVisible = 0; 623 | defaultConfigurationName = Release; 624 | }; 625 | 08D02B4C2369D2890092AAF1 /* Build configuration list for PBXNativeTarget "VideoOverlayProcessor" */ = { 626 | isa = XCConfigurationList; 627 | buildConfigurations = ( 628 | 08D02B4D2369D2890092AAF1 /* Debug */, 629 | 08D02B4E2369D2890092AAF1 /* Release */, 630 | ); 631 | defaultConfigurationIsVisible = 0; 632 | defaultConfigurationName = Release; 633 | }; 634 | 08D02B4F2369D2890092AAF1 /* Build configuration list for PBXNativeTarget "VideoOverlayProcessorTests" */ = { 635 | isa = XCConfigurationList; 636 | buildConfigurations = ( 637 | 08D02B502369D2890092AAF1 /* Debug */, 638 | 08D02B512369D2890092AAF1 /* Release */, 639 | ); 640 | defaultConfigurationIsVisible = 0; 641 | defaultConfigurationName = Release; 642 | }; 643 | 1CDC7FA1236A369B0019F02E /* Build configuration list for PBXNativeTarget "Example" */ = { 644 | isa = XCConfigurationList; 645 | buildConfigurations = ( 646 | 1CDC7F9F236A369B0019F02E /* Debug */, 647 | 1CDC7FA0236A369B0019F02E /* Release */, 648 | ); 649 | defaultConfigurationIsVisible = 0; 650 | defaultConfigurationName = Release; 651 | }; 652 | /* End XCConfigurationList section */ 653 | }; 654 | rootObject = 08D02B2F2369D2890092AAF1 /* Project object */; 655 | } 656 | -------------------------------------------------------------------------------- /VideoOverlayProcessor.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /VideoOverlayProcessor.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /VideoOverlayProcessor/Classes/BaseOverlay.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BaseOverlay.swift 3 | // VideoOverlayProcessor 4 | // 5 | // Created by Dawid Płatek on 30/10/2019. 6 | // Copyright © 2019 Inspace Labs. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import AVFoundation 11 | 12 | class BaseOverlay { 13 | let frame: CGRect 14 | let delay: TimeInterval 15 | let duration: TimeInterval 16 | let backgroundColor: UIColor 17 | 18 | var startAnimation: CAAnimation { 19 | let animation = CABasicAnimation(keyPath: "opacity") 20 | animation.fromValue = timeRange.start.seconds == 0.0 ? 1.0 : 0.0 21 | animation.toValue = 1.0 22 | animation.beginTime = AVCoreAnimationBeginTimeAtZero + timeRange.start.seconds 23 | animation.duration = 0.01 // WORKAROUND: we have to change the duration to avoid animating initial phase 24 | animation.fillMode = .forwards 25 | animation.isRemovedOnCompletion = false 26 | 27 | return animation 28 | } 29 | 30 | var endAnimation: CAAnimation { 31 | let animation = CABasicAnimation(keyPath: "opacity") 32 | animation.fromValue = 1.0 33 | animation.toValue = 0.0 34 | animation.beginTime = AVCoreAnimationBeginTimeAtZero + timeRange.start.seconds + timeRange.duration.seconds 35 | animation.duration = 0.01 // WORKAROUND: we have to change the duration to avoid animating final phase 36 | animation.fillMode = .forwards 37 | animation.isRemovedOnCompletion = false 38 | 39 | return animation 40 | } 41 | 42 | var layer: CALayer { 43 | fatalError("Subclasses need to implement the `layer` property.") 44 | } 45 | 46 | var timeRange: CMTimeRange { 47 | let timescale: Double = 1000 48 | 49 | let startTime = CMTimeMake(value: Int64(delay*timescale), timescale: Int32(timescale)) 50 | let durationTime = CMTimeMake(value: Int64(duration*timescale), timescale: Int32(timescale)) 51 | 52 | return CMTimeRangeMake(start: startTime, duration: durationTime) 53 | } 54 | 55 | init(frame: CGRect, 56 | delay: TimeInterval, 57 | duration: TimeInterval, 58 | backgroundColor: UIColor = UIColor.clear) { 59 | 60 | self.frame = frame 61 | self.delay = delay 62 | self.duration = duration 63 | self.backgroundColor = backgroundColor 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /VideoOverlayProcessor/Classes/ImageOverlay.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ImageOverlay.swift 3 | // VideoOverlayProcessor 4 | // 5 | // Created by Dawid Płatek on 30/10/2019. 6 | // Copyright © 2019 Inspace Labs. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class ImageOverlay: BaseOverlay { 12 | let image: UIImage 13 | 14 | override var layer: CALayer { 15 | let imageLayer = CALayer() 16 | imageLayer.contents = image.cgImage 17 | imageLayer.backgroundColor = backgroundColor.cgColor 18 | imageLayer.frame = frame 19 | imageLayer.opacity = 0.0 20 | 21 | return imageLayer 22 | } 23 | 24 | init(image: UIImage, 25 | frame: CGRect, 26 | delay: TimeInterval, 27 | duration: TimeInterval, 28 | backgroundColor: UIColor = UIColor.clear) { 29 | 30 | self.image = image 31 | 32 | super.init(frame: frame, delay: delay, duration: duration, backgroundColor: backgroundColor) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /VideoOverlayProcessor/Classes/TextOverlay.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TextOverlay.swift 3 | // VideoOverlayProcessor 4 | // 5 | // Created by Dawid Płatek on 30/10/2019. 6 | // Copyright © 2019 Inspace Labs. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class TextOverlay: BaseOverlay { 12 | let text: String 13 | let textColor: UIColor 14 | let font: UIFont 15 | let textAlignment: NSTextAlignment 16 | 17 | override var layer: CALayer { 18 | let textLayer = CATextLayer() 19 | textLayer.backgroundColor = backgroundColor.cgColor 20 | textLayer.foregroundColor = textColor.cgColor 21 | textLayer.string = text 22 | textLayer.isWrapped = true 23 | textLayer.font = font 24 | textLayer.alignmentMode = layerAlignmentMode 25 | textLayer.frame = frame 26 | textLayer.opacity = 0.0 27 | textLayer.displayIfNeeded() 28 | 29 | return textLayer 30 | } 31 | 32 | private var layerAlignmentMode: CATextLayerAlignmentMode { 33 | switch textAlignment { 34 | case NSTextAlignment.left: 35 | return CATextLayerAlignmentMode.left 36 | case NSTextAlignment.center: 37 | return CATextLayerAlignmentMode.center 38 | case NSTextAlignment.right: 39 | return CATextLayerAlignmentMode.right 40 | case NSTextAlignment.justified: 41 | return CATextLayerAlignmentMode.justified 42 | case NSTextAlignment.natural: 43 | return CATextLayerAlignmentMode.natural 44 | @unknown default: 45 | return CATextLayerAlignmentMode.center 46 | } 47 | } 48 | 49 | init(text: String, 50 | frame: CGRect, 51 | delay: TimeInterval, 52 | duration: TimeInterval, 53 | backgroundColor: UIColor = UIColor.clear, 54 | textColor: UIColor = UIColor.black, 55 | font: UIFont = UIFont.systemFont(ofSize: 28), 56 | textAlignment: NSTextAlignment = NSTextAlignment.center) { 57 | 58 | self.text = text 59 | self.textColor = textColor 60 | self.font = font 61 | self.textAlignment = textAlignment 62 | 63 | super.init(frame: frame, delay: delay, duration: duration, backgroundColor: backgroundColor) 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /VideoOverlayProcessor/Classes/VideoOverlayProcessor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // VideoOverlayProcessor.swift 3 | // VideoOverlayProcessor 4 | // 5 | // Created by Dawid Płatek on 30/10/2019. 6 | // Copyright © 2019 Inspace Labs. All rights reserved. 7 | // 8 | 9 | import AVFoundation 10 | 11 | class VideoOverlayProcessor { 12 | let inputURL: URL 13 | let outputURL: URL 14 | 15 | var outputPresetName: String = AVAssetExportPresetHighestQuality 16 | 17 | private var overlays: [BaseOverlay] = [] 18 | 19 | var videoSize: CGSize { 20 | let asset = AVURLAsset(url: inputURL) 21 | return asset.tracks(withMediaType: AVMediaType.video).first?.naturalSize ?? CGSize.zero 22 | } 23 | 24 | var videoDuration: TimeInterval { 25 | let asset = AVURLAsset(url: inputURL) 26 | return asset.duration.seconds 27 | } 28 | 29 | private var asset: AVAsset { 30 | return AVURLAsset(url: inputURL) 31 | } 32 | 33 | // MARK: Initializers 34 | 35 | init(inputURL: URL, outputURL: URL) { 36 | self.inputURL = inputURL 37 | self.outputURL = outputURL 38 | } 39 | 40 | // MARK: Processing 41 | 42 | func process(_ completionHandler: @escaping (_ exportSession: AVAssetExportSession?) -> Void) { 43 | let composition = AVMutableComposition() 44 | let asset = AVURLAsset(url: inputURL) 45 | 46 | guard let videoTrack = asset.tracks(withMediaType: AVMediaType.video).first else { 47 | completionHandler(nil) 48 | return 49 | } 50 | 51 | guard let compositionVideoTrack: AVMutableCompositionTrack = composition.addMutableTrack(withMediaType: AVMediaType.video, preferredTrackID: CMPersistentTrackID(kCMPersistentTrackID_Invalid)) else { 52 | completionHandler(nil) 53 | return 54 | } 55 | 56 | let timeRange = CMTimeRangeMake(start: CMTime.zero, duration: asset.duration) 57 | 58 | do { 59 | try compositionVideoTrack.insertTimeRange(timeRange, of: videoTrack, at: CMTime.zero) 60 | compositionVideoTrack.preferredTransform = videoTrack.preferredTransform 61 | } catch { 62 | completionHandler(nil) 63 | return 64 | } 65 | 66 | if let audioTrack = asset.tracks(withMediaType: AVMediaType.audio).first { 67 | let compositionAudioTrack = composition.addMutableTrack(withMediaType: AVMediaType.audio, preferredTrackID: CMPersistentTrackID(kCMPersistentTrackID_Invalid)) 68 | 69 | do { 70 | try compositionAudioTrack?.insertTimeRange(timeRange, of: audioTrack, at: CMTime.zero) 71 | } catch { 72 | completionHandler(nil) 73 | return 74 | } 75 | } 76 | 77 | let overlayLayer = CALayer() 78 | let videoLayer = CALayer() 79 | overlayLayer.frame = CGRect(x: 0, y: 0, width: videoSize.width, height: videoSize.height) 80 | videoLayer.frame = CGRect(x: 0, y: 0, width: videoSize.width, height: videoSize.height) 81 | overlayLayer.addSublayer(videoLayer) 82 | 83 | overlays.forEach { (overlay) in 84 | let layer = overlay.layer 85 | layer.add(overlay.startAnimation, forKey: "startAnimation") 86 | layer.add(overlay.endAnimation, forKey: "endAnimation") 87 | overlayLayer.addSublayer(layer) 88 | } 89 | 90 | let videoComposition = AVMutableVideoComposition() 91 | videoComposition.renderSize = videoSize 92 | videoComposition.frameDuration = CMTimeMake(value: 1, timescale: 30) 93 | videoComposition.animationTool = AVVideoCompositionCoreAnimationTool(postProcessingAsVideoLayer: videoLayer, in: overlayLayer) 94 | 95 | let instruction = AVMutableVideoCompositionInstruction() 96 | instruction.timeRange = CMTimeRangeMake(start: CMTime.zero, duration: composition.duration) 97 | _ = composition.tracks(withMediaType: AVMediaType.video)[0] as AVAssetTrack 98 | 99 | let layerInstruction = AVMutableVideoCompositionLayerInstruction(assetTrack: compositionVideoTrack) 100 | instruction.layerInstructions = [layerInstruction] 101 | videoComposition.instructions = [instruction] 102 | 103 | guard let exportSession = AVAssetExportSession(asset: composition, presetName: outputPresetName) else { 104 | completionHandler(nil) 105 | return 106 | } 107 | 108 | exportSession.outputURL = outputURL 109 | exportSession.outputFileType = AVFileType.mp4 110 | exportSession.shouldOptimizeForNetworkUse = true 111 | exportSession.videoComposition = videoComposition 112 | exportSession.exportAsynchronously { () -> Void in 113 | completionHandler(exportSession) 114 | } 115 | } 116 | 117 | func addOverlay(_ overlay: BaseOverlay) { 118 | overlays.append(overlay) 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /VideoOverlayProcessor/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | $(MARKETING_VERSION) 19 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | 22 | 23 | -------------------------------------------------------------------------------- /VideoOverlayProcessor/VideoOverlayProcessor.h: -------------------------------------------------------------------------------- 1 | // 2 | // VideoOverlayProcessor.h 3 | // VideoOverlayProcessor 4 | // 5 | // Created by Dawid Płatek on 30/10/2019. 6 | // Copyright © 2019 Inspace Labs. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | //! Project version number for VideoOverlayProcessor. 12 | FOUNDATION_EXPORT double VideoOverlayProcessorVersionNumber; 13 | 14 | //! Project version string for VideoOverlayProcessor. 15 | FOUNDATION_EXPORT const unsigned char VideoOverlayProcessorVersionString[]; 16 | 17 | // In this header, you should import all the public headers of your framework using statements like #import 18 | 19 | 20 | -------------------------------------------------------------------------------- /VideoOverlayProcessorTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /VideoOverlayProcessorTests/VideoOverlayProcessorTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // VideoOverlayProcessorTests.swift 3 | // VideoOverlayProcessorTests 4 | // 5 | // Created by Dawid Płatek on 30/10/2019. 6 | // Copyright © 2019 Inspace Labs. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import VideoOverlayProcessor 11 | 12 | class VideoOverlayProcessorTests: XCTestCase { 13 | 14 | override func setUp() { 15 | // Put setup code here. This method is called before the invocation of each test method in the class. 16 | } 17 | 18 | override func tearDown() { 19 | // Put teardown code here. This method is called after the invocation of each test method in the class. 20 | } 21 | 22 | func testExample() { 23 | // This is an example of a functional test case. 24 | // Use XCTAssert and related functions to verify your tests produce the correct results. 25 | } 26 | 27 | func testPerformanceExample() { 28 | // This is an example of a performance test case. 29 | self.measure { 30 | // Put the code you want to measure the time of here. 31 | } 32 | } 33 | 34 | } 35 | --------------------------------------------------------------------------------