Changeset View
Changeset View
Standalone View
Standalone View
native/media/media-utils.js
// @flow | // @flow | ||||
import invariant from 'invariant'; | import invariant from 'invariant'; | ||||
import { Image } from 'react-native'; | import { Image } from 'react-native'; | ||||
import { pathFromURI, sanitizeFilename } from 'lib/media/file-utils.js'; | import { pathFromURI, sanitizeFilename } from 'lib/media/file-utils.js'; | ||||
import type { | import type { | ||||
Dimensions, | Dimensions, | ||||
MediaMissionStep, | MediaMissionStep, | ||||
MediaMissionFailure, | MediaMissionFailure, | ||||
NativeMediaSelection, | NativeMediaSelection, | ||||
GenerateThumbhashMediaMissionStep, | |||||
} from 'lib/types/media-types.js'; | } from 'lib/types/media-types.js'; | ||||
import { getMessageForException } from 'lib/utils/errors.js'; | |||||
import { fetchFileInfo } from './file-utils.js'; | import { fetchFileInfo } from './file-utils.js'; | ||||
import { processImage } from './image-utils.js'; | import { processImage } from './image-utils.js'; | ||||
import { saveMedia } from './save-media.js'; | import { saveMedia } from './save-media.js'; | ||||
import { processVideo } from './video-utils.js'; | import { processVideo } from './video-utils.js'; | ||||
import { generateThumbHash } from '../utils/thumbhash-module.js'; | |||||
type MediaProcessConfig = { | type MediaProcessConfig = { | ||||
+hasWiFi: boolean, | +hasWiFi: boolean, | ||||
// Blocks return until we can confirm result has the correct MIME | // Blocks return until we can confirm result has the correct MIME | ||||
+finalFileHeaderCheck?: boolean, | +finalFileHeaderCheck?: boolean, | ||||
+onTranscodingProgress?: (percent: number) => void, | +onTranscodingProgress?: (percent: number) => void, | ||||
}; | }; | ||||
type SharedMediaResult = { | type SharedMediaResult = { | ||||
+success: true, | +success: true, | ||||
+uploadURI: string, | +uploadURI: string, | ||||
+shouldDisposePath: ?string, | +shouldDisposePath: ?string, | ||||
+filename: string, | +filename: string, | ||||
+mime: string, | +mime: string, | ||||
+dimensions: Dimensions, | +dimensions: Dimensions, | ||||
+thumbHash: ?string, | |||||
}; | }; | ||||
export type MediaResult = | export type MediaResult = | ||||
| { +mediaType: 'photo', ...SharedMediaResult } | | { +mediaType: 'photo', ...SharedMediaResult } | ||||
| { | | { | ||||
+mediaType: 'video', | +mediaType: 'video', | ||||
...SharedMediaResult, | ...SharedMediaResult, | ||||
+uploadThumbnailURI: string, | +uploadThumbnailURI: string, | ||||
+loop: boolean, | +loop: boolean, | ||||
▲ Show 20 Lines • Show All 43 Lines • ▼ Show 20 Lines | |||||
): Promise<$ReadOnlyArray<MediaMissionStep>> { | ): Promise<$ReadOnlyArray<MediaMissionStep>> { | ||||
let initialURI = null, | let initialURI = null, | ||||
uploadURI = null, | uploadURI = null, | ||||
uploadThumbnailURI = null, | uploadThumbnailURI = null, | ||||
dimensions = selection.dimensions, | dimensions = selection.dimensions, | ||||
mediaType = null, | mediaType = null, | ||||
mime = null, | mime = null, | ||||
loop = false, | loop = false, | ||||
resultReturned = false; | resultReturned = false, | ||||
thumbHash = null; | |||||
const returnResult = (failure?: MediaMissionFailure) => { | const returnResult = (failure?: MediaMissionFailure) => { | ||||
invariant( | invariant( | ||||
!resultReturned, | !resultReturned, | ||||
'returnResult called twice in innerProcessMedia', | 'returnResult called twice in innerProcessMedia', | ||||
); | ); | ||||
resultReturned = true; | resultReturned = true; | ||||
if (failure) { | if (failure) { | ||||
sendResult(failure); | sendResult(failure); | ||||
Show All 13 Lines | if (mediaType === 'video') { | ||||
uploadURI, | uploadURI, | ||||
uploadThumbnailURI, | uploadThumbnailURI, | ||||
shouldDisposePath, | shouldDisposePath, | ||||
filename, | filename, | ||||
mime, | mime, | ||||
mediaType, | mediaType, | ||||
dimensions, | dimensions, | ||||
loop, | loop, | ||||
thumbHash, | |||||
}); | }); | ||||
} else { | } else { | ||||
sendResult({ | sendResult({ | ||||
success: true, | success: true, | ||||
uploadURI, | uploadURI, | ||||
shouldDisposePath, | shouldDisposePath, | ||||
filename, | filename, | ||||
mime, | mime, | ||||
mediaType, | mediaType, | ||||
dimensions, | dimensions, | ||||
thumbHash, | |||||
}); | }); | ||||
} | } | ||||
}; | }; | ||||
const steps = [], | const steps = [], | ||||
completeBeforeFinish = []; | completeBeforeFinish = []; | ||||
const finish = async (failure?: MediaMissionFailure) => { | const finish = async (failure?: MediaMissionFailure) => { | ||||
if (!resultReturned) { | if (!resultReturned) { | ||||
▲ Show 20 Lines • Show All 64 Lines • ▼ Show 20 Lines | if (!videoResult.success) { | ||||
return await finish(videoResult); | return await finish(videoResult); | ||||
} | } | ||||
({ | ({ | ||||
uri: uploadURI, | uri: uploadURI, | ||||
thumbnailURI: uploadThumbnailURI, | thumbnailURI: uploadThumbnailURI, | ||||
mime, | mime, | ||||
dimensions, | dimensions, | ||||
loop, | loop, | ||||
thumbHash, | |||||
} = videoResult); | } = videoResult); | ||||
} else if (mediaType === 'photo') { | } else if (mediaType === 'photo') { | ||||
const { steps: imageSteps, result: imageResult } = await processImage({ | const { steps: imageSteps, result: imageResult } = await processImage({ | ||||
uri: initialURI, | uri: initialURI, | ||||
dimensions, | dimensions, | ||||
mime, | mime, | ||||
fileSize, | fileSize, | ||||
orientation, | orientation, | ||||
}); | }); | ||||
steps.push(...imageSteps); | steps.push(...imageSteps); | ||||
if (!imageResult.success) { | if (!imageResult.success) { | ||||
return await finish(imageResult); | return await finish(imageResult); | ||||
} | } | ||||
({ uri: uploadURI, mime, dimensions } = imageResult); | ({ uri: uploadURI, mime, dimensions, thumbHash } = imageResult); | ||||
} else { | } else { | ||||
invariant(false, `unknown mediaType ${mediaType}`); | invariant(false, `unknown mediaType ${mediaType}`); | ||||
} | } | ||||
if (uploadURI === initialURI) { | if (uploadURI === initialURI) { | ||||
return await finish(); | return await finish(); | ||||
} | } | ||||
Show All 26 Lines | return new Promise((resolve, reject) => { | ||||
Image.getSize( | Image.getSize( | ||||
uri, | uri, | ||||
(width: number, height: number) => resolve({ height, width }), | (width: number, height: number) => resolve({ height, width }), | ||||
reject, | reject, | ||||
); | ); | ||||
}); | }); | ||||
} | } | ||||
export { processMedia, getDimensions }; | async function generateThumbhashStep( | ||||
uri: string, | |||||
): Promise<GenerateThumbhashMediaMissionStep> { | |||||
let thumbHash, exceptionMessage; | |||||
try { | |||||
thumbHash = await generateThumbHash(uri); | |||||
} catch (err) { | |||||
exceptionMessage = getMessageForException(err); | |||||
} | |||||
return { | |||||
step: 'generate_thumbhash', | |||||
success: !!thumbHash && !exceptionMessage, | |||||
exceptionMessage, | |||||
thumbHash, | |||||
}; | |||||
} | |||||
export { processMedia, getDimensions, generateThumbhashStep }; |