Compare commits

...

1 Commits

Author SHA1 Message Date
Clint Rutkas
80389dca7c testing out to see how this works 2025-07-24 09:21:29 -07:00
11 changed files with 623 additions and 1 deletions

2
deps/cziplib vendored

View File

@@ -330,6 +330,8 @@
<RowDefinition Height="*" />
<RowDefinition Height="28" />
<RowDefinition Height="*" />
<RowDefinition Height="28" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<TextBlock x:Uid="DateTimeCheatSheet_Title" FontWeight="SemiBold" />
<ListView
@@ -451,6 +453,47 @@
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<TextBlock
x:Uid="ExifCheatSheet_Title"
Grid.Row="6"
Margin="0,10,0,0"
FontWeight="SemiBold" />
<ListView
Grid.Row="7"
Margin="-4,12,0,0"
IsItemClickEnabled="True"
ItemClick="DateTimeItemClick"
ItemsSource="{x:Bind ExifShortcuts}"
SelectionMode="None">
<ListView.ItemTemplate>
<DataTemplate x:DataType="local:PatternSnippet">
<Grid Margin="-10,0,0,0" ColumnSpacing="8">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Border
Padding="8"
HorizontalAlignment="Left"
Background="{ThemeResource ButtonBackground}"
BorderBrush="{ThemeResource ButtonBorderBrush}"
BorderThickness="1"
CornerRadius="4">
<TextBlock
FontFamily="Consolas"
Foreground="{ThemeResource ButtonForeground}"
Text="{x:Bind Code}" />
</Border>
<TextBlock
Grid.Column="1"
VerticalAlignment="Center"
FontSize="12"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
Text="{x:Bind Description}" />
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
</Flyout>
</Button.Flyout>

View File

@@ -225,6 +225,18 @@ namespace winrt::PowerRenameUI::implementation
m_RandomizerShortcuts.Append(winrt::make<PatternSnippet>(L"${rstringdigit=36}", manager.MainResourceMap().GetValue(L"Resources/RandomizerCheatSheet_Digit").ValueAsString()));
m_RandomizerShortcuts.Append(winrt::make<PatternSnippet>(L"${ruuidv4}", manager.MainResourceMap().GetValue(L"Resources/RandomizerCheatSheet_Uuid").ValueAsString()));
m_exifShortcuts = winrt::single_threaded_observable_vector<PowerRenameUI::PatternSnippet>();
m_exifShortcuts.Append(winrt::make<PatternSnippet>(L"$CameraMake", manager.MainResourceMap().GetValue(L"Resources/ExifCheatSheet_CameraMake").ValueAsString()));
m_exifShortcuts.Append(winrt::make<PatternSnippet>(L"$CameraModel", manager.MainResourceMap().GetValue(L"Resources/ExifCheatSheet_CameraModel").ValueAsString()));
m_exifShortcuts.Append(winrt::make<PatternSnippet>(L"$LensModel", manager.MainResourceMap().GetValue(L"Resources/ExifCheatSheet_LensModel").ValueAsString()));
m_exifShortcuts.Append(winrt::make<PatternSnippet>(L"$FNumber", manager.MainResourceMap().GetValue(L"Resources/ExifCheatSheet_FNumber").ValueAsString()));
m_exifShortcuts.Append(winrt::make<PatternSnippet>(L"$ISO", manager.MainResourceMap().GetValue(L"Resources/ExifCheatSheet_ISO").ValueAsString()));
m_exifShortcuts.Append(winrt::make<PatternSnippet>(L"$ExposureTime", manager.MainResourceMap().GetValue(L"Resources/ExifCheatSheet_ExposureTime").ValueAsString()));
m_exifShortcuts.Append(winrt::make<PatternSnippet>(L"$FocalLength", manager.MainResourceMap().GetValue(L"Resources/ExifCheatSheet_FocalLength").ValueAsString()));
m_exifShortcuts.Append(winrt::make<PatternSnippet>(L"$ExifDateTaken", manager.MainResourceMap().GetValue(L"Resources/ExifCheatSheet_ExifDateTaken").ValueAsString()));
m_exifShortcuts.Append(winrt::make<PatternSnippet>(L"$ImageWidth", manager.MainResourceMap().GetValue(L"Resources/ExifCheatSheet_ImageWidth").ValueAsString()));
m_exifShortcuts.Append(winrt::make<PatternSnippet>(L"$ImageHeight", manager.MainResourceMap().GetValue(L"Resources/ExifCheatSheet_ImageHeight").ValueAsString()));
InitializeComponent();
m_etwTrace.UpdateState(true);

