mirror of
https://github.com/CorentinTh/it-tools.git
synced 2025-05-05 13:57:10 -04:00
Merge 57519777ee
into a07806cd15
This commit is contained in:
commit
03a807d779
6 changed files with 205 additions and 1 deletions
|
@ -57,11 +57,13 @@
|
||||||
"date-fns": "^2.29.3",
|
"date-fns": "^2.29.3",
|
||||||
"dompurify": "^3.0.6",
|
"dompurify": "^3.0.6",
|
||||||
"emojilib": "^3.0.10",
|
"emojilib": "^3.0.10",
|
||||||
|
"exifreader": "^4.20.0",
|
||||||
"figue": "^1.2.0",
|
"figue": "^1.2.0",
|
||||||
"fuse.js": "^6.6.2",
|
"fuse.js": "^6.6.2",
|
||||||
"highlight.js": "^11.7.0",
|
"highlight.js": "^11.7.0",
|
||||||
"iarna-toml-esm": "^3.0.5",
|
"iarna-toml-esm": "^3.0.5",
|
||||||
"ibantools": "^4.3.3",
|
"ibantools": "^4.3.3",
|
||||||
|
"jpeg-quality-estimator": "^1.0.1",
|
||||||
"json5": "^2.2.3",
|
"json5": "^2.2.3",
|
||||||
"jwt-decode": "^3.1.2",
|
"jwt-decode": "^3.1.2",
|
||||||
"libphonenumber-js": "^1.10.28",
|
"libphonenumber-js": "^1.10.28",
|
||||||
|
|
24
pnpm-lock.yaml
generated
24
pnpm-lock.yaml
generated
|
@ -71,6 +71,9 @@ dependencies:
|
||||||
emojilib:
|
emojilib:
|
||||||
specifier: ^3.0.10
|
specifier: ^3.0.10
|
||||||
version: 3.0.10
|
version: 3.0.10
|
||||||
|
exifreader:
|
||||||
|
specifier: ^4.20.0
|
||||||
|
version: 4.20.0
|
||||||
figue:
|
figue:
|
||||||
specifier: ^1.2.0
|
specifier: ^1.2.0
|
||||||
version: 1.2.0
|
version: 1.2.0
|
||||||
|
@ -86,6 +89,9 @@ dependencies:
|
||||||
ibantools:
|
ibantools:
|
||||||
specifier: ^4.3.3
|
specifier: ^4.3.3
|
||||||
version: 4.3.3
|
version: 4.3.3
|
||||||
|
jpeg-quality-estimator:
|
||||||
|
specifier: ^1.0.1
|
||||||
|
version: 1.0.1
|
||||||
json5:
|
json5:
|
||||||
specifier: ^2.2.3
|
specifier: ^2.2.3
|
||||||
version: 2.2.3
|
version: 2.2.3
|
||||||
|
@ -3992,6 +3998,13 @@ packages:
|
||||||
- vue
|
- vue
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/@xmldom/xmldom@0.8.10:
|
||||||
|
resolution: {integrity: sha512-2WALfTl4xo2SkGCYRt6rDTFfk9R1czmBvUQy12gK2KuRKIpWEhcbbzy8EZXtz/jkRqHX8bFEc6FC1HjX4TUWYw==}
|
||||||
|
engines: {node: '>=10.0.0'}
|
||||||
|
requiresBuild: true
|
||||||
|
dev: false
|
||||||
|
optional: true
|
||||||
|
|
||||||
/@zhead/schema@1.0.0-beta.13:
|
/@zhead/schema@1.0.0-beta.13:
|
||||||
resolution: {integrity: sha512-P1A1vRGFBhITco8Iw4/hvnDYoE/SoVrd71dW1pBFdXJb3vP+pBtoOuhbEKy0ROJGOyzQuqvFibcwzyLlWMqNiQ==}
|
resolution: {integrity: sha512-P1A1vRGFBhITco8Iw4/hvnDYoE/SoVrd71dW1pBFdXJb3vP+pBtoOuhbEKy0ROJGOyzQuqvFibcwzyLlWMqNiQ==}
|
||||||
dev: false
|
dev: false
|
||||||
|
@ -5533,6 +5546,13 @@ packages:
|
||||||
strip-final-newline: 2.0.0
|
strip-final-newline: 2.0.0
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/exifreader@4.20.0:
|
||||||
|
resolution: {integrity: sha512-C28BhOHe5svd0Jj/5DGSIXD3PnPp46gfvHN4OkRfvHYZHkcJMhxeUxlwsgJ6Yl62zlZRtmfN+9suZFg0fv4hgg==}
|
||||||
|
requiresBuild: true
|
||||||
|
optionalDependencies:
|
||||||
|
'@xmldom/xmldom': 0.8.10
|
||||||
|
dev: false
|
||||||
|
|
||||||
/extend-shallow@2.0.1:
|
/extend-shallow@2.0.1:
|
||||||
resolution: {integrity: sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==}
|
resolution: {integrity: sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
|
@ -6456,6 +6476,10 @@ packages:
|
||||||
hasBin: true
|
hasBin: true
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/jpeg-quality-estimator@1.0.1:
|
||||||
|
resolution: {integrity: sha512-Znaq+msIqs8Gmhg9JSdDjxUAZMOwYXWIURrfluimn5u2yJ4QAEDhf0tnTMkv3ikcHJoJysG5ewxfbqUXyw/Djg==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/js-beautify@1.14.6:
|
/js-beautify@1.14.6:
|
||||||
resolution: {integrity: sha512-GfofQY5zDp+cuHc+gsEXKPpNw2KbPddreEo35O6jT6i0RVK6LhsoYBhq5TvK4/n74wnA0QbK8gGd+jUZwTMKJw==}
|
resolution: {integrity: sha512-GfofQY5zDp+cuHc+gsEXKPpNw2KbPddreEo35O6jT6i0RVK6LhsoYBhq5TvK4/n74wnA0QbK8gGd+jUZwTMKJw==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
|
|
155
src/tools/image-exif-reader/image-exif-reader.vue
Normal file
155
src/tools/image-exif-reader/image-exif-reader.vue
Normal file
|
@ -0,0 +1,155 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import ExifReader from 'exifreader';
|
||||||
|
import getJpegQuality from 'jpeg-quality-estimator';
|
||||||
|
import { formatBytes } from '@/utils/convert';
|
||||||
|
|
||||||
|
interface Tag {
|
||||||
|
id: number
|
||||||
|
name: string
|
||||||
|
value: any
|
||||||
|
description: string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface TagsSection {
|
||||||
|
[name: string]: Tag
|
||||||
|
}
|
||||||
|
|
||||||
|
const tagsSections = ref<{ name: string; title: string }[]>([
|
||||||
|
{ name: 'file', title: 'File Tags' },
|
||||||
|
{ name: 'jfif', title: 'JFIF Tags' },
|
||||||
|
{ name: 'pngFile', title: 'PNG File Tags' },
|
||||||
|
{ name: 'pngText', title: 'PNG Text Tags' },
|
||||||
|
{ name: 'png', title: 'PNG Tags' },
|
||||||
|
{ name: 'exif', title: 'EXIF Tags' },
|
||||||
|
{ name: 'iptc', title: 'IPTC Tags' },
|
||||||
|
{ name: 'xmp', title: 'XMP Tags' },
|
||||||
|
{ name: 'icc', title: 'ICC Tags' },
|
||||||
|
{ name: 'riff', title: 'RIFF Tags' },
|
||||||
|
{ name: 'gif', title: 'GIF Tags' },
|
||||||
|
{ name: 'Thumbnail', title: 'Thumbnail Tags' },
|
||||||
|
{ name: 'photoshop', title: 'Photoshop Tags' },
|
||||||
|
]);
|
||||||
|
|
||||||
|
const errorMessage = ref<string>('');
|
||||||
|
const tags = ref<ExifReader.ExpandedTags>({});
|
||||||
|
const status = ref<'idle' | 'parsed' | 'error' | 'loading'>('idle');
|
||||||
|
const file = ref<File | null>(null);
|
||||||
|
const quality = ref<number>(-1);
|
||||||
|
|
||||||
|
const openStreetMapUrl = computed(
|
||||||
|
() => {
|
||||||
|
const gpsLatitude = tags.value.gps?.Latitude;
|
||||||
|
const gpsLongitude = tags.value.gps?.Longitude;
|
||||||
|
return gpsLatitude && gpsLongitude ? `https://www.openstreetmap.org/?mlat=${gpsLatitude}&mlon=${gpsLongitude}#map=18/${gpsLatitude}/${gpsLongitude}` : undefined;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
async function onImageUploaded(uploadedFile: File) {
|
||||||
|
file.value = uploadedFile;
|
||||||
|
|
||||||
|
const fileBuffer = await uploadedFile.arrayBuffer();
|
||||||
|
status.value = 'loading';
|
||||||
|
try {
|
||||||
|
quality.value = getJpegQuality(new Uint8Array(fileBuffer));
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
quality.value = -1;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
tags.value = await ExifReader.load(fileBuffer, { expanded: true });
|
||||||
|
status.value = 'parsed';
|
||||||
|
}
|
||||||
|
catch (e: any) {
|
||||||
|
errorMessage.value = e.toString();
|
||||||
|
status.value = 'error';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function getSection(sectionName: string): TagsSection | null {
|
||||||
|
const sections = tags.value as { [name: string]: TagsSection };
|
||||||
|
return sections[sectionName] ? sections[sectionName] : null;
|
||||||
|
}
|
||||||
|
const addSpacesToTagNames = (label: string) => label.replace(/([A-Z][a-z])/g, ' $1').trim();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div style="flex: 0 0 100%">
|
||||||
|
<div mx-auto max-w-600px>
|
||||||
|
<c-file-upload title="Drag and drop a Image file here, or click to select a file" @file-upload="onImageUploaded" />
|
||||||
|
|
||||||
|
<c-card v-if="file" mt-4 flex gap-2>
|
||||||
|
<div font-bold>
|
||||||
|
{{ file.name }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
{{ formatBytes(file.size) }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="tags.Thumbnail">
|
||||||
|
<img :src="`data:image/jpeg;base64,${tags.Thumbnail.base64}`" max-w-200px>
|
||||||
|
</div>
|
||||||
|
</c-card>
|
||||||
|
|
||||||
|
<div v-if="status === 'error'">
|
||||||
|
<c-alert mt-4>
|
||||||
|
Error parsing image file: {{ errorMessage }}
|
||||||
|
</c-alert>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<c-card v-if="quality >= 0" title="JPEG Quality" mt-4>
|
||||||
|
<input-copyable
|
||||||
|
label="JPEG Quality (%)"
|
||||||
|
label-position="left"
|
||||||
|
label-width="150px"
|
||||||
|
label-align="right"
|
||||||
|
mb-2
|
||||||
|
:value="quality"
|
||||||
|
/>
|
||||||
|
</c-card>
|
||||||
|
|
||||||
|
<c-card v-if="status === 'parsed' && openStreetMapUrl" title="GPS Infos" mt-4>
|
||||||
|
<div flex gap-2>
|
||||||
|
<c-label label="Latitude">
|
||||||
|
{{ tags.gps?.Latitude?.toFixed(4) }}
|
||||||
|
</c-label>
|
||||||
|
<c-label label="Longitude">
|
||||||
|
{{ tags.gps?.Longitude?.toFixed(4) }}
|
||||||
|
</c-label>
|
||||||
|
<c-label label="Altitude">
|
||||||
|
{{ tags.gps?.Altitude?.toFixed(4) }}
|
||||||
|
</c-label>
|
||||||
|
</div>
|
||||||
|
<c-button :href="openStreetMapUrl" target="_blank" mt-4>
|
||||||
|
Localize on Open Street Map
|
||||||
|
</c-button>
|
||||||
|
</c-card>
|
||||||
|
<c-card v-if="status === 'parsed' && !openStreetMapUrl" mt-4>
|
||||||
|
No GPS Information
|
||||||
|
</c-card>
|
||||||
|
|
||||||
|
<div v-if="status === 'parsed'">
|
||||||
|
<div v-for="section in tagsSections" :key="section.name">
|
||||||
|
<c-card v-if="getSection(section.name)" :title="section.title" mt-4>
|
||||||
|
<input-copyable
|
||||||
|
v-for="({ description }, tagName) in getSection(section.name)"
|
||||||
|
:key="tagName"
|
||||||
|
:label="addSpacesToTagNames(String(tagName))"
|
||||||
|
label-position="left"
|
||||||
|
label-width="150px"
|
||||||
|
label-align="right"
|
||||||
|
mb-2
|
||||||
|
disabled="disabled"
|
||||||
|
:value="description ?? '<binary>'"
|
||||||
|
/>
|
||||||
|
</c-card>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="status === 'parsed'" style="flex: 0 0 100%" mt-5 flex flex-col gap-4 />
|
||||||
|
|
||||||
|
<div font-size-3>
|
||||||
|
Made with <a href="https://github.com/mattiasw/ExifReader" target="_blank">ExifReader</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
12
src/tools/image-exif-reader/index.ts
Normal file
12
src/tools/image-exif-reader/index.ts
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
import { FileInfo } from '@vicons/tabler';
|
||||||
|
import { defineTool } from '../tool';
|
||||||
|
|
||||||
|
export const tool = defineTool({
|
||||||
|
name: 'Image EXIF/Metadata/GPS/JPEG Quality reader',
|
||||||
|
path: '/image-exif-reader',
|
||||||
|
description: 'Read EXIF, IPTC, XMP, GPS and other metadata, JPEG Quality, and other infos from images files',
|
||||||
|
keywords: ['image', 'exif', 'reader', 'iptc', 'gps', 'xmp', 'jpeg', 'quality'],
|
||||||
|
component: () => import('./image-exif-reader.vue'),
|
||||||
|
icon: FileInfo,
|
||||||
|
createdAt: new Date('2024-01-09'),
|
||||||
|
});
|
4
src/tools/image-exif-reader/jpeg-quality-estimator.d.ts
vendored
Normal file
4
src/tools/image-exif-reader/jpeg-quality-estimator.d.ts
vendored
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
declare module 'jpeg-quality-estimator' {
|
||||||
|
const getJpegQuality: (file: Uint8Array) => number;
|
||||||
|
export default getJpegQuality;
|
||||||
|
}
|
|
@ -76,6 +76,7 @@ import { tool as urlParser } from './url-parser';
|
||||||
import { tool as uuidGenerator } from './uuid-generator';
|
import { tool as uuidGenerator } from './uuid-generator';
|
||||||
import { tool as macAddressLookup } from './mac-address-lookup';
|
import { tool as macAddressLookup } from './mac-address-lookup';
|
||||||
import { tool as xmlFormatter } from './xml-formatter';
|
import { tool as xmlFormatter } from './xml-formatter';
|
||||||
|
import { tool as imageExifReader } from './image-exif-reader';
|
||||||
import { tool as yamlViewer } from './yaml-viewer';
|
import { tool as yamlViewer } from './yaml-viewer';
|
||||||
|
|
||||||
export const toolsByCategory: ToolCategory[] = [
|
export const toolsByCategory: ToolCategory[] = [
|
||||||
|
@ -127,7 +128,13 @@ export const toolsByCategory: ToolCategory[] = [
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Images and videos',
|
name: 'Images and videos',
|
||||||
components: [qrCodeGenerator, wifiQrCodeGenerator, svgPlaceholderGenerator, cameraRecorder],
|
components: [
|
||||||
|
qrCodeGenerator,
|
||||||
|
wifiQrCodeGenerator,
|
||||||
|
svgPlaceholderGenerator,
|
||||||
|
cameraRecorder,
|
||||||
|
imageExifReader,
|
||||||
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Development',
|
name: 'Development',
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue