Compare commits

...

6 Commits

Author SHA1 Message Date
Yu Leng (from Dev Box)
a1bb51530d init 2025-08-29 15:40:44 +08:00
Yu Leng (from Dev Box)
acff5cbf65 update 2025-08-29 13:51:16 +08:00
Yu Leng (from Dev Box)
ee76efdf06 update 2025-08-29 11:02:52 +08:00
Yu Leng (from Dev Box)
48ad7bf940 update 2025-08-29 10:36:19 +08:00
Yu Leng (from Dev Box)
f311b5a983 update 2025-08-28 16:26:32 +08:00
Yu Leng (from Dev Box)
911fb2b7bd init 2025-08-28 15:35:17 +08:00
23 changed files with 2536 additions and 33 deletions

3
.gitmodules vendored
View File

@@ -7,3 +7,6 @@
[submodule "deps/cziplib"]
path = deps/cziplib
url = https://github.com/kuba--/zip.git
[submodule "deps/TinyEXIF"]
path = deps/TinyEXIF
url = https://github.com/cdcseacave/TinyEXIF

1
deps/TinyEXIF vendored Submodule

Submodule deps/TinyEXIF added at 79e124e1e1

24
deps/TinyEXIF.props vendored Normal file
View File

@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<Project>
<ItemDefinitionGroup>
<ClCompile>
<AdditionalIncludeDirectories>$(MSBuildThisFileDirectory)TinyEXIF;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<PreprocessorDefinitions>TINYEXIF_NO_XMP_SUPPORT;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<!-- Disable all code analysis for TinyEXIF usage -->
<EnableCodeAnalysis>false</EnableCodeAnalysis>
<ExternalWarningLevel>TurnOffAllWarnings</ExternalWarningLevel>
<TreatWarningAsError>false</TreatWarningAsError>
</ClCompile>
</ItemDefinitionGroup>
<ItemGroup>
<ClCompile Include="$(MSBuildThisFileDirectory)TinyEXIF\TinyEXIF.cpp">
<EnableCodeAnalysis>false</EnableCodeAnalysis>
<ExternalWarningLevel>TurnOffAllWarnings</ExternalWarningLevel>
<TreatWarningAsError>false</TreatWarningAsError>
<PrecompiledHeader>NotUsing</PrecompiledHeader>
<WarningLevel>Level1</WarningLevel>
<DisableSpecificWarnings>26495;26451;26493;%(DisableSpecificWarnings)</DisableSpecificWarnings>
</ClCompile>
</ItemGroup>
</Project>

1
deps/cxxopts vendored Submodule

Submodule deps/cxxopts added at 12e496da3d

View File

@@ -69,7 +69,7 @@
<PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
</ClCompile>
<Link>
<AdditionalDependencies>kernel32.lib;user32.lib;dwmapi.lib;Shcore.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalDependencies>kernel32.lib;user32.lib;dwmapi.lib;Shcore.lib;windowscodecs.lib;propsys.lib;ole32.lib;%(AdditionalDependencies)</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)'=='Release'">
@@ -79,7 +79,7 @@
<Link>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<AdditionalDependencies>kernel32.lib;user32.lib;dwmapi.lib;Shcore.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalDependencies>kernel32.lib;user32.lib;dwmapi.lib;Shcore.lib;windowscodecs.lib;propsys.lib;ole32.lib;%(AdditionalDependencies)</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<ItemGroup>

View File

@@ -238,6 +238,9 @@ void App::OnLaunched(LaunchActivatedEventArgs const&)
#else
#define BUFSIZE 4096 * 4
// push test folder "C:\Users\yuleng\testcase"
g_files.push_back(L"C:\\Users\\yuleng\\testcase");
BOOL bSuccess;
WCHAR chBuf[BUFSIZE];
DWORD dwRead;

View File

@@ -583,6 +583,7 @@
<ComboBoxItem x:Uid="FileTimeParts_CreationTime" />
<ComboBoxItem x:Uid="FileTimeParts_ModificationTime" />
<ComboBoxItem x:Uid="FileTimeParts_AccessTime" />
<ComboBoxItem x:Uid="FileTimeParts_EXIFTime" />
</ComboBox>
</StackPanel>

View File

@@ -802,6 +802,7 @@ namespace winrt::PowerRenameUI::implementation
UpdateFlag(CreationTime, UpdateFlagCommand::Set);
UpdateFlag(ModificationTime, UpdateFlagCommand::Reset);
UpdateFlag(AccessTime, UpdateFlagCommand::Reset);
UpdateFlag(EXIFTime, UpdateFlagCommand::Reset);
}
else if (selectedIndex == 1)
{
@@ -809,6 +810,7 @@ namespace winrt::PowerRenameUI::implementation
UpdateFlag(ModificationTime, UpdateFlagCommand::Set);
UpdateFlag(CreationTime, UpdateFlagCommand::Reset);
UpdateFlag(AccessTime, UpdateFlagCommand::Reset);
UpdateFlag(EXIFTime, UpdateFlagCommand::Reset);
}
else if (selectedIndex == 2)
{
@@ -816,6 +818,15 @@ namespace winrt::PowerRenameUI::implementation
UpdateFlag(AccessTime, UpdateFlagCommand::Set);
UpdateFlag(CreationTime, UpdateFlagCommand::Reset);
UpdateFlag(ModificationTime, UpdateFlagCommand::Reset);
UpdateFlag(EXIFTime, UpdateFlagCommand::Reset);
}
else if (selectedIndex == 3)
{
// EXIF time
UpdateFlag(AccessTime, UpdateFlagCommand::Reset);
UpdateFlag(CreationTime, UpdateFlagCommand::Reset);
UpdateFlag(ModificationTime, UpdateFlagCommand::Reset);
UpdateFlag(EXIFTime, UpdateFlagCommand::Set);
}
});
}

View File

@@ -423,4 +423,7 @@
<data name="FileTimeParts_AccessTime.Content" xml:space="preserve">
<value>Access Time</value>
</data>
<data name="FileTimeParts_EXIFTime.Content" xml:space="preserve">
<value>Media EXIF Time</value>
</data>
</root>

View File

@@ -0,0 +1,830 @@
#include "pch.h"
#include "MediaMetadataExtractor.h"
#include <fstream>
#include <vector>
#include <sstream>
#include <iomanip>
#include <comdef.h>
#include <mutex>
#include <unordered_map>
#include <chrono>
#include <algorithm>
using namespace PowerRenameLib;
namespace
{
// WIC metadata property paths - comprehensive mapping
const std::map<std::wstring, std::wstring> COMMON_EXIF_PATHS = {
{L"Make", L"/app1/ifd/{ushort=271}"},
{L"Model", L"/app1/ifd/{ushort=272}"},
{L"DateTime", L"/app1/ifd/{ushort=306}"},
{L"DateTimeOriginal", L"/app1/ifd/exif/{ushort=36867}"},
{L"DateTimeDigitized", L"/app1/ifd/exif/{ushort=36868}"},
{L"ISO", L"/app1/ifd/exif/{ushort=34855}"},
{L"FNumber", L"/app1/ifd/exif/{ushort=33437}"},
{L"ExposureTime", L"/app1/ifd/exif/{ushort=33434}"},
{L"FocalLength", L"/app1/ifd/exif/{ushort=37386}"},
{L"ExposureBias", L"/app1/ifd/exif/{ushort=37380}"},
{L"WhiteBalance", L"/app1/ifd/exif/{ushort=37384}"},
{L"Flash", L"/app1/ifd/exif/{ushort=37385}"},
{L"Orientation", L"/app1/ifd/{ushort=274}"},
{L"XResolution", L"/app1/ifd/{ushort=282}"},
{L"YResolution", L"/app1/ifd/{ushort=283}"},
{L"Software", L"/app1/ifd/{ushort=305}"},
{L"Artist", L"/app1/ifd/{ushort=315}"},
{L"Copyright", L"/app1/ifd/{ushort=33432}"},
{L"ColorSpace", L"/app1/ifd/exif/{ushort=40961}"},
{L"PixelXDimension", L"/app1/ifd/exif/{ushort=40962}"},
{L"PixelYDimension", L"/app1/ifd/exif/{ushort=40963}"},
{L"SceneCaptureType", L"/app1/ifd/exif/{ushort=41990}"},
{L"MeteringMode", L"/app1/ifd/exif/{ushort=37383}"},
{L"LightSource", L"/app1/ifd/exif/{ushort=37384}"}
};
const std::map<std::wstring, std::wstring> GPS_PATHS = {
{L"GPSLatitude", L"/app1/ifd/gps/{ushort=2}"},
{L"GPSLatitudeRef", L"/app1/ifd/gps/{ushort=1}"},
{L"GPSLongitude", L"/app1/ifd/gps/{ushort=4}"},
{L"GPSLongitudeRef", L"/app1/ifd/gps/{ushort=3}"},
{L"GPSAltitude", L"/app1/ifd/gps/{ushort=6}"},
{L"GPSAltitudeRef", L"/app1/ifd/gps/{ushort=5}"},
{L"GPSTimeStamp", L"/app1/ifd/gps/{ushort=7}"},
{L"GPSDateStamp", L"/app1/ifd/gps/{ushort=29}"}
};
const std::map<std::wstring, std::wstring> IPTC_PATHS = {
{L"Title", L"/app13/irb/8bimiptc/iptc/object name"},
{L"Caption", L"/app13/irb/8bimiptc/iptc/caption"},
{L"Keywords", L"/app13/irb/8bimiptc/iptc/keywords"},
{L"Category", L"/app13/irb/8bimiptc/iptc/category"},
{L"Credit", L"/app13/irb/8bimiptc/iptc/credit"},
{L"Source", L"/app13/irb/8bimiptc/iptc/source"},
{L"Byline", L"/app13/irb/8bimiptc/iptc/by-line"},
{L"BylineTitle", L"/app13/irb/8bimiptc/iptc/by-line title"},
{L"City", L"/app13/irb/8bimiptc/iptc/city"},
{L"ProvinceState", L"/app13/irb/8bimiptc/iptc/province or state"},
{L"CountryName", L"/app13/irb/8bimiptc/iptc/country or primary location name"}
};
// Helper function to convert PROPVARIANT to MetadataValue
WICMetadataExtractor::MetadataValue PropVariantToMetadataValue(const PROPVARIANT& pv)
{
switch (pv.vt)
{
case VT_LPWSTR:
return pv.pwszVal ? std::wstring(pv.pwszVal) : std::wstring{};
case VT_BSTR:
return pv.bstrVal ? std::wstring(pv.bstrVal) : std::wstring{};
case VT_LPSTR:
if (pv.pszVal)
{
int size_needed = MultiByteToWideChar(CP_UTF8, 0, pv.pszVal, -1, NULL, 0);
if (size_needed > 0)
{
std::wstring result(static_cast<size_t>(size_needed) - 1, 0);
MultiByteToWideChar(CP_UTF8, 0, pv.pszVal, -1, &result[0], size_needed);
return result;
}
}
return std::wstring{};
case VT_I1:
return static_cast<int32_t>(pv.cVal);
case VT_I2:
return static_cast<int32_t>(pv.iVal);
case VT_I4:
return pv.lVal;
case VT_UI1:
return static_cast<uint32_t>(pv.bVal);
case VT_UI2:
return static_cast<uint32_t>(pv.uiVal);
case VT_UI4:
return pv.ulVal;
case VT_R4:
return static_cast<double>(pv.fltVal);
case VT_R8:
return pv.dblVal;
case VT_BOOL:
return pv.boolVal != VARIANT_FALSE;
case VT_UI1 | VT_VECTOR:
if (pv.caub.pElems && pv.caub.cElems > 0)
{
return std::vector<uint8_t>(pv.caub.pElems, pv.caub.pElems + pv.caub.cElems);
}
return std::vector<uint8_t>{};
default:
// Try to convert to string as fallback
PWSTR pszValue = nullptr;
if (SUCCEEDED(PropVariantToStringAlloc(pv, &pszValue)) && pszValue)
{
std::wstring result = pszValue;
CoTaskMemFree(pszValue);
return result;
}
return std::wstring{};
}
}
// Cache for metadata to improve performance
class MetadataCache
{
private:
mutable std::mutex m_mutex;
std::unordered_map<std::wstring, WICMetadataExtractor::ImageInfo> m_cache;
bool m_enabled = true;
size_t m_maxSize = 100; // Maximum cached items
public:
void Put(const std::wstring& filePath, const WICMetadataExtractor::ImageInfo& info)
{
if (!m_enabled) return;
std::lock_guard<std::mutex> lock(m_mutex);
if (m_cache.size() >= m_maxSize)
{
// Simple LRU: remove first item
m_cache.erase(m_cache.begin());
}
m_cache[filePath] = info;
}
std::optional<WICMetadataExtractor::ImageInfo> Get(const std::wstring& filePath) const
{
if (!m_enabled) return std::nullopt;
std::lock_guard<std::mutex> lock(m_mutex);
auto it = m_cache.find(filePath);
return (it != m_cache.end()) ? std::make_optional(it->second) : std::nullopt;
}
void Clear()
{
std::lock_guard<std::mutex> lock(m_mutex);
m_cache.clear();
}
void SetEnabled(bool enabled)
{
m_enabled = enabled;
}
size_t Size() const
{
std::lock_guard<std::mutex> lock(m_mutex);
return m_cache.size();
}
};
}
// PIMPL implementation
class WICMetadataExtractor::Impl
{
private:
bool m_comInitialized = false;
CComPtr<IWICImagingFactory> m_pWicFactory;
MetadataCache m_cache;
public:
Impl()
{
// Initialize COM
HRESULT hr = CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED);
if (SUCCEEDED(hr))
{
m_comInitialized = true;
}
// Create WIC factory
hr = CoCreateInstance(
CLSID_WICImagingFactory,
nullptr,
CLSCTX_INPROC_SERVER,
IID_IWICImagingFactory,
reinterpret_cast<LPVOID*>(&m_pWicFactory)
);
if (FAILED(hr))
{
m_pWicFactory = nullptr;
}
}
~Impl()
{
m_pWicFactory = nullptr;
if (m_comInitialized)
{
CoUninitialize();
}
}
bool IsWicInitialized() const
{
return GetWicFactory() != nullptr;
}
MetadataCache& GetCache() { return m_cache; }
IWICImagingFactory* GetWicFactory() const { return m_pWicFactory; }
std::optional<ImageInfo> ExtractImageInfo(const std::wstring& filePath, const ExtractionOptions& options)
{
if (!IsWicInitialized())
{
return std::nullopt;
}
// Check cache first
if (options.cacheMetadata)
{
auto cached = m_cache.Get(filePath);
if (cached.has_value())
{
return cached;
}
}
try
{
CComPtr<IWICBitmapDecoder> pDecoder;
HRESULT hr = GetWicFactory()->CreateDecoderFromFilename(
filePath.c_str(),
nullptr,
GENERIC_READ,
WICDecodeMetadataCacheOnLoad,
&pDecoder
);
if (FAILED(hr))
{
return std::nullopt;
}
ImageInfo info;
// Get container format
GUID containerFormat;
if (SUCCEEDED(pDecoder->GetContainerFormat(&containerFormat)))
{
info.containerFormat = GetFormatNameFromGuid(containerFormat);
}
// Get the first frame
CComPtr<IWICBitmapFrameDecode> pFrame;
hr = pDecoder->GetFrame(0, &pFrame);
if (FAILED(hr))
{
return std::nullopt;
}
// Get basic image properties
hr = pFrame->GetSize(&info.width, &info.height);
if (FAILED(hr))
{
return std::nullopt;
}
// Get pixel format
WICPixelFormatGUID pixelFormat;
if (SUCCEEDED(pFrame->GetPixelFormat(&pixelFormat)))
{
info.pixelFormat = GetPixelFormatName(pixelFormat);
CComPtr<IWICComponentInfo> pComponentInfo;
hr = GetWicFactory()->CreateComponentInfo(pixelFormat, &pComponentInfo);
if (SUCCEEDED(hr))
{
CComPtr<IWICPixelFormatInfo> pPixelFormatInfo;
hr = pComponentInfo->QueryInterface(IID_IWICPixelFormatInfo, reinterpret_cast<void**>(&pPixelFormatInfo));
if (SUCCEEDED(hr))
{
hr = pPixelFormatInfo->GetBitsPerPixel(&info.bitsPerPixel);
}
}
}
// Get metadata reader
CComPtr<IWICMetadataQueryReader> pQueryReader;
hr = pFrame->GetMetadataQueryReader(&pQueryReader);
if (SUCCEEDED(hr))
{
// Extract EXIF metadata
if (options.includeExif)
{
ExtractMetadataGroup(pQueryReader, COMMON_EXIF_PATHS, info.exifData);
}
// Extract GPS metadata
if (options.includeGps)
{
ExtractMetadataGroup(pQueryReader, GPS_PATHS, info.gpsData);
}
// Extract IPTC metadata
if (options.includeIptc)
{
ExtractMetadataGroup(pQueryReader, IPTC_PATHS, info.iptcData);
}
// Extract XMP metadata (if available)
if (options.includeXmp)
{
ExtractXmpMetadata(pQueryReader, info.xmpData);
}
// Extract all available metadata paths for comprehensive coverage
if (options.includeCustom)
{
ExtractAllAvailableMetadata(pQueryReader, info.customData);
}
}
// Cache the result
if (options.cacheMetadata)
{
m_cache.Put(filePath, info);
}
return info;
}
catch (...)
{
return std::nullopt;
}
}
std::optional<FormatInfo> GetFormatInfo(const std::wstring& filePath)
{
if (!IsWicInitialized())
{
return std::nullopt;
}
try
{
CComPtr<IWICBitmapDecoder> pDecoder;
HRESULT hr = GetWicFactory()->CreateDecoderFromFilename(
filePath.c_str(),
nullptr,
GENERIC_READ,
WICDecodeMetadataCacheOnDemand,
&pDecoder
);
if (FAILED(hr))
{
return std::nullopt;
}
FormatInfo formatInfo;
GUID containerFormat;
if (SUCCEEDED(pDecoder->GetContainerFormat(&containerFormat)))
{
formatInfo.formatName = GetFormatNameFromGuid(containerFormat);
formatInfo.fileExtensions = GetFileExtensionsFromGuid(containerFormat);
formatInfo.mimeTypes = GetMimeTypesFromGuid(containerFormat);
}
// Check if metadata is supported
CComPtr<IWICBitmapFrameDecode> pFrame;
hr = pDecoder->GetFrame(0, &pFrame);
if (SUCCEEDED(hr))
{
CComPtr<IWICMetadataQueryReader> pQueryReader;
formatInfo.supportsMetadata = SUCCEEDED(pFrame->GetMetadataQueryReader(&pQueryReader));
}
// Check multi-frame support
UINT frameCount = 0;
if (SUCCEEDED(pDecoder->GetFrameCount(&frameCount)))
{
formatInfo.supportsMultiFrame = frameCount > 1;
}
return formatInfo;
}
catch (...)
{
return std::nullopt;
}
}
std::vector<FormatInfo> GetSupportedFormats()
{
std::vector<FormatInfo> formats;
if (!IsWicInitialized())
{
return formats;
}
try
{
CComPtr<IEnumUnknown> pEnum;
HRESULT hr = GetWicFactory()->CreateComponentEnumerator(
WICDecoder,
WICComponentEnumerateDefault,
&pEnum
);
if (FAILED(hr))
{
return formats;
}
IUnknown* pUnknown = nullptr;
ULONG fetched = 0;
while (pEnum->Next(1, &pUnknown, &fetched) == S_OK && fetched == 1)
{
CComPtr<IWICBitmapDecoderInfo> pDecoderInfo;
hr = pUnknown->QueryInterface(IID_IWICBitmapDecoderInfo, reinterpret_cast<void**>(&pDecoderInfo));
pUnknown->Release();
if (SUCCEEDED(hr))
{
FormatInfo formatInfo;
UINT length = 0;
hr = pDecoderInfo->GetFriendlyName(0, nullptr, &length);
if (SUCCEEDED(hr) && length > 0)
{
std::vector<WCHAR> buffer(length);
hr = pDecoderInfo->GetFriendlyName(length, buffer.data(), &length);
if (SUCCEEDED(hr))
{
formatInfo.formatName = buffer.data();
}
}
// Get file extensions
length = 0;
hr = pDecoderInfo->GetFileExtensions(0, nullptr, &length);
if (SUCCEEDED(hr) && length > 0)
{
std::vector<WCHAR> buffer(length);
hr = pDecoderInfo->GetFileExtensions(length, buffer.data(), &length);
if (SUCCEEDED(hr))
{
formatInfo.fileExtensions = buffer.data();
}
}
// Get MIME types
length = 0;
hr = pDecoderInfo->GetMimeTypes(0, nullptr, &length);
if (SUCCEEDED(hr) && length > 0)
{
std::vector<WCHAR> buffer(length);
hr = pDecoderInfo->GetMimeTypes(length, buffer.data(), &length);
if (SUCCEEDED(hr))
{
std::wstring mimeTypesStr = buffer.data();
// Split by comma
std::wstringstream ss(mimeTypesStr);
std::wstring item;
while (std::getline(ss, item, L','))
{
// Trim whitespace
item.erase(0, item.find_first_not_of(L" \t"));
item.erase(item.find_last_not_of(L" \t") + 1);
if (!item.empty())
{
formatInfo.mimeTypes.push_back(item);
}
}
}
}
formatInfo.supportsMetadata = true; // Assume metadata support for now
formatInfo.supportsMultiFrame = false; // Will be determined per file
formats.push_back(formatInfo);
}
}
}
catch (...)
{
// Return partial results on error
}
return formats;
}
bool HasMetadataType(const std::wstring& filePath, const std::wstring& metadataType)
{
if (!IsWicInitialized())
{
return false;
}
try
{
CComPtr<IWICBitmapDecoder> pDecoder;
HRESULT hr = GetWicFactory()->CreateDecoderFromFilename(
filePath.c_str(),
nullptr,
GENERIC_READ,
WICDecodeMetadataCacheOnDemand,
&pDecoder
);
if (FAILED(hr))
{
return false;
}
CComPtr<IWICBitmapFrameDecode> pFrame;
hr = pDecoder->GetFrame(0, &pFrame);
if (FAILED(hr))
{
return false;
}
CComPtr<IWICMetadataQueryReader> pQueryReader;
hr = pFrame->GetMetadataQueryReader(&pQueryReader);
if (FAILED(hr))
{
return false;
}
// Check specific metadata type paths
if (metadataType == L"exif")
{
PROPVARIANT pv;
PropVariantInit(&pv);
bool hasExif = SUCCEEDED(pQueryReader->GetMetadataByName(L"/app1/ifd/exif", &pv));
PropVariantClear(&pv);
return hasExif;
}
else if (metadataType == L"iptc")
{
PROPVARIANT pv;
PropVariantInit(&pv);
bool hasIptc = SUCCEEDED(pQueryReader->GetMetadataByName(L"/app13/irb/8bimiptc", &pv));
PropVariantClear(&pv);
return hasIptc;
}
else if (metadataType == L"xmp")
{
PROPVARIANT pv;
PropVariantInit(&pv);
bool hasXmp = SUCCEEDED(pQueryReader->GetMetadataByName(L"/xmp", &pv));
PropVariantClear(&pv);
return hasXmp;
}
else if (metadataType == L"gps")
{
PROPVARIANT pv;
PropVariantInit(&pv);
bool hasGps = SUCCEEDED(pQueryReader->GetMetadataByName(L"/app1/ifd/gps", &pv));
PropVariantClear(&pv);
return hasGps;
}
return false;
}
catch (...)
{
return false;
}
}
std::optional<MetadataValue> GetMetadataByPath(const std::wstring& filePath, const std::wstring& propertyPath)
{
if (!IsWicInitialized())
{
return std::nullopt;
}
try
{
CComPtr<IWICBitmapDecoder> pDecoder;
HRESULT hr = GetWicFactory()->CreateDecoderFromFilename(
filePath.c_str(),
nullptr,
GENERIC_READ,
WICDecodeMetadataCacheOnDemand,
&pDecoder
);
if (FAILED(hr))
{
return std::nullopt;
}
CComPtr<IWICBitmapFrameDecode> pFrame;
hr = pDecoder->GetFrame(0, &pFrame);
if (FAILED(hr))
{
return std::nullopt;
}
CComPtr<IWICMetadataQueryReader> pQueryReader;
hr = pFrame->GetMetadataQueryReader(&pQueryReader);
if (FAILED(hr))
{
return std::nullopt;
}
PROPVARIANT pv;
PropVariantInit(&pv);
hr = pQueryReader->GetMetadataByName(propertyPath.c_str(), &pv);
if (SUCCEEDED(hr))
{
auto result = PropVariantToMetadataValue(pv);
PropVariantClear(&pv);
return result;
}
PropVariantClear(&pv);
return std::nullopt;
}
catch (...)
{
return std::nullopt;
}
}
private:
void ExtractMetadataGroup(CComPtr<IWICMetadataQueryReader> pQueryReader,
const std::map<std::wstring, std::wstring>& paths,
std::map<std::wstring, MetadataValue>& output)
{
for (const auto& [key, path] : paths)
{
PROPVARIANT pv;
PropVariantInit(&pv);
if (SUCCEEDED(pQueryReader->GetMetadataByName(path.c_str(), &pv)))
{
output[key] = PropVariantToMetadataValue(pv);
}
PropVariantClear(&pv);
}
}
void ExtractXmpMetadata(CComPtr<IWICMetadataQueryReader> pQueryReader,
std::map<std::wstring, MetadataValue>& output)
{
// XMP metadata extraction - simplified implementation
// In a full implementation, you would parse the XMP XML
PROPVARIANT pv;
PropVariantInit(&pv);
if (SUCCEEDED(pQueryReader->GetMetadataByName(L"/xmp", &pv)))
{
output[L"XMP_Raw"] = PropVariantToMetadataValue(pv);
}
PropVariantClear(&pv);
}
void ExtractAllAvailableMetadata(CComPtr<IWICMetadataQueryReader> pQueryReader,
std::map<std::wstring, MetadataValue>& output)
{
// This would enumerate all available metadata paths
// For now, we'll add some common additional paths
std::vector<std::wstring> additionalPaths = {
L"/app1/ifd/{ushort=256}", // ImageWidth
L"/app1/ifd/{ushort=257}", // ImageLength
L"/app1/ifd/{ushort=258}", // BitsPerSample
L"/app1/ifd/{ushort=259}", // Compression
L"/app1/ifd/{ushort=262}", // PhotometricInterpretation
L"/app1/ifd/{ushort=277}", // SamplesPerPixel
};
for (const auto& path : additionalPaths)
{
PROPVARIANT pv;
PropVariantInit(&pv);
if (SUCCEEDED(pQueryReader->GetMetadataByName(path.c_str(), &pv)))
{
output[path] = PropVariantToMetadataValue(pv);
}
PropVariantClear(&pv);
}
}
std::wstring GetFormatNameFromGuid(const GUID& guid)
{
// Map common format GUIDs to friendly names
if (guid == GUID_ContainerFormatJpeg) return L"JPEG";
if (guid == GUID_ContainerFormatPng) return L"PNG";
if (guid == GUID_ContainerFormatGif) return L"GIF";
if (guid == GUID_ContainerFormatBmp) return L"BMP";
if (guid == GUID_ContainerFormatTiff) return L"TIFF";
if (guid == GUID_ContainerFormatIco) return L"ICO";
if (guid == GUID_ContainerFormatWmp) return L"WMP";
// Convert GUID to string for unknown formats
OLECHAR* guidString = nullptr;
if (SUCCEEDED(StringFromCLSID(guid, &guidString)))
{
std::wstring result = guidString;
CoTaskMemFree(guidString);
return result;
}
return L"Unknown";
}
std::wstring GetFileExtensionsFromGuid(const GUID& guid)
{
// Map format GUIDs to file extensions
if (guid == GUID_ContainerFormatJpeg) return L".jpg,.jpeg";
if (guid == GUID_ContainerFormatPng) return L".png";
if (guid == GUID_ContainerFormatGif) return L".gif";
if (guid == GUID_ContainerFormatBmp) return L".bmp";
if (guid == GUID_ContainerFormatTiff) return L".tif,.tiff";
if (guid == GUID_ContainerFormatIco) return L".ico";
if (guid == GUID_ContainerFormatWmp) return L".wdp,.hdp,.jxr";
return L"";
}
std::vector<std::wstring> GetMimeTypesFromGuid(const GUID& guid)
{
// Map format GUIDs to MIME types
if (guid == GUID_ContainerFormatJpeg) return {L"image/jpeg"};
if (guid == GUID_ContainerFormatPng) return {L"image/png"};
if (guid == GUID_ContainerFormatGif) return {L"image/gif"};
if (guid == GUID_ContainerFormatBmp) return {L"image/bmp"};
if (guid == GUID_ContainerFormatTiff) return {L"image/tiff"};
if (guid == GUID_ContainerFormatIco) return {L"image/x-icon"};
if (guid == GUID_ContainerFormatWmp) return {L"image/vnd.ms-photo"};
return {};
}
std::wstring GetPixelFormatName(const GUID& guid)
{
// Map pixel format GUIDs to friendly names
if (guid == GUID_WICPixelFormat24bppRGB) return L"24bpp RGB";
if (guid == GUID_WICPixelFormat32bppRGBA) return L"32bpp RGBA";
if (guid == GUID_WICPixelFormat8bppGray) return L"8bpp Grayscale";
if (guid == GUID_WICPixelFormat1bppIndexed) return L"1bpp Indexed";
if (guid == GUID_WICPixelFormat8bppIndexed) return L"8bpp Indexed";
// Convert GUID to string for unknown formats
OLECHAR* guidString = nullptr;
if (SUCCEEDED(StringFromCLSID(guid, &guidString)))
{
std::wstring result = guidString;
CoTaskMemFree(guidString);
return result;
}
return L"Unknown";
}
};
// Constructor and Destructor
WICMetadataExtractor::WICMetadataExtractor() : m_pImpl(std::make_unique<Impl>())
{
}
WICMetadataExtractor::~WICMetadataExtractor() = default;
// Main interface implementation
std::optional<WICMetadataExtractor::ImageInfo> WICMetadataExtractor::ExtractImageInfo(
const std::wstring& filePath,
const ExtractionOptions& options)
{
return m_pImpl->ExtractImageInfo(filePath, options);
}
std::optional<WICMetadataExtractor::FormatInfo> WICMetadataExtractor::GetFormatInfo(const std::wstring& filePath)
{
return m_pImpl->GetFormatInfo(filePath);
}
std::vector<WICMetadataExtractor::FormatInfo> WICMetadataExtractor::GetSupportedFormats()
{
return m_pImpl->GetSupportedFormats();
}
bool WICMetadataExtractor::HasMetadataType(const std::wstring& filePath, const std::wstring& metadataType)
{
return m_pImpl->HasMetadataType(filePath, metadataType);
}
std::optional<WICMetadataExtractor::MetadataValue> WICMetadataExtractor::GetMetadataByPath(
const std::wstring& filePath,
const std::wstring& propertyPath)
{
return m_pImpl->GetMetadataByPath(filePath, propertyPath);
}
void WICMetadataExtractor::ClearCache()
{
m_pImpl->GetCache().Clear();
}
void WICMetadataExtractor::SetCacheEnabled(bool enabled)
{
m_pImpl->GetCache().SetEnabled(enabled);
}
size_t WICMetadataExtractor::GetCacheSize() const
{
return m_pImpl->GetCache().Size();
}

