Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix image fetch from URL #61

Merged
merged 2 commits into from
Nov 28, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 58 additions & 2 deletions src/request-data.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -538,9 +538,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://"};
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 @@ -559,14 +601,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 @@ -92,7 +92,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 @@ -263,9 +264,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 @@ -280,28 +278,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 @@ -362,6 +363,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 @@ -465,6 +469,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 @@ -536,6 +541,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 @@ -630,6 +638,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