├── .travis.yml ├── Example ├── Podfile ├── Podfile.lock ├── Pods │ ├── CropPickerView │ │ ├── CropPickerView │ │ │ └── Classes │ │ │ │ ├── BezierPath+.swift │ │ │ │ ├── CropDimView.swift │ │ │ │ ├── CropPickerView.swift │ │ │ │ ├── CropResult.swift │ │ │ │ ├── CropView.swift │ │ │ │ ├── LineButton.swift │ │ │ │ ├── NSLayoutConstraint+.swift │ │ │ │ ├── UIImage+fixOrientation.swift │ │ │ │ ├── UIImageView+.swift │ │ │ │ └── UIView+.swift │ │ ├── LICENSE │ │ └── README.md │ ├── Local Podspecs │ │ └── VideoConverter.podspec.json │ ├── Manifest.lock │ ├── Pods.xcodeproj │ │ ├── project.pbxproj │ │ └── xcuserdata │ │ │ ├── apple.xcuserdatad │ │ │ └── xcschemes │ │ │ │ ├── CropPickerView.xcscheme │ │ │ │ ├── Pods-VideoConverter_Example.xcscheme │ │ │ │ ├── Pods-VideoConverter_Tests.xcscheme │ │ │ │ ├── VideoConverter.xcscheme │ │ │ │ ├── VideoTrim.xcscheme │ │ │ │ └── xcschememanagement.plist │ │ │ ├── corpdocfriends.xcuserdatad │ │ │ └── xcschemes │ │ │ │ └── xcschememanagement.plist │ │ │ └── gwanhokim.xcuserdatad │ │ │ └── xcschemes │ │ │ └── xcschememanagement.plist │ ├── Target Support Files │ │ ├── CropPickerView │ │ │ ├── CropPickerView-Info.plist │ │ │ ├── CropPickerView-dummy.m │ │ │ ├── CropPickerView-prefix.pch │ │ │ ├── CropPickerView-umbrella.h │ │ │ ├── CropPickerView.debug.xcconfig │ │ │ ├── CropPickerView.modulemap │ │ │ └── CropPickerView.release.xcconfig │ │ ├── Pods-VideoConverter_Example │ │ │ ├── Pods-VideoConverter_Example-Info.plist │ │ │ ├── Pods-VideoConverter_Example-acknowledgements.markdown │ │ │ ├── Pods-VideoConverter_Example-acknowledgements.plist │ │ │ ├── Pods-VideoConverter_Example-dummy.m │ │ │ ├── Pods-VideoConverter_Example-frameworks.sh │ │ │ ├── Pods-VideoConverter_Example-umbrella.h │ │ │ ├── Pods-VideoConverter_Example.debug.xcconfig │ │ │ ├── Pods-VideoConverter_Example.modulemap │ │ │ └── Pods-VideoConverter_Example.release.xcconfig │ │ ├── Pods-VideoConverter_Tests │ │ │ ├── Pods-VideoConverter_Tests-Info.plist │ │ │ ├── Pods-VideoConverter_Tests-acknowledgements.markdown │ │ │ ├── Pods-VideoConverter_Tests-acknowledgements.plist │ │ │ ├── Pods-VideoConverter_Tests-dummy.m │ │ │ ├── Pods-VideoConverter_Tests-umbrella.h │ │ │ ├── Pods-VideoConverter_Tests.debug.xcconfig │ │ │ ├── Pods-VideoConverter_Tests.modulemap │ │ │ └── Pods-VideoConverter_Tests.release.xcconfig │ │ ├── VideoConverter │ │ │ ├── VideoConverter-Info.plist │ │ │ ├── VideoConverter-dummy.m │ │ │ ├── VideoConverter-prefix.pch │ │ │ ├── VideoConverter-umbrella.h │ │ │ ├── VideoConverter.debug.xcconfig │ │ │ ├── VideoConverter.modulemap │ │ │ └── VideoConverter.release.xcconfig │ │ └── VideoTrim │ │ │ ├── VideoTrim-Info.plist │ │ │ ├── VideoTrim-dummy.m │ │ │ ├── VideoTrim-prefix.pch │ │ │ ├── VideoTrim-umbrella.h │ │ │ ├── VideoTrim.debug.xcconfig │ │ │ ├── VideoTrim.modulemap │ │ │ └── VideoTrim.release.xcconfig │ └── VideoTrim │ │ ├── LICENSE │ │ ├── README.md │ │ └── VideoTrim │ │ └── Classes │ │ ├── Int+Time.swift │ │ ├── VideoTrim.swift │ │ └── VisualEffectImageView.swift ├── Tests │ ├── Info.plist │ └── Tests.swift ├── VideoConverter.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ └── contents.xcworkspacedata │ ├── xcshareddata │ │ └── xcschemes │ │ │ └── VideoConverter-Example.xcscheme │ └── xcuserdata │ │ ├── corpdocfriends.xcuserdatad │ │ └── xcschemes │ │ │ └── xcschememanagement.plist │ │ └── gwanhokim.xcuserdatad │ │ └── xcschemes │ │ └── xcschememanagement.plist ├── VideoConverter.xcworkspace │ ├── contents.xcworkspacedata │ ├── xcshareddata │ │ └── IDEWorkspaceChecks.plist │ └── xcuserdata │ │ ├── apple.xcuserdatad │ │ ├── UserInterfaceState.xcuserstate │ │ └── xcdebugger │ │ │ └── Breakpoints_v2.xcbkptlist │ │ ├── corpdocfriends.xcuserdatad │ │ └── UserInterfaceState.xcuserstate │ │ └── gwanhokim.xcuserdatad │ │ └── UserInterfaceState.xcuserstate └── VideoConverter │ ├── AppDelegate.swift │ ├── Base.lproj │ ├── LaunchScreen.xib │ └── Main.storyboard │ ├── CropViewController.swift │ ├── Images.xcassets │ └── AppIcon.appiconset │ │ └── Contents.json │ ├── Info.plist │ ├── UIImage+Rotate.swift │ ├── VideoView.swift │ └── ViewController.swift ├── LICENSE ├── README.md ├── VideoConverter.podspec ├── VideoConverter ├── Assets │ └── .gitkeep └── Classes │ ├── .gitkeep │ ├── ConverterCrop.swift │ ├── ConverterDegree.swift │ ├── ConverterOption.swift │ └── VideoConverter.swift ├── _Pods.xcodeproj └── img ├── gif1.gif ├── gif2.gif ├── gif3.gif ├── img1.png ├── img2.png └── img3.png /.travis.yml: -------------------------------------------------------------------------------- 1 | # references: 2 | # * https://www.objc.io/issues/6-build-tools/travis-ci/ 3 | # * https://github.com/supermarin/xcpretty#usage 4 | 5 | osx_image: xcode12.2 6 | language: swift 7 | # cache: cocoapods 8 | podfile: Example/Podfile 9 | before_install: 10 | - gem install cocoapods # Since Travis is not always on latest version 11 | - pod install --project-directory=Example 12 | script: 13 | - xcodebuild -version 14 | - xcodebuild -showsdks 15 | - xcodebuild -list 16 | - set -o pipefail && xcodebuild test -workspace Example/VideoConverter.xcworkspace -scheme VideoConverter-Example -destination 'platform=iOS Simulator,name=iPhone 12,OS=14.2' | xcpretty 17 | - pod lib lint 18 | -------------------------------------------------------------------------------- /Example/Podfile: -------------------------------------------------------------------------------- 1 | use_frameworks! 2 | 3 | target 'VideoConverter_Example' do 4 | pod 'VideoConverter', :path => '../' 5 | pod 'VideoTrim', '0.1.0' 6 | pod 'CropPickerView', '0.2.3' 7 | 8 | target 'VideoConverter_Tests' do 9 | inherit! :search_paths 10 | 11 | 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /Example/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - CropPickerView (0.2.3) 3 | - VideoConverter (0.1.0) 4 | - VideoTrim (0.1.0) 5 | 6 | DEPENDENCIES: 7 | - CropPickerView (= 0.2.3) 8 | - VideoConverter (from `../`) 9 | - VideoTrim (= 0.1.0) 10 | 11 | SPEC REPOS: 12 | trunk: 13 | - CropPickerView 14 | - VideoTrim 15 | 16 | EXTERNAL SOURCES: 17 | VideoConverter: 18 | :path: "../" 19 | 20 | SPEC CHECKSUMS: 21 | CropPickerView: 95decf62e991b0a07d54f9138a9c62edab7e67fa 22 | VideoConverter: f2aa15571fe74b3c4a27ce8d3050297e517093fc 23 | VideoTrim: fd6362eb20f194fa09b5e36c529e8ae1f7b5fc9f 24 | 25 | PODFILE CHECKSUM: 28ee5c7cbfa94510a2183bf4b435ed6398efbe97 26 | 27 | COCOAPODS: 1.10.0.beta.2 28 | -------------------------------------------------------------------------------- /Example/Pods/CropPickerView/CropPickerView/Classes/BezierPath+.swift: -------------------------------------------------------------------------------- 1 | //Copyright (c) 2018 pikachu987 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 | 21 | import UIKit 22 | 23 | extension UIBezierPath { 24 | @discardableResult 25 | func move(_ x: CGFloat, _ y: CGFloat) -> UIBezierPath{ 26 | self.move(to: CGPoint(x: x, y: y)) 27 | return self 28 | } 29 | 30 | @discardableResult 31 | func line(_ x: CGFloat, _ y: CGFloat) -> UIBezierPath { 32 | self.addLine(to: CGPoint(x: x, y: y)) 33 | return self 34 | } 35 | 36 | @discardableResult 37 | func closed() -> UIBezierPath { 38 | self.close() 39 | return self 40 | } 41 | 42 | @discardableResult 43 | func strokeFill(_ color: UIColor, lineWidth: CGFloat = 1) -> UIBezierPath { 44 | color.set() 45 | self.lineWidth = lineWidth 46 | self.stroke() 47 | self.fill() 48 | return self 49 | } 50 | 51 | @discardableResult 52 | func stroke(_ color: UIColor, lineWidth: CGFloat = 1) -> UIBezierPath { 53 | color.set() 54 | self.lineWidth = lineWidth 55 | self.stroke() 56 | return self 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /Example/Pods/CropPickerView/CropPickerView/Classes/CropDimView.swift: -------------------------------------------------------------------------------- 1 | //Copyright (c) 2018 pikachu987 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 | 21 | import UIKit 22 | 23 | // CropDimView 24 | class CropDimView: UIView { 25 | private var path: CGPath? 26 | 27 | init() { 28 | super.init(frame: .zero) 29 | self.isUserInteractionEnabled = false 30 | } 31 | 32 | required init?(coder aDecoder: NSCoder) { 33 | super.init(coder: aDecoder) 34 | } 35 | 36 | func mask(_ path: CGPath, duration: TimeInterval, animated: Bool) { 37 | self.path = path 38 | if let mask = self.layer.mask as? CAShapeLayer { 39 | mask.removeAllAnimations() 40 | if animated { 41 | let animation = CABasicAnimation(keyPath: "path") 42 | animation.delegate = self 43 | animation.fromValue = mask.path 44 | animation.toValue = path 45 | animation.byValue = path 46 | animation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeInEaseOut) 47 | animation.isRemovedOnCompletion = false 48 | animation.fillMode = .forwards 49 | animation.duration = duration 50 | mask.add(animation, forKey: "path") 51 | } else { 52 | mask.path = path 53 | } 54 | } else { 55 | let maskLayer = CAShapeLayer() 56 | maskLayer.fillRule = CAShapeLayerFillRule.evenOdd 57 | maskLayer.backgroundColor = UIColor.clear.cgColor 58 | maskLayer.path = path 59 | self.layer.mask = maskLayer 60 | } 61 | } 62 | } 63 | 64 | // MARK: CAAnimationDelegate 65 | extension CropDimView: CAAnimationDelegate { 66 | public func animationDidStop(_ anim: CAAnimation, finished flag: Bool) { 67 | guard let path = self.path else { return } 68 | if let mask = self.layer.mask as? CAShapeLayer { 69 | mask.removeAllAnimations() 70 | mask.path = path 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /Example/Pods/CropPickerView/CropPickerView/Classes/CropResult.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CropResult.swift 3 | // CropPickerView 4 | // 5 | // Created by Apple on 2020/09/05. 6 | // 7 | 8 | import Foundation 9 | 10 | public struct CropResult { 11 | public var error: Error? 12 | public var image: UIImage? 13 | public var cropFrame: CGRect? 14 | public var imageSize: CGSize? 15 | 16 | public init() { } 17 | 18 | public init(error: Error, cropFrame: CGRect? = nil, imageSize: CGSize? = nil) { 19 | self.error = error 20 | self.cropFrame = cropFrame 21 | self.imageSize = imageSize 22 | } 23 | 24 | public init(image: UIImage, cropFrame: CGRect? = nil, imageSize: CGSize? = nil) { 25 | self.image = image 26 | self.cropFrame = cropFrame 27 | self.imageSize = imageSize 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Example/Pods/CropPickerView/CropPickerView/Classes/CropView.swift: -------------------------------------------------------------------------------- 1 | //Copyright (c) 2018 pikachu987 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 | 21 | import UIKit 22 | 23 | // Crop LineView 24 | class CropView: UIView { 25 | private let margin: CGFloat = 0 26 | private let lineSize: CGFloat = 1 27 | 28 | var lineColor: UIColor? = .white { 29 | willSet { 30 | self.topLineView.backgroundColor = newValue 31 | self.bottomLineView.backgroundColor = newValue 32 | self.leftLineView.backgroundColor = newValue 33 | self.rightLineView.backgroundColor = newValue 34 | self.horizontalRightLineView.backgroundColor = newValue 35 | self.horizontalLeftLineView.backgroundColor = newValue 36 | self.verticalTopLineView.backgroundColor = newValue 37 | self.verticalBottomLineView.backgroundColor = newValue 38 | } 39 | } 40 | 41 | private lazy var topLineView: UIView = { 42 | let view = UIView() 43 | self.addSubview(view) 44 | view.translatesAutoresizingMaskIntoConstraints = false 45 | self.addConstraint(NSLayoutConstraint(item: self, attribute: .leading, relatedBy: .equal, toItem: view, attribute: .leading, multiplier: 1, constant: self.margin).priority(950)) 46 | self.addConstraint(NSLayoutConstraint(item: self, attribute: .trailing, relatedBy: .equal, toItem: view, attribute: .trailing, multiplier: 1, constant: self.margin).priority(950)) 47 | self.addConstraint(NSLayoutConstraint(item: self, attribute: .top, relatedBy: .equal, toItem: view, attribute: .top, multiplier: 1, constant: self.margin).priority(950)) 48 | view.addConstraint(NSLayoutConstraint(item: view, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .height, multiplier: 1, constant: self.lineSize).priority(950)) 49 | return view 50 | }() 51 | 52 | private lazy var leftLineView: UIView = { 53 | let view = UIView() 54 | self.addSubview(view) 55 | view.translatesAutoresizingMaskIntoConstraints = false 56 | self.addConstraint(NSLayoutConstraint(item: self, attribute: .leading, relatedBy: .equal, toItem: view, attribute: .leading, multiplier: 1, constant: self.margin).priority(950)) 57 | self.addConstraint(NSLayoutConstraint(item: self, attribute: .bottom, relatedBy: .equal, toItem: view, attribute: .bottom, multiplier: 1, constant: self.margin).priority(950)) 58 | self.addConstraint(NSLayoutConstraint(item: self, attribute: .top, relatedBy: .equal, toItem: view, attribute: .top, multiplier: 1, constant: self.margin).priority(950)) 59 | view.addConstraint(NSLayoutConstraint(item: view, attribute: .width, relatedBy: .equal, toItem: nil, attribute: .width, multiplier: 1, constant: self.lineSize).priority(950)) 60 | return view 61 | }() 62 | 63 | private lazy var bottomLineView: UIView = { 64 | let view = UIView() 65 | self.addSubview(view) 66 | view.translatesAutoresizingMaskIntoConstraints = false 67 | self.addConstraint(NSLayoutConstraint(item: self, attribute: .leading, relatedBy: .equal, toItem: view, attribute: .leading, multiplier: 1, constant: self.margin).priority(950)) 68 | self.addConstraint(NSLayoutConstraint(item: self, attribute: .trailing, relatedBy: .equal, toItem: view, attribute: .trailing, multiplier: 1, constant: self.margin).priority(950)) 69 | self.addConstraint(NSLayoutConstraint(item: self, attribute: .bottom, relatedBy: .equal, toItem: view, attribute: .top, multiplier: 1, constant: self.margin).priority(950)) 70 | view.addConstraint(NSLayoutConstraint(item: view, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .height, multiplier: 1, constant: self.lineSize).priority(950)) 71 | return view 72 | }() 73 | 74 | private lazy var rightLineView: UIView = { 75 | let view = UIView() 76 | self.addSubview(view) 77 | view.translatesAutoresizingMaskIntoConstraints = false 78 | self.addConstraint(NSLayoutConstraint(item: self, attribute: .trailing, relatedBy: .equal, toItem: view, attribute: .trailing, multiplier: 1, constant: self.margin).priority(950)) 79 | self.addConstraint(NSLayoutConstraint(item: self, attribute: .bottom, relatedBy: .equal, toItem: view, attribute: .bottom, multiplier: 1, constant: self.margin).priority(950)) 80 | self.addConstraint(NSLayoutConstraint(item: self, attribute: .top, relatedBy: .equal, toItem: view, attribute: .top, multiplier: 1, constant: self.margin).priority(950)) 81 | view.addConstraint(NSLayoutConstraint(item: view, attribute: .width, relatedBy: .equal, toItem: nil, attribute: .width, multiplier: 1, constant: self.lineSize).priority(950)) 82 | return view 83 | }() 84 | 85 | private lazy var horizontalLeftLineView: UIView = { 86 | let view = UIView() 87 | self.addSubview(view) 88 | view.translatesAutoresizingMaskIntoConstraints = false 89 | self.addConstraint(NSLayoutConstraint(item: self.horizontalLeftView, attribute: .trailing, relatedBy: .equal, toItem: view, attribute: .leading, multiplier: 1, constant: 0).priority(950)) 90 | self.addConstraint(NSLayoutConstraint(item: self.bottomLineView, attribute: .top, relatedBy: .equal, toItem: view, attribute: .bottom, multiplier: 1, constant: 0).priority(950)) 91 | self.addConstraint(NSLayoutConstraint(item: self.topLineView, attribute: .bottom, relatedBy: .equal, toItem: view, attribute: .top, multiplier: 1, constant: 0).priority(950)) 92 | view.addConstraint(NSLayoutConstraint(item: view, attribute: .width, relatedBy: .equal, toItem: nil, attribute: .width, multiplier: 1, constant: self.lineSize).priority(950)) 93 | return view 94 | }() 95 | 96 | private lazy var horizontalRightLineView: UIView = { 97 | let view = UIView() 98 | self.addSubview(view) 99 | view.translatesAutoresizingMaskIntoConstraints = false 100 | self.addConstraint(NSLayoutConstraint(item: self.horizontalCenterView, attribute: .trailing, relatedBy: .equal, toItem: view, attribute: .leading, multiplier: 1, constant: 0).priority(950)) 101 | self.addConstraint(NSLayoutConstraint(item: self.bottomLineView, attribute: .top, relatedBy: .equal, toItem: view, attribute: .bottom, multiplier: 1, constant: 0).priority(950)) 102 | self.addConstraint(NSLayoutConstraint(item: self.topLineView, attribute: .bottom, relatedBy: .equal, toItem: view, attribute: .top, multiplier: 1, constant: 0).priority(950)) 103 | view.addConstraint(NSLayoutConstraint(item: view, attribute: .width, relatedBy: .equal, toItem: nil, attribute: .width, multiplier: 1, constant: self.lineSize).priority(950)) 104 | return view 105 | }() 106 | 107 | private lazy var verticalTopLineView: UIView = { 108 | let view = UIView() 109 | self.addSubview(view) 110 | view.translatesAutoresizingMaskIntoConstraints = false 111 | self.addConstraint(NSLayoutConstraint(item: self.verticalTopView, attribute: .bottom, relatedBy: .equal, toItem: view, attribute: .top, multiplier: 1, constant: 0).priority(950)) 112 | self.addConstraint(NSLayoutConstraint(item: self.leftLineView, attribute: .trailing, relatedBy: .equal, toItem: view, attribute: .leading, multiplier: 1, constant: 0).priority(950)) 113 | self.addConstraint(NSLayoutConstraint(item: self.rightLineView, attribute: .leading, relatedBy: .equal, toItem: view, attribute: .trailing, multiplier: 1, constant: 0).priority(950)) 114 | view.addConstraint(NSLayoutConstraint(item: view, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .height, multiplier: 1, constant: self.lineSize).priority(950)) 115 | return view 116 | }() 117 | 118 | private lazy var verticalBottomLineView: UIView = { 119 | let view = UIView() 120 | self.addSubview(view) 121 | view.translatesAutoresizingMaskIntoConstraints = false 122 | self.addConstraint(NSLayoutConstraint(item: self.verticalCenterView, attribute: .bottom, relatedBy: .equal, toItem: view, attribute: .top, multiplier: 1, constant: 0).priority(950)) 123 | self.addConstraint(NSLayoutConstraint(item: self.leftLineView, attribute: .trailing, relatedBy: .equal, toItem: view, attribute: .leading, multiplier: 1, constant: 0).priority(950)) 124 | self.addConstraint(NSLayoutConstraint(item: self.rightLineView, attribute: .leading, relatedBy: .equal, toItem: view, attribute: .trailing, multiplier: 1, constant: 0).priority(950)) 125 | view.addConstraint(NSLayoutConstraint(item: view, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .height, multiplier: 1, constant: self.lineSize).priority(950)) 126 | return view 127 | }() 128 | 129 | private lazy var horizontalLeftView: UIView = { 130 | let view = UIView() 131 | self.addSubview(view) 132 | view.translatesAutoresizingMaskIntoConstraints = false 133 | self.addConstraint(NSLayoutConstraint(item: self.leftLineView, attribute: .trailing, relatedBy: .equal, toItem: view, attribute: .leading, multiplier: 1, constant: 0).priority(950)) 134 | self.addConstraint(NSLayoutConstraint(item: self.bottomLineView, attribute: .top, relatedBy: .equal, toItem: view, attribute: .bottom, multiplier: 1, constant: 0).priority(950)) 135 | self.addConstraint(NSLayoutConstraint(item: self.topLineView, attribute: .bottom, relatedBy: .equal, toItem: view, attribute: .top, multiplier: 1, constant: 0).priority(950)) 136 | return view 137 | }() 138 | 139 | private lazy var horizontalCenterView: UIView = { 140 | let view = UIView() 141 | self.addSubview(view) 142 | view.translatesAutoresizingMaskIntoConstraints = false 143 | self.addConstraint(NSLayoutConstraint(item: self.horizontalLeftLineView, attribute: .trailing, relatedBy: .equal, toItem: view, attribute: .leading, multiplier: 1, constant: 0).priority(950)) 144 | self.addConstraint(NSLayoutConstraint(item: self.bottomLineView, attribute: .top, relatedBy: .equal, toItem: view, attribute: .bottom, multiplier: 1, constant: 0).priority(950)) 145 | self.addConstraint(NSLayoutConstraint(item: self.topLineView, attribute: .bottom, relatedBy: .equal, toItem: view, attribute: .top, multiplier: 1, constant: 0).priority(950)) 146 | self.addConstraint(NSLayoutConstraint(item: self.horizontalLeftView, attribute: .width, relatedBy: .equal, toItem: view, attribute: .width, multiplier: 1, constant: 0).priority(950)) 147 | return view 148 | }() 149 | 150 | private lazy var horizontalRightView: UIView = { 151 | let view = UIView() 152 | self.addSubview(view) 153 | view.translatesAutoresizingMaskIntoConstraints = false 154 | self.addConstraint(NSLayoutConstraint(item: self.horizontalRightLineView, attribute: .trailing, relatedBy: .equal, toItem: view, attribute: .leading, multiplier: 1, constant: 0).priority(950)) 155 | self.addConstraint(NSLayoutConstraint(item: self.bottomLineView, attribute: .top, relatedBy: .equal, toItem: view, attribute: .bottom, multiplier: 1, constant: 0).priority(950)) 156 | self.addConstraint(NSLayoutConstraint(item: self.topLineView, attribute: .bottom, relatedBy: .equal, toItem: view, attribute: .top, multiplier: 1, constant: 0).priority(950)) 157 | self.addConstraint(NSLayoutConstraint(item: self.rightLineView, attribute: .leading, relatedBy: .equal, toItem: view, attribute: .trailing, multiplier: 1, constant: 0).priority(950)) 158 | self.addConstraint(NSLayoutConstraint(item: self.horizontalLeftView, attribute: .width, relatedBy: .equal, toItem: view, attribute: .width, multiplier: 1, constant: 0).priority(950)) 159 | return view 160 | }() 161 | 162 | private lazy var verticalTopView: UIView = { 163 | let view = UIView() 164 | self.addSubview(view) 165 | view.translatesAutoresizingMaskIntoConstraints = false 166 | self.addConstraint(NSLayoutConstraint(item: self.topLineView, attribute: .bottom, relatedBy: .equal, toItem: view, attribute: .top, multiplier: 1, constant: 0).priority(950)) 167 | self.addConstraint(NSLayoutConstraint(item: self.leftLineView, attribute: .trailing, relatedBy: .equal, toItem: view, attribute: .leading, multiplier: 1, constant: 0).priority(950)) 168 | self.addConstraint(NSLayoutConstraint(item: self.rightLineView, attribute: .leading, relatedBy: .equal, toItem: view, attribute: .trailing, multiplier: 1, constant: 0).priority(950)) 169 | return view 170 | }() 171 | 172 | private lazy var verticalCenterView: UIView = { 173 | let view = UIView() 174 | self.addSubview(view) 175 | view.translatesAutoresizingMaskIntoConstraints = false 176 | self.addConstraint(NSLayoutConstraint(item: self.verticalTopLineView, attribute: .bottom, relatedBy: .equal, toItem: view, attribute: .top, multiplier: 1, constant: 0).priority(950)) 177 | self.addConstraint(NSLayoutConstraint(item: self.leftLineView, attribute: .trailing, relatedBy: .equal, toItem: view, attribute: .leading, multiplier: 1, constant: 0).priority(950)) 178 | self.addConstraint(NSLayoutConstraint(item: self.rightLineView, attribute: .leading, relatedBy: .equal, toItem: view, attribute: .trailing, multiplier: 1, constant: 0).priority(950)) 179 | self.addConstraint(NSLayoutConstraint(item: self.verticalTopView, attribute: .height, relatedBy: .equal, toItem: view, attribute: .height, multiplier: 1, constant: 0).priority(950)) 180 | return view 181 | }() 182 | 183 | private lazy var verticalBottomView: UIView = { 184 | let view = UIView() 185 | self.addSubview(view) 186 | view.translatesAutoresizingMaskIntoConstraints = false 187 | self.addConstraint(NSLayoutConstraint(item: self.verticalBottomLineView, attribute: .bottom, relatedBy: .equal, toItem: view, attribute: .top, multiplier: 1, constant: 0).priority(950)) 188 | self.addConstraint(NSLayoutConstraint(item: self.leftLineView, attribute: .trailing, relatedBy: .equal, toItem: view, attribute: .leading, multiplier: 1, constant: 0).priority(950)) 189 | self.addConstraint(NSLayoutConstraint(item: self.rightLineView, attribute: .leading, relatedBy: .equal, toItem: view, attribute: .trailing, multiplier: 1, constant: 0).priority(950)) 190 | self.addConstraint(NSLayoutConstraint(item: self.verticalTopView, attribute: .height, relatedBy: .equal, toItem: view, attribute: .height, multiplier: 1, constant: 0).priority(950)) 191 | self.addConstraint(NSLayoutConstraint(item: self.bottomLineView, attribute: .top, relatedBy: .equal, toItem: view, attribute: .bottom, multiplier: 1, constant: 0).priority(950)) 192 | return view 193 | }() 194 | 195 | override func awakeFromNib() { 196 | super.awakeFromNib() 197 | 198 | self.initVars() 199 | } 200 | 201 | init() { 202 | super.init(frame: .zero) 203 | 204 | self.initVars() 205 | } 206 | 207 | override init(frame: CGRect) { 208 | super.init(frame: frame) 209 | 210 | self.initVars() 211 | } 212 | 213 | required init?(coder aDecoder: NSCoder) { 214 | super.init(coder: aDecoder) 215 | } 216 | 217 | private func initVars() { 218 | self.isUserInteractionEnabled = false 219 | self.backgroundColor = .clear 220 | self.topLineView.alpha = 1 221 | self.leftLineView.alpha = 1 222 | self.bottomLineView.alpha = 1 223 | self.rightLineView.alpha = 1 224 | self.horizontalLeftLineView.alpha = 0 225 | self.horizontalRightLineView.alpha = 0 226 | self.verticalTopLineView.alpha = 0 227 | self.verticalBottomLineView.alpha = 0 228 | 229 | self.horizontalLeftView.alpha = 0 230 | self.horizontalCenterView.alpha = 0 231 | self.horizontalRightView.alpha = 0 232 | self.verticalTopView.alpha = 0 233 | self.verticalCenterView.alpha = 0 234 | self.verticalBottomView.alpha = 0 235 | } 236 | 237 | func line(_ isHidden: Bool, animated: Bool) { 238 | if animated { 239 | UIView.animate(withDuration: 0.3) { 240 | if isHidden { 241 | self.horizontalRightLineView.alpha = 0 242 | self.horizontalLeftLineView.alpha = 0 243 | self.verticalTopLineView.alpha = 0 244 | self.verticalBottomLineView.alpha = 0 245 | } else { 246 | self.horizontalRightLineView.alpha = 1 247 | self.horizontalLeftLineView.alpha = 1 248 | self.verticalTopLineView.alpha = 1 249 | self.verticalBottomLineView.alpha = 1 250 | } 251 | } 252 | } else { 253 | if isHidden { 254 | self.horizontalRightLineView.alpha = 0 255 | self.horizontalLeftLineView.alpha = 0 256 | self.verticalTopLineView.alpha = 0 257 | self.verticalBottomLineView.alpha = 0 258 | } else { 259 | self.horizontalRightLineView.alpha = 1 260 | self.horizontalLeftLineView.alpha = 1 261 | self.verticalTopLineView.alpha = 1 262 | self.verticalBottomLineView.alpha = 1 263 | } 264 | } 265 | } 266 | } 267 | -------------------------------------------------------------------------------- /Example/Pods/CropPickerView/CropPickerView/Classes/LineButton.swift: -------------------------------------------------------------------------------- 1 | //Copyright (c) 2018 pikachu987 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 | 21 | import UIKit 22 | 23 | // Called when the button's highlighted is false. 24 | protocol LineButtonDelegate: class { 25 | func lineButtonUnHighlighted() 26 | } 27 | 28 | // Side, Edge LineButton 29 | class LineButton: UIButton { 30 | weak var delegate: LineButtonDelegate? 31 | 32 | private var type: ButtonLineType 33 | 34 | override var isHighlighted: Bool { 35 | didSet { 36 | if !self.isHighlighted { 37 | self.delegate?.lineButtonUnHighlighted() 38 | } 39 | } 40 | } 41 | 42 | // MARK: Init 43 | init(_ type: ButtonLineType) { 44 | self.type = type 45 | super.init(frame: CGRect(x: 0, y: 0, width: 50, height: 50)) 46 | 47 | self.setTitle(nil, for: .normal) 48 | self.translatesAutoresizingMaskIntoConstraints = false 49 | if type != .center { 50 | self.widthConstraint(constant: 50) 51 | self.heightConstraint(constant: 50) 52 | self.alpha = 0 53 | } 54 | } 55 | 56 | required init?(coder aDecoder: NSCoder) { 57 | fatalError("init(coder:) has not been implemented") 58 | } 59 | 60 | func edgeLine(_ color: UIColor?) { 61 | self.setImage(self.type.view(color)?.imageWithView?.withRenderingMode(.alwaysOriginal), for: .normal) 62 | } 63 | } 64 | 65 | enum ButtonLineType { 66 | case center 67 | case leftTop, rightTop, leftBottom, rightBottom, top, left, right, bottom 68 | 69 | var rotate: CGFloat { 70 | switch self { 71 | case .leftTop: 72 | return 0 73 | case .rightTop: 74 | return CGFloat.pi/2 75 | case .rightBottom: 76 | return CGFloat.pi 77 | case .leftBottom: 78 | return CGFloat.pi/2*3 79 | case .top: 80 | return 0 81 | case .left: 82 | return CGFloat.pi/2*3 83 | case .right: 84 | return CGFloat.pi/2 85 | case .bottom: 86 | return CGFloat.pi 87 | case .center: 88 | return 0 89 | } 90 | } 91 | 92 | var yMargin: CGFloat { 93 | switch self { 94 | case .rightBottom, .bottom: 95 | return 1 96 | default: 97 | return 0 98 | } 99 | } 100 | 101 | var xMargin: CGFloat { 102 | switch self { 103 | case .leftBottom: 104 | return 1 105 | default: 106 | return 0 107 | } 108 | } 109 | 110 | func view(_ color: UIColor?) -> UIView? { 111 | var view: UIView? 112 | if self == .leftTop || self == .rightTop || self == .leftBottom || self == .rightBottom { 113 | view = ButtonLineType.EdgeView(self, color: color) 114 | } else { 115 | view = ButtonLineType.SideView(self, color: color) 116 | } 117 | view?.isOpaque = false 118 | view?.tintColor = color 119 | return view 120 | } 121 | 122 | class LineView: UIView { 123 | var type: ButtonLineType 124 | var color: UIColor? 125 | init(_ type: ButtonLineType, color: UIColor?) { 126 | self.type = type 127 | self.color = color 128 | super.init(frame: CGRect(x: 0, y: 0, width: 50, height: 50)) 129 | } 130 | required init?(coder aDecoder: NSCoder) { 131 | fatalError("init(coder:) has not been implemented") 132 | } 133 | func apply(_ path: UIBezierPath) { 134 | var pathTransform = CGAffineTransform.identity 135 | pathTransform = pathTransform.translatedBy(x: 25, y: 25) 136 | pathTransform = pathTransform.rotated(by: self.type.rotate) 137 | pathTransform = pathTransform.translatedBy(x: -25 - self.type.xMargin, y: -25 - self.type.yMargin) 138 | path.apply(pathTransform) 139 | path.closed() 140 | .strokeFill(self.color ?? .white) 141 | } 142 | } 143 | 144 | class EdgeView: LineView { 145 | override func draw(_ rect: CGRect) { 146 | let path = UIBezierPath() 147 | .move(6, 6) 148 | .line(6, 20) 149 | .line(8, 20) 150 | .line(8, 8) 151 | .line(20, 8) 152 | .line(20, 6) 153 | .line(6, 6) 154 | self.apply(path) 155 | } 156 | } 157 | class SideView: LineView { 158 | override func draw(_ rect: CGRect) { 159 | let path = UIBezierPath() 160 | .move(15, 6) 161 | .line(35, 6) 162 | .line(35, 8) 163 | .line(15, 8) 164 | .line(15, 6) 165 | self.apply(path) 166 | } 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /Example/Pods/CropPickerView/CropPickerView/Classes/NSLayoutConstraint+.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NSLayoutConstraint+.swift 3 | // CropPickerView 4 | // 5 | // Created by Gwanho Kim on 02/12/2018. 6 | // 7 | 8 | import UIKit 9 | 10 | extension NSLayoutConstraint { 11 | func priority(_ value: CGFloat) -> NSLayoutConstraint { 12 | self.priority = UILayoutPriority(Float(value)) 13 | return self 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Example/Pods/CropPickerView/CropPickerView/Classes/UIImage+fixOrientation.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIImage+fixOrientation.swift 3 | // CropPickerView 4 | // 5 | // Created by Apple on 2020/08/23. 6 | // 7 | 8 | import UIKit 9 | 10 | // 이미지 회전 원복 11 | extension UIImage { 12 | var fixOrientation: UIImage? { 13 | if self.imageOrientation == .up { return self } 14 | var transform = CGAffineTransform.identity 15 | 16 | if self.imageOrientation == .down || self.imageOrientation == .downMirrored { 17 | transform = transform.translatedBy(x: self.size.width, y: self.size.height) 18 | transform = transform.rotated(by: CGFloat(Double.pi)) 19 | } 20 | 21 | if self.imageOrientation == .left || self.imageOrientation == .leftMirrored { 22 | transform = transform.translatedBy(x: self.size.width, y: 0) 23 | transform = transform.rotated(by: CGFloat(Double.pi / 2.0)) 24 | } 25 | 26 | if self.imageOrientation == .right || self.imageOrientation == .rightMirrored { 27 | transform = transform.translatedBy(x: 0, y: self.size.height) 28 | transform = transform.rotated(by: CGFloat(-Double.pi / 2.0)) 29 | } 30 | 31 | if self.imageOrientation == .upMirrored || self.imageOrientation == .downMirrored { 32 | transform = transform.translatedBy(x: self.size.width, y: 0) 33 | transform = transform.scaledBy(x: -1, y: 1) 34 | } 35 | 36 | if self.imageOrientation == .leftMirrored || self.imageOrientation == .rightMirrored { 37 | transform = transform.translatedBy(x: self.size.height, y: 0) 38 | transform = transform.scaledBy(x: -1, y: 1) 39 | } 40 | 41 | guard let cgImage = self.cgImage, 42 | let colorSpace = cgImage.colorSpace else { return nil } 43 | 44 | guard let ctx = CGContext(data: nil, width: Int(self.size.width), height: Int(self.size.height), 45 | bitsPerComponent: cgImage.bitsPerComponent, bytesPerRow: 0, 46 | space: colorSpace, 47 | bitmapInfo: cgImage.bitmapInfo.rawValue) else { return nil } 48 | 49 | ctx.concatenate(transform) 50 | 51 | if self.imageOrientation == .left || 52 | self.imageOrientation == .leftMirrored || 53 | self.imageOrientation == .right || 54 | self.imageOrientation == .rightMirrored { 55 | ctx.draw(cgImage, in: CGRect(x: 0, y: 0, width: self.size.height, height: self.size.width)) 56 | } else { 57 | ctx.draw(cgImage, in: CGRect(x: 0, y: 0, width: self.size.width, height: self.size.height)) 58 | } 59 | guard let makeImage = ctx.makeImage() else { return nil } 60 | return UIImage(cgImage: makeImage) 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /Example/Pods/CropPickerView/CropPickerView/Classes/UIImageView+.swift: -------------------------------------------------------------------------------- 1 | //Copyright (c) 2018 pikachu987 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 | 21 | import UIKit 22 | 23 | extension UIImageView { 24 | var frameForImageInImageViewAspectFit: CGRect { 25 | if let img = self.image { 26 | let imageRatio = img.size.width / img.size.height 27 | let viewRatio = self.frame.size.width / self.frame.size.height 28 | if(imageRatio < viewRatio) { 29 | let scale = self.frame.size.height / img.size.height 30 | let width = scale * img.size.width 31 | let topLeftX = (self.frame.size.width - width) * 0.5 32 | return CGRect(x: topLeftX, y: 0, width: width, height: self.frame.size.height) 33 | } else { 34 | let scale = self.frame.size.width / img.size.width 35 | let height = scale * img.size.height 36 | let topLeftY = (self.frame.size.height - height) * 0.5 37 | return CGRect(x: 0, y: topLeftY, width: self.frame.size.width, height: height) 38 | } 39 | } 40 | return CGRect(x: 0, y: 0, width: 0, height: 0) 41 | } 42 | 43 | var imageFrame: CGRect { 44 | let imageViewSize = self.frame.size 45 | guard let imageSize = self.image?.size else { return CGRect.zero } 46 | let imageRatio = imageSize.width / imageSize.height 47 | let imageViewRatio = imageViewSize.width / imageViewSize.height 48 | if imageRatio < imageViewRatio { 49 | let scaleFactor = imageViewSize.height / imageSize.height 50 | let width = imageSize.width * scaleFactor 51 | let topLeftX = (imageViewSize.width - width) * 0.5 52 | return CGRect(x: topLeftX, y: 0, width: width, height: imageViewSize.height) 53 | } else { 54 | let scalFactor = imageViewSize.width / imageSize.width 55 | let height = imageSize.height * scalFactor 56 | let topLeftY = (imageViewSize.height - height) * 0.5 57 | return CGRect(x: 0, y: topLeftY, width: imageViewSize.width, height: height) 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /Example/Pods/CropPickerView/CropPickerView/Classes/UIView+.swift: -------------------------------------------------------------------------------- 1 | //Copyright (c) 2018 pikachu987 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 | 21 | import UIKit 22 | 23 | extension UIView { 24 | 25 | var imageWithView: UIImage? { 26 | if #available(iOS 10.0, *) { 27 | let renderer = UIGraphicsImageRenderer(bounds: self.bounds) 28 | return renderer.image { rendererContext in 29 | self.layer.render(in: rendererContext.cgContext) 30 | } 31 | } else { 32 | UIGraphicsBeginImageContextWithOptions(self.bounds.size, self.isOpaque, 0.0) 33 | if let cgContext = UIGraphicsGetCurrentContext() { 34 | self.layer.render(in: cgContext) 35 | } 36 | self.drawHierarchy(in: self.bounds, afterScreenUpdates: true) 37 | let image = UIGraphicsGetImageFromCurrentImageContext() 38 | UIGraphicsEndImageContext() 39 | return image 40 | } 41 | } 42 | 43 | func edgesConstraint(subView: UIView, constant: CGFloat = 0) { 44 | self.leadingConstraint(subView: subView, constant: constant) 45 | self.trailingConstraint(subView: subView, constant: constant) 46 | self.topConstraint(subView: subView, constant: constant) 47 | self.bottomConstraint(subView: subView, constant: constant) 48 | } 49 | 50 | func sizeConstraint(subView: UIView, constant: CGFloat = 0) { 51 | self.widthConstraint(subView: subView, constant: constant) 52 | self.heightConstraint(subView: subView, constant: constant) 53 | } 54 | 55 | func sizeConstraint(constant: CGFloat = 0) { 56 | self.widthConstraint(constant: constant) 57 | self.heightConstraint(constant: constant) 58 | } 59 | 60 | @discardableResult 61 | func leadingConstraint(subView: UIView, constant: CGFloat = 0, multiplier: CGFloat = 1, relatedBy: NSLayoutConstraint.Relation = .equal) -> NSLayoutConstraint { 62 | let constraint = NSLayoutConstraint(item: self, attribute: .leading, relatedBy: relatedBy, toItem: subView, attribute: .leading, multiplier: multiplier, constant: constant) 63 | self.addConstraint(constraint) 64 | return constraint 65 | } 66 | 67 | @discardableResult 68 | func trailingConstraint(subView: UIView, constant: CGFloat = 0, multiplier: CGFloat = 1, relatedBy: NSLayoutConstraint.Relation = .equal) -> NSLayoutConstraint { 69 | let constraint = NSLayoutConstraint(item: self, attribute: .trailing, relatedBy: relatedBy, toItem: subView, attribute: .trailing, multiplier: multiplier, constant: constant) 70 | self.addConstraint(constraint) 71 | return constraint 72 | } 73 | 74 | @discardableResult 75 | func topConstraint(subView: UIView, constant: CGFloat = 0, multiplier: CGFloat = 1, relatedBy: NSLayoutConstraint.Relation = .equal) -> NSLayoutConstraint { 76 | let constraint = NSLayoutConstraint(item: self, attribute: .top, relatedBy: relatedBy, toItem: subView, attribute: .top, multiplier: multiplier, constant: constant) 77 | self.addConstraint(constraint) 78 | return constraint 79 | } 80 | 81 | @discardableResult 82 | func bottomConstraint(subView: UIView, constant: CGFloat = 0, multiplier: CGFloat = 1, relatedBy: NSLayoutConstraint.Relation = .equal) -> NSLayoutConstraint { 83 | let constraint = NSLayoutConstraint(item: self, attribute: .bottom, relatedBy: relatedBy, toItem: subView, attribute: .bottom, multiplier: multiplier, constant: constant) 84 | self.addConstraint(constraint) 85 | return constraint 86 | } 87 | 88 | @discardableResult 89 | func centerXConstraint(subView: UIView, constant: CGFloat = 0, multiplier: CGFloat = 1, relatedBy: NSLayoutConstraint.Relation = .equal) -> NSLayoutConstraint { 90 | let constraint = NSLayoutConstraint(item: self, attribute: .centerX, relatedBy: relatedBy, toItem: subView, attribute: .centerX, multiplier: multiplier, constant: constant) 91 | self.addConstraint(constraint) 92 | return constraint 93 | } 94 | 95 | @discardableResult 96 | func centerYConstraint(subView: UIView, constant: CGFloat = 0, multiplier: CGFloat = 1, relatedBy: NSLayoutConstraint.Relation = .equal) -> NSLayoutConstraint { 97 | let constraint = NSLayoutConstraint(item: self, attribute: .centerY, relatedBy: relatedBy, toItem: subView, attribute: .centerY, multiplier: multiplier, constant: constant) 98 | self.addConstraint(constraint) 99 | return constraint 100 | } 101 | 102 | @discardableResult 103 | func leadingConstraint(item: UIView, subView: UIView, constant: CGFloat = 0, multiplier: CGFloat = 1, relatedBy: NSLayoutConstraint.Relation = .equal) -> NSLayoutConstraint { 104 | let constraint = NSLayoutConstraint(item: item, attribute: .leading, relatedBy: relatedBy, toItem: subView, attribute: .leading, multiplier: multiplier, constant: constant) 105 | self.addConstraint(constraint) 106 | return constraint 107 | } 108 | 109 | @discardableResult 110 | func trailingConstraint(item: UIView, subView: UIView, constant: CGFloat = 0, multiplier: CGFloat = 1, relatedBy: NSLayoutConstraint.Relation = .equal) -> NSLayoutConstraint { 111 | let constraint = NSLayoutConstraint(item: item, attribute: .trailing, relatedBy: relatedBy, toItem: subView, attribute: .trailing, multiplier: multiplier, constant: constant) 112 | self.addConstraint(constraint) 113 | return constraint 114 | } 115 | 116 | @discardableResult 117 | func topConstraint(item: UIView, subView: UIView, constant: CGFloat = 0, multiplier: CGFloat = 1, relatedBy: NSLayoutConstraint.Relation = .equal) -> NSLayoutConstraint { 118 | let constraint = NSLayoutConstraint(item: item, attribute: .top, relatedBy: relatedBy, toItem: subView, attribute: .top, multiplier: multiplier, constant: constant) 119 | self.addConstraint(constraint) 120 | return constraint 121 | } 122 | 123 | @discardableResult 124 | func bottomConstraint(item: UIView, subView: UIView, constant: CGFloat = 0, multiplier: CGFloat = 1, relatedBy: NSLayoutConstraint.Relation = .equal) -> NSLayoutConstraint { 125 | let constraint = NSLayoutConstraint(item: item, attribute: .bottom, relatedBy: relatedBy, toItem: subView, attribute: .bottom, multiplier: multiplier, constant: constant) 126 | self.addConstraint(constraint) 127 | return constraint 128 | } 129 | 130 | @discardableResult 131 | func centerXConstraint(item: UIView, subView: UIView, constant: CGFloat = 0, multiplier: CGFloat = 1, relatedBy: NSLayoutConstraint.Relation = .equal) -> NSLayoutConstraint { 132 | let constraint = NSLayoutConstraint(item: item, attribute: .centerX, relatedBy: relatedBy, toItem: subView, attribute: .centerX, multiplier: multiplier, constant: constant) 133 | self.addConstraint(constraint) 134 | return constraint 135 | } 136 | 137 | @discardableResult 138 | func centerYConstraint(item: UIView, subView: UIView, constant: CGFloat = 0, multiplier: CGFloat = 1, relatedBy: NSLayoutConstraint.Relation = .equal) -> NSLayoutConstraint { 139 | let constraint = NSLayoutConstraint(item: item, attribute: .centerY, relatedBy: relatedBy, toItem: subView, attribute: .centerY, multiplier: multiplier, constant: constant) 140 | self.addConstraint(constraint) 141 | return constraint 142 | } 143 | 144 | @discardableResult 145 | func widthConstraint(item: UIView, subView: UIView, constant: CGFloat = 0, multiplier: CGFloat = 1, relatedBy: NSLayoutConstraint.Relation = .equal) -> NSLayoutConstraint { 146 | let constraint = NSLayoutConstraint(item: item, attribute: .width, relatedBy: relatedBy, toItem: subView, attribute: .width, multiplier: multiplier, constant: constant) 147 | self.addConstraint(constraint) 148 | return constraint 149 | } 150 | 151 | @discardableResult 152 | func heightConstraint(item: UIView, subView: UIView, constant: CGFloat = 0, multiplier: CGFloat = 1, relatedBy: NSLayoutConstraint.Relation = .equal) -> NSLayoutConstraint { 153 | let constraint = NSLayoutConstraint(item: item, attribute: .height, relatedBy: relatedBy, toItem: subView, attribute: .height, multiplier: multiplier, constant: constant) 154 | self.addConstraint(constraint) 155 | return constraint 156 | } 157 | 158 | @discardableResult 159 | func widthConstraint(subView: UIView, constant: CGFloat = 0, multiplier: CGFloat = 1, relatedBy: NSLayoutConstraint.Relation = .equal) -> NSLayoutConstraint { 160 | let constraint = NSLayoutConstraint(item: self, attribute: .width, relatedBy: relatedBy, toItem: subView, attribute: .width, multiplier: multiplier, constant: constant) 161 | self.addConstraint(constraint) 162 | return constraint 163 | } 164 | 165 | @discardableResult 166 | func heightConstraint(subView: UIView, constant: CGFloat = 0, multiplier: CGFloat = 1, relatedBy: NSLayoutConstraint.Relation = .equal) -> NSLayoutConstraint { 167 | let constraint = NSLayoutConstraint(item: self, attribute: .height, relatedBy: relatedBy, toItem: subView, attribute: .height, multiplier: multiplier, constant: constant) 168 | self.addConstraint(constraint) 169 | return constraint 170 | } 171 | 172 | @discardableResult 173 | func widthConstraint(constant: CGFloat = 0, multiplier: CGFloat = 1, relatedBy: NSLayoutConstraint.Relation = .equal) -> NSLayoutConstraint { 174 | let constraint = NSLayoutConstraint(item: self, attribute: .width, relatedBy: relatedBy, toItem: nil, attribute: .width, multiplier: multiplier, constant: constant) 175 | self.addConstraint(constraint) 176 | return constraint 177 | } 178 | 179 | @discardableResult 180 | func heightConstraint(constant: CGFloat = 0, multiplier: CGFloat = 1, relatedBy: NSLayoutConstraint.Relation = .equal) -> NSLayoutConstraint { 181 | let constraint = NSLayoutConstraint(item: self, attribute: .height, relatedBy: relatedBy, toItem: nil, attribute: .height, multiplier: multiplier, constant: constant) 182 | self.addConstraint(constraint) 183 | return constraint 184 | } 185 | } 186 | -------------------------------------------------------------------------------- /Example/Pods/CropPickerView/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2018 pikachu987 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 | -------------------------------------------------------------------------------- /Example/Pods/CropPickerView/README.md: -------------------------------------------------------------------------------- 1 | # CropPickerView 2 | 3 | [![Version](https://img.shields.io/cocoapods/v/CropPickerView.svg?style=flat)](https://cocoapods.org/pods/CropPickerView) 4 | [![License](https://img.shields.io/cocoapods/l/CropPickerView.svg?style=flat)](https://cocoapods.org/pods/CropPickerView) 5 | [![Platform](https://img.shields.io/cocoapods/p/CropPickerView.svg?style=flat)](https://cocoapods.org/pods/CropPickerView) 6 | [![Swift 5.0](https://img.shields.io/badge/Swift-5.0-orange.svg?style=flat)](https://developer.apple.com/swift/) 7 | 8 | ## Introduce 9 | 10 | The Corner and Side buttons allow you to modify the position of the crop and UIScrollView to zoom the image. If the image is larger than the area of the UIScrollView, the image can be scrolled up and down, left and right, and if the image is smaller than the area of the UIScrollView, the image is always centered. 11 | 12 | ### [CropPickerController](https://github.com/pikachu987/CropPickerController) 13 | 14 | If you want to see the CropPickerViewController that is created with CropView, see [CropPickerController](https://github.com/pikachu987/CropPickerController) 15 | 16 |
17 | 18 | ### CropPickerView 19 | 20 | 21 | 22 | 23 | 24 | ## Requirements 25 | 26 | `CropPickerView` written in Swift 5.0. Compatible with iOS 8.0+ 27 | 28 | ## Installation 29 | 30 | CropPickerView is available through [CocoaPods](https://cocoapods.org). To install 31 | it, simply add the following line to your Podfile: 32 | 33 | ```ruby 34 | pod 'CropPickerView' 35 | ``` 36 | 37 | ## Usage 38 | 39 | 40 | ### Xib or Storyboard file 41 | 42 | setting 43 | 44 | ![image](./img/1.png) 45 | 46 | ![image](./img/2.png) 47 | 48 | done! 49 | 50 |


