관리자 페이지의 UX 를 더 개선해보자!
개선할 것
- 동적 인터페이스:
Download Subtitle
버튼을 누르면
Downloading...
표시 ✔로딩 애니메이션
표시 ✔- 제목을 눌러서 새로고침 ✔
- 영상 제목 위에 영상 썸네일 이미지도 작게 추가하기 ✔
- 다운로드가 완료되면
- 알아서
이미 MongoDB에 저장된 Subtitle 에 대하여 다운로드 버튼 비활성화
✔- Downloaded 버튼을 누르면, 새로운 화면에서 "[타임스탬프] 텍스트" 형식으로 모든 자막을 보여준다.
자바스크립트를 사용하여 Download Subtitle
버튼 클릭 시 AJAX 요청을 서버로 보내고, 응답을 받은 후 페이지를 새로고침합니다.
로딩 애니메이션 추가
<div>
요소를 생성하여 로딩 애니메이션(스피너)을 나타내는 'loader-downloading' 클래스를 적용합니다.서버 요청 (Fetch API 사용)
fetch
함수를 사용하여 서버에 자막 파일 다운로드 요청을 보냅니다./download/${channelId}/${videoId}
형식으로 구성됩니다.응답 처리
window.location.reload()
).// 자막 다운로드 함수
function downloadSubtitle(channelId, videoId, button) {
// 버튼 비활성화 및 스타일 변경
button.disabled = true;
button.className = 'downloading-button';
button.innerHTML = 'Downloading...';
// 로딩 애니메이션 요소 생성 및 추가
var loader = document.createElement('div');
loader.className = 'loader-downloading';
button.parentNode.insertBefore(loader, button.nextSibling);
fetch(`/download/${channelId}/${videoId}`)
.then(response => {
// 다운로드 후 처리
// 페이지 새로고침
window.location.reload();
})
.catch(error => {
console.error('Error:', error);
button.disabled = false;
button.className = 'download-link';
button.innerHTML = 'Download Subtitle';
loader.remove();
});
}
HTML에 해당 자바스크립트 함수를 호출하는 버튼 링크를 추가합니다. onclick
속성을 통해 버튼 클릭 시 downloadSubtitle
함수를 호출합니다.
<button onclick="downloadSubtitle('{{ data.channel_id }}', '{{ video.video_id }}', this)" class="download-link">Download Subtitle</button>
Downloading... 과 로딩 애니메이션의 CSS style 은 다음과 같습니다.
/* Downloading... */
.downloading-button {
display: inline-block;
padding: 10px 15px;
margin-top: 10px;
background-color: #90EE90; /* 초록 배경 */
color: white;
text-decoration: none;
border-radius: 5px;
border: none;
transition: background-color 0.3s, transform 0.3s;
cursor: pointer;
}
.loader-downloading {
margin: auto; /* 가운데 정렬 */
border: 5px solid #f3f3f3; /* Light grey */
border-top: 5px solid #90EE90; /* Blue */
border-radius: 50%;
width: 15px;
height: 15px;
animation: spin 2s linear infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
Get Whisper Subtitle by Channel ID 을 누르면 모든 정보가 초기화되고 첫 주소로 돌아가도록 하고 싶었습니다.
html 수정을 아래와 같이,
<h1 onclick="redirectToHome()" style="cursor: pointer;">Get Whisper Subtitle by Channel ID</h1>
script.js 수정을 아래와 같이 했습니다.
// 새로고침
function redirectToHome() {
window.location.href = '/'; // 홈페이지 URL로 리다이렉트
}
영상의 썸네일 이미지를 구하는 법은 생각보다 간단합니다.
# 비디오 정보에서 썸네일 URL 추출 및 출력
for video in video_info:
video_id = video['video_id']
thumbnail_url = f'https://img.youtube.com/vi/{video_id}/maxresdefault.jpg'
print(f"비디오 ID: {video_id}, 썸네일 URL: {thumbnail_url}")
# 이 URL을 웹페이지에서 사용할 수 있습니다.
# 예: <img src="{thumbnail_url}" alt="썸네일">
유튜브 썸네일은 5가지의 해상도로 제공됩니다.
기본 (Default) 해상도
https://img.youtube.com/vi/[VideoID]/default.jpg
중간 (Medium) 해상도
https://img.youtube.com/vi/[VideoID]/mqdefault.jpg
고화질 (High) 해상도
https://img.youtube.com/vi/[VideoID]/hqdefault.jpg
표준 정의 (Standard Definition) 해상도
https://img.youtube.com/vi/[VideoID]/sddefault.jpg
최대 해상도 (Max Resolution) 해상도
https://img.youtube.com/vi/[VideoID]/maxresdefault.jpg
저희 프로젝트에선 간단히 기본 해상도를 사용하기로 했습니다.
그래서 이렇게 수정해줬습니다.
{% if data.videos %}
<div class="grid-container">
{% for video in data.videos %}
<div class="card">
<!-- 썸네일 이미지 추가 -->
{% set thumbnail_url = 'https://img.youtube.com/vi/' ~ video.video_id ~ '/default.jpg' %}
<img src="{{ thumbnail_url }}" alt="{{ video.title }}">
<!-- 비디오 제목 링크 -->
<a href="{{ video.link }}" class="video-link">{{ video.title }}</a>
<p class="published-date">{{ video.published_at }}</p><!--업로드 날짜 표시-->
<!-- transcription 존재 여부에 따라 버튼 상태와 메시지를 변경합니다. -->
{% if video.transcription_exists %}
<button class="exists-in-db" disabled>Downloaded</button>
{% else %}
<button onclick="downloadSubtitle('{{ data.channel_id }}', '{{ video.video_id }}', this)" class="download-link">Download Subtitle</button>
{% endif %}
<!--mongodb 버전이었다. 제목을 누르면 바로 다운로드가 되는 코드-->
<!--<a href="/download/{{ video.id }}">{{ video.title }}</a>-->
</div>
썸네일이 보이니 영상을 더 구별하기 쉬워졌습니다. ㅎㅎ
라우트를 만들어줬습니다.
@app.route('/view-transcription/<channel_id>/<video_id>')
def view_transcription(channel_id, video_id):
transcription = videos_db_query.find_mongodb_trans(channel_id, video_id)
if transcription is None:
return "Transcription not found", 404
return render_template('transcription.html', transcription=transcription, video_id=video_id)
transcription.html 은 아래와 같습니다. "[분:초] 텍스트" 형식으로 나오도록 곧 바꿔줘야겠습니다.
<!DOCTYPE html>
<html>
<head>
<title>View Transcription</title>
</head>
<body>
{% if transcription.segments %}
<ul>
{% for segment in transcription.segments %}
<li>
<strong>Time:</strong> {{ segment.start }}s
<strong>Text:</strong> {{ segment.text }}
</li>
{% endfor %}
</ul>
{% else %}
<p>No transcription segments found.</p>
{% endif %}
<a href="/">Back to Home</a>
</body>
</html>
index.html 은 아래와 같이 변경해주었습니다.
{% if video.transcription_exists %}
<!--<button class="exists-in-db" disabled>Downloaded</button>-->
<button onclick="viewDownload('{{ data.channel_id }}', '{{ video.video_id }}')" class="view-downloaded">View Downloaded</button>
{% else %}
아래의 코드는 이에 따른 js, css 입니다.
// 다운로드한 자막 펼쳐보기
function viewDownload(channelId, videoId) {
console.log(`Viewing transcription for Channel ID: ${channelId}, Video ID: ${videoId}`);
window.location.href = `/view-transcription/${channelId}/${videoId}`;
}
/* View Downloaded 이미 다운로드된 자막 펼쳐보기 */
.view-downloaded {
display: inline-block;
padding: 10px 15px;
margin-top: 10px;
background-color: #90EE90; /* 밝은 초록 */
color: white;
text-decoration: none;
border-radius: 5px;
border: none;
transition: background-color 0.3s, transform 0.3s;
}
.view-downloaded:hover {
background-color: #66de66; /* 진한 초록 */
transform: translateY(-2px);
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
}
이제, 유튜브의 형식대로 [분:초] 형식으로 타임스탬프를 수정해주겠습니다.
# 자막 정보의 타임스탬프를 [초] 형식에서 [분:초] 형식으로 변환합니다.
def format_seconds_to_min_sec(seconds):
minutes = int(seconds // 60)
seconds = int(seconds % 60)
return f"{minutes:02d}:{seconds:02d}"
@app.route('/view-transcription/<channel_id>/<video_id>')
def view_transcription(channel_id, video_id):
transcription = videos_db_query.find_mongodb_trans(channel_id, video_id)
if transcription is None:
return "Transcription not found", 404
# 각 segment 의 시간을 분:초 형식으로 변환한다.
for segment in transcription['segments']:
segment['formatted_start'] = youtube_data.format_seconds_to_min_sec(segment['start'])
return render_template('transcription.html', transcription=transcription, video_id=video_id)
그리고 transcription.html 을 아래와 같이 변경해줍니다.
<!DOCTYPE html>
<html>
<head>
<title>View Transcription</title>
</head>
<body>
{% if transcription.segments %}
<ul>
{% for segment in transcription.segments %}
<li>
[{{ segment.formatted_start }}] {{ segment.text }}
</li>
{% endfor %}
</ul>
{% else %}
<p>No transcription segments found.</p>
{% endif %}
<a href="/">Back to Home</a>
</body>
</html>