Skip to content

Commit

Permalink
Fix image fetch from URL (#61)
Browse files Browse the repository at this point in the history
* fix image fetch from url

* add render width
  • Loading branch information
royshil committed Nov 28, 2023
1 parent c295b13 commit 2a0d073
Show file tree
Hide file tree
Showing 4 changed files with 83 additions and 17 deletions.
60 changes: 58 additions & 2 deletions src/request-data.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -598,9 +598,51 @@ url_source_request_data unserialize_request_data(std::string serialized_request_
return request_data;
}

bool isURL(const std::string &str)
{
// List of common URL schemes
std::string schemes[] = {"http:https://", "https://"};
for (const auto &scheme : schemes) {
if (str.substr(0, scheme.size()) == scheme) {
return true;
}
}
return false;
}

// Fetch image from url and get bytes
std::vector<uint8_t> fetch_image(std::string url)
std::vector<uint8_t> fetch_image(std::string url, std::string &mime_type)
{
// Check if the "url" is actually a file path
if (isURL(url) == false) {
// This is a file request (at least it's not a url)
// Read the file
std::ifstream imagefile(trim(url), std::ios::binary);
if (!imagefile) {
obs_log(LOG_INFO, "Failed to open file, %s", strerror(errno));
// Return an error response
std::vector<uint8_t> responseFail;
return responseFail;
}
std::vector<uint8_t> responseBody((std::istreambuf_iterator<char>(imagefile)),
std::istreambuf_iterator<char>());
imagefile.close();

// infer the mime type from the file extension
std::string extension = url.substr(url.find_last_of(".") + 1);
if (extension == "jpg" || extension == "jpeg") {
mime_type = "image/jpeg";
} else if (extension == "png") {
mime_type = "image/png";
} else if (extension == "gif") {
mime_type = "image/gif";
} else {
mime_type = "image/unknown";
}

return responseBody;
}

// Build the request with libcurl
CURL *curl = curl_easy_init();
if (!curl) {
Expand All @@ -619,14 +661,28 @@ std::vector<uint8_t> fetch_image(std::string url)

// Send the request
code = curl_easy_perform(curl);
curl_easy_cleanup(curl);
if (code != CURLE_OK) {
curl_easy_cleanup(curl);
obs_log(LOG_INFO, "Failed to send request: %s", curl_easy_strerror(code));
// Return an error response
std::vector<uint8_t> responseFail;
return responseFail;
}

// get the mime type from the response headers
char *ct;
code = curl_easy_getinfo(curl, CURLINFO_CONTENT_TYPE, &ct);
if (code != CURLE_OK) {
curl_easy_cleanup(curl);
obs_log(LOG_INFO, "Failed to get mime type: %s", curl_easy_strerror(code));
// Return an error response
std::vector<uint8_t> responseFail;
return responseFail;
}
mime_type = std::string(ct);

curl_easy_cleanup(curl);

return responseBody;
}

Expand Down
2 changes: 1 addition & 1 deletion src/request-data.h
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ std::string serialize_request_data(url_source_request_data *request_data);
url_source_request_data unserialize_request_data(std::string serialized_request_data);

// Fetch image from url and get bytes
std::vector<uint8_t> fetch_image(std::string url);
std::vector<uint8_t> fetch_image(std::string url, std::string &out_mime_type);

// encode bytes to base64
std::string base64_encode(const std::vector<uint8_t> &bytes);
Expand Down
2 changes: 1 addition & 1 deletion src/ui/text-render-helper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ void render_text_with_qtextdocument(const std::string &text, uint32_t &width, ui
.replace("{css_props}", QString::fromStdString(css_props));
QTextDocument textDocument;
textDocument.setHtml(html);
textDocument.setTextWidth(640);
textDocument.setTextWidth(width);

QPixmap pixmap(textDocument.size().toSize());
pixmap.fill(Qt::transparent);
Expand Down
36 changes: 23 additions & 13 deletions src/url-source.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ struct url_source_data {
bool output_is_image_url = false;
struct obs_source_frame frame;
bool send_to_stream = false;
uint32_t render_width = 640;

// Text source to output the text to
obs_weak_source_t *output_source = nullptr;
Expand Down Expand Up @@ -256,9 +257,6 @@ void curl_loop(struct url_source_data *usd)
cur_time = get_time_ns();
usd->frame.timestamp = cur_time - start_time;

uint32_t width = 0;
uint32_t height = 0;

if (usd->request_data.output_type == "Audio (data)") {
if (!is_valid_output_source_name(usd->output_source_name)) {
obs_log(LOG_ERROR,
Expand All @@ -273,28 +271,31 @@ void curl_loop(struct url_source_data *usd)
if (text.empty()) {
text = response.body_parts_parsed[0];
} else {
// if output is image URL - fetch the image and convert it to base64
// if output is image or image-URL - fetch the image and convert it to base64
if (usd->output_is_image_url ||
usd->request_data.output_type == "Image (data)") {
// use fetch_image to get the image
std::vector<uint8_t> image_data;
std::string mime_type = "image/png";
if (usd->request_data.output_type ==
"Image (data)") {
// if the output type is image data - use the response body bytes
image_data = response.body_bytes;
// get the mime type from the response headers if available
if (response.headers.find("content-type") !=
response.headers.end()) {
mime_type =
response.headers
["content-type"];
}
} else {
fetch_image(response.body_parts_parsed[0]);
// use fetch_image to get the image
image_data = fetch_image(
response.body_parts_parsed[0],
mime_type);
}
// convert the image to base64
const std::string base64_image =
base64_encode(image_data);
// get the mime type from the response headers if available
std::string mime_type = "image/png";
if (response.headers.find("content-type") !=
response.headers.end()) {
mime_type =
response.headers["content-type"];
}
// build an image tag with the base64 image
response.body_parts_parsed[0] =
"<img src=\"data:" + mime_type +
Expand Down Expand Up @@ -355,6 +356,9 @@ void curl_loop(struct url_source_data *usd)
obs_source_output_video(usd->source, &usd->frame);
} else {
uint8_t *renderBuffer = nullptr;
uint32_t width = usd->render_width;
uint32_t height = 0;

// render the text with QTextDocument
render_text_with_qtextdocument(
text, width, height, &renderBuffer, usd->css_props);
Expand Down Expand Up @@ -458,6 +462,7 @@ void url_source_update(void *data, obs_data_t *settings)
usd->css_props = obs_data_get_string(settings, "css_props");
usd->output_text_template = obs_data_get_string(settings, "template");
usd->send_to_stream = obs_data_get_bool(settings, "send_to_stream");
usd->render_width = (uint32_t)obs_data_get_int(settings, "render_width");

// update the text source
const char *new_text_source_name = obs_data_get_string(settings, "text_sources");
Expand Down Expand Up @@ -529,6 +534,9 @@ void url_source_defaults(obs_data_t *s)

// Default Template
obs_data_set_default_string(s, "template", "{{output}}");

// Default Render Width
obs_data_set_default_int(s, "render_width", 640);
}

bool setup_request_button_click(obs_properties_t *, obs_property_t *, void *button_data)
Expand Down Expand Up @@ -623,6 +631,8 @@ obs_properties_t *url_source_properties(void *data)
"Use {{body}} variable for unparsed object/array representation of the "
"entire response");

obs_properties_add_int(ppts, "render_width", "Render Width (px)", 100, 10000, 1);

return ppts;
}

Expand Down

0 comments on commit 2a0d073

Please sign in to comment.