Compare commits

..

2 Commits

Author SHA1 Message Date
e96f23ddc8 revised README.md 2023-09-11 21:04:51 +03:00
28e6c8d611 decoupled 2023-09-11 20:35:55 +03:00
8 changed files with 135 additions and 158 deletions

107
README.md
View File

@ -1,67 +1,76 @@
# AnkiAI # AnkiAI - Automated Anki Deck Creator
AnkiAI is a robust system that converts images containing text into structured Anki cards using Optical Character Recognition (OCR) and OpenAI's GPT-4 language model. Users can quickly generate decks of flashcards from their images for effective study. AnkiAI is a tool that leverages OCR (Optical Character Recognition) and GPT-3's powerful natural language processing capabilities to automatically generate Anki decks from images containing text.
## Features ### Overview
- Converts image content to textual content using OCR.
- Uses OpenAI's GPT-4 model to structure the content into Anki decks and cards.
- Outputs the structured content as an Anki package.
## Dependencies - AnkiAI is designed to streamline the process of creating Anki decks from images.
- genanki: Used for creating Anki decks and cards. - The core idea is to use OCR to extract text from images and then use GPT-3 to transform this text into a structured Anki deck format.
- Pillow: Image processing library. - Users can make a POST request to a Flask server endpoint with their images to receive the Anki deck (.apkg file).
- openai: API library for OpenAI's GPT-4 model.
- flask: Web server to host the service.
## Setup and Installation ### Directory Structure
- `.vscode/`: Contains configuration for VSCode debugger for Flask applications.
- `ankiai.py`: The main script that drives the creation of Anki decks from images.
- `constants.py`: Contains constant variables used across the project.
- `deck_creation.py`: Contains logic for communicating with OpenAI's API and deck creation using genanki.
- `image_processing.py`: Processes images, converting them for OCR and then performing OCR to extract text.
- `logging_config.py`: Logging configuration for the entire project.
- `server.py`: Flask server that provides an API endpoint to upload images and get back an Anki deck.
### Requirements
To run AnkiAI, you'll need to have the following dependencies installed:
```
genanki==0.8.0
Pillow
openai
flask
```
You can install these via `pip` using the `requirements.txt` file:
```bash
pip install -r requirements.txt
```
### How to Run
1. **Environment Variables**: Make sure to set the `OPENAI_API_KEY` environment variable to your OpenAI API key.
2. **Run the Flask server**:
1. Clone this repository:
```bash ```bash
git clone https://git.rudefox.io/bj/anki-json2ankicards.git python server.py
cd json2ankicards
``` ```
2. Set up a virtual environment and activate it: This will start the Flask server. You can then make a POST request to `http://localhost:5000/deck-from-images` with your images to get an Anki deck.
3. **Run Directly**:
If you prefer not to use the Flask server, you can also run `ankiai.py` directly:
```bash ```bash
python3 -m venv venv python ankiai.py <directory_path_containing_images>
source venv/bin/activate
``` ```
3. Install the required packages: ### How to Debug (VSCode Users)
```bash
pip install -r requirements.txt
```
4. Set up the OpenAI API key: - Open the project in VSCode.
```bash - Set up your breakpoints.
export OPENAI_API_KEY=your_openai_api_key - Use the VSCode debugger and select "Python: Flask" to start debugging the Flask server.
```
5. Run the server: ### Important Notes
```bash
python server.py
```
## Usage - **API Key**: For the project to work, it is essential to have the `OPENAI_API_KEY` environment variable set.
- **Image Types**: Currently, the image processing module supports PNG, JPG, and JPEG formats.
- **Output**: The output `.apkg` file (Anki package file) will be named `out.apkg`.
1. Start the server as mentioned above. ### Acknowledgements
2. Use a tool like [Postman](https://www.postman.com/) or `curl` to send images to `http://localhost:5000/deck-from-images` as a multi-part POST request. This project heavily relies on the `openai` library for processing and the `genanki` library for deck generation.
3. The server will respond with a downloadable Anki package. Import this into your Anki app and start studying! ### Contributions
## Modules Contributions are always welcome. Please create a new issue or a pull request for any bug fixes or feature requests.
1. **ankiai.py**: The main module that orchestrates the flow.
2. **images2text.py**: Converts image content into text using OCR.
3. **json2deck.py**: Converts structured JSON data into an Anki package.
4. **prompt4cards.py**: Uses OpenAI to structure the content into Anki decks and cards.
5. **server.py**: Flask server to host the service.
## Contributing
Contributions are welcome! Please submit a pull request or open an issue to discuss changes or fixes.
## License
[MIT License](LICENSE)

View File

@ -2,18 +2,18 @@ import sys
import logging import logging
from logging_config import setup_logging from logging_config import setup_logging
from images2text import main as ocr_images from image_processing import process_images
from prompt4cards import prompt_for_card_content, response_to_json from deck_creation import prompt_for_card_content, response_to_json, to_package
from json2deck import to_package
APKG_FILE = "out.apkg"
setup_logging() setup_logging()
def images_to_package(directory_path, outfile): def images_to_package(directory_path):
ocr_text = ocr_images(directory_path) ocr_text = process_images(directory_path)
response_text = prompt_for_card_content(ocr_text) response_text = prompt_for_card_content(ocr_text)
deck_json = response_to_json(response_text) deck_json = response_to_json(response_text)
to_package(deck_json).write_to_file(outfile) return to_package(deck_json)
logging.info(f"Deck created at: {outfile}")
if __name__ == "__main__": if __name__ == "__main__":
@ -21,4 +21,5 @@ if __name__ == "__main__":
print("Usage: python ankiai.py <directory_path_containing_images>") print("Usage: python ankiai.py <directory_path_containing_images>")
sys.exit(1) sys.exit(1)
images_to_package(sys.argv[1]) images_to_package(sys.argv[1]).write_to_file(APKG_FILE)
logging.info(f"Deck created at: {APKG_FILE}")

View File

@ -1,8 +1,10 @@
# File and Directory Constants # File and Directory Constants
IMAGE_KEY="image"
APKG_FILE="out.apkg"
CONVERTED_DIR = "converted" CONVERTED_DIR = "converted"
FINAL_OUTPUT = "final.txt" TEXT_OCR_FILE = "final.txt"
IMAGE_EXTENSIONS = ['.png', '.jpg', '.jpeg'] IMAGE_EXTENSIONS = ['.png', '.jpg', '.jpeg']
OUTPUT_FILENAME = "output_deck.json" DECK_JSON_FILE = "output_deck.json"
# API Constants # API Constants
API_KEY_ENV = "OPENAI_API_KEY" API_KEY_ENV = "OPENAI_API_KEY"

View File

@ -1,8 +1,12 @@
import openai import openai
import os import os
import sys
import json import json
from constants import API_KEY_ENV, CHAT_MODEL, OUTPUT_FILENAME import genanki
from logging_config import setup_logging
from constants import API_KEY_ENV, CHAT_MODEL
setup_logging()
API_KEY = os.environ.get(API_KEY_ENV) API_KEY = os.environ.get(API_KEY_ENV)
@ -83,21 +87,44 @@ def response_to_json(response_text):
} }
if __name__ == "__main__": # Create a new model for our cards. This is necessary for genanki.
if len(sys.argv) != 2: MY_MODEL = genanki.Model(
print("Usage: python prompt4cards.py <text_file_path>") 1607372319,
sys.exit(1) "Simple Model",
fields=[
{"name": "Title"},
{"name": "Question"},
{"name": "Answer"},
],
templates=[
{
"name": "{{Title}}",
"qfmt": "{{Question}}",
"afmt": "{{FrontSide}}<hr id='answer'>{{Answer}}",
},
])
text_file_path = sys.argv[1] def json_file_to_package(json_path):
with open(json_path, 'r', encoding='utf-8') as f:
json_data = json.load(f)
package = to_package(json_data)
# Read the text content return package
with open(text_file_path, 'r') as file:
text_content = file.read()
response_text = prompt_for_card_content(text_content) def to_package(deck_json):
deck_json = response_to_json(response_text) deck_title = deck_json["DeckTitle"]
deck = genanki.Deck(1607372319, deck_title)
with open(OUTPUT_FILENAME, 'w') as json_file: for card_json in deck_json["Cards"]:
json.dump(deck_json, json_file) title = card_json["Title"]
question = card_json["Question"]
answer = card_json["Answer"]
print(f"Saved generated deck to {OUTPUT_FILENAME}") note = genanki.Note(
model=MY_MODEL,
fields=[title, question, answer]
)
deck.add_note(note)
return genanki.Package(deck)

