550 lines
13 KiB
C
550 lines
13 KiB
C
/*
|
|
* tvheadend, intial mux list
|
|
* Copyright (C) 2012 Adam Sutton
|
|
*
|
|
* 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 "dvb.h"
|
|
#include "filebundle.h"
|
|
#include "config.h"
|
|
#include "scanfile.h"
|
|
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#include <unistd.h>
|
|
#include <dirent.h>
|
|
#include <stdlib.h>
|
|
#include <libgen.h>
|
|
#include <stdio.h>
|
|
#include <errno.h>
|
|
#include <string.h>
|
|
#include <strings.h>
|
|
#include <ctype.h>
|
|
|
|
scanfile_region_list_t scanfile_regions_DVBC;
|
|
scanfile_region_list_t scanfile_regions_DVBT;
|
|
scanfile_region_list_t scanfile_regions_DVBS;
|
|
scanfile_region_list_t scanfile_regions_ATSC;
|
|
|
|
/* **************************************************************************
|
|
* Country codes
|
|
* *************************************************************************/
|
|
|
|
static const struct {
|
|
const char *code;
|
|
const char *name;
|
|
} tldlist[] = {
|
|
{"auto", "--Generic--"},
|
|
{"ad", "Andorra"},
|
|
{"ar", "Argentina"},
|
|
{"at", "Austria"},
|
|
{"au", "Australia"},
|
|
{"ax", "Aland Islands"},
|
|
{"be", "Belgium"},
|
|
{"bg", "Bulgaria"},
|
|
{"br", "Brazil"},
|
|
{"ca", "Canada"},
|
|
{"ch", "Switzerland"},
|
|
{"cz", "Czech Republic"},
|
|
{"de", "Germany"},
|
|
{"dk", "Denmark"},
|
|
{"es", "Spain"},
|
|
{"fi", "Finland"},
|
|
{"fr", "France"},
|
|
{"gr", "Greece"},
|
|
{"hk", "Hong Kong"},
|
|
{"hr", "Croatia"},
|
|
{"hu", "Hungary"},
|
|
{"ie", "Ireland"},
|
|
{"il", "Israel"},
|
|
{"ir", "Iran"},
|
|
{"is", "Iceland"},
|
|
{"it", "Italy"},
|
|
{"lt", "Lithuania"},
|
|
{"lu", "Luxembourg"},
|
|
{"lv", "Latvia"},
|
|
{"nl", "Netherlands"},
|
|
{"no", "Norway"},
|
|
{"nz", "New Zealand"},
|
|
{"pl", "Poland"},
|
|
{"pt", "Portugal"},
|
|
{"ro", "Romania"},
|
|
{"ru", "Russia"},
|
|
{"se", "Sweden"},
|
|
{"si", "Slovenia"},
|
|
{"sk", "Slovakia"},
|
|
{"tw", "Taiwan"},
|
|
{"ua", "Ukraine"},
|
|
{"uk", "United Kingdom"},
|
|
{"us", "United States"},
|
|
{"vn", "Vietnam"},
|
|
};
|
|
|
|
static const char *
|
|
tldcode2longname(const char *tld)
|
|
{
|
|
int i;
|
|
for(i = 0; i < sizeof(tldlist) / sizeof(tldlist[0]); i++)
|
|
if(!strcmp(tld, tldlist[i].code))
|
|
return tldlist[i].name;
|
|
return tld;
|
|
}
|
|
|
|
/* **************************************************************************
|
|
* Type specific parsers
|
|
* *************************************************************************/
|
|
|
|
static int
|
|
scanfile_load_atsc ( dvb_mux_conf_t *mux, const char *line )
|
|
{
|
|
char qam[20];
|
|
int r;
|
|
|
|
r = sscanf(line, "%u %s", &mux->dmc_fe_freq, qam);
|
|
if (r != 2) return 1;
|
|
mux->dmc_fe_type = DVB_TYPE_ATSC;
|
|
mux->dmc_fe_delsys = DVB_SYS_ATSC;
|
|
if ((mux->dmc_fe_modulation = dvb_str2qam(qam)) == -1) return 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
scanfile_load_dvbt ( dvb_mux_conf_t *mux, const char *line )
|
|
{
|
|
char bw[20], fec[20], fec2[20], qam[20], mode[20], guard[20], hier[20];
|
|
int r;
|
|
|
|
if (*line == '2') {
|
|
unsigned int plp_id, system_id;
|
|
r = sscanf(line+1, "%u %s", &plp_id, bw);
|
|
if (r == 2 && plp_id < 1000 && strstr(bw, "MHz") == 0) {
|
|
r = sscanf(line+1, "%u %u %u %10s %10s %10s %10s %10s %10s %10s",
|
|
&plp_id, &system_id, &mux->dmc_fe_freq, bw, fec, fec2, qam,
|
|
mode, guard, hier);
|
|
if(r != 10) return 1;
|
|
} else {
|
|
r = sscanf(line+1, "%u %10s %10s %10s %10s %10s %10s %10s %u",
|
|
&mux->dmc_fe_freq, bw, fec, fec2, qam,
|
|
mode, guard, hier, &plp_id);
|
|
if(r == 8) plp_id = 0; /* auto? */ else
|
|
if(r != 9) return 1;
|
|
}
|
|
mux->dmc_fe_delsys = DVB_SYS_DVBT2;
|
|
} else {
|
|
r = sscanf(line, "%u %10s %10s %10s %10s %10s %10s %10s",
|
|
&mux->dmc_fe_freq, bw, fec, fec2, qam, mode, guard, hier);
|
|
if(r != 8) return 1;
|
|
mux->dmc_fe_delsys = DVB_SYS_DVBT;
|
|
}
|
|
|
|
mux->dmc_fe_type = DVB_TYPE_T;
|
|
if ((mux->u.dmc_fe_ofdm.bandwidth = dvb_str2bw(bw)) == -1) return 1;
|
|
if ((mux->dmc_fe_modulation = dvb_str2qam(qam)) == -1) return 1;
|
|
if ((mux->u.dmc_fe_ofdm.code_rate_HP = dvb_str2fec(fec)) == -1) return 1;
|
|
if ((mux->u.dmc_fe_ofdm.code_rate_LP = dvb_str2fec(fec2)) == -1) return 1;
|
|
if ((mux->u.dmc_fe_ofdm.transmission_mode = dvb_str2mode(mode)) == -1) return 1;
|
|
if ((mux->u.dmc_fe_ofdm.guard_interval = dvb_str2guard(guard)) == -1) return 1;
|
|
if ((mux->u.dmc_fe_ofdm.hierarchy_information = dvb_str2hier(hier)) == -1) return 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
scanfile_load_dvbs ( dvb_mux_conf_t *mux, const char *line )
|
|
{
|
|
char pol[20], fec[20], qam[20], rolloff[20];
|
|
int r, v2 = 0;
|
|
|
|
if (*line == '2') {
|
|
v2 = 2;
|
|
line++;
|
|
}
|
|
|
|
r = sscanf(line, "%u %s %u %s %s %s",
|
|
&mux->dmc_fe_freq, pol, &mux->u.dmc_fe_qpsk.symbol_rate,
|
|
fec, rolloff, qam);
|
|
if (r < (4+v2)) return 1;
|
|
|
|
mux->dmc_fe_type = DVB_TYPE_S;
|
|
if ((mux->u.dmc_fe_qpsk.polarisation = dvb_str2pol(pol)) == -1) return 1;
|
|
if ((mux->u.dmc_fe_qpsk.fec_inner = dvb_str2fec(fec)) == -1) return 1;
|
|
if (v2) {
|
|
mux->dmc_fe_delsys = DVB_SYS_DVBS2;
|
|
if ((mux->dmc_fe_rolloff = dvb_str2rolloff(rolloff)) == -1) return 1;
|
|
if ((mux->dmc_fe_modulation = dvb_str2qam(qam)) == -1) return 1;
|
|
} else {
|
|
mux->dmc_fe_delsys = DVB_SYS_DVBS;
|
|
mux->dmc_fe_rolloff = DVB_ROLLOFF_35;
|
|
mux->dmc_fe_modulation = DVB_MOD_QPSK;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
scanfile_load_dvbc ( dvb_mux_conf_t *mux, const char *line )
|
|
{
|
|
char fec[20], qam[20];
|
|
int r;
|
|
|
|
r = sscanf(line, "%u %u %s %s",
|
|
&mux->dmc_fe_freq, &mux->u.dmc_fe_qam.symbol_rate, fec, qam);
|
|
if(r != 4) return 1;
|
|
|
|
mux->dmc_fe_type = DVB_TYPE_C;
|
|
mux->dmc_fe_delsys = DVB_SYS_DVBC_ANNEX_A;
|
|
if ((mux->u.dmc_fe_qam.fec_inner = dvb_str2fec(fec)) == -1) return 1;
|
|
if ((mux->dmc_fe_modulation = dvb_str2qam(qam)) == -1) return 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/* **************************************************************************
|
|
* File processing
|
|
* *************************************************************************/
|
|
|
|
/*
|
|
* Sorting
|
|
*/
|
|
static int
|
|
scanfile_network_dvbs_pos(char *n, int *rpos)
|
|
{
|
|
int len = strlen(n), pos = len - 1, frac = 0;
|
|
|
|
if (len > 0 && n[pos] != 'W' && n[pos] != 'E')
|
|
return 0;
|
|
pos--;
|
|
while (pos >= 0 && isdigit(n[pos]))
|
|
pos--;
|
|
*rpos = 0;
|
|
if (pos >= 0 && n[pos] == '.') {
|
|
pos--;
|
|
while (pos >= 0 && isdigit(n[pos]))
|
|
pos--;
|
|
if (len - pos < 3)
|
|
return 0;
|
|
sscanf(n + pos + 1, "%i.%i", rpos, &frac);
|
|
} else {
|
|
if (len - pos < 2)
|
|
return 0;
|
|
sscanf(n + pos + 1, "%i", rpos);
|
|
}
|
|
n[pos] = '\0';
|
|
*rpos *= 10;
|
|
*rpos += frac;
|
|
if (n[len-1] == 'W')
|
|
*rpos = -*rpos;
|
|
return 1;
|
|
}
|
|
|
|
static int
|
|
scanfile_network_cmp
|
|
( scanfile_network_t *a, scanfile_network_t *b )
|
|
{
|
|
return strcmp(a->sfn_name, b->sfn_name);
|
|
}
|
|
static int
|
|
scanfile_region_cmp
|
|
( scanfile_region_t *a, scanfile_region_t *b )
|
|
{
|
|
return strcmp(a->sfr_name, b->sfr_name);
|
|
}
|
|
|
|
/*
|
|
* Create/Find region entry
|
|
*
|
|
* TODO: not sure why I didn't use RB here!
|
|
*/
|
|
static scanfile_region_t *
|
|
scanfile_region_create
|
|
( const char *type, const char *id, const char *desc )
|
|
{
|
|
scanfile_region_t *reg;
|
|
scanfile_region_list_t *list = NULL;
|
|
if (!strcmp(type, "dvb-s")) list = &scanfile_regions_DVBS;
|
|
else if (!strcmp(type, "dvb-t")) list = &scanfile_regions_DVBT;
|
|
else if (!strcmp(type, "dvb-c")) list = &scanfile_regions_DVBC;
|
|
else if (!strcmp(type, "atsc")) list = &scanfile_regions_ATSC;
|
|
if (!list) return NULL;
|
|
|
|
LIST_FOREACH(reg, list, sfr_link) {
|
|
if (!strcmp(reg->sfr_id, id)) break;
|
|
}
|
|
|
|
if (!reg) {
|
|
tvhtrace("scanfile", "%s region %s created", type, id);
|
|
reg = calloc(1, sizeof(scanfile_region_t));
|
|
reg->sfr_id = strdup(id);
|
|
reg->sfr_name = strdup(desc);
|
|
LIST_INSERT_SORTED(list, reg, sfr_link, scanfile_region_cmp);
|
|
}
|
|
|
|
return reg;
|
|
}
|
|
|
|
/*
|
|
* Process mux entry
|
|
*/
|
|
static void
|
|
scanfile_load_one ( scanfile_network_t *net, const char *line )
|
|
{
|
|
int r = 1;
|
|
dvb_mux_conf_t *mux = calloc(1, sizeof(dvb_mux_conf_t));
|
|
|
|
switch (line[0]) {
|
|
case 'A':
|
|
r = scanfile_load_atsc(mux, line+1);
|
|
break;
|
|
case 'T':
|
|
r = scanfile_load_dvbt(mux, line+1);
|
|
break;
|
|
case 'S':
|
|
r = scanfile_load_dvbs(mux, line+1);
|
|
break;
|
|
case 'C':
|
|
r = scanfile_load_dvbc(mux, line+1);
|
|
break;
|
|
}
|
|
|
|
tvhtrace("scanfile", "[%s] %s", line, r ? "FAIL" : "OK");
|
|
if (r) {
|
|
free(mux);
|
|
} else {
|
|
LIST_INSERT_HEAD(&net->sfn_muxes, mux, dmc_link);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Process a file
|
|
*/
|
|
static void
|
|
scanfile_load_file
|
|
( const char *type, fb_dir *dir, const char *name )
|
|
{
|
|
int i, opos;
|
|
fb_file *fp;
|
|
scanfile_region_t *reg = NULL;
|
|
scanfile_network_t *net;
|
|
char *str;
|
|
char buf[256], buf2[256], buf3[256];
|
|
tvhtrace("scanfile", "load file %s", name);
|
|
|
|
fp = fb_open2(dir, name, 1, 0);
|
|
if (!fp) return;
|
|
|
|
/* Region */
|
|
strncpy(buf, name, sizeof(buf));
|
|
if (!strcmp(type, "dvb-s")) {
|
|
reg = scanfile_region_create(type, "geo", "Geo-synchronous Orbit");
|
|
} else {
|
|
str = buf;
|
|
while (*str) {
|
|
if (*str == '-') {
|
|
*str = '\0';
|
|
reg = scanfile_region_create(type, buf, tldcode2longname(buf));
|
|
*str = '-';
|
|
break;
|
|
}
|
|
str++;
|
|
}
|
|
}
|
|
if (!reg) {
|
|
fb_close(fp);
|
|
return;
|
|
}
|
|
|
|
/* Network */
|
|
str = buf;
|
|
while (*str) {
|
|
if (!isprint(*str)) *str = '_';
|
|
str++;
|
|
}
|
|
*str = '\0';
|
|
if (!strcmp(type, "dvb-s") && scanfile_network_dvbs_pos(buf, &opos)) {
|
|
snprintf(buf3, sizeof(buf3), "%c%3i.%i%c:%s", opos < 0 ? '<' : '>',
|
|
abs(opos) / 10, abs(opos) % 10,
|
|
opos < 0 ? 'W' :'E', buf);
|
|
strcpy(buf, buf3);
|
|
}
|
|
snprintf(buf2, sizeof(buf2), "%s_%s", type, buf);
|
|
net = calloc(1, sizeof(scanfile_network_t));
|
|
net->sfn_id = strdup(buf2);
|
|
net->sfn_name = strdup(buf);
|
|
LIST_INSERT_SORTED(®->sfr_networks, net, sfn_link, scanfile_network_cmp);
|
|
|
|
/* Process file */
|
|
while (!fb_eof(fp)) {
|
|
|
|
/* Get line */
|
|
memset(buf, 0, sizeof(buf));
|
|
if (!fb_gets(fp, buf, sizeof(buf) - 1)) break;
|
|
i = 0;
|
|
while (buf[i]) {
|
|
if (buf[i] == '#')
|
|
buf[i] = '\0';
|
|
else
|
|
i++;
|
|
}
|
|
while (i > 0 && buf[i-1] < 32) buf[--i] = 0;
|
|
|
|
/* Process mux */
|
|
switch (*buf) {
|
|
case 'A':
|
|
case 'C':
|
|
case 'T':
|
|
case 'S':
|
|
scanfile_load_one(net, buf);
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
fb_close(fp);
|
|
}
|
|
|
|
/*
|
|
* Process directory
|
|
*
|
|
* Note: should we follow symlinks?
|
|
*/
|
|
static void
|
|
scanfile_load_dir
|
|
( const char *path, const char *type, int lvl )
|
|
{
|
|
char p[256];
|
|
fb_dir *dir;
|
|
fb_dirent *de;
|
|
tvhtrace("scanfile", "load dir %s", path);
|
|
|
|
if (lvl >= 3) return;
|
|
if (!(dir = fb_opendir(path))) return;
|
|
lvl++;
|
|
|
|
while ((de = fb_readdir(dir))) {
|
|
if (*de->name == '.') continue;
|
|
if (de->type == FB_DIR) {
|
|
snprintf(p, sizeof(p), "%s/%s", path, de->name);
|
|
scanfile_load_dir(p, de->name, lvl+1);
|
|
} else if (type) {
|
|
scanfile_load_file(type, dir, de->name);
|
|
}
|
|
}
|
|
|
|
fb_closedir(dir);
|
|
}
|
|
|
|
/*
|
|
* Initialise the mux list
|
|
*/
|
|
void
|
|
scanfile_init ( void )
|
|
{
|
|
const char *path = config_get_muxconfpath();
|
|
if (!path || !*path)
|
|
#if ENABLE_DVBSCAN
|
|
path = "data/dvb-scan";
|
|
#else
|
|
path = "/usr/share/dvb";
|
|
#endif
|
|
scanfile_load_dir(path, NULL, 0);
|
|
}
|
|
|
|
/*
|
|
* Destroy the mux list
|
|
*/
|
|
static void
|
|
scanfile_done_region( scanfile_region_list_t *list )
|
|
{
|
|
scanfile_region_t *reg;
|
|
scanfile_network_t *net;
|
|
dvb_mux_conf_t *mux;
|
|
|
|
while ((reg = LIST_FIRST(list)) != NULL) {
|
|
LIST_REMOVE(reg, sfr_link);
|
|
while ((net = LIST_FIRST(®->sfr_networks)) != NULL) {
|
|
LIST_REMOVE(net, sfn_link);
|
|
while ((mux = LIST_FIRST(&net->sfn_muxes)) != NULL) {
|
|
LIST_REMOVE(mux, dmc_link);
|
|
free(mux);
|
|
}
|
|
free((void *)net->sfn_id);
|
|
free((void *)net->sfn_name);
|
|
free(net);
|
|
}
|
|
free((void *)reg->sfr_id);
|
|
free((void *)reg->sfr_name);
|
|
free(reg);
|
|
}
|
|
}
|
|
|
|
void
|
|
scanfile_done ( void )
|
|
{
|
|
scanfile_done_region(&scanfile_regions_DVBS);
|
|
scanfile_done_region(&scanfile_regions_DVBT);
|
|
scanfile_done_region(&scanfile_regions_DVBC);
|
|
scanfile_done_region(&scanfile_regions_ATSC);
|
|
}
|
|
|
|
/*
|
|
* Find scanfile
|
|
*/
|
|
scanfile_network_t *
|
|
scanfile_find ( const char *id )
|
|
{
|
|
char *tok, *s = NULL, *tmp;
|
|
scanfile_region_t *r = NULL;
|
|
scanfile_network_t *n = NULL;
|
|
scanfile_region_list_t *l;
|
|
tmp = strdup(id);
|
|
|
|
/* Type */
|
|
if (!(tok = strtok_r(tmp, "/", &s)))
|
|
goto fail;
|
|
if (!strcasecmp(tok, "dvbt"))
|
|
l = &scanfile_regions_DVBT;
|
|
else if (!strcasecmp(tok, "dvbc"))
|
|
l = &scanfile_regions_DVBC;
|
|
else if (!strcasecmp(tok, "dvbs"))
|
|
l = &scanfile_regions_DVBS;
|
|
else if (!strcasecmp(tok, "atsc"))
|
|
l = &scanfile_regions_ATSC;
|
|
else
|
|
goto fail;
|
|
|
|
/* Region */
|
|
if (!(tok = strtok_r(NULL, "/", &s)))
|
|
goto fail;
|
|
LIST_FOREACH(r, l, sfr_link)
|
|
if (!strcmp(r->sfr_id, tok))
|
|
break;
|
|
if (!r) goto fail;
|
|
|
|
/* Network */
|
|
if (!(tok = strtok_r(NULL, "/", &s)))
|
|
goto fail;
|
|
LIST_FOREACH(n, &r->sfr_networks, sfn_link)
|
|
if (!strcmp(n->sfn_id, tok))
|
|
break;
|
|
|
|
free(tmp);
|
|
return n;
|
|
|
|
fail:
|
|
free(tmp);
|
|
return NULL;
|
|
}
|