View File

@@ -88,6 +88,7 @@ namespace winrt::PowerRenameUI::implementation
winrt::Windows::Foundation::Collections::IObservableVector<PowerRenameUI::PatternSnippet> DateTimeShortcuts() { return m_dateTimeShortcuts; }
winrt::Windows::Foundation::Collections::IObservableVector<PowerRenameUI::PatternSnippet> CounterShortcuts() { return m_CounterShortcuts; }
winrt::Windows::Foundation::Collections::IObservableVector<PowerRenameUI::PatternSnippet> RandomizerShortcuts() { return m_RandomizerShortcuts; }
winrt::Windows::Foundation::Collections::IObservableVector<PowerRenameUI::PatternSnippet> ExifShortcuts() { return m_exifShortcuts; }
hstring OriginalCount();
void OriginalCount(hstring value);
@@ -111,6 +112,7 @@ namespace winrt::PowerRenameUI::implementation
winrt::Windows::Foundation::Collections::IObservableVector<PowerRenameUI::PatternSnippet> m_dateTimeShortcuts;
winrt::Windows::Foundation::Collections::IObservableVector<PowerRenameUI::PatternSnippet> m_CounterShortcuts;
winrt::Windows::Foundation::Collections::IObservableVector<PowerRenameUI::PatternSnippet> m_RandomizerShortcuts;
winrt::Windows::Foundation::Collections::IObservableVector<PowerRenameUI::PatternSnippet> m_exifShortcuts;
// Used by PowerRenameManagerEvents
HRESULT OnRename(_In_ IPowerRenameItem* renameItem);

View File

@@ -405,6 +405,39 @@
<data name="RandomizerCheatSheet_Uuid" xml:space="preserve">
<value>Random UUID according to v4 specification.</value>
</data>
<data name="ExifCheatSheet_Title.Text" xml:space="preserve">
<value>Replace using EXIF metadata</value>
</data>
<data name="ExifCheatSheet_CameraMake" xml:space="preserve">
<value>Camera manufacturer from EXIF data</value>
</data>
<data name="ExifCheatSheet_CameraModel" xml:space="preserve">
<value>Camera model from EXIF data</value>
</data>
<data name="ExifCheatSheet_LensModel" xml:space="preserve">
<value>Lens model from EXIF data</value>
</data>
<data name="ExifCheatSheet_FNumber" xml:space="preserve">
<value>F-number (aperture) from EXIF data</value>
</data>
<data name="ExifCheatSheet_ISO" xml:space="preserve">
<value>ISO sensitivity from EXIF data</value>
</data>
<data name="ExifCheatSheet_ExposureTime" xml:space="preserve">
<value>Exposure time (shutter speed) from EXIF data</value>
</data>
<data name="ExifCheatSheet_FocalLength" xml:space="preserve">
<value>Focal length from EXIF data</value>
</data>
<data name="ExifCheatSheet_ExifDateTaken" xml:space="preserve">
<value>Date photo was taken from EXIF data</value>
</data>
<data name="ExifCheatSheet_ImageWidth" xml:space="preserve">
<value>Image width in pixels from EXIF data</value>
</data>
<data name="ExifCheatSheet_ImageHeight" xml:space="preserve">
<value>Image height in pixels from EXIF data</value>
</data>
<data name="ToggleButton_RandItems.[using:Microsoft.UI.Xaml.Automation]AutomationProperties.Name" xml:space="preserve">
<value>Random string features</value>
</data>

