Add esfilter (elementary stream filter)
Some feeds (especially from satellite) includes many language mutations and other elementary streams. It may be useful to define the filters and order scheme for the streaming and DVR. The service information dialog is extended to show all PIDs and filtered PIDs to easy show the used PIDs. See the included help file for more description.
This commit is contained in:
parent
2b37a0610e
commit
47b97e52a8
23 changed files with 1888 additions and 80 deletions
4
Makefile
4
Makefile
|
@ -117,6 +117,7 @@ SRCS = src/version.c \
|
|||
src/rtsp.c \
|
||||
src/fsmonitor.c \
|
||||
src/cron.c \
|
||||
src/esfilter.c
|
||||
|
||||
SRCS-${CONFIG_UPNP} += \
|
||||
src/upnp.c
|
||||
|
@ -131,7 +132,8 @@ SRCS += \
|
|||
src/api/api_mpegts.c \
|
||||
src/api/api_epg.c \
|
||||
src/api/api_epggrab.c \
|
||||
src/api/api_imagecache.c
|
||||
src/api/api_imagecache.c \
|
||||
src/api/api_esfilter.c
|
||||
|
||||
SRCS += \
|
||||
src/parsers/parsers.c \
|
||||
|
|
109
docs/html/config_esfilter.html
Normal file
109
docs/html/config_esfilter.html
Normal file
|
@ -0,0 +1,109 @@
|
|||
<div class="hts-doc-text">
|
||||
|
||||
This table defines rules to filter and order the elementary streams
|
||||
like video or audio from the input feed.
|
||||
|
||||
<p>
|
||||
The execution order of commands is granted. It means that first rule
|
||||
is executed for all available streams then second and so on.
|
||||
|
||||
<p>
|
||||
If any elementary stream is not marked as ignored or exclusive, it is
|
||||
used. If you like to ignore unknown elementary streams, add a rule
|
||||
to the end of grid with the any (not defined) comparisons and
|
||||
with the action ignore.
|
||||
|
||||
<p>
|
||||
The rules for different elementary stream groups (video, audio,
|
||||
teletext, subtitle, CA, other) are executed separately (as visually edited).
|
||||
|
||||
<p>
|
||||
For the visual verification of the filtering, there is a service info
|
||||
dialog in the Configuration / DVB Inputs / Services window . This dialog
|
||||
shows the received PIDs and filtered PIDs in one window.
|
||||
|
||||
<p>
|
||||
The rules are listed / edited in a grid.
|
||||
|
||||
<ul>
|
||||
<li>To edit a cell, double click on it. After a cell is changed it
|
||||
will flags one of its corner to red to indicated that it has been
|
||||
changed. To commit these changes back to Tvheadend press the
|
||||
'Save changes' button. In order to change a Checkbox cell you only
|
||||
have to click once in it.
|
||||
|
||||
<li>To add a new entry, press the 'Add entry' button. The new (empty) entry
|
||||
will be created on the server but will not be in its enabled state.
|
||||
You can now change all the cells to the desired values, check the
|
||||
'enable' box and then press 'Save changes' to activate the new entry.
|
||||
|
||||
<li>To delete one or more entries, select the lines (by clicking once on
|
||||
them), and press the 'Delete selected' button. A pop up
|
||||
will ask you to confirm your request.
|
||||
|
||||
<li>To move up or down one or more entries, select the lines (by clicking
|
||||
once on them), and press the 'Move up' or 'Move down' button.
|
||||
</ul>
|
||||
|
||||
<p>
|
||||
The columns have the following functions:
|
||||
|
||||
<dl>
|
||||
<dt>Enabled
|
||||
<dd>If selected, the rule will be enabled.
|
||||
|
||||
<dt>Stream Type
|
||||
<dd>Select the elementary stream type to compare. Empty field means any.
|
||||
|
||||
<dt>Language
|
||||
<dd>Select the language to compare. Empty field means any.
|
||||
|
||||
<dt>Service
|
||||
<dd>The service to compare. Empty field means any.
|
||||
|
||||
<dt>CA Identification
|
||||
<dd>The CAID to compare. Empty field means any.
|
||||
|
||||
<dt>CA Provider
|
||||
<dd>The CA provider to compare. Empty field means any.
|
||||
|
||||
<dt>PID
|
||||
<dd>Program identification (PID) number to compare. Zero means any.
|
||||
This comparison is processed only when service comparison is active.
|
||||
|
||||
<dt>Action
|
||||
<dd>The rule action defines the operation when all comparisons succeeds.
|
||||
|
||||
<dl>
|
||||
|
||||
<dt>NONE
|
||||
<dd>No action, may be used for the logging and a comparison verification.
|
||||
|
||||
<dt>USE
|
||||
<dd>Use this elementary stream.
|
||||
|
||||
<dt>ONCE
|
||||
<dd>Use this elementary stream only once per selected language.
|
||||
The first successfully compared rule wins.
|
||||
|
||||
<dt>EXCLUSIVE
|
||||
<dd>Use only this elementary stream. No other elementary streams
|
||||
will be used.
|
||||
|
||||
<dt>EMPTY
|
||||
<dd>Add this elementary stream only when no elementary streams are
|
||||
used from previous rules.
|
||||
|
||||
<dt>IGNORE
|
||||
<dd>Ignore this elementary stream. This stream is not used. Another
|
||||
successfully compared rule with different action may override it.
|
||||
|
||||
</dl>
|
||||
|
||||
<dt>Log
|
||||
<dd>Write a short message to log identifying the matched parameters.
|
||||
It is useful for debugging your setup or structure of incoming
|
||||
streams.
|
||||
|
||||
</dl>
|
||||
</div>
|
|
@ -125,6 +125,7 @@ void api_init ( void )
|
|||
api_epggrab_init();
|
||||
api_status_init();
|
||||
api_imagecache_init();
|
||||
api_esfilter_init();
|
||||
}
|
||||
|
||||
void api_done ( void )
|
||||
|
|
|
@ -67,12 +67,14 @@ void api_epg_init ( void );
|
|||
void api_epggrab_init ( void );
|
||||
void api_status_init ( void );
|
||||
void api_imagecache_init ( void );
|
||||
void api_esfilter_init ( void );
|
||||
|
||||
/*
|
||||
* IDnode
|
||||
*/
|
||||
typedef struct api_idnode_grid_conf
|
||||
{
|
||||
int tindex;
|
||||
int start;
|
||||
int limit;
|
||||
idnode_filter_t filter;
|
||||
|
|
101
src/api/api_esfilter.c
Normal file
101
src/api/api_esfilter.c
Normal file
|
@ -0,0 +1,101 @@
|
|||
/*
|
||||
* API - elementary stream filter related calls
|
||||
*
|
||||
* Copyright (C) 2014 Jaroslav Kysela
|
||||
*
|
||||
* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "tvheadend.h"
|
||||
#include "esfilter.h"
|
||||
#include "lang_codes.h"
|
||||
#include "access.h"
|
||||
#include "api.h"
|
||||
|
||||
static void
|
||||
api_esfilter_grid
|
||||
( idnode_set_t *ins, api_idnode_grid_conf_t *conf, htsmsg_t *args,
|
||||
esfilter_class_t cls )
|
||||
{
|
||||
esfilter_t *esf;
|
||||
|
||||
TAILQ_FOREACH(esf, &esfilters[cls], esf_link) {
|
||||
idnode_set_add(ins, (idnode_t*)esf, &conf->filter);
|
||||
}
|
||||
}
|
||||
|
||||
static int
|
||||
api_esfilter_create
|
||||
( void *opaque, const char *op, htsmsg_t *args, htsmsg_t **resp,
|
||||
esfilter_class_t cls )
|
||||
{
|
||||
htsmsg_t *conf;
|
||||
|
||||
if (!(conf = htsmsg_get_map(args, "conf")))
|
||||
return EINVAL;
|
||||
|
||||
pthread_mutex_lock(&global_lock);
|
||||
esfilter_create(cls, NULL, conf, 1);
|
||||
pthread_mutex_unlock(&global_lock);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#define ESFILTER(func, t) \
|
||||
static void api_esfilter_grid_##func \
|
||||
( idnode_set_t *ins, api_idnode_grid_conf_t *conf, htsmsg_t *args ) \
|
||||
{ return api_esfilter_grid(ins, conf, args, (t)); } \
|
||||
static int api_esfilter_create_##func \
|
||||
( void *opaque, const char *op, htsmsg_t *args, htsmsg_t **resp ) \
|
||||
{ return api_esfilter_create(opaque, op, args, resp, (t)); }
|
||||
|
||||
ESFILTER(video, ESF_CLASS_VIDEO);
|
||||
ESFILTER(audio, ESF_CLASS_AUDIO);
|
||||
ESFILTER(teletext, ESF_CLASS_TELETEXT);
|
||||
ESFILTER(subtit, ESF_CLASS_SUBTIT);
|
||||
ESFILTER(ca, ESF_CLASS_CA);
|
||||
ESFILTER(other, ESF_CLASS_OTHER);
|
||||
|
||||
void api_esfilter_init ( void )
|
||||
{
|
||||
static api_hook_t ah[] = {
|
||||
{ "esfilter/video/class", ACCESS_ANONYMOUS, api_idnode_class, (void*)&esfilter_class_video },
|
||||
{ "esfilter/video/grid", ACCESS_ANONYMOUS, api_idnode_grid, api_esfilter_grid_video },
|
||||
{ "esfilter/video/create", ACCESS_ADMIN, api_esfilter_create_video, NULL },
|
||||
|
||||
{ "esfilter/audio/class", ACCESS_ANONYMOUS, api_idnode_class, (void*)&esfilter_class_audio },
|
||||
{ "esfilter/audio/grid", ACCESS_ANONYMOUS, api_idnode_grid, api_esfilter_grid_audio },
|
||||
{ "esfilter/audio/create", ACCESS_ADMIN, api_esfilter_create_audio, NULL },
|
||||
|
||||
{ "esfilter/teletext/class", ACCESS_ANONYMOUS, api_idnode_class, (void*)&esfilter_class_teletext },
|
||||
{ "esfilter/teletext/grid", ACCESS_ANONYMOUS, api_idnode_grid, api_esfilter_grid_teletext },
|
||||
{ "esfilter/teletext/create",ACCESS_ADMIN, api_esfilter_create_teletext, NULL },
|
||||
|
||||
{ "esfilter/subtit/class", ACCESS_ANONYMOUS, api_idnode_class, (void*)&esfilter_class_subtit },
|
||||
{ "esfilter/subtit/grid", ACCESS_ANONYMOUS, api_idnode_grid, api_esfilter_grid_subtit },
|
||||
{ "esfilter/subtit/create", ACCESS_ADMIN, api_esfilter_create_subtit, NULL },
|
||||
|
||||
{ "esfilter/ca/class", ACCESS_ANONYMOUS, api_idnode_class, (void*)&esfilter_class_ca },
|
||||
{ "esfilter/ca/grid", ACCESS_ANONYMOUS, api_idnode_grid, api_esfilter_grid_ca },
|
||||
{ "esfilter/ca/create", ACCESS_ADMIN, api_esfilter_create_ca, NULL },
|
||||
|
||||
{ "esfilter/other/class", ACCESS_ANONYMOUS, api_idnode_class, (void*)&esfilter_class_other },
|
||||
{ "esfilter/other/grid", ACCESS_ANONYMOUS, api_idnode_grid, api_esfilter_grid_other },
|
||||
{ "esfilter/other/create", ACCESS_ADMIN, api_esfilter_create_other, NULL },
|
||||
|
||||
{ NULL },
|
||||
};
|
||||
|
||||
api_register_all(ah);
|
||||
}
|
|
@ -377,8 +377,8 @@ exit:
|
|||
}
|
||||
|
||||
static int
|
||||
api_idnode_delete
|
||||
( void *opaque, const char *op, htsmsg_t *args, htsmsg_t **resp )
|
||||
api_idnode_handler
|
||||
( htsmsg_t *args, htsmsg_t **resp, void (*handler)(idnode_t *in) )
|
||||
{
|
||||
int err = 0;
|
||||
idnode_t *in;
|
||||
|
@ -400,7 +400,7 @@ api_idnode_delete
|
|||
HTSMSG_FOREACH(f, uuids) {
|
||||
if (!(uuid = htsmsg_field_get_string(f))) continue;
|
||||
if (!(in = idnode_find(uuid, NULL))) continue;
|
||||
idnode_delete(in);
|
||||
handler(in);
|
||||
}
|
||||
|
||||
/* Single */
|
||||
|
@ -409,7 +409,7 @@ api_idnode_delete
|
|||
if (!(in = idnode_find(uuid, NULL)))
|
||||
err = ENOENT;
|
||||
else
|
||||
idnode_delete(in);
|
||||
handler(in);
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&global_lock);
|
||||
|
@ -417,14 +417,37 @@ api_idnode_delete
|
|||
return err;
|
||||
}
|
||||
|
||||
static int
|
||||
api_idnode_delete
|
||||
( void *opaque, const char *op, htsmsg_t *args, htsmsg_t **resp )
|
||||
{
|
||||
return api_idnode_handler(args, resp, idnode_delete);
|
||||
}
|
||||
|
||||
static int
|
||||
api_idnode_moveup
|
||||
( void *opaque, const char *op, htsmsg_t *args, htsmsg_t **resp )
|
||||
{
|
||||
return api_idnode_handler(args, resp, idnode_moveup);
|
||||
}
|
||||
|
||||
static int
|
||||
api_idnode_movedown
|
||||
( void *opaque, const char *op, htsmsg_t *args, htsmsg_t **resp )
|
||||
{
|
||||
return api_idnode_handler(args, resp, idnode_movedown);
|
||||
}
|
||||
|
||||
void api_idnode_init ( void )
|
||||
{
|
||||
static api_hook_t ah[] = {
|
||||
{ "idnode/load", ACCESS_ANONYMOUS, api_idnode_load, NULL },
|
||||
{ "idnode/save", ACCESS_ADMIN, api_idnode_save, NULL },
|
||||
{ "idnode/tree", ACCESS_ANONYMOUS, api_idnode_tree, NULL },
|
||||
{ "idnode/class", ACCESS_ANONYMOUS, api_idnode_class, NULL },
|
||||
{ "idnode/delete", ACCESS_ADMIN, api_idnode_delete, NULL },
|
||||
{ "idnode/load", ACCESS_ANONYMOUS, api_idnode_load, NULL },
|
||||
{ "idnode/save", ACCESS_ADMIN, api_idnode_save, NULL },
|
||||
{ "idnode/tree", ACCESS_ANONYMOUS, api_idnode_tree, NULL },
|
||||
{ "idnode/class", ACCESS_ANONYMOUS, api_idnode_class, NULL },
|
||||
{ "idnode/delete", ACCESS_ADMIN, api_idnode_delete, NULL },
|
||||
{ "idnode/moveup", ACCESS_ADMIN, api_idnode_moveup, NULL },
|
||||
{ "idnode/movedown", ACCESS_ADMIN, api_idnode_movedown, NULL },
|
||||
{ NULL },
|
||||
};
|
||||
|
||||
|
|
|
@ -92,12 +92,45 @@ api_service_mapper_notify ( void )
|
|||
notify_by_msg("servicemapper", api_mapper_status_msg());
|
||||
}
|
||||
|
||||
static htsmsg_t *
|
||||
api_service_streams_get_one ( elementary_stream_t *es )
|
||||
{
|
||||
htsmsg_t *e = htsmsg_create_map();
|
||||
htsmsg_add_u32(e, "index", es->es_index);
|
||||
htsmsg_add_u32(e, "pid", es->es_pid);
|
||||
htsmsg_add_str(e, "type", streaming_component_type2txt(es->es_type));
|
||||
htsmsg_add_str(e, "language", es->es_lang);
|
||||
if (SCT_ISSUBTITLE(es->es_type)) {
|
||||
htsmsg_add_u32(e, "composition_id", es->es_composition_id);
|
||||
htsmsg_add_u32(e, "ancillary_id", es->es_ancillary_id);
|
||||
} else if (SCT_ISAUDIO(es->es_type)) {
|
||||
htsmsg_add_u32(e, "audio_type", es->es_audio_type);
|
||||
} else if (SCT_ISVIDEO(es->es_type)) {
|
||||
htsmsg_add_u32(e, "width", es->es_width);
|
||||
htsmsg_add_u32(e, "height", es->es_height);
|
||||
htsmsg_add_u32(e, "duration", es->es_frame_duration);
|
||||
htsmsg_add_u32(e, "aspect_num", es->es_aspect_num);
|
||||
htsmsg_add_u32(e, "aspect_den", es->es_aspect_den);
|
||||
} else if (es->es_type == SCT_CA) {
|
||||
caid_t *ca;
|
||||
htsmsg_t *e2, *l2 = htsmsg_create_list();
|
||||
LIST_FOREACH(ca, &es->es_caids, link) {
|
||||
e2 = htsmsg_create_map();
|
||||
htsmsg_add_u32(e2, "caid", ca->caid);
|
||||
htsmsg_add_u32(e2, "provider", ca->providerid);
|
||||
htsmsg_add_msg(l2, NULL, e2);
|
||||
}
|
||||
htsmsg_add_msg(e, "caids", l2);
|
||||
}
|
||||
return e;
|
||||
}
|
||||
|
||||
static int
|
||||
api_service_streams
|
||||
( void *opaque, const char *op, htsmsg_t *args, htsmsg_t **resp )
|
||||
{
|
||||
const char *uuid;
|
||||
htsmsg_t *e, *st;
|
||||
htsmsg_t *e, *st, *stf;
|
||||
service_t *s;
|
||||
elementary_stream_t *es;
|
||||
|
||||
|
@ -116,6 +149,7 @@ api_service_streams
|
|||
/* Build response */
|
||||
pthread_mutex_lock(&s->s_stream_mutex);
|
||||
st = htsmsg_create_list();
|
||||
stf = htsmsg_create_list();
|
||||
if (s->s_pcr_pid) {
|
||||
e = htsmsg_create_map();
|
||||
htsmsg_add_u32(e, "pid", s->s_pcr_pid);
|
||||
|
@ -128,39 +162,17 @@ api_service_streams
|
|||
htsmsg_add_str(e, "type", "PMT");
|
||||
htsmsg_add_msg(st, NULL, e);
|
||||
}
|
||||
TAILQ_FOREACH(es, &s->s_components, es_link) {
|
||||
htsmsg_t *e = htsmsg_create_map();
|
||||
htsmsg_add_u32(e, "index", es->es_index);
|
||||
htsmsg_add_u32(e, "pid", es->es_pid);
|
||||
htsmsg_add_str(e, "type", streaming_component_type2txt(es->es_type));
|
||||
htsmsg_add_str(e, "language", es->es_lang);
|
||||
if (SCT_ISSUBTITLE(es->es_type)) {
|
||||
htsmsg_add_u32(e, "composition_id", es->es_composition_id);
|
||||
htsmsg_add_u32(e, "ancillary_id", es->es_ancillary_id);
|
||||
} else if (SCT_ISAUDIO(es->es_type)) {
|
||||
htsmsg_add_u32(e, "audio_type", es->es_audio_type);
|
||||
} else if (SCT_ISVIDEO(es->es_type)) {
|
||||
htsmsg_add_u32(e, "width", es->es_width);
|
||||
htsmsg_add_u32(e, "height", es->es_height);
|
||||
htsmsg_add_u32(e, "duration", es->es_frame_duration);
|
||||
htsmsg_add_u32(e, "aspect_num", es->es_aspect_num);
|
||||
htsmsg_add_u32(e, "aspect_den", es->es_aspect_den);
|
||||
} else if (es->es_type == SCT_CA) {
|
||||
caid_t *ca;
|
||||
htsmsg_t *e2, *l2 = htsmsg_create_list();
|
||||
LIST_FOREACH(ca, &es->es_caids, link) {
|
||||
e2 = htsmsg_create_map();
|
||||
htsmsg_add_u32(e2, "caid", ca->caid);
|
||||
htsmsg_add_u32(e2, "provider", ca->providerid);
|
||||
htsmsg_add_msg(l2, NULL, e2);
|
||||
}
|
||||
htsmsg_add_msg(e, "caids", l2);
|
||||
}
|
||||
htsmsg_add_msg(st, NULL, e);
|
||||
}
|
||||
TAILQ_FOREACH(es, &s->s_components, es_link)
|
||||
htsmsg_add_msg(st, NULL, api_service_streams_get_one(es));
|
||||
if (TAILQ_FIRST(&s->s_filt_components) == NULL ||
|
||||
s->s_status == SERVICE_IDLE)
|
||||
service_build_filter(s);
|
||||
TAILQ_FOREACH(es, &s->s_filt_components, es_filt_link)
|
||||
htsmsg_add_msg(stf, NULL, api_service_streams_get_one(es));
|
||||
*resp = htsmsg_create_map();
|
||||
htsmsg_add_str(*resp, "name", s->s_nicename);
|
||||
htsmsg_add_msg(*resp, "streams", st);
|
||||
htsmsg_add_msg(*resp, "fstreams", stf);
|
||||
pthread_mutex_unlock(&s->s_stream_mutex);
|
||||
|
||||
/* Done */
|
||||
|
|
1023
src/esfilter.c
Normal file
1023
src/esfilter.c
Normal file
File diff suppressed because it is too large
Load diff
110
src/esfilter.h
Normal file
110
src/esfilter.h
Normal file
|
@ -0,0 +1,110 @@
|
|||
/*
|
||||
* tvheadend, Elementary Stream Filter
|
||||
* Copyright (C) 2014 Jaroslav Kysela
|
||||
*
|
||||
* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef __TVH_ESFILTER_H__
|
||||
#define __TVH_ESFILTER_H__
|
||||
|
||||
#include "tvheadend.h"
|
||||
#include "idnode.h"
|
||||
|
||||
typedef enum {
|
||||
ESF_CLASS_NONE = 0,
|
||||
ESF_CLASS_VIDEO,
|
||||
ESF_CLASS_AUDIO,
|
||||
ESF_CLASS_TELETEXT,
|
||||
ESF_CLASS_SUBTIT,
|
||||
ESF_CLASS_CA,
|
||||
ESF_CLASS_OTHER,
|
||||
ESF_CLASS_LAST = ESF_CLASS_OTHER
|
||||
} esfilter_class_t;
|
||||
|
||||
#define ESF_CLASS_IS_VALID(i) \
|
||||
((i) >= ESF_CLASS_VIDEO && (i) <= ESF_CLASS_LAST)
|
||||
|
||||
extern const idclass_t esfilter_class;
|
||||
extern const idclass_t esfilter_class_video;
|
||||
extern const idclass_t esfilter_class_audio;
|
||||
extern const idclass_t esfilter_class_teletext;
|
||||
extern const idclass_t esfilter_class_subtit;
|
||||
extern const idclass_t esfilter_class_ca;
|
||||
extern const idclass_t esfilter_class_other;
|
||||
|
||||
#define ESF_MASK_VIDEO \
|
||||
(SCT_MASK(SCT_MPEG2VIDEO) | SCT_MASK(SCT_H264) | SCT_MASK(SCT_VP8))
|
||||
|
||||
#define ESF_MASK_AUDIO \
|
||||
(SCT_MASK(SCT_MPEG2AUDIO) | SCT_MASK(SCT_AC3) | SCT_MASK(SCT_AAC) | \
|
||||
SCT_MASK(SCT_EAC3) | SCT_MASK(SCT_MP4A) | SCT_MASK(SCT_VORBIS))
|
||||
|
||||
#define ESF_MASK_TELETEXT \
|
||||
SCT_MASK(SCT_TELETEXT)
|
||||
|
||||
#define ESF_MASK_SUBTIT \
|
||||
(SCT_MASK(SCT_DVBSUB) | SCT_MASK(SCT_TEXTSUB))
|
||||
|
||||
#define ESF_MASK_CA \
|
||||
SCT_MASK(SCT_CA)
|
||||
|
||||
#define ESF_MASK_OTHER \
|
||||
SCT_MASK(SCT_MPEGTS)
|
||||
|
||||
extern uint32_t esfilterclsmask[];
|
||||
|
||||
TAILQ_HEAD(esfilter_entry_queue, esfilter);
|
||||
|
||||
extern struct esfilter_entry_queue esfilters[];
|
||||
|
||||
typedef enum {
|
||||
ESFA_NONE = 0,
|
||||
ESFA_USE, /* use this stream */
|
||||
ESFA_ONCE, /* use this stream once per language */
|
||||
ESFA_EXCLUSIVE, /* use this stream exclusively */
|
||||
ESFA_EMPTY, /* use this stream when no streams were added */
|
||||
ESFA_IGNORE,
|
||||
ESFA_LAST = ESFA_IGNORE
|
||||
} esfilter_action_t;
|
||||
|
||||
typedef struct esfilter {
|
||||
idnode_t esf_id;
|
||||
TAILQ_ENTRY(esfilter) esf_link;
|
||||
|
||||
int esf_class;
|
||||
int esf_save;
|
||||
int esf_index;
|
||||
int esf_enabled;
|
||||
uint32_t esf_type;
|
||||
char esf_language[4];
|
||||
char esf_service[UUID_HEX_SIZE];
|
||||
int esf_pid;
|
||||
uint16_t esf_caid;
|
||||
uint32_t esf_caprovider;
|
||||
int esf_action;
|
||||
int esf_log;
|
||||
char *esf_comment;
|
||||
} esfilter_t;
|
||||
|
||||
esfilter_t *esfilter_create
|
||||
(esfilter_class_t esf_class, const char *uuid, htsmsg_t *conf, int save);
|
||||
|
||||
const char * esfilter_class2txt(int cls);
|
||||
const char * esfilter_action2txt(esfilter_action_t a);
|
||||
|
||||
void esfilter_init(void);
|
||||
void esfilter_done(void);
|
||||
|
||||
#endif /* __TVH_ESFILTER_H__ */
|
28
src/idnode.c
28
src/idnode.c
|
@ -140,20 +140,40 @@ idnode_unlink(idnode_t *in)
|
|||
/**
|
||||
*
|
||||
*/
|
||||
void
|
||||
idnode_delete(idnode_t *in)
|
||||
static void
|
||||
idnode_handler(size_t off, idnode_t *in)
|
||||
{
|
||||
void (**fcn)(idnode_t *);
|
||||
lock_assert(&global_lock);
|
||||
const idclass_t *idc = in->in_class;
|
||||
while (idc) {
|
||||
if (idc->ic_delete) {
|
||||
idc->ic_delete(in);
|
||||
fcn = (void *)idc + off;
|
||||
if (*fcn) {
|
||||
(*fcn)(in);
|
||||
break;
|
||||
}
|
||||
idc = idc->ic_super;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
idnode_delete(idnode_t *in)
|
||||
{
|
||||
return idnode_handler(offsetof(idclass_t, ic_delete), in);
|
||||
}
|
||||
|
||||
void
|
||||
idnode_moveup(idnode_t *in)
|
||||
{
|
||||
return idnode_handler(offsetof(idclass_t, ic_moveup), in);
|
||||
}
|
||||
|
||||
void
|
||||
idnode_movedown(idnode_t *in)
|
||||
{
|
||||
return idnode_handler(offsetof(idclass_t, ic_movedown), in);
|
||||
}
|
||||
|
||||
/* **************************************************************************
|
||||
* Info
|
||||
* *************************************************************************/
|
||||
|
|
17
src/idnode.h
17
src/idnode.h
|
@ -42,7 +42,8 @@ typedef struct idnode_set
|
|||
/*
|
||||
* Class definition
|
||||
*/
|
||||
typedef struct idclass {
|
||||
typedef struct idclass idclass_t;
|
||||
struct idclass {
|
||||
const struct idclass *ic_super; /// Parent class
|
||||
const char *ic_class; /// Class name
|
||||
const char *ic_caption; /// Class description
|
||||
|
@ -51,11 +52,13 @@ typedef struct idclass {
|
|||
const char *ic_event; /// Events to fire on add/delete/title
|
||||
|
||||
/* Callbacks */
|
||||
idnode_set_t *(*ic_get_childs)(idnode_t *self);
|
||||
const char *(*ic_get_title) (idnode_t *self);
|
||||
void (*ic_save) (idnode_t *self);
|
||||
void (*ic_delete) (idnode_t *self);
|
||||
} idclass_t;
|
||||
idnode_set_t *(*ic_get_childs) (idnode_t *self);
|
||||
const char *(*ic_get_title) (idnode_t *self);
|
||||
void (*ic_save) (idnode_t *self);
|
||||
void (*ic_delete) (idnode_t *self);
|
||||
void (*ic_moveup) (idnode_t *self);
|
||||
void (*ic_movedown) (idnode_t *self);
|
||||
};
|
||||
|
||||
/*
|
||||
* Node definition
|
||||
|
@ -120,6 +123,8 @@ const char *idnode_get_title (idnode_t *in);
|
|||
int idnode_is_leaf (idnode_t *in);
|
||||
int idnode_is_instance (idnode_t *in, const idclass_t *idc);
|
||||
void idnode_delete (idnode_t *in);
|
||||
void idnode_moveup (idnode_t *in);
|
||||
void idnode_movedown (idnode_t *in);
|
||||
|
||||
void *idnode_find (const char *uuid, const idclass_t *idc);
|
||||
idnode_set_t *idnode_find_all(const idclass_t *idc);
|
||||
|
|
|
@ -61,6 +61,7 @@
|
|||
#include "timeshift.h"
|
||||
#include "fsmonitor.h"
|
||||
#include "lang_codes.h"
|
||||
#include "esfilter.h"
|
||||
#if ENABLE_LIBAV
|
||||
#include "libav.h"
|
||||
#include "plumbing/transcoding.h"
|
||||
|
@ -767,6 +768,7 @@ main(int argc, char **argv)
|
|||
imagecache_init();
|
||||
|
||||
http_client_init();
|
||||
esfilter_init();
|
||||
|
||||
service_init();
|
||||
|
||||
|
@ -877,6 +879,7 @@ main(int argc, char **argv)
|
|||
tvhftrace("main", hts_settings_done);
|
||||
tvhftrace("main", dvb_done);
|
||||
tvhftrace("main", lang_str_done);
|
||||
tvhftrace("main", esfilter_done);
|
||||
tvhftrace("main", urlparse_done);
|
||||
|
||||
tvhlog(LOG_NOTICE, "STOP", "Exiting HTS Tvheadend");
|
||||
|
|
183
src/service.c
183
src/service.c
|
@ -44,6 +44,7 @@
|
|||
#include "lang_codes.h"
|
||||
#include "descrambler.h"
|
||||
#include "input.h"
|
||||
#include "esfilter.h"
|
||||
|
||||
static void service_data_timeout(void *aux);
|
||||
static void service_class_save(struct idnode *self);
|
||||
|
@ -284,7 +285,7 @@ service_stop(service_t *t)
|
|||
/**
|
||||
* Clean up each stream
|
||||
*/
|
||||
TAILQ_FOREACH(st, &t->s_components, es_link)
|
||||
TAILQ_FOREACH(st, &t->s_filt_components, es_link)
|
||||
stream_clean(st);
|
||||
|
||||
t->s_status = SERVICE_IDLE;
|
||||
|
@ -319,6 +320,161 @@ service_remove_subscriber(service_t *t, th_subscription_t *s,
|
|||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
#define ESFM_USED (1<<0)
|
||||
#define ESFM_IGNORE (1<<1)
|
||||
|
||||
static void
|
||||
service_build_filter_add(service_t *t, elementary_stream_t *st,
|
||||
elementary_stream_t **sta, int *p)
|
||||
{
|
||||
/* only once */
|
||||
if (st->es_filter & ESFM_USED)
|
||||
return;
|
||||
st->es_filter |= ESFM_USED;
|
||||
TAILQ_INSERT_TAIL(&t->s_filt_components, st, es_filt_link);
|
||||
sta[*p] = st;
|
||||
(*p)++;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
void
|
||||
service_build_filter(service_t *t)
|
||||
{
|
||||
elementary_stream_t *st, *st2, **sta;
|
||||
esfilter_t *esf;
|
||||
caid_t *ca;
|
||||
int i, n, p, o, exclusive;
|
||||
uint32_t mask;
|
||||
|
||||
/* rebuild the filtered and ordered components */
|
||||
TAILQ_INIT(&t->s_filt_components);
|
||||
|
||||
for (i = ESF_CLASS_VIDEO; i <= ESF_CLASS_LAST; i++)
|
||||
if (!TAILQ_EMPTY(&esfilters[i]))
|
||||
goto filter;
|
||||
|
||||
TAILQ_FOREACH(st, &t->s_components, es_link)
|
||||
TAILQ_INSERT_TAIL(&t->s_filt_components, st, es_filt_link);
|
||||
return;
|
||||
|
||||
filter:
|
||||
n = 0;
|
||||
TAILQ_FOREACH(st, &t->s_components, es_link) {
|
||||
st->es_filter = 0;
|
||||
n++;
|
||||
}
|
||||
|
||||
sta = alloca(sizeof(elementary_stream_t *) * n);
|
||||
|
||||
for (i = ESF_CLASS_VIDEO, p = 0; i <= ESF_CLASS_LAST; i++) {
|
||||
o = p;
|
||||
mask = esfilterclsmask[i];
|
||||
if (TAILQ_EMPTY(&esfilters[i])) {
|
||||
TAILQ_FOREACH(st, &t->s_components, es_link) {
|
||||
if ((mask & SCT_MASK(st->es_type)) != 0)
|
||||
service_build_filter_add(t, st, sta, &p);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
exclusive = 0;
|
||||
TAILQ_FOREACH(esf, &esfilters[i], esf_link) {
|
||||
if (!esf->esf_enabled)
|
||||
continue;
|
||||
TAILQ_FOREACH(st, &t->s_components, es_link) {
|
||||
if ((mask & SCT_MASK(st->es_type)) == 0)
|
||||
continue;
|
||||
if (esf->esf_type && (esf->esf_type & SCT_MASK(st->es_type)) == 0)
|
||||
continue;
|
||||
if (esf->esf_language[0] &&
|
||||
strncmp(esf->esf_language, st->es_lang, 4))
|
||||
continue;
|
||||
if (esf->esf_service && esf->esf_service[0]) {
|
||||
if (strcmp(esf->esf_service, idnode_uuid_as_str(&t->s_id)))
|
||||
continue;
|
||||
if (esf->esf_pid && esf->esf_pid != st->es_pid)
|
||||
continue;
|
||||
}
|
||||
if (i == ESF_CLASS_CA &&
|
||||
(esf->esf_caid != -1 || esf->esf_caprovider != -1)) {
|
||||
LIST_FOREACH(ca, &st->es_caids, link) {
|
||||
if (esf->esf_caid != -1 && ca->caid != esf->esf_caid)
|
||||
continue;
|
||||
if (esf->esf_caprovider != -1 && ca->providerid != esf->esf_caprovider)
|
||||
continue;
|
||||
break;
|
||||
}
|
||||
if (ca == NULL)
|
||||
continue;
|
||||
}
|
||||
if (esf->esf_log)
|
||||
tvhlog(LOG_INFO, "service", "esfilter: %s %03d %05d %s %s %s %s",
|
||||
esfilter_class2txt(i), esf->esf_index,
|
||||
st->es_pid, streaming_component_type2txt(st->es_type),
|
||||
lang_code_get(st->es_lang), t->s_nicename,
|
||||
esfilter_action2txt(esf->esf_action));
|
||||
switch (esf->esf_action) {
|
||||
case ESFA_NONE:
|
||||
break;
|
||||
case ESFA_IGNORE:
|
||||
st->es_filter |= ESFM_IGNORE;
|
||||
break;
|
||||
case ESFA_USE:
|
||||
service_build_filter_add(t, st, sta, &p);
|
||||
break;
|
||||
case ESFA_ONCE:
|
||||
if (esf->esf_language[0] == '\0') {
|
||||
service_build_filter_add(t, st, sta, &p);
|
||||
} else {
|
||||
int present = 0;
|
||||
TAILQ_FOREACH(st2, &t->s_components, es_link) {
|
||||
if ((st2->es_filter & ESFM_USED) == 0)
|
||||
continue;
|
||||
if (strcmp(st2->es_lang, st->es_lang) == 0) {
|
||||
present = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!present)
|
||||
service_build_filter_add(t, st, sta, &p);
|
||||
}
|
||||
break;
|
||||
case ESFA_EXCLUSIVE:
|
||||
break;
|
||||
case ESFA_EMPTY:
|
||||
if (p == o)
|
||||
service_build_filter_add(t, st, sta, &p);
|
||||
break;
|
||||
default:
|
||||
tvhlog(LOG_DEBUG, "service", "Unknown esfilter action %d", esf->esf_action);
|
||||
break;
|
||||
}
|
||||
if (esf->esf_action == ESFA_EXCLUSIVE) {
|
||||
/* forget previous work */
|
||||
while (p > o) {
|
||||
p--;
|
||||
TAILQ_REMOVE(&t->s_filt_components, sta[p], es_filt_link);
|
||||
}
|
||||
service_build_filter_add(t, st, sta, &p);
|
||||
exclusive = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!exclusive) {
|
||||
TAILQ_FOREACH(st, &t->s_components, es_link) {
|
||||
if ((mask & SCT_MASK(st->es_type)) != 0 &&
|
||||
(st->es_filter & (ESFM_USED|ESFM_IGNORE)) == 0)
|
||||
service_build_filter_add(t, st, sta, &p);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
|
@ -336,6 +492,8 @@ service_start(service_t *t, int instance)
|
|||
t->s_streaming_status = 0;
|
||||
t->s_scrambled_seen = 0;
|
||||
|
||||
service_build_filter(t);
|
||||
|
||||
if((r = t->s_start_feed(t, instance)))
|
||||
return r;
|
||||
|
||||
|
@ -349,7 +507,7 @@ service_start(service_t *t, int instance)
|
|||
/**
|
||||
* Initialize stream
|
||||
*/
|
||||
TAILQ_FOREACH(st, &t->s_components, es_link)
|
||||
TAILQ_FOREACH(st, &t->s_filt_components, es_link)
|
||||
stream_init(st);
|
||||
|
||||
pthread_mutex_unlock(&t->s_stream_mutex);
|
||||
|
@ -501,6 +659,7 @@ service_destroy(service_t *t, int delconf)
|
|||
|
||||
t->s_status = SERVICE_ZOMBIE;
|
||||
|
||||
TAILQ_INIT(&t->s_filt_components);
|
||||
while((st = TAILQ_FIRST(&t->s_components)) != NULL)
|
||||
service_stream_destroy(t, st);
|
||||
|
||||
|
@ -552,6 +711,7 @@ service_create0
|
|||
t->s_channel_name = service_channel_name;
|
||||
t->s_provider_name = service_provider_name;
|
||||
TAILQ_INIT(&t->s_components);
|
||||
TAILQ_INIT(&t->s_filt_components);
|
||||
t->s_last_pid = -1;
|
||||
|
||||
streaming_pad_init(&t->s_streaming_pad);
|
||||
|
@ -631,7 +791,7 @@ elementary_stream_t *
|
|||
service_stream_create(service_t *t, int pid,
|
||||
streaming_component_type_t type)
|
||||
{
|
||||
elementary_stream_t *st;
|
||||
elementary_stream_t *st, *st2;
|
||||
int i = 0;
|
||||
int idx = 0;
|
||||
lock_assert(&t->s_stream_mutex);
|
||||
|
@ -662,8 +822,14 @@ service_stream_create(service_t *t, int pid,
|
|||
if(t->s_flags & S_DEBUG)
|
||||
tvhlog(LOG_DEBUG, "service", "Add stream %s", st->es_nicename);
|
||||
|
||||
if(t->s_status == SERVICE_RUNNING)
|
||||
stream_init(st);
|
||||
if(t->s_status == SERVICE_RUNNING) {
|
||||
service_build_filter(t);
|
||||
TAILQ_FOREACH(st2, &t->s_filt_components, es_filt_link)
|
||||
if (st2 == st) {
|
||||
stream_init(st);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return st;
|
||||
}
|
||||
|
@ -842,9 +1008,10 @@ service_restart(service_t *t, int had_components)
|
|||
streaming_msg_free(sm);
|
||||
}
|
||||
|
||||
service_build_filter(t);
|
||||
descrambler_service_start(t);
|
||||
|
||||
if(TAILQ_FIRST(&t->s_components) != NULL) {
|
||||
if(TAILQ_FIRST(&t->s_filt_components) != NULL) {
|
||||
sm = streaming_msg_create_data(SMT_START,
|
||||
service_build_stream_start(t));
|
||||
streaming_pad_deliver(&t->s_streaming_pad, sm);
|
||||
|
@ -871,7 +1038,7 @@ service_build_stream_start(service_t *t)
|
|||
|
||||
lock_assert(&t->s_stream_mutex);
|
||||
|
||||
TAILQ_FOREACH(st, &t->s_components, es_link)
|
||||
TAILQ_FOREACH(st, &t->s_filt_components, es_filt_link)
|
||||
n++;
|
||||
|
||||
ss = calloc(1, sizeof(streaming_start_t) +
|
||||
|
@ -880,7 +1047,7 @@ service_build_stream_start(service_t *t)
|
|||
ss->ss_num_components = n;
|
||||
|
||||
n = 0;
|
||||
TAILQ_FOREACH(st, &t->s_components, es_link) {
|
||||
TAILQ_FOREACH(st, &t->s_filt_components, es_filt_link) {
|
||||
streaming_start_component_t *ssc = &ss->ss_components[n++];
|
||||
ssc->ssc_index = st->es_index;
|
||||
ssc->ssc_type = st->es_type;
|
||||
|
|
|
@ -35,6 +35,7 @@ struct channel;
|
|||
typedef struct elementary_stream {
|
||||
|
||||
TAILQ_ENTRY(elementary_stream) es_link;
|
||||
TAILQ_ENTRY(elementary_stream) es_filt_link;
|
||||
int es_position;
|
||||
struct service *es_service;
|
||||
|
||||
|
@ -124,6 +125,9 @@ typedef struct elementary_stream {
|
|||
/* SI section processing (horrible hack) */
|
||||
void *es_section;
|
||||
|
||||
/* Filter temporary variable */
|
||||
uint32_t es_filter;
|
||||
|
||||
} elementary_stream_t;
|
||||
|
||||
|
||||
|
@ -400,9 +404,10 @@ typedef struct service {
|
|||
int s_caid;
|
||||
|
||||
/**
|
||||
* List of all components.
|
||||
* List of all and filtered components.
|
||||
*/
|
||||
struct elementary_stream_queue s_components;
|
||||
struct elementary_stream_queue s_filt_components;
|
||||
int s_last_pid;
|
||||
elementary_stream_t *s_last_es;
|
||||
|
||||
|
@ -429,6 +434,8 @@ void service_done(void);
|
|||
|
||||
int service_start(service_t *t, int instance);
|
||||
|
||||
void service_build_filter(service_t *t);
|
||||
|
||||
service_t *service_create0(service_t *t, const idclass_t *idc, const char *uuid, int source_type, htsmsg_t *conf);
|
||||
|
||||
#define service_create(t, c, u, s, m)\
|
||||
|
|
|
@ -74,7 +74,7 @@ subscription_link_service(th_subscription_t *s, service_t *t)
|
|||
|
||||
pthread_mutex_lock(&t->s_stream_mutex);
|
||||
|
||||
if(TAILQ_FIRST(&t->s_components) != NULL) {
|
||||
if(TAILQ_FIRST(&t->s_filt_components) != NULL) {
|
||||
|
||||
if(s->ths_start_message != NULL)
|
||||
streaming_msg_free(s->ths_start_message);
|
||||
|
@ -122,7 +122,7 @@ subscription_unlink_service0(th_subscription_t *s, int reason, int stop)
|
|||
streaming_target_disconnect(&t->s_streaming_pad, &s->ths_input);
|
||||
|
||||
if(stop &&
|
||||
TAILQ_FIRST(&t->s_components) != NULL &&
|
||||
TAILQ_FIRST(&t->s_filt_components) != NULL &&
|
||||
s->ths_state == SUBSCRIPTION_GOT_SERVICE) {
|
||||
// Send a STOP message to the subscription client
|
||||
sm = streaming_msg_create_code(SMT_STOP, reason);
|
||||
|
|
|
@ -220,8 +220,11 @@ typedef enum {
|
|||
SCT_MP4A,
|
||||
SCT_VP8,
|
||||
SCT_VORBIS,
|
||||
SCT_LAST = SCT_VORBIS
|
||||
} streaming_component_type_t;
|
||||
|
||||
#define SCT_MASK(t) (1 << (t))
|
||||
|
||||
#define SCT_ISVIDEO(t) ((t) == SCT_MPEG2VIDEO || (t) == SCT_H264 || \
|
||||
(t) == SCT_VP8)
|
||||
|
||||
|
|
|
@ -149,6 +149,7 @@ extjs_root(http_connection_t *hc, const char *remain, void *opaque)
|
|||
extjs_load(hq, "static/app/capmteditor.js");
|
||||
extjs_load(hq, "static/app/tvadapters.js");
|
||||
extjs_load(hq, "static/app/idnode.js");
|
||||
extjs_load(hq, "static/app/esfilter.js");
|
||||
#if ENABLE_LINUXDVB
|
||||
extjs_load(hq, "static/app/mpegts.js");
|
||||
#endif
|
||||
|
|
108
src/webui/static/app/esfilter.js
Normal file
108
src/webui/static/app/esfilter.js
Normal file
|
@ -0,0 +1,108 @@
|
|||
/*
|
||||
* Elementary Stream Filters
|
||||
*/
|
||||
|
||||
tvheadend.esfilter_tab = function(panel)
|
||||
{
|
||||
tvheadend.idnode_grid(panel, {
|
||||
url : 'api/esfilter/video',
|
||||
comet : 'esfilter_video',
|
||||
titleS : 'Video Stream Filter',
|
||||
titleP : 'Video Stream Filters',
|
||||
tabIndex : 0,
|
||||
add : {
|
||||
url : 'api/esfilter/video',
|
||||
create : {}
|
||||
},
|
||||
del : true,
|
||||
move : true,
|
||||
help : function() {
|
||||
new tvheadend.help('Elementary Stream Filter', 'config_esfilter.html');
|
||||
}
|
||||
});
|
||||
|
||||
tvheadend.idnode_grid(panel, {
|
||||
url : 'api/esfilter/audio',
|
||||
comet : 'esfilter_audio',
|
||||
titleS : 'Audio Stream Filter',
|
||||
titleP : 'Audio Stream Filters',
|
||||
tabIndex : 1,
|
||||
add : {
|
||||
url : 'api/esfilter/audio',
|
||||
create : {}
|
||||
},
|
||||
del : true,
|
||||
move : true,
|
||||
help : function() {
|
||||
new tvheadend.help('Elementary Stream Filter', 'config_esfilter.html');
|
||||
}
|
||||
});
|
||||
|
||||
tvheadend.idnode_grid(panel, {
|
||||
url : 'api/esfilter/teletext',
|
||||
comet : 'esfilter_teletext',
|
||||
titleS : 'Teletext Stream Filter',
|
||||
titleP : 'Teletext Stream Filters',
|
||||
tabIndex : 2,
|
||||
add : {
|
||||
url : 'api/esfilter/teletext',
|
||||
create : {}
|
||||
},
|
||||
del : true,
|
||||
move : true,
|
||||
help : function() {
|
||||
new tvheadend.help('Elementary Stream Filter', 'config_esfilter.html');
|
||||
}
|
||||
});
|
||||
|
||||
tvheadend.idnode_grid(panel, {
|
||||
url : 'api/esfilter/subtit',
|
||||
comet : 'esfilter_subtit',
|
||||
titleS : 'Subtitle Stream Filter',
|
||||
titleP : 'Subtitle Stream Filters',
|
||||
tabIndex : 3,
|
||||
add : {
|
||||
url : 'api/esfilter/subtit',
|
||||
create : {}
|
||||
},
|
||||
del : true,
|
||||
move : true,
|
||||
help : function() {
|
||||
new tvheadend.help('Elementary Stream Filter', 'config_esfilter.html');
|
||||
}
|
||||
});
|
||||
|
||||
tvheadend.idnode_grid(panel, {
|
||||
url : 'api/esfilter/ca',
|
||||
comet : 'esfilter_ca',
|
||||
titleS : 'CA Stream Filter',
|
||||
titleP : 'CA Stream Filters',
|
||||
tabIndex : 4,
|
||||
add : {
|
||||
url : 'api/esfilter/ca',
|
||||
create : {}
|
||||
},
|
||||
del : true,
|
||||
move : true,
|
||||
help : function() {
|
||||
new tvheadend.help('Elementary Stream Filter', 'config_esfilter.html');
|
||||
}
|
||||
});
|
||||
|
||||
tvheadend.idnode_grid(panel, {
|
||||
url : 'api/esfilter/other',
|
||||
comet : 'esfilter_other',
|
||||
titleS : 'Other Stream Filter',
|
||||
titleP : 'Other Stream Filters',
|
||||
tabIndex : 5,
|
||||
add : {
|
||||
url : 'api/esfilter/other',
|
||||
create : {}
|
||||
},
|
||||
del : true,
|
||||
move : true,
|
||||
help : function() {
|
||||
new tvheadend.help('Elementary Stream Filter', 'config_esfilter.html');
|
||||
}
|
||||
});
|
||||
}
|
|
@ -148,6 +148,14 @@
|
|||
background-image: url(../icons/delete.png) !important;
|
||||
}
|
||||
|
||||
.moveup {
|
||||
background-image: url(../icons/arrow_up.png) !important;
|
||||
}
|
||||
|
||||
.movedown {
|
||||
background-image: url(../icons/arrow_down.png) !important;
|
||||
}
|
||||
|
||||
.save {
|
||||
background-image: url(../icons/save.png) !important;
|
||||
}
|
||||
|
@ -286,6 +294,11 @@
|
|||
|
||||
.arrow_switch {
|
||||
background-image: url(../icons/arrow_switch.png) !important;
|
||||
|
||||
}
|
||||
|
||||
.stream_config {
|
||||
background-image: url(../icons/film_edit.png) !important;
|
||||
}
|
||||
|
||||
.x-smallhdr {
|
||||
|
|
|
@ -145,7 +145,6 @@ tvheadend.IdNodeField = function (conf)
|
|||
width : w,
|
||||
dataIndex: this.id,
|
||||
header : this.text,
|
||||
sortable : true,
|
||||
editor : this.editor({create: false}),
|
||||
renderer : this.renderer(),
|
||||
hidden : this.hidden,
|
||||
|
@ -665,10 +664,12 @@ tvheadend.idnode_grid = function(panel, conf)
|
|||
var undoBtn = null;
|
||||
var addBtn = null;
|
||||
var delBtn = null;
|
||||
var upBtn = null;
|
||||
var downBtn = null;
|
||||
var editBtn = null;
|
||||
|
||||
/* Model */
|
||||
var idnode = new tvheadend.IdNode(d);
|
||||
var idnode = new tvheadend.IdNode(d);
|
||||
for (var i = 0; i < idnode.length(); i++) {
|
||||
var f = idnode.field(i);
|
||||
var c = f.column();
|
||||
|
@ -708,8 +709,11 @@ tvheadend.idnode_grid = function(panel, conf)
|
|||
});
|
||||
|
||||
/* Model */
|
||||
var sortable = true;
|
||||
if (conf.move)
|
||||
sortable = false;
|
||||
var model = new Ext.grid.ColumnModel({
|
||||
defaultSortable : true,
|
||||
defaultSortable : sortable,
|
||||
columns : columns
|
||||
});
|
||||
|
||||
|
@ -727,6 +731,10 @@ tvheadend.idnode_grid = function(panel, conf)
|
|||
select.on('selectionchange', function(s){
|
||||
if (delBtn)
|
||||
delBtn.setDisabled(s.getCount() == 0);
|
||||
if (upBtn) {
|
||||
upBtn.setDisabled(s.getCount() == 0);
|
||||
downBtn.setDisabled(s.getCount() == 0);
|
||||
}
|
||||
editBtn.setDisabled(s.getCount() != 1);
|
||||
if (conf.selected)
|
||||
conf.selected(s);
|
||||
|
@ -809,7 +817,59 @@ tvheadend.idnode_grid = function(panel, conf)
|
|||
});
|
||||
buttons.push(delBtn);
|
||||
}
|
||||
if (conf.add || conf.del)
|
||||
if (conf.move) {
|
||||
upBtn = new Ext.Toolbar.Button({
|
||||
tooltip : 'Move selected entries up',
|
||||
iconCls : 'moveup',
|
||||
text : 'Move Up',
|
||||
disabled : true,
|
||||
handler : function() {
|
||||
var r = select.getSelections();
|
||||
if (r && r.length > 0) {
|
||||
var uuids = []
|
||||
for ( var i = 0; i < r.length; i++ )
|
||||
uuids.push(r[i].id)
|
||||
Ext.Ajax.request({
|
||||
url : 'api/idnode/moveup',
|
||||
params : {
|
||||
uuid: Ext.encode(uuids)
|
||||
},
|
||||
success : function(d)
|
||||
{
|
||||
store.reload();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
buttons.push(upBtn);
|
||||
downBtn = new Ext.Toolbar.Button({
|
||||
tooltip : 'Move selected entries down',
|
||||
iconCls : 'movedown',
|
||||
text : 'Move Down',
|
||||
disabled : true,
|
||||
handler : function() {
|
||||
var r = select.getSelections();
|
||||
if (r && r.length > 0) {
|
||||
var uuids = []
|
||||
for ( var i = 0; i < r.length; i++ )
|
||||
uuids.push(r[i].id)
|
||||
Ext.Ajax.request({
|
||||
url : 'api/idnode/movedown',
|
||||
params : {
|
||||
uuid: Ext.encode(uuids)
|
||||
},
|
||||
success : function(d)
|
||||
{
|
||||
store.reload();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
buttons.push(downBtn);
|
||||
}
|
||||
if (conf.add || conf.del || conf.move)
|
||||
buttons.push('-');
|
||||
editBtn = new Ext.Toolbar.Button({
|
||||
tooltip : 'Edit selected entry',
|
||||
|
|
|
@ -99,15 +99,6 @@ tvheadend.show_service_streams = function ( data ) {
|
|||
var i, j;
|
||||
var html = '';
|
||||
|
||||
html += '<table style="font-size:8pt;font-family:mono;padding:2px"';
|
||||
html += '<tr>';
|
||||
html += '<th style="width:50px;font-weight:bold">Index</th>';
|
||||
html += '<th style="width:120px;font-weight:bold">PID</th>';
|
||||
html += '<th style="width:100px;font-weight:bold">Type</th>';
|
||||
html += '<th style="width:75px;font-weight:bold">Language</th>';
|
||||
html += '<th style="width:*;font-weight:bold">Details</th>';
|
||||
html += '</tr>';
|
||||
|
||||
function hexstr ( d ) {
|
||||
return ('0000' + d.toString(16)).slice(-4);
|
||||
}
|
||||
|
@ -126,8 +117,23 @@ tvheadend.show_service_streams = function ( data ) {
|
|||
return r;
|
||||
}
|
||||
|
||||
for (i = 0; i < data.streams.length; i++) {
|
||||
var s = data.streams[i];
|
||||
function header ( ) {
|
||||
html += '<table style="font-size:8pt;font-family:mono;padding:2px"';
|
||||
html += '<tr>';
|
||||
html += '<th style="width:50px;font-weight:bold">Index</th>';
|
||||
html += '<th style="width:120px;font-weight:bold">PID</th>';
|
||||
html += '<th style="width:100px;font-weight:bold">Type</th>';
|
||||
html += '<th style="width:75px;font-weight:bold">Language</th>';
|
||||
html += '<th style="width:*;font-weight:bold">Details</th>';
|
||||
html += '</tr>';
|
||||
|
||||
}
|
||||
|
||||
function single ( s ) {
|
||||
html += '<tr><td colspan="5">' + s + '</td></tr>';
|
||||
}
|
||||
|
||||
function stream ( s ) {
|
||||
var d = ' ';
|
||||
var p = '0x' + hexstr(s.pid) + ' / ' + fixstr(s.pid);
|
||||
|
||||
|
@ -146,16 +152,36 @@ tvheadend.show_service_streams = function ( data ) {
|
|||
}
|
||||
html += '<td>' + d + '</td>';
|
||||
html += '</tr>';
|
||||
}
|
||||
}
|
||||
|
||||
header();
|
||||
|
||||
if (data.streams.length) {
|
||||
for (i = 0; i < data.streams.length; i++)
|
||||
stream(data.streams[i]);
|
||||
} else
|
||||
single('None');
|
||||
|
||||
single(' ');
|
||||
single('<h3>After filtering and reordering (without PCR and PMT)</h3>');
|
||||
header();
|
||||
|
||||
if (data.fstreams.length)
|
||||
for (i = 0; i < data.fstreams.length; i++)
|
||||
stream(data.fstreams[i]);
|
||||
else
|
||||
single('<p>None</p>');
|
||||
|
||||
var win = new Ext.Window({
|
||||
title : 'Service details for ' + data.name,
|
||||
layout : 'fit',
|
||||
width : 650,
|
||||
height : 300,
|
||||
height : 400,
|
||||
plain : true,
|
||||
bodyStyle : 'padding: 5px',
|
||||
html : html
|
||||
html : html,
|
||||
autoScroll: true,
|
||||
autoShow: true
|
||||
});
|
||||
win.show();
|
||||
}
|
||||
|
|
|
@ -295,6 +295,17 @@ function accessUpdate(o) {
|
|||
tabs1.push(tvheadend.conf_csa);
|
||||
}
|
||||
|
||||
/* Stream Config */
|
||||
tvheadend.conf_stream = new Ext.TabPanel({
|
||||
activeTab: 0,
|
||||
autoScroll: true,
|
||||
title: 'Stream',
|
||||
iconCls: 'stream_config',
|
||||
items: []
|
||||
});
|
||||
tvheadend.esfilter_tab(tvheadend.conf_stream);
|
||||
tabs1.push(tvheadend.conf_stream);
|
||||
|
||||
/* Debug */
|
||||
tabs1.push(new tvheadend.tvhlog);
|
||||
|
||||
|
|
1
src/webui/static/icons/film_edit.png
Symbolic link
1
src/webui/static/icons/film_edit.png
Symbolic link
|
@ -0,0 +1 @@
|
|||
../../../../vendor/famfamsilk/film_edit.png
|
Loading…
Add table
Reference in a new issue