51 | 52 | ### Code editor 53 | 54 | ```swift 55 | import CropPickerView 56 | ``` 57 | 58 | ```swift 59 | let cropPickerView = CropPickerView() 60 | self.view.addSubview(cropPickerView) 61 | ``` 62 | 63 | done! 64 | 65 |


66 | 67 | 68 | 69 | ### Property 70 | 71 | image 72 | 73 | ```swift 74 | 75 | cropPickerView.image = image 76 | cropPickerView.image(image, crop: CGRect(x: 50, y: 30, width: 100, height: 80), isRealCropRect: false) 77 | cropPickerView.image(image, crop: CGRect(x: 50, y: 30, width: 100, height: 80), isRealCropRect: true) 78 | cropPickerView.image(image, isMin: false, crop: CGRect(x: 50, y: 30, width: 100, height: 80), isRealCropRect: true) 79 | cropPickerView.image(image, isMin: false) 80 | 81 | ``` 82 | 83 | color 84 | 85 | ```swift 86 | 87 | cropPickerView.cropLineColor = UIColor.gray 88 | cropPickerView.scrollBackgroundColor = UIColor.gray 89 | cropPickerView.imageBackgroundColor = UIColor.gray 90 | cropPickerView.dimBackgroundColor = UIColor(white: 0, alpha: 0.1) 91 | 92 | ``` 93 | 94 | zoom 95 | 96 | ```swift 97 | 98 | cropPickerView.scrollMinimumZoomScale = 1 99 | cropPickerView.scrollMaximumZoomScale = 2 100 | 101 | ``` 102 | 103 | cropSize 104 | 105 | ```swift 106 | 107 | cropPickerView.cropMinSize = 200 108 | 109 | ``` 110 | 111 |