View File

@@ -0,0 +1,206 @@
#pragma once
#include <string>
#include <memory>
#include <map>
#include <vector>
#include <optional>
#include <variant>
namespace PowerRenameLib
{
/// <summary>
/// Windows Imaging Component (WIC) based metadata extractor
/// Designed to leverage WIC's full capabilities for comprehensive image metadata extraction
/// </summary>
class WICMetadataExtractor
{
public:
/// <summary>
/// Metadata value type - can hold different data types returned by WIC
/// </summary>
using MetadataValue = std::variant<
std::wstring, // String values
int32_t, // Integer values
uint32_t, // Unsigned integer values
double, // Floating point values
bool, // Boolean values
std::vector<uint8_t> // Binary data
>;
/// <summary>
/// Comprehensive metadata container with WIC-native organization
/// </summary>
struct ImageInfo
{
// Basic image properties
uint32_t width = 0;
uint32_t height = 0;
uint32_t bitsPerPixel = 0;
std::wstring pixelFormat;
std::wstring containerFormat;
// All metadata organized by source
std::map<std::wstring, MetadataValue> exifData; // EXIF metadata
std::map<std::wstring, MetadataValue> iptcData; // IPTC metadata
std::map<std::wstring, MetadataValue> xmpData; // XMP metadata
std::map<std::wstring, MetadataValue> ifdData; // IFD metadata
std::map<std::wstring, MetadataValue> gpsData; // GPS metadata
std::map<std::wstring, MetadataValue> customData; // Custom/other metadata
};
/// <summary>
/// Supported image format information
/// </summary>
struct FormatInfo
{
std::wstring formatName;
std::wstring fileExtensions; // Comma-separated
std::vector<std::wstring> mimeTypes;
bool supportsMetadata = false;
bool supportsMultiFrame = false;
};
/// <summary>
/// Metadata extraction options
/// </summary>
struct ExtractionOptions
{
bool includeExif = true;
bool includeIptc = true;
bool includeXmp = true;
bool includeGps = true;
bool includeCustom = true;
bool includeThumbnails = false;
bool cacheMetadata = true;
uint32_t maxBinaryDataSize = 1024 * 1024; // 1MB limit for binary data
};
WICMetadataExtractor();
~WICMetadataExtractor();
/// <summary>
/// Extract comprehensive metadata using WIC's native capabilities
/// </summary>
/// <param name="filePath">Path to the image file</param>
/// <param name="options">Extraction options</param>
/// <returns>Complete image information including all available metadata</returns>
std::optional<ImageInfo> ExtractImageInfo(const std::wstring& filePath,
const ExtractionOptions& options = {});
/// <summary>
/// Get detailed format information for a file
/// </summary>
/// <param name="filePath">Path to the image file</param>
/// <returns>Format information if supported</returns>
std::optional<FormatInfo> GetFormatInfo(const std::wstring& filePath);
/// <summary>
/// Get all supported formats by the current WIC installation
/// </summary>
/// <returns>List of all supported image formats</returns>
std::vector<FormatInfo> GetSupportedFormats();
/// <summary>
/// Check if specific metadata type is available in the file
/// </summary>
/// <param name="filePath">Path to the image file</param>
/// <param name="metadataType">Type to check (e.g., L"exif", L"iptc", L"xmp")</param>
/// <returns>True if the metadata type exists</returns>
bool HasMetadataType(const std::wstring& filePath, const std::wstring& metadataType);
/// <summary>
/// Extract specific metadata value by WIC property path
/// </summary>
/// <param name="filePath">Path to the image file</param>
/// <param name="propertyPath">WIC metadata path (e.g., L"/app1/ifd/exif/{ushort=272}")</param>
/// <returns>Metadata value if found</returns>
std::optional<MetadataValue> GetMetadataByPath(const std::wstring& filePath,
const std::wstring& propertyPath);
/// <summary>
/// Smart metadata formatter with WIC-aware formatting
/// </summary>
class MetadataFormatter
{
public:
/// <summary>
/// Format any metadata value to string with appropriate formatting
/// </summary>
static std::wstring FormatValue(const MetadataValue& value);
/// <summary>
/// Format GPS coordinates to human-readable string
/// </summary>
static std::wstring FormatGpsCoordinates(const MetadataValue& latitude,
const MetadataValue& longitude,
const MetadataValue& latRef = {},
const MetadataValue& lonRef = {});
/// <summary>
/// Format camera settings to readable string
/// </summary>
static std::wstring FormatCameraSettings(const std::map<std::wstring, MetadataValue>& exifData);
/// <summary>
/// Format date/time with various output formats
/// </summary>
static std::wstring FormatDateTime(const MetadataValue& dateTime,
const std::wstring& format = L"yyyy-MM-dd");
/// <summary>
/// Format exposure settings (aperture, shutter, ISO)
/// </summary>
static std::wstring FormatExposureSettings(const MetadataValue& aperture,
const MetadataValue& shutter,
const MetadataValue& iso);
};
/// <summary>
/// PowerRename integration helper - provides common rename patterns
/// </summary>
class RenamePatternProvider
{
public:
/// <summary>
/// Get available rename patterns based on available metadata
/// </summary>
static std::vector<std::wstring> GetAvailablePatterns(const ImageInfo& imageInfo);
/// <summary>
/// Resolve pattern to actual value
/// </summary>
static std::wstring ResolvePattern(const std::wstring& pattern, const ImageInfo& imageInfo);
/// <summary>
/// Get smart suggestions for rename patterns based on file content
/// </summary>
static std::vector<std::wstring> GetSmartSuggestions(const ImageInfo& imageInfo);
/// <summary>
/// Batch pattern resolution for multiple patterns
/// </summary>
static std::map<std::wstring, std::wstring> ResolvePatterns(
const std::vector<std::wstring>& patterns,
const ImageInfo& imageInfo);
};
/// <summary>
/// Metadata cache management
/// </summary>
void ClearCache();
void SetCacheEnabled(bool enabled);
size_t GetCacheSize() const;
private:
class Impl;
std::unique_ptr<Impl> m_pImpl;
};
/// <summary>
/// Convenient aliases for backward compatibility and easier usage
/// </summary>
using MediaMetadataExtractor = WICMetadataExtractor; // Alias for existing code
using ImageMetadata = WICMetadataExtractor::ImageInfo; // Alias for existing code
}

View File

@@ -0,0 +1,222 @@
// Modern WIC-based MediaMetadataExtractor Example
// This demonstrates the full capabilities of the new WIC-based implementation
#include "MediaMetadataExtractor.h"
#include <iostream>
#include <iomanip>
using namespace PowerRenameLib;
void PrintImageInfo(const WICMetadataExtractor::ImageInfo& imageInfo)
{
std::wcout << L"=== WIC Image Information ===" << std::endl;
// Basic image properties
std::wcout << L"Dimensions: " << imageInfo.width << L"x" << imageInfo.height << std::endl;
std::wcout << L"Bits per Pixel: " << imageInfo.bitsPerPixel << std::endl;
std::wcout << L"Pixel Format: " << imageInfo.pixelFormat << std::endl;
std::wcout << L"Container Format: " << imageInfo.containerFormat << std::endl;
// EXIF metadata
if (!imageInfo.exifData.empty())
{
std::wcout << L"\n--- EXIF Data ---" << std::endl;
for (const auto& [key, value] : imageInfo.exifData)
{
std::wcout << key << L": " << WICMetadataExtractor::MetadataFormatter::FormatValue(value) << std::endl;
}
}
// GPS metadata
if (!imageInfo.gpsData.empty())
{
std::wcout << L"\n--- GPS Data ---" << std::endl;
for (const auto& [key, value] : imageInfo.gpsData)
{
std::wcout << key << L": " << WICMetadataExtractor::MetadataFormatter::FormatValue(value) << std::endl;
}
}
// IPTC metadata
if (!imageInfo.iptcData.empty())
{
std::wcout << L"\n--- IPTC Data ---" << std::endl;
for (const auto& [key, value] : imageInfo.iptcData)
{
std::wcout << key << L": " << WICMetadataExtractor::MetadataFormatter::FormatValue(value) << std::endl;
}
}
// XMP metadata
if (!imageInfo.xmpData.empty())
{
std::wcout << L"\n--- XMP Data ---" << std::endl;
for (const auto& [key, value] : imageInfo.xmpData)
{
std::wcout << key << L": " << WICMetadataExtractor::MetadataFormatter::FormatValue(value) << std::endl;
}
}
// Custom metadata
if (!imageInfo.customData.empty())
{
std::wcout << L"\n--- Additional Metadata ---" << std::endl;
for (const auto& [key, value] : imageInfo.customData)
{
std::wcout << key << L": " << WICMetadataExtractor::MetadataFormatter::FormatValue(value) << std::endl;
}
}
}
void DemonstrateBasicUsage()
{
std::wcout << L"=== Basic Usage Demo ===" << std::endl;
WICMetadataExtractor extractor;
// Example file path - replace with actual image file
std::wstring filePath = L"C:\\example\\photo.jpg";
// Check format support
auto formatInfo = extractor.GetFormatInfo(filePath);
if (formatInfo.has_value())
{
std::wcout << L"Format: " << formatInfo->formatName << std::endl;
std::wcout << L"Extensions: " << formatInfo->fileExtensions << std::endl;
std::wcout << L"Supports Metadata: " << (formatInfo->supportsMetadata ? L"Yes" : L"No") << std::endl;
std::wcout << L"Multi-frame: " << (formatInfo->supportsMultiFrame ? L"Yes" : L"No") << std::endl;
}
// Extract comprehensive metadata
WICMetadataExtractor::ExtractionOptions options;
options.includeExif = true;
options.includeIptc = true;
options.includeXmp = true;
options.includeGps = true;
options.includeCustom = true;
auto imageInfo = extractor.ExtractImageInfo(filePath, options);
if (imageInfo.has_value())
{
PrintImageInfo(imageInfo.value());
}
else
{
std::wcout << L"Failed to extract metadata" << std::endl;
}
}
void DemonstrateRenamePatterns()
{
std::wcout << L"\n=== Rename Pattern Demo ===" << std::endl;
WICMetadataExtractor extractor;
std::wstring filePath = L"C:\\example\\photo.jpg";
auto imageInfo = extractor.ExtractImageInfo(filePath);
if (!imageInfo.has_value())
{
std::wcout << L"No image data available for pattern demo" << std::endl;
return;
}
// Get available patterns
auto patterns = WICMetadataExtractor::RenamePatternProvider::GetAvailablePatterns(imageInfo.value());
std::wcout << L"Available Patterns:" << std::endl;
for (const auto& pattern : patterns)
{
std::wstring value = WICMetadataExtractor::RenamePatternProvider::ResolvePattern(pattern, imageInfo.value());
std::wcout << L"{" << pattern << L"} -> " << value << std::endl;
}
// Get smart suggestions
auto suggestions = WICMetadataExtractor::RenamePatternProvider::GetSmartSuggestions(imageInfo.value());
std::wcout << L"\nSmart Rename Suggestions:" << std::endl;
for (const auto& suggestion : suggestions)
{
std::wcout << L" " << suggestion << std::endl;
}
}
void DemonstrateAdvancedFeatures()
{
std::wcout << L"\n=== Advanced Features Demo ===" << std::endl;
WICMetadataExtractor extractor;
// List all supported formats
auto supportedFormats = extractor.GetSupportedFormats();
std::wcout << L"Supported formats (" << supportedFormats.size() << L"):" << std::endl;
for (const auto& format : supportedFormats)
{
std::wcout << L" " << format.formatName << L" (" << format.fileExtensions << L")" << std::endl;
}
// Test specific metadata types
std::wstring filePath = L"C:\\example\\photo.jpg";
std::wcout << L"\nMetadata type availability:" << std::endl;
std::wcout << L" EXIF: " << (extractor.HasMetadataType(filePath, L"exif") ? L"Yes" : L"No") << std::endl;
std::wcout << L" IPTC: " << (extractor.HasMetadataType(filePath, L"iptc") ? L"Yes" : L"No") << std::endl;
std::wcout << L" XMP: " << (extractor.HasMetadataType(filePath, L"xmp") ? L"Yes" : L"No") << std::endl;
std::wcout << L" GPS: " << (extractor.HasMetadataType(filePath, L"gps") ? L"Yes" : L"No") << std::endl;
// Extract specific metadata by path
auto cameraModel = extractor.GetMetadataByPath(filePath, L"/app1/ifd/{ushort=272}");
if (cameraModel.has_value())
{
std::wcout << L"Camera Model (direct path): " <<
WICMetadataExtractor::MetadataFormatter::FormatValue(cameraModel.value()) << std::endl;
}
// Cache management
std::wcout << L"Cache size: " << extractor.GetCacheSize() << L" items" << std::endl;
}
void DemonstrateFormatterFeatures()
{
std::wcout << L"\n=== Formatter Features Demo ===" << std::endl;
// Create sample metadata values for demonstration
WICMetadataExtractor::MetadataValue sampleDouble = 2.8;
WICMetadataExtractor::MetadataValue sampleInt = 400;
WICMetadataExtractor::MetadataValue sampleString = std::wstring(L"Canon EOS R5");
WICMetadataExtractor::MetadataValue sampleBool = true;
std::wcout << L"Formatted values:" << std::endl;
std::wcout << L" Double: " << WICMetadataExtractor::MetadataFormatter::FormatValue(sampleDouble) << std::endl;
std::wcout << L" Integer: " << WICMetadataExtractor::MetadataFormatter::FormatValue(sampleInt) << std::endl;
std::wcout << L" String: " << WICMetadataExtractor::MetadataFormatter::FormatValue(sampleString) << std::endl;
std::wcout << L" Boolean: " << WICMetadataExtractor::MetadataFormatter::FormatValue(sampleBool) << std::endl;
// Demonstrate exposure settings formatting
std::wcout << L"Exposure Settings: " <<
WICMetadataExtractor::MetadataFormatter::FormatExposureSettings(sampleDouble, 0.008, sampleInt) << std::endl;
}
// Main example function
void RunCompleteExample()
{
std::wcout << L"WIC-based MediaMetadataExtractor Complete Example" << std::endl;
std::wcout << L"=================================================" << std::endl;
try
{
DemonstrateBasicUsage();
DemonstrateRenamePatterns();
DemonstrateAdvancedFeatures();
DemonstrateFormatterFeatures();
std::wcout << L"\nExample completed successfully!" << std::endl;
}
catch (const std::exception& e)
{
std::wcout << L"Error: " << e.what() << std::endl;
}
catch (...)
{
std::wcout << L"Unknown error occurred" << std::endl;
}
}

View File

