/*****************************************************************************
* video_epg.c : EPG manipulation functions
*****************************************************************************
* Copyright (C) 2010 Adrien Maglo
* 2017 VLC authors, VideoLAN and VideoLabs
*
* Author: Adrien Maglo <magsoft@videolan.org>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
*****************************************************************************/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include <time.h>
#include <vlc_common.h>
#include <vlc_configuration.h>
#include <vlc_vout.h>
#include <vlc_vout_osd.h>
#include <vlc_input_item.h>
#include <vlc_epg.h>
#include <vlc_url.h>
#include "vout_spuregion_helper.h"
/* Layout percentage defines */
#define OSDEPG_MARGIN 0.025
#define OSDEPG_MARGINS (OSDEPG_MARGIN * 2)
#define OSDEPG_PADDING 0.05 /* inner margins */
#define OSDEPG_WIDTH (1.0 - OSDEPG_MARGINS)
#define OSDEPG_HEIGHT 0.25
#define OSDEPG_LEFT OSDEPG_MARGIN
#define OSDEPG_TOP (1.0 - OSDEPG_MARGINS - OSDEPG_HEIGHT + OSDEPG_MARGIN)
/* layout */
#define OSDEPG_ROWS_COUNT 10
#define OSDEPG_ROW_HEIGHT (1.0 / OSDEPG_ROWS_COUNT)
#define OSDEPG_LOGO_SIZE (OSDEPG_HEIGHT)
/* shortcuts */
#define OSDEPG_RIGHT (1.0 - OSDEPG_MARGIN)
#define OSDEPG_ROWS(x) (OSDEPG_ROW_HEIGHT * x)
#define OSDEPG_ROW(x) (OSDEPG_ROWS(x))
#define EPGOSD_TEXTSIZE_NAME (OSDEPG_ROWS(2))
#define EPGOSD_TEXTSIZE_PROG (OSDEPG_ROWS(2))
#define EPGOSD_TEXTSIZE_NTWK (OSDEPG_ROWS(2))
#define RGB_COLOR1 0xf48b00
#define ARGB_BGCOLOR 0xC0333333
//#define RGB_COLOR1 0x2badde
//#define ARGB_BGCOLOR 0xc003182d
typedef struct
{
vlc_epg_t *epg;
int64_t time;
char *art;
vlc_object_t *obj;
} epg_spu_updater_sys_t;
static char * GetDefaultArtUri( void )
{
char *psz_uri = NULL;
char *psz_path = config_GetSysPath(VLC_SYSDATA_DIR, "icons/hicolor/"
"128x128/"PACKAGE_NAME".png");
if( psz_path != NULL )
{
psz_uri = vlc_path2uri( psz_path, NULL );
free( psz_path );
}
return psz_uri;
}
#define GRADIENT_COLORS 40
static subpicture_region_t * vout_OSDBackground(int x, int y,
int width, int height,
uint32_t i_argb)
{
/* Create a new subpicture region */
video_palette_t palette;
spuregion_CreateVGradientPalette( &palette, GRADIENT_COLORS, i_argb, 0xFF000000 );
video_format_t fmt;
video_format_Init(&fmt, VLC_CODEC_YUVP);
fmt.i_width = fmt.i_visible_width = width;
fmt.i_height = fmt.i_visible_height = height;
fmt.i_sar_num = 1;
fmt.i_sar_den = 1;
fmt.p_palette = &palette;
subpicture_region_t *region = subpicture_region_New(&fmt);
if (!region)
return NULL;
region->b_absolute = false; region->b_in_window = false;
region->i_align = SUBPICTURE_ALIGN_LEFT | SUBPICTURE_ALIGN_TOP;
region->i_x = x;
region->i_y = y;
spuregion_CreateVGradientFill( region->p_picture->p, palette.i_entries );
return region;
}
static subpicture_region_t * vout_OSDEpgSlider(int x, int y,
int width, int height,
float ratio)
{
/* Create a new subpicture region */
video_palette_t palette = {
.i_entries = 4,
.palette = {
[0] = { HEX2YUV(RGB_COLOR1), 0x20 }, /* Bar fill remain/background */
[1] = { HEX2YUV(0x00ff00), 0xff },
[2] = { HEX2YUV(RGB_COLOR1), 0xC0 }, /* Bar fill */
[3] = { HEX2YUV(0xffffff), 0xff }, /* Bar outline */
},
};
video_format_t fmt;
video_format_Init(&fmt, VLC_CODEC_YUVP);
fmt.i_width = fmt.i_visible_width = width;
fmt.i_height = fmt.i_visible_height = height;
fmt.i_sar_num = 1;
fmt.i_sar_den = 1;
fmt.p_palette = &palette;
subpicture_region_t *region = subpicture_region_New(&fmt);
if (!region)
return NULL;
region->b_absolute = false; region->b_in_window = false;
region->i_align = SUBPICTURE_ALIGN_LEFT | SUBPICTURE_ALIGN_TOP;
region->i_x = x;
region->i_y = y;
picture_t *picture = region->p_picture;
ratio = VLC_CLIP(ratio, 0, 1);
int filled_part_width = ratio * width;
for (int j = 0; j < height; j++) {
for (int i = 0; i < width; ) {
/* Slider border. */
bool is_outline = j == 0 || j == height - 1 ||
i == 0 || i == width - 1;
/* We can see the video through the part of the slider
which corresponds to the leaving time. */
bool is_border = j < 3 || j > height - 4 ||
i < 3 || i > width - 4 ||
i < filled_part_width;
uint8_t color = 2 * is_border + is_outline;
if(i >= 3 && i < width - 4)
{
if(filled_part_width > 4)
memset(&picture->p->p_pixels[picture->p->i_pitch * j + i],
color, filled_part_width - 4);
if(width > filled_part_width + 4)
memset(&picture->p->p_pixels[picture->p->i_pitch * j + filled_part_width],
color, width - filled_part_width - 4);
i = __MAX(i+1, filled_part_width - 1);
}
else
{
picture->p->p_pixels[picture->p->i_pitch * j + i] = color;
i++;
}
}
}
return region;
}
static void vout_OSDSegmentSetNoWrap(text_segment_t *p_segment)
{
for( ; p_segment; p_segment = p_segment->p_next )
{
p_segment->style->e_wrapinfo = STYLE_WRAP_NONE;
p_segment->style->i_features |= STYLE_HAS_WRAP_INFO;
}
}
static text_segment_t * vout_OSDSegment(const char *psz_text, int size, uint32_t color)
{
text_segment_t *p_segment = text_segment_New(psz_text);
if(unlikely(!p_segment))
return NULL;
/* Set text style */
p_segment->style = text_style_Create(STYLE_NO_DEFAULTS);
if (unlikely(!p_segment->style))
{
text_segment_Delete(p_segment);
return NULL;
}
p_segment->style->i_font_size = __MAX(size ,1 );
p_segment->style->i_font_color = color;
p_segment->style->i_font_alpha = STYLE_ALPHA_OPAQUE;
p_segment->style->i_outline_alpha = STYLE_ALPHA_TRANSPARENT;
p_segment->style->i_shadow_alpha = STYLE_ALPHA_TRANSPARENT;
p_segment->style->i_features |= STYLE_HAS_FONT_ALPHA | STYLE_HAS_FONT_COLOR |
STYLE_HAS_OUTLINE_ALPHA | STYLE_HAS_SHADOW_ALPHA;
return p_segment;
}
static subpicture_region_t * vout_OSDImage( vlc_object_t *p_obj,
int x, int y, int w, int h,
const char *psz_uri )
{
video_format_t fmt_out;
video_format_Init( &fmt_out, VLC_CODEC_YUVA );
fmt_out.i_width = fmt_out.i_visible_width = w;
fmt_out.i_height = fmt_out.i_visible_height = h;
subpicture_region_t *image =
spuregion_CreateFromPicture( p_obj, &fmt_out, psz_uri );
if( image )
{
image->b_absolute = false; image->b_in_window = false;
image->i_x = x;
image->i_y = y;
image->i_align = SUBPICTURE_ALIGN_LEFT|SUBPICTURE_ALIGN_TOP;
}
return image;
}
static void vout_OSDRegionConstrain(subpicture_region_t *p_region, int w, int h)
{
p_region->i_max_width = w;
p_region->i_max_height = h;
}
static subpicture_region_t * vout_OSDTextRegion(text_segment_t *p_segment,
int x, int y )
{
subpicture_region_t *region;
if (!p_segment)
return NULL;
/* Create a new subpicture region */
region = subpicture_region_NewText();
if (!region)
return NULL;
region->b_absolute = false; region->b_in_window = false;
region->fmt.i_sar_num = 1;
region->fmt.i_sar_den = 1;
region->p_text = p_segment;
region->i_align = SUBPICTURE_ALIGN_LEFT | SUBPICTURE_ALIGN_TOP;
region->text_flags |= SUBPICTURE_ALIGN_LEFT | SUBPICTURE_ALIGN_TOP;
region->text_flags |= VLC_SUBPIC_TEXT_FLAG_TEXT_NOT_BALANCED;
region->i_x = x;
region->i_y = y;
return region;
}
static subpicture_region_t * vout_OSDEpgText(const char *text,
int x, int y,
int size, uint32_t color)
{
return vout_OSDTextRegion(vout_OSDSegment(text, size, color), x, y);
}
static char * vout_OSDPrintTime(time_t t)
{
char *psz;
struct tm tms;
localtime_r(&t, &tms);
if(asprintf(&psz, "%2.2d:%2.2d", tms.tm_hour, tms.tm_min) < 0)
psz = NULL;
return psz;
}
static subpicture_region_t * vout_OSDEpgEvent(const vlc_epg_event_t *p_evt,
int x, int y, int size)
{
text_segment_t *p_segment = NULL;
char *psz_start = vout_OSDPrintTime(p_evt->i_start);
char *psz_end = vout_OSDPrintTime(p_evt->i_start + p_evt->i_duration);
char *psz_text;
if( -1 < asprintf(&psz_text, "%s-%s ", psz_start, psz_end))
{
p_segment = vout_OSDSegment(psz_text, size, RGB_COLOR1);
if( p_segment )
p_segment->p_next = vout_OSDSegment(p_evt->psz_name, size, 0xffffff);
vout_OSDSegmentSetNoWrap( p_segment );
}
free( psz_start );
free( psz_end );
if(!p_segment)
return NULL;
return vout_OSDTextRegion(p_segment, x, y);
}
static void vout_FillRightPanel(epg_spu_updater_sys_t *p_sys,
int x, int y,
int width, int height,
int rx, int ry,
vlc_spu_regions *regions)
{
float f_progress = 0;
VLC_UNUSED(ry);
subpicture_region_t *last;
/* Display the name of the channel. */
last = vout_OSDEpgText(p_sys->epg->psz_name,
x,
y,
height * EPGOSD_TEXTSIZE_NAME,
0x00ffffff);
if(last)
vlc_spu_regions_push(regions, last);
const vlc_epg_event_t *p_current = p_sys->epg->p_current;
vlc_epg_event_t *p_next = NULL;
if(!p_sys->epg->p_current && p_sys->epg->i_event)
p_current = p_sys->epg->pp_event[0];
for(size_t i=0; i<p_sys->epg->i_event; i++)
{
if( p_sys->epg->pp_event[i]->i_id != p_current->i_id )
{
p_next = p_sys->epg->pp_event[i];
break;
}
}
/* Display the name of the current program. */
if(p_current)
{
last = vout_OSDEpgEvent(p_current,
x,
y + height * OSDEPG_ROW(2),
height * EPGOSD_TEXTSIZE_PROG);
if(last)
{
/* region rendering limits */
vout_OSDRegionConstrain(last, width, 0);
vlc_spu_regions_push(regions, last);
}
}
/* NEXT EVENT */
if(p_next)
{
last = vout_OSDEpgEvent(p_next,
x,
y + height * OSDEPG_ROW(5),
height * EPGOSD_TEXTSIZE_PROG);
if(last)
{
/* region rendering limits */
vout_OSDRegionConstrain(last, width, 0);
vlc_spu_regions_push(regions, last);
}
}
if(p_sys->time && p_sys->epg->p_current)
{
f_progress = (p_sys->time - p_sys->epg->p_current->i_start) /
(float)p_sys->epg->p_current->i_duration;
}
/* Display the current program time slider. */
last = vout_OSDEpgSlider(x + width * 0.05,
y + height * OSDEPG_ROW(9),
width * 0.90,
height * OSDEPG_ROWS(1),
f_progress);
if (last)
vlc_spu_regions_push(regions, last);
/* Format the hours */
if(p_sys->time)
{
char *psz_network = vout_OSDPrintTime(p_sys->time);
if(psz_network)
{
last = vout_OSDEpgText(psz_network,
rx,
y + height * OSDEPG_ROW(0),
height * EPGOSD_TEXTSIZE_NTWK,
RGB_COLOR1);
free(psz_network);
if(last)
{
last->i_align = SUBPICTURE_ALIGN_TOP|SUBPICTURE_ALIGN_RIGHT;
vlc_spu_regions_push(regions, last);
}
}
}
}
static void vout_BuildOSDEpg(epg_spu_updater_sys_t *p_sys,
vlc_spu_regions *regions,
int x, int y,
int visible_width,
int visible_height)
{
subpicture_region_t *last;
const int i_padding = visible_height * (OSDEPG_HEIGHT * OSDEPG_PADDING);
last = vout_OSDBackground(x + visible_width * OSDEPG_LEFT,
y + visible_height * OSDEPG_TOP,
visible_width * OSDEPG_WIDTH,
visible_height * OSDEPG_HEIGHT,
ARGB_BGCOLOR);
if(last)
vlc_spu_regions_push(regions, last);
struct
{
int x;
int y;
int w;
int h;
int rx;
int ry;
} panel = {
x + visible_width * OSDEPG_LEFT + i_padding,
y + visible_height * OSDEPG_TOP + i_padding,
visible_width * OSDEPG_WIDTH - 2 * i_padding,
visible_height * OSDEPG_HEIGHT - 2 * i_padding,
visible_width * OSDEPG_LEFT + i_padding,
visible_height * (1.0 - OSDEPG_TOP - OSDEPG_HEIGHT) + i_padding,
};
if( p_sys->art )
{
struct
{
int x;
int y;
int w;
int h;
} logo = {
panel.x,
panel.y,
panel.h,
panel.h,
};
last = vout_OSDBackground(logo.x,
logo.y,
logo.w,
logo.h,
0xFF000000 | RGB_COLOR1);
if(last)
vlc_spu_regions_push(regions, last);
int logo_padding = visible_height * (OSDEPG_LOGO_SIZE * OSDEPG_PADDING);
last = vout_OSDImage( p_sys->obj,
logo.x + logo_padding,
logo.y + logo_padding,
logo.w - 2 * logo_padding,
logo.h - 2 * logo_padding,
p_sys->art );
if(last)
vlc_spu_regions_push(regions, last);
/* shrink */
panel.x += logo.w + i_padding;
panel.w -= logo.w + i_padding;
}
vout_FillRightPanel( p_sys,
panel.x,
panel.y,
panel.w,
panel.h,
panel.rx,
panel.ry,
regions );
}
static void OSDEpgUpdate(subpicture_t *subpic,
const struct vlc_spu_updater_configuration *cfg)
{
epg_spu_updater_sys_t *sys = subpic->updater.sys;
const video_format_t *fmt_dst = cfg->current.video_dst;
if (video_format_IsSimilar(cfg->previous.video_dst, fmt_dst))
return;
vlc_spu_regions_Clear( &subpic->regions );
unsigned i_x_offset = fmt_dst->i_x_offset * fmt_dst->i_sar_num / fmt_dst->i_sar_den;
subpic->i_original_picture_width = fmt_dst->i_visible_width * fmt_dst->i_sar_num / fmt_dst->i_sar_den;
subpic->i_original_picture_height = fmt_dst->i_visible_height;
vout_BuildOSDEpg(sys, &subpic->regions,
i_x_offset,
fmt_dst->i_y_offset,
subpic->i_original_picture_width,
subpic->i_original_picture_height);
}
static void OSDEpgDestroy(subpicture_t *subpic)
{
epg_spu_updater_sys_t *sys = subpic->updater.sys;
if( sys->epg )
vlc_epg_Delete(sys->epg);
free( sys->art );
free(sys);
}
int vout_OSDEpg(vout_thread_t *vout, input_item_t *input)
{
vlc_epg_t *epg = NULL;
int64_t epg_time;
/* Look for the current program EPG event */
vlc_mutex_lock(&input->lock);
const vlc_epg_t *tmp = input->p_epg_table;
if ( tmp )
{
/* Pick table designated event, or first/next one */
const vlc_epg_event_t *p_current_event = tmp->p_current;
epg = vlc_epg_New(tmp->i_id, tmp->i_source_id);
if(epg)
{
if( p_current_event )
{
vlc_epg_event_t *p_event = vlc_epg_event_Duplicate(p_current_event);
if(p_event)
{
if(!vlc_epg_AddEvent(epg, p_event))
vlc_epg_event_Delete(p_event);
else
vlc_epg_SetCurrent(epg, p_event->i_start);
}
}
/* Add next event if any */
vlc_epg_event_t *p_next = NULL;
for(size_t i=0; i<tmp->i_event; i++)
{
vlc_epg_event_t *p_evt = tmp->pp_event[i];
if((!p_next || p_next->i_start > p_evt->i_start) &&
(!p_current_event || (p_evt->i_id != p_current_event->i_id &&
p_evt->i_start >= p_current_event->i_start +
p_current_event->i_duration )))
{
p_next = tmp->pp_event[i];
}
}
if( p_next )
{
vlc_epg_event_t *p_event = vlc_epg_event_Duplicate(p_next);
if(!vlc_epg_AddEvent(epg, p_event))
vlc_epg_event_Delete(p_event);
}
if(epg->i_event > 0)
{
epg->psz_name = strdup(tmp->psz_name);
}
else
{
vlc_epg_Delete(epg);
epg = NULL;
}
}
}
epg_time = input->i_epg_time;
vlc_mutex_unlock(&input->lock);
/* If no EPG event has been found. */
if (epg == NULL)
return VLC_EGENERIC;
if(epg->psz_name == NULL) /* Fallback (title == channel name) */
epg->psz_name = input_item_GetMeta( input, vlc_meta_Title );
epg_spu_updater_sys_t *sys = malloc(sizeof(*sys));
if (!sys) {
vlc_epg_Delete(epg);
return VLC_EGENERIC;
}
sys->epg = epg;
sys->obj = VLC_OBJECT(vout);
sys->time = epg_time;
sys->art = input_item_GetMeta( input, vlc_meta_ArtworkURL );
if( !sys->art )
sys->art = GetDefaultArtUri();
static const struct vlc_spu_updater_ops spu_ops =
{
.update = OSDEpgUpdate,
.destroy = OSDEpgDestroy,
};
subpicture_updater_t updater = {
.sys = sys,
.ops = &spu_ops,
};
const vlc_tick_t now = vlc_tick_now();
subpicture_t *subpic = subpicture_New(&updater);
if (!subpic) {
vlc_epg_Delete(sys->epg);
free(sys);
return VLC_EGENERIC;
}
subpic->i_channel = VOUT_SPU_CHANNEL_OSD;
subpic->i_start = now;
subpic->i_stop = now + VLC_TICK_FROM_SEC(3);
subpic->b_ephemer = true;
subpic->b_fade = true;
subpic->b_subtitle = false;
vout_PutSubpicture(vout, subpic);
return VLC_SUCCESS;
}
¤ Dauer der Verarbeitung: 0.5 Sekunden
(vorverarbeitet am 2026-11-04)
¤
*© Formatika GbR, Deutschland
|
|