/**
* Fetches video, playlist, and subscription counts for the authenticated user's YouTube account.
* This version breaks down the video AND playlist counts by privacy status.
* Logs the results to the Apps Script execution log.
* Note: You can get similar information from the YouTube panel of your Google Account Dashboard
* @see https://myaccount.google.com/dashboard
* Author: Mr Shane
* Version: 2025-08-11
* Prerequisites: Youtube Data API v3
*/
function getMyYouTubeStats() {
const videoCounts = { public: 0, private: 0, unlisted: 0, total: 0 };
const playlistCounts = { public: 0, private: 0, unlisted: 0, total: 0 };
const stats = {
subscriptions: 'N/A',
comments: 'Not available via public API' // This is the correct, final status.
};
try {
const channelResponse = YouTube.Channels.list('contentDetails', { mine: true });
if (channelResponse && channelResponse.items && channelResponse.items.length > 0) {
const uploadsPlaylistId = channelResponse.items[0].contentDetails.relatedPlaylists.uploads;
let pageToken = null;
do {
const playlistItemsResponse = YouTube.PlaylistItems.list('status', {
playlistId: uploadsPlaylistId,
maxResults: 50,
pageToken: pageToken
});
if (playlistItemsResponse.items) {
playlistItemsResponse.items.forEach(item => {
switch (item.status.privacyStatus) {
case 'public': videoCounts.public++; break;
case 'private': videoCounts.private++; break;
case 'unlisted': videoCounts.unlisted++; break;
}
});
}
pageToken = playlistItemsResponse.nextPageToken;
} while (pageToken);
videoCounts.total = videoCounts.public + videoCounts.private + videoCounts.unlisted;
} else {
Logger.log('Could not find a YouTube channel associated with this account.');
return;
}
let playlistPageToken = null;
do {
const playlistsResponse = YouTube.Playlists.list('status', {
mine: true,
maxResults: 50,
pageToken: playlistPageToken
});
if (playlistsResponse.items) {
playlistsResponse.items.forEach(playlist => {
switch (playlist.status.privacyStatus) {
case 'public': playlistCounts.public++; break;
case 'private': playlistCounts.private++; break;
case 'unlisted': playlistCounts.unlisted++; break;
}
});
}
playlistPageToken = playlistsResponse.nextPageToken;
} while (playlistPageToken);
playlistCounts.total = playlistCounts.public + playlistCounts.private + playlistCounts.unlisted;
stats.subscriptions = getTotalCount_(YouTube.Subscriptions, { mine: true, part: 'id' });
Logger.log('--- Your YouTube Stats (Detailed) ---');
Logger.log('Total Videos: ' + videoCounts.total);
Logger.log(' - Public: ' + videoCounts.public);
Logger.log(' - Private: ' + videoCounts.private);
Logger.log(' - Unlisted: ' + videoCounts.unlisted);
Logger.log('-------------------------------------');
Logger.log('Total Playlists: ' + playlistCounts.total);
Logger.log(' - Public: ' + playlistCounts.public);
Logger.log(' - Private: ' + playlistCounts.private);
Logger.log(' - Unlisted: ' + playlistCounts.unlisted);
Logger.log('-------------------------------------');
Logger.log('Total Active Subscriptions: ' + stats.subscriptions);
Logger.log('Total Comments: ' + stats.comments);
Logger.log('-------------------------------------');
} catch (e) {
Logger.log('Error: ' + e.message);
}
}
/**
* A helper function to paginate through a YouTube API list resource and get a simple total count.
* This is only used for subscriptions.
* @private
*/
function getTotalCount_(resource, options) {
let count = 0;
let pageToken = null;
const requestOptions = { ...options, maxResults: 50 };
do {
requestOptions.pageToken = pageToken;
const response = resource.list(requestOptions.part, requestOptions);
if (response.items) {
count += response.items.length;
}
pageToken = response.nextPageToken;
} while (pageToken);
return count;
}
/**
* Code.gs
* Get the video and view counts for a specified channel handle.
* Author: Mr Shane
* Version: 2025-12-24
* Prerequisites: YouTube Data API v3
* IMPORTANT: While UUSH is a common shortcut to find a channel's Shorts, it isn't an "official" public playlist in the same way the standard "Uploads" playlist is. Because of this, the YouTube API often returns inconsistent results (missing videos or temporary caching issues) when you iterate through it using PlaylistItems. In other words, if you run the the script multiple times for the same channel then you must expect the results to vary and not be 100% accurate at any time.
*/
function doGet(){
return HtmlService.createHtmlOutputFromFile('Index')
.setTitle('YouTube Channel Statistics')
.addMetaTag('viewport','width=device-width, initial-scale=1')
.setXFrameOptionsMode(HtmlService.XFrameOptionsMode.ALLOWALL);
}
function getChannelStats(input){
if(!input)return{error:'Please enter a handle, URL, or Channel ID.'};
const channelData=resolveChannel(input);
if(!channelData)return{error:'Channel not found. Try the exact Handle (@Name) or Channel ID.'};
const {channelId,channelName,handle,subscriberCount}=channelData;
const shortsId=channelId.replace(/^UC/,'UUSH');
const shortsStats=getPlaylistStats(shortsId);
const longId=channelId.replace(/^UC/,'UULF');
const longStats=getPlaylistStats(longId);
const totalVids=shortsStats.count+longStats.count;
const totalViews=shortsStats.views+longStats.views;
const firstLongDate=longStats.oldestDate?formatDate(longStats.oldestDate):'-';
const firstShortDate=shortsStats.oldestDate?formatDate(shortsStats.oldestDate):'-';
return{
success:true,
channelName:channelName,
handle:handle,
channelId:channelId,
subscriberCount:subscriberCount,
firstVideoDate:firstLongDate,
firstShortDate:firstShortDate,
data:[
{cat:'Total',vids:totalVids,views:totalViews},
{cat:'Longs',vids:longStats.count,views:longStats.views},
{cat:'Shorts',vids:shortsStats.count,views:shortsStats.views}
]
};
}
function resolveChannel(rawInput){
let q=rawInput.toString().trim();
const idMatch=q.match(/(UC[\w-]{22})/);
if(idMatch){
try{
const res=YouTube.Channels.list('statistics,contentDetails,snippet',{id:idMatch[1]});
if(res.items?.length)return parseChannel(res.items[0]);
}catch(e){}
}
try{q=decodeURIComponent(q);}catch(e){}
q=q.replace(/^(https?:\/\/)?(www\.)?(youtube\.com|youtu\.be)\//i,'');
q=q.replace(/^(channel\/|c\/|user\/)/i,'');
q=q.split(/[/?#]/)[0];
q=q.replace(/^@/,'');
if(!q)return null;
const tryHandle='@'+q;
try{
const res=YouTube.Channels.list('statistics,contentDetails,snippet',{forHandle:tryHandle});
if(res.items?.length)return parseChannel(res.items[0]);
}catch(e){}
try{
const search=YouTube.Search.list('id',{q:q,type:'channel',maxResults:1});
if(search.items?.length){
const foundId=search.items[0].id.channelId;
const res=YouTube.Channels.list('statistics,contentDetails,snippet',{id:foundId});
if(res.items?.length)return parseChannel(res.items[0]);
}
}catch(e){}
return null;
}
function parseChannel(item){
return{
channelId:item.id,
totalViews:parseInt(item.statistics.viewCount,10)||0,
subscriberCount:parseInt(item.statistics.subscriberCount,10)||0,
uploadsId:item.contentDetails.relatedPlaylists.uploads,
channelName:item.snippet.title,
handle:item.snippet.customUrl||'No Handle'
};
}
function getPlaylistStats(playlistId){
let stats={count:0,views:0,oldestDate:null};
let pageToken='';
try{
do{
const plRes=YouTube.PlaylistItems.list('contentDetails',{playlistId:playlistId,maxResults:50,pageToken:pageToken});
if(!plRes.items?.length)break;
stats.count+=plRes.items.length;
const lastItem=plRes.items[plRes.items.length-1];
if(lastItem.contentDetails?.videoPublishedAt){
stats.oldestDate=lastItem.contentDetails.videoPublishedAt;
}
const ids=plRes.items.map(i=>i.contentDetails.videoId).join(',');
const vidRes=YouTube.Videos.list('statistics',{id:ids});
if(vidRes.items){
stats.views+=vidRes.items.reduce((sum,v)=>sum+(parseInt(v.statistics.viewCount,10)||0),0);
}
pageToken=plRes.nextPageToken;
}while(pageToken);
}catch(e){return stats;}
return stats;
}
function formatDate(isoString){
if(!isoString)return'-';
const d=new Date(isoString);
return d.toISOString().split('T')[0];
}
<!-- Index.html -->
<!DOCTYPE html>
<html>
<head>
<base target="_top">
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;500;700&display=swap" rel="stylesheet">
<style>
html,body{min-height:100vh}
body{font-family:'Roboto',-apple-system,sans-serif;background-color:#f4f6f8;margin:0;padding:20px;display:flex;flex-direction:column;align-items:center}
.container{background:white;max-width:500px;width:100%;padding:20px;border-radius:12px;box-shadow:0 4px 10px rgba(0,0,0,0.1);box-sizing:border-box}
h2{margin-top:0;color:#cc0000;text-align:center}
.input-group{display:flex;gap:10px;margin-bottom:20px}
input{flex-grow:1;padding:12px;border:1px solid #ddd;border-radius:8px;font-size:16px;outline:none;-webkit-appearance:none}
input:focus{border-color:#cc0000}
button{background-color:#cc0000;color:white;border:none;padding:12px 20px;border-radius:8px;font-size:16px;font-weight:bold;cursor:pointer}
button:disabled{background-color:#e0e0e0;color:#999}
#resultArea{display:none}
.info-box{background:#f9f9f9;padding:12px;border-radius:8px;margin-bottom:15px;font-size:13px;border:1px solid #eee}
.info-row{display:flex;justify-content:space-between;margin-bottom:6px}
.info-label{font-weight:bold;color:#666}
.info-value{color:#222;text-align:right;word-break:break-all;margin-left:10px}
.divider{height:1px;background:#eee;margin:8px 0}
table{width:100%;border-collapse:collapse;font-size:13px}
th{text-align:left;color:#555;font-size:12px;text-transform:uppercase;border-bottom:2px solid #eee;padding:8px}
td{padding:6px 8px;border-bottom:1px solid #f0f0f0}
.num{text-align:right;font-family:monospace;font-size:1.0em;color:#333}
th.num{text-align:right}
#loading{display:none;text-align:center;color:#666;margin-top:20px;font-style:italic}
#error{color:#d32f2f;background:#ffebee;padding:10px;border-radius:6px;display:none;margin-bottom:15px;text-align:center}
</style>
</head>
<body>
<div class="container">
<h2>YouTube Channel Statistics</h2>
<div class="input-group">
<input type="text" id="handleInput" placeholder="URL, Handle, or Channel ID"/>
<button id="btn" onclick="fetchStats()">Go</button>
</div>
<div id="loading">Analyzing channel data...</div>
<div id="error"></div>
<div id="resultArea">
<div class="info-box">
<div class="info-row"><span class="info-label">Channel Name:</span><span class="info-value" id="outName"></span></div>
<div class="info-row"><span class="info-label">Channel Handle:</span><span class="info-value" id="outHandle"></span></div>
<div class="info-row"><span class="info-label">Channel ID:</span><span class="info-value" id="outId"></span></div>
<div class="info-row"><span class="info-label">Subscribers:</span><span class="info-value" id="outSubs"></span></div>
<div class="divider"></div>
<div class="info-row"><span class="info-label">First Long:</span><span class="info-value" id="outFirstLong"></span></div>
<div class="info-row"><span class="info-label">First Short:</span><span class="info-value" id="outFirstShort"></span></div>
</div>
<table id="statsTable">
<thead>
<tr>
<th>Category</th>
<th class="num">Videos</th>
<th class="num">Views</th>
<th class="num">Avg</th>
</tr>
</thead>
<tbody id="tableBody"></tbody>
</table>
</div>
</div>
<script>
function fetchStats(){
const input=document.getElementById('handleInput').value.trim();
if(!input)return;
const btn=document.getElementById('btn'),loading=document.getElementById('loading'),resultArea=document.getElementById('resultArea'),errorDiv=document.getElementById('error');
btn.disabled=true;
btn.innerText='...';
loading.style.display='block';
resultArea.style.display='none';
errorDiv.style.display='none';
google.script.run.withSuccessHandler(onSuccess).withFailureHandler(onFailure).getChannelStats(input);
}
function onSuccess(response){
resetUI();
if(response.error){
const errDiv=document.getElementById('error');
errDiv.innerText=response.error;
errDiv.style.display='block';
return;
}
document.getElementById('outName').innerText=response.channelName;
document.getElementById('outHandle').innerText=response.handle;
document.getElementById('outId').innerText=response.channelId;
document.getElementById('outSubs').innerText=response.subscriberCount.toLocaleString();
document.getElementById('outFirstLong').innerText=response.firstVideoDate;
document.getElementById('outFirstShort').innerText=response.firstShortDate;
const tbody=document.getElementById('tableBody');
tbody.innerHTML='';
response.data.forEach(row=>{
const avg=row.vids>0?Math.round(row.views/row.vids):0;
const tr=document.createElement('tr');
tr.innerHTML=`<td>${row.cat}</td><td class="num">${row.vids.toLocaleString()}</td><td class="num">${row.views.toLocaleString()}</td><td class="num">${avg.toLocaleString()}</td>`;
tbody.appendChild(tr);
});
document.getElementById('resultArea').style.display='block';
}
function onFailure(err){
resetUI();
const errDiv=document.getElementById('error');
errDiv.innerText="System Error: "+err.message;
errDiv.style.display='block';
}
function resetUI(){
document.getElementById('btn').disabled=false;
document.getElementById('btn').innerText='Go';
document.getElementById('loading').style.display='none';
}
</script>
</body>
</html>
/**
* Code.gs
* Get specific video metrics from a URL with translation capabilities.
* Author: Mr Shane
* Version: 2026-04-16
*/
function doGet() {
return HtmlService.createHtmlOutputFromFile('Index')
.setTitle('YouTube Video Metadata Inspector')
.addMetaTag('viewport', 'width=device-width, initial-scale=1')
.setXFrameOptionsMode(HtmlService.XFrameOptionsMode.ALLOWALL);
}
function getVideoStats(url) {
if (!url) return { error: 'Please enter a video URL.' };
const videoId = extractId(url);
if (!videoId) return { error: 'Could not parse Video ID. Check the URL format.' };
try {
const res = YouTube.Videos.list('snippet,statistics', { id: videoId });
if (!res.items || res.items.length === 0) {
return { error: 'Video not found. It may be private or deleted.' };
}
const item = res.items[0];
const snip = item.snippet;
const stats = item.statistics;
let categoryName = 'Unknown';
if (snip.categoryId) {
try {
const catRes = YouTube.VideoCategories.list('snippet', { id: snip.categoryId });
if (catRes.items && catRes.items.length > 0) {
categoryName = catRes.items[0].snippet.title;
} else {
categoryName = 'Category ID: ' + snip.categoryId;
}
} catch (e) {
categoryName = 'Category ID: ' + snip.categoryId;
}
}
const titleStr = snip.title || 'No title';
const tagsStr = snip.tags ? snip.tags.join(', ') : 'No keywords found';
let translatedTitle = titleStr;
let translatedTags = tagsStr;
try {
translatedTitle = LanguageApp.translate(titleStr, '', 'en');
} catch (e) {
translatedTitle = 'Auto-translation failed: ' + e.message;
}
if (snip.tags && snip.tags.length > 0) {
try {
translatedTags = LanguageApp.translate(tagsStr, '', 'en');
} catch (e) {
translatedTags = 'Auto-translation failed: ' + e.message;
}
}
return {
success: true,
title: titleStr,
translatedTitle: translatedTitle,
category: categoryName,
channelName: snip.channelTitle,
channelId: snip.channelId,
publishDate: Utilities.formatDate(new Date(snip.publishedAt), "GMT", "yyyy-MM-dd HH:mm"),
views: parseInt(stats.viewCount || 0).toLocaleString(),
likes: parseInt(stats.likeCount || 0).toLocaleString(),
comments: parseInt(stats.commentCount || 0).toLocaleString(),
tags: tagsStr,
translatedTags: translatedTags
};
} catch (e) {
return { error: 'API Error: ' + e.message };
}
}
function translateOverride(title, tags, sourceLang) {
let resTitle = title;
let resTags = tags;
try {
if (title && title !== 'No title') {
resTitle = LanguageApp.translate(title, sourceLang, 'en');
}
} catch(e) { resTitle = 'Translation error: ' + e.message; }
try {
if (tags && tags !== 'No keywords found') {
resTags = LanguageApp.translate(tags, sourceLang, 'en');
}
} catch(e) { resTags = 'Translation error: ' + e.message; }
return { translatedTitle: resTitle, translatedTags: resTags };
}
function extractId(url) {
const regExp = /^.*((youtu.be\/)|(v\/)|(\/u\/\w\/)|(embed\/)|(watch\?))\??v?=?([^#&?]*).*/;
const match = url.match(regExp);
return (match && match[7].length == 11) ? match[7] : (url.includes('shorts/') ? url.split('shorts/')[1].split(/[?&]/)[0] : null);
}
<!-- Index.html -->
<!DOCTYPE html>
<html>
<head>
<base target="_top">
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;500;700&display=swap" rel="stylesheet">
<style>
body { font-family: 'Roboto', sans-serif; background-color: #f0f2f5; margin: 0; padding: 20px; display: flex; flex-direction: column; align-items: center; min-height: 100vh; }
.card { background: white; max-width: 600px; width: 100%; padding: 25px; border-radius: 15px; box-shadow: 0 10px 25px rgba(0,0,0,0.05); box-sizing: border-box; }
h2 { color: #ff0000; margin-top: 0; text-align: center; }
.input-box { display: flex; gap: 10px; margin-bottom: 20px; }
input { flex: 1; padding: 12px; border: 1px solid #ddd; border-radius: 8px; font-size: 14px; outline: none; }
button { background: #ff0000; color: white; border: none; padding: 12px 24px; border-radius: 8px; font-weight: bold; cursor: pointer; transition: opacity 0.2s; }
button:active { opacity: 0.7; }
button:disabled { background: #ccc; }
.copy-btn { background: #666; color: white; border: none; border-radius: 4px; padding: 4px 8px; font-size: 11px; margin-left: 10px; text-transform: uppercase; letter-spacing: 0.5px; flex-shrink: 0; cursor: pointer; font-weight: bold; }
.result-row { display: flex; justify-content: space-between; align-items: center; padding: 10px; border-bottom: 1px solid #f0f0f0; font-size: 14px; }
.label { font-weight: bold; color: #555; }
.value { color: #000; text-align: right; max-width: 65%; word-wrap: break-word; }
.tags-container { position: relative; margin-top: 10px; font-size: 13px; color: #666; background: #f9f9f9; padding: 15px; border-radius: 8px; min-height: 40px; word-break: break-word; }
#error { color: #d32f2f; background: #ffebee; padding: 10px; border-radius: 8px; display: none; margin-bottom: 15px; text-align: center; }
.trans-header { display: flex; justify-content: space-between; align-items: center; background: #eef7ff; padding: 10px 15px; border-radius: 8px; margin-bottom: 15px; }
select { padding: 6px; border: 1px solid #ddd; border-radius: 6px; font-size: 13px; outline: none; cursor: pointer; }
.trans-highlight { color: #004488; }
.block-header { display: flex; justify-content: space-between; align-items: center; margin-top: 15px; padding: 0 10px; }
.footer-spacer { height: 120px; width: 100%; pointer-events: none; }
</style>
</head>
<body>
<div class="card">
<h2>YouTube Video Metadata Inspector</h2>
<div class="input-box">
<input type="text" id="videoUrl" placeholder="Paste YouTube Video URL here...">
<button id="goBtn" onclick="runInspec()">Inspect</button>
</div>
<div id="error"></div>
<div id="results" style="display:none;">
<div class="trans-header">
<span class="label trans-highlight">Translation Override:</span>
<select id="langSelect" onchange="overrideTranslation()">
<option value="">Auto-Detect</option>
<option value="es">Spanish</option>
<option value="fr">French</option>
<option value="de">German</option>
<option value="pt">Portuguese</option>
<option value="id">Indonesian</option>
<option value="ru">Russian</option>
<option value="ja">Japanese</option>
<option value="ko">Korean</option>
<option value="zh-CN">Chinese (Simplified)</option>
<option value="ar">Arabic</option>
<option value="hi">Hindi</option>
<option value="it">Italian</option>
<option value="pl">Polish</option>
<option value="tr">Turkish</option>
<option value="vi">Vietnamese</option>
<option value="th">Thai</option>
</select>
</div>
<div class="result-row">
<span class="label">Original Title</span>
<div style="display:flex; align-items:center; justify-content: flex-end; width: 70%;">
<span class="value" id="vTitle"></span>
<button class="copy-btn" onclick="copyText('vTitle')">Copy</button>
</div>
</div>
<div class="result-row" style="background: #eef7ff; border-radius: 6px; border-bottom: none; margin: 5px 0 10px 0;">
<span class="label trans-highlight">Translated Title (EN)</span>
<div style="display:flex; align-items:center; justify-content: flex-end; width: 60%;">
<span class="value trans-highlight" id="vTransTitle" style="font-weight: 500;"></span>
<button class="copy-btn" style="background:#004488" onclick="copyText('vTransTitle')">Copy</button>
</div>
</div>
<div class="result-row"><span class="label">Video Category</span><span class="value" id="vCategory"></span></div>
<div class="result-row"><span class="label">Channel Name</span><span class="value" id="vChan"></span></div>
<div class="result-row"><span class="label">Channel ID</span><span class="value" id="vChanId"></span></div>
<div class="result-row"><span class="label">Video Published</span><span class="value" id="vDate"></span></div>
<div class="result-row"><span class="label">Video Views</span><span class="value" id="vViews"></span></div>
<div class="result-row"><span class="label">Video Likes</span><span class="value" id="vLikes"></span></div>
<div class="result-row"><span class="label">Video Comments</span><span class="value" id="vComm"></span></div>
<div class="block-header">
<div class="label">Original Keywords/Tags:</div>
<button class="copy-btn" onclick="copyText('vTags')">Copy Tags</button>
</div>
<div class="tags-container" id="vTags"></div>
<div class="block-header">
<div class="label trans-highlight">Translated Keywords/Tags (EN):</div>
<button class="copy-btn" style="background:#004488" onclick="copyText('vTransTags')">Copy Tags</button>
</div>
<div class="tags-container" id="vTransTags" style="background: #eef7ff; color: #004488;"></div>
</div>
</div>
<div class="footer-spacer"></div>
<script>
let currentOriginalTags = "";
let currentOriginalTitle = "";
function copyText(elementId) {
const text = document.getElementById(elementId).innerText;
if (!text || text === 'Translating...' || text === 'No keywords found' || text === 'No title') return;
const btn = event.currentTarget;
const oldText = btn.innerText;
navigator.clipboard.writeText(text).then(() => {
btn.innerText = 'COPIED!';
btn.style.background = '#28a745';
setTimeout(() => {
btn.innerText = oldText;
btn.style.background = elementId.toLowerCase().includes('trans') ? '#004488' : '#666';
}, 1500);
}).catch(err => {
alert("Copy failed. Please select and copy manually.");
});
}
function decodeHtml(html) {
const txt = document.createElement("textarea");
txt.innerHTML = html;
return txt.value;
}
function runInspec() {
const url = document.getElementById('videoUrl').value;
const btn = document.getElementById('goBtn');
const errorDiv = document.getElementById('error');
const resDiv = document.getElementById('results');
btn.disabled = true;
btn.innerText = 'Loading...';
errorDiv.style.display = 'none';
resDiv.style.display = 'none';
document.getElementById('langSelect').value = "";
google.script.run
.withSuccessHandler(data => {
btn.disabled = false;
btn.innerText = 'Inspect';
if (data.error) {
errorDiv.innerText = data.error;
errorDiv.style.display = 'block';
} else {
currentOriginalTitle = data.title;
document.getElementById('vTitle').innerText = data.title;
document.getElementById('vTransTitle').innerText = decodeHtml(data.translatedTitle);
document.getElementById('vCategory').innerText = data.category;
document.getElementById('vChan').innerText = data.channelName;
document.getElementById('vChanId').innerText = data.channelId;
document.getElementById('vDate').innerText = data.publishDate;
document.getElementById('vViews').innerText = data.views;
document.getElementById('vLikes').innerText = data.likes;
document.getElementById('vComm').innerText = data.comments;
currentOriginalTags = data.tags;
document.getElementById('vTags').innerText = data.tags;
document.getElementById('vTransTags').innerText = decodeHtml(data.translatedTags);
resDiv.style.display = 'block';
}
})
.withFailureHandler(err => {
btn.disabled = false;
btn.innerText = 'Inspect';
errorDiv.innerText = "Execution failed.";
errorDiv.style.display = 'block';
})
.getVideoStats(url);
}
function overrideTranslation() {
if (!currentOriginalTags && !currentOriginalTitle) return;
const langCode = document.getElementById('langSelect').value;
const transTitleBox = document.getElementById('vTransTitle');
const transTagsBox = document.getElementById('vTransTags');
transTitleBox.innerText = 'Translating...';
if (currentOriginalTags && currentOriginalTags !== 'No keywords found') {
transTagsBox.innerText = 'Translating...';
}
google.script.run
.withSuccessHandler(result => {
transTitleBox.innerText = decodeHtml(result.translatedTitle);
transTagsBox.innerText = decodeHtml(result.translatedTags);
})
.withFailureHandler(err => {
transTitleBox.innerText = 'Error translating';
transTagsBox.innerText = 'Error translating: ' + err.message;
})
.translateOverride(currentOriginalTitle, currentOriginalTags, langCode);
}
</script>
</body>
</html>