View File

@@ -0,0 +1,273 @@
#include "pch.h"
#include "ExifReader.h"
#include <propvarutil.h>
#include <sstream>
#include <iomanip>
ExifReader::ExifReader() : m_hasExifData(false)
{
InitializeWIC();
}
ExifReader::~ExifReader()
{
ClearExifData();
}
HRESULT ExifReader::InitializeWIC()
{
return CoCreateInstance(
CLSID_WICImagingFactory,
nullptr,
CLSCTX_INPROC_SERVER,
IID_PPV_ARGS(&m_pWICFactory));
}
HRESULT ExifReader::ReadExifData(_In_ PCWSTR filePath)
{
ClearExifData();
if (!m_pWICFactory)
{
return E_FAIL;
}
return ExtractExifFromFile(filePath);
}
HRESULT ExifReader::ExtractExifFromFile(_In_ PCWSTR filePath)
{
Microsoft::WRL::ComPtr<IWICBitmapDecoder> pDecoder;
HRESULT hr = m_pWICFactory->CreateDecoderFromFilename(
filePath,
nullptr,
GENERIC_READ,
WICDecodeMetadataCacheOnLoad,
&pDecoder);
if (FAILED(hr))
{
return hr;
}
Microsoft::WRL::ComPtr<IWICBitmapFrameDecode> pFrame;
hr = pDecoder->GetFrame(0, &pFrame);
if (FAILED(hr))
{
return hr;
}
Microsoft::WRL::ComPtr<IWICMetadataQueryReader> pReader;
hr = pFrame->GetMetadataQueryReader(&pReader);
if (FAILED(hr))
{
return hr;
}
// Extract common EXIF data
std::wstring value;
// Camera information
if (SUCCEEDED(ReadMetadataProperty(pReader.Get(), L"/app1/ifd/exif/{ushort=271}", value)))
m_exifData[L"CameraMake"] = value;
if (SUCCEEDED(ReadMetadataProperty(pReader.Get(), L"/app1/ifd/exif/{ushort=272}", value)))
m_exifData[L"CameraModel"] = value;
if (SUCCEEDED(ReadMetadataProperty(pReader.Get(), L"/app1/ifd/exif/{ushort=42036}", value)))
m_exifData[L"LensModel"] = value;
// Shooting parameters
PROPVARIANT prop;
PropVariantInit(&prop);
if (SUCCEEDED(pReader->GetMetadataByName(L"/app1/ifd/exif/{ushort=33434}", &prop)))
{
m_exifData[L"ExposureTime"] = FormatExposureTime(prop);
PropVariantClear(&prop);
}
if (SUCCEEDED(pReader->GetMetadataByName(L"/app1/ifd/exif/{ushort=33437}", &prop)))
{
m_exifData[L"FNumber"] = FormatFNumber(prop);
PropVariantClear(&prop);
}
if (SUCCEEDED(pReader->GetMetadataByName(L"/app1/ifd/exif/{ushort=34855}", &prop)))
{
if (prop.vt == VT_UI2)
m_exifData[L"ISO"] = std::to_wstring(prop.uiVal);
PropVariantClear(&prop);
}
if (SUCCEEDED(pReader->GetMetadataByName(L"/app1/ifd/exif/{ushort=37386}", &prop)))
{
m_exifData[L"FocalLength"] = FormatRational(prop);
PropVariantClear(&prop);
}
// Date/Time
if (SUCCEEDED(ReadMetadataProperty(pReader.Get(), L"/app1/ifd/exif/{ushort=36867}", value)))
m_exifData[L"ExifDateTaken"] = value;
if (SUCCEEDED(ReadMetadataProperty(pReader.Get(), L"/app1/ifd/exif/{ushort=306}", value)))
m_exifData[L"ExifDateTime"] = value;
// Image dimensions
UINT width = 0, height = 0;
if (SUCCEEDED(pFrame->GetSize(&width, &height)))
{
m_exifData[L"ImageWidth"] = std::to_wstring(width);
m_exifData[L"ImageHeight"] = std::to_wstring(height);
}
// Orientation
if (SUCCEEDED(pReader->GetMetadataByName(L"/app1/ifd/{ushort=274}", &prop)))
{
if (prop.vt == VT_UI2)
m_exifData[L"Orientation"] = std::to_wstring(prop.uiVal);
PropVariantClear(&prop);
}
// GPS data (if available)
if (SUCCEEDED(ReadMetadataProperty(pReader.Get(), L"/app1/ifd/gps/{ushort=2}", value)))
m_exifData[L"GPSLatitude"] = value;
if (SUCCEEDED(ReadMetadataProperty(pReader.Get(), L"/app1/ifd/gps/{ushort=4}", value)))
m_exifData[L"GPSLongitude"] = value;
if (SUCCEEDED(ReadMetadataProperty(pReader.Get(), L"/app1/ifd/gps/{ushort=6}", value)))
m_exifData[L"GPSAltitude"] = value;
m_hasExifData = !m_exifData.empty();
return m_hasExifData ? S_OK : S_FALSE;
}
HRESULT ExifReader::ReadMetadataProperty(_In_ IWICMetadataQueryReader* pReader, _In_ PCWSTR query, _Out_ std::wstring& value)
{
PROPVARIANT prop;
PropVariantInit(&prop);
HRESULT hr = pReader->GetMetadataByName(query, &prop);
if (SUCCEEDED(hr))
{
if (prop.vt == VT_LPSTR && prop.pszVal)
{
// Convert ANSI to Unicode
int len = MultiByteToWideChar(CP_ACP, 0, prop.pszVal, -1, nullptr, 0);
if (len > 0)
{
std::vector<wchar_t> buffer(len);
MultiByteToWideChar(CP_ACP, 0, prop.pszVal, -1, buffer.data(), len);
value = buffer.data();
}
}
else if (prop.vt == VT_LPWSTR && prop.pwszVal)
{
value = prop.pwszVal;
}
else if (prop.vt == VT_UI2)
{
value = std::to_wstring(prop.uiVal);
}
else if (prop.vt == VT_UI4)
{
value = std::to_wstring(prop.ulVal);
}
else if (prop.vt == (VT_UI4 | VT_VECTOR) && prop.caul.cElems >= 2)
{
// Rational number (numerator/denominator)
double rational = static_cast<double>(prop.caul.pElems[0]) / static_cast<double>(prop.caul.pElems[1]);
std::wostringstream oss;
oss << std::fixed << std::setprecision(2) << rational;
value = oss.str();
}
PropVariantClear(&prop);
}
return hr;
}
std::wstring ExifReader::FormatExposureTime(const PROPVARIANT& prop)
{
if (prop.vt == (VT_UI4 | VT_VECTOR) && prop.caul.cElems >= 2)
{
ULONG numerator = prop.caul.pElems[0];
ULONG denominator = prop.caul.pElems[1];
if (numerator == 1)
{
return L"1/" + std::to_wstring(denominator);
}
else if (denominator == 1)
{
return std::to_wstring(numerator);
}
else
{
double exposure = static_cast<double>(numerator) / static_cast<double>(denominator);
std::wostringstream oss;
oss << std::fixed << std::setprecision(3) << exposure;
return oss.str();
}
}
return L"";
}
std::wstring ExifReader::FormatFNumber(const PROPVARIANT& prop)
{
if (prop.vt == (VT_UI4 | VT_VECTOR) && prop.caul.cElems >= 2)
{
double fNumber = static_cast<double>(prop.caul.pElems[0]) / static_cast<double>(prop.caul.pElems[1]);
std::wostringstream oss;
oss << L"f/" << std::fixed << std::setprecision(1) << fNumber;
return oss.str();
}
return L"";
}
std::wstring ExifReader::FormatRational(const PROPVARIANT& prop)
{
if (prop.vt == (VT_UI4 | VT_VECTOR) && prop.caul.cElems >= 2)
{
double value = static_cast<double>(prop.caul.pElems[0]) / static_cast<double>(prop.caul.pElems[1]);
std::wostringstream oss;
oss << std::fixed << std::setprecision(1) << value;
return oss.str();
}
return L"";
}
std::wstring ExifReader::FormatDateTime(const PROPVARIANT& prop)
{
// EXIF DateTime format is "YYYY:MM:DD HH:MM:SS"
// We'll return it as-is, but could format differently if needed
if (prop.vt == VT_LPSTR && prop.pszVal)
{
int len = MultiByteToWideChar(CP_ACP, 0, prop.pszVal, -1, nullptr, 0);
if (len > 0)
{
std::vector<wchar_t> buffer(len);
MultiByteToWideChar(CP_ACP, 0, prop.pszVal, -1, buffer.data(), len);
return buffer.data();
}
}
return L"";
}
std::wstring ExifReader::GetExifValue(_In_ PCWSTR parameterName)
{
auto it = m_exifData.find(parameterName);
if (it != m_exifData.end())
{
return it->second;
}
return L"";
}
void ExifReader::ClearExifData()
{
m_exifData.clear();
m_hasExifData = false;
}

