CSV to Description
This program takes the table from videos_csv_writer and copies the video's description metadata into the individual video's description field in Google Photos.
Python (with re, time, and csv modules), chromedriver, selenium, selenium stealth
# REQUIRES Selenium, selenium_stealth, and chromedriver installed on machine!
# pip install selenium
# pip install selenium_stealth
# Download latest chromedriver here: https://chromedriver.chromium.org/
# Put chromedriver in /usr/local/bin
# IMPORTANT If this is your first time running thise code, you will need to login to your Google Account manually before you can backup your photos. Follow these steps:
#1 This program will create a directory whereever this program is saved called "Chrome" where the profile data is stored. This will allow the webdriver to stay logged into your Google account. Keep this folder if you want to remian logged in.
#2 Run this program and login to your Google account. Afterwards, navigate to Google Photos and copy the link of where you want to start the descriptionizer (This may be an album with all the photos that need descriptions). Then, set the "photos_start" varaible equal to that link, choose the amount of photos you want the program to loop over, and set the "logged_in" varialbe to True:
photos_start = '#LINK THE FIRST PHOTO WHERE THE DESCRIPTIONIZER SHOULD START#'
NUM_PHOTOS = 25
logged_in = False
#4 Save the pathname of the CSV with the descriptions (from the videos_csv_writer program) to the following variable:
filename = "video_descriptions.csv"
#4 Google Photos' code can be weird sometimes. It might take a couple of runs before it starts to recognize the metadata fields, but once it does, it should run flawlessly afterwards.
import csv
import time
import re
from selenium import webdriver
from selenium_stealth import stealth
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.common.action_chains import ActionChains
from selenium.webdriver.chrome.options import Options
# Sets all the necessary options for Selenium to run properly.
options = webdriver.ChromeOptions()
options.add_argument("start-maximized")
options.add_experimental_option("excludeSwitches", ["enable-automation"])
options.add_experimental_option('useAutomationExtension', False)
options.add_argument(r"--user-data-dir=Chrome/Default") #Path to your chrome profile
options.add_argument(r'--profile-directory=Profile 3')
driver = webdriver.Chrome(options=options, executable_path=r"/usr/local/bin/chromedriver")
# Sets the chromedriver to stealth mode so you can log in properly.
stealth(driver,
languages=["en-US", "en"],
vendor="Google Inc.",
platform="macos",
webgl_vendor="Intel Inc.",
renderer="Intel Iris OpenGL Engine",
fix_hairline=True,
)
# Initializes some variables:
photo_num = 0
f = open(filename, "r")
# if an info panel fails to load, this runs through a sequence that guarenttes is shows up and is recorded
def error_solver(i, count):
if (count < 1):
print(count)
print('\x1b[6;30;42m' + "Solving Error..." + '\x1b[0m')
time.sleep(.1)
actions = ActionChains(driver)
actions.send_keys(Keys.LEFT)
actions.perform()
time.sleep(.1)
go_next()
count += 1
begin_find(count, i)
else:
print('\x1b[6;30;42m' + "Solving Stubborn Error..." + '\x1b[0m')
time.sleep(.75)
go_next()
time.sleep(.5)
go_next()
time.sleep(.5)
actions = ActionChains(driver)
actions.send_keys(Keys.LEFT)
actions.perform()
actions.perform()
time.sleep(.5)
count += 1
begin_find(count, i)
# first function that runs. Tt finds the info panel and finds the string of info for the active (viewing) photo
def begin_find(count, i):
# Finding Info Group with XPATH
time.sleep(0.25)
info = './/*[@id="ow45"]/div[2]'
WebDriverWait(driver, 5).until(EC.visibility_of_all_elements_located((By.XPATH, info)))
Group1 = driver.find_elements_by_xpath(info)
for items in Group1:
# Splitting strings into the 3 pieces
text = items.get_attribute("innerHTML")
start = text.find("c-wiz")
end = text.find("</c-data></c-wiz>") + len("</c-data></c-wiz>")
# 1st piece
substring1 = text[start:end]
# 2nd piece --> need to make it so that if WUbige appears twice, it needs to cut into 3 pieces.
substring2 = text[end:]
# 3rd piece
twoend = substring2.find("</c-data></c-wiz>") + len("</c-data></c-wiz>")
substring3 = substring2[twoend:]
substring2 = substring2[:twoend]
#This determines which substring contains the active photo (it is random between the three)
if "style=\"display: none;\"" not in substring1:
description, title = find_info(substring1, i, count)
elif "style=\"display: none;\"" not in substring2:
description, title = find_info(substring2, i, count)
elif "style=\"display: none;\"" not in substring3:
description, title = find_info(substring3, i, count)
# If no active data is found, there is a problem:
elif "style=\"display: none;\"" in substring1 and substring2 and substring3:
print('\x1b[6;30;42m' + "Error: No Info found" + '\x1b[0m')
time.sleep(.5)
count += 1
if count == 3:
driver.close()
else:
error_solver(i, count)
with open(filename, mode='r') as csv_file:
csv_reader = csv.reader(csv_file, delimiter=',')
line_count = 0
# Loops through each row in the csv.
for row in csv_reader:
grab_des = False
col_count = 0
for col in row:
col_count += 1
if col_count == 1:
# If the column filename equals the active filename, then adding the description is a GO!
if col == title:
grab_des = True
if grab_des == True and col_count == 2:
csv_des = col
commas = csv_des.replace("`", ",")
global target_des
target_des = commas.replace("\\n", "\n")
grab_des = False
if target_des in description:
return True
else:
return False
# Finds the description and filename in the active photo's info panel
def find_info(a_substring, i, count):
if "class=\"n4jc2d\" aria-hidden=\"true\">" in a_substring:
des_start = a_substring.find("class=\"n4jc2d\" aria-hidden=\"true\">") + len("class=\"n4jc2d\" aria-hidden=\"true\">")
temp_des = a_substring[des_start:]
des_end = temp_des.find("</div>")
description = ("\"" + temp_des[:des_end] + "\"")
if "Add a description" in description:
description = ""
else:
description = ""
if "aria-label=\"Filename:" in a_substring:
title_start = a_substring.find("aria-label=\"Filename:") + len("aria-label=\"Filename:") + 1
temp_title = a_substring[title_start:]
title_end = temp_title.find("\">")
title = temp_title[:title_end]
else:
title = ""
return (description, title)
# Adds the description to the description field in the Google Photos' Info panel.
def add_description(count):
des_path = "//*[@id=\"ow45\"]/div[2]/c-wiz[1]/div/div[2]/div/div/div/textarea"
des_path_2 = "//*[@id=\"ow45\"]/div[2]/c-wiz[2]/div/div[2]/div/div/div/textarea"
time.sleep(1)
# The description field is different for for the first photo vs. the rest.
if photo_num == 1:
driver.find_element_by_xpath(des_path).click()
# Since there may already be content in the description, it is necessary to key down to the botton of the field before typing.
for i in range(1,50):
driver.find_element_by_xpath(des_path).send_keys(Keys.DOWN)
driver.find_element_by_xpath(des_path).send_keys("\n" + target_des + Keys.TAB)
elif photo_num > 1:
driver.find_element_by_xpath(des_path_2).click()
# Since there may already be content in the description, it is necessary to key down to the botton of the field before typing.
for i in range(1,50):
driver.find_element_by_xpath(des_path_2).send_keys(Keys.DOWN)
driver.find_element_by_xpath(des_path_2).send_keys("\n" + target_des + Keys.TAB)
# pretty much clicks the next button to go to the next funtion
def go_next():
time.sleep(0.5)
actions = ActionChains(driver)
actions.send_keys(Keys.RIGHT)
actions.perform()
# running though all of the photos specified according to NUM_PHOTOS, closing Chrome and the program at the end.
if logged_in:
driver.get(photos_start)
time.sleep(1)
for i in range(1, NUM_PHOTOS+1):
photo_num += 1
count = 0
comment_in_description = begin_find(count, i)
if comment_in_description == False:
add_description(count)
go_next()
else:
print("comment already there / none to start")
go_next()
else:
driver.get("https://photos.google.com/login")
time.sleep(10000)
print('\x1b[6;30;42m' + "Finished commenting " + str(NUM_PHOTOS) + " photos!" + '\x1b[0m')
f.close()
driver.quit()