Building AI Enabled Youtube Playlist Manager using Tfjs/Google apis

Summary

We will create AI enabled youtube playlist manager. We will implement two ML model (NER/Toxicity) and try to use both models in our youtube playlist manager application.

Technologies

We will use react, Bootstrap, Google API’s, Tensflowjs model to create playlist manager

Basic Setup

Frontend:

  1. React App → npx create-react-app youtube_playlist_manager
  2. You need to install create-react-app globally using npm install -g create-react-app
  1. You can follow https://developers.google.com/drive/activity/v1/guides/project guideline to create a google project
  1. After selecting your project navigate to https://console.cloud.google.com/apis/library/ and go to api library and search for YouTube Data API v3. As you will enable google api from Google developer console. You need to createcredentials and configure authentication steps
  1. Now go to credentials screen and create credentials. Copy client-id which will be used for implementation
  2. Once you will create credentials blow screen will be displayed

Implementation

If installation is complete go to created project youtube_playlist_manager

  1. Go to Components folder inside src create a folder Components
  2. Create file LoginContainer.js, ProfileCard.js inside src→Components
  3. Create folders components inside src→Components
  4. Go to src→Components→Components create folder name Utis and YoutubeComponents
  5. Create files TfNer.js/TfToxicity.js and WordProcessor.js and WordVocab.js inside src→Components→Components→Utils
  6. Create file PlayListContainer.js, ProfileCard.js inside YoutubeComponents

Building Google Login

We will start Google login implementation.
Login screen will look like below

