mirror of
https://github.com/Palakis/obs-websocket.git
synced 2024-08-30 18:12:16 +00:00
Requests: GetStreamScreenshot
Adds a new request called `GetStreamScreenshot` which returns a Base64-encoded screenshot of the stream (program).
This commit is contained in:
parent
0548c7798a
commit
3faf2deb2a
@ -168,6 +168,7 @@ const std::unordered_map<std::string, RequestMethodHandler> RequestHandler::_han
|
||||
{"StartStream", &RequestHandler::StartStream},
|
||||
{"StopStream", &RequestHandler::StopStream},
|
||||
{"SendStreamCaption", &RequestHandler::SendStreamCaption},
|
||||
{"GetStreamScreenshot", &RequestHandler::GetStreamScreenshot},
|
||||
|
||||
// Record
|
||||
{"GetRecordStatus", &RequestHandler::GetRecordStatus},
|
||||
|
@ -187,6 +187,7 @@ private:
|
||||
RequestResult StartStream(const Request &);
|
||||
RequestResult StopStream(const Request &);
|
||||
RequestResult SendStreamCaption(const Request &);
|
||||
RequestResult GetStreamScreenshot(const Request &request);
|
||||
|
||||
// Record
|
||||
RequestResult GetRecordStatus(const Request &);
|
||||
|
@ -33,7 +33,7 @@ with this program. If not, see <https://www.gnu.org/licenses/>
|
||||
* @responseField obsWebSocketVersion | String | Current obs-websocket version
|
||||
* @responseField rpcVersion | Number | Current latest obs-websocket RPC version
|
||||
* @responseField availableRequests | Array<String> | Array of available RPC requests for the currently negotiated RPC version
|
||||
* @responseField supportedImageFormats | Array<String> | Image formats available in `GetSourceScreenshot` and `SaveSourceScreenshot` requests.
|
||||
* @responseField supportedImageFormats | Array<String> | Image formats available in `GetSourceScreenshot`, `SaveSourceScreenshot` and `GetStreamScreenshot` requests.
|
||||
* @responseField platform | String | Name of the platform. Usually `windows`, `macos`, or `ubuntu` (linux flavor). Not guaranteed to be any of those
|
||||
* @responseField platformDescription | String | Description of the platform, like `Windows 10 (10.0)`
|
||||
*
|
||||
|
@ -17,8 +17,98 @@ You should have received a copy of the GNU General Public License along
|
||||
with this program. If not, see <https://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
#include <QBuffer>
|
||||
#include <QImageWriter>
|
||||
#include <QFileInfo>
|
||||
#include <QImage>
|
||||
#include <QDir>
|
||||
|
||||
#include "RequestHandler.h"
|
||||
|
||||
QImage TakeStreamScreenshot(bool &success, uint32_t requestedWidth = 0, uint32_t requestedHeight = 0)
|
||||
{
|
||||
// Get info about the program
|
||||
obs_video_info ovi;
|
||||
obs_get_video_info(&ovi);
|
||||
const uint32_t streamWidth = ovi.base_width;
|
||||
const uint32_t streamHeight = ovi.base_height;
|
||||
const double streamAspectRatio = ((double)streamWidth / (double)streamHeight);
|
||||
|
||||
uint32_t imgWidth = streamWidth;
|
||||
uint32_t imgHeight = streamHeight;
|
||||
|
||||
// Determine suitable image width
|
||||
if (requestedWidth) {
|
||||
imgWidth = requestedWidth;
|
||||
|
||||
if (!requestedHeight)
|
||||
imgHeight = ((double)imgWidth / streamAspectRatio);
|
||||
}
|
||||
|
||||
// Determine suitable image height
|
||||
if (requestedHeight) {
|
||||
imgHeight = requestedHeight;
|
||||
|
||||
if (!requestedWidth)
|
||||
imgWidth = ((double)imgHeight * streamAspectRatio);
|
||||
}
|
||||
|
||||
// Create final image texture
|
||||
QImage ret(imgWidth, imgHeight, QImage::Format::Format_RGBA8888);
|
||||
ret.fill(0);
|
||||
|
||||
// Video image buffer
|
||||
uint8_t *videoData = nullptr;
|
||||
uint32_t videoLinesize = 0;
|
||||
|
||||
// Enter graphics context
|
||||
obs_enter_graphics();
|
||||
|
||||
gs_texrender_t *texRender = gs_texrender_create(GS_RGBA, GS_ZS_NONE);
|
||||
gs_stagesurf_t *stageSurface = gs_stagesurface_create(imgWidth, imgHeight, GS_RGBA);
|
||||
|
||||
success = false;
|
||||
gs_texrender_reset(texRender);
|
||||
if (gs_texrender_begin(texRender, imgWidth, imgHeight)) {
|
||||
vec4 background;
|
||||
vec4_zero(&background);
|
||||
|
||||
gs_clear(GS_CLEAR_COLOR, &background, 0.0f, 0);
|
||||
gs_ortho(0.0f, (float)streamWidth, 0.0f, (float)streamHeight, -100.0f, 100.0f);
|
||||
|
||||
gs_blend_state_push();
|
||||
gs_blend_function(GS_BLEND_ONE, GS_BLEND_ZERO);
|
||||
|
||||
obs_render_main_texture();
|
||||
|
||||
gs_blend_state_pop();
|
||||
gs_texrender_end(texRender);
|
||||
|
||||
gs_stage_texture(stageSurface, gs_texrender_get_texture(texRender));
|
||||
if (gs_stagesurface_map(stageSurface, &videoData, &videoLinesize)) {
|
||||
int lineSize = ret.bytesPerLine();
|
||||
for (uint y = 0; y < imgHeight; y++) {
|
||||
memcpy(ret.scanLine(y), videoData + (y * videoLinesize), lineSize);
|
||||
}
|
||||
gs_stagesurface_unmap(stageSurface);
|
||||
success = true;
|
||||
}
|
||||
}
|
||||
|
||||
gs_stagesurface_destroy(stageSurface);
|
||||
gs_texrender_destroy(texRender);
|
||||
|
||||
obs_leave_graphics();
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool IsStreamImageFormatValid(std::string format)
|
||||
{
|
||||
QByteArrayList supportedFormats = QImageWriter::supportedImageFormats();
|
||||
return supportedFormats.contains(format.c_str());
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the status of the stream output.
|
||||
*
|
||||
@ -160,3 +250,80 @@ RequestResult RequestHandler::SendStreamCaption(const Request &request)
|
||||
|
||||
return RequestResult::Success();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a Base64-encoded screenshot of the stream.
|
||||
*
|
||||
* The `imageWidth` and `imageHeight` parameters are treated as "scale to inner", meaning the smallest ratio will be used and the aspect ratio of the original resolution is kept.
|
||||
* If `imageWidth` and `imageHeight` are not specified, the compressed image will use the full resolution of the stream.
|
||||
*
|
||||
* @requestField imageFormat | String | Image compression format to use. Use `GetVersion` to get compatible image formats
|
||||
* @requestField ?imageWidth | Number | Width to scale the screenshot to | >= 8, <= 4096 | Stream value is used
|
||||
* @requestField ?imageHeight | Number | Height to scale the screenshot to | >= 8, <= 4096 | Stream value is used
|
||||
* @requestField ?imageCompressionQuality | Number | Compression quality to use. 0 for high compression, 100 for uncompressed. -1 to use "default" (whatever that means, idk) | >= -1, <= 100 | -1
|
||||
*
|
||||
* @responseField imageData | String | Base64-encoded screenshot
|
||||
*
|
||||
* @requestType GetOutputScreenshot
|
||||
* @complexity 4
|
||||
* @rpcVersion -1
|
||||
* @initialVersion 5.4.0
|
||||
* @category stream
|
||||
* @api requests
|
||||
*/
|
||||
RequestResult RequestHandler::GetStreamScreenshot(const Request &request)
|
||||
{
|
||||
RequestStatus::RequestStatus statusCode;
|
||||
std::string comment;
|
||||
std::string imageFormat = request.RequestData["imageFormat"];
|
||||
|
||||
if (!IsStreamImageFormatValid(imageFormat))
|
||||
return RequestResult::Error(RequestStatus::InvalidRequestField,
|
||||
"Your specified image format is invalid or not supported by this system.");
|
||||
|
||||
uint32_t requestedWidth{0};
|
||||
uint32_t requestedHeight{0};
|
||||
int compressionQuality{-1};
|
||||
|
||||
if (request.Contains("imageWidth")) {
|
||||
if (!request.ValidateOptionalNumber("imageWidth", statusCode, comment, 8, 4096))
|
||||
return RequestResult::Error(statusCode, comment);
|
||||
|
||||
requestedWidth = request.RequestData["imageWidth"];
|
||||
}
|
||||
|
||||
if (request.Contains("imageHeight")) {
|
||||
if (!request.ValidateOptionalNumber("imageHeight", statusCode, comment, 8, 4096))
|
||||
return RequestResult::Error(statusCode, comment);
|
||||
|
||||
requestedHeight = request.RequestData["imageHeight"];
|
||||
}
|
||||
|
||||
if (request.Contains("imageCompressionQuality")) {
|
||||
if (!request.ValidateOptionalNumber("imageCompressionQuality", statusCode, comment, -1, 100))
|
||||
return RequestResult::Error(statusCode, comment);
|
||||
|
||||
compressionQuality = request.RequestData["imageCompressionQuality"];
|
||||
}
|
||||
|
||||
bool success;
|
||||
QImage renderedImage = TakeStreamScreenshot(success, requestedWidth, requestedHeight);
|
||||
|
||||
if (!success)
|
||||
return RequestResult::Error(RequestStatus::RequestProcessingFailed, "Failed to render screenshot.");
|
||||
|
||||
QByteArray encodedImgBytes;
|
||||
QBuffer buffer(&encodedImgBytes);
|
||||
buffer.open(QBuffer::WriteOnly);
|
||||
|
||||
if (!renderedImage.save(&buffer, imageFormat.c_str(), compressionQuality))
|
||||
return RequestResult::Error(RequestStatus::RequestProcessingFailed, "Failed to encode screenshot.");
|
||||
|
||||
buffer.close();
|
||||
|
||||
QString encodedPicture = QString("data:image/%1;base64,").arg(imageFormat.c_str()).append(encodedImgBytes.toBase64());
|
||||
|
||||
json responseData;
|
||||
responseData["imageData"] = encodedPicture.toStdString();
|
||||
return RequestResult::Success(responseData);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user