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 *****************************************************************************/
30 #include <vlc_common.h>
32 #include <vlc_vout_osd.h>
33 #include <vlc_events.h>
34 #include <vlc_input_item.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)
49 #define OSDEPG_ROWS_COUNT 10
50 #define OSDEPG_ROW_HEIGHT (1.0 / OSDEPG_ROWS_COUNT)
51 #define OSDEPG_LOGO_SIZE (OSDEPG_HEIGHT)
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
85 static char * GetDefaultArtUri( void )
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
);
99 #define GRADIENT_COLORS 40
101 static subpicture_region_t
* vout_OSDBackground(int x
, int y
,
102 int width
, int height
,
105 /* Create a new subpicture region */
106 video_palette_t palette
;
107 spuregion_CreateVGradientPalette( &palette
, GRADIENT_COLORS
, i_argb
, 0xFF000000 );
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
;
115 fmt
.p_palette
= &palette
;
117 subpicture_region_t
*region
= subpicture_region_New(&fmt
);
121 region
->i_align
= SUBPICTURE_ALIGN_LEFT
| SUBPICTURE_ALIGN_TOP
;
125 spuregion_CreateVGradientFill( region
->p_picture
->p
, palette
.i_entries
);
130 static subpicture_region_t
* vout_OSDEpgSlider(int x
, int y
,
131 int width
, int height
,
134 /* Create a new subpicture region */
135 video_palette_t 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 */
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
;
151 fmt
.p_palette
= &palette
;
153 subpicture_region_t
*region
= subpicture_region_New(&fmt
);
157 region
->i_align
= SUBPICTURE_ALIGN_LEFT
| SUBPICTURE_ALIGN_TOP
;
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
++) {
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
;
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
))
200 p_segment
->style
= text_style_Create(STYLE_NO_DEFAULTS
);
201 if (unlikely(!p_segment
->style
))
203 text_segment_Delete(p_segment
);
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
;
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
);
233 image
->i_align
= SUBPICTURE_ALIGN_LEFT
|SUBPICTURE_ALIGN_TOP
;
238 static void vout_OSDRegionConstrain(subpicture_region_t
*p_region
, int w
, int h
)
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
,
251 subpicture_region_t
*region
;
256 /* Create a new subpicture region */
257 video_format_Init(&fmt
, VLC_CODEC_TEXT
);
261 region
= subpicture_region_New(&fmt
);
265 region
->p_text
= p_segment
;
266 region
->i_align
= SUBPICTURE_ALIGN_LEFT
| SUBPICTURE_ALIGN_TOP
;
269 region
->b_balanced_text
= false;
274 static subpicture_region_t
* vout_OSDEpgText(const char *text
,
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
)
285 localtime_r(&t
, &tms
);
286 if(asprintf(&psz
, "%2.2d:%2.2d", tms
.tm_hour
, tms
.tm_min
) < 0)
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
);
298 if( -1 < asprintf(&psz_text
, "%s-%s ", psz_start
, psz_end
))
300 p_segment
= vout_OSDSegment(psz_text
, size
, RGB_COLOR1
);
302 p_segment
->p_next
= vout_OSDSegment(p_evt
->psz_name
, size
, 0xffffff);
303 vout_OSDSegmentSetNoWrap( p_segment
);
309 return vout_OSDTextRegion(p_segment
, x
, y
);
312 static void vout_FillRightPanel(subpicture_updater_sys_t
*p_sys
,
314 int width
, int height
,
316 subpicture_region_t
**last_ptr
)
318 float f_progress
= 0;
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
,
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
];
347 /* Display the name of the current program. */
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);
357 last_ptr
= &(*last_ptr
)->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);
370 last_ptr
= &(*last_ptr
)->p_next
;
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),
383 height
* OSDEPG_ROWS(1),
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
,
395 (*last_ptr
)->i_align
= SUBPICTURE_ALIGN_TOP
|SUBPICTURE_ALIGN_RIGHT
;
396 last_ptr
= &(*last_ptr
)->p_next
;
402 static subpicture_region_t
* vout_BuildOSDEpg(subpicture_updater_sys_t
*p_sys
,
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
,
418 last_ptr
= &(*last_ptr
)->p_next
;
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
,
453 *last_ptr
= vout_OSDBackground(logo
.x
,
457 0xFF000000 | RGB_COLOR1
);
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
,
469 last_ptr
= &(*last_ptr
)->p_next
;
472 panel
.x
+= logo
.w
+ i_padding
;
473 panel
.w
-= logo
.w
+ i_padding
;
476 vout_FillRightPanel( p_sys
,
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
,
493 VLC_UNUSED(subpic
); VLC_UNUSED(ts
);
494 VLC_UNUSED(fmt_src
); VLC_UNUSED(has_src_changed
);
497 if (!has_dst_changed
)
502 static void OSDEpgUpdate(subpicture_t
*subpic
,
503 const video_format_t
*fmt_src
,
504 const video_format_t
*fmt_dst
,
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
,
522 fmt
.i_visible_height
);
525 static void OSDEpgDestroy(subpicture_t
*subpic
)
527 subpicture_updater_sys_t
*sys
= subpic
->updater
.p_sys
;
529 vlc_epg_Delete(sys
->epg
);
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
;
545 /* Look for the current program EPG event */
546 vlc_mutex_lock(&input
->lock
);
548 const vlc_epg_t
*tmp
= input
->p_epg_table
;
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
);
556 if( p_current_event
)
558 vlc_epg_event_t
*p_event
= vlc_epg_event_Duplicate(p_current_event
);
561 if(!vlc_epg_AddEvent(epg
, p_event
))
562 vlc_epg_event_Delete(p_event
);
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
];
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
);
590 epg
->psz_name
= strdup(tmp
->psz_name
);
599 epg_time
= input
->i_epg_time
;
600 vlc_mutex_unlock(&input
->lock
);
602 /* If no EPG event has been found. */
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
));
615 sys
->obj
= VLC_OBJECT(vout
);
616 sys
->time
= epg_time
;
617 sys
->art
= input_item_GetMeta( input
, vlc_meta_ArtworkURL
);
619 sys
->art
= GetDefaultArtUri();
621 subpicture_updater_t updater
= {
622 .pf_validate
= OSDEpgValidate
,
623 .pf_update
= OSDEpgUpdate
,
624 .pf_destroy
= OSDEpgDestroy
,
628 const mtime_t now
= mdate();
629 subpicture_t
*subpic
= subpicture_New(&updater
);
631 vlc_epg_Delete(sys
->epg
);
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
);