ImageUpdate.php 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175
  1. <?php
  2. namespace App\Jobs\ImageOptimizePipeline;
  3. use App\Jobs\MediaPipeline\MediaStoragePipeline;
  4. use App\Media;
  5. use Illuminate\Bus\Queueable;
  6. use Illuminate\Contracts\Queue\ShouldQueue;
  7. use Illuminate\Foundation\Bus\Dispatchable;
  8. use Illuminate\Queue\InteractsWithQueue;
  9. use Illuminate\Queue\SerializesModels;
  10. use Illuminate\Support\Str;
  11. use ImageOptimizer;
  12. use Log;
  13. use Storage;
  14. class ImageUpdate implements ShouldQueue
  15. {
  16. use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
  17. protected $media;
  18. protected $protectedMimes = [
  19. 'image/jpeg',
  20. 'image/png',
  21. 'image/webp',
  22. 'image/avif',
  23. ];
  24. /**
  25. * Delete the job if its models no longer exist.
  26. *
  27. * @var bool
  28. */
  29. public $deleteWhenMissingModels = true;
  30. /**
  31. * Create a new job instance.
  32. *
  33. * @return void
  34. */
  35. public function __construct(Media $media)
  36. {
  37. $this->media = $media;
  38. }
  39. /**
  40. * Execute the job.
  41. *
  42. * @return void
  43. */
  44. public function handle()
  45. {
  46. $media = $this->media;
  47. if (! $media) {
  48. return;
  49. }
  50. $disk = Storage::disk(config('filesystems.default'));
  51. $localFs = config('filesystems.default') === 'local';
  52. $mediaPath = $media->media_path;
  53. $fileSize = 0;
  54. if ($localFs) {
  55. $path = storage_path('app/'.$media->media_path);
  56. $thumbPath = storage_path('app/'.$media->thumbnail_path);
  57. if (! is_file($path)) {
  58. return;
  59. }
  60. $mediaPath = $path;
  61. } else {
  62. if (! $disk->exists($media->media_path)) {
  63. return;
  64. }
  65. }
  66. if ((bool) config_cache('pixelfed.optimize_image') && $localFs) {
  67. if (in_array($media->mime, $this->protectedMimes) == true) {
  68. try {
  69. $thumbPath = storage_path('app/'.$media->thumbnail_path);
  70. if (file_exists($thumbPath)) {
  71. ImageOptimizer::optimize($thumbPath);
  72. }
  73. if (! $media->skip_optimize) {
  74. $mediaPath = storage_path('app/'.$media->media_path);
  75. ImageOptimizer::optimize($mediaPath);
  76. }
  77. } catch (\Exception $e) {
  78. if (config('app.dev_log')) {
  79. Log::error('Image optimization failed: '.$e->getMessage());
  80. }
  81. }
  82. }
  83. } elseif ((bool) config_cache('pixelfed.optimize_image') && ! $localFs) {
  84. if (in_array($media->mime, $this->protectedMimes) == true) {
  85. $this->optimizeRemoteImages($media, $disk);
  86. }
  87. }
  88. try {
  89. $photo_size = $this->getFileSize($media->media_path);
  90. $thumb_size = $media->thumbnail_path ? $this->getFileSize($media->thumbnail_path) : 0;
  91. $total = ($photo_size + $thumb_size);
  92. $media->size = $total;
  93. $media->save();
  94. } catch (\Exception $e) {
  95. if (config('app.dev_log')) {
  96. Log::error('Failed to calculate media sizes: '.$e->getMessage());
  97. }
  98. }
  99. MediaStoragePipeline::dispatch($media);
  100. }
  101. protected function getFileSize($path)
  102. {
  103. $disk = Storage::disk(config('filesystems.default'));
  104. $localFs = config('filesystems.default') === 'local';
  105. if (! $path || empty($path)) {
  106. return 0;
  107. }
  108. if ($localFs) {
  109. return filesize(storage_path('app/'.$path)) ?? 0;
  110. } else {
  111. return $disk->size($path) ?? 0;
  112. }
  113. }
  114. /**
  115. * Optimize images stored on remote storage (S3, etc)
  116. */
  117. protected function optimizeRemoteImages($media, $disk)
  118. {
  119. try {
  120. $tempDir = sys_get_temp_dir().'/pixelfed_optimize_'.Str::random(18);
  121. mkdir($tempDir, 0755, true);
  122. if ($media->thumbnail_path) {
  123. $tempThumb = $tempDir.'/thumb_'.basename($media->thumbnail_path);
  124. $thumbContents = $disk->get($media->thumbnail_path);
  125. file_put_contents($tempThumb, $thumbContents);
  126. ImageOptimizer::optimize($tempThumb);
  127. $disk->put($media->thumbnail_path, file_get_contents($tempThumb));
  128. unlink($tempThumb);
  129. }
  130. if (! $media->skip_optimize) {
  131. $tempMedia = $tempDir.'/media_'.basename($media->media_path);
  132. $mediaContents = $disk->get($media->media_path);
  133. file_put_contents($tempMedia, $mediaContents);
  134. ImageOptimizer::optimize($tempMedia);
  135. $disk->put($media->media_path, file_get_contents($tempMedia));
  136. unlink($tempMedia);
  137. }
  138. rmdir($tempDir);
  139. } catch (\Exception $e) {
  140. if (isset($tempDir) && is_dir($tempDir)) {
  141. array_map('unlink', glob($tempDir.'/*'));
  142. rmdir($tempDir);
  143. }
  144. if (config('app.dev_log')) {
  145. Log::error('Remote image optimization failed: '.$e->getMessage());
  146. }
  147. }
  148. }
  149. }