@@ -0,0 +1,36 @@
#include "pch.h"
#include "MetadataExtractor.h"
#include <iostream>
// Simple test function to demonstrate MetadataExtractor usage
void TestMetadataExtractor()
{
try
{
PowerRenameLib::MetadataExtractor extractor;
// Test with a sample path (this is just for demonstration)
std::wstring testImagePath = L"C:\\test\\sample.jpg";
// Extract EXIF metadata
auto metadata = extractor.ExtractEXIFMetadata(testImagePath);
// Test different formatting patterns
std::wstring cameraResult = extractor.FormatMetadataForRename(metadata, L"camera");
std::wstring dateResult = extractor.FormatMetadataForRename(metadata, L"date");
std::wstring dimensionsResult = extractor.FormatMetadataForRename(metadata, L"dimensions");
// In a real application, these would be used for renaming files
// For now, this just demonstrates the API works
// Test XMP metadata extraction (currently returns placeholder)
auto xmpData = extractor.ExtractXMPMetadata(testImagePath);
// The MetadataExtractor is now successfully integrated
// and can be used by PowerRename to extract metadata for renaming
}
catch (...)
{
// Handle any errors during testing
}
}

View File

@@ -22,6 +22,7 @@ enum PowerRenameFlags
CreationTime = 0x4000,
ModificationTime = 0x8000,
AccessTime = 0x10000,
EXIFTime = 0x20000,
};
enum PowerRenameFilters

View File

@@ -1,5 +1,6 @@
#include "pch.h"
#include "PowerRenameItem.h"
#include <MediaMetadataExtractor.h>
int CPowerRenameItem::s_id = 0;
@@ -71,10 +72,14 @@ IFACEMETHODIMP CPowerRenameItem::GetTime(_In_ DWORD flags, _Outptr_ SYSTEMTIME*
{
parsedTimeType = PowerRenameFlags::AccessTime;
}
else if (flags & PowerRenameFlags::EXIFTime)
{
parsedTimeType = PowerRenameFlags::EXIFTime;
}
else
{
// Default to modification time if no specific flag is set
parsedTimeType = PowerRenameFlags::CreationTime;
parsedTimeType = PowerRenameFlags::CreationTime;
}
if (m_isTimeParsed && parsedTimeType == m_parsedTimeType)
@@ -83,47 +88,135 @@ IFACEMETHODIMP CPowerRenameItem::GetTime(_In_ DWORD flags, _Outptr_ SYSTEMTIME*
}
else
{
HANDLE hFile = CreateFileW(m_path, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);
if (hFile != INVALID_HANDLE_VALUE)
if (parsedTimeType == PowerRenameFlags::EXIFTime)
{
FILETIME FileTime;
bool success = false;
// Get Time by PowerRenameFlags
switch (parsedTimeType)
// check if file is an media file which may contain EXIF metadata
std::wstring extension = PathFindExtensionW(m_path);
if (extension.empty())
{
case PowerRenameFlags::CreationTime:
success = GetFileTime(hFile, &FileTime, NULL, NULL);
break;
case PowerRenameFlags::ModificationTime:
success = GetFileTime(hFile, NULL, NULL, &FileTime);
break;
case PowerRenameFlags::AccessTime:
success = GetFileTime(hFile, NULL, &FileTime, NULL);
break;
default:
// Default to modification time if no specific flag is set
success = GetFileTime(hFile, NULL, NULL, &FileTime);
break;
// if file is not a supported image type, use 1900-01-01 00:00:00 as fallback
SYSTEMTIME exifTime = {};
exifTime.wYear = static_cast<WORD>(1900);
exifTime.wMonth = static_cast<WORD>(01);
exifTime.wDay = static_cast<WORD>(01);
exifTime.wHour = static_cast<WORD>(00);
exifTime.wMinute = static_cast<WORD>(00);
exifTime.wSecond = static_cast<WORD>(00);
m_time = exifTime;
m_isTimeParsed = true;
m_parsedTimeType = parsedTimeType;
hr = S_OK;
return S_OK;
}
std::transform(extension.begin(), extension.end(), extension.begin(), ::towlower);
if (extension != L".jpg" && extension != L".jpeg")
{
// if file is not a supported image type, use 1900-01-01 00:00:00 as fallback
SYSTEMTIME exifTime = {};
exifTime.wYear = static_cast<WORD>(1900);
exifTime.wMonth = static_cast<WORD>(01);
exifTime.wDay = static_cast<WORD>(01);
exifTime.wHour = static_cast<WORD>(00);
exifTime.wMinute = static_cast<WORD>(00);
exifTime.wSecond = static_cast<WORD>(00);
m_time = exifTime;
m_isTimeParsed = true;
m_parsedTimeType = parsedTimeType;
hr = S_OK;
return S_OK;
}
if (success)
// using MediaMetadataExtractor to extract EXIF metadata
PowerRenameLib::WICMetadataExtractor extractor;
auto imageInfo = extractor.ExtractImageInfo(m_path);
if (!imageInfo.has_value())
{
SYSTEMTIME SystemTime, LocalTime;
if (FileTimeToSystemTime(&FileTime, &SystemTime))
hr = E_FAIL;
}
else
{
// Try to get date from EXIF data
auto it = imageInfo->exifData.find(L"DateTimeOriginal");
if (it == imageInfo->exifData.end()) {
it = imageInfo->exifData.find(L"DateTime");
}
if (it != imageInfo->exifData.end() && std::holds_alternative<std::wstring>(it->second))
{
if (SystemTimeToTzSpecificLocalTime(NULL, &SystemTime, &LocalTime))
const auto& dateTaken = std::get<std::wstring>(it->second);
// EXIF date format: "YYYY:MM:DD HH:MM:SS"
int year, month, day, hour, minute, second;
if (swscanf_s(dateTaken.c_str(), L"%d:%d:%d %d:%d:%d", &year, &month, &day, &hour, &minute, &second) == 6)
{
m_time = LocalTime;
SYSTEMTIME exifTime = {};
exifTime.wYear = static_cast<WORD>(year);
exifTime.wMonth = static_cast<WORD>(month);
exifTime.wDay = static_cast<WORD>(day);
exifTime.wHour = static_cast<WORD>(hour);
exifTime.wMinute = static_cast<WORD>(minute);
exifTime.wSecond = static_cast<WORD>(second);
m_time = exifTime;
m_isTimeParsed = true;
m_parsedTimeType = parsedTimeType;
hr = S_OK;
}
else
{
hr = E_FAIL;
}
}
else
{
hr = E_FAIL;
}
}
}
else
{
HANDLE hFile = CreateFileW(m_path, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);
if (hFile != INVALID_HANDLE_VALUE)
{
FILETIME FileTime;
bool success = false;
CloseHandle(hFile);
// Get Time by PowerRenameFlags
switch (parsedTimeType)
{
case PowerRenameFlags::CreationTime:
success = GetFileTime(hFile, &FileTime, NULL, NULL);
break;
case PowerRenameFlags::ModificationTime:
success = GetFileTime(hFile, NULL, NULL, &FileTime);
break;
case PowerRenameFlags::AccessTime:
success = GetFileTime(hFile, NULL, &FileTime, NULL);
break;
default:
// Default to modification time if no specific flag is set
success = GetFileTime(hFile, NULL, NULL, &FileTime);
break;
}
if (success)
{
SYSTEMTIME SystemTime, LocalTime;
if (FileTimeToSystemTime(&FileTime, &SystemTime))
{
if (SystemTimeToTzSpecificLocalTime(NULL, &SystemTime, &LocalTime))
{
m_time = LocalTime;
m_isTimeParsed = true;
m_parsedTimeType = parsedTimeType;
hr = S_OK;
}
}
}
}
CloseHandle(hFile);
}
}
*time = m_time;
return hr;

View File

@@ -16,19 +16,25 @@
</ImportGroup>
<ImportGroup Label="Shared">
</ImportGroup>
<ImportGroup Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<PropertyGroup>
<OutDir>..\..\..\..\$(Platform)\$(Configuration)\WinUI3Apps\</OutDir>
<DepsPath>$(ProjectDir)..\..\..\..\deps</DepsPath>
</PropertyGroup>
<ImportGroup Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<!-- Removed TinyEXIF import as we now use Windows Imaging Component -->
<ItemDefinitionGroup>
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<PreprocessorDefinitions>WIN32;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<AdditionalIncludeDirectories>$(ProjectDir)..\;$(ProjectDir)..\ui;$(ProjectDir)..\dll;$(ProjectDir)..\lib;$(ProjectDir)..\..\..\;$(ProjectDir)..\..\..\common\Telemetry;%(AdditionalIncludeDirectories);$(GeneratedFilesDir)</AdditionalIncludeDirectories>
<AdditionalOptions>/FS %(AdditionalOptions)</AdditionalOptions>
</ClCompile>
<Link>
<AdditionalDependencies>windowscodecs.lib;propsys.lib;ole32.lib;%(AdditionalDependencies)</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<ItemGroup>
<ClInclude Include="Enumerating.h" />
@@ -47,6 +53,7 @@
<ClInclude Include="pch.h" />
<ClInclude Include="targetver.h" />
<ClInclude Include="trace.h" />
<ClInclude Include="MediaMetadataExtractor.h" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="Enumerating.cpp" />
@@ -64,6 +71,9 @@
<PrecompiledHeader Condition="'$(UsePrecompiledHeaders)' != 'false'">Create</PrecompiledHeader>
</ClCompile>
<ClCompile Include="trace.cpp" />
<ClCompile Include="MediaMetadataExtractor.cpp" />
<ClCompile Include="WICMetadataFormatter.cpp" />
<ClCompile Include="WICRenamePatternProvider.cpp" />
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />

View File

@@ -480,7 +480,8 @@ HRESULT CPowerRenameManager::s_CreateInstance(_Outptr_ IPowerRenameManager** pps
}
CPowerRenameManager::CPowerRenameManager() :
m_refCount(1)
m_refCount(1),
m_metadataExtractor(std::make_unique<PowerRenameLib::MediaMetadataExtractor>())
{
InitializeCriticalSection(&m_critsecReentrancy);
}
@@ -1180,3 +1181,89 @@ void CPowerRenameManager::_Cleanup()
_ClearEventHandlers();
_ClearPowerRenameItems();
}
IFACEMETHODIMP CPowerRenameManager::ExtractMetadataForRename(_In_ PCWSTR filePath, _In_ PCWSTR pattern, _Out_ LPWSTR* result)
{
HRESULT hr = E_FAIL;
*result = nullptr;
try
{
if (m_metadataExtractor && filePath && pattern)
{
std::wstring filePathStr(filePath);
std::wstring patternStr(pattern);
// Check if this is an image file (simple extension check)
std::wstring extension = filePathStr.substr(filePathStr.find_last_of(L".") + 1);
std::transform(extension.begin(), extension.end(), extension.begin(), ::towlower);
if (extension == L"jpg" || extension == L"jpeg" || extension == L"tiff" ||
extension == L"tif" || extension == L"png" || extension == L"bmp")
{
// Extract image metadata
auto imageInfo = m_metadataExtractor->ExtractImageInfo(filePathStr);
if (imageInfo.has_value())
{
// Use RenamePatternProvider to resolve pattern
std::wstring formattedResult = PowerRenameLib::WICMetadataExtractor::RenamePatternProvider::ResolvePattern(patternStr, *imageInfo);
if (!formattedResult.empty())
{
// Allocate and copy the result
size_t resultLength = formattedResult.length() + 1;
*result = static_cast<LPWSTR>(CoTaskMemAlloc(resultLength * sizeof(WCHAR)));
if (*result)
{
wcscpy_s(*result, resultLength, formattedResult.c_str());
hr = S_OK;
}
else
{
hr = E_OUTOFMEMORY;
}
}
else
{
// Return "Unknown" if no metadata found
const wchar_t* unknownResult = L"Unknown";
size_t resultLength = wcslen(unknownResult) + 1;
*result = static_cast<LPWSTR>(CoTaskMemAlloc(resultLength * sizeof(WCHAR)));
if (*result)
{
wcscpy_s(*result, resultLength, unknownResult);
hr = S_OK;
}
else
{
hr = E_OUTOFMEMORY;
}
}
}
}
else
{
// Not an image file
const wchar_t* notImageResult = L"NotImage";
size_t resultLength = wcslen(notImageResult) + 1;
*result = static_cast<LPWSTR>(CoTaskMemAlloc(resultLength * sizeof(WCHAR)));
if (*result)
{
wcscpy_s(*result, resultLength, notImageResult);
hr = S_OK;
}
else
{
hr = E_OUTOFMEMORY;
}
}
}
}
catch (...)
{
hr = E_UNEXPECTED;
}
return hr;
}

