yhzion commited on
Commit
a67ccdd
·
verified ·
1 Parent(s): f504b21

비디오트랙이 끝나면 재생이 멈춰야 함. 플레이헤드가 중심 전에 있을때는 재생시 플레이 헤드가 중심에 닿을때까지 플레이헤드가 움직이면서 재생이 되어야 함. 지금은 재생하면 바로 중심으로 이동해서 재생됨. - Initial Deployment

Browse files
Files changed (2) hide show
  1. README.md +7 -5
  2. index.html +535 -19
README.md CHANGED
@@ -1,10 +1,12 @@
1
  ---
2
- title: Auto Scroll Timeline
3
- emoji: 📈
4
- colorFrom: green
5
- colorTo: gray
6
  sdk: static
7
  pinned: false
 
 
8
  ---
9
 
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
1
  ---
2
+ title: auto-scroll-timeline
3
+ emoji: 🐳
4
+ colorFrom: yellow
5
+ colorTo: yellow
6
  sdk: static
7
  pinned: false
8
+ tags:
9
+ - deepsite
10
  ---
11
 
12
+ Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
index.html CHANGED
@@ -1,19 +1,535 @@
1
- <!doctype html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width" />
6
- <title>My static Space</title>
7
- <link rel="stylesheet" href="style.css" />
8
- </head>
9
- <body>
10
- <div class="card">
11
- <h1>Welcome to your static Space!</h1>
12
- <p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
13
- <p>
14
- Also don't forget to check the
15
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
16
- </p>
17
- </div>
18
- </body>
19
- </html>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Premiere Pro Style Timeline</title>
7
+ <script src="https://cdn.tailwindcss.com"></script>
8
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
9
+ <style>
10
+ .timeline-container {
11
+ --zoom-level: 1;
12
+ --playhead-position: 0;
13
+ --timeline-height: 200px;
14
+ --video-track-height: 100px;
15
+ --audio-track-height: 60px;
16
+ --ruler-height: 40px;
17
+ --duration: 600000; /* 10 minutes in pixels (100px per second) */
18
+ }
19
+
20
+ .timeline-wrapper {
21
+ overflow: hidden;
22
+ position: relative;
23
+ height: calc(var(--timeline-height) + var(--ruler-height));
24
+ }
25
+
26
+ .timeline-ruler {
27
+ height: var(--ruler-height);
28
+ background-color: #2d3748;
29
+ position: sticky;
30
+ top: 0;
31
+ z-index: 20;
32
+ }
33
+
34
+ .timeline-content {
35
+ height: var(--timeline-height);
36
+ background-color: #1a202c;
37
+ position: relative;
38
+ transform: scaleX(var(--zoom-level));
39
+ transform-origin: left center;
40
+ width: calc(100% / var(--zoom-level));
41
+ will-change: transform;
42
+ }
43
+
44
+ .video-track {
45
+ height: var(--video-track-height);
46
+ background-color: #2d3748;
47
+ margin: 10px;
48
+ border-radius: 4px;
49
+ position: relative;
50
+ overflow: hidden;
51
+ }
52
+
53
+ .audio-track {
54
+ height: var(--audio-track-height);
55
+ background-color: #2d3748;
56
+ margin: 10px;
57
+ border-radius: 4px;
58
+ position: relative;
59
+ overflow: hidden;
60
+ }
61
+
62
+ .waveform {
63
+ position: absolute;
64
+ bottom: 0;
65
+ left: 0;
66
+ width: 100%;
67
+ height: 100%;
68
+ display: flex;
69
+ align-items: flex-end;
70
+ }
71
+
72
+ .waveform-bar {
73
+ flex: 1;
74
+ background-color: #4299e1;
75
+ margin-right: 1px;
76
+ min-width: 1px;
77
+ }
78
+
79
+ .playhead {
80
+ position: absolute;
81
+ top: 0;
82
+ left: var(--playhead-position);
83
+ width: 2px;
84
+ height: 100%;
85
+ background-color: #f56565;
86
+ z-index: 10;
87
+ pointer-events: none;
88
+ }
89
+
90
+ .playhead::after {
91
+ content: '';
92
+ position: absolute;
93
+ top: 0;
94
+ left: -5px;
95
+ width: 0;
96
+ height: 0;
97
+ border-left: 6px solid transparent;
98
+ border-right: 6px solid transparent;
99
+ border-top: 10px solid #f56565;
100
+ }
101
+
102
+ .timeline-clip {
103
+ position: absolute;
104
+ height: 100%;
105
+ background-color: #4299e1;
106
+ border-radius: 2px;
107
+ cursor: move;
108
+ user-select: none;
109
+ }
110
+
111
+ .timeline-clip:hover {
112
+ filter: brightness(1.1);
113
+ }
114
+
115
+ .ruler-marker {
116
+ position: absolute;
117
+ bottom: 0;
118
+ width: 1px;
119
+ height: 10px;
120
+ background-color: #718096;
121
+ }
122
+
123
+ .ruler-number {
124
+ position: absolute;
125
+ bottom: 12px;
126
+ transform: translateX(-50%);
127
+ font-size: 10px;
128
+ color: #a0aec0;
129
+ }
130
+
131
+ .zoom-controls {
132
+ position: absolute;
133
+ right: 10px;
134
+ top: 10px;
135
+ z-index: 30;
136
+ background-color: rgba(45, 55, 72, 0.8);
137
+ border-radius: 4px;
138
+ padding: 5px;
139
+ }
140
+
141
+ .scrub-area {
142
+ position: absolute;
143
+ top: 0;
144
+ left: 0;
145
+ width: 100%;
146
+ height: 100%;
147
+ z-index: 5;
148
+ cursor: grab;
149
+ }
150
+
151
+ .scrub-area:active {
152
+ cursor: grabbing;
153
+ }
154
+ </style>
155
+ </head>
156
+ <body class="bg-gray-900 text-white p-4">
157
+ <div class="max-w-6xl mx-auto">
158
+ <h1 class="text-2xl font-bold mb-4">Video Timeline Editor</h1>
159
+
160
+ <div class="bg-gray-800 rounded-lg p-4 mb-4">
161
+ <div class="flex items-center justify-between mb-2">
162
+ <div class="flex space-x-2">
163
+ <button id="playBtn" class="bg-blue-600 hover:bg-blue-700 px-4 py-2 rounded">
164
+ <i class="fas fa-play"></i>
165
+ </button>
166
+ <button id="pauseBtn" class="bg-gray-600 hover:bg-gray-700 px-4 py-2 rounded">
167
+ <i class="fas fa-pause"></i>
168
+ </button>
169
+ <button id="stopBtn" class="bg-gray-600 hover:bg-gray-700 px-4 py-2 rounded">
170
+ <i class="fas fa-stop"></i>
171
+ </button>
172
+ </div>
173
+ <div class="text-sm">
174
+ <span id="timeDisplay">00:00:00:00</span>
175
+ </div>
176
+ </div>
177
+
178
+ <div class="relative timeline-container">
179
+ <div class="zoom-controls flex space-x-1">
180
+ <button id="zoomIn" class="bg-gray-700 hover:bg-gray-600 w-8 h-8 rounded flex items-center justify-center">
181
+ <i class="fas fa-search-plus"></i>
182
+ </button>
183
+ <button id="zoomOut" class="bg-gray-700 hover:bg-gray-600 w-8 h-8 rounded flex items-center justify-center">
184
+ <i class="fas fa-search-minus"></i>
185
+ </button>
186
+ <button id="zoomReset" class="bg-gray-700 hover:bg-gray-600 w-8 h-8 rounded flex items-center justify-center">
187
+ <i class="fas fa-expand"></i>
188
+ </button>
189
+ </div>
190
+
191
+ <div class="timeline-wrapper">
192
+ <div class="timeline-ruler" id="ruler"></div>
193
+ <div class="timeline-content" id="timelineContent">
194
+ <div class="playhead" id="playhead"></div>
195
+ <div class="scrub-area" id="scrubArea"></div>
196
+ <div class="video-track" id="videoTrack">
197
+ <div class="timeline-clip" style="left: 0; width: 100%; background-color: rgba(66, 153, 225, 0.3);"></div>
198
+ </div>
199
+ <div class="audio-track" id="audioTrack">
200
+ <div class="waveform" id="waveform"></div>
201
+ </div>
202
+ </div>
203
+ </div>
204
+ </div>
205
+ </div>
206
+ </div>
207
+
208
+ <script>
209
+ document.addEventListener('DOMContentLoaded', function() {
210
+ // DOM elements
211
+ const timelineContainer = document.querySelector('.timeline-container');
212
+ const timelineContent = document.getElementById('timelineContent');
213
+ const ruler = document.getElementById('ruler');
214
+ const playhead = document.getElementById('playhead');
215
+ const scrubArea = document.getElementById('scrubArea');
216
+ const playBtn = document.getElementById('playBtn');
217
+ const pauseBtn = document.getElementById('pauseBtn');
218
+ const stopBtn = document.getElementById('stopBtn');
219
+ const timeDisplay = document.getElementById('timeDisplay');
220
+ const zoomInBtn = document.getElementById('zoomIn');
221
+ const zoomOutBtn = document.getElementById('zoomOut');
222
+ const zoomResetBtn = document.getElementById('zoomReset');
223
+
224
+ // State variables
225
+ let isPlaying = false;
226
+ let playheadPosition = 0;
227
+ let maxPosition = 600000; // 10 minutes in pixels (100px per second)
228
+ let fps = 30;
229
+ let currentTime = 0;
230
+ let zoomLevel = 1;
231
+ let animationId;
232
+ let isScrubbing = false;
233
+ let clips = Array.from(document.querySelectorAll('.timeline-clip'));
234
+
235
+ // Initialize ruler
236
+ function initRuler() {
237
+ ruler.innerHTML = '';
238
+ const rulerWidth = timelineContent.offsetWidth;
239
+ const markerInterval = 100; // pixels between markers
240
+ const numberInterval = 500; // pixels between numbers
241
+
242
+ // Calculate how many markers we need
243
+ const markerCount = Math.ceil(rulerWidth / markerInterval);
244
+
245
+ for (let i = 0; i <= markerCount; i++) {
246
+ const pos = i * markerInterval;
247
+
248
+ // Create marker
249
+ const marker = document.createElement('div');
250
+ marker.className = 'ruler-marker';
251
+ marker.style.left = `${pos}px`;
252
+
253
+ // Adjust height based on interval
254
+ if (i % 5 === 0) {
255
+ marker.style.height = '20px';
256
+ } else if (i % 10 === 0) {
257
+ marker.style.height = '30px';
258
+ }
259
+
260
+ ruler.appendChild(marker);
261
+
262
+ // Add numbers at larger intervals
263
+ if (i % 5 === 0) {
264
+ const number = document.createElement('div');
265
+ number.className = 'ruler-number';
266
+ number.style.left = `${pos}px`;
267
+ number.textContent = formatTimeFromPixels(pos);
268
+ ruler.appendChild(number);
269
+ }
270
+ }
271
+ }
272
+
273
+ // Format time from pixels
274
+ function formatTimeFromPixels(pixels) {
275
+ const seconds = pixels / 100;
276
+ const minutes = Math.floor(seconds / 60);
277
+ const hours = Math.floor(minutes / 60);
278
+ const remainingSeconds = Math.floor(seconds % 60);
279
+ const frames = Math.floor((seconds % 1) * fps);
280
+
281
+ return `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${remainingSeconds.toString().padStart(2, '0')}:${frames.toString().padStart(2, '0')}`;
282
+ }
283
+
284
+ // Update time display
285
+ function updateTimeDisplay() {
286
+ timeDisplay.textContent = formatTimeFromPixels(playheadPosition);
287
+ }
288
+
289
+ // Play animation
290
+ function play() {
291
+ if (isPlaying) return;
292
+
293
+ isPlaying = true;
294
+ playBtn.classList.remove('bg-blue-600');
295
+ playBtn.classList.add('bg-gray-600');
296
+ pauseBtn.classList.remove('bg-gray-600');
297
+ pauseBtn.classList.add('bg-blue-600');
298
+
299
+ let lastTimestamp = null;
300
+ const timelineRect = timelineContent.getBoundingClientRect();
301
+ const centerOffset = timelineRect.width / 2;
302
+
303
+ // First move playhead to center if it's before center
304
+ if (playheadPosition < centerOffset) {
305
+ const moveToCenter = () => {
306
+ playheadPosition += 5; // Adjust speed as needed
307
+
308
+ if (playheadPosition >= centerOffset) {
309
+ playheadPosition = centerOffset;
310
+ startPlayback();
311
+ return;
312
+ }
313
+
314
+ const translateX = centerOffset - playheadPosition;
315
+ timelineContent.style.transform = `scaleX(${zoomLevel}) translateX(${translateX}px)`;
316
+ playhead.style.left = `${playheadPosition}px`;
317
+ updateTimeDisplay();
318
+
319
+ if (isPlaying) {
320
+ animationId = requestAnimationFrame(moveToCenter);
321
+ }
322
+ };
323
+
324
+ animationId = requestAnimationFrame(moveToCenter);
325
+ } else {
326
+ startPlayback();
327
+ }
328
+ }
329
+
330
+ function startPlayback() {
331
+ let lastTimestamp = null;
332
+
333
+ function animate(timestamp) {
334
+ if (!lastTimestamp) lastTimestamp = timestamp;
335
+ const deltaTime = timestamp - lastTimestamp;
336
+ lastTimestamp = timestamp;
337
+
338
+ // Move playhead
339
+ playheadPosition += deltaTime * 0.1; // Adjust speed as needed
340
+
341
+ if (playheadPosition > maxPosition) {
342
+ playheadPosition = maxPosition;
343
+ pause();
344
+ return;
345
+ }
346
+
347
+ // Pan timeline to keep playhead centered
348
+ const timelineRect = timelineContent.getBoundingClientRect();
349
+ const centerOffset = timelineRect.width / 2;
350
+ const translateX = centerOffset - playheadPosition;
351
+ timelineContent.style.transform = `scaleX(${zoomLevel}) translateX(${translateX}px)`;
352
+
353
+ // Update UI
354
+ playhead.style.left = `${playheadPosition}px`;
355
+ updateTimeDisplay();
356
+
357
+ if (isPlaying) {
358
+ animationId = requestAnimationFrame(animate);
359
+ }
360
+ }
361
+
362
+ animationId = requestAnimationFrame(animate);
363
+ }
364
+
365
+ // Pause animation
366
+ function pause() {
367
+ isPlaying = false;
368
+ cancelAnimationFrame(animationId);
369
+ playBtn.classList.remove('bg-gray-600');
370
+ playBtn.classList.add('bg-blue-600');
371
+ pauseBtn.classList.remove('bg-blue-600');
372
+ pauseBtn.classList.add('bg-gray-600');
373
+ }
374
+
375
+ // Stop animation
376
+ function stop() {
377
+ pause();
378
+ playheadPosition = 0;
379
+ playhead.style.left = '0px';
380
+ updateTimeDisplay();
381
+ const timelineRect = timelineContent.getBoundingClientRect();
382
+ const centerOffset = timelineRect.width / 2;
383
+ timelineContent.style.transform = `scaleX(${zoomLevel}) translateX(${centerOffset}px)`;
384
+ }
385
+
386
+ // Zoom functions
387
+ function zoomIn() {
388
+ if (zoomLevel < 4) {
389
+ zoomLevel += 0.25;
390
+ applyZoom();
391
+ }
392
+ }
393
+
394
+ function zoomOut() {
395
+ if (zoomLevel > 0.25) {
396
+ zoomLevel -= 0.25;
397
+ applyZoom();
398
+ }
399
+ }
400
+
401
+ function zoomReset() {
402
+ zoomLevel = 1;
403
+ applyZoom();
404
+ }
405
+
406
+ function applyZoom() {
407
+ timelineContainer.style.setProperty('--zoom-level', zoomLevel);
408
+ initRuler();
409
+
410
+ // Adjust playhead position to stay at same time after zoom
411
+ playhead.style.left = `${playheadPosition}px`;
412
+ }
413
+
414
+ // Scrubbing functionality
415
+ scrubArea.addEventListener('mousedown', function(e) {
416
+ isScrubbing = true;
417
+ const rect = timelineContent.getBoundingClientRect();
418
+ const x = e.clientX - rect.left;
419
+ playheadPosition = x * zoomLevel;
420
+ playhead.style.left = `${playheadPosition}px`;
421
+ updateTimeDisplay();
422
+
423
+ if (isPlaying) {
424
+ pause();
425
+ }
426
+ });
427
+
428
+ document.addEventListener('mousemove', function(e) {
429
+ if (isScrubbing) {
430
+ const rect = timelineContent.getBoundingClientRect();
431
+ const x = e.clientX - rect.left;
432
+ playheadPosition = Math.max(0, Math.min(x * zoomLevel, maxPosition));
433
+ playhead.style.left = `${playheadPosition}px`;
434
+ updateTimeDisplay();
435
+ }
436
+ });
437
+
438
+ document.addEventListener('mouseup', function() {
439
+ isScrubbing = false;
440
+ });
441
+
442
+ // Mouse wheel zoom
443
+ timelineContent.addEventListener('wheel', function(e) {
444
+ e.preventDefault();
445
+
446
+ if (e.ctrlKey) {
447
+ // Zoom in/out based on wheel direction
448
+ if (e.deltaY < 0) {
449
+ zoomIn();
450
+ } else {
451
+ zoomOut();
452
+ }
453
+ } else {
454
+ // Horizontal scrolling
455
+ const currentTransform = window.getComputedStyle(timelineContent).transform;
456
+ let currentTranslateX = 0;
457
+
458
+ if (currentTransform !== 'none') {
459
+ const matrix = new DOMMatrix(currentTransform);
460
+ currentTranslateX = matrix.m41;
461
+ }
462
+
463
+ timelineContent.style.transform = `scaleX(${zoomLevel}) translateX(${currentTranslateX + e.deltaY}px)`;
464
+ }
465
+ });
466
+
467
+ // Make clips draggable
468
+ clips.forEach(clip => {
469
+ let isDragging = false;
470
+ let offsetX = 0;
471
+
472
+ clip.addEventListener('mousedown', function(e) {
473
+ if (e.button !== 0) return; // Only left mouse button
474
+
475
+ isDragging = true;
476
+ const rect = clip.getBoundingClientRect();
477
+ offsetX = e.clientX - rect.left;
478
+
479
+ // Bring to front
480
+ clip.style.zIndex = '15';
481
+
482
+ e.stopPropagation(); // Prevent triggering scrub
483
+ });
484
+
485
+ document.addEventListener('mousemove', function(e) {
486
+ if (isDragging) {
487
+ const rect = timelineContent.getBoundingClientRect();
488
+ const x = (e.clientX - rect.left) / zoomLevel - offsetX;
489
+
490
+ // Constrain to timeline bounds
491
+ const minX = 0;
492
+ const maxX = timelineContent.offsetWidth - clip.offsetWidth;
493
+
494
+ clip.style.left = `${Math.max(minX, Math.min(x, maxX))}px`;
495
+ }
496
+ });
497
+
498
+ document.addEventListener('mouseup', function() {
499
+ isDragging = false;
500
+ clip.style.zIndex = '';
501
+ });
502
+ });
503
+
504
+ // Event listeners
505
+ playBtn.addEventListener('click', play);
506
+ pauseBtn.addEventListener('click', pause);
507
+ stopBtn.addEventListener('click', stop);
508
+ zoomInBtn.addEventListener('click', zoomIn);
509
+ zoomOutBtn.addEventListener('click', zoomOut);
510
+ zoomResetBtn.addEventListener('click', zoomReset);
511
+
512
+ // Generate random waveform data
513
+ function generateWaveform() {
514
+ const waveform = document.getElementById('waveform');
515
+ waveform.innerHTML = '';
516
+
517
+ const barCount = 500; // Number of waveform bars
518
+
519
+ for (let i = 0; i < barCount; i++) {
520
+ const bar = document.createElement('div');
521
+ bar.className = 'waveform-bar';
522
+ const height = Math.random() * 80 + 20;
523
+ bar.style.height = `${height}%`;
524
+ waveform.appendChild(bar);
525
+ }
526
+ }
527
+
528
+ // Initialize
529
+ initRuler();
530
+ updateTimeDisplay();
531
+ generateWaveform();
532
+ });
533
+ </script>
534
+ <p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=yhzion/auto-scroll-timeline" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
535
+ </html>