Skip to content
Snippets Groups Projects
Verified Commit fa13e4b3 authored by Janne Mareike Koschinski's avatar Janne Mareike Koschinski
Browse files

Implement improved EXIF support

parent 55dfe092
No related branches found
No related tags found
1 merge request!1Replace entire project structure
Pipeline #2580 failed
img {
max-width: 200px;
max-height: 200px;
}
...@@ -7,9 +7,17 @@ export interface Image { ...@@ -7,9 +7,17 @@ export interface Image {
updated_at: string, updated_at: string,
original_name: string, original_name: string,
mime_type: string, mime_type: string,
state: string, state: ImageState,
metadata: { metadata: {
[key: string]: string [key: string]: string
}, },
url: string, url: string,
} }
export enum ImageState {
CREATED = "created",
QUEUED = "queued",
IN_PROGRESS = "in_progress",
DONE = "done",
ERROR = "error"
}
...@@ -9,14 +9,12 @@ export default function ImageList() { ...@@ -9,14 +9,12 @@ export default function ImageList() {
<div> <div>
<p>{status}</p> <p>{status}</p>
<p>{error as string}</p> <p>{error as string}</p>
<ul>
{data?.map(image => ( {data?.map(image => (
<ImageView <ImageView
key={image.id} key={image.id}
image={image} image={image}
/> />
))} ))}
</ul>
</div> </div>
); );
} }
import React, {Fragment} from "react";
import {ImageMetadata, ratioToTime} from "../metadata/ImageMetadata";
import {ratioToFloat} from "../metadata/Ratio";
import {ExposureMode} from "../metadata/ExposureMode";
import {ExposureProgram} from "../metadata/ExposureProgram";
import {LightSource} from "../metadata/LightSource";
import {MeteringMode} from "../metadata/MeteringMode";
import {WhiteBalance} from "../metadata/WhiteBalance";
import {SceneMode} from "../metadata/SceneMode";
import {ContrastProcessing} from "../metadata/ContrastProcessing";
import {SharpnessProcessing} from "../metadata/SharpnessProcessing";
import {SubjectDistanceRange} from "../metadata/SubjectDistanceRange";
import {BrightnessMedium, Camera, Copyright, Event, Exposure, PhotoCamera, ZoomIn} from "@material-ui/icons";
import {AngleAcute, ArrowExpandHorizontal, Blur, CameraTimer, Flash, WhiteBalanceIncandescent} from "mdi-material-ui";
import {FlashMode} from "../metadata/FlashMode";
import {ListItem, ListItemIcon, ListItemText} from "@material-ui/core";
export interface ImageMetadataViewProps {
metadata: ImageMetadata
}
export default function ImageMetadataView({metadata}: ImageMetadataViewProps) {
return (
<Fragment>
{metadata.make !== undefined && (
<ListItem dense>
<ListItemIcon><PhotoCamera/></ListItemIcon>
<ListItemText primary="Make" secondary={metadata.make}/>
</ListItem>
)}
{metadata.model !== undefined && (
<ListItem dense>
<ListItemText inset primary="Model" secondary={metadata.model}/>
</ListItem>
)}
{metadata.software !== undefined && (
<ListItem dense>
<ListItemText inset primary="Software" secondary={metadata.software}/>
</ListItem>
)}
{metadata.copyright !== undefined && (
<ListItem dense>
<ListItemIcon><Copyright/></ListItemIcon>
<ListItemText primary="Copyright" secondary={metadata.copyright}/>
</ListItem>
)}
{metadata.dateTimeCreated !== undefined && (
<ListItem dense>
<ListItemIcon><Event/></ListItemIcon>
<ListItemText primary="Created At" secondary={metadata.dateTimeCreated.toISOString()}/>
</ListItem>
)}
{metadata.dateTimeDigitized !== undefined && (
<ListItem dense>
<ListItemText inset primary="Digitized At"
secondary={metadata.dateTimeDigitized.toISOString()}/>
</ListItem>
)}
{metadata.dateTimeOriginal !== undefined && (
<ListItem dense>
<ListItemText inset primary="Shot At" secondary={metadata.dateTimeOriginal.toISOString()}/>
</ListItem>
)}
{metadata.digitalZoomRatio !== undefined && (
<ListItem dense>
<ListItemIcon><ZoomIn/></ListItemIcon>
<ListItemText primary="Zoom" secondary={`${ratioToFloat(metadata.digitalZoomRatio)}x`}/>
</ListItem>
)}
{metadata.focalLength !== undefined && (
<ListItem dense>
<ListItemIcon><AngleAcute/></ListItemIcon>
<ListItemText primary="Focal Length" secondary={`${ratioToFloat(metadata.focalLength)}mm`}/>
</ListItem>
)}
{metadata.focalLength35mm !== undefined && (
<ListItem dense>
<ListItemText inset primary="35mm equivalent"
secondary={`${ratioToFloat(metadata.focalLength35mm)}mm`}/>
</ListItem>
)}
{metadata.shutterSpeed !== undefined && (
<ListItem dense>
<ListItemIcon><CameraTimer/></ListItemIcon>
<ListItemText primary="Shutter Speed" secondary={ratioToTime(metadata.shutterSpeed)}/>
</ListItem>
)}
{metadata.aperture !== undefined && (
<ListItem dense>
<ListItemIcon><Camera/></ListItemIcon>
<ListItemText primary="Aperture" secondary={ratioToFloat(metadata.aperture)}/>
</ListItem>
)}
{metadata.isoSpeedRating !== undefined && (
<ListItem dense>
<ListItemIcon>ISO</ListItemIcon>
<ListItemText primary="ISO" secondary={metadata.isoSpeedRating}/>
</ListItem>
)}
{metadata.exposure !== undefined && (
<ListItem dense>
<ListItemIcon><Exposure/></ListItemIcon>
<ListItemText primary="Exposure" secondary={ratioToFloat(metadata.exposure)}/>
</ListItem>
)}
{metadata.exposureMode !== undefined && (
<ListItem dense>
<ListItemText inset primary="Mode" secondary={ExposureMode[metadata.exposureMode]}/>
</ListItem>
)}
{metadata.exposureProgram !== undefined && (
<ListItem dense>
<ListItemText inset primary="Program" secondary={ExposureProgram[metadata.exposureProgram]}/>
</ListItem>
)}
{metadata.meteringMode !== undefined && (
<ListItem dense>
<ListItemText inset primary="Metering mode" secondary={MeteringMode[metadata.meteringMode]}/>
</ListItem>
)}
{metadata.flash !== undefined && (
<Fragment>
<ListItem dense>
<ListItemIcon><Flash/></ListItemIcon>
<ListItemText primary="Flash"
secondary={metadata.flash.available ? "Available" : "Unavailable"}/>
</ListItem>
<ListItem dense>
<ListItemText inset primary="Fired" secondary={metadata.flash.fired ? "Yes" : "No"}/>
</ListItem>
<ListItem dense>
<ListItemText inset primary="Red Eye Reduction"
secondary={metadata.flash.redEyeReduction ? "Yes" : "No"}/>
</ListItem>
{metadata.flash.mode !== undefined && (
<ListItem dense>
<ListItemText inset primary="Mode" secondary={FlashMode[metadata.flash.mode]}/>
</ListItem>
)}
{metadata.flash.strength !== undefined && (
<ListItem dense>
<ListItemText inset primary="Strength" secondary={`${metadata.flash.strength} BCPS`}/>
</ListItem>
)}
<ListItem dense>
<ListItemText inset primary="Strobe Detection"
secondary={!metadata.flash.strobeDetection.available ? "Unvailable" :
metadata.flash.strobeDetection.detected ? "Strobe detected" :
"No strobe detected"}
/>
</ListItem>
</Fragment>
)}
{metadata.lightSource !== undefined && (
<ListItem dense>
<ListItemIcon><WhiteBalanceIncandescent/></ListItemIcon>
<ListItemText primary="Light Source" secondary={LightSource[metadata.lightSource]}/>
</ListItem>
)}
{metadata.whiteBalance !== undefined && (
<ListItem dense>
<ListItemIcon><WhiteBalanceIncandescent/></ListItemIcon>
<ListItemText primary="White balance" secondary={WhiteBalance[metadata.whiteBalance]}/>
</ListItem>
)}
{metadata.subjectDistance !== undefined && (
<ListItem dense>
<ListItemIcon><ArrowExpandHorizontal/></ListItemIcon>
<ListItemText primary="Subject Distance" secondary={metadata.subjectDistance}/>
</ListItem>
)}
{metadata.subjectDistanceRange !== undefined && (
<ListItem dense>
<ListItemIcon><ArrowExpandHorizontal/></ListItemIcon>
<ListItemText primary="Subject Distance Range"
secondary={SubjectDistanceRange[metadata.subjectDistanceRange]}/>
</ListItem>
)}
{metadata.sceneMode !== undefined && (
<p><b>Scene Mode</b>: {SceneMode[metadata.sceneMode]}</p>
)}
{metadata.contrast !== undefined && (
<ListItem dense>
<ListItemIcon><BrightnessMedium/></ListItemIcon>
<ListItemText primary="Contrast Processing" secondary={ContrastProcessing[metadata.contrast]}/>
</ListItem>
)}
{metadata.sharpness !== undefined && (
<ListItem dense>
<ListItemIcon><Blur/></ListItemIcon>
<ListItemText primary="Sharpness Processing" secondary={SharpnessProcessing[metadata.sharpness]}/>
</ListItem>
)}
</Fragment>
);
}
import {Image} from "../api/model/Image"; import {Image} from "../api/model/Image";
import React, {Fragment, useMemo, useState} from "react"; import React, {useMemo, useState} from "react";
import {useUpdateImage} from "../api/useUpdateImage"; import {useUpdateImage} from "../api/useUpdateImage";
import {useDeleteImage} from "../api/useDeleteImage"; import {useDeleteImage} from "../api/useDeleteImage";
import {parseMetadata, ratioToTime} from "../metadata/ImageMetadata"; import {parseMetadata} from "../metadata/ImageMetadata";
import {ratioToFloat} from "../metadata/Ratio"; import ImageMetadataView from "./ImageMetadataView";
import {ExposureMode} from "../metadata/ExposureMode"; import {Button, List, ListItem, ListItemIcon, ListItemText, TextField} from "@material-ui/core";
import {ExposureProgram} from "../metadata/ExposureProgram"; import {Event, Info} from "@material-ui/icons";
import {LightSource} from "../metadata/LightSource"; import {File, Tag} from "mdi-material-ui";
import {MeteringMode} from "../metadata/MeteringMode";
import {WhiteBalance} from "../metadata/WhiteBalance";
import {SceneMode} from "../metadata/SceneMode";
import {ContrastProcessing} from "../metadata/ContrastProcessing";
import {SharpnessProcessing} from "../metadata/SharpnessProcessing";
import {SubjectDistanceRange} from "../metadata/SubjectDistanceRange";
import {BrightnessMedium, Camera, Copyright, Event, Exposure, PhotoCamera, ZoomIn} from "@material-ui/icons";
import {AngleAcute, ArrowExpandHorizontal, Blur, CameraTimer, Flash, WhiteBalanceIncandescent} from "mdi-material-ui";
import {FlashMode} from "../metadata/FlashMode";
export interface ImageProps { export interface ImageProps {
image: Image image: Image
...@@ -37,135 +28,70 @@ export default function ImageView({image}: ImageProps) { ...@@ -37,135 +28,70 @@ export default function ImageView({image}: ImageProps) {
<p>RemoveError: {JSON.stringify(removeError, null, 2)}</p> <p>RemoveError: {JSON.stringify(removeError, null, 2)}</p>
<p>UpdateLoading: {JSON.stringify(updateLoading, null, 2)}</p> <p>UpdateLoading: {JSON.stringify(updateLoading, null, 2)}</p>
<p>RemoveLoading: {JSON.stringify(removeLoading, null, 2)}</p> <p>RemoveLoading: {JSON.stringify(removeLoading, null, 2)}</p>
<p>{image.id}</p> <img src={image.url + "l"} alt=""/>
<p>{image.owner}</p> <List dense>
<label> <ListItem dense>
Title <ListItemIcon><Info/></ListItemIcon>
<input <ListItemText primary="Id" secondary={image.id}/>
type="text" </ListItem>
<ListItem dense>
<ListItemText inset primary="Owner" secondary={image.owner}/>
</ListItem>
<ListItem dense>
<ListItemText inset primary="State" secondary={image.state}/>
</ListItem>
<ListItem dense>
<ListItemIcon><Tag/></ListItemIcon>
{/* TODO: Fix this ugly nesting */}
<ListItemText
primary="Title"
secondary={<TextField
fullWidth
value={title} value={title}
onChange={({target: {value}}) => onChange={({target: {value}}) =>
setTitle(value)} setTitle(value)}
/>}
/> />
</label> </ListItem>
<br/> <ListItem dense>
<label> {/* TODO: Fix this ugly nesting */}
Description <ListItemText
<input inset
type="text" primary="Description"
secondary={<TextField
fullWidth
value={description} value={description}
onChange={({target: {value}}) => onChange={({target: {value}}) =>
setDescription(value)} setDescription(value)}
/>}
/> />
</label> </ListItem>
<p>{image.original_name}</p> <ListItem dense>
<p>{image.mime_type}</p> <ListItemIcon><File/></ListItemIcon>
<p>{image.created_at}</p> <ListItemText primary="Filename" secondary={image.original_name}/>
<p>{image.updated_at}</p> </ListItem>
<p>{image.state}</p> <ListItem dense>
<h3>Metadata</h3> <ListItemText inset primary="MIME Type" secondary={image.mime_type}/>
{metadata.make !== undefined && ( </ListItem>
<p><b>Make</b>: {metadata.make}</p> <ListItem dense>
)} <ListItemIcon><Event/></ListItemIcon>
{metadata.model !== undefined && ( <ListItemText primary="Uploaded At" secondary={image.created_at}/>
<p><PhotoCamera/><b>Model</b>: {metadata.model}</p> </ListItem>
)} <ListItem dense>
{metadata.software !== undefined && ( <ListItemText inset primary="Modified At" secondary={image.updated_at}/>
<p><b>Software</b>: {metadata.software}</p> </ListItem>
)} <ImageMetadataView metadata={metadata}/>
{metadata.copyright !== undefined && ( </List>
<p><Copyright/><b>Copyright</b>: {metadata.copyright}</p> <Button
)}
{metadata.dateTimeCreated !== undefined && (
<p><Event/><b>DateTime Created</b>: {metadata.dateTimeCreated?.toISOString()}</p>
)}
{metadata.dateTimeDigitized !== undefined && (
<p><Event/><b>DateTime Digitized</b>: {metadata.dateTimeDigitized?.toISOString()}</p>
)}
{metadata.dateTimeOriginal !== undefined && (
<p><Event/><b>DateTime Original</b>: {metadata.dateTimeOriginal?.toISOString()}</p>
)}
{metadata.digitalZoomRatio !== undefined && (
<p><ZoomIn/><b>Digital Zoom</b>: {ratioToFloat(metadata.digitalZoomRatio)}</p>
)}
{metadata.exposure !== undefined && (
<p><Exposure/><b>Exposure</b>: {ratioToFloat(metadata.exposure)}</p>
)}
{metadata.exposureMode !== undefined && (
<p><Exposure/><b>Exposure Mode</b>: {ExposureMode[metadata.exposureMode]}</p>
)}
{metadata.exposureProgram !== undefined && (
<p><Exposure/><b>Exposure Program</b>: {ExposureProgram[metadata.exposureProgram]}</p>
)}
{metadata.shutterSpeed !== undefined && (
<p><CameraTimer/><b>Shutter Speed</b>: {ratioToTime(metadata.shutterSpeed)}</p>
)}
{metadata.aperture !== undefined && (
<p><Camera/><b>Aperture</b>: {ratioToFloat(metadata.aperture)}</p>
)}
{metadata.focalLength !== undefined && (
<p><AngleAcute/><b>Focal Length</b>: {ratioToFloat(metadata.focalLength)}mm</p>
)}
{metadata.focalLength35mm !== undefined && (
<p><AngleAcute/><b>Focal Length (35mm equivalent)</b>: {ratioToFloat(metadata.focalLength35mm)}mm</p>
)}
{metadata.isoSpeedRating !== undefined && (
<p><b>ISO</b>: {metadata.isoSpeedRating}</p>
)}
{metadata.flash !== undefined && (
<Fragment>
<p><Flash/><b>Flash</b></p>
<p><b>Available</b>: {metadata.flash.available ? "Yes" : "No"}</p>
<p><b>Fired</b>: {metadata.flash.fired ? "Yes" : "No"}</p>
<p><b>Red Eye Reduction</b>: {metadata.flash.redEyeReduction ? "Yes" : "No"}</p>
<p><b>Strobe Detection Available</b>: {metadata.flash.strobeDetection.available ? "Yes" : "No"}</p>
<p><b>Strobe Detection Used</b>: {metadata.flash.strobeDetection.detected ? "Yes" : "No"}</p>
{metadata.flash.mode !== undefined && (
<p><b>Flash Mode</b>: {FlashMode[metadata.flash.mode]}</p>
)}
</Fragment>
)}
{metadata.lightSource !== undefined && (
<p><WhiteBalanceIncandescent/><b>Light source</b>: {LightSource[metadata.lightSource]}</p>
)}
{metadata.meteringMode !== undefined && (
<p><b>Metering mode</b>: {MeteringMode[metadata.meteringMode]}</p>
)}
{metadata.whiteBalance !== undefined && (
<p><WhiteBalanceIncandescent/><b>White balance</b>: {WhiteBalance[metadata.whiteBalance]}</p>
)}
{metadata.sceneMode !== undefined && (
<p><b>Scene Mode</b>: {SceneMode[metadata.sceneMode]}</p>
)}
{metadata.contrast !== undefined && (
<p><BrightnessMedium/><b>Contrast Processing</b>: {ContrastProcessing[metadata.contrast]}</p>
)}
{metadata.sharpness !== undefined && (
<p><Blur/><b>Sharpness Processing</b>: {SharpnessProcessing[metadata.sharpness]}</p>
)}
{metadata.subjectDistance !== undefined && (
<p><ArrowExpandHorizontal/><b>Subject Distance</b>: {metadata.subjectDistance}</p>
)}
{metadata.subjectDistanceRange !== undefined && (
<p><ArrowExpandHorizontal/><b>Subject Distance
Range</b>: {SubjectDistanceRange[metadata.subjectDistanceRange]}</p>
)}
<img src={image.url + "t"} alt=""/>
<br/>
<input
type="submit"
value="Save"
onClick={() => update({ onClick={() => update({
...image, ...image,
title, title,
description, description,
})} })}
/> >Save</Button>
<input <Button
type="submit"
value="Delete"
onClick={() => remove(image)} onClick={() => remove(image)}
/> >Delete</Button>
</div> </div>
) )
} }
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment