mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-01-28 06:57:27 +01:00
Compare commits
6 Commits
tools/Rele
...
yuleng/ren
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a1bb51530d | ||
|
|
acff5cbf65 | ||
|
|
ee76efdf06 | ||
|
|
48ad7bf940 | ||
|
|
f311b5a983 | ||
|
|
911fb2b7bd |
3
.gitmodules
vendored
3
.gitmodules
vendored
@@ -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
1
deps/TinyEXIF
vendored
Submodule
Submodule deps/TinyEXIF added at 79e124e1e1
24
deps/TinyEXIF.props
vendored
Normal file
24
deps/TinyEXIF.props
vendored
Normal 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
1
deps/cxxopts
vendored
Submodule
Submodule deps/cxxopts added at 12e496da3d
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
830
src/modules/powerrename/lib/MediaMetadataExtractor.cpp
Normal file
830
src/modules/powerrename/lib/MediaMetadataExtractor.cpp
Normal 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();
|
||||
}
|
||||
206
src/modules/powerrename/lib/MediaMetadataExtractor.h
Normal file
206
src/modules/powerrename/lib/MediaMetadataExtractor.h
Normal 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
|
||||
}
|
||||
222
src/modules/powerrename/lib/MediaMetadataExtractor_Example.cpp
Normal file
222
src/modules/powerrename/lib/MediaMetadataExtractor_Example.cpp
Normal 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;
|
||||
}
|
||||
}
|
||||
36
src/modules/powerrename/lib/MetadataTest.cpp
Normal file
36
src/modules/powerrename/lib/MetadataTest.cpp
Normal 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
|
||||
}
|
||||
}
|
||||
@@ -22,6 +22,7 @@ enum PowerRenameFlags
|
||||
CreationTime = 0x4000,
|
||||
ModificationTime = 0x8000,
|
||||
AccessTime = 0x10000,
|
||||
EXIFTime = 0x20000,
|
||||
};
|
||||
|
||||
enum PowerRenameFilters
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
231
src/modules/powerrename/lib/WICMetadataFormatter.cpp
Normal file
231
src/modules/powerrename/lib/WICMetadataFormatter.cpp
Normal 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();
|
||||
}
|
||||
403
src/modules/powerrename/lib/WICRenamePatternProvider.cpp
Normal file
403
src/modules/powerrename/lib/WICRenamePatternProvider.cpp
Normal 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;
|
||||
}
|
||||
132
src/modules/powerrename/lib/WICTestApp.cpp
Normal file
132
src/modules/powerrename/lib/WICTestApp.cpp
Normal 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;
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user