diff --git a/README.md b/README.md index 9a66fc8..2031358 100644 --- a/README.md +++ b/README.md @@ -10,8 +10,8 @@ for personal use with your preferred audiobook player during the loan period. # Features -- AutoBooks Web: Uses selenium and chromedriver to download the odm files from overdrive without user interaction. -- Uses odmpy to fulfill and convert odm files to chapterized m4b audiobooks. +- AutoBooks Web: Uses requests to download odm files. +- Uses odmpy to fulfill and convert odm files to m4b audiobooks with chapters. - Moves the generated audiobooks to a chosen folder. - Backs up the download files in case you need to re-download the books. - Logs to console and timestamped logfile. @@ -20,7 +20,7 @@ for personal use with your preferred audiobook player during the loan period. # Prerequisites -- Tools: git, ffmpeg, odmpy, chromedriver (Installed in setup guide.) +- Tools: git, ffmpeg, odmpy (Installed in setup guide.) - Accounts: [Cronitor](https://cronitor.io/) For script monitoring, optional but will display errors if not setup. # Links diff --git a/autobooks/AutoBooks.py b/autobooks/AutoBooks.py index 67c787e..57492ad 100644 --- a/autobooks/AutoBooks.py +++ b/autobooks/AutoBooks.py @@ -1,5 +1,6 @@ import glob import os +import platform import shutil import sys from configparser import ConfigParser @@ -12,20 +13,13 @@ import odmpy.odm as odmpy import pandas as pd import requests -import selenium from loguru import logger -from selenium import webdriver -from selenium.webdriver.chrome.options import Options -from selenium.webdriver.common.by import By -from selenium.webdriver.common.keys import Keys -from utils import InterceptHandler, RedactingFormatter, process_logfile +from utils import InterceptHandler, RedactingFormatter, process_logfile, parse_form, craft_booklist # Set Vars -version = "0.3" # Version number of script +version = "0.4" # Version number of script error_count = 0 -good_odm_list, bad_odm_list, library_list, book_id_list, book_title_list, book_odm_list = ([ -] for i in range(6)) script_dir = os.path.join(Path.home(), "AutoBooks") csv_path = os.path.join(script_dir, 'web_known_files.csv') @@ -35,8 +29,8 @@ else: os.mkdir(script_dir) main_conf = requests.get( - 'https://raw.githubusercontent.com/ivybowman/AutoBooks/main/autobooks_template.conf') - folders = ['log', 'downloads', 'profile', 'source_backup'] + 'https://raw.githubusercontent.com/ivybowman/AutoBooks/main/autobooks_template.ini') + folders = ['log', 'downloads', 'source_backup'] for folder in folders: os.mkdir(os.path.join(script_dir, folder)) with open(os.path.join(script_dir, "autobooks.conf"), mode='wb') as local_file: @@ -47,7 +41,9 @@ # Logging Config LOG_PATH = os.path.join(script_dir, 'log') -LOG_FILENAME = os.path.join(LOG_PATH, "AutoBooks.log") +LOG_FILENAME = os.path.join( + script_dir, 'log', 'AutoBooks-{:%H-%M-%S_%m-%d}.log'.format(datetime.now())) + patterns = ['[34m[1m', '[39m[22m', '[34m[22m', '[35m[22m'] console_log_format = "{time:HH:mm:ss A} [{name}:{function}] {level}: {message}\n{exception}" cronitor_log_format = "[{name}:{function}] {level}: {message}\n{exception}" @@ -56,38 +52,41 @@ logger.configure(handlers=[ {'sink': sys.stderr, "format": console_log_format}, {'sink': LOG_FILENAME, - "format": redacting_formatter.format, "retention": 10, "rotation": "1 day"}, + "format": redacting_formatter.format, "retention": 10}, ]) +# logging.getLogger().setLevel('DEBUG') +# logging.getLogger().addHandler(InterceptHandler()) odmpy.logger.handlers.clear() odmpy.logger.addHandler(InterceptHandler()) # Read config file parser = ConfigParser() -parser.read(os.path.join(script_dir, "autobooks.conf")) -odm_dir = parser.get("DEFAULT", "odm_folder") -out_dir = parser.get("DEFAULT", "out_folder") +parser.read(os.path.join(script_dir, "autobooks.ini")) +config = parser['DEFAULT'] library_count = len(parser.sections()) + # Cronitor Setup https://cronitor.io/ -cronitor.api_key = parser.get("DEFAULT", "cronitor_apikey") -monitor = cronitor.Monitor(parser.get("DEFAULT", "cronitor_name_main")) +cronitor.api_key = config['cronitor_apikey'] +monitor = cronitor.Monitor(config['cronitor_monitor']) # Function to process the books. def process(odm_list): global error_count + good_odm_list = [] + bad_odm_list = [] + odmpy_args = ["odmpy", "dl", "--nobookfolder"] logger.info('Begin processing book list: {}', " ".join(odm_list)) for x in odm_list: - if parser.get('DEFAULT', "test_args") == "True": - odmpy_args = ["odmpy", "dl", "--nobookfolder", x] + if config['odmpy_test_args'] == "True": + odmpy_args.extend([x]) else: - odmpy_args = ["odmpy", "dl", "-c", "-m", "--mergeformat", "m4b", "--nobookfolder", x] + odmpy_args.extend(["-c", "-m", "--mergeformat", "m4b", x]) with patch.object(sys, 'argv', odmpy_args): try: odmpy.run() - except FileNotFoundError: - logger.error("Could not find odm file {}", x) - except FileExistsError: - logger.error("FileAlreadyExists, likely from m4b creation attempt") + except(FileNotFoundError, FileExistsError) as e: + logger.error("Error starting odm {} Message: {}", x, e) except SystemExit as e: bad_odm_list.append(x) if os.path.isfile("cover.jpg"): @@ -96,6 +95,7 @@ def process(odm_list): else: good_odm_list.append(x) logger.info("Book Processing Finished") + return good_odm_list, bad_odm_list # Function to clean up in and out files. @@ -103,11 +103,11 @@ def cleanup(m4b_list, odm_list, odm_folder): global error_count # Move m4b files to out_dir for x in m4b_list: - if os.path.isfile(os.path.join(out_dir + x)): + if os.path.isfile(os.path.join(config["out_folder"] + x)): logger.error("Book {} already exists in out dir skipped", x) error_count += 1 else: - shutil.move(os.path.join(odm_folder, x), os.path.join(out_dir, x)) + shutil.move(os.path.join(odm_folder, x), os.path.join(config["out_folder"], x)) logger.info("Moved book {} to out_dir", x) # Backup source files for x in odm_list: @@ -122,192 +122,144 @@ def cleanup(m4b_list, odm_list, odm_folder): logger.info("Moved file pair {} to source files", x) -# Function for login -def web_login(driver, name, card_num, pin, select): - global error_count - logger.info("Logging into library: {}", name) - # Attempt selecting library from dropdown - if select != "False": - select_box = driver.find_element( - By.XPATH, '//input[@id="signin-options"]') - webdriver.ActionChains(driver).move_to_element( - select_box).click().send_keys(select).perform() - sleep(1) - webdriver.ActionChains(driver).send_keys( - Keys.ARROW_DOWN).send_keys(Keys.RETURN).perform() - # Attempt sending card number - try: - driver.find_element(By.ID, "username").send_keys(card_num) - except selenium.common.exceptions.NoSuchElementException: - logger.critical("Can't find card number field skipped library {}", name) - error_count += 1 - # Attempt sending pin Note:Some pages don't have pin input - if pin != "False": - driver.find_element(By.ID, "password").send_keys(pin) - driver.find_element( - By.CSS_SELECTOR, "button.signin-button.button.secondary").click() - sleep(5) +def web_login(subdomain, card_num, pin, select): + login_session = requests.Session() + logger.info("Logging into: {}", subdomain) + box = login_session.get(f'https://{subdomain}.overdrive.com/account/ozone/sign-in?forward=%2F') + logger.success('Fetched login page. Status Code: {}', box.status_code) + form_list = parse_form(box, "loginForms")['forms'] + x = 0 + if len(form_list) != 1: + for form in form_list: + if select in form['displayName']: + logger.success('Matched Config: {} to {}', select, form['displayName']) + print(select, "Matches: ", form['displayName'], "ils:", form['ilsName']) + x = form_list.index(form) + break + sleep(0.5) + auth = login_session.post(f'https://{subdomain}.overdrive.com/account/signInOzone', + params=(('forwardUrl', '/'),), + data={ + 'ilsName': form_list[x]['ilsName'], + 'authType': form_list[x]['type'], + 'libraryName': form_list[x]['displayName'], + 'username': card_num, + 'password': pin + }) + logger.success("Logged into: {} Status Code: {} ", subdomain, auth.status_code) + # print("AUTH URL: ", auth.url) + return auth.url, form_list[x]['ilsName'], login_session # Function to download loans from OverDrive page -def web_dl(driver, df, name): +def web_dl(df, session, base_url, name, book_list): global error_count - # Gather all book title elements and check if any found - books = driver.find_elements(By.XPATH, '//a[@tabindex="0"][@role="link"]') - if len(books) == 0: + df_out = pd.DataFrame() + logger.info("Begin DL from library: {} ", name) + odm_list = [] + book_count = 0 + if len(book_list) == 0: logger.warning("Can't find books skipped library: {}", name) error_count += 1 - return () - else: - logger.info("Begin DL from library: {} ", name) - book_count = 0 - for i in books: - # Fetch info about the book - book_url = i.get_attribute('href') - book_info = i.get_attribute('aria-label') - book_info_split = book_info.split(". Audiobook. Expires in") - book_dl_url = book_url.replace( - '/media/', '/media/download/audiobook-mp3/') - book_id = int(''.join(filter(str.isdigit, book_url))) - book_title = book_info_split[0] - - # Check if found book is a not known audiobook - if "Audiobook." in book_info: - if str(book_id) in df['book_id'].to_string(): - logger.info('Skipped {} found in known books', book_title) - else: - # Download book - driver.get(book_dl_url) - logger.info("Downloaded book: {}", book_title) - book_odm = max(glob.glob("*.odm"), key=os.path.getmtime) - book_count += 1 - - # Add book data to vars - library_list.append(name) - book_id_list.append(book_id) - book_title_list.append(book_title) - book_odm_list.append(book_odm) - sleep(1) - sleep(1) - logger.info("Finished downloading {} books from library {}", - book_count, name) - return () - - -def main_run(): - # AutoBooks - logger.info("Started AutoBooks V.{} By:IvyB", version) - # Try to change to ODM folder - try: - os.chdir(odm_dir) - except FileNotFoundError: - logger.critical("The provided .odm dir was not found, exiting") - sys.exit(1) - else: - odm_list = glob.glob("*.odm") - monitor.ping(state='run', - message=f"AutoBooks by IvyB v.{version} \n" - f"logfile: {LOG_FILENAME}\n odm_dir: {odm_dir} \n out_dir: {out_dir} \n" - f"odm_list:{odm_list}") - - # Check if any .odm files exist in odm_dir - if len(odm_list) == 0: - monitor.ping(state='fail', message='Error: No .odm files found, exiting', - metrics={'error_count': error_count}) - logger.critical("No .odm files found, exiting") - sys.exit(1) - else: - process(odm_list) - # Cleanup files - m4blist = glob.glob("*.m4b") - cleanup(m4blist, good_odm_list, odm_dir) - # Send complete event and log to Cronitor - log_str = process_logfile(LOG_FILENAME, terms=( - "Downloading", "expired", "generating", "merged", "saved")) - monitor.ping(state='complete', message=log_str, - metrics={'count': len(odm_list), 'error_count': error_count}) + return df_out + for book in book_list: + if book['format'] != "ebook-overdrive": + print("AudioBook Info: ", book['title'], "-", book['id'], "-", book['format']) + id_query = df.query('book_id == ' + book['id']) + if id_query.empty is False: + logger.info('Skipped "{}" found in known books', book['title']) + else: + # Short wait then download ODM + sleep(0.5) + odm = session.get(f'{base_url}media/download/audiobook-mp3/{book["id"]}') + odm_filename = odm.url.split("/")[-1].split('?')[0] + with open(odm_filename, "wb") as f: + f.write(odm.content) + odm_list.append(odm_filename) + book_count += 1 + print(odm_filename) + # Save book info to dataframe + df_book = pd.DataFrame([[name, book['id'], book['title'], odm_filename]], + columns=['ils_name', 'book_id', 'book_title', 'book_odm']) + df_out = pd.concat([df_out, df_book]) + logger.info("Downloaded book: {} as {}", book['title'], odm_filename) + sleep(1) + logger.info("Finished downloading {} books from library {}", + book_count, name) + return df_out, odm_list # AutoBooks Web Code def web_run(): + global error_count + web_odm_list = [] if len(parser.sections()) == 0: logger.critical("No libraries configured!") sys.exit(1) else: - logger.info("Started AutoBooks Web V.{} By:IvyB", version) + logger.info("Started AutoBooks Web V.{} By:IvyB on Host: {}", version, platform.node()) monitor.ping(state='run', - message=(f'AutoBooks Web by IvyB V.{version} \n' + message=(f'AutoBooks Web by IvyB V.{version} on Host: {platform.node()} \n' f'logfile: {LOG_FILENAME} \n LibraryCount: {str(library_count)}')) - # Configure WebDriver options - options = Options() - prefs = { - "download.default_directory": os.path.join(script_dir, "downloads"), - "download.prompt_for_download": False, - "download.directory_upgrade": True - } - options.add_argument('user-data-dir=' + - os.path.join(script_dir, "profile")) - # Headless mode check - if parser.get('DEFAULT', "web_headless") == "True": - options.add_argument('--headless') - options.add_argument('--disable-gpu') - options.add_experimental_option('prefs', prefs) - driver = webdriver.Chrome(options=options) - # Attempt to read known files csv for checking books if os.path.exists(csv_path): df = pd.read_csv(csv_path, sep=",") else: # Failing above create an empty df for checking df = pd.DataFrame({ - 'book_id': book_id_list, + 'book_id': [''], }) os.chdir(os.path.join(script_dir, "downloads")) # For every library, open site, attempt sign in, and attempt download. - for i in range(0, len(parser.sections())): - library_index = 'library_' + str(i) - library_subdomain = parser.get(library_index, "library_subdomain") - library_name = parser.get(library_index, "library_name") - logger.info("Started library {}", library_name) - url = "https://" + library_subdomain + ".overdrive.com/" - driver.get(url + "account/loans") - sleep(3) - # Check signed in status and either sign in or move on - if "/account/ozone/sign-in" in driver.current_url: - web_login(driver, library_name, parser.get(library_index, "card_number"), - parser.get(library_index, "card_pin"), parser.get(library_index, "library_select")) - web_dl(driver, df, library_name) - sleep(2) - # Output book data to csv - df_out = pd.DataFrame({ - 'library_name': library_list, - 'book_id': book_id_list, - 'book_title': book_title_list, - 'book_odm': book_odm_list - }) - if os.path.exists(csv_path): - df_out.to_csv(csv_path, mode='a', index=False, header=False) - else: - df_out.to_csv(csv_path, mode='w', index=False, header=True) - driver.close() + for i in range(0, library_count): + lib_conf = parser['library_' + str(i)] + logger.info("Begin Processing library: {}", lib_conf['library_name']) + sleep(0.5) + base_url, ils_name, session = web_login(lib_conf['library_subdomain'], + lib_conf['card_number'], + lib_conf['card_pin'], + lib_conf['library_select'], ) + if not base_url.endswith('/'): + base_url = base_url + '/' + loans = session.get(f'{base_url}account/loans') + sleep(0.5) + if loans.status_code == 200: + book_list = craft_booklist(loans) + if len(book_list) != 0: + df_out, odm_list = web_dl(df, session, base_url, ils_name, book_list) + sleep(0.5) + if web_odm_list == [] and odm_list != []: + web_odm_list = odm_list + else: + web_odm_list.extend(odm_list) + sleep(2) + # Write book data to csv + if os.path.isfile(csv_path): + df_out.to_csv(csv_path, mode='a', index=False, header=False) + else: + df_out.to_csv(csv_path, mode='w', index=False, header=True) + else: + logger.warning("Can't find books skipped library: {}", lib_conf['library_name']) + error_count += 1 + session.close() logger.info("AutoBooksWeb Complete") - web_odm_list = glob.glob("*.odm") # Process log file for Cronitor. - process_logfile(LOG_FILENAME, terms=("web", "ERROR")) - monitor.ping(state='complete', message="".join(web_odm_list), + web_log = process_logfile(LOG_FILENAME, terms=("web", "ERROR")) + monitor.ping(state='complete', + message=f'{"".join(web_log)}', metrics={'count': len(web_odm_list), 'error_count': error_count}) # Call DL to process odm files from web if len(web_odm_list) != 0: - logger.info("Started AutoBooks V.{} By:IvyB", version) + logger.info("Started AutoBooks V.{} By:IvyB on Host: {}", version, platform.node()) monitor.ping(state='run', - message=f"AutoBooks by IvyB v.{version} \n" - f"logfile: {LOG_FILENAME}\n odm_dir: {odm_dir} \n out_dir: {out_dir} \n" + message=f"AutoBooks by IvyB v.{version} on Host: {platform.node()} \n" + f"logfile: {LOG_FILENAME}\n out_dir: {config['out_folder']} \n" f"odm_list:{web_odm_list}") - process(web_odm_list) + good_odm_list, bad_odm_list = process(web_odm_list) m4blist = glob.glob("*.m4b") cleanup(m4blist, good_odm_list, os.path.join( script_dir, "downloads")) @@ -317,8 +269,42 @@ def web_run(): # Send complete event and log to Cronitor monitor.ping(state='complete', message=log_str, metrics={'count': len(web_odm_list), 'error_count': error_count}) - # return["\n".join(title_list), error_count] -if __name__ == "__main__" and parser.get('DEFAULT', "test_run") == "True": +def main_run(): + # AutoBooks + logger.info("Started AutoBooks V.{} By:IvyB", version) + # Try to change to ODM folder + try: + os.chdir(config["odm_folder"]) + except FileNotFoundError: + logger.critical("The provided .odm dir was not found, exiting") + sys.exit(1) + else: + odm_list = glob.glob("*.odm") + monitor.ping(state='run', + message=f"AutoBooks by IvyB v.{version} \n" + f"logfile: {LOG_FILENAME}\n odm_dir: {config['odm_folder']} \n " + f"out_dir: {config['out_folder']} \n" + f"odm_list:{odm_list}") + + # Check if any .odm files exist in odm_dir + if len(odm_list) == 0: + monitor.ping(state='fail', message='Error: No .odm files found, exiting', + metrics={'error_count': error_count}) + logger.critical("No .odm files found, exiting") + sys.exit(1) + else: + good_odm_list, bad_odm_list = process(odm_list) + # Cleanup files + m4blist = glob.glob("*.m4b") + cleanup(m4blist, good_odm_list, config["odm_folder"]) + # Send complete event and log to Cronitor + log_str = process_logfile(LOG_FILENAME, terms=( + "Downloading", "expired", "generating", "merged", "saved")) + monitor.ping(state='complete', message=log_str, + metrics={'count': len(odm_list), 'error_count': error_count}) + + +if __name__ == "__main__" and config["test_run"] == "True": web_run() diff --git a/autobooks/DiscordBot.py b/autobooks/DiscordBot.py index bc2c4af..1803a9f 100644 --- a/autobooks/DiscordBot.py +++ b/autobooks/DiscordBot.py @@ -7,13 +7,10 @@ import pandas as pd from discord.ext import commands -from AutoBooks import web_run, main_run, version, script_dir, parser, csv_path, LOG_FILENAME, logger +from AutoBooks import web_run, main_run, version, script_dir, config, csv_path, LOG_FILENAME, logger # Bot Settings -try: - token = parser.get("DEFAULT", "discord_bot_token") -except KeyError: - logger.critical("Bot token field not found in config file, exiting.") +token = config["discord_bot_token"] bot = commands.Bot(command_prefix='?') @@ -72,7 +69,7 @@ async def hello(ctx): try: df = pd.read_csv(csv_path, sep=",") embed_var = discord.Embed(title="Autobooks Known Books", - description=df['audiobook_title'].to_string(index=False), color=0xFFAFCC) + description=df['book_title'].to_string(index=False), color=0xFFAFCC) embed_var.set_footer(text="OS: " + platform.platform() + " Host: " + os.uname()) await ctx.channel.send(embed=embed_var) except FileNotFoundError: diff --git a/autobooks/testconf.py b/autobooks/testconf.py new file mode 100644 index 0000000..e8702f4 --- /dev/null +++ b/autobooks/testconf.py @@ -0,0 +1,34 @@ +import glob +import os +import shutil +import sys +from configparser import ConfigParser +from configobj import ConfigObj +from datetime import datetime +from pathlib import Path +from time import sleep +from unittest.mock import patch +import json +import cronitor +import odmpy.odm as odmpy +import pandas as pd +import requests +from loguru import logger + +script_dir = os.path.join(Path.home(), "AutoBooks") +#config = ConfigObj(os.path.join(script_dir, "autobooks.ini")) +# Read config file +parser = ConfigParser() +parser.read(os.path.join(script_dir, "autobooks.ini")) +print(parser.sections()) +config = parser['DEFAULT'] +print(section['cronitor_apikey']) +section = parser.default_section + +#print(config.keys()) +#odm_dir = parser.get("DEFAULT", "odm_folder") +#out_dir = parser.get("DEFAULT", "out_folder") +#library_count = len(parser.sections()) +# Cronitor Setup https://cronitor.io/ +#cronitor.api_key = parser.get("DEFAULT", "cronitor_apikey") +#monitor = cronitor.Monitor(parser.get("DEFAULT", "cronitor_name_main")) \ No newline at end of file diff --git a/autobooks/utils.py b/autobooks/utils.py index 43c98f8..33d3e22 100644 --- a/autobooks/utils.py +++ b/autobooks/utils.py @@ -1,6 +1,7 @@ import logging - +import lxml.etree from loguru import logger +import json # Formatter to remove patterns from log output @@ -45,3 +46,31 @@ def process_logfile(logfile, terms=None): if any(term in line for term in terms): log_list.append(line) return "".join(log_list) + + +def parse_form(box, sort): + form_dict = {} + txt = lxml.etree.HTML(box.content) + js = str(txt.xpath(f"//script[contains(text(), 'window.OverDrive.{sort} =')]/text()")[0]).strip() + split_1 = js.split(sep=" = ") + for i in range(0, len(split_1)): + if "window.OverDrive." + sort in split_1[i]: + form_dict = split_1[i + 1].strip().split(';')[0] + break + return dict(json.loads(form_dict)) + + +def craft_booklist(loans_page): + book_dict = parse_form(loans_page, "mediaItems") + book_list_parse = [] + for i in book_dict: + book_format = book_dict[i]['overDriveFormat']['id'] + book_title = book_dict[i]['title'] + book_id = book_dict[i]['id'] + book_parse = { + 'id': book_id, + 'title': book_title, + 'format': book_format, + } + book_list_parse.append(book_parse) + return book_list_parse diff --git a/setup.md b/setup.md index e3c8b8e..50ca715 100644 --- a/setup.md +++ b/setup.md @@ -13,8 +13,8 @@ Open a PowerShell window then follow the steps below. `Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser` 2. Run the Scoop installer, this is a package manager used to install some prerequisites. `iwr -useb get.scoop.sh | iex` -3. Install prerequisites. Note: Chromedriver requires Google Chrome to be installed. -`scoop install ffmpeg chromedriver git` +3. Install prerequisites. +`scoop install ffmpeg git` ## macOS Setup Guide @@ -23,20 +23,15 @@ Open a terminal window then follow the steps below. Works with both M1 and Intel 2. Install Homebrew. Be sure to follow instructions at the end for adding it to path. `/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"` 3. Install prerequisites. On trying to use these tools you might see an unideditifed developer pop up, this is normal just open the folder and ctrl+click or right click on the file and click open. -Note: Chromedriver requires Google Chrome to be installed. -`brew install ffmpeg chromedriver` +`brew install ffmpeg` ## Debian/Ubuntu Linux Setup Guide Open a terminal window then follow the steps below. 2. Update package list. `sudo apt-get update` -2. Install most prerequisites. -`sudo apt-get install -y unzip ffmpeg git` -3. Run script to install the other prerequisites. Note: Chrome is required for AutoBooksWeb. -With Google Chrome: `/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/ivybowman/AutoBooks/main/ubuntusetupchrome.sh)"` -Without Google Chrome: `/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/ivybowman/AutoBooks/main/ubuntusetup.sh)"` -Note: AutoBooks Web is not compatible with WSL, and requires X11 forwarding to run via ssh if headless mode is disabled. +2. Install prerequisites. +`sudo apt-get install -y ffmpeg git` ## AutoBooks Install & Setup (All Operating Systems) @@ -45,17 +40,17 @@ Note: AutoBooks Web is not compatible with WSL, and requires X11 forwarding to r To install from the latest source run the following command. `pip3 install git+https://git@github.com/ivybowman/autobooks.git --upgrade --force-reinstall` To install from a specific version run the following command. -`pip3 install git+https://git@github.com/ivybowman/autobooks.git@v0.2.1-alpha --upgrade` +`pip3 install git+https://git@github.com/ivybowman/autobooks.git@v0.3-alpha --upgrade` To uninstall AutoBooks run the following command. `pip3 uninstall autobooks` ### Configuration 1. Open a terminal and run `autobooks` this will run setup commands to create the data folder. -2. Edit the `autobooks.conf` file using one of the commands below or by browsing to the autobooks folder inside your home directory. -- Windows(GUI) PowerShell: `notepad $env:USERPROFILE\AutoBooks\autobooks.conf` -- Windows(GUI) Command Prompt: `notepad %userprofile%\AutoBooks\autobooks.conf` -- macOS(GUI) Terminal: `open -a TextEdit ~/AutoBooks/autobooks.conf` -- Linux or macOS(CLI): `nano ~/AutoBooks/autobooks.conf` +2. Edit the `autobooks.ini` file using one of the commands below or by browsing to the autobooks folder inside your home directory. +- Windows(GUI) PowerShell: `notepad $env:USERPROFILE\AutoBooks\autobooks.ini` +- Windows(GUI) Command Prompt: `notepad %userprofile%\AutoBooks\autobooks.ini` +- macOS(GUI) Terminal: `open -a TextEdit ~/AutoBooks/autobooks.ini` +- Linux or macOS(CLI): `nano ~/AutoBooks/autobooks.ini` diff --git a/setup.py b/setup.py index f840a05..4e78d58 100644 --- a/setup.py +++ b/setup.py @@ -1,6 +1,6 @@ from setuptools import setup -VERSION = '0.3' +VERSION = '0.4' DESCRIPTION = 'Python tool to automate processing a batch of OverDrive audiobooks.' LONG_DESCRIPTION = 'Python tool to automate processing a batch of OverDrive audiobooks.' @@ -21,8 +21,8 @@ "autobooks-discord = autobooks.AutoBooksDiscord:run" ] }, - install_requires=['odmpy @ git+https://git@github.com/ping/odmpy.git', "cronitor", "pandas", "discord.py", - "selenium", "requests", "loguru"], + install_requires=["odmpy @ git+https://git@github.com/ping/odmpy.git", "cronitor", "pandas", "discord.py", + "requests", "loguru", "lxml"], include_package_data=True, platforms="any", keywords=['python', 'AutoBooks'],