View File

@@ -2,6 +2,7 @@
#include <vector>
#include <map>
#include "srwlock.h"
#include "MediaMetadataExtractor.h"
#include <PowerRenameInterfaces.h>
@@ -43,6 +44,9 @@ public:
IFACEMETHODIMP GetRenameItemFactory(_COM_Outptr_ IPowerRenameItemFactory** ppItemFactory);
IFACEMETHODIMP PutRenameItemFactory(_In_ IPowerRenameItemFactory* pItemFactory);
// Metadata extraction methods (demonstration)
IFACEMETHODIMP ExtractMetadataForRename(_In_ PCWSTR filePath, _In_ PCWSTR pattern, _Out_ LPWSTR* result);
uint32_t GetVisibleItemRealIndex(const uint32_t index) const override;
// IPowerRenameRegExEvents
@@ -121,6 +125,9 @@ protected:
CComPtr<IPowerRenameItemFactory> m_spItemFactory;
CComPtr<IPowerRenameRegEx> m_spRegEx;
// Metadata extractor for image files
std::unique_ptr<PowerRenameLib::MediaMetadataExtractor> m_metadataExtractor;
_Guarded_by_(m_lockEvents) std::vector<RENAME_MGR_EVENT> m_powerRenameManagerEvents;
_Guarded_by_(m_lockItems) std::map<int, IPowerRenameItem*> m_renameItems;
_Guarded_by_(m_lockItems) std::vector<bool> m_isVisible;

View File

@@ -0,0 +1,231 @@
#include "pch.h"
#include "MediaMetadataExtractor.h"
using namespace PowerRenameLib;
// MetadataFormatter implementation
std::wstring WICMetadataExtractor::MetadataFormatter::FormatValue(const MetadataValue& value)
{
return std::visit([](auto&& arg) -> std::wstring {
using T = std::decay_t<decltype(arg)>;
if constexpr (std::is_same_v<T, std::wstring>)
{
return arg;
}
else if constexpr (std::is_same_v<T, int32_t>)
{
return std::to_wstring(arg);
}
else if constexpr (std::is_same_v<T, uint32_t>)
{
return std::to_wstring(arg);
}
else if constexpr (std::is_same_v<T, double>)
{
std::wostringstream oss;
oss << std::fixed << std::setprecision(2) << arg;
return oss.str();
}
else if constexpr (std::is_same_v<T, bool>)
{
return arg ? L"True" : L"False";
}
else if constexpr (std::is_same_v<T, std::vector<uint8_t>>)
{
return L"[Binary Data: " + std::to_wstring(arg.size()) + L" bytes]";
}
else
{
return L"Unknown";
}
}, value);
}
std::wstring WICMetadataExtractor::MetadataFormatter::FormatGpsCoordinates(
const MetadataValue& latitude,
const MetadataValue& longitude,
const MetadataValue& latRef,
const MetadataValue& lonRef)
{
try
{
double lat = 0.0, lon = 0.0;
std::wstring latRefStr, lonRefStr;
// Extract latitude
if (std::holds_alternative<double>(latitude))
{
lat = std::get<double>(latitude);
}
else if (std::holds_alternative<std::vector<uint8_t>>(latitude))
{
// Handle rational array format (degrees, minutes, seconds)
const auto& data = std::get<std::vector<uint8_t>>(latitude);
if (data.size() >= 24) // 3 rationals * 8 bytes each
{
// This is a simplified parser - real implementation would handle endianness
// and properly parse the rational format
lat = 0.0; // Placeholder - would need proper rational parsing
}
}
// Extract longitude
if (std::holds_alternative<double>(longitude))
{
lon = std::get<double>(longitude);
}
else if (std::holds_alternative<std::vector<uint8_t>>(longitude))
{
// Similar handling as latitude
lon = 0.0; // Placeholder
}
// Extract reference directions
if (std::holds_alternative<std::wstring>(latRef))
{
latRefStr = std::get<std::wstring>(latRef);
}
if (std::holds_alternative<std::wstring>(lonRef))
{
lonRefStr = std::get<std::wstring>(lonRef);
}
// Apply reference directions
if (latRefStr == L"S") lat = -lat;
if (lonRefStr == L"W") lon = -lon;
std::wostringstream oss;
oss << std::fixed << std::setprecision(6) << lat << L", " << lon;
return oss.str();
}
catch (...)
{
return L"Invalid GPS Data";
}
}
std::wstring WICMetadataExtractor::MetadataFormatter::FormatCameraSettings(
const std::map<std::wstring, MetadataValue>& exifData)
{
std::wostringstream oss;
bool first = true;
auto addSetting = [&](const std::wstring& key, const std::wstring& prefix = L"", const std::wstring& suffix = L"")
{
auto it = exifData.find(key);
if (it != exifData.end())
{
if (!first) oss << L", ";
oss << prefix << FormatValue(it->second) << suffix;
first = false;
}
};
addSetting(L"ISO", L"ISO");
addSetting(L"FNumber", L"f/");
addSetting(L"ExposureTime", L"", L"s");
addSetting(L"FocalLength", L"", L"mm");
return oss.str();
}
std::wstring WICMetadataExtractor::MetadataFormatter::FormatDateTime(
const MetadataValue& dateTime,
const std::wstring& format)
{
if (!std::holds_alternative<std::wstring>(dateTime))
{
return L"";
}
std::wstring dateStr = std::get<std::wstring>(dateTime);
if (dateStr.empty())
{
return L"";
}
// Convert "YYYY:MM:DD HH:MM:SS" to requested format
if (format == L"yyyy-MM-dd")
{
std::replace(dateStr.begin(), dateStr.end(), L':', L'-');
size_t spacePos = dateStr.find(L' ');
if (spacePos != std::wstring::npos)
{
return dateStr.substr(0, spacePos);
}
}
else if (format == L"yyyy-MM-dd_HH-mm-ss")
{
std::replace(dateStr.begin(), dateStr.end(), L':', L'-');
std::replace(dateStr.begin(), dateStr.end(), L' ', L'_');
// Fix time part
size_t underscorePos = dateStr.find(L'_');
if (underscorePos != std::wstring::npos)
{
std::wstring timePart = dateStr.substr(underscorePos + 1);
std::replace(timePart.begin(), timePart.end(), L'-', L':');
// Re-fix time separators
size_t firstColon = timePart.find(L':');
if (firstColon != std::wstring::npos)
{
size_t secondColon = timePart.find(L':', firstColon + 1);
if (secondColon != std::wstring::npos)
{
timePart[firstColon] = L'-';
timePart[secondColon] = L'-';
}
}
return dateStr.substr(0, underscorePos + 1) + timePart;
}
}
return dateStr;
}
std::wstring WICMetadataExtractor::MetadataFormatter::FormatExposureSettings(
const MetadataValue& aperture,
const MetadataValue& shutter,
const MetadataValue& iso)
{
std::wostringstream oss;
if (std::holds_alternative<double>(aperture))
{
double fNum = std::get<double>(aperture);
if (fNum > 0.0)
{
oss << L"f/" << std::fixed << std::setprecision(1) << fNum;
}
}
if (std::holds_alternative<double>(shutter))
{
double shutterSpeed = std::get<double>(shutter);
if (shutterSpeed > 0.0)
{
if (!oss.str().empty()) oss << L" ";
if (shutterSpeed >= 1.0)
{
oss << std::fixed << std::setprecision(1) << shutterSpeed << L"s";
}
else
{
oss << L"1/" << static_cast<int>(1.0 / shutterSpeed) << L"s";
}
}
}
if (std::holds_alternative<int32_t>(iso) || std::holds_alternative<uint32_t>(iso))
{
int isoValue = std::holds_alternative<int32_t>(iso) ?
std::get<int32_t>(iso) :
static_cast<int>(std::get<uint32_t>(iso));
if (isoValue > 0)
{
if (!oss.str().empty()) oss << L" ";
oss << L"ISO" << isoValue;
}
}
return oss.str();
}

View File

