diff --git a/README.md b/README.md index 96dc40f..e2391c2 100644 --- a/README.md +++ b/README.md @@ -9,10 +9,6 @@ https://openmemories.readthedocs.io/devices.html I am not affiliated with OpenAI or Sony, this is not an official app. -Currently, **using this app will get your DALL-E 2 account automatically banned**, because OpenAI consider it to be a -"web scraping tool" in violation of their Terms and Conditions. So check back here after DALL-E 2 has released their -public API for a version that complies with their new terms. - ![Screenshot of upload in progress](screenshot.jpg) ## Installation ## @@ -88,27 +84,31 @@ Task completed successfully ## Configuration -Your Sony camera can't connect to OpenAI directly (because it only supports TLS 1.0, which is too outdated), so you need -to deploy a backend to your Amazon AWS account that will serve as a proxy for connecting to OpenAI. To create this backend, +Your Sony camera can't connect to OpenAI directly (because this Android version only supports TLS 1.0, which is too +outdated), so you need to deploy a backend to your Amazon AWS account that will serve as a proxy for connecting to +OpenAI. To create this backend, [deploy quantum-mirror-lambda](https://github.com/Sherlock-Photography/quantum-mirror-lambda). After the deploy is complete, it'll give you an "EndpointForCameraTokenTxt" value to use. -Create a text file in the root folder of your camera's SD Card called "TOKEN.TXT". On the first line, enter the -EndpointForCameraTokenTxt value, e.g.: +On the OpenAI website, generate a new API key for your account on this page: + +https://beta.openai.com/account/api-keys - https://xxxx.cloudfront.net/ +Now create a text file in the root folder of your camera's SD Card called "AI-SET.TXT", and paste this content into it: -Now on the second line of the TOKEN.TXT file you need to provide Quantum Mirror with your DALL-E credentials. +```properties +api-key=sk-xxx +endpoint=https://xxxx.cloudfront.net/ +size=1024x1024 +count=4 +``` -On the DALL-E Labs website, log in to your account, then open your browser's Developer Tools and go to the Network tab. -Click on your "My Collection page" on the DALL-E website. Then in the network tab, examine the request for the "private" URL, -and in the request parameters look for the "Authorization: sess-..." line. This token represents your logged-on -DALL-E session. +On the first line replace the `sk-xxx` with the API key you got from OpenAI. -Copy that "sess-..." token, and paste it into the second line of your TOKEN.TXT, like so: +On the second line, enter your `EndpointForCameraTokenTxt` value. - https://xxxx.cloudfront.net/ - sess-xxxxxxxxxxxxxxxxxxxxx +You can edit the size and count to change how big generated images should be and how many images should be generated in +each batch. Valid sizes are 256x256, 512x512, or 1024x1024 (only). Valid counts are 1-10. Set up a WiFi connection in your Sony settings (e.g. to a phone WiFi hotspot), since it must be connected at the time you take your photo for it to be submitted to DALL-E. diff --git a/app/src/main/java/com/obsidium/bettermanual/MainActivity.java b/app/src/main/java/com/obsidium/bettermanual/MainActivity.java index cb34381..5f7d514 100644 --- a/app/src/main/java/com/obsidium/bettermanual/MainActivity.java +++ b/app/src/main/java/com/obsidium/bettermanual/MainActivity.java @@ -41,6 +41,7 @@ import java.net.HttpURLConnection; import java.net.URL; import java.text.SimpleDateFormat; +import java.util.Properties; import java.util.concurrent.*; /** @@ -74,6 +75,9 @@ public class MainActivity extends BaseActivity implements ActivityInterface, Cam private String m_bearerToken; private ExecutorService m_dalleExecutor = Executors.newSingleThreadExecutor(); + private String m_genImageSize; + private int m_getImageCount; + private boolean m_facesPresent = false; private WifiManager wifiManager; @@ -125,40 +129,44 @@ public void onReceive(Context context, Intent intent) { }; try { - loadBearerToken(); + loadSettings(); } catch (Exception e) { - Log.e(TAG, "Bad TOKEN.TXT file: " + e); - showDallEProgress("Bad TOKEN.TXT file: " + e, false); + Log.e(TAG, "Bad AI-SET.TXT file: " + e); + showDallEProgress("Bad AI-SET file: " + e, false); } } - private void loadBearerToken() throws IOException { - File filename = new File(Environment.getExternalStorageDirectory(), "TOKEN.TXT"); + private void loadSettings() throws IOException { + File filename = new File(Environment.getExternalStorageDirectory(), "AI-SET.TXT"); + + Log.d(TAG, "Loading settings from " + filename.toString()); - Log.d(TAG, "Loading bearer token from " + filename.toString()); + Properties prop = new Properties(); - BufferedReader reader = new BufferedReader(new FileReader(filename)); + prop.load(new FileInputStream(filename)); - String line = reader.readLine().trim(); - if (!line.startsWith("http")) { - throw new RuntimeException("Missing endpoint url"); + String endpoint = prop.getProperty("endpoint", ""); + + if (!endpoint.startsWith("http")) { + throw new RuntimeException("Missing endpoint"); } - if (!line.endsWith("/")) { - line = line + "/"; + if (!endpoint.endsWith("/")) { + endpoint = endpoint + "/"; } - m_endPointPrefix = line; + m_endPointPrefix = endpoint; - line = reader.readLine().trim(); + String token = prop.getProperty("api-key", ""); - if (line.length() < 16) { - throw new RuntimeException("Missing bearer token"); + if (token.length() < 16) { + throw new RuntimeException("Missing api-key"); } - m_bearerToken = line; + m_bearerToken = token; - reader.close(); + m_genImageSize = prop.getProperty("size", "1024x1024"); + m_getImageCount = Integer.parseInt(prop.getProperty("count", "4"), 10); } protected void wifiStateChanged(int state) { @@ -236,7 +244,7 @@ private String readStreamAsString(InputStream in) throws IOException { } private void logDallEToFile(String msg) { - File file = new File(Environment.getExternalStorageDirectory(), "DALL-E.TXT"); + File file = new File(Environment.getExternalStorageDirectory(), "AI-LOG.TXT"); try { BufferedWriter writer = new BufferedWriter(new FileWriter(file, true)); @@ -250,7 +258,7 @@ private void logDallEToFile(String msg) { } private JSONObject uploadDallEPicture(final byte[] bytes) throws IOException, JSONException { - URL u = new URL(m_endPointPrefix + "submitImage"); + URL u = new URL(m_endPointPrefix + "submitImage?size=" + m_genImageSize + "&count=" + m_getImageCount); HttpURLConnection c = (HttpURLConnection) u.openConnection(); @@ -284,29 +292,8 @@ private JSONObject uploadDallEPicture(final byte[] bytes) throws IOException, JS return (JSONObject) new JSONTokener(readStreamAsString(in)).nextValue(); } - private JSONObject pollDallETask(final String taskID) throws IOException, JSONException { - URL u = new URL(m_endPointPrefix + "pollTask/" + taskID); - - HttpURLConnection c = (HttpURLConnection) u.openConnection(); - - c.setUseCaches(false); - c.setRequestMethod("GET"); - c.setRequestProperty("X-Authorization", "Bearer " + m_bearerToken); - c.setDoInput(true); - - c.connect(); - - if (c.getResponseCode() != HttpURLConnection.HTTP_OK) { - throw new IOException("HTTP error " + c.getResponseCode() + " (" + c.getResponseMessage() + ")"); - } - - InputStream in = c.getInputStream(); - - return (JSONObject) new JSONTokener(readStreamAsString(in)).nextValue(); - } - private InputStream startDallEImageDownload(String url) throws IOException { - URL u = new URL(url.replace("https://openailabsprodscus.blob.core.windows.net/", m_endPointPrefix + "getImage/")); + URL u = new URL(url.replace("https://oaidalleapiprodscus.blob.core.windows.net/", m_endPointPrefix + "getImage/")); HttpURLConnection c = (HttpURLConnection) u.openConnection(); @@ -340,60 +327,29 @@ private void runDallE(final byte[] jpegBytes) { try { JSONObject responseJSON = uploadDallEPicture(jpegBytes); - String status = responseJSON.optString("status", "failed"); - String taskID; - boolean success = false; - if (!("success".equals(status) || "pending".equals(status))) { - // Check for a friendly-formatted error from OpenAI - if (responseJSON.getJSONObject("error") != null) { - String message = responseJSON.getJSONObject("error").getString("message"); + // Check for a friendly-formatted error from OpenAI: + JSONObject error = responseJSON.optJSONObject("error"); - Log.e(TAG, "Upload error: " + message); - logDallEToFile("Upload error: " + message); - showDallEProgress("Error: " + message, false); + if (error != null) { + String message = error.getString("message"); - return; - } - - throw new RuntimeException("Task submit failed"); - } - - taskID = responseJSON.getString("id"); - - logDallEToFile("Submitted: https://labs.openai.com/e/" + taskID.replace("task-", "")); - - for (int i = 0; i < 40; i++) { - Log.d(TAG, "DALL-e task status: " + status); - - if ("pending".equals(status)) { - showDallEProgress("AI working...", false); - - Thread.sleep(3000); - responseJSON = pollDallETask(taskID); - - status = responseJSON.getString("status"); - } else if ("succeeded".equals(status)) { - success = true; - break; - } else { - throw new RuntimeException("Task failed"); - } - } + Log.e(TAG, "Upload error: " + message); + logDallEToFile("Upload error: " + message); + showDallEProgress("Error: " + message, false); - if (!success) { - throw new RuntimeException("Timed out waiting for AI"); + return; } showDallEProgress("Downloading images...", false); - JSONArray generations = responseJSON.getJSONObject("generations").getJSONArray("data"); + JSONArray generations = responseJSON.getJSONArray("data"); CountDownLatch downloadCounter = new CountDownLatch(generations.length()); for (int i = 0; i < generations.length(); i++) { JSONObject generation = generations.getJSONObject(i); - final String imageURL = generation.getJSONObject("generation").getString("image_path"); + final String imageURL = generation.getString("url"); m_handler.post(() -> { try { @@ -430,7 +386,7 @@ public void onPictureTaken(byte[] bytes, Camera camera) { Log.d(TAG, "Captured jpeg, size: " + bytes.length); if (m_bearerToken == null) { - showDallEProgress("No TOKEN.TXT file found!", true); + showDallEProgress("No AI-SET.TXT file found!", true); } else if (m_facesPresent) { showDallEProgress("Not uploading due to faces", true); } else { diff --git a/gradlew b/gradlew old mode 100644 new mode 100755