Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: show video duration #1628

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,7 @@ model Video {
url String
published_at DateTime
thumbnail String
duration String @default("00:00")
slug String @unique
playlists PlaylistOnVideo[]
shows ShowVideo[]
Expand Down
29 changes: 25 additions & 4 deletions src/lib/videos/PlaylistVideo.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,12 @@

<div>
<a href={`/videos/${playlist.slug}/${video.slug}`}>
<img src={video.thumbnail} class="thumbnail" alt={video.title} />
<div class="thumbnail-wrapper">
<img src={video.thumbnail} class="thumbnail" alt={video.title} />
{#if video.duration}
<span class="duration">{video.duration}</span>
{/if}
</div>
<h3 class="h6">{video.title}</h3>
</a>
</div>
Expand All @@ -23,9 +28,25 @@
margin-top: 0;
}

.thumbnail {
overflow: hidden;
border-radius: var(--brad);

.thumbnail-wrapper {
position: relative;

.thumbnail {
overflow: hidden;
border-radius: var(--brad);
}
.duration {
position: absolute;
display: inline-block;
background: var(--yellow);
color: var(--black);
border-radius: 1rem;
padding: 0.25rem;
right: 1rem;
top: 1rem;
font-weight: 600;
}
}

img {
Expand Down
16 changes: 16 additions & 0 deletions src/lib/videos/parseDuration.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// Match one part of the duration string at a time and pad with 0
// Avoids a complex / hard to understand regex for matching all parts at once
function parseTime(duration: string, part: 'H' | 'M' | 'S') {
const match = duration.match(new RegExp(`(\\d+)${part}`));
return match ? match[1].padStart(1, '0') : '00';
}

// Parses duration from YouTube duration format (ISO 8601) e.g. PT1H14M28S to 01:14:28
export default function parseDuration(duration: string) {
const hours = parseTime(duration, 'H');
const minutes = parseTime(duration, 'M');
const seconds = parseTime(duration, 'S');

const result = `${minutes}:${seconds}`;
return hours !== '00' ? `${hours}:${duration}` : result;
}
13 changes: 3 additions & 10 deletions src/routes/(site)/videos/[p_slug]/+page.svelte
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
<script lang="ts">
import PlaylistVideo from "$/lib/videos/PlaylistVideo.svelte";

export let data;
$: ({ playlist } = data);
</script>
Expand All @@ -7,21 +9,12 @@
<h1 class="h3">{playlist.title}</h1>
<div class="playlist-grid grid">
{#each playlist.videos as { video }}
<a href={`/videos/${playlist.slug}/${video.slug}`}>
<img src={video.thumbnail} class="thumbnail" alt={video.title} />
<h3 class="h6">{video.title}</h3>
</a>
<PlaylistVideo {playlist} {video} />
{/each}
</div>
{/if}

<style lang="postcss">
img {
width: 100%;
}
.h6 {
margin-bottom: 0;
}
.playlist-grid {
display: grid;
grid-gap: 20px;
Expand Down
30 changes: 15 additions & 15 deletions src/server/video/youtube_api.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { YOUTUBE_CHANNEL_ID } from '$/const';
import { prisma_client } from '$/hooks.server';
import parseDuration from '$/lib/videos/parseDuration';
import { YOUTUBE_API_KEY } from '$env/static/private';
import type { Video } from '@prisma/client';
import slug from 'speakingurl';

// Youtube importer
Expand Down Expand Up @@ -147,7 +149,7 @@ export async function import_playlist(playlist_id: string) {

// Fetch video details using the videos endpoint
const videos_response = await fetch(
`https://www.googleapis.com/youtube/v3/videos?part=snippet,status&id=${video_ids.join(',')}&key=${process.env.YOUTUBE_API_KEY}`
`https://www.googleapis.com/youtube/v3/videos?part=snippet,status,contentDetails&id=${video_ids.join(',')}&key=${process.env.YOUTUBE_API_KEY}`
);
const videos_data = await videos_response.json();

Expand All @@ -159,24 +161,22 @@ export async function import_playlist(playlist_id: string) {
}

try {
const duration = parseDuration(item.contentDetails.duration);
const data: Omit<Video, 'id'> = {
title: item.snippet.title,
slug: slug(item.snippet.title),
description: item.snippet.description,
duration,
url: `https://www.youtube.com/watch?v=${item.id}`,
published_at: new Date(item.snippet.publishedAt),
thumbnail: item.snippet.thumbnails.maxres.url
};
const video = await prisma_client.video.upsert({
where: { id: item.id },
update: {
title: item.snippet.title,
slug: slug(item.snippet.title),
description: item.snippet.description,
url: `https://www.youtube.com/watch?v=${item.id}`,
published_at: new Date(item.snippet.publishedAt),
thumbnail: item.snippet.thumbnails.maxres.url
},
update: data,
create: {
id: item.id,
title: item.snippet.title,
slug: slug(item.snippet.title),
description: item.snippet.description,
url: `https://www.youtube.com/watch?v=${item.id}`,
published_at: new Date(item.snippet.publishedAt),
thumbnail: item.snippet.thumbnails.maxres.url
...data
}
});

Expand Down
Loading