contrib: protobuf: Require version 3.1.0+
[vlc.git] / src / video_output / video_epg.c
blob834367d007c6dab80c373f6d19df43f7aee5b083
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 RGB_COLOR1 0xf48b00
64 //#define ARGB_BGCOLOR 0xC0333333
66 #define RGB_COLOR1 0x2badde
67 #define ARGB_BGCOLOR 0xc003182d
69 struct subpicture_updater_sys_t
71 vlc_epg_t *epg;
72 int64_t time;
73 char *art;
74 vlc_object_t *obj;
77 static char * GetDefaultArtUri( void )
79 char *psz_uri = NULL;
80 char *psz_path = config_GetSysPath(VLC_SYSDATA_DIR, "icons/hicolor/"
81 "128x128/"PACKAGE_NAME".png");
82 if( psz_path != NULL )
84 psz_uri = vlc_path2uri( psz_path, NULL );
85 free( psz_path );
87 return psz_uri;
90 #define GRADIENT_COLORS 40
92 static subpicture_region_t * vout_OSDBackground(int x, int y,
93 int width, int height,
94 uint32_t i_argb)
96 /* Create a new subpicture region */
97 video_palette_t palette;
98 spuregion_CreateVGradientPalette( &palette, GRADIENT_COLORS, i_argb, 0xFF000000 );
100 video_format_t fmt;
101 video_format_Init(&fmt, VLC_CODEC_YUVP);
102 fmt.i_width = fmt.i_visible_width = width;
103 fmt.i_height = fmt.i_visible_height = height;
104 fmt.i_sar_num = 1;
105 fmt.i_sar_den = 1;
106 fmt.p_palette = &palette;
108 subpicture_region_t *region = subpicture_region_New(&fmt);
109 if (!region)
110 return NULL;
112 region->i_align = SUBPICTURE_ALIGN_LEFT | SUBPICTURE_ALIGN_TOP;
113 region->i_x = x;
114 region->i_y = y;
116 spuregion_CreateVGradientFill( region->p_picture->p, palette.i_entries );
118 return region;
121 static subpicture_region_t * vout_OSDEpgSlider(int x, int y,
122 int width, int height,
123 float ratio)
125 /* Create a new subpicture region */
126 video_palette_t palette = {
127 .i_entries = 4,
128 .palette = {
129 [0] = { HEX2YUV(RGB_COLOR1), 0x20 }, /* Bar fill remain/background */
130 [1] = { HEX2YUV(0x00ff00), 0xff },
131 [2] = { HEX2YUV(RGB_COLOR1), 0xC0 }, /* Bar fill */
132 [3] = { HEX2YUV(0xffffff), 0xff }, /* Bar outline */
136 video_format_t fmt;
137 video_format_Init(&fmt, VLC_CODEC_YUVP);
138 fmt.i_width = fmt.i_visible_width = width;
139 fmt.i_height = fmt.i_visible_height = height;
140 fmt.i_sar_num = 1;
141 fmt.i_sar_den = 1;
142 fmt.p_palette = &palette;
144 subpicture_region_t *region = subpicture_region_New(&fmt);
145 if (!region)
146 return NULL;
148 region->i_align = SUBPICTURE_ALIGN_LEFT | SUBPICTURE_ALIGN_TOP;
149 region->i_x = x;
150 region->i_y = y;
152 picture_t *picture = region->p_picture;
154 ratio = VLC_CLIP(ratio, 0, 1);
155 int filled_part_width = ratio * width;
157 for (int j = 0; j < height; j++) {
158 for (int i = 0; i < width; ) {
159 /* Slider border. */
160 bool is_outline = j == 0 || j == height - 1 ||
161 i == 0 || i == width - 1;
162 /* We can see the video through the part of the slider
163 which corresponds to the leaving time. */
164 bool is_border = j < 3 || j > height - 4 ||
165 i < 3 || i > width - 4 ||
166 i < filled_part_width;
168 uint8_t color = 2 * is_border + is_outline;
169 if(i >= 3 && i < width - 4)
171 if(filled_part_width > 4)
172 memset(&picture->p->p_pixels[picture->p->i_pitch * j + i],
173 color, filled_part_width - 4);
174 if(width > filled_part_width + 4)
175 memset(&picture->p->p_pixels[picture->p->i_pitch * j + filled_part_width],
176 color, width - filled_part_width - 4);
177 i = __MAX(i+1, filled_part_width - 1);
179 else
181 picture->p->p_pixels[picture->p->i_pitch * j + i] = color;
182 i++;
187 return region;
190 static void vout_OSDSegmentSetNoWrap(text_segment_t *p_segment)
192 for( ; p_segment; p_segment = p_segment->p_next )
194 p_segment->style->e_wrapinfo = STYLE_WRAP_NONE;
195 p_segment->style->i_features |= STYLE_HAS_WRAP_INFO;
199 static text_segment_t * vout_OSDSegment(const char *psz_text, int size, uint32_t color)
201 text_segment_t *p_segment = text_segment_New(psz_text);
202 if(unlikely(!p_segment))
203 return NULL;
205 /* Set text style */
206 p_segment->style = text_style_Create(STYLE_NO_DEFAULTS);
207 if (unlikely(!p_segment->style))
209 text_segment_Delete(p_segment);
210 return NULL;
213 p_segment->style->i_font_size = __MAX(size ,1 );
214 p_segment->style->i_font_color = color;
215 p_segment->style->i_font_alpha = STYLE_ALPHA_OPAQUE;
216 p_segment->style->i_outline_alpha = STYLE_ALPHA_TRANSPARENT;
217 p_segment->style->i_shadow_alpha = STYLE_ALPHA_TRANSPARENT;
218 p_segment->style->i_features |= STYLE_HAS_FONT_ALPHA | STYLE_HAS_FONT_COLOR |
219 STYLE_HAS_OUTLINE_ALPHA | STYLE_HAS_SHADOW_ALPHA;
221 return p_segment;
224 static subpicture_region_t * vout_OSDImage( vlc_object_t *p_obj,
225 int x, int y, int w, int h,
226 const char *psz_uri )
228 video_format_t fmt_out;
229 video_format_Init( &fmt_out, VLC_CODEC_YUVA );
230 fmt_out.i_width = fmt_out.i_visible_width = w;
231 fmt_out.i_height = fmt_out.i_visible_height = h;
233 subpicture_region_t *image =
234 spuregion_CreateFromPicture( p_obj, &fmt_out, psz_uri );
235 if( image )
237 image->i_x = x;
238 image->i_y = y;
239 image->i_align = SUBPICTURE_ALIGN_LEFT|SUBPICTURE_ALIGN_TOP;
241 return image;
244 static void vout_OSDRegionConstrain(subpicture_region_t *p_region, int w, int h)
246 if( p_region )
248 p_region->i_max_width = w;
249 p_region->i_max_height = h;
253 static subpicture_region_t * vout_OSDTextRegion(text_segment_t *p_segment,
254 int x, int y )
256 video_format_t fmt;
257 subpicture_region_t *region;
259 if (!p_segment)
260 return NULL;
262 /* Create a new subpicture region */
263 video_format_Init(&fmt, VLC_CODEC_TEXT);
264 fmt.i_sar_num = 1;
265 fmt.i_sar_den = 1;
267 region = subpicture_region_New(&fmt);
268 if (!region)
269 return NULL;
271 region->p_text = p_segment;
272 region->i_align = SUBPICTURE_ALIGN_LEFT | SUBPICTURE_ALIGN_TOP;
273 region->i_text_align = SUBPICTURE_ALIGN_LEFT | SUBPICTURE_ALIGN_TOP;
274 region->i_x = x;
275 region->i_y = y;
276 region->b_balanced_text = false;
278 return region;
281 static subpicture_region_t * vout_OSDEpgText(const char *text,
282 int x, int y,
283 int size, uint32_t color)
285 return vout_OSDTextRegion(vout_OSDSegment(text, size, color), x, y);
288 static char * vout_OSDPrintTime(time_t t)
290 char *psz;
291 struct tm tms;
292 localtime_r(&t, &tms);
293 if(asprintf(&psz, "%2.2d:%2.2d", tms.tm_hour, tms.tm_min) < 0)
294 psz = NULL;
295 return psz;
298 static subpicture_region_t * vout_OSDEpgEvent(const vlc_epg_event_t *p_evt,
299 int x, int y, int size)
301 text_segment_t *p_segment = NULL;
302 char *psz_start = vout_OSDPrintTime(p_evt->i_start);
303 char *psz_end = vout_OSDPrintTime(p_evt->i_start + p_evt->i_duration);
304 char *psz_text;
305 if( -1 < asprintf(&psz_text, "%s-%s ", psz_start, psz_end))
307 p_segment = vout_OSDSegment(psz_text, size, RGB_COLOR1);
308 if( p_segment )
309 p_segment->p_next = vout_OSDSegment(p_evt->psz_name, size, 0xffffff);
310 vout_OSDSegmentSetNoWrap( p_segment );
312 free( psz_start );
313 free( psz_end );
314 if(!p_segment)
315 return NULL;
316 return vout_OSDTextRegion(p_segment, x, y);
319 static void vout_FillRightPanel(subpicture_updater_sys_t *p_sys,
320 int x, int y,
321 int width, int height,
322 int rx, int ry,
323 subpicture_region_t **last_ptr)
325 float f_progress = 0;
326 VLC_UNUSED(ry);
328 /* Display the name of the channel. */
329 *last_ptr = vout_OSDEpgText(p_sys->epg->psz_name,
332 height * EPGOSD_TEXTSIZE_NAME,
333 0x00ffffff);
334 if(*last_ptr)
335 last_ptr = &(*last_ptr)->p_next;
337 const vlc_epg_event_t *p_current = p_sys->epg->p_current;
338 vlc_epg_event_t *p_next = NULL;
339 if(!p_sys->epg->p_current && p_sys->epg->i_event)
340 p_current = p_sys->epg->pp_event[0];
342 for(size_t i=0; i<p_sys->epg->i_event; i++)
344 if( p_sys->epg->pp_event[i]->i_id != p_current->i_id )
346 p_next = p_sys->epg->pp_event[i];
347 break;
351 /* Display the name of the current program. */
352 if(p_current)
354 *last_ptr = vout_OSDEpgEvent(p_current,
356 y + height * OSDEPG_ROW(2),
357 height * EPGOSD_TEXTSIZE_PROG);
358 /* region rendering limits */
359 vout_OSDRegionConstrain(*last_ptr, width, 0);
360 if(*last_ptr)
361 last_ptr = &(*last_ptr)->p_next;
364 /* NEXT EVENT */
365 if(p_next)
367 *last_ptr = vout_OSDEpgEvent(p_next,
369 y + height * OSDEPG_ROW(5),
370 height * EPGOSD_TEXTSIZE_PROG);
371 /* region rendering limits */
372 vout_OSDRegionConstrain(*last_ptr, width, 0);
373 if(*last_ptr)
374 last_ptr = &(*last_ptr)->p_next;
377 if(p_sys->time)
379 f_progress = (p_sys->time - p_sys->epg->p_current->i_start) /
380 (float)p_sys->epg->p_current->i_duration;
383 /* Display the current program time slider. */
384 *last_ptr = vout_OSDEpgSlider(x + width * 0.05,
385 y + height * OSDEPG_ROW(9),
386 width * 0.90,
387 height * OSDEPG_ROWS(1),
388 f_progress);
389 if (*last_ptr)
390 last_ptr = &(*last_ptr)->p_next;
392 /* Format the hours */
393 if(p_sys->time)
395 char *psz_network = vout_OSDPrintTime(p_sys->time);
396 if(psz_network)
398 *last_ptr = vout_OSDEpgText(psz_network,
400 y + height * OSDEPG_ROW(0),
401 height * EPGOSD_TEXTSIZE_NTWK,
402 RGB_COLOR1);
403 free(psz_network);
404 if(*last_ptr)
406 (*last_ptr)->i_align = SUBPICTURE_ALIGN_TOP|SUBPICTURE_ALIGN_RIGHT;
407 last_ptr = &(*last_ptr)->p_next;
413 static subpicture_region_t * vout_BuildOSDEpg(subpicture_updater_sys_t *p_sys,
414 int x, int y,
415 int visible_width,
416 int visible_height)
418 subpicture_region_t *head;
419 subpicture_region_t **last_ptr = &head;
421 const int i_padding = visible_height * (OSDEPG_HEIGHT * OSDEPG_PADDING);
423 *last_ptr = vout_OSDBackground(x + visible_width * OSDEPG_LEFT,
424 y + visible_height * OSDEPG_TOP,
425 visible_width * OSDEPG_WIDTH,
426 visible_height * OSDEPG_HEIGHT,
427 ARGB_BGCOLOR);
428 if(*last_ptr)
429 last_ptr = &(*last_ptr)->p_next;
431 struct
433 int x;
434 int y;
435 int w;
436 int h;
437 int rx;
438 int ry;
439 } panel = {
440 x + visible_width * OSDEPG_LEFT + i_padding,
441 y + visible_height * OSDEPG_TOP + i_padding,
442 visible_width * OSDEPG_WIDTH - 2 * i_padding,
443 visible_height * OSDEPG_HEIGHT - 2 * i_padding,
444 visible_width * OSDEPG_LEFT + i_padding,
445 visible_height * (1.0 - OSDEPG_TOP - OSDEPG_HEIGHT) + i_padding,
449 if( p_sys->art )
451 struct
453 int x;
454 int y;
455 int w;
456 int h;
457 } logo = {
458 panel.x,
459 panel.y,
460 panel.h,
461 panel.h,
464 *last_ptr = vout_OSDBackground(logo.x,
465 logo.y,
466 logo.w,
467 logo.h,
468 0xFF000000 | RGB_COLOR1);
469 if(*last_ptr)
470 last_ptr = &(*last_ptr)->p_next;
472 int logo_padding = visible_height * (OSDEPG_LOGO_SIZE * OSDEPG_PADDING);
473 *last_ptr = vout_OSDImage( p_sys->obj,
474 logo.x + logo_padding,
475 logo.y + logo_padding,
476 logo.w - 2 * logo_padding,
477 logo.h - 2 * logo_padding,
478 p_sys->art );
479 if(*last_ptr)
480 last_ptr = &(*last_ptr)->p_next;
482 /* shrink */
483 panel.x += logo.w + i_padding;
484 panel.w -= logo.w + i_padding;
487 vout_FillRightPanel( p_sys,
488 panel.x,
489 panel.y,
490 panel.w,
491 panel.h,
492 panel.rx,
493 panel.ry,
494 last_ptr );
496 return head;
499 static int OSDEpgValidate(subpicture_t *subpic,
500 bool has_src_changed, const video_format_t *fmt_src,
501 bool has_dst_changed, const video_format_t *fmt_dst,
502 mtime_t ts)
504 VLC_UNUSED(subpic); VLC_UNUSED(ts);
505 VLC_UNUSED(fmt_src); VLC_UNUSED(has_src_changed);
506 VLC_UNUSED(fmt_dst);
508 if (!has_dst_changed)
509 return VLC_SUCCESS;
510 return VLC_EGENERIC;
513 static void OSDEpgUpdate(subpicture_t *subpic,
514 const video_format_t *fmt_src,
515 const video_format_t *fmt_dst,
516 mtime_t ts)
518 subpicture_updater_sys_t *sys = subpic->updater.p_sys;
519 VLC_UNUSED(fmt_src); VLC_UNUSED(ts);
521 video_format_t fmt = *fmt_dst;
522 fmt.i_width = fmt.i_width * fmt.i_sar_num / fmt.i_sar_den;
523 fmt.i_visible_width = fmt.i_visible_width * fmt.i_sar_num / fmt.i_sar_den;
524 fmt.i_x_offset = fmt.i_x_offset * fmt.i_sar_num / fmt.i_sar_den;
526 subpic->i_original_picture_width = fmt.i_visible_width;
527 subpic->i_original_picture_height = fmt.i_visible_height;
529 subpic->p_region = vout_BuildOSDEpg(sys,
530 fmt.i_x_offset,
531 fmt.i_y_offset,
532 fmt.i_visible_width,
533 fmt.i_visible_height);
536 static void OSDEpgDestroy(subpicture_t *subpic)
538 subpicture_updater_sys_t *sys = subpic->updater.p_sys;
539 if( sys->epg )
540 vlc_epg_Delete(sys->epg);
541 free( sys->art );
542 free(sys);
546 * \brief Show EPG information about the current program of an input item
547 * \param vout pointer to the vout the information is to be showed on
548 * \param p_input pointer to the input item the information is to be showed
549 * \param i_action osd_epg_action_e action
551 int vout_OSDEpg(vout_thread_t *vout, input_item_t *input )
553 vlc_epg_t *epg = NULL;
554 int64_t epg_time;
556 /* Look for the current program EPG event */
557 vlc_mutex_lock(&input->lock);
559 const vlc_epg_t *tmp = input->p_epg_table;
560 if ( tmp )
562 /* Pick table designated event, or first/next one */
563 const vlc_epg_event_t *p_current_event = tmp->p_current;
564 epg = vlc_epg_New(tmp->i_id, tmp->i_source_id);
565 if(epg)
567 if( p_current_event )
569 vlc_epg_event_t *p_event = vlc_epg_event_Duplicate(p_current_event);
570 if(p_event)
572 if(!vlc_epg_AddEvent(epg, p_event))
573 vlc_epg_event_Delete(p_event);
574 else
575 vlc_epg_SetCurrent(epg, p_event->i_start);
579 /* Add next event if any */
580 vlc_epg_event_t *p_next = NULL;
581 for(size_t i=0; i<tmp->i_event; i++)
583 vlc_epg_event_t *p_evt = tmp->pp_event[i];
584 if((!p_next || p_next->i_start > p_evt->i_start) &&
585 (!p_current_event || (p_evt->i_id != p_current_event->i_id &&
586 p_evt->i_start >= p_current_event->i_start +
587 p_current_event->i_duration )))
589 p_next = tmp->pp_event[i];
592 if( p_next )
594 vlc_epg_event_t *p_event = vlc_epg_event_Duplicate(p_next);
595 if(!vlc_epg_AddEvent(epg, p_event))
596 vlc_epg_event_Delete(p_event);
599 if(epg->i_event > 0)
601 epg->psz_name = strdup(tmp->psz_name);
603 else
605 vlc_epg_Delete(epg);
606 epg = NULL;
610 epg_time = input->i_epg_time;
611 vlc_mutex_unlock(&input->lock);
613 /* If no EPG event has been found. */
614 if (epg == NULL)
615 return VLC_EGENERIC;
617 if(epg->psz_name == NULL) /* Fallback (title == channel name) */
618 epg->psz_name = input_item_GetMeta( input, vlc_meta_Title );
620 subpicture_updater_sys_t *sys = malloc(sizeof(*sys));
621 if (!sys) {
622 vlc_epg_Delete(epg);
623 return VLC_EGENERIC;
625 sys->epg = epg;
626 sys->obj = VLC_OBJECT(vout);
627 sys->time = epg_time;
628 sys->art = input_item_GetMeta( input, vlc_meta_ArtworkURL );
629 if( !sys->art )
630 sys->art = GetDefaultArtUri();
632 subpicture_updater_t updater = {
633 .pf_validate = OSDEpgValidate,
634 .pf_update = OSDEpgUpdate,
635 .pf_destroy = OSDEpgDestroy,
636 .p_sys = sys
639 const mtime_t now = mdate();
640 subpicture_t *subpic = subpicture_New(&updater);
641 if (!subpic) {
642 vlc_epg_Delete(sys->epg);
643 free(sys);
644 return VLC_EGENERIC;
647 subpic->i_channel = VOUT_SPU_CHANNEL_OSD;
648 subpic->i_start = now;
649 subpic->i_stop = now + 3000 * INT64_C(1000);
650 subpic->b_ephemer = true;
651 subpic->b_absolute = false;
652 subpic->b_fade = true;
653 subpic->b_subtitle = false;
655 vout_PutSubpicture(vout, subpic);
657 return VLC_SUCCESS;