Skip to content

Commit fc14d8b

Browse files
committed
add corrections + allow yt url for yt-desc
1 parent 82b5792 commit fc14d8b

File tree

4 files changed

+92
-6
lines changed

4 files changed

+92
-6
lines changed

content-testing/schemas.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ const {
1313
youtubePlaylistIdValidator,
1414
videoNumberValidator,
1515
timestampsArrayValidator,
16+
correctionsArrayValidator,
1617
urlOrRelativeLinkValidator
1718
} = require('./validators');
1819

@@ -44,6 +45,8 @@ const baseVideosSchema = strictObject({
4445

4546
timestamps: timestampsArrayValidator.required(),
4647

48+
corrections: correctionsArrayValidator,
49+
4750
codeExamples: array(
4851
strictObject({
4952
title: string().required(),

content-testing/validators.js

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,42 @@ const timestampsArrayValidator = array(
264264
return true;
265265
});
266266

267+
const correctionsArrayValidator = array(
268+
strictObject({
269+
time: timestampValidator.required(),
270+
title: string().required()
271+
}).required()
272+
).test((values, context) => {
273+
if (!values) return true;
274+
275+
// Per https://support.google.com/youtube/answer/57404
276+
277+
// Timestamps should be sequential
278+
const sequenceErrors = [];
279+
let previousTime = -1;
280+
281+
for (const value of values) {
282+
const currentTime = timestampToSeconds(value.time);
283+
284+
if (currentTime <= previousTime) sequenceErrors.push(value);
285+
286+
previousTime = currentTime;
287+
}
288+
289+
if (sequenceErrors.length > 0) {
290+
const details = sequenceErrors
291+
.map((v) => `${v.time} - ${v.title}`)
292+
.join(' | ');
293+
const plural = sequenceErrors.length > 1 ? 's are' : ' is';
294+
295+
return context.createError({
296+
message: `${sequenceErrors.length} correction${plural} not in sequential order | ${details}`
297+
});
298+
}
299+
300+
return true;
301+
});
302+
267303
// generate all valid track paths, including deeplinks to chapters and videos
268304
const tracksWithChaptersOrVideosSlugs = new Set(slugs.tracks);
269305
paths.tracks.forEach((p) => {
@@ -330,5 +366,6 @@ module.exports = {
330366
youtubePlaylistIdValidator,
331367
videoNumberValidator,
332368
timestampsArrayValidator,
369+
correctionsArrayValidator,
333370
urlOrRelativeLinkValidator
334371
};

content/videos/pixels/painting-with-pixels/index.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,12 @@
5353
"title": "Outro"
5454
}
5555
],
56+
"corrections": [
57+
{
58+
"time": "02:40",
59+
"title": "This old example is not using classes in JavaScript. Check the accompanying code for an updated version!"
60+
}
61+
],
5662
"codeExamples": [
5763
{
5864
"title": "Painting with Pixels Array",

node-scripts/yt-description.mjs

Lines changed: 46 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,9 @@
33
// Usage:
44
// npm run yt-desc
55
// npm run yt-desc https://thecodingtrain.com/path/to/video/page
6+
// npm run yt-desc https://youtube.com/watch?v=videoId
67
// npm run yt-desc ./path/to/index.json
7-
// npm run yt-desc ./path/to/index.json -- -c # copy desc to clipboard
8+
// npm run yt-desc ./path/to/index.json -- -c # copy to clipboard
89

910
// Output files are saved to `./_descriptions` directory
1011

@@ -227,6 +228,26 @@ function resolveCTLink(url) {
227228
return `https://youtu.be/${page.videoId}`;
228229
}
229230

231+
/**
232+
* Retrieves Coding Train video for a given YT link
233+
* @param {URL} url YT video url
234+
* @returns {video} video object
235+
*/
236+
function resolveYTLink(url) {
237+
// youtube.com or youtu.be
238+
if (
239+
url.hostname.includes('youtube.com') ||
240+
url.hostname.includes('youtu.be')
241+
) {
242+
const videoId = url.searchParams.get('v') || url.pathname.slice(1);
243+
const video = videos.find((vid) => vid.data.videoId === videoId);
244+
if (video) {
245+
return video;
246+
}
247+
}
248+
return null;
249+
}
250+
230251
/**
231252
* Finds the most occuring item in an array
232253
* @param {string[]} arr array of items
@@ -414,6 +435,14 @@ function writeDescription(video) {
414435
}
415436
}
416437

438+
// Corrections
439+
if (data.corrections && data.corrections.length > 0) {
440+
description += '\nCorrections: \n';
441+
for (const correction of data.corrections) {
442+
description += `${correction.time} ${correction.title}\n`;
443+
}
444+
}
445+
417446
// Credits
418447
const defaultCredits = `Editing by Mathieu Blanchette
419448
Animations by Jason Heglund
@@ -479,7 +508,7 @@ const allTracks = [...mainTracks, ...sideTracks];
479508

480509
const args = process.argv.slice(2);
481510
const video = args.filter((arg) => !arg.startsWith('-'))[0];
482-
const copyToClipboard = args.includes('-c');
511+
const copyToClipboard = args.includes('-c') || args.includes('--copy');
483512

484513
const directory = 'content/videos';
485514

@@ -494,10 +523,21 @@ const allTracks = [...mainTracks, ...sideTracks];
494523
let specifiedVideos = [];
495524
try {
496525
// coding train website url
497-
const pathName = new URL(video).pathname;
498-
specifiedVideos = videos.filter(
499-
(data) => '/' + data.pageURL === pathName
500-
);
526+
const url = new URL(video);
527+
if (url.hostname == 'thecodingtrain.com') {
528+
const pathName = url.pathname;
529+
specifiedVideos = videos.filter(
530+
(data) => '/' + data.pageURL === pathName
531+
);
532+
} else {
533+
const video = resolveYTLink(url);
534+
if (video) {
535+
specifiedVideos = [video];
536+
} else {
537+
console.log('❌ Could not find video for', url.href);
538+
return;
539+
}
540+
}
501541
} catch (e) {
502542
// local index.json path
503543
let filePath = video;

0 commit comments

Comments
 (0)