fix: 画像ファイルの縦横サイズの取得で Exif Orientation を考慮する (#8014)
* 画像ファイルの縦横サイズの取得で Exif Orientation を考慮する * test: Add rotate.jpg test * Webpublic 画像を返す時のみ Exif Orientation を考慮して縦横サイズを返す * test: Support orientation
This commit is contained in:
parent
f33ded3107
commit
22464c434e
7 changed files with 70 additions and 8 deletions
|
@ -19,6 +19,7 @@ export type FileInfo = {
|
||||||
};
|
};
|
||||||
width?: number;
|
width?: number;
|
||||||
height?: number;
|
height?: number;
|
||||||
|
orientation?: number;
|
||||||
blurhash?: string;
|
blurhash?: string;
|
||||||
warnings: string[];
|
warnings: string[];
|
||||||
};
|
};
|
||||||
|
@ -47,6 +48,7 @@ export async function getFileInfo(path: string): Promise<FileInfo> {
|
||||||
// image dimensions
|
// image dimensions
|
||||||
let width: number | undefined;
|
let width: number | undefined;
|
||||||
let height: number | undefined;
|
let height: number | undefined;
|
||||||
|
let orientation: number | undefined;
|
||||||
|
|
||||||
if (['image/jpeg', 'image/gif', 'image/png', 'image/apng', 'image/webp', 'image/bmp', 'image/tiff', 'image/svg+xml', 'image/vnd.adobe.photoshop'].includes(type.mime)) {
|
if (['image/jpeg', 'image/gif', 'image/png', 'image/apng', 'image/webp', 'image/bmp', 'image/tiff', 'image/svg+xml', 'image/vnd.adobe.photoshop'].includes(type.mime)) {
|
||||||
const imageSize = await detectImageSize(path).catch(e => {
|
const imageSize = await detectImageSize(path).catch(e => {
|
||||||
|
@ -61,6 +63,7 @@ export async function getFileInfo(path: string): Promise<FileInfo> {
|
||||||
} else if (imageSize.wUnits === 'px') {
|
} else if (imageSize.wUnits === 'px') {
|
||||||
width = imageSize.width;
|
width = imageSize.width;
|
||||||
height = imageSize.height;
|
height = imageSize.height;
|
||||||
|
orientation = imageSize.orientation;
|
||||||
|
|
||||||
// 制限を超えている画像は octet-stream にする
|
// 制限を超えている画像は octet-stream にする
|
||||||
if (imageSize.width > 16383 || imageSize.height > 16383) {
|
if (imageSize.width > 16383 || imageSize.height > 16383) {
|
||||||
|
@ -87,6 +90,7 @@ export async function getFileInfo(path: string): Promise<FileInfo> {
|
||||||
type,
|
type,
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
|
orientation,
|
||||||
blurhash,
|
blurhash,
|
||||||
warnings,
|
warnings,
|
||||||
};
|
};
|
||||||
|
@ -163,6 +167,7 @@ async function detectImageSize(path: string): Promise<{
|
||||||
height: number;
|
height: number;
|
||||||
wUnits: string;
|
wUnits: string;
|
||||||
hUnits: string;
|
hUnits: string;
|
||||||
|
orientation?: number;
|
||||||
}> {
|
}> {
|
||||||
const readable = fs.createReadStream(path);
|
const readable = fs.createReadStream(path);
|
||||||
const imageSize = await probeImageSize(readable);
|
const imageSize = await probeImageSize(readable);
|
||||||
|
|
|
@ -77,7 +77,7 @@ export class DriveFile {
|
||||||
default: {},
|
default: {},
|
||||||
comment: 'The any properties of the DriveFile. For example, it includes image width/height.'
|
comment: 'The any properties of the DriveFile. For example, it includes image width/height.'
|
||||||
})
|
})
|
||||||
public properties: { width?: number; height?: number; avgColor?: string };
|
public properties: { width?: number; height?: number; orientation?: number; avgColor?: string };
|
||||||
|
|
||||||
@Index()
|
@Index()
|
||||||
@Column('boolean')
|
@Column('boolean')
|
||||||
|
|
|
@ -28,6 +28,19 @@ export class DriveFileRepository extends Repository<DriveFile> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public getPublicProperties(file: DriveFile): DriveFile['properties'] {
|
||||||
|
if (file.properties.orientation != null) {
|
||||||
|
const properties = JSON.parse(JSON.stringify(file.properties));
|
||||||
|
if (file.properties.orientation >= 5) {
|
||||||
|
[properties.width, properties.height] = [properties.height, properties.width];
|
||||||
|
}
|
||||||
|
properties.orientation = undefined;
|
||||||
|
return properties;
|
||||||
|
}
|
||||||
|
|
||||||
|
return file.properties;
|
||||||
|
}
|
||||||
|
|
||||||
public getPublicUrl(file: DriveFile, thumbnail = false, meta?: Meta): string | null {
|
public getPublicUrl(file: DriveFile, thumbnail = false, meta?: Meta): string | null {
|
||||||
// リモートかつメディアプロキシ
|
// リモートかつメディアプロキシ
|
||||||
if (file.uri != null && file.userHost != null && config.mediaProxy != null) {
|
if (file.uri != null && file.userHost != null && config.mediaProxy != null) {
|
||||||
|
@ -122,7 +135,7 @@ export class DriveFileRepository extends Repository<DriveFile> {
|
||||||
size: file.size,
|
size: file.size,
|
||||||
isSensitive: file.isSensitive,
|
isSensitive: file.isSensitive,
|
||||||
blurhash: file.blurhash,
|
blurhash: file.blurhash,
|
||||||
properties: file.properties,
|
properties: opts.self ? file.properties : this.getPublicProperties(file),
|
||||||
url: opts.self ? file.url : this.getPublicUrl(file, false, meta),
|
url: opts.self ? file.url : this.getPublicUrl(file, false, meta),
|
||||||
thumbnailUrl: this.getPublicUrl(file, true, meta),
|
thumbnailUrl: this.getPublicUrl(file, true, meta),
|
||||||
comment: file.comment,
|
comment: file.comment,
|
||||||
|
@ -202,6 +215,11 @@ export const packedDriveFileSchema = {
|
||||||
optional: true as const, nullable: false as const,
|
optional: true as const, nullable: false as const,
|
||||||
example: 720
|
example: 720
|
||||||
},
|
},
|
||||||
|
orientation: {
|
||||||
|
type: 'number' as const,
|
||||||
|
optional: true as const, nullable: false as const,
|
||||||
|
example: 8
|
||||||
|
},
|
||||||
avgColor: {
|
avgColor: {
|
||||||
type: 'string' as const,
|
type: 'string' as const,
|
||||||
optional: true as const, nullable: false as const,
|
optional: true as const, nullable: false as const,
|
||||||
|
|
|
@ -372,12 +372,16 @@ export default async function(
|
||||||
const properties: {
|
const properties: {
|
||||||
width?: number;
|
width?: number;
|
||||||
height?: number;
|
height?: number;
|
||||||
|
orientation?: number;
|
||||||
} = {};
|
} = {};
|
||||||
|
|
||||||
if (info.width) {
|
if (info.width) {
|
||||||
properties['width'] = info.width;
|
properties['width'] = info.width;
|
||||||
properties['height'] = info.height;
|
properties['height'] = info.height;
|
||||||
}
|
}
|
||||||
|
if (info.orientation != null) {
|
||||||
|
properties['orientation'] = info.orientation;
|
||||||
|
}
|
||||||
|
|
||||||
const profile = user ? await UserProfiles.findOne(user.id) : null;
|
const profile = user ? await UserProfiles.findOne(user.id) : null;
|
||||||
|
|
||||||
|
|
|
@ -17,6 +17,7 @@ describe('Get file info', () => {
|
||||||
},
|
},
|
||||||
width: undefined,
|
width: undefined,
|
||||||
height: undefined,
|
height: undefined,
|
||||||
|
orientation: undefined,
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
@ -34,6 +35,7 @@ describe('Get file info', () => {
|
||||||
},
|
},
|
||||||
width: 512,
|
width: 512,
|
||||||
height: 512,
|
height: 512,
|
||||||
|
orientation: undefined,
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
@ -51,6 +53,7 @@ describe('Get file info', () => {
|
||||||
},
|
},
|
||||||
width: 256,
|
width: 256,
|
||||||
height: 256,
|
height: 256,
|
||||||
|
orientation: undefined,
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
@ -68,6 +71,7 @@ describe('Get file info', () => {
|
||||||
},
|
},
|
||||||
width: 256,
|
width: 256,
|
||||||
height: 256,
|
height: 256,
|
||||||
|
orientation: undefined,
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
@ -85,6 +89,7 @@ describe('Get file info', () => {
|
||||||
},
|
},
|
||||||
width: 256,
|
width: 256,
|
||||||
height: 256,
|
height: 256,
|
||||||
|
orientation: undefined,
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
@ -102,6 +107,7 @@ describe('Get file info', () => {
|
||||||
},
|
},
|
||||||
width: 256,
|
width: 256,
|
||||||
height: 256,
|
height: 256,
|
||||||
|
orientation: undefined,
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
@ -120,6 +126,7 @@ describe('Get file info', () => {
|
||||||
},
|
},
|
||||||
width: 256,
|
width: 256,
|
||||||
height: 256,
|
height: 256,
|
||||||
|
orientation: undefined,
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
@ -137,6 +144,25 @@ describe('Get file info', () => {
|
||||||
},
|
},
|
||||||
width: 25000,
|
width: 25000,
|
||||||
height: 25000,
|
height: 25000,
|
||||||
|
orientation: undefined,
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('Rotate JPEG', async (async () => {
|
||||||
|
const path = `${__dirname}/resources/rotate.jpg`;
|
||||||
|
const info = await getFileInfo(path) as any;
|
||||||
|
delete info.warnings;
|
||||||
|
delete info.blurhash;
|
||||||
|
assert.deepStrictEqual(info, {
|
||||||
|
size: 12624,
|
||||||
|
md5: '68d5b2d8d1d1acbbce99203e3ec3857e',
|
||||||
|
type: {
|
||||||
|
mime: 'image/jpeg',
|
||||||
|
ext: 'jpg'
|
||||||
|
},
|
||||||
|
width: 512,
|
||||||
|
height: 256,
|
||||||
|
orientation: 8,
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
|
|
BIN
packages/backend/test/resources/rotate.jpg
Normal file
BIN
packages/backend/test/resources/rotate.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 12 KiB |
|
@ -44,12 +44,18 @@ export default defineComponent({
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
const lightbox = new PhotoSwipeLightbox({
|
const lightbox = new PhotoSwipeLightbox({
|
||||||
dataSource: props.mediaList.filter(media => media.type.startsWith('image')).map(media => ({
|
dataSource: props.mediaList.filter(media => media.type.startsWith('image')).map(media => {
|
||||||
|
const item = {
|
||||||
src: media.url,
|
src: media.url,
|
||||||
w: media.properties.width,
|
w: media.properties.width,
|
||||||
h: media.properties.height,
|
h: media.properties.height,
|
||||||
alt: media.name,
|
alt: media.name,
|
||||||
})),
|
};
|
||||||
|
if (media.properties.orientation != null && media.properties.orientation >= 5) {
|
||||||
|
[item.w, item.h] = [item.h, item.w];
|
||||||
|
}
|
||||||
|
return item;
|
||||||
|
}),
|
||||||
gallery: gallery.value,
|
gallery: gallery.value,
|
||||||
children: '.image',
|
children: '.image',
|
||||||
thumbSelector: '.image',
|
thumbSelector: '.image',
|
||||||
|
@ -77,6 +83,9 @@ export default defineComponent({
|
||||||
itemData.src = file.url;
|
itemData.src = file.url;
|
||||||
itemData.w = Number(file.properties.width);
|
itemData.w = Number(file.properties.width);
|
||||||
itemData.h = Number(file.properties.height);
|
itemData.h = Number(file.properties.height);
|
||||||
|
if (file.properties.orientation != null && file.properties.orientation >= 5) {
|
||||||
|
[itemData.w, itemData.h] = [itemData.h, itemData.w];
|
||||||
|
}
|
||||||
itemData.msrc = file.thumbnailUrl;
|
itemData.msrc = file.thumbnailUrl;
|
||||||
itemData.thumbCropped = true;
|
itemData.thumbCropped = true;
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in a new issue