pax_global_header00006660000000000000000000000064141441433350014514gustar00rootroot0000000000000052 comment=c78a255706c572347b10f73a5ba32be22e7f6519 vdr-plugin-live-3.1.3/000077500000000000000000000000001414414333500145445ustar00rootroot00000000000000vdr-plugin-live-3.1.3/.gitignore000066400000000000000000000002261414414333500165340ustar00rootroot00000000000000CVS .#* *~ *.o *.a *.so gen_version_suffix.h pages/*.cpp css/*.cpp javascript/*.cpp po/*.mo po/live.pot .dependencies .cvsignore .libs locale .*.edep vdr-plugin-live-3.1.3/CONTRIBUTORS000066400000000000000000000022031414414333500164210ustar00rootroot00000000000000Grams of suggestions, bugreports, patches and other contributions have been provided by the people on the VDR-Portal and VDR-Portal chatrooms. Special thanks go to the following individuals (if your name is missing here, please send an email to dh+vdr@gekrumbel.de): Michael Brueckner for icons and images and parts of the overall visual design. Rolf Ahrenberg for lots of finish translations and the initial implementation of streaming of live tv via streamdev into a browser window using the vlc plugin for firefox. And a lot of usefull hints about the features of the vlc plugins, some of which still need to be added to LIVE. Patrice Staudt (in memoriam) for the french translations. Matthias Kortstiege for a patch that activates the SSL support in tntnet. LIVE can then be used over https too. This works only with tntnet versions above 1.6.0.6. Recomended is tntnet version 1.6.2 and higher. Diego Pierotto for the italian translations. John Germs, Chavonbravo from CaptiveWorks (http://captiveworks.org) for the addition of channel numbers on the live pages. Martin Wache contributed the MultiSchedule view. vdr-plugin-live-3.1.3/COPYING000066400000000000000000000431061414414333500156030ustar00rootroot00000000000000 GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc. 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Lesser General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. vdr-plugin-live-3.1.3/HISTORY000066400000000000000000000014071414414333500156320ustar00rootroot00000000000000VDR Plugin 'live' Revision History ----------------------------------- 2007-01-01: Version 0.0.1 - Initial revision. 2007-05-28: Version 0.1.0 - First release. 2008-04-30: Version 0.2.0 - Version 0.2.0 2013-03-24: Version 0.3.0 - Declares GIT-Tree as new stable version. See http://live.vdr-developer.org about details. 2015-11-04: Version 0.3.1 - Tagged as release_0-3-1 even it was not officially released, but this version was used long time by many distributions and it marks the last commit of the previous maintainer Dieter Hametner. 2017-06-24: Version 2.3.1 - This version is compatible with VDR 2.2.0 and 2.3.x and will be released soon. 2021-01-16: Version 3.0.0 - Adjusted for VDR 2.4 - Include several patches from https://www.vdr-portal.de vdr-plugin-live-3.1.3/Makefile000066400000000000000000000201041414414333500162010ustar00rootroot00000000000000# # Makefile for the 'LIVE' Video Disk Recorder plugin # # The official name of this plugin. # This name will be used in the '-P...' option of VDR to load the plugin. # By default the main source file also carries this name. PLUGIN := live ### The version number of this plugin (taken from the main source file): VERSION := $(shell grep '\#define LIVEVERSION ' setup.h | awk '{ print $$3 }' | sed -e 's/[";]//g') ### Check for libpcre c++ wrapper HAVE_LIBPCRECPP := $(shell pcre-config --libs-cpp) ### The directory environment: # Use package data if installed...otherwise assume we're under the VDR source directory: PKGCFG = $(if $(VDRDIR),$(shell pkg-config --variable=$(1) $(VDRDIR)/vdr.pc),$(shell PKG_CONFIG_PATH="$$PKG_CONFIG_PATH:../../.." pkg-config --variable=$(1) vdr)) LIBDIR = $(call PKGCFG,libdir) LOCDIR = $(call PKGCFG,locdir) PLGCFG = $(call PKGCFG,plgcfg) RESDIR = $(call PKGCFG,resdir) # TMPDIR ?= /tmp ### The compiler options: export CFLAGS = $(call PKGCFG,cflags) export CXXFLAGS = $(call PKGCFG,cxxflags) ECPPC ?= ecppc ### The version number of VDR's plugin API: APIVERSION := $(call PKGCFG,apiversion) ### Allow user defined options to overwrite defaults: -include $(PLGCFG) include global.mk ### Determine tntnet and cxxtools versions: TNTNET-CONFIG := $(shell which tntnet-config 2>/dev/null) ifeq ($(TNTNET-CONFIG),) TNTVERSION = $(shell pkg-config --modversion tntnet | sed -e's/\.//g' | sed -e's/pre.*//g' | awk '/^..$$/ { print $$1."000"} /^...$$/ { print $$1."00"} /^....$$/ { print $$1."0" } /^.....$$/ { print $$1 }') CXXFLAGS += $(shell pkg-config --cflags tntnet) LIBS += $(shell pkg-config --libs tntnet) else TNTVERSION = $(shell tntnet-config --version | sed -e's/\.//g' | sed -e's/pre.*//g' | awk '/^..$$/ { print $$1."000"} /^...$$/ { print $$1."00"} /^....$$/ { print $$1."0" } /^.....$$/ { print $$1 }') CXXFLAGS += $(shell tntnet-config --cxxflags) LIBS += $(shell tntnet-config --libs) endif # $(info $$TNTVERSION is [${TNTVERSION}]) CXXTOOLVER = $(shell cxxtools-config --version | sed -e's/\.//g' | sed -e's/pre.*//g' | awk '/^..$$/ { print $$1."000"} /^...$$/ { print $$1."00"} /^....$$/ { print $$1."0" } /^.....$$/ { print $$1 }') ### Optional configuration features PLUGINFEATURES := ifneq ($(HAVE_LIBPCRECPP),) PLUGINFEATURES += -DHAVE_LIBPCRECPP CXXFLAGS += $(shell pcre-config --cflags) LIBS += $(HAVE_LIBPCRECPP) endif # -Wno-deprecated-declarations .. get rid of warning: ‘template class std::auto_ptr’ is deprecated CXXFLAGS += -std=c++11 -Wfatal-errors -Wundef -Wno-deprecated-declarations ### export all vars for sub-makes, using absolute paths LIBDIR := $(abspath $(LIBDIR)) LOCDIR := $(abspath $(LOCDIR)) export unexport PLUGIN ### The name of the distribution archive: ARCHIVE := $(PLUGIN)-$(VERSION) PACKAGE := vdr-$(ARCHIVE) ### The name of the shared object file: SOFILE := libvdr-$(PLUGIN).so ### Installed shared object file: SOINST = $(DESTDIR)$(LIBDIR)/$(SOFILE).$(APIVERSION) ### Includes and Defines (add further entries here): DEFINES += -D_GNU_SOURCE -DPLUGIN_NAME_I18N='"$(PLUGIN)"' -DTNTVERSION=$(TNTVERSION) -DCXXTOOLVER=$(CXXTOOLVER) DEFINES += -DDISABLE_TEMPLATES_COLLIDING_WITH_STL VERSIONSUFFIX = gen_version_suffix.h ### The object files (add further files here): PLUGINOBJS := $(PLUGIN).o thread.o tntconfig.o setup.o i18n.o timers.o \ tools.o recman.o tasks.o status.o epg_events.o epgsearch.o \ grab.o md5.o filecache.o livefeatures.o preload.o timerconflict.o \ users.o osd_status.o ffmpeg.o PLUGINSRCS := $(patsubst %.o,%.cpp,$(PLUGINOBJS)) WEB_LIB_PAGES := libpages.a WEB_DIR_PAGES := pages WEB_PAGES := $(WEB_DIR_PAGES)/$(WEB_LIB_PAGES) WEB_LIB_CSS := libcss.a WEB_DIR_CSS := css WEB_CSS := $(WEB_DIR_CSS)/$(WEB_LIB_CSS) WEB_LIB_JAVA := libjavascript.a WEB_DIR_JAVA := javascript WEB_JAVA := $(WEB_DIR_JAVA)/$(WEB_LIB_JAVA) WEBLIBS := $(WEB_PAGES) $(WEB_CSS) $(WEB_JAVA) SUBDIRS := $(WEB_DIR_PAGES) $(WEB_DIR_CSS) $(WEB_DIR_JAVA) ### The main target: .PHONY: all all: lib i18n @true ### Implicit rules: $(WEB_DIR_PAGES)/%.o: $(WEB_DIR_PAGES)/%.cpp $(WEB_DIR_PAGES)/%.ecpp @$(MAKE) -C $(WEB_DIR_PAGES) --no-print-directory PLUGINFEATURES="$(PLUGINFEATURES)" $(notdir $@) $(WEB_DIR_CSS)/%.o: @$(MAKE) -C $(WEB_DIR_CSS) --no-print-directory PLUGINFEATURES="$(PLUGINFEATURES)" $(notdir $@) $(WEB_DIR_JAVA)/%.o: @$(MAKE) -C $(WEB_DIR_JAVA) --no-print-directory PLUGINFEATURES="$(PLUGINFEATURES)" $(notdir $@) %.o: %.cpp $(call PRETTY_PRINT,"CC" $@) $(Q)$(CXX) $(CXXFLAGS) -c $(DEFINES) $(PLUGINFEATURES) $(INCLUDES) $< ### Dependencies: MAKEDEP = $(CXX) -MM -MG DEPFILE = .dependencies $(DEPFILE): Makefile @$(MAKEDEP) $(CXXFLAGS) $(DEFINES) $(PLUGINFEATURES) $(INCLUDES) $(PLUGINSRCS) > $@ ifneq ($(MAKECMDGOALS),clean) -include $(DEPFILE) endif ### For all recursive Targets: recursive-%: @$(MAKE) --no-print-directory $* ### Internationalization (I18N): PODIR := po I18Npo := $(wildcard $(PODIR)/*.po) I18Nmo := $(addsuffix .mo, $(foreach file, $(I18Npo), $(basename $(file)))) I18Nmsgs := $(addprefix $(DESTDIR)$(LOCDIR)/, $(addsuffix /LC_MESSAGES/vdr-$(PLUGIN).mo, $(notdir $(foreach file, $(I18Npo), $(basename $(file)))))) I18Npot := $(PODIR)/$(PLUGIN).pot I18Npot_deps = $(PLUGINSRCS) $(wildcard $(WEB_DIR_PAGES)/*.cpp) setup.h epg_events.h $(I18Npot): $(I18Npot_deps) $(call PRETTY_PRINT,"GT" $@) $(Q)xgettext -C -cTRANSLATORS --no-wrap --no-location -k -ktr -ktrNOOP --omit-header -o $@ $(I18Npot_deps) .PHONY: I18Nmo I18Nmo: $(I18Nmo) @true %.mo: %.po $(if $(DISABLE_I18Nmo_txt),,@echo "Creating *.mo") @msgfmt -c -o $@ $< $(eval DISABLE_I18Nmo_txt := 1) %.po: $(I18Npot) $(if $(DISABLE_I18Npo_txt),,@echo "Creating *.po") @msgmerge -U --no-wrap --no-location --backup=none -q -N $@ $< @touch $@ $(eval DISABLE_I18Npo_txt := 1) $(I18Nmsgs): $(DESTDIR)$(LOCDIR)/%/LC_MESSAGES/vdr-$(PLUGIN).mo: $(PODIR)/%.mo $(if $(DISABLE_I18Nmoinst_txt),,@echo "Installing *.mo") @install -D -m644 $< $@ $(eval DISABLE_I18Nmoinst_txt := 1) .PHONY: inst_I18Nmsg inst_I18Nmsg: $(I18Nmsgs) @true # When building in parallel, this will tell make to keep an order in the steps recursive-I18Nmo: subdirs recursive-inst_I18Nmsg: recursive-I18Nmo .PHONY: i18n i18n: subdirs recursive-I18Nmo .PHONY: install-i18n install-i18n: i18n recursive-inst_I18Nmsg ### Targets: $(VERSIONSUFFIX): FORCE ifneq ($(MAKECMDGOALS),clean) @./buildutil/version-util $(VERSIONSUFFIX) || ./buildutil/version-util -F $(VERSIONSUFFIX) endif .PHONY: subdirs $(SUBDIRS) subdirs: $(SUBDIRS) $(SUBDIRS): ifneq ($(MAKECMDGOALS),clean) @$(MAKE) -C $@ --no-print-directory PLUGINFEATURES="$(PLUGINFEATURES)" all else @$(MAKE) -C $@ --no-print-directory clean endif $(SOFILE): $(PLUGINOBJS) $(WEBLIBS) $(call PRETTY_PRINT,"LD" $@) $(Q)$(CXX) $(CXXFLAGS) $(LDFLAGS) -shared $(PLUGINOBJS) -Wl,--whole-archive $(WEBLIBS) -Wl,--no-whole-archive $(LIBS) -o $@ .PHONY: sofile sofile: $(SOFILE) @true # When building in parallel, this will tell make to keep an order in the steps recursive-sofile: subdirs recursive-soinst: recursive-sofile # When building in parallel, this will tell make to build VERSIONSUFFIX as first subdirs $(PLUGINOBJS): $(VERSIONSUFFIX) .PHONY: lib lib: $(VERSIONSUFFIX) subdirs $(PLUGINOBJS) recursive-sofile .PHONY: soinst soinst: $(SOINST) $(SOINST): $(SOFILE) $(call PRETTY_PRINT,"Installing" $<) $(Q)install -D $< $@ .PHONY: install-lib install-lib: lib recursive-soinst .PHONY: install-web install-web: @mkdir -p $(DESTDIR)$(RESDIR)/plugins/$(PLUGIN) @cp -a live/* $(DESTDIR)$(RESDIR)/plugins/$(PLUGIN)/ .PHONY: install install: install-lib install-i18n install-web .PHONY: dist dist: $(I18Npo) $(MAKE) --no-print-directory clean @-rm -rf $(TMPDIR)/$(ARCHIVE) @mkdir $(TMPDIR)/$(ARCHIVE) @cp -a * $(TMPDIR)/$(ARCHIVE) @tar czf $(TMPDIR)/$(PACKAGE).tar.gz -C $(TMPDIR) $(ARCHIVE) @-rm -rf $(TMPDIR)/$(ARCHIVE) @echo Distribution package created as $(TMPDIR)/$(PACKAGE).tar.gz .PHONY: clean clean: subdirs $(call PRETTY_PRINT,"CLN top") @-rm -f $(I18Nmo) $(I18Npot) @-rm -f $(PLUGINOBJS) $(DEPFILE) *.so *.tgz core* *~ @-rm -f $(VERSIONSUFFIX) .PRECIOUS: $(I18Npo) .PHONY: FORCE FORCE: vdr-plugin-live-3.1.3/README000066400000000000000000000210051414414333500154220ustar00rootroot00000000000000This is a "plugin" for the Video Disk Recorder (VDR). Written by: Thomas Keil Sascha Volkenandt Currently maintained by: Markus Ehrnsperger ( MarkusE @ https://www.vdr-portal.de) Previously Maintained by: Dieter Hametner Christian Wieninger Jasmin Jessich Project's homepage: https://github.com/MarkusEh/vdr-plugin-live Project's old homepage: http://live.vdr-developer.org Latest version available at: https://github.com/MarkusEh/vdr-plugin-live See the file COPYING for license information. IMPORTANT: ========== This version of LIVE does not support VDR versions older than 2.0.0 any more (maybe still some older VDR devel versions leading to VDR 2.0.0 but that is not tested). The release_0-3-1 tag is the last commit by Dieter Hametner. It is was the live version used by many distributions. Description: ============ Live, the "Live Interactive VDR Environment", is a plugin providing the possibility to interactively control the VDR and some of it's plugins by a web interface. Unlike external utility programs that communicate with VDR and it's plugins by SVDRP, Live has direct access to VDR's data structures and is thus very fast. Requirements: ============= VDR >= 2.0.0 gcc >= 4.8.1 PCRE >= 8.0.2 - http://www.pcre.org/ Tntnet >= 1.5.3 - http://www.tntnet.org/download.hms Cxxtools >= 1.4.3 - http://www.tntnet.org/download.hms Tntnet provides basic webserver functions for live and needs cxxtools. Boost provides some data structures we need. While currently relying on the full blown package we might provide a stripped down version in the future. PCRE provides filtering for recordings. Some older versions pcre-config tool doesn't contain C++ wrapper option, but filtering support can be forced via commandline: make HAVE_LIBPCRECPP="-lpcrecpp -lpcre" If you optionaly want to regenerate the i18n-generated.h header file for backward compatible i18n (VDR version prior to 1.5.7) you also need: (See also the Internationalization section below) Locale::PO - perl module from CPAN www.cpan.org The default i18n-generated.h header contains all translations from GIT. Users that just want to stay on bleeding development edge of live do not need Locale::PO installed. How to get Locale::PO - Use search function on www.cpan.org to obtain module. - Check if your distribution provides the package. (e.g. in Debian the package name is liblocale-po-perl) If you added new translations in your language specific .po file and still want to use an VDR older than version 1.5.7 you must regenerate i18n-generated.h by calling make with the target generate-i18n. Only in this case you need to have Locale::PO installed on your system. Installation: ============= If you compile the plugin outside of the VDR source codes you must copy the resulting binary to VDRs directory where the other plugins are expected. In order to work correctly you must copy the subdirectory 'live' from the source distribution to the directory where the vdr plugins look for their resource files. The pure VDR default for this config directory is: /video/plugins, but this depends also from the parameters -c or -v (see 'vdr --help' for details). cp -a /live /plugins Setup ===== Live provides a username/password protection, so that it can be used from the internet. The default username and password are: admin/live The default port is 8008. You can also specifiy this parameter via commandline: -p PORT, --port=PORT use PORT to listen for incoming connections (default: 8008) -i IP, --ip=IP bind server only to specified IP, may appear multiple times (default: 0.0.0.0, ::0) Additional SSL options are available now. See "How to make LIVE listen for ssl connections" section below on hints how to setup SSL. To display images, you can use: -e <...> --epgimages=/path/to/epgimages use EPG images created by plugins like tvm2vdr -t <...> --tvscraperimages=/path/to/tvscraperimages use images created by tvscraper Example: --tvscraperimages=/var/cache/vdr/plugins/tvscraper The rest of the parameters can be adjusted in VDR's OSD or in the web interface. The password is stored as a MD5 hash. "Last Channel" is the last channel in the channels list, that live displays. This is especially useful if you have VDR's automatic channel update active. For example, you can add a group seperator ":@1000 Found automatically" to channels.conf an set this parameter to "1000". Thus, everything VDR finds during scanning (which can after a few months be well more than 3000 channels) won't be displayed. How to make LIVE listen for ssl connections =========================================== To make LIVE listen for incoming ssl connections you`ll have to use a Tntnet version > 1.6.0.6. By default it will listen on port 8443. * Example: https://localhost:8443 In order to start the SslListener LIVE requires a SSL certificate. If no SSL certificate is specified via commandline option, LIVE will try to use the default certificate location '$VDRDIR/plugins/live/live.pem'. If neither the default nor the custom certificate (given by the commandline option) could be found, LIVE will only start the default HTTP Listener (default: 8008) Note: Since the gnutls SslListener was broken in Tntnet versions prior to SVN revision 1035 you will have to recompile Tntnet with "./configure --with-ssl=openssl" to make it work. Alternatively install version 1.6.2 or higher of tntnet on your system. SSL Commandline options ======================= -s PORT, --sslport=PORT use PORT to listen for incoming ssl connections (default: 8443) -c CERT, --cert=CERT path to a custom ssl certificate file (default: $CONFIGDIR/live.pem) -k KEY, --cert=CERT path to a custom ssl certificate key file (default: $CONFIGDIR/live-key.pem) Creating a self-signed SSL server certificate ============================================= To create a self-signed certificate file you`ll have to run this litte command. $> cd /put/your/path/here/vdr/plugins/live $> openssl req -new -x509 -keyout live-key.pem -out live.pem -days 365 -nodes While generating the certifcate you`ll be asked to answer a couple of questions. When it prompts to enter the "Common Name" you`ll have to specify the full qualified dns server name of the machine LIVE is running on (eg. vdr.example.com). If your vdr doesn`t have a full qualified dns name, you should use the ip LIVE is listening on. Note: This is just a quick'n dirty way to create a SSL self-signed certicate. Recent browsers (like Firefox 3) will complain about it because the certificate wasnt signed by a known Certificate Authority (CA). So how does LIVE work? ====================== Basically, Live itself is a Tntnet webserver integrated into the plugin structure VDR needs. This webserver, running in VDR's environment, is provided with all public data structures VDR provides for plugins and thus has very fast access to information like the EPG, timers or recordings. Live's "pages" are written in "ecpp", a language integrating C++ and HTML in one file, very much like e.g. PHP or ASP weave functionality and "static" content information together. Contribute! =========== If you would like to contribute, please read doc/dev-contribute.txt and doc/TODO.txt. Internationalization (i18n) =========================== LIVE uses the same i18n support like VDR does since version 1.5.7. This version of LIVE can not support i18n compatible with VDR versions older than 1.5.7. All localization files are found in the po subdirectory of the LIVE plugin source. Security consideratios ====================== Live uses the tntnet MapUrl mechanism to map different request urls to tntnet components. One component 'content.ecpp' delivers files found in the file system. When given the wrong 'path' it could retrieve any file from the server where live runs on. Therefore content.ecpp has beem enhanced to check the paths before returning files. A second measure against missuse is to limit the mappings from MapUrl to only valid files. In the current version this approach has been taken. But due to the 'difficulty' to fully understand regular expressions, this might get spoiled again by 'unchecked' code contribution. vdr-plugin-live-3.1.3/buildutil/000077500000000000000000000000001414414333500165415ustar00rootroot00000000000000vdr-plugin-live-3.1.3/buildutil/version-util000077500000000000000000000050471414414333500211350ustar00rootroot00000000000000#!/bin/bash # ----------------------------------------------------------------------------- # Shell script to determine the last commit version of the project It # checks for CVS and .git repositories. The output is a string that # can be used in a define at compile time to automatically mark # repository versions. For CVS repositories the string contains the # date and time of the commit that lead to the current version of # files. For git repositories the output contains the git-id of the # current tree. An indication if localy modified files exist is # added currently only for CVS. # ----------------------------------------------------------------------------- [ $# -lt 1 ] && echo "USAGE: version-util [-F] " && exit 1 VERS_FILE=$1 FORCE_EMPTY=0 [ "$VERS_FILE" == "-F" -a $# -lt 2 ] && echo "USAGE: version-util [-F] " && exit 1 if [ $# -gt 1 ]; then VERS_FILE=$2 FORCE_EMPTY=1 fi SCRIPT_PATH=`dirname $0` # echo "file: ${VERS_FILE}, force = $FORCE_EMPTY" # exit 0 createVers() { cat < /dev/null } cvsData() { fileVersions cvsLog } cvsVers() { d=`cvsData \ | awk -f ${SCRIPT_PATH}/version-util.awk \ | sort -u \ | tail -1 \ | tr -d ' \-:'` m=`cvs status 2> /dev/null \ | grep 'Status: Locally Modified' > /dev/null && echo "_MOD"` echo "_cvs_${d}${m}" } gitVers() { b=`git branch \ | grep '^*' \ | sed -e's/^* //'` h=`git show --pretty=format:"%h_%ci" HEAD \ | head -1 \ | tr -d ' \-:'` echo "_git_${b}_${h}" } emptyVers() { echo "" } checkVers() { s=`$1` if [ ! -e ${VERS_FILE} ]; then echo "$VERS_FILE does not exist! creating a new one." createVers $s > ${VERS_FILE} else v=`grep '^#define VERSION_SUFFIX' ${VERS_FILE} \ | awk '{print $3}'` if [ "$v" != "\"$s\"" ]; then echo "$VERS_FILE is being recreated!" createVers $s > ${VERS_FILE} fi fi } if [ $FORCE_EMPTY -eq 1 ]; then checkVers emptyVers exit 0 fi if [ -d CVS ]; then checkVers cvsVers exit 0 fi if [ -d .git ]; then checkVers gitVers exit 0 fi checkVers emptyVers vdr-plugin-live-3.1.3/buildutil/version-util.awk000066400000000000000000000013361414414333500217100ustar00rootroot00000000000000BEGIN { FS="|"; init_revisions = 1; rev_trigger = 0; date_trigger = 0; } /= == ===marker=== == =/ { init_revisions = 0; FS=";"; next; } init_revisions == 1 { # print "XXX " $1, $2; file_revs[$1] = $2; next; } /^Working file:/ { rev_trigger = 0; if (match($0, "^Working file: (.*)$", f) > 0) { if (f[1] in file_revs) { revision = "revision " file_revs[f[1]]; rev_trigger = 1; } } # print "FFF " f[1], revision, rev_trigger; next; } rev_trigger == 1 && /^revision/ { if (match($0, revision) > 0) { # print "FOUND " revision, $0; rev_trigger = 0; date_trigger = 1; } next; } date_trigger == 1 { if (match($1, "date: (.*)", d) > 0) { print d[1]; } date_trigger = 0; } { next; } vdr-plugin-live-3.1.3/cache.h000066400000000000000000000043251414414333500157640ustar00rootroot00000000000000#ifndef VGSTOOLS_CACHE_H #define VGSTOOLS_CACHE_H #include "stdext.h" #include #include #include /* Interface for TValue: * size_t weight() * bool is_newer( time_t ) * bool load() * */ namespace vgstools { template> class cache { public: typedef TKey key_type; typedef TValue mapped_type; typedef std::shared_ptr ptr_type; private: typedef std::pair value_type; typedef std::list ValueList; typedef std::map KeyMap; public: cache( size_t maxWeight ) : m_maxWeight( maxWeight ) , m_currentWeight( 0 ) {} size_t weight() const { return m_currentWeight; } size_t count() const { return m_values.size(); } ptr_type get( key_type const& key ) { assert( m_lookup.size() == m_values.size() ); typename KeyMap::iterator it = m_lookup.find( key ); ptr_type result = it != m_lookup.end() ? it->second->second : ptr_type( new mapped_type( key ) ); if ( it != m_lookup.end() ) { if ( result->is_current() ) { if ( it->second != m_values.begin() ) { m_values.erase( it->second ); it->second = m_values.insert( m_values.begin(), std::make_pair( key, result ) ); } return result; } m_currentWeight -= result->weight(); m_values.erase( it->second ); } if ( !result->load() ) { if ( it != m_lookup.end() ) m_lookup.erase( it ); return ptr_type(); } // put new object into cache if ( result->weight() < m_maxWeight ) { m_currentWeight += result->weight(); typename ValueList::iterator element = m_values.insert( m_values.begin(), std::make_pair( key, result ) ); if ( it != m_lookup.end() ) it->second = element; else m_lookup.insert( std::make_pair( key, element ) ); while ( m_currentWeight > m_maxWeight ) { value_type& value = m_values.back(); m_currentWeight -= value.second->weight(); m_lookup.erase( m_lookup.find( value.first ) ); m_values.pop_back(); } } return result; } private: std::size_t m_maxWeight; std::size_t m_currentWeight; ValueList m_values; KeyMap m_lookup; }; } // namespace vgstools #endif // VGSTOOLS_CACHE_H vdr-plugin-live-3.1.3/css/000077500000000000000000000000001414414333500153345ustar00rootroot00000000000000vdr-plugin-live-3.1.3/css/Makefile000066400000000000000000000020001414414333500167640ustar00rootroot00000000000000### The official name of this plugin. PLUGIN := live ### Additional options to silence TNTNET warnings TNTFLAGS ?= -Wno-overloaded-virtual -Wno-unused-function ### Includes and Defines (add further entries here): INCLUDES += -I$(VDRDIR)/include -I.. ### The object files (add further files here): OBJS := styles.o SRCS := $(patsubst %.o,%.cpp,$(OBJS)) include ../global.mk ### The main target: all: libcss.a @true ### Implicit rules: %.o: %.cpp $(call PRETTY_PRINT,"CC css/" $@) $(Q)$(CXX) $(CXXFLAGS) $(TNTFLAGS) -c $(DEFINES) $(PLUGINFEATURES) $(INCLUDES) $< ifeq ($(shell test $(TNTVERSION) -ge 30000; echo $$?),0) %.cpp: %.ecpp else %.cpp: %.css endif $(call PRETTY_PRINT,"ECPP css/" $@) $(Q)$(ECPPC) $(ECPPFLAGS) $(ECPPFLAGS_CSS) -b -m "text/css" $< ### Targets: libcss.a: $(OBJS) $(call PRETTY_PRINT,"AR css/" $@) $(Q)$(AR) r $@ $^ $(AR_NUL) clean: $(call PRETTY_PRINT,"CLN css/") @rm -f *~ *.o core* libcss.a $(SRCS) dist: clean @echo "Nothing to do for distribution here ..." .PRECIOUS: $(SRCS) vdr-plugin-live-3.1.3/css/siteprefs.css000066400000000000000000000040011414414333500200450ustar00rootroot00000000000000/* ###################### # This file is part of vdr-live! # It is here to give the users the possibility to change the # default css style of vdr-live to their needs. # # If you don't want to change default settings, make this file # empty, but don't delete it. ###################### */ /* uncomment this below, to make all tables full page width. */ /* table { width: 100%; } */ /* comment this out, if you want epg images at their native size * the popup windows. This here restricts their width to 120 px. * You might also only change size. */ .info-win span.epg_images { width: 120px; } .notify-win { margin: 0px auto; max-width: 480px; /* depends on the tip backround image width */ color: #fff; } .notify-win .notify-win-top .notify-win-c, .notify-win .notify-win-bot .notify-win-c { font-size: 1px; /* ensure minimum height */ height: 17px; } .notify-win .notify-win-top { background: transparent url(/img/rounded-box-green-tl.png) no-repeat 0px 0px; margin-right: 17px; /* space for right corner */ } .notify-win .notify-win-top .notify-win-c { background: transparent url(/img/rounded-box-green-tr.png) no-repeat right 0px; margin-right: -17px; /* pull right corner back over "empty" space (from above margin) */ } .notify-win .notify-win-body { background: transparent url(/img/rounded-box-green-ml.png) repeat-y 0px 0px; margin-right: 17px; } .notify-win .notify-win-body .notify-win-c { background: transparent url(/img/rounded-box-green-mr.png) repeat-y right 0px; margin-right: -17px; } .notify-win .notify-win-body .notify-win-c .notify-win-s { /* optional gradient overlay */ /* background: transparent url(/img/rounded-box-green-ms.jpg) repeat-x 0px 0px; */ padding: 0px 17px 0px 17px; } .notify-win .notify-win-bot { background: transparent url(/img/rounded-box-green-bl.png) no-repeat 0px 0px; margin-right: 17px; } .notify-win .notify-win-bot .notify-win-c { background: transparent url(/img/rounded-box-green-br.png) no-repeat right 0px; margin-right: -17px; } vdr-plugin-live-3.1.3/css/styles.css000066400000000000000000000551421414414333500174000ustar00rootroot00000000000000/* ###################### # Globals ###################### */ html, body { color: black; background-color: white; } body { margin: 0px; padding: 0px; font-size: 11px; font-family: Verdana, Arial, Helvetica, sans-serif; } table { font-size: 11px; font-family: Verdana, Arial, Helvetica, sans-serif; margin: 0px; } tr, td { padding-top: 0px; padding-bottom: 0px; } form { margin: 0; padding: 0; } input { border: 1px solid #6D96A9; font-size: 11px; font-family: Verdana, Arial, Helvetica, sans-serif; background: #FEFEFE; margin: 0; } select { border: 1px solid #6D96A9; font-size: 11px; font-family: Verdana, Arial, Helvetica, sans-serif; } img { border: 0; } a { text-decoration: none; } a:hover { text-decoration: underline; } a:active { text-decoration: underline; } /* ######################### # global style properties ######################### */ .bold { font-weight: bold; } /* ============================== = Dotted Frame ============================== */ .dotted { border: 1px dotted #bbbbbb; padding: 3px; margin: 2px; float: left; background-color: #f3f3f3; } .title { font-weight: bold; } .short { font-weight: normal; } .more { font-weight: bold; cursor: pointer; } .withmargin { margin: 5px; } .nomargin { margin: 0px; } .notpresent { display: none; } .nowrap { white-space: nowrap; } /* ###################### # Tooltip style for hints ###################### */ .hint-tip { margin: 0px auto; max-width: 480px; /* depends on the tip backround image width */ color: #fff; } .hint-tip .hint-tip-top .hint-tip-c, .hint-tip .hint-tip-bot .hint-tip-c { font-size: 1px; /* ensure minimum height */ height: 17px; } .hint-tip .hint-tip-top { background: transparent url(img/rounded-box-blue-tl.png) no-repeat 0px 0px; margin-right: 17px; /* space for right corner */ } .hint-tip .hint-tip-top .hint-tip-c { background: transparent url(img/rounded-box-blue-tr.png) no-repeat right 0px; margin-right: -17px; /* pull right corner back over "empty" space (from above margin) */ } .hint-tip .hint-tip-bdy { background: transparent url(img/rounded-box-blue-ml.png) repeat-y 0px 0px; margin-right: 17px; } .hint-tip .hint-tip-bdy .hint-tip-c { background: transparent url(img/rounded-box-blue-mr.png) repeat-y right 0px; margin-right: -17px; } .hint-tip .hint-tip-bdy .hint-tip-c .hint-tip-s { /* optional gradient overlay */ /* background: transparent url(img/rounded-box-blue-ms.jpg) repeat-x 0px 0px; */ padding: 0px 17px 0px 17px; } .hint-tip .hint-tip-bot { background: transparent url(img/rounded-box-blue-bl.png) no-repeat 0px 0px; margin-right: 17px; } .hint-tip .hint-tip-bot .hint-tip-c { background: transparent url(img/rounded-box-blue-br.png) no-repeat right 0px; margin-right: -17px; } .hint-title { display: none; } /* ###################### # Style for positive notification popup ###################### */ .ok-info { margin: 0px auto; max-width: 480px; /* depends on the tip backround image width */ color: #fff; } .ok-info .ok-info-top .ok-info-c, .ok-info .ok-info-bot .ok-info-c { font-size: 1px; /* ensure minimum height */ height: 17px; } .ok-info .ok-info-top { background: transparent url(/img/rounded-box-green-tl.png) no-repeat 0px 0px; margin-right: 17px; /* space for right corner */ } .ok-info .ok-info-top .ok-info-c { background: transparent url(/img/rounded-box-green-tr.png) no-repeat right 0px; margin-right: -17px; /* pull right corner back over "empty" space (from above margin) */ } .ok-info .ok-info-body { background: transparent url(/img/rounded-box-green-ml.png) repeat-y 0px 0px; margin-right: 17px; } .ok-info .ok-info-body .ok-info-c { background: transparent url(/img/rounded-box-green-mr.png) repeat-y right 0px; margin-right: -17px; } .ok-info .ok-info-body .ok-info-c .ok-info-s { /* optional gradient overlay */ /* background: transparent url(/img/rounded-box-green-ms.jpg) repeat-x 0px 0px; */ padding: 0px 17px 0px 17px; } .ok-info .ok-info-bot { background: transparent url(/img/rounded-box-green-bl.png) no-repeat 0px 0px; margin-right: 17px; } .ok-info .ok-info-bot .ok-info-c { background: transparent url(/img/rounded-box-green-br.png) no-repeat right 0px; margin-right: -17px; } /* ###################### # Style for negative notification popup ###################### */ .err-info { margin: 0px auto; max-width: 480px; /* depends on the tip backround image width */ color: #fff; } .err-info .err-info-top .err-info-c, .err-info .err-info-bot .err-info-c { font-size: 1px; /* ensure minimum height */ height: 17px; } .err-info .err-info-top { background: transparent url(/img/rounded-box-redwine-tl.png) no-repeat 0px 0px; margin-right: 17px; /* space for right corner */ } .err-info .err-info-top .err-info-c { background: transparent url(/img/rounded-box-redwine-tr.png) no-repeat right 0px; margin-right: -17px; /* pull right corner back over "empty" space (from above margin) */ } .err-info .err-info-body { background: transparent url(/img/rounded-box-redwine-ml.png) repeat-y 0px 0px; margin-right: 17px; } .err-info .err-info-body .err-info-c { background: transparent url(/img/rounded-box-redwine-mr.png) repeat-y right 0px; margin-right: -17px; } .err-info .err-info-body .err-info-c .err-info-s { /* optional gradient overlay */ /* background: transparent url(/img/rounded-box-redwine-ms.jpg) repeat-x 0px 0px; */ padding: 0px 17px 0px 17px; } .err-info .err-info-bot { background: transparent url(/img/rounded-box-redwine-bl.png) no-repeat 0px 0px; margin-right: 17px; } .err-info .err-info-bot .err-info-c { background: transparent url(/img/rounded-box-redwine-br.png) no-repeat right 0px; margin-right: -17px; } /* ###################### # General styles for epg info ###################### */ div.epg_content { border: 1px solid black; } span.epg_images { float: right; margin-left: 5px; } /* ############################## # Infowin styles for epg infos ############################## */ div.info-win { width: 560px; max-width: 2048px; border: none; margin: 0px auto; } .info-win .info-win-top .info-win-c { /*font-size: 1px;*/ /* ensure minimum height */ height: 37px; } .info-win .info-win-bot .info-win-c { font-size: 1px; /* ensure minimum height */ height: 21px; } .info-win .info-win-top { background: transparent url(img/info-win-t-l.png) no-repeat 0px 0px; margin-right: 26px; /* space for right corner */ } .info-win .info-win-top .info-win-c { background: transparent url(img/info-win-t-r.png) no-repeat right 0px; margin-right: -26px; /* pull right corner back over "empty" space (from above margin) */ overflow: hidden; } .info-win .info-win-top .info-win-c .info-win-t { color: #FFF; font-weight: bold; margin-top: 14px; margin-left: 15px; float: left; max-width: 490px; } .info-win .info-win-top .info-win-c .info-win-b { margin-top: 17px; margin-right: 26px; float: right; } .info-win .info-win-top .info-win-c .info-win-b .close { width: 16px; height: 16px; background: transparent url(img/close.png) no-repeat top right; } .info-win .info-win-top .info-win-c .info-win-b .close:hover { width: 16px; height: 16px; background: transparent url(img/close_red.png) no-repeat top right; } .info-win .info-win-body { background: transparent url(img/info-win-m-l.png) repeat-y 0px 0px; margin-right: 26px; } .info-win .info-win-body .info-win-c { background: transparent url(img/info-win-m-r.png) repeat-y right 0px; margin-right: -26px; } .info-win .info-win-body .info-win-c .info-win-s { padding: 0px 26px 0px 14px; } .info-win .info-win-bot { background: transparent url(img/info-win-b-l.png) no-repeat 0px 0px; margin-right: 26px; } .info-win .info-win-bot .info-win-c { background: transparent url(img/info-win-b-r.png) no-repeat right 0px; margin-right: -26px; } .hint-title { display: none; } /* ####################### # Menue ####################### */ div.menu { background: #000057 url(img/menu_line_bg.png) repeat-x; min-height: 27px; margin: 0; padding: 0 0 0 10px; line-height: 20px; vertical-align: middle; border-top: 1px solid black; border-bottom: 1px solid black; color: #6D96A9; } div.menu a { color: white; font-weight: bold; } a#login { color: #F5FF2D; } div.menu a.active { color: #FFDB88; font-weight: bold; } div.menu form { display: inline; } div.inhalt { overflow: auto; padding: 10px; } /* styles for pagemenu */ div#pagemenu { margin-top: 2px; padding-top: 6px; background: #FFFFFF url(img/bg_line.png) top repeat-x; } div#pagemenu div { padding-bottom: 6px; background: #FFFFFF url(img/bg_line_top.png) bottom repeat-x; } div#pagemenu div div { padding: 2px 0px 2px 10px; background: #E9EFFF; border-top: 1px solid #C0C1DA; border-bottom: 1px solid #C0C1DA; } div#pagemenu div div div { border: 0; padding: 0; margin: 0; } div#pagemenu form { display: inline; } div#pagemenu a { color: black; font-weight: bold; } div#pagemenu a.active { color: blue; font-weight: bold; } div#pagemenu form a { margin-left: 1em; } div#pagemenu form a img { vertical-align: middle; margin-top: -5px; } /* styles for messagebar */ div#messagebar { margin-top: 8px; margin-bottom: 2px; line-height: 20px; min-height: 27px; vertical-align: middle; background: #7CAD3F url(img/msgbar_line_bg.png) top repeat-x; border-top: 1px solid #2B6B00; border-bottom: 1px solid #2B6B00; } div#messagebar div { color: black; } div#messagebar div div { padding-left: 10px; background: transparent; } div#messagebar div div div { border: 0; padding: 0; margin: 0; } div#messagebar a { color: black; font-weight: bold; } div#messagebar a.active { color: blue; font-weight: bold; } div#messagebar span#mbmessage { padding: 0em 1em 0em 1em; color: #A14040; font-weight: bold; } /* ####################### # Info Box (near logo) ####################### */ img.logo { float: left; margin-top: 5px; margin-left: 5px; margin-right: 25px; } div#infobox { float: left; border: 1px solid #C0C1DA; margin: 5px; min-width: 320px; } div#infobox div.st_header { overflow: hidden; padding: 1px 4px; background: #E9EFFF; border-bottom: 1px solid #C0C1DA; } div#infobox div.st_header div.now { float: right; } div#infobox div.st_header div.caption { float: left; overflow: hidden; font-weight: bold; padding: 0px; } div#infobox div.st_content { overflow: hidden; padding: 4px; background: white url(img/bg_line_top.png) top left repeat-x; } div#infobox div.st_content div.duration { float: right; padding-left: 2em; } div#infobox div.st_content div.name { float: left; overflow: hidden; font-weight: bold; } div#infobox div.st_controls { overflow: hidden; padding: 4px; } div#infobox div.st_controls div.st_pbar { padding-top: 4px; float: right; } div#infobox div.st_controls div { float: left; } div#infobox div.st_controls div.st_update { padding-right: 5px; border-right: 1px solid #C0C1DA; } div#infobox div.st_controls div#infobox_recording_buttons { padding-left: 5px; } div#infobox div.st_controls div#infobox_channel_buttons { padding-left: 5px; } /* ################################# # Buttons ################################# */ button { width: 100px; height: 20px; color: #FFFFFF; font-size: 11px; border: 0px; vertical-align: middle; text-align: center; cursor: pointer; padding-bottom: 3px; } button.green { background-image: url(img/button_green.png); } button.red { background-image: url(img/button_red.png); } button.blue { background-image: url(img/button_blue.png); } button.yellow { background-image: url(img/button_yellow.png); } /* ################################ # general table cell classes ################################ */ table td.buttonpanel { text-align: right; } table td.toprow { } table th.toprow { } table td.bottomrow { border-bottom: 1px solid black !important; } table td.leftcol { border-left: 1px solid black; } table td.rightcol { border-right: 1px solid black; } table td.topaligned { vertical-align: top; } table td.padded { padding: 3px 7px 3px 3px; } /* ################ # Event (page: whats_on.html) ################ */ div.event { width: 255px; height: 255px; padding: 0; margin-right: 5px; float: left; } div.event div.station { margin: 0; padding: 0; width: 255px; } div.station div { margin: 0; padding: 0; background: url(img/bg_box_l.png) top left no-repeat; height: 23px; } div.station div div { background: url(img/bg_box_r.png) top right no-repeat; } div.station div div div { background: url(img/bg_box_h.png) repeat-x; line-height: 20px; vertical-align: middle; text-align: left; margin-right: 3px; margin-left: 3px; padding-left: 5px; padding-top: 2px; font-weight: bold; } div.station div div div a { color: #ffffff; font-weight: bold; } td div.station { vertical-align: middle; } td div.station a { color: black; font-weight: bold; } div.content { width: 253px; height: 220px; padding: 0; margin: 0; overflow: hidden; background: white url(img/bg_tools.png) top left repeat-y; border-left: 1px solid #000000; border-right: 1px solid #000000; border-bottom: 1px solid #000000; } div.content div.tools { float: left; width: 26px; height: 220px; margin: 0; padding: 0; padding-top: 3px; text-align: center; vertical-align: top; } div.content div.tools img { margin: 2px 5px; } div.content div { padding-left: 0px; margin-left: 35px; } div.content div div { margin-left: 0px; } div.description { margin: 5px 5px 0px 5px; overflow: hidden; } div.title { margin: 5px; } div.short { margin: 5px; } div.info { padding: 5px 5px 5px 0px; overflow: hidden; } div.info div.time { float: right; padding: 0px; } div.info div.date { float: left; padding: 0px; } div.progress { overflow: hidden; padding-right: 4px; margin: 0px; } div.progress div { float: right; padding: 0px; } div.__progress { overflow: hidden; width: 100px; height: 8px; border: 1px solid #C0C1DA; } div.__progress_invisible { overflow: hidden; width: 100px; height: 10px; border: 0px none transparent; } div.__progress div.__elapsed { float: left; height: 8px; background-color: #E9EFFF; } /* ################################## # table listing # (this is used in listing tables) ################################## */ table.listing { padding: 0px; margin: 0px; } table.listing tr td { background: transparent url(img/bg_line.png) bottom repeat-x; border-bottom: 1px solid #C0C1DA; } table.listing tr td.action { padding-left: 3px; padding-right: 5px; } table.listing tr td.leftcol { padding-left: 7px; padding-right: 5px; } table.listing tr td.current { color: blue; } table.listing tr td.noborder { background: none; border-style: none; } table.listing tr.head td { color: white; font-weight: bold; margin: 0px; padding: 0px; border-bottom: 1px solid black; } table.listing tr.description td { font-weight: bold; background: #E9EFFF; } table.listing tr.spacer td { height: 10px; background: transparent; border-bottom: 0px; } table.listing a { color: black; font-weight: bold; } /* ################################## # table schedule # (this is used for the MultiSchedule) ################################## */ table.mschedule { padding: 0px; margin: 0px; } table.mschedule tr { height: 12px; } table.mschedule tr td.event { background: transparent url(img/bg_line.png) bottom repeat-x; border-bottom: 1px solid #C0C1DA; } table.mschedule tr td.has_timer { background-color: #FFE0E0; } table.mschedule tr td.odd { background-color: #D0D0ff; } table.mschedule tr td.even { background-color: #ffffff; } table.mschedule tr td.current_row { background-color: #ffB0B0; } table.mschedule tr td.leftcol { padding-left: 7px; padding-right: 5px; } table.mschedule div.content1 { min-width: 10em; max-width: 30em; } table.mschedule div.tools1 { float: right; } table.mschedule div.start { float: left; } table.mschedule tr td div.title { padding: 3px; clear: both; } table.mschedule tr td div.short { clear: both; overflow: hidden; } table.mschedule tr td div.description { overflow: hidden; clear: both; } table.mschedule a { color: black; font-weight: bold; } /* ############################## # Blue Background Thingy ############################## */ div.boxheader { margin: 0px; padding: 0px; background: url(img/bg_box_l.png) top left no-repeat; } div.boxheader div { margin: 0px; background: url(img/bg_box_r.png) top right no-repeat; } div.boxheader div div { background: url(img/bg_box_h.png) repeat-x; vertical-align: middle; text-align: left; margin-right: 3px; margin-left: 3px; padding-left: 5px; padding-top: 2px; color: white; font-weight: bold; height: 21px; line-height: 20px; } /* ############################## # Recordings ############################## */ div.recordings { border: 1px solid black; } .recordings ul { list-style-type: none; padding: 0px; margin: 0px; } div.recording_item { overflow: hidden; background: url(img/bg_line.png) bottom repeat-x; border-bottom: 1px solid #C0C1DA; } .recording_item div { float: left; } .recording_item div.recording_imgs{ margin-right: 0.5em; } .recording_item div.recording_spec { padding-top: 0.5ex; } .recording_item div.recording_day { width: 13em; } .recording_item div.recording_date { width: 5.25em; } .recording_item div.recording_time { width: 5.75em; } .recording_item div.recording_duration { width: 4em; } .recording_item div.recording_name { font-weight: bold; cursor: pointer; } .recording_item div.recording_name span { font-weight: normal; cursor: pointer; } .recording_item div.recording_name_new { font-weight: bold; color: orangered; cursor: pointer; } .recording_item div.recording_name_new a { color: orangered; } .recording_item div.recording_name_new span { font-weight: normal; } .recording_item div.recording_errors { width: 26px; } .recording_item div.recording_sd_hd { width: 35px; } .recording_item div.recording_aspect_ratio { width: 35px; } .recording_item div.recording_arch { float: right; padding-top: 0.5ex; padding-left: 0.5em; padding-right: 0.5em; } .recording_item div.recording_actions { float: right; padding-right: 3em; } /* ############################## # Remote Control Keypad ############################## */ div.screenshot { background-image: url(img/tv.jpg); background-repeat: no-repeat; height: 320px; width: 569px; float: left; padding: 20px 20px 98px 21px; margin-right: 20px; } div.screenshot img { width: 569px; height: 320px; } /* ############################## # Remote page - OSD ############################## */ #osd { background-color:black; color:white; height: 320px; width: 569px; } .osd { padding: 5px 0px 5px 10px; position:relative; } .osd div { margin:0px; tab-size:4; font-family: "Courier New", Courier, monospace; } .osdMessage { position:absolute; overflow:auto; top:250px; max-height: 100px; min-height:20px; width: 510px; background-color:yellow; color:black; padding:10px; border:2px solid red; } .osdText, .osdItems { overflow:auto; overflow-x:hidden; height: 267px; width: 555px; margin:5px 0px !important; } .osdItem, .osdTitle { cursor:pointer; white-space: nowrap; } .osdItem.selected { background-color:#cccccc; color:#000000; } .osdButtons {} .osdButtons div { float:left; margin-right:5px; padding:2px; width:130px; text-align: center; cursor:pointer; } .osdButtonGreen {background-color:green} .osdButtonYellow {background-color:yellow;color:black} .osdButtonBlue {background-color:blue} .osdButtonRed {background-color:red} /* ############################## # Error widget ############################## */ table.error { border: 1px solid #E9360D; margin: 2px; padding: 0px; } table.error tr td.message { padding: 5px; } table.error tr td.title { background: #E9360D; color: white; font-weight: bold; margin: 0; padding: 3px 3px 3px 10px; } table.error td.border { padding: 0; margin: 0; width: 1px; } /* ############################## # Formular Tables # (are used in forms to group input elements) ############################## */ table.formular { margin-top: 10px; } table.formular tr td { vertical-align: top; vertical-align: middle; background: url(img/bg_line.png) bottom repeat-x; border-bottom: 1px solid #C0C1DA; } table.formular tr td input { margin-top: 5px; margin-bottom: 2px; } table.formular tr td .dotted input { margin-top: 0px; } table.formular tr td.leftcol { padding-left: 2px; } table.formular tr td.rightcol { padding-right: 7px; } table.formular tr td.label { font-weight: bold; vertical-align: top; } table.formular tr.head td { color: white; font-weight: bold; margin: 0; padding: 0; border: none; } table.dependent { background-color: #FFFFFF; margin-top: 0px; } table.dependent tr td { background: transparent; vertical-align: middle; } /* ############################## # Login ############################## */ table.login { margin: 0 auto; } table.login tr td { padding: 3px 5px; text-align: right; } /* ############################## # Styles for EPG-Boxes ############################## */ div.caption { padding: 2px 0px 0px 5px; } div.epg_content { padding: 0px 0px 7px 0px; margin: 0px 0px 0px 0px; background: transparent url(img/bg_tools.png) top left repeat-y; border: 1px solid black; overflow: hidden; } div.epg_content div { margin-left: 35px; } div.epg_content div.epg_tools { float: left; width: 26px; margin: 0; padding: 0; padding-top: 3px; text-align: center; vertical-align: top; } div.epg_content div div { margin-left: 0px; } div.epg_content div.epg_tools img { margin: 2px 5px; } span.epg_images img.epg_image { width: 100%; } /* some adaptions when shown in info-win subwindow */ /* .info-win div.epg_content div div.progress div { margin-left: 0px; } */ .info-win div.boxheader { display: none; } .info-win div.epg_content { border: 0px; } /* ############################## # Style adaptions for About box ############################## */ div.epg_content div.about_left { text-align: right; float: left; width: 175px; } div.epg_content div.about_right { padding-left: 200px; } div.epg_content div.about_line { padding-left: 0px; } div.epg_content div.about_head { font-weight: bold; margin-left: -9px; padding-top: 6px; } div.epg_content div.about_head div { padding-bottom: 6px; margin-left: 0px; } div.epg_content div.about_head div div { padding: 2px 0px 2px 10px; background: #E9EFFF; border-top: 1px solid #C0C1DA; border-bottom: 1px solid #C0C1DA; } vdr-plugin-live-3.1.3/css/styles.ecpp000077700000000000000000000000001414414333500215602styles.cssustar00rootroot00000000000000vdr-plugin-live-3.1.3/doc/000077500000000000000000000000001414414333500153115ustar00rootroot00000000000000vdr-plugin-live-3.1.3/doc/ChangeLog000066400000000000000000000307721414414333500170740ustar00rootroot000000000000002013-04-04 Dieter Hametner * This file is discontinued. To get an overview of the changes please consult the git history found on http://projects.vdr-developer.org/git/vdr-plugin-live.git/ 2009-09-07 Christian Wieninger * new user management within setup that also handles different user rights 2008-11-19 Christian Wieninger * new setup option to display channels without EPG 2008-10-21 Christian Wieninger * edit_timer.ecpp: new menu entry to select a recording directory. requires epgsearch plugin. * epgsearch.h/cpp: read the directory list via epgsearch's service interface version 1.2 2008-08-04 Christian Wieninger * italian translation update, thanks to Diego Pierotto 2008-02-07 Christian Wieninger * new menu with timer conflicts 2008-02-07 Dieter Hametner * buildutil/version-util: Further posix-ified the script. 2008-02-06 Dieter Hametner * buildutil/version-util: Function definitions in shell should not have whitespaces between the name and the parentheses. * i18n-generated.h: updated with latest translation contributions. 2008-01-25 Dieter Hametner * recman.cpp: Fixed memory leak, which resulted through the use of circular references by using tr1::shared_ptr, where a tr1::weak_ptr would have been needed. * pages/recordings.ecpp: Added button to delete that single recording. This feature is somehow limited in usability and should be used only for occasional deletion of recordings, because the page reloads when a recording is deleted. Unfortunately there is currently no way to remember the position to where the user navigated before he hit the delete button. In order to delete an other recording in the same subdirectory he will need to navigate there again after the page reloaded. 2008-01-18 Dieter Hametner * buildutil/version-util*: Added a shell and an awk script to calculate a version suffix string out of CVS current working dir status. This was a request bei jo01 and helps distinguish if newer versions are awailable. It should not break builds if something goes wrong in the script. At least it was my intention. The script also supports git repositories. But it has not been tested if it determines the correct git commit id based on current workdir contents. The caluculated version suffix is appended to the LIVE version string visible in the about box (?-Menu entry). 2008-01-15 Christian Wieninger * whats_on.ecpp: added listing 'Favorites', that lists all search results of search timers with setting 'Use in favorites menu' * Makefile: reversed Makefile changes that avoided commit conflicts, but caused compile time problems * po/*.po: added "translation team" since msgfmt complains about that 2008-01-04 Dieter Hametner * Makefile: Thanks to user 'ernie' in vdr-portal.de, who pointed out that the Makefile uses a bashism without setting SHELL to bash. UPDATE: User 'Keef' pointed out a way to omit bash arithmetic expressions. So the dependency on bash could be dropped again. 2007-12-25 Dieter Hametner - Added configuration option to disable the creation of IMDb URLs in the epg-info sections. This was done upon of feature reqest (Bug #401). Some minor fixes for the IMDb URLs in recordings. 2007-12-23 Dieter Hametner * po/*.po Modified headers in the .po files and updated copyright information to be more LIVE plugin aware. * Makefile Changed top level Makefile to not create headers in live.pot file. This prevents creation of new date header in .po file at fresh translations after updates from CVS and should avoid continous conflicts at every cvs update even if no changes took place in the local files. 2007-12-22 Dieter Hametner * live/js/live/vlc.js Added an own mootools based implementation of a controlling class for the vlc plugin as proposed by Rolf Ahrenberg. Features currently supported are: - start/stop play (pause is left out because it provides no timeshift functionality). - mute sound. (Use this instead of pause) - switch to full screen mode. - close the popup window. The class is customizable and you can see in vlc.ecpp how customization for the changed button strings has been done. 2007-12-18 Dieter Hametner - Integrated a new patch Rolf Ahrenberg sent to me privately The patch updates finish translations. Autoplay and the 'standard' video size is used in the playback window. 2007-12-17 Dieter Hametner - Adapted Rolf Ahrenbergs patch for VLC plugin streaming to the browser window using streamdev-server plugin on VDR. See Bug-Entry #343. You can stream current running program from the "whats_on now" schedules page into an extra browser window if javascript is active. Otherwise you get redirected onto a dedicated new live streaming page. 2007-10-21 Dieter Hametner - Renamed recordings.h/cpp files to recman.h/cpp. Adapted files that included them. - recman.h has extended functionality for recordings. It is not used yet. 2007-10-17 Dieter Hametner * css/styles.css * live/themes/orange-blue/css/theme.css Fix missing background color settings for browsers that don't have white as default background. Thanks to zirias. See: http://www.vdr-portal.de/board/thread.php?postid=659497#post659497 2007-09-18 Dieter Hametner - Eliminated 'images' directory. The images are now not longer compiled with ecpp into the executable module of live. With the content.ecpp part and file cache we have a equally performant solution to compiled in files. - Added file cache preload functionality. The file cache is filled with a list of files defined at compile time on plugin startup time. 2007-09-09 Dieter Hametner * tntconfig.cpp: allways give absolute paths to content.ecpp * pages/content.ecpp: check for absolute paths which don't contain upward references (e.g. '../') and deny such requests. 2007-09-07 Dieter Hametner * tntconfig.cpp: Checked and adapted MapUrl regular expressions to be more live setup secure. 2007-08-19 Dieter Hametner - Adapted (but not tested) live for the new localisation scheme since VDR 1.5.7 Might need some additional tweaking... 2007-07-29 Dieter Hametner - Implemented status notification popup if ajax is active. - Without Ajax it is now possible to request actions from vdr via a static page. 2007-07-22 Dieter Hametner Added toolbox buttons to epg info popup windows. Some style fixes for this. * pages/whats_on.ecpp: Use new pageelems.epg_tool_box component. * pages/pageelems.ecpp: new epg_tool_box component. 2007-07-21 Dieter Hametner * live/js/live/pageenhance.js: Enhance a normal web page with nifty web 2.0 features. * live/js/live/infowin.js: stand alone class. Used by pageenhance.js * live/js/live/hinttips.js: stand alone class. Used by pageenhance.js 2007-07-21 Dieter Hametner Made epgimages better styleable. Displaying them as floats right of the epg description text. 2007-07-20 Christian Wieninger Added support for EPG images: Specify the directory with your EPG images via the new commandline option '-e ' or '--epgimages= like -P'live -e /video/epgimages' 2007-07-12 Dieter Hametner Changed the javascript base of live. We now use the 'mootools' framework (see http://www.mootools.net for infos) to handle javascript in a browser independend fashion and for nifty Web 2.0 features. Based on this framework we have now tooltips that use the XHTML standard 'title' attribute and Web-2.0 popup windows for epg information. This Epg information is loaded on demand and once loaded, they are cached in the page for further viewing. On the other hand this also provides us with a solution to have live functioning without javascript at all. When done right, the same functionality can be achieved with or without enabled javascript in the browser. Currently there still are javascript only features, which will be resolved in the next weeks. This is a rather big change on many files, so they are not all mentioned here. 2007-06-22 Dieter Hametner Start of new 'standalone' javascript source directory for live javascript files. - Use mootools http://www.mootools.net/ as base library for 'modern' Javascript based functionality. 2007-06-15 Dieter Hametner * setup.ecpp: added option to disable infobox at all. 2007-06-14 Dieter Hametner * infobox: Keep update status of infobox in session. This allows the user to switch off status updates and change live pages. After a page change the status is updated once and then the users choice is respected. 2007-06-14 Dieter Hametner * infobox: show 'user friendly' error message when something went wrong while updating the status box. Fixed tooltip message for toggle update on/off of status box. 2007-06-13 Dieter Hametner * pages/schedule.ecpp: If no channel is given, and a current channel is known to vdr, select it when calling the schedule page in live. 2007-06-12 Dieter Hametner Added orange-blue theme as an example of a theme with dark background and light foreground colors. This theme also demonstrates the use of exchanged images (logo.png, tv.jpg and remotecontrol.jpg) * styles.css: some minor style fixes, that became visible while creating the orange-blue theme. 2007-06-11 Dieter Hametner Fixed style layout of the tables. Added class 'bottomrow' to the rows that are followed by empty spacer rows. 2007-06-06 Dieter Hametner Use GetConfigDir instead of USRDIR define. * pages/*.ecpp: begin of unification of table markup. Still needs some tweaking but the general framework is in place. * styles.css: Removed different table styles. Added two general table styles: - listing: for tables showing listings like search results or schedules. - formular: for tables used in input forms to layout the input elements. Added some general use styles, like 'bold', 'more', 'withmargin', 'nomargin', 'short', 'title', 'dotted' 2007-06-03 Dieter Hametner Added CSS based themeing support. For details please read doc/css-themeing.txt and doc/dev-conventions.txt. * setup.h, setup.cpp, setup.ecpp: added setup for theme and selection of theme. * pages/*.ecpp: added support for themable images. * tntconfig.cpp: cascaded search for images, to support themeing. 2007-06-03 Christian Wieninger Setup includes now a local net mask specifying the address range without necessary login (#321) 2007-06-02 Christian Wieninger required version of VDR is now >= 1.4.0-2 2007-06-01 Sascha Volkenandt The detection of featured plugins was uniformed. The display in the about box now reads "active: " or "required: " 2007-06-01 Dieter Hametner These changes fix bug entry #339 * css-themeing.txt: describe how to do css themeing. * content.ecpp: - check for additional parameter and use it as mime type. - use compile time variable USRDIR for path to the files loaded via content.ecpp * pageelems.ecpp: link to css/siteprefs.css * pages/*.ecpp: changed style link to pageelems.stylesheet component. * tntconfig.cpp: added MapUrl for css/cssfile. unrecorded Sascha Volkenandt Due to the introduction of a uniform header for C++ standard extenstions, the boost library is now only necessary if the used g++ compiler version is less than 4.0 vdr-plugin-live-3.1.3/doc/TODO.txt000066400000000000000000000014551414414333500166240ustar00rootroot00000000000000This is a list of ideas and TODO-Items for live plugin. - Add better support for the numerous CSS bugs in IE. Maybe by splitting style files in a similar way like it is done for YAML (http://www.yaml.de) - Create a CSS-themeing friendly url scheme. I.E. something like img//button.png. Where default as theme is always taken if is not found. - Give users the chance to override the selected style with some user-override settings. - Deliver truely static content, like images, styles, ECMAscript with tntnets sendfile functionality after resolving user selected themes paths. Take care to support browser cache optimization. - Deliver epg box infos through AJAX. This would make a ECMAScript capable browser mandatory. - Provide a way to get the infos on extra 'static' pages. vdr-plugin-live-3.1.3/doc/css-themeing.txt000066400000000000000000000105431414414333500204430ustar00rootroot00000000000000Live themeing in a few steps: ============================= - Copy the 'themes' directory from the sources to $VDRCONFIG/plugins/live (default: /video/vdr/plugins/live) - Go to setup page, select desired theme from listbox. You can add own themes by creating in themes a subdirectory with your theme. Read further for more detailed information about themeing. How to do live theming with CSS. ================================ Live supports CSS theming. While the structure of the html pages is given by the plugin, there is the possibility to change the look through CSS and exchanged images. Themable resources ------------------ CSS stylesheets and referenced images are themable. That means a theme can replace the icons and background images in the markup. Access scheme for the css stylesheets ------------------------------------- Each live page requests at least three stylesheets in the following order: 1. 'styles.css' (the build in stylesheet) is requested. 2. The theme master stylesheet 'theme.css' is requested. 3. A site preferences stylesheet is requested ('siteprefs.css') Location for the stylesheets ---------------------------- The initial stylesheet 'styles.css' provides a basic layout. It is a builtin stylesheet and can not be altered after live is compiled and installed. The theme stylesheed 'theme.css' is requested through following url: themes//css/theme.css The site preference stylesheet is requested through this url: css/siteprefs.css Access scheme for themable images --------------------------------- All themable images in the pages, that live delivers to the browser are accessed through the url themes//img/ If a image is not found under that url, the image is searched in common/img/ And if not found there, an attempt to deliver a built in image is taken. Location of the resources in the file system -------------------------------------------- All themable content must be present in the directory specified by 'GetConfigDir'. GetConfigDir returns at runtime the position in the filesystem where the plugins configuration file is stored. The location is build from the vdr config path appended with the plugins name (i.E. /var/lib/vdr/plugins/live). The themes are located in the 'themes' subdirectory of the above path. Structure of a theme package ---------------------------- A theme package consists of directory named after the theme name. It must contain the subdirectories 'css' and 'img'. Under css and img no other subdirectories are allowed for security reasons (see below). In the subdirectory css a stylesheet theme.css must exist in oder to override or extend the already defined styles from 'styles.css'. Additional images referenced through the stylesheet and images replacing the default images go to the 'img' subdirectory. Replacing images must have the same name like the image to be replaced. The live distribution comes with a few predefinded theme packages. You should take look into them to better understand this structure. Selecting a theme in live ------------------------- In the live setup page, the user can select the desired theme. When the settings are saved the selected themes become active. Live detects the available themes dynamicaly by scanning the 'themes' directory in plugins config directory for available themes and creates the select box from this information. So the installation of a new theme is easyly done by unpacking a theme-archive in the themes directory. This assumes the theme-archive follows the structure of a theme package as described above. Security provisions ------------------- Live will map every url starting with themes//css or themes//img to exactly these directories under the location of the themes directory. That means any path components after 'img' or 'css' are discarded. Only the basename of the url is appended to these directories. This is to prevent possible malicous requests to other locations in the filesystem by adding '..' to the request path. The downside of this is, that no additional directories below 'img' and 'css' are possible for the theme designer. User Contribution ================= If you created a nice new look, you can provide it to us. We will try to include it into the live distribution. If you need special html support for your styling needs, don't hesitate to submit a suggestion. vdr-plugin-live-3.1.3/doc/dev-conventions.txt000066400000000000000000000055021414414333500211750ustar00rootroot00000000000000Live development guidelines =========================== This file contains some guidelines for developers about what to obey when adding new functionality to live plugin. First of all please look at the existing code and how it was done there. We are still open for improvement suggestions though. We want to support a broad range of browsers. On one side are hand held devices like WEB-enabled PDAs or mobile phones. They often lack full grown support for ECMAScript and have small screen sizes. The other extreme are the desktop browsers like FireFox, Konqueror, Opera and perhaps IE (if the 'powers that be' make him more CSS compliant). Here WEB 2.0 features can improve the users experience. With or without ECMAScript -------------------------- Since not all browsers support ECMAScript, we need to make sure all functions live wants to provide need to be accessible through links. With the mootools framework and its selection functions we can enhance the user experience through ECMAScript by selecting the relevant elements in the DOM and attaching event handlers from the loaded script files. Thus when the user disables ECMAScript in his browser (or the browser does not support it) the traditional web technique of jumping between pages provides the functions. With enabled ECMAScript the event handlers can take over and provide a nifty Web 2.0 technique solution to the user. To enable a tooltip just add a 'title' attribute on the element and load 'hinttips.js' in your pages (Actually this will be allready done for you if you use the live page-framework). For popup windows that asynchronously load its contents you need to use normal links like your link text here . If 'infowin.js' is loaded it will enhance these links with AJAX functionality. If not the link will change to a new page with the requested information. This means that both users with and without ECMAScript support will benefit from the functions in live. Themeing -------- Current CSS based themeing in live depends on additional stylesheets and a configurable location to retrieve images from (see css-themeing.txt). Developers must use the <& pageelems.stylesheets &> component in their pages to include both the default and the themed stylesheet. This is the easy part, because stylesheets are referred in the header at a central location. More difficult is the access to images, which is spread around the pages at the corresponding locations. To support this, a new method in the live Setup class (see file setup.h) has been added. It is called 'GetThemedLink'. For every image, that might be customized, you must use a img tag according to this example: ") $>" alt="someimage" /> Please take a look in the existing ecpp pages for additional usage examples. vdr-plugin-live-3.1.3/epg_events.cpp000066400000000000000000000323171414414333500174150ustar00rootroot00000000000000 #include "epg_events.h" #include "tools.h" #include "recman.h" #include "setup.h" // STL headers need to be before VDR tools.h (included by ) #include #include #include #include #ifndef TVM2VDR_PL_WORKAROUND #define TVM2VDR_PL_WORKAROUND 0 #endif namespace vdrlive { /* * ------------------------------------------------------------------------- * EpgInfo * ------------------------------------------------------------------------- */ EpgInfo::EpgInfo(const std::string& id, const std::string& caption) : m_eventId(id), m_caption(caption) { } EpgInfo::~EpgInfo() { } const std::string EpgInfo::CurrentTime(const char* format) const { return FormatDateTime(format, time(0)); } const std::string EpgInfo::StartTime(const char* format) const { time_t start = GetStartTime(); return start ? FormatDateTime(format, start) : ""; } const std::string EpgInfo::EndTime(const char* format) const { time_t end = GetEndTime(); return end ? FormatDateTime(format, end) : ""; } int EpgInfo::Elapsed() const { return EpgEvents::ElapsedTime(GetStartTime(), GetEndTime()); } int EpgInfo::Duration() const { return EpgEvents::Duration(GetStartTime(), GetEndTime()); } /* * ------------------------------------------------------------------------- * EpgEvent * ------------------------------------------------------------------------- */ EpgEvent::EpgEvent(const std::string& id, const cEvent* event, const char* channelName) : EpgInfo(id, channelName), m_event(event) { } EpgEvent::~EpgEvent() { } /* * ------------------------------------------------------------------------- * EpgString * ------------------------------------------------------------------------- */ EpgString::EpgString(const std::string& id, const std::string& caption, const std::string& info) : EpgInfo(id, caption), m_info(info) { } EpgString::~EpgString() { } const std::string EpgString::Title() const { return m_info; } const std::string EpgString::ShortDescr() const { return ""; } const std::string EpgString::LongDescr() const { return ""; } time_t EpgString::GetStartTime() const { return time(0); } time_t EpgString::GetEndTime() const { return time(0); } /* * ------------------------------------------------------------------------- * EpgRecording * ------------------------------------------------------------------------- */ EpgRecording::EpgRecording(const std::string& recid, const cRecording* recording, const char* caption) : EpgInfo(recid, (caption != 0) ? caption : ""), m_recording(recording), m_ownCaption(caption != 0), m_checkedArchived(false), m_archived() { } EpgRecording::~EpgRecording() { m_recording = 0; } const std::string EpgRecording::Caption() const { if (m_ownCaption) { return EpgInfo::Caption(); } if (!m_recording) { return ""; } return Name(); } const std::string EpgRecording::Title() const { if (!m_recording) { return ""; } const cRecordingInfo* info = m_recording->Info(); return (info && info->Title()) ? info->Title() : Name(); } const std::string EpgRecording::ShortDescr() const { const cRecordingInfo* info = m_recording ? m_recording->Info() : 0; return (info && info->ShortText()) ? info->ShortText() : ""; } const std::string EpgRecording::LongDescr() const { const cRecordingInfo* info = m_recording ? m_recording->Info() : 0; return (info && info->Description()) ? info->Description() : ""; } const std::string EpgRecording::Archived() const { if (!m_checkedArchived && m_recording) { m_archived = RecordingsManager::GetArchiveDescr(m_recording); m_checkedArchived = true; } return m_archived; } const std::string EpgRecording::FileName() const { return m_recording->FileName(); } time_t EpgRecording::GetStartTime() const { return m_recording ? m_recording->Start() : 0; } time_t EpgRecording::GetEndTime() const { time_t endTime = 0; if (m_recording) { time_t startTime = m_recording->Start(); int length = m_recording->LengthInSeconds(); endTime = (length < 0) ? startTime : startTime + length; } return endTime; } int EpgRecording::Elapsed() const { cControl* pControl = cControl::Control(); if (pControl) { int current, total; if (pControl->GetIndex(current,total)) { if (total) { return (100 * current) / total; } } } return 0; } const std::string EpgRecording::Name() const { std::string name(m_recording->Name()); size_t index = name.find_last_of('~'); if (index != std::string::npos) { name = name.substr(index+1); } return name; } /* * ------------------------------------------------------------------------- * EmptyEvent * ------------------------------------------------------------------------- */ EmptyEvent::EmptyEvent(std::string const &id, tChannelID const &channelID, const char* channelName) : EpgInfo(id, channelName), m_channelID(channelID) { } EmptyEvent::~EmptyEvent() { } /* * ------------------------------------------------------------------------- * EpgEvents * ------------------------------------------------------------------------- */ namespace EpgEvents { std::string EncodeDomId(tChannelID const &chanId, tEventID const &eId) { std::string channelId(chanId.ToString()); std::string eventId("event_"); channelId = vdrlive::EncodeDomId(channelId, ".-", "pm"); eventId += channelId; eventId += '_'; eventId += lexical_cast(eId); return eventId; } void DecodeDomId(std::string const &epgid, tChannelID& channelId, tEventID& eventId) { std::string const eventStr("event_"); size_t delimPos = epgid.find_last_of('_'); std::string cIdStr = epgid.substr(eventStr.length(), delimPos - eventStr.length()); cIdStr = vdrlive::DecodeDomId(cIdStr, "mp", "-."); std::string const eIdStr = epgid.substr(delimPos+1); channelId = tChannelID::FromString(cIdStr.c_str()); eventId = lexical_cast(eIdStr); } EpgInfoPtr CreateEpgInfo(std::string const &epgid, cSchedules const *schedules) { std::string const errorInfo(tr("Epg error")); tEventID eventId = tEventID(); tChannelID channelId = tChannelID(); DecodeDomId(epgid, channelId, eventId); #if VDRVERSNUM >= 20301 LOCK_CHANNELS_READ; cChannel const *channel = Channels->GetByChannelID(channelId); #else cChannel const *channel = Channels.GetByChannelID(channelId); #endif if (!channel) { return CreateEpgInfo(epgid, errorInfo, tr("Wrong channel id")); } cSchedule const *schedule = schedules->GetSchedule(channel); if (!schedule) { return CreateEpgInfo(epgid, errorInfo, tr("Channel has no schedule")); } cEvent const *event = schedule->GetEvent(eventId); if (!event) { return CreateEpgInfo(epgid, errorInfo, tr("Wrong event id")); } return CreateEpgInfo(channel, event, epgid.c_str()); } EpgInfoPtr CreateEpgInfo(cChannel const *chan, cEvent const *event, char const *idOverride) { assert(chan); if (event) { std::string domId(idOverride ? idOverride : EncodeDomId(chan->GetChannelID(), event->EventID())); return EpgInfoPtr(new EpgEvent(domId, event, chan->Name())); } if (LiveSetup().GetShowChannelsWithoutEPG()) { std::string domId(idOverride ? idOverride : EncodeDomId(chan->GetChannelID(), 0)); return EpgInfoPtr(new EmptyEvent(domId, chan->GetChannelID(), chan->Name())); } return EpgInfoPtr(); } EpgInfoPtr CreateEpgInfo(std::string const &recid, cRecording const *recording, char const *caption) { return EpgInfoPtr(new EpgRecording(recid, recording, caption)); } EpgInfoPtr CreateEpgInfo(std::string const &id, std::string const &caption, std::string const &info) { return EpgInfoPtr(new EpgString(id, caption, info)); } bool ScanForEpgImages(std::string const & imageId, std::string const & wildcard, std::list & images) { bool found = false; const std::string filemask(LiveSetup().GetEpgImageDir() + "/" + imageId + wildcard); glob_t globbuf; globbuf.gl_offs = 0; if (!LiveSetup().GetEpgImageDir().empty() && glob(filemask.c_str(), GLOB_DOOFFS, NULL, &globbuf) == 0) { for(size_t i = 0; i < globbuf.gl_pathc; i++) { const std::string imagefile(globbuf.gl_pathv[i]); size_t delimPos = imagefile.find_last_of('/'); images.push_back(imagefile.substr(delimPos+1)); found = true; } globfree(&globbuf); } return found; } bool ScanForRecImages(std::string const & imageId, std::string const & recfolder , std::list & images) { bool found = false; const std::string filetypes[] = {"png", "jpg", "PNG", "JPG"}; int size = sizeof(filetypes)/sizeof(filetypes[0]); if (recfolder.empty()) { // dsyslog( "live: ScanForRecImages: recFolder empty for %s", imageId.c_str()); return found; } for (int j = 0; j < size; j++) { const std::string filemask(recfolder + "/*." + filetypes[j]); glob_t globbuf; globbuf.gl_offs = 0; if (glob(filemask.c_str(), GLOB_DOOFFS, NULL, &globbuf) == 0) { for(size_t i = 0; i < globbuf.gl_pathc; i++) { const std::string imagefile(globbuf.gl_pathv[i]); const std::string imagecopy(imagefile); size_t delimPos = imagefile.find_last_of('/'); images.push_back(imagefile.substr(delimPos+1)); // create a temporary symlink of the image in /tmp const std::string imagename(imagefile.substr(delimPos+1)); const std::string tmpfile("/tmp/" + imageId + "_" + imagename); char cmdBuff[500]; sprintf(cmdBuff,"ln -s \"%s\" \"%s\"",imagefile.c_str(),tmpfile.c_str()); int s = system(cmdBuff); if (s < 0) esyslog("live: ERROR: Couldn't execute command %s", cmdBuff); found = true; } globfree(&globbuf); } } return found; } class cTvMedia { public: std::string path; int width; int height; }; class ScraperGetPoster { public: // in const cEvent *event; // check type for this event const cRecording *recording; // or for this recording //out cTvMedia poster; }; std::string PosterTvscraper(const cEvent *event, const cRecording *recording) { if (LiveSetup().GetTvscraperImageDir().empty() ) return ""; static cPlugin *pTVScraper = cPluginManager::GetPlugin("tvscraper"); if (pTVScraper) { ScraperGetPoster call; call.event = event; call.recording = recording; if (pTVScraper->Service("GetPoster", &call)) { if(call.poster.path.compare(0, LiveSetup().GetTvscraperImageDir().length(), LiveSetup().GetTvscraperImageDir()) == 0) return call.poster.path.substr(LiveSetup().GetTvscraperImageDir().length()); } } return ""; } std::list EpgImages(std::string const &epgid) { size_t delimPos = epgid.find_last_of('_'); std::string imageId = epgid.substr(delimPos+1); std::list images; // Initially we scan for images that follow the scheme // '_.*' where distincition is any // character sequence. Usually distinction will be used // to assign more than one image to an epg event. Thus it // will be a digit or number. The sorting of the images // will depend on the 'distinction' lexical sorting // (similar to what ls does). // Example: // 112123_0.jpg first epg image for event id 112123 // 112123_1.png second epg image for event id 112123 if (! ScanForEpgImages(imageId, "_*.*", images)) { // if we didn't find images that follow the scheme // above we try to find images that contain only the // event id as file name without extension: if (! ScanForEpgImages(imageId, ".*", images)) { #if TVM2VDR_PL_WORKAROUND // if we didn't get images try to work arround a // bug in tvm2vdr. tvm2vdr seems always to use // one digit less, which leads in some rare cases // to the bug in LIVE, that unrelated and to many // images are displayed. But without this 'fix' // no images would be visible at all. The bug // should be fixed in tvm2vdr.pl (Perl version of // tvm2vdr). There exists a plugin - also called // tvm2vdr - which does not have that bug. imageId = imageId.substr(0, imageId.size()-1); ScanForEpgImages(imageId, "*.*", images); #endif } } return images; } std::list RecImages(std::string const &epgid, std::string const &recfolder) { size_t delimPos = epgid.find_last_of('_'); std::string imageId = epgid.substr(delimPos+1); std::list images; // Scan for all images in recording directory ScanForRecImages(imageId, recfolder, images); return images; } int ElapsedTime(time_t const startTime, time_t const endTime) { // Elapsed time is only meaningful when there is a non zero // duration (e.g. startTime != endTime and endTime > startTime) int duration = Duration(startTime, endTime); if (duration > 0) { time_t now = time(0); if ((startTime <= now) && (now <= endTime)) { return 100 * (now - startTime) / duration; } } return -1; } int Duration(time_t const startTime, time_t const endTime) { return endTime - startTime; } } // namespace EpgEvents }; // namespace vdrlive vdr-plugin-live-3.1.3/epg_events.h000066400000000000000000000171321414414333500170600ustar00rootroot00000000000000#ifndef VDR_LIVE_EPG_EVENTS_H #define VDR_LIVE_EPG_EVENTS_H #include "stdext.h" // STL headers need to be before VDR tools.h (included by ) #include #include #if TNTVERSION >= 30000 #include // must be loaded before any vdr include because of duplicate macros (LOG_ERROR, LOG_DEBUG, LOG_INFO) #endif #include #include #include namespace vdrlive { class EpgInfo; typedef std::shared_ptr EpgInfoPtr; // ------------------------------------------------------------------------- namespace EpgEvents { std::string EncodeDomId(tChannelID const &chanId, tEventID const &eventId); void DecodeDomId(std::string const &epgid, tChannelID &chanId, tEventID &eventId); /** * Allocate and initalize an epgEvent instance with the * passed channel and event information. * Never call this function with a NULL chan pointer */ EpgInfoPtr CreateEpgInfo(cChannel const *chan, cEvent const *event, char const *idOverride = 0); /** * This is the inverse creator for epgInfos to the creator above. */ EpgInfoPtr CreateEpgInfo(std::string const &epgid, cSchedules const *schedules); /** * Allocate and initalize an epgEvent instance with the * passed recording information. */ EpgInfoPtr CreateEpgInfo(std::string const &recid, cRecording const *recording, char const *caption = 0); /** * Allocate and initalize an epgEvent instance with the * passed string informations */ EpgInfoPtr CreateEpgInfo(std::string const &id, std::string const &caption, std::string const &info); /** * Return a list of EpgImage paths for a given epgid. */ std::string PosterTvscraper(const cEvent *event, const cRecording *recording); std::list EpgImages(std::string const &epgid); /** * Return a list of RecImages in the given folder. */ std::list RecImages(std::string const &epgid, std::string const &recfolder); /** * Calculate the duration. A duration can be zero or * negative. Negative durations are considered invalid by * LIVE. */ int Duration(time_t const startTime, time_t const endTime); /** * Calculate the elapsed time of a positive duration. This * takes into account the startTime and the current time. If * the current time is not in the interval startTime <= * currTime <= endTime the return value is -1. */ int ElapsedTime(time_t const startTime, time_t const endTime); } // namespace EpgEvents // ------------------------------------------------------------------------- class EpgInfo { protected: EpgInfo(std::string const &id, std::string const &caption); public: virtual ~EpgInfo(); virtual std::string const Id() const { return m_eventId; } virtual std::string const Caption() const { return m_caption; } virtual std::string const Title() const = 0; virtual std::string const ShortDescr() const = 0; virtual std::string const LongDescr() const = 0; virtual cChannel const * Channel() const { return 0; } virtual std::string const Archived() const { return ""; } virtual std::string const FileName() const { return ""; } virtual std::string const StartTime(const char* format) const; virtual std::string const EndTime(const char* format) const; virtual std::string const CurrentTime(const char* format) const; virtual int Duration() const; virtual int Elapsed() const; virtual time_t GetStartTime() const = 0; virtual time_t GetEndTime() const = 0; virtual cEvent const *Event() const { return NULL; } private: std::string m_eventId; std::string m_caption; }; // ------------------------------------------------------------------------- class EpgString : public EpgInfo { friend EpgInfoPtr EpgEvents::CreateEpgInfo(std::string const &, std::string const &, std::string const &); protected: EpgString(std::string const &id, std::string const &caption, std::string const &info); public: virtual ~EpgString(); virtual std::string const Title() const; virtual std::string const ShortDescr() const; virtual std::string const LongDescr() const; virtual time_t GetStartTime() const; virtual time_t GetEndTime() const; virtual std::string const FileName() const { return ""; } private: const std::string m_info; }; // ------------------------------------------------------------------------- class EpgEvent : public EpgInfo { friend EpgInfoPtr EpgEvents::CreateEpgInfo(cChannel const *, cEvent const *, char const *); protected: EpgEvent(std::string const &id, cEvent const *event, char const *channelName); public: virtual ~EpgEvent(); virtual std::string const Title() const { return std::string(m_event->Title() ? m_event->Title() : ""); } virtual std::string const ShortDescr() const { return std::string(m_event->ShortText() ? m_event->ShortText() : ""); } virtual std::string const LongDescr() const { return std::string(m_event->Description() ? m_event->Description() : ""); } virtual time_t GetStartTime() const { return m_event->StartTime(); } virtual time_t GetEndTime() const { return m_event->EndTime(); } #if VDRVERSNUM >= 20301 virtual cChannel const * Channel() const { LOCK_CHANNELS_READ; return Channels->GetByChannelID(m_event->ChannelID());} #else virtual cChannel const * Channel() const { return Channels.GetByChannelID(m_event->ChannelID());} #endif virtual std::string const FileName() const { return ""; } virtual cEvent const *Event() const { return m_event; } private: cEvent const * m_event; }; // ------------------------------------------------------------------------- class EmptyEvent : public EpgInfo { friend EpgInfoPtr EpgEvents::CreateEpgInfo(cChannel const *, cEvent const *, char const *); protected: EmptyEvent(std::string const &id, tChannelID const &channelID, const char* channelName); public: virtual ~EmptyEvent(); virtual std::string const Title() const { return tr("No EPG information available"); } virtual std::string const ShortDescr() const { return ""; } virtual std::string const LongDescr() const { return ""; } virtual time_t GetStartTime() const { return 0; } virtual time_t GetEndTime() const { return 0; } #if VDRVERSNUM >= 20301 virtual cChannel const * Channel() const { LOCK_CHANNELS_READ; return Channels->GetByChannelID(m_channelID);} #else virtual cChannel const * Channel() const { return Channels.GetByChannelID(m_channelID);} #endif virtual std::string const FileName() const { return ""; } private: tChannelID m_channelID; }; // ------------------------------------------------------------------------- class EpgRecording : public EpgInfo { friend EpgInfoPtr EpgEvents::CreateEpgInfo(std::string const &, cRecording const *, char const *); protected: EpgRecording(std::string const &recid, cRecording const *recording, char const *caption); const std::string Name() const; public: virtual ~EpgRecording(); virtual std::string const Caption() const; virtual std::string const Title() const; virtual std::string const ShortDescr() const; virtual std::string const LongDescr() const; virtual std::string const Archived() const; virtual std::string const FileName() const; virtual time_t GetStartTime() const; virtual time_t GetEndTime() const; virtual int Elapsed() const; private: const cRecording* m_recording; bool m_ownCaption; mutable bool m_checkedArchived; mutable std::string m_archived; }; }; // namespace vdrlive #endif // VDR_LIVE_EPG_EVENTS_H vdr-plugin-live-3.1.3/epgsearch.cpp000066400000000000000000000540771414414333500172260ustar00rootroot00000000000000 #include "epgsearch.h" #include "epgsearch/services.h" #include "exception.h" #include "livefeatures.h" #include "tools.h" #include namespace vdrlive { static char ServiceInterface[] = "Epgsearch-services-v1.0"; bool operator<( SearchTimer const& left, SearchTimer const& right ) { std::string leftlower = left.m_search; std::string rightlower = right.m_search; std::transform(leftlower.begin(), leftlower.end(), leftlower.begin(), (int(*)(int)) tolower); std::transform(rightlower.begin(), rightlower.end(), rightlower.begin(), (int(*)(int)) tolower); return leftlower < rightlower; } bool CheckEpgsearchVersion() { /* @winni: Falls Du an der Versionsnummer Anpassungen vornehmen willst, mach das bitte in livefeatures.h ganz unten. Danke */ const Features& f = LiveFeatures(); if ( f.Loaded() ) { if ( !f.Recent() ) throw HtmlError( tr("Required minimum version of epgsearch: ") + std::string( f.MinVersion() )); return true; } return false; } SearchTimer::SearchTimer() { Init(); } void SearchTimer::Init() { m_id = -1; m_useTime = false; m_startTime = 0; m_stopTime = 0; m_useChannel = NoChannel; m_useCase = false; m_mode = 0; m_useTitle = true; m_useSubtitle = true; m_useDescription = true; m_useDuration = false; m_minDuration = 0; m_maxDuration = 0; m_useDayOfWeek = false; m_dayOfWeek = 0; m_useEpisode = false; m_priority = lexical_cast(EPGSearchSetupValues::ReadValue("DefPriority")); m_lifetime = lexical_cast(EPGSearchSetupValues::ReadValue("DefLifetime")); m_fuzzytolerance = 1; m_useInFavorites = false; m_useAsSearchtimer = 0; m_action = 0; m_delAfterDays = 0; m_recordingsKeep = 0; m_pauseOnNrRecordings = 0; m_switchMinBefore = 1; m_useExtEPGInfo = false; m_useVPS = false; m_marginstart = lexical_cast(EPGSearchSetupValues::ReadValue("DefMarginStart")); m_marginstop = lexical_cast(EPGSearchSetupValues::ReadValue("DefMarginStop")); m_avoidrepeats = false; m_allowedrepeats = 0; m_compareTitle = false; m_compareSubtitle = 0; m_compareSummary = false; m_repeatsWithinDays = 0; m_blacklistmode = 0; m_menuTemplate = 0; m_delMode = 0; m_delAfterCountRecs = 0; m_delAfterDaysOfFirstRec = 0; m_useAsSearchTimerFrom = 0; m_useAsSearchTimerTil = 0; m_catvaluesAvoidRepeat = 0; m_ignoreMissingEPGCats = false; } SearchTimer::SearchTimer( std::string const& data ) { Init(); std::vector parts = StringSplit( data, ':' ); try { std::vector::const_iterator part = parts.begin(); for ( int i = 0; part != parts.end(); ++i, ++part ) { switch ( i ) { case 0: m_id = lexical_cast( *part ); break; case 1: m_search = StringReplace( StringReplace( *part, "|", ":" ), "!^pipe^!", "|" ); break; case 2: m_useTime = lexical_cast( *part ); break; case 3: if ( m_useTime ) m_startTime = lexical_cast( *part ); break; case 4: if ( m_useTime ) m_stopTime = lexical_cast( *part ); break; case 5: m_useChannel = lexical_cast( *part ); break; case 6: ParseChannel( *part ); break; case 7: m_useCase = lexical_cast( *part ); break; case 8: m_mode = lexical_cast( *part ); break; case 9: m_useTitle = lexical_cast( *part ); break; case 10: m_useSubtitle = lexical_cast( *part ); break; case 11: m_useDescription = lexical_cast( *part ); break; case 12: m_useDuration = lexical_cast( *part ); break; case 13: if ( m_useDuration ) m_minDuration = lexical_cast( *part ); break; case 14: if ( m_useDuration ) m_maxDuration = lexical_cast( *part ); break; case 15: m_useAsSearchtimer = lexical_cast( *part ); break; case 16: m_useDayOfWeek = lexical_cast( *part ); break; case 17: m_dayOfWeek = lexical_cast( *part ); break; case 18: m_useEpisode = lexical_cast( *part ); break; case 19: m_directory = StringReplace( StringReplace( *part, "|", ":" ), "!^pipe^!", "|" ); break; case 20: m_priority = lexical_cast( *part ); break; case 21: m_lifetime = lexical_cast( *part ); break; case 22: m_marginstart = lexical_cast( *part ); break; case 23: m_marginstop = lexical_cast( *part ); break; case 24: m_useVPS = lexical_cast( *part ); break; case 25: m_action = lexical_cast( *part ); break; case 26: m_useExtEPGInfo = lexical_cast( *part ); break; case 27: ParseExtEPGInfo( *part ); break; case 28: m_avoidrepeats = lexical_cast( *part ); break; case 29: m_allowedrepeats = lexical_cast( *part ); break; case 30: m_compareTitle = lexical_cast( *part ); break; case 31: m_compareSubtitle = lexical_cast( *part ); break; case 32: m_compareSummary = lexical_cast( *part ); break; case 33: m_catvaluesAvoidRepeat = lexical_cast< long >( *part ); break; case 34: m_repeatsWithinDays = lexical_cast( *part ); break; case 35: m_delAfterDays = lexical_cast( *part ); break; case 36: m_recordingsKeep = lexical_cast( *part ); break; case 37: m_switchMinBefore = lexical_cast( *part ); break; case 38: m_pauseOnNrRecordings = lexical_cast( *part ); break; case 39: m_blacklistmode = lexical_cast( *part ); break; case 40: ParseBlacklist( *part ); break; case 41: m_fuzzytolerance = lexical_cast( *part ); break; case 42: m_useInFavorites = lexical_cast( *part ); break; case 43: m_menuTemplate = lexical_cast( *part ); break; case 44: m_delMode = lexical_cast( *part ); break; case 45: m_delAfterCountRecs = lexical_cast( *part ); break; case 46: m_delAfterDaysOfFirstRec = lexical_cast( *part ); break; case 47: m_useAsSearchTimerFrom = lexical_cast( *part ); break; case 48: m_useAsSearchTimerTil = lexical_cast( *part ); break; case 49: m_ignoreMissingEPGCats = lexical_cast( *part ); break; } } } catch ( bad_lexical_cast const& ex ) { } } std::string SearchTimer::ToText() { std::string tmp_Start; std::string tmp_Stop; std::string tmp_minDuration; std::string tmp_maxDuration; std::string tmp_chanSel; std::string tmp_search; std::string tmp_directory; std::string tmp_catvalues; std::string tmp_blacklists; tmp_search = StringReplace(StringReplace(m_search, "|", "!^pipe^!"), ":", "|"); tmp_directory = StringReplace(StringReplace(m_directory, "|", "!^pipe^!"), ":", "|"); if (m_useTime) { std::stringstream os; os << std::setw(4) << std::setfill('0') << m_startTime; tmp_Start = os.str(); os.str(""); os << std::setw(4) << std::setfill('0') << m_stopTime; tmp_Stop = os.str(); } if (m_useDuration) { std::stringstream os; os << std::setw(4) << std::setfill('0') << m_minDuration; tmp_minDuration = os.str(); os.str(""); os << std::setw(4) << std::setfill('0') << m_maxDuration; tmp_maxDuration = os.str(); } if (m_useChannel==1) { #if VDRVERSNUM >= 20301 LOCK_CHANNELS_READ; cChannel const* channelMin = Channels->GetByChannelID( m_channelMin ); cChannel const* channelMax = Channels->GetByChannelID( m_channelMax ); #else cChannel const* channelMin = Channels.GetByChannelID( m_channelMin ); cChannel const* channelMax = Channels.GetByChannelID( m_channelMax ); #endif if (channelMax && channelMin->Number() < channelMax->Number()) tmp_chanSel = *m_channelMin.ToString() + std::string("|") + *m_channelMax.ToString(); else tmp_chanSel = *m_channelMin.ToString(); } if (m_useChannel==2) tmp_chanSel = m_channels; if (m_useExtEPGInfo) { for(unsigned int i=0; i0 && m_useChannel<3)?tmp_chanSel:"0") << ":" << (m_useCase?1:0) << ":" << m_mode << ":" << (m_useTitle?1:0) << ":" << (m_useSubtitle?1:0) << ":" << (m_useDescription?1:0) << ":" << (m_useDuration?1:0) << ":" << tmp_minDuration << ":" << tmp_maxDuration << ":" << m_useAsSearchtimer << ":" << (m_useDayOfWeek?1:0) << ":" << m_dayOfWeek << ":" << (m_useEpisode?1:0) << ":" << tmp_directory << ":" << m_priority << ":" << m_lifetime << ":" << m_marginstart << ":" << m_marginstop << ":" << (m_useVPS?1:0) << ":" << m_action << ":" << (m_useExtEPGInfo?1:0) << ":" << tmp_catvalues << ":" << (m_avoidrepeats?1:0) << ":" << m_allowedrepeats << ":" << (m_compareTitle?1:0) << ":" << m_compareSubtitle << ":" << (m_compareSummary?1:0) << ":" << m_catvaluesAvoidRepeat << ":" << m_repeatsWithinDays << ":" << m_delAfterDays << ":" << m_recordingsKeep << ":" << m_switchMinBefore << ":" << m_pauseOnNrRecordings << ":" << m_blacklistmode << ":" << tmp_blacklists << ":" << m_fuzzytolerance << ":" << (m_useInFavorites?1:0) << ":" << m_menuTemplate << ":" << m_delMode << ":" << m_delAfterCountRecs << ":" << m_delAfterDaysOfFirstRec << ":" << (long) m_useAsSearchTimerFrom << ":" << (long) m_useAsSearchTimerTil << ":" << m_ignoreMissingEPGCats; return os.str(); } void SearchTimer::ParseChannel( std::string const& data ) { switch ( m_useChannel ) { case NoChannel: m_channels = tr("All"); break; case Interval: ParseChannelIDs( data ); break; case Group: m_channels = data; break; case FTAOnly: m_channels = tr("FTA"); break; } } void SearchTimer::ParseChannelIDs( std::string const& data ) { std::vector parts = StringSplit( data, '|' ); m_channelMin = lexical_cast( parts[ 0 ] ); #if VDRVERSNUM >= 20301 LOCK_CHANNELS_READ; cChannel const* channel = Channels->GetByChannelID( m_channelMin ); #else cChannel const* channel = Channels.GetByChannelID( m_channelMin ); #endif if ( channel != 0 ) m_channels = channel->Name(); if ( parts.size() < 2 ) return; m_channelMax = lexical_cast( parts[ 1 ] ); #if VDRVERSNUM >= 20301 channel = Channels->GetByChannelID( m_channelMax ); #else channel = Channels.GetByChannelID( m_channelMax ); #endif if ( channel != 0 ) m_channels += std::string( " - " ) + channel->Name(); } void SearchTimer::ParseExtEPGInfo( std::string const& data ) { m_ExtEPGInfo = StringSplit( data, '|' ); } void SearchTimer::ParseBlacklist( std::string const& data ) { m_blacklistIDs = StringSplit( data, '|' ); } std::string SearchTimer::StartTimeFormatted() { time_t start = cTimer::SetTime(time(NULL), (((StartTime() / 100 ) % 100) * 60 * 60) + (StartTime() % 100 * 60)); return FormatDateTime(tr("%I:%M %p"), start); } std::string SearchTimer::StopTimeFormatted() { time_t stop = cTimer::SetTime(time(NULL), (((StopTime() / 100 ) % 100) * 60 * 60) + (StopTime() % 100 * 60)); return FormatDateTime(tr("%I:%M %p"), stop); } std::string SearchTimer::UseAsSearchTimerFrom(std::string const& format) { return DatePickerToC(m_useAsSearchTimerFrom, format); } std::string SearchTimer::UseAsSearchTimerTil(std::string const& format) { return DatePickerToC(m_useAsSearchTimerTil, format); } void SearchTimer::SetUseAsSearchTimerFrom(std::string const& datestring, std::string const& format) { m_useAsSearchTimerFrom = GetDateFromDatePicker(datestring, format); } void SearchTimer::SetUseAsSearchTimerTil(std::string const& datestring, std::string const& format) { m_useAsSearchTimerTil = GetDateFromDatePicker(datestring, format); } SearchTimers::SearchTimers() { Reload(); } bool SearchTimers::Reload() { Epgsearch_services_v1_0 service; if ( !CheckEpgsearchVersion() || cPluginManager::CallFirstService(ServiceInterface, &service) == 0 ) throw HtmlError( tr("EPGSearch version outdated! Please update.") ); #if VDRVERSNUM >= 20301 LOCK_CHANNELS_READ; #else ReadLock channelsLock( Channels, 0 ); #endif std::list timers = service.handler->SearchTimerList(); m_timers.assign( timers.begin(), timers.end() ); m_timers.sort(); return true; } bool SearchTimers::Save(SearchTimer* searchtimer) { Epgsearch_services_v1_0 service; if ( !CheckEpgsearchVersion() || cPluginManager::CallFirstService(ServiceInterface, &service) == 0 ) throw HtmlError( tr("EPGSearch version outdated! Please update.") ); if (!searchtimer) return false; #if VDRVERSNUM >= 20301 LOCK_CHANNELS_READ; #else ReadLock channelsLock( Channels, 0 ); #endif if (searchtimer->Id() >= 0) return service.handler->ModSearchTimer(searchtimer->ToText()); else { searchtimer->SetId(0); int id = service.handler->AddSearchTimer(searchtimer->ToText()); if (id >= 0) searchtimer->SetId(id); return (id >= 0); } } SearchTimer* SearchTimers::GetByTimerId( std::string const& id ) { for (SearchTimers::iterator timer = m_timers.begin(); timer != m_timers.end(); ++timer) if (timer->Id() == lexical_cast(id)) return &*timer; return NULL; } bool SearchTimers::ToggleActive(std::string const& id) { SearchTimer* search = GetByTimerId( id ); if (!search) return false; search->SetUseAsSearchTimer(search->UseAsSearchTimer()==1?0:1); return Save(search); } bool SearchTimers::Delete(std::string const& id) { SearchTimer* search = GetByTimerId( id ); if (!search) return false; Epgsearch_services_v1_0 service; if ( !CheckEpgsearchVersion() || cPluginManager::CallFirstService(ServiceInterface, &service) == 0 ) throw HtmlError( tr("EPGSearch version outdated! Please update.") ); if (service.handler->DelSearchTimer(lexical_cast( id ))) return Reload(); return false; } void SearchTimers::TriggerUpdate() { Epgsearch_updatesearchtimers_v1_0 service; service.showMessage = true; if ( !CheckEpgsearchVersion() || cPluginManager::CallFirstService("Epgsearch-updatesearchtimers-v1.0", &service) == 0 ) throw HtmlError( tr("EPGSearch version outdated! Please update.") ); } bool SearchTimer::BlacklistSelected(int id) const { for(unsigned int i=0; i parts = StringSplit( data, '|' ); try { std::vector::const_iterator part = parts.begin(); for ( int i = 0; part != parts.end(); ++i, ++part ) { switch ( i ) { case 0: m_id = lexical_cast( *part ); break; case 1: m_name = *part; break; case 2: m_menuname = *part; break; case 3: ParseValues( *part ); break; case 4: m_searchmode = lexical_cast( *part ); break; } } } catch ( bad_lexical_cast const& ex ) { } } void ExtEPGInfo::ParseValues( std::string const& data ) { m_values = StringSplit( data, ',' ); } bool ExtEPGInfo::Selected(unsigned int index, std::string const& values) { if (index >= m_values.size()) return false; std::string extepgvalue = StringTrim(m_values[index]); std::vector parts; parts = StringSplit( values, ',' ); for(unsigned int i=0; i infos = service.handler->ExtEPGInfoList(); m_infos.assign( infos.begin(), infos.end() ); } ChannelGroup::ChannelGroup( std::string const& data ) { std::vector parts = StringSplit( data, '|' ); try { std::vector::const_iterator part = parts.begin(); for ( int i = 0; part != parts.end(); ++i, ++part ) { switch ( i ) { case 0: m_name = *part; break; } } } catch ( bad_lexical_cast const& ex ) { } } ChannelGroups::ChannelGroups() { Epgsearch_services_v1_0 service; if ( !CheckEpgsearchVersion() || cPluginManager::CallFirstService(ServiceInterface, &service) == 0 ) throw HtmlError( tr("EPGSearch version outdated! Please update.") ); std::list list = service.handler->ChanGrpList(); m_list.assign( list.begin(), list.end() ); } Blacklist::Blacklist( std::string const& data ) { std::vector parts = StringSplit( data, ':' ); try { std::vector::const_iterator part = parts.begin(); for ( int i = 0; part != parts.end(); ++i, ++part ) { switch ( i ) { case 0: m_id = lexical_cast( *part ); break; case 1: m_search = StringReplace( StringReplace( *part, "|", ":" ), "!^pipe^!", "|" ); break; } } } catch ( bad_lexical_cast const& ex ) { } } Blacklists::Blacklists() { Epgsearch_services_v1_0 service; if ( !CheckEpgsearchVersion() || cPluginManager::CallFirstService(ServiceInterface, &service) == 0 ) throw HtmlError( tr("EPGSearch version outdated! Please update.") ); std::list list = service.handler->BlackList(); m_list.assign( list.begin(), list.end() ); m_list.sort(); } SearchResult::SearchResult( std::string const& data ) { std::vector parts = StringSplit( data, ':' ); try { std::vector::const_iterator part = parts.begin(); for ( int i = 0; part != parts.end(); ++i, ++part ) { switch ( i ) { case 0: m_searchId = lexical_cast( *part ); break; case 1: m_eventId = lexical_cast( *part ); break; case 2: m_title = StringReplace( *part, "|", ":" ); break; case 3: m_shorttext = StringReplace( *part, "|", ":" ); break; case 4: m_description = StringReplace( *part, "|", ":" ); break; case 5: m_starttime = lexical_cast( *part ); break; case 6: m_stoptime = lexical_cast( *part ); break; case 7: m_channel = lexical_cast( *part ); break; case 8: m_timerstart = lexical_cast( *part ); break; case 9: m_timerstop = lexical_cast( *part ); break; case 10: m_file = *part; break; case 11: m_timerMode = lexical_cast( *part ); break; } } } catch ( bad_lexical_cast const& ex ) { } } const cEvent* SearchResult::GetEvent(const cChannel* Channel) { if (!Channel) return NULL; #if VDRVERSNUM >= 20301 LOCK_SCHEDULES_READ; #else cSchedulesLock schedulesLock; const cSchedules* Schedules = cSchedules::Schedules(schedulesLock); if (!Schedules) return NULL; #endif const cSchedule *Schedule = Schedules->GetSchedule(Channel); if (!Schedule) return NULL; return Schedule->GetEvent(m_eventId); } std::set SearchResults::querySet; void SearchResults::GetByID(int id) { Epgsearch_services_v1_0 service; if ( !CheckEpgsearchVersion() || cPluginManager::CallFirstService(ServiceInterface, &service) == 0 ) throw HtmlError( tr("EPGSearch version outdated! Please update.") ); std::list list = service.handler->QuerySearchTimer(id); m_list.assign( list.begin(), list.end() ); m_list.sort(); } void SearchResults::GetByQuery(std::string const& query) { Epgsearch_services_v1_0 service; if ( !CheckEpgsearchVersion() || cPluginManager::CallFirstService(ServiceInterface, &service) == 0 ) throw HtmlError( tr("EPGSearch version outdated! Please update.") ); std::list list = service.handler->QuerySearch(query); m_list.assign( list.begin(), list.end() ); m_list.sort(); } std::string SearchResults::AddQuery(std::string const& query) { querySet.insert(query); return MD5Hash(query); } std::string SearchResults::PopQuery(std::string const& md5) { std::string query; if (!md5.empty()) { std::set::iterator it; for (it = querySet.begin(); it != querySet.end(); it++) { if (md5 == MD5Hash(*it)) { query = *it; querySet.erase(it); break; } } } return query; } RecordingDirs::RecordingDirs(bool shortList) { if (shortList) { Epgsearch_services_v1_2 service; if ( !CheckEpgsearchVersion() || cPluginManager::CallFirstService(ServiceInterface, &service) == 0 ) throw HtmlError( tr("EPGSearch version outdated! Please update.") ); m_set = service.handler->ShortDirectoryList(); } else { Epgsearch_services_v1_0 service; if ( !CheckEpgsearchVersion() || cPluginManager::CallFirstService(ServiceInterface, &service) == 0 ) throw HtmlError( tr("EPGSearch version outdated! Please update.") ); m_set = service.handler->DirectoryList(); } } std::string EPGSearchSetupValues::ReadValue(const std::string& entry) { Epgsearch_services_v1_0 service; if ( !CheckEpgsearchVersion() || cPluginManager::CallFirstService(ServiceInterface, &service) == 0 ) throw HtmlError( tr("EPGSearch version outdated! Please update.") ); return service.handler->ReadSetupValue(entry); } bool EPGSearchSetupValues::WriteValue(const std::string& entry, const std::string& value) { Epgsearch_services_v1_0 service; if ( !CheckEpgsearchVersion() || cPluginManager::CallFirstService(ServiceInterface, &service) == 0 ) throw HtmlError( tr("EPGSearch version outdated! Please update.") ); return service.handler->WriteSetupValue(entry, value); } std::string EPGSearchExpr::EvaluateExpr(const std::string& expr, const cEvent* event) { Epgsearch_services_v1_2 service; if ( !CheckEpgsearchVersion() || cPluginManager::CallFirstService(ServiceInterface, &service) == 0 ) throw HtmlError( tr("EPGSearch version outdated! Please update.") ); return service.handler->Evaluate(expr, event); } } // namespace vdrlive vdr-plugin-live-3.1.3/epgsearch.h000066400000000000000000000353451414414333500166700ustar00rootroot00000000000000#ifndef VDR_LIVE_EPGSEARCH_H #define VDR_LIVE_EPGSEARCH_H // STL headers need to be before VDR tools.h (included by ) #include #include #include #include #include #if TNTVERSION >= 30000 #include // must be loaded before any vdr include because of duplicate macros (LOG_ERROR, LOG_DEBUG, LOG_INFO) #endif #include #include namespace vdrlive { class SearchTimer; bool operator<( SearchTimer const& left, SearchTimer const& right ); class SearchTimer { public: enum eUseChannel { NoChannel = 0, Interval = 1, Group = 2, FTAOnly = 3 }; SearchTimer(); SearchTimer( std::string const& data ); void Init(); std::string ToText(); friend bool operator<( SearchTimer const& left, SearchTimer const& right ); int Id() const { return m_id; } void SetId(int id) { m_id = id; } std::string const& Search() const { return m_search; } void SetSearch(std::string const& search) { m_search = search; } int SearchMode() { return m_mode; } void SetSearchMode(int mode) { m_mode = mode; } int Tolerance() const { return m_fuzzytolerance; } void SetTolerance(int tolerance) { m_fuzzytolerance = tolerance; } bool MatchCase() const { return m_useCase; } void SetMatchCase(bool useCase) { m_useCase = useCase; } bool UseTime() const { return m_useTime; } void SetUseTime(bool useTime) { m_useTime = useTime; } bool UseTitle() const { return m_useTitle; } void SetUseTitle(bool useTitle) { m_useTitle = useTitle; } bool UseSubtitle() const { return m_useSubtitle; } void SetUseSubtitle(bool useSubtitle) { m_useSubtitle = useSubtitle; } bool UseDescription() const { return m_useDescription; } void SetUseDescription(bool useDescription) { m_useDescription = useDescription; } int StartTime() const { return m_startTime; } std::string StartTimeFormatted(); void SetStartTime(int startTime) { m_startTime = startTime; } int StopTime() const { return m_stopTime; } std::string StopTimeFormatted(); void SetStopTime(int stopTime) { m_stopTime = stopTime; } eUseChannel UseChannel() const { return static_cast( m_useChannel ); } void SetUseChannel(eUseChannel useChannel) { m_useChannel = useChannel; } tChannelID ChannelMin() const { return m_channelMin; } void SetChannelMin(tChannelID channelMin) { m_channelMin = channelMin; } tChannelID ChannelMax() const { return m_channelMax; } void SetChannelMax(tChannelID channelMax) { m_channelMax = channelMax; } std::string ChannelText() const { return m_channels; } void SetChannelText(const std::string& channels) { m_channels = channels; } int UseAsSearchTimer() const { return m_useAsSearchtimer; } void SetUseAsSearchTimer(int useAsSearchtimer) { m_useAsSearchtimer = useAsSearchtimer; } bool UseDuration() const { return m_useDuration; } void SetUseDuration(bool useDuration) { m_useDuration = useDuration; } int MinDuration() const { return m_minDuration; } void SetMinDuration(int minDuration) { m_minDuration = minDuration; } int MaxDuration() const { return m_maxDuration; } void SetMaxDuration(int maxDuration) { m_maxDuration = maxDuration; } bool UseDayOfWeek() const { return m_useDayOfWeek; } void SetUseDayOfWeek(bool useDayOfWeek) { m_useDayOfWeek = useDayOfWeek; } int DayOfWeek() const { return m_dayOfWeek; } void SetDayOfWeek(int dayOfWeek) { m_dayOfWeek = dayOfWeek; } bool UseInFavorites() const { return m_useInFavorites; } void SetUseInFavorites(bool useInFavorites) { m_useInFavorites = useInFavorites; } int SearchTimerAction() const { return m_action; } void SetSearchTimerAction(int action) { m_action = action; } bool UseSeriesRecording() const { return m_useEpisode; } void SetUseSeriesRecording(bool useEpisode) { m_useEpisode = useEpisode; } std::string const& Directory() const { return m_directory; } void SetDirectory(std::string const& directory) { m_directory = directory; } int DelRecsAfterDays() const { return m_delAfterDays; } void SetDelRecsAfterDays(int delAfterDays) { m_delAfterDays = delAfterDays; } int KeepRecs() const { return m_recordingsKeep; } void SetKeepRecs(int recordingsKeep) { m_recordingsKeep = recordingsKeep; } int PauseOnRecs() const {return m_pauseOnNrRecordings; } void SetPauseOnRecs(int pauseOnNrRecordings) { m_pauseOnNrRecordings = pauseOnNrRecordings; } int BlacklistMode() const {return m_blacklistmode; } void SetBlacklistMode(int blacklistmode) { m_blacklistmode = blacklistmode; } bool BlacklistSelected(int id) const; void ParseBlacklist( std::string const& data ); int SwitchMinBefore() const { return m_switchMinBefore; } void SetSwitchMinBefore(int switchMinBefore) { m_switchMinBefore = switchMinBefore; } bool UseExtEPGInfo() const { return m_useExtEPGInfo; } void SetUseExtEPGInfo(bool useExtEPGInfo) { m_useExtEPGInfo = useExtEPGInfo; } std::vector ExtEPGInfo() const { return m_ExtEPGInfo; } void SetExtEPGInfo(const std::vector& ExtEPGInfo) { m_ExtEPGInfo = ExtEPGInfo; } bool AvoidRepeats() const { return m_avoidrepeats; } void SetAvoidRepeats(bool avoidrepeats) { m_avoidrepeats = avoidrepeats; } int AllowedRepeats() const { return m_allowedrepeats; } void SetAllowedRepeats(int allowedrepeats) { m_allowedrepeats = allowedrepeats; } int RepeatsWithinDays() const { return m_repeatsWithinDays; } void SetRepeatsWithinDays(int repeatsWithinDays) { m_repeatsWithinDays = repeatsWithinDays; } bool CompareTitle() const { return m_compareTitle; } void SetCompareTitle(bool compareTitle) { m_compareTitle = compareTitle; } int CompareSubtitle() const { return m_compareSubtitle; } void SetCompareSubtitle(int compareSubtitle) { m_compareSubtitle = compareSubtitle; } bool CompareSummary() const { return m_compareSummary; } void SetCompareSummary(bool compareSummary) { m_compareSummary = compareSummary; } unsigned long CompareCategories() const { return m_catvaluesAvoidRepeat; } void SetCompareCategories(unsigned long compareCategories) { m_catvaluesAvoidRepeat = compareCategories; } int Priority() const { return m_priority; } void SetPriority(int priority) { m_priority = priority; } int Lifetime() const { return m_lifetime; } void SetLifetime(int lifetime) { m_lifetime = lifetime; } int MarginStart() const { return m_marginstart; } void SetMarginStart(int marginstart) { m_marginstart = marginstart; } int MarginStop() const { return m_marginstop; } void SetMarginStop(int marginstop) { m_marginstop = marginstop; } bool UseVPS() const { return m_useVPS; } void SetUseVPS(bool useVPS) { m_useVPS = useVPS; } int DelMode() const { return m_delMode; } void SetDelMode(int delMode) { m_delMode = delMode; } int DelAfterCountRecs() const { return m_delAfterCountRecs; } void SetDelAfterCountRecs(int delAfterCountRecs) { m_delAfterCountRecs = delAfterCountRecs; } int DelAfterDaysOfFirstRec() const { return m_delAfterDaysOfFirstRec; } void SetDelAfterDaysOfFirstRec(int delAfterDaysOfFirstRec) { m_delAfterDaysOfFirstRec = delAfterDaysOfFirstRec; } std::string UseAsSearchTimerFrom(std::string const& format); void SetUseAsSearchTimerFrom(std::string const& datestring, std::string const& format); std::string UseAsSearchTimerTil(std::string const& format); void SetUseAsSearchTimerTil(std::string const& datestring, std::string const& format); bool IgnoreMissingEPGCats() const { return m_ignoreMissingEPGCats; } void SetIgnoreMissingEPGCats(bool ignoreMissingEPGCats) { m_ignoreMissingEPGCats = ignoreMissingEPGCats; } private: int m_id; std::string m_search; bool m_useTime; int m_startTime; int m_stopTime; int m_useChannel; tChannelID m_channelMin; tChannelID m_channelMax; std::string m_channels; bool m_useCase; int m_mode; bool m_useTitle; bool m_useSubtitle; bool m_useDescription; bool m_useDuration; int m_minDuration; int m_maxDuration; bool m_useDayOfWeek; int m_dayOfWeek; bool m_useEpisode; int m_priority; int m_lifetime; int m_fuzzytolerance; bool m_useInFavorites; int m_useAsSearchtimer; int m_action; std::string m_directory; int m_delAfterDays; int m_recordingsKeep; int m_pauseOnNrRecordings; int m_switchMinBefore; int m_marginstart; int m_marginstop; bool m_useVPS; bool m_useExtEPGInfo; std::vector m_ExtEPGInfo; bool m_avoidrepeats; int m_allowedrepeats; bool m_compareTitle; int m_compareSubtitle; bool m_compareSummary; int m_repeatsWithinDays; int m_blacklistmode; std::vector m_blacklistIDs; int m_menuTemplate; unsigned long m_catvaluesAvoidRepeat; int m_delMode; int m_delAfterCountRecs; int m_delAfterDaysOfFirstRec; time_t m_useAsSearchTimerFrom; time_t m_useAsSearchTimerTil; bool m_ignoreMissingEPGCats; void ParseChannel( std::string const& data ); void ParseChannelIDs( std::string const& data ); void ParseExtEPGInfo( std::string const& data ); }; class ExtEPGInfo { public: ExtEPGInfo(std::string const& data ); int Id() const { return m_id; } std::string Name() const { return m_menuname; } std::vector Values() const { return m_values; } bool Selected(unsigned int index, std::string const& values); private: int m_id; std::string m_name; std::string m_menuname; std::vector m_values; int m_searchmode; void ParseValues( std::string const& data ); }; class ExtEPGInfos { public: typedef std::list ExtEPGInfoList; typedef ExtEPGInfoList::size_type size_type; typedef ExtEPGInfoList::iterator iterator; typedef ExtEPGInfoList::const_iterator const_iterator; ExtEPGInfos(); size_type size() const { return m_infos.size(); } iterator begin() { return m_infos.begin(); } const_iterator begin() const { return m_infos.begin(); } iterator end() { return m_infos.end(); } const_iterator end() const { return m_infos.end(); } private: ExtEPGInfoList m_infos; }; class ChannelGroup { public: ChannelGroup(std::string const& data ); std::string Name() { return m_name; } private: std::string m_name; }; class ChannelGroups { public: typedef std::list ChannelGroupList; typedef ChannelGroupList::size_type size_type; typedef ChannelGroupList::iterator iterator; typedef ChannelGroupList::const_iterator const_iterator; ChannelGroups(); size_type size() const { return m_list.size(); } iterator begin() { return m_list.begin(); } const_iterator begin() const { return m_list.begin(); } iterator end() { return m_list.end(); } const_iterator end() const { return m_list.end(); } private: ChannelGroupList m_list; }; class SearchTimers { public: typedef std::list TimerList; typedef TimerList::size_type size_type; typedef TimerList::iterator iterator; typedef TimerList::const_iterator const_iterator; SearchTimers(); bool Save(SearchTimer* searchtimer); bool Reload(); size_type size() const { return m_timers.size(); } iterator begin() { return m_timers.begin(); } const_iterator begin() const { return m_timers.begin(); } iterator end() { return m_timers.end(); } const_iterator end() const { return m_timers.end(); } SearchTimer* GetByTimerId( std::string const& id ); bool ToggleActive(std::string const& id); bool Delete(std::string const& id); void TriggerUpdate(); private: TimerList m_timers; }; class Blacklist { public: Blacklist( std::string const& data ); std::string const& Search() const { return m_search; } int Id() const { return m_id; } bool operator<( Blacklist const& other ) const { return Search() < other.Search(); } private: int m_id; std::string m_search; }; class Blacklists { public: typedef std::list blacklist; typedef blacklist::size_type size_type; typedef blacklist::iterator iterator; typedef blacklist::const_iterator const_iterator; Blacklists(); size_type size() const { return m_list.size(); } iterator begin() { return m_list.begin(); } const_iterator begin() const { return m_list.begin(); } iterator end() { return m_list.end(); } const_iterator end() const { return m_list.end(); } private: blacklist m_list; }; class SearchResult { public: SearchResult( std::string const& data ); int SearchId() const { return m_searchId; } tEventID EventId() const { return m_eventId; } std::string const& Title() const { return m_title; } std::string const& ShortText() const { return m_shorttext; } std::string const& Description() const { return m_description; } time_t StartTime() const { return m_starttime; } time_t StopTime() const { return m_stoptime; } tChannelID Channel() const { return m_channel; } time_t TimerStartTime() const { return m_timerstart; } time_t TimerStopTime() const { return m_timerstop; } int TimerMode() const { return m_timerMode; } bool operator<( SearchResult const& other ) const { return m_starttime < other.m_starttime; } const cEvent* GetEvent(const cChannel* Channel); #if VDRVERSNUM >= 20301 /* Be careful when calling this function concerning the lock order: * Timers, Channels, Recordings Schedules */ const cChannel* GetChannel() { LOCK_CHANNELS_READ; return Channels->GetByChannelID(m_channel); } #else const cChannel* GetChannel() { return Channels.GetByChannelID(m_channel); } #endif private: int m_searchId; tEventID m_eventId; std::string m_title; std::string m_shorttext; std::string m_description; time_t m_starttime; time_t m_stoptime; tChannelID m_channel; time_t m_timerstart; time_t m_timerstop; std::string m_file; int m_timerMode; }; class SearchResults { static std::set querySet; public: typedef std::list searchresults; typedef searchresults::size_type size_type; typedef searchresults::iterator iterator; typedef searchresults::const_iterator const_iterator; SearchResults() {} void GetByID(int id); void GetByQuery(std::string const& query); size_type size() const { return m_list.size(); } iterator begin() { return m_list.begin(); } const_iterator begin() const { return m_list.begin(); } iterator end() { return m_list.end(); } const_iterator end() const { return m_list.end(); } void merge(SearchResults& r) {m_list.merge(r.m_list); m_list.sort();} static std::string AddQuery(std::string const& query); static std::string PopQuery(std::string const& md5); private: searchresults m_list; }; class RecordingDirs { public: typedef std::set recordingdirs; typedef recordingdirs::size_type size_type; typedef recordingdirs::iterator iterator; typedef recordingdirs::const_iterator const_iterator; RecordingDirs(bool shortList=false); iterator begin() { return m_set.begin(); } const_iterator begin() const { return m_set.begin(); } iterator end() { return m_set.end(); } const_iterator end() const { return m_set.end(); } private: recordingdirs m_set; }; class EPGSearchSetupValues { public: static std::string ReadValue(const std::string& entry); static bool WriteValue(const std::string& entry, const std::string& value); }; class EPGSearchExpr { public: static std::string EvaluateExpr(const std::string& expr, const cEvent* event); }; } // namespace vdrlive #endif // VDR_LIVE_EPGSEARCH_H vdr-plugin-live-3.1.3/epgsearch/000077500000000000000000000000001414414333500165055ustar00rootroot00000000000000vdr-plugin-live-3.1.3/epgsearch/services.h000066400000000000000000000154261414414333500205110ustar00rootroot00000000000000/* Copyright (C) 2004-2008 Christian Wieninger This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Or, point your browser to http://www.gnu.org/licenses/old-licenses/gpl-2.0.html The author can be reached at cwieninger@gmx.de The project's page is at http://winni.vdr-developer.org/epgsearch */ #ifndef EPGSEARCHSERVICES_INC #define EPGSEARCHSERVICES_INC // STL headers need to be before VDR tools.h (included by ) #include #include #include #include #include // Data structure for service "Epgsearch-search-v1.0" struct Epgsearch_search_v1_0 { // in char* query; // search term int mode; // search mode (0=phrase, 1=and, 2=or, 3=regular expression) int channelNr; // channel number to search in (0=any) bool useTitle; // search in title bool useSubTitle; // search in subtitle bool useDescription; // search in description // out cOsdMenu* pResultMenu; // pointer to the menu of results }; // Data structure for service "Epgsearch-exttimeredit-v1.0" struct Epgsearch_exttimeredit_v1_0 { // in cTimer* timer; // pointer to the timer to edit bool bNew; // flag that indicates, if this is a new timer or an existing one const cEvent* event; // pointer to the event corresponding to this timer (may be NULL) // out cOsdMenu* pTimerMenu; // pointer to the menu of results }; // Data structure for service "Epgsearch-updatesearchtimers-v1.0" struct Epgsearch_updatesearchtimers_v1_0 { // in bool showMessage; // inform via osd when finished? }; // Data structure for service "Epgsearch-osdmessage-v1.0" struct Epgsearch_osdmessage_v1_0 { // in char* message; // the message to display eMessageType type; }; // Data structure for service "EpgsearchMenu-v1.0" struct EpgSearchMenu_v1_0 { // in // out cOsdMenu* Menu; // pointer to the menu }; // Data structure for service "Epgsearch-lastconflictinfo-v1.0" struct Epgsearch_lastconflictinfo_v1_0 { // in // out time_t nextConflict; // next conflict date, 0 if none int relevantConflicts; // number of relevant conflicts int totalConflicts; // total number of conflicts }; // Data structure for service "Epgsearch-searchresults-v1.0" struct Epgsearch_searchresults_v1_0 { // in char* query; // search term int mode; // search mode (0=phrase, 1=and, 2=or, 3=regular expression) int channelNr; // channel number to search in (0=any) bool useTitle; // search in title bool useSubTitle; // search in subtitle bool useDescription; // search in description // out class cServiceSearchResult : public cListObject { public: const cEvent* event; explicit cServiceSearchResult(const cEvent* Event) : event(Event) {} }; cList* pResultList; // pointer to the results }; // Data structure for service "Epgsearch-switchtimer-v1.0" struct Epgsearch_switchtimer_v1_0 { // in const cEvent* event; int mode; // mode (0=query existance, 1=add/modify, 2=delete) // in/out int switchMinsBefore; int announceOnly; // out bool success; // result }; // Data structures for service "Epgsearch-services-v1.0" class cServiceHandler { public: virtual std::list SearchTimerList() = 0; // returns a list of search timer entries in the same format as used in epgsearch.conf virtual int AddSearchTimer(const std::string&) = 0; // adds a new search timer and returns its ID (-1 on error) virtual bool ModSearchTimer(const std::string&) = 0; // edits an existing search timer and returns success virtual bool DelSearchTimer(int) = 0; // deletes search timer with given ID and returns success virtual std::list QuerySearchTimer(int) = 0; // returns the search result of the searchtimer with given ID in the same format as used in SVDRP command 'QRYS' (->MANUAL) virtual std::list QuerySearch(std::string) = 0; // returns the search result of the searchtimer with given settings in the same format as used in SVDRP command 'QRYS' (->MANUAL) virtual std::list ExtEPGInfoList() = 0; // returns a list of extended EPG categories in the same format as used in epgsearchcats.conf virtual std::list ChanGrpList() = 0; // returns a list of channel groups maintained by epgsearch virtual std::list BlackList() = 0; // returns a list of blacklists in the same format as used in epgsearchblacklists.conf virtual std::set DirectoryList() = 0; // List of all recording directories used in recordings, timers, search timers or in epgsearchdirs.conf virtual ~cServiceHandler() {} // Read a setup value virtual std::string ReadSetupValue(const std::string& entry) = 0; // Write a setup value virtual bool WriteSetupValue(const std::string& entry, const std::string& value) = 0; }; struct Epgsearch_services_v1_0 { // in/out std::unique_ptr handler; }; // Data structures for service "Epgsearch-services-v1.1" class cServiceHandler_v1_1 : public cServiceHandler { public: // Get timer conflicts virtual std::list TimerConflictList(bool relOnly=false) = 0; // Check if a conflict check is advised virtual bool IsConflictCheckAdvised() = 0; }; struct Epgsearch_services_v1_1 { // in/out std::unique_ptr handler; }; // Data structures for service "Epgsearch-services-v1.2" class cServiceHandler_v1_2 : public cServiceHandler_v1_1 { public: // List of all recording directories used in recordings, timers (and optionally search timers or in epgsearchdirs.conf) virtual std::set ShortDirectoryList() = 0; // Evaluate an expression against an event virtual std::string Evaluate(const std::string& expr, const cEvent* event) = 0; }; struct Epgsearch_services_v1_2 { // in/out std::unique_ptr handler; }; #endif vdr-plugin-live-3.1.3/exception.h000066400000000000000000000005131414414333500167120ustar00rootroot00000000000000#ifndef VDR_LIVE_EXCEPTION_H #define VDR_LIVE_EXCEPTION_H #include namespace vdrlive { class HtmlError: public std::runtime_error { public: explicit HtmlError( std::string const& message ): std::runtime_error( message ) {} virtual ~HtmlError() throw() {} }; } // namespace vdrlive #endif // VDR_LIVE_EXCEPTION_H vdr-plugin-live-3.1.3/ffmpeg.cpp000066400000000000000000000152461414414333500165240ustar00rootroot00000000000000 #include "ffmpeg.h" #include "setup.h" #include #include #include #if TNTVERSION >= 30000 #include #include #endif #include #include namespace vdrlive { FFmpegThread::FFmpegThread() :cThread("stream utility handler") { targetChannel = -1; dsyslog("Live: FFmpegTread() created"); } FFmpegThread::~FFmpegThread() { Stop(); dsyslog("Live: FFmpegTread() destructed"); } void FFmpegThread::StartFFmpeg(std::string s, int channel, int vopt) { if (targetChannel != channel || vOption != vopt) { dsyslog("Live: FFmpegTread::StartFFmpeg() change channel %d -> %d", targetChannel, channel); if ( Active() ) Stop(); targetChannel = channel; vOption = vopt; } session = s; Start(); dsyslog("Live: FFmpegTread::StartFFmpeg() completed"); } void FFmpegThread::Stop() { cw.Signal(); dsyslog("Live: FFmpegTread::Stop() try stopping"); if ( Active() ) Cancel( 5 ); dsyslog("Live: FFmpegTread::Stop() stopped"); } void FFmpegThread::Touch() { touch = true; } void FFmpegThread::Action() { dsyslog("Live: FFmpegTread::Action() started channel = %d", targetChannel); cPipe2 pp; std::string def = "ffmpeg -loglevel warning -f mpegts -analyzeduration 1.2M -probesize 5M " "-i -map 0:v -map 0:a:0 -c:v copy -c:a aac -ac 2"; std::vector vopts; vopts.push_back( LiveSetup().GetStreamVideoOpt0() ); vopts.push_back( LiveSetup().GetStreamVideoOpt1() ); vopts.push_back( LiveSetup().GetStreamVideoOpt2() ); vopts.push_back( LiveSetup().GetStreamVideoOpt3() ); if (vopts[0].empty() || vopts[0].find("") == std::string::npos) { // h264 vopts[0] = def; LiveSetup().SetStreamVideoOpt0(vopts[0]); } if (vopts[1].empty() || vopts[1].find("") == std::string::npos) { // h265 vopts[1] = def; LiveSetup().SetStreamVideoOpt1(vopts[1]); } if (vopts[2].empty() || vopts[2].find("") == std::string::npos) { // mpeg2 vopts[2] = def; LiveSetup().SetStreamVideoOpt2(vopts[2]); } if (vopts[3].empty() || vopts[3].find("") == std::string::npos) { // others vopts[3] = def; LiveSetup().SetStreamVideoOpt3(vopts[3]); } std::string packerCmd(vopts[vOption]); std::stringstream ss; ss.str(""); ss << "\"http://localhost:" << LiveSetup().GetStreamdevPort() << "/" << targetChannel << "\""; packerCmd.replace(packerCmd.find(""), 7, ss.str()); dsyslog("Live: FFmpegTread::Action packetizer cmd: %s", packerCmd.c_str()); try { int retry = 0; int count = 0; do { ss.str(""); ss << "mkdir -p /tmp/live-hls-buffer/" << session << " && " "cd /tmp/live-hls-buffer/" << session << " && rm -rf * && " "exec " << packerCmd << " " "-f hls -hls_time 1 -hls_start_number_source datetime -hls_flags delete_segments " "-master_pl_name master_"; ss << targetChannel; ss << ".m3u8 ffmpeg_"; ss << targetChannel; ss << "_data.m3u8"; bool ret = pp.Open(ss.str().c_str(), "w"); // start ffmpeg dsyslog("Live: FFmpegTread::Action::Open(%d) ffmpeg started", ret); ss.str(""); ss << "/tmp/live-hls-buffer/" << session << "/master_"; ss << targetChannel; ss << ".m3u8"; count = 0; do { cw.Wait(1000); std::ifstream f(ss.str().c_str()); if (f.good()) break; // check if ffmpeg starts to generate output dsyslog("Live: FFmpegTread::Action() ffmpeg starting... %d", count); } while (Running() && pp.Check() == 0 && ++count < 6); if (pp.Check() < 0) continue; if (count < 6) { dsyslog("Live: FFmpegTread::Action() ffmpeg running %d", count); break; } else { // ffmpeg did not start properly fwrite("q", 1, 1, pp); fflush(pp); // send quit commmand to ffmpeg usleep(200e3); int r = pp.Close(); dsyslog("Live: FFmpegTread::Action::Close(%d) disabled ffmpeg", r); usleep(500e3); } } while (retry++ < 2 && Running()); if (retry > 1) return; touch = false; count = 0; while (Running() && pp.Check() == 0 && count++ < 60) { if (touch) { touch = false; count = 0; } cw.Wait(1000); } fwrite("q", 1, 1, pp); fflush(pp); // send quit commmand to ffmpeg usleep(500e3); int r = pp.Close(); dsyslog("Live: FFmpegTread::Action::Close(%d) disabled ffmpeg", r); } catch (std::exception const& ex) { esyslog("ERROR: live FFmpegTread::Action() failed: %s", ex.what()); } dsyslog("Live: FFmpegTread::Action() finished"); } // --- cPipe2 ----------------------------------------------------------------- // cPipe2::Open() and cPipe2::Close() are based on code originally received from // Andreas Vitting cPipe2::cPipe2(void) { pid = -1; f = NULL; } cPipe2::~cPipe2() { Close(); } bool cPipe2::Open(const char *Command, const char *Mode) { int fd[2]; if (pipe(fd) < 0) { LOG_ERROR; return false; } if ((pid = fork()) < 0) { // fork failed LOG_ERROR; close(fd[0]); close(fd[1]); return false; } const char *mode = "w"; int iopipe = 0; if (pid > 0) { // parent process terminated = false; if (strcmp(Mode, "r") == 0) { mode = "r"; iopipe = 1; } close(fd[iopipe]); if ((f = fdopen(fd[1 - iopipe], mode)) == NULL) { LOG_ERROR; close(fd[1 - iopipe]); } return f != NULL; } else { // child process int iofd = STDOUT_FILENO; if (strcmp(Mode, "w") == 0) { iopipe = 1; iofd = STDIN_FILENO; } close(fd[iopipe]); if (dup2(fd[1 - iopipe], iofd) == -1) { // now redirect LOG_ERROR; close(fd[1 - iopipe]); _exit(-1); } else { int MaxPossibleFileDescriptors = getdtablesize(); for (int i = STDERR_FILENO + 1; i < MaxPossibleFileDescriptors; i++) close(i); //close all dup'ed filedescriptors if (execl("/bin/sh", "sh", "-c", Command, NULL) == -1) { LOG_ERROR_STR(Command); close(fd[1 - iopipe]); _exit(-1); } } _exit(0); } } int cPipe2::Check(void) { int ret = -1; if (terminated) return -1; if (pid > 0) { int status = 0; ret = waitpid(pid, &status, WNOHANG);; if (ret < 0) { if (errno != EINTR && errno != ECHILD) { LOG_ERROR; return ret; } } if (ret > 0) terminated = true; } return ret; } int cPipe2::Close(void) { int ret = -1; if (f) { fclose(f); f = NULL; } if (pid > 0) { int status = 0; int i = 5; while (i > 0) { ret = waitpid(pid, &status, WNOHANG); if (ret < 0) { if (errno != EINTR && errno != ECHILD) { LOG_ERROR; break; } } else if (ret == pid) break; i--; cCondWait::SleepMs(100); } if (!i) { kill(pid, SIGINT); cCondWait::SleepMs(100); ret = waitpid(pid, &status, WNOHANG); kill(pid, SIGKILL); cCondWait::SleepMs(100); ret = waitpid(pid, &status, WNOHANG); } else if (ret == -1 || !WIFEXITED(status)) ret = -1; pid = -1; } return ret; } } // namespace vdrlive vdr-plugin-live-3.1.3/ffmpeg.h000066400000000000000000000015231414414333500161620ustar00rootroot00000000000000#ifndef VDR_LIVE_THREAD_H #define VDR_LIVE_THREAD_H #include #include namespace vdrlive { class FFmpegThread : public cThread { public: FFmpegThread(); ~FFmpegThread(); void StartFFmpeg(std::string s, int channel, int vopt); void Stop(); void Touch(); protected: void Action(); private: cCondWait cw; bool touch = false; int targetChannel; int vOption = 0; std::string session; }; // cPipe2 implements a pipe that closes all unnecessary file descriptors in // the child process. This is an improved variant of the vdr::cPipe class cPipe2 { private: pid_t pid; bool terminated = false; FILE *f; public: cPipe2(void); ~cPipe2(); operator FILE* () { return f; } bool Open(const char *Command, const char *Mode); int Check(void); int Close(void); }; } // namespace vdrlive #endif // VDR_LIVE_THREAD_H vdr-plugin-live-3.1.3/filecache.cpp000066400000000000000000000016221414414333500171540ustar00rootroot00000000000000 #include "filecache.h" #include #include #include #include namespace vdrlive { std::time_t FileObject::get_filetime( std::string const& path ) { struct stat sbuf; if ( stat( path.c_str(), &sbuf ) < 0 ) return 0; return sbuf.st_ctime; } bool FileObject::load() { std::ifstream ifs( m_path.c_str(), std::ios::in | std::ios::binary | std::ios::ate ); if ( !ifs ) return false; std::streamsize size = ifs.tellg(); ifs.seekg( 0, std::ios::beg ); std::vector data( size ); data.resize( size ); ifs.read( &data[0], size ); ifs.close(); m_ctime = get_filetime( m_path ); m_data.swap( data ); return true; } FileCache& LiveFileCache() { static FileCache instance( 1000000 ); return instance; } } // namespace vdrlive #if 0 using namespace vdrlive; int main() { FileCache::ptr_type f = LiveFileCache().get("/tmp/live/active.png"); } #endif vdr-plugin-live-3.1.3/filecache.h000066400000000000000000000032111414414333500166150ustar00rootroot00000000000000#ifndef VDR_LIVE_FILECACHE_H #define VDR_LIVE_FILECACHE_H #include "cache.h" // STL headers need to be before VDR tools.h (included by ) #include #include #include #include #include namespace vdrlive { class FileObject { public: FileObject( std::string const& path ) : m_ctime( std::numeric_limits::max() ) , m_path( path ) {} std::size_t size() const { return m_data.size(); } std::size_t weight() const { return size(); } bool is_current() const { return m_ctime == get_filetime( m_path ); } bool load(); char const* data() const { return &m_data[0]; } std::time_t ctime() const { return m_ctime; } private: static std::time_t get_filetime( std::string const& path ); mutable std::time_t m_ctime; std::string m_path; std::vector m_data; }; class FileCache: public vgstools::cache { typedef vgstools::cache base_type; public: FileCache( size_t maxWeight ): base_type( maxWeight ) {} ptr_type get( key_type const& key ) { cMutexLock lock( &m_mutex ); // dsyslog( "vdrlive::FileCache::get( %s )", key.c_str() ); // dsyslog( "vdrlive::FileCache had %u entries (weight: %u)", count(), weight() ); ptr_type result = base_type::get( key ); // dsyslog( "vdrlive::FileCache now has %u entries (weight: %u)", count(), weight() ); // dsyslog( "vdrlive::FileCache::get( %s ) = %p", key.c_str(), result.get() ); return result; } private: cMutex m_mutex; }; //typedef vgstools::cache FileCache; FileCache& LiveFileCache(); } // namespace vdrlive #endif // VDR_LIVE_FILECACHE_H vdr-plugin-live-3.1.3/global.mk000066400000000000000000000017111414414333500163350ustar00rootroot00000000000000# # Add macros and definitions which shall be available for all Makefiles # This might be added to VDR main directory in the future # build mode (0 - non-verbose, 1 - verbose) VERBOSE ?= 0 # Desplay percentage (0 - no percentage, 1 - print xxx% (not 100% accurate!)) #WITH_PERCENT ?= 0 # does not work currently override WITH_PERCENT := 0 # pretty print macros ifeq ($(WITH_PERCENT),1) ifndef ECHO I := i TARGET_COUNTER = $(words $(I)) $(eval I += i) TOTAL_TARGETS := $(shell $(MAKE) $(MAKECMDGOALS) --dry-run --file=$(firstword $(MAKEFILE_LIST)) \ --no-print-directory --no-builtin-rules --no-builtin-variables ECHO="COUNTTHIS" | grep -c "COUNTTHIS") ECHO = echo "[$(shell expr " $(shell echo $$((${TARGET_COUNTER} * 100 / ${TOTAL_TARGETS})))" : '.*\(...\)$$')%]" endif else ECHO := echo endif ifeq ($(VERBOSE),0) override Q := @ PRETTY_PRINT = @$(ECHO) $(1) AR_NUL := > /dev/null 2>&1 else override Q := PRETTY_PRINT := AR_NUL := endif vdr-plugin-live-3.1.3/grab.cpp000066400000000000000000000037451414414333500161740ustar00rootroot00000000000000 #include "grab.h" #include "tasks.h" #include namespace vdrlive { const unsigned int GrabMinIntervalMs = 500; const unsigned int GrabPauseIntervalMs = GrabMinIntervalMs * 20; class GrabImageTask: public StickyTask { public: explicit GrabImageTask( int quality = 80, int width = 729, int height = 480 ) : m_firstTime( 0 ) , m_lastTime( 0 ) , m_quality( quality ) , m_width( width ) , m_height( height ) {} void Activate() { m_firstTime = 0; } bool IsActive(); private: uint64_t m_firstTime; uint64_t m_lastTime; int m_quality; int m_width; int m_height; bool GrabImage(); virtual void Action(); }; bool GrabImageTask::IsActive() { cMutexLock lock( &LiveTaskManager() ); return GrabImage(); } bool GrabImageTask::GrabImage() { cDevice* device = cDevice::PrimaryDevice(); if ( device == 0 ) { SetError( tr("Couldn't aquire primary device") ); return false; } int size = 0; uchar* image = device->GrabImage( size, true, m_quality, m_width, m_height ); if ( image == 0 ) { SetError( tr("Couldn't grab image from primary device") ); return false; } LiveGrabImageManager().PutImage( reinterpret_cast( image ), size ); return true; } void GrabImageTask::Action() { uint64_t now = cTimeMs::Now(); if ( m_firstTime == 0 ) m_firstTime = now; if ( now - m_lastTime < GrabMinIntervalMs || now - m_firstTime > GrabPauseIntervalMs ) return; if ( !GrabImage() ) return; m_lastTime = now; } GrabImageManager::GrabImageManager() : m_task( new GrabImageTask ) , m_size( 0 ) { } GrabImageInfo GrabImageManager::GetImage() const { cMutexLock lock( &LiveTaskManager() ); m_task->Activate(); return std::make_pair( m_image, m_size ); } bool GrabImageManager::CanGrab() const { return m_task->IsActive(); } void GrabImageManager::PutImage( char* image, int size ) { m_image.reset( image, &free ); m_size = size; } GrabImageManager& LiveGrabImageManager() { static GrabImageManager instance; return instance; } } // namespace vdrlive vdr-plugin-live-3.1.3/grab.h000066400000000000000000000013551414414333500156340ustar00rootroot00000000000000#ifndef VDR_LIVE_GRAB_H #define VDR_LIVE_GRAB_H #include "stdext.h" #include namespace vdrlive { typedef std::shared_ptr GrabImagePtr; typedef std::pair GrabImageInfo; class GrabImageTask; class GrabImageManager { friend GrabImageManager& LiveGrabImageManager(); friend class GrabImageTask; public: bool CanGrab() const; GrabImageInfo GetImage() const; private: GrabImageManager(); GrabImageManager( GrabImageManager const& ); GrabImageManager& operator=( GrabImageManager const& ); void PutImage( char* image, int size ); std::unique_ptr m_task; GrabImagePtr m_image; int m_size; }; GrabImageManager& LiveGrabImageManager(); } // namespace vdrlive #endif // VDR_LIVE_GRAB_H vdr-plugin-live-3.1.3/i18n.cpp000066400000000000000000000013531414414333500160310ustar00rootroot00000000000000/* This file has some own functionality and is used as backward compatibility for vdr prior to version 1.5.7 language support. Backward compatibility to old language support has been dropped on Feb 13, 2015. */ #include "i18n.h" #include namespace vdrlive { I18n& LiveI18n() { static I18n instance; return instance; } I18n::I18n() : m_encoding(cCharSetConv::SystemCharacterTable() ? cCharSetConv::SystemCharacterTable() : "UTF-8") { // fix encoding spelling for html standard. std::string const iso("iso"); if (m_encoding.find(iso) != std::string::npos) { if (iso.length() == m_encoding.find_first_of("0123456789")) { m_encoding.insert(iso.length(), "-"); } } } } // namespace vdrlive vdr-plugin-live-3.1.3/i18n.h000066400000000000000000000005561414414333500155020ustar00rootroot00000000000000#ifndef VDR_LIVE_I18N_H #define VDR_LIVE_I18N_H #include namespace vdrlive { class I18n { friend I18n& LiveI18n(); private: std::string m_encoding; I18n( I18n const& ); // don't copy I18n(); public: std::string const& CharacterEncoding() const { return m_encoding; } }; I18n& LiveI18n(); } // namespace vdrlive #endif // VDR_LIVE_I18N_H vdr-plugin-live-3.1.3/javascript/000077500000000000000000000000001414414333500167125ustar00rootroot00000000000000vdr-plugin-live-3.1.3/javascript/AUTHORS000066400000000000000000000003011414414333500177540ustar00rootroot00000000000000DOM Tooltip Library 0.70 Maintainer: Dan Allen Contributors: Josh Gross Jason Rust Vic Mackey vdr-plugin-live-3.1.3/javascript/BUGS000066400000000000000000000015511414414333500173770ustar00rootroot00000000000000$Id: BUGS,v 1.1 2007/01/04 22:29:18 thomas Exp $ These are the known bugs/issues in the DOM Tooltip library: - Opera7 popups up a native tooltip title for the link, which goes over the custom tooltip (all you need to do is disable tooltips in the opera preferences) - you cannot use the margin style on the body in Opera7, you have to use padding instead you can read over at opera.com why they don't support this...apparently not a legit style - offset* properties do not account for margins, so styles with margins could lead to issues - fading in and fading out in mozilla is somewhat flaky...it works but has flickering...this flickering is NOT the tooltip code, it is the rendering of the styles in mozilla...only time will help us here (this seems to be resolved in Firefox 1.0) - inframe tips do not appear over top of the iframe in Opera and Konqueror vdr-plugin-live-3.1.3/javascript/Changelog000066400000000000000000000377131414414333500205370ustar00rootroot00000000000000$Id: Changelog,v 1.1 2007/01/04 22:29:18 thomas Exp $ DOM Tootip: Javascript tooltip generator version 0.7.3 (SVN HEAD): * added example integration with behaviour.js * added domTT_postponeActivation option to workaround 'operation aborted' error * added domTT_closeAll function to remove all tooltips on page * 'content' option can now be a function to return the content * make id prefix configurable * close velcro tip by clicking on it * allow tips to be created with no event (null value) * allow disabling of collision detection * fixed problem of tip not disappearing on rapid mouse movement * fixed problem of using global mouse position before first mouse movement * prevent tooltip events on banned tags, such as OPTION (for consistency) * improvements to example14 to allow caption to render properly version 0.7.2 (2006/04/12): * added example to demonstrate custom positioning with a parent * fixes in clear timeout made in domLib.js * fixed a regression in the collision detection that left elements hidden * content and caption are only cloned if domTT_cloneNodes is set to true, otherwise the reference is used version 0.7.1 (2005/07/16): * changed fading library from alphaAPI to fadomatic * fixed problem with fade where links and buttons would become inactive * fixed problem where tooltips would hang around if browser doesn't support fading * released under Apache 2.0 license * added example for dynamically updating tooltip content * added method for updating tooltip content, domTT_update() * enabled caption to be html or a DOM node * removed the clone() prototype method in domLib to prevent conflicts with other libraries * option to have only one tip show at a time * fixed edge detection to be more precise * added fadeMax as upper limit for an alpha fade-in * fixed wrapping problem when tip nears edge in opera and IE * replace domLib_isKonq with domLib_isKHTML * added auto-generated tooltips from the title attribute for elements with class "tooltip" * custom offsets can be set on a per-tip basis * tips are now indexed on both tip id and owner id, for greater flexibility * added a convenient method for use in custom close events, domTT_close() * custom id can now be used for the tip for easy reference * domTT_classPrefix is now domTT_styleClass, option classPrefix is now styleClass * wrapper div for contents styled with generic class 'contents' * wrapper for caption is now styled with generic class 'caption' * make drag an optional parameter for tip to turn on/off dragging of sticky tips * trail can now be either 'x' or 'y' which will lock trailing to a single axis * added HOWTO * updated all examples version 0.7.0 (2004/11/10): * Create tip on parent specified instead on document.body and then moving the tip in the DOM * Added a standards mode detection, which was the root of edge bleed issues * Added info in README about how to make the program smaller using jsmin * Fixed memory leaks in IE caused by using inner functions * Now works in Mac IE * Tooltips can be created from a child iframe element * IE 5.0 (Windows) removed from supported list of browsers version 0.6.0 (2003/02/13): * major rewrite (please consult this changelog and example for new requirements) * made fading modular using alphaAPI (seperate file, alphaAPI.js) * large gains in speed and compliance (fix Konq and IE 5 bugs) * eliminate need for domTT_activate() in the mousemove event handler!!! * new option 'trail' to specify tip to follow mouse movement (only for absolute) * changed domTT_true/false() functions to makeTrue/false()!!! * changed option 'status' option to 'statusText'!!! * changed 'prefix' option to 'classPrefix'!!! * changed 'close' option to 'closeAction'!!! * made dragging of tips loadable (seperate file, domTT_drag.js) * divided out common functions from domTT functions (seperate file, domLib.js) * browser detection variables now prefixed with domLib_is*!!! * to create an onload window, first option is unique id (no longer have 'id' option) version 0.5.5 (2003/02/09): * fixed major crashes in IE 5.0 (cannot use delays since setTimeout is buggy) * fixed hideList error in all browsers * fixed a bug on example10.html when using a popup version 0.5.4 (2003/02/05): * fixed a scroll offset problem when IE is in compatibility mode * fixed problem where select box detection nixed element inside tooltip version 0.5.3 (2003/01/29): * fix misspelled document on like 971 in domTT.js version 0.5.2 (2003/01/17): * fix for document.documentElement.scrollTop for IE in standards compliance mode version 0.5.1 (2002/12/19): * implemented callTimeout() as an wrapper for setTimeout() for variable persistence * konqueror can now implement delays for tips!!! * konqueror can now handle tip lifetime!!! * added workaround for some konqueror quirks version 0.5.0 (2002/12/19): * fixed invalid variable name tmp_offsetX...regression from fixes in 0.4.9 version 0.4.9 (2002/12/18): * reworked domTT_deactivate() a bit * can now specify an 'id' option on each tip to have multiple tips on one trigger * fixed the activateTimeout process to rid of lingering bugs * updated demos version 0.4.8 (2002/12/11): * fixed recursion bug version 0.4.7 (2002/12/08): * dragging of sticky tips in konqueror!! * cleaned up the mouseout code a great deal and now it actually works as expected * selects only appear again when all tooltips which hid them are cleared away!!! * fixed IE javascript error caused by global onmousemove operating before page load * simplified deactivate by putting code for unhide selects in detectCollisions() * updated demos version 0.4.6 (2002/12/07): * eliminated unnecessary code in domTT_show() * konqueror fixes (checks in wrong place, clientHeight problem) * fixed onclick x, y measurement in konqueror version 0.4.5 (2002/12/06): * added maxWidth option (false to disable) and width option now independent * added workaround for maxWidth bug in Opera * switch to toggling display property to hide rather than using visibility hidden * fixed error in IE 5.5 bypassing a safeguard and causing a javascript error * fixed compliance error with IE 5.5 when executing IE hack for float * fixed height calculation with IE 5.5 vs IE 6 (compliance difference) * fixed case when hack IE code was executing under the wrong circumstances * fixing small javascript errors * totally block IE 5.0 until I can get to testing it * demo fixes and cleanups * fixed missing check for tip object existance in mouseout function version 0.4.4 (2002/12/06): * fixed onload problem in IE version 0.4.3 (2002/12/05): * code cleanups with strict compliance mozilla * fixed so that using domTT_activate() can be used as an onload event * closeLink will be interpreted as html (but note the link is automatically created) version 0.4.2 (2002/12/05): * fixed missing units in drag update * fixed width calculation in IE in strict mode * fix document.body.clientHeight -> document.documentElement.clientHeight (IE strict.dtd) * catch permission errors in mozilla to write status text version 0.4.1 (2002/12/05): * forget to add contentEditable when made changed to domTT_create() in 0.4.0 * fixes to the domTT_isDescendantOf to exclude absolute elements * fixed error in mozilla (tip was trying to be destroyed twice) * fixed regexp bug in IE 5.01 * fixed link in demo for opera (example8.html) * fixed javascript error in IE when triggerObj was #document * fixed IE bug when contentEditable property was screwing up the height * demo fixes version 0.4.0 (2002/12/02): * add required 'this' add the beginning of every domTT_activate() call * prevent tip from disappearing when mouseout goes to child of target element * tons of code cleanup dealing with onmouseout * 'status' now clears after each mouseout, even if tip is sticky or velcro * added 'width' option, which overides the global domTT_maxWidth (and the style) * merged logic in create() and show() so that create() can use show() (normalize) version 0.3.2 (2002/12/01): * changed 'close' to 'closeLink' since it was confusing what it was * added relative positioned tips (inline), added option 'position' * maxWidth of 0 will be ignored * fixed a fade bug when tooltip object exists (domTT_show()) * several other fade bugs fixed version 0.3.1 (2002/12/01): * 'caption' can be set to false to force it not to show, even when using 'type' sticky * fixed error "Could not get cursor property" in IE5 because must use 'hand' not 'pointer' * misspelled descendant * cleaned up the preserving of onmouseout a ton * 'caption' only has to be set to false if type is 'sticky', otherwise it can be left off * updated demos version 0.3.0 (2002/11/30): * added global domTT_lifetime to set how long the tip stays alive when mouse is stationary * added option 'lifetime' for each individual tip (0 for infinite) * added fixed position tooltip option by passing in 'x' and 'y' as options * changed hash method itemExists to hasItem to be DOM compliant * perserve the onmouseout that existed on the target rather than just overwriting * new type 'velcro', which disappears when you mouseout of the tooltip instead of target * added ability to fade out and changed 'fade' option from boolean to in/out/both/neither * added fade direction to the domTT_doFade() function to hande fade in both directions * made a global variable for domTT_onClose, either 'hide' or 'remove' * changed 'deactivate' option to 'onClose' which can be 'hide' or 'remove' * added 'grid' option and domTT_grid global to snap to a grid on updates (0 for no grid) * got rid of domTT_defaultStatus, just use window.defaultStatus for this value * code cleanups * demo addition and cleanups version 0.2.3 (2002/11/27): * added domTT_false() as a wrapper for links that make IFRAME tooltips to cancel click * fixed case when domTT_isGecko was not deteting select-multiple with size=1 * can specify only 'status' to domTT_activate, and will change status and register clear * made demo pages for library * removed a hack width setting width because I was confused before...and didn't need it * made global setting variable for domTT_prefix version 0.2.2 (2002/11/21): * fade-in on tips!!! (mozilla and IE only) * global option for fade on or off (click events don't use fade ever) * added option to domTT_activate for fade version 0.2.1 (2002/11/21): * perfect support for Opera7 !!! (what a great browser as far as standards go!) * no need for select collision detection in opera (again, tremendous) * prevented the close element from being draggable in all browsers (works this time) * fixed bug that opera does not hide IFRAME children when tip is hidden or destroyed * added domTT_defaultStatus to be used when clearing status bar * for opera, you will want to disable all opera tooltips except 'element titles' * added 'mousedown' as a trigger to set delay to 0 (3 types of mouse depress possible) version 0.2.0 (2002/11/20): * domTT_activate returns the id of the tip if it needs to be referenced externally * added domTT_isActive() to check for an activated tip, returns tip object if active * create domTT_true() function, which should be used to wrap domTT_activate for onmouseover * second option to domTT_deactivate is optional (default to true) * domTT_predefined now takes all the options domTT_activate takes * domTT_activate loads in predefined options if predefined is the first option * domTT_activate uses options from domTT_activate call to override predefined options * take off restriction for status of onmouseover and just let it happen as it will * caption now not used if empty, even if it is sticky (can externally close tip) * added 'contextmenu' event type alongside 'click' for auto changing delay to 0 * if content is empty, bail on creating the tip (hmmm...still thinking on this) * Gecko always makes the tip 4px too wide, for some unknown reason (maybe gecko bug?) * bug in right edge detection (was giving the width the padding instead of taking away) * fixed bug in global onmousemove (wasn't passing event to function for mozilla) * fixed edge detection, which was not accounting for scroll offset * made function domTT_correctEdgeBleed() for edge bleeding (since I used it twice) * code cleanups, added docs and another example page version 0.1.7 (2002/11/18): * domTT_close can be an object, hence an image for an 'X' for close * drag limited to the caption bar for sticky tips * added domTT_addPredefined function for caching tip definitions * added ability to pass in custom prefix for class styles, other than domTT * can pass in 'close' option for text/image to be used as close markup * fixed bug for onmouseover sticky tip which prevented cancel of tip creation onmouseout * added a new example.html file version 0.1.6 (2002/11/17): * added option for directionality in tips (southeast, southwest, northeast, northwest) * set default options at beginning of domTT_activate() instead of checking for each * global setting for mouse height so that offset is from edge of mouse cursor * added LICENSE, README to package * finished screen edge detection and correction * custom close text for sticky * can globally turn off dragging of sticky tips version 0.1.5 (2002/11/16): * ability to grab current mouse position when tip is created on delay * option for not using current mouse position when tip is created on delay (use passed in) * changed mouseover to mousemove for event on the tooltip (prevent artifact tooltips) * added delay as option (will use global if not passed in) * added status as an option, which will change the status bar text * eliminated collision detection delay when tip is already visible * 'sticky' option changed to 'type' and can be 'greasy' or 'sticky' * fixed some serious bugs in setTimout logic when destroying tips * created function domTT_show() for showing hidden tip (previously created) version 0.1.4 (2002/11/15): * ability to drag sticky tooltips (lots of work here) * change domTT_getPosition to domTT_getOffsets * return more information from domTT_getOffsets * simplify domTT_detectCollisions (now requires only one argument) * made function for getting mouse position (since browsers do crazy things) * the 'X' part of the tip is not draggable version 0.1.3 (2002/11/14): * konqueror support (lots of fixes for this) (onclick is somewhate hacked) * browser variables instead of using javascript objects to differentiate * eliminated duplicate mouse_x and mouse_y code * changed lamda function calls in setTimeout to support konqueror * getPosition returns right and bottom as well version 0.1.2 (2002/11/13): * fixed case when you flew over object and then clicked fast to create sticky and it failed * domTT_deactivate now takes an object instead of id (avoids lookup) * fixed problem with onmousemove after onclick beating setTimeout(...,0) on windows * fixed the e.target to e.currentTarget for mozilla (which is the registered target) * sticky tips now work correctly * fixed domTT_detectCollisions to be subject to the activate delay on tip unhide * no longer dependent on global Hash() function...arguments become hash internally * account for the scroll offset when working with event coordinates * compensated for lack of max-width for IE * fixed broken float right for IE (cannot assign through DOM) * float right causes tooltip to stretch to widht of page, fixed that * fixed javascript error because IE doesn't have e.target (event.srcElement instead) version 0.1.1 (2002/11/10): * pass in options as Hash * cache created tips to reuse via visibility style * auto-assign onmouseout to deactivate * add ability to have sticky * implemented zIndex so new tips can go over old tips * no delay for onclick tips * implemented delay when toggling visibility of cached tips * ability to pass in html content version 0.1.0 (2002/10/30): * Initial release vdr-plugin-live-3.1.3/javascript/HOWTO.html000066400000000000000000000271161414414333500205070ustar00rootroot00000000000000 DOM Tooltip Library :: HOWTO

DOM Tooltip Library HOWTO

Usage

This library offers a variety of ways to enable custom tooltips. The standard way of creating a tooltip is to add the domTT_activate() call to one of the event handlers of the target element. Tips can also be created programatically, which can be used for popping up inline windows. Another option is to call the method domTT_replaceTitles(), which replaces all elements containing the class 'tooltip' and the title attribute with a custom tooltip on mouseover.

Examples

<a href="index.html" onmouseover="domTT_activate(this, event, 'content', 'My first tooltip', 'trail', true);">sample link</a>

Options

The only required option to create a tooltip is the 'content' option. Most of the options below have a default value which is held in a global variable. If the option is specified, it overrides the default. The options are summarized below.

caption [text|xhtml|DOM Node]
An auto-generated caption will be created if this text is present. Set this to false when creating a sticky tip to prevent the automatic caption with close link.
content [text|xhtml|DOM Node|function] (required)
The main content of the tip, which may be XHTML text. Note that when using a function, it is only called when the tip is activated, so it is necessary to use 'closeMethod' of 'destroy' to redraw each time.
clearMouse [boolean]
This option flags whether the tip should attempt to avoid the mouse when the direction is south.
closeAction ['hide'|'destroy']
Determines if the tip should be destroyed (removed from DOM) or just hidden when deactivated. (If fading is used, hiding is forced)
closeLink [text|xhtml|DOM Node]
The text that should be used for the auto-generated close link used for sticky tips.
delay [ms]
Time in milliseconds before the tip appears.
direction ['southeast'|'southwest'|'northeast'|'northwest'|'north'|'south']
The position of the tip relative to the mouse.
draggable [boolean]
Determines of the sticky tooltip can be dragged.
fade ['in'|'out'|'neither'|'both']
Sets the alpha effect when the tip is created or destroyed.
fadeMax [0-100]
Sets the maximum alpha that should be used for the alpha effect. (Minimum is always 0, or invisible)
grid [px]
Snaps the trailing to a grid, so that the tip only moves after a set number of pixels.
id [string]
The XML id that should be assigned to the tip. Using this setting allows for external manipulation of the tip.
inframe [boolean]
Hints that the tooltip is inside an iframe, so that the tip can be manipulated from the parent frame.
lifetime [ms]
The duration of time before the tooltip is automatically terminated, as long as it is still activated.
offsetX [px]
The left-to-right offset from the event where the tip should be placed.
offsetY [px]
The top-to-bottom offset from the event where the tip should be placed.
parent [DOM Node]
The parent node on which the tip should be appended. Usually used for tips with a relative position.
position ['absolute'|'relative'|'fixed']
The style position of the tip. (In most cases, the position is 'absolute')
predefined [string]
A reference to a previously defined tooltip using domTT_addPredefined()
statusText [string]
Sets the text to be used in the statusbar when the tip is activated. If used with mouseover, it is necessary to wrap the domTT_activate() call in 'return domTT_true()'.
styleClass [string]
The class that will be assigned to the tip. The contents of the tip is assigned the class 'content' and the caption of the tip is assigned the class 'caption'.
type ['greasy'|'sticky'|'velcro']
Sets the fate of the tip. A greasy tip disappears when a mouse out occurs on owner. A sticky tip stays active until explicitly destroyed (a caption is also forced). A velcro tip disappears when a mouse out occurs on the tip itself.
trail [true|false|'x'|'y']
On which axis the tip should be updated when the mouse moves. A value of true updates on both axes.
lazy [boolean]
Whether or not to enable a delay when updating the mouse position of a trailing tip (drunk tooltip).
width [px]
A manual width for the tip.
maxWidth [px]
A manual maximum width for the tip. (In Firefox, this is controlled by the max-width style of the element)
x [px]
The absolute x position to be used for the tip location. This can be a calculated value, such as 'this.offsetLeft + 5'.
y [px]
The absolute y position to be used for the tip location. This can be a calculated value, such as 'this.offsetTop + 5'.

Function Reference

The DOM tooltip library offers a handful of functions, prefixed with 'domTT_', to aid in manipulating the tooltips. The following is a reference of these function calls.

domTT_activate()
The primary method for activating a tooltip, typically placed in an event handler such as onmouseover. The first two arguments must always be this and event, respectively. These two variable capture the environment on which to act. The rest of the arguments alternate between option name and option value from the list above.
domTT_addPredefined()
For times when tooltip creation should be seperated from activation, this method can be used to prepare the tooltips. The first argument is a unique name for the configuration, and the rest of the arguments alternate between option name and option value from the list of options above. A preconfigured tip can be reused any number of times, and the activate call can override options in the predefined configuration. A typical activate call for a predefined tooltip looks like: domTT_activate(this, event, 'predefined', 'mytip')
domTT_close()
A more flexible alternative to domTT_deactivate(), this function assists in immediately closing the tooltip. The argument can either be a child object of the tip, the tip id or the owner id. From within a tooltip, the call is typically: domTT_close(this)
domTT_deactivate()
In most cases, this method is automatically registered on the target element when domTT_activate() is first called. However, it may be necessary to programmatically close a tooltip. In this case, this method can be used. It requires one parameter, which can be the ID of the tooltip, the ID of the target or the target object itself.
domTT_mouseout()
When a tooltip is activate, it automatically registers a mouseout handler to close to tooltip unless one already exists. If you intend to have a custom mouseout handler on the target that activates the tip, you must make a call to domTT_mouseout(this, event) from within your mouseout handler. Otherwise, the tooltip will never be deactivated!
domTT_update()
If you find that you need to update the contents or caption of a tooltip after it has been created, you will find this function convenient. You must specify a tooltip id, target id or target node and new text or XHTML content and it will be used to replace the content of the existing tooltip. The third parameter allows you to specify either 'content' or 'caption', though it is optional and 'content' is the default.
makeTrue()/makeFalse()
These methods are indended soley for coaxing the return value of an event handler. Of times, it is necessary to return either true or false to the event handler, regardless of what the function in the handler might return. These methods will guarantee that result. For instance, the onclick handler must return false for a link or else the browser will follow the target href. Conversely, the handler must return true or else the browser will stall. These methods just help to achieve the desired behavior.

Global Settings

All of the options above can be used a global settings by prepending the 'domTT_' prefix. These assignments are a good way to establish common behaviors for your tooltips on the webpage. A couple of additional settings are documented here. Be sure to include your custom values below the point when the domTT library is included so that they override the default values.

domTT_oneOnly
Set to this true to allow only one active tip at a time. When a tooltip is created, it will deactivate the last opened tooltip. You can access the last opened tooltip using the global variable domTT_lastOpened
fading
In order to use the fading feature, you must include the library fadomatic.js in your web page. See examples #5.
dragging
In order to drag tooltips, you must include the library domTT_drag.js in your web page. See example #4.
domTT_useGlobalMousePosition
This setting will allow domTT to register an event handler on the document to track the position of the mouse. Registering this event allows the trailing feature to behave more smoothly.
domTT_screenEdgeDetection
Enable the screen edge detection feature, which will prevent the tip from leaving the page. Setting this to false disables this feature.
domTT_screenEdgePadding
When using screen edge detection, this setting specifies how close to the edge the tip should be allowed to travel.
domTT_detectCollisions
If you would rather not having the select boxes and other interfering elements hidden when a visual collision is detected, you may turn off the functionality globally using this flag.

Common Problems

  • If tips are not disappearing in low latency situations, use onmouseover = domTT_deactivate(this.id); to be sure it is discarded. (It seems, though, that a bug fix in 0.7.3 solved this problem.)
  • If the error 'operation aborted' is being thrown in IE, use the domTT_postponeActivation along with domTT_documentLoaded to prevent these types of page load problems.
  • To get dynamic updates on your tips using AJAX, you can either address the tip by its id, manipulate the node linked to the content, or use this domTT_update method. This point to remember is that the content is only processed when the tip is activated.
  • Disabled form fields do not have positions in the page. This prevents tooltips from being placed next to them. In order to use tooltips with form fields that are inactive, please use the onfocus="this.blur()" trick along with appropriate visual styling. In general, the disabled attribute causes other side-effects anyway.
vdr-plugin-live-3.1.3/javascript/LICENSE000066400000000000000000000261361414414333500177270ustar00rootroot00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. vdr-plugin-live-3.1.3/javascript/Makefile000066400000000000000000000020701414414333500203510ustar00rootroot00000000000000### The official name of this plugin. PLUGIN := live ### Additional options to silence TNTNET warnings TNTFLAGS ?= -Wno-overloaded-virtual -Wno-unused-function ### Includes and Defines (add further entries here): INCLUDES += -I$(VDRDIR)/include -I.. ### The object files (add further files here): OBJS := treeview.o SRCS := $(patsubst %.o,%.cpp,$(OBJS)) include ../global.mk ### The main target: all: libjavascript.a @true ### Implicit rules: %.o: %.cpp $(call PRETTY_PRINT,"CC javascript/" $@) $(Q)$(CXX) $(CXXFLAGS) $(TNTFLAGS) -c $(DEFINES) $(PLUGINFEATURES) $(INCLUDES) $< ifeq ($(shell test $(TNTVERSION) -ge 30000; echo $$?),0) %.cpp: %.ecpp else %.cpp: %.js endif $(call PRETTY_PRINT,"ECPP javascript/" $@) $(Q)$(ECPPC) $(ECPPFLAGS) $(ECPPFLAGS_JS) -b -m "text/javascript" $< ### Targets: libjavascript.a: $(OBJS) $(call PRETTY_PRINT,"AR javascript/" $@) $(Q)$(AR) r $@ $^ $(AR_NUL) clean: $(call PRETTY_PRINT,"CLN javascript/") @rm -f *~ *.o core* libjavascript.a $(SRCS) dist: clean @echo "Nothing to do for distribution here ..." .PRECIOUS: $(SRCS) vdr-plugin-live-3.1.3/javascript/README000066400000000000000000000155071414414333500176020ustar00rootroot00000000000000$Id: README,v 1.1 2007/01/04 22:29:18 thomas Exp $ Project: DOM Tooltip Maintainer: Dan Allen (Mojavelinux) Contributors: Josh Gross (JPortalHome) Jason Rust (CodeJanitor) License: Apache 2.0 What is it? ----------- This javascript library will allow you to have dynamic and configurable tooltips on your html pages. There are several other tooltip libraries on the web, but you will find that this library is very complete and stable. It includes support for all the modern browsers and behaves in a consistent way across these platforms. This library does not support Netscape 4. Netscape 4 is no longer an acceptable browser and it is time to move forward. That is why I prefixed the project title with DOM. If your browser doesn't support the DOM standard, then this library won't work. How does it work? ----------------- This library supports Gecko (Mozilla/Netscape6+,Firefox, etc), IE 5.5+, IE on Mac, Safari, Konqueror and Opera 7 (which includes full DOM and CSS2 support). The tooltips are configured through class definitions in your stylesheet and the rest is up to javascript. The tooltips may consist of two parts, the caption and the content. The caption is optional. The tips can either be greasy, sticky, or velcro. Greasy means that they move around when you move the mouse around and go away when you leave the element. Sticky means that they stick around after you leave the element and are otherwise stationary. Velcro tips disappear after a mouseout occurs on the tip itself. The tooltips also have directionality, so you can have tips that are 'northeast', 'northwest', 'southeast' or 'southwest' of the mouse. Be sure to include the file 'domLib.js' whereever you use 'domTT.js' and if you want to have draggable tips or opacity fading, include the 'domTT_drag.js' and 'fadomatic.js' files as well. **Some of these libraries are available under the domLib project.** Please see the HOWTO.html for details on how to use the library and the options that are available. Why is this program so big? --------------------------- Partly because it is feature rich and thus there is lots of code, partly because we try to use comments where ever necessary to clarify issues, and partly because our coding style is one which makes liberal use of whitespace. However, to save your users the trouble of downloading 50k of JavaScript every time they come to your page you can use Douglas Crockford's excellent jsmin program to strip all non-essentials from the code, reducing it's size by as much as 50%. This program has been verified to work with his program, located at: http://www.crockford.com/javascript/jsmin.html An example usage would be: bash# ./jsmin domTT.js domTT_min.js \ "domTT is Copyright Dan Allen (dan.allen@mojavelinux.com) (2002 - 2005). Licensed under the Apache 2.0 license" Anything I should know? ----------------------- Additionally, this tooltip library autodetects select boxes in IE and the scrollbar on multiple selects in mozilla (the only issue mozilla has) and HIDES them whenever the tooltip collides with them. Hence, it has full collision detection with components which cannot use the zIndex propery!! In order for it to work correctly you'll want to follow the example stylesheet pretty closely. It has been crafted to work well on all the supported browsers. Note the body { margin: 0; } style which is needed if you want it to work decently on Mac IE and Opera. (According the W3C standard, a body cannot have a margin anyway). By default, the library accounts for the size of the mouse when the tip is in one of the two southern positions. If you need to turn off this correction on a given tip, set the option 'clearMouse' to false. If the size of the mouse needs to be adjusted, overwrite the global variable domTT_mouseHeight. In 0.7.1, the hiding of flash animations was added for those browsers that cannot put html over top of flash. However, there are a couple of requirements. First, the syntax that must be used is: ...alternate (non-flash) html here!... If you use the object+embed syntax, the flash will not be properly detected. The second requirement is that the flash must be included in an element with a position of either absolute or relative. If it is not, the flash animation will be relocated in the DOM and therefore will not be aware of its location on the page, making collision detection worthless. Why did you write it? --------------------- The reason I wrote this library is because I wanted a library which used 100% DOM to do the tooltips, was easy to configure, was fast, and which was freely distributable. You may use this library in personal or company, open source or proprietry projects. I wrote it for you, so enjoy it. All I ask is that you spread the word and please give me credit by leaving in my comments. If you do make patches, please let me know about them on my forums. Viva Open Source!! Important Changes in 0.7.1 -------------------------- Fading library is now fadomatic.js, so be sure to change your include when using the fade feature. This is a third party library developed by fadomatic@chimpen.com. You can find this library at the following URL: http://chimpen.com/fadomatic The global setting domTT_classPrefix was changed to domTT_styleClass and the equivalent options without the domTT_ prefix in the domTT_activate() function call have been changed. Important Changes in 0.70 ----------------------------- IE 5.0 is no longer supported because its DOM implementation is just too crippled. Many of the complaints about the positioning of the tips and the screen edge detecting are now fixed. Important API Changes in 0.60 ------------------------------ The release is 0.60 and it introduced several significant changes over the 0.55 release. Several of the options have changed names. The following is a list: 'status' -> 'statusText' 'close' -> 'closeAction' 'prefix' -> 'classPrefix' 'id' -> *no longer an option* There is also a new option 'trail' which is used in place of mousemove. Do not use domTT_activate() in the onmousemove event handler any longer!!! It was far too slow to parse the same options all over again from onmouseover, so now just specify the domTT_activate() in onmouseover with the option trail. Also to note, the functions domTT_true() and domTT_false() have been changed to makeTrue() and makeFalse() respectively to make them more understandable. Be sure to include the file 'domLib.js' whereever you use 'domTT.js' and if you want to have draggable tips or opacity fading, include the 'domTT_drag.js' and 'fadomatic.js' files as well. vdr-plugin-live-3.1.3/javascript/TODO000066400000000000000000000252401414414333500174050ustar00rootroot00000000000000$Id: TODO,v 1.1 2007/01/04 22:29:18 thomas Exp $ TODO ---- [+] deactivate delay with appropriate timeout cancel handles (can then do the fancy work with velcro tip) [+] use json for options to tip [+] content can be a function, but until tip is destroyed, it isn't called again (call in domTT_show) [+] option to use the DOM node rather than cloning for caption/content [+] possible edge detection problem in Opera [+] build a namespace for dom.lib dom.tt dom.menu or something [+] doInDomTT() which would allow a function to be run when the tip is to be positioned [+] issue a callback to fadomatic to destroy tip on fadeOut completion (allow the destory action for fading tips) [+] make this domTT_runShow more clear as to what it is doing...maybe even rework the timeout process we cannot allow for the timeout to hold onto any object that might reference the DOM, else it won't get garbage collected [+] think about creating a lightweight version that uses replaceTitles [+] create addEvent() function for posting global events [+] lazy trailing broken in Opera [+] maxWidth handling is a bit ugly [+] maybe not show contents when dragging? (also resize of sticky tooltips) [+] drag API / add snap to grid when dragging THOUGHTS -------- [+] make alpha settings a sub-object or array...so we can set: max, step, etc... [+] a more smooth lazy trail should be implemented by not moving the full amount each time [+] make cursor "move" if caption is draggable [+] Konq and Opera don't allow tip to flow over IFRAME in example11...but the code is working NOTES ----- never use: onmouseover = function(e) { ... } as it will create a circular reference because of the function definition [1,2,3] array object {'one' : 1, 'two' : 2} object cannot use setTimeout() with anonymous function in IE 5.0 since it crashes the browser when you execute clearTimeout() following this call in compliance mode, IE measures use document.documentElement for many of the document.body functions Text node cannot have an event in IE, but it can in mozilla IE bubbles by default, which means that the child nodes get the event before the offsetParent nodes setting an event to null in mozilla does not remove events registered using addEventListener...but it does in IE. IE 5.0* does not have the .*? functionality in regexp bug in linux onmousedown causes onmouseout beforehand on IE, if there is an onmousemove and onmouseover, both will fire onmouseover // innerHTML the DOM way var range = document.createRange(); range.selectNodeContents(document.body); var fragment = range.createContextualFragment('test'); var tooltip = document.createElement('div'); tooltip.appendChild(fragment); WHAT I AM NOT THRILLED ABOUT ---------------------------- - (mozilla keeps firing events after javascript functions have been unloaded - when appending a tooltip to a table cell, the table seems to get stretched out DONE ---- [@] delay firing tooltip events until page has loaded (fix IE bug) [@] missing fade should still allow tip to close [@] one tooltip at a time [@] mousemove makes konqueror flip the tip up (was a konqueror bug, fixed in 3.2) [@] IE not detecting bottom edge of browser [@] created global constant for standards mode detection [@] if position relative, don't append to parent until tip is done creating or else parent get's all screwed up [@] show should not set left and top style if position relative [@] incorrect use of pending...pending should be between inactive and active [@] fade fails when mouseout while fading when fading in 'in' only [@] fade in is VERY slow [@] domTT_isActive awkward [@] velcro not working right [@] strange stuff going on when doing click to stick [@] mouseover then mouseout still has issues (sticky tips) [@] what if tooltip is inactive and domTT_activate() fires again [@] fix the FIXME's [@] change domTT_true and domTT_false to makeTrue() and makeFalse() [@] fix fades [@] drag not working [@] put delay back in for IE 5.0/Konq [@] mousemove update is overridden by the mouseover placement, firing at wrong time [@] cut the use of writing onmouseout() if is specified, same with onmousemove (make automatic) [@] get rid of the float and go with tables instead so we can cut code [@] try to speed it up a bit [@] create lite version for simpler cases [@] make a domTT_deactivate() that the user can use to make his/her own onmouseout() [@] check for standards compliance mode, make it work either way [@] problem if select box is inside tooltip [@] infinite recursion bug in konqueror when using onmouseover [@] be able to specify unique id as option [@] use callTimeout [@] problem with the setTimeout calls when using mousemove...before the tooltip activates, many setTimeouts can be registered, but only one can be cancelled [@] have domTT_deactivate take a tip id or a tip object [@] possibly make use of 'this' in the drag functions [@] fix onmouseout to work as expected [@] show off the preserving of the onmouseout [@] talk about the auto width in the demo (which overlib does not have) [@] select detection does not account for visible sticky tooltips...for this we have to have a global hash of the open tips...then, when we are going to change the style of a select element to visible, we check the open tips and make sure it is not conflicting with any of them [@] some demos not working in Konqueror [@] maxWidth handling is a bit awkward [@] velcro having a rough time in IE, and in mozilla linux, cannot mousedown before it closes [@] use typeof() instead of this.undefined [@] strip out the javascript code for reporting errors in the packaged file [@] store onmouseout function in the eventTarget attribute? [@] common function for placing the tip (show and create do this stuff) [@] be able to specify the width [@] make function for domTT_mouseout() [@] think about window.status on Example 4, right link [@] few places where I get the mouse_x/mouse_y when I don't need it (relative position) [@] when doing onmouseout for a greasy, make sure we are not entering a child...hmmm [@] make example for relative position [@] make maxWidth of 0 ignored [@] since we can specify parent, be able to specify relative position or something so that we can just have the tooltip expand into the container [@] have makefile fill in the version [@] if event target is documentElement, coax it to document.body [@] cursor errors in IE 5.5 [@] we don't need domTT_defaultStatus, just use window.defaultStatus [@] add option for lifetime to tip [@] create demo for velcro [@] snap to grid tooltip (problem is we need to determine the difference from where the tip is located now, and where we would put it next, not just the mouse position) [@] be able to fade out on close [@] mouseover tip functionality...be able to keep it open when you mouseover, maybe another type...velcro [@] don't kill the onmouseout the existed on tip...try to just add the close (see foo.html) [@] fixed position tooltip (maybe pass in coordinates) [@] change domTT_deactivate attribute to domTT_destroy [@] tip lifetime for onmouseover [@] set prefix for each tooltip in example pages [@] maybe creat a domTT_writeStatus() function to be used for onmouseover [@] added domTT_false() for onclick events on links that open IFRAMES [@] remove hardcoded -1000px for a setting [@] make fade a passed in option [@] fading tooltip [@] check for valid browser and don't execute if not meet requirements [@] opera7 does not hide contents of IFRAME when tooltip is hidden [@] make domTT_defaultStatus option [@] prevent dragging close element [@] drag bug in Opera 7 [@] full Opera 7 support! [@] try Opera 7 [@] function for detect screen edge? [@] edge detection not account for scroll (messes up placement bigtime when scroll is in effect) [@] if content is empty, need to bail on creating tip [@] why oncontextmenu no deliver onclick [@] add domTT_isActive() [@] add option 'deactivate' to be 'hide' or 'destroy', important for embedded IFRAMES [@] option not to have X for sticky, but put onclick on document [@] maybe remove the restriction for onmouseover and let it happen as it will [@] more options for predefined can come over to tip definition, overridden by those passed in [@] release to dynamic drive ddsubmit@yahoo.com [@] if you have onmouseover sticky, and you mouseover then leave, it doesn't kill tip [@] variable for tipObj visibility state to prevent all the lookups [@] be able to specify style prefix for different styles on different tips [@] add addPredefined() function [@] make only draggable from caption [@] be able to turn off dragging of sticky tips [@] cleaner call to domTT_show() [@] screenshot for freshmeat [@] custom close text [@] full screen edge detection [@] handle the mouse cursor height automatically [@] prettier way to handle default options [@] support for directional tips (extending west from mouse action instead of east) [@] eventDelay should be set to 0 if onmousemove and visible so it updates right away [@] make function for reappear, domTT_show() [@] when mouseover, use most recent location of mouse position [@] perhaps do type instead of 'sticky' true or false? [@] collision detection shouldn't delay when doing mousemove/click and tip is visible [@] adjustable delay for tip in options [@] change in_options to options in domTT_activate [@] drag in IE jumps when select domTTContent area [@] mouseout in IE when sticky active makes select boxes come back, event when tooptip is over it [@] X should not be a dragable part of tooltip [@] function for get mouse event position [@] domTT_detectCollisions should do the lines just above it [@] be able to drag sticky [@] have getPosition return all 4 coordinates [@] konqueror support [@] browser variables [@] code for x and y is twice in the activate function [@] when you fly in and then click on the object while the tip is being created, it bugs out [@] another paramater to domTT_activate() whether to unhide selects [@] domTT_deactivate to take object instead of id [@] fix problem with onmousemove after onclick beating setTimeout(...,0) on windows [@] after we use a sticky, and it is closed, mousemove no longer works [@] if we are sticky, all events on the same object should be cancelled [@] be able to do sticky on click when onmousemove is used for non-sticky [@] hiding of select boxes is not subject to the delay that the tooltip is subject to [@] do internal hash, from the arguments after event [@] itemExists to hash [@] account for scroll offset of page [@] fix max-width for IE [@] fix broken float right for IE [@] fix e.target to event.srcElement for IE [@] need delay on reappear from cache [@] no delay for onclick [@] implement zIndex so new tips can go over old tips [@] cache tooltips created by using visibility style [@] pass in options as Hash [@] auto-assign onmouseout [@] add ability to have sticky (x in caption...what if we don't have caption?) vdr-plugin-live-3.1.3/javascript/ajax.js000066400000000000000000000032521414414333500201750ustar00rootroot00000000000000function LiveAjaxCall(mode, url) { var xml = null; if (window.XMLHttpRequest) { xml = new XMLHttpRequest(); if (("xml" == mode) && xml.overrideMimeType) xml.overrideMimeType('text/xml'); } else if (window.ActiveXObject) { try { xml = new ActiveXObject("Msxml2.XMLHTTP"); } catch (e) { try { xml = new ActiveXObject("Microsoft.XMLHTTP"); } catch (e) {} } } this.url = url; this.xml = xml; this.onerror = function(message) {}; this.oncomplete = function() {}; this.request = function(param, value) { var url = this.url; if (param != "") { var url = this.url+'?'+param+"="+value; } var obj = this; this.xml.onreadystatechange = function() { obj.readystatechanged(); } this.xml.open('GET', url, true); this.xml.send(null); }; this.readystatechanged = function() { try { if (this.xml.readyState == 4) { if (this.xml.status == 200) { if ("xml" == mode) { var xmldoc = xml.responseXML; var result = Number(xmldoc.getElementsByTagName('response').item(0).firstChild.data); if (!result) { var error = xmldoc.getElementsByTagName('error').item(0).firstChild.data; this.onerror(error); } else { this.oncomplete(); } } else { this.oncomplete(); } } else { this.onerror('Invocation of webservice "'+this.url+'" failed with http status code '+this.xml.status); } } } catch (e) { this.onerror('Invocation of webservice "'+this.url+'" failed with exception: '+e.message); } }; } function LiveSimpleAjaxRequest(url, param, value) { var xml = new LiveAjaxCall("xml", url); xml.onerror = function(message) { alert(message); } xml.request(param, value); }; vdr-plugin-live-3.1.3/javascript/alphaAPI.js000066400000000000000000000146031414414333500206730ustar00rootroot00000000000000/** $Id: alphaAPI.js,v 1.1 2007/01/04 22:29:18 thomas Exp $ */ // {{{ license /* * Copyright 2002-2005 Dan Allen, Mojavelinux.com (dan.allen@mojavelinux.com) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ // }}} // {{{ intro /** * Title: alphaAPI * Original Author: chrisken * Original Url: http://www.cs.utexas.edu/users/chrisken/alphaapi.html * * Modified by Dan Allen * Note: When the stopAlpha is reached and it is equal to 0, the element's * style is set to display: none to fix a bug in domTT */ // }}} function alphaAPI(element, fadeInDelay, fadeOutDelay, startAlpha, stopAlpha, offsetTime, deltaAlpha) { // {{{ properties this.element = typeof(element) == 'object' ? element : document.getElementById(element); this.fadeInDelay = fadeInDelay || 40; this.fadeOutDelay = fadeOutDelay || this.fadeInDelay; this.startAlpha = startAlpha; this.stopAlpha = stopAlpha; // make sure a filter exists so an error is not thrown if (typeof(this.element.filters) == 'object') { if (typeof(this.element.filters.alpha) == 'undefined') { this.element.style.filter += 'alpha(opacity=100)'; } } this.offsetTime = (offsetTime || 0) * 1000; this.deltaAlpha = deltaAlpha || 10; this.timer = null; this.paused = false; this.started = false; this.cycle = false; this.command = function() {}; return this; // }}} } // use prototype methods to save memory // {{{ repeat() alphaAPI.prototype.repeat = function(repeat) { this.cycle = repeat ? true : false; } // }}} // {{{ setAlphaBy() alphaAPI.prototype.setAlphaBy = function(deltaAlpha) { this.setAlpha(this.getAlpha() + deltaAlpha); } // }}} // {{{ toggle() alphaAPI.prototype.toggle = function() { if (!this.started) { this.start(); } else if (this.paused) { this.unpause(); } else { this.pause(); } } // }}} // {{{ timeout() alphaAPI.prototype.timeout = function(command, delay) { this.command = command; this.timer = setTimeout(command, delay); } // }}} // {{{ setAlpha() alphaAPI.prototype.setAlpha = function(opacity) { if (typeof(this.element.filters) == 'object') { this.element.filters.alpha.opacity = opacity; } else if (this.element.style.setProperty) { this.element.style.setProperty('opacity', opacity / 100, ''); // handle the case of mozilla < 1.7 this.element.style.setProperty('-moz-opacity', opacity / 100, ''); // handle the case of old kthml this.element.style.setProperty('-khtml-opacity', opacity / 100, ''); } } // }}} // {{{ getAlpha() alphaAPI.prototype.getAlpha = function() { if (typeof(this.element.filters) == 'object') { return this.element.filters.alpha.opacity; } else if (this.element.style.getPropertyValue) { var opacityValue = this.element.style.getPropertyValue('opacity'); // handle the case of mozilla < 1.7 if (opacityValue == '') { opacityValue = this.element.style.getPropertyValue('-moz-opacity'); } // handle the case of old khtml if (opacityValue == '') { opacityValue = this.element.style.getPropertyValue('-khtml-opacity'); } return opacityValue * 100; } return 100; } // }}} // {{{ start() alphaAPI.prototype.start = function() { this.started = true; this.setAlpha(this.startAlpha); // determine direction if (this.startAlpha > this.stopAlpha) { var instance = this; this.timeout(function() { instance.fadeOut(); }, this.offsetTime); } else { var instance = this; this.timeout(function() { instance.fadeIn(); }, this.offsetTime); } } // }}} // {{{ stop() alphaAPI.prototype.stop = function() { this.started = false; this.setAlpha(this.stopAlpha); if (this.stopAlpha == 0) { this.element.style.display = 'none'; } this.stopTimer(); this.command = function() {}; } // }}} // {{{ reset() alphaAPI.prototype.reset = function() { this.started = false; this.setAlpha(this.startAlpha); this.stopTimer(); this.command = function() {}; } // }}} // {{{ pause() alphaAPI.prototype.pause = function() { this.paused = true; this.stopTimer(); } // }}} // {{{ unpause() alphaAPI.prototype.unpause = function() { this.paused = false; if (!this.started) { this.start(); } else { this.command(); } } // }}} // {{{ stopTimer() alphaAPI.prototype.stopTimer = function() { clearTimeout(this.timer); this.timer = null; } // }}} // {{{ fadeOut() alphaAPI.prototype.fadeOut = function() { this.stopTimer(); if (this.getAlpha() > this.stopAlpha) { this.setAlphaBy(-1 * this.deltaAlpha); var instance = this; this.timeout(function() { instance.fadeOut(); }, this.fadeOutDelay); } else { if (this.cycle) { var instance = this; this.timeout(function() { instance.fadeIn(); }, this.fadeInDelay); } else { if (this.stopAlpha == 0) { this.element.style.display = 'none'; } this.started = false; } } } // }}} // {{{ fadeIn() alphaAPI.prototype.fadeIn = function() { this.stopTimer(); if (this.getAlpha() < this.startAlpha) { this.setAlphaBy(this.deltaAlpha); var instance = this; this.timeout(function() { instance.fadeIn(); }, this.fadeInDelay); } else { if (this.cycle) { var instance = this; this.timeout(function() { instance.fadeOut(); }, this.fadeOutDelay); } else { this.started = false; } } } // }}} vdr-plugin-live-3.1.3/javascript/domLib.js000066400000000000000000000410551414414333500204630ustar00rootroot00000000000000/** $Id: domLib.js,v 1.1 2007/01/04 22:29:18 thomas Exp $ */ // {{{ license /* * Copyright 2002-2005 Dan Allen, Mojavelinux.com (dan.allen@mojavelinux.com) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ // }}} // {{{ intro /** * Title: DOM Library Core * Version: 0.70 * * Summary: * A set of commonly used functions that make it easier to create javascript * applications that rely on the DOM. * * Updated: 2005/05/17 * * Maintainer: Dan Allen * Maintainer: Jason Rust * * License: Apache 2.0 */ // }}} // {{{ global constants (DO NOT EDIT) // -- Browser Detection -- var domLib_userAgent = navigator.userAgent.toLowerCase(); var domLib_isMac = navigator.appVersion.indexOf('Mac') != -1; var domLib_isWin = domLib_userAgent.indexOf('windows') != -1; // NOTE: could use window.opera for detecting Opera var domLib_isOpera = domLib_userAgent.indexOf('opera') != -1; var domLib_isOpera7up = domLib_userAgent.match(/opera.(7|8)/i); var domLib_isSafari = domLib_userAgent.indexOf('safari') != -1; var domLib_isKonq = domLib_userAgent.indexOf('konqueror') != -1; // Both konqueror and safari use the khtml rendering engine var domLib_isKHTML = (domLib_isKonq || domLib_isSafari || domLib_userAgent.indexOf('khtml') != -1); var domLib_isIE = (!domLib_isKHTML && !domLib_isOpera && (domLib_userAgent.indexOf('msie 5') != -1 || domLib_userAgent.indexOf('msie 6') != -1 || domLib_userAgent.indexOf('msie 7') != -1)); var domLib_isIE5up = domLib_isIE; var domLib_isIE50 = (domLib_isIE && domLib_userAgent.indexOf('msie 5.0') != -1); var domLib_isIE55 = (domLib_isIE && domLib_userAgent.indexOf('msie 5.5') != -1); var domLib_isIE5 = (domLib_isIE50 || domLib_isIE55); // safari and konq may use string "khtml, like gecko", so check for destinctive / var domLib_isGecko = domLib_userAgent.indexOf('gecko/') != -1; var domLib_isMacIE = (domLib_isIE && domLib_isMac); var domLib_isIE55up = domLib_isIE5up && !domLib_isIE50 && !domLib_isMacIE; var domLib_isIE6up = domLib_isIE55up && !domLib_isIE55; // -- Browser Abilities -- var domLib_standardsMode = (document.compatMode && document.compatMode == 'CSS1Compat'); var domLib_useLibrary = (domLib_isOpera7up || domLib_isKHTML || domLib_isIE5up || domLib_isGecko || domLib_isMacIE || document.defaultView); // fixed in Konq3.2 var domLib_hasBrokenTimeout = (domLib_isMacIE || (domLib_isKonq && domLib_userAgent.match(/konqueror\/3.([2-9])/) == null)); var domLib_canFade = (domLib_isGecko || domLib_isIE || domLib_isSafari || domLib_isOpera); var domLib_canDrawOverSelect = (domLib_isMac || domLib_isOpera || domLib_isGecko); var domLib_canDrawOverFlash = (domLib_isMac || domLib_isWin); // -- Event Variables -- var domLib_eventTarget = domLib_isIE ? 'srcElement' : 'currentTarget'; var domLib_eventButton = domLib_isIE ? 'button' : 'which'; var domLib_eventTo = domLib_isIE ? 'toElement' : 'relatedTarget'; var domLib_stylePointer = domLib_isIE ? 'hand' : 'pointer'; // NOTE: a bug exists in Opera that prevents maxWidth from being set to 'none', so we make it huge var domLib_styleNoMaxWidth = domLib_isOpera ? '10000px' : 'none'; var domLib_hidePosition = '-1000px'; var domLib_scrollbarWidth = 14; var domLib_autoId = 1; var domLib_zIndex = 100; // -- Detection -- var domLib_collisionElements; var domLib_collisionsCached = false; var domLib_timeoutStateId = 0; var domLib_timeoutStates = new Hash(); // }}} // {{{ DOM enhancements if (!document.ELEMENT_NODE) { document.ELEMENT_NODE = 1; document.ATTRIBUTE_NODE = 2; document.TEXT_NODE = 3; document.DOCUMENT_NODE = 9; document.DOCUMENT_FRAGMENT_NODE = 11; } function domLib_clone(obj) { var copy = {}; for (var i in obj) { var value = obj[i]; try { if (value != null && typeof(value) == 'object' && value != window && !value.nodeType) { copy[i] = domLib_clone(value); } else { copy[i] = value; } } catch(e) { copy[i] = value; } } return copy; } // }}} // {{{ class Hash() function Hash() { this.length = 0; this.numericLength = 0; this.elementData = []; for (var i = 0; i < arguments.length; i += 2) { if (typeof(arguments[i + 1]) != 'undefined') { this.elementData[arguments[i]] = arguments[i + 1]; this.length++; if (arguments[i] == parseInt(arguments[i])) { this.numericLength++; } } } } // using prototype as opposed to inner functions saves on memory Hash.prototype.get = function(in_key) { if (typeof(this.elementData[in_key]) != 'undefined') { return this.elementData[in_key]; } return null; } Hash.prototype.set = function(in_key, in_value) { if (typeof(in_value) != 'undefined') { if (typeof(this.elementData[in_key]) == 'undefined') { this.length++; if (in_key == parseInt(in_key)) { this.numericLength++; } } return this.elementData[in_key] = in_value; } return false; } Hash.prototype.remove = function(in_key) { var tmp_value; if (typeof(this.elementData[in_key]) != 'undefined') { this.length--; if (in_key == parseInt(in_key)) { this.numericLength--; } tmp_value = this.elementData[in_key]; delete this.elementData[in_key]; } return tmp_value; } Hash.prototype.size = function() { return this.length; } Hash.prototype.has = function(in_key) { return typeof(this.elementData[in_key]) != 'undefined'; } Hash.prototype.find = function(in_obj) { for (var tmp_key in this.elementData) { if (this.elementData[tmp_key] == in_obj) { return tmp_key; } } return null; } Hash.prototype.merge = function(in_hash) { for (var tmp_key in in_hash.elementData) { if (typeof(this.elementData[tmp_key]) == 'undefined') { this.length++; if (tmp_key == parseInt(tmp_key)) { this.numericLength++; } } this.elementData[tmp_key] = in_hash.elementData[tmp_key]; } } Hash.prototype.compare = function(in_hash) { if (this.length != in_hash.length) { return false; } for (var tmp_key in this.elementData) { if (this.elementData[tmp_key] != in_hash.elementData[tmp_key]) { return false; } } return true; } // }}} // {{{ domLib_isDescendantOf() function domLib_isDescendantOf(in_object, in_ancestor, in_bannedTags) { if (in_object == null) { return false; } if (in_object == in_ancestor) { return true; } if (typeof(in_bannedTags) != 'undefined' && (',' + in_bannedTags.join(',') + ',').indexOf(',' + in_object.tagName + ',') != -1) { return false; } while (in_object != document.documentElement) { try { if ((tmp_object = in_object.offsetParent) && tmp_object == in_ancestor) { return true; } else if ((tmp_object = in_object.parentNode) == in_ancestor) { return true; } else { in_object = tmp_object; } } // in case we get some wierd error, assume we left the building catch(e) { return false; } } return false; } // }}} // {{{ domLib_detectCollisions() /** * For any given target element, determine if elements on the page * are colliding with it that do not obey the rules of z-index. */ function domLib_detectCollisions(in_object, in_recover, in_useCache) { // the reason for the cache is that if the root menu is built before // the page is done loading, then it might not find all the elements. // so really the only time you don't use cache is when building the // menu as part of the page load if (!domLib_collisionsCached) { var tags = []; if (!domLib_canDrawOverFlash) { tags[tags.length] = 'object'; } if (!domLib_canDrawOverSelect) { tags[tags.length] = 'select'; } domLib_collisionElements = domLib_getElementsByTagNames(tags, true); domLib_collisionsCached = in_useCache; } // if we don't have a tip, then unhide selects if (in_recover) { for (var cnt = 0; cnt < domLib_collisionElements.length; cnt++) { var thisElement = domLib_collisionElements[cnt]; if (!thisElement.hideList) { thisElement.hideList = new Hash(); } thisElement.hideList.remove(in_object.id); if (!thisElement.hideList.length) { domLib_collisionElements[cnt].style.visibility = 'visible'; if (domLib_isKonq) { domLib_collisionElements[cnt].style.display = ''; } } } return; } else if (domLib_collisionElements.length == 0) { return; } // okay, we have a tip, so hunt and destroy var objectOffsets = domLib_getOffsets(in_object); for (var cnt = 0; cnt < domLib_collisionElements.length; cnt++) { var thisElement = domLib_collisionElements[cnt]; // if collision element is in active element, move on // WARNING: is this too costly? if (domLib_isDescendantOf(thisElement, in_object)) { continue; } // konqueror only has trouble with multirow selects if (domLib_isKonq && thisElement.tagName == 'SELECT' && (thisElement.size <= 1 && !thisElement.multiple)) { continue; } if (!thisElement.hideList) { thisElement.hideList = new Hash(); } var selectOffsets = domLib_getOffsets(thisElement); var center2centerDistance = Math.sqrt(Math.pow(selectOffsets.get('leftCenter') - objectOffsets.get('leftCenter'), 2) + Math.pow(selectOffsets.get('topCenter') - objectOffsets.get('topCenter'), 2)); var radiusSum = selectOffsets.get('radius') + objectOffsets.get('radius'); // the encompassing circles are overlapping, get in for a closer look if (center2centerDistance < radiusSum) { // tip is left of select if ((objectOffsets.get('leftCenter') <= selectOffsets.get('leftCenter') && objectOffsets.get('right') < selectOffsets.get('left')) || // tip is right of select (objectOffsets.get('leftCenter') > selectOffsets.get('leftCenter') && objectOffsets.get('left') > selectOffsets.get('right')) || // tip is above select (objectOffsets.get('topCenter') <= selectOffsets.get('topCenter') && objectOffsets.get('bottom') < selectOffsets.get('top')) || // tip is below select (objectOffsets.get('topCenter') > selectOffsets.get('topCenter') && objectOffsets.get('top') > selectOffsets.get('bottom'))) { thisElement.hideList.remove(in_object.id); if (!thisElement.hideList.length) { thisElement.style.visibility = 'visible'; if (domLib_isKonq) { thisElement.style.display = ''; } } } else { thisElement.hideList.set(in_object.id, true); thisElement.style.visibility = 'hidden'; if (domLib_isKonq) { thisElement.style.display = 'none'; } } } } } // }}} // {{{ domLib_getOffsets() function domLib_getOffsets(in_object, in_preserveScroll) { if (typeof(in_preserveScroll) == 'undefined') { in_preserveScroll = false; } var originalObject = in_object; var originalWidth = in_object.offsetWidth; var originalHeight = in_object.offsetHeight; var offsetLeft = 0; var offsetTop = 0; while (in_object) { offsetLeft += in_object.offsetLeft; offsetTop += in_object.offsetTop; in_object = in_object.offsetParent; // consider scroll offset of parent elements if (in_object && !in_preserveScroll) { offsetLeft -= in_object.scrollLeft; offsetTop -= in_object.scrollTop; } } // MacIE misreports the offsets (even with margin: 0 in body{}), still not perfect if (domLib_isMacIE) { offsetLeft += 10; offsetTop += 10; } return new Hash( 'left', offsetLeft, 'top', offsetTop, 'right', offsetLeft + originalWidth, 'bottom', offsetTop + originalHeight, 'leftCenter', offsetLeft + originalWidth/2, 'topCenter', offsetTop + originalHeight/2, 'radius', Math.max(originalWidth, originalHeight) ); } // }}} // {{{ domLib_setTimeout() function domLib_setTimeout(in_function, in_timeout, in_args) { if (typeof(in_args) == 'undefined') { in_args = []; } if (in_timeout == -1) { // timeout event is disabled return 0; } else if (in_timeout == 0) { in_function(in_args); return 0; } // must make a copy of the arguments so that we release the reference var args = domLib_clone(in_args); if (!domLib_hasBrokenTimeout) { return setTimeout(function() { in_function(args); }, in_timeout); } else { var id = domLib_timeoutStateId++; var data = new Hash(); data.set('function', in_function); data.set('args', args); domLib_timeoutStates.set(id, data); data.set('timeoutId', setTimeout('domLib_timeoutStates.get(' + id + ').get(\'function\')(domLib_timeoutStates.get(' + id + ').get(\'args\')); domLib_timeoutStates.remove(' + id + ');', in_timeout)); return id; } } // }}} // {{{ domLib_clearTimeout() function domLib_clearTimeout(in_id) { if (!domLib_hasBrokenTimeout) { if (in_id > 0) { clearTimeout(in_id); } } else { if (domLib_timeoutStates.has(in_id)) { clearTimeout(domLib_timeoutStates.get(in_id).get('timeoutId')) domLib_timeoutStates.remove(in_id); } } } // }}} // {{{ domLib_getEventPosition() function domLib_getEventPosition(in_eventObj) { var eventPosition = new Hash('x', 0, 'y', 0, 'scrollX', 0, 'scrollY', 0); // IE varies depending on standard compliance mode if (domLib_isIE) { var doc = (domLib_standardsMode ? document.documentElement : document.body); // NOTE: events may fire before the body has been loaded if (doc) { eventPosition.set('x', in_eventObj.clientX + doc.scrollLeft); eventPosition.set('y', in_eventObj.clientY + doc.scrollTop); eventPosition.set('scrollX', doc.scrollLeft); eventPosition.set('scrollY', doc.scrollTop); } } else { eventPosition.set('x', in_eventObj.pageX); eventPosition.set('y', in_eventObj.pageY); eventPosition.set('scrollX', in_eventObj.pageX - in_eventObj.clientX); eventPosition.set('scrollY', in_eventObj.pageY - in_eventObj.clientY); } return eventPosition; } // }}} // {{{ domLib_cancelBubble() function domLib_cancelBubble(in_event) { var eventObj = in_event ? in_event : window.event; eventObj.cancelBubble = true; } // }}} // {{{ domLib_getIFrameReference() function domLib_getIFrameReference(in_frame) { if (domLib_isGecko || domLib_isIE) { return in_frame.frameElement; } else { // we could either do it this way or require an id on the frame // equivalent to the name var name = in_frame.name; if (!name || !in_frame.parent) { return null; } var candidates = in_frame.parent.document.getElementsByTagName('iframe'); for (var i = 0; i < candidates.length; i++) { if (candidates[i].name == name) { return candidates[i]; } } return null; } } // }}} // {{{ domLib_getElementsByClass() function domLib_getElementsByClass(in_class) { var elements = domLib_isIE5 ? document.all : document.getElementsByTagName('*'); var matches = []; var cnt = 0; for (var i = 0; i < elements.length; i++) { if ((" " + elements[i].className + " ").indexOf(" " + in_class + " ") != -1) { matches[cnt++] = elements[i]; } } return matches; } // }}} // {{{ domLib_getElementsByTagNames() function domLib_getElementsByTagNames(in_list, in_excludeHidden) { var elements = []; for (var i = 0; i < in_list.length; i++) { var matches = document.getElementsByTagName(in_list[i]); for (var j = 0; j < matches.length; j++) { // skip objects that have nested embeds, or else we get "flashing" if (matches[j].tagName == 'OBJECT' && domLib_isGecko) { var kids = matches[j].childNodes; var skip = false; for (var k = 0; k < kids.length; k++) { if (kids[k].tagName == 'EMBED') { skip = true; break; } } if (skip) continue; } if (in_excludeHidden && domLib_getComputedStyle(matches[j], 'visibility') == 'hidden') { continue; } elements[elements.length] = matches[j]; } } return elements; } // }}} // {{{ domLib_getComputedStyle() function domLib_getComputedStyle(in_obj, in_property) { if (domLib_isIE) { var humpBackProp = in_property.replace(/-(.)/, function (a, b) { return b.toUpperCase(); }); return eval('in_obj.currentStyle.' + humpBackProp); } // getComputedStyle() is broken in konqueror, so let's go for the style object else if (domLib_isKonq) { //var humpBackProp = in_property.replace(/-(.)/, function (a, b) { return b.toUpperCase(); }); return eval('in_obj.style.' + in_property); } else { return document.defaultView.getComputedStyle(in_obj, null).getPropertyValue(in_property); } } // }}} // {{{ makeTrue() function makeTrue() { return true; } // }}} // {{{ makeFalse() function makeFalse() { return false; } // }}} vdr-plugin-live-3.1.3/javascript/domTT.js000066400000000000000000000730341414414333500203060ustar00rootroot00000000000000/** $Id: domTT.js,v 1.2 2007/01/21 21:23:02 tadi Exp $ */ // {{{ license /* * Copyright 2002-2005 Dan Allen, Mojavelinux.com (dan.allen@mojavelinux.com) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ // }}} // {{{ intro /** * Title: DOM Tooltip Library * Version: 0.7.3 * * Summary: * Allows developers to add custom tooltips to the webpages. Tooltips are * generated using the domTT_activate() function and customized by setting * a handful of options. * * Maintainer: Dan Allen * Contributors: * Josh Gross * Jason Rust * * License: Apache 2.0 * However, if you use this library, you earn the position of official bug * reporter :) Please post questions or problem reports to the newsgroup: * * http://groups-beta.google.com/group/dom-tooltip * * If you are doing this for commercial work, perhaps you could send me a few * Starbucks Coffee gift dollars or PayPal bucks to encourage future * developement (NOT REQUIRED). E-mail me for my snail mail address. * * Homepage: http://www.mojavelinux.com/projects/domtooltip/ * * Newsgroup: http://groups-beta.google.com/group/dom-tooltip * * Freshmeat Project: http://freshmeat.net/projects/domtt/?topic_id=92 * * Updated: 2005/07/16 * * Supported Browsers: * Mozilla (Gecko), IE 5.5+, IE on Mac, Safari, Konqueror, Opera 7 * * Usage: * Please see the HOWTO documentation. **/ // }}} // {{{ settings (editable) // IE mouse events seem to be off by 2 pixels var domTT_offsetX = (domLib_isIE ? -2 : 0); var domTT_offsetY = (domLib_isIE ? 4 : 2); var domTT_direction = 'southeast'; var domTT_mouseHeight = domLib_isIE ? 13 : 19; var domTT_closeLink = 'X'; var domTT_closeAction = 'hide'; var domTT_activateDelay = 500; var domTT_maxWidth = false; var domTT_styleClass = 'domTT'; var domTT_fade = 'neither'; var domTT_lifetime = 0; var domTT_grid = 0; var domTT_trailDelay = 200; var domTT_useGlobalMousePosition = true; var domTT_postponeActivation = false; var domTT_tooltipIdPrefix = '[domTT]'; var domTT_screenEdgeDetection = true; var domTT_screenEdgePadding = 4; var domTT_oneOnly = false; var domTT_cloneNodes = false; var domTT_detectCollisions = true; var domTT_bannedTags = ['OPTION']; var domTT_draggable = false; if (typeof(domTT_dragEnabled) == 'undefined') { domTT_dragEnabled = false; } // }}} // {{{ globals (DO NOT EDIT) var domTT_predefined = new Hash(); // tooltips are keyed on both the tip id and the owner id, // since events can originate on either object var domTT_tooltips = new Hash(); var domTT_lastOpened = 0; var domTT_documentLoaded = false; var domTT_mousePosition = null; // }}} // {{{ document.onmousemove if (domLib_useLibrary && domTT_useGlobalMousePosition) { document.onmousemove = function(in_event) { if (typeof(in_event) == 'undefined') { in_event = window.event; } domTT_mousePosition = domLib_getEventPosition(in_event); if (domTT_dragEnabled && domTT_dragMouseDown) { domTT_dragUpdate(in_event); } } } // }}} // {{{ domTT_activate() function domTT_activate(in_this, in_event) { if (!domLib_useLibrary || (domTT_postponeActivation && !domTT_documentLoaded)) { return false; } // make sure in_event is set (for IE, some cases we have to use window.event) if (typeof(in_event) == 'undefined') { in_event = window.event; } // don't allow tooltips on banned tags (such as OPTION) if (in_event != null) { var target = in_event.srcElement ? in_event.srcElement : in_event.target; if (target != null && (',' + domTT_bannedTags.join(',') + ',').indexOf(',' + target.tagName + ',') != -1) { return false; } } var owner = document.body; // we have an active event so get the owner if (in_event != null && in_event.type.match(/key|mouse|click|contextmenu/i)) { // make sure we have nothing higher than the body element if (in_this.nodeType && in_this.nodeType != document.DOCUMENT_NODE) { owner = in_this; } } // non active event (make sure we were passed a string id) else { if (typeof(in_this) != 'object' && !(owner = domTT_tooltips.get(in_this))) { // NOTE: two steps to avoid "flashing" in gecko var embryo = document.createElement('div'); owner = document.body.appendChild(embryo); owner.style.display = 'none'; owner.id = in_this; } } // make sure the owner has a unique id if (!owner.id) { owner.id = '__autoId' + domLib_autoId++; } // see if we should only be opening one tip at a time // NOTE: this is not "perfect" yet since it really steps on any other // tip working on fade out or delayed close, but it get's the job done if (domTT_oneOnly && domTT_lastOpened) { domTT_deactivate(domTT_lastOpened); } domTT_lastOpened = owner.id; var tooltip = domTT_tooltips.get(owner.id); if (tooltip) { if (tooltip.get('eventType') != in_event.type) { if (tooltip.get('type') == 'greasy') { tooltip.set('closeAction', 'destroy'); domTT_deactivate(owner.id); } else if (tooltip.get('status') != 'inactive') { return owner.id; } } else { if (tooltip.get('status') == 'inactive') { tooltip.set('status', 'pending'); tooltip.set('activateTimeout', domLib_setTimeout(domTT_runShow, tooltip.get('delay'), [owner.id, in_event])); return owner.id; } // either pending or active, let it be else { return owner.id; } } } // setup the default options hash var options = new Hash( 'caption', '', 'content', '', 'clearMouse', true, 'closeAction', domTT_closeAction, 'closeLink', domTT_closeLink, 'delay', domTT_activateDelay, 'direction', domTT_direction, 'draggable', domTT_draggable, 'fade', domTT_fade, 'fadeMax', 100, 'grid', domTT_grid, 'id', domTT_tooltipIdPrefix + owner.id, 'inframe', false, 'lifetime', domTT_lifetime, 'offsetX', domTT_offsetX, 'offsetY', domTT_offsetY, 'parent', document.body, 'position', 'absolute', 'styleClass', domTT_styleClass, 'type', 'greasy', 'trail', false, 'lazy', false ); // load in the options from the function call for (var i = 2; i < arguments.length; i += 2) { // load in predefined if (arguments[i] == 'predefined') { var predefinedOptions = domTT_predefined.get(arguments[i + 1]); for (var j in predefinedOptions.elementData) { options.set(j, predefinedOptions.get(j)); } } // set option else { options.set(arguments[i], arguments[i + 1]); } } options.set('eventType', in_event != null ? in_event.type : null); // immediately set the status text if provided if (options.has('statusText')) { try { window.status = options.get('statusText'); } catch(e) {} } // if we didn't give content...assume we just wanted to change the status and return if (!options.has('content') || options.get('content') == '' || options.get('content') == null) { if (typeof(owner.onmouseout) != 'function') { owner.onmouseout = function(in_event) { domTT_mouseout(this, in_event); }; } return owner.id; } options.set('owner', owner); domTT_create(options); // determine the show delay options.set('delay', (in_event != null && in_event.type.match(/click|mousedown|contextmenu/i)) ? 0 : parseInt(options.get('delay'))); domTT_tooltips.set(owner.id, options); domTT_tooltips.set(options.get('id'), options); options.set('status', 'pending'); options.set('activateTimeout', domLib_setTimeout(domTT_runShow, options.get('delay'), [owner.id, in_event])); return owner.id; } // }}} // {{{ domTT_create() function domTT_create(in_options) { var tipOwner = in_options.get('owner'); var parentObj = in_options.get('parent'); var parentDoc = parentObj.ownerDocument || parentObj.document; // create the tooltip and hide it // NOTE: two steps to avoid "flashing" in gecko var embryo = parentDoc.createElement('div'); var tipObj = parentObj.appendChild(embryo); tipObj.style.position = 'absolute'; tipObj.style.left = '0px'; tipObj.style.top = '0px'; tipObj.style.visibility = 'hidden'; tipObj.id = in_options.get('id'); tipObj.className = in_options.get('styleClass'); var contentBlock; var tableLayout = false; if (in_options.get('caption') || (in_options.get('type') == 'sticky' && in_options.get('caption') !== false)) { tableLayout = true; // layout the tip with a hidden formatting table var tipLayoutTable = tipObj.appendChild(parentDoc.createElement('table')); tipLayoutTable.style.borderCollapse = 'collapse'; if (domLib_isKHTML) { tipLayoutTable.cellSpacing = 0; } var tipLayoutTbody = tipLayoutTable.appendChild(parentDoc.createElement('tbody')); var numCaptionCells = 0; var captionRow = tipLayoutTbody.appendChild(parentDoc.createElement('tr')); var captionCell = captionRow.appendChild(parentDoc.createElement('td')); captionCell.style.padding = '0px'; var caption = captionCell.appendChild(parentDoc.createElement('div')); caption.className = 'caption'; if (domLib_isIE50) { caption.style.height = '100%'; } if (in_options.get('caption').nodeType) { caption.appendChild(domTT_cloneNodes ? in_options.get('caption').cloneNode(1) : in_options.get('caption')); } else { caption.innerHTML = in_options.get('caption'); } if (in_options.get('type') == 'sticky') { var numCaptionCells = 2; var closeLinkCell = captionRow.appendChild(parentDoc.createElement('td')); closeLinkCell.style.padding = '0px'; var closeLink = closeLinkCell.appendChild(parentDoc.createElement('div')); closeLink.className = 'caption'; if (domLib_isIE50) { closeLink.style.height = '100%'; } closeLink.style.textAlign = 'right'; closeLink.style.cursor = domLib_stylePointer; // merge the styles of the two cells closeLink.style.borderLeftWidth = caption.style.borderRightWidth = '0px'; closeLink.style.paddingLeft = caption.style.paddingRight = '0px'; closeLink.style.marginLeft = caption.style.marginRight = '0px'; if (in_options.get('closeLink').nodeType) { closeLink.appendChild(in_options.get('closeLink').cloneNode(1)); } else { closeLink.innerHTML = in_options.get('closeLink'); } closeLink.onclick = function() { domTT_deactivate(tipOwner.id); }; closeLink.onmousedown = function(in_event) { if (typeof(in_event) == 'undefined') { in_event = window.event; } in_event.cancelBubble = true; }; // MacIE has to have a newline at the end and must be made with createTextNode() if (domLib_isMacIE) { closeLinkCell.appendChild(parentDoc.createTextNode("\n")); } } // MacIE has to have a newline at the end and must be made with createTextNode() if (domLib_isMacIE) { captionCell.appendChild(parentDoc.createTextNode("\n")); } var contentRow = tipLayoutTbody.appendChild(parentDoc.createElement('tr')); var contentCell = contentRow.appendChild(parentDoc.createElement('td')); contentCell.style.padding = '0px'; if (numCaptionCells) { if (domLib_isIE || domLib_isOpera) { contentCell.colSpan = numCaptionCells; } else { contentCell.setAttribute('colspan', numCaptionCells); } } contentBlock = contentCell.appendChild(parentDoc.createElement('div')); if (domLib_isIE50) { contentBlock.style.height = '100%'; } } else { contentBlock = tipObj.appendChild(parentDoc.createElement('div')); } contentBlock.className = 'contents'; var content = in_options.get('content'); // allow content has a function to return the actual content if (typeof(content) == 'function') { content = content(in_options.get('id')); } if (content != null && content.nodeType) { contentBlock.appendChild(domTT_cloneNodes ? content.cloneNode(1) : content); } else { contentBlock.innerHTML = content; } // adjust the width if specified if (in_options.has('width')) { tipObj.style.width = parseInt(in_options.get('width')) + 'px'; } // check if we are overridding the maxWidth // if the browser supports maxWidth, the global setting will be ignored (assume stylesheet) var maxWidth = domTT_maxWidth; if (in_options.has('maxWidth')) { if ((maxWidth = in_options.get('maxWidth')) === false) { tipObj.style.maxWidth = domLib_styleNoMaxWidth; } else { maxWidth = parseInt(in_options.get('maxWidth')); tipObj.style.maxWidth = maxWidth + 'px'; } } // HACK: fix lack of maxWidth in CSS for KHTML and IE if (maxWidth !== false && (domLib_isIE || domLib_isKHTML) && tipObj.offsetWidth > maxWidth) { tipObj.style.width = maxWidth + 'px'; } in_options.set('offsetWidth', tipObj.offsetWidth); in_options.set('offsetHeight', tipObj.offsetHeight); // konqueror miscalcuates the width of the containing div when using the layout table based on the // border size of the containing div if (domLib_isKonq && tableLayout && !tipObj.style.width) { var left = document.defaultView.getComputedStyle(tipObj, '').getPropertyValue('border-left-width'); var right = document.defaultView.getComputedStyle(tipObj, '').getPropertyValue('border-right-width'); left = left.substring(left.indexOf(':') + 2, left.indexOf(';')); right = right.substring(right.indexOf(':') + 2, right.indexOf(';')); var correction = 2 * ((left ? parseInt(left) : 0) + (right ? parseInt(right) : 0)); tipObj.style.width = (tipObj.offsetWidth - correction) + 'px'; } // if a width is not set on an absolutely positioned object, both IE and Opera // will attempt to wrap when it spills outside of body...we cannot have that if (domLib_isIE || domLib_isOpera) { if (!tipObj.style.width) { // HACK: the correction here is for a border tipObj.style.width = (tipObj.offsetWidth - 2) + 'px'; } // HACK: the correction here is for a border tipObj.style.height = (tipObj.offsetHeight - 2) + 'px'; } // store placement offsets from event position var offsetX, offsetY; // tooltip floats if (in_options.get('position') == 'absolute' && !(in_options.has('x') && in_options.has('y'))) { // determine the offset relative to the pointer switch (in_options.get('direction')) { case 'northeast': offsetX = in_options.get('offsetX'); offsetY = 0 - tipObj.offsetHeight - in_options.get('offsetY'); break; case 'northwest': offsetX = 0 - tipObj.offsetWidth - in_options.get('offsetX'); offsetY = 0 - tipObj.offsetHeight - in_options.get('offsetY'); break; case 'north': offsetX = 0 - parseInt(tipObj.offsetWidth/2); offsetY = 0 - tipObj.offsetHeight - in_options.get('offsetY'); break; case 'southwest': offsetX = 0 - tipObj.offsetWidth - in_options.get('offsetX'); offsetY = in_options.get('offsetY'); break; case 'southeast': offsetX = in_options.get('offsetX'); offsetY = in_options.get('offsetY'); break; case 'south': offsetX = 0 - parseInt(tipObj.offsetWidth/2); offsetY = in_options.get('offsetY'); break; } // if we are in an iframe, get the offsets of the iframe in the parent document if (in_options.get('inframe')) { var iframeObj = domLib_getIFrameReference(window); if (iframeObj) { var frameOffsets = domLib_getOffsets(iframeObj); offsetX += frameOffsets.get('left'); offsetY += frameOffsets.get('top'); } } } // tooltip is fixed else { offsetX = 0; offsetY = 0; in_options.set('trail', false); } // set the direction-specific offsetX/Y in_options.set('offsetX', offsetX); in_options.set('offsetY', offsetY); if (in_options.get('clearMouse') && in_options.get('direction').indexOf('south') != -1) { in_options.set('mouseOffset', domTT_mouseHeight); } else { in_options.set('mouseOffset', 0); } if (domLib_canFade && typeof(Fadomatic) == 'function') { if (in_options.get('fade') != 'neither') { var fadeHandler = new Fadomatic(tipObj, 10, 0, 0, in_options.get('fadeMax')); in_options.set('fadeHandler', fadeHandler); } } else { in_options.set('fade', 'neither'); } // setup mouse events if (in_options.get('trail') && typeof(tipOwner.onmousemove) != 'function') { tipOwner.onmousemove = function(in_event) { domTT_mousemove(this, in_event); }; } if (typeof(tipOwner.onmouseout) != 'function') { tipOwner.onmouseout = function(in_event) { domTT_mouseout(this, in_event); }; } if (in_options.get('type') == 'sticky') { if (in_options.get('position') == 'absolute' && domTT_dragEnabled && in_options.get('draggable')) { // inserted by tadi: begin if (typeof(captionRow) == 'undefined') { captionRow = tipObj; } // inserted by tadi: end if (domLib_isIE) { captionRow.onselectstart = function() { return false; }; } // setup drag captionRow.onmousedown = function(in_event) { domTT_dragStart(tipObj, in_event); }; captionRow.onmousemove = function(in_event) { domTT_dragUpdate(in_event); }; captionRow.onmouseup = function() { domTT_dragStop(); }; } } else if (in_options.get('type') == 'velcro') { /* can use once we have deactivateDelay tipObj.onmouseover = function(in_event) { if (typeof(in_event) == 'undefined') { in_event = window.event; } var tooltip = domTT_tooltips.get(tipObj.id); if (in_options.get('lifetime')) { domLib_clearTimeout(in_options.get('lifetimeTimeout'); } }; */ tipObj.onmouseout = function(in_event) { if (typeof(in_event) == 'undefined') { in_event = window.event; } if (!domLib_isDescendantOf(in_event[domLib_eventTo], tipObj, domTT_bannedTags)) { domTT_deactivate(tipOwner.id); } }; // NOTE: this might interfere with links in the tip tipObj.onclick = function(in_event) { domTT_deactivate(tipOwner.id); }; } if (in_options.get('position') == 'relative') { tipObj.style.position = 'relative'; } in_options.set('node', tipObj); in_options.set('status', 'inactive'); } // }}} // {{{ domTT_show() // in_id is either tip id or the owner id function domTT_show(in_id, in_event) { // should always find one since this call would be cancelled if tip was killed var tooltip = domTT_tooltips.get(in_id); var status = tooltip.get('status'); var tipObj = tooltip.get('node'); if (tooltip.get('position') == 'absolute') { var mouseX, mouseY; if (tooltip.has('x') && tooltip.has('y')) { mouseX = tooltip.get('x'); mouseY = tooltip.get('y'); } else if (!domTT_useGlobalMousePosition || domTT_mousePosition == null || status == 'active' || tooltip.get('delay') == 0) { var eventPosition = domLib_getEventPosition(in_event); var eventX = eventPosition.get('x'); var eventY = eventPosition.get('y'); if (tooltip.get('inframe')) { eventX -= eventPosition.get('scrollX'); eventY -= eventPosition.get('scrollY'); } // only move tip along requested trail axis when updating position if (status == 'active' && tooltip.get('trail') !== true) { var trail = tooltip.get('trail'); if (trail == 'x') { mouseX = eventX; mouseY = tooltip.get('mouseY'); } else if (trail == 'y') { mouseX = tooltip.get('mouseX'); mouseY = eventY; } } else { mouseX = eventX; mouseY = eventY; } } else { mouseX = domTT_mousePosition.get('x'); mouseY = domTT_mousePosition.get('y'); if (tooltip.get('inframe')) { mouseX -= domTT_mousePosition.get('scrollX'); mouseY -= domTT_mousePosition.get('scrollY'); } } // we are using a grid for updates if (tooltip.get('grid')) { // if this is not a mousemove event or it is a mousemove event on an active tip and // the movement is bigger than the grid if (in_event.type != 'mousemove' || (status == 'active' && (Math.abs(tooltip.get('lastX') - mouseX) > tooltip.get('grid') || Math.abs(tooltip.get('lastY') - mouseY) > tooltip.get('grid')))) { tooltip.set('lastX', mouseX); tooltip.set('lastY', mouseY); } // did not satisfy the grid movement requirement else { return false; } } // mouseX and mouseY store the last acknowleged mouse position, // good for trailing on one axis tooltip.set('mouseX', mouseX); tooltip.set('mouseY', mouseY); var coordinates; if (domTT_screenEdgeDetection) { coordinates = domTT_correctEdgeBleed( tooltip.get('offsetWidth'), tooltip.get('offsetHeight'), mouseX, mouseY, tooltip.get('offsetX'), tooltip.get('offsetY'), tooltip.get('mouseOffset'), tooltip.get('inframe') ? window.parent : window ); } else { coordinates = { 'x' : mouseX + tooltip.get('offsetX'), 'y' : mouseY + tooltip.get('offsetY') + tooltip.get('mouseOffset') }; } // update the position tipObj.style.left = coordinates.x + 'px'; tipObj.style.top = coordinates.y + 'px'; // increase the tip zIndex so it goes over previously shown tips tipObj.style.zIndex = domLib_zIndex++; } // if tip is not active, active it now and check for a fade in if (status == 'pending') { // unhide the tooltip tooltip.set('status', 'active'); tipObj.style.display = ''; tipObj.style.visibility = 'visible'; var fade = tooltip.get('fade'); if (fade != 'neither') { var fadeHandler = tooltip.get('fadeHandler'); if (fade == 'out' || fade == 'both') { fadeHandler.haltFade(); if (fade == 'out') { fadeHandler.halt(); } } if (fade == 'in' || fade == 'both') { fadeHandler.fadeIn(); } } if (tooltip.get('type') == 'greasy' && tooltip.get('lifetime') != 0) { tooltip.set('lifetimeTimeout', domLib_setTimeout(domTT_runDeactivate, tooltip.get('lifetime'), [tipObj.id])); } } if (tooltip.get('position') == 'absolute' && domTT_detectCollisions) { // utilize original collision element cache domLib_detectCollisions(tipObj, false, true); } } // }}} // {{{ domTT_close() // in_handle can either be an child object of the tip, the tip id or the owner id function domTT_close(in_handle) { var id; if (typeof(in_handle) == 'object' && in_handle.nodeType) { var obj = in_handle; while (!obj.id || !domTT_tooltips.get(obj.id)) { obj = obj.parentNode; if (obj.nodeType != document.ELEMENT_NODE) { return; } } id = obj.id; } else { id = in_handle; } domTT_deactivate(id); } // }}} // {{{ domTT_closeAll() // run through the tooltips and close them all function domTT_closeAll() { // NOTE: this will iterate 2x # of tooltips for (var id in domTT_tooltips.elementData) { domTT_close(id); } } // }}} // {{{ domTT_deactivate() // in_id is either the tip id or the owner id function domTT_deactivate(in_id) { var tooltip = domTT_tooltips.get(in_id); if (tooltip) { var status = tooltip.get('status'); if (status == 'pending') { // cancel the creation of this tip if it is still pending domLib_clearTimeout(tooltip.get('activateTimeout')); tooltip.set('status', 'inactive'); } else if (status == 'active') { if (tooltip.get('lifetime')) { domLib_clearTimeout(tooltip.get('lifetimeTimeout')); } var tipObj = tooltip.get('node'); if (tooltip.get('closeAction') == 'hide') { var fade = tooltip.get('fade'); if (fade != 'neither') { var fadeHandler = tooltip.get('fadeHandler'); if (fade == 'out' || fade == 'both') { fadeHandler.fadeOut(); } else { fadeHandler.hide(); } } else { tipObj.style.display = 'none'; } } else { tooltip.get('parent').removeChild(tipObj); domTT_tooltips.remove(tooltip.get('owner').id); domTT_tooltips.remove(tooltip.get('id')); } tooltip.set('status', 'inactive'); if (domTT_detectCollisions) { // unhide all of the selects that are owned by this object // utilize original collision element cache domLib_detectCollisions(tipObj, true, true); } } } } // }}} // {{{ domTT_mouseout() function domTT_mouseout(in_owner, in_event) { if (!domLib_useLibrary) { return false; } if (typeof(in_event) == 'undefined') { in_event = window.event; } var toChild = domLib_isDescendantOf(in_event[domLib_eventTo], in_owner, domTT_bannedTags); var tooltip = domTT_tooltips.get(in_owner.id); if (tooltip && (tooltip.get('type') == 'greasy' || tooltip.get('status') != 'active')) { // deactivate tip if exists and we moved away from the owner if (!toChild) { domTT_deactivate(in_owner.id); try { window.status = window.defaultStatus; } catch(e) {} } } else if (!toChild) { try { window.status = window.defaultStatus; } catch(e) {} } } // }}} // {{{ domTT_mousemove() function domTT_mousemove(in_owner, in_event) { if (!domLib_useLibrary) { return false; } if (typeof(in_event) == 'undefined') { in_event = window.event; } var tooltip = domTT_tooltips.get(in_owner.id); if (tooltip && tooltip.get('trail') && tooltip.get('status') == 'active') { // see if we are trailing lazy if (tooltip.get('lazy')) { domLib_setTimeout(domTT_runShow, domTT_trailDelay, [in_owner.id, in_event]); } else { domTT_show(in_owner.id, in_event); } } } // }}} // {{{ domTT_addPredefined() function domTT_addPredefined(in_id) { var options = new Hash(); for (var i = 1; i < arguments.length; i += 2) { options.set(arguments[i], arguments[i + 1]); } domTT_predefined.set(in_id, options); } // }}} // {{{ domTT_correctEdgeBleed() function domTT_correctEdgeBleed(in_width, in_height, in_x, in_y, in_offsetX, in_offsetY, in_mouseOffset, in_window) { var win, doc; var bleedRight, bleedBottom; var pageHeight, pageWidth, pageYOffset, pageXOffset; var x = in_x + in_offsetX; var y = in_y + in_offsetY + in_mouseOffset; win = (typeof(in_window) == 'undefined' ? window : in_window); // Gecko and IE swaps values of clientHeight, clientWidth properties when // in standards compliance mode from documentElement to document.body doc = ((domLib_standardsMode && (domLib_isIE || domLib_isGecko)) ? win.document.documentElement : win.document.body); // for IE in compliance mode if (domLib_isIE) { pageHeight = doc.clientHeight; pageWidth = doc.clientWidth; pageYOffset = doc.scrollTop; pageXOffset = doc.scrollLeft; } else { pageHeight = doc.clientHeight; pageWidth = doc.clientWidth; if (domLib_isKHTML) { pageHeight = win.innerHeight; } pageYOffset = win.pageYOffset; pageXOffset = win.pageXOffset; } // we are bleeding off the right, move tip over to stay on page // logic: take x position, add width and subtract from effective page width if ((bleedRight = (x - pageXOffset) + in_width - (pageWidth - domTT_screenEdgePadding)) > 0) { x -= bleedRight; } // we are bleeding to the left, move tip over to stay on page // if tip doesn't fit, we will go back to bleeding off the right // logic: take x position and check if less than edge padding if ((x - pageXOffset) < domTT_screenEdgePadding) { x = domTT_screenEdgePadding + pageXOffset; } // if we are bleeding off the bottom, flip to north // logic: take y position, add height and subtract from effective page height if ((bleedBottom = (y - pageYOffset) + in_height - (pageHeight - domTT_screenEdgePadding)) > 0) { y = in_y - in_height - in_offsetY; } // if we are bleeding off the top, flip to south // if tip doesn't fit, we will go back to bleeding off the bottom // logic: take y position and check if less than edge padding if ((y - pageYOffset) < domTT_screenEdgePadding) { y = in_y + domTT_mouseHeight + in_offsetY; } return {'x' : x, 'y' : y}; } // }}} // {{{ domTT_isActive() // in_id is either the tip id or the owner id function domTT_isActive(in_id) { var tooltip = domTT_tooltips.get(in_id); if (!tooltip || tooltip.get('status') != 'active') { return false; } else { return true; } } // }}} // {{{ domTT_runXXX() // All of these domMenu_runXXX() methods are used by the event handling sections to // avoid the circular memory leaks caused by inner functions function domTT_runDeactivate(args) { domTT_deactivate(args[0]); } function domTT_runShow(args) { domTT_show(args[0], args[1]); } // }}} // {{{ domTT_replaceTitles() function domTT_replaceTitles(in_decorator) { var elements = domLib_getElementsByClass('tooltip'); for (var i = 0; i < elements.length; i++) { if (elements[i].title) { var content; if (typeof(in_decorator) == 'function') { content = in_decorator(elements[i]); } else { content = elements[i].title; } content = content.replace(new RegExp('\'', 'g'), '\\\''); elements[i].onmouseover = new Function('in_event', "domTT_activate(this, in_event, 'content', '" + content + "')"); elements[i].title = ''; } } } // }}} // {{{ domTT_update() // Allow authors to update the contents of existing tips using the DOM // Unfortunately, the tip must already exist, or else no work is done. // TODO: make getting at content or caption cleaner function domTT_update(handle, content, type) { // type defaults to 'content', can also be 'caption' if (typeof(type) == 'undefined') { type = 'content'; } var tip = domTT_tooltips.get(handle); if (!tip) { return; } var tipObj = tip.get('node'); var updateNode; if (type == 'content') { //
... updateNode = tipObj.firstChild; if (updateNode.className != 'contents') { // ... <%cpp> } else { <%cpp> } <# ---------------------------------------------------------------------- #> <#
<$ tr("Existing Recording:") $>
#> <# word-break:break-all; #> vdr-plugin-live-3.1.3/pages/screenshot.ecpp000066400000000000000000000007401414414333500206720ustar00rootroot00000000000000<%pre> #include #include using namespace vdrlive; <%args> int quality = 80; int width = 569; int height = 320; <%session scope="global"> bool logged_in(false); <%cpp> if (!logged_in && LiveSetup().UseAuth()) return reply.redirect("login.html"); reply.setContentType("image/jpg"); GrabImageInfo image = LiveGrabImageManager().GetImage(); if ( image.second > 0 ) reply.out().write( image.first.get(), image.second ); vdr-plugin-live-3.1.3/pages/searchepg.ecpp000066400000000000000000000445401414414333500204640ustar00rootroot00000000000000<%pre> #include #include #include using namespace vdrlive; <%args> // input parameters // form parameters bool useextendedsearch = false; std::string search = ""; int mode = 0; bool matchcase = false; int tolerance = 1; bool usetitle = false; bool usesubtitle = false; bool usedescr = false; int usechannel = SearchTimer::NoChannel; tChannelID channelfrom; tChannelID channelto; std::string changrpsel = ""; bool usetime = false; std::string start_h = "00"; std::string start_m = "00"; std::string stop_h = "00"; std::string stop_m = "00"; bool useduration = false; int durationmin = 0; int durationmax = 90; bool useweekday = false; bool wday_mon = false; bool wday_tue = false; bool wday_wed = false; bool wday_thu = false; bool wday_fri = false; bool wday_sat = false; bool wday_sun = false; int blacklistmode = 0; std::string blacklistids[]; bool useextepginfo = false; std::string extepgvalues[]; std::string blacklistids_internal; <%session scope="global"> bool logged_in(false); <%request scope="page"> SearchTimer* searchtimer; ExtEPGInfos extEPGInfos; ChannelGroups channelGroups; Blacklists blacklists; <%include>page_init.eh <{ if (!logged_in && LiveSetup().UseAuth()) return reply.redirect("login.html"); }> <%cpp> #define SELECTIF(x) reply.out() << ( (x) ? "selected=\"selected\"" : "" ); #define CHECKIF(x) reply.out() << ( (x) ? "checked=\"checked\"" : "" ); searchtimer = 0; if ( request.getMethod() == "POST") { SearchTimer searchtimer; searchtimer.SetSearch(search); searchtimer.SetSearchMode(mode); searchtimer.SetTolerance(tolerance); searchtimer.SetMatchCase(matchcase); searchtimer.SetUseTitle(usetitle); searchtimer.SetUseSubtitle(usesubtitle); searchtimer.SetUseDescription(usedescr); searchtimer.SetUseExtEPGInfo(useextepginfo); if (useextepginfo) { std::vector< std::string > infos; unsigned int i=0; for (ExtEPGInfos::iterator extinfo = extEPGInfos.begin(); extinfo != extEPGInfos.end(); ++extinfo, i++) { std::stringstream os; os << extinfo->Id() << "#" << (i <& pageelems.doc_type &> VDR Live - <$ tr("Search") $> <& pageelems.stylesheets &> <& pageelems.ajax_js &> <& pageelems.logo &> <& menu active=("searchepg") &>
... updateNode = updateNode.firstChild.firstChild.nextSibling.firstChild.firstChild; } } else { updateNode = tipObj.firstChild; if (updateNode.className == 'contents') { // missing caption return; } // <%cpp> } <# ---------------------------------------------------------------------- #> <%def epg_tt_box> <%args> std::string boxId; std::string caption; std::string tools_comp; std::string time; std::string title; std::string short_descr; std::string long_descr; std::string filename; std::string archived; std::string epgImage; int elapsed = -1;
<$ (caption) $>
<& (tools_comp) id=(boxId) archived=(archived) detail=(1) title=(title) &>
<%cpp> if (!archived.empty()) { <$ (archived + " ") $><%cpp> } <$ (time) $>
<%cpp> if (elapsed >= 0) {
<& pageelems.progressbar progress=(elapsed) &>
<%cpp> }
<$ (title) $>
<$ (short_descr) $>
<%cpp> std::list images = EpgEvents::EpgImages(boxId); for(std::list::iterator it = images.begin(); it != images.end(); ++it ) { <%cpp> } if(!epgImage.empty() ) { <%cpp> } std::list images1 = EpgEvents::RecImages(boxId, filename); size_t delimPos = boxId.find_last_of('_'); std::string recId = (delimPos)?boxId.substr(delimPos+1):boxId; for(std::list::iterator it = images1.begin(); it != images1.end(); ++it ) { <%cpp> } <%cpp> reply.out() << StringEscapeAndBreak(long_descr);
<# ---------------------------------------------------------------------- #> <%def about_tt_box>
<$ tr(LIVESUMMARY) $>
<$ tr("Authors") $>
<$ tr("Project Idea") $>:
Thomas Keil (Thomas)
<$ tr("Webserver") $>:
Sascha Volkenandt (LordJaxom)
<$ tr("Current Maintainer") $>:
Markus Ehrnsperger (MarkusE @ VDR Portal)
<$ tr("Previous Maintainer") $>:
Jasmin Jessich (jasminj)
<$ tr("Project leader") $>:
Dieter Hametner (tadi)
<$ tr("Content") $>:
Christian Wieninger (winni)
<$ tr("Graphics") $>:
Michael Brückner (skiller2k1)
<$ tr("Information") $>
<$ tr("LIVE version") $>:
<$ LIVEVERSION $><$ VERSION_SUFFIX $>
<$ tr("VDR version") $>:
<$ VDRVERSION $>
<$ tr("Features") $>
EPGsearch:
<%cpp> Features< features::epgsearch >& epgsearch = LiveFeatures< features::epgsearch >(); "/> <%cpp> if ( epgsearch.Recent() ) { <$ tr("active") $>: <$ epgsearch.Version() $> <%cpp> } else { <$ tr("required") $>: <$ epgsearch.MinVersion() $> <%cpp> } (<$ tr("Homepage") $>)
Streamdev server:
<%cpp> Features< features::streamdev_server >& streamdev = LiveFeatures< features::streamdev_server >(); "/> <%cpp> if ( streamdev.Loaded() ) { <$ tr("active") $>: <$ streamdev.Version() $> <%cpp> } else { <$ tr("required") $>: <$ streamdev.MinVersion() $> <%cpp> } (<$ tr("Homepage") $>)
<$ tr("Bugs and suggestions") $>
<$ tr("If you encounter any bugs or would like to suggest new features, please use our bugtracker") $>:
https://github.com/MarkusEh/vdr-plugin-live/issues
 
vdr-plugin-live-3.1.3/pages/pause_recording.ecpp000066400000000000000000000010041414414333500216600ustar00rootroot00000000000000<%pre> #include #include using namespace vdrlive; <%args> std::string param; <%session scope="global"> bool logged_in(false); <%cpp> if (!logged_in && LiveSetup().UseAuth()) return reply.redirect("login.html"); reply.setContentType( "application/xml" ); PauseRecordingTask task( param ); LiveTaskManager().Execute( task ); <& xmlresponse.ajax name=("pause_recording") pname=("recording") value=(param) result=(task.Result()) error=(task.Error()) &> vdr-plugin-live-3.1.3/pages/play_recording.ecpp000066400000000000000000000012651414414333500215210ustar00rootroot00000000000000<%pre> #include #include #include #include using namespace vdrlive; <%args> std::string param; <%session scope="global"> bool logged_in(false); <%cpp> if (!logged_in && LiveSetup().UseAuth()) return reply.redirect("login.html"); if (!cUser::CurrentUserHasRightTo(UR_STARTREPLAY)) throw HtmlError( tr("Sorry, no permission. Please contact your administrator!") ); reply.setContentType( "application/xml" ); PlayRecordingTask task( param ); LiveTaskManager().Execute( task ); <& xmlresponse.ajax name=("play_recording") pname=("recording") value=(param) result=(task.Result()) error=(task.Error()) &> vdr-plugin-live-3.1.3/pages/playlist.ecpp000066400000000000000000000040641414414333500203610ustar00rootroot00000000000000<%pre> #include #include #include using namespace vdrlive; <%args> tChannelID channel; std::string channel_str; std::string recid; <%session scope="global"> bool logged_in(false); <%request scope="page"> cChannel* Channel; <%include>page_init.eh <%cpp> #if TNTVERSION >= 30000 channel = channel.FromString(channel_str.c_str()); // Tntnet30: get struct channel from parameter string #endif if (!logged_in && LiveSetup().UseAuth()) return reply.redirect("login.html"); if (!cUser::CurrentUserHasRightTo(UR_STARTREPLAY)) throw HtmlError( tr("Sorry, no permission. Please contact your administrator!") ); // the availabilty of Channel signals that we will do live tv streaming. Channel = 0; if (recid.empty()) { #if VDRVERSNUM >= 20301 LOCK_CHANNELS_READ; Channel = (cChannel *)Channels->GetByChannelID(channel); #else ReadLock channelsLock(Channels); if (!channelsLock) { throw HtmlError(tr("Couldn't aquire access to channels, please try again later.")); } Channel = Channels.GetByChannelID(channel); #endif if (Channel == 0) { throw HtmlError( tr("Couldn't find channel or no channels available. Maybe you mistyped your request?") ); } } std::string server = request.getHost(); server = server.substr(0, server.rfind(':')); std::string videourl; reply.setContentType("audio/x-mpegurl"); if (Channel != 0) { std::string channelname = Channel->Name(); int streamdevPort = LiveSetup().GetStreamdevPort(); videourl = std::string("#EXTM3U\n#EXTINF:-1,") + channelname + "\nhttp://" + server + ":" + lexical_cast(streamdevPort) + "/" + LiveSetup().GetStreamdevType() + "/" + *Channel->GetChannelID().ToString(); } else { videourl = std::string("#EXTM3U\n#EXTINF:-1\nhttp://") + server + ":" + lexical_cast(LiveSetup().GetServerPort()) + "/recstream.html?recid=" + recid; } <& playlist.m3u videourl=(videourl) &> <%include>page_exit.eh <%def m3u> <%args> std::string videourl; <$ videourl $> vdr-plugin-live-3.1.3/pages/recordings.ecpp000066400000000000000000000335741414414333500206670ustar00rootroot00000000000000<%pre> #include #include #include #include #include #include #ifdef HAVE_LIBPCRECPP #include #endif #include #define MB_PER_MINUTE 25.75 // this is just an estimate! #include std::string argList; std::string recoring_item; size_t max_length_recoring_item = 0; using namespace vdrlive; <%args> std::string sort; std::string todel; std::string diskinfo; std::string filter; std::string deletions[]; std::string flat; <%session scope="global"> bool logged_in(false); <# scope="page" should be enough but does not work with tntnet 3.0 #> <%request scope="global"> std::string currentSort; std::string deleteResult; std::string currentFilter; std::string currentFlat; <%include>page_init.eh <%cpp> if (!logged_in && LiveSetup().UseAuth()) return reply.redirect("login.html"); // sort dpends on old sort, an sort parameter currentSort = LiveSetup().GetLastSortingMode(); if (currentSort.empty() ) currentSort = "nameasc"; if (!sort.empty()) { if (sort == "date") currentSort = (currentSort == "dateasc") ? "datedesc" : "dateasc"; else if (sort == "name") currentSort = (currentSort == "nameasc") ? "namedesc" : "nameasc"; else if (sort == "errors") currentSort = (currentSort == "errorsasc") ? "errorsdesc" : "errorsasc"; else if (sort == "duplicates") currentSort = sort; LiveSetup().SetLastSortingMode(currentSort); } // flat defaults from sort, but can we overwritten if (currentSort == "errorsasc" || currentSort == "errorsdesc" || currentSort == "duplicates") currentFlat = "true"; else currentFlat = "false"; if (! flat.empty() ) currentFlat = flat; currentFilter = filter; pageTitle = tr("Recordings"); deleteResult = ""; if (!todel.empty()) { if (!cUser::CurrentUserHasRightTo(UR_DELRECS)) throw HtmlError( tr("Sorry, no permission. Please contact your administrator!") ); deletions.push_back(todel); } #if TNTVERSION >= 30000 typedef std::vector deletions_type; #endif for (deletions_type::const_iterator it = deletions.begin(); it != deletions.end(); ++it) { if (cUser::CurrentUserHasRightTo(UR_DELRECS)) { RemoveRecordingTask task(*it); LiveTaskManager().Execute(task); if (!task.Result()) deleteResult += std::string() + tr("ERROR:") + " " + task.Error() + "
"; else deleteResult += std::string() + tr("Deleted recording:") + " " + StringReplace(task.RecName(), "~", "/") + "
"; } else { throw HtmlError( tr("Sorry, no permission. Please contact your administrator!") ); } } deletions.clear(); int FreeMB, UsedMB; #if APIVERSNUM > 20101 int Percent = cVideoDirectory::VideoDiskSpace(&FreeMB, &UsedMB); #else int Percent = VideoDiskSpace(&FreeMB, &UsedMB); #endif int Minutes = int(double(FreeMB) / MB_PER_MINUTE); int Hours = Minutes / 60; Minutes %= 60; diskinfo = cString::sprintf("%s %d%% - %2d:%02d %s", trVDR("Disk"), Percent, Hours, Minutes, trVDR("free")); <& pageelems.doc_type &> VDR-Live - <$ pageTitle $> <& pageelems.stylesheets &> <& pageelems.ajax_js &> <& pageelems.logo &> <%cpp> if (!deleteResult.empty()) { <& menu active=("recordings") component=("recordings.delete_error") &> <%cpp> } else { <& menu active=("recordings") component=("recordings.sort_options") &> <%cpp> }
<$ std::string(tr("List of recordings")) + " (" + diskinfo + ")" $>
<%cpp> #if VDRVERSNUM >= 20301 int rec_cnt; { LOCK_RECORDINGS_READ; rec_cnt = Recordings->Count(); // Access VDRs global cRecordings Recordings instance. } if (rec_cnt == 0) { #else if (Recordings.Count() == 0) { // Access VDRs global cRecordings Recordings instance. #endif <$ tr("No recordings found") $> <%cpp> } else {
    <& recordings.recordings_item filter=(currentFilter) &>
<%cpp> }
<%include>page_exit.eh <# ---------------------------------------------------------------------- #> <%def recordings_item> <%args> filter; path[]; int level = 0; int counter = 0; std::string idHash = ""; <%cpp> recoring_item.reserve(6000); // Max Buffer used: 5.636 bytes RecordingsTreePtr recordingsTree(LiveRecordingsManager()->GetRecordingsTree()); std::list recItems; std::list::iterator recIter; if( currentFlat != "true" ) { RecordingsMap::iterator iter; RecordingsMap::iterator end = recordingsTree->end(path); for (iter = recordingsTree->begin(path); iter != end; ++iter) { RecordingsItemPtr recItem = iter->second; if (recItem->IsDir()) { recItems.push_back(recItem); } } if (currentSort == "namedesc") recItems.sort(RecordingsItemPtrCompare::ByDescendingNameDescSort); else if (currentSort == "nameasc") recItems.sort(RecordingsItemPtrCompare::ByAscendingNameDescSort); for (recIter = recItems.begin(); recIter != recItems.end(); ++recIter) { RecordingsItemPtr recItem = *recIter; counter++; /* search trough directory for new recordings */ bool newR = false; if ( LiveSetup().GetMarkNewRec() ) { std::vector p(path); p.push_back(recItem->Name()); newR = checkNew(recordingsTree, p);; }
  • <& rec_item_dir name=(recItem->Name()) level=(recItem->Level() ) newR=(newR ? "_new" : "") &> <%cpp> #if TNT_QUERYPARAMS_NO_BOOL tnt::QueryParams recItemParams(qparam); #else tnt::QueryParams recItemParams(qparam, false); #endif #if TNTVERSION >= 30000 typedef std::vector path_type; #endif for (path_type::const_iterator i = path.begin(); i != path.end(); ++i) { recItemParams.add("path", *i); } #if TNTVERSION >= 30000 recItemParams.add("path[]", recItem->Name()); #else recItemParams.add("path", recItem->Name()); #endif recItemParams.add("level", lexical_cast(level + 1)); recItemParams.add("filter", filter); std::string combinedId = recItem->Name() + "_"; combinedId += counter + "_" + level; idHash = MD5Hash(combinedId);
  • <%cpp> } recItems.clear(); for (iter = recordingsTree->begin(path); iter != end; ++iter) { RecordingsItemPtr recItem = iter->second; if (!recItem->IsDir()) { recItems.push_back(recItem); } } } else { if (currentSort == "duplicates") { addAllDuplicateRecordings(recItems, recordingsTree); } else { addAllRecordings(recItems, recordingsTree, path); } } if (currentSort == "dateasc") recItems.sort(RecordingsItemPtrCompare::ByAscendingDate); else if (currentSort == "datedesc") recItems.sort(RecordingsItemPtrCompare::ByDescendingDate); else if (currentSort == "namedesc") recItems.sort(RecordingsItemPtrCompare::ByDescendingNameDescSort); else if (currentSort == "nameasc") recItems.sort(RecordingsItemPtrCompare::ByAscendingNameDescSort); else if (currentSort == "errorsasc" || currentSort == "errorsdesc") recItems.sort(RecordingsItemPtrCompare::ByDescendingRecordingErrors); argList.clear(); argList.append("&sort="); argList.append(currentSort); argList.append("&filter="); argList.append(currentFilter); for (recIter = recItems.begin(); recIter != recItems.end(); ++recIter) { RecordingsItemPtr recItem = *recIter; recoring_item.clear(); recItem->AppendasHtml(recoring_item, currentFlat == "true", argList); max_length_recoring_item = std::max(max_length_recoring_item, recoring_item.length() ); #ifdef HAVE_LIBPCRECPP pcrecpp::RE re(filter.c_str(), pcrecpp::UTF8().set_caseless(true)); if (filter.empty() || re.PartialMatch(recItem->Name()) || re.PartialMatch(recItem->ShortText()?recItem->ShortText() : "" ) || re.PartialMatch(recItem->Description()?recItem->Description() : "")) #endif { <$$ recoring_item $> <# Buffer used: <$ recoring_item.length() $> bytes Max Buffer used: <$ max_length_recoring_item $> bytes #> <%cpp> } } <# ---------------------------------------------------------------------- #> <%def sort_options> <%cpp> { " /><$ tr("Sort by name") $> | " /><$ tr("Sort by date") $> | " /><$ tr("Duplicates") $> <%cpp> #if VDRVERSNUM >= 20505 | " /><$ tr("Errors") $> <%cpp> #endif #ifdef HAVE_LIBPCRECPP | <$ tr("Filter") $>:  <& tooltip.help text=(tr("Look in recordings titles and subtitles for the given string and display only the matching ones. You may also use perl compatible regular expressions (PCRE).")) &> <%cpp> #endif | " alt="" <& tooltip.hint text=(tr("Expand all folders")) &>/> " alt="" <& tooltip.hint text=(tr("Collapse all folders")) &>/>
    <%cpp> } <# ---------------------------------------------------------------------- #> <%def del_rec> <%args> std::string id; <%cpp> { >" alt="" /><%cpp> } <# ---------------------------------------------------------------------- #> <%def edit_rec> <%args> std::string id; <%cpp> { " alt="" <& tooltip.hint text=(tr("Edit recording")) &> /><%cpp> } <# ---------------------------------------------------------------------- #> <%def rec_tools> <%args> std::string id; std::string title; <& pageelems.ajax_action_href action="play_recording" param=(id) tip=(tr("play this recording.")) image="play.png" alt="" &> <& pageelems.m3u_playlist_recording recid=(id) &> <& pageelems.imdb_info_href title=(title) &> <& recordings.edit_rec id=(id) &> <& recordings.del_rec id=(id) &> <# ---------------------------------------------------------------------- #> <%def archived_disc> <%args> std::string archived; std::string title; " alt="on_dvd" <& tooltip.hint text=(archived) &> /> <& pageelems.imdb_info_href title=(title) &> <# ---------------------------------------------------------------------- #> <%def rec_item_dir> <%args> std::string name; int level; std::string newR;
    <{ if(level > 1) { }> <{ } }>
    <$ name $>
     
    <# ---------------------------------------------------------------------- #> <%def delete_error> <& recordings.sort_options &> <%cpp> { reply.out() << deleteResult; } vdr-plugin-live-3.1.3/pages/recstream.ecpp000066400000000000000000000042051414414333500205020ustar00rootroot00000000000000<%pre> #include #include using namespace vdrlive; off_t RecSize(cRecording const * recording) { cFileName recFile(recording->FileName(), false, false, recording->IsPesRecording()); off_t recSize = 0; for (cUnbufferedFile *recData = recFile.Open(); recData; recData = recFile.NextFile()) { struct stat buf; if (0 == stat(recFile.Name(), &buf)) { recSize += buf.st_size; // dsyslog("live: size of recording part %s is %ld", recFile.Name(), buf.st_size); } else { esyslog("live: can't determine size of %s", recFile.Name()); } } // dsyslog("live: total size of %s is %ld", recording->FileName(), recSize); return recSize; } <%args> std::string recid; <%session scope="global"> bool logged_in(false); <%cpp> //if (!logged_in && LiveSetup().UseAuth()) return reply.redirect("login.html"); cRecording const * recording = LiveRecordingsManager()->GetByMd5Hash(recid); if (recording) { reply.setContentType("video/mpeg"); reply.setKeepAliveHeader(); reply.setContentLengthHeader(RecSize(recording)); reply.setDirectMode(); cFileName recFile(recording->FileName(), false, false, recording->IsPesRecording()); // dsyslog("live: start send video data."); for (cUnbufferedFile *recData = recFile.Open(); recData; recData = recFile.NextFile()) { char buffer[KILOBYTE(16)]; ssize_t bytesRead = 0; // dsyslog("live: send file %s", recFile->Name()); while (0 < (bytesRead = recData->Read(buffer, sizeof(buffer)))) { // dsyslog("live: copy %zd bytes", bytesRead); reply.out().write(buffer, bytesRead); if (!reply.out()) { return HTTP_GONE; } #if TNT_WATCHDOG_SILENCE request.touch(); // retrigger the watchdog. #endif } // dsyslog("live: bytesRead = %zd", bytesRead); if (bytesRead < 0) { return HTTP_PARTIAL_CONTENT; } } // dsyslog("live: finished send video data."); reply.out() << std::flush; return HTTP_OK; } return DECLINED; vdr-plugin-live-3.1.3/pages/remote.ecpp000066400000000000000000000253641414414333500200210ustar00rootroot00000000000000<%pre> #include #include #include #include using namespace vdrlive; <%args> int channel = -1; <%session scope="global"> bool logged_in(false); <%request scope="page"> cChannel* Channel; <%include>page_init.eh <{ if (!logged_in && LiveSetup().UseAuth()) return reply.redirect("login.html"); }> <%cpp> if (!cUser::CurrentUserHasRightTo(UR_USEREMOTE)) throw HtmlError( tr("Sorry, no permission. Please contact your administrator!") ); pageTitle = tr("Remote Control"); #if VDRVERSNUM < 20301 ReadLock channelsLock( Channels ); if ( !channelsLock ) throw HtmlError( tr("Couldn't aquire access to channels, please try again later.") ); #endif // cChannel* Channel; (see %request above) if ( channel > 0 ) { #if VDRVERSNUM >= 20301 LOCK_CHANNELS_READ; Channel = (cChannel *)Channels->GetByNumber( channel ); #else Channel = Channels.GetByNumber( channel ); #endif } else { if (cDevice::CurrentChannel()) { #if VDRVERSNUM >= 20301 LOCK_CHANNELS_READ; Channel = (cChannel *)Channels->GetByNumber(cDevice::CurrentChannel()); #else Channel = Channels.GetByNumber(cDevice::CurrentChannel()); #endif } else { #if VDRVERSNUM >= 20301 LOCK_CHANNELS_READ; Channel = (cChannel *)Channels->Get( Channels->GetNextNormal( -1 ) ); #else Channel = Channels.Get( Channels.GetNextNormal( -1 ) ); #endif } } if ( Channel == 0 ) throw HtmlError( tr("Couldn't find channel or no channels available. Maybe you mistyped your request?") ); <& pageelems.doc_type &> VDR-Live - <$ pageTitle $> <& pageelems.stylesheets &> <& pageelems.ajax_js &> % reply.sout().imbue(std::locale()); <& pageelems.logo &> <& menu active="remote" component=("remote.remote_actions")>
    <{ if (LiveGrabImageManager().CanGrab()) { }> <{ } }>
    " width="162" height="378" border="0" usemap="#remote" alt="" /> Power 1 2 3 4 5 6 7 8 9 0 Vol+ Vol- P+ P- Mute Record Menu Exit Subtitles Audio Up Right Down Left Ok Red Green Yellow Blue
    <%include>page_exit.eh <%def remote_actions> <$ tr("Selection") $>:  <{ if ( LiveGrabImageManager().CanGrab()) { }>              - <{ } }>   -   vdr-plugin-live-3.1.3/pages/rwd_recording.ecpp000066400000000000000000000010051414414333500213400ustar00rootroot00000000000000<%pre> #include #include using namespace vdrlive; <%args> std::string param; <%session scope="global"> bool logged_in(false); <%cpp> if (!logged_in && LiveSetup().UseAuth()) return reply.redirect("login.html"); reply.setContentType( "application/xml" ); BackwardRecordingTask task( param ); LiveTaskManager().Execute( task ); <& xmlresponse.ajax name=("rwd_recording") pname=("recording") value=(param) result=(task.Result()) error=(task.Error()) &> vdr-plugin-live-3.1.3/pages/schedule.ecpp000066400000000000000000000336611414414333500203210ustar00rootroot00000000000000<%pre> #include #include #include #include #include #include using namespace vdrlive; bool searchNameDesc(RecordingsItemPtr &RecItem, std::list &RecItems, const std::string &Name, const std::string &ShortText, const std::string &Description, long Duration) { if(RecItems.begin() == RecItems.end() ) return false; // there are no recordings // find all recordings with equal name RecordingsItemPtr dummy (new RecordingsItemDummy(Name, ShortText, Description, Duration)); std::list::iterator recIterLowName = std::lower_bound (RecItems.begin(), RecItems.end(), dummy, RecordingsItemPtrCompare::ByAscendingName); std::list::iterator recIterUpName = std::upper_bound (recIterLowName , RecItems.end(), dummy, RecordingsItemPtrCompare::ByAscendingName); if ( recIterLowName == recIterUpName ) return false; // there is no recording with this name // find all recordings with matching short text / description std::list::iterator recIterLow = std::lower_bound (recIterLowName, recIterUpName, dummy, RecordingsItemPtrCompare::ByAscendingNameDesc); std::list::iterator recIterUp = std::upper_bound (recIterLow, recIterUpName, dummy, RecordingsItemPtrCompare::ByAscendingNameDesc); if(recIterLow != recIterUp) { // exact match found if (RecordingsItemPtrCompare::FindBestMatch(RecItem, recIterLow, recIterUp, dummy) > 0) return true; RecItem = *recIterLow; return true; } // no exact match found, get recording with most matching characters int numEqualCharsLow = 0; int numEqualCharsUp = 0; if(recIterLow != recIterLowName) { --recIterLow; RecordingsItemPtrCompare::Compare2(numEqualCharsLow, *recIterLow, dummy); } if(recIterUp != recIterUpName) RecordingsItemPtrCompare::Compare2(numEqualCharsUp , *recIterUp , dummy); if ( numEqualCharsLow > numEqualCharsUp ) { if( numEqualCharsLow > 5 ) { RecItem = *recIterLow; return true; } } else { if( numEqualCharsUp > 5 ) { RecItem = *recIterUp; return true; } } // no exact match found, get best match int num_match_rec = RecordingsItemPtrCompare::FindBestMatch(RecItem, recIterLowName, recIterUpName, dummy); if(num_match_rec == 0 || num_match_rec > 5) return false; // no matching lenght or series (too many matching length) return true; } <%args> int channel = -1; <%session scope="global"> bool logged_in(false); <# scope="page" should be enough but does not work with tntnet 3.0 #> <%request scope="global"> cChannel* Channel; <%include>page_init.eh <%cpp> if (!logged_in && LiveSetup().UseAuth()) return reply.redirect("login.html"); <%cpp> pageTitle = trVDR("Schedule"); std::vector path; std::list recItems; RecordingsTreePtr recordingsTree(LiveRecordingsManager()->GetRecordingsTree()); addAllRecordings(recItems, recordingsTree, path); recItems.sort(RecordingsItemPtrCompare::ByAscendingNameDesc); #if VDRVERSNUM < 20301 cSchedulesLock schedulesLock; cSchedules const* schedules = cSchedules::Schedules( schedulesLock ); ReadLock channelsLock( Channels ); if ( !channelsLock ) throw HtmlError( tr("Couldn't aquire access to channels, please try again later.") ); #endif // cChannel* Channel; (see %request above) if ( channel > 0 ) { #if VDRVERSNUM >= 20301 LOCK_CHANNELS_READ; Channel = (cChannel *)Channels->GetByNumber( channel ); #else Channel = Channels.GetByNumber( channel ); #endif } else { #if VDRVERSNUM >= 20301 LOCK_CHANNELS_READ; #endif if (cDevice::CurrentChannel()) { #if VDRVERSNUM >= 20301 Channel = (cChannel *)Channels->GetByNumber(cDevice::CurrentChannel()); #else Channel = Channels.GetByNumber(cDevice::CurrentChannel()); #endif } else { #if VDRVERSNUM >= 20301 Channel = (cChannel *)Channels->Get( Channels->GetNextNormal( -1 ) ); #else Channel = Channels.Get( Channels.GetNextNormal( -1 ) ); #endif } } if ( Channel == 0 ) throw HtmlError( tr("Couldn't find channel or no channels available. Maybe you mistyped your request?") ); cSchedule const* Schedule; #if VDRVERSNUM >= 20301 { LOCK_SCHEDULES_READ; Schedule = Schedules->GetSchedule( (const cChannel *)Channel ); } #else Schedule = schedules->GetSchedule( Channel ); #endif <& pageelems.doc_type &> VDR Live - <$ pageTitle $> <& pageelems.stylesheets &> <& pageelems.ajax_js &> <& pageelems.logo &> <& menu active=("schedule") component=("schedule.channel_selection") &>
    <%cpp> if ( Schedule == 0 ) { <$ tr("No schedules available for this channel") $>. <%cpp> } else {
    ... updateNode = updateNode.firstChild.firstChild.firstChild.firstChild; } // TODO: allow for a DOM node as content updateNode.innerHTML = content; } // }}} vdr-plugin-live-3.1.3/javascript/domTT_drag.js000066400000000000000000000050401414414333500212730ustar00rootroot00000000000000/** $Id: domTT_drag.js,v 1.1 2007/01/04 22:29:18 thomas Exp $ */ // {{{ license /* * Copyright 2002-2005 Dan Allen, Mojavelinux.com (dan.allen@mojavelinux.com) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ // }}} // {{{ globals (DO NOT EDIT) var domTT_dragEnabled = true; var domTT_currentDragTarget; var domTT_dragMouseDown; var domTT_dragOffsetLeft; var domTT_dragOffsetTop; // }}} // {{{ domTT_dragStart() function domTT_dragStart(in_this, in_event) { if (typeof(in_event) == 'undefined') { in_event = window.event; } var eventButton = in_event[domLib_eventButton]; if (eventButton != 1 && !domLib_isKHTML) { return; } domTT_currentDragTarget = in_this; in_this.style.cursor = 'move'; // upgrade our z-index in_this.style.zIndex = ++domLib_zIndex; var eventPosition = domLib_getEventPosition(in_event); var targetPosition = domLib_getOffsets(in_this); domTT_dragOffsetLeft = eventPosition.get('x') - targetPosition.get('left'); domTT_dragOffsetTop = eventPosition.get('y') - targetPosition.get('top'); domTT_dragMouseDown = true; } // }}} // {{{ domTT_dragUpdate() function domTT_dragUpdate(in_event) { if (domTT_dragMouseDown) { if (domLib_isGecko) { window.getSelection().removeAllRanges() } if (domTT_useGlobalMousePosition && domTT_mousePosition != null) { var eventPosition = domTT_mousePosition; } else { if (typeof(in_event) == 'undefined') { in_event = window.event; } var eventPosition = domLib_getEventPosition(in_event); } domTT_currentDragTarget.style.left = (eventPosition.get('x') - domTT_dragOffsetLeft) + 'px'; domTT_currentDragTarget.style.top = (eventPosition.get('y') - domTT_dragOffsetTop) + 'px'; // update the collision detection domLib_detectCollisions(domTT_currentDragTarget); } } // }}} // {{{ domTT_dragStop() function domTT_dragStop() { if (domTT_dragMouseDown) { domTT_dragMouseDown = false; domTT_currentDragTarget.style.cursor = 'default'; domTT_currentDragTarget = null; if (domLib_isGecko) { window.getSelection().removeAllRanges() } } } // }}} vdr-plugin-live-3.1.3/javascript/fadomatic.js000066400000000000000000000107541414414333500212060ustar00rootroot00000000000000/** $Id: fadomatic.js,v 1.1 2007/01/04 22:29:18 thomas Exp $ */ // Title: Fadomatic // Version: 1.2 // Homepage: http://chimpen.com/fadomatic // Author: Philip McCarthy // Fade interval in milliseconds // Make this larger if you experience performance issues Fadomatic.INTERVAL_MILLIS = 50; // Creates a fader // element - The element to fade // speed - The speed to fade at, from 0.0 to 100.0 // initialOpacity (optional, default 100) - element's starting opacity, 0 to 100 // minOpacity (optional, default 0) - element's minimum opacity, 0 to 100 // maxOpacity (optional, default 0) - element's minimum opacity, 0 to 100 function Fadomatic (element, rate, initialOpacity, minOpacity, maxOpacity) { this._element = element; this._intervalId = null; this._rate = rate; this._isFadeOut = true; // Set initial opacity and bounds // NB use 99 instead of 100 to avoid flicker at start of fade this._minOpacity = 0; this._maxOpacity = 99; this._opacity = 99; if (typeof minOpacity != 'undefined') { if (minOpacity < 0) { this._minOpacity = 0; } else if (minOpacity > 99) { this._minOpacity = 99; } else { this._minOpacity = minOpacity; } } if (typeof maxOpacity != 'undefined') { if (maxOpacity < 0) { this._maxOpacity = 0; } else if (maxOpacity > 99) { this._maxOpacity = 99; } else { this._maxOpacity = maxOpacity; } if (this._maxOpacity < this._minOpacity) { this._maxOpacity = this._minOpacity; } } if (typeof initialOpacity != 'undefined') { if (initialOpacity > this._maxOpacity) { this._opacity = this._maxOpacity; } else if (initialOpacity < this._minOpacity) { this._opacity = this._minOpacity; } else { this._opacity = initialOpacity; } } // See if we're using W3C opacity, MSIE filter, or just // toggling visiblity if(typeof element.style.opacity != 'undefined') { this._updateOpacity = this._updateOpacityW3c; } else if(typeof element.style.filter != 'undefined') { // If there's not an alpha filter on the element already, // add one if (element.style.filter.indexOf("alpha") == -1) { // Attempt to preserve existing filters var existingFilters=""; if (element.style.filter) { existingFilters = element.style.filter+" "; } element.style.filter = existingFilters+"alpha(opacity="+this._opacity+")"; } this._updateOpacity = this._updateOpacityMSIE; } else { this._updateOpacity = this._updateVisibility; } this._updateOpacity(); } // Initiates a fade out Fadomatic.prototype.fadeOut = function () { this._isFadeOut = true; this._beginFade(); } // Initiates a fade in Fadomatic.prototype.fadeIn = function () { this._isFadeOut = false; this._beginFade(); } // Makes the element completely opaque, stops any fade in progress Fadomatic.prototype.show = function () { this.haltFade(); this._opacity = this._maxOpacity; this._updateOpacity(); } // Makes the element completely transparent, stops any fade in progress Fadomatic.prototype.hide = function () { this.haltFade(); this._opacity = 0; this._updateOpacity(); } // Halts any fade in progress Fadomatic.prototype.haltFade = function () { clearInterval(this._intervalId); } // Resumes a fade where it was halted Fadomatic.prototype.resumeFade = function () { this._beginFade(); } // Pseudo-private members Fadomatic.prototype._beginFade = function () { this.haltFade(); var objref = this; this._intervalId = setInterval(function() { objref._tickFade(); },Fadomatic.INTERVAL_MILLIS); } Fadomatic.prototype._tickFade = function () { if (this._isFadeOut) { this._opacity -= this._rate; if (this._opacity < this._minOpacity) { this._opacity = this._minOpacity; this.haltFade(); } } else { this._opacity += this._rate; if (this._opacity > this._maxOpacity ) { this._opacity = this._maxOpacity; this.haltFade(); } } this._updateOpacity(); } Fadomatic.prototype._updateVisibility = function () { if (this._opacity > 0) { this._element.style.visibility = 'visible'; } else { this._element.style.visibility = 'hidden'; } } Fadomatic.prototype._updateOpacityW3c = function () { this._element.style.opacity = this._opacity/100; this._updateVisibility(); } Fadomatic.prototype._updateOpacityMSIE = function () { this._element.filters.alpha.opacity = this._opacity; this._updateVisibility(); } Fadomatic.prototype._updateOpacity = null; vdr-plugin-live-3.1.3/javascript/treeview.ecpp000077700000000000000000000000001414414333500236002treeview.jsustar00rootroot00000000000000vdr-plugin-live-3.1.3/javascript/treeview.js000066400000000000000000000101021414414333500210740ustar00rootroot00000000000000// --------------------------------------------- // --- Name: Easy DHTML Treeview -- // --- Author: D.D. de Kerf -- // --- Adapted: Jasmin Jessich -- // --- Adapted: hepi (via patch) -- // --- Adapted: Dieter Hametner -- // --- Version: 0.3 Date: 14-6-2017 -- // --------------------------------------------- function findSibling(node, name) { while ((node.nextSibling.nodeType != Node.ELEMENT_NODE) || (node.nextSibling.nodeName != name)) { node = node.nextSibling; } if (node.nextSibling.nodeName == name) return node.nextSibling; return null; } function findChildNode(node, className) { for (idx = 0; idx < node.childNodes.length; idx++) { n = node.childNodes.item(idx); if (n.nodeType == Node.ELEMENT_NODE) { attr = n.getAttributeNode("class"); if ((attr != null) && (attr.nodeValue == className)) { return n; } } } return null; } function findImageNode(node, className) { for (idx = 0; idx < node.childNodes.length; idx++) { n = node.childNodes.item(idx); if ((n.nodeType == Node.ELEMENT_NODE) && (n.nodeName == "IMG")) { attr = n.getAttributeNode("class"); if ((attr != null) && (attr.nodeValue == className)) { return n; } } } return null; } function setImages(node, expand, folder) { // Change the image (if there is an image) if (node.childNodes.length > 0) { expandNode = findImageNode(node, "recording_expander"); if (expandNode != null) expandNode.src = expand; folderNode = findImageNode(node, "recording_folder"); if (folderNode != null) folderNode.src = folder; } } function Toggle(node) { // Unfold the branch if it isn't visible sibling = findSibling(node, "UL"); if (sibling == null) return; imgChild = findChildNode(node, "recording_imgs"); if (sibling.style.display == 'none') { if (imgChild != null) setImages(imgChild, "img/minus.png", "img/folder_open.png"); sibling.style.display = 'block'; updateCookieOnExpand( sibling.id ); } // Collapse the branch if it IS visible else { updateCookieOnCollapse( sibling.id ); if (imgChild != null) setImages(imgChild, "img/plus.png", "img/folder_closed.png"); sibling.style.display = 'none'; } } function updateCookieOnExpand( id ) { var openNodes = readCookie( cookieNameRec ); if (openNodes == null || openNodes == "") openNodes = id; else openNodes += "," + id; createCookie( cookieNameRec, openNodes, 14 ); } function updateCookieOnCollapse( id ) { var openNodes = readCookie( cookieNameRec ); if (openNodes != null) openNodes = openNodes.split(","); else openNodes = []; for (var z=0; z& g_collate_char = std::use_facet >(g_locale); cUsers Users; Plugin::Plugin(void) { } const char *Plugin::CommandLineHelp(void) { return LiveSetup().CommandLineHelp(); } bool Plugin::ProcessArgs(int argc, char *argv[]) { return LiveSetup().ParseCommandLine( argc, argv ); } bool Plugin::Start(void) { m_configDirectory = canonicalize_file_name(cPlugin::ConfigDirectory( PLUGIN_NAME_I18N )); m_resourceDirectory = canonicalize_file_name(cPlugin::ResourceDirectory( PLUGIN_NAME_I18N )); // force status monitor startup LiveStatusMonitor(); // preload files into file Cache PreLoadFileCache(m_resourceDirectory); // load users Users.Load(AddDirectory(m_configDirectory.c_str(), "users.conf"), true); // XXX error handling m_thread.reset( new ServerThread ); m_thread->Start(); return true; } void Plugin::Stop(void) { m_thread->Stop(); } void Plugin::MainThreadHook(void) { LiveTimerManager().DoPendingWork(); LiveTaskManager().DoScheduledTasks(); } cString Plugin::Active(void) { return NULL; } cMenuSetupPage *Plugin::SetupMenu(void) { return new cMenuSetupLive(); } bool Plugin::SetupParse(const char *Name, const char *Value) { return LiveSetup().ParseSetupEntry( Name, Value ); } } // namespace vdrlive VDRPLUGINCREATOR(vdrlive::Plugin); // Don't touch this! vdr-plugin-live-3.1.3/live.h000066400000000000000000000026321414414333500156570ustar00rootroot00000000000000#ifndef VDR_LIVE_LIVE_H #define VDR_LIVE_LIVE_H #include "thread.h" // STL headers need to be before VDR tools.h (included by ) #include #if TNTVERSION >= 30000 #include // must be loaded before any vdr include because of duplicate macros (LOG_ERROR, LOG_DEBUG, LOG_INFO) #endif #ifndef DISABLE_TEMPLATES_COLLIDING_WITH_STL // To get rid of the swap definition in vdr/tools.h #define DISABLE_TEMPLATES_COLLIDING_WITH_STL #endif #include namespace vdrlive { class Plugin : public cPlugin { public: Plugin(void); virtual const char *Version(void) { return VERSION; } virtual const char *Description(void) { return tr(DESCRIPTION); } virtual const char *CommandLineHelp(void); virtual bool ProcessArgs(int argc, char *argv[]); virtual bool Start(void); virtual void Stop(void); virtual void MainThreadHook(void); virtual cString Active(void); virtual cMenuSetupPage *SetupMenu(void); virtual bool SetupParse(const char *Name, const char *Value); static std::string const& GetConfigDirectory() { return m_configDirectory; } static std::string const& GetResourceDirectory() { return m_resourceDirectory; } private: static const char *VERSION; static const char *DESCRIPTION; static std::string m_configDirectory; static std::string m_resourceDirectory; std::unique_ptr m_thread; }; } // namespace vdrlive #endif // VDR_LIVE_LIVE_H vdr-plugin-live-3.1.3/live/000077500000000000000000000000001414414333500155035ustar00rootroot00000000000000vdr-plugin-live-3.1.3/live/css/000077500000000000000000000000001414414333500162735ustar00rootroot00000000000000vdr-plugin-live-3.1.3/live/css/DatePicker.css000066400000000000000000000020641414414333500210220ustar00rootroot00000000000000 input.DatePicker{ display: block; width: 150px; padding: 3px 3px 3px 24px; border: 1px solid #0070bf; cursor: pointer; } .dp_container{ position: relative; padding: 0; z-index: 500; } .dp_cal{ background-color: #fff; border: 1px solid #0070bf; position: absolute; width: 177px; top: 24px; left: 0; margin: 0px 0px 3px 0px; } .dp_cal table{ width: 100%; border-collapse: collapse; border-spacing: 0; } .dp_cal select{ margin: 2px 3px; font-size: 11px; } .dp_cal select option{ padding: 1px 3px; } .dp_cal th, .dp_cal td{ width: 14.2857%; text-align: center; font-size: 11px; padding: 2px 0; } .dp_cal th{ border: solid #aad4f2; border-width: 1px 0; color: #797774; background: #daf2e6; font-weight: bold; } .dp_cal td{ cursor: pointer; } .dp_cal thead th{ background: #d9eefc; } .dp_cal td.dp_roll{ color: #000; background: #fff6bf; } /* must have this for the IE6 select box hiding */ .dp_hide{ visibility: hidden; } .dp_empty{ background: #eee; } .dp_today{ background: #daf2e6; } .dp_selected{ color: #ff0000; background: #328dcf; } vdr-plugin-live-3.1.3/live/css/siteprefs.css000066400000000000000000000011641414414333500210130ustar00rootroot00000000000000/* ###################### # This file is part of vdr-live! # It is here to give the users the possibility to change the # default css style of vdr-live to their needs. # # If you don't want to change default settings, make this file # empty, but don't delete it. ###################### */ /* uncomment this below, to make all tables full page width. */ /* table { width: 100%; } */ /* comment this out, if you want epg images at their native size * the popup windows. This here restricts their width to 120 px. * You might also only change size. */ .info-win span.epg_images { width: 120px; } vdr-plugin-live-3.1.3/live/img/000077500000000000000000000000001414414333500162575ustar00rootroot00000000000000vdr-plugin-live-3.1.3/live/img/169.png000066400000000000000000000012531414414333500173050ustar00rootroot00000000000000PNG  IHDRF(t|lPLTEGpLdp#tRNS"v<X/gJ@P08קϏ@HMoIDATHv @DPIXi|jIYd6t<y;"v[[?Q^˧QFeLxxn6])LMFqr0&W FI(f&sa¨K) +N$ iݷii[?aZW:D\Ǵ -_4Bg`fKy UE/.6~֦O#}I_L}3!}`Jisd{7Md cdJZ*e#gR7UK&X6EIS s놀6!X=UqA$Gۺl^vYvZ!Yh܋!2IENDB`vdr-plugin-live-3.1.3/live/img/NoRecordingErrors.png000066400000000000000000000056021414414333500223760ustar00rootroot00000000000000PNG  IHDRPLTEGpL??>>????>>66)g)66>>????!S!>>>>55>>!S!55#Z# S 5577>><<=77  >>66.u.>>==>>==-t-88(g(;;>>>>::<<9922;;(g(;;  1}1==>>=33<<(&b&*k*<<:: # +n+"W".7==0z07777>>;;==>>66==::<<====<<>>>>;;::>>==<<77;;/x/::C2%_%!S!.u.66>>99P$[$55,p,885588==K'b''b'7788H====9966==, 88#X# S #/w/77  4444D<<1|144&`&27> R PC  ??>>.ItRNS "A1:* h' B-=\z/4 `tK}O8bj;Q 2&,MlFUS%#X?wv!yrH]Ddp7̅)nV|$ se3ϸ 1Zof5&(٫;xS> ݜ͏Ͽ.Y+IqmeE|aļљ'2*dHN4moBFꛢIY{su3IDATxw\gM!!BHBiTP( eb::P պZVvi{Oҽ=?D{.y/\ G8pCBX,?Mk hH?sYK 0Kvܨk'iA (4b}sh ( ѲgwE&-E3/{s-ڝʍ꼓 _tʕ,a90rVSM|Q@)lSfa&4 (rKg^@ `Xoڒ< XW#PD|ǿ70KDMHAzƌ2kl@U ;T3s.A{0ʎWMl(W2 *Ёp+U='(i WWkʫ>Ҝ67fFk <88UJkba rTWf^U fo]0Xl@X0vSI9Y5.N9o^>t$UV#ekE2' 0u<BQ^hv"X>ڜ:$qsS>![C{@3y*Vqz I+Jӓgе1٢Yv9e3g#P-32JQ>1,]%YF~Dfڜӣ3KOlxb-*բL.'s -܇"Wbc{~wEF\-Bw3lН"هϿkG;uxn4ҟ>ksVJW"Q8FokgSԏni)_Gy'*5y'~X|m|*6^#(hCX.{ZTve/@T"XTXɧ0&W"xSC;&މW2pݎ& `*A#H1E- h` z`TqbqGA`/38%3@ ŘM_ Xvz(|C>|߷5r Hu -NNV>uU\Om-h'>)T8SOwmGʉ:IL{8AF Ȉhq"F DGF0 '˼L{`G &@`9q/1R`{W!qW5k \PGp ]qqDZ #اd?-a2xpT(nCoZ~+8dS`|x? lk>? LN[3 0ْ ZAoKVՒv^[#(8q'zP~Sޞ ~⑭x;>4~'X6*gGЇW5?뿿͝42`iE^(M0ԎH.UhQR ֚ںCͅ-C+¾ NWVh-lIJ+_P!*SyJk9H% VMHIв 6G jjLL(~_k>j(~DyŠ_#jD3۲IENDB`vdr-plugin-live-3.1.3/live/img/NotCheckedForRecordingErrors.png000066400000000000000000000024571414414333500245050ustar00rootroot00000000000000PNG  IHDRe5 cHRMz&u0`:pQ<PLTEg@.tRNSJsr21XW =<ed鎍'&)OkbKGD/#  pHYs  IDATx]WRQqP B 3zy{Ϭ5k/s[sXeYeYeYi0a#$Ou87p6)Q?|>\0"ӀO,x_E_o XaX=}k?w< 4`~r$: q0aOAty:7 .'[]qdw!t9Łr#s9zrι.͵. Xێα.W Vέ. T]Ω.7 R0r} X~G̜#Cr vt9 Gs{]!ʍ.\ s%Ĺ%͑u":ҭ.P\tG:ruMusC]`PC]rNL|G:F5aGn?]85.{B} h>Ge#jߑuH$8=. dK#*Hz$9R0.i K#%Hр*$:R4. K#e|(K=R:.H進d#q(K=R>.YHŀbd#Jw (K=R3.HՀ"d#UJ{ ץ=rv9ALuiulvAIf}תLs4HZrfrR]>]4.uߵSr)u)U +]ʿkӥV]ji7@K#ur.U4 եΑ9rLJGRH]ji;K#uv.ӥ#tؑj]vZkK#KEN#rtYiwRؑ#rםudzu9.\#ti9tiBti˶H]8dr@=q$Y*$ɲ,˲,˲,˲,J YIENDB`vdr-plugin-live-3.1.3/live/img/RecordingErrors.png000066400000000000000000000042741414414333500221050ustar00rootroot00000000000000PNG  IHDRe5 cHRMz&u0`:pQ<PLTE################################################################################################################################################################################ZBtRNS ,Lml+J~٫}XԙW|*&%^\312/)'jf{$!b >;[Vwvayd._(-zYTKHIiS"]7Rr8e0Z#ϔG:bKGDCd pHYs  [IDATx{WTU ~ $ (H f 0`EԢAR ZUZu9g_~~Z߽a`0 `0ı--}{Ǝ̬@Nв9܌y K,zVIiYa9l**C69b?/}ZYE܄kvAJ_7DyYW<ҰOk|p YkZHAm?DBxᰖ/DK/GH #joG;H0b! tz^){zUO4 :H2C2"錌9&+8)59w*qI!'H)'RYr&S)q{H o'[t\|ϑ6:,l;E?7 T?M~<[ gWGS3>ZS(yO|i?#E8#Bg] w\?lAuFJ P: Osq!>Qb p c ,0hS`/"Ǩ|?P|.]XR6Jk5xW>#\ZV+Gg6Q,!9-vlG l#,;W: Vrk@/Pʿ)?,k>UH¹ى-bsˮ-9D8N 8FKxwx6Z S{bolpNKT 'V \;)pbF7lhDgXs@_-&1m__3!Tit1Ax̄1#7L +2?U+_ .}c&*E_י$Ɣ1if_$24(;&[92߫`9/л(i4 35u3^9͔R?>TS՚`:<s}'GL'ͷ} {nj&>;w+,bniep_^Z8rj:CrO,anye*yjZ8ִGߵr3 `0 q RtIENDB`vdr-plugin-live-3.1.3/live/img/active.png000066400000000000000000000015261414414333500202440ustar00rootroot00000000000000PNG  IHDRaIDAT8]MlU{y}mֶPbjMP4jبF E#H AeabNB!012*h$&DSyX)mm^gݙ{0:sxv_ɨ&}7"sZi[|"=sk~ 5hVB_"dֱ\úAv`1rZ:uDu|ۛs)W`,\͏KS@Szkt"::^p!3$J԰EsuΩ`Ѧl1_E(`]NfS]iT +ZnL* BA\2@ɫS>~פ$jlO lHI/q*h[y.+uA ˀ#ܽnV`֞!Ǥ0`  U6%"Թ݁g\;Asf9m5׭7>lcK^zfWɽ6-p_KQ~\X sK/K/pk-y.d䦕\]87Ko3n箾 ]-48%j60bolPQ"X9ѝD~Z'l6YeʒvY̳C=žV :`Duc5Ifvy,^.к_^iScD>f)P*T2BX'Z(I80E.sTUT*"њN7$:+L;yuz'rSlSA1,-[ hijIENDB`vdr-plugin-live-3.1.3/live/img/arrow.png000066400000000000000000000006431414414333500201220ustar00rootroot00000000000000PNG  IHDRasBIT|d pHYsyy8tEXtSoftwarewww.inkscape.org< IDAT8c?%"47QIZZz####3.ಱuPWWNWTTijjdddp%,dddb3YXXKHH&Ǐ:O\|1 PPPhijjo߾2tvv=_x/0ppp2 `@^^>TRR*n߿Y|}} sʕ+!>} 781Bff֛e˖700< aaﴴ300cUC1qq À\0 ?u<<-/%& ()^FE?=sDD?@;;nGFFFhON{JK9={NN00..LL:9yRPORB@HI/.MORP}UTQQq[YCDUT=:KK;=WUYYEE<8>A;B<=`a\_[[`c`^{ec``Y[adgeYZ`_hkUUggjkONQR^]NRZZikPNkkrtWSpqUVtqigmooofeWUrueelh_^xy^b|xvww`^~{{~xw}{wxyxsrnquvppolppÉЈ酄~ሉӌ􅈯Ŕٖ擑ә¥Уݢ硠٥ȫĮϱdz¶˴سԶϹ㴱ɽӿ! NETSCAPE2.0!Created with The GIMP! ,Z H*f"ߴ.3(‡ @]A8\tQdyASA*hfBn\\:ԫȴӀ! ,` H*F$$ߴ j3芅 9hh1!6Xɒt lR6hI%n&aˇ$80ݍzJ! ,Z H*&bx7:3hKʖR~ ^AK:\9;(Y̱/ q R<\>cʼPȴiӀ! ,Z H*L(ܞq.v68XTQ3*eR,E <'W1C~S3a:2-=L'Cxt(>\ʴ! ,Z H*LG)Lrp>$cG=W֞A(SC@*U)Gǎ|PB]?=t'gCQ<ʴ)Ӏ! ,Z H*tg*r0ݣE(O.AT/IRiAa(W C^N!GZ>w(Tɇ>\ʴ)! ,\ H*gkNПTjQ,GZVx1TR Ah(W|Վ[CyPpީZ%֛Xӧ! ,W H*Glp ]&`|H0_(S VI(zG㰛 E꒷p_-J1*M! ,V H*GL!Axsޮ^ 60^0N$H(OGdJ,#H`-LX="TxPHR ! ,\ H*kNTjQ,V.]n5ֽbZɒdzSd9;n 9r * s)XӦ! ,X H*j*ПL&FGRF*U0NRYjc(WԎ6C"U3!N#].xtDžçP ! ,\ H*&FLq DUG%-d@!2R‡;'W/%dIB-=7ȼ%ӧ! ,b H*$$ߴ j3F=^h!6XĄRْmmJL8.Ä1Pt7(VJj@! ,Z H*v# ߴ N3h%$`^: \tQ\ 5<¶|@ɊLԫȴӀ;vdr-plugin-live-3.1.3/live/img/bg_box_h.png000066400000000000000000000001761414414333500205400ustar00rootroot00000000000000PNG  IHDRʚ%EIDATUȻ 0J7HCU8%㓌4e,QQ(sjFx :eڿIENDB`vdr-plugin-live-3.1.3/live/img/bg_header_l.png000066400000000000000000000002661414414333500212040ustar00rootroot00000000000000PNG  IHDR`}IDATӅ1` W /cB:ۯ-?v9"\ܾ&hs7y167mf!vgҐ1- kffx?(?єof>OIENDB`vdr-plugin-live-3.1.3/live/img/bg_header_r.png000066400000000000000000000002761414414333500212130ustar00rootroot00000000000000PNG  IHDR`IDATӍ1 @Eߊ0 $EA7& x o]:[8Np{<_x Rg>Z7)ƹ b&kv1D4 c&:@)[WB'ʽIENDB`vdr-plugin-live-3.1.3/live/img/bg_line.png000066400000000000000000000002201414414333500203560ustar00rootroot00000000000000PNG  IHDRrcfbKGD pHYs  tIME a5&OIDATc? L20c rQIENDB`vdr-plugin-live-3.1.3/live/img/bg_line_top.png000066400000000000000000000001321414414333500212420ustar00rootroot00000000000000PNG  IHDR1!IDATcx&f&&f&V(߿ufu#IENDB`vdr-plugin-live-3.1.3/live/img/bg_tools.png000066400000000000000000000002271414414333500205760ustar00rootroot00000000000000PNG  IHDR!*PbKGD pHYs  tIME;k˖D$IDATc|?K5TXXYXXRPY{IENDB`vdr-plugin-live-3.1.3/live/img/button_blue.png000066400000000000000000000006261414414333500213130ustar00rootroot00000000000000PNG  IHDRd{bKGD pHYs  tIME 2i/H#IDATX햱JQE̾dI"M"4Ә L#V V"؉u$L}co9pson0`H~wc<;39Žt[ӄ 7W_ ޢH=P2BKY,^f*PTX7:xaFBw0%_!oZC H ,EE+!LF.€Ƀ[I\Pዲj%18M6W9L 6XԛX-{L"—\rsz.M1vO{)T.BIENDB`vdr-plugin-live-3.1.3/live/img/button_green.png000066400000000000000000000007041414414333500214610ustar00rootroot00000000000000PNG  IHDRd{bKGD pHYs  tIME 4!$*tEXtCommentCreated with The GIMPd%n(IDATX=JCAFDH#DI=DI {݀,,R  X0wESYwcSUyzN K1f{i^oNw?ZPd8ĵ/644,\($MFё mFqP"C0!&0!iT!0P 22#y2(dن&Ą&_ qY v\]DpTu$?j췺7&V "$1[T5<']MG/LgS Kqv+͵m~Twz IENDB`vdr-plugin-live-3.1.3/live/img/button_new.png000066400000000000000000000016431414414333500211550ustar00rootroot00000000000000PNG  IHDRasBIT|d pHYsSSe-tEXtSoftwarewww.inkscape.org< IDAT8]S=Hkg=wD!\̏( >B>͵]:tqx.E_;^RDx&RK)tkKJFiBroݯCQg>Cxͧ_|411BVWW[^`}}:ȅPJj[YY@ ?JzK)1RPJAJ 岨j&I۽0|)(a(@)c Rr9LNN.lmm}FX|MRacccHRpWWWX[[C,<c q1^O,뚣1 !z(BEZsqf>$i0 q1`~!c8>}tGGG݅Q4M\^^bccA ͡P( @k.Bu]iZ-~r99c N l6Iӟ9Tk>AiH)w 1_TD\&IENDB`vdr-plugin-live-3.1.3/live/img/button_red.png000066400000000000000000000007171414414333500211370ustar00rootroot00000000000000PNG  IHDRd{bKGD pHYs  tIME 08tEXtCommentCreated with The GIMPd%n3IDATX?JAƿ7C&M%A X[i,bFCAPl<d- 6κ̌`'9qyKtSNP%+;:|Wݤ=-eN:ᦿF*wV];A)Xܕ!t @:!3&< a! C`9 ! y4xGʂWwBXBC$'ICDBH?0 4[hF[ S j$3l*r=?>.N9E,P X?<]~%UI!EIENDB`vdr-plugin-live-3.1.3/live/img/button_yellow.png000066400000000000000000000006331414414333500216750ustar00rootroot00000000000000PNG  IHDRd{bKGD pHYs  tIME 3WV<(IDATX=JAgޝ]HF ~|4xk[hAA(H*B+aezU?3xq7䀰`5R^LRM1?q]D@fé bquML(OM(L7A q?Ds1B+ǻ' i}7q8ȿ l/RF4@JMH i4\P6+nqjK"dGXx27g1^X A@Bp*WG/7IENDB`vdr-plugin-live-3.1.3/live/img/close.png000066400000000000000000000010771414414333500200770ustar00rootroot00000000000000PNG  IHDRaIDAT8œ?`O}1b1F "P(Ԃࣕ"tn"nqEʛRp,b :34Pjg\\~q]^vݧtm4M{<C^.{~/~劉DeYnbqljFdg,ò,8Id 0p8L.r0N?&WmCuB8NAҎ.r`ƛ;CUDZA˲!s<{-GXW//Op?Q/x弅VvIENDB`vdr-plugin-live-3.1.3/live/img/close_red.png000066400000000000000000000011131414414333500207200ustar00rootroot00000000000000PNG  IHDRaIDAT8œMHq3_wfwY md]+1қ{X j%:u`]NAx+ƒDwA$$(,AnPCxyy^s]v]BT >ԑdr;F͓Zei!`'I"\ Kccr ]w0~(8]ڪx A0;{[Y!΄!mbè0ǗlU0::ٙ%-u\ǐdH@BץM2)NLȏ XUmlpն.iǡ YMMm;$u]XHIymxJ=[_ix"n@4/N,Wgg~avrUOմQgy_σ={\>&76< >Ni?'9׉PBIENDB`vdr-plugin-live-3.1.3/live/img/del.png000066400000000000000000000015001414414333500175250ustar00rootroot00000000000000PNG  IHDRaIDAT8eMh\Uޤ3/34Q&Ij;IjшcPh %  Zp;Ett)EAH+mDd޼x3޻jZsGpG+Kͧ2S*$Nhj%l.gy\73}3"aR$"W.%Wdz>6 8A>;Q;PYF6:1I&^=u" '  GіE% m ~ءwPYf4O/j ‹/Py3X$\^9r7 .GQ|uƷM4Jo =41&Z<jfHLJ#ӯL7̉,#Rj*"4y; NJ IENDB`vdr-plugin-live-3.1.3/live/img/edit.png000066400000000000000000000014511414414333500177130ustar00rootroot00000000000000PNG  IHDRasBIT|d pHYs(8tEXtSoftwarewww.inkscape.org<IDAT8eMhUGof=\M4Ƙ( E]X& Fԥt_থ4 DAtSPB)j[9gsq6wU-M{T*<~N3V{e0YZӴvY"qM«-2VL,pQ]ҳrP=̃kAsPB&p ow@LwB*c;7a:}rn {NNWpO|LIeY 7t8uZ^F@48ơJv%pG0=øMǩ;BryB9`Gs3&mSwQ W^? ?CK j@{,% ϶%PGE: "DkQd*!X7*"֪ƈOnI.n9 $`3Bʠp%K8t _@_ADGO\*H.r"Y܊>nv q2_P,!I%A=PG~+'.F.i(GE$Ll0::JFcOF-feZΟa*fy _J &#;pIENDB`vdr-plugin-live-3.1.3/live/img/english.png000066400000000000000000000014101414414333500204120ustar00rootroot00000000000000PNG  IHDRh6sRGBIDAT(oSeyCW15H ,.jh!dn 11 o'41 #o& XoDtle>ڳ}Cmmҙp8w(L50 $]oq?3CYM{D n}W߻تnt힋m\]^hy닿]m$t'cJ33DNwetFs @^J Ns .s:Riڣol^7l:5HtdZ'63U}L)?P{ht/ݸ+ @$6fyDe1jիכAeӉ?xo8짟k%R~p2 " A>]Z2׬߲zdMl "T$~@CϽNξxv75z8A$ ""(!0 "T\v>&'=XHw@ VW/}()eƊ_}՗tX."` SbBQC r׉87<0i@T\PG o_x=01rFje/6*3r698943S7GFUL@NK!O{RP2GMYOcLD(MiS<V<RKWp(Sq=SjWlXjUS Uu/WnWLWNXLdY_:XqK[e%]nZTZNldCo]\]WaJ]RTab`_>btes gvlElE6h,i"m|v0mmw?y;]tfy>QtrstFxxu:x)x~M~RPLMK}YuU#\vgo F#]SÓS Mc UŝDRơIȢHdȠeǧRĩWƩ\ǭZȱZ԰g˴^̶a˸`˸b̹cмj׸{ں|Ӝӝ%-2',0(m4&" 5oJ<=[h{8t}k\f# $XAV_ 3LW] )6FyDPIi!~S*;z>ap.G`1+fK:Qusjbde/rMcYZ|RlTnHNE^vBO@9qxwC 7Ug??( @<N rV o%[H x !r ]alo$j,la z+f/{$}%i) }&&&/,q!30<+/h,p0-91 (0015y(;-'O+DA,G<2:N0Et:n8!/673Is@ =C4L|56;C61@>G86#;S<.>TF>1M@U&@SF@IO0BMX>Me=M|F'L?QGMC:@>GZJBV`N*AHICBG7N%J\&KVG=FR?VJV{HQP/=PON9JGHQK*PZtX&Qf_PjRh_ U^7S[RO`BQ_NSSnRBPESETh%TgVm\"U>cjQeVYWXp]8[;SaZq\m[us\Po$^gc1XSc,_wf\_^F`?B`b`]R^Xax*boIgTd`e;bmm(5fdc|d}\jO#gocOr'dWm=k@g i~Jl^hNei`i|y0jtlF jkjh[lCqdlqD_q]Ui} om^pphrVyBpvfxYyJbvu;xDQIX{.yyeChNMO<Rxnʘ7N:eǛ?Y˜]j͠EkTe˥NƥQȨGţZZéQĥwǬ`ɯX֮_nδ]ʵf̹^в{λ`ܹrֹxf׸Կpm۾}Ŋnj˂tΘ۠9gWP4KPgvFPc|PWdWKWacjƥS* Ct*FRCu";b@ui'~[[ &_yکM #_yʸBMM-\>\ө8}l/??? _vdr-plugin-live-3.1.3/live/img/ffw.png000066400000000000000000000015241414414333500175510ustar00rootroot00000000000000PNG  IHDRasBIT|d pHYsBtEXtSoftwarewww.inkscape.org<IDAT8mS=hSQyILZ4T4RHUAut.](A"jԊJ)6E^jMk^{ywChm;XTS_(XJ,ch۱ֲ@P$a楔')%d2T*B\ׅ8X,ޞ+$ kτl+b-hQ*$cu,?sg&y^V Z Z q.@KK Rt{kt}W677x,zؽX ssI>uAH}(0?0H$ދժ{Hk#qFLO3qCV/iR,B//_hԠM^%9SHP A8S]k_˅x2^e W C1@/{!9۷hӧtuU@)9g"zz xR*P_߱ |?+=jlˉvyy#r2z7nd2qm΍Йlq,fzcɄ9v(J)g>LJ FB fnڛCDDt@hQ"Vㅘ# :qW,.C7Gn636!^#h d3sFOrՍ×@j[?`qebh)X(”nkHb{i-Xpkb"Q*"6㩰:o] [6ΊMM%S( -&v7yΊԿ~7 '$?IENDB`vdr-plugin-live-3.1.3/live/img/folder_open.png000066400000000000000000000013721414414333500212640ustar00rootroot00000000000000PNG  IHDRaIDAT8˕kY7ɛIЉUQJ]Xu6]  Jgnf1Tp%\(URmBҴZǦIӦG.AP8<}',:Y,^rjok޼>{p>yrܹ6d96<41_"l#.K~}چG1T*u!6d>KT[mۼR]\pǛ [W4ׂ?~HܛiA`"ZL$36ftt䡴cHGRH'`ޫ6qOSP`ǃ/)vHgWgZ((&@\Rɼ{ /dEK\jV7܃Uz*вUOڄ4I l6?PCh@k2;*ᅯTsk]/՞ni^[tĉ65#O_W<ۯ$ÝK,/n[5@jH69$bĬ8IRX-n%@.~&E]pxlZƙPF7jpտFؒ2Heٮ'=y+g/_ڿf}_dsj"K?!-7t@i ZxՇ:M}e6VCFB~-гcJj]+kD('hL}(8"iF|mh&,Lp:3DFZ0 aX: WG7+ ! 0 b4=2zjxCmo:| qC>7K'm 86 ҖQN>.6y;G4(۝Η+K 3V:֏dݍjk,铩T0w $]|~F[drGۻ{s|" I$ "mZ[6qY<9?X!NOmIENDB`vdr-plugin-live-3.1.3/live/img/hd.png000066400000000000000000000027541414414333500173700ustar00rootroot00000000000000PNG  IHDRF(CЌgAMA a cHRMz&u0`:pQ<bKGD pHYsodtIME  SIDAThoUU߽ЖGTh->F5qbFG??q`H hL|! T[ ؇}n=޳-0w[{Sٺu4z\@LڴȽqD_+ЋJʵ]%s4iLb0#7pA/ xU(`[r۲YRqaRwc; Y[#fOInGr=fIJ#5،op;b6;K:6G\bs aUX3i! Sqe"1o0w% iڊ~tE0K[yb֣"V\@́mϵ'7x]//:߭A2^ j2T^hOBƈ]O5iR)p ֈK*夺C ۞h6|On9f5E8K8T&9b#n*1}'؝\&hjMwSq&{fQ(H}x#5=A2Za1^[bzėh]†-eK8_s[|AXޒJ+4w5昕R`Wnݸ*ᾲĬ&.?].kBS껌FbRgi>0Ԃ/ Geu48kÿ%Z#g;ubjlvE:pPTԄ#"D]:"T >*OF9bgK-Mv`NLPPŰ ':R*1CvG̴ e60]_JtGK8-0hf 7F_%|qgvL։/ۇ0Z‘N?%;cPJ {T,SlkR~īaR$CgV )XD[`EZ~9,,TR~B0!D9UfZU7 G%E#14A=R z0*,R#SQ QU:E?Ύ/uAO]{D:!Z ݀^w,}ra89 O%kifzRJ޻Gv.P&;65p^B/yk%J.:`v%! tZuIuGIͥ"j}NI 65:/wG183h1 Di8z>Sx4k妮|MN>,`XWɗL80u(n`IVUcc_I %qk߮bE&3} UmV): b?~oW 2Lw^hPuݐPfF9.И!1$TD༎GM^7Tg&IpY*>PgN ^J!Nhۋ7)Mrۋ$_ lIGq2obEr51_oNAlIENDB`vdr-plugin-live-3.1.3/live/img/imdb.png000066400000000000000000000016761414414333500177120ustar00rootroot00000000000000PNG  IHDRabKGD pHYs  tIME )elKIDAT8moSusshvm)ܧı!.#!1~dWx%\w&^x 71xED ͸Vڍڞ~JC}ܼP]>ƫzvJDPZ+vucӯ>WWo,ۥ!lR7k?˓~Zz)x{*9/6DPB]TXO7 .}]a"[Y [Tks5J_e |^o% p{4Fh6 Ja( [f"GH(V[i*N44Ճi=][QNtщʀ-DB[rdLlrEx!tO]xkrWQeM; XNb_czmCl#}2=򻹲"MAkcşY3{ڽL#|s6B8TEUmD[ +$sI$IŴm\2S+{M7'8vNYH)­Gn.`?,$ >2[Jx(Ca:mU?Vy}V|6~ ^͍*( .ev|,Y|JP}=gN)d(.խ +.`4-8&dsΏ2@˪&Kd*3@ N?apPG0ãzwڵO#})FqEF4`p_OW x2oIϹuޫU23@x {IENDB`vdr-plugin-live-3.1.3/live/img/inactive.png000066400000000000000000000014451414414333500205730ustar00rootroot00000000000000PNG  IHDRaIDAT8]Mk3Usd#i MH.$v#TTYpP\A]LR(J] Y*TlJ&M2̸Y} 7F-)euRsch+˲Wvz4MGrzzj*|Ƕm1F#󻻻<2 dbl6NZ}R 2>\.>ۜ1Q1^`'IS_A1b,8Zk1ih4$ ^0 J<!Zk1˲(lffg!뾣Q1DQȬe'a/->RVu5˲_oޤIENDB`vdr-plugin-live-3.1.3/live/img/info-win-b-l.png000066400000000000000000000015141414414333500211640ustar00rootroot00000000000000PNG  IHDR0bKGD pHYs  tIME8 PIDATxN00@/jM-0Cb0C+JZ%Y8#qˬ%\%Nr6${.So>e |I>S_w*{o @;3CYGusX{9dp h =2؏$9f>пr_d9,} d`|X_!z-}ui-sŁD0r@z@:D0f% ۪^/PLeM p,]I>\gpC8sr<R~NYn2S`G/]+PG@VHu?2_'0#P9vtr@ @9>`N``_]+,50p:0*{7j87Z=T+F8>KQ1`ʺbq_ >+OuP`ڦm%Pk#Pߵ;koRUs7^vJm}/kȞ+M y[h'IENDB`vdr-plugin-live-3.1.3/live/img/info-win-b-r.png000066400000000000000000000007221414414333500211720ustar00rootroot00000000000000PNG  IHDRXbKGD pHYs  tIME8 W_IDATHǽ[r0 E_UwmB_AbG(Qh#])b{F.;pN|>o;7`3/5~ހ,~V`zJ,(2& w7eY*AgjɌ:'}M*4IVI*dU]e(juؒp:D -V`nqe+ R,N I h`M(hs㾈\-=_O[ ]8޸jpZ.%cѴ8:odcYns0ed'[ˍbMP:(PԓIdzN]8ҷy+_1"%0IENDB`vdr-plugin-live-3.1.3/live/img/info-win-m-l.png000066400000000000000000000002641414414333500212000ustar00rootroot00000000000000PNG  IHDRUbKGD pHYs  tIME"NAIDATh @9 $۟e\ZD;0m<_Xb;|9$I$I$I$I!K IENDB`vdr-plugin-live-3.1.3/live/img/info-win-m-r.png000066400000000000000000000002601414414333500212020ustar00rootroot00000000000000PNG  IHDR[bKGD pHYs  tIME#C=IDAT]ʻ @ѷeO`C5ȁa̜#NXa6tlp,}osF"IENDB`vdr-plugin-live-3.1.3/live/img/info-win-t-l.png000066400000000000000000000021531414414333500212060ustar00rootroot00000000000000PNG  IHDR%4bKGD pHYs  tIME8 PIDATx͊UTdFwDBt A,E$ ᵘ==S.TOϯv灏sře}%w88~c1\C~s}{:ˎ}.WxCrɾDHO~o8` tuAMC:AnRe~r1(ܟ8Ϟp@Y4k{ݙ'p>ϼPfUcY\ tp熙K^l?]VI M500"cܿ h.:uvAPWPt@ us@$'M@;^`Z꾆6I6m8̬H1&yo$7]ڎ\mg$}G8Q80em(v 8ζKv ɯ寿iB5 ʿcN2gO<Η_$Y9M8@hu@"pGIr C6(Z$z$SzSֆvX5Q|ُ95Jޮ'`,Bɽ{@[GI8Xi844Ob,Į 5p,AS% tIrzqzk%I~y.+|K oqz7 l N` . .s`n9``w,@v/,?\.p1uIENDB`vdr-plugin-live-3.1.3/live/img/info-win-t-r.png000066400000000000000000000013141414414333500212120ustar00rootroot00000000000000PNG  IHDR%\fbKGD pHYs  tIME8 PYIDATHǽnAwv'h)y*^# 5")H%) HA`L&{wvIN7.f2 lYD}SfľY]l(6޵ٕؿR; ]+U5Y)VSue"Or}#ȴ&pl VG }xS`&e4N5wwxD\@4dI7۪n9:> ٛO=gw/,ʺjm |!qSl٥B{>_OaY)XJm{غni8IQʪnSvT,}m̉FC/|HM~m36Wef^rc7tUhK1Yź)JAhPpTy~7RTCz|1B{ڑ!%&ȑp!lm3R扑$5#I)o{,nT/"&('6a}N "sD8r'(6 >'CV?',(ŨIENDB`vdr-plugin-live-3.1.3/live/img/logo.png000066400000000000000000000333301414414333500177270ustar00rootroot00000000000000PNG  IHDRF5Sh IDATxսwE?\<ɓg`IDAQ( nP^]wu]1qEqs\*APr030äS33`P޷?=sfߧlK`C"$BIxF2jzhB4 h+HsG]tҒ==jJ"-r*az OP'~&s 1$TX*QMj+r2Ёk\@Fq&wu79 t B"S"[*.h6DAޚ/= _,MVl_̐.W}1gQ J,oQ׸ vIٳ?-`g[0d)W>2 Dž_(,?Y&1um΂_,9nb1Qlq!$+wTztvHD W+;e9&FfZWbFH=?HLjaBjAcaC NE$nUlںcPޑ D,)dC]/Y-HJL0TYfm`: mB,n1[Ҏ쮡ڭyIyokASl 0v xWP3,Gv!P?GP8ԲS` @v e>t Jie\q!]kF4a dFPJv#X| 24P>̵q-!7$2ʱah8}\wlcVsۈ_~+jS t>#"#AZ%k %ɸƨօ׀i7Y~ 2yYbdaMЦ@XB6FeX먄Hmrj-h Ĺcd46A֭]hq Ǘ!$y'>w7˶""؍?kqlGCigw}>;U:٬WC˗Ս8B<.ق,TMG-m[ d12"c&"c!)Jj$({{Gq^D`2beM\f:1ntQ ݻ:HLz1-!" 22DΑ131Vd2BP,4z۷74w szhiAئij\yUܑ'DFFזאJ;gLwXeR^|El{ecD좯Zih<Üig\5IQ=APUV1籉gmb9Ä_۪I&KhIy9%#t3)f$R ef1>o$_}䙧2yئa< pvUG_yFrhZ1*E Ssꅉ-dsd (GUUy0xHQ lǓQ_dfWUdɚDP(p1oŊ=BOV_Ơ#BGizBJfgc=<{6[*hWVM +ѿ4}vtֱ7Ve‚[9ixRx2I!)K*JvκC>W_h&)/'kOL{,+]O DS(!PJ*ؐ1%;DΓ'',\];nDDqG_֨ÊT¸Y,{^ؗNjWd|]sc]0gC@,cIva+܏B2vFi+<笱b@` D5Zk5YNeÆ +V>|x$O F}r˟9[͌3Nx?:h2cҸx¢`N?Z\iB4B&]BCޥU}#u{}<HDvJ&D `o}Ѱ囍[KKv+QsZD7O8̒*a\@y`K#4ʕ+BuO7]bE۞sӦMAK&RXMm @HD |m*/+>LPB`("#$!dw}mq%4VZKOhRRRR*jlɂR>6#+$%%$ҟy'MRRHHt()0)[ .|^4m͛7o>s]3f\ÏfinnWO8OJ$j2MH7|c QmbiR4)(CITbgRFBR0c)R2*dҎmzwo(A&RD~}>U'7DdTֹo}x9AmvK r.6+f_r$duqcR&oȧ\jg}hx'lbn;vVgRQ4oϤt M:H [w8 Hzq?_MI*m;Aܚ 6?|H C4]NSZ,q(}^-}?7ۡ/./-5F:#Z,^ủ'e4] FZj[^~~kxڵmqɦd(:ݾεa!CdNZaCd"R)cT“BʤŒn<UwnQ`"HyY#Yt@4*ݾIGcSKN\rRpf3leO>jס?Ee9h1rb&KtZ5+b.kxڤLDa,gW/ْLy3hW@UWW?SuuuQ9hhz~|\_9Ta';旖xɇo.O⚄d1QA8ԳfӆC(U:hP0:,i启1B:A_[[;sLؿvbdlO2MLFkd2L&- /nnܹdr]diڴAZ1ghمuS94hqc[ϤN6m޳Y~5W_B GNF =א}qSvMeM嘫KF@,ټ g۷#f -ùfk MH$.|ZCS(|o +*.:{oѨ]60$BnK/_tq|h$RCDbqq9)=;k]|VqA H$.'t 3?vMkH]q#_ϳO_ 2X9V̮m*.=yyy>2-D1xc؇~'N}@ PTT ,F ϋFMM`4 =r؀7o2y+rg;KO8"aQxܸS-ۣmV VaOIѐ]@`;RJ\%XxOsrPa* INizO/6>ڒ\͖ʊ d&*8;s_;\ßzWǣnYy}PZǀdDK\Qg9c6ίPq,3wܥ_ݧwkV͜y'ACdҚgpB+czM|-fZƌY`AKKˆ |ӧ:Vf!նREwa)ݧGEcTJ%jgVɐ!"@]k!n pفdI?~ϧ@+۶ǣ/]8m| -n絴7M<9w*``AA_zɧ}pА3VSt#=㪩-`2p6d2dbeee?lA#٣B\ڂ~- zl'ҧw;U0>;oѣ9]kgB`̡/W$**:]UsVJ )%J)U$B%&L lذܦIDATjܹ6mjE0I7 NRgu֍ץ\'kqeHa;쿻^ySN< @ná9s5gV"YM.|?]N3ɞ`Rf?#] }@Gk!2زK.={_̒0nQf~"iڳΓc }uu{`ӦM˖-kS?|j0m݀2tOI&Um1 C`,>bm+`Xt 2NcҲޓ'?9<!cq'GQQQp&t>@g%NT! "!~ ra]߾Cnjupvi' : ƞAv Gcʲ;>,SxO*/?y,G$ WfLJڜ$ﬔLڶ .^| .kjnz+K#Jڶ ӐcV*&qRc- w3ѣرc#?7 lPvk7l}MUrcG?<}IOPZ ! ;}]{#W0lh첳wBÐ=)HM Zzv|c;!`Z"h5]{Qnj:Ќf5[i@xID<;O\;!yEWyRC~Ƕ-ƑB-pf;~w|tæ-˗XreuM-l^22Ern2LO37Lpre!ܳ7CD1mDkEx著jk$K^;ImOXp]O4lx]MO.>7OoHXHB[6<^PPwC_?b0nLncޟ':{va3!΁Yҟ0 EJmݶq݆],8fAg 'N<%2: 3MH*~K?Z4fb|9FkcAAO^w龿~F>[gn;\ XeEh],00z|?R9όRۓW߭uzn䬝Njb< n~}ʿX\wJRq lJgCgY;VbYPM[փl?t XM`ݩ+{Cm1pAAmȠ3O(P{dcs̛1%! wlG%/8>5xmc$ lD]G^{>=%E|; l"FĊ*Flݺ~=>MV(~9˟q(q {4o|Uod 4[IgvI' YP6l0`t&;p J{fGM\.՝ @A%`[%w])kFŽAAa>>>MI1ae+8.& :|9"{P o7p{ߴp wȌY|kr(($vՃ(@Cgؕi  t~L>ŭ46M^N֧0XlГs>زs $XQ&KEy2٭Tw5˂Gw7,3V2l'۠>d q]u2tۭ){`9jpn^|òv0%5MB@ha У<jDc-6@@gP( __MH ^l^T)\~6%K?aÇ%.q,`0v[ih#m/e`6!; I@+X=+\ͱ?Uuy0)8!XN Y[wԉE%]F#[(z@8E$ap猕%~;奠4 %s~~=Ǖx3 6 `k(.W2<)<@wFC6+~8qRpaƪ4U>1#<ԻGYc<;QfoSӭ}Â3o}{ۗw| BÏ=t oKuvRnjAb9ߜ2~z9 q޼qS*>r|@2 $p@I0vܶc^ : v5(eoi֑`H0B^*hϪ{xhȓ4a#oaO|EFAۜ?kxuK}w?/殈KvD-۟k^ N뮿[.ׯ? t}^|Pj 52rg_cЪn!px,d79%ƳufI@H6r64[b{WH{|w]}8~q:93-2A2>?(Q -V/|qKg!o /k{ʉ*es[0嶛?P|ҸK7KXxenrM5^t(fv)ْ=]mxio޸P9T,ZDo1Y`ÏyТ b6Mi3#qFd9SiHst2Lpji-B)@Ңĸ*QLlٵ*q?q'..)3ikK;}.ʍ E*;nD/1 /qSY"td8tܻȤ]T|c?lAHfN "@o>zɍng)Ӧ6p``0_o?%[ZiZSRhx|6vqmdA-m5 JKK%Ɵп[ouɦM"Քs-}},PzԉBj3/޴)%1Z.E4|L7P4"09N#F7qcGi%""qo}URJ:(vJm71Foy.8>/Dk^N>Dιm27ZqIENDB`vdr-plugin-live-3.1.3/live/img/logo_login.png000066400000000000000000001011561414414333500211210ustar00rootroot00000000000000PNG  IHDR,xwXw IDATx}we̜z{z'tjAAH*~ꧻbֆe*X@V\z K(I(I&{Ι}cfΙSMo8̝3W$u q–4@A\]*g:?Bq=Zu22P0mPJQRP"h8L0ZLaA)V!n}$<HǥwP;v8pr" @pXzVUT%Lz+=X"W l/A(TS3-/kn.?x8:3Ae@))2i,&$uR*(Hw"" CJ]sU`@@Ykof,7LGb W1URi(R.Ҋ7(4IVA("A8MUיleФ('ˊ͸-o`v1Qj',[tcb"}$Q=l]{h_o0o*%_e\4 B͑0ƚa%Ct~L Ej&KuF:6A=] g}2i[Ej]i^8Pڀ =z.ۮXCL+R@k:zYcN(/Bac8]Pq4#XxaEtv[-O;g$=gʿ5Ԉ#%q2d3`jM6ngx0V&Щ; 4~Fg1[$;= NkPe@(l!EHˍƱZpx%'ajz" @Ta eu 0* ,lHdJndm* h9OyE4;~팓B쾎71rCF[JD ZX+;KP&9pZq&@rԡjW{E2{i'Ȅ4ji lU-~Я IU9hXGU:_OuvS4e+lY!ATVHTݛ7}lG?j8 "P獖IܬlJڸ`޾W^?*t.0/"do&AHJ`bԋ5inD\%W^w ;hƞ?P@Xru[)Lij#ljjFM߸n;CfT*Q;_;NEd"AE ka~k2WDI&J-X9s<wm?Sڌ%:f!ٕ? ּn"1NuR/9* X)hw1[Y* _R=ʄZ.]!a:llj0UOv\ ̖YD'>AfČ, أW_?)K EՃo;o_H+WcՁ'Ĕ2 Z&΃ STvj< ToD:DEd7vH(0PqFA Ttcr-fta\{c2&|F8jj޷?esv0qBߎH3]Ü'fdS]jbmªљEv3s՚%f9u7?eq/䝊D[ /eLYXRu:L @힡8!iH# ʨ"庥J.G4>QQ,L,7ν5U L8P5vBә0PJzW?;w]EB1ZI[!&zBGUA I&Lfqu/9`_bex%u*zRes}5R͘^5JK8rJDX6T5T{z]XIYN&uU>@_٥dB4Fn)Ӱƛv1ª$"LHk(,_i/WP)kA[Q/Q ,O  W^3xs߅6xqb~/ԂCPlʌzFuҧ<#f G#z) 6S NV&L'5S 0CD& f,K vAck5(Iv V-;AB8qy?oFWut ut Q^\u 9qD0k2F|ZKL2 a*dGzށ5'QVuaw0EP, T-lCkuPG _ oqmpi?\Gǔk*D򁆪T2 xVxtގ;kV ] T@Ⱥ+*TV.Ă@'鬊b dլnG# QaYb6%2b,c-d%4J1k TКGʲufGe1H;H1"e `Ɲ hx7ߩT (Z#Ҏ%"T" )er5kR1æ߰#e}J)T!̔DUghp0M[ܷ^8/ݹ YbJ<} x4B)Y @;NPyBq9+lTFfa[{悎.(߿b˛\{m Ai˖4C5i('"dtW.DUã XrZ/rNs31g(Os 3VHPh"""Ld[R8:jFMo׍ ک+ZQbXϙ%*dTMزb[PQ+tm-#b &âZ"Rj1?_?\n2vݽܵ-)gTz -g)@);P8- T6A;bUR#|3 (c-I-Q:"T_1hA.#-W%//s3j=}T|P, 2Ϋk 00rsR;:򎶌 4KDRWUĪ \XW~_ ё՞?N4bLG8AF9*CCx͕/_6,;Mw ~wk6CcAf-յqs sv _pNى+ub.Q *U1&mݚRʞ[Z Oym}nA39=bv )˞(@yOj[.dE?߳U1+֞[6IĖu(vMELs7vvLw#"`j?P`X3>>yj ͐pN<7ݰ;7)-wDƛ[M_ qTA'Vn 1C;7Z^MX@ߵf}+OʞRb",0+1(Y{a; b*FzJ:C3h&4'3gr 0@m@]*ie- t%{Vrft^*-Js6.4՘tC3,C_Xw_3?s{O?&piM6(dN9a+YmcTAeE#87^Ox/>| r%QMuo!a{6 E m7[lIZ{dR/oEG,Vaa"\;9$af&fJyl${ԲY=m{m<~{M*0#EN/@td c,MԚX-=T"3EHXD8:8gn[cCo8}w+ARXda S :4+C8m} bCRPsS'SWt uuw1s "2:}*EʎLoY@3ccw ?mP.҈@NEt YڇCKM:}**0=~#Î 9ץpiS'<_ vs_>>|.>"'= Qn[{{{g͚>)'goxiàftdxlt$ȢBEQJ-\3}4ϫ(J{gD 1qtݷo֎FK@PaTJGQyrИPeVD"{|-Ogx+eLlNs0(]T_ۗtA]ݹ~/}l;\g(jAv4vU'GX{%ȡ-C8i=Bϛ[XKN`ǭ%d-%R|;cm댢\CA$-EըGL!Re_1/:J%S]iM]С RgH<s,9btUZƽD-[)Ρ׽u.\844DD-zٲeW\q?qc̹{u]]]ܮ!CwWo;ke4|6q`C!os:z_ArZ)eqiR 2 Mb0 X;EK30{šUYK}w}0uwG3N7HH g(:O p];. *JY)NF Xr_<˲fdI2[qub kSv"S*e}=ikkiu n۶}o?=Ahzea/4DסqTt-OC("DLϢ}Je36cSkK7 tNStvg mo=wxJwhEe,ƽYH jVT(oT6?mgM{[V6y!.S̳& Tiib|k^ *Ǥ 1Y) ^}3gTJرa[ҥKDZ?G>8wp=GeR1}M` ("ڛN? +oHKb̭LM!Yl%IaىqE  x IDAT,@KAq& +{-ꙷ*ek(΢gKɿ}_=嵏. Ќ'CMVZaP!P/D]dsC9ЇDaH shBaΜ97nnSNmmm,;° i-ۂ֦*4.io8r@m 2 !!ff0bqQm&(w ,{ws s)-vPʎ^E$?Y"1-*ɄT8: Ijy`->Ӹolll7T3raH7oZN?[We]ۚ'f[ cOnRa#"0#PG6Q)kLjɀ xˆ85$Dmm/ c\wnQN!'U- *3G /mܲٗ;dՎv UDUsZ-CUYcio8n!jIa/X:H 3Y2Jxf2%5z˗/\'*2y_׮%PRUKrx]ijwXJ,2A$qZfܒt\ϛ,[7 (w GE>iM00mG*JϾJHxw=d%j+،` S^{3+* (4J@bjN>{gllldd$˥J1w6n׷`l6KHxgGJ ؂t"+)zy־3g1#=K"Fck;M8B,bFkgj +$db^*!kE' K^{UEGIzU71DDN0yۣ[_3>o`q}/hݯ G5i1ZX4M I)7a&;^zǎs1L1fqqVZ~}Rٹӎ g\ .p8q?6䑒O;̽ rͽ'$2!X K4vؐ3wVv) XsP1[6,d)7OHQ*}Ec0EpЖϷ200?/\̸^P,xRtmwYWt5ZcLՉKAͱZT5|Ki&s( %\q-7OqE\ci,)i;٠U ;OUgW'y]{}W#Bd2AH!h-ktׇ~+Wt*RW" DJsQ$a_eN;= 0Z6Pms&+Q@N\n͐<xdjOSQ gܲpk*.d]:͞ f2MV qF)D*!ILPC~Ӗe]}1/:%aKUQmjNChYe.~ =v)YJh%D`&vٴK.`qdbVQD$Nm$q\[_x\82V2 QI;^sN=XlR>AfaN}* L; {{m'/6S]O<頥oyk>Ю >{ pI':2>- ,{ٌN\8f(Oqց0 Rn޺v_cpx?~gmb>xmlCJIyhx+%QQQfsV0+Sm-Ht,$Q'ɗh9?g֎!AW#@*-q$mۅ ߍC?jsfEw_1:W†@jZ%l y~gq'l)jL&pfL Z Btoٲ_w (ӎm-xjCO_ `Of"Gd]=6ljw>#UMx *mGRĺuzznOpOi9Y()/ \ZI͹]nVS[P(L}=,3 "%]L, "!|7}_t] m\1MQ{q2W^g>򞿠vtt!,)qjJi0 ${'wttzBBm/ԇXVg#I@1S@&gP~{S6v1L( =6ש'gd:O5]&II]f&F֭touڢqTӛ"6"d*E nm$I!&4&4ZR9(AT7\=WVTN py,o0 [Qā(Ay 6`}`z9!\zY-Jeo:r ko"cF;Z9Gf#%Y%^2m]jI[%ݣ>@TDEv=^o] ;@MԑHSȆy8/|bFu "dg[ %l1Pn*aED7]߶ 'WR qDϡ¨OOW4Qm,sJ!h 8Xjt' *& vϻ.~bq{ZB g!0Y`gr a.~Q+~:EQB5i(phڵYn')4֯EJblG9Μ9+ɤ]Dp YꪰT`AOl$a&Y{LA6B0N[}+_r(eˎѹsV?'i<[D.j" zLඵ&_ T$V!e&fI$%*:#7uC@Jý!x˖}ygNU6j;bYdqv|#^{ºNI@Vg;ZͿo} |g7n1HRQphe R}j[[n1^V@ܙ!掐RcF7;|q(6I^MyU KP[ucccb 3\Bf jBFbc\àUP{$&{'mi/_c_AFW:>][U++e6p:cΝ7/+`"ȬEQr+BuSN:Z+R&m08OP:~Ȼ#MLHW7Z訥fM!*@NcFfb`I\wץx+ va,.۷5Sb040ֹb.U]g\vBd8 mQ]΅ ȡunKԽ^5VRJ, RQ_TADG#[[W2hp_:%]Df=`0!'*( 2Zٻ*øPvq=$tXfd,zTo|O%7:ZD5d#-34Q 3_e26g0DLVD_,e1Dlqu73r1awt}G0;7GB(8##+GM-bׯ_?>>>>>{B.+$jdR,T/"J!`YD@XPL @3\{>z1HAS{ $9P #FC[`ߞϜ9s*ꛏB6>066e Œy= # G;mY Y)`Pz'fT'W׽5~66NR]9!*˙󭢓$l N i9#c& W[sȗ:Oz0c 8t?{`?heF\Lsg_/4mʕgu jTGk`AQD_!4UwbaɂSҘcո dI@ed= _z̙37v4o~_֗6V*ёJ̅|ӎ9c$.Gys-;_jR$*dҒ V (z2/swrc1bF(ڳ -oq^ ef&YqJn2pa$(n8$emxYTqDb'roPXdSܶŋtLqBaWE)Gkjx eaPDTc Fl0zӤ-RPS[{ no/ݐDZG|Gfrk- 3Z)=ɛ ϥ} S bBQȒ)Q##xˈ/=UQ$ x )mc"OokkpTUVV(I"OG,qʼ?Z`Gǿ+ \em~^|֬YWTv5~ъ!7UDxuk~/ ` u6ܹ?3Xk+_|aki'?Y~`tĂ.5x2" ({4P4J2&$_U`U{'#r%:ث^Ljh - n-=u߾|͙;oyK?ӞR(V2〄peg?x-T*~.}Y_ݙDa ގ#eMCLٳN;aڴiy@mM c!;҈Aa"W_>6͞=WZ THG#%C̞u۶7/Yg?ٳ BwwA,)H8[:>x]P8L* )&A%(Uޏ~Ra8*)Qr[ J($L+aG)I!5IB`B[y$P Ϛ;gngg׮zُ=o/K2UnTJY fd+涗exȂ [dR(*y D^pZ6aQ˨"4EUb&T@@j%Z|7r9+Ƅ& Mb*%LP6oЄ1K\w6|ddmٲT*M(AULVR@ "' %եVյqHP AU cH"q㘹,Qů3X]%Ditx`Le腁=fj7߹8M^=GxܙCã ]]]8GΜ]s|LY!F!e؋KH܃;xaf")dTUOSRt<&NBa)_uwg{{{DnY k~yˆWEDyK%n{Q9Bn5_~vwzrM;{ڝD7Hv!(GKy[N=Q8v#aXa-|3/~ώ8 DA v2B&+*L6B/1BU z"~^v?/xQGt`۶m<;ί}kGuT/ КhQ1wŕ6Sᆹ# "H"0zim?z[0΀'N`M4`  #i]u?oIyws;UuTidܺ曄))HP)l1P M\y,"İJg23)YZ1pS$D-O.7Q^\e9jL$<W+A">؊Õw}`D(뚚|O C"̪EO9V9-b(!r]CMD9"Ґt7;}η~M{)"H%"v %8D!$nטV=w*RD! Xd^!ŪXۈ^x']V)ښiz{SȗЄQH_pYgO2uI&N}[fʉL%GG*rh-/g- ÕJ)aP1aF5QW7q$\D(pılNPÅM [;/ɣf'?}iaГ?'+J2ZW\r}x稔1*>"D,d-"΂ΝT$nYP#_Sb|_ϟOn޼enذN?Si=sLU Ș{4 è\ҳfRZ!P5`CP*֯_s̹+6m\'QzѦdUI"1dll*ZZşnҮ{n*w7wM9NRgއO8m|1Z.<78*@A XXfurʭu?n{vϨ/<\ +ADS)'p=^h#!|)5jYs%!‹N gl"8ٚ*KȾ\w/w_|锣[Z} IJ[ȫөR;$DhgϞn7iO@ѷxĉCAjL:XS(:5xkyr*q k?iR,V[G-a,[k8P36|U*-@&h1)4iGtJ( %@x)%`׷wZ&|U:e$j+C'Tēg/[)6K뽶,`h|oϛ7o}}}y7&4P̋iaZwCD6ާ8o|v֎?׿͛{{{_VZBD$UvX[:G?TJvwӧth?AyWDk3ky, )&Eϭ{9nnnrTjkk{o߾gϞX}Ow<]^ zj K]H@b>O>yGfLhmKs!tCK1LNɪzh'rY_ű㝫JDX Yc)TQ] "V wAOԸtiɀZO~ ZF6| 20EϬ+a HZsDGGKHSd|ǚF񏘔"p;ǴAsΉ&$(N;+FF2b_f0H3)&EǬ86paƌNLlv;wig>;_h[׾H oasLu]5]mmVkM\Gq;NqJfkll9ۢ0qc]BYu)sU\" v|&r 5|.Ju ၁5WV>uN '82ɾ4:xP㟄EP[[[T[x;;x( ZEb2Z@u@ xӉH&:$qvƘJR*b:ٺu]|͉77?E[Ɖʕ>XQKt5kէjVmaE%0ĉ3MXu+Mk>0*S_Ȅ$6AH`IA4`% I!6ytU W, M-Ga Vf'[[u׽p.]fPu AO6 ̞7wƋ2\DA\35I .JbqI5ܹsAZ՞fVH&9fe1R[cvhhx=daki*TIQ[xR굉2uw (A,XW 8qatf}ﻴTDT}+$~1V;_¨P*=ꨃzވXWE~׻VJOFm'&vR*\}yǎzNU?!✄T? ŭcɧ,>kJVYb2g\u̝3+U  T3x.vkàF{l&cv\Tz|LJÛW̪Z'b2&^k\N3qb^v'W*q[ g2/b:dRijlJD[4@*]4buY? bkSNYԼ֫BV- GlJ%b>;HT۶}+nS$t@U[cB`+zphV>%k4Ȫj b0'sI&+].A\֎{Ϟ/0{l(ZEѰW" ]riU+}a Ÿ}1cfp#'ث 0&fRZ$,}z/ /r΢%ӌI3V:~{5qUMBUD61;QY,"!Ҁ$UL VCO^WWG,w^-|sW!V6<W)ƔW_UܑJ94+.~1}đ);wK&MRxIQs4OP֭{t]]c'Nkkc/(@,ʔ˦=ȑ+״vq'|W?믜pR͜5e,0$z&XSWĮj߶pc@lɒu,dMEJ7>aGߞxb۔Tm9ޘc/_у SixZ; JoSN;1 GXl(bDiZ)&NS}q'rmg}5?qƂe[y{01tf~d:)!J_Ėq̑"V"矱.X8cܸ1@_sBG;ͥN!g䎪w!sBtkWRy\!"HTݙ ? fΚ>ZV_œ /U /R(ũ[Ngӓ+?3;VJ0vmIH˻`FΚGHcG=:aȝ~ϝ:&K[c_0ƐՍc7ᨊ\gz۶m۷oߺu͛Rr םsO!2s:=tDcPD2&,QRy1q.Kdܯ;d TÎ _ ls\kKXe"r^Ø0I'Һb[7s?y-ܔ)#jHXսa0,/nH6 UM#$ꐵa;8!u_TeQzT6: >Vd!޿r쿞pDI丮]i;_ݰdr,&ڧ {kܛ~wT*jjv2.饳t5\Gǹqf425wbzgҫ?Ge~|)(wdY˪ؘ2I&^3^>p^`Z#[rۂ/ Tvr'x)'/x+G -r#ڡkmi5gȢw)7:3,$!ÏH&ܙ `Q/X!c[ q9rO5;V5X|>i} 8_˛kVT;^]k~XqXyg۶mw޵k׮]zzz֭[O`-xϜ17T6}T칽B,$ JP8qDښJ~!o9SM|mO(JrYh?FڋdISnآR~d'RҬ٬ͧž|?_H+wᦇ[H-9)Z=s)ԿAE?esj1VtMM~9}HF4jvu'_X邏hni~-)~gq:Ӷ#ʃ bT|e㩜sйuZf9rܳOK\+ɓ&oSwd0V\gO=t5r7;mJ!@Hɸ1r;q]vIҸ1-$LvoLcX{Rm=8XY~XŔ?R٪t$ܯhĪɦS mZ>^!6c=A`Z R8 ޶vؗQUj'}'+uWPْ6ґ0"V k[ IDAToLooFo\uөTS*drL~D " u RBpӓR;Csq#R*DUcܐ +fwx11?W 8pҭJecNwS>'췂A *u8yO6v߯~|MiqT=ߋlCQ:0G,m[V"`hN34CEP,*#GNRU$}\m^#.fu7vƕMh}䃫g66k-R wZvn9R1xc\E7/]d :f2ފfD|jEx/S ͷ>hL&Zjy+b{L(9vő S+2('U(`?;u>Q>u(&Xy +dkl>aJ_2"; L6DTA!=݋=}#(VT% j>%#gOv*-xsS8.o%N_]#/LSB+b$B~NJbE"j (h TI'PV͔hX=-];`E T H"Hn 8JyH_zY4IP H1~98 c# 4.EGTˈh ?)wO7RKG l]6 aC6  +@[dcf0r9(5l"J9;H;wUkkC^6cz=8P]aYpQU] 1gRbs_oL"\}l.c$K(R\4vGXYOT~kCn'6#\ ;?rXʞvauzIv!%9r.x=Q9a}Qq6Ȫ#DO~L#swu /^B( eڌo^FWH ka. O s^ ;B.ajKĩPuC|k?x<p8ͺ,&@8%N7Mk7/3v #j@049sϟ;)Qn+J*} [u7>+-O2势؝Oz`KHJQXdE# `CD@CPEi3B4[TNR. /hDFfg-,s| y ^oierT٠3m.`FnV/nu`zk{9^ssIt+05u`o?=:|2hR'roo\v;qm',4jk:9-yaJini{@Jbz>rv !BAѱ b 6qF hfv%˿roΰ_Lhew+> W%BD Ai+TJ% A*x7@)lhF`"qAS}kK3N:r9\+,^h^;z؏~*(|j\5D\F"$ 6`^xԉ}lN%T\3I2 Y2GLhT'[X `8 x!Rp3@%)Öa*ºr4)953%Yh^8I'ͰDjA)YT( m@‚/3%famI b́Nۨg;\s;߶ ˟YHgaaGI)!eɱ-6ʼni;ԚOE%|lG75?ڦVr/7&"r.7Tnrs.t¸]tbCiW/WGЕuKjV)1)o' |Dg-҇6oq<PucȤjCx)A1U*  22rSw;k>c.DAF~LDl}H 4P*cHͰ]R) aV)AU&X~jcӡqYr'ť]ӊ,SY`4g+^jSc +P6>l̺YwsLSY뛳޳\d F矽@)~@zts7gũճMH 6>e-a"Y#O %D鯠rʼc[[Hc1Ɖ_K8^7=e"T0F.@Ayx\a~V.wᦦ W>mSH&<dQ @eTLL. ^]u[lX0a1ʐkɱ2aimj3c?O6DʪT&Sa0 .Gǻ+Vb4?j-*7̈́?l^@[un"kAr-9ۭnn"9˅-B_ '8E=M,~*F,i -V[RBʰ')Yv_ e%er>̪2`bmA[$gDb:~&t% Y8e*VI1_%$gJкxɢ 'glW|}cn,l]CNdDVBMBV>\p֜#r-i$"L13w<58"|[g?VLOlǣJ6XLq4-DF%Ɨo+[ʝfQsKh>" hbs(id*EGО4di`CX!R.&c|n˯>D#z&q8!C㮽3[zTE{ʟ2%:K*ExJAi3ϘߔxA:vh)βVvogFKD^1Կ=9d%e1CĀa&Yt{éqL^5NJ{㸱#¦>qy?SdamX` 9Z.}mq!uli׭F~ښL(QHdFzxX P#bD  Ba 1HUWi<Ew8">D;4;6~u=wm 4*wEt?ZhCc'Ͽan/3OoRJ˙{x%"4' E 3&ukC(LfHݟ.SjdqpCCL,DP WKIDb9* T5n*냕 yODuW± \d{ ɍplBJɆ6`Z\#=Dx1c`ƶڧ yd!a`e Y RcrR,1= iyr- p ,m{ߝ+>ۇfe6mȤsUXq є=IQD/٬ג}^$$Lk(*fӐa BQ,kLNzLɛH~v茥 &Wm.^9fG0f³>Vv(;o5  b+6!Hw-sΟtﺹ.jۗ\ 6H` WHHܒ\iwt6b@! ^7{ =`-+X@KByINW3&)Lĕ]ܖm{̑3zu X4&=oՌL:wX;hCFKq]iH$:R [ ttL@de$qQjE:RJ)–%*t*cY6i&c v5aCw;] )T4RL q8TOWu>xu|{Ȕa 'ƵgpϾbPd3-:ͩnqY!h-6]Rq&{G+&M 2_7~KjZsymSnBDH3HF3TKŮ۾8}3'OfB\RDD Z;up6NKܧf1Q+<0D8cbjLڲÈ)zB]".<2@H1]LSꭣR fH,]08!sba*V JMT)On53mfeHo\@/r\'mf%8,kz.%`=¥ 1axeߚpK7:^6ݪ3]:եR-=RW ?R: r&Oū^k[ފDJW?4аtK1 We <@6'7nMqCTbOnk^p衇Iw5~IJ@"pyS?o \ (lжd gF!PZ=kܢSN@l.hr8>)J|j  UPb`C1*7;ntG;;G@"6)!lh% ۨ`LIrvȊҾiV)] >Ld!AU-vYM0gAP%{ZrRL&/; 9mEeW=dќ1]qIJs'6qu)7IQ"joKFj/V^ٛ6dglL"?=GwC{c&telKi>ef+S SovM'ӑr\WѥܖK"VܿYϜfI&u2:(hesAP`J5x7e$0BPJ DLFĚHZrS1@4}|n:i "!qfjCOmؿttx+Y2s\%Jfm֔7;W=~Y 5!m'M[i$ǰ/^GQl@bkILNM"pbQYy  %e`{oH@5א?+CfуPfw0 <ǾOS bgEOąkDXC" z >6%ku(+.Lv޾8Ϻ IL˱G->lo6Rs>q#0E;,*F۔K3zL{M9yaf6;^C]9"}66[u݋bJ{HD 0rIfL?gEg̚>Ӯ:}ROwۼf%;My"ܓ UF`À%!IDATSq͛4.x-3"D㤱ɉu>l纍"Ɂ4QM7T r)oi(>  dR×-nm{sB=AP('EFz45R\%23K&f`D֮q\s9k64MߙJ:VJk0R_{׎0DgֻUrDEI q:*n@ pwkǎABX)<ݎ%kf糳! ^^?U{q IENDB`vdr-plugin-live-3.1.3/live/img/menu_line_bg.png000066400000000000000000000002711414414333500214100ustar00rootroot00000000000000PNG  IHDRXtbKGD pHYs  tIME FIDAT] 0 {+ 嚆GU|,8(ȑ(QD}4DQ`,{k 9IENDB`vdr-plugin-live-3.1.3/live/img/minus.png000066400000000000000000000007301414414333500201200ustar00rootroot00000000000000PNG  IHDRaIDAT8Փ?Q{Ο82LFlA$i`e||HBA0),\ e--37{;oR%Kv2\" '۶?Lǯqry6p8nU*0 B@)Q=k4ju[OOQTz.4MR IrjBԣ]9!/<ӓ$pi 1P,xWar|l)%Bja07n8*.o3߫{{xޗWb/&&&D )rHU՘,ˇE9ixuuqcyyy}a[0 C!NGtt]&}QTb~eؖ8z$I###3SSS4BՃP,.&[OvL-$I݃LNɧ7_i64[dc$4;SG\>L_.M_.㸔g} h \^B*Wgg˜Oq+ܽSaw<ϧ\^j2!hZD"7?ŗ=B2zK,~A35;` ݷ uGf_l0QӴd2j}9;d e^Gﺮֻ\\\N+z], !eapi777+읮۶MӦDv5c%~wzz3rttT*}͹ aP(BPɭjm2t:iZ- c#6ϥ(Jnii )x!d~~UUvA@\q3BRʒ(F.#P.mm_J^d2KPJFQ-L4t:DQD*֢("),8WWWWhh4)%L8?0 8P!PUzy TUe0pssC c0E|?瑎UU) ,躎a ChD"AX}|gnnT*lZ]N|||eY߫@ P*bİKnN``eta`a 7t 3|=^{ՋB-dR  Ld>dO,/f_؀wo5g++Рh~1\S(U7R`՛? _g` (ijj^aARI?tfs fxӻO [ļ 030r6O!. ./j'"`_Pk13K= `1__RݽW/1 12()3Hk(2<}aq5 1ֲmz1Hcbc ?~\ȴ^k 2 |e`e/?q㥃130O߁Q.PSzB ^hƒz~dЖ LL   X|}R1hvlI A1.0%D?=P#s@F^⃉{IENDB`vdr-plugin-live-3.1.3/live/img/one_uparrow.png000066400000000000000000000014151414414333500213260ustar00rootroot00000000000000PNG  IHDRagAMA7tEXtSoftwareAdobe ImageReadyqe<IDATxb?% ("Mga.FFFv LQ2 "zF&S٘ݷ6NF> >Ī93J-xS~va246￧70 3\ˠf8( /@A n];@a5/%UTca`e`V T.%ƕSop np5/ =.d&vnv^ ȳd'̰?1=z?n&)MB&k .[@RaMG0|+3jwxR㿓vo^0tQP ap$kWWfc|o>mո́Fm&ӑK&X;\Cc e0ꎢl& ~"<ppru_}Eu`kXxEc _V!K8 znz`7`t D:ˉ.Zsxï y>u)<ڬ[ tRww30SlH !EI錅iuuR2I넌@k~pS鬬,%cLl0fT Ͽn*5H)Kcq nYRyIENDB`vdr-plugin-live-3.1.3/live/img/plus.png000066400000000000000000000007401414414333500177510ustar00rootroot00000000000000PNG  IHDRaIDAT8S=Q= sLP DBRR $.H!+ O),Ї&N3#"̂˖=\.0Q˲;{7p77>z=6W}vJd2P7IDJc|>oh$be~! !_@2H`2`<1vVZr ]]L(iR* 4,dYHtb(r9~fp8 B\ם]Lh4n8 !PUzv! x<~,wg|;m߶m1vZן{7qXEQ|qT*o3vLvIENDB`vdr-plugin-live-3.1.3/live/img/record.png000066400000000000000000000014571414414333500202520ustar00rootroot00000000000000PNG  IHDRasBIT|d pHYsBtEXtSoftwarewww.inkscape.org<IDAT8MkTgΙsI6!`SH ( ɢn%+lqP` v~9]HRJ s_,$olՌѫΥU\<|oµgg_[[M[k}Q$IBzF[)zիBOƘryJ,0:8@# 2gww% ;WZ.˲à`"M9}ssLNMv\Z?~Z;J} &Ð_gbi ߷U-/-1Z %O#đ!K[-3{ Җ06xpx31)6kJ IzI.p7)$ Jm͚qoɓgE)ŸRJ h=P%(V?DCڂ,yQVyq섣8?G",ޮYcc3qg*OB32>^{91p 0B }:)|KuCd*%c,#/3NkVc;XJƨR'WmU[cf|:ڷB)| j6CaMzT!suJ\ƻG'6G'0tLmbb836ʘ$ܢLY( ?‹N\k.He{WuTxV)u&;MG2s_/Κ)&Ѡ RyJ1iףHR82;<|ѱ4c,ZJh SyݱnKBe Bۅ_C`_"{c0ܿh=s_v+Bքaj;$%7@&thitB,͙=i)r{W;g8f@]Wh wxӘūs&49}T'u+~|TcpGH@ Q=5I)INvcDa}5Z)JH'[ZBK^ Y$$JI8I?(O0xIIH^.TTe%8$NN EZ.AU9j4%:U+ϸKu8$An+ o)>4OKSFPQ |Զ*T'\ac{pvNskG-Bʱ]CQKÜQşMV-PU´YY)Ti4RGx_V`0]k`?OPLd*KiT3Är0F @ ?է*.6 \+uI7JH `IA[룣=iA[a7Zt a l,(rdwmKL$J:,qaf:} ISS%mȎT3JT}lQ#H0N2G!0X#$yӛw::oS .Ry_K-w:;yTBPI*J oAEԨIaHZ:Gu=. ?8@=OPAQh"s [eyĶi+Z@ dO NkJÿ x +RAW:̴ P" H#,ى37Ӣ"cQ%D8AJss`vZA*T2;8wVCKo_%Dpeg~ykWfVF @Hgğ74-N`@g$t%χ}pqJuO rSk@XP=D$IJO `u@8Dza)ZBCҒO`9H ! Rqq#=PR!Fq(K %++O!%NЯ:  l6$2Gh[l̷DTHf_Zs\m+g-wuG9nG hum B[ym.M;:WEaTR֡?yx~S%o5 ýgdtoأ++oѿbpfALl~A{mK!; M1 50=̐}~o]ftYXJI [ZR3+zm#x6ۘ|wWЇb )=AKtTF#zm1i|8be';f35uN2۹0[@}[]ul$m^n4[J֔AcgќSG5CMU}Yk/QEemhEes4V6q#qcG _spcE%xV́$4Nd1'N DWzc)\(H%)}6d^z»\;3*PXE BV T(Ab]mũD>@  ԋ3M]"mB@ 8'Ē{Ir9OoVݜ 8TBܜg9?% 2}1pJw1ι"xKWKwGI ,.,uIR=i)ئ`4 ` _{dۊm6qZnJ%LĎA`e+N8# p_qǜmBdtdeG`H#fnR6J@!Ce`g_'-Do@=^p˯$]P@2ZhQ+_]B:5sCH;,씏;)c%KؑRGmY\rA[S+Q$d 1 7a ~*>*W ۾Lֿ/ҽ#>l +;o^$|($c}sF㟽Z[?O\&;ioɔGIܐI =G۽6--[.ڧpyL-t+@'iuQøq9nm}iJ֠qzm BY)JX`V:Ah Ch$=.% 9v۟Y'j,:qBR W #| {;V5-1z;q)*I 7k;,'.w b;JW2z'$Ε|yӍM}$5BW=x[\D̞9ѳH u`8!)A8&}[mJP Ha~p%4Fj8EPVGcxk,3qq :q%IIH3$NI7 OJ252 ,-A!*$S@dgHLTa\+Dq4.]#:"*{*}%89>=\=`XҡAKuMʼnT xKM!Jqiql IWM*GTJFIkDM$ &xuyd 1lՏiv䄤N'}zTF|%<#:轢{LITzh4z08qQnqBi=p$qpg<,d昜Zqji¶׎`m@ PP'$yAi;C)9- %`ْp3~mVb6\F|hYڕXIvu4[>,_VI}AYY~&v,,Ees4VrdGfٳB#m>`4^?υhn?G"Ǚز";(qPA{8?OOeŷe/MTJBŐ H l2IHFE>O4 j$بFIiv% fpx_t]f]@n[{Y#MwqSQ0Ұ[a0 Z}ޙ%Fhʒ䂃ڒ:)F`6RKWhl)ٹ`Qn8vk{m/%mc~cP.(kqĶl(JFNF7+ ]$(w;vK!2͔qYPm l8NX/O,)Ũ$%6$2iFȻ6բpi$%qݚ3/N_"zT(Z--* #~ci23\-iC78HH#$\B J@l >o.TT8`K  jzC A cKiWO6kQV(RW^~k8p4BP"H LY:.B] \c!`"s|MQtJJm ) @d ^ȰM$4bm\MJ-d4Tjİ~LH&ohXot~B;gk-߮/5 Vu)^f.J`ZRI~BIv?f=;rA: ^4cܡ%@ +l[ƝM[.,H"4;;OZ_uG%kQ=jIi{8#VXLmYp-Ԕ$O_ 2IB@Lʒ9w wdQV[W)#POa6 RQX55\LxW0yK8m/6LGdIX =3IiBP#¸% JT Gat ^.=jY|?E2^0d&mL!aGFYH83f| g"qeQJ RI8}1Ol7gu 0Ofa2JpmJ7GQ DIjP2xNp!Q';sx%h\'$On+ T`#6?4nʕ >d+N6,&>"3Kp>LW wZ+f~2򷾺mhK_O[h/5t?A#lS.޳G6?}CqI@d. -p|DuPOR nDBIKm`JK,!`z5RT;P)4@S.%`lJH5A8xF9 +'{Ҭ9nmenBK-Ң (*Z$' m3Tc72X@⺼OYv)x9'Oj{œ*wm$hW HYBARZ)0I򉦭w6]Ѡ (Z@SqDv} .S+yRlvqJ~4d$%ΐZG@  cF1$;R` Ej+ 8@NnjfKePH$98P<Ʀ2PR2m56ƣP1E Y:Dd82% ZJp2N08e$IN w{!O4)| @8$`ce\H צD%{Rsu rh+yݿ_4~u2[掎XqXscZ!?wJ[>H^7}R[EQhvNeʹtc)+Lf<.]u%$"JlvCbN"qձC))ЖRuIp SU$n.Rحj**Ǎi*9=y R:f[}Lv$cq@NE 9KIk.Ƴ?I]OG-/ "Z޲0+A؃I*/4T@JߐcefiYh!k@Zٕ)U$m?UcKVƘ~Xd8{ }a++4 ZD^#Y,{E,{ G vN**#H|^UaV >8">;!a<вZxU2:m=)lw!DlT9Ԁ@Z{* #X}a|L, q_.-p*c)%#u-GR9{)#(nݠtVлo8-S:d澞NHpgac>+mskΣP҃ d3׌ĂRjԶ9!NVCX7g7G>G$A[eBqŸn0VAQ$H4ybKJܣ/B՗=@K=JmbdJl=ZklT>=aɦdW-E/-N1ƵTqՒN\=Q%Mn?<&v*=5#:˷{fl@.?-{)43khhoTֈ\Q2I :A=3ArwtԱM8N$r)=<\nx?SOZN Mym&dx\lwlA؍#jtI+^ohmz䙒Zi%KZՀRi dJa26 I_(X$ ɝ5]2㶰 +EI $x$^=]<ÕquS-q) c $ud ՓWO7lh1)JH*Vqɴ,~uز_TfJd\$#=\J)&~|)y|.$VyZZd`2rI%_~FS[22Wм@$6@"5kb*\I!`mEhﶆ GQo/\/Cq*}C6X JMfSv#ŹT-N8;i +K{J 3$c)ocTOќ;k2sTGkmq_/4+@Rwݣ&]DU%OBl+s'CWuȌS"*C. ߷5g;M=~Ke%lGqƔ.OUp8@ +JK`Ո0p *ZyOP\['Ϫ Ϫ4RMk+m UY\΂ǾBAsqk\X>,?л-寣:걜$INqޒR,2BSN02@U4MvZS9[q]Q!a+N BV'* .dgzu.-S%2JdJߌOAib50b;!Y)m/ XBA' c,VꥲP,x0%NVQpH H@ {G- Lv%Jix Z2#  AN<ֹޔda/6%rweA)jݓD$$@bZ6 I)$Gq Se>맅 Tq 7wyJ?pa>-kKCxH oW5HNs%+c^QHXJ/g>!OZ\CWI9C8dk}5>}N:hXGe.$TA)PP%;2\Z#3"Me,,Y$5(JRSVL`Xi2@J2@"zX 0G矷s^UF_ Dk7&M?IgA%ܺ  }BPZ+5/Ee7-ń#3s5m:@{~I_QYI% %@RG" U Zo)ua!C >!U ɗ_+ZqZqhЖҢDH9$rNr3a_.u2ۊ@u@` =昴b; $aݙJMJXHHnN0rO !u>w=qddOA 7Znv_ a4֖OkyH¸ imiKS Ť %'/$lz* y5XXѶ&s,$ Y=fvh6-Iw=}iSITW$K ɐ \Gr#bjIz꠸uQZ\ijtIPp8ҁ($ x՚H2M_ڳ1f^[[3׏].b=ih p? eFJGy)]5*m@*uKrHܕ!!}Ջn"_h,`@ xRvɳRz}'I|G]!IwreĊnI}s Aʞ{SBA! sr ti]d\ǟx_lL{`HQ(X 3'iν[rb D%i[.e%H8+lIH#Hr/ 2#r~B*tJTl o|nT( OiIsm*#4f{%\z$[4ڏ&)7=Oiz#No_T'pwdҿ}6jIGAA;^n. hVGW.xXіIΧTyXm_aD8XK'd!nRrpuU+/+*ud)sn)!KV;γZai14&"BOO$nVJq`a[O].jOaC#|b=@+ UNOܵc@yJkbRHm?0ω&_% EѢ02p2O3N.-f"[1AZa&_uW˚0,e X;ۋ$ODNH=W[*ccr \.}45-QQ=3~PDKBFGhB^J/7y(3z >\H#ј4ǽmWRnSt:qR ITrC c=nG}hv t'~; _u`o4 Q<,}i}k*/1\oMes:XD~;hag:ў?Y>Q9Tˉ}QpFst&}6իuK!nFqdd7aPUSa[&So@!R#K"JcH)Jk w;P  '\59h)K ੥c$~KET'N#m;㒐HI<$G( {f VVSHS#-˃Yᘆ8+ Jx H9V0 ;`}qv4Ͻ-;*ߝ-En weGr8o>tB pv2ph$FCfmr܊ȒxIm<(dXI#lo&F+F')^x'=16wfdz:vBI?0+\#< ݴꨠ~-,@$M }+N `d $n“O>Atč䵁v!7 \jQ*9=٤-5$-}NxoW%$6@K,d4;+,!gF_ ]6NĐA# V%~OjQ$o@wʜ/-n7,q}xi(JikL% )m%`+>ri.D8 VպVk5B]Ǭh?uUTe_ XlIz!xv1ӣk+?^G;Px>?v̂.'FGj-J wy8<|A~5JqԐkZ8@h[zOeXJyN  p1yQg\b\fu.Gun>`~(M9dqE9A#lctaY5z3n*]Up;H|-JYʊBIRr4>v¶r}%[!| eJ8@ d` ݙJڳFԑBHqZYBбhP;ȥ7m:ۖ5C;~H(QTmݻT*ZTsc0Y'd lW  $&o l]{xe 1+04[8v͞n+'ooyoljq=ɌADhI'$DyI}~ղ<0 ]ojSkz jDaxؠwXTUEWƜE`CsJG^ ZrU@ڑtuj(p9 M:; ky:Itac-.!*$$``owD2Zu%}#~c 醭 c>fJzK}^u4^ =. G|Z{m2̴˔zIZrrGlI:VHK$uq Ыf5GQ(hLg4BTqIT=U/ѫ#HYy@cYQ4i[@?PVW"QUQX). ?j*ǍV7/]>CEyѠn#s}桩)X)ZB @|ܛ%,! !'sX$ J|="CjwK|JO$r V|47/.&8xKeJh/NNR{N7 mp.v-d7!'*P Zy~IV̅o! w)ϼκH0ovۓ1MCmPYQ -])VI9Y >IWsFqeCn2eDZTRI*[y%Cp} T9qhV=m@)S5өmB2Ďn% Oſɩq^GBey0P!L"=b%R]9m:1P.W㰷HC eKqa)p*[KnnQ 'چAEJ HD]GN< ʀK%q>pa\5$p)ds`yBCcX`zz䝪<Nhf6k(+S-){ j5m'SyךE9IVY?8L8N Oj?( ^e -Pi [´O+Α  MmKsYDJq5ZG D`㊋tn(sEEOvslyHK7JP3B/9[` lk kbm$4F '\0O7lT|ϸ@n֪<V@VT'pHv DVVJ>7!#UEYB?. ?l.*0(V[3鸦VS{F^).zًmjnLrŊP W”dzJPoN{$RER:j) e-{+9EYxnw ORUX}z2>'ޘ>k^XRR&ZKEqjN)@v3UD ;TG.h`{o]xV?Q/I6G/ AʔkR4Ѥb4d29VOa4ɯR4;}piʂzu-^':u6ROF!CUwۙm]:]G=7yGuMވŹ045ėH8Фq%icmS/ܔV+dR(+uIHP'ׁVQI]][WUFQVL[[Ə .|6fBb>֘C;wSԵIM#q>~r[0y؊ p%^ףM}0x+>P4sc$%8X4qv% {P>=]q(6߂ KJaͥ҆ 8F1VkI{oU&~r:~iMg`R 9(<\vœ@T *##9Ms-A/% lFݞDϺbؖJ=D`'ƾ/2S S}jh=?/@Tu?.ߚ΍>}> 6Ȥ q]d~T%]D֝!_;4MH5dGJ!*- 8v՚Z~%˰RKHqm)'SrO2`bmJg+[)π"x(KYbr-u결3W7]jW UlOvq綢sM7j-El:z^4O6+Sdu+JmTB ! d(Gxe?%uJ*V9!AsqMSVWYn MO6`[lg><[4Ґ\uec!pUٽG\Ѕq%O 9V:9$PTEjS06֡3,y[<.6# R¸µm8s僂S\+iO K@HeI(\F:+xIWY`gw/8K @$zɃĤxd^#,[ל]|=U^s$ qq몽}A0pY[KNIPڬL~Vp_qPW^q+Ae2jf9uEI=4%m~N;I sc7 kdA} {SmIT AK ^G>B6p@ŵ u*PRRw$C6T-ĈI}ĴHSQR;ڸE+ɻmǏ;ek;p9vgU.蟾p7,y)my[*V1i$|k:Yi @TH #UYP_lv{O4["\CAa$;e V_!Q&nwb3<=s)9UVr>+Jݫڵ3yGV)>e{R4H@={zl}Ni0C{kvifObi}q Cځ@R8` 9ו[IdoJ:d ^W#uwWZG̖lozՖ}r3ݕOtpJ\v9U*>ڸ*ʅ Ӛvæᘖ <c*c2qYQ*>9VVVPvdr-plugin-live-3.1.3/live/img/rounded-box-blue-bl.png000066400000000000000000000010201414414333500225240ustar00rootroot00000000000000PNG  IHDRZĞIDATxMn@㡡mڦT E8DšVU|u&nIb[v띏FH`8ρ _*[$IRŠgu9.?:d$OYU,_ypItqW.X|,f%IY]Yk% Ud=qQOsOx n8;(G',I_*:-`fg#xBboaDIIz cRwyB1piB=L,W$IEbGk;/)L]y@&4v vm9dƒ$ 4i/MbIӝ p%I'<j;K2&SIENDB`vdr-plugin-live-3.1.3/live/img/rounded-box-blue-tr.png000066400000000000000000000005321414414333500225630ustar00rootroot00000000000000PNG  IHDR;mG!IDAT8˭=N@F/^F@C )@8O 1Yq4YB4}vg>d q@& # v}F:`r HOvB{!@.4ćC&@ŐAJ@!PhܭVcu _^=n+:SUsS\^akV/K%V; И 'PɛJ}1 p,@6ԋ\r|{'Ob /{)m4 QPEA :{1IENDB`vdr-plugin-live-3.1.3/live/img/rounded-box-green-bl.png000066400000000000000000000010471414414333500227060ustar00rootroot00000000000000PNG  IHDRZĞbKGDIDATxMNP׎K!$MEH vЌX{@,[jZ1Bb(4P8\j&HG-;;tOB) +@>{IIԨtƳ" 8I:a\D1p.I^3 ]xr|jw,IRZuUބ8:pOxR z8÷Cd eIRRwU/]ʝC`xOzn>IHJ`¸ȃz(rxtz{K?{{nKEjWuƮ1ƮU¸Z[No cIq|.b]g㏮#^ڡnZc6%IÉ<Ë@& qrjM9-1;jI8c5UU+qt $i@g*9}"iI~GŁ WyƍWDDD,5i:IENDB`vdr-plugin-live-3.1.3/live/img/rounded-box-green-mr.png000066400000000000000000000001601414414333500227220ustar00rootroot00000000000000PNG  IHDR8Ea7IDATxm rp 0x .Іgˉ^| eATIENDB`vdr-plugin-live-3.1.3/live/img/rounded-box-green-tl.png000066400000000000000000000007541414414333500227340ustar00rootroot00000000000000PNG  IHDRZĞbKGDIDATxKNP@Ӈmx &ġ!ź!{㵶 *:|M :9K H?}S(I8$I%y~7Ȓ$mc܌oNܒ$7Ϣ߸ 蘔%IRT-C!plA$iolW@V ` {Z6ib an q؎( +s:$s2Oa=-!1%3`I{n\q:$TL:l&㒗u89êrb\^Id*T< !^ oi<(Ix&S/nKC[ 0KɸQ0eXҴ!$i1n{8I]C.+O$I0تqT,I< 'mIENDB`vdr-plugin-live-3.1.3/live/img/rounded-box-green-tr.png000066400000000000000000000005621414414333500227370ustar00rootroot00000000000000PNG  IHDR;mGbKGD'IDATxڭJ@ͦM1T zPT=ī'ABT"]/2]āawg~$q`b|3ZB2F*oHPB΀D@u=(4,p8 UjLZ7bށWM]/ ƽ>+`y>3nX|Z:^~hMq`\\I^K Rf(.g8\,Q*7:J3 8@ 6ԉ=\r|{N|R;DAqXIENDB`vdr-plugin-live-3.1.3/live/img/rounded-box-orange-bl.png000066400000000000000000000010761414414333500230630ustar00rootroot00000000000000PNG  IHDRZĞbKGDvY2 pHYs  tIME (YIDATxOn@@JK-jbͥpJ\kR+ulLFnZl>f7ʨlx 4ӳO_$I*ܫKgo}e$m&**ߋKw] ρ_h3cI6detS8-F dIU, <h7rρCpO>v%IparN>d %I>O#qW;gny$IY9=ZW70onz <0~Ufs cInquZ2λ\ Gt8릋cX\DZt~ qvizL,X,S$I݊sUjaX|'6%IZ3WYJ.=pyZU~jpw3vĒ${n[y-IcW$I emn IENDB`vdr-plugin-live-3.1.3/live/img/rounded-box-orange-br.png000066400000000000000000000006021414414333500230630ustar00rootroot00000000000000PNG  IHDR;mGbKGDWCT pHYs  tIME +IDAT8˭ANAE_,ܸ $7 Nfl&2 Ȍ c%_ڕ#s`A ^|ᏊB\v;J`|NkNrvmwدj{pȁ7`+7eA R ;>u Gx}m1&]rg?$(,2k^'o$ O2`ڈ~Tl!+54/B Ӕ&~;W1&,B{FJIENDB`vdr-plugin-live-3.1.3/live/img/rounded-box-orange-ml.png000066400000000000000000000002561414414333500230750ustar00rootroot00000000000000PNG  IHDRbKGDvY2 pHYs  tIME nw;IDAT8ñ @@G(WnJQ #(K^0`AǺ_=5x|oIENDB`vdr-plugin-live-3.1.3/live/img/rounded-box-orange-mr.png000066400000000000000000000002551414414333500231020ustar00rootroot00000000000000PNG  IHDR8EabKGDvY2 pHYs  tIME*aU:IDATm g_ %?"с8@x6<^뱅%eP wIENDB`vdr-plugin-live-3.1.3/live/img/rounded-box-orange-tl.png000066400000000000000000000010031414414333500230730ustar00rootroot00000000000000PNG  IHDRZĞbKGD pHYs  tIME/IDATxMN@@PiOBIENDB`vdr-plugin-live-3.1.3/live/img/rounded-box-orange-tr.png000066400000000000000000000006101414414333500231040ustar00rootroot00000000000000PNG  IHDR;mGbKGDvY2 pHYs  tIME!.+9IDAT8˭J@nIm7{|b=U| y<%]N861 .0 2VBtEXtComment̖IDATxۿn@HI j`` сAb!%i܄w䆒O:9^>ٗ50×<9^!IzwWa4Xs`OK4StL~,Ql{$I88Ζ01X(&T'Tw $IW8]E7GG $IWu'8O'Tal%ICYVBtEXtComment̖"IDAT8˭1N@E#Ɨhi5PR)^c ͟h1FBLvX[ 0HTCZd"s ãN@?LAXfw(]M Rq4X 1HQr/ ηH8< mR gU E r5pgP̕Uraq ҏ!$pf. v&Nr`.#Qq)wG~)%Ll;AcL4 0HhUBf{}魭D7IENDB`vdr-plugin-live-3.1.3/live/img/rounded-box-redwine-ml.png000066400000000000000000000002651414414333500232570ustar00rootroot00000000000000PNG  IHDRsRGBbKGD pHYs  tIME%5IDAT8 kC0UX,Tv ȿ;NT\10^'"""Z6TR(IENDB`vdr-plugin-live-3.1.3/live/img/rounded-box-redwine-mr.png000066400000000000000000000002641414414333500232640ustar00rootroot00000000000000PNG  IHDR8EasRGBbKGD pHYs  tIME%8ٛ4IDATm ! D$139! Ԍ/> LX;A IENDB`vdr-plugin-live-3.1.3/live/img/rounded-box-redwine-tl.png000066400000000000000000000010701414414333500232610ustar00rootroot00000000000000PNG  IHDRZĞsRGBbKGD pHYs  tIME"_)tEXtComment̖IDATxKNP@ӇE[ĠQcL܁cg=bݐ{㵶 :|M[HtrC H?K(I8_X$ [g?dIV 17]e$IMU4W $IꟊsdcG60Vx?3Ȓ$-xʖ qpLª$1~U-IGmwC<ѝ{4l$qB.ʜ%I[9Dz't&$3uos%Iz?"ۇɸI8yէaL+I7_LUg!wgmϻ2J6䚋Cv$Iko܊7m+ $Iq ejYէg4X Ÿx'/鍗KOi?lU@ռ1T,IҚ2*&Gw@IENDB`vdr-plugin-live-3.1.3/live/img/rounded-box-redwine-tr.png000066400000000000000000000006701414414333500232740ustar00rootroot00000000000000PNG  IHDR;mGsRGBbKGD pHYs  tIME"_)tEXtComment̖$IDAT8˭J@͚ͦh 9x/oCx(*zeVK80ݙS$ P@FK T`, OרB[!G@&A6RC:A WjLZvRށU]/ƽ>+`9).ˏ_Q97>:-qG0 ?r٣䊃0j`22}#-܌&ej!WηWwbBh| Ҕ8DAqX\\c;,)E1czM^=F7205jw!x6JŁ?P,agfJ)|l;Rbr5<{Ŗؙ"2{=AM!d",/{ti+ߎcǷAix=‰S]p5Ç:ʄ0>`cqڌ*%R a 5?j-+@jS)+X^%?Mofj`Lth2jtu~|QB}1y~yի(QU\˕O;{ 놫P 3l%7ԭ Y:*8_n0UFbӻM*hj“܀EF "3gvroxu6/3]JԈ$, Aݔ4U;ʰLj (yTEf)L0ӞBUwffBކ#<# I3w3; njBLj At[Ts$U ()t.ԬY*W1mTEa6!&"QI.nѴy%b5 3##42 W0f^BzFX1ƥ.gNJIj X4XBNV&ޱE[<p( !rZW_ n'9cv_nI̡q9LeO)EyVd ù,/.]Nl PO"pyLxMZEWKˉ\_x4w%nT\s:+Ke6$jn+)..)zqLDx1TE'AD 'yeЌP8V)¤Ě@Jy\֭!4߿Zכ :dg:_&zoj!>heOZCߏ}&3c{-߲tyhiWA~h?{ҌU$ mŴZ 73+~q`IENDB`vdr-plugin-live-3.1.3/live/img/search.png000066400000000000000000000016541414414333500202400ustar00rootroot00000000000000PNG  IHDRasBIT|d pHYsFe?tEXtSoftwarewww.inkscape.org<)IDAT8mKleϥ#S*4m"XD1!nQ+iH0JㅰPх]وkC%+T0vzL| ig{NyDUYuvk>P|+e^Z[vD|ѣۚN!|p%Yۥ /e]{y'74qay#WAR NME܇boMtX@w-řIx56Xɖi0JdR=cBG'|zǞ{:!c8@# U=@-|uv(2_̍Hg4uy;f I\2pk~[+eN NsiF&x|K3ߧۧ/8w)&W%Y,!"@IیV\aָ_NsvBw[Ep*+hYG^џ|>. >×\c5_Vv'8s I =|߃/6''J&Q1qB8m[33l,8=ms)e~8!&''9p8p8DQ~}?n4ōKQJ-VO\q'ٙ4ժ)>BLOOD&ώFןq]A)1Ȳ KKKN/Dg4Y[ I}LMMYVJa0sssh4o tZ4MtxPJq~~0 HW?K)uD‰蟃8tRt[HZٗAׂ !c `#@ɎŬT ߺ$A "ECQ*5 }j]hD\EQEQPl%ߔ1pL[8 rTcEt4V;1[6p+`L%2mukuuNk\:IENDB`vdr-plugin-live-3.1.3/live/img/stop_update.png000066400000000000000000000013541414414333500213170ustar00rootroot00000000000000PNG  IHDRabKGD pHYs  d_tIME  ?yIDATxmKawPjqpPT$ nPtRts:\2H MVtF/|H& _a Ax)ވaP:qva+:r,Z\/Z+KKoU4͐zg'.@R xYzkr2C[@>pw -D(퍽LXq] PUH BRa-?>F͛VBNOAWWaj ad$II`<\6l9]XY wMifL^Mu]X +>v@8! X^h la4꽬 xB@c#"Àk q}0 d:Ԅ( \ 8,, QqdJCV$I4m3eYBT?pVT:;s6Ȫi ;|?>>-ː(b4ɉs;l4MܪTrBaeSRRFDG6`ͯ];nÏ&㧯IENDB`vdr-plugin-live-3.1.3/live/img/stream_button.png000066400000000000000000000015001414414333500216470ustar00rootroot00000000000000PNG  IHDRasBIT|d pHYsBtEXtSoftwarewww.inkscape.org<IDAT8uMkSA̝/SbRp#((DP,mAAZ7YBAqaRǢV`P+1Jo&q(If5s;cЮBVJ1Z !,˜U j{(TSJ[b㠔u] 0m۾8Nnp0++ToQJ9Gm #](ˊ1< (psG{{{lQ E4ֿ֑R"JJQylQHFcdnUǚDtjW)cל>`L5w@ H$ ʟ >EEFFGGOOSSTTWW\\]]^^^^``eeffhhrrttuuwwxx{{||~~! NETSCAPE2.0! ,Kg H!LXP`m*:$PCi%@Yl$;#E=4"1ɟ ! ,Kg HٰMbP x*b$PCiY p?#@`FH4jɟ ! ,Kg H)QBOq$HP *Ϊ$QGi5aY*1d!D#H0GQ4ҁɟ ! ,Jg H1P}@P`#*$qSOi"Y$H$$I##ыD.Y!τ! ,Hg HF $RbXP%* %1TSi%q,4La$R(GLaeg€! ,Hg HjESP'*%qUWi%q!CL>yb$RHHb Δqeg€! ,Hg H:s&TPP(*:%V[i%q,+VLcT8(I$eg€! ,Gg H$T޼)P`**Z%qW_i%q,9rLd2UF'j$ѓFX"23a@! ,Gg HʔiU9rpV+*z%Xci%qHLZdUF'j<%FYB23a@! ,Eg H*UUDrpVĉgŒ+FQ3J(p(|EŖ [IlQVTPL! ,=g Hz*VZrpVĉ-J"LJ3 d(rI>&eEY e0 ! ,g H*\ȰÇ#JHŁ! ,Fg H4iU JPD[IlUqV,:Β(r"D%#E*يcLj;8Ndܙ0 ! ,Hg HgThP`)*J%1W_i%q.]Licԩ$RxI%eg€! ,Hg HvrSP`(**%V[i%q%KLRbR$RH""ڬqeg€! ,Hg HVJRn܈P&*%QUWi%q9L)bS$JҨG"`qeg€! ,Hg H2ڱQT@P %*$TSi%q,,Laҥ$&ҸF4aeg€! ,Kg H!P}*95Dq$jvIgXw?f-<KAQ c)$ ‘$u }ݶmc;gN8/ˀM4ƛf'{-`I\?t~=%؆["`|tb:G3Y]Lv ==i>< `sv|K<`pyǾ:{m;Im:\$v&Px۔YOI|1q2CU3zqrI$VaKE-]mX?n[oj+#(G)m 3gzs@AK]`f|ǥ*k ^;;@ ,;\dIP$s`*N0O֮],%C$NA ךG|;>v2q;ϓyyO:Š)|Э'tG\2R*utvIO@8@(D|>qS~Hzsιh9 av.y2d\Cp}9)1 T8apG~O`qJPj(K\L1LMoHĺ8 [ ?KKVGhWX9 GQO sbx}1Nvi`A<+=NL".ffeGO_yIBg߸؜{c#(̊LI!}| d#OkGm`IJ1$uhսҥ;F!v q֬ZN-i-F98> ר;xvZLd8=rgKvaXy|xHU { g _lI^9x߹1v۩k;]w " O~]hҹf%9 H.W8hWJy$)m|edq.f:[J ؂1׭Vd/d&3NTzUԖQY!b|=q8<`T+s|n\J1=zkMSl}wfj8;PlsBxʺp2<aCmAm4f'lLExfmhf$+=2>0}_4"ncy h/r^摆pb!"f@ȥB# |ܒA';q2s*d$FGTM5+'p'9T-peYP* pyg#;TO *Cr;}}+H-[9@J1F4#j*/99t?YK!k2ƌ@s118qQ_#\HRgx\=ߥ1MFBȠ!$v檤d#2p?)q 5<-Ǝow.9 .n o % 9U->k,ZL: =jyegFS,OQԞqKh5yQsI$:=@)c{xv|~qqU=7&YMv.%gj0:⢻XXW[nr[nӌT<&avl`~HW[h @9r9֩)AZLb$㞝{zrPta|C #8 qHJ?1d-Ю84Oh-ec 廲s|q cߑӻ,GUtMdqWG{iVIc]嶓nш`Hveq=`@73<⧺M)&`88?^*<$W["6RA@mq9V{aެɺ2znQ` :LK=3FʷLCi*[(H;"c'99}uc:Q P8o>ϖZN]$ r\w='6kW$l2OVsV.#D+%xC^ƦI"b7r* ̷xNgi9{,HϘFUG3?vM+BC!9lS8Z?!gi`69 H@4H\#R}Cۚ yGvdL7rcUxYj|# :jڃ#99\zw$x 7ܢ+I=ssT1N;YrwV<9'CϥX[;xŬ$<4Axu$v)XؾfAvU T#^ľc޻ۯ8!x4,7lGk` B{*3*$t6I#FyTb3ݴd -| !EGK;!E!'o=qJ^MQ482-IOzG@ ҙB\\<!EK\dg ) ]Lk ~`Zz6D;X\/Q8'8:jڮC)|<Δ&4fI?x062NNژ-m.mcxϚA\pq׎԰_$<͡O@O補qM RŤ L >SE07Q!İ=6rz4DŢR0\$ N lHI`}TuF4:&̭0 n ӡNy#"(xBHnR. &{}ך[Ky<2JG}ͷ ewVy:7p5Z'yXwUϙ & 99xM*FH$LbIU'@#שK$2 $ыz9IP%YaxDd .H9cOkY0r=Gh%ùI(#V\)5qvb+tdJ>EF#YE-%S!$qsn,R#y*x:4g$JKyBb$|XxSC{V?.&d0c}e{(}#u#HKil[x-JmV}cҀs+jںYe*rs~'UBNCBH!T68hyr2bRC[R:s/A< JX'9{zP :60dxeEMێ}^T̲y+u٣DU Hry<FjJe%A %zg{OVУ,NfwqJo)|]봅idz sO&x6O<$RbMLbcLB) c<A="v du |`|@%GeHUid ؗW  ܜdm*]5,oA!xr:g< wQF'6$ grs}yԳR4[i~1T3[yIYnERB8IsԚzې]W1r82k%/,Q;I;pړSPTJ?QHeQYoV]ۑv=W\L8e~6#a偁ژp~ұ>E|\1$'&dkK)IP2E\ &G|d2%to[[!T \os.}h1hEYJgPrs֛CjBbIyiYJpI@ 7EoUed5 npDyfWʖl`}v03:@͹+*,ˎ?UHRee?Bl83T1mx, <{-֟Jmn?0hZVV '`wۏ4@}es^31}?PmC[[n=B z_dn2q Õ8 Z#34dƒg<y׭1I [gbFc'G@9nq$gu hϐd$,zrd!P n:9^x1lOǦ)`}G~[j}&6:[ qh0"Rǿ#TiH w4Qpl%Г>d _{tVefmk*g'uu)`IIa)!z``q> ".X3a\c"o"(᠉X9'?8;j{fI:6&>i^m:v`1*p1N\>}#`y~H ~a\2;tz'DbllKw;1ӥ6ͥK!)I'roSקڏڦ(mTHAX1Ԃg> iB0D\V]``t(/u:)*+Ovt#8Y`Hecn#=x$ӯhmVVPNsI'͡=*vӈtݗ(;t5vO+7P״-IB"kNa?}g$>SPXRI~Q\~(/ ^i0m<0?Ҁ,x[FOYi@kqs&4^lӿ'_#F'?fI\v2i'+@#Zwd?ӿ'Th:.L{H1C΁]OB"2ӿ'G#:v~lQ@iݓ;uEd5rOF?ZPG#zqyoi !-,Bߢh(((('!X$6)$\>}}|jZr# }oY׺. =IN1@h-/`Wx*WWQSe!@[4VI[[C(4FXH( c'EQEQEQEQEC  !"$"$C`" ^  !"1AQ#2aBRq$3Tb4CrtDSV%&'7EFd5Ucu6eWA ?>"lyU1|2[Rq补7I !RRE^?3)wSwL`Bx&L%W>ڊm;zE7ŬU7cޕjwſ:3;VӐp&?^ BK.D?tiZq!?ףs^?!t7??BI~/ERN_&0'g &NhG?7OqBGj}s#k &/B9?lКwSo9D5_ho(Fs>1*&׿hbq?q.s&G_qOFWB9e?ucԘs]џ亟&|Gs^|d?hmOF Ͽ8ScB@1Xkd%ע|/YdW?>ɓz!ܲՙߕ9M?Ň~RsLWkba|_-q^Vٓ~Gy&ןf\'??Lx7LETv'=??j t5RV=k\ xh)=|GL'X+| 8^W=n_&N]C|9H6?^_x_ӬVd~=7<{?o=q,zq,_< !i2uL?@4v11e|7G~HAW?{d'U^?Md^7G>2Y>17sO{GT,4B?<|8j>2`sJ?z7j~3Aϼ_L O/I5пML5?MM5;z?k~S?QhbqbT5ޑ+i.smD5GA5Qyg(Ўzo>0 }kMv/P8_4# zʌHOOO :yC'y |^I't^ Li(VV!~|]COw+{ֿXG(Є[Sg{NyBF'yF?B#U+8yiQ5XB7xVRZJmJg\{];,ضTO!c1vh6)am]Ѥ]N=@sj vgr-Qo]w# 'ԌNok}fp-nq8JTU Z[Z5XD R*Pw?ի+PTJzK۪m."JgUݧLj)iWΐ "8D5c:ԏ7nrk-G[uumPq*N;J3wEQ_.P\%*  `{ƃ^޻#Сp3!Y 8} ORGGWśnZӌSő%3%&8_,:$wGgآ-"=M wՓ g[mJOcB7LXLğڕ S,*c>_#!JǶ1?Z Iu6H.Ro d(<8}U]\*ǜ| Sb.RpfoMw.*<*ֶ0.A s@v3TQ}h8ʖ'ZuY$;:Kh*p8R-\^6mtV]d<\dEE $} nְ\me˧g02qk8ιE9a%ۮFj!E^TR+q`utoCDȔԊ>3\G^Z _|Cv 9ʉpp}-GQP{KܮEl ѤFg!6#@w<܊6X',Ud>~|-Ɍ̃(JN 9bn!kSsyO~ao&j[5Eeм $VU:_53+U;R_YLMl#xo()Ph:nW4ِR܆cvP!=Nqseqً-Tc{Q"QzSdG8R-wzHXt=qS~]ѸME~֔6݄Fa#ЭiH)t~j|@FF䭱⸤]ͥ9H m.]hRd[M^:<א(ZÊ(t@9FgoPlr)NHjm8ҷc)>4's|Pn%mi+s *1N;:5$*H!|䩊%?XܯUd) e1$rcTۗ_w x궆z$ cw#?ԶJFIxY!Nm*`)jկTmT0Ȏnł}r\ Op}1{i5={s^}428z%D_e8[gnA)n6TV)Ewӊ^˦heĸPRࡓ)'ӵMm ҉]>d6|W-sio:ۉmծ3gmE-)H*.yќh @`_L:Ͳy^Khp!1J3:2sٯS'It*fS${,%>O?wֆ\Xk/".%Uҧ42ԗPI#ΰ;Wx[vl^ Ȱ]jY|͕6:1 4i$XzLgFN !$:UX9CI+qne %#́n"tCb WcJB ǧ]srTNm6/󫭻 BϡAaI)C?4"-1!>"URH#dgT!|S}KKb:mIrZ@V2rr?󴨩qm4ʹ[0`}~>Ct|TkkY$u`xϕE ޾ޞ߭Ȩqtn\~>1GF2Qo.yeiS)J}FRe(` -)y VҬu~3FĬn;Ey[Ҽ/D!+RTYGP=@-VKl0O1袢l먊˲떼= <}t} -QWq@iv׊ڣ9⠒}2{?*WISDhvgB ~2hN:Ked,:pM+Bn|9lF3)a>T@w֙>mQmDW]/b)G|~d2 wW,9!UHZv/f"AR2RÈ" Y {%^mxn4<}xjǶacvS.x3dLSW[o~Qe͏?y2}K G~C: m"ò5b9g$':O> Q1m*x. _mI2ӊm@Ηme2rdS2VϺy}Il0:I:8~rd1ro\ZFz !d]neHNȳmS r8J{:o^Fkl&K)4"-ˤ \z+p+gʰn FPڤU)A $ty [;ƚv?$3ʈI%! pAw/mMnJÒ!X8˥M6?x:%*ӹ.THJ#Dˍ8(O]gW%۽j3a`g(u z+-:a(YnEˬA#< e!c=d}:Ϯ{ڑDTnAjJTlr[eOYRц+vpo.$gx)q(RF0쒜۴2lO{)&M}FO cwԟnpq=d*W N5ټ[;4kq߯d8Lާm;&Dk"[N&Fi2[#( Zv雸ium\FԤEbjϢPrpHh3ooAi-X !,d`d`c?ۃ+" &TUJjc'";OMݶ2 \oA*~XRYGʗ[+G>DqlRB4q*Vi ZXZZC\]%ytLCrI 8g3g>*Lm! YlWH MlWwm~b-%Th~n*qE*u@:;d뺯.K!r )ΆG-S %7縦RH“q sz=! RW꤅:=4j;Nu.Vlq!ȸZ)IRҔ}mUT6W&̖NI~dq)ͥaSBeIF3!%``R1~/YV)XFn7Ԭ)OՏvsY_@ٛfNf 8jC$w4ǺvwBTzNi-(]I_Czt=4~c}o(i[iYw 3՛q\{:Z;RGR#}A>%7oKZv5ȸ9|3 R7jC#) {њԉΕ$<#(p9 JbD]!1o$~WO*wf3?omjvVo3%Vwm4N?TKL7J 4ӵNrrCRlCD[BьpT R*Zm&9_[)a=$gӆ@iPߋ g$6Oߡm42ynD7TÒ20}^'t1+K#r|FA˪ qF1}OkhL9,-H傕:IBv*y[h<*Kq_XKFJ8N;쎺XϰJ'߯1vJB>BBO)ϾG`cui]yR&VCJ%)n 2PCϕ<߹w2"nSRطUng/yh-`rB\ʙϿDuXˤXYM 3JT ] < hR\ <@jC)[@ Y=K56˒RCn(ԥHqĨ09uެld0Bb@9}?W/Eqb;b; ,yI'?О^>; Oɍ3~XnKu qK#8$ /K͋ѝDx™< PNcpg}ZJ:D5SW!j[/ AKZ[)d#etSJ"30J3}Zm>ODH;bK5jCyb϶ C)*8sXیn6'Y|;{h v|@P2I'hzVۏm dECDȏݪ5S-c/p"qS#@+9Z6%\Asáw{czN ߕm("b(!A:1@¬t@YI2weGcy=1Tt3g8iJ%)TPh'BaäۛyW0j2fFXiŒWO>za5t1즢S.R+># 1I9ii.w]W5{b3_Z &!9%Z?]~cֈ&eD>}':O|Ax#-3/.k,|>.\`KʠX^>kl:+ѺώE?+-]~G:4ük£(ԭ2Gʦџ @p[BX*xGе z_z ;CKgnƐO2AʉG;Y֧nk At {: kɒ}JsFUm窖"q%9Cmct'еS6=ܥXE@S;*!Ruq;BdSϡ.^U,JA`+ V!Jm?:Uh((Yt RC{ڀmUKz,%-+!!*mAM$d?Q=ɘs|5%S,+CiOC Hƹ͆V̫:tyXn#RYJy-ky!*p~ϓ"w U]6Q&-g?}s݊ݗ[UۧE-Jo| CI H𒟯A_mr"ojB%% AG|TpOUbq] ȯLe&arJZ^Zp(P'%^8՝U$Ulݿw Z"t9PJThX ᰜG@nƷB$4xDŽ$8߉z}4[|N͹ENf<$$ЀаI*_XkJݱظϻ=MĭSGkTJToJOG+-8JZo' FAoօ v X P(YגIHh^,5+p\ɛvӑ_&#l(Y`DnV0%;-AؒcaM%%3XoMk*k)6RWgnX$O$6c@ֹ=ꜗgM2B*: y#=xH8)@)QLjuI~i> d z7ЭᷨbDuQ> ]s\V SN v/̓]1fy$όOΗo7o%Sf䴣j>˯:? C) pʓ[,9q-)Z]< $wc\gR/{ۈw6>-J I,YA#Ш>T3!oHhMi崍)) xjq]?Lef*l=r\rRT$`1c+PA&ӕݏ:iC.68]q*)AzƭN婏1O*l(mp'Z2BN3iUr}S!eG:eiZs꒐pb8Ԭ[Ryk? Oj Ǧ{ƀUSe%R-a43n18 {ղZQT)Q]K|3Rg|{vDfWYRʱ1~C(Sw%:}4%ٕʹ?1'nToT*JフqJ+: n;3b^QLtc#H>C,g95ѭcIc"Z+ - ' @\'wWeciY6;Ɲ :]Jխ 8[D e&D{×~gKGց.I~W̶8|[SIAϠҋ[t<f9ߥBR/c,4W!O <:=#]dqD9j"pI8YR;: u4֑77'&#pՅzW)c)iOi޸*T$u/PA9яЊP45ލ?A%-SZ*(V.9iw&˲q3*~JP+@t1]RZѣžϼbyIyI$ Ā:Нe)SZ˒˫rlWrFA.q+ZO?zd.^cc II93e.z5Jw$)T٥ WŽv棉n= +\u)kNI=p}H=cO9`Ȅ%ۓ0J\=K$kJ6-yRdwe!QT+QueC8$ǰ;gmkFH^돡.>oClqBHWntw|rmr*4->pOd wܨL=d[-"mA%^BNz'iv SduFq_t 8I#̠}ױv׮zm5q,`PƒJ\xi %\ҸSe\^cHg HQ*~b nlfL*r馔rRsgћog~*1Tщ)]8I9T;t[j{[rjjVĝk.m|G"Tt8"&dɻNbTSyRqf92PTJPؗ]fc[V[P)#$( }eո_m[fo<zPmB31/|Fz 4ß=-GI'3렰˛x+e?+%mwdcg]UwqyU ㊉iգ~Y5ۋ{7[&#MP9:>yU>'kS$nQVJ@ZXA(攺Tx'AԚBv +iS)w KSB[^9. kpY"D-eKy{ B I@Hז)Y~HVöԈq19$66H$MydUm|HLp}`חA㣾mޮ{^[P'II}'5 -k[,emV8 rgYCw} EQۏb }xxRl=1<6mRn.;-*AgO'A~O I:[\7eYeHuK$)'9ϴ+ꚄbsW])Œ a:(O^+}))a<1%jʺ}=i<"}: c"KnGQN.79h_dIn'lP\!u/d-J'lj|pTJ*KaC`ʍm2S Uўvbe/o*lǂϑt F@QCê@b($Fp $rC.?GOkW3=s=CYiֿ ?kluy 3G Hu [CqX" gG̬ #0%2A9 3>׷n #hIRׯDu[-?'mm8Rd|:>#a VL"&bAvlJ `c95Q֭حciUepx<8 yRxMu}i~!,ߌ{感 |4COC&$L4oSyqjŞw:KC¼Fp8A'>~Jg6ZVbp).3@h)){@HP v-+_}>3>Ruݕl7F-oDR^=%T0Vr3>&X|cd.;>xh.?FA=z[r7S^@C7&[@ g]*p~3*M-t׉ 26?2>yKHjō¶rq88@ymRt~8P6zP) c=dr xi_dWI#Ma"#R]uA$C%< I>wo_m[&r7Dy2Hz磻Ͱru;^\HzuTU0Tf B_ \5{dH lka[2lecvҚs$)~~kIW&;r6\8,5U% R@Ѷ׋"!*T$+%i[d4$6F޳Qjs$Eq9%(dj~1cnvORV(:Qet0ࢇ.oT-9J-[RZB)Q}lȏ5όߑ"SatP%gж=t6kZQʡr2)?RT-dO[YuMnXZ`PwRvDom ;oֻ~"젶2^,7ŬR#Fn=Z˒}+l̟|%Y:䫰\ eXrZYym!7 +\sws[) ,C\GԠ$0e#' ~~rkVWݩr0CYPV2|=[t",B˛-^5&R-aְze^+m}\a"{(3i'amyRPRKH %4:`Շ"o:s$"@p0 'N=@Bin& _rX] \zvXdVS.KZe򲰎 ^#"3IJ^YK" *e KGd 6!Fhuͥ^x#$!OKQVC=+kgo\V y8)C$X:ήnCQ5b"pk?K:"!6қmZK쫭ۛ(HSs`ْܐ+'礸rS@4rGJXlmUU_})!-GˡF{Vߔѥ4olP$Η+RfP)ZAHR 鳉nV< Vf{r2qsߢ} ʣjdἶ n1/BԕɃtB٢UՅdKC>2B,VayA #>]u<8p_EcxS Z[x63^e 6į;iw"?(I[}֠-iIʈ2|CTMإvL eW]a*+-%$JR+^V4[aL'l9dHmAY}yPp{{6ވby6n%Ik,({8#ףFaVId)ў~ʐJG$Jqa@hm=|tEvٛBTx5F( Z$%Y}O^LJTJG^%Am$eТA'?[F符EHWJ JTFJAƕlr,+ܵvK1˓@( 99sq:wWQmvnoyŽ BbA[[ ih8q'?6InJ_Yo)J=5n4Z6S@KȇHL`|ۊz[n{eF}v5Vp,@~O|;$<.-jbU$`yRrr|Av.m`#,jQqhOU}:Ѹ*kq5hYR}r]Ǩw>K\3[<P!1`e+}nkDoہg̠S̉EIǨ=A'm}UK>3۞n8 aG++Wd s' rwe׈(aȭ.A[e.g>I jj)6s-ˑ%xVZC$[[rn0R/Ot!G(KQc2t]7j$8tҼ~ R:zv"(y1WʊI p:dɏÛ15TAH[I?$C7|Kiy򖓬aSy9ۡj+@P{iRJbRRV =Sޛen Ej*Ymb1T89dq~Y_ڰ߾˻=YrR2TҁP i R=r]꙼[lTP eip#88^$u4-|Fҡe/YYzF~I6cVFTH +x)E)Z K@۷[Vr~7B `gGtyK/Bm%\" χ-6syTr4: {#fw=CN{_>^%'s{0P~u˶wN߮7km ׾~= o QA8<Ǜ)SWnQʭ=1[|JТ;J}?% \/-?KG$=Ӯvlhu3kŒɇT)Ϫ!TtM}3떖سY| R\=r4GQ&xEBivRocyegkZnn2ܙ!W>c r:<:dfMwԠ¥vJHXa B@d6^kkJE|ž^Exg~ =B`+Zi]x~ܴ3q*"vֱT7L%RP#/\emYӷ-P̀#[y  S#U-ov¼jD)g#Km)O-!ԴO3o\(0h%߸!d~93{e,k+ ᤢ#ox4@ZVG4~Z ܶ+9JrpqgѻzιcX l9J(f-[K:ۅc8u6o)t o۶CIe1Rvae^ʼ&rU${7nUenЯٯD)́xe$ckQG.N^a{DPi ~]Mt;9H䄹>AMy 4kHTd-  vؒ믕YjԓB&cK?! ௏@髻Zi:{U[qbZt&}#e+ ǮT}tٳ颳aN>$ jBGU !2u*-ɂRRP8Ƕ[my_O)E bu!j;AX ,7'I{M٬l"H⥵KCҠx8s*ΊPnkHMUR_KnGDdcr>e ~e%+]3e$)=*IZXիN@- QcUj/+w3wWQc68bN9Iq[#'9>9nYL*clo)kJ!G`!TA]ߠ,;r RM 0G'a'y:sV;_HRe#'}B1KM+iygr6Ԕ^qR1>ߖdRiJ-kHD)IrYmC: vΑ<5m9-5\{TR裏ZlB̙~ -< J3'c\K.8*m<@w-;qnblM5>Ι1qO6A>N."2if929G,WV}$Gu%Ë.Euj.a]E8S@Μwuenw!~*y F #3g/[!M%JSR3͵I H#u.O";vK 0TBd*NAjljUʽb@ ]f]mI?! cB}kn㮻U]HOAփmj=fhSoFU5U:ZI?!k怠A[fkQK"%6>q+d#̒}<~QovKDɷdpLek ?Տ\i'ݽ$+ql*^y7\Sks*ĶqM6-6JCs=x(=a`ALgK2iYfHqg%1R^OyFڔ^UJ&Au 8UED0u~T-*>Ѫnt g2SD81ȫ#Cg?C4S/t;]_Wwp!, i:A%)G4h [<"\GSTR>(JB%VǬѷ}Bµ H! B wNἨ¾E#YvbT RCЄ(I)MS9[炗̞+H~]},׷%Ġ[+$"\Z5z(ȲM^躙Tl4VB#81QӼ͋V08Rn(p9!ũݡ'ץu NͨT%e#Sᠥ$ʔg~nI,"[;jh:RZRVZ~kfeUX.S)m .4V=G骳%Pe:dDaql|!BJP[6jTqa]hi1䦝>+! EJc * Y΃vM}S L.+$7%9#cVh~#e-x9VQ9땐\pGoiH2cK",#"8ҩK2N TI =k|mnIu*L7H<F2yMSqu_XmЖS >IP!84q,o˷3m̧b) Dh L>V#8*;Ufnn;Pa84 Fݜ76C' o%NTˊG uM  īCmX_0a*9zμSn8`DuH.~Z}=݆ydbHi;Z]u) )*8) CՀg2e)RK p@쁧knʲ`峋Qrlxq,m]$)Tsڋ@}z͊TXtjHyF9q=j!p=B2Zqػkm+bGV:H@иu-g;be$4 *YrZӝt)Sߛ4gÇ?Dw%Uh.O4H$z{~ cJi-JPHS %KI'Ač'y ;ԊԧUJKBpl7ߞHlEZNoOlLݪDžcYg"EbXa&lg I䜜%+4jҍqLZϸ*}C-qLd75r4+E,K-_%K̈ ¼ۈ[t xΘɆ|Yk\m d8@}Bpzᕱ]vC]DiQDT*x $G$nyHuEmTOz 7wҏqYmg tDZ=vʝSDĚ9ez~ [^6ݨ!mRaAj<>={h{unJwt|)kyu!YC\: 6[s;e,r [2HQ)UijsNVU&Ul\yK)p,d J=[?n8v$XĬꋱw9" VtO3fiۃnF 1,ap@uHSqd2\N=zۑݟ[B㌳ 8sWO:z;93udVw^Ym(mM+W(⡎mnfY˩G &R+)w%,w ?GNvL:v0̆-:VOI[r\JS씟@? @C)x ջaאT‹M46d6=-ITd3b"hCnTS!@gJ]/m y3X>5钧 OG>u|-[" %$d)'ʖR깧d)$ K7ė_]}~Y$HP9i;d]̥V|x]D8d5++ Quʓj)s̨@!0]BT $Ts0>o\Xja5E5$<|'e'Gwm&Y|7 Mʤ Ilzh|'kzȭ [%ȜJ̇=14w1 v0%'sӹǬtQ-:_wF0]c/o+`&ڄsWQ}A 2[ p* CUSBcW6&<)dI.2s$^]3N,N ˹!aqc(H AmWΨմMkieU:Kd~U4zCF^6n KpW[J>d]f"\k-ףj‹^nJJ^ION]M(Ƹ\MKN_K/lט$ΌB[8+JY캦unZ1̀;pJ wUq#IaTonߛ_''FkkVA9 GʺiR`)csv~ݕ_ 3*/9LCJPNBQO=n6!4y0Ԛ Pr^Zp$4m?kgo/WU*j!PADN}qmV(w MwʼS2}'ʏA7 8 xSJl%iiKQ> {;e)m ލ碗O^SUG}E[s[OK >!HP (G"G]PAcǹrC$~%I/9KBz^VV@Jwe[S Рx=OZ&KbLY[]F?ɭ-8SH5\#MmƼ/ m)GԔ$GTd";z0?B9 =sW;6 st~x\xnKBVN[?L8>`H7Mɱ1$ǎIP k*@u~ٶSc~h,dJ[}aa`9gP6%ї蠫۾$y0kg__6~8~]A!n?6TK1ct;gVD ˪ \!#=_G$KG d˻U!g7\CGmQ8Ӭ ZfiuZ7-qw @#0eWWoA{jS'$mx(ItmkOwI\1ڏ-*Ϲ+o= F'8K\#Lw0=2;ƮV_5&˦hrCf"} VrmN,R=|V)T9X.JO-9X樓m%ӷ*΁Ek6I'Tm  kW,[Ñy)%)Ԫ-Z@+m!ܷvQWG}Cm Y8<0GR$g#9iqNeZh*q/+q[e&|r>Za3U.}T%mgպҺ??qaB 6m,!Cqy@+JOM&Ga1') Ԁ?4e!G~n񭔍ژ\۸dΛ ,VYxvVy{^[U%M]m]Pq^šCJųFK:OkuI*s?*š-qz2!3(%8n8u{/Mે^gnx!ȻNMk_{>: lODYD&S)-I9c[m6,m+~yRjBTq/5/mعm;xAM"㒉 L,@[BaR|دzD !˅Gv"'y7~Vy=2 $|G\m%yp1*o Lx-r")ĄZ1R c?!ލHt HL$n38(X<i'&c)$PJ0c4nNtt[ B>Cm#G6{cیæ$WuBJN~'_MQũ٢9)^3:(~A:zocȺv$|4H24G0|qqPO]3d-R X_]K=cvJM%v˨K.g'cVFq?eD2^B\e*b!!%XR<}m87fRGuUiZTP!Ɗʼz(h'ğ*mSw3Y.!yqEX^tT]ɽ#tdD)'+ rcVKcx-[' #>%a]m86PyY9(YAS3^fG~aTtH6O׸8J #郝I7 v ;D7w]((~i@ Q_7<mo(yA9MeS}O:zn6l  _5VA.9=qUVXZw$zF 5܄]xV' ? v< LS2SsN`)q-wr#7|;bZTW`ЂKrlD?M" 2eV& L7q/sIcB'svﭔ9)}UԻI*ye%APA g@6f֧u&MQ>O%,q]-KZJI#tJz8neMdJs #+G#\r3aY4^f;6!m6Gx $gTFD5ɏb]5.0V2 yZG]zq]ݔcNthBxcGP8תJFJ-cb9l7-еdrRYQ%|H8p3ʍùuI[mɭ?e->^j +$Ɔdo["\̲RN>oońN΃V>B!WXuL.%DB 8}jٕ ":&RlNJzP 9}]"ʦ+h# u 2rRr/jwu]"=w6 er-2@#@Yv\ROy_*xB,eؠzC\pmNyFR@SipSTǶ*n$l-CE)+{RzPVvGۓd0*ZQSs9Y S6;8 A`T6y\Vl,Q􀶣B[z>-ޖWeE0Bu qRӏP R=5&t$Hsjʼu !ǒ#r{QáNw36bSb:*@#RMC׿^5{U3UZuR'.:P *A!:eu;ኇ$ZZn #lԄǬSntۂ-u>y{#75{IU8Dr0968Q4޲QAW1 A}6vݪMk7e*nTqW`#$9e+ۏD ob+22R P5NGېƚbC'>: #!~e8=@{hk%J/F Оl&Ja 8;-J|0?S!J9׽V5Se Ka=փSO8N?<h]ଥS[ ¼Dke\OY灏}Ԋ4V4=")Պ}4#ʸRgҎf=zW;ǘZbL{/qՓ+Ο0U})%3S}>3"]\指;A %9Vٷ~O~J\R$p}7zڰxp ݏ%ǻ PJ` x",Wi)Tf$g* JO34df>%l2[;@ =S>TͶ.̑O'{h.DwĆֲ˿D%*XO,[LkFb圗]HG€3bʪ`VX0q3?k*ʜE.)8"Sp8J)r;^ܟѥ̓GOktøo៉#oouw/=\j?=ugo r`O0lG eTOq$>-VVDT۸V*'0<H ǐg]6N$&Q)Cɔz.Ǩ87E끴OXx1Od`,2A~@ [W~m8ųZG)Ӝ`h鯺Pfl]bΏLJIFKe'hնN ɑU,D$gԤ 2O;V& HDrŴd8TzT~:ȗ&, mk}ŴJ'@[nmen#rO1#*:綖_׼吨 kg"CCJ傰?TaU۷.™cs+.ĮuM#] %Ga~&s頱{mcշ=\C+tHRHT?X9ƚeF䯙Gt6dR@)!) W W^:_cʆKYHOʑ"S o.*mWL¤.VH}L{d Lh6Ibj.᭄ώh3U韦;h1ܭbԸ!qCOB=k~cV0\g[ 8Rr<́QH>{0_aܺ0lO8X#pS r<[o `-Dc˅3ބQ3gi0|EHzFܢT*rGکsc ǣd _TPiV`ĺ+Hz,_7IS%d=cʌ{2REյmKZ_nJ1*6 XZc-ɔ-Z\9Ra%X:NV6S__ ɴ!(C-)@ ݭ~"JKOxD#RG'#;Zķ]L7bPx=\wQ*:T6硷WˬrU&у}Zf[\^vC}XtӔHG ~#q='_𔗻̴B,J5ܯsr]\B(8p%.B}{ 6tI򯷜@̠|1@$+ hnzCWӳ~ǎƇMqHAJO\H]ωWn3kbYL3%:#ZH(uJIN}jKv}2@FB=aETng <"G!,L5rheĽ{p-& 2(*C3ޔs mM.Qm2#?_& ۫pBsuPG;:hJ/W䈨_dIM97 m[r$Mc-:)Pڊp;L| yM 8IAżXg׽UIm G&NW$ F}˓.%<БXIZ0X 롤 wRct&!Hq*R%HW^GC;.Eo[P P#A@h n|+EyjR~Ri2P맚ӃS}_J~֝tq_?2QYo\RK-n-!?Ӥnŕn"<*G2 RJN3U͏S`_MS*/C*JK9p=FlKnJśtg[O1G3! o)j$U9"$ːۋ-Ji}) -1T4~y}C"2}d<$-\:#/rDqWUdU qx}{sQvᓶwk] 8䴅+' \y+wnZ.]7]*(gg[G`_`d:.>z uG #D6#3e2><)Hf|4̢x@Rkן,Ȅ[&#KHPm+ | um{qV (wm'Lu>\4RB9 { -PKzJ[ H'Rd<\JHJv] ,dܩ 8ǿvJA۶QAzSSI+#>qZ䋺zٯsƌIiGf:V(QN_nQ3 ⯁qnl|1sk7 j2khll8B*)@8: 6#tnö!*cj4j^ZpR<@>!>c:®E}܆e?E;œˊJ[QQj;,5C>5Mvz<5\ر-d a.}v]Jcоԉ.-%,/O$)IN/t7FVQd" JCwϗ V@)#[ \Jv*2ڗ\o'zՍV3\us'Az҇ʐT )'*:juN..;!:1SEl"=[˩ҠZnYJΆ)(Zl8N|ﭨ5[Z5 12CbKW=Q]=% (_X:Jv²x>6Щ":c2Y!9⒖T@^S?Ng\2'Z+l:R|IYѵ zmjȡ˒b<`I)%4VzRCB=W[~M.\b$)kP eD7D",^Dy$ܜ@ /}z3!ǛdZZn,2HOZHǭU;6RyqaH$tnW$cUv,vT6wlxξ2%!\Bҵ:1ߠײmgdZ_W<_厰81wn͌+mgW&4xP/&uZn|ˤ7*8KH.9TrPU m8v ~4QIIjJ1펆M\YNN{gƍ eC谆'}H'PTJy5lIC-<% րf^tDQZzJ솂ݍ- z O&W;Eia +֙ $%Mf Ḙ2_e[2ځnIAA BUJR/).b7)ո ;|3C<>UA΂MahSA$fmjq:(О`Tb;OR=?fwڣCݙ-8AXKKn>|D2;W+Ir;%KC]b@I%ղG,?Stb32jZFCq% ڒN8]t RןtsnR(l@mKr ~s{L8CN~mc--I ^>EUŽ4g{&8f:}%9Šd$9o^hn]cG)^;l1ᴷo+0.#N˱-.S R21%$_f:֞Q/2+a ' #8TGJM"u D%Uj y+l+ǩ(QZA> 7[4CiHwsʙāQ]nX*rYO1)!ĠrB<2 {moT-FRsRl+}V3ո6JiU>>ˋjyP#\v+mݥHd% xj$eJumJIkQvZgr8rɒfĨTH#9rǦt0ƒdBJ\ |@[+uo{`H}T$>^>*PgAfSeJrqD7e{OZB,7~%UNZAJ|ďr'Dm.cemt١X,}c>L=ƏXNv,׈sS DN@ƬKݛU0c!!* "aP {arh^Ard6c.>r9 #=1"Gɱ.)d`É xDZ! $-qLםìNH*- ġ{~"4–}GpK ,$3:d;9)51#X3L{ JJsuށfٻճv턖c0uB^RJrT@>)Q0F߅164WGa`Z;'89Ƶn>%5 S)Q,~d$p}B1NhcJBgtHIV8M?|ۡf2Q0 ҕ0U9W6 K|Ȕg![Je]%'ܗصr.^r)r \ 9'8ǵ՘4\klnf'˺YwFN0)2AY8?b9L&s2Ty%\J_:E]HX[+J_!_0PM|lmK.!y0:q(OHz ػcnkj3[L/AJ8Ad#Npt) 3$ &\?р*~x %'5VMv fQ.x0j娣J2vqݮ@m^5! H!H+(<$N@돾847>r]l`u#8=v ,Jj6d=ziL|EISѣ\ȭĴӖ]mm(xt'56%nʙkZ!>ϗ1' A ^S\UAwq4&dgXC?G4s P5ߝrjXx$L1*3th> e!":+%$:IPu)"wx:2qCz/)\9 dSJ(WRڼCIQ8Lų^ۻYl$ܷ"DqTP \|C2D_w?hHnHc('މo* Z?{X(k+[$RڲxDjt7mӥDı"bEZW9z v}u͞2YYt·c2͔G9_p:AtVo &[SW5Kmr$)I8]di~+Vmz w"Bψ>唔+3Mg7&UcLD b`}ۀ%RPIs{nb撸-H x'=˗Oʩ|gLV!)S#)GOYuS, mP3YxK ?zkD'+V1*esmG2ԕz£}~Ί:cs[?>p!žjk>T%y#L{qIݤ6Ne_ :H)OG$ch,]idϙqR@.Y=lPk|G&1gT`&Ì[IR4*Zߡ:qm frm(jT'*OV>1(jfTe;-mIZԩϧD0hmbUME ϐ Ex}sƄ!aWBZ? t9$`bJ]H-\1O!"VzŔsҪmzZ Jr#App8vjtjyVL(V.8U~#i21@cjm%6kILe2^wl)6\n5$d8RH+idJsR]#׾̹s.TIsVxTJIN=NtqޯٖqS ,@~<=1^bB$$y1IS$8(؆ dgyTNa`> l rSnܻEz&RcĘ['-s}{HLv,Jf¸LB^BA+ I=v鲝!_+ak %^si*8Q̮3cL颾 WbnD%,EOIiԎƌG4J`Ml.A)ǣ9}*r^p4cwkffY:.c6 Ts}ʬ5I&$GE\+)T!XItKmʩkVfS#y1I̓N^:'ʉ沐(rHicoXP@(xeqIez<}$`J'h2+rG~;IB8 nu;ِBĸjF%&g[#A.vMm|*w-Ԥxh\{rj'qc(+vC֢M*y*f;g&R;JД~)o XUX}Jq|$z6Y'[)'h)}K"L몧\64qxϤTۣG"OȲMUmU"\S;KkB5>)kД5[omJˏQl[PU.4$ RҶi՜q3j&׸;4ʈvK^B<@==c9ͽ`}=u{zc#IlIBP$:/h<ܛsr͓lRa8>#!KEKi-'qCWO2JMȫ n'̿ SnGkSGL:V <cfx唩Cts*e!#`hmfݬɖvrNJX=Ty# /n鯡rӤ!@i'ɂ^:?Cmɼi!?% JJBˬZTvdjfbO?@ ohvt[kfX8bW'`C$g _kSN>,!Xh!n6 KyrB{L;MbQzT&!2zF=5{]2$n6Udꞧ%-+]3vIcԅc4Hq_vQ_dUϬs-[% 7*qZ֒T{R0iz56TAYRWx}@"h&7VTL(WyhPaC@] ;q99vGŽ }qeSșlmlfBK t)2Gnu묪>SAE٬vjVlԷ@:4]TõY+Xyi8B>*2+b;ERIY6h(%YZ-6B1]PKB:%Nk&cqo8›m-0@]9ozO Kkj,l ## ZO(iioQ=c!pd>MƄi9*J`9NjiM%U֜(g$)Cko6p݉Qaelb11P%KBw:)]vgw*pX}3JTdLRκ&Ye!1Homn6 D >-n:+AlB*Ie9'RM41,1k"Hx}JxϐeF[ViN! r-y JJAY<;4)KZza)Vn8q,4^T8ki`(IR{%=Yoq`̑[c8'>aI$Ï1ѭ/nhXRin?TXKi/ 98u,=bَMD1\p{"TE&􁙵N!D)^ u$ J=}GCA8mxR*r,|# 9D=j֧uYnM:% D\;!D@҄ $#׭.ZoMm23~W)NC*q0qp/A"1hWq nӮ {;'?]v/\{n~1֮[h%E u-L xJBSM)xRuB3|W |Y!#9~w(qU\붎! RKc%rGGDlhו"g;!Ժ-1}uq {Zeڔ'[r=F? s}7F~Oyd:Yn뛆h)+o83뢗7$QțEe5|:q_V% *'jegOܳ\5fy,"Zd)|ڔLw桴"DpIn,8~H%]{/n{M64[v TSptALxXՍ׿#5Lqow?+;jeDveP'R }4V`Y1sl#(ٺ)%+ q~ǡZ޿ۂ|fIuGn7z.~aWKZͼۣz:ӶA[I*q8B: 5M۷3ns` K`zEm K~tCO <-_L ݹ"feLmutf@8Ʋ# *>"9ˑ8)J3޶7bQKlť!y0 r??]y6VbVYWA)*->><ζ6sn\a;39-~R1z#F[bd\3ůe)%9uw(Bz[+ՕhŨ}ā)%%|V}2 su^SS̵L'҆U\9+~sGr~ kbDٗ2sŗFH`MwOC m~U@w.W:1\ǘ R쐡4Ưnƒ6-11^iC$3۽knNn;uOZRM)^)Ki($ږ̨QdYB Aq)Ii>A=C*.F}*u-?e8{-i*)倄YnZBA%?9RwL1nْp\])8=ubjιPošWO.&At99΃U} --"d#P- 8G e>ۉRY셺j *# u7 Sו)s:*P~$`vcϵޡMԫ'B.JHd%RG^ ;{nEn #IPY[G5W%5u$YrXCf0pƛqlښYB+;- جXͼy%Ryɐ̏ޤ{-+qaCinLaus wԼ!Z~T+uMʕx6Gg =kЭU6 k!Q $)Ķ@P'8qJ02=4B{EUv4h\w+XG:}|6 4J4f2l,wfl\C' m^#jG}]t'#n8z$7[OZ\I'약(c %2Lx$yNRU㖪Moi^(KR_lUCmcUપ7+5>j%[}SZ-|YͶ8%_֔V z>]ebfl{?\hdpJJIdAclƋrS T;%Y-(A)[5:brt`@qIK=EE(HTV8묶eyiW ̮)_lBda KXSWvr}d VWl|) j.MM,s\Y/Hq>a(SYQ[5W"ݠn@}SsȔ#3i_G185f$%Jar@#ҥN=O:_&bWUݵ(㩔bෟ7Ծ':\:2^L(BJ O`F=ƀn'oGrY2Zǃͯ'WkK~X@mNx rJqSε=*&gBeblجICOD:4> Gj4\m1aƟJS7˅GEH:7ۙW"XڬH3"C Al-)W8 pӕޛ~[q謣sX6V g->4 $tVJ+ƓGhءx%Ҟ '$XWn/l yKٰ\ĒO|JEXZe" ˲l:aAl$vK)Ϡ =K{pnkJHtv[Ҷ,up)}>Ù9δPS[>-"8VkښpzTЌCA0TK4=͘wJiZ!JJJU΍[DMNE6-<,UR 'DրloOܪuņ5sPh"d-l6+"= a$ _/4>bX4 [a^%0jq>. )짤 Hkjm/,wl?m2,|7Nq޷}e`ݴ͇_[zDYVLVt. }{ھBf+HW\z1ZFH=n7Yn#u*  yr posɻvзE*vrǀQH|,hG}:ݹ~,L@ S[K\pxğ>e&dl%ȱ}* q* v?蠾:VufIX90F4YO_W8*܂w5)>1T8l4vtf="=5T&|W_eZl4!>#$+JrVr6. %jQࢦ9vGC8ѩۍ6aIg .Tzₔ\q[cc}Q\YTH#. ׹ऀ3&CM2yȅ(^|?"j4X{pUKz[OO(` m70h0S@aZ$ O~{D[mC-䗟GZ @'oVX#t79xgIq#՝=")k$8Gs0NP[HS>R*)-.^ܙ C+BV N830js;i9W Vt+ HNϮ*˖^ .A6 GJҖ <@wE}7k7dL/*K5* Ǧ=t usS !:җa Vڱ,Zyr+Υ˓'2*-\#tYwh6ٖRM[Iy -$8ݧzjgȻ# W|NN8=?`BU$nzd8$p>P2 Tdmiq ̀aSp_{QTW⪽2VIatC@gH^L6[KJ=AZ |ӊU~0?73 :|&U^ɒ$O!t̪0]do8Ik$-Ot7mJcgT;GqٌDy8U?уX7`ȓ6EmC.BSђo%_Fޚw36kVP0|r$YN>q^3T;tQRZLfRx=_kKL5ø,dE|sl/ʑ#_]yRc@õ&|C܎ɏWXϸ 8~8J;>W}k ˚'Z*@1.8n9/L`{i;pmfylWƔnAoP6GyHk1a/o7w: a)*T$I%#$h:),6#gqŅ>#q!L$ʕdiBNvÒIٖʺ%\ ׮I]{+rXG^ᔿ#(uM)>TJжVpCXR·[#*\TE ()J7"G$@u;ۧݴ֮i(غ!_x }1΄mg|V\$ ICR99:'N=Xjƍ.ʐON(qs3 T^AN==N Qj*`ϲ5|hz:*vW13MGktJ\?E-:[ qͼ<4=\so1ݼ?.vَJqL"%#!1}9HzD>Х5!Xh~ 2]'8:wLrRYJl j*/&B8*x>шVѶڑm!#9jPH)0I3UtL(GovBa.#?r+jM*hK|]{)}T9+=21Uv2v{k }j"SAiJZ%)HunPkk)jn|#&K+OJW!#˄{Z*q }ҶP A!7R 3.eԣ0*>Nb-$H6^Vԃ\%xJ}t<DmbP?c %IyoT <̝}Pi!e2$IIKeG<y|X vcA?0PBU";(hc!dsƺF {ft9KK?+#%&235\O\BDdpY@7fnh.zLmT?SJCj$]ei{(׬ɛuASd w~e&-w%DJ,Fm$m\B>G7LkZk2rU_^Kiҵw4mIG5< "J'!-?#8[mw7]:ICo(e(spzzJ;.N̵"@jIa|l*85o}UT&uwL@Y7}GW ztutU IM% %M')gi*X%>'zƗgmd;3szUyI|7(@* :vo*٨&=reGBK Cy`wxV/eeCqAIRG/zh/]xis2&a0W ۣVF"vUa<.W*j)J\8*H9)g5>咖vK-I9K h8䓂F>%gpYJ}ϼ dZFy~ERR.YVج[qn;KJ|% %X#'JvTܥ4v1]34;z@5* \HTR?DhD7DnZ&KW)e頫s [&$PԻ'RV2@NJ>-̩s&7.t#kSTiO42ANc1U´ۉR#c&.{N/H굖~Et+Jnr TeVs"e+Ϩ*nk}D@>b B83߷_waY!>%88tLvl%;y*;TK)Yn8=vO[V̩{Eo>eBbRdDGMa[U;q@n2+Vĉ JX8OSi /ϧ۵/2eg-\Ԧr; 'iR=xZ[ˋ&;Iski,";^"I֛U~ܿYP^mB{ p8BAkeG'iƱJ ƌŠI%kXϡ9Py*ʗpځX)0}=qCM=$]fhOT|Ê$d-.?;==wՄO͛s"6!@ ԒQ߱x{ʹrS`3c*C )ι8өeCph+M6\J/ݕZ8z3i璸 =ۋHLZ[>IO+6@6q8[^ݻdEEK}"ťxDh$sRQOmJ`L%*.O|O,`S#oJې߲z ?8C QE>l}"Q+D9KJD$\ZH$rJZtsԻdiR] gFB 8>4& "Κt9QԔ <J>&kn pxǴ}O QiLmuzAK){֬y}°0 ߠd{e-R,-nq\\qtB`;$QTSջp*<^+c?TS_SΌvKmEfCm,O!Oc#]FfS-'K>:͊4r.#}@Mj¯t0bO)[(iD+#d)6 6=7ľbĊ8` Guc]jm&mo.3r$JWj[YSD{{h=^o%hvI5x<@چ?$.̏"X1$H{EdH%C zBi|N+pX>UoiX?] vO脉Q,z)KeA6;xTܑe`TZ)qʓH%0 irehKRg7:ژMWɅ,%Âp dtNn =U.H\2mM'E%^ @~2'kuΏa\Dd%4n7@$GqW{cZ"3h$Jkg# 'A>n~E}3RQ᫉VqO0t;jjdiwjUUnr:daDuZ`YmM S(#!Ʈq1萖ߩKʡ.Ɩ l!SvBB<4 2i^M+3nݬ[yX:1°y%I #Ev5u쩹M~͚zZ*SAYʰ[iv6ИeJRVPҋde!G%Gӽx.Bۑf,@ G.@g Q9=6:^ܶ#_ŀ'ԟ䗜\k;j+LD/෹\3֚=G:!R!5!A.-,s 'IJGy:/U+سYJ^CmQiS >kDq"\Vf7%!V2\Wj5Whs˥lSW!py ` `gT?R;wuN'Sm.`' Ce*(-FFuE&nJչm)w0G2Y9ƀ];Bv+0>אe|!-JӏG胵|_En?*3n8 zzմs= >Zdqr+L zӭdɌѦuBCOx;:3FF{a8[uD~Ǿ^ْÖ0Mțo6+hC#(V3{QC+l[TdH|:*R[iʥ~fSڸtCmrIzx㴟nu-vOormk+$>lOX@{gKl뎺pjKm=[) EЛ&!Uhf1*I`p2LVPj}+Mi$I'ւnƕ>y0@5JJSzٙڑzʔPad=,/)lA$dc qLe,4dʖӍEiHBI \I;*=^Sl򴓑-rq^Tf?6L$[t)Ǡ|=}}ιVZGњChO#8:n~jE$hj5YG0FXl9H>')~JPN-*8q LA\4,iδ$+JYOk7t7]K6܇ r$Zpw}契*ҿرVrSYr>zwE܃%OK2V`q/Cm#895P~BWe5 GD?UҼ#|`M'7rsiY`S״Ӥc?)Ʈ H 'JL%VKqijHm1=^!Rfmͣ%\=Ih;q{زi]s_6:ʐ r :{LE-{"PSdTL>:, %j $Ѩ7&3 8zR\z <dڔVUu[u?dH k%yΛ]Fϛa ?,YQ|( =)Շ O{t4so#+mD/p]-n{ I}3q(Yt[9ΚYMQ αm*2:8JRi4 26fuLrS wjOrOK`Wn+Jj+0Gb&S'eC83_ ˪yPdJZ#S_P5OdmyNCn-)va^*@s >$̌'[W.5lƝSwuZA qƻHI:,_=%n"SvHZqI[D5Mm^}A&'O$V+NMtm~VkIߦƭa m^$tfm$_W=qՕ@mvDdʐ ) 1(?.(nZ[NY9.H}!_U铫QPm)iSHq0h s[BW?gAmy Wd8R-W$'ۗbWŨgܜ:cN*BTz#3q7S2j*Kאa8ąWJM)M-a#*?]ܗ,qcQ[% B<V\RI rtw5Z{Y(X+y% ք7 n{mKvuT$OG\unNR6zU+uZSmKRrT8ZOA5I}KCmѢxK⤌`]#YȺwV |;GCn9w& =%(qW9kl[HJr;alLKveI*:kg®ۻFDRTj=qrWC+렢#dȭб)%:PJr}l+nXm 94B'-4HL紼z4ɮvImG奣Bz$- A7$Ȼv^V]xr#XǙ`=5YݕdP̄,%$dlmOc9Pǯ}0E$xSo˰!dO2A*Am@a`43qĴE+n7X29)3GzO t]HXYd8 JF/Z<؍Qx|C|WȔ IނSuk)v4ڦ"Lw`28 gӳ\i&&KCAճf+~NK\|U6A>=]F[f)-ƃih]} sUۚf悽mOQ!ӌΌ }@v,7$zރc{viÕubHV+ u@ӋcSaO*;֮ĭݛC1-~7$Nu'Pmmʇf0+/X-k%L%`%?s[&ڦjSWxS/O~Q t:tU頳c~3N9MLDmGiH?qn=gK9UTX* rH#l6PUg>hܰ\ 盵qۆC!+!d 9A Y +hÌ0v8v;/IxJYX WM,^l΄xk.0AZ[Ih.gӰʭTtfETu^HM2G gMWW]iڱAqVPDТʔ1jT>cZcM+iN<Anv,F5IP5Og$;Fw wڦ3"cbp㒕-P?Umϳe@MpiYd[QI@u#n8Pl.Tb6$)`$226⠰zCTQD) >!)':smRVlwt$md̔qd8Q=3a5*=i0 [HgV H=)Ϯ̽1ne$xtH$ܒ4vyLٻubd@2oI H#3}uM 5"b7 \Rظ! c. SScEb,җ!ːɎ e|VNNΘ{kFh [,r\mGף}N(Y{>PYJiIDJZ4q?jFm.USy*rJB1CNm]:qtXAPg6\-pëÌGM5tZHw'ʘCE[KkRAӌcAl8W[V\d^Z)3Ȓt|BZYJO6bVBQVaAͮLuiO!$h rl{%XJ~sn2TO xd$Z@m0:f2,~?T6y)LfHl.KV ?ی.sy푒%CiH)9$hnٶ֧[iRL2&+n%Y!I c/Om N*.ŁK:8>炓u[{QOCm*2_q(un:O%iePڸf;,ʔ*J uEUm4riPZi1`y6x)NGhnñ`<ɋTd4Ǒ>UQ@9 szfWI(r5zf5ҐIZG%pwl?L"iWakFs4Z88Wn&ue}Ŏ⥶z%_A+H \'vTF[ω"-J0ړ?C6;] ZH[1[LB&> >)ő鎎[ttw 9\n/̥ *X<ǛJ*&3^fs{n) G,d+QUUڧRwqZe> ([yLJ1zM:w"c"g4ZIqkZ uͺO̱&KZ8wϦ!DOLIw/q,XC2BbZqo~OM[Qdn7 <*DdYW!BB,n+fZt| PO/$`r:롛fjSM7Fq)[_O^?{ZnR} mR]I P+Q$M y{f-UW֧&>KDȔdz! 9ݢd*;_O-8ʉVR<@{3[]rlWw ʵ_}1hT kluҼ{)풙6oϏ*Lt(%I)J +3צo-Ƃ<* s|:1] u6M&8)BS/)x=eiYE)ЍWeukO\Vd)#]-̣e̯r440R9A=ty-ML"cI8KmZE(:e}4ɕl@Obˏ+:iI'F(l$cEܲA1#)>tksݔVнp4501$$weI&6ݷBNYJse/dm=S}G)+O**R]I$dϷFJ5Yۣ&2<5c>#hqAs뤕.*cu0,qÎ~z ݡ{ \7:An}?CvRYi|%*M]3/km5t1*%zyjBi KI z"[-ʾ3”, L$K R@r'J]ffJ⭖PfÎR*B=c_oQM߼{[-g~ z]J|WgJ_5;ڒ!cJ[Ql= (l(s"*t |%ArA‡'jd9ʵ!b l6r#~9CbACq_SȮ6DmB|t:N}p-nԡFeȯ 2U&*SdgA iMj=)WC+t0G0[irx뀣ְU]vYgiI0~^'ͱMdE^Om@ =5Vzא,b%<2PaA`xRO]*?!S"R~A m z Nn͇l.iK3x\ࡁ cGӥ ;WBeګ選PȝkY+ H?~ P84>Z݉fZ~ѐ#$[kF1@znĸ:"YF!Km 0J#@'qֵ 6c2@ jˉg6 0 #FFkr 9V۰m$qSQJs!yԘŧv=vܞo>2Y=C'9 nUbeQ6Z. 9Rc \:zL@٩%(9xcuېif?g{\p+mFH,JTN05Q"]~DxMcu (!%1j=q~Uⷺ,Ȗyx  ~C%[YL=ʠ )^ ? P8]jnh1jB/. W~lHq?5/ u}T}CJܗR uӞ٭Yv*PhPy'!)R>\g道:$7=iSo~$Fa='C{qͷ_Z۱۝>hDiZ^@J)PAUMF~&e_Vn'⣑*٫,=&"I8HAZ}GXJ[oB%UĢ:U(,\[oޙީVt6Rڀ+Ic 7^:jTj|DXCmCzta˵Wp+;xֲV~"@qi~}6͒!םGI 6֌c ,ށ%Kw2+ޝ\rҌmRJ;'J EUo{"c%2u"x#_V${ k\R-ې.'`[n%N)E+)Rcr{mCdr''4W=6Q\WTE g}^Î1$7Vϭ#=* +9#B'Oۮ^܃K18mH姊')[j2A` vJRqBl#@nSkiG y.`$KQ㴿O >cҞ=Tp|n+XI=:rn6]mdbxvDp@!IϩN7VzϴڵQH|+6ʃ$8u^r>ġKnKQ֩JJS8J m"r;)r&YX YR cRIz^vLH΄=I* qi:P1_oSʝdkf|WY V{ջc7Z 5CikJd$M:9XiqǐzIyʬjwm!LZiq sRz o\VI3i6;S$%(uoFpoon \i$K 9B%fI@ jkchʋdR0T!($u9MehЭ/Z.S"\G?@SNWtzM m-3fI]xJɼ`)q] ^ʡ}!"7Ø=TjK@gZ.lwm[A('7[߉﬌K;ip[P! g铏MBwgJey+ #8̕-6G~5zUջq*@T˩u5ۑI=Ozal7lH~SbBؗ*By~{RAN.|o#q%/Nah5( =npT=W 6닋^tQ9P)D+׮G"Ώ [)UC1C$)Ima%9ejp!IJER$/RӐEII[>r-xy%lԸϷ9 O~z4 U;'kۿeo1vw 1$q d]:S3<*$(t0yҒT\}u腓S6ۛF-0mkÐ)씨!x88ztVf+WwicK4w o!](-(#.pz:VaUtч6L6aEqK-箊9q4T쭷 *lv.nIy<I Py7%cgʼnD.JTrG]@j=S'B@G4G8QO$(=>[mZ^ '!HS2'`;UgE7>+!.գ9Ǘ t2=dv750DT-RNInr2{H|{{6Ҏ5RGbKen)΋^ n(zRB-/\RlGzT4YSï~]iH]"[ q:g׃}I΁-2; 1}uk&15!r(p#@NEDٕe jHHC?3!>j+w߲ 6~dהjjZK!$~3}ܛSċN#Rd(>rꤟnt6eċ+ [Bھ#|݉.BpJ8Qy>v l:*wָ(%e'I`}O UZj75MVT NsAR}t&Sjx&Hܧ*+@ٽv|f7\EaYeƁ%M#.mG#1%XOiQ%}r Tq~'#+UJi\$&1im89' %aC:|jbYϰUlz+q)$K*'TS$YLkwa, ]e!C>\+OcEJie9)JmӾS 1[-U^+lu'he)J.O`oٳii&[aQH!$rI*IrոZ/6^VZq1R1:[E<3]WPZi)Ra/ vN%JFOX] sVGqJ@quĔo۳}}S1aJ[m;yBx:0t3Ғ2'F\gv^lۜ7e9vHӢv5s1nIe-DyŌWIK-Ÿ7JzZ#8i6c DrHIi!h=@a{6O*:mh4ԡȸ 9p}Sݕfu5|V,T|'e>%$IHH'?"bNXI_O>&7ۅWp6jf+ɯ|&'8_:Fa1)#{rIS0ͦOuA$% 8 ߔ1a׵U5ҥ SN8B8#TA)뛪ۉ ;V6+Zx֟coxv$yW]'zH3nWnkՑCiq\a0O^);(n=;>dy" =8sAp<$Z ijVu\&2%'!H NyK vN}}y?i+y%xy@|݄$&,V܄7䬑dziqqUvlВ\u@ *s(BgZm۴!WP'. &JԠT cBJҷ}?OJ'% !n##=$9I$Lu*Ԇ^BUIH8:Jڻɽ2n3+R0RC/DAD:g!WUˤu+TG~CN-PD:v7&%k;fc2J$Ϙ|(㛅醀wO724V% q 9Ry5 )YHғmq³eZ|h GB.usQ);($9UIGJs%+'Q~υ2}S)K/غW4 ZۤPMfFĭqƱ9ϕx(R >\gv^lۜ7e9vHӢv5s1nIe-DyŌWIK-Ÿ7JzZ#8i6c DrHIi!h=@a{6O*:mh4ԡȸ 9p}Sݕfu5|V,T|'e>%$IHH'?"bNXI_O>&7ۅWp6jf+ɯ|&'8_:Fa1)#{rIS0ͦOuA$% 8 ߔ1a׵U5ҥ SN8B8#TA)뛪ۉ ;V6+Zx֟coxv$yW]'zH3nWnkՑCiq\a0O^);(n=;>dy" =8sAp<$Z ijVu\&2%'!H NyK vN}}y?i+y%xy@|݄$&,V܄7䬑dziqqUvlВ\u@ *s(BgZm۴!WP'. &JԠT cBJҷ}?OJ'% !n##=$?5{xݍɹe^ZHZ¸0A26KQ ҶZTd5Ng#) ?3{_ks*Rn9)$7=+9PEe]k*X?,!Zb,DfR섃ߢxց/Ƣ=lG }\<ŵ0@{cK{y;Zt1y/eJ>/tQ-]#9pGu+]SG{|Uր LpF@AvgMib b㭀'JYy|@=y&H͜MċWbKo2FDTKe/F=;d[eswV2\&BV[dt- 8H9EYfE zRox\h,wBfbT5Ϯ{r`e^,K.-JxN%T33ֹ7F%kaʹfSǾHZ2@#:7o6%ήjlB[lӺy*PϨP*΁ /7nwP!oDi ‚ y|cLΆ*fSx1gȩ \Ҳ\#'$k/ZxT ɭJ2U⣊ |e\m*uϲm3V@$Iϸ:UIsxz|rKxiԃ럈}Ε+ݻ_.K."T'8- !) {99$ fT"uPe)] JRT8C|~z 1cLe`r=KUd8Ӈg`{Uq)\i^4tĝ20uq :Ԯ(JּzIߪg|݄u:3KCRy֊]9S$#E7^̲uBܑZyeX#YeIK“jr7 vv,>Um9OJzuEva^lF3n) R};%E0RWyZWɉſr,28| )+K:\L ]=Aӊ@ o])~1|cTRJ}jeIR-Ww:m׭v5ےbSr ~tRGaq:ʮvJ}BVr{ҵu5kv٭?ʵ'c'ZZ}H0ۚb5rdqA|}0Wpq([w"r<Ma<*rQ0J;RUYКi~7AAϕ! c(m-Smzd ߒY%6_>:i]Ao^ƛe_kI}2R┧%<3nS&kܓq+#e!X`4 wHQj$q+QTVWD4\JU%60S;utɊsVBKǎ۪>=kvY6&;ܘ$:]I JZ{ Bϔw]E}D9z]lJrbė W~rA%j'OzFٷ5,$Fg7bA䔉 Ryc9X[v6ꢹg eеtu8xF4zE}#uqݜJ)$>\@'A}5U7a2$Դ㏵p% -k=``9L|CdH;iq\/#$KCKsYJz.f+ߦ).*T.a}Rdtp;:{ЦXHQ&lǍ`~J9?*+A>Һx͏|xfB"([̨KJPN$}=]h4Ydd[W! h c4Z仫Znz[ A):\g%I  D*keRH)O4a.K @ X`'=\ѥ n7 ߊq#YiѿH?Xt ]wäm]LYƏqߙ ɑderPezǯ|cD6v{|IR Q$NC8`yZ=}tzJnݑ`KUJLI!$(9 U>-|6Tsq YE sP,`GaJJLM;xV]k??.q0褭}Fz5lo.lMK֬j4 ')>!Tָj5/m\6 T{ -q> @z2-mOXMioa|\Cg)](4A-7W-"[ĥ;$}heβ^o +}upC$J~oIGbCmH Dh(dq H'7\ڌؼOubGZ]\*9y,xL9c^P#%`wV𬻔~~\K\aX#I[ 3kF\ؚYRy4,ָkBQǪ>[,WƇ&ghN\ #) gVSL Xm qiY)OA*L\ 1>@R=z 3[SP/Yu'A$s `b J*/! JP? Yi!W>FD6@P*Qqkזq#J{'hb;J\neMmLqט@KG$%m±G.P#|<$ց?p:;%aP6y %HE^2J 8-WUq }K2ϥ&RTG^V6R֋Xx3GJ0HJbqId$2O^çmo4CXdJ.C 3=gAr;'e" L-_a)kqY( /v='؝}}b؅Z<1x7Suj9Tf)Q1Хʊܡ6i*H\e'$({tUS>mDp[mX4GH IǠ*mvQGd`τz@a)gNL 9 8X3,cn%#܈TQQ`?)Hy7Wti{oq]hU0 hIkPpgׇzxіikj~rkK}& B[?JqG~G6z F)na'i&rn%/` ,}{@/>uxnHX C'>G% V{L;otJ@J#GC&38OV猶G>Aͼ归<`)Ȓ~m A%#ӗiCvg[0iƤ(QI HV8h>FsȮ]{Ls-iq(j';Fz]Cpm=ϺgCqͺRARKg(9a"7w#q>YɑA[(>!t&wcSad8r1 o+؏)fTŢU݆ۦMj|{Z}6HIB@=Ntxn*dms!}^ J8=6>Aaw3JzTꉰ"m-ǐ@F #~gj6Cu.ڶQO㬨##)Ozi{dEw.V). jq -g_(ԱбSiSsݓ.T O!?%E9 q-5SQ KP <[ H*0H>h,[Iuɤ~<[G-9âڐ]z0~ceḧ.3 ջDAUc$Rt%88Ruaͤ\ٵ-K IRHKgyA2]ܷENUSiz:mRA˪IIV Q!A] #^#2>+o"QaC"C@GPQI[%ďB %2 D íu(7lkzU-Q!KaN-KNz7jZҢU7 +9਒=xuOl݃bqCmώ!d Jꁓ#wRDUsp6|{,l$xk  inV@)vl gmK!O˅:G^+[.eH oRR 6TQ>>ŝI6*k\*TRNRVbFr{~WwɲڛmEulYfuIy@-R'=ijCse]XKCͶODI3ȐN[j^o|HK po`=}=q۸]UF vm`'_CӶ2\ o5g>O^p6֛SyDPSY>lK Ǹ][?v1߷2Y5䴿)V$1-?#v &5=axR T7s]PV> {uL;w9bakPφ 299aC~@[>=Zt0k|$^$Cx~GK_w7wPxװniIP!!NBdS {/ڸ f$YV:5J|T_?9W!! 0dtC@S.w75+ zɜLsTc0J&yR"s_3!+.6edD롪Sw*6m)aX\;Q quk!@IzzBwd̗.=lrA$JU\oˉJ~z +[ͳs2֝s섦3Yk%W`t:$oZ$4@/E*J QItɹ>[M[[XN8`J) dc磥+fc7)!%ʭL2 p Ζm)rsDtᒼ?yٜ[_|?%c@nӥ\a.I[ ʯʼ:CuS>!̋&\xs[ZRSԴS_mT$9D!q#]HQQQK+.>hep̈Rt1Oк[Eho. .J:#ԺHe*=1;N۞lFr=( lUEu Q9Z\*Ps5ٷu}Ȇ܊b lKS}0Ƕ̘W9MñdGǜ9QHq%D3"}5٠1XB%$ RBj7PvӢ6ӡ=!|P{1}*7`GKn+J%u$u;NiS{mi)h[OǂHJ96Q~-m:wx&3JJCbUQ=/+nVȸr3kiB^)p}PtqS `?ʄ}BMYJ 4 "IݒRjRpI##$Η~noĸ[BTԙ!KO+ !Ji#|CnȬJf*]q/Ӱ#׫ڷN-1:Be2`$y}h+kkJ6撼EZ\yhqh_MҹIZG#8 8IPvW%%Ďvz#aEEGG(D}Y7cV dqh/EV nK-|vRe %+o A U%χK%̟C8 J|Dyz^u[3pvGêf,M4s89E9im[n[椠?<'$8e){ӵdnZRf;qV6Җ]s' %9@kv۰`miΩKKs*`@)g-: Tzc%NBYY/)^Fq\GtV>I_[[aH8 S]]`XOa u< g9Ǡ q75q8J5k}M3:\ J:NDn-PeNAm W$B2y u>[n;9 .p덜|q1FͬMջ|*i5%%QZy@yжP0sAh]* Um"d(-Kn4rR\*CK9!#Y_:MM}TV-irl`k>H8>:ֻ:5u=+ J Ff9pdI+{9jw#WEpجݐB-Eqg!OW|ڽU7CƟg[HBiy8צI2[`Gl%4fCO\[dV~G6oQ+M6f%r%6%*ՎC=;]^+pi>%%v!+F֍mJvnbexlD-[l]KAC>iD,mUANM_ma7#{aɍsҙ-#$jrӰx&D]=@%R}=6UnD&MQyRNBQ -IC{>;6k%n55sN%IP)($D`sFxy ;ZgR3Vܡo-[TWTR$#L-Dgqo]l[nTvk؊S3C,zdEb,~:~lY1i_ݡҶ? Wŧ-]&¦]oj8S)!)PVIP'AE;vۮbƒd2Ͳ׈PP?)vU.¥X_J$^e@ =5Z}Y9ci|2Nl%FOiH=(s諭\-N)3(,:Kz`wX_n͵-t*%}k[Ö?46쨞Jd.\zIeuŴ )>@)>1tְ۠Bcnm̎;9Z$ z6z s1..v~&T{uZSdn\R[SlƏ60R mA@W҆*DG%$A)g#s՘5bkVKВL!HNh8Ma+E. a^$)bçKanbAq8%h z:,8ʵUKҪb5V<x<”>Lb¥4iU*C%9"Jp%y*'zBi⸤:^Y'). cF@O_Cf6ye)c`OyHIc IѲ>m~&%<jqZS *R =v~9V*$1ز\yJQ'$8`LA3bmU76wLx᱒ W.1^nǛ?!-x2Zqm >b>dmZJS_cL4(%)xH_e;k kaA JN2`8UQ0䆖%a=[\RpR xkqr%ZB.}5k4Wekai}A< 4 ~ZٮvjšuT>y~*m},?Z={} HO?]*iZH{5Ŏۯ)Xv\TUI*vTHCiܭfÂ$̦B)žB=rp-fqY"%mϋ̧JH kynػ$ˆyxm%I'P)x T\Mo(UN*Y RŒ RȭhuĂ|ǮOC[sv[}OҰ2$ 'jq6^۪l탱ҘnMt aLO{nuv=ڹnL TfPB J*eTU~S|̥(eEd.`+LJΦ0+"UHžqmÒCG2E}FڈdÃr&KIm"3u)([g$码;^Nv^اyQS++jK rVBϩHN?=mԢi))>-RY@Krq'>XНv˒/g̳vgXASc>nD֝ԫՑ(MS & @:}rB >gp8emyLv=<ˉ#:ͭykrEQ1dV=E%UKbnu/T%qY9Ia@?U$ڴJKpBȰxHnEJa׊rA 4ZNۏnGuk %'@>={m$nP]T0-D<=i;i2Xع0%.nÈ֝˷mwZ;vd i ).! eŹE5á[IZmG<4: d$h,uIU]\d1U[Y,@OXՍ>Ӄ&xo6"2^-xrG5mZ 3Q+*OJ1 ['ǯlp%e;bPtҀp:cހT {mȱ^JbT-~\gq߇ɰ'6ƓoDoЀTf >e)q|N9^:+[O0'$4T{<9>gX[wU-GȦ5c+SU/?s 4TOqDCm.dF(I Kp0@uAr ȐC2R 8 y Atkn 9qf+yfP_J=sH8-\[;u#qĘQu4 $@ /J13 ڥU7A!JXSuAjZxO>Qhkbr߫}԰ۏI@V@$C&uQmv:S I;n )ۉyvNǢW-´*L?8 S)_c׽]Ry2*ۇ?)ɎIRH>fR2@cmgSbi*UMaO8p!~#_GfmDC2al%]JG-s@'wLjlS<)%9+!DԤ'jQ]Hv Vu, %8``N[em Ye3yd`a±7"zkNUgȔ|wz&ʓh҂rZ9I!X[szn2<Ԧ;d[Ă}Oּ5"omc^2PTp|+WJUS*Ա7|_úyYag aZ%%!SdX<$7"Xah%M0kQ9 -KC'nmǷ#}ډtJzS|G}T v6QXͷb.K*BZ~lvݝri,l\WZr?7?{N۶-lW2M[Ao ‡jaU ޭ6J@rA4[Qu}Sz֪-¬Zi '~CjviY7n/E\5l6Ur6ٖ`>2iBZ@WO(J+"LBZZ2[(X-TK^UgrTWfntVvUmx9*VO>䰍jχvP* ilKQ >Am QܔͷO+ g.@Cݱ l7 Guan"WL좰_m Jk=>[*1C{*mhnH,C$ڸ$C@YUt@KEVJqOݺ@͓sR6}x?>3H.6 _uwPn9jVK  C@{29c?WP]ẶS.#*KR|V't g*BКW\H? -kF~j a*ۉuS9LکRDy)%$S cJVfD ێ5;STjK +8muOC8ƀ`+} d%[[ܒp%%N$YԾyTJ:Jq?6 u.8Ms}|X/0ͬiHy訋- t)(9oAuY[?mVHR2J55I*tkpc׳2~0-n*yA]!lMƟ`_Ǿ5<T_ȵ1c>8&0/i$`:yA6z+ˮokq.m$540k?0UTVʏӳ:6ӎےԷ&u^f::q sNo) kϴdʊ儤('y$NZ[Fm^~mRe2is\}N7!]ғ.} κ~=߈4;Z5|gî-?N)$"> ܶ饢oUd9 c~yRIOq|+qS1lba2&=aV82BV2G:[J_~֠<+HRsD"~v7bXn<$1r:qhR;q-?5aTsTndXFTYO6Ŝ1(VCJ`AG[/(`0_H8vJqk21>Vڰ))]qAeGBFF=8OjHJIj]e_AyuG JRKA){w룖 f*zk."0v-N?y-X7fM\n"ObHm#m*峿9UCwTL-.|㤠HO2w54'7Ώu3]&;m^b\l WP$5[_& ,6"[P)*Pp_BvGP|W(E$2=qkU,gY:"-2І+ʔ;)$*~Qڐ!Z\1- |Ԡ'-9aK(mQTDJ|K"N;pܫmIup,d";8R8GAd Zn euMRMpȔDzBqwHƿ{@J'I`#l%^,2+V96q9M:Yqٗ. q=Xospgs?*{XJ]|#)(*)G_F|;n?ktN}hfMOSAOi*yF?„r q0MU4LG+J IVGY=uk[M"/$kJG{u՞ܬfOٸfr2J'JT8iZL[wfQ!R"J%pil u}\M$Uj]zy~!' M+,du셰b«SmaDr2 ihO?ǾSLay|,!q9?yI)(@.r'5nٓI;ָDuRSKuŧ815Fi6uTkiR7-":LNWDyJUkRT+;8}+p?\j֞$ ʷP|'RoO`(/˻-$ZֻN,#J8OG~mV򲟝-aY\$xcY핸%?RʹȈqWmxO)mzt[˃['m*i@3bڶQP񣡢Fr|܁`qJ{j-`fc.p82UZ/[7pqT6Cl a!#>SZ]Ҭ&]-\q1f-Ĥ7*G>ֶ(4v\3oA)|!shNn} ;!]j/CGKj@RsHPAk4mmB*ٻiʑ  ( rSqS ,G*OK++@}>KfRyKnix*ET< lC][8wq@acKGCdGAbm2},84n#H@GrQ'˜O]emSm^AqLv"Bh ů< hHH.P~XdzYd ,v@CkƏ!3*0lc# jV 4`qD7o ;ٚ"BSe| '^ۯ;_&ݨmϥ*i.缹y$XԬJR=mfZ3VuKmJ;q^H)茅cM4Jd{2•UQ(sKe痡W*L.wsOļe ׆p2z:vB1YaU6 9L4'@i~שO&0>@ʼ 9tlɤHtk\":N))iaZ mҤn[5+ *Dt$(H<뜫D>vʨWU%+Lw 6pIi%*W#խ=UI6UnOy:3p) 8Q=Ӧ_v[Hvd)nXF?q D'8)Q:3e?:[-nIe^#Lj)+qK~i 0RA('9_~J/+q64+ y x .Q driGt\AI2^1,VCed@xHVFrF=4У3ڭځ9NHinR\Z~ BYZowdVTHv(2j -NH}=r4G/h7NzTҀfūml0GCD4 㫠-:ZL!\}vqeR^n*lI|)8@iHBF}|h48ͥXM[nHb[HoTc}slQnh=5%TfKRCO:߅8Н^Bv+C &_DԀ@2ރl}4EŪT Qa`y mR>Av++qAEƗ-s?\%#{^dXpiG/6.O99>ڧz2ڼWTliD_y@+y6"\(2' fX(כhCfUb`(G̭ iS# Aȷ.jFܔn'SVJBg)Wƺ;*QΗ}i/7^ogma$ꐵ{U5~|Yy5ie"UqN8GGvf;5ڮ0K($EoY`vF$U_o|7Erd8u'.; rV5n1+j4< om*lj#.c둪7{ڪv\í1)-h7x}E0HX{j1!CqŴK Q¤H%%XzIV6.X&kW&Bԇ2@8U,:߳+CB bOägLe'T|ɗ7P>zLgbŴO)!#BT=NKH9쑢V1(:oݞդ)vAW *pT㱍 v]z *:PSWe| B=t5ͳT6XUJPXS<zo{`\0L[JB i"pRA#F̫iQ&a$% `(:TJI'@gkػi)Nr,-k2I_Cm$]9U;hQsGKwǑX?x |NHժv"咬ZW)m@!Bz'~R5V- ۨ.IBShs> ; [{_Okf&IǤI>ऌ{?MP:6j²HK1R# WIRz9^d{?)ke d@pK[~qYlHnhw=-ƃ1)i$𕑟@a֧JzaLnd|Bu:i!'~fT :+ӂ_((s'#%::lfMٙnˆۓYRpґ௓+A`k(vhw2.+ONLJRF2Z#_ l5/oC1eVUx  nLɶ\M+H-XNsr.uQCǀ LJr#[JRG^nwFp*f]e([ьd'+_jQRp85Ns[1/lܥ5Oo>#Bgmݼ+7,З%DK2QqFiia3o#J{+߉\7 !\ܶuhںPψ ӟYӑZ 5x{o%]=# y}0[V@QsYu|B[ii+F{@KmꚻjR< 2ݙ,JLĥ7da$d)νMa25d6XQZ*#̩JP =c$h9ý#=]c0u5Ȗ[ARFXF,7,b:y^AB)OC$aDzm֋vJC&EivJ3IJYYǩJ67u-0)rmdq ' OJȔ1[a:Ǥ!- =GZ>"[k!<y`)Sה6G_7nmUoY$)-Hj%k-%)5h2C9>k'"]P]CSlIs^{ mxϔ@P=T{[):GSԩҶ՜ F}zŹ^,԰7tfFvgIBx o$둢?s!W]̔"KK(%B3~֚}zf=.R~#l8oRLz L;;IILwYZPNA ?I zօvW=RP"2IAR:0fQKm*[ iǙˬ%̟+hl#S&wO=<0 Y$BmF N>jν0T@:҅~'RTR}pyr5 ʹw.-g` d:99oH PY2/2a ۿ)-  vQu0OMUp8_BBZmL1߇YcŏUͬh[L Ķ:#4rTIv=FLf'Ң>=vuSg{phl8rQFG$FF|#]o%p~֋=xA\@PkM>r:PRG`P۞،KC 0Kd+q,ܺ\{q1_䶜F GH ߗ؛z[wJZ(p ^A볎 Uҙf͝!8vxdd!P=%|{Qݻ-V, kHi*K.MG@a*dO׌bXsjSͨ.6pAʲU}>_:Ҳ= yŊMI.ĞOY>Y=Qfo`nhԵmL[Dpp՜y9FѧWRSBmBBW+0Uh 9Η 嵾mm)iL<%9a`d6pF`_qm91"~CJԤ'6Ԗ"-ʭg.(ZRq3 GTY/oH'&{b}-zKSyjI*O|Z0]*%uDF '|{q5ofZ Q2 URh,d)i2kV5ˑ"BK`TC$Q^Ѻ}ΆlPGJ#)8ԇnkZ>7?>=oPӥJTFLSTöE1%4䓔dd ЀuNlk_GaU 3ҴYIL9$yz*%mrmdb!c#֎a?>_s}uB߸l9\ 璘>N2Vs88ZohW%^EwfД` a$}Q׮;>߃H V 2i$+A^kR]NY?,Uj|4@sZ,C)r,eYN0%xO- YA+w|@2IS8;B'ʨ`sނs% |g:f^y=)AzwMxhKKc!QR0ae3aWnD@q ќ)*) I  }ce|ztJz:qByh>nH2=VˮiV0^Kze(%ir"JVq/wCǡ[?{L-=Ozhret-RӀZiE8Shoz``wSk*-2Qu t ZA{KGw1 m<=%F $Lr>2WNiMBpei8 I!N2r2n{AfGܔ ڏcUo/ (+Eg+O(74lk_GaU 3ҴYIL9$yz*B)ɻ3&OԠIYQTCqΔ[͓`y)Q-g8|sÎ I^yyoqeBR<5G^8~- 'uX$7[!BRy9ڿ!}"A\Vdfs1W9%L 3e\HS/g-X?C3ڍXd;KxJ~L:ȄHRGؐz [#mKwo;dU[ 0JB{0>=dYjvV "tȲue8h{-<3e\!QR+$N Si`?iɫ%L:S 'x*N1ϗOz Si̖'Hr;UxIS@YL4"+ߝ6QD\u..2RUʐF HBR3t͇ͅ]]E<'FrLr)$@*H=NyR#)Cu! 炥i [.Zz;-Iȉ)Z%o'l#ky0.H=颲ʴn9жKNjSAM+D遂{#EMr6vB B~%GiԀ}_'Aj5-vv O΄A$B}1C];©6 K)$x8ѺeYrST&zj?BKIp9FG=gv>'|v}MH?i0y<<A?h.mK'&؎uDi*-E% 4 I{#Tw" i5?~i-/'B@Is'U TYXn&a.4-`P 1y~mgc0+aUí >%CN5T箕K1sl|yR^y#Dzq1>0TXe[aDx6rκ [xvl;BP&&%@#(Dd%A@gaIw2|VxE18PRRRpr=l <^+: xp% 'M3dmrE$B7ߑå%*=wKsk>TycpBYJ l^Ὂ^寨-1qRCj+-#!)JIyiK~ʾ֜08z5, /;=O-Q1-`6Ukm3U~7O ~"r߱ юg|BqU{g[-uRH_O_m5;n<["SI=@u'g#A7݉2kިve)b)y%i$ OCƙjQc#qm?YvRMkpa{*RPy3ۉM[8"lg0)+y z vjё-.ηwB%x**8U#~J+#JS SiJڲBatŪZdGwdKN5^Y`eÍ4*A=M褸z+7!D Q֔=8([+N@ܞ]\4i0FS?x#:{t?Q!jB ~[/ܴRWԐA4Z橈3 1k%?L!I $V~✞ołąf$ TPN$z^s~KeTGllYqSOAHQ%e};ۢƪ)'H[ )$-! q8'dAoxHq`66d;A###U,9 ķ|XtDy8B0Sei#ȋ \ti.;<98dH] BtXwg&BCVBԣoMmC{z;T7T}_ҝRC#&=ڶ[YmV(2AR Y% qIoydJZnʘ&2&0DJ[Tdg*(A`7 ۛ )U%q+IP=0rC2hbRW"Y-qJֵ'׬%}iSa&3WƝkS)$YeI (RA9K"s]- MUau~'Kq-0xy',n)4U$Ȑp!Xqj _y'MI] qk) O֥%^: \UE n:%OT rdV] mRKii-7 KlHrAh 9 <^},'m(Qn4!#a+=[k QʖH8>#;ھtaOm*^BDM)C8)RF}j ~Ƌ_)(\ [[ͧeO<Nsz 1vu.t[S3W%pA[]Ivq?[~P&$dIJkHR#VignP-|/Xa91^i dI쨎<+BqQ|}f=*Qm?'<ܹauZT9*H^ \ R {cA7lZp2j 7!BO맥rOEom]q\LKp+-S+(ZU<Apwn]Mi|m̻()XsFq!fݞoS5hhŒ0΄.Ws_#:Ji"~#FBz"JMKm 6On)K>"{#BӷٛNM'΋!4#SlǓ' `v}ʹ~*x\\@JkuuVNWz}* ~GjRvA蒵kN*ʭěxGuiyUωy$a8q̡@s;K*$`Z]28r u}cQhvaK2^e@% 5oI~ͽME !Ҝ p0ґA>;4om1IBVM￈_7ٸ۪qÔӛf3Rp19c{}g\vD!ioe;qe˘5mP]Ǐo[꘏MЊV|4>C)qj*+iHK.Q>2<@목K$H_G.l!f!F<4.$(I*r֖7:"bV2DP|9NorA VЉR܉#[e'\H !_]J6m^ܓ!meLN@V[z@QίmU\+mnF\ZO)i( d3޵m_>U^T%7>L yד`q_;FN۫F|4uH%>݅z򃠴.ӛ mȒ H Ѥ ՜M ߓ84i,YP9K_d=הy:6\)Q,%Xn,k5+LTĹ#> q*ZNŃDQ=2%(} #:Q$ds΂_*˝ZK mHe PKW$(d;:i{RK_.HR*IW| mn TN wC*${+TmRձT]6J)lD!52\ϝֹv3iiYd6yq s 1ӥ=+F ;EqVe%N6ڂmyB~hU^pʄ넸"_ڔݐz$}L*ʭěxGuiyUωy$a8q̡@s;K*$`Z]28r u}cQhvaK2^e@% 5oI~ͽME !Ҝ p0ґA>;4om1IBVM￈_7ٸ۪qÔӛf3Rp19c{}g\vD!ioe;qe˘5mP]Ǐo[꘏MЊV|4>C)qj*+iHK.Q>2<@목K$H_G.l!f!F<4.$(I*r֖7:"bV2DP|9NorA VЉR܉#[e'\H !_]J6m^ܓ!meLN@V[z@QίmU\+mnF\ZO)i( d3޵m_>U^T%7>L yד`q_;FN۫F|4uH%>݅z򃠴.ӛ mȒ H Ѥ ՜M ߓ84i,YP9K_d=הy:6\)Q,%Xn,k5+LTĹ#> q*ZNŃDQ=2%(} #:Q$ds΂_*˝ZK mHe PKW$(d;:i{RK_.HR*IW| mn TN wC*${+TmRձT]6J)lD!52\ϝBډ蔧eZ.on;qUFۯUL~+Cp\>[_]5Dڸ81g!@TmT`߼pcwNKa"='Up;H V[=k;}e`rҗʑȠgWunnWwoJFIDvTfyy[q+˦ugw2ԩ2v^ߔ8p0tvpV\f~u'Ȣ\HA{B+h+$BETEŋdu~4!,IQ9:_>*7d*A+;`*gZT1s1֖6ܛ˛耧ICWR xY$O^`Ntm:ZOyݪ֒|ao1HzL՗j:妋{n bJ+t* 7ģv}GgTC+eeVLN>`a,J}x\jۧrNX&CL\P󸶒VB2=t {4v6v-4dU6p! O#1? ~v}Yʗ. PyQpH.8\Bw45E.d*J*xO,dg宊m*w&DU˲fKkt180,6e*sSS5rkv:x+'J;#B7Encg&qϕ5^Z ~ic l0BS?#]pmٰ֊p>e)H 2 +'9V-lh8aqNӉY OchgmW6U L-)+iJWYd}Aј{Ǘp!ڦTy/䔩]v@ʉLǺWo8Ί)< eoz;BvtQҲ"7 SHv2vsrUtD;O%G(Q'd#:Xk \Sy 2O0TpNKg ks"⩕OV𕪾CuR]yT 3N׶֕(i?&JvC c?.S2|wf[“<%LEmBTRFHR =Я+k휃Ӊ.u%ь|2G]wWۭKLjbÌ-DxPp/8>cnmXWwR|Ǐf_G^t6[`u}{0LN}I!|)m/4B)'Bॼ+S7hИ,7S9 WIj{H.XiӱgiPP3R4Opc9s#Lujq礱,r'-$:O+:U?zs|{6~?.4sPJPyp'#46q]WNK!A2"[2AkPƪ_eWn6 8(n;EyJbC'wmDũU!ܸ@ʰ}O4[O/Hӎ>XR}FHH)R:vaHua 2S7ʨAKe 7wK]w48lmY,:ۇlj %?_1Vrq1d>BvqV) N7]g[_!F~#FJzi$7 VSu$9Rv3an\rW$CgӿCiUs*mӴm֩.ch/w}vx3\iEOϏ̓ȫ =,]\Mn)3t~JQY*m( y8@g>/#ҦD:7(ܐ[zC_8Aہŧ889`邚v3oHHo zdTR)Uζ&;ОqYi_0C*ֆ <{qId(M ?*-a6;kz[Re\^KCRI p<$'ۉXQn݋YddTve)Iji0IRz3o:WC-CApu~߭K>[bc0[-xAA^F T3H%TE Pzs^2kDe}TH-88y߾4v+tV[3PRg4E.n!x8 'fש;)+HC<.rH\Giz zNk{Q1BARZH՟M1\atN)G# Ps8g L6qIX JV<@}~K;b<4gIUL垖q߮vN~CVRT( i@q*9y6H2$!iFz -9L{~FjC~K N0u1ބ}ӍȚJ,d(W4Q$E:M_!BhaPm(CgC\;Nڐ+*_\hrhҒH[0!>NwZ %uFZ{,)JNG\WLh5̵ ﵬلJ͋ћyP"TXj j^1 [ZlB 00RD*Z(MRӚ]Z#-꠶ZAi [Ҷ٘&:9z)u+q ǡI?M~ߛ6NI_BDVߊs@=rr;N{(Sךt[LĒtb^ @@ƬiT*mSH*B8'p0}5tV4RgfWTYCh|$2粿 ~Vޖo4[YFwԇS. 4BkA4EwZg(cWT14D}}uf{/lFl-!)Gi'&;p=p4J[I$66 )>b\ZI )8kV㯧}϶\Y*Aߛ6ֳfѵ6Sxo*B|9x *Y*$=r=&MH]nˏa) >낛lI r4ߴS&=B~}5Z9 R8zx.q΃υMHݑCҦ8 [Jh+\[_X%]k)T"vcX\G%%+[mzu=JꝯמђuKL?PO$yu5rdTnrB@bUGeXq _m}*xk˔JZ+[܇|D6 29kb|kVR|w9ܭ[an$$ځV3bu.v5gܹBzlZ|6ԖMooCr[usuJ,=4 SVś5¡-+q'BN b>⯢KHiq jQ RwMuY;Z]k)®CNP `dPD#@]z.=pzL#Tn/m S*HLE W!eJP脥a$uͻxn©knA=ǾML܎YXŒӏ.GpW7=;@=4}]3fkE.ڴEoRPe.:Ǧ4܏"¶ASd[F-@1!*q]#MWBb`]YZ`*a*)r`>+i2Sv;Zi9 ;Iˉ󫱀{񒲞uL=0eXJ2I9 dMWcӖ$ť s _촴c*S2 Rj  m;W/a5Zu `0([N3Idg#՚Ѧ :uFٛ *#c1s[}F̼Yy3BՎ ]S%%*j6B->Rd㮈 JP$%"ByXc8Np?:Ш nUuwQ#6qR<2G@ih~E[q~f֥>JPq) }$imiCdVӭngZR `V}|xsyB0PJ@zh}mW|XZ+~BxBAA̝LNER- k :2˪O<<ށ7͜[gtԆ]RHa*O$‚dHi6ͰDWi**0TR{ǪNqq#7BZ- ̸=H/K2j raT&}q9[dVn˩єߊey T)(1޷BntuJ 7+j+NLiPO 'Gctp-i~r|ME-Db0d-RRTkW7.ڊjiW˫RQF8 9$}Jz ;]ˤT5p%Gz; {FŁP[+.bT6i-VrKd%9(ѭ{zJk,+ Reʦア% TFz][o\f eR٨ǔ,c242)3u{e"uQ\UHǘპ׵;moO!U9lCPBZ((8-%U?i1 )n2IYm-̲sd^%0EHQ렒_<~y(QFen_HU&?̿l'\P7K\rr)qr(nBȜ!翦n%aˊ1$?*q p *GU܂~F + <R1wBx VxRHYSU!,ƝJ*an3c2}Ƿ i<mgqָsCۻHqϒ_.ڛ*%8)'Pn~cW)I*,O>Y)mxVBa= Y6(Iv28ANNNr5r4Mm-ݯh sO,, =/vGDH`&Ku\[ h47E׵TDdW!fRҜtPg~>%f3*lI̦e Z8%@K$ULfdI(SJp8{>kLR!ðYR"23)}8u*m3+CvjI&E|9e4ݶU$ ${@`7TiƪƿoB5G___ڭ.749?hRzWbhٶǺ RbGK?+')J8:tߤ2'Y dUB]1x<j -MiNC\}/l(Ckۘ谈+w2ӓ(YyiV: g5q4NvhOWeu"d-*IO qdYO벉+s\Ғ>qy8_(('$ttCHdNu ȪFb0^ysA)X)ե͔*ˊkzζTA*[a%!$-)Y[kۘ谈+w2ӓ(YyiV: g4!mfvWR&I1ڰ<(ҤO呤NXn(5oCˍOAl<@$ߣ|.٦QSPfÁى')sOg3"P/-TO)˗1=Й-g1hPl!91Rf5猩&GʄTV>H).꾚zQ4SV(Y݌ytz $'#Ek7mԥ9"CHSqc!B[eXZ  k,/A~Jvcƥ2C!$-n;~SDlw, :0%@Ced:$YVL}ߋrޜR_O(HqtuכϠҕ|kBЉjeT<$m+$ߠΞ6&ݙ ҃얦!k J8$#n"em~_e&qSK u6QڐSeCDշ GT$i 8S*0+QLB|*۩U\\[r^Q+I$R!]\Ji u!ԭjKpQ)X/12, ,zu%BiRq}< dku[~f4П!NHkkQ$$8T3X*=/+}*T{UD1v[,"{Šqe9⒱}rKkJ.dYi81|eK'!)u'~$cНý*m5X@ x4OgIU5b^\Ѷ8Ճȩd =e9ތ{nұ/9 Z&{(% 'B^M^ۗ 9 IO5_\KOҔT!t2$ k11Jj.!󐓟/\ $XWm='i˯'Zu|g ~:-]7u*%R\4"r#$$~v}*|YKrW_:^n[|1:"rR9OYgvUYm>tr&CA =ePS"C]e51qpid$rqB3Lv6,zp'TU;aK cBƲ1],$^pꋰ j sIN4 vjjcYj;JRg]"XK :Uꬥ-/&S i)N3$)ZzjEnq/75/xmӒ0I {,ٷcMB$",Q$3ۡ@VsAjlitn%rR!]C-&I* dtP=&^ۙG>ιێIs 5_%LҗH4r%Jb&*Z` JSڒEd*PeHq' KI8> _l;6,qG&5-L( +F=~b{ͷlreBBR]K O0@P·Y;%‘,0؂s$]Wqag ed\Lw8B/nۆuDQ\]t*uʁ>`g)wgTֱEeuthjhÓ+O>G_ܴʒY2Z!ǿs Xo"xpjG%%'mӐqll[ V0i.8-,JRXPI-/Y=Cd!|֔)*ʏyP9TR:ԝ%1ǟoG!$PA[|mo( DiĿa % =c׭Y 6nܖN% qX~PJK$2Rq3u6ۡq=mDaPa<+H9*g+=箻%B=Zsl Y撼Awx>Dv7V̒qד,ā$4ڳbە#OESr<A[.'E?ק;-&6K.Yqp%_UQ}icR*/NBb*Fz4Ԓ@ s_`]+Yx+g 8m/@H THvNƾ۲ZcڑR0V爵'{mQY _m BI I >U݊%I&_˱:-nȟ$p%o)sӭ:H)H }4eŬ2ʸMs Ϛ\>rҘG'rrP E+whȦQf\nyKGJHkdኼSm"! ܢ xҷyz&+Ig`U(bs9#JiF]_oHRe7yف9)q, 﫟j=ܿbIi %DD[9 )?]{o}yߗa§i x'8OX@ 1W ޭͭx*_!ෑߓ խa&΢nYERK* *U%I|6H8(i !Vꛊt/'Yܕ'ӔQ!d?h5kSRcȤ,q%98"[=JtoYanVUPIJʘJQvq|J#=o ]MBqb|RRkO% dd]lߚuo-.xj#JϨ:ZcWrXKr)U4( @RypvBۥdqt!m#?v9(IUY)LAkҖDzH`*=#mWTli\ւuYϘ 8,r@9^UAv9V.D|8Jp.m3Nw 8P`'C$ZDۉK?nb+yʝpP p2C>kB;=?u G3lk-~: Amj  1|oNw ^V7SwӁkB.|AHӐBhZ1lEQyYB҂U' }[}5)"dʳe֦爷_A cǦ3~;"()%xdw 8Q)Mbצ!iwid~_^#-I8$z{jԴʬwYXZb4:”t :o֡N=e+T齴i2ńS s_(/r$pAV~_CJAz~)azt}mƄL?YFf"D\h{kp8A ƺ) $+cJBHSUXZNJ۱*IYֵZzZ!4ur;=u1!$ۓ|n=&>T4I^}O{tK1IqUyr |gN5OzRwq+]~O#Rq [B)l=[`|.κn38#CRazt}gkץ6@k8 E6\WCR 8KR)Gb\*g!k/>Iss"oHiD'vΝu@ҐvVU&$9h#>}MoˈŹ*x] HKN u SS0]('8C.~[[R YB]1XI ?vϿGޡo#JG6,e5V ijḴ M鞿=_[srGkPTU0 맏מN(GpQPKP*$rV' 8ҙ7pR%"'_ AK-#|7(/"XcjpT:RxW>`39@K2ElB6WsHOޔ}?`K|EM>Gb39cCS 3o6baY0 Dyzɓ~~ ׼zҐnH/:Mbڝo_‘`]I]E^3Sޔ>溹Ukz9syg3ڝNK휅GJb R 91CBSHBՒ0ϧKme}chXGe 6XaZGd`3^}[ N75gc?:=5v:Њ-ok?:#ok7/q$BC?.%YIW$c OMJpOՈC`Lq:lz69:?p¦3tmM!\?9u8d}r>prǙ'`~Vls^rNLzji _ok+ Fv&r`q_ݨ6 'ߵѵ4sQ+ y_ݮސse*7t]{ސrv?b>= OK?\LI!\#WjyjtMHW6=>n6aZ]#u;Յs+2Fdv; ˬIFSHW8Wk!5Eο@DvS]:B6_ʡ_kð,CHP /0/ tn]c4ڱ*@85Akg>)G?# /0/ -?>$g;?d.>#@ql#XSRu?PG~*YݯFU]SH@DףaY*jt]M!\TObv CүF?5/?@l* v>nv J^hQ?_ݮ+֯G`Y*Y_ݮ `Z*Y_ݯBWkji {g;֯GΎ²U+ UB y_ݩd?QO]SV@,H~+fd__ݮ+a7?ݮ+F=d>C##A$tM!\`Ӌ[s)׈})!)3tMM!\{RC?cq$Noam9+% Bդ% 4R8RDHu}0W|[CNZ1ij޾fl@nz?_ZUbByM>?04UnʌP}ԅݯ": ^PCܒRGތi=VHg!*iHSN%i_S :/`#NN ZR>ۿPJ&&H wYWci :d6e8mSSSA5554SSSA5554SSSA5554SSSA5554SSSA5554SSSA5554SSSA5554SSSA5554SSSA5554SSSA5554SSSA5554SSSA5554SSSA5554}|KPJP57W9RܔⳟUdj|o7v .2x?YQʱl+8NݭDTIC$J=zE(>ȰQ::|ڬ*?]WV bWZ'^(k: ::tТzڇ5U@נ)@zզg(cu@I#E[֒YV|׋I}6Snw[)ÄkƔ@/;<N+moALu}=Ɲ)D$8:aIO.ϧz6&!l״B`xu0ښ&jjjh&jjjh&jjjh&jjjh&jjjh&jjjh&jjjh&jjjh&jjjh&jjjh&jjjh&jjjh&6袍{ A-u3bn--|pC,abVZPZ_xg &P@!4V+ ӝֆBp"X=:LSicE=c"xz /ːRTx|׮\x?;xW)B:5'0҉R:ؠ`BPA:ޮJ} +!N$"9`::FZn_W!9sYLO )$⻂fX9*4tƈD:BNS*HpN[<#]+՛sZ 5OoIENDB`vdr-plugin-live-3.1.3/live/js/000077500000000000000000000000001414414333500161175ustar00rootroot00000000000000vdr-plugin-live-3.1.3/live/js/live/000077500000000000000000000000001414414333500170565ustar00rootroot00000000000000vdr-plugin-live-3.1.3/live/js/live/browserwin.js000066400000000000000000000046541414414333500216260ustar00rootroot00000000000000/* * This is part of the live vdr plugin. See COPYING for license information. * * browserwin.js * * BrowserWin class, BrowserWin.Manager class * * Extension of mootools to create and manage browser windows. */ /* Class: BrowserWin Create and browswer window pointing at an specific URL. Arguments: Options: Note: */ var BrowserWin = new Class({ options: { size: { width: 720, height: 640 }, toolbar: false, location: false, directories: false, statusbar: false, menubar: false, scrollbar: false, resizable: true, wm: false // overide default window manager. }, initialize: function(id, url, options){ this.setOptions(options); this.id = id; this.wm = this.options.wm || BrowserWin.$wm; this.wm.register(this, url); }, create: function(url){ winOpts = "height=" + this.options.size.height; winOpts += ",width=" + this.options.size.width; winOpts += ",toolbar=" + this.options.toolbar; winOpts += ",location=" + this.options.toolbar; winOpts += ",directories=" + this.options.directories; winOpts += ",statusbar=" + this.options.statusbar; winOpts += ",menubar=" + this.options.menubar; winOpts += ",scrollbars=" + this.options.scrollbars; winOpts += ",resizable=" + this.options.resizable; if ($defined(this.options.top)) { winOpts += ",top=" + this.options.top; } if ($defined(this.options.left)) { winOpts += ",left=" + this.options.left; } this.$winRef = window.open(url, this.id, winOpts); }, close: function(){ this.wm.unregister(this); } }); BrowserWin.implement(new Events, new Options); BrowserWin.Manager = new Class({ options: { onRegister: Class.empty, onUnregister: Class.empty }, initialize: function(options){ this.setOptions(options); this.hashTab = new Hash(); }, register: function(browserWin, url){ this.unregister(browserWin); browserWin.create(url); this.hashTab.set(browserWin.id, browserWin); this.fireEvent('onRegister', [browserWin]); }, unregister: function(browserWin){ if (this.hashTab.hasKey(browserWin.id)) { winRef = this.hashTab.get(browserWin.id); winRef.$winRef.close(); this.fireEvent('onUnregister', [winRef]); this.hashTab.remove(browserWin.id); } } }); BrowserWin.Manager.implement(new Events, new Options); BrowserWin.$wm = null; window.addEvent('domready', function(){ BrowserWin.$wm = new BrowserWin.Manager(); }); vdr-plugin-live-3.1.3/live/js/live/header.js000066400000000000000000000014111414414333500206410ustar00rootroot00000000000000function adjustHeader() { // exchange body top margin by equally-sized header-padding element var padding = document.getElementById( "padding" ); if( padding.style.height == "" ) { var bodyStyles = ( window.getComputedStyle )? getComputedStyle( document.body, null ) : document.body.currentStyle; padding.style.height = bodyStyles.marginTop; padding.style.backgroundColor = ( window.bgColor )? window.bgColor : "white"; padding.style.display = ""; document.body.style.marginTop = "0px"; } // expand underlay to header's height var header = document.getElementById( "header" ); var underlay = document.getElementById( "underlay" ); underlay.style.height = header.offsetHeight+ "px"; underlay.style.display = ""; header.style.position = "fixed"; return; } vdr-plugin-live-3.1.3/live/js/live/hinttips.js000066400000000000000000000022031414414333500212530ustar00rootroot00000000000000/* * This is part of the live vdr plugin. See COPYING for license information. * * HintTips class. * * Extension of mootools Tips class for rounded corner tooltips of * variable size up to some maximum. */ var HintTips = Tips.extend({ initialize: function(elements, options){ this.parent(elements, options); this.toolTip.empty(); /* top border of tip */ var hd = new Element('div', {'class': this.options.className + '-tip-top'}).inject(this.toolTip); hd = new Element('div', {'class': this.options.className + '-tip-c'}).inject(hd); /* body of tip: some helper divs and content */ this.wrapper = new Element('div', {'class': this.options.className + '-tip-bdy'}).inject(this.toolTip); this.wrapper = new Element('div', {'class': this.options.className + '-tip-c'}).inject(this.wrapper); this.wrapper = new Element('div', {'class': this.options.className + '-tip-s'}).inject(this.wrapper); /* bottom border of tip */ var bt = new Element('div', {'class': this.options.className + '-tip-bot'}).inject(this.toolTip); bt = new Element('div', {'class': this.options.className + '-tip-c'}).inject(bt); } }); vdr-plugin-live-3.1.3/live/js/live/infowin.js000066400000000000000000000242261414414333500210730ustar00rootroot00000000000000/* * This is part of the live vdr plugin. See COPYING for license information. * * InfoWin.js * * InfoWin class, InfoWin.Manager class, InfoWin.Ajax class. * * Extension of mootools to display a popup window with some html * code. */ /* Class: InfoWin Create an information window as overlay to current page. Arguments: Options: Note: A window consists of a frame-element. This is the overall containing element used to control the display and size of the window. It is accesable through the 'winFrame' property. The InfoWin class provides the followin properties to fill the window with content: - titleBox: the element meant to place the title of the window into. - buttonBox: here the default window buttons are created. You might clear this and create your own kind of window controls. - winBody: this is where your window contents goes. */ var InfoWin = new Class({ options: { timeout: 0, onShow: Class.empty, onHide: Class.empty, onDomExtend: Class.empty, destroyOnHide: false, className: 'info', wm: false, // overide default window manager. draggable: true, resizable: true, buttonimg: 'transparent.png', bodyselect: 'div.content', titleselect: 'div.caption', classSuffix: '-win', idSuffix: '-id', offsets: {'x': -16, 'y': -16} }, initialize: function(id, options){ this.setOptions(options); this.wm = this.options.wm || InfoWin.$wm; this.winFrame = $(id + this.options.classSuffix + this.options.idSuffix); if (!$defined(this.winFrame)){ this.buildFrame(id); this.build(id); this.wm.register(this); } }, // internal: build new window element. // // build sets up a frame for a new InfoWin. The parent element // of the window frame has the id '-win-id'. The function // must return true if the body of the InfoWin has been filled // with the user data, false otherwise. build: function(id){ // header of window: upper shadows, corners title and controls var top = new Element('div', { 'class': this.options.className + this.options.classSuffix + '-top' }).inject(this.winFrame); if (this.options.draggable) this.winFrame.makeDraggable({'handle': top}); top = new Element('div', { 'class': this.options.className + this.options.classSuffix + '-c' }).inject(top); this.titleBox = new Element('div', { 'class': this.options.className + this.options.classSuffix + '-t' }).inject(top); this.buttonBox = new Element('div', { 'class': this.options.className + this.options.classSuffix + '-b' }).inject(top); var cls = new Element('div', { 'class': 'close' }).inject(this.buttonBox); cls.addEvent('click', function(event){ var event = new Event(event); event.stop(); return this.hide(); }.bind(this)); cls = new Element('img', { 'src': this.options.buttonimg, 'alt': 'close', 'width': '16px', 'height': '16px' }).inject(cls); // body of window: user content. var bdy = new Element('div', { 'class': this.options.className + this.options.classSuffix + '-body' }).inject(this.winFrame); bdy = new Element('div', { 'class': this.options.className + this.options.classSuffix + '-c' }).inject(bdy); this.winBody = new Element('div', { 'class': this.options.className + this.options.classSuffix + '-s' }).inject(bdy); // bottom border of window: lower shadows and corners, optional // resize handle. var bot = new Element('div', { 'class': this.options.className + this.options.classSuffix + '-bot' }).inject(this.winFrame); bot = new Element('div', { 'class': this.options.className + this.options.classSuffix + '-c' }).inject(bot); if (this.options.resizable) { this.winFrame.makeResizable({'handle': bot}); } if (!this.fillTitle(id)) { // todo: add generic title } return this.fillBody(id); }, buildFrame: function(id){ this.winFrame = new Element('div', { 'id': id + this.options.classSuffix + this.options.idSuffix, 'class': this.options.className + this.options.classSuffix, 'styles': { 'position': 'absolute', 'top': '0', 'left': '0' } }); }, show: function(event){ this.position(event); this.fireEvent('onShow', [this.winFrame]); this.wm.raise(this); if (this.options.timeout) this.timer = this.hide.delay(this.options.timeout, this); return false; }, hide: function(){ this.fireEvent('onHide', [this.winFrame]); if (this.options.destroyOnHide) { this.wm.unregister(this); for (var z in this) this[z] = null; this.destroyed = true; } else { this.wm.bury(this); } return false; }, fillBody: function(id){ var bodyElems = $$('#'+ id + ' ' + this.options.bodyselect); if ($defined(bodyElems) && bodyElems.length > 0) { this.winBody.empty(); this.fireEvent('onDomExtend', [id, bodyElems]); this.winBody.adopt(bodyElems); return true; } return false; }, fillTitle: function(id){ var titleElems = $$('#' + id + ' ' + this.options.titleselect); if ($defined(titleElems) && titleElems.length > 0) { this.titleBox.empty().adopt(titleElems); return true; } return false; }, position: function(event){ var prop = {'x': 'left', 'y': 'top'}; for (var z in prop) { var pos = event.page[z] + this.options.offsets[z]; this.winFrame.setStyle(prop[z], pos); } } }); InfoWin.implement(new Events, new Options); /* Class: InfoWin.Manager Provide an container and events for the created info win instances. Closed info-wins are preserved in a hidden dom element and used again if a window with a closed id is openend again. */ InfoWin.Manager = new Class({ options: { closedContainer: 'infowin-closed', openedContainer: 'infowin-opened', onRegister: Class.empty, onUnregister: Class.empty, onRaise: Class.empty, onBury: Class.empty }, initialize: function(options){ this.setOptions(options); // initialize properties this.closedWins and this.openedWins: ['closed', 'opened'].each(function(kind){ var wins = kind + 'Wins'; var opts = this.options[kind + 'Container']; this[wins] = $(opts); if (!$defined(this[wins])){ this[wins] = new Element('div', { 'id': opts, 'styles' : { 'display' : (kind == 'closed') ? 'none' : 'block' } }); this[wins].inject(document.body); } }, this); }, register: function(infoWin){ this.fireEvent('onRegister', [infoWin]); infoWin.winFrame.addEvent('click', function(){ this.raise(infoWin); }.bind(this)); infoWin.winFrame.inject(this.closedWins); }, unregister: function(infoWin){ this.fireEvent('onUnregister', [infoWin]); infoWin.winFrame.remove(); }, raise: function(infoWin){ this.fireEvent('onRaise', [infoWin]); infoWin.winFrame.inject(this.openedWins); }, bury: function(infoWin){ this.fireEvent('onBury', [infoWin]); infoWin.winFrame.inject(this.closedWins); } }); InfoWin.Manager.implement(new Events, new Options); InfoWin.$wm = null; window.addEvent('domready', function(){ InfoWin.$wm = new InfoWin.Manager(); }); /* Class: InfoWin.Ajax Use an instance of mootools Ajax class to asynchronously request the content of an info win. */ InfoWin.Ajax = InfoWin.extend({ options: { loadingMsg: 'loading', errorMsg: 'an error occured!', onError: Class.empty }, initialize: function(id, url, options){ this.parent(id, options); if ($defined(this.ajaxResponse)) { this.addEvent('onError', function(){ this.hide.delay(1000, this); }.bind(this)); var ajax = new Ajax(url, { update: this.ajaxResponse, onComplete: function(text, xmldoc){ this.fillTitle(id); this.fillBody(id); this.ajaxResponse.remove(); }.bind(this), onFailure: function(transport){ this.titleBox.setHTML(this.options.errorMsg); this.fireEvent('onError', [id, url]); }.bind(this) }).request('async=1'); } }, // this function gets called when no previous instance for 'id' // created a dom subtree for an infowin. build: function(id){ if (!this.parent(id)) { this.titleBox.setHTML(this.options.loadingMsg); this.ajaxResponse = new Element('div', { 'styles' : { 'display': 'none' } }).inject(this.winFrame); } } }); /* Class: Infowin.Notifier Creates a notification popup that disappears automaticaly. Usefull for a confirmation message after a AJAX action request. */ InfoWin.Notifier = InfoWin.extend({ options: { timeout: 2500, destroyOnHide: true, className: 'ok', classSuffix: '-info', message: '', offsets: {'x': 16, 'y': 16} }, initialize: function(id, options){ this.parent(id, options); }, build: function(id){ /* top border of hint */ var top = new Element('div', { 'class': this.options.className + this.options.classSuffix + '-top' }).inject(this.winFrame); top = new Element('div', { 'class': this.options.className + this.options.classSuffix + '-c' }).inject(top); /* body of tip: some helper divs and content */ var bdy = new Element('div', { 'class': this.options.className + this.options.classSuffix + '-body' }).inject(this.winFrame); bdy = new Element('div', { 'class': this.options.className + this.options.classSuffix + '-c' }).inject(bdy); this.winBody = new Element('div', { 'class': this.options.className + this.options.classSuffix + '-s' }).inject(bdy); /* bottom border of tip */ var bot = new Element('div', { 'class': this.options.className + this.options.classSuffix + '-bot' }).inject(this.winFrame); bot = new Element('div', { 'class': this.options.className + this.options.classSuffix + '-c' }).inject(bot); return this.fillBody(id); }, fillBody: function(id){ this.winFrame.setStyle('position', 'fixed'); this.winBody.empty().setHTML(this.options.message); return true; }, position: function(event){ var prop = {'x': 'left', 'y': 'top'}; for (var z in prop) { var pos = this.options.offsets[z]; this.winFrame.setStyle(prop[z], pos); } } }); vdr-plugin-live-3.1.3/live/js/live/liveajax.js000066400000000000000000000004121414414333500212140ustar00rootroot00000000000000/* * Javascript function for quick ajax requests. * This file needs mootools.js to be included on the pages. */ function LiveSimpleAjaxRequest(url, param, value) { var req = new Ajax(url, { method : 'post' }).request(param + '=' + value + '&async=1'); }; vdr-plugin-live-3.1.3/live/js/live/pageenhance.js000066400000000000000000000117461414414333500216630ustar00rootroot00000000000000/* * This is part of the live vdr plugin. See COPYING for license information. * * PageEnhance class. * * This class applies several functions to the page based on * selectors. This class is project dependent and not a general * purpose class. */ var PageEnhance = new Class({ options: { epgLinkSelector: 'a[href^="epginfo.html?epgid"]', actionLinkSelector: 'a[href^="vdr_request/"]', editTimerSelector: 'a[href^="edit_timer.html?timerid"]', hintTipSelector: '*[title]', hintClassName: 'hint', infoWinOptions: { bodyselect: 'div.epg_content', loadingMsg: 'loading', errorMsg: 'an error occured!' }, notifyIdPrefix: 'notify', notifyStrings: { successMsg: ' Success!', errorMsg: ' failed!' }, datePickerSelector: 'input.DatePicker', datePickerOptions: '' }, initialize: function(options){ this.setOptions(options); this.$notifyCount = 0; window.addEvent('domready', this.domReadySetup.bind(this)); window.addEvent('mousedown', this.mouseDownSetup.bind(this)); }, // actions applied on domready to the page. domReadySetup: function(){ $$(this.options.epgLinkSelector).each(this.epgPopup.bind(this)); this.addHintTips($$(this.options.hintTipSelector)); $$(this.options.actionLinkSelector).each(this.vdrRequest.bind(this)); $$(this.options.datePickerSelector).each(this.datePicker.bind(this)); // the following line activates timer editing in popup window. // but it does not yet work like expected. So we leave it deactivated currently. // $$(this.options.editTimerSelector).each(this.editTimer.bind(this)); }, // actions applied on mouse down. mouseDownSetup: function(){ $$('.' + this.options.hintClassName + '-tip').setStyle('visibility', 'hidden'); }, // registered as 'onDomExtend' event for InfoWin. Takes care to // enhance the new dom elements too. domExtend: function(id, elems){ var sel = '#' + id + ' ' + this.options.hintTipSelector; elems = $$(sel); this.addHintTips(elems); $$('#' + id + ' ' + this.options.actionLinkSelector).each(this.vdrRequest.bind(this)); }, // Epg Popup function. Apply to all elements that should // pop up an Epg InfoWin window. epgPopup: function(el){ var href = el.href; var epgid = $pick(href, ""); if (epgid != "") { var extractId = /epgid=(\w+)/; var found = extractId.exec(epgid); if ($defined(found) && found.length > 1) { epgid = found[1]; el.addEvent('click', function(event){ var event = new Event(event); new InfoWin.Ajax(epgid, href, $merge(this.options.infoWinOptions, { onDomExtend: this.domExtend.bind(this) })).show(event); event.stop(); return false; }.bind(this)); } } }, // Edit Timer Popup function. Apply to all elements that should // pop up a timer edit windows based on InfoWin window. editTimer: function(el){ var href = el.href; var timerid = $pick(href, ""); if (timerid != "") { var extractId = /timerid=(.+)/; var found = extractId.exec(timerid); if ($defined(found) && found.length > 1) { timerid = found[1]; el.addEvent('click', function(event){ var event = new Event(event); new InfoWin.Ajax(timerid, href, $merge(this.options.infoWinOptions, { bodyselect: '', modal: true, onDomExtend: this.domExtend.bind(this) })).show(event); event.stop(); return false; }.bind(this)); } } }, // function that requests an action from the server vdr. vdrRequest: function(el){ el.addEvent('click', function(event, element){ var href = $pick(element.href, ""); if (href != "") { this.$notifyCount++; var req = new Ajax(href, { method: 'post', onComplete: function(text, xmldoc) { try { var success = xmldoc.getElementsByTagName('response').item(0).firstChild.nodeValue; new InfoWin.Notifier(this.options.notifyIdPrefix + this.$notifyCount, { className: success == '1' ? 'ok' : 'err', message: success == '1' ? this.options.notifyStrings.successMsg : this.options.notifyStrings.errorMsg }).show(event); } catch(e) { } }.bind(this) }); req.request('async=1'); event.stop(); return false; } return true; }.bindWithEvent(this, el)); }, // change normal 'title'-Attributes into enhanced hinttips // usesd by domExtend and domReadySetup functions. addHintTips: function(elems){ if (!$defined(this.tips)) { this.tips = new HintTips(elems, { maxTitleChars: 100, className: this.options.hintClassName }); } else { $$(elems).each(this.tips.build, this.tips); } }, // add datepicker to input element datePicker: function(el){ el.alt = this.options.datePickerOptions; new DatePicker(el); } }); PageEnhance.implement(new Events, new Options); vdr-plugin-live-3.1.3/live/js/live/vdr_status.js000066400000000000000000000132321414414333500216130ustar00rootroot00000000000000/* * Javascript functions for the status update box. * This file needs mootools.js to be included on the pages. */ var LiveVdrInfo = Ajax.extend({ options: { autoCancel: true }, initialize: function(url, boxId) { this.parent(url, null); this.addEvent('onComplete', this.showInfo); this.addEvent('onFailure', this.reportError); this.boxId = boxId; this.reload = true; this.timer = null; }, request: function(update) { this.parent({update: update ? "1" : "0"}); }, showInfo: function(text, xmldoc) { try { this.selectInfoElems(xmldoc); this.setEpgInfo(xmldoc); this.setInfoMessage(xmldoc); this.setUpdate(xmldoc); } catch (e) { this.reportError(null); } }, reportError: function(transport) { this.setTextContent('caption', 'ERROR'); var message; if (transport != null) { message = $("__infobox_request_err").firstChild.nodeValue; } else { message = $("__infobox_update_err").firstChild.nodeValue; } this.setTextContent('name', message); }, // private function to switch visibility of controls. selectInfoElems: function(xmldoc) { var infoType = xmldoc.getElementsByTagName('type').item(0); var channel = $(this.boxId + '_channel_buttons'); var playback = $(this.boxId + '_recording_buttons'); if (infoType.firstChild.nodeValue != "channel") { channel.style.display = 'none'; playback.style.display = 'block'; this.setTextContent('pause', infoType.firstChild.nodeValue); this.setTextContent('play', infoType.firstChild.nodeValue); this.setTextContent('rwd', infoType.firstChild.nodeValue); this.setTextContent('ffw', infoType.firstChild.nodeValue); this.setTextContent('stop', infoType.firstChild.nodeValue); } else { playback.style.display = 'none'; channel.style.display = 'block'; } }, // private function to activate the info message display if the // corresponding element is found in the current page. setInfoMessage: function(xmldoc) { var info = xmldoc.getElementsByTagName('info').item(0); if (! $defined(info)) return; var messagebar = $('messagebar'); if (! $defined(messagebar)) return; var message = xmldoc.getElementsByTagName('message').item(0); var url = xmldoc.getElementsByTagName('url').item(0); if (message.firstChild.nodeValue != "") { $('mbmessage').setText(message.firstChild.nodeValue); if ($defined(url.firstChild)) { $('mbdelimiter').removeClass('notpresent'); $('mbreact').setProperty('href', url.firstChild.nodeValue); } else { $('mbdelimiter').addClass('notpresent'); $('mbreact').addClass('notpresent'); } messagebar.removeClass('notpresent'); } }, // private function to display information from epg info. setEpgInfo: function(xmldoc) { var epgInfo = xmldoc.getElementsByTagName('epginfo').item(0); for (var i = 0; i < epgInfo.childNodes.length; i++) { var node = epgInfo.childNodes.item(i); if (node.nodeType == 1) { var textContent = ""; if (node.firstChild != null) textContent = node.firstChild.nodeValue; this.setTextContent(node.nodeName, textContent); } } }, // private function to update text contents. setTextContent: function(nodeName, textContent) { var docNode = $(this.boxId + '_' + nodeName); if (docNode != null) { switch (nodeName) { case "caption": case "timenow": case "name": case "duration": { if (docNode.innerHTML != textContent) docNode.innerHTML = textContent; break; } case "elapsed": { var width = textContent + "px"; if (docNode.style.width != width) docNode.style.width = width; break; } case "nextchan": case "prevchan": { if (textContent != "") { // docNode.href = "javascript:LiveSimpleAjaxRequest('switch_channel.xml', 'param', '" + textContent + "');"; docNode.href = "vdr_request/switch_channel?param=" + textContent; docNode.style.visibility = "visible"; } else { docNode.style.visibility = "hidden"; } break; } case "pause": case "play": case "rwd": case "ffw": case "stop": { if (textContent != "") { // docNode.href = "javascript:LiveSimpleAjaxRequest('" + nodeName + "_recording.xml', 'param', '" + textContent + "');"; docNode.href = "vdr_request/" + nodeName + "_recording?param=" + textContent; docNode.style.visibility = "visible"; } else { docNode.style.visibility = "hidden"; } break; } default: break; } } }, // private function to determine update status and to trigger // the next update. setUpdate: function(xmldoc) { /* check if we still need to update the status */ var upd = xmldoc.getElementsByTagName('update').item(0); var rel = (upd.firstChild.nodeValue == "1"); if (rel != this.reload) { this.reload = rel; var img = $('statusReloadBtn'); if (img != null) { // change image according to state. img.src = this.reload ? 'img/stop_update.png' : 'img/reload.png'; } } if (this.reload) this.timer = this.request.delay(1000, this, true); }, toggleUpdate: function() { if (this.reload) { if (this.timer != null) { this.timer = $clear(this.timer); } } this.request(!this.reload); }, pageFinished: function() { if (this.reload) { if (this.timer != null) { this.timer = $clear(this.timer); } } this.cancel(); } }); vdr-plugin-live-3.1.3/live/js/mootools/000077500000000000000000000000001414414333500177725ustar00rootroot00000000000000vdr-plugin-live-3.1.3/live/js/mootools/DatePicker.js000066400000000000000000000206731414414333500223530ustar00rootroot00000000000000/* * DatePicker * @author Rick Hopkins * @modified by Micah Nolte and Martin Va�ina * @version 0.3.2 * @classDescription A date picker object. Created with the help of MooTools v1.11 * MIT-style License. -- start it up by doing this in your domready: $$('input.DatePicker').each( function(el){ new DatePicker(el); }); */ var DatePicker = new Class({ /* set and create the date picker text box */ initialize: function(dp){ // Options defaults this.dayChars = 1; // number of characters in day names abbreviation this.dayNames = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday']; this.daysInMonth = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; this.format = 'mm/dd/yyyy'; this.monthNames = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December']; this.startDay = 7; // 1 = week starts on Monday, 7 = week starts on Sunday this.yearOrder = 'asc'; this.yearRange = 10; this.yearStart = (new Date().getFullYear()); // Finds the entered date, or uses the current date if(dp.value != '') { dp.then = new Date(dp.value); dp.today = new Date(); } else { dp.then = dp.today = new Date(); } // Set beginning time and today, remember the original dp.oldYear = dp.year = dp.then.getFullYear(); dp.oldMonth = dp.month = dp.then.getMonth(); dp.oldDay = dp.then.getDate(); dp.nowYear = dp.today.getFullYear(); dp.nowMonth = dp.today.getMonth(); dp.nowDay = dp.today.getDate(); // Pull the rest of the options from the alt attr if(dp.alt) { options = Json.evaluate(dp.alt); } else { options = []; } dp.options = { monthNames: (options.monthNames && options.monthNames.length == 12 ? options.monthNames : this.monthNames) || this.monthNames, daysInMonth: (options.daysInMonth && options.daysInMonth.length == 12 ? options.daysInMonth : this.daysInMonth) || this.daysInMonth, dayNames: (options.dayNames && options.dayNames.length == 7 ? options.dayNames : this.dayNames) || this.dayNames, startDay : options.startDay || this.startDay, dayChars : options.dayChars || this.dayChars, format: options.format || this.format, yearStart: options.yearStart || this.yearStart, yearRange: options.yearRange || this.yearRange, yearOrder: options.yearOrder || this.yearOrder }; dp.setProperties({'id':dp.getProperty('name'), 'readonly':true}); dp.container = false; dp.calendar = false; dp.interval = null; dp.active = false; dp.onclick = dp.onfocus = this.create.pass(dp, this); }, /* create the calendar */ create: function(dp){ if (dp.calendar) return false; // Hide select boxes while calendar is up if(window.ie6){ $$('select').addClass('dp_hide'); } /* create the outer container */ dp.container = new Element('div', {'class':'dp_container'}).injectBefore(dp); /* create timers */ dp.container.onmouseover = dp.onmouseover = function(){ $clear(dp.interval); }; dp.container.onmouseout = dp.onmouseout = function(){ dp.interval = setInterval(function(){ if (!dp.active) this.remove(dp); }.bind(this), 500); }.bind(this); /* create the calendar */ dp.calendar = new Element('div', {'class':'dp_cal'}).injectInside(dp.container); /* create the date object */ var date = new Date(); /* create the date object */ if (dp.month && dp.year) { date.setFullYear(dp.year, dp.month, 1); } else { dp.month = date.getMonth(); dp.year = date.getFullYear(); date.setDate(1); } dp.year % 4 == 0 ? dp.options.daysInMonth[1] = 29 : dp.options.daysInMonth[1] = 28; /* set the day to first of the month */ var firstDay = (1-(7+date.getDay()-dp.options.startDay)%7); /* create the month select box */ monthSel = new Element('select', {'id':dp.id + '_monthSelect'}); for (var m = 0; m < dp.options.monthNames.length; m++){ monthSel.options[m] = new Option(dp.options.monthNames[m], m); if (dp.month == m) monthSel.options[m].selected = true; } /* create the year select box */ yearSel = new Element('select', {'id':dp.id + '_yearSelect'}); i = 0; dp.options.yearStart ? dp.options.yearStart : dp.options.yearStart = date.getFullYear(); if (dp.options.yearOrder == 'desc'){ for (var y = dp.options.yearStart; y > (dp.options.yearStart - dp.options.yearRange - 1); y--){ yearSel.options[i] = new Option(y, y); if (dp.year == y) yearSel.options[i].selected = true; i++; } } else { for (var y = dp.options.yearStart; y < (dp.options.yearStart + dp.options.yearRange + 1); y++){ yearSel.options[i] = new Option(y, y); if (dp.year == y) yearSel.options[i].selected = true; i++; } } /* start creating calendar */ calTable = new Element('table'); calTableThead = new Element('thead'); calSelRow = new Element('tr'); calSelCell = new Element('th', {'colspan':'7'}); monthSel.injectInside(calSelCell); yearSel.injectInside(calSelCell); calSelCell.injectInside(calSelRow); calSelRow.injectInside(calTableThead); calTableTbody = new Element('tbody'); /* create day names */ calDayNameRow = new Element('tr'); for (var i = 0; i < dp.options.dayNames.length; i++) { calDayNameCell = new Element('th'); calDayNameCell.appendText(dp.options.dayNames[(dp.options.startDay+i)%7].substr(0, dp.options.dayChars)); calDayNameCell.injectInside(calDayNameRow); } calDayNameRow.injectInside(calTableTbody); /* create the day cells */ while (firstDay <= dp.options.daysInMonth[dp.month]){ calDayRow = new Element('tr'); for (i = 0; i < 7; i++){ if ((firstDay <= dp.options.daysInMonth[dp.month]) && (firstDay > 0)){ calDayCell = new Element('td', {'class':dp.id + '_calDay', 'axis':dp.year + '|' + (parseInt(dp.month) + 1) + '|' + firstDay}).appendText(firstDay).injectInside(calDayRow); } else { calDayCell = new Element('td', {'class':'dp_empty'}).appendText(' ').injectInside(calDayRow); } // Show the previous day if ( (firstDay == dp.oldDay) && (dp.month == dp.oldMonth ) && (dp.year == dp.oldYear) ) { calDayCell.addClass('dp_selected'); } // Show today if ( (firstDay == dp.nowDay) && (dp.month == dp.nowMonth ) && (dp.year == dp.nowYear) ) { calDayCell.addClass('dp_today'); } firstDay++; } calDayRow.injectInside(calTableTbody); } /* table into the calendar div */ calTableThead.injectInside(calTable); calTableTbody.injectInside(calTable); calTable.injectInside(dp.calendar); /* set the onmouseover events for all calendar days */ $$('td.' + dp.id + '_calDay').each(function(el){ el.onmouseover = function(){ el.addClass('dp_roll'); }.bind(this); }.bind(this)); /* set the onmouseout events for all calendar days */ $$('td.' + dp.id + '_calDay').each(function(el){ el.onmouseout = function(){ el.removeClass('dp_roll'); }.bind(this); }.bind(this)); /* set the onclick events for all calendar days */ $$('td.' + dp.id + '_calDay').each(function(el){ el.onclick = function(){ ds = el.axis.split('|'); dp.value = this.formatValue(dp, ds[0], ds[1], ds[2]); this.remove(dp); }.bind(this); }.bind(this)); /* set the onchange event for the month & year select boxes */ monthSel.onfocus = function(){ dp.active = true; }; monthSel.onchange = function(){ dp.month = monthSel.value; dp.year = yearSel.value; this.remove(dp); this.create(dp); }.bind(this); yearSel.onfocus = function(){ dp.active = true; }; yearSel.onchange = function(){ dp.month = monthSel.value; dp.year = yearSel.value; this.remove(dp); this.create(dp); }.bind(this); }, /* Format the returning date value according to the selected formation */ formatValue: function(dp, year, month, day){ /* setup the date string variable */ var dateStr = ''; /* check the length of day */ if (day < 10) day = '0' + day; if (month < 10) month = '0' + month; /* check the format & replace parts // thanks O'Rey */ dateStr = dp.options.format.replace( /dd/i, day ).replace( /mm/i, month ).replace( /yyyy/i, year ); dp.month = dp.oldMonth = '' + (month - 1) + ''; dp.year = dp.oldYear = year; dp.oldDay = day; /* return the date string value */ return dateStr; }, /* Remove the calendar from the page */ remove: function(dp){ $clear(dp.interval); dp.active = false; if (window.opera) dp.container.empty(); else if (dp.container) dp.container.remove(); dp.calendar = false; dp.container = false; $$('select.dp_hide').removeClass('dp_hide'); } }); vdr-plugin-live-3.1.3/live/js/mootools/mootools.v1.11.js000066400000000000000000003662161414414333500227660ustar00rootroot00000000000000/* Script: Core.js Mootools - My Object Oriented javascript. License: MIT-style license. MooTools Copyright: copyright (c) 2007 Valerio Proietti, MooTools Credits: - Class is slightly based on Base.js (c) 2006 Dean Edwards, License - Some functions are inspired by those found in prototype.js (c) 2005 Sam Stephenson sam [at] conio [dot] net, MIT-style license - Documentation by Aaron Newton (aaron.newton [at] cnet [dot] com) and Valerio Proietti. */ var MooTools = { version: '1.11' }; /* Section: Core Functions */ /* Function: $defined Returns true if the passed in value/object is defined, that means is not null or undefined. Arguments: obj - object to inspect */ function $defined(obj){ return (obj != undefined); }; /* Function: $type Returns the type of object that matches the element passed in. Arguments: obj - the object to inspect. Example: >var myString = 'hello'; >$type(myString); //returns "string" Returns: 'element' - if obj is a DOM element node 'textnode' - if obj is a DOM text node 'whitespace' - if obj is a DOM whitespace node 'arguments' - if obj is an arguments object 'object' - if obj is an object 'string' - if obj is a string 'number' - if obj is a number 'boolean' - if obj is a boolean 'function' - if obj is a function 'regexp' - if obj is a regular expression 'class' - if obj is a Class. (created with new Class, or the extend of another class). 'collection' - if obj is a native htmlelements collection, such as childNodes, getElementsByTagName .. etc. false - (boolean) if the object is not defined or none of the above. */ function $type(obj){ if (!$defined(obj)) return false; if (obj.htmlElement) return 'element'; var type = typeof obj; if (type == 'object' && obj.nodeName){ switch(obj.nodeType){ case 1: return 'element'; case 3: return (/\S/).test(obj.nodeValue) ? 'textnode' : 'whitespace'; } } if (type == 'object' || type == 'function'){ switch(obj.constructor){ case Array: return 'array'; case RegExp: return 'regexp'; case Class: return 'class'; } if (typeof obj.length == 'number'){ if (obj.item) return 'collection'; if (obj.callee) return 'arguments'; } } return type; }; /* Function: $merge merges a number of objects recursively without referencing them or their sub-objects. Arguments: any number of objects. Example: >var mergedObj = $merge(obj1, obj2, obj3); >//obj1, obj2, and obj3 are unaltered */ function $merge(){ var mix = {}; for (var i = 0; i < arguments.length; i++){ for (var property in arguments[i]){ var ap = arguments[i][property]; var mp = mix[property]; if (mp && $type(ap) == 'object' && $type(mp) == 'object') mix[property] = $merge(mp, ap); else mix[property] = ap; } } return mix; }; /* Function: $extend Copies all the properties from the second passed object to the first passed Object. If you do myWhatever.extend = $extend the first parameter will become myWhatever, and your extend function will only need one parameter. Example: (start code) var firstOb = { 'name': 'John', 'lastName': 'Doe' }; var secondOb = { 'age': '20', 'sex': 'male', 'lastName': 'Dorian' }; $extend(firstOb, secondOb); //firstOb will become: { 'name': 'John', 'lastName': 'Dorian', 'age': '20', 'sex': 'male' }; (end) Returns: The first object, extended. */ var $extend = function(){ var args = arguments; if (!args[1]) args = [this, args[0]]; for (var property in args[1]) args[0][property] = args[1][property]; return args[0]; }; /* Function: $native Will add a .extend method to the objects passed as a parameter, but the property passed in will be copied to the object's prototype only if non previously existent. Its handy if you dont want the .extend method of an object to overwrite existing methods. Used automatically in MooTools to implement Array/String/Function/Number methods to browser that dont support them whitout manual checking. Arguments: a number of classes/native javascript objects */ var $native = function(){ for (var i = 0, l = arguments.length; i < l; i++){ arguments[i].extend = function(props){ for (var prop in props){ if (!this.prototype[prop]) this.prototype[prop] = props[prop]; if (!this[prop]) this[prop] = $native.generic(prop); } }; } }; $native.generic = function(prop){ return function(bind){ return this.prototype[prop].apply(bind, Array.prototype.slice.call(arguments, 1)); }; }; $native(Function, Array, String, Number); /* Function: $chk Returns true if the passed in value/object exists or is 0, otherwise returns false. Useful to accept zeroes. Arguments: obj - object to inspect */ function $chk(obj){ return !!(obj || obj === 0); }; /* Function: $pick Returns the first object if defined, otherwise returns the second. Arguments: obj - object to test picked - the default to return Example: (start code) function say(msg){ alert($pick(msg, 'no meessage supplied')); } (end) */ function $pick(obj, picked){ return $defined(obj) ? obj : picked; }; /* Function: $random Returns a random integer number between the two passed in values. Arguments: min - integer, the minimum value (inclusive). max - integer, the maximum value (inclusive). Returns: a random integer between min and max. */ function $random(min, max){ return Math.floor(Math.random() * (max - min + 1) + min); }; /* Function: $time Returns the current timestamp Returns: a timestamp integer. */ function $time(){ return new Date().getTime(); }; /* Function: $clear clears a timeout or an Interval. Returns: null Arguments: timer - the setInterval or setTimeout to clear. Example: >var myTimer = myFunction.delay(5000); //wait 5 seconds and execute my function. >myTimer = $clear(myTimer); //nevermind See also: , */ function $clear(timer){ clearTimeout(timer); clearInterval(timer); return null; }; /* Class: Abstract Abstract class, to be used as singleton. Will add .extend to any object Arguments: an object Returns: the object with an .extend property, equivalent to <$extend>. */ var Abstract = function(obj){ obj = obj || {}; obj.extend = $extend; return obj; }; //window, document var Window = new Abstract(window); var Document = new Abstract(document); document.head = document.getElementsByTagName('head')[0]; /* Class: window Some properties are attached to the window object by the browser detection. Note: browser detection is entirely object-based. We dont sniff. Properties: window.ie - will be set to true if the current browser is internet explorer (any). window.ie6 - will be set to true if the current browser is internet explorer 6. window.ie7 - will be set to true if the current browser is internet explorer 7. window.gecko - will be set to true if the current browser is Mozilla/Gecko. window.webkit - will be set to true if the current browser is Safari/Konqueror. window.webkit419 - will be set to true if the current browser is Safari2 / webkit till version 419. window.webkit420 - will be set to true if the current browser is Safari3 (Webkit SVN Build) / webkit over version 419. window.opera - is set to true by opera itself. */ window.xpath = !!(document.evaluate); if (window.ActiveXObject) window.ie = window[window.XMLHttpRequest ? 'ie7' : 'ie6'] = true; else if (document.childNodes && !document.all && !navigator.taintEnabled) window.webkit = window[window.xpath ? 'webkit420' : 'webkit419'] = true; else if (document.getBoxObjectFor != null) window.gecko = true; /*compatibility*/ window.khtml = window.webkit; Object.extend = $extend; /*end compatibility*/ //htmlelement if (typeof HTMLElement == 'undefined'){ var HTMLElement = function(){}; if (window.webkit) document.createElement("iframe"); //fixes safari HTMLElement.prototype = (window.webkit) ? window["[[DOMElement.prototype]]"] : {}; } HTMLElement.prototype.htmlElement = function(){}; //enables background image cache for internet explorer 6 if (window.ie6) try {document.execCommand("BackgroundImageCache", false, true);} catch(e){}; /* Script: Class.js Contains the Class Function, aims to ease the creation of reusable Classes. License: MIT-style license. */ /* Class: Class The base class object of the framework. Creates a new class, its initialize method will fire upon class instantiation. Initialize wont fire on instantiation when you pass *null*. Arguments: properties - the collection of properties that apply to the class. Example: (start code) var Cat = new Class({ initialize: function(name){ this.name = name; } }); var myCat = new Cat('Micia'); alert(myCat.name); //alerts 'Micia' (end) */ var Class = function(properties){ var klass = function(){ return (arguments[0] !== null && this.initialize && $type(this.initialize) == 'function') ? this.initialize.apply(this, arguments) : this; }; $extend(klass, this); klass.prototype = properties; klass.constructor = Class; return klass; }; /* Property: empty Returns an empty function */ Class.empty = function(){}; Class.prototype = { /* Property: extend Returns the copy of the Class extended with the passed in properties. Arguments: properties - the properties to add to the base class in this new Class. Example: (start code) var Animal = new Class({ initialize: function(age){ this.age = age; } }); var Cat = Animal.extend({ initialize: function(name, age){ this.parent(age); //will call the previous initialize; this.name = name; } }); var myCat = new Cat('Micia', 20); alert(myCat.name); //alerts 'Micia' alert(myCat.age); //alerts 20 (end) */ extend: function(properties){ var proto = new this(null); for (var property in properties){ var pp = proto[property]; proto[property] = Class.Merge(pp, properties[property]); } return new Class(proto); }, /* Property: implement Implements the passed in properties to the base Class prototypes, altering the base class, unlike . Arguments: properties - the properties to add to the base class. Example: (start code) var Animal = new Class({ initialize: function(age){ this.age = age; } }); Animal.implement({ setName: function(name){ this.name = name } }); var myAnimal = new Animal(20); myAnimal.setName('Micia'); alert(myAnimal.name); //alerts 'Micia' (end) */ implement: function(){ for (var i = 0, l = arguments.length; i < l; i++) $extend(this.prototype, arguments[i]); } }; //internal Class.Merge = function(previous, current){ if (previous && previous != current){ var type = $type(current); if (type != $type(previous)) return current; switch(type){ case 'function': var merged = function(){ this.parent = arguments.callee.parent; return current.apply(this, arguments); }; merged.parent = previous; return merged; case 'object': return $merge(previous, current); } } return current; }; /* Script: Class.Extras.js Contains common implementations for custom classes. In Mootools is implemented in , and and many more. License: MIT-style license. */ /* Class: Chain An "Utility" Class. Its methods can be implemented with into any . Currently implemented in , and . In for example, is used to execute a list of function, one after another, once the effect is completed. The functions will not be fired all togheter, but one every completion, to create custom complex animations. Example: (start code) var myFx = new Fx.Style('element', 'opacity'); myFx.start(1,0).chain(function(){ myFx.start(0,1); }).chain(function(){ myFx.start(1,0); }).chain(function(){ myFx.start(0,1); }); //the element will appear and disappear three times (end) */ var Chain = new Class({ /* Property: chain adds a function to the Chain instance stack. Arguments: fn - the function to append. */ chain: function(fn){ this.chains = this.chains || []; this.chains.push(fn); return this; }, /* Property: callChain Executes the first function of the Chain instance stack, then removes it. The first function will then become the second. */ callChain: function(){ if (this.chains && this.chains.length) this.chains.shift().delay(10, this); }, /* Property: clearChain Clears the stack of a Chain instance. */ clearChain: function(){ this.chains = []; } }); /* Class: Events An "Utility" Class. Its methods can be implemented with into any . In Class, for example, is used to give the possibility add any number of functions to the Effects events, like onComplete, onStart, onCancel. Events in a Class that implements can be either added as an option, or with addEvent. Never with .options.onEventName. Example: (start code) var myFx = new Fx.Style('element', 'opacity').addEvent('onComplete', function(){ alert('the effect is completed'); }).addEvent('onComplete', function(){ alert('I told you the effect is completed'); }); myFx.start(0,1); //upon completion it will display the 2 alerts, in order. (end) Implementing: This class can be implemented into other classes to add the functionality to them. Goes well with the class. Example: (start code) var Widget = new Class({ initialize: function(){}, finish: function(){ this.fireEvent('onComplete'); } }); Widget.implement(new Events); //later... var myWidget = new Widget(); myWidget.addEvent('onComplete', myfunction); (end) */ var Events = new Class({ /* Property: addEvent adds an event to the stack of events of the Class instance. Arguments: type - string; the event name (e.g. 'onComplete') fn - function to execute */ addEvent: function(type, fn){ if (fn != Class.empty){ this.$events = this.$events || {}; this.$events[type] = this.$events[type] || []; this.$events[type].include(fn); } return this; }, /* Property: fireEvent fires all events of the specified type in the Class instance. Arguments: type - string; the event name (e.g. 'onComplete') args - array or single object; arguments to pass to the function; if more than one argument, must be an array delay - (integer) delay (in ms) to wait to execute the event Example: (start code) var Widget = new Class({ initialize: function(arg1, arg2){ ... this.fireEvent("onInitialize", [arg1, arg2], 50); } }); Widget.implement(new Events); (end) */ fireEvent: function(type, args, delay){ if (this.$events && this.$events[type]){ this.$events[type].each(function(fn){ fn.create({'bind': this, 'delay': delay, 'arguments': args})(); }, this); } return this; }, /* Property: removeEvent removes an event from the stack of events of the Class instance. Arguments: type - string; the event name (e.g. 'onComplete') fn - function that was added */ removeEvent: function(type, fn){ if (this.$events && this.$events[type]) this.$events[type].remove(fn); return this; } }); /* Class: Options An "Utility" Class. Its methods can be implemented with into any . Used to automate the options settings, also adding Class when the option begins with on. Example: (start code) var Widget = new Class({ options: { color: '#fff', size: { width: 100 height: 100 } }, initialize: function(options){ this.setOptions(options); } }); Widget.implement(new Options); //later... var myWidget = new Widget({ color: '#f00', size: { width: 200 } }); //myWidget.options = {color: #f00, size: {width: 200, height: 100}} (end) */ var Options = new Class({ /* Property: setOptions sets this.options Arguments: defaults - object; the default set of options options - object; the user entered options. can be empty too. Note: if your Class has implemented, every option beginning with on, followed by a capital letter (onComplete) becomes an Class instance event. */ setOptions: function(){ this.options = $merge.apply(null, [this.options].extend(arguments)); if (this.addEvent){ for (var option in this.options){ if ($type(this.options[option] == 'function') && (/^on[A-Z]/).test(option)) this.addEvent(option, this.options[option]); } } return this; } }); /* Script: Array.js Contains Array prototypes, <$A>, <$each> License: MIT-style license. */ /* Class: Array A collection of The Array Object prototype methods. */ //custom methods Array.extend({ /* Property: forEach Iterates through an array; This method is only available for browsers without native *forEach* support. For more info see *forEach* executes the provided function (callback) once for each element present in the array. callback is invoked only for indexes of the array which have assigned values; it is not invoked for indexes which have been deleted or which have never been assigned values. Arguments: fn - function to execute with each item in the array; passed the item and the index of that item in the array bind - the object to bind "this" to (see ) Example: >['apple','banana','lemon'].each(function(item, index){ > alert(index + " = " + item); //alerts "0 = apple" etc. >}, bindObj); //optional second arg for binding, not used here */ forEach: function(fn, bind){ for (var i = 0, j = this.length; i < j; i++) fn.call(bind, this[i], i, this); }, /* Property: filter This method is provided only for browsers without native *filter* support. For more info see *filter* calls a provided callback function once for each element in an array, and constructs a new array of all the values for which callback returns a true value. callback is invoked only for indexes of the array which have assigned values; it is not invoked for indexes which have been deleted or which have never been assigned values. Array elements which do not pass the callback test are simply skipped, and are not included in the new array. Arguments: fn - function to execute with each item in the array; passed the item and the index of that item in the array bind - the object to bind "this" to (see ) Example: >var biggerThanTwenty = [10,3,25,100].filter(function(item, index){ > return item > 20; >}); >//biggerThanTwenty = [25,100] */ filter: function(fn, bind){ var results = []; for (var i = 0, j = this.length; i < j; i++){ if (fn.call(bind, this[i], i, this)) results.push(this[i]); } return results; }, /* Property: map This method is provided only for browsers without native *map* support. For more info see *map* calls a provided callback function once for each element in an array, in order, and constructs a new array from the results. callback is invoked only for indexes of the array which have assigned values; it is not invoked for indexes which have been deleted or which have never been assigned values. Arguments: fn - function to execute with each item in the array; passed the item and the index of that item in the array bind - the object to bind "this" to (see ) Example: >var timesTwo = [1,2,3].map(function(item, index){ > return item*2; >}); >//timesTwo = [2,4,6]; */ map: function(fn, bind){ var results = []; for (var i = 0, j = this.length; i < j; i++) results[i] = fn.call(bind, this[i], i, this); return results; }, /* Property: every This method is provided only for browsers without native *every* support. For more info see *every* executes the provided callback function once for each element present in the array until it finds one where callback returns a false value. If such an element is found, the every method immediately returns false. Otherwise, if callback returned a true value for all elements, every will return true. callback is invoked only for indexes of the array which have assigned values; it is not invoked for indexes which have been deleted or which have never been assigned values. Arguments: fn - function to execute with each item in the array; passed the item and the index of that item in the array bind - the object to bind "this" to (see ) Example: >var areAllBigEnough = [10,4,25,100].every(function(item, index){ > return item > 20; >}); >//areAllBigEnough = false */ every: function(fn, bind){ for (var i = 0, j = this.length; i < j; i++){ if (!fn.call(bind, this[i], i, this)) return false; } return true; }, /* Property: some This method is provided only for browsers without native *some* support. For more info see *some* executes the callback function once for each element present in the array until it finds one where callback returns a true value. If such an element is found, some immediately returns true. Otherwise, some returns false. callback is invoked only for indexes of the array which have assigned values; it is not invoked for indexes which have been deleted or which have never been assigned values. Arguments: fn - function to execute with each item in the array; passed the item and the index of that item in the array bind - the object to bind "this" to (see ) Example: >var isAnyBigEnough = [10,4,25,100].some(function(item, index){ > return item > 20; >}); >//isAnyBigEnough = true */ some: function(fn, bind){ for (var i = 0, j = this.length; i < j; i++){ if (fn.call(bind, this[i], i, this)) return true; } return false; }, /* Property: indexOf This method is provided only for browsers without native *indexOf* support. For more info see *indexOf* compares a search element to elements of the Array using strict equality (the same method used by the ===, or triple-equals, operator). Arguments: item - any type of object; element to locate in the array from - integer; optional; the index of the array at which to begin the search (defaults to 0) Example: >['apple','lemon','banana'].indexOf('lemon'); //returns 1 >['apple','lemon'].indexOf('banana'); //returns -1 */ indexOf: function(item, from){ var len = this.length; for (var i = (from < 0) ? Math.max(0, len + from) : from || 0; i < len; i++){ if (this[i] === item) return i; } return -1; }, /* Property: each Same as . Arguments: fn - function to execute with each item in the array; passed the item and the index of that item in the array bind - optional, the object that the "this" of the function will refer to. Example: >var Animals = ['Cat', 'Dog', 'Coala']; >Animals.each(function(animal){ > document.write(animal) >}); */ /* Property: copy returns a copy of the array. Returns: a new array which is a copy of the current one. Arguments: start - integer; optional; the index where to start the copy, default is 0. If negative, it is taken as the offset from the end of the array. length - integer; optional; the number of elements to copy. By default, copies all elements from start to the end of the array. Example: >var letters = ["a","b","c"]; >var copy = letters.copy(); // ["a","b","c"] (new instance) */ copy: function(start, length){ start = start || 0; if (start < 0) start = this.length + start; length = length || (this.length - start); var newArray = []; for (var i = 0; i < length; i++) newArray[i] = this[start++]; return newArray; }, /* Property: remove Removes all occurrences of an item from the array. Arguments: item - the item to remove Returns: the Array with all occurrences of the item removed. Example: >["1","2","3","2"].remove("2") // ["1","3"]; */ remove: function(item){ var i = 0; var len = this.length; while (i < len){ if (this[i] === item){ this.splice(i, 1); len--; } else { i++; } } return this; }, /* Property: contains Tests an array for the presence of an item. Arguments: item - the item to search for in the array. from - integer; optional; the index at which to begin the search, default is 0. If negative, it is taken as the offset from the end of the array. Returns: true - the item was found false - it wasn't Example: >["a","b","c"].contains("a"); // true >["a","b","c"].contains("d"); // false */ contains: function(item, from){ return this.indexOf(item, from) != -1; }, /* Property: associate Creates an object with key-value pairs based on the array of keywords passed in and the current content of the array. Arguments: keys - the array of keywords. Example: (start code) var Animals = ['Cat', 'Dog', 'Coala', 'Lizard']; var Speech = ['Miao', 'Bau', 'Fruuu', 'Mute']; var Speeches = Animals.associate(Speech); //Speeches['Miao'] is now Cat. //Speeches['Bau'] is now Dog. //... (end) */ associate: function(keys){ var obj = {}, length = Math.min(this.length, keys.length); for (var i = 0; i < length; i++) obj[keys[i]] = this[i]; return obj; }, /* Property: extend Extends an array with another one. Arguments: array - the array to extend ours with Example: >var Animals = ['Cat', 'Dog', 'Coala']; >Animals.extend(['Lizard']); >//Animals is now: ['Cat', 'Dog', 'Coala', 'Lizard']; */ extend: function(array){ for (var i = 0, j = array.length; i < j; i++) this.push(array[i]); return this; }, /* Property: merge merges an array in another array, without duplicates. (case- and type-sensitive) Arguments: array - the array to merge from. Example: >['Cat','Dog'].merge(['Dog','Coala']); //returns ['Cat','Dog','Coala'] */ merge: function(array){ for (var i = 0, l = array.length; i < l; i++) this.include(array[i]); return this; }, /* Property: include includes the passed in element in the array, only if its not already present. (case- and type-sensitive) Arguments: item - item to add to the array (if not present) Example: >['Cat','Dog'].include('Dog'); //returns ['Cat','Dog'] >['Cat','Dog'].include('Coala'); //returns ['Cat','Dog','Coala'] */ include: function(item){ if (!this.contains(item)) this.push(item); return this; }, /* Property: getRandom returns a random item in the Array */ getRandom: function(){ return this[$random(0, this.length - 1)] || null; }, /* Property: getLast returns the last item in the Array */ getLast: function(){ return this[this.length - 1] || null; } }); //copies Array.prototype.each = Array.prototype.forEach; Array.each = Array.forEach; /* Section: Utility Functions */ /* Function: $A() Same as , but as function. Useful to apply Array prototypes to iterable objects, as a collection of DOM elements or the arguments object. Example: (start code) function myFunction(){ $A(arguments).each(argument, function(){ alert(argument); }); }; //the above will alert all the arguments passed to the function myFunction. (end) */ function $A(array){ return Array.copy(array); }; /* Function: $each Use to iterate through iterables that are not regular arrays, such as builtin getElementsByTagName calls, arguments of a function, or an object. Arguments: iterable - an iterable element or an objct. function - function to apply to the iterable. bind - optional, the 'this' of the function will refer to this object. Function argument: The function argument will be passed the following arguments. item - the current item in the iterator being procesed index - integer; the index of the item, or key in case of an object. Examples: (start code) $each(['Sun','Mon','Tue'], function(day, index){ alert('name:' + day + ', index: ' + index); }); //alerts "name: Sun, index: 0", "name: Mon, index: 1", etc. //over an object $each({first: "Sunday", second: "Monday", third: "Tuesday"}, function(value, key){ alert("the " + key + " day of the week is " + value); }); //alerts "the first day of the week is Sunday", //"the second day of the week is Monday", etc. (end) */ function $each(iterable, fn, bind){ if (iterable && typeof iterable.length == 'number' && $type(iterable) != 'object'){ Array.forEach(iterable, fn, bind); } else { for (var name in iterable) fn.call(bind || iterable, iterable[name], name); } }; /*compatibility*/ Array.prototype.test = Array.prototype.contains; /*end compatibility*/ /* Script: String.js Contains String prototypes. License: MIT-style license. */ /* Class: String A collection of The String Object prototype methods. */ String.extend({ /* Property: test Tests a string with a regular expression. Arguments: regex - a string or regular expression object, the regular expression you want to match the string with params - optional, if first parameter is a string, any parameters you want to pass to the regex ('g' has no effect) Returns: true if a match for the regular expression is found in the string, false if not. See Example: >"I like cookies".test("cookie"); // returns true >"I like cookies".test("COOKIE", "i") // ignore case, returns true >"I like cookies".test("cake"); // returns false */ test: function(regex, params){ return (($type(regex) == 'string') ? new RegExp(regex, params) : regex).test(this); }, /* Property: toInt parses a string to an integer. Returns: either an int or "NaN" if the string is not a number. Example: >var value = "10px".toInt(); // value is 10 */ toInt: function(){ return parseInt(this, 10); }, /* Property: toFloat parses a string to an float. Returns: either a float or "NaN" if the string is not a number. Example: >var value = "10.848".toFloat(); // value is 10.848 */ toFloat: function(){ return parseFloat(this); }, /* Property: camelCase Converts a hiphenated string to a camelcase string. Example: >"I-like-cookies".camelCase(); //"ILikeCookies" Returns: the camel cased string */ camelCase: function(){ return this.replace(/-\D/g, function(match){ return match.charAt(1).toUpperCase(); }); }, /* Property: hyphenate Converts a camelCased string to a hyphen-ated string. Example: >"ILikeCookies".hyphenate(); //"I-like-cookies" */ hyphenate: function(){ return this.replace(/\w[A-Z]/g, function(match){ return (match.charAt(0) + '-' + match.charAt(1).toLowerCase()); }); }, /* Property: capitalize Converts the first letter in each word of a string to Uppercase. Example: >"i like cookies".capitalize(); //"I Like Cookies" Returns: the capitalized string */ capitalize: function(){ return this.replace(/\b[a-z]/g, function(match){ return match.toUpperCase(); }); }, /* Property: trim Trims the leading and trailing spaces off a string. Example: >" i like cookies ".trim() //"i like cookies" Returns: the trimmed string */ trim: function(){ return this.replace(/^\s+|\s+$/g, ''); }, /* Property: clean trims () a string AND removes all the double spaces in a string. Returns: the cleaned string Example: >" i like cookies \n\n".clean() //"i like cookies" */ clean: function(){ return this.replace(/\s{2,}/g, ' ').trim(); }, /* Property: rgbToHex Converts an RGB value to hexidecimal. The string must be in the format of "rgb(255,255,255)" or "rgba(255,255,255,1)"; Arguments: array - boolean value, defaults to false. Use true if you want the array ['FF','33','00'] as output instead of "#FF3300" Returns: hex string or array. returns "transparent" if the output is set as string and the fourth value of rgba in input string is 0. Example: >"rgb(17,34,51)".rgbToHex(); //"#112233" >"rgba(17,34,51,0)".rgbToHex(); //"transparent" >"rgb(17,34,51)".rgbToHex(true); //['11','22','33'] */ rgbToHex: function(array){ var rgb = this.match(/\d{1,3}/g); return (rgb) ? rgb.rgbToHex(array) : false; }, /* Property: hexToRgb Converts a hexidecimal color value to RGB. Input string must be the hex color value (with or without the hash). Also accepts triplets ('333'); Arguments: array - boolean value, defaults to false. Use true if you want the array [255,255,255] as output instead of "rgb(255,255,255)"; Returns: rgb string or array. Example: >"#112233".hexToRgb(); //"rgb(17,34,51)" >"#112233".hexToRgb(true); //[17,34,51] */ hexToRgb: function(array){ var hex = this.match(/^#?(\w{1,2})(\w{1,2})(\w{1,2})$/); return (hex) ? hex.slice(1).hexToRgb(array) : false; }, /* Property: contains checks if the passed in string is contained in the String. also accepts an optional second parameter, to check if the string is contained in a list of separated values. Example: >'a b c'.contains('c', ' '); //true >'a bc'.contains('bc'); //true >'a bc'.contains('b', ' '); //false */ contains: function(string, s){ return (s) ? (s + this + s).indexOf(s + string + s) > -1 : this.indexOf(string) > -1; }, /* Property: escapeRegExp Returns string with escaped regular expression characters Example: >var search = 'animals.sheeps[1]'.escapeRegExp(); // search is now 'animals\.sheeps\[1\]' Returns: Escaped string */ escapeRegExp: function(){ return this.replace(/([.*+?^${}()|[\]\/\\])/g, '\\$1'); } }); Array.extend({ /* Property: rgbToHex see , but as an array method. */ rgbToHex: function(array){ if (this.length < 3) return false; if (this.length == 4 && this[3] == 0 && !array) return 'transparent'; var hex = []; for (var i = 0; i < 3; i++){ var bit = (this[i] - 0).toString(16); hex.push((bit.length == 1) ? '0' + bit : bit); } return array ? hex : '#' + hex.join(''); }, /* Property: hexToRgb same as , but as an array method. */ hexToRgb: function(array){ if (this.length != 3) return false; var rgb = []; for (var i = 0; i < 3; i++){ rgb.push(parseInt((this[i].length == 1) ? this[i] + this[i] : this[i], 16)); } return array ? rgb : 'rgb(' + rgb.join(',') + ')'; } }); /* Script: Function.js Contains Function prototypes and utility functions . License: MIT-style license. Credits: - Some functions are inspired by those found in prototype.js (c) 2005 Sam Stephenson sam [at] conio [dot] net, MIT-style license */ /* Class: Function A collection of The Function Object prototype methods. */ Function.extend({ /* Property: create Main function to create closures. Returns: a function. Arguments: options - An Options object. Options: bind - The object that the "this" of the function will refer to. Default is the current function. event - If set to true, the function will act as an event listener and receive an event as first argument. If set to a class name, the function will receive a new instance of this class (with the event passed as argument's constructor) as first argument. Default is false. arguments - A single argument or array of arguments that will be passed to the function when called. If both the event and arguments options are set, the event is passed as first argument and the arguments array will follow. Default is no custom arguments, the function will receive the standard arguments when called. delay - Numeric value: if set, the returned function will delay the actual execution by this amount of milliseconds and return a timer handle when called. Default is no delay. periodical - Numeric value: if set, the returned function will periodically perform the actual execution with this specified interval and return a timer handle when called. Default is no periodical execution. attempt - If set to true, the returned function will try to execute and return either the results or false on error. Default is false. */ create: function(options){ var fn = this; options = $merge({ 'bind': fn, 'event': false, 'arguments': null, 'delay': false, 'periodical': false, 'attempt': false }, options); if ($chk(options.arguments) && $type(options.arguments) != 'array') options.arguments = [options.arguments]; return function(event){ var args; if (options.event){ event = event || window.event; args = [(options.event === true) ? event : new options.event(event)]; if (options.arguments) args.extend(options.arguments); } else args = options.arguments || arguments; var returns = function(){ return fn.apply($pick(options.bind, fn), args); }; if (options.delay) return setTimeout(returns, options.delay); if (options.periodical) return setInterval(returns, options.periodical); if (options.attempt) try {return returns();} catch(err){return false;}; return returns(); }; }, /* Property: pass Shortcut to create closures with arguments and bind. Returns: a function. Arguments: args - the arguments passed. must be an array if arguments > 1 bind - optional, the object that the "this" of the function will refer to. Example: >myFunction.pass([arg1, arg2], myElement); */ pass: function(args, bind){ return this.create({'arguments': args, 'bind': bind}); }, /* Property: attempt Tries to execute the function, returns either the result of the function or false on error. Arguments: args - the arguments passed. must be an array if arguments > 1 bind - optional, the object that the "this" of the function will refer to. Example: >myFunction.attempt([arg1, arg2], myElement); */ attempt: function(args, bind){ return this.create({'arguments': args, 'bind': bind, 'attempt': true})(); }, /* Property: bind method to easily create closures with "this" altered. Arguments: bind - optional, the object that the "this" of the function will refer to. args - optional, the arguments passed. must be an array if arguments > 1 Returns: a function. Example: >function myFunction(){ > this.setStyle('color', 'red'); > // note that 'this' here refers to myFunction, not an element > // we'll need to bind this function to the element we want to alter >}; >var myBoundFunction = myFunction.bind(myElement); >myBoundFunction(); // this will make the element myElement red. */ bind: function(bind, args){ return this.create({'bind': bind, 'arguments': args}); }, /* Property: bindAsEventListener cross browser method to pass event firer Arguments: bind - optional, the object that the "this" of the function will refer to. args - optional, the arguments passed. must be an array if arguments > 1 Returns: a function with the parameter bind as its "this" and as a pre-passed argument event or window.event, depending on the browser. Example: >function myFunction(event){ > alert(event.clientx) //returns the coordinates of the mouse.. >}; >myElement.onclick = myFunction.bindAsEventListener(myElement); */ bindAsEventListener: function(bind, args){ return this.create({'bind': bind, 'event': true, 'arguments': args}); }, /* Property: delay Delays the execution of a function by a specified duration. Arguments: delay - the duration to wait in milliseconds. bind - optional, the object that the "this" of the function will refer to. args - optional, the arguments passed. must be an array if arguments > 1 Example: >myFunction.delay(50, myElement) //wait 50 milliseconds, then call myFunction and bind myElement to it >(function(){alert('one second later...')}).delay(1000); //wait a second and alert */ delay: function(delay, bind, args){ return this.create({'delay': delay, 'bind': bind, 'arguments': args})(); }, /* Property: periodical Executes a function in the specified intervals of time Arguments: interval - the duration of the intervals between executions. bind - optional, the object that the "this" of the function will refer to. args - optional, the arguments passed. must be an array if arguments > 1 */ periodical: function(interval, bind, args){ return this.create({'periodical': interval, 'bind': bind, 'arguments': args})(); } }); /* Script: Number.js Contains the Number prototypes. License: MIT-style license. */ /* Class: Number A collection of The Number Object prototype methods. */ Number.extend({ /* Property: toInt Returns this number; useful because toInt must work on both Strings and Numbers. */ toInt: function(){ return parseInt(this); }, /* Property: toFloat Returns this number as a float; useful because toFloat must work on both Strings and Numbers. */ toFloat: function(){ return parseFloat(this); }, /* Property: limit Limits the number. Arguments: min - number, minimum value max - number, maximum value Returns: the number in the given limits. Example: >(12).limit(2, 6.5) // returns 6.5 >(-4).limit(2, 6.5) // returns 2 >(4.3).limit(2, 6.5) // returns 4.3 */ limit: function(min, max){ return Math.min(max, Math.max(min, this)); }, /* Property: round Returns the number rounded to specified precision. Arguments: precision - integer, number of digits after the decimal point. Can also be negative or zero (default). Example: >12.45.round() // returns 12 >12.45.round(1) // returns 12.5 >12.45.round(-1) // returns 10 Returns: The rounded number. */ round: function(precision){ precision = Math.pow(10, precision || 0); return Math.round(this * precision) / precision; }, /* Property: times Executes a passed in function the specified number of times Arguments: function - the function to be executed on each iteration of the loop Example: >(4).times(alert); */ times: function(fn){ for (var i = 0; i < this; i++) fn(i); } }); /* Script: Element.js Contains useful Element prototypes, to be used with the dollar function <$>. License: MIT-style license. Credits: - Some functions are inspired by those found in prototype.js (c) 2005 Sam Stephenson sam [at] conio [dot] net, MIT-style license */ /* Class: Element Custom class to allow all of its methods to be used with any DOM element via the dollar function <$>. */ var Element = new Class({ /* Property: initialize Creates a new element of the type passed in. Arguments: el - string; the tag name for the element you wish to create. you can also pass in an element reference, in which case it will be extended. props - object; the properties you want to add to your element. Accepts the same keys as , but also allows events and styles Props: the key styles will be used as setStyles, the key events will be used as addEvents. any other key is used as setProperty. Example: (start code) new Element('a', { 'styles': { 'display': 'block', 'border': '1px solid black' }, 'events': { 'click': function(){ //aaa }, 'mousedown': function(){ //aaa } }, 'class': 'myClassSuperClass', 'href': 'http://mad4milk.net' }); (end) */ initialize: function(el, props){ if ($type(el) == 'string'){ if (window.ie && props && (props.name || props.type)){ var name = (props.name) ? ' name="' + props.name + '"' : ''; var type = (props.type) ? ' type="' + props.type + '"' : ''; delete props.name; delete props.type; el = '<' + el + name + type + '>'; } el = document.createElement(el); } el = $(el); return (!props || !el) ? el : el.set(props); } }); /* Class: Elements - Every dom function such as <$$>, or in general every function that returns a collection of nodes in mootools, returns them as an Elements class. - The purpose of the Elements class is to allow methods to work also on array. - Elements is also an Array, so it accepts all the methods. - Every node of the Elements instance is already extended with <$>. Example: >$$('myselector').each(function(el){ > //... >}); some iterations here, $$('myselector') is also an array. >$$('myselector').setStyle('color', 'red'); every element returned by $$('myselector') also accepts methods, in this example every element will be made red. */ var Elements = new Class({ initialize: function(elements){ return (elements) ? $extend(elements, this) : this; } }); Elements.extend = function(props){ for (var prop in props){ this.prototype[prop] = props[prop]; this[prop] = $native.generic(prop); } }; /* Section: Utility Functions Function: $ returns the element passed in with all the Element prototypes applied. Arguments: el - a reference to an actual element or a string representing the id of an element Example: >$('myElement') // gets a DOM element by id with all the Element prototypes applied. >var div = document.getElementById('myElement'); >$(div) //returns an Element also with all the mootools extentions applied. You'll use this when you aren't sure if a variable is an actual element or an id, as well as just shorthand for document.getElementById(). Returns: a DOM element or false (if no id was found). Note: you need to call $ on an element only once to get all the prototypes. But its no harm to call it multiple times, as it will detect if it has been already extended. */ function $(el){ if (!el) return null; if (el.htmlElement) return Garbage.collect(el); if ([window, document].contains(el)) return el; var type = $type(el); if (type == 'string'){ el = document.getElementById(el); type = (el) ? 'element' : false; } if (type != 'element') return null; if (el.htmlElement) return Garbage.collect(el); if (['object', 'embed'].contains(el.tagName.toLowerCase())) return el; $extend(el, Element.prototype); el.htmlElement = function(){}; return Garbage.collect(el); }; /* Function: $$ Selects, and extends DOM elements. Elements arrays returned with $$ will also accept all the methods. The return type of element methods run through $$ is always an array. If the return array is only made by elements, $$ will be applied automatically. Arguments: HTML Collections, arrays of elements, arrays of strings as element ids, elements, strings as selectors. Any number of the above as arguments are accepted. Note: if you load , $$ will also accept CSS Selectors, otherwise the only selectors supported are tag names. Example: >$$('a') //an array of all anchor tags on the page >$$('a', 'b') //an array of all anchor and bold tags on the page >$$('#myElement') //array containing only the element with id = myElement. (only with ) >$$('#myElement a.myClass') //an array of all anchor tags with the class "myClass" >//within the DOM element with id "myElement" (only with ) >$$(myelement, myelement2, 'a', ['myid', myid2, 'myid3'], document.getElementsByTagName('div')) //an array containing: >// the element referenced as myelement if existing, >// the element referenced as myelement2 if existing, >// all the elements with a as tag in the page, >// the element with id = myid if existing >// the element with id = myid2 if existing >// the element with id = myid3 if existing >// all the elements with div as tag in the page Returns: array - array of all the dom elements matched, extended with <$>. Returns as . */ document.getElementsBySelector = document.getElementsByTagName; function $$(){ var elements = []; for (var i = 0, j = arguments.length; i < j; i++){ var selector = arguments[i]; switch($type(selector)){ case 'element': elements.push(selector); case 'boolean': break; case false: break; case 'string': selector = document.getElementsBySelector(selector, true); default: elements.extend(selector); } } return $$.unique(elements); }; $$.unique = function(array){ var elements = []; for (var i = 0, l = array.length; i < l; i++){ if (array[i].$included) continue; var element = $(array[i]); if (element && !element.$included){ element.$included = true; elements.push(element); } } for (var n = 0, d = elements.length; n < d; n++) elements[n].$included = null; return new Elements(elements); }; Elements.Multi = function(property){ return function(){ var args = arguments; var items = []; var elements = true; for (var i = 0, j = this.length, returns; i < j; i++){ returns = this[i][property].apply(this[i], args); if ($type(returns) != 'element') elements = false; items.push(returns); }; return (elements) ? $$.unique(items) : items; }; }; Element.extend = function(properties){ for (var property in properties){ HTMLElement.prototype[property] = properties[property]; Element.prototype[property] = properties[property]; Element[property] = $native.generic(property); var elementsProperty = (Array.prototype[property]) ? property + 'Elements' : property; Elements.prototype[elementsProperty] = Elements.Multi(property); } }; /* Class: Element Custom class to allow all of its methods to be used with any DOM element via the dollar function <$>. */ Element.extend({ /* Property: set you can set events, styles and properties with this shortcut. same as calling new Element. */ set: function(props){ for (var prop in props){ var val = props[prop]; switch(prop){ case 'styles': this.setStyles(val); break; case 'events': if (this.addEvents) this.addEvents(val); break; case 'properties': this.setProperties(val); break; default: this.setProperty(prop, val); } } return this; }, inject: function(el, where){ el = $(el); switch(where){ case 'before': el.parentNode.insertBefore(this, el); break; case 'after': var next = el.getNext(); if (!next) el.parentNode.appendChild(this); else el.parentNode.insertBefore(this, next); break; case 'top': var first = el.firstChild; if (first){ el.insertBefore(this, first); break; } default: el.appendChild(this); } return this; }, /* Property: injectBefore Inserts the Element before the passed element. Arguments: el - an element reference or the id of the element to be injected in. Example: >html: >
    >
    >js: >$('mySecondElement').injectBefore('myElement'); >resulting html: >
    >
    */ injectBefore: function(el){ return this.inject(el, 'before'); }, /* Property: injectAfter Same as , but inserts the element after. */ injectAfter: function(el){ return this.inject(el, 'after'); }, /* Property: injectInside Same as , but inserts the element inside. */ injectInside: function(el){ return this.inject(el, 'bottom'); }, /* Property: injectTop Same as , but inserts the element inside, at the top. */ injectTop: function(el){ return this.inject(el, 'top'); }, /* Property: adopt Inserts the passed elements inside the Element. Arguments: accepts elements references, element ids as string, selectors ($$('stuff')) / array of elements, array of ids as strings and collections. */ adopt: function(){ var elements = []; $each(arguments, function(argument){ elements = elements.concat(argument); }); $$(elements).inject(this); return this; }, /* Property: remove Removes the Element from the DOM. Example: >$('myElement').remove() //bye bye */ remove: function(){ return this.parentNode.removeChild(this); }, /* Property: clone Clones the Element and returns the cloned one. Arguments: contents - boolean, when true the Element is cloned with childNodes, default true Returns: the cloned element Example: >var clone = $('myElement').clone().injectAfter('myElement'); >//clones the Element and append the clone after the Element. */ clone: function(contents){ var el = $(this.cloneNode(contents !== false)); if (!el.$events) return el; el.$events = {}; for (var type in this.$events) el.$events[type] = { 'keys': $A(this.$events[type].keys), 'values': $A(this.$events[type].values) }; return el.removeEvents(); }, /* Property: replaceWith Replaces the Element with an element passed. Arguments: el - a string representing the element to be injected in (myElementId, or div), or an element reference. If you pass div or another tag, the element will be created. Returns: the passed in element Example: >$('myOldElement').replaceWith($('myNewElement')); //$('myOldElement') is gone, and $('myNewElement') is in its place. */ replaceWith: function(el){ el = $(el); this.parentNode.replaceChild(el, this); return el; }, /* Property: appendText Appends text node to a DOM element. Arguments: text - the text to append. Example: >
    hey
    >$('myElement').appendText(' howdy'); //myElement innerHTML is now "hey howdy" */ appendText: function(text){ this.appendChild(document.createTextNode(text)); return this; }, /* Property: hasClass Tests the Element to see if it has the passed in className. Returns: true - the Element has the class false - it doesn't Arguments: className - string; the class name to test. Example: >
    >$('myElement').hasClass('testClass'); //returns true */ hasClass: function(className){ return this.className.contains(className, ' '); }, /* Property: addClass Adds the passed in class to the Element, if the element doesnt already have it. Arguments: className - string; the class name to add Example: >
    >$('myElement').addClass('newClass'); //
    */ addClass: function(className){ if (!this.hasClass(className)) this.className = (this.className + ' ' + className).clean(); return this; }, /* Property: removeClass Works like , but removes the class from the element. */ removeClass: function(className){ this.className = this.className.replace(new RegExp('(^|\\s)' + className + '(?:\\s|$)'), '$1').clean(); return this; }, /* Property: toggleClass Adds or removes the passed in class name to the element, depending on if it's present or not. Arguments: className - the class to add or remove Example: >
    >$('myElement').toggleClass('myClass'); >
    >$('myElement').toggleClass('myClass'); >
    */ toggleClass: function(className){ return this.hasClass(className) ? this.removeClass(className) : this.addClass(className); }, /* Property: setStyle Sets a css property to the Element. Arguments: property - the property to set value - the value to which to set it; for numeric values that require "px" you can pass an integer Example: >$('myElement').setStyle('width', '300px'); //the width is now 300px >$('myElement').setStyle('width', 300); //the width is now 300px */ setStyle: function(property, value){ switch(property){ case 'opacity': return this.setOpacity(parseFloat(value)); case 'float': property = (window.ie) ? 'styleFloat' : 'cssFloat'; } property = property.camelCase(); switch($type(value)){ case 'number': if (!['zIndex', 'zoom'].contains(property)) value += 'px'; break; case 'array': value = 'rgb(' + value.join(',') + ')'; } this.style[property] = value; return this; }, /* Property: setStyles Applies a collection of styles to the Element. Arguments: source - an object or string containing all the styles to apply. When its a string it overrides old style. Examples: >$('myElement').setStyles({ > border: '1px solid #000', > width: 300, > height: 400 >}); OR >$('myElement').setStyles('border: 1px solid #000; width: 300px; height: 400px;'); */ setStyles: function(source){ switch($type(source)){ case 'object': Element.setMany(this, 'setStyle', source); break; case 'string': this.style.cssText = source; } return this; }, /* Property: setOpacity Sets the opacity of the Element, and sets also visibility == "hidden" if opacity == 0, and visibility = "visible" if opacity > 0. Arguments: opacity - float; Accepts values from 0 to 1. Example: >$('myElement').setOpacity(0.5) //make it 50% transparent */ setOpacity: function(opacity){ if (opacity == 0){ if (this.style.visibility != "hidden") this.style.visibility = "hidden"; } else { if (this.style.visibility != "visible") this.style.visibility = "visible"; } if (!this.currentStyle || !this.currentStyle.hasLayout) this.style.zoom = 1; if (window.ie) this.style.filter = (opacity == 1) ? '' : "alpha(opacity=" + opacity * 100 + ")"; this.style.opacity = this.$tmp.opacity = opacity; return this; }, /* Property: getStyle Returns the style of the Element given the property passed in. Arguments: property - the css style property you want to retrieve Example: >$('myElement').getStyle('width'); //returns "400px" >//but you can also use >$('myElement').getStyle('width').toInt(); //returns 400 Returns: the style as a string */ getStyle: function(property){ property = property.camelCase(); var result = this.style[property]; if (!$chk(result)){ if (property == 'opacity') return this.$tmp.opacity; result = []; for (var style in Element.Styles){ if (property == style){ Element.Styles[style].each(function(s){ var style = this.getStyle(s); result.push(parseInt(style) ? style : '0px'); }, this); if (property == 'border'){ var every = result.every(function(bit){ return (bit == result[0]); }); return (every) ? result[0] : false; } return result.join(' '); } } if (property.contains('border')){ if (Element.Styles.border.contains(property)){ return ['Width', 'Style', 'Color'].map(function(p){ return this.getStyle(property + p); }, this).join(' '); } else if (Element.borderShort.contains(property)){ return ['Top', 'Right', 'Bottom', 'Left'].map(function(p){ return this.getStyle('border' + p + property.replace('border', '')); }, this).join(' '); } } if (document.defaultView) result = document.defaultView.getComputedStyle(this, null).getPropertyValue(property.hyphenate()); else if (this.currentStyle) result = this.currentStyle[property]; } if (window.ie) result = Element.fixStyle(property, result, this); if (result && property.test(/color/i) && result.contains('rgb')){ return result.split('rgb').splice(1,4).map(function(color){ return color.rgbToHex(); }).join(' '); } return result; }, /* Property: getStyles Returns an object of styles of the Element for each argument passed in. Arguments: properties - strings; any number of style properties Example: >$('myElement').getStyles('width','height','padding'); >//returns an object like: >{width: "10px", height: "10px", padding: "10px 0px 10px 0px"} */ getStyles: function(){ return Element.getMany(this, 'getStyle', arguments); }, walk: function(brother, start){ brother += 'Sibling'; var el = (start) ? this[start] : this[brother]; while (el && $type(el) != 'element') el = el[brother]; return $(el); }, /* Property: getPrevious Returns the previousSibling of the Element, excluding text nodes. Example: >$('myElement').getPrevious(); //get the previous DOM element from myElement Returns: the sibling element or undefined if none found. */ getPrevious: function(){ return this.walk('previous'); }, /* Property: getNext Works as Element.getPrevious, but tries to find the nextSibling. */ getNext: function(){ return this.walk('next'); }, /* Property: getFirst Works as , but tries to find the firstChild. */ getFirst: function(){ return this.walk('next', 'firstChild'); }, /* Property: getLast Works as , but tries to find the lastChild. */ getLast: function(){ return this.walk('previous', 'lastChild'); }, /* Property: getParent returns the $(element.parentNode) */ getParent: function(){ return $(this.parentNode); }, /* Property: getChildren returns all the $(element.childNodes), excluding text nodes. Returns as . */ getChildren: function(){ return $$(this.childNodes); }, /* Property: hasChild returns true if the passed in element is a child of the $(element). */ hasChild: function(el){ return !!$A(this.getElementsByTagName('*')).contains(el); }, /* Property: getProperty Gets the an attribute of the Element. Arguments: property - string; the attribute to retrieve Example: >$('myImage').getProperty('src') // returns whatever.gif Returns: the value, or an empty string */ getProperty: function(property){ var index = Element.Properties[property]; if (index) return this[index]; var flag = Element.PropertiesIFlag[property] || 0; if (!window.ie || flag) return this.getAttribute(property, flag); var node = this.attributes[property]; return (node) ? node.nodeValue : null; }, /* Property: removeProperty Removes an attribute from the Element Arguments: property - string; the attribute to remove */ removeProperty: function(property){ var index = Element.Properties[property]; if (index) this[index] = ''; else this.removeAttribute(property); return this; }, /* Property: getProperties same as , but for properties */ getProperties: function(){ return Element.getMany(this, 'getProperty', arguments); }, /* Property: setProperty Sets an attribute for the Element. Arguments: property - string; the property to assign the value passed in value - the value to assign to the property passed in Example: >$('myImage').setProperty('src', 'whatever.gif'); //myImage now points to whatever.gif for its source */ setProperty: function(property, value){ var index = Element.Properties[property]; if (index) this[index] = value; else this.setAttribute(property, value); return this; }, /* Property: setProperties Sets numerous attributes for the Element. Arguments: source - an object with key/value pairs. Example: (start code) $('myElement').setProperties({ src: 'whatever.gif', alt: 'whatever dude' }); whatever dude (end) */ setProperties: function(source){ return Element.setMany(this, 'setProperty', source); }, /* Property: setHTML Sets the innerHTML of the Element. Arguments: html - string; the new innerHTML for the element. Example: >$('myElement').setHTML(newHTML) //the innerHTML of myElement is now = newHTML */ setHTML: function(){ this.innerHTML = $A(arguments).join(''); return this; }, /* Property: setText Sets the inner text of the Element. Arguments: text - string; the new text content for the element. Example: >$('myElement').setText('some text') //the text of myElement is now = 'some text' */ setText: function(text){ var tag = this.getTag(); if (['style', 'script'].contains(tag)){ if (window.ie){ if (tag == 'style') this.styleSheet.cssText = text; else if (tag == 'script') this.setProperty('text', text); return this; } else { this.removeChild(this.firstChild); return this.appendText(text); } } this[$defined(this.innerText) ? 'innerText' : 'textContent'] = text; return this; }, /* Property: getText Gets the inner text of the Element. */ getText: function(){ var tag = this.getTag(); if (['style', 'script'].contains(tag)){ if (window.ie){ if (tag == 'style') return this.styleSheet.cssText; else if (tag == 'script') return this.getProperty('text'); } else { return this.innerHTML; } } return ($pick(this.innerText, this.textContent)); }, /* Property: getTag Returns the tagName of the element in lower case. Example: >$('myImage').getTag() // returns 'img' Returns: The tag name in lower case */ getTag: function(){ return this.tagName.toLowerCase(); }, /* Property: empty Empties an element of all its children. Example: >$('myDiv').empty() // empties the Div and returns it Returns: The element. */ empty: function(){ Garbage.trash(this.getElementsByTagName('*')); return this.setHTML(''); } }); Element.fixStyle = function(property, result, element){ if ($chk(parseInt(result))) return result; if (['height', 'width'].contains(property)){ var values = (property == 'width') ? ['left', 'right'] : ['top', 'bottom']; var size = 0; values.each(function(value){ size += element.getStyle('border-' + value + '-width').toInt() + element.getStyle('padding-' + value).toInt(); }); return element['offset' + property.capitalize()] - size + 'px'; } else if (property.test(/border(.+)Width|margin|padding/)){ return '0px'; } return result; }; Element.Styles = {'border': [], 'padding': [], 'margin': []}; ['Top', 'Right', 'Bottom', 'Left'].each(function(direction){ for (var style in Element.Styles) Element.Styles[style].push(style + direction); }); Element.borderShort = ['borderWidth', 'borderStyle', 'borderColor']; Element.getMany = function(el, method, keys){ var result = {}; $each(keys, function(key){ result[key] = el[method](key); }); return result; }; Element.setMany = function(el, method, pairs){ for (var key in pairs) el[method](key, pairs[key]); return el; }; Element.Properties = new Abstract({ 'class': 'className', 'for': 'htmlFor', 'colspan': 'colSpan', 'rowspan': 'rowSpan', 'accesskey': 'accessKey', 'tabindex': 'tabIndex', 'maxlength': 'maxLength', 'readonly': 'readOnly', 'frameborder': 'frameBorder', 'value': 'value', 'disabled': 'disabled', 'checked': 'checked', 'multiple': 'multiple', 'selected': 'selected' }); Element.PropertiesIFlag = { 'href': 2, 'src': 2 }; Element.Methods = { Listeners: { addListener: function(type, fn){ if (this.addEventListener) this.addEventListener(type, fn, false); else this.attachEvent('on' + type, fn); return this; }, removeListener: function(type, fn){ if (this.removeEventListener) this.removeEventListener(type, fn, false); else this.detachEvent('on' + type, fn); return this; } } }; window.extend(Element.Methods.Listeners); document.extend(Element.Methods.Listeners); Element.extend(Element.Methods.Listeners); var Garbage = { elements: [], collect: function(el){ if (!el.$tmp){ Garbage.elements.push(el); el.$tmp = {'opacity': 1}; } return el; }, trash: function(elements){ for (var i = 0, j = elements.length, el; i < j; i++){ if (!(el = elements[i]) || !el.$tmp) continue; if (el.$events) el.fireEvent('trash').removeEvents(); for (var p in el.$tmp) el.$tmp[p] = null; for (var d in Element.prototype) el[d] = null; Garbage.elements[Garbage.elements.indexOf(el)] = null; el.htmlElement = el.$tmp = el = null; } Garbage.elements.remove(null); }, empty: function(){ Garbage.collect(window); Garbage.collect(document); Garbage.trash(Garbage.elements); } }; window.addListener('beforeunload', function(){ window.addListener('unload', Garbage.empty); if (window.ie) window.addListener('unload', CollectGarbage); }); /* Script: Element.Event.js Contains the Event Class, Element methods to deal with Element events, custom Events, and the Function prototype bindWithEvent. License: MIT-style license. */ /* Class: Event Cross browser methods to manage events. Arguments: event - the event Properties: shift - true if the user pressed the shift control - true if the user pressed the control alt - true if the user pressed the alt meta - true if the user pressed the meta key wheel - the amount of third button scrolling code - the keycode of the key pressed page.x - the x position of the mouse, relative to the full window page.y - the y position of the mouse, relative to the full window client.x - the x position of the mouse, relative to the viewport client.y - the y position of the mouse, relative to the viewport key - the key pressed as a lowercase string. key also returns 'enter', 'up', 'down', 'left', 'right', 'space', 'backspace', 'delete', 'esc'. Handy for these special keys. target - the event target relatedTarget - the event related target Example: (start code) $('myLink').onkeydown = function(event){ var event = new Event(event); //event is now the Event class. alert(event.key); //returns the lowercase letter pressed alert(event.shift); //returns true if the key pressed is shift if (event.key == 's' && event.control) alert('document saved'); }; (end) */ var Event = new Class({ initialize: function(event){ if (event && event.$extended) return event; this.$extended = true; event = event || window.event; this.event = event; this.type = event.type; this.target = event.target || event.srcElement; if (this.target.nodeType == 3) this.target = this.target.parentNode; this.shift = event.shiftKey; this.control = event.ctrlKey; this.alt = event.altKey; this.meta = event.metaKey; if (['DOMMouseScroll', 'mousewheel'].contains(this.type)){ this.wheel = (event.wheelDelta) ? event.wheelDelta / 120 : -(event.detail || 0) / 3; } else if (this.type.contains('key')){ this.code = event.which || event.keyCode; for (var name in Event.keys){ if (Event.keys[name] == this.code){ this.key = name; break; } } if (this.type == 'keydown'){ var fKey = this.code - 111; if (fKey > 0 && fKey < 13) this.key = 'f' + fKey; } this.key = this.key || String.fromCharCode(this.code).toLowerCase(); } else if (this.type.test(/(click|mouse|menu)/)){ this.page = { 'x': event.pageX || event.clientX + document.documentElement.scrollLeft, 'y': event.pageY || event.clientY + document.documentElement.scrollTop }; this.client = { 'x': event.pageX ? event.pageX - window.pageXOffset : event.clientX, 'y': event.pageY ? event.pageY - window.pageYOffset : event.clientY }; this.rightClick = (event.which == 3) || (event.button == 2); switch(this.type){ case 'mouseover': this.relatedTarget = event.relatedTarget || event.fromElement; break; case 'mouseout': this.relatedTarget = event.relatedTarget || event.toElement; } this.fixRelatedTarget(); } return this; }, /* Property: stop cross browser method to stop an event */ stop: function(){ return this.stopPropagation().preventDefault(); }, /* Property: stopPropagation cross browser method to stop the propagation of an event */ stopPropagation: function(){ if (this.event.stopPropagation) this.event.stopPropagation(); else this.event.cancelBubble = true; return this; }, /* Property: preventDefault cross browser method to prevent the default action of the event */ preventDefault: function(){ if (this.event.preventDefault) this.event.preventDefault(); else this.event.returnValue = false; return this; } }); Event.fix = { relatedTarget: function(){ if (this.relatedTarget && this.relatedTarget.nodeType == 3) this.relatedTarget = this.relatedTarget.parentNode; }, relatedTargetGecko: function(){ try {Event.fix.relatedTarget.call(this);} catch(e){this.relatedTarget = this.target;} } }; Event.prototype.fixRelatedTarget = (window.gecko) ? Event.fix.relatedTargetGecko : Event.fix.relatedTarget; /* Property: keys you can add additional Event keys codes this way: Example: (start code) Event.keys.whatever = 80; $(myelement).addEvent(keydown, function(event){ event = new Event(event); if (event.key == 'whatever') console.log(whatever key clicked). }); (end) */ Event.keys = new Abstract({ 'enter': 13, 'up': 38, 'down': 40, 'left': 37, 'right': 39, 'esc': 27, 'space': 32, 'backspace': 8, 'tab': 9, 'delete': 46 }); /* Class: Element Custom class to allow all of its methods to be used with any DOM element via the dollar function <$>. */ Element.Methods.Events = { /* Property: addEvent Attaches an event listener to a DOM element. Arguments: type - the event to monitor ('click', 'load', etc) without the prefix 'on'. fn - the function to execute Example: >$('myElement').addEvent('click', function(){alert('clicked!')}); */ addEvent: function(type, fn){ this.$events = this.$events || {}; this.$events[type] = this.$events[type] || {'keys': [], 'values': []}; if (this.$events[type].keys.contains(fn)) return this; this.$events[type].keys.push(fn); var realType = type; var custom = Element.Events[type]; if (custom){ if (custom.add) custom.add.call(this, fn); if (custom.map) fn = custom.map; if (custom.type) realType = custom.type; } if (!this.addEventListener) fn = fn.create({'bind': this, 'event': true}); this.$events[type].values.push(fn); return (Element.NativeEvents.contains(realType)) ? this.addListener(realType, fn) : this; }, /* Property: removeEvent Works as Element.addEvent, but instead removes the previously added event listener. */ removeEvent: function(type, fn){ if (!this.$events || !this.$events[type]) return this; var pos = this.$events[type].keys.indexOf(fn); if (pos == -1) return this; var key = this.$events[type].keys.splice(pos,1)[0]; var value = this.$events[type].values.splice(pos,1)[0]; var custom = Element.Events[type]; if (custom){ if (custom.remove) custom.remove.call(this, fn); if (custom.type) type = custom.type; } return (Element.NativeEvents.contains(type)) ? this.removeListener(type, value) : this; }, /* Property: addEvents As , but accepts an object and add multiple events at once. */ addEvents: function(source){ return Element.setMany(this, 'addEvent', source); }, /* Property: removeEvents removes all events of a certain type from an element. if no argument is passed in, removes all events. Arguments: type - string; the event name (e.g. 'click') */ removeEvents: function(type){ if (!this.$events) return this; if (!type){ for (var evType in this.$events) this.removeEvents(evType); this.$events = null; } else if (this.$events[type]){ this.$events[type].keys.each(function(fn){ this.removeEvent(type, fn); }, this); this.$events[type] = null; } return this; }, /* Property: fireEvent executes all events of the specified type present in the element. Arguments: type - string; the event name (e.g. 'click') args - array or single object; arguments to pass to the function; if more than one argument, must be an array delay - (integer) delay (in ms) to wait to execute the event */ fireEvent: function(type, args, delay){ if (this.$events && this.$events[type]){ this.$events[type].keys.each(function(fn){ fn.create({'bind': this, 'delay': delay, 'arguments': args})(); }, this); } return this; }, /* Property: cloneEvents Clones all events from an element to this element. Arguments: from - element, copy all events from this element type - optional, copies only events of this type */ cloneEvents: function(from, type){ if (!from.$events) return this; if (!type){ for (var evType in from.$events) this.cloneEvents(from, evType); } else if (from.$events[type]){ from.$events[type].keys.each(function(fn){ this.addEvent(type, fn); }, this); } return this; } }; window.extend(Element.Methods.Events); document.extend(Element.Methods.Events); Element.extend(Element.Methods.Events); /* Section: Custom Events */ Element.Events = new Abstract({ /* Event: mouseenter In addition to the standard javascript events (load, mouseover, mouseout, click, etc.) contains two custom events this event fires when the mouse enters the area of the dom element; will not be fired again if the mouse crosses over children of the element (unlike mouseover) Example: >$(myElement).addEvent('mouseenter', myFunction); */ 'mouseenter': { type: 'mouseover', map: function(event){ event = new Event(event); if (event.relatedTarget != this && !this.hasChild(event.relatedTarget)) this.fireEvent('mouseenter', event); } }, /* Event: mouseleave this event fires when the mouse exits the area of the dom element; will not be fired again if the mouse crosses over children of the element (unlike mouseout) Example: >$(myElement).addEvent('mouseleave', myFunction); */ 'mouseleave': { type: 'mouseout', map: function(event){ event = new Event(event); if (event.relatedTarget != this && !this.hasChild(event.relatedTarget)) this.fireEvent('mouseleave', event); } }, 'mousewheel': { type: (window.gecko) ? 'DOMMouseScroll' : 'mousewheel' } }); Element.NativeEvents = [ 'click', 'dblclick', 'mouseup', 'mousedown', //mouse buttons 'mousewheel', 'DOMMouseScroll', //mouse wheel 'mouseover', 'mouseout', 'mousemove', //mouse movement 'keydown', 'keypress', 'keyup', //keys 'load', 'unload', 'beforeunload', 'resize', 'move', //window 'focus', 'blur', 'change', 'submit', 'reset', 'select', //forms elements 'error', 'abort', 'contextmenu', 'scroll' //misc ]; /* Class: Function A collection of The Function Object prototype methods. */ Function.extend({ /* Property: bindWithEvent automatically passes MooTools Event Class. Arguments: bind - optional, the object that the "this" of the function will refer to. args - optional, an argument to pass to the function; if more than one argument, it must be an array of arguments. Returns: a function with the parameter bind as its "this" and as a pre-passed argument event or window.event, depending on the browser. Example: >function myFunction(event){ > alert(event.client.x) //returns the coordinates of the mouse.. >}; >myElement.addEvent('click', myFunction.bindWithEvent(myElement)); */ bindWithEvent: function(bind, args){ return this.create({'bind': bind, 'arguments': args, 'event': Event}); } }); /* Script: Element.Filters.js add Filters capability to . License: MIT-style license. */ /* Class: Elements A collection of methods to be used with <$$> elements collections. */ Elements.extend({ /* Property: filterByTag Filters the collection by a specified tag name. Returns a new Elements collection, while the original remains untouched. */ filterByTag: function(tag){ return new Elements(this.filter(function(el){ return (Element.getTag(el) == tag); })); }, /* Property: filterByClass Filters the collection by a specified class name. Returns a new Elements collection, while the original remains untouched. */ filterByClass: function(className, nocash){ var elements = this.filter(function(el){ return (el.className && el.className.contains(className, ' ')); }); return (nocash) ? elements : new Elements(elements); }, /* Property: filterById Filters the collection by a specified ID. Returns a new Elements collection, while the original remains untouched. */ filterById: function(id, nocash){ var elements = this.filter(function(el){ return (el.id == id); }); return (nocash) ? elements : new Elements(elements); }, /* Property: filterByAttribute Filters the collection by a specified attribute. Returns a new Elements collection, while the original remains untouched. Arguments: name - the attribute name. operator - optional, the attribute operator. value - optional, the attribute value, only valid if the operator is specified. */ filterByAttribute: function(name, operator, value, nocash){ var elements = this.filter(function(el){ var current = Element.getProperty(el, name); if (!current) return false; if (!operator) return true; switch(operator){ case '=': return (current == value); case '*=': return (current.contains(value)); case '^=': return (current.substr(0, value.length) == value); case '$=': return (current.substr(current.length - value.length) == value); case '!=': return (current != value); case '~=': return current.contains(value, ' '); } return false; }); return (nocash) ? elements : new Elements(elements); } }); /* Script: Element.Selectors.js Css Query related functions and extensions License: MIT-style license. */ /* Section: Utility Functions */ /* Function: $E Selects a single (i.e. the first found) Element based on the selector passed in and an optional filter element. Returns as . Arguments: selector - string; the css selector to match filter - optional; a DOM element to limit the scope of the selector match; defaults to document. Example: >$E('a', 'myElement') //find the first anchor tag inside the DOM element with id 'myElement' Returns: a DOM element - the first element that matches the selector */ function $E(selector, filter){ return ($(filter) || document).getElement(selector); }; /* Function: $ES Returns a collection of Elements that match the selector passed in limited to the scope of the optional filter. See Also: for an alternate syntax. Returns as . Returns: an array of dom elements that match the selector within the filter Arguments: selector - string; css selector to match filter - optional; a DOM element to limit the scope of the selector match; defaults to document. Examples: >$ES("a") //gets all the anchor tags; synonymous with $$("a") >$ES('a','myElement') //get all the anchor tags within $('myElement') */ function $ES(selector, filter){ return ($(filter) || document).getElementsBySelector(selector); }; $$.shared = { 'regexp': /^(\w*|\*)(?:#([\w-]+)|\.([\w-]+))?(?:\[(\w+)(?:([!*^$]?=)["']?([^"'\]]*)["']?)?])?$/, 'xpath': { getParam: function(items, context, param, i){ var temp = [context.namespaceURI ? 'xhtml:' : '', param[1]]; if (param[2]) temp.push('[@id="', param[2], '"]'); if (param[3]) temp.push('[contains(concat(" ", @class, " "), " ', param[3], ' ")]'); if (param[4]){ if (param[5] && param[6]){ switch(param[5]){ case '*=': temp.push('[contains(@', param[4], ', "', param[6], '")]'); break; case '^=': temp.push('[starts-with(@', param[4], ', "', param[6], '")]'); break; case '$=': temp.push('[substring(@', param[4], ', string-length(@', param[4], ') - ', param[6].length, ' + 1) = "', param[6], '"]'); break; case '=': temp.push('[@', param[4], '="', param[6], '"]'); break; case '!=': temp.push('[@', param[4], '!="', param[6], '"]'); } } else { temp.push('[@', param[4], ']'); } } items.push(temp.join('')); return items; }, getItems: function(items, context, nocash){ var elements = []; var xpath = document.evaluate('.//' + items.join('//'), context, $$.shared.resolver, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null); for (var i = 0, j = xpath.snapshotLength; i < j; i++) elements.push(xpath.snapshotItem(i)); return (nocash) ? elements : new Elements(elements.map($)); } }, 'normal': { getParam: function(items, context, param, i){ if (i == 0){ if (param[2]){ var el = context.getElementById(param[2]); if (!el || ((param[1] != '*') && (Element.getTag(el) != param[1]))) return false; items = [el]; } else { items = $A(context.getElementsByTagName(param[1])); } } else { items = $$.shared.getElementsByTagName(items, param[1]); if (param[2]) items = Elements.filterById(items, param[2], true); } if (param[3]) items = Elements.filterByClass(items, param[3], true); if (param[4]) items = Elements.filterByAttribute(items, param[4], param[5], param[6], true); return items; }, getItems: function(items, context, nocash){ return (nocash) ? items : $$.unique(items); } }, resolver: function(prefix){ return (prefix == 'xhtml') ? 'http://www.w3.org/1999/xhtml' : false; }, getElementsByTagName: function(context, tagName){ var found = []; for (var i = 0, j = context.length; i < j; i++) found.extend(context[i].getElementsByTagName(tagName)); return found; } }; $$.shared.method = (window.xpath) ? 'xpath' : 'normal'; /* Class: Element Custom class to allow all of its methods to be used with any DOM element via the dollar function <$>. */ Element.Methods.Dom = { /* Property: getElements Gets all the elements within an element that match the given (single) selector. Returns as . Arguments: selector - string; the css selector to match Examples: >$('myElement').getElements('a'); // get all anchors within myElement >$('myElement').getElements('input[name=dialog]') //get all input tags with name 'dialog' >$('myElement').getElements('input[name$=log]') //get all input tags with names ending with 'log' Notes: Supports these operators in attribute selectors: - = : is equal to - ^= : starts-with - $= : ends-with - != : is not equal to Xpath is used automatically for compliant browsers. */ getElements: function(selector, nocash){ var items = []; selector = selector.trim().split(' '); for (var i = 0, j = selector.length; i < j; i++){ var sel = selector[i]; var param = sel.match($$.shared.regexp); if (!param) break; param[1] = param[1] || '*'; var temp = $$.shared[$$.shared.method].getParam(items, this, param, i); if (!temp) break; items = temp; } return $$.shared[$$.shared.method].getItems(items, this, nocash); }, /* Property: getElement Same as , but returns only the first. Alternate syntax for <$E>, where filter is the Element. Returns as . Arguments: selector - string; css selector */ getElement: function(selector){ return $(this.getElements(selector, true)[0] || false); }, /* Property: getElementsBySelector Same as , but allows for comma separated selectors, as in css. Alternate syntax for <$$>, where filter is the Element. Returns as . Arguments: selector - string; css selector */ getElementsBySelector: function(selector, nocash){ var elements = []; selector = selector.split(','); for (var i = 0, j = selector.length; i < j; i++) elements = elements.concat(this.getElements(selector[i], true)); return (nocash) ? elements : $$.unique(elements); } }; Element.extend({ /* Property: getElementById Targets an element with the specified id found inside the Element. Does not overwrite document.getElementById. Arguments: id - string; the id of the element to find. */ getElementById: function(id){ var el = document.getElementById(id); if (!el) return false; for (var parent = el.parentNode; parent != this; parent = parent.parentNode){ if (!parent) return false; } return el; }/*compatibility*/, getElementsByClassName: function(className){ return this.getElements('.' + className); } /*end compatibility*/ }); document.extend(Element.Methods.Dom); Element.extend(Element.Methods.Dom); /* Script: Element.Form.js Contains Element prototypes to deal with Forms and their elements. License: MIT-style license. */ /* Class: Element Custom class to allow all of its methods to be used with any DOM element via the dollar function <$>. */ Element.extend({ /* Property: getValue Returns the value of the Element, if its tag is textarea, select or input. getValue called on a multiple select will return an array. */ getValue: function(){ switch(this.getTag()){ case 'select': var values = []; $each(this.options, function(option){ if (option.selected) values.push($pick(option.value, option.text)); }); return (this.multiple) ? values : values[0]; case 'input': if (!(this.checked && ['checkbox', 'radio'].contains(this.type)) && !['hidden', 'text', 'password'].contains(this.type)) break; case 'textarea': return this.value; } return false; }, getFormElements: function(){ return $$(this.getElementsByTagName('input'), this.getElementsByTagName('select'), this.getElementsByTagName('textarea')); }, /* Property: toQueryString Reads the children inputs of the Element and generates a query string, based on their values. Used internally in Example: (start code)
    (end) Returns: email=bob@bob.com&zipCode=90210 */ toQueryString: function(){ var queryString = []; this.getFormElements().each(function(el){ var name = el.name; var value = el.getValue(); if (value === false || !name || el.disabled) return; var qs = function(val){ queryString.push(name + '=' + encodeURIComponent(val)); }; if ($type(value) == 'array') value.each(qs); else qs(value); }); return queryString.join('&'); } }); /* Script: Element.Dimensions.js Contains Element prototypes to deal with Element size and position in space. Note: The functions in this script require n XHTML doctype. License: MIT-style license. */ /* Class: Element Custom class to allow all of its methods to be used with any DOM element via the dollar function <$>. */ Element.extend({ /* Property: scrollTo Scrolls the element to the specified coordinated (if the element has an overflow) Arguments: x - the x coordinate y - the y coordinate Example: >$('myElement').scrollTo(0, 100) */ scrollTo: function(x, y){ this.scrollLeft = x; this.scrollTop = y; }, /* Property: getSize Return an Object representing the size/scroll values of the element. Example: (start code) $('myElement').getSize(); (end) Returns: (start code) { 'scroll': {'x': 100, 'y': 100}, 'size': {'x': 200, 'y': 400}, 'scrollSize': {'x': 300, 'y': 500} } (end) */ getSize: function(){ return { 'scroll': {'x': this.scrollLeft, 'y': this.scrollTop}, 'size': {'x': this.offsetWidth, 'y': this.offsetHeight}, 'scrollSize': {'x': this.scrollWidth, 'y': this.scrollHeight} }; }, /* Property: getPosition Returns the real offsets of the element. Arguments: overflown - optional, an array of nested scrolling containers for scroll offset calculation, use this if your element is inside any element containing scrollbars Example: >$('element').getPosition(); Returns: >{x: 100, y:500}; */ getPosition: function(overflown){ overflown = overflown || []; var el = this, left = 0, top = 0; do { left += el.offsetLeft || 0; top += el.offsetTop || 0; el = el.offsetParent; } while (el); overflown.each(function(element){ left -= element.scrollLeft || 0; top -= element.scrollTop || 0; }); return {'x': left, 'y': top}; }, /* Property: getTop Returns the distance from the top of the window to the Element. Arguments: overflown - optional, an array of nested scrolling containers, see Element::getPosition */ getTop: function(overflown){ return this.getPosition(overflown).y; }, /* Property: getLeft Returns the distance from the left of the window to the Element. Arguments: overflown - optional, an array of nested scrolling containers, see Element::getPosition */ getLeft: function(overflown){ return this.getPosition(overflown).x; }, /* Property: getCoordinates Returns an object with width, height, left, right, top, and bottom, representing the values of the Element Arguments: overflown - optional, an array of nested scrolling containers, see Element::getPosition Example: (start code) var myValues = $('myElement').getCoordinates(); (end) Returns: (start code) { width: 200, height: 300, left: 100, top: 50, right: 300, bottom: 350 } (end) */ getCoordinates: function(overflown){ var position = this.getPosition(overflown); var obj = { 'width': this.offsetWidth, 'height': this.offsetHeight, 'left': position.x, 'top': position.y }; obj.right = obj.left + obj.width; obj.bottom = obj.top + obj.height; return obj; } }); /* Script: Window.DomReady.js Contains the custom event domready, for window. License: MIT-style license. */ /* Section: Custom Events */ /* Event: domready executes a function when the dom tree is loaded, without waiting for images. Only works when called from window. Credits: (c) Dean Edwards/Matthias Miller/John Resig, remastered for MooTools. Arguments: fn - the function to execute when the DOM is ready Example: > window.addEvent('domready', function(){ > alert('the dom is ready'); > }); */ Element.Events.domready = { add: function(fn){ if (window.loaded){ fn.call(this); return; } var domReady = function(){ if (window.loaded) return; window.loaded = true; window.timer = $clear(window.timer); this.fireEvent('domready'); }.bind(this); if (document.readyState && window.webkit){ window.timer = function(){ if (['loaded','complete'].contains(document.readyState)) domReady(); }.periodical(50); } else if (document.readyState && window.ie){ if (!$('ie_ready')){ var src = (window.location.protocol == 'https:') ? '://0' : 'javascript:void(0)'; document.write(' (end) */ send: function(options){ return new Ajax(this.getProperty('action'), $merge({data: this.toQueryString()}, options, {method: 'post'})).request(); } }); /* Script: Json.js Simple Json parser and Stringyfier, See: License: MIT-style license. */ /* Class: Json Simple Json parser and Stringyfier, See: */ var Json = { /* Property: toString Converts an object to a string, to be passed in server-side scripts as a parameter. Although its not normal usage for this class, this method can also be used to convert functions and arrays to strings. Arguments: obj - the object to convert to string Returns: A json string Example: (start code) Json.toString({apple: 'red', lemon: 'yellow'}); '{"apple":"red","lemon":"yellow"}' (end) */ toString: function(obj){ switch($type(obj)){ case 'string': return '"' + obj.replace(/(["\\])/g, '\\$1') + '"'; case 'array': return '[' + obj.map(Json.toString).join(',') + ']'; case 'object': var string = []; for (var property in obj) string.push(Json.toString(property) + ':' + Json.toString(obj[property])); return '{' + string.join(',') + '}'; case 'number': if (isFinite(obj)) break; case false: return 'null'; } return String(obj); }, /* Property: evaluate converts a json string to an javascript Object. Arguments: str - the string to evaluate. if its not a string, it returns false. secure - optionally, performs syntax check on json string. Defaults to false. Credits: Json test regexp is by Douglas Crockford . Example: >var myObject = Json.evaluate('{"apple":"red","lemon":"yellow"}'); >//myObject will become {apple: 'red', lemon: 'yellow'} */ evaluate: function(str, secure){ return (($type(str) != 'string') || (secure && !str.test(/^("(\\.|[^"\\\n\r])*?"|[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t])+?$/))) ? null : eval('(' + str + ')'); } }; /* Script: Hash.js Contains the class Hash. License: MIT-style license. */ /* Class: Hash It wraps an object that it uses internally as a map. The user must use set(), get(), and remove() to add/change, retrieve and remove values, it must not access the internal object directly. null/undefined values are allowed. Note: Each hash instance has the length property. Arguments: obj - an object to convert into a Hash instance. Example: (start code) var hash = new Hash({a: 'hi', b: 'world', c: 'howdy'}); hash.remove('b'); // b is removed. hash.set('c', 'hello'); hash.get('c'); // returns 'hello' hash.length // returns 2 (a and c) (end) */ var Hash = new Class({ length: 0, initialize: function(object){ this.obj = object || {}; this.setLength(); }, /* Property: get Retrieves a value from the hash. Arguments: key - The key Returns: The value */ get: function(key){ return (this.hasKey(key)) ? this.obj[key] : null; }, /* Property: hasKey Check the presence of a specified key-value pair in the hash. Arguments: key - The key Returns: True if the Hash contains a value for the specified key, otherwise false */ hasKey: function(key){ return (key in this.obj); }, /* Property: set Adds a key-value pair to the hash or replaces a previous value associated with the key. Arguments: key - The key value - The value */ set: function(key, value){ if (!this.hasKey(key)) this.length++; this.obj[key] = value; return this; }, setLength: function(){ this.length = 0; for (var p in this.obj) this.length++; return this; }, /* Property: remove Removes a key-value pair from the hash. Arguments: key - The key */ remove: function(key){ if (this.hasKey(key)){ delete this.obj[key]; this.length--; } return this; }, /* Property: each Calls a function for each key-value pair. The first argument passed to the function will be the value, the second one will be the key, like $each. Arguments: fn - The function to call for each key-value pair bind - Optional, the object that will be referred to as "this" in the function */ each: function(fn, bind){ $each(this.obj, fn, bind); }, /* Property: extend Extends the current hash with an object containing key-value pairs. Values for duplicate keys will be replaced by the new ones. Arguments: obj - An object containing key-value pairs */ extend: function(obj){ $extend(this.obj, obj); return this.setLength(); }, /* Property: merge Merges the current hash with multiple objects. */ merge: function(){ this.obj = $merge.apply(null, [this.obj].extend(arguments)); return this.setLength(); }, /* Property: empty Empties all hash values properties and values. */ empty: function(){ this.obj = {}; this.length = 0; return this; }, /* Property: keys Returns an array containing all the keys, in the same order as the values returned by . Returns: An array containing all the keys of the hash */ keys: function(){ var keys = []; for (var property in this.obj) keys.push(property); return keys; }, /* Property: values Returns an array containing all the values, in the same order as the keys returned by . Returns: An array containing all the values of the hash */ values: function(){ var values = []; for (var property in this.obj) values.push(this.obj[property]); return values; } }); /* Section: Utility Functions */ /* Function: $H Shortcut to create a Hash from an Object. */ function $H(obj){ return new Hash(obj); }; /* Script: Tips.js Tooltips, BubbleTips, whatever they are, they will appear on mouseover License: MIT-style license. Credits: The idea behind Tips.js is based on Bubble Tooltips () by Alessandro Fulcitiniti */ /* Class: Tips Display a tip on any element with a title and/or href. Note: Tips requires an XHTML doctype. Arguments: elements - a collection of elements to apply the tooltips to on mouseover. options - an object. See options Below. Options: maxTitleChars - the maximum number of characters to display in the title of the tip. defaults to 30. showDelay - the delay the onShow method is called. (defaults to 100 ms) hideDelay - the delay the onHide method is called. (defaults to 100 ms) className - the prefix for your tooltip classNames. defaults to 'tool'. the whole tooltip will have as classname: tool-tip the title will have as classname: tool-title the text will have as classname: tool-text offsets - the distance of your tooltip from the mouse. an Object with x/y properties. fixed - if set to true, the toolTip will not follow the mouse. Events: onShow - optionally you can alter the default onShow behaviour with this option (like displaying a fade in effect); onHide - optionally you can alter the default onHide behaviour with this option (like displaying a fade out effect); Example: (start code) (end) Note: The title of the element will always be used as the tooltip body. If you put :: on your title, the text before :: will become the tooltip title. */ var Tips = new Class({ options: { onShow: function(tip){ tip.setStyle('visibility', 'visible'); }, onHide: function(tip){ tip.setStyle('visibility', 'hidden'); }, maxTitleChars: 30, showDelay: 100, hideDelay: 100, className: 'tool', offsets: {'x': 16, 'y': 16}, fixed: false }, initialize: function(elements, options){ this.setOptions(options); this.toolTip = new Element('div', { 'class': this.options.className + '-tip', 'styles': { 'position': 'absolute', 'top': '0', 'left': '0', 'visibility': 'hidden' } }).inject(document.body); this.wrapper = new Element('div').inject(this.toolTip); $$(elements).each(this.build, this); if (this.options.initialize) this.options.initialize.call(this); }, build: function(el){ el.$tmp.myTitle = (el.href && el.getTag() == 'a') ? el.href.replace('http://', '') : (el.rel || false); if (el.title){ var dual = el.title.split('::'); if (dual.length > 1){ el.$tmp.myTitle = dual[0].trim(); el.$tmp.myText = dual[1].trim(); } else { el.$tmp.myText = el.title; } el.removeAttribute('title'); } else { el.$tmp.myText = false; } if (el.$tmp.myTitle && el.$tmp.myTitle.length > this.options.maxTitleChars) el.$tmp.myTitle = el.$tmp.myTitle.substr(0, this.options.maxTitleChars - 1) + "…"; el.addEvent('mouseenter', function(event){ this.start(el); if (!this.options.fixed) this.locate(event); else this.position(el); }.bind(this)); if (!this.options.fixed) el.addEvent('mousemove', this.locate.bindWithEvent(this)); var end = this.end.bind(this); el.addEvent('mouseleave', end); el.addEvent('trash', end); }, start: function(el){ this.wrapper.empty(); if (el.$tmp.myTitle){ this.title = new Element('span').inject(new Element('div', {'class': this.options.className + '-title'}).inject(this.wrapper)).setHTML(el.$tmp.myTitle); } if (el.$tmp.myText){ this.text = new Element('span').inject(new Element('div', {'class': this.options.className + '-text'}).inject(this.wrapper)).setHTML(el.$tmp.myText); } $clear(this.timer); this.timer = this.show.delay(this.options.showDelay, this); }, end: function(event){ $clear(this.timer); this.timer = this.hide.delay(this.options.hideDelay, this); }, position: function(element){ var pos = element.getPosition(); this.toolTip.setStyles({ 'left': pos.x + this.options.offsets.x, 'top': pos.y + this.options.offsets.y }); }, locate: function(event){ var win = {'x': window.getWidth(), 'y': window.getHeight()}; var scroll = {'x': window.getScrollLeft(), 'y': window.getScrollTop()}; var tip = {'x': this.toolTip.offsetWidth, 'y': this.toolTip.offsetHeight}; var prop = {'x': 'left', 'y': 'top'}; for (var z in prop){ var pos = event.page[z] + this.options.offsets[z]; if ((pos + tip[z] - scroll[z]) > win[z]) pos = event.page[z] - this.options.offsets[z] - tip[z]; this.toolTip.setStyle(prop[z], pos); }; }, show: function(){ if (this.options.timeout) this.timer = this.hide.delay(this.options.timeout, this); this.fireEvent('onShow', [this.toolTip]); }, hide: function(){ this.fireEvent('onHide', [this.toolTip]); } }); Tips.implement(new Events, new Options); vdr-plugin-live-3.1.3/live/js/mootools/readme.mootools.config000066400000000000000000000024771414414333500243020ustar00rootroot00000000000000This file documents the configuration of mootools! The otherwise cool feature of mootools to select only needed functionality can be a nightmare when trying to add additional features in a distributed development situation. Because the configured mootools download does not document in a central position which options have been selected to optain a specific version of mootools. Therefore this file, which documents the minimal selections needed to get a valid mootools configuration for the features of mootools used in live. In order to obtain the right mootools configuration follow these steps: - Go to mootools download page: http://mootools.net/download - Usualy 'Core' is preselected. This is OK :) but you can unselect it to have a 'clean' starting base. - Scroll down to the bottom of the page and select the following options towards the top of the download page in this order: 1. Tips 2. Hash 3. Json 4. Ajax 5. Drag.Move 6. Window.DomReady 7. Element.Selectors - Then open 'Choose compression type' and select 'No Compression' to have fully documented mootools source. This helps when developing own functionality and when trying do debug errors with Firebug. If mootools is extended by additional modules, please document this here in this file, by updating the selection scheme above. vdr-plugin-live-3.1.3/live/themes/000077500000000000000000000000001414414333500167705ustar00rootroot00000000000000vdr-plugin-live-3.1.3/live/themes/marine/000077500000000000000000000000001414414333500202435ustar00rootroot00000000000000vdr-plugin-live-3.1.3/live/themes/marine/css/000077500000000000000000000000001414414333500210335ustar00rootroot00000000000000vdr-plugin-live-3.1.3/live/themes/marine/css/theme.css000066400000000000000000000003321414414333500226450ustar00rootroot00000000000000/* ############################################## # This is the theme file for the marine theme. # It is empty because marine theme is the default live theme. ############################################## */ vdr-plugin-live-3.1.3/live/themes/marine/img/000077500000000000000000000000001414414333500210175ustar00rootroot00000000000000vdr-plugin-live-3.1.3/live/themes/marine/img/zap.png000066400000000000000000000015511414414333500223210ustar00rootroot00000000000000PNG  IHDRasBIT|d pHYsBtEXtSoftwarewww.inkscape.org<IDAT8mSKhQ=y4&iVӴI[*hW[ƅBB .jqQA7n\H XP mSS15id& ml.{9^wgH%o g6,675}!p xs|z]U֍UQUAQs|gW+K%*iQ:E?h=|y~3%U["WNG3I+_M,8muͮ*}:pv2pد04QVb{{R&j]6`Y%[Tux!{zv2 nb%)3G5!%0 kTU89 _3x@J |}hX_?!=Yݭ{u$;9 ?$`ΩaNT4^Le JAn7,A^@W ?ރPrpΜG:m&jngc[ΦlK2c;O&Fr|O!Ɍ  "Vᛢ50b$޾z7nţ(*c Z@IENDB`vdr-plugin-live-3.1.3/live/themes/orange-blue/000077500000000000000000000000001414414333500211705ustar00rootroot00000000000000vdr-plugin-live-3.1.3/live/themes/orange-blue/css/000077500000000000000000000000001414414333500217605ustar00rootroot00000000000000vdr-plugin-live-3.1.3/live/themes/orange-blue/css/theme.css000066400000000000000000000146731414414333500236070ustar00rootroot00000000000000/* ###################### # Globals ###################### */ html, body { background-color: #081966; color: white; } table { background-color: #263480; } input { border: 1px solid #081966; background: #DDDDFF; } select { border: 1px solid #081966; background: #DDDDFF; } a { color: #F5EE74; } /* ###################### # Tooltip style for hints ###################### */ .hint-tip { color: black; } .hint-tip .hint-tip-top { background: transparent url(/img/rounded-box-orange-tl.png) no-repeat 0px 0px; } .hint-tip .hint-tip-top .hint-tip-c { background: transparent url(/img/rounded-box-orange-tr.png) no-repeat right 0px; } .hint-tip .hint-tip-bdy { background: transparent url(/img/rounded-box-orange-ml.png) repeat-y 0px 0px; } .hint-tip .hint-tip-bdy .hint-tip-c { background: transparent url(/img/rounded-box-orange-mr.png) repeat-y right 0px; } .hint-tip .hint-tip-bot { background: transparent url(/img/rounded-box-orange-bl.png) no-repeat 0px 0px; } .hint-tip .hint-tip-bot .hint-tip-c { background: transparent url(/img/rounded-box-orange-br.png) no-repeat right 0px; } /* ############################## # Tooltip style for epg infos ############################## */ .info-win .info-win-top { background: transparent url(../img/info-win-t-l.png) no-repeat 0px 0px; } .info-win .info-win-top .info-win-c { background: transparent url(../img/info-win-t-r.png) no-repeat right 0px; } .info-win .info-win-top .info-win-c .info-win-t { color: #000; } .info-win .info-win-body { background: transparent url(../img/info-win-m-l.png) repeat-y 0px 0px; } .info-win .info-win-body .info-win-c { background: transparent url(../img/info-win-m-r.png) repeat-y right 0px; } .info-win .info-win-bot { background: transparent url(../img/info-win-b-l.png) no-repeat 0px 0px; } .info-win .info-win-bot .info-win-c { background: transparent url(../img/info-win-b-r.png) no-repeat right 0px; } /* ####################### # Menue ####################### */ div.menu { background: #FCBC40 url(../img/menu_line_bg.png) repeat-x; border-top: 1px solid #FFFDDD; border-bottom: 1px solid #FFFDDD; color: #122DBA; } div.menu a { color: black; } a#login { color: red; } div.menu a.active { color: #122DBA; } div#pagemenu { background: #FFFFFF url(../img/bg_line.png) top repeat-x; } div#pagemenu div { background: #FFFFFF url(../img/bg_line_top.png) bottom repeat-x; } div#pagemenu div div { background: #122DBA; border-top: 1px solid #C0C0FF; border-bottom: 1px solid #C0C0FF; } div#pagemenu a { color: #FCBC40; } div#pagemenu a.active { color: #FFFDDD; } div#pagemenu span { color: #FCBC40; } div#pagemenu span.sep { color: #FCBC40; } /* ####################### # Info Box (near logo) ####################### */ div#infobox { border: 1px solid #FCBC40; } div#infobox div.st_header { background: #122DBA; border-bottom: 1px solid #C0C0FF; } div#infobox div.st_content { background: #081966 url(../img/bg_line_top.png) top left repeat-x; } div#infobox div.st_controls div.st_update { border-right: 1px solid #FCBC40; } /* ################################ # general table cell classes ################################ */ table td.bottomrow { border-bottom: 1px solid #FFFDDD !important; } table td.leftcol { border-left: 1px solid #FFFDDD; } table td.rightcol { border-right: 1px solid #FFFDDD; } /* ################ # Mulitschedule ################ */ table.mschedule tr td.even { background-color: #081999; } table.mschedule tr td.odd { background-color: #081966; } table.mschedule tr td.current_row { background-color: #FCB840; } table.mschedule a { color: inherit; } /* ################ # Event ################ */ div.station div { background: url(../img/bg_box_l.png) top left no-repeat; } div.station div div { background: url(../img/bg_box_r.png) top right no-repeat; } div.station div div div { background: url(../img/bg_box_h.png) repeat-x; } div.station div div div a { color: #000000; } td div.station a { color: #FCBC40; } div.content { background: #263480 url(../img/bg_tools.png) top left repeat-y; border-left: 1px solid #FFFDDD; border-right: 1px solid #FFFDDD; border-bottom: 1px solid #FFFDDD; } div.__progress { border: 1px solid #C0C0FF; } div.__progress div.__elapsed { background-color: #FCB840; } /* ############# # Timers ############# */ table.listing tr td { /* background: url(../img/bg_line.png) bottom repeat-x; */ background: transparent; border-bottom: 1px solid #FCBC40; } table.listing tr.description td { background: #122DBA; } table.listing tr td.current { color: #FCBC40; } table.listing tr.spacer td { background-color: #081966; } table.listing a { color: #FCBC40; } /* ############################## # Blue Background Thingy ############################## */ div.boxheader { background: url(../img/bg_box_l.png) top left no-repeat; } div.boxheader div { background: url(../img/bg_box_r.png) top right no-repeat; } div.boxheader div div { background: url(../img/bg_box_h.png) repeat-x; color: black; } /* ############################## # Recordings ############################## */ div.recordings { border: 1px solid #FFFDDD; background-color: #263480; } div.recording_item { /* background: url(../img/bg_line.png) bottom repeat-x; */ background: transparent; border-bottom: 1px solid #FCBC40; } /* ############################## # Remote Control Keypad ############################## */ div.screenshot { background-image: url(../img/tv.jpg); } /* ############################## # Dotted Frame ############################## */ div.dotted { border: 1px dotted #bbbbbb; padding: 3px; margin: 2px; float: left; background-color: #122dBA; } /* ############################## # Edit Tables ############################## */ table.formular tr td { /* background: url(../img/bg_line.png) bottom repeat-x; */ background: transparent; border-bottom: 1px solid #FCBC40; } table.dependent { background-color: #263480; } /* ############################## # Infowin support styles for EPG-Boxes ############################## */ .info-win div.epg_content { background: transparent url(../img/bg_tools.png) top left repeat-y; } /* ############################## # About box ############################## */ .info-win div.epg_content div.about_head div div { background: #122DBA; border-top: 1px solid #C0C1DA; border-bottom: 1px solid #C0C1DA; } vdr-plugin-live-3.1.3/live/themes/orange-blue/img/000077500000000000000000000000001414414333500217445ustar00rootroot00000000000000vdr-plugin-live-3.1.3/live/themes/orange-blue/img/bg_box_h.png000066400000000000000000000001741414414333500242230ustar00rootroot00000000000000PNG  IHDRʚ%CIDATmȱ0 _-Qd[~ "[ܧ5DžL"v"(䝯.EhP|n.s)+HnSIENDB`vdr-plugin-live-3.1.3/live/themes/orange-blue/img/bg_box_l.png000066400000000000000000000002121414414333500242200ustar00rootroot00000000000000PNG  IHDRo2QIDATӍ1@DxYZ@B c&Rf=Kc6@jFz[LaGRuύ1//IENDB`vdr-plugin-live-3.1.3/live/themes/orange-blue/img/bg_box_r.png000066400000000000000000000002151414414333500242310ustar00rootroot00000000000000PNG  IHDRo2TIDATӍ10 (##Gf5C%2@-Cd`tgQ5MX^R$;>1guIENDB`vdr-plugin-live-3.1.3/live/themes/orange-blue/img/bg_header_h.png000066400000000000000000000002051414414333500246560ustar00rootroot00000000000000PNG  IHDRtLIDATm @ /4V` <$y ƒ\ܻUu $NΗy ׹!(Dbը=A4= o XvêUm'Qˏ¾h=`>%IENDB`vdr-plugin-live-3.1.3/live/themes/orange-blue/img/bg_line.png000066400000000000000000000001201414414333500240420ustar00rootroot00000000000000PNG  IHDRrcfIDATc)bbb`f=IENDB`vdr-plugin-live-3.1.3/live/themes/orange-blue/img/bg_line_top.png000066400000000000000000000001261414414333500247320ustar00rootroot00000000000000PNG  IHDRrcfIDATc>0B IENDB`vdr-plugin-live-3.1.3/live/themes/orange-blue/img/bg_tools.png000066400000000000000000000002401414414333500242560ustar00rootroot00000000000000PNG  IHDR!PHYbKGD&4y pHYs  tIME P"-IDATc`$K L?X?12}f`e|T R#IENDB`vdr-plugin-live-3.1.3/live/themes/orange-blue/img/info-win-b-l.png000066400000000000000000000015231414414333500246510ustar00rootroot00000000000000PNG  IHDR0bKGD pHYs  tIME.ёIDATxQn0tE}@ۙ#v%)4Es$veJߒLI\'Irc$w B?Sz@ $_z[?U{MG+̄Cot? ;?=ڇ!]NS8i 8O}k,ul̇u`]=`}a9\2WH`k*$ϫYF `Z$rZx[ҵjuO=ɇV{vVxz"@17}!`M`.s0`2\lk)]NGF0`u*.UCN?#0P3BdO&|4-IENDB`vdr-plugin-live-3.1.3/live/themes/orange-blue/img/info-win-b-r.png000066400000000000000000000007331414414333500246610ustar00rootroot00000000000000PNG  IHDRXbKGD pHYs  tIME/ ;hIDATHǽ[r0 Ed$M yV=ՌZ.o 7 \7 _wnf$3UH2)&ZM@Ilu*AgjɌ:'}M*49TjUS@gɪV5JSf&d)XWrvk /[VQ6ei"+ D_Q(mMQ|3:XUv_DZ3Td-.lo\5N\]u QcQd8Z6T"ȝtSkt3,UuEg2ȼ 耞X~pˏ?<42<3IENDB`vdr-plugin-live-3.1.3/live/themes/orange-blue/img/info-win-m-l.png000066400000000000000000000002731414414333500246650ustar00rootroot00000000000000PNG  IHDRUbKGD pHYs  tIME8#9HIDAThñ P@1FKY-S:ܨF_TMչTsTkuױ_3}^"o# IENDB`vdr-plugin-live-3.1.3/live/themes/orange-blue/img/info-win-m-r.png000066400000000000000000000002701414414333500246700ustar00rootroot00000000000000PNG  IHDR[bKGD pHYs  tIME4EIDAT]ʻ 0l\!:*hʃ e{?Z0cB0bn=M2vIENDB`vdr-plugin-live-3.1.3/live/themes/orange-blue/img/info-win-t-l.png000066400000000000000000000021431414414333500246720ustar00rootroot00000000000000PNG  IHDR%4bKGD pHYs  tIME-IDATx1nFv)Y`|")IrQ*Lt).w֊)AW+w} MrQO ptʑg !l~=ya_ox_ٷ o L}pݠ@p>[S bTe~r7(N fK8LSPu\=` w/٬PzѬ ,=`=/.$'8k``E@8 h7S]u<> ٟA;x@ 9'IΚjmv/׎Irӆڎj0">}]ׯ^;o8>eb_Cu,@%f$W/^&Nx{@PG&9W/ G8ciϗo~LNXXHΓ0z\d.D8qrI*"$Іa)i$~nRNgr3~ųx/QǸVIENDB`vdr-plugin-live-3.1.3/live/themes/orange-blue/img/logo.png000066400000000000000000000332351414414333500234200ustar00rootroot00000000000000PNG  IHDRDZ9' IDATxսweWu'[ksνXY%X@`0n 6n=3ap68"Y( ^n眽Zǹ/*s~WN{z[}UOo1*O~yWۺu@Q)XLLr A|މ>ϳuNެjs 7pR)03[DA "o@Uo 8B P@8qQ ${z>77?===q~l=;X|̄NO|SO??T|IPV@>hh ҥ5(Ksù):N_DZcǧ&ؚg|HCeϞ>73еWϽPj>ټcBd ,GG~?%FU+m⡲^YS:\Y-3\/f*EH{_=x$SA]Wr Oؾ+n{:"69)Gmd_P};7<41C] +BiYbC%6fJ`i5~U>#i4R2@m࣏Wk5c m$xk(" P̙ǟ<xq(udK^vӁ|D_0_ϚTsc%3PV*r }y$ޠ#i[޶ s*,~\ (XD+|iAe>!ldC t#07 o/S'+lҥ{)dtaU{O9/>؁,mpsOĐLK&(}1"#j}Y B "PQeR"AoH^Dq? ^1Q'77{SfZA$, ike!1EY&o ]Wj|'/6oW:JF||@1 BCR&``EJ!e #kU-Кl 81@D[!0334B .(~3d܁ GñT*ƪ8ʗ]w7Zi HNEd/>gO5ԩ3gƆ~YRDBϋ Yˈ<kPbYn@@kM(9)%+S_/o׏Nc0/N+/]/TEADUjaj@TCX\ x92I\پuޭYJ b@V%EHXgNTG`ndKց=:e l]vSbM u4,ryF$K=hdMUhRؒ77N[rG9$L6~ųVU(Qkۖk8yI=R(PĤ,1skqL+c1aO iz~y(JovC69u2DCr[ bV#ձ:U%pO< <; Rz MSwۋ|=y4zU_k'"4d:ln#Q36KǙ 'M/w9EÖVfyG' 'Ld" A6:>B)/QqH{zUyQ iwMm ^G'kS{\U4ڳvvcnj|qfk-:~f>R,EDDWA 8g=rXr>y/9 "*GHȴsQ\y+:&gVp6y{p^.=D]w=^O'Pu}yk}VJT$ sRy82uYCsEfNv{A oh Wݕ<ˍyUa$YX~x]'o91 P)ގl 'O|1@ha".s!ffn>xQAD'Aċ(qCF +r_640g AED/]>6~ H$,MIT[z+g!f׮G?/P$ {Y'%fQ5!t[]?;v %ؑCoZM3M$j{"#cVsZ }p~ѫY4:63nxS H._'J>Իz˭G:DE/Ї~=9PQd|wmƼAH`ʕ^-%A %v|p V !SroNaI\Q\ 1Gx_V-[@jMӜڳo>^ۙd[TќP^IDE4$kTn۶uߑW(vT*]wmd71v[7m37kںu3ɄH+;cS==33JRe-oy#GOxIdz.Upft h1%Fq%Iq)֠x^+!T58wK^֎LL(Х9 6F'kD,5jY8=|G{27j,\3俊(ط2"n-i&=Dc5'ȺyHl$s:,/6x`xx>"xsuaooсdx5 sam>[ly,T؞c7k{ϒ.x <{jS3j7>JYHUlq6݌1SG8"W<G'Qmx& &!AƯٝ#jdz2JYѲ.-g6x,IUxխy#𶷽9mUW?v#?s?T_f\Q~#pviyޘ^|nڱ>zVγ ]vE0Jny$EHH,Ss7Sd;SP懮4lϰSUՅvvثVn.0 ̬j#ȇQ*p8ev>|K]{>_=:芅#~wg) 6n5-*Fl,A.Kâ^- ݹiהtTCަB2} +O\bl4Wc$j.Z>tofɝ"&pڦX&]D˛A9jC-,CE{*HcxY+ʿ~WtO\[M~'tͷ'd͕\$H\ A=Ԯ9&kBKzM 3!k(2&m9}ޝc6@ɧ.:`@FUi) iW**#N~2<3r+ ><5gssL0tn<<x\;HօY% :)|4dkIZt+GO~oѝ-/'ϯw UtV@Xmy|`K01@D$ Ijjs**A_B5ro.q61댾%XUmB~"2fWL@| @z|dJOJ(nQ^]8\t"-u.rZ==zwdѐb!qZН /%Bk2^ʭx@"bN&~`8)jʼ ];lZ rM.td̅] H|}5qg <^TX]BLhIW=-{7I۳%Km;̭b"EA%Kџ$"2;}? %=ٛ/㗺.DؽŹvr&pBf1d,BRH]"bsj#",98RFyg6R-JҚ5E%GJkč[1k!AHT˳6.%*#C;m'yULV^$Pm]D)"fDalݵ= G^5 Wwݣ@&ο޶3WEB DqUVN@DCԑWЊyfՏ \".^സqiuL;c>8LaZ6QΡ#ӵjo}lb%[Qiy;>_? waSNΦ4XkM,z\'ODd(zuW1aۂx_wy&r1IsW J"K;+"at`gOy0`#W%کNָ j#dx$~ _4lirV68Q[I օ`uVP7,Ѣ§}{_p߸P%cW9B}|s_~w~j+y2jf,VP,`f5F @E.\"iZӥTV']`J*Qq¥F&?r5J$ZgZTvCQ'& Myz~E@N6gSi/"dmXQBj 2Q^`xEҝ Pȫ?>O74!Xc$&"sY}:v캃{u/:n&2+ո6vU1UI $þM`@CNJeKktu 0PܵcSBn8ˎcVj TnoMa{v4l@DzYuҴ̢I(st1/ \Q N e?dj͙.vcEf=$̀ xÇzjxҳ Cuanbb"ͲZ|>–-[~7~?CW1`U$zYZjwHL׉!pp&MvlRE蟺KS@aK}-%Gg4l/2><" $F6aKwr71rtY O'I Y!4qF2ManO]uUB; )\ "^8!d@ɩ[_m™ӧ{UݷlC9808BNe 5N=oWBޜ;Nc>ϧv'h V '}hǁMryM sj*B*QLĆ]O򕻒$3?[ieyHN'Mζ-[KIO/2o|W5O}rCO+cig^9cnXYǯt0ڰ E^(Rkbeeph1! TKrED?LcCDYHHHQ ޸dR4lޓXLx-1 ۴sMBq,$L⹞Zo玱M7{{IY|<|,M_7*充^{woc-*s^K9 hs˔`bv5g0kZjw@dLlo5zeOO"泵3Fc%!RG iUf_18p"RCd= C@l|cӚ @ CTI`x6`N}TPnSP@# 3)8ZyGIʛ@M+?q(s~V 5ճ)v l 2 s M8#KTؠlj`< &s~D"(!id` Cm)@t44|ZwuI8^vy&&Fߖ-}]YUW]UV)ͩE0r,]zgɟiUOO:'NJCAj:Dmח㞧SFĭ [zDB/DR^o;kηI Z* %kNh6V")T!LBai%-%ƍA*rc(T*vܞmYC܋x kFҶaʕ+JY-,,0C$WѾMqF۹ Ϧ޵ӵsg"Sq aT(* yE*fsnS$GWDa2l*Bla{nD\`ԲRQ.yW2yBUŰ2Uj"0嵆O1LsD1 )Z.(4#i;~uD|!W Y5>bId%{ !yD7 eVf!"igݩm&0yc 5̆9X$(Y/|gzWI-M۝Faӝ?M]0z}{<0=z;ERD$Td֫ $TD|6߸`Qf56R.t T^2$ gB #J4$#S(mcWfY󀒐,̥Nz. \bJVTÏ<Ϝ;un D\bN苣S**+>e iK 0lN̶UÓ%̕#;Jq^Ngϴ,7?]\xuWLm3w8 &Ƕ2I*FB!oZyJydBGN|y3qUժ% 񦞤wp`ʥ13`F$kMtjО]S| m4Ό ryZkvDخ4aiR1'rJ c9w2AM#]|= (]֟~sڛ8׷oNoe=2Dé&0}qCJU՝d7j<ؙcc{q+@Tn5Ov#'Kg.==~V ;*'-U`_{6Hu&ЊQZǽ-Lf&^9y۞oK0Sk #aᝅ~՛~\*bh mÀwWFsU$jQwi0ւKN}m|+Ձ@DOJc&l -m Ȱl{v-F@$xy3[7C&vólNM=7ڊ.zlMϞNmv\DbNh͖w6$\5p d-0A?K= ر1 >;q{_OaPa]JD夓VƷ<3{D5T.T|+=#k,ug.yAM/go!1ǑsmR0'< _o}n:^R@-ӚYcLdŋwk+Z{~rzSB E,}ÎFR 3ψݵ 5w>RA!GԇpE8J؞7gnt vʦ߃uUM[3So;NdT׽JL.ou0n4cJ^|7U@dS=V9R($N߷*bؼh}06=~ʨFXjșrXzm_oK:^U`A+Ff J0W4&I^V;@'F JğwE6=;n+ _o+[O3uǒh*3/xwm^Sbw]`p❘G5@` 'NQ2"ܫC:dsG?hWƮg5Yёr9 Sr 15d^33:O.Xu;Y:goW*PL9{J_yDzFӝ׍gʹ&sѼBv Lj~կ%(%cޫ6؈󓇒+pGR6 7XEDyb\JE๣6fOsV)U.Z-6Ln-<'F`$17:>,L#=mWK𮞶SSGN:8}h=v!ډFඁ=&-ެ&()_/s;_3eDϨ%A8M];DlDq\ܡ!xAV &@oOڞ(JzdN}ڨ1TCL1" ȑxMDcE{+*Sc"X@D{zπ[fJXh-T;A?y6?uxl>{O6X9,J;8u\4z%fu1wգLiQS?MtߚF ^ʢJ^ΉX[CYhZ1pVa<1}Z0s~v-[Fd6'"r;Dژ ŖwAHECT(H;stfwZJLњ6PiO>T"lz\9F0)ѳMRs 7^lXn.aIENDB`vdr-plugin-live-3.1.3/live/themes/orange-blue/img/menu_line_bg.png000066400000000000000000000001761414414333500251010ustar00rootroot00000000000000PNG  IHDRXtEIDATmA0 ixNօ LBb)uZ8]$b%B^:p>ׁ4A {qyn.,MeqIENDB`vdr-plugin-live-3.1.3/live/themes/orange-blue/img/remotecontrol.jpg000066400000000000000000000440401414414333500253440ustar00rootroot00000000000000JFIFHHC  !"$"$Cz"] !1AQ"aq2#35Bstu$%'47RbrCDc&Ude6SET+!13Aq"2QaB ?8#jV+%䏘+I-p2qIp/nؒM5r^eĀm 9c}͍)")33(b}XX:kr`?_}{MfJ;M58P{#^b3 <[lHgsϠ0}֌4uLZI#@XF&1i{6'zh7㡫[OV⾠߽oj:ʫZ4ǩ{$?2oIt{mtyÿ)V>hueK={!:KFxyo7XXi37?`}ӫu׎edRDcVO@pH#b'^dpNi14'.5mZ$R ?zuT1ȹG 'Y -9I?652zc^9Q$CsURbF 0!̈́p3GJ+*$ R,!>~se7q* I$sBvF,ı۩''@ (M,EOO7vY$ $@@$ؑᑹΝTȰFeQLH78t־c6Bp> xwtk´r!*NJpqԃbVʃUJqwJ 6c*\4I+sKˁNxtW%eC5sMR\FY¢(uH;PtRӪJX)P`yF >k@Vw{e`HIC1:dsJuM,#JDG$i$ie` 4PH:5pK]8g S(iB@. 3p%S 3Ί$U'̏kAlwG(I2 ~ma0N Yє T Z'm&\c:aMtZ˕Ej)JPFv @ D둀o07j:{㢆G=j ' +Hc`pA]((9ݤQ PH! d9Ŭ 'B9$%,?I?5O~5'YT~5'Yz/ECݑouP9Ih/?6 ɽ2%eq'?ůBu)i<8*z3tBP#4Wr>^ޭtWz'B T6N[uvip ^6 šCY$'̝3qr8OӴTȉ)iY  s [p!2!/nLPsqHEExX\KwS `lA"sf([0 U`47`CB'rZZQ_0&3g 4SAQ9ӭ4Gr0mvbUdSc, 8 p2 d DVg&>5F)JI:*eZzv/$pQ&yUSI8''`NqNޔ7gpDYeqM\!ngSUG1:얭[֕ %ƹ.i\TLKxAR!0$0(C>VZJQ8WN9#;WC8?W;8ߊ@> ֓딌< LCAC>ZDPwQ#EPHƙY$dp};Ur U3K#c,I88x7P bGz{DgO:? Ob#Ӫ?}>:{_J+QnO7܏@]7?=?j> /R^_[[# C\+E9"yj6 ;I$:"~-~ "qݎ\ZZkN 4c]>.QBKtoa۲B %UrGP5ɐsH]TS#JYj9pȪ INM]-;M"SΈ 8ԅ#| |;k"9I"u #Ln;-;BOfn:NrH{&Ñ }e{6,%4ׁe-exO7y'w 4DWA H'Cշ^D9USvl3xf-otn")]SQr<]XO!#[ԲH3B@45#ԛD|F@e{tA=<őXp $JJ3Uԟs>'=WiwxixF5U|-MK>HDrEhJ %yRpݾzZ ꖊLQFiA$4 Gjv$Qz!t3+j.2}g*LW(ϿC@ڛo:}Ly{os{2 Ʒ4ȏ5?kߋQPd;w.iytgݯ/R^_\['ᨊZzYij⨧JIOPA|xA-~jm0յejB"(B l2IP79Pʞ8ZU %m* S\b@<, 68 <yG``71ЅR}#'KpΞ& Rc'9 `?:t}KQ,G1ULcEPM5UP6MbQsf4Ԓ##'l]nwKt3,$Lj# H. /h0`Fg8#x@p5y ǟT 5'QWs_^d44)=}mTfHcK@I;^`OEn?4v::V%Afr:㙉8gS5|9|1d[?:kY=\kuI&$0`I2 dO$aU@l GڭΏUJK"9FVD0@>@J(d!AzUyzmUT֫KzdifCJHT*\A2P jx.$]A9c!w7?U\*OUDIUU P X+hLSMsS`@ 6 Mm^U3>dM Zʓl0 ʘga!B 88; aAi1k'75[m@ʂu[~uFՍX5}(zAϸ| u9cCoG S }̫$eձ slC-~/q5M kifb H'`H.'QܻzC1{MOr'WIՕAikQSWS4qD^CXlP \eikzV~=i; VQ':hྔT=]/lA~,ht::uԗB?$m4>1tFP$I5W\'0N(O:i7%B:cCC[l-RΆp=ېTF#i`C"0\ZF:vFsA-uu[m-ªS4)cWB@'"bh*ᦎJ5`2|Na%Oq=ijjwx&#$CPYt` PJBvV H u+q 4o )x77vR*pAAumOUU=52*`"!:9$H 0zVAKIGvU9@ع $jXz4ˤqFr@ 74➲*VhX20#]m7 RICl%5. 1] @u*QI9㈾Y`3αڣ/Q$g=D. Cxеp9})ᐏF50pUfU.25IPvc}W\-@gWU[99GPU%STwbw00ʑƀ%JzUD2]2~&DQz\$$Ji兀$*"e)(I7c=dh\'9'r+$4;p >>4W=W4SRp2䒵ndnN@ Ki\3j)lQG3,Ou ZHbBv$GKU=YbQ@-H0>M" -] {SLZGU1=`'b7Ȱ:E.-pFA؃᠖3D1bco5حi2D٦jEGt3iIP (?CםxhkcŎE2}c֯+)fp_J*c͔iMW݌6;kSnl#p#96WV5qIDJ+={aDS"3 A%RNNc:x% l@5X!Vg&uu1#F ǘ.˯E[fG[䧉ܒ r!H P&8F79zG W 4S HY_xc|Z4JXȑ1ƏҞ* 񬑰VC I+u ?>bө)q7T#<+HaCEQOIN{VToWzU[%#;H=jF٥NZ`K< =٫"o Us(MҠVh'CD_rM':g~2}cוֹx$dT?:hHHc;voß)V5`SnUc_lZ'YYy~v?jx}xJBiS*tF{ߘN}*(^~Z)$yNy .Ŝ=b A\IGG/-bkʞH$}=r%0Z*֐[`Ϋ>>^ NQc%Lcؒ$Rt|Q•U7,I[O-+H|J}'D@ cvu'qHu&F8nj2qIݫidA2wU&%aіÏbH?>XI#QB$p7;L(a*:{A],Hjkն1QWP $3a[.4.Z) XIe]J# 괆r*#9?{=7q7 GX%@6%Q/  dq[GSC>Q}Ϋl}Ot?y/Ll/:6~Y7:iྔP=p.Ic?xStMN9_jVPIJIXJ#?U %<5V;d#SyN\2>\f빯1O~Ӡ*:G3imuoڲuna`Ĝ+i8#V $TONT1Ӧv8Ɓj?}cyo LtXz_ݡ<Ā"r\4ÊH-f*k͔]X4{.xwm?V 9YIy~mli]ʮ }zhY[€I?@OHO5K 8N02@몚]gU-ȗeviQG,Cu(и^kaJ.!,=- VjJ`F G؂1uZ*$ ]ԅXoO ʍQdSR`UN\qI F0 ! m_P ``:F:H %dn +3:;C.!>Wc V1 8m -S<dcERrJ:PIg51[SNiG{8 d{‚Gύ{KZ*yTS޼43r܌i`[P>*gȒ}S QYг;)'ğ N}ux蒂ofbnЕ:wbДE c] ȪH aO~~ 5xVGT8|oizkͺ= Zx!Qǀ$ WȼS5MxJ僙\T$pZS6qԠ47JR Z!$F NRQl2FFAuhL}}\ )YC"$XU Pdrf|5Xۣ3S%M %ȤH08͒D^w%e\Y4U%ey# – "V R|xO':c:?':i/cͼR=پk(touCES}7;V9Nŏqߛ`Z'YIy~m}cvF:Z%r ׈r4`H$zc@_Tz#KT! #pw#D(f MKHPI`) dk+.q+Q8IXP9$f¿spiu5qҸĊ!A+s0N[<bȐS,I':mpZgJzxT(nN0rO@t 퐹>'Rt춻}5hi<:d.֖ZJPMBpVDA dlNRUՃ[-yМsJ؃MUՕ쒰IS|0zl}:8~JUK@ʿ5msI.|ɏQY@qGw5f qEkLDV,z2s@31T{NOqSZtN"ov8:TnKƸIXn" rs֞j*_n8{l]X4.;m?_ŮbyN9CGtsh~'Lhfۦ5_uJ:{-,Ur(HpF\諟 XLpUtKh2DS1>z5 D+hyi6i'$n. c=vğA;a$t~oh'%-zcUogASܸrL6!RDk :oSJPԓ/p'stߡ =ZaWBkbT,eDnK$|=GAz+tɪ]O @9?%=Rw9$b:g umJcǃ<5Zpu1XgVHثfI+ V" N}4g>':Qܔ8F7$r-Wi!RvpGx5m܂15D;#ȚW Jqܒ1 =R-U&%8* $jGT:r|vG\.|ߔ?~ZA^ MyH^+\$;h49o5bAqkS qN"#=6u`\.aǗ]f3u%e~'M:i?')N~ۋxPIId"oձ7>U4ܩe`3s5A Kq=.ڦri b, х쮍h9ʌs8-GjA5UpUO$:2a^RM(3{f JB65-K0kIKU,p'lo]YµjtZ~vD^DrNp_C_]BYsrH8;dd8:“!5kS4Ҟf$ƂwA3 @ ;!RF:t+kULWM0rs/XlədRIOAl #iP?{_.N׏EX 02sSxb-][k=e[1BX3.7 e߉؞[zbT2D{j-3JwjU8B  mN̝1$@7 @jN' ÜW$tbĖQ'? P N[mN: @e  9HdRn,$Y2̤I9GGnr24'-mQTSG} :?۸:%hiǠ9' l_ A;肋 Ȕ-on1W}{с^up\HvĈ#.Oo~>왡m_M5dAV}G߰w8̶LO)dNNTn.v-d90 Ń( WƦ"^Q̒ r.sACC{knPH0T"0$9nlr {%{GI4Tq-U#'o۫I1"0?6 xy# ,GRh2aOCRrB3 q#gE^B (f1ͥNvC]H_LCt5hiesɀ 'cL.Wy*eG yNmVo]Jy Vb*5Ut/U+JG`>`4X|%q_yq|sxb4C 3D})ؕye{i(&zČw7<>,|ONn4S78w?9gmV…[;_㦰ѭ\8:jDi;k//,dDܾZ{ltptȲP=1CzApIʟ1R:ĵlW;2qH2q2a kSCD m_k]c?zen>RIE uz.9/an){qKKR9lzPCmA h4Ɲhvlty5RDR#9Ɛjˆ P|g-s_p#>ni=?\?*f>\?*fq"bͷ~o XPw؉-{|?WV$5ԗcxgN^FU1C߯V[19mQK78fUTdzox{Gj% !_">`cXs媹(A*ܿ,y%Q}+'ߍ#\hXTS,TGayRSC;iG񃷙?w./-j%84G3pDAsBumb+50SS$,GPWB_"u ٯ*9xz9b'2|1CWK;S~р^^΃^U=㸙pWw6ׂ1B?)K|A<Ԑ[$zJX3tdWPv8m@3=ɤ8 %e^-= c*C}8ZrJqQt=qke1[cwI|ʊHIL2Z{h~wm6zGqCo&3FI5-4.aCAؕum0nxU7LMѬF&)16*kKo rnEœg`*k;G41FBddg#=4[@]L2P V؍=EgU| OO^_BiFk0W;E[Z&+9mNˆ6AOcʤYʤY.|/H=q؉f<2?CubS}^&Ue#LGH5ԗc8j9 ᐐ9 _ j{%L<2Ь>̒@R%Wxts=R#U$8YBA$(# @ Gje&1.2B7DH> n)j[-?4'ϫ${(dd*uddyoW:#s*i܆9'lFP*"Z`m9AETwYr/+m.~|8*xB !e0[#ڃdL(O`JsFpLgVfJ0{uĉ։DS"@ȱ!f 2?'89=6ݼ,rcށ`GNxi *RvsG]o(L ?6f6s"Oe%HPq tRg4KgQSD6DUDR8H}|@OA_ pzᨶ۶:?~:mJe:׎?O~nya"~*f7 KYkk +Mv+4AEPSD"*7׎'xZ'QOxjZdaonzdy389`fuőJ!޶fPŏL 4x_8@Cپ$)磥M[i"!8FF^muCpr|;џBB'݂=h&l2?,=YQVSo]t[||>'Uנ܃D| 9_XdLwWJTz{:~٬_(/TFj/@m~ȟ谲ZWU{9T#$V|AX}G e[|?FȫXUPuRFIEB烙od|tMeKK߅0uSX$>PD>  }U'%Ǣ#X۔ OӅ_ Qe#|/KH##//X)cg/떾6q\#2Xi!:Fr yU&SR_Ptq$1-40&vKMSD4[뻲+3e~|ULۡ3˂'I;>z1r]9K-v-ʮ%QJgۯMDu'3r~fgO]ȱ9ˈkaU)ݒ1`NG(ǰg:jž3ccNV(;A7WS]XfRs6@Q«})WҨٚf]vdr-plugin-live-3.1.3/live/themes/orange-blue/img/tv.jpg000066400000000000000000003563641414414333500231200ustar00rootroot00000000000000JFIFHH&DExifII*2:(BHH(%HHJFIFC    $.' ",#(7),01444'9=82<.342C  2!!22222222222222222222222222222222222222222222222222" }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz ?mMC27HFs>e\n|[@}9v\-[>b)bIr3רV/ (tU7P2{v ,C'$+|18yV D'*(kI"6e 8?9z}do $~v$>E]cb2}d"i K,(QB cnOןZ;=:-%3Oz2(p5~à§i*Lj9!8}%;w3ڥ9h'si/= e62@|)ݲH.dk+vz3c>DL67$z&LA'?sGYh2W!O9zv!3X#Fy]ƣ71?9}i<G'HscYiA,U cנ}%pe 'xv]ģRBnqO73$1h<VOOŊI" Z)mroVbFAwrߛ{eYB>Km' ccuD@<9݌"LB'Rr7P ,Hc!k>7[3ĎwL{] ;],%C$NA ך9p5@:C'd)vI:EhVʺ Yf#A\.NrrOUu R)m :NS$玧w]Ƅ|?,k|0>/E1>SG=9?:ȎYIb7 vE9ӜbosqʰyAGw #q!y4 $}$TR=UG,3#nGX/%qK%s+#rql#U{h˹6rvGAA}1G3EJ 3Wp%cDVfc|{m#I%&vĐ:ؐ8=I-ꖱʆ(vUba8sנs>dp k3-P5\>:ފ2vZҹh183WA ,1Mp?vxW y{BEE݂4knppACkOԥ}VIZE @#3H%p[װiդ{(c!A'o#- }\ %BrxHϽKo$q²/VXFyHG9*F>9$i;IYw13*}IYfWۆmP 8hEcIxWpLp5ŵ;|8_p:ὖmHUP>\9>Kb:Ü`yi\d2ogW$'9 .o7#lȞR /~>6kw41l,K$P#<fݵF= xA*7$ ޽AI,=m$SӀ8G~QunKEFٳK3$3RK,qEg.hЅgb11523q6nRCq8?Ge8bHg 0:hVbOpT v+FqZKt`-0C-,`ܜ1UTn{ěV$T/C&m0*d$ ? :B1͜;) HdBI>bx}1Nvi`A<+=NL".ffeGO_yIBg߸؜{c#(̊LI!}| d#OkGm`IJ1$uhսҥ;F!v q֬ZN-i-F98> ר;xvZLd䟨 I%dB?u8ӊn *SaN 9stjI;X!PwHnTwoEiR}G4H)QF3]f$uW%Qe'9=yn)y-qp:Xш^q0F2GN*+kYxam<}9>p5[$6(8vTdk/38l/啞(/9L,;g9=GR{u*Y.mPօD"Vg nu$r88k .O\=sT2\\ܙe7A8z#Wc/!b._amʩm߿N1R<ϛlIٲ+2- o5ԫj!DcVq:Sf,[:8I$dq䌻QAe;RFP&m#p`^NOQǹ{Ak,+O._-ݔ#㎀cb>*ݻm$/psdO;J,~k\v2p:hԿOC?Eo/rqrEʌjb&y*0]\rv9:t >.Sq d ;OM^Tm26,Z.] )lf[m*l]Ӆ}8TPi$,GR'@ dFH# x95^Jl0ՐZC7FOM1R bY'0U< dIRD2F ؑ 9ǭ+WwA\gCN]>; 8,ǯ={(Qh%g`Upqc+8Eo iAXJ;s:جpe;bٕv=n})qk+e`ETg<#r(ql`ǸfknyfF۱v]<׎015x)ڦG-F21'b=j۳4살Y}s(9+Kq5. lG,=ܼ֝P4+>N|琁qJ7ąWs'93n?hf+ʌt 9|`-wL ~GsgX|900.Ax=H2gϑ.0i㑎dTmhvZB<Չ(sJAqng-'.[xC9l.?ƞwxbC6bpO'px9"AՒDhlqCg29`_]yh5Y^mAUVH=;y9HF8rXnx!9O 8'ayahٙ)9,-S|N]sn?Z   @x9OZd3BZ7Ή[8'4_Fr@8c#;{԰(& >RbsNq۪.T3ϸ`H'8l2Z6dbdP*XX`/Q~F#4oEzL`xeHEEH`zZɻ8I$^y:HI"7 NyCjmKUs{gxObZ[}0`";0.s܈FB_c1cc= ?VQmm+6Bq'c#>MQ482-IOzG@KZ)Mz+x($ ϗ8ڵOxyZXҬ. gV~Pb*znY732I)䜝ۑǵe?lAo{iso @#|rB#Nv$y 06m xzEn-$dX`9z) %6F d֤b4DŢR0\$ N lHI`}TuF4:&̭Kd7r plE!n$7\) tMuŽ>q-弞l%T #ې2˂;v{m弝PƊM8ǁcp,;*̐͜<~&okH&1p褪 ufPdʒ\Ł=w$(ΰ[2$ 뜀1Em,nHs_[玣ߊ{naܤuKs.主W@daF2Oh&#,ة[89{Ti)ŕA{Gc3~$ o(LB䏛s*h`s{*V7%,FWc~lq:P.ev^WU+,YNNp<8h]IYhU)*>OO.X|LJHtxKtG_ Tne'aڃK'<~JgP̏ cqϡ֪YO%n4q芡O'H\Q,H!qr3$LQwen1@-3ޯrvm,Psbgd\cWשVqs)E!lgH'U_BAL3;# }(Ӥ|YXVIz =̄#$ۓ-}^K&{ /;GL灎j;Ԅ,_ ~Ͻ6/"8j)N$kyfWʒ-`g=)t%rVUY׏h"%~r9p*fb$R،y=jOԥH̊rp64TB-~p++pr0;ǚ qn>ݲNӎK>N-C[[.z)0݃e,$+q@cTFg.dh xӯZcX&Ř,O|r94RI~#>@ qPdgu`#K$p f`8pր)pf$~^Ǡ<=1K;UܗF7tק81[ =W9_ʥk=JFfxm]˹|2 kf-)m $E@OۧZ-4nV^cXU98O>M JNCyс|xPGmh󇙏0B ~s7yolpDJo=̳$`JğA6;Hga O8]ǧhO<$V?0rcRUH"16w%;Nқfҥ?n$GFK]GSK6$RHAR 3J4A!HA".X+)V00:uBVEaaT':z|Io,0Wd$2UK17zsZi`<6Fp+('9|vdo%NǕ>I.gCqij[,:PsenHrK9 1$8䊾lno HpRv9?ÜTc*nʱD.p?ZLjhZxԿ+x{:+X]d `L?Ƹ+'Q.c /^?@:>KPs˹ 7~G*yw;#7˧:߯qQz_ʃ]g?f?^ާiZѢI Ry$76gx?^dZ.qjZZ5aaH^6S)dЧ gW-Ze "}M;:mFo_j|MO׮KH!~ifpH#shWRP81XQ*q&t?z?f?^M짶_D}6?CXvv=W?LC ?&bS 3~뎢/Bst?z?f덢Tv_|Ag~?\m}^s?gOYlGkTt~1oge7c,xW?EŸ`p+RB(((CLG߃sOKr&`Erv-_PV1u#'Mw#I3XTIs۩5C7ʑz WLȯ9iߘBxyW5yRH,Qg{&el'ںKtԅ>n gs\eγ qOOݢfsqrCO<ܷx;IUR\4Os+@8KNJxTtQ^((((C  !"$"$C`"_ !"1AQ2a#BRq$34CETUbt56dru%&DSceWs/Q1Rq23!4Aa"b ?;}2Y-s t-IB@Vp /sZl|fCHz=KN.k/`/k_e;2a(ڿ@Aץ[Sy)b1׳DLßʏKYK_M Mz7Md^:ytQ39?d{ZO?]kѼ4^:y8ThG$Rףh?@??D&F~3N>`щ'D1)'Hy$ɬ, 3N>^(2]Ot+ғK_PY$שP? 3N>^?P?]qzpj~Y%g/s*4|>;-ewZ{/Qz7oQr;£G?&-eK_NY%;?.\' 1sgO?|~ǜJ5?-.\' 2sCkvAK_M~Z3Mh09xQQֿ?,X/ZcџmOFɆ\'3tI_zQotޯ=}, clQg/s:4|?WowGӽ2LwFcr{W_z?c.&˦r;ãG7oF?/Z?׊d霼up^no=ׯ_:|d霼up;ss_sZcs9;AC2uҳ3N>z?ǯ/>3zڹ-}0`g/s:4pѿkmc=@WUK]d7Ù!:g/s:4|{-} 7ҿӬVd霼upjkn@ jX'ש'9xѤ>r/׿ޏ{-}7:9xѣnGPcz_H~[Q?t^:y8thczhOo^U/qL׿.\'5pOW$hk1Q{ 3N>j-pW-x#XͿxSџm3NC_vH?\?)WW?-&iup'<9-yύZakV?u?,3N>bg?wZ|HKr;£GCKwZ %yF%7(9xQL޻-y[2J5Ir;£G˟֐{ZD#ɟǿANM3N>[H1k1#F?ڽ-}Jw?AU9xQih֍?^="'5g/s*4|2#ןA?Vڻ=}ceGG?#^;11g/s*4|~#Җ~,CkPl(G{~-w[X## { |q}en4R|%jWMs?* %6uSChͣ]{ctگcyn3\ܦKTvˮRۄ׻i~mn7 J{B~ ֪V׮M{>-?,ex”)hJ]Ojlxo~C&=|-HK?^N':rۏ,G@aOm}c6ЂЗfu#ޘܲިЬa |B!e?;YXUIaHc-rv뒸w9zƭ{reYBT5%Gi*>JgUۖݧLj)iWΐ "8D5c:ԏ7nrk-G[uumPq*N;J3wEQ_.P\%*  `{ƃ^޻#Сp3!Y 8} ORGGWśnZӌSő%3%&8_,:$wGgآ-""=M wՓ g[mJOcB7LXLğڕ S,*c>_#!JǶ1?Z Iu6H.Ro d(<8h}U]\*ǜ| Sb.RpfoMw.*<*ֶ0.A s@v3TQ}h8ʖ'u~$;:Kh*p8 \^6mtV]d<\dEE $} nְ\me˧g02qk8ιE9a%ۮFj!E^TR+q`utoCDȔԊ>3\G^Z _|Cv 9ʉpp}-GQP{KܮEl ѤFg!6#@w<܊6X',Ud>~|-Ɍ̃(JN 9bn!kSsyOnao&j[5Ieм $VU:_53+U;R_YLMl#xo()Ph:nW4ِR܆cvP!=Nqseqً-Tc=r:3nfs,?)!n5-EtqhT 4uIDFeOGR0Qx:TJEV)>2q2WU+S4jo7 Ғ󛰒$z189ΏOܕ3W y)Mԫ}:: ULz:2KGP% Xq^7%ɨ!;mv *Rl6ӌ+v:B Bw7 F55-%yrs F2@)c>gFW)~w2\1_P|BR=VNpmSߙ*RJϧ/A|F;MMuspǍ.hmHHPl\ ?ߗЌRpo) &!Bd:O z {V~NmP#iPW": Qp->RIƉ=guzIyS0XRqrG@}|DmI/YR: Z_KN){.R@!AJkN.ŦJӒa)18$@~Vx|F`"lK $A6rN4 7LM ^<9*rw'?+m +wޚ|;qQ:E۸3ή)o>$ (\?TK_KDqVsAH{+e PuI-.mK'sʘaiXqSdJ{UJj9X nJ{U({zz{#~7s"bfar[BB=Ϋy˾E 啥MVr9(r!K5tXLXX\d렧lHC!=[J̨NoJIRɬ(zՠF%l6'JQQ6uERCieu^tC>y4_krYmQFRqPIs P>I d}p؇+ 4;Gҳ!\А?nx{ՙ{ 'X Բ_2]yGBAac7}@Nn|#Zz*V;L6(lۮ1G R>?2q[k|G߻Wūɐzp*y$ {?SAR2RÈ" YWJ,:ZiQ0x%#8׏ml4\fȘ! g@8 !M,7כ/ =ElF8(A> zh-D7I諍&_|>e'|N):( *:^ٷEU˕yOT˵[>[%$zU[@Sɽqk4s;}uQ";"Ͷ֔>Oh1Χ!)z[}y᭰.|RR렯6."lJ/[r+H&HXYN;q*g<ZR'l8Srta(*nJݨ2[˅1pr< \J(,{$:%2 %qI_yq~DxX'D6ۜqOY'xH?S{#vo&4y?:M".Zwſ 9,7[FbNɵ㓣 s̴iֻݺf}u[eW)2XDZxT0;%&aPAZKV/+HK,-Yq6ȭRȎ`|&SGwm̲([JQOQ'yB"M.\JUB%.֖hPc A{,^|E4"ǰܒ|9|G϶o|J- |9f[HBVdd>x8_mXIU++D_\e>QJ!]d8m:˒m=H sǡA?}K %7縦RH“q sz=! RW꤅:=4j;Nu.Vlq!ȸZ)IRҔ}mUT6W&̖NI~dq)ͥaSBeIF3!%``R1~/IV)XFn7Ԭ)OՏvsY_@ٛfNf 8j$w4ǺvwBTzNi-(]I_Czt=4~c}}o(i[iYw 3ߑ6 #t4͘wR8 DG|Jnޗhck/qrLf /o՞GZSݣ5驩++m HyGPp+# =gs{ĈFCbIu@T1g3~՞1W`g8KJ?M!8۩2i~`7*=̗֘n!> DӵVrrRlCD[BьpT R*Zm&9_[)a=,)B ÑTjW[Cf^D*!GVr#mJJX?wB*UPDQCl +jtc¬t@YI2weGcy=1Tt3g8iJ%)TPh'0mN<۫Q[p53#,4+̧}0ʚzSQ)B)V dx4;.ի}-CzړBڊI?p?1DL2 XP>'ʀ ֗5P>TUJdW.0e%P, pC~Ѷ ShQa"ڊҮKhw}m fGjaQjV`Sh C-avߑ,BPڼq #]mhZ=R=!ץ׳cHR'Q ieD肣LS5{S=wK5>C9#]*i6KrAWQ8 6ⱎI:ZV)rR,")nvpCgCJ)gr:읡2)l\*TRzG0|Lg+6\xc4 ].vԶ@>%fUa'ދ&bk JHJfPSI=Oc@f-},f\_ Imǔ (`S=qsa7t*NsrԖRAKZ@cJ>g2*p Unrm2_sxw>]حmue_mtRԨ(ݖ4 )t&"-<=߷ϱ,RPwHKg 5Z.uے Rf$ϥ ҅ pBU郍Y[rIVq'@k1uN6N 9t `kz)COGxHKCq -W@U7)ݼ۔TQfc}bKmJ- BhZU.>}n%oTR' 8ZUڥnS}RxAIsNtlnJ)t\m-9߉Ԗ:!<œ?\eaWMq_;p!)+uu[[xmܰ(HF1&*Utdƛ,ž(:\oB3ֱkGG-ɆЀ cG ⓻{+cN.pyuj@"o2Гd"Q=˿3#IOX@yOqF$ֿY aM[at-GʤiE{-]fiL`NUȜj@'j,;nEДX 8S;m2aDZ|ɲmFʦߒT;A.q >;/ WT4h00)/)$x^,*bC9rYunM@%%kY)^L3kbBvެa2]i)'#Вs1P:BF]3794BQnq#Rmݽa|6KV-~BI'i,[rcѦB^K'IduWţ"TL?*6YJej.̰(gx:thԻ+q%ܳm`3͐3ߨX sΙ.7p]3NE\"攅GN@17qmʋK}ղ,*$BZPpU$vg`Ψ6L}]Dgua@NCd< wZ{hmz窦Y70˖ (4G` "U+1&\U5&4} [~l ۹.:F IkB)m`iA%'=q;}RiXM*[ﺃTJAIӷE&&j\Iֲqr%MC*rl HdF%Ku7.n;aGH+9#99)h0cO)Kq,9H/$x{> \׌S*"[˦[KWx6F;9{;Y)}L7%\8Z9;8՚]urn !":>DC.ו\S须r|LnE>FhTT TiKG@A}4I'oP¶e2p%1$%I>MۯG ,KܖT' h,}tya)Rl;mHrCjISdA<I9כ I{u^&T@qxy[+y}:; f5庸+5 `y{)I=wމsP>rֵLY|5a*Ï*p58YWzДUݸf+0!NIQس)a,#hۥ)}\~Ҥ_:ydRtt~}PTTrBsJ(bN&!5uҜY*l V:B/ɫMtϵYLزCy+P=UGXL{IkS[r:=pq@ݳ"Kq;b弡 C}!jQ9j D kjQ4)cUǵ盂P= R^ %#Xm[irX){ySd_<|J2Doj0ˋL:$ [*v"DgBI'-1Q*2C q?X?ҵط*ٝqsNGJլt* R3uԶ:SNn8ZG>#URۃk+ c9 댐pN}eL۸eHy*)zKOlev#/)-Ώ>nBUS5cݛ'҂Md]֭حciUepx<8 yRxMu}i~!,ߌ{感 |4COC&$L4oSyqjŞw:KC¼Fp8A'>~Jg6ZVbp).3@h)){@HP v-+_}>3>Ruݕl7F-oDR^=%T0Vr3>&X|cd.;>xδ[ F-s L!ԇ-"9 @8IMV޴p,O 5)Ε*}m+I9A!Ĥ\Io QCnqd I\ ^1ڑ 9r ԧPߛcQv㵕-)gp$y 6ԽXo$ZTDgO1G.7ECpIIvbPyETWߦO/K 8`]H6w I۶koh1My3DVЋk5_%r:?0R"n][6hS| xʒRA-74ŵfD_1n)e`a(}λU3gTZA>ema~$ }tm}mH (.p롷ZҎU 6L͐OOVsU$8WaK+MփZD*aAI}Aypj\ [%G2k%@t2}4φBkr6ԕGDl#:${}{hP[~, x{p HJ|Lj:'.K[{'ҧc<+2}gےp$Rn)hQaie崆T9p9Ϸl3| qRO@Àe?t6Io5ɭ[_v#eAXۍoߡb\1ojDhޱ5Jak#-,z)_;m? #\FGqH',;kʕ.װJ3ݔÎW`nUS' 嶠OmSq}mL.+"Z񐔾uMSs=|Isj-.goȰufF x;IuקÇm}eeL&iio/S}yGs|L۳7:[ҊǴ$kzFvVn^{wZ>RO rG'* t Q7b 06nI]u8#)K0GE{mXQm0eA!egyC$w@I?3z"UۭZy%T3^m-['EFy*A)A)quaY֮kRۭ2(m; NFp0tfm QPm[$(>hW<~OPg9=z{hlU3q(9Q*]{얾B`BJnmb󅆐0 +T2YNWj0cٗ&)iZP)mADr$rt'z]ߚze,%Hx Rw+__dʖ]c!,rr$(n*{8\x(sǛ.,,RIQcF}'PqNi 7p0c1)mjR[[`JRM[;mV䍡T2!ĦR!6- _C]zWplzϹT6J%} ѯu~][~bÍ\lBʺ_p*NNO3neM\sEYJ.- JTZ7Vn&Rbm > B}oK.O U5s vvR zN\LWUS')QcAe z\8;ɗ"E\%?Y)pq%?,v@}  k>*)灓JGF/_AjÝ͸.6$2RI!թ{JM|%*i ΊLpwI~?GۣP,lzHM%%*Q=ޝ叇nb̔dR$HqHprx:EޯX̶vj*"ϴBU<Oc$']F#|o3>edJ*NF=A= ;kY{qP\K >YZ%Hl8PV.G7vCEm&YwR|)s9RO`hSVLٳn\+°gZX$up9 m4AKp˝=Ѕ@-E:8OAvtR+_KݨqJ@@)H[)@~&IڂcPZ숡Q_*(Zzm$}´뵓&>nK|FPv@=!o${-+&_H͟c ǖG;~ 5Q]nk[MrSv6JTqd+wR.#QRCh(A)$#Շ]˒ْSHR`j7TU4JeU8ʧXS+HyB_ooMa&ө8ݜl;6+)i]$&E[QKS"L0NE|`t;Nj,6.n\[)n6[Y8o,Le_T^նKjn>D!Zm#h ub"ܝñSҵwJ}1MԤ4ZOxRKNFu>Ip45vZ|G _[a-OkP[HJ)E$o\zEN뮉_EDweG*tǏm+Ba)?dJWD$&2>9p #-`xt#oNհmͮZ. 0Z&RSUȼ'|=6Bk\ŜLRz둦:6[Z*|0#KK}c+=\twu S֗Gd9%>Ek7n#,6òUbB_zS& \[ZUb+..8#I: ,CJ [(R{ImnaCIJ듍ey|Uy\JL+'Vv9Q[d.p$!0O}z+jΝoed vhYvIکk{^R!N8[iN8R >Vme q4$m9Had'*Nqze^K5Bxi( (U%MVw-muuMݺP)HZ\hke`¿qn%Mndg% Y@)^tۯrMgKy;>K rZFJR\in鵰X)kH0?NR ?粋V5G%Nh4G|Íg]>[텪fLepu2 zvI'~qDR F爉mK?x%M} }}s"%]Z- ̐s8Q8>V3w;_ pSJ3fʿ8B @g?B3ֽzm+qXɌ.<~Y[Q: ~!=%1U2B[WЅeH[Og#w t)fI% .*R2qd!#+H1nYǓ=tV$9x! 8[Sf9񜲙v6D\RJH+.eYO/Ylۺ4+6k) m3 ^Yi Q˿7~,e"6romߗa]8R9!.~)FOy;SrB49.e|@-t}HgBk]$VZ$ǭ @xH8+zjֻlNwVX] _AHJ%_]"mlh|f~S%$+xڲxǬˆjLJ`<人`y$,N=`^WQ`a2qHkZV   ~lcEvk=Ȓ8m@Rw-rJps5ۚ#6wUT@ۑ$*fXC@Id}aLI m*pq9 x֖7uj*-Ӑ# o86,`>9'`+Z]mͷykR\hj }On[Fn-;{zJ&Z҈a8 tGqݎD*ZiךaFH[$/'Xn}}r|+! S$dBP>)im40ۮFڒk*PR?WП]^ :RمT[mih; K-c'AX"dž5ڥOr<>*Rr=qkMY:o[0٧T @Dqcwյ])vP%ugM24gn-÷_LM)沠!.: n=d~b#(vc#|z/%gKky;Zʮ\8WV+ᡉ6JZS>d pn+QVyRJY!Hm@}§`;:8r"ߒT1ˏE#ީ9o™{9a3N! )O?9 1-yELHC%}TPK뤭YnkC^6 3=+9yQ}\G|$9)(-P4Rqsܷ/ř=TV8-3R>YoKAD-\W"I EP j PM e1u2hpGpc%yIIw|{aqpPs*CSimI=͡'˥u NͨT%e#Sᠥ$ʔg~nI,"[;jh:RZRVZ~kfeUX.S)m .4V=G骳%Pe:dDaql|!BJP[6jTqa]hi1䦝>+! EJc * ΃vM~yyԒJ14?زݏeh TRS.8@WW7CS1V%cđ aiT''T>PnM\ωUa"UZ%8+t2-H-F RN*$fÉ>6`7aʕ&Z$y˞#&)틸q,6hK)uP$8HZZSѱx"4g&R+IP]d *3m|cOMqǰrVOU#zignWsh!y\'*e#Dr2HhS~B?( 1*?i)3V0x6qJpr!Tq<8-i}m9O}-a^Y0'A~*XZ|ֵ]JB{JJ=4E_c ~u`.Dx́JTiR; ie۲9l\*K0WI iP^b<=.~юgOZi!+P Pelz`ݐkQ,=vTQʞ#+Q'64E5~fl"=O]pFz}-q&1j)YmBivVn׍7j"Een[o;PZ8^*[]F. ZcHyP,%|=MƹrlKV̴F?I %> -Vnij  ")e. 8)P{ܺgN݄Qv?#BZ!JR{p?-;pm(a!%8 8 u.,Qˉǡ@[r"`R\qa:ܧpQJ㟢OGg2n¬\>~ %j%T1߾mԖ2u3Ew5Ue.䥎֝9.ɝg[.־EVJ+nK J}=h(~%7Ol7_ Qi߆̆pG@e\7JlaQBWmJc$( C_o7;|okGY.*qI{XWh۾r r_FB|e.}B@Ai;-sǑ5nV+2%\cVp1=&Cѱy>SIugH K(m^oT֓MZan'Ǎ5@nS+fCY>"RѺY'\;qFRg<ʈjVVtŒ.p7["KL$a4[Wd`痮u,fnk'RJy퐁|g 8:[qMJ ADF@'yj]Kя/侠skiD'6gI 3&\˃r ,o*B[JN{'LPvȏUwX O)o:HZ'9uַ97#Tq"G&U mqPSh)I'Ucnu$J Pn)jJNI]i( 㠵15r5 X%[ܬI>V٦x ~Crx4yWnjJ֔٧%=^}<&սC im<JB>*Js3 _ȟ)BjHu CiR'x@9Q΄YP`aNԐ7/`2NFN=ݴ)gH(n6*d-$wXAqk#ОUֿK1",)l"q+2Xm)旮Ǭu/ XWYz0UB̗CD9hkB9ۭ>#URۅf؃H9n|$5IVO =\X|:<$ʖ1['8; yt9;h86c.䅅S󕌡#'o\ _:oWVD5mVpHm-H=d^Aw4le3hgxmE Iʶj*5z;m!(䤡U9 4TBkXmۄ} QvyۻLIJ:ٔ% pP@z[oIn˪nl\U| S8Oz5W4FEqYh-I ^r{`vdy)sܤ|6( O?^+==7gU=q⩂4D!E:KFbLΠs I--嫉WOIq6v{R" htJ9Q9Jg=6>bp|ϯ50!qQ̨y\]ӏ\ǎ4K\p斔IE&X\B6ڙ` Zoz)y9():]wY55\.r -r x(,bѸ#Hqb^#ďĩ%CM8)9Vp>:i3i<3\QeY%m磓$7USb u t Gc8dtТMHl\ϵq*nB;=e\>٪G=})I%+V];rl\JV8duL)Y萐;]Bż9bRJա/ [oMMqwet{D6E# )A$}(Fr3V}qX g-ZZupRN>]gɇ##15RJBVJ=Fq}[+;+m: ½d}s}~0`HF\]wHͩݻL0iuWk%hgk.=uR[۵YߺKZ4Z[4nĿpX#tyj8HT 0Oٲ)'-22;R\PYP ײ$n uv\?Ƶ܌lq9T,3f}5"̂!2LmIϐi7^ci_ʐR|RMӷ.9(PqR eO}t&+7@pHr^+$~YG`*wwl-3""GDuιR\WbdNj"7."lHKUs! P8cJ݌|D@˸K@_K#=s:ҎڑؿUn01rf2OJiE' 8#AInME>M/:|)$6 =qj jgDݸ:bKz;{ZJPA 'qw4hm* X)5NU˵acK>X ~ݳ^MuLgk}PSBB@ @O1;P%,n49 gw hsFm"Ƹj4 /l[ZPIW'l)4CCm 9Y QkG'ꙑuikR ʊuMuGf| dha ⠞g[R@v=zېI0KP\ (Oƭ*ʉldˎTBBJ%jy po\pot̥UF~ҵlȡ8CyH$PO?Tڧm,f6\B⊱-MzGD56&z'̉SOKINW #D+s\L0[$Nl%(Gd+}}2J5ºq,l O'O[)rR_v3UK>>@΀mnͭNLO}jKY@ZGӆ骕 p6f#FT>G# WFglÓ!(ie9wmJ!m6Gx $gTFD5ɏb]5.0V2 yZG]zq]ݔcNthBxcGP8תJFJ cb9l7-еdrRYQ%|H8p3JùuI[mɭ?e->^j +$Ɔdo["\̲RN>ooN΃V>B!WXuL%DB 8}jٕ ":&RlNJzP 9}]"ʦ+h# u 2rRr/FP;.滈ejyv@c qh.)'Rܕ朽5NZ{niFii8Q\''jia4{6C1~,38c>R f uO.oǕfH j?%N'raey6\PC!)[)P(-8(cc]ኝn'Okn"Dy{#75{IU8Dr0968Q4޲QAW1 A}Б6vݪMk7e*nTqW`#$9e+ۏD ob+22R P5.GƚbC'>: #!~e8=@{hk%J/F Оl&Ja 8;-J|0>S!J9׽V5Se Ka=փSO8N?h]ଥS[ ¼Dke\OY灏}Ԋ4V4=")Պ}4#ʸRgҎf=zW;ǘZbL{/qՓ+Ο0U})%3S}>3"]\指;A %9Vٷ~O~J\R$`}7zڰxp ݏ%ǻ PJ` x",Wi)Tf$g* JO34df>%l2[;@ =S>TͶ.̑O'{h.DwĆֲ˿D%*XO,[LkFb圗]HG€3bʪ`VX0qD3?+*ʜE.)8"Sp8J)r;^ܟ߻\gHez;Gez\{?']j? |6g֟)c~كe"Dxڶ!92*xE\hڔ!\CuBc'uQJX$iX JSҝJg@bŁ7Zc-o)YBy~տڍ"WXLB]BMwWIF:eCw\tٍܲlHhi\Vr蒱4jvPS,`xeؕΩp#R/}4/mcr^z뒝en IJ8]vܕ(ul`7׊H$%!JQJԧBkLyP݉k)7R$RaM9CCc=vxۥ]m*[)xT%yAଂ57L^BCE5~" )֤y)ӄ ֮ܭÉLY`y#0n= 'uϩąHij\Kk`9WIs)i]$`:Ln$ٻXےPc[E G*};ui TɣK +TZF3<ԣ?wgaNmjE $>+Dח ^VR>P) Tp4Lkx!z|%8zz֦y(U2}Wi.6,䖜[D}nb ;*[%pUeVr*_4zM7@SQ]i+iRT,:@D9RL5[nlh Dӌp~mm[8N>:ߙ lA'@?kɂuϳ `4BXpp `vN $.Fa!msCm.@zw=DKTΩ3 hKG.%#Vۖ<.K[.pm='߯M0bƒO*$%^CbJ+'Tc؜֊.歯j^BrQWn)u)*GWnmLoXʐ<{ (- :vu8^(VM @iH'/zVX͍ 6kP_x# %"?W)9%/7za(% ?L˽UQҠɷ= ]d8B662޲}å_G"t*s9N9RRYˀ N;t-*liѝm<+?ż}TƇQm%O~\]l"SKHYm/%ذi'%Ih{%5O3'HH[Hߦn7 +Xv? 5I%, u)Y>d5=rQCF.b=[M!]Aq4K8=NH89'Qv)JD6c3᧴@eg>r]O6ffD%1m*Z@ƒn8XNsAkYDl](>"cᢐΈ[7& QoZ[PgA?Iq* C\O*ʞ3P]t+mG7r$vmGnAENNr$ Z{e>k.mf73M%eZTG: gQ}G#wT2KkŸGͰJ {YΘ]1ɭ fG˄Z|b{2vdGLѻ؄!֙yiH ,uxzjc rq s.))mEGp@u{ðXLY ed )$m5~Di-Srn0 B8% L7m} oYszK [ZJ}rSk>Qqќ1:o\"s\@Zd\ *8*m R6؀6p%]p>GX)϶p++ 5e׍sbDŽ1OOq ۾fu)BR$K<]':%YEd%*o>_ )YzǶnZ+/=sE)بR j_mrc)AV6">1Xrq̝kJj[*B=Rd7ĤT8@U;8KNJ Nu|:o.SJiUe*bSK:P8h}8I `mh؀%.\eEt7# `~`) ʝN۳BE DdJZ)RTzz)ObEsPP(hOWKYFA'gF׺l-]".Ju$ZIH ~Vm]m6(r 9@$s\PjʼGxMbIڋrr1`,w$mjHIi[!?ui#Vk8JGÏqŅ#БRDe^ĒqUORvݱ:ȖTyq JGg~^ʸ6l=uk}\R@95a(qc%=j6ݛWϒS(LhVy6Syufǀ`CckK<ؕZa<@|8U'Ljd/[{J[ 'ΌZKͅҐ`)ҦE g #8i6~[BkN뮥zDWRK)rR 3[2Ԙ2~-K ?b[ $֥ތگ__J_YgV#J!E4ZRy*IImAE,#88=k\yˣDLiA9y$!RG`$~ho9T|_rL)HoTp]Ns.dG:9hFqkG(˪hԔc iY21vύ@1$W#a !xOOUol̨2 jؒZyKA_ŕ+c3%Qde = Z@q RMȯA-vÞ=W2IuJY͐05t':-[-QLd eܒA̪#^R\nSq1-\vg$y}8ԃb s!!!,$# 'DI=EJuSMxQ=u! @[vD{~!mFc=2Zq,2|dv_ޮWʓ ͮHvJ*:Ke#YV~@dfdԵ>'N?1% ڒN8]t RןtsnR(l@mKr }аs{L8CN~mc--I ^>EUŽ4g{&8f:}%9Šd$9o^hn]cG)^;l1ᴷo+0.#N˱-.S R21$_f:֞Q2+a ' #8TGJM"u D%Uj y+l+ǩ(QZA> 7[4CiHwsʙāQ]nX*rYO1)!ĠrB<2 =6YW*Tuf [G#)9)p6y߾ɿl}sd% 4* ŵhrCx`(cP|óͳDMZA)982=-\V[av`BҒ* x\>bG9U6։12l>ua&ŞTGg'd;k{R'5qP<$YRUnY7 }LXk 9~m S\u1ՉK}ajO7LHT~}05Х1*\Ȱn]іJ<`U3!$`c>Pq)VҞelk,[mR\aAť؜ҔH)gOss'ezۇ\g'^{r!L44@h;?3COdp~j/گҖ-m[n.ز!uioÒ(s)GH~n[s͕hKoT orO$=5e^ک 3/ PQ;Wߗ[BR ! q)u}sԇ!^6%,q!Q>6D.)ZuT<E0d2>oݰF\xRb;/iaBF~Lb:%5$yiaiRIRN~"ٛ7{:vݰc8(B4ëIPJE*6X&]&Ɗ8+Gdg8֭u4ĦJz<*%6̀$OR>,iV{4[L`]sPV # 024;t1lZJ#aR_ZRZ!Jp 7wOyL7SK`QIPlBےzEЛ^S42AKg |TKmwK62rFH+?cV(g3 yeN)@*R]uĠu4^uԈŊzm\) qy?yJJ>+oͶԾ"S S8Ҍ-ǩЪ]jf6涬5ȒYHk#Np ?T7BPڽ2K2eM <Ny-Y=l~[0ݚsW-E*Tt}gӳrnjAGYA)'2r\}OyĨطAKg6g^A5˰h ?qe$DBVU/@ʒq[H}Zd"/33U%N+$Gk} uj2s>@z:D'C?{#|%E$+8_#tu61֍El%o' R'* DX3#:q<-%G#RǧxѭRu7 %Nf9WT?E!F'pK) )^(q!H%3{n-fݰ#r܉ IRIBPJ3>!xșq"/;$7 d۱RsD-e5Ȓ mY< o5r:OVqDX1Q+UÙlAo:DgCf#Xe8r Gg:+}O-%[9qaHPWI$c4Y ZX|x hJr{xIG"\10>)D~$dmE1~KMsI\OJYO{^I,n2bls_dk(=ྊA#H :ne$Ϳ]Jfk7O6x0 |a9'Rv(E|ժ#ELNxV]ڒATzԽ>_QUgLnkgڮ>d8mM`gʄ$zc)3܌ԀаKr@))Pz w3sQ-,.0JCe¾K8|D¦7, qi9*R@ %KB{'XZn-!,C]ʹxE Tʄ^*Ӗ34bE LʗR̵e  _JlyTA a6;f-[;TR /\x7^ϸWDɿZTI,r(_g,YMW=*ǡKU/"9GZ^߻WhVwFi~XL…bAJ_HZWsm.YX,6Obc)Z>$-G۽laI*pɯa%'%a’A[N$(}RSh&eۙuʧBKŲROqsnƯ̶}TQg ƀ WM{jq%LPԢbP'ǥR>Ӂ:mJ[D.yGZ=O5rJUꮐIcn DVAO"M1ڴy) 1 yz mO%$? ۦt!|+$8]zlzͤqG2͏/t_Wgi+ UL"G"$GypIcF#t0`R&ld sƔш?8ʁ'\ ,zma~sqE }z97 Rnklf W2J_-fcV>mtv晉mU-j r$bU8lw9# dl4ۑh>!%jMc|k) R$6v=͆ v[Qpdn6Y؂F v"˛w$x>( I rS*ݙ 4,K_b\Bj&q[+1$dɾرb)HrJILj1Ƿ z9j&|=62rj`3*K6d)X`TmԆ稨eMbTFV]ǰuc4gg)Rj2| x)tpVZ7?`䩘IJ|+BR}~#ic׽Woqc9)rNj'Pd$c)lq,2#pK>RR\Cn9>'"6UT2cZԅ"(W Ϯďcv{tXMiLb e+)A{oUEfainŏRC\D'Tms_[qL-Z Hj4:2BPWm*S.=F߹mBSV̸Г)KJuVsǧΚ]c^ *"JE-{/l'C6T֊!%jI txWFGS0fUW}R4i*;QzB{҈B<ݢPwtt,Duq!O8Yq'!`/@8QGRw"(QSkHH'$i ?vy o'x0m WRR0A<:#l{EirJbۋRQIj>Sgƾ2 hf%JSo-6AӗxdCvctyRȆki#Crp$uroimʆ6MOI`,:Q-cu ]< a*6?",7[m`2/MF~/M֭0bVM[+ JЂ24wqP7lT8{uIY:JřR a=vS&Y))b_g]Q*B'4 H]NCg& qx &cPx%*[* .kS%ٓ(>AW]<46,jq e3O\u IPy:֧"|YnC :ClS֖֘w=mM1kr6MױBd-Y?:F=5{]2$n6Udꞧ%-+]3vIcԅc4Hq_(۲{gVι򭒆^8ms-kI*=)[ 4ֽo*ߎséRA@h +qZ>u4{VCՓhS*&+żLUK(X0LBM.H;v#aGK[¾z㲩LMmy]`g!%:ZR#F:UuXuil;5+^xR[ ggT} sڬs,]j䙊HQCmE2 {&vlg]Pϓ,}$7භGGdRp}rߏycMy@y!m$)VUDdlyu5$F ?c#YQVսymZSB\^C%D: VO 0zぺuFNM5 wi6ַ}lO?@+= v-puX݌R휸)GY;)R$yS;%S"fZ),?=ũ -'$ Vn ׊_\ _@[@@:C^DUԭ2%C|8J=`rWxmVOU*\K X! /TOtwy\X7$niD%-IOXRI!9p jtkK۱2p>UK?xNN4K6lؿqcSE5sW!nç#$=}kw fmSuQ$WBC'9B_Q{N-t[^$\dCŏa#NS}^jwU!32PEñnRA m(B {K\=z鵩64-c1QmOL44>ZJqWہK*J?[ ބuS !E6g%dBF;#spXmBK$$L *EsvCuoOy I@[Ec%}u {Zeڔ'[r=F>s}7F~Oyd:Yn뛆h)+o83뢗7$QțEe5|:q_V% *'jegOܳ\5fy,"Zd)|ڔLw桴"DpIn,8~H%]{/n{M64[v TSptALxXՍ׿#5Lqow?+;jeDveP'R}4V`Y1sl#(ٺ)%+ q}ǡZ޿ۂ|fIuGn7z.~aWKZͼۣz:ӶA[I*q8B: 5M۷3ns` K`zEm K~!RJ@Oܑ3s2vߺ:3 BrcYHSrȜ[ibl\G} %B[~bҐ<Zg9[ap+{Y1i+, IyRx@g[~9].Z0卝avҖN>@o)P#AױY{f.@ײ:ӉZF;r=h-\jb>@ +ʙ@VI Ɖ)Z&CQY.?N9c?]5"@vl˙Qb J$cAN&㻧6p*;+B̮cG)vHPp{~ycWaASuۛ܌嘘G4޵r'g7 )Z&B@u`}YmKfTz(,g r ܤ_t!LTJu^# ھi:~:D^潖ݴB_pˬ-! wL1nْp\])8=ubjιPošWO.&At99΃U} --"d#(\dyH#\†v䲟Ximĩ,vB^RڂJ>aMÃ_\ǎ; 6y)t@X5{swm?u*˒1) p=0T׾b^ۑ`Em-vHTVjgM]me\ٌ1[6PJ`@uAH;v+&o4Ifj>ǔm'd# oq\Ë =kKrc ^k6 d! gƂՓ\MU*hT6;9>XnکolY '>(Q!N%:9ÊW!-XB㸡Ze>RO,md ]/Pzk)kp$uԖ;emMkTf6D}ZRYZ@'uɋWղF)r8{1UU))$u'M ݍS,fiN(.{P$;`cTn8^ыp[һ˅ g]'#%.]~ WQZ㮲qG]^*>'2} I86TK».cbM\KIq-Y]䤃qO:`S፦e;8dj['9{Vwj*Cʆ[Y ydKY _}_7UZ3c_8*#HC쥡 <ҽT PS.wQ!kCNdMJ$A&y)rR**}1h[n,`<ć,XԘ쀔rTc%C9 ΄EFV.S_ Hk$Rʓ9'ԑ:>α_4Y4Uqd!Mg8 GmlFל\wsvO!-GELK"SČ߶ͦM}Zm))ˤ:ͥKiDztMĮٻkrQ!S(ko>n-}O@۠tte-ҘQ(%e*AS {Nߪ7?d%ُ_O:o_D4 <MgR ڝP;ID3jzUM#.΄)XFe' $uh:,}lս:ibK>˞o |uo2DY(fEĆ@[R2p!@+k6YFlZ|ixIܕtW1ۍ& +i C yKM<@NI)J@7^AV]=م`%ž%a]c!HnD[eu ‚H(?֖S@{9Ɨ6֔췥mY 6덗R|,$3shّ\|ZDpL׵5װ))}`7bi!{0֕ҵ*B铝iQmjZyX-K#-*@O1ޟ9i}U6S !j8i`x?OI:UJݗ*vۯjCd(tlrʲ}4E.ZUm$VEv{"I-(,+_Di}ıh½ihJa}J\@R3OINې_TXOTHle1Y#L(oO oin\#=I'=(Z\@2 }ŵV! ! b0z懶Ź#m۸쯝J0m9 _E\rnݯ-J1!*uF ~9ynηn_"P8.ºV>\%8eyq'Ϩ-Yn G{p(r,gJx$cy;ʈ'4(/llY"RV;n!))8>w)VSǕEc)N<犷 JOLd$d?΀ǤBdϊk S톂'p$}Ӄ}3TWj.Y8e7$J<T g56qvZf9,1=,4$ԗJO\PRsW6klloÕJ+K0"*x H ~/\gaո(%-.6\K R{۠Jͼ୭UmdZe4RpBT3G@m6v,mJv\KnQ'hKӟ62}tjvmù'5Q& 9"2FNBF=Zpm *iU5Euz7"!!|TIITvbbDU&C { H>d4((Ǚ\R%w&E\aDOJV`:V؁Sslm}ƉC 4aJʊO$ǹ E~$;+k IyIXap%P} ~en?Cs['pW= ]Y;s"CDw<= J}ZE3*¢^]TiE}EG#Hk*=J>h;FU DiL6;yF}4 srɐ0b]}.jPaA8 o{ƪ:'3/֙᫓p,@HmO2D-⬹epT`yd-)l98~WxVkSy!FDq⤹sPoʾrzc@? wW50MܱR/í)qIV mr諵" Z,2|.*Ҡ2%|G˼D ̲ZmMT&)i'>>ş T>Ej 3rp!Łz'j&s!%lÇRxeҠ{'DChwMÉd3 *cRT3 H|i~&$yՕ%l.J#;)ֻ GŢ WCڍ7 dU:G*K cX7>OBaJfZT() S>U9ѹ\q2Lq$8O eT_Fil yHC^!m2}8{ihmS>8& g#Ă~:y 3dV1R%=&\tcH\u2<Gq33few"Ez<%COu!>]- x>a% ')#=uFߓ];DWANG6"Pow&e~ڬiAy@)?C#Ij#ƥ/$;oOV;,RА$1Ӫ,tmĆ0#*V{XB ;ڦs'YڷK-&SfXB* p(^$p}tghcu {R֥4P+BYdSwQaK yo؏qQ0(`v܉PmnvZEbHs|Q`HA{:!Up$$7U$! EJ˟<ql~{92??/Om"]v^e맼|W)c1g<oǑi憞?_,}nQ;len%kY PdG={"^ܟZhR,4?jR.}y& ݹ)Q6n5\֓ H!TgLhkh~kHˑuf($$묋]mhP߈]ZF2+ &e4%CS>{?֪vaIٻ=䵆ϵT)蠴-G{g:(b5յXTU7>%+ǐ@J@beN޸{\p y>s[ (z} ֐ n j2QBmq~'Xj^ߤZn /Mb+jA@Y%I>k[61(q*CD`rNmq4\Y2Te$K h<{,;1 (!*4N1q߹]#zB=:%lZiuY풉wm~|猚K.'!"28r ,m74{ r}GY=R6uȪx)!z Ǯߴv=k_dͺ֠2fZ_uF;?f ;%jP6] pci!uwetsQ9*B%g@Xe X^iZ 6W#%vpo쟸zҶNhwR298BEc=ur'fZ]^BC 5Z>bRЕ o}UT&uwL@Y7}GG ztutU IM% %M')\FuYYNۭ7 ٮVTҒyKjWv !خbyE/Y2Ӊ@HqOg#@'pSG8LL:>gi*X%>'I=zcK2^CIqqm<> z_;X?lyC M|i2#! @H}zS`;+T|O۲!z8_{#4Ur϶#lT&6*TW)t Ny ^ip̲M4\L>a~\ @'yu"\j]fSHsy 1Rk誘v¢Pa,g\ڔI>ƾ]m[2% K(e9}5mTȮFY[&A)`׾=N}*K>nHG$r3啜sRI쀟oQHkmo.,!&c5ͭ0`%դz 0>-LzHf%lHZ]`% ;==wՄO͛s"6!vxjIg(،m<=\R )1!\iԲc8qY4D.%ʏ-^e s\[gUmŤXb-u$Û yJ8Funɲ"ȢbҼ"4g9Jl ] 0&XA eo 'htxn0C)A}[Rormo=xc@υ(6M("Iq.-j$9%\ Vy]W2L.DTh3#!{P~S[]XgM:O\EjJ|[R Rp|NƁƎpY5зLKxd[xJR[HϨM}oW= ykV<XQ 2E{Ľv7b.R:CNxNNn(۪vӸ|@ЕLC(m||V:ퟶ^R/jk~* %R_$yZl/([9m"5g-†Yj8iǜRO,׭ 1TvW8]QYK*S: {kݛ eCp'ZYFE}Ϡ~H#@7[-XULI"?KpE8 (dc̀R1}6ԲXG V!(z VͶ}Dmq0nD)JKa *homM-nВ^&>G{P$ّYK$xHLa>t:ROXH@ӭ/n g܊W3+?롻zn*=Ŗُ^3Il\^x)Mxv}A$'vOȯ`jCR]j:|5q*އ2Ƕ[֧Bۖqz#`Mg`Wu鈀#r~G\^]rZ*T!Ƥ4U%ťad)H'EvޕE{k)KmR:m*q=cրm}<(;>sKd x|KڥMU2odgl@u'CUiԎSq IJ8 @`Ń~I[n@[nJfŇB@ g8 gyNӅ` d2Zqyd}=n;W$VoA9LϾ1m[@W9S ?P3Ii'+) HG:Hj_$4>dgSTOw{5͙*9k  J܉b9x2c9G)t;ſaN&Dêa.|YA]m:-Kd6(jDqH/';Ig\lTmMg!(K p:φ2@u q{nNAJ tO-@5 ]} bVkZKs0#yg?elenmj{Rj<Y-6O]~h&on\ iPYC᧚C8CQԤ1΀_9[NDXʝ5i:n:KH s]jv-{3<; mE :'fCۂɋBoZa  JRFF9[3MQM&Lm8V ğcH86_+I9r'~5JmSd.9 "JXK(z[@uOnڶz?-B<B|OP~sv-T2)#D|Q>`2aF9?.KUZ=udYnITu(ǐmgp" = D᤮)eKO~u !_V:}t؏Y7,r_p)>(^%=7-,WVŌʚSsW_R0}UJ?#&MZ/0ˑY!Ľ R@VE% SPtCe]+<1 N:{sw/W6v={M:@f3"Aഀ{*?D=Ud;ƜDJA8@xb.nmF۬8Y~;a~EmcS;x-5kC"״Rױa",e1&EDrPV@@sq:rk0É}%G!vH @mYi ]S&DvL[cm$H46qb|w (1\Qm$Z'8(A\yH}ulE€ҝXqGCAw62B ܆ės8GE=Քٕްң),A:㡳*C@{r ?/˾i#h&nT+U=Qx6@W$ }⴨y梳 Dv!"l%>| T=sp:eۑj ˡJԡE:<TFםD9;җn(?QࠐSR@ HiburV˞ Li;Wz_kl[nhxT̕V"+e1`xJGHPXmEHST_Ѩ / ub~o|'|zUW^tv Vrk3osZN}465kUklWJ#K0i$t 3Hh۲'3&TO IHiEСu҆5\͕Zi!mPZXX>:EEҖ;jY  ƀ>!{!K#tךU{&LQÁ+RerK}{ziV%|Zi(pYëf?)8%G:<q!˯}8=s*Iy*춢+?A@4y.plHUtRz"oO+2s]ar5P#e[a - s͛nαB[ (mv$,$ǐWU-Rn>*1q[PG9:CiJGCCooWدB)iO-e-I\SDi>@}&.ڏ{JFd.;Ê3=tg"U[45i ܘ2)_?Dg魱m Z5* 텱2;,{mʒ>Uurlυ]wqv9z(䮇VAEuvGz<ɑ[bSKuC# pl+nXm 94B'-4HL紼z4ɮvImG奣Bz$- A7$Ȼv^V]xr#XǙ`=5YݕdP̄,%$dlmOc9Pǯ}0E$xSo˰!dO2A*Am@a`43qĴE+n7X29)u3GzO t]HXYd8 JF/ٻZ<؍Qx|C|WȔ IނSuk)v4ڦ"Lw`28 gӳ\i&&KCAճf+~NK\|U6A>=]F[f)-ƃih]} sUۚf悽mOQ!ӌΌ }@v,7$zރc{viÕubHV+ u@ӋcSaO*;֮ĭݛC1-}$N~u'Pmmʇf0+/X-k%L%`%?s[&ڦjSWxS/O~Q t:tU頳c~3N9MLDmGiH?|qn=gK9UTX* rH#l6PUg>hܰ\ 盵qۆC!+!d 9 Y +\чaהpv_)<oMYW6*ٝ \a!d !X]ϧa[t̊u]~$qjΚk͵b=kDέkPDТʔ1jT>cZcM+iN<Anv,F5IP5Oz3 9$ѝĈ,8xe9vu\rU>䶵(d,8:4fÅueΏEꥨB>L+yA)Mui%d(hUmn[HNx>*BOƈ^07&ˡwPEg6P3!m))JM^p\sl!K yRB{+ £:ԢLD"Ҧ(u^W HPS}54[1eR(p:2XS:η ^#)Xp9y1F:[}{mm=c '}etAh̤R4ݾ.Pfn*x5I*)]e yE!ÏCt*spV.fstq!\vpDԎ%N)<1t?2k1;l5^SIC#eD)F1il.ɝ"%EbRJ. sK !r?-i`$p"m_ΐˆWG@tˋX]tyP1#ݰ.cװ3Zo,cn0ǡmY#FKl8 ' f_Z1mK%2`dc$#|=+W]:HC'9.H⢖8 OX>mGy=Md}ġ׷ /4倞J9iʠ%qwYB1)1T)Jh:i3v bmA(JSߎCł[&,9QDVmE(aF1=_ߛC;Ʋ$JA%i߮u~ť]KQx j*PpOXmmq]YՕ/xr;J:#w5pSO}lKx#>$c%(8JjNG|[t1jZk)!no2 l$G::f۵mY\2ru2@*[p` m+jKo{7 Ϣhi]&9ȭDzgAVVjJ3eNj9kU'&Ԡm2@u+!ִU7l܋$0ӄr)i%ŭj' (i*6M?2|/x9j h>lN==3&MǸa D9~2r]iŽ?w?=4oEM4e\q S O@ckD;i %A?,˿l73[lYM45rħo~>z(HsmkͻO+p!Km'0&J-Iu$%@ D=4/W_Z-"SKΒPSv_ <|)8ڡUzzCӝ*[.esr8%IZ!kG[=-GĉkxGQSqn[ *7e\-(`%#c:-Ĩۻ;nEO-p| [qlx69 YխYKVke`s(} )oyG|)=d4fn"mj&hchT kluҼ{)풙6oϏ*Lt(%I)J +3צo-Ƃ<* s|:1] u6M&8)BS/)x=eiYE)ЍWeukO\Vd)#]-̣e̯r440R9A=ty-ML"cI8KmZE(:e}4ɕl@Obˏ+:iI'F(l$cEܲA1#)>tksݔVнp4501$$weI&6ݷBNYJse/dۢzS&WXUc*R]I$dߟnBjIAFMn dxj|F`I+]8U`X)(5:9qt?H]=Iezy$~$[6|WYz0U@/{?ͧ] m7ssSbѮ:?iKnB[zT<!mԥ$KROdt]CG-_J@gbI|%c) c9 HJ33%CqV(z3aPD)Gʕ!`O&oOr=ͭ쳌ŽOPBV>d+۳x7]~ڔvƨWjH )mGr 4lϼԫ+59 s5k6C{Qjmj$%*Gˌ02sއ]ă⾧]-*mOĈ 'Ct}JnԡFeȯ 2U&*SdgA iMj=)WC+t0G0[irx뀣ְU]vYgiI0~^'ͱMdE^Om@ =5Vzא,b%<2PaA`xRO]*?!S"R~A m z Nn͇l.iK3x\ࡁ cGӥ ;WBeګ選PȝkY+ H?~ P84>Z݉fZ~ѐ#$[kF1@znĸ:"YF!Km 0J#@'qֵ 6c2@ jˉg6 0 #FFkr 9V۰m$qSQJsyԘŧv=vܞo>2Y=C'9 %LUբʣmش ,g\rc \:zL@٩%(Ė9xcuېif?g{\p+mFH,JTN05Q"]~DxMcu (!%1j=q~Uⷺ,Ȗyx  ~C%[YL=ʠ )^ >P8jnh1jB/. W~lHq?/ u}T}CJܗR ud+۳cV^CnRxXgAgpYƋ*s_%pD$*sN{f2foqت-B[ `mA䜄HqN{ؐwPWr+M)a;({\Uݛnc:|Љ. .)SĠMl+]uqqJ $OG#TUW]VY{LlDY-QWjp6Mn6ޅ"K/ثDuO! -PX>^04&߽3߽RmAॵV=z)>nNu[uԩwl8e2,d94] ojBW"vd%#EC8㟠Ykv$m%kSIFC; gt99NNa\ Diq^StNJW`]{8ƨ^Ēj[>`>w &Ԭ% P$]?n{r ,OuQ"3+m1)J $K"oȏ)Ly$!乀F9-G4WHFf-:[q>cjF#5^\#* o$~*_x;U .;Ź/UGZ)+Nx(' S=ȚebcR'+eJx2ҕI'AzVNa1#:$-ǚ9O @Ƈ}O*u;֢S>x$)#IxTX 36SP~v-߉KlR I^2 wv,WnU$m!IQS;Y=sZR(j hɂLDvDty@(Y_i*)⸒b+ʘO])^fRRTnJK@ hn6]]ʍ%Q#aQCI_1><+v̂W%a!$ f+ +|:`R’HgKhP-FʺN8O~ϱܻTǵx%*f x$d%mW%=: Fn/i/.K6-*|d@#V wInlo:ٵLk ֔ *XIV=gq uS5$rіi!mK8$Yۈ-7)G*CN8!簥( A0)!:_ uWѴ8@&mHr4J̒;Hՙ3o; ; `-mCPHsfѷZ_71\D/))ʸ(*~$`^[Rf2̒\$+x>R::+~νCBDo 0z#ԗS낀εm*]-o#x3⃅QOso!YvCum <:B@'"eΕonW<<F+aqO*2Zm@>jҫ3wU!⩗Rk#z$S^oMnvą.T0* 2]TG J^&ß kP]A z੬z6mrRWc '\]:EmȶREb<~c BIR!G>mJryCdH_3߇> :=ΒD}N[HK&٩qosoNhvN׷~c##b%I%ju+ngŭyjTHP<`$c '֧m6[vam χ!/6&Nc"1ip$ pHQ{}ۘ[½om35 q2"O3>B\e(OwƫS[S[nyU0T-n8͓l!{>>9Y#>gwro\mې\!M!J*#% w9]ùhImMإ~u..+vHIh*ɨ^ 6-LەEz;i„]О=?s_w:ʴEK,{9_9ee 7X44s孅9B؈ͻBRH:{ulYtx?*Ҷ@*1PJO~rhލD{͛fO%\FR@$絬Γ9w5h!GӉ$8|%0F=NZ+{V~6tDZKmqRj3fI/t\ÒQ'%1`=thrUu%T12 ǒZ#$^0hڟkw,L>gE7>+!.գ9Ǘ t2=dv750DT-RNInr2{H|{{6Ҏ5RGbKen)΋^ n(zRB /\RlGzT4YSï~iH]"[ q:g׃}I΁-2; 1}uk&1ߚʈ9ex8'U]Kl2|5$$VInQnoVU[Vi?2k`55`%?WϿRMԩAbEħCک2TTk ROc:2Em_nėk! DI8% (<^q] ;ksÆ6y|\vsE2$>*-}5JN&ëR*~UI܄9 >Q ip)YpB|WǶ!ϯܔuu1ݾrs*R!|JcX3a;[2JV,ـO#R'>\N ݺ7=+"mf!.M¢B I T: qV[p^lcc%>u:}yg6 hR(_0J̌W欏u[11K#F1ޔ):!Y;1gb4[iqjv uGai5v:W+?7 )`>ek/]= H.WlmO rONwԶFZwRۅ"5zR"؋U]TwrB$upI:kw{eݹf*V%`^%\u佹a=% um:҅0e+ZT0r:cɉZ٩̒ 36J8m d~ /WpJd>s#Eb\'\PRΊm)6|+;^Ɖ_9+z!Y O[\;joӲCZTxJt9+)BRyw;۾LS'E1bxS@-N<`UMT dj*,JkWIQ, =s~wke͹*zk 6Sa>d^α]}:,n qW-ZgXI[bOWv[ D)) JBG>l7 89WزvBNO4Nr}4(S1\~M49 Iz3 iIKےH&m6x./0 !.`eYxm\׈݆e(JqN() H'\WHiحZ9-rt>Z%DF3cmܷ݁hmuj>wPpǾf7U'v.+IoSVlJ_q#$@wv}>Ss!kO8ktTAO~ Zƴ}Ƈ_5Fk#t4꽖>sFr]uXC^ήK:Ky/z9H'ީFSuVqX#p1`Ú t z[OeV s291(4&!9 @bsX=cs6MQ\|ϖ +4g OmV%M= 1$1b%m%d;#Kk%&fOۭH9SBGC>o6ݦ ҅9u0oi2V\ ~VnzQ>--L(Np q$&L&cuH@!U4xJAUM9+v籘ZցBxr :'9=m=m4 ]%Z8Jpm} $ 1+Y53P&|)G-wH$]n}2|ǟq.@KIʓɮ(OG)JGE6k>/GBDJ=Tt'뭮KZMD!*J<%:S)_<;&|)JY~ׄd)J֖'`0Gm25%k5|$BA?;ċfٽ5)䰟cGgXԮs)hrK,j',dO@.RYn-U}#HC #LJXҏɸ'ȥ[? G4NI G ٵ6mjyQkDYN6Eŕxi?sS3$ѭb;-%!#GdLZ۶wÜɋlY;ycP!''u|'g9>Ětn[_ ?&KrSiO~$Ĥo%$ L_6A˟)O5rK:p*Ae'?LЭiZ}C5ؑ:u^Qtl#@͹^!gWVE sk%={T)8T8cx0 WpH=hd- [[sȔ 19,VW19aخg>bq+vjTXCr|gGHuWےeBKKmr'$̡ k#ꡟmicnD_iBd74+RP.de H?M+J7O=( tox&O'8JԅT@\v6_#&嗣,G!xj!k ㎼ -D3o+JiS L:((ZGΞa}̪J>WqJK߄d @ zt:4J`Th]WK~ߦGZ k7%rϔR@ü;5-iT何*o;qG]ttI=ԭwM9[ލVʓZ:l-1èNiٟ6}OX7-뎶)g uAc@e"C6q7-]a-˱P -pi#MenQX5s Z1mV>aд' ַ1 .Si;o&<p̐!+>eR8=@o]ނ%Mq)6ґ4.8Rx19 C jNUnhW6̛ry2%eXK#T@Դpl-U!jsOFzKx`Rx뀦ݸdH2b݌۝T Z{:-eIsZ&..x}N*S$2w36mpHr$q첰l S7\f6У>m5!XKϯJgh9f )HiAs! ? ٛ=P>qɁx[-,(:º RxZXg#X@:4.TK@\W-q+ .U3ދ7:uNş"6HsJˉp1?juR$3&R-*Z@W*d'0 `O!r} n/K>ɷ+SHz Xu@Q'>U%㯋/.ץRy%Փ?*WIv]":\DN;>pZBRrrI =teWSeETS"@pJ C>Acv/Tǒ[za1)qϙ) (,Rnh:d`=6n:@:Ԯ(JּjI۪g|݄u:3KCRy֊]9S$#E7^̲uBܑZyeX#YeIK“jr7 vv,>Um9OJzuEva^lF3n) R};%E0RWyZWɉſr,28| )+K:\L ]=Aӊ@ o])~1|cTRJ}jeIR-m H{x3.;NB@SI[ du[ش6W/L]“[nZyrh6DseZ5%eR$q<P0G('gWٻV6ֻm1a)xf9Q IHJsysS)#˰M8ڝkeWN%>En+ZR=ZgPZ m H|->I$fnh1t28ɾ`Q|q88G] Kӻۑ  &䰞P9(u% K)*rM@EklçqJT)EB}Gcae>:/V 0d$6=Ƃ۫xV2,l%T8P N}qVڷQٱ&DE.ax#+k}1>=W-7tg@= އ)t#m.2dhinsY>)O_ɠ֥{be%Ucʐ>@OTgO{ 7$miG>UVr˜9WODE viJ º䓏19k1ll+u@ !mBtF|8uk^_ r33"qKҬ! whj4 ')>!Tָj5/m\6 T{ -q> @z2-mOXMioa|\Cg)](4A-7W-"[ĥ;$}heβ^o +:IB}Sy PgL_ʘbCH Dh(dq H'7\ڌؼOubGZ]\*9y,xL9c^P#%`wV𬻔~~\K\aX#I[ 3kF\ؚYRy4,ָkBQǪ>[,WƇ&ghN\ #) gVSL Xm qiY)OA*L\ 1>@R=z 3[SP/Yu'A$s `b J*/! JP?CZeGƐګi#"m((u8J_fn}g.726\c8 %Y1hmoO!D;@=22tVJ(v3[1m$>SM(ypOw Qsc~91.;z+qť2JԣS;*]=ELX6[SX(KXuDYR’[@v{ۉ{lq~<r%]X?*8>Dg)Q@CBR{*#SvN\kE{vm_u:Uy7$ ~zP% O0IB21~*a R7% %'+s[|FI#ПCxr KudI?6ʒ˴䏡}ޭup4RaIy(D+U}4xniW.潦ZR9ƎZx~ =}6LGfte K)%h3sހݰz,Ƞ@-`: q|;1)M\|KP7}G锳*b*m&k >=->$$ l Ѐ':7 Jy6ҹ >ke/CzJy%DCh;=*uD˶y r#p~]3Cm! m[uqT42"λۗQbԵ8eҔjXXɗ*Fkϟix8-5SQ KPъ;dHk=dX21脟rb;g[qk( 8%@*>Ug^vڒvd6M$;RMɥŀ8뒡wmrvnnKyE,% Z*$zJJ7Vⵒx5mq1Tm`-8TNxlW ǭh KszPʰ +$޾%v^^4_1KC.8ehHʸ $ҿHLWve|er8#ܧr)M⩊{$W#6~X~l=rzz"]e} )_I-% )Ĭ7=C+QC$7P'%eg'~if;]-!w|?4R ygbtGOIi2QOd$uz~==%sPmΗk(gCkx#7*ڎB#2puCpd{JMo$e9k}+q^ }T/=`o,io- 8a eo}'m&_[lb9jAtrQ?`5~љ\fw舂b I$RJJpqzvÛI uVkkl[K/W!(+-ăe^?iqgon2:t0ڤOU)$$H6=CJ1""Gu9¼GY`+>e)$}=tVsẸ ;$E R%Aď`0L,J ;55҅a=G6]DJ<5Q4YAw+s Nv636%'o#p o`-M2T))ZJNp*Q`Mz$țz5ˮ@K)|)+i1y#9=SdmMY6p"ֺHS$ABÍ)Ju4ws,%'}IQl$H'AzF]CM5/7$%8GD7H͸ohw}LĄI60VR/fqٙkm-J-"S%#J@䌓ڏ#0 hHr{9V⧤!8 Plp5F2@-q<₁|Av4Ӯ+Ǻ?1 bKO'ymy3Mr & {uD;w9bakPφ 299aC~@[>=Zt0k|$^$Cxڿ;U{zCPGHBJKO˦ﬥ@~chƑ$)q)8JU$Ғ{Knv7c\U-!*jLRI䐥ʕJڐ~:4!C7zdVQ%3XӮK|CJJpk[UQUQ!o0DYar[q<>U~>MK*;7JILDƭH(y̄Y0ޮL*|‚Rg2V4=CIKHOX_L'\q yJk+'>Ӓ\HQ\Kin*XgPs%9>b-@\4IDuC !y+iy'?u2A(BR}r>'ȥQCh.IKfJGK'$݋k!>"@h4X$`2.DLFPQȸ@rǗ$g -\V*CM܍i5C|)AIǦG(.Z:ě2KP\Kwi? m+a?r"˿ͶD-)Ar]\OZӴ<׶ݞܶdx-4JK'Dޱ$]IkiӼc7њWTx y[rE} XDSN$ X:ΩM{+8ˊ5xp[$m\nT9\iN쒗R^"N )'t]cv;~%R͐u$I Z|Ym W3I8wVEeS1U: 4~ ^պuUlU)E'%ȓ^;'c?Բ|蔔LjԂ0zIZJZ>,E J ͢!`~(% pݓ-onsO<:䔹$ηA duj )/4 -6r{O 39*5čMĸʲ|@'=SC,ϱrڞɳDGT0Qꗐ~ ooSY}g_*1ј$!!'* ?3|[56ᔔ0dDp=BI9KػY즲*P$ .@NaF ;ٻNjI_[[aH8 S]]`XOa u< g9Ǡ q75q8J5k}M3:\ J:NDn-PeNAm W$B2y u>[n;9 .p덜|q1FͬMջ|*i5%%QZy@yжP0sdAh]* Um"d(-Kn4rR\*CK9#Y_:MM}TV-irl`k>H8>:ֻ:5u=+ J Ff9pdI+{9jw#WEpجݐB-Eqg!OW|ڽU7CƟg[HBiy8צI2[`Gl%4fCO\[dV~G6oQ+M6f%r%6%~*ՎC=;]^+pi>%%B#dF{{7o 2GUg6"XoB(Ǯ_IgW;HKRëmM%^xiY XƗh;hm_@[Ƽa^9N% I)R}=wרϮV['RPϧu, )[e{Frm"WXA}mrc\,<4@Kx> ڽ$=E[9Q T^T@³a RG^nMkGpE[MBSDT>J0 0,wѯ5B9֩~l*XyˠJ۔7U9Z{ 6JTi8뭋cmʀޭqлJt|Xu@4TL@{EoO͗K7 -+[+lqIrUl*e#n0YRZmd: )Ss4K%dmG D ָ;oAT b|a*ZlN y'Xjdَʢ%;E>Se s;Ίokzp:8T(.gחށb}~[%6Ե\{ǖh)Λ\Ļ軪qW4DQ'AkNFᑸrMImM \ 2)Rz‚SAr QKu{Ij54u,DR$9bc@ˬA'9/}Ym5"LyQw6;qDu0rJR==4[<'MtƝj ~,>M /O'סwHh b2^ibYz0U@~@;Z\{swv:?iۆYm˔;jN*$͠(19o+kY༉3)/l0\'KvY{Zteem)99)𒥒=px$Z[*v.F0*i^IRI ^) cgX[SJ%n"BdԵr+Zm 6}qŜVamkym :IڇoM;`t|vS)iۼ:]Ey[iSU+L?8 S)_c׽Hҫ[[yO&UE[pG91*ZRTVHrvʹlS-%T)9.;y+WlͨfL8? "d#>[hRx2Nz#mU22%`(0VMJ+֒ފ.'s kwl-A|9,fx, 4u8V3AOMiJLYD0RaPNTWg)$+ x~SVw_ާl |O0:\כ<$^ml`kJj씥X:).]Kw;퇙z+O FrOz&բR[6ECr,%TUO_QԴ2v{r=ݨLwK\Tg7)(w?zIظci%|v*|t})j'L>'mNa&Ɂ(u)p` s#Zw.ݵQkdڹj.J }0N%=W{ʭoHM&igP-ꑠ%UusꝐdžֵVqngpI=c{V7[N {pz,V!=cJ ;ցh7FSʸp>+x)lzs}9–̗Qf_UBUJ۱zP%"ƶy* QR䶣ۭrU~& wO.m [@dg̵.6/"?]}#U>1#:r|d䆃Jg>C,+cnƬejq{J.GAFin:Ȉm̈2$Iqq׆nH!nVֳYyyyfSt! _daO!cH8Nmvag.,ڸs2JuB:P'} '+gbn8 "ᴕ$(@0IR0Ft%xU;T]f2$)KJn-KW" g>- lYAoqַ ?J3v{n-Jai7mЁ2q>=îWjV0%R?4s :I!>&={iU-*~#-de)C*+$v;ZfVu6)YETne[RԵ`0˓Icb cA0[;n(v\5l%R\' Bˋs  k=CeV&zҏ?y(it HYn}FNceZ8 8Yi}Mfl=Dd[+qr1%EsPk@{NĴg)\W8TrbN=9__}kUKfKv3/תġ* u׽ۑc[@Ũr[Q*e``O;m'6FMm-23ZҗZI¾ETS >2~CAI%GC顖u}^r|cV28=YB?GWGKmKx:I q.O +zחuzt{hwÍU1Ԭ u4i^]ý~ʗaY&[HLI2)-(}zOYμcNr,CITw[NjI$sq*##Tzl77M)6R}Эс4qDXʇ0}s!@L& aYeԶV y PRR=bEKE%,ڋI)n?|u- 8._eMUY~$aTOocL$''ZgƭʹF2LufAm(K^( R@Ie E}IKPkXfKe ʜ kߪlJj٢ Ξގܖʢm>JgܖUYeD 6)z@ !8&1X@1Z _z]VSMIA gG԰ e^&6oeM-#%P>hd;Wh ;ctvJ hjbN8I۩A =4\75(Sg[1Tk QX܅mT qiB $4Όq#;sq؅>n2:X򤸟!='ub}|[gM6r!- |Key-@ YkZ6UOxs>f (o}\-Q†4SڭG'R!p09(O?좭U3ͪ$G BRH,08_T}5fm< $@[ȳ5K֨dҳЇZ+3h |6ܰ~H?1U̕' 2RT@8K瞥DOCO#[XmRE$k{xymcJCEDXϑi)MA<}yel_!Y"Kl+T^y'I^΂GtDQ2Zv$HO%6~}!~(!?Q"ŌpLgBБRx 녇8_4Àta# 'w벥:gܘUA>xHiP@tvˊRANQ͖jBj:R5Opuݣ wq!KҞ*Q?sêeI+#AQtGl6հyul~y0՜ rЭ8=!p!]1,gR^8?H'KRwcqmشM*Ai܄ t%|d)=t8֮HU1l`WHqjKGiXЩèڢȼS!6פq$9huY2nC}fE5;"t߆LW;[I!!/[XyR#yAsvے-(y#:C*+QJ<#HaJ;)^m$!}*NI#̜NGE*W^΀Ӧ[ʧ+KbOhಅ!2I% ͫQa&=5&Rř>,BoĈr* Nn qC'q ˊ)oCPAhdͻo}|@M"CLSjjR@+p-!L!Gw2ۉMdFwzUSNim2p}W_\r\tIj4ha.-~`%ԩN%Ӏ9gՃum%MSnLY,u69#'ugDSvןiEɕ HPO+H9xNtۘ6s\e|=VoB5'>\}t{Ihvjχ]R+Z+>RHE}::l%KEwkDroZ y $#wV㳀bŎdLz­Np'KjdeCrt U5ֿf9@x-oIm;VB?iEboV.xIibtХvZk©rɾ m9hbP>$4?s@t9_fQV&`1W0p4׌ eHc}$`Sm7pSκU)({q@Ԑ4+;,(:kTіS*G->.SoCQJTIT]v8D`Eh!(maȓ}?y{-7*wpfo]\ Ny(~:DYrST\%2%05^rP=g+5qްX0lH W ykJUMyN}qpk~NfD5:qeˈweCcY@OC%V<Ēc}=|J t?H''~gipu]c#^{"0No]6yFm+y kQZ|G;+c7]VP2*mS *)$eYd0sםqnVgҕ4s\2<5+)=cMT=YZSҟq-ͰeO4q׮==C.Ҵ^̣B0UFDJ \m9@> %rT7{0KP=xjm __yc'wd-ZCn #P ~y<OLjy19W⒄}R"}^V4)kG^`)%=-,4\Z~CR6ƋI몣[Jl4dp*% Wr_tmڗ*]T10 %\9WVV$UVUS?|z},G8N~]nA"ֵtMaT228ǟZآrzkK;7t:c q;޽a4W܆uL+-IϢ#eCٮx^ŴqLf*DPJ֤42BR iʊL?97cUL((r =,<}i-eI/o͹UyRڤ(%}unVV,m/_ [} ~JGEY_m 8]=D.sr==u}Ney1ێ *??x$Vlm!"DPeCOAc!g$Q_ٯ7>ЇX̪P)ZKt/ҦGCƁolݿS6fk̉ eO+U~x9$+NN< ^vMŻQZJT ]yrIXԬJR=mfZ3VuKmJ;q^H)茅cM4Jd{2•UQ(sKe痡W*L.wsOļe ׆p2z:vB1YaU6 9LZ4?kƧ^C ,!q9?yI)(@.r'5nٓI;ָDuRSKuŧ814cEuQHܶkVT2I8Q_y+9V}6KPJVlJTGÜqZzxl**C)Jug=>Rq{L. kZ:&Rܰ*?"NpR1t [g~t[epʼGgӊSWV㴗wgTK6"!_;ŵ>`QNrӪl^V2lh#. Vf\ 6h?M?Ҏ踃I O rdbYƆ

    :1A+X62Pe[lzin^n$ ͋WjDaCƎriWA)ZtC38 @-UjlMRT RpҐO]h48ͥXM[nHb[yHoTc}slQnh=5%TfKRCO:߅8Н^Bv+C &_DԀ@2ރl}4EŪT Qa`y mR>Av++qAEƗ-s?\%#{^dXpiG/6.O99>ڧz2ڼWTliD_ty@+y6"\(2'fX(כhCfUb`(G̭ ϺiS# Aȷ.jFܔn'SVJBg)Wƺ;*QΗ}i/7^ogma$ꐵ{U5~|Yy5ie"UqN8GGvf;5ڮ0K($EoY`vF$U_o|7Erd8u'.; rV5n1+j4< om*lj#.c둪7{ڪv\í1)-h7x}E0HX{j1!CqŴK QR$xg=d$+m{,lu`j! SjC p*Zovx!ġ1Bde3Ϧ2t{d˛=]&3DXb'*Y%H+pbbMj\Ĉ ; S8s*NqƂWnm.X(Dm tψJ!^w*CV,hQ%(})ƞ NJpS.#S󒐤BG-.zH:*|ĺIox)ICX R}.eiӜ(Z쌰W@gnUN6TgmGaGOtN݄V\UXJV٘$/+ 1ס}+sUk)rЯĜ-6)=렳 woBizKy Hǻ xSa+$Y.; t'.N]WjֿZJT|5Ah{鞫ͤF|h3z2!KBO Y,4 mjt}&L~ˤ+YSkpAn*@߸8)(B2r2POcω{ ldm6j}-?%' ) 9Ҹ ƹϼmfs."khe-c)ů;`vR43[a,XiJiOxiuX=}}Tjn+q[iMҰRQ~eNd'7"wU8|xpTħ!Hr8u* uytng+[veۦ\2BrR?uĝw+ى}f-hθ>*#yIZ{>n_EYe("\l(,PGߴ{5kML,76yxVxD^N7yQ ^峮SE?.P|EV^Oͧ#ܛx(zem#ڲ*1QgL%]|VgHA=@n+S]м.*ْĤYSqFFFBTޖ+Y[(&CaŋoU¢9ʔ2F|;=+%3 iqY3ZHʼm%$`HSSXos?'^Xu;/80"[k!<y`)Sה6G_7nmUoY$)-Hj%k-%)5h2C9>k'"]P]CSlIs^{ mxϔ@P=T{[):GSԩҶ՜ F}zŹ^,԰7tfFvgIBx o$둢?s!W]̔"KK(%B3~֚}zf=.R~#l8oRLz L;;IILwYZPNA >I zօvW=RP"2Iy) pHǘk3(o%e-̴nceTTGOL6U;҉HSўWWׄ!G6rBWg^* KB[B_O`)E>Vw<͹;Uu0EI$CZ[GTtmߔ(dQo'ئzkOV ҜZ_/̎u!?!-6^QY1Nj*4DO-w[Xq@(cT~ݹs*w$V£&E3saiQYB[b: =M [um9R##Am##>^A]?kES]liYKtx|Fq.SʊJ:"a# q>=և-Wˋ(*nJ}p4iemg\z6i\[:q$8z:D`/ 敧Thirg#j^{rIi\(s ҧ5jӿҖ>ox$0~ Ilx8D9$  SQUBLo<ŒS-@$%a*I^ `jܛY+@'Grp=B?7u '+"S[Jqwk$/& ЯKLK72)Iu߯]w }dx~-!G)<_`ޑ +Pm2[p39]H'酖+&n[aa#;YlQdqd-[U}{vT/Ӡ;;]cAQ'Bx{KI!>}ӡdTQ›%pЯ[B-׎mQ*Rt%틧ܟ*mehlW%c#|F͍r*SzV7b) c/^^Q97uX&t4Q3`)ڔ)+*9ʃ{9Ҕ+}qswPٲr",%1*<|>eqqցi7+/";JB0]s>(GoŤd2HQO';W0>X8H+y f*$d|g aeIۖXq[#m= p1-7y5v$S\u!DTI09IAjm9݄Gr33j {/{_Qa#&Zc|#ʤW-([osFBRӶ͕}9a+9JqjkXA#>^$w{ʞ=[bZmۖfAbAϣs,*όZ<3ґ21ۢ-k+nvyQ"D({OϮFoɻd׽P*8rS /4SJH3 dԢFl줚Ѷ Tg]?W7m]qDa'RWA#=tգ"[]oB 5`JTUQq*G,t6WGaƦ1aҕd!==9N?H%Tȏ;nvm!ƜjIJIseÍ4*A=M褸z+7!D Q֔=8([+N@ܞ]\4i0FS?rGt~C4_iN $4 9hSgMf&cK~2.-JB@HuY[e9=ߋ Hq%I .1ZH#ν;~%mdʨ-`زcʧC GKjwEU{ SOCjRIB<VZB1@pN4ȃw[I\"Rmm #wϗߒFGF-YrnAQر*pL`$׸sG *+m\wx rpɐ(A^<;ϹjM$T҅G!JyՔH'|ޚTw68o.0e:GE@6L{mh*ЬQ%eʃ,J㈒ߛ uȴݕ1^MdMa z23BTP9=-[neo7-Z@RK,/9--(WIP=0rC2hbRW"Y-q%kZ@ gs鴩UōcNqx$) 9J.UتRٖM-ZQr$GI[+:x=)X$|gݱ߻[" 34Z$'8/FNT rdV] mRKii-7 KlHrAh 9 <^},'m(Qn4!ᰕa-(K$w]y_:k0j^\[l/!e"o&#~M?cE.ϲbcxc'9E= λOe:w- )K8 B\oTs;[y^?(W2$Xljɵ)@@4\teuu,Cm 2RTGdivѡ_8r>3灕(p6@r4 =.j*:"]A)j:W0B}rCM["/ƧL$C p;ēiDobGW74R@EJG{.G7\ۿ)WmDnhi([s. (JV:hY:F)5iZF&?7Ve맼|W)@[i?7N0w7PZwRۆnv\ShIxsqM~B^ uum>tY jCex<9ttnmqP>s Uc[۪rkPpXkZ;RD\$/'mhVbMݼg#ļr0q8PFb}NVZpt.RA9v:A j౉p;u0%/2 yˍ?m_souSQo`ƒ~樴s8\$%zOr3[v RP7l; q3inx v+ء- 17Cӽܴ?i"•"T"kbABz@#AR9W$[qGiǙSPV^<}3GyqW6 yĶۦѥZJ@cY=[omWϕ`m Mυ.SHl$z>7ѓ=oɶğ v]ROa@- vCr$-kE-i){#gC.7;/zK-TRqu%^cM jTz Vj,J++q.e)ϪB3l& ӱ`8~OL JpδIsz V$‚Bz?ZR vTjmగ˱R'U.ÿleC4kCl8ӻkB]%JJTlyUM:cJ[%HA?}LsOp}`vMmĖ'/Xl].<{I>\ջoR%6U^Z B@)qB.Nq=uK'pWrJn+uxJZdFy3lXF.ntY jCex<9ttOoJsUlIS6so8Z?Ud~ק2:.:v/7d+_~ʴrm&3Z^rUs^I9N8s(P#\q>ʧh+ -X8VxL ;Dc 5rpXĸTZzbzehʸv [mhˋI-% t1{֭ʰ~6ꄦ—)$6TZ` N=A huu؞ԷbO;.ħ۰O^Ptsv;!Ta54AZs顗Uwf %*)p8쇺1Zk5*= µemfi2g!~ޙ6wKUiذhj?'T}ADgZ $N~YkTys[qaAm J)j ҇cM;OqXC5T6 pXKeة BU^*~a߶t2{ T ! iݵ!.s%Dpzꍪ_z<߉Y1- RK98|];dKҗ߂۬6TJ½$cjԷ}w)*-CC΅ '8P%v+q7RiB:r<@-2| yC*KnDzN&VHfA$ɉBYCk!$rurt|TnɥU*VvpU|(c$c-nmǹ77U3OH+ H# ^&t"%MS%U_!o$JcԐ֙.6uM/.ĕW )RToGΩ6WlOʬ|YJ 'YYl{%գNLhqm$,9$dd{hl!ZhK0l/CyKa|G"1? ~v}Yʗ. PyQpH.8\Bw45E.d*J*xO,dgᮊm*w&DU˲fKkt1840,6e*sSS5rkv:x+'J;#B7Encg&qϕ5^Z ~ic l0BS?#]pmٰ֊p>e)H 2 +'9V-lh8aqNӉY OchgmW6U L-)+iJWYd}Aј{Ǘp!ڦTy/䔩]v@ʉ=LǺWo8Ί)< eoz;BvtQҲ"7 SH[@;9igҪZ"HAxn2Qt [׬])<'[*Q8'%59ۑqTʧwJ_!պǩ.۪CϧZkU{kJSlzJ;!)Q>nƁV;-Iu}B"!^Cu)#$e)}ƏhWAiɗ:ŇߒT) ]UvtwĩeKQ$/q)KۛV5Uo-jٗbם X`^ 0|;bogRHe! [bK,/<+Ƕ Iн)o *t& bB8)#>ں+^% +Zho=}G ԍ)9\Zy,c IjI=zs|{6~?.4sPJPyp'#46q]WNK!A2"[2AkPƪ_eWn6 8(n;EyJAJ];UbԪ\2,x* U=F "rY8VϩƗki۲%4㏡?q+ YH #8JG^].ql)c#n!PbFU l~z+ Uؠ;K%[p;!$*N=,洋Sj/VS#o4o[ڵk/]= IsyWOC\s5֪Su>#UsD*Nfs-㋔J򤕠(y}9 wm3*eMv48ABFBp}ΫDaRbn$6Ta!9΋G{G䳶#J*~|~dE\O.Yg$:KvN~CVRT( i@q*9y6H2$!iFz -9L=m#5! %]SyH J^pWg:PBzMe|–\axZ(Aƃn"&ϯِ40J!pۡ~w'mmHv.4n:y HOlGy]k' R&7 s-C{k6a83bfu+ȯ,;ZZ}·aֶ[*$P g/bJT`dVt-Zpp?9߾4v+tV[3Pc3yGRzkꝔ$Em܁ia9$.G#=yKl5I(NF -${Ϧ.m(uB+}:˝#Ry #ЀZEcE- 6vmuE9^!gBC(J{+w:maVIEm7q-Hu2SH.-~@Od_Vr,qjf5{K9JIVgkYK2m$y֑ri#J_lDoyɴCnk-@(KkuŤ𢓁ߦn:y[q7܁``)|!eŞx=#mk6mSj^ڵ>7򪮤'ÙТH#B`dԅѼꙜڛ\eJNub1'P-mW(#g)k['JspFlty#/.q΃υMHݑCҦ8 [Jh+\[_X%]k)T"vcX\G%%+[mzu=JꝯמђuKL?PO$yu5rdTnrB@bUGeXq _mƧs[Ľr"mH² 8+ӼgfU\ʛtui qO7:9( ݈q8WWaK-ƒJJWQ% rg5#Yq?_s>?2O" `',t vL:bVJ@[J:`݌yi a/L@RV8Vʻ9ڄzN7"k-+u^!G|4wAP6}~̅ BTw?D ;}> sS I;kj@u侸>q$ q`B}_fX ;ص@&JXGlY8R6ѸkjkY ĕ)6^Exd8߲47wWԽ^c&3Q'``C=t{ULP 5&F[AlӀ[Ҷ٘I=$?Wo͛^T줯!"+o K r9=):_abIDM v1Ii V}4slD I[6\!B8T>~к+)mnxin+,4Y >bBVs_˿Q֫oK J-mjC^ʚGqmpu{"QfS1ߍ*YǘPN>=#ZͶf\pFi#δIUb% {ͭMגtYj1B[[-$5qۍY K .,P kYhڛRթ7Uu!>W\oNI8t_p^}ZCK̥GÁ %$@4n׼'f8 [R6bZm;Ͱ^qOr&FRrx_=meB1(uTT  Vu^ފsLmsj;/ -.VsPxN?qխϲ[RHܗ~R*VO:0/ĠE bBR$,&0cn `WZ:u3m7M+>\c$yƋ$U>GamjXcJR3IOW#MSl;N"n>s='rҐ(<3ÛjrVHr;OG{j[  ~$d`Ztn*olCXaդ]V}q vl ?0C Ry'c'GNMmZ%ʻIQTIܒ=Ppsh%+֩mO(PH>eW8VDtMB^YP[ 56Y"pܮ]L6U.RaH@tGs˪Pit[Tx]rcM:|!8 8#G؛7)RO*Ni*%@lv{sr|5-cÜGԨwӵܺKC[WJJzTwF}3XƎ]u[ %+->y:R 'ilX5*?Ci֛ug!-BSoIVUes*LTpTP= C+aKAl[5ո򓅕}SA4Q%2nv̤CS;늩]0s@` [mrd8J-jHZKE=uEJ_g;XF:἖e-FS+-y@8sZkĽ =t^k`/VCo% 88Y_"\,؋BiBQ Tˊp}:9#X=u3+W6nU%.:Ce R98C-Ĵl999qS[ft(]SB~5ĘqJW%`}t&о˯b(JPc*J Y$)=W(3KثVܢ[}i?&qֲz<\:dg:}ժFdcCC JQ#x~=h(ʊK0v˶( {S, c8Y4ɸ'qM;f\yx*x;~!ϺozF􈻂F2SIq.PqEjVxcr[C-)$~y(zْ*\,_ʳ!rB8u&צx͋i $e)22M4 V;Pu*VQ9Uh̹]JϿxC0N貲uʔñ֐J \ ?>ްqU"<8,GqVPPH>ìԟ߅6EuLa,VO,͍T)9 ^J>nft";")S.<> 3YҦS2#i2dWC߆XSHK8(mG a#:k#otݿ4S6/6ZOxRY?uڦ?dlJ_W*֟)a_⥉jb)I/(Pr2;$҇w~PȝfӸ0U va惐RS@T,69 rU| =GCnc#NNO,etX(XH^D!mfvWR&I1ڰ<(ҤOᑤNXn(5oCˍOAl<@$ۣ|.٦QSPfÁى')sOg3"P/-TO)˗1=Й-g1hPl!91Rf5猩&GʄTV>H)4.꾚zQ4SV(Y݌ytz '#Ek7mԥ9"CHSqc!B[eXZ  k,/A~Jvcƥ2C!$-n;)Ϣsh6BۻqU lFo!2ҲAOCiBTN sȦboŹmoNKU'̔$8::͌giJ5 UhD X2*lKBTҕQgOnimAIQvKSZJ%nzN7pz2T?fƲ8ZR:}H)߲B"jۀex4q)$rO!p>m*e.-/ h(E$~wv̉MB%4߆V5 8(\w[\D@p:!yTh`O(HV߼ٍ4'S&I !% V gK_J:~LfݖⰢYNxzu\Rҋ5qZN*L_RJpI䟨 '@+pJ&[Mj&<%:#B !ÙRtMjŅX54m>ڰyrs끤+ 9uR_Œ@C'EDKƐ J&mJvWَF}%n!<|p%b3%`yP#I)9 #I𜱺vQ%nk< yIRצHGv]Lt͇ ORp!(#͟LfEV^ZSi.b#z2[P(cٴBrckRL .!n\}Sh]}4Vh@QW JA*OFRn۩JrDC09:ʱ赂A99{nY0_^ǍKd\BHZw~SD:lw, :0%@Ced:$VL}ߋrޜR_O(HqtuכϠҕ|kBЉjeT<$m+$ߠΞ6&ݙ ҃얦!k J8$#n"em~_e&qSK u6QڐSeCDշ GT$i 8S*0+HQLB|*۩U\\[r^Q+I$ѪR!]\Ji u!ԭjKpQ)X/12, ,zu%B >P25y[[ehO$L5u@BK*tΗN*t=J"ͻ-=aE A`8IX>d5wk,ⴜT2:?P1NVޕ MbLy {Ł,Jt G<C3骚Ջ ^/.jhk-k}`*y%OYFz9A7*^۲~ËGC1։^:H>ЗWGqSMW4@(!:S`'>h]0̉(Bb) L~RZ|$GI6VOoIr!)q־3qp/ߢNWo+JT;!/x숀03 >9:߸#n_J*hܕÎ eNNS}t흦wUle[OIPv+mn)8OYG~lȐsYaLvl:a*PG96q\+INF%XЄ 2q&LsWm ):09Z$S-<]{fZĆdTYWx=H+;¢qN-)nK$IZJS )q֞@ڑh2\Kl'[tHK7svmEI--HI- x"h_ڛe4qF\rPIRJE8ss(G;{q؉.aٵóbͭ$y 2cX_-RXlDc .گݟg\vZ)[T$% e԰ c l~ؼpE0C]l)2 .8>"J{ 78pѾ[VLdxx _Gqހ%vf⌫jYrܯPm-$2<>?eϡКXf1_es<#?)rgrxc#FЇa=Tɕ6YiVU5#tA~cϜ?mMα-iPG4 +m[ٴ䖦F_5al) :k{GAƋe!RD) )jJ.!C>QGiNt۷cn{Wf鬍BCiLܥ$y6X耄:K67<[=8u')K8΁eFwJ⢸א!EՀOm'8m,yU0w-YCKι9oˈI6$#8 RO^ʼX͵*8O[..+(P!O R(󕮽Rm-|J F$j[X#Pt%ˉ$-OګiĜu=]=#o%㆖B[ϪrEu@acm*㗠""fG/2sQ=s.Vmkw]EȪLqɲ\  V}2y&uMkt[VWWF9=!})((tul MO .;Ř )KXp2<{:Չf'YW$y!+Ѳ\R\RxLfm9 hfűܕj:3 Qi2).%ڿhz G! ,\V;洡IVT{ʁ=4ڧb6).<|Llŕ%I=8z>PvҸulLnnFmkRs'VU:QfJC,,QT% Gױ.ą[On)мfCrTNRCG'̅TկO!IjLW"Ĕ8߰JN銄(L\Z cySt)О-ۙRd0a8M#W~N{>yذ!KΩA?ucB:?#WPR3H|0% EM$${Qߦo=58[Su)ۖ7%2 `/*"ɕ;kJMaY҆Vn|D{ +è-èݰ,65e06?6q|J#=o ]MBqb|RRkO% dd]lߚuo-.xj#g`-E9Tp,U%9 BG )it%9ӀB$5[atnsbꞬILÎ$9' X 4ql~Wp̑jc\}T2O8uMtAolL/|؇ !?3vo~E߉[VkCm;A{j^{r!+}_ͷV=;VgF=c~Z|G;9{p ]sD GС,g8B(ջtb9EG8S 9Ǧ3~;"()%xdw 8Q)Mbצ( S ZB^spYFf"D\h{kp8A tRH VƘFʚǪ׼, x*`5JH'iA;krI~"-s. ;EL=6*jXZb Q2e$'<ޡN4|!Ժ=$˧YC@'7Hݳ|PEl yY:μ֘9?&קG0<ֺk}zSdݍg|WKhqY*P9qg|5S&#.XZ峐Lj۵ЗRZ$}vҿ974 }^;gN: iƫu7TT@b8h堏 16[.#RVJA`&ZvKpEuHZBA>sڐR?gAI9c'>]z10sh"fS[ovJ<> :$|FJ*_S 8 }x y鉁J 6Q(QPKP*$rV' 8ҙ7pR%"'_ AQ5K-&kQΧppVDbqP!I_tz}pg72L[ ݮaLL V{IJ2sa϶mMq_,BÉ8 @-?ع>7ĝ8qKu F5'q6%e.8hqyPi-LL|9٫}[9)K [ 0~T|@ǡ5+pUS= Xm`zrAo~-<=10s&Gb39cCS 3o6br7/IѺufc GGYz0\>oMI" [vQxAx$`P ku'7?e_kM}}kͦ޺c}<>>l?w/!`o6?]'uV]!nOبl] * Muw3V9^9#=>SO&;Gi'sx~'?kmM?lvvk|˩]tգ?]O+uMuw3V?ٟ뵐 ;qMuw3Vc?kSO&;Gg7nvׇ7nv̺G 'Q> ufmN#޻pvͻݯ~0+xa I:zJn4W4_LE8ϥ6vݸ_3%紧4״";n嶥A$3#UUDp XqlvwkAd_k]M{MuwfZ>#ݸݙ^SO&;Gۋٟ뵉pYMuw3Vۋݟ?Ev?MM>lz~SO&;GC7f''q3vgɮNj_k4~ۇٟ&Muw3VO@lo~vgy7 fzy5IZ>߸ݚP|~_ݙ_1i]'s5hslR0k|}7ǧV̚y5IZ>ٟy]4k}=6 6??$99r?MM_.i]'s5h}|T;KZa_S?'KJW2?s_1ji]'s5h|?scyvsc|]tգ_6")n*=2y?~7ŽmlEnFqZl$!@;>:Z_t<Mu555&jjjh&jjjh&jjjh&jjjh&jjjh&jjjh&jjjh&jjk̏xu3c-ǎ g14=s^w4GS )!喏B zkJ3kTƲhӖ%:?l -PRQ1x(*YssgXL`>E%ea{$x,`GеiMQ10#u +6|5Q\8.cOQjSDcTAZ?N( C阼ai9>ɧZv|bU5-# yv1agދus>۷}dǺӥG]y+C<H\^][U)'qT6dz vxhxJ:Q(1Rn\Y%O(]_:,)GH@5\yΏϔ^зR ΪJ(ʀƞv ڍ12(RBR#E H,!=~|4*jܕ31:Oy*\&-r:ͺ:F̽T{\U4kwmL*]=E_u$F: ^PCܒRGF5x?.uYQW83b#{Э~VXn*,KSݪKp-D]̖<Mją?a/ƫ3_){-?Ŀ&j ޟM)]XSn7+ ׫c{OS '6ߚFqz? zTMQKMMM] Hm *Q3 98[ hH[x /qTG|;ΗةDX{yEq|Y>JI>odGYbE)D.E^Qu@|ڬ*?]WV ʶ)}un}u↱zSA qס: qC[|NuT z΀r]Zfr;:?~ #Y)$y+u[k /}TFZH鲛s8NFiD CP#G&%y/[Eb[*!k|DU KjmIRRa_jgӽ4n7vfIb#+_AֻE9L9A 8 jkI>^q5554SSSA5554SSSA5554SSSA5554SSSA5554SSSA5554SSSA5554SSSA5554SSSA5554SSSA5554SSSA5554SSSA5554SSS@~?'FTx9jFz=gqz΂޸mc%R-6Rpu<*TY&]>d\$,Φ~TrմD%~L-S+N%дOpuZxE~ZiQ%pAOFՓ֝6gEyO?ڒ8{΄wkj=TˤWL>-xQk8^>?q3@toH?kJ5o> $j=M@B ;h buip$iٽ?PABCgY :iѫ1s=sq~@A@;Z)ם)BFTo|kku{b:B-3LS5rL9QI:ssVvLṋCQƳsنx/ItzpGZ,WQ*Mw8II+Piԓ)簁(~ʮᠶ{u+2.dQz!o4G'n'c|>yS<$'zu&b`qUrī:^ZZJrNׯs;8=x:׺SSSA5554SSSA5554SSSA5554SSSA5554SSSA5554SSSA5554SSSA5554SSSA5554SSSA5554SSSA5554SSSA5554SSSA5554SSSA5554^MMD5GEZbn?jjkMMmqi _SSAϾu54X䬦&jjjh&jjjh&jjjh&jjjh&jjjh&jjjh&jjjh?vdr-plugin-live-3.1.3/live/themes/orange-blue/img/zap.png000066400000000000000000000015511414414333500232460ustar00rootroot00000000000000PNG  IHDRasBIT|d pHYsBtEXtSoftwarewww.inkscape.org<IDAT8mSKhQ=y4&iVӴI[*hW[ƅBB .jqQA7n\H XP mSS15id& ml.{9^wgH%o g6,675}!p xs|z]U֍UQUAQs|gW+K%*iQ:E?h=|y~3%U["WNG3I+_M,8muͮ*}:pv2pد04QVb{{R&j]6`Y%[Tux!{zv2 nb%)3G5!%0 kTU89 _3x@J |}hX_?!=Yݭ{u$;9 ?$`ΩaNT4^Le JAn7,A^@W ?ރPrpΜG:m&jngc[ΦlK2c;O&Fr|O!Ɍ  "Vᛢ50b$޾z7nţ(*c Z@IENDB`vdr-plugin-live-3.1.3/live/themes/redwine/000077500000000000000000000000001414414333500204255ustar00rootroot00000000000000vdr-plugin-live-3.1.3/live/themes/redwine/css/000077500000000000000000000000001414414333500212155ustar00rootroot00000000000000vdr-plugin-live-3.1.3/live/themes/redwine/css/theme.css000066400000000000000000000113171414414333500230340ustar00rootroot00000000000000/* ###################### # Globals ###################### */ input { border: 1px solid #963B5F; } select { border: 1px solid #963B5F; } /* ###################### # Tooltip style for hints ###################### */ .hint-tip { color: white; } .hint-tip .hint-tip-top { background: transparent url(/img/rounded-box-redwine-tl.png) no-repeat 0px 0px; } .hint-tip .hint-tip-top .hint-tip-c { background: transparent url(/img/rounded-box-redwine-tr.png) no-repeat right 0px; } .hint-tip .hint-tip-bdy { background: transparent url(/img/rounded-box-redwine-ml.png) repeat-y 0px 0px; } .hint-tip .hint-tip-bdy .hint-tip-c { background: transparent url(/img/rounded-box-redwine-mr.png) repeat-y right 0px; } .hint-tip .hint-tip-bot { background: transparent url(/img/rounded-box-redwine-bl.png) no-repeat 0px 0px; } .hint-tip .hint-tip-bot .hint-tip-c { background: transparent url(/img/rounded-box-redwine-br.png) no-repeat right 0px; } /* ############################## # Tooltip style for epg infos ############################## */ .domTTepg div.epg_content { background: white url(../img/bg_tools.png) top left repeat-y; } /* ####################### # Menue ####################### */ div.menu { background: #000057 url(../img/menu_line_bg.png) repeat-x; color: #FFE9FA; } div#pagemenu { background: #FFFFFF url(../img/bg_line.png) top repeat-x; } div#pagemenu div { background: #FFFFFF url(../img/bg_line_top.png) bottom repeat-x; } div#pagemenu div div { background: #FFE9FA; border-top: 1px solid #DA8DA8; border-bottom: 1px solid #DA8DA8; } div#pagemenu a.active { color: #984E79; } div#pagemenu span.sep { color: #DA8DA8; } div#pagemenu span.active { color: #984E79; } /* ####################### # Info Box (near logo) ####################### */ div#infobox { border: 1px solid #DA8DA8; } div#infobox div.st_header { background: #FFE9FA; border-bottom: 1px solid #DA8DA8; } div#infobox div.st_content { background: white url(../img/bg_line_top.png) top left repeat-x; } div#infobox div.st_controls div.st_update { border-right: 1px solid #DA8DA8; } /* ####################### # Head Box ####################### */ div.head_box_l { background-image: url(../img/bg_header_l.png); } div.head_box_m { background-image: url(../img/bg_header_h.png); } div.head_box_r { background-image: url(../img/bg_header_r.png); } button.smallbutton { background-image: url(../img/button_blue.png); } button.green { background-image: url(../img/button_green.png); } button.red { background-image: url(../img/button_red.png); } button.blue { background-image: url(../img/button_blue.png); } /* ################ # Event ################ */ div.station div { background: url(../img/bg_box_l.png) top left no-repeat; } div.station div div { background: url(../img/bg_box_r.png) top right no-repeat; } div.station div div div { background: url(../img/bg_box_h.png) repeat-x; } div.content { background: white url(../img/bg_tools.png) top left repeat-y; } div.__progress { border: 1px solid #DA8DA8; } div.__progress div.__elapsed { background-color: #FFE9FA; } /* ############################## # Blue Background Thingy ############################## */ div.boxheader { background: url(../img/bg_box_l.png) top left no-repeat; } div.boxheader div { background: url(../img/bg_box_r.png) top right no-repeat; } div.boxheader div div { background: url(../img/bg_box_h.png) repeat-x; } /* ############################## # Recordings ############################## */ div.recording_item { background: url(../img/bg_line.png) bottom repeat-x; border-bottom: 1px solid #DA8DA8; } /* ############################## # Remote Control Keypad ############################## */ div.screenshot { background-image: url(../img/tv.jpg); } /* ############################## # Edit Tables ############################## */ table.formular tr td { background: url(../img/bg_line.png) bottom repeat-x; border-bottom: 1px solid #DA8DA8; } /* ############################## # Search results ############################## */ table.listing tr td.head { background: #963B5F; } table.listing tr td { background: url(../img/bg_line.png) bottom repeat-x; border-bottom: 1px solid #DA8DA8; } /* ############################## # About box ############################## */ .about_box div.about_content div.about_head { background: #FFFFFF url(../img/bg_line.png) top repeat-x; } .about_box div.about_content div.about_head div { background: #FFFFFF url(../img/bg_line_top.png) bottom repeat-x; } .about_box div.about_content div.about_head div div { background: #FFE9FA; border-top: 1px solid #DA8DA8; border-bottom: 1px solid #DA8DA8; } vdr-plugin-live-3.1.3/live/themes/redwine/img/000077500000000000000000000000001414414333500212015ustar00rootroot00000000000000vdr-plugin-live-3.1.3/live/themes/redwine/img/bg_box_h.png000066400000000000000000000002741414414333500234610ustar00rootroot00000000000000PNG  IHDRʚ%bKGD pHYs  tIME1>~IIDAT= DNR(r>0|6ێDr>3|ctA 1&-K_1!&IENDB`vdr-plugin-live-3.1.3/live/themes/redwine/img/bg_box_l.png000066400000000000000000000003251414414333500234620ustar00rootroot00000000000000PNG  IHDRo2bKGD pHYs  tIME 'RbIDATuл 0 EXL 4A H Z@IENDB`vdr-plugin-live-3.1.3/live/themes/veltliner/000077500000000000000000000000001414414333500207745ustar00rootroot00000000000000vdr-plugin-live-3.1.3/live/themes/veltliner/css/000077500000000000000000000000001414414333500215645ustar00rootroot00000000000000vdr-plugin-live-3.1.3/live/themes/veltliner/css/theme.css000066400000000000000000000131471414414333500234060ustar00rootroot00000000000000/* ############################## # Globals ############################## */ input { border: 1px solid #004F00; } select { border: 1px solid #E0F000; } /* ###################### # Tooltip style for hints ###################### */ .hint-tip { color: white; } .hint-tip .hint-tip-top { background: transparent url(/img/rounded-box-green-tl.png) no-repeat 0px 0px; } .hint-tip .hint-tip-top .hint-tip-c { background: transparent url(/img/rounded-box-green-tr.png) no-repeat right 0px; } .hint-tip .hint-tip-bdy { background: transparent url(/img/rounded-box-green-ml.png) repeat-y 0px 0px; } .hint-tip .hint-tip-bdy .hint-tip-c { background: transparent url(/img/rounded-box-green-mr.png) repeat-y right 0px; } .hint-tip .hint-tip-bot { background: transparent url(/img/rounded-box-green-bl.png) no-repeat 0px 0px; } .hint-tip .hint-tip-bot .hint-tip-c { background: transparent url(/img/rounded-box-green-br.png) no-repeat right 0px; } /* ############################## # Tooltip style for epg infos ############################## */ .info-win .info-win-top { background: transparent url(../img/info-win-t-l.png) no-repeat 0px 0px; } .info-win .info-win-top .info-win-c { background: transparent url(../img/info-win-t-r.png) no-repeat right 0px; } .info-win .info-win-top { color: white; } .info-win-c .info-win-t { color: #000; } /* ####################### # Menue ####################### */ div.menu { background: #000020 url(../img/menu_line_bg.png) repeat-x; color: #005E00; } div#pagemenu { background: #00F0F0 url(../img/bg_line.png) top repeat-x; } div#pagemenu div { background: #00F0F0 url(../img/bg_line_top.png) bottom repeat-x; } div#pagemenu div div { background: #00AE00; border-top: 1px solid #006300; border-bottom: 1px solid #005E00; } div#pagemenu a.active { color: #E03000; } div#pagemenu span.sep { color: #001E00; } div#pagemenu span.active { color: #E03000; } /* ####################### # Info Box (near logo) ####################### */ div#infobox { border: 1px solid #005F00; } div#infobox div.st_header { background: #00AE00; border-bottom: 1px solid #006300; } div#infobox div.st_content { background: white url(../img/bg_line_top.png) top left repeat-x; } div#infobox div.st_controls div.st_update { border-right: 1px solid #009100; } /* ####################### # Head Box ####################### */ div.head_box_l { background-image: url(../img/bg_header_l.png); } div.head_box_m { background-image: url(../img/bg_header_h.png); } div.head_box_r { background-image: url(../img/bg_header_r.png); } button.smallbutton { background-image: url(../img/button_blue.png); } button.green { background-image: url(../img/button_green.png); } button.red { background-image: url(../img/button_red.png); } button.blue { background-image: url(../img/button_blue.png); } /* ################ # Event ################ */ div.station div { background: url(../img/bg_box_l.png) top left no-repeat; } div.station div div { background: url(../img/bg_box_r.png) top right no-repeat; } div.station div div div { background: url(../img/bg_box_h.png) repeat-x; } div.content { background: white url(../img/bg_tools.png) top left repeat-y; } div.__progress { border: 1px solid #006E00; } div.__progress div.__elapsed { background-color: #FFCC00; } /* ############# # Timers ############# */ table.timers tr td { background: url(../img/bg_line.png) bottom repeat-x; border-bottom: 1px solid #006E00; } table.timers tr.description td { background: #00AE00; } /* ############################## # Schedule ############################## */ table.schedule tr td.head { background: #00AE00; } table.schedule tr td { background: url(../img/bg_line.png) bottom repeat-x; border-bottom: 1px solid #006E00; } /* ############################## # Blue Background Thingy ############################## */ div.boxheader { background: url(../img/bg_box_l.png) top left no-repeat; } div.boxheader div { background: url(../img/bg_box_r.png) top right no-repeat; } div.boxheader div div { background: url(../img/bg_box_h.png) repeat-x; } /* ############################## # Recordings ############################## */ div.recording_item { background: url(../img/bg_line.png) bottom repeat-x; border-bottom: 1px solid #006E00; } /* ############################## # Remote Control Keypad ############################## */ div.screenshot { background-image: url(../img/tv.jpg); } /* ############################## # Edit Tables ############################## */ table.edit tr td { background: url(../img/bg_line.png) bottom repeat-x; border-bottom: 1px solid #006E00; } /* ############################## # Search results ############################## */ table.searchresults tr td.head { background: #00AE00; } table.searchresults tr td { background: url(../img/bg_line.png) bottom repeat-x; border-bottom: 1px solid #006E00; } /* ############################## # Infowin support styles for EPG-Boxes ############################## */ .info-win div.epg_content { background: transparent url(../img/bg_tools.png) top left repeat-y; } /* ############################## # About box ############################## */ .about_box div.about_content div.about_head { background: #F0F0F0 url(../img/bg_line.png) top repeat-x; } .about_box div.about_content div.about_head div { background: #F0F0F0 url(../img/bg_line_top.png) bottom repeat-x; } .about_box div.about_content div.about_head div div { background: #009900; border-top: 1px solid #006E00; border-bottom: 1px solid #006E00; } vdr-plugin-live-3.1.3/live/themes/veltliner/img/000077500000000000000000000000001414414333500215505ustar00rootroot00000000000000vdr-plugin-live-3.1.3/live/themes/veltliner/img/bg_box_h.png000066400000000000000000000001771414414333500240320ustar00rootroot00000000000000PNG  IHDREXFIDATxMDZ @ 胚>$L0l* EZ_[orZ!rhPyO!0!/IENDB`vdr-plugin-live-3.1.3/live/themes/veltliner/img/bg_box_h1.png000066400000000000000000000002561414414333500241110ustar00rootroot00000000000000PNG  IHDREX pHYs  tIMEMIDATx-Ʊ 0 D!؁ } Jzo]69-8 DPJWIw3o0ڮIENDB`vdr-plugin-live-3.1.3/live/themes/veltliner/img/bg_box_l.png000066400000000000000000000002631414414333500240320ustar00rootroot00000000000000PNG  IHDRA betRNShAhIDATx} @@E&ЋV Ћ&88X q~32N$ɳ+kF]-HcvyYn ݻ"8г-K=p:2)LIENDB`vdr-plugin-live-3.1.3/live/themes/veltliner/img/bg_box_r.png000066400000000000000000000002621414414333500240370ustar00rootroot00000000000000PNG  IHDRA betRNShAgIDATx}=@@Fn"q0HSKPر(NCL3g<(_ e=CJr$[cl|.CiŒ͂7BIENDB`vdr-plugin-live-3.1.3/live/themes/veltliner/img/bg_header_h.png000066400000000000000000000002271414414333500244660ustar00rootroot00000000000000PNG  IHDRbtRNShALIDATxM @؇5 GÇέ}]+>i9C[(2׺yŋX}x:<d%I'"iBgN"Dx}0 Tu-pOoBb 3vr!-ݜ-O((j./~cǕ 2>k~_@Hǝ?IENDB`vdr-plugin-live-3.1.3/live/themes/veltliner/img/bg_header_r.png000066400000000000000000000003351414414333500245000ustar00rootroot00000000000000PNG  IHDRf3tRNShAIDATx}1 @El!U`MlX{! IENDB`vdr-plugin-live-3.1.3/live/themes/veltliner/img/bg_tools.png000066400000000000000000000002041414414333500240620ustar00rootroot00000000000000PNG  IHDR!*PtRNS}?[ pHYs  $IDATxc|%T5XXXXXRIENDB`vdr-plugin-live-3.1.3/live/themes/veltliner/img/button_blue.png000066400000000000000000000007041414414333500246010ustar00rootroot00000000000000PNG  IHDRd{bKGD pHYs  tIME 4!$*tEXtCommentCreated with The GIMPd%n(IDATX=JCAFDH#DI=DI {݀,,R  X0wESYwcSUyzN K1f{i^oNw?ZPd8ĵ/644,\($MFё mFqP"C0!&0!iT!0P 22#y2(dن&Ą&_ qY v\]DpTu$?j췺7&V "$1[T5<']MG/LgS Kqv+͵m~Twz IENDB`vdr-plugin-live-3.1.3/live/themes/veltliner/img/info-win-t-l.png000066400000000000000000000020741414414333500245010ustar00rootroot00000000000000PNG  IHDR%4IDATxݽ$U~zvM oKlA440_dY0kXvϠٚ*y8S[U|ֻ4fy`>wߜ=i.#r9m>7'J࿴8 L}/vns3(D8mp>gS @;ZfPx:% ˙p@3Oas35N`7}?B3q]>wjos3d{O%OXدtIV9Vs OpfK@ԁa=Ϋ"G3)Pau(J(Yj@=^`G s llp@3#Jǀ xjXI^7ϱuǀvxϡ3e񫬷;9`zV=XĽ{@VIۿT`h}'HXccs=כkn`nm:|4I_UX8`ak2IENDB`vdr-plugin-live-3.1.3/live/themes/veltliner/img/logo.png000066400000000000000000000446601414414333500232300ustar00rootroot00000000000000PNG  IHDRFgAMA abKGD pHYs B(xtIME-6 IDATxԽwE?ͻ,91 T *"agά(NE=Āb%ywvLz~LؙztW?SO=fD'/D2xym>.Iӄ`}du7@'5?ڜC73Y m{kTy>]xM~b̍ Daݍ-oÄ (+ EnG8'|44F2d'87WŠKlW/"SQS޺}_|v`~s7UEHQr<,kPMd1ljUODB3?}¨ZQA"FeRa06ͯf߾LU9-o85.6>Ng>-3ŝ#AU%E^!iDA0-ܖ!,%3ɳ%M"g2^if>L]=Mg;@[z_Ikp;W͙hd@_c⚯{^Qn 35д _Ke}EF@IĪDwMD_^W //L 4%wh fHe{y|4&{f Ky MlZސԫfwjz6[U\}?!۪Z e7p>+bMo>ȥ1y d6XXPZC./uHExU9 R^mijӵe3]݃Jvn "a~Ա 8 +hnԭjT$Xvi+V`M3mH)r[g&X%^0xWPRC l]Zm(ɹ :b IXxpC7 #o|SFw=3ŇQSA M[EKL@y8{[Ai"&ǘ=;%4S\@ lnSa _h@aX{_MOGl11<*W\Ǚ$F4JA'|!,=l}^='eYvva3k8  9fĖR{{zsyBt"06s -tY~m G@ :gL mokxoSڙ~`)^1 }!̒U;I2Bac`N, [i:Gp.g/1 ^$9A?gƾ}j*U RXYKt  d(;)I͠J(>5ݡIw=}[m5?^t~#\QzϪ7焖E`3=ƶ5Z' 3+w3y :+e2ւj(v0 4`6Zp,#POW >t3gL>NyWb:;|gcH! p;'\ӈpu#κmA Uiľ/n 4"\ӈP0sL뮻 o BU V5ƎGYǕ#0+ꁠOqYƾz XxqfyDꛢ>ٳqc{\ 0B4,g>8\f(OH1 L+^tݿjGQ7FgplUϢaW+Du V˜ˋ8 @XYfk՞Xj_njWSr؄sT?f>Km(*@5-j6!R;o4}`M"O/OT&f;:Bp@S]pM!4ՅDm}ꂦ`J2.^qÍB4vlmBB{˙n-܅Jc|6{J+YNBU!NtݥKpPUԮ8բT??g( ]Kºu8.Z mͯW*e+r(# W<SDNvF)~sNUSU K1Ftp =u 4E/N/ܕ-^EcaE}cՒ*&SUI|89d 6`P3$8(+U]B{fš:9q6bd!t?l|>&w5C͐<&f*!A0֬E}tuf+%LPrî}8P]pYJEx{}' ruwMaMD$9ǼJx,49XʐE~Zxc,3cd7ś~]G9I9%QTz۝9SX:6~ٍt|%?ݺ@ 5;`d'Qo!?Vm8&s8J K|(<3\M)|kxue2 yw^@Dذa|h{ ѐi ]7O(ǭ->ӎFW0"@Qy[g<%B'^ʜ&~yMWhb3Hf4y>I~!6|U:Gg-b1šN=T%KN(W~qܓ.3XvS߿&Nf*M­i5;A 5+0).[=KYι/IK8pAgt)IyΚvsy-h*vյF6eͽedHc0숑x;1j($Ž>8{r%ӷ,|%Y{,6e-"0{U!T+}I$GPud{hW7u+6jBwJY_K'0/26Ԫ}A6]u.c—khvFfZmS^%vE1l=P\ g $^{| yyyzmv9z]-)jGAIÍK*YcH2 k_ܭpw]?W!bP M=ՙč{ZvɔuH$ȑG#b|23G43ƈ@UU垚MӜ_"NW/n0lAkBBQ Jo*rvGl[+?,HؗĠuT/k+è[Vt?I2ȷT\oȌA1d`Wz"b оc]XbE:[ @۳W]yN;Lg$SCf &(UV#7n).j+S TA9VzeAB\kpus"\gK#Q5aM ߳b9 MU6ml&1jiK y:08T'dEiH]۶aEG{Unt&XrnԮk.LbUg+r}H5n:~!BcUV>HY ~ )]PaK~!\lg7+`}+.r-/wzIn9:f:􌊸.E\pu (qA@0,OX|nhrkxo dh؜ڕ2 JbgwG8L;힦)4r@jmz9 JLjcC dT͢^@b 5Ni+᠍;M26wYߠ#Zk@0 `-w"# $I%2BE:w(]L\ &"0r&x"&q.+^VӨ73?"]1#/AY' ӂ0& 8^UيEn#I=M݄0L4~F1,]lt͉;Z>85Fٰfޮgj6֙Kܦߛ0A05AF"b^7T]_Z_ްaGC[gXpM2'KY>8PpnͫxW3NF3fr)1{'OF 4 gYSl Csޏ(6| ?gq.=RM:9T' [|2+'mD|0Yn2$3gݶN˰tbi]1@u?s ШTz vz 'pʺuoO?V} J>ڝǚ )= Fr$YnkHy#,ye'ED&xqP\aJIn:3n["%"FH$93`RwKε,kv2a #^C<𳛖9 '# 5j%j4jO9 d,5!;WMADSB4򇏌+EΚiq ֭CÏ4! FVTEZ*B`gw vwm2u2ntESE1EM CqEkK Y0b!' q|PٝcO(/ڎ[~a f/0CV3RC~z+k zm]sEo=po#ɗ#k8dsYa>c 3@ L]C/πllTt]@ʦHcQO׍;ˊ ƒ tw~rG ĨK $;Į)P#8M]So;{#l@s#E627hZJfKM JcFGLc`fq{օS_Ӓ!ӄa49),ABȲml#Z@X4_~.Mev` IDATQaA+"=y raWK+%!4"h1Ϲ +7la!b3hĠ3ŰsU*}8Ɩwf= [#4eQC.Iʹw)yb-ƞ>,Iiv6OߛEģ@ yOK7-+E ԹVZw% ؆Ԙj_Zm9he)~MO{j+ ")n ń|K6ndG= A}2pе; 9!0p tX,] e~$Kpc8wW ʡ=A%7}!GhOw[Q٥,m'0l.` ?{YqJIC9$9W搩n#=A) ’e΀_q̞3## GƮnxi,,G z.į$+IԪobp7H2E~-ȑ!ed2^ Y%ng&A$(YP0,9h3cu$0(RH&?Ò% v¶m۠:TԵeh0H"a"+C pY<W Wt苮X\i9.kϙ2͍,OdHv N* gq7^\Hyn}K86TJ1ͪEё=DJ4'\P @ 6'\Z [w`[Cy<@gH]M]7Omن,0Cѫ~5؏!U`pN4W߮P>"옇k}u-YX2_d]dHCLڋeC2D‘StHu' }[Gf;Ι@&Dm|X33mje}]*̾#{S*nĮOTb٪4$9džcAζu #0JζoiCF}z[QՕm 0sv *^j% V;, ȊLkmY6juSHb=e@!gW.G9 '2ro,/|e+)t 9DŽcB7#rm^hT[r-%=\;WzYo#b؈60QZ@6 +[)4 b5|e'zQVkFX33Y%W4;;L. al{~y ˛̍SIrajwUhg5g~u)q: $z, Pu=ϩ@WHÁ^QU-| 3Ao{`F]\I /)5gўR:~ `>,qiXY @1o( ?yyC:&;mpn~C3pS. E!T/gG8 3POA. y=V`Um A# W/'ff.;#7;z^Sp+C9GP9fT)J*GBhWig{@W%e c V UJO@bdu M6`"w6?灳h\o]̢ZYg~f-[NFj/KgjF:!-qIÔjY|1cֈZusisLidkB`l[߾຃V6s[m¿\i8R2}6,y%N_߲:-WAAvQVqfaP5yc{3\YS|Dng-ƶn)vDްs](Lᣡ&M\WͿhAZ7&-ivk;tܸ+O; 4;ybd:X",(>fku?^7)M 7BB"FK.x>ah.>C@a;|Uqdj_5qd{K0jM|v{-xR2;N{3 ;;7Y2jtC{7nκV5yan f1'ӪLZOXrK!=ZJ+ aaya=î+^لa,N*q4oh%uJ>9ۜ_5!}dD9a8!ܦMAۭ?kc/t)\N3v%0 2*}y7wCfm(?V 2I9xiY?Լ&څsϔ.g9WyͅouvX Hybi_ ̵fJNc=vR߾}G9|6o)--LDx督\ZeQra h'&}qw׆% =Ėc1˚i[=9O;Q+JK !&d]h&͇r00r1(..?͛[`sT -Mٴ_qCG^ !j2X`Qːa!Rk?C7/`ܳWs`j`uN) "D;wz|o`á7!ʶM4$IX&N*A>qy_-JœY^&Azב#ۀ'ɦǐrp31;'5:\&Ϥ7f\^`L.3XպlSw} &KPbE7C:{f=}symbĎ^ [p~KCŒ.hY8"*,D^]}vbD˕l<zqB^?۽:/l 3꾶7!@1T;oN0|ty5="00:؍L701!q/o!|mv' %V4cǼەtU3;̚?e) tr?Je?9L*Fm˲ zuq(; +PtjЈOgd 'D@g s1:U– 0h+fi]"V6ۍ] h`9x`1;vD| v̸B7k-"GtpwȌ2BDaq~Mu4>q=& IqF }6bBS–(bc}7 )l\ t<Ƥ1 Q5^K@KA2@6V(`zCCEJgC!Ӷ "L\R_Sɠ]/i*0|n : L*!ۮ!gDƄ泡89i:C̻?8³IfUAYp1dVѸ3rDsQ_Vg- 2K#s{H96d~=L^Wǯn|=sл`r8P`vF8K%|?Z?;c#a PgiZr,$mKgQ:I00йDؽpƝaZ󹘮鏵@X< JC52􊒓lA?E1|",߹86_LjѢʳ#cFµiV^ҕ2ʒJ Ek[ ?/K89zđς  3Ox88%K%xo+K`ѡi4Pxh[4I¢kE:倷_^ <,zƺ([344le  BtpFj5LV ir>1Gf7GO<]trp*ד4B xE}漌I^LDa|gI^\lٵUUUSlNY"(I1?g;+i Iffhc 1S0eHvAD#3e0鷾[=.S h?Ì!m4 ԧJokv h˵2Hĥ9K/ݹ 9fϙ܂"YYmO8Bmm `<Ӹ;PPP^{wqF~|fl$ &Df[`H}z! ؆20 v}̈́ط<@)BV\=2!gHm93,` f  J|=Q"I~ I<+9r qI~Ixhn3S1\;6!;l#'gsrgsۛ *!>rEz-cl>I \JIjC[ƻ7UuRn/-U n1VL֝HaI8gnBv$R Qڞ4:p?oCj*v` (q+̘1NNe^v{ UQka^up ' ҥ GYfӇq##%nZmxVo7_~mLolUa%@1[J6[46*+d%z5q8h{MXCpw T@pRtVŖ!:cH !!;}sǽlHHB$3=3gvy|;@&tC Bv7DOl^I;ѮDP\TʟKK`$ ԦU(-_X˗s c@hؚ J;T?SA\ka-Rhj˜<*<&_G[cP_:zxIi2(g{!Kx!|`T"u?HS1K_(Ax '%icT$P+6mKX-y(46ZGMo_hF IDAT1q"9MVE3(W3Fu}6ubs!["Rk5yhnnƝ7^;vh;?[֊ pN|aum|Aŏ./oCf+1 e3졗{}IQ%XUu8A$M}[`20tڢ.8dNƭ7G?@H 1u3囙W_J6WD"&"KsG墧G F&?HBlJ!]ĒwcOW =x* D@\"ڏ]gh;ڃ{ H 7/9ܼA~q/JhDݣ8|p`_ UejLɐA]dtq=@ PC:t޺}t}ƛ+9]B7 1aڻ-d}!췐ƃ-q7X>}'Q}Z!DFϺp]Dv%߲GIďQÈ܊kΎ()-qPJ*: du!L(tBF/3M7Et8T9GF@qȑ8 6txL6H<DDK! b39Z)1 3X֖ n ~)ea|pZ$sFX6ãxiۦAݱ^[n Ȱ<PPPpwn ?BɠL Ʊ@s;L_&^)=3d79781smϯZ[pEqb,E=hhI}:{f+OLf)jF&A'AMc dD8f\i_ﶡg0 Rap:dڔZhc?9dCʓsS  C `!f%>ʡhH|.$#a#0%`R!m[~gigɘUBzn|"p0g8oҫ!miޮs7( B81Mg|ubdZϽmOM7RƲ@Ɏg\k~L"7Br>\asu* )Lfg`76/qH+}"I5aLHLhaQW@-ׇ/cNWK: ~8dȓ4}:! 3+pf J0%3I YZ 2b#wF.lc︉h 5ksCP#:Fip觰}wĵ@|*jb4G!! ݓwBX:(Bڄ^Sc vAV'Ӏ{y!o&eXhQN>sFPLuHVјV#A9aQ01BߟMJ5: 2d4{%ES@6ΝMIIENDB`vdr-plugin-live-3.1.3/live/themes/veltliner/img/logo_login.png000066400000000000000000001236711414414333500244200ustar00rootroot00000000000000PNG  IHDR,wDMcgAMA abKGD pHYs B(xtIME,!]] IDATx}wTENgqɌ0s^u +5kDPuEdA{~3À~ϯy u֭>\bp~ }t{g$I.Ѝ6f+ oGB# D7( ZucsnܷÏT^yj wTO g[\o!8 |!vN`1k]==^m_N-^_ɡP|EjJut9=$G;V0Ee1B_4L0Ui=dO(>Ks(Gr#iltjPbOMseQyf/2 c}\38v 1$ dy_ab D+hw#$JFD+l;"s03Z퐚4\!9@N|͵Qy ,q_$*V:B;* .ё J 4 !W 0x%S*2 |cG ^JE@ehpUy4ҫp0T3OSHN# dt@m҆!9%PJ1o<={C>GuT$ )yYKN>]J*?&T)px!W brէ)./NnNJ-)>ۚ0a\9ZsS㝝Tpd)ptՋ%36e$ j%K 9|ӇhV9 Erا@=,02#"0H M׶u/o&_߲Gg W :kĨz; (p#'}yI\5+Žl<@9N=oUZ\wŨ|kĘc陵c[vNs'Epͅ׺*Oyf4uW?޴94%όۈ `e)i<=mU f|hRND>8ì˩H1K&XZL8*9Ju5Ms3f15><%j`R;-!:Q@ 薈vBf%wB[TeIXy-yeܼ׼z+5n8$j65]ݹ"Ǡ qM\Ыu8wFFͩvk9]|yF.k,#C8陓ٳJw;Z!EU ӫ]BI 8fY']ׯ>Mq/|N$C!Gpus~l̡N-E&;ڛpgxAI b.bchWMmgq"{sPk ERpC%O⿅kp+G֤Sv[Wt*T iŨ8">ngxㄐ$n`lڽ⭚ b 5kq<h *X3g!wy̓>~^W$}Y\ʍW'4H9K ?8 PNn?(%!4!5UQ4x+pf،Od&]܋tj?nEjU{k*P@Fpu;@uʈ,Υ!oGhAJ w*Ŭg sL`FCu{& OjG. 9 N~GE[=X,]C*˲p.W-] %E]*Ň<;/9~EVBxY)j X-)ss&3+c#xERI(o I^r\6R\zE <0]g#vЀPR^_zh9 F4|Z:6mނj#~og9A.rdW-ӶC% wt+J3_WxW:e wʳù3Bjk,/]?crB)+p@U ΀j-gG^;!6QuJ}aOUrɼPic.װS%Om:3J9/nԙ@1cKMhZ>Zg,`*60 -"wc J q;qz :;U+ zºi/_QIMCEan` 7DjnuMK,`ۗ֟ =372ŕxŔ# νbк 6ß؎b7P1 qH!oӼ.V`⺉(&Pp˴Z.qξ?F*J ssfrZeY7tc]8|[|O_}Ɇa2sctغ7bA*bHU 2n.~q9- Nnmii H,F00G@̠j{ r\8J',z w{Q!p0D|* QLj<]q,2~,/3o A {V.NEMrn߳o= E8Q+.l;2E&TKE@q`Ù?{z+ EHcˎZ4jh?j݃cwʼnG@04E(2xF)I>7 PZ nS!$/1mU{{64YY!כMVD`X_J "HuԼ&B%b270 "vm#hF_7-ef*rv3^☩}Eh T {E>w"*QR?Ppz!h[|,.gѹ);D9mOa뷰guHWi.+ Bb>C==B91%q S3E/-~$}iTݮ۾k/Lr9%EGxUpZwвY"aZ,=PpOqĭc N3Za0jf=RC *Ѓ Hƕp:jKdX|v3a4d}sbu$'WkӺgY/yJJ2*\''uGKx L}o+ [hۚB)?5i"I+!x:8a p;L6Փ۶=>GVb" 'ǶrJ#(xt~p83Y@af~8a3~z{,ަ8Y8|V/ -'j4D:rrm!0ok}db% 5ϴBIdtq Q]]5+u7_*\dOu9k{ Xs>lE4.[{Նɇn)P67DoM*z ~Xym7aq&^#ɂߔ=s\N8.9 TEO۷i @SZ[BWR s M IƜT8ӂ2><a("!LבD1  ݘq^fd@P3~Q<(+6vIu (_5{Ä́րn2Y~<"Ng5h׎φD-o %iƇ2oBėP4~EkgQp9[b.$U 4ɡ"CBHvj\5hn_HlN; DVU|4oȐdR[X!2 AxXy<oGLԵ p@ӒKr-FG@F( QjnQ)GZi2.~}fs9c#ާט!BX&.qYI2(IYT@=>ljjgfJcԾx`O+8gHѼ- 4g B3!ܶ)z3g^9 dCo9 $ ɮՁ~ExW4C~(+!T@8Le >oذ~EE4iҬ>7?Kph/ý \Sx@rJсW&8DGŇ2KuQGsvL4"R$p(RďGv&J5RbH}(RGzꅥKAQ#(8?,Y Yrʸ Q?OΑO1b[+5_TGԺhBx7wu1 SQ$@Ɋo($ Jeghb*{!~It:kGD "- -թϓEjz}=ѬCpCDrɐDB eÑpmht:Rms)ʯLs2ŎȪnD;0u oo Q!g}o @ߝuF9y Ǩftʒ/ԅu"KtB}&tQ'GNgڥN8rY)DC=, ]~ p6Acjt5,+ew@@IxC%ׯ EuZ(p.8h Dј-YUq9=E;n9E4Z]CV;0}nDiͨU'+qO#m#tɳ _ d-!r8('gR8 cA4lJnH+""U< 5P]7Z6-n.%;ߞ:akPbo/]m?X`B7T7Ma:l`;OMw+vj%?ԝ!4VNX=@k ?#iF`Wb:qԥCʷKB_MrYhYN黰K!H:" R<+R}h-\FFYB*pC}Z" gǶ{}XO8bQ6<5 CӀǂUX*WHXC(0`Gճ<,{J"D]Z wW49if3jݢŦ?5z j W!8 ⲛ} / g /z㒍e` DTS@x+C~0xJ쐠^r"Web0?zGo=X-&rka ]Q$YM.8%:gG^STX4Wʌe|j35!od>-H~)m/ݝ6@4y5izv)wldQ=Bb$WzLPDgǽ?9+}Rqa¢tA9o᧖?K|_lP(ZZ,fe_^$N@1! }0uo*;OΜʈ(#uAF@ÌF@pE|ʯ؃d=iwrS-aoњ |kZ-z:oH~zY?' … w}=ܸ( ٩~άcUB.onz@B䪓ܢ&,]Qn!$~Ȅզ^E9&yS,ͷ:^h_ksšz3}!lidO(MC0Sk>ԁcM;f ؙDolTJzI9h}s_EXV0%I|wdFq aQN_mtI?[N׷xR ja".w=#hN'xտv/y|7ygӺ53555OeYXt 8cdk֬y8rإ62O{t"3Y6@.mumDB(#;uoE[YOKE!X澸{}t<)ʙ'#[ z=__ƛNXomR&RI֪+:D3#G苻 9j`üOBabv9dG/By7݉}Ŵ췛A&%ÐepM{JKnod~׏7TU yv_v*NǵjV/\)p)%4\[`{0"g0AIeu&JO.V17dOY#/걂9 ;ڵ~j )MQw$g+H{m\L9Tamroy&Q0h}pQJ脰,֓O>Y?~Z#j!ry5 Ӄ{/ 3eʔSLysȒOn SÞJ?ƛR 8(Da%*h*1 v{܎j+93z+v|T;XqoKD DqDc##oAW ,gg1u+cR4aN7 +^A^=Xvoп_tM Ok߈ŋ+Ĕ)Sp饗m{~kϽ,ё@E}|6YNp8cKMA' (<!dŭg9catng{anxJG#Gk–h!8tͫ PW^! 6- KȘ\nN].! M# tQ7{ߡ B2+6c?୩owy*FaɒxGOOڵkyCCy1XPN~ qd\'4yCHK.0#h@pRwqH!0 ŀKsEk~~=4vZ?н!3vR2$,I@Ԛ-<`zVaR ` 1Lb2Yͧ"Hr9 , D YMcFs?> G -ѻG1|)H>,Y%%%ڎFLC$Jbx8÷TeUkrf*tR4 O P];ddpH$IFXIuIKΑ>Tkg7I;Q*hl&eym ѿv4usT, i6͖D`s&r/hcFG3ն(XPD2 ]ņexΝr 9 1j̞-`+n1il8 w(uaHnt;+`!D)WL+q~E#]ԔRr f A$@ dLHy! L 8OYP[U ɾbD#p1Xxnf:bFY9U{Xg|!0MS_y>[$HN#L&+'?+'RjѴk8kdD~]}>4*C1ߕrvM"9Q|f+A4ȋ 1-u쓔Þ _B/>El$<eӪ)/h2}䈉Dayz%řͯcl.f2YqGv{봭'Xb"1 |n]9OJ*)㫛烮yR;eg'Ye \W0:?CEf1 _-o! d$$Q@BG:d",؄E ?)KMIgqǴxcI;y& ]m@~/3g@h@@`h;Xׄ/6le4у +b?p`zII: XluW4C 5<aҰ9@TЄ~s{ ܜKMJJb҈Vqz? &"#[cM0 @.`$ zp<9y Z}dVuGzZKx,@h!Ʈ6)zOȅ07!Vt׎ 59ztјBR!=jZvYrY8(jԺAg8)sf]5Vq$UH-2,ظ/fpOD⋈(C s+=5FX4;-I/më#'8oaka2 )ߦ4!wZ;7z,"G9=sB* 7yeb2;6:ing p69JXhϫbtmˊ}& Si?3kSw"g\ɂ;uIZgslbil"U:{f7GYR]iTۏwDQ8p;rׄjR3\ykaגЧ4_g(P~ y P z" }rUpp"qG;T) 4~.jRwHMI>Ƅ^}~l9v~8\.0iS~dǺpB\4:Lns/\$;24obcy:t2#ڽiOSMpՄP8ݮ>i:XSR X .W>K]}׻P}bW_]:'lX0a,HY~?E*?!y@$ LMkI2MMh-*@v_\.N | 0`F5XK}j,MiDӴOS3KT0U-K@ 9,bױ=a >]CE+xG68V./.x@ IDAT=^}c$+{;Lڜ, KF$Eb@̯((A|%)HI , .pCU}i{9amȑ덿Vf}E:?^5.Xf T>x<)WpeM;a(z5U21zQY\@cMc :K;>"<_m*cxE<+B,6{_Ra;#w-2|!-#;_83۟IT䃳R(e FۥوbM_R^ׯg x Xk9?q5ewg5x}hnW{U i6}S]<{6o4:PlGwgϥ ; k?zǧ} б)) c \W5~PvQn^K'SkpFg̵^E7>GXcϯdt RPױ[8G,X෿ p@܍K+"~d޾>D9vl?9o~)ɚ^LDmzIm"յu6-r:)v U~$v]М@u 5TdBSPt{p!jB7Au.R i&X ޏVc|E3$u4M6>rX@ /whRl >`(C]xg烱ŭ0*[XQK‚MOXf!'ş1"`eMhP)EJ2R!d>4-[yO$C5m0rD ]Qw)lc Y,e'{M&ْZ6C$G+;O W'(XBFAKٿ E(%~2Q?޹ E9qQ"ʌgP:B桥$,MaE#*~J!fr}z6K.K-?w00;rࠁD-eHUVqYX}%<*vhV=vY/?Թ, "yvۆΘBXd6>6HgaZHHM3mQXYXB9_wu]а{W}~ 7{=J˅55 6aVX[@ì'F#@%%*pG/ee/ȮG rGFQe_[W{~ʚJ"TM;=FZнaf||t Px^jr]}FGBαUeʹ套^7֕ ̗W?6{5z4r!΢EnZO|ZBFTeHz#GQTGDeT 2VH PiEjR&VNHê<;5P8-4L|tVB!eCDL`שmǚ;.I GH%oF ϵXe:‰JkV 7:E1Vs~7uSFUs{|pkof H'rS̞39swc.1nd|m#_FH8묳v>LTqKSً z5B;2iL*SJd5DŽ`Ph5 2W=cpxw1̙j VDg%㉿DsL%нw݋PB7@:Bd;0ԍR2mߪ걄"KE8B^{M:w>^w=SMg31_o9wK-zad2>ӄaȦM1cƥT 絇ԁQu.Dû7mݾmOLgf?6ٶqjgۚڟmkh-t6.&nɱ/K%@[Ʃ43# :l3`)*6E2:F8 g>kҕ`l zdp[\m]T\/s!#禛noEJn&(x~y>;j#ⱮcCnX1B !K'KAtG+Xʾky_y1HHf%%ˆ&+:~pX?}bGQZ a#ֲ_-$%B[TJ 8)E@ǡRZ,JcHu%%:(YR<*WN0rHqʩ]v9Ǎ! !u$HvqG.7rtuuL&OU %ҕB^ݮeE1F0TKVE h&'>kXJ68qR,K |N2M[jLm,!Ǩ# zi}bR GIRI`pze1Ky))HҬ]]Cߋ 0z".,|ƦF?~MJ -(%#DP)En!pٍ6R0^Q^<'8ow±k[ "](QmhTf}ZZZ8w>SV>g9T͵!!<0L=Sc\5˭p'$1Fu88q q\PжO}^+HG< :HX',#f6a>R1]Tt#d*A/w^{%Ċ{ wo?.'5OJJPR]2i,YEh"VZŪUCI8)--mE.؄,'JcM]_+ FA*Q$Yv u~Q mʱYZ&h몾6ǭŻ0tI}1M6?ABϾ|I;/K "% o'`s8;ss2;Te*h~ǧ_fӑbL/Yg/teG0cBٸt{n_uM 9& ==Q D1mh]'.^hp)`\b bۣM(o-S`2N&d&cKDyGI҅^ltWՖWmx>1gRTπCƐr%~yGqurg@ڟ'OᩫI:TH˸w%TJ(C1<o#5&ayxB2-5EA`KDQ9 0v+yQ n3,tw,hAl@ 6ɚ!ř -ޣ8i4lfb#zJȕ3Jd'u]sHZL/NH9k QRZW[Sa[yF5{ޠʸ ͋xnBvc~_m.b%12!WAw@S ){UpuGa2xj.P]1 H oʃ[ 2e!ߛge38[Ӟ5T1c_4)L #&ȬɊ}qAɎ=˸³{Q/t/Z蔎'iJ>q&.tkTb8EnpxTt͹@/g3ֈm ;!3z(6]{,W_U$b?Fz#̀s!k^+ ^Pz64,Z}IS_Za⍘1қߧ "SZ@x.TtsnLNAtx!*mP)L4 9/%Rw*ei#].T1Qi14BK>Z-9p~Muw34,/{#FO(#@^"`!+FB˜Խ&9q!xMK4&פ&ZzV8^P1ARԻ^*оt!j0? Q!DɌ?"[3ڲoEwULTȄ٘bcCs8ʽ%Y&g9._}>4 w]"xN~;'j,e/4/ȝ ̿ѣXdq"MDmR {._k<Ͳ6?}fi79KVng[=H_tgy6IMbd(a Xq}d6.&Z6`\PNfIT@*w-! ?P4){{]jN0k!176pOiS$^2GFŠ<M{i xJ2j/] "w)9 f"G:!Hpb#@3L9 <֖3M UTsb #:d7ۅ "8|)4$jֲ*&]"IĻcK BKTAk|o+3A/Mfd#׼:cߵwT|E)wͫLlHW PќX<_҄ "r4 swVБ4߷YIIsei􏶚H l1zޭd(dZW城JKB+kl52J(|k+Il8iִPc~!aƊz!h0{1HƢ=.t AF].!j͒I.fY"(8ƨÕ;Ϡ9K'85M$Vx4L8 uO ,Mܱ%ۦY w;[h?|4?uW{(6maSƜϭ.iu;V&'۾sCkאk %&~cmb aVBxR׭jcf4r |GT'5ƥi\uе6Kz۬gX^&4c5KyLaCkui=Ow裯}`nZØ=]d"Bqdz^mF5]NY /nNJ-n[Lla4E?Q}?"+A*ec!!x6'ѬHrMԯ=}24wEܮR W\~q/h?㪥'ٌgu(aa+hCu#_}%wŻ_&!tRl^ΧqL⮭æ3totƜ^\kZ' +R(Jf#b#$(W'!I4:ڸ$ߴ12ŵq=x;xkG.ܔ+I6de2uӵ>Gwd=MC,-Ծi,d-GF; "M͉ IDATGMi5$)bK1XJg66cmXkɁYK&( A>\ݶ vČx.&d<.QaWJJά~mizCߣ[z=4/"S?݆FķohitX[I# 1s7/oPSXۛtqon_wÜSTR$l>c< Xl~96nzY99S&Nr恞\`LSEBPzK;(ąYk}kE֘v底{{@=[xK6WcL[~`"aJy_4n [~#F}4*faX&Πml ~ϬuC[t5fT9;+@`@[Q{-a΢Ei1LS1W;yS[: <'9,KԘk`,+,c#XZ$L?kW>=~K4`d~pz$T; $p~oXltJoWbSV@ۂY׬RqhUĖ 0ᡭ@k$U(,UYB~UrRJ C>ұ"GhdI[l( tCziB9?gbdjZl!QT5V*ypZ( bց- btNB~(_(|5+]}o ²o= 5.tg)p}׮Rsm)M C>M gdʋw 1۹}mirڤO+$05j>\O-A7jm߭çr, a0" -:` f-~{7rg 円a{`눹5#C!+tD9ȴg\/m_9,CX^ Yq&WsADYdEsgvBT$:1{E@鯟{[?f}SMcF]JyL7ᛮ^K!A)MH>55M6}CDl e%y΢;IL)2<.C,6".e跶*y╆)EY~#b%CQtmNz_i '-C [j:J]m<EbPyֹvHtȆ/^wo-*hzg¸T{p$C Σ;c-U<%_:ygEc֯AT;&JJl` Z 6Xb-J^<%7aTZ6>UEccCQ*1L1!T@S\ j-gj }h5! }}.v _mq@vGuwn˴_0qdPydsoA y|)tE5(S/Xl HpVsXvgG-J))$ԱفJT % +lހ%R`v@nzQf)6wyJX+I.(>f@ @uJ#t[TĆ6a6MYZkMuGcj k5`ŀmkFce-WD)ib2N +J$ ґ͎F)(WeC0z7 \sȤymIӞ%@bI|fX۹>ӏ`[+9&YxU ZS$l=756-EiPj@(X !Um#XfOvA&ɦ;} N Bj+plƒ{)GnQ>T0R`֨Dk]([!:/?\E˃ɊW*H]Kr.F9vwh߼lT~9X$f$[0D6m/ґLAo9mkj3v=9Ǧ[g;CT"K ܲ%XH*IQSyYSgd~{<W)bFҙg I`Dռj'>K"o\[0fԧ?3$&sOo1w9IW!V[p|uk2uv"Iۂ T]%hjCgW7߿(~?-"NӴׇ9#ِn3NL>Y9PsчlV?z8]|cwXw*OYMo%`bz i+ZWjkia{H,iIr~>q 1RSO}Mn bu=6[Y@%/x3qRʛl!I9 `yyӓzNggÄ>Gs/FZLJd*E3l/eT/Acq@ ?N1.]=W>GpL5Nli&L;CLސ)pUf4Jwqex]k1-. 4Y!38 }mC~hFa9;Ba fUxIS{ϼpn^U9fPM"STmk`^8WƝvȫ 'ߩ>iJ*ZlAautݟA5h{ Ncj8Z.1ݽ]W>vt MɻxOح1YCjd ꫈m=1ʜkrK $&Ν!98^|#?L$lqEz:C5؍.XqiB*XU`ޖB]8̕:Ul'~>f}[ةQIj&oȾ/K9W_ Z'}~.,We̓a>߭rAێ2YdEi ,s$h'ޯ<= ]N_ )?)c,6̶vMb"9%AzDM@*H:lmtRz|m .BZZZ7q9G麮d2bc6󀻁(; |1'(]`zR˽& -HNq {) lZ#=y֗z܉jc?yjB߹gl=G)j>ܼu>)n>=7￉#87G~BHycGMM?YVSg~ w"5%AbӢ`kpV%R{|MGο'>1fƌֲa~ӟl2b.jfzrE~u娅6`rأqF9U/*Bs}O]+owow{N;mRjHo{7uOf9gxv0kpUKb=nਈ"^G/=g~5r Jl6!@~m@ϒɉe60nCJ^R ZJ}#k,c?uߗi5Vў7RCJɪ'$Եac^+{Oswu8{%C:fȤJA^I/4j>TE9?7`XLMӞ{蠃v!ZM)̙3Y|9H{#+DEۺ OGw:J.z]!Nx :^̓^~fO?8 ZDzsw^?*^~& 2:DA*I!_jy\8Yit[TZe4Lv?sc?n+VdBd<+lz"ߚ%w`٫s1U TH$ i%L]j+0 Yn]G)Щ/?S8&xd@JJj ŐT 5A]8bJoe;ߎW`B|)jۈY(Cȋ°RS@F)fі<)W^6 _{.{!"B"Q(EɲFqfCn} ݏ{WOD ȯP!_(4"ò<(X,FSS1>,aEڗ!Xp/yUjTd2AOOZk `A?&e+QT^?ҽ.[D R BVZ63~÷37FNG\T}~J tw7kA1~$%kw,kBTXm >0~(ڢ*}ㅗx/ LHcc>|zh NJ$䡡o Ic6w Q̾a5—=]*Ү]~ ^@%r ; BȂĄCnvBuoHk[+W^~)Wӟ,Gutg\r-bvD%7+^ 79i*ȾijDH5 5Z~ % ?I24[MvQ#G{´{e@ pR-K_5m|֬~*!GsN޻U:Sd=lƯ&amރ\>GOZ 2bޤR>[Ҏڬ @?q\|Vn%ƏwX52*j.w2Ng L[W*Vj{V770{Ǚl\_"&TǶ>Vk1X $d/]^w(FrmncqipS/[;әe*ưA|ޏvҗگ!* N9? 8Vʀ}':| Z,fMx8 Q,.frN'AL[(G[ذ뺥'YU -h' e(!(ږXEd!RǕMR !˖%f d 1#X~V,G{{=מ5k^8$za| y<_>g>F7'x~"z湁bb=qma؃MVCTX|/$Hnn>},B1B@%'ӵcqÆvAq+KX$yuق̹T )f6y]Y)@X,~n{>2rߋB 1Q]<@H>bW?gT/ھFQtzUDm), ~*47Kx=b_A0jVAvUtэbB>|&e9Ҽ!;؆ek9Lp XAD$ Ө~<$E(e ib \2-_Q)(ńd&^dm)*-N]ȷ*k"L9N]n(e_8".ЂMYh䬱A n}c?!4DpFݔ3V3`'WdBڅ! uXAze|t #Z"*ao3Wi')C86Q4}X5w[13iy3pFAW-ȢИ, le%)m V?} Z0P!{v>[S}74ƃH– tHϢRƉwh[7? jc01V7R.UR(az, |AӵקRFEYd3KW_yvOUs5F}@[>_kN~ IDAT8ynݺKktchE!k ZZD"(EW~zu\khLhmYS J ' ]&c7˖+ڛiJI{B[#K&Axc*wn]_϶ok:cJBcJ+dCS}ӴiPX)YN-*Z)޾u4&LTzO:oxԊ+HR\ (!udl &6\_gŬXs'xTwy}XH`::J}NOh?C7q%YSl擮@֭>}'"1p]ClK no{;[@)ǩ?ߓ?.\vl6ti/^@K:~,e4=fa-魹`fȨ*IZPCÈBƭm-ٯWp;R` U@'"&V؅^o7u!>Oss+qۤ%R)Vtxx0kb~wjv1Q*Q ""aêB <r8~TrUf$' #$>;N|vQN='4v0ڠb a c웋ArA=<%TL.,y=A4`cB Rr;X62"uob@Aꛙw 槺Rev؏s8)،fWk?P?x!Pqǝە#!]/x? A荳?êQ2^"6’_~z[7]xnٲeѢEZg?|_?3҂/}zzzvmV.rDsMS|Q܌i7cdǏwd^2O ckƊc :vMukK'aШք)JC%" ȝ ;CX/\jP]Y.hZ7J-gz{r/)2:sʃf+niɯp1P RNcPq__㎗$Zde;gѨ5n#[GK>\GSNգ@۝#~8`JWsC>0-'#;iəcESw!RBkۙEE͙^ C D/Xj$L>%j-MOZ.o+XêvʱʯrYTLa ڧuY6mD=Ӹ;xo`G}ukb(Hl6Yl+c LFN<$6lxAjjjFuww/%ɅzIa拥o*Է*e* z@iD.BMwΡ%4ވg6e7׵,okU"Ş"h$T^DbVzk[7Q C*"?=k]޲QHOBAjt(yj>"xH%> `%,-:m`O8G/MjL-+)>.U“>UĊ* =EgGvBvbׅx{S_1ylO <"BnG2!Np_ZˉڵFFvd)u;T8k֬?{ʊ[nZև[P#()9cDtv+/YEPX,ǑR8NC>o 0V%^Bf2%m*MV9?0 V{zsP% 0nNm}2ZyS@jtoT2Lgp/-˛S^}4x>5yI-Bq Rl!JvWUQWX Q`ѧ>H]3! AߗCgEv”4JJ,!QrzrFh]wZ?k>i|dyJʫ-qPD)R"{p=Jݖ_"RM9׹Yomho)kw1y'6RĮ]8SI c\ijk|=$sJ(a-9{?t@v]JNN\,Tt:M28x h uJe׌KdT iS-%&%3.nŎ+tѿb^60v8mK" ;HD} & MqG78H%>i)l$Q"$ZfNgQ*Ժ~I3Z͛QxeFY2XȊsMK >nmm\RC}[2]J)4Ԉdx-rܵІa"5B]@ARVv1&x$ 2 \c.&pы8 b+DS*0=weXJ`We@9sX0o.^YC:&JQ(BB8NJqYkD] Y\= (S͙;xrMod].TNʙƭ !F׫vB`YjJP+6 MK/[tc^|[|%yJINX-SZE6b±>#RkKeqg[i'+K.>VD!/HF$8OY(,UM]CRkZc@/G+c#z3nk@7*$# E+gv\Q Uh2UATa쳱3dvdq1XJr/➕-NZ>Ia4h=v">baKĒe,>z!gTf6plIXT*Q,ˮP,8q/` >:$*T1)eJ뇉G05]Hr(ZNߓ+f:mC$aK ! }Eh5N5 踂Va_a'O[:5svA.Hzݴ);Y'TD7ْ@u zVLehcz8I򥞔وPC٢7Rk<YeGZ -V><vNP!sg(,-N6>ʗX)5c f!O°,=v /_[ ,.*q10444ayQ^o%o>D4;)^PkEv"d5"c;40>"p~D6U>c z%d?VBa%R?JnWk5JxY/kLQZ@|l=t]G_0w}3eqP5€L8f35Ȉ Rg>;Z5)'\8iі.:5ɔ;=Nϣ"9#w'<ʥogcif7Zc^&&XbhsXSRʹ23k + b10S'dӻJ\=c( #oPW[Z63^#MFz>~ Z?wc b_,==a`pm9r-Iz`<}Oڑ!߾Ӗ}j'^$gƇ󘽻CT+01(ьWtێ.5W.l2_--5գnt _r!h n Vg,w#Eh4 }h^?]t4 ( k]hyUQɷnr;SGlG·z6EϦ>۴ȓKxwRK|z {_/SХDYObFsy_6M_$S7ts*9;*ja Ba7fovĹ{◵t[1+1'ɱ];D{ؽObൔP9 YPUZ _0lNdT>>t9@Er;KJ՜GH ; PB,?ɸH csُ6u477rN=yTu1Ikk`=r'7En ~ߛ8i> M߯ %#M#+\ÎujUSßU_).[!)i:%2Ŏ};]$W$9+6a@U }y_Z0bͱ -bjm5MԴŻV3i$}\X=tZvK)oI+Oq=O3UF8pMj$kvhшX; OcݣN&7q)')x+Dk[#bۃ{^plc`ݫ@jBVaq$QTT>^5;ڃT5'Sð[/,~G^^?${OWL8^K=K͜vg퓇+^l{0m-ms[ d}&Q {O3 o"#6[35uG? (IE;o | =YblS+]]s<:s H*+ GxLDRk9kԗ/T'~)kf:9cA#Jl 2˯wJEt)~JJNQM%0P.Ba'=`y``5.d=u +!v@|tPs~Wv$Rc{2 ^`/knU%%xi]13`/8'rf%Q[Ș8sZ^}+'v@N·b3ȜMv[~AhNz,@QTj*~3e{ q㙯,Ue;* 7NcY!%·cRDj,"jHv`_=χ ?d75$fWs0.XnA=7X1\C|NHr +HohGQa_{| Vȅ@n\7z|lƚUAq鑘BLXFzkOlY8qˏ{+;oZ(f0\bsR00ÆsdNZ@b@7*-yf><а(NdF5i>Vظ"]jܽ޼LYe|XbhmxO͖$[JA.?5 nc͘n)0njws_f>N9RW1 u=XG,S%Hp 5Xh܂ctSQ6*&4 ; f uo[_V1.ıS'a6{ɇjPIHΉ;#~+MԇE4irx H';Λ8oD|џYM~@5Q=Y=37E E3 ]1=8[hN!Hg5r1Byʓfe @֔.pWe.K k(AAt{ &@6dͻ;xY8b"& y cS(ߐ]+iPI [=o|[?"InvsBkv}%>ۧ:XN<"y,2hQ7zD3XsTt_}yr_ ȁ(Ɠ)Wst%d%F&xVsϏ<_KC/8"wxAX1qj&7TݡPȓcgx( R)YSc_rU h E&c`ՁL FA"^ nb_v ;45ԿUWNVJhTGA**]?C.':Cў5@Hor{ҎuoQM*N,[hW[,{gRbՉO|i6 0ґD;廳.xc]y!z>Jf4 *.rBI܃g(TδS8'଻f/3tԒ|ua׌9\Ta=J+e!~%ݫ VW笻g bdioXHYƃ [`zĜoyֿ|Yq\PHܔ'x_M}7|hsr9NɄǸrױZБ+`%%v":MfkU؍F!vI6/4 Mr`h.P;,_v-I *;a*>p}P"*я./ t~-h#V "beD0fެ/kָpnȸ^]! 3Mh?[ZPȰ*|g␘R/^Z& %kѪB'g &bHY>..ADKABHQ^RǛ9~^s2o驹:x(ʷc BIDATzyK KX J]*\ƌkJl2e R_ q#p&;kٶq rDll" 2(XȈ;o v&6S}kܯ3@wp[7l~ M*iZ^\ V߄utq2kvaM|[mpۣ]?Hs){kɪ/TuG\$JIT z KqBFPl5޶UXBws ^ PrV&oc`KmGvy|ֹ N~f.H}MNAe}/Ε O^N3[Dx}K[.)=.6o>V\Dj*,Œ-v.3祳h,2[{jZǯkNnmok%24@Nb׉nCϺ+ Aqq#0$OpF7@Km*1n^4w r+[`%vBb$ʚ\Z29vhה>S8^0)X INk7[D, o+LPFb'ZtnKt#H AA~@sNV[_82 {aj"Cΰ~,nwR_\ϰ]4̏Ҵ(Frq@dD_> {atDρZحzJ3;3?MAݵz$m^|U7Y~ĘXAͶQpp" -2>è5WT'&eœ]?_v 3Acܜ&vADEvBaUcٮ'0\T>T:,j%33CS3;1D^D}sxԷ\dNx-^|yuq.+КlP +&K[+URotAHY"lh`|x%|*O^8ۛt쨧;F 1>F9)U3f|J_E 5J 4Eg%Caz3.ŴƠ"e= r[Q&?6[\,n1 8oo\\s_]*+Оy{aCboUjL,x&`hGbD섫]s#?Թ_:l+:Q8{ 10%fŜWHekatV~Os%'ݢ@W"Ō>TTRߘ,mM_xE$zR6ӼOnH@!_gĚEP0)eUin G"1[wwHo=?r a|!Bo{?Q}qܢ`{ŌR,I^)7TKb֦+yK{ >*8s X23$x^dڟe?p2/)\ ( c~f7S| 1MAGv>RUrAp i^^pXf"!ǞIENDB`vdr-plugin-live-3.1.3/live/themes/veltliner/img/menu_line_bg.png000066400000000000000000000002231414414333500246760ustar00rootroot00000000000000PNG  IHDR2:r# pHYs  EIDATxM 0 cfFb"B,:䴜H=N߽=-j*T1_M! ~9* >xIENDB`vdr-plugin-live-3.1.3/live/themes/veltliner/img/zap.png000066400000000000000000000015171414414333500230540ustar00rootroot00000000000000PNG  IHDRabKGD pHYsrntIME4GIDATxmkSy?&mGlԴ#uѱ&uE 7 "nQ,RѩFJ--dڙش7in=~p O0b/p{z` >LY\ MVA!H) Lcd2HRH24ukCv\(w !ܽ#QN@* T@Dǽ՛ 4D=e9y0ÁwZvg0? I4O6ڷ- O3#9I_FWpRm zXHfm;s6+ۜ;ش{ ώ3?@:=0}eYYFUU\c"NCIč,{Am=Y\"nJd pij71a/YdP8fV؋®4a SUfĮLbU~P!OM\ TL u[ wyɵ^ lEػyDB)£s8\fpgy.RUF63V-aF^|XϪ<Բ1ϟxVL8iW.IC/ɷBrJݹLM8 H-SSXppZ`QK<7F6@ˑoW[7~U>VUX#3/ d|hxM.SV'NjNK Cb/Rp,]^UO+&=FIENDB`vdr-plugin-live-3.1.3/livefeatures.cpp000066400000000000000000000014561414414333500177540ustar00rootroot00000000000000 #include "livefeatures.h" #include "tools.h" namespace vdrlive { SplitVersion::SplitVersion( std::string version ) : m_version( 0 ) { static const int factors[] = { 100000000, 1000000, 1000, 1 }; size_t pos = version.find('-'); if ( pos != std::string::npos ) { m_suffix = version.substr( pos + 1 ); version.erase( pos ); } std::vector parts = StringSplit( version, '.' ); for ( size_t i = 0; i < parts.size() && i < sizeof(factors)/sizeof(factors[0]); ++i ) { m_version += atoi( parts[ i ].c_str() ) * factors[ i ]; } } bool SplitVersion::operator<( const SplitVersion& right ) const { if ( m_version == right.m_version ) { if (right.m_suffix.empty()) { return false; } return m_suffix < right.m_suffix; } return m_version < right.m_version; } } // namespace vdrlive vdr-plugin-live-3.1.3/livefeatures.h000066400000000000000000000032571414414333500174220ustar00rootroot00000000000000#ifndef VDR_LIVE_FEATURES_H #define VDR_LIVE_FEATURES_H // STL headers need to be before VDR tools.h (included by ) #include #if TNTVERSION >= 30000 #include // must be loaded before any vdr include because of duplicate macros (LOG_ERROR, LOG_DEBUG, LOG_INFO) #endif #include namespace vdrlive { class SplitVersion { public: explicit SplitVersion( std::string version ); bool operator<( const SplitVersion& right ) const; private: int m_version; std::string m_suffix; }; template class Features; template Features& LiveFeatures(); template class Features { friend Features& LiveFeatures<>(); public: bool Loaded() const { return m_plugin != 0; } bool Recent() const { return !(m_version < m_minVersion); } char const* Version() const { return m_plugin ? m_plugin->Version() : ""; } char const* MinVersion() const { return Feat::MinVersion(); } private: cPlugin* m_plugin; SplitVersion m_version; SplitVersion m_minVersion; Features() : m_plugin( cPluginManager::GetPlugin( Feat::Plugin() ) ) , m_version( Version() ) , m_minVersion( Feat::MinVersion() ) {} }; template Features& LiveFeatures() { static Features instance; return instance; } namespace features { struct epgsearch { static const char* Plugin() { return "epgsearch"; } static const char* MinVersion() { return "0.9.25.beta6"; } }; struct streamdev_server { static const char* Plugin() { return "streamdev-server"; } static const char* MinVersion() { return "?"; } }; } // namespace features } // namespace vdrlive #endif // VDR_LIVE_FEATURES_H vdr-plugin-live-3.1.3/md5.cpp000066400000000000000000000213771414414333500157470ustar00rootroot00000000000000///////////////////////////////////////////////////////////////////////// // MD5.cpp // Implementation file for MD5 class // // This C++ Class implementation of the original RSA Data Security, Inc. // MD5 Message-Digest Algorithm is copyright (c) 2002, Gary McNickle. // All rights reserved. This software is a derivative of the "RSA Data // Security, Inc. MD5 Message-Digest Algorithm" // // You may use this software free of any charge, but without any // warranty or implied warranty, provided that you follow the terms // of the original RSA copyright, listed below. // // Original RSA Data Security, Inc. Copyright notice ///////////////////////////////////////////////////////////////////////// // // Copyright (C) 1991-2, RSA Data Security, Inc. Created 1991. All // rights reserved. // // License to copy and use this software is granted provided that it // is identified as the "RSA Data Security, Inc. MD5 Message-Digest // Algorithm" in all material mentioning or referencing this software // or this function. // License is also granted to make and use derivative works provided // that such works are identified as "derived from the RSA Data // Security, Inc. MD5 Message-Digest Algorithm" in all material // mentioning or referencing the derived work. // RSA Data Security, Inc. makes no representations concerning either // the merchantability of this software or the suitability of this // software for any particular purpose. It is provided "as is" // without express or implied warranty of any kind. // These notices must be retained in any copies of any part of this // documentation and/or software. ///////////////////////////////////////////////////////////////////////// #include "md5.h" #include #include #include static unsigned char PADDING[64] = { 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; #define S11 7 #define S12 12 #define S13 17 #define S14 22 #define S21 5 #define S22 9 #define S23 14 #define S24 20 #define S31 4 #define S32 11 #define S33 16 #define S34 23 #define S41 6 #define S42 10 #define S43 15 #define S44 21 // PrintMD5: Converts a completed md5 digest into a char* string. char* PrintMD5(uchar md5Digest[16]) { char chBuffer[256]; char chEach[10]; int nCount; memset(chBuffer,0,256); memset(chEach, 0, 10); for (nCount = 0; nCount < 16; nCount++) { sprintf(chEach, "%02x", md5Digest[nCount]); // strncat(chBuffer, chEach, sizeof(chEach)); // compiler warning with gcc V9 strncat(chBuffer, chEach, sizeof(chBuffer)-strlen(chBuffer)-1); // no need to limit to the size of chEach, // because it will only append up to the 0 byte of chEach, // but dont overflow chBuffer and let room the terminating 0 } return strdup(chBuffer); } // MD5String: Performs the MD5 algorithm on a char* string, returning // the results as a char*. char* MD5String(char* szString) { int nLen = strlen(szString); md5 alg; alg.Update((unsigned char*)szString, (unsigned int)nLen); alg.Finalize(); return PrintMD5(alg.Digest()); } // md5::Init // Initializes a new context. void md5::Init() { memset(m_Count, 0, 2 * sizeof(uint4)); m_State[0] = 0x67452301; m_State[1] = 0xefcdab89; m_State[2] = 0x98badcfe; m_State[3] = 0x10325476; } // md5::Update // MD5 block update operation. Continues an MD5 message-digest // operation, processing another message block, and updating the // context. void md5::Update(uchar* chInput, uint4 nInputLen) { uint4 i, index, partLen; // Compute number of bytes mod 64 index = (unsigned int)((m_Count[0] >> 3) & 0x3F); // Update number of bits if ((m_Count[0] += (nInputLen << 3)) < (nInputLen << 3)) m_Count[1]++; m_Count[1] += (nInputLen >> 29); partLen = 64 - index; // Transform as many times as possible. if (nInputLen >= partLen) { memcpy( &m_Buffer[index], chInput, partLen ); Transform(m_Buffer); for (i = partLen; i + 63 < nInputLen; i += 64) Transform(&chInput[i]); index = 0; } else i = 0; // Buffer remaining input memcpy( &m_Buffer[index], &chInput[i], nInputLen-i ); } // md5::Finalize // MD5 finalization. Ends an MD5 message-digest operation, writing // the message digest and zeroizing the context. void md5::Finalize() { uchar bits[8]; uint4 index, padLen; // Save number of bits Encode (bits, m_Count, 8); // Pad out to 56 mod 64 index = (unsigned int)((m_Count[0] >> 3) & 0x3f); padLen = (index < 56) ? (56 - index) : (120 - index); Update(PADDING, padLen); // Append length (before padding) Update (bits, 8); // Store state in digest Encode (m_Digest, m_State, 16); memset(m_Count, 0, 2 * sizeof(uint4)); memset(m_State, 0, 4 * sizeof(uint4)); memset(m_Buffer,0, 64 * sizeof(uchar)); } // md5::Transform // MD5 basic transformation. Transforms state based on block. void md5::Transform (uchar* block) { uint4 a = m_State[0], b = m_State[1], c = m_State[2], d = m_State[3], x[16]; Decode (x, block, 64); // Round 1 FF (a, b, c, d, x[ 0], S11, 0xd76aa478); FF (d, a, b, c, x[ 1], S12, 0xe8c7b756); FF (c, d, a, b, x[ 2], S13, 0x242070db); FF (b, c, d, a, x[ 3], S14, 0xc1bdceee); FF (a, b, c, d, x[ 4], S11, 0xf57c0faf); FF (d, a, b, c, x[ 5], S12, 0x4787c62a); FF (c, d, a, b, x[ 6], S13, 0xa8304613); FF (b, c, d, a, x[ 7], S14, 0xfd469501); FF (a, b, c, d, x[ 8], S11, 0x698098d8); FF (d, a, b, c, x[ 9], S12, 0x8b44f7af); FF (c, d, a, b, x[10], S13, 0xffff5bb1); FF (b, c, d, a, x[11], S14, 0x895cd7be); FF (a, b, c, d, x[12], S11, 0x6b901122); FF (d, a, b, c, x[13], S12, 0xfd987193); FF (c, d, a, b, x[14], S13, 0xa679438e); FF (b, c, d, a, x[15], S14, 0x49b40821); // Round 2 GG (a, b, c, d, x[ 1], S21, 0xf61e2562); GG (d, a, b, c, x[ 6], S22, 0xc040b340); GG (c, d, a, b, x[11], S23, 0x265e5a51); GG (b, c, d, a, x[ 0], S24, 0xe9b6c7aa); GG (a, b, c, d, x[ 5], S21, 0xd62f105d); GG (d, a, b, c, x[10], S22, 0x2441453); GG (c, d, a, b, x[15], S23, 0xd8a1e681); GG (b, c, d, a, x[ 4], S24, 0xe7d3fbc8); GG (a, b, c, d, x[ 9], S21, 0x21e1cde6); GG (d, a, b, c, x[14], S22, 0xc33707d6); GG (c, d, a, b, x[ 3], S23, 0xf4d50d87); GG (b, c, d, a, x[ 8], S24, 0x455a14ed); GG (a, b, c, d, x[13], S21, 0xa9e3e905); GG (d, a, b, c, x[ 2], S22, 0xfcefa3f8); GG (c, d, a, b, x[ 7], S23, 0x676f02d9); GG (b, c, d, a, x[12], S24, 0x8d2a4c8a); // Round 3 HH (a, b, c, d, x[ 5], S31, 0xfffa3942); HH (d, a, b, c, x[ 8], S32, 0x8771f681); HH (c, d, a, b, x[11], S33, 0x6d9d6122); HH (b, c, d, a, x[14], S34, 0xfde5380c); HH (a, b, c, d, x[ 1], S31, 0xa4beea44); HH (d, a, b, c, x[ 4], S32, 0x4bdecfa9); HH (c, d, a, b, x[ 7], S33, 0xf6bb4b60); HH (b, c, d, a, x[10], S34, 0xbebfbc70); HH (a, b, c, d, x[13], S31, 0x289b7ec6); HH (d, a, b, c, x[ 0], S32, 0xeaa127fa); HH (c, d, a, b, x[ 3], S33, 0xd4ef3085); HH (b, c, d, a, x[ 6], S34, 0x4881d05); HH (a, b, c, d, x[ 9], S31, 0xd9d4d039); HH (d, a, b, c, x[12], S32, 0xe6db99e5); HH (c, d, a, b, x[15], S33, 0x1fa27cf8); HH (b, c, d, a, x[ 2], S34, 0xc4ac5665); // Round 4 II (a, b, c, d, x[ 0], S41, 0xf4292244); II (d, a, b, c, x[ 7], S42, 0x432aff97); II (c, d, a, b, x[14], S43, 0xab9423a7); II (b, c, d, a, x[ 5], S44, 0xfc93a039); II (a, b, c, d, x[12], S41, 0x655b59c3); II (d, a, b, c, x[ 3], S42, 0x8f0ccc92); II (c, d, a, b, x[10], S43, 0xffeff47d); II (b, c, d, a, x[ 1], S44, 0x85845dd1); II (a, b, c, d, x[ 8], S41, 0x6fa87e4f); II (d, a, b, c, x[15], S42, 0xfe2ce6e0); II (c, d, a, b, x[ 6], S43, 0xa3014314); II (b, c, d, a, x[13], S44, 0x4e0811a1); II (a, b, c, d, x[ 4], S41, 0xf7537e82); II (d, a, b, c, x[11], S42, 0xbd3af235); II (c, d, a, b, x[ 2], S43, 0x2ad7d2bb); II (b, c, d, a, x[ 9], S44, 0xeb86d391); m_State[0] += a; m_State[1] += b; m_State[2] += c; m_State[3] += d; memset(x, 0, sizeof(x)); } // md5::Encode // Encodes input (uint4) into output (uchar). Assumes nLength is // a multiple of 4. void md5::Encode(uchar* dest, uint4* src, uint4 nLength) { uint4 i, j; assert(nLength % 4 == 0); for (i = 0, j = 0; j < nLength; i++, j += 4) { dest[j] = (uchar)(src[i] & 0xff); dest[j+1] = (uchar)((src[i] >> 8) & 0xff); dest[j+2] = (uchar)((src[i] >> 16) & 0xff); dest[j+3] = (uchar)((src[i] >> 24) & 0xff); } } // md5::Decode // Decodes input (uchar) into output (uint4). Assumes nLength is // a multiple of 4. void md5::Decode(uint4* dest, uchar* src, uint4 nLength) { uint4 i, j; assert(nLength % 4 == 0); for (i = 0, j = 0; j < nLength; i++, j += 4) { dest[i] = ((uint4)src[j]) | (((uint4)src[j+1])<<8) | (((uint4)src[j+2])<<16) | (((uint4)src[j+3])<<24); } } vdr-plugin-live-3.1.3/md5.h000066400000000000000000000065771414414333500154210ustar00rootroot00000000000000///////////////////////////////////////////////////////////////////////// // MD5.cpp // Implementation file for MD5 class // // This C++ Class implementation of the original RSA Data Security, Inc. // MD5 Message-Digest Algorithm is copyright (c) 2002, Gary McNickle. // All rights reserved. This software is a derivative of the "RSA Data // Security, Inc. MD5 Message-Digest Algorithm" // // You may use this software free of any charge, but without any // warranty or implied warranty, provided that you follow the terms // of the original RSA copyright, listed below. // // Original RSA Data Security, Inc. Copyright notice ///////////////////////////////////////////////////////////////////////// // // Copyright (C) 1991-2, RSA Data Security, Inc. Created 1991. All // rights reserved. // // License to copy and use this software is granted provided that it // is identified as the "RSA Data Security, Inc. MD5 Message-Digest // Algorithm" in all material mentioning or referencing this software // or this function. // License is also granted to make and use derivative works provided // that such works are identified as "derived from the RSA Data // Security, Inc. MD5 Message-Digest Algorithm" in all material // mentioning or referencing the derived work. // RSA Data Security, Inc. makes no representations concerning either // the merchantability of this software or the suitability of this // software for any particular purpose. It is provided "as is" // without express or implied warranty of any kind. // These notices must be retained in any copies of any part of this // documentation and/or software. ///////////////////////////////////////////////////////////////////////// typedef unsigned int uint4; typedef unsigned short int uint2; typedef unsigned char uchar; char* PrintMD5(uchar md5Digest[16]); char* MD5String(char* szString); class md5 { // Methods public: md5() { Init(); } void Init(); void Update(uchar* chInput, uint4 nInputLen); void Finalize(); uchar* Digest() { return m_Digest; } private: void Transform(uchar* block); void Encode(uchar* dest, uint4* src, uint4 nLength); void Decode(uint4* dest, uchar* src, uint4 nLength); inline uint4 rotate_left(uint4 x, uint4 n) { return ((x << n) | (x >> (32-n))); } inline uint4 F(uint4 x, uint4 y, uint4 z) { return ((x & y) | (~x & z)); } inline uint4 G(uint4 x, uint4 y, uint4 z) { return ((x & z) | (y & ~z)); } inline uint4 H(uint4 x, uint4 y, uint4 z) { return (x ^ y ^ z); } inline uint4 I(uint4 x, uint4 y, uint4 z) { return (y ^ (x | ~z)); } inline void FF(uint4& a, uint4 b, uint4 c, uint4 d, uint4 x, uint4 s, uint4 ac) { a += F(b, c, d) + x + ac; a = rotate_left(a, s); a += b; } inline void GG(uint4& a, uint4 b, uint4 c, uint4 d, uint4 x, uint4 s, uint4 ac) { a += G(b, c, d) + x + ac; a = rotate_left(a, s); a += b; } inline void HH(uint4& a, uint4 b, uint4 c, uint4 d, uint4 x, uint4 s, uint4 ac) { a += H(b, c, d) + x + ac; a = rotate_left(a, s); a += b; } inline void II(uint4& a, uint4 b, uint4 c, uint4 d, uint4 x, uint4 s, uint4 ac) { a += I(b, c, d) + x + ac; a = rotate_left(a, s); a += b; } // Data private: uint4 m_State[4]; uint4 m_Count[2]; uchar m_Buffer[64] = {0}; uchar m_Digest[16] = {0}; uchar m_Finalized = 0; }; vdr-plugin-live-3.1.3/osd_status.cpp000066400000000000000000000116411414414333500174430ustar00rootroot00000000000000 #include "osd_status.h" #include namespace vdrlive { OsdStatusMonitor::OsdStatusMonitor():title(),message(),red(),green(),yellow(),blue(),text(),selected(-1),lastUpdate(0){ memset(&tabs, 0, sizeof(tabs)); } OsdStatusMonitor::~OsdStatusMonitor() { OsdClear(); } void OsdStatusMonitor::OsdClear() { title = message = text = ""; red = green = yellow = blue = ""; items.Clear(); selected = -1; memset(&tabs, 0, sizeof(tabs)); lastUpdate= clock(); } void OsdStatusMonitor::OsdTitle(const char *Title) { title = Title ? Title : ""; lastUpdate= clock(); } void OsdStatusMonitor::OsdStatusMessage(const char *Message) { message = Message ? Message : ""; lastUpdate= clock(); } void OsdStatusMonitor::OsdHelpKeys(const char *Red, const char *Green, const char *Yellow, const char *Blue) { red = Red ? Red :""; green = Green ? Green : ""; yellow = Yellow ? Yellow : ""; blue = Blue ? Blue : ""; lastUpdate= clock(); } void OsdStatusMonitor::OsdItem(const char *Text, int Index) { const char* tab; const char* colStart = Text; for (int col = 0; col < MaxTabs && (tab = strchr(colStart, '\t')); col++) { int width = tab - colStart + 1; if (width > tabs[col]) tabs[col] = width; colStart = colStart + width; } items.Add(new cLiveOsdItem(Text)); lastUpdate= clock(); } void OsdStatusMonitor::OsdCurrentItem(const char *Text) { int i = -1; int best = -1; int dist = items.Count(); cLiveOsdItem * currentItem = NULL; cLiveOsdItem *bestItem = NULL; for (cLiveOsdItem *item = items.First(); item; item = items.Next(item)) { if (++i == selected) currentItem = item; if ( item->Text().compare(Text) == 0) { if (abs(i - selected) < dist) { // best match is the one closest to previous position best = i; bestItem= item; dist = abs(i - selected); } else if (selected < 0) { // previous position unknown - take first match best = i; bestItem= item; break; } else { // we already have a better match, so we're done break; } } } if (best >= 0) { // found matching item selected = best; bestItem->Select(true); if (currentItem && currentItem != bestItem){ currentItem->Select(false); lastUpdate= clock(); } } else if (currentItem) { // no match: the same item is still selected but its text changed currentItem->Update(Text); lastUpdate= clock(); } } void OsdStatusMonitor::OsdTextItem(const char *Text, bool Scroll) { if (Text) { text = Text; //text= text.replace( text.begin(), text.end(), '\n', '|'); } else { text = ""; } lastUpdate= clock(); } std::string const OsdStatusMonitor::GetTitleHtml() {return !title.empty() ? "

    " : "";} std::string const OsdStatusMonitor::GetMessageHtml() {return !message.empty() ? "
    " + EncodeHtml(message) + "
    " : "";} std::string const OsdStatusMonitor::GetRedHtml() {return !red.empty() ? "
    " + EncodeHtml(red) + "
    " : "";} std::string const OsdStatusMonitor::GetGreenHtml() {return !green.empty() ? "
    " + EncodeHtml(green) + "
    " : "";} std::string const OsdStatusMonitor::GetYellowHtml() {return !yellow.empty() ? "
    " + EncodeHtml(yellow) + "
    " : "";} std::string const OsdStatusMonitor::GetBlueHtml() {return !blue.empty() ? "
    " + EncodeHtml(blue) + "
    " : "";} std::string const OsdStatusMonitor::GetTextHtml() {return !text.empty() ? "
    " + EncodeHtml(text) + "
    " : "";} std::string const OsdStatusMonitor::GetButtonsHtml() { std::string buffer= GetRedHtml() + GetGreenHtml() + GetYellowHtml() + GetBlueHtml(); return !buffer.empty() ? "
    " + buffer + "
    " : ""; } std::string const OsdStatusMonitor::GetItemsHtml(void){ std::string buffer= ""; for (cLiveOsdItem *item = items.First(); item; item = items.Next(item)) { buffer += "
    isSelected()) buffer += " selected"; buffer += "\">"; buffer += EncodeHtml(item->Text()); buffer += "
    "; } return !buffer.empty() ? "
    " + buffer + "
    " : ""; } std::string const OsdStatusMonitor::GetHtml(){ std::stringstream ss; ss << lastUpdate; return "
    " + GetTitleHtml() + GetItemsHtml() + GetTextHtml() + GetMessageHtml() + GetButtonsHtml() + "
    "; } std::string const OsdStatusMonitor::EncodeHtml(const std::string& html) { std::stringstream ss; std::string::const_iterator i; for (i = html.begin(); i != html.end(); ++i) { if (*i == '<') ss << "<"; else if (*i == '>') ss << ">"; else if (*i == '&') ss << "&"; else if (*i == '"') ss << """; else ss << static_cast(*i); // Copy untranslated } return ss.str(); } OsdStatusMonitor& LiveOsdStatusMonitor() { static OsdStatusMonitor instance; return instance; } } // namespace vdrlive vdr-plugin-live-3.1.3/osd_status.h000066400000000000000000000047241414414333500171140ustar00rootroot00000000000000#ifndef VDR_LIVE_OSD_STATUS_H #define VDR_LIVE_OSD_STATUS_H // STL headers need to be before VDR tools.h (included by ) #include #include namespace vdrlive { class cLiveOsdItem: public cListObject { private: std::string text; bool selected; public: std::string Text() const { return text; } int isSelected() const {return selected;} void Select(const bool doSelect) { selected= doSelect; }; void Update(const char* Text) { text = Text ? Text : ""; }; explicit cLiveOsdItem(const char* Text):text(),selected(false) { text = Text ? Text : ""; }; ~cLiveOsdItem() { } }; class OsdStatusMonitor: public cStatus { friend OsdStatusMonitor& LiveOsdStatusMonitor(); public: enum { MaxTabs = 6 }; private: OsdStatusMonitor(); OsdStatusMonitor( OsdStatusMonitor const& ); std::string title; std::string message; std::string red; std::string green; std::string yellow; std::string blue; std::string text; int selected; cList items; unsigned short tabs[MaxTabs]; clock_t lastUpdate; protected: // static void append(char *&tail, char type, const char *src, int max); public: std::string const GetTitle() const {return title;} std::string const GetMessage() const {return message;} std::string const GetRed() const {return red;} std::string const GetGreen() const {return green;} std::string const GetYellow() const {return yellow;} std::string const GetBlue() const {return blue;} std::string const GetText() const {return text;} virtual std::string const GetHtml(); virtual std::string const GetTitleHtml(); virtual std::string const GetMessageHtml(); virtual std::string const GetRedHtml(); virtual std::string const GetGreenHtml(); virtual std::string const GetYellowHtml(); virtual std::string const GetBlueHtml(); virtual std::string const GetTextHtml(); virtual std::string const GetButtonsHtml(); virtual std::string const GetItemsHtml(); virtual void OsdClear(); virtual void OsdTitle(const char *Title); virtual void OsdStatusMessage(const char *Message); virtual void OsdHelpKeys(const char *Red, const char *Green, const char *Yellow, const char *Blue); virtual void OsdTextItem(const char *Text, bool Scroll); virtual void OsdItem(const char *Text, int Index); virtual void OsdCurrentItem(const char *Text); virtual ~OsdStatusMonitor(); std::string const EncodeHtml(const std::string& html); }; OsdStatusMonitor& LiveOsdStatusMonitor(); } // namespace vdrlive #endif // VDR_LIVE_STATUS_H vdr-plugin-live-3.1.3/pages/000077500000000000000000000000001414414333500156435ustar00rootroot00000000000000vdr-plugin-live-3.1.3/pages/Makefile000066400000000000000000000037051414414333500173100ustar00rootroot00000000000000### The official name of this plugin. PLUGIN := live ### Additional options to silence TNTNET warnings TNTFLAGS ?= -Wno-overloaded-virtual -Wno-unused-variable DEFINES += -DDISABLE_TEMPLATES_COLLIDING_WITH_STL ### Includes and Defines (add further entries here): INCLUDES += -I$(VDRDIR)/include -I.. ### The object files (add further files here): OBJS := menu.o recordings.o schedule.o multischedule.o screenshot.o \ timers.o whats_on.o switch_channel.o keypress.o remote.o \ channels_widget.o edit_timer.o error.o pageelems.o tooltip.o \ searchtimers.o edit_searchtimer.o searchresults.o \ searchepg.o login.o ibox.o xmlresponse.o play_recording.o \ pause_recording.o stop_recording.o ffw_recording.o \ rwd_recording.o setup.o content.o epginfo.o timerconflicts.o \ recstream.o users.o edit_user.o edit_recording.o osd.o \ playlist.o stream.o stream_data.o SRCS := $(patsubst %.o,%.cpp,$(OBJS)) ESRCS := $(patsubst %.o,%.ecpp,$(OBJS)) ESRCS_DEPS := $(patsubst %.o,.%.edep,$(OBJS)) include ../global.mk ### The main target: all: libpages.a @true ### Implicit rules: %.o: %.cpp $(call PRETTY_PRINT,"CC pages/" $@) $(Q)$(CXX) $(CXXFLAGS) $(TNTFLAGS) -c $(DEFINES) $(PLUGINFEATURES) $(INCLUDES) $< %.cpp: %.ecpp $(call PRETTY_PRINT,"ECPP pages/" $@) $(Q)$(ECPPC) $(ECPPFLAGS) $(ECPPFLAGS_CPP) $< .%.edep: %.ecpp @$(ECPPC) -M $(ECPPFLAGS) $(ECPPFLAGS_CPP) $< > $@ ### Dependencies: MAKEDEP := $(CXX) -MM -MG DEPFILE := .dependencies $(DEPFILE): Makefile $(SRCS) $(ESRCS) @$(MAKEDEP) $(DEFINES) $(PLUGINFEATURES) $(INCLUDES) $(SRCS) > $@ ifneq ($(MAKECMDGOALS),clean) -include $(DEPFILE) -include $(ESRCS_DEPS) endif ### Targets: libpages.a: $(OBJS) $(call PRETTY_PRINT,"AR pages/" $@) $(Q)$(AR) r $@ $^ $(AR_NUL) clean: $(call PRETTY_PRINT,"CLN pages/") @rm -f *~ *.o core* libpages.a $(SRCS) $(DEPFILE) $(ESRCS_DEPS) dist: clean @echo "Nothing to do for distribution here ..." .PRECIOUS: $(SRCS) vdr-plugin-live-3.1.3/pages/channels_widget.ecpp000066400000000000000000000026721414414333500216610ustar00rootroot00000000000000<%pre> #include #include #if VDRVERSNUM < 20300 #include // ReadLock #endif using namespace vdrlive; <%args> name = "channel"; selected; onchange; bool channelid = false; <%session scope="global"> bool logged_in(false); <{ if (!logged_in && LiveSetup().UseAuth()) return reply.redirect("login.html"); }> <%cpp> #if VDRVERSNUM >= 20301 LOCK_CHANNELS_READ; #else ReadLock channelsLock( Channels ); if ( !channelsLock ) throw HtmlError( tr("Couldn't aquire access to channels, please try again later.") ); #endif vdr-plugin-live-3.1.3/pages/content.ecpp000066400000000000000000000030021414414333500201610ustar00rootroot00000000000000<%pre> #include #include using namespace vdrlive; <%session scope="global"> bool logged_in(false); <%cpp> //if (!logged_in && LiveSetup().UseAuth()) return reply.redirect("login.html"); std::string mime("image/png"); if (request.getArgsCount() > 0) { #if TNT_MAPURL_NAMED_ARGS mime = request.getArg("mime-type"); #else mime = request.getArg(0); #endif // dsyslog("vdrlive::content found mime arg (%s)", mime.c_str()); } reply.setContentType(mime); // dsyslog("vdrlive::content::mimetype(%s)", mime.c_str()); std::string const path(request.getPathInfo()); // dsyslog("vdrlive::content: path = %s", path.c_str()); // security checking of path. In order to not allow exploits the // path must be absolute and not contain any upward references (e.g '../') if (path.empty()) { return HTTP_BAD_REQUEST; } if ('/' != path[0]) { return HTTP_BAD_REQUEST; } if (std::string::npos != path.find("../", 1)) { return HTTP_BAD_REQUEST; } FileCache::ptr_type f = LiveFileCache().get(path); if (f.get() == 0) { // dsyslog("vdrlive::content: DECLINED"); return DECLINED; } std::string ctime = tnt::HttpMessage::htdate(f->ctime()); std::string browserTime = request.getHeader(tnt::httpheader::ifModifiedSince); if (browserTime == ctime) { // dsyslog("vdrlive::content: HTTP_NOT_MODIFIED"); return HTTP_NOT_MODIFIED; } // dsyslog("vdrlive::content: send %d bytes of data", f->size()); reply.setHeader(tnt::httpheader::lastModified, ctime); reply.out().write(f->data(), f->size()); vdr-plugin-live-3.1.3/pages/edit_recording.ecpp000066400000000000000000000133321414414333500214770ustar00rootroot00000000000000<%pre> #include #include #include #include #include using namespace vdrlive; <%args> // input parameters std::string recid; std::string async; // form parameters std::string name = ""; std::string directory = ""; std::string options[]; <%session scope="global"> bool logged_in(false); std::string edit_rec_referer; <%request scope="page"> const cRecording* recording; <%include>page_init.eh <%cpp> if (!logged_in && LiveSetup().UseAuth()) return reply.redirect("login.html"); if (!cUser::CurrentUserHasRightTo(UR_EDITRECS)) throw HtmlError( tr("Sorry, no permission. Please contact your administrator!") ); bool ajaxReq = !async.empty() && (lexical_cast(async) != 0); std::string message; recording = NULL; if (!recid.empty()) { recording = LiveRecordingsManager()->GetByMd5Hash(recid); if (!recording) throw HtmlError(tr("Couldn't find recording. Maybe you mistyped your request?")); } if (request.getMethod() == "POST") { if (name.empty()) message = tr("Please set a name for the recording!"); else if (recording) { bool copy_only = false; #if TNTVERSION >= 30000 typedef std::vector options_type; #endif for (options_type::const_iterator it = options.begin(); it != options.end(); ++it) { if (*it == "delresume") LiveRecordingsManager()->DeleteResume(recording); else if (*it == "delmarks") LiveRecordingsManager()->DeleteMarks(recording); else if (*it == "copy") copy_only = true; } options.clear(); std::string filename = directory.empty() ? name : StringReplace(directory, "/", "~") + "~" + name; if (LiveRecordingsManager()->MoveRecording(recording, FileSystemExchangeChars(filename, true), copy_only)) return reply.redirect(!edit_rec_referer.empty() ? edit_rec_referer : "recordings.html"); else message = tr("Cannot copy, rename or move the recording."); } } if (message.empty()) edit_rec_referer = request.getHeader("Referer:", "recordings.html"); if (recording) { std::string path = recording->Name(); size_t found = path.find_last_of("~"); if (found != std::string::npos) { directory = StringReplace(path.substr(0, found), "~", "/"); name = path.substr(found + 1); } else { directory = ""; name = path; } } <& pageelems.doc_type &> VDR Live - <$ tr("Edit recording") $> <%cpp> if (!ajaxReq) { <& pageelems.stylesheets &> <& pageelems.ajax_js &> <%cpp> } <%cpp> if (!ajaxReq) { <& pageelems.logo &> <& menu active=("recordings") &> <%cpp> }
    <%cpp> if (recording && recording->Info()->ShortText()) { <%cpp> } if (recording && recording->Info()->Description()) { <%cpp> } if (recording && recording->Info()->Aux()) { <%cpp> }
    <$ tr("Edit recording") $>
    <$ tr("Name") $>:
    <$ tr("Directory") $>:
    <$ tr("Delete resume information") $>:
    <$ tr("Delete marks information") $>:
    <$ tr("Copy only") $>:
    <$ tr("Short description") $>:
    <$ recording->Info()->ShortText() $>
    <$ tr("Description") $>:
    <$ recording->Info()->Description() $>
    <$ tr("Auxiliary info") $>:
    <$ recording->Info()->Aux() $>
    <$ message $>
    <%include>page_exit.eh vdr-plugin-live-3.1.3/pages/edit_searchtimer.ecpp000066400000000000000000001130141414414333500220270ustar00rootroot00000000000000<%pre> #include #include #include #include #include using namespace vdrlive; <%args> // input parameters std::string searchtimerid; std::string test; // form parameters std::string search = ""; int mode = 0; bool matchcase = false; int tolerance = 1; bool usetitle = false; bool usesubtitle = false; bool usedescr = false; int usechannel = SearchTimer::NoChannel; tChannelID channelfrom; tChannelID channelto; std::string changrpsel = ""; bool usetime = false; std::string start_h = "00"; std::string start_m = "00"; std::string stop_h = "00"; std::string stop_m = "00"; bool useduration = false; int durationmin = 0; int durationmax = 90; bool useweekday = false; bool wday_mon = false; bool wday_tue = false; bool wday_wed = false; bool wday_thu = false; bool wday_fri = false; bool wday_sat = false; bool wday_sun = false; bool useinfavorites = false; int useassearchtimer = 0; int searchtimeraction = 0; bool seriesrecording = false; std::string directory = ""; int delrecafterdays = 0; int keeprecs = 0; int pauseonrecs = 0; int blacklistmode = 0; std::string blacklistids[]; int switchminbefore = 0; bool useextepginfo = false; std::string extepgvalues[]; bool avoidrepeats = false; int allowedrepeats = 0; int repeatswithindays = 0; bool comparetitle = false; int comparesubtitle = 0; bool comparesummary = false; unsigned avoidrepeatscatselected[]; int priority = 0; int lifetime = 0; int marginstart = 0; int marginstop = 0; bool usevps = false; bool delmode = false; int delaftercountrecs = 0; int delafterdaysoffirstrec = 0; std::string blacklistids_internal; std::string useassearchtimerfrom; std::string useassearchtimerto; bool ignoreMissingEPGCats = false; <%session scope="global"> bool logged_in(false); <%request scope="page"> ExtEPGInfos extEPGInfos; ChannelGroups channelGroups; Blacklists blacklists; RecordingDirs recordingdirs; SearchTimer* editsearchtimer; <%include>page_init.eh <{ if (!logged_in && LiveSetup().UseAuth()) return reply.redirect("login.html"); }> <%cpp> if (!cUser::CurrentUserHasRightTo(UR_EDITSTIMERS)) throw HtmlError( tr("Sorry, no permission. Please contact your administrator!") ); #define SELECTIF(x) reply.out() << ( (x) ? "selected=\"selected\"" : "" ); #define CHECKIF(x) reply.out() << ( (x) ? "checked=\"checked\"" : "" ); SearchTimers searchtimers; bool testmode = !test.empty(); editsearchtimer = NULL; if ( request.getMethod() == "POST") { SearchTimer searchtimer; if ( !searchtimerid.empty() && !testmode) { editsearchtimer = searchtimers.GetByTimerId( searchtimerid ); if ( editsearchtimer == 0 ) throw HtmlError( tr("Couldn't find searchtimer. Maybe you mistyped your request?") ); searchtimer.SetId(editsearchtimer->Id()); } searchtimer.SetSearch(search); searchtimer.SetSearchMode(mode); searchtimer.SetTolerance(tolerance); searchtimer.SetMatchCase(matchcase); searchtimer.SetUseTitle(usetitle); searchtimer.SetUseSubtitle(usesubtitle); searchtimer.SetUseDescription(usedescr); searchtimer.SetUseExtEPGInfo(useextepginfo); if (useextepginfo) { std::vector infos; unsigned int i=0; for (ExtEPGInfos::iterator extinfo = extEPGInfos.begin(); extinfo != extEPGInfos.end(); ++extinfo, i++) { std::stringstream os; os << extinfo->Id() << "#" << (iSearch(); mode = searchtimer->SearchMode(); tolerance = searchtimer->Tolerance(); matchcase = searchtimer->MatchCase(); usetitle = searchtimer->UseTitle(); usesubtitle = searchtimer->UseSubtitle(); usedescr = searchtimer->UseDescription(); usechannel = searchtimer->UseChannel(); channelfrom = searchtimer->ChannelMin(); channelto = searchtimer->ChannelMax(); if (!channelto.Valid() && channelfrom.Valid()) channelto = channelfrom; if (usechannel == SearchTimer::Group) changrpsel = searchtimer->ChannelText(); usetime = searchtimer->UseTime(); std::stringstream os; os << std::setw(2) << std::setfill('0') << ( searchtimer->StartTime() / 100 ) % 100; start_h = os.str(); os.str(""); os << std::setw(2) << std::setfill('0') << searchtimer->StartTime() % 100; start_m = os.str(); os.str(""); os << std::setw(2) << std::setfill('0') << ( searchtimer->StopTime() / 100 ) % 100; stop_h = os.str(); os.str(""); os << std::setw(2) << std::setfill('0') << searchtimer->StopTime() % 100; stop_m = os.str(); useduration = searchtimer->UseDuration(); if (useduration) { durationmin = searchtimer->MinDuration(); durationmax = searchtimer->MaxDuration(); } useweekday = searchtimer->UseDayOfWeek(); if (useweekday) { int dayofweek = searchtimer->DayOfWeek(); if (dayofweek >= 0) { wday_sun = (dayofweek == 0); wday_mon = (dayofweek == 1); wday_tue = (dayofweek == 2); wday_wed = (dayofweek == 3); wday_thu = (dayofweek == 4); wday_fri = (dayofweek == 5); wday_sat = (dayofweek == 6); } else { wday_sun = -dayofweek & 0x01; wday_mon = -dayofweek & 0x02; wday_tue = -dayofweek & 0x04; wday_wed = -dayofweek & 0x08; wday_thu = -dayofweek & 0x10; wday_fri = -dayofweek & 0x20; wday_sat = -dayofweek & 0x40; } } useinfavorites = searchtimer->UseInFavorites(); useassearchtimer = searchtimer->UseAsSearchTimer(); if (useassearchtimer == 2) { useassearchtimerfrom = searchtimer->UseAsSearchTimerFrom(tr("mm/dd/yyyy")); useassearchtimerto = searchtimer->UseAsSearchTimerTil(tr("mm/dd/yyyy")); } searchtimeraction = searchtimer->SearchTimerAction(); seriesrecording = searchtimer->UseSeriesRecording(); directory = searchtimer->Directory(); delrecafterdays = searchtimer->DelRecsAfterDays(); keeprecs = searchtimer->KeepRecs(); pauseonrecs = searchtimer->PauseOnRecs(); blacklistmode = searchtimer->BlacklistMode(); switchminbefore = searchtimer->SwitchMinBefore(); useextepginfo = searchtimer->UseExtEPGInfo(); std::vector infos = searchtimer->ExtEPGInfo(); for(unsigned int i=0; i parts = StringSplit( infos[i], '#' ); extepgvalues.push_back(parts.size() > 1?parts[1]:""); } else extepgvalues.push_back(""); } ignoreMissingEPGCats = searchtimer->IgnoreMissingEPGCats(); avoidrepeats = searchtimer->AvoidRepeats(); allowedrepeats = searchtimer->AllowedRepeats(); repeatswithindays = searchtimer->RepeatsWithinDays(); comparetitle = searchtimer->CompareTitle(); comparesubtitle = searchtimer->CompareSubtitle(); comparesummary = searchtimer->CompareSummary(); for(unsigned int i=0; iCompareCategories() & (1<Priority(); lifetime = searchtimer->Lifetime(); marginstart = searchtimer->MarginStart(); marginstop = searchtimer->MarginStop(); usevps = searchtimer->UseVPS(); delmode = searchtimer->DelMode(); delaftercountrecs = searchtimer->DelAfterCountRecs(); delafterdaysoffirstrec = searchtimer->DelAfterDaysOfFirstRec(); editsearchtimer = searchtimer; } else { for(unsigned int i=0; i(EPGSearchSetupValues::ReadValue("DefPriority")); lifetime = lexical_cast(EPGSearchSetupValues::ReadValue("DefLifetime")); marginstart = lexical_cast(EPGSearchSetupValues::ReadValue("DefMarginStart")); marginstop = lexical_cast(EPGSearchSetupValues::ReadValue("DefMarginStop")); } <& pageelems.doc_type &> VDR Live - <$ editsearchtimer ? tr("Edit search timer") : tr("New search timer") $> <& pageelems.stylesheets &> <& pageelems.ajax_js &> <& pageelems.logo &> <& menu active=("searchtimers") &>
    % if (extEPGInfos.size() > 0) { % }
    <$ editsearchtimer ? tr("Edit search timer") : tr("New search timer") $>
    <$ tr("Search term") $>:
    <$ tr("Search mode" ) $>:
    <$ tr("Match case") $>:
    id="matchcase"/>
    <$ tr("Search in") $>:
    />
    />
    />
    <$ tr("Use extended EPG info" ) $>:
    onclick="changeduseextepginfo(this)" id="useextepginfo"/>
    <$ tr("Use channel" ) $>:
    <$ tr("Use time") $>:
    onclick="changedusetime(this)" id="usetime" />
    <$ tr("Use duration") $>:
    onclick="changeduseduration(this)" id="useduration" />
    <$ tr("Use day of week") $>:
    onclick="changeduseweekday(this)" id="useweekday" />
    <$ tr("Use blacklists" ) $>:
    <$ tr("Use in favorites menu") $>:
    />
    <$ tr("Use as search timer" ) $>:
    <%include>page_exit.eh vdr-plugin-live-3.1.3/pages/edit_timer.ecpp000066400000000000000000000334761414414333500206560ustar00rootroot00000000000000<%pre> #include #include #include #include #include #include #include #include #include #include using namespace vdrlive; <%args> // input parameters std::string timerid; std::string epgid; std::string async; // form parameters tChannelID channel; // used with Tntnet20 std::string channel_string = ""; // used with Tntnet30 int active = 1; std::string title = ""; std::string remoteServerName = ""; std::string date = ""; bool wday_mon = false; bool wday_tue = false; bool wday_wed = false; bool wday_thu = false; bool wday_fri = false; bool wday_sat = false; bool wday_sun = false; int start_h = 0; int start_m = 0; int end_h = 0; int end_m = 0; bool vps = 0; int priority = 0; int lifetime = 0; std::string aux = ""; std::string directory = ""; <%session scope="global"> bool logged_in(false); std::string edit_timerreferer; TimerConflictNotifier timerNotifier; <%request scope="page"> const cTimer* timer; <%include>page_init.eh <%cpp> #if TNTVERSION >= 30000 channel = channel.FromString(channel_string.c_str()); // Tntnet30: get struct channel from parameter string #endif if (!logged_in && LiveSetup().UseAuth()) return reply.redirect("login.html"); if (!cUser::CurrentUserHasRightTo(UR_EDITTIMERS)) throw HtmlError( tr("Sorry, no permission. Please contact your administrator!") ); bool ajaxReq = !async.empty() && (lexical_cast(async) != 0); tChannelID channelid = tChannelID(); tEventID eventid = tEventID(); std::string message; cMutexLock timersLock( &LiveTimerManager() ); SortedTimers& timers = LiveTimerManager().GetTimers(); timer = 0; int timerId = 0; if ( !timerid.empty() ) { std::string tId = SortedTimers::DecodeDomId(timerid); // dsyslog("live: DEBUG: TIMER: timerid = %s", timerid); // dsyslog("live: DEBUG: TIMER: tId = %s", tId.c_str()); timer = timers.GetByTimerId(tId); if ( timer == 0 ) throw HtmlError( tr("Couldn't find timer. Maybe you mistyped your request?") ); else timerId = timer->Id(); } if ( request.getMethod() == "POST" ) { const char* oldRemote = NULL; if ( timer != 0 ) { oldRemote = timer->Remote(); // dsyslog("live: found valid timer on server '%s'", oldRemote); } uint flags = ( active ? tfActive : 0 ) | ( vps ? tfVps : 0 ); std::string weekdays = std::string( wday_mon ? "M" : "-" ) + ( wday_tue ? "T" : "-" ) + ( wday_wed ? "W" : "-" ) + ( wday_thu ? "T" : "-" ) + ( wday_fri ? "F" : "-" ) + ( wday_sat ? "S" : "-" ) + ( wday_sun ? "S" : "-" ); int start = start_h * 100 + start_m; int stop = end_h * 100 + end_m; if (!directory.empty()) title = directory + "~" + title; if (title.empty()) message = tr("Please set a title for the timer!"); else { // TRANSLATORS: only adjust the ordering and separators, don't translate the m's, d's and y's time_t tdate = GetDateFromDatePicker(date, tr("mm/dd/yyyy")); std::string sdate = DatePickerToC(tdate, "yyyy-mm-dd"); const char* remote = ( remoteServerName == "") ? NULL : remoteServerName.c_str(); // dsyslog("live: remote '%s'", remote); LiveTimerManager().UpdateTimer( timerId, remote, oldRemote, flags, channel, weekdays, sdate, start, stop, priority, lifetime, title, aux ); timerNotifier.SetTimerModification(); return reply.redirect(!edit_timerreferer.empty()?edit_timerreferer:"timers.html"); } } if (message.empty()) edit_timerreferer = request.getHeader("Referer:", "timers.html"); std::unique_ptr eventTimer; if ( timer == 0 && !epgid.empty()) { EpgEvents::DecodeDomId(epgid, channelid, eventid); if ( channelid.Valid() && eventid != 0 ) { cSchedule const* schedule; #if VDRVERSNUM >= 20301 { LOCK_SCHEDULES_READ; schedule = Schedules->GetSchedule( channelid ); } #else cSchedulesLock schedLock; cSchedules const* schedules = cSchedules::Schedules( schedLock ); schedule = schedules->GetSchedule( channelid ); #endif const cEvent *event = schedule->GetEvent( eventid ); if (event) eventTimer.reset( new cTimer( event ) ); else { esyslog("live: edit timer with eventid %d not valid", eventid); eventTimer.reset( new cTimer() ); eventTimer->SetFlags( tfActive ); } } else { eventTimer.reset( new cTimer() ); eventTimer->SetFlags( tfActive ); } timer = eventTimer.get(); } if (timer == 0) { eventTimer.reset( new cTimer() ); eventTimer->SetFlags( tfActive ); timer = eventTimer.get(); } if ( timer != 0 ) { active = timer->Flags() & tfActive; channel = timer->Channel()->GetChannelID(); title = timer->File() ? timer->File() : ""; remoteServerName = timer->Remote() ? timer->Remote() : ""; // dsyslog("live: remoteServerName '%s'", remoteServerName.c_str()); if ( LiveFeatures().Recent() ) { std::vector directories = StringSplit( title, '~' ); if (directories.size() > 1) { directory = directories[0]; title = title.substr(directory.size()+1); } } // TRANSLATORS: only adjust the ordering and separators, don't translate the m's, d's and y's date = timer->Day() ? DatePickerToC(timer->Day(), tr("mm/dd/yyyy")) : ""; wday_mon = timer->WeekDays() & 0x01; wday_tue = timer->WeekDays() & 0x02; wday_wed = timer->WeekDays() & 0x04; wday_thu = timer->WeekDays() & 0x08; wday_fri = timer->WeekDays() & 0x10; wday_sat = timer->WeekDays() & 0x20; wday_sun = timer->WeekDays() & 0x40; start_h = ( timer->Start() / 100 ) % 100; start_m = timer->Start() % 100; end_h = ( timer->Stop() / 100 ) % 100; end_m = timer->Stop() % 100; vps = timer->Flags() & tfVps; priority = timer->Priority(); lifetime = timer->Lifetime(); aux = timer->Aux() ? timer->Aux() : ""; } <& pageelems.doc_type &> VDR Live - <$ timer ? tr("Edit timer") : tr("New timer") $> <%cpp> if (!ajaxReq) { <& pageelems.stylesheets &> <& pageelems.ajax_js &> <%cpp> } <%cpp> if (!ajaxReq) { <& pageelems.logo &> <& menu active=("timers") &> <%cpp> }
    <%cpp> #if TNTVERSION >= 30000 std::string name = "channel_string"; // Tntnet30 does not work with parameter type tChannelID #else std::string name = "channel"; #endif <%cpp> cStringList svdrpServerNames; if (GetSVDRPServerNames(&svdrpServerNames)) { svdrpServerNames.Sort(true); <%cpp> svdrpServerNames.Clear(); } <%cpp> if ( LiveFeatures().Recent() ) { RecordingDirs recordingdirs(true); <%cpp> }
    <$ timer ? tr("Edit timer") : tr("New timer") $>
    <$ trVDR("Active") $>:
    type="radio">
    type="radio">
    <$ trVDR("Channel") $>:
    <& channels_widget name=(name) channelid=(true) selected=(channel) &>
    <$ tr("Title" ) $>:
    <$ tr("Server" ) $>:
    <$ tr("Directory" ) $>:
    <$ trVDR("Day") $>:
    <$ tr("Weekday") $>:
    />
    />
    />
    />
    />
    />
    />
    <$ trVDR("Start") $>:
    :
    <$ trVDR("Stop") $>:
    :
    <$ tr("Use VPS") $>:
    />
    <$ trVDR("Priority") $>:
    <$ trVDR("Lifetime") $>:
    <$ message $>
    <%include>page_exit.eh vdr-plugin-live-3.1.3/pages/edit_user.ecpp000066400000000000000000000146251414414333500205070ustar00rootroot00000000000000<%pre> #include #include #include using namespace vdrlive; <%args> // input parameters std::string userid; // form parameters std::string username; std::string password; bool ur_editsetup = false; bool ur_addtimers = false; bool ur_deltimers = false; bool ur_delrecs = false; bool ur_useremote = false; bool ur_startreplay = false; bool ur_switchchnl = false; bool ur_addstimers = false; bool ur_delstimers = false; bool ur_editrecs = false; <%session scope="global"> bool logged_in(false); <%request scope="page"> cUser* editUser; <%include>page_init.eh <%cpp> if (!logged_in && LiveSetup().UseAuth()) return reply.redirect("login.html"); #define CHECKIF(x) reply.out() << ( (x) ? "checked=\"checked\"" : "" ); editUser = NULL; if (request.getMethod() == "POST") { if (!userid.empty()) { editUser = Users.GetByUserId( userid ); if ( editUser == 0 ) throw HtmlError( tr("Couldn't find user. Maybe you mistyped your request?") ); editUser->SetName(username); if (password != std::string(editUser->GetPasswordLength(), '*')) editUser->SetPassword(password); } else { if (Users.GetByUserName( username )) throw HtmlError( tr("This user name is already in use!") ); editUser = new cUser(Users.GetNewId(), username, password); Users.Add(editUser); } editUser->SetUserrights(0); if (ur_editsetup) editUser->SetRight(UR_EDITSETUP); if (ur_addtimers) editUser->SetRight(UR_EDITTIMERS); if (ur_deltimers) editUser->SetRight(UR_DELTIMERS); if (ur_delrecs) editUser->SetRight(UR_DELRECS); if (ur_useremote) editUser->SetRight(UR_USEREMOTE); if (ur_startreplay) editUser->SetRight(UR_STARTREPLAY); if (ur_switchchnl) editUser->SetRight(UR_SWITCHCHNL); if (ur_addstimers) editUser->SetRight(UR_EDITSTIMERS); if (ur_delstimers) editUser->SetRight(UR_DELSTIMERS); if (ur_editrecs) editUser->SetRight(UR_EDITRECS); Users.Save(); return reply.redirect("users.html"); } pageTitle = !userid.empty() ? tr("Edit user") : tr("New user"); if ( !userid.empty() ) { cUser* User = Users.GetByUserId( userid ); if ( User == 0 ) throw HtmlError( tr("Couldn't find user. Maybe you mistyped your request?") ); username = User->Name(); password = std::string(User->GetPasswordLength(), '*'); ur_editsetup = User->HasRightTo(UR_EDITSETUP); ur_addtimers = User->HasRightTo(UR_EDITTIMERS); ur_deltimers = User->HasRightTo(UR_DELTIMERS); ur_delrecs = User->HasRightTo(UR_DELRECS); ur_useremote = User->HasRightTo(UR_USEREMOTE); ur_startreplay = User->HasRightTo(UR_STARTREPLAY); ur_switchchnl = User->HasRightTo(UR_SWITCHCHNL); ur_addstimers = User->HasRightTo(UR_EDITSTIMERS); ur_delstimers = User->HasRightTo(UR_DELSTIMERS); ur_editrecs = User->HasRightTo(UR_EDITRECS); editUser = User; } else { ur_editsetup = true; ur_addtimers = true; ur_deltimers = true; ur_delrecs = true; ur_useremote = true; ur_startreplay = true; ur_switchchnl = true; ur_addstimers = true; ur_delstimers = true; ur_editrecs = true; } <& pageelems.doc_type &> VDR Live - <$ pageTitle $> <& pageelems.stylesheets &> <& pageelems.ajax_js &> <& pageelems.logo &> <& menu active=("users") &>
    <$ pageTitle $>
    <$ tr("Name" ) $>:
    <$ tr("Password" ) $>:
    <$ tr("User rights") $>:
    />
    />
    />
    />
    />
    />
    />
    <%cpp> if (LiveFeatures().Recent()) { />
    />
    <%cpp> } />
    <%include>page_exit.eh vdr-plugin-live-3.1.3/pages/epginfo.ecpp000066400000000000000000000106101414414333500201410ustar00rootroot00000000000000<%pre> #include #include #include #include #include #include #if VDRVERSNUM < 20301 namespace vdrlive { class SchedulesLock { public: SchedulesLock() : m_schedulesLock() {} operator cSchedulesLock& () { return m_schedulesLock; } private: SchedulesLock(SchedulesLock const &schedulesLock) {} cSchedulesLock m_schedulesLock; }; typedef stdext::shared_ptr SchedulesLockPtr; } #endif using namespace vdrlive; <%args> std::string epgid; std::string async; <%session scope="global"> bool logged_in(false); <%request scope="global"> EpgInfoPtr epgEvent; std::string epgImage; <%include>page_init.eh <%cpp> if (!logged_in && LiveSetup().UseAuth()) return reply.redirect("login.html"); pageTitle = tr("Electronic program guide information"); bool ajaxReq = !async.empty() && (lexical_cast(async) != 0); bool aboutBox = false; #if VDRVERSNUM >= 20301 bool isEvent = false; #else // One of these get initialized when needed. When freed by getting // out of scope they will release (if initialized) important // Semaphores/Locks. SchedulesLockPtr schedulesLockPtr; #endif RecordingsManagerPtr recordings; epgImage.clear(); if (!epgid.empty()) { const std::string recording_s("recording_"); const std::string event_s("event_"); const std::string aboutbox("aboutBox"); // check for recording: if (epgid.compare(0, recording_s.length(), recording_s) == 0) { recordings = LiveRecordingsManager(); const cRecording *recording = recordings->GetByMd5Hash(epgid); if (recording == 0) { throw HtmlError(tr("Couldn't find recording or no recordings available")); } epgEvent = EpgEvents::CreateEpgInfo(epgid, recording); epgImage = EpgEvents::PosterTvscraper(NULL, recording); } // check for event: else if (epgid.compare(0, event_s.length(), event_s) == 0) { #if VDRVERSNUM >= 20301 /* Need to lock here channels also, because CreateEpgInfo will lock * it also and this will result in a wrong lock order */ LOCK_CHANNELS_READ; LOCK_SCHEDULES_READ; epgEvent = EpgEvents::CreateEpgInfo(epgid, Schedules); isEvent = true; #else schedulesLockPtr = SchedulesLockPtr(new SchedulesLock); if (!schedulesLockPtr) { throw HtmlError(tr("Error aquiring schedules lock")); } const cSchedules* schedules = cSchedules::Schedules(*schedulesLockPtr); if (!schedules) { throw HtmlError(tr("Error aquiring schedules")); } epgEvent = EpgEvents::CreateEpgInfo(epgid, schedules); #endif const cEvent* event = epgEvent->Event(); epgImage = EpgEvents::PosterTvscraper(event, NULL); } // check for aboutbox: else if (epgid.compare(0, aboutbox.length(), aboutbox) == 0) { aboutBox = true; } } <& pageelems.doc_type &> VDR-Live - <$ pageTitle $> <%cpp> if (!ajaxReq) { <& pageelems.stylesheets &> <& pageelems.ajax_js &> <%cpp> } <%cpp> if (!ajaxReq) { <& pageelems.logo &> <& menu &> <%cpp> }
    <%cpp> if (epgEvent) { std::string start(epgEvent->StartTime("%a,") + std::string(" ") + epgEvent->StartTime(tr("%b %d %y")) + std::string(" ") + epgEvent->StartTime(tr("%I:%M %p"))); std::string tools_component; if (recordings) { tools_component = epgEvent->Archived().empty() ? "recordings.rec_tools" : "recordings.archived_disc" ; } #if VDRVERSNUM >= 20301 if (isEvent) #else if (schedulesLockPtr) #endif tools_component = "epginfo.epgTools"; <& pageelems.epg_tt_box boxId=(epgEvent->Id()) caption=(epgEvent->Caption()) tools_comp=(tools_component) time=(start) title=(epgEvent->Title()) short_descr=(epgEvent->ShortDescr()) long_descr=(epgEvent->LongDescr()) archived=(epgEvent->Archived()) elapsed=(epgEvent->Elapsed()) filename=(epgEvent->FileName()) epgImage=(epgImage) &> <%cpp> } if (aboutBox) { <& pageelems.about_tt_box &> <%cpp> }
    <%include>page_exit.eh <# ---------------------------------------------------------------------- #> <%def epgTools> <%args> std::string id; std::string title; int detail; <& pageelems.epg_tool_box detail=(detail) epgid=(id) title=(title) startTime=(epgEvent->GetStartTime()) endTime=(epgEvent->GetEndTime()) &> vdr-plugin-live-3.1.3/pages/error.ecpp000066400000000000000000000015551414414333500176530ustar00rootroot00000000000000<%pre> #include using namespace vdrlive; <%args> pageTitle; errorTitle = tr("Page error"); errorMessage; <%session scope="global"> bool logged_in(false); <{ if (!logged_in && LiveSetup().UseAuth()) return reply.redirect("login.html"); }> <& pageelems.doc_type &> VDR-Live - <$ pageTitle $> <& pageelems.stylesheets &> <& pageelems.logo &> <& menu &>
    <& error.error_widget errorTitle=(errorTitle) errorMessage=(errorMessage) &>
    <%def error_widget> <%args> errorTitle; errorMessage;
    <$ errorTitle $>
    <$ errorMessage $>
    vdr-plugin-live-3.1.3/pages/errors.vim000066400000000000000000000001731414414333500176750ustar00rootroot00000000000000make: menu.ecpp: Command not found make: channels_widget.ecpp: Command not found make: *** [channels_widget.cpp] Error 127 vdr-plugin-live-3.1.3/pages/event_widget.ecpp000066400000000000000000000013431414414333500212010ustar00rootroot00000000000000<%pre> #include using namespace vdrlive; <%args> title; short_description; description; channel_name; start; end; <%session scope="global"> bool logged_in(false); <{ if (!logged_in && LiveSetup().UseAuth()) return reply.redirect("login.html"); }>
    <$ channel_name $>
     
    <$ start $> - <$ end $>
    <$ title $>
    <$ short_description $>
    <$ description $>
    vdr-plugin-live-3.1.3/pages/ffw_recording.ecpp000066400000000000000000000010041414414333500213250ustar00rootroot00000000000000<%pre> #include #include using namespace vdrlive; <%args> std::string param; <%session scope="global"> bool logged_in(false); <%cpp> if (!logged_in && LiveSetup().UseAuth()) return reply.redirect("login.html"); reply.setContentType( "application/xml" ); ForwardRecordingTask task( param ); LiveTaskManager().Execute( task ); <& xmlresponse.ajax name=("ffw_recording") pname=("recording") value=(param) result=(task.Result()) error=(task.Error()) &> vdr-plugin-live-3.1.3/pages/ibox.ecpp000066400000000000000000000123101414414333500174520ustar00rootroot00000000000000<%pre> #include #include #include #include #include #include using namespace vdrlive; <%args> int update; <%session scope="global"> bool logged_in(false); int update_status(1); TimerConflictNotifier timerNotifier(); <%cpp> if (!logged_in && LiveSetup().UseAuth()) return reply.redirect("login.html"); std::string EMPTY_STR; tChannelID prev_chan; tChannelID next_chan; reply.setContentType( "application/xml" ); if (update_status != update) { update_status = update; } std::string infoMsg; std::string infoUrl; if (timerNotifier.ShouldNotify()) { infoMsg = timerNotifier.Message(); infoUrl = "timerconflicts.html"; } const char* NowReplaying = cReplayControl::NowReplaying(); EpgInfoPtr epgEvent; if (NowReplaying) { RecordingsManagerPtr recManager = LiveRecordingsManager(); #if VDRVERSNUM >= 20301 // is is OK to lock here, because CreateEpgInfo will *not* lock other lists LOCK_RECORDINGS_READ; cRecording *recording = (cRecording *)Recordings->GetByName(NowReplaying); #else cRecording *recording = Recordings.GetByName(NowReplaying); #endif if (recording) { epgEvent = EpgEvents::CreateEpgInfo(recManager->Md5Hash(recording), recording, tr("playing recording")); } } else { std::string CHANNEL_STR("channel"); #if VDRVERSNUM >= 20301 LOCK_CHANNELS_READ; #else ReadLock channelsLock( Channels ); #endif if (cDevice::CurrentChannel()) { const int SKIP_GAP = 1; #if VDRVERSNUM >= 20301 cChannel* Channel = (cChannel *)Channels->GetByNumber(cDevice::CurrentChannel()); cChannel* tmp = (cChannel *)Channels->GetByNumber(Channels->GetPrevNormal(cDevice::CurrentChannel()), -SKIP_GAP); #else cChannel* Channel = Channels.GetByNumber(cDevice::CurrentChannel()); cChannel* tmp = Channels.GetByNumber(Channels.GetPrevNormal(cDevice::CurrentChannel()), -SKIP_GAP); #endif if (tmp) prev_chan = tmp->GetChannelID(); #if VDRVERSNUM >= 20301 tmp = (cChannel *)Channels->GetByNumber(Channels->GetNextNormal(cDevice::CurrentChannel()), SKIP_GAP); #else tmp = Channels.GetByNumber(Channels.GetNextNormal(cDevice::CurrentChannel()), SKIP_GAP); #endif if (tmp) next_chan = tmp->GetChannelID(); const std::string chanName(Channel->Name()); #if VDRVERSNUM >= 20301 LOCK_SCHEDULES_READ; #else cSchedulesLock schedulesLock; const cSchedules* Schedules = cSchedules::Schedules(schedulesLock); #endif const cSchedule *Schedule = Schedules->GetSchedule(Channel); if (Schedule) { const cEvent *Event = Schedule->GetPresentEvent(); if (Event) { epgEvent = EpgEvents::CreateEpgInfo(Channel, Event, CHANNEL_STR.c_str()); } else { const std::string noInfo(tr("no epg info for current event!")); epgEvent = EpgEvents::CreateEpgInfo(CHANNEL_STR, chanName, noInfo); } } else { const std::string noInfo(tr("no epg info for current channel!")); epgEvent = EpgEvents::CreateEpgInfo(CHANNEL_STR, Channel->Name(), noInfo); } } else { const std::string chanName(tr("no current channel!")); epgEvent = EpgEvents::CreateEpgInfo(CHANNEL_STR, chanName, chanName); } } if (!epgEvent) { const std::string ERROR_STR("error"); const std::string noInfo(tr("error retrieving status info!")); const std::string chanName(tr("no current channel!")); epgEvent = EpgEvents::CreateEpgInfo(ERROR_STR, chanName, noInfo); } else { if (prev_chan.Valid() && next_chan.Valid()) { <& xmlresponse.ibox update=(update_status) type=(epgEvent->Id()) caption=(epgEvent->Caption()) currentTime=(epgEvent->CurrentTime(tr("%I:%M:%S %p"))) duration=(epgEvent->StartTime(tr("%I:%M %p")) + std::string(" - ") + epgEvent->EndTime(tr("%I:%M %p"))) title=(epgEvent->Title()) elapsed=(epgEvent->Elapsed()) prev_chan=(prev_chan) next_chan=(next_chan) infoMsg=(infoMsg) infoUrl=(infoUrl) &> <%cpp> } else if (prev_chan.Valid()) { <& xmlresponse.ibox update=(update_status) type=(epgEvent->Id()) caption=(epgEvent->Caption()) currentTime=(epgEvent->CurrentTime(tr("%I:%M:%S %p"))) duration=(epgEvent->StartTime(tr("%I:%M %p")) + std::string(" - ") + epgEvent->EndTime(tr("%I:%M %p"))) title=(epgEvent->Title()) elapsed=(epgEvent->Elapsed()) prev_chan=(prev_chan) infoMsg=(infoMsg) infoUrl=(infoUrl) &> <%cpp> } else if (next_chan.Valid()) { <& xmlresponse.ibox update=(update_status) type=(epgEvent->Id()) caption=(epgEvent->Caption()) currentTime=(epgEvent->CurrentTime(tr("%I:%M:%S %p"))) duration=(epgEvent->StartTime(tr("%I:%M %p")) + std::string(" - ") + epgEvent->EndTime(tr("%I:%M %p"))) title=(epgEvent->Title()) elapsed=(epgEvent->Elapsed()) next_chan=(next_chan) infoMsg=(infoMsg) infoUrl=(infoUrl) &> <%cpp> } else { <& xmlresponse.ibox update=(update_status) type=(epgEvent->Id()) caption=(epgEvent->Caption()) currentTime=(epgEvent->CurrentTime(tr("%I:%M:%S %p"))) duration=(epgEvent->StartTime(tr("%I:%M %p")) + std::string(" - ") + epgEvent->EndTime(tr("%I:%M %p"))) title=(epgEvent->Title()) elapsed=(epgEvent->Elapsed()) infoMsg=(infoMsg) infoUrl=(infoUrl) &> <%cpp> } } vdr-plugin-live-3.1.3/pages/keypress.ecpp000066400000000000000000000010051414414333500203550ustar00rootroot00000000000000<%pre> #include #include using namespace vdrlive; <%args> int keycode = kNone; <%session scope="global"> bool logged_in(false); <%cpp> if (!logged_in && LiveSetup().UseAuth()) return reply.redirect("login.html"); reply.setContentType("application/xml"); <$ keycode $> <$ cRemote::Put( (eKeys)keycode ) ? "1" : "0" $> vdr-plugin-live-3.1.3/pages/login.ecpp000066400000000000000000000034131414414333500176250ustar00rootroot00000000000000<%pre> #include #include using namespace vdrlive; <%args> std::string login; std::string password; std::string action; <%session scope="global"> bool logged_in(false); <{ std::string message; if (action == "login") { if (Users.ValidLogin(login, password)) { logged_in = true; cUsers::logged_in_user = login; } else { message = tr("Wrong username or password"); } } else if (action == "logout") { logged_in = false; cUsers::logged_in_user = ""; } LiveSetup().CheckLocalNet(request.getPeerIp()); if (logged_in || !LiveSetup().UseAuth()) return reply.redirect(LiveSetup().GetStartScreenLink()); }> <& pageelems.doc_type &> VDR-Live - <$ tr("Login") $> <& pageelems.stylesheets &> <& pageelems.ajax_js &>
    <$ tr("VDR Live Login") $>
    " alt="VDR Live">
    <$ message $>
    vdr-plugin-live-3.1.3/pages/menu.ecpp000066400000000000000000000074771414414333500174770ustar00rootroot00000000000000<%pre> #include #include #include using namespace vdrlive; <%args> active; component; <%session scope="global"> bool logged_in(false); TimerConflictNotifier timerNotifier(); <# scope="page" should be enough but does not work with tntnet 3.0 #> <%request scope="global"> std::string set_active; std::string set_component; <%cpp> if (!logged_in && LiveSetup().UseAuth()) return reply.redirect("login.html"); set_active = active; if (!component.empty()) { set_component = component; }
    ">
    <$ tr("Your attention is required") $>: <$ timerNotifier.Message() $> <$ tr("React") $> <%cpp> if (LiveSetup().GetUseAjax()) { ">| <$ tr("Dismiss") $> <%cpp> }
    <%cpp> if (!component.empty()) { <%cpp> }
    <%def setactive> <%args> current; <%cpp> if (current == set_active) { class="active"<%cpp> } <%def component> <%args> current; <%cpp> if ((current == set_active) && (!set_component.empty())) { <& (set_component) &><%cpp> } vdr-plugin-live-3.1.3/pages/multischedule.ecpp000066400000000000000000000514161414414333500213720ustar00rootroot00000000000000<%pre> #include #include #include #include #include using namespace vdrlive; struct SchedEntry { std::string title; std::string short_description; std::string description; std::string description_trunc; std::string start; std::string end; std::string day; std::string epgid; bool truncated; bool has_timer; int start_row; int row_count; }; std::string channel_groups_setting; std::vector channel_groups_names; std::vector< std::vector > channel_groups_numbers; std::vector times_names; std::vector times_start; <%args> int channel = -1; unsigned int time_para = 0; <%session scope="global"> bool logged_in(false); <%request scope="global"> unsigned int channel_group=0; unsigned int time_selected=0; <%include>page_init.eh <%cpp> if (!logged_in && LiveSetup().UseAuth()) return reply.redirect("login.html"); pageTitle = tr("MultiSchedule"); #if VDRVERSNUM < 20301 ReadLock channelsLock( Channels ); if ( !channelsLock ) throw HtmlError( tr("Couldn't aquire access to channels, please try again later.") ); #endif #define MAX_CHANNELS 10 #define MAX_DAYS 3 #define MAX_HOURS 8 #define MINUTES_PER_ROW 5 #define CHARACTERS_PER_ROW 30 if ( ( channel_groups_setting.compare(LiveSetup().GetChannelGroups()) != 0 ) || ( channel_groups_numbers.size() == 0 ) ) { #if VDRVERSNUM >= 20301 LOCK_CHANNELS_READ; #endif // build the groups of channels to display std::string channelGroups=LiveSetup().GetChannelGroups(); if ( channelGroups.empty() ) { // setup default channel groups int lastChannel = LiveSetup().GetLastChannel(); if ( lastChannel == 0 ) #if VDRVERSNUM >= 20301 lastChannel = Channels->MaxNumber(); #else lastChannel = Channels.MaxNumber(); #endif std::stringstream groups; int i = 0; #if VDRVERSNUM >= 20301 for (cChannel *channel = (cChannel *)Channels->First(); channel && (channel->Number() <= lastChannel); channel = (cChannel *)Channels->Next(channel)) #else for (cChannel *channel = Channels.First(); channel && (channel->Number() <= lastChannel); channel = Channels.Next(channel)) #endif { if (channel->GroupSep()) continue; groups << channel->Number(); if ( (++i % 5) == 0 ) groups << ";"; else groups << ","; } channelGroups = groups.str(); LiveSetup().SetChannelGroups( channelGroups ); } channel_groups_names.clear(); channel_groups_numbers.clear(); channel_groups_setting = channelGroups; size_t groupSep; std::string thisGroup = ""; while ( ! channelGroups.empty() ) { groupSep = channelGroups.find(';'); thisGroup = channelGroups.substr(0, groupSep ); if ( groupSep != channelGroups.npos ) channelGroups.erase(0, groupSep+1 ); else channelGroups=""; int cur_group_count=0; channel_groups_names.push_back( std::string() ); channel_groups_numbers.push_back( std::vector() ); while ( !thisGroup.empty() ) { std::string thisChannel; try { size_t channelSep = thisGroup.find(','); thisChannel = thisGroup.substr(0, channelSep ); if ( cur_group_count++ != 0 ) channel_groups_names.back() += std::string( " - " ); else channel_groups_names.back() += thisChannel += std::string( ": " ); if ( channelSep != thisGroup.npos ) thisGroup.erase( 0, channelSep+1 ); else thisGroup = ""; int channel_no = lexical_cast< int > (thisChannel); #if VDRVERSNUM >= 20301 cChannel* Channel = (cChannel *)Channels->GetByNumber( channel_no ); #else cChannel* Channel = Channels.GetByNumber( channel_no ); #endif if ( !Channel ) { esyslog("live: could not find channel no '%s'.", thisChannel.c_str() ); continue; } channel_groups_names.back() += std::string( Channel->Name() ); channel_groups_numbers.back().push_back( Channel->Number() ); if ( cur_group_count>=MAX_CHANNELS ) { // start new group if group gets too large cur_group_count=0; channel_groups_names.push_back( std::string() ); channel_groups_numbers.push_back( std::vector() ); } } catch ( const bad_lexical_cast & ) { esyslog("live: could not convert '%s' into a channel number", thisChannel.c_str()); continue; } } } } if ( channel < 0 ) { if (cDevice::CurrentChannel()) { // find group corresponding to current channel int curGroup =0; int curChannel = cDevice::CurrentChannel(); for ( std::vector< std::vector >::iterator grIt = channel_groups_numbers.begin(); grIt != channel_groups_numbers.end() && channel < 0; ++grIt, ++curGroup ) { for ( std::vector::iterator chIt = (*grIt).begin(); chIt != (*grIt).end() && channel < 0; ++ chIt ) { if ( *chIt == curChannel ) channel_group = channel = curGroup; } } // if nothing is found, fall back to group 0 if ( channel < 0 ) channel = 0; } else { channel_group = channel; } } if ( channel >= (int)channel_groups_numbers.size() ) channel = 0; channel_group = channel; { // build time list times_names.clear(); times_start.clear(); // calculate time of midnight (localtime) and convert back to GMT time_t now = (time(NULL)/3600)*3600; time_t now_local = time(NULL); struct tm tm_r; if ( localtime_r( &now_local, &tm_r ) == 0 ) { std::stringstream builder; builder << "cannot represent timestamp " << now_local << " as local time"; throw std::runtime_error( builder.str() ); } tm_r.tm_hour=0; tm_r.tm_min=0; tm_r.tm_sec=0; time_t midnight = mktime( &tm_r ); // add four 8h steps per day to the time list for (int i=0; i<4*MAX_DAYS ; i++ ) { times_start.push_back( midnight + MAX_HOURS*3600*i ); } std::vector< std::string > parts = StringSplit( LiveSetup().GetTimes(), ';' ); std::vector< time_t > offsets; std::vector< std::string >::const_iterator part = parts.begin(); for ( ; part != parts.end(); ++part ) { try { unsigned int sep = (*part).find(':'); std::string hour = (*part).substr(0, sep ); if ( sep == (*part).npos ) { esyslog("live: Error parsing time '%s'", (*part).c_str() ); continue; } std::string min = (*part).substr(sep+1, (*part).npos ); offsets.push_back( lexical_cast( hour )*60*60 + lexical_cast( min ) *60 ); } catch ( const bad_lexical_cast & ) { esyslog("live: Error parsing time '%s'", part->c_str() ); }; }; // add the time of the favourites to the time list for (int i=0; i< MAX_DAYS ; i++ ) { std::vector< time_t >::const_iterator offset = offsets.begin(); for ( ; offset != offsets.end(); ++offset ) { times_start.push_back( midnight + 24*3600*i + *offset ); } } // add now times_start.push_back( now ); // sort the times std::sort( times_start.begin(), times_start.end() ); // delete every time which has already passed while ( *times_start.begin()< now ) times_start.erase(times_start.begin() ); // build the corresponding names for ( std::vector< time_t >::const_iterator start = times_start.begin(); start != times_start.end(); ++start ) { times_names.push_back(FormatDateTime( tr("%A, %x"), *start) +std::string(" ")+ FormatDateTime( tr("%I:%M %p"), *start) ); } // the first time is now times_names[0]=tr("Now"); if ( time_para >= times_names.size() ) time_para = times_names.size()-1; time_selected=time_para; } <& pageelems.doc_type &> VDR Live - <$ pageTitle $> <& pageelems.stylesheets &> <& pageelems.ajax_js &> <& pageelems.logo &> <& menu active=("multischedule") component=("multischedule.channel_selection") &>
    <%cpp> #if VDRVERSNUM < 20301 cSchedulesLock schedulesLock; cSchedules const* schedules = cSchedules::Schedules( schedulesLock ); #endif time_t now = time(NULL); if ( time_para >= times_start.size() ) time_para = times_start.size()-1; time_t sched_start = (times_start[ time_para ]/300)*300; time_t max_hours; try { max_hours = lexical_cast( LiveSetup().GetScheduleDuration() ); } catch ( const bad_lexical_cast & ) { esyslog("live: could not convert '%s' into a schedule duration", LiveSetup().GetScheduleDuration().c_str()); max_hours = 8; }; if (max_hours > 48) max_hours = 48; time_t sched_end = sched_start + 60 * 60 * max_hours; int sched_end_row = ( sched_end - sched_start ) / 60 / MINUTES_PER_ROW; std::list table[MAX_CHANNELS]; std::vector channel_names(channel_groups_numbers[ channel ].size() ); std::vector channel_IDs(channel_groups_numbers[ channel ].size() ); if ( channel >= (int)channel_groups_numbers.size() ) channel = channel_groups_numbers.size()-1; //for ( int chan = 0; chan= 20301 { LOCK_CHANNELS_READ; Channel = (cChannel *)Channels->GetByNumber( chan ); } #else Channel = Channels.GetByNumber( chan ); #endif if ( ! Channel ) continue; if ( Channel->GroupSep() || !Channel->Name() || !*Channel->Name() ) continue; channel_names[ j ] = Channel->Name(); channel_IDs[ j ] = Channel->GetChannelID(); cSchedule const* Schedule; #if VDRVERSNUM >= 20301 { LOCK_SCHEDULES_READ; Schedule = Schedules->GetSchedule( Channel ); } #else Schedule = schedules->GetSchedule( Channel ); #endif if ( ! Schedule ) continue; for (const cEvent *Event = Schedule->Events()->First(); Event; Event = Schedule->Events()->Next(Event) ) { if (Event->EndTime() <= sched_start ) continue; if (Event->StartTime() >= sched_end ) continue; EpgInfoPtr epgEvent = EpgEvents::CreateEpgInfo(Channel, Event); if ( prev_row < 0 && Event->StartTime() > sched_start + MINUTES_PER_ROW ) { // insert dummy event at start table[ j ].push_back( SchedEntry() ); SchedEntry &en=table[ j ].back(); int event_start_row = (Event->StartTime() - sched_start) / 60 / MINUTES_PER_ROW; en.start_row = 0; en.row_count = event_start_row; if ( en.row_count < 1 ) en.row_count = 1; // no title and no start time = dummy event en.title = ""; en.start = ""; prev_row = en.start_row + en.row_count; } table[ j ].push_back( SchedEntry() ); SchedEntry &en=table[j].back(); en.title = epgEvent->Title(); en.short_description = epgEvent->ShortDescr(); en.description = epgEvent->LongDescr(); en.start = epgEvent->StartTime(tr("%I:%M %p")); en.end = epgEvent->EndTime(tr("%I:%M %p")); en.day = epgEvent->StartTime(tr("%A, %b %d %Y")); en.epgid = EpgEvents::EncodeDomId(Channel->GetChannelID(), Event->EventID()); en.has_timer = LiveTimerManager().GetTimer(Event->EventID(), Channel->GetChannelID() ) != 0; en.start_row = prev_row > 0 ? prev_row : 0; int end_time = Schedule->Events()->Next(Event) ? Schedule->Events()->Next(Event)->StartTime() : Event->EndTime(); if (end_time > sched_end) end_time = sched_end; int next_event_start_row = (end_time - sched_start) / 60 / MINUTES_PER_ROW; en.row_count = next_event_start_row - en.start_row; if ( en.row_count < 1 ) en.row_count = 1; prev_row = en.start_row + en.row_count; // truncate description if too long en.truncated=false; en.description_trunc=StringWordTruncate( en.description, CHARACTERS_PER_ROW*(en.row_count-2), en.truncated ); }; if ( table[ j ].begin() == table[ j ].end() ) { // no entries... create a single dummy entry table[ j ].push_back( SchedEntry() ); SchedEntry &en=table[ j ].back(); en.start_row = 0; en.row_count = sched_end_row; // no title and no start time = dummy event en.title = ""; en.start = ""; } } <%cpp> <%cpp> for ( unsigned int channel = 0; channel< channel_names.size() ; channel++) { <%cpp> } <%cpp> bool odd=true; std::list::iterator cur_event[ MAX_CHANNELS ]; for (int i=0;i now ) { row_class +=" current_row "; } row_class += odd ? " odd " : " even "; <%cpp> for ( unsigned int channel = 0; channel< channel_names.size() ; channel++) { // output spacer column <%cpp> if ( cur_event[channel] == table[channel].end() || cur_event[channel]->start_row != row ) { // no new event in this channel, skip it SchedEntry &en=*(--table[channel].end()); if ( row == 0 || en.start_row + en.row_count == row) { <%cpp> } continue; } SchedEntry &en=*cur_event[channel]; if (en.title.empty() && en.start.empty() ) { // empty dummy event <%cpp> ++cur_event[channel]; continue; } // output an event cell <%cpp> // move to next event for this channel ++cur_event[channel]; } <%cpp> } <%cpp> for ( unsigned int channel = 0; channel <= channel_names.size() ; channel++) { <%cpp> }
    <$ tr("Time") $>
     
    <$ channel_names[channel] $> <# reply.sout() automatically escapes special characters to html entities #> <& pageelems.ajax_action_href action="switch_channel" tip=(tr("Switch to this channel.")) param=(channel_IDs[channel]) image="zap.png" alt="" &> <& pageelems.hls_channel channelId=(channel_IDs[channel]) &> <& pageelems.m3u_playlist_channel channelId=(channel_IDs[channel]) &>
     
    <%cpp> if ( minutes < MINUTES_PER_ROW ) { <$ FormatDateTime( tr("%I:%M %p"), sched_start + row * 60 * MINUTES_PER_ROW ) $> <%cpp> } else {   <%cpp> }  
    " rowspan="<$ en.row_count $>">
    <& pageelems.event_timer epgid=(en.epgid) &> <%cpp> if (LiveFeatures().Recent() ) { " alt="" <& tooltip.hint text=(tr("Search for repeats.")) &>> <%cpp> } else { <%cpp> } <& pageelems.imdb_info_href title=(en.title) &>
    <$ en.start $>
    <%cpp> if ( en.row_count>2 && !en.short_description.empty() ) {
    <$ en.short_description.empty() ? " " : en.short_description $>
    <%cpp> } if ( en.row_count>3 && ! en.description_trunc.empty() ) {
    <$en.description_trunc$>... <%cpp> if ( en.truncated ) { <& tooltip.display domId=en.epgid &>> <$ tr("more") $> <%cpp> }
    <%cpp> }
       
    <%include>page_exit.eh <%def channel_selection>
    % // <& pageelems.ajax_action_href action="switch_channel" tip=(tr("Switch to this channel.")) param=(Channel->GetChannelID()) image="zap.png" alt="" &>
    | « Prev | Next » vdr-plugin-live-3.1.3/pages/osd.ecpp000066400000000000000000000006301414414333500173000ustar00rootroot00000000000000<%pre> #include #include using namespace vdrlive; <%session scope="global"> bool logged_in(false); <%request scope="page"> <%cpp> if (!logged_in && LiveSetup().UseAuth()) return reply.redirect("login.html"); reply.setContentType( "application/xml" ); reply.out() << LiveOsdStatusMonitor().GetHtml(); vdr-plugin-live-3.1.3/pages/page_exit.eh000066400000000000000000000004571414414333500201340ustar00rootroot00000000000000<# do not add to Makefile #> <%pre> #include <%cpp> spoint.commit(); } catch ( vdrlive::HtmlError const& ex ) { tnt::QueryParams param = qparam; param.add( "pageTitle", pageTitle ); param.add( "errorMessage", ex.what() ); callComp( "error", request, reply, param ); } vdr-plugin-live-3.1.3/pages/page_init.eh000066400000000000000000000005711414414333500201230ustar00rootroot00000000000000<%pre> // do not add to Makefile // and do not write a ecpp comment into this file. It must produce no // html output not even an empty line. #include #include <%request scope="global"> std::string pageTitle; <%cpp> try { reply.setHeader("Expires", "Mon, 26 Jul 1997 05:00:00 GMT"); tnt::Savepoint spoint( reply ); vdr-plugin-live-3.1.3/pages/pageelems.ecpp000066400000000000000000000441251414414333500204640ustar00rootroot00000000000000<%pre> #include #include #include #include #include #include #include using namespace vdrlive; <%session scope="global"> int update_status(1); <# ---------------------------------------------------------------------- #> <%def doc_type> <# ---------------------------------------------------------------------- #> <%def stylesheets> "/> <# ---------------------------------------------------------------------- #> <%def logo>
    "><& pageelems.event_timer epgid=(epgid) &> "><& pageelems.ajax_action_href action="switch_channel" tip=(tr("Switch to this channel.")) param=(channelId) image="zap.png" alt="" &> "><%cpp>if (LiveFeatures().Recent() && eventId != 0) { " alt="" <& tooltip.hint text=(tr("Search for repeats.")) &>><%cpp> } else { <%cpp> } "><%cpp>if ((duration == 0) || (elapsed > 0)) { <& pageelems.hls_channel channelId=(channelId) &><%cpp> } "><%cpp>if ((duration == 0) || (elapsed > 0)) { <& pageelems.m3u_playlist_channel channelId=(channelId) &><%cpp> } "> <%cpp> if (eventId != 0) { <& pageelems.imdb_info_href title=(title) &> <%cpp> }
    <%cpp> std::string current_day = ""; const cEvent* PresentEvent = Schedule->GetPresentEvent(); time_t now = time(NULL) - ::Setup.EPGLinger * 60; RecordingsItemPtr recItem; bool recItemFound; #if VDRVERSNUM >= 20301 for (const cEvent *Event = (cEvent *)Schedule->Events()->First(); Event; Event = (cEvent *)Schedule->Events()->Next(Event)) { #else for (const cEvent *Event = Schedule->Events()->First(); Event; Event = Schedule->Events()->Next(Event)) { #endif if (Event->EndTime() <= now && Event != PresentEvent) continue; EpgInfoPtr epgEvent = EpgEvents::CreateEpgInfo(Channel, Event); std::string title(epgEvent->Title()); std::string short_description(epgEvent->ShortDescr()); std::string description(epgEvent->LongDescr()); std::string start(epgEvent->StartTime(tr("%I:%M %p"))); std::string end(epgEvent->EndTime(tr("%I:%M %p"))); int durationMinutes = epgEvent->Duration() / 60; std::string duration(durationMinutes < 0 ? "" : FormatDuration(tr("(%d:%02d)"), durationMinutes / 60, durationMinutes % 60)); std::string day(epgEvent->StartTime(tr("%A, %b %d %Y"))); std::string epgid = EpgEvents::EncodeDomId(Channel->GetChannelID(), Event->EventID()); bool truncated = false; bool lastEventCurrentDay = false; recItemFound = searchNameDesc(recItem, recItems, title, short_description, description, epgEvent->Duration() ); { #if VDRVERSNUM >= 20301 cEvent* NextEvent = (cEvent *)Schedule->Events()->Next(Event); #else cEvent* NextEvent = Schedule->Events()->Next(Event); #endif if (!NextEvent) { lastEventCurrentDay = true; } else { std::string nday(NextEvent->StartTime() ? FormatDateTime(tr("%A, %b %d %Y"), NextEvent->StartTime()) : ""); lastEventCurrentDay = (day != nday); } } if (current_day != day) { if (current_day != "") { <%cpp> } <%cpp> current_day = day; } <%cpp> if(recItemFound) { std::string duration(recItem->Duration() < 0 ? "" : FormatDuration(tr("(%d:%02d)"), recItem->Duration() / 60, recItem->Duration() % 60)); std::string shortDescr(recItem->RecInfo()->ShortText() ? recItem->RecInfo()->ShortText() : ""); #if VDRVERSNUM >= 20505 int recordingErrors = recItem->RecInfo()->Errors(); #else int recordingErrors = -1; #endif std::string channelName(recItem->RecInfo()->ChannelName() ? recItem->RecInfo()->ChannelName() : ""); const char *SD_HD_icon = recItem->SD_HD_icon(); std::string r_description(recItem->RecInfo()->Description() ? recItem->RecInfo()->Description() : ""); std::string hint(tr("Click to view details.")); if (!shortDescr.empty()) hint = shortDescr + "
    " + hint; else if (!r_description.empty()) hint = r_description + "
    " + hint; std::string name(recItem->Name()); if( *(const char *)recItem->Recording()->Folder() ) { name.append(" ("); name.append( (const char *)recItem->Recording()->Folder() ); name.append(")"); } <& rec_item_file name=(name) level=(recItem->Level()) id=(recItem->Id()) startTime=(recItem->StartTime()) duration=(duration) hint=(hint) shortDescr=(shortDescr) recordingErrors=(recordingErrors) channelName=(channelName) SD_HD_icon=(SD_HD_icon) archived=(RecordingsManager::GetArchiveDescr(recItem->Recording())) lastEventCurrentDay=(lastEventCurrentDay) &> <%cpp> } <%cpp> }
    <$ day $>
    "><& pageelems.event_timer epgid=(epgid) &> "><%cpp>if (LiveFeatures().Recent() ) { " alt="" <& tooltip.hint text=(tr("Search for repeats.")) &>><%cpp> } else { <%cpp> } "><& pageelems.imdb_info_href title=(title) &> ">
    <$ start $> - <$ end $>
    <$ duration $>
    topaligned rightcol ">
    <%cpp> }
    <%include>page_exit.eh <%def channel_selection>
    <& channels_widget name=("channel") selected=(Channel ? *Channel->GetChannelID().ToString() : "") onchange=("document.forms.channels.submit()") &> <& pageelems.ajax_action_href action="switch_channel" tip=(tr("Switch to this channel.")) param=(Channel->GetChannelID()) image="zap.png" alt="" &> <& pageelems.hls_channel channelId=(Channel->GetChannelID()) &> <& pageelems.m3u_playlist_channel channelId=(Channel->GetChannelID()) &>
    | « Prev | Next » <# ---------------------------------------------------------------------- #> <%def rec_item_file> <%args> std::string name; int level; std::string id; time_t startTime; std::string duration; std::string hint; std::string shortDescr; int recordingErrors; std::string channelName; std::string SD_HD_icon; std::string archived; bool lastEventCurrentDay; <%cpp> if (!archived.empty()) {
    "> <$ tr("Existing Recording:") $> "> " alt="on_dvd" <& tooltip.hint text=(archived) &> /> "> <$ tr("Existing Recording:") $> ">
    <$ FormatDateTime(tr("%a,"), startTime) $> <$ FormatDateTime(tr("%b %d %y"), startTime) $> <$ FormatDateTime(tr("%I:%M %p"), startTime) $>
    <$ duration $>
    "> <%cpp> #if VDRVERSNUM >= 20505 std::string recording_errors_icon; std::string recordingErrorsStr; if (recordingErrors == 0) { recording_errors_icon = "NoRecordingErrors.png"; recordingErrorsStr = tr("No recording errors"); } if (recordingErrors == -1) { recording_errors_icon = "NotCheckedForRecordingErrors.png"; recordingErrorsStr = tr("Recording errors unknown") ; } if (recordingErrors > 0) { recording_errors_icon = "RecordingErrors.png"; recordingErrorsStr = tr("Number of recording errors:"); recordingErrorsStr += " " + std::to_string(recordingErrors); }
    " width = "16px" alt="" <& tooltip.hint text=(recordingErrorsStr) &> />
    " width = "25px" alt="" <& tooltip.hint text=(channelName) &> />
    <%cpp> #endif <%cpp> #if VDRVERSNUM >= 20505
    <%cpp> #endif
    <$ tr("Search settings") $>
    <$ tr("Search term") $>:
    <$ tr("Extended search") $>:
    onclick="changeduseextendedsearch(this)" id="useextendedsearch"/>
    <%include>page_exit.eh vdr-plugin-live-3.1.3/pages/searchresults.ecpp000066400000000000000000000107551414414333500214130ustar00rootroot00000000000000<%pre> #include #include #include #include using namespace vdrlive; <%args> // input parameters std::string searchtimerid; std::string searchtimerquery; std::string searchplain; <%session scope="global"> bool logged_in(false); <%request scope="page"> std::string searchterm=""; <%include>page_init.eh <%cpp> if (!logged_in && LiveSetup().UseAuth()) return reply.redirect("login.html"); pageTitle = tr("Search results"); SearchResults results; if (!searchtimerid.empty()) results.GetByID(lexical_cast< int >(searchtimerid)); if (!searchtimerquery.empty()) results.GetByQuery(SearchResults::PopQuery(searchtimerquery)); if (!searchplain.empty()) { SearchTimer s; s.SetId(0); s.SetSearch(searchplain); s.SetSearchMode(0); s.SetUseTitle(true); s.SetUseSubtitle(false); s.SetUseDescription(false); results.GetByQuery(s.ToText()); searchterm = searchplain; } <& pageelems.doc_type &> VDR-Live - <$ pageTitle $> <& pageelems.stylesheets &> <& pageelems.ajax_js &> <& pageelems.logo &> <& menu active=("searchepg") component=("searchresults.searchresults_actions") &>
    % if (results.size() == 0) { <$ tr("No search results") $> % } <%cpp> std::string current_day = ""; for (SearchResults::iterator result = results.begin(); result != results.end(); ++result) { #if VDRVERSNUM >= 20301 cStateKey StateKey; if (const cChannels *Channels = cChannels::GetChannelsRead(StateKey)) { #ifdef DEBUG_LOCK dsyslog("live: pages/searchresults.ecpp LOCK_CHANNELS_READ"); #endif cChannel* channel = (cChannel *)Channels->GetByChannelID(result->Channel()); #else cChannel* channel = Channels.GetByChannelID(result->Channel()); #endif if (!channel) { StateKey.Remove(); continue; } std::string channelname = channel->Name(); int channelnr = channel->Number(); std::string start(result->StartTime() ? FormatDateTime(tr("%I:%M %p"), result->StartTime()) : ""); std::string end(result->StopTime() ? FormatDateTime(tr("%I:%M %p"), result->StopTime()) : ""); std::string day(result->StartTime() ? FormatDateTime(tr("%A, %b %d %Y"), result->StartTime()) : ""); std::string description = result->Description(); std::string epgid = EpgEvents::EncodeDomId(result->Channel(), result->EventId()); bool truncated = false; bool bottom = false; SearchResults::iterator nextResult = result; ++nextResult; if (nextResult == results.end()) bottom = true; else { std::string nextDay(nextResult->StartTime() ? FormatDateTime(tr("%A, %b %d %Y"), nextResult->StartTime()) : ""); bottom = (day != nextDay); } if (current_day != day) { if (current_day != "") { % } % current_day = day; % } % StateKey.Remove(); // release channels read lock before calling event_timer which make a timers read lock % } % }
    <$ day $>
    "><& pageelems.event_timer epgid=(epgid) &> "> ">
    <$ start $> - <$ end $>
    ">
    <%include>page_exit.eh <%def searchresults_actions> <$ tr("New search timer") $> vdr-plugin-live-3.1.3/pages/searchtimers.ecpp000066400000000000000000000101701414414333500212040ustar00rootroot00000000000000<%pre> #include #include #include using namespace vdrlive; <%args> // input parameters std::string searchtimerid; std::string action; <%session scope="global"> bool logged_in(false); <%include>page_init.eh <%cpp> if (!logged_in && LiveSetup().UseAuth()) return reply.redirect("login.html"); <%cpp> pageTitle = tr("Searchtimers"); SearchTimers timers; if ( !searchtimerid.empty() ) { if (action == "toggle") timers.ToggleActive(searchtimerid); if (action == "delete") { if (!cUser::CurrentUserHasRightTo(UR_DELSTIMERS)) throw HtmlError( tr("Sorry, no permission. Please contact your administrator!") ); timers.Delete(searchtimerid); } } if (action == "update") timers.TriggerUpdate(); <& pageelems.doc_type &> VDR-Live - <$ pageTitle $> <& pageelems.stylesheets &> <& pageelems.ajax_js &> <& pageelems.logo &> <& menu active=("searchtimers") component=("searchtimers.searchtimer_actions")>
    <%cpp> for (SearchTimers::iterator timer = timers.begin(); timer != timers.end(); ++timer) { SearchTimers::iterator nextTimer = timer; ++nextTimer; bool bottom = (nextTimer == timers.end()); <%cpp> }
    <$ pageTitle $>
    <$ tr("Expression") $>
    <$ tr("Channel") $>
    <$ tr("Starts between") $>
    "><%cpp> if(timer->UseAsSearchTimer()) { " alt=""><%cpp> } ">
    <$ timer->Search() $>
    ">
    <$ timer->ChannelText() $>
    ">
    UseTime() ? timer->StartTimeFormatted()+" - "+timer->StopTimeFormatted() ?>
    ">UseAsSearchTimer() ? "active.png" : "inactive.png") $>" alt="<$ tr("Toggle search timer actions (in)active") $>" <& tooltip.hint text=(tr("Toggle search timer actions (in)active")) &>> ">" alt="<$ tr("Browse search timer results") $>" <& tooltip.hint text=(tr("Browse search timer results")) &>> ">" alt="<$ tr("Edit search timer") $>" <& tooltip.hint text=(tr("Edit search timer")) &>> ">')">" alt="<$ tr("Delete search timer") $>" <& tooltip.hint text=(tr("Delete search timer")) &>>
    <%include>page_exit.eh <%def searchtimer_actions> <$ tr("New search timer") $> | <$ tr("Trigger search timer update") $> vdr-plugin-live-3.1.3/pages/setup.ecpp000066400000000000000000000357331414414333500176670ustar00rootroot00000000000000<%pre> #include #include #include #include using namespace vdrlive; <%args> std::string lastchannel; bool useauth = false; std::string login; std::string pass; std::string times; std::string channelGroups; std::string scheduleDuration; std::string startscreen; std::string theme; std::string localnetmask; std::string showLogo; std::string useAjax; std::string showInfoBox; std::string useStreamdev; std::string markNewRec; std::string showIMDb; std::string showChannelsWithoutEPG; std::string streamdevport; std::string streamdevtype; std::string streamVideoOpt0; std::string streamVideoOpt1; std::string streamVideoOpt2; std::string streamVideoOpt3; int authchanged = 0; <%session scope="global"> bool logged_in(false); <%include>page_init.eh <%cpp> if (!logged_in && LiveSetup().UseAuth() ) return reply.redirect("login.html"); if (!cUser::CurrentUserHasRightTo(UR_EDITSETUP)) throw HtmlError( tr("Sorry, no permission. Please contact your administrator!") ); #define SELECTIF(x) reply.out() << ( (x) ? "selected=\"selected\"" : "" ); #define CHECKIF(x) reply.out() << ( (x) ? "checked=\"checked\"" : "" ); std::string message; if ( request.getMethod() == "POST") { if (authchanged && useauth && (login.empty() || pass.empty())) message = tr("Please set login and password!"); else { LiveSetup().SetLastChannel(lastchannel != "" ? lexical_cast< int >(lastchannel):0); LiveSetup().SetUseAuth(useauth); if (useauth) { LiveSetup().SetAdminLogin(login); if (pass != "") LiveSetup().SetAdminPassword(pass); LiveSetup().SetLocalNetMask(localnetmask); LiveSetup().CheckLocalNet(request.getPeerIp()); } LiveSetup().SetTimes(times); LiveSetup().SetChannelGroups(channelGroups); LiveSetup().SetScheduleDuration(scheduleDuration); LiveSetup().SetStartScreen(startscreen); LiveSetup().SetTheme(theme); LiveSetup().SetShowLogo(!showLogo.empty()); LiveSetup().SetUseAjax(!useAjax.empty()); if (LiveSetup().GetUseAjax()) { LiveSetup().SetShowInfoBox(!showInfoBox.empty()); } LiveSetup().SetUseStreamdev(!useStreamdev.empty()); LiveSetup().SetStreamdevPort(streamdevport.empty() ? 3000 : lexical_cast(streamdevport)); LiveSetup().SetStreamdevType(streamdevtype.empty() ? "PES" : streamdevtype); LiveSetup().SetMarkNewRec(!markNewRec.empty()); std::string def = "ffmpeg -loglevel warning -f mpegts -analyzeduration 1.2M -probesize 5M " "-i -map 0:v -map 0:a:0 -c:v copy -c:a aac -ac 2"; LiveSetup().SetStreamVideoOpt0(streamVideoOpt0.empty() ? def : streamVideoOpt0); LiveSetup().SetStreamVideoOpt1(streamVideoOpt1.empty() ? def : streamVideoOpt1); LiveSetup().SetStreamVideoOpt2(streamVideoOpt2.empty() ? def : streamVideoOpt2); LiveSetup().SetStreamVideoOpt3(streamVideoOpt3.empty() ? def : streamVideoOpt3); LiveSetup().SetShowIMDb(!showIMDb.empty()); LiveSetup().SetShowChannelsWithoutEPG(!showChannelsWithoutEPG.empty()); LiveSetup().SaveSetup(); message = tr("Setup saved."); } } pageTitle = tr("Setup"); int ilastchannel = LiveSetup().GetLastChannel(); if (ilastchannel == std::numeric_limits< int >::max()) lastchannel = ""; else lastchannel = lexical_cast(ilastchannel); login = LiveSetup().GetAdminLogin(); useauth = LiveSetup().GetUseAuth(); times = LiveSetup().GetTimes(); channelGroups = LiveSetup().GetChannelGroups(); scheduleDuration = LiveSetup().GetScheduleDuration(); startscreen = LiveSetup().GetStartScreen(); theme = LiveSetup().GetTheme(); localnetmask = LiveSetup().GetLocalNetMask(); showLogo = LiveSetup().GetShowLogo() ? "1" : ""; useAjax = LiveSetup().GetUseAjax() ? "1" : ""; showInfoBox = LiveSetup().GetShowInfoBox() ? "1" : ""; useStreamdev = LiveSetup().GetUseStreamdev() ? "1" : ""; streamdevport = lexical_cast(LiveSetup().GetStreamdevPort()); streamdevtype = LiveSetup().GetStreamdevType(); markNewRec = LiveSetup().GetMarkNewRec() ? "1" : ""; streamVideoOpt0 = LiveSetup().GetStreamVideoOpt0(); streamVideoOpt1 = LiveSetup().GetStreamVideoOpt1(); streamVideoOpt2 = LiveSetup().GetStreamVideoOpt2(); streamVideoOpt3 = LiveSetup().GetStreamVideoOpt3(); showIMDb = LiveSetup().GetShowIMDb() ? "1" : ""; showChannelsWithoutEPG = LiveSetup().GetShowChannelsWithoutEPG() ? "1" : ""; <& pageelems.doc_type &> VDR-Live - <$ pageTitle $> <& pageelems.stylesheets &> <& pageelems.ajax_js &> <& pageelems.logo &> <& menu active=("setup") &>
    <$ tr("Setup") $>
    <$ tr("Use authentication") $>:
    CHECKIF(useauth); onclick="changeduseauth(this)"/>
    <$ tr("Show live logo image") $>:
    CHECKIF(!showLogo.empty()); />
    <$ tr("Use ajax technology") $>:
    CHECKIF(!useAjax.empty()); onclick="changeduseajax(this)"/>
    <$ tr("Allow video streaming") $>:
    CHECKIF(!useStreamdev.empty()); onclick="changedusestreamdev(this)"/>
    <$ tr("Mark new recordings") $>:
    CHECKIF(!markNewRec.empty()); />
    <$ tr("Add links to IMDb") $>:
    CHECKIF(!showIMDb.empty()); />
    <$ tr("Last channel to display") $>:
    <$ tr("Additional fixed times in 'What's on?'") $>:
    <& tooltip.help text=(tr("Format is HH:MM. Separate multiple times with a semicolon")) &>
    <$ tr("Channel groups for MultiSchedule") $>:
    <& tooltip.help text=(tr("Separate channels with a comma ',', separate groups with a semi-colon ';'")) &>
    <$ tr("Duration of MultiSchedule in hours") $>:
    <$ tr("Show channels without EPG") $>:
    CHECKIF(!showChannelsWithoutEPG.empty()); />
    <$ tr("Start page") $>:
    <$ tr("Theme") $>:
    <%include>page_exit.eh vdr-plugin-live-3.1.3/pages/stop_recording.ecpp000066400000000000000000000010021414414333500215260ustar00rootroot00000000000000<%pre> #include #include using namespace vdrlive; <%args> std::string param; <%session scope="global"> bool logged_in(false); <%cpp> if (!logged_in && LiveSetup().UseAuth()) return reply.redirect("login.html"); reply.setContentType( "application/xml" ); StopRecordingTask task( param ); LiveTaskManager().Execute( task ); <& xmlresponse.ajax name=("stop_recording") pname=("recording") value=(param) result=(task.Result()) error=(task.Error()) &> vdr-plugin-live-3.1.3/pages/stream.ecpp000066400000000000000000000153721414414333500200170ustar00rootroot00000000000000<%pre> #include #include #include #include #include using namespace vdrlive; <%args> std::string channelid_str; int channel = 1; <%session scope="global"> bool logged_in(false); FFmpegThread *f_worker = NULL; <%request scope="global"> cChannel* Channel; <%include>page_init.eh <%cpp> tChannelID channelid; channelid = channelid.FromString(channelid_str.c_str()); if (!logged_in && LiveSetup().UseAuth()) return reply.redirect("login.html"); pageTitle = trVDR("Stream"); // check for session cookie. std::string session; if (request.hasCookie("tntnet")) { session = request.getCookie("tntnet"); dsyslog("vdrlive::stream::session(%s)", session.c_str()); } else { esyslog("vdrlive::stream: no session cookie found"); return reply.redirect(""); } #if VDRVERSNUM < 20301 ReadLock channelsLock( Channels ); if ( !channelsLock ) throw HtmlError( tr("Couldn't aquire access to channels, please try again later.") ); #endif if ( qparam.has("channelid_str") ) { #if VDRVERSNUM >= 20301 LOCK_CHANNELS_READ; cChannel *c = (cChannel *)Channels->GetByChannelID( channelid ); #else cChannel *c = Channels.GetByChannelID( channelid ); #endif if ( c ) channel = c->Number(); } if ( channel > 0 ) { #if VDRVERSNUM >= 20301 LOCK_CHANNELS_READ; Channel = (cChannel *)Channels->GetByNumber( channel ); #else Channel = Channels.GetByNumber( channel ); #endif } if ( Channel == 0 ) throw HtmlError( tr("Couldn't find channel or no channels available. Maybe you mistyped your request?") ); dsyslog("vdrlive::stream::vtype(%d)", Channel->Vtype()); dsyslog("vdrlive::stream::f_worker(%p)", f_worker); if ( !f_worker) { f_worker = new FFmpegThread(); dsyslog("vdrlive::stream: new FFmpegThread created"); } switch( Channel->Vtype() ) { case 27: // h264 f_worker->StartFFmpeg(session, channel, 0); break; case 36: // h265 f_worker->StartFFmpeg(session, channel, 1); break; case 2: // mpeg2 f_worker->StartFFmpeg(session, channel, 2); break; default: // others f_worker->StartFFmpeg(session, channel, 3); } dsyslog("vdrlive::stream::StartFFmpeg(%d)", channel); <# <& pageelems.doc_type &> #> VDR Live - <$ pageTitle $> <& pageelems.stylesheets &> <& pageelems.ajax_js &> <& pageelems.logo &> <& menu active=("stream") component=("stream.channel_selection") &>
    <%cpp> dsyslog("vdrlive::stream: generating EPG info"); const cSchedule *Schedule = NULL; { #if VDRVERSNUM >= 20301 LOCK_SCHEDULES_READ; #else cSchedulesLock schedulesLock; const cSchedules* Schedules = cSchedules::Schedules(schedulesLock); #endif Schedule = Schedules->GetSchedule(Channel); } const cEvent *Event = NULL; if (Schedule) Event = Schedule->GetPresentEvent(); EpgInfoPtr epgEvent; if (Event) { epgEvent = EpgEvents::CreateEpgInfo(Channel, Event); tChannelID chanId; tEventID eventId; EpgEvents::DecodeDomId(epgEvent->Id(), chanId, eventId); char const * timeFormat = tr("%I:%M %p"); char const * dateFormat = tr("%A, %x"); std::string headTime = FormatDateTime(timeFormat, time(0)); std::string headDate = FormatDateTime(dateFormat, time(0)); std::string startTime(epgEvent->StartTime(tr("%I:%M %p"))); std::string endTime(epgEvent->EndTime(tr("%I:%M %p"))); std::string startDate(epgEvent->StartTime(tr("%a, %x"))); std::string timeSpan(startTime + " - " + endTime); if (startTime.empty() && endTime.empty()) { timeSpan = headTime; startDate = headDate; } static const size_t maximumDescriptionLength = 300; std::string longDescription = StringEscapeAndBreak(StringWordTruncate(epgEvent->LongDescr(), maximumDescriptionLength)) + "

    " + tr("Click to view details."); <& pageelems.epg_tool_box detail=(0) epgid=(epgEvent->Id()) title=(epgEvent->Title()) startTime=(epgEvent->GetStartTime()) endTime=(epgEvent->GetEndTime()) lastCurrentChanel=(1) &>
    <$ (timeSpan) $>
    <& pageelems.progressbar progress=(epgEvent->Elapsed()) duration=(epgEvent->Duration()) &>
    %} // if (Event)
    <%include>page_exit.eh <%def channel_selection>
    <& channels_widget name=("channel") selected=(Channel ? *Channel->GetChannelID().ToString() : "") onchange=("player.stop(); document.forms.channels.submit()") &> <& pageelems.ajax_action_href action="switch_channel" tip=(tr("Switch to this channel.")) param=(Channel->GetChannelID()) image="zap.png" alt="" &>
    | « Prev | Next » vdr-plugin-live-3.1.3/pages/stream_data.ecpp000066400000000000000000000056661414414333500210150ustar00rootroot00000000000000<%pre> #include #include #include #include #include #include #include #include using namespace vdrlive; <%session scope="global"> bool logged_in(false); FFmpegThread *f_worker = NULL; <%cpp> if(f_worker) f_worker->Touch(); tnt::SessionUnlocker unlck = tnt::SessionUnlocker(request, true); reply.setHeader("Expires", "Mon, 26 Jul 1997 05:00:00 GMT"); // assign Mime type to repply tnt::MimeDb mimeDb("/etc/mime.types"); std::string mime = mimeDb.getMimetype(request.getPathInfo()); reply.setContentType(mime); // check for session cookie. TODO: handle secure cookie for ssl std::string session; if (request.hasCookie("tntnet")) { session = request.getCookie("tntnet"); } // forge target path from requested path. std::string path(request.getPathInfo()); if (path.substr(0, 7) == "/media/") { path.replace(0, 7, "/tmp/live-hls-buffer/"); } else return DECLINED; // try to open the target file uint8_t retry; if (path.rfind(".m3u8") != std::string::npos && path.find("master_") != std::string::npos) retry = 100; else retry = 5; std::ifstream f; do { f.open ( path.c_str(), std::ios::binary ); if (!f.is_open()) { usleep(200e3); } } while (!f.is_open() && --retry); // fail if file did not appear if (!f.is_open()) { dsyslog("vdrlive::stream_data: DECLINED"); return DECLINED; } // wait until TARGETDURATION in playlist is != 0 if (path.rfind(".m3u8") != std::string::npos && path.find("ffmpeg_") != std::string::npos) { std::string line; int count = 20; while(getline(f, line) && count) { if (line.find("#EXT-X-TARGETDURATION:") != std::string::npos) { if (! (atoi(line.erase(0, 22).c_str()))) { count--; f.close(); usleep(100e3); f.open( path.c_str(), std::ios::binary ); } } } f.clear(); // unset eof flag } usleep(100e3); f.seekg( 0, std::ios::end ); std::streamsize size = f.tellg(); f.seekg( 0, std::ios::beg ); unsigned httpReturn = HTTP_OK; std::string range = request.getHeader(tnt::httpheader::range); off_t offset = 0, stop = size-1; if (!range.empty()) { range.erase(0,6); std::stringstream ss(range); char tmp; ss >> offset >> tmp >> stop; dsyslog("vdrlive::stream_data::range(%ld to %ld)", offset, stop); if (offset > size) return HTTP_RANGE_NOT_SATISFIABLE; if ((stop+1) > size) stop = size - 1; httpReturn = HTTP_PARTIAL_CONTENT; std::stringstream contentRange; contentRange << offset << ('-') << stop << ('/') << size; reply.setHeader(tnt::httpheader::contentRange, contentRange.str()); f.seekg( offset, std::ios::beg ); } char buffer[KILOBYTE(64)]; size_t r, c = stop - offset+ 1; while (r = f.readsome(buffer, (c < (long int) sizeof(buffer))?c:sizeof(buffer))) { reply.out().write(buffer, r); c -= r; if (!reply.out()) { return HTTP_GONE; } #if TNT_WATCHDOG_SILENCE request.touch(); // retrigger the watchdog. #endif } reply.out() << std::flush; return httpReturn; vdr-plugin-live-3.1.3/pages/switch_channel.ecpp000066400000000000000000000020601414414333500215030ustar00rootroot00000000000000<%pre> #include #include #include #include using namespace vdrlive; <%args> std::string param; std::string async; <%cpp> tChannelID paramID; paramID = paramID.FromString(param.c_str()); bool ajaxReq = !async.empty() && (lexical_cast(async) != 0); std::string referrer; if (!cUser::CurrentUserHasRightTo(UR_SWITCHCHNL)) throw HtmlError( tr("Sorry, no permission. Please contact your administrator!") ); if (ajaxReq) { reply.setContentType( "application/xml" ); } else { referrer = request.getHeader("Referer:"); } SwitchChannelTask task( paramID ); LiveTaskManager().Execute( task ); if (!ajaxReq) { if (!referrer.empty()) { return reply.redirect(referrer); } Normale Seite:
    channel: <$ paramID $>
    result: <$ (task.Result()) $>
    error: <$ (task.Error()) $>
    Seitenende! <%cpp> } else { <& xmlresponse.ajax name=("switch_channel") pname=("channel") value=(paramID) result=(task.Result()) error=(task.Error()) &> <%cpp> } vdr-plugin-live-3.1.3/pages/timerconflicts.ecpp000066400000000000000000000157211414414333500215470ustar00rootroot00000000000000<%pre> #include #include #include #include #include using namespace vdrlive; static const size_t maximumDescriptionLength = 300; <%args> // input parameters <%session scope="global"> bool logged_in(false); <%include>page_init.eh <%cpp> if (!logged_in && LiveSetup().UseAuth()) return reply.redirect("login.html"); <%cpp> pageTitle = tr("Timer conflicts"); TimerConflicts timerConflicts; cMutexLock timersLock( &LiveTimerManager() ); SortedTimers& timers = LiveTimerManager().GetTimers(); <& pageelems.doc_type &> VDR-Live - <$ pageTitle $> <& pageelems.stylesheets &> <& pageelems.ajax_js &> <& pageelems.logo &> <& menu active=("timersconflicts")>
    % if (timerConflicts.size() == 0) { <$ tr("No timer conflicts") $> % } else { <%cpp> for (TimerConflicts::iterator conflict = timerConflicts.begin(); conflict != timerConflicts.end(); ++conflict) { const std::list< TimerInConflict >& conflTimers = conflict->ConflictingTimers(); for (std::list< TimerInConflict >::const_iterator confltimer = conflTimers.begin(); confltimer != conflTimers.end(); ++confltimer) { <%cpp> for (std::list::const_iterator timerIndex = confltimer->concurrentTimerIndices.begin(); timerIndex != confltimer->concurrentTimerIndices.end(); ++timerIndex) { #if VDRVERSNUM >= 20301 LOCK_TIMERS_READ; cTimer* timer = (cTimer *)Timers->GetById(*timerIndex, (confltimer->remote == "") ? NULL : confltimer->remote.c_str()); #else cTimer* timer = Timers.Get(*timerIndex-1); #endif if (!timer) continue; std::list< int >::const_iterator nexttimerIndex = timerIndex; ++nexttimerIndex; bool bottom = (nexttimerIndex == confltimer->concurrentTimerIndices.end()); std::string timerStateImg = "transparent.png"; std::string timerStateHint; if (timer->Id() == confltimer->timerIndex) { timerStateImg = "timerconflict.gif"; timerStateHint = tr("Timer has a conflict."); } else if (timer->Flags() & tfActive) { timerStateImg = "arrow.png"; timerStateHint = tr("Timer is active."); } EpgInfoPtr epgEvent; std::string longDescription; std::string searchTimName; std::string title; #if VDRVERSNUM >= 20301 if (!timer->Event()) { LOCK_SCHEDULES_READ; timer->SetEventFromSchedule(Schedules); } #else if (!timer->Event()) timer->SetEventFromSchedule(); #endif if (timer->Event()) { epgEvent = EpgEvents::CreateEpgInfo(timer->Channel(), timer->Event()); longDescription = StringEscapeAndBreak(SortedTimers::GetTimerInfo(*timer)) + "
    " + StringEscapeAndBreak(StringWordTruncate(epgEvent->LongDescr(), maximumDescriptionLength)) + "

    " + tr("Click to view details."); searchTimName = SortedTimers::SearchTimerInfo(*timer, "searchtimer"); title = epgEvent->Title(); }
    % } <%cpp> } }
    <$ FormatDateTime(tr("%A, %x"), conflict->ConflictTime()) + " " + FormatDateTime(tr("%I:%M %p"), conflict->ConflictTime()) + " - " + lexical_cast(confltimer->percentage) + "%" $><$ tr("Server")$>: <$ (confltimer->remote == "" ) ? tr("local") : confltimer->remote $>
    <$ trVDR("Channel") $>
    <$ trVDR("Start") $>
    <$ trVDR("Stop") $>
    <$ trVDR("Priority") $>
    <$ trVDR("File") $>
    <$ tr("Searchtimer") $>
    ">" alt="" <%cpp> if (!timerStateHint.empty()) { <& tooltip.hint text=(timerStateHint) &><%cpp> } > "> ">
    <$ FormatDateTime(tr("%I:%M %p"), timer->StartTime()) $>
    ">
    <$ FormatDateTime(tr("%I:%M %p"), timer->StopTime()) $>
    ">
    <$ timer->Priority() $>
    "> ">
    <$ searchTimName $>
    ">" alt="" <& tooltip.hint text=(tr("Search for repeats.")) &>> ">Flags() & tfActive) ? "active.png" : "inactive.png") $>" alt="" <& tooltip.hint text=(tr("Toggle timer active/inactive")) &>> "><& pageelems.edit_timer timerId=(timers.GetTimerId(*timer)) &> ">" alt="" <& tooltip.hint text=(tr("Delete timer")) &>>
    % }
    <%include>page_exit.eh vdr-plugin-live-3.1.3/pages/timers.ecpp000066400000000000000000000172431414414333500200260ustar00rootroot00000000000000<%pre> #include #include #include #include #include #include #include using namespace vdrlive; static const size_t maximumDescriptionLength = 300; <%args> // input parameters std::string timerid; std::string action; <%session scope="global"> bool logged_in(false); <%request scope="page"> const cTimer* timer; TimerConflictNotifier timerNotifier; <%include>page_init.eh <%cpp> if (!logged_in && LiveSetup().UseAuth()) return reply.redirect("login.html"); pageTitle = trVDR("Timers"); cMutexLock timersLock( &LiveTimerManager() ); SortedTimers& timers = LiveTimerManager().GetTimers(); timer = 0; if ( !timerid.empty() ) { std::string tId = SortedTimers::DecodeDomId(timerid); // dsyslog("live: DEBUG: TIMER: tId = %s", tId.c_str()); timer = timers.GetByTimerId(tId); if ( timer == 0 ) throw HtmlError( tr("Couldn't find timer. Maybe you mistyped your request?") ); if (action == "delete") { if (!cUser::CurrentUserHasRightTo(UR_DELTIMERS)) throw HtmlError( tr("Sorry, no permission. Please contact your administrator!") ); // dsyslog("live: timers.ecpp timer->Id() %d", timer->Id()); // dsyslog("live: timers.ecpp timer->Remote() %s", timer->Remote()); LiveTimerManager().DelTimer(timer->Id(), timer->Remote()); timerNotifier.SetTimerModification(); return reply.redirect("timers.html"); } if (action == "toggle") { LiveTimerManager().ToggleTimerActive(timer->Id(), timer->Remote()); timerNotifier.SetTimerModification(); return reply.redirect("timers.html"); } } std::string previousDay = ""; <& pageelems.doc_type &> VDR-Live - <$ pageTitle $> <& pageelems.stylesheets &> <& pageelems.ajax_js &> <& pageelems.logo &> <& menu active=("timers") component=("timers.timer_actions")>
    <%cpp> #ifdef DEBUG_LOCK dsyslog("live: pages/timers.ecpp LOCK_TIMERS_READ"); #endif LOCK_TIMERS_READ; cSortedTimers sortedTimers(Timers); if (sortedTimers.Size() == 0) { <$ tr("No timer defined") $> % } else { <%cpp> // output of the timer list: for (int i = 0; i < sortedTimers.Size(); i++) { const cTimer *timer = sortedTimers[i]; EpgInfoPtr epgEvent; std::string longDescription; std::string searchTimName; std::string searchTimId; if (timer->Event()) { epgEvent = EpgEvents::CreateEpgInfo(timer->Channel(), timer->Event()); longDescription = StringEscapeAndBreak(SortedTimers::GetTimerInfo(*timer)) + "
    " + StringEscapeAndBreak(StringWordTruncate(epgEvent->LongDescr(), maximumDescriptionLength)) + "

    " + tr("Click to view details."); searchTimName = SortedTimers::SearchTimerInfo(*timer, "searchtimer"); searchTimId = SortedTimers::SearchTimerInfo(*timer, "s-id"); } std::string currentDay = SortedTimers::GetTimerDays(timer); const cTimer *nextTimer = NULL; if (i < (sortedTimers.Size() - 1)) nextTimer = sortedTimers[i + 1]; bool bottom = false; if (i == sortedTimers.Size() - 1) bottom = true; else { std::string nextDay = SortedTimers::GetTimerDays(nextTimer); bottom = (currentDay != nextDay); } if (previousDay != currentDay) { if (!previousDay.empty()) { <%cpp> } previousDay = currentDay; <%cpp> } std::string timerStateImg = "transparent.png"; std::string timerStateHint; if (timer->Recording()) { timerStateImg = "arrow_rec.gif"; timerStateHint = tr("Timer is recording."); } else if (timer->Flags() & tfActive) { timerStateImg = "arrow.png"; timerStateHint = tr("Timer is active."); } % } else { <$ searchTimName $> % } <%cpp> } }
    <$ currentDay $>
    <$ trVDR("Channel") $>
    <$ trVDR("Start") $>
    <$ trVDR("Stop") $>
    <$ trVDR("File") $>
    <$ trVDR("Record on") $>
    <$ tr("Searchtimer") $>
    ">" alt="" <%cpp> if (!timerStateHint.empty()) { <& tooltip.hint text=(timerStateHint) &><%cpp> } > "> ">
    <$ FormatDateTime(tr("%I:%M %p"), timer->StartTime()) $>
    ">
    <$ FormatDateTime(tr("%I:%M %p"), timer->StopTime()) $>
    "> ">
    <$ timer->Local()?trVDR(" "):timer->Remote() $>
    ">
    % if ( !timer->Local()) { <$ searchTimName $>
    ">Flags() & tfActive) ? "active.png" : "inactive.png") $>" alt="" <& tooltip.hint text=(tr("Toggle timer active/inactive")) &>> "><& pageelems.edit_timer timerId=(timers.GetTimerId(*timer)) &> ">" alt="" <& tooltip.hint text=(tr("Delete timer")) &>>
    <%include>page_exit.eh <%def timer_actions> <$ tr("New timer") $> % if ( LiveFeatures< features::epgsearch >().Recent() ) { | <$ tr("Timer conflicts") $> % } vdr-plugin-live-3.1.3/pages/tooltip.ecpp000066400000000000000000000005411414414333500202060ustar00rootroot00000000000000<%pre> #include using namespace vdrlive; <%def hint> <%args> text; title="<$ text $>" <%def display> <%args> domId; href="epginfo.html?epgid=<$ domId $>" <%def help> <%args> text; " alt="" <& hint text=(text) &>> vdr-plugin-live-3.1.3/pages/users.ecpp000066400000000000000000000042011414414333500176520ustar00rootroot00000000000000<%pre> #include #include using namespace vdrlive; <%args> // input parameters std::string userid; std::string action; <%session scope="global"> bool logged_in(false); <%include>page_init.eh <%cpp> if (!logged_in && LiveSetup().UseAuth()) return reply.redirect("login.html"); <%cpp> pageTitle = tr("Users"); if ( !userid.empty() ) { if (action == "delete") { Users.Del(Users.GetByUserId( userid )); Users.Save(); } } <& pageelems.doc_type &> VDR-Live - <$ pageTitle $> <& pageelems.stylesheets &> <& pageelems.ajax_js &> <& pageelems.logo &> <& menu active=("users") component=("users.user_actions")>
    <%cpp> cUser* user = Users.First(); while (user) { bool bottom = (Users.Next(user) == NULL); if (user) { <%cpp> } user = Users.Next(user); }
    <$ pageTitle $>
    <$ tr("Name") $>
    ">
    <$ user->Name() $>
    ">" alt="" <& tooltip.hint text=(tr("Edit user")) &>> ">" alt="" <& tooltip.hint text=(tr("Delete user")) &>>
    <%include>page_exit.eh <%def user_actions> <$ tr("New user") $> vdr-plugin-live-3.1.3/pages/whats_on.ecpp000066400000000000000000000261021414414333500203370ustar00rootroot00000000000000<%pre> #include #include #include #include #include using namespace vdrlive; static const size_t maximumDescriptionLength = 300; static const size_t maximumTooltipHintLength = 150; <%args> type = "now"; std::string mode; std::string attime; std::string fixtime; <%session scope="global"> bool logged_in(false); <# scope="page" should be enough but does not work with tntnet 3.0 #> <%request scope="global"> std::string current_type; std::string current_mode; std::string current_attime; std::string current_fixtime; std::string current_displaytime; <%include>page_init.eh <%cpp> if (!logged_in && LiveSetup().UseAuth()) return reply.redirect("login.html"); std::string head; time_t seektime = 0; std::string displaytime; std::string headTime; std::string headDate; if (mode.empty()) mode = LiveSetup().GetLastWhatsOnListMode(); else LiveSetup().SetLastWhatsOnListMode(mode); current_type = type; current_mode = mode; current_attime = attime; current_fixtime = fixtime; char const * timeFormat = tr("%I:%M %p"); char const * dateFormat = tr("%A, %x"); if (mode == "detail") { dateFormat = tr("%a, %x"); } if (type == "now") { headTime = FormatDateTime(timeFormat, time(0)); headDate = FormatDateTime(dateFormat, time(0)); head = std::string(tr("What's running on")) + " " + headDate + " " + tr("at") + " " + headTime; } else if (type == "next") { headTime = FormatDateTime(timeFormat, time(0) + 3600); headDate = FormatDateTime(dateFormat, time(0) + 3600); head = tr("What's on next?"); } else if (type == "at") { if (attime != "") displaytime = ExpandTimeString(attime); else if (fixtime != "") displaytime = ExpandTimeString(fixtime); current_displaytime = displaytime; seektime = GetTimeT(displaytime); if (seektime - time(0) + 3600 < 0) // if wanted time is past more then 1h, then use tomorrow seektime += SECSINDAY; headTime = FormatDateTime(timeFormat, seektime); headDate = FormatDateTime(dateFormat, seektime); head = std::string(tr("What's running on")) + " " + headDate + " " + tr("at") + std::string(" ") + headTime; } else if (type == "favs") { head = tr("Favorites"); } <& pageelems.doc_type &> VDR-Live - <$ head $> <& pageelems.stylesheets &> <& pageelems.ajax_js &> <& pageelems.logo &> <& menu active=("whats_on") component=("whats_on.whats_on_actions")>
    % if (mode == "list") { % } <%cpp> std::list eventList; // collect the broadcasts if (type != "favs") { #if VDRVERSNUM >= 20301 LOCK_CHANNELS_READ; { #else ReadLock channelsLock( Channels ); if (channelsLock) { #endif #if VDRVERSNUM >= 20301 for (cChannel *Channel = (cChannel *)Channels->First(); Channel && Channel->Number() <= LiveSetup().GetLastChannel(); Channel = (cChannel *)Channels->Next(Channel)) { #else for (cChannel *Channel = Channels.First(); Channel && Channel->Number() <= LiveSetup().GetLastChannel(); Channel = Channels.Next(Channel)) { #endif if (Channel->GroupSep()) { continue; } const cSchedule *Schedule; { #if VDRVERSNUM >= 20301 LOCK_SCHEDULES_READ; #else cSchedulesLock schedulesLock; const cSchedules* Schedules = cSchedules::Schedules(schedulesLock); #endif Schedule = Schedules->GetSchedule(Channel); } if (!Schedule) { continue; } const cEvent *Event = NULL; if (type == "now") Event = Schedule->GetPresentEvent(); else if (type == "next") Event = Schedule->GetFollowingEvent(); else if (type == "at") Event = Schedule->GetEventAround(seektime); if (!Event && !LiveSetup().GetShowChannelsWithoutEPG()) continue; EpgInfoPtr epgEvent = EpgEvents::CreateEpgInfo(Channel, Event); eventList.push_back(epgEvent); } } } else // get favorite broadcasts from epgsearch { SearchResults results; SearchTimers timers; for (SearchTimers::iterator timer = timers.begin(); timer != timers.end(); ++timer) { if (!timer->UseInFavorites()) continue; SearchResults curresults; curresults.GetByID(timer->Id()); results.merge(curresults); } time_t now = time(NULL); for (SearchResults::iterator result = results.begin(); result != results.end(); ++result) { long diff = result->StartTime() - now; if (labs(diff) >= 24*60*60) continue; // skip broadcasts more than a day away const cChannel* Channel = result->GetChannel(); if (!Channel) continue; EpgInfoPtr epgEvent = EpgEvents::CreateEpgInfo(Channel, result->GetEvent(Channel)); eventList.push_back(epgEvent); } } // display broadcasts for(std::list::iterator i = eventList.begin(); i != eventList.end(); ++i ) { EpgInfoPtr epgEvent = *i; bool truncated = false; std::string truncDescription = StringWordTruncate(epgEvent->LongDescr(), maximumTooltipHintLength, truncated); std::string longDescription = StringEscapeAndBreak(StringWordTruncate(epgEvent->LongDescr(), maximumDescriptionLength)) + "

    " + tr("Click to view details."); const cChannel* Channel = epgEvent->Channel(); if (!Channel) continue; int chNumber = Channel->Number(); std::string startTime(epgEvent->StartTime(tr("%I:%M %p"))); std::string endTime(epgEvent->EndTime(tr("%I:%M %p"))); std::string startDate(epgEvent->StartTime(tr("%a, %x"))); std::string timeSpan(startTime + " - " + endTime); if (startTime.empty() && endTime.empty()) { timeSpan = mode=="detail" ? headTime : std::string(); startDate = headDate; } if (mode == "detail") {
    <& pageelems.epg_tool_box detail=(1) epgid=(epgEvent->Id()) title=(epgEvent->Title()) startTime=(epgEvent->GetStartTime()) endTime=(epgEvent->GetEndTime()) &>
    <$ (startDate) $>
    <$ (timeSpan) $>
    <& pageelems.progressbar progress=(epgEvent->Elapsed()) duration=(epgEvent->Duration()) &>
    <$ (epgEvent->Title()) $>
    <$ (epgEvent->ShortDescr()) $>
    <$ truncDescription $>
    % if (truncated) { % }
    <%cpp> } else { // mode == "list" std::list::iterator last = i; bool lastCurrentChanel = (++last == eventList.end()); tChannelID chanId; tEventID eventId; EpgEvents::DecodeDomId(epgEvent->Id(), chanId, eventId);
    <& pageelems.epg_tool_box detail=(0) epgid=(epgEvent->Id()) title=(epgEvent->Title()) startTime=(epgEvent->GetStartTime()) endTime=(epgEvent->GetEndTime()) lastCurrentChanel=(lastCurrentChanel ? 1 : 0) &> % } <%cpp> } % if (mode == "list") {
    <$ head $>
    ">
    <$ (timeSpan) $>
    <& pageelems.progressbar progress=(epgEvent->Elapsed()) duration=(epgEvent->Duration()) &>
    "> ">
    % }
    <%include>page_exit.eh <# ------------------------------------------------------------------------- #> <%def whats_on_actions> " href="whats_on.html?type=now&mode=<$ current_mode $>" id="nowhref"><$ tr("Now") $> | " href="whats_on.html?type=next&mode=<$ current_mode $>" id="nexthref"><$ tr("Next") $> | "><$ tr("What's on") $> | "><$ tr("at") $> " onchange="showspectime(this)"/> | % if ( LiveFeatures< features::epgsearch >().Recent() ) { " href="whats_on.html?type=favs&mode=<$ current_mode $>"><$ tr("Favorites") $> | % } % if ( current_mode == "list" ) { <$ tr("Details view") $> % } else { <$ tr("List view") $> % } vdr-plugin-live-3.1.3/pages/xmlresponse.ecpp000066400000000000000000000030661414414333500211000ustar00rootroot00000000000000<%pre> #include using namespace vdrlive; std::string const XMLHEADER = ""; <# ------------------------------------------------------------------------- #> <%def ajax> <%args> std::string name; std::string pname; std::string value; bool result; std::string error; <$$ XMLHEADER $> <$ value $> <$ result $> <$ error $> <# ------------------------------------------------------------------------- #> <%def ibox> <%args> int update; std::string type; std::string currentTime; std::string caption; std::string title; std::string duration; int elapsed; std::string prev_chan; std::string next_chan; std::string infoMsg; std::string infoUrl; <$$ XMLHEADER $> <$ update $> 1 <$ update $> <$ type $> <$ caption $> <$ currentTime $> <$ title $> <$ duration $> <$ elapsed $> <$ next_chan $> <$ prev_chan $> <%cpp> if (!infoMsg.empty()) { <$ infoMsg $> <$ infoUrl $> <%cpp> } vdr-plugin-live-3.1.3/po/000077500000000000000000000000001414414333500151625ustar00rootroot00000000000000vdr-plugin-live-3.1.3/po/cs_CZ.po000066400000000000000000000440131414414333500165250ustar00rootroot00000000000000# translation of cs_CZ.po to # VDR plugin language source file. # Copyright (C) 2007 Klaus Schmidinger # This file is distributed under the same license as the VDR-LIVE package. # Vladimr Brta , 2006 # msgid "" msgstr "" "Project-Id-Version: VDR-LIVE 0.2.0\n" "Report-Msgid-Bugs-To: \n" "PO-Revision-Date: 2010-05-10 22:15+0200\n" "Last-Translator: Vladimír Bárta ,Radek Stastny \n" "Language-Team: see developers in README\n" "Language: cs_CZ\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" msgid "Last channel to display" msgstr "Poslední zobrazovaný kanál" msgid "No limit" msgstr "Bez limitu" msgid "Use authentication" msgstr "Použít autentifikaci" msgid "No" msgstr "Ne" msgid "Yes" msgstr "Ano" msgid "Admin login" msgstr "" msgid "Admin password" msgstr "Admin heslo" #, c-format msgid "%A, %x" msgstr "" msgid "Searchtimer" msgstr "Nahrávání" msgid "Error in timer settings" msgstr "Chyba v nastavení nahrávání" msgid "Timer already defined" msgstr "Nahrávaní již nastaveno" msgid "Timer not defined" msgstr "Nahrávaní není nastaveno" msgid "Timers are being edited - try again later" msgstr "Nahrávaní někdo mění - zkuste později" msgid "On archive DVD No." msgstr "" msgid "On archive HDD No." msgstr "" msgid "Couldn't find channel or no channels available." msgstr "Kanál neexistuje, nebo je nedostupný." msgid "Couldn't switch to channel." msgstr "Nelze přepnout na kanál." msgid "Couldn't find recording or no recordings available." msgstr "Nelze nalézt nahrávky, nebo žádné neexistují" msgid "Cannot control playback!" msgstr "Přehrávaní nelze ovládat!" msgid "Not playing a recording." msgstr "Nepřehrává se nahrávka." msgid "Not playing the same recording as from request." msgstr "Nepřehrává se požadovaná nahrávka." msgid "Attempt to delete recording currently in playback." msgstr "Pokus o smazání sledované nahrávky" msgid "Epg error" msgstr "Chyba EPG" msgid "Wrong channel id" msgstr "Špatné číslo kanálu" msgid "Channel has no schedule" msgstr "Kanál nemá žádný program" msgid "Wrong event id" msgstr "Chybné id pořadu" msgid "Required minimum version of epgsearch: " msgstr "Potřebná minimální verze epgsearch: " msgid "All" msgstr "Vše" msgid "FTA" msgstr "FTA" msgid "%I:%M %p" msgstr "%H:%M" msgid "EPGSearch version outdated! Please update." msgstr "Verze EPGSearch je zastaralá! Použijte novější." msgid "Couldn't aquire primary device" msgstr "Nelze otevřít primární zarízení" msgid "Couldn't grab image from primary device" msgstr "Nelze získat obrázek z primárního zařízení" msgid "Timer conflict check detected " msgstr "" #, fuzzy msgid "conflict" msgstr "Nahrávání bez kolize" #, fuzzy msgid "conflicts" msgstr "Nahrávání bez kolize" msgid "Electronic program guide information" msgstr "Elektronický průvodce programem (EPG)" msgid "Couldn't find recording or no recordings available" msgstr "Nelze nalézt nahrávky, nebo žádné neexistují" msgid "Error aquiring schedules lock" msgstr "Chyba při vytváření zámku pro nahrávky" msgid "Error aquiring schedules" msgstr "Chyba při plánování" msgid "%b %d %y" msgstr "%d.%m." msgid "Searchtimers" msgstr "Automatické nahrávání" msgid "Sorry, no permission. Please contact your administrator!" msgstr "Bohužel chybějící oprávnění. Zeptejte se svého administrátora!" msgid "Expression" msgstr "Výraz" msgid "Channel" msgstr "Kanál" msgid "Starts between" msgstr "Začíná mezi" msgid "Toggle search timer actions (in)active" msgstr "(De)Aktivace automatického nahrávání" msgid "Browse search timer results" msgstr "Listování výsledky vyhledávání" msgid "Edit search timer" msgstr "Změna automatického nahrávaní" msgid "Delete this search timer?" msgstr "Smazat automatické nahrávání?" msgid "Delete search timer" msgstr "Smazat automatické nahrávání?" msgid "New search timer" msgstr "Nové automatické nahrávání" msgid "Trigger search timer update" msgstr "Aktualizovat položky nahrávání" msgid "Wrong username or password" msgstr "Chybné jméno, nebo heslo" msgid "Login" msgstr "přihlásit" msgid "VDR Live Login" msgstr "VDR Live autorizace" msgid "User" msgstr "Uživatel" msgid "Password" msgstr "Heslo" msgid "Remote Control" msgstr "Dálkové ovládání" msgid "Couldn't aquire access to channels, please try again later." msgstr "Nelze získat přístup k programům, zkuste později." msgid "Couldn't find channel or no channels available. Maybe you mistyped your request?" msgstr "Kanál je nedostupný, nebo neexistuje. Zkontrolujte zadání." msgid "Selection" msgstr "výběr" msgid "Snapshot interval" msgstr "Interval snímků obrazovky" msgid "Stop" msgstr "" #, c-format msgid "%a, %x" msgstr "%a, %x" msgid "What's running on" msgstr "Co právě bězí v " msgid "at" msgstr " " msgid "What's on next?" msgstr "Co běží potom?" msgid "Favorites" msgstr "Oblíbené" msgid "Click to view details." msgstr "Detaily" msgid "View the schedule of this channel" msgstr "Zobraz program kanálu" msgid " - " msgstr "" msgid "more" msgstr "více" msgid "Now" msgstr "Nyní" msgid "Next" msgstr "Další" msgid "What's on" msgstr "Co poběží v" msgid "Details view" msgstr "Detailní pohled" msgid "List view" msgstr "Souhrný pohled" msgid "Find more at the Internet Movie Database." msgstr "Vyhledej více na Internet Movie Database." msgid "Stream this channel into browser." msgstr "" msgid "Stream this recording into media player." msgstr "" msgid "Record this" msgstr "Nahrát" msgid "Edit timer" msgstr "Změna nahrávání" msgid "loading data" msgstr "aktualizace údajů" msgid "an error occured!" msgstr "problém!" msgid "Request succeeded!" msgstr "Požadevek úspěšný." msgid "Request failed!" msgstr "Požadavek selhal!" msgid "Sunday" msgstr "Neděle" msgid "Monday" msgstr "Pondělí" msgid "Tuesday" msgstr "Úterý" msgid "Wednesday" msgstr "Středa" msgid "Thursday" msgstr "Čtvrtek" msgid "Friday" msgstr "Pátek" msgid "Saturday" msgstr "Sobota" #. TRANSLATORS: only adjust the ordering and separators, don't translate the m's, d's and y's msgid "mm/dd/yyyy" msgstr "dd.mm.yyyy" msgid "January" msgstr "leden" msgid "February" msgstr "únor" msgid "March" msgstr "březen" msgid "April" msgstr "duben" msgid "May" msgstr "květen" msgid "June" msgstr "červen" msgid "July" msgstr "červenec" msgid "August" msgstr "srpen" msgid "September" msgstr "září" msgid "October" msgstr "říjen" msgid "November" msgstr "listopad" msgid "December" msgstr "prosinec" msgid "retrieving status ..." msgstr "zjišťování stavu..." msgid "Toggle updates on/off." msgstr "aktualizovat změny ano/ne." msgid "stop playback" msgstr "ukončit přehrávání" msgid "resume playback" msgstr "pokračovat v přehrávání" msgid "pause playback" msgstr "přerušit přehrávání" msgid "fast rewind" msgstr "rychle zpět" msgid "fast forward" msgstr "rychle vpřed" msgid "previous channel" msgstr "předchozí kanál" msgid "next channel" msgstr "další kanál" msgid "No server response!" msgstr "Server neodpovídá!" msgid "Failed to update infobox!" msgstr "Aktualizace infookna selhala!" msgid "Switch to this channel." msgstr "Přepnout" msgid "Search for repeats." msgstr "Vyhledat reprízy" msgid "Authors" msgstr "Autoři" msgid "Project Idea" msgstr "nápad" msgid "Webserver" msgstr "web server" msgid "Current Maintainer" msgstr "" msgid "Project leader" msgstr "vedoucí týmu" msgid "Content" msgstr "obsah" msgid "Graphics" msgstr "grafika" msgid "Information" msgstr "Informace" msgid "LIVE version" msgstr "LIVE verze" msgid "VDR version" msgstr "VDR verze" msgid "Features" msgstr "Moduly" msgid "active" msgstr "aktivní" msgid "required" msgstr "vyżadováno" msgid "Homepage" msgstr "Domovská stránka" msgid "Bugs and suggestions" msgstr "Chyby a připomínky" msgid "If you encounter any bugs or would like to suggest new features, please use our bugtracker" msgstr "Pokud odhalíte nějaké chyby, příp. si přejete nové vlastnosti, použijte prosím bugtracker" msgid "What's on?" msgstr "Právě se vysílá" msgid "MultiSchedule" msgstr "" msgid "Search" msgstr "Vyhledej" msgid "Recordings" msgstr "Nahrávky" msgid "Web-Streaming" msgstr "" msgid "Logout" msgstr "Odhlášení" msgid "Your attention is required" msgstr "Nutná reakce" msgid "React" msgstr "řešit" msgid "Dismiss" msgstr "neřešit" msgid "Couldn't find user. Maybe you mistyped your request?" msgstr "Nelze nalézt zadaného uživatele. Byl zadán správně?" msgid "This user name is already in use!" msgstr "Toto jméno již použito!" msgid "Edit user" msgstr "Změna uživatele" msgid "New user" msgstr "Nový uživatel" msgid "Name" msgstr "Jméno" msgid "User rights" msgstr "Oprávnění" msgid "Edit setup" msgstr "Měnit nastavení" msgid "Add or edit timers" msgstr "Nastavení nahrávání" msgid "Delete timers" msgstr "Rušení nahrávání" msgid "Delete recordings" msgstr "Mazání nahrávek" msgid "Use remote menu" msgstr "Menu dálkového ovládání" msgid "Start replay" msgstr "Přehrávání" msgid "Switch channel" msgstr "Přepínání" msgid "Add or edit search timers" msgstr "Nastavení aut. nahrávání" msgid "Delete search timers" msgstr "Mazání aut. nahrávání" #, fuzzy msgid "Edit recordings" msgstr "Seznam nahrávek" msgid "Save" msgstr "Uložit" msgid "Cancel" msgstr "Zrušit" msgid "Search results" msgstr "Výsledky vyhledávání" msgid "No search results" msgstr "žádné výsledky vyhledávání" msgid "%A, %b %d %Y" msgstr "%a, %d.%m. " msgid "Couldn't find timer. Maybe you mistyped your request?" msgstr "Nelze najít nahrávaní. Je správně zadáno?" msgid "Please set a title for the timer!" msgstr "Prosím nastavte název nahrávání!" msgid "New timer" msgstr "Nové nahrávání" msgid "Title" msgstr "Název" msgid "Server" msgstr "" msgid "Directory" msgstr "Složka" msgid "Weekday" msgstr "Den v týdnu" msgid "Use VPS" msgstr "Použít VPS" msgid "ERROR:" msgstr "CHYBA:" msgid "Deleted recording:" msgstr "Smazaná nahrávka:" msgid "List of recordings" msgstr "Seznam nahrávek" msgid "No recordings found" msgstr "žádné nahrávky" #, fuzzy msgid "Delete selected" msgstr "Smazat automatické nahrávání?" #, fuzzy, no-c-format msgid "%a," msgstr "%a, %x" #. TRANSLATORS: recording duration format HH:MM #, c-format msgid "(%d:%02d)" msgstr "" msgid "Sort by name" msgstr "" #, fuzzy msgid "Sort by date" msgstr "do data" msgid "Filter" msgstr "" msgid "Look in recordings titles and subtitles for the given string and display only the matching ones. You may also use perl compatible regular expressions (PCRE)." msgstr "" msgid "Expand all folders" msgstr "" msgid "Collapse all folders" msgstr "" msgid "Delete this recording from hard disc!" msgstr "Smazat nahrávku!" #, fuzzy msgid "Edit recording" msgstr "Seznam nahrávek" msgid "play this recording." msgstr "Přehrát nahrávku" msgid "No schedules available for this channel" msgstr "Pro kanál není dostupný program" msgid "Couldn't find searchtimer. Maybe you mistyped your request?" msgstr "Nelze najít Automatické nahrávání. Byl požadavek správně zadán?" msgid "Search text too short - use anyway?" msgstr "Zadaný text je krátký - opravdu použít?" msgid "Search term" msgstr "Vyhledej" msgid "Search mode" msgstr "Vyhledávací režim" msgid "phrase" msgstr "slovní spojení" msgid "all words" msgstr "všechna slova" msgid "at least one word" msgstr "alespoň jedno slovo" msgid "match exactly" msgstr "přesný výraz" msgid "regular expression" msgstr "regulární výraz" msgid "fuzzy" msgstr "" msgid "Tolerance" msgstr "Tolerance" msgid "Match case" msgstr "Velikost písmen" msgid "Search in" msgstr "Vyhledávat v" msgid "Episode" msgstr "Epizoda" msgid "Description" msgstr "Popis" msgid "Use extended EPG info" msgstr "Použít rozšířené EPG" msgid "Ignore missing EPG info" msgstr "Ignorovat chybějící EPG" msgid "When active this can cause very many timers. So please always first test this search before using it as search timer!" msgstr "Toto nastavení vytvoří mnoho požadavků na nahrávání. Před uložením oveřte chování pomocí Testu!" msgid "Use channel" msgstr "Použij kanál" msgid "interval" msgstr "" msgid "channel group" msgstr "skupina kanálů" msgid "only FTA" msgstr "Pouze FTA" msgid "from channel" msgstr "od kanálu" msgid "to channel" msgstr "do kanálu" msgid "Use time" msgstr "Použij čas" msgid "Start after" msgstr "Začíná po" msgid "The time the show may start at the earliest" msgstr "Čas, kdy může začít nejdříve." msgid "Start before" msgstr "Začíná před" msgid "The time the show may start at the latest" msgstr "Čas, kdy může začít nejpozději" msgid "Use duration" msgstr "Použij délku" msgid "Min. duration" msgstr "délka min." msgid "Max. duration" msgstr "délka max." msgid "Use day of week" msgstr "Použij den v týdnu" msgid "Use blacklists" msgstr "Použít blacklist" msgid "all" msgstr "vše" msgid "Use in favorites menu" msgstr "Přidat do Oblíbených" msgid "Use as search timer" msgstr "Další podmínky vyhledávání" msgid "user defined" msgstr "ano - dočasný" msgid "from date" msgstr "od data" msgid "to date" msgstr "do data" msgid "Record" msgstr "Nahrát" msgid "Announce only" msgstr "Pouze upozornit" msgid "Switch only" msgstr "Přepnout" msgid "Announce and Switch" msgstr "" msgid "Announce via email" msgstr "" msgid "Inactive Record" msgstr "" msgid "Series recording" msgstr "Nahrávání seriálu" msgid "Delete recordings after ... days" msgstr "Vymazat nahrávky po ... dnech" msgid "Keep ... recordings" msgstr "Ponechat ... nahrávek" msgid "Pause when ... recordings exist" msgstr "Vynechat pokud existuje ... nahrávek" msgid "Avoid repeats" msgstr "Vynechávat reprízy" msgid "Allowed repeats" msgstr "Povolit reprízy" msgid "Only repeats within ... days" msgstr "Reprízy po ... dnech" msgid "Compare title" msgstr "Porovnávat název" msgid "Compare subtitle" msgstr "Porovnávat popis" msgid "if present" msgstr "pokud existuje" msgid "Compare summary" msgstr "Porovnávat popis" msgid "Compare" msgstr "Porovnat" msgid "Auto-delete search timer" msgstr "Smazání automatického nahrávání" msgid "after ... recordings" msgstr "po ... nahrávkách" msgid "after ... days after first rec." msgstr "po ... dnech po první nahrávce" msgid "Switch ... minutes before start" msgstr "Přepnout ... minut před začátkem" msgid "Test" msgstr "" msgid "No timer defined" msgstr "Nahrávání není nastaveno" msgid "Timer is recording." msgstr "Probíhá nahrávání" msgid "Timer is active." msgstr "Nahrávaní povoleno" msgid "Toggle timer active/inactive" msgstr "Povolit/zrušit nahrávání" msgid "Delete timer" msgstr "Zrušit nahrávání!" #, fuzzy msgid "Timer conflicts" msgstr "Nahrávání bez kolize" msgid "Please set login and password!" msgstr "Nastavte uživatele a heslo!" msgid "Setup saved." msgstr "Nastavení uloženo." msgid "Setup" msgstr "Nastavení" msgid "User management" msgstr "" msgid "Local net (no login required)" msgstr "Lokální síť (bez přilášení)" msgid "Show live logo image" msgstr "Zobrazit logo" msgid "Use ajax technology" msgstr "Používat dynamické stránky (ajax)" msgid "Show dynamic VDR information box" msgstr "Použít dynamický blok informací" msgid "Allow video streaming" msgstr "Povolit streamování videa" msgid "Web-Streaming h264" msgstr "" msgid "Web-Streaming HEVC" msgstr "" msgid "Web-Streaming MPEG2" msgstr "" msgid "Web-Streaming others" msgstr "" msgid "Streamdev server port" msgstr "" msgid "Streamdev stream type" msgstr "Streamdev typ streamu" msgid "Mark new recordings" msgstr "" msgid "Add links to IMDb" msgstr "Přidat odkazy k IMDb" #, fuzzy msgid "Additional fixed times in 'What's on?'" msgstr "Přidat časy pro zobrazení v \"Právě se vysílá\"" msgid "Format is HH:MM. Separate multiple times with a semicolon" msgstr "Formát je HH:MM. Oddělte více časů středníkem" #, fuzzy msgid "Channel groups for MultiSchedule" msgstr "Kanál nemá žádný program" msgid "Separate channels with a comma ',', separate groups with a semi-colon ';'" msgstr "" msgid "Duration of MultiSchedule in hours" msgstr "" msgid "Show channels without EPG" msgstr "" msgid "Start page" msgstr "Startovací stránka" msgid "Theme" msgstr "Téma" msgid "playing recording" msgstr "sledování nahrávky" msgid "no epg info for current event!" msgstr "chybí detaily pro tento pořad" msgid "no epg info for current channel!" msgstr "chybí program pro tento kanál" msgid "no current channel!" msgstr "není zvolen žádný kanál" msgid "error retrieving status info!" msgstr "chyba při zjišťování statusu!" msgid "%I:%M:%S %p" msgstr "%H:%M:%S" #, fuzzy msgid "Couldn't find recording. Maybe you mistyped your request?" msgstr "Nelze nalézt zadaného uživatele. Byl zadán správně?" #, fuzzy msgid "Please set a name for the recording!" msgstr "Prosím nastavte název nahrávání!" msgid "Cannot copy, rename or move the recording." msgstr "" #, fuzzy msgid "Delete resume information" msgstr "Elektronický průvodce programem (EPG)" msgid "Delete marks information" msgstr "" msgid "Copy only" msgstr "" #, fuzzy msgid "Short description" msgstr "Popis" msgid "Auxiliary info" msgstr "" msgid "Search settings" msgstr "Nastavení hledání" msgid "Extended search" msgstr "Rozšířené hledání" msgid "no" msgstr "ne" msgid "Time" msgstr "" msgid "Users" msgstr "Uživatelé" msgid "Delete user" msgstr "Smazat uživatele" msgid "Page error" msgstr "Chyba stránky" msgid "No timer conflicts" msgstr "Nahrávání bez kolize" msgid "local" msgstr "" msgid "Timer has a conflict." msgstr "Kolize v nastavení nahrávání" msgid "Live Interactive VDR Environment" msgstr "" msgid "No EPG information available" msgstr "Nejsou dostupné informace o programu" #~ msgid "VLC media URL" #~ msgstr "VLC adresa" vdr-plugin-live-3.1.3/po/de_DE.po000066400000000000000000000466531414414333500165000ustar00rootroot00000000000000# VDR LIVE plugin language source file. # Copyright (C) 2007 LIVE Development team. See http://live.vdr-developer.org # This file is distributed under the same license as the VDR-LIVE package. # Klaus Schmiedinger , 2003 # Christian Wieniger , 2007 # Dieter Hametner , 2007 # msgid "" msgstr "" "Project-Id-Version: VDR-LIVE 0.2.0\n" "Report-Msgid-Bugs-To: \n" "PO-Revision-Date: 2007-08-19 20:15+0200\n" "Last-Translator: Dieter Hametner \n" "Language-Team: see developers in README\n" "Language: de_DE\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" msgid "Last channel to display" msgstr "EPG-Anzeige bis Kanal" msgid "No limit" msgstr "Alle zeigen" msgid "Use authentication" msgstr "Authentifizierung nutzen" msgid "No" msgstr "Nein" msgid "Yes" msgstr "Ja" msgid "Admin login" msgstr "Admin Login" msgid "Admin password" msgstr "Admin Passwort" #, c-format msgid "%A, %x" msgstr "%A, %x" msgid "Searchtimer" msgstr "Suchtimer" msgid "Error in timer settings" msgstr "Fehler in den Timer Einstellungen" msgid "Timer already defined" msgstr "Timer ist bereits vorhanden" msgid "Timer not defined" msgstr "Timer wurde nicht erstellt" msgid "Timers are being edited - try again later" msgstr "Timer werden bearbeitet - später nochmal versuchen" msgid "On archive DVD No." msgstr "Auf Archiv-DVD Nr." msgid "On archive HDD No." msgstr "Auf Archiv-HDD Nr." msgid "Couldn't find channel or no channels available." msgstr "Kann das Programm nicht finden oder keine vorhanden." msgid "Couldn't switch to channel." msgstr "Kann nicht zu dem Programm umschalten." msgid "Couldn't find recording or no recordings available." msgstr "Kann die Aufnahme nicht finden oder keine Aufnahmen vorhanden." msgid "Cannot control playback!" msgstr "Kann die Wiedergabe nicht steuern." msgid "Not playing a recording." msgstr "Es wird keine Aufnahme abgespielt." msgid "Not playing the same recording as from request." msgstr "Es wird nicht die selbe Aufnahme abgespielt." msgid "Attempt to delete recording currently in playback." msgstr "Versuch die gerade abgepielte Aufnahme zu löschen." msgid "Epg error" msgstr "EPG Fehler" msgid "Wrong channel id" msgstr "Fehlerhafte Kanal Id" msgid "Channel has no schedule" msgstr "Kanal hat keine Programminfos" msgid "Wrong event id" msgstr "Fehlerhafte Sendungs Id" msgid "Required minimum version of epgsearch: " msgstr "Benötigte Mindestversion von epgsearch: " msgid "All" msgstr "Alle" msgid "FTA" msgstr "FTA" msgid "%I:%M %p" msgstr "%H:%M" msgid "EPGSearch version outdated! Please update." msgstr "EPGSearch-Version zu alt, bitte updaten!" msgid "Couldn't aquire primary device" msgstr "Konnte auf das 'primary device' nicht zugreifen" msgid "Couldn't grab image from primary device" msgstr "Konnte kein Bild vom 'primary device' bekommen" msgid "Timer conflict check detected " msgstr "Die Timer Konflikt Überprüfung hat " msgid "conflict" msgstr "Timerkonflikt entdeckt" msgid "conflicts" msgstr "Timerkonflikte entdeckt" msgid "Electronic program guide information" msgstr "Elektronische Programminformation" msgid "Couldn't find recording or no recordings available" msgstr "Die Aufnahme nicht finden oder keine Aufnahmen vorhanden" msgid "Error aquiring schedules lock" msgstr "Fehler beim Zugriffschutz für die Programminfos" msgid "Error aquiring schedules" msgstr "Fehler beim Zugriff auf Programinfos" msgid "%b %d %y" msgstr "%d.%m.%y" msgid "Searchtimers" msgstr "Suchtimer" msgid "Sorry, no permission. Please contact your administrator!" msgstr "Leider nicht erlaubt. Bitte kontaktieren Sie Ihren Administrator!" msgid "Expression" msgstr "Suchbegriff" msgid "Channel" msgstr "Kanal" msgid "Starts between" msgstr "Beginnt zwischen" msgid "Toggle search timer actions (in)active" msgstr "Aktionen des Suchtimers (de)aktivieren" msgid "Browse search timer results" msgstr "Suchtimerergebnisse betrachten" msgid "Edit search timer" msgstr "Suchtimer bearbeiten" msgid "Delete this search timer?" msgstr "Diesen Suchtimer löschen?" msgid "Delete search timer" msgstr "Suchtimer löschen" msgid "New search timer" msgstr "Neuen Suchtimer anlegen" msgid "Trigger search timer update" msgstr "Suchtimer-Update starten" msgid "Wrong username or password" msgstr "Falscher Benutzername oder Passwort" msgid "Login" msgstr "Anmelden" msgid "VDR Live Login" msgstr "VDR Live Login" msgid "User" msgstr "Benutzer" msgid "Password" msgstr "Passwort" msgid "Remote Control" msgstr "Fernbedienung" msgid "Couldn't aquire access to channels, please try again later." msgstr "Zugriff auf die Kanäle wurde verweigert. Bitte später versuchen." msgid "Couldn't find channel or no channels available. Maybe you mistyped your request?" msgstr "Konnte Kanal nicht finden oder keine Kanäle verfügbar. Ist die Anfrage korrekt?" msgid "Selection" msgstr "Auswahl" msgid "Snapshot interval" msgstr "Snapshot-Intervall" msgid "Stop" msgstr "Stopp" #, c-format msgid "%a, %x" msgstr "%a, %x" msgid "What's running on" msgstr "Was läuft am" msgid "at" msgstr "um" msgid "What's on next?" msgstr "Was läuft als nächstes?" msgid "Favorites" msgstr "Favoriten" msgid "Click to view details." msgstr "Für Details klicken." msgid "View the schedule of this channel" msgstr "Zeige Programm dieses Kanals" msgid " - " msgstr " - " msgid "more" msgstr "mehr" msgid "Now" msgstr "Jetzt" msgid "Next" msgstr "als Nächstes" msgid "What's on" msgstr "Was läuft" msgid "Details view" msgstr "Ausführliche Ansicht" msgid "List view" msgstr "Listenansicht" msgid "Find more at the Internet Movie Database." msgstr "Weitere Informationen in der Internet Movie Database." msgid "Stream this channel into browser." msgstr "" msgid "Stream this recording into media player." msgstr "Aufnahme in Mediaplayer abspielen." msgid "Record this" msgstr "Diese Sendung aufnehmen" msgid "Edit timer" msgstr "Timer bearbeiten" msgid "loading data" msgstr "Daten nachladen" msgid "an error occured!" msgstr "Es ist ein Fehler aufgetreten!" msgid "Request succeeded!" msgstr "Aktion durchgeführt!" msgid "Request failed!" msgstr "Aktion fehlgeschlagen!" msgid "Sunday" msgstr "Sonntag" msgid "Monday" msgstr "Montag" msgid "Tuesday" msgstr "Dienstag" msgid "Wednesday" msgstr "Mittwoch" msgid "Thursday" msgstr "Donnerstag" msgid "Friday" msgstr "Freitag" msgid "Saturday" msgstr "Samstag" #. TRANSLATORS: only adjust the ordering and separators, don't translate the m's, d's and y's msgid "mm/dd/yyyy" msgstr "dd.mm.yyyy" msgid "January" msgstr "Januar" msgid "February" msgstr "Februar" msgid "March" msgstr "März" msgid "April" msgstr "April" msgid "May" msgstr "Mai" msgid "June" msgstr "Juni" msgid "July" msgstr "Juli" msgid "August" msgstr "August" msgid "September" msgstr "September" msgid "October" msgstr "Oktober" msgid "November" msgstr "November" msgid "December" msgstr "Dezember" msgid "retrieving status ..." msgstr "Hole Status ..." msgid "Toggle updates on/off." msgstr "Statusabfrage ein- oder ausschalten." msgid "stop playback" msgstr "Anhalten" msgid "resume playback" msgstr "Fortsetzen" msgid "pause playback" msgstr "Pause" msgid "fast rewind" msgstr "Suchlauf rückwärts" msgid "fast forward" msgstr "Suchlauf vorwärts" msgid "previous channel" msgstr "Sender zurück" msgid "next channel" msgstr "Sender vor" msgid "No server response!" msgstr "Der Server antwortet nicht!" msgid "Failed to update infobox!" msgstr "Kann Infobox nicht aktualisieren!" msgid "Switch to this channel." msgstr "Zu diesem Kanal umschalten." msgid "Search for repeats." msgstr "Nach Wiederholungen suchen." msgid "Authors" msgstr "Autoren" msgid "Project Idea" msgstr "Projekt Idee" msgid "Webserver" msgstr "Webserver" msgid "Current Maintainer" msgstr "Derzeitiger Entwickler" msgid "Previous Maintainer" msgstr "Bisheriger Entwickler" msgid "Project leader" msgstr "Projektleiter" msgid "Content" msgstr "Inhalte" msgid "Graphics" msgstr "Grafiken" msgid "Information" msgstr "Informationen" msgid "LIVE version" msgstr "LIVE Version" msgid "VDR version" msgstr "VDR Version" msgid "Features" msgstr "Unterstütze Plugins" msgid "active" msgstr "aktiv" msgid "required" msgstr "erforderlich" msgid "Homepage" msgstr "Homepage" msgid "Bugs and suggestions" msgstr "Fehlerberichte und Vorschläge" msgid "If you encounter any bugs or would like to suggest new features, please use our bugtracker" msgstr "Für Fehler oder Verbesserungsvorschläge steht unser Bugtracker bereit" msgid "What's on?" msgstr "Was läuft?" msgid "MultiSchedule" msgstr "Zeitleiste" msgid "Search" msgstr "Suchen" msgid "Recordings" msgstr "Aufnahmen" msgid "Web-Streaming" msgstr "" msgid "Logout" msgstr "Abmelden" msgid "Your attention is required" msgstr "Ihre Aufmerksamkeit ist erforderlich" msgid "React" msgstr "Reagieren" msgid "Dismiss" msgstr "Verwerfen" msgid "Couldn't find user. Maybe you mistyped your request?" msgstr "Konnte Benutzer nicht finden. Evtl. fehlerhafte Anforderung?" msgid "This user name is already in use!" msgstr "Dieser Benutzername wird bereits verwendet!" msgid "Edit user" msgstr "Benutzer bearbeiten" msgid "New user" msgstr "Neuen Benutzer anlegen" msgid "Name" msgstr "Name" msgid "User rights" msgstr "Benutzerrechte" msgid "Edit setup" msgstr "Einstellungen bearbeiten" msgid "Add or edit timers" msgstr "Timer hinzufügen oder ändern" msgid "Delete timers" msgstr "Timer löschen" msgid "Delete recordings" msgstr "Aufnahmen löschen" msgid "Use remote menu" msgstr "Menü Fernbedienung nutzen" msgid "Start replay" msgstr "Wiedergabe starten" msgid "Switch channel" msgstr "Zu diesem Kanal umschalten." msgid "Add or edit search timers" msgstr "Suchtimer hinzufügen oder ändern" msgid "Delete search timers" msgstr "Suchtimer löschen" msgid "Edit recordings" msgstr "Aufnahmen editieren" msgid "Save" msgstr "Speichern" msgid "Cancel" msgstr "Abbrechen" msgid "Search results" msgstr "Suchergebnisse" msgid "No search results" msgstr "keine Suchergebnisse" msgid "%A, %b %d %Y" msgstr "%A, %d.%m.%Y" msgid "Couldn't find timer. Maybe you mistyped your request?" msgstr "Konnte Timer nicht finden. Evtl. fehlerhafte Anforderung?" msgid "Please set a title for the timer!" msgstr "Bitte einen Titel fᅵr den Timer angeben!" msgid "New timer" msgstr "Neuen Timer anlegen" msgid "Title" msgstr "Titel" msgid "Server" msgstr "" msgid "Directory" msgstr "Verzeichnis" msgid "Weekday" msgstr "Wochentag" msgid "Use VPS" msgstr "VPS verwenden" msgid "ERROR:" msgstr "FEHLER:" msgid "Deleted recording:" msgstr "Gelöschte Aufnahme:" msgid "List of recordings" msgstr "Liste der Aufnahmen" msgid "No recordings found" msgstr "Keine Aufnahmen vorhanden" msgid "Delete selected" msgstr "Löschen" #, no-c-format msgid "%a," msgstr "%a," #. TRANSLATORS: recording duration format HH:MM #, c-format msgid "(%d:%02d)" msgstr "(%d:%02d)" msgid "Sort by name" msgstr "Sortiere nach Name" msgid "Sort by date" msgstr "Sortiere nach Datum" msgid "Filter" msgstr "Filter" msgid "Look in recordings titles and subtitles for the given string and display only the matching ones. You may also use perl compatible regular expressions (PCRE)." msgstr "Suche in Aufnahme-Titel und Untertitel nach dem angegebenen Wort und zeige nur die zutreffenden an. Es sind auch Perl kompatible reguläre Ausdrücke (PCRE) erlaubt." msgid "Expand all folders" msgstr "Alle Ordner aufklappen" msgid "Collapse all folders" msgstr "Alle Ordner einklappen" msgid "Delete this recording from hard disc!" msgstr "Diese Aufnahme von der Festplatte löschen!" msgid "Edit recording" msgstr "Aufnahme editieren" msgid "play this recording." msgstr "Diese Aufnahme abspielen." msgid "No schedules available for this channel" msgstr "Für diesen Kanal liegen keine EPG-Informationen vor" msgid "Couldn't find searchtimer. Maybe you mistyped your request?" msgstr "Keinen Suchtimer gefunden. Möglicherweise ein Tippfehler in der Anfrage?" msgid "Search text too short - use anyway?" msgstr "Suchtext zu kurz - trotzdem verwenden?" msgid "Search term" msgstr "Suchbegriff" msgid "Search mode" msgstr "Suchmodus" msgid "phrase" msgstr "Ausdruck" msgid "all words" msgstr "alle Worte" msgid "at least one word" msgstr "ein Wort" msgid "match exactly" msgstr "exakt" msgid "regular expression" msgstr "regulärer Ausdruck" msgid "fuzzy" msgstr "unscharf" msgid "Tolerance" msgstr "Toleranz" msgid "Match case" msgstr "Groß/klein" msgid "Search in" msgstr "Suche in" msgid "Episode" msgstr "Episode" msgid "Description" msgstr "Beschreibung" msgid "Use extended EPG info" msgstr "Verw. erweiterte EPG Info" msgid "Ignore missing EPG info" msgstr "Ignoriere fehlende EPG Info" msgid "When active this can cause very many timers. So please always first test this search before using it as search timer!" msgstr "Diese Einstellung kann bei Aktivierung sehr viele Timer erzeugen. Also bitte immer zuerst die Suche testen, bevor sie als Suchtimer verwendet wird!" msgid "Use channel" msgstr "Verw. Kanal" msgid "interval" msgstr "Bereich" msgid "channel group" msgstr "Kanalgruppe" msgid "only FTA" msgstr "ohne PayTV" msgid "from channel" msgstr "von Kanal" msgid "to channel" msgstr "bis Kanal" msgid "Use time" msgstr "Verw. Uhrzeit" msgid "Start after" msgstr "Start nach" msgid "The time the show may start at the earliest" msgstr "Die Zeit, zu der die Sendung frühestens anfangen darf" msgid "Start before" msgstr "Start vor" msgid "The time the show may start at the latest" msgstr "Die Zeit, zu der die Sendung spätestens angefangen haben muss" msgid "Use duration" msgstr "Verw. Dauer" msgid "Min. duration" msgstr "Min. Dauer" msgid "Max. duration" msgstr "Max. Dauer" msgid "Use day of week" msgstr "Verw. Wochentag" msgid "Use blacklists" msgstr "Verw. Ausschlusslisten" msgid "all" msgstr "alle" msgid "Use in favorites menu" msgstr "In Favoritenmenü verw." msgid "Use as search timer" msgstr "Als Suchtimer verwenden" msgid "user defined" msgstr "benutzer-definiert" msgid "from date" msgstr "ab Datum" msgid "to date" msgstr "bis Datum" msgid "Record" msgstr "Aufnehmen" msgid "Announce only" msgstr "Nur ankündigen" msgid "Switch only" msgstr "Nur umschalten" msgid "Announce and Switch" msgstr "Ankündigen und umschalten" msgid "Announce via email" msgstr "Mit E-Mail ankündigen" msgid "Inactive Record" msgstr "Deaktiviert" msgid "Series recording" msgstr "Serienaufnahme" msgid "Delete recordings after ... days" msgstr "Aufn. nach ... Tagen löschen" msgid "Keep ... recordings" msgstr "Behalte ... Aufnahmen" msgid "Pause when ... recordings exist" msgstr "Pause, wenn ... Aufnahmen exist." msgid "Avoid repeats" msgstr "Vermeide Wiederholung" msgid "Allowed repeats" msgstr "Erlaubte Wiederholungen" msgid "Only repeats within ... days" msgstr "Nur Wiederh. innerhalb ... Tagen" msgid "Compare title" msgstr "Vergleiche Titel" msgid "Compare subtitle" msgstr "Vergleiche Untertitel" msgid "if present" msgstr "wenn vorhanden" msgid "Compare summary" msgstr "Vergleiche Beschreibung" msgid "Compare" msgstr "Vergleiche" msgid "Auto-delete search timer" msgstr "Suchtimer automatisch löschen" msgid "after ... recordings" msgstr "nach ... Aufnahmen" msgid "after ... days after first rec." msgstr "nach ... Tagen nach erster Aufnahme" msgid "Switch ... minutes before start" msgstr "Umschalten ... Minuten vor Start" msgid "Test" msgstr "Testen" msgid "No timer defined" msgstr "Keine Timer vorhanden" msgid "Timer is recording." msgstr "Timer zeichnet auf." msgid "Timer is active." msgstr "Timer ist aktiv." msgid "Toggle timer active/inactive" msgstr "Timer aktiv/inaktiv schalten" msgid "Delete timer" msgstr "Timer löschen" msgid "Timer conflicts" msgstr "Timer-Konflikte" msgid "Please set login and password!" msgstr "Bitte Login und Passwort angeben!" msgid "Setup saved." msgstr "Einstellungen gespeichert." msgid "Setup" msgstr "Einstellungen" msgid "User management" msgstr "Benutzerverwaltung" msgid "Local net (no login required)" msgstr "Lokales Netz (keine Anmeldung notwendig)" msgid "Show live logo image" msgstr "Zeige das Live Logo" msgid "Use ajax technology" msgstr "Verwende AJAX Technologie" msgid "Show dynamic VDR information box" msgstr "Zeige dynamische VDR Status Box" msgid "Allow video streaming" msgstr "Erlaube Videoanzeige im Browser" msgid "Web-Streaming h264" msgstr "" msgid "Web-Streaming HEVC" msgstr "" msgid "Web-Streaming MPEG2" msgstr "" msgid "Web-Streaming others" msgstr "" msgid "Streamdev server port" msgstr "Streamdev Server Port" msgid "Streamdev stream type" msgstr "Streamdev Stream Typ" msgid "Mark new recordings" msgstr "Markiere neue Aufnahmen" msgid "Add links to IMDb" msgstr "Füge Links zur IMDb hinzu" msgid "Additional fixed times in 'What's on?'" msgstr "Zusätzliche Zeitpunkte in 'Was läuft?'" msgid "Format is HH:MM. Separate multiple times with a semicolon" msgstr "Format ist HH:MM. Mehrere Zeiten durch Semikolon trennen" msgid "Channel groups for MultiSchedule" msgstr "Kanalgruppen für die Zeitleiste" msgid "Separate channels with a comma ',', separate groups with a semi-colon ';'" msgstr "Trenne Kanäle mit Komma ',', trenne Kanalgruppen mit Semikolon ';'" msgid "Duration of MultiSchedule in hours" msgstr "Dauer der Zeitleiste in Stunden" msgid "Show channels without EPG" msgstr "Zeige Kanäle ohne EPG" msgid "Start page" msgstr "Startseite" msgid "Theme" msgstr "Thema" msgid "Existing Recording:" msgstr "Vorhandene Aufnahme:" msgid "playing recording" msgstr "Wiedergabe" msgid "no epg info for current event!" msgstr "Keine Infos zur Sendung!" msgid "no epg info for current channel!" msgstr "Dieser Kanal hat kein EPG!" msgid "no current channel!" msgstr "Keinen Kanal gefunden!" msgid "error retrieving status info!" msgstr "Fehler: Status nicht verfügbar!" msgid "%I:%M:%S %p" msgstr "%H:%M:%S" msgid "Couldn't find recording. Maybe you mistyped your request?" msgstr "Konnte die Aufnahme nicht finden. Möglicherweise ein Tippfehler in der Anfrage?" msgid "Please set a name for the recording!" msgstr "Bitte einen Namen für die Aufnahme setzen!" msgid "Cannot copy, rename or move the recording." msgstr "Konnte die Aufnahme nicht umbenennen oder verschieben." msgid "Delete resume information" msgstr "Wiedergabeposition löschen" msgid "Delete marks information" msgstr "Schnittmarken löschen" msgid "Copy only" msgstr "Nur kopieren" msgid "Short description" msgstr "Kurzbeschreibung" msgid "Auxiliary info" msgstr "Zusätzliche Infos" msgid "Search settings" msgstr "Einstellungen zur Suche" msgid "Extended search" msgstr "Erweiterte Suche" msgid "no" msgstr "nein" msgid "Time" msgstr "Zeit" msgid "Users" msgstr "Benutzer" msgid "Delete user" msgstr "Benutzer löschen" msgid "Page error" msgstr "Seitenfehler" msgid "No timer conflicts" msgstr "Keine Timer-Konflikte" msgid "local" msgstr "lokal" msgid "Timer has a conflict." msgstr "Timer hat einen Konflikt." msgid "Live Interactive VDR Environment" msgstr "Live Interactive VDR Environment" msgid "No EPG information available" msgstr "Keine EPG Daten vorhanden" msgid "Duplicates" msgstr "Suche mehrfache Aufnahmen" msgid "Errors" msgstr "Sortiere nach Zahl der Aufnahmefehler" #~ msgid "Stream this channel into media player." #~ msgstr "Sender in Mediaplayer abspielen." #~ msgid "Web-Streaming packetizer command" #~ msgstr "Web-Streaming - Pfad zu FFmpeg" #~ msgid "VLC media URL" #~ msgstr "VLC Medien URL" #~ msgid "(%d')" #~ msgstr "%d'" #~ msgid "Show duration in 'Recordings'" #~ msgstr "Aufnahmedauer bei 'Aufnahmen' anzeigen" vdr-plugin-live-3.1.3/po/es_ES.po000066400000000000000000000471111414414333500165240ustar00rootroot00000000000000# VDR LIVE plugin language source file. # Copyright (C) 2007 LIVE Development team. See http://live.vdr-developer.org # This file is distributed under the same license as the VDR-LIVE package. # Ruben Nunez Francisco , 2002 # msgid "" msgstr "" "Project-Id-Version: VDR-LIVE 0.2.0\n" "Report-Msgid-Bugs-To: \n" "PO-Revision-Date: 2007-08-19 20:15+0200\n" "Last-Translator: Javier Bradineras \n" "Language-Team: see developers in README\n" "Language: es_ES\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=ISO-8859-15\n" "Content-Transfer-Encoding: 8bit\n" msgid "Last channel to display" msgstr "ltimo canal para mostrar" msgid "No limit" msgstr "Sin lmite" msgid "Use authentication" msgstr "Usar autentificacin" msgid "No" msgstr "No" msgid "Yes" msgstr "Si" msgid "Admin login" msgstr "Nombre del administrador" msgid "Admin password" msgstr "Contrasea del administrador" #, c-format msgid "%A, %x" msgstr "%A, %x" msgid "Searchtimer" msgstr "Buscador Programaciones" msgid "Error in timer settings" msgstr "Error en la configuracin de la programacin" msgid "Timer already defined" msgstr "Programacin ya definida" msgid "Timer not defined" msgstr "Programacin no definida" msgid "Timers are being edited - try again later" msgstr "Las programaciones estn siendo editadas - intntelo de nuevo ms tarde" msgid "On archive DVD No." msgstr "En archivo DVD nmero" msgid "On archive HDD No." msgstr "En archivo HDD nmero" msgid "Couldn't find channel or no channels available." msgstr "No se puede encontrar el canal o canales no disponibles" msgid "Couldn't switch to channel." msgstr "No se puede cambiar al canal" msgid "Couldn't find recording or no recordings available." msgstr "No se puede encontrar la grabacin o grabaciones no disponibles" msgid "Cannot control playback!" msgstr "No se puede controlar la reproduccin" msgid "Not playing a recording." msgstr "No se est reproduciendo una grabacin" msgid "Not playing the same recording as from request." msgstr "No se est reproduciendo la misma grabacin que la solicitada" msgid "Attempt to delete recording currently in playback." msgstr "Intentar borrar la grabacin que se est reproduciendo" msgid "Epg error" msgstr "Error en EPG" msgid "Wrong channel id" msgstr "Id de canal errneo" msgid "Channel has no schedule" msgstr "El canal no tiene programacin" msgid "Wrong event id" msgstr "Id de evento errneo" msgid "Required minimum version of epgsearch: " msgstr "Versin mnima de epgsearch requerida: " msgid "All" msgstr "Todo" msgid "FTA" msgstr "FTA" msgid "%I:%M %p" msgstr "%H:%M" msgid "EPGSearch version outdated! Please update." msgstr "Versin de EPGSearch antigua!. Por favor actualice" msgid "Couldn't aquire primary device" msgstr "No se puede capturar el sintonizador principal" msgid "Couldn't grab image from primary device" msgstr "No se puede capturar la imagen con el sintonizador principal" msgid "Timer conflict check detected " msgstr "Detectados conflictos de programaciones" msgid "conflict" msgstr "conflicto" msgid "conflicts" msgstr "conflictos" msgid "Electronic program guide information" msgstr "Informacin de la Gua Electrnica de Programacin (EPG)" msgid "Couldn't find recording or no recordings available" msgstr "No se puede encontrar la grabacin o no hay programaciones disponibles" msgid "Error aquiring schedules lock" msgstr "Error adquiriendo el bloqueo de las programaciones" msgid "Error aquiring schedules" msgstr "Error adquiriendo programaciones" msgid "%b %d %y" msgstr "%b %d %y" msgid "Searchtimers" msgstr "Programadores de bsqueda" msgid "Sorry, no permission. Please contact your administrator!" msgstr "Lo siento, no tiene permiso. Por favor contacte con su administrador!" msgid "Expression" msgstr "Expresin" msgid "Channel" msgstr "Canal" msgid "Starts between" msgstr "Iniciar entre" msgid "Toggle search timer actions (in)active" msgstr "Cambiar acciones de la programacin de bsqueda (in)activo" msgid "Browse search timer results" msgstr "Navegar por los resultados de la bsqueda" msgid "Edit search timer" msgstr "Editar la programacin de bsqueda" msgid "Delete this search timer?" msgstr "Borrar este programador de bsqueda" msgid "Delete search timer" msgstr "Borrar programador de bsqueda" msgid "New search timer" msgstr "Nueva programacin de bsqueda" msgid "Trigger search timer update" msgstr "Actualizar las programaciones" msgid "Wrong username or password" msgstr "Usuario o contrasea errneos" msgid "Login" msgstr "Entrar" msgid "VDR Live Login" msgstr "Acceso a VDR Live" msgid "User" msgstr "Usuario" msgid "Password" msgstr "Contrasea" msgid "Remote Control" msgstr "Mando a distancia" msgid "Couldn't aquire access to channels, please try again later." msgstr "No se puede tener acceso a los canales, por favor intntelo ms tarde" msgid "Couldn't find channel or no channels available. Maybe you mistyped your request?" msgstr "No se puede encontrar el canal o no hay canales disponibles. Puede que lo haya escrito de forma errnea?" msgid "Selection" msgstr "Seleccin" msgid "Snapshot interval" msgstr "Intervalo de visionado" msgid "Stop" msgstr "Detener" #, c-format msgid "%a, %x" msgstr "%a, %x" msgid "What's running on" msgstr "Qu se est reproduciendo?" msgid "at" msgstr "a las" msgid "What's on next?" msgstr "Qu hay a continuacin?" msgid "Favorites" msgstr "Favoritos" msgid "Click to view details." msgstr "Pulsar para ver detalles" msgid "View the schedule of this channel" msgstr "Ver la programacin de este canal" msgid " - " msgstr " - " msgid "more" msgstr "ms" msgid "Now" msgstr "Ahora" msgid "Next" msgstr "Siguiente" msgid "What's on" msgstr "Qu hay ahora?" msgid "Details view" msgstr "Ver detalles" msgid "List view" msgstr "Ver lista" msgid "Find more at the Internet Movie Database." msgstr "Encontrar ms en la Base de Datos de Pelculas de Internet" msgid "Stream this channel into browser." msgstr "" msgid "Stream this recording into media player." msgstr "" msgid "Record this" msgstr "Grabar esto" msgid "Edit timer" msgstr "Editar programacin" msgid "loading data" msgstr "cargando datos" msgid "an error occured!" msgstr "ha ocurrido un error!" msgid "Request succeeded!" msgstr "Bsqueda completada!" msgid "Request failed!" msgstr "La bsqueda ha fallado!" msgid "Sunday" msgstr "Domingo" msgid "Monday" msgstr "Lunes" msgid "Tuesday" msgstr "Martes" msgid "Wednesday" msgstr "Mircoles" msgid "Thursday" msgstr "Jueves" msgid "Friday" msgstr "Viernes" msgid "Saturday" msgstr "Sbado" #. TRANSLATORS: only adjust the ordering and separators, don't translate the m's, d's and y's msgid "mm/dd/yyyy" msgstr "dd/mm/yyyy" msgid "January" msgstr "Enero" msgid "February" msgstr "Febrero" msgid "March" msgstr "Marzo" msgid "April" msgstr "Abril" msgid "May" msgstr "Mayo" msgid "June" msgstr "Junio" msgid "July" msgstr "Julio" msgid "August" msgstr "Agosto" msgid "September" msgstr "Septiembre" msgid "October" msgstr "Octubre" msgid "November" msgstr "Noviembre" msgid "December" msgstr "Diciembre" msgid "retrieving status ..." msgstr "recibiendo estado ..." msgid "Toggle updates on/off." msgstr "Activar actualizaciones activado/desactivado" msgid "stop playback" msgstr "detener reproduccin" msgid "resume playback" msgstr "continuar reproduccin" msgid "pause playback" msgstr "pausar reproduccin" msgid "fast rewind" msgstr "rebobinado rpido" msgid "fast forward" msgstr "avance rpido" msgid "previous channel" msgstr "canal previo" msgid "next channel" msgstr "canal siguiente" msgid "No server response!" msgstr "El servidor no responde!" msgid "Failed to update infobox!" msgstr "La actualizacin de infobox ha fallado!" msgid "Switch to this channel." msgstr "Cambiar a este canal" msgid "Search for repeats." msgstr "Buscar repeticiones" msgid "Authors" msgstr "Autores" msgid "Project Idea" msgstr "Idea del proyecto" msgid "Webserver" msgstr "Servidor Web" msgid "Current Maintainer" msgstr "" msgid "Project leader" msgstr "Jefe del proyecto" msgid "Content" msgstr "Contenido" msgid "Graphics" msgstr "Grficos" msgid "Information" msgstr "Informacin" msgid "LIVE version" msgstr "Versin LIVE" msgid "VDR version" msgstr "Versin VDR" msgid "Features" msgstr "Caractersticas" msgid "active" msgstr "activo" msgid "required" msgstr "requerido" msgid "Homepage" msgstr "Pgina principal" msgid "Bugs and suggestions" msgstr "Sugerencias y bugs" msgid "If you encounter any bugs or would like to suggest new features, please use our bugtracker" msgstr "Si encuentra algn bug o le gustara sugerir nuevas caractersticas, por favor use nuestro bugtracker" msgid "What's on?" msgstr "Qu se emite ahora?" msgid "MultiSchedule" msgstr "Multi programacin" msgid "Search" msgstr "Buscar" msgid "Recordings" msgstr "Grabaciones" msgid "Web-Streaming" msgstr "" msgid "Logout" msgstr "Salir" msgid "Your attention is required" msgstr "Se requiere su atencin" msgid "React" msgstr "Resolver" msgid "Dismiss" msgstr "Ignorar" msgid "Couldn't find user. Maybe you mistyped your request?" msgstr "No se puede encontrar el usuario. Puede que lo haya escrito errneamente?" msgid "This user name is already in use!" msgstr "Este nombre de usuario ya se est usando!" msgid "Edit user" msgstr "Editar usuario" msgid "New user" msgstr "Nuevo usuario" msgid "Name" msgstr "Nombre" msgid "User rights" msgstr "Derechos de usuario" msgid "Edit setup" msgstr "Editar configuracin" msgid "Add or edit timers" msgstr "Aadir o editar programaciones" msgid "Delete timers" msgstr "Borrar programaciones" msgid "Delete recordings" msgstr "Borrar grabaciones" msgid "Use remote menu" msgstr "Usar men remoto" msgid "Start replay" msgstr "Iniciar de nuevo la reproduccin" msgid "Switch channel" msgstr "Cambiar canal" msgid "Add or edit search timers" msgstr "Aadir o editar programaciones de bsqueda" msgid "Delete search timers" msgstr "Borrar programaciones de bsqueda" msgid "Edit recordings" msgstr "Editar grabaciones" msgid "Save" msgstr "Grabar" msgid "Cancel" msgstr "Cancelar" msgid "Search results" msgstr "Resultados de la bsqueda" msgid "No search results" msgstr "Bsqueda sin resultados" msgid "%A, %b %d %Y" msgstr "%A, %b %d %Y" msgid "Couldn't find timer. Maybe you mistyped your request?" msgstr "No se puede encontrar la programacin. Puede que la haya escrito errneamente?" msgid "Please set a title for the timer!" msgstr "Por favor introduzca un ttulo para la programacin!" msgid "New timer" msgstr "Nueva programacin" msgid "Title" msgstr "Ttulo" msgid "Server" msgstr "" msgid "Directory" msgstr "Directorio" msgid "Weekday" msgstr "Da de la semana" msgid "Use VPS" msgstr "Usar VPS" msgid "ERROR:" msgstr "ERROR" msgid "Deleted recording:" msgstr "Grabacin borrada:" msgid "List of recordings" msgstr "Lista de grabaciones" msgid "No recordings found" msgstr "No se han encontrado grabaciones" msgid "Delete selected" msgstr "Borrar seleccin" #, no-c-format msgid "%a," msgstr "%a," #. TRANSLATORS: recording duration format HH:MM #, c-format msgid "(%d:%02d)" msgstr "" msgid "Sort by name" msgstr "Ordenar por nombre" msgid "Sort by date" msgstr "Ordenar por fecha" msgid "Filter" msgstr "Filtrar" msgid "Look in recordings titles and subtitles for the given string and display only the matching ones. You may also use perl compatible regular expressions (PCRE)." msgstr "Buscar en grabaciones ttulos y subttulos la cadena indicada y mostrar nicamente las que coincidan. Puede tambin usar expresiones regulares tipo perl (PCRE)." msgid "Expand all folders" msgstr "" msgid "Collapse all folders" msgstr "" msgid "Delete this recording from hard disc!" msgstr "Borrar esta grabacin del disco duro!" msgid "Edit recording" msgstr "Editar grabacin" msgid "play this recording." msgstr "reproducir esta grabacin" msgid "No schedules available for this channel" msgstr "No hay programaciones disponibles para este canal" msgid "Couldn't find searchtimer. Maybe you mistyped your request?" msgstr "No se puede encontrar la programacin. Puede que la haya escrito errneamente?" msgid "Search text too short - use anyway?" msgstr "Texto a buscar demasiado corto - usar de todos modos?" msgid "Search term" msgstr "Trmino de bsqueda" msgid "Search mode" msgstr "Modo de bsqueda" msgid "phrase" msgstr "Frase" msgid "all words" msgstr "Todas las palabras" msgid "at least one word" msgstr "Al menos una palabra" msgid "match exactly" msgstr "Coincidencia exacta" msgid "regular expression" msgstr "Expresin Regular" msgid "fuzzy" msgstr "aproximada" msgid "Tolerance" msgstr "Tolerancia" msgid "Match case" msgstr "Coincidir Mayscula/Minscula" msgid "Search in" msgstr "Buscar en" msgid "Episode" msgstr "Episodio" msgid "Description" msgstr "Descripcin" msgid "Use extended EPG info" msgstr "Usar informacin EPG ampliada" msgid "Ignore missing EPG info" msgstr "Ignorar EPG sin informacin" msgid "When active this can cause very many timers. So please always first test this search before using it as search timer!" msgstr "Cuando se activa esta opcin se pueden producir muchas programaciones. Por lo tanto, haga el favor de testear siempre la programacin de bsqueda antes de usarla" msgid "Use channel" msgstr "Usar canal" msgid "interval" msgstr "intervalo" msgid "channel group" msgstr "grupo de canales" msgid "only FTA" msgstr "nicamente FTA" msgid "from channel" msgstr "del canal" msgid "to channel" msgstr "al canal" msgid "Use time" msgstr "Usar hora" msgid "Start after" msgstr "Inicia despus de" msgid "The time the show may start at the earliest" msgstr "La hora del programa puede comenzar antes de" msgid "Start before" msgstr "Iniciar antes de" msgid "The time the show may start at the latest" msgstr "La hora del programa puede comenzar despus de" msgid "Use duration" msgstr "Usar duracin" msgid "Min. duration" msgstr "Duracin mnima" msgid "Max. duration" msgstr "Duracin mxima" msgid "Use day of week" msgstr "Usar da de la semana" msgid "Use blacklists" msgstr "Usar lista negra" msgid "all" msgstr "Todo" msgid "Use in favorites menu" msgstr "Usar en men de favoritos" msgid "Use as search timer" msgstr "Usar como programador de bsqueda" msgid "user defined" msgstr "definido por el usuario" msgid "from date" msgstr "desde la fecha" msgid "to date" msgstr "hasta la fecha" msgid "Record" msgstr "Grabar" msgid "Announce only" msgstr "nicamente avisar" msgid "Switch only" msgstr "nicamente cambiar" msgid "Announce and Switch" msgstr "" msgid "Announce via email" msgstr "" msgid "Inactive Record" msgstr "" msgid "Series recording" msgstr "Grabacin de series" msgid "Delete recordings after ... days" msgstr "Borrar grabaciones despus de ... das" msgid "Keep ... recordings" msgstr "Mantener ... grabaciones" msgid "Pause when ... recordings exist" msgstr "Pausar cuando existan ... grabaciones" msgid "Avoid repeats" msgstr "Evitar repeticiones" msgid "Allowed repeats" msgstr "Repeticiones permitidas" msgid "Only repeats within ... days" msgstr "nicamente repeticiones dentro de ... das" msgid "Compare title" msgstr "Comparar ttulo" msgid "Compare subtitle" msgstr "Comparar subttulo" msgid "if present" msgstr "si est presente" msgid "Compare summary" msgstr "Comparar resumen" msgid "Compare" msgstr "Comparar" msgid "Auto-delete search timer" msgstr "Borrado automtico de la programacin de bsqueda" msgid "after ... recordings" msgstr "tras ... grabaciones" msgid "after ... days after first rec." msgstr "tras ... das despus de la primera grabacin" msgid "Switch ... minutes before start" msgstr "Cambia ... minutos antes de comenzar" msgid "Test" msgstr "Probar" msgid "No timer defined" msgstr "No hay definida programacin" msgid "Timer is recording." msgstr "La programacin se est grabando." msgid "Timer is active." msgstr "La programacin est activa" msgid "Toggle timer active/inactive" msgstr "Cambiar programacin activa/inactiva" msgid "Delete timer" msgstr "Borrar programacin" msgid "Timer conflicts" msgstr "Conflictos de programaciones" msgid "Please set login and password!" msgstr "Por favor introduzca nombre de usuario y contrasea" msgid "Setup saved." msgstr "Configuracin guardada" msgid "Setup" msgstr "Configuracin" msgid "User management" msgstr "Administracin de usuarios" msgid "Local net (no login required)" msgstr "Red local (sin autentificacin requerida)" msgid "Show live logo image" msgstr "Muestra la imagen del logo Live" msgid "Use ajax technology" msgstr "Usar tecnologa ajax" msgid "Show dynamic VDR information box" msgstr "Mostrar informacin dinmica de VDR box" msgid "Allow video streaming" msgstr "Permitir streaming de vdeo" msgid "Web-Streaming h264" msgstr "" msgid "Web-Streaming HEVC" msgstr "" msgid "Web-Streaming MPEG2" msgstr "" msgid "Web-Streaming others" msgstr "" msgid "Streamdev server port" msgstr "Puerto del servidor streamdev" msgid "Streamdev stream type" msgstr "Tipo de stream de streamdev" msgid "Mark new recordings" msgstr "" msgid "Add links to IMDb" msgstr "Aadir enlaces a IMDb" msgid "Additional fixed times in 'What's on?'" msgstr "Horas fijas adicionales en 'Qu se emite ahora?'" msgid "Format is HH:MM. Separate multiple times with a semicolon" msgstr "El formato es HH:MM. Separado mltiples veces con un punto y coma" msgid "Channel groups for MultiSchedule" msgstr "Grupos de canales para multi programacin" msgid "Separate channels with a comma ',', separate groups with a semi-colon ';'" msgstr "Canales separados con una coma ',', grupos separados con un punto y coma ';'" msgid "Duration of MultiSchedule in hours" msgstr "Duracin de la multi programacin en horas" msgid "Show channels without EPG" msgstr "Mostrar canales sin EPG" msgid "Start page" msgstr "Pgina de inicio" msgid "Theme" msgstr "Tema" msgid "playing recording" msgstr "reproduciendo grabacin" msgid "no epg info for current event!" msgstr "No hay informacin de epg para este programa!" msgid "no epg info for current channel!" msgstr "no hay informacin de epg para el canal actual!" msgid "no current channel!" msgstr "no canal actual!" msgid "error retrieving status info!" msgstr "error recibiendo la informacin de estado" msgid "%I:%M:%S %p" msgstr "%H:%M:%S" msgid "Couldn't find recording. Maybe you mistyped your request?" msgstr "No se puede encontrar la grabacin. Puede haberla escrito errneamente?" msgid "Please set a name for the recording!" msgstr "Por favor, introduzca un nombre para la grabacin!" msgid "Cannot copy, rename or move the recording." msgstr "No se puede copiar, renombre o mueva la grabacin." msgid "Delete resume information" msgstr "Borrar marcador de continuacin" msgid "Delete marks information" msgstr "Borrar informacin de marcadores" msgid "Copy only" msgstr "Copia nicamente" msgid "Short description" msgstr "Descripcin corta" msgid "Auxiliary info" msgstr "Informacin auxiliar" msgid "Search settings" msgstr "Configuracin de bsquedas" msgid "Extended search" msgstr "Bsqueda extendida" msgid "no" msgstr "no" msgid "Time" msgstr "Hora" msgid "Users" msgstr "Usuarios" msgid "Delete user" msgstr "Borrar usuario" msgid "Page error" msgstr "Error en la pgina" msgid "No timer conflicts" msgstr "No hay conflictos de programaciones" msgid "local" msgstr "" msgid "Timer has a conflict." msgstr "La programacin tiene un conflicto" msgid "Live Interactive VDR Environment" msgstr "Entorno interactivo de Live VDR" msgid "No EPG information available" msgstr "No hay informacin disponible de la EPG" #~ msgid "VLC media URL" #~ msgstr "medios URL VLC" #~ msgid "(%d')" #~ msgstr "(%d')" #~ msgid "Show duration in 'Recordings'" #~ msgstr "Duracin del programa en 'Grabaciones'" vdr-plugin-live-3.1.3/po/fi_FI.po000066400000000000000000000454241414414333500165070ustar00rootroot00000000000000# VDR LIVE plugin language source file. # Copyright (C) 2007 LIVE Development team. See http://live.vdr-developer.org # This file is distributed under the same license as the VDR-LIVE package. # Rolf Ahrenberg # msgid "" msgstr "" "Project-Id-Version: VDR-LIVE 0.2.0\n" "Report-Msgid-Bugs-To: \n" "PO-Revision-Date: 2007-08-19 20:15+0200\n" "Last-Translator: Rolf Ahrenberg \n" "Language-Team: see developers in README\n" "Language: fi_FI\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" msgid "Last channel to display" msgstr "Näytä viimeisenä kanava" msgid "No limit" msgstr "ei rajoitusta" msgid "Use authentication" msgstr "Käytä autentikointia" msgid "No" msgstr "ei" msgid "Yes" msgstr "kyllä" msgid "Admin login" msgstr "Ylläpidon käyttäjätunnus" msgid "Admin password" msgstr "Ylläpidon salasana" #, c-format msgid "%A, %x" msgstr "%A %x" msgid "Searchtimer" msgstr "Hakuajastimet" msgid "Error in timer settings" msgstr "Ajastimen asetukset virheelliset" msgid "Timer already defined" msgstr "Ajastin jo määritelty" msgid "Timer not defined" msgstr "Ajastinta ei ole määritelty" msgid "Timers are being edited - try again later" msgstr "Ajastimia muokataan - yritä uudelleen myöhemmin" msgid "On archive DVD No." msgstr "Arkistointi-DVD:llä numero" msgid "On archive HDD No." msgstr "Arkistointi-HDD:llä numero" msgid "Couldn't find channel or no channels available." msgstr "Kanavaa ei löydy tai yhtään kanavaa ei ole saatavilla." msgid "Couldn't switch to channel." msgstr "Kanavan valinta epäonnistui." msgid "Couldn't find recording or no recordings available." msgstr "Tallennetta ei löydy tai yhtään tallennetta ei ole saatavilla." msgid "Cannot control playback!" msgstr "Toiston hallinta epäonnistui!" msgid "Not playing a recording." msgstr "Tallennetta ei toisteta." msgid "Not playing the same recording as from request." msgstr "Pyydettyä tallennetta ei toisteta." msgid "Attempt to delete recording currently in playback." msgstr "Yritetään poistaa toistettavaa tallennetta." msgid "Epg error" msgstr "Ohjelmaoppaan virhe" msgid "Wrong channel id" msgstr "Väärä kanavan tunniste" msgid "Channel has no schedule" msgstr "Kanavalla ei ole ohjelmatietoja" msgid "Wrong event id" msgstr "Väärä tapahtuman tunniste" msgid "Required minimum version of epgsearch: " msgstr "Vaadittava versio EPGSearch-laajennoksesta: " msgid "All" msgstr "Kaikki" msgid "FTA" msgstr "Vapaat" msgid "%I:%M %p" msgstr "%H:%M" msgid "EPGSearch version outdated! Please update." msgstr "EPGSearch-laajennos pitäisi päivittää!" msgid "Couldn't aquire primary device" msgstr "Ensisijaisen DVB-sovittimen käyttäminen epäonnistui!" msgid "Couldn't grab image from primary device" msgstr "Kuvan kaappaus ensisijaiselta DVB-sovittimelta epäonnistui!" msgid "Timer conflict check detected " msgstr "Tarkistuksessa löydettiin " msgid "conflict" msgstr "päällekkäinen ajastin" msgid "conflicts" msgstr "päällekkäistä ajastinta" msgid "Electronic program guide information" msgstr "Ohjelmaoppaan tiedot" msgid "Couldn't find recording or no recordings available" msgstr "Tallennetta ei löydy tai yhtään tallennetta ei ole saatavilla." msgid "Error aquiring schedules lock" msgstr "Ohjelmatietojen lukitus epäonnistui!" msgid "Error aquiring schedules" msgstr "Ohjelmatietojen haku epäonnistui!" msgid "%b %d %y" msgstr "%d.%m.%y" msgid "Searchtimers" msgstr "Hakuajastimet" msgid "Sorry, no permission. Please contact your administrator!" msgstr "Ei käyttöoikeutta. Ota yhteyttä ylläpitäjään!" msgid "Expression" msgstr "Hakutermi" msgid "Channel" msgstr "Kanava" msgid "Starts between" msgstr "Alkaa välillä" msgid "Toggle search timer actions (in)active" msgstr "Aseta hakuajastin päälle/pois" msgid "Browse search timer results" msgstr "Selaa hakutuloksia" msgid "Edit search timer" msgstr "Muokkaa hakuajastinta" msgid "Delete this search timer?" msgstr "Poistetaanko tämä hakuajastin?" msgid "Delete search timer" msgstr "Poista hakuajastin" msgid "New search timer" msgstr "Luo uusi hakuajastin" msgid "Trigger search timer update" msgstr "Päivitä hakuajastimet" msgid "Wrong username or password" msgstr "Väärä käyttäjätunnus tai salasana" msgid "Login" msgstr "Kirjaudu sisään" msgid "VDR Live Login" msgstr "VDR Live - sisäänkirjautuminen" msgid "User" msgstr "Käyttäjä" msgid "Password" msgstr "Salasana" msgid "Remote Control" msgstr "Kauko-ohjain" msgid "Couldn't aquire access to channels, please try again later." msgstr "Kanavien käyttäminen epäonnistui! Yritä myöhemmin uudelleen." msgid "Couldn't find channel or no channels available. Maybe you mistyped your request?" msgstr "Kanavaa ei löydy. Kirjoititko varmasti oikein?" msgid "Selection" msgstr "valittu" msgid "Snapshot interval" msgstr "Kuvien päivitysväli" msgid "Stop" msgstr "Pysäytä" #, c-format msgid "%a, %x" msgstr "%a %x" msgid "What's running on" msgstr "Menossa" msgid "at" msgstr "kello" msgid "What's on next?" msgstr "Tulossa seuraavaksi?" msgid "Favorites" msgstr "Suosikit" msgid "Click to view details." msgstr "Napsauta katsoaksesi lisätietoja." msgid "View the schedule of this channel" msgstr "Näytä ohjelmisto kanavalta" msgid " - " msgstr "" msgid "more" msgstr "lisätietoja" msgid "Now" msgstr "Nyt" msgid "Next" msgstr "Seuraavaksi" msgid "What's on" msgstr "Menossa" msgid "Details view" msgstr "Ruudukkonäkymä" msgid "List view" msgstr "Listanäkymä" msgid "Find more at the Internet Movie Database." msgstr "Hae IMDB:stä" msgid "Stream this channel into browser." msgstr "" msgid "Stream this recording into media player." msgstr "" msgid "Record this" msgstr "Tallenna ohjelma" msgid "Edit timer" msgstr "Muokkaa ajastinta" msgid "loading data" msgstr "ladataan tietoja" msgid "an error occured!" msgstr "virhe havaittu!" msgid "Request succeeded!" msgstr "Pyyntö onnistui!" msgid "Request failed!" msgstr "Pyyntö epäonnistui!" msgid "Sunday" msgstr "Sunnuntai" msgid "Monday" msgstr "Maanantai" msgid "Tuesday" msgstr "Tiistai" msgid "Wednesday" msgstr "Keskiviikko" msgid "Thursday" msgstr "Torstai" msgid "Friday" msgstr "Perjantai" msgid "Saturday" msgstr "Lauantai" #. TRANSLATORS: only adjust the ordering and separators, don't translate the m's, d's and y's msgid "mm/dd/yyyy" msgstr "mm/dd/yyyy" msgid "January" msgstr "Tammikuu" msgid "February" msgstr "Helmikuu" msgid "March" msgstr "Maaliskuu" msgid "April" msgstr "Huhtikuu" msgid "May" msgstr "Toukokuu" msgid "June" msgstr "Kesäkuu" msgid "July" msgstr "Heinäkuu" msgid "August" msgstr "Elokuu" msgid "September" msgstr "Syyskuu" msgid "October" msgstr "Lokakuu" msgid "November" msgstr "Marraskuu" msgid "December" msgstr "Joulukuu" msgid "retrieving status ..." msgstr "Haetaan tietoja ..." msgid "Toggle updates on/off." msgstr "Aseta tilannekysely päälle/pois" msgid "stop playback" msgstr "Lopeta toisto" msgid "resume playback" msgstr "Jatka toistoa" msgid "pause playback" msgstr "Pysäytä toisto" msgid "fast rewind" msgstr "Pikakelaus taaksepäin" msgid "fast forward" msgstr "Pikakelaus eteenpäin" msgid "previous channel" msgstr "Edellinen kanava" msgid "next channel" msgstr "Seuraava kanava" msgid "No server response!" msgstr "Palvelin ei vastaa!" msgid "Failed to update infobox!" msgstr "Infolaatikon päivitys epäonnistui!" msgid "Switch to this channel." msgstr "Vaihda kanavalle" msgid "Search for repeats." msgstr "Etsi toistuvat" msgid "Authors" msgstr "Tekijät" msgid "Project Idea" msgstr "Projektin idea" msgid "Webserver" msgstr "HTTP-palvelin" msgid "Current Maintainer" msgstr "" msgid "Project leader" msgstr "Projektipäällikkö" msgid "Content" msgstr "Sisältö" msgid "Graphics" msgstr "Grafiikka" msgid "Information" msgstr "Tietoja" msgid "LIVE version" msgstr "LIVE-versio" msgid "VDR version" msgstr "VDR-versio" msgid "Features" msgstr "Tuetut laajennokset" msgid "active" msgstr "käytössä" msgid "required" msgstr "vaadittava" msgid "Homepage" msgstr "Kotisivu" msgid "Bugs and suggestions" msgstr "Virheraportoinnit ja parannusehdotukset" msgid "If you encounter any bugs or would like to suggest new features, please use our bugtracker" msgstr "Voit raportoida sekä virheet että parannusehdotukset suoraan havaintotietokantaan" msgid "What's on?" msgstr "Menossa?" msgid "MultiSchedule" msgstr "Ohjelmaopas" msgid "Search" msgstr "Etsi" msgid "Recordings" msgstr "Tallenteet" msgid "Web-Streaming" msgstr "" msgid "Logout" msgstr "Kirjaudu ulos" msgid "Your attention is required" msgstr "Huomiotasi tarvitaan" msgid "React" msgstr "Reagoi" msgid "Dismiss" msgstr "Ohita" msgid "Couldn't find user. Maybe you mistyped your request?" msgstr "Käyttäjää ei löydy. Kirjoititko varmasti oikein?" msgid "This user name is already in use!" msgstr "Käyttäjänimi on jo käytössä!" msgid "Edit user" msgstr "Muokkaa käyttäjää" msgid "New user" msgstr "Luo uusi käyttäjä" msgid "Name" msgstr "Nimi" msgid "User rights" msgstr "Käyttöoikeudet" msgid "Edit setup" msgstr "Muokkaa asetuksia" msgid "Add or edit timers" msgstr "Lisää/muokkaa ajastimia" msgid "Delete timers" msgstr "Poista ajastimia" msgid "Delete recordings" msgstr "Poista tallenteita" msgid "Use remote menu" msgstr "Käytä kauko-ohjainta" msgid "Start replay" msgstr "Aloita toisto" msgid "Switch channel" msgstr "Vaihda kanavaa" msgid "Add or edit search timers" msgstr "Lisää/muokkaa hakuajastimia" msgid "Delete search timers" msgstr "Poista hakuajastimia" msgid "Edit recordings" msgstr "Muokkaa tallenteita" msgid "Save" msgstr "Tallenna" msgid "Cancel" msgstr "Peru" msgid "Search results" msgstr "Hakutulokset" msgid "No search results" msgstr "Ei hakutuloksia" msgid "%A, %b %d %Y" msgstr "%A %d.%m.%Y" msgid "Couldn't find timer. Maybe you mistyped your request?" msgstr "Ajastinta ei löydy. Kirjoititko varmasti oikein?" msgid "Please set a title for the timer!" msgstr "Aseta nimi ajastimelle!" msgid "New timer" msgstr "Luo uusi ajastin" msgid "Title" msgstr "Otsikko" msgid "Server" msgstr "" msgid "Directory" msgstr "Hakemisto" msgid "Weekday" msgstr "Viikonpäivä" msgid "Use VPS" msgstr "Käytä VPS-toimintoa" msgid "ERROR:" msgstr "VIRHE:" msgid "Deleted recording:" msgstr "Poistettu tallenne:" msgid "List of recordings" msgstr "Tallennelistaus" msgid "No recordings found" msgstr "Tallenteita ei löydy" msgid "Delete selected" msgstr "Poista valitut" #, no-c-format msgid "%a," msgstr "%a" #. TRANSLATORS: recording duration format HH:MM #, c-format msgid "(%d:%02d)" msgstr "" msgid "Sort by name" msgstr "Järjestä nimen mukaan" msgid "Sort by date" msgstr "Järjestä päivämäärän mukaan" msgid "Filter" msgstr "Suodata" msgid "Look in recordings titles and subtitles for the given string and display only the matching ones. You may also use perl compatible regular expressions (PCRE)." msgstr "Suodata tallenteet nimi- ja kuvauskentistä hakusanan avulla. Voit käyttää myös Perl-yhteensopivia säännöllisiä lausekkeita (PCRE)." msgid "Expand all folders" msgstr "Avaa kaikki kansiot" msgid "Collapse all folders" msgstr "Sulje kaikki kansiot" msgid "Delete this recording from hard disc!" msgstr "Poista tallenne kovalevyltä!" msgid "Edit recording" msgstr "Muokkaa tallennetta" msgid "play this recording." msgstr "Toista tallenne" msgid "No schedules available for this channel" msgstr "Tälle kanavalle ei ole saatavilla ohjelmistoa" msgid "Couldn't find searchtimer. Maybe you mistyped your request?" msgstr "Hakuajastimia ei löydetä! Onko pyyntö oikein kirjoitettu?" msgid "Search text too short - use anyway?" msgstr "Liian suppea hakuehto - etsitäänkö silti?" msgid "Search term" msgstr "Hakuehto" msgid "Search mode" msgstr "Hakutapa" msgid "phrase" msgstr "fraasi" msgid "all words" msgstr "kaikki sanat" msgid "at least one word" msgstr "yksi sana" msgid "match exactly" msgstr "täsmällinen" msgid "regular expression" msgstr "säännöllinen lauseke" msgid "fuzzy" msgstr "sumea" msgid "Tolerance" msgstr "Toleranssi" msgid "Match case" msgstr "Huomioi kirjainkoko" msgid "Search in" msgstr "Hae kentistä" msgid "Episode" msgstr "Jakson nimi" msgid "Description" msgstr "Kuvaus" msgid "Use extended EPG info" msgstr "Käytä laajennettua ohjelmaopasta" msgid "Ignore missing EPG info" msgstr "Jätä puuttuvat ohjelmatiedot huomioimatta" msgid "When active this can cause very many timers. So please always first test this search before using it as search timer!" msgstr "Asetuksen ottaminen käyttöön saattaa luoda lukemattomia ajastimia. Testaa haku ennenkuin teet siitä hakuajastimen!" msgid "Use channel" msgstr "Käytä kanavaa" msgid "interval" msgstr "kyllä" msgid "channel group" msgstr "kanavaryhmä" msgid "only FTA" msgstr "vapaat kanavat" msgid "from channel" msgstr "Kanavasta" msgid "to channel" msgstr "Kanavaan" msgid "Use time" msgstr "Käytä aloitusaikaa" msgid "Start after" msgstr "Aloitusaika aikaisintaan" msgid "The time the show may start at the earliest" msgstr "Lähetyksen aloitusaika aikaisintaan" msgid "Start before" msgstr "Aloitusaika viimeistään" msgid "The time the show may start at the latest" msgstr "Lähetyksen aloitusaika viimeistään" msgid "Use duration" msgstr "Käytä kestoaikaa" msgid "Min. duration" msgstr "Kestoaika vähintään" msgid "Max. duration" msgstr "Kestoaika enintään" msgid "Use day of week" msgstr "Käytä viikonpäivää" msgid "Use blacklists" msgstr "Käytä mustia listoja" msgid "all" msgstr "kaikki" msgid "Use in favorites menu" msgstr "Käytä suosikkina" msgid "Use as search timer" msgstr "Käytä hakuajastimena" msgid "user defined" msgstr "käyttäjän määrittelemä" msgid "from date" msgstr "alkaen" msgid "to date" msgstr "päättyen" msgid "Record" msgstr "Tallenna" msgid "Announce only" msgstr "Muistutus" msgid "Switch only" msgstr "Kanavanvaihto" msgid "Announce and Switch" msgstr "" msgid "Announce via email" msgstr "" msgid "Inactive Record" msgstr "" msgid "Series recording" msgstr "Sarjatallennus" msgid "Delete recordings after ... days" msgstr "Poista tallenteet ... päivän jälkeen" msgid "Keep ... recordings" msgstr "Säilytä ... tallennetta" msgid "Pause when ... recordings exist" msgstr "Keskeytä ... tallenteen jälkeen" msgid "Avoid repeats" msgstr "Estä uusinnat" msgid "Allowed repeats" msgstr "Sallittujen uusintojen lukumäärä" msgid "Only repeats within ... days" msgstr "Vain uusinnat ... päivän sisällä" msgid "Compare title" msgstr "Vertaa nimeä" msgid "Compare subtitle" msgstr "Vertaa jakson nimeä" msgid "if present" msgstr "jos olemassa" msgid "Compare summary" msgstr "Vertaa kuvausta" msgid "Compare" msgstr "Vertaa" msgid "Auto-delete search timer" msgstr "Poista automaattisesti hakuajastimet" msgid "after ... recordings" msgstr "... tallenteen jälkeen" msgid "after ... days after first rec." msgstr "... päivän jälkeen ensimmäisestä" msgid "Switch ... minutes before start" msgstr "Vaihda ... minuuttia ennen alkua" msgid "Test" msgstr "Testaa" msgid "No timer defined" msgstr "Ajastinta ei ole määritelty" msgid "Timer is recording." msgstr "Ajastinta tallennetaan" msgid "Timer is active." msgstr "Aktiivinen ajastin" msgid "Toggle timer active/inactive" msgstr "Aseta ajastin päälle/pois" msgid "Delete timer" msgstr "Poista ajastin" msgid "Timer conflicts" msgstr "Päällekkäiset ajastimet" msgid "Please set login and password!" msgstr "Aseta käyttäjätunnus sekä salasana!" msgid "Setup saved." msgstr "Asetukset tallennettu." msgid "Setup" msgstr "Asetukset" msgid "User management" msgstr "Käyttäjähallinta" msgid "Local net (no login required)" msgstr "Paikallinen verkko (ei autentikointia)" msgid "Show live logo image" msgstr "Näytä Live-logo" msgid "Use ajax technology" msgstr "Käytä AJAX-tekniikkaa" msgid "Show dynamic VDR information box" msgstr "Näytä dynaaminen VDR:n infolaatikko" msgid "Allow video streaming" msgstr "Salli lähetyksen suoratoisto" msgid "Web-Streaming h264" msgstr "" msgid "Web-Streaming HEVC" msgstr "" msgid "Web-Streaming MPEG2" msgstr "" msgid "Web-Streaming others" msgstr "" msgid "Streamdev server port" msgstr "Streamdev-palvelimen portti" msgid "Streamdev stream type" msgstr "Streamdev-lähetteen tyyppi" msgid "Mark new recordings" msgstr "" msgid "Add links to IMDb" msgstr "Lisää linkit IMDb:hen" msgid "Additional fixed times in 'What's on?'" msgstr "Lisäajankohdat 'Menossa?'-sivulle" msgid "Format is HH:MM. Separate multiple times with a semicolon" msgstr "Käytä HH:MM formaattia ja erota ajankohdat puolipisteellä" msgid "Channel groups for MultiSchedule" msgstr "Kanavaryhmät 'Ohjelmaopas'-sivulle" msgid "Separate channels with a comma ',', separate groups with a semi-colon ';'" msgstr "Käytä pilkkua erottimena kanaville ja puolipistettä kanavaryhmille" msgid "Duration of MultiSchedule in hours" msgstr "'Ohjelmaopas'-sivun kesto tunteina" msgid "Show channels without EPG" msgstr "Näytä ohjelmaoppaattomat kanavat" msgid "Start page" msgstr "Aloitussivu" msgid "Theme" msgstr "Ulkoasu" msgid "playing recording" msgstr "Toistetaan tallennetta" msgid "no epg info for current event!" msgstr "Lähetyksellä ei ole ohjelmatietoja!" msgid "no epg info for current channel!" msgstr "Kanavalla ei ole ohjelmatietoja!" msgid "no current channel!" msgstr "Kanavaa ei löydy!" msgid "error retrieving status info!" msgstr "Virhe: tilannetietoja ei saatavilla!" msgid "%I:%M:%S %p" msgstr "%H:%M:%S" msgid "Couldn't find recording. Maybe you mistyped your request?" msgstr "Tallennetta ei löydy. Kirjoititko varmasti oikein?" msgid "Please set a name for the recording!" msgstr "Aseta nimi tallenteelle!" msgid "Cannot copy, rename or move the recording." msgstr "Tallenteen kopioiminen, uudelleen nimeäminen tai siirto epäonnistui!" msgid "Delete resume information" msgstr "Poista paluutiedot" msgid "Delete marks information" msgstr "Poista merkinnät" msgid "Copy only" msgstr "Kopioi ainoastaan" msgid "Short description" msgstr "Lyhyt kuvaus" msgid "Auxiliary info" msgstr "Lisätiedot" msgid "Search settings" msgstr "Hakuasetukset" msgid "Extended search" msgstr "Laajennettu haku" msgid "no" msgstr "ei" msgid "Time" msgstr "Ajankohta" msgid "Users" msgstr "Käyttäjät" msgid "Delete user" msgstr "Poista käyttäjä" msgid "Page error" msgstr "Sivuvirhe" msgid "No timer conflicts" msgstr "Ei päällekkäisiä ajastimia" msgid "local" msgstr "" msgid "Timer has a conflict." msgstr "Päällekkäinen ajastin" msgid "Live Interactive VDR Environment" msgstr "Interaktiivinen VDR-ympäristö" msgid "No EPG information available" msgstr "Ohjelmatietoja ei saatavilla" #~ msgid "VLC media URL" #~ msgstr "VLC:n URL" #~ msgid "(%d')" #~ msgstr "%d'" #~ msgid "Show duration in 'Recordings'" #~ msgstr "Näytä kestoajat 'Tallenteet'-sivulla" #~ msgid "Subtitle" #~ msgstr "Lyhyt kuvaus" vdr-plugin-live-3.1.3/po/fr_FR.po000066400000000000000000000470621414414333500165310ustar00rootroot00000000000000# VDR LIVE plugin language source file. # Copyright (C) 2007 LIVE Development team. See http://live.vdr-developer.org # This file is distributed under the same license as the VDR-LIVE package. # Jean-Claude Repetto , 2001 # Olivier Jacques , 2003 # Gregoire Favre , 2003 # Nicolas Huillard , 2005 # msgid "" msgstr "" "Project-Id-Version: VDR-LIVE 0.2.0\n" "Report-Msgid-Bugs-To: \n" "PO-Revision-Date: 2007-08-19 20:15+0200\n" "Last-Translator: Nicolas Huillard \n" "Language-Team: see developers in README\n" "Language: fr_FR\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=ISO-8859-1\n" "Content-Transfer-Encoding: 8bit\n" msgid "Last channel to display" msgstr "Dernire chane affiche" msgid "No limit" msgstr "Afficher tout" msgid "Use authentication" msgstr "Utiliser l'authentification" msgid "No" msgstr "Non" msgid "Yes" msgstr "Oui" msgid "Admin login" msgstr "Ouverture Admin" msgid "Admin password" msgstr "Mot de passe de l'Admin" #, c-format msgid "%A, %x" msgstr "%A, %x" msgid "Searchtimer" msgstr "Recherche de programmation" msgid "Error in timer settings" msgstr "Erreur dans la configuration de programmation" msgid "Timer already defined" msgstr "Programmation dj dfinit" msgid "Timer not defined" msgstr "Programmation non dfinit" msgid "Timers are being edited - try again later" msgstr "Programmation sont en cours d'dition - essayer plus tard" msgid "On archive DVD No." msgstr "Sur le DVD archive no" msgid "On archive HDD No." msgstr "Sur le HDD archive noä" msgid "Couldn't find channel or no channels available." msgstr "Impossible de trouver la chane ou pas de chanes disponibles." msgid "Couldn't switch to channel." msgstr "Impossible de passer la chane." msgid "Couldn't find recording or no recordings available." msgstr "Impossible de trouver l'enregistrement ou aucun enregistrements disponibles." msgid "Cannot control playback!" msgstr "Ne peut pas contrler la lecture!" msgid "Not playing a recording." msgstr "Ne joue pas l'enregistrement." msgid "Not playing the same recording as from request." msgstr "" msgid "Attempt to delete recording currently in playback." msgstr "Tentative d'effacer l'enregistrement en cours de lecture." msgid "Epg error" msgstr "Erreur EPG" msgid "Wrong channel id" msgstr "Faux ID de chane" msgid "Channel has no schedule" msgstr "La chane n'a pas de programmation" msgid "Wrong event id" msgstr "Faux ID d'vnement" msgid "Required minimum version of epgsearch: " msgstr "Version minimum requise d'epgsearch: " msgid "All" msgstr "Tout" msgid "FTA" msgstr "FTA" msgid "%I:%M %p" msgstr "%H:%M" msgid "EPGSearch version outdated! Please update." msgstr "Version EPGSearch prime! Mettre jour Svp." msgid "Couldn't aquire primary device" msgstr "Impossible d'acqurir priphrique primaire" msgid "Couldn't grab image from primary device" msgstr "Impossible de rcuprer l'image du priphrique primaire" #, fuzzy msgid "Timer conflict check detected " msgstr "Programmation est active." #, fuzzy msgid "conflict" msgstr "Programmation est active." #, fuzzy msgid "conflicts" msgstr "Programmation est active." msgid "Electronic program guide information" msgstr "Guide lectronique d'information des programmes EPG" msgid "Couldn't find recording or no recordings available" msgstr "Impossible de trouver un enregistrement ou aucun enregistrements disponibles" msgid "Error aquiring schedules lock" msgstr "Erreur lors de l'acquisition du verrouillage de programmation de recherche" msgid "Error aquiring schedules" msgstr "Erreur lors de l'acquisition de programmation" msgid "%b %d %y" msgstr "%d.%m.%y" msgid "Searchtimers" msgstr "Recherche de programmation" msgid "Sorry, no permission. Please contact your administrator!" msgstr "" msgid "Expression" msgstr "Expression de recherche" msgid "Channel" msgstr "Chane" msgid "Starts between" msgstr "Dpart entre" msgid "Toggle search timer actions (in)active" msgstr "Actions de la programmation de recherche (in)actives" msgid "Browse search timer results" msgstr "Passer en revue les rsultats de programmation de recherche" msgid "Edit search timer" msgstr "Editer l'expression de recherche" msgid "Delete this search timer?" msgstr "Effacer cette programmation de recherche?" msgid "Delete search timer" msgstr "Effacer la programmation de recherche" msgid "New search timer" msgstr "Crer nouvelle programmation de recherche" msgid "Trigger search timer update" msgstr "Mise jour des recherches de programmation maintenant" msgid "Wrong username or password" msgstr "Nom de l'utilisateur ou mot de passei sont erron" msgid "Login" msgstr "Session" msgid "VDR Live Login" msgstr "Session VDR Live" msgid "User" msgstr "Utilisateur" msgid "Password" msgstr "Mot de passe" msgid "Remote Control" msgstr "Tlcommande du VDR" msgid "Couldn't aquire access to channels, please try again later." msgstr "Impossible d'avoir accs des chanes, ressayer ultrieurement." msgid "Couldn't find channel or no channels available. Maybe you mistyped your request?" msgstr "N'a pas pu trouver la chane. Votre requte est t'elle corrte? " msgid "Selection" msgstr "Selection" #, fuzzy msgid "Snapshot interval" msgstr "intervalle" msgid "Stop" msgstr "Stop" #, fuzzy, c-format msgid "%a, %x" msgstr "%A, %x" #, fuzzy msgid "What's running on" msgstr "Qu'y a t'il au programme vers" msgid "at" msgstr "" msgid "What's on next?" msgstr "Quoi faire ensuite?" msgid "Favorites" msgstr "Favoris" msgid "Click to view details." msgstr "Clic pour voire les dtails." msgid "View the schedule of this channel" msgstr "Regarder le programme de cette chane" msgid " - " msgstr "" msgid "more" msgstr "plus" msgid "Now" msgstr "maintenant" msgid "Next" msgstr "prochainement" msgid "What's on" msgstr "Actuellement" msgid "Details view" msgstr "Vue dtaile" msgid "List view" msgstr "Vue en liste" msgid "Find more at the Internet Movie Database." msgstr "Trouver plus d'information du film dans la base de donnes film IMDB." msgid "Stream this channel into browser." msgstr "" msgid "Stream this recording into media player." msgstr "" msgid "Record this" msgstr "Enregistrer cette mission" msgid "Edit timer" msgstr "Editer la programmation" msgid "loading data" msgstr "Chargement des donnes" msgid "an error occured!" msgstr "Une erreur s'est produite!" msgid "Request succeeded!" msgstr "Demande russi!" msgid "Request failed!" msgstr "chec de la requte!" msgid "Sunday" msgstr "Dimanche" msgid "Monday" msgstr "Lundi" msgid "Tuesday" msgstr "Mardi" msgid "Wednesday" msgstr "Mercredi" msgid "Thursday" msgstr "Jeudi" msgid "Friday" msgstr "Vendredi" msgid "Saturday" msgstr "Samedi" #. TRANSLATORS: only adjust the ordering and separators, don't translate the m's, d's and y's msgid "mm/dd/yyyy" msgstr "mm/dd/yyyy" msgid "January" msgstr "Janvier" msgid "February" msgstr "Fvrier" msgid "March" msgstr "Mars" msgid "April" msgstr "Avril" msgid "May" msgstr "Mai" msgid "June" msgstr "Juin" msgid "July" msgstr "Juillet" msgid "August" msgstr "Aot" msgid "September" msgstr "Septembre" msgid "October" msgstr "Octobre" msgid "November" msgstr "Novembre" msgid "December" msgstr "Dcembre" msgid "retrieving status ..." msgstr "mise jours de l'tat" msgid "Toggle updates on/off." msgstr "Activer/Dsactiver l'update du status" msgid "stop playback" msgstr "arrter la lecture" msgid "resume playback" msgstr "continuer" msgid "pause playback" msgstr "pause" msgid "fast rewind" msgstr "retour rapide" msgid "fast forward" msgstr "avance rapide" msgid "previous channel" msgstr "chane prcdent" msgid "next channel" msgstr "Chane suivante" msgid "No server response!" msgstr "Pas de rponse du serveur!" msgid "Failed to update infobox!" msgstr "chec de la mise jour infobox!" msgid "Switch to this channel." msgstr "Changer vers cette chane. " msgid "Search for repeats." msgstr "Recherche de rptitions." msgid "Authors" msgstr "Auteur" #, fuzzy msgid "Project Idea" msgstr "Chef de projet" msgid "Webserver" msgstr "Serveur Web" msgid "Current Maintainer" msgstr "" msgid "Project leader" msgstr "Chef de projet" msgid "Content" msgstr "Contenu" msgid "Graphics" msgstr "Graphiques" msgid "Information" msgstr "Information" msgid "LIVE version" msgstr "Version LIVE" msgid "VDR version" msgstr "Version VDR" msgid "Features" msgstr "Soutien des plugins" msgid "active" msgstr "actif" msgid "required" msgstr "requis" msgid "Homepage" msgstr "Page d'accueil" msgid "Bugs and suggestions" msgstr "Bogues et suggestions" msgid "If you encounter any bugs or would like to suggest new features, please use our bugtracker" msgstr "Si vous rencontrez n'importe quels bogue ou voudriez suggrer de nouveaux dispositifs, employer notre bugtracker svp" msgid "What's on?" msgstr "Actuellement?" msgid "MultiSchedule" msgstr "" msgid "Search" msgstr "Recherche" msgid "Recordings" msgstr "Enregistrements" msgid "Web-Streaming" msgstr "" msgid "Logout" msgstr "Fin de session" msgid "Your attention is required" msgstr "" msgid "React" msgstr "" msgid "Dismiss" msgstr "" #, fuzzy msgid "Couldn't find user. Maybe you mistyped your request?" msgstr "N'a pas pu trouver la programmation. Peut-tre vous avez une erreur dans votre requte?" msgid "This user name is already in use!" msgstr "" #, fuzzy msgid "Edit user" msgstr "Editer la programmation" #, fuzzy msgid "New user" msgstr "Nouvelle programmation" msgid "Name" msgstr "" #, fuzzy msgid "User rights" msgstr "Utilisateur" #, fuzzy msgid "Edit setup" msgstr "Editer la programmation" #, fuzzy msgid "Add or edit timers" msgstr "Editer la programmation" #, fuzzy msgid "Delete timers" msgstr "Effacer la programmation" #, fuzzy msgid "Delete recordings" msgstr "Enregistrement de srie" #, fuzzy msgid "Use remote menu" msgstr "Utiliser dans le menu favoris" #, fuzzy msgid "Start replay" msgstr "Page de dpart" #, fuzzy msgid "Switch channel" msgstr "Changer vers cette chane. " #, fuzzy msgid "Add or edit search timers" msgstr "Editer l'expression de recherche" #, fuzzy msgid "Delete search timers" msgstr "Effacer la programmation de recherche" msgid "Edit recordings" msgstr "" msgid "Save" msgstr "Sauvegarde" msgid "Cancel" msgstr "Interrompre" msgid "Search results" msgstr "Rsultats" msgid "No search results" msgstr "pas de rsultat de recherche" msgid "%A, %b %d %Y" msgstr "%A, %d.%m.%Y" msgid "Couldn't find timer. Maybe you mistyped your request?" msgstr "N'a pas pu trouver la programmation. Peut-tre vous avez une erreur dans votre requte?" msgid "Please set a title for the timer!" msgstr "Veuillez indiquer un titre pour la programmation!" msgid "New timer" msgstr "Nouvelle programmation" msgid "Title" msgstr "Titre" msgid "Server" msgstr "" msgid "Directory" msgstr "Dossier" msgid "Weekday" msgstr "Jour de la semaine" msgid "Use VPS" msgstr "Utiliser VPS" msgid "ERROR:" msgstr "ERREUR:" msgid "Deleted recording:" msgstr "" msgid "List of recordings" msgstr "Liste des enregistrements" msgid "No recordings found" msgstr "Pas d'enregistrement" msgid "Delete selected" msgstr "" #, no-c-format msgid "%a," msgstr "%a," #. TRANSLATORS: recording duration format HH:MM #, c-format msgid "(%d:%02d)" msgstr "" msgid "Sort by name" msgstr "" msgid "Sort by date" msgstr "" msgid "Filter" msgstr "" msgid "Look in recordings titles and subtitles for the given string and display only the matching ones. You may also use perl compatible regular expressions (PCRE)." msgstr "" msgid "Expand all folders" msgstr "" msgid "Collapse all folders" msgstr "" msgid "Delete this recording from hard disc!" msgstr "" msgid "Edit recording" msgstr "" msgid "play this recording." msgstr "lire cette enregistrement." msgid "No schedules available for this channel" msgstr "Aucune programmation disponible pour cette chane" msgid "Couldn't find searchtimer. Maybe you mistyped your request?" msgstr "Impossible de trouver la programmation de recherche. Peut-tre que votre demande de fautes de frappe?" msgid "Search text too short - use anyway?" msgstr "Texte de recherche est trop court - l'utiliser quand mme?" msgid "Search term" msgstr "Mot cl" msgid "Search mode" msgstr "Mode de recherche" msgid "phrase" msgstr "Phrase" msgid "all words" msgstr "tout les mots" msgid "at least one word" msgstr "un mot" msgid "match exactly" msgstr "correspond exactement" msgid "regular expression" msgstr "expression rguliere" msgid "fuzzy" msgstr "imprcis" msgid "Tolerance" msgstr "Tolrance" msgid "Match case" msgstr "Maj/Minuscule" msgid "Search in" msgstr "Recherche dans" msgid "Episode" msgstr "pisode" msgid "Description" msgstr "Description" msgid "Use extended EPG info" msgstr "Utiliser les infos EPG avances" msgid "Ignore missing EPG info" msgstr "Ignorez EPG info manquante" msgid "When active this can cause very many timers. So please always first test this search before using it as search timer!" msgstr "Lorsqu'elle est active, cela peut provoquer de trs nombreuses programmations. Donc sil vous plat toujours d'abord tester cette recherche avant de l'utiliser comme progammation de recherche!" msgid "Use channel" msgstr "Utiliser la chane" msgid "interval" msgstr "intervalle" msgid "channel group" msgstr "Groupe de chanes" msgid "only FTA" msgstr "sans TV-Payante" msgid "from channel" msgstr "de la chane" msgid "to channel" msgstr " la chane" msgid "Use time" msgstr "Utiliser l'heure" msgid "Start after" msgstr "Dpart aprs" msgid "The time the show may start at the earliest" msgstr "L'heure lorsque l'mission doit commencer au plus tt" msgid "Start before" msgstr "Dpart avant" msgid "The time the show may start at the latest" msgstr "L'heure lorsque l'mission doit commencer au plus tard" msgid "Use duration" msgstr "Dure d'utilisation" msgid "Min. duration" msgstr "Dure min." msgid "Max. duration" msgstr "Dure max." msgid "Use day of week" msgstr "Utiliser les jours de la semaine" msgid "Use blacklists" msgstr "Utiliser la liste des exclus" msgid "all" msgstr "tous" msgid "Use in favorites menu" msgstr "Utiliser dans le menu favoris" msgid "Use as search timer" msgstr "Utiliser la recherche" msgid "user defined" msgstr "Dfinis par l'utilisateur" msgid "from date" msgstr " partir de la date" msgid "to date" msgstr " ce jour" msgid "Record" msgstr "Enregistre" msgid "Announce only" msgstr "Annoncer seulement le dbut de l'mission" msgid "Switch only" msgstr "Seulement changer de chaine" msgid "Announce and Switch" msgstr "" msgid "Announce via email" msgstr "" msgid "Inactive Record" msgstr "" msgid "Series recording" msgstr "Enregistrement de srie" msgid "Delete recordings after ... days" msgstr "Effacer l'enregistrement aprs ... jours" msgid "Keep ... recordings" msgstr "Garder .... les enregistrements" msgid "Pause when ... recordings exist" msgstr "Pause, lorsque ... l'enregistrement existe." msgid "Avoid repeats" msgstr "Eviter les rptitions" msgid "Allowed repeats" msgstr "Rptitions autorises" msgid "Only repeats within ... days" msgstr "Que rptition, pendant ... jours" msgid "Compare title" msgstr "Comparer titres" msgid "Compare subtitle" msgstr "Comparer les sous-titres" msgid "if present" msgstr "" msgid "Compare summary" msgstr "Comparer les descriptions" msgid "Compare" msgstr "Comparer" msgid "Auto-delete search timer" msgstr "Auto-suppression de programmation de recherche" msgid "after ... recordings" msgstr "aprs ... enregistrements" msgid "after ... days after first rec." msgstr "aprs ... jours aprs le premier enregistrement" msgid "Switch ... minutes before start" msgstr "Changer ... minutes avant le dbut" msgid "Test" msgstr "Tester" msgid "No timer defined" msgstr "Aucune programmation dfinie" msgid "Timer is recording." msgstr "Enregistrement de srie" msgid "Timer is active." msgstr "Programmation est active." msgid "Toggle timer active/inactive" msgstr "Programmation basculer actif/inactif" msgid "Delete timer" msgstr "Effacer la programmation" #, fuzzy msgid "Timer conflicts" msgstr "Programmation est active." msgid "Please set login and password!" msgstr "Entre le nom d'utilisateur et le mot de passe svp!" msgid "Setup saved." msgstr "Paramtre sauvegard" msgid "Setup" msgstr "Configuration" msgid "User management" msgstr "" msgid "Local net (no login required)" msgstr "Rseau local (non requis)" msgid "Show live logo image" msgstr "Afficher l'image du logo de live" msgid "Use ajax technology" msgstr "Utilisez la technologie Ajax" msgid "Show dynamic VDR information box" msgstr "Voir la bote d'information dynamique VDR" msgid "Allow video streaming" msgstr "Permettez-streaming vido" msgid "Web-Streaming h264" msgstr "" msgid "Web-Streaming HEVC" msgstr "" msgid "Web-Streaming MPEG2" msgstr "" msgid "Web-Streaming others" msgstr "" msgid "Streamdev server port" msgstr "Port du serveur streamdev" msgid "Streamdev stream type" msgstr "Type du serveur streamdev" msgid "Mark new recordings" msgstr "" msgid "Add links to IMDb" msgstr "Ajouter des liens vers IMDb" msgid "Additional fixed times in 'What's on?'" msgstr "priodes fixes additionnelles dans 'actuellement?'" msgid "Format is HH:MM. Separate multiple times with a semicolon" msgstr "Le format est HH:MM . Plusieurs priodes spares avec un point-virgule" #, fuzzy msgid "Channel groups for MultiSchedule" msgstr "La chane n'a pas de programmation" msgid "Separate channels with a comma ',', separate groups with a semi-colon ';'" msgstr "" msgid "Duration of MultiSchedule in hours" msgstr "" msgid "Show channels without EPG" msgstr "" msgid "Start page" msgstr "Page de dpart" msgid "Theme" msgstr "Thme" msgid "playing recording" msgstr "Lire l'enregistrement" msgid "no epg info for current event!" msgstr "Pas d'infos pour l'mission!" msgid "no epg info for current channel!" msgstr "Cette chane n'a pas d'EPG!" msgid "no current channel!" msgstr "pas de chane trouv!" msgid "error retrieving status info!" msgstr "erreur de rcupration du statut d'info!" msgid "%I:%M:%S %p" msgstr "%H:%M:%S" msgid "Couldn't find recording. Maybe you mistyped your request?" msgstr "" msgid "Please set a name for the recording!" msgstr "" msgid "Cannot copy, rename or move the recording." msgstr "" msgid "Delete resume information" msgstr "" msgid "Delete marks information" msgstr "" msgid "Copy only" msgstr "" #, fuzzy msgid "Short description" msgstr "Description" msgid "Auxiliary info" msgstr "" msgid "Search settings" msgstr "Rglages de recherche" msgid "Extended search" msgstr "Recherche tendue" msgid "no" msgstr "non" msgid "Time" msgstr "" #, fuzzy msgid "Users" msgstr "Utilisateur" #, fuzzy msgid "Delete user" msgstr "Effacer la programmation" msgid "Page error" msgstr "Erreur de paging" #, fuzzy msgid "No timer conflicts" msgstr "Programmation est active." msgid "local" msgstr "" #, fuzzy msgid "Timer has a conflict." msgstr "Programmation est active." msgid "Live Interactive VDR Environment" msgstr "" msgid "No EPG information available" msgstr "" #~ msgid "VLC media URL" #~ msgstr "VLC lien mdia " #, fuzzy #~ msgid "Subtitle" #~ msgstr "Titre" #, fuzzy #~ msgid "Please set a name and password for the user!" #~ msgstr "Entre le nom d'utilisateur et le mot de passe svp!" #~ msgid "Interval" #~ msgstr "Intervalle" #~ msgid "Show schedule of channel" #~ msgstr "Montrer le programme de la chane" #~ msgid "%a, %b %d" #~ msgstr "%a, %d.%m." #, fuzzy #~ msgid "Stream into browser" #~ msgstr "Diffusez cette chane vers le navigateur." #~ msgid "Edit this" #~ msgstr "Changer cette programmation" #, fuzzy #~ msgid "Start" #~ msgstr "Page de dpart" #, fuzzy #~ msgid "File" #~ msgstr "Titre" vdr-plugin-live-3.1.3/po/it_IT.po000066400000000000000000000474511414414333500165450ustar00rootroot00000000000000# VDR LIVE plugin language source file. # Copyright (C) 2007 LIVE Development team. See http://live.vdr-developer.org # This file is distributed under the same license as the VDR-LIVE package. # Alberto Carraro , 2001 # Antonio Ospite , 2003 # Sean Carlos , 2005 # msgid "" msgstr "" "Project-Id-Version: VDR-LIVE 0.2.0\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: \n" "PO-Revision-Date: 2011-12-25 23:22+0100\n" "Last-Translator: Diego Pierotto \n" "Language-Team: see developers in README\n" "Language: it_IT\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Poedit-Language: Italian\n" "X-Poedit-Country: ITALY\n" "X-Poedit-SourceCharset: utf-8\n" msgid "Last channel to display" msgstr "Ultimo canale da visualizzare" msgid "No limit" msgstr "Mostra tutti" msgid "Use authentication" msgstr "Utilizza autenticazione" msgid "No" msgstr "No" msgid "Yes" msgstr "Sì" msgid "Admin login" msgstr "Nome amministratore" msgid "Admin password" msgstr "Password amministratore" #, c-format msgid "%A, %x" msgstr "%A, %x" msgid "Searchtimer" msgstr "Timer di ricerca" msgid "Error in timer settings" msgstr "Errore nelle impostazioni timer" msgid "Timer already defined" msgstr "Timer già definito" msgid "Timer not defined" msgstr "Timer non definito" msgid "Timers are being edited - try again later" msgstr "I timer sono stati modificati - riprova più tardi" msgid "On archive DVD No." msgstr "In archivio DVD No." msgid "On archive HDD No." msgstr "In archivio HDD No." msgid "Couldn't find channel or no channels available." msgstr "Impossibile trovare canale o canali disponibili." msgid "Couldn't switch to channel." msgstr "Impossibile cambiare canale." msgid "Couldn't find recording or no recordings available." msgstr "Impossibile trovare registrazione o registrazioni disponibili." msgid "Cannot control playback!" msgstr "Impossibile controllare la riproduzione!" msgid "Not playing a recording." msgstr "Nessuna registrazione in riproduzione." msgid "Not playing the same recording as from request." msgstr "Riproduzione di una registrazione diversa da quella richiesta." msgid "Attempt to delete recording currently in playback." msgstr "Tentativo di eliminare una registrazione in corso." msgid "Epg error" msgstr "Errore EPG" msgid "Wrong channel id" msgstr "ID canale errato" msgid "Channel has no schedule" msgstr "Il canale non ha programmazione" msgid "Wrong event id" msgstr "ID evento errato" msgid "Required minimum version of epgsearch: " msgstr "Versione minima richiesta di EPGSearch:" msgid "All" msgstr "Tutti" msgid "FTA" msgstr "Gratuiti" msgid "%I:%M %p" msgstr "%I:%M %p" msgid "EPGSearch version outdated! Please update." msgstr "Versione di EPGSearch troppo vecchia! Devi aggiornarla." msgid "Couldn't aquire primary device" msgstr "Impossibile identificare la scheda primaria" msgid "Couldn't grab image from primary device" msgstr "Impossibile ottenere immagini dalla scheda primaria" msgid "Timer conflict check detected " msgstr "Verifica conflitti timer rilevati" msgid "conflict" msgstr "conflitto" msgid "conflicts" msgstr "conflitti" msgid "Electronic program guide information" msgstr "Info Guida Elettronica Programmi (EPG)" msgid "Couldn't find recording or no recordings available" msgstr "Impossibile trovare registrazioni o nessuna registrazione disponibile" msgid "Error aquiring schedules lock" msgstr "Errore acquisizione blocco programmi" msgid "Error aquiring schedules" msgstr "Errore acquisizione programmi" msgid "%b %d %y" msgstr "%b %d %y" msgid "Searchtimers" msgstr "Timer di ricerca" msgid "Sorry, no permission. Please contact your administrator!" msgstr "Spiacenti, senza permessi. Per favore contatta l'amministratore!" msgid "Expression" msgstr "Termine ricerca" msgid "Channel" msgstr "Canale" msgid "Starts between" msgstr "Inizia tra" msgid "Toggle search timer actions (in)active" msgstr "Attiva/disattiva azioni ricerca timer" msgid "Browse search timer results" msgstr "Visualizza risultati ricerca" msgid "Edit search timer" msgstr "Modifica timer di ricerca" msgid "Delete this search timer?" msgstr "Eliminare questo timer di ricerca?" msgid "Delete search timer" msgstr "Elimina timer di ricerca" msgid "New search timer" msgstr "Nuovo timer di ricerca" msgid "Trigger search timer update" msgstr "Avvia aggiornamento timer di ricerca" msgid "Wrong username or password" msgstr "Nome utente o password errata" msgid "Login" msgstr "Entra" msgid "VDR Live Login" msgstr "Accesso VDR Live" msgid "User" msgstr "Utente" msgid "Password" msgstr "Password" msgid "Remote Control" msgstr "Telecomando" msgid "Couldn't aquire access to channels, please try again later." msgstr "Impossibile avere accesso ai canali, riprova più tardi." msgid "Couldn't find channel or no channels available. Maybe you mistyped your request?" msgstr "Impossibile trovare il canale o i canali disponibili. Hai digitato correttamente la richiesta?" msgid "Selection" msgstr "Selezione" msgid "Snapshot interval" msgstr "Intervallo schermata" msgid "Stop" msgstr "Ferma" #, c-format msgid "%a, %x" msgstr "%a, %x" msgid "What's running on" msgstr "In programmazione alle" msgid "at" msgstr "alle" msgid "What's on next?" msgstr "In programmazione dopo" msgid "Favorites" msgstr "Preferiti" msgid "Click to view details." msgstr "Fai click per vedere i dettagli" msgid "View the schedule of this channel" msgstr "Visualizza scheda programmi del canale" msgid " - " msgstr " - " msgid "more" msgstr "Altro" msgid "Now" msgstr "Adesso" msgid "Next" msgstr "Prossimi" msgid "What's on" msgstr "In programmazione" msgid "Details view" msgstr "Vedi come dettagli" msgid "List view" msgstr "Vedi come elenco" msgid "Find more at the Internet Movie Database." msgstr "Cerca ulteriori informazioni nel database internet (IMDB)" msgid "Stream this channel into browser." msgstr "" msgid "Stream this recording into media player." msgstr "" msgid "Record this" msgstr "Registra questa trasmissione" msgid "Edit timer" msgstr "Modifica timer" msgid "loading data" msgstr "caricamento dati" msgid "an error occured!" msgstr "Errore rilevato!" msgid "Request succeeded!" msgstr "Richiesta completata!" msgid "Request failed!" msgstr "Richiesta fallita!" msgid "Sunday" msgstr "Domenica" msgid "Monday" msgstr "Lunedì" msgid "Tuesday" msgstr "Martedì" msgid "Wednesday" msgstr "Mercoledì" msgid "Thursday" msgstr "Giovedì" msgid "Friday" msgstr "Venerdì" msgid "Saturday" msgstr "Sabato" #. TRANSLATORS: only adjust the ordering and separators, don't translate the m's, d's and y's msgid "mm/dd/yyyy" msgstr "mm/dd/yyyy" msgid "January" msgstr "Gennaio" msgid "February" msgstr "Febbraio" msgid "March" msgstr "Marzo" msgid "April" msgstr "Aprile" msgid "May" msgstr "Maggio" msgid "June" msgstr "Giugno" msgid "July" msgstr "Luglio" msgid "August" msgstr "Agosto" msgid "September" msgstr "Settembre" msgid "October" msgstr "Ottobre" msgid "November" msgstr "Novembre" msgid "December" msgstr "Dicembre" msgid "retrieving status ..." msgstr "Rilevamento stato ..." msgid "Toggle updates on/off." msgstr "Attiva/disattiva aggiornamenti" msgid "stop playback" msgstr "Ferma" msgid "resume playback" msgstr "Continua" msgid "pause playback" msgstr "Pausa" msgid "fast rewind" msgstr "Riavvolgi" msgid "fast forward" msgstr "Avanza" msgid "previous channel" msgstr "Canale precedente" msgid "next channel" msgstr "Canale successivo" msgid "No server response!" msgstr "Nessuna risposta dal server!" msgid "Failed to update infobox!" msgstr "Aggiornamento tabella info fallita!" msgid "Switch to this channel." msgstr "Sintonizza questo canale" msgid "Search for repeats." msgstr "Cerca repliche" msgid "Authors" msgstr "Autori" msgid "Project Idea" msgstr "Idea progetto" msgid "Webserver" msgstr "Server web" msgid "Current Maintainer" msgstr "" msgid "Project leader" msgstr "Capo progetto" msgid "Content" msgstr "Contenuti" msgid "Graphics" msgstr "Grafiche" msgid "Information" msgstr "Informazioni" msgid "LIVE version" msgstr "Versione LIVE" msgid "VDR version" msgstr "Versione VDR" msgid "Features" msgstr "Funzioni" msgid "active" msgstr "attivo" msgid "required" msgstr "richiesto" msgid "Homepage" msgstr "Pagina principale" msgid "Bugs and suggestions" msgstr "Rapporto errori e proposte" msgid "If you encounter any bugs or would like to suggest new features, please use our bugtracker" msgstr "Se riscontri degli errori o vuoi chiedere nuove funzioni utilizza il nostro bugtracker" msgid "What's on?" msgstr "In programmazione" msgid "MultiSchedule" msgstr "Multi Programmazione" msgid "Search" msgstr "Ricerca" msgid "Recordings" msgstr "Registrazioni" msgid "Web-Streaming" msgstr "" msgid "Logout" msgstr "Esci" msgid "Your attention is required" msgstr "ATTENZIONE" msgid "React" msgstr "Risolvi" msgid "Dismiss" msgstr "Ignora" msgid "Couldn't find user. Maybe you mistyped your request?" msgstr "Impossibile trovare l'utente. Hai digitato correttamente la richiesta?" msgid "This user name is already in use!" msgstr "Questo nome utente è già in uso!" msgid "Edit user" msgstr "Modifica utente" msgid "New user" msgstr "Nuovo utente" msgid "Name" msgstr "Nome" msgid "User rights" msgstr "Permessi utente" msgid "Edit setup" msgstr "Modifica opzioni" msgid "Add or edit timers" msgstr "Aggiungi o modifica timer" msgid "Delete timers" msgstr "Elimina timer" msgid "Delete recordings" msgstr "Elimina registrazioni" msgid "Use remote menu" msgstr "Utilizza menu remoto" msgid "Start replay" msgstr "Avvia riproduzione" msgid "Switch channel" msgstr "Cambia canale" msgid "Add or edit search timers" msgstr "Aggiungi o modifica timer di ricerca" msgid "Delete search timers" msgstr "Elimina timer di ricerca" msgid "Edit recordings" msgstr "Modifica registrazioni" msgid "Save" msgstr "Salva" msgid "Cancel" msgstr "Annulla" msgid "Search results" msgstr "Risultati ricerca" msgid "No search results" msgstr "Nessun risultato ricerca" msgid "%A, %b %d %Y" msgstr "%A, %b %d %Y" msgid "Couldn't find timer. Maybe you mistyped your request?" msgstr "Impossibile trovare il timer. Hai digitato correttamente la richiesta?" msgid "Please set a title for the timer!" msgstr "Imposta il titolo del timer!" msgid "New timer" msgstr "Nuovo timer" msgid "Title" msgstr "Titolo" msgid "Server" msgstr "" msgid "Directory" msgstr "Directory" msgid "Weekday" msgstr "Giorno della settimana" msgid "Use VPS" msgstr "Utilizza VPS" msgid "ERROR:" msgstr "ERRORE:" msgid "Deleted recording:" msgstr "Registrazione eliminata:" msgid "List of recordings" msgstr "Elenco registrazioni" msgid "No recordings found" msgstr "Nessuna registrazione trovata" msgid "Delete selected" msgstr "Elimina selezionati" #, no-c-format msgid "%a," msgstr "%a," #. TRANSLATORS: recording duration format HH:MM #, c-format msgid "(%d:%02d)" msgstr "(%d:%02d)" msgid "Sort by name" msgstr "Ordina per nome" msgid "Sort by date" msgstr "Ordina per data" msgid "Filter" msgstr "Filtro" msgid "Look in recordings titles and subtitles for the given string and display only the matching ones. You may also use perl compatible regular expressions (PCRE)." msgstr "Cerca nei titoli e sottotitoli delle registrazioni determinati valori e mostra solo quelli attinenti. Puoi anche usare espressioni regolari compatibili perl (PCRE)" msgid "Expand all folders" msgstr "Espandi tutte le cartelle" msgid "Collapse all folders" msgstr "Comprimi tutte le cartelle" msgid "Delete this recording from hard disc!" msgstr "Elimina questa registrazione dal disco fisso!" msgid "Edit recording" msgstr "Modifica registrazione" msgid "play this recording." msgstr "Riproduci questa registrazione" msgid "No schedules available for this channel" msgstr "Nessun programma disponibile per questo canale" msgid "Couldn't find searchtimer. Maybe you mistyped your request?" msgstr "Impossibile trovare timer di ricerca. Hai digitato correttamente la richiesta?" msgid "Search text too short - use anyway?" msgstr "Testo da cercare troppo breve - continuare lo stesso?" msgid "Search term" msgstr "Termine ricerca" msgid "Search mode" msgstr "Modalità ricerca" msgid "phrase" msgstr "frase" msgid "all words" msgstr "tutte le parole" msgid "at least one word" msgstr "almeno una parola" msgid "match exactly" msgstr "corrispondenza esatta" msgid "regular expression" msgstr "espressione regolare" msgid "fuzzy" msgstr "imprecisa" msgid "Tolerance" msgstr "Tolleranza" msgid "Match case" msgstr "Maiuscolo/Minuscolo" msgid "Search in" msgstr "Cerca in" msgid "Episode" msgstr "Episodio" msgid "Description" msgstr "Descrizione" msgid "Use extended EPG info" msgstr "Utilizza informazioni EPG estesa" msgid "Ignore missing EPG info" msgstr "Ignora info EPG mancante" msgid "When active this can cause very many timers. So please always first test this search before using it as search timer!" msgstr "Attivare questa opzione può creare molti timer. Quindi prova sempre questa ricerca prima di usarla come timer di ricerca!" msgid "Use channel" msgstr "Utilizza canale" msgid "interval" msgstr "intervallo" msgid "channel group" msgstr "gruppo canali" msgid "only FTA" msgstr "solo gratuiti" msgid "from channel" msgstr "da canale" msgid "to channel" msgstr "a canale" msgid "Use time" msgstr "Utilizza ora" msgid "Start after" msgstr "Inizio dopo" msgid "The time the show may start at the earliest" msgstr "L'ora precedente più vicina per l'inizio evento" msgid "Start before" msgstr "Inizio prima" msgid "The time the show may start at the latest" msgstr "L'ora successiva più tardi per l'inizio evento" msgid "Use duration" msgstr "Utilizza durata" msgid "Min. duration" msgstr "Durata Minima" msgid "Max. duration" msgstr "Durata Massima" msgid "Use day of week" msgstr "Utilizza giorno della settimana" msgid "Use blacklists" msgstr "Utilizza lista esclusioni" msgid "all" msgstr "tutti" msgid "Use in favorites menu" msgstr "Utilizza nel menu Preferiti" msgid "Use as search timer" msgstr "Utilizza come timer di ricerca" msgid "user defined" msgstr "definito dall'utente" msgid "from date" msgstr "da data" msgid "to date" msgstr "a data" msgid "Record" msgstr "Registra" msgid "Announce only" msgstr "Solo annuncio" msgid "Switch only" msgstr "Solo cambio canale" msgid "Announce and Switch" msgstr "" msgid "Announce via email" msgstr "" msgid "Inactive Record" msgstr "" msgid "Series recording" msgstr "Registrazioni in serie" msgid "Delete recordings after ... days" msgstr "Elimina registrazioni dopo ... giorni" msgid "Keep ... recordings" msgstr "Mantieni ... registrazioni" msgid "Pause when ... recordings exist" msgstr "Pausa, quando ... la registrazione esiste" msgid "Avoid repeats" msgstr "Evita repliche" msgid "Allowed repeats" msgstr "Repliche permesse" msgid "Only repeats within ... days" msgstr "Ripeti solo entro ... giorni" msgid "Compare title" msgstr "Confronta titoli" msgid "Compare subtitle" msgstr "Confronta sottotitoli" msgid "if present" msgstr "se presente" msgid "Compare summary" msgstr "Confronta descrizione" msgid "Compare" msgstr "Confronta" msgid "Auto-delete search timer" msgstr "Eliminazione automatica timer di ricerca" msgid "after ... recordings" msgstr "dopo ... registrazioni" msgid "after ... days after first rec." msgstr "dopo ... giorni dopo la prima registrazione" msgid "Switch ... minutes before start" msgstr "Cambia ... minuti prima dell'inizio" msgid "Test" msgstr "Prova" msgid "No timer defined" msgstr "Nessun timer definito" msgid "Timer is recording." msgstr "Timer in registrazione." msgid "Timer is active." msgstr "Timer attivo." msgid "Toggle timer active/inactive" msgstr "Attiva/disattiva timer" msgid "Delete timer" msgstr "Elimina timer" msgid "Timer conflicts" msgstr "Conflitti timer" msgid "Please set login and password!" msgstr "Digita un nome utente e password!" msgid "Setup saved." msgstr "Opzioni salvate." msgid "Setup" msgstr "Opzioni" msgid "User management" msgstr "Gestione utenti" msgid "Local net (no login required)" msgstr "Rete locale (nessun accesso richiesto)" msgid "Show live logo image" msgstr "Mostra logo immagine Live" msgid "Use ajax technology" msgstr "Utilizza tecnologia Ajax" msgid "Show dynamic VDR information box" msgstr "Mostra tabella informazione dinamica VDR" msgid "Allow video streaming" msgstr "Permetti trasmissione video" msgid "Web-Streaming h264" msgstr "" msgid "Web-Streaming HEVC" msgstr "" msgid "Web-Streaming MPEG2" msgstr "" msgid "Web-Streaming others" msgstr "" msgid "Streamdev server port" msgstr "Porta server Streamdev" msgid "Streamdev stream type" msgstr "Tipo trasmissione Streamdev" msgid "Mark new recordings" msgstr "" msgid "Add links to IMDb" msgstr "Aggiungi collegamenti a IMDB" msgid "Additional fixed times in 'What's on?'" msgstr "Ore aggiuntive fisse menu \"In programmazione\"" msgid "Format is HH:MM. Separate multiple times with a semicolon" msgstr "Il formato è HH:MM. Separa le ore multiple con un punto e virgola" msgid "Channel groups for MultiSchedule" msgstr "Gruppi canali per Multi Programmazione" msgid "Separate channels with a comma ',', separate groups with a semi-colon ';'" msgstr "Separa canali con una virgola ',', separa gruppi con un punto e virgola ';'" msgid "Duration of MultiSchedule in hours" msgstr "Durata Multi Programmazione (in ore)" msgid "Show channels without EPG" msgstr "Mostra canali senza EPG" msgid "Start page" msgstr "Pagina iniziale" msgid "Theme" msgstr "Tema" msgid "playing recording" msgstr "Riproduzione registrazione" msgid "no epg info for current event!" msgstr "Evento attuale senza info EPG!" msgid "no epg info for current channel!" msgstr "Canale attuale senza info EPG!" msgid "no current channel!" msgstr "Canale non trovato!" msgid "error retrieving status info!" msgstr "Errore: stato non disponibile!" msgid "%I:%M:%S %p" msgstr "%I:%M:%S %p" msgid "Couldn't find recording. Maybe you mistyped your request?" msgstr "Impossibile trovare la registrazione. Hai digitato correttamente la richiesta?" msgid "Please set a name for the recording!" msgstr "Imposta un nome alla registrazione!" msgid "Cannot copy, rename or move the recording." msgstr "Impossibile copiare, rinominare o spostare la registrazione." msgid "Delete resume information" msgstr "Elimina informazione ripristino" msgid "Delete marks information" msgstr "Elimina informazione marcatori" msgid "Copy only" msgstr "Solo copia" msgid "Short description" msgstr "Descrizione breve" msgid "Auxiliary info" msgstr "Informazioni ausiliarie" msgid "Search settings" msgstr "Impostazioni ricerca" msgid "Extended search" msgstr "Ricerca avanzata" msgid "no" msgstr "no" msgid "Time" msgstr "Ora" msgid "Users" msgstr "Utenti" msgid "Delete user" msgstr "Elimina utente" msgid "Page error" msgstr "Errore pagina" msgid "No timer conflicts" msgstr "Nessun timer in conflitto" msgid "local" msgstr "" msgid "Timer has a conflict." msgstr "Il timer ha un conflitto." msgid "Live Interactive VDR Environment" msgstr "Ambiente interattivo LIVE per VDR" msgid "No EPG information available" msgstr "Nessuna informazione EPG disponibile" #~ msgid "VLC media URL" #~ msgstr "Collegamento multimediale di VLC" #~ msgid "(%d')" #~ msgstr "(%d')" #~ msgid "Show duration in 'Recordings'" #~ msgstr "Mostra durata in 'Registrazioni'" #, fuzzy #~ msgid "Subtitle" #~ msgstr "Titolo" #, fuzzy #~ msgid "Please set a name and password for the user!" #~ msgstr "Digita un nome utente e password!" #~ msgid "Interval" #~ msgstr "Intervallo" #~ msgid "Show schedule of channel" #~ msgstr "Mostra scheda programmi canale" #~ msgid "%a, %b %d" #~ msgstr "%a, %b %d" #~ msgid "Stream into browser" #~ msgstr "Trasmetti nel browser" #~ msgid "Edit this" #~ msgstr "Modifica timer" #, fuzzy #~ msgid "Start" #~ msgstr "Pagina iniziale" #, fuzzy #~ msgid "File" #~ msgstr "Titolo" #, fuzzy #~ msgid "%A, %B %d %Y" #~ msgstr "%A, %b %d %Y" vdr-plugin-live-3.1.3/po/lt_LT.po000066400000000000000000000451031414414333500165430ustar00rootroot00000000000000# VDR LIVE plugin language source file. # Copyright (C) 2007 LIVE Development team. See http://live.vdr-developer.org # This file is distributed under the same license as the VDR-LIVE package. # Valdemaras Pipiras , 2009 # msgid "" msgstr "" "Project-Id-Version: VDR-LIVE 0.2.0\n" "Report-Msgid-Bugs-To: \n" "PO-Revision-Date: 2007-08-19 20:15+0200\n" "Last-Translator: Valdemaras Pipiras \n" "Language-Team: see developers in README\n" "Language: lt_LT\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" msgid "Last channel to display" msgstr "Paskutinis rodytas kanalas" msgid "No limit" msgstr "Neribojama" msgid "Use authentication" msgstr "Reikia autentifikuotis" msgid "No" msgstr "Ne" msgid "Yes" msgstr "Taip" msgid "Admin login" msgstr "Administratoriaus prisijungimo vardas" msgid "Admin password" msgstr "Administratoriaus slapyvardis" #, c-format msgid "%A, %x" msgstr "%A, %x" msgid "Searchtimer" msgstr "Paieška laikmatyje" msgid "Error in timer settings" msgstr "Klaida laikmačio nustatymuose" msgid "Timer already defined" msgstr "Laikmatis jau nustatytas" msgid "Timer not defined" msgstr "Laikmatis nenustatytas" msgid "Timers are being edited - try again later" msgstr "Laikmatis šio metu kuoreguojamas - pabandykit vėliau" msgid "On archive DVD No." msgstr "DVB Nr." #, fuzzy msgid "On archive HDD No." msgstr "DVB Nr." msgid "Couldn't find channel or no channels available." msgstr "Nerastas arba neegzistuojantis kanalas." msgid "Couldn't switch to channel." msgstr "Negali perjungti kanalo." msgid "Couldn't find recording or no recordings available." msgstr "Negali rasti įrašo arba įrašo visai nėra" msgid "Cannot control playback!" msgstr "Negali valdyti grojimo!" msgid "Not playing a recording." msgstr "Negroja įrašo." msgid "Not playing the same recording as from request." msgstr "Negroja to paties norimo įrašo." msgid "Attempt to delete recording currently in playback." msgstr "Bandoma ištrinti įrašą iš grojaraščio." msgid "Epg error" msgstr "Epg klaida" msgid "Wrong channel id" msgstr "Klaidingas kanalo ID" msgid "Channel has no schedule" msgstr "Kainalas neturi programos aprašo" msgid "Wrong event id" msgstr "Klaidingas įvykio ID" msgid "Required minimum version of epgsearch: " msgstr "Darbui reikalinga minimali epgsearch įskiepo versija: " msgid "All" msgstr "Visi" msgid "FTA" msgstr "Nukoduoti kanalai" msgid "%I:%M %p" msgstr "%I:%M %p" msgid "EPGSearch version outdated! Please update." msgstr "Pasenusi EPGSearch įskiepo versija! Atsinaujinkit." msgid "Couldn't aquire primary device" msgstr "Negali nustatyti pirminio įvesties įrenginio" msgid "Couldn't grab image from primary device" msgstr "Negali nuskaityti paveikslo iš pirminio įvesties įrenginio" msgid "Timer conflict check detected " msgstr "Eina laikmačio nesklandumų paieška " msgid "conflict" msgstr "nesklandumas" msgid "conflicts" msgstr "nesklandumai" msgid "Electronic program guide information" msgstr "Elektroninis programų gidas" msgid "Couldn't find recording or no recordings available" msgstr "Negali rasti įrašo arba įrašų iš vis nėra" msgid "Error aquiring schedules lock" msgstr "Negali gauti programų užrakto" msgid "Error aquiring schedules" msgstr "Negauna programų aprašų" msgid "%b %d %y" msgstr "%b %d %y" msgid "Searchtimers" msgstr "Paieškos laikmačiai" msgid "Sorry, no permission. Please contact your administrator!" msgstr "Atleiskit, bet jūs neturite užtektinai teisių. Susisiekite su sistemos administratoriumi!" msgid "Expression" msgstr "Išraiška" msgid "Channel" msgstr "Kanalas" msgid "Starts between" msgstr "Prasideda tarp" msgid "Toggle search timer actions (in)active" msgstr "Įjungti/Išjungti paieškos laikmačio aktyvumą" msgid "Browse search timer results" msgstr "Naršyti po paieškos laikmačio rezultatus" msgid "Edit search timer" msgstr "Koreguoti paieškos laikmatį" msgid "Delete this search timer?" msgstr "Ištrinti šį paieškos laikmatį?" msgid "Delete search timer" msgstr "Ištrinti paieškos laikmatį" msgid "New search timer" msgstr "Naujas paieškos laikmatis" msgid "Trigger search timer update" msgstr "Įjungti paieškos laikmačio atnaujinimą" msgid "Wrong username or password" msgstr "Blogas prisijungimo vardas arba slapyvardis" msgid "Login" msgstr "Prisijungimas" msgid "VDR Live Login" msgstr "VDR Live prisijungimas" msgid "User" msgstr "Vartotojo vardas" msgid "Password" msgstr "Slapyvardis" msgid "Remote Control" msgstr "Distancinis pultas" msgid "Couldn't aquire access to channels, please try again later." msgstr "Neprieina prie kanalų, pabandykit vėliau." msgid "Couldn't find channel or no channels available. Maybe you mistyped your request?" msgstr "Kanalas nerastas arba jo iš vis nėra. Gal klaidingai įvedėte paieškos frazę?" msgid "Selection" msgstr "Pasirinkimas" msgid "Snapshot interval" msgstr "Momentinių nuotraukų intervalas" msgid "Stop" msgstr "Sustapdyti" #, c-format msgid "%a, %x" msgstr "%a, %x" msgid "What's running on" msgstr "Kas rodoma" msgid "at" msgstr " " msgid "What's on next?" msgstr "Kas bus transliuojama po to?" msgid "Favorites" msgstr "Mėgiamiausi" msgid "Click to view details." msgstr "Spustelkit kad gautumėt daugiau informacijos." msgid "View the schedule of this channel" msgstr "Žiūrėti šio kanalo programą" msgid " - " msgstr " - " msgid "more" msgstr "daugiau" msgid "Now" msgstr "Dabar" msgid "Next" msgstr "Sekantis" msgid "What's on" msgstr "Kas rodoma šio metu" msgid "Details view" msgstr "Detalus sąrašas" msgid "List view" msgstr "Sąrašas" msgid "Find more at the Internet Movie Database." msgstr "Gauti daugiau informacijos iš IMDb." msgid "Stream this channel into browser." msgstr "" msgid "Stream this recording into media player." msgstr "" msgid "Record this" msgstr "Įrašyti" msgid "Edit timer" msgstr "Koreguoti laikmatį" msgid "loading data" msgstr "duomenys kraunami" msgid "an error occured!" msgstr "klaida!" msgid "Request succeeded!" msgstr "Užklausa pavyko!" msgid "Request failed!" msgstr "Užklausa nepavyko!" msgid "Sunday" msgstr "Sekmadienis" msgid "Monday" msgstr "Pirmadienis" msgid "Tuesday" msgstr "Antradienis" msgid "Wednesday" msgstr "Trečiadienis" msgid "Thursday" msgstr "Ketvirtadienis" msgid "Friday" msgstr "Penktadienis" msgid "Saturday" msgstr "Šeštadienis" #. TRANSLATORS: only adjust the ordering and separators, don't translate the m's, d's and y's msgid "mm/dd/yyyy" msgstr "yyyy-mm-dd" msgid "January" msgstr "Sausis" msgid "February" msgstr "Vasaris" msgid "March" msgstr "Kovas" msgid "April" msgstr "Balandis" msgid "May" msgstr "Gegužė" msgid "June" msgstr "Birželis" msgid "July" msgstr "Liepa" msgid "August" msgstr "Rugpjūtis" msgid "September" msgstr "Rugsėjis" msgid "October" msgstr "Spalis" msgid "November" msgstr "Lapkritis" msgid "December" msgstr "Gruodis" msgid "retrieving status ..." msgstr "užklausos būklė ..." msgid "Toggle updates on/off." msgstr "Įjungti/Išjunti atnaujinimus" msgid "stop playback" msgstr "sustapdyti grojimą" msgid "resume playback" msgstr "paleisti grojimą iš naujo" msgid "pause playback" msgstr "pristapdyti grojimą" msgid "fast rewind" msgstr "greitas atsukimas atgal" msgid "fast forward" msgstr "greitas prasukimas pirmyn" msgid "previous channel" msgstr "prieš tai buvęs kanalas" msgid "next channel" msgstr "sekantis kanalas" msgid "No server response!" msgstr "Serveris neatsako!" msgid "Failed to update infobox!" msgstr "Negali atnaujinti infolentelės!" msgid "Switch to this channel." msgstr "Pereiti į šį kanalą." msgid "Search for repeats." msgstr "Ieškoti pasikartojimų." msgid "Authors" msgstr "Autoriai" msgid "Project Idea" msgstr "Projekto idėja" msgid "Webserver" msgstr "Web serveris" msgid "Current Maintainer" msgstr "" msgid "Project leader" msgstr "Projekto vadovas" msgid "Content" msgstr "Turinys" msgid "Graphics" msgstr "Grafika" msgid "Information" msgstr "Informacija" msgid "LIVE version" msgstr "LIVE versija" msgid "VDR version" msgstr "VDR versija" msgid "Features" msgstr "Savybės" msgid "active" msgstr "aktyvus" msgid "required" msgstr "reikalingas" msgid "Homepage" msgstr "Pradžia" msgid "Bugs and suggestions" msgstr "Klaidos ir pasiūlymai" msgid "If you encounter any bugs or would like to suggest new features, please use our bugtracker" msgstr "Jei radote klaidų ar norėtumėte pasiūlyti naujų savybių, rašykit į mūsų bugtrakerį" msgid "What's on?" msgstr "Kas rodoma šiuo metu?" msgid "MultiSchedule" msgstr "" msgid "Search" msgstr "Paieška" msgid "Recordings" msgstr "Įrašai" msgid "Web-Streaming" msgstr "" msgid "Logout" msgstr "Atsijungti" msgid "Your attention is required" msgstr "Dėmesio" msgid "React" msgstr "Reguoti" msgid "Dismiss" msgstr "Atmesti" msgid "Couldn't find user. Maybe you mistyped your request?" msgstr "Toks vartotojas nerastas. Gal klaidingai įvedėte užklausos frazę?" msgid "This user name is already in use!" msgstr "Toks vartotojo vardas jau naudojamas!" msgid "Edit user" msgstr "Koreguoti vartotoją" msgid "New user" msgstr "Naujas vartotojas" msgid "Name" msgstr "Vardas" msgid "User rights" msgstr "Vartotojo teisės" msgid "Edit setup" msgstr "Koreguoti nustatymus" msgid "Add or edit timers" msgstr "Pridėti arba koreguoti laikmačius" msgid "Delete timers" msgstr "Ištrinti laikmačius" msgid "Delete recordings" msgstr "Ištrinti įrašus" msgid "Use remote menu" msgstr "Naudoti nuotolinį meniu" msgid "Start replay" msgstr "Paleisti kartojimą" msgid "Switch channel" msgstr "Perjungti kanalą" msgid "Add or edit search timers" msgstr "Pridėti arba koreguoti paieškos laikmačius" msgid "Delete search timers" msgstr "Ištrinti paieškos laikmačius" msgid "Edit recordings" msgstr "" msgid "Save" msgstr "Išsaugoti" msgid "Cancel" msgstr "Atmesti" msgid "Search results" msgstr "Paieškos rezultatai" msgid "No search results" msgstr "Nėra paieškos rezultatų" msgid "%A, %b %d %Y" msgstr "%A, %b %d %Y" msgid "Couldn't find timer. Maybe you mistyped your request?" msgstr "Toks laikmatis nerastas. Gal įvedėte klaidingą paieškos frazę?" msgid "Please set a title for the timer!" msgstr "Laikmačiui būtina įvesti pavadinimą!" msgid "New timer" msgstr "Naujas laikmatis" msgid "Title" msgstr "Pavadinimas" msgid "Server" msgstr "" msgid "Directory" msgstr "Katalogas" msgid "Weekday" msgstr "Savaitės diena" msgid "Use VPS" msgstr "Naudoti VPS" msgid "ERROR:" msgstr "KLAIDA:" msgid "Deleted recording:" msgstr "Ištrynė įrašą:" msgid "List of recordings" msgstr "Įrašų sąrašas" msgid "No recordings found" msgstr "Įrašų nėra" msgid "Delete selected" msgstr "" #, no-c-format msgid "%a," msgstr "%a," #. TRANSLATORS: recording duration format HH:MM #, c-format msgid "(%d:%02d)" msgstr "" msgid "Sort by name" msgstr "" msgid "Sort by date" msgstr "" msgid "Filter" msgstr "" msgid "Look in recordings titles and subtitles for the given string and display only the matching ones. You may also use perl compatible regular expressions (PCRE)." msgstr "" msgid "Expand all folders" msgstr "" msgid "Collapse all folders" msgstr "" msgid "Delete this recording from hard disc!" msgstr "Ištrinti šį įrašą iš disko!" msgid "Edit recording" msgstr "" msgid "play this recording." msgstr "groti šį įrašą." msgid "No schedules available for this channel" msgstr "Šis kanalas neturi programos" msgid "Couldn't find searchtimer. Maybe you mistyped your request?" msgstr "Nerandamas paieškos laikmatis. Gal pateikėte netikslią užklausą?" msgid "Search text too short - use anyway?" msgstr "Ieškoma frazė per trumpa - vistiek ją naudoti?" msgid "Search term" msgstr "Paieškos frazė" msgid "Search mode" msgstr "Paieškos ręžimas" msgid "phrase" msgstr "frazė" msgid "all words" msgstr "visi žodžiai" msgid "at least one word" msgstr "bent vienas žodis" msgid "match exactly" msgstr "tikslus atitikmuo" msgid "regular expression" msgstr "reguliari išraiška" msgid "fuzzy" msgstr "neapibrėžta" msgid "Tolerance" msgstr "Tolerancija" msgid "Match case" msgstr "Kreiti dėmesį į raidžių registrą" msgid "Search in" msgstr "Ieškoti" msgid "Episode" msgstr "Epizodas" msgid "Description" msgstr "Aprašymas" msgid "Use extended EPG info" msgstr "Naudoti išplėstą EPG informaciją" msgid "Ignore missing EPG info" msgstr "Ignoruoti trūkstamą EPG informaciją" msgid "When active this can cause very many timers. So please always first test this search before using it as search timer!" msgstr "Aktyvavus gali įjungti labai daug laikmačių. Taigi visalaik pirmiausia išbandykit šią paiesšką prieš naudodamiesi laikmačio paieška!" msgid "Use channel" msgstr "Naudoti kanalą" msgid "interval" msgstr "intervalas" msgid "channel group" msgstr "kanalo grupė" msgid "only FTA" msgstr "tik nekoduoti" msgid "from channel" msgstr "iš kanalo" msgid "to channel" msgstr "į kanalą" msgid "Use time" msgstr "Naudoti laiką" msgid "Start after" msgstr "Pradėti po to" msgid "The time the show may start at the earliest" msgstr "Laikas kada laida gali prasidėti anksčiausiai" msgid "Start before" msgstr "Pradėti prieš" msgid "The time the show may start at the latest" msgstr "Laikas kada laida gali prasidėti vėliausiai" msgid "Use duration" msgstr "Naudoti trukmę" msgid "Min. duration" msgstr "Min. trukmė" msgid "Max. duration" msgstr "Max. trukmė" msgid "Use day of week" msgstr "Naudoti savaitės dianą" msgid "Use blacklists" msgstr "Naudoti juoduosius sąrašus" msgid "all" msgstr "visi" msgid "Use in favorites menu" msgstr "Naudoti mėgiamiausiųjų sąraše" msgid "Use as search timer" msgstr "Naudoti kaip paieškos laikmatį" msgid "user defined" msgstr "vartotojo pasirinktas" msgid "from date" msgstr "nuo" msgid "to date" msgstr "iki" msgid "Record" msgstr "Įrašyti" msgid "Announce only" msgstr "Tik pristato" msgid "Switch only" msgstr "Tik pereina" msgid "Announce and Switch" msgstr "" msgid "Announce via email" msgstr "" msgid "Inactive Record" msgstr "" msgid "Series recording" msgstr "Įrašai" msgid "Delete recordings after ... days" msgstr "Ištrinti įrašus po ... dienų" msgid "Keep ... recordings" msgstr "Laikyti ... įrašų" msgid "Pause when ... recordings exist" msgstr "Sustapdyti kai pasiekia ... įrašų ribą" msgid "Avoid repeats" msgstr "Išvengti pakartojimų" msgid "Allowed repeats" msgstr "Leisti pakartojimus" msgid "Only repeats within ... days" msgstr "Kartoti tik po ... dienų" msgid "Compare title" msgstr "Palyginti pavadinimus" msgid "Compare subtitle" msgstr "Palyginti aprašus" msgid "if present" msgstr "jei egzistuoja" msgid "Compare summary" msgstr "Palyginti santrauką" msgid "Compare" msgstr "Palyginti" msgid "Auto-delete search timer" msgstr "Automatiškai ištrinti paieškos laikmatį" msgid "after ... recordings" msgstr "po ... įrašų" msgid "after ... days after first rec." msgstr "po ... dienų kai buvo padarytas pirmas įrašas" msgid "Switch ... minutes before start" msgstr "Įjungti ... minutėmis anksčiau prieš pradedant įrašą" msgid "Test" msgstr "Pabandyti" msgid "No timer defined" msgstr "Nenustatytas laikmatis" msgid "Timer is recording." msgstr "Laikmatis įrašinėja." msgid "Timer is active." msgstr "Laikmatis įjungtas." msgid "Toggle timer active/inactive" msgstr "Perjungti laikmačio aktyvumo/neaktyvumo būseną" msgid "Delete timer" msgstr "Ištrinti laikmatį" msgid "Timer conflicts" msgstr "Laikmačio klaidos" msgid "Please set login and password!" msgstr "Nurodykite prisijungimo vardą ir slapyvardį!" msgid "Setup saved." msgstr "Nustatymai išsaugoti." msgid "Setup" msgstr "Nustatymai" msgid "User management" msgstr "Vartotojo koregavimas" msgid "Local net (no login required)" msgstr "Vietinis tinklas (nereikalingas prisijungimas)" msgid "Show live logo image" msgstr "Rodyti live įskiepo logotipą" msgid "Use ajax technology" msgstr "Naudoti AJAX" msgid "Show dynamic VDR information box" msgstr "Rodyti dinaminį VDR informacijos skydelį" msgid "Allow video streaming" msgstr "Leisti video transliavimą" msgid "Web-Streaming h264" msgstr "" msgid "Web-Streaming HEVC" msgstr "" msgid "Web-Streaming MPEG2" msgstr "" msgid "Web-Streaming others" msgstr "" msgid "Streamdev server port" msgstr "Streamdev serverio prievadas" msgid "Streamdev stream type" msgstr "Streamdev serverio tipas" msgid "Mark new recordings" msgstr "" msgid "Add links to IMDb" msgstr "Pridėti nuorodas į IMDb" msgid "Additional fixed times in 'What's on?'" msgstr "papildomi nustatyti laikai 'Kas šio metu rodoma?' skiltyje" msgid "Format is HH:MM. Separate multiple times with a semicolon" msgstr "Laiko formatas yra HH:MM. Jei naudosite keletą laikų, atskirkite juos naudodami kabliataškį" #, fuzzy msgid "Channel groups for MultiSchedule" msgstr "Kainalas neturi programos aprašo" msgid "Separate channels with a comma ',', separate groups with a semi-colon ';'" msgstr "" msgid "Duration of MultiSchedule in hours" msgstr "" msgid "Show channels without EPG" msgstr "Rodyti EPG neturinčius kanalis" msgid "Start page" msgstr "Pradinis puslapis" msgid "Theme" msgstr "Tema" msgid "playing recording" msgstr "grojamas įrašas" msgid "no epg info for current event!" msgstr "šis įvykis neturi epg įrašo!" msgid "no epg info for current channel!" msgstr "šis kanalas neturi epg įrašo!" msgid "no current channel!" msgstr "nėra šito kanalo!" msgid "error retrieving status info!" msgstr "negali gauti informacijos apie būklę!" msgid "%I:%M:%S %p" msgstr "%I:%M:%S %p" msgid "Couldn't find recording. Maybe you mistyped your request?" msgstr "" msgid "Please set a name for the recording!" msgstr "" msgid "Cannot copy, rename or move the recording." msgstr "" msgid "Delete resume information" msgstr "" msgid "Delete marks information" msgstr "" msgid "Copy only" msgstr "" #, fuzzy msgid "Short description" msgstr "Aprašymas" msgid "Auxiliary info" msgstr "" msgid "Search settings" msgstr "Paieškos nustatymai" msgid "Extended search" msgstr "Išplėstinė paieška" msgid "no" msgstr "ne" msgid "Time" msgstr "" msgid "Users" msgstr "Vartotojai" msgid "Delete user" msgstr "Ištrinti vartotoją" msgid "Page error" msgstr "Puslapio klaida" msgid "No timer conflicts" msgstr "Nėra laikmačio klaidų" msgid "local" msgstr "" msgid "Timer has a conflict." msgstr "Laikmatis turi klaidų." msgid "Live Interactive VDR Environment" msgstr "Live interaktyvioji VDR aplinka" msgid "No EPG information available" msgstr "Nėra EPG informacijos" #~ msgid "VLC media URL" #~ msgstr "Adresas VLC grotuvui" #, fuzzy #~ msgid "Subtitle" #~ msgstr "Pavadinimas" vdr-plugin-live-3.1.3/po/nl_NL.po000066400000000000000000000327731414414333500165400ustar00rootroot00000000000000# VDR LIVE plugin language source file. # Copyright (C) 2007 LIVE Development team. See http://live.vdr-developer.org # This file is distributed under the same license as the VDR-LIVE package. # Arnold Niessen , 2001 # Hans Dingemans , 2003 # Maarten Wisse , 2005 # msgid "" msgstr "" "Project-Id-Version: VDR-LIVE 0.2.0\n" "Report-Msgid-Bugs-To: \n" "PO-Revision-Date: 2007-08-19 20:15+0200\n" "Last-Translator: Maarten Wisse \n" "Language-Team: see developers in README\n" "Language: nl_NL\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=ISO-8859-15\n" "Content-Transfer-Encoding: 8bit\n" msgid "Last channel to display" msgstr "" msgid "No limit" msgstr "" msgid "Use authentication" msgstr "" msgid "No" msgstr "" msgid "Yes" msgstr "" msgid "Admin login" msgstr "" msgid "Admin password" msgstr "" #, c-format msgid "%A, %x" msgstr "" #, fuzzy msgid "Searchtimer" msgstr "Gebruik als zoek timer" msgid "Error in timer settings" msgstr "" msgid "Timer already defined" msgstr "" msgid "Timer not defined" msgstr "" msgid "Timers are being edited - try again later" msgstr "" msgid "On archive DVD No." msgstr "" msgid "On archive HDD No." msgstr "" msgid "Couldn't find channel or no channels available." msgstr "" msgid "Couldn't switch to channel." msgstr "" msgid "Couldn't find recording or no recordings available." msgstr "" msgid "Cannot control playback!" msgstr "" msgid "Not playing a recording." msgstr "" msgid "Not playing the same recording as from request." msgstr "" msgid "Attempt to delete recording currently in playback." msgstr "" msgid "Epg error" msgstr "" msgid "Wrong channel id" msgstr "" msgid "Channel has no schedule" msgstr "" msgid "Wrong event id" msgstr "" msgid "Required minimum version of epgsearch: " msgstr "" msgid "All" msgstr "" msgid "FTA" msgstr "" msgid "%I:%M %p" msgstr "" msgid "EPGSearch version outdated! Please update." msgstr "" msgid "Couldn't aquire primary device" msgstr "" msgid "Couldn't grab image from primary device" msgstr "" #, fuzzy msgid "Timer conflict check detected " msgstr "Serie's opnemen" #, fuzzy msgid "conflict" msgstr "Serie's opnemen" #, fuzzy msgid "conflicts" msgstr "Serie's opnemen" msgid "Electronic program guide information" msgstr "" msgid "Couldn't find recording or no recordings available" msgstr "" msgid "Error aquiring schedules lock" msgstr "" msgid "Error aquiring schedules" msgstr "" msgid "%b %d %y" msgstr "" msgid "Searchtimers" msgstr "" msgid "Sorry, no permission. Please contact your administrator!" msgstr "" msgid "Expression" msgstr "" msgid "Channel" msgstr "" msgid "Starts between" msgstr "" msgid "Toggle search timer actions (in)active" msgstr "" msgid "Browse search timer results" msgstr "" msgid "Edit search timer" msgstr "" msgid "Delete this search timer?" msgstr "" msgid "Delete search timer" msgstr "" msgid "New search timer" msgstr "" msgid "Trigger search timer update" msgstr "" msgid "Wrong username or password" msgstr "" msgid "Login" msgstr "" msgid "VDR Live Login" msgstr "" msgid "User" msgstr "" msgid "Password" msgstr "" msgid "Remote Control" msgstr "" msgid "Couldn't aquire access to channels, please try again later." msgstr "" msgid "Couldn't find channel or no channels available. Maybe you mistyped your request?" msgstr "" msgid "Selection" msgstr "" #, fuzzy msgid "Snapshot interval" msgstr "interval" msgid "Stop" msgstr "" #, c-format msgid "%a, %x" msgstr "" msgid "What's running on" msgstr "" msgid "at" msgstr "" msgid "What's on next?" msgstr "" msgid "Favorites" msgstr "" msgid "Click to view details." msgstr "" msgid "View the schedule of this channel" msgstr "" msgid " - " msgstr "" msgid "more" msgstr "" msgid "Now" msgstr "" msgid "Next" msgstr "" msgid "What's on" msgstr "" msgid "Details view" msgstr "" msgid "List view" msgstr "" msgid "Find more at the Internet Movie Database." msgstr "" msgid "Stream this channel into browser." msgstr "" msgid "Stream this recording into media player." msgstr "" msgid "Record this" msgstr "" msgid "Edit timer" msgstr "" msgid "loading data" msgstr "" msgid "an error occured!" msgstr "" msgid "Request succeeded!" msgstr "" msgid "Request failed!" msgstr "" msgid "Sunday" msgstr "" msgid "Monday" msgstr "" msgid "Tuesday" msgstr "" msgid "Wednesday" msgstr "" msgid "Thursday" msgstr "" msgid "Friday" msgstr "" msgid "Saturday" msgstr "" #. TRANSLATORS: only adjust the ordering and separators, don't translate the m's, d's and y's msgid "mm/dd/yyyy" msgstr "" msgid "January" msgstr "" msgid "February" msgstr "" msgid "March" msgstr "" msgid "April" msgstr "" msgid "May" msgstr "" msgid "June" msgstr "" msgid "July" msgstr "" msgid "August" msgstr "" msgid "September" msgstr "" msgid "October" msgstr "" msgid "November" msgstr "" msgid "December" msgstr "" msgid "retrieving status ..." msgstr "" msgid "Toggle updates on/off." msgstr "" msgid "stop playback" msgstr "" msgid "resume playback" msgstr "" msgid "pause playback" msgstr "" msgid "fast rewind" msgstr "" msgid "fast forward" msgstr "" msgid "previous channel" msgstr "" msgid "next channel" msgstr "" msgid "No server response!" msgstr "" msgid "Failed to update infobox!" msgstr "" msgid "Switch to this channel." msgstr "" msgid "Search for repeats." msgstr "" msgid "Authors" msgstr "" msgid "Project Idea" msgstr "" msgid "Webserver" msgstr "" msgid "Current Maintainer" msgstr "" msgid "Project leader" msgstr "" msgid "Content" msgstr "" msgid "Graphics" msgstr "" msgid "Information" msgstr "" msgid "LIVE version" msgstr "" msgid "VDR version" msgstr "" msgid "Features" msgstr "" msgid "active" msgstr "" msgid "required" msgstr "" msgid "Homepage" msgstr "" msgid "Bugs and suggestions" msgstr "" msgid "If you encounter any bugs or would like to suggest new features, please use our bugtracker" msgstr "" msgid "What's on?" msgstr "" msgid "MultiSchedule" msgstr "" msgid "Search" msgstr "" msgid "Recordings" msgstr "" msgid "Web-Streaming" msgstr "" msgid "Logout" msgstr "" msgid "Your attention is required" msgstr "" msgid "React" msgstr "" msgid "Dismiss" msgstr "" msgid "Couldn't find user. Maybe you mistyped your request?" msgstr "" msgid "This user name is already in use!" msgstr "" #, fuzzy msgid "Edit user" msgstr "Serie's opnemen" msgid "New user" msgstr "" msgid "Name" msgstr "" msgid "User rights" msgstr "" #, fuzzy msgid "Edit setup" msgstr "Serie's opnemen" #, fuzzy msgid "Add or edit timers" msgstr "Gebruik als zoek timer" #, fuzzy msgid "Delete timers" msgstr "Serie's opnemen" #, fuzzy msgid "Delete recordings" msgstr "Serie's opnemen" #, fuzzy msgid "Use remote menu" msgstr "Gebruik tijd" #, fuzzy msgid "Start replay" msgstr "Start voor" #, fuzzy msgid "Switch channel" msgstr "tot kanaal" #, fuzzy msgid "Add or edit search timers" msgstr "Gebruik als zoek timer" #, fuzzy msgid "Delete search timers" msgstr "Serie's opnemen" msgid "Edit recordings" msgstr "" msgid "Save" msgstr "" msgid "Cancel" msgstr "" msgid "Search results" msgstr "" msgid "No search results" msgstr "" msgid "%A, %b %d %Y" msgstr "" msgid "Couldn't find timer. Maybe you mistyped your request?" msgstr "" msgid "Please set a title for the timer!" msgstr "" msgid "New timer" msgstr "" msgid "Title" msgstr "" msgid "Server" msgstr "" msgid "Directory" msgstr "Directory" msgid "Weekday" msgstr "" msgid "Use VPS" msgstr "" msgid "ERROR:" msgstr "" #, fuzzy msgid "Deleted recording:" msgstr "Serie's opnemen" msgid "List of recordings" msgstr "" msgid "No recordings found" msgstr "" msgid "Delete selected" msgstr "" #, no-c-format msgid "%a," msgstr "" #. TRANSLATORS: recording duration format HH:MM #, c-format msgid "(%d:%02d)" msgstr "" msgid "Sort by name" msgstr "" msgid "Sort by date" msgstr "" msgid "Filter" msgstr "" msgid "Look in recordings titles and subtitles for the given string and display only the matching ones. You may also use perl compatible regular expressions (PCRE)." msgstr "" msgid "Expand all folders" msgstr "" msgid "Collapse all folders" msgstr "" msgid "Delete this recording from hard disc!" msgstr "" msgid "Edit recording" msgstr "" msgid "play this recording." msgstr "" msgid "No schedules available for this channel" msgstr "" msgid "Couldn't find searchtimer. Maybe you mistyped your request?" msgstr "" msgid "Search text too short - use anyway?" msgstr "Zoek tekst tekort - toch gebruiken?" msgid "Search term" msgstr "" msgid "Search mode" msgstr "" msgid "phrase" msgstr "uitdruk" msgid "all words" msgstr "alle woorden" msgid "at least one word" msgstr "ten minste een woord" msgid "match exactly" msgstr "precies passend" msgid "regular expression" msgstr "reguliere uitdruk king" msgid "fuzzy" msgstr "" msgid "Tolerance" msgstr "" msgid "Match case" msgstr "Idem case" msgid "Search in" msgstr "" msgid "Episode" msgstr "" msgid "Description" msgstr "" msgid "Use extended EPG info" msgstr "Gebruik uitgebreide EPG info" msgid "Ignore missing EPG info" msgstr "" msgid "When active this can cause very many timers. So please always first test this search before using it as search timer!" msgstr "" msgid "Use channel" msgstr "Gebruik kanaal" msgid "interval" msgstr "interval" msgid "channel group" msgstr "kanaal groep" msgid "only FTA" msgstr "alleen FTA" msgid "from channel" msgstr "van kanaal" msgid "to channel" msgstr "tot kanaal" msgid "Use time" msgstr "Gebruik tijd" msgid "Start after" msgstr "Start na" msgid "The time the show may start at the earliest" msgstr "" msgid "Start before" msgstr "Start voor" msgid "The time the show may start at the latest" msgstr "" msgid "Use duration" msgstr "Gebruiks duur" msgid "Min. duration" msgstr "Min. duur" msgid "Max. duration" msgstr "Max. duur" msgid "Use day of week" msgstr "Gebruik dag van de week" msgid "Use blacklists" msgstr "" msgid "all" msgstr "" msgid "Use in favorites menu" msgstr "" msgid "Use as search timer" msgstr "Gebruik als zoek timer" msgid "user defined" msgstr "" msgid "from date" msgstr "" msgid "to date" msgstr "" msgid "Record" msgstr "Opnemen" msgid "Announce only" msgstr "Alleen aankondigen (geen timer)" msgid "Switch only" msgstr "" msgid "Announce and Switch" msgstr "" msgid "Announce via email" msgstr "" msgid "Inactive Record" msgstr "" msgid "Series recording" msgstr "Serie's opnemen" msgid "Delete recordings after ... days" msgstr "" msgid "Keep ... recordings" msgstr "" msgid "Pause when ... recordings exist" msgstr "" msgid "Avoid repeats" msgstr "" msgid "Allowed repeats" msgstr "" msgid "Only repeats within ... days" msgstr "" msgid "Compare title" msgstr "" msgid "Compare subtitle" msgstr "" msgid "if present" msgstr "" msgid "Compare summary" msgstr "" msgid "Compare" msgstr "" msgid "Auto-delete search timer" msgstr "" msgid "after ... recordings" msgstr "" msgid "after ... days after first rec." msgstr "" msgid "Switch ... minutes before start" msgstr "" msgid "Test" msgstr "" msgid "No timer defined" msgstr "" #, fuzzy msgid "Timer is recording." msgstr "Serie's opnemen" msgid "Timer is active." msgstr "" msgid "Toggle timer active/inactive" msgstr "" msgid "Delete timer" msgstr "" #, fuzzy msgid "Timer conflicts" msgstr "Serie's opnemen" msgid "Please set login and password!" msgstr "" msgid "Setup saved." msgstr "" msgid "Setup" msgstr "" msgid "User management" msgstr "" msgid "Local net (no login required)" msgstr "" msgid "Show live logo image" msgstr "" msgid "Use ajax technology" msgstr "" msgid "Show dynamic VDR information box" msgstr "" msgid "Allow video streaming" msgstr "" msgid "Web-Streaming h264" msgstr "" msgid "Web-Streaming HEVC" msgstr "" msgid "Web-Streaming MPEG2" msgstr "" msgid "Web-Streaming others" msgstr "" msgid "Streamdev server port" msgstr "" msgid "Streamdev stream type" msgstr "" msgid "Mark new recordings" msgstr "" msgid "Add links to IMDb" msgstr "" msgid "Additional fixed times in 'What's on?'" msgstr "" msgid "Format is HH:MM. Separate multiple times with a semicolon" msgstr "" msgid "Channel groups for MultiSchedule" msgstr "" msgid "Separate channels with a comma ',', separate groups with a semi-colon ';'" msgstr "" msgid "Duration of MultiSchedule in hours" msgstr "" msgid "Show channels without EPG" msgstr "" msgid "Start page" msgstr "" msgid "Theme" msgstr "" msgid "playing recording" msgstr "" msgid "no epg info for current event!" msgstr "" msgid "no epg info for current channel!" msgstr "" msgid "no current channel!" msgstr "" msgid "error retrieving status info!" msgstr "" msgid "%I:%M:%S %p" msgstr "" msgid "Couldn't find recording. Maybe you mistyped your request?" msgstr "" msgid "Please set a name for the recording!" msgstr "" msgid "Cannot copy, rename or move the recording." msgstr "" msgid "Delete resume information" msgstr "" msgid "Delete marks information" msgstr "" msgid "Copy only" msgstr "" msgid "Short description" msgstr "" msgid "Auxiliary info" msgstr "" msgid "Search settings" msgstr "" msgid "Extended search" msgstr "" msgid "no" msgstr "" msgid "Time" msgstr "" msgid "Users" msgstr "" #, fuzzy msgid "Delete user" msgstr "Serie's opnemen" msgid "Page error" msgstr "" #, fuzzy msgid "No timer conflicts" msgstr "Serie's opnemen" msgid "local" msgstr "" #, fuzzy msgid "Timer has a conflict." msgstr "Serie's opnemen" msgid "Live Interactive VDR Environment" msgstr "" msgid "No EPG information available" msgstr "" #, fuzzy #~ msgid "Start" #~ msgstr "Start na" vdr-plugin-live-3.1.3/po/pl_PL.po000066400000000000000000000457521414414333500165450ustar00rootroot00000000000000# VDR LIVE plugin language source file. # Copyright (C) 2007 LIVE Development team. See http://live.vdr-developer.org # This file is distributed under the same license as the VDR-LIVE package. # Robert Polak , 2011 # Tomasz Maciej Nowak , 2019 2021 # msgid "" msgstr "" "Project-Id-Version: VDR-LIVE 0.2.0\n" "Report-Msgid-Bugs-To: \n" "PO-Revision-Date: 2021-05-12 19:42+0200\n" "Last-Translator: Tomasz Maciej Nowak \n" "Language-Team: see developers in README\n" "Language: pl_PL\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" msgid "Last channel to display" msgstr "Ostatni kanał na liście" msgid "No limit" msgstr "Bez limitu" msgid "Use authentication" msgstr "Użyj autoryzacji" msgid "No" msgstr "Nie" msgid "Yes" msgstr "Tak" msgid "Admin login" msgstr "Login Admina" msgid "Admin password" msgstr "Hasło Admina" #, c-format msgid "%A, %x" msgstr "" msgid "Searchtimer" msgstr "Timer wyszukiwania" msgid "Error in timer settings" msgstr "Błąd w ustawieniach timera" msgid "Timer already defined" msgstr "Timer został już zdefiniowany" msgid "Timer not defined" msgstr "Timer nie zdefiniowany" msgid "Timers are being edited - try again later" msgstr "Timery są edytowane - spróbuj ponownie później" msgid "On archive DVD No." msgstr "W archiwum DVD nr" msgid "On archive HDD No." msgstr "W archiwum HDD nr" msgid "Couldn't find channel or no channels available." msgstr "Nie można odnaleść kanału lub kanały nie są dostępne." msgid "Couldn't switch to channel." msgstr "Nie można przełączyć kanału." msgid "Couldn't find recording or no recordings available." msgstr "Nie można odnaleźć nagrania lub nagranie nie jest dostępne." msgid "Cannot control playback!" msgstr "Nie można kontrolować odtwarzania!" msgid "Not playing a recording." msgstr "Nie można odtworzyć nagrania." msgid "Not playing the same recording as from request." msgstr "Nie można odtworzyć żądanego nagrania." msgid "Attempt to delete recording currently in playback." msgstr "Próba usunięcia aktualnie odtwarzanego nagrania." msgid "Epg error" msgstr "Błąd EPG" msgid "Wrong channel id" msgstr "Błędny id kanału" msgid "Channel has no schedule" msgstr "Brak programu dla wybranego kanału" msgid "Wrong event id" msgstr "Błędny id audycji" msgid "Required minimum version of epgsearch: " msgstr "Wymagana wersja epgsearch (minimum):" msgid "All" msgstr "Wszystkie" msgid "FTA" msgstr "Niekodowane - FTA" msgid "%I:%M %p" msgstr "%H:%M" msgid "EPGSearch version outdated! Please update." msgstr "Plugin EPGSearch jest nieaktualny! Proszę zaktualizować." msgid "Couldn't aquire primary device" msgstr "Nie mozna uzyskać dostępu do pierwszego interfejsu DVB" msgid "Couldn't grab image from primary device" msgstr "Nie można pobrać obrazu z pierwszego interfejsu DVB" msgid "Timer conflict check detected " msgstr "Wykryto kolizje timerów " msgid "conflict" msgstr "kolizję" msgid "conflicts" msgstr "kolizje" msgid "Electronic program guide information" msgstr "Informacje EPG" msgid "Couldn't find recording or no recordings available" msgstr "Nie można odnaleźć nagrania" msgid "Error aquiring schedules lock" msgstr "Błąd podczas próby blokady programu" msgid "Error aquiring schedules" msgstr "Błąd podczas ściągania programu" msgid "%b %d %y" msgstr "" msgid "Searchtimers" msgstr "Timery wyszukiwania" msgid "Sorry, no permission. Please contact your administrator!" msgstr "Brak uprawenień. Skontaktuj się ze swoim administratorem!" msgid "Expression" msgstr "Wyrażenie" msgid "Channel" msgstr "Kanał" msgid "Starts between" msgstr "Początek między" msgid "Toggle search timer actions (in)active" msgstr "Aktywuj/deaktywuj timer wyszukiwania" msgid "Browse search timer results" msgstr "Przeglądaj wyniki timera wyszukiwania" msgid "Edit search timer" msgstr "Edycja timera wyszukiwania" msgid "Delete this search timer?" msgstr "Usunąć ten timer wyszukiwania?" msgid "Delete search timer" msgstr "Usuń timer wyszukiwania" msgid "New search timer" msgstr "Nowy timer wyszukiwania" msgid "Trigger search timer update" msgstr "Zaprogramuj aktywne timery wyszukiwania" msgid "Wrong username or password" msgstr "Błędna nazwa lub hasło użytkownika" msgid "Login" msgstr "Logowanie" msgid "VDR Live Login" msgstr "Login VDR Live" msgid "User" msgstr "Użytkownik" msgid "Password" msgstr "Hasło" msgid "Remote Control" msgstr "Pilot" msgid "Couldn't aquire access to channels, please try again later." msgstr "Nie można uzyskać dostępu do kanałów, spróbój później." msgid "Couldn't find channel or no channels available. Maybe you mistyped your request?" msgstr "Nie można odnaleść kanału lub kanały nie są dostępne." msgid "Selection" msgstr "wybrane" msgid "Snapshot interval" msgstr "Okres pomiędzy migawkami" msgid "Stop" msgstr "Stop" #, c-format msgid "%a, %x" msgstr "" msgid "What's running on" msgstr "Teraz w TV:" msgid "at" msgstr "o godz." msgid "What's on next?" msgstr "Nastepnie" msgid "Favorites" msgstr "Ulubione" msgid "Click to view details." msgstr "Kliknij aby zobaczyć szczegóły" msgid "View the schedule of this channel" msgstr "Wyświetl program dla tego kanału" msgid " - " msgstr "" msgid "more" msgstr "więcej" msgid "Now" msgstr "Teraz" msgid "Next" msgstr "Następnie" msgid "What's on" msgstr "Audycje" msgid "Details view" msgstr "Widok szczegółowy" msgid "List view" msgstr "Widok listy" msgid "Find more at the Internet Movie Database." msgstr "Szukaj w IMDb.com" msgid "Stream this channel into browser." msgstr "Oglądaj kanał w przeglądarce" msgid "Stream this recording into media player." msgstr "Oglądaj nagranie w odtwarzaczu multimediów" msgid "Record this" msgstr "Nagraj" msgid "Edit timer" msgstr "Edycja timera" msgid "loading data" msgstr "ładuję ..." msgid "an error occured!" msgstr "wystąpił błąd!" msgid "Request succeeded!" msgstr "Zakończono powodzeniem!" msgid "Request failed!" msgstr "Zakończono niepowodzeniem!" msgid "Sunday" msgstr "Niedziela" msgid "Monday" msgstr "Poniedziałek" msgid "Tuesday" msgstr "Wtorek" msgid "Wednesday" msgstr "Środa" msgid "Thursday" msgstr "Czwartek" msgid "Friday" msgstr "Piątek" msgid "Saturday" msgstr "Sobota" #. TRANSLATORS: only adjust the ordering and separators, don't translate the m's, d's and y's msgid "mm/dd/yyyy" msgstr "dd/mm/yyyy" msgid "January" msgstr "Styczeń" msgid "February" msgstr "Luty" msgid "March" msgstr "Marzec" msgid "April" msgstr "Kwiecień" msgid "May" msgstr "Maj" msgid "June" msgstr "Czerwiec" msgid "July" msgstr "Lipiec" msgid "August" msgstr "Sierpień" msgid "September" msgstr "Wrzesień" msgid "October" msgstr "Październik" msgid "November" msgstr "Listopad" msgid "December" msgstr "Grudzień" msgid "retrieving status ..." msgstr "odczytywanie stanu ..." msgid "Toggle updates on/off." msgstr "Włącz/wyłącz aktualizację stanu." msgid "stop playback" msgstr "zatrzymaj odtwarzanie" msgid "resume playback" msgstr "wznów odtwarzanie" msgid "pause playback" msgstr "pauzuj odtwarzanie" msgid "fast rewind" msgstr "przewiń do tyłu" msgid "fast forward" msgstr "przewiń do przodu" msgid "previous channel" msgstr "poprzeni kanał" msgid "next channel" msgstr "następny kanał" msgid "No server response!" msgstr "Serwer nie odpowiada!" msgid "Failed to update infobox!" msgstr "Błąd poczas aktualizacji panela!" msgid "Switch to this channel." msgstr "Przełącz na ten kanał" msgid "Search for repeats." msgstr "Szukaj powtórek" msgid "Authors" msgstr "Autorzy" msgid "Project Idea" msgstr "Pomysł projektu" msgid "Webserver" msgstr "Serwer HTTP" msgid "Current Maintainer" msgstr "Obecny opiekun" msgid "Previous Maintainer" msgstr "Poprzedni opiekun" msgid "Project leader" msgstr "Lider projektu" msgid "Content" msgstr "Zawartość" msgid "Graphics" msgstr "Grafika" msgid "Information" msgstr "Informacje" msgid "LIVE version" msgstr "Wersja LIVE" msgid "VDR version" msgstr "Wersja VDR" msgid "Features" msgstr "Dodatki" msgid "active" msgstr "aktywny" msgid "required" msgstr "wymagany" msgid "Homepage" msgstr "Strona domowa" msgid "Bugs and suggestions" msgstr "Błędy i sugestie" msgid "If you encounter any bugs or would like to suggest new features, please use our bugtracker" msgstr "Jeśli znajdziesz jakieś błędy lub chciałbyś zasugerować nowe funkcje, skorzystaj z naszego bugtrackera" msgid "What's on?" msgstr "Teraz w TV" msgid "MultiSchedule" msgstr "Multiprzewodnik" msgid "Search" msgstr "Szukaj" msgid "Recordings" msgstr "Nagrania" msgid "Web-Streaming" msgstr "Oglądanie" msgid "Logout" msgstr "Wyloguj" msgid "Your attention is required" msgstr "Uwaga, coś nie gra " msgid "React" msgstr "Sprawdź" msgid "Dismiss" msgstr "Anuluj" msgid "Couldn't find user. Maybe you mistyped your request?" msgstr "Nie można odnaleźć użytkownika." msgid "This user name is already in use!" msgstr "Nazwa użytkownika jest już używana!" msgid "Edit user" msgstr "Edycja użytkownia" msgid "New user" msgstr "Nowy użytkownik" msgid "Name" msgstr "Nazwa" msgid "User rights" msgstr "Uprawnienia" msgid "Edit setup" msgstr "Edycja ustawień" msgid "Add or edit timers" msgstr "Dodawanie i edycja timerów" msgid "Delete timers" msgstr "Usuwanie timerów" msgid "Delete recordings" msgstr "Usuwanie niagrań" msgid "Use remote menu" msgstr "Używanie pilota" msgid "Start replay" msgstr "Startowane odtwarzania" msgid "Switch channel" msgstr "Przełączanie kanałów" msgid "Add or edit search timers" msgstr "Dodawanie i edycja timerów wyszukiwania" msgid "Delete search timers" msgstr "Usuwanie timerów wyszukiwania" msgid "Edit recordings" msgstr "Edycja nagrań" msgid "Save" msgstr "Zapisz" msgid "Cancel" msgstr "Anuluj" msgid "Search results" msgstr "Wyniki wyszykiwania" msgid "No search results" msgstr "Nic nie znaleziono" msgid "%A, %b %d %Y" msgstr "" msgid "Couldn't find timer. Maybe you mistyped your request?" msgstr "Nie można odnaleźć timera." msgid "Please set a title for the timer!" msgstr "Proszę podać tytuł dla timera!" msgid "New timer" msgstr "Nowy timer" msgid "Title" msgstr "Tytuł" msgid "Server" msgstr "Serwer" msgid "Directory" msgstr "Katalog" msgid "Weekday" msgstr "Dzień" msgid "Use VPS" msgstr "Użyj VPS" msgid "ERROR:" msgstr "BŁĄD:" msgid "Deleted recording:" msgstr "Usunięto nagranie:" msgid "List of recordings" msgstr "Lista nagrań" msgid "No recordings found" msgstr "Nie znaleziono nagrań" msgid "Delete selected" msgstr "Usuń zaznaczone" #, no-c-format msgid "%a," msgstr "" #. TRANSLATORS: recording duration format HH:MM #, c-format msgid "(%d:%02d)" msgstr "" msgid "Sort by name" msgstr "Sortuj według nazwy" msgid "Sort by date" msgstr "Sortuj według daty" msgid "Filter" msgstr "Filtr" msgid "Look in recordings titles and subtitles for the given string and display only the matching ones. You may also use perl compatible regular expressions (PCRE)." msgstr "Pokaż nagrania zgodne z wpisanym wyrażeniem (można używać także wyrażeń regularnych typu PERL)." msgid "Expand all folders" msgstr "Rozwiń wszystkie katalogi" msgid "Collapse all folders" msgstr "Zwiń wszystkie katalogi" msgid "Delete this recording from hard disc!" msgstr "Usuń to nagranie z twardego dysku!" msgid "Edit recording" msgstr "Edycja nagrania" msgid "play this recording." msgstr "Odtwórz nagranie" msgid "No schedules available for this channel" msgstr "Brak listy audycji dla tego kanału" msgid "Couldn't find searchtimer. Maybe you mistyped your request?" msgstr "Nie można odnaleźć timera wyszukiwania." msgid "Search text too short - use anyway?" msgstr "Wyszukiwany tekst jest za krótki - czy napewno kontynuować?" msgid "Search term" msgstr "Szukany tekst" msgid "Search mode" msgstr "Tryb wyszukiwania" msgid "phrase" msgstr "wyrażenie" msgid "all words" msgstr "wszystkie słowa" msgid "at least one word" msgstr "dowolne słowo" msgid "match exactly" msgstr "dokładnie całe wyrażenie" msgid "regular expression" msgstr "wyrażenie regularne" msgid "fuzzy" msgstr "niedoprecyzowane" msgid "Tolerance" msgstr "Tolerancja" msgid "Match case" msgstr "Uwzględnij wielkość liter" msgid "Search in" msgstr "Szukaj w" msgid "Episode" msgstr "Epizod" msgid "Description" msgstr "Opis" msgid "Use extended EPG info" msgstr "Użyj rozszerzonej informacji EPG" msgid "Ignore missing EPG info" msgstr "Ignoruj brakujące informacje EPG" msgid "When active this can cause very many timers. So please always first test this search before using it as search timer!" msgstr "Aktywacja timera wyszukiwania może spowodować dodanie wielu niepotrzebnych timerów, więc przed zapisem najpiew przetestuj działanie wyszukiwania!" msgid "Use channel" msgstr "Kanał" msgid "interval" msgstr "zakres" msgid "channel group" msgstr "grupa kanałów" msgid "only FTA" msgstr "tylko niekodowane" msgid "from channel" msgstr "od kanału" msgid "to channel" msgstr "do kanału" msgid "Use time" msgstr "Czas" msgid "Start after" msgstr "Rozpoczęcie po" msgid "The time the show may start at the earliest" msgstr "Wyszukiwana audycja może rozpocząć się najwcześniej o ..." msgid "Start before" msgstr "Rozpoczęcie przed" msgid "The time the show may start at the latest" msgstr "Wyszukiwana audycja może rozpocząć się najpóźniej o ..." msgid "Use duration" msgstr "Czas trwania" msgid "Min. duration" msgstr "Min. czas trwania" msgid "Max. duration" msgstr "Max. czas trwania" msgid "Use day of week" msgstr "Dzień" msgid "Use blacklists" msgstr "Czarna lista" msgid "all" msgstr "wszystkie" msgid "Use in favorites menu" msgstr "Użyj ulubionych" msgid "Use as search timer" msgstr "Aktywuj timer" msgid "user defined" msgstr "w wybranym okersie" msgid "from date" msgstr "od dnia" msgid "to date" msgstr "do dnia" msgid "Record" msgstr "Nagraj" msgid "Announce only" msgstr "Tylko powiadomienie" msgid "Switch only" msgstr "Tylko przełącz" msgid "Announce and Switch" msgstr "Ogłoś i przełącz" msgid "Announce via email" msgstr "Ołoś via email" msgid "Inactive Record" msgstr "Nieaktywne nagranie" msgid "Series recording" msgstr "Nagrywanie serii" msgid "Delete recordings after ... days" msgstr "Usuń nagranie po ... dniach:" msgid "Keep ... recordings" msgstr "Zatrzymaj ... nagrań:" msgid "Pause when ... recordings exist" msgstr "Zatrzymaj gdy istnieje ... nagrań" msgid "Avoid repeats" msgstr "Opcje powtórek" msgid "Allowed repeats" msgstr "Liczba powtórek" msgid "Only repeats within ... days" msgstr "Nagrywaj powtórki przez ... dni" msgid "Compare title" msgstr "Porównaj tytuły" msgid "Compare subtitle" msgstr "Porównaj podtytuły" msgid "if present" msgstr "jeżeli istenieją" msgid "Compare summary" msgstr "Podsumowanie porównania" msgid "Compare" msgstr "Porównaj" msgid "Auto-delete search timer" msgstr "Automatycznie usuń timer" msgid "after ... recordings" msgstr "po ... nagraniach" msgid "after ... days after first rec." msgstr "po ... dniach nagrywania" msgid "Switch ... minutes before start" msgstr "Przełącz na ... minut przed rozpoczęcziem" msgid "Test" msgstr "Testuj" msgid "No timer defined" msgstr "Brak zdefiniowanych timerów." msgid "Timer is recording." msgstr "Trwa nagranie timera." msgid "Timer is active." msgstr "Timer jest aktywny." msgid "Toggle timer active/inactive" msgstr "Aktywuj/deaktywuj timer" msgid "Delete timer" msgstr "Usuń timer" msgid "Timer conflicts" msgstr "Kolizje timerów" msgid "Please set login and password!" msgstr "Proszę wpisać login i hasło!" msgid "Setup saved." msgstr "Zapisano ustawienia." msgid "Setup" msgstr "Ustawienia" msgid "User management" msgstr "Zarządzanie użytkownikami" msgid "Local net (no login required)" msgstr "Sieć lokalna (logowanie nie wymagane)" msgid "Show live logo image" msgstr "Pokazuj logo VDR-Live" msgid "Use ajax technology" msgstr "Używaj technologii ajax" msgid "Show dynamic VDR information box" msgstr "Pokazuj panel informacyjny" msgid "Allow video streaming" msgstr "Zezwól na streamowanie strumienia video" msgid "Web-Streaming h264" msgstr "Parametry HLS dla AVC (H.264)" msgid "Web-Streaming HEVC" msgstr "Parametry HLS dla HEVC (H.265)" msgid "Web-Streaming MPEG2" msgstr "Parametry HLS dla MPEG2 (H.262)" msgid "Web-Streaming others" msgstr "Parametry HLS dla innych" msgid "Streamdev server port" msgstr "Port serwera Streamdev" msgid "Streamdev stream type" msgstr "Typ strumienia Streamdev" msgid "Mark new recordings" msgstr "Oznacz nowe nagrania" msgid "Add links to IMDb" msgstr "Pokazuj odnośniki do IMDb" msgid "Additional fixed times in 'What's on?'" msgstr "Lista wyboru godzin w zakładce 'Teraz w TV'" msgid "Format is HH:MM. Separate multiple times with a semicolon" msgstr "Wpisz czasy, które mają być wyświetlane na liście wyboru w formacie GG:MM. Oddzielaj poszczególne wpisy średnikiem." msgid "Channel groups for MultiSchedule" msgstr "Grypy kanałów w 'Multipzewodniku'" msgid "Separate channels with a comma ',', separate groups with a semi-colon ';'" msgstr "Kanały oddzielaj przecinkiem, grypu kanałów oddzielaj średnikiem (np. 1,2,7,3,11;4,5,6;21,22,23,40" msgid "Duration of MultiSchedule in hours" msgstr "Czas jaki ma obejmować Multiprzewodnik (w godzinach) " msgid "Show channels without EPG" msgstr "Pokazuj kanały bez EPG" msgid "Start page" msgstr "Strona startowa" msgid "Theme" msgstr "Skórka" msgid "playing recording" msgstr "Odtwarzanie nagrania" msgid "no epg info for current event!" msgstr "brak informacji o audycji!" msgid "no epg info for current channel!" msgstr "brak informacji epg dla kanału!" msgid "no current channel!" msgstr "brak informacji o kanale!" msgid "error retrieving status info!" msgstr "błąd podczas odczytu stanu!" msgid "%I:%M:%S %p" msgstr "%H:%M:%S" msgid "Couldn't find recording. Maybe you mistyped your request?" msgstr "Nie można odnaleźć nagrania." msgid "Please set a name for the recording!" msgstr "Proszę podać nazwę nagrania!" msgid "Cannot copy, rename or move the recording." msgstr "Nie można skopiować, przenieść ani zmienić nazwy nagrania." msgid "Delete resume information" msgstr "Usuń informację o wznowieniu" msgid "Delete marks information" msgstr "Usuń informację o znacznikach" msgid "Copy only" msgstr "Skopiuj do katalogu" msgid "Short description" msgstr "Krótki opis" msgid "Auxiliary info" msgstr "Dodatkowe informacje" msgid "Search settings" msgstr "Ustawienia wyszukiwania" msgid "Extended search" msgstr "Wyszukiwanie zaawansowane" msgid "no" msgstr "nie" msgid "Time" msgstr "Czas" msgid "Users" msgstr "Użytkownicy" msgid "Delete user" msgstr "Usuń użytkownika" msgid "Page error" msgstr "Błąd strony" msgid "No timer conflicts" msgstr "Brak kolizji timerów" msgid "local" msgstr "lokalny" msgid "Timer has a conflict." msgstr "Timer jest w kolizji." msgid "Live Interactive VDR Environment" msgstr "" msgid "No EPG information available" msgstr "Brak informacji EPG" msgid "Stream this channel into media player." msgstr "Oglądaj kanał w odtwarzaczu multimediów" vdr-plugin-live-3.1.3/po/ru_RU.po000066400000000000000000000554251414414333500165710ustar00rootroot00000000000000# VDR LIVE plugin language source file. # Copyright (C) 2007 LIVE Development team. See http://live.vdr-developer.org # This file is distributed under the same license as the VDR-LIVE package. # Dmitrii Kiselev , 2013 # msgid "" msgstr "" "Project-Id-Version: VDR-LIVE 0.3.0\n" "Report-Msgid-Bugs-To: \n" "PO-Revision-Date: 2013-06-21 20:15+0200\n" "Last-Translator: Dmitrii Kiselev \n" "Language-Team: see developers in README\n" "Language: ru\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" msgid "Last channel to display" msgstr "Последний канал для отображения" msgid "No limit" msgstr "Без ограничений" msgid "Use authentication" msgstr "Идентификация пользователя" msgid "No" msgstr "Нет" msgid "Yes" msgstr "Да" msgid "Admin login" msgstr "Имя администратора" msgid "Admin password" msgstr "Пароль администратора" #, c-format msgid "%A, %x" msgstr "" msgid "Searchtimer" msgstr "Поисковый таймер" msgid "Error in timer settings" msgstr "Ошибка в настройках таймера" msgid "Timer already defined" msgstr "Таймер установлен" msgid "Timer not defined" msgstr "Таймер не установлен" msgid "Timers are being edited - try again later" msgstr "Таймер редактируется - попробуйте позже" msgid "On archive DVD No." msgstr "В архиве на DVD No." msgid "On archive HDD No." msgstr "В архиве на HDD No." msgid "Couldn't find channel or no channels available." msgstr "Канал не найден или недоступен." msgid "Couldn't switch to channel." msgstr "Переключение на канал невозможно." msgid "Couldn't find recording or no recordings available." msgstr "Запись не найдена или более недоступна." msgid "Cannot control playback!" msgstr "Управление воспросизведением недоступно!" msgid "Not playing a recording." msgstr "Запись не проигрывается." msgid "Not playing the same recording as from request." msgstr "" msgid "Attempt to delete recording currently in playback." msgstr "" msgid "Epg error" msgstr "Ошибка телегида" msgid "Wrong channel id" msgstr "Неверный id канала" msgid "Channel has no schedule" msgstr "Нет расписания канала" msgid "Wrong event id" msgstr "Ошибка id события" msgid "Required minimum version of epgsearch: " msgstr "Минимальный требования к версии EPGSearch: " msgid "All" msgstr "Все" msgid "FTA" msgstr "Открытые" msgid "%I:%M %p" msgstr "%H:%M" msgid "EPGSearch version outdated! Please update." msgstr "Версия EPGSearch устарела! Пожалуйста обновите EPGSearch. " msgid "Couldn't aquire primary device" msgstr "Нет доступа к основному устройству" msgid "Couldn't grab image from primary device" msgstr "Не могу получить изображение с основного устройства" msgid "Timer conflict check detected " msgstr "Обнаружен конфликт таймеров" msgid "conflict" msgstr "конфликт" msgid "conflicts" msgstr "конфликты" msgid "Electronic program guide information" msgstr "Информация телегида" msgid "Couldn't find recording or no recordings available" msgstr "Запись не найдена или недоступна" msgid "Error aquiring schedules lock" msgstr "Ошибка во время получения расписания" msgid "Error aquiring schedules" msgstr "Ошибка в расписании" msgid "%b %d %y" msgstr "" msgid "Searchtimers" msgstr "Поисковый таймер" msgid "Sorry, no permission. Please contact your administrator!" msgstr "Нет доступа, обратитесь к администратору! " msgid "Expression" msgstr "Запрос" msgid "Channel" msgstr "Канал" msgid "Starts between" msgstr "Начинать между" msgid "Toggle search timer actions (in)active" msgstr "Включить поисковый таймер" msgid "Browse search timer results" msgstr "Просмотреть результаты поискового таймера" msgid "Edit search timer" msgstr "Изменить поисковый таймер" msgid "Delete this search timer?" msgstr "Удалить этот поисковый таймер?" msgid "Delete search timer" msgstr "Удалить поисковый таймер" msgid "New search timer" msgstr "Новый поисковый таймер" msgid "Trigger search timer update" msgstr "Обновить" msgid "Wrong username or password" msgstr "Неправильное имя пользователя или пароль" msgid "Login" msgstr "Вход" msgid "VDR Live Login" msgstr "VDR Live Login" msgid "User" msgstr "Пользователь" msgid "Password" msgstr "Пароль" msgid "Remote Control" msgstr "Удаленное управление" msgid "Couldn't aquire access to channels, please try again later." msgstr "Нет доступа к каналу, попробуйте позже" msgid "Couldn't find channel or no channels available. Maybe you mistyped your request?" msgstr "Канал не найден или недоступен. Проверьте правильность запроса." msgid "Selection" msgstr "Выбор" msgid "Snapshot interval" msgstr "Интервал между снимками" msgid "Stop" msgstr "Стоп" #, c-format msgid "%a, %x" msgstr "" msgid "What's running on" msgstr "Что идет сейчас?" msgid "at" msgstr "в" msgid "What's on next?" msgstr "Что даллее?" msgid "Favorites" msgstr "Избранные" msgid "Click to view details." msgstr "Нажмите чтобы узнать больше." msgid "View the schedule of this channel" msgstr "Просмотреть расписание на этом канале" msgid " - " msgstr "" msgid "more" msgstr "еще" msgid "Now" msgstr "Сейчас" msgid "Next" msgstr "Далее" msgid "What's on" msgstr "Сейчас" msgid "Details view" msgstr "Подробно" msgid "List view" msgstr "Список" msgid "Find more at the Internet Movie Database." msgstr "Найти на Kinopoisk.ru." msgid "Stream this channel into browser." msgstr "" msgid "Stream this recording into media player." msgstr "" msgid "Record this" msgstr "Запись." msgid "Edit timer" msgstr "Изменить таймер" msgid "loading data" msgstr "загрузка данных" msgid "an error occured!" msgstr "обнаружена ошибка!" msgid "Request succeeded!" msgstr "Запрос выполнен!" msgid "Request failed!" msgstr "Запрос не выполнен!" msgid "Sunday" msgstr "Воскресенье" msgid "Monday" msgstr "Понедельник" msgid "Tuesday" msgstr "Вторник" msgid "Wednesday" msgstr "Среда" msgid "Thursday" msgstr "Четверг" msgid "Friday" msgstr "Пятница" msgid "Saturday" msgstr "Суббота" #. TRANSLATORS: only adjust the ordering and separators, don't translate the m's, d's and y's msgid "mm/dd/yyyy" msgstr "dd/mm/yyyy" msgid "January" msgstr "Январь" msgid "February" msgstr "Февраль" msgid "March" msgstr "Март" msgid "April" msgstr "Апрель" msgid "May" msgstr "Май" msgid "June" msgstr "Июнь" msgid "July" msgstr "Июль" msgid "August" msgstr "Август" msgid "September" msgstr "Сентябрь" msgid "October" msgstr "Октябрь" msgid "November" msgstr "Ноябрь" msgid "December" msgstr "Декабрь" msgid "retrieving status ..." msgstr "получение статуса … " msgid "Toggle updates on/off." msgstr "Включить обновления on/off." msgid "stop playback" msgstr "остановить воспроизведение" msgid "resume playback" msgstr "продолжить воспроизведение" msgid "pause playback" msgstr "пауза" msgid "fast rewind" msgstr "перемотка вперед" msgid "fast forward" msgstr "перемотка назад" msgid "previous channel" msgstr "предыдущий канал" msgid "next channel" msgstr "следующий канал" msgid "No server response!" msgstr "Сервер не отвечает!" msgid "Failed to update infobox!" msgstr "Ошибка обновления infobox!" msgid "Switch to this channel." msgstr "Переключиться на этот канал." msgid "Search for repeats." msgstr "Найти повторы." msgid "Authors" msgstr "Авторы" msgid "Project Idea" msgstr "Идея проекта" msgid "Webserver" msgstr "WEB сервер" msgid "Current Maintainer" msgstr "" msgid "Project leader" msgstr "Ведущий проекта" msgid "Content" msgstr "Содержание" msgid "Graphics" msgstr "Графика" msgid "Information" msgstr "Информация" msgid "LIVE version" msgstr "Версия LIVE" msgid "VDR version" msgstr "Версия VDR" msgid "Features" msgstr "Опции" msgid "active" msgstr "активный" msgid "required" msgstr "требуется" msgid "Homepage" msgstr "Домашняя страница" msgid "Bugs and suggestions" msgstr "Ошибки и пожелания" msgid "If you encounter any bugs or would like to suggest new features, please use our bugtracker" msgstr "Если у вас возникнут ошибки или Вы хотите расширить функции LIVE, пожалуйста, воспользуйтесь bugtracker" msgid "What's on?" msgstr "Сейчас" msgid "MultiSchedule" msgstr "Мультирасписание" msgid "Search" msgstr "Поиск" msgid "Recordings" msgstr "Записи" msgid "Web-Streaming" msgstr "" msgid "Logout" msgstr "Выход" msgid "Your attention is required" msgstr "Требуется Ваше внимание" msgid "React" msgstr "Реагировать" msgid "Dismiss" msgstr "Освободить" msgid "Couldn't find user. Maybe you mistyped your request?" msgstr "Пользователь не найден. Проверьте правильность запроса." msgid "This user name is already in use!" msgstr "Это имя пользователя уже используется!" msgid "Edit user" msgstr "Изменить пользователя" msgid "New user" msgstr "Новый пользователь" msgid "Name" msgstr "Имя" msgid "User rights" msgstr "Права пользователя" msgid "Edit setup" msgstr "Изменять настройки" msgid "Add or edit timers" msgstr "Добавлять или изменять таймеры" msgid "Delete timers" msgstr "Удалять таймеры" msgid "Delete recordings" msgstr "Удалять записи" msgid "Use remote menu" msgstr "Удаленное управление" msgid "Start replay" msgstr "Начинать воспроизведение" msgid "Switch channel" msgstr "Переключать каналы" msgid "Add or edit search timers" msgstr "Добавлять или изменять поисковые таймеры" msgid "Delete search timers" msgstr "Удалять поисковые таймеры" msgid "Edit recordings" msgstr "Изменять запись" msgid "Save" msgstr "Сохранить" msgid "Cancel" msgstr "Отменить" msgid "Search results" msgstr "Результат поиска" msgid "No search results" msgstr "Нет результатов поиска" msgid "%A, %b %d %Y" msgstr "%A, %d.%m.%Y" msgid "Couldn't find timer. Maybe you mistyped your request?" msgstr "Таймер не найден. Проверьте правильность запроса." msgid "Please set a title for the timer!" msgstr "Введите название таймера" msgid "New timer" msgstr "Новый таймер" msgid "Title" msgstr "Название" msgid "Server" msgstr "" msgid "Directory" msgstr "Каталог" msgid "Weekday" msgstr "День недели" msgid "Use VPS" msgstr "VPS" msgid "ERROR:" msgstr "ОШИБКА:" msgid "Deleted recording:" msgstr "Удаленный записи:" msgid "List of recordings" msgstr "Список записей" msgid "No recordings found" msgstr "Записи не найдены" msgid "Delete selected" msgstr "Удалить выбранные" #, no-c-format msgid "%a," msgstr "" #. TRANSLATORS: recording duration format HH:MM #, c-format msgid "(%d:%02d)" msgstr "" msgid "Sort by name" msgstr "Название" msgid "Sort by date" msgstr "Дата" msgid "Filter" msgstr "Фильтр" msgid "Look in recordings titles and subtitles for the given string and display only the matching ones. You may also use perl compatible regular expressions (PCRE)." msgstr "Искать только в названиях записей и субтитров в данной строке и отобразить совпадающие. Также вы можете использовать perl совместимые стандартные команды (PCRE)." msgid "Expand all folders" msgstr "Развернуть все папки" msgid "Collapse all folders" msgstr "Свернуть все папки" msgid "Delete this recording from hard disc!" msgstr "Удалить эту запись с жесткого диска!" msgid "Edit recording" msgstr "Изменить запись." msgid "play this recording." msgstr "Проиграть эту запись." msgid "No schedules available for this channel" msgstr "Расписание для данного канала недоступно" msgid "Couldn't find searchtimer. Maybe you mistyped your request?" msgstr "Поисковый таймер не найден. Проверьте правильность запроса." msgid "Search text too short - use anyway?" msgstr "Текст запроса слишком короткий, продолжить?" msgid "Search term" msgstr "Поисковый запрос" msgid "Search mode" msgstr "Метод поиска" msgid "phrase" msgstr "фраза" msgid "all words" msgstr "все слова" msgid "at least one word" msgstr "хотя бы одно слово" msgid "match exactly" msgstr "точное совпадение" msgid "regular expression" msgstr "стандартное выражение" msgid "fuzzy" msgstr "неопределенное" msgid "Tolerance" msgstr "Допустимое" msgid "Match case" msgstr "С учетом регистра" msgid "Search in" msgstr "Искать в" msgid "Episode" msgstr "Эпизод" msgid "Description" msgstr "Описание" msgid "Use extended EPG info" msgstr "Расширенная информацию телегида" msgid "Ignore missing EPG info" msgstr "Игнорировать отсутствие информации телегида" msgid "When active this can cause very many timers. So please always first test this search before using it as search timer!" msgstr "Внимание! Проверьте поисковый таймер перед включением этой функции. Это поможет избежать ошибочных записей." msgid "Use channel" msgstr "Канал" msgid "interval" msgstr "интервал" msgid "channel group" msgstr "группа каналов" msgid "only FTA" msgstr "только открытые" msgid "from channel" msgstr "с канала" msgid "to channel" msgstr "по канал" msgid "Use time" msgstr "Время" msgid "Start after" msgstr "Запускать после" msgid "The time the show may start at the earliest" msgstr "Программа может начаться раньше" msgid "Start before" msgstr "Запускать до" msgid "The time the show may start at the latest" msgstr "Программа может начаться позже" msgid "Use duration" msgstr "Продолжительность" msgid "Min. duration" msgstr "Мин. продолжительность" msgid "Max. duration" msgstr "Макс. продолжительность" msgid "Use day of week" msgstr "День недели" msgid "Use blacklists" msgstr "Черный список" msgid "all" msgstr "все" msgid "Use in favorites menu" msgstr "В избранное меню" msgid "Use as search timer" msgstr "Поисковый таймер" msgid "user defined" msgstr "настроить" msgid "from date" msgstr "с даты" msgid "to date" msgstr "по дату" msgid "Record" msgstr "Запись" msgid "Announce only" msgstr "Только объявлять" msgid "Switch only" msgstr "Только переключать" msgid "Announce and Switch" msgstr "" msgid "Announce via email" msgstr "" msgid "Inactive Record" msgstr "" msgid "Series recording" msgstr "Запись серий" msgid "Delete recordings after ... days" msgstr "Удалить запись после … дней" msgid "Keep ... recordings" msgstr "Хранить запись … дней" msgid "Pause when ... recordings exist" msgstr "Останавлить по достижению … записей" msgid "Avoid repeats" msgstr "Избегать повторов" msgid "Allowed repeats" msgstr "Разрешать повторы" msgid "Only repeats within ... days" msgstr "Разрешать повторы … дней" msgid "Compare title" msgstr "Сравнить название" msgid "Compare subtitle" msgstr "Сравнить субтитры" msgid "if present" msgstr "если есть" msgid "Compare summary" msgstr "Сравнить размер" msgid "Compare" msgstr "Сравнить" msgid "Auto-delete search timer" msgstr "Автоматически удалять поисковый таймер" msgid "after ... recordings" msgstr "после … записей" msgid "after ... days after first rec." msgstr "после … дней после первой записи" msgid "Switch ... minutes before start" msgstr "Переключаться за … минут до начала" msgid "Test" msgstr "Тест" msgid "No timer defined" msgstr "Таймер не установлен" msgid "Timer is recording." msgstr "Таймер записывает." msgid "Timer is active." msgstr "Таймер активен" msgid "Toggle timer active/inactive" msgstr "Переключить таймер активны/неактивный" msgid "Delete timer" msgstr "Удалить таймер" msgid "Timer conflicts" msgstr "Таймер конфликт" msgid "Please set login and password!" msgstr "Пожалуйста установите логин и пароль!" msgid "Setup saved." msgstr "Настройки сохранены." msgid "Setup" msgstr "Настройка" msgid "User management" msgstr "Настройки пользователя" msgid "Local net (no login required)" msgstr "Локальная сеть (авторизация не требуется)" msgid "Show live logo image" msgstr "Показать логотип LIVE" msgid "Use ajax technology" msgstr "Использовать ajax" msgid "Show dynamic VDR information box" msgstr "Показывать инфо панель VDR" msgid "Allow video streaming" msgstr "Разрешить видео стриминг" msgid "Web-Streaming h264" msgstr "" msgid "Web-Streaming HEVC" msgstr "" msgid "Web-Streaming MPEG2" msgstr "" msgid "Web-Streaming others" msgstr "" msgid "Streamdev server port" msgstr "Streamdev порт" msgid "Streamdev stream type" msgstr "Streamdev тип стриминга" msgid "Mark new recordings" msgstr "" msgid "Add links to IMDb" msgstr "Добавить ссылки на Kinopoisk.ru" msgid "Additional fixed times in 'What's on?'" msgstr "Поправка времени в разделе 'Сейчас'" msgid "Format is HH:MM. Separate multiple times with a semicolon" msgstr "Формат HH:MM. Разделяется несколько раз при помощи запятой" msgid "Channel groups for MultiSchedule" msgstr "Группа каналов для мультирасписания" msgid "Separate channels with a comma ',', separate groups with a semi-colon ';'" msgstr "Разделить каналы при помощи ',', разделите группы при помощи ';'" msgid "Duration of MultiSchedule in hours" msgstr "Продолжительность мультирасписания в часах" msgid "Show channels without EPG" msgstr "Показывать каналы без EPG" msgid "Start page" msgstr "Стартовая страница" msgid "Theme" msgstr "Тема" msgid "playing recording" msgstr "воспроизведение записи" msgid "no epg info for current event!" msgstr "нет информации телегида о текущем событии!" msgid "no epg info for current channel!" msgstr "нет информации телегида на данном канале!" msgid "no current channel!" msgstr "нет канала!" msgid "error retrieving status info!" msgstr "ошибка при получении статуса" msgid "%I:%M:%S %p" msgstr "%H:%M:%S" msgid "Couldn't find recording. Maybe you mistyped your request?" msgstr "Запись не найдена. Проверьте правильность запроса." msgid "Please set a name for the recording!" msgstr "Пожалуйста присвойте имя записи!" msgid "Cannot copy, rename or move the recording." msgstr "Копирование невозможно, переименуйте или переместите запись. " msgid "Delete resume information" msgstr "Удалить информацию о воспроизведении" msgid "Delete marks information" msgstr "Удалить маркеры" msgid "Copy only" msgstr "Копировать" msgid "Short description" msgstr "Краткое описание" msgid "Auxiliary info" msgstr "Другое" msgid "Search settings" msgstr "Настройки поиска" msgid "Extended search" msgstr "Расширенный поиск" msgid "no" msgstr "нет" msgid "Time" msgstr "Время" msgid "Users" msgstr "Пользователь" msgid "Delete user" msgstr "Удалить пользователя" msgid "Page error" msgstr "Ошибка страницы" msgid "No timer conflicts" msgstr "Нет конфликта таймеров" msgid "local" msgstr "" msgid "Timer has a conflict." msgstr "Есть конфликт таймеров" msgid "Live Interactive VDR Environment" msgstr "" msgid "No EPG information available" msgstr "EPG информация недоступна" #~ msgid "VLC media URL" #~ msgstr "VLC медиа URL" #~ msgid "ERROR" #~ msgstr "ОШИБКА" vdr-plugin-live-3.1.3/po/sk_SK.po000066400000000000000000000454171414414333500165470ustar00rootroot00000000000000# VDR LIVE plugin language source file. # Copyright (C) 2007 LIVE Development team. See http://live.vdr-developer.org # This file is distributed under the same license as the VDR-LIVE package. # Vladimr Brta , 2006 # msgid "" msgstr "" "Project-Id-Version: VDR-LIVE 0.2.0\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: \n" "PO-Revision-Date: 2012-12-03 21:41+0100\n" "Last-Translator: Milan Hrala \n" "Language-Team: Slovak \n" "Language: sk_SK\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=ISO-8859-2\n" "Content-Transfer-Encoding: 8bit\n" msgid "Last channel to display" msgstr "Posledn zobrazen kanl na obrazovke" msgid "No limit" msgstr "Bez obmedzenia" msgid "Use authentication" msgstr "Poui overenie totonosti" msgid "No" msgstr "Nie" msgid "Yes" msgstr "no" msgid "Admin login" msgstr "Prihlasovacie meno admina" msgid "Admin password" msgstr "Heslo admina" #, c-format msgid "%A, %x" msgstr "%A, %x" msgid "Searchtimer" msgstr "Vyhadva vysielacieho asu" msgid "Error in timer settings" msgstr "Chyba v nastaven plnu" msgid "Timer already defined" msgstr "Pln nahrvania nie je stanoven." msgid "Timer not defined" msgstr "Pln nahrvania nie je stanoven." msgid "Timers are being edited - try again later" msgstr "Plny sa upravuj - skste to znova neskr" msgid "On archive DVD No." msgstr "Na archvne DVD." msgid "On archive HDD No." msgstr "Na archvne HDD." msgid "Couldn't find channel or no channels available." msgstr "Nenaiel iadny kanl alebo kanly nie s k dispozcii." msgid "Couldn't switch to channel." msgstr "Ned sa prepn na kanl." msgid "Couldn't find recording or no recordings available." msgstr "Nenali sa iadne zznamy alebo nahrvky nie s k dispozcii." msgid "Cannot control playback!" msgstr "Nemono ovlda prehrvanie!" msgid "Not playing a recording." msgstr "Zznam sa neprehrva." msgid "Not playing the same recording as from request." msgstr "Ak sa vyiada rovnak nahrvka, tak ju neprehraje." msgid "Attempt to delete recording currently in playback." msgstr "Pokus o vymazanie nahrvky, ktor je v sasnosti v prehrvan." msgid "Epg error" msgstr "Epg chyba" msgid "Wrong channel id" msgstr "patn id kanlu" msgid "Channel has no schedule" msgstr "Kanl nem sprievodcu televznym programom" msgid "Wrong event id" msgstr "Nesprvne ID udalosti" msgid "Required minimum version of epgsearch: " msgstr "Povinn minimlna verzia epgsearch:" msgid "All" msgstr "Vetky" msgid "FTA" msgstr "Von" msgid "%I:%M %p" msgstr "%H:%M" msgid "EPGSearch version outdated! Please update." msgstr "Zastaral verzia EPGSearch! Prosm aktualizujte ju." msgid "Couldn't aquire primary device" msgstr "Nepodarilo sa zska hlavn zariadenie" msgid "Couldn't grab image from primary device" msgstr "Nemono stiahnu obraz z hlavnho zariadenia" msgid "Timer conflict check detected " msgstr "Konflikt plnu nahrvania " msgid "conflict" msgstr "konflikt " msgid "conflicts" msgstr "konflikty " msgid "Electronic program guide information" msgstr "Elektronick sprievodca televznym programom" msgid "Couldn't find recording or no recordings available" msgstr "Nenaiel sa zznam alebo nahrvky nie s k dispozcii" msgid "Error aquiring schedules lock" msgstr "Uzamknut zskavanie televzneho programu" msgid "Error aquiring schedules" msgstr "Chyba pri zskavan televzneho programu" msgid "%b %d %y" msgstr "%b %d %y" msgid "Searchtimers" msgstr "Vyhadva vysielacieho asu" msgid "Sorry, no permission. Please contact your administrator!" msgstr "Prepte, nemte oprvnenie. Prosm kontaktujte vaeho administrtora!" msgid "Expression" msgstr "Pripomienky" msgid "Channel" msgstr "Kanl" msgid "Starts between" msgstr "Zana medzi" msgid "Toggle search timer actions (in)active" msgstr "Vypna vyhadvania. Zapn/Vypn" msgid "Browse search timer results" msgstr "Zobrazi vsledky vyhadvania" msgid "Edit search timer" msgstr "prava vyhadvaa vysielacieho asu" msgid "Delete this search timer?" msgstr "Vymaza tento vyhadan pln?" msgid "Delete search timer" msgstr "Vymaza vyhadva" msgid "New search timer" msgstr "Nov vyhadva vysielacieho asu" msgid "Trigger search timer update" msgstr "Spusti obnovenie vyhadvania asu" msgid "Wrong username or password" msgstr "Chybn uvatesk meno alebo heslo" msgid "Login" msgstr "Meno" msgid "VDR Live Login" msgstr "VDR Live meno" msgid "User" msgstr "Uvate" msgid "Password" msgstr "Heslo" msgid "Remote Control" msgstr "Diakov ovldanie" msgid "Couldn't aquire access to channels, please try again later." msgstr "Ned sa zska prstup ku kanlom, prosm skste to znova neskr." msgid "Couldn't find channel or no channels available. Maybe you mistyped your request?" msgstr "Nenaiel sa iadny kanl alebo kanly nie s k dispozcii. Mono ste zle napsali vau poiadavku?" msgid "Selection" msgstr "Vybran" msgid "Snapshot interval" msgstr "Interval snmkovania" msgid "Stop" msgstr "Zastavi" #, c-format msgid "%a, %x" msgstr "%a, %x" msgid "What's running on" msgstr "o prve ide?" msgid "at" msgstr "o" msgid "What's on next?" msgstr "o nasleduje?" msgid "Favorites" msgstr "Obben" msgid "Click to view details." msgstr "Podrobnosti zskate kliknutm." msgid "View the schedule of this channel" msgstr "Zobrazi televzny program pre tento kanl" msgid " - " msgstr " - " msgid "more" msgstr "viac" msgid "Now" msgstr "Teraz ide" msgid "Next" msgstr "Nasleduje" msgid "What's on" msgstr "o bude" msgid "Details view" msgstr "Zobrazi podrobnosti" msgid "List view" msgstr "Zobrazi ako zoznam" msgid "Find more at the Internet Movie Database." msgstr "Viac na Internet Movie Database." msgid "Stream this channel into browser." msgstr "" msgid "Stream this recording into media player." msgstr "" msgid "Record this" msgstr "Nahra" msgid "Edit timer" msgstr "Upravi pln" msgid "loading data" msgstr "natanie dt" msgid "an error occured!" msgstr "dolo k chybe!" msgid "Request succeeded!" msgstr "Vyhovelo iadosti!" msgid "Request failed!" msgstr "Chyba iadosti!" msgid "Sunday" msgstr "Nedea" msgid "Monday" msgstr "Pondelok" msgid "Tuesday" msgstr "Utorok" msgid "Wednesday" msgstr "Streda" msgid "Thursday" msgstr "tvrtok" msgid "Friday" msgstr "Piatok" msgid "Saturday" msgstr "Sobota" #. TRANSLATORS: only adjust the ordering and separators, don't translate the m's, d's and y's msgid "mm/dd/yyyy" msgstr "mm/dd/yyyy" msgid "January" msgstr "Janur" msgid "February" msgstr "februr" msgid "March" msgstr "Marec" msgid "April" msgstr "Aprl" msgid "May" msgstr "Mj" msgid "June" msgstr "Jn" msgid "July" msgstr "Jl" msgid "August" msgstr "August" msgid "September" msgstr "September" msgid "October" msgstr "Oktber" msgid "November" msgstr "November" msgid "December" msgstr "December" msgid "retrieving status ..." msgstr "natavanie stavu" msgid "Toggle updates on/off." msgstr "Prepna aktualizcii zapn/vypn." msgid "stop playback" msgstr "zastavenie prehrvania" msgid "resume playback" msgstr "obnovi prehrvanie" msgid "pause playback" msgstr "pozastavenie prehrvania" msgid "fast rewind" msgstr "rchlo pretoi" msgid "fast forward" msgstr "rchlo vpred" msgid "previous channel" msgstr "predchdzajci kanl" msgid "next channel" msgstr "al kanl" msgid "No server response!" msgstr "Server neodpoved!" msgid "Failed to update infobox!" msgstr "Nepodarilo sa aktualizova infobox!" msgid "Switch to this channel." msgstr "Prepn na tento kanl." msgid "Search for repeats." msgstr "Vyhada reprzy." msgid "Authors" msgstr "Autori" msgid "Project Idea" msgstr "Mylienka projektu" msgid "Webserver" msgstr "Webov server" msgid "Current Maintainer" msgstr "" msgid "Project leader" msgstr "Vedci projektu" msgid "Content" msgstr "Obsah" msgid "Graphics" msgstr "Grafika" msgid "Information" msgstr "Informcie" msgid "LIVE version" msgstr "LIVE verzia" msgid "VDR version" msgstr "VDR verzia" msgid "Features" msgstr "Charakteristika" msgid "active" msgstr "aktvny" msgid "required" msgstr "poadovan" msgid "Homepage" msgstr "Domovsk strnka" msgid "Bugs and suggestions" msgstr "Chyby a podnety" msgid "If you encounter any bugs or would like to suggest new features, please use our bugtracker" msgstr "Ak narazte na akkovek chybu, alebo ak chcete navrhn nov funkcie, pouite prosm n BUGTRACKER." msgid "What's on?" msgstr "o prve ide?" msgid "MultiSchedule" msgstr "TV program v stpcoch" msgid "Search" msgstr "Hada" msgid "Recordings" msgstr "Nahrvky" msgid "Web-Streaming" msgstr "" msgid "Logout" msgstr "Odhlsi" msgid "Your attention is required" msgstr "Upozornenie! " msgid "React" msgstr "Reagova" msgid "Dismiss" msgstr "Zru" msgid "Couldn't find user. Maybe you mistyped your request?" msgstr "Nepodarilo sa njs uvatea. Mono ste preklepli vau poiadavku?" msgid "This user name is already in use!" msgstr "To to uvatesk meno sa u pouva" msgid "Edit user" msgstr "Upravi Uvatea" msgid "New user" msgstr "Nov uvate" msgid "Name" msgstr "Meno" msgid "User rights" msgstr "Uvatesk prva" msgid "Edit setup" msgstr "Upravi nastavenie" msgid "Add or edit timers" msgstr "Prida a upravi plny" msgid "Delete timers" msgstr "Vymaza plny" msgid "Delete recordings" msgstr "Vymaza nahrvky" msgid "Use remote menu" msgstr "Poui vzdialen menu" msgid "Start replay" msgstr "Spusti prehrvanie" msgid "Switch channel" msgstr "Prepn kanl" msgid "Add or edit search timers" msgstr "Prida alebo upravi vyhadvanie plnov" msgid "Delete search timers" msgstr "Vymaza vyhadva plnov" msgid "Edit recordings" msgstr "Upravi nahrvky" msgid "Save" msgstr "Uloi" msgid "Cancel" msgstr "Zrui" msgid "Search results" msgstr "Vsledky vyhadvania" msgid "No search results" msgstr "Hadanie bez vsledku" msgid "%A, %b %d %Y" msgstr "%A, %b %d %Y" msgid "Couldn't find timer. Maybe you mistyped your request?" msgstr "Nenaiel sa pln nahrvania. Je mon, e ste zle napsali vau poiadavku." msgid "Please set a title for the timer!" msgstr "Prosm nastavte titul pre pln nahrvania!" msgid "New timer" msgstr "Nov pln" msgid "Title" msgstr "V nzve" msgid "Server" msgstr "" msgid "Directory" msgstr "Adresr" msgid "Weekday" msgstr "Pracovn de" msgid "Use VPS" msgstr "Poui VPS" msgid "ERROR:" msgstr "CHYBA:" msgid "Deleted recording:" msgstr "Vymazan nahrvka:" msgid "List of recordings" msgstr "Zoznam nahrvok" msgid "No recordings found" msgstr "Nenala sa nahrvka" msgid "Delete selected" msgstr "Vymaza vybran" #, no-c-format msgid "%a," msgstr "%a," #. TRANSLATORS: recording duration format HH:MM #, c-format msgid "(%d:%02d)" msgstr "(%d:%02d)" msgid "Sort by name" msgstr "Zotriedi podla mena" msgid "Sort by date" msgstr "Zotriedi podla dtumu" msgid "Filter" msgstr "Filter" msgid "Look in recordings titles and subtitles for the given string and display only the matching ones. You may also use perl compatible regular expressions (PCRE)." msgstr "Pozrite sa na nahrvky titulov a titulky pre dan reazec a zobrazi iba tie vyhovujce. Mete poui kompatibiln perl regulrne vrazy (PCRE)." msgid "Expand all folders" msgstr "Rozbali vetky zloky" msgid "Collapse all folders" msgstr "Zbali vetky zloky" msgid "Delete this recording from hard disc!" msgstr "Vymaza nahrvku z HDD" msgid "Edit recording" msgstr "Upravi nahrvku" msgid "play this recording." msgstr "Prehra zznam." msgid "No schedules available for this channel" msgstr "Pre tento kanl sa nenaiel iadny televzny program." msgid "Couldn't find searchtimer. Maybe you mistyped your request?" msgstr "Nenaiel sa vyhadvan as. Mono ste zle napsali vau poiadavku?" msgid "Search text too short - use anyway?" msgstr "Hadan text je prli krtky - poui aj tak?" msgid "Search term" msgstr "Hadan pojem" msgid "Search mode" msgstr "Spsob hadania" msgid "phrase" msgstr "vraz" msgid "all words" msgstr "vetky slov" msgid "at least one word" msgstr "aspo jedno slovo" msgid "match exactly" msgstr "porovna presne" msgid "regular expression" msgstr "regulrny vraz" msgid "fuzzy" msgstr "nejasn" msgid "Tolerance" msgstr "tolerancia" msgid "Match case" msgstr "Prpadne porovna" msgid "Search in" msgstr "Hada v" msgid "Episode" msgstr "v epizde" msgid "Description" msgstr "v popise" msgid "Use extended EPG info" msgstr "Poui rozren EPG informcie" msgid "Ignore missing EPG info" msgstr "Ignorova chbajce EPG informcie" msgid "When active this can cause very many timers. So please always first test this search before using it as search timer!" msgstr "Aktvny me spsobi vemi vea plnov nahrvania. Take, prosm, vdy najprv testujte vyhadvanie pred pouitm!" msgid "Use channel" msgstr "Uri kanl" msgid "interval" msgstr "rozmedzie" msgid "channel group" msgstr "skupina kanlov" msgid "only FTA" msgstr "iba volne riten" msgid "from channel" msgstr "od kanlu" msgid "to channel" msgstr "po kanl" msgid "Use time" msgstr "Uri as" msgid "Start after" msgstr "tart po" msgid "The time the show may start at the earliest" msgstr "as, ke show me zaa o najskr" msgid "Start before" msgstr "tart pred" msgid "The time the show may start at the latest" msgstr "V ase, ke show me zaa najneskr" msgid "Use duration" msgstr "Uri dku" msgid "Min. duration" msgstr "Minimlna dka" msgid "Max. duration" msgstr "Maximlna dka" msgid "Use day of week" msgstr "Uri de v tdni" msgid "Use blacklists" msgstr "Poui iernu listinu" msgid "all" msgstr "vetky" msgid "Use in favorites menu" msgstr "Pouite v menu obbench" msgid "Use as search timer" msgstr "Poui ako vyhadva plnu" msgid "user defined" msgstr "pouvateom definovan" msgid "from date" msgstr "od dtumu" msgid "to date" msgstr "do dtumu" msgid "Record" msgstr "Nahra" msgid "Announce only" msgstr "Len oznamova" msgid "Switch only" msgstr "Iba prepna" msgid "Announce and Switch" msgstr "" msgid "Announce via email" msgstr "" msgid "Inactive Record" msgstr "" msgid "Series recording" msgstr "Sriov nahrvky" msgid "Delete recordings after ... days" msgstr "Odstrnenie nahrvok po ... doch" msgid "Keep ... recordings" msgstr "Uchova ... nahrvok" msgid "Pause when ... recordings exist" msgstr "Pauza, ke ... nahrvky existuj" msgid "Avoid repeats" msgstr "Vyhn sa reprzam" msgid "Allowed repeats" msgstr "Povolen reprzy" msgid "Only repeats within ... days" msgstr "Opakova iba do ... dn" msgid "Compare title" msgstr "Porovna titul" msgid "Compare subtitle" msgstr "Porovna titulky" msgid "if present" msgstr "ak je prtomn" msgid "Compare summary" msgstr "Porovna celkovo" msgid "Compare" msgstr "Porovnanie" msgid "Auto-delete search timer" msgstr "Samo zmazanie vyhadanho plnu" msgid "after ... recordings" msgstr "po ... nahrvkach" msgid "after ... days after first rec." msgstr "po ... doch po prvom nahrat." msgid "Switch ... minutes before start" msgstr "Prepn ... mint pred zaiatkom" msgid "Test" msgstr "Testova" msgid "No timer defined" msgstr "iadny pln nahrvania nie je stanoven" msgid "Timer is recording." msgstr "Nahrva sa poda plnu." msgid "Timer is active." msgstr "Pln je aktvny." msgid "Toggle timer active/inactive" msgstr "Vypna plnu Zapn/vypn" msgid "Delete timer" msgstr "Vymaza pln" msgid "Timer conflicts" msgstr "Konflikty plnu" msgid "Please set login and password!" msgstr "Prosm nastavte uvatesk meno a heslo!" msgid "Setup saved." msgstr "Nastavenie uloen." msgid "Setup" msgstr "Nastavenie" msgid "User management" msgstr "Uvatesk manament" msgid "Local net (no login required)" msgstr "Miestna sie (nevyaduje prihlsenie)" msgid "Show live logo image" msgstr "Zobrazi obrzok live loga" msgid "Use ajax technology" msgstr "Poui ajax technolgiu" msgid "Show dynamic VDR information box" msgstr "Zobrazi dynamicky VDR informan box" msgid "Allow video streaming" msgstr "Povoli prdov video" msgid "Web-Streaming h264" msgstr "" msgid "Web-Streaming HEVC" msgstr "" msgid "Web-Streaming MPEG2" msgstr "" msgid "Web-Streaming others" msgstr "" msgid "Streamdev server port" msgstr "Streamdev port servera" msgid "Streamdev stream type" msgstr "Typ prdovho videa" msgid "Mark new recordings" msgstr "" msgid "Add links to IMDb" msgstr "Prida odkazy na IMDB" msgid "Additional fixed times in 'What's on?'" msgstr "Dodaton as v ponuke 'o prve ide?'" msgid "Format is HH:MM. Separate multiple times with a semicolon" msgstr "Vo formte HH:MM. Oddelen niekokokrt bodkoiarkou" msgid "Channel groups for MultiSchedule" msgstr "Skupiny kanlov pre televzny program v stpcoch" msgid "Separate channels with a comma ',', separate groups with a semi-colon ';'" msgstr "Kanly oddeli s iarkou ',', skupiny kanlov oddeova bodkoiarkou ';'" msgid "Duration of MultiSchedule in hours" msgstr "Dka stpcovitho televzneho programu v hodinch" msgid "Show channels without EPG" msgstr "Zobrazi kanly s EPG" msgid "Start page" msgstr "tartovacia strnka" msgid "Theme" msgstr "Vzhad" msgid "playing recording" msgstr "Prehrvanie zznamu" msgid "no epg info for current event!" msgstr "iadne EPG informcie na aktulnu udalos!" msgid "no epg info for current channel!" msgstr "iadne epg pre vybran kanl!" msgid "no current channel!" msgstr "iadny vybran kanl!" msgid "error retrieving status info!" msgstr "Chyba pri natan informci o stave!" msgid "%I:%M:%S %p" msgstr "%H:%M:%S" msgid "Couldn't find recording. Maybe you mistyped your request?" msgstr "Nenala sa nahrvka. Mono ste zle napsali vau poiadavku?" msgid "Please set a name for the recording!" msgstr "Prosm zvolte meno pre nahrvku!" msgid "Cannot copy, rename or move the recording." msgstr "Nemono koprova, premenova alebo presun nahrvku." msgid "Delete resume information" msgstr "Znovu zmaza informcie" msgid "Delete marks information" msgstr "Vymaza informan znaky" msgid "Copy only" msgstr "Iba koprova" msgid "Short description" msgstr "Strun opis" msgid "Auxiliary info" msgstr "Pomocn info" msgid "Search settings" msgstr "Nastavenie hadania" msgid "Extended search" msgstr "Rozren vyhadvanie" msgid "no" msgstr "nie" msgid "Time" msgstr "as" msgid "Users" msgstr "Uvatelia" msgid "Delete user" msgstr "Vymaza uvatea" msgid "Page error" msgstr "Chyba strnky" msgid "No timer conflicts" msgstr "iadne konflikty" msgid "local" msgstr "" msgid "Timer has a conflict." msgstr "Pln m konflikt." msgid "Live Interactive VDR Environment" msgstr "iv interaktvne VDR prostredie" msgid "No EPG information available" msgstr "EPG informcie s nedostupn" #~ msgid "VLC media URL" #~ msgstr "VLC mdia URL" #~ msgid "(%d')" #~ msgstr "(%d')" #~ msgid "Show duration in 'Recordings'" #~ msgstr "Zobrazi dku v 'Nahrvkach'" vdr-plugin-live-3.1.3/po/sv_SE.po000066400000000000000000000442211414414333500165440ustar00rootroot00000000000000# VDR LIVE plugin language source file. # Copyright (C) 2007 LIVE Development team. See http://live.vdr-developer.org # This file is distributed under the same license as the VDR-LIVE package. # Dmitrii Kiselev , 2013 # msgid "" msgstr "" "Project-Id-Version: VDR-LIVE 0.3.0\n" "Report-Msgid-Bugs-To: \n" "PO-Revision-Date: 2013-06-21 20:15+0200\n" "Last-Translator: Fredrik Olofsson \n" "Language-Team: see developers in README\n" "Language: ru\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" msgid "Last channel to display" msgstr "Sista kanal att visa" msgid "No limit" msgstr "Ingen gräns" msgid "Use authentication" msgstr "Använd inloggning" msgid "No" msgstr "Nej" msgid "Yes" msgstr "Ja" msgid "Admin login" msgstr "" msgid "Admin password" msgstr "Admin lösen" #, c-format msgid "%A, %x" msgstr "" msgid "Searchtimer" msgstr "Söktimer" msgid "Error in timer settings" msgstr "Fel i timer-inställning" msgid "Timer already defined" msgstr "Timer redan satt" msgid "Timer not defined" msgstr "Timer ej definierad" msgid "Timers are being edited - try again later" msgstr "Timers redigieras - försök igen senare" msgid "On archive DVD No." msgstr "På arkiv-dvd nr." msgid "On archive HDD No." msgstr "På arkiv-hd nr." msgid "Couldn't find channel or no channels available." msgstr "Kunde inte hitta kanal eller inga kanaler tillgängliga" msgid "Couldn't switch to channel." msgstr "Kunde inte byta till kanal." msgid "Couldn't find recording or no recordings available." msgstr "Kunde inte hitta inspelning eller inga inspelningar tillgängliga" msgid "Cannot control playback!" msgstr "Kan inte styra uppspelning!" msgid "Not playing a recording." msgstr "Spelar inte en inspelning" msgid "Not playing the same recording as from request." msgstr "Spelar inte samma inspelnging som begärdes." msgid "Attempt to delete recording currently in playback." msgstr "Försök att ta bort inspelning som för närvarande spelas upp." msgid "Epg error" msgstr "EPG-fel." msgid "Wrong channel id" msgstr "Fel kanal-id" msgid "Channel has no schedule" msgstr "Kanalen har inga scheman" msgid "Wrong event id" msgstr "Fel händelse-id" msgid "Required minimum version of epgsearch: " msgstr "Minsta version av epgsearch som krävs:" msgid "All" msgstr "Alla" msgid "FTA" msgstr "Okrypterade" msgid "%I:%M %p" msgstr "%H:%M" msgid "EPGSearch version outdated! Please update." msgstr "För gammal version av EPGSearch. Vänligen uppdatera." msgid "Couldn't aquire primary device" msgstr "Kunde inte få tillgång till primära enheten." msgid "Couldn't grab image from primary device" msgstr "Kunde inte få bild från primära enheten" msgid "Timer conflict check detected " msgstr "Timer-konflikt koll upptäckt" msgid "conflict" msgstr "konflikt" msgid "conflicts" msgstr "konflikter" msgid "Electronic program guide information" msgstr "EPG-information" msgid "Couldn't find recording or no recordings available" msgstr "Kunde inte hitta inspelning eller inga inspelningar tillgängliga" msgid "Error aquiring schedules lock" msgstr "Fil vid låsning av scheman" msgid "Error aquiring schedules" msgstr "Fel vid hämtning av scheman" msgid "%b %d %y" msgstr "" msgid "Searchtimers" msgstr "Sök-timers" msgid "Sorry, no permission. Please contact your administrator!" msgstr "Inte tillåtet för dig. Kontakta din administratör!" msgid "Expression" msgstr "Uttryck" msgid "Channel" msgstr "Kanal" msgid "Starts between" msgstr "Börjar mellan" msgid "Toggle search timer actions (in)active" msgstr "" msgid "Browse search timer results" msgstr "Visa söktimer-resultat" msgid "Edit search timer" msgstr "Redigera sök-timer" msgid "Delete this search timer?" msgstr "Ta bort denna sök-timer?" msgid "Delete search timer" msgstr "Ta bort sök-timer" msgid "New search timer" msgstr "Ny sök-timer" msgid "Trigger search timer update" msgstr "Tvinga sök-timer uppdatering" msgid "Wrong username or password" msgstr "Fel användarnamn eller lösenord" msgid "Login" msgstr "" msgid "VDR Live Login" msgstr "VDR Live Login" msgid "User" msgstr "Användare" msgid "Password" msgstr "Lösenord" msgid "Remote Control" msgstr "Fjärrkontroll" msgid "Couldn't aquire access to channels, please try again later." msgstr "Kunde inte få tillgång till kanaler, försök igen senare" msgid "Couldn't find channel or no channels available. Maybe you mistyped your request?" msgstr "Kan inte hitta kanal eller inga kanaler är tillgängliga. Kontrollera stavningen?" msgid "Selection" msgstr "Val" msgid "Snapshot interval" msgstr "" msgid "Stop" msgstr "Stop" #, c-format msgid "%a, %x" msgstr "" msgid "What's running on" msgstr "Vad körs på" msgid "at" msgstr "kl" msgid "What's on next?" msgstr "Vad är nästa program?" msgid "Favorites" msgstr "Favoriter" msgid "Click to view details." msgstr "Klicka för detaljer" msgid "View the schedule of this channel" msgstr "Visa schemat för denna kanal" msgid " - " msgstr "" msgid "more" msgstr "mera" msgid "Now" msgstr "Nu" msgid "Next" msgstr "Nästa" msgid "What's on" msgstr "Vad är på?" msgid "Details view" msgstr "Detaljvy" msgid "List view" msgstr "Listvy" msgid "Find more at the Internet Movie Database." msgstr "Hitta mera på IMDB" msgid "Stream this channel into browser." msgstr "" msgid "Stream this recording into media player." msgstr "" msgid "Record this" msgstr "Spela in detta." msgid "Edit timer" msgstr "Redigera timer" msgid "loading data" msgstr "Laddar data" msgid "an error occured!" msgstr "Ett fel inträffade!" msgid "Request succeeded!" msgstr "Begäran lyckades!" msgid "Request failed!" msgstr "Begäran misslyckades!" msgid "Sunday" msgstr "Söndag" msgid "Monday" msgstr "Måndag" msgid "Tuesday" msgstr "Tisdag" msgid "Wednesday" msgstr "Onsdag" msgid "Thursday" msgstr "Torsdag" msgid "Friday" msgstr "Fredag" msgid "Saturday" msgstr "Lördag" #. TRANSLATORS: only adjust the ordering and separators, don't translate the m's, d's and y's msgid "mm/dd/yyyy" msgstr "dd/mm/yyyy" msgid "January" msgstr "Januari" msgid "February" msgstr "Februari" msgid "March" msgstr "Mars" msgid "April" msgstr "April" msgid "May" msgstr "Maj" msgid "June" msgstr "Juni" msgid "July" msgstr "Juli" msgid "August" msgstr "Augusti" msgid "September" msgstr "September" msgid "October" msgstr "Oktober" msgid "November" msgstr "November" msgid "December" msgstr "December" msgid "retrieving status ..." msgstr "Hämtar status ..." msgid "Toggle updates on/off." msgstr "Uppdateringar på/av." msgid "stop playback" msgstr "stoppa uppspelning" msgid "resume playback" msgstr "återuppta uppspelning" msgid "pause playback" msgstr "pausa uppspelning" msgid "fast rewind" msgstr "spola bakåt" msgid "fast forward" msgstr "spola framåt" msgid "previous channel" msgstr "förra kanalen" msgid "next channel" msgstr "nästa kanal" msgid "No server response!" msgstr "Inget svar från servern!" msgid "Failed to update infobox!" msgstr "Misslyckades att uppdatera info-rutan!" msgid "Switch to this channel." msgstr "Byt till denna kanal." msgid "Search for repeats." msgstr "Sök efter upprepningar." msgid "Authors" msgstr "Författare" msgid "Project Idea" msgstr "Projekt-idé" msgid "Webserver" msgstr "Webserver" msgid "Current Maintainer" msgstr "" msgid "Project leader" msgstr "Projektledare" msgid "Content" msgstr "Innehåll" msgid "Graphics" msgstr "Grafik" msgid "Information" msgstr "" msgid "LIVE version" msgstr "Version av LIVE" msgid "VDR version" msgstr "Version av VDR" msgid "Features" msgstr "" msgid "active" msgstr "aktiv" msgid "required" msgstr "krävs" msgid "Homepage" msgstr "Hemsida" msgid "Bugs and suggestions" msgstr "Felrapporter och förslag" msgid "If you encounter any bugs or would like to suggest new features, please use our bugtracker" msgstr "Om du hittar några fel eller vill föreslå nya funktioner, vänligen använd vår bugg-tracker" msgid "What's on?" msgstr "Vad spelas?" msgid "MultiSchedule" msgstr "MultiSchema" msgid "Search" msgstr "Sök" msgid "Recordings" msgstr "Inspelningar" msgid "Web-Streaming" msgstr "" msgid "Logout" msgstr "Logga ut" msgid "Your attention is required" msgstr "Din uppmärksamhet krävs" msgid "React" msgstr "Reagera" msgid "Dismiss" msgstr "Avfärda" msgid "Couldn't find user. Maybe you mistyped your request?" msgstr "Kan inte hitta användare. Kontrollera stavningen?" msgid "This user name is already in use!" msgstr "Användarnamnet används redan!" msgid "Edit user" msgstr "Redigera användare" msgid "New user" msgstr "Ny användare" msgid "Name" msgstr "Namn" msgid "User rights" msgstr "Användarrättigheter" msgid "Edit setup" msgstr "Redigera inställningar" msgid "Add or edit timers" msgstr "Lägg till eller ta bort timers" msgid "Delete timers" msgstr "Radera timers" msgid "Delete recordings" msgstr "Radera inspelningar" msgid "Use remote menu" msgstr "Använd fjärrmeny" msgid "Start replay" msgstr "Påbörja repetetionsuppspelning" msgid "Switch channel" msgstr "Byt kanal" msgid "Add or edit search timers" msgstr "Lägg till eller redigera sök-timers" msgid "Delete search timers" msgstr "Radera sök-timers" msgid "Edit recordings" msgstr "Redigera inspelningar" msgid "Save" msgstr "Spara" msgid "Cancel" msgstr "Avbryt" msgid "Search results" msgstr "Sökresultat" msgid "No search results" msgstr "Inga sökresultat" msgid "%A, %b %d %Y" msgstr "%A, %d.%m.%Y" msgid "Couldn't find timer. Maybe you mistyped your request?" msgstr "Kan inte hitta timer. Kontrollera stavningen?" msgid "Please set a title for the timer!" msgstr "Sätt ett namn på timern!" msgid "New timer" msgstr "Ny timer" msgid "Title" msgstr "Titel" msgid "Server" msgstr "" msgid "Directory" msgstr "Bibliotek" msgid "Weekday" msgstr "Veckodag" msgid "Use VPS" msgstr "VPS" msgid "ERROR:" msgstr "FEL:" msgid "Deleted recording:" msgstr "Raderade inspelningar:" msgid "List of recordings" msgstr "Lista över inspelningar" msgid "No recordings found" msgstr "Inga inspelningar funna" msgid "Delete selected" msgstr "Ta bort valda" #, no-c-format msgid "%a," msgstr "" #. TRANSLATORS: recording duration format HH:MM #, c-format msgid "(%d:%02d)" msgstr "" msgid "Sort by name" msgstr "Sortera efter namn" msgid "Sort by date" msgstr "Sortera efter datum" msgid "Filter" msgstr "" msgid "Look in recordings titles and subtitles for the given string and display only the matching ones. You may also use perl compatible regular expressions (PCRE)." msgstr "Titta efter given sträng i inspelningarnas titlar och undertitlar och visa endast de som matchar. Du kan också använda PERL-kompatibla reguljära uttryck. (PCRE)" msgid "Expand all folders" msgstr "Utöka alla bibliotek" msgid "Collapse all folders" msgstr "Kollapsa alla bibliotek" msgid "Delete this recording from hard disc!" msgstr "Ta bort den inspelning från hårddisken!" msgid "Edit recording" msgstr "Redigera inspelningen" msgid "play this recording." msgstr "Spela upp denna inspelning" msgid "No schedules available for this channel" msgstr "Inga scheman för denna kanal!" msgid "Couldn't find searchtimer. Maybe you mistyped your request?" msgstr "Kunde inte hitta sök-timer. Kontrollera stavningen?" msgid "Search text too short - use anyway?" msgstr "Sök-timerns text är för kort. Använd ändå?" msgid "Search term" msgstr "Sök fras" msgid "Search mode" msgstr "Sökläge" msgid "phrase" msgstr "fras" msgid "all words" msgstr "Alla ord" msgid "at least one word" msgstr "Minst ett ord" msgid "match exactly" msgstr "Matcha exakt" msgid "regular expression" msgstr "Reguljärt uttryck" msgid "fuzzy" msgstr "Lurvigt" msgid "Tolerance" msgstr "Tolerans" msgid "Match case" msgstr "Matcha versaler / gemener" msgid "Search in" msgstr "Sök i" msgid "Episode" msgstr "Avsnitt" msgid "Description" msgstr "Beskrivning" msgid "Use extended EPG info" msgstr "Använd utökad EPG-information" msgid "Ignore missing EPG info" msgstr "Ignorera saknad EPG-info" msgid "When active this can cause very many timers. So please always first test this search before using it as search timer!" msgstr "När aktiverad kan detta skapa väldigt många timers. Så pröva därför endast detta först innan du använder det som sök-timer" msgid "Use channel" msgstr "Använd kanal" msgid "interval" msgstr "Intervall" msgid "channel group" msgstr "Kanalgrupp" msgid "only FTA" msgstr "endast okodade" msgid "from channel" msgstr "från kanal" msgid "to channel" msgstr "till kanal" msgid "Use time" msgstr "Använd tid" msgid "Start after" msgstr "Börja efter" msgid "The time the show may start at the earliest" msgstr "Tidigaste begynnelsetid för program" msgid "Start before" msgstr "Börja innan" msgid "The time the show may start at the latest" msgstr "När programmet senast måste börja (vilken tid)" msgid "Use duration" msgstr "Använd längd" msgid "Min. duration" msgstr "Minsta längd" msgid "Max. duration" msgstr "Största längd" msgid "Use day of week" msgstr "Använd veckodag" msgid "Use blacklists" msgstr "Använd svartlista" msgid "all" msgstr "alla" msgid "Use in favorites menu" msgstr "Använd i favoriter-menyn" msgid "Use as search timer" msgstr "Använd som sök-timer" msgid "user defined" msgstr "användardefinierad" msgid "from date" msgstr "Från datum" msgid "to date" msgstr "till datum" msgid "Record" msgstr "Spela in" msgid "Announce only" msgstr "Publicera endast" msgid "Switch only" msgstr "Byt endast" msgid "Announce and Switch" msgstr "" msgid "Announce via email" msgstr "" msgid "Inactive Record" msgstr "" msgid "Series recording" msgstr "Serieinspelning" msgid "Delete recordings after ... days" msgstr "Ta bort inspelningar efter ... dagar" msgid "Keep ... recordings" msgstr "Spara ... inspelningar" msgid "Pause when ... recordings exist" msgstr "Pausa när ... inspelning existerar" msgid "Avoid repeats" msgstr "Undvik upprepningar" msgid "Allowed repeats" msgstr "Tillåtna upprepningar" msgid "Only repeats within ... days" msgstr "Endast upprepningar inom ... dagar" msgid "Compare title" msgstr "Jämför titel" msgid "Compare subtitle" msgstr "Jämför undertitel" msgid "if present" msgstr "om tillgänglig" msgid "Compare summary" msgstr "Jämför sammanfattning" msgid "Compare" msgstr "Jämför" msgid "Auto-delete search timer" msgstr "Ta automatiskt bort sök-timer" msgid "after ... recordings" msgstr "efter ... inspelningar" msgid "after ... days after first rec." msgstr "efter ... dagar efter första inspelning." msgid "Switch ... minutes before start" msgstr "Byt ... minuter innan start" msgid "Test" msgstr "Test" msgid "No timer defined" msgstr "Ingen timer definierad" msgid "Timer is recording." msgstr "Timern spelar in." msgid "Timer is active." msgstr "Timern är aktiv" msgid "Toggle timer active/inactive" msgstr "Välj aktiv/inaktiv för timer" msgid "Delete timer" msgstr "Ta bort timer" msgid "Timer conflicts" msgstr "Timerkonflikter" msgid "Please set login and password!" msgstr "Vänligen sätt login och lösenord!" msgid "Setup saved." msgstr "Inställningar sparade" msgid "Setup" msgstr "Inställningar" msgid "User management" msgstr "Användarhantering" msgid "Local net (no login required)" msgstr "Lokalt nät (inget login krävs)" msgid "Show live logo image" msgstr "Visa live logo" msgid "Use ajax technology" msgstr "Använd ajax-teknik" msgid "Show dynamic VDR information box" msgstr "Visa dynamisk VDR-informationsruta" msgid "Allow video streaming" msgstr "Tillåt videoströmmning" msgid "Web-Streaming h264" msgstr "" msgid "Web-Streaming HEVC" msgstr "" msgid "Web-Streaming MPEG2" msgstr "" msgid "Web-Streaming others" msgstr "" msgid "Streamdev server port" msgstr "Streamdev serverns port" msgid "Streamdev stream type" msgstr "Streamdev strömtyp" msgid "Mark new recordings" msgstr "" msgid "Add links to IMDb" msgstr "Lägg till länkar till IMDb" msgid "Additional fixed times in 'What's on?'" msgstr "Ytterligare fixerade tider i 'Vad spelas?'" msgid "Format is HH:MM. Separate multiple times with a semicolon" msgstr "Formatet är HH:MM. Avdela flera tider med semi-kolon" msgid "Channel groups for MultiSchedule" msgstr "Kanal-grupper för MultiSchedule" msgid "Separate channels with a comma ',', separate groups with a semi-colon ';'" msgstr "Avdela kanaler med ett komma ',', avdela grupper med semi-kolon ';'" msgid "Duration of MultiSchedule in hours" msgstr "Längd på MultiSchedule i timmar" msgid "Show channels without EPG" msgstr "Visa kanaler utan EPG" msgid "Start page" msgstr "Startsida" msgid "Theme" msgstr "Tema" msgid "playing recording" msgstr "Spelar upp inspelning" msgid "no epg info for current event!" msgstr "Ingen epg-info för nuvarande händelse!" msgid "no epg info for current channel!" msgstr "Ingen epg-info för nuvarande kanal!" msgid "no current channel!" msgstr "Ingen nuvarande kanal!" msgid "error retrieving status info!" msgstr "fel vid hämtning av statusinfo!" msgid "%I:%M:%S %p" msgstr "%H:%M:%S" msgid "Couldn't find recording. Maybe you mistyped your request?" msgstr "Kunde inte hitta inspelningen. Kolla stavningen?" msgid "Please set a name for the recording!" msgstr "Bestäm namn för inspelningen" msgid "Cannot copy, rename or move the recording." msgstr "Kan inte kopiera, byta namn på eller flytta inspelningen " msgid "Delete resume information" msgstr "Radera information för att återuppta" msgid "Delete marks information" msgstr "Ta bort märkningsinformation" msgid "Copy only" msgstr "Kopiera endast" msgid "Short description" msgstr "Kort beskrivning" msgid "Auxiliary info" msgstr "Ytterligare information" msgid "Search settings" msgstr "Sök inställningar" msgid "Extended search" msgstr "Utökad sökning" msgid "no" msgstr "nej" msgid "Time" msgstr "Tid" msgid "Users" msgstr "Användare" msgid "Delete user" msgstr "Radera användare" msgid "Page error" msgstr "Fel på sidan" msgid "No timer conflicts" msgstr "Inga timerkonflikter" msgid "local" msgstr "" msgid "Timer has a conflict." msgstr "Upptäckt konflikt för timern" msgid "Live Interactive VDR Environment" msgstr "" msgid "No EPG information available" msgstr "EPG-information saknas" #~ msgid "VLC media URL" #~ msgstr "VLC media-URL" #~ msgid "ERROR" #~ msgstr "FEL" vdr-plugin-live-3.1.3/preload.cpp000066400000000000000000000061311414414333500166770ustar00rootroot00000000000000 #include "preload.h" #include "filecache.h" #include namespace vdrlive { // to get an updated list of these files do: // (cd live; find * -type f ! -wholename '*CVS*' ! -wholename '*themes*' ! -name '*~' ! -name '.*') | awk '{print "\"" $1 "\","}' // and clean out unneeded entries. void PreLoadFileCache(std::string const& configDir) { static char const * const preloadFiles[] = { "css/siteprefs.css", "img/rounded-box-blue-bl.png", "img/rounded-box-blue-br.png", "img/rounded-box-blue-ml.png", "img/rounded-box-blue-mr.png", "img/rounded-box-blue-tr.png", "img/rounded-box-green-bl.png", "img/rounded-box-blue-tl.png", "img/rounded-box-green-br.png", "img/rounded-box-green-ml.png", "img/rounded-box-green-mr.png", "img/del.png", "img/info-win-t-r.png", "img/info-win-m-l.png", "img/info-win-m-r.png", "img/info-win-b-l.png", "img/info-win-b-r.png", "img/close_red.png", "img/info-win-t-l.png", "img/rounded-box-green-tl.png", "img/rounded-box-green-tr.png", "img/rounded-box-orange-bl.png", "img/rounded-box-orange-br.png", "img/rounded-box-orange-ml.png", "img/rounded-box-orange-mr.png", "img/rounded-box-orange-tl.png", "img/rounded-box-orange-tr.png", "img/active.png", "img/arrow.png", "img/bg_box_h.png", "img/bg_box_l.png", "img/bg_box_r.png", "img/bg_header_h.png", "img/bg_header_l.png", "img/bg_header_r.png", "img/bg_line.png", "img/bg_line_top.png", "img/bg_tools.png", "img/button_blue.png", "img/button_green.png", "img/button_new.png", "img/button_red.png", "img/button_yellow.png", "img/close.png", "img/edit.png", "img/ffw.png", "img/file.png", "img/folder_closed.png", "img/folder_open.png", "img/help.png", "img/imdb.png", "img/inactive.png", "img/logo_login.png", "img/logo.png", "img/menu_line_bg.png", "img/minus.png", "img/movie.png", "img/on_dvd.png", "img/one_downarrow.png", "img/one_uparrow.png", "img/pause.png", "img/play.png", "img/plus.png", "img/record.png", "img/record_timer.png", "img/reload.png", "img/rwd.png", "img/search.png", "img/stop.png", "img/stop_update.png", "img/transparent.png", "img/zap.png", "img/remotecontrol.jpg", "img/tv.jpg", "img/arrow_rec.gif", "img/favicon.ico", "img/playlist.pn", "img/sd.png", "img/hd.png", "img/RecordingErrors.png", "img/NoRecordingErrors.png", "img/NotCheckedForRecordingErrors.png", "js/live/vdr_status.js", "js/live/infowin.js", "js/live/header.js", "js/live/liveajax.js", "js/live/hinttips.js", "js/live/pageenhance.js", "js/mootools/mootools.v1.11.js", 0 }; FileCache& fc = LiveFileCache(); size_t i = 0; while (preloadFiles[i]) { FileCache::ptr_type f = fc.get(configDir + "/" + preloadFiles[i]); if (0 == f.get()) { isyslog("live: can't preload %s/%s! Generated pages might be degraded!", configDir.c_str(), preloadFiles[i]); } i++; } isyslog("live: initial file cache has %zu entries and needs %zu bytes of data!", fc.count(), fc.weight()); } } vdr-plugin-live-3.1.3/preload.h000066400000000000000000000002661414414333500163470ustar00rootroot00000000000000#ifndef VDR_LIVE_PRELOAD_H #define VDR_LIVE_PRELOAD_H #include namespace vdrlive { void PreLoadFileCache(std::string const& configDir); }; #endif // VDR_LIVE_PRELOAD_H vdr-plugin-live-3.1.3/recman.cpp000066400000000000000000001140001414414333500165110ustar00rootroot00000000000000 #include "recman.h" #include "tools.h" // STL headers need to be before VDR tools.h (included by ) #include #include #include #include #define INDEXFILESUFFIX "/index.vdr" #define LENGTHFILESUFFIX "/length.vdr" namespace vdrlive { /** * Implementation of class RecordingsManager: */ std::weak_ptr RecordingsManager::m_recMan; std::shared_ptr RecordingsManager::m_recTree; std::shared_ptr RecordingsManager::m_recList; std::shared_ptr RecordingsManager::m_recDirs; #if VDRVERSNUM >= 20301 cStateKey RecordingsManager::m_recordingsStateKey; #else int RecordingsManager::m_recordingsState = 0; #endif // The RecordingsManager holds a VDR lock on the // Recordings. Additionally the singleton instance of // RecordingsManager is held in a weak pointer. If it is not in // use any longer, it will be freed automaticaly, which leads to a // release of the VDR recordings lock. Upon requesting access to // the RecordingsManager via LiveRecordingsManger function, first // the weak ptr is locked (obtaining a std::shared_ptr from an possible // existing instance) and if not successfull a new instance is // created, which again locks the VDR Recordings. // // RecordingsManager provides factory methods to obtain other // recordings data structures. The data structures returned keep if // needed the instance of RecordingsManager alive until destructed // themselfs. This way the use of LIVE::recordings is straight // forward and does hide the locking needs from the user. #if VDRVERSNUM >= 20301 RecordingsManager::RecordingsManager() #else RecordingsManager::RecordingsManager() : m_recordingsLock(&Recordings) #endif { } RecordingsTreePtr RecordingsManager::GetRecordingsTree() const { RecordingsManagerPtr recMan = EnsureValidData(); if (! recMan) { return RecordingsTreePtr(recMan, std::shared_ptr()); } return RecordingsTreePtr(recMan, m_recTree); } RecordingsListPtr RecordingsManager::GetRecordingsList(bool ascending) const { RecordingsManagerPtr recMan = EnsureValidData(); if (! recMan) { return RecordingsListPtr(recMan, std::shared_ptr()); } return RecordingsListPtr(recMan, std::shared_ptr(new RecordingsList(m_recList, ascending))); } RecordingsListPtr RecordingsManager::GetRecordingsList(time_t begin, time_t end, bool ascending) const { RecordingsManagerPtr recMan = EnsureValidData(); if (! recMan) { return RecordingsListPtr(recMan, std::shared_ptr()); } return RecordingsListPtr(recMan, std::shared_ptr(new RecordingsList(m_recList, ascending))); } DirectoryListPtr RecordingsManager::GetDirectoryList() const { RecordingsManagerPtr recMan = EnsureValidData(); if (!recMan) { return DirectoryListPtr(recMan, std::shared_ptr()); } return DirectoryListPtr(recMan, m_recDirs); } std::string RecordingsManager::Md5Hash(cRecording const * recording) const { return "recording_" + MD5Hash(recording->FileName()); } cRecording const * RecordingsManager::GetByMd5Hash(std::string const & hash) const { if (!hash.empty()) { #if VDRVERSNUM >= 20301 LOCK_RECORDINGS_READ; for (cRecording* rec = (cRecording *)Recordings->First(); rec; rec = (cRecording *)Recordings->Next(rec)) { #else for (cRecording* rec = Recordings.First(); rec; rec = Recordings.Next(rec)) { #endif if (hash == Md5Hash(rec)) return rec; } } return 0; } bool RecordingsManager::MoveRecording(cRecording const * recording, std::string const & name, bool copy) const { if (!recording) return false; std::string oldname = recording->FileName(); size_t found = oldname.find_last_of("/"); if (found == std::string::npos) return false; #if APIVERSNUM > 20101 std::string newname = std::string(cVideoDirectory::Name()) + "/" + name + oldname.substr(found); #else std::string newname = std::string(VideoDirectory) + "/" + name + oldname.substr(found); #endif if (!MoveDirectory(oldname.c_str(), newname.c_str(), copy)) { esyslog("live: renaming failed from '%s' to '%s'", oldname.c_str(), newname.c_str()); return false; } #if VDRVERSNUM >= 20301 LOCK_RECORDINGS_WRITE; if (!copy) Recordings->DelByName(oldname.c_str()); Recordings->AddByName(newname.c_str()); #else if (!copy) Recordings.DelByName(oldname.c_str()); Recordings.AddByName(newname.c_str()); #endif cRecordingUserCommand::InvokeCommand(*cString::sprintf("rename \"%s\"", *strescape(oldname.c_str(), "\\\"$'")), newname.c_str()); return true; } void RecordingsManager::DeleteResume(cRecording const * recording) const { if (!recording) return; cResumeFile ResumeFile(recording->FileName(), recording->IsPesRecording()); ResumeFile.Delete(); } void RecordingsManager::DeleteMarks(cRecording const * recording) const { if (!recording) return; cMarks marks; marks.Load(recording->FileName()); if (marks.Count()) { cMark *mark = marks.First(); while (mark) { cMark *nextmark = marks.Next(mark); marks.Del(mark); mark = nextmark; } marks.Save(); } } void RecordingsManager::DeleteRecording(cRecording const * recording) const { if (!recording) return; std::string name(recording->FileName()); const_cast(recording)->Delete(); #if VDRVERSNUM >= 20301 LOCK_RECORDINGS_WRITE; Recordings->DelByName(name.c_str()); #else Recordings.DelByName(name.c_str()); #endif } int RecordingsManager::GetArchiveType(cRecording const * recording) { std::string filename = recording->FileName(); std::string dvdFile = filename + "/dvd.vdr"; if (0 == access(dvdFile.c_str(), R_OK)) { return 1; } std::string hddFile = filename + "/hdd.vdr"; if (0 == access(hddFile.c_str(), R_OK)) { return 2; } return 0; } std::string const RecordingsManager::GetArchiveId(cRecording const * recording, int archiveType) { std::string filename = recording->FileName(); if (archiveType==1) { std::string dvdFile = filename + "/dvd.vdr"; std::ifstream dvd(dvdFile.c_str()); if (dvd) { std::string archiveDisc; std::string videoDisc; dvd >> archiveDisc; if ("0000" == archiveDisc) { dvd >> videoDisc; return videoDisc; } return archiveDisc; } } else if(archiveType==2) { std::string hddFile = filename + "/hdd.vdr"; std::ifstream hdd(hddFile.c_str()); if (hdd) { std::string archiveDisc; hdd >> archiveDisc; return archiveDisc; } } return ""; } std::string const RecordingsManager::GetArchiveDescr(cRecording const * recording) { int archiveType; std::string archived; archiveType = GetArchiveType(recording); if (archiveType==1) { archived += " ["; archived += tr("On archive DVD No."); archived += ": "; archived += GetArchiveId(recording, archiveType); archived += "]"; } else if (archiveType==2) { archived += " ["; archived += tr("On archive HDD No."); archived += ": "; archived += GetArchiveId(recording, archiveType); archived += "]"; } return archived; } #if VDRVERSNUM >= 20301 bool RecordingsManager::StateChanged () { bool result = false; // will return != 0 only, if the Recordings List has been changed since last read if (cRecordings::GetRecordingsRead(m_recordingsStateKey)) { result = true; m_recordingsStateKey.Remove(); } return result; } #endif RecordingsManagerPtr RecordingsManager::EnsureValidData() { // Get singleton instance of RecordingsManager. 'this' is not // an instance of std::shared_ptr of the singleton // RecordingsManager, so we obtain it in the overall // recommended way. RecordingsManagerPtr recMan = LiveRecordingsManager(); if (! recMan) { // theoretically this code is never reached ... esyslog("live: lost RecordingsManager instance while using it!"); return RecordingsManagerPtr(); } // StateChanged must be executed every time, so not part of // the short cut evaluation in the if statement below. #if VDRVERSNUM >= 20301 bool stateChanged = StateChanged(); #else bool stateChanged = Recordings.StateChanged(m_recordingsState); #endif if (stateChanged || (!m_recTree) || (!m_recList) || (!m_recDirs)) { if (stateChanged) { m_recTree.reset(); m_recList.reset(); m_recDirs.reset(); } if (stateChanged || !m_recTree) { m_recTree = std::shared_ptr(new RecordingsTree(recMan)); } if (!m_recTree) { esyslog("live: creation of recordings tree failed!"); return RecordingsManagerPtr(); } if (stateChanged || !m_recList) { m_recList = std::shared_ptr(new RecordingsList(RecordingsTreePtr(recMan, m_recTree))); } if (!m_recList) { esyslog("live: creation of recordings list failed!"); return RecordingsManagerPtr(); } if (stateChanged || !m_recDirs) { m_recDirs = std::shared_ptr(new DirectoryList(recMan)); } if (!m_recDirs) { esyslog("live: creation of directory list failed!"); return RecordingsManagerPtr(); } } return recMan; } ShortTextDescription::ShortTextDescription(const char * ShortText, const char * Description) : m_short_text(ShortText), m_description(Description) { } wint_t ShortTextDescription::getNextNonPunctChar() { wint_t result; do { if( m_short_text && *m_short_text ) result = getNextUtfCodepoint(m_short_text); else result = getNextUtfCodepoint(m_description); } while (result && iswpunct(result)); return towlower(result); } int RecordingsItemPtrCompare::Compare2(int &numEqualChars, const RecordingsItemPtr & first, const RecordingsItemPtr & second) { // compare short text & description numEqualChars = 0; ShortTextDescription chfirst (first ->ShortText() , first ->Description() ); ShortTextDescription chsecond(second->ShortText() , second->Description() ); wint_t flc; wint_t slc; do { flc = chfirst.getNextNonPunctChar(); slc = chsecond.getNextNonPunctChar(); if ( flc < slc ) return -1; if ( flc > slc ) return 1; ++numEqualChars; } while ( flc && slc ); --numEqualChars; return 0; } int RecordingsItemPtrCompare::FindBestMatch(RecordingsItemPtr & BestMatch, const std::list::iterator & First, const std::list::iterator & Last, const RecordingsItemPtr & EPG_Entry){ // d: length of movie in minutes, without commercial breaks // Assumption: the maximum length of commercial breaks is cb% * d // Assumption: cb = 34% const long cb = 34; // lengthLowerBound: minimum of d, if EPG_Entry->Duration() has commercial breaks long lengthLowerBound = EPG_Entry->Duration() * 100 / ( 100 + cb) ; // lengthUpperBound: if EPG_Entry->Duration() is d (no commercial breaks), max length of recording with commercial breaks // Add VDR recording margins to this value long lengthUpperBound = EPG_Entry->Duration() * (100 + cb) / 100; lengthUpperBound += ::Setup.MarginStart + ::Setup.MarginStop; if(EPG_Entry->Duration() >= 90 && lengthLowerBound < 70) lengthLowerBound = 70; int numRecordings = 0; int min_deviation = 100000; std::list::iterator bestMatchIter; for ( std::list::iterator iter = First; iter != Last; ++iter) if ( (*iter)->Duration() >= lengthLowerBound && (*iter)->Duration() <= lengthUpperBound ) { int deviation = abs( (*iter)->Duration() - EPG_Entry->Duration() ); if (deviation < min_deviation || numRecordings == 0) { min_deviation = deviation; bestMatchIter = iter; } ++numRecordings; } if (numRecordings > 0) BestMatch = *bestMatchIter; return numRecordings; } /** * Implemetation of class RecordingsItemPtrCompare */ bool RecordingsItemPtrCompare::ByAscendingDate(const RecordingsItemPtr & first, const RecordingsItemPtr & second) { return (first->StartTime() < second->StartTime()); } bool RecordingsItemPtrCompare::ByDescendingDate(const RecordingsItemPtr & first, const RecordingsItemPtr & second) { return (first->StartTime() >= second->StartTime()); } int RecordingsItemPtrCompare::Compare(int &numEqualChars, const RecordingsItemPtr &first, const RecordingsItemPtr &second) { numEqualChars = 0; int i = first->NameForSearch().compare(second->NameForSearch() ); if(i != 0) return i; // name is identical, compare short text i = RecordingsItemPtrCompare::compareLC(numEqualChars, first->ShortText(), second->ShortText() ); if(i != 0) return i; i = RecordingsItemPtrCompare::compareLC(numEqualChars, first->Description(), second->Description() ); return i; } bool RecordingsItemPtrCompare::ByAscendingName(const RecordingsItemPtr & first, const RecordingsItemPtr & second) // return first < second { return first->NameForSearch().compare(second->NameForSearch() ) < 0; } bool RecordingsItemPtrCompare::ByAscendingNameShortText(const RecordingsItemPtr & first, const RecordingsItemPtr & second) // return first < second { int i = first->NameForSearch().compare(second->NameForSearch() ); if(i != 0) return i < 0; return RecordingsItemPtrCompare::compareLC(i, first->ShortText(), second->ShortText() ) < 0; } bool RecordingsItemPtrCompare::ByAscendingNameDesc(const RecordingsItemPtr & first, const RecordingsItemPtr & second) // return first < second { int i = first->NameForSearch().compare(second->NameForSearch() ); if(i != 0) return i < 0; return RecordingsItemPtrCompare::Compare2(i, first, second) < 0; } bool RecordingsItemPtrCompare::ByAscendingNameDescSort(const RecordingsItemPtr & first, const RecordingsItemPtr & second) // return first < second // used for sort { int i = first->NameForSort().compare(second->NameForSort() ); if(i != 0) return i < 0; return RecordingsItemPtrCompare::Compare2(i, first, second) < 0; } bool RecordingsItemPtrCompare::ByDescendingNameDescSort(const RecordingsItemPtr & first, const RecordingsItemPtr & second) // return first > second // used for sort { return RecordingsItemPtrCompare::ByAscendingNameDescSort(second, first); } bool RecordingsItemPtrCompare::ByDescendingRecordingErrors(const RecordingsItemPtr & first, const RecordingsItemPtr & second){ return first->RecordingErrors() >= second->RecordingErrors(); } std::string RecordingsItemPtrCompare::getNameForSort(const std::string &Name){ // remove punctuation characters at the beginning of the string unsigned int start; for(start = 0; start < Name.length() && ispunct( Name[ start ] ); start++ ); return g_collate_char.transform(Name.data()+start, Name.data()+Name.length()); } int RecordingsItemPtrCompare::compareLC(int &numEqualChars, const char *first, const char *second) { bool fe = !first || !first[0]; // is first string empty string? bool se = !second || !second[0]; // is second string empty string? if (fe && se) return 0; if (se) return 1; if (fe) return -1; // compare strings case-insensitive for(; *first && *second; ) { // if (*first == *second) { first++; second++; numEqualChars++; continue; } wint_t flc = towlower(getNextUtfCodepoint(first)); wint_t slc = towlower(getNextUtfCodepoint(second)); if ( flc < slc ) return -1; if ( flc > slc ) return 1; numEqualChars++; } if (*second ) return -1; if (*first ) return 1; return 0; } /** * Implementation of class RecordingsItem: */ RecordingsItem::RecordingsItem(std::string const & name, RecordingsItemPtr parent) : m_level((parent != NULL) ? parent->Level() + 1 : 0), m_name(name), m_name_for_sort(RecordingsItemPtrCompare::getNameForSort(name)), m_name_for_search(RecordingsItem::GetNameForSearch(name)), m_entries(), m_parent(parent) { } RecordingsItem::~RecordingsItem() { } std::string RecordingsItem::GetNameForSearch(std::string const & name) { std::string result; result.reserve(name.length()); const char *name_c = name.c_str(); while(*name_c) { wint_t codepoint = getNextUtfCodepoint(name_c); if(!iswpunct(codepoint) ) AppendUtfCodepoint(result, towlower(codepoint)); } return result; } /** * Implementation of class RecordingsItemDir: */ RecordingsItemDir::RecordingsItemDir(const std::string& name, int level, RecordingsItemPtr parent) : RecordingsItem(name, parent), m_level(level) { // dsyslog("live: REC: C: dir %s -> %s", name.c_str(), parent ? parent->Name().c_str() : "ROOT"); } RecordingsItemDir::~RecordingsItemDir() { // dsyslog("live: REC: D: dir %s", Name().c_str()); } /** * Implementation of class RecordingsItemRec: */ RecordingsItemRec::RecordingsItemRec(const std::string& id, const std::string& name, const cRecording* recording, RecordingsItemPtr parent) : RecordingsItem(name, parent), m_recording(recording), m_id(id), m_isArchived(RecordingsManager::GetArchiveType(m_recording) ), m_duration(m_recording->FileName() ? m_recording->LengthInSeconds() / 60 : 0) { // dsyslog("live: REC: C: rec %s -> %s", name.c_str(), parent->Name().c_str()); } RecordingsItemRec::~RecordingsItemRec() { // dsyslog("live: REC: D: rec %s", Name().c_str()); } void RecordingsItemRec::AppendHint(std::string &target) const { if (RecInfo()->ShortText() ) { AppendHtmlEscapedAndCorrectNonUTF8(target, RecInfo()->ShortText() ); target.append("<br />"); } else if (RecInfo()->Description() ) { AppendHtmlEscapedAndCorrectNonUTF8(target, RecInfo()->Description() ); target.append("<br />"); } AppendHtmlEscaped(target, tr("Click to view details.") ); } const char *RecordingsItemRec::RecordingErrorsIcon() const { if (RecordingErrors() == 0) return "NoRecordingErrors.png"; if (RecordingErrors() > 0) return "RecordingErrors.png"; return "NotCheckedForRecordingErrors.png"; } void RecordingsItemRec::AppendRecordingErrorsStr(std::string &target) const { if (RecordingErrors() == 0) AppendHtmlEscaped(target, tr("No recording errors")); if (RecordingErrors() > 0) { AppendHtmlEscaped(target, tr("Number of recording errors:")); target.append(" "); target.append(std::to_string(RecordingErrors() )); } if (RecordingErrors() < 0) AppendHtmlEscaped(target, tr("Recording errors unknown")); } const int RecordingsItemRec::SD_HD() { if ( m_video_SD_HD >= 0 ) return m_video_SD_HD; const cComponents *components = RecInfo()->Components(); if(components) for( int ix = 0; ix < components->NumComponents(); ix++) { tComponent * component = components->Component(ix); if (component->stream == 1 || component->stream == 5) { switch (component->type) { case 1: case 5: m_video_SD_HD = 0; break; case 2: case 3: case 6: case 7: m_video_SD_HD = 0; break; case 4: case 8: m_video_SD_HD = 0; break; case 9: case 13: m_video_SD_HD = 1; break; case 10: case 11: case 14: case 15: m_video_SD_HD = 1; break; case 12: case 16: m_video_SD_HD = 1; break; } } } if(m_video_SD_HD == -1) { m_video_SD_HD = 0; if(RecInfo()->ChannelName() ) { size_t l = strlen(RecInfo()->ChannelName() ); if( l > 3 && RecInfo()->ChannelName()[l-2] == 'H' && RecInfo()->ChannelName()[l-1] == 'D') m_video_SD_HD = 1; } } return m_video_SD_HD; } void RecordingsItemRec::AppendIMDb(std::string &target) const { if (LiveSetup().GetShowIMDb()) { target.append("\n"); } } void RecordingsItemRec::AppendRecordingAction(std::string &target, const char *A, const char *Img, const char *Title, const std::string argList){ target.append("\n"); } void RecordingsItemRec::AppendasHtml(std::string &target, bool displayFolder, const std::string argList){ // list item, classes, space depending on level target.append("
  • "); if(!displayFolder) { // add some space target.append("\n"); } if (IsArchived() ) { target.append("\"on_dvd\""); } else { #if TNTVERSION >= 30000 target.append("" ); } // recording_spec: Day, time & duration target.append("
    \n
    "); AppendDateTime(target, tr("%a,"), StartTime()); // day of week target.append(" "); AppendDateTime(target, tr("%b %d %y"), StartTime()); // date target.append(" "); AppendDateTime(target, tr("%I:%M %p"), StartTime() ); // time target.append("
    "); if(Duration() >= 0) AppendDuration(target, tr("(%d:%02d)"), Duration() / 60, Duration() % 60); target.append("
    "); // RecordingErrors, Icon #if VDRVERSNUM >= 20505 target.append("
    "); #endif // HD_SD, with channel name target.append("
    ChannelName() ); target.append("\" />
    \n"); // Recording name target.append("
    \n
    "); if (!IsArchived()) { AppendRecordingAction(target, "vdr_request/play_recording?param=", "play.png", "play this recording", argList); AppendRecordingAction(target, "playlist.m3u?recid=", "playlist.png", "Stream this recording into media player.", argList); AppendIMDb(target); AppendRecordingAction(target, "edit_recording.html?recid=", "edit.png", "Edit recording", argList); AppendRecordingAction(target, "recordings.html?todel=", "del.png", "Delete this recording from hard disc!", argList); } else { target.append(""); AppendIMDb(target); } target.append("
    "); if (IsArchived()) { target.append("
    "); AppendHtmlEscaped(target, ArchiveDescr().c_str() ); target.append("
    "); } target.append("
  • "); } /** * Implemetation of class RecordingsItemDummy */ RecordingsItemDummy::RecordingsItemDummy(const std::string &Name, const std::string &ShortText, const std::string &Description, long Duration): RecordingsItem(Name, RecordingsItemPtr() ), m_short_text(ShortText.c_str() ), m_description(Description.c_str() ), m_duration( Duration / 60 ) { } /** * Implementation of class RecordingsTree: */ RecordingsTree::RecordingsTree(RecordingsManagerPtr recMan) : m_maxLevel(0), m_root(new RecordingsItemDir("", 0, RecordingsItemPtr())) { // esyslog("live: DH: ****** RecordingsTree::RecordingsTree() ********"); #if VDRVERSNUM >= 20301 LOCK_RECORDINGS_READ; for (cRecording* recording = (cRecording *)Recordings->First(); recording; recording = (cRecording *)Recordings->Next(recording)) { #else for (cRecording* recording = Recordings.First(); recording; recording = Recordings.Next(recording)) { #endif if (m_maxLevel < recording->HierarchyLevels()) { m_maxLevel = recording->HierarchyLevels(); } RecordingsItemPtr dir = m_root; std::string name(recording->Name()); // esyslog("live: DH: recName = '%s'", recording->Name()); int level = 0; size_t index = 0; size_t pos = 0; do { pos = name.find('~', index); if (pos != std::string::npos) { std::string dirName(name.substr(index, pos - index)); index = pos + 1; RecordingsMap::iterator i = findDir(dir, dirName); if (i == dir->m_entries.end()) { RecordingsItemPtr recPtr (new RecordingsItemDir(dirName, level, dir)); dir->m_entries.insert(std::pair (dirName, recPtr)); i = findDir(dir, dirName); #if 0 if (i != dir->m_entries.end()) { // esyslog("live: DH: added dir: '%s'", dirName.c_str()); } else { // esyslog("live: DH: panic: didn't found inserted dir: '%s'", dirName.c_str()); } #endif } dir = i->second; // esyslog("live: DH: current dir: '%s'", dir->Name().c_str()); level++; } else { std::string recName(name.substr(index, name.length() - index)); RecordingsItemPtr recPtr (new RecordingsItemRec(recMan->Md5Hash(recording), recName, recording, dir)); dir->m_entries.insert(std::pair (recName, recPtr)); // esyslog("live: DH: added rec: '%s'", recName.c_str()); } } while (pos != std::string::npos); } // esyslog("live: DH: ------ RecordingsTree::RecordingsTree() --------"); } RecordingsTree::~RecordingsTree() { // esyslog("live: DH: ****** RecordingsTree::~RecordingsTree() ********"); } RecordingsMap::iterator RecordingsTree::begin(const std::vector& path) { if (path.empty()) { return m_root->m_entries.begin(); } RecordingsItemPtr recItem = m_root; for (std::vector::const_iterator i = path.begin(); i != path.end(); ++i) { std::pair range = recItem->m_entries.equal_range(*i); for (RecordingsMap::iterator iter = range.first; iter != range.second; ++iter) { if (iter->second->IsDir()) { recItem = iter->second; break; } } } return recItem->m_entries.begin(); } RecordingsMap::iterator RecordingsTree::end(const std::vector&path) { if (path.empty()) { return m_root->m_entries.end(); } RecordingsItemPtr recItem = m_root; for (std::vector::const_iterator i = path.begin(); i != path.end(); ++i) { std::pair range = recItem->m_entries.equal_range(*i); for (RecordingsMap::iterator iter = range.first; iter != range.second; ++iter) { if (iter->second->IsDir()) { recItem = iter->second; break; } } } return recItem->m_entries.end(); } RecordingsMap::iterator RecordingsTree::findDir(RecordingsItemPtr& dir, const std::string& dirName) { std::pair range = dir->m_entries.equal_range(dirName); for (RecordingsMap::iterator i = range.first; i != range.second; ++i) { if (i->second->IsDir()) { return i; } } return dir->m_entries.end(); } /** * Implementation of class RecordingsTreePtr: */ RecordingsTreePtr::RecordingsTreePtr() : std::shared_ptr(), m_recManPtr() { } RecordingsTreePtr::RecordingsTreePtr(RecordingsManagerPtr recManPtr, std::shared_ptr recTree) : std::shared_ptr(recTree), m_recManPtr(recManPtr) { } RecordingsTreePtr::~RecordingsTreePtr() { } /** * Implementation of class RecordingsList: */ RecordingsList::RecordingsList(RecordingsTreePtr recTree) : m_pRecVec(new RecVecType()) { if (!m_pRecVec) { return; } std::stack treeStack; treeStack.push(recTree->Root()); while (!treeStack.empty()) { RecordingsItemPtr current = treeStack.top(); treeStack.pop(); for (RecordingsMap::const_iterator iter = current->begin(); iter != current->end(); ++iter) { RecordingsItemPtr recItem = iter->second; if (recItem->IsDir()) { treeStack.push(recItem); } else { m_pRecVec->push_back(recItem); } } } } RecordingsList::RecordingsList(std::shared_ptr recList, bool ascending) : m_pRecVec(new RecVecType(recList->size())) { if (!m_pRecVec) { return; } if (ascending) { partial_sort_copy(recList->begin(), recList->end(), m_pRecVec->begin(), m_pRecVec->end(), Ascending()); } else { partial_sort_copy(recList->begin(), recList->end(), m_pRecVec->begin(), m_pRecVec->end(), Descending()); } } RecordingsList::RecordingsList(std::shared_ptr recList, time_t begin, time_t end, bool ascending) : m_pRecVec(new RecVecType()) { if (end > begin) { return; } if (!m_pRecVec) { return; } remove_copy_if(recList->begin(), recList->end(), m_pRecVec->end(), NotInRange(begin, end)); if (ascending) { sort(m_pRecVec->begin(), m_pRecVec->end(), Ascending()); } else { sort(m_pRecVec->begin(), m_pRecVec->end(), Descending()); } } RecordingsList::~RecordingsList() { if (m_pRecVec) { delete m_pRecVec, m_pRecVec = 0; } } RecordingsList::NotInRange::NotInRange(time_t begin, time_t end) : m_begin(begin), m_end(end) { } bool RecordingsList::NotInRange::operator()(RecordingsItemPtr const &x) const { return (x->StartTime() < m_begin) || (m_end >= x->StartTime()); } /** * Implementation of class RecordingsList: */ RecordingsListPtr::RecordingsListPtr(RecordingsManagerPtr recManPtr, std::shared_ptr recList) : std::shared_ptr(recList), m_recManPtr(recManPtr) { } RecordingsListPtr::~RecordingsListPtr() { } /** * Implementation of class DirectoryList: */ DirectoryList::DirectoryList(RecordingsManagerPtr recManPtr) : m_pDirVec(new DirVecType()) { if (!m_pDirVec) { return; } m_pDirVec->push_back(""); // always add root directory for (cNestedItem* item = Folders.First(); item; item = Folders.Next(item)) { // add folders.conf entries InjectFoldersConf(item); } #if VDRVERSNUM >= 20301 LOCK_RECORDINGS_READ; for (cRecording* recording = (cRecording *)Recordings->First(); recording; recording = (cRecording*)Recordings->Next(recording)) { #else for (cRecording* recording = Recordings.First(); recording; recording = Recordings.Next(recording)) { #endif std::string name = recording->Name(); size_t found = name.find_last_of("~"); if (found != std::string::npos) { m_pDirVec->push_back(StringReplace(name.substr(0, found), "~", "/")); } } m_pDirVec->sort(); m_pDirVec->unique(); } DirectoryList::~DirectoryList() { if (m_pDirVec) { delete m_pDirVec, m_pDirVec = 0; } } void DirectoryList::InjectFoldersConf(cNestedItem * folder, std::string parent) { if (!folder) { return; } std::string dir = std::string((parent.size() == 0) ? "" : parent + "/") + folder->Text(); m_pDirVec->push_back(StringReplace(dir, "_", " ")); if (!folder->SubItems()) { return; } for(cNestedItem* item = folder->SubItems()->First(); item; item = folder->SubItems()->Next(item)) { InjectFoldersConf(item, dir); } } /** * Implementation of class DirectoryListPtr: */ DirectoryListPtr::DirectoryListPtr(RecordingsManagerPtr recManPtr, std::shared_ptr recDirs) : std::shared_ptr(recDirs), m_recManPtr(recManPtr) { } DirectoryListPtr::~DirectoryListPtr() { } /** * Implementation of function LiveRecordingsManager: */ RecordingsManagerPtr LiveRecordingsManager() { RecordingsManagerPtr r = RecordingsManager::m_recMan.lock(); if (r) { return r; } else { RecordingsManagerPtr n(new RecordingsManager); RecordingsManager::m_recMan = n; return n; } } bool checkNew(RecordingsTreePtr recordingsTree, std::vector p) { bool newR = false; RecordingsMap::iterator iter; for (iter = recordingsTree->begin(p); iter != recordingsTree->end(p); iter++) { RecordingsItemPtr recItem = iter->second; if(!recItem->IsDir()) newR |= recItem->Recording()->GetResume() <= 0; else { std::vector pp(p); pp.push_back(recItem->Name()); newR |= checkNew(recordingsTree, pp); } } return newR; } void addAllRecordings(std::list &RecItems, RecordingsTreePtr &RecordingsTree, std::vector &P){ for (RecordingsMap::iterator iter = RecordingsTree->begin(P); iter != RecordingsTree->end(P); ++iter) { RecordingsItemPtr recItem = iter->second; if (!recItem->IsDir()) { RecItems.push_back(recItem); } else { std::vector pp(P); pp.push_back(recItem->Name()); addAllRecordings(RecItems, RecordingsTree, pp); } } } void addAllDuplicateRecordings(std::list &DuplicateRecItems, RecordingsTreePtr &RecordingsTree){ std::vector path; std::list recItems; std::list::iterator currentRecItem, recIterUpName, recIterLowName, recIterUpShortText, recIterLowShortText; int numberOfRecordingsWithThisName; bool isSeries; addAllRecordings(recItems, RecordingsTree, path); recItems.sort(RecordingsItemPtrCompare::ByAscendingNameShortText); for (currentRecItem = recItems.begin(); currentRecItem != recItems.end(); ){ recIterLowName = currentRecItem; recIterUpName = std::upper_bound (currentRecItem , recItems.end(), *currentRecItem, RecordingsItemPtrCompare::ByAscendingName); currentRecItem++; if (recIterLowName == recIterUpName ) continue; // there is no recording with this name (internal error) if (currentRecItem == recIterUpName ) continue; // there is only one recording with this name for( numberOfRecordingsWithThisName = 1; currentRecItem != recIterUpName; currentRecItem++, numberOfRecordingsWithThisName++); if (numberOfRecordingsWithThisName > 5) isSeries = true; else isSeries = false; if (isSeries) { for (currentRecItem = recIterLowName; currentRecItem != recIterUpName;){ recIterLowShortText = currentRecItem; recIterUpShortText = std::upper_bound (currentRecItem , recIterUpName, *currentRecItem, RecordingsItemPtrCompare::ByAscendingNameShortText); currentRecItem++; if (currentRecItem == recIterUpShortText ) continue; // there is only one recording with this short text for(currentRecItem = recIterLowShortText; currentRecItem != recIterUpShortText; currentRecItem++) DuplicateRecItems.push_back(*currentRecItem); } } else { // not a series for(currentRecItem = recIterLowName; currentRecItem != recIterUpName; currentRecItem++) DuplicateRecItems.push_back(*currentRecItem); } } } } // namespace vdrlive vdr-plugin-live-3.1.3/recman.h000066400000000000000000000424631414414333500161730ustar00rootroot00000000000000#ifndef VDR_LIVE_RECORDINGS_H #define VDR_LIVE_RECORDINGS_H #include "stdext.h" #include "setup.h" #include "tools.h" // STL headers need to be before VDR tools.h (included by ) #include #include #include #include #if TNTVERSION >= 30000 #include // must be loaded before any vdr include because of duplicate macros (LOG_ERROR, LOG_DEBUG, LOG_INFO) #endif #include namespace vdrlive { // Forward declations from epg_events.h class EpgInfo; typedef std::shared_ptr EpgInfoPtr; /** * Some forward declarations */ class RecordingsManager; class RecordingsTree; class RecordingsTreePtr; class RecordingsList; class RecordingsListPtr; class DirectoryList; class DirectoryListPtr; class RecordingsItem; typedef std::shared_ptr RecordingsManagerPtr; typedef std::shared_ptr RecordingsItemPtr; typedef std::weak_ptr RecordingsItemWeakPtr; typedef std::multimap RecordingsMap; /** * Class for managing recordings inside the live plugin. It * provides some convenience methods and provides automatic * locking during requests on the recordings or during the * traversion of the recordings tree or lists, which can only be * obtained through methods of the RecordingsManager. */ class RecordingsManager { friend RecordingsManagerPtr LiveRecordingsManager(); public: /** * Returns a shared pointer to a fully populated * recordings tree. */ RecordingsTreePtr GetRecordingsTree() const; /** * Return a shared pointer to a populated recordings * list. The list is optionally sorted ascending or * descending by date and may be constrained by a date * range. */ RecordingsListPtr GetRecordingsList(bool ascending = true) const; RecordingsListPtr GetRecordingsList(time_t begin, time_t end, bool ascending = true) const; /** * Returns a shared pointer to a fully populated * directory list. */ DirectoryListPtr GetDirectoryList() const; /** * generates a Md5 hash from a cRecording entry. It can be used * to reidentify a recording. */ std::string Md5Hash(cRecording const * recording) const; /** * fetches a cRecording from VDR's Recordings collection. Returns * NULL if recording was not found */ cRecording const* GetByMd5Hash(std::string const & hash) const; /** * Move a recording with the given hash according to * VDRs recording mechanisms. */ bool MoveRecording(cRecording const * recording, std::string const & name, bool copy = false) const; /** * Delete recording resume with the given hash according to * VDRs recording mechanisms. */ void DeleteResume(cRecording const * recording) const; /** * Delete recording marks with the given hash according to * VDRs recording mechanisms. */ void DeleteMarks(cRecording const * recording) const; /** * Delete a recording with the given hash according to * VDRs recording deletion mechanisms. */ void DeleteRecording(cRecording const * recording) const; /** * Determine wether the recording has been archived on * removable media (e.g. DVD-ROM) */ static int GetArchiveType(cRecording const * recording); /** * Provide an identification of the removable media * (e.g. DVD-ROM Number or Name) where the recording has * been archived. */ static std::string const GetArchiveId(cRecording const * recording, int archiveType); static std::string const GetArchiveDescr(cRecording const * recording); private: RecordingsManager(); #if VDRVERSNUM >= 20301 static bool StateChanged(); #endif static RecordingsManagerPtr EnsureValidData(); static std::weak_ptr m_recMan; static std::shared_ptr m_recTree; static std::shared_ptr m_recList; static std::shared_ptr m_recDirs; #if VDRVERSNUM >= 20301 static cStateKey m_recordingsStateKey; #else static int m_recordingsState; #endif cThreadLock m_recordingsLock; }; class ShortTextDescription { public: ShortTextDescription(const char * ShortText, const char * Description); wint_t getNextNonPunctChar(); private: const char * m_short_text; const char * m_description; }; /** * Class containing possible recordings compare functions */ class RecordingsItemPtrCompare { public: static bool ByAscendingDate(const RecordingsItemPtr & first, const RecordingsItemPtr & second); static bool ByDescendingDate(const RecordingsItemPtr & first, const RecordingsItemPtr & second); static bool ByAscendingName(const RecordingsItemPtr & first, const RecordingsItemPtr & second); static bool ByAscendingNameShortText(const RecordingsItemPtr & first, const RecordingsItemPtr & second); static bool ByAscendingNameDesc(const RecordingsItemPtr & first, const RecordingsItemPtr & second); static bool ByAscendingNameDescSort(const RecordingsItemPtr & first, const RecordingsItemPtr & second); static bool ByDescendingNameDescSort(const RecordingsItemPtr & first, const RecordingsItemPtr & second); static bool ByDescendingRecordingErrors(const RecordingsItemPtr & first, const RecordingsItemPtr & second); static std::string getNameForSort(const std::string &Name); static int compareLC(int &numEqualChars, const char *first, const char *second); // as std::compare, but compare lower case static int Compare(int &numEqualChars, const RecordingsItemPtr &first, const RecordingsItemPtr &second); static int Compare2(int &numEqualChars, const RecordingsItemPtr &first, const RecordingsItemPtr &second); static int FindBestMatch(RecordingsItemPtr &BestMatch, const std::list::iterator & First, const std::list::iterator & Last, const RecordingsItemPtr & EPG_Entry); }; /** * Base class for entries in recordings tree and recordings list. * All opeations possible on recordings are performed against * pointers to instances of recordings items. The C++ polymorphy * delegates them to the 'right' class. */ class RecordingsItem { friend class RecordingsTree; protected: RecordingsItem(const std::string& name, RecordingsItemPtr parent); public: virtual ~RecordingsItem(); virtual time_t StartTime() const = 0; virtual bool IsDir() const = 0; virtual long Duration() const = 0; virtual const std::string& Name() const { return m_name; } virtual const std::string& NameForSort() const { return m_name_for_sort; } virtual const std::string& NameForSearch() const { return m_name_for_search; } virtual const char * ShortText() const { return RecInfo()? RecInfo()->ShortText():0; } virtual const char * Description() const { return RecInfo()? RecInfo()->Description():0; } virtual const std::string Id() const = 0; virtual const cRecording* Recording() const { return 0; } virtual const cRecordingInfo* RecInfo() const { return 0; } RecordingsMap::const_iterator begin() const { return m_entries.begin(); } RecordingsMap::const_iterator end() const { return m_entries.end(); } int Level() { return m_level; } // To display the recuring on the UI virtual const int IsArchived() const { return 0 ; } virtual const std::string ArchiveDescr() const { return "" ; } virtual const char *NewR() const { return "" ; } virtual const int RecordingErrors() const { return -1; } virtual const char *RecordingErrorsIcon() const { return ""; } virtual void AppendRecordingErrorsStr(std::string &target) const { }; virtual const int SD_HD() { return 0; } virtual const char *SD_HD_icon() { return ""; } virtual void AppendasHtml(std::string &target, bool displayFolder, const std::string argList) { } private: std::string GetNameForSearch(std::string const & name); int m_level; const std::string m_name; const std::string m_name_for_sort; const std::string m_name_for_search; RecordingsMap m_entries; RecordingsItemWeakPtr m_parent; }; /** * A recordings item that resembles a directory with other * subdirectories and/or real recordings. */ class RecordingsItemDir : public RecordingsItem { public: RecordingsItemDir(const std::string& name, int level, RecordingsItemPtr parent); virtual ~RecordingsItemDir(); virtual time_t StartTime() const { return 0; } virtual long Duration() const { return 0; } virtual bool IsDir() const { return true; } virtual std::string const Id() const { return ""; } private: int m_level; }; /** * A recordings item that represents a real recording. This is * the leaf item in the recordings tree or one of the items in * the recordings list. */ class RecordingsItemRec : public RecordingsItem { public: RecordingsItemRec(const std::string& id, const std::string& name, const cRecording* recording, RecordingsItemPtr parent); virtual ~RecordingsItemRec(); virtual time_t StartTime() const { return m_recording->Start(); } virtual long Duration() const { return m_duration; } virtual bool IsDir() const { return false; } virtual const std::string Id() const { return m_id; } virtual const cRecording* Recording() const { return m_recording; } virtual const cRecordingInfo* RecInfo() const { return m_recording->Info(); } // To display the recuring on the UI virtual const int IsArchived() const { return m_isArchived ; } virtual const std::string ArchiveDescr() const { return RecordingsManager::GetArchiveDescr(m_recording) ; } virtual const char *NewR() const { return LiveSetup().GetMarkNewRec() && (Recording()->GetResume() <= 0) ? "_new" : "" ; } #if VDRVERSNUM >= 20505 virtual const int RecordingErrors() const { return RecInfo()->Errors(); } #else virtual const int RecordingErrors() const { return -1; } #endif virtual const char *RecordingErrorsIcon() const; void AppendRecordingErrorsStr(std::string &target) const; virtual const int SD_HD(); virtual const char *SD_HD_icon() { return SD_HD() == 0 ? "sd.png": "hd.png"; } virtual void AppendasHtml(std::string &target, bool displayFolder, const std::string argList); void AppendHint(std::string &target) const; void AppendIMDb(std::string &target) const; void AppendRecordingAction(std::string &target, const char *A, const char *Img, const char *Title, const std::string argList); private: const cRecording *m_recording; const std::string m_id; const int m_isArchived; const long m_duration; // duration in minutes int m_video_SD_HD = -1; // 0 is SD, 1 is HD }; /** * Class containing recordings to compare or "dummy" recordings, i.e. data from EPG which can be compared with a recording */ class RecordingsItemDummy: public RecordingsItem { public: RecordingsItemDummy(const std::string &Name, const std::string &ShortText, const std::string &Description, long Duration); ~RecordingsItemDummy() { }; const char * ShortText() const { return m_short_text; } const char * Description() const { return m_description; } virtual time_t StartTime() const { return 0; } virtual long Duration() const { return m_duration; } // duration in minutes virtual bool IsDir() const { return false; } virtual std::string const Id() const { return ""; } private: const char * m_short_text; const char * m_description; const long m_duration; }; /** * The recordings tree contains all recordings in a file system * tree like fashion. */ class RecordingsTree { friend class RecordingsManager; private: RecordingsTree(RecordingsManagerPtr recManPtr); public: virtual ~RecordingsTree(); RecordingsItemPtr const & Root() const { return m_root; } RecordingsMap::iterator begin(const std::vector& path); RecordingsMap::iterator end(const std::vector&path); int MaxLevel() const { return m_maxLevel; } private: int m_maxLevel; RecordingsItemPtr m_root; RecordingsMap::iterator findDir(RecordingsItemPtr& dir, const std::string& dirname); }; /** * A smart pointer to a recordings tree. As long as an instance of this * exists the recordings are locked in the vdr. */ class RecordingsTreePtr : public std::shared_ptr { friend class RecordingsManager; private: RecordingsTreePtr(RecordingsManagerPtr recManPtr, std::shared_ptr recTree); public: RecordingsTreePtr(); virtual ~RecordingsTreePtr(); private: RecordingsManagerPtr m_recManPtr; }; /** * The recordings list contains all real recordings in a list * sorted by a given sorting predicate function. The directory * entries are not part of this list. The path towards the root * can be obtained via the 'parent' members of the recordings * items. */ class RecordingsList { friend class RecordingsManager; private: RecordingsList(RecordingsTreePtr recTree); RecordingsList(std::shared_ptr recList, bool ascending); RecordingsList(std::shared_ptr recList, time_t begin, time_t end, bool ascending); public: typedef std::vector RecVecType; virtual ~RecordingsList(); RecVecType::const_iterator begin() const { return m_pRecVec->begin(); } RecVecType::const_iterator end() const { return m_pRecVec->end(); } RecVecType::size_type size() const { return m_pRecVec->size(); } private: class Ascending { public: bool operator()(RecordingsItemPtr const &x, RecordingsItemPtr const &y) const { return x->StartTime() < y->StartTime(); } }; class Descending { public: bool operator()(RecordingsItemPtr const &x, RecordingsItemPtr const &y) const { return y->StartTime() < x->StartTime(); } }; class NotInRange { public: NotInRange(time_t begin, time_t end); bool operator()(RecordingsItemPtr const &x) const; private: time_t m_begin; time_t m_end; }; private: RecVecType *m_pRecVec; }; /** * A smart pointer to a recordings list. As long as an instance of this * exists the recordings are locked in the vdr. */ class RecordingsListPtr : public std::shared_ptr { friend class RecordingsManager; private: RecordingsListPtr(RecordingsManagerPtr recManPtr, std::shared_ptr recList); public: virtual ~RecordingsListPtr(); private: RecordingsManagerPtr m_recManPtr; }; /** * The recording directory list contains all real directory entries. */ class DirectoryList { friend class RecordingsManager; private: DirectoryList(RecordingsManagerPtr recManPtr); void InjectFoldersConf(cNestedItem * folder, std::string parent = ""); public: typedef std::list DirVecType; virtual ~DirectoryList(); DirVecType::const_iterator begin() const { return m_pDirVec->begin(); } DirVecType::const_iterator end() const { return m_pDirVec->end(); } DirVecType::size_type size() const { return m_pDirVec->size(); } private: DirVecType *m_pDirVec; }; /** * A smart pointer to a directory list. As long as an instance of this * exists the recordings are locked in the vdr. */ class DirectoryListPtr : public std::shared_ptr { friend class RecordingsManager; private: DirectoryListPtr(RecordingsManagerPtr recManPtr, std::shared_ptr recDirs); public: virtual ~DirectoryListPtr(); private: RecordingsManagerPtr m_recManPtr; }; /** * return singleton instance of RecordingsManager as a shared Pointer. * This ensures that after last use of the RecordingsManager it is * deleted. After deletion of the original RecordingsManager a repeated * call to this function creates a new RecordingsManager which is again * kept alive as long references to it exist. */ RecordingsManagerPtr LiveRecordingsManager(); bool checkNew(RecordingsTreePtr recordingsTree, std::vector p); /** * Create a (flat) list of all recordings. * sample code to achieve this: * std::vector path; * std::list recItems; * RecordingsTreePtr recordingsTree(LiveRecordingsManager()->GetRecordingsTree()); * addAllRecordings(recItems, recordingsTree, path); */ void addAllRecordings(std::list &RecItems, RecordingsTreePtr &RecordingsTree, std::vector &P); void addAllDuplicateRecordings(std::list &DuplicateRecItems, RecordingsTreePtr &RecordingsTree); } // namespace vdrlive #endif // VDR_LIVE_RECORDINGS_H vdr-plugin-live-3.1.3/setup.cpp000066400000000000000000000374251414414333500164230ustar00rootroot00000000000000 #include "setup.h" #include "tools.h" #include "tntfeatures.h" // STL headers need to be before VDR tools.h (included by ) #include #include #include #include #include #ifdef __FreeBSD__ #include #include #endif #include namespace vdrlive { Setup::Setup(): m_serverPort( 8008 ), m_serverSslPort( 8443 ), m_serverSslCert(), m_serverSslKey(), m_lastChannel( 0 ), m_screenshotInterval( 1000 ), m_useAuth( 1 ), m_adminLogin("admin"), m_channelGroups( "" ), m_scheduleDuration( "8" ), m_theme("marine"), m_themedLinkPrefix("themes/" + m_theme + "/"), m_themedLinkPrefixImg("themes/marine/img/"), m_lastwhatsonlistmode("detail"), m_lastsortingmode("nameasc"), m_tntnetloglevel("WARN"), m_showLogo(1), m_useAjax(1), m_showInfoBox(1), m_useStreamdev(1), m_streamdevPort(3000), m_streamdevType(), m_markNewRec(1), m_streamVopt0("ffmpeg -loglevel warning -f mpegts -analyzeduration 1.2M -probesize 5M -i -map 0:v -map 0:a:0 " "-c:v copy -c:a aac -ac 2"), m_streamVopt1("ffmpeg -loglevel warning -f mpegts -analyzeduration 1.2M -probesize 5M -i -map 0:v -map 0:a:0 " "-c:v libx264 -preset ultrafast -crf 23 -tune zerolatency -g 25 -r 25 -c:a aac -ac 2"), m_streamVopt2("ffmpeg -loglevel warning -f mpegts -analyzeduration 1.2M -probesize 5M -i -map 0:v -map 0:a:0 " "-c:v libx264 -preset ultrafast -crf 23 -tune zerolatency -g 25 -r 25 -c:a aac -ac 2"), m_streamVopt3("ffmpeg -loglevel warning -f mpegts -analyzeduration 1.2M -probesize 5M -i -map 0:v -map 0:a:0 " "-c:v libx264 -preset ultrafast -crf 23 -tune zerolatency -g 25 -r 25 -c:a aac -ac 2"), m_showIMDb(1), m_showChannelsWithoutEPG(0) { m_adminPasswordMD5 = "4:" + MD5Hash("live"); liveplugin = cPluginManager::GetPlugin("live"); } bool Setup::ParseCommandLine( int argc, char* argv[] ) { static struct option opts[] = { { "port", required_argument, NULL, 'p' }, { "ip", required_argument, NULL, 'i' }, { "log", required_argument, NULL, 'l' }, { "epgimages", required_argument, NULL, 'e' }, { "tvscraperimages", required_argument, NULL, 't' }, { "sslport", required_argument, NULL, 's' }, { "cert", required_argument, NULL, 'c' }, { "key", required_argument, NULL, 'k' }, { 0 } }; int optchar, optind = 0; while ( ( optchar = getopt_long( argc, argv, "p:i:l:e:s:c:", opts, &optind ) ) != -1 ) { switch ( optchar ) { case 'p': m_serverPort = atoi( optarg ); break; case 'i': m_serverIps.push_back( optarg ); break; case 'l': m_tntnetloglevel = optarg; break; case 'e': m_epgimagedir = optarg; break; case 't': m_tvscraperimagedir = optarg; if(!m_tvscraperimagedir.empty() && m_tvscraperimagedir[m_tvscraperimagedir.length()-1] != '/') m_tvscraperimagedir += "/"; break; case 's': m_serverSslPort = atoi( optarg ); break; case 'c': m_serverSslCert = optarg; break; case 'k': m_serverSslKey = optarg; break; default: return false; } } return CheckServerPort() && CheckServerSslPort() && CheckServerIps(); } char const* Setup::CommandLineHelp() const { if ( m_helpString.empty() ) { std::stringstream builder; builder << " -p PORT, --port=PORT use PORT to listen for incoming connections\n" " (default: " << m_serverPort << ")\n" << " -i IP, --ip=IP bind server only to specified IP, may appear\n" " multiple times\n" " (default: 0.0.0.0)\n" << " -s PORT, --sslport=PORT use PORT to listen for incoming ssl connections\n" " (default: " << m_serverSslPort << ")\n" << " -c CERT, --cert=CERT full path to a custom ssl certificate file\n" << " -k KEY, --key=KEY full path to a custom ssl certificate key file\n" << " -l level, --log=level log level for tntnet (values: WARN, ERROR, INFO, DEBUG, TRACE)\n" << " -e , --epgimages= directory for epgimages\n" << " -t , --tvscraperimages= directory for tvscraper images\n"; m_helpString = builder.str(); } return m_helpString.c_str(); } bool Setup::ParseSetupEntry( char const* name, char const* value ) { if ( strcmp( name, "LastChannel" ) == 0 ) m_lastChannel = atoi( value ); else if ( strcmp( name, "ScreenshotInterval" ) == 0 ) m_screenshotInterval = atoi( value ); else if ( strcmp( name, "UseAuth" ) == 0 ) m_useAuth = atoi( value ); else if ( strcmp( name, "AdminLogin" ) == 0 ) m_adminLogin = value; else if ( strcmp( name, "AdminPasswordMD5" ) == 0 ) m_adminPasswordMD5 = value; else if ( strcmp( name, "UserdefTimes" ) == 0 ) m_times = value; else if ( strcmp( name, "ChannelGroups" ) == 0 ) m_channelGroups = value; else if ( strcmp( name, "ScheduleDuration" ) == 0 ) m_scheduleDuration = value; else if ( strcmp( name, "StartPage" ) == 0 ) m_startscreen = value; else if ( strcmp( name, "Theme" ) == 0 ) SetTheme(value); else if ( strcmp( name, "LocalNetMask" ) == 0 ) { m_localnetmask = value; } else if ( strcmp( name, "LastWhatsOnListMode" ) == 0 ) { m_lastwhatsonlistmode = value; } else if ( strcmp( name, "LastSortingMode" ) == 0 ) { m_lastsortingmode = value; } else if ( strcmp( name, "ShowLogo" ) == 0 ) { m_showLogo = atoi(value); } else if ( strcmp( name, "UseAjax" ) == 0 ) { m_useAjax = atoi(value); } else if ( strcmp( name, "ShowInfoBox" ) == 0 ) { m_showInfoBox = atoi(value); } else if ( strcmp( name, "UseStreamdev" ) == 0 ) { m_useStreamdev = atoi(value); } else if ( strcmp( name, "StreamdevPort" ) == 0 ) { m_streamdevPort = atoi(value); } else if ( strcmp( name, "StreamdevType" ) == 0 ) { m_streamdevType = value; } else if ( strcmp( name, "StreamVideoOpt0" ) == 0 ) { m_streamVopt0 = value; } else if ( strcmp( name, "StreamVideoOpt1" ) == 0 ) { m_streamVopt1 = value; } else if ( strcmp( name, "StreamVideoOpt2" ) == 0 ) { m_streamVopt2 = value; } else if ( strcmp( name, "StreamVideoOpt3" ) == 0 ) { m_streamVopt3 = value; } else if ( strcmp( name, "ScreenShotInterval" ) == 0 ) { m_screenshotInterval = atoi(value); } else if ( strcmp( name, "MarkNewRec" ) == 0 ) { m_markNewRec = atoi(value); } else if ( strcmp( name, "ShowIMDb" ) == 0 ) { m_showIMDb = atoi(value); } else if ( strcmp( name, "ShowChannelsWithoutEPG" ) == 0 ) { m_showChannelsWithoutEPG = atoi(value); } else return false; return true; } bool Setup::CheckServerPort() { if ( m_serverPort <= 0 || m_serverPort > std::numeric_limits::max() ) { esyslog( "live: ERROR: server port %d is not a valid port number", m_serverPort ); std::cerr << "ERROR: live server port " << m_serverPort << " is not a valid port number" << std::endl; return false; } return true; } bool Setup::CheckServerSslPort() { if ( m_serverSslPort <= 0 || m_serverSslPort > std::numeric_limits::max() ) { esyslog( "live: ERROR: server ssl port %d is not a valid port number", m_serverSslPort ); std::cerr << "ERROR: live server ssl port " << m_serverSslPort << " is not a valid port number" << std::endl; return false; } return true; } namespace { struct IpValidator { bool operator() (std::string const & ip) { struct in6_addr buf; struct in_addr buf4; esyslog( "live: INFO: validating server ip '%s'", ip.c_str()); std::cerr << "INFO: validating live server ip '" << ip << "'" << std::endl; bool valid = inet_aton(ip.c_str(), &buf4) || inet_pton(AF_INET6, ip.c_str(), &buf); if (!valid) { esyslog( "live: ERROR: server ip %s is not a valid ip address", ip.c_str()); std::cerr << "ERROR: live server ip '" << ip << "' is not a valid ip address" << std::endl; } return valid; } }; } bool Setup::CheckServerIps() { if ( m_serverIps.empty() ) { #if TNT_IPV6_V6ONLY m_serverIps.push_back(""); return true; #else FILE* f = fopen("/proc/sys/net/ipv6/bindv6only", "r"); if (f) { bool bindv6only = false; int c = fgetc(f); if (c != EOF) { bindv6only = ((c - '0') != 0); } fclose(f); f = NULL; esyslog( "live: INFO: bindv6only=%d", bindv6only); // add a default IPv6 listener address m_serverIps.push_back("::"); // skip the default IPv4 listener address if the IPv6 one will be bound also to v4 if (!bindv6only) return true; } // add a default IPv4 listener address m_serverIps.push_back("0.0.0.0"); // we assume these are ok :) return true; #endif // TNT_IPV6_V6ONLY } IpList::iterator i = partition(m_serverIps.begin(), m_serverIps.end(), IpValidator()); m_serverIps.erase(i, m_serverIps.end()); return !m_serverIps.empty(); } std::string const Setup::GetMD5HashAdminPassword() const { // format is : std::vector parts = StringSplit( m_adminPasswordMD5, ':' ); return (parts.size() > 1) ? parts[1] : ""; } int Setup::GetAdminPasswordLength() const { // format is : std::vector parts = StringSplit( m_adminPasswordMD5, ':' ); return (parts.size() > 0) ? lexical_cast( parts[0] ) : 0; } std::string Setup::SetAdminPassword(std::string password) { std::stringstream passwordStr; passwordStr << password.size() << ":" << MD5Hash(password); m_adminPasswordMD5 = passwordStr.str(); return m_adminPasswordMD5; } std::string const Setup::GetStartScreenLink() const { if (m_startscreen == "whatsonnext") return "whats_on.html?type=next"; else if (m_startscreen == "schedule") return "schedule.html"; else if (m_startscreen == "multischedule") return "multischedule.html"; else if (m_startscreen == "timers") return "timers.html"; else if (m_startscreen == "recordings") return "recordings.html"; else return "whats_on.html?type=now"; } bool Setup::UseAuth() const { return m_useAuth && !GetIsLocalNet(); } bool Setup::CheckLocalNet(const std::string& ip) { // split local net mask in net and range std::vector parts = StringSplit( m_localnetmask, '/' ); if (parts.size() != 2) return false; std::string net = parts[0]; int range = lexical_cast(parts[1]); // split net and ip addr in its 4 subcomponents std::vector netparts = StringSplit( net, '.' ); std::vector addrparts = StringSplit( ip, '.' ); if (netparts.size() != 4 || addrparts.size() != 4) return false; // to binary representation std::stringstream bin_netstream; bin_netstream << std::bitset<8>(lexical_cast(netparts[0])) << std::bitset<8>(lexical_cast(netparts[1])) << std::bitset<8>(lexical_cast(netparts[2])) << std::bitset<8>(lexical_cast(netparts[3])); std::stringstream bin_addrstream; bin_addrstream << std::bitset<8>(lexical_cast(addrparts[0])) << std::bitset<8>(lexical_cast(addrparts[1])) << std::bitset<8>(lexical_cast(addrparts[2])) << std::bitset<8>(lexical_cast(addrparts[3])); // compare range std::string bin_net = bin_netstream.str(); std::string bin_addr = bin_addrstream.str(); std::string bin_net_range(bin_net.begin(), bin_net.begin() + range); std::string addr_net_range(bin_addr.begin(), bin_addr.begin() + range); m_islocalnet = (bin_net_range == addr_net_range); return m_islocalnet; } bool Setup::SaveSetup() { if (!liveplugin) return false; liveplugin->SetupStore("LastChannel", m_lastChannel); liveplugin->SetupStore("UseAuth", m_useAuth); if (m_useAuth) { liveplugin->SetupStore("AdminLogin", m_adminLogin.c_str()); liveplugin->SetupStore("AdminPasswordMD5", m_adminPasswordMD5.c_str()); liveplugin->SetupStore("LocalNetMask", m_localnetmask.c_str()); } liveplugin->SetupStore("UserdefTimes", m_times.c_str()); liveplugin->SetupStore("ChannelGroups", m_channelGroups.c_str()); liveplugin->SetupStore("ScheduleDuration", m_scheduleDuration.c_str()); liveplugin->SetupStore("StartPage", m_startscreen.c_str()); liveplugin->SetupStore("Theme", m_theme.c_str()); liveplugin->SetupStore("LastWhatsOnListMode", m_lastwhatsonlistmode.c_str()); liveplugin->SetupStore("LastSortingMode", m_lastsortingmode.c_str()); liveplugin->SetupStore("ShowLogo", m_showLogo); liveplugin->SetupStore("UseAjax", m_useAjax); liveplugin->SetupStore("ShowInfoBox", m_showInfoBox); liveplugin->SetupStore("UseStreamdev", m_useStreamdev); liveplugin->SetupStore("StreamdevPort", m_streamdevPort); liveplugin->SetupStore("StreamdevType", m_streamdevType.c_str()); liveplugin->SetupStore("StreamVideoOpt0", m_streamVopt0.c_str()); liveplugin->SetupStore("StreamVideoOpt1", m_streamVopt1.c_str()); liveplugin->SetupStore("StreamVideoOpt2", m_streamVopt2.c_str()); liveplugin->SetupStore("StreamVideoOpt3", m_streamVopt3.c_str()); liveplugin->SetupStore("ScreenShotInterval", m_screenshotInterval); liveplugin->SetupStore("MarkNewRec", m_markNewRec); liveplugin->SetupStore("ShowIMDb", m_showIMDb); liveplugin->SetupStore("ShowChannelsWithoutEPG", m_showChannelsWithoutEPG); return true; } Setup& LiveSetup() { static Setup instance; return instance; } cMenuSetupLive::cMenuSetupLive(): cMenuSetupPage() { m_lastChannel = vdrlive::LiveSetup().GetLastChannel(); m_useAuth = vdrlive::LiveSetup().UseAuth(); strcpy(m_adminLogin, vdrlive::LiveSetup().GetAdminLogin().c_str()); m_oldpasswordMD5 = m_newpasswordMD5 = vdrlive::LiveSetup().GetMD5HashAdminPassword(); std::string strHidden(vdrlive::LiveSetup().GetAdminPasswordLength(), '*'); strn0cpy(m_tmpPassword, strHidden.c_str(), sizeof(m_tmpPassword)); strcpy(m_adminPassword, ""); Set(); } void cMenuSetupLive::Set(void) { int current = Current(); Clear(); //Add(new cMenuEditIntItem(tr("Last channel to display"), &m_lastChannel, 0, 65536)); Add(new cMenuEditChanItem(tr("Last channel to display"), &m_lastChannel, tr("No limit"))); //Add(new cMenuEditIntItem(tr("Screenshot interval"), &m_lastChannel, 0, 65536)); Add(new cMenuEditBoolItem(tr("Use authentication"), &m_useAuth, tr("No"), tr("Yes"))); Add(new cMenuEditStrItem( tr("Admin login"), m_adminLogin, sizeof(m_adminLogin), tr(FileNameChars))); Add(new cMenuEditStrItem( tr("Admin password"), m_tmpPassword, sizeof(m_tmpPassword), tr(FileNameChars))); SetCurrent(Get(current)); Display(); } void cMenuSetupLive::Store(void) { vdrlive::LiveSetup().SetLastChannel(m_lastChannel); vdrlive::LiveSetup().SetUseAuth(m_useAuth); vdrlive::LiveSetup().SetAdminLogin(m_adminLogin); if (m_oldpasswordMD5 != m_newpasswordMD5) // only save the password if needed vdrlive::LiveSetup().SetAdminPassword(m_adminPassword); LiveSetup().SaveSetup(); } bool cMenuSetupLive::InEditMode(const char* ItemText, const char* ItemName, const char* ItemValue) { bool bEditMode = true; // ugly solution to detect, if in edit mode char* value = strdup(ItemText); strreplace(value, ItemName, ""); strreplace(value, ":\t", ""); // for bigpatch strreplace(value, "\t", ""); if (strlen(value) == strlen(ItemValue)) bEditMode = false; free(value); return bEditMode; } eOSState cMenuSetupLive::ProcessKey(eKeys Key) { const char* ItemText = Get(Current())->Text(); bool bPassWasInEditMode = false; if (ItemText && strlen(ItemText) > 0 && strstr(ItemText, tr("Admin password")) == ItemText) bPassWasInEditMode = InEditMode(ItemText, tr("Admin password"), m_tmpPassword); eOSState state = cMenuSetupPage::ProcessKey(Key); ItemText = Get(Current())->Text(); bool bPassIsInEditMode = false; if (ItemText && strlen(ItemText) > 0 && strstr(ItemText, tr("Admin password")) == ItemText) bPassIsInEditMode = InEditMode(ItemText, tr("Admin password"), m_tmpPassword); if (bPassWasInEditMode && !bPassIsInEditMode) { strcpy(m_adminPassword, m_tmpPassword); m_newpasswordMD5 = MD5Hash(m_tmpPassword); std::string strHidden(strlen(m_adminPassword), '*'); strcpy(m_tmpPassword, strHidden.c_str()); Set(); Display(); } if (!bPassWasInEditMode && bPassIsInEditMode) { strcpy(m_tmpPassword, ""); Set(); Display(); state = cMenuSetupPage::ProcessKey(Key); } return state; } } // namespace vdrlive vdr-plugin-live-3.1.3/setup.h000066400000000000000000000172161414414333500160640ustar00rootroot00000000000000#ifndef VDR_LIVE_SETUP_H #define VDR_LIVE_SETUP_H // STL headers need to be before VDR tools.h (included by ) #include #include #include #if TNTVERSION >= 30000 #include // must be loaded before any vdr include because of duplicate macros (LOG_ERROR, LOG_DEBUG, LOG_INFO) #endif #include #define LIVEVERSION "3.1.3" #define LIVEVERSNUM 30103 #define LIVESUMMARY trNOOP("Live Interactive VDR Environment") namespace vdrlive { // forward declaration, see below class cMenuSetupLive; class Setup { friend Setup& LiveSetup(); friend class cMenuSetupLive; // friend declaration is not forward // declaration, although gcc 3.3 claims so public: typedef std::list IpList; // commandline int GetServerPort() const { return m_serverPort; } int GetServerSslPort() const { return m_serverSslPort; } std::string GetServerSslCert() const { return m_serverSslCert; } std::string GetServerSslKey() const { return m_serverSslKey; } IpList const& GetServerIps() const { return m_serverIps; } // vdr-setup int GetLastChannel() const { return m_lastChannel == 0 ? std::numeric_limits::max() : m_lastChannel; } int GetScreenshotInterval() const { return m_screenshotInterval; } std::string const GetAdminLogin() const { return m_adminLogin; } std::string const GetMD5HashAdminPassword() const; int GetAdminPasswordLength() const; bool GetUseAuth() const { return m_useAuth; } bool UseAuth() const; std::string const GetTimes() const { return m_times; } std::string const GetChannelGroups() const { return m_channelGroups; } std::string const GetScheduleDuration() const { return m_scheduleDuration; } std::string const GetStartScreen() const { return m_startscreen; } std::string const GetStartScreenLink() const; std::string const GetTheme() const { return m_theme; } std::string const GetThemedLink(std::string const & type, const std::string& name) const { return GetThemedLinkPrefix() + type + "/" + name; } std::string const GetThemedLinkPrefix() const { return m_themedLinkPrefix ; } std::string const GetThemedLinkPrefixImg() const { return m_themedLinkPrefixImg ; } std::string const GetLocalNetMask() const { return m_localnetmask; }; bool GetIsLocalNet() const { return m_islocalnet; }; std::string const GetLastWhatsOnListMode() const { return m_lastwhatsonlistmode; } std::string const GetLastSortingMode() const { return m_lastsortingmode; } std::string const GetTntnetLogLevel() const { return m_tntnetloglevel; } bool GetShowLogo() const { return m_showLogo != 0; } bool GetUseAjax() const { return m_useAjax != 0; } bool GetShowInfoBox() const { return m_showInfoBox != 0; } bool GetUseStreamdev() const { return m_useStreamdev != 0; } int GetStreamdevPort() const { return m_streamdevPort; } std::string const GetStreamdevType() const { return m_streamdevType; } bool GetMarkNewRec() const { return m_markNewRec != 0; } std::string const GetStreamVideoOpt0() const { return m_streamVopt0; } std::string const GetStreamVideoOpt1() const { return m_streamVopt1; } std::string const GetStreamVideoOpt2() const { return m_streamVopt2; } std::string const GetStreamVideoOpt3() const { return m_streamVopt3; } bool GetShowIMDb() const { return m_showIMDb != 0; } std::string const GetEpgImageDir() { return m_epgimagedir; } std::string const GetTvscraperImageDir() { return m_tvscraperimagedir; } bool GetShowChannelsWithoutEPG() const { return m_showChannelsWithoutEPG != 0; } void SetLastChannel(int lastChannel) { m_lastChannel = lastChannel; } void SetAdminLogin(std::string const & login) { m_adminLogin = login; } std::string SetAdminPassword(std::string password); void SetUseAuth(int auth) { m_useAuth = auth; } void SetScreenshotInterval(int interval) { m_screenshotInterval = interval; } void SetTimes(std::string const & times) { m_times = times; } void SetChannelGroups(std::string const & channelGroups) { m_channelGroups = channelGroups; } void SetScheduleDuration(std::string const & scheduleDuration) { m_scheduleDuration = scheduleDuration; } void SetStartScreen(std::string const & startscreen) { m_startscreen = startscreen; } void SetTheme(std::string const & theme) { m_theme = theme; m_themedLinkPrefix = "themes/" + theme + "/"; m_themedLinkPrefixImg = m_themedLinkPrefix + "img/"; } void SetLocalNetMask(std::string const & localnetmask) { m_localnetmask = localnetmask; } void SetIsLocalNet(bool islocalnet) { m_islocalnet = islocalnet; } void SetLastWhatsOnListMode(std::string const & mode) { m_lastwhatsonlistmode = mode; SaveSetup(); } void SetLastSortingMode(std::string const & mode) { m_lastsortingmode = mode; SaveSetup(); } void SetShowLogo(bool show) { m_showLogo = show ? 1 : 0; } void SetUseAjax(bool use) { m_useAjax = use ? 1 : 0; } void SetShowInfoBox(bool show) { m_showInfoBox = show ? 1 : 0; } void SetUseStreamdev(bool use) { m_useStreamdev = use ? 1 : 0; } void SetStreamdevPort(int port) { m_streamdevPort = port; } void SetStreamdevType(std::string const & type) { m_streamdevType = type; } void SetMarkNewRec(bool show) { m_markNewRec = show ? 1 : 0; } void SetStreamVideoOpt0(std::string const & opt) { m_streamVopt0 = opt; } void SetStreamVideoOpt1(std::string const & opt) { m_streamVopt1 = opt; } void SetStreamVideoOpt2(std::string const & opt) { m_streamVopt2 = opt; } void SetStreamVideoOpt3(std::string const & opt) { m_streamVopt3 = opt; } void SetShowIMDb(bool show) { m_showIMDb = show ? 1 : 0; } void SetShowChannelsWithoutEPG(bool show) { m_showChannelsWithoutEPG = show ? 1 : 0; } bool SaveSetup(); bool ParseCommandLine( int argc, char* argv[] ); char const* CommandLineHelp() const; bool ParseSetupEntry( char const* name, char const* value ); bool CheckLocalNet(std::string const & ip); private: Setup(); Setup( Setup const& ); // me cPlugin* liveplugin; mutable std::string m_helpString; // commandline options int m_serverPort; int m_serverSslPort; std::string m_serverSslCert; std::string m_serverSslKey; static std::string m_configDirectory; IpList m_serverIps; std::string m_epgimagedir; std::string m_tvscraperimagedir; // setup options int m_lastChannel; int m_screenshotInterval; int m_useAuth; std::string m_adminLogin; std::string m_adminPasswordMD5; std::string m_times; std::string m_channelGroups; std::string m_scheduleDuration; std::string m_startscreen; std::string m_theme; std::string m_themedLinkPrefix; std::string m_themedLinkPrefixImg; std::string m_localnetmask; bool m_islocalnet; std::string m_lastwhatsonlistmode; std::string m_lastsortingmode; std::string m_tntnetloglevel; int m_showLogo; int m_useAjax; int m_showInfoBox; int m_useStreamdev; int m_streamdevPort; std::string m_streamdevType; int m_markNewRec; std::string m_streamVopt0; std::string m_streamVopt1; std::string m_streamVopt2; std::string m_streamVopt3; int m_showIMDb; int m_showChannelsWithoutEPG; bool CheckServerPort(); bool CheckServerIps(); bool CheckServerSslPort(); }; Setup& LiveSetup(); class cMenuSetupLive : public cMenuSetupPage { protected: virtual void Store(void); virtual eOSState ProcessKey(eKeys Key); public: cMenuSetupLive(); private: int m_lastChannel; int m_screenshotInterval; int m_useAuth; char m_adminLogin[20]; char m_adminPassword[20]; char m_tmpPassword[20]; std::string m_oldpasswordMD5; std::string m_newpasswordMD5; void Set(void); bool InEditMode(const char* ItemText, const char* ItemName, const char* ItemValue); }; } // namespace vdrlive #endif // VDR_LIVE_SETUP_H vdr-plugin-live-3.1.3/status.cpp000066400000000000000000000007071414414333500165770ustar00rootroot00000000000000 #include "status.h" #include "timers.h" namespace vdrlive { StatusMonitor::StatusMonitor() { } void StatusMonitor::TimerChange(const cTimer *Timer, eTimerChange Change) { LiveTimerManager().SetReloadTimers(); } void StatusMonitor::Recording( cDevice const*, char const*, char const*, bool ) { LiveTimerManager().SetReloadTimers(); } StatusMonitor& LiveStatusMonitor() { static StatusMonitor instance; return instance; } } // namespace vdrlive vdr-plugin-live-3.1.3/status.h000066400000000000000000000012501414414333500162360ustar00rootroot00000000000000#ifndef VDR_LIVE_STATUS_H #define VDR_LIVE_STATUS_H #if TNTVERSION >= 30000 #include // must be loaded before any vdr include because of duplicate macros (LOG_ERROR, LOG_DEBUG, LOG_INFO) #endif #include namespace vdrlive { class StatusMonitor: public cStatus { friend StatusMonitor& LiveStatusMonitor(); private: StatusMonitor(); StatusMonitor( StatusMonitor const& ); virtual void TimerChange(const cTimer *Timer, eTimerChange Change); virtual void Recording( cDevice const* Device, char const* Name, char const* FileName, bool On ); }; StatusMonitor& LiveStatusMonitor(); } // namespace vdrlive #endif // VDR_LIVE_STATUS_H vdr-plugin-live-3.1.3/stdext.h000066400000000000000000000002711414414333500162300ustar00rootroot00000000000000#pragma once #if __cplusplus >= 201103L #include #include #else #error "this plugin needs a compiler with C++11 support, ie. gcc-4.8.1 or higher" #endif vdr-plugin-live-3.1.3/tasks.cpp000066400000000000000000000140411414414333500163750ustar00rootroot00000000000000 #include "tasks.h" #include "stdext.h" #include "recman.h" #if VDRVERSNUM < 20300 #include "tools.h" // ReadLock #endif // STL headers need to be before VDR tools.h (included by ) #include #include #include namespace vdrlive { const char* NowReplaying() { return cReplayControl::NowReplaying(); } StickyTask::StickyTask() { LiveTaskManager().AddStickyTask( *this ); } StickyTask::~StickyTask() { LiveTaskManager().RemoveStickyTask( *this ); } void SwitchChannelTask::Action() { #if VDRVERSNUM >= 20301 LOCK_CHANNELS_READ; cChannel* channel = (cChannel *)Channels->GetByChannelID( m_channel ); #else ReadLock lock( Channels ); cChannel* channel = Channels.GetByChannelID( m_channel ); #endif if ( channel == 0 ) { SetError( tr("Couldn't find channel or no channels available.") ); return; } #if VDRVERSNUM >= 20301 if ( !Channels->SwitchTo( channel->Number() ) ) #else if ( !Channels.SwitchTo( channel->Number() ) ) #endif SetError( tr("Couldn't switch to channel.") ); } void PlayRecordingTask::Action() { RecordingsManagerPtr recordings = LiveRecordingsManager(); cRecording const* recording = recordings->GetByMd5Hash( m_recording ); if ( recording == 0 ) { SetError( tr("Couldn't find recording or no recordings available.") ); return; } const char *current = NowReplaying(); if (!current || (0 != strcmp(current, recording->FileName()))) { cReplayControl::SetRecording( 0 ); cControl::Shutdown(); cReplayControl::SetRecording( recording->FileName() ); cControl::Launch( new cReplayControl ); cControl::Attach(); } else { cReplayControl* replayControl = reinterpret_cast(cControl::Control()); if (! replayControl) { SetError(tr("Cannot control playback!")); return; } replayControl->Play(); } } void PauseRecordingTask::Action() { RecordingsManagerPtr recordings = LiveRecordingsManager(); cRecording const* recording = recordings->GetByMd5Hash( m_recording ); if ( recording == 0 ) { SetError( tr("Couldn't find recording or no recordings available.") ); return; } const char *current = NowReplaying(); if (!current) { SetError(tr("Not playing a recording.")); return; } if (0 != strcmp(current, recording->FileName())) { // not replaying same recording like in request SetError(tr("Not playing the same recording as from request.")); return; } cReplayControl* replayControl = reinterpret_cast(cControl::Control()); if (! replayControl) { SetError(tr("Cannot control playback!")); return; } replayControl->Pause(); } void StopRecordingTask::Action() { RecordingsManagerPtr recordings = LiveRecordingsManager(); cRecording const* recording = recordings->GetByMd5Hash( m_recording ); if ( recording == 0 ) { SetError( tr("Couldn't find recording or no recordings available.") ); return; } const char *current = NowReplaying(); if (!current) { SetError(tr("Not playing a recording.")); return; } cReplayControl::SetRecording( 0 ); cControl::Shutdown(); } void ForwardRecordingTask::Action() { RecordingsManagerPtr recordings = LiveRecordingsManager(); cRecording const* recording = recordings->GetByMd5Hash( m_recording ); if ( recording == 0 ) { SetError( tr("Couldn't find recording or no recordings available.") ); return; } const char *current = NowReplaying(); if (!current) { SetError(tr("Not playing a recording.")); return; } if (0 != strcmp(current, recording->FileName())) { // not replaying same recording like in request SetError(tr("Not playing the same recording as from request.")); return; } cReplayControl* replayControl = reinterpret_cast(cControl::Control()); if (! replayControl) { SetError(tr("Cannot control playback!")); return; } replayControl->Forward(); } void BackwardRecordingTask::Action() { RecordingsManagerPtr recordings = LiveRecordingsManager(); cRecording const* recording = recordings->GetByMd5Hash( m_recording ); if ( recording == 0 ) { SetError(tr("Couldn't find recording or no recordings available.")); return; } const char *current = NowReplaying(); if (!current) { SetError(tr("Not playing a recording.")); return; } if (0 != strcmp(current, recording->FileName())) { // not replaying same recording like in request SetError(tr("Not playing the same recording as from request.")); return; } cReplayControl* replayControl = reinterpret_cast(cControl::Control()); if (! replayControl) { SetError(tr("Cannot control playback!")); return; } replayControl->Backward(); } void RemoveRecordingTask::Action() { RecordingsManagerPtr recordings = LiveRecordingsManager(); cRecording const * recording = recordings->GetByMd5Hash( m_recording ); if ( recording == 0 ) { SetError( tr("Couldn't find recording or no recordings available.") ); return; } m_recName = recording->Name(); const char *current = NowReplaying(); if (current && (0 == strcmp(current, recording->FileName()))) { SetError(tr("Attempt to delete recording currently in playback.")); return; } recordings->DeleteRecording(recording); } TaskManager::TaskManager() { } void TaskManager::AddStickyTask( Task& task ) { cMutexLock lock( this ); m_stickyTasks.push_back( &task ); } void TaskManager::RemoveStickyTask( Task& task ) { cMutexLock lock( this ); m_stickyTasks.erase( find( m_stickyTasks.begin(), m_stickyTasks.end(), &task ) ); } bool TaskManager::Execute( Task& task ) { cMutexLock lock( this ); m_taskQueue.push_back( &task ); m_scheduleWait.Wait( *this ); return task.Result(); } void TaskManager::DoScheduledTasks() { if ( m_taskQueue.empty() && m_stickyTasks.empty() ) return; using std::for_each; using std::bind; using namespace std::placeholders; cMutexLock lock( this ); for_each( m_taskQueue.begin(), m_taskQueue.end(), bind( &Task::Action, _1 ) ); for_each( m_stickyTasks.begin(), m_stickyTasks.end(), bind( &Task::Action, _1 ) ); m_taskQueue.clear(); m_scheduleWait.Broadcast(); } TaskManager& LiveTaskManager() { static TaskManager instance; return instance; } } // namespace vdrlive vdr-plugin-live-3.1.3/tasks.h000066400000000000000000000060161414414333500160450ustar00rootroot00000000000000#ifndef VDR_LIVE_TASKS_H #define VDR_LIVE_TASKS_H // STL headers need to be before VDR tools.h (included by ) #include #include #if TNTVERSION >= 30000 #include // must be loaded before any vdr include because of duplicate macros (LOG_ERROR, LOG_DEBUG, LOG_INFO) #endif #include #include namespace vdrlive { class Task; class TaskManager: public cMutex { friend TaskManager& LiveTaskManager(); friend class StickyTask; typedef std::vector TaskList; public: bool Execute( Task& task ); // may only be called from Plugin::MainThreadHook void DoScheduledTasks(); private: TaskManager(); TaskManager( TaskManager const& ); void AddStickyTask( Task& task ); void RemoveStickyTask( Task& task ); TaskList m_taskQueue; TaskList m_stickyTasks; cCondVar m_scheduleWait; }; class Task { friend void TaskManager::DoScheduledTasks(); public: virtual ~Task() {} bool Result() const { return m_result; } std::string const& Error() const { return m_error; } protected: explicit Task() : m_result( true ) {} Task( Task const& ); void SetError( std::string const& error ) { m_result = false; m_error = error; } private: bool m_result; std::string m_error; virtual void Action() = 0; }; class StickyTask: public Task { protected: explicit StickyTask(); virtual ~StickyTask(); }; class SwitchChannelTask: public Task { public: explicit SwitchChannelTask( tChannelID channel ): m_channel( channel ) {} private: tChannelID m_channel; virtual void Action() override; }; class RecordingTask: public Task { protected: explicit RecordingTask(std::string const& recording) : m_recording(recording) {} std::string m_recording; }; class PlayRecordingTask: public RecordingTask { public: explicit PlayRecordingTask( std::string const& recording ) : RecordingTask(recording) {} virtual void Action() override; }; class PauseRecordingTask: public RecordingTask { public: explicit PauseRecordingTask( std::string const& recording ) : RecordingTask(recording) {} virtual void Action() override; }; class StopRecordingTask: public RecordingTask { public: explicit StopRecordingTask( std::string const& recording ) : RecordingTask(recording) {} virtual void Action() override; }; class ForwardRecordingTask: public RecordingTask { public: explicit ForwardRecordingTask( std::string const& recording ) : RecordingTask(recording) {} virtual void Action() override; }; class BackwardRecordingTask: public RecordingTask { public: explicit BackwardRecordingTask( std::string const& recording ) : RecordingTask(recording) {} virtual void Action() override; }; class RemoveRecordingTask: public RecordingTask { public: explicit RemoveRecordingTask( std::string const& recording ) : RecordingTask(recording) {} virtual void Action() override; std::string const & RecName() const { return m_recName; } private: std::string m_recName; }; TaskManager& LiveTaskManager(); } // namespace vdrlive #endif // VDR_LIVE_TASKS_H vdr-plugin-live-3.1.3/thread.cpp000066400000000000000000000013141414414333500165160ustar00rootroot00000000000000 #include "thread.h" #include "tntconfig.h" #include namespace vdrlive { using namespace tnt; ServerThread::ServerThread() { } ServerThread::~ServerThread() { Stop(); } void ServerThread::Stop() { if ( Active() ) { m_server->shutdown(); Cancel( 5 ); } } void ServerThread::Action() { try { m_server.reset(new Tntnet()); TntConfig::Get().Configure(*m_server); m_server->run(); m_server.reset(0); } catch (std::exception const& ex) { // XXX move initial error handling to live.cpp esyslog("live: ERROR: live httpd server crashed: %s", ex.what()); std::cerr << "HTTPD FATAL ERROR: " << ex.what() << std::endl; //cThread::EmergencyExit(true); } } } // namespace vdrlive vdr-plugin-live-3.1.3/thread.h000066400000000000000000000006151414414333500161660ustar00rootroot00000000000000#ifndef VDR_LIVE_THREAD_H #define VDR_LIVE_THREAD_H #include #include namespace tnt { class Tntnet; } namespace vdrlive { class ServerThread : public cThread { public: ServerThread(); virtual ~ServerThread(); void Stop(); protected: virtual void Action(); private: std::unique_ptr m_server; }; } // namespace vdrlive #endif // VDR_LIVE_THREAD_H vdr-plugin-live-3.1.3/timerconflict.cpp000066400000000000000000000157521414414333500201240ustar00rootroot00000000000000 #include "timerconflict.h" #include "tools.h" #include "epgsearch/services.h" // STL headers need to be before VDR tools.h (included by ) #include #include #include namespace vdrlive { bool CheckEpgsearchVersion(); static char ServiceInterface[] = "Epgsearch-services-v1.1"; bool operator<( TimerConflict const& left, TimerConflict const& right ) { return left.conflictTime < right.conflictTime; } TimerConflict::TimerConflict() { Init(); } void TimerConflict::Init() { conflictTime = 0; } TimerConflict::TimerConflict( std::string const& data ) { Init(); // dsyslog("live: TimerConflict() data '%s'", data.c_str()); std::vector parts = StringSplit( data, ':' ); try { std::vector::const_iterator part = parts.begin(); if (parts.size() > 0) { conflictTime = lexical_cast( *part++ ); for ( int i = 1; part != parts.end(); ++i, ++part ) { std::vector timerparts = StringSplit( *part, '|' ); std::vector::const_iterator timerpart = timerparts.begin(); TimerInConflict timer; for ( int j = 0; timerpart != timerparts.end(); ++j, ++timerpart ) { switch (j) { case 0: timer.timerIndex = lexical_cast( *timerpart ); break; case 1: timer.percentage = lexical_cast( *timerpart ); break; case 2: { std::vector conctimerparts = StringSplit( *timerpart, '#' ); std::vector::const_iterator conctimerpart = conctimerparts.begin(); for ( int k = 0; conctimerpart != conctimerparts.end(); ++k, ++conctimerpart ) timer.concurrentTimerIndices.push_back(lexical_cast( *conctimerpart )); break; } case 3: { timer.remote = *timerpart; break; } } } conflictingTimers.push_back(timer); } } } catch ( bad_lexical_cast const& ex ) { } } TimerConflicts::TimerConflicts() { Epgsearch_services_v1_1 service; if ( CheckEpgsearchVersion() && cPluginManager::CallFirstService(ServiceInterface, &service)) { cServiceHandler_v1_1* handler = dynamic_cast(service.handler.get()); if (handler) { std::list conflicts = service.handler->TimerConflictList(); // for(std::list::const_iterator i = conflicts.begin(); i != conflicts.end(); ++i) { // dsyslog("live: TimerConflicts::TimerConflicts() conflicts '%s'",i->c_str()); // } GetRemote(conflicts); // add remote VDR conflicts m_conflicts.assign( conflicts.begin(), conflicts.end() ); m_conflicts.sort(); } } // for (TimerConflicts::iterator conflict=m_conflicts.begin(); conflict!=m_conflicts.end(); conflict++) { // const std::list& conflTimers = conflict->ConflictingTimers(); // for (std::list::const_iterator confltimer = conflTimers.begin(); confltimer != conflTimers.end(); ++confltimer) { // dsyslog("live: TimerConflicts::TimerConflictsi() Timer ID with conflict '%d'", confltimer->timerIndex ); // dsyslog("live: TimerConflicts::TimerConflictsi() conflict on server '%s'", confltimer->remote.c_str() ); // for (std::list::const_iterator timerIndex = confltimer->concurrentTimerIndices.begin(); timerIndex != confltimer->concurrentTimerIndices.end(); ++timerIndex) { // dsyslog("live: TimerConflicts::TimerConflicts() concurrent Timer IDs '%d'", *timerIndex); // } // } // } } void TimerConflicts::GetRemote(std::list & conflicts ) { cStringList svdrpServerNames; if (GetSVDRPServerNames(&svdrpServerNames)) { svdrpServerNames.Sort(true); } for (int i = 0; i < svdrpServerNames.Size(); i++) { std::string remoteServer = svdrpServerNames[i]; // dsyslog("live: TimerConflicts::GetRemote() found remote server '%s'", remoteServer.c_str()); cStringList response; std::string command = "PLUG epgsearch lscc"; bool svdrpOK = ExecSVDRPCommand(remoteServer.c_str(), command.c_str(), &response); if ( !svdrpOK ) { esyslog("live: TimerConflicts::GetRemote() svdrp command '%s' on remote server '%s'failed", command.c_str(), remoteServer.c_str()); } else { for (int i = 0; i < response.Size(); i++) { int code = SVDRPCode(response[i]); // dsyslog("live: GetRemote() response[i] '%s'", response[i]); switch ( code ) { case 900: { std::string rConflict = response[i]; std::string remConflict = rConflict.substr(4); remConflict.append("|"); remConflict.append(remoteServer); // dsyslog("live: TimerConflicts::GetRemote() found remote conflict '%s' ", remConflict.c_str()); conflicts.push_back(remConflict); break; } case 901: break; // no conflict found default: { esyslog("live: TimerConflicts::GetRemote() svdrp command '%s' failed, respone: %s", command.c_str(), response[i]); svdrpOK = false; break; } } } if ( svdrpOK ) { // dsyslog("live: TimerConflicts::GetRemote() on server '%s' successful", remoteServer.c_str()); } else { esyslog("live: TimerConflicts::GetRemote() on server '%s' failed", remoteServer.c_str()); } } response.Clear(); } svdrpServerNames.Clear(); } bool TimerConflicts::CheckAdvised() { Epgsearch_services_v1_1 service; if (CheckEpgsearchVersion() && cPluginManager::CallFirstService(ServiceInterface, &service)) { cServiceHandler_v1_1* handler = dynamic_cast(service.handler.get()); if (!handler) return false; else return handler->IsConflictCheckAdvised(); } else return false; } TimerConflictNotifier::TimerConflictNotifier() : lastCheck(0) , lastTimerModification(0) , conflicts() { } TimerConflictNotifier::~TimerConflictNotifier() { } bool TimerConflictNotifier::ShouldNotify() { time_t now = time(0); bool reCheckAdvised((now - lastCheck) > CHECKINTERVAL); bool recentTimerChange((now - lastTimerModification) <= CHECKINTERVAL); if (recentTimerChange || (reCheckAdvised && TimerConflicts::CheckAdvised())) { lastCheck = now; conflicts.reset(new TimerConflicts()); return conflicts->size() > 0; } return false; } void TimerConflictNotifier::SetTimerModification() { lastTimerModification = time(0); } std::string TimerConflictNotifier::Message() const { int count = conflicts ? conflicts->size() : 0; std::string msg = tr("Timer conflict check detected "); msg += ConvertToString(count) + " "; if (count == 1) msg += tr("conflict"); else msg += tr("conflicts"); return count > 0 ? msg + "!" : ""; } std::string TimerConflictNotifier::Url() const { return "timerconflicts.html"; } } // namespace vdrlive vdr-plugin-live-3.1.3/timerconflict.h000066400000000000000000000046461414414333500175710ustar00rootroot00000000000000#ifndef VDR_LIVE_TIMERCONFLICT_H #define VDR_LIVE_TIMERCONFLICT_H #include "stdext.h" #include #include #include namespace vdrlive { // classes for timer conflict interface // conflicting timer class TimerInConflict { public: int timerIndex; // it's index in VDR std::string remote = ""; int percentage; // percentage of recording std::list concurrentTimerIndices; // concurrent timer indices TimerInConflict(int TimerIndex=-1, int Percentage=0) : timerIndex(TimerIndex), percentage(Percentage) {} }; class TimerConflict; bool operator<( TimerConflict const& left, TimerConflict const& right ); // one timer conflict time class TimerConflict { time_t conflictTime; // time of conflict std::list conflictingTimers; // conflicting timers at this time friend bool operator<( TimerConflict const& left, TimerConflict const& right ); public: TimerConflict( std::string const& data ); TimerConflict(); void Init(); time_t ConflictTime() { return conflictTime; } const std::list& ConflictingTimers() { return conflictingTimers; } }; class TimerConflicts { public: typedef std::list ConflictList; typedef ConflictList::size_type size_type; typedef ConflictList::iterator iterator; typedef ConflictList::const_iterator const_iterator; TimerConflicts(); size_type size() const { return m_conflicts.size(); } iterator begin() { return m_conflicts.begin(); } const_iterator begin() const { return m_conflicts.begin(); } iterator end() { return m_conflicts.end(); } const_iterator end() const { return m_conflicts.end(); } static bool CheckAdvised(); private: void GetRemote(std::list & conflicts); ConflictList m_conflicts; }; class TimerConflictNotifier { public: typedef std::shared_ptr TimerConflictsPtr; TimerConflictNotifier(); virtual ~TimerConflictNotifier(); bool ShouldNotify(); void SetTimerModification(); std::string Message() const; std::string Url() const; TimerConflictsPtr const CurrentConflicts() const { return conflicts; } static int const CHECKINTERVAL = 5; // recheck value in seconds. private: time_t lastCheck; time_t lastTimerModification; TimerConflictsPtr conflicts; }; // class TimerConflictNotifier } // namespace vdrlive #endif // VDR_LIVE_TIMERCONFLICT_H vdr-plugin-live-3.1.3/timers.cpp000066400000000000000000000512561414414333500165640ustar00rootroot00000000000000#include "timers.h" #include "exception.h" #include "tools.h" // STL headers need to be before VDR tools.h (included by ) #include #include #include #include #include namespace vdrlive { static char const* const TIMER_DELETE = "DELETE"; static char const* const TIMER_TOGGLE = "TOGGLE"; SortedTimers::SortedTimers() #if VDRVERSNUM < 20301 : m_state( 0 ) #endif { } std::string SortedTimers::GetTimerId( cTimer const& timer ) { std::stringstream builder; builder << timer.Channel()->GetChannelID() << ":" << timer.WeekDays() << ":" << timer.Day() << ":" << timer.Start() << ":" << timer.Stop(); return builder.str(); } const cTimer* SortedTimers::GetByTimerId( std::string const& timerid ) { std::vector parts = StringSplit( timerid, ':' ); if ( parts.size() < 5 ) { esyslog("live: GetByTimerId: invalid format %s", timerid.c_str() ); return 0; } #if VDRVERSNUM >= 20301 #ifdef DEBUG_LOCK dsyslog("live: timers.cpp SortedTimers::GetByTimerId() LOCK_TIMERS_READ"); dsyslog("live: timers.cpp SortedTimers::GetByTimerId() LOCK_CHANNELS_READ"); #endif LOCK_TIMERS_READ LOCK_CHANNELS_READ; cChannel* channel = (cChannel *)Channels->GetByChannelID( tChannelID::FromString( parts[0].c_str() ) ); #else cChannel* channel = Channels.GetByChannelID( tChannelID::FromString( parts[0].c_str() ) ); #endif if ( channel == 0 ) { esyslog("live: GetByTimerId: no channel %s", parts[0].c_str() ); return 0; } try { int weekdays = lexical_cast( parts[1] ); time_t day = lexical_cast( parts[2] ); int start = lexical_cast( parts[3] ); int stop = lexical_cast( parts[4] ); cMutexLock MutexLock(&m_mutex); for (cTimer* timer = (cTimer *)Timers->First(); timer; timer = (cTimer *)Timers->Next(timer)) { if ( timer->Channel() == channel && ( ( weekdays != 0 && timer->WeekDays() == weekdays ) || ( weekdays == 0 && timer->Day() == day ) ) && timer->Start() == start && timer->Stop() == stop ) return &*timer; } } catch ( bad_lexical_cast const& ex ) { esyslog("live: GetByTimer: bad cast"); } return 0; } std::string SortedTimers::EncodeDomId(std::string const& timerid) { std::string tId("timer_"); tId += vdrlive::EncodeDomId(timerid, ".-:", "pmc"); return tId; } std::string SortedTimers::DecodeDomId(std::string const &timerDomId) { std::string const timerStr("timer_"); std::string tId = timerDomId.substr(timerStr.length()); return vdrlive::DecodeDomId(tId, "pmc", ".-:"); } std::string SortedTimers::GetTimerDays(cTimer const *timer) { if (!timer) return ""; std::string currentDay = timer->WeekDays() > 0 ? *cTimer::PrintDay(0, timer->WeekDays(), true) : FormatDateTime(tr("%A, %x"), timer->Day()); return currentDay; } std::string SortedTimers::GetTimerInfo(cTimer const& timer) { std::stringstream info; info << trVDR("Priority") << ": " << timer.Priority() << std::endl; info << trVDR("Lifetime") << ": " << timer.Lifetime() << std::endl; info << trVDR("VPS") << ": " << (timer.HasFlags(tfVps)?trVDR("yes"):trVDR("no")) << std::endl; if (timer.Aux()) { std::string epgsearchinfo = GetXMLValue(timer.Aux(), "epgsearch"); if (!epgsearchinfo.empty()) { std::string searchtimer = GetXMLValue(epgsearchinfo, "searchtimer"); if (!searchtimer.empty()) info << tr("Searchtimer") << ": " << searchtimer << std::endl; } } #if VDRVERSNUM >= 20400 if (timer.Local()) { info << trVDR("Record on") << ": " << trVDR(" ") << std::endl; } else { info << trVDR("Record on") << ": " << timer.Remote() << std::endl; } #endif return info.str(); } std::string SortedTimers::SearchTimerInfo(cTimer const& timer, std::string const& value) { std::stringstream info; if (timer.Aux()) { std::string epgsearchinfo = GetXMLValue(timer.Aux(), "epgsearch"); if (!epgsearchinfo.empty()) { std::string data = GetXMLValue(epgsearchinfo, value); if (!data.empty()) info << data; } } return info.str(); } #if VDRVERSNUM >= 20301 bool SortedTimers::Modified() { bool modified = false; // will return != 0 only, if the Timers List has been changed since last read if (cTimers::GetTimersRead(m_TimersStateKey)) { modified = true; m_TimersStateKey.Remove(); } return modified; } #endif TimerManager::TimerManager() { } void TimerManager::UpdateTimer( int timerId, const char* remote, const char* oldRemote, int flags, const tChannelID& channel, std::string const& weekdays, std::string const& day, int start, int stop, int priority, int lifetime, std::string const& title, std::string const& aux ) { cMutexLock lock( this ); std::stringstream builder; builder << flags << ":" << channel << ":" << ( weekdays != "-------" ? weekdays : "" ) << ( weekdays == "-------" || day.empty() ? "" : "@" ) << day << ":" << start << ":" << stop << ":" << priority << ":" << lifetime << ":" << StringReplace(title, ":", "|" ) << ":" << StringReplace(aux, ":", "|" ); // Use StringReplace here because if ':' are characters in the // title or aux string it breaks parsing of timer definition // in VDRs cTimer::Parse method. The '|' will be replaced // back to ':' by the cTimer::Parse() method. // Fix was submitted by rofafor: see // http://www.vdr-portal.de/board/thread.php?threadid=100398 dsyslog("live: UpdateTimer() timerId '%d'", timerId); dsyslog("live: UpdateTimer() remote '%s'", remote); dsyslog("live: UpdateTimer() oldRemote '%s'", oldRemote); dsyslog("live: UpdateTimer() channel '%s'", *(channel.ToString())); dsyslog("live: UpdateTimer() builder '%s'", builder.str().c_str()); timerStruct timerData = { .id = timerId, .remote=remote, .oldRemote=oldRemote, .builder=builder.str() }; // dsyslog("live: SV: in UpdateTimer"); m_updateTimers.push_back( timerData ); // dsyslog("live: SV: wait for update"); m_updateWait.Wait( *this ); // dsyslog("live: SV: update done"); std::string error = GetError( timerData ); if ( !error.empty() ) throw HtmlError( error ); } void TimerManager::DelTimer( int timerId, const char* remote ) { cMutexLock lock( this ); dsyslog("live: DelTimer() timerId '%d'", timerId); dsyslog("live: DelTimer() remote '%s'", remote); timerStruct timerData{ .id=timerId, .remote=remote, .oldRemote=remote, .builder=TIMER_DELETE }; m_updateTimers.push_back( timerData ); m_updateWait.Wait( *this ); std::string error = GetError( timerData ); if ( !error.empty() ) throw HtmlError( error ); } void TimerManager::ToggleTimerActive( int timerId, const char* remote) { cMutexLock lock( this ); timerStruct timerData{ .id=timerId, .remote=remote, .oldRemote=remote, .builder=TIMER_TOGGLE }; m_updateTimers.push_back( timerData ); m_updateWait.Wait( *this ); std::string error = GetError( timerData ); if ( !error.empty() ) throw HtmlError( error ); } void TimerManager::DoPendingWork() { if ( m_updateTimers.size() == 0 && !m_timers.Modified() && !m_reloadTimers ) return; cMutexLock lock( this ); if ( m_updateTimers.size() > 0 ) { DoUpdateTimers(); } // dsyslog("live: SV: signalling waiters"); m_updateWait.Broadcast(); } void TimerManager::DoUpdateTimers() { // dsyslog("live: SV: updating timers"); for ( TimerList::iterator timer = m_updateTimers.begin(); timer != m_updateTimers.end(); ++timer ) { // dsyslog("live: DoUpdateTimers() timerid '%d'", timer->id ); // dsyslog("live: DoUpdateTimers() remote '%s'", timer->remote ); // dsyslog("live: DoUpdateTimers() builder '%s'", timer->builder.c_str() ); if ( timer->id == 0 ) // new timer DoInsertTimer( *timer ); else if ( timer->builder == TIMER_DELETE ) // delete timer DoDeleteTimer( *timer ); else if ( timer->builder == TIMER_TOGGLE ) // toggle timer DoToggleTimer( *timer ); else // update timer DoUpdateTimer( *timer ); } m_updateTimers.clear(); } void TimerManager::DoInsertTimer( timerStruct& timerData ) { if ( timerData.remote ) { // add remote timer via svdrpsend dsyslog("live: DoInsertTimer() add remote timer on server '%s'", timerData.remote); cStringList response; std::string command = "NEWT "; command.append(timerData.builder); dsyslog("live: DoInsertTimer() svdrp command '%s'", command.c_str()); bool svdrpOK = ExecSVDRPCommand(timerData.remote, command.c_str(), &response); if ( !svdrpOK ) { esyslog("live: svdrp command on remote server %s failed", timerData.remote); } else { for (int i = 0; i < response.Size(); i++) { int code = SVDRPCode(response[i]); if (code != 250) { esyslog("live: DoInsertTimer() svdrp failed, respone: %s", response[i]); svdrpOK = false; } } if ( svdrpOK ) { isyslog("live: remote timer '%s' on server '%s' added", timerData.builder.c_str(), timerData.remote); } else { dsyslog("live: TimerManager::DoInsertTimer(): error in settings for remote timer"); StoreError(timerData, tr("Error in timer settings")); } } response.Clear(); } else { // add local timer std::unique_ptr newTimer( new cTimer ); if ( !newTimer->Parse( timerData.builder.c_str() ) ) { dsyslog("live: TimerManager::DoInsertTimer(): error in settings for local timer"); StoreError( timerData, tr("Error in timer settings") ); return; } #if VDRVERSNUM >= 20301 dsyslog("live: DoInsertTimer() add local timer"); #ifdef DEBUG_LOCK dsyslog("live: timers.cpp TimerManager::DoInsertTimer() LOCK_TIMERS_WRITE"); #endif LOCK_TIMERS_WRITE; Timers->SetExplicitModify(); const cTimer *checkTimer = Timers->GetTimer( newTimer.get() ); #else const cTimer* checkTimer = Timers.GetTimer( newTimer.get() ); #endif if ( checkTimer ) { StoreError( timerData, tr("Timer already defined") ); return; } #if VDRVERSNUM >= 20301 Timers->Add( newTimer.get() ); Timers->SetModified(); #else Timers.Add( newTimer.get() ); Timers.SetModified(); #endif isyslog( "live: local timer %s added", *newTimer->ToDescr() ); newTimer.release(); } } void TimerManager::DoUpdateTimer( timerStruct& timerData ) { dsyslog("live: DoUpdateTimer() timerid '%d'", timerData.id ); dsyslog("live: DoUpdateTimer() remote '%s'", timerData.remote ); dsyslog("live: DoUpdateTimer() oldRemote '%s'", timerData.oldRemote ); dsyslog("live: DoUpdateTimer() builder '%s'", timerData.builder.c_str() ); if ( timerData.remote && timerData.oldRemote ) { // old and new are remote if ( timerData.remote == timerData.oldRemote ) { // timer stays on the same remote server dsyslog("live: DoUptimer() update timer on remote server '%s'", timerData.remote); cStringList response; std::string command = "MODT "; command.append(std::to_string(timerData.id)); command.append(" "); command.append(timerData.builder); dsyslog("live: DoUpdateTimer() svdrp command '%s'", command.c_str()); bool svdrpOK = ExecSVDRPCommand(timerData.remote, command.c_str(), &response); if ( !svdrpOK ) { esyslog("live: svdr command on remote server %s failed", timerData.remote); } else { bool responseOK = true; for (int i = 0; i < response.Size(); i++) { int code = SVDRPCode(response[i]); if (code != 250) { esyslog("live: DoInsertTimer() svdrp respone: %s", response[i]); responseOK = false; } } if ( responseOK ) { isyslog("live: remote timer '%s' on server '%s' updated", command.c_str(), timerData.remote); } else { StoreError(timerData, tr("Error in timer settings")); } } response.Clear(); } else { // timer moves from one remote server to another isyslog("live: DoUptimer() move timer from remote server '%s' to remote server '%s'", timerData.oldRemote, timerData.remote); timerStruct timerDataMove = { .id = timerData.id, .remote=timerData.oldRemote, .oldRemote=timerData.oldRemote, .builder=timerData.builder}; DoDeleteTimer( timerDataMove ); timerDataMove = { .id = timerData.id, .remote=timerData.remote, .oldRemote=timerData.remote, .builder=timerData.builder}; DoInsertTimer( timerDataMove ); } } else if ( timerData.remote && !timerData.oldRemote ) { // move timer from local to remote dsyslog("live: DoUpdateTimer() move timer from local to remote server"); timerStruct timerDataMove = { .id = timerData.id, .remote=NULL, .oldRemote=NULL, .builder=timerData.builder}; DoDeleteTimer( timerDataMove ); timerDataMove = { .id = timerData.id, .remote=timerData.remote, .oldRemote=timerData.remote, .builder=timerData.builder}; DoInsertTimer( timerDataMove ); } else if ( !timerData.remote && timerData.oldRemote ) { // move timer from remote to local dsyslog("live: DoUpdateTimer() move timer from remote server to local"); timerStruct timerDataMove = { .id = timerData.id, .remote=timerData.oldRemote, .oldRemote=timerData.oldRemote, .builder=timerData.builder}; DoDeleteTimer( timerDataMove ); timerDataMove = { .id = timerData.id, .remote=NULL, .oldRemote=NULL, .builder=timerData.builder}; DoInsertTimer( timerDataMove ); } else { // old and new are local dsyslog("live: DoUpdateTimer() old and new timer are local"); #if VDRVERSNUM >= 20301 #ifdef DEBUG_LOCK dsyslog("live: timers.cpp TimerManager::DoUpdateTimer() LOCK_TIMERS_WRITE"); #endif LOCK_TIMERS_WRITE; Timers->SetExplicitModify(); cTimer* oldTimer = Timers->GetById( timerData.id, timerData.oldRemote ); dsyslog("live: DoUpdateTimer() change local timer '%s'", *oldTimer->ToDescr()); #else cTimer* oldTimer = Timers.GetTimer( const_cast(timerData.first) ); #endif if ( oldTimer == 0 ) { StoreError( timerData, tr("Timer not defined") ); return; } #if VDRVERSNUM < 20301 if ( Timers.BeingEdited() ) { StoreError( timerData, tr("Timers are being edited - try again later") ); return; } #endif cTimer copy = *oldTimer; if ( !copy.Parse( timerData.builder.c_str() ) ) { StoreError( timerData, tr("Error in timer settings") ); return; } *oldTimer = copy; #if VDRVERSNUM >= 20301 Timers->SetModified(); #else Timers.SetModified(); #endif isyslog("live: local timer %s modified (%s)", *oldTimer->ToDescr(), oldTimer->HasFlags(tfActive) ? "active" : "inactive"); } } void TimerManager::DoDeleteTimer( timerStruct& timerData ) { dsyslog("live: DoDeleteTimer() timerid '%d'", timerData.id ); dsyslog("live: DoDeleteTimer() remote '%s'", timerData.remote ); dsyslog("live: DoDeleteTimer() builder '%s'", timerData.builder.c_str() ); if ( timerData.remote ) { // delete remote timer via svdrpsend dsyslog("live: DoDeleteTimer() delete remote timer id '%d' from server '%s'", timerData.id, timerData.remote); cStringList response; std::string command = "DELT "; command.append(std::to_string(timerData.id)); bool svdrpOK = ExecSVDRPCommand(timerData.remote, command.c_str(), &response); if ( !svdrpOK ) { esyslog( "live: delete remote timer id %d failed", timerData.id); } else { for (int i = 0; i < response.Size(); i++) { int code = SVDRPCode(response[i]); if (code != 250) { esyslog("live: DoDeleteTimer() svdrp failed, respone: %s", response[i]); svdrpOK = false; } } if ( svdrpOK ) { isyslog("live: remote timer '%s' on server '%s' deleted", command.c_str(), timerData.remote); } else { StoreError(timerData, tr("Error in timer settings")); } } response.Clear(); } else { // delete local timer dsyslog("live: DoDeleteTimer() delete local timer id '%d'", timerData.id); #if VDRVERSNUM < 20301 if ( Timers.BeingEdited() ) { StoreError( timerData, tr("Timers are being edited - try again later") ); return; } #endif #if VDRVERSNUM >= 20301 #ifdef DEBUG_LOCK dsyslog("live: timers.cpp TimerManager::DoDeleteTimer() LOCK_TIMERS_WRITE"); #endif LOCK_TIMERS_WRITE; Timers->SetExplicitModify(); cTimer* oldTimer = Timers->GetById( timerData.id, timerData.remote ); #else cTimer* oldTimer = Timers.GetTimer( const_cast(timerData.first) ); #endif if ( oldTimer == 0 ) { StoreError( timerData, tr("Timer not defined") ); return; } cTimer copy = *oldTimer; if ( oldTimer->Recording() ) { oldTimer->Skip(); #if VDRVERSNUM >= 20301 cRecordControls::Process( Timers, time( 0 ) ); #else cRecordControls::Process( time( 0 ) ); #endif } #if VDRVERSNUM >= 20301 Timers->Del( oldTimer ); Timers->SetModified(); #else Timers.Del( oldTimer ); Timers.SetModified(); #endif isyslog("live: local timer %s deleted", *copy.ToDescr()); } } void TimerManager::DoToggleTimer( timerStruct& timerData ) { if ( timerData.remote ) { // toggle remote timer via svdrpsend #ifdef DEBUG_LOCK dsyslog("live: timers.cpp TimerManager::DoToggleTimer() LOCK_TIMERS_READ"); #endif LOCK_TIMERS_READ; const cTimer* toggleTimer = Timers->GetById( timerData.id, timerData.remote ); std::string command = "MODT "; command.append(std::to_string(timerData.id)); if (toggleTimer->HasFlags(tfActive)) { dsyslog("live: DoToggleTimer() timer is active"); command.append("off "); } else { dsyslog("live: DoToggleTimer() timer is not active"); command.append("on "); } cStringList response; dsyslog("live: DoToggleTimer svdrp command '%s'", command.c_str()); bool svdrpOK = ExecSVDRPCommand(timerData.remote, command.c_str(), &response); if ( !svdrpOK ) { esyslog("live: svdr command on remote server %s failed", timerData.remote); } else { for (int i = 0; i < response.Size(); i++) { int code = SVDRPCode(response[i]); if (code != 250) { esyslog("live: DoToggleTimer() svdrp failed, respone: %s", response[i]); svdrpOK = false; } } if ( svdrpOK ) { isyslog("live: remote timer '%s' on server '%s' toggled", command.c_str(), timerData.remote); } else { StoreError(timerData, tr("Error in timer settings")); } } response.Clear(); } else { // toggle local timer #if VDRVERSNUM < 20301 if ( Timers.BeingEdited() ) { StoreError( timerData, tr("Timers are being edited - try again later") ); return; } #endif #if VDRVERSNUM >= 20301 #ifdef DEBUG_LOCK dsyslog("live: timers.cpp TimerManager::DoToggleTimer() LOCK_TIMERS_WRITE"); #endif LOCK_TIMERS_WRITE; Timers->SetExplicitModify(); cTimer* toggleTimer = Timers->GetById( timerData.id, timerData.remote ); #else cTimer* toggleTimer = Timers.GetTimer( const_cast(timerData.first) ); #endif if ( toggleTimer == 0 ) { StoreError( timerData, tr("Timer not defined") ); return; } #if VDRVERSNUM >= 20301 toggleTimer->OnOff(); Timers->SetModified(); #else toggleTimer->OnOff(); Timers.SetModified(); #endif isyslog("live: timer %s toggled %s", *toggleTimer->ToDescr(), toggleTimer->HasFlags(tfActive) ? "on" : "off"); } } void TimerManager::StoreError( timerStruct const& timerData, std::string const& error ) { m_failedUpdates.push_back( ErrorPair( timerData, error ) ); } std::string TimerManager::GetError( timerStruct const& timerData ) { for ( ErrorList::iterator error = m_failedUpdates.begin(); error != m_failedUpdates.end(); ++error ) { if ( error->first.id == timerData.id && error->first.remote == timerData.remote && error->first.oldRemote == timerData.oldRemote && error->first.builder == timerData.builder ) { std::string message = error->second; m_failedUpdates.erase( error ); return message; } } return ""; } const cTimer* TimerManager::GetTimer(tEventID eventid, tChannelID channelid) { cMutexLock timersLock( &LiveTimerManager() ); #ifdef DEBUG_LOCK dsyslog("live: timers.cpp TimerManager::GetTimer() LOCK_TIMERS_READ"); #endif LOCK_TIMERS_READ; for (cTimer* timer = (cTimer *)Timers->First(); timer; timer = (cTimer *)Timers->Next(timer)) { if (timer->Channel() && timer->Channel()->GetChannelID() == channelid) { if (timer->Event() && timer->Event()->EventID() == eventid) return &*timer; } } return NULL; } TimerManager& LiveTimerManager() { static TimerManager instance; return instance; } } // namespace vdrlive vdr-plugin-live-3.1.3/timers.h000066400000000000000000000057531414414333500162320ustar00rootroot00000000000000#ifndef VDR_LIVE_TIMERS_H #define VDR_LIVE_TIMERS_H // STL headers need to be before VDR tools.h (included by ) #include #include #if TNTVERSION >= 30000 #include // must be loaded before any vdr include because of duplicate macros (LOG_ERROR, LOG_DEBUG, LOG_INFO) #endif #include namespace vdrlive { class SortedTimers: public std::list { friend class TimerManager; public: static std::string GetTimerId(cTimer const& timer); const cTimer* GetByTimerId(std::string const& timerid); // en- or decodes a timer into an id usable for DOM Ids. static std::string EncodeDomId(std::string const& timerid); static std::string DecodeDomId(std::string const &timerDomId); #if VDRVERSNUM >= 20301 bool Modified(); #else bool Modified() { return Timers.Modified(m_state); } #endif static std::string GetTimerDays(cTimer const *timer); static std::string GetTimerInfo(cTimer const& timer); static std::string SearchTimerInfo(cTimer const& timer, std::string const& value); private: SortedTimers(); SortedTimers( SortedTimers const& ); cMutex m_mutex; #if VDRVERSNUM >= 20301 cStateKey m_TimersStateKey; #else int m_state; #endif }; class TimerManager: public cMutex { friend TimerManager& LiveTimerManager(); public: SortedTimers& GetTimers() { return m_timers; } void UpdateTimer( int timerId, const char* remote, const char* oldRemote, int flags, const tChannelID& channel, std::string const& weekdays, std::string const& day, int start, int stop, int priority, int lifetime, std::string const& title, std::string const& aux ); void DelTimer( int timerId, const char* remote); void ToggleTimerActive( int timerId, const char* remote); // may only be called from Plugin::MainThreadHook void DoPendingWork(); const cTimer* GetTimer(tEventID eventid, tChannelID channelid); void SetReloadTimers() { m_reloadTimers = true; } private: typedef struct { int id; const char* remote; const char* oldRemote; std::string builder; } timerStruct; typedef std::pair ErrorPair; typedef std::list TimerList; typedef std::list ErrorList; TimerManager(); TimerManager( TimerManager const& ); SortedTimers m_timers; TimerList m_updateTimers; ErrorList m_failedUpdates; cCondVar m_updateWait; bool m_reloadTimers = false; void DoUpdateTimers(); void DoInsertTimer( timerStruct& timerData ); void DoUpdateTimer( timerStruct& timerData ); void DoDeleteTimer( timerStruct& timerData ); void DoToggleTimer( timerStruct& timerData ); void StoreError( timerStruct const& timerData, std::string const& error ); std::string GetError( timerStruct const& timerData ); }; TimerManager& LiveTimerManager(); } // namespace vdrlive #endif // VDR_LIVE_TIMERS_H vdr-plugin-live-3.1.3/tntconfig.cpp000066400000000000000000000204411414414333500172440ustar00rootroot00000000000000 #include "tntconfig.h" #include "i18n.h" #include "live.h" #include "setup.h" #if TNT_LOG_SERINFO #include #include #else #include #endif namespace vdrlive { TntConfig::TntConfig() { } namespace { std::string GetResourcePath() { std::string resourceDir(Plugin::GetResourceDirectory()); return resourceDir; } void MapUrl(tnt::Tntnet & app, const char *rule, const char * component, std::string const & instPath, const char * pathInfo, const char * mime_type) { #if TNT_MAPURL_NAMED_ARGS tnt::Mapping::args_type argMap; argMap.insert(std::make_pair("mime-type", mime_type)); #endif app.mapUrl(rule, component) .setPathInfo(instPath + pathInfo) #if TNT_MAPURL_NAMED_ARGS .setArgs(argMap); #else .pushArg(mime_type); #endif } } void TntConfig::Configure(tnt::Tntnet& app) const { std::string const configDir(Plugin::GetConfigDirectory()); #if TNT_LOG_SERINFO cxxtools::SerializationInfo si; std::istringstream logXmlConf( "\n" " " + LiveSetup().GetTntnetLogLevel() + "\n" " \n" " \n" " cxxtools\n" " " + LiveSetup().GetTntnetLogLevel() + "\n" " \n" " \n" " tntnet\n" " " + LiveSetup().GetTntnetLogLevel() + "\n" " \n" " \n" "\n" ); cxxtools::xml::XmlDeserializer d(logXmlConf); d.deserialize(si); log_init(si); #else std::istringstream logConf( "rootLogger=" + LiveSetup().GetTntnetLogLevel() + "\n" "logger.tntnet=" + LiveSetup().GetTntnetLogLevel() + "\n" "logger.cxxtools=" + LiveSetup().GetTntnetLogLevel() + "\n" ); log_init(logConf); #endif // +++ CAUTION +++ CAUTION +++ CAUTION +++ CAUTION +++ CAUTION +++ // ------------------------------------------------------------------------ // These mapUrl statements are very security sensitive! // A wrong mapping to content may allow retrieval of arbitrary files // from your VDR system via live. // Two meassures are taken against this in our implementation: // 1. The MapUrls need to be checked regulary against possible exploits // One tool to do this can be found here: // http://www.lumadis.be/regex/test_regex.php // Newly inserted MapUrls should be marked with author and confirmed // by a second party. (use source code comments for this) // 2. content.ecpp checks the given path to be // a. an absolute path starting at / // b. not containing ../ paths components // In order to do so, the MapUrl statements must create absolute // path arguments to content@ // ------------------------------------------------------------------------ // +++ CAUTION +++ CAUTION +++ CAUTION +++ CAUTION +++ CAUTION +++ app.mapUrl("^/$", "login"); // the following redirects vdr_request URL to the component // specified by the action parameter. // inserted by 'tadi' -- verified with above, but not counterchecked yet! app.mapUrl("^/vdr_request/([^.]+)", "$1"); // the following redirects play_video URL to the content component. // inserted by 'tadi' -- not verified, not counterchecked yet! //app.mapUrl("^/vlc/(.+)", "static@tntnet") // .setPathInfo("/$1") // .pushArg(string("DocumentRoot=") + VideoDirectory); // the following selects the theme specific 'theme.css' file // inserted by 'tadi' -- verified with above, but not counterchecked yet! MapUrl(app, "^/themes/([^/]*)/css.*/(.+\\.css)", "content", GetResourcePath(), "/themes/$1/css/$2", "text/css"); // the following rules provide a search scheme for images. The first // rule where a image is found, terminates the search. // 1. /themes//img/. // 2. /img/. // deprecated: 3. . (builtin images) // inserted by 'tadi' -- verified with above, but not counterchecked yet! MapUrl(app, "^/themes/([^/]*)/img.*/(.+)\\.(.+)", "content", GetResourcePath(), "/themes/$1/img/$2.$3", "image/$3"); MapUrl(app, "^/themes/([^/]*)/img.*/(.+)\\.(.+)", "content", GetResourcePath(), "/img/$2.$3", "image/$3"); // deprecated: file << "MapUrl ^/themes/([^/]*)/img.*/(.+)\\.(.+) $2@" << std::endl; if (!LiveSetup().GetTvscraperImageDir().empty()) { // Epg images from tvscrapper: Movies MapUrl(app, "^/tvscraper/movies/([^/]*)\\.([^./]+)", "content", LiveSetup().GetTvscraperImageDir() + "movies", "/$1.$2", "image/$2"); // Epg images from tvscrapper: Series MapUrl(app, "^/tvscraper/series/([^/]*)/([^/]*)\\.([^./]+)", "content", LiveSetup().GetTvscraperImageDir() + "series", "/$1/$2.$3", "image/$3"); } // Epg images std::string const epgImgPath(LiveSetup().GetEpgImageDir()); if (!epgImgPath.empty()) { // inserted by 'tadi' -- verified with above, but not counterchecked yet! MapUrl(app, "^/epgimages/([^/]*)\\.([^./]+)", "content", epgImgPath, "/$1.$2", "image/$2"); } // rec images MapUrl(app, "^/recimages/([^/]*)/([^/]*)\\.([^./]+)", "content", "", "/tmp/$1_$2.$3", "image/$3"); // select additional (not build in) javascript. // WARNING: no path components with '.' in the name are allowed. Only // the basename may contain dots and must end with '.js' // inserted by 'tadi' -- verified with above, but not counterchecked yet! MapUrl(app, "^/js(/[^.]*)([^/]*\\.js)", "content", GetResourcePath(), "/js$1$2", "text/javascript"); // map to 'css/basename(uri)' // inserted by 'tadi' -- verified with above, but not counterchecked yet! MapUrl(app, "^/css.*/(.+)", "content", GetResourcePath(), "/css/$1", "text/css"); // map to 'img/basename(uri)' // inserted by 'tadi' -- verified with above, but not counterchecked yet! MapUrl(app, "^/img.*/(.+)\\.([^.]+)", "content", GetResourcePath(), "/img/$1.$2", "image/$2"); // Map favicon.ico into img directory MapUrl(app, "^/favicon.ico$", "content", GetResourcePath(), "/img/favicon.ico", "image/x-icon"); // Map HLS streaming data folder. Module stream_data.ecpp is used to serve content. app.mapUrl("^/media/(.+)", "stream_data"); // takes first path components without 'extension' when it does not // contain '.' // modified by 'tadi' -- verified with above, but not counterchecked yet! app.mapUrl("^/([^./]+)(.*)?", "$1"); #if TNT_GLOBAL_TNTCONFIG tnt::TntConfig::it().sessionTimeout = 86400; tnt::TntConfig::it().defaultContentType = std::string("text/html; charset=") + LiveI18n().CharacterEncoding(); #else tnt::Sessionscope::setDefaultTimeout(86400); tnt::HttpReply::setDefaultContentType(std::string("text/html; charset=") + LiveI18n().CharacterEncoding()); #endif Setup::IpList const& ips = LiveSetup().GetServerIps(); int port = LiveSetup().GetServerPort(); size_t listenFailures = 0; for (Setup::IpList::const_iterator ip = ips.begin(); ip != ips.end(); ++ip) { try { esyslog("live: INFO: attempt to listen on ip = '%s'", ip->c_str()); app.listen(*ip, port); } catch (std::exception const & ex) { esyslog("live: ERROR: ip = %s is invalid: exception = %s", ip->c_str(), ex.what()); if (++listenFailures == ips.size()) { // if no listener was initialized we throw at // least the last exception to the next layer. throw; } } } int s_port = LiveSetup().GetServerSslPort(); std::string s_cert = LiveSetup().GetServerSslCert(); std::string s_key = LiveSetup().GetServerSslKey(); if (s_cert.empty()) { s_cert = configDir + "/live.pem"; } if (s_key.empty()) { s_key = configDir + "/live-key.pem"; } if (std::ifstream( s_cert.c_str() ) && std::ifstream( s_key.c_str() ) ) { for ( Setup::IpList::const_iterator ip = ips.begin(); ip != ips.end(); ++ip ) { app.sslListen(s_cert, s_key, *ip, s_port); } } else { esyslog( "live: ERROR: Unable to load cert/key (%s / %s): %s", s_cert.c_str(), s_key.c_str(), strerror( errno ) ); } } TntConfig const& TntConfig::Get() { static TntConfig instance; return instance; } } // namespace vdrlive vdr-plugin-live-3.1.3/tntconfig.h000066400000000000000000000006351414414333500167140ustar00rootroot00000000000000#ifndef VDR_LIVE_TNTCONFIG_H #define VDR_LIVE_TNTCONFIG_H #if TNTVERSION >= 30000 #include #endif #include "tntfeatures.h" #include namespace vdrlive { class TntConfig { public: static TntConfig const& Get(); void Configure(tnt::Tntnet& app) const; private: TntConfig(); TntConfig( TntConfig const& ); }; } // namespace vdrlive #endif // VDR_LIVE_TNTCONFIG_H vdr-plugin-live-3.1.3/tntfeatures.h000066400000000000000000000022111414414333500172550ustar00rootroot00000000000000#ifndef VDR_LIVE_TNTFEATURES_H #define VDR_LIVE_TNTFEATURES_H // This header mapps tntversion strings, whose 'structure' changes over time, // to features of tntnet used in the live plugin. This avoids scattering the // version check for TNTVERSION over several source files in live. Thus when // an other change in the structure of the version string was needed then only // this file needs to be adapted. // Query params without boolean parameter #define TNT_QUERYPARAMS_NO_BOOL (TNTVERSION >= 22000) // new version of TNTNET allow the request watchdog to be silenced. #define TNT_WATCHDOG_SILENCE (TNTVERSION >= 16900) // version of TNTNET that binds ipv6 addresses with IPV6_V6ONLY flag set to true #define TNT_IPV6_V6ONLY (CXXTOOLVER >= 21000) // version of TNTNET with properties deserializer for logger configuration args. #define TNT_LOG_SERINFO (CXXTOOLVER >= 22000) // version of TNTNET wich expects name, value mappings for Url-Mapper arguments. #define TNT_MAPURL_NAMED_ARGS (TNTVERSION >= 22000) // version of TNTNET where configuration is global #define TNT_GLOBAL_TNTCONFIG (TNTVERSION >= 22000) #endif // VDR_LIVE_TNTFEATURES_H vdr-plugin-live-3.1.3/tools.cpp000066400000000000000000000514661414414333500164240ustar00rootroot00000000000000 #include "tools.h" #include "md5.h" #include #include // STL headers need to be before VDR tools.h (included by ) #include #include #include using namespace tnt; std::istream& operator>>( std::istream& is, tChannelID& ret ) { std::string line; if (!std::getline( is, line ) ) { if (0 == is.gcount()) { is.clear(is.rdstate() & ~std::ios::failbit); return is; } if (!is.eof()) { is.setstate( std::ios::badbit ); return is; } } if ( !line.empty() && !( ret = tChannelID::FromString( line.c_str() ) ).Valid() ) is.setstate( std::ios::badbit ); return is; } namespace vdrlive { void AppendHtmlEscaped(std::string &target, const char* s){ // append c-string s to target, html escape some chsracters if(!s) return; size_t i = 0; const char* notAppended = s; // moving forward, notAppended is the position of the first character which is not yet appended, in i the number of not yet appended chars for (const char* current = s; *current; current++) { switch(*current) { case '&': target.append(notAppended, i); target.append("&"); notAppended = notAppended + i + 1; i = 0; break; case '\"': target.append(notAppended, i); target.append("""); notAppended = notAppended + i + 1; i = 0; break; case '\'': target.append(notAppended, i); target.append("'"); notAppended = notAppended + i + 1; i = 0; break; case '<': target.append(notAppended, i); target.append("<"); notAppended = notAppended + i + 1; i = 0; break; case '>': target.append(notAppended, i); target.append(">"); notAppended = notAppended + i + 1; i = 0; break; default: i++; break; } } target.append(notAppended, i); } void AppendHtmlEscapedAndCorrectNonUTF8(std::string &target, const char* s){ // append c-string s to target, html escape some chsracters // replace invalid UTF8 characters with ? if(!s) return; int l = 0; // length of current utf8 codepoint size_t i = 0; // number of not yet appended chars const char* notAppended = s; // position of the first character which is not yet appended for (const char* current = s; *current; current+=l) { l = utf8CodepointIsValid(current); switch(l) { case 1: switch(*current) { case '&': target.append(notAppended, i); target.append("&"); notAppended = notAppended + i + 1; i = 0; break; case '\"': target.append(notAppended, i); target.append("""); notAppended = notAppended + i + 1; i = 0; break; case '\'': target.append(notAppended, i); target.append("'"); notAppended = notAppended + i + 1; i = 0; break; case '<': target.append(notAppended, i); target.append("<"); notAppended = notAppended + i + 1; i = 0; break; case '>': target.append(notAppended, i); target.append(">"); notAppended = notAppended + i + 1; i = 0; break; default: i++; break; } break; case 2: case 3: case 4: i += l; break; default: // invalid UTF8 target.append(notAppended, i); target.append("?"); notAppended = notAppended + i + 1; i = 0; l = 1; } } target.append(notAppended, i); } void AppendCorrectNonUTF8(std::string &target, const char* s){ // append c-string s to target // replace invalid UTF8 characters with ? if(!s) return; int l = 0; // length of current utf8 codepoint size_t i = 0; // number of not yet appended chars const char* notAppended = s; // position of the first character which is not yet appended for (const char* current = s; *current; current+=l) { l = utf8CodepointIsValid(current); if( l > 0) { i += l; continue; } // invalid UTF8 target.append(notAppended, i); target.append("?"); notAppended = notAppended + i + 1; i = 0; l = 1; } target.append(notAppended, i); } wint_t getNextUtfCodepoint(const char *&p){ // get next codepoint, and increment p // 0 is returned at end of string, and p will point to the end of the string (0) if(!p || !*p) return 0; // do { l = utf8CodepointIsValid(p); } while ( l == 0 && *(++p)); int l = utf8CodepointIsValid(p); if( l == 0 ) { p++; return '?'; } return Utf8ToUtf32(p, l); } int utf8CodepointIsValid(const char *p){ // In case of invalid UTF8, return 0 // otherwise, return number of characters for this UTF codepoint static const uint8_t LEN[] = {2,2,2,2,3,3,4,0}; int len = ((*p & 0xC0) == 0xC0) * LEN[(*p >> 3) & 7] + ((*p | 0x7F) == 0x7F); for (int k=1; k < len; k++) if ((p[k] & 0xC0) != 0x80) len = 0; return len; } wint_t Utf8ToUtf32(const char *&p, int len){ // assumes, that uft8 validity checks have already been done. len must be provided. call utf8CodepointIsValid first // change p to position of next codepoint (p = p + len) static const uint8_t FF_MSK[] = {0xFF >>0, 0xFF >>0, 0xFF >>3, 0xFF >>4, 0xFF >>5, 0xFF >>0, 0xFF >>0, 0xFF >>0}; wint_t val = *p & FF_MSK[len]; const char *q = p + len; for (p++; p < q; p++) val = (val << 6) | (*p & 0x3F); return val; } void AppendUtfCodepoint(std::string &target, wint_t codepoint){ if (codepoint <= 0x7F){ target.push_back( (char) (codepoint) ); return; } if (codepoint <= 0x07FF) { target.push_back( (char) (0xC0 | (codepoint >> 6 ) ) ); target.push_back( (char) (0x80 | (codepoint & 0x3F)) ); return; } if (codepoint <= 0xFFFF) { target.push_back( (char) (0xE0 | ( codepoint >> 12)) ); target.push_back( (char) (0x80 | ((codepoint >> 6) & 0x3F)) ); target.push_back( (char) (0x80 | ( codepoint & 0x3F)) ); return; } target.push_back( (char) (0xF0 | ((codepoint >> 18) & 0x07)) ); target.push_back( (char) (0x80 | ((codepoint >> 12) & 0x3F)) ); target.push_back( (char) (0x80 | ((codepoint >> 6) & 0x3F)) ); target.push_back( (char) (0x80 | ( codepoint & 0x3F)) ); return; } void AppendDuration( std::string &target, char const* format, int hours, int minutes ) { char result[ 32 ]; if ( snprintf(result, sizeof(result), format, hours, minutes) < 0 ) { std::stringstream builder; builder << "cannot represent duration " << hours << ":" << minutes << " as requested"; throw std::runtime_error( builder.str() ); } target.append(result); } std::string FormatDuration( char const* format, int hours, int minutes ) { std::string result; AppendDuration(result, format, hours, minutes ); return result; } void AppendDateTime(std::string &target, char const* format, time_t time ) { struct tm tm_r; if ( localtime_r( &time, &tm_r ) == 0 ) { std::stringstream builder; builder << "cannot represent timestamp " << time << " as local time"; throw std::runtime_error( builder.str() ); } char result[ 256 ]; if ( strftime( result, sizeof( result ), format, &tm_r ) == 0 ) { std::stringstream builder; builder << "representation of timestamp " << time << " exceeds " << sizeof( result ) << " bytes"; throw std::runtime_error( builder.str() ); } target.append(result); } std::string FormatDateTime( char const* format, time_t time ) { std::string result; AppendDateTime(result, format, time ); return result; } std::string StringReplace( std::string const& text, std::string const& substring, std::string const& replacement ) { std::string result = text; size_t pos = 0; while ( ( pos = result.find( substring, pos ) ) != std::string::npos ) { result.replace( pos, substring.length(), replacement ); pos += replacement.length(); } return result; } std::vector StringSplit( std::string const& text, char delimiter ) { std::vector result; size_t last = 0, pos; while ( ( pos = text.find( delimiter, last ) ) != std::string::npos ) { result.push_back( text.substr( last, pos - last ) ); last = pos + 1; } if ( last < text.length() ) result.push_back( text.substr( last ) ); return result; } int StringToInt( std::string const& string, int base ) { char* end; int result = strtol( string.c_str(), &end, base ); if ( *end == '\0' ) return result; return 0; } std::string StringRepeat(int times, const std::string& input) { std::string result; for (int i = 0; i < times; i++) { result += input; } return result; } std::string StringWordTruncate(const std::string& input, size_t maxLen, bool& truncated) { if (input.length() <= maxLen) { truncated = false; return input; } truncated = true; std::string result = input.substr(0, maxLen); size_t pos = result.find_last_of(" \t,;:.\n?!'\"/\\()[]{}*+-"); return result.substr(0, pos); } std::string StringFormatBreak(std::string const& input) { return StringReplace( input, "\n", "
    " ); } std::string StringEscapeAndBreak( std::string const& input ) { std::stringstream plainBuilder; HtmlEscOstream builder( plainBuilder ); builder << input; return StringReplace( plainBuilder.str(), "\n", "
    " ); } std::string StringTrim(std::string const& str) { std::string res = str; size_t pos = res.find_last_not_of(' '); if(pos != std::string::npos) { res.erase(pos + 1); pos = res.find_first_not_of(' '); if(pos != std::string::npos) res.erase(0, pos); } else res.erase(res.begin(), res.end()); return res; } std::string ZeroPad(int number) { std::stringstream os; os << std::setw(2) << std::setfill('0') << number; return os.str(); } std::string MD5Hash(std::string const& str) { char* szInput = strdup(str.c_str()); if (!szInput) return ""; char* szRes = MD5String(szInput); std::string res = szRes; free(szRes); return res; /* unsigned char md5[MD5_DIGEST_LENGTH]; MD5(reinterpret_cast(str.c_str()), str.size(), md5); std::stringstream hashStr; hashStr << std::hex; for (size_t i = 0; i < MD5_DIGEST_LENGTH; i++) hashStr << (0 + md5[i]); return hashStr.str(); */ } #define HOURS(x) ((x)/100) #define MINUTES(x) ((x)%100) std::string ExpandTimeString(std::string timestring) { size_t colonpos = timestring.find(":"); if (colonpos == std::string::npos) { if (timestring.size() == 1) timestring = "0" + timestring + ":00"; else if (timestring.size() == 2) timestring = timestring + ":00"; else if (timestring.size() == 3) timestring = "0" + std::string(timestring.begin(), timestring.begin() + 1) + ":" + std::string(timestring.begin() + 1, timestring.end()); else timestring = std::string(timestring.begin(), timestring.begin() + 2) + ":" + std::string(timestring.begin() + 2, timestring.end()); } else { std::string hours = std::string(timestring.begin(), timestring.begin() + colonpos); std::string mins = std::string(timestring.begin() + colonpos + 1, timestring.end()); hours = std::string(std::max(0,(int)(2 - hours.size())), '0') + hours; mins = std::string(std::max(0,(int)(2 - mins.size())), '0') + mins; timestring = hours + ":" + mins; } return timestring; } time_t GetTimeT(std::string timestring) // timestring in HH:MM { timestring = StringReplace(timestring, ":", ""); int iTime = lexical_cast( timestring ); struct tm tm_r; time_t t = time(NULL); tm* tmnow = localtime_r(&t, &tm_r); tmnow->tm_hour = HOURS(iTime); tmnow->tm_min = MINUTES(iTime); return mktime(tmnow); } struct urlencoder { std::ostream& ostr_; explicit urlencoder( std::ostream& ostr ): ostr_( ostr ) {} void operator()( char ch ) { static const char allowedChars[] = { // 0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , A , B , C , D , E , F , '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', //00 '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', //10 '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', 0x2D,0x2E,0x2F, //20 0x30,0x31,0x32,0x33,0x34,0x35,0x36,0x37,0x38,0x39,'_', '_', '_', '_', '_', '_', //30 '_', 0x41,0x42,0x43,0x44,0x45,0x46,0x47,0x48,0x49,0x4A,0x4B,0x4C,0x4D,0x4E,0x4F, //40 0x50,0x51,0x52,0x53,0x54,0x55,0x56,0x57,0x58,0x59,0x5A,'_', '_', '_', '_', '_', //50 '_', 0x61,0x62,0x63,0x64,0x65,0x66,0x67,0x68,0x69,0x6A,0x6B,0x6C,0x6D,0x6E,0x6F, //60 0x70,0x71,0x72,0x73,0x74,0x75,0x76,0x77,0x78,0x79,0x7A,'_', '_', '_', '_', '_' //70 // everything above 127 (for signed char, below zero) is replaced with '_' }; if ( ch == ' ' ) ostr_ << '+'; else if ( static_cast(ch) < 0 || allowedChars[ size_t( ch ) ] == '_' ) ostr_ << '%' << std::setw( 2 ) << std::setfill( '0' ) << std::hex << int( static_cast(ch) ); else ostr_ << ch; } }; std::string StringUrlEncode( std::string const& input ) { std::stringstream ostr; for_each( input.begin(), input.end(), urlencoder( ostr ) ); return ostr.str(); } // returns the content of ... std::string GetXMLValue( std::string const& xml, std::string const& element ) { std::string start = "<" + element + ">"; std::string end = ""; size_t startPos = xml.find(start); if (startPos == std::string::npos) return ""; size_t endPos = xml.find(end); if (endPos == std::string::npos) return ""; return xml.substr(startPos + start.size(), endPos - startPos - start.size()); } // return the time value as time_t from formatted with time_t GetDateFromDatePicker(std::string const& datestring, std::string const& format) { if (datestring.empty()) return 0; int year = lexical_cast(datestring.substr(format.find("yyyy"), 4)); int month = lexical_cast(datestring.substr(format.find("mm"), 2)); int day = lexical_cast(datestring.substr(format.find("dd"), 2)); struct tm tm_r; tm_r.tm_year = year - 1900; tm_r.tm_mon = month -1; tm_r.tm_mday = day; tm_r.tm_hour = tm_r.tm_min = tm_r.tm_sec = 0; tm_r.tm_isdst = -1; // makes sure mktime() will determine the correct DST setting return mktime(&tm_r); } // format is in datepicker format ('mm' for month, 'dd' for day, 'yyyy' for year) std::string DatePickerToC(time_t date, std::string const& format) { if (date == 0) return ""; std::string cformat = format; cformat = StringReplace(cformat, "mm", "%m"); cformat = StringReplace(cformat, "dd", "%d"); cformat = StringReplace(cformat, "yyyy", "%Y"); return FormatDateTime(cformat.c_str(), date); } std::string EncodeDomId(std::string const & toEncode, char const * from, char const * to) { std::string encoded = toEncode; for (; *from && *to; from++, to++) { replace(encoded.begin(), encoded.end(), *from, *to); } return encoded; } std::string DecodeDomId(std::string const & toDecode, char const * from, char const * to) { std::string decoded = toDecode; for (; *from && *to; from++, to++) { replace(decoded.begin(), decoded.end(), *from, *to); } return decoded; } std::string FileSystemExchangeChars(std::string const & s, bool ToFileSystem) { char *str = strdup(s.c_str()); str = ExchangeChars(str, ToFileSystem); std::string data = str; if (str) { free(str); } return data; } bool MoveDirectory(std::string const & sourceDir, std::string const & targetDir, bool copy) { const char* delim = "/"; std::string source = sourceDir; std::string target = targetDir; // add missing directory delimiters if (source.compare(source.size() - 1, 1, delim) != 0) { source += "/"; } if (target.compare(target.size() - 1, 1, delim) != 0) { target += "/"; } if (source != target) { // validate target directory if (target.find(source) != std::string::npos) { esyslog("live: cannot move under sub-directory\n"); return false; } RemoveFileOrDir(target.c_str()); if (!MakeDirs(target.c_str(), true)) { esyslog("live: cannot create directory %s", target.c_str()); return false; } struct stat st1, st2; stat(source.c_str(), &st1); stat(target.c_str(),&st2); if (!copy && (st1.st_dev == st2.st_dev)) { #if APIVERSNUM > 20101 if (!cVideoDirectory::RenameVideoFile(source.c_str(), target.c_str())) { #else if (!RenameVideoFile(source.c_str(), target.c_str())) { #endif esyslog("live: rename failed from %s to %s", source.c_str(), target.c_str()); return false; } } else { int required = DirSizeMB(source.c_str()); int available = FreeDiskSpaceMB(target.c_str()); // validate free space if (required < available) { cReadDir d(source.c_str()); struct dirent *e; bool success = true; // allocate copying buffer const int len = 1024 * 1024; char *buffer = MALLOC(char, len); if (!buffer) { esyslog("live: cannot allocate renaming buffer"); return false; } // loop through all files, but skip all subdirectories while ((e = d.Next()) != NULL) { // skip generic entries if (strcmp(e->d_name, ".") && strcmp(e->d_name, "..") && strcmp(e->d_name, "lost+found")) { std::string sourceFile = source + e->d_name; std::string targetFile = target + e->d_name; // copy only regular files if (!stat(sourceFile.c_str(), &st1) && S_ISREG(st1.st_mode)) { int r = -1, w = -1; cUnbufferedFile *inputFile = cUnbufferedFile::Create(sourceFile.c_str(), O_RDONLY | O_LARGEFILE); cUnbufferedFile *outputFile = cUnbufferedFile::Create(targetFile.c_str(), O_RDWR | O_CREAT | O_LARGEFILE); // validate files if (!inputFile || !outputFile) { esyslog("live: cannot open file %s or %s", sourceFile.c_str(), targetFile.c_str()); success = false; break; } // do actual copy dsyslog("live: copying %s to %s", sourceFile.c_str(), targetFile.c_str()); do { r = inputFile->Read(buffer, len); if (r > 0) w = outputFile->Write(buffer, r); else w = 0; } while (r > 0 && w > 0); DELETENULL(inputFile); DELETENULL(outputFile); // validate result if (r < 0 || w < 0) { success = false; break; } } } } // release allocated buffer free(buffer); // delete all created target files and directories if (!success) { size_t found = target.find_last_of(delim); if (found != std::string::npos) { target = target.substr(0, found); } if (!RemoveFileOrDir(target.c_str(), true)) { esyslog("live: cannot remove target %s", target.c_str()); } found = target.find_last_of(delim); if (found != std::string::npos) { target = target.substr(0, found); } if (!RemoveEmptyDirectories(target.c_str(), true)) { esyslog("live: cannot remove target directory %s", target.c_str()); } esyslog("live: copying failed"); return false; } else if (!copy && !RemoveFileOrDir(source.c_str(), true)) { // delete source files esyslog("live: cannot remove source directory %s", source.c_str()); return false; } // delete all empty source directories if (!copy) { size_t found = source.find_last_of(delim); if (found != std::string::npos) { source = source.substr(0, found); #if APIVERSNUM > 20101 while (source != cVideoDirectory::Name()) { #else while (source != VideoDirectory) { #endif found = source.find_last_of(delim); if (found == std::string::npos) break; source = source.substr(0, found); if (!RemoveEmptyDirectories(source.c_str(), true)) break; } } } } else { esyslog("live: %s requires %dMB - only %dMB available", copy ? "moving" : "copying", required, available); // delete all created empty target directories size_t found = target.find_last_of(delim); if (found != std::string::npos) { target = target.substr(0, found); #if APIVERSNUM > 20101 while (target != cVideoDirectory::Name()) { #else while (target != VideoDirectory) { #endif found = target.find_last_of(delim); if (found == std::string::npos) break; target = target.substr(0, found); if (!RemoveEmptyDirectories(target.c_str(), true)) break; } } return false; } } } return true; } } // namespace vdrlive vdr-plugin-live-3.1.3/tools.h000066400000000000000000000117171414414333500160640ustar00rootroot00000000000000#ifndef VDR_LIVE_TOOLS_H #define VDR_LIVE_TOOLS_H // uncomment to debug lock sequence // #define DEBUG_LOCK // STL headers need to be before VDR tools.h (included by ) #include #include #include #include #if TNTVERSION >= 30000 #include // must be loaded before any vdr include because of duplicate macros (LOG_ERROR, LOG_DEBUG, LOG_INFO) #include "cxxtools/serializationinfo.h" #endif #ifndef DISABLE_TEMPLATES_COLLIDING_WITH_STL // To get rid of the swap definition in vdr/tools.h #define DISABLE_TEMPLATES_COLLIDING_WITH_STL #endif #include std::istream& operator>>( std::istream& is, tChannelID& ret ); inline std::ostream& operator<<( std::ostream& os, tChannelID const& id ) { return os << *id.ToString(); } #if TNTVERSION >= 30000 namespace cxxtools { class SerializationInfo; inline void operator<<= (cxxtools::SerializationInfo& si, const tChannelID& id) { // dsyslog("live: operator<<= called"); } inline void operator>>= (const cxxtools::SerializationInfo& si, tChannelID& id) { // dsyslog("live: operator>>= called"); } } #endif namespace vdrlive { extern const std::locale g_locale; extern const std::collate& g_collate_char; void AppendHtmlEscaped(std::string &target, const char* s); void AppendHtmlEscapedAndCorrectNonUTF8(std::string &target, const char *str); void AppendCorrectNonUTF8(std::string &target, const char* s); wint_t getNextUtfCodepoint(const char *&p); int utf8CodepointIsValid(const char *p); // In case of invalid UTF8, return 0. Otherwise: Number of characters wint_t Utf8ToUtf32(const char *&p, int len); // assumes, that uft8 validity checks have already been done. len must be provided. call utf8CodepointIsValid first void AppendUtfCodepoint(std::string &target, wint_t codepoint); void AppendDuration(std::string &target, char const* format, int hours, int minutes ); std::string FormatDuration( char const* format, int hours, int minutes ); void AppendDateTime(std::string &target, char const* format, time_t time ); std::string FormatDateTime( char const* format, time_t time ); std::string StringReplace( std::string const& text, std::string const& substring, std::string const& replacement ); std::vector StringSplit( std::string const& text, char delimiter ); int StringToInt( std::string const& string, int base = 10 ); std::string StringRepeat(int times, const std::string& input); std::string StringWordTruncate(const std::string& input, size_t maxLen, bool& truncated); inline std::string StringWordTruncate(const std::string& input, size_t maxLen) { bool dummy; return StringWordTruncate(input, maxLen, dummy); } std::string StringEscapeAndBreak( std::string const& input ); std::string StringFormatBreak(std::string const& input); std::string StringTrim(const std::string& str); std::string ZeroPad(int number); std::string MD5Hash(std::string const& str); time_t GetTimeT(std::string timestring); std::string ExpandTimeString(std::string timestring); std::string StringUrlEncode( std::string const& input ); std::string GetXMLValue( std::string const& xml, std::string const& element ); time_t GetDateFromDatePicker(std::string const& datestring, std::string const& format); std::string DatePickerToC(time_t date, std::string const& format); std::string EncodeDomId(std::string const & toEncode, char const * from = ".-:", char const * to = "pmc"); std::string DecodeDomId(std::string const & toDecode, char const * from = "pmc", char const * to = ".-:"); std::string FileSystemExchangeChars(std::string const & s, bool ToFileSystem); bool MoveDirectory(std::string const & sourceDir, std::string const & targetDir, bool copy = false); struct bad_lexical_cast: std::runtime_error { bad_lexical_cast(): std::runtime_error( "bad lexical cast" ) {} }; template To lexical_cast( From const& from ) { std::stringstream parser; parser << from; To result; parser >> result; if ( !parser ) throw bad_lexical_cast(); return result; } template std::string ConvertToString( From const& from, std::locale const& loc = g_locale ) { std::ostringstream parser; parser.imbue( loc ); parser << from; return parser.str(); } class ReadLock { private: typedef void (ReadLock::*safe_bool)() const; public: explicit ReadLock(cRwLock& lock, int timeout = 100) : m_lock(lock) , m_locked(false) { if (m_lock.Lock( false, timeout )) m_locked = true; } ~ReadLock() { if (m_locked) m_lock.Unlock(); } operator safe_bool() const { return m_locked ? &ReadLock::safe_bool_idiom : 0; } private: ReadLock(ReadLock const&); cRwLock& m_lock; bool m_locked; void safe_bool_idiom() const {} }; } // namespace vdrlive #endif // VDR_LIVE_TOOLS_H vdr-plugin-live-3.1.3/users.cpp000066400000000000000000000104261414414333500164140ustar00rootroot00000000000000/* * users.cpp: User specific rights for the LIVE plugin. * * See the README file for copyright information and how to reach the author. */ #include "users.h" #include "tools.h" #include "setup.h" #include namespace vdrlive { std::string cUsers::logged_in_user; // -- cUser ----------------------------------------------------------------- cUser::cUser(int ID, const std::string& Name, const std::string& Password) : m_ID(ID), m_Name(Name) { SetPassword(Password); } void cUser::SetPassword(const std::string& Password) { std::stringstream passwordStr; passwordStr << Password.size() << "|" << MD5Hash(Password); m_PasswordMD5 = passwordStr.str(); } int cUser::GetPasswordLength() const { // format is : std::vector parts = StringSplit( m_PasswordMD5, '|' ); return (parts.size() > 0) ? lexical_cast( parts[0] ) : 0; } std::string const cUser::GetMD5HashPassword() const { // format is : std::vector parts = StringSplit( m_PasswordMD5, '|' ); return (parts.size() > 1) ? parts[1] : ""; } bool cUser::Parse(const char *s) { char *line; char *pos; char *pos_next; int parameter = 1; int valuelen; #define MAXVALUELEN (10 * MaxFileName) char value[MAXVALUELEN]; pos = line = strdup(s); pos_next = pos + strlen(pos); if (*pos_next == '\n') *pos_next = 0; while (*pos) { while (*pos == ' ') pos++; if (*pos) { if (*pos != ':') { pos_next = strchr(pos, ':'); if (!pos_next) pos_next = pos + strlen(pos); valuelen = pos_next - pos + 1; if (valuelen > MAXVALUELEN) { esyslog("live: entry '%s' is too long. Will be truncated!", pos); valuelen = MAXVALUELEN; } strn0cpy(value, pos, valuelen); pos = pos_next; switch (parameter) { case 1: m_ID = lexical_cast(value); break; case 2: m_Name = value; break; case 3: m_PasswordMD5 = value; break; case 4: m_Userrights = lexical_cast(value); break; default: break; } //switch } parameter++; } if (*pos) pos++; } //while free(line); return (parameter >= 4) ? true : false; } const char *cUser::ToText(void) { char* buffer = NULL; if (asprintf(&buffer, "%d:%s:%s:%d", m_ID, m_Name.c_str(), m_PasswordMD5.c_str(), m_Userrights) < 0) return NULL; return buffer; } bool cUser::Save(FILE *f) { return fprintf(f, "%s\n", ToText()) > 0; } bool cUser::HasRightTo(eUserRights right) { return ((m_Userrights & (1 << (right-1))) != 0); } bool cUser::CurrentUserHasRightTo(eUserRights right) { if (!LiveSetup().UseAuth()) return true; cUser* user = cUsers::GetByUserName(cUsers::logged_in_user); return (cUsers::logged_in_user == LiveSetup().GetAdminLogin() || (user && (user->m_Userrights & (1 << (right-1))) != 0)); } void cUser::SetRight(eUserRights right) { isyslog("live: set right '%d' in '%d'", right, m_Userrights); m_Userrights |= (1 << (right-1)); isyslog("live: now rights are '%d'", m_Userrights); } bool cUsers::Delete(const std::string& Name) { cUser* user = Users.First(); while (user) { if (user->Name() == Name) { Users.Del(user); Users.Save(); return true; } user = Users.Next(user); } return false; } cUser* cUsers::GetByUserId(const std::string& Id) { cUser* user = Users.First(); while (user) { if (user->Id() == atoi(Id.c_str())) return user; user = Users.Next(user); } return NULL; } cUser* cUsers::GetByUserName(const std::string& Name) { cUser* user = Users.First(); while (user) { if (user->Name() == Name) return user; user = Users.Next(user); } return NULL; } int cUsers::GetNewId() { int iMaxId = -1; cUser* user = Users.First(); while (user) { if (iMaxId < user->Id()) iMaxId = user->Id(); user = Users.Next(user); } return iMaxId + 1; } bool cUsers::ValidUserLogin(const std::string& login, const std::string& password) { cUser* user = GetByUserName(login); if (user && MD5Hash(password) == user->GetMD5HashPassword()) return true; return false; } bool cUsers::ValidLogin(const std::string& login, const std::string& password) { return ((login == LiveSetup().GetAdminLogin() && MD5Hash(password) == LiveSetup().GetMD5HashAdminPassword()) || ValidUserLogin(login, password)); } } vdr-plugin-live-3.1.3/users.h000066400000000000000000000040121414414333500160530ustar00rootroot00000000000000#ifndef VDR_LIVE_USERS_H #define VDR_LIVE_USERS_H // STL headers need to be before VDR tools.h (included by ) #include #if TNTVERSION >= 30000 #include // must be loaded before any vdr include because of duplicate macros (LOG_ERROR, LOG_DEBUG, LOG_INFO) #endif #include #include namespace vdrlive { enum eUserRights { UR_EDITSETUP=1, UR_EDITTIMERS, UR_DELTIMERS, UR_DELRECS, UR_USEREMOTE, UR_STARTREPLAY, UR_SWITCHCHNL, UR_EDITSTIMERS, UR_DELSTIMERS, UR_EDITRECS }; // --- cUser -------------------------------------------------------- class cUser : public cListObject { int m_ID; std::string m_Name; std::string m_PasswordMD5; int m_Userrights; public: cUser() : m_ID(-1), m_Userrights(0) {} cUser(int ID, const std::string& Name, const std::string& Password); int Id() const { return m_ID; } std::string Name() const { return m_Name; } std::string PasswordMD5() const { return m_PasswordMD5; } int Userrights() const { return m_Userrights; } int GetPasswordLength() const; std::string const GetMD5HashPassword() const; void SetId(int Id) { m_ID = Id; } void SetName(const std::string& Name) { m_Name = Name; } void SetPassword(const std::string& Password); void SetUserrights(int Userrights) { m_Userrights = Userrights; } bool HasRightTo(eUserRights right); static bool CurrentUserHasRightTo(eUserRights right); void SetRight(eUserRights right); bool Parse(const char *s); const char *ToText(void); bool Save(FILE *f); }; // --- cUsers -------------------------------------------------------- class cUsers : public cConfig { public: bool ValidUserLogin(const std::string& login, const std::string& password); bool ValidLogin(const std::string& login, const std::string& password); bool Delete(const std::string& Name); static cUser* GetByUserId(const std::string& Id); static cUser* GetByUserName(const std::string& Name); int GetNewId(); static std::string logged_in_user; }; extern cUsers Users; } #endif