Skip to content

Commit

Permalink
Youtube (#490)
Browse files Browse the repository at this point in the history
* feat: generate playlist csv (WIP)

* feat: use Youtube API for processing
  • Loading branch information
jy95 authored Jul 19, 2024
1 parent 8700969 commit 9cd4e34
Show file tree
Hide file tree
Showing 6 changed files with 649 additions and 18 deletions.
5 changes: 3 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -35,5 +35,6 @@ yarn-error.log*
next-env.d.ts

# Files for playlist report
Informations relatives aux tableaux.csv
playlists_stats.json
playlists_stats.csv
playlists_stats.json
YOUR_CLIENT_SECRET_FILE.json
94 changes: 94 additions & 0 deletions generate-playlist-csv.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
/**
* Sample Node.js code for youtubeAnalytics.reports.query
* See instructions for running these code samples locally:
* https://developers.google.com/explorer-help/code-samples#nodejs
*/

import { readFile } from "fs/promises";
import { createWriteStream } from "fs";
import { google } from "googleapis";
import input from '@inquirer/input';

const scope = ["https://www.googleapis.com/auth/youtube.readonly"];
const START_DATE = "2023-09-01";
const END_DATE = "2024-03-31";
const MAX_RESULTS = 50;

// Load client secrets from a local file.
// https://stackoverflow.com/a/52222827/6149867
const content = await readFile("YOUR_CLIENT_SECRET_FILE.json");

// Authorize a client with credentials, then make API call.
const credentials = JSON.parse(content);
const { client_secret, client_id, redirect_uris } = credentials.installed;
const oAuth2Client = new google.auth.OAuth2(
client_id,
client_secret,
redirect_uris[0]
);

const authUrl = oAuth2Client.generateAuthUrl({
access_type: "offline",
scope: scope
});

console.log(authUrl);
const authMessage = `Visit the URL above to authorize this app`;
const code = await input({ message: authMessage });

oAuth2Client.getToken(code, (err, token) => {
if (err) return callApi(err);
oAuth2Client.setCredentials(token);
callApi(oAuth2Client);
});

// What to do with result


/**
* Define and execute the API request.
* @param {google.auth.OAuth2} auth An authorized OAuth2 client.
*/
const callApi = async (auth) => {
const youtubeAnalytics = google.youtubeAnalytics({ version: "v2", auth });

try {
const response = await youtubeAnalytics.reports.query({
dimensions: "playlist",
ids: "channel==MINE",
maxResults: MAX_RESULTS,
metrics: "views,estimatedMinutesWatched",
sort: "-estimatedMinutesWatched",
startDate: START_DATE,
endDate: END_DATE
});

const rows = response.data.rows || [];

if (rows.length > 0) {
const filePath = "playlists_stats.csv";
const stream = createWriteStream(filePath);

// Write headers to the stream
stream.write("Playlist,Title,Views,WatchTime (minutes)\n");

for (const row of rows) {
const [playListId, views, watchTime] = row;

// Write each row to the stream
// TODO have to find a way to retrive title of playlist
stream.write(`${playListId},"${playListId}",${views},${watchTime}\n`);
}

// Close the stream and indicate completion
stream.end(() => {
console.log("Data written to playlists_stats.csv successfully.");
});

} else {
console.log("No data found.");
}
} catch (error) {
console.log("The API returned an error: ", error.errors);
}
};
13 changes: 6 additions & 7 deletions generate-playlists-stats.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,13 @@ import { readFile, writeFile, access } from "fs/promises";
import { resolve as resolvePath } from "path";


// Download from
// https://studio.youtube.com/channel/UCG0N7IV-C43AM9psxslejCQ/analytics/tab-content/period-default/explore?entity_type=CHANNEL&entity_id=UCG0N7IV-C43AM9psxslejCQ&r_dimensions=IN_CURATED_CONTENT&r_values=%27IN_CURATED_CONTENT%27&r_inclusive_starts=&r_exclusive_ends=&time_period=4_weeks&explore_type=TABLE_AND_CHART&metric=VIEWS&granularity=DAY&t_metrics=VIEWS&t_metrics=WATCH_TIME&dimension=PLAYLIST&o_column=VIEWS&o_direction=ANALYTICS_ORDER_DIRECTION_DESC
const csvFilePath = 'Informations relatives aux tableaux.csv';
// Params
const csvFilePath = 'playlists_stats.csv';
const basePicturesPath = "./public"
const outputFilePath = 'playlists_stats.json';

// First line are headers, second a total summary
const numberOfHeaders = 2;
// First line are headers
const numberOfHeaders = 1;

// Functions
const generateImagePath = (playlistId) => resolvePath(`${basePicturesPath}/covers/${playlistId}/cover.webp`);
Expand Down Expand Up @@ -48,7 +47,7 @@ async function readCSV(filePath) {
// Check if it is a real line or not
if (columns.length < 4) continue;

const [playlistId, title, views, watchTimeInHours] = columns;
const [playlistId, title, views, watchTimeInMinutes] = columns;
const imagePath = generateImagePath(playlistId);

// Check if it is a gaming playlist
Expand All @@ -60,7 +59,7 @@ async function readCSV(filePath) {
title: title,
imagePath: imagePath,
views: Number.parseInt(views),
watchTimeInHours: Number(watchTimeInHours)
watchTimeInMinutes: Number(watchTimeInMinutes)
});
} catch {
console.log(`\t ${title} is not a game - skipping`);
Expand Down
Loading

0 comments on commit 9cd4e34

Please sign in to comment.