Skip to content

Commit f014f1a

Browse files
lsakeeSangwook123
authored andcommitted
[feat] #62 upload jpg to S3
1 parent bd1c161 commit f014f1a

File tree

3 files changed

+62
-92
lines changed

3 files changed

+62
-92
lines changed

feature/upload/src/main/java/com/record/upload/UploadViewModel.kt

+7-5
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,8 @@ import android.util.Log
55
import androidx.lifecycle.viewModelScope
66
import com.record.ui.base.BaseViewModel
77
import com.record.upload.extension.GalleryVideo
8-
import com.record.upload.extension.extractAndUploadThumbnail
9-
import com.record.upload.extension.reencodeVideo
108
import com.record.upload.extension.uploadFileToS3PresignedUrl
9+
import com.record.upload.extension.uploadFileToS3ThumbnailPresignedUrl
1110
import com.record.upload.repository.UploadRepository
1211
import dagger.hilt.android.lifecycle.HiltViewModel
1312
import kotlinx.coroutines.launch
@@ -38,12 +37,15 @@ class UploadViewModel @Inject constructor(
3837
}
3938
}
4039

41-
fun uploadVideoToS3Bucket(file: File) =
40+
fun uploadVideoToS3Bucket(context: Context,file: File) =
4241
viewModelScope.launch {
43-
Log.d("outputfile", "$file ")
44-
Log.d("outputfile", "${uiState.value.bucketUrl} ")
4542
uploadFileToS3PresignedUrl(uiState.value.bucketUrl, file) { success, message ->
4643
println(message)
44+
Log.d("messageFile", "$message ")
45+
}
46+
uploadFileToS3ThumbnailPresignedUrl(context,uiState.value.thumbnailUrl, file) { success, message ->
47+
println(message)
48+
Log.d("messageThumbnail", "$message ")
4749
}
4850
}
4951

feature/upload/src/main/java/com/record/upload/VideoPickerScreen.kt

+1-2
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,6 @@ import com.record.upload.component.bottomsheet.DefinedContentBottomSheet
6767
import com.record.upload.component.bottomsheet.SelectedVideoBottomSheet
6868
import com.record.upload.extension.GalleryVideo
6969
import com.record.upload.extension.getAllVideos
70-
import com.record.upload.extension.getVideoEncodingInfo
7170
import kotlinx.coroutines.flow.collectLatest
7271
import kotlinx.coroutines.launch
7372
import timber.log.Timber
@@ -96,7 +95,7 @@ fun VideoPickerRoute(
9695
onClickContentChip = viewModel::setSelectedList,
9796
setVideo = viewModel::setVideo,
9897
uploadVideoS3Bucket = {
99-
viewModel.uploadVideoToS3Bucket(it)
98+
viewModel.uploadVideoToS3Bucket(context,it)
10099
},
101100
)
102101
}

feature/upload/src/main/java/com/record/upload/extension/UploadExtension.kt

+54-85
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package com.record.upload.extension
22

33
import android.content.ContentUris
44
import android.content.Context
5+
import android.graphics.Bitmap
56
import android.media.MediaMetadataRetriever
67
import android.net.Uri
78
import android.provider.MediaStore
@@ -12,8 +13,6 @@ import com.abedelazizshe.lightcompressorlibrary.VideoQuality
1213
import com.abedelazizshe.lightcompressorlibrary.config.Configuration
1314
import com.abedelazizshe.lightcompressorlibrary.config.SaveLocation
1415
import com.abedelazizshe.lightcompressorlibrary.config.SharedStorageConfiguration
15-
import com.arthenica.mobileffmpeg.Config
16-
import com.arthenica.mobileffmpeg.FFmpeg
1716
import kotlinx.coroutines.CoroutineScope
1817
import kotlinx.coroutines.Dispatchers
1918
import kotlinx.coroutines.launch
@@ -22,6 +21,7 @@ import okhttp3.OkHttpClient
2221
import okhttp3.Request
2322
import okhttp3.RequestBody
2423
import java.io.File
24+
import java.io.FileOutputStream
2525
import java.io.IOException
2626

2727
fun getAllVideos(
@@ -166,52 +166,8 @@ fun formatDuration(durationMillis: Long): String {
166166
return String.format("%d:%02d", minutes, seconds)
167167
}
168168

169-
fun getVideoEncodingInfo(context: Context, videoFile: File) {
170-
val retriever = MediaMetadataRetriever()
171-
try {
172-
retriever.setDataSource(context, Uri.fromFile(videoFile))
173-
val mimeType = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_MIMETYPE)
174-
val duration = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION)
175-
val width = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH)
176-
val height = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT)
177-
val bitrate = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_BITRATE)
178-
179-
Log.d("VideoInfo", "MIME Type: $mimeType")
180-
Log.d("VideoInfo", "Duration: $duration ms")
181-
Log.d("VideoInfo", "Width: $width pixels")
182-
Log.d("VideoInfo", "Height: $height pixels")
183-
Log.d("VideoInfo", "Bitrate: $bitrate bps")
184-
} catch (e: Exception) {
185-
e.printStackTrace()
186-
} finally {
187-
retriever.release()
188-
}
189-
}
190-
191-
fun reencodeVideo(inputFile: File, outputFile: File, callback: (Boolean, String) -> Unit) {
192-
val command = arrayOf(
193-
"-i", inputFile.absolutePath,
194-
"-c:v", "libx264", // 비디오 코덱 설정
195-
"-b:v", "1000k", // 비디오 비트레이트 설정
196-
"-c:a", "aac", // 오디오 코덱 설정
197-
"-b:a", "192k", // 오디오 비트레이트 설정
198-
"-movflags", "faststart", // 웹 스트리밍을 위해 메타데이터를 파일의 시작 부분에 배치
199-
outputFile.absolutePath
200-
)
201-
Config.setLogLevel(Config.getLogLevel())
202-
Config.enableLogCallback { logMessage ->
203-
Log.d("FFmpegLog", logMessage.text)
204-
}
205-
FFmpeg.executeAsync(command) { executionId, returnCode ->
206-
if (returnCode == Config.RETURN_CODE_SUCCESS) {
207-
callback(true, "Re-encoding successful")
208-
} else {
209-
callback(false, "Re-encoding failed with return code $returnCode")
210-
}
211-
}
212-
}
213-
214169
fun uploadFileToS3PresignedUrl(presignedUrl: String, file: File, callback: (Boolean, String) -> Unit) {
170+
215171
val client = OkHttpClient()
216172
val mediaType = "application/octet-stream".toMediaTypeOrNull()
217173
val requestBody = RequestBody.create(mediaType, file)
@@ -236,49 +192,62 @@ fun uploadFileToS3PresignedUrl(presignedUrl: String, file: File, callback: (Bool
236192
})
237193
}
238194

239-
fun extractAndUploadThumbnail(videoFile: File, thumbnailFile: File, presignedUrl: String, callback: (Boolean, String) -> Unit) {
240-
val extractCommand = arrayOf(
241-
"ffmpeg",
242-
"-i", videoFile.absolutePath,
243-
"-ss", "00:00:01.000",
244-
"-vframes", "1",
245-
thumbnailFile.absolutePath
246-
)
195+
fun uploadFileToS3ThumbnailPresignedUrl(context: Context,presignedUrl: String, file: File, callback: (Boolean, String) -> Unit) {
196+
val videoPath = file.absolutePath
197+
val outputImagePath = File(context.cacheDir,"${ file.name }.jpg")
198+
getVideoFrameAt1Sec(videoPath, outputImagePath.absolutePath)
199+
val client = OkHttpClient()
200+
val mediaType = "application/octet-stream".toMediaTypeOrNull()
201+
val requestBody = RequestBody.create(mediaType, outputImagePath)
247202

248-
try {
249-
val process = ProcessBuilder(*extractCommand).start()
250-
process.waitFor()
251-
if (process.exitValue() == 0) {
252-
val client = OkHttpClient()
253-
val mediaType = "image/jpeg".toMediaTypeOrNull()
254-
val requestBody = RequestBody.create(mediaType, thumbnailFile)
203+
val request = Request.Builder()
204+
.url(presignedUrl)
205+
.put(requestBody)
206+
.build()
255207

256-
val request = Request.Builder()
257-
.url(presignedUrl)
258-
.put(requestBody)
259-
.build()
208+
client.newCall(request).enqueue(object : okhttp3.Callback {
209+
override fun onFailure(call: okhttp3.Call, e: IOException) {
210+
callback(false, "Upload failed: ${e.message}")
211+
}
260212

261-
client.newCall(request).enqueue(object : okhttp3.Callback {
262-
override fun onFailure(call: okhttp3.Call, e: IOException) {
263-
callback(false, "Upload failed: ${e.message}")
264-
}
213+
override fun onResponse(call: okhttp3.Call, response: okhttp3.Response) {
214+
if (response.isSuccessful) {
215+
callback(true, "Upload successful")
216+
} else {
217+
callback(false, "Upload failed: ${response.message}")
218+
}
219+
}
220+
})
221+
}
222+
fun getVideoFrameAt1Sec(videoPath: String, outputImagePath: String) {
223+
val retriever = MediaMetadataRetriever()
224+
try {
225+
retriever.setDataSource(videoPath)
226+
val timeUs = 1 * 1000000 // 1초를 마이크로초로 변환
227+
val bitmap: Bitmap? = retriever.getFrameAtTime(timeUs.toLong(), MediaMetadataRetriever.OPTION_CLOSEST_SYNC)
265228

266-
override fun onResponse(call: okhttp3.Call, response: okhttp3.Response) {
267-
if (response.isSuccessful) {
268-
callback(true, "Upload successful")
269-
} else {
270-
callback(false, "Upload failed: ${response.message}")
271-
}
272-
}
273-
})
274-
} else {
275-
callback(false, "Thumbnail extraction failed with return code ${process.exitValue()}")
229+
if (bitmap != null) {
230+
saveBitmapAsJpeg(bitmap, outputImagePath)
276231
}
277-
} catch (e: IOException) {
278-
e.printStackTrace()
279-
callback(false, "Thumbnail extraction failed with exception: ${e.message}")
280-
} catch (e: InterruptedException) {
232+
} catch (e: Exception) {
281233
e.printStackTrace()
282-
callback(false, "Thumbnail extraction failed with exception: ${e.message}")
234+
} finally {
235+
retriever.release()
283236
}
284237
}
238+
239+
fun saveBitmapAsJpeg(bitmap: Bitmap, outputPath: String) {
240+
var out: FileOutputStream? = null
241+
try {
242+
out = FileOutputStream(outputPath)
243+
bitmap.compress(Bitmap.CompressFormat.JPEG, 90, out)
244+
} catch (e: IOException) {
245+
e.printStackTrace()
246+
} finally {
247+
try {
248+
out?.close()
249+
} catch (e: IOException) {
250+
e.printStackTrace()
251+
}
252+
}
253+
}

0 commit comments

Comments
 (0)