At any point, you can test the maximum possible String length -- considering both the hard-coded limit and available unfragmented heap -- with a single loop like this
for (size_t z = 6000; ; z += 10000) {
Serial.print(z);
String s;
if (s.reserve(z)) {
Serial.println(" is OK");
delay(10);
} else {
Serial.println(" is too big");
delay(2500); // if you need to pause to see the result
break;
}
}
It's the same status code, 400 Bad Request, but the response body may have details for what it wrong.
In this case, the Python server code is expecting request.files; a direct upload that is not multipart will do abort(400), which won't say anything useful. (You could add a message to that.) But maybe it will when trying multipart, and there's an error with it. So definitely do the
String response = client.getString();
Serial.println(response);
regardless of the httpResponseCode. You could alter the server to handle both direct or multipart. The direct upload of a File as a Stream is -- as you have discovered -- much simpler on the client/Arduino side. But you should also be able to make the multipart work as well.
You mean 33KB out of 65KB? Is any data that is present actually correct, but some of it is missing (which parts); or is it all garbled with the wrong bytes? Does the header have the correct lengths after the "RIFF" and "data"? What is the Content-Length that is sent? Is that the complete file length plus the several dozen other bytes that are added?
It will take a little time to put together a multipart stream.