From b96379b65a5cab957e39803f808aff3db57744f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20=C3=96man?= Date: Thu, 2 Jul 2015 17:24:35 +0200 Subject: [PATCH] Add screenshot->imgur feature --- Makefile | 7 +- src/api/httpcontrol.c | 6 +- src/api/screenshot.c | 268 +++++++++++++++++++++++++++++++++++ src/api/screenshot.h | 4 + src/event.h | 1 + src/networking/http_server.c | 1 + src/ui/glw/glw.c | 25 ++++ src/ui/glw/glw.h | 2 +- src/ui/glw/glw_opengl_ogl.c | 16 +++ 9 files changed, 324 insertions(+), 6 deletions(-) create mode 100644 src/api/screenshot.c create mode 100644 src/api/screenshot.h diff --git a/Makefile b/Makefile index 7a0558fb50..b9389912a8 100644 --- a/Makefile +++ b/Makefile @@ -263,8 +263,11 @@ ${BUILDDIR}/src/sd/avahi.o : CFLAGS = $(CFLAGS_AVAHI) -Wall -Werror ${OPTFLAGS} SRCS += src/api/xmlrpc.c \ src/api/soap.c \ -SRCS-$(CONFIG_HTTPSERVER) += src/api/httpcontrol.c -SRCS-$(CONFIG_HTTPSERVER) += src/api/stpp.c +SRCS-$(CONFIG_HTTPSERVER) += \ + src/api/httpcontrol.c \ + src/api/screenshot.c \ + src/api/stpp.c \ + SRCS-$(CONFIG_AIRPLAY) += src/api/airplay.c ############################################################## diff --git a/src/api/httpcontrol.c b/src/api/httpcontrol.c index 190874d012..2f38f0a090 100644 --- a/src/api/httpcontrol.c +++ b/src/api/httpcontrol.c @@ -126,15 +126,15 @@ hc_root_old(http_connection_t *hc) "" "" "", APPNAMEUSER); - + htsbuf_qprintf(&out, "

Diagnostics

"); diag_html(hc, &out); - + htsbuf_qprintf(&out, "

Upload screenshot to imgur

"); htsbuf_qprintf(&out, "

Upload and test new translation (.lang) file