@@ -0,0 +1,403 @@
#include "pch.h"
#include "MediaMetadataExtractor.h"
#include <algorithm>
using namespace PowerRenameLib;
// RenamePatternProvider implementation
std::vector<std::wstring> WICMetadataExtractor::RenamePatternProvider::GetAvailablePatterns(const ImageInfo& imageInfo)
{
std::vector<std::wstring> patterns;
// Basic patterns always available
patterns.push_back(L"filename");
patterns.push_back(L"extension");
if (imageInfo.width > 0 && imageInfo.height > 0)
{
patterns.push_back(L"dimensions");
patterns.push_back(L"width");
patterns.push_back(L"height");
}
if (!imageInfo.containerFormat.empty())
{
patterns.push_back(L"format");
}
// EXIF-based patterns
if (!imageInfo.exifData.empty())
{
if (imageInfo.exifData.find(L"Make") != imageInfo.exifData.end() ||
imageInfo.exifData.find(L"Model") != imageInfo.exifData.end())
{
patterns.push_back(L"camera");
}
if (imageInfo.exifData.find(L"DateTimeOriginal") != imageInfo.exifData.end() ||
imageInfo.exifData.find(L"DateTime") != imageInfo.exifData.end())
{
patterns.push_back(L"date");
patterns.push_back(L"datetime");
}
if (imageInfo.exifData.find(L"ISO") != imageInfo.exifData.end())
{
patterns.push_back(L"iso");
}
if (imageInfo.exifData.find(L"FNumber") != imageInfo.exifData.end())
{
patterns.push_back(L"aperture");
}
if (imageInfo.exifData.find(L"ExposureTime") != imageInfo.exifData.end())
{
patterns.push_back(L"shutter");
}
if (imageInfo.exifData.find(L"FocalLength") != imageInfo.exifData.end())
{
patterns.push_back(L"focal");
}
patterns.push_back(L"camera_settings");
patterns.push_back(L"exposure_settings");
}
// GPS patterns
if (!imageInfo.gpsData.empty())
{
patterns.push_back(L"location");
patterns.push_back(L"gps");
}
// IPTC patterns
if (!imageInfo.iptcData.empty())
{
if (imageInfo.iptcData.find(L"Title") != imageInfo.iptcData.end())
{
patterns.push_back(L"title");
}
if (imageInfo.iptcData.find(L"Keywords") != imageInfo.iptcData.end())
{
patterns.push_back(L"keywords");
}
if (imageInfo.iptcData.find(L"Byline") != imageInfo.iptcData.end())
{
patterns.push_back(L"author");
}
if (imageInfo.iptcData.find(L"Caption") != imageInfo.iptcData.end())
{
patterns.push_back(L"caption");
}
}
return patterns;
}
std::wstring WICMetadataExtractor::RenamePatternProvider::ResolvePattern(
const std::wstring& pattern,
const ImageInfo& imageInfo)
{
if (pattern == L"dimensions")
{
return std::to_wstring(imageInfo.width) + L"x" + std::to_wstring(imageInfo.height);
}
else if (pattern == L"width")
{
return std::to_wstring(imageInfo.width);
}
else if (pattern == L"height")
{
return std::to_wstring(imageInfo.height);
}
else if (pattern == L"format")
{
return imageInfo.containerFormat;
}
else if (pattern == L"pixelformat")
{
return imageInfo.pixelFormat;
}
else if (pattern == L"bitsperpixel")
{
return std::to_wstring(imageInfo.bitsPerPixel);
}
else if (pattern == L"camera")
{
std::wstring make, model;
auto makeIt = imageInfo.exifData.find(L"Make");
auto modelIt = imageInfo.exifData.find(L"Model");
if (makeIt != imageInfo.exifData.end())
{
make = MetadataFormatter::FormatValue(makeIt->second);
}
if (modelIt != imageInfo.exifData.end())
{
model = MetadataFormatter::FormatValue(modelIt->second);
}
if (!make.empty() && !model.empty())
{
return make + L" " + model;
}
else if (!model.empty())
{
return model;
}
else if (!make.empty())
{
return make;
}
return L"Unknown";
}
else if (pattern == L"date")
{
auto dateIt = imageInfo.exifData.find(L"DateTimeOriginal");
if (dateIt == imageInfo.exifData.end())
{
dateIt = imageInfo.exifData.find(L"DateTime");
}
if (dateIt != imageInfo.exifData.end())
{
return MetadataFormatter::FormatDateTime(dateIt->second, L"yyyy-MM-dd");
}
return L"Unknown";
}
else if (pattern == L"datetime")
{
auto dateIt = imageInfo.exifData.find(L"DateTimeOriginal");
if (dateIt == imageInfo.exifData.end())
{
dateIt = imageInfo.exifData.find(L"DateTime");
}
if (dateIt != imageInfo.exifData.end())
{
return MetadataFormatter::FormatDateTime(dateIt->second, L"yyyy-MM-dd_HH-mm-ss");
}
return L"Unknown";
}
else if (pattern == L"iso")
{
auto isoIt = imageInfo.exifData.find(L"ISO");
if (isoIt != imageInfo.exifData.end())
{
return L"ISO" + MetadataFormatter::FormatValue(isoIt->second);
}
return L"Unknown";
}
else if (pattern == L"aperture")
{
auto apertureIt = imageInfo.exifData.find(L"FNumber");
if (apertureIt != imageInfo.exifData.end())
{
return L"f" + MetadataFormatter::FormatValue(apertureIt->second);
}
return L"Unknown";
}
else if (pattern == L"shutter")
{
auto shutterIt = imageInfo.exifData.find(L"ExposureTime");
if (shutterIt != imageInfo.exifData.end())
{
if (std::holds_alternative<double>(shutterIt->second))
{
double speed = std::get<double>(shutterIt->second);
if (speed >= 1.0)
{
return MetadataFormatter::FormatValue(shutterIt->second) + L"s";
}
else
{
return L"1-" + std::to_wstring(static_cast<int>(1.0 / speed)) + L"s";
}
}
return MetadataFormatter::FormatValue(shutterIt->second);
}
return L"Unknown";
}
else if (pattern == L"focal")
{
auto focalIt = imageInfo.exifData.find(L"FocalLength");
if (focalIt != imageInfo.exifData.end())
{
return MetadataFormatter::FormatValue(focalIt->second) + L"mm";
}
return L"Unknown";
}
else if (pattern == L"camera_settings")
{
return MetadataFormatter::FormatCameraSettings(imageInfo.exifData);
}
else if (pattern == L"exposure_settings")
{
auto apertureIt = imageInfo.exifData.find(L"FNumber");
auto shutterIt = imageInfo.exifData.find(L"ExposureTime");
auto isoIt = imageInfo.exifData.find(L"ISO");
MetadataValue aperture = apertureIt != imageInfo.exifData.end() ? apertureIt->second : MetadataValue{};
MetadataValue shutter = shutterIt != imageInfo.exifData.end() ? shutterIt->second : MetadataValue{};
MetadataValue iso = isoIt != imageInfo.exifData.end() ? isoIt->second : MetadataValue{};
return MetadataFormatter::FormatExposureSettings(aperture, shutter, iso);
}
else if (pattern == L"location" || pattern == L"gps")
{
auto latIt = imageInfo.gpsData.find(L"GPSLatitude");
auto lonIt = imageInfo.gpsData.find(L"GPSLongitude");
auto latRefIt = imageInfo.gpsData.find(L"GPSLatitudeRef");
auto lonRefIt = imageInfo.gpsData.find(L"GPSLongitudeRef");
if (latIt != imageInfo.gpsData.end() && lonIt != imageInfo.gpsData.end())
{
MetadataValue latRef = latRefIt != imageInfo.gpsData.end() ? latRefIt->second : MetadataValue{};
MetadataValue lonRef = lonRefIt != imageInfo.gpsData.end() ? lonRefIt->second : MetadataValue{};
return MetadataFormatter::FormatGpsCoordinates(latIt->second, lonIt->second, latRef, lonRef);
}
return L"Unknown";
}
else if (pattern == L"title")
{
auto titleIt = imageInfo.iptcData.find(L"Title");
if (titleIt != imageInfo.iptcData.end())
{
return MetadataFormatter::FormatValue(titleIt->second);
}
return L"Unknown";
}
else if (pattern == L"keywords")
{
auto keywordsIt = imageInfo.iptcData.find(L"Keywords");
if (keywordsIt != imageInfo.iptcData.end())
{
std::wstring keywords = MetadataFormatter::FormatValue(keywordsIt->second);
// Replace spaces and commas with underscores for filename compatibility
std::replace(keywords.begin(), keywords.end(), L' ', L'_');
std::replace(keywords.begin(), keywords.end(), L',', L'_');
return keywords;
}
return L"Unknown";
}
else if (pattern == L"author")
{
auto authorIt = imageInfo.iptcData.find(L"Byline");
if (authorIt != imageInfo.iptcData.end())
{
return MetadataFormatter::FormatValue(authorIt->second);
}
return L"Unknown";
}
else if (pattern == L"caption")
{
auto captionIt = imageInfo.iptcData.find(L"Caption");
if (captionIt != imageInfo.iptcData.end())
{
std::wstring caption = MetadataFormatter::FormatValue(captionIt->second);
// Truncate caption if too long for filename
if (caption.length() > 50)
{
caption = caption.substr(0, 47) + L"...";
}
// Replace invalid filename characters
const std::wstring invalidChars = L"<>:\"/\\|?*";
for (wchar_t c : invalidChars)
{
std::replace(caption.begin(), caption.end(), c, L'_');
}
return caption;
}
return L"Unknown";
}
return L"Unknown";
}
std::vector<std::wstring> WICMetadataExtractor::RenamePatternProvider::GetSmartSuggestions(const ImageInfo& imageInfo)
{
std::vector<std::wstring> suggestions;
// Smart suggestions based on available metadata
auto availablePatterns = GetAvailablePatterns(imageInfo);
// Photography-focused suggestions
if (std::find(availablePatterns.begin(), availablePatterns.end(), L"date") != availablePatterns.end())
{
if (std::find(availablePatterns.begin(), availablePatterns.end(), L"camera") != availablePatterns.end())
{
suggestions.push_back(L"{date}_{camera}");
suggestions.push_back(L"{camera}_{date}");
}
if (std::find(availablePatterns.begin(), availablePatterns.end(), L"exposure_settings") != availablePatterns.end())
{
suggestions.push_back(L"{date}_{exposure_settings}");
}
suggestions.push_back(L"{date}_{dimensions}");
suggestions.push_back(L"{datetime}");
}
// Creative content suggestions
if (std::find(availablePatterns.begin(), availablePatterns.end(), L"title") != availablePatterns.end())
{
suggestions.push_back(L"{title}_{date}");
suggestions.push_back(L"{title}_{dimensions}");
if (std::find(availablePatterns.begin(), availablePatterns.end(), L"author") != availablePatterns.end())
{
suggestions.push_back(L"{title}_{author}");
}
}
// Technical/archival suggestions
if (std::find(availablePatterns.begin(), availablePatterns.end(), L"camera_settings") != availablePatterns.end())
{
suggestions.push_back(L"{camera}_{camera_settings}");
suggestions.push_back(L"{date}_{camera_settings}");
}
// Location-based suggestions
if (std::find(availablePatterns.begin(), availablePatterns.end(), L"location") != availablePatterns.end())
{
suggestions.push_back(L"{date}_{location}");
if (std::find(availablePatterns.begin(), availablePatterns.end(), L"title") != availablePatterns.end())
{
suggestions.push_back(L"{title}_{location}");
}
}
// Keyword-based suggestions
if (std::find(availablePatterns.begin(), availablePatterns.end(), L"keywords") != availablePatterns.end())
{
suggestions.push_back(L"{keywords}_{date}");
suggestions.push_back(L"{date}_{keywords}");
}
// Default fallbacks
suggestions.push_back(L"{format}_{dimensions}");
suggestions.push_back(L"{width}x{height}");
return suggestions;
}
std::map<std::wstring, std::wstring> WICMetadataExtractor::RenamePatternProvider::ResolvePatterns(
const std::vector<std::wstring>& patterns,
const ImageInfo& imageInfo)
{
std::map<std::wstring, std::wstring> results;
for (const auto& pattern : patterns)
{
results[pattern] = ResolvePattern(pattern, imageInfo);
}
return results;
}

View File

