-
Notifications
You must be signed in to change notification settings - Fork 7
JXL export #82
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
JXL export #82
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,72 @@ | ||
| using PhotoLocator.Helpers; | ||
| using PhotoLocator.Metadata; | ||
| using System; | ||
| using System.Diagnostics; | ||
| using System.IO; | ||
| using System.Threading; | ||
| using System.Threading.Tasks; | ||
| using System.Windows.Media.Imaging; | ||
|
|
||
| namespace PhotoLocator.PictureFileFormats | ||
| { | ||
| public static class JpegXlFileFormatHandler | ||
| { | ||
| public const string EncoderName = "cjxl.exe"; | ||
| internal static string EncoderPath { get; } = Path.Combine(AppContext.BaseDirectory, "jpegli", EncoderName); | ||
|
|
||
| public static void SaveToStream(BitmapSource bitmap, Stream dest, string encoderPath, BitmapMetadata? metadata, int quality) | ||
| { | ||
| using var srcStream = new MemoryStream(); | ||
| var encoder = new PngBitmapEncoder(); | ||
| if (metadata is null) | ||
| encoder.Frames.Add(BitmapFrame.Create(bitmap)); | ||
| else | ||
| encoder.Frames.Add(BitmapFrame.Create(bitmap, null, ExifHandler.CreateMetadataForEncoder(metadata, encoder), null)); | ||
| encoder.Save(srcStream); | ||
| srcStream.Position = 0; | ||
| JpegliEncoder.Process(encoderPath, srcStream, ".png", dest, ".jxl", $"-q {quality}"); | ||
| } | ||
|
|
||
| public static void SaveToFile(BitmapSource image, string targetPath, BitmapMetadata? metadata = null, Settings.ISettings? settings = null) | ||
| { | ||
| using (var stream = new FileStream(targetPath, FileMode.Create, FileAccess.Write)) | ||
| SaveToStream(image, stream, EncoderPath, metadata, settings?.JpegQuality ?? GeneralFileFormatHandler.DefaultJpegQuality); | ||
| if (metadata is not null && !string.IsNullOrEmpty(settings?.ExifToolPath)) | ||
| ExifTool.SetMetadataAsync(targetPath, targetPath, metadata, settings.ExifToolPath, CancellationToken.None).Wait(); | ||
|
Comment on lines
+34
to
+35
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
# Locate the handler file and show the relevant region
ls -la
fd -i "JpegXlFileFormatHandler.cs" .
echo "---- File context around handler ----"
FILE=$(fd -i "JpegXlFileFormatHandler.cs" . -a | head -n 1)
echo "Using: $FILE"
sed -n '1,120p' "$FILE" | nl -ba | sed -n '25,80p'
echo "---- Exact lines 34-60 (best effort) ----"
sed -n '20,60p' "$FILE" | nl -ba
# Find ExifTool and SetMetadataAsync definition/overloads
echo "---- Searching for SetMetadataAsync ----"
rg -n "SetMetadataAsync" -S . -g'*.cs'
# Show the ExifTool.SetMetadataAsync implementation(s)
echo "---- Showing implementation(s) ----"
rg -n "Task\s+SetMetadataAsync|SetMetadataAsync\s*\(" -S . -g'*.cs' | head -n 50
# Attempt to open the first implementation file(s)
# (We use sed ranges after locating filenames.)
IMPL_FILES=$(rg -l "SetMetadataAsync" -S . -g'*.cs' | tr '\n' ' ')
echo "---- Implementation files (top 10) ----"
for f in $(rg -l "SetMetadataAsync" -S . -g'*.cs' | head -n 10); do echo "$f"; done
# Show likely ExifTool class file(s)
for f in $(rg -l "class\s+ExifTool" -S . -g'*.cs' | head -n 5); do
echo "---- ExifTool class snippet: $f ----"
sed -n '1,220p' "$f" | nl -ba | head -n 220
done
# Find call sites of ExifTool.SetMetadataAsync that may also use .Wait() or .Result
echo "---- Call sites using .Wait() / .Result ----"
rg -n "SetMetadataAsync\([^)]*\)\.(Wait|Result)" -S . -g'*.cs'
rg -n "\.Wait\(\)\s*;" -S . -g'*.cs' | head -n 50
# Find where JpegXlFileFormatHandler is invoked from the WPF save flow (async vs sync)
echo "---- Searching for JpegXlFileFormatHandler usage ----"
rg -n "JpegXlFileFormatHandler" -S . -g'*.cs'Repository: meesoft/PhotoLocator Length of output: 2051 🏁 Script executed: #!/bin/bash
set -euo pipefail
FILE="PhotoLocator/PictureFileFormats/JpegXlFileFormatHandler.cs"
echo "---- Handler snippet (lines 1-120) ----"
sed -n '1,120p' "$FILE" | head -n 120
echo "---- Handler snippet (lines 30-55) ----"
sed -n '30,55p' "$FILE"
echo "---- Search for SetMetadataAsync in repo ----"
rg -n "SetMetadataAsync" PhotoLocator -S --glob='*.cs'
echo "---- Search for SetMetadataAsync(...).Wait() ----"
rg -n "SetMetadataAsync\s*\([^)]*\)\s*\.\s*Wait\s*\(\s*\)" PhotoLocator -S --glob='*.cs' || true
echo "---- Search for ExifTool class ----"
rg -n "class\s+ExifTool\b" PhotoLocator -S --glob='*.cs'
echo "---- Show ExifTool.* implementation(s) ----"
rg -n "Task\s+SetMetadataAsync|SetMetadataAsync\s*\(" PhotoLocator -S --glob='*.cs'
# List candidate implementation files and show surrounding code
IMPL_FILES=$(rg -l "SetMetadataAsync" PhotoLocator -S --glob='*.cs' | head -n 20)
echo "---- Candidate implementation files (first 20) ----"
echo "$IMPL_FILES"
for f in $IMPL_FILES; do
echo "==== $f (first 220 lines) ===="
sed -n '1,220p' "$f"
doneRepository: meesoft/PhotoLocator Length of output: 20589 🏁 Script executed: #!/bin/bash
set -euo pipefail
rg -n "JpegXlFileFormatHandler\.SaveToFile" -S . --glob='*.cs'
echo "----"
rg -n "SaveToFile\s*\(" PhotoLocator -S --glob='*.cs' | head -n 200
echo "----"
# Try to find typical WPF command/save entry points mentioning "Save" and "PictureFileFormats"
rg -n "Save.*(Async|Task)|Dispatcher|SyncContext|SynchronizationContext" PhotoLocator -S --glob='*.cs' | head -n 200
echo "----"
# Search for ExifTool usage and any async caller patterns
rg -n "ExifTool\.SetMetadataAsync" -S . --glob='*.cs'Repository: meesoft/PhotoLocator Length of output: 7226 🏁 Script executed: #!/bin/bash
set -euo pipefail
FILELIST=(
PhotoLocator/PictureFileFormats/GeneralFileFormatHandler.cs
PhotoLocator/MainViewModel.cs
PhotoLocator/ImageTransformCommands.cs
PhotoLocator/VideoTransformCommands.cs
PhotoLocator/PictureItemViewModel.cs
PhotoLocator/Helpers/ExceptionHandler.cs
PhotoLocator/App.xaml.cs
)
for f in "${FILELIST[@]}"; do
echo "==== $f (head 220) ===="
sed -n '1,220p' "$f" | nl -ba | head -n 220
done
# Print focused ranges around the identified call sites
echo "==== Focus: GeneralFileFormatHandler.cs around line ~60-120 ===="
sed -n '40,120p' PhotoLocator/PictureFileFormats/GeneralFileFormatHandler.cs | nl -ba
echo "==== Focus: MainViewModel.cs around line ~430-490 ===="
sed -n '420,500p' PhotoLocator/MainViewModel.cs | nl -ba
echo "==== Focus: ImageTransformCommands.cs around line ~60-280 ===="
sed -n '60,280p' PhotoLocator/ImageTransformCommands.cs | nl -ba
echo "==== Focus: VideoTransformCommands.cs around lines ~1030-1120 ===="
sed -n '1020,1125p' PhotoLocator/VideoTransformCommands.cs | nl -ba
echo "==== Focus: PictureItemViewModel.cs around line ~330-420 (SaveGeoTagAsync mention) ===="
sed -n '300,420p' PhotoLocator/PictureItemViewModel.cs | nl -ba
echo "==== Focus: ExceptionHandler.cs around line ~1-120 ===="
sed -n '1,120p' PhotoLocator/Helpers/ExceptionHandler.cs | nl -ba
echo "==== Focus: App.xaml.cs around dispatcher exception handler ===="
sed -n '1,120p' PhotoLocator/App.xaml.cs | nl -baRepository: meesoft/PhotoLocator Length of output: 207 🏁 Script executed: #!/bin/bash
set -euo pipefail
show_range () {
local file="$1"
local start="$2"
local end="$3"
echo "==== $file ($start-$end) ===="
sed -n "${start},${end}p" "$file" | cat -n
}
show_range PhotoLocator/PictureFileFormats/GeneralFileFormatHandler.cs 1 140
show_range PhotoLocator/PictureFileFormats/GeneralFileFormatHandler.cs 140 220
show_range PhotoLocator/MainViewModel.cs 420 560
show_range PhotoLocator/ImageTransformCommands.cs 1 260
show_range PhotoLocator/ImageTransformCommands.cs 260 340
show_range PhotoLocator/VideoTransformCommands.cs 1000 1125
show_range PhotoLocator/PictureItemViewModel.cs 300 430
show_range PhotoLocator/Helpers/ExceptionHandler.cs 1 120
show_range PhotoLocator/App.xaml.cs 1 120Repository: meesoft/PhotoLocator Length of output: 46048 🏁 Script executed: #!/bin/bash
set -euo pipefail
# Show more context around the clipboard image paste loop in MainViewModel
# (Search for the clipboard image / Clipboard.ContainsImage() block)
rg -n "Clipboard\.ContainsImage\(\)" PhotoLocator/MainViewModel.cs --glob='*.cs'
# Print a wider window around that match
MATCH_LINE=$(rg -n "Clipboard\.ContainsImage\(\)" PhotoLocator/MainViewModel.cs --glob='*.cs' | head -n1 | cut -d: -f1)
echo "Match at line: $MATCH_LINE"
start=$((MATCH_LINE-60))
end=$((MATCH_LINE+80))
sed -n "${start},${end}p" PhotoLocator/MainViewModel.cs | cat -n
# Inspect VideoTransformCommands area around a method that calls GeneralFileFormatHandler.SaveToFile
# Find the enclosing command/delegate for the snippet lines
rg -n "GeneralFileFormatHandler\.SaveToFile\(" PhotoLocator/VideoTransformCommands.cs --glob='*.cs'
# For first occurrence near earlier snippet, show surrounding context
# We'll just show around each occurrence line.
rg -n "GeneralFileFormatHandler\.SaveToFile\(" PhotoLocator/VideoTransformCommands.cs --glob='*.cs' | while read -r line; do
ln=$(echo "$line" | cut -d: -f1)
file=$(echo "$line" | cut -d: -f1)
: # noop
done
# Actually extract for each match: line number and show window
while IFS=: read -r file ln rest; do
echo "---- $file match at $ln ----"
s=$((ln-60)); e=$((ln+80))
sed -n "${s},${e}p" "$file" | cat -n | head -n 220
done < <(rg -n "GeneralFileFormatHandler\.SaveToFile\(" PhotoLocator/VideoTransformCommands.cs --glob='*.cs' | head -n 10)
# Search exception handling patterns for AggregateException / Wait usage
rg -n "AggregateException" PhotoLocator --glob='*.cs'
rg -n "\.Wait\(\)" PhotoLocator --glob='*.cs' | head -n 50
# Find any callers that don't unwrap AggregateException from tasks
rg -n "await .*SaveToFile|SaveToFile\(" PhotoLocator --glob='*.cs' | head -n 100Repository: meesoft/PhotoLocator Length of output: 7508 🏁 Script executed: #!/bin/bash
set -euo pipefail
# 1) Find the command/method in VideoTransformCommands that contains the SaveToFile call.
# We'll print surrounding larger region and then locate its entry point.
FILE="PhotoLocator/VideoTransformCommands.cs"
# show matches for SaveToFile
echo "---- SaveToFile call sites in VideoTransformCommands.cs ----"
rg -n "GeneralFileFormatHandler\.SaveToFile\(" "$FILE" --glob='*.cs'
# Pick first match and dump a bigger region above it
FIRST_LN=$(rg -n "GeneralFileFormatHandler\.SaveToFile\(" "$FILE" --glob='*.cs' | head -n1 | cut -d: -f1)
echo "First SaveToFile line: $FIRST_LN"
START=$((FIRST_LN-220))
END=$((FIRST_LN+120))
echo "---- Context $START-$END ----"
sed -n "${START},${END}p" "$FILE" | cat -n
# 2) Look for wrapping Task.Run/await patterns around that processing delegate.
echo "---- Search for Task.Run / RunProcessWithProgressBar in VideoTransformCommands.cs ----"
rg -n "RunProcessWithProgressBarAsync|Task\.Run\(|Dispatcher\.|await Task\.Run" "$FILE" --glob='*.cs'
# 3) Find where output file extension is determined for image sequence/video/time-slice processing.
# Search for ".jxl" usage.
echo "---- Search for jxl in VideoTransformCommands.cs ----"
rg -n "\"?\.jxl|jxl\"" "$FILE" --glob='*.cs'
# 4) Find any other direct SaveToFile calls (not inside Task.Run) in repo.
echo "---- Find SaveToFile invocations not wrapped in Task.Run ----"
# Heuristic: show lines with SaveToFile( and look at nearest few lines above for Task.Run.
# We'll just list all SaveToFile calls with 20 lines context.
rg -n "GeneralFileFormatHandler\.SaveToFile\(" PhotoLocator --glob='*.cs' -C 2
# 5) Determine if any call to JpegXlFileFormatHandler.SaveToFile happens outside Task.Run.
echo "---- Calls to JpegXlFileFormatHandler.SaveToFile ----"
rg -n "JpegXlFileFormatHandler\.SaveToFile\(" PhotoLocator --glob='*.cs' -C 2Repository: meesoft/PhotoLocator Length of output: 21394 🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "---- GeneralFileFormatHandler.SaveToFile call sites (with small context) ----"
rg -n "GeneralFileFormatHandler\.SaveToFile\(" PhotoLocator --glob='*.cs' -C 3
echo "---- Any direct JpegXlFileFormatHandler.SaveToFile call sites (with context) ----"
rg -n "JpegXlFileFormatHandler\.SaveToFile\(" PhotoLocator --glob='*.cs' -C 3
echo "---- Call sites of JpegXlFileFormatHandler.SaveToFile within Task.Run bodies (heuristic) ----"
rg -n "Task\.Run\([^)]*\)[\s\S]*JpegXlFileFormatHandler\.SaveToFile" PhotoLocator --glob='*.cs' || true
echo "---- Identify if any SaveToFile call happens outside Task.Run ----"
# Heuristic: list SaveToFile call lines and 5 lines above, then user can see if Task.Run is near.
rg -n "GeneralFileFormatHandler\.SaveToFile\(" PhotoLocator --glob='*.cs' | cut -d: -f1 | sort -u | while read -r file; do
:
doneRepository: meesoft/PhotoLocator Length of output: 8668 Remove the
🤖 Prompt for AI Agents |
||
| } | ||
|
|
||
| public static void TranscodeToJxl(string sourcePath, string targetPath, string? arguments, CancellationToken ct) | ||
| { | ||
| using var process = new Process(); | ||
| process.StartInfo.FileName = EncoderPath; | ||
| process.StartInfo.UseShellExecute = false; | ||
| process.StartInfo.RedirectStandardError = true; | ||
| process.StartInfo.CreateNoWindow = true; | ||
| process.StartInfo.Arguments = $"\"{sourcePath}\" \"{targetPath}\" {arguments}"; | ||
| process.Start(); | ||
| string? output = null; | ||
| var outputTask = Task.Run(() => output = process.StandardError.ReadToEnd(), ct); | ||
| try | ||
| { | ||
| try | ||
| { | ||
| if (!process.WaitForExit(60000)) | ||
| throw new TimeoutException(); | ||
| if (process.ExitCode != 0) | ||
| throw new IOException("Codec failed with exit code " + process.ExitCode); | ||
| } | ||
| finally | ||
| { | ||
| if (outputTask.Wait(1000, ct)) | ||
| Log.Write(output); | ||
| } | ||
| } | ||
| catch (Exception ex) | ||
| { | ||
| if (output is null) | ||
| throw; | ||
| throw new IOException("Codec failed with: " + output, ex); | ||
|
meesoft marked this conversation as resolved.
|
||
| } | ||
| } | ||
| } | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.