import React from 'react';
import logo from './logo.svg';
import './App.css';
import AppContainer from './Components/AppContainer';
function App() { return (
<AppContainer />
); }
export default App;
import React from ‘react’;import LoginContainer from ‘./Components/LoginContainer’;import YoutubeContainer from ‘./Components/YoutubeContainer’;export default class AppContainer extends React.Component {constructor(props) {  super(props);  this.state = { googleAuth: null }  this.receiveGoogleAuth = this.receiveGoogleAuth.bind(this);}receiveGoogleAuth(loginResponse) {  this.setState({googleAuth: loginResponse});}getComponent() {  if(this.state.googleAuth === null) {   return (<LoginContainer receiveGoogleAuth={(response) =>        this.receiveGoogleAuth(response)} />);} else {
return (<YoutubeContainer google={this.state.googleResponse} />)}
}render() { return (
<div className=”container-fluid bg-light h-100">
{this.getComponent()}
</div>
)}}
import React from 'react';import LoginContainer from './Components/LoginContainer';import YoutubeContainer from './Components/YoutubeContainer';
receiveGoogleAuth(loginResponse) {  this.setState({googleAuth: loginResponse});}
getComponent() {if(this.state.googleAuth === null) { 
return (<LoginContainer receiveGoogleAuth={(response) => this.receiveGoogleAuth(response)} />);
} else {
return (<YoutubeContainer google={this.state.googleAuth} />)
}}
import React from 'react';
import GoogleLogin from 'react-google-login';
constructor(props) {
super(props);
this.state = {
"googleClientId": "Your_google_app_client_id"
}}
.loginContainer {   position: absolute !important;
top: 30%;
left: 35%;
}
<GoogleLoginclientId={this.state.googleClientId}
scope="https://www.googleapis.com/auth/youtube https://www.googleapis.com/auth/youtube.force-ssl"
icon={true}
accessType="offline"
responseType="permissions"
offline={true}
onSuccess={(response) => this.props.receiveGoogleAuth(response)}
onFailure={(response) => this.props.receiveGoogleAuth(response)}
/>
import React from 'react';
import ProfileContainer from './YoutubeComponents/ProfileCard';
import PlayListContainer from './YoutubeComponents/PlayListContainer';
import {getYoutubePlaylists, getYoutubePlayListItems} from './Utils/Api';
componentWillMount() { const _this = this;
getYoutubePlaylists(this.props.google.accessToken).then((data) => {
_this.setState({playlists: data});
});
}
export function getYoutubePlaylists(access_token) {const params = {
"part":"snippet,contentDetails,id,localizations,player,snippet,status",
"mine": true,
"maxResults": 50
}
return get_api(access_token, "playlists", params);
}
function get_api(access_token, end_point, params) {let apiUrl = `${BASE_API_URL}${end_point}?access_token=${access_token}`;Object.keys(params).map((key) => {
apiUrl = apiUrl + `&${key}=${deco deURIComponent(params[key])}`;
});
return fetch(apiUrl, {
method: "GET",
headers: {
'Content-Type': 'application/json'
},}).then((response) => {
return response.json();
});
}
Object.keys(params).map((key) => {
apiUrl = apiUrl + `&${key}=${deco deURIComponent(params[key])}`;
});
fetchplayListitems(playListid) { const _this = this;
const playlistItems = this.state.playlistitems;
playlistItems.loading = true;
this.setState({playlistitems: playlistItems}, () => {
getYoutubePlayListItems(this.props.google.accessToken, playListid).then((data) => {
const playlistItems = _this.state.playlistitems;
playlistItems.loading = false;
playlistItems.data[playListid] = data;
_this.setState({playlistitems: playlistItems});
})}); }
export function getYoutubePlayListItems( access_token, playListId ) {const params = {"part": "snippet,contentDetails,id,status",
"playlistId": playListId,
"maxResults": 50
}
return get_api(access_token, "playlistItems", params);
}
{(this.state.playlists.items && this.state.playlists.items.length > 0) ?<PlayListContainer
profile={this.props.google.profileObj}
access_token={this.props.google.accessToken}
playlists = {this.state.playlists}
playlistitems = {this.state.playlistitems}
fetchplayListitems={(playListId) => this.fetchplayListitems(playListId)}/> : <div className="alert alert-info">
No playlist found
</div>
}
<div className={`col-${this.state.playListId === null ? "12": "2"}`}> <div className="">
{this.props.playlists.items.map((item) => {
return (
<div style={{cursor: "pointer"}} className={`card ${this.state.playListId=== item.id ? "text-white bg-info":""}`} onClick={() => this.openPlaylists(item.id)}> <div className="card-body">
<h5 className="card-title" style={{"textDecoration": "underline"}}>{item.snippet.title}</h5>
<p className="card-text">{item.contentDetails.itemCount} videos.</p>
</div>
</div>
)})}
</div>
</div>
openPlaylists(playListId) {
this.setState({playListId}, () => {
this.props.fetchplayListitems(playListId);
});
}
componentWillReceiveProps(nextProps) {if(nextProps.playlistitems.data[this.state.playListId]) {      this.setState({is_processing: true}, () => {   this.categorizePlaylist(nextProps.playlistitems.data[this.state.playListId].items);
});}}
async categorizePlaylist(items) {
const categorizedData = await items.reduce(async (allItems, item) => {
const accumulator = await allItems;
const name = await predictArtist(item.snippet.title);
let nameItems = [];
if (accumulator[name]) {
nameItems = accumulator[name];
}
nameItems.push(item);
accumulator[name] = nameItems;
return accumulator;
}, {});
const names = Object.keys(categorizedData);
const index = names.indexOf('other');
if (index > -1) {
names.splice(index, 1);
}
names.push('other');
this.setState(({is_processing: false, categoryData: categorizedData, names}));
}

First ML implementation (NER) Predict artist name from title

