soiz1 commited on
Commit
ddace5e
·
1 Parent(s): ba501e2

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +578 -19
index.html CHANGED
@@ -1,19 +1,578 @@
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="ja">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>音声合成プレイヤー</title>
7
+ <style>
8
+ body {
9
+ font-family: 'Arial', sans-serif;
10
+ background-color: #0a192f;
11
+ color: #e6f1ff;
12
+ margin: 0;
13
+ padding: 20px;
14
+ display: flex;
15
+ flex-direction: column;
16
+ align-items: center;
17
+ }
18
+
19
+ h1 {
20
+ color: #64ffda;
21
+ text-align: center;
22
+ margin-bottom: 30px;
23
+ font-size: 2.5em;
24
+ text-shadow: 0 0 10px rgba(100, 255, 218, 0.3);
25
+ }
26
+
27
+ .container {
28
+ display: flex;
29
+ width: 100%;
30
+ max-width: 1200px;
31
+ gap: 20px;
32
+ }
33
+
34
+ .left-panel, .right-panel {
35
+ flex: 1;
36
+ background-color: #112240;
37
+ border-radius: 10px;
38
+ padding: 20px;
39
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
40
+ }
41
+
42
+ .drop-box {
43
+ border: 2px dashed #64ffda;
44
+ border-radius: 8px;
45
+ padding: 20px;
46
+ min-height: 150px;
47
+ margin-bottom: 20px;
48
+ transition: all 0.3s;
49
+ background-color: rgba(100, 255, 218, 0.05);
50
+ }
51
+
52
+ .drop-box.highlight {
53
+ background-color: rgba(100, 255, 218, 0.1);
54
+ border-color: #64ffda;
55
+ box-shadow: 0 0 15px rgba(100, 255, 218, 0.2);
56
+ }
57
+
58
+ .sound-box {
59
+ display: inline-block;
60
+ background-color: #233554;
61
+ color: #e6f1ff;
62
+ padding: 8px 15px;
63
+ margin: 5px;
64
+ border-radius: 20px;
65
+ border: 1px solid #64ffda;
66
+ cursor: move;
67
+ user-select: none;
68
+ transition: all 0.2s;
69
+ }
70
+
71
+ .sound-box:hover {
72
+ background-color: #1e2a47;
73
+ transform: translateY(-2px);
74
+ }
75
+
76
+ .sound-box.selected {
77
+ background-color: #64ffda;
78
+ color: #0a192f;
79
+ font-weight: bold;
80
+ }
81
+
82
+ .video-container {
83
+ position: relative;
84
+ width: 100%;
85
+ margin-bottom: 20px;
86
+ }
87
+
88
+ video {
89
+ width: 100%;
90
+ border-radius: 8px;
91
+ box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
92
+ }
93
+
94
+ .controls {
95
+ display: flex;
96
+ justify-content: center;
97
+ gap: 15px;
98
+ margin-bottom: 20px;
99
+ }
100
+
101
+ button {
102
+ background-color: #233554;
103
+ color: #e6f1ff;
104
+ border: 1px solid #64ffda;
105
+ border-radius: 5px;
106
+ padding: 10px 20px;
107
+ cursor: pointer;
108
+ transition: all 0.3s;
109
+ font-size: 1em;
110
+ }
111
+
112
+ button:hover {
113
+ background-color: #64ffda;
114
+ color: #0a192f;
115
+ transform: translateY(-2px);
116
+ box-shadow: 0 5px 15px rgba(100, 255, 218, 0.3);
117
+ }
118
+
119
+ .settings {
120
+ background-color: #112240;
121
+ border-radius: 10px;
122
+ padding: 20px;
123
+ margin-top: 20px;
124
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
125
+ width: 100%;
126
+ max-width: 1200px;
127
+ }
128
+
129
+ .settings h2 {
130
+ color: #64ffda;
131
+ margin-top: 0;
132
+ border-bottom: 1px solid #233554;
133
+ padding-bottom: 10px;
134
+ }
135
+
136
+ .setting-group {
137
+ display: flex;
138
+ flex-wrap: wrap;
139
+ gap: 20px;
140
+ margin-bottom: 15px;
141
+ }
142
+
143
+ .setting-item {
144
+ flex: 1;
145
+ min-width: 200px;
146
+ }
147
+
148
+ label {
149
+ display: block;
150
+ margin-bottom: 5px;
151
+ color: #ccd6f6;
152
+ }
153
+
154
+ input[type="range"], input[type="number"] {
155
+ width: 100%;
156
+ background-color: #233554;
157
+ border: 1px solid #1e2a47;
158
+ border-radius: 5px;
159
+ padding: 8px;
160
+ color: #e6f1ff;
161
+ }
162
+
163
+ input[type="range"] {
164
+ -webkit-appearance: none;
165
+ height: 5px;
166
+ background: #233554;
167
+ border-radius: 5px;
168
+ }
169
+
170
+ input[type="range"]::-webkit-slider-thumb {
171
+ -webkit-appearance: none;
172
+ width: 15px;
173
+ height: 15px;
174
+ background: #64ffda;
175
+ border-radius: 50%;
176
+ cursor: pointer;
177
+ }
178
+
179
+ .status {
180
+ margin-top: 20px;
181
+ padding: 10px;
182
+ background-color: rgba(100, 255, 218, 0.1);
183
+ border-left: 3px solid #64ffda;
184
+ border-radius: 0 5px 5px 0;
185
+ }
186
+
187
+ .tech-decoration {
188
+ position: absolute;
189
+ width: 100%;
190
+ height: 100%;
191
+ top: 0;
192
+ left: 0;
193
+ pointer-events: none;
194
+ z-index: -1;
195
+ opacity: 0.1;
196
+ background:
197
+ linear-gradient(90deg, #112240 1px, transparent 1px) 0 0 / 20px 20px,
198
+ linear-gradient(#112240 1px, transparent 1px) 0 0 / 20px 20px;
199
+ }
200
+ </style>
201
+ </head>
202
+ <body>
203
+ <div class="tech-decoration"></div>
204
+ <h1>音声合成プレイヤー</h1>
205
+
206
+ <div class="container">
207
+ <div class="left-panel">
208
+ <h2>音声アセット</h2>
209
+ <div id="sound-assets">
210
+ <div class="sound-box" draggable="true" data-sound="p.mp3">p.mp3</div>
211
+ <div class="sound-box" draggable="true" data-sound="a.mp3">a.mp3</div>
212
+ <div class="sound-box" draggable="true" data-sound="t.mp3">t.mp3</div>
213
+ <div class="sound-box" draggable="true" data-sound="s.mp3">s.mp3</div>
214
+ </div>
215
+
216
+ <h2>ドロップボックス</h2>
217
+ <div id="drop-box" class="drop-box">
218
+ <p>音声ファイルをここにドラッグしてください</p>
219
+ </div>
220
+ </div>
221
+
222
+ <div class="right-panel">
223
+ <h2>プレビュー</h2>
224
+ <div class="video-container">
225
+ <video id="video" controls>
226
+ <source src="v.mp4" type="video/mp4">
227
+ お使いのブラウザはビデオタグをサポートしていません。
228
+ </video>
229
+ </div>
230
+
231
+ <div class="controls">
232
+ <button id="play-btn">再生</button>
233
+ <button id="pause-btn">一時停止</button>
234
+ <button id="stop-btn">停止</button>
235
+ <button id="reset-btn">リセット</button>
236
+ </div>
237
+
238
+ <div class="status" id="status">
239
+ 準備ができました。音声をドロップボックスに追加してください。
240
+ </div>
241
+ </div>
242
+ </div>
243
+
244
+ <div class="settings">
245
+ <h2>設定</h2>
246
+ <div class="setting-group">
247
+ <div class="setting-item">
248
+ <label for="start-time">再生開始秒数 (秒)</label>
249
+ <input type="number" id="start-time" min="0" value="0" step="0.1">
250
+ </div>
251
+ <div class="setting-item">
252
+ <label for="end-time">再生終了秒数 (秒)</label>
253
+ <input type="number" id="end-time" min="0" step="0.1">
254
+ </div>
255
+ </div>
256
+
257
+ <div class="setting-group">
258
+ <div class="setting-item">
259
+ <label for="volume">音量 (0-3)</label>
260
+ <input type="range" id="volume" min="0" max="3" step="0.1" value="1">
261
+ <span id="volume-value">1</span>
262
+ </div>
263
+ <div class="setting-item">
264
+ <label for="playback-rate">再生速度 (0.5-2)</label>
265
+ <input type="range" id="playback-rate" min="0.5" max="2" step="0.1" value="1">
266
+ <span id="playback-rate-value">1</span>
267
+ </div>
268
+ </div>
269
+
270
+ <div class="setting-item">
271
+ <label>
272
+ <input type="checkbox" id="loop-checkbox">
273
+ ループ再生
274
+ </label>
275
+ </div>
276
+ </div>
277
+
278
+ <script>
279
+ document.addEventListener('DOMContentLoaded', function() {
280
+ // 要素を取得
281
+ const soundAssets = document.getElementById('sound-assets');
282
+ const dropBox = document.getElementById('drop-box');
283
+ const video = document.getElementById('video');
284
+ const playBtn = document.getElementById('play-btn');
285
+ const pauseBtn = document.getElementById('pause-btn');
286
+ const stopBtn = document.getElementById('stop-btn');
287
+ const resetBtn = document.getElementById('reset-btn');
288
+ const statusDiv = document.getElementById('status');
289
+
290
+ // 設定要素
291
+ const startTimeInput = document.getElementById('start-time');
292
+ const endTimeInput = document.getElementById('end-time');
293
+ const volumeInput = document.getElementById('volume');
294
+ const volumeValue = document.getElementById('volume-value');
295
+ const playbackRateInput = document.getElementById('playback-rate');
296
+ const playbackRateValue = document.getElementById('playback-rate-value');
297
+ const loopCheckbox = document.getElementById('loop-checkbox');
298
+
299
+ // 音声コンテキストとノード
300
+ let audioContext;
301
+ let audioBuffers = {};
302
+ let soundSources = [];
303
+ let videoDuration = 0;
304
+
305
+ // 初期化
306
+ init();
307
+
308
+ async function init() {
309
+ try {
310
+ // 動画の長さを取得
311
+ video.addEventListener('loadedmetadata', function() {
312
+ videoDuration = video.duration;
313
+ endTimeInput.value = videoDuration.toFixed(1);
314
+ endTimeInput.max = videoDuration;
315
+ });
316
+
317
+ // 音声コンテキストを初期化
318
+ audioContext = new (window.AudioContext || window.webkitAudioContext)();
319
+
320
+ // 既存の音声アセットを事前ロード
321
+ const soundElements = soundAssets.querySelectorAll('.sound-box');
322
+ for (const element of soundElements) {
323
+ const soundFile = element.getAttribute('data-sound');
324
+ await loadSound(soundFile);
325
+ }
326
+
327
+ // イベントリスナーを設定
328
+ setupEventListeners();
329
+
330
+ statusDiv.textContent = "準備ができました。音声をドロップボックスに追加してください。";
331
+ } catch (error) {
332
+ console.error("初期化エラー:", error);
333
+ statusDiv.textContent = "初期化中にエラーが発生しました: " + error.message;
334
+ }
335
+ }
336
+
337
+ function setupEventListeners() {
338
+ // ドラッグ&ドロップイベント
339
+ dropBox.addEventListener('dragover', function(e) {
340
+ e.preventDefault();
341
+ dropBox.classList.add('highlight');
342
+ });
343
+
344
+ dropBox.addEventListener('dragleave', function() {
345
+ dropBox.classList.remove('highlight');
346
+ });
347
+
348
+ dropBox.addEventListener('drop', function(e) {
349
+ e.preventDefault();
350
+ dropBox.classList.remove('highlight');
351
+
352
+ const soundFile = e.dataTransfer.getData('text/plain');
353
+ if (soundFile && soundFile.endsWith('.mp3')) {
354
+ addSoundToDropBox(soundFile);
355
+ }
356
+ });
357
+
358
+ // 音声アセットのドラッグ開始
359
+ soundAssets.querySelectorAll('.sound-box').forEach(box => {
360
+ box.addEventListener('dragstart', function(e) {
361
+ e.dataTransfer.setData('text/plain', this.getAttribute('data-sound'));
362
+ });
363
+ });
364
+
365
+ // コントロールボタン
366
+ playBtn.addEventListener('click', playAll);
367
+ pauseBtn.addEventListener('click', pauseAll);
368
+ stopBtn.addEventListener('click', stopAll);
369
+ resetBtn.addEventListener('click', resetAll);
370
+
371
+ // 設定変更イベント
372
+ volumeInput.addEventListener('input', function() {
373
+ volumeValue.textContent = this.value;
374
+ });
375
+
376
+ playbackRateInput.addEventListener('input', function() {
377
+ playbackRateValue.textContent = this.value;
378
+ video.playbackRate = this.value;
379
+ });
380
+
381
+ // 動画のループ処理
382
+ video.addEventListener('timeupdate', function() {
383
+ const startTime = parseFloat(startTimeInput.value) || 0;
384
+ const endTime = parseFloat(endTimeInput.value) || videoDuration;
385
+
386
+ if (loopCheckbox.checked && video.currentTime >= endTime) {
387
+ video.currentTime = startTime;
388
+ restartAudio(startTime);
389
+ }
390
+ });
391
+ }
392
+
393
+ async function loadSound(soundFile) {
394
+ if (audioBuffers[soundFile]) return; // 既にロード済み
395
+
396
+ try {
397
+ const response = await fetch(soundFile);
398
+ const arrayBuffer = await response.arrayBuffer();
399
+ audioBuffers[soundFile] = await audioContext.decodeAudioData(arrayBuffer);
400
+ } catch (error) {
401
+ console.error(`音声ファイルのロードエラー (${soundFile}):`, error);
402
+ throw error;
403
+ }
404
+ }
405
+
406
+ function addSoundToDropBox(soundFile) {
407
+ // 既に追加されているかチェック
408
+ if (dropBox.querySelector(`.sound-box[data-sound="${soundFile}"]`)) {
409
+ statusDiv.textContent = `"${soundFile}" は既に追加されています。`;
410
+ return;
411
+ }
412
+
413
+ // 新しい音声ボックスを作成
414
+ const soundBox = document.createElement('div');
415
+ soundBox.className = 'sound-box';
416
+ soundBox.setAttribute('data-sound', soundFile);
417
+ soundBox.textContent = soundFile;
418
+ soundBox.draggable = true;
419
+
420
+ // ドラッグ開始イベント
421
+ soundBox.addEventListener('dragstart', function(e) {
422
+ e.dataTransfer.setData('text/plain', this.getAttribute('data-sound'));
423
+ e.dataTransfer.effectAllowed = 'move';
424
+ });
425
+
426
+ // ドロップボックス内でのドラッグオーバー
427
+ soundBox.addEventListener('dragover', function(e) {
428
+ e.preventDefault();
429
+ e.dataTransfer.dropEffect = 'move';
430
+ });
431
+
432
+ // ドロップボックス内でのドロップ(並べ替え)
433
+ soundBox.addEventListener('drop', function(e) {
434
+ e.preventDefault();
435
+ const draggedSound = e.dataTransfer.getData('text/plain');
436
+ const draggedElement = dropBox.querySelector(`.sound-box[data-sound="${draggedSound}"]`);
437
+
438
+ if (draggedElement && draggedElement !== this) {
439
+ const dropY = e.clientY;
440
+ const thisY = this.getBoundingClientRect().top;
441
+
442
+ if (dropY < thisY) {
443
+ dropBox.insertBefore(draggedElement, this);
444
+ } else {
445
+ dropBox.insertBefore(draggedElement, this.nextSibling);
446
+ }
447
+ }
448
+ });
449
+
450
+ // ダブルクリックで削除
451
+ soundBox.addEventListener('dblclick', function() {
452
+ this.remove();
453
+ statusDiv.textContent = `"${soundFile}" を削除しました。`;
454
+ });
455
+
456
+ dropBox.appendChild(soundBox);
457
+ statusDiv.textContent = `"${soundFile}" を追加しました。`;
458
+ }
459
+
460
+ function playAll() {
461
+ const startTime = parseFloat(startTimeInput.value) || 0;
462
+ const endTime = parseFloat(endTimeInput.value) || videoDuration;
463
+ const volume = parseFloat(volumeInput.value) || 1;
464
+ const playbackRate = parseFloat(playbackRateInput.value) || 1;
465
+
466
+ // 動画を再生
467
+ video.currentTime = startTime;
468
+ video.playbackRate = playbackRate;
469
+ video.play().catch(e => console.error("動画再生エラー:", e));
470
+
471
+ // 音声を再生
472
+ stopAudio(); // 既存の音声を停止
473
+
474
+ const soundBoxes = dropBox.querySelectorAll('.sound-box');
475
+ if (soundBoxes.length === 0) {
476
+ statusDiv.textContent = "再生中 (音声なし)";
477
+ return;
478
+ }
479
+
480
+ let playTime = audioContext.currentTime;
481
+
482
+ soundBoxes.forEach(box => {
483
+ const soundFile = box.getAttribute('data-sound');
484
+ const audioBuffer = audioBuffers[soundFile];
485
+
486
+ if (audioBuffer) {
487
+ const source = audioContext.createBufferSource();
488
+ const gainNode = audioContext.createGain();
489
+
490
+ source.buffer = audioBuffer;
491
+ source.playbackRate.value = playbackRate;
492
+ gainNode.gain.value = volume;
493
+
494
+ source.connect(gainNode);
495
+ gainNode.connect(audioContext.destination);
496
+
497
+ source.start(playTime, startTime, endTime - startTime);
498
+
499
+ soundSources.push({
500
+ source: source,
501
+ gain: gainNode
502
+ });
503
+ }
504
+ });
505
+
506
+ statusDiv.textContent = `再生中 (${startTime.toFixed(1)}秒~${endTime.toFixed(1)}秒)`;
507
+ }
508
+
509
+ function pauseAll() {
510
+ video.pause();
511
+ statusDiv.textContent = "一時停止中";
512
+ }
513
+
514
+ function stopAll() {
515
+ video.pause();
516
+ video.currentTime = parseFloat(startTimeInput.value) || 0;
517
+ stopAudio();
518
+ statusDiv.textContent = "停止中";
519
+ }
520
+
521
+ function resetAll() {
522
+ video.pause();
523
+ video.currentTime = 0;
524
+ stopAudio();
525
+ statusDiv.textContent = "リセットしました";
526
+ }
527
+
528
+ function stopAudio() {
529
+ soundSources.forEach(source => {
530
+ try {
531
+ source.source.stop();
532
+ } catch (e) {
533
+ console.error("音声停止エラー:", e);
534
+ }
535
+ });
536
+ soundSources = [];
537
+ }
538
+
539
+ function restartAudio(startTime) {
540
+ if (soundSources.length === 0) return;
541
+
542
+ stopAudio();
543
+
544
+ const volume = parseFloat(volumeInput.value) || 1;
545
+ const playbackRate = parseFloat(playbackRateInput.value) || 1;
546
+ const endTime = parseFloat(endTimeInput.value) || videoDuration;
547
+
548
+ let playTime = audioContext.currentTime;
549
+
550
+ const soundBoxes = dropBox.querySelectorAll('.sound-box');
551
+ soundBoxes.forEach(box => {
552
+ const soundFile = box.getAttribute('data-sound');
553
+ const audioBuffer = audioBuffers[soundFile];
554
+
555
+ if (audioBuffer) {
556
+ const source = audioContext.createBufferSource();
557
+ const gainNode = audioContext.createGain();
558
+
559
+ source.buffer = audioBuffer;
560
+ source.playbackRate.value = playbackRate;
561
+ gainNode.gain.value = volume;
562
+
563
+ source.connect(gainNode);
564
+ gainNode.connect(audioContext.destination);
565
+
566
+ source.start(playTime, startTime, endTime - startTime);
567
+
568
+ soundSources.push({
569
+ source: source,
570
+ gain: gainNode
571
+ });
572
+ }
573
+ });
574
+ }
575
+ });
576
+ </script>
577
+ </body>
578
+ </html>