mirror of
https://github.com/bpatrik/pigallery2.git
synced 2025-01-08 04:03:48 +02:00
Merge pull request #841 from martyone/xmp-time
Read creation date from XMP sidecar too
This commit is contained in:
commit
5f0a7a1873
@ -82,9 +82,14 @@ export class MetadataLoader {
|
||||
if (Utils.isInt32(parseInt(stream.avg_frame_rate, 10))) {
|
||||
metadata.fps = parseInt(stream.avg_frame_rate, 10) || null;
|
||||
}
|
||||
metadata.creationDate =
|
||||
Date.parse(stream.tags.creation_time) ||
|
||||
metadata.creationDate;
|
||||
if (
|
||||
stream.tags !== undefined &&
|
||||
typeof stream.tags.creation_time === 'string'
|
||||
) {
|
||||
metadata.creationDate =
|
||||
Date.parse(stream.tags.creation_time) ||
|
||||
metadata.creationDate;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -139,22 +144,51 @@ export class MetadataLoader {
|
||||
if (fs.existsSync(sidecarPath)) {
|
||||
const sidecarData = await exifr.sidecar(sidecarPath);
|
||||
if (sidecarData !== undefined) {
|
||||
if ((sidecarData as SideCar).dc.subject !== undefined) {
|
||||
if (metadata.keywords === undefined) {
|
||||
metadata.keywords = [];
|
||||
}
|
||||
let keywords = (sidecarData as SideCar).dc.subject || [];
|
||||
if (typeof keywords === 'string') {
|
||||
keywords = [keywords];
|
||||
}
|
||||
for (const kw of keywords) {
|
||||
if (metadata.keywords.indexOf(kw) === -1) {
|
||||
metadata.keywords.push(kw);
|
||||
if ((sidecarData as SideCar).dc !== undefined) {
|
||||
if ((sidecarData as SideCar).dc.subject !== undefined) {
|
||||
if (metadata.keywords === undefined) {
|
||||
metadata.keywords = [];
|
||||
}
|
||||
let keywords = (sidecarData as SideCar).dc.subject || [];
|
||||
if (typeof keywords === 'string') {
|
||||
keywords = [keywords];
|
||||
}
|
||||
for (const kw of keywords) {
|
||||
if (metadata.keywords.indexOf(kw) === -1) {
|
||||
metadata.keywords.push(kw);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if ((sidecarData as SideCar).xmp.Rating !== undefined) {
|
||||
metadata.rating = (sidecarData as SideCar).xmp.Rating;
|
||||
let hasPhotoshopDate = false;
|
||||
if ((sidecarData as SideCar).photoshop !== undefined) {
|
||||
if ((sidecarData as SideCar).photoshop.DateCreated !== undefined) {
|
||||
const date = Utils.timestampToMS((sidecarData as SideCar).photoshop.DateCreated, null);
|
||||
if (date) {
|
||||
metadata.creationDate = date;
|
||||
hasPhotoshopDate = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (Object.hasOwn(sidecarData, 'xap')) {
|
||||
(sidecarData as any)['xmp'] = (sidecarData as any)['xap'];
|
||||
delete (sidecarData as any)['xap'];
|
||||
}
|
||||
if ((sidecarData as SideCar).xmp !== undefined) {
|
||||
if ((sidecarData as SideCar).xmp.Rating !== undefined) {
|
||||
metadata.rating = (sidecarData as SideCar).xmp.Rating;
|
||||
}
|
||||
if (
|
||||
!hasPhotoshopDate && (
|
||||
(sidecarData as SideCar).xmp.CreateDate !== undefined ||
|
||||
(sidecarData as SideCar).xmp.ModifyDate !== undefined
|
||||
)
|
||||
) {
|
||||
metadata.creationDate =
|
||||
Utils.timestampToMS((sidecarData as SideCar).xmp.CreateDate, null) ||
|
||||
Utils.timestampToMS((sidecarData as SideCar).xmp.ModifyDate, null) ||
|
||||
metadata.creationDate;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -199,43 +233,6 @@ export class MetadataLoader {
|
||||
mergeOutput: false //don't merge output, because things like Microsoft Rating (percent) and xmp.rating will be merged
|
||||
};
|
||||
|
||||
//function to convert timestamp into milliseconds taking offset into account
|
||||
const timestampToMS = (timestamp: string, offset: string) => {
|
||||
if (!timestamp) {
|
||||
return undefined;
|
||||
}
|
||||
//replace : with - in the yyyy-mm-dd part of the timestamp.
|
||||
let formattedTimestamp = timestamp.substring(0,9).replaceAll(':', '-') + timestamp.substring(9,timestamp.length);
|
||||
if (formattedTimestamp.indexOf("Z") > 0) { //replace Z (and what comes after the Z) with offset
|
||||
formattedTimestamp.substring(0, formattedTimestamp.indexOf("Z")) + (offset ? offset : '+00:00');
|
||||
} else if (formattedTimestamp.indexOf("+") > 0) { //don't do anything
|
||||
} else { //add offset
|
||||
formattedTimestamp = formattedTimestamp + (offset ? offset : '+00:00');
|
||||
}
|
||||
//parse into MS and return
|
||||
return Date.parse(formattedTimestamp);
|
||||
}
|
||||
|
||||
//function to calculate offset from exif.exif.gpsTimeStamp or exif.gps.GPSDateStamp + exif.gps.GPSTimestamp
|
||||
const getTimeOffsetByGPSStamp = (timestamp: string, gpsTimeStamp: string, gps: any) => {
|
||||
let UTCTimestamp = gpsTimeStamp;
|
||||
if (!UTCTimestamp &&
|
||||
gps &&
|
||||
gps.GPSDateStamp &&
|
||||
gps.GPSTimeStamp) { //else use exif.gps.GPS*Stamp if available
|
||||
//GPS timestamp is always UTC (+00:00)
|
||||
UTCTimestamp = gps.GPSDateStamp.replaceAll(':', '-') + gps.GPSTimeStamp.join(':');
|
||||
}
|
||||
if (UTCTimestamp && timestamp) {
|
||||
//offset in minutes is the difference between gps timestamp and given timestamp
|
||||
//to calculate this correctly, we have to work with the same offset
|
||||
const offsetMinutes = (timestampToMS(timestamp, '+00:00')- timestampToMS(UTCTimestamp, '+00:00')) / 1000 / 60;
|
||||
return Utils.getOffsetString(offsetMinutes);
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
//Function to convert html code for special characters into their corresponding character (used in exif.photoshop-section)
|
||||
const unescape = (tag: string) => {
|
||||
return tag.replace(/&#([0-9]{1,3});/gi, function (match, numStr) {
|
||||
@ -368,32 +365,32 @@ export class MetadataLoader {
|
||||
//DateTimeOriginal is when the camera shutter closed
|
||||
let offset = exif.exif.OffsetTimeOriginal; //OffsetTimeOriginal is the corresponding offset
|
||||
if (!offset) { //Find offset among other options if possible
|
||||
offset = exif.exif.OffsetTimeDigitized || exif.exif.OffsetTime || getTimeOffsetByGPSStamp(exif.exif.DateTimeOriginal, exif.exif.GPSTimeStamp, exif.gps);
|
||||
offset = exif.exif.OffsetTimeDigitized || exif.exif.OffsetTime || Utils.getTimeOffsetByGPSStamp(exif.exif.DateTimeOriginal, exif.exif.GPSTimeStamp, exif.gps);
|
||||
}
|
||||
metadata.creationDate = timestampToMS(exif.exif.DateTimeOriginal, offset);
|
||||
metadata.creationDate = Utils.timestampToMS(exif.exif.DateTimeOriginal, offset);
|
||||
metadata.creationDateOffset = offset;
|
||||
} else if (exif.exif.CreateDate) { //using else if here, because DateTimeOriginal has preceedence
|
||||
//Create is when the camera wrote the file (typically within the same ms as shutter close)
|
||||
let offset = exif.exif.OffsetTimeDigitized; //OffsetTimeDigitized is the corresponding offset
|
||||
if (!offset) { //Find offset among other options if possible
|
||||
offset = exif.exif.OffsetTimeOriginal || exif.exif.OffsetTime || getTimeOffsetByGPSStamp(exif.exif.DateTimeOriginal, exif.exif.GPSTimeStamp, exif.gps);
|
||||
offset = exif.exif.OffsetTimeOriginal || exif.exif.OffsetTime || Utils.getTimeOffsetByGPSStamp(exif.exif.DateTimeOriginal, exif.exif.GPSTimeStamp, exif.gps);
|
||||
}
|
||||
metadata.creationDate = timestampToMS(exif.exif.CreateDate, offset);
|
||||
metadata.creationDate = Utils.timestampToMS(exif.exif.CreateDate, offset);
|
||||
metadata.creationDateOffset = offset;
|
||||
} else if (exif.ifd0?.ModifyDate) { //using else if here, because DateTimeOriginal and CreatDate have preceedence
|
||||
let offset = exif.exif.OffsetTime; //exif.Offsettime is the offset corresponding to ifd0.ModifyDate
|
||||
if (!offset) { //Find offset among other options if possible
|
||||
offset = exif.exif.DateTimeOriginal || exif.exif.OffsetTimeDigitized || getTimeOffsetByGPSStamp(exif.ifd0.ModifyDate, exif.exif.GPSTimeStamp, exif.gps);
|
||||
offset = exif.exif.DateTimeOriginal || exif.exif.OffsetTimeDigitized || Utils.getTimeOffsetByGPSStamp(exif.ifd0.ModifyDate, exif.exif.GPSTimeStamp, exif.gps);
|
||||
}
|
||||
metadata.creationDate = timestampToMS(exif.ifd0.ModifyDate, offset);
|
||||
metadata.creationDate = Utils.timestampToMS(exif.ifd0.ModifyDate, offset);
|
||||
metadata.creationDateOffset = offset
|
||||
} else if (exif.ihdr && exif.ihdr["Creation Time"]) {// again else if (another fallback date if the good ones aren't there) {
|
||||
const any_offset = exif.exif.DateTimeOriginal || exif.exif.OffsetTimeDigitized || exif.exif.OffsetTime || getTimeOffsetByGPSStamp(exif.ifd0.ModifyDate, exif.exif.GPSTimeStamp, exif.gps);
|
||||
metadata.creationDate = timestampToMS(exif.ihdr["Creation Time"], any_offset);
|
||||
const any_offset = exif.exif.DateTimeOriginal || exif.exif.OffsetTimeDigitized || exif.exif.OffsetTime || Utils.getTimeOffsetByGPSStamp(exif.ifd0.ModifyDate, exif.exif.GPSTimeStamp, exif.gps);
|
||||
metadata.creationDate = Utils.timestampToMS(exif.ihdr["Creation Time"], any_offset);
|
||||
metadata.creationDateOffset = any_offset;
|
||||
} else if (exif.xmp?.MetadataDate) {// again else if (another fallback date if the good ones aren't there - metadata date is probably later than actual creation date, but much better than file time) {
|
||||
const any_offset = exif.exif.DateTimeOriginal || exif.exif.OffsetTimeDigitized || exif.exif.OffsetTime || getTimeOffsetByGPSStamp(exif.ifd0.ModifyDate, exif.exif.GPSTimeStamp, exif.gps);
|
||||
metadata.creationDate = timestampToMS(exif.xmp.MetadataDate, any_offset);
|
||||
const any_offset = exif.exif.DateTimeOriginal || exif.exif.OffsetTimeDigitized || exif.exif.OffsetTime || Utils.getTimeOffsetByGPSStamp(exif.ifd0.ModifyDate, exif.exif.GPSTimeStamp, exif.gps);
|
||||
metadata.creationDate = Utils.timestampToMS(exif.xmp.MetadataDate, any_offset);
|
||||
metadata.creationDateOffset = any_offset;
|
||||
}
|
||||
if (exif.exif.LensModel && exif.exif.LensModel !== '') {
|
||||
@ -623,25 +620,51 @@ export class MetadataLoader {
|
||||
const sidecarData = await exifr.sidecar(sidecarPath);
|
||||
|
||||
if (sidecarData !== undefined) {
|
||||
if ((sidecarData as SideCar).dc.subject !== undefined) {
|
||||
if (metadata.keywords === undefined) {
|
||||
metadata.keywords = [];
|
||||
}
|
||||
let keywords = (sidecarData as SideCar).dc.subject || [];
|
||||
if (typeof keywords === 'string') {
|
||||
keywords = [keywords];
|
||||
}
|
||||
for (const kw of keywords) {
|
||||
if (metadata.keywords.indexOf(kw) === -1) {
|
||||
metadata.keywords.push(kw);
|
||||
if ((sidecarData as SideCar).dc !== undefined) {
|
||||
if ((sidecarData as SideCar).dc.subject !== undefined) {
|
||||
if (metadata.keywords === undefined) {
|
||||
metadata.keywords = [];
|
||||
}
|
||||
let keywords = (sidecarData as SideCar).dc.subject || [];
|
||||
if (typeof keywords === 'string') {
|
||||
keywords = [keywords];
|
||||
}
|
||||
for (const kw of keywords) {
|
||||
if (metadata.keywords.indexOf(kw) === -1) {
|
||||
metadata.keywords.push(kw);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if ((sidecarData as SideCar).xmp.Rating !== undefined) {
|
||||
metadata.rating = (sidecarData as SideCar).xmp.Rating;
|
||||
let hasPhotoshopDate = false;
|
||||
if ((sidecarData as SideCar).photoshop !== undefined) {
|
||||
if ((sidecarData as SideCar).photoshop.DateCreated !== undefined) {
|
||||
const date = Utils.timestampToMS((sidecarData as SideCar).photoshop.DateCreated, null);
|
||||
if (date) {
|
||||
metadata.creationDate = date;
|
||||
hasPhotoshopDate = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if ((sidecarData as SideCar).xmp.CreateDate) {
|
||||
metadata.creationDate = timestampToMS((sidecarData as SideCar).xmp.CreateDate, null);
|
||||
if (Object.hasOwn(sidecarData, 'xap')) {
|
||||
(sidecarData as any)['xmp'] = (sidecarData as any)['xap'];
|
||||
delete (sidecarData as any)['xap'];
|
||||
}
|
||||
if ((sidecarData as SideCar).xmp !== undefined) {
|
||||
if ((sidecarData as SideCar).xmp.Rating !== undefined) {
|
||||
metadata.rating = (sidecarData as SideCar).xmp.Rating;
|
||||
}
|
||||
if (
|
||||
!hasPhotoshopDate && (
|
||||
(sidecarData as SideCar).xmp.CreateDate !== undefined ||
|
||||
(sidecarData as SideCar).xmp.ModifyDate !== undefined
|
||||
)
|
||||
) {
|
||||
metadata.creationDate =
|
||||
Utils.timestampToMS((sidecarData as SideCar).xmp.CreateDate, null) ||
|
||||
Utils.timestampToMS((sidecarData as SideCar).xmp.ModifyDate, null) ||
|
||||
metadata.creationDate;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -124,6 +124,43 @@ export class Utils {
|
||||
return new Date(new Date(d).toISOString().substring(0,19) + (offset ? offset : '')).getFullYear();
|
||||
}
|
||||
|
||||
//function to convert timestamp into milliseconds taking offset into account
|
||||
static timestampToMS(timestamp: string, offset: string) {
|
||||
if (!timestamp) {
|
||||
return undefined;
|
||||
}
|
||||
//replace : with - in the yyyy-mm-dd part of the timestamp.
|
||||
let formattedTimestamp = timestamp.substring(0,9).replaceAll(':', '-') + timestamp.substring(9,timestamp.length);
|
||||
if (formattedTimestamp.indexOf("Z") > 0) { //replace Z (and what comes after the Z) with offset
|
||||
formattedTimestamp.substring(0, formattedTimestamp.indexOf("Z")) + (offset ? offset : '+00:00');
|
||||
} else if (formattedTimestamp.indexOf("+") > 0) { //don't do anything
|
||||
} else { //add offset
|
||||
formattedTimestamp = formattedTimestamp + (offset ? offset : '+00:00');
|
||||
}
|
||||
//parse into MS and return
|
||||
return Date.parse(formattedTimestamp);
|
||||
}
|
||||
|
||||
//function to calculate offset from exif.exif.gpsTimeStamp or exif.gps.GPSDateStamp + exif.gps.GPSTimestamp
|
||||
static getTimeOffsetByGPSStamp(timestamp: string, gpsTimeStamp: string, gps: any) {
|
||||
let UTCTimestamp = gpsTimeStamp;
|
||||
if (!UTCTimestamp &&
|
||||
gps &&
|
||||
gps.GPSDateStamp &&
|
||||
gps.GPSTimeStamp) { //else use exif.gps.GPS*Stamp if available
|
||||
//GPS timestamp is always UTC (+00:00)
|
||||
UTCTimestamp = gps.GPSDateStamp.replaceAll(':', '-') + gps.GPSTimeStamp.join(':');
|
||||
}
|
||||
if (UTCTimestamp && timestamp) {
|
||||
//offset in minutes is the difference between gps timestamp and given timestamp
|
||||
//to calculate this correctly, we have to work with the same offset
|
||||
const offsetMinutes = (Utils.timestampToMS(timestamp, '+00:00')- Utils.timestampToMS(UTCTimestamp, '+00:00')) / 1000 / 60;
|
||||
return Utils.getOffsetString(offsetMinutes);
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
static getOffsetString(offsetMinutes: number) {
|
||||
if (-720 <= offsetMinutes && offsetMinutes <= 840) {
|
||||
//valid offset is within -12 and +14 hrs (https://en.wikipedia.org/wiki/List_of_UTC_offsets)
|
||||
|
@ -32,6 +32,7 @@ export interface MediaDimension {
|
||||
export interface SideCar {
|
||||
dc?: SideCarDc;
|
||||
xmp?: SideCarXmp;
|
||||
photoshop?: SideCarPhotoshop;
|
||||
}
|
||||
|
||||
export interface SideCarDc {
|
||||
@ -41,6 +42,13 @@ export interface SideCarDc {
|
||||
export interface SideCarXmp {
|
||||
Rating?: RatingTypes;
|
||||
CreateDate?: string;
|
||||
ModifyDate?: string;
|
||||
}
|
||||
|
||||
export interface SideCarPhotoshop {
|
||||
// Corresponds to Exif.Photo.DateTimeOriginal. No corresponding key exists in
|
||||
// the xmp namespace!
|
||||
DateCreated?: string;
|
||||
}
|
||||
|
||||
export const MediaDTOUtils = {
|
||||
|
BIN
test/backend/assets/sidecar/20240107_110258.jpg
Normal file
BIN
test/backend/assets/sidecar/20240107_110258.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 15 KiB |
73
test/backend/assets/sidecar/20240107_110258.jpg.xmp
Normal file
73
test/backend/assets/sidecar/20240107_110258.jpg.xmp
Normal file
@ -0,0 +1,73 @@
|
||||
<?xpacket begin="" id="W5M0MpCehiHzreSzNTczkc9d"?>
|
||||
<x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="XMP Core 4.4.0-Exiv2">
|
||||
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
|
||||
<rdf:Description rdf:about=""
|
||||
xmlns:xap="http://ns.adobe.com/xap/1.0/"
|
||||
xmlns:exif="http://ns.adobe.com/exif/1.0/"
|
||||
xmlns:digiKam="http://www.digikam.org/ns/1.0/"
|
||||
xmlns:tiff="http://ns.adobe.com/tiff/1.0/"
|
||||
xmlns:photoshop="http://ns.adobe.com/photoshop/1.0/"
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xap:Rating="3"
|
||||
xap:CreatorTool="digiKam-8.2.0"
|
||||
xap:ModifyDate="2024-01-07T11:02:59.177"
|
||||
xap:CreateDate="2024-01-07T11:02:59.177"
|
||||
exif:GPSVersionID="2.0.0.0"
|
||||
exif:GPSLatitude="50,5.3752490N"
|
||||
exif:GPSLongitude="14,23.8445710E"
|
||||
exif:GPSMapDatum="WGS-84"
|
||||
exif:ExifVersion="0220"
|
||||
exif:PixelXDimension="10"
|
||||
exif:PixelYDimension="13"
|
||||
exif:ExposureTime="1/105"
|
||||
exif:FNumber="220/100"
|
||||
exif:ExposureProgram="2"
|
||||
exif:ShutterSpeedValue="1/105"
|
||||
exif:ApertureValue="227/100"
|
||||
exif:BrightnessValue="0/100"
|
||||
exif:ExposureBiasValue="0/100"
|
||||
exif:MaxApertureValue="227/100"
|
||||
exif:MeteringMode="2"
|
||||
exif:FocalLength="375/100"
|
||||
exif:ExposureMode="0"
|
||||
exif:WhiteBalance="0"
|
||||
exif:DigitalZoomRatio="100/100"
|
||||
exif:FocalLengthIn35mmFilm="25"
|
||||
exif:SceneCaptureType="0"
|
||||
exif:ImageUniqueID="A64QLMD00YM"
|
||||
tiff:ImageWidth="10"
|
||||
tiff:ImageLength="13"
|
||||
tiff:Orientation="1"
|
||||
tiff:YCbCrPositioning="1"
|
||||
tiff:XResolution="72/1"
|
||||
tiff:YResolution="72/1"
|
||||
tiff:ResolutionUnit="2"
|
||||
tiff:Make="samsung"
|
||||
tiff:Model="SM-A715F"
|
||||
tiff:Software="digiKam-8.2.0"
|
||||
photoshop:DateCreated="2024-01-07T11:02:59.177">
|
||||
<exif:Flash
|
||||
exif:Fired="False"
|
||||
exif:Return="0"
|
||||
exif:Mode="0"
|
||||
exif:Function="False"
|
||||
exif:RedEyeMode="False"/>
|
||||
<exif:ISOSpeedRatings>
|
||||
<rdf:Seq>
|
||||
<rdf:li>40</rdf:li>
|
||||
</rdf:Seq>
|
||||
</exif:ISOSpeedRatings>
|
||||
<digiKam:TagsList>
|
||||
<rdf:Seq>
|
||||
<rdf:li>Travel</rdf:li>
|
||||
</rdf:Seq>
|
||||
</digiKam:TagsList>
|
||||
<dc:subject>
|
||||
<rdf:Bag>
|
||||
<rdf:li>Travel</rdf:li>
|
||||
</rdf:Bag>
|
||||
</dc:subject>
|
||||
</rdf:Description>
|
||||
</rdf:RDF>
|
||||
</x:xmpmeta>
|
||||
<?xpacket end="w"?>
|
22
test/backend/assets/sidecar/20240107_110258.json
Normal file
22
test/backend/assets/sidecar/20240107_110258.json
Normal file
@ -0,0 +1,22 @@
|
||||
{
|
||||
"cameraData": {
|
||||
"ISO": 40,
|
||||
"exposure": 0.009524,
|
||||
"fStop": 2.2,
|
||||
"focalLength": 3.75,
|
||||
"make": "samsung",
|
||||
"model": "SM-A715F"
|
||||
},
|
||||
"creationDate": 1704625379177,
|
||||
"creationDateOffset": "+01:00",
|
||||
"fileSize": 15126,
|
||||
"size": {
|
||||
"height": 13,
|
||||
"width": 10
|
||||
},
|
||||
"keywords": [
|
||||
"Výlet",
|
||||
"Travel"
|
||||
],
|
||||
"rating": 3
|
||||
}
|
BIN
test/backend/assets/sidecar/20240121_102400.JPG
Executable file
BIN
test/backend/assets/sidecar/20240121_102400.JPG
Executable file
Binary file not shown.
After Width: | Height: | Size: 25 KiB |
91
test/backend/assets/sidecar/20240121_102400.JPG.xmp
Normal file
91
test/backend/assets/sidecar/20240121_102400.JPG.xmp
Normal file
@ -0,0 +1,91 @@
|
||||
<?xpacket begin="" id="W5M0MpCehiHzreSzNTczkc9d"?>
|
||||
<x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="XMP Core 4.4.0-Exiv2">
|
||||
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
|
||||
<rdf:Description rdf:about=""
|
||||
xmlns:xmp="http://ns.adobe.com/xap/1.0/"
|
||||
xmlns:exif="http://ns.adobe.com/exif/1.0/"
|
||||
xmlns:digiKam="http://www.digikam.org/ns/1.0/"
|
||||
xmlns:tiff="http://ns.adobe.com/tiff/1.0/"
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:photoshop="http://ns.adobe.com/photoshop/1.0/"
|
||||
xmp:CreatorTool="NIKON Z 30 Ver.01.00 "
|
||||
xmp:Rating="3"
|
||||
xmp:ModifyDate="2024-03-04T12:03:45.65"
|
||||
xmp:CreateDate="2024-03-04T12:03:45.65"
|
||||
exif:GPSLatitude="50,5.3752490N"
|
||||
exif:GPSLongitude="14,23.8445710E"
|
||||
exif:GPSMapDatum="WGS-84"
|
||||
exif:ExifVersion="0231"
|
||||
exif:FlashpixVersion="0100"
|
||||
exif:CompressedBitsPerPixel="4/1"
|
||||
exif:PixelXDimension="15"
|
||||
exif:PixelYDimension="10"
|
||||
exif:ExposureTime="10/3200"
|
||||
exif:FNumber="630/100"
|
||||
exif:ExposureProgram="0"
|
||||
exif:ExposureBiasValue="4/6"
|
||||
exif:MeteringMode="5"
|
||||
exif:LightSource="11"
|
||||
exif:FocalLength="160/10"
|
||||
exif:SensingMethod="2"
|
||||
exif:FileSource="3"
|
||||
exif:SceneType="1"
|
||||
exif:CFAPattern="2 0 2 0 0 1 1 2"
|
||||
exif:CustomRendered="1"
|
||||
exif:ExposureMode="0"
|
||||
exif:WhiteBalance="1"
|
||||
exif:FocalLengthIn35mmFilm="24"
|
||||
exif:SceneCaptureType="0"
|
||||
exif:GainControl="0"
|
||||
exif:Contrast="0"
|
||||
exif:Saturation="0"
|
||||
exif:Sharpness="0"
|
||||
exif:SubjectDistanceRange="0"
|
||||
exif:GPSVersionID="2.3.0.0"
|
||||
tiff:ImageWidth="15"
|
||||
tiff:ImageLength="10"
|
||||
tiff:Orientation="1"
|
||||
tiff:YCbCrPositioning="2"
|
||||
tiff:XResolution="300/1"
|
||||
tiff:YResolution="300/1"
|
||||
tiff:ResolutionUnit="2"
|
||||
tiff:Make="NIKON CORPORATION"
|
||||
tiff:Model="NIKON Z 30"
|
||||
tiff:Software="digiKam-8.2.0"
|
||||
photoshop:DateCreated="2024-01-21T10:24:00.93">
|
||||
<exif:Flash
|
||||
exif:Fired="False"
|
||||
exif:Return="0"
|
||||
exif:Mode="0"
|
||||
exif:Function="False"
|
||||
exif:RedEyeMode="False"/>
|
||||
<exif:ComponentsConfiguration>
|
||||
<rdf:Seq>
|
||||
<rdf:li>1</rdf:li>
|
||||
<rdf:li>2</rdf:li>
|
||||
<rdf:li>3</rdf:li>
|
||||
<rdf:li>0</rdf:li>
|
||||
</rdf:Seq>
|
||||
</exif:ComponentsConfiguration>
|
||||
<exif:ISOSpeedRatings>
|
||||
<rdf:Seq>
|
||||
<rdf:li>100</rdf:li>
|
||||
</rdf:Seq>
|
||||
</exif:ISOSpeedRatings>
|
||||
<digiKam:TagsList>
|
||||
<rdf:Seq>
|
||||
<rdf:li>Travel</rdf:li>
|
||||
</rdf:Seq>
|
||||
</digiKam:TagsList>
|
||||
<dc:creator>
|
||||
<rdf:Seq/>
|
||||
</dc:creator>
|
||||
<dc:subject>
|
||||
<rdf:Bag>
|
||||
<rdf:li>Travel</rdf:li>
|
||||
</rdf:Bag>
|
||||
</dc:subject>
|
||||
</rdf:Description>
|
||||
</rdf:RDF>
|
||||
</x:xmpmeta>
|
||||
<?xpacket end="w"?>
|
23
test/backend/assets/sidecar/20240121_102400.json
Normal file
23
test/backend/assets/sidecar/20240121_102400.json
Normal file
@ -0,0 +1,23 @@
|
||||
{
|
||||
"cameraData": {
|
||||
"ISO": 100,
|
||||
"exposure": 0.003125,
|
||||
"fStop": 6.3,
|
||||
"focalLength": 16,
|
||||
"lens": "NIKKOR Z DX 16-50mm f/3.5-6.3 VR",
|
||||
"make": "NIKON CORPORATION",
|
||||
"model": "NIKON Z 30"
|
||||
},
|
||||
"creationDate": 1705832640930,
|
||||
"creationDateOffset": "+01:00",
|
||||
"fileSize": 25556,
|
||||
"size": {
|
||||
"height": 10,
|
||||
"width": 15
|
||||
},
|
||||
"keywords": [
|
||||
"Výlet",
|
||||
"Travel"
|
||||
],
|
||||
"rating": 3
|
||||
}
|
15
test/backend/assets/sidecar/20240128_105420.json
Normal file
15
test/backend/assets/sidecar/20240128_105420.json
Normal file
@ -0,0 +1,15 @@
|
||||
{
|
||||
"bitRate": 184871,
|
||||
"creationDate": 1706435660000,
|
||||
"duration": 1000,
|
||||
"fileSize": 23132,
|
||||
"size": {
|
||||
"height": 46,
|
||||
"width": 80
|
||||
},
|
||||
"fps": 60000,
|
||||
"keywords": [
|
||||
"Travel"
|
||||
],
|
||||
"rating": 3
|
||||
}
|
BIN
test/backend/assets/sidecar/20240128_105420.mp4
Normal file
BIN
test/backend/assets/sidecar/20240128_105420.mp4
Normal file
Binary file not shown.
115
test/backend/assets/sidecar/20240128_105420.mp4.xmp
Normal file
115
test/backend/assets/sidecar/20240128_105420.mp4.xmp
Normal file
@ -0,0 +1,115 @@
|
||||
<?xpacket begin="" id="W5M0MpCehiHzreSzNTczkc9d"?>
|
||||
<x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="XMP Core 4.4.0-Exiv2">
|
||||
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
|
||||
<rdf:Description rdf:about=""
|
||||
xmlns:video="http://www.video/"
|
||||
xmlns:xmpDM="http://ns.adobe.com/xmp/1.0/DynamicMedia/"
|
||||
xmlns:audio="http://www.audio/"
|
||||
xmlns:exif="http://ns.adobe.com/exif/1.0/"
|
||||
xmlns:tiff="http://ns.adobe.com/tiff/1.0/"
|
||||
xmlns:xap="http://ns.adobe.com/xap/1.0/"
|
||||
xmlns:Iptc4xmpExt="http://iptc.org/std/Iptc4xmpExt/2008-02-29/"
|
||||
xmlns:LImage="http://ns.leiainc.com/photos/1.0/image/"
|
||||
xmlns:digiKam="http://www.digikam.org/ns/1.0/"
|
||||
xmlns:photoshop="http://ns.adobe.com/photoshop/1.0/"
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
video:duration="1001"
|
||||
video:MaxBitRate="184871"
|
||||
video:StreamCount="3"
|
||||
video:Codec="h264"
|
||||
video:CodecDescription="H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10"
|
||||
video:Format="yuv420p"
|
||||
video:ColorMode="bt709"
|
||||
video:ColorSpace="CCIR-709"
|
||||
video:Width="80"
|
||||
video:FrameWidth="80"
|
||||
video:SourceImageWidth="80"
|
||||
video:Height="46"
|
||||
video:FrameHeight="46"
|
||||
video:SourceImageHeight="46"
|
||||
video:FrameSize="w:80, h:46, unit:pixels"
|
||||
video:AspectRatio="80/46"
|
||||
video:FrameRate="59.9401"
|
||||
video:BitDepth="24"
|
||||
video:Language="eng"
|
||||
video:TrackCreateDate="3789284060"
|
||||
video:HandlerDescription="VideoHandler"
|
||||
video:MajorBrand="qt "
|
||||
video:CompatibleBrands="qt niko"
|
||||
video:MinorVersion="538315008"
|
||||
video:Encoder="Lavf60.16.100"
|
||||
video:DateTimeOriginal="2024-01-28T09:54:20"
|
||||
video:DateUTC="2024-01-28T09:54:20"
|
||||
video:ModificationDate="2024-01-28T09:54:20"
|
||||
video:DateTimeDigitized="2024-01-28T09:54:20"
|
||||
video:FileName="DSC_1928_20240128_105420_80p.mp4"
|
||||
video:FileSize="0"
|
||||
video:FileType="mp4"
|
||||
video:MimeType="video/mp4"
|
||||
xmpDM:duration="1001"
|
||||
xmpDM:videoColorSpace="CCIR-709"
|
||||
xmpDM:FieldOrder="Progressive"
|
||||
xmpDM:videoFrameSize="w:80, h:46, unit:pixels"
|
||||
xmpDM:videoPixelAspectRatio="80/46"
|
||||
xmpDM:videoFrameRate="59.94"
|
||||
xmpDM:videoPixelDepth="8Int"
|
||||
xmpDM:shotDate="ne led 28 10:54:20 2024"
|
||||
xmpDM:audioSampleRate="48000"
|
||||
xmpDM:audioChannelType="Stereo"
|
||||
xmpDM:audioSampleType="32Float"
|
||||
audio:Codec="aac"
|
||||
audio:CodecDescription="AAC (Advanced Audio Coding)"
|
||||
audio:SampleRate="48000"
|
||||
audio:ChannelType="Stereo"
|
||||
audio:Format="fltp"
|
||||
audio:SampleType="32Float"
|
||||
audio:TrackLang="eng"
|
||||
audio:TrackCreateDate="3789284060"
|
||||
audio:HandlerDescription="SoundHandler"
|
||||
exif:DateTimeOriginal="2024-01-28T09:54:20"
|
||||
exif:DateTimeDigitized="2024-01-28T09:54:20"
|
||||
exif:ExifVersion="0232"
|
||||
exif:FlashpixVersion="0100"
|
||||
exif:ColorSpace="65535"
|
||||
exif:PixelXDimension="80"
|
||||
exif:PixelYDimension="46"
|
||||
exif:GPSVersionID="2.0.0.0"
|
||||
exif:GPSLatitude="50,5.3752490N"
|
||||
exif:GPSLongitude="14,23.8445710E"
|
||||
exif:GPSMapDatum="WGS-84"
|
||||
tiff:DateTime="2024-01-28T09:54:20"
|
||||
tiff:ImageWidth="80"
|
||||
tiff:ImageLength="46"
|
||||
tiff:YCbCrPositioning="1"
|
||||
tiff:XResolution="72/1"
|
||||
tiff:YResolution="72/1"
|
||||
tiff:ResolutionUnit="2"
|
||||
xap:MetadataDate="2024-01-28T09:54:20"
|
||||
xap:Rating="3"
|
||||
xap:ModifyDate="2024-01-28T09:54:20"
|
||||
xap:CreateDate="2024-01-28T09:54:20"
|
||||
Iptc4xmpExt:audioBitsPerSample="16"
|
||||
LImage:MinorVersion="538315008"
|
||||
photoshop:DateCreated="2024-01-28T09:54:20">
|
||||
<exif:ComponentsConfiguration>
|
||||
<rdf:Seq>
|
||||
<rdf:li>1</rdf:li>
|
||||
<rdf:li>2</rdf:li>
|
||||
<rdf:li>3</rdf:li>
|
||||
<rdf:li>0</rdf:li>
|
||||
</rdf:Seq>
|
||||
</exif:ComponentsConfiguration>
|
||||
<digiKam:TagsList>
|
||||
<rdf:Seq>
|
||||
<rdf:li>Travel</rdf:li>
|
||||
</rdf:Seq>
|
||||
</digiKam:TagsList>
|
||||
<dc:subject>
|
||||
<rdf:Bag>
|
||||
<rdf:li>Travel</rdf:li>
|
||||
</rdf:Bag>
|
||||
</dc:subject>
|
||||
</rdf:Description>
|
||||
</rdf:RDF>
|
||||
</x:xmpmeta>
|
||||
<?xpacket end="w"?>
|
15
test/backend/assets/sidecar/20240128_120909.json
Normal file
15
test/backend/assets/sidecar/20240128_120909.json
Normal file
@ -0,0 +1,15 @@
|
||||
{
|
||||
"bitRate": 183168,
|
||||
"creationDate": 1706440145000,
|
||||
"duration": 1000,
|
||||
"fileSize": 22896,
|
||||
"size": {
|
||||
"height": 80,
|
||||
"width": 46
|
||||
},
|
||||
"fps": 30,
|
||||
"keywords": [
|
||||
"Travel"
|
||||
],
|
||||
"rating": 3
|
||||
}
|
BIN
test/backend/assets/sidecar/20240128_120909.mp4
Normal file
BIN
test/backend/assets/sidecar/20240128_120909.mp4
Normal file
Binary file not shown.
117
test/backend/assets/sidecar/20240128_120909.mp4.xmp
Normal file
117
test/backend/assets/sidecar/20240128_120909.mp4.xmp
Normal file
@ -0,0 +1,117 @@
|
||||
<?xpacket begin="" id="W5M0MpCehiHzreSzNTczkc9d"?>
|
||||
<x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="XMP Core 4.4.0-Exiv2">
|
||||
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
|
||||
<rdf:Description rdf:about=""
|
||||
xmlns:video="http://www.video/"
|
||||
xmlns:xmpDM="http://ns.adobe.com/xmp/1.0/DynamicMedia/"
|
||||
xmlns:audio="http://www.audio/"
|
||||
xmlns:exif="http://ns.adobe.com/exif/1.0/"
|
||||
xmlns:tiff="http://ns.adobe.com/tiff/1.0/"
|
||||
xmlns:xap="http://ns.adobe.com/xap/1.0/"
|
||||
xmlns:Iptc4xmpExt="http://iptc.org/std/Iptc4xmpExt/2008-02-29/"
|
||||
xmlns:LImage="http://ns.leiainc.com/photos/1.0/image/"
|
||||
xmlns:digiKam="http://www.digikam.org/ns/1.0/"
|
||||
xmlns:photoshop="http://ns.adobe.com/photoshop/1.0/"
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
video:duration="1000"
|
||||
video:MaxBitRate="183168"
|
||||
video:StreamCount="2"
|
||||
video:Codec="h264"
|
||||
video:CodecDescription="H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10"
|
||||
video:Format="yuv420p"
|
||||
video:ColorMode="bt709"
|
||||
video:ColorSpace="CCIR-709"
|
||||
video:Width="80"
|
||||
video:FrameWidth="80"
|
||||
video:SourceImageWidth="80"
|
||||
video:Height="46"
|
||||
video:FrameHeight="46"
|
||||
video:SourceImageHeight="46"
|
||||
video:FrameSize="w:80, h:46, unit:pixels"
|
||||
video:AspectRatio="16/9"
|
||||
video:FrameRate="30"
|
||||
video:BitDepth="24"
|
||||
video:Orientation="6"
|
||||
video:Language="und"
|
||||
video:TrackCreateDate="3789288545"
|
||||
video:HandlerDescription="VideoHandler"
|
||||
video:MajorBrand="mp42"
|
||||
video:CompatibleBrands="mp42mp41isomiso2"
|
||||
video:MinorVersion="0"
|
||||
video:Encoder="Lavf60.16.100"
|
||||
video:DateTimeOriginal="2024-01-28T11:09:05"
|
||||
video:DateUTC="2024-01-28T11:09:05"
|
||||
video:ModificationDate="2024-01-28T11:09:05"
|
||||
video:DateTimeDigitized="2024-01-28T11:09:05"
|
||||
video:FileName="20240128_120909_80p.mp4"
|
||||
video:FileSize="0"
|
||||
video:FileType="mp4"
|
||||
video:MimeType="video/mp4"
|
||||
xmpDM:duration="1000"
|
||||
xmpDM:videoColorSpace="CCIR-709"
|
||||
xmpDM:FieldOrder="Progressive"
|
||||
xmpDM:videoFrameSize="w:80, h:46, unit:pixels"
|
||||
xmpDM:videoPixelAspectRatio="16/9"
|
||||
xmpDM:videoFrameRate="30"
|
||||
xmpDM:videoPixelDepth="8Int"
|
||||
xmpDM:shotDate="ne led 28 12:09:05 2024"
|
||||
xmpDM:audioSampleRate="48000"
|
||||
xmpDM:audioChannelType="Stereo"
|
||||
xmpDM:audioSampleType="32Float"
|
||||
audio:Codec="aac"
|
||||
audio:CodecDescription="AAC (Advanced Audio Coding)"
|
||||
audio:SampleRate="48000"
|
||||
audio:ChannelType="Stereo"
|
||||
audio:Format="fltp"
|
||||
audio:SampleType="32Float"
|
||||
audio:TrackLang="und"
|
||||
audio:TrackCreateDate="3789288545"
|
||||
audio:HandlerDescription="SoundHandler"
|
||||
exif:DateTimeOriginal="2024-01-28T11:09:05"
|
||||
exif:DateTimeDigitized="2024-01-28T11:09:05"
|
||||
exif:ExifVersion="0232"
|
||||
exif:FlashpixVersion="0100"
|
||||
exif:ColorSpace="65535"
|
||||
exif:PixelXDimension="80"
|
||||
exif:PixelYDimension="46"
|
||||
exif:GPSVersionID="2.0.0.0"
|
||||
exif:GPSLatitude="50,5.3752490N"
|
||||
exif:GPSLongitude="14,23.8445710E"
|
||||
exif:GPSMapDatum="WGS-84"
|
||||
tiff:DateTime="2024-01-28T11:09:05"
|
||||
tiff:ImageWidth="81"
|
||||
tiff:ImageLength="46"
|
||||
tiff:Orientation="6"
|
||||
tiff:YCbCrPositioning="1"
|
||||
tiff:XResolution="72/1"
|
||||
tiff:YResolution="72/1"
|
||||
tiff:ResolutionUnit="2"
|
||||
xap:MetadataDate="2024-01-28T11:09:05"
|
||||
xap:Rating="3"
|
||||
xap:ModifyDate="2024-01-28T11:09:05"
|
||||
xap:CreateDate="2024-01-28T11:09:05"
|
||||
Iptc4xmpExt:audioBitsPerSample="16"
|
||||
LImage:MinorVersion="0"
|
||||
photoshop:DateCreated="2024-01-28T11:09:05">
|
||||
<exif:ComponentsConfiguration>
|
||||
<rdf:Seq>
|
||||
<rdf:li>1</rdf:li>
|
||||
<rdf:li>2</rdf:li>
|
||||
<rdf:li>3</rdf:li>
|
||||
<rdf:li>0</rdf:li>
|
||||
</rdf:Seq>
|
||||
</exif:ComponentsConfiguration>
|
||||
<digiKam:TagsList>
|
||||
<rdf:Seq>
|
||||
<rdf:li>Travel</rdf:li>
|
||||
</rdf:Seq>
|
||||
</digiKam:TagsList>
|
||||
<dc:subject>
|
||||
<rdf:Bag>
|
||||
<rdf:li>Travel</rdf:li>
|
||||
</rdf:Bag>
|
||||
</dc:subject>
|
||||
</rdf:Description>
|
||||
</rdf:RDF>
|
||||
</x:xmpmeta>
|
||||
<?xpacket end="w"?>
|
BIN
test/backend/assets/sidecar/20240128_185808.JPG
Normal file
BIN
test/backend/assets/sidecar/20240128_185808.JPG
Normal file
Binary file not shown.
After Width: | Height: | Size: 46 KiB |
114
test/backend/assets/sidecar/20240128_185808.JPG.xmp
Normal file
114
test/backend/assets/sidecar/20240128_185808.JPG.xmp
Normal file
@ -0,0 +1,114 @@
|
||||
<?xpacket begin="" id="W5M0MpCehiHzreSzNTczkc9d"?>
|
||||
<x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="XMP Core 4.4.0-Exiv2">
|
||||
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
|
||||
<rdf:Description rdf:about=""
|
||||
xmlns:xmp="http://ns.adobe.com/xap/1.0/"
|
||||
xmlns:exif="http://ns.adobe.com/exif/1.0/"
|
||||
xmlns:acdsee="http://ns.acdsee.com/iptc/1.0/"
|
||||
xmlns:MicrosoftPhoto="http://ns.microsoft.com/photo/1.0/"
|
||||
xmlns:digiKam="http://www.digikam.org/ns/1.0/"
|
||||
xmlns:lr="http://ns.adobe.com/lightroom/1.0/"
|
||||
xmlns:mediapro="http://ns.iview-multimedia.com/mediapro/1.0/"
|
||||
xmlns:tiff="http://ns.adobe.com/tiff/1.0/"
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:photoshop="http://ns.adobe.com/photoshop/1.0/"
|
||||
xmp:CreatorTool="NIKON Z 30 Ver.01.00 "
|
||||
xmp:Rating="3"
|
||||
xmp:ModifyDate="2024-01-28T18:58:08.66"
|
||||
xmp:CreateDate="2024-01-28T18:58:08.66"
|
||||
exif:GPSLatitude="50,5.3752490N"
|
||||
exif:GPSLongitude="14,23.8445710E"
|
||||
exif:GPSMapDatum="WGS-84"
|
||||
exif:ExifVersion="0231"
|
||||
exif:FlashpixVersion="0100"
|
||||
exif:CompressedBitsPerPixel="4/1"
|
||||
exif:PixelXDimension="15"
|
||||
exif:PixelYDimension="10"
|
||||
exif:ExposureTime="10/250"
|
||||
exif:FNumber="420/100"
|
||||
exif:ExposureProgram="2"
|
||||
exif:ExposureBiasValue="0/6"
|
||||
exif:MeteringMode="5"
|
||||
exif:LightSource="0"
|
||||
exif:FocalLength="250/10"
|
||||
exif:SensingMethod="2"
|
||||
exif:FileSource="3"
|
||||
exif:SceneType="1"
|
||||
exif:CFAPattern="2 0 2 0 0 1 1 2"
|
||||
exif:CustomRendered="1"
|
||||
exif:ExposureMode="0"
|
||||
exif:WhiteBalance="0"
|
||||
exif:FocalLengthIn35mmFilm="37"
|
||||
exif:SceneCaptureType="0"
|
||||
exif:GainControl="2"
|
||||
exif:Contrast="0"
|
||||
exif:Saturation="0"
|
||||
exif:Sharpness="0"
|
||||
exif:SubjectDistanceRange="0"
|
||||
exif:GPSVersionID="2.3.0.0"
|
||||
acdsee:categories="<Categories><Category Assigned="1">Travel</Category></Categories>"
|
||||
acdsee:rating="3"
|
||||
MicrosoftPhoto:Rating="50"
|
||||
tiff:ImageWidth="15"
|
||||
tiff:ImageLength="10"
|
||||
tiff:Orientation="1"
|
||||
tiff:YCbCrPositioning="2"
|
||||
tiff:XResolution="300/1"
|
||||
tiff:YResolution="300/1"
|
||||
tiff:ResolutionUnit="2"
|
||||
tiff:Make="NIKON CORPORATION"
|
||||
tiff:Model="NIKON Z 30"
|
||||
tiff:Software="digiKam-8.2.0"
|
||||
photoshop:DateCreated="2024-01-28T18:58:08.66"
|
||||
photoshop:Urgency="4">
|
||||
<exif:Flash
|
||||
exif:Fired="False"
|
||||
exif:Return="0"
|
||||
exif:Mode="0"
|
||||
exif:Function="False"
|
||||
exif:RedEyeMode="False"/>
|
||||
<exif:ComponentsConfiguration>
|
||||
<rdf:Seq>
|
||||
<rdf:li>1</rdf:li>
|
||||
<rdf:li>2</rdf:li>
|
||||
<rdf:li>3</rdf:li>
|
||||
<rdf:li>0</rdf:li>
|
||||
</rdf:Seq>
|
||||
</exif:ComponentsConfiguration>
|
||||
<exif:ISOSpeedRatings>
|
||||
<rdf:Seq>
|
||||
<rdf:li>25600</rdf:li>
|
||||
</rdf:Seq>
|
||||
</exif:ISOSpeedRatings>
|
||||
<MicrosoftPhoto:LastKeywordXMP>
|
||||
<rdf:Bag>
|
||||
<rdf:li>Travel</rdf:li>
|
||||
</rdf:Bag>
|
||||
</MicrosoftPhoto:LastKeywordXMP>
|
||||
<digiKam:TagsList>
|
||||
<rdf:Seq>
|
||||
<rdf:li>Travel</rdf:li>
|
||||
</rdf:Seq>
|
||||
</digiKam:TagsList>
|
||||
<lr:hierarchicalSubject>
|
||||
<rdf:Bag>
|
||||
<rdf:li>Travel</rdf:li>
|
||||
</rdf:Bag>
|
||||
</lr:hierarchicalSubject>
|
||||
<mediapro:CatalogSets>
|
||||
<rdf:Bag>
|
||||
<rdf:li>Travel</rdf:li>
|
||||
</rdf:Bag>
|
||||
</mediapro:CatalogSets>
|
||||
<dc:creator>
|
||||
<rdf:Seq/>
|
||||
</dc:creator>
|
||||
<dc:subject>
|
||||
<rdf:Bag>
|
||||
<rdf:li>Travel</rdf:li>
|
||||
</rdf:Bag>
|
||||
</dc:subject>
|
||||
</rdf:Description>
|
||||
</rdf:RDF>
|
||||
</x:xmpmeta>
|
||||
<?xpacket end="w"?>
|
23
test/backend/assets/sidecar/20240128_185808.json
Normal file
23
test/backend/assets/sidecar/20240128_185808.json
Normal file
@ -0,0 +1,23 @@
|
||||
{
|
||||
"cameraData": {
|
||||
"ISO": 25600,
|
||||
"exposure": 0.04,
|
||||
"fStop": 4.2,
|
||||
"focalLength": 25,
|
||||
"lens": "NIKKOR Z DX 16-50mm f/3.5-6.3 VR",
|
||||
"make": "NIKON CORPORATION",
|
||||
"model": "NIKON Z 30"
|
||||
},
|
||||
"creationDate": 1706468288660,
|
||||
"creationDateOffset": "+01:00",
|
||||
"fileSize": 47059,
|
||||
"size": {
|
||||
"height": 10,
|
||||
"width": 15
|
||||
},
|
||||
"keywords": [
|
||||
"Výlet",
|
||||
"Travel"
|
||||
],
|
||||
"rating": 3
|
||||
}
|
@ -5,7 +5,7 @@
|
||||
},
|
||||
"bitRate": 1794127,
|
||||
"duration": 290,
|
||||
"creationDate": 1709052692000,
|
||||
"creationDate": 1542482851000,
|
||||
"fileSize": 65073,
|
||||
"fps": 40000,
|
||||
"keywords": [
|
16
test/backend/assets/sidecar/bunny_1sec_v2.json
Normal file
16
test/backend/assets/sidecar/bunny_1sec_v2.json
Normal file
@ -0,0 +1,16 @@
|
||||
{
|
||||
"size": {
|
||||
"width": 640,
|
||||
"height": 360
|
||||
},
|
||||
"bitRate": 1794127,
|
||||
"duration": 290,
|
||||
"creationDate": 1542482851000,
|
||||
"fileSize": 65073,
|
||||
"fps": 40000,
|
||||
"keywords": [
|
||||
"rabbit",
|
||||
"test"
|
||||
],
|
||||
"rating": 4
|
||||
}
|
@ -5,7 +5,7 @@
|
||||
},
|
||||
"bitRate": 1794127,
|
||||
"duration": 290,
|
||||
"creationDate": 1709052692000,
|
||||
"creationDate": 1542482851000,
|
||||
"fileSize": 65073,
|
||||
"fps": 40000,
|
||||
"keywords": [
|
@ -5,6 +5,7 @@ import {Utils} from '../../../../../src/common/Utils';
|
||||
import * as path from 'path';
|
||||
import * as fs from 'fs';
|
||||
import {PhotoProcessing} from '../../../../../src/backend/model/fileaccess/fileprocessing/PhotoProcessing';
|
||||
import {VideoProcessing} from '../../../../../src/backend/model/fileaccess/fileprocessing/VideoProcessing';
|
||||
import {Config} from '../../../../../src/common/config/private/Config';
|
||||
import {DatabaseType} from '../../../../../src/common/config/private/PrivateConfig';
|
||||
|
||||
@ -101,61 +102,61 @@ describe('MetadataLoader', () => {
|
||||
});
|
||||
it('should load sidecar file with file extension for video', async () => {
|
||||
const data = await MetadataLoader.loadVideoMetadata(path.join(__dirname, '/../../../assets/sidecar/bunny_1sec.mp4'));
|
||||
const expected = require(path.join(__dirname, '/../../../assets/sidecar/bunny_1sec.mp4.json'));
|
||||
const expected = require(path.join(__dirname, '/../../../assets/sidecar/bunny_1sec.json'));
|
||||
expect(Utils.clone(data)).to.be.deep.equal(expected);
|
||||
});
|
||||
|
||||
it('should load sidecar file without file extension for video', async () => {
|
||||
const data = await MetadataLoader.loadVideoMetadata(path.join(__dirname, '/../../../assets/sidecar/bunny_1sec_v2.mp4'));
|
||||
const expected = require(path.join(__dirname, '/../../../assets/sidecar/bunny_1sec.mp4.json'));//sidecar "bunny_1sec_v2.xmp" is identical to "bunny_1sec.mp4.xmp" so we expect the same result
|
||||
const expected = require(path.join(__dirname, '/../../../assets/sidecar/bunny_1sec.json'));//sidecar "bunny_1sec_v2.xmp" is identical to "bunny_1sec.mp4.xmp" so we expect the same result
|
||||
expect(Utils.clone(data)).to.be.deep.equal(expected);
|
||||
});
|
||||
|
||||
it('should retrieve both keywords from sidecar file for video', async () => {
|
||||
const data = await MetadataLoader.loadVideoMetadata(path.join(__dirname, '/../../../assets/sidecar/bunny_1sec.mp4'));
|
||||
const expected = require(path.join(__dirname, '/../../../assets/sidecar/bunny_1sec.mp4.json'));
|
||||
const expected = require(path.join(__dirname, '/../../../assets/sidecar/bunny_1sec.json'));
|
||||
expect(Utils.clone(data)).to.be.deep.equal(expected);
|
||||
});
|
||||
|
||||
it('should retrieve one keyword from sidecar file for video', async () => {
|
||||
const data = await MetadataLoader.loadVideoMetadata(path.join(__dirname, '/../../../assets/sidecar/bunny_1sec_v3.mp4'));
|
||||
const expected = require(path.join(__dirname, '/../../../assets/sidecar/bunny_1sec_v3.mp4.json'));
|
||||
const expected = require(path.join(__dirname, '/../../../assets/sidecar/bunny_1sec_v3.json'));
|
||||
expect(Utils.clone(data)).to.be.deep.equal(expected);
|
||||
});
|
||||
|
||||
it('should load sidecar file with file extension for photo', async () => {
|
||||
const data = await MetadataLoader.loadPhotoMetadata(path.join(__dirname, '/../../../assets/sidecar/no_metadata.jpg'));
|
||||
const expected = require(path.join(__dirname, '/../../../assets/sidecar/no_metadata.jpg.json'));
|
||||
const expected = require(path.join(__dirname, '/../../../assets/sidecar/no_metadata.json'));
|
||||
expect(Utils.clone(data)).to.be.deep.equal(expected);
|
||||
});
|
||||
|
||||
it('should load sidecar file without file extension for photo', async () => {
|
||||
const data = await MetadataLoader.loadPhotoMetadata(path.join(__dirname, '/../../../assets/sidecar/no_metadata_v2.jpg'));
|
||||
const expected = require(path.join(__dirname, '/../../../assets/sidecar/no_metadata_v2.jpg.json'));
|
||||
const expected = require(path.join(__dirname, '/../../../assets/sidecar/no_metadata_v2.json'));
|
||||
expect(Utils.clone(data)).to.be.deep.equal(expected);
|
||||
});
|
||||
|
||||
it('should retrieve both keywords from sidecar file for photo', async () => {
|
||||
const data = await MetadataLoader.loadPhotoMetadata(path.join(__dirname, '/../../../assets/sidecar/no_metadata.jpg'));
|
||||
const expected = require(path.join(__dirname, '/../../../assets/sidecar/no_metadata.jpg.json'));
|
||||
const expected = require(path.join(__dirname, '/../../../assets/sidecar/no_metadata.json'));
|
||||
expect(Utils.clone(data)).to.be.deep.equal(expected);
|
||||
});
|
||||
|
||||
it('should retrieve one keyword from sidecar file for photo', async () => {
|
||||
const data = await MetadataLoader.loadPhotoMetadata(path.join(__dirname, '/../../../assets/sidecar/no_metadata_v3.jpg'));
|
||||
const expected = require(path.join(__dirname, '/../../../assets/sidecar/no_metadata_v3.jpg.json'));
|
||||
const expected = require(path.join(__dirname, '/../../../assets/sidecar/no_metadata_v3.json'));
|
||||
expect(Utils.clone(data)).to.be.deep.equal(expected);
|
||||
});
|
||||
|
||||
it('should read keywords from photo without sidecar file', async () => {
|
||||
const data = await MetadataLoader.loadPhotoMetadata(path.join(__dirname, '/../../../assets/sidecar/metadata.jpg'));
|
||||
const expected = require(path.join(__dirname, '/../../../assets/sidecar/metadata.jpg.json'));
|
||||
const expected = require(path.join(__dirname, '/../../../assets/sidecar/metadata.json'));
|
||||
expect(Utils.clone(data)).to.be.deep.equal(expected);
|
||||
});
|
||||
|
||||
it('should merge keywords from photo with keywords from sidecar', async () => {
|
||||
const data = await MetadataLoader.loadPhotoMetadata(path.join(__dirname, '/../../../assets/sidecar/metadata_v2.jpg'));
|
||||
const expected = require(path.join(__dirname, '/../../../assets/sidecar/metadata_v2.jpg.json')); //"metadata_v2.jpg" is identical to "metadata.jpg" and "metadata_v2.xmp" contains 2 different keywords
|
||||
const expected = require(path.join(__dirname, '/../../../assets/sidecar/metadata_v2.json')); //"metadata_v2.jpg" is identical to "metadata.jpg" and "metadata_v2.xmp" contains 2 different keywords
|
||||
expect(Utils.clone(data)).to.be.deep.equal(expected);
|
||||
});
|
||||
|
||||
@ -257,4 +258,24 @@ describe('MetadataLoader', () => {
|
||||
expect(Utils.clone(data)).to.be.deep.equal(expected);
|
||||
});
|
||||
|
||||
describe('should load metadata from sidecar files', () => {
|
||||
const root = path.join(__dirname, '/../../../assets/sidecar');
|
||||
const files = fs.readdirSync(root);
|
||||
for (const item of files) {
|
||||
const fullFilePath = path.join(root, item);
|
||||
if (PhotoProcessing.isPhoto(fullFilePath)) {
|
||||
it(item, async () => {
|
||||
const data = await MetadataLoader.loadPhotoMetadata(fullFilePath);
|
||||
const expected = require(fullFilePath.split('.').slice(0, -1).join('.') + '.json');
|
||||
expect(Utils.clone(data)).to.be.deep.equal(expected);
|
||||
});
|
||||
} else if (VideoProcessing.isVideo(fullFilePath)) {
|
||||
it(item, async () => {
|
||||
const data = await MetadataLoader.loadVideoMetadata(fullFilePath);
|
||||
const expected = require(fullFilePath.split('.').slice(0, -1).join('.') + '.json');
|
||||
expect(Utils.clone(data)).to.be.deep.equal(expected);
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user