diff options
Diffstat (limited to 'p2pvr/scanner/eitRows.cpp')
-rw-r--r-- | p2pvr/scanner/eitRows.cpp | 493 |
1 files changed, 493 insertions, 0 deletions
diff --git a/p2pvr/scanner/eitRows.cpp b/p2pvr/scanner/eitRows.cpp new file mode 100644 index 0000000..aea639f --- /dev/null +++ b/p2pvr/scanner/eitRows.cpp @@ -0,0 +1,493 @@ +/* + * 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 <boost/bind.hpp> +#include <boost/date_time/gregorian_calendar.hpp> + +#include <linux/dvb/dmx.h> +#include "si_tables.h" + +#include "rowProcessor.h" +#include "eitRows.h" + +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; +}; + +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 *) +{ +} + +void +EitRows::setFilter(const Glib::ustring &) +{ +} + +unsigned int +EitRows::columnCount() const +{ + return 1; +} + +const Glib::ustring & +EitRows::getColumnName(unsigned int) const { + return title; +} + +VariableType +EitRows::getCurrentValue(const Glib::ustring &) const { + return current->title; +} + +VariableType +EitRows::getCurrentValue(unsigned int) const { + return current->title; +} + +bool +EitRows::isNull(unsigned int ) const { + return false; +} + +bool +EitRows::isNull(const Glib::ustring & ) const { + return false; +} + +#define returnAttr(name) if (attrName == #name) return boost::bind(getMember<EitProgram, VariableType>, &EitProgram::name, boost::ref(current)) +RowSet::RowAttribute +EitRows::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); + throw NoSuchAttribute(attrName); +} + +DECLARE_LOADER("eitrows", EitRows); + +EitRows::EitRows(const xmlpp::Element * p) : + RowSet(p), + DvbSiReaderHelper(p), + current(NULL) +{ +} + +EitRows::~EitRows() +{ +} + +static int time_offset = 0; +static int chan_filter = 0; +static int chan_filter_mask = 0; + +void +EitRows::parseEventDescription(const u_char *data) 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; + if (evtlen) { + current->titleLang = Glib::ustring((const char *)&evtdesc->lang_code1, 3); + current->title = convert((const char *)evtdesc->data, evtlen); + } + + size_t dsclen = evtdesc->data[evtlen]; + if (dsclen) { + current->subtitle = convert((const char *)evtdesc->data + evtlen + 1, dsclen); + } +} + +/* Parse 0x4E Extended Event Descriptor. {{{ */ +void +EitRows::parseLongEventDescription(const u_char *data) 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 +EitRows::parseComponentDescription(const u_char *data) 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 +EitRows::parseContentDescription(const u_char *data) 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 +EitRows::parseRatingDescription(const u_char *data) 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 +EitRows::parseContentIdentifierDescription(const u_char *data) 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 +EitRows::parseDescription(const u_char * data, size_t len) 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); + break; + case 0x4E: //long evt descriptor [desc] + parseLongEventDescription(p); + break; + case 0x50: //component desc [language] [video] [audio] [subtitles] + parseComponentDescription(p); + break; + case 0x53: // CA Identifier Descriptor + break; + case 0x54: // content desc [category] + parseContentDescription(p); + break; + case 0x55: // Parental Rating Descriptor [rating] + parseRatingDescription(p); + 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); + 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; +} /*}}}*/ + +class SeenProgram { + public: + SeenProgram(int sid, int eid); + bool operator<(const SeenProgram &) const; + const int sid; + const int eid; +}; +SeenProgram::SeenProgram(int s, int e) : + sid(s), + eid(e) +{ +} +bool +SeenProgram::operator<(const SeenProgram & o) const +{ + return ((this->sid < o.sid) || ((this->sid == o.sid) && (this->eid < o.eid))); +} + +typedef std::set<SeenProgram> SeenPrograms; +SeenPrograms seenPrograms; +bool +EitRows::parseSIT(const u_char *data, size_t len, const RowProcessor * rp) const { + 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)); + rp->rowReady(); + rowNum += 1; + 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 = 18; // 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 RowProcessor * rp) const +{ + openInput(); + readTables(rp); + closeInput(); +} + |