Signed-off-by: Michael Mayer <michael@photoprism.app>
This commit is contained in:
parent
d4cbb60b92
commit
527fc0319e
22 changed files with 167 additions and 39 deletions
|
@ -165,6 +165,8 @@ export default class Util {
|
|||
switch (value) {
|
||||
case "jpg":
|
||||
return "JPEG";
|
||||
case "jxl":
|
||||
return "JPEG XL";
|
||||
case "raw":
|
||||
return "Unprocessed Sensor Data (RAW)";
|
||||
case "mov":
|
||||
|
|
|
@ -470,18 +470,18 @@ export class Photo extends RestModel {
|
|||
}
|
||||
|
||||
if (!file) {
|
||||
file = this.gifFile();
|
||||
file = this.animatedFile();
|
||||
}
|
||||
|
||||
return file;
|
||||
});
|
||||
|
||||
gifFile() {
|
||||
animatedFile() {
|
||||
if (!this.Files) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return this.Files.find((f) => f.FileType === FormatGif);
|
||||
return this.Files.find((f) => f.FileType === FormatGif || !!f.Frames);
|
||||
}
|
||||
|
||||
videoUrl() {
|
||||
|
|
|
@ -5,14 +5,13 @@ import (
|
|||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/photoprism/photoprism/pkg/video"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"github.com/photoprism/photoprism/internal/get"
|
||||
"github.com/photoprism/photoprism/internal/photoprism"
|
||||
"github.com/photoprism/photoprism/internal/query"
|
||||
"github.com/photoprism/photoprism/pkg/clean"
|
||||
"github.com/photoprism/photoprism/pkg/video"
|
||||
)
|
||||
|
||||
// GetVideo streams videos.
|
||||
|
|
|
@ -29,3 +29,24 @@ func (c *Config) ImageMagickBlacklist() string {
|
|||
func (c *Config) ImageMagickEnabled() bool {
|
||||
return !c.DisableImageMagick()
|
||||
}
|
||||
|
||||
// JpegXLDecoderBin returns the JPEG XL decoder executable file name.
|
||||
func (c *Config) JpegXLDecoderBin() string {
|
||||
return findBin("", "djxl")
|
||||
}
|
||||
|
||||
// JpegXLEnabled checks if JPEG XL file format support is enabled.
|
||||
func (c *Config) JpegXLEnabled() bool {
|
||||
return !c.DisableImageMagick()
|
||||
}
|
||||
|
||||
// DisableJpegXL checks if JPEG XL file format support is disabled.
|
||||
func (c *Config) DisableJpegXL() bool {
|
||||
if c.options.DisableJpegXL {
|
||||
return true
|
||||
} else if c.JpegXLDecoderBin() == "" {
|
||||
c.options.DisableJpegXL = true
|
||||
}
|
||||
|
||||
return c.options.DisableJpegXL
|
||||
}
|
||||
|
|
|
@ -279,6 +279,11 @@ var Flags = CliFlags{
|
|||
Usage: "disable conversion of HEIC images with libheif",
|
||||
EnvVar: "PHOTOPRISM_DISABLE_HEIFCONVERT",
|
||||
}}, {
|
||||
Flag: cli.BoolFlag{
|
||||
Name: "disable-jpegxl",
|
||||
Usage: "disable JPEG XL file format support",
|
||||
EnvVar: "PHOTOPRISM_DISABLE_JPEGXL",
|
||||
}}, {
|
||||
Flag: cli.BoolFlag{
|
||||
Name: "disable-raw",
|
||||
Usage: "disable indexing and conversion of RAW images",
|
||||
|
|
|
@ -76,6 +76,7 @@ type Options struct {
|
|||
DisableImageMagick bool `yaml:"DisableImageMagick" json:"DisableImageMagick" flag:"disable-imagemagick"`
|
||||
DisableHeifConvert bool `yaml:"DisableHeifConvert" json:"DisableHeifConvert" flag:"disable-heifconvert"`
|
||||
DisableVector bool `yaml:"DisableVector" json:"DisableVector" flag:"disable-vector"`
|
||||
DisableJpegXL bool `yaml:"DisableJpegXL" json:"DisableJpegXL" flag:"disable-jpegxl"`
|
||||
DisableRaw bool `yaml:"DisableRaw" json:"DisableRaw" flag:"disable-raw"`
|
||||
RawPresets bool `yaml:"RawPresets" json:"RawPresets" flag:"raw-presets"`
|
||||
ExifBruteForce bool `yaml:"ExifBruteForce" json:"ExifBruteForce" flag:"exif-bruteforce"`
|
||||
|
|
|
@ -100,6 +100,7 @@ func (c *Config) Report() (rows [][]string, cols []string) {
|
|||
{"disable-heifconvert", fmt.Sprintf("%t", c.DisableHeifConvert())},
|
||||
{"disable-rsvgconvert", fmt.Sprintf("%t", c.DisableRsvgConvert())},
|
||||
{"disable-vector", fmt.Sprintf("%t", c.DisableVector())},
|
||||
{"disable-jpegxl", fmt.Sprintf("%t", c.DisableJpegXL())},
|
||||
{"disable-raw", fmt.Sprintf("%t", c.DisableRaw())},
|
||||
|
||||
// Format Flags.
|
||||
|
@ -185,6 +186,7 @@ func (c *Config) Report() (rows [][]string, cols []string) {
|
|||
{"imagemagick-blacklist", c.ImageMagickBlacklist()},
|
||||
{"heifconvert-bin", c.HeifConvertBin()},
|
||||
{"rsvgconvert-bin", c.RsvgConvertBin()},
|
||||
{"jpegxldecoder-bin", c.JpegXLDecoderBin()},
|
||||
|
||||
// Thumbnails.
|
||||
{"download-token", c.DownloadToken()},
|
||||
|
|
|
@ -704,6 +704,9 @@ func (m *File) SetFrames(n int) {
|
|||
// Update FPS.
|
||||
if m.FileFPS <= 0 && m.FileDuration > 0 {
|
||||
m.FileFPS = float64(m.FileFrames) / m.FileDuration.Seconds()
|
||||
} else if m.FileFPS == 0 && m.FileDuration == 0 {
|
||||
m.FileFPS = 30.0 // Assume 30 frames per second.
|
||||
m.FileDuration = time.Duration(float64(m.FileFrames)/m.FileFPS) * time.Second
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -18,8 +18,8 @@ func AvcConvertCommand(fileName, avcName, ffmpegBin, bitrate string, encoder Avc
|
|||
// Don't transcode more than one video at the same time.
|
||||
useMutex = true
|
||||
|
||||
// Animated GIF?
|
||||
if fs.FileType(fileName) == fs.ImageGIF {
|
||||
// Animated GIF or PNG?
|
||||
if fs.FileType(fileName) == fs.ImageGIF || fs.FileType(fileName) == fs.ImagePNG {
|
||||
result = exec.Command(
|
||||
ffmpegBin,
|
||||
"-i", fileName,
|
||||
|
|
|
@ -25,7 +25,7 @@ type Data struct {
|
|||
TimeZone string `meta:"-"`
|
||||
Duration time.Duration `meta:"Duration,MediaDuration,TrackDuration"`
|
||||
FPS float64 `meta:"VideoFrameRate,VideoAvgFrameRate"`
|
||||
Frames int `meta:"FrameCount"`
|
||||
Frames int `meta:"FrameCount,AnimationFrames"`
|
||||
Codec string `meta:"CompressorID,VideoCodecID,CodecID,FileType"`
|
||||
Title string `meta:"Headline,Title" xmp:"dc:title" dc:"title,title.Alt"`
|
||||
Subject string `meta:"Subject,PersonInImage,ObjectName,HierarchicalSubject,CatalogSets" xmp:"Subject"`
|
||||
|
|
|
@ -88,9 +88,14 @@ func (c *Convert) JpegConvertCommands(f *MediaFile, jpegName string, xmpName str
|
|||
result = append(result, exec.Command(c.conf.ExifToolBin(), "-q", "-q", "-b", "-PreviewImage", f.FileName()))
|
||||
}
|
||||
|
||||
// Decode JPEG XL image if support is enabled.
|
||||
if f.IsJpegXL() && c.conf.JpegXLEnabled() {
|
||||
result = append(result, exec.Command(c.conf.JpegXLDecoderBin(), f.FileName(), jpegName))
|
||||
}
|
||||
|
||||
// Try ImageMagick for other image file formats if allowed.
|
||||
if c.conf.ImageMagickEnabled() && c.imagemagickBlacklist.Allow(fileExt) &&
|
||||
(f.IsImage() || f.IsVector() && c.conf.VectorEnabled() || f.IsRaw() && c.conf.RawEnabled()) {
|
||||
(f.IsImage() && !f.IsJpegXL() || f.IsVector() && c.conf.VectorEnabled() || f.IsRaw() && c.conf.RawEnabled()) {
|
||||
quality := fmt.Sprintf("%d", c.conf.JpegQuality())
|
||||
resize := fmt.Sprintf("%dx%d>", c.conf.JpegSize(), c.conf.JpegSize())
|
||||
args := []string{f.FileName(), "-flatten", "-resize", resize, "-quality", quality, jpegName}
|
||||
|
|
|
@ -31,9 +31,14 @@ func (c *Convert) PngConvertCommands(f *MediaFile, pngName string) (result []*ex
|
|||
result = append(result, exec.Command(c.conf.FFmpegBin(), "-y", "-i", f.FileName(), "-ss", ffmpeg.PreviewTimeOffset(f.Duration()), "-vframes", "1", pngName))
|
||||
}
|
||||
|
||||
// Decode JPEG XL image if support is enabled.
|
||||
if f.IsJpegXL() && c.conf.JpegXLEnabled() {
|
||||
result = append(result, exec.Command(c.conf.JpegXLDecoderBin(), f.FileName(), pngName))
|
||||
}
|
||||
|
||||
// Try ImageMagick for other image file formats if allowed.
|
||||
if c.conf.ImageMagickEnabled() && c.imagemagickBlacklist.Allow(fileExt) &&
|
||||
(f.IsImage() || f.IsVector() && c.conf.VectorEnabled() || f.IsRaw() && c.conf.RawEnabled()) {
|
||||
(f.IsImage() && !f.IsJpegXL() || f.IsVector() && c.conf.VectorEnabled() || f.IsRaw() && c.conf.RawEnabled()) {
|
||||
resize := fmt.Sprintf("%dx%d>", c.conf.PngSize(), c.conf.PngSize())
|
||||
args := []string{f.FileName(), "-flatten", "-resize", resize, pngName}
|
||||
result = append(result, exec.Command(c.conf.ImageMagickBin(), args...))
|
||||
|
|
|
@ -393,6 +393,8 @@ func (ind *Index) UserMediaFile(m *MediaFile, o IndexOptions, originalName, phot
|
|||
if metaData := m.MetaData(); metaData.Error == nil {
|
||||
file.FileCodec = metaData.Codec
|
||||
file.SetMediaUTC(metaData.TakenAt)
|
||||
file.SetDuration(metaData.Duration)
|
||||
file.SetFPS(metaData.FPS)
|
||||
file.SetFrames(metaData.Frames)
|
||||
file.SetProjection(metaData.Projection)
|
||||
file.SetHDR(metaData.IsHDR())
|
||||
|
@ -412,6 +414,11 @@ func (ind *Index) UserMediaFile(m *MediaFile, o IndexOptions, originalName, phot
|
|||
file.InstanceID = metaData.InstanceID
|
||||
}
|
||||
}
|
||||
|
||||
// Set the photo type to animated if it is an animated PNG.
|
||||
if photo.TypeSrc == entity.SrcAuto && photo.PhotoType == entity.MediaImage && m.IsAnimatedImage() {
|
||||
photo.PhotoType = entity.MediaAnimated
|
||||
}
|
||||
case m.IsXMP():
|
||||
if metaData, err := meta.XMP(m.FileName()); err == nil {
|
||||
// Update basic metadata.
|
||||
|
@ -494,7 +501,7 @@ func (ind *Index) UserMediaFile(m *MediaFile, o IndexOptions, originalName, phot
|
|||
|
||||
// Update photo type if an image and not manually modified.
|
||||
if photo.TypeSrc == entity.SrcAuto && photo.PhotoType == entity.MediaImage {
|
||||
if m.IsAnimatedGif() {
|
||||
if m.IsAnimatedImage() {
|
||||
photo.PhotoType = entity.MediaAnimated
|
||||
} else if m.IsRaw() {
|
||||
photo.PhotoType = entity.MediaRaw
|
||||
|
|
|
@ -738,6 +738,15 @@ func (m *MediaFile) IsJpeg() bool {
|
|||
return m.MimeType() == fs.MimeTypeJpeg
|
||||
}
|
||||
|
||||
// IsJpegXL checks if the file is a JPEG XL image with a supported file type extension.
|
||||
func (m *MediaFile) IsJpegXL() bool {
|
||||
if fs.FileType(m.fileName) != fs.ImageJPEGXL {
|
||||
return false
|
||||
}
|
||||
|
||||
return m.MimeType() == fs.MimeTypeJpegXL
|
||||
}
|
||||
|
||||
// IsPng checks if the file is a PNG image with a supported file type extension.
|
||||
func (m *MediaFile) IsPng() bool {
|
||||
if fs.FileType(m.fileName) != fs.ImagePNG {
|
||||
|
@ -748,7 +757,8 @@ func (m *MediaFile) IsPng() bool {
|
|||
|
||||
// Since mime type detection is expensive, it is only
|
||||
// performed after other checks have passed.
|
||||
return m.MimeType() == fs.MimeTypePng
|
||||
mimeType := m.MimeType()
|
||||
return mimeType == fs.MimeTypePng || mimeType == fs.MimeTypeAnimatedPng
|
||||
}
|
||||
|
||||
// IsGif checks if the file is a GIF image with a supported file type extension.
|
||||
|
@ -823,9 +833,9 @@ func (m *MediaFile) Duration() time.Duration {
|
|||
return m.MetaData().Duration
|
||||
}
|
||||
|
||||
// IsAnimatedGif checks if the file is an animated GIF with a supported file type extension.
|
||||
func (m *MediaFile) IsAnimatedGif() bool {
|
||||
return m.IsGif() && m.MetaData().Frames > 1
|
||||
// IsAnimatedImage checks if the file is an animated image with a supported file type extension.
|
||||
func (m *MediaFile) IsAnimatedImage() bool {
|
||||
return (m.IsGif() || m.IsPng()) && m.MetaData().Frames > 1
|
||||
}
|
||||
|
||||
// IsJson checks if the file is a JSON sidecar file with a supported file type extension.
|
||||
|
@ -886,7 +896,7 @@ func (m *MediaFile) IsRaw() bool {
|
|||
|
||||
// IsAnimated returns true if it is a video or animated image.
|
||||
func (m *MediaFile) IsAnimated() bool {
|
||||
return m.IsVideo() || m.IsAnimatedGif()
|
||||
return m.IsVideo() || m.IsAnimatedImage()
|
||||
}
|
||||
|
||||
// IsVideo returns true if this is a video file.
|
||||
|
|
|
@ -1506,7 +1506,7 @@ func TestMediaFile_IsAnimated(t *testing.T) {
|
|||
assert.Equal(t, false, f.IsVideo())
|
||||
assert.Equal(t, false, f.IsAnimated())
|
||||
assert.Equal(t, true, f.IsGif())
|
||||
assert.Equal(t, false, f.IsAnimatedGif())
|
||||
assert.Equal(t, false, f.IsAnimatedImage())
|
||||
assert.Equal(t, false, f.IsSidecar())
|
||||
}
|
||||
})
|
||||
|
@ -1518,7 +1518,7 @@ func TestMediaFile_IsAnimated(t *testing.T) {
|
|||
assert.Equal(t, false, f.IsVideo())
|
||||
assert.Equal(t, true, f.IsAnimated())
|
||||
assert.Equal(t, true, f.IsGif())
|
||||
assert.Equal(t, true, f.IsAnimatedGif())
|
||||
assert.Equal(t, true, f.IsAnimatedImage())
|
||||
assert.Equal(t, false, f.IsSidecar())
|
||||
}
|
||||
})
|
||||
|
@ -1530,7 +1530,7 @@ func TestMediaFile_IsAnimated(t *testing.T) {
|
|||
assert.Equal(t, true, f.IsVideo())
|
||||
assert.Equal(t, true, f.IsAnimated())
|
||||
assert.Equal(t, false, f.IsGif())
|
||||
assert.Equal(t, false, f.IsAnimatedGif())
|
||||
assert.Equal(t, false, f.IsAnimatedImage())
|
||||
assert.Equal(t, false, f.IsSidecar())
|
||||
}
|
||||
})
|
||||
|
|
|
@ -6,7 +6,6 @@ import (
|
|||
"strings"
|
||||
|
||||
"github.com/photoprism/photoprism/internal/entity"
|
||||
"github.com/photoprism/photoprism/pkg/fs"
|
||||
"github.com/photoprism/photoprism/pkg/media"
|
||||
)
|
||||
|
||||
|
@ -78,7 +77,7 @@ func VideoByPhotoUID(photoUID string) (*entity.File, error) {
|
|||
return &f, fmt.Errorf("photo uid required")
|
||||
}
|
||||
|
||||
err := Db().Where("photo_uid = ? AND (file_video = 1 OR file_type = ?)", photoUID, fs.ImageGIF).
|
||||
err := Db().Where("photo_uid = ? AND (file_video = 1 OR file_frames > 0 OR file_type = 'gif')", photoUID).
|
||||
Order("file_video DESC, file_duration DESC, file_frames DESC").
|
||||
Preload("Photo").First(&f).Error
|
||||
return &f, err
|
||||
|
|
|
@ -17,10 +17,12 @@ var Extensions = FileExtensions{
|
|||
".jfif": ImageJPEG,
|
||||
".jfi": ImageJPEG,
|
||||
".thm": ImageJPEG,
|
||||
".jxl": ImageJPEGXL,
|
||||
".tif": ImageTIFF,
|
||||
".tiff": ImageTIFF,
|
||||
".psd": ImagePSD,
|
||||
".png": ImagePNG,
|
||||
".apng": ImagePNG,
|
||||
".pn": ImagePNG,
|
||||
".gif": ImageGIF,
|
||||
".bmp": ImageBMP,
|
||||
|
|
|
@ -14,6 +14,7 @@ import (
|
|||
const (
|
||||
ImageRaw Type = "raw" // RAW image
|
||||
ImageJPEG Type = "jpg" // JPEG image
|
||||
ImageJPEGXL Type = "jxl" // JPEG XL image
|
||||
ImagePNG Type = "png" // PNG image
|
||||
ImageGIF Type = "gif" // GIF image
|
||||
ImageTIFF Type = "tiff" // TIFF image
|
||||
|
|
|
@ -5,6 +5,7 @@ var TypeInfo = map[Type]string{
|
|||
ImageRaw: "Unprocessed Sensor Data",
|
||||
ImageDNG: "Adobe Digital Negative",
|
||||
ImageJPEG: "Joint Photographic Experts Group (JPEG)",
|
||||
ImageJPEGXL: "JPEG XL",
|
||||
ImagePNG: "Portable Network Graphics",
|
||||
ImageGIF: "Graphics Interchange Format",
|
||||
ImageTIFF: "Tag Image File Format",
|
||||
|
|
|
@ -8,24 +8,26 @@ import (
|
|||
)
|
||||
|
||||
const (
|
||||
MimeTypeUnknown = ""
|
||||
MimeTypeJpeg = "image/jpeg"
|
||||
MimeTypePng = "image/png"
|
||||
MimeTypeGif = "image/gif"
|
||||
MimeTypeBitmap = "image/bmp"
|
||||
MimeTypeTiff = "image/tiff"
|
||||
MimeTypeDNG = "image/dng"
|
||||
MimeTypeAVIF = "image/avif"
|
||||
MimeTypeHEIC = "image/heic"
|
||||
MimeTypeWebP = "image/webp"
|
||||
MimeTypeMP4 = "video/mp4"
|
||||
MimeTypeMOV = "video/quicktime"
|
||||
MimeTypeSVG = "image/svg+xml"
|
||||
MimeTypeAI = "application/vnd.adobe.illustrator"
|
||||
MimeTypePS = "application/ps"
|
||||
MimeTypeEPS = "image/eps"
|
||||
MimeTypeXML = "text/xml"
|
||||
MimeTypeJSON = "application/json"
|
||||
MimeTypeUnknown = ""
|
||||
MimeTypeJpeg = "image/jpeg"
|
||||
MimeTypeJpegXL = "image/jxl"
|
||||
MimeTypePng = "image/png"
|
||||
MimeTypeAnimatedPng = "image/vnd.mozilla.apng"
|
||||
MimeTypeGif = "image/gif"
|
||||
MimeTypeBitmap = "image/bmp"
|
||||
MimeTypeTiff = "image/tiff"
|
||||
MimeTypeDNG = "image/dng"
|
||||
MimeTypeAVIF = "image/avif"
|
||||
MimeTypeHEIC = "image/heic"
|
||||
MimeTypeWebP = "image/webp"
|
||||
MimeTypeMP4 = "video/mp4"
|
||||
MimeTypeMOV = "video/quicktime"
|
||||
MimeTypeSVG = "image/svg+xml"
|
||||
MimeTypeAI = "application/vnd.adobe.illustrator"
|
||||
MimeTypePS = "application/ps"
|
||||
MimeTypeEPS = "image/eps"
|
||||
MimeTypeXML = "text/xml"
|
||||
MimeTypeJSON = "application/json"
|
||||
)
|
||||
|
||||
// MimeType returns the mime type of a file, or an empty string if it could not be detected.
|
||||
|
|
|
@ -7,6 +7,7 @@ var Formats = map[fs.Type]Type{
|
|||
fs.ImageRaw: Raw,
|
||||
fs.ImageDNG: Raw,
|
||||
fs.ImageJPEG: Image,
|
||||
fs.ImageJPEGXL: Image,
|
||||
fs.ImagePNG: Image,
|
||||
fs.ImageGIF: Image,
|
||||
fs.ImageTIFF: Image,
|
||||
|
|
62
scripts/dist/install-jxl.sh
vendored
Executable file
62
scripts/dist/install-jxl.sh
vendored
Executable file
|
@ -0,0 +1,62 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
# This installs JPEG XL on Linux.
|
||||
# bash <(curl -s https://raw.githubusercontent.com/photoprism/photoprism/develop/scripts/dist/install-jxl.sh)
|
||||
|
||||
PATH="/usr/local/sbin:/usr/sbin:/sbin:/usr/local/bin:/usr/bin:/bin:/scripts:$PATH"
|
||||
|
||||
# Abort if not executed as root.
|
||||
if [[ $(id -u) != "0" ]]; then
|
||||
echo "Usage: run ${0##*/} as root" 1>&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ $PHOTOPRISM_ARCH ]]; then
|
||||
SYSTEM_ARCH=$PHOTOPRISM_ARCH
|
||||
else
|
||||
SYSTEM_ARCH=$(uname -m)
|
||||
fi
|
||||
|
||||
LIB_VERSION=${2:-v0.8.1}
|
||||
SYSTEM_ARCH=$("$(dirname "$0")/arch.sh")
|
||||
DESTARCH=${DESTARCH:-$SYSTEM_ARCH}
|
||||
|
||||
set -e
|
||||
|
||||
. /etc/os-release
|
||||
|
||||
ARCHIVE="jxl-debs-${DESTARCH}-ubuntu-22.04-${LIB_VERSION}.tar.gz"
|
||||
URL="https://github.com/libjxl/libjxl/releases/download/${LIB_VERSION}/${ARCHIVE}"
|
||||
TMPDIR="/tmp/jpegxl"
|
||||
|
||||
echo "------------------------------------------------"
|
||||
echo "VERSION: $LIB_VERSION"
|
||||
echo "ARCHIVE: $ARCHIVE"
|
||||
echo "------------------------------------------------"
|
||||
|
||||
echo "Installing JPEG XL for ${DESTARCH^^}..."
|
||||
|
||||
case $DESTARCH in
|
||||
amd64 | AMD64 | x86_64 | x86-64)
|
||||
if [[ $VERSION_CODENAME == "jammy" ]]; then
|
||||
apt-get update
|
||||
apt-get install -f libtcmalloc-minimal4 libhwy-dev libhwy0
|
||||
rm -rf /tmp/jpegxl
|
||||
mkdir -p "$TMPDIR"
|
||||
echo "Extracting \"$URL\" to \"$TMPDIR\"."
|
||||
wget --inet4-only -c "$URL" -O - | tar --overwrite --mode=755 -xz -C "$TMPDIR"
|
||||
(cd "$TMPDIR" && dpkg -i jxl_0.8.1_amd64.deb libjxl_0.8.1_amd64.deb libjxl-dev_0.8.1_amd64.deb)
|
||||
apt --fix-broken install
|
||||
rm -rf /tmp/jpegxl
|
||||
else
|
||||
echo "install-jxl: target distribution currently unsupported"
|
||||
fi
|
||||
;;
|
||||
|
||||
*)
|
||||
echo "Unsupported Machine Architecture: \"$BUILD_ARCH\"" 1>&2
|
||||
exit 0
|
||||
;;
|
||||
esac
|
||||
|
||||
echo "Done."
|
Loading…
Reference in a new issue