💭

深掘り spatie/laravel-medialibrary - 画像の変換

2025/02/12に公開

https://zenn.dev/catatsumuri/articles/4713e1c974d6a8

の続きっちゃ続き

今回利用するもののdeploy

弊githubリポジトリよりspatie-lib-demoブランチを...

git clone -b spatie-lib-demo https://github.com/catatsumuri/laravel-inertia-stack-ja.git

今回はlaravel sail...ではなく、すなわちdockerとかの環境ではない素のdebianに導入した。ライブラリーは最低限しか入っていない状態でのテスト。

画像のconversion

これは

config/media-library.php
    /*
     * The engine that should perform the image conversions.
     * Should be either `gd` or `imagick`.
     */
    'image_driver' => env('IMAGE_DRIVER', 'gd'),

の設定でわかるように、gdimagickを導入する必要がある。初期状態ではgdが選ばれるのでこの段階でgdドライバーがないとエラーになる。

なお、ここではgdimagickも検証してみよう

# apt install php-gd php-imagick
Reading package lists... Done
Building dependency tree... Done
Reading state information... Done
The following additional packages will be installed:
  fonts-droid-fallback fonts-noto-mono fonts-urw-base35 ghostscript gsfonts
  imagemagick-6-common libavahi-client3 libavahi-common-data libavahi-common3
  libcups2 libfftw3-double3 libgs-common libgs10 libgs10-common libidn12
  libijs-0.35 libjbig2dec0 liblcms2-2 liblqr-1-0 libltdl7
  libmagickcore-6.q16-6 libmagickwand-6.q16-6 libopenjp2-7 libpaper-utils
  libpaper1 libwebpdemux2 libwebpmux3 php8.2-gd php8.2-imagick poppler-data

さらに exif パッケージも導入し、調査してみる

exifの状態

このようにいかにも位置情報が付いていそうな鉄っぽい画像をアップロードした

# find storage/app/public/3/
storage/app/public/3/
storage/app/public/3/conversions
storage/app/public/3/conversions/2024年06月02日_14時58分32秒-thumb.jpg
storage/app/public/3/conversions/2024年06月02日_14時58分32秒-original.jpg
storage/app/public/3/2024年06月02日_14時58分32秒.jpg

現在このように id=3 という状態でアップロードされており、exifを検証すると

$ exif storage/app/public/3/2024年06月02日_14時58分32秒.jpg
EXIF tags in 'storage/app/public/3/2024年06月02日_14時58分32秒.jpg' ('Motorola' byte order):
--------------------+----------------------------------------------------------
Tag                 |Value
--------------------+----------------------------------------------------------
Manufacturer        |Apple
Model               |iPhone 12 mini
Orientation         |Top-left
X-Resolution        |300
Y-Resolution        |300
Resolution Unit     |Inch
Software            |17.1.2
Date and Time       |2024:06:02 14:58:32
YCbCr Positioning   |Centered
Compression         |JPEG compression
X-Resolution        |72
Y-Resolution        |72
Resolution Unit     |Inch
Exposure Time       |1/284 sec.
F-Number            |f/1.6
Exposure Program    |Normal program
ISO Speed Ratings   |32
Exif Version        |Exif Version 2.32
Date and Time (Origi|2024:06:02 14:58:32
Date and Time (Digit|2024:06:02 14:58:32
Offset Time For Date|+09:00
Offset Time For Date|+09:00
Offset Time For Date|+09:00
Components Configura|Y Cb Cr -
Shutter Speed       |8.15 EV (1/284 sec.)
Aperture            |1.36 EV (f/1.6)
Brightness          |6.47 EV (304.51 cd/m^2)
Exposure Bias       |0.00 EV
Metering Mode       |Pattern
Flash               |Flash did not fire, compulsory flash mode
Focal Length        |4.2 mm
Subject Area        |Within rectangle (width 2208, height 1387) around (x,y) =
Maker Note          |1535 bytes undefined data
Sub-second Time (Ori|898
Sub-second Time (Dig|898
FlashPixVersion     |FlashPix Version 1.0
Color Space         |Uncalibrated
Pixel X Dimension   |1512
Pixel Y Dimension   |2016
Sensing Method      |One-chip color area sensor
Scene Type          |Directly photographed
Exposure Mode       |Auto exposure
White Balance       |Auto white balance
Focal Length in 35mm|26
Scene Capture Type  |Standard
Lens Specification  |1.550000, 4.2, 1.6, 2.4
Lens Make           |Apple
Lens Model          |iPhone 12 mini back dual wide camera 4.2mm f/1.6
Composite Image     |2
North or South Latit|N
Latitude            |35, 10, 13.48
East or West Longitu|E
Longitude           |136, 52, 54.01
Altitude Reference  |Sea level
Altitude            |2.23306
GPS Time (Atomic Clo|05:58:31.00
Speed Unit          |K
Speed of GPS Receive|0.6900000
GPS Image Direction |T
GPS Image Direction |34.4986
Reference for Bearin|T
Bearing of Destinati|34.4986
GPS Date            |2024:06:02
GPS Horizontal Posit|8.12382
--------------------+----------------------------------------------------------
EXIF data contains a thumbnail (11514 bytes).

このようになっている。ここでセンシティブな値と言えばもちろん

North or South Latit|N
Latitude            |35, 10, 13.48
East or West Longitu|E
Longitude           |136, 52, 54.01

この辺がとりわけセンシティブな値であろう。

% exif storage/app/public/3/conversions/2024年06月02日_14時58分32秒-original.jpg
Corrupt data
The data provided does not follow the specification.
ExifLoader: The data supplied does not seem to contain EXIF data.

originalとして保存した画像からはこれがトリミングされているというか、GDにはexifを引き継ぐ機能がない(んだと思う、こういう知識は実際専門外だけど)

imagickで同様の処理をするとexifは残留する

config/media-library.php
    /*
     * The engine that should perform the image conversions.
     * Should be either `gd` or `imagick`.
     */
    // 'image_driver' => env('IMAGE_DRIVER', 'gd'),
    'image_driver' => env('IMAGE_DRIVER', 'imagick'),

などとして同様の処理を施してみると

exif storage/app/public/4/conversions/2024年06月02日_14時58分32秒-original.jpg
EXIF tags in 'storage/app/public/4/conversions/2024年06月02日_14時58分32秒-original.jpg' ('Motorola' byte order):
--------------------+----------------------------------------------------------
Tag                 |Value
--------------------+----------------------------------------------------------
Manufacturer        |Apple
Model               |iPhone 12 mini
Orientation         |Top-left
X-Resolution        |300
....<省略

このようにconversionした画像にexifが残る。

jpegoptimを導入して自動最適化機能によりexifを削除するようにする

これを解消するには jpegoptimを導入するだけ。これでspatie/laravel-medialibraryはこのコマンドを探して自動起動してexifを取り除いてくる

apt install jpegoptim


exifが消滅した

この辺に関しては https://spatie.be/docs/laravel-medialibrary/v11/converting-images/optimizing-converted-images を参照。

いずれにしたって、installドキュメントなどで外部ツールに依存している事を明確にして正しくデプロイするようにする。いくつもインスタンスをインストールする場合はこういうのを人力でやると失敗いしそうですね。

画像のリサイズ方法

たとえば、この鉄の画像は1512x2016という解像度だ。これを何も考えず600x600指定にすると

        $this->addMediaConversion('thumb')
              ->width(600)
              ->height(600)
              ->nonQueued();


450x600

このように縦横をうまいこと合わせてくれるんだけど、ここでのコードの意図とはちょっと違ってきますよね。600,600にジャストフィットするようにするには

use Spatie\Image\Enums\Fit;
// ...
        $this->addMediaConversion('thumb')
             ->fit(Fit::Crop, 600, 600)
             ->nonQueued();

とすると


600x600

となる。このあたりは以下のドキュメントにある

https://spatie.be/docs/image/v3/image-manipulations/resizing-images#content-fit

いろいろやる

fit(Fit::Crop, 300, 600)

$this->addMediaConversion('thumb')
     ->fit(Fit::Crop, 300, 600)
     ->nonQueued();


300x600、かなり縦長になった

fit(Fit::Contain, 300, 300)

$this->addMediaConversion('thumb')
     ->fit(Fit::Contain, 300, 300)
     ->nonQueued();


アスペクト比を維持しつつ最大サイズに収める(余白あり)。この場合縦横どちらか最大300

fit(Fit::Max, 400, 400)

$this->addMediaConversion('thumb')
     ->fit(Fit::Max, 400, 400)
     ->nonQueued();


Contain と同じだが、小さい画像は拡大しない。これは例が悪い(元が小さくない)のでよくわからんすね。試してみてください

fit(Fit::Fill, 600, 600)

$this->addMediaConversion('thumb')
     ->fit(Fit::Fill, 600, 600)
     ->background('#ffffff')
     ->nonQueued();


余白を指定色で埋める

fit(Fit::Stretch, 600, 600)

$this->addMediaConversion('thumb')
     ->fit(Fit::Stretch, 600, 600)
     ->nonQueued();


強引に述ばす。ほぼ使えないと思う

いずれにせよ、用途にあった最適なリサイズ方法を選択する必要がある。avatarなどはスクウェアがいいでしょうしね。

PDFのサムネイル

ここではPDFをアップロードする。前回のコードではvalidationを何も考えてないのでアップロードできる。この段階ではもちろん

このようにサムネイルを生成できないのでエラーになっている。しかしspatieではpdfのサムネイルを生成する事が可能である。

apt install imagemagick ghostscript

する。しかし

となった場合は

  <!-- disable ghostscript format types -->
  <!--
  <policy domain="coder" rights="none" pattern="PS" />
  <policy domain="coder" rights="none" pattern="PS2" />
  <policy domain="coder" rights="none" pattern="PS3" />
  <policy domain="coder" rights="none" pattern="EPS" />
  <policy domain="coder" rights="none" pattern="PDF" />
  <policy domain="coder" rights="none" pattern="XPS" />
  -->

この辺をコメントアウトしてwebサーバーを再起動すると読めるようになるかも。 サンプルのpdf をアップロードすると

なお、ページ指定なども可能である。っていっても2ページ目を指定して2ページ目がなかったらどうなるんでしょ。面倒なのでやってません。

動画ファイルからのサムネイル生成

php-ffmpeg/php-ffmpegを入れておくとサムネイルが生成される

このようになった場合は

sudo apt install ffmpeg

としてffmpegを導入しておく。

https://www.sample-videos.com/

からダウンロードしたファイルをアップロードしてみると

このようになる

$this->addMediaConversion('thumb')
     ->fit(Fit::Crop, 400, 400)
     ->nonQueued();

PDFなんてもんは大抵1枚目を表示しておけば事足りるはずなのであるが、動画の場合は割とそういうわけにもいかない事がある。そのような時は

$this->addMediaConversion('thumb')
     ->fit(Fit::Crop, 400, 400)
     ->extractVideoFrameAtSecond(2) // 2秒後のフレームを取得
     ->nonQueued();


2秒後の画像が取得できた

以上、主にconversionに関して見てきた

なお、(本当の)オリジナルファイルをlocalに、conversionをS3に、といった複雑な事もできるっちゃできるので色々やってみたい方はドキュメントをあたってみてくださいなと。

このライブラリーに関してはもう少しいろいろ語るポイントがあるので、あと1回はやりたい

Discussion