video_epg.c 9.96 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
/*****************************************************************************
 * video_epg.c : EPG manipulation functions
 *****************************************************************************
 * Copyright (C) 2010 Adrien Maglo
 *
 * 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 General Public License as published by
 * the Free Software Foundation; either version 2 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, 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 <vlc_common.h>
#include <vlc_vout.h>
29
#include <vlc_vout_osd.h>
30 31 32 33 34 35 36 37 38 39
#include <vlc_events.h>
#include <vlc_input_item.h>
#include <vlc_epg.h>

/* Layout percentage defines */
#define EPG_TOP 0.7
#define EPG_LEFT 0.1
#define EPG_NAME_SIZE 0.05
#define EPG_PROGRAM_SIZE 0.03

Laurent Aimar's avatar
Laurent Aimar committed
40 41 42
static subpicture_region_t * vout_OSDEpgSlider(int x, int y,
                                               int width, int height,
                                               float ratio)
43 44
{
    video_format_t fmt;
Laurent Aimar's avatar
Laurent Aimar committed
45
    subpicture_region_t *region;
46 47

    /* Create a new subpicture region */
Laurent Aimar's avatar
Laurent Aimar committed
48 49 50
    video_format_Init(&fmt, VLC_CODEC_YUVA);
    fmt.i_width  = fmt.i_visible_width  = width;
    fmt.i_height = fmt.i_visible_height = height;
Laurent Aimar's avatar
Laurent Aimar committed
51 52
    fmt.i_sar_num = 0;
    fmt.i_sar_den = 1;
53

Laurent Aimar's avatar
Laurent Aimar committed
54 55
    region = subpicture_region_New(&fmt);
    if (!region)
56 57
        return NULL;

Laurent Aimar's avatar
Laurent Aimar committed
58 59
    region->i_x = x;
    region->i_y = y;
60

Laurent Aimar's avatar
Laurent Aimar committed
61
    picture_t *picture = region->p_picture;
62

Laurent Aimar's avatar
Laurent Aimar committed
63 64
    ratio = __MIN(__MAX(ratio, 0), 1);
    int filled_part_width = ratio * width;
65

Laurent Aimar's avatar
Laurent Aimar committed
66 67 68 69
    for (int j = 0; j < height; j++) {
        for (int i = 0; i < width; i++) {
            #define WRITE_COMP(plane, value) \
                picture->p[plane].p_pixels[picture->p[plane].i_pitch * j + i] = value
70 71

            /* Draw the slider. */
Laurent Aimar's avatar
Laurent Aimar committed
72 73 74 75 76
            bool is_outline = j == 0 || j == height - 1 ||
                              i == 0 || i == width  - 1;
            WRITE_COMP(0, is_outline ? 0x00 : 0xff);
            WRITE_COMP(1, 0x80);
            WRITE_COMP(2, 0x80);
77 78 79

            /* We can see the video through the part of the slider
               which corresponds to the leaving time. */
Laurent Aimar's avatar
Laurent Aimar committed
80 81 82 83
            bool is_border = j < 3 || j > height - 4 ||
                             i < 3 || i > width  - 4 ||
                             i < filled_part_width;
            WRITE_COMP(3, is_border ? 0xff : 0x00);
84 85 86 87 88

            #undef WRITE_COMP
        }
    }

Laurent Aimar's avatar
Laurent Aimar committed
89
    return region;
90 91 92
}


Laurent Aimar's avatar
Laurent Aimar committed
93 94 95
static subpicture_region_t * vout_OSDEpgText(const char *text,
                                             int x, int y,
                                             int size, uint32_t color)
96 97
{
    video_format_t fmt;
Laurent Aimar's avatar
Laurent Aimar committed
98
    subpicture_region_t *region;
99

Laurent Aimar's avatar
Laurent Aimar committed
100
    if (!text)
101 102 103
        return NULL;

    /* Create a new subpicture region */
Laurent Aimar's avatar
Laurent Aimar committed
104
    video_format_Init(&fmt, VLC_CODEC_TEXT);
Laurent Aimar's avatar
Laurent Aimar committed
105 106
    fmt.i_sar_num = 0;
    fmt.i_sar_den = 1;
107

Laurent Aimar's avatar
Laurent Aimar committed
108 109
    region = subpicture_region_New(&fmt);
    if (!region)
110 111 112
        return NULL;

    /* Set subpicture parameters */
Laurent Aimar's avatar
Laurent Aimar committed
113 114 115 116
    region->psz_text = strdup(text);
    region->i_align  = 0;
    region->i_x      = x;
    region->i_y      = y;
117 118

    /* Set text style */
Laurent Aimar's avatar
Laurent Aimar committed
119 120 121 122 123
    region->p_style = text_style_New();
    if (region->p_style) {
        region->p_style->i_font_size  = size;
        region->p_style->i_font_color = color;
        region->p_style->i_font_alpha = 0;
124 125
    }

Laurent Aimar's avatar
Laurent Aimar committed
126
    return region;
127 128 129
}


