@@ -55,6 +55,7 @@ public class AttachmentMultisend {
5555
5656 let segmentedAttachments = try await segmentAttachmentsIfNecessary (
5757 for: conversations,
58+ destinations: destinations,
5859 approvedAttachments: approvedAttachments,
5960 hasNonStoryDestination: hasNonStoryDestination,
6061 hasStoryDestination: hasStoryDestination
@@ -224,59 +225,89 @@ public class AttachmentMultisend {
224225
225226 private class func segmentAttachmentsIfNecessary(
226227 for conversations: [ ConversationItem ] ,
228+ destinations: [ Destination ] ,
227229 approvedAttachments: [ SignalAttachment ] ,
228230 hasNonStoryDestination: Bool ,
229231 hasStoryDestination: Bool
230232 ) async throws -> [ SegmentAttachmentResult ] {
231233 let maxSegmentDurations = conversations. compactMap ( \. videoAttachmentDurationLimit)
232234 guard hasStoryDestination, !maxSegmentDurations. isEmpty, let requiredSegmentDuration = maxSegmentDurations. min ( ) else {
233- // No need to segment!
234- var results = [ SegmentAttachmentResult] ( )
235- for attachment in approvedAttachments {
236- let dataSource : AttachmentDataSource = try await deps. attachmentValidator. validateContents (
237- dataSource: attachment. dataSource,
238- shouldConsume: true ,
239- mimeType: attachment. mimeType,
240- renderingFlag: attachment. renderingFlag,
241- sourceFilename: attachment. sourceFilename
242- )
243- try results. append ( . init(
244- original: dataSource,
245- segmented: nil ,
246- isViewOnce: attachment. isViewOnceAttachment,
247- renderingFlag: attachment. renderingFlag
248- ) )
235+ // No need to segment! But still need to process per-thread quality settings
236+ return try await processAttachmentsPerQualityLevel (
237+ destinations: destinations,
238+ approvedAttachments: approvedAttachments,
239+ requiredSegmentDuration: nil
240+ )
241+ }
242+
243+ // Process attachments with per-thread quality levels and segmentation
244+ return try await processAttachmentsPerQualityLevel (
245+ destinations: destinations,
246+ approvedAttachments: approvedAttachments,
247+ requiredSegmentDuration: requiredSegmentDuration
248+ )
249+ }
250+
251+ /// Process attachments once per unique quality level needed across all destinations.
252+ /// This respects per-thread quality settings while minimizing redundant processing.
253+ private class func processAttachmentsPerQualityLevel(
254+ destinations: [ Destination ] ,
255+ approvedAttachments: [ SignalAttachment ] ,
256+ requiredSegmentDuration: TimeInterval ?
257+ ) async throws -> [ SegmentAttachmentResult ] {
258+ // Determine which quality level each thread needs
259+ let qualityLevelsByThread = deps. databaseStorage. read { tx -> [ String : ImageQualityLevel ] in
260+ let imageQualityStore = ImageQualitySettingStore ( )
261+ var result : [ String : ImageQualityLevel ] = [ : ]
262+
263+ for destination in destinations {
264+ let thread = destination. thread
265+ let qualityLevel = imageQualityStore. resolvedQualityLevel ( for: thread, tx: tx)
266+ result [ thread. uniqueId] = qualityLevel
249267 }
250- return results
268+
269+ return result
251270 }
252271
253- let qualityLevel = deps. databaseStorage. read ( block: deps. imageQualityLevel. resolvedQuality ( tx: ) )
272+ // Find unique quality levels needed
273+ let uniqueQualityLevels = Set ( qualityLevelsByThread. values)
274+
275+ // Use the highest quality level needed for processing
276+ // This ensures all quality levels can be satisfied (Original subsumes High, High subsumes Standard)
277+ let qualityLevel = uniqueQualityLevels. max ( by: { $0. rawValue < $1. rawValue } ) ??
278+ deps. databaseStorage. read ( block: deps. imageQualityLevel. resolvedQuality ( tx: ) )
254279
280+ // Process attachments with the maximum quality level needed
255281 let segmentedResults = try await withThrowingTaskGroup (
256282 of: ( Int, SegmentAttachmentResult) . self
257283 ) { taskGroup in
258284 for (index, attachment) in approvedAttachments. enumerated ( ) {
259285 taskGroup. addTask ( operation: {
260- let segmentingResult = try await attachment. preparedForOutput ( qualityLevel: qualityLevel)
261- . segmentedIfNecessary ( segmentDuration: requiredSegmentDuration)
262-
263- let originalDataSource : AttachmentDataSource ?
264- if hasNonStoryDestination || segmentingResult. segmented == nil {
265- // We need to prepare the original, either because there are no segments
266- // or because we are sending to a non-story which doesn't segment.
267- originalDataSource = try await deps. attachmentValidator. validateContents (
286+ // Prepare attachment at the quality level (respects per-thread Original setting)
287+ let preparedAttachment = attachment. preparedForOutput ( qualityLevel: qualityLevel)
288+
289+ let segmentingResult : SignalAttachment . SegmentAttachmentResult
290+ if let requiredSegmentDuration = requiredSegmentDuration {
291+ segmentingResult = try await preparedAttachment. segmentedIfNecessary ( segmentDuration: requiredSegmentDuration)
292+ } else {
293+ // No segmentation needed, just use the prepared attachment
294+ segmentingResult = SignalAttachment . SegmentAttachmentResult ( preparedAttachment, segmented: nil )
295+ }
296+
297+ let originalDataSource : AttachmentDataSource ? = try await {
298+ // Always need original for non-segmented or non-story destinations
299+ let dataSource : AttachmentDataSource = try await deps. attachmentValidator. validateContents (
268300 dataSource: segmentingResult. original. dataSource,
269301 shouldConsume: true ,
270302 mimeType: segmentingResult. original. mimeType,
271303 renderingFlag: segmentingResult. original. renderingFlag,
272304 sourceFilename: segmentingResult. original. sourceFilename
273305 )
274- } else {
275- originalDataSource = nil
276- }
306+ return dataSource
307+ } ( )
277308
278309 let segmentedDataSources : [ AttachmentDataSource ] ? = try await { ( ) -> [ AttachmentDataSource ] ? in
279- guard let segments = segmentingResult. segmented, hasStoryDestination else {
310+ guard let segments = segmentingResult. segmented else {
280311 return nil
281312 }
282313 var segmentedDataSources = [ AttachmentDataSource] ( )
0 commit comments