├── .gitignore ├── LICENSE ├── README.md ├── main.py └── requirements.txt /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | share/python-wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | MANIFEST 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .nox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *.cover 49 | *.py,cover 50 | .hypothesis/ 51 | .pytest_cache/ 52 | cover/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | .pybuilder/ 76 | target/ 77 | 78 | # Jupyter Notebook 79 | .ipynb_checkpoints 80 | 81 | # IPython 82 | profile_default/ 83 | ipython_config.py 84 | 85 | # pyenv 86 | # For a library or package, you might want to ignore these files since the code is 87 | # intended to run in multiple environments; otherwise, check them in: 88 | # .python-version 89 | 90 | # pipenv 91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 94 | # install all needed dependencies. 95 | #Pipfile.lock 96 | 97 | # poetry 98 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 99 | # This is especially recommended for binary packages to ensure reproducibility, and is more 100 | # commonly ignored for libraries. 101 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 102 | #poetry.lock 103 | 104 | # pdm 105 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 106 | #pdm.lock 107 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 108 | # in version control. 109 | # https://pdm.fming.dev/#use-with-ide 110 | .pdm.toml 111 | 112 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 113 | __pypackages__/ 114 | 115 | # Celery stuff 116 | celerybeat-schedule 117 | celerybeat.pid 118 | 119 | # SageMath parsed files 120 | *.sage.py 121 | 122 | # Environments 123 | .env 124 | .venv 125 | env/ 126 | venv/ 127 | ENV/ 128 | env.bak/ 129 | venv.bak/ 130 | 131 | # Spyder project settings 132 | .spyderproject 133 | .spyproject 134 | 135 | # Rope project settings 136 | .ropeproject 137 | 138 | # mkdocs documentation 139 | /site 140 | 141 | # mypy 142 | .mypy_cache/ 143 | .dmypy.json 144 | dmypy.json 145 | 146 | # Pyre type checker 147 | .pyre/ 148 | 149 | # pytype static type analyzer 150 | .pytype/ 151 | 152 | # Cython debug symbols 153 | cython_debug/ 154 | 155 | # PyCharm 156 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 157 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 158 | # and can be added to the global gitignore or merged into this file. For a more nuclear 159 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 160 | #.idea/ 161 | 162 | # Environment variables file 163 | .env 164 | 165 | output 166 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Jacob Whitehead 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # kdp-gpt 2 | An open-source python script that can generate books using ChatGPT to be listed on Amazon KDP. The script will ask you for a title and description for the book, as well as the tone and how many chapters you want to include; a pdf file will then be generated. 3 | 4 | ## Setup 5 | 1. Navigate to https://platform.openai.com/account/api-keys to generate an API key 6 | 2. Create a .env file and add your key 7 | ``` 8 | OPENAI_API_KEY=... 9 | ``` 10 | 3. Create python virtual environment, this could be done in many ways but I prefer using venv 11 | ```bash 12 | python3 -m venv .venv 13 | ``` 14 | 4. Install requirements.txt 15 | ```bash 16 | pip install -r requirements.txt 17 | ``` 18 | 19 | ## Usage 20 | ```bash 21 | python3 main.py 22 | ``` 23 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | import os 2 | import openai 3 | import urllib.request 4 | import json 5 | from dotenv import load_dotenv 6 | from reportlab.pdfgen import canvas 7 | from reportlab.lib.units import inch 8 | from reportlab.platypus import Paragraph 9 | from reportlab.lib.styles import ParagraphStyle 10 | from reportlab.lib.enums import TA_CENTER 11 | 12 | # Load env variables from .env file 13 | load_dotenv() 14 | openai.api_key = os.getenv('OPENAI_API_KEY') 15 | 16 | # Message history to maintain conversation context 17 | current_messages = [] 18 | 19 | # List of chapters and corresponding images 20 | book_chapters = [] 21 | 22 | # User input 23 | title = input('Provide a title for the book: ') 24 | description = input('Provide a description for the book: ') 25 | tone = input('Provide the tone that the book should be written in: ') 26 | chapter_count = int( 27 | input('Provide the number of chapters that the book should contain: ')) 28 | 29 | 30 | def send_message(messages: list): 31 | response = openai.ChatCompletion.create( 32 | model='gpt-3.5-turbo', 33 | temperature=0, 34 | messages=messages 35 | ) 36 | return response.choices[0].message.content 37 | 38 | 39 | def create_and_send_message(role: str, content: str): 40 | new_message = {'role': role, 'content': content} 41 | current_messages.append(new_message) 42 | message_res = send_message(current_messages) 43 | return message_res 44 | 45 | 46 | def download_url(image_url: str, file_name: str, max_retries: int): 47 | if max_retries == 0: 48 | raise Exception('Max download attempts reached') 49 | try: 50 | urllib.request.urlretrieve(image_url, file_name) 51 | except ValueError: 52 | download_url(image_url, file_name, max_retries - 1) 53 | 54 | 55 | def create_image(prompt: str, chapter_index: int): 56 | response = openai.Image.create( 57 | prompt=prompt, 58 | n=1, 59 | size='900x720', 60 | ) 61 | image_url = response['data'][0]['url'] 62 | file_name = f'output/{chapter_index}.png' 63 | download_url(image_url, file_name, 5) 64 | return file_name 65 | 66 | 67 | # Provide chatgpt context 68 | create_and_send_message('system', 'You are an expert author') 69 | 70 | # Generate a list of chapters 71 | print('Generating chapters') 72 | chapter_descs = create_and_send_message( 73 | 'user', f'Write a list of {chapter_count} chapters for a book called {title}, about {description}, with {tone} tone').split('\n\n') 74 | it = iter(chapter_descs) 75 | chapter_descs = [f'{x}{y}' for x, y in zip(it, it)] 76 | 77 | # Generate chapter texts 78 | for i in range(chapter_count): 79 | chapter_no = i + 1 80 | print(f'Generating chapter {chapter_no}/{chapter_count}') 81 | chapter_text = create_and_send_message( 82 | 'user', f'Write chapter number {chapter_no} in at least 500 words') 83 | prompt = chapter_descs[i] 84 | chapter_image = create_image(prompt, i) 85 | book_chapter = { 86 | 'text': chapter_text, 87 | 'image': chapter_image, 88 | } 89 | book_chapters.append(book_chapter) 90 | 91 | # Dump raw data 92 | with open('output/raw_book_data.json', 'w') as final: 93 | json.dump(book_chapters, final) 94 | 95 | # Generate pdf file 96 | # TODO: Fix this so that words don't wrap https://docs.reportlab.com/reportlab/userguide/ch1_intro/ 97 | print('Generating book pdf') 98 | pdf_file_name = title.replace(' ', '') 99 | pdf = canvas.Canvas(f'output/{pdf_file_name}.pdf') 100 | pdf.setTitle(title) 101 | 102 | title_style = ParagraphStyle('title') 103 | title_style.textColor = 'black' 104 | title_style.borderColor = 'black' 105 | title_style.borderWidth = 1 106 | title_style.alignment = TA_CENTER 107 | title_style.leading = 120 108 | title_style.fontSize = 50 109 | 110 | chapter_style = ParagraphStyle('chapter') 111 | chapter_style.textColor = 'black' 112 | chapter_style.borderColor = 'black' 113 | chapter_style.borderWidth = 1 114 | chapter_style.alignment = TA_CENTER 115 | chapter_style.leading = 120 116 | chapter_style.fontSize = 28 117 | 118 | body_style = ParagraphStyle('body') 119 | body_style.textColor = 'black' 120 | body_style.borderColor = 'black' 121 | body_style.borderWidth = 1 122 | body_style.alignment = TA_CENTER 123 | body_style.leading = 120 124 | body_style.fontSize = 14 125 | 126 | for chapter in book_chapters: 127 | paragraphs = chapter.split('\n\n') 128 | chapter_title = paragraphs[0] 129 | paragraphs.pop(0) 130 | # TODO: Add chapter title to page with chapter para style 131 | # TODO: Add each remaining paragraph with body para style 132 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | openai 2 | python-dotenv 3 | reportlab 4 | --------------------------------------------------------------------------------