openvibers/openvibe.community Commits
Record orientation state for normalized Community images (#351)
Changes
@@@ -2,6 +2,10 @@## Unreleased++- Community records `orientation applied` on new Media image objects after Media normalizes their EXIF before upload.++ This keeps already-corrected screenshots and attachments out of the later AI orientation queue. Test:++ `test/attachments.test.js`.++- Community paste screenshots and forum attachments now send source image bytes to Media's shared EXIF normalizerbefore upload. Media applies orientation to pixels and removes camera/location metadata; Community stores thenormalized image hash and byte size. The screenshot upload, censor replacement, and forum attachment paths had@@@ -79,7 +79,8 @@ function createMediaObjects({ config, fetchImpl = globalThis.fetch } = {}) {if (!normalized && mime !== 'image/gif') buffer = await normalizeImage(buffer, mime);const sha256 = crypto.createHash('sha256').update(buffer).digest('hex');const init = await call('POST', '', {-- json: { kind, visibility, size_bytes: buffer.length, mime_type: mime, filename, content_hash: sha256, metadata: { source } },++ json: { kind, visibility, size_bytes: buffer.length, mime_type: mime, filename, content_hash: sha256,++ metadata: { source, orientation_status: 'orientation applied' } },headers: /^usr_[0-9A-HJKMNP-TV-Z]{26}$/.test(String(owner || '')) ? { 'X-OV-Subject': owner } : {},});const id = init.id || (init.object && init.object.id);@@@ -96,6 +96,7 @@ const PNG = Buffer.concat([Buffer.from('89504e470d0a1a0a', 'hex'), chunk('IHDR',assert.strictEqual(first.url, `https://openvibe.media/o/${first.media_id}`);const o = objects.get(first.media_id);assert.strictEqual(o.owner, alex.subject_id); assert.strictEqual(o.visibility, 'unlisted', 'reachable by link, never listed on openvibe.media'); assert.strictEqual(o.status, 'ready');++ assert.strictEqual(o.metadata.orientation_status, 'orientation applied', 'the later AI pass can skip images already normalized by Media');assert.ok(!o.bytes.includes(Buffer.from('secret')), 'the tEXt chunk never reached Media');assert.strictEqual(normalizedImages, 1, 'Media normalization ran before object creation');assert.ok(PNG.includes(Buffer.from('secret')));
@@ -2,6 +2,10 @@
@@ -2,6 +2,10 @@
## Unreleased
## Unreleased
+- Community records `orientation applied` on new Media image objects after Media normalizes their EXIF before upload.
+ This keeps already-corrected screenshots and attachments out of the later AI orientation queue. Test:
+ `test/attachments.test.js`.
+
- Community paste screenshots and forum attachments now send source image bytes to Media's shared EXIF normalizer
- Community paste screenshots and forum attachments now send source image bytes to Media's shared EXIF normalizer
before upload. Media applies orientation to pixels and removes camera/location metadata; Community stores the
before upload. Media applies orientation to pixels and removes camera/location metadata; Community stores the
normalized image hash and byte size. The screenshot upload, censor replacement, and forum attachment paths had
normalized image hash and byte size. The screenshot upload, censor replacement, and forum attachment paths had
@@ -79,7 +79,8 @@ function createMediaObjects({ config, fetchImpl = globalThis.fetch } = {}) {
@@ -79,7 +79,8 @@ function createMediaObjects({ config, fetchImpl = globalThis.fetch } = {}) {
if (!normalized && mime !== 'image/gif') buffer = await normalizeImage(buffer, mime);
if (!normalized && mime !== 'image/gif') buffer = await normalizeImage(buffer, mime);
const sha256 = crypto.createHash('sha256').update(buffer).digest('hex');
const sha256 = crypto.createHash('sha256').update(buffer).digest('hex');
const init = await call('POST', '', {
const init = await call('POST', '', {
- json: { kind, visibility, size_bytes: buffer.length, mime_type: mime, filename, content_hash: sha256, metadata: { source } },
+ json: { kind, visibility, size_bytes: buffer.length, mime_type: mime, filename, content_hash: sha256,
+ metadata: { source, orientation_status: 'orientation applied' } },
headers: /^usr_[0-9A-HJKMNP-TV-Z]{26}$/.test(String(owner || '')) ? { 'X-OV-Subject': owner } : {},
headers: /^usr_[0-9A-HJKMNP-TV-Z]{26}$/.test(String(owner || '')) ? { 'X-OV-Subject': owner } : {},
});
});
const id = init.id || (init.object && init.object.id);
const id = init.id || (init.object && init.object.id);
@@ -96,6 +96,7 @@ const PNG = Buffer.concat([Buffer.from('89504e470d0a1a0a', 'hex'), chunk('IHDR',
@@ -96,6 +96,7 @@ const PNG = Buffer.concat([Buffer.from('89504e470d0a1a0a', 'hex'), chunk('IHDR',
assert.strictEqual(first.url, `https://openvibe.media/o/${first.media_id}`);
assert.strictEqual(first.url, `https://openvibe.media/o/${first.media_id}`);
const o = objects.get(first.media_id);
const o = objects.get(first.media_id);
assert.strictEqual(o.owner, alex.subject_id); assert.strictEqual(o.visibility, 'unlisted', 'reachable by link, never listed on openvibe.media'); assert.strictEqual(o.status, 'ready');
assert.strictEqual(o.owner, alex.subject_id); assert.strictEqual(o.visibility, 'unlisted', 'reachable by link, never listed on openvibe.media'); assert.strictEqual(o.status, 'ready');
+ assert.strictEqual(o.metadata.orientation_status, 'orientation applied', 'the later AI pass can skip images already normalized by Media');
assert.ok(!o.bytes.includes(Buffer.from('secret')), 'the tEXt chunk never reached Media');
assert.ok(!o.bytes.includes(Buffer.from('secret')), 'the tEXt chunk never reached Media');
assert.strictEqual(normalizedImages, 1, 'Media normalization ran before object creation');
assert.strictEqual(normalizedImages, 1, 'Media normalization ran before object creation');
assert.ok(PNG.includes(Buffer.from('secret')));
assert.ok(PNG.includes(Buffer.from('secret')));