vout: refresh EPG OSD
[vlc.git] / src / video_output / video_epg.c
blob9576eb068290f0a71632b60668d195aa11701fb5
1 /*****************************************************************************
2 * video_epg.c : EPG manipulation functions
3 *****************************************************************************
4 * Copyright (C) 2010 Adrien Maglo
5 * 2017 VLC authors, VideoLAN and VideoLabs
7 * Author: Adrien Maglo <magsoft@videolan.org>
9 * This program is free software; you can redistribute it and/or modify it
10 * under the terms of the GNU Lesser General Public License as published by
11 * the Free Software Foundation; either version 2.1 of the License, or
12 * (at your option) any later version.
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU Lesser General Public License for more details.
19 * You should have received a copy of the GNU Lesser General Public License
20 * along with this program; if not, write to the Free Software Foundation,
21 * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
22 *****************************************************************************/
24 #ifdef HAVE_CONFIG_H
25 # include "config.h"
26 #endif
28 #include <time.h>
30 #include <vlc_common.h>
31 #include <vlc_vout.h>
32 #include <vlc_vout_osd.h>
33 #include <vlc_events.h>
34 #include <vlc_input_item.h>
35 #include <vlc_epg.h>
36 #include <vlc_url.h>
37 #include "vout_spuregion_helper.h"
39 /* Layout percentage defines */
40 #define OSDEPG_MARGIN 0.025
41 #define OSDEPG_MARGINS (OSDEPG_MARGIN * 2)
42 #define OSDEPG_PADDING 0.05 /* inner margins */
43 #define OSDEPG_WIDTH (1.0 - OSDEPG_MARGINS)
44 #define OSDEPG_HEIGHT 0.25
45 #define OSDEPG_LEFT OSDEPG_MARGIN
46 #define OSDEPG_TOP (1.0 - OSDEPG_MARGINS - OSDEPG_HEIGHT + OSDEPG_MARGIN)
48 /* layout */
49 #define OSDEPG_ROWS_COUNT 10
50 #define OSDEPG_ROW_HEIGHT (1.0 / OSDEPG_ROWS_COUNT)
51 #define OSDEPG_LOGO_SIZE (OSDEPG_HEIGHT)
53 /* shortcuts */
54 #define OSDEPG_RIGHT (1.0 - OSDEPG_MARGIN)
56 #define OSDEPG_ROWS(x) (OSDEPG_ROW_HEIGHT * x)
57 #define OSDEPG_ROW(x) (OSDEPG_ROWS(x))
59 #define EPGOSD_TEXTSIZE_NAME (OSDEPG_ROWS(2))
60 #define EPGOSD_TEXTSIZE_PROG (OSDEPG_ROWS(2))
61 #define EPGOSD_TEXTSIZE_NTWK (OSDEPG_ROWS(2))
63 #define RGB2YUV( R, G, B ) \
64 ((0.257 * R) + (0.504 * G) + (0.098 * B) + 16), \
65 (-(0.148 * R) - (0.291 * G) + (0.439 * B) + 128),\
66 ((0.439 * R) - (0.368 * G) - (0.071 * B) + 128)
68 #define HEX2YUV( rgb ) \
69 RGB2YUV( (rgb >> 16), ((rgb & 0xFF00) >> 8), (rgb & 0xFF) )
71 //#define RGB_COLOR1 0xf48b00
72 //#define ARGB_BGCOLOR 0xC0333333
74 #define RGB_COLOR1 0x2badde
75 #define ARGB_BGCOLOR 0xc003182d
77 struct subpicture_updater_sys_t
79 vlc_epg_t *epg;
80 int64_t time;
81 char *art;
82 vlc_object_t *obj;
85 static char * GetDefaultArtUri( void )
87 char *psz_uri = NULL;
88 char *psz_path;
89 char *psz_datadir = config_GetDataDir();
90 if( asprintf( &psz_path, "%s/icons/128x128/vlc.png", psz_datadir ) >= 0 )
92 psz_uri = vlc_path2uri( psz_path, NULL );
93 free( psz_path );
95 free( psz_datadir );
96 return psz_uri;
99 #define GRADIENT_COLORS 40
101 static subpicture_region_t * vout_OSDBackground(int x, int y,
102 int width, int height,
103 uint32_t i_argb)
105 /* Create a new subpicture region */
106 video_palette_t palette;
107 spuregion_CreateVGradientPalette( &palette, GRADIENT_COLORS, i_argb, 0xFF000000 );
109 video_format_t fmt;
110 video_format_Init(&fmt, VLC_CODEC_YUVP);
111 fmt.i_width = fmt.i_visible_width = width;
112 fmt.i_height = fmt.i_visible_height = height;
113 fmt.i_sar_num = 1;
114 fmt.i_sar_den = 1;
115 fmt.p_palette = &palette;
117 subpicture_region_t *region = subpicture_region_New(&fmt);
118 if (!region)
119 return NULL;
121 region->i_align = SUBPICTURE_ALIGN_LEFT | SUBPICTURE_ALIGN_TOP;
122 region->i_x = x;
123 region->i_y = y;
125 spuregion_CreateVGradientFill( region->p_picture->p, palette.i_entries );
127 return region;
130 static subpicture_region_t * vout_OSDEpgSlider(int x, int y,
131 int width, int height,
132 float ratio)
134 /* Create a new subpicture region */
135 video_palette_t palette = {
136 .i_entries = 4,
137 .palette = {
138 [0] = { HEX2YUV(RGB_COLOR1), 0x20 }, /* Bar fill remain/background */
139 [1] = { HEX2YUV(0x00ff00), 0xff },
140 [2] = { HEX2YUV(RGB_COLOR1), 0xC0 }, /* Bar fill */
141 [3] = { HEX2YUV(0xffffff), 0xff }, /* Bar outline */
145 video_format_t fmt;
146 video_format_Init(&fmt, VLC_CODEC_YUVP);
147 fmt.i_width = fmt.i_visible_width = width;
148 fmt.i_height = fmt.i_visible_height = height;
149 fmt.i_sar_num = 1;
150 fmt.i_sar_den = 1;
151 fmt.p_palette = &palette;
153 subpicture_region_t *region = subpicture_region_New(&fmt);
154 if (!region)
155 return NULL;
157 region->i_align = SUBPICTURE_ALIGN_LEFT | SUBPICTURE_ALIGN_TOP;
158 region->i_x = x;
159 region->i_y = y;
161 picture_t *picture = region->p_picture;
163 ratio = VLC_CLIP(ratio, 0, 1);
164 int filled_part_width = ratio * width;
166 for (int j = 0; j < height; j++) {
167 for (int i = 0; i < width; i++) {
168 /* Slider border. */
169 bool is_outline = j == 0 || j == height - 1 ||
170 i == 0 || i == width - 1;
171 /* We can see the video through the part of the slider
172 which corresponds to the leaving time. */
173 bool is_border = j < 3 || j > height - 4 ||
174 i < 3 || i > width - 4 ||
175 i < filled_part_width;
177 picture->p->p_pixels[picture->p->i_pitch * j + i] = 2 * is_border + is_outline;
181 return region;
184 static void vout_OSDSegmentSetNoWrap(text_segment_t *p_segment)
186 for( ; p_segment; p_segment = p_segment->p_next )
188 p_segment->style->e_wrapinfo = STYLE_WRAP_NONE;
189 p_segment->style->i_features |= STYLE_HAS_WRAP_INFO;
193 static text_segment_t * vout_OSDSegment(const char *psz_text, int size, uint32_t color)
195 text_segment_t *p_segment = text_segment_New(psz_text);
196 if(unlikely(!p_segment))
197 return NULL;
199 /* Set text style */
200 p_segment->style = text_style_Create(STYLE_NO_DEFAULTS);
201 if (unlikely(!p_segment->style))
203 text_segment_Delete(p_segment);
204 return NULL;
207 p_segment->style->i_font_size = __MAX(size ,1 );
208 p_segment->style->i_font_color = color;
209 p_segment->style->i_font_alpha = STYLE_ALPHA_OPAQUE;
210 p_segment->style->i_outline_alpha = STYLE_ALPHA_TRANSPARENT;
211 p_segment->style->i_shadow_alpha = STYLE_ALPHA_TRANSPARENT;
212 p_segment->style->i_features |= STYLE_HAS_FONT_ALPHA | STYLE_HAS_FONT_COLOR |
213 STYLE_HAS_OUTLINE_ALPHA | STYLE_HAS_SHADOW_ALPHA;
215 return p_segment;
218 static subpicture_region_t * vout_OSDImage( vlc_object_t *p_obj,
219 int x, int y, int w, int h,
220 const char *psz_uri )
222 video_format_t fmt_out;
223 video_format_Init( &fmt_out, VLC_CODEC_YUVA );
224 fmt_out.i_width = fmt_out.i_visible_width = w;
225 fmt_out.i_height = fmt_out.i_visible_height = h;
227 subpicture_region_t *image =
228 spuregion_CreateFromPicture( p_obj, &fmt_out, psz_uri );
229 if( image )
231 image->i_x = x;
232 image->i_y = y;
233 image->i_align = SUBPICTURE_ALIGN_LEFT|SUBPICTURE_ALIGN_TOP;
235 return image;
238 static void vout_OSDRegionConstrain(subpicture_region_t *p_region, int w, int h)
240 if( p_region )
242 p_region->i_max_width = w;
243 p_region->i_max_height = h;
247 static subpicture_region_t * vout_OSDTextRegion(text_segment_t *p_segment,
248 int x, int y )
250 video_format_t fmt;
251 subpicture_region_t *region;
253 if (!p_segment)
254 return NULL;
256 /* Create a new subpicture region */
257 video_format_Init(&fmt, VLC_CODEC_TEXT);
258 fmt.i_sar_num = 1;
259 fmt.i_sar_den = 1;
261 region = subpicture_region_New(&fmt);
262 if (!region)
263 return NULL;
265 region->p_text = p_segment;
266 region->i_align = SUBPICTURE_ALIGN_LEFT | SUBPICTURE_ALIGN_TOP;
267 region->i_x = x;
268 region->i_y = y;
269 region->b_balanced_text = false;
271 return region;
274 static subpicture_region_t * vout_OSDEpgText(const char *text,
275 int x, int y,
276 int size, uint32_t color)
278 return vout_OSDTextRegion(vout_OSDSegment(text, size, color), x, y);
281 static char * vout_OSDPrintTime(time_t t)
283 char *psz;
284 struct tm tms;
285 localtime_r(&t, &tms);
286 if(asprintf(&psz, "%2.2d:%2.2d", tms.tm_hour, tms.tm_min) < 0)
287 psz = NULL;
288 return psz;
291 static subpicture_region_t * vout_OSDEpgEvent(const vlc_epg_event_t *p_evt,
292 int x, int y, int size)
294 text_segment_t *p_segment = NULL;
295 char *psz_start = vout_OSDPrintTime(p_evt->i_start);
296 char *psz_end = vout_OSDPrintTime(p_evt->i_start + p_evt->i_duration);
297 char *psz_text;
298 if( -1 < asprintf(&psz_text, "%s-%s ", psz_start, psz_end))
300 p_segment = vout_OSDSegment(psz_text, size, RGB_COLOR1);
301 if( p_segment )
302 p_segment->p_next = vout_OSDSegment(p_evt->psz_name, size, 0xffffff);
303 vout_OSDSegmentSetNoWrap( p_segment );
305 free( psz_start );
306 free( psz_end );
307 if(!p_segment)
308 return NULL;
309 return vout_OSDTextRegion(p_segment, x, y);
312 static void vout_FillRightPanel(subpicture_updater_sys_t *p_sys,
313 int x, int y,
314 int width, int height,
315 int rx, int ry,
316 subpicture_region_t **last_ptr)
318 float f_progress = 0;
319 VLC_UNUSED(ry);
321 /* Format the hours */
322 char *psz_network = vout_OSDPrintTime(p_sys->time);
324 /* Display the name of the channel. */
325 *last_ptr = vout_OSDEpgText(p_sys->epg->psz_name,
328 height * EPGOSD_TEXTSIZE_NAME,
329 0x00ffffff);
330 if(*last_ptr)
331 last_ptr = &(*last_ptr)->p_next;
333 const vlc_epg_event_t *p_current = p_sys->epg->p_current;
334 vlc_epg_event_t *p_next = NULL;
335 if(!p_sys->epg->p_current && p_sys->epg->i_event)
336 p_current = p_sys->epg->pp_event[0];
338 for(size_t i=0; i<p_sys->epg->i_event; i++)
340 if( p_sys->epg->pp_event[i]->i_id != p_current->i_id )
342 p_next = p_sys->epg->pp_event[i];
343 break;
347 /* Display the name of the current program. */
348 if(p_current)
350 *last_ptr = vout_OSDEpgEvent(p_current,
352 y + height * OSDEPG_ROW(2),
353 height * EPGOSD_TEXTSIZE_PROG);
354 /* region rendering limits */
355 vout_OSDRegionConstrain(*last_ptr, width, 0);
356 if(*last_ptr)
357 last_ptr = &(*last_ptr)->p_next;
360 /* NEXT EVENT */
361 if(p_next)
363 *last_ptr = vout_OSDEpgEvent(p_next,
365 y + height * OSDEPG_ROW(5),
366 height * EPGOSD_TEXTSIZE_PROG);
367 /* region rendering limits */
368 vout_OSDRegionConstrain(*last_ptr, width, 0);
369 if(*last_ptr)
370 last_ptr = &(*last_ptr)->p_next;
373 if(p_sys->time)
375 f_progress = (p_sys->time - p_sys->epg->p_current->i_start) /
376 (float)p_sys->epg->p_current->i_duration;
379 /* Display the current program time slider. */
380 *last_ptr = vout_OSDEpgSlider(x + width * 0.05,
381 y + height * OSDEPG_ROW(9),
382 width * 0.90,
383 height * OSDEPG_ROWS(1),
384 f_progress);
385 if (*last_ptr)
386 last_ptr = &(*last_ptr)->p_next;
388 *last_ptr = vout_OSDEpgText(psz_network,
390 y + height * OSDEPG_ROW(0),
391 height * EPGOSD_TEXTSIZE_NTWK,
392 RGB_COLOR1);
393 if(*last_ptr)
395 (*last_ptr)->i_align = SUBPICTURE_ALIGN_TOP|SUBPICTURE_ALIGN_RIGHT;
396 last_ptr = &(*last_ptr)->p_next;
399 free(psz_network);
402 static subpicture_region_t * vout_BuildOSDEpg(subpicture_updater_sys_t *p_sys,
403 int x, int y,
404 int visible_width,
405 int visible_height)
407 subpicture_region_t *head;
408 subpicture_region_t **last_ptr = &head;
410 const int i_padding = visible_height * (OSDEPG_HEIGHT * OSDEPG_PADDING);
412 *last_ptr = vout_OSDBackground(x + visible_width * OSDEPG_LEFT,
413 y + visible_height * OSDEPG_TOP,
414 visible_width * OSDEPG_WIDTH,
415 visible_height * OSDEPG_HEIGHT,
416 ARGB_BGCOLOR);
417 if(*last_ptr)
418 last_ptr = &(*last_ptr)->p_next;
420 struct
422 int x;
423 int y;
424 int w;
425 int h;
426 int rx;
427 int ry;
428 } panel = {
429 x + visible_width * OSDEPG_LEFT + i_padding,
430 y + visible_height * OSDEPG_TOP + i_padding,
431 visible_width * OSDEPG_WIDTH - 2 * i_padding,
432 visible_height * OSDEPG_HEIGHT - 2 * i_padding,
433 visible_width * OSDEPG_LEFT + i_padding,
434 visible_height * (1.0 - OSDEPG_TOP - OSDEPG_HEIGHT) + i_padding,
438 if( p_sys->art )
440 struct
442 int x;
443 int y;
444 int w;
445 int h;
446 } logo = {
447 panel.x,
448 panel.y,
449 panel.h,
450 panel.h,
453 *last_ptr = vout_OSDBackground(logo.x,
454 logo.y,
455 logo.w,
456 logo.h,
457 0xFF000000 | RGB_COLOR1);
458 if(*last_ptr)
459 last_ptr = &(*last_ptr)->p_next;
461 int logo_padding = visible_height * (OSDEPG_LOGO_SIZE * OSDEPG_PADDING);
462 *last_ptr = vout_OSDImage( p_sys->obj,
463 logo.x + logo_padding,
464 logo.y + logo_padding,
465 logo.w - 2 * logo_padding,
466 logo.h - 2 * logo_padding,
467 p_sys->art );
468 if(*last_ptr)
469 last_ptr = &(*last_ptr)->p_next;
471 /* shrink */
472 panel.x += logo.w + i_padding;
473 panel.w -= logo.w + i_padding;
476 vout_FillRightPanel( p_sys,
477 panel.x,
478 panel.y,
479 panel.w,
480 panel.h,
481 panel.rx,
482 panel.ry,
483 last_ptr );
485 return head;
488 static int OSDEpgValidate(subpicture_t *subpic,
489 bool has_src_changed, const video_format_t *fmt_src,
490 bool has_dst_changed, const video_format_t *fmt_dst,
491 mtime_t ts)
493 VLC_UNUSED(subpic); VLC_UNUSED(ts);
494 VLC_UNUSED(fmt_src); VLC_UNUSED(has_src_changed);
495 VLC_UNUSED(fmt_dst);
497 if (!has_dst_changed)
498 return VLC_SUCCESS;
499 return VLC_EGENERIC;
502 static void OSDEpgUpdate(subpicture_t *subpic,
503 const video_format_t *fmt_src,
504 const video_format_t *fmt_dst,
505 mtime_t ts)
507 subpicture_updater_sys_t *sys = subpic->updater.p_sys;
508 VLC_UNUSED(fmt_src); VLC_UNUSED(ts);
510 video_format_t fmt = *fmt_dst;
511 fmt.i_width = fmt.i_width * fmt.i_sar_num / fmt.i_sar_den;
512 fmt.i_visible_width = fmt.i_visible_width * fmt.i_sar_num / fmt.i_sar_den;
513 fmt.i_x_offset = fmt.i_x_offset * fmt.i_sar_num / fmt.i_sar_den;
515 subpic->i_original_picture_width = fmt.i_visible_width;
516 subpic->i_original_picture_height = fmt.i_visible_height;
518 subpic->p_region = vout_BuildOSDEpg(sys,
519 fmt.i_x_offset,
520 fmt.i_y_offset,
521 fmt.i_visible_width,
522 fmt.i_visible_height);
525 static void OSDEpgDestroy(subpicture_t *subpic)
527 subpicture_updater_sys_t *sys = subpic->updater.p_sys;
528 if( sys->epg )
529 vlc_epg_Delete(sys->epg);
530 free( sys->art );
531 free(sys);
535 * \brief Show EPG information about the current program of an input item
536 * \param vout pointer to the vout the information is to be showed on
537 * \param p_input pointer to the input item the information is to be showed
538 * \param i_action osd_epg_action_e action
540 int vout_OSDEpg(vout_thread_t *vout, input_item_t *input )
542 vlc_epg_t *epg = NULL;
543 int64_t epg_time;
545 /* Look for the current program EPG event */
546 vlc_mutex_lock(&input->lock);
548 const vlc_epg_t *tmp = input->p_epg_table;
549 if ( tmp )
551 /* Pick table designated event, or first/next one */
552 const vlc_epg_event_t *p_current_event = tmp->p_current;
553 epg = vlc_epg_New(tmp->i_id, tmp->i_source_id);
554 if(epg)
556 if( p_current_event )
558 vlc_epg_event_t *p_event = vlc_epg_event_Duplicate(p_current_event);
559 if(p_event)
561 if(!vlc_epg_AddEvent(epg, p_event))
562 vlc_epg_event_Delete(p_event);
563 else
564 vlc_epg_SetCurrent(epg, p_event->i_start);
568 /* Add next event if any */
569 vlc_epg_event_t *p_next = NULL;
570 for(size_t i=0; i<tmp->i_event; i++)
572 vlc_epg_event_t *p_evt = tmp->pp_event[i];
573 if((!p_next || p_next->i_start > p_evt->i_start) &&
574 (!p_current_event || (p_evt->i_id != p_current_event->i_id &&
575 p_evt->i_start >= p_current_event->i_start +
576 p_current_event->i_duration )))
578 p_next = tmp->pp_event[i];
581 if( p_next )
583 vlc_epg_event_t *p_event = vlc_epg_event_Duplicate(p_next);
584 if(!vlc_epg_AddEvent(epg, p_event))
585 vlc_epg_event_Delete(p_event);
588 if(epg->i_event > 0)
590 epg->psz_name = strdup(tmp->psz_name);
592 else
594 vlc_epg_Delete(epg);
595 epg = NULL;
599 epg_time = input->i_epg_time;
600 vlc_mutex_unlock(&input->lock);
602 /* If no EPG event has been found. */
603 if (epg == NULL)
604 return VLC_EGENERIC;
606 if(epg->psz_name == NULL) /* Fallback (title == channel name) */
607 epg->psz_name = input_item_GetMeta( input, vlc_meta_Title );
609 subpicture_updater_sys_t *sys = malloc(sizeof(*sys));
610 if (!sys) {
611 vlc_epg_Delete(epg);
612 return VLC_EGENERIC;
614 sys->epg = epg;
615 sys->obj = VLC_OBJECT(vout);
616 sys->time = epg_time;
617 sys->art = input_item_GetMeta( input, vlc_meta_ArtworkURL );
618 if( !sys->art )
619 sys->art = GetDefaultArtUri();
621 subpicture_updater_t updater = {
622 .pf_validate = OSDEpgValidate,
623 .pf_update = OSDEpgUpdate,
624 .pf_destroy = OSDEpgDestroy,
625 .p_sys = sys
628 const mtime_t now = mdate();
629 subpicture_t *subpic = subpicture_New(&updater);
630 if (!subpic) {
631 vlc_epg_Delete(sys->epg);
632 free(sys);
633 return VLC_EGENERIC;
636 subpic->i_channel = VOUT_SPU_CHANNEL_OSD;
637 subpic->i_start = now;
638 subpic->i_stop = now + 3000 * INT64_C(1000);
639 subpic->b_ephemer = true;
640 subpic->b_absolute = false;
641 subpic->b_fade = true;
642 subpic->b_subtitle = false;
644 vout_PutSubpicture(vout, subpic);
646 return VLC_SUCCESS;