112 | 113 | ### Method 114 | 115 | crop 116 | 117 | ```swift 118 | 119 | cropPickerView.crop { (result) in 120 | if let error = (result.error as NSError?) { 121 | let alertController = UIAlertController(title: "Error", message: error.domain, preferredStyle: .alert) 122 | alertController.addAction(UIAlertAction(title: "Ok", style: .cancel, handler: nil)) 123 | self.present(alertController, animated: true, completion: nil) 124 | return 125 | } 126 | self.imageView.image = result.image 127 | } 128 | 129 | ``` 130 | 131 |

132 | 133 | ### Delegate 134 | 135 | ```swift 136 | 137 | class ViewController: UIViewController{ 138 | override func viewDidLoad() { 139 | super.viewDidLoad() 140 | 141 | let cropPickerView = CropPickerView() 142 | cropPickerView.delegate = self 143 | } 144 | } 145 | 146 | // MARK: CropPickerViewDelegate 147 | extension ViewController: CropPickerViewDelegate { 148 | func cropPickerView(_ cropPickerView: CropPickerView, result: CropResult) { 149 | 150 | } 151 | } 152 | 153 | ``` 154 | 155 | 156 | ## Author 157 | 158 | pikachu987, pikachu77769@gmail.com 159 | 160 | ## License 161 | 162 | CropPickerView is available under the MIT license. See the LICENSE file for more info. 163 | -------------------------------------------------------------------------------- /Example/Pods/Local Podspecs/VideoConverter.podspec.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "VideoConverter", 3 | "version": "0.1.0", 4 | "summary": "A short description of VideoConverter.", 5 | "description": "TODO: Add long description of the pod here.", 6 | "homepage": "https://github.com/pikachu987/VideoConverter", 7 | "license": { 8 | "type": "MIT", 9 | "file": "LICENSE" 10 | }, 11 | "authors": { 12 | "pikachu987": "pikachu77769@gmail.com" 13 | }, 14 | "source": { 15 | "git": "https://github.com/pikachu987/VideoConverter.git", 16 | "tag": "0.1.0" 17 | }, 18 | "platforms": { 19 | "ios": "8.0" 20 | }, 21 | "source_files": "VideoConverter/Classes/**/*" 22 | } 23 | -------------------------------------------------------------------------------- /Example/Pods/Manifest.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - CropPickerView (0.2.3) 3 | - VideoConverter (0.1.0) 4 | - VideoTrim (0.1.0) 5 | 6 | DEPENDENCIES: 7 | - CropPickerView (= 0.2.3) 8 | - VideoConverter (from `../`) 9 | - VideoTrim (= 0.1.0) 10 | 11 | SPEC REPOS: 12 | trunk: 13 | - CropPickerView 14 | - VideoTrim 15 | 16 | EXTERNAL SOURCES: 17 | VideoConverter: 18 | :path: "../" 19 | 20 | SPEC CHECKSUMS: 21 | CropPickerView: 95decf62e991b0a07d54f9138a9c62edab7e67fa 22 | VideoConverter: f2aa15571fe74b3c4a27ce8d3050297e517093fc 23 | VideoTrim: fd6362eb20f194fa09b5e36c529e8ae1f7b5fc9f 24 | 25 | PODFILE CHECKSUM: 28ee5c7cbfa94510a2183bf4b435ed6398efbe97 26 | 27 | COCOAPODS: 1.10.0.beta.2 28 | -------------------------------------------------------------------------------- /Example/Pods/Pods.xcodeproj/xcuserdata/apple.xcuserdatad/xcschemes/CropPickerView.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 44 | 50 | 51 | 53 | 54 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /Example/Pods/Pods.xcodeproj/xcuserdata/apple.xcuserdatad/xcschemes/Pods-VideoConverter_Example.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 44 | 50 | 51 | 53 | 54 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /Example/Pods/Pods.xcodeproj/xcuserdata/apple.xcuserdatad/xcschemes/Pods-VideoConverter_Tests.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 44 | 50 | 51 | 53 | 54 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /Example/Pods/Pods.xcodeproj/xcuserdata/apple.xcuserdatad/xcschemes/VideoConverter.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 44 | 50 | 51 | 53 | 54 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /Example/Pods/Pods.xcodeproj/xcuserdata/apple.xcuserdatad/xcschemes/VideoTrim.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 44 | 50 | 51 | 53 | 54 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /Example/Pods/Pods.xcodeproj/xcuserdata/apple.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | CropPickerView.xcscheme 8 | 9 | isShown 10 | 11 | 12 | Pods-VideoConverter_Example.xcscheme 13 | 14 | isShown 15 | 16 | 17 | Pods-VideoConverter_Tests.xcscheme 18 | 19 | isShown 20 | 21 | 22 | VideoConverter.xcscheme 23 | 24 | isShown 25 | 26 | 27 | VideoTrim.xcscheme 28 | 29 | isShown 30 | 31 | 32 | 33 | SuppressBuildableAutocreation 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /Example/Pods/Pods.xcodeproj/xcuserdata/corpdocfriends.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | CropPickerView.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 2 11 | 12 | Pods-VideoConverter_Example.xcscheme_^#shared#^_ 13 | 14 | orderHint 15 | 4 16 | 17 | Pods-VideoConverter_Tests.xcscheme_^#shared#^_ 18 | 19 | orderHint 20 | 3 21 | 22 | VideoConverter.xcscheme_^#shared#^_ 23 | 24 | orderHint 25 | 5 26 | 27 | VideoTrim.xcscheme_^#shared#^_ 28 | 29 | orderHint 30 | 1 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /Example/Pods/Pods.xcodeproj/xcuserdata/gwanhokim.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | CropPickerView.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 3 11 | 12 | Pods-VideoConverter_Example.xcscheme_^#shared#^_ 13 | 14 | orderHint 15 | 4 16 | 17 | Pods-VideoConverter_Tests.xcscheme_^#shared#^_ 18 | 19 | orderHint 20 | 2 21 | 22 | VideoConverter.xcscheme_^#shared#^_ 23 | 24 | orderHint 25 | 5 26 | 27 | VideoTrim.xcscheme_^#shared#^_ 28 | 29 | orderHint 30 | 1 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/CropPickerView/CropPickerView-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | ${PRODUCT_BUNDLE_IDENTIFIER} 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | ${PRODUCT_NAME} 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | $(MARKETING_VERSION) 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | ${CURRENT_PROJECT_VERSION} 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/CropPickerView/CropPickerView-dummy.m: -------------------------------------------------------------------------------- 1 | #import 2 | @interface PodsDummy_CropPickerView : NSObject 3 | @end 4 | @implementation PodsDummy_CropPickerView 5 | @end 6 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/CropPickerView/CropPickerView-prefix.pch: -------------------------------------------------------------------------------- 1 | #ifdef __OBJC__ 2 | #import 3 | #else 4 | #ifndef FOUNDATION_EXPORT 5 | #if defined(__cplusplus) 6 | #define FOUNDATION_EXPORT extern "C" 7 | #else 8 | #define FOUNDATION_EXPORT extern 9 | #endif 10 | #endif 11 | #endif 12 | 13 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/CropPickerView/CropPickerView-umbrella.h: -------------------------------------------------------------------------------- 1 | #ifdef __OBJC__ 2 | #import 3 | #else 4 | #ifndef FOUNDATION_EXPORT 5 | #if defined(__cplusplus) 6 | #define FOUNDATION_EXPORT extern "C" 7 | #else 8 | #define FOUNDATION_EXPORT extern 9 | #endif 10 | #endif 11 | #endif 12 | 13 | 14 | FOUNDATION_EXPORT double CropPickerViewVersionNumber; 15 | FOUNDATION_EXPORT const unsigned char CropPickerViewVersionString[]; 16 | 17 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/CropPickerView/CropPickerView.debug.xcconfig: -------------------------------------------------------------------------------- 1 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO 2 | CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/CropPickerView 3 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 4 | OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS 5 | PODS_BUILD_DIR = ${BUILD_DIR} 6 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 7 | PODS_ROOT = ${SRCROOT} 8 | PODS_TARGET_SRCROOT = ${PODS_ROOT}/CropPickerView 9 | PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates 10 | PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} 11 | SKIP_INSTALL = YES 12 | USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES 13 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/CropPickerView/CropPickerView.modulemap: -------------------------------------------------------------------------------- 1 | framework module CropPickerView { 2 | umbrella header "CropPickerView-umbrella.h" 3 | 4 | export * 5 | module * { export * } 6 | } 7 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/CropPickerView/CropPickerView.release.xcconfig: -------------------------------------------------------------------------------- 1 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO 2 | CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/CropPickerView 3 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 4 | OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS 5 | PODS_BUILD_DIR = ${BUILD_DIR} 6 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 7 | PODS_ROOT = ${SRCROOT} 8 | PODS_TARGET_SRCROOT = ${PODS_ROOT}/CropPickerView 9 | PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates 10 | PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} 11 | SKIP_INSTALL = YES 12 | USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES 13 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-VideoConverter_Example/Pods-VideoConverter_Example-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | ${PRODUCT_BUNDLE_IDENTIFIER} 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | ${PRODUCT_NAME} 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | ${CURRENT_PROJECT_VERSION} 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-VideoConverter_Example/Pods-VideoConverter_Example-acknowledgements.markdown: -------------------------------------------------------------------------------- 1 | # Acknowledgements 2 | This application makes use of the following third party libraries: 3 | 4 | ## CropPickerView 5 | 6 | Copyright (c) 2018 pikachu987 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining a copy 9 | of this software and associated documentation files (the "Software"), to deal 10 | in the Software without restriction, including without limitation the rights 11 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | copies of the Software, and to permit persons to whom the Software is 13 | furnished to do so, subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be included in 16 | all copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | THE SOFTWARE. 25 | 26 | 27 | ## VideoConverter 28 | 29 | MIT License 30 | 31 | Copyright (c) 2020 pikachu987 32 | 33 | Permission is hereby granted, free of charge, to any person obtaining a copy 34 | of this software and associated documentation files (the "Software"), to deal 35 | in the Software without restriction, including without limitation the rights 36 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 37 | copies of the Software, and to permit persons to whom the Software is 38 | furnished to do so, subject to the following conditions: 39 | 40 | The above copyright notice and this permission notice shall be included in 41 | all copies or substantial portions of the Software. 42 | 43 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 44 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 45 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 46 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 47 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 48 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 49 | THE SOFTWARE. 50 | 51 | 52 | ## VideoTrim 53 | 54 | MIT License 55 | 56 | Copyright (c) 2020 Gwan-ho Kim 57 | 58 | Permission is hereby granted, free of charge, to any person obtaining a copy 59 | of this software and associated documentation files (the "Software"), to deal 60 | in the Software without restriction, including without limitation the rights 61 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 62 | copies of the Software, and to permit persons to whom the Software is 63 | furnished to do so, subject to the following conditions: 64 | 65 | The above copyright notice and this permission notice shall be included in all 66 | copies or substantial portions of the Software. 67 | 68 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 69 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 70 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 71 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 72 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 73 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 74 | SOFTWARE. 75 | 76 | Generated by CocoaPods - https://cocoapods.org 77 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-VideoConverter_Example/Pods-VideoConverter_Example-acknowledgements.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreferenceSpecifiers 6 | 7 | 8 | FooterText 9 | This application makes use of the following third party libraries: 10 | Title 11 | Acknowledgements 12 | Type 13 | PSGroupSpecifier 14 | 15 | 16 | FooterText 17 | Copyright (c) 2018 pikachu987 <pikachu77769@gmail.com> 18 | 19 | Permission is hereby granted, free of charge, to any person obtaining a copy 20 | of this software and associated documentation files (the "Software"), to deal 21 | in the Software without restriction, including without limitation the rights 22 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 23 | copies of the Software, and to permit persons to whom the Software is 24 | furnished to do so, subject to the following conditions: 25 | 26 | The above copyright notice and this permission notice shall be included in 27 | all copies or substantial portions of the Software. 28 | 29 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 30 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 31 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 32 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 33 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 34 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 35 | THE SOFTWARE. 36 | 37 | License 38 | MIT 39 | Title 40 | CropPickerView 41 | Type 42 | PSGroupSpecifier 43 | 44 | 45 | FooterText 46 | MIT License 47 | 48 | Copyright (c) 2020 pikachu987 <pikachu77769@gmail.com> 49 | 50 | Permission is hereby granted, free of charge, to any person obtaining a copy 51 | of this software and associated documentation files (the "Software"), to deal 52 | in the Software without restriction, including without limitation the rights 53 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 54 | copies of the Software, and to permit persons to whom the Software is 55 | furnished to do so, subject to the following conditions: 56 | 57 | The above copyright notice and this permission notice shall be included in 58 | all copies or substantial portions of the Software. 59 | 60 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 61 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 62 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 63 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 64 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 65 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 66 | THE SOFTWARE. 67 | 68 | License 69 | MIT 70 | Title 71 | VideoConverter 72 | Type 73 | PSGroupSpecifier 74 | 75 | 76 | FooterText 77 | MIT License 78 | 79 | Copyright (c) 2020 Gwan-ho Kim <pikachu77769@gmail.com> 80 | 81 | Permission is hereby granted, free of charge, to any person obtaining a copy 82 | of this software and associated documentation files (the "Software"), to deal 83 | in the Software without restriction, including without limitation the rights 84 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 85 | copies of the Software, and to permit persons to whom the Software is 86 | furnished to do so, subject to the following conditions: 87 | 88 | The above copyright notice and this permission notice shall be included in all 89 | copies or substantial portions of the Software. 90 | 91 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 92 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 93 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 94 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 95 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 96 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 97 | SOFTWARE. 98 | 99 | License 100 | MIT 101 | Title 102 | VideoTrim 103 | Type 104 | PSGroupSpecifier 105 | 106 | 107 | FooterText 108 | Generated by CocoaPods - https://cocoapods.org 109 | Title 110 | 111 | Type 112 | PSGroupSpecifier 113 | 114 | 115 | StringsTable 116 | Acknowledgements 117 | Title 118 | Acknowledgements 119 | 120 | 121 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-VideoConverter_Example/Pods-VideoConverter_Example-dummy.m: -------------------------------------------------------------------------------- 1 | #import 2 | @interface PodsDummy_Pods_VideoConverter_Example : NSObject 3 | @end 4 | @implementation PodsDummy_Pods_VideoConverter_Example 5 | @end 6 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-VideoConverter_Example/Pods-VideoConverter_Example-frameworks.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | set -u 4 | set -o pipefail 5 | 6 | function on_error { 7 | echo "$(realpath -mq "${0}"):$1: error: Unexpected failure" 8 | } 9 | trap 'on_error $LINENO' ERR 10 | 11 | if [ -z ${FRAMEWORKS_FOLDER_PATH+x} ]; then 12 | # If FRAMEWORKS_FOLDER_PATH is not set, then there's nowhere for us to copy 13 | # frameworks to, so exit 0 (signalling the script phase was successful). 14 | exit 0 15 | fi 16 | 17 | echo "mkdir -p ${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 18 | mkdir -p "${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 19 | 20 | COCOAPODS_PARALLEL_CODE_SIGN="${COCOAPODS_PARALLEL_CODE_SIGN:-false}" 21 | SWIFT_STDLIB_PATH="${DT_TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" 22 | BCSYMBOLMAP_DIR="BCSymbolMaps" 23 | 24 | 25 | # This protects against multiple targets copying the same framework dependency at the same time. The solution 26 | # was originally proposed here: https://lists.samba.org/archive/rsync/2008-February/020158.html 27 | RSYNC_PROTECT_TMP_FILES=(--filter "P .*.??????") 28 | 29 | # Copies and strips a vendored framework 30 | install_framework() 31 | { 32 | if [ -r "${BUILT_PRODUCTS_DIR}/$1" ]; then 33 | local source="${BUILT_PRODUCTS_DIR}/$1" 34 | elif [ -r "${BUILT_PRODUCTS_DIR}/$(basename "$1")" ]; then 35 | local source="${BUILT_PRODUCTS_DIR}/$(basename "$1")" 36 | elif [ -r "$1" ]; then 37 | local source="$1" 38 | fi 39 | 40 | local destination="${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 41 | 42 | if [ -L "${source}" ]; then 43 | echo "Symlinked..." 44 | source="$(readlink "${source}")" 45 | fi 46 | 47 | if [ -d "${source}/${BCSYMBOLMAP_DIR}" ]; then 48 | # Locate and install any .bcsymbolmaps if present, and remove them from the .framework before the framework is copied 49 | find "${source}/${BCSYMBOLMAP_DIR}" -name "*.bcsymbolmap"|while read f; do 50 | echo "Installing $f" 51 | install_bcsymbolmap "$f" "$destination" 52 | rm "$f" 53 | done 54 | rmdir "${source}/${BCSYMBOLMAP_DIR}" 55 | fi 56 | 57 | # Use filter instead of exclude so missing patterns don't throw errors. 58 | echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --links --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${source}\" \"${destination}\"" 59 | rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --links --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${source}" "${destination}" 60 | 61 | local basename 62 | basename="$(basename -s .framework "$1")" 63 | binary="${destination}/${basename}.framework/${basename}" 64 | 65 | if ! [ -r "$binary" ]; then 66 | binary="${destination}/${basename}" 67 | elif [ -L "${binary}" ]; then 68 | echo "Destination binary is symlinked..." 69 | dirname="$(dirname "${binary}")" 70 | binary="${dirname}/$(readlink "${binary}")" 71 | fi 72 | 73 | # Strip invalid architectures so "fat" simulator / device frameworks work on device 74 | if [[ "$(file "$binary")" == *"dynamically linked shared library"* ]]; then 75 | strip_invalid_archs "$binary" 76 | fi 77 | 78 | # Resign the code if required by the build settings to avoid unstable apps 79 | code_sign_if_enabled "${destination}/$(basename "$1")" 80 | 81 | # Embed linked Swift runtime libraries. No longer necessary as of Xcode 7. 82 | if [ "${XCODE_VERSION_MAJOR}" -lt 7 ]; then 83 | local swift_runtime_libs 84 | swift_runtime_libs=$(xcrun otool -LX "$binary" | grep --color=never @rpath/libswift | sed -E s/@rpath\\/\(.+dylib\).*/\\1/g | uniq -u) 85 | for lib in $swift_runtime_libs; do 86 | echo "rsync -auv \"${SWIFT_STDLIB_PATH}/${lib}\" \"${destination}\"" 87 | rsync -auv "${SWIFT_STDLIB_PATH}/${lib}" "${destination}" 88 | code_sign_if_enabled "${destination}/${lib}" 89 | done 90 | fi 91 | } 92 | # Copies and strips a vendored dSYM 93 | install_dsym() { 94 | local source="$1" 95 | warn_missing_arch=${2:-true} 96 | if [ -r "$source" ]; then 97 | # Copy the dSYM into the targets temp dir. 98 | echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${source}\" \"${DERIVED_FILES_DIR}\"" 99 | rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${source}" "${DERIVED_FILES_DIR}" 100 | 101 | local basename 102 | basename="$(basename -s .dSYM "$source")" 103 | binary_name="$(ls "$source/Contents/Resources/DWARF")" 104 | binary="${DERIVED_FILES_DIR}/${basename}.dSYM/Contents/Resources/DWARF/${binary_name}" 105 | 106 | # Strip invalid architectures from the dSYM. 107 | if [[ "$(file "$binary")" == *"Mach-O "*"dSYM companion"* ]]; then 108 | strip_invalid_archs "$binary" "$warn_missing_arch" 109 | fi 110 | if [[ $STRIP_BINARY_RETVAL == 0 ]]; then 111 | # Move the stripped file into its final destination. 112 | echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --links --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${DERIVED_FILES_DIR}/${basename}.framework.dSYM\" \"${DWARF_DSYM_FOLDER_PATH}\"" 113 | rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --links --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${DERIVED_FILES_DIR}/${basename}.dSYM" "${DWARF_DSYM_FOLDER_PATH}" 114 | else 115 | # The dSYM was not stripped at all, in this case touch a fake folder so the input/output paths from Xcode do not reexecute this script because the file is missing. 116 | touch "${DWARF_DSYM_FOLDER_PATH}/${basename}.dSYM" 117 | fi 118 | fi 119 | } 120 | 121 | # Used as a return value for each invocation of `strip_invalid_archs` function. 122 | STRIP_BINARY_RETVAL=0 123 | 124 | # Strip invalid architectures 125 | strip_invalid_archs() { 126 | binary="$1" 127 | warn_missing_arch=${2:-true} 128 | # Get architectures for current target binary 129 | binary_archs="$(lipo -info "$binary" | rev | cut -d ':' -f1 | awk '{$1=$1;print}' | rev)" 130 | # Intersect them with the architectures we are building for 131 | intersected_archs="$(echo ${ARCHS[@]} ${binary_archs[@]} | tr ' ' '\n' | sort | uniq -d)" 132 | # If there are no archs supported by this binary then warn the user 133 | if [[ -z "$intersected_archs" ]]; then 134 | if [[ "$warn_missing_arch" == "true" ]]; then 135 | echo "warning: [CP] Vendored binary '$binary' contains architectures ($binary_archs) none of which match the current build architectures ($ARCHS)." 136 | fi 137 | STRIP_BINARY_RETVAL=1 138 | return 139 | fi 140 | stripped="" 141 | for arch in $binary_archs; do 142 | if ! [[ "${ARCHS}" == *"$arch"* ]]; then 143 | # Strip non-valid architectures in-place 144 | lipo -remove "$arch" -output "$binary" "$binary" 145 | stripped="$stripped $arch" 146 | fi 147 | done 148 | if [[ "$stripped" ]]; then 149 | echo "Stripped $binary of architectures:$stripped" 150 | fi 151 | STRIP_BINARY_RETVAL=0 152 | } 153 | 154 | # Copies the bcsymbolmap files of a vendored framework 155 | install_bcsymbolmap() { 156 | local bcsymbolmap_path="$1" 157 | local destination="${BUILT_PRODUCTS_DIR}" 158 | echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${bcsymbolmap_path}" "${destination}"" 159 | rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${bcsymbolmap_path}" "${destination}" 160 | } 161 | 162 | # Signs a framework with the provided identity 163 | code_sign_if_enabled() { 164 | if [ -n "${EXPANDED_CODE_SIGN_IDENTITY:-}" -a "${CODE_SIGNING_REQUIRED:-}" != "NO" -a "${CODE_SIGNING_ALLOWED}" != "NO" ]; then 165 | # Use the current code_sign_identity 166 | echo "Code Signing $1 with Identity ${EXPANDED_CODE_SIGN_IDENTITY_NAME}" 167 | local code_sign_cmd="/usr/bin/codesign --force --sign ${EXPANDED_CODE_SIGN_IDENTITY} ${OTHER_CODE_SIGN_FLAGS:-} --preserve-metadata=identifier,entitlements '$1'" 168 | 169 | if [ "${COCOAPODS_PARALLEL_CODE_SIGN}" == "true" ]; then 170 | code_sign_cmd="$code_sign_cmd &" 171 | fi 172 | echo "$code_sign_cmd" 173 | eval "$code_sign_cmd" 174 | fi 175 | } 176 | 177 | if [[ "$CONFIGURATION" == "Debug" ]]; then 178 | install_framework "${BUILT_PRODUCTS_DIR}/CropPickerView/CropPickerView.framework" 179 | install_framework "${BUILT_PRODUCTS_DIR}/VideoConverter/VideoConverter.framework" 180 | install_framework "${BUILT_PRODUCTS_DIR}/VideoTrim/VideoTrim.framework" 181 | fi 182 | if [[ "$CONFIGURATION" == "Release" ]]; then 183 | install_framework "${BUILT_PRODUCTS_DIR}/CropPickerView/CropPickerView.framework" 184 | install_framework "${BUILT_PRODUCTS_DIR}/VideoConverter/VideoConverter.framework" 185 | install_framework "${BUILT_PRODUCTS_DIR}/VideoTrim/VideoTrim.framework" 186 | fi 187 | if [ "${COCOAPODS_PARALLEL_CODE_SIGN}" == "true" ]; then 188 | wait 189 | fi 190 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-VideoConverter_Example/Pods-VideoConverter_Example-umbrella.h: -------------------------------------------------------------------------------- 1 | #ifdef __OBJC__ 2 | #import 3 | #else 4 | #ifndef FOUNDATION_EXPORT 5 | #if defined(__cplusplus) 6 | #define FOUNDATION_EXPORT extern "C" 7 | #else 8 | #define FOUNDATION_EXPORT extern 9 | #endif 10 | #endif 11 | #endif 12 | 13 | 14 | FOUNDATION_EXPORT double Pods_VideoConverter_ExampleVersionNumber; 15 | FOUNDATION_EXPORT const unsigned char Pods_VideoConverter_ExampleVersionString[]; 16 | 17 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-VideoConverter_Example/Pods-VideoConverter_Example.debug.xcconfig: -------------------------------------------------------------------------------- 1 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES 2 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO 3 | FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/CropPickerView" "${PODS_CONFIGURATION_BUILD_DIR}/VideoConverter" "${PODS_CONFIGURATION_BUILD_DIR}/VideoTrim" 4 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 5 | HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/CropPickerView/CropPickerView.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/VideoConverter/VideoConverter.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/VideoTrim/VideoTrim.framework/Headers" 6 | LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks' 7 | OTHER_LDFLAGS = $(inherited) -framework "CropPickerView" -framework "VideoConverter" -framework "VideoTrim" 8 | OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS 9 | PODS_BUILD_DIR = ${BUILD_DIR} 10 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 11 | PODS_PODFILE_DIR_PATH = ${SRCROOT}/. 12 | PODS_ROOT = ${SRCROOT}/Pods 13 | PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates 14 | USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES 15 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-VideoConverter_Example/Pods-VideoConverter_Example.modulemap: -------------------------------------------------------------------------------- 1 | framework module Pods_VideoConverter_Example { 2 | umbrella header "Pods-VideoConverter_Example-umbrella.h" 3 | 4 | export * 5 | module * { export * } 6 | } 7 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-VideoConverter_Example/Pods-VideoConverter_Example.release.xcconfig: -------------------------------------------------------------------------------- 1 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES 2 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO 3 | FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/CropPickerView" "${PODS_CONFIGURATION_BUILD_DIR}/VideoConverter" "${PODS_CONFIGURATION_BUILD_DIR}/VideoTrim" 4 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 5 | HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/CropPickerView/CropPickerView.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/VideoConverter/VideoConverter.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/VideoTrim/VideoTrim.framework/Headers" 6 | LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks' 7 | OTHER_LDFLAGS = $(inherited) -framework "CropPickerView" -framework "VideoConverter" -framework "VideoTrim" 8 | OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS 9 | PODS_BUILD_DIR = ${BUILD_DIR} 10 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 11 | PODS_PODFILE_DIR_PATH = ${SRCROOT}/. 12 | PODS_ROOT = ${SRCROOT}/Pods 13 | PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates 14 | USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES 15 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-VideoConverter_Tests/Pods-VideoConverter_Tests-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | ${PRODUCT_BUNDLE_IDENTIFIER} 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | ${PRODUCT_NAME} 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | ${CURRENT_PROJECT_VERSION} 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-VideoConverter_Tests/Pods-VideoConverter_Tests-acknowledgements.markdown: -------------------------------------------------------------------------------- 1 | # Acknowledgements 2 | This application makes use of the following third party libraries: 3 | Generated by CocoaPods - https://cocoapods.org 4 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-VideoConverter_Tests/Pods-VideoConverter_Tests-acknowledgements.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreferenceSpecifiers 6 | 7 | 8 | FooterText 9 | This application makes use of the following third party libraries: 10 | Title 11 | Acknowledgements 12 | Type 13 | PSGroupSpecifier 14 | 15 | 16 | FooterText 17 | Generated by CocoaPods - https://cocoapods.org 18 | Title 19 | 20 | Type 21 | PSGroupSpecifier 22 | 23 | 24 | StringsTable 25 | Acknowledgements 26 | Title 27 | Acknowledgements 28 | 29 | 30 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-VideoConverter_Tests/Pods-VideoConverter_Tests-dummy.m: -------------------------------------------------------------------------------- 1 | #import 2 | @interface PodsDummy_Pods_VideoConverter_Tests : NSObject 3 | @end 4 | @implementation PodsDummy_Pods_VideoConverter_Tests 5 | @end 6 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-VideoConverter_Tests/Pods-VideoConverter_Tests-umbrella.h: -------------------------------------------------------------------------------- 1 | #ifdef __OBJC__ 2 | #import 3 | #else 4 | #ifndef FOUNDATION_EXPORT 5 | #if defined(__cplusplus) 6 | #define FOUNDATION_EXPORT extern "C" 7 | #else 8 | #define FOUNDATION_EXPORT extern 9 | #endif 10 | #endif 11 | #endif 12 | 13 | 14 | FOUNDATION_EXPORT double Pods_VideoConverter_TestsVersionNumber; 15 | FOUNDATION_EXPORT const unsigned char Pods_VideoConverter_TestsVersionString[]; 16 | 17 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-VideoConverter_Tests/Pods-VideoConverter_Tests.debug.xcconfig: -------------------------------------------------------------------------------- 1 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO 2 | FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/CropPickerView" "${PODS_CONFIGURATION_BUILD_DIR}/VideoConverter" "${PODS_CONFIGURATION_BUILD_DIR}/VideoTrim" 3 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 4 | HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/CropPickerView/CropPickerView.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/VideoConverter/VideoConverter.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/VideoTrim/VideoTrim.framework/Headers" 5 | OTHER_LDFLAGS = $(inherited) -framework "CropPickerView" -framework "VideoConverter" -framework "VideoTrim" 6 | PODS_BUILD_DIR = ${BUILD_DIR} 7 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 8 | PODS_PODFILE_DIR_PATH = ${SRCROOT}/. 9 | PODS_ROOT = ${SRCROOT}/Pods 10 | PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates 11 | USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES 12 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-VideoConverter_Tests/Pods-VideoConverter_Tests.modulemap: -------------------------------------------------------------------------------- 1 | framework module Pods_VideoConverter_Tests { 2 | umbrella header "Pods-VideoConverter_Tests-umbrella.h" 3 | 4 | export * 5 | module * { export * } 6 | } 7 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-VideoConverter_Tests/Pods-VideoConverter_Tests.release.xcconfig: -------------------------------------------------------------------------------- 1 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO 2 | FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/CropPickerView" "${PODS_CONFIGURATION_BUILD_DIR}/VideoConverter" "${PODS_CONFIGURATION_BUILD_DIR}/VideoTrim" 3 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 4 | HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/CropPickerView/CropPickerView.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/VideoConverter/VideoConverter.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/VideoTrim/VideoTrim.framework/Headers" 5 | OTHER_LDFLAGS = $(inherited) -framework "CropPickerView" -framework "VideoConverter" -framework "VideoTrim" 6 | PODS_BUILD_DIR = ${BUILD_DIR} 7 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 8 | PODS_PODFILE_DIR_PATH = ${SRCROOT}/. 9 | PODS_ROOT = ${SRCROOT}/Pods 10 | PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates 11 | USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES 12 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/VideoConverter/VideoConverter-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | ${PRODUCT_BUNDLE_IDENTIFIER} 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | ${PRODUCT_NAME} 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | $(MARKETING_VERSION) 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | ${CURRENT_PROJECT_VERSION} 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/VideoConverter/VideoConverter-dummy.m: -------------------------------------------------------------------------------- 1 | #import 2 | @interface PodsDummy_VideoConverter : NSObject 3 | @end 4 | @implementation PodsDummy_VideoConverter 5 | @end 6 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/VideoConverter/VideoConverter-prefix.pch: -------------------------------------------------------------------------------- 1 | #ifdef __OBJC__ 2 | #import 3 | #else 4 | #ifndef FOUNDATION_EXPORT 5 | #if defined(__cplusplus) 6 | #define FOUNDATION_EXPORT extern "C" 7 | #else 8 | #define FOUNDATION_EXPORT extern 9 | #endif 10 | #endif 11 | #endif 12 | 13 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/VideoConverter/VideoConverter-umbrella.h: -------------------------------------------------------------------------------- 1 | #ifdef __OBJC__ 2 | #import 3 | #else 4 | #ifndef FOUNDATION_EXPORT 5 | #if defined(__cplusplus) 6 | #define FOUNDATION_EXPORT extern "C" 7 | #else 8 | #define FOUNDATION_EXPORT extern 9 | #endif 10 | #endif 11 | #endif 12 | 13 | 14 | FOUNDATION_EXPORT double VideoConverterVersionNumber; 15 | FOUNDATION_EXPORT const unsigned char VideoConverterVersionString[]; 16 | 17 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/VideoConverter/VideoConverter.debug.xcconfig: -------------------------------------------------------------------------------- 1 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO 2 | CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/VideoConverter 3 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 4 | OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS 5 | PODS_BUILD_DIR = ${BUILD_DIR} 6 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 7 | PODS_ROOT = ${SRCROOT} 8 | PODS_TARGET_SRCROOT = ${PODS_ROOT}/../.. 9 | PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates 10 | PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} 11 | SKIP_INSTALL = YES 12 | USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES 13 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/VideoConverter/VideoConverter.modulemap: -------------------------------------------------------------------------------- 1 | framework module VideoConverter { 2 | umbrella header "VideoConverter-umbrella.h" 3 | 4 | export * 5 | module * { export * } 6 | } 7 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/VideoConverter/VideoConverter.release.xcconfig: -------------------------------------------------------------------------------- 1 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO 2 | CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/VideoConverter 3 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 4 | OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS 5 | PODS_BUILD_DIR = ${BUILD_DIR} 6 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 7 | PODS_ROOT = ${SRCROOT} 8 | PODS_TARGET_SRCROOT = ${PODS_ROOT}/../.. 9 | PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates 10 | PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} 11 | SKIP_INSTALL = YES 12 | USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES 13 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/VideoTrim/VideoTrim-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | ${PRODUCT_BUNDLE_IDENTIFIER} 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | ${PRODUCT_NAME} 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | $(MARKETING_VERSION) 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | ${CURRENT_PROJECT_VERSION} 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/VideoTrim/VideoTrim-dummy.m: -------------------------------------------------------------------------------- 1 | #import 2 | @interface PodsDummy_VideoTrim : NSObject 3 | @end 4 | @implementation PodsDummy_VideoTrim 5 | @end 6 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/VideoTrim/VideoTrim-prefix.pch: -------------------------------------------------------------------------------- 1 | #ifdef __OBJC__ 2 | #import 3 | #else 4 | #ifndef FOUNDATION_EXPORT 5 | #if defined(__cplusplus) 6 | #define FOUNDATION_EXPORT extern "C" 7 | #else 8 | #define FOUNDATION_EXPORT extern 9 | #endif 10 | #endif 11 | #endif 12 | 13 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/VideoTrim/VideoTrim-umbrella.h: -------------------------------------------------------------------------------- 1 | #ifdef __OBJC__ 2 | #import 3 | #else 4 | #ifndef FOUNDATION_EXPORT 5 | #if defined(__cplusplus) 6 | #define FOUNDATION_EXPORT extern "C" 7 | #else 8 | #define FOUNDATION_EXPORT extern 9 | #endif 10 | #endif 11 | #endif 12 | 13 | 14 | FOUNDATION_EXPORT double VideoTrimVersionNumber; 15 | FOUNDATION_EXPORT const unsigned char VideoTrimVersionString[]; 16 | 17 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/VideoTrim/VideoTrim.debug.xcconfig: -------------------------------------------------------------------------------- 1 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO 2 | CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/VideoTrim 3 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 4 | OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS 5 | PODS_BUILD_DIR = ${BUILD_DIR} 6 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 7 | PODS_ROOT = ${SRCROOT} 8 | PODS_TARGET_SRCROOT = ${PODS_ROOT}/VideoTrim 9 | PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates 10 | PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} 11 | SKIP_INSTALL = YES 12 | USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES 13 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/VideoTrim/VideoTrim.modulemap: -------------------------------------------------------------------------------- 1 | framework module VideoTrim { 2 | umbrella header "VideoTrim-umbrella.h" 3 | 4 | export * 5 | module * { export * } 6 | } 7 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/VideoTrim/VideoTrim.release.xcconfig: -------------------------------------------------------------------------------- 1 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO 2 | CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/VideoTrim 3 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 4 | OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS 5 | PODS_BUILD_DIR = ${BUILD_DIR} 6 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 7 | PODS_ROOT = ${SRCROOT} 8 | PODS_TARGET_SRCROOT = ${PODS_ROOT}/VideoTrim 9 | PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates 10 | PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} 11 | SKIP_INSTALL = YES 12 | USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES 13 | -------------------------------------------------------------------------------- /Example/Pods/VideoTrim/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Gwan-ho Kim 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Example/Pods/VideoTrim/README.md: -------------------------------------------------------------------------------- 1 | # VideoTrim 2 | 3 | 4 | [![Version](https://img.shields.io/cocoapods/v/VideoTrim.svg?style=flat)](https://cocoapods.org/pods/VideoTrim) 5 | [![License](https://img.shields.io/cocoapods/l/VideoTrim.svg?style=flat)](https://cocoapods.org/pods/VideoTrim) 6 | [![Platform](https://img.shields.io/cocoapods/p/VideoTrim.svg?style=flat)](https://cocoapods.org/pods/VideoTrim) 7 | [![Swift 5.0](https://img.shields.io/badge/Swift-5.0-orange.svg?style=flat)](https://developer.apple.com/swift/) 8 | 9 | ## Introduce 10 | 11 | You can extract an image for each video frame as a video asset and set the start time and end time. 12 | 13 |
14 | 15 | ### VideoTrim 16 | 17 | 18 | 19 | |-|-| 20 | |---|---| 21 | ||| 22 | 23 | ## Requirements 24 | 25 | `VideoTrim` written in Swift 5.0. Compatible with iOS 8.0+ 26 | 27 | ## Installation 28 | 29 | VideoTrim is available through [CocoaPods](https://cocoapods.org). To install 30 | it, simply add the following line to your Podfile: 31 | 32 | ```ruby 33 | pod 'VideoTrim' 34 | ``` 35 | 36 | ## Usage 37 | 38 | ```swift 39 | import VideoTrim 40 | ``` 41 | 42 | ```swift 43 | let videoTrim = VideoTrim() 44 | self.view.addSubview(videoTrim) 45 | ``` 46 | 47 | done! 48 | 49 |


