I am using an esp32s3 sense for sneding image data in base64 encoding, right when i get the base64 data directly from my hardware, the image comes out perfectly but when i try to reconstruct the image from the server, the data seems missing, interestingly the data header and footer in both the server and client is the same but the length is different.
Client Code:
void send_photo(const char* fileName) { //sends photo via post
WiFiClient c;
File file = readFile(SD, fileName);
if (!file) {
Serial.println("Failed to read file");
return;
}
uint8_t* buffer = new uint8_t[file.size()];
file.read(buffer, file.size());
String encoded = base64::encode(buffer, file.size()); //gets the saved image from sd card
delete[] buffer;
file.close();
writeencoded(SD, "/encoded.txt", encoded,encoded.length()); //saves in SD card for comparison
Serial.println(encoded.length());
if(c.connect(HOST IP,443)){
c.println("POST /upload1 HTTP/1.1");
c.println("Host: HOST IP");
c.println("Content-Type: application/x-www-form-urlencoded");
// Serial.println(sizeof(encoded));
c.println("Content-Length: " + String(encoded.length()));
c.println();
c.println(encoded);
c.println();
Serial.println("Photo sent successfully");
}
}
File readFile(fs::FS &fs, const char * path) {
File file = fs.open(path, FILE_READ);
if (!file) {
Serial.println("Failed to open file");
return File();
}
return file;
}
void photo_save(const char * fileName) {
// Take a photo
camera_fb_t *fb = esp_camera_fb_get();
if (!fb) {
Serial.println("Failed to get camera frame buffer");
return;
}
writeFile(SD, fileName, fb->buf, fb->len);
fb_buf = fb->buf;
fb_len = fb->len;
// Release image buffer
esp_camera_fb_return(fb);
Serial.println("Photo saved to file");
}
void writeFile(fs::FS &fs, const char * path, uint8_t * data, size_t len){
Serial.printf("Writing file: %s\n", path);
File file = fs.open(path, FILE_WRITE);
if(!file){
Serial.println("Failed to open file for writing");
return;
}
if(file.write(data, len) == len){
Serial.println("File written");
} else {
Serial.println("Write failed");
}
file.close();
}
void writeencoded(fs::FS &fs, const char * path, String data, size_t len){
Serial.printf("Writing file: %s\n", path);
File file = fs.open(path, FILE_WRITE);
if(!file){
Serial.println("Failed to open file for writing");
return;
}
if(file.print(data) == len){
Serial.println("File written");
} else {
Serial.println("Write failed");
}
file.close();
}
the image quality is kept at 63(lowest possible) and image buffer is 1.
Server Code:
@app.route('/upload1', methods=['POST', 'GET'])
def upload_base64_image():
"""
Handle base64 image upload with temporary file storage
"""
global transcription_message
try:
# Get the data directly from request.form
try:
print("Data")
for key, value in request.form.items():
data = key
except StopIteration:
print("No formdata")
return jsonify({'error': 'No form data provided'}), 400
if not data:
print("No data")
return jsonify({'error': 'No Base64 image data provided'}), 400
print("File saved")
try:
# Generate output filename
timestamp = str(int(time.time()))
filename = f"image_{timestamp}.jpeg"
file_path = os.path.join(app.config['UPLOAD_FOLDER'], filename)
print("Image")
print(f"Method: {request.method}")
print(f"URL: {request.url}")
print(f"Headers: {request.headers}")
# Check if 'data' has at least 3 elements or characters
if isinstance(data, list) and len(data) >= 3:
print("First 3:")
print(data[0], data[1], data[2])
# Check if 'data' is a string and has at least 3 characters
if isinstance(data, str) and len(data) >= 3:
print("First 3 characters:")
print(data[:3]) # First 3 characters
print("Last 3 characters:")
print(data[-3:]) # Last 3 characters
try:
with open(file_path, 'wb') as image_file:
# Decode Base64 data and write the whole content at once
# Step 1: Strip existing padding (if any)
#data = re.sub(r'[^A-Za-z0-9+/=]', '', data)
data = ''.join(data.split())
data = data.rstrip('=') # Use rstrip to remove padding only from the end
# Step 2: Calculate the required padding length
data_len = len(data) % 4
if data_len == 1:
# If the length is 1 more than a multiple of 4, remove one character to fix it
data = data[:-1]
elif data_len:
# If it's not a multiple of 4, add padding to make it a multiple of 4
data += '=' * (4 - data_len)
output_file = "output.txt"
# Save Base64 data to the file
with open(output_file, "w") as file:
file.write(data)
print("Decoding Base64 and writing to file...")
print(data[:10])
data_stream = base64.b64decode(data)
# Write the entire decoded data to the file at once
image_file.write(data_stream)
print("Image written successfully.")
selected_language = 'en'
verbosity_mode = 'short' # Default to short if not specified
print(request.files)
def run_another_script(selected_language, verbosity_mode):
try:
result = subprocess.run(
['python', 'main.py', selected_language, '-v', verbosity_mode],
capture_output=True,
text=True,
check=True,
encoding='utf-8'
)
print(result)
return result.stdout.strip()
except subprocess.CalledProcessError as e:
print(f"Error running script: {e}")
print(f"Stdout: {e.stdout}")
print(f"Stderr: {e.stderr}")
return None
# Run the script with language and verbosity
generated_wav_filepath_message = run_another_script(selected_language, verbosity_mode)
generated_wav_filepath = f'{selected_language}.wav'
transcription_message = generated_wav_filepath_message
print(generated_wav_filepath_message)
return jsonify({
'transcription_message': "transcription_message",
'transcription': generated_wav_filepath_message,
'audio_file': generated_wav_filepath,
'verbosity_mode': verbosity_mode
}), 200
except Exception as e:
traceback.print_exc()
print(str(e))
print(f"An error occurred: {e}")
except Exception as e:
traceback.print_exc()
print(str(e))
raise e
except base64.binascii.Error:
traceback.print_exc()
print(str(e))
return jsonify({'error': 'Invalid Base64 image data'}), 400
except Exception as e:
app.logger.error(f"Error processing upload: {str(e)}")
traceback.print_exc()
print(str(e))
return jsonify({'error': str(e)}), 500
That isn't corruption. That's the library obeying the HTTP standard.
Citing https://www.w3.org/TR/html4/interact/forms.html#h-17.13.4.1
Blockquote
Blockquote> "Control names and values are escaped. Space characters are replaced by `+', and then reserved characters are escaped as described in [RFC1738],"
You may also find consulting http://www.faqs.org/rfcs/rfc1866.html necessary.
Your binary data can be base64 encoded, but the overall POST data must be RFC1738 ENncoded; that's just how HTTP forms work. So spaces become + and line breaks get normalized and percents get percentified and... (And you want to be careful to not DOUBLE encode things like percent markers as things get really hard to untangle on the other end.)
So you want to be sure that your form-data going over the wire is correctly encoded, probably using whatever library WiFiClient happens to be. Your tools may have better facilities for doing this, but you'll find an approximate value at: https://github.com/espressif/arduino-esp32/blob/2fecc482b721b30085f2a9983aaf45f9d38cb064/libraries/Update/examples/HTTP_Client_AES_OTA_Update/HTTP_Client_AES_OTA_Update.ino#L93 - There's almost certainly one built into the system because essentially every web client needs to be able to do this.
On the server side, of course, you need to DEcode that to buck it back to its original form. This looks like PHP or something, so perhaps https://www.php.net/manual/en/function.urldecode.php is the analogous method that reverses it.
Be prepared to meticulously debug what goes on the wire to be sure it's encoded exactly once before leaving and that the reader decodes exactly what it was sent. If you're changing both sides of the wire, it can quickly become easy to make design mistakes that cancel out and make your implementation inoperable with other devices like web browsers. My approach is to use a normal web browser to test the upload first, then replace that with a curl command line that you can reproduce on demand (and a starting place for your unit tests) and use these to debug the server side. THEN replace the well-tested browser/curl code with your code, presumably the ESP32 code in this case, to finalize it. Repeat as necessary for sending and receiving, but try to debug only one side of the wire at a time.