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:
Shadowfacts 2022-06-20 00:00:34 -04:00
parent 9fa352d4f8
commit 037b717e60
2 changed files with 16 additions and 15 deletions

View File

@ -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)))
} }

View File

@ -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
} }