88
99 "github.com/photoprism/photoprism/internal/ffmpeg"
1010 "github.com/photoprism/photoprism/internal/ffmpeg/encode"
11+ "github.com/photoprism/photoprism/internal/raw"
1112)
1213
1314// JpegConvertCmds returns the supported commands for converting a MediaFile to JPEG, sorted by priority.
@@ -21,10 +22,11 @@ func (w *Convert) JpegConvertCmds(f *MediaFile, jpegName string, xmpName string)
2122 // Add suitable conversion commands depending on the file type, codec, runtime environment, and configuration.
2223 fileExt := f .Extension ()
2324 maxSize := strconv .Itoa (w .conf .JpegSize ())
25+ rawEnabled := w .conf .RawEnabled ()
2426
2527 // On a Mac, use the Apple Scriptable image processing system to convert images to JPEG,
2628 // see https://ss64.com/osx/sips.html.
27- if (f .IsRaw () || f .IsHeif ()) && w .conf .SipsEnabled () && w .sipsExclude .Allow (fileExt ) {
29+ if (f .IsRaw () && rawEnabled || f .IsHeif ()) && w .conf .SipsEnabled () && w .sipsExclude .Allow (fileExt ) {
2830 result = append (result , NewConvertCmd (
2931 // #nosec G204 -- arguments are built from validated config and file paths.
3032 exec .Command (w .conf .SipsBin (), "-Z" , maxSize , "-s" , "format" , "jpeg" , "--out" , jpegName , f .FileName ())),
@@ -47,82 +49,43 @@ func (w *Convert) JpegConvertCmds(f *MediaFile, jpegName string, xmpName string)
4749 )
4850 }
4951
50- // Convert RAW files to JPEG with Darktable and/or RawTherapee.
51- if f .IsRaw () && w .conf .RawEnabled () {
52- if w .conf .DarktableEnabled () && w .darktableExclude .Allow (fileExt ) {
53- var args []string
54-
55- // Set RAW, XMP, and JPEG filenames.
56- if xmpName != "" {
57- args = []string {f .FileName (), xmpName , jpegName }
58- } else {
59- args = []string {f .FileName (), jpegName }
60- }
61-
62- // Set RAW to JPEG conversion options.
63- if w .conf .RawPresets () {
64- useMutex = true // can run one instance only with presets enabled
65- args = append (args , "--width" , maxSize , "--height" , maxSize , "--hq" , "true" , "--upscale" , "false" )
66- } else {
67- useMutex = false // --apply-custom-presets=false disables locking
68- args = append (args , "--apply-custom-presets" , "false" , "--width" , maxSize , "--height" , maxSize , "--hq" , "true" , "--upscale" , "false" )
52+ // Convert RAW files to JPEG: Darktable/RawTherapee render when RAW conversion is enabled, while
53+ // ExifTool preview extraction also runs when rendering is disabled, so existing previews stay usable.
54+ if f .IsRaw () {
55+ if rawEnabled {
56+ if w .conf .DarktableEnabled () && w .darktableExclude .Allow (fileExt ) {
57+ cmd , mutex := raw .DarktableCmd (raw.DarktableOptions {
58+ Bin : w .conf .DarktableBin (),
59+ RawName : f .FileName (),
60+ XmpName : xmpName ,
61+ JpegName : jpegName ,
62+ MaxSize : w .conf .JpegSize (),
63+ Presets : w .conf .RawPresets (),
64+ ConfigDir : conf .DarktableConfigPath (),
65+ CacheDir : conf .DarktableCachePath (),
66+ })
67+ useMutex = mutex
68+ result = append (result , NewConvertCmd (cmd ))
6969 }
7070
71- // Set library, config, and cache location.
72- args = append (args , "--core" , "--library" , ":memory:" )
71+ // Render the RAW with RawTherapee when Darktable is unavailable or fails. Output is
72+ // rejected on an untrustworthy decode (raw.DecoderErrors) so the embedded preview wins.
73+ if w .conf .RawTherapeeEnabled () && w .rawTherapeeExclude .Allow (fileExt ) {
74+ profile := filepath .Join (conf .AssetsPath (), "profiles" , "raw.pp3" )
7375
74- if dir := conf .DarktableConfigPath (); dir != "" {
75- args = append (args , "--configdir" , dir )
76+ result = append (result , NewConvertCmd (
77+ raw .TherapeeCmd (w .conf .RawTherapeeBin (), f .FileName (), jpegName , profile , int (w .conf .JpegQuality ())),
78+ ).WithStderrRejection (raw .DecoderErrors ... ))
7679 }
77-
78- if dir := conf .DarktableCachePath (); dir != "" {
79- args = append (args , "--cachedir" , dir )
80- }
81-
82- result = append (result , NewConvertCmd (
83- // #nosec G204 -- arguments are built from validated config and file paths.
84- exec .Command (w .conf .DarktableBin (), args ... )),
85- )
8680 }
8781
88- // Extract the largest embedded JPEG preview with ExifTool as a fallback before
89- // RawTherapee. The camera-rendered preview always has correct colors, unlike a
90- // generic demosaic of a sensor the RAW decoder cannot identify (e.g. very recent
91- // Canon CR3 bodies, which otherwise come out magenta). It only wins when Darktable
92- // is unavailable or fails, so supported cameras keep their full RAW rendering.
93- // JpgFromRaw is the full-resolution image and PreviewImage the smaller fallback;
94- // listed largest-first so the convert loop prefers the bigger one.
95- if w .conf .ExifToolEnabled () {
96- result = append (result , NewConvertCmd (
97- // #nosec G204 -- arguments are built from validated config and file paths.
98- exec .Command (w .conf .ExifToolBin (), "-q" , "-q" , "-b" , "-JpgFromRaw" , f .FileName ())).WithImageVerification (),
99- )
100- result = append (result , NewConvertCmd (
101- // #nosec G204 -- arguments are built from validated config and file paths.
102- exec .Command (w .conf .ExifToolBin (), "-q" , "-q" , "-b" , "-PreviewImage" , f .FileName ())).WithImageVerification (),
103- )
82+ // Extract the embedded camera preview (largest first): the fallback after the RAW developers,
83+ // and the only option when RAW rendering is disabled. Colors stay correct for sensors they
84+ // cannot identify (recent Canon CR3 bodies otherwise come out magenta). Skipped if unusable.
85+ if w .conf .ExifToolEnabled () && raw .PreviewExtAllowed (fileExt ) {
86+ result = append (result , NewConvertCmd (raw .ExifToolJpgFromRawCmd (w .conf .ExifToolBin (), f .FileName ())).WithImageVerification ())
87+ result = append (result , NewConvertCmd (raw .ExifToolPreviewImageCmd (w .conf .ExifToolBin (), f .FileName ())).WithImageVerification ())
10488 }
105-
106- if w .conf .RawTherapeeEnabled () && w .rawTherapeeExclude .Allow (fileExt ) {
107- jpegQuality := fmt .Sprintf ("-j%d" , w .conf .JpegQuality ())
108- profile := filepath .Join (conf .AssetsPath (), "profiles" , "raw.pp3" )
109-
110- args := []string {"-o" , jpegName , "-p" , profile , "-s" , "-d" , jpegQuality , "-js3" , "-b8" , "-c" , f .FileName ()}
111-
112- result = append (result , NewConvertCmd (
113- // #nosec G204 -- arguments are built from validated config and file paths.
114- exec .Command (w .conf .RawTherapeeBin (), args ... )),
115- )
116- }
117- }
118-
119- // Use ExifTool to extract JPEG thumbnails from Digital Negative (DNG) files.
120- if f .IsDng () && w .conf .ExifToolEnabled () {
121- // Example: exiftool -b -PreviewImage -w IMG_4691.DNG.jpg IMG_4691.DNG
122- result = append (result , NewConvertCmd (
123- // #nosec G204 -- arguments are built from validated config and file paths.
124- exec .Command (w .conf .ExifToolBin (), "-q" , "-q" , "-b" , "-PreviewImage" , f .FileName ())).WithImageVerification (),
125- )
12689 }
12790
12891 // Use "djxl" to convert JPEG XL images as a fallback when libvips lacks native support.
0 commit comments