Skip to content

Instantly share code, notes, and snippets.

@leoherzog
Last active Dec 27, 2019
Embed
What would you like to do?
Google Drive TVDB Video Renamer

Google Drive TVDB Video Renamer

Google Apps Script code to go through a folder in Google Drive, recursively find all of the video files, and based off of the file names, rename them all to be consistent via information from The TVDB.

Setup

  1. Copy the two .gs files from this repo into a new Google Apps Script project
  2. Change the folder ID in the quotes in line 1 to the Google Driue folder ID that contains the video files (that can be found in the URL when opening the folder)
  3. Change the name of the show in the quotes on line 2 to be the name of the show
  4. Create a free account at the TVDB to obtain an API key. Get your API key information and copy it into line 84.
  5. Run the "run" function (and grant OAuth permissions if necessary)
var folderId = "1T4U5WK0gpoGzfrZW76UIObsg6YNwHLaP"; // id of the folder
var showName = "LOST"; // name of the show
var seasonOverride;
function run() {
var episodes = getEpisodeList_();
var files = getVideoFilesInFolder_(folderId);
for (var i in files) {
var fileName = files[i].getName();
var fileExtension = fileName.split('.').pop();
var numbers = parse_(fileName);
Logger.log(numbers);
// get the episode in our tvdb list
var episode;
for (var j in episodes) {
if (numbers.episode && numbers.episode[1]) {
if (episodes[j].airedSeason == numbers.season[1] && episodes[j].airedEpisodeNumber == numbers.episode[1]) {
episode = episodes[j];
break;
}
}
}
if (!episode || !numbers.episode) {
Logger.log("Couldn't find an episode for " + fileName);
continue;
}
Logger.log("Matched " + fileName + " to TVDB Season " + numbers.season[1] + " Episode " + numbers.episode[1] + ". Renaming...");
files[i].setName(showName + " S" + numbers.season[1] + "E" + numbers.episode[1] + " - " + episode.episodeName + "." + fileExtension);
}
}
function getEpisodeList_() {
var param = {"headers": {"Authorization": "Bearer " + PropertiesService.getScriptProperties().getProperty("token")}};
try {
var search = UrlFetchApp.fetch("https://api.thetvdb.com/search/series?name=" + showName, param).getContentText();
search = JSON.parse(search);
}
catch(e) {
refreshLogin_();
param = {"headers": {"Authorization": "Bearer " + PropertiesService.getScriptProperties().getProperty("token")}};
var search = UrlFetchApp.fetch("https://api.thetvdb.com/search/series?name=" + showName, param).getContentText();
search = JSON.parse(search);
}
Logger.log("Found " + search.data[0].seriesName + "...");
showname = search.data[0].seriesName;
var showId = search.data[0].id;
var episodesList = UrlFetchApp.fetch("https://api.thetvdb.com/series/" + showId + "/episodes", param).getContentText();
episodesList = JSON.parse(episodesList);
var episodes = episodesList.data;
// if there is pagation on the results (100 per page), grab the next ones and add them to the array
while (episodesList.links.next) {
var episodesList = UrlFetchApp.fetch("https://api.thetvdb.com/series/" + showId + "/episodes?page=" + episodesList.links.next, param).getContentText();
episodesList = JSON.parse(episodesList);
episodes = episodes.concat(episodesList.data);
}
// we now have the full list
// Logger.log(JSON.stringify(episodes));
// Logger.log(episodes.length);
return episodes;
}
function refreshLogin_() {
var api = {"apikey": "V2XMYATYE81YASDF", "username": "username123", "userkey": "T8ZP6EYL5QX5ASDF"}; // dummy info, edit these
try {
var token = PropertiesService.getScriptProperties().getProperty("token");
var refresh = UrlFetchApp.fetch("https://api.thetvdb.com/refresh_token", {"headers": {"Authorization": "Bearer " + token}}).getContentText();
refresh = JSON.parse(refresh);
refresh = refresh.token;
PropertiesService.getScriptProperties().setProperty("token", refresh);
Logger.log("Token refreshed");
}
catch(e) {
Logger.log("Not authorized yet. Authorizing...");
var login = UrlFetchApp.fetch("https://api.thetvdb.com/login", {"method": "post", "payload": JSON.stringify(api), "headers": {"Accept": "application/json", "Content-Type": "application/json"}}).getContentText();
login = JSON.parse(login);
login = login.token;
PropertiesService.getScriptProperties().setProperty("token", login);
}
return PropertiesService.getScriptProperties().getProperty("token");
}
function getVideoFilesInFolder_(folderId) {
var files = [];
// files in root
var fileIterator = DriveApp.getFolderById(folderId).getFiles();
while (fileIterator.hasNext()) {
var file = fileIterator.next();
if (file.getMimeType().indexOf("video") > -1) {
files.push(file);
}
}
// get folders
var subfolders = [];
var folderIterator = DriveApp.getFolderById(folderId).getFolders();
while (folderIterator.hasNext()) {
var folder = folderIterator.next();
subfolders.push(folder);
}
// and add their contents too
for (var i in subfolders) {
var subfolderFiles = getVideoFilesInFolder_(subfolders[i].getId());
files = files.concat(subfolderFiles);
}
return files;
}
function fixExtensions() {
var files = getVideoFilesInFolder_(folderId);
for (var i in files) {
var fileName = files[i].getName();
var fileExtension = fileName.split('.').pop();
if (fileExtension.length > 4) {
var mimetype = files[i].getMimeType();
var extension = getExtensionForMimeType_(mimetype);
Logger.log("Adding " + extension + " extension to " + fileName);
files[i].setName(files[i].getName() + "." + extension);
}
}
}
function getExtensionForMimeType_(mime) {
switch(mime.trim().toLowerCase()) {
case "video/mp4":
return "mp4";
break;
case "video/x-matroska":
return "mkv";
break;
case "video/webm":
return "webm";
break;
case "video/quicktime":
return "mov";
break;
case "video/x-m4v":
return "m4v";
break;
case "video/x-msvideo":
return "avi";
break;
case "video/mpeg":
return "mpg";
break;
case "video/ogg":
return "ogv";
break;
case "video/3gpp":
return "3gp";
break;
case "video/3gpp2":
return "3g2";
break;
default:
throw "Unrecgonized video mimetype: " + mime;
}
}
// lifted from https://github.com/clement-escolano/parse-torrent-title
function parse_(name) {
var parser = {"season": [], "episode": []};
// Season
parser.season.push(/S([0-9]{1,2}) ?E[0-9]{1,2}/i);
parser.season.push(/([0-9]{1,2})x[0-9]{1,2}/);
parser.season.push(/(?:Saison|Season)[. _-]?([0-9]{1,2})/i);
// Episode
parser.episode.push(/S[0-9]{1,2} ?E([0-9]{1,2})/i);
parser.episode.push(/[0-9]{1,2}x([0-9]{1,2})/);
parser.episode.push(/[ée]p(?:isode)?[. _-]?([0-9]{1,3})/i);
var toReturn = {};
for (var i in parser) {
for (var j in parser[i]) {
if (parser[i][j].test(name)) {
toReturn[i] = name.match(parser[i][j]);
break;
}
}
}
if (!toReturn.season) {
toReturn.season = ["", seasonOverride];
}
if (!toReturn.episode) {
toReturn.episode = null;
}
return toReturn;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment