akhaliq HF Staff commited on
Commit
aefc326
·
verified ·
1 Parent(s): f020ed0

Upload index.js with huggingface_hub

Browse files
Files changed (1) hide show
  1. index.js +305 -56
index.js CHANGED
@@ -1,76 +1,325 @@
1
- import { pipeline } from 'https://cdn.jsdelivr.net/npm/@huggingface/transformers@3.4.1';
 
 
 
 
2
 
3
- // Reference the elements that we will need
4
- const status = document.getElementById('status');
5
- const fileUpload = document.getElementById('upload');
6
- const imageContainer = document.getElementById('container');
7
- const example = document.getElementById('example');
8
 
9
- const EXAMPLE_URL = 'https://huggingface.co/datasets/Xenova/transformers.js-docs/resolve/main/city-streets.jpg';
 
 
 
 
 
 
 
 
 
 
 
 
10
 
11
- // Create a new object detection pipeline
12
- status.textContent = 'Loading model...';
13
- const detector = await pipeline('object-detection', 'Xenova/detr-resnet-50');
14
- status.textContent = 'Ready';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
15
 
16
- example.addEventListener('click', (e) => {
 
 
 
 
17
  e.preventDefault();
18
- detect(EXAMPLE_URL);
 
 
 
 
19
  });
20
 
21
- fileUpload.addEventListener('change', function (e) {
22
- const file = e.target.files[0];
23
- if (!file) {
 
 
 
 
 
 
24
  return;
25
  }
 
 
 
 
 
 
 
 
 
26
 
27
- const reader = new FileReader();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
28
 
29
- // Set up a callback when the file is loaded
30
- reader.onload = e2 => detect(e2.target.result);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
31
 
32
- reader.readAsDataURL(file);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
33
  });
34
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
35
 
36
- // Detect objects in the image
37
- async function detect(img) {
38
- imageContainer.innerHTML = '';
39
- imageContainer.style.backgroundImage = `url(${img})`;
 
40
 
41
- status.textContent = 'Analysing...';
42
- const output = await detector(img, {
43
- threshold: 0.5,
44
- percentage: true,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
45
  });
46
- status.textContent = '';
47
- output.forEach(renderBox);
 
 
 
 
 
 
 
48
  }
49
 
50
- // Render a bounding box and label on the image
51
- function renderBox({ box, label }) {
52
- const { xmax, xmin, ymax, ymin } = box;
53
-
54
- // Generate a random color for the box
55
- const color = '#' + Math.floor(Math.random() * 0xFFFFFF).toString(16).padStart(6, 0);
56
-
57
- // Draw the box
58
- const boxElement = document.createElement('div');
59
- boxElement.className = 'bounding-box';
60
- Object.assign(boxElement.style, {
61
- borderColor: color,
62
- left: 100 * xmin + '%',
63
- top: 100 * ymin + '%',
64
- width: 100 * (xmax - xmin) + '%',
65
- height: 100 * (ymax - ymin) + '%',
66
- })
67
-
68
- // Draw label
69
- const labelElement = document.createElement('span');
70
- labelElement.textContent = label;
71
- labelElement.className = 'bounding-box-label';
72
- labelElement.style.backgroundColor = color;
73
-
74
- boxElement.appendChild(labelElement);
75
- imageContainer.appendChild(boxElement);
76
  }
 
 
 
 
1
+ import {
2
+ AutoProcessor,
3
+ AutoModelForImageTextToText,
4
+ TextStreamer,
5
+ } from 'https://cdn.jsdelivr.net/npm/@huggingface/transformers@3.7.2';
6
 
7
+ let processor = null;
8
+ let model = null;
9
+ let videoFile = null;
10
+ let frames = [];
11
+ let captions = [];
12
 
13
+ // DOM Elements
14
+ const uploadArea = document.getElementById('uploadArea');
15
+ const videoInput = document.getElementById('videoInput');
16
+ const videoSection = document.getElementById('videoSection');
17
+ const videoPlayer = document.getElementById('videoPlayer');
18
+ const frameCanvas = document.getElementById('frameCanvas');
19
+ const processBtn = document.getElementById('processBtn');
20
+ const progressSection = document.getElementById('progressSection');
21
+ const progressFill = document.getElementById('progressFill');
22
+ const progressText = document.getElementById('progressText');
23
+ const resultsSection = document.getElementById('resultsSection');
24
+ const framesList = document.getElementById('framesList');
25
+ const deviceSelect = document.getElementById('deviceSelect');
26
 
27
+ // Check WebGPU support
28
+ async function checkWebGPU() {
29
+ if (!navigator.gpu) {
30
+ deviceSelect.querySelector('option[value="webgpu"]').disabled = true;
31
+ deviceSelect.value = 'wasm';
32
+ }
33
+ }
34
+
35
+ // Initialize model
36
+ async function initializeModel() {
37
+ try {
38
+ progressText.textContent = 'Loading processor...';
39
+ progressFill.style.width = '30%';
40
+
41
+ const model_id = 'onnx-community/FastVLM-0.5B-ONNX';
42
+ processor = await AutoProcessor.from_pretrained(model_id);
43
+
44
+ progressText.textContent = 'Loading model (this may take a moment)...';
45
+ progressFill.style.width = '60%';
46
+
47
+ const device = deviceSelect.value === 'webgpu' ? 'webgpu' : 'wasm';
48
+ model = await AutoModelForImageTextToText.from_pretrained(model_id, {
49
+ device: device,
50
+ dtype: {
51
+ embed_tokens: 'fp16',
52
+ vision_encoder: 'q4',
53
+ decoder_model_merged: 'q4',
54
+ },
55
+ });
56
+
57
+ progressFill.style.width = '100%';
58
+ progressText.textContent = 'Model loaded successfully!';
59
+
60
+ return true;
61
+ } catch (error) {
62
+ console.error('Error initializing model:', error);
63
+ progressText.textContent = 'Error loading model. Please refresh and try again.';
64
+ return false;
65
+ }
66
+ }
67
+
68
+ // Upload handling
69
+ uploadArea.addEventListener('click', () => videoInput.click());
70
+
71
+ uploadArea.addEventListener('dragover', (e) => {
72
+ e.preventDefault();
73
+ uploadArea.classList.add('dragover');
74
+ });
75
 
76
+ uploadArea.addEventListener('dragleave', () => {
77
+ uploadArea.classList.remove('dragover');
78
+ });
79
+
80
+ uploadArea.addEventListener('drop', (e) => {
81
  e.preventDefault();
82
+ uploadArea.classList.remove('dragover');
83
+ const files = e.dataTransfer.files;
84
+ if (files.length > 0 && files[0].type.startsWith('video/')) {
85
+ handleVideoFile(files[0]);
86
+ }
87
  });
88
 
89
+ videoInput.addEventListener('change', (e) => {
90
+ if (e.target.files.length > 0) {
91
+ handleVideoFile(e.target.files[0]);
92
+ }
93
+ });
94
+
95
+ function handleVideoFile(file) {
96
+ if (file.size > 100 * 1024 * 1024) {
97
+ alert('File size exceeds 100MB limit');
98
  return;
99
  }
100
+
101
+ videoFile = file;
102
+ const url = URL.createObjectURL(file);
103
+ videoPlayer.src = url;
104
+ videoSection.classList.remove('hidden');
105
+ resultsSection.classList.add('hidden');
106
+ frames = [];
107
+ captions = [];
108
+ }
109
 
110
+ // Extract frames from video
111
+ async function extractFrames() {
112
+ const interval = parseInt(document.getElementById('frameInterval').value);
113
+ const ctx = frameCanvas.getContext('2d');
114
+ const duration = videoPlayer.duration;
115
+ frames = [];
116
+
117
+ for (let time = 0; time < duration; time += interval) {
118
+ videoPlayer.currentTime = time;
119
+ await new Promise(resolve => {
120
+ videoPlayer.onseeked = resolve;
121
+ });
122
+
123
+ frameCanvas.width = videoPlayer.videoWidth;
124
+ frameCanvas.height = videoPlayer.videoHeight;
125
+ ctx.drawImage(videoPlayer, 0, 0);
126
+
127
+ const blob = await new Promise(resolve => {
128
+ frameCanvas.toBlob(resolve, 'image/jpeg', 0.9);
129
+ });
130
+
131
+ frames.push({
132
+ time: time,
133
+ blob: blob,
134
+ dataUrl: await blobToDataUrl(blob)
135
+ });
136
+ }
137
+
138
+ return frames;
139
+ }
140
 
141
+ function blobToDataUrl(blob) {
142
+ return new Promise((resolve) => {
143
+ const reader = new FileReader();
144
+ reader.onloadend = () => resolve(reader.result);
145
+ reader.readAsDataURL(blob);
146
+ });
147
+ }
148
+
149
+ // Generate caption for a frame
150
+ async function generateCaption(imageDataUrl, frameIndex, totalFrames) {
151
+ try {
152
+ progressText.textContent = `Processing frame ${frameIndex + 1} of ${totalFrames}...`;
153
+ progressFill.style.width = `${((frameIndex + 1) / totalFrames) * 100}%`;
154
+
155
+ const messages = [
156
+ {
157
+ role: 'user',
158
+ content: '<image>Describe this video frame in detail. What is happening in this scene?',
159
+ },
160
+ ];
161
+
162
+ const prompt = processor.apply_chat_template(messages, {
163
+ add_generation_prompt: true,
164
+ });
165
+
166
+ // Create image element from data URL
167
+ const img = new Image();
168
+ img.src = imageDataUrl;
169
+ await new Promise(resolve => img.onload = resolve);
170
+
171
+ const inputs = await processor(img, prompt, {
172
+ add_special_tokens: false,
173
+ });
174
+
175
+ let streamedText = '';
176
+ const outputs = await model.generate({
177
+ ...inputs,
178
+ max_new_tokens: 256,
179
+ do_sample: false,
180
+ streamer: new TextStreamer(processor.tokenizer, {
181
+ skip_prompt: true,
182
+ skip_special_tokens: false,
183
+ callback_function: (text) => {
184
+ streamedText += text;
185
+ },
186
+ }),
187
+ });
188
+
189
+ const decoded = processor.batch_decode(
190
+ outputs.slice(null, [inputs.input_ids.dims.at(-1), null]),
191
+ { skip_special_tokens: true }
192
+ );
193
+
194
+ return decoded[0];
195
+ } catch (error) {
196
+ console.error('Error generating caption:', error);
197
+ return 'Error generating caption for this frame';
198
+ }
199
+ }
200
 
201
+ // Process video
202
+ processBtn.addEventListener('click', async () => {
203
+ if (!videoFile) return;
204
+
205
+ processBtn.disabled = true;
206
+ progressSection.classList.remove('hidden');
207
+ resultsSection.classList.add('hidden');
208
+
209
+ try {
210
+ // Initialize model if not already loaded
211
+ if (!model || !processor) {
212
+ const success = await initializeModel();
213
+ if (!success) {
214
+ processBtn.disabled = false;
215
+ return;
216
+ }
217
+ }
218
+
219
+ // Extract frames
220
+ progressText.textContent = 'Extracting frames...';
221
+ progressFill.style.width = '20%';
222
+ frames = await extractFrames();
223
+
224
+ // Generate captions
225
+ captions = [];
226
+ for (let i = 0; i < frames.length; i++) {
227
+ const caption = await generateCaption(frames[i].dataUrl, i, frames.length);
228
+ captions.push({
229
+ time: frames[i].time,
230
+ caption: caption,
231
+ thumbnail: frames[i].dataUrl
232
+ });
233
+
234
+ // Update results in real-time
235
+ displayResults();
236
+ resultsSection.classList.remove('hidden');
237
+ }
238
+
239
+ progressText.textContent = 'Processing complete!';
240
+ setTimeout(() => {
241
+ progressSection.classList.add('hidden');
242
+ }, 2000);
243
+
244
+ } catch (error) {
245
+ console.error('Processing error:', error);
246
+ progressText.textContent = 'Error processing video. Please try again.';
247
+ }
248
+
249
+ processBtn.disabled = false;
250
  });
251
 
252
+ // Display results
253
+ function displayResults() {
254
+ framesList.innerHTML = '';
255
+
256
+ captions.forEach((item, index) => {
257
+ const frameCard = document.createElement('div');
258
+ frameCard.className = 'frame-card';
259
+
260
+ const time = formatTime(item.time);
261
+
262
+ frameCard.innerHTML = `
263
+ <div class="frame-thumbnail">
264
+ <img src="${item.thumbnail}" alt="Frame at ${time}">
265
+ <span class="frame-time">${time}</span>
266
+ </div>
267
+ <div class="frame-caption">
268
+ <p>${item.caption}</p>
269
+ </div>
270
+ `;
271
+
272
+ framesList.appendChild(frameCard);
273
+ });
274
+ }
275
 
276
+ function formatTime(seconds) {
277
+ const mins = Math.floor(seconds / 60);
278
+ const secs = Math.floor(seconds % 60);
279
+ return `${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
280
+ }
281
 
282
+ // Export functions
283
+ document.getElementById('exportJson').addEventListener('click', () => {
284
+ const data = JSON.stringify(captions, null, 2);
285
+ downloadFile(data, 'captions.json', 'application/json');
286
+ });
287
+
288
+ document.getElementById('exportSrt').addEventListener('click', () => {
289
+ let srt = '';
290
+ captions.forEach((item, index) => {
291
+ const startTime = formatSrtTime(item.time);
292
+ const endTime = formatSrtTime(item.time + 5);
293
+ srt += `${index + 1}\n${startTime} --> ${endTime}\n${item.caption}\n\n`;
294
+ });
295
+ downloadFile(srt, 'captions.srt', 'text/plain');
296
+ });
297
+
298
+ document.getElementById('exportTxt').addEventListener('click', () => {
299
+ let txt = '';
300
+ captions.forEach(item => {
301
+ txt += `[${formatTime(item.time)}] ${item.caption}\n\n`;
302
  });
303
+ downloadFile(txt, 'captions.txt', 'text/plain');
304
+ });
305
+
306
+ function formatSrtTime(seconds) {
307
+ const hours = Math.floor(seconds / 3600);
308
+ const mins = Math.floor((seconds % 3600) / 60);
309
+ const secs = Math.floor(seconds % 60);
310
+ const ms = Math.floor((seconds % 1) * 1000);
311
+ return `${hours.toString().padStart(2, '0')}:${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')},${ms.toString().padStart(3, '0')}`;
312
  }
313
 
314
+ function downloadFile(content, filename, type) {
315
+ const blob = new Blob([content], { type });
316
+ const url = URL.createObjectURL(blob);
317
+ const a = document.createElement('a');
318
+ a.href = url;
319
+ a.download = filename;
320
+ a.click();
321
+ URL.revokeObjectURL(url);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
322
  }
323
+
324
+ // Initialize
325
+ checkWebGPU();