Skip to content

Commit c63bf4f

Browse files
committed
Add decode-s3-paths plugin
Plugin to decode base64-encoded fileuri parameters in resolve URLs, displaying human-readable S3/GCS/Azure paths in Text tags. Useful when displaying cloud storage paths for reference during image quality control or data validation tasks.
1 parent 28cc9cc commit c63bf4f

File tree

4 files changed

+143
-0
lines changed

4 files changed

+143
-0
lines changed

manifest.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,5 +82,11 @@
8282
"description": "Dynamically populates a text box given the input of a text area tag for span based labeling",
8383
"path": "dynamic-text-spans",
8484
"private": false
85+
},
86+
{
87+
"title": "Decode S3/cloud storage paths",
88+
"description": "Decodes base64-encoded fileuri parameters in resolve URLs to show human-readable S3/GCS/Azure paths",
89+
"path": "decode-s3-paths",
90+
"private": false
8591
}
8692
]

src/decode-s3-paths/data.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"data": {
3+
"image": "/tasks/12345/resolve/?fileuri=czM6Ly9teS1idWNrZXQvaW1hZ2VzL3NhbXBsZS5qcGc="
4+
}
5+
}

src/decode-s3-paths/plugin.js

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
/**
2+
* Plugin: Decode S3 paths from base64 fileuri parameters
3+
*
4+
* When using cloud storage (S3, GCS, Azure), Label Studio generates resolve URLs
5+
* with base64-encoded file paths. This plugin decodes those paths to show
6+
* human-readable S3/GCS/Azure paths in Text tags.
7+
*
8+
* Example:
9+
* Before: /tasks/123/resolve/?fileuri=czM6Ly9idWNrZXQvcGF0aC9maWxlLmpwZw==
10+
* After: s3://bucket/path/file.jpg
11+
*/
12+
(function () {
13+
"use strict";
14+
15+
// Cache for already processed elements (avoid re-processing)
16+
const processedElements = new WeakSet();
17+
18+
// Debounce timer
19+
let debounceTimer = null;
20+
21+
/**
22+
* Decode base64 fileuri to S3/GCS/Azure path
23+
* @param {string} text - Text containing fileuri parameter
24+
* @returns {string|null} - Decoded path or null
25+
*/
26+
function decodeBase64Path(text) {
27+
const match = text.match(/fileuri=([A-Za-z0-9+/=_-]+)/);
28+
if (!match) return null;
29+
30+
try {
31+
// Handle URL-safe base64 variants (replace - with + and _ with /)
32+
const b64 = match[1].replace(/-/g, "+").replace(/_/g, "/");
33+
return atob(b64);
34+
} catch (e) {
35+
console.warn("[S3 Path Decoder] Failed to decode:", e.message);
36+
return null;
37+
}
38+
}
39+
40+
/**
41+
* Process text nodes and replace encoded paths with decoded ones
42+
*/
43+
function processTextNodes() {
44+
// Target elements likely containing path text
45+
const candidates = document.querySelectorAll(
46+
'[class*="text"], [class*="Text"], span, p, div',
47+
);
48+
49+
candidates.forEach((el) => {
50+
// Skip already processed elements
51+
if (processedElements.has(el)) return;
52+
53+
// Only process leaf text nodes (elements with single text child)
54+
if (el.childNodes.length !== 1 || el.childNodes[0].nodeType !== 3) return;
55+
56+
const text = el.textContent;
57+
if (!text || !text.includes("/resolve/?fileuri=")) return;
58+
59+
const decoded = decodeBase64Path(text);
60+
if (decoded) {
61+
el.textContent = text.replace(
62+
/\/tasks\/\d+\/resolve\/\?fileuri=[A-Za-z0-9+/=_-]+/,
63+
decoded,
64+
);
65+
processedElements.add(el);
66+
}
67+
});
68+
}
69+
70+
/**
71+
* Debounced processing to avoid excessive DOM operations
72+
*/
73+
function debouncedProcess() {
74+
if (debounceTimer) clearTimeout(debounceTimer);
75+
debounceTimer = setTimeout(processTextNodes, 150);
76+
}
77+
78+
/**
79+
* Initialize the plugin
80+
*/
81+
function init() {
82+
// Initial processing with staggered retries for dynamic content
83+
processTextNodes();
84+
setTimeout(processTextNodes, 500);
85+
setTimeout(processTextNodes, 1500);
86+
87+
// Watch for dynamic content changes (task navigation, etc.)
88+
const observer = new MutationObserver(debouncedProcess);
89+
90+
observer.observe(document.body, {
91+
childList: true,
92+
subtree: true,
93+
});
94+
95+
// Cleanup on page unload
96+
window.addEventListener("beforeunload", () => {
97+
observer.disconnect();
98+
if (debounceTimer) clearTimeout(debounceTimer);
99+
});
100+
}
101+
102+
// Start when DOM is ready
103+
if (document.readyState === "loading") {
104+
document.addEventListener("DOMContentLoaded", init);
105+
} else {
106+
init();
107+
}
108+
})();

src/decode-s3-paths/view.xml

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<View>
2+
<Header value="Image Quality Review"/>
3+
4+
<!-- Display decoded S3 path for reference -->
5+
<View style="padding: 10px; background-color: #f5f5f5; border-radius: 4px; margin-bottom: 15px;">
6+
<Text name="image_path" value="Path: $image"/>
7+
</View>
8+
9+
<!-- The image to review -->
10+
<Image name="img" value="$image" zoom="true"/>
11+
12+
<!-- Simple quality assessment -->
13+
<View style="margin-top: 15px;">
14+
<Header value="Is this image acceptable?"/>
15+
<Choices name="quality" toName="img" choice="single" required="true">
16+
<Choice value="Yes"/>
17+
<Choice value="No"/>
18+
</Choices>
19+
</View>
20+
21+
<TextArea name="notes" toName="img"
22+
placeholder="Optional notes..."
23+
rows="2" maxSubmissions="1"/>
24+
</View>

0 commit comments

Comments
 (0)