View File

@@ -0,0 +1,31 @@
#pragma once
#include <string>
#include <unordered_map>
#include <wincodec.h>
#include <wrl/client.h>
class ExifReader
{
public:
ExifReader();
~ExifReader();
HRESULT ReadExifData(_In_ PCWSTR filePath);
std::wstring GetExifValue(_In_ PCWSTR parameterName);
bool HasExifData() const { return m_hasExifData; }
private:
Microsoft::WRL::ComPtr<IWICImagingFactory> m_pWICFactory;
std::unordered_map<std::wstring, std::wstring> m_exifData;
bool m_hasExifData;
HRESULT InitializeWIC();
HRESULT ExtractExifFromFile(_In_ PCWSTR filePath);
HRESULT ReadMetadataProperty(_In_ IWICMetadataQueryReader* pReader, _In_ PCWSTR query, _Out_ std::wstring& value);
std::wstring FormatExposureTime(_In_ const PROPVARIANT& prop);
std::wstring FormatFNumber(_In_ const PROPVARIANT& prop);
std::wstring FormatRational(_In_ const PROPVARIANT& prop);
std::wstring FormatDateTime(_In_ const PROPVARIANT& prop);
void ClearExifData();
};

View File

@@ -1,5 +1,6 @@
#include "pch.h"
#include "Helpers.h"
#include "ExifReader.h"
#include <regex>
#include <ShlGuid.h>
#include <cstring>
@@ -707,4 +708,188 @@ std::wstring CreateGuidStringWithoutBrackets()
}
return L"";
}
bool isExifUsed(_In_ PCWSTR source)
{
if (!source || wcslen(source) == 0)
{
return false;
}
bool used = false;
static const std::array patterns = {
std::wregex{ L"(([^\\$]|^)(\\$\\$)*)\\$CameraMake" },
std::wregex{ L"(([^\\$]|^)(\\$\\$)*)\\$CameraModel" },
std::wregex{ L"(([^\\$]|^)(\\$\\$)*)\\$LensModel" },
std::wregex{ L"(([^\\$]|^)(\\$\\$)*)\\$FNumber" },
std::wregex{ L"(([^\\$]|^)(\\$\\$)*)\\$ISO" },
std::wregex{ L"(([^\\$]|^)(\\$\\$)*)\\$ExposureTime" },
std::wregex{ L"(([^\\$]|^)(\\$\\$)*)\\$FocalLength" },
std::wregex{ L"(([^\\$]|^)(\\$\\$)*)\\$ExifDateTime" },
std::wregex{ L"(([^\\$]|^)(\\$\\$)*)\\$ExifDateTaken" },
std::wregex{ L"(([^\\$]|^)(\\$\\$)*)\\$ImageWidth" },
std::wregex{ L"(([^\\$]|^)(\\$\\$)*)\\$ImageHeight" },
std::wregex{ L"(([^\\$]|^)(\\$\\$)*)\\$Orientation" },
std::wregex{ L"(([^\\$]|^)(\\$\\$)*)\\$GPSLatitude" },
std::wregex{ L"(([^\\$]|^)(\\$\\$)*)\\$GPSLongitude" },
std::wregex{ L"(([^\\$]|^)(\\$\\$)*)\\$GPSAltitude" }
};
for (size_t i = 0; !used && i < patterns.size(); i++)
{
if (std::regex_search(source, patterns[i]))
{
used = true;
}
}
return used;
}
HRESULT GetExifFileName(_Out_ PWSTR result, UINT cchMax, _In_ PCWSTR source, _In_ PCWSTR filePath)
{
HRESULT hr = E_INVALIDARG;
if (!source || wcslen(source) == 0 || !filePath || wcslen(filePath) == 0)
{
return hr;
}
// Check if file is an image by extension
std::wstring filePathStr(filePath);
std::wstring extension = filePathStr.substr(filePathStr.find_last_of(L"."));
std::transform(extension.begin(), extension.end(), extension.begin(), ::towlower);
if (extension != L".jpg" && extension != L".jpeg" && extension != L".tif" &&
extension != L".tiff" && extension != L".png" && extension != L".bmp")
{
// Not an image file, return original source
return StringCchCopy(result, cchMax, source);
}
ExifReader exifReader;
hr = exifReader.ReadExifData(filePath);
if (FAILED(hr) || !exifReader.HasExifData())
{
// No EXIF data available, return original source
return StringCchCopy(result, cchMax, source);
}
std::wstring res(source);
wchar_t replaceTerm[MAX_PATH] = { 0 };
// Replace EXIF parameters with actual values
std::wstring value;
// Camera information
value = exifReader.GetExifValue(L"CameraMake");
if (!value.empty())
{
StringCchPrintf(replaceTerm, MAX_PATH, TEXT("%s%s"), L"$01", value.c_str());
res = regex_replace(res, std::wregex(L"(([^\\$]|^)(\\$\\$)*)\\$CameraMake"), replaceTerm);
}
value = exifReader.GetExifValue(L"CameraModel");
if (!value.empty())
{
StringCchPrintf(replaceTerm, MAX_PATH, TEXT("%s%s"), L"$01", value.c_str());
res = regex_replace(res, std::wregex(L"(([^\\$]|^)(\\$\\$)*)\\$CameraModel"), replaceTerm);
}
value = exifReader.GetExifValue(L"LensModel");
if (!value.empty())
{
StringCchPrintf(replaceTerm, MAX_PATH, TEXT("%s%s"), L"$01", value.c_str());
res = regex_replace(res, std::wregex(L"(([^\\$]|^)(\\$\\$)*)\\$LensModel"), replaceTerm);
}
// Shooting parameters
value = exifReader.GetExifValue(L"FNumber");
if (!value.empty())
{
StringCchPrintf(replaceTerm, MAX_PATH, TEXT("%s%s"), L"$01", value.c_str());
res = regex_replace(res, std::wregex(L"(([^\\$]|^)(\\$\\$)*)\\$FNumber"), replaceTerm);
}
value = exifReader.GetExifValue(L"ISO");
if (!value.empty())
{
StringCchPrintf(replaceTerm, MAX_PATH, TEXT("%s%s"), L"$01", value.c_str());
res = regex_replace(res, std::wregex(L"(([^\\$]|^)(\\$\\$)*)\\$ISO"), replaceTerm);
}
value = exifReader.GetExifValue(L"ExposureTime");
if (!value.empty())
{
StringCchPrintf(replaceTerm, MAX_PATH, TEXT("%s%s"), L"$01", value.c_str());
res = regex_replace(res, std::wregex(L"(([^\\$]|^)(\\$\\$)*)\\$ExposureTime"), replaceTerm);
}
value = exifReader.GetExifValue(L"FocalLength");
if (!value.empty())
{
StringCchPrintf(replaceTerm, MAX_PATH, TEXT("%s%s"), L"$01", value.c_str());
res = regex_replace(res, std::wregex(L"(([^\\$]|^)(\\$\\$)*)\\$FocalLength"), replaceTerm);
}
// Date/Time
value = exifReader.GetExifValue(L"ExifDateTaken");
if (!value.empty())
{
StringCchPrintf(replaceTerm, MAX_PATH, TEXT("%s%s"), L"$01", value.c_str());
res = regex_replace(res, std::wregex(L"(([^\\$]|^)(\\$\\$)*)\\$ExifDateTaken"), replaceTerm);
}
value = exifReader.GetExifValue(L"ExifDateTime");
if (!value.empty())
{
StringCchPrintf(replaceTerm, MAX_PATH, TEXT("%s%s"), L"$01", value.c_str());
res = regex_replace(res, std::wregex(L"(([^\\$]|^)(\\$\\$)*)\\$ExifDateTime"), replaceTerm);
}
// Image dimensions
value = exifReader.GetExifValue(L"ImageWidth");
if (!value.empty())
{
StringCchPrintf(replaceTerm, MAX_PATH, TEXT("%s%s"), L"$01", value.c_str());
res = regex_replace(res, std::wregex(L"(([^\\$]|^)(\\$\\$)*)\\$ImageWidth"), replaceTerm);
}
value = exifReader.GetExifValue(L"ImageHeight");
if (!value.empty())
{
StringCchPrintf(replaceTerm, MAX_PATH, TEXT("%s%s"), L"$01", value.c_str());
res = regex_replace(res, std::wregex(L"(([^\\$]|^)(\\$\\$)*)\\$ImageHeight"), replaceTerm);
}
value = exifReader.GetExifValue(L"Orientation");
if (!value.empty())
{
StringCchPrintf(replaceTerm, MAX_PATH, TEXT("%s%s"), L"$01", value.c_str());
res = regex_replace(res, std::wregex(L"(([^\\$]|^)(\\$\\$)*)\\$Orientation"), replaceTerm);
}
// GPS data
value = exifReader.GetExifValue(L"GPSLatitude");
if (!value.empty())
{
StringCchPrintf(replaceTerm, MAX_PATH, TEXT("%s%s"), L"$01", value.c_str());
res = regex_replace(res, std::wregex(L"(([^\\$]|^)(\\$\\$)*)\\$GPSLatitude"), replaceTerm);
}
value = exifReader.GetExifValue(L"GPSLongitude");
if (!value.empty())
{
StringCchPrintf(replaceTerm, MAX_PATH, TEXT("%s%s"), L"$01", value.c_str());
res = regex_replace(res, std::wregex(L"(([^\\$]|^)(\\$\\$)*)\\$GPSLongitude"), replaceTerm);
}
value = exifReader.GetExifValue(L"GPSAltitude");
if (!value.empty())
{
StringCchPrintf(replaceTerm, MAX_PATH, TEXT("%s%s"), L"$01", value.c_str());
res = regex_replace(res, std::wregex(L"(([^\\$]|^)(\\$\\$)*)\\$GPSAltitude"), replaceTerm);
}
hr = StringCchCopy(result, cchMax, res.c_str());
return hr;
}

View File

@@ -8,6 +8,8 @@ HRESULT GetTrimmedFileName(_Out_ PWSTR result, UINT cchMax, _In_ PCWSTR source);
HRESULT GetTransformedFileName(_Out_ PWSTR result, UINT cchMax, _In_ PCWSTR source, DWORD flags, bool isFolder);
HRESULT GetDatedFileName(_Out_ PWSTR result, UINT cchMax, _In_ PCWSTR source, SYSTEMTIME fileTime);
bool isFileTimeUsed(_In_ PCWSTR source);
HRESULT GetExifFileName(_Out_ PWSTR result, UINT cchMax, _In_ PCWSTR source, _In_ PCWSTR filePath);
bool isExifUsed(_In_ PCWSTR source);
bool ShellItemArrayContainsRenamableItem(_In_ IShellItemArray* shellItemArray);
bool DataObjectContainsRenamableItem(_In_ IUnknown* dataSource);
HRESULT GetShellItemArrayFromDataObject(_In_ IUnknown* dataSource, _COM_Outptr_ IShellItemArray** items);

View File

@@ -32,6 +32,7 @@
</ItemDefinitionGroup>
<ItemGroup>
<ClInclude Include="Enumerating.h" />
<ClInclude Include="ExifReader.h" />
<ClInclude Include="Helpers.h" />
<ClInclude Include="MRUListHandler.h" />
<ClInclude Include="PowerRenameEnum.h" />
@@ -50,6 +51,7 @@
</ItemGroup>
<ItemGroup>
<ClCompile Include="Enumerating.cpp" />
<ClCompile Include="ExifReader.cpp" />
<ClCompile Include="Helpers.cpp" />
<ClCompile Include="MRUListHandler.cpp" />
<ClCompile Include="PowerRenameEnum.cpp" />

