From dab012c11f59e84c0b68ac8c6ff7f3111c593c90 Mon Sep 17 00:00:00 2001 From: randomdan Date: Thu, 30 May 2013 11:10:49 +0000 Subject: Tidy up, extend ICE interface --- p2pvr/cron/epg.xml | 35 +++ p2pvr/cron/importEpg.xml | 67 ++++++ p2pvr/cron/importSchedule.xml | 67 ------ p2pvr/cron/schedule.xml | 35 --- p2pvr/ice/Jamfile.jam | 4 +- p2pvr/ice/p2pvr.ice | 56 +++++ p2pvr/ice/p2tv.ice | 9 - p2pvr/scanner/Jamfile.jam | 20 +- p2pvr/scanner/eitRows.cpp | 528 ------------------------------------------ p2pvr/scanner/eitRows.h | 52 ----- p2pvr/scanner/epgRows.cpp | 528 ++++++++++++++++++++++++++++++++++++++++++ p2pvr/scanner/epgRows.h | 52 +++++ 12 files changed, 747 insertions(+), 706 deletions(-) create mode 100644 p2pvr/cron/epg.xml create mode 100644 p2pvr/cron/importEpg.xml delete mode 100644 p2pvr/cron/importSchedule.xml delete mode 100644 p2pvr/cron/schedule.xml create mode 100644 p2pvr/ice/p2pvr.ice delete mode 100644 p2pvr/ice/p2tv.ice delete mode 100644 p2pvr/scanner/eitRows.cpp delete mode 100644 p2pvr/scanner/eitRows.h create mode 100644 p2pvr/scanner/epgRows.cpp create mode 100644 p2pvr/scanner/epgRows.h diff --git a/p2pvr/cron/epg.xml b/p2pvr/cron/epg.xml new file mode 100644 index 0000000..8fa9828 --- /dev/null +++ b/p2pvr/cron/epg.xml @@ -0,0 +1,35 @@ + + + + + + + + <titleLang source="parent" attribute="titleLang" depth="1" /> + <subtitle source="parent" attribute="subtitle" depth="1" /> + <serviceID source="parent" attribute="serviceID" depth="1" /> + <descLang source="parent" attribute="descLang" depth="1" /> + <desc1 source="parent" attribute="desc1" depth="1" /> + <desc2 source="parent" attribute="desc2" depth="1" /> + <desc3 source="parent" attribute="desc3" depth="1" /> + <videoAspect source="parent" attribute="videoAspect" depth="1" /> + <videoFrameRate source="parent" attribute="videoFrameRate" depth="1" /> + <videoHD source="parent" attribute="videoHD" depth="1" /> + <audioChannels source="parent" attribute="audioChannels" depth="1" /> + <language source="parent" attribute="language" depth="1" /> + <teletextSubtitleLang source="parent" attribute="teletextSubtitleLang" depth="1" /> + <category source="parent" attribute="category" depth="1" /> + <dvbRating source="parent" attribute="dvbRating" depth="1" /> + <contentItemID source="parent" attribute="contentItemID" depth="1" /> + <contentRecommendation source="parent" attribute="contentRecommendation" depth="1" /> + <contentSeriesID source="parent" attribute="contentSeriesID" depth="1" /> + <startTime source="parent" attribute="startTime" depth="1" /> + <stopTime source="parent" attribute="stopTime" depth="1" /> + <eventID source="parent" attribute="eventID" depth="1" /> + <episode source="parent" attribute="episode" depth="1" /> + <episodes source="parent" attribute="episodes" depth="1" /> + <year source="parent" attribute="year" depth="1" /> + <flags source="parent" attribute="flags" depth="1" /> + </columns> + </p2:view> +</block> diff --git a/p2pvr/cron/importEpg.xml b/p2pvr/cron/importEpg.xml new file mode 100644 index 0000000..c3858f5 --- /dev/null +++ b/p2pvr/cron/importEpg.xml @@ -0,0 +1,67 @@ +<?xml version="1.0"?> +<block name="importSchedule" xmlns:p2="http://project2.randomdan.homeip.net"> + <p2:library path="libp2pvr-scan-p2.so" /> + <p2:epgrows name="schedule" demux="/dev/dvb/adapter0/demux0" /> + <p2:sqlmerge name="mergeSchedule" datasource="postgres" targettable="programs"> + <p2:iterate name="programs" source="schedule"> + <p2:sqlmergeinsert name="insertProgram"> + <parameters> + <title source="parent" attribute="title" depth="1" /> + <titleLang source="parent" attribute="titleLang" depth="1" /> + <subtitle source="parent" attribute="subtitle" depth="1" /> + <serviceID source="parent" attribute="serviceID" depth="1" /> + <descLang source="parent" attribute="descLang" depth="1" /> + <desc1 source="parent" attribute="desc1" depth="1" /> + <desc2 source="parent" attribute="desc2" depth="1" /> + <desc3 source="parent" attribute="desc3" depth="1" /> + <videoAspect source="parent" attribute="videoAspect" depth="1" /> + <videoFrameRate source="parent" attribute="videoFrameRate" depth="1" /> + <videoHD source="parent" attribute="videoHD" depth="1" /> + <audioChannels source="parent" attribute="audioChannels" depth="1" /> + <language source="parent" attribute="language" depth="1" /> + <teletextSubtitleLang source="parent" attribute="teletextSubtitleLang" depth="1" /> + <category source="parent" attribute="category" depth="1" /> + <dvbRating source="parent" attribute="dvbRating" depth="1" /> + <contentItemID source="parent" attribute="contentItemID" depth="1" /> + <contentRecommendation source="parent" attribute="contentRecommendation" depth="1" /> + <contentSeriesID source="parent" attribute="contentSeriesID" depth="1" /> + <startTime source="parent" attribute="startTime" depth="1" /> + <stopTime source="parent" attribute="stopTime" depth="1" /> + <eventID source="parent" attribute="eventID" depth="1" /> + <episode source="parent" attribute="episode" depth="1" /> + <episodes source="parent" attribute="episodes" depth="1" /> + <year source="parent" attribute="year" depth="1" /> + <flags source="parent" attribute="flags" depth="1" /> + </parameters> + </p2:sqlmergeinsert> + </p2:iterate> + <columns> + <title /> + <titleLang /> + <subtitle /> + <serviceID key="true" /> + <eventID key="true" /> + <descLang /> + <desc1 /> + <desc2 /> + <desc3 /> + <videoAspect /> + <videoFrameRate /> + <videoHD /> + <audioChannels /> + <language /> + <teletextSubtitleLang /> + <category /> + <dvbRating /> + <contentItemID /> + <contentRecommendation /> + <contentSeriesID /> + <startTime /> + <stopTime /> + <episode /> + <episodes /> + <year /> + <flags /> + </columns> + </p2:sqlmerge> +</block> diff --git a/p2pvr/cron/importSchedule.xml b/p2pvr/cron/importSchedule.xml deleted file mode 100644 index 2cc0765..0000000 --- a/p2pvr/cron/importSchedule.xml +++ /dev/null @@ -1,67 +0,0 @@ -<?xml version="1.0"?> -<block name="importSchedule" xmlns:p2="http://project2.randomdan.homeip.net"> - <p2:library path="libp2pvr-scan-p2.so" /> - <p2:eitrows name="schedule" demux="/dev/dvb/adapter0/demux0" /> - <p2:sqlmerge name="mergeSchedule" datasource="postgres" targettable="programs"> - <p2:iterate name="programs" source="schedule"> - <p2:sqlmergeinsert name="insertProgram"> - <parameters> - <title source="parent" attribute="title" depth="1" /> - <titleLang source="parent" attribute="titleLang" depth="1" /> - <subtitle source="parent" attribute="subtitle" depth="1" /> - <serviceID source="parent" attribute="serviceID" depth="1" /> - <descLang source="parent" attribute="descLang" depth="1" /> - <desc1 source="parent" attribute="desc1" depth="1" /> - <desc2 source="parent" attribute="desc2" depth="1" /> - <desc3 source="parent" attribute="desc3" depth="1" /> - <videoAspect source="parent" attribute="videoAspect" depth="1" /> - <videoFrameRate source="parent" attribute="videoFrameRate" depth="1" /> - <videoHD source="parent" attribute="videoHD" depth="1" /> - <audioChannels source="parent" attribute="audioChannels" depth="1" /> - <language source="parent" attribute="language" depth="1" /> - <teletextSubtitleLang source="parent" attribute="teletextSubtitleLang" depth="1" /> - <category source="parent" attribute="category" depth="1" /> - <dvbRating source="parent" attribute="dvbRating" depth="1" /> - <contentItemID source="parent" attribute="contentItemID" depth="1" /> - <contentRecommendation source="parent" attribute="contentRecommendation" depth="1" /> - <contentSeriesID source="parent" attribute="contentSeriesID" depth="1" /> - <startTime source="parent" attribute="startTime" depth="1" /> - <stopTime source="parent" attribute="stopTime" depth="1" /> - <eventID source="parent" attribute="eventID" depth="1" /> - <episode source="parent" attribute="episode" depth="1" /> - <episodes source="parent" attribute="episodes" depth="1" /> - <year source="parent" attribute="year" depth="1" /> - <flags source="parent" attribute="flags" depth="1" /> - </parameters> - </p2:sqlmergeinsert> - </p2:iterate> - <columns> - <title /> - <titleLang /> - <subtitle /> - <serviceID key="true" /> - <eventID key="true" /> - <descLang /> - <desc1 /> - <desc2 /> - <desc3 /> - <videoAspect /> - <videoFrameRate /> - <videoHD /> - <audioChannels /> - <language /> - <teletextSubtitleLang /> - <category /> - <dvbRating /> - <contentItemID /> - <contentRecommendation /> - <contentSeriesID /> - <startTime /> - <stopTime /> - <episode /> - <episodes /> - <year /> - <flags /> - </columns> - </p2:sqlmerge> -</block> diff --git a/p2pvr/cron/schedule.xml b/p2pvr/cron/schedule.xml deleted file mode 100644 index 8d76e99..0000000 --- a/p2pvr/cron/schedule.xml +++ /dev/null @@ -1,35 +0,0 @@ -<?xml version="1.0"?> -<block name="importSchedule" xmlns:p2="http://project2.randomdan.homeip.net"> - <p2:library path="libp2pvr-scan-p2.so" /> - <p2:eitrows name="schedule" demux="/dev/dvb/adapter0/demux0" /> - <p2:view name="showSchedule" source="schedule" rootname="sched" recordname="show"> - <columns> - <title source="parent" attribute="title" depth="1" /> - <titleLang source="parent" attribute="titleLang" depth="1" /> - <subtitle source="parent" attribute="subtitle" depth="1" /> - <serviceID source="parent" attribute="serviceID" depth="1" /> - <descLang source="parent" attribute="descLang" depth="1" /> - <desc1 source="parent" attribute="desc1" depth="1" /> - <desc2 source="parent" attribute="desc2" depth="1" /> - <desc3 source="parent" attribute="desc3" depth="1" /> - <videoAspect source="parent" attribute="videoAspect" depth="1" /> - <videoFrameRate source="parent" attribute="videoFrameRate" depth="1" /> - <videoHD source="parent" attribute="videoHD" depth="1" /> - <audioChannels source="parent" attribute="audioChannels" depth="1" /> - <language source="parent" attribute="language" depth="1" /> - <teletextSubtitleLang source="parent" attribute="teletextSubtitleLang" depth="1" /> - <category source="parent" attribute="category" depth="1" /> - <dvbRating source="parent" attribute="dvbRating" depth="1" /> - <contentItemID source="parent" attribute="contentItemID" depth="1" /> - <contentRecommendation source="parent" attribute="contentRecommendation" depth="1" /> - <contentSeriesID source="parent" attribute="contentSeriesID" depth="1" /> - <startTime source="parent" attribute="startTime" depth="1" /> - <stopTime source="parent" attribute="stopTime" depth="1" /> - <eventID source="parent" attribute="eventID" depth="1" /> - <episode source="parent" attribute="episode" depth="1" /> - <episodes source="parent" attribute="episodes" depth="1" /> - <year source="parent" attribute="year" depth="1" /> - <flags source="parent" attribute="flags" depth="1" /> - </columns> - </p2:view> -</block> diff --git a/p2pvr/ice/Jamfile.jam b/p2pvr/ice/Jamfile.jam index e77a3ae..9055b2e 100644 --- a/p2pvr/ice/Jamfile.jam +++ b/p2pvr/ice/Jamfile.jam @@ -4,8 +4,8 @@ lib Ice : : <name>Ice ; lib IceUtil : : <name>IceUtil ; lib pthread : : <name>pthread ; -lib p2tvice : - p2tv.ice : +lib p2pvrice : + [ glob *.ice ] : <library>Ice <library>IceUtil <library>pthread diff --git a/p2pvr/ice/p2pvr.ice b/p2pvr/ice/p2pvr.ice new file mode 100644 index 0000000..aaa01bf --- /dev/null +++ b/p2pvr/ice/p2pvr.ice @@ -0,0 +1,56 @@ +module P2TV { + struct Recording { + int recordingId; + string Title; + string Subtitle; + string Description; + long startTime; + long endTime; + long fileSize; + int serviceId; + }; + sequence<Recording> RecordingList; + + struct Schedule { + int scheduleId; + }; + sequence<Schedule> ScheduleList; + + struct Program { + int programId; + int serviceId; + int eventId; + string Title; + string Subtitle; + string Description; + long startTime; + long endTime; + }; + sequence<Program> ProgramList; + + struct Service { + int serviceId; + string name; + string authority; + }; + sequence<Service> ServiceList; + + interface Recordings { + idempotent void DeleteRecording(int recordingId); + idempotent RecordingList GetRecordings(); + }; + + interface Schedules { + idempotent void DeleteSchedule(int scheduleId); + idempotent ScheduleList GetSchedules(); + idempotent int UpdateSchedule(Schedule newSchedule); + idempotent void DoReschedule(); + }; + + interface EPG { + idempotent ServiceList GetServices(); + idempotent ProgramList GetPrograms(int serviceId, int limit); + idempotent ProgramList GetNowAndNext(); + }; +}; + diff --git a/p2pvr/ice/p2tv.ice b/p2pvr/ice/p2tv.ice deleted file mode 100644 index 7602ffa..0000000 --- a/p2pvr/ice/p2tv.ice +++ /dev/null @@ -1,9 +0,0 @@ -module P2TV { - interface Recordings { - void DeleteRecording(int recordingId); - }; - interface Schedules { - void DoReschedule(); - }; -}; - diff --git a/p2pvr/scanner/Jamfile.jam b/p2pvr/scanner/Jamfile.jam index e7af082..768e86c 100644 --- a/p2pvr/scanner/Jamfile.jam +++ b/p2pvr/scanner/Jamfile.jam @@ -1,23 +1,17 @@ -lib boost_regex : : <name>boost_regex ; - +alias glibmm : : : : + <cflags>"`pkg-config --cflags glibmm-2.4`" + <linkflags>"`pkg-config --libs glibmm-2.4`" + ; project : requirements <variant>debug:<linkflags>-Wl,-z,defs <cflags>"-W -Wall -Werror -Wwrite-strings" ; -# Scanner - the common part implementing the ICE interface -lib p2pvr-scan-ice : icescan.cpp scanner.ice : <library>p2pvr-scan-p2 ; - # Scanner - t -lib p2pvr-scan-p2 : - eitRows.cpp - serviceRows.cpp - dvbSiReaderHelper.cpp +lib p2pvrscanner : + [ glob *.cpp ] : <library>../../project2/common//p2common + <library>glibmm ; -# ScannerICE - the ICE test app -#exe scanice : ice_scan.cpp : <library>p2pvr-scan ; - -explicit p2pvr-scan-ice ; diff --git a/p2pvr/scanner/eitRows.cpp b/p2pvr/scanner/eitRows.cpp deleted file mode 100644 index cb69882..0000000 --- a/p2pvr/scanner/eitRows.cpp +++ /dev/null @@ -1,528 +0,0 @@ -/* - * tv_grab_dvb - dump dvb epg info in xmltv - * Version 0.2 - 20/04/2004 - First Public Release - * - * Copyright (C) 2004 Mark Bryars <dvb at darkskiez d0t co d0t uk> - * - * DVB code Mercilessly ripped off from dvddate - * dvbdate Copyright (C) Laurence Culhane 2002 <dvbdate@holmes.demon.co.uk> - * - * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - * Or, point your browser to http://www.gnu.org/copyleft/gpl.html - */ - -#include <stdio.h> -#include <stdlib.h> -#include <unistd.h> -#include <string.h> -#include <sys/ioctl.h> -#include <errno.h> -#include <stdint.h> -#include <time.h> -#include <glibmm/regex.h> -#include <boost/regex.hpp> -#include <boost/bind.hpp> -#include <boost/date_time/gregorian_calendar.hpp> -#include <boost/algorithm/string/predicate.hpp> -#include <boost/algorithm/string/trim.hpp> -#include <boost/foreach.hpp> -#include <boost/lexical_cast.hpp> - -#include <linux/dvb/dmx.h> -#include "si_tables.h" - -#include "rowProcessor.h" -#include "eitRows.h" -#include <boost/tuple/tuple_comparison.hpp> - -struct EitProgram { - VariableType serviceID; - VariableType eventID; - VariableType title; - VariableType titleLang; - VariableType subtitle; - VariableType descLang; - VariableType desc1; - VariableType desc2; - VariableType desc3; - VariableType videoAspect; - VariableType videoFrameRate; - VariableType videoHD; - VariableType audioChannels; - VariableType language; - VariableType teletextSubtitleLang; - VariableType category; - VariableType dvbRating; - VariableType contentItemID; - VariableType contentSeriesID; - VariableType contentRecommendation; - VariableType startTime; - VariableType stopTime; - VariableType episode; - VariableType episodes; - VariableType year; - VariableType flags; -}; - -static Glib::ustring title("title"); -template <class C, class M> -const M & -getMember(M C::*t, C * p) { - return p->*t; -} -SimpleMessageException(NoSuchAttribute); - -void -EitRows::loadComplete(const CommonObjects *) -{ -} - -EitRowState::EitRowState() : - current(NULL) -{ -} - -const Columns & -EitRowState::getColumns() const -{ - return columns; -} - -#define returnAttr(name) if (attrName == #name) return boost::bind(getMember<EitProgram, VariableType>, &EitProgram::name, boost::ref(current)) -RowState::RowAttribute -EitRowState::resolveAttr(const Glib::ustring & attrName) const { - returnAttr(serviceID); - returnAttr(eventID); - returnAttr(title); - returnAttr(titleLang); - returnAttr(subtitle); - returnAttr(descLang); - returnAttr(desc1); - returnAttr(desc2); - returnAttr(desc3); - returnAttr(videoAspect); - returnAttr(videoFrameRate); - returnAttr(videoHD); - returnAttr(audioChannels); - returnAttr(language); - returnAttr(teletextSubtitleLang); - returnAttr(category); - returnAttr(dvbRating); - returnAttr(contentItemID); - returnAttr(contentSeriesID); - returnAttr(contentRecommendation); - returnAttr(startTime); - returnAttr(stopTime); - returnAttr(episode); - returnAttr(episodes); - returnAttr(year); - returnAttr(flags); - throw NoSuchAttribute(attrName); -} - -DECLARE_LOADER("eitrows", EitRows); - -EitRows::EitRows(const ScriptNodePtr p) : - RowSet(p), - DvbSiReaderHelper(p) -{ -} - -EitRows::~EitRows() -{ -} - -static int time_offset = 0; -static int chan_filter = 0; -static int chan_filter_mask = 0; -static Glib::RefPtr<Glib::Regex> episodeRegex = Glib::Regex::create("[ (]+(?:\\w+ )?([0-9]+)(?: of |/)([0-9]+)[.)]+"); -static Glib::RefPtr<Glib::Regex> yearRegex = Glib::Regex::create("\\(([0-9]{4})[ )]+"); -static Glib::RefPtr<Glib::Regex> flagsRegex = Glib::Regex::create("[ []+([A-Z,]+)\\]"); - -void -EitRowState::parseEventDescription(const u_char * data, EitProgram * current) const { - assert(GetDescriptorTag(data) == 0x4D); - const struct descr_short_event *evtdesc = reinterpret_cast<const struct descr_short_event *>(data); - - size_t evtlen = evtdesc->event_name_length; - StrPtr title, subtitle, desc; - if (evtlen) { - current->titleLang = Glib::ustring((const char *)&evtdesc->lang_code1, 3); - title = convert((const char *)evtdesc->data, evtlen); - } - - size_t dsclen = evtdesc->data[evtlen]; - if (dsclen) { - subtitle = convert((const char *)evtdesc->data + evtlen + 1, dsclen); - } - if (subtitle) { - Glib::MatchInfo matches; - if (episodeRegex->match(*subtitle, matches)) { - current->episode = boost::lexical_cast<int>(matches.fetch(1)); - current->episodes = boost::lexical_cast<int>(matches.fetch(2)); - *subtitle = episodeRegex->replace_literal(*subtitle, 0, "", Glib::REGEX_MATCH_NOTEMPTY); - } - if (yearRegex->match(*subtitle, matches)) { - current->year = boost::lexical_cast<int>(matches.fetch(1)); - *subtitle = yearRegex->replace_literal(*subtitle, 0, "", Glib::REGEX_MATCH_NOTEMPTY); - } - if (flagsRegex->match(*subtitle, matches)) { - current->flags = matches.fetch(1); - *subtitle = yearRegex->replace_literal(*subtitle, 0, "", Glib::REGEX_MATCH_NOTEMPTY); - } - } - if (title && subtitle) { - if (boost::algorithm::ends_with(*title, "...") && boost::algorithm::starts_with(*subtitle, "...")) { - title->resize(title->length() - 3); - *title += " "; - size_t dot = subtitle->find('.', 4); - if (dot == Glib::ustring::npos) { - title->append(*subtitle, 3, subtitle->length() - 3); - } - else { - title->append(*subtitle, 3, dot - 3); - subtitle->erase(0, dot + 2); - } - } - size_t colon = subtitle->find(':'); - if (colon != Glib::ustring::npos) { - desc = StrPtr(new Glib::ustring(*subtitle, colon + 1, subtitle->length() - colon)); - subtitle->resize(colon); - } - else { - colon = title->find(':'); - desc = subtitle; - if (colon != Glib::ustring::npos) { - subtitle = StrPtr(new Glib::ustring(*title, colon + 1, title->length() - colon)); - title->resize(colon); - } - else { - subtitle.reset(); - } - } - } - if (title) { - boost::algorithm::trim_if(*title, isspace); - current->title = *title; - } - if (subtitle) { - boost::algorithm::trim_if(*subtitle, isspace); - if (!subtitle->empty()) { - current->subtitle = *subtitle; - } - } - if (desc) { - boost::algorithm::trim_if(*desc, isspace); - if (!desc->empty()) { - current->desc1 = *desc; - } - } -} - -/* Parse 0x4E Extended Event Descriptor. {{{ */ -void -EitRowState::parseLongEventDescription(const u_char * data, EitProgram * current) const { - assert(GetDescriptorTag(data) == 0x4E); - const struct descr_extended_event *levt = reinterpret_cast<const struct descr_extended_event *>(data); - bool non_empty = (levt->descriptor_number || levt->last_descriptor_number || levt->length_of_items || levt->data[0]); - - if (non_empty && levt->descriptor_number == 0) { - current->descLang = Glib::ustring((const char *)&levt->lang_code1, 3); - - const u_char *p = reinterpret_cast<const u_char *>(&levt->data); -#ifndef NDEBUG - const void *data_end = data + DESCR_GEN_LEN + GetDescriptorLength(data); -#endif - while (p < levt->data + levt->length_of_items) { - const struct item_extended_event *name = reinterpret_cast<const struct item_extended_event *>(p); - size_t name_len = name->item_description_length; - assert(p + ITEM_EXTENDED_EVENT_LEN + name_len < data_end); - current->desc1 = *convert((const char *)name->data, name_len); - - p += ITEM_EXTENDED_EVENT_LEN + name_len; - - const struct item_extended_event *value = reinterpret_cast<const struct item_extended_event *>(p); - size_t value_len = value->item_description_length; - assert(p + ITEM_EXTENDED_EVENT_LEN + value_len < data_end); - current->desc2 = *convert((const char *)value->data, value_len); - - p += ITEM_EXTENDED_EVENT_LEN + value_len; - } - const struct item_extended_event *text = reinterpret_cast<const struct item_extended_event *>(p); - size_t len = text->item_description_length; - if (non_empty && len) { - current->desc3 = *convert((const char *)text->data, len); - } - - } -} /*}}}*/ - -/* Parse 0x50 Component Descriptor. {{{ - video is a flag, 1=> output the video information, 0=> output the - audio information. seen is a pointer to a counter to ensure we - only output the first one of each (XMLTV can't cope with more than - one) */ -void -EitRowState::parseComponentDescription(const u_char * data, EitProgram * current) const { - assert(GetDescriptorTag(data) == 0x50); - const struct descr_component *dc = reinterpret_cast<const struct descr_component *>(data); - - switch (dc->stream_content) { - case 0x01: // Video Info - current->videoHD = ((dc->component_type - 1) & 0x08) ? 1 : 0; - current->videoFrameRate = ((dc->component_type - 1) & 0x04) ? 30 : 25; - current->videoAspect = ((dc->component_type - 1) & 0x03); - break; - case 0x02: // Audio Info - current->audioChannels = dc->component_type; - current->language = Glib::ustring((const char *)&dc->lang_code1, 3); - break; - case 0x03: // Teletext Info - // FIXME: is there a suitable XMLTV output for this? - // if ((dc->component_type)&0x10) //subtitles - // if ((dc->component_type)&0x20) //subtitles for hard of hearing - current->teletextSubtitleLang = Glib::ustring((const char *)&dc->lang_code1, 3); - break; - // case 0x04: // AC3 info - } -} /*}}}*/ - -void -EitRowState::parseContentDescription(const u_char * data, EitProgram * current) const { - assert(GetDescriptorTag(data) == 0x54); - const struct descr_content * dc = reinterpret_cast<const struct descr_content *>(data); - for (const u_char * p = reinterpret_cast<const u_char*>(&dc->data); p < data + DESCR_GEN_LEN + dc->descriptor_length; p += NIBBLE_CONTENT_LEN) { - const struct nibble_content *nc = reinterpret_cast<const nibble_content *>(p); - int c1 = (nc->content_nibble_level_1 << 4) + nc->content_nibble_level_2; - // This is weird in the uk, they use user but not content, and almost the same values - current->category = c1 ? c1 : (nc->user_nibble_1 << 4) + nc->user_nibble_2; - } -} - -void -EitRowState::parseRatingDescription(const u_char * data, EitProgram * current) const { - assert(GetDescriptorTag(data) == 0x55); - const struct descr_parental_rating * pr = reinterpret_cast<const struct descr_parental_rating *>(data); - for (const u_char * p = reinterpret_cast<const u_char *>(&pr->data); p < data + DESCR_GEN_LEN + pr->descriptor_length; p += PARENTAL_RATING_ITEM_LEN) { - const struct parental_rating_item *pr = reinterpret_cast<const struct parental_rating_item *>(p); - switch (pr->rating) { - case 0x00: /*undefined*/ - break; - case 0x01 ... 0x0F: - current->dvbRating = pr->rating + 3; - break; - case 0x10 ... 0xFF: /*broadcaster defined*/ - break; - } - } -} - -int parsePrivateDataSpecifier(const u_char *data) { - assert(GetDescriptorTag(data) == 0x5F); - return GetPrivateDataSpecifier(data); -} - -/* Parse 0x76 Content Identifier Descriptor. {{{ */ -/* See ETSI TS 102 323, section 12 */ -void -EitRowState::parseContentIdentifierDescription(const u_char * data, EitProgram * current) const { - assert(GetDescriptorTag(data) == 0x76); - const struct descr_content_identifier *ci = reinterpret_cast<const struct descr_content_identifier *>(data); - for (const u_char * p = reinterpret_cast<const u_char *>(&ci->data); p < data + DESCR_GEN_LEN + ci->descriptor_length; p += DESCR_GEN_LEN + ci->descriptor_length) { - const struct descr_content_identifier_crid *crid = reinterpret_cast<const struct descr_content_identifier_crid *>(p); - - switch (crid->crid_location) - { - case 0x01: /* Carried in Content Identifier Table (CIT) */ - default: - break; - case 0x00: /* Carried explicitly within descriptor */ - struct descr_content_identifier_crid_local * crid_data = (descr_content_identifier_crid_local_t *)&crid->crid_ref_data; - size_t len = crid_data->crid_length; - switch (crid->crid_type) { - case 0x01: - case 0x31: - current->contentItemID = Glib::ustring((const char *)crid_data->crid_byte, len); - break; - case 0x02: - case 0x32: - current->contentSeriesID = Glib::ustring((const char *)crid_data->crid_byte, len); - break; - case 0x03: - case 0x33: - current->contentRecommendation = Glib::ustring((const char *)crid_data->crid_byte, len); - break; - } - break; - } - } -} /*}}}*/ - -/* Parse Descriptor. {{{ - * Tags should be output in this order: - -'title', 'sub-title', 'desc', 'credits', 'date', 'category', 'language', -'orig-language', 'length', 'icon', 'url', 'country', 'episode-num', -'video', 'audio', 'previously-shown', 'premiere', 'last-chance', -'new', 'subtitles', 'rating', 'star-rating' -*/ -void -EitRowState::parseDescription(const u_char * data, size_t len, EitProgram * current) const { - int pds = 0; - for (const u_char * p = data; p < data + len; p += DESCR_GEN_LEN + GetDescriptorLength(p)) { - const struct descr_gen *desc = reinterpret_cast<const struct descr_gen *>(p); - switch (GetDescriptorTag(desc)) { - case 0: - break; - case 0x4D: //short evt desc, [title] [sub-title] - // there can be multiple language versions of these - parseEventDescription(p, current); - break; - case 0x4E: //long evt descriptor [desc] - parseLongEventDescription(p, current); - break; - case 0x50: //component desc [language] [video] [audio] [subtitles] - parseComponentDescription(p, current); - break; - case 0x53: // CA Identifier Descriptor - break; - case 0x54: // content desc [category] - parseContentDescription(p, current); - break; - case 0x55: // Parental Rating Descriptor [rating] - parseRatingDescription(p, current); - break; - case 0x5f: // Private Data Specifier - pds = parsePrivateDataSpecifier(p); - break; - case 0x64: // Data broadcast desc - Text Desc for Data components - break; - case 0x69: // Programm Identification Label - break; - case 0x81: // TODO ??? - if (pds == 5) // ARD_ZDF_ORF - break; - case 0x82: // VPS (ARD, ZDF, ORF) - if (pds == 5) // ARD_ZDF_ORF - // TODO: <programme @vps-start="???"> - break; - case 0x4F: // Time Shifted Event - case 0x52: // Stream Identifier Descriptor - case 0x5E: // Multi Lingual Component Descriptor - case 0x83: // Logical Channel Descriptor (some kind of news-ticker on ARD-MHP-Data?) - case 0x84: // Preferred Name List Descriptor - case 0x85: // Preferred Name Identifier Descriptor - case 0x86: // Eacem Stream Identifier Descriptor - break; - case 0x76: // Content identifier descriptor - parseContentIdentifierDescription(p, current); - break; - default: - break; - } - } -} /*}}}*/ - -/* Check that program has at least a title as is required by xmltv.dtd. {{{ */ -static bool validateDescription(const u_char *data, size_t len) { - for (const u_char * p = data; p < data + len; p += DESCR_GEN_LEN + GetDescriptorLength(p)) { - const struct descr_gen *desc = reinterpret_cast<const struct descr_gen *>(p); - if (GetDescriptorTag(desc) == 0x4D) { - const struct descr_short_event *evtdesc = reinterpret_cast<const struct descr_short_event *>(p); - // make sure that title isn't empty - if (evtdesc->event_name_length) return true; - } - } - return false; -} /*}}}*/ - -bool -EitRowState::parseInfoTable(const u_char *data, size_t len, const RowProcessor * rp) { - const struct eit *e = reinterpret_cast<const struct eit *>(data); - - len -= 4; //remove CRC - - // For each event listing - bool found = false; - for (const u_char *p = reinterpret_cast<const u_char *>(&e->data); p < data + len; p += EIT_EVENT_LEN + GetEITDescriptorsLoopLength(p)) { - const struct eit_event *evt = reinterpret_cast<const struct eit_event *>(p); - SeenProgram sp(HILO(e->service_id), HILO(evt->event_id)); - if (seenPrograms.find(sp) != seenPrograms.end()) { - continue; - } - seenPrograms.insert(sp); - - // No program info at end! Just skip it - if (GetEITDescriptorsLoopLength(evt) == 0) { - continue; - } - - boost::gregorian::date startDate(boost::gregorian::gregorian_calendar::from_modjulian_day_number(HILO(evt->mjd))); - boost::posix_time::ptime startTime(startDate); - startTime += boost::posix_time::time_duration( - BcdCharToInt(evt->start_time_h) + time_offset, - BcdCharToInt(evt->start_time_m), - BcdCharToInt(evt->start_time_s)); - EitProgram results; - current = &results; - results.startTime = startTime; - results.stopTime = startTime + boost::posix_time::time_duration( - BcdCharToInt(evt->duration_h), - BcdCharToInt(evt->duration_m), - BcdCharToInt(evt->duration_s)); - - // a program must have a title that isn't empty - if (!validateDescription(reinterpret_cast<const u_char *>(&evt->data), GetEITDescriptorsLoopLength(evt))) { - continue; - } - - - results.serviceID = HILO(e->service_id); - results.eventID = HILO(evt->event_id); - - parseDescription(reinterpret_cast<const u_char *>(&evt->data), GetEITDescriptorsLoopLength(evt), &results); - process(rp); - found = true; - } - return found; -} - -SimpleMessageException(DemuxSetFilterFailure); - -void -EitRows::filterInput(int fd) const -{ - struct dmx_sct_filter_params sctFilterParams; - memset(&sctFilterParams, 0, sizeof(dmx_sct_filter_params)); - sctFilterParams.pid = 0x12; // EIT data - sctFilterParams.timeout = 0; - sctFilterParams.flags = DMX_IMMEDIATE_START; - sctFilterParams.filter.filter[0] = chan_filter; // 4e is now/next this multiplex, 4f others - sctFilterParams.filter.mask[0] = chan_filter_mask; - - if (ioctl(fd, DMX_SET_FILTER, &sctFilterParams) < 0) { - throw DemuxSetFilterFailure(strerror(errno)); - } -} - -void -EitRows::execute(const Glib::ustring &, const RowProcessor * rp) const -{ - EitRowState state; - openInput(); - readTables(boost::bind(&EitRowState::parseInfoTable, &state, _1, _2, rp)); - closeInput(); -} - diff --git a/p2pvr/scanner/eitRows.h b/p2pvr/scanner/eitRows.h deleted file mode 100644 index 2a3603f..0000000 --- a/p2pvr/scanner/eitRows.h +++ /dev/null @@ -1,52 +0,0 @@ -#ifndef EITROWS_H -#define EITROWS_H - -#include "scripts.h" -#include "rowSet.h" -#include "variables.h" -#include "dvbSiReaderHelper.h" -#include <boost/tuple/tuple.hpp> - -class EitProgram; - -class EitRows : public RowSet, DvbSiReaderHelper { - public: - EitRows(const ScriptNodePtr p); - ~EitRows(); - - void execute(const Glib::ustring &, const RowProcessor *) const; - void loadComplete(const CommonObjects *); - - private: - void filterInput(int fd) const; -}; - -class EitRowState : public RowState, DvbSiParserHelper { - public: - EitRowState(); - const Columns & getColumns() const; - RowAttribute resolveAttr(const Glib::ustring & attrName) const; - - bool parseInfoTable(const u_char *data, size_t len, const RowProcessor *); - - private: - void parseEventDescription(const u_char *data, EitProgram * current) const; - void parseLongEventDescription(const u_char *data, EitProgram * current) const; - void parseComponentDescription(const u_char *data, EitProgram * current) const; - void parseContentDescription(const u_char *data, EitProgram * current) const; - void parseRatingDescription(const u_char *data, EitProgram * current) const; - void parseContentIdentifierDescription(const u_char *data, EitProgram * current) const; - void parseDescription(const u_char * data, size_t len, EitProgram * current) const; - - typedef boost::tuple<int, int> SeenProgram; - typedef std::set<SeenProgram> SeenPrograms; - SeenPrograms seenPrograms; - - Columns columns; - friend class EitRows; - mutable EitProgram * current; -}; - - -#endif - diff --git a/p2pvr/scanner/epgRows.cpp b/p2pvr/scanner/epgRows.cpp new file mode 100644 index 0000000..7cf8de6 --- /dev/null +++ b/p2pvr/scanner/epgRows.cpp @@ -0,0 +1,528 @@ +/* + * tv_grab_dvb - dump dvb epg info in xmltv + * Version 0.2 - 20/04/2004 - First Public Release + * + * Copyright (C) 2004 Mark Bryars <dvb at darkskiez d0t co d0t uk> + * + * DVB code Mercilessly ripped off from dvddate + * dvbdate Copyright (C) Laurence Culhane 2002 <dvbdate@holmes.demon.co.uk> + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * Or, point your browser to http://www.gnu.org/copyleft/gpl.html + */ + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <sys/ioctl.h> +#include <errno.h> +#include <stdint.h> +#include <time.h> +#include <glibmm/regex.h> +#include <boost/regex.hpp> +#include <boost/bind.hpp> +#include <boost/date_time/gregorian_calendar.hpp> +#include <boost/algorithm/string/predicate.hpp> +#include <boost/algorithm/string/trim.hpp> +#include <boost/foreach.hpp> +#include <boost/lexical_cast.hpp> + +#include <linux/dvb/dmx.h> +#include "si_tables.h" + +#include "rowProcessor.h" +#include "epgRows.h" +#include <boost/tuple/tuple_comparison.hpp> + +struct EpgProgram { + VariableType serviceID; + VariableType eventID; + VariableType title; + VariableType titleLang; + VariableType subtitle; + VariableType descLang; + VariableType desc1; + VariableType desc2; + VariableType desc3; + VariableType videoAspect; + VariableType videoFrameRate; + VariableType videoHD; + VariableType audioChannels; + VariableType language; + VariableType teletextSubtitleLang; + VariableType category; + VariableType dvbRating; + VariableType contentItemID; + VariableType contentSeriesID; + VariableType contentRecommendation; + VariableType startTime; + VariableType stopTime; + VariableType episode; + VariableType episodes; + VariableType year; + VariableType flags; +}; + +static Glib::ustring title("title"); +template <class C, class M> +const M & +getMember(M C::*t, C * p) { + return p->*t; +} +SimpleMessageException(NoSuchAttribute); + +void +EpgRows::loadComplete(const CommonObjects *) +{ +} + +EpgRowState::EpgRowState() : + current(NULL) +{ +} + +const Columns & +EpgRowState::getColumns() const +{ + return columns; +} + +#define returnAttr(name) if (attrName == #name) return boost::bind(getMember<EpgProgram, VariableType>, &EpgProgram::name, boost::ref(current)) +RowState::RowAttribute +EpgRowState::resolveAttr(const Glib::ustring & attrName) const { + returnAttr(serviceID); + returnAttr(eventID); + returnAttr(title); + returnAttr(titleLang); + returnAttr(subtitle); + returnAttr(descLang); + returnAttr(desc1); + returnAttr(desc2); + returnAttr(desc3); + returnAttr(videoAspect); + returnAttr(videoFrameRate); + returnAttr(videoHD); + returnAttr(audioChannels); + returnAttr(language); + returnAttr(teletextSubtitleLang); + returnAttr(category); + returnAttr(dvbRating); + returnAttr(contentItemID); + returnAttr(contentSeriesID); + returnAttr(contentRecommendation); + returnAttr(startTime); + returnAttr(stopTime); + returnAttr(episode); + returnAttr(episodes); + returnAttr(year); + returnAttr(flags); + throw NoSuchAttribute(attrName); +} + +DECLARE_LOADER("epgrows", EpgRows); + +EpgRows::EpgRows(const ScriptNodePtr p) : + RowSet(p), + DvbSiReaderHelper(p) +{ +} + +EpgRows::~EpgRows() +{ +} + +static int time_offset = 0; +static int chan_filter = 0; +static int chan_filter_mask = 0; +static Glib::RefPtr<Glib::Regex> episodeRegex = Glib::Regex::create("[ (]+(?:\\w+ )?([0-9]+)(?: of |/)([0-9]+)[.)]+"); +static Glib::RefPtr<Glib::Regex> yearRegex = Glib::Regex::create("\\(([0-9]{4})[ )]+"); +static Glib::RefPtr<Glib::Regex> flagsRegex = Glib::Regex::create("[ []+([A-Z,]+)\\]"); + +void +EpgRowState::parseEventDescription(const u_char * data, EpgProgram * current) const { + assert(GetDescriptorTag(data) == 0x4D); + const struct descr_short_event *evtdesc = reinterpret_cast<const struct descr_short_event *>(data); + + size_t evtlen = evtdesc->event_name_length; + StrPtr title, subtitle, desc; + if (evtlen) { + current->titleLang = Glib::ustring((const char *)&evtdesc->lang_code1, 3); + title = convert((const char *)evtdesc->data, evtlen); + } + + size_t dsclen = evtdesc->data[evtlen]; + if (dsclen) { + subtitle = convert((const char *)evtdesc->data + evtlen + 1, dsclen); + } + if (subtitle) { + Glib::MatchInfo matches; + if (episodeRegex->match(*subtitle, matches)) { + current->episode = boost::lexical_cast<int>(matches.fetch(1)); + current->episodes = boost::lexical_cast<int>(matches.fetch(2)); + *subtitle = episodeRegex->replace_literal(*subtitle, 0, "", Glib::REGEX_MATCH_NOTEMPTY); + } + if (yearRegex->match(*subtitle, matches)) { + current->year = boost::lexical_cast<int>(matches.fetch(1)); + *subtitle = yearRegex->replace_literal(*subtitle, 0, "", Glib::REGEX_MATCH_NOTEMPTY); + } + if (flagsRegex->match(*subtitle, matches)) { + current->flags = matches.fetch(1); + *subtitle = yearRegex->replace_literal(*subtitle, 0, "", Glib::REGEX_MATCH_NOTEMPTY); + } + } + if (title && subtitle) { + if (boost::algorithm::ends_with(*title, "...") && boost::algorithm::starts_with(*subtitle, "...")) { + title->resize(title->length() - 3); + *title += " "; + size_t dot = subtitle->find('.', 4); + if (dot == Glib::ustring::npos) { + title->append(*subtitle, 3, subtitle->length() - 3); + } + else { + title->append(*subtitle, 3, dot - 3); + subtitle->erase(0, dot + 2); + } + } + size_t colon = subtitle->find(':'); + if (colon != Glib::ustring::npos) { + desc = StrPtr(new Glib::ustring(*subtitle, colon + 1, subtitle->length() - colon)); + subtitle->resize(colon); + } + else { + colon = title->find(':'); + desc = subtitle; + if (colon != Glib::ustring::npos) { + subtitle = StrPtr(new Glib::ustring(*title, colon + 1, title->length() - colon)); + title->resize(colon); + } + else { + subtitle.reset(); + } + } + } + if (title) { + boost::algorithm::trim_if(*title, isspace); + current->title = *title; + } + if (subtitle) { + boost::algorithm::trim_if(*subtitle, isspace); + if (!subtitle->empty()) { + current->subtitle = *subtitle; + } + } + if (desc) { + boost::algorithm::trim_if(*desc, isspace); + if (!desc->empty()) { + current->desc1 = *desc; + } + } +} + +/* Parse 0x4E Extended Event Descriptor. {{{ */ +void +EpgRowState::parseLongEventDescription(const u_char * data, EpgProgram * current) const { + assert(GetDescriptorTag(data) == 0x4E); + const struct descr_extended_event *levt = reinterpret_cast<const struct descr_extended_event *>(data); + bool non_empty = (levt->descriptor_number || levt->last_descriptor_number || levt->length_of_items || levt->data[0]); + + if (non_empty && levt->descriptor_number == 0) { + current->descLang = Glib::ustring((const char *)&levt->lang_code1, 3); + + const u_char *p = reinterpret_cast<const u_char *>(&levt->data); +#ifndef NDEBUG + const void *data_end = data + DESCR_GEN_LEN + GetDescriptorLength(data); +#endif + while (p < levt->data + levt->length_of_items) { + const struct item_extended_event *name = reinterpret_cast<const struct item_extended_event *>(p); + size_t name_len = name->item_description_length; + assert(p + ITEM_EXTENDED_EVENT_LEN + name_len < data_end); + current->desc1 = *convert((const char *)name->data, name_len); + + p += ITEM_EXTENDED_EVENT_LEN + name_len; + + const struct item_extended_event *value = reinterpret_cast<const struct item_extended_event *>(p); + size_t value_len = value->item_description_length; + assert(p + ITEM_EXTENDED_EVENT_LEN + value_len < data_end); + current->desc2 = *convert((const char *)value->data, value_len); + + p += ITEM_EXTENDED_EVENT_LEN + value_len; + } + const struct item_extended_event *text = reinterpret_cast<const struct item_extended_event *>(p); + size_t len = text->item_description_length; + if (non_empty && len) { + current->desc3 = *convert((const char *)text->data, len); + } + + } +} /*}}}*/ + +/* Parse 0x50 Component Descriptor. {{{ + video is a flag, 1=> output the video information, 0=> output the + audio information. seen is a pointer to a counter to ensure we + only output the first one of each (XMLTV can't cope with more than + one) */ +void +EpgRowState::parseComponentDescription(const u_char * data, EpgProgram * current) const { + assert(GetDescriptorTag(data) == 0x50); + const struct descr_component *dc = reinterpret_cast<const struct descr_component *>(data); + + switch (dc->stream_content) { + case 0x01: // Video Info + current->videoHD = ((dc->component_type - 1) & 0x08) ? 1 : 0; + current->videoFrameRate = ((dc->component_type - 1) & 0x04) ? 30 : 25; + current->videoAspect = ((dc->component_type - 1) & 0x03); + break; + case 0x02: // Audio Info + current->audioChannels = dc->component_type; + current->language = Glib::ustring((const char *)&dc->lang_code1, 3); + break; + case 0x03: // Teletext Info + // FIXME: is there a suitable XMLTV output for this? + // if ((dc->component_type)&0x10) //subtitles + // if ((dc->component_type)&0x20) //subtitles for hard of hearing + current->teletextSubtitleLang = Glib::ustring((const char *)&dc->lang_code1, 3); + break; + // case 0x04: // AC3 info + } +} /*}}}*/ + +void +EpgRowState::parseContentDescription(const u_char * data, EpgProgram * current) const { + assert(GetDescriptorTag(data) == 0x54); + const struct descr_content * dc = reinterpret_cast<const struct descr_content *>(data); + for (const u_char * p = reinterpret_cast<const u_char*>(&dc->data); p < data + DESCR_GEN_LEN + dc->descriptor_length; p += NIBBLE_CONTENT_LEN) { + const struct nibble_content *nc = reinterpret_cast<const nibble_content *>(p); + int c1 = (nc->content_nibble_level_1 << 4) + nc->content_nibble_level_2; + // This is weird in the uk, they use user but not content, and almost the same values + current->category = c1 ? c1 : (nc->user_nibble_1 << 4) + nc->user_nibble_2; + } +} + +void +EpgRowState::parseRatingDescription(const u_char * data, EpgProgram * current) const { + assert(GetDescriptorTag(data) == 0x55); + const struct descr_parental_rating * pr = reinterpret_cast<const struct descr_parental_rating *>(data); + for (const u_char * p = reinterpret_cast<const u_char *>(&pr->data); p < data + DESCR_GEN_LEN + pr->descriptor_length; p += PARENTAL_RATING_ITEM_LEN) { + const struct parental_rating_item *pr = reinterpret_cast<const struct parental_rating_item *>(p); + switch (pr->rating) { + case 0x00: /*undefined*/ + break; + case 0x01 ... 0x0F: + current->dvbRating = pr->rating + 3; + break; + case 0x10 ... 0xFF: /*broadcaster defined*/ + break; + } + } +} + +int parsePrivateDataSpecifier(const u_char *data) { + assert(GetDescriptorTag(data) == 0x5F); + return GetPrivateDataSpecifier(data); +} + +/* Parse 0x76 Content Identifier Descriptor. {{{ */ +/* See ETSI TS 102 323, section 12 */ +void +EpgRowState::parseContentIdentifierDescription(const u_char * data, EpgProgram * current) const { + assert(GetDescriptorTag(data) == 0x76); + const struct descr_content_identifier *ci = reinterpret_cast<const struct descr_content_identifier *>(data); + for (const u_char * p = reinterpret_cast<const u_char *>(&ci->data); p < data + DESCR_GEN_LEN + ci->descriptor_length; p += DESCR_GEN_LEN + ci->descriptor_length) { + const struct descr_content_identifier_crid *crid = reinterpret_cast<const struct descr_content_identifier_crid *>(p); + + switch (crid->crid_location) + { + case 0x01: /* Carried in Content Identifier Table (CIT) */ + default: + break; + case 0x00: /* Carried explicitly within descriptor */ + struct descr_content_identifier_crid_local * crid_data = (descr_content_identifier_crid_local_t *)&crid->crid_ref_data; + size_t len = crid_data->crid_length; + switch (crid->crid_type) { + case 0x01: + case 0x31: + current->contentItemID = Glib::ustring((const char *)crid_data->crid_byte, len); + break; + case 0x02: + case 0x32: + current->contentSeriesID = Glib::ustring((const char *)crid_data->crid_byte, len); + break; + case 0x03: + case 0x33: + current->contentRecommendation = Glib::ustring((const char *)crid_data->crid_byte, len); + break; + } + break; + } + } +} /*}}}*/ + +/* Parse Descriptor. {{{ + * Tags should be output in this order: + +'title', 'sub-title', 'desc', 'credits', 'date', 'category', 'language', +'orig-language', 'length', 'icon', 'url', 'country', 'episode-num', +'video', 'audio', 'previously-shown', 'premiere', 'last-chance', +'new', 'subtitles', 'rating', 'star-rating' +*/ +void +EpgRowState::parseDescription(const u_char * data, size_t len, EpgProgram * current) const { + int pds = 0; + for (const u_char * p = data; p < data + len; p += DESCR_GEN_LEN + GetDescriptorLength(p)) { + const struct descr_gen *desc = reinterpret_cast<const struct descr_gen *>(p); + switch (GetDescriptorTag(desc)) { + case 0: + break; + case 0x4D: //short evt desc, [title] [sub-title] + // there can be multiple language versions of these + parseEventDescription(p, current); + break; + case 0x4E: //long evt descriptor [desc] + parseLongEventDescription(p, current); + break; + case 0x50: //component desc [language] [video] [audio] [subtitles] + parseComponentDescription(p, current); + break; + case 0x53: // CA Identifier Descriptor + break; + case 0x54: // content desc [category] + parseContentDescription(p, current); + break; + case 0x55: // Parental Rating Descriptor [rating] + parseRatingDescription(p, current); + break; + case 0x5f: // Private Data Specifier + pds = parsePrivateDataSpecifier(p); + break; + case 0x64: // Data broadcast desc - Text Desc for Data components + break; + case 0x69: // Programm Identification Label + break; + case 0x81: // TODO ??? + if (pds == 5) // ARD_ZDF_ORF + break; + case 0x82: // VPS (ARD, ZDF, ORF) + if (pds == 5) // ARD_ZDF_ORF + // TODO: <programme @vps-start="???"> + break; + case 0x4F: // Time Shifted Event + case 0x52: // Stream Identifier Descriptor + case 0x5E: // Multi Lingual Component Descriptor + case 0x83: // Logical Channel Descriptor (some kind of news-ticker on ARD-MHP-Data?) + case 0x84: // Preferred Name List Descriptor + case 0x85: // Preferred Name Identifier Descriptor + case 0x86: // Eacem Stream Identifier Descriptor + break; + case 0x76: // Content identifier descriptor + parseContentIdentifierDescription(p, current); + break; + default: + break; + } + } +} /*}}}*/ + +/* Check that program has at least a title as is required by xmltv.dtd. {{{ */ +static bool validateDescription(const u_char *data, size_t len) { + for (const u_char * p = data; p < data + len; p += DESCR_GEN_LEN + GetDescriptorLength(p)) { + const struct descr_gen *desc = reinterpret_cast<const struct descr_gen *>(p); + if (GetDescriptorTag(desc) == 0x4D) { + const struct descr_short_event *evtdesc = reinterpret_cast<const struct descr_short_event *>(p); + // make sure that title isn't empty + if (evtdesc->event_name_length) return true; + } + } + return false; +} /*}}}*/ + +bool +EpgRowState::parseInfoTable(const u_char *data, size_t len, const RowProcessor * rp) { + const struct eit *e = reinterpret_cast<const struct eit *>(data); + + len -= 4; //remove CRC + + // For each event listing + bool found = false; + for (const u_char *p = reinterpret_cast<const u_char *>(&e->data); p < data + len; p += EIT_EVENT_LEN + GetEITDescriptorsLoopLength(p)) { + const struct eit_event *evt = reinterpret_cast<const struct eit_event *>(p); + SeenProgram sp(HILO(e->service_id), HILO(evt->event_id)); + if (seenPrograms.find(sp) != seenPrograms.end()) { + continue; + } + seenPrograms.insert(sp); + + // No program info at end! Just skip it + if (GetEITDescriptorsLoopLength(evt) == 0) { + continue; + } + + boost::gregorian::date startDate(boost::gregorian::gregorian_calendar::from_modjulian_day_number(HILO(evt->mjd))); + boost::posix_time::ptime startTime(startDate); + startTime += boost::posix_time::time_duration( + BcdCharToInt(evt->start_time_h) + time_offset, + BcdCharToInt(evt->start_time_m), + BcdCharToInt(evt->start_time_s)); + EpgProgram results; + current = &results; + results.startTime = startTime; + results.stopTime = startTime + boost::posix_time::time_duration( + BcdCharToInt(evt->duration_h), + BcdCharToInt(evt->duration_m), + BcdCharToInt(evt->duration_s)); + + // a program must have a title that isn't empty + if (!validateDescription(reinterpret_cast<const u_char *>(&evt->data), GetEITDescriptorsLoopLength(evt))) { + continue; + } + + + results.serviceID = HILO(e->service_id); + results.eventID = HILO(evt->event_id); + + parseDescription(reinterpret_cast<const u_char *>(&evt->data), GetEITDescriptorsLoopLength(evt), &results); + process(rp); + found = true; + } + return found; +} + +SimpleMessageException(DemuxSetFilterFailure); + +void +EpgRows::filterInput(int fd) const +{ + struct dmx_sct_filter_params sctFilterParams; + memset(&sctFilterParams, 0, sizeof(dmx_sct_filter_params)); + sctFilterParams.pid = 0x12; // EIT data + sctFilterParams.timeout = 0; + sctFilterParams.flags = DMX_IMMEDIATE_START; + sctFilterParams.filter.filter[0] = chan_filter; // 4e is now/next this multiplex, 4f others + sctFilterParams.filter.mask[0] = chan_filter_mask; + + if (ioctl(fd, DMX_SET_FILTER, &sctFilterParams) < 0) { + throw DemuxSetFilterFailure(strerror(errno)); + } +} + +void +EpgRows::execute(const Glib::ustring &, const RowProcessor * rp) const +{ + EpgRowState state; + openInput(); + readTables(boost::bind(&EpgRowState::parseInfoTable, &state, _1, _2, rp)); + closeInput(); +} + diff --git a/p2pvr/scanner/epgRows.h b/p2pvr/scanner/epgRows.h new file mode 100644 index 0000000..6f155e2 --- /dev/null +++ b/p2pvr/scanner/epgRows.h @@ -0,0 +1,52 @@ +#ifndef EPGROWS_H +#define EPGROWS_H + +#include "scripts.h" +#include "rowSet.h" +#include "variables.h" +#include "dvbSiReaderHelper.h" +#include <boost/tuple/tuple.hpp> + +class EpgProgram; + +class EpgRows : public RowSet, DvbSiReaderHelper { + public: + EpgRows(const ScriptNodePtr p); + ~EpgRows(); + + void execute(const Glib::ustring &, const RowProcessor *) const; + void loadComplete(const CommonObjects *); + + private: + void filterInput(int fd) const; +}; + +class EpgRowState : public RowState, DvbSiParserHelper { + public: + EpgRowState(); + const Columns & getColumns() const; + RowAttribute resolveAttr(const Glib::ustring & attrName) const; + + bool parseInfoTable(const u_char *data, size_t len, const RowProcessor *); + + private: + void parseEventDescription(const u_char *data, EpgProgram * current) const; + void parseLongEventDescription(const u_char *data, EpgProgram * current) const; + void parseComponentDescription(const u_char *data, EpgProgram * current) const; + void parseContentDescription(const u_char *data, EpgProgram * current) const; + void parseRatingDescription(const u_char *data, EpgProgram * current) const; + void parseContentIdentifierDescription(const u_char *data, EpgProgram * current) const; + void parseDescription(const u_char * data, size_t len, EpgProgram * current) const; + + typedef boost::tuple<int, int> SeenProgram; + typedef std::set<SeenProgram> SeenPrograms; + SeenPrograms seenPrograms; + + Columns columns; + friend class EpgRows; + mutable EpgProgram * current; +}; + + +#endif + -- cgit v1.2.3