@@ -0,0 +1,132 @@
#include "pch.h"
#include "MediaMetadataExtractor.h"
#include <iostream>
#include <iomanip>
using namespace PowerRenameLib;
void PrintMetadataValue(const WICMetadataExtractor::MetadataValue& value)
{
std::visit([](const auto& v) {
using T = std::decay_t<decltype(v)>;
if constexpr (std::is_same_v<T, std::wstring>) {
std::wcout << v;
} else if constexpr (std::is_same_v<T, int32_t>) {
std::wcout << v;
} else if constexpr (std::is_same_v<T, uint32_t>) {
std::wcout << v;
} else if constexpr (std::is_same_v<T, double>) {
std::wcout << std::fixed << std::setprecision(2) << v;
} else if constexpr (std::is_same_v<T, bool>) {
std::wcout << (v ? L"true" : L"false");
} else if constexpr (std::is_same_v<T, std::vector<uint8_t>>) {
std::wcout << L"[Binary data: " << v.size() << L" bytes]";
}
}, value);
}
void PrintImageInfo(const WICMetadataExtractor::ImageInfo& info)
{
std::wcout << L"Image Information:\n";
std::wcout << L" Dimensions: " << info.width << L" x " << info.height << L"\n";
std::wcout << L" Format: " << info.containerFormat << L"\n";
std::wcout << L" Pixel Format: " << info.pixelFormat << L"\n";
std::wcout << L" Bits per Pixel: " << info.bitsPerPixel << L"\n\n";
if (!info.exifData.empty()) {
std::wcout << L"EXIF Data:\n";
for (const auto& [key, value] : info.exifData) {
std::wcout << L" " << key << L": ";
PrintMetadataValue(value);
std::wcout << L"\n";
}
std::wcout << L"\n";
}
if (!info.gpsData.empty()) {
std::wcout << L"GPS Data:\n";
for (const auto& [key, value] : info.gpsData) {
std::wcout << L" " << key << L": ";
PrintMetadataValue(value);
std::wcout << L"\n";
}
std::wcout << L"\n";
}
if (!info.iptcData.empty()) {
std::wcout << L"IPTC Data:\n";
for (const auto& [key, value] : info.iptcData) {
std::wcout << L" " << key << L": ";
PrintMetadataValue(value);
std::wcout << L"\n";
}
std::wcout << L"\n";
}
}
int wmain(int argc, wchar_t* argv[])
{
if (argc != 2) {
std::wcout << L"Usage: WICTestApp <image_file_path>\n";
return 1;
}
const std::wstring filePath = argv[1];
std::wcout << L"Testing WIC Metadata Extractor with: " << filePath << L"\n\n";
try {
WICMetadataExtractor extractor;
// Test format info
auto formatInfo = extractor.GetFormatInfo(filePath);
if (formatInfo.has_value()) {
std::wcout << L"Format Information:\n";
std::wcout << L" Format Name: " << formatInfo->formatName << L"\n";
std::wcout << L" File Extensions: " << formatInfo->fileExtensions << L"\n";
std::wcout << L" Supports Metadata: " << (formatInfo->supportsMetadata ? L"Yes" : L"No") << L"\n";
std::wcout << L" Supports Multi-Frame: " << (formatInfo->supportsMultiFrame ? L"Yes" : L"No") << L"\n\n";
}
// Test image info extraction
WICMetadataExtractor::ExtractionOptions options;
options.includeExif = true;
options.includeGps = true;
options.includeIptc = true;
options.includeXmp = true;
options.cacheMetadata = true;
auto imageInfo = extractor.ExtractImageInfo(filePath, options);
if (imageInfo.has_value()) {
PrintImageInfo(imageInfo.value());
} else {
std::wcout << L"Failed to extract image information.\n";
}
// Test metadata type detection
std::wcout << L"Metadata Type Detection:\n";
std::wcout << L" Has EXIF: " << (extractor.HasMetadataType(filePath, L"exif") ? L"Yes" : L"No") << L"\n";
std::wcout << L" Has GPS: " << (extractor.HasMetadataType(filePath, L"gps") ? L"Yes" : L"No") << L"\n";
std::wcout << L" Has IPTC: " << (extractor.HasMetadataType(filePath, L"iptc") ? L"Yes" : L"No") << L"\n";
std::wcout << L" Has XMP: " << (extractor.HasMetadataType(filePath, L"xmp") ? L"Yes" : L"No") << L"\n\n";
// Test supported formats
auto formats = extractor.GetSupportedFormats();
std::wcout << L"Supported Formats (" << formats.size() << L" total):\n";
for (size_t i = 0; i < std::min(formats.size(), 5ul); ++i) {
std::wcout << L" " << formats[i].formatName << L" (" << formats[i].fileExtensions << L")\n";
}
if (formats.size() > 5) {
std::wcout << L" ... and " << (formats.size() - 5) << L" more\n";
}
} catch (const std::exception& e) {
std::cout << "Exception: " << e.what() << std::endl;
return 1;
} catch (...) {
std::wcout << L"Unknown exception occurred.\n";
return 1;
}
return 0;
}

View File

@@ -28,5 +28,14 @@
#include <charconv>
#include <string>
#include <random>
#include <map>
#include <memory>
#include <fstream>
#include <winrt/base.h>
// Windows Imaging Component (WIC) headers
#include <wincodec.h>
#include <wincodecsdk.h>
#include <propkey.h>
#include <propvarutil.h>

View File

@@ -0,0 +1,189 @@
// Unit tests for WIC-based MediaMetadataExtractor
// This file provides basic tests for the new WIC implementation
#include "pch.h"
#include "MediaMetadataExtractor.h"
#include <gtest/gtest.h>
#include <filesystem>
using namespace PowerRenameLib;
class MediaMetadataExtractorTest : public ::testing::Test
{
protected:
void SetUp() override
{
extractor = std::make_unique<MediaMetadataExtractor>();
}
void TearDown() override
{
extractor.reset();
}
std::unique_ptr<MediaMetadataExtractor> extractor;
};
TEST_F(MediaMetadataExtractorTest, InitializationTest)
{
ASSERT_NE(extractor, nullptr);
}
TEST_F(MediaMetadataExtractorTest, FormatSupportTest)
{
// Test common image formats
EXPECT_TRUE(extractor->IsFormatSupported(L"test.jpg"));
EXPECT_TRUE(extractor->IsFormatSupported(L"test.jpeg"));
EXPECT_TRUE(extractor->IsFormatSupported(L"test.png"));
EXPECT_TRUE(extractor->IsFormatSupported(L"test.gif"));
EXPECT_TRUE(extractor->IsFormatSupported(L"test.bmp"));
EXPECT_TRUE(extractor->IsFormatSupported(L"test.tiff"));
EXPECT_TRUE(extractor->IsFormatSupported(L"test.tif"));
// Test case insensitive
EXPECT_TRUE(extractor->IsFormatSupported(L"test.JPG"));
EXPECT_TRUE(extractor->IsFormatSupported(L"test.PNG"));
// Test RAW formats
EXPECT_TRUE(extractor->IsFormatSupported(L"test.cr2"));
EXPECT_TRUE(extractor->IsFormatSupported(L"test.nef"));
EXPECT_TRUE(extractor->IsFormatSupported(L"test.arw"));
EXPECT_TRUE(extractor->IsFormatSupported(L"test.dng"));
// Test modern formats
EXPECT_TRUE(extractor->IsFormatSupported(L"test.heic"));
EXPECT_TRUE(extractor->IsFormatSupported(L"test.heif"));
EXPECT_TRUE(extractor->IsFormatSupported(L"test.webp"));
// Test unsupported formats
EXPECT_FALSE(extractor->IsFormatSupported(L"test.txt"));
EXPECT_FALSE(extractor->IsFormatSupported(L"test.pdf"));
EXPECT_FALSE(extractor->IsFormatSupported(L"test.mp4"));
// Test files without extension
EXPECT_FALSE(extractor->IsFormatSupported(L"test"));
EXPECT_FALSE(extractor->IsFormatSupported(L""));
}
TEST_F(MediaMetadataExtractorTest, NonExistentFileTest)
{
// Test with non-existent file
auto metadata = extractor->ExtractMetadata(L"non_existent_file.jpg");
// Should return empty metadata without crashing
EXPECT_EQ(metadata.width, 0);
EXPECT_EQ(metadata.height, 0);
EXPECT_TRUE(metadata.cameraModel.empty());
EXPECT_TRUE(metadata.dateTaken.empty());
}
TEST_F(MediaMetadataExtractorTest, PatternFormattingTest)
{
MediaMetadataExtractor::ImageMetadata metadata;
// Set up test data
metadata.width = 1920;
metadata.height = 1080;
metadata.cameraModel = L"Canon EOS R5";
metadata.dateTaken = L"2024-03-15 14:30:25";
metadata.iso = 400;
metadata.aperture = 2.8;
metadata.shutterSpeed = 0.008; // 1/125s
metadata.focalLength = 85.0;
metadata.gpsLocation = L"40.748817, -73.985428";
metadata.artist = L"John Doe";
metadata.title = L"Test Image";
metadata.rating = L"5";
metadata.orientation = L"Normal";
metadata.colorSpace = L"sRGB";
metadata.software = L"Adobe Lightroom";
metadata.bitDepth = 24;
metadata.flashUsed = true;
// Test basic patterns
EXPECT_EQ(extractor->FormatMetadataForRename(metadata, L"camera"), L"Canon EOS R5");
EXPECT_EQ(extractor->FormatMetadataForRename(metadata, L"date"), L"2024-03-15");
EXPECT_EQ(extractor->FormatMetadataForRename(metadata, L"dimensions"), L"1920x1080");
EXPECT_EQ(extractor->FormatMetadataForRename(metadata, L"width"), L"1920");
EXPECT_EQ(extractor->FormatMetadataForRename(metadata, L"height"), L"1080");
EXPECT_EQ(extractor->FormatMetadataForRename(metadata, L"iso"), L"ISO400");
EXPECT_EQ(extractor->FormatMetadataForRename(metadata, L"aperture"), L"f2.8");
EXPECT_EQ(extractor->FormatMetadataForRename(metadata, L"shutter"), L"1/125s");
EXPECT_EQ(extractor->FormatMetadataForRename(metadata, L"focal"), L"85mm");
EXPECT_EQ(extractor->FormatMetadataForRename(metadata, L"location"), L"40.748817, -73.985428");
EXPECT_EQ(extractor->FormatMetadataForRename(metadata, L"artist"), L"John Doe");
EXPECT_EQ(extractor->FormatMetadataForRename(metadata, L"title"), L"Test Image");
EXPECT_EQ(extractor->FormatMetadataForRename(metadata, L"rating"), L"5");
EXPECT_EQ(extractor->FormatMetadataForRename(metadata, L"orientation"), L"Normal");
EXPECT_EQ(extractor->FormatMetadataForRename(metadata, L"colorspace"), L"sRGB");
EXPECT_EQ(extractor->FormatMetadataForRename(metadata, L"software"), L"Adobe Lightroom");
EXPECT_EQ(extractor->FormatMetadataForRename(metadata, L"bitdepth"), L"24bit");
EXPECT_EQ(extractor->FormatMetadataForRename(metadata, L"flash"), L"Flash");
// Test datetime pattern
std::wstring datetime = extractor->FormatMetadataForRename(metadata, L"datetime");
EXPECT_EQ(datetime, L"2024-03-15_14-30-25");
// Test unknown pattern
EXPECT_EQ(extractor->FormatMetadataForRename(metadata, L"unknown"), L"Unknown");
// Test empty metadata
MediaMetadataExtractor::ImageMetadata emptyMetadata;
EXPECT_EQ(extractor->FormatMetadataForRename(emptyMetadata, L"camera"), L"Unknown");
EXPECT_EQ(extractor->FormatMetadataForRename(emptyMetadata, L"date"), L"Unknown");
EXPECT_EQ(extractor->FormatMetadataForRename(emptyMetadata, L"iso"), L"Unknown");
// Test flash when not used
emptyMetadata.flashUsed = false;
EXPECT_EQ(extractor->FormatMetadataForRename(emptyMetadata, L"flash"), L"NoFlash");
}
TEST_F(MediaMetadataExtractorTest, BackwardCompatibilityTest)
{
// Test that ExtractEXIFMetadata still works (calls ExtractMetadata internally)
auto metadata = extractor->ExtractEXIFMetadata(L"non_existent_file.jpg");
// Should return empty metadata without crashing
EXPECT_EQ(metadata.width, 0);
EXPECT_EQ(metadata.height, 0);
EXPECT_TRUE(metadata.cameraModel.empty());
}
TEST_F(MediaMetadataExtractorTest, ShutterSpeedFormattingTest)
{
MediaMetadataExtractor::ImageMetadata metadata;
// Test fast shutter speed (fraction)
metadata.shutterSpeed = 0.008; // 1/125s
EXPECT_EQ(extractor->FormatMetadataForRename(metadata, L"shutter"), L"1/125s");
// Test very fast shutter speed
metadata.shutterSpeed = 0.001; // 1/1000s
EXPECT_EQ(extractor->FormatMetadataForRename(metadata, L"shutter"), L"1/1000s");
// Test slow shutter speed (seconds)
metadata.shutterSpeed = 2.5; // 2.5 seconds
EXPECT_EQ(extractor->FormatMetadataForRename(metadata, L"shutter"), L"2.5s");
// Test 1 second
metadata.shutterSpeed = 1.0;
EXPECT_EQ(extractor->FormatMetadataForRename(metadata, L"shutter"), L"1.0s");
}
// Performance test (optional - can be disabled for regular runs)
TEST_F(MediaMetadataExtractorTest, DISABLED_PerformanceTest)
{
const int iterations = 1000;
auto start = std::chrono::high_resolution_clock::now();
for (int i = 0; i < iterations; ++i)
{
extractor->IsFormatSupported(L"test.jpg");
}
auto end = std::chrono::high_resolution_clock::now();
auto duration = std::chrono::duration_cast<std::chrono::microseconds>(end - start);
// Should be very fast (less than 1ms per call)
EXPECT_LT(duration.count() / iterations, 1000); // Less than 1000 microseconds per call
}