View File

@@ -14,6 +14,8 @@ bool DoRename(CComPtr<IPowerRenameRegEx>& spRenameRegEx, unsigned long& itemEnum
PWSTR replaceTerm = nullptr;
bool useFileTime = false;
bool useExif = false;
wchar_t expandedReplaceTerm[MAX_PATH] = { 0 };
winrt::check_hresult(spRenameRegEx->GetReplaceTerm(&replaceTerm));
@@ -21,6 +23,11 @@ bool DoRename(CComPtr<IPowerRenameRegEx>& spRenameRegEx, unsigned long& itemEnum
{
useFileTime = true;
}
if (isExifUsed(replaceTerm))
{
useExif = true;
}
CoTaskMemFree(replaceTerm);
int id = -1;
@@ -82,12 +89,44 @@ bool DoRename(CComPtr<IPowerRenameRegEx>& spRenameRegEx, unsigned long& itemEnum
winrt::check_hresult(spRenameRegEx->PutFileTime(fileTime));
}
// Process EXIF metadata if needed
if (useExif)
{
PWSTR filePath = nullptr;
winrt::check_hresult(spItem->GetPath(&filePath));
// Get the current replace term again to expand EXIF patterns
PWSTR currentReplaceTerm = nullptr;
winrt::check_hresult(spRenameRegEx->GetReplaceTerm(&currentReplaceTerm));
// Expand EXIF patterns in replace term
HRESULT hr = GetExifFileName(expandedReplaceTerm, ARRAYSIZE(expandedReplaceTerm), currentReplaceTerm, filePath);
if (SUCCEEDED(hr))
{
// Temporarily set the expanded replace term
winrt::check_hresult(spRenameRegEx->PutReplaceTerm(expandedReplaceTerm, true));
}
CoTaskMemFree(currentReplaceTerm);
CoTaskMemFree(filePath);
}
PWSTR newName = nullptr;
// Failure here means we didn't match anything or had nothing to match
// Call put_newName with null in that case to reset it
winrt::check_hresult(spRenameRegEx->Replace(sourceName, &newName, itemEnumIndex));
// Restore original replace term if we modified it for EXIF
if (useExif)
{
PWSTR originalReplaceTerm = nullptr;
winrt::check_hresult(spRenameRegEx->GetReplaceTerm(&originalReplaceTerm));
// Note: We don't need to restore here since the replace term was temporarily changed
// The original replace term is preserved in the RegEx object
CoTaskMemFree(originalReplaceTerm);
}
if (useFileTime)
{
winrt::check_hresult(spRenameRegEx->ResetFileTime());