import * as tf from '@tensorflow/tfjs';
import {make_sequences} from './WordProcessor';
import {tags_vocab} from './WordVocabs';
let model, emodel;
async function loadNerModel() { model = await   tf.loadLayersModel('http://deepdivision.net/NERjs/tfjs_models/ner/model.json'); let outputs_ = [model.output,  model.getLayer("attention_vector").output];
emodel = tf.model({inputs: model.input, outputs: outputs_});
}
loadNerModel().then(() => {
console.log("Ner model loaded");
});
loadNerModel().then(() => {
console.log("Ner model loaded");
});
const words = sentence.split(' ');
let sequence = make_sequences(words);
let tensor = tf.tensor1d(sequence, 'int32').expandDims(0);
let [predictions, attention_probs] = await emodel.predict(tensor);
attention_probs = await attention_probs.data();
predictions = await predictions.argMax(-1).data();
let predictions_tags = Array();
predictions.forEach(function(tagid) {
predictions_tags.push(getKey(tags_vocab, tagid));
});
const bper = predictions.filter(({tag}) => tag === "B-PER");
const iper = predictions.filter(({tag}) => tag === "I-PER");
{
“jon manyer”: [{
“Songs”
},
“other”: [{
“Songs”
}]

Second ML implementation (Toxicity Detection)

You can see in PlayListContainer.js we have created a button to check Toxicity. Here we will fetch 30 comments from youtube video and run toxicity on them

<button onClick={() => this.checkToxicity(videoId)} className="btn btn-primary">
Check Toxicity
</button>
const videoToxicity = this.state.videoToxicity;videoToxicity[videoId] = {
"processing": true,
"deductions": {}
}
this.setState({videoToxicity}, () => {const _this = this;
getVideoComments(this.props.access_token, videoId).then((data) => {
const comments = data.items.map(({snippet}) => {
return snippet.topLevelComment.snippet.textOriginal;
});predictToxicElements(comments).then((predictions) => {
videoToxicity[videoId] = {
"processing": false,
"deductions": predictions
};
_this.setState({videoToxicity});
});});})
export function getVideoComments( access_token, videoId ) {const params = {
"part": "id,snippet",
"videoId": videoId,
"maxResults": 30,
textFormat: "plainText"
}
return get_api(access_token, "commentThreads", params);
}
const comments = data.items.map(({snippet}) =>  {
return snippet.topLevelComment.snippet.textOriginal;
});
predictToxicElements(comments).then((predictions) => {
videoToxicity[videoId] = {
"processing": false,
"deductions": predictions
};
_this.setState({videoToxicity});
});
async function loadToxicityModel() {  toxicity.load().then((model) => {
try {
toxicityModel = model;
} catch(e) {
console.log(e);
}}).catch((e) => {
console.log("Error occured : ", e);
});}
loadToxicityModel().then(() => {
console.log("ToxicityModel loaded");
})
export async function predictToxicElements(sentences) {const predictions = await toxicityModel.classify(sentences);
const deductions = predictions.reduce((allItems, {label, results}) => {
const labelCount = results.filter(({match}) => match === true).length;
if (labelCount > 0) {
allItems[label] = labelCount;
}
return allItems;
}, {});
return deductions;
}
{"label": "identity_attack", 
"results": [{
"probabilities": {
"0": 0.999657154083252,
"1": 0.00034280409454368055
},"match": false
},
"label": "insult",
results: [{
"probabilities": {
"0": 0.999657154083252,
"1": 0.00034280409454368055
},
"match": false
}]}
[
"identity_attack",
"insult",
"obscene",
"severe_toxicity",
"sexual_explicit",
"threat",
"toxicity"
]
const labelCount = results.filter(({match}) =>  match === true).length;if (labelCount > 0) {
allItems[label] = labelCount;
}
return allItems;
<select onChange={(e) => this.applyAction(e)} className="form-control" name="" id="">
<option style={{'display': 'none'}}>Select Action</option>
<option value="delete"> Delete </option>
</select>
applyAction(e) {  if(e.target.value === "delete") {
deleteVideoElement(this.props.access_token, this.state.videoElementId).then((data) => {
console.log("Delete response : ", data);
this.props.fetchplayListitems(this.state.playListId);
});}}
export function deleteVideoElement( access_token, videoItemId ) {    const params = {
"id": videoItemId
}
return delete_api(access_token, "playlistItems", params);
}

Conclusion and Further Work

As we have seen that just including models in UI we can achieve run time intelligence which we never had in general. You can extend implementation and enhance this feature as you may like

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store