@@ -471,12 +471,25 @@ class SecureImageRepository(
471471 var tempFile: File ? = null
472472
473473 return try {
474+ if (! video.videoFile.exists()) {
475+ Timber .e(" Video file does not exist: ${video.videoFile.absolutePath} " )
476+ return null
477+ }
478+
479+ Timber .d(" Video file size: ${video.videoFile.length()} bytes" )
480+
474481 decryptor = streamingScheme.createStreamingDecryptor(video.videoFile)
475482
476483 // Create a temporary file with decrypted video content
477- // We need enough data for MediaMetadataRetriever to extract a frame
478- // Typically the first few MB are sufficient
479- val bytesToRead = minOf(decryptor.totalSize, THUMBNAIL_EXTRACTION_BYTES )
484+ // MP4 files often have moov atom at the end, so we need enough data
485+ // For smaller videos, read the whole file to ensure we get the moov atom
486+ val bytesToRead = if (decryptor.totalSize <= SMALL_VIDEO_THRESHOLD ) {
487+ Timber .d(" Small video, reading entire file for thumbnail" )
488+ decryptor.totalSize
489+ } else {
490+ Timber .d(" Large video, reading first $THUMBNAIL_EXTRACTION_BYTES bytes" )
491+ minOf(decryptor.totalSize, THUMBNAIL_EXTRACTION_BYTES )
492+ }
480493 val buffer = ByteArray (bytesToRead.toInt())
481494 val bytesRead = decryptor.read(0 , buffer, 0 , buffer.size)
482495
@@ -508,11 +521,30 @@ class SecureImageRepository(
508521 return try {
509522 MediaMetadataRetriever ().use { retriever ->
510523 retriever.setDataSource(videoFile.absolutePath)
511- // Get frame at 1 second (or first frame if video is shorter)
512- retriever.getFrameAtTime(1_000_000 , MediaMetadataRetriever .OPTION_CLOSEST_SYNC )
524+
525+ // Try frame at 1 second first
526+ var frame = retriever.getFrameAtTime(1_000_000 , MediaMetadataRetriever .OPTION_CLOSEST_SYNC )
527+
528+ // If that fails, try frame at 0 (first frame)
529+ if (frame == null ) {
530+ Timber .d(" Frame at 1s not found, trying first frame" )
531+ frame = retriever.getFrameAtTime(0 , MediaMetadataRetriever .OPTION_CLOSEST_SYNC )
532+ }
533+
534+ // Last resort: try any frame
535+ if (frame == null ) {
536+ Timber .d(" Specific frames not found, trying any frame" )
537+ frame = retriever.frameAtTime
538+ }
539+
540+ if (frame == null ) {
541+ Timber .w(" Could not extract any frame from video: ${videoFile.name} " )
542+ }
543+
544+ frame
513545 }
514546 } catch (e: Exception ) {
515- Timber .e(e, " Failed to extract video frame" )
547+ Timber .e(e, " Failed to extract video frame from: ${videoFile.name} " )
516548 null
517549 }
518550 }
@@ -741,8 +773,11 @@ class SecureImageRepository(
741773 const val THUMBNAILS_DIR = " .thumbnails"
742774 const val MAX_DECOY_PHOTOS = 10
743775
744- // Amount of video data to decrypt for thumbnail extraction (5MB should be enough for moov atom)
745- private const val THUMBNAIL_EXTRACTION_BYTES = 5L * 1024 * 1024
776+ // Amount of video data to decrypt for thumbnail extraction
777+ // MP4 files may have moov atom at the end, so we need to read more data
778+ // For videos under 50MB, we read the whole file; otherwise we read 20MB
779+ private const val THUMBNAIL_EXTRACTION_BYTES = 20L * 1024 * 1024
780+ private const val SMALL_VIDEO_THRESHOLD = 50L * 1024 * 1024
746781
747782 internal fun generateCopyName (dir : File , originalName : String ): String {
748783 val base = originalName.substringBeforeLast(" ." )
0 commit comments