diff --git a/.gitignore b/.gitignore index e98183de..bba2cfd3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,6 @@ build.* config.default + +.cproject +.project +.settings diff --git a/Makefile b/Makefile index d4a104ca..10219170 100644 --- a/Makefile +++ b/Makefile @@ -159,6 +159,17 @@ CFLAGS_com += -D_FILE_OFFSET_BITS=64 CFLAGS_com += -I${BUILDDIR} -I${CURDIR}/src -I${CURDIR} CFLAGS_com += -DHTS_VERSION=\"$(VERSION)\" +MKBUNDLE = $(CURDIR)/support/mkbundle + +ifndef V +ECHO = printf "$(1)\t%s\n" $(2) +BRIEF = CC MKBUNDLE CXX +MSG = $@ +$(foreach VAR,$(BRIEF), \ + $(eval $(VAR) = @$$(call ECHO,$(VAR),$$(MSG)); $($(VAR)))) +endif + + all: ${PROG} .PHONY: clean distclean @@ -186,7 +197,7 @@ ifneq ($(VERSION), $(CURVERSION)) .PHONY: src/version.c $(info Version changed) src/version.c: - echo $(VERSION) >${BUILDDIR}/ver + @echo $(VERSION) >${BUILDDIR}/ver endif @@ -200,6 +211,5 @@ include support/${OSENV}.mk $(BUILDDIR)/bundles/%.o: $(BUILDDIR)/bundles/%.c $(CC) -I${CURDIR}/src -c -o $@ $< -$(BUILDDIR)/bundles/%.c: % $(CURDIR)/support/mkbundle - $(CURDIR)/support/mkbundle \ - -o $@ -s $< -d ${BUILDDIR}/bundles/$<.d -p $< -z +$(BUILDDIR)/bundles/%.c: % + $(MKBUNDLE) -o $@ -s $< -d ${BUILDDIR}/bundles/$<.d -p $< -z diff --git a/contrib/redhat/tvheadend b/contrib/redhat/tvheadend new file mode 100755 index 00000000..fa4b9100 --- /dev/null +++ b/contrib/redhat/tvheadend @@ -0,0 +1,128 @@ +#!/bin/sh +# +# tvheadend Start/Stop the hts tvheadend daemon. +# +# chkconfig: 345 90 60 +# description: Tvheadend is a TV streaming server for Linux supporting +# DVB-S, DVB-S2, DVB-C, DVB-T, ATSC, IPTV, and Analog video +# (V4L) as input sources. It also comes with a powerful and +# easy to use web interface both used for configuration and +# day-to-day operations, such as searching the EPG and +# scheduling recordings. + +### BEGIN INIT INFO +# Provides: tvheadend +# Required-Start: $local_fs $syslog +# Required-Stop: $local_fs $syslog +# Default-Start: 345 +# Default-Stop: 90 +# Short-Description: run tvheadend daemon +# Description: Tvheadend is a TV streaming server for Linux supporting +# DVB-S, DVB-S2, DVB-C, DVB-T, ATSC, IPTV, and Analog video +# (V4L) as input sources. It also comes with a powerful and +# easy to use web interface both used for configuration and +# day-to-day operations, such as searching the EPG and +# scheduling recordings. +### END INIT INFO + +[ -f /etc/sysconfig/tvheadend ] || { + [ "$1" = "status" ] && exit 4 || exit 6 +} + +RETVAL=0 +prog="tvheadend" +exec=/usr/local/bin/tvheadend +lockfile=/var/lock/subsys/tvheadend +sysconfig=/etc/sysconfig/tvheadend + +# Source function library. +. /etc/rc.d/init.d/functions + +[ -e /etc/sysconfig/$prog ] && . /etc/sysconfig/$prog + +start() { + [ -x $exec ] || exit 5 + [ -f $sysconfig ] || exit 6 + echo -n $"Starting $prog: " + daemon $exec $OPTIONS + retval=$? + echo + [ $retval -eq 0 ] && touch $lockfile +} + +stop() { + echo -n $"Stopping $prog: " + if [ -n "`pidfileofproc $exec`" ]; then + killproc $exec + RETVAL=3 + else + failure $"Stopping $prog" + fi + retval=$? + echo + [ $retval -eq 0 ] && rm -f $lockfile +} + +restart() { + stop + start +} + +reload() { + echo -n $"Reloading $prog: " + if [ -n "`pidfileofproc $exec`" ]; then + killproc $exec -HUP + else + failure $"Reloading $prog" + fi + retval=$? + echo +} + +force_reload() { + # new configuration takes effect after restart + restart +} + +rh_status() { + # run checks to determine if the service is running or use generic status + status -p /var/run/tvheadend.pid $prog +} + +rh_status_q() { + rh_status >/dev/null 2>&1 +} + + +case "$1" in + start) + rh_status_q && exit 0 + $1 + ;; + stop) + rh_status_q || exit 0 + $1 + ;; + restart) + $1 + ;; + reload) + rh_status_q || exit 7 + $1 + ;; + force-reload) + force_reload + ;; + status) + rh_status + ;; + condrestart|try-restart) + rh_status_q || exit 0 + restart + ;; + *) + echo $"Usage: $0 {start|stop|status|restart|condrestart|try-restart|reload|force-reload}" + exit 2 +esac +exit $? + diff --git a/contrib/redhat/tvheadend.spec b/contrib/redhat/tvheadend.spec new file mode 100644 index 00000000..dd2fa8c4 --- /dev/null +++ b/contrib/redhat/tvheadend.spec @@ -0,0 +1,75 @@ +Name: tvheadend +Summary: TV streaming server +Version: 2.12.cae47cf +Release: 1%{dist} +License: GPL +Group: Applications/Multimedia +URL: http://www.lonelycoder.com/tvheadend +Packager: Jouk Hettema +Source: tvheadend-%{version}.tar.bz2 +Prefix: /usr +BuildRequires: avahi-devel, openssl, glibc, zlib + +%description +Tvheadend is a TV streaming server for Linux supporting DVB-S, DVB-S2, +DVB-C, DVB-T, ATSC, IPTV, and Analog video (V4L) as input sources. + +%prep +%setup -q + +%build +%configure --release --prefix=%{prefix}/bin +%{__make} + +%install +%{__rm} -rf %{buildroot} + +mkdir -p $RPM_BUILD_ROOT/%{prefix} + +%{__install} -d -m0755 %{buildroot}%{prefix}/bin +%{__install} -d -m0755 %{buildroot}/etc/tvheadend +%{__install} -d -m0755 %{buildroot}/etc/sysconfig +%{__install} -d -m0755 %{buildroot}/etc/rc.d/init.d +%{__install} -d -m0755 %{buildroot}%{prefix}/shared/man/man1 + +%{__install} -m0755 build.Linux/tvheadend %{buildroot}%{prefix}/bin/ +%{__install} -m0755 man/tvheadend.1 %{buildroot}%{prefix}/shared/man/man1 +%{__install} -m0755 contrib/redhat/tvheadend %{buildroot}/etc/rc.d/init.d + +cat >> %{buildroot}/etc/sysconfig/tvheadend << EOF +OPTIONS="-f -u tvheadend -g tvheadend -c /etc/tvheadend -s -C" +EOF + +%pre +groupadd -f -r tvheadend >/dev/null 2>&! || : +useradd -s /sbin/nologin -c "Tvheadend" -M -r tvheadend -g tvheadend -G video >/dev/null 2>&! || : + +%preun +if [ $1 = 0 ]; then + /sbin/service tvheadend stop >/dev/null 2>&1 + /sbin/chkconfig --del tvheadend +fi + +%post +/sbin/chkconfig --add tvheadend + +if [ "$1" -ge "1" ]; then + /sbin/service tvheadend condrestart >/dev/null 2>&1 || : +fi + +%clean +%{__rm} -rf %{buildroot} + +%files +%defattr(-,root,root) +%doc debian/changelog LICENSE README +%dir %attr(-,tvheadend,root) %{prefix} +%{prefix}/bin/tvheadend +%{prefix}/shared/man/man1/tvheadend.1* +%{_sysconfdir}/rc.d/init.d/tvheadend +/etc/tvheadend +%config /etc/sysconfig/tvheadend + +%changelog +* Fri Feb 18 2011 Jouk Hettema - 2.12.cae47cf +- initial build for fedora 14 diff --git a/debian/changelog b/debian/changelog index b8283b76..86bc0ec1 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,36 @@ +tvheadend (2.12.99) hts; urgency=low + + * Debian package has been renamed from hts-tvheadend to tvheadend + + * Add functionality to delete recordings + + * Better support for playing in web clients. + Temporary tickets are used instead of username/password authentication + + * Remove lock contention in CWC updates + + * Fix bug in IPTV PAT parser (Ticket #318) + + * Store EPG on disk so it can be reloaded on restart + + * Add support for Viaccess EMM + + * Add support for DRECrypt EMM + + * Depend on OpenSSL for cryptographic features + + * RTSP has been dropped. It was too buggy and noone wanted to maintain it + + * Fix bug in JSON encoder. Ticket #163 + + * Fix crash in HTTP service. Ticket #334 + + * Added http-streaming of services. Tested and working with mplayer. + + * Add support for building RPM packages + + -- Andreas Öman Sat, 19 Feb 2011 12:57:09 +0100 + hts-tvheadend (2.12) hts; urgency=low * Add support for IPTV over IPv6 diff --git a/debian/control b/debian/control index ee7e9234..28dc1e97 100644 --- a/debian/control +++ b/debian/control @@ -1,15 +1,25 @@ -Source: hts-tvheadend +Source: tvheadend Section: main Priority: extra Maintainer: Andreas Öman -Build-Depends: debhelper (>= 5) +Build-Depends: debhelper (>= 7.0.50) Standards-Version: 3.7.3 -Package: hts-tvheadend +Package: tvheadend Architecture: any Depends: ${shlibs:Depends}, libavahi-client3 Recommends: xmltv -Enhances: hts-showtime -Description: HTS Tvheadend - TV backend for use with hts-showtime and various other clients. - Based on ffmpeg 'http://www.ffmpeg.org/' and ExtJS 'http://www.extjs.org/' +Enhances: showtime +Replaces: hts-tvheadend +Homepage: http://www.lonelycoder.com/tvheadend +Description: Tvheadend + TV backend for use with Showtime, XBMC and various other clients. + Uses ExtJS 'http://www.extjs.org/' + +Package: tvheadend-dbg +Architecture: any +Section: debug +Priority: extra +Depends: tvheadend (= ${binary:Version}), ${misc:Depends} +Description: Debug symbols for Tvheadend + This package contains the debugging symbols for Tvheadend. diff --git a/debian/rules b/debian/rules index ac4a519a..76591cd5 100755 --- a/debian/rules +++ b/debian/rules @@ -1,38 +1,34 @@ #!/usr/bin/make -f +# Uncomment this to turn on verbose mode. +#export DH_VERBOSE=1 + DEB_BUILD_GNU_TYPE := $(shell dpkg-architecture -qDEB_BUILD_GNU_TYPE) -config.mak: configure - dh_testdir - ./configure --release --prefix=/usr +%: + dh $@ -clean: +override_dh_clean: dh_testdir dh_testroot rm -rf build.* - dh_clean + dh_clean +override_dh_auto_clean: + dh_clean -build: config.mak +override_dh_auto_configure: + dh_testdir + ./configure --release --prefix=/usr + +override_dh_auto_build: $(MAKE) -binary: - dh_testdir - dh_testroot - dh_installdirs - dh_install - $(MAKE) prefix=$(CURDIR)/debian/hts-tvheadend/usr install - dh_installchangelogs - dh_installinit --name tvheadend - dh_installdocs - dh_installdebconf - dh_link - dh_strip - dh_compress - dh_fixperms - dh_makeshlibs - dh_installdeb - dh_shlibdeps - dh_gencontrol - dh_md5sums - dh_builddeb +override_dh_install: + $(MAKE) prefix=$(CURDIR)/debian/tvheadend/usr install + +override_dh_installinit: + dh_installinit --name tvheadend + +override_dh_strip: + dh_strip --dbg-package=tvheadend-dbg diff --git a/debian/hts-tvheadend.config b/debian/tvheadend.config similarity index 74% rename from debian/hts-tvheadend.config rename to debian/tvheadend.config index cffd8a59..50da1916 100644 --- a/debian/hts-tvheadend.config +++ b/debian/tvheadend.config @@ -12,17 +12,17 @@ while [ "$STATE" != 0 -a "$STATE" != 4 ]; do case "$STATE" in 1) # Ask for username - db_input high hts-tvheadend/admin_username || true + db_input high tvheadend/admin_username || true ;; 2) # Ask for password - db_input high hts-tvheadend/admin_password || true + db_input high tvheadend/admin_password || true ;; 3) # Display a final note - db_input high hts-tvheadend/webinterface || true + db_input high tvheadend/webinterface || true ;; esac diff --git a/debian/hts-tvheadend.docs b/debian/tvheadend.docs similarity index 100% rename from debian/hts-tvheadend.docs rename to debian/tvheadend.docs diff --git a/debian/hts-tvheadend.postinst b/debian/tvheadend.postinst similarity index 90% rename from debian/hts-tvheadend.postinst rename to debian/tvheadend.postinst index 22692d09..1d5e60cc 100644 --- a/debian/hts-tvheadend.postinst +++ b/debian/tvheadend.postinst @@ -27,11 +27,11 @@ configure) echo >>"${HTS_SUPERUSERCONF}" "{" - if db_get hts-tvheadend/admin_username; then + if db_get tvheadend/admin_username; then echo >>"${HTS_SUPERUSERCONF}" '"username": "'$RET'",' fi - if db_get hts-tvheadend/admin_password; then + if db_get tvheadend/admin_password; then echo >>"${HTS_SUPERUSERCONF}" '"password": "'$RET'"' fi diff --git a/debian/hts-tvheadend.postrm b/debian/tvheadend.postrm similarity index 100% rename from debian/hts-tvheadend.postrm rename to debian/tvheadend.postrm diff --git a/debian/hts-tvheadend.templates b/debian/tvheadend.templates similarity index 85% rename from debian/hts-tvheadend.templates rename to debian/tvheadend.templates index 79ffea26..2e5a650c 100644 --- a/debian/hts-tvheadend.templates +++ b/debian/tvheadend.templates @@ -1,4 +1,4 @@ -Template: hts-tvheadend/admin_username +Template: tvheadend/admin_username Type: string Description: Choose a username for Tvheadend administrator. Tvheadend is accessed via a world reachable web interface (assuming your @@ -8,10 +8,10 @@ Description: Choose a username for Tvheadend administrator. If you want to change the superuser account you need to reconfigure the Tvheadend package. -Template: hts-tvheadend/admin_password +Template: tvheadend/admin_password Type: password Description: Administrator password. -Template: hts-tvheadend/webinterface +Template: tvheadend/webinterface Type: note Description: After installation Tvheadend can be accessed via HTTP on port 9981. From this machine you can point your web-browser to http://localhost:9981/ diff --git a/debian/hts-tvheadend.tvheadend.init b/debian/tvheadend.tvheadend.init similarity index 98% rename from debian/hts-tvheadend.tvheadend.init rename to debian/tvheadend.tvheadend.init index a98f50d2..2e424b97 100644 --- a/debian/hts-tvheadend.tvheadend.init +++ b/debian/tvheadend.tvheadend.init @@ -1,6 +1,6 @@ #! /bin/sh ### BEGIN INIT INFO -# Provides: hts-tvheadend +# Provides: tvheadend # Required-Start: $local_fs $remote_fs udev # Required-Stop: $local_fs $remote_fs # Default-Start: 2 3 4 5 diff --git a/src/access.c b/src/access.c index 6de0d728..080d6d84 100644 --- a/src/access.c +++ b/src/access.c @@ -30,6 +30,7 @@ #include #include +#include #include "tvheadend.h" #include "access.h" @@ -37,10 +38,116 @@ #include "settings.h" struct access_entry_queue access_entries; +struct access_ticket_queue access_tickets; const char *superuser_username; const char *superuser_password; +/** + * + */ +static void +access_ticket_destroy(access_ticket_t *at) +{ + free(at->at_id); + free(at->at_resource); + TAILQ_REMOVE(&access_tickets, at, at_link); + free(at); +} + +/** + * + */ +static access_ticket_t * +access_ticket_find(const char *id) +{ + access_ticket_t *at = NULL; + + if(id != NULL) { + TAILQ_FOREACH(at, &access_tickets, at_link) + if(!strcmp(at->at_id, id)) + return at; + } + + return NULL; +} + +/** + * + */ +static void +access_ticket_timout(void *aux) +{ + access_ticket_t *at = aux; + + access_ticket_destroy(at); +} + +/** + * Create a new ticket for the requested resource and generate a id for it + */ +const char * +access_ticket_create(const char *resource) +{ + uint8_t buf[20]; + char id[41]; + unsigned int i; + access_ticket_t *at; + static const char hex_string[16] = "0123456789ABCDEF"; + + at = calloc(1, sizeof(access_ticket_t)); + + RAND_bytes(buf, 20); + + //convert to hexstring + for(i=0; i> 4) & 0xF)]; + id[(i*2)+1] = hex_string[(buf[i]) & 0x0F]; + } + id[40] = '\0'; + + at->at_id = strdup(id); + at->at_resource = strdup(resource); + + TAILQ_INSERT_TAIL(&access_tickets, at, at_link); + gtimer_arm(&at->at_timer, access_ticket_timout, at, 60*5); + + return at->at_id; +} + +/** + * + */ +int +access_ticket_delete(const char *id) +{ + access_ticket_t *at; + + if((at = access_ticket_find(id)) == NULL) + return -1; + + gtimer_disarm(&at->at_timer); + access_ticket_destroy(at); + + return 0; +} + +/** + * + */ +int +access_ticket_verify(const char *id, const char *resource) +{ + access_ticket_t *at; + + if((at = access_ticket_find(id)) == NULL) + return -1; + + if(strcmp(at->at_resource, resource)) + return -1; + + return 0; +} /** * @@ -409,6 +516,7 @@ static const dtable_class_t access_dtc = { .dtc_record_delete = access_record_delete, .dtc_read_access = ACCESS_ADMIN, .dtc_write_access = ACCESS_ADMIN, + .dtc_mutex = &global_lock, }; /** @@ -422,7 +530,17 @@ access_init(int createdefault) access_entry_t *ae; const char *s; + static struct { + pid_t pid; + struct timeval tv; + } randseed; + + randseed.pid = getpid(); + gettimeofday(&randseed.tv, NULL); + RAND_seed(&randseed, sizeof(randseed)); + TAILQ_INIT(&access_entries); + TAILQ_INIT(&access_tickets); dt = dtable_create(&access_dtc, "accesscontrol", NULL); diff --git a/src/access.h b/src/access.h index 834a372c..7af118e2 100644 --- a/src/access.h +++ b/src/access.h @@ -42,14 +42,36 @@ typedef struct access_entry { uint32_t ae_netmask; /* derived from ae_prefixlen */ } access_entry_t; +TAILQ_HEAD(access_ticket_queue, access_ticket); + +extern struct access_ticket_queue access_tickets; + +typedef struct access_ticket { + char *at_id; + + TAILQ_ENTRY(access_ticket) at_link; + + gtimer_t at_timer; + char *at_resource; +} access_ticket_t; #define ACCESS_STREAMING 0x1 #define ACCESS_WEB_INTERFACE 0x2 #define ACCESS_RECORDER 0x4 #define ACCESS_ADMIN 0x8 - #define ACCESS_FULL 0x3f +/** + * Create a new ticket for the requested resource and generate a id for it + */ +const char* access_ticket_create(const char *resource); + +/** + * Verifies that a given ticket id matches a resource + */ +int access_ticket_verify(const char *id, const char *resource); + +int access_ticket_delete(const char *ticket_id); /** * Verifies that the given user in combination with the source ip * complies with the requested mask diff --git a/src/capmt.c b/src/capmt.c index 0a0cefec..18cdf2da 100644 --- a/src/capmt.c +++ b/src/capmt.c @@ -841,6 +841,7 @@ static const dtable_class_t capmt_dtc = { .dtc_record_delete = capmt_entry_delete, .dtc_read_access = ACCESS_ADMIN, .dtc_write_access = ACCESS_ADMIN, + .dtc_mutex = &global_lock, }; /** diff --git a/src/channels.c b/src/channels.c index 8dd409f9..f13e0ed4 100644 --- a/src/channels.c +++ b/src/channels.c @@ -858,6 +858,7 @@ static const dtable_class_t channel_tags_dtc = { .dtc_record_delete = channel_tag_record_delete, .dtc_read_access = ACCESS_ADMIN, .dtc_write_access = ACCESS_ADMIN, + .dtc_mutex = &global_lock, }; diff --git a/src/channels.h b/src/channels.h index 9cb02fdc..6dfbc73e 100644 --- a/src/channels.h +++ b/src/channels.h @@ -45,6 +45,7 @@ typedef struct channel { struct event_tree ch_epg_events; struct event *ch_epg_current; + struct event *ch_epg_next; gtimer_t ch_epg_timer_head; gtimer_t ch_epg_timer_current; diff --git a/src/cwc.c b/src/cwc.c index b47563bd..1c9eeb11 100644 --- a/src/cwc.c +++ b/src/cwc.c @@ -59,6 +59,8 @@ typedef enum { CARD_CONAX, CARD_SECA, CARD_VIACCESS, + CARD_NAGRA, + CARD_NDS, CARD_UNKNOWN } card_type_t; @@ -95,6 +97,7 @@ TAILQ_HEAD(cwc_message_queue, cwc_message); LIST_HEAD(ecm_section_list, ecm_section); static struct cwc_queue cwcs; static pthread_cond_t cwc_config_changed; +static pthread_mutex_t cwc_mutex; static char *crypt_md5(const char *pw, const char *salt); /** @@ -202,7 +205,7 @@ typedef struct cwc { int cwc_writer_running; struct cwc_message_queue cwc_writeq; - TAILQ_ENTRY(cwc) cwc_link; /* Linkage protected via global_lock */ + TAILQ_ENTRY(cwc) cwc_link; /* Linkage protected via cwc_mutex */ struct cwc_service_list cwc_services; @@ -269,12 +272,14 @@ typedef struct cwc { */ static void cwc_service_destroy(th_descrambler_t *td); -static void cwc_detecs_card_type(cwc_t *cwc); +static void cwc_detect_card_type(cwc_t *cwc); void cwc_emm_conax(cwc_t *cwc, uint8_t *data, int len); void cwc_emm_irdeto(cwc_t *cwc, uint8_t *data, int len); void cwc_emm_dre(cwc_t *cwc, uint8_t *data, int len); void cwc_emm_seca(cwc_t *cwc, uint8_t *data, int len); void cwc_emm_viaccess(cwc_t *cwc, uint8_t *data, int len); +void cwc_emm_nagra(cwc_t *cwc, uint8_t *data, int len); +void cwc_emm_nds(cwc_t *cwc, uint8_t *data, int len); /** @@ -412,8 +417,8 @@ des_make_login_key(cwc_t *cwc, uint8_t *k) des14[i] = cwc->cwc_confedkey[i] ^ k[i]; des_key_spread(des14, spread); - DES_key_sched((DES_cblock *)spread, &cwc->cwc_k1); - DES_key_sched((DES_cblock *)(spread+8), &cwc->cwc_k2); + DES_set_key_unchecked((DES_cblock *)spread, &cwc->cwc_k1); + DES_set_key_unchecked((DES_cblock *)(spread+8), &cwc->cwc_k2); } /** @@ -431,8 +436,8 @@ des_make_session_key(cwc_t *cwc) des14[i % 14] ^= k2[i]; des_key_spread(des14, spread); - DES_key_sched((DES_cblock *)spread, &cwc->cwc_k1); - DES_key_sched((DES_cblock *)(spread+8), &cwc->cwc_k2); + DES_set_key_unchecked((DES_cblock *)spread, &cwc->cwc_k1); + DES_set_key_unchecked((DES_cblock *)(spread+8), &cwc->cwc_k2); } /** @@ -479,6 +484,9 @@ cwc_send_msg(cwc_t *cwc, const uint8_t *msg, size_t len, int sid, int enq) pthread_mutex_unlock(&cwc->cwc_writer_mutex); } else { n = write(cwc->cwc_fd, buf, len); + if(n != len) + tvhlog(LOG_INFO, "cwc", "write error %s", strerror(errno)); + free(cm); } return seq & 0xffff; @@ -566,7 +574,7 @@ cwc_decode_card_data_reply(cwc_t *cwc, uint8_t *msg, int len) cwc->cwc_connected = 1; cwc_comet_status_update(cwc); cwc->cwc_caid = (msg[4] << 8) | msg[5]; - n = psi_caid2name(cwc->cwc_caid) ?: "Unknown"; + n = psi_caid2name(cwc->cwc_caid & 0xff00) ?: "Unknown"; memcpy(cwc->cwc_ua, &msg[6], 8); @@ -578,7 +586,7 @@ cwc_decode_card_data_reply(cwc_t *cwc, uint8_t *msg, int len) cwc->cwc_ua[0], cwc->cwc_ua[1], cwc->cwc_ua[2], cwc->cwc_ua[3], cwc->cwc_ua[4], cwc->cwc_ua[5], cwc->cwc_ua[6], cwc->cwc_ua[7], nprov); - cwc_detecs_card_type(cwc); + cwc_detect_card_type(cwc); msg += 15; plen -= 12; @@ -644,7 +652,7 @@ cwc_decode_card_data_reply(cwc_t *cwc, uint8_t *msg, int len) * based on the equivalent in sasc-ng */ static void -cwc_detecs_card_type(cwc_t *cwc) +cwc_detect_card_type(cwc_t *cwc) { uint8_t c_sys = cwc->cwc_caid >> 8; @@ -674,6 +682,16 @@ cwc_detecs_card_type(cwc_t *cwc) tvhlog(LOG_INFO, "cwc", "%s: dre card", cwc->cwc_hostname); break; + case 0x18: + cwc->cwc_card_type = CARD_NAGRA; + tvhlog(LOG_INFO, "cwc", "%s: nagra card", + cwc->cwc_hostname); + break; + case 0x09: + cwc->cwc_card_type = CARD_NDS; + tvhlog(LOG_INFO, "cwc", "%s: nds card", + cwc->cwc_hostname); + break; default: cwc->cwc_card_type = CARD_UNKNOWN; break; @@ -770,18 +788,16 @@ handle_ecm_reply(cwc_service_t *ct, ecm_section_t *es, uint8_t *msg, "Obtained key for for service \"%s\" in %lld ms, from %s", t->s_svcname, delay, ct->cs_cwc->cwc_hostname); - pthread_mutex_lock(&t->s_stream_mutex); ct->cs_keystate = CS_RESOLVED; memcpy(ct->cs_cw, msg + 3, 16); ct->cs_pending_cw_update = 1; - pthread_mutex_unlock(&t->s_stream_mutex); } } /** * Handle running reply - * global_lock is held + * cwc_mutex is held */ static int cwc_running_reply(cwc_t *cwc, uint8_t msgtype, uint8_t *msg, int len) @@ -836,9 +852,9 @@ cwc_read(cwc_t *cwc, void *buf, size_t len, int timeout) { int r; - pthread_mutex_unlock(&global_lock); + pthread_mutex_unlock(&cwc_mutex); r = tcp_read_timeout(cwc->cwc_fd, buf, len, timeout); - pthread_mutex_lock(&global_lock); + pthread_mutex_lock(&cwc_mutex); if(cwc_must_break(cwc)) return ECONNABORTED; @@ -1031,23 +1047,23 @@ cwc_thread(void *aux) struct timespec ts; int attempts = 0; - pthread_mutex_lock(&global_lock); + pthread_mutex_lock(&cwc_mutex); while(cwc->cwc_running) { while(cwc->cwc_running && cwc->cwc_enabled == 0) - pthread_cond_wait(&cwc->cwc_cond, &global_lock); + pthread_cond_wait(&cwc->cwc_cond, &cwc_mutex); snprintf(hostname, sizeof(hostname), "%s", cwc->cwc_hostname); port = cwc->cwc_port; tvhlog(LOG_INFO, "cwc", "Attemping to connect to %s:%d", hostname, port); - pthread_mutex_unlock(&global_lock); + pthread_mutex_unlock(&cwc_mutex); fd = tcp_connect(hostname, port, errbuf, sizeof(errbuf), 10); - pthread_mutex_lock(&global_lock); + pthread_mutex_lock(&cwc_mutex); if(fd == -1) { attempts++; @@ -1092,7 +1108,7 @@ cwc_thread(void *aux) "%s: Automatic connection attempt in in %d seconds", cwc->cwc_hostname, d); - pthread_cond_timedwait(&cwc_config_changed, &global_lock, &ts); + pthread_cond_timedwait(&cwc_config_changed, &cwc_mutex, &ts); } tvhlog(LOG_INFO, "cwc", "%s destroyed", cwc->cwc_hostname); @@ -1110,7 +1126,7 @@ cwc_thread(void *aux) free((void *)cwc->cwc_hostname); free(cwc); - pthread_mutex_unlock(&global_lock); + pthread_mutex_unlock(&cwc_mutex); return NULL; } @@ -1164,7 +1180,7 @@ cwc_emm(uint8_t *data, int len) { cwc_t *cwc; - lock_assert(&global_lock); + pthread_mutex_lock(&cwc_mutex); TAILQ_FOREACH(cwc, &cwcs, cwc_link) { if(cwc->cwc_forward_emm && cwc->cwc_writer_running) { @@ -1184,11 +1200,18 @@ cwc_emm(uint8_t *data, int len) case CARD_DRE: cwc_emm_dre(cwc, data, len); break; + case CARD_NAGRA: + cwc_emm_nagra(cwc, data, len); + break; + case CARD_NDS: + cwc_emm_nds(cwc, data, len); + break; case CARD_UNKNOWN: break; } } } + pthread_mutex_unlock(&cwc_mutex); } @@ -1447,7 +1470,7 @@ cwc_emm_viaccess(cwc_t *cwc, uint8_t *data, int mlen) } /** - * + * t->s_streaming_mutex is held */ static void cwc_table_input(struct th_descrambler *td, struct service *t, @@ -1581,6 +1604,51 @@ cwc_emm_dre(cwc_t *cwc, uint8_t *data, int len) cwc_send_msg(cwc, data, len, 0, 1); } +void +cwc_emm_nagra(cwc_t *cwc, uint8_t *data, int len) +{ + int match = 0; + unsigned char hexserial[4]; + + if (data[0] == 0x83) { // unique|shared + hexserial[0] = data[5]; + hexserial[1] = data[4]; + hexserial[2] = data[3]; + hexserial[3] = data[6]; + if (memcmp(hexserial, &cwc->cwc_ua[4], (data[7] == 0x10) ? 3 : 4) == 0) + match = 1; + } + else if (data[0] == 0x82) { // global + match = 1; + } + + if (match) + cwc_send_msg(cwc, data, len, 0, 1); +} + +void +cwc_emm_nds(cwc_t *cwc, uint8_t *data, int len) +{ + int match = 0; + int i; + int serial_count = ((data[3] >> 4) & 3) + 1; + unsigned char emmtype = (data[3] & 0xC0) >> 6; + + if (emmtype == 1 || emmtype == 2) { // unique|shared + for (i = 0; i < serial_count; i++) { + if (memcmp(&data[i * 4 + 4], &cwc->cwc_ua[4], 5 - emmtype) == 0) { + match = 1; + break; + } + } + } + else if (emmtype == 0) { // global + match = 1; + } + + if (match) + cwc_send_msg(cwc, data, len, 0, 1); +} /** * @@ -1667,7 +1735,7 @@ cwc_descramble(th_descrambler_t *td, service_t *t, struct elementary_stream *st, } /** - * global_lock is held + * cwc_mutex is held * s_stream_mutex is held */ static void @@ -1715,7 +1783,7 @@ cwc_find_stream_by_caid(service_t *t, int caid) /** * Check if our CAID's matches, and if so, link * - * global_lock is held + * global_lock is held. Not that we care about that, but either way, it is. */ void cwc_service_start(service_t *t) @@ -1724,7 +1792,7 @@ cwc_service_start(service_t *t) cwc_service_t *ct; th_descrambler_t *td; - lock_assert(&global_lock); + pthread_mutex_lock(&cwc_mutex); TAILQ_FOREACH(cwc, &cwcs, cwc_link) { if(cwc->cwc_caid == 0) continue; @@ -1753,6 +1821,7 @@ cwc_service_start(service_t *t) service_nicename(t), cwc->cwc_hostname, cwc->cwc_port); } + pthread_mutex_unlock(&cwc_mutex); } @@ -1762,10 +1831,11 @@ cwc_service_start(service_t *t) static void cwc_destroy(cwc_t *cwc) { - lock_assert(&global_lock); + pthread_mutex_lock(&cwc_mutex); TAILQ_REMOVE(&cwcs, cwc, cwc_link); cwc->cwc_running = 0; pthread_cond_signal(&cwc->cwc_cond); + pthread_mutex_unlock(&cwc_mutex); } @@ -1890,8 +1960,6 @@ cwc_entry_update(void *opaque, const char *id, htsmsg_t *values, int maycreate) if((cwc = cwc_entry_find(id, maycreate)) == NULL) return NULL; - lock_assert(&global_lock); - if((s = htsmsg_get_str(values, "username")) != NULL) { free(cwc->cwc_username); cwc->cwc_username = strdup(s); @@ -2025,6 +2093,7 @@ static const dtable_class_t cwc_dtc = { .dtc_record_delete = cwc_entry_delete, .dtc_read_access = ACCESS_ADMIN, .dtc_write_access = ACCESS_ADMIN, + .dtc_mutex = &cwc_mutex, }; @@ -2038,7 +2107,7 @@ cwc_init(void) dtable_t *dt; TAILQ_INIT(&cwcs); - + pthread_mutex_init(&cwc_mutex, NULL); pthread_cond_init(&cwc_config_changed, NULL); dt = dtable_create(&cwc_dtc, "cwc", NULL); diff --git a/src/dtable.h b/src/dtable.h index fd0b422d..5a480ac2 100644 --- a/src/dtable.h +++ b/src/dtable.h @@ -40,6 +40,8 @@ typedef struct dtable_class { int dtc_read_access; int dtc_write_access; + pthread_mutex_t *dtc_mutex; + } dtable_class_t; diff --git a/src/dvb/dvb.h b/src/dvb/dvb.h index 347fe31a..9c14e23f 100644 --- a/src/dvb/dvb.h +++ b/src/dvb/dvb.h @@ -200,6 +200,11 @@ typedef struct th_dvb_adapter { int tda_allpids_dmx_fd; int tda_dump_fd; + uint32_t tda_last_fec; + + int tda_unc_is_delta; /* 1 if we believe FE_READ_UNCORRECTED_BLOCKS + * return dela values */ + } th_dvb_adapter_t; diff --git a/src/dvb/dvb_fe.c b/src/dvb/dvb_fe.c index 23821134..9e7b7890 100644 --- a/src/dvb/dvb_fe.c +++ b/src/dvb/dvb_fe.c @@ -40,6 +40,42 @@ #include "notify.h" #include "dvr/dvr.h" +/** + * Return uncorrected block (since last read) + * + * Some adapters report delta themselfs, some return ever increasing value + * we need to deal with that ourselfs + */ +static int +dvb_fe_get_unc(th_dvb_adapter_t *tda) +{ + uint32_t fec; + int d, r; + + if(ioctl(tda->tda_fe_fd, FE_READ_UNCORRECTED_BLOCKS, &fec)) + return 0; // read failed, just say 0 + + if(tda->tda_unc_is_delta) + return fec; + + d = (int)fec - (int)tda->tda_last_fec; + + if(d < 0) { + tda->tda_unc_is_delta = 1; + tvhlog(LOG_DEBUG, "dvb", + "%s: FE_READ_UNCORRECTED_BLOCKS returns delta updates (delta=%d)", + tda->tda_displayname, d); + return fec; + } + + r = fec - tda->tda_last_fec; + + tda->tda_last_fec = fec; + return r; +} + + + /** * Front end monitor * @@ -79,7 +115,7 @@ dvb_fe_monitor(void *aux) if(status == -1) { /* We have a lock, don't hold off */ tda->tda_fe_monitor_hold = 0; /* Reset FEC counter */ - ioctl(tda->tda_fe_fd, FE_READ_UNCORRECTED_BLOCKS, &fec); + dvb_fe_get_unc(tda); } else { tda->tda_fe_monitor_hold--; return; @@ -89,8 +125,7 @@ dvb_fe_monitor(void *aux) if(status == -1) { /* Read FEC counter (delta) */ - if(ioctl(tda->tda_fe_fd, FE_READ_UNCORRECTED_BLOCKS, &fec)) - fec = 0; + fec = dvb_fe_get_unc(tda); tdmi->tdmi_fec_err_histogram[tdmi->tdmi_fec_err_ptr++] = fec; if(tdmi->tdmi_fec_err_ptr == TDMI_FEC_ERR_HISTOGRAM_SIZE) @@ -277,7 +312,7 @@ static int check_frontend (int fe_fd, int dvr, int human_readable) { (void)dvr; fe_status_t status; uint16_t snr, signal; - uint32_t ber, uncorrected_blocks; + uint32_t ber; int timeout = 0; do { @@ -292,15 +327,13 @@ static int check_frontend (int fe_fd, int dvr, int human_readable) { snr = -2; if (ioctl(fe_fd, FE_READ_BER, &ber) == -1) ber = -2; - if (ioctl(fe_fd, FE_READ_UNCORRECTED_BLOCKS, &uncorrected_blocks) == -1) - uncorrected_blocks = -2; if (human_readable) { - printf ("status %02x | signal %3u%% | snr %3u%% | ber %d | unc %d | ", - status, (signal * 100) / 0xffff, (snr * 100) / 0xffff, ber, uncorrected_blocks); + printf ("status %02x | signal %3u%% | snr %3u%% | ber %d | ", + status, (signal * 100) / 0xffff, (snr * 100) / 0xffff, ber); } else { - printf ("status %02x | signal %04x | snr %04x | ber %08x | unc %08x | ", - status, signal, snr, ber, uncorrected_blocks); + printf ("status %02x | signal %04x | snr %04x | ber %08x | ", + status, signal, snr, ber); } if (status & FE_HAS_LOCK) printf("FE_HAS_LOCK"); diff --git a/src/dvb/dvb_multiplex.c b/src/dvb/dvb_multiplex.c index 3d910d86..9756fd6e 100644 --- a/src/dvb/dvb_multiplex.c +++ b/src/dvb/dvb_multiplex.c @@ -1104,6 +1104,9 @@ dvb_mux_copy(th_dvb_adapter_t *dst, th_dvb_mux_instance_t *tdmi_src) if(t_src->s_svcname != NULL) t_dst->s_svcname = strdup(t_src->s_svcname); + if(t_src->s_dvb_default_charset != NULL) + t_dst->s_dvb_default_charset = strdup(t_src->s_dvb_default_charset); + if(t_src->s_ch != NULL) service_map_channel(t_dst, t_src->s_ch, 0); diff --git a/src/dvb/dvb_satconf.c b/src/dvb/dvb_satconf.c index 964754ea..6a41e35c 100644 --- a/src/dvb/dvb_satconf.c +++ b/src/dvb/dvb_satconf.c @@ -218,6 +218,7 @@ static const dtable_class_t satconf_dtc = { .dtc_record_delete = satconf_entry_delete, .dtc_read_access = ACCESS_ADMIN, .dtc_write_access = ACCESS_ADMIN, + .dtc_mutex = &global_lock, }; diff --git a/src/dvb/dvb_support.c b/src/dvb/dvb_support.c index b7098a55..3ad58cd5 100644 --- a/src/dvb/dvb_support.c +++ b/src/dvb/dvb_support.c @@ -74,7 +74,7 @@ dvb_conversion_init(void) */ int -dvb_get_string(char *dst, size_t dstlen, const uint8_t *src, size_t srclen) +dvb_get_string(char *dst, size_t dstlen, const uint8_t *src, size_t srclen, char *dvb_default_charset) { iconv_t ic; int len; @@ -121,7 +121,15 @@ dvb_get_string(char *dst, size_t dstlen, const uint8_t *src, size_t srclen) return -1; default: - ic = convert_latin1; + if (dvb_default_charset != NULL && sscanf(dvb_default_charset, "ISO8859-%d", &i) > 0) { + if (i > 0 && i < 16) { + ic = convert_iso_8859[i]; + } else { + ic = convert_latin1; + } + } else { + ic = convert_latin1; + } break; } @@ -175,14 +183,14 @@ dvb_get_string(char *dst, size_t dstlen, const uint8_t *src, size_t srclen) int dvb_get_string_with_len(char *dst, size_t dstlen, - const uint8_t *buf, size_t buflen) + const uint8_t *buf, size_t buflen, char *dvb_default_charset) { int l = buf[0]; if(l + 1 > buflen) return -1; - if(dvb_get_string(dst, dstlen, buf + 1, l)) + if(dvb_get_string(dst, dstlen, buf + 1, l, dvb_default_charset)) return -1; return l + 1; diff --git a/src/dvb/dvb_support.h b/src/dvb/dvb_support.h index 2cdf474b..7ec2d5ee 100644 --- a/src/dvb/dvb_support.h +++ b/src/dvb/dvb_support.h @@ -51,10 +51,10 @@ #define DVB_DESC_LOCAL_CHAN 0x83 int dvb_get_string(char *dst, size_t dstlen, const uint8_t *src, - const size_t srclen); + const size_t srclen, char *dvb_default_charset); int dvb_get_string_with_len(char *dst, size_t dstlen, - const uint8_t *buf, size_t buflen); + const uint8_t *buf, size_t buflen, char *dvb_default_charset); #define bcdtoint(i) ((((i & 0xf0) >> 4) * 10) + (i & 0x0f)) diff --git a/src/dvb/dvb_tables.c b/src/dvb/dvb_tables.c index de18cd02..f5b34b3d 100644 --- a/src/dvb/dvb_tables.c +++ b/src/dvb/dvb_tables.c @@ -369,7 +369,8 @@ tdt_add(th_dvb_mux_instance_t *tdmi, struct dmx_sct_filter_params *fparams, static int dvb_desc_short_event(uint8_t *ptr, int len, char *title, size_t titlelen, - char *desc, size_t desclen) + char *desc, size_t desclen, + char *dvb_default_charset) { int r; @@ -377,11 +378,11 @@ dvb_desc_short_event(uint8_t *ptr, int len, return -1; ptr += 3; len -= 3; - if((r = dvb_get_string_with_len(title, titlelen, ptr, len)) < 0) + if((r = dvb_get_string_with_len(title, titlelen, ptr, len, dvb_default_charset)) < 0) return -1; ptr += r; len -= r; - if((r = dvb_get_string_with_len(desc, desclen, ptr, len)) < 0) + if((r = dvb_get_string_with_len(desc, desclen, ptr, len, dvb_default_charset)) < 0) return -1; return 0; @@ -394,7 +395,8 @@ static int dvb_desc_extended_event(uint8_t *ptr, int len, char *desc, size_t desclen, char *item, size_t itemlen, - char *text, size_t textlen) + char *text, size_t textlen, + char *dvb_default_charset) { int count = ptr[4], r; uint8_t *localptr = ptr + 5, *items = localptr; @@ -434,7 +436,7 @@ dvb_desc_extended_event(uint8_t *ptr, int len, count = localptr[0]; /* get text */ - if((r = dvb_get_string_with_len(text, textlen, localptr, locallen)) < 0) + if((r = dvb_get_string_with_len(text, textlen, localptr, locallen, dvb_default_charset)) < 0) return -1; return 0; @@ -459,11 +461,11 @@ dvb_desc_service(uint8_t *ptr, int len, uint8_t *typep, ptr++; len--; - if((r = dvb_get_string_with_len(provider, providerlen, ptr, len)) < 0) + if((r = dvb_get_string_with_len(provider, providerlen, ptr, len, NULL)) < 0) return -1; ptr += r; len -= r; - if((r = dvb_get_string_with_len(name, namelen, ptr, len)) < 0) + if((r = dvb_get_string_with_len(name, namelen, ptr, len, NULL)) < 0) return -1; ptr += r; len -= r; return 0; @@ -482,13 +484,7 @@ dvb_eit_callback(th_dvb_mux_instance_t *tdmi, uint8_t *ptr, int len, th_dvb_adapter_t *tda = tdmi->tdmi_adapter; uint16_t serviceid; - int version; - uint8_t section_number; - uint8_t last_section_number; uint16_t transport_stream_id; - uint16_t original_network_id; - uint8_t segment_last_section_number; - uint8_t last_table_id; uint16_t event_id; time_t start_time, stop_time; @@ -513,13 +509,13 @@ dvb_eit_callback(th_dvb_mux_instance_t *tdmi, uint8_t *ptr, int len, return -1; serviceid = ptr[0] << 8 | ptr[1]; - version = ptr[2] >> 1 & 0x1f; - section_number = ptr[3]; - last_section_number = ptr[4]; + // version = ptr[2] >> 1 & 0x1f; + // section_number = ptr[3]; + // last_section_number = ptr[4]; transport_stream_id = ptr[5] << 8 | ptr[6]; - original_network_id = ptr[7] << 8 | ptr[8]; - segment_last_section_number = ptr[9]; - last_table_id = ptr[10]; + // original_network_id = ptr[7] << 8 | ptr[8]; + // segment_last_section_number = ptr[9]; + // last_table_id = ptr[10]; if((ptr[2] & 1) == 0) { /* current_next_indicator == next, skip this */ @@ -586,7 +582,8 @@ dvb_eit_callback(th_dvb_mux_instance_t *tdmi, uint8_t *ptr, int len, case DVB_DESC_SHORT_EVENT: if(!dvb_desc_short_event(ptr, dlen, title, sizeof(title), - desc, sizeof(desc))) { + desc, sizeof(desc), + t->s_dvb_default_charset)) { changed |= epg_event_set_title(e, title); changed |= epg_event_set_desc(e, desc); } @@ -602,7 +599,8 @@ dvb_eit_callback(th_dvb_mux_instance_t *tdmi, uint8_t *ptr, int len, if(!dvb_desc_extended_event(ptr, dlen, extdesc, sizeof(extdesc), extitem, sizeof(extitem), - exttext, sizeof(exttext))) { + exttext, sizeof(exttext), + t->s_dvb_default_charset)) { char language[4]; memcpy(language, &ptr[1], 3); @@ -640,15 +638,9 @@ dvb_sdt_callback(th_dvb_mux_instance_t *tdmi, uint8_t *ptr, int len, uint8_t tableid, void *opaque) { service_t *t; - int version; - uint8_t section_number; - uint8_t last_section_number; uint16_t service_id; uint16_t transport_stream_id; - uint16_t original_network_id; - - int reserved; - int running_status, free_ca_mode; + int free_ca_mode; int dllen; uint8_t dtag, dlen; @@ -665,11 +657,11 @@ dvb_sdt_callback(th_dvb_mux_instance_t *tdmi, uint8_t *ptr, int len, if(tdmi->tdmi_transport_stream_id != transport_stream_id) return -1; - version = ptr[2] >> 1 & 0x1f; - section_number = ptr[3]; - last_section_number = ptr[4]; - original_network_id = ptr[5] << 8 | ptr[6]; - reserved = ptr[7]; + // version = ptr[2] >> 1 & 0x1f; + // section_number = ptr[3]; + // last_section_number = ptr[4]; + // original_network_id = ptr[5] << 8 | ptr[6]; + // reserved = ptr[7]; if((ptr[2] & 1) == 0) { /* current_next_indicator == next, skip this */ @@ -682,8 +674,8 @@ dvb_sdt_callback(th_dvb_mux_instance_t *tdmi, uint8_t *ptr, int len, while(len >= 5) { service_id = ptr[0] << 8 | ptr[1]; - reserved = ptr[2]; - running_status = (ptr[3] >> 5) & 0x7; + // reserved = ptr[2]; + // running_status = (ptr[3] >> 5) & 0x7; free_ca_mode = (ptr[3] >> 4) & 0x1; dllen = ((ptr[3] & 0x0f) << 8) | ptr[4]; @@ -837,7 +829,6 @@ dvb_cat_callback(th_dvb_mux_instance_t *tdmi, uint8_t *ptr, int len, uint8_t tableid, void *opaque) { int tag, tlen; - uint16_t caid; uint16_t pid; if((ptr[2] & 1) == 0) { @@ -854,7 +845,7 @@ dvb_cat_callback(th_dvb_mux_instance_t *tdmi, uint8_t *ptr, int len, len -= 2; switch(tag) { case DVB_DESC_CA: - caid = ( ptr[0] << 8) | ptr[1]; + // caid = ( ptr[0] << 8) | ptr[1]; pid = ((ptr[2] & 0x1f) << 8) | ptr[3]; if(pid == 0) @@ -956,7 +947,7 @@ dvb_table_sat_delivery(th_dvb_mux_instance_t *tdmi, uint8_t *ptr, int len, uint16_t tsid) { int freq, symrate; - uint16_t orbital_pos; + // uint16_t orbital_pos; struct dvb_mux_conf dmc; if(!tdmi->tdmi_adapter->tda_autodiscovery) @@ -976,7 +967,7 @@ dvb_table_sat_delivery(th_dvb_mux_instance_t *tdmi, uint8_t *ptr, int len, if(!freq) return -1; - orbital_pos = bcdtoint(ptr[4]) * 100 + bcdtoint(ptr[5]); + // orbital_pos = bcdtoint(ptr[4]) * 100 + bcdtoint(ptr[5]); symrate = bcdtoint(ptr[7]) * 100000 + bcdtoint(ptr[8]) * 1000 + @@ -1122,7 +1113,7 @@ dvb_nit_callback(th_dvb_mux_instance_t *tdmi, uint8_t *ptr, int len, switch(tag) { case DVB_DESC_NETWORK_NAME: - if(dvb_get_string(networkname, sizeof(networkname), ptr, tlen)) + if(dvb_get_string(networkname, sizeof(networkname), ptr, tlen, NULL)) return -1; if(strcmp(tdmi->tdmi_network ?: "", networkname)) diff --git a/src/dvb/dvb_transport.c b/src/dvb/dvb_transport.c index b438d964..62b776bc 100644 --- a/src/dvb/dvb_transport.c +++ b/src/dvb/dvb_transport.c @@ -234,11 +234,14 @@ dvb_transport_load(th_dvb_mux_instance_t *tdmi) service_make_nicename(t); psi_load_service_settings(c, t); pthread_mutex_unlock(&t->s_stream_mutex); - + + s = htsmsg_get_str(c, "dvb_default_charset"); + t->s_dvb_default_charset = s ? strdup(s) : NULL; + s = htsmsg_get_str(c, "channelname"); if(htsmsg_get_u32(c, "mapped", &u32)) u32 = 0; - + if(s && u32) service_map_channel(t, channel_find_by_name(s, 1, 0), 0); } @@ -271,6 +274,9 @@ dvb_transport_save(service_t *t) htsmsg_add_str(m, "channelname", t->s_ch->ch_name); htsmsg_add_u32(m, "mapped", 1); } + + if(t->s_dvb_default_charset != NULL) + htsmsg_add_str(m, "dvb_default_charset", t->s_dvb_default_charset); pthread_mutex_lock(&t->s_stream_mutex); psi_save_service_settings(m, t); @@ -431,6 +437,9 @@ dvb_transport_build_msg(service_t *t) if(t->s_ch != NULL) htsmsg_add_str(m, "channelname", t->s_ch->ch_name); + if(t->s_dvb_default_charset != NULL) + htsmsg_add_str(m, "dvb_default_charset", t->s_dvb_default_charset); + return m; } diff --git a/src/dvr/dvr.h b/src/dvr/dvr.h index 888fc986..e8ebee9e 100644 --- a/src/dvr/dvr.h +++ b/src/dvr/dvr.h @@ -242,6 +242,8 @@ dvr_entry_t *dvr_entry_create(const char *dvr_config_name, epg_episode_t *ee, uint8_t content_type, dvr_prio_t pri); +dvr_entry_t *dvr_entry_update(dvr_entry_t *de, const char* de_title, int de_start, int de_stop); + void dvr_init(void); void dvr_autorec_init(void); @@ -274,6 +276,10 @@ void dvr_extra_time_pre_set(dvr_config_t *cfg, int d); void dvr_extra_time_post_set(dvr_config_t *cfg, int d); +void dvr_entry_delete(dvr_entry_t *de); + +void dvr_entry_cancel_delete(dvr_entry_t *de); + /** * Query interface */ diff --git a/src/dvr/dvr_autorec.c b/src/dvr/dvr_autorec.c index 91f18fa5..bd3e148b 100644 --- a/src/dvr/dvr_autorec.c +++ b/src/dvr/dvr_autorec.c @@ -403,6 +403,7 @@ static const dtable_class_t autorec_dtc = { .dtc_record_delete = autorec_record_delete, .dtc_read_access = ACCESS_RECORDER, .dtc_write_access = ACCESS_RECORDER, + .dtc_mutex = &global_lock, }; /** diff --git a/src/dvr/dvr_db.c b/src/dvr/dvr_db.c index c7933c62..efbc6c17 100644 --- a/src/dvr/dvr_db.c +++ b/src/dvr/dvr_db.c @@ -320,6 +320,20 @@ dvr_entry_create(const char *config_name, } +/** + * + */ +static const char * +longest_string(const char *a, const char *b) +{ + if(b == NULL) + return a; + if(a == NULL) + return b; + return strlen(a) > strlen(b) ? a : b; +} + + /** * */ @@ -328,12 +342,19 @@ dvr_entry_create_by_event(const char *config_name, event_t *e, const char *creator, dvr_autorec_entry_t *dae, dvr_prio_t pri) { + const char *desc = NULL; if(e->e_channel == NULL || e->e_title == NULL) return NULL; + // Try to find best description + + desc = longest_string(e->e_desc, e->e_ext_desc); + desc = longest_string(desc, e->e_ext_item); + desc = longest_string(desc, e->e_ext_text); + return dvr_entry_create(config_name, e->e_channel, e->e_start, e->e_stop, - e->e_title, e->e_desc, creator, dae, &e->e_episode, + e->e_title, desc, creator, dae, &e->e_episode, e->e_content_type, pri); } @@ -589,6 +610,26 @@ dvr_timer_expire(void *aux) } +/** + * + */ +dvr_entry_t * +dvr_entry_update(dvr_entry_t *de, const char* de_title, int de_start, int de_stop) +{ + + de->de_title = strdup(de_title); + de->de_start = de_start; + de->de_stop = de_stop; + + dvr_entry_save(de); + htsp_dvr_entry_update(de); + dvr_entry_notify(de); + + + tvhlog(LOG_INFO, "dvr", "\"%s\" on \"%s\": Updated Timer", de->de_title, de->de_channel->ch_name); + + return de; +} /** * @@ -834,9 +875,9 @@ dvr_init(void) } } - dvr_autorec_init(); - dvr_db_load(); + + dvr_autorec_init(); } /** @@ -1160,3 +1201,73 @@ dvr_val2pri(dvr_prio_t v) { return val2str(v, priotab) ?: "invalid"; } + + +/** + * + */ +void +dvr_entry_delete(dvr_entry_t *de) +{ + if(de->de_filename != NULL) { + if(unlink(de->de_filename) && errno != ENOENT) + tvhlog(LOG_WARNING, "dvr", "Unable to remove file '%s' from disk -- %s", + de->de_filename, strerror(errno)); + + /* Also delete directories, if they were created for the recording and if they are empty */ + + dvr_config_t *cfg = dvr_config_find_by_name_default(de->de_config_name); + char path[500]; + + snprintf(path, sizeof(path), "%s", cfg->dvr_storage); + + if(cfg->dvr_flags & DVR_DIR_PER_TITLE || cfg->dvr_flags & DVR_DIR_PER_CHANNEL || cfg->dvr_flags & DVR_DIR_PER_DAY) { + char *p; + int l; + + l = strlen(de->de_filename); + p = alloca(l + 1); + memcpy(p, de->de_filename, l); + p[l--] = 0; + + for(; l >= 0; l--) { + if(p[l] == '/') { + p[l] = 0; + if(strncmp(p, cfg->dvr_storage, strlen(p)) == 0) + break; + if(rmdir(p) == -1) + break; + } + } + } + + } + dvr_entry_remove(de); +} + +/** + * + */ +void +dvr_entry_cancel_delete(dvr_entry_t *de) +{ + switch(de->de_sched_state) { + case DVR_SCHEDULED: + dvr_entry_remove(de); + break; + + case DVR_RECORDING: + de->de_dont_reschedule = 1; + dvr_stop_recording(de, SM_CODE_ABORTED); + case DVR_COMPLETED: + dvr_entry_delete(de); + break; + + case DVR_MISSED_TIME: + dvr_entry_remove(de); + break; + + default: + abort(); + } +} diff --git a/src/epg.c b/src/epg.c index c5f05ba3..aac82ea9 100644 --- a/src/epg.c +++ b/src/epg.c @@ -16,6 +16,8 @@ * along with this program. If not, see . */ +#include +#include #include #include #include @@ -25,9 +27,11 @@ #include "tvheadend.h" #include "channels.h" +#include "settings.h" #include "epg.h" #include "dvr/dvr.h" #include "htsp.h" +#include "htsmsg_binary.h" #define EPG_MAX_AGE 86400 @@ -49,16 +53,20 @@ e_ch_cmp(const event_t *a, const event_t *b) * */ static void -epg_set_current(channel_t *ch, event_t *e) +epg_set_current(channel_t *ch, event_t *e, event_t *next) { - if(ch->ch_epg_current == e) + if(next == NULL && e != NULL) + next = RB_NEXT(e, e_channel_link); + + if(ch->ch_epg_current == e && ch->ch_epg_next == next) return; ch->ch_epg_current = e; + ch->ch_epg_next = next; if(e != NULL) gtimer_arm_abs(&ch->ch_epg_timer_current, epg_ch_check_current_event, ch, MAX(e->e_stop, dispatch_clock + 1)); - htsp_event_update(ch, e); + htsp_channgel_update_current(ch); } /** @@ -69,17 +77,16 @@ epg_ch_check_current_event(void *aux) { channel_t *ch = aux; event_t skel, *e = ch->ch_epg_current; - if(e != NULL) { if(e->e_start <= dispatch_clock && e->e_stop > dispatch_clock) { - epg_set_current(ch, e); + epg_set_current(ch, e, NULL); return; } if((e = RB_NEXT(e, e_channel_link)) != NULL) { if(e->e_start <= dispatch_clock && e->e_stop > dispatch_clock) { - epg_set_current(ch, e); + epg_set_current(ch, e, NULL); return; } } @@ -87,22 +94,22 @@ epg_ch_check_current_event(void *aux) e = epg_event_find_by_time(ch, dispatch_clock); if(e != NULL) { - epg_set_current(ch, e); + epg_set_current(ch, e, NULL); return; } - epg_set_current(ch, NULL); - skel.e_start = dispatch_clock; e = RB_FIND_GT(&ch->ch_epg_events, &skel, e_channel_link, e_ch_cmp); if(e != NULL) { gtimer_arm_abs(&ch->ch_epg_timer_current, epg_ch_check_current_event, ch, MAX(e->e_start, dispatch_clock + 1)); + epg_set_current(ch, NULL, e); + } else { + epg_set_current(ch, NULL, NULL); } } - /** * */ @@ -299,11 +306,13 @@ epg_remove_event_from_channel(channel_t *ch, event_t *e) epg_event_unref(e); if(ch->ch_epg_current == e) { - epg_set_current(ch, NULL); + epg_set_current(ch, NULL, n); if(n != NULL) gtimer_arm_abs(&ch->ch_epg_timer_current, epg_ch_check_current_event, ch, n->e_start); + } else if(ch->ch_epg_next == e) { + epg_set_current(ch, ch->ch_epg_current, NULL); } if(wasfirst && (e = RB_FIRST(&ch->ch_epg_events)) != NULL) { @@ -527,12 +536,192 @@ epg_content_group_find_by_name(const char *name) } -/* +/** + * + */ +static int +epg_event_create_by_msg(htsmsg_t *c, time_t now) +{ + channel_t *ch; + event_t *e = NULL; + uint32_t ch_id = 0; + uint32_t e_start = 0; + uint32_t e_stop = 0; + int e_dvb_id = 0, v; + const char *s; + + // Now create the event + if(htsmsg_get_u32(c, "ch_id", &ch_id)) + return 0; + + if((ch = channel_find_by_identifier(ch_id)) == NULL) + return 0; + + if(htsmsg_get_u32(c, "start", &e_start)) + return 0; + + if(htsmsg_get_u32(c, "stop", &e_stop)) + return 0; + + if(e_stop < now) + return 0; + + if(htsmsg_get_s32(c, "dvb_id", &e_dvb_id)) + e_dvb_id = -1; + + e = epg_event_create(ch, e_start, e_stop, e_dvb_id, NULL); + + if((s = htsmsg_get_str(c, "title")) != NULL) + epg_event_set_title(e, s); + + if((s = htsmsg_get_str(c, "desc")) != NULL) + epg_event_set_desc(e, s); + + if(!htsmsg_get_s32(c, "season", &v)) + e->e_episode.ee_season = v; + + if(!htsmsg_get_s32(c, "episode", &v)) + e->e_episode.ee_episode = v; + + if(!htsmsg_get_s32(c, "part", &v)) + e->e_episode.ee_part = v; + + if((s = htsmsg_get_str(c, "epname")) != NULL) + tvh_str_set(&e->e_episode.ee_onscreen, s); + + return 1; +} + + +/** + * + */ +static void +epg_load(void) +{ + struct stat st; + int fd = hts_settings_open_file(0, "epgdb"); + time_t now; + int created = 0; + + time(&now); + + if(fd == -1) + return; + + if(fstat(fd, &st)) { + close(fd); + return; + } + uint8_t *mem = mmap(NULL, st.st_size, PROT_READ, MAP_SHARED, fd, 0); + if(mem == MAP_FAILED) { + close(fd); + return; + } + const uint8_t *rp = mem; + size_t remain = st.st_size; + + while(remain > 4) { + int msglen = (rp[0] << 24) | (rp[1] << 16) | (rp[2] << 8) | rp[3]; + remain -= 4; + rp += 4; + + if(msglen > remain) { + tvhlog(LOG_ERR, "EPG", "Malformed EPG database, skipping some data"); + break; + } + htsmsg_t *m = htsmsg_binary_deserialize(rp, msglen, NULL); + + created += epg_event_create_by_msg(m, now); + + htsmsg_destroy(m); + rp += msglen; + remain -= msglen; + } + + munmap(mem, st.st_size); + close(fd); + tvhlog(LOG_NOTICE, "EPG", "Injected %d event from disk database", created); +} + +/** * */ void epg_init(void) { + channel_t *ch; + + epg_load(); + + RB_FOREACH(ch, &channel_name_tree, ch_name_link) + epg_ch_check_current_event(ch); +} + + +/** + * Save the epg on disk + */ +void +epg_save(void) +{ + event_t *e; + int num_saved = 0; + size_t msglen; + void *msgdata; + channel_t *ch; + + int fd = hts_settings_open_file(1, "epgdb"); + + RB_FOREACH(ch, &channel_name_tree, ch_name_link) { + RB_FOREACH(e, &ch->ch_epg_events, e_channel_link) { + if(!e->e_start || !e->e_stop) + continue; + + htsmsg_t *m = htsmsg_create_map(); + htsmsg_add_u32(m, "ch_id", ch->ch_id); + htsmsg_add_u32(m, "start", e->e_start); + htsmsg_add_u32(m, "stop", e->e_stop); + if(e->e_title != NULL) + htsmsg_add_str(m, "title", e->e_title); + if(e->e_desc != NULL) + htsmsg_add_str(m, "desc", e->e_desc); + if(e->e_dvb_id) + htsmsg_add_u32(m, "dvb_id", e->e_dvb_id); + + if(e->e_episode.ee_season) + htsmsg_add_u32(m, "season", e->e_episode.ee_season); + + if(e->e_episode.ee_episode) + htsmsg_add_u32(m, "episode", e->e_episode.ee_episode); + + if(e->e_episode.ee_part) + htsmsg_add_u32(m, "part", e->e_episode.ee_part); + + if(e->e_episode.ee_onscreen) + htsmsg_add_str(m, "epname", e->e_episode.ee_onscreen); + + + int r = htsmsg_binary_serialize(m, &msgdata, &msglen, 0x10000); + htsmsg_destroy(m); + + if(!r) { + ssize_t written = write(fd, msgdata, msglen); + int err = errno; + free(msgdata); + if(written != msglen) { + tvhlog(LOG_DEBUG, "epg", "Failed to store EPG on disk -- %s", + strerror(err)); + close(fd); + hts_settings_remove("epgdb"); + return; + } + } + num_saved++; + } + } + close(fd); + tvhlog(LOG_DEBUG, "EPG", "Stored EPG data for %d events on disk", num_saved); } diff --git a/src/epg.h b/src/epg.h index 017d7097..ebd7b0c9 100644 --- a/src/epg.h +++ b/src/epg.h @@ -20,6 +20,7 @@ #define EPG_H #include "channels.h" +#include "settings.h" @@ -68,6 +69,8 @@ typedef struct event { */ void epg_init(void); +void epg_save(void); + /** * All the epg_event_set_ function return 1 if it actually changed * the EPG records. otherwise it returns 0. @@ -79,11 +82,9 @@ void epg_init(void); * can combine multiple set()'s into one update * */ -int epg_event_set_title(event_t *e, const char *title) - __attribute__ ((warn_unused_result)); +int epg_event_set_title(event_t *e, const char *title); -int epg_event_set_desc(event_t *e, const char *desc) - __attribute__ ((warn_unused_result)); +int epg_event_set_desc(event_t *e, const char *desc); int epg_event_set_ext_desc(event_t *e, int ext_dn, const char *desc) __attribute__ ((warn_unused_result)); diff --git a/src/htsmsg_xml.c b/src/htsmsg_xml.c index 87ba6494..7c494770 100644 --- a/src/htsmsg_xml.c +++ b/src/htsmsg_xml.c @@ -637,7 +637,7 @@ htsmsg_xml_parse_cd0(xmlparser_t *xp, } if(cc == NULL) { - if(*src <= 32) { + if(*src < 32) { src++; continue; } diff --git a/src/htsp.c b/src/htsp.c index 9a49ad21..608b5001 100644 --- a/src/htsp.c +++ b/src/htsp.c @@ -309,6 +309,8 @@ htsp_build_channel(channel_t *ch, const char *method) htsmsg_add_u32(out, "eventId", ch->ch_epg_current != NULL ? ch->ch_epg_current->e_id : 0); + htsmsg_add_u32(out, "nextEventId", + ch->ch_epg_next ? ch->ch_epg_next->e_id : 0); LIST_FOREACH(ctm, &ch->ch_ctms, ctm_channel_link) { ct = ctm->ctm_tag; @@ -497,21 +499,55 @@ htsp_method_addDvrEntry(htsp_connection_t *htsp, htsmsg_t *in) dvr_entry_t *de; dvr_entry_sched_state_t dvr_status; const char *dvr_config_name; - - if(htsmsg_get_u32(in, "eventId", &eventid)) - return htsp_error("Missing argument 'eventId'"); - - if((e = epg_event_find_by_id(eventid)) == NULL) - return htsp_error("Event does not exist"); if((dvr_config_name = htsmsg_get_str(in, "configName")) == NULL) dvr_config_name = ""; - - //create the dvr entry - de = dvr_entry_create_by_event(dvr_config_name,e, - htsp->htsp_username ? - htsp->htsp_username : "anonymous", - NULL, DVR_PRIO_NORMAL); + + if(htsmsg_get_u32(in, "eventId", &eventid)) + eventid = -1; + + if ((e = epg_event_find_by_id(eventid)) == NULL) + { + uint32_t iChannelId, iStartTime, iStopTime, iPriority; + channel_t *channel; + const char *strTitle = NULL, *strDescription = NULL, *strCreator = NULL; + + // no event found with this event id. + // check if there is at least a start time, stop time, channel id and title set + if (htsmsg_get_u32(in, "channelId", &iChannelId) || + htsmsg_get_u32(in, "start", &iStartTime) || + htsmsg_get_u32(in, "stop", &iStopTime) || + (strTitle = htsmsg_get_str(in, "title")) == NULL) + { + // not enough info available to create a new entry + return htsp_error("Invalid arguments"); + } + + // invalid channel + if ((channel = channel_find_by_identifier(iChannelId)) == NULL) + return htsp_error("Channel does not exist"); + + // get the optional attributes + if (htsmsg_get_u32(in, "priority", &iPriority)) + iPriority = DVR_PRIO_NORMAL; + + if ((strDescription = htsmsg_get_str(in, "description")) == NULL) + strDescription = ""; + + if ((strCreator = htsmsg_get_str(in, "creator")) == NULL || strcmp(strCreator, "") == 0) + strCreator = htsp->htsp_username ? htsp->htsp_username : "anonymous"; + + // create the dvr entry + de = dvr_entry_create(dvr_config_name, channel, iStartTime, iStopTime, strTitle, strDescription, strCreator, NULL, NULL, 0, iPriority); + } + else + { + //create the dvr entry + de = dvr_entry_create_by_event(dvr_config_name,e, + htsp->htsp_username ? + htsp->htsp_username : "anonymous", + NULL, DVR_PRIO_NORMAL); + } dvr_status = de != NULL ? de->de_sched_state : DVR_NOSTATE; @@ -535,15 +571,53 @@ htsp_method_addDvrEntry(htsp_connection_t *htsp, htsmsg_t *in) } /** - * delete a Dvrentry + * update a Dvrentry */ -static htsmsg_t * -htsp_method_deleteDvrEntry(htsp_connection_t *htsp, htsmsg_t *in) +static htsmsg_t * +htsp_method_updateDvrEntry(htsp_connection_t *htsp, htsmsg_t *in) { htsmsg_t *out; uint32_t dvrEntryId; dvr_entry_t *de; + uint32_t start; + uint32_t stop; + const char *title = NULL; + if(htsmsg_get_u32(in, "id", &dvrEntryId)) + return htsp_error("Missing argument 'id'"); + + if( (de = dvr_entry_find_by_id(dvrEntryId)) == NULL) + return htsp_error("id not found"); + + if(htsmsg_get_u32(in, "start", &start)) + start = de->de_start; + + if(htsmsg_get_u32(in, "stop", &stop)) + stop = de->de_stop; + + title = htsmsg_get_str(in, "title"); + if (title == NULL) + title = de->de_title; + + de = dvr_entry_update(de, title, start, stop); + + //create response + out = htsmsg_create_map(); + htsmsg_add_u32(out, "success", 1); + + return out; +} + +/** + * cancel a Dvrentry + */ +static htsmsg_t * +htsp_method_cancelDvrEntry(htsp_connection_t *htsp, htsmsg_t *in) +{ + htsmsg_t *out; + uint32_t dvrEntryId; + dvr_entry_t *de; + if(htsmsg_get_u32(in, "id", &dvrEntryId)) return htsp_error("Missing argument 'id'"); @@ -555,7 +629,32 @@ htsp_method_deleteDvrEntry(htsp_connection_t *htsp, htsmsg_t *in) //create response out = htsmsg_create_map(); htsmsg_add_u32(out, "success", 1); - + + return out; +} + +/** + * delete a Dvrentry + */ +static htsmsg_t * +htsp_method_deleteDvrEntry(htsp_connection_t *htsp, htsmsg_t *in) +{ + htsmsg_t *out; + uint32_t dvrEntryId; + dvr_entry_t *de; + + if(htsmsg_get_u32(in, "id", &dvrEntryId)) + return htsp_error("Missing argument 'id'"); + + if( (de = dvr_entry_find_by_id(dvrEntryId)) == NULL) + return htsp_error("id not found"); + + dvr_entry_cancel_delete(de); + + //create response + out = htsmsg_create_map(); + htsmsg_add_u32(out, "success", 1); + return out; } @@ -925,6 +1024,8 @@ struct { { "unsubscribe", htsp_method_unsubscribe, ACCESS_STREAMING}, { "subscriptionChangeWeight", htsp_method_change_weight, ACCESS_STREAMING}, { "addDvrEntry", htsp_method_addDvrEntry, ACCESS_RECORDER}, + { "updateDvrEntry", htsp_method_updateDvrEntry, ACCESS_RECORDER}, + { "cancelDvrEntry", htsp_method_cancelDvrEntry, ACCESS_RECORDER}, { "deleteDvrEntry", htsp_method_deleteDvrEntry, ACCESS_RECORDER}, { "epgQuery", htsp_method_epgQuery, ACCESS_STREAMING}, @@ -1147,8 +1248,13 @@ htsp_write_scheduler(void *aux) /* ignore return value */ r = write(htsp->htsp_fd, dptr, dlen); + if(r != dlen) + tvhlog(LOG_INFO, "htsp", "%s: Write error -- %s", + htsp->htsp_logname, strerror(errno)); free(dptr); pthread_mutex_lock(&htsp->htsp_out_mutex); + if(r != dlen) + break; } pthread_mutex_unlock(&htsp->htsp_out_mutex); @@ -1256,7 +1362,7 @@ htsp_async_send(htsmsg_t *m) * global_lock is held */ void -htsp_event_update(channel_t *ch, event_t *e) +htsp_channgel_update_current(channel_t *ch) { htsmsg_t *m; time_t now; @@ -1266,10 +1372,10 @@ htsp_event_update(channel_t *ch, event_t *e) htsmsg_add_str(m, "method", "channelUpdate"); htsmsg_add_u32(m, "channelId", ch->ch_id); - if(e == NULL) - e = epg_event_find_by_time(ch, now); - - htsmsg_add_u32(m, "eventId", e ? e->e_id : 0); + htsmsg_add_u32(m, "eventId", + ch->ch_epg_current ? ch->ch_epg_current->e_id : 0); + htsmsg_add_u32(m, "nextEventId", + ch->ch_epg_next ? ch->ch_epg_next->e_id : 0); htsp_async_send(m); } @@ -1525,6 +1631,18 @@ htsp_subscription_start(htsp_subscription_t *hs, const streaming_start_t *ss) htsmsg_add_u32(c, "width", ssc->ssc_width); if(ssc->ssc_height) htsmsg_add_u32(c, "height", ssc->ssc_height); + if (ssc->ssc_aspect_num) + htsmsg_add_u32(c, "aspect_num", ssc->ssc_aspect_num); + if (ssc->ssc_aspect_den) + htsmsg_add_u32(c, "aspect_den", ssc->ssc_aspect_den); + } + + if (SCT_ISAUDIO(ssc->ssc_type)) + { + if (ssc->ssc_channels) + htsmsg_add_u32(c, "channels", ssc->ssc_channels); + if (ssc->ssc_sri) + htsmsg_add_u32(c, "rate", ssc->ssc_sri); } htsmsg_add_msg(streams, NULL, c); diff --git a/src/htsp.h b/src/htsp.h index db6ca8d7..7dcf33e6 100644 --- a/src/htsp.h +++ b/src/htsp.h @@ -24,7 +24,7 @@ void htsp_init(void); -void htsp_event_update(channel_t *ch, event_t *e); +void htsp_channgel_update_current(channel_t *ch); void htsp_channel_add(channel_t *ch); void htsp_channel_update(channel_t *ch); diff --git a/src/http.c b/src/http.c index ea8a4f98..f56f5f22 100644 --- a/src/http.c +++ b/src/http.c @@ -123,7 +123,8 @@ static const char * http_rc2str(int code) { switch(code) { - case HTTP_STATUS_OK: return "Ok"; + case HTTP_STATUS_OK: return "OK"; + case HTTP_STATUS_PARTIAL_CONTENT: return "Partial Content"; case HTTP_STATUS_NOT_FOUND: return "Not found"; case HTTP_STATUS_UNAUTHORIZED: return "Unauthorized"; case HTTP_STATUS_BAD_REQUEST: return "Bad request"; @@ -149,8 +150,10 @@ static const char *cachemonths[12] = { */ void http_send_header(http_connection_t *hc, int rc, const char *content, - int contentlen, const char *encoding, const char *location, - int maxage, const char *range) + int64_t contentlen, + const char *encoding, const char *location, + int maxage, const char *range, + const char *disposition) { struct tm tm0, *tm; htsbuf_queue_t hdrs; @@ -204,12 +207,15 @@ http_send_header(http_connection_t *hc, int rc, const char *content, htsbuf_qprintf(&hdrs, "Content-Type: %s\r\n", content); if(contentlen > 0) - htsbuf_qprintf(&hdrs, "Content-Length: %d\r\n", contentlen); + htsbuf_qprintf(&hdrs, "Content-Length: %"PRId64"\r\n", contentlen); if(range) { htsbuf_qprintf(&hdrs, "Accept-Ranges: %s\r\n", "bytes"); htsbuf_qprintf(&hdrs, "Content-Range: %s\r\n", range); } + + if(disposition != NULL) + htsbuf_qprintf(&hdrs, "Content-Disposition: %s\r\n", disposition); htsbuf_qprintf(&hdrs, "\r\n"); @@ -226,7 +232,7 @@ http_send_reply(http_connection_t *hc, int rc, const char *content, const char *encoding, const char *location, int maxage) { http_send_header(hc, rc, content, hc->hc_reply.hq_size, - encoding, location, maxage, 0); + encoding, location, maxage, 0, NULL); if(hc->hc_no_output) return; @@ -308,6 +314,15 @@ http_redirect(http_connection_t *hc, const char *location) int http_access_verify(http_connection_t *hc, int mask) { + const char *ticket_id = http_arg_get(&hc->hc_req_args, "ticket"); + + if(!access_ticket_verify(ticket_id, hc->hc_url)) { + tvhlog(LOG_INFO, "HTTP", "%s: using ticket %s for %s", + inet_ntoa(hc->hc_peer->sin_addr), ticket_id, + hc->hc_url); + return 0; + } + return access_verify(hc->hc_username, hc->hc_password, (struct sockaddr *)hc->hc_peer, mask); } @@ -318,7 +333,7 @@ http_access_verify(http_connection_t *hc, int mask) * Returns 1 if we should disconnect * */ -static void +static int http_exec(http_connection_t *hc, http_path_t *hp, char *remain) { int err; @@ -328,8 +343,12 @@ http_exec(http_connection_t *hc, http_path_t *hp, char *remain) else err = hp->hp_callback(hc, remain, hp->hp_opaque); + if(err == -1) + return 1; + if(err) http_error(hc, err); + return 0; } @@ -353,8 +372,7 @@ http_cmd_get(http_connection_t *hc) if(args != NULL) http_parse_get_args(hc, args); - http_exec(hc, hp, remain); - return 0; + return http_exec(hc, hp, remain); } @@ -416,8 +434,7 @@ http_cmd_post(http_connection_t *hc, htsbuf_queue_t *spill) http_error(hc, HTTP_STATUS_NOT_FOUND); return 0; } - http_exec(hc, hp, remain); - return 0; + return http_exec(hc, hp, remain); } diff --git a/src/http.h b/src/http.h index 8cc497c3..2a29b8a9 100644 --- a/src/http.h +++ b/src/http.h @@ -30,6 +30,7 @@ typedef struct http_arg { } http_arg_t; #define HTTP_STATUS_OK 200 +#define HTTP_STATUS_PARTIAL_CONTENT 206 #define HTTP_STATUS_FOUND 302 #define HTTP_STATUS_BAD_REQUEST 400 #define HTTP_STATUS_UNAUTHORIZED 401 @@ -80,11 +81,6 @@ typedef struct http_connection { char *hc_username; char *hc_password; - int hc_authenticated; /* Used by RTSP, it seems VLC does not - send authentication headers for each - command, so we just say that it's ok - if it has authenticated at least once */ - struct config_head *hc_user_config; int hc_no_output; @@ -116,8 +112,9 @@ void http_output_content(http_connection_t *hc, const char *content); void http_redirect(http_connection_t *hc, const char *location); void http_send_header(http_connection_t *hc, int rc, const char *content, - int contentlen, const char *encoding, - const char *location, int maxage, const char *range); + int64_t contentlen, const char *encoding, + const char *location, int maxage, const char *range, + const char *disposition); typedef int (http_callback_t)(http_connection_t *hc, const char *remain, void *opaque); diff --git a/src/iptv_input.c b/src/iptv_input.c index 70bdb6f4..44938edc 100644 --- a/src/iptv_input.c +++ b/src/iptv_input.c @@ -60,13 +60,18 @@ iptv_got_pat(const uint8_t *ptr, size_t len, void *aux) len -= 8; ptr += 8; - if(len < 4) - return; + while(len >= 4) { - prognum = ptr[0] << 8 | ptr[1]; - pmt = (ptr[2] & 0x1f) << 8 | ptr[3]; + prognum = ptr[0] << 8 | ptr[1]; + pmt = (ptr[2] & 0x1f) << 8 | ptr[3]; - t->s_pmt_pid = pmt; + if(prognum != 0) { + t->s_pmt_pid = pmt; + return; + } + ptr += 4; + len -= 4; + } } @@ -119,7 +124,7 @@ iptv_ts_input(service_t *t, const uint8_t *tsb) static void * iptv_thread(void *aux) { - int nfds, fd, r, j; + int nfds, fd, r, j, hlen; uint8_t tsb[65536], *buf; struct epoll_event ev; service_t *t; @@ -153,7 +158,17 @@ iptv_thread(void *aux) if((tsb[1] & 0x7f) != 33) continue; - int hlen = (tsb[0] & 0xf) * 4 + 12; + hlen = (tsb[0] & 0xf) * 4 + 12; + + if(tsb[0] & 0x10) { + // Extension (X bit) == true + + if(r < hlen + 4) + continue; // Packet size < hlen + extension header + + // Skip over extension header (last 2 bytes of header is length) + hlen += ((tsb[hlen + 2] << 8) | tsb[hlen + 3]) * 4; + } if(r < hlen || (r - hlen) % 188 != 0) continue; diff --git a/src/main.c b/src/main.c index 71afd566..17683a41 100644 --- a/src/main.c +++ b/src/main.c @@ -357,6 +357,8 @@ main(int argc, char **argv) pthread_mutex_lock(&global_lock); + time(&dispatch_clock); + trap_init(argv[0]); /** @@ -433,6 +435,8 @@ main(int argc, char **argv) mainloop(); + epg_save(); + tvhlog(LOG_NOTICE, "STOP", "Exiting HTS Tvheadend"); if(forkaway) diff --git a/src/parser_h264.c b/src/parser_h264.c index 0ab7e944..c77fc2d1 100644 --- a/src/parser_h264.c +++ b/src/parser_h264.c @@ -348,7 +348,7 @@ h264_decode_slice_header(elementary_stream_t *st, bitstream_t *bs, int *pkttype, int *duration, int *isfield) { h264_private_t *p; - int slice_type, pps_id, sps_id, fnum; + int slice_type, pps_id, sps_id; if((p = st->es_priv) == NULL) return -1; @@ -378,7 +378,7 @@ h264_decode_slice_header(elementary_stream_t *st, bitstream_t *bs, int *pkttype, if(p->sps[sps_id].max_frame_num_bits == 0) return -1; - fnum = read_bits(bs, p->sps[sps_id].max_frame_num_bits); + skip_bits(bs, p->sps[sps_id].max_frame_num_bits); int field = 0; int timebase = 180000; diff --git a/src/psi.c b/src/psi.c index 42231b7c..5c792a58 100644 --- a/src/psi.c +++ b/src/psi.c @@ -306,6 +306,9 @@ psi_desc_ca(service_t *t, const uint8_t *buffer, int size) i += nanolen; } break; + case 0x4a00://DRECrypt + provid = size < 4 ? 0 : buffer[4]; + break; default: provid = 0; break; diff --git a/src/service.c b/src/service.c index 68832e48..49e04bd7 100644 --- a/src/service.c +++ b/src/service.c @@ -465,6 +465,7 @@ service_destroy(service_t *t) free(t->s_identifier); free(t->s_svcname); free(t->s_provider); + free(t->s_dvb_default_charset); while((st = TAILQ_FIRST(&t->s_components)) != NULL) { TAILQ_REMOVE(&t->s_components, st, es_link); @@ -503,6 +504,7 @@ service_create(const char *identifier, int type, int source_type) t->s_refcount = 1; t->s_enabled = 1; t->s_pcr_last = PTS_UNSET; + t->s_dvb_default_charset = NULL; TAILQ_INIT(&t->s_components); streaming_pad_init(&t->s_streaming_pad); @@ -674,6 +676,23 @@ service_map_channel(service_t *t, channel_t *ch, int save) t->s_config_save(t); } +/** + * + */ +void +service_set_dvb_default_charset(service_t *t, const char *dvb_default_charset) +{ + lock_assert(&global_lock); + + if(t->s_dvb_default_charset != NULL && !strcmp(t->s_dvb_default_charset, dvb_default_charset)) + return; + + free(t->s_dvb_default_charset); + t->s_dvb_default_charset = strdup(dvb_default_charset); + t->s_config_save(t); +} + + /** * */ @@ -720,6 +739,14 @@ service_is_tv(service_t *t) t->s_servicetype == ST_AC_HDTV; } +/** + * + */ +int +service_is_radio(service_t *t) +{ + return t->s_servicetype == ST_RADIO; +} /** * diff --git a/src/service.h b/src/service.h index b2a687f4..4b8458e5 100644 --- a/src/service.h +++ b/src/service.h @@ -476,6 +476,12 @@ typedef struct service { int64_t s_current_pts; + /** + * DVB default charset + * used to overide the default ISO6937 per service + */ + char *s_dvb_default_charset; + } service_t; @@ -516,6 +522,8 @@ const char *service_servicetype_txt(service_t *t); int service_is_tv(service_t *t); +int service_is_radio(service_t *t); + void service_destroy(service_t *t); void service_remove_subscriber(service_t *t, struct th_subscription *s, @@ -559,5 +567,6 @@ uint16_t service_get_encryption(service_t *t); int service_get_signal_status(service_t *t, signal_status_t *status); +void service_set_dvb_default_charset(service_t *t, const char *dvb_default_charset); #endif // SERVICE_H__ diff --git a/src/serviceprobe.c b/src/serviceprobe.c index 5b144396..826f7965 100644 --- a/src/serviceprobe.c +++ b/src/serviceprobe.c @@ -44,8 +44,8 @@ static pthread_cond_t serviceprobe_cond; void serviceprobe_enqueue(service_t *t) { - if(!service_is_tv(t)) - return; /* Don't even consider non-tv channels */ + if(!service_is_tv(t) && !service_is_radio(t)) + return; /* Don't even consider non-tv/non-radio channels */ if(t->s_sp_onqueue) return; @@ -170,9 +170,11 @@ serviceprobe_thread(void *aux) tvhlog(LOG_INFO, "serviceprobe", "%20s: mapped to channel \"%s\"", t->s_svcname, t->s_svcname); - channel_tag_map(ch, channel_tag_find_by_name("TV channels", 1), 1); - tvhlog(LOG_INFO, "serviceprobe", "%20s: joined tag \"%s\"", - t->s_svcname, "TV channels"); + if(service_is_tv(t)) { + channel_tag_map(ch, channel_tag_find_by_name("TV channels", 1), 1); + tvhlog(LOG_INFO, "serviceprobe", "%20s: joined tag \"%s\"", + t->s_svcname, "TV channels"); + } switch(t->s_servicetype) { case ST_SDTV: @@ -183,6 +185,9 @@ serviceprobe_thread(void *aux) case ST_AC_HDTV: str = "HDTV"; break; + case ST_RADIO: + str = "Radio"; + break; default: str = NULL; } diff --git a/src/settings.c b/src/settings.c index 1c5128b3..ca3c7270 100644 --- a/src/settings.c +++ b/src/settings.c @@ -284,3 +284,57 @@ hts_settings_remove(const char *pathfmt, ...) return; unlink(fullpath); } + + +/** + * + */ +int +hts_settings_open_file(int for_write, const char *pathfmt, ...) +{ + char path[256]; + char fullpath[256]; + int x, l; + va_list ap; + struct stat st; + char *n; + + if(settingspath == NULL) + return -1; + + va_start(ap, pathfmt); + vsnprintf(path, sizeof(path), pathfmt, ap); + va_end(ap); + + n = path; + + while(*n) { + if(*n == ':' || *n == '?' || *n == '*' || *n > 127 || *n < 32) + *n = '_'; + n++; + } + + l = strlen(path); + + for(x = 0; x < l; x++) { + if(path[x] == '/') { + /* It's a directory here */ + + path[x] = 0; + snprintf(fullpath, sizeof(fullpath), "%s/%s", settingspath, path); + + if(stat(fullpath, &st) && mkdir(fullpath, 0700)) { + tvhlog(LOG_ALERT, "settings", "Unable to create dir \"%s\": %s", + fullpath, strerror(errno)); + return -1; + } + path[x] = '/'; + } + } + + snprintf(fullpath, sizeof(fullpath), "%s/%s", settingspath, path); + + int flags = for_write ? O_CREAT | O_TRUNC | O_WRONLY : O_RDONLY; + + return tvh_open(fullpath, flags, 0700); +} diff --git a/src/settings.h b/src/settings.h index 6be8cffe..1713d9d2 100644 --- a/src/settings.h +++ b/src/settings.h @@ -32,4 +32,6 @@ void hts_settings_remove(const char *pathfmt, ...); const char *hts_settings_get_root(void); +int hts_settings_open_file(int for_write, const char *pathfmt, ...); + #endif /* HTSSETTINGS_H__ */ diff --git a/src/tcp.c b/src/tcp.c index 171a4dc6..27ed5602 100644 --- a/src/tcp.c +++ b/src/tcp.c @@ -181,18 +181,18 @@ int tcp_write_queue(int fd, htsbuf_queue_t *q) { htsbuf_data_t *hd; - int l, r; + int l, r = 0; while((hd = TAILQ_FIRST(&q->hq_q)) != NULL) { TAILQ_REMOVE(&q->hq_q, hd, hd_link); l = hd->hd_data_len - hd->hd_data_off; - r = write(fd, hd->hd_data + hd->hd_data_off, l); + r |= !!write(fd, hd->hd_data + hd->hd_data_off, l); free(hd->hd_data); free(hd); } q->hq_size = 0; - return 0; + return r; } diff --git a/src/teletext.c b/src/teletext.c index 86057d76..ff7a72db 100644 --- a/src/teletext.c +++ b/src/teletext.c @@ -375,7 +375,7 @@ tt_subtitle_deliver(service_t *t, elementary_stream_t *parent, tt_mag_t *ttm) static void tt_decode_line(service_t *t, elementary_stream_t *st, uint8_t *buf) { - uint8_t mpag, line, s12, s34, c; + uint8_t mpag, line, s12, c; int page, magidx, i; tt_mag_t *ttm; tt_private_t *ttp; @@ -416,7 +416,6 @@ tt_decode_line(service_t *t, elementary_stream_t *st, uint8_t *buf) ttm->ttm_curpage = page; s12 = ham_decode(buf[4], buf[5]); - s34 = ham_decode(buf[6], buf[7]); c = ham_decode(buf[8], buf[9]); ttm->ttm_lang = c >> 5; diff --git a/src/trap.c b/src/trap.c index 0184f1f8..1e92497d 100644 --- a/src/trap.c +++ b/src/trap.c @@ -30,6 +30,7 @@ char tvh_binshasum[20]; #include #include #include +#include #if ENABLE_EXECINFO #include #endif @@ -49,6 +50,7 @@ char tvh_binshasum[20]; static char line1[200]; static char tmpbuf[1024]; static char libs[1024]; +static char self[PATH_MAX]; static void sappend(char *buf, size_t l, const char *fmt, ...) @@ -62,10 +64,76 @@ sappend(char *buf, size_t l, const char *fmt, ...) +/** + * + */ +static int +add2lineresolve(const char *binary, void *addr, char *buf0, size_t buflen) +{ + char *buf = buf0; + int fd[2], r, f; + const char *argv[5]; + pid_t p; + char addrstr[30], *cp; + + argv[0] = "addr2line"; + argv[1] = "-e"; + argv[2] = binary; + argv[3] = addrstr; + argv[4] = NULL; + + snprintf(addrstr, sizeof(addrstr), "%p", (void *)((intptr_t)addr-1)); + + if(pipe(fd) == -1) + return -1; + + if((p = fork()) == -1) + return -1; + + if(p == 0) { + close(0); + close(2); + close(fd[0]); + dup2(fd[1], 1); + close(fd[1]); + if((f = open("/dev/null", O_RDWR)) == -1) + exit(1); + + dup2(f, 0); + dup2(f, 2); + close(f); + + execve("/usr/bin/addr2line", (char *const *) argv, environ); + exit(2); + } + + close(fd[1]); + *buf = 0; + while(buflen > 1) { + r = read(fd[0], buf, buflen); + if(r < 1) + break; + + buf += r; + buflen -= r; + *buf = 0; + cp = strchr(buf0, '\n'); + if(cp != NULL) { + *cp = 0; + break; + } + } + close(fd[0]); + return 0; +} + + + static void traphandler(int sig, siginfo_t *si, void *UC) { ucontext_t *uc = UC; + char buf[200]; #if ENABLE_EXECINFO static void *frames[MAXFRAMES]; int nframes = backtrace(frames, MAXFRAMES); @@ -122,6 +190,11 @@ traphandler(int sig, siginfo_t *si, void *UC) continue; } + if(self[0] && !add2lineresolve(self, frames[i], buf, sizeof(buf))) { + tvhlog_spawn(LOG_ALERT, "CRASH", "%s %p", buf, frames[i]); + continue; + } + if(dli.dli_fname != NULL && dli.dli_fbase != NULL) { tvhlog_spawn(LOG_ALERT, "CRASH", "%s %p", dli.dli_fname, @@ -153,6 +226,7 @@ extern const char *htsversion_full; void trap_init(const char *ver) { + int r; uint8_t digest[20]; struct sigaction sa, old; char path[256]; @@ -160,6 +234,11 @@ trap_init(const char *ver) SHA_CTX binsum; int fd; + r = readlink("/proc/self/exe", self, sizeof(self) - 1); + if(r == -1) + self[0] = 0; + else + self[r] = 0; if((fd = open("/proc/self/exe", O_RDONLY)) != -1) { struct stat st; diff --git a/src/webui/extjs.c b/src/webui/extjs.c index 58b9cc81..cd95ceb4 100644 --- a/src/webui/extjs.c +++ b/src/webui/extjs.c @@ -152,7 +152,7 @@ extjs_root(http_connection_t *hc, const char *remain, void *opaque) "\tpadding:0;\n" "\tborder:0 none;\n" "\toverflow:hidden;\n" - "\theight:100%;\n" + "\theight:100%%;\n" "}\n" "#systemlog {\n" "\tfont:normal 12px courier; font-weight: bold;\n" @@ -230,7 +230,7 @@ extjs_tablemgr(http_connection_t *hc, const char *remain, void *opaque) in = entries != NULL ? htsmsg_json_deserialize(entries) : NULL; - pthread_mutex_lock(&global_lock); + pthread_mutex_lock(dt->dt_dtc->dtc_mutex); if(!strcmp(op, "create")) { if(http_access_verify(hc, dt->dt_dtc->dtc_write_access)) @@ -264,15 +264,15 @@ extjs_tablemgr(http_connection_t *hc, const char *remain, void *opaque) } else { bad: - pthread_mutex_unlock(&global_lock); + pthread_mutex_unlock(dt->dt_dtc->dtc_mutex); return HTTP_STATUS_BAD_REQUEST; noaccess: - pthread_mutex_unlock(&global_lock); + pthread_mutex_unlock(dt->dt_dtc->dtc_mutex); return HTTP_STATUS_BAD_REQUEST; } - pthread_mutex_unlock(&global_lock); + pthread_mutex_unlock(dt->dt_dtc->dtc_mutex); if(in != NULL) htsmsg_destroy(in); @@ -805,6 +805,19 @@ extjs_dvr(http_connection_t *hc, const char *remain, void *opaque) out = htsmsg_create_map(); htsmsg_add_u32(out, "success", 1); + } else if(!strcmp(op, "deleteEntry")) { + s = http_arg_get(&hc->hc_req_args, "entryId"); + + if((de = dvr_entry_find_by_id(atoi(s))) == NULL) { + pthread_mutex_unlock(&global_lock); + return HTTP_STATUS_BAD_REQUEST; + } + + dvr_entry_delete(de); + + out = htsmsg_create_map(); + htsmsg_add_u32(out, "success", 1); + } else if(!strcmp(op, "createEntry")) { const char *config_name = http_arg_get(&hc->hc_req_args, "config_name"); @@ -1103,6 +1116,7 @@ service_update(htsmsg_t *in) uint32_t u32; const char *id; const char *chname; + const char *dvb_default_charset; TAILQ_FOREACH(f, &in->hm_fields, hmf_link) { if((c = htsmsg_get_map_by_field(f)) == NULL || @@ -1117,6 +1131,9 @@ service_update(htsmsg_t *in) if((chname = htsmsg_get_str(c, "channelname")) != NULL) service_map_channel(t, channel_find_by_name(chname, 1, 0), 1); + + if((dvb_default_charset = htsmsg_get_str(c, "dvb_default_charset")) != NULL) + service_set_dvb_default_charset(t, dvb_default_charset); } } @@ -1198,6 +1215,9 @@ extjs_servicedetails(http_connection_t *hc, htsmsg_add_msg(out, "streams", streams); + if(t->s_dvb_default_charset != NULL) + htsmsg_add_str(out, "dvb_default_charset", t->s_dvb_default_charset); + pthread_mutex_unlock(&global_lock); htsmsg_json_serialize(out, hq, 0); @@ -1422,6 +1442,7 @@ extjs_service_update(htsmsg_t *in) uint32_t u32; const char *id; const char *chname; + const char *dvb_default_charset; TAILQ_FOREACH(f, &in->hm_fields, hmf_link) { if((c = htsmsg_get_map_by_field(f)) == NULL || @@ -1436,6 +1457,9 @@ extjs_service_update(htsmsg_t *in) if((chname = htsmsg_get_str(c, "channelname")) != NULL) service_map_channel(t, channel_find_by_name(chname, 1, 0), 1); + + if((dvb_default_charset = htsmsg_get_str(c, "dvb_default_charset")) != NULL) + service_set_dvb_default_charset(t, dvb_default_charset); } } diff --git a/src/webui/simpleui.c b/src/webui/simpleui.c index 197825af..8e322f6d 100644 --- a/src/webui/simpleui.c +++ b/src/webui/simpleui.c @@ -383,7 +383,7 @@ page_status(http_connection_t *hc, if (DVR_SCHEDULED == de->de_sched_state) { - timelefttemp = (int) (de->de_start - now) / 60; // output minutes + timelefttemp = (int) ((de->de_start - now) / 60) - de->de_start_extra; // output minutes if (timelefttemp < timeleft) timeleft = timelefttemp; } @@ -398,19 +398,23 @@ page_status(http_connection_t *hc, "%02d/%02d/%02d" "" "%d" + "%d" "" "" "%02d/%02d/%02d" "" "%d" + "%d" "" "%s", a.tm_year + 1900, a.tm_mon, a.tm_mday, a.tm_hour, a.tm_min, de->de_start, + de->de_start_extra, b.tm_year+1900, b.tm_mon, b.tm_mday, b.tm_hour, b.tm_min, de->de_stop, + de->de_stop_extra, de->de_title); rstatus = val2str(de->de_sched_state, recstatustxt); diff --git a/src/webui/static/app/chconf.js b/src/webui/static/app/chconf.js index c2f10f5c..18476ec7 100644 --- a/src/webui/static/app/chconf.js +++ b/src/webui/static/app/chconf.js @@ -30,6 +30,7 @@ tvheadend.channels = new Ext.data.JsonStore({ fields: ['name', 'chid', 'xmltvsrc', 'tags', 'ch_icon', 'epg_pre_start', 'epg_post_end', 'number'], id: 'chid', + sortInfo: { field: 'number', direction: "ASC" }, url: "channels", baseParams: { op: 'list' @@ -163,7 +164,7 @@ tvheadend.chconf = function() dataIndex: 'chid', width: 50, renderer: function(value, metadata, record, row, col, store) { - url = 'stream/channelid/' + value + url = 'playlist/channelid/' + value return "Play" } }, diff --git a/src/webui/static/app/dvb.js b/src/webui/static/app/dvb.js index d0c257b0..9063cdbc 100644 --- a/src/webui/static/app/dvb.js +++ b/src/webui/static/app/dvb.js @@ -371,7 +371,7 @@ tvheadend.dvb_services = function(adapterId) { dataIndex: 'id', width: 50, renderer: function(value, metadata, record, row, col, store) { - url = makeRTSPprefix() + 'service/' + value + url = 'stream/service/' + value return 'Play' } }, @@ -394,6 +394,45 @@ tvheadend.dvb_services = function(adapterId) { displayField:'name' }) }, + { + header: "DVB default charset", + dataIndex: 'dvb_default_charset', + width: 200, + renderer: function(value, metadata, record, row, col, store) { + return value ? value : + 'ISO6937'; + }, + editor: new fm.ComboBox({ + mode: 'local', + store: new Ext.data.SimpleStore({ + fields: ['key','value'], + data: [ + ['ISO6937','default'], + ['ISO6937','ISO6937'], + ['ISO8859-1','ISO8859-1'], + ['ISO8859-2','ISO8859-2'], + ['ISO8859-3','ISO8859-3'], + ['ISO8859-4','ISO8859-4'], + ['ISO8859-5','ISO8859-5'], + ['ISO8859-6','ISO8859-6'], + ['ISO8859-7','ISO8859-7'], + ['ISO8859-8','ISO8859-8'], + ['ISO8859-9','ISO8859-9'], + ['ISO8859-10','ISO8859-10'], + ['ISO8859-11','ISO8859-11'], + ['ISO8859-12','ISO8859-12'], + ['ISO8859-13','ISO8859-13'], + ['ISO8859-14','ISO8859-14'], + ['ISO8859-15','ISO8859-15'] + ] + }), + typeAhead: true, + lazyRender: true, + triggerAction: 'all', + displayField:'value', + valueField:'key' + }) + }, { header: "Type", dataIndex: 'type', @@ -440,7 +479,7 @@ tvheadend.dvb_services = function(adapterId) { root: 'entries', fields: Ext.data.Record.create([ 'id', 'enabled', 'type', 'sid', 'pmt', 'pcr', - 'svcname', 'network', 'provider', 'mux', 'channelname' + 'svcname', 'network', 'provider', 'mux', 'channelname', 'dvb_default_charset' ]), url: "dvb/services/" + adapterId, autoLoad: true, diff --git a/src/webui/static/app/dvr.js b/src/webui/static/app/dvr.js index 10fed19a..74de7cf2 100644 --- a/src/webui/static/app/dvr.js +++ b/src/webui/static/app/dvr.js @@ -69,7 +69,7 @@ tvheadend.dvrDetails = function(entry) { content += '
' + 'Download '+ parseInt(entry.filesize/1000000) + ' MB
' + - "Play" + + "Play" + '
'; } @@ -98,6 +98,13 @@ tvheadend.dvrDetails = function(entry) { text: "Abort recording" }); break; + case 'completedError': + case 'completed': + win.addButton({ + handler: deleteEvent, + text: "Delete recording" + }); + break; } @@ -120,6 +127,21 @@ tvheadend.dvrDetails = function(entry) { }); } + function deleteEvent() { + Ext.Ajax.request({ + url: 'dvr', + params: {entryId: entry.id, op: 'deleteEntry'}, + + success:function(response, options) { + win.close();v + }, + + failure:function(response, options) { + Ext.MessageBox.alert('DVR', response.statusText); + } + }); + } + } /** diff --git a/src/webui/static/app/tvheadend.js b/src/webui/static/app/tvheadend.js index a2047b09..1066f6f8 100644 --- a/src/webui/static/app/tvheadend.js +++ b/src/webui/static/app/tvheadend.js @@ -31,19 +31,29 @@ tvheadend.help = function(title, pagename) { * Displays a mediaplayer using VLC plugin */ tvheadend.VLC = function(url) { + + function randomString() { + var chars = "ABCDEFGHIJKLMNOPQRSTUVWXTZabcdefghiklmnopqrstuvwxyz"; + var string_length = 8; + var randomstring = ''; + for (var i=0; i' + chName + ''; - missingPlugin.innerHTML = '

You are missing a plugin for your browser.

'; - missingPlugin.innerHTML += '

You can still watch ' + chUrl + ' using an external player.

'; - missingPlugin.style.display = 'block'; - return; + missingPlugin.innerHTML = '

Embedded player could not be started.
You are probably missing VLC Mozilla plugin for your browser.

'; + missingPlugin.innerHTML += '

M3U Playlist

'; + missingPlugin.innerHTML += '

Direct URL

'; } - - vlc.style.display = 'block'; - - if(vlc.playlist && vlc.playlist.isPlaying) { + else { vlc.playlist.stop(); - } - if(vlc.playlist && vlc.playlist.items.count) { - vlc.playlist.items.clear(); - } - - vlc.playlist.add(url, chName, ""); - vlc.playlist.play(); - vlc.audio.volume = slider.getValue(); + vlc.playlist.items.clear(); + vlc.playlist.add(streamurl); + vlc.playlist.playItem(0); + vlc.audio.volume = slider.getValue(); + } } ); @@ -117,7 +120,7 @@ tvheadend.VLC = function(url) { height: 384 + 56, constrainHeader: true, iconCls: 'eye', - resizable: false, + resizable: true, tbar: [ selectChannel, '-', @@ -143,9 +146,8 @@ tvheadend.VLC = function(url) { iconCls: 'control_stop', tooltip: 'Stop', handler: function() { - if(vlc.playlist && vlc.playlist.items.count) { + if(vlc.playlist) { vlc.playlist.stop(); - vlc.style.display = 'none'; } } }, @@ -154,9 +156,12 @@ tvheadend.VLC = function(url) { iconCls: 'control_fullscreen', tooltip: 'Fullscreen', handler: function() { - if(vlc.playlist && vlc.playlist.isPlaying) { + if(vlc.playlist && vlc.playlist.isPlaying && (vlc.VersionInfo.substr(0,3) != '1.1')) { vlc.video.toggleFullscreen(); } + else if (vlc.VersionInfo.substr(0,3) == '1.1') { + alert('Fullscreen mode is broken in VLC 1.1.x'); + } } }, '-', @@ -169,12 +174,45 @@ tvheadend.VLC = function(url) { items: [vlc, missingPlugin] }); - win.on('render', function() { + win.on('beforeShow', function() { win.getTopToolbar().add(slider); win.getTopToolbar().add(new Ext.Toolbar.Spacer()); win.getTopToolbar().add(new Ext.Toolbar.Spacer()); win.getTopToolbar().add(new Ext.Toolbar.Spacer()); win.getTopToolbar().add(sliderLabel); + + // check if vlc plugin wasn't initialised correctly + if(!vlc.playlist || (vlc.playlist == 'undefined')) { + vlc.style.display = 'none'; + + missingPlugin.innerHTML = '

Embedded player could not be started.
You are probably missing VLC Mozilla plugin for your browser.

'; + + if (url) { + var channelid = url.substr(url.lastIndexOf('/')); + var streamurl = 'stream/channelid/' + channelid; + var playlisturl = 'playlist/channelid/' + channelid; + missingPlugin.innerHTML += '

M3U Playlist

'; + missingPlugin.innerHTML += '

Direct URL

'; + } + + missingPlugin.style.display = 'block'; + } + else { + // check if the window was opened with an url-parameter + if (url) { + vlc.playlist.items.clear(); + vlc.playlist.add(url); + vlc.playlist.playItem(0); + + //enable yadif2x deinterlacer for vlc > 1.1 + var point1 = vlc.VersionInfo.indexOf('.'); + var point2 = vlc.VersionInfo.indexOf('.', point1+1); + var majVersion = vlc.VersionInfo.substring(0,point1); + var minVersion = vlc.VersionInfo.substring(point1+1,point2); + if ((majVersion >= 1) && (minVersion >= 1)) + vlc.video.deinterlace.enable("yadif2x"); + } + } }); win.show(); diff --git a/src/webui/webui.c b/src/webui/webui.c index eda7f4bd..2d812615 100644 --- a/src/webui/webui.c +++ b/src/webui/webui.c @@ -115,41 +115,12 @@ page_static_file(http_connection_t *hc, const char *remain, void *opaque) return 404; } - http_send_header(hc, 200, content, st.st_size, NULL, NULL, 10, 0); + http_send_header(hc, 200, content, st.st_size, NULL, NULL, 10, 0, NULL); sendfile(hc->hc_fd, fd, NULL, st.st_size); close(fd); return 0; } -/** - * Playlist (.pls format) for the rtsp streams - */ -static int -page_rtsp_playlist(http_connection_t *hc, const char *remain, void *opaque) -{ - htsbuf_queue_t *hq = &hc->hc_reply; - channel_t *ch = NULL; - int i = 0; - const char *host = http_arg_get(&hc->hc_args, "Host"); - - htsbuf_qprintf(hq, "[playlist]\n"); - - scopedgloballock(); - - RB_FOREACH(ch, &channel_name_tree, ch_name_link) { - i++; - htsbuf_qprintf(hq, "File%d=rtsp://%s/channelid/%d\n", i, host, ch->ch_id); - htsbuf_qprintf(hq, "Title%d=%s\n", i, ch->ch_name); - htsbuf_qprintf(hq, "Length%d=%d\n\n", i, -1); - } - - htsbuf_qprintf(hq, "NumberOfEntries=%d\n\n", i); - htsbuf_qprintf(hq, "Version=2"); - - http_output_content(hc, "audio/x-scpls; charset=UTF-8"); - return 0; -} - /** * HTTP stream loop */ @@ -219,6 +190,10 @@ http_stream_run(http_connection_t *hc, streaming_queue_t *sq) pat_ts[4] = 0x00; run = (write(hc->hc_fd, pat_ts, 188) == 188); + if(!run) { + break; + } + //Send PMT memset(pmt_ts, 0xff, 188); psi_build_pmt(ss, pmt_ts+5, 183, pcrpid); @@ -262,12 +237,15 @@ http_stream_run(http_connection_t *hc, streaming_queue_t *sq) } /** - * Playlist with http streams (.m3u format) + * Output a playlist with http streams for a channel (.m3u format) */ -static void -http_stream_playlist(http_connection_t *hc) +static int +http_stream_playlist(http_connection_t *hc, channel_t *channel) { htsbuf_queue_t *hq = &hc->hc_reply; + char buf[255]; + const char *ticket_id = NULL; + channel_t *ch = NULL; const char *host = http_arg_get(&hc->hc_args, "Host"); @@ -275,13 +253,148 @@ http_stream_playlist(http_connection_t *hc) htsbuf_qprintf(hq, "#EXTM3U\n"); RB_FOREACH(ch, &channel_name_tree, ch_name_link) { - htsbuf_qprintf(hq, "#EXTINF:-1,%s\n", ch->ch_name); - htsbuf_qprintf(hq, "http://%s/stream/channelid/%d\n", host, ch->ch_id); + if (channel == NULL || ch == channel) { + htsbuf_qprintf(hq, "#EXTINF:-1,%s\n", ch->ch_name); + + snprintf(buf, sizeof(buf), "/stream/channelid/%d", ch->ch_id); + ticket_id = access_ticket_create(buf); + htsbuf_qprintf(hq, "http://%s%s?ticket=%s\n", host, buf, ticket_id); + } } - http_output_content(hc, "application/x-mpegURL"); + http_output_content(hc, "audio/x-mpegurl"); pthread_mutex_unlock(&global_lock); + + return 0; +} + +/** + * Output a playlist with a http stream for a dvr entry (.m3u format) + */ +static int +http_dvr_playlist(http_connection_t *hc, int dvr_id) +{ + htsbuf_queue_t *hq = &hc->hc_reply; + char buf[255]; + const char *ticket_id = NULL; + dvr_entry_t *de = NULL; + time_t durration = 0; + off_t fsize = 0; + int bandwidth = 0; + const char *host = http_arg_get(&hc->hc_args, "Host"); + + pthread_mutex_lock(&global_lock); + + de = dvr_entry_find_by_id(dvr_id); + + if(de) { + durration = de->de_stop - de->de_start; + durration += (de->de_stop_extra + de->de_start_extra)*60; + + fsize = dvr_get_filesize(de); + + if(fsize) { + bandwidth = ((8*fsize) / (durration*1024.0)); + + htsbuf_qprintf(hq, "#EXTM3U\n"); + htsbuf_qprintf(hq, "#EXTINF:%d,%s\n", durration, de->de_title); + + htsbuf_qprintf(hq, "#EXT-X-TARGETDURATION:%d\n", durration); + htsbuf_qprintf(hq, "#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=%d\n", bandwidth); + + snprintf(buf, sizeof(buf), "/dvrfile/%d", dvr_id); + ticket_id = access_ticket_create(buf); + htsbuf_qprintf(hq, "http://%s%s?ticket=%s\n", host, buf, ticket_id); + + http_output_content(hc, "application/x-mpegURL"); + } + } + pthread_mutex_unlock(&global_lock); + + if(!de || !fsize) { + http_error(hc, HTTP_STATUS_BAD_REQUEST); + return HTTP_STATUS_BAD_REQUEST; + } else { + return 0; + } +} + +/** + * Handle requests for playlists + */ +static int +page_http_playlist(http_connection_t *hc, const char *remain, void *opaque) +{ + char *components[2]; + channel_t *ch = NULL; + int dvr_id = -1; + + if(remain == NULL) { + http_stream_playlist(hc, NULL); + return 0; + } + + if(http_tokenize((char *)remain, components, 2, '/') != 2) { + http_error(hc, HTTP_STATUS_BAD_REQUEST); + return HTTP_STATUS_BAD_REQUEST; + } + + http_deescape(components[1]); + + pthread_mutex_lock(&global_lock); + + if(!strcmp(components[0], "channelid")) { + ch = channel_find_by_identifier(atoi(components[1])); + } else if(!strcmp(components[0], "channel")) { + ch = channel_find_by_name(components[1], 0, 0); + } else if(!strcmp(components[0], "dvrid")) { + dvr_id = atoi(components[1]); + } + + pthread_mutex_unlock(&global_lock); + + if(ch) + return http_stream_playlist(hc, ch); + else if(dvr_id >= 0) + return http_dvr_playlist(hc, dvr_id); + else { + http_error(hc, HTTP_STATUS_BAD_REQUEST); + return HTTP_STATUS_BAD_REQUEST; + } +} + +/** + * Subscribes to a service and starts the streaming loop + */ +static int +http_stream_service(http_connection_t *hc, service_t *service) +{ + streaming_queue_t sq; + th_subscription_t *s; + + pthread_mutex_lock(&global_lock); + + streaming_queue_init(&sq, ~SMT_TO_MASK(SUBSCRIPTION_RAW_MPEGTS)); + + s = subscription_create_from_service(service, + "HTTP", &sq.sq_st, + SUBSCRIPTION_RAW_MPEGTS); + + + pthread_mutex_unlock(&global_lock); + + //We won't get a START command, send http-header here. + http_output_content(hc, "video/mp2t"); + + http_stream_run(hc, &sq); + + pthread_mutex_lock(&global_lock); + subscription_unsubscribe(s); + pthread_mutex_unlock(&global_lock); + streaming_queue_deinit(&sq); + + return 0; } /** @@ -319,23 +432,20 @@ http_stream_channel(http_connection_t *hc, channel_t *ch) /** * Handle the http request. http://tvheadend/stream/channelid/ * http://tvheadend/stream/channel/ + * http://tvheadend/stream/service/ */ static int http_stream(http_connection_t *hc, const char *remain, void *opaque) { char *components[2]; channel_t *ch = NULL; - - if(http_access_verify(hc, ACCESS_STREAMING)) { - http_error(hc, HTTP_STATUS_UNAUTHORIZED); - return HTTP_STATUS_UNAUTHORIZED; - } + service_t *service = NULL; hc->hc_keep_alive = 0; if(remain == NULL) { - http_stream_playlist(hc); - return 0; + http_error(hc, HTTP_STATUS_BAD_REQUEST); + return HTTP_STATUS_BAD_REQUEST; } if(http_tokenize((char *)remain, components, 2, '/') != 2) { @@ -351,16 +461,20 @@ http_stream(http_connection_t *hc, const char *remain, void *opaque) ch = channel_find_by_identifier(atoi(components[1])); } else if(!strcmp(components[0], "channel")) { ch = channel_find_by_name(components[1], 0, 0); + } else if(!strcmp(components[0], "service")) { + service = service_find_by_identifier(components[1]); } pthread_mutex_unlock(&global_lock); - if(ch == NULL) { + if(ch != NULL) { + return http_stream_channel(hc, ch); + } else if(service != NULL) { + return http_stream_service(hc, service); + } else { http_error(hc, HTTP_STATUS_BAD_REQUEST); return HTTP_STATUS_BAD_REQUEST; } - - return http_stream_channel(hc, ch); } @@ -373,7 +487,6 @@ page_static_bundle(http_connection_t *hc, const char *remain, void *opaque) const struct filebundle *fb = opaque; const struct filebundle_entry *fbe; const char *content = NULL, *postfix; - int n; if(remain == NULL) return 404; @@ -389,9 +502,11 @@ page_static_bundle(http_connection_t *hc, const char *remain, void *opaque) if(!strcmp(fbe->filename, remain)) { http_send_header(hc, 200, content, fbe->size, - fbe->original_size == -1 ? NULL : "gzip", NULL, 10, 0); + fbe->original_size == -1 ? NULL : "gzip", NULL, 10, 0, + NULL); /* ignore return value */ - n = write(hc->hc_fd, fbe->data, fbe->size); + if(write(hc->hc_fd, fbe->data, fbe->size) != fbe->size) + return -1; return 0; } } @@ -405,24 +520,21 @@ page_static_bundle(http_connection_t *hc, const char *remain, void *opaque) static int page_dvrfile(http_connection_t *hc, const char *remain, void *opaque) { - int fd; + int fd, i; struct stat st; const char *content = NULL, *postfix, *range; dvr_entry_t *de; char *fname; char range_buf[255]; - off_t content_len, file_start, file_end; + char disposition[256]; + off_t content_len, file_start, file_end, chunk; + ssize_t r; if(remain == NULL) return 404; pthread_mutex_lock(&global_lock); - if(http_access_verify(hc, ACCESS_RECORDER)) { - pthread_mutex_unlock(&global_lock); - return HTTP_STATUS_UNAUTHORIZED; - } - de = dvr_entry_find_by_id(atoi(remain)); if(de == NULL || de->de_filename == NULL) { pthread_mutex_unlock(&global_lock); @@ -468,19 +580,42 @@ page_dvrfile(http_connection_t *hc, const char *remain, void *opaque) content_len = file_end - file_start+1; - sprintf(range_buf, "bytes %"PRId64"-%"PRId64"/%"PRId64"", file_start, file_end, st.st_size); + sprintf(range_buf, "bytes %"PRId64"-%"PRId64"/%"PRId64"", + file_start, file_end, st.st_size); if(file_start > 0) lseek(fd, file_start, SEEK_SET); - http_send_header(hc, 200, content, content_len, NULL, NULL, 10, range_buf); - sendfile(hc->hc_fd, fd, NULL, content_len); - close(fd); + if(de->de_title != NULL) { + snprintf(disposition, sizeof(disposition), + "attachment; filename=%s.mkv", de->de_title); + i = 20; + while(disposition[i]) { + if(disposition[i] == ' ') + disposition[i] = '_'; + i++; + } + + } else { + disposition[0] = 0; + } - if(range) - return 206; - else - return 0; + http_send_header(hc, range ? HTTP_STATUS_PARTIAL_CONTENT : HTTP_STATUS_OK, + content, content_len, NULL, NULL, 10, + range ? range_buf : NULL, + disposition[0] ? disposition : NULL); + + if(!hc->hc_no_output) { + while(content_len > 0) { + chunk = MIN(1024 * 1024 * 1024, content_len); + r = sendfile(hc->hc_fd, fd, NULL, chunk); + if(r == -1) + return -1; + content_len -= r; + } + } + close(fd); + return 0; } @@ -546,7 +681,7 @@ webui_init(const char *contentpath) http_path_add("/dvrfile", NULL, page_dvrfile, ACCESS_WEB_INTERFACE); http_path_add("/favicon.ico", NULL, favicon, ACCESS_WEB_INTERFACE); - http_path_add("/channels.pls", NULL, page_rtsp_playlist, ACCESS_WEB_INTERFACE); + http_path_add("/playlist", NULL, page_http_playlist, ACCESS_WEB_INTERFACE); http_path_add("/state", NULL, page_statedump, ACCESS_ADMIN); diff --git a/support/posix.mk b/support/posix.mk index af845586..995afc9f 100644 --- a/support/posix.mk +++ b/support/posix.mk @@ -1,16 +1,24 @@ -prefix ?= $(INSTALLPREFIX) -INSTBIN= $(prefix)/bin -INSTMAN= $(prefix)/share/man1 +INSTBIN= ${DESTDIR}${INSTALLPREFIX}/bin +INSTMAN= ${DESTDIR}${INSTALLPREFIX}/share/man1 +INSTDBG= ${DESTDIR}/usr/lib/debug/${INSTALLPREFIX}/bin MAN=man/tvheadend.1 install: ${PROG} ${MAN} - mkdir -p ${DESTDIR}$(INSTBIN) - install -s ${PROG} ${DESTDIR}$(INSTBIN) + mkdir -p ${INSTBIN} + mkdir -p ${INSTDBG} + install -T ${PROG} ${INSTBIN}/tvheadend - mkdir -p ${DESTDIR}$(INSTMAN) - install ${MAN} ${DESTDIR}$(INSTMAN) + objcopy --only-keep-debug ${INSTBIN}/tvheadend ${INSTDBG}/tvheadend.debug + strip -g ${INSTBIN}/tvheadend + + objcopy --add-gnu-debuglink=${INSTDBG}/tvheadend.debug ${INSTBIN}/tvheadend + + + mkdir -p ${INSTMAN} + install ${MAN} ${INSTMAN} uninstall: - rm -f ${DESTDIR}$(INSTBIN)/${PROG} - rm -f ${DESTDIR}$(INSTMAN)/${MAN} + rm -f ${INSTBIN}/tvheadend + rm -f ${INSTDBG}/tvheadend.debug + rm -f ${INSTMAN}/tvheadend.1