50 | 51 | 52 | 53 | ### Property 54 | 55 | Set Property 56 | 57 | asset 58 | 59 | ```swift 60 | 61 | videoTrim.asset = AVAsset(url: url) // After setting the asset, the image frame is extracted. 62 | 63 | videoTrim.currentTime = CMTime(value: 0, timescale: 0) // The frame bar position changes with currentTime. 64 | 65 | ``` 66 | 67 | ```swift 68 | 69 | videoTrim.frameImageCount = 20 // Number of frame images 70 | videoTrim.trimReaminWidth = 50 // Video trim minimum length 71 | 72 | videoTrim.topMargin = 4 // The top margin of the screen. 73 | videoTrim.bottomMargin = 8 // The bottom margin of the screen. 74 | videoTrim.leadingMargin = 0 // The leading margin of the screen. 75 | videoTrim.trailingMargin = 0 // The trailing margin of the screen. 76 | 77 | videoTrim.frameHeight = 48 // The image frame height. 78 | 79 | videoTrim.trimMaskDimViewColor = UIColor(white: 0/255, alpha: 0.7) // The color of the screen overlay outside the start time and end time. 80 | videoTrim.trimLineRadius = 4 // The radius of the trim line. 81 | videoTrim.trimLineWidth = 4 // Border width of the trim line. 82 | videoTrim.trimLineViewColor = UIColor.white.cgColor // This is the trim line color. 83 | 84 | videoTrim.playLineRadius = 3 // The radius of the play line. 85 | videoTrim.playLineWidth = 6 // This is the border width of the play line. 86 | videoTrim.playLineVerticalSize = 4 // The difference between the height of the play line and the top and bottom of the image frame. 87 | videoTrim.playTimeLineViewColor = UIColor.white // This is the play time line color. 88 | 89 | videoTrim.timeColor = UIColor.white // time text color. 90 | videoTrim.timeFont = UIFont.systemFont(ofSize: 15) // time text font. 91 | 92 | ``` 93 | 94 | Get Property 95 | 96 | ```swift 97 | 98 | videoTrim.playTime // CMTime of PlayTime. 99 | videoTrim.startTime // CMTime of start time. 100 | videoTrim.endTime // CMTime of end time. 101 | videoTrim.durationTime // CMTime from start time to end time. 102 | 103 | ``` 104 | 105 |

106 | 107 | ### Delegate 108 | 109 | ```swift 110 | 111 | class ViewController: UIViewController{ 112 | override func viewDidLoad() { 113 | super.viewDidLoad() 114 | 115 | let videoTrim = VideoTrim() 116 | videoTrim.delegate = self 117 | } 118 | } 119 | 120 | // MARK: VideoTrimDelegate 121 | extension ViewController: VideoTrimDelegate { 122 | func videoTrimStartTrimChange(_ videoTrim: VideoTrim) { // It is called when you touch the start time, end time, and play time. 123 | 124 | } 125 | 126 | func videoTrimEndTrimChange(_ videoTrim: VideoTrim) { // Called at the end of touch start time, end time and play time. 127 | 128 | } 129 | 130 | func videoTrimPlayTimeChange(_ videoTrim: VideoTrim) { // Called when touching the start time, end time and play time. 131 | 132 | } 133 | } 134 | 135 | ``` 136 | 137 | 138 | ## Author 139 | 140 | pikachu987, pikachu77769@gmail.com 141 | 142 | ## License 143 | 144 | VideoTrim is available under the MIT license. See the LICENSE file for more info. 145 | -------------------------------------------------------------------------------- /Example/Pods/VideoTrim/VideoTrim/Classes/Int+Time.swift: -------------------------------------------------------------------------------- 1 | //MIT License 2 | // 3 | //Copyright (c) 2020 Gwan-ho Kim 4 | // 5 | //Permission is hereby granted, free of charge, to any person obtaining a copy 6 | //of this software and associated documentation files (the "Software"), to deal 7 | //in the Software without restriction, including without limitation the rights 8 | //to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | //copies of the Software, and to permit persons to whom the Software is 10 | //furnished to do so, subject to the following conditions: 11 | // 12 | //The above copyright notice and this permission notice shall be included in all 13 | //copies or substantial portions of the Software. 14 | // 15 | //THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | //IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | //FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | //AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | //LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | //OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | //SOFTWARE. 22 | 23 | import Foundation 24 | 25 | // MARK: Int + time 26 | extension Int { 27 | var time: String { 28 | let second = self 29 | let min = second / 60 30 | let sec = second % 60 31 | let secValue = sec < 10 ? "0\(sec)" : "\(sec)" 32 | return "\(min):\(secValue)" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Example/Pods/VideoTrim/VideoTrim/Classes/VisualEffectImageView.swift: -------------------------------------------------------------------------------- 1 | //MIT License 2 | // 3 | //Copyright (c) 2020 Gwan-ho Kim 4 | // 5 | //Permission is hereby granted, free of charge, to any person obtaining a copy 6 | //of this software and associated documentation files (the "Software"), to deal 7 | //in the Software without restriction, including without limitation the rights 8 | //to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | //copies of the Software, and to permit persons to whom the Software is 10 | //furnished to do so, subject to the following conditions: 11 | // 12 | //The above copyright notice and this permission notice shall be included in all 13 | //copies or substantial portions of the Software. 14 | // 15 | //THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | //IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | //FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | //AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | //LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | //OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | //SOFTWARE. 22 | 23 | import UIKit 24 | 25 | // MARK: VisualEffectImageView 26 | class VisualEffectImageView: UIImageView { 27 | private let frameVisualEffectView: UIVisualEffectView = { 28 | let visualEffect = UIBlurEffect(style: .light) 29 | let view = UIVisualEffectView(effect: visualEffect) 30 | view.translatesAutoresizingMaskIntoConstraints = false 31 | view.isUserInteractionEnabled = true 32 | return view 33 | }() 34 | 35 | override init(frame: CGRect) { 36 | super.init(frame: frame) 37 | 38 | self.addSubview(self.frameVisualEffectView) 39 | self.addConstraints([ 40 | NSLayoutConstraint(item: self, attribute: .leading, relatedBy: .equal, toItem: self.frameVisualEffectView, attribute: .leading, multiplier: 1, constant: 0), 41 | NSLayoutConstraint(item: self, attribute: .trailing, relatedBy: .equal, toItem: self.frameVisualEffectView, attribute: .trailing, multiplier: 1, constant: 0), 42 | NSLayoutConstraint(item: self, attribute: .top, relatedBy: .equal, toItem: self.frameVisualEffectView, attribute: .top, multiplier: 1, constant: 0), 43 | NSLayoutConstraint(item: self, attribute: .bottom, relatedBy: .equal, toItem: self.frameVisualEffectView, attribute: .bottom, multiplier: 1, constant: 0) 44 | ]) 45 | } 46 | 47 | required init?(coder: NSCoder) { 48 | fatalError("init(coder:) has not been implemented") 49 | } 50 | 51 | func showVisualEffect() { 52 | self.frameVisualEffectView.frame = self.bounds 53 | self.frameVisualEffectView.alpha = 0.6 54 | self.frameVisualEffectView.isHidden = false 55 | } 56 | 57 | func hideVisualEffect() { 58 | UIView.animate(withDuration: 0.2, animations: { 59 | self.frameVisualEffectView.alpha = 0 60 | }, completion: { (_) in 61 | self.frameVisualEffectView.isHidden = true 62 | }) 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /Example/Tests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /Example/Tests/Tests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | import VideoConverter 3 | 4 | class Tests: XCTestCase { 5 | 6 | override func setUp() { 7 | super.setUp() 8 | // Put setup code here. This method is called before the invocation of each test method in the class. 9 | } 10 | 11 | override func tearDown() { 12 | // Put teardown code here. This method is called after the invocation of each test method in the class. 13 | super.tearDown() 14 | } 15 | 16 | func testExample() { 17 | // This is an example of a functional test case. 18 | XCTAssert(true, "Pass") 19 | } 20 | 21 | func testPerformanceExample() { 22 | // This is an example of a performance test case. 23 | self.measure() { 24 | // Put the code you want to measure the time of here. 25 | } 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /Example/VideoConverter.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Example/VideoConverter.xcodeproj/xcshareddata/xcschemes/VideoConverter-Example.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 38 | 39 | 44 | 45 | 51 | 52 | 53 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 76 | 78 | 84 | 85 | 86 | 87 | 93 | 95 | 101 | 102 | 103 | 104 | 106 | 107 | 110 | 111 | 112 | -------------------------------------------------------------------------------- /Example/VideoConverter.xcodeproj/xcuserdata/corpdocfriends.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | VideoConverter-Example.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /Example/VideoConverter.xcodeproj/xcuserdata/gwanhokim.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | VideoConverter-Example.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /Example/VideoConverter.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Example/VideoConverter.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Example/VideoConverter.xcworkspace/xcuserdata/apple.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pikachu987/VideoConverter/0911dde2c3151e88b6f34e43c668891b28182699/Example/VideoConverter.xcworkspace/xcuserdata/apple.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /Example/VideoConverter.xcworkspace/xcuserdata/apple.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | -------------------------------------------------------------------------------- /Example/VideoConverter.xcworkspace/xcuserdata/corpdocfriends.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pikachu987/VideoConverter/0911dde2c3151e88b6f34e43c668891b28182699/Example/VideoConverter.xcworkspace/xcuserdata/corpdocfriends.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /Example/VideoConverter.xcworkspace/xcuserdata/gwanhokim.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pikachu987/VideoConverter/0911dde2c3151e88b6f34e43c668891b28182699/Example/VideoConverter.xcworkspace/xcuserdata/gwanhokim.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /Example/VideoConverter/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // VideoConverter 4 | // 5 | // Created by pikachu987 on 09/13/2020. 6 | // Copyright (c) 2020 pikachu987. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | 17 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 18 | // Override point for customization after application launch. 19 | return true 20 | } 21 | 22 | func applicationWillResignActive(_ application: UIApplication) { 23 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 24 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. 25 | } 26 | 27 | func applicationDidEnterBackground(_ application: UIApplication) { 28 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 29 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 30 | } 31 | 32 | func applicationWillEnterForeground(_ application: UIApplication) { 33 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. 34 | } 35 | 36 | func applicationDidBecomeActive(_ application: UIApplication) { 37 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 38 | } 39 | 40 | func applicationWillTerminate(_ application: UIApplication) { 41 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 42 | } 43 | 44 | 45 | } 46 | 47 | -------------------------------------------------------------------------------- /Example/VideoConverter/Base.lproj/LaunchScreen.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 25 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /Example/VideoConverter/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 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /Example/VideoConverter/CropViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CropViewController.swift 3 | // VideoConverter_Example 4 | // 5 | // Created by Apple on 2020/09/13. 6 | // Copyright © 2020 CocoaPods. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import CropPickerView 11 | 12 | protocol CropDelegate: class { 13 | func cropImage(_ imageSize: CGSize, cropFrame: CGRect) 14 | } 15 | 16 | class CropViewController: UIViewController { 17 | weak var delegate: CropDelegate? 18 | 19 | private var cropView: CropPickerView = { 20 | let view = CropPickerView() 21 | view.translatesAutoresizingMaskIntoConstraints = false 22 | view.cropLineColor = .white 23 | view.dimBackgroundColor = UIColor(white: 0, alpha: 0.6) 24 | return view 25 | }() 26 | 27 | private let image: UIImage 28 | 29 | init(image: UIImage) { 30 | self.image = image 31 | super.init(nibName: nil, bundle: nil) 32 | } 33 | 34 | required init?(coder: NSCoder) { 35 | fatalError("init(coder:) has not been implemented") 36 | } 37 | 38 | override func viewDidLoad() { 39 | super.viewDidLoad() 40 | 41 | self.view.clipsToBounds = true 42 | self.view.backgroundColor = .black 43 | self.view.addSubview(self.cropView) 44 | 45 | var bottomConstant: CGFloat = 0 46 | if #available(iOS 11.0, *) { 47 | bottomConstant = UIApplication.shared.keyWindow?.safeAreaInsets.bottom ?? 0 48 | } 49 | self.view.addConstraints([ 50 | NSLayoutConstraint(item: self.cropView, attribute: .top, relatedBy: .equal, toItem: self.view, attribute: .top, multiplier: 1, constant: 10), 51 | NSLayoutConstraint(item: self.cropView, attribute: .bottom, relatedBy: .equal, toItem: self.view, attribute: .bottom, multiplier: 1, constant: -(bottomConstant + 10)), 52 | NSLayoutConstraint(item: self.cropView, attribute: .leading, relatedBy: .equal, toItem: self.view, attribute: .leading, multiplier: 1, constant: 10), 53 | NSLayoutConstraint(item: self.cropView, attribute: .trailing, relatedBy: .equal, toItem: self.view, attribute: .trailing, multiplier: 1, constant: -10) 54 | ]) 55 | 56 | DispatchQueue.main.async { 57 | self.cropView.image(self.image, isMin: true) 58 | } 59 | } 60 | 61 | override func viewWillAppear(_ animated: Bool) { 62 | super.viewWillAppear(animated) 63 | 64 | self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: "Close", style: .plain, target: self, action: #selector(self.closeTab(_:))) 65 | self.navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Crop", style: .plain, target: self, action: #selector(self.cropTab(_:))) 66 | 67 | self.navigationController?.navigationBar.isTranslucent = false 68 | self.navigationController?.navigationBar.barTintColor = .black 69 | self.navigationController?.navigationBar.backgroundColor = .black 70 | self.navigationController?.navigationBar.setBackgroundImage(UIImage(), for: .default) 71 | self.navigationController?.navigationBar.shadowImage = UIImage() 72 | } 73 | 74 | @objc private func closeTab(_ sender: UIBarButtonItem) { 75 | self.dismiss(animated: true, completion: nil) 76 | } 77 | 78 | @objc private func cropTab(_ sender: UIButton) { 79 | self.cropView.crop { [weak self] (crop) in 80 | guard let self = self else { return } 81 | if let error = crop.error { 82 | DispatchQueue.main.async { 83 | let alertController = UIAlertController(title: "Error", message: error.localizedDescription, preferredStyle: .alert) 84 | alertController.addAction(UIAlertAction(title: "Confirm", style: .default, handler: nil)) 85 | self.present(alertController, animated: true, completion: nil) 86 | } 87 | return 88 | } 89 | if let cropFrame = crop.cropFrame, let imageSize = crop.imageSize { 90 | self.dismiss(animated: true) { 91 | self.delegate?.cropImage(imageSize, cropFrame: cropFrame) 92 | } 93 | } 94 | } 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /Example/VideoConverter/Images.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" : "ios-marketing", 45 | "size" : "1024x1024", 46 | "scale" : "1x" 47 | } 48 | ], 49 | "info" : { 50 | "version" : 1, 51 | "author" : "xcode" 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /Example/VideoConverter/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | NSAppTransportSecurity 26 | 27 | NSAllowsArbitraryLoads 28 | 29 | 30 | NSCameraUsageDescription 31 | Video Converter 32 | NSPhotoLibraryUsageDescription 33 | Video Converter 34 | UILaunchStoryboardName 35 | LaunchScreen 36 | UIMainStoryboardFile 37 | Main 38 | UIRequiredDeviceCapabilities 39 | 40 | armv7 41 | 42 | UISupportedInterfaceOrientations 43 | 44 | UIInterfaceOrientationPortrait 45 | UIInterfaceOrientationLandscapeLeft 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /Example/VideoConverter/UIImage+Rotate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIImage+Rotate.swift 3 | // VideoConverter_Example 4 | // 5 | // Created by Apple on 2020/09/13. 6 | // Copyright © 2020 CocoaPods. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | extension UIImage { 12 | func rotate(radians: Float) -> UIImage? { 13 | var newSize = CGRect(origin: .zero, size: self.size).applying(CGAffineTransform(rotationAngle: CGFloat(radians))).size 14 | newSize.width = floor(newSize.width) 15 | newSize.height = floor(newSize.height) 16 | UIGraphicsBeginImageContextWithOptions(newSize, false, self.scale) 17 | let context = UIGraphicsGetCurrentContext() 18 | context?.translateBy(x: newSize.width/2, y: newSize.height/2) 19 | context?.rotate(by: CGFloat(radians)) 20 | self.draw(in: CGRect(x: -self.size.width/2, y: -self.size.height/2, width: self.size.width, height: self.size.height)) 21 | let newImage = UIGraphicsGetImageFromCurrentImageContext() 22 | UIGraphicsEndImageContext() 23 | return newImage 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Example/VideoConverter/VideoView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // VideoView.swift 3 | // VideoConverter_Example 4 | // 5 | // Created by Apple on 2020/09/13. 6 | // Copyright © 2020 CocoaPods. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import AVKit 11 | 12 | protocol VideoDelegate: class { 13 | func videoPlaying() 14 | } 15 | 16 | class VideoView: UIView { 17 | private let viewType: VideoViewType 18 | 19 | weak var delegate: VideoDelegate? 20 | 21 | let containerView: UIView = { 22 | let view = UIView() 23 | view.translatesAutoresizingMaskIntoConstraints = false 24 | view.backgroundColor = .clear 25 | return view 26 | }() 27 | 28 | private let playerContainerView: UIView = { 29 | let view = UIView() 30 | view.translatesAutoresizingMaskIntoConstraints = false 31 | view.backgroundColor = .clear 32 | return view 33 | }() 34 | 35 | private let dimView: DimView = { 36 | let dimView = DimView() 37 | dimView.translatesAutoresizingMaskIntoConstraints = false 38 | dimView.backgroundColor = UIColor(white: 0/255, alpha: 0.8) 39 | return dimView 40 | }() 41 | 42 | private let playerLayer: AVPlayerLayer = { 43 | return AVPlayerLayer() 44 | }() 45 | 46 | private let playButton: UIButton = { 47 | let button = UIButton(type: .system) 48 | button.translatesAutoresizingMaskIntoConstraints = false 49 | return button 50 | }() 51 | 52 | var videoRect: CGRect { 53 | if self.degree == 0 || self.degree == 180 { 54 | return self.playerLayer.videoRect 55 | } else if self.degree == 90 || self.degree == 270 { 56 | return CGRect(x: self.playerLayer.videoRect.origin.y, y: self.playerLayer.videoRect.origin.x, width: self.playerLayer.videoRect.size.height, height: self.playerLayer.videoRect.size.width) 57 | } else { 58 | return .zero 59 | } 60 | } 61 | 62 | var player: AVPlayer? { 63 | return self.playerLayer.player 64 | } 65 | 66 | private var timer: Timer? 67 | 68 | var asset: AVAsset? { 69 | didSet { 70 | if let asset = self.asset { 71 | self.playerLayer.player = AVPlayer(playerItem: AVPlayerItem(asset: asset)) 72 | self.playerLayer.frame = self.playerContainerView.bounds 73 | 74 | if self.viewType == .convert { 75 | self.startTime = .zero 76 | self.endTime = self.player?.currentItem?.asset.duration ?? .zero 77 | } 78 | } 79 | } 80 | } 81 | 82 | var url: URL? { 83 | didSet { 84 | if let url = self.url { 85 | self.playerLayer.player = AVPlayer(url: url) 86 | self.playerLayer.frame = self.playerContainerView.bounds 87 | 88 | if self.viewType == .convert { 89 | self.startTime = .zero 90 | self.endTime = self.player?.currentItem?.asset.duration ?? .zero 91 | } 92 | } 93 | } 94 | } 95 | 96 | var isMute: Bool = false { 97 | didSet { 98 | self.player?.isMuted = self.isMute 99 | } 100 | } 101 | 102 | var isPlaying: Bool { 103 | guard let player = self.player else { return false } 104 | return player.rate != 0 && player.error == nil 105 | } 106 | 107 | var degree: CGFloat = 0 { 108 | didSet { 109 | let dimFrame = self.dimFrame 110 | self.dimFrame = dimFrame 111 | } 112 | } 113 | 114 | var dimFrame: CGRect? = nil { 115 | didSet { 116 | if let dimFrame = self.dimFrame { 117 | var maskX: CGFloat = 0 118 | var maskY: CGFloat = 0 119 | var maskWidth: CGFloat = 0 120 | var maskHeight: CGFloat = 0 121 | if self.degree == 0 || self.degree == 180 { 122 | maskX = ((self.dimView.frame.width - self.playerLayer.videoRect.width) / 2) + dimFrame.origin.x 123 | maskY = ((self.dimView.frame.height - self.playerLayer.videoRect.height) / 2) + dimFrame.origin.y 124 | maskWidth = dimFrame.width 125 | maskHeight = dimFrame.height 126 | } else if self.degree == 90 || self.degree == 270 { 127 | maskX = ((self.dimView.frame.width - self.playerLayer.videoRect.height) / 2) + dimFrame.origin.x 128 | maskY = ((self.dimView.frame.height - self.playerLayer.videoRect.width) / 2) + dimFrame.origin.y 129 | maskWidth = dimFrame.width 130 | maskHeight = dimFrame.height 131 | } 132 | let rect = CGRect(x: maskX, y: maskY, width: maskWidth, height: maskHeight) 133 | let path = UIBezierPath(rect: rect) 134 | path.append(UIBezierPath(rect: self.dimView.bounds)) 135 | self.dimView.mask(path.cgPath, duration: 0, animated: false) 136 | self.dimView.isHidden = false 137 | } else { 138 | self.dimView.isHidden = true 139 | } 140 | } 141 | } 142 | 143 | var startTime: CMTime = .zero 144 | var endTime: CMTime = .zero 145 | var durationTime: CMTime = .zero 146 | 147 | init(viewType: VideoViewType) { 148 | self.viewType = viewType 149 | super.init(frame: .zero) 150 | 151 | self.backgroundColor = .black 152 | 153 | self.addSubview(self.containerView) 154 | self.addSubview(self.dimView) 155 | self.addSubview(self.playButton) 156 | self.containerView.addSubview(self.playerContainerView) 157 | 158 | let heightConstraint = NSLayoutConstraint(item: self, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .height, multiplier: 1, constant: 240) 159 | heightConstraint.priority = UILayoutPriority(rawValue: 950) 160 | self.addConstraints([ 161 | heightConstraint 162 | ]) 163 | 164 | self.addConstraints([ 165 | NSLayoutConstraint(item: self.containerView, attribute: .leading, relatedBy: .equal, toItem: self, attribute: .leading, multiplier: 1, constant: 0), 166 | NSLayoutConstraint(item: self.containerView, attribute: .trailing, relatedBy: .equal, toItem: self, attribute: .trailing, multiplier: 1, constant: 0), 167 | NSLayoutConstraint(item: self.containerView, attribute: .top, relatedBy: .equal, toItem: self, attribute: .top, multiplier: 1, constant: 0), 168 | NSLayoutConstraint(item: self.containerView, attribute: .bottom, relatedBy: .equal, toItem: self, attribute: .bottom, multiplier: 1, constant: 0) 169 | ]) 170 | 171 | self.addConstraints([ 172 | NSLayoutConstraint(item: self.dimView, attribute: .leading, relatedBy: .equal, toItem: self, attribute: .leading, multiplier: 1, constant: 0), 173 | NSLayoutConstraint(item: self.dimView, attribute: .trailing, relatedBy: .equal, toItem: self, attribute: .trailing, multiplier: 1, constant: 0), 174 | NSLayoutConstraint(item: self.dimView, attribute: .top, relatedBy: .equal, toItem: self, attribute: .top, multiplier: 1, constant: 0), 175 | NSLayoutConstraint(item: self.dimView, attribute: .bottom, relatedBy: .equal, toItem: self, attribute: .bottom, multiplier: 1, constant: 0) 176 | ]) 177 | 178 | self.addConstraints([ 179 | NSLayoutConstraint(item: self.playButton, attribute: .leading, relatedBy: .equal, toItem: self, attribute: .leading, multiplier: 1, constant: 0), 180 | NSLayoutConstraint(item: self.playButton, attribute: .trailing, relatedBy: .equal, toItem: self, attribute: .trailing, multiplier: 1, constant: 0), 181 | NSLayoutConstraint(item: self.playButton, attribute: .top, relatedBy: .equal, toItem: self, attribute: .top, multiplier: 1, constant: 0), 182 | NSLayoutConstraint(item: self.playButton, attribute: .bottom, relatedBy: .equal, toItem: self, attribute: .bottom, multiplier: 1, constant: 0) 183 | ]) 184 | 185 | self.containerView.addConstraints([ 186 | NSLayoutConstraint(item: self.playerContainerView, attribute: .leading, relatedBy: .equal, toItem: self.containerView, attribute: .leading, multiplier: 1, constant: 0), 187 | NSLayoutConstraint(item: self.playerContainerView, attribute: .trailing, relatedBy: .equal, toItem: self.containerView, attribute: .trailing, multiplier: 1, constant: 0), 188 | NSLayoutConstraint(item: self.playerContainerView, attribute: .top, relatedBy: .equal, toItem: self.containerView, attribute: .top, multiplier: 1, constant: 0), 189 | NSLayoutConstraint(item: self.playerContainerView, attribute: .bottom, relatedBy: .equal, toItem: self.containerView, attribute: .bottom, multiplier: 1, constant: 0) 190 | ]) 191 | 192 | self.playerContainerView.layoutIfNeeded() 193 | self.playerContainerView.layer.addSublayer(self.playerLayer) 194 | self.playerLayer.frame = self.playerContainerView.bounds 195 | 196 | self.playButton.addTarget(self, action: #selector(self.togglePlay(_:)), for: .touchUpInside) 197 | DispatchQueue.main.async { 198 | self.restoreCrop() 199 | } 200 | } 201 | 202 | required init?(coder: NSCoder) { 203 | fatalError("init(coder:) has not been implemented") 204 | } 205 | 206 | @objc private func togglePlay(_ sender: UIButton) { 207 | if self.isPlaying { 208 | self.pause() 209 | } else { 210 | self.play() 211 | } 212 | } 213 | 214 | @objc private func timerAction(_ sender: Timer) { 215 | self.delegate?.videoPlaying() 216 | if let player = self.player { 217 | let current = player.currentTime() 218 | let currentTime = CGFloat(current.value) / CGFloat(current.timescale) 219 | let endTime = CGFloat(self.endTime.value) / CGFloat(self.endTime.timescale) 220 | if currentTime >= endTime { 221 | sender.invalidate() 222 | self.pause() 223 | self.player?.seek(to: self.startTime, completionHandler: { (_) in 224 | self.delegate?.videoPlaying() 225 | }) 226 | } 227 | } 228 | } 229 | 230 | func play() { 231 | guard let player = self.playerLayer.player else { return } 232 | player.play() 233 | self.timer = Timer.scheduledTimer(timeInterval: 0.3, target: self, selector: #selector(self.timerAction(_:)), userInfo: nil, repeats: true) 234 | self.delegate?.videoPlaying() 235 | } 236 | 237 | func pause() { 238 | guard let player = self.playerLayer.player else { return } 239 | player.pause() 240 | self.timer?.invalidate() 241 | self.timer = nil 242 | } 243 | 244 | func invalidate() { 245 | self.timer?.invalidate() 246 | self.timer = nil 247 | guard let player = self.playerLayer.player else { return } 248 | if self.isPlaying { 249 | player.pause() 250 | } 251 | } 252 | 253 | func restoreCrop() { 254 | self.dimFrame = nil 255 | } 256 | } 257 | 258 | // MARK: VideoView + VideoViewType 259 | extension VideoView { 260 | enum VideoViewType { 261 | case `default` 262 | case convert 263 | } 264 | } 265 | 266 | // MARK: VideoView + DimView 267 | extension VideoView { 268 | class DimView: UIView { 269 | private var path: CGPath? 270 | 271 | init() { 272 | super.init(frame: .zero) 273 | self.isUserInteractionEnabled = false 274 | } 275 | 276 | required init?(coder aDecoder: NSCoder) { 277 | super.init(coder: aDecoder) 278 | } 279 | 280 | func mask(_ path: CGPath, duration: TimeInterval, animated: Bool) { 281 | self.path = path 282 | if let mask = self.layer.mask as? CAShapeLayer { 283 | mask.removeAllAnimations() 284 | if animated { 285 | let animation = CABasicAnimation(keyPath: "path") 286 | animation.delegate = self 287 | animation.fromValue = mask.path 288 | animation.toValue = path 289 | animation.byValue = path 290 | animation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeInEaseOut) 291 | animation.isRemovedOnCompletion = false 292 | animation.fillMode = .forwards 293 | animation.duration = duration 294 | mask.add(animation, forKey: "path") 295 | } else { 296 | mask.path = path 297 | } 298 | } else { 299 | let maskLayer = CAShapeLayer() 300 | maskLayer.fillRule = CAShapeLayerFillRule.evenOdd 301 | maskLayer.backgroundColor = UIColor.clear.cgColor 302 | maskLayer.path = path 303 | self.layer.mask = maskLayer 304 | } 305 | } 306 | } 307 | } 308 | 309 | // MARK: VideoView.DimView + CAAnimationDelegate 310 | extension VideoView.DimView: CAAnimationDelegate { 311 | public func animationDidStop(_ anim: CAAnimation, finished flag: Bool) { 312 | guard let path = self.path else { return } 313 | if let mask = self.layer.mask as? CAShapeLayer { 314 | mask.removeAllAnimations() 315 | mask.path = path 316 | } 317 | } 318 | } 319 | -------------------------------------------------------------------------------- /Example/VideoConverter/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // VideoConverter 4 | // 5 | // Created by pikachu987 on 09/13/2020. 6 | // Copyright (c) 2020 pikachu987. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import VideoConverter 11 | import VideoTrim 12 | import AVKit 13 | import AVFoundation 14 | import Photos 15 | 16 | class ViewController: UIViewController { 17 | private let scrollView: UIScrollView = { 18 | let scrollView = UIScrollView() 19 | scrollView.translatesAutoresizingMaskIntoConstraints = false 20 | return scrollView 21 | }() 22 | 23 | private let containerView: UIView = { 24 | let view = UIView() 25 | view.translatesAutoresizingMaskIntoConstraints = false 26 | view.backgroundColor = .black 27 | return view 28 | }() 29 | 30 | private let videoView: VideoView = { 31 | let videoView = VideoView(viewType: .default) 32 | videoView.translatesAutoresizingMaskIntoConstraints = false 33 | return videoView 34 | }() 35 | 36 | private let videoTrim: VideoTrim = { 37 | let videoTrim = VideoTrim() 38 | videoTrim.translatesAutoresizingMaskIntoConstraints = false 39 | videoTrim.topMargin = 4 40 | videoTrim.bottomMargin = 8 41 | return videoTrim 42 | }() 43 | 44 | private let convertVideoView: VideoView = { 45 | let videoView = VideoView(viewType: .convert) 46 | videoView.translatesAutoresizingMaskIntoConstraints = false 47 | return videoView 48 | }() 49 | 50 | private let toolbar: UIToolbar = { 51 | let toolbar = UIToolbar(frame: CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width, height: 35)) 52 | toolbar.translatesAutoresizingMaskIntoConstraints = false 53 | return toolbar 54 | }() 55 | 56 | private let progressView: UIProgressView = { 57 | let progressView = UIProgressView(progressViewStyle: .default) 58 | progressView.translatesAutoresizingMaskIntoConstraints = false 59 | progressView.progress = 0 60 | return progressView 61 | }() 62 | 63 | private let fileDownloadProgressView: UIProgressView = { 64 | let progressView = UIProgressView(progressViewStyle: .default) 65 | progressView.translatesAutoresizingMaskIntoConstraints = false 66 | progressView.progress = 0 67 | progressView.isHidden = true 68 | return progressView 69 | }() 70 | 71 | private var cropBarButtonItem: UIBarButtonItem? 72 | private var rotateBarButtonItem: UIBarButtonItem? 73 | private var qualityBarButtonItem: UIBarButtonItem? 74 | private var muteBarButtonItem: UIBarButtonItem? 75 | 76 | private var videoConverter: VideoConverter? 77 | 78 | private var isPlaying = false 79 | 80 | private var rotate: Double = 0 81 | 82 | private var isMute: Bool { 83 | return self.muteBarButtonItem?.title?.lowercased() == "Mute On".lowercased() 84 | } 85 | 86 | private var preset: String? 87 | 88 | override func viewDidLoad() { 89 | super.viewDidLoad() 90 | 91 | self.view.backgroundColor = .black 92 | 93 | self.view.addSubview(self.scrollView) 94 | self.view.addSubview(self.fileDownloadProgressView) 95 | self.scrollView.addSubview(self.containerView) 96 | 97 | self.containerView.addSubview(self.videoView) 98 | self.containerView.addSubview(self.videoTrim) 99 | self.containerView.addSubview(self.toolbar) 100 | self.containerView.addSubview(self.progressView) 101 | self.containerView.addSubview(self.convertVideoView) 102 | 103 | self.view.addConstraints([ 104 | NSLayoutConstraint(item: self.scrollView, attribute: .leading, relatedBy: .equal, toItem: self.view, attribute: .leading, multiplier: 1, constant: 0), 105 | NSLayoutConstraint(item: self.scrollView, attribute: .trailing, relatedBy: .equal, toItem: self.view, attribute: .trailing, multiplier: 1, constant: 0), 106 | NSLayoutConstraint(item: self.scrollView, attribute: .top, relatedBy: .equal, toItem: self.view, attribute: .top, multiplier: 1, constant: 0), 107 | NSLayoutConstraint(item: self.scrollView, attribute: .bottom, relatedBy: .equal, toItem: self.view, attribute: .bottom, multiplier: 1, constant: 0) 108 | ]) 109 | 110 | let containerViewHeightConstraint = NSLayoutConstraint(item: self.containerView, attribute: .height, relatedBy: .equal, toItem: self.scrollView, attribute: .height, multiplier: 1, constant: 0) 111 | containerViewHeightConstraint.priority = UILayoutPriority(rawValue: 1) 112 | self.scrollView.addConstraints([ 113 | NSLayoutConstraint(item: self.containerView, attribute: .leading, relatedBy: .equal, toItem: self.scrollView, attribute: .leading, multiplier: 1, constant: 0), 114 | NSLayoutConstraint(item: self.containerView, attribute: .trailing, relatedBy: .equal, toItem: self.scrollView, attribute: .trailing, multiplier: 1, constant: 0), 115 | NSLayoutConstraint(item: self.containerView, attribute: .top, relatedBy: .equal, toItem: self.scrollView, attribute: .top, multiplier: 1, constant: 0), 116 | NSLayoutConstraint(item: self.containerView, attribute: .bottom, relatedBy: .equal, toItem: self.scrollView, attribute: .bottom, multiplier: 1, constant: 0), 117 | NSLayoutConstraint(item: self.containerView, attribute: .width, relatedBy: .equal, toItem: self.scrollView, attribute: .width, multiplier: 1, constant: 0), 118 | containerViewHeightConstraint 119 | ]) 120 | 121 | self.containerView.addConstraints([ 122 | NSLayoutConstraint(item: self.videoView, attribute: .top, relatedBy: .equal, toItem: self.containerView, attribute: .top, multiplier: 1, constant: 0), 123 | NSLayoutConstraint(item: self.videoView, attribute: .leading, relatedBy: .equal, toItem: self.containerView, attribute: .leading, multiplier: 1, constant: 0), 124 | NSLayoutConstraint(item: self.videoView, attribute: .trailing, relatedBy: .equal, toItem: self.containerView, attribute: .trailing, multiplier: 1, constant: 0) 125 | ]) 126 | 127 | self.containerView.addConstraints([ 128 | NSLayoutConstraint(item: self.videoTrim, attribute: .top, relatedBy: .equal, toItem: self.videoView, attribute: .bottom, multiplier: 1, constant: 0), 129 | NSLayoutConstraint(item: self.videoTrim, attribute: .leading, relatedBy: .equal, toItem: self.containerView, attribute: .leading, multiplier: 1, constant: 0), 130 | NSLayoutConstraint(item: self.videoTrim, attribute: .trailing, relatedBy: .equal, toItem: self.containerView, attribute: .trailing, multiplier: 1, constant: 0) 131 | ]) 132 | 133 | self.containerView.addConstraints([ 134 | NSLayoutConstraint(item: self.toolbar, attribute: .top, relatedBy: .equal, toItem: self.videoTrim, attribute: .bottom, multiplier: 1, constant: 0), 135 | NSLayoutConstraint(item: self.toolbar, attribute: .leading, relatedBy: .equal, toItem: self.containerView, attribute: .leading, multiplier: 1, constant: 0), 136 | NSLayoutConstraint(item: self.toolbar, attribute: .trailing, relatedBy: .equal, toItem: self.containerView, attribute: .trailing, multiplier: 1, constant: 0) 137 | ]) 138 | 139 | self.containerView.addConstraints([ 140 | NSLayoutConstraint(item: self.progressView, attribute: .top, relatedBy: .equal, toItem: self.toolbar, attribute: .bottom, multiplier: 1, constant: 0), 141 | NSLayoutConstraint(item: self.progressView, attribute: .leading, relatedBy: .equal, toItem: self.containerView, attribute: .leading, multiplier: 1, constant: 0), 142 | NSLayoutConstraint(item: self.progressView, attribute: .trailing, relatedBy: .equal, toItem: self.containerView, attribute: .trailing, multiplier: 1, constant: 0) 143 | ]) 144 | 145 | self.progressView.addConstraints([ 146 | NSLayoutConstraint(item: self.progressView, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .height, multiplier: 1, constant: 2) 147 | ]) 148 | 149 | self.containerView.addConstraints([ 150 | NSLayoutConstraint(item: self.convertVideoView, attribute: .top, relatedBy: .equal, toItem: self.progressView, attribute: .bottom, multiplier: 1, constant: 20), 151 | NSLayoutConstraint(item: self.convertVideoView, attribute: .leading, relatedBy: .equal, toItem: self.containerView, attribute: .leading, multiplier: 1, constant: 0), 152 | NSLayoutConstraint(item: self.convertVideoView, attribute: .trailing, relatedBy: .equal, toItem: self.containerView, attribute: .trailing, multiplier: 1, constant: 0), 153 | NSLayoutConstraint(item: self.convertVideoView, attribute: .bottom, relatedBy: .equal, toItem: self.containerView, attribute: .bottom, multiplier: 1, constant: 0) 154 | ]) 155 | 156 | let topConstant = self.navigationController?.navigationBar.frame.height ?? 0 157 | self.view.addConstraints([ 158 | NSLayoutConstraint(item: self.fileDownloadProgressView, attribute: .top, relatedBy: .equal, toItem: self.view, attribute: .top, multiplier: 1, constant: UIApplication.shared.statusBarFrame.height + topConstant), 159 | NSLayoutConstraint(item: self.fileDownloadProgressView, attribute: .leading, relatedBy: .equal, toItem: self.view, attribute: .leading, multiplier: 1, constant: 0), 160 | NSLayoutConstraint(item: self.fileDownloadProgressView, attribute: .trailing, relatedBy: .equal, toItem: self.view, attribute: .trailing, multiplier: 1, constant: 0) 161 | ]) 162 | 163 | self.fileDownloadProgressView.addConstraints([ 164 | NSLayoutConstraint(item: self.fileDownloadProgressView, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .height, multiplier: 1, constant: 2) 165 | ]) 166 | 167 | let button = UIButton(type: .system) 168 | button.setTitle("Videos", for: .normal) 169 | button.addTarget(self, action: #selector(self.videoTap(_:)), for: .touchUpInside) 170 | self.navigationItem.titleView = button 171 | self.navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .action, target: self, action: #selector(self.convertTap(_:))) 172 | 173 | self.cropBarButtonItem = UIBarButtonItem(title: "Crop", style: .plain, target: self, action: #selector(self.cropTap(_:))) 174 | self.rotateBarButtonItem = UIBarButtonItem(title: "Rotate", style: .plain, target: self, action: #selector(self.rotateTap(_:))) 175 | self.qualityBarButtonItem = UIBarButtonItem(title: "Quality", style: .plain, target: self, action: #selector(self.qualityTap(_:))) 176 | self.muteBarButtonItem = UIBarButtonItem(title: "Mute Off", style: .plain, target: self, action: #selector(self.muteTap(_:))) 177 | 178 | var toolbarItems = [UIBarButtonItem]() 179 | toolbarItems.append(UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil)) 180 | if let barButtonItem = self.cropBarButtonItem { 181 | toolbarItems.append(barButtonItem) 182 | } 183 | toolbarItems.append(UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil)) 184 | if let barButtonItem = self.rotateBarButtonItem { 185 | toolbarItems.append(barButtonItem) 186 | } 187 | toolbarItems.append(UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil)) 188 | if let barButtonItem = self.qualityBarButtonItem { 189 | toolbarItems.append(barButtonItem) 190 | } 191 | toolbarItems.append(UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil)) 192 | if let barButtonItem = self.muteBarButtonItem { 193 | toolbarItems.append(barButtonItem) 194 | } 195 | toolbarItems.append(UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil)) 196 | 197 | self.toolbar.setItems(toolbarItems, animated: false) 198 | 199 | self.videoView.delegate = self 200 | self.videoTrim.delegate = self 201 | 202 | self.permission { (alertController) in 203 | if alertController != nil { 204 | self.showPublicVideo() 205 | return 206 | } 207 | let fetchOptions = PHFetchOptions() 208 | fetchOptions.predicate = NSPredicate(format: "mediaType = %d", PHAssetMediaType.video.rawValue) 209 | fetchOptions.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: true)] 210 | let assets = PHAsset.fetchAssets(with: fetchOptions) 211 | if let asset = assets.lastObject { 212 | let videoRequestOptions = PHVideoRequestOptions() 213 | videoRequestOptions.isNetworkAccessAllowed = true 214 | PHCachingImageManager.default().requestAVAsset(forVideo: asset, options: videoRequestOptions) { (asset, _, _) in 215 | DispatchQueue.main.async { 216 | if let urlAsset = asset as? AVURLAsset { 217 | self.videoView.url = urlAsset.url 218 | let asset = AVAsset(url: urlAsset.url) 219 | self.videoTrim.asset = asset 220 | self.videoConverter = VideoConverter(asset: asset) 221 | self.updateTrimTime() 222 | } else if let asset = asset { 223 | self.videoView.asset = asset 224 | self.videoTrim.asset = asset 225 | self.videoConverter = VideoConverter(asset: asset) 226 | self.updateTrimTime() 227 | } 228 | } 229 | } 230 | } else { 231 | self.showPublicVideo() 232 | } 233 | } 234 | } 235 | 236 | override func viewWillDisappear(_ animated: Bool) { 237 | super.viewWillDisappear(animated) 238 | 239 | self.videoView.invalidate() 240 | self.convertVideoView.invalidate() 241 | } 242 | 243 | // MARK: Convert 244 | @objc private func convertTap(_ sender: UIBarButtonItem) { 245 | guard let videoConverter = self.videoConverter else { return } 246 | 247 | var videoConverterCrop: ConverterCrop? 248 | if let dimFrame = self.videoView.dimFrame { 249 | videoConverterCrop = ConverterCrop(frame: dimFrame, contrastSize: self.videoView.videoRect.size) 250 | } 251 | videoConverter.convert(ConverterOption( 252 | trimRange: CMTimeRange(start: self.videoTrim.startTime, duration: self.videoTrim.durationTime), 253 | convertCrop: videoConverterCrop, 254 | rotate: CGFloat(.pi/2 * self.rotate), 255 | quality: self.preset, 256 | isMute: self.isMute), progress: { [weak self] (progress) in 257 | self?.progressView.setProgress(Float(progress ?? 0), animated: false) 258 | }, completion: { [weak self] (url, error) in 259 | if let error = error { 260 | let alertController = UIAlertController(title: nil, message: error.localizedDescription, preferredStyle: .alert) 261 | alertController.addAction(UIAlertAction(title: "Confirm", style: .default, handler: nil)) 262 | self?.present(alertController, animated: true) 263 | } else { 264 | self?.convertVideoView.url = url 265 | self?.progressView.setProgress(0, animated: false) 266 | } 267 | }) 268 | } 269 | } 270 | 271 | // MARK: ViewController + Update Trim 272 | extension ViewController { 273 | private func updateTrimTime() { 274 | self.videoView.startTime = self.videoTrim.startTime 275 | self.videoView.endTime = self.videoTrim.endTime 276 | self.videoView.durationTime = self.videoTrim.durationTime 277 | } 278 | } 279 | 280 | // MARK: ViewController + Crop & Rotate & Qulity & Mute 281 | extension ViewController { 282 | @objc private func cropTap(_ sender: UIBarButtonItem) { 283 | guard let asset = self.videoView.player?.currentItem?.asset, 284 | let currentTime = self.videoView.player?.currentTime() else { return } 285 | let imageGenerator = AVAssetImageGenerator(asset: asset) 286 | imageGenerator.appliesPreferredTrackTransform = true 287 | guard let imageRef = try? imageGenerator.copyCGImage(at: currentTime, actualTime: nil) else { return } 288 | guard let image = UIImage(cgImage: imageRef).rotate(radians: Float(CGFloat(.pi/2 * self.rotate))) else { return } 289 | let viewController = CropViewController(image: image) 290 | viewController.delegate = self 291 | let navigationController = UINavigationController(rootViewController: viewController) 292 | navigationController.modalPresentationStyle = .fullScreen 293 | self.present(navigationController, animated: true, completion: nil) 294 | } 295 | 296 | @objc private func rotateTap(_ sender: UIBarButtonItem) { 297 | var transform = CGAffineTransform.identity 298 | self.rotate += 1 299 | if self.rotate == 4 { 300 | self.rotate = 0 301 | self.videoView.degree = 0 302 | } else { 303 | let rotate = CGFloat(.pi/2 * self.rotate) 304 | transform = transform.rotated(by: rotate) 305 | self.videoView.degree = rotate * 180 / CGFloat.pi 306 | } 307 | self.videoView.dimFrame = nil 308 | self.videoView.containerView.transform = transform 309 | } 310 | 311 | @objc private func qualityTap(_ sender: UIBarButtonItem) { 312 | guard let asset = self.videoView.player?.currentItem?.asset else { return } 313 | let presets = AVAssetExportSession.exportPresets(compatibleWith: asset) 314 | let alertController = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet) 315 | alertController.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil)) 316 | presets.forEach { (preset) in 317 | alertController.addAction(UIAlertAction(title: preset, style: .default, handler: { (_) in 318 | self.preset = preset 319 | })) 320 | } 321 | self.present(alertController, animated: true) 322 | } 323 | 324 | @objc private func muteTap(_ sender: UIBarButtonItem) { 325 | self.muteBarButtonItem?.title = self.isMute ? "Mute Off" : "Mute On" 326 | self.videoView.isMute = self.isMute 327 | } 328 | } 329 | 330 | // MARK: ViewController + Video Albums & Camera 331 | extension ViewController { 332 | @objc private func videoTap(_ sender: UIButton) { 333 | let alertController = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet) 334 | alertController.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil)) 335 | alertController.addAction(UIAlertAction(title: "Albums", style: .default, handler: { (_) in 336 | let pickerController = UIImagePickerController() 337 | pickerController.delegate = self 338 | pickerController.mediaTypes = ["public.movie"] 339 | pickerController.sourceType = .photoLibrary 340 | self.present(pickerController, animated: true, completion: nil) 341 | })) 342 | alertController.addAction(UIAlertAction(title: "Camera", style: .default, handler: { (_) in 343 | let pickerController = UIImagePickerController() 344 | pickerController.delegate = self 345 | pickerController.mediaTypes = ["public.movie"] 346 | pickerController.sourceType = .camera 347 | self.present(pickerController, animated: true, completion: nil) 348 | })) 349 | self.present(alertController, animated: true) 350 | } 351 | } 352 | 353 | // MARK: ViewController + showPublicVideo 354 | extension ViewController { 355 | private func showPublicVideo() { 356 | guard let url = URL(string: "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ForBiggerBlazes.mp4") else { return } 357 | self.fileDownloadProgressView.isHidden = false 358 | self.fileDownloadProgressView.progress = 0 359 | DispatchQueue.global().async { 360 | let session = URLSession(configuration: URLSessionConfiguration.default, delegate: self, delegateQueue: nil) 361 | let downloadTask = session.downloadTask(with: url) 362 | downloadTask.resume() 363 | } 364 | } 365 | } 366 | 367 | // MARK: ViewController + URLSessionDownloadDelegate 368 | extension ViewController: URLSessionDownloadDelegate { 369 | func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) { 370 | let tempDirectoryURL = URL(fileURLWithPath: NSTemporaryDirectory(), isDirectory: true) 371 | let targetURL = tempDirectoryURL.appendingPathComponent("ForBiggerBlazes.mp4") 372 | try? FileManager.default.moveItem(at: location, to: targetURL) 373 | DispatchQueue.main.async { 374 | self.fileDownloadProgressView.isHidden = true 375 | self.fileDownloadProgressView.progress = 1 376 | 377 | self.videoView.url = targetURL 378 | let asset = AVAsset(url: targetURL) 379 | self.videoTrim.asset = asset 380 | self.videoConverter = VideoConverter(asset: asset) 381 | self.updateTrimTime() 382 | } 383 | } 384 | 385 | func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) { 386 | let progress = Double(totalBytesWritten) / Double(totalBytesExpectedToWrite) 387 | DispatchQueue.main.async { 388 | self.fileDownloadProgressView.progress = Float(progress) 389 | } 390 | } 391 | } 392 | 393 | // MARK: ViewController + Permission 394 | extension ViewController { 395 | private func permission(_ handler: @escaping ((UIAlertController?) -> Void)) { 396 | if PHPhotoLibrary.authorizationStatus() == .authorized { 397 | DispatchQueue.main.async { handler(nil) } 398 | } else if PHPhotoLibrary.authorizationStatus() == .denied { 399 | let alertController = UIAlertController(title: "Permission", message: "Permission", preferredStyle: .alert) 400 | alertController.addAction(UIAlertAction(title: "Confirm", style: .default, handler: nil)) 401 | handler(alertController) 402 | } else { 403 | PHPhotoLibrary.requestAuthorization { (status) in 404 | switch status { 405 | case .authorized: 406 | DispatchQueue.main.async { handler(nil) } 407 | default: 408 | let alertController = UIAlertController(title: "Permission", message: "Permission", preferredStyle: .alert) 409 | alertController.addAction(UIAlertAction(title: "Confirm", style: .default, handler: nil)) 410 | handler(alertController) 411 | } 412 | } 413 | } 414 | } 415 | } 416 | 417 | // MARK: ViewController + UIImagePickerControllerDelegate, UINavigationControllerDelegate 418 | extension ViewController: UIImagePickerControllerDelegate, UINavigationControllerDelegate { 419 | func imagePickerControllerDidCancel(_ picker: UIImagePickerController) { 420 | picker.dismiss(animated: true, completion: nil) 421 | } 422 | 423 | func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any]) { 424 | picker.dismiss(animated: true, completion: nil) 425 | guard let url = info[.mediaURL] as? URL else { return } 426 | self.preset = nil 427 | self.rotate = 0 428 | self.videoView.containerView.transform = CGAffineTransform.identity 429 | self.videoView.degree = 0 430 | self.videoView.url = url 431 | let asset = AVAsset(url: url) 432 | self.videoTrim.asset = asset 433 | self.videoView.restoreCrop() 434 | self.videoConverter = VideoConverter(asset: asset) 435 | self.updateTrimTime() 436 | } 437 | } 438 | 439 | // MARK: ViewController + VideoDelegate 440 | extension ViewController: VideoDelegate { 441 | func videoPlaying() { 442 | self.videoTrim.currentTime = self.videoView.player?.currentTime() 443 | } 444 | } 445 | 446 | // MARK: ViewController + VideoTrimDelegate 447 | extension ViewController: VideoTrimDelegate { 448 | func videoTrimStartTrimChange(_ view: VideoTrim) { 449 | self.isPlaying = self.videoView.isPlaying 450 | self.videoView.pause() 451 | } 452 | 453 | func videoTrimEndTrimChange(_ view: VideoTrim) { 454 | self.updateTrimTime() 455 | if self.isPlaying { 456 | self.videoView.play() 457 | } 458 | } 459 | 460 | func videoTrimPlayTimeChange(_ view: VideoTrim) { 461 | self.videoView.player?.seek(to: CMTime(value: CMTimeValue(view.playTime.value + view.startTime.value), timescale: view.playTime.timescale)) 462 | self.updateTrimTime() 463 | } 464 | } 465 | 466 | // MARK: ViewController + CropDelegate 467 | extension ViewController: CropDelegate { 468 | func cropImage(_ imageSize: CGSize, cropFrame: CGRect) { 469 | let videoRect = self.videoView.videoRect 470 | let frameX = cropFrame.origin.x * videoRect.size.width / imageSize.width 471 | let frameY = cropFrame.origin.y * videoRect.size.height / imageSize.height 472 | let frameWidth = cropFrame.size.width * videoRect.size.width / imageSize.width 473 | let frameHeight = cropFrame.size.height * videoRect.size.height / imageSize.height 474 | let dimFrame = CGRect(x: frameX, y: frameY, width: frameWidth, height: frameHeight) 475 | self.videoView.dimFrame = dimFrame 476 | } 477 | } 478 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 pikachu987 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # VideoConverter 2 | 3 | [![CI Status](https://img.shields.io/travis/pikachu987/VideoConverter.svg?style=flat)](https://travis-ci.org/pikachu987/VideoConverter) 4 | [![Version](https://img.shields.io/cocoapods/v/VideoConverter.svg?style=flat)](https://cocoapods.org/pods/VideoConverter) 5 | [![License](https://img.shields.io/cocoapods/l/VideoConverter.svg?style=flat)](https://cocoapods.org/pods/VideoConverter) 6 | [![Platform](https://img.shields.io/cocoapods/p/VideoConverter.svg?style=flat)](https://cocoapods.org/pods/VideoConverter) 7 | ![](https://img.shields.io/badge/Supported-iOS9%20%7C%20OSX%2010.9-4BC51D.svg?style=flat-square) 8 | [![Swift 5.0](https://img.shields.io/badge/Swift-5.0-orange.svg?style=flat)](https://developer.apple.com/swift/) 9 | 10 | ## Introduce 11 | 12 | Video can be cropped by x, y, width, and height, and can be rotated 0, 90, 180, 270 degrees. 13 | And you can adjust the duration of video playback with startTime and endTime or durationTime, and you can also mute mode. 14 | 15 |
16 | 17 | ### VideoConverter 18 | 19 | |-|-|-| 20 | |---|---|---| 21 | |||| 22 | 23 | |Trim|Crop|Rotate| 24 | |---|---|---| 25 | |||| 26 | 27 | ## Requirements 28 | 29 | ## Installation 30 | 31 | VideoConverter is available through [CocoaPods](https://cocoapods.org). To install 32 | it, simply add the following line to your Podfile: 33 | 34 | ```ruby 35 | pod 'VideoConverter' 36 | ``` 37 | 38 | ## Usage 39 | 40 | ```swift 41 | import VideoConverter 42 | ``` 43 | 44 | ```swift 45 | let videoConverter = VideoConverter(asset: asset) 46 | videoConverter.convert { (url, error) in 47 | 48 | } 49 | ``` 50 | 51 | ### convert 52 | 53 | ```swift 54 | videoConverter.convert(<#T##option: ConverterOption?##ConverterOption?#>, progress: <#T##((Double?) -> Void)?##((Double?) -> Void)?##(Double?) -> Void#>, completion: <#T##((URL?, Error?) -> Void)##((URL?, Error?) -> Void)##(URL?, Error?) -> Void#>) 55 | ``` 56 | 57 | ```swift 58 | var videoConverterCrop: ConverterCrop? 59 | if let dimFrame = self.videoView.dimFrame { 60 | videoConverterCrop = ConverterCrop(frame: dimFrame, contrastSize: self.videoView.videoRect.size) 61 | } 62 | 63 | videoConverter.convert(ConverterOption( 64 | trimRange: CMTimeRange(start: self.videoTrim.startTime, duration: self.videoTrim.durationTime), 65 | convertCrop: videoConverterCrop, 66 | rotate: CGFloat(.pi/2 * self.rotate), 67 | quality: self.preset, 68 | isMute: self.isMute), progress: { [weak self] (progress) in 69 | self?.progressView.setProgress(Float(progress ?? 0), animated: false) 70 | }, completion: { [weak self] (url, error) in 71 | if let error = error { 72 | let alertController = UIAlertController(title: nil, message: error.localizedDescription, preferredStyle: .alert) 73 | alertController.addAction(UIAlertAction(title: "Confirm", style: .default, handler: nil)) 74 | self?.present(alertController, animated: true) 75 | } else { 76 | self?.convertVideoView.url = url 77 | self?.progressView.setProgress(0, animated: false) 78 | } 79 | }) 80 | ``` 81 | 82 | ### ConverterOption 83 | 84 | ```swift 85 | var trimRange: CMTimeRange // Start time and end time or start time and duration time 86 | var convertCrop: ConverterCrop // Crop position, size 87 | var rotate: CGFloat // radian rotation 88 | var quality: String // Video quality 89 | var isMute: Bool // Mute mode 90 | ``` 91 | 92 | ### ConverterCrop 93 | 94 | ```swift 95 | var frame: CGRect // Crop position, size 96 | var contrastSize: CGSize // Crop reference size 97 | ``` 98 | 99 | ## Author 100 | 101 | pikachu987, pikachu77769@gmail.com 102 | 103 | ## License 104 | 105 | VideoConverter is available under the MIT license. See the LICENSE file for more info. 106 | -------------------------------------------------------------------------------- /VideoConverter.podspec: -------------------------------------------------------------------------------- 1 | # 2 | # Be sure to run `pod lib lint VideoConverter.podspec' to ensure this is a 3 | # valid spec before submitting. 4 | # 5 | # Any lines starting with a # are optional, but their use is encouraged 6 | # To learn more about a Podspec see https://guides.cocoapods.org/syntax/podspec.html 7 | # 8 | 9 | Pod::Spec.new do |s| 10 | s.name = 'VideoConverter' 11 | s.version = '0.1.5' 12 | s.summary = 'Video Crop, Rotate, Trim, Mute' 13 | s.description = <<-DESC 14 | Video can be cropped by x, y, width, and height, and can be rotated 0, 90, 180, 270 degrees. 15 | And you can adjust the duration of video playback with startTime and endTime or durationTime, and you can also mute mode. 16 | DESC 17 | s.homepage = 'https://github.com/pikachu987/VideoConverter' 18 | s.license = { :type => 'MIT', :file => 'LICENSE' } 19 | s.author = { 'pikachu987' => 'pikachu77769@gmail.com' } 20 | s.source = { :git => 'https://github.com/pikachu987/VideoConverter.git', :tag => s.version.to_s } 21 | s.ios.deployment_target = '9.0' 22 | s.source_files = 'VideoConverter/Classes/**/*' 23 | s.swift_version = '5.0' 24 | end 25 | -------------------------------------------------------------------------------- /VideoConverter/Assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pikachu987/VideoConverter/0911dde2c3151e88b6f34e43c668891b28182699/VideoConverter/Assets/.gitkeep -------------------------------------------------------------------------------- /VideoConverter/Classes/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pikachu987/VideoConverter/0911dde2c3151e88b6f34e43c668891b28182699/VideoConverter/Classes/.gitkeep -------------------------------------------------------------------------------- /VideoConverter/Classes/ConverterCrop.swift: -------------------------------------------------------------------------------- 1 | //MIT License 2 | // 3 | //Copyright (c) 2020 pikachu987 4 | // 5 | //Permission is hereby granted, free of charge, to any person obtaining a copy 6 | //of this software and associated documentation files (the "Software"), to deal 7 | //in the Software without restriction, including without limitation the rights 8 | //to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | //copies of the Software, and to permit persons to whom the Software is 10 | //furnished to do so, subject to the following conditions: 11 | // 12 | //The above copyright notice and this permission notice shall be included in 13 | //all copies or substantial portions of the Software. 14 | // 15 | //THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | //IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | //FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | //AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | //LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | //OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | //THE SOFTWARE. 22 | 23 | import UIKit 24 | 25 | public struct ConverterCrop { 26 | public var frame: CGRect 27 | public var contrastSize: CGSize 28 | 29 | public init(frame: CGRect, contrastSize: CGSize) { 30 | self.frame = frame 31 | self.contrastSize = contrastSize 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /VideoConverter/Classes/ConverterDegree.swift: -------------------------------------------------------------------------------- 1 | //MIT License 2 | // 3 | //Copyright (c) 2020 pikachu987 4 | // 5 | //Permission is hereby granted, free of charge, to any person obtaining a copy 6 | //of this software and associated documentation files (the "Software"), to deal 7 | //in the Software without restriction, including without limitation the rights 8 | //to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | //copies of the Software, and to permit persons to whom the Software is 10 | //furnished to do so, subject to the following conditions: 11 | // 12 | //The above copyright notice and this permission notice shall be included in 13 | //all copies or substantial portions of the Software. 14 | // 15 | //THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | //IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | //FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | //AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | //LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | //OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | //THE SOFTWARE. 22 | 23 | import UIKit 24 | 25 | enum ConverterDegree: String { 26 | case degree0 27 | case degree90 28 | case degree180 29 | case degree270 30 | 31 | static func convert(degree: CGFloat) -> ConverterDegree { 32 | if degree == 0 || degree == 360 { 33 | return .degree0 34 | } else if degree == 90 { 35 | return .degree90 36 | } else if degree == 180 { 37 | return .degree180 38 | } else if degree == 270 { 39 | return .degree270 40 | } else { 41 | return .degree90 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /VideoConverter/Classes/ConverterOption.swift: -------------------------------------------------------------------------------- 1 | //MIT License 2 | // 3 | //Copyright (c) 2020 pikachu987 4 | // 5 | //Permission is hereby granted, free of charge, to any person obtaining a copy 6 | //of this software and associated documentation files (the "Software"), to deal 7 | //in the Software without restriction, including without limitation the rights 8 | //to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | //copies of the Software, and to permit persons to whom the Software is 10 | //furnished to do so, subject to the following conditions: 11 | // 12 | //The above copyright notice and this permission notice shall be included in 13 | //all copies or substantial portions of the Software. 14 | // 15 | //THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | //IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | //FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | //AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | //LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | //OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | //THE SOFTWARE. 22 | 23 | import Foundation 24 | import AVKit 25 | 26 | public struct ConverterOption { 27 | public var trimRange: CMTimeRange? 28 | public var convertCrop: ConverterCrop? 29 | public var rotate: CGFloat? 30 | public var quality: String? 31 | public var isMute: Bool 32 | 33 | public init(trimRange: CMTimeRange?, convertCrop: ConverterCrop?, rotate: CGFloat?, quality: String?, isMute: Bool = false) { 34 | self.trimRange = trimRange 35 | self.convertCrop = convertCrop 36 | self.rotate = rotate 37 | self.quality = quality 38 | self.isMute = isMute 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /VideoConverter/Classes/VideoConverter.swift: -------------------------------------------------------------------------------- 1 | //MIT License 2 | // 3 | //Copyright (c) 2020 pikachu987 4 | // 5 | //Permission is hereby granted, free of charge, to any person obtaining a copy 6 | //of this software and associated documentation files (the "Software"), to deal 7 | //in the Software without restriction, including without limitation the rights 8 | //to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | //copies of the Software, and to permit persons to whom the Software is 10 | //furnished to do so, subject to the following conditions: 11 | // 12 | //The above copyright notice and this permission notice shall be included in 13 | //all copies or substantial portions of the Software. 14 | // 15 | //THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | //IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | //FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | //AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | //LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | //OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | //THE SOFTWARE. 22 | 23 | import UIKit 24 | import AVKit 25 | 26 | open class VideoConverter { 27 | public let asset: AVAsset 28 | public let presets: [String] 29 | 30 | public var option: ConverterOption? 31 | 32 | private var assetExportsSession: AVAssetExportSession? 33 | private var timer: Timer? 34 | 35 | private var progressCallback: ((Double?) -> Void)? 36 | 37 | private var videoTrack: AVAssetTrack? { 38 | return self.asset.tracks(withMediaType: .video).first 39 | } 40 | 41 | private var radian: CGFloat? { 42 | guard let videoTrank = self.videoTrack else { return nil } 43 | return atan2(videoTrank.preferredTransform.b, videoTrank.preferredTransform.a) + (self.option?.rotate ?? 0) 44 | } 45 | 46 | private var converterDegree: ConverterDegree? { 47 | guard let radian = self.radian else { return nil } 48 | let degree = radian * 180 / .pi 49 | return ConverterDegree.convert(degree: degree) 50 | } 51 | 52 | private var naturalSize: CGSize? { 53 | guard let videoTrack = self.videoTrack, 54 | let converterDegree = self.converterDegree else { return nil } 55 | if converterDegree == .degree90 || converterDegree == .degree270 { 56 | return CGSize(width: videoTrack.naturalSize.height, height: videoTrack.naturalSize.width) 57 | } else { 58 | return CGSize(width: videoTrack.naturalSize.width, height: videoTrack.naturalSize.height) 59 | } 60 | } 61 | 62 | private var cropFrame: CGRect? { 63 | guard let crop = self.option?.convertCrop else { return nil } 64 | guard let naturalSize = self.naturalSize else { return nil } 65 | let contrastSize = crop.contrastSize 66 | let frame = crop.frame 67 | let cropX = frame.origin.x * naturalSize.width / contrastSize.width 68 | let cropY = frame.origin.y * naturalSize.height / contrastSize.height 69 | let cropWidth = frame.size.width * naturalSize.width / contrastSize.width 70 | let cropHeight = frame.size.height * naturalSize.height / contrastSize.height 71 | let cropFrame = CGRect(x: cropX, y: cropY, width: cropWidth, height: cropHeight) 72 | return cropFrame 73 | } 74 | 75 | public init(asset: AVAsset) { 76 | self.asset = asset 77 | self.presets = AVAssetExportSession.exportPresets(compatibleWith: asset) 78 | } 79 | 80 | // Restore 81 | open func restore() { 82 | self.option = nil 83 | self.assetExportsSession?.cancelExport() 84 | self.assetExportsSession = nil 85 | self.timer?.invalidate() 86 | self.timer = nil 87 | self.progressCallback = nil 88 | } 89 | 90 | // Convert 91 | open func convert(_ option: ConverterOption? = nil, temporaryFileName: String? = nil, progress: ((Double?) -> Void)? = nil, completion: @escaping ((URL?, Error?) -> Void)) { 92 | self.restore() 93 | guard let videoTrack = self.videoTrack else { 94 | DispatchQueue.main.async { 95 | completion(nil, NSError(domain: "Can't find video", code: 404, userInfo: nil)) 96 | } 97 | return 98 | } 99 | self.option = option 100 | if self.renderSize?.width == 0 || self.renderSize?.height == 0 { 101 | self.restore() 102 | DispatchQueue.main.async { 103 | completion(nil, NSError(domain: "The crop size is too small", code: 503, userInfo: nil)) 104 | } 105 | return 106 | } 107 | 108 | let composition = AVMutableComposition() 109 | 110 | var trackTimeRange: CMTimeRange 111 | if let trimRange = option?.trimRange { 112 | trackTimeRange = trimRange 113 | } else { 114 | trackTimeRange = CMTimeRange(start: .zero, duration: self.asset.duration) 115 | } 116 | 117 | guard let videoCompositionTrack = composition.addMutableTrack(withMediaType: .video, preferredTrackID: kCMPersistentTrackID_Invalid) else { 118 | self.restore() 119 | DispatchQueue.main.async { 120 | completion(nil, NSError(domain: "Can't find video", code: 404, userInfo: nil)) 121 | } 122 | return 123 | } 124 | 125 | // trim 126 | try? videoCompositionTrack.insertTimeRange(trackTimeRange, of: videoTrack, at: .zero) 127 | 128 | // mute 129 | if !(option?.isMute ?? false) { 130 | if let audioTrack = self.asset.tracks(withMediaType: AVMediaType.audio).first { 131 | let audioCompositionTrack = composition.addMutableTrack(withMediaType: .audio, preferredTrackID: kCMPersistentTrackID_Invalid) 132 | // mute trim 133 | try? audioCompositionTrack?.insertTimeRange(trackTimeRange, of: audioTrack, at: .zero) 134 | } 135 | } 136 | 137 | let compositionInstructions = AVMutableVideoCompositionInstruction() 138 | compositionInstructions.timeRange = CMTimeRange(start: .zero, duration: self.asset.duration) 139 | compositionInstructions.backgroundColor = UIColor(red: 0/255, green: 0/255, blue: 0/255, alpha: 1).cgColor 140 | 141 | let layerInstructions = AVMutableVideoCompositionLayerInstruction(assetTrack: videoCompositionTrack) 142 | // opacity 143 | layerInstructions.setOpacity(1.0, at: .zero) 144 | // transform 145 | if let transform = self.transform { 146 | layerInstructions.setTransform(transform, at: .zero) 147 | } 148 | compositionInstructions.layerInstructions = [layerInstructions] 149 | 150 | let videoComposition = AVMutableVideoComposition() 151 | videoComposition.instructions = [compositionInstructions] 152 | // size 153 | if let renderSize = self.renderSize { 154 | videoComposition.renderSize = renderSize 155 | } 156 | videoComposition.frameDuration = CMTimeMake(value: 1, timescale: 30) 157 | // saveFile 158 | let temporaryFileName = temporaryFileName ?? "TrimmedMovie.mp4" 159 | let url = URL(fileURLWithPath: "\(NSTemporaryDirectory())\(temporaryFileName)") 160 | try? FileManager.default.removeItem(at: url) 161 | 162 | self.progressCallback = progress 163 | // progress timer 164 | DispatchQueue.main.async { 165 | if #available(iOS 10.0, *) { 166 | self.timer = Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true) { [weak self] (time) in 167 | if let progress = self?.assetExportsSession?.progress { 168 | self?.progressCallback?(Double(progress)) 169 | if progress >= 1 { 170 | self?.timer?.invalidate() 171 | self?.timer = nil 172 | } 173 | } else if self?.assetExportsSession == nil { 174 | self?.timer?.invalidate() 175 | self?.timer = nil 176 | } 177 | } 178 | } else { 179 | self.timer = Timer.scheduledTimer(timeInterval: 0.1, target: self, selector: #selector(self.timerAction(_:)), userInfo: nil, repeats: true) 180 | } 181 | } 182 | 183 | // quality 184 | let presetName = option?.quality ?? AVAssetExportPresetHighestQuality 185 | self.assetExportsSession = AVAssetExportSession(asset: composition, presetName: presetName) 186 | self.assetExportsSession?.outputFileType = AVFileType.mp4 187 | self.assetExportsSession?.shouldOptimizeForNetworkUse = true 188 | self.assetExportsSession?.videoComposition = videoComposition 189 | self.assetExportsSession?.outputURL = url 190 | 191 | self.assetExportsSession?.exportAsynchronously(completionHandler: { 192 | self.timer?.invalidate() 193 | self.timer = nil 194 | DispatchQueue.main.async { 195 | self.progressCallback?(1) 196 | self.progressCallback = nil 197 | if let url = self.assetExportsSession?.outputURL, self.assetExportsSession?.status == .completed { 198 | completion(url, nil) 199 | } else { 200 | completion(nil, self.assetExportsSession?.error) 201 | } 202 | self.restore() 203 | } 204 | }) 205 | } 206 | 207 | // Video Size 208 | private var renderSize: CGSize? { 209 | guard let naturalSize = self.naturalSize else { return nil } 210 | var renderSize = naturalSize 211 | if let cropFrame = self.cropFrame { 212 | let width = floor(cropFrame.size.width / 16) * 16 213 | let height = floor(cropFrame.size.height / 16) * 16 214 | renderSize = CGSize(width: width, height: height) 215 | } 216 | return renderSize 217 | } 218 | 219 | // Video Rotate & Rrigin 220 | private var transform: CGAffineTransform? { 221 | guard let naturalSize = self.naturalSize, 222 | let radian = self.radian, 223 | let converterDegree = self.converterDegree else { return nil } 224 | 225 | var transform = CGAffineTransform.identity 226 | transform = transform.rotated(by: radian) 227 | if converterDegree == .degree90 { 228 | transform = transform.translatedBy(x: 0, y: -naturalSize.width) 229 | } else if converterDegree == .degree180 { 230 | transform = transform.translatedBy(x: -naturalSize.width, y: -naturalSize.height) 231 | } else if converterDegree == .degree270 { 232 | transform = transform.translatedBy(x: -naturalSize.height, y: 0) 233 | } 234 | 235 | if let cropFrame = self.cropFrame { 236 | if converterDegree == .degree0 { 237 | transform = transform.translatedBy(x: -cropFrame.origin.x, y: -cropFrame.origin.y) 238 | } else if converterDegree == .degree90 { 239 | transform = transform.translatedBy(x: -cropFrame.origin.y, y: cropFrame.origin.x) 240 | } else if converterDegree == .degree180 { 241 | transform = transform.translatedBy(x: cropFrame.origin.x, y: cropFrame.origin.y) 242 | } else if converterDegree == .degree270 { 243 | transform = transform.translatedBy(x: cropFrame.origin.y, y: -cropFrame.origin.x) 244 | } 245 | } 246 | return transform 247 | } 248 | 249 | // Progress Time Timer 250 | @objc private func timerAction(_ sender: Timer) { 251 | if let progress = self.assetExportsSession?.progress { 252 | self.progressCallback?(Double(progress)) 253 | if progress >= 1 { 254 | self.timer?.invalidate() 255 | self.timer = nil 256 | } 257 | } else if self.assetExportsSession == nil { 258 | self.timer?.invalidate() 259 | self.timer = nil 260 | } 261 | } 262 | } 263 | -------------------------------------------------------------------------------- /_Pods.xcodeproj: -------------------------------------------------------------------------------- 1 | Example/Pods/Pods.xcodeproj -------------------------------------------------------------------------------- /img/gif1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pikachu987/VideoConverter/0911dde2c3151e88b6f34e43c668891b28182699/img/gif1.gif -------------------------------------------------------------------------------- /img/gif2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pikachu987/VideoConverter/0911dde2c3151e88b6f34e43c668891b28182699/img/gif2.gif -------------------------------------------------------------------------------- /img/gif3.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pikachu987/VideoConverter/0911dde2c3151e88b6f34e43c668891b28182699/img/gif3.gif -------------------------------------------------------------------------------- /img/img1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pikachu987/VideoConverter/0911dde2c3151e88b6f34e43c668891b28182699/img/img1.png -------------------------------------------------------------------------------- /img/img2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pikachu987/VideoConverter/0911dde2c3151e88b6f34e43c668891b28182699/img/img2.png -------------------------------------------------------------------------------- /img/img3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pikachu987/VideoConverter/0911dde2c3151e88b6f34e43c668891b28182699/img/img3.png --------------------------------------------------------------------------------