"); htsbuf_qprintf(&out, ""); - + return http_send_reply(hc, 0, "text/html", NULL, NULL, 0, &out); } diff --git a/src/api/screenshot.c b/src/api/screenshot.c new file mode 100644 index 0000000000..cda2ccd948 --- /dev/null +++ b/src/api/screenshot.c @@ -0,0 +1,268 @@ +/* + * Copyright (C) 2007-2015 Lonelycoder AB + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * This program is also available under a commercial proprietary license. + * For more information, contact andreas@lonelycoder.com + */ + +#include "main.h" +#include "networking/http.h" +#include "networking/http_server.h" +#include "task.h" +#include "event.h" +#include "screenshot.h" +#include "image/pixmap.h" +#include "htsmsg/htsmsg_json.h" +#include "fileaccess/http_client.h" + +#include +#include + +static hts_mutex_t screenshot_mutex; +static http_connection_t *screenshot_connection; + + +/** + * + */ +static int +hc_screenshot(http_connection_t *hc, const char *remain, + void *opaque, http_cmd_t method) +{ + hts_mutex_lock(&screenshot_mutex); + if(screenshot_connection != NULL) { + hts_mutex_unlock(&screenshot_mutex); + return 502; + } + screenshot_connection = hc; + hts_mutex_unlock(&screenshot_mutex); + + event_to_ui(event_create(EVENT_MAKE_SCREENSHOT, sizeof(event_t))); + return 0; +} + + +/** + * + */ +typedef struct response { + char *errmsg; + char *url; +} response_t; + + +/** + * + */ +static void +screenshot_response_task(void *task) +{ + response_t *r = task; + hts_mutex_lock(&screenshot_mutex); + if(screenshot_connection == NULL) { + hts_mutex_unlock(&screenshot_mutex); + } else { + http_connection_t *hc = screenshot_connection; + screenshot_connection = NULL; + hts_mutex_unlock(&screenshot_mutex); + + if(r->url != NULL) { + http_redirect(hc, r->url); + } else { + const char *msg = r->errmsg; + if(msg == NULL) + msg = "Error not specified"; + htsbuf_queue_t out; + htsbuf_queue_init(&out, 0); + htsbuf_append(&out, msg, strlen(msg)); + htsbuf_append_byte(&out, '\n'); + http_send_reply(hc, 500, "text/plain", NULL, NULL, 0, &out); + } + } + free(r->url); + free(r->errmsg); + free(r); +} + + + +/** + * + */ +static void +screenshot_response(const char *url, const char *errmsg) +{ + response_t *r = calloc(1, sizeof(response_t)); + r->url = url ? strdup(url) : NULL; + r->errmsg = errmsg ? strdup(errmsg) : NULL; + asyncio_run_task(screenshot_response_task, r); +} + + +/** + * + */ +static buf_t * +screenshot_compress(pixmap_t *pm) +{ + AVCodec *codec = avcodec_find_encoder(AV_CODEC_ID_MJPEG); + if(codec == NULL) + return NULL; + + const int width = pm->pm_width; + const int height = pm->pm_height; + + AVCodecContext *ctx = avcodec_alloc_context3(codec); + ctx->pix_fmt = AV_PIX_FMT_YUVJ420P; + ctx->time_base.den = 1; + ctx->time_base.num = 1; + ctx->sample_aspect_ratio.num = 1; + ctx->sample_aspect_ratio.den = 1; + ctx->width = width; + ctx->height = height; + + if(avcodec_open2(ctx, codec, NULL) < 0) { + TRACE(TRACE_ERROR, "ScreenShot", "Unable to open image encoder"); + return NULL; + } + + AVFrame *oframe = av_frame_alloc(); + + avpicture_alloc((AVPicture *)oframe, ctx->pix_fmt, width, height); + + const uint8_t *ptr[4] = {pm->pm_data + pm->pm_linesize * (height - 1)}; + int strides[4] = {-pm->pm_linesize}; + + struct SwsContext *sws; + sws = sws_getContext(width, height, PIX_FMT_RGB32, + width, height, ctx->pix_fmt, SWS_BILINEAR, + NULL, NULL, NULL); + + sws_scale(sws, ptr, strides, + 0, height, &oframe->data[0], &oframe->linesize[0]); + sws_freeContext(sws); + + oframe->pts = AV_NOPTS_VALUE; + AVPacket out; + memset(&out, 0, sizeof(AVPacket)); + int got_packet; + int r = avcodec_encode_video2(ctx, &out, oframe, &got_packet); + buf_t *b; + if(r >= 0 && got_packet) { + b = buf_create_and_adopt(out.size, out.data, &av_free); + } else { + assert(out.data == NULL); + b = NULL; + } + av_frame_free(&oframe); + avcodec_close(ctx); + av_free(ctx); + return b; +} + + +/** + * + */ +static void +screenshot_process(void *task) +{ + pixmap_t *pm = task; + + if(pm == NULL) { + screenshot_response(NULL, "Screenshot not supported on this platform"); + return; + } + + TRACE(TRACE_DEBUG, "Screenshot", "Processing image %d x %d", + pm->pm_width, pm->pm_height); + + buf_t *b = screenshot_compress(pm); + pixmap_release(pm); + if(b == NULL) { + screenshot_response(NULL, "Unable to compress image"); + return; + } + + buf_t *result = NULL; + htsbuf_queue_t hq; + htsbuf_queue_init(&hq, 0); + + htsbuf_append(&hq, "image=", 6); + htsbuf_append_and_escape_url_len(&hq, buf_cstr(b), buf_len(b)); + + char errbuf[256]; + + int ret = http_req("https://api.imgur.com/3/upload", + HTTP_FLAGS(FA_CONTENT_ON_ERROR), + HTTP_REQUEST_HEADER("Authorization", + "Client-ID 7c79b311d4797ed"), + HTTP_RESULT_PTR(&result), + HTTP_POSTDATA(&hq, "application/x-www-form-urlencoded"), + HTTP_ERRBUF(errbuf, sizeof(errbuf)), + NULL); + + + if(ret) { + screenshot_response(NULL, errbuf); + } else { + + htsmsg_t *response = htsmsg_json_deserialize(buf_cstr(result)); + if(response == NULL) { + screenshot_response(NULL, "Unable to parse imgur response"); + } else { + + if(htsmsg_get_u32_or_default(response, "success", 0)) { + const char *url = htsmsg_get_str_multi(response, "data", "link", NULL); + screenshot_response(url, "No link in imgur response"); + } else { + const char *msg = htsmsg_get_str_multi(response, "data", "error", NULL); + if(msg == NULL) { + screenshot_response(NULL, "Unkown imgur error"); + } else { + snprintf(errbuf, sizeof(errbuf), "Imgur error: %s", msg); + screenshot_response(NULL, errbuf); + } + } + htsmsg_release(response); + } + buf_release(result); + } + buf_release(b); +} + + +/** + * + */ +void +screenshot_deliver(pixmap_t *pm) +{ + task_run(screenshot_process, pixmap_dup(pm)); +} + + +/** + * + */ +static void +screenshot_init(void) +{ + hts_mutex_init(&screenshot_mutex); + http_path_add("/showtime/screenshot", NULL, hc_screenshot, 0); +} + +INITME(INIT_GROUP_API, screenshot_init, NULL); diff --git a/src/api/screenshot.h b/src/api/screenshot.h new file mode 100644 index 0000000000..f0635c8fe4 --- /dev/null +++ b/src/api/screenshot.h @@ -0,0 +1,4 @@ +#pragma once + +struct pixmap; +void screenshot_deliver(struct pixmap *pm); diff --git a/src/event.h b/src/event.h index cf6dce0ef3..73673f36c7 100644 --- a/src/event.h +++ b/src/event.h @@ -160,6 +160,7 @@ typedef enum { EVENT_REDIRECT = 25, EVENT_PROPREF = 26, EVENT_DYNAMIC_ACTION = 27, + EVENT_MAKE_SCREENSHOT = 28, } event_type_t; diff --git a/src/networking/http_server.c b/src/networking/http_server.c index f3a6f2766f..5a283e57f1 100644 --- a/src/networking/http_server.c +++ b/src/networking/http_server.c @@ -394,6 +394,7 @@ http_send_reply(http_connection_t *hc, int rc, const char *content, else htsbuf_appendq(&hc->hc_output, output); } + http_write(hc); return 0; } diff --git a/src/ui/glw/glw.c b/src/ui/glw/glw.c index 775aa4e532..739231497d 100644 --- a/src/ui/glw/glw.c +++ b/src/ui/glw/glw.c @@ -35,6 +35,8 @@ #include "glw_style.h" #include "glw_navigation.h" +#include "api/screenshot.h" + static void glw_focus_init_widget(glw_t *w, float weight); static void glw_focus_leave(glw_t *w); static void glw_root_set_hover(glw_root_t *gr, glw_t *w); @@ -1970,6 +1972,24 @@ glw_kill_screensaver(glw_root_t *gr) return r; } + +/** + * + */ +static void +glw_screenshot(glw_root_t *gr) +{ + if(gr->gr_br_read_pixels == NULL) { + screenshot_deliver(NULL); + return; + } + + pixmap_t *pm = gr->gr_br_read_pixels(gr); + screenshot_deliver(pm); + pixmap_release(pm); +} + + /** * */ @@ -1985,6 +2005,11 @@ glw_dispatch_event(glw_root_t *gr, event_t *e) return; } + if(e->e_type == EVENT_MAKE_SCREENSHOT) { + glw_screenshot(gr); + return; + } + if(e->e_type == EVENT_KEYDESC) { if(glw_event(gr, e)) diff --git a/src/ui/glw/glw.h b/src/ui/glw/glw.h index 1548e73182..a9e3b52ebf 100644 --- a/src/ui/glw/glw.h +++ b/src/ui/glw/glw.h @@ -862,7 +862,7 @@ typedef struct glw_root { */ glw_backend_root_t gr_be; void (*gr_be_render_unlocked)(struct glw_root *gr); - + struct pixmap *(*gr_br_read_pixels)(struct glw_root *gr); /** * Settings */ diff --git a/src/ui/glw/glw_opengl_ogl.c b/src/ui/glw/glw_opengl_ogl.c index e3a687fc56..7bca9904a0 100644 --- a/src/ui/glw/glw_opengl_ogl.c +++ b/src/ui/glw/glw_opengl_ogl.c @@ -107,6 +107,19 @@ glw_rtt_destroy(glw_root_t *gr, glw_rtt_t *grtt) } +/** + * + */ +static pixmap_t * +opengl_read_pixels(glw_root_t *gr) +{ + pixmap_t *pm = pixmap_create(gr->gr_width, gr->gr_height, PIXMAP_BGR32, 0); + + glReadPixels(0, 0, gr->gr_width, gr->gr_height, + GL_BGRA, GL_UNSIGNED_BYTE, pm->pm_data); + return pm; +} + /** * @@ -121,6 +134,7 @@ glw_opengl_init_context(glw_root_t *gr) glEnable(GL_CULL_FACE); glPixelStorei(GL_UNPACK_ALIGNMENT, PIXMAP_ROW_ALIGN); + glPixelStorei(GL_PACK_ALIGNMENT, PIXMAP_ROW_ALIGN); glGetIntegerv(GL_MAX_TEXTURE_IMAGE_UNITS, &tu); if(tu < 6) { @@ -136,5 +150,7 @@ glw_opengl_init_context(glw_root_t *gr) const char *renderer = (const char *)glGetString(GL_RENDERER); TRACE(TRACE_INFO, "GLW", "OpenGL Renderer: '%s' by '%s'", renderer, vendor); + gr->gr_br_read_pixels = opengl_read_pixels; + return glw_opengl_shaders_init(gr, 0); }