20
images2text.py → image_processing.py Executable file → Normal file
View File

@ -5,13 +5,21 @@ import logging
from logging_config import setup_logging from logging_config import setup_logging
from subprocess import run, CalledProcessError from subprocess import run, CalledProcessError
from concurrent.futures import ThreadPoolExecutor from concurrent.futures import ThreadPoolExecutor
from utilities import is_image_file, ensure_directory_exists from constants import CONVERTED_DIR, TEXT_OCR_FILE, IMAGE_EXTENSIONS
from constants import CONVERTED_DIR, FINAL_OUTPUT
setup_logging() setup_logging()
def is_image_file(path):
return any(path.lower().endswith(ext) for ext in IMAGE_EXTENSIONS)
def ensure_directory_exists(directory):
if not os.path.exists(directory):
os.mkdir(directory)
def convert_image(image_path): def convert_image(image_path):
logging.info(f"Converting {image_path}...") logging.info(f"Converting {image_path}...")
converted_path = os.path.join(CONVERTED_DIR, os.path.basename(image_path)) converted_path = os.path.join(CONVERTED_DIR, os.path.basename(image_path))
@ -62,7 +70,7 @@ def process_image(image_path):
return None return None
def main(directory_path): def process_images(directory_path):
final_text = [] final_text = []
ensure_directory_exists(CONVERTED_DIR) ensure_directory_exists(CONVERTED_DIR)
@ -80,10 +88,10 @@ def main(directory_path):
# Filter out any None values and write the text to final.txt # Filter out any None values and write the text to final.txt
final_text = [text for text in final_text if text is not None] final_text = [text for text in final_text if text is not None]
with open(FINAL_OUTPUT, 'w') as f: with open(TEXT_OCR_FILE, 'w') as f:
f.write("\n".join(final_text)) f.write("\n".join(final_text))
logging.info(f"All images processed! Final output saved to {FINAL_OUTPUT}") logging.info(f"All images processed! Final output saved to {TEXT_OCR_FILE}")
return final_text # Add this line return final_text # Add this line
@ -91,4 +99,4 @@ if __name__ == "__main__":
if len(sys.argv) != 2: if len(sys.argv) != 2:
print("Usage: python images2text.py <directory_path>") print("Usage: python images2text.py <directory_path>")
sys.exit(1) sys.exit(1)
main(sys.argv[1]) process_images(sys.argv[1])

View File

@ -1,61 +0,0 @@
import json
import genanki
import sys
import logging
from logging_config import setup_logging
setup_logging()
# Create a new model for our cards. This is necessary for genanki.
MY_MODEL = genanki.Model(
1607372319,
"Simple Model",
fields=[
{"name": "Title"},
{"name": "Question"},
{"name": "Answer"},
],
templates=[
{
"name": "{{Title}}",
"qfmt": "{{Question}}",
"afmt": "{{FrontSide}}<hr id='answer'>{{Answer}}",
},
])
def json_file_to_package(json_path):
with open(json_path, 'r', encoding='utf-8') as f:
json_data = json.load(f)
package = to_package(json_data)
return package
def to_package(deck_json):
deck_title = deck_json["DeckTitle"]
deck = genanki.Deck(1607372319, deck_title)
for card_json in deck_json["Cards"]:
title = card_json["Title"]
question = card_json["Question"]
answer = card_json["Answer"]
note = genanki.Note(
model=MY_MODEL,
fields=[title, question, answer]
)
deck.add_note(note)
return genanki.Package(deck)
if __name__ == "__main__":
if len(sys.argv) != 3:
print("Usage: python convert.py <input_json> <output_apkg>")
sys.exit(1)
input_json = sys.argv[1]
output_apkg = sys.argv[2]
json_file_to_package(input_json).write_to_file(output_apkg)
logging.info(f"Deck created at: {output_apkg}")

View File

@ -3,17 +3,16 @@ import tempfile
import shutil import shutil
import logging import logging
from logging_config import setup_logging
from flask import Flask, request, send_from_directory, jsonify from flask import Flask, request, send_from_directory, jsonify
from werkzeug.utils import secure_filename from werkzeug.utils import secure_filename
from ankiai import images_to_package from ankiai import images_to_package
from constants import IMAGE_KEY, OUTPUT_FILE, NO_IMAGE_PART_ERROR, NO_SELECTED_FILE_ERROR, INVALID_FILENAME_ERROR from constants import IMAGE_KEY, APKG_FILE, NO_IMAGE_PART_ERROR, NO_SELECTED_FILE_ERROR, INVALID_FILENAME_ERROR
setup_logging()
from logging_config import setup_logging from logging_config import setup_logging
setup_logging()
app = Flask(__name__) app = Flask(__name__)
def save_uploaded_images(images, directory): def save_uploaded_images(images, directory):
@ -41,8 +40,9 @@ def deck_from_images():
save_uploaded_images(images, temp_dir) save_uploaded_images(images, temp_dir)
try: try:
images_to_package(temp_dir, OUTPUT_FILE) images_to_package(temp_dir).write_to_file(APKG_FILE)
return send_from_directory('.', OUTPUT_FILE, as_attachment=True) logging.info(f"Anki package written to {APKG_FILE}")
return send_from_directory('.', APKG_FILE, as_attachment=True)
except Exception as e: except Exception as e:
logging.error("Exception occurred: "+str(e), exc_info=True) logging.error("Exception occurred: "+str(e), exc_info=True)
return jsonify({'error': str(e)}), 500 return jsonify({'error': str(e)}), 500

View File

@ -1,9 +0,0 @@
import os
from constants import IMAGE_EXTENSIONS
def is_image_file(path):
return any(path.lower().endswith(ext) for ext in IMAGE_EXTENSIONS)
def ensure_directory_exists(directory):
if not os.path.exists(directory):
os.mkdir(directory)