| Windows | Linux | macOS | |
|---|---|---|---|
| Build | |||
| CodeQL | |||
| Release |
I used this repo as a base to shape my own PhotoManager with optimizations, new features, bugs fix... Thank you jpablodrexler for your work β€οΈ
PhotoManager is a desktop application that allows:
- Visualization of image galleries.
- Find duplicates.
- Copy / move images.
- Know if images are corrupted or rotated.
- Import / export images from local folders / shared folders in the local network.
- Delete images in local or shared folders that are not present in source folder.
- Detect videos duplicates.
- Convert assets between HEIC, PNG, and JPG.
- Update editable settings and the application theme from the UI.
It is a local tool, that does not require an Internet connection to work. Your data stay on your computer and nothing is collected from you. The SQLite database is stored locally on your computer.
- Handling corrupted images and videos.
Open the PhotoManager\PhotoManager.UI\appsettings.json and configure it.
Basic usage: run the .exe file.
Advanced usage: open the solution file PhotoManager\PhotoManager.slnx, set PhotoManager\PhotoManager.UI\PhotoManager.UI.csproj as the project to launch and run it.
- Download the archive for your platform from the latest release:
- Windows:
photo-manager-{version}-win-x64.zip - Linux:
photo-manager-{version}-linux-x64.tar.gz - macOS (Intel):
photo-manager-{version}-osx-x64.tar.gz - macOS (Apple Silicon):
photo-manager-{version}-osx-arm64.tar.gz
- Windows:
- Extract the archive to a new folder.
- Configure the appsettings.json file
- Run the application:
- Windows: Run
PhotoManager.UI.exe - Linux / macOS: Run
./PhotoManager.UI(you may need tochmod +x PhotoManager.UIfirst)
- Windows: Run
- The application automatically creates a
Database/folder next to the executable to store the database and backups.
If you use the .exe file, you can find a appsettings.json file in the same directory.
You can also find it at PhotoManager\PhotoManager.UI\appsettings.json.
The aim is to let you configure it as you need.
On first startup, PhotoManager reads these values and saves all editable settings in the local SQLite database.
After that, the database values override appsettings.json. Use the Settings menu in the app to update them.
The Project section is the only exception: it always stays in appsettings.json and is not editable from the UI.
The Asset part is about settings of asset: πΌοΈ
AnalyseVideos = false: Enable it to extract the first frame from each videos, that will be stored in the folderFirstFrameVideosFolderName(Path settings).CorruptedMessage = "The asset is corrupted": The message to display when the asset is corrupted.RotatedMessage = "The asset has been rotated": The message to display when the asset has been rotated from the original.CatalogBatchSize = 100000: The max amount of pictures to analyse, once the number reached, the analyse will stop.CatalogCooldownMinutes = 2: The number of minutes before starting a new analysis.CorruptedImageOrientation = 10000: The default orientation for a corrupted image, while it's corrupted, the tool cannot determine it.DefaultExifOrientation = 1: The default Exif orientation (0 degree) if theSystem.Photo.Orientationvalue has not been stored in the metadata of the image.DetectThumbnails = false: Enable it to detect duplicates between a thumbnail and the original (needUsingPHashto be true).SyncAssetsEveryXMinutes = false: Enable it to sync your assets every X minutes (CatalogCooldownMinutes).ThumbnailMaxHeight = 150: The height of the thumbnail.ThumbnailMaxWidth = 200: The width of the thumbnail.
The Hash part is about settings of hash (for basic usages, you will not need to modify the settings): π©βπ»
PHashThreshold = 10: The value of the threshold (used whenUsingPHashis true).UsingDHash = false: Enable it to hash in DHash (Difference Hash). This hashing method returns "00000000000000" for some pictures.UsingMD5Hash = false: Enable it to hash in MD5.UsingPHash = false: Enable it to hash in PHash (Perceptual Hash). It can detect duplicates between rotated assets (improve detection), thumbnails, images part and same images with differents resolutions. Performances are decreased with PHash by 6 times (for ex: 0.17s for 140 pictures with SHA512 and 1.11s with PHash).
The HashingService works as follows:
- If
UsingPHashis βοΈ, then all the assets will have a PHash as hash type. - If
UsingPHashis β andUsingDHashis βοΈ, then all the assets will have a DHash as hash type. - If
UsingPHashis β,UsingDHashis β andUsingMD5Hashis βοΈ, then all the assets will have a MD5Hash as hash type. - If
UsingPHash,UsingDHashandUsingMD5Hashare β, then all the assets will have a basic hash type (SHA512).
About DetectThumbnails, you will need to set UsingPHash to true as well because:
Between Original and Thumbnail:
- PHash the hamming distance is 10/210 (the most accurate).
- DHash the hamming distance is 5/14.
- MD5Hash the hamming distance is 32/32.
- SHA512 the hamming distance is 118/128.
Moreover, there is a parameter that you can adjust following the need, it is PHashThreshold.
The max advised is less than 90 (for example 68 can detect false positives).
The default value is 10, because it can detect a Thumbnail and an original with low quality as duplicates.
But 5 or 6 is often used as a default value in image comparison libraries.
If the hamming distance is lower or equal to the PHashThreshold value, then it is a duplicate.
The lower the value of PHashThreshold, the more precise it is.
The Paths part is about settings of paths: π
AssetsDirectory = "~/Pictures": The directory where your assets are, to analyse them.ExemptedFolderPath = "~/Pictures/PhotoManagerExempted": The path where PhotoManager will protect your assets and if there are duplicates in others paths, you will be able to delete all of them except the assets in this exempted path.FirstFrameVideosFolderName = "OutputVideoFirstFrame": The folder to save the first frame for each video file (used if you setAnalyseVideosto true). The full path is built fromAssetsDirectoryand this folder name.
AssetsDirectory and ExemptedFolderPath support several expansion syntaxes:
| Syntax | Platform | Example |
|---|---|---|
~ or ~/... or ~\... |
All | ~/Pictures β C:\Users\<user>\Pictures (Windows) or /home/<user>/Pictures (Linux/macOS) |
%VARIABLE% |
Windows | %USERPROFILE%\Photos β C:\Users\<user>\Photos |
$VARIABLE or ${VARIABLE} |
Linux/macOS | $HOME/Pictures β /home/<user>/Pictures |
| Absolute path | All | C:\Photos (Windows) or /mnt/data/Photos (Linux/macOS) |
Cross-drive paths (Windows): You can point to any drive letter. For example:
{
"AssetsDirectory": "E:\\Workspace\\PhotoManager\\Test",
"ExemptedFolderPath": "E:\\Workspace\\PhotoManager\\Test\\Exempted"
}Cross-platform note: On Windows, forward slashes in paths (e.g. ~/Workspace/Photos) are automatically
normalized to backslashes. On Linux/macOS, backslashes are kept as-is by the OS path APIs.
Always use the native separator for your platform, or rely on ~ expansion which handles it for you.
Examples by platform:
Windows:
{
"AssetsDirectory": "C:\\Users\\John\\Pictures",
"ExemptedFolderPath": "C:\\Users\\John\\Pictures\\Exempted"
}Linux/macOS:
{
"AssetsDirectory": "~/Pictures",
"ExemptedFolderPath": "~/Pictures/Exempted"
}Using environment variables:
{
"AssetsDirectory": "%PHOTO_LIBRARY%",
"ExemptedFolderPath": "${PHOTO_LIBRARY}/Exempted"
}The Pipeline part is about catalog pipeline tuning (concurrency and buffering, update it only for a certain purpose): β‘
The catalog runs as a multi-stage pipeline (read files β process and hash β persist to database). These settings tune how many files are handled in parallel and how large the queues between stages are. The defaults suit most computers; on a more powerful machine you can raise the concurrency and buffer sizes to catalog faster.
ReadConcurrency = 4: The maximum number of files read from disk in parallel.ProcessConcurrency = 0: The number of parallel workers that build assets and thumbnails.0means use all available CPU cores.ReadBufferSize = 16: The capacity of the queue between the read stage and the process stage.ProcessBufferSize = 32: The capacity of the queue between the process stage and the persist stage.PersistBufferSize = 64: The capacity of the queue between the persist stage and the result callback.PersistBatchSize = 50: The number of assets written to the database per batch.
The Project part is about settings of project (there is no need to update it): ποΈ
Name = "PhotoManager: The name of the tool.Owner = "Toto": The name of the owner of the tool.
The Storage part is about settings of storage (update it only for a certain purpose): πΎ
BackupsToKeep = 2: The number of backups to keep (the oldest ones are deleted).ThumbnailsDictionaryEntriesToKeep = 5: The number of dictionnaries to keep (the key is the path of the current folder and the value is a dictionnary where the key is the asset's name and the value its data).
The Ui part is about desktop UI settings: π₯οΈ
ThemeMode = "System": Uses the operating system theme. Supported values areSystem,Light, andDark.
Open Settings > Settings... to update the editable Asset, Hash, Paths, Pipeline, Storage, and Ui settings.
The assets directory and exempted folder fields include a browse button that opens the native folder picker.
Saving writes the values to the local database, so they keep overriding appsettings.json on the next startup.
Open File > Convert Assets... to select images with the native file picker and convert:
- HEIC / HEIF to JPG or PNG.
- PNG to JPG.
- JPG / JPEG to PNG.
Converted files are written one by one to a Converted folder next to the selected source images.
Metadata is preserved when the target format supports it, but format-specific metadata can be lost during conversion.
- .bmp - Windows bitmap
- .dng - RAW Format
- .gif - Graphics Interchange Format
- .heic - Apple Format
- .ico - Icon file
- .jfif - JPEG File Interchange Format
- .jpeg, .jpg - Joint Photographic Experts Group
- .png - Portable Network Graphics
- .tiff, .tif - Tagged Image File Format
- .webp - WebP image
- .3g2 - Mobile video
- .3gp - Mobile video
- .asf - Advanced Systems Format
- .av1 - Video coding format for videos transmissions
- .avi - Audio Video Interleave
- .flv - Flash video
- .m4v - MP4 video
- .mkv - Matroska video
- .mov - QuickTime movie
- .mp4 - MP4 video
- .mpeg - Moving Picture Experts Group
- .mpg - Moving Picture Experts Group
- .ogv - Ogg Vorbis video
- .webm / av1 - WebM video
- .wmv - Windows Media Video
There are three ways to delete duplicates:
The button Delete β‘οΈ This will delete the selected duplicate.
The button DeleteAllButThis β‘οΈ This will delete all duplicates associated to this asset and keep the selected duplicate.
The button Delete Every Duplicates Linked To Exempt Folder β‘οΈ This will delete all duplicates found everywhere that has a duplicate linked in the ExemptedFolderPath, but all duplicates in the ExemptedFolderPath will be protected.
:memo: Note: The ExemptedFolder should not contain any folder inside, all assets must be in the root (otherwise, they will not be exempted).
PhotoManager is able to detect duplicates between videos.
To do so, you'll need to set AnalyseVideos to true and put all of your videos in a single folder.
It will create another folder (it should not exists), which is FirstFrameVideosFolderName, and will store the first frame for each video inside it (with the name of the video file).
:warning: Be aware that the FirstFrameVideosFolderName folder should be deleted before each run to prevent some conflicts.
- .NET 10.0
- Avalonia UI
- SQLite (via Microsoft.Data.Sqlite)
- NUnit
- NSubstitute
- MagickImage
- Microsoft Extensions Logging
- coverlet
- ReportGenerator
- FFMpegCore
To generate a local coverage report:
pwsh PhotoManager/test-with-coverage.ps1Use -Filter "FullyQualifiedName~ClassName" for focused runs. The report is written to PhotoManager/TestResults.
This project uses versioned FFmpeg runtime packages for video duplicate detection.
At build time, PhotoManager.Common copies ffmpeg and ffprobe from the matching Curiosity.FFmpeg.Runtimes.* NuGet package into the output folder under Ffmpeg/Bin.
This keeps frame extraction deterministic without keeping Windows-only RAR archives or a custom extraction task in the repository.
If you need to override the bundled binaries, set PHOTOMANAGER_FFMPEG_BINARY_FOLDER or FFMPEG_BINARY_FOLDER to a folder containing the platform-specific ffmpeg executable.
If neither the bundled folder nor an override exists, FFMpegCore falls back to resolving ffmpeg from PATH.
The remaining versioned dll is located here: PhotoManager\PhotoManager.Tests\MSBuildTask It is only used for the well working of the tests, accross each machine. The FileDateTask.dll is used to set a fixed date for every tests files used for integration testing. It is launched by a MSBuild custom task in here: PhotoManager\PhotoManager.Tests\PhotoManager.Tests.csproj
I've made a specific repo for the custom dll, injected in the project: photo-manager-tasks