forked from shadowfacts/Tusker
Include filename extension for attachments
Fixes posting attachments on pleroma resulting in them served as application/octet-stream, even though we're sending the mime type as well
This commit is contained in:
parent
9fa352d4f8
commit
037b717e60
|
@ -48,13 +48,13 @@ enum CompositionAttachmentData {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func getData(completion: @escaping (Result<(Data, String), Error>) -> Void) {
|
func getData(completion: @escaping (Result<(Data, UTType), Error>) -> Void) {
|
||||||
switch self {
|
switch self {
|
||||||
case let .image(image):
|
case let .image(image):
|
||||||
// Export as JPEG instead of PNG, otherweise photos straight from the camera are too large
|
// Export as JPEG instead of PNG, otherweise photos straight from the camera are too large
|
||||||
// for Mastodon in its default configuration (max of 10MB).
|
// for Mastodon in its default configuration (max of 10MB).
|
||||||
// The quality of 0.8 was chosen completely arbitrarily, it may need to be tuned in the future.
|
// The quality of 0.8 was chosen completely arbitrarily, it may need to be tuned in the future.
|
||||||
completion(.success((image.jpegData(compressionQuality: 0.8)!, "image/jpeg")))
|
completion(.success((image.jpegData(compressionQuality: 0.8)!, .jpeg)))
|
||||||
case let .asset(asset):
|
case let .asset(asset):
|
||||||
if asset.mediaType == .image {
|
if asset.mediaType == .image {
|
||||||
let options = PHImageRequestOptions()
|
let options = PHImageRequestOptions()
|
||||||
|
@ -68,19 +68,19 @@ enum CompositionAttachmentData {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
let mimeType: String
|
let utType: UTType
|
||||||
if dataUTI == "public.heic" {
|
if dataUTI == "public.heic" {
|
||||||
// neither Mastodon nor Pleroma handles HEIC well, so convert to JPEG
|
// neither Mastodon nor Pleroma handles HEIC well, so convert to JPEG
|
||||||
let image = CIImage(data: data)!
|
let image = CIImage(data: data)!
|
||||||
let context = CIContext()
|
let context = CIContext()
|
||||||
let colorSpace = image.colorSpace ?? CGColorSpace(name: CGColorSpace.sRGB)!
|
let colorSpace = image.colorSpace ?? CGColorSpace(name: CGColorSpace.sRGB)!
|
||||||
data = context.jpegRepresentation(of: image, colorSpace: colorSpace, options: [:])!
|
data = context.jpegRepresentation(of: image, colorSpace: colorSpace, options: [:])!
|
||||||
mimeType = "image/jpeg"
|
utType = .jpeg
|
||||||
} else {
|
} else {
|
||||||
mimeType = UTType(dataUTI)!.preferredMIMEType!
|
utType = UTType(dataUTI)!
|
||||||
}
|
}
|
||||||
|
|
||||||
completion(.success((data, mimeType)))
|
completion(.success((data, utType)))
|
||||||
}
|
}
|
||||||
} else if asset.mediaType == .video {
|
} else if asset.mediaType == .video {
|
||||||
let options = PHVideoRequestOptions()
|
let options = PHVideoRequestOptions()
|
||||||
|
@ -109,11 +109,11 @@ enum CompositionAttachmentData {
|
||||||
|
|
||||||
case let .drawing(drawing):
|
case let .drawing(drawing):
|
||||||
let image = drawing.imageInLightMode(from: drawing.bounds, scale: 1)
|
let image = drawing.imageInLightMode(from: drawing.bounds, scale: 1)
|
||||||
completion(.success((image.pngData()!, "image/png")))
|
completion(.success((image.pngData()!, .png)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static func exportVideoData(session: AVAssetExportSession, completion: @escaping (Result<(Data, String), Error>) -> Void) {
|
private static func exportVideoData(session: AVAssetExportSession, completion: @escaping (Result<(Data, UTType), Error>) -> Void) {
|
||||||
session.outputFileType = .mp4
|
session.outputFileType = .mp4
|
||||||
session.outputURL = FileManager.default.temporaryDirectory.appendingPathComponent("exported_video_\(UUID())").appendingPathExtension("mp4")
|
session.outputURL = FileManager.default.temporaryDirectory.appendingPathComponent("exported_video_\(UUID())").appendingPathExtension("mp4")
|
||||||
session.exportAsynchronously {
|
session.exportAsynchronously {
|
||||||
|
@ -123,7 +123,7 @@ enum CompositionAttachmentData {
|
||||||
}
|
}
|
||||||
do {
|
do {
|
||||||
let data = try Data(contentsOf: session.outputURL!)
|
let data = try Data(contentsOf: session.outputURL!)
|
||||||
completion(.success((data, "video/mp4")))
|
completion(.success((data, .mpeg4Movie)))
|
||||||
} catch {
|
} catch {
|
||||||
completion(.failure(.videoExport(error)))
|
completion(.failure(.videoExport(error)))
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import Pachyderm
|
import Pachyderm
|
||||||
|
import UniformTypeIdentifiers
|
||||||
|
|
||||||
class PostService: ObservableObject {
|
class PostService: ObservableObject {
|
||||||
private let mastodonController: MastodonController
|
private let mastodonController: MastodonController
|
||||||
|
@ -66,15 +67,15 @@ class PostService: ObservableObject {
|
||||||
attachments.reserveCapacity(draft.attachments.count)
|
attachments.reserveCapacity(draft.attachments.count)
|
||||||
for (index, attachment) in draft.attachments.enumerated() {
|
for (index, attachment) in draft.attachments.enumerated() {
|
||||||
let data: Data
|
let data: Data
|
||||||
let mimeType: String
|
let utType: UTType
|
||||||
do {
|
do {
|
||||||
(data, mimeType) = try await getData(for: attachment)
|
(data, utType) = try await getData(for: attachment)
|
||||||
currentStep += 1
|
currentStep += 1
|
||||||
} catch let error as CompositionAttachmentData.Error {
|
} catch let error as CompositionAttachmentData.Error {
|
||||||
throw Error.attachmentData(index: index, cause: error)
|
throw Error.attachmentData(index: index, cause: error)
|
||||||
}
|
}
|
||||||
do {
|
do {
|
||||||
let uploaded = try await uploadAttachment(data: data, mimeType: mimeType, description: attachment.attachmentDescription)
|
let uploaded = try await uploadAttachment(data: data, utType: utType, description: attachment.attachmentDescription)
|
||||||
attachments.append(uploaded)
|
attachments.append(uploaded)
|
||||||
currentStep += 1
|
currentStep += 1
|
||||||
} catch let error as Client.Error {
|
} catch let error as Client.Error {
|
||||||
|
@ -84,7 +85,7 @@ class PostService: ObservableObject {
|
||||||
return attachments
|
return attachments
|
||||||
}
|
}
|
||||||
|
|
||||||
private func getData(for attachment: CompositionAttachment) async throws -> (Data, String) {
|
private func getData(for attachment: CompositionAttachment) async throws -> (Data, UTType) {
|
||||||
return try await withCheckedThrowingContinuation { continuation in
|
return try await withCheckedThrowingContinuation { continuation in
|
||||||
attachment.data.getData { result in
|
attachment.data.getData { result in
|
||||||
switch result {
|
switch result {
|
||||||
|
@ -97,8 +98,8 @@ class PostService: ObservableObject {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func uploadAttachment(data: Data, mimeType: String, description: String?) async throws -> Attachment {
|
private func uploadAttachment(data: Data, utType: UTType, description: String?) async throws -> Attachment {
|
||||||
let formAttachment = FormAttachment(mimeType: mimeType, data: data, fileName: "file")
|
let formAttachment = FormAttachment(mimeType: utType.preferredMIMEType!, data: data, fileName: "file.\(utType.preferredFilenameExtension!)")
|
||||||
let req = Client.upload(attachment: formAttachment, description: description)
|
let req = Client.upload(attachment: formAttachment, description: description)
|
||||||
return try await mastodonController.run(req).0
|
return try await mastodonController.run(req).0
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue