diff --git a/api_output.py b/api_output.py
index abc2fe4..d17724b 100644
--- a/api_output.py
+++ b/api_output.py
@@ -4,7 +4,7 @@
import io
from functools import partial
import xml.etree.ElementTree as ET
-from urllib.parse import urlparse
+from urllib.parse import urlparse, urlencode
import threading
from sc_logging import logger
@@ -12,7 +12,8 @@
from storage import fetch_data, subscribe_to_data
out_api_url = fetch_data("scoresight.json", "out_api_url", None)
-out_api_encoding = fetch_data("scoresight.json", "out_api_encoding", "JSON")
+out_api_encoding = fetch_data("scoresight.json", "out_api_encoding", "JSON (Full)")
+out_api_method = fetch_data("scoresight.json", "out_api_method", "POST")
def is_valid_url_urllib(url):
@@ -33,13 +34,21 @@ def setup_out_api_encoding(encoding):
out_api_encoding = encoding
+def setup_out_api_method(method):
+ global out_api_method
+ out_api_method = method
+
+
subscribe_to_data("scoresight.json", "out_api_url", setup_out_api_url)
subscribe_to_data("scoresight.json", "out_api_encoding", setup_out_api_encoding)
+subscribe_to_data("scoresight.json", "out_api_method", setup_out_api_method)
def update_out_api(data: list[TextDetectionTargetWithResult]):
- if out_api_url is None or out_api_encoding is None:
- logger.error(f"Output API not set up: {out_api_url}, {out_api_encoding}")
+ if out_api_url is None or out_api_encoding is None or out_api_method is None:
+ logger.error(
+ f"Output API not set up: {out_api_url}, {out_api_encoding}, {out_api_method}"
+ )
return
# validate the URL
@@ -51,14 +60,20 @@ def update_out_api(data: list[TextDetectionTargetWithResult]):
def send_data():
try:
- if out_api_encoding == "JSON":
- response = send_json(data)
- elif out_api_encoding == "XML":
- response = send_xml(data)
- elif out_api_encoding == "CSV":
- response = send_csv(data)
+ if out_api_method == "GET":
+ response = send_get(data)
else:
- logger.error("Invalid encoding: %s", out_api_encoding)
+ if out_api_encoding.startswith("JSON"):
+ response = send_json(data, out_api_encoding)
+ elif out_api_encoding == "XML":
+ response = send_xml(data)
+ elif out_api_encoding == "CSV":
+ response = send_csv(data)
+ else:
+ logger.error("Invalid encoding: %s", out_api_encoding)
+ return
+
+ if response is None:
return
if response.status_code != 200:
@@ -72,13 +87,41 @@ def send_data():
thread.start()
-def send_json(data: list[TextDetectionTargetWithResult]):
+def send_get(data: list[TextDetectionTargetWithResult]):
+ out_api_url_copy = out_api_url
+ # add the data to the URL as query parameters
+ # check if the URL already has query parameters
+ if "?" in out_api_url_copy:
+ out_api_url_copy += "&"
+ else:
+ out_api_url_copy += "?"
+ out_api_url_copy += urlencode({result.name: result.result for result in data})
+ logger.debug(f"GET URL: {out_api_url_copy}")
+ response = requests.get(out_api_url_copy)
+ return response
+
+
+def send_json(data: list[TextDetectionTargetWithResult], encoding: str):
headers = {"Content-Type": "application/json"}
- response = requests.post(
- out_api_url,
- headers=headers,
- data=json.dumps([result.to_dict() for result in data]),
- )
+ if encoding == "JSON (Full)":
+ json_data_dump = json.dumps([result.to_dict() for result in data])
+ elif encoding == "JSON (Simple key-value)":
+ json_data_dump = json.dumps({result.name: result.result for result in data})
+ if out_api_method == "POST":
+ response = requests.post(
+ out_api_url,
+ headers=headers,
+ data=json_data_dump,
+ )
+ elif out_api_method == "PUT":
+ response = requests.put(
+ out_api_url,
+ headers=headers,
+ data=json_data_dump,
+ )
+ else:
+ logger.error(f"Invalid method: {out_api_method}")
+ return None
return response
@@ -95,7 +138,13 @@ def send_xml(data: list[TextDetectionTargetWithResult]):
resultEl.set("width", str(targetWithResult.width()))
resultEl.set("height", str(targetWithResult.height()))
xml_data = ET.tostring(root, encoding="utf-8")
- response = requests.post(out_api_url, headers=headers, data=xml_data)
+ if out_api_method == "POST":
+ response = requests.post(out_api_url, headers=headers, data=xml_data)
+ elif out_api_method == "PUT":
+ response = requests.put(out_api_url, headers=headers, data=xml_data)
+ else:
+ logger.error(f"Invalid method: {out_api_method}")
+ return None
return response
@@ -116,5 +165,11 @@ def send_csv(data: list[TextDetectionTargetWithResult]):
result.height(),
]
)
- response = requests.post(out_api_url, headers=headers, data=output.getvalue())
+ if out_api_method == "POST":
+ response = requests.post(out_api_url, headers=headers, data=output.getvalue())
+ elif out_api_method == "PUT":
+ response = requests.put(out_api_url, headers=headers, data=output.getvalue())
+ else:
+ logger.error(f"Invalid method: {out_api_method}")
+ return None
return response
diff --git a/docs/image-29.png b/docs/image-29.png
new file mode 100644
index 0000000..79fbe06
Binary files /dev/null and b/docs/image-29.png differ
diff --git a/docs/image-30.png b/docs/image-30.png
new file mode 100644
index 0000000..3296c81
Binary files /dev/null and b/docs/image-30.png differ
diff --git a/docs/out_api.md b/docs/out_api.md
index b6392dd..c642c1c 100644
--- a/docs/out_api.md
+++ b/docs/out_api.md
@@ -1,4 +1,4 @@
-# ScoreSight Outboud API Integration Tutorial
+# ScoreSight Outbound API Integration Tutorial
ScoreSight now offers the ability to send OCR-extracted scoreboard data to external APIs. This tutorial will guide you through setting up the feature and provide a simple Python script to receive the data.
@@ -6,13 +6,15 @@ ScoreSight now offers the ability to send OCR-extracted scoreboard data to exter
1. Open ScoreSight and navigate to the "API" tab in the bottom left corner.
-![alt text](image-21.png)
+![alt text](image-29.png)
2. Check the box labeled "Send out API requests to external services".
3. Enter the URL where you want to send the data in the provided box.
-4. Select the encoding format for the data: JSON, XML, or CSV.
+4. Select the encoding format for the data: JSON (Full), Json (Simple key-value) XML, or CSV.
-![alt text](image-22.png)
+![alt text](image-30.png)
+
+5. Choose the HTTP method for the API request: POST, PUT, or GET.
## Troubleshooting
@@ -35,13 +37,17 @@ If you encounter issues with the API integration, follow these steps to troubles
3. Encoding Format:
- Verify that the encoding format selected in ScoreSight (JSON, XML, or CSV) matches the format your receiving script or API expects.
-4. Server Availability and Authentication:
+4. HTTP Method:
+ - Ensure that the selected HTTP method (POST, PUT, or GET) is supported by your receiving API.
+ - Verify that your API is configured to handle the chosen method correctly.
+
+5. Server Availability and Authentication:
- If using the provided Python script, make sure it's running before attempting to send data from ScoreSight.
- For external APIs, check if the service is up and accessible.
- If your external API requires an API key or authentication, ensure these details are correctly included in the URL or headers.
- If testing locally, check that your firewall isn't blocking the connection.
-5. Test with a Simple Server:
+6. Test with a Simple Server:
- Use the provided Python script below as a test server to isolate whether the issue is with ScoreSight or the receiving end.
If problems persist after trying these steps, consider reaching out to ScoreSight support for further assistance.
@@ -56,16 +62,30 @@ import json
class RequestHandler(BaseHTTPRequestHandler):
def do_POST(self):
- content_length = int(self.headers['Content-Length'])
- post_data = self.rfile.read(content_length)
-
- print("Received data:")
- try:
- # Assuming JSON format, adjust if using XML or CSV
- data = json.loads(post_data.decode('utf-8'))
- print(json.dumps(data, indent=2))
- except json.JSONDecodeError:
- print(post_data.decode('utf-8'))
+ self.handle_request()
+
+ def do_PUT(self):
+ self.handle_request()
+
+ def do_GET(self):
+ self.handle_request()
+
+ def handle_request(self):
+ if self.command in ['POST', 'PUT']:
+ content_length = int(self.headers['Content-Length'])
+ post_data = self.rfile.read(content_length)
+
+ print(f"Received {self.command} data:")
+ try:
+ # Assuming JSON format, adjust if using XML or CSV
+ data = json.loads(post_data.decode('utf-8'))
+ print(json.dumps(data, indent=2))
+ except json.JSONDecodeError:
+ print(post_data.decode('utf-8'))
+ elif self.command == 'GET':
+ print("Received GET request")
+ print(f"Path: {self.path}")
+ print(f"Headers: {self.headers}")
self.send_response(200)
self.end_headers()
@@ -100,7 +120,7 @@ You may see in the console an output similar to:
```
127.0.0.1 - - [nn/nn/nnnn 10:57:03] "POST / HTTP/1.1" 200 -
-Received data: Name,Text,State,X,Y,Width,Height
+Received POST data: Name,Text,State,X,Y,Width,Height
Time,0:52,SameNoChange,843.656319861826,663.0215827338131,359.13669064748206,207.19424460431662
Home Score,35,SameNoChange,525.969716884406,638.4370727628325,214.62426933453253,175.27648662320166
```
diff --git a/mainwindow.py b/mainwindow.py
index 89092bc..a0691db 100644
--- a/mainwindow.py
+++ b/mainwindow.py
@@ -227,6 +227,14 @@ def __init__(self, translator: QTranslator, parent: QObject):
fetch_data("scoresight.json", "out_api_encoding", "JSON")
)
)
+ self.ui.comboBox_outApiMethod.currentTextChanged.connect(
+ partial(self.globalSettingsChanged, "out_api_method")
+ )
+ self.ui.comboBox_outApiMethod.setCurrentIndex(
+ self.ui.comboBox_outApiMethod.findText(
+ fetch_data("scoresight.json", "out_api_method", "POST")
+ )
+ )
self.obs_websocket_client = None
diff --git a/mainwindow.ui b/mainwindow.ui
index 647e46c..63c3592 100644
--- a/mainwindow.ui
+++ b/mainwindow.ui
@@ -1457,7 +1457,12 @@
-
- JSON
+ JSON (Full)
+
+
+ -
+
+ JSON (Simple key-value)
-
@@ -1511,16 +1516,28 @@
-
-
-
- false
-
-
- Not implemented yet.
-
-
- Websocket
+
+
+
+ 50
+ 16777215
+
+
-
+
+ POST
+
+
+ -
+
+ PUT
+
+
+ -
+
+ GET
+
+
diff --git a/sc_logging.py b/sc_logging.py
index 09930c0..532267b 100644
--- a/sc_logging.py
+++ b/sc_logging.py
@@ -4,59 +4,72 @@
from datetime import datetime
from dotenv import load_dotenv
-# Load the environment variables from the .env file
-load_dotenv(os.path.abspath(os.path.join(os.path.dirname(__file__), ".env")))
-
-# Create a logger
-logger = logging.getLogger(__name__)
-logger.setLevel(logging.DEBUG)
-
-# get the user data directory
-data_dir = user_log_dir("scoresight")
-if not os.path.exists(data_dir):
- os.makedirs(data_dir)
-
-current_time = datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
-
-# basic config - send all logs to a file
-logging.basicConfig(
- filename=os.path.join(data_dir, f"scoresight_std_{current_time}.log"),
- level=logging.INFO,
-)
-
-# prepend the user data directory
-log_file_path = os.path.join(data_dir, f"scoresight_{current_time}.log")
-
-# Create a file handler
-file_handler = logging.FileHandler(log_file_path)
-file_handler.setLevel(logging.DEBUG)
-
-# Create a formatter
-formatter = logging.Formatter("%(asctime)s - %(levelname)s - %(module)s - %(message)s")
-file_handler.setFormatter(formatter)
-
-# Add the file handler to the logger
-logger.addHandler(file_handler)
-
-# if the .env file has a debug flag, set the logger to output to console
-if os.getenv("SCORESIGHT_DEBUG"):
- console_handler = logging.StreamHandler()
- console_handler.setLevel(logging.DEBUG)
- console_handler.setFormatter(formatter)
- logger.addHandler(console_handler)
- logger.debug("Debug mode enabled")
-
-# check to see if there are more log files, and only keep the most recent 10
-log_files = [
- f
- for f in os.listdir(data_dir)
- if f.startswith("scoresight_") and f.endswith(".log")
-]
-# sort log files by date
-log_files.sort()
-if len(log_files) > 10:
- for f in log_files[:-10]:
- try:
- os.remove(os.path.join(data_dir, f))
- except PermissionError as e:
- logger.error(f"Failed to remove log file: {f}")
+
+def setup_logging():
+ # Load the environment variables from the .env file
+ load_dotenv(os.path.abspath(os.path.join(os.path.dirname(__file__), ".env")))
+
+ # Create a logger
+ logger = logging.getLogger(__name__)
+ logger.setLevel(logging.DEBUG)
+
+ # get the user data directory
+ data_dir = user_log_dir("scoresight")
+ if not os.path.exists(data_dir):
+ os.makedirs(data_dir)
+
+ current_time = datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
+
+ # basic config - send all logs to a file
+ logging.basicConfig(
+ filename=os.path.join(data_dir, f"scoresight_std_{current_time}.log"),
+ level=logging.INFO,
+ )
+
+ # prepend the user data directory
+ log_file_path = os.path.join(data_dir, f"scoresight_{current_time}.log")
+
+ # Create a file handler
+ file_handler = logging.FileHandler(log_file_path)
+ file_handler.setLevel(logging.DEBUG)
+
+ # Create a formatter
+ formatter = logging.Formatter(
+ "%(asctime)s - %(levelname)s - %(module)s - %(message)s"
+ )
+ file_handler.setFormatter(formatter)
+
+ # Add the file handler to the logger
+ logger.addHandler(file_handler)
+
+ # if the .env file has a debug flag, set the logger to output to console
+ if os.getenv("SCORESIGHT_DEBUG"):
+ console_handler = logging.StreamHandler()
+ console_handler.setLevel(logging.DEBUG)
+ console_handler.setFormatter(formatter)
+ logger.addHandler(console_handler)
+ logger.debug("Debug mode enabled")
+
+ # check to see if there are more log files, and only keep the most recent 10
+ log_files = [
+ f
+ for f in os.listdir(data_dir)
+ if f.startswith("scoresight_") and f.endswith(".log")
+ ]
+ # sort log files by date
+ log_files.sort()
+ if len(log_files) > 10:
+ for f in log_files[:-10]:
+ try:
+ os.remove(os.path.join(data_dir, f))
+ except PermissionError as e:
+ logger.error(f"Failed to remove log file: {f}")
+
+ return logger, file_handler, log_file_path
+
+
+try:
+ # Create a logger
+ logger, file_handler, log_file_path = setup_logging()
+except Exception as e:
+ print(f"Error setting up logging: {e}")
diff --git a/ui_mainwindow.py b/ui_mainwindow.py
index ae9f8c8..ced6658 100644
--- a/ui_mainwindow.py
+++ b/ui_mainwindow.py
@@ -774,6 +774,7 @@ def setupUi(self, MainWindow):
self.comboBox_api_encode.addItem("")
self.comboBox_api_encode.addItem("")
self.comboBox_api_encode.addItem("")
+ self.comboBox_api_encode.addItem("")
self.comboBox_api_encode.setObjectName(u"comboBox_api_encode")
self.formLayout_3.setWidget(4, QFormLayout.FieldRole, self.comboBox_api_encode)
@@ -793,11 +794,14 @@ def setupUi(self, MainWindow):
self.horizontalLayout_27.addWidget(self.lineEdit_api_url)
- self.checkBox_is_websocket = QCheckBox(self.widget_24)
- self.checkBox_is_websocket.setObjectName(u"checkBox_is_websocket")
- self.checkBox_is_websocket.setEnabled(False)
+ self.comboBox_outApiMethod = QComboBox(self.widget_24)
+ self.comboBox_outApiMethod.addItem("")
+ self.comboBox_outApiMethod.addItem("")
+ self.comboBox_outApiMethod.addItem("")
+ self.comboBox_outApiMethod.setObjectName(u"comboBox_outApiMethod")
+ self.comboBox_outApiMethod.setMaximumSize(QSize(50, 16777215))
- self.horizontalLayout_27.addWidget(self.checkBox_is_websocket)
+ self.horizontalLayout_27.addWidget(self.comboBox_outApiMethod)
self.formLayout_3.setWidget(1, QFormLayout.FieldRole, self.widget_24)
@@ -1223,15 +1227,16 @@ def retranslateUi(self, MainWindow):
self.tabWidget_outputs.setTabText(self.tabWidget_outputs.indexOf(self.tab_vmix), QCoreApplication.translate("MainWindow", u"VMix", None))
self.checkBox_enableOutAPI.setText(QCoreApplication.translate("MainWindow", u"Send out API requests to external services.", None))
self.label_21.setText(QCoreApplication.translate("MainWindow", u"Encode", None))
- self.comboBox_api_encode.setItemText(0, QCoreApplication.translate("MainWindow", u"JSON", None))
- self.comboBox_api_encode.setItemText(1, QCoreApplication.translate("MainWindow", u"XML", None))
- self.comboBox_api_encode.setItemText(2, QCoreApplication.translate("MainWindow", u"CSV", None))
+ self.comboBox_api_encode.setItemText(0, QCoreApplication.translate("MainWindow", u"JSON (Full)", None))
+ self.comboBox_api_encode.setItemText(1, QCoreApplication.translate("MainWindow", u"JSON (Simple key-value)", None))
+ self.comboBox_api_encode.setItemText(2, QCoreApplication.translate("MainWindow", u"XML", None))
+ self.comboBox_api_encode.setItemText(3, QCoreApplication.translate("MainWindow", u"CSV", None))
self.lineEdit_api_url.setPlaceholderText(QCoreApplication.translate("MainWindow", u"http://", None))
-#if QT_CONFIG(tooltip)
- self.checkBox_is_websocket.setToolTip(QCoreApplication.translate("MainWindow", u"Not implemented yet.", None))
-#endif // QT_CONFIG(tooltip)
- self.checkBox_is_websocket.setText(QCoreApplication.translate("MainWindow", u"Websocket", None))
+ self.comboBox_outApiMethod.setItemText(0, QCoreApplication.translate("MainWindow", u"POST", None))
+ self.comboBox_outApiMethod.setItemText(1, QCoreApplication.translate("MainWindow", u"PUT", None))
+ self.comboBox_outApiMethod.setItemText(2, QCoreApplication.translate("MainWindow", u"GET", None))
+
self.label_20.setText(QCoreApplication.translate("MainWindow", u"URL", None))
self.tabWidget_outputs.setTabText(self.tabWidget_outputs.indexOf(self.tab_api), QCoreApplication.translate("MainWindow", u"API", None))
self.pushButton_stopUpdates.setText(QCoreApplication.translate("MainWindow", u"Stop Updates", None))