Skip to content

Instantly share code, notes, and snippets.

@jaredcat
Last active November 26, 2024 23:01
Show Gist options
  • Save jaredcat/a6623bd6f7137e28c03df533b3bbafe7 to your computer and use it in GitHub Desktop.
Save jaredcat/a6623bd6f7137e28c03df533b3bbafe7 to your computer and use it in GitHub Desktop.
YouTube Playlist TVDB Data
// USE WITH https://greasyfork.org/en/scripts/518978-tvdb-episode-input-automation
const PLAYLIST_ID = 'ENTER A PLAYLIST ID HERE';
async function getPlaylistVideos(playlistUrl) {
// Get playlist page
const response = await fetch(playlistUrl);
const html = await response.text();
// Extract video IDs from playlist page
const videoIds = [
...new Set([...html.matchAll(/"videoId":"([^"]+)"/g)].map((m) => m[1])),
];
const videos = [];
let episodeCount = 0;
for (const videoId of videoIds) {
episodeCount++;
try {
const videoData = await getVideoDetails(
`https://www.youtube.com/watch?v=${videoId}`,
episodeCount,
);
videos.push(videoData);
// Respect YouTube's rate limiting
await new Promise((resolve) => setTimeout(resolve, 1000));
} catch (error) {
console.error(`Error processing video ${videoId}:`, error);
}
}
return videos;
}
async function getVideoDetails(videoUrl, fallbackEpisodeNumber) {
const response = await fetch(videoUrl);
const html = await response.text();
// Extract data from ytInitialData
const dataMatch = html.match(/ytInitialData\s*=\s*({.+?});/);
const playerDataMatch = html.match(/ytInitialPlayerResponse\s*=\s*({.+?});/);
if (!dataMatch || !playerDataMatch) {
throw new Error('Could not extract video data');
}
const data = JSON.parse(dataMatch[1]);
const playerData = JSON.parse(playerDataMatch[1]);
// Get video details
const videoDetails = playerData.videoDetails;
const primaryInfo =
data.contents?.twoColumnWatchNextResults?.results?.results?.contents?.[0]
?.videoPrimaryInfoRenderer;
// Get episode number from title
const episodeMatch = videoDetails.title.match(/\[(\d+)\]/);
let episode = fallbackEpisodeNumber;
if (!episodeMatch) {
console.warn(
`Could not extract episode number in: ${videoDetails.title}\n\t Falling back to: ${fallbackEpisodeNumber}`,
);
} else {
episode = episodeMatch[1];
}
// Get formatted title
const title = videoDetails.title.split(' |')[0].trim();
// Get upload date from date text
const dateText = primaryInfo?.dateText?.simpleText?.trim() || '';
// Get description
const description = videoDetails.shortDescription || '';
const firstLine = description.split('\n')[0]?.trim();
// Get length in minutes
const lengthSeconds = parseInt(videoDetails.lengthSeconds);
const lengthMinutes = Math.ceil(Math.round((lengthSeconds / 60) * 100) / 100);
return {
number: `${episode}`,
name: title,
overview: firstLine,
date: convertDate(dateText),
runtime: lengthMinutes,
};
}
function convertDate(dateStr) {
const date = new Date(dateStr);
return date.toISOString().split('T')[0];
}
getPlaylistVideos(`https://www.youtube.com/playlist?list=${PLAYLIST_ID}`)
.then((videos) => {
console.table(videos);
console.log(videos);
})
.catch((error) => console.error('Error:', error));
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment