Save media
This commit is contained in:
parent
493ea98f13
commit
2eddf8c558
|
@ -0,0 +1,54 @@
|
||||||
|
// Copyright © 2021 Metabolist. All rights reserved.
|
||||||
|
|
||||||
|
import AVFoundation
|
||||||
|
|
||||||
|
enum AssetExportError: Error {
|
||||||
|
case exportSetup
|
||||||
|
case export
|
||||||
|
}
|
||||||
|
|
||||||
|
extension AVURLAsset {
|
||||||
|
func exportWithoutAudioTrack(completion: @escaping ((Result<URL, AssetExportError>) -> Void)) {
|
||||||
|
let composition = AVMutableComposition()
|
||||||
|
let exportDirectory = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString)
|
||||||
|
|
||||||
|
guard let sourceVideoTrack = tracks(withMediaType: .video).first,
|
||||||
|
let compositionVideoTrack = composition.addMutableTrack(
|
||||||
|
withMediaType: .video,
|
||||||
|
preferredTrackID: kCMPersistentTrackID_Invalid),
|
||||||
|
case .success = Result(catching: {
|
||||||
|
try compositionVideoTrack.insertTimeRange(
|
||||||
|
CMTimeRange(start: .zero, duration: duration),
|
||||||
|
of: sourceVideoTrack, at: .zero)
|
||||||
|
}),
|
||||||
|
let exportSession = AVAssetExportSession(
|
||||||
|
asset: composition,
|
||||||
|
presetName: AVAssetExportPresetHighestQuality),
|
||||||
|
exportSession.supportedFileTypes.contains(.mp4),
|
||||||
|
case .success = Result(catching: {
|
||||||
|
try FileManager.default.createDirectory(
|
||||||
|
at: exportDirectory,
|
||||||
|
withIntermediateDirectories: false)
|
||||||
|
})
|
||||||
|
else {
|
||||||
|
completion(.failure(.exportSetup))
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let exportURL = exportDirectory.appendingPathComponent(url.lastPathComponent)
|
||||||
|
|
||||||
|
exportSession.outputFileType = AVFileType.mp4
|
||||||
|
exportSession.outputURL = exportURL
|
||||||
|
exportSession.timeRange = CMTimeRange(start: .zero, duration: duration)
|
||||||
|
exportSession.exportAsynchronously {
|
||||||
|
guard exportSession.status == .completed else {
|
||||||
|
completion(.failure(.export))
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
completion(.success(exportURL))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -39,6 +39,7 @@
|
||||||
"attachment.edit.thumbnail.prompt" = "Drag the circle on the preview to choose the focal point which will always be in view on all thumbnails";
|
"attachment.edit.thumbnail.prompt" = "Drag the circle on the preview to choose the focal point which will always be in view on all thumbnails";
|
||||||
"attachment.sensitive-content" = "Sensitive content";
|
"attachment.sensitive-content" = "Sensitive content";
|
||||||
"attachment.media-hidden" = "Media hidden";
|
"attachment.media-hidden" = "Media hidden";
|
||||||
|
"attachment.unable-to-export-media" = "Unable to export media";
|
||||||
"bookmarks" = "Bookmarks";
|
"bookmarks" = "Bookmarks";
|
||||||
"camera-access.title" = "Camera access needed";
|
"camera-access.title" = "Camera access needed";
|
||||||
"camera-access.description" = "Open system settings to allow camera access";
|
"camera-access.description" = "Open system settings to allow camera access";
|
||||||
|
|
|
@ -51,6 +51,7 @@
|
||||||
D059373E25AB8D5200754FDF /* CompositionPollOptionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D059373D25AB8D5200754FDF /* CompositionPollOptionView.swift */; };
|
D059373E25AB8D5200754FDF /* CompositionPollOptionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D059373D25AB8D5200754FDF /* CompositionPollOptionView.swift */; };
|
||||||
D059373F25AB8D5200754FDF /* CompositionPollOptionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D059373D25AB8D5200754FDF /* CompositionPollOptionView.swift */; };
|
D059373F25AB8D5200754FDF /* CompositionPollOptionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D059373D25AB8D5200754FDF /* CompositionPollOptionView.swift */; };
|
||||||
D059376125ABE2E800754FDF /* XMLUnescaper.swift in Sources */ = {isa = PBXBuildFile; fileRef = D059376025ABE2E800754FDF /* XMLUnescaper.swift */; };
|
D059376125ABE2E800754FDF /* XMLUnescaper.swift in Sources */ = {isa = PBXBuildFile; fileRef = D059376025ABE2E800754FDF /* XMLUnescaper.swift */; };
|
||||||
|
D05E688525B55AE8001FB2C6 /* AVURLAsset+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05E688425B55AE8001FB2C6 /* AVURLAsset+Extensions.swift */; };
|
||||||
D0625E59250F092900502611 /* StatusListCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0625E58250F092900502611 /* StatusListCell.swift */; };
|
D0625E59250F092900502611 /* StatusListCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0625E58250F092900502611 /* StatusListCell.swift */; };
|
||||||
D0625E5D250F0B5C00502611 /* StatusContentConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0625E5C250F0B5C00502611 /* StatusContentConfiguration.swift */; };
|
D0625E5D250F0B5C00502611 /* StatusContentConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0625E5C250F0B5C00502611 /* StatusContentConfiguration.swift */; };
|
||||||
D06BC5E625202AD90079541D /* ProfileViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D06BC5E525202AD90079541D /* ProfileViewController.swift */; };
|
D06BC5E625202AD90079541D /* ProfileViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D06BC5E525202AD90079541D /* ProfileViewController.swift */; };
|
||||||
|
@ -218,6 +219,7 @@
|
||||||
D059373225AAEA7000754FDF /* CompositionPollView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompositionPollView.swift; sourceTree = "<group>"; };
|
D059373225AAEA7000754FDF /* CompositionPollView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompositionPollView.swift; sourceTree = "<group>"; };
|
||||||
D059373D25AB8D5200754FDF /* CompositionPollOptionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompositionPollOptionView.swift; sourceTree = "<group>"; };
|
D059373D25AB8D5200754FDF /* CompositionPollOptionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompositionPollOptionView.swift; sourceTree = "<group>"; };
|
||||||
D059376025ABE2E800754FDF /* XMLUnescaper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = XMLUnescaper.swift; sourceTree = "<group>"; };
|
D059376025ABE2E800754FDF /* XMLUnescaper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = XMLUnescaper.swift; sourceTree = "<group>"; };
|
||||||
|
D05E688425B55AE8001FB2C6 /* AVURLAsset+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AVURLAsset+Extensions.swift"; sourceTree = "<group>"; };
|
||||||
D0625E58250F092900502611 /* StatusListCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusListCell.swift; sourceTree = "<group>"; };
|
D0625E58250F092900502611 /* StatusListCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusListCell.swift; sourceTree = "<group>"; };
|
||||||
D0625E5C250F0B5C00502611 /* StatusContentConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusContentConfiguration.swift; sourceTree = "<group>"; };
|
D0625E5C250F0B5C00502611 /* StatusContentConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusContentConfiguration.swift; sourceTree = "<group>"; };
|
||||||
D0666A2124C677B400F3F04B /* Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = Tests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
D0666A2124C677B400F3F04B /* Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = Tests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
|
@ -569,6 +571,7 @@
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
D01C6FAB252024BD003D0300 /* Array+Extensions.swift */,
|
D01C6FAB252024BD003D0300 /* Array+Extensions.swift */,
|
||||||
|
D05E688425B55AE8001FB2C6 /* AVURLAsset+Extensions.swift */,
|
||||||
D0F0B135251AA12700942152 /* CollectionItem+Extensions.swift */,
|
D0F0B135251AA12700942152 /* CollectionItem+Extensions.swift */,
|
||||||
D0C7D46E24F76169001EBDBB /* KingfisherOptionsInfo+Extensions.swift */,
|
D0C7D46E24F76169001EBDBB /* KingfisherOptionsInfo+Extensions.swift */,
|
||||||
D0C7D46B24F76169001EBDBB /* NSMutableAttributedString+Extensions.swift */,
|
D0C7D46B24F76169001EBDBB /* NSMutableAttributedString+Extensions.swift */,
|
||||||
|
@ -834,6 +837,7 @@
|
||||||
D0FE1C8F253686F9003EF1EB /* PlayerView.swift in Sources */,
|
D0FE1C8F253686F9003EF1EB /* PlayerView.swift in Sources */,
|
||||||
D0CE9F87258B076900E3A6B6 /* AttachmentUploadView.swift in Sources */,
|
D0CE9F87258B076900E3A6B6 /* AttachmentUploadView.swift in Sources */,
|
||||||
D0F0B113251A86A000942152 /* AccountContentConfiguration.swift in Sources */,
|
D0F0B113251A86A000942152 /* AccountContentConfiguration.swift in Sources */,
|
||||||
|
D05E688525B55AE8001FB2C6 /* AVURLAsset+Extensions.swift in Sources */,
|
||||||
D036AA02254B6101009094DF /* NotificationListCell.swift in Sources */,
|
D036AA02254B6101009094DF /* NotificationListCell.swift in Sources */,
|
||||||
D08B8D42253F92B600B1EBEF /* ImagePageViewController.swift in Sources */,
|
D08B8D42253F92B600B1EBEF /* ImagePageViewController.swift in Sources */,
|
||||||
D01F41D924F880C400D55A2D /* TouchFallthroughTextView.swift in Sources */,
|
D01F41D924F880C400D55A2D /* TouchFallthroughTextView.swift in Sources */,
|
||||||
|
|
|
@ -2,10 +2,6 @@
|
||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
<plist version="1.0">
|
<plist version="1.0">
|
||||||
<dict>
|
<dict>
|
||||||
<key>NSMicrophoneUsageDescription</key>
|
|
||||||
<string>Enables Metatext to take videos and add them to your posts.</string>
|
|
||||||
<key>NSCameraUsageDescription</key>
|
|
||||||
<string>Enables Metatext to take photos and videos and add them to your posts.</string>
|
|
||||||
<key>CFBundleDevelopmentRegion</key>
|
<key>CFBundleDevelopmentRegion</key>
|
||||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||||
<key>CFBundleExecutable</key>
|
<key>CFBundleExecutable</key>
|
||||||
|
@ -24,6 +20,14 @@
|
||||||
<string>1</string>
|
<string>1</string>
|
||||||
<key>LSRequiresIPhoneOS</key>
|
<key>LSRequiresIPhoneOS</key>
|
||||||
<true/>
|
<true/>
|
||||||
|
<key>NSCameraUsageDescription</key>
|
||||||
|
<string>Enables Metatext to take photos and videos and add them to your posts</string>
|
||||||
|
<key>NSMicrophoneUsageDescription</key>
|
||||||
|
<string>Enables Metatext to take videos and add them to your posts</string>
|
||||||
|
<key>NSPhotoLibraryAddUsageDescription</key>
|
||||||
|
<string>Enables Metatext to add items to your Photo Library</string>
|
||||||
|
<key>NSPhotoLibraryUsageDescription</key>
|
||||||
|
<string>Enables Metatext to access photos from your library and add them to your posts</string>
|
||||||
<key>UIApplicationSceneManifest</key>
|
<key>UIApplicationSceneManifest</key>
|
||||||
<dict>
|
<dict>
|
||||||
<key>UIApplicationSupportsMultipleScenes</key>
|
<key>UIApplicationSupportsMultipleScenes</key>
|
||||||
|
|
|
@ -48,6 +48,12 @@ final class ImagePageViewController: UIPageViewController {
|
||||||
systemItem: .close,
|
systemItem: .close,
|
||||||
primaryAction: UIAction { [weak self] _ in self?.presentingViewController?.dismiss(animated: true) })
|
primaryAction: UIAction { [weak self] _ in self?.presentingViewController?.dismiss(animated: true) })
|
||||||
|
|
||||||
|
navigationItem.rightBarButtonItem = .init(
|
||||||
|
systemItem: .action,
|
||||||
|
primaryAction: UIAction { [weak self] _ in
|
||||||
|
(self?.viewControllers?.first as? ImageViewController)?.presentActivityViewController()
|
||||||
|
})
|
||||||
|
|
||||||
navigationController?.barHideOnTapGestureRecognizer.addTarget(
|
navigationController?.barHideOnTapGestureRecognizer.addTarget(
|
||||||
self,
|
self,
|
||||||
action: #selector(toggleDescriptionVisibility))
|
action: #selector(toggleDescriptionVisibility))
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
// Copyright © 2020 Metabolist. All rights reserved.
|
// Copyright © 2020 Metabolist. All rights reserved.
|
||||||
|
|
||||||
|
import AVFoundation
|
||||||
import Kingfisher
|
import Kingfisher
|
||||||
import UIKit
|
import UIKit
|
||||||
import ViewModels
|
import ViewModels
|
||||||
|
@ -146,6 +147,44 @@ extension ImageViewController {
|
||||||
self.descriptionBackgroundView.alpha = self.descriptionBackgroundView.alpha > 0 ? 0 : 1
|
self.descriptionBackgroundView.alpha = self.descriptionBackgroundView.alpha > 0 ? 0 : 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func presentActivityViewController() {
|
||||||
|
if let image = imageView.image {
|
||||||
|
let activityViewController = UIActivityViewController(activityItems: [image], applicationActivities: [])
|
||||||
|
|
||||||
|
present(activityViewController, animated: true)
|
||||||
|
} else if let asset = playerView.player?.currentItem?.asset as? AVURLAsset {
|
||||||
|
asset.exportWithoutAudioTrack { result in
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
switch result {
|
||||||
|
case let .success(url):
|
||||||
|
let activityViewController = UIActivityViewController(
|
||||||
|
activityItems: [url],
|
||||||
|
applicationActivities: [])
|
||||||
|
|
||||||
|
activityViewController.completionWithItemsHandler = { _, _, _, _ in
|
||||||
|
try? FileManager.default.removeItem(at: url.deletingLastPathComponent())
|
||||||
|
}
|
||||||
|
|
||||||
|
self.present(activityViewController, animated: true)
|
||||||
|
case .failure:
|
||||||
|
let alertController = UIAlertController(
|
||||||
|
title: nil,
|
||||||
|
message: NSLocalizedString("attachment.unable-to-export-media", comment: ""),
|
||||||
|
preferredStyle: .alert)
|
||||||
|
|
||||||
|
let okAction = UIAlertAction(
|
||||||
|
title: NSLocalizedString("ok", comment: ""),
|
||||||
|
style: .default) { _ in }
|
||||||
|
|
||||||
|
alertController.addAction(okAction)
|
||||||
|
|
||||||
|
self.present(alertController, animated: true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension ImageViewController: UIScrollViewDelegate {
|
extension ImageViewController: UIScrollViewDelegate {
|
||||||
|
|
Loading…
Reference in New Issue