Laurent Aimar's avatar
Laurent Aimar committed
130 131 132 133
static subpicture_region_t * vout_BuildOSDEpg(vlc_epg_t *epg,
                                              int x, int y,
                                              int visible_width,
                                              int visible_height)
134
{
Laurent Aimar's avatar
Laurent Aimar committed
135 136
    subpicture_region_t *head;
    subpicture_region_t **last_ptr = &head;
137

Laurent Aimar's avatar
Laurent Aimar committed
138
    time_t current_time = time(NULL);
139 140

    /* Display the name of the channel. */
Laurent Aimar's avatar
Laurent Aimar committed
141 142 143 144 145
    *last_ptr = vout_OSDEpgText(epg->psz_name,
                                x + visible_width  * EPG_LEFT,
                                y + visible_height * EPG_TOP,
                                visible_height * EPG_NAME_SIZE,
                                0x00ffffff);
146

Laurent Aimar's avatar
Laurent Aimar committed
147 148
    if (!*last_ptr)
        return head;
149 150

    /* Display the name of the current program. */
Laurent Aimar's avatar
Laurent Aimar committed
151 152 153 154 155 156
    last_ptr = &(*last_ptr)->p_next;
    *last_ptr = vout_OSDEpgText(epg->p_current->psz_name,
                                x + visible_width  * (EPG_LEFT + 0.025),
                                y + visible_height * (EPG_TOP + 0.05),
                                visible_height * EPG_PROGRAM_SIZE,
                                0x00ffffff);
157

Laurent Aimar's avatar
Laurent Aimar committed
158 159
    if (!*last_ptr)
        return head;
160 161

    /* Display the current program time slider. */
Laurent Aimar's avatar
Laurent Aimar committed
162 163 164 165 166 167 168
    last_ptr = &(*last_ptr)->p_next;
    *last_ptr = vout_OSDEpgSlider(x + visible_width  * EPG_LEFT,
                                  y + visible_height * (EPG_TOP + 0.1),
                                  visible_width  * (1 - 2 * EPG_LEFT),
                                  visible_height * 0.05,
                                  (current_time - epg->p_current->i_start)
                                  / (float)epg->p_current->i_duration);
169

Laurent Aimar's avatar
Laurent Aimar committed
170 171
    if (!*last_ptr)
        return head;
172 173 174

    /* Format the hours of the beginning and the end of the current program. */
    struct tm tm_start, tm_end;
Laurent Aimar's avatar
Laurent Aimar committed
175 176 177 178 179 180 181 182 183 184
    time_t t_start = epg->p_current->i_start;
    time_t t_end = epg->p_current->i_start + epg->p_current->i_duration;
    localtime_r(&t_start, &tm_start);
    localtime_r(&t_end, &tm_end);
    char text_start[128];
    char text_end[128];
    snprintf(text_start, sizeof(text_start), "%2.2d:%2.2d",
             tm_start.tm_hour, tm_start.tm_min);
    snprintf(text_end, sizeof(text_end), "%2.2d:%2.2d",
             tm_end.tm_hour, tm_end.tm_min);
185 186

    /* Display those hours. */
Laurent Aimar's avatar
Laurent Aimar committed
187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204
    last_ptr = &(*last_ptr)->p_next;
    *last_ptr = vout_OSDEpgText(text_start,
                                x + visible_width  * (EPG_LEFT + 0.02),
                                y + visible_height * (EPG_TOP + 0.15),
                                visible_height * EPG_PROGRAM_SIZE,
                                0x00ffffff);

    if (!*last_ptr)
        return head;

    last_ptr = &(*last_ptr)->p_next;
    *last_ptr = vout_OSDEpgText(text_end,
                                x + visible_width  * (1 - EPG_LEFT - 0.085),
                                y + visible_height * (EPG_TOP + 0.15),
                                visible_height * EPG_PROGRAM_SIZE,
                                0x00ffffff);

    return head;
205 206
}

207 208
struct subpicture_updater_sys_t
{
Laurent Aimar's avatar
Laurent Aimar committed
209
    vlc_epg_t *epg;
210 211
};

Laurent Aimar's avatar
Laurent Aimar committed
212 213 214 215
static int OSDEpgValidate(subpicture_t *subpic,
                          bool has_src_changed, const video_format_t *fmt_src,
                          bool has_dst_changed, const video_format_t *fmt_dst,
                          mtime_t ts)
216
{
Laurent Aimar's avatar
Laurent Aimar committed
217 218
    VLC_UNUSED(subpic); VLC_UNUSED(ts); VLC_UNUSED(fmt_src);
    VLC_UNUSED(has_dst_changed); VLC_UNUSED(fmt_dst);
219

Laurent Aimar's avatar
Laurent Aimar committed
220
    if (!has_src_changed && !has_dst_changed)
221 222 223 224
        return VLC_SUCCESS;
    return VLC_EGENERIC;
}

Laurent Aimar's avatar
Laurent Aimar committed
225 226 227 228
static void OSDEpgUpdate(subpicture_t *subpic,
                         const video_format_t *fmt_src,
                         const video_format_t *fmt_dst,
                         mtime_t ts)
229
{
Laurent Aimar's avatar
Laurent Aimar committed
230 231 232 233 234 235 236 237 238 239
    subpicture_updater_sys_t *sys = subpic->updater.p_sys;
    VLC_UNUSED(fmt_dst); VLC_UNUSED(ts);

    subpic->i_original_picture_width  = fmt_src->i_width;
    subpic->i_original_picture_height = fmt_src->i_height;
    subpic->p_region = vout_BuildOSDEpg(sys->epg,
                                        fmt_src->i_x_offset,
                                        fmt_src->i_y_offset,
                                        fmt_src->i_visible_width,
                                        fmt_src->i_visible_height);
240 241
}

Laurent Aimar's avatar
Laurent Aimar committed
242
static void OSDEpgDestroy(subpicture_t *subpic)
243
{
Laurent Aimar's avatar
Laurent Aimar committed
244
    subpicture_updater_sys_t *sys = subpic->updater.p_sys;
245

Laurent Aimar's avatar
Laurent Aimar committed
246 247
    vlc_epg_Delete(sys->epg);
    free(sys);
248
}
249 250 251

/**
 * \brief Show EPG information about the current program of an input item
Laurent Aimar's avatar
Laurent Aimar committed
252
 * \param vout pointer to the vout the information is to be showed on
253 254
 * \param p_input pointer to the input item the information is to be showed
 */
Laurent Aimar's avatar
Laurent Aimar committed
255
int vout_OSDEpg(vout_thread_t *vout, input_item_t *input)
256
{
Laurent Aimar's avatar
Laurent Aimar committed
257 258
    char *now_playing = input_item_GetNowPlaying(input);
    vlc_epg_t *epg = NULL;
259

Laurent Aimar's avatar
Laurent Aimar committed
260
    vlc_mutex_lock(&input->lock);
261 262

    /* Look for the current program EPG event */
Laurent Aimar's avatar
Laurent Aimar committed
263 264 265 266 267 268 269 270
    for (int i = 0; i < input->i_epg; i++) {
        vlc_epg_t *tmp = input->pp_epg[i];

        if (tmp->p_current &&
            tmp->p_current->psz_name && now_playing != NULL &&
            !strcmp(tmp->p_current->psz_name, now_playing)) {
            epg = vlc_epg_New(tmp->psz_name);
            vlc_epg_Merge(epg, tmp);
271 272 273 274
            break;
        }
    }

Laurent Aimar's avatar
Laurent Aimar committed
275
    vlc_mutex_unlock(&input->lock);
276

Adrien Maglo's avatar
Adrien Maglo committed
277
    /* If no EPG event has been found. */
Laurent Aimar's avatar
Laurent Aimar committed
278
    if (epg == NULL)
Adrien Maglo's avatar
Adrien Maglo committed
279 280
        return VLC_EGENERIC;

Laurent Aimar's avatar
Laurent Aimar committed
281 282 283
    subpicture_updater_sys_t *sys = malloc(sizeof(*sys));
    if (!sys) {
        vlc_epg_Delete(epg);
Adrien Maglo's avatar
Adrien Maglo committed
284
        return VLC_EGENERIC;
285
    }
Laurent Aimar's avatar
Laurent Aimar committed
286
    sys->epg = epg;
287 288 289 290
    subpicture_updater_t updater = {
        .pf_validate = OSDEpgValidate,
        .pf_update   = OSDEpgUpdate,
        .pf_destroy  = OSDEpgDestroy,
Laurent Aimar's avatar
Laurent Aimar committed
291
        .p_sys       = sys
292 293
    };

Laurent Aimar's avatar
Laurent Aimar committed
294 295 296 297 298
    const mtime_t now = mdate();
    subpicture_t *subpic = subpicture_New(&updater);
    if (!subpic) {
        vlc_epg_Delete(sys->epg);
        free(sys);
299 300
        return VLC_EGENERIC;
    }
301

Laurent Aimar's avatar
Laurent Aimar committed
302 303 304 305 306 307
    subpic->i_channel  = SPU_DEFAULT_CHANNEL;
    subpic->i_start    = now;
    subpic->i_stop     = now + 3000 * INT64_C(1000);
    subpic->b_ephemer  = true;
    subpic->b_absolute = true;
    subpic->b_fade     = true;
Adrien Maglo's avatar
Adrien Maglo committed
308

Laurent Aimar's avatar
Laurent Aimar committed
309
    vout_PutSubpicture(vout, subpic);
310 311 312

    return VLC_SUCCESS;
}