pax_global_header00006660000000000000000000000064147767756160014544gustar00rootroot0000000000000052 comment=b3136f7a080a7c8557f274ba7ce53b1972b0cad4 vdr-plugin-live-3.5.0/000077500000000000000000000000001477677561600145755ustar00rootroot00000000000000vdr-plugin-live-3.5.0/.gitignore000066400000000000000000000002261477677561600165650ustar00rootroot00000000000000CVS .#* *~ *.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.5.0/CONTRIBUTORS000066400000000000000000000022031477677561600164520ustar00rootroot00000000000000Grams 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.5.0/COPYING000066400000000000000000000431251477677561600156350ustar00rootroot00000000000000 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.5.0/HISTORY000066400000000000000000000225341477677561600156670ustar00rootroot00000000000000VDR 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 2023-01-18: Version 3.1.11 - Use new tvscraper service interface "class cScraperVideo" 2023-06-18: Version 3.2 - Support mobile devices - Support tvscraper data, including images - Improve performance, especially for recordings - Show duplicate recordings - Show errors in recordings - And many other, smaller improvements 2023-11-11: Version 3.3.0 - Verschieben von Aufzeichnungen in ein neues Verzeichnis vereinfacht (basierend auf einem Patch von maazl). - Markieren von Radio Aufnahmen (basierend auf einem Patch von maazl). - Anzeige des Grundes, aus dem Autotimer erstellt wurden (braucht tvscraper 1.2.4.+) - Behalten der scroll Position des Browsers bei der Navigation (dank einem Vorschlag von vdr_rossi) - Popup zum Bestätigen des Löschens von Aufnahmen beim Löschen einzelner Aufnahmen mit dem roten x. - Drop support for Internet Explorer Requirements: - gcc v8 or later. gcc must support -std=c++17. -std=c++1z should also work for complilers not yet supporting -std=c++17. - vdr 2.4.0 or later. Recommended: vdr 2.6.0 or later. 2023-11-26: Version 3.3.2 - Bug fix bei Aufnahmen ohne tvscraper - Performance-Verbesserungen beim Ändern/Löschen von Aufnahmen. Für optimale Performance solltet ihr auch tvscraper auf 1.2.5 updaten - Entfernen / Umpriorisierung von Debug Meldungen (Dank an kfb77 für's Melden). 2024-01-04: Version 3.3.4 - Fix possible shortdump . Dank an utiltiy für die Fehlermeldung - Fix compiler error with gcc version 8.3.0. Dank an maazl . - Fix: edit timer, Fehlerbehandlung bei fehlendem Titel. Dank an maazl - Fix für neue Aufzeichnungen kamel5, danke für's Melden! - Bei VDR version 2.6.5. werden bei Aufnahmen, die mit VDR 2.6.5 gemacht wurden, Stream-Informationen angezeigt. - Kanallogos (Patch von rüsseltier ) - Auch Kanallogos mit "/" im Namen werden unterstützt. Danke, rüsseltier - Kanallogos werden auch gefunden, wenn der Dateiname nur Kleinbuchstaben hat - Hintergrund für Kanallogos ist jetzt transparent 2024-04-10 Version 3.3.5 - Zum Anschauen von Aufzeichnungen mit vlc wird jetzt die streamdev URL verwendet, was auch Springen ermöglicht - Zahlreiche kleinere Verbesserungen und Fehlerbehebungen 2024-09-12 Version 3.3.6 - Zahlreiche Verbesserungen, die vor allem SHofmann beigetragen hat. - Compiliert auch wieder mit 2.7.1 2024-09-29: Version 3.3.7 - mark all events a timer is recording, not only the first one - allow streaming of recording to the browser - ffmpeg commands are now in a configuraton file, and removed from the settings view - remove --tvscraperimages commandline option - remove ShowPlayMediaplayer from setup 2024-10-25: Version 3.3.8 - Zahlreiche Verbesserungen, die vor allem shofmann beigetragen hat. - Die Suche nach Wiederholungen funktioniert nun auch, wenn im Namen Zeichen wie & vorkommen - Code cleanup 2024-11-04: Version 3.3.9 - Unterstützung von Timerüberwachung mit epgsearch über die Sendungskennung. Dank an LotharE für den Feature Request und Test. - MainThreadHook wird nicht mehr verwendet. Damit werden viele Aktionen schneller ausgeführt, z.B. Löschen von Timern, ... Außerdem wird der Fehler behoben, dass in der "Fernbedienung" Ansicht das OSD nicht gezeigt wurde während VDR eine Meldung ausgegeben hat. Dank für die Fehlermeldung und Analyse an SHofmann . 2024-11-18: Version 3.3.10 - In der Timerliste werden inaktive Timer nun grau hinterlegt, zur besseren Sichtbarkeit. - Die siteprefs.css wurde basierend auf dem Vorschlag von SHofmann erweitert. Zum Aufzoomen von Bildern bei mouseover. - Für die Zeitleiste gibt es nun keine Beschränkung der Spaltenzahl mehr. Die Mindestbreite einer Spalte wurde auf 15em erhöht. Das kann in der siteprefs.css angepasst werden (table.mschedule div.content1 {min-width: 15em; }). - Für die Zeitleiste gibt es in den Einstellungen nun verschiedene Optionen, z.B. können die in VDR definierten Kanalgruppen verwendet werden. Anmerkung: Wenn ihr viele Spalten habt, solltet ihr Euch mit den Möglichkeiten Eures Browsers zum vertikalen Scrollen vertraut machen. 2024-12-09: Version 3.3.11 - bessere optische Trennung der Timer (ev. müsst ihr den Browser-Cache leeren). - Korrektur von Montag (s. Beitrag #474). Dank an zimuland für die Meldung und SHofmann für die Korrektur - Code cleanup 2024-12-22: Version 3.3.12 - die von SHofmann gepostete Korrektur der Sprache bei repetitiven Timern - die von SHofmann gepostete Korrektur für den Font mit VDR Symbolen - Im tab Fernbedienung: - Bug fix: Funktioniert jetzt auch, wenn in einem Text ein Backslash steht - Unterstützung von "OK" -> Anzeige des aktuellen Kanals mit dem EPG von "jetzt" und "nächstes". - Beim Anwählen einer Zeile mit der Maus wird verifiziert, dass VDR die generierten Tastendrücke auch verarbeitet. Funktioniert damit auch bei längeren Listen - Die OSD Anzeige wird erst upgedatet, wenn das "finale" OSD da ist. Basiert auf Timing, funktioniert daher nicht immer - Zur Unterstützung von nicht anwählbaren Listenelementen muss VDR gepatcht werden. S. https://www.vdr-portal.de/forum/index.php?thread/135887-live-weiterentwicklung-v3-3-x/&postID=1377841#post1377841 2024-12-31: Version 3.4: Thanks to all users @vdr-portal.de for feedback, tests, bug reports, ... Summary of changes to Version 3.2: - Whats on: - Display channel logos. Code contributed by rüsseltier@vdr-portal.de - Multischedule: - Remove restriction regarding max. number of channels on one page. If you use many channels on one page, make yourself familiar with vertical scrolling features of your browser. - More display options - Timers: - Support epgsearch timer monitoring features - Display reason for tvscraper auto-timer - Display transponder information - Better optical separation of inactive timers - Searchtimers: - Better optical separation of inactive searchtimers - Recordings: - Display stream-information like resolution. Only for recordings recorded with VDR >= 2.6.5 - Confirmation popup before deleting single recordings with the red x icon - If streamdev-server is available, use streamdev-server URL to play recordings with external media player. This allows to jump in recordings. - Mark radio recordings. Code contributed by maazl@vdr-portal.de - Simplified moving recordings to a new folder. Code contributed by maazl@vdr-portal.de - Remote: - Support keyboard - Improved OSD - Optional: Patch VDR for support of non-selectable list elements. See https://www.vdr-portal.de/forum/index.php?thread/135887-live-weiterentwicklung-v3-3-x/&postID=1377841#post1377841 - Streaming: - ffmpeg commands are now in a configuration file, and removed from the settings view. Code contributed by SHofmann@vdr-portal.de - Support recordings. Code contributed by SHofmann@vdr-portal.de - Others: - Remove --tvscraperimages command line option - Remove ShowPlayMediaplayer from setup - Many bug fixes, usability and UI improvements, performance improvements, code cleanup, ... Special thanks to SHofmann@vdr-portal.de for his contributions 2025-02-05: Version 3.4.1: - Fix: Parsing additional times used in multischedule and what's on - Fix: Crash in case of an empty entry in channelgroups. Thanks to SHofmann @vdr-portal.de for reporting - Fix: Ensure to have locks before any access to VDR objects like cEvent, cRecording, ... - Timers & Recordings: Remove locks defined in live, rely on VDRs locks only - Remove grab.h, grab.cpp, tasks.h, tasks.cpp - note for grab.h: Snapshot picture has now less delay to picture on tv - Allow shorter Snapshot-Interval - note for tasks.h: - These tasks require VDR LOCKs. While it seems to be a good idea to - have these LOCKs in a separate thread, it is actually a bad one: - As we wait for these tasks to finish, this can result in invalid LOCK - sequences not detected by VDR but resulting in deadlocks. - affected: Switch channel, remove timer, remove recording, (play, pause, stop, forward, backward) recording - remove PCRE2 dependency, use c++ std::regex / std::regex_match - Hide empty directories in recordings on search (thanks to maazl for the patch) 2025-02-28: Version 3.4.2: - Together with vdr 2.7.4, improve the OSD on remote.html 2025- - : Version 3.5.0: - do not completely re-build the live recordings on each change in vdr recordings. only re-create the changed recordings - recordings, which are still being recorded: -- Are displayed in a different color -- The correct length and file size is displayed. Thanks to @nobanzai and @zimuland for reporting - support svg channel logos. Thanks @dile for the request, and @SHofmann for the Patch. - title short text and description can now also be changed for (old) pes recordings vdr-plugin-live-3.5.0/Makefile000066400000000000000000000202351477677561600162370ustar00rootroot00000000000000# # 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): HASH := \# VERSION := $(shell awk '/$(HASH)define LIVEVERSION/ { print $$3 }' setup.h | sed -e 's/[";]//g') $(info $$VERSION is [${VERSION}]) # figure out VERSION_SUFFIX VERSION_SUFFIX := ifneq ($(shell which git),) ifeq ($(shell test -d .git || echo void),) VERS_B := $(shell git branch | grep '^*' | sed -e's/^* //') VERS_H := $(shell git show --pretty=format:"%h_%ci" HEAD | head -1 | tr -d ' \-:') VERS_P := $(shell git status -uno --porcelain | grep -qc . && echo "_patched") VERSION_SUFFIX += _git_$(VERS_B)_$(VERS_H)$(VERS_P) $(info VERSION_SUFFIX = $(VERSION_SUFFIX)) endif endif ifneq ($(shell which quilt),) ifeq ($(shell quilt applied 2>&1 > /dev/null; echo $$?),0) VERSION_SUFFIX += _quilt_$(shell quilt applied | tr '\n' '_') $(info VERSION_SUFFIX = [${VERSION_SUFFIX}]) endif endif $(info $$VERSION_SUFFIX is [${VERSION_SUFFIX}]) PKG_CONFIG ?= pkg-config ### 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) CFGDIR := $(call PKGCFG,configdir) 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 := # -Wno-deprecated-declarations .. get rid of warning: ‘template class std::auto_ptr’ is deprecated CXXFLAGS += -std=c++17 -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 DEFINES += -DVERSION_SUFFIX='"$(VERSION_SUFFIX)"' ### The object files (add further files here): PLUGINOBJS := $(PLUGIN).o recman.o epg_events.o thread.o tntconfig.o setup.o \ timers.o tools.o status.o epgsearch.o \ md5.o filecache.o livefeatures.o preload.o timerconflict.o \ users.o osd_status.o ffmpeg.o xxhash.o i18n.o PLUGINSRCS := $(patsubst %.o,%.cpp,$(PLUGINOBJS)) WEB_LIB_PAGES := libpages.a WEB_DIR_PAGES := pages WEB_PAGES := $(WEB_DIR_PAGES)/$(WEB_LIB_PAGES) WEBLIBS := $(WEB_PAGES) SUBDIRS := $(WEB_DIR_PAGES) ### 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 $@) %.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: .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 .PHONY: lib lib: 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-conf install-conf: mkdir -p $(DESTDIR)$(CFGDIR)/plugins/$(PLUGIN) @for i in conf/*; do\ if ! [ -e $(DESTDIR)$(CFGDIR)/plugins/$(PLUGIN)/$$i ] ; then\ cp -p $$i $(DESTDIR)$(CFGDIR)/plugins/$(PLUGIN);\ fi;\ done .PHONY: install install: install-lib install-i18n install-web install-conf .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* *~ .PRECIOUS: $(I18Npo) .PHONY: FORCE FORCE: vdr-plugin-live-3.5.0/README000066400000000000000000000175731477677561600154720ustar00rootroot00000000000000This 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. Description: ============ Live, the "Live Interactive VDR Environment", is a VDR plugin providing a web interface to VDR and some of it's plugins. Requirements: ============= System using an UTF-8 locale VDR >= 2.4.0 gcc >= v8, must support -std=c++17. -std=c++1z should also work for complilers not yet supporting -std=c++17. Tntnet >= 2.2.1 - http://www.tntnet.org/download.hms // https://web.archive.org/web/20160314183814/http://www.tntnet.org/download.html Cxxtools >= 2.2.1 - http://www.tntnet.org/download.hms // https://web.archive.org/web/20160314183814/http://www.tntnet.org/download.html Tntnet provides basic web server functions for live and needs cxxtools. Optional: tvscraper >= 1.2.13, for images and other information from [TMDB](https://www.themoviedb.org/) & TheTVDB.com Installation: ============= If you compile the plugin outside of the VDR source code you must copy the resulting binary to the directory where VDR plugins are expected. You also must copy the subdirectory 'live' from the source distribution to the directory where the VDR plugins look for their resource files. The VDR default for this config directory is: /video/plugins, but this depends also on the parameters -c or -v (see 'vdr --help' for details). cp -a /live /plugins On debian like systems, is /usr/share/vdr ====== Setup: ====== Configuration files: ==================== siteprefs.css - Allows UI adjustments ffmpeg.conf - To play live tv / recordings directly in the browser, data must be converted. ffmpeg commands used for this are in ffmpeg.conf On debian like systems: - These files are installed in /etc/vdr/plugins/live - /var/lib/vdr/plugins/live/ffmpeg.conf is a symbolic link to /etc/vdr/plugins/live/ffmpeg.conf - /usr/share/vdr/plugins/live/css/siteprefs.css is a symbolic link to /etc/vdr/plugins/live/siteprefs.css Command line options: ===================== The default port is 8008. You can change this via command line: -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. See "How to make LIVE listen for SSL connections" section below on hints how to setup SSL. To display images or channel logos, you can use: -e <...> --epgimages=/path/to/epgimages use EPG images created by plugins like tvm2vdr --chanlogos=/path/to/channellogos use channel logos (PNG only, case sensitive) Settings in VDR's OSD: ====================== Note: These settings are also available in the web interface. Live provides a username/password protection. The default username and password are: admin/live 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 separator ":@1000 Found automatically" to channels.conf and 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 to SSL connections: ============================================ In order to start the SslListener LIVE requires a SSL certificate. If no SSL certificate is specified via command-line 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 command-line option) could be found, LIVE will only start the default HTTP Listener (default: 8008) If you want do disable the SslListener for any reason, specify SSL port number 0 with the command-line option. SSL Command-line options: ========================= -s PORT, --sslport=PORT use PORT to listen for incoming SSL connections (default: 8443) use PORT 0 to disable incoming SSL connections -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 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 certificate 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 (e.g. 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-and-dirty way to create a SSL self-signed certificate. Browsers will complain about it because the certificate wasn't signed by a known Certificate Authority (CA). So how does LIVE work? ====================== Basically, Live itself is a Tntnet Web server integrated into the plugin structure VDR needs. This Web server, 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. Security considerations: ======================== Please use live in a controlled environment only, like your home network or a save VPN of your home network. You cannot expect sufficient security to expose live to the public internet. Of course, better security is always appreciated, so please send patches increasing security. One possible security issue (there might be others ...): 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 been enhanced to check the paths before returning files. A second measure against misuse 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. Errors ====== If VDR crashes on start with these journal messages: loading plugin: /usr/local/lib/vdr/libvdr-live.so.x.x. terminate called after throwing an instance of 'std::runtime_error' what(): locale::facet::_S_create_c_locale name not valid Most likely, the locale is not installed correctly. On Debian based systems, use: dpkg-reconfigure locales and restart VDR. vdr-plugin-live-3.5.0/buildutil/000077500000000000000000000000001477677561600165725ustar00rootroot00000000000000vdr-plugin-live-3.5.0/buildutil/version-util000077500000000000000000000050501477677561600211600ustar00rootroot00000000000000#!/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 locally 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.5.0/buildutil/version-util.awk000066400000000000000000000013361477677561600217410ustar00rootroot00000000000000BEGIN { 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.5.0/cache.h000066400000000000000000000045271477677561600160210ustar00rootroot00000000000000#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.5.0/conf/000077500000000000000000000000001477677561600155225ustar00rootroot00000000000000vdr-plugin-live-3.5.0/conf/ffmpeg.conf000066400000000000000000000030071477677561600176350ustar00rootroot00000000000000# FFMPEG commands for streaming channels chnH264 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 chnHEVC 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 chnMPG2 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 chnDFLT 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 # FFMPEG commands for streaming recordings recH264 ffmpeg -loglevel warning -f mpegts -analyzeduration 1.2M -probesize 5M -re -i -map 0:v -map 0:a:0 -c:v copy -c:a aac -ac 2 recHVEC ffmpeg -loglevel warning -f mpegts -analyzeduration 1.2M -probesize 5M -re -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 recMPG2 ffmpeg -loglevel warning -f mpegts -analyzeduration 1.2M -probesize 5M -re -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 recDFLT ffmpeg -loglevel warning -f mpegts -analyzeduration 1.2M -probesize 5M -re -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 vdr-plugin-live-3.5.0/doc/000077500000000000000000000000001477677561600153425ustar00rootroot00000000000000vdr-plugin-live-3.5.0/doc/ChangeLog000066400000000000000000000314601477677561600171200ustar00rootroot00000000000000unrecorded Markus Ehrnsperger * The git repository is now https://github.com/MarkusEh/vdr-plugin-live 2013-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 available. 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 calculated 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 .. request (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 .. continuous 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 localization 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: standalone class. Used by pageenhance.js ..* live/js/live/hinttips.js: standalone class. Used by pageenhance.js 2007-07-21 Dieter Hametner ..Made EPG images better style-able. 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 command-line 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 independent 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 themeable 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 ..extensions, the boost library is now only necessary if the used ..g++ compiler version is less than 4.0 vdr-plugin-live-3.5.0/doc/LOCKs.txt000066400000000000000000000005501477677561600170160ustar00rootroot00000000000000Concept: A method returning a VDR object like cRecording requires the corresponding VDR global table (cRecordings) as input parameter. Reasoning: cRecordings (LOCK_RECORDINGS_READ) must be locked anyhow, otherwise it is not allowed to use the returned cRecording. So, requesting this paramater reduces the chance that the developer will forget this rule ... vdr-plugin-live-3.5.0/doc/TODO.txt000066400000000000000000000014721477677561600166540ustar00rootroot00000000000000This 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 truly static content, like images, styles, ECMAscript with Tntnet's send-file functionality after resolving user selected themes paths. Take care to support browser cache optimization. - Deliver EPG box information through AJAX. This would make a ECMAScript capable browser mandatory. - Provide a way to get the information on extra 'static' pages. vdr-plugin-live-3.5.0/doc/css-themeing.txt000066400000000000000000000105601477677561600204730ustar00rootroot00000000000000Live 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 themeable. 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 themeable images ---------------------------------- All themeable 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 themeable content must be present in the directory specified by 'GetConfigDir'. GetConfigDir returns at runtime the position in the file system 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 predefined 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 dynamically 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 easily 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 malicious requests to other locations in the file system 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.5.0/doc/dev-conventions.txt000066400000000000000000000055041477677561600212300ustar00rootroot00000000000000Live 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 already 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 an '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.5.0/doc/keyboard_shortcuts.txt000066400000000000000000000004351477677561600220230ustar00rootroot00000000000000the keyboard shortcuts in the remote view: -> menu -> back -> OK -> red -> green -> jellow -> blue -> fast rewind -> fast fwd -> menu -> mute -> vol down -> vol up -> channel up -> channel down vdr-plugin-live-3.5.0/doc/timer_monitoring.txt000066400000000000000000000052451477677561600214760ustar00rootroot00000000000000See https://www.vdr-portal.de/forum/index.php?thread/135887-live-weiterentwicklung-v3-3-x/&postID=1376226#post1376226 German only, sorry :( . Ich muss da glaube ich etwas ausholen: Normalerweise will ich mit einem Timer ein Event aufzeichnen. Die Frage ist nun, wie kann ich ein Event eindeutig identifizieren? Dass die Sender gelegentlich mal Zeiten verschieben, ist ja bekannt. Also gibt es: 1. VPS: Klar definiert und im Standard und von VDR ohne Plugins unterstützt: Ein Event bekommt eine VPS Zeit, und diese VPS Zeit ändert sich nicht, auch wenn sich das Event verschiebt. Bei einem VPS Timer zeichnet VDR alle Events auf, die diese VPS Zeit haben. Auch dann, wenn sich diese Events verschieben und die Startzeit der Events nichts mehr mit der VPS Zeit zu tun hat. Einziger Nachteil: Der Sender muss das unterstützen 2. Sendungskennung == "Event ID": Der Sender schickt bei jedem Event eine ID mit. Leider nicht immer die gleiche ID, d.h. wenn die ID von "Avengers" gestern noch 4567 war, kann sie heute auch 1234 sein. Manche Sender ändern diese IDs recht oft, bei anderen bleibt sie meistens stabil. Erfahrung: Wenn ein Event ganz neu in's EPG kommt, ändert sich die ID mit sehr hoher Wahrscheinlichkeit. Wenn das Event schon 1-2 Tage im EPG ist, bleibt die ID normalerweise stabil. Es gibt aber auch Sender, bei denen diese ID nie stabil bleibt ... 3. Uhrzeit: Epgsearch merkt sich Start und Dauer des Events, und versucht anhand dieser Daten ein verschobenes Event wiederzufinden. Vorteil: Funktioniert auch, wenn der Sender die Event-ID ändert. Sollte insbesondere bei kleineren Änderungen der Startzeit sehr zuverlässig funktionieren. Sender ändern normalerweise die Dauer der Events nicht. 4. VDR ohne VPS und ohne Epgsearch: VDR wählt das Event mit der größten Überdeckung. Also das Event, das möglichst vollständig im Zeitraum (Timer Start - Timer Stop) liegt. Um dies möglichst gut machen zu können, verkürzt VDR gegebenenfalls die Timer-Margin (vorne und hinten), so dass beim Anlegen des Timers der Timer nur ein Event überdeckt. Sicherlich die schlechteste Option: Durch die später nicht mehr reproduzierbare Änderung von Start und Endzeit des Timers wird es schwieriger, des original Event zu identifizieren. Ich kann später nicht einmal mehr die Länge des Events herausfinden, zu dem der Timer angelegt wurde. Dass das Event dann möglicherweise nicht mehr korrekt identifiziert wird, ist weniger schlimm: VDR zeichnet von Timer Start - Timer Stop auf, da ist das Event egal. Übel ist, dass durch die Verkürzung der Timer-Margins ein Stück der Sendung fehlen kann, obwohl sie sich nur sehr wenig verschoben hat. Siehe Vor- und Nachlauf bei Aufnahmen ~ Markus vdr-plugin-live-3.5.0/epg_events.cpp000066400000000000000000000446251477677561600174530ustar00rootroot00000000000000#include "epg_events.h" #include "tools.h" #include "timers.h" #include "recman.h" #include "setup.h" // STL headers need to be before VDR tools.h (included by ) #include #include #include #include #include #include #include #ifndef TVM2VDR_PL_WORKAROUND #define TVM2VDR_PL_WORKAROUND 0 #endif namespace vdrlive { /* * ------------------------------------------------------------------------- * EpgInfo * ------------------------------------------------------------------------- */ const std::string EpgInfo::CurrentTime(const char* format) { return std::string(cToSvDateTime(format, time(0))); } const std::string EpgInfo::StartTime(const char* format) const { return m_startTime ? std::string(cToSvDateTime(format, m_startTime)) : ""; } const std::string EpgInfo::EndTime(const char* format) const { return m_endTime ? std::string(cToSvDateTime(format, m_endTime)) : ""; } int EpgInfo::Elapsed() const { if (m_type == 0) return -1; // not an event, not a recording ... if (m_type == 1) return EpgEvents::ElapsedTime(m_startTime, m_endTime); // epg event if (!m_recordingId) return -1; // try currently playing recording if any #if APIVERSNUM >= 20402 cMutexLock mutexLock; cControl* pControl = cControl::Control(mutexLock); #else cControl* pControl = cControl::Control(); #endif if (pControl) { int current, total; LOCK_RECORDINGS_READ; const cRecording* playing = pControl->GetRecording(); if (playing && playing->Id() == m_recordingId && pControl->GetIndex(current,total) && total) return (100 * current) / total; } // Check for resume position next if (m_resume > 0 && m_numFrames > 0) return (100 * m_resume) / m_numFrames; return -1; } cSv EpgInfo::Name() const { cSv name(m_name); size_t index = name.find_last_of('~'); if (index != std::string::npos) { name = name.substr(index+1); } return name; } /* * ------------------------------------------------------------------------- * EpgEvents * ------------------------------------------------------------------------- */ namespace EpgEvents { std::string EncodeDomId(tChannelID const &chanId, tEventID eId) { cToSvConcat eventId("event_"); stringAppendChannel(eventId, chanId, 'p', 'm').concat('_', eId); return std::string(eventId); } void DecodeDomId(cSv epgid, tChannelID& channelId, tEventID &eventId) { size_t delimPos = epgid.find_last_of('_'); cToSvConcat channelIdStr(epgid.substr(6, delimPos - 6)); // remove "event_" at start and "_" at end vdrlive::DecodeDomId(channelIdStr.begin(), channelIdStr.end(), "mp", "-."); channelId = tChannelID::FromString(channelIdStr.c_str()); eventId = parse_int(epgid.substr(delimPos+1)); } const cEvent *GetEventByEpgId(cSv epgid, const cSchedules *Schedules) { // LOCK_SCHEDULES_READ -> and pass Schedules. // Keep the lock as long as you need the event! if (epgid.empty() ) return nullptr; tChannelID channelid; tEventID eventid; DecodeDomId(epgid, channelid, eventid); if ( !channelid.Valid() || eventid == 0 ) return nullptr; const cSchedule *schedule = Schedules->GetSchedule(channelid); if (!schedule) return nullptr; #if APIVERSNUM >= 20502 return schedule->GetEventById( eventid ); #else return schedule->GetEvent( eventid ); #endif } bool GetEventChannelByEpgId(const cEvent *&event, const cChannel *&channel, cSv epgid, const cChannels *Channels, const cSchedules *Schedules) { // LOCK_CHANNELS_READ; LOCK_SCHEDULES_READ -> and pass Channels, Schedules. // Keep the lock as long as you need the event! // return true if event & channel was found event = nullptr; channel = nullptr; if (epgid.empty() ) return false; tChannelID channelid; tEventID eventid; DecodeDomId(epgid, channelid, eventid); if (!channelid.Valid() || eventid == 0) return false; channel = Channels->GetByChannelID(channelid); if (!channel) return false; const cSchedule *schedule = Schedules->GetSchedule(channel); if (!schedule) return false; #if APIVERSNUM >= 20502 event = schedule->GetEventById(eventid); #else event = schedule->GetEvent(eventid); #endif return event; } bool ScanForEpgImages(cSv imageId, cSv wildcard, std::list & images) { bool found = false; const std::string filemask = concat(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_view imagefile(globbuf.gl_pathv[i]); size_t delimPos = imagefile.find_last_of('/'); images.push_back(std::move(std::string(imagefile.substr(delimPos+1)))); found = true; } globfree(&globbuf); } return found; } bool ScanForEpgImages(cSv channelId, cSv eventId, cSv wildcard, std::list & images) { if (LiveSetup().GetEpgImageDir().empty() ) return false; if (ScanForEpgImages(cToSvConcat(channelId, "_", eventId), wildcard, images)) return true; return ScanForEpgImages(eventId, wildcard, images); } bool ScanForRecImages(cSv imageId, cSv recfolder, std::list & images) { // format of imageId: if (recfolder.empty()) return false; bool found = false; for (cSv filetype: cSplit("png,jpg,jpeg,webp,PNG,JPG", ',')) { cToSvConcat filemask(recfolder, "/*.", filetype); 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 cSv imagefile(globbuf.gl_pathv[i]); size_t delimPos = imagefile.find_last_of('/'); const cSv imagename(imagefile.substr(delimPos+1)); // file name, part in path after last / // create a temporary symlink of the image in tmpImageDir cToSvConcat tmpfile(tmpImageDir, imageId, "_", imagename); if (symlink(globbuf.gl_pathv[i], tmpfile.c_str()) < 0 && errno != EEXIST) { esyslog("live: ERROR: Couldn't create symlink, target = %s, linkpath = %s, error: %s", globbuf.gl_pathv[i], tmpfile.c_str(), strerror(errno)); } else { images.push_back(std::move(std::string(imagename))); found = true; } } globfree(&globbuf); } } return found; } bool PosterTvscraper(cTvMedia &media, const cEvent *event, const cRecording *recording) { media.path = ""; media.width = media.height = 0; if (LiveSetup().GetTvscraperImageDir().empty() ) return false; ScraperGetPoster call; call.event = event; call.recording = recording; if (ScraperCallService("GetPoster", &call) && ! call.poster.path.empty() ) { media.path = ScraperImagePath2Live(call.poster.path); media.width = call.poster.width; media.height = call.poster.height; return true; } return false; } std::list EpgImages(cSv epgid) { // format of epgid: event__ size_t delimPosFirst = epgid.find_first_of('_'); size_t delimPosLast = epgid.find_last_of('_'); cSv channelIdStr_enc = epgid.substr(delimPosFirst+1, delimPosLast-delimPosFirst-1); std::string channelId = vdrlive::DecodeDomId(channelIdStr_enc, "mp", "-."); cSv eventId = epgid.substr(delimPosLast+1); std::list images; // Initially we scan for images that follow the scheme // '__.*' where distinction 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: // S19.2E-1-2222-33333_112123_0.jpg first epg image for event id 112123 // S19.2E-1-2222-33333_112123_1.png second epg image for event id 112123 // If no image is found with channelId it will be searched // with the eventId only following the scheme: // '_.*' // Example: // 112123_0.jpg first epg image for event id 112123 // 112123_1.png second epg image for event id 112123 if (! ScanForEpgImages(channelId, eventId, "_*.*", 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(channelId, eventId, ".*", images)) { #if TVM2VDR_PL_WORKAROUND // if we didn't get images try to work around 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. eventId = eventId.substr(0, eventId.size()-1); ScanForEpgImages(channelId, eventId, "*.*", images); #endif } } return images; } std::list RecImages(cSv epgid, cSv recfolder) { // format of epgid: recording_ size_t delimPos = epgid.find_last_of('_'); cSv 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 void EpgInfo::Clear() { *this = EpgInfo(); } void EpgInfo::CreateEpgInfo(cSv epgid) { const cChannel *channel; const cEvent *event; LOCK_CHANNELS_READ; LOCK_SCHEDULES_READ; EpgEvents::GetEventChannelByEpgId(event, channel, epgid, Channels, Schedules); if (!channel) { CreateEpgInfo(epgid, tr("Epg error"), tr("Wrong channel id")); return; } if (!event) { CreateEpgInfo(epgid, tr("Epg error"), tr("Wrong event id")); return; } return CreateEpgInfo(channel, event, epgid); } void EpgInfo::InitializeScraperVideo(cEvent const *event, cRecording const *recording) { cGetScraperVideo getScraperVideo(event, recording); if (getScraperVideo.call(LiveSetup().GetPluginTvscraper())) m_scraperVideo.swap(getScraperVideo.m_scraperVideo); } void EpgInfo::CreateEpgInfo(cChannel const *chan, cEvent const *event, cSv idOverride) { assert(chan); if (event) { m_type = 1; m_eventId = idOverride.empty() ? EpgEvents::EncodeDomId(chan->GetChannelID(), event->EventID()):idOverride; m_caption = cSv(chan->Name()); m_channelId = chan->GetChannelID(); m_channelName = cSv(chan->Name()); m_channelNumber = chan->Number(); m_title = cSv(event->Title()); m_shortText = cSv(event->ShortText()); m_description = cSv(event->Description()); m_startTime = event->StartTime(); m_endTime = event->EndTime(); m_eventDuration = event->Duration(); InitializeScraperVideo(event, nullptr); for (int i = 0; i < MaxEventContents; ++i) m_contents[i] = event->Contents(i); m_parentalRating = event->ParentalRating(); return; } if (LiveSetup().GetShowChannelsWithoutEPG()) { m_type = 1; m_eventId = idOverride.empty() ? EpgEvents::EncodeDomId(chan->GetChannelID(), 0):idOverride; m_caption = cSv(chan->Name()); m_channelId = chan->GetChannelID(); m_channelName = cSv(chan->Name()); m_channelNumber = chan->Number(); } } void EpgInfo::CreateEpgInfo(cSv recid, cRecording const *recording, char const *caption) { m_eventId = recid; m_type = 2; for (int i = 0; i < MaxEventContents; ++i) m_contents[i] = 0; if (recording) { m_recordingId = recording->Id(); m_fileName = cSv(recording->FileName()); m_name = cSv(recording->Name()); const cRecordingInfo* info = recording->Info(); if (info) { m_title = cSv(info->Title()); m_shortText = cSv(info->ShortText()); m_description = cSv(info->Description()); m_channelId = info->ChannelID(); m_channelName = cSv(info->ChannelName()); const cEvent *event = info->GetEvent(); if (event) { m_eventDuration = event->Duration(); for (int i = 0; i < MaxEventContents; ++i) m_contents[i] = event->Contents(i); m_parentalRating = event->ParentalRating(); } } if (m_title.empty()) m_title = Name(); m_startTime = recording->Start(); int length = recording->LengthInSeconds(); m_endTime = (length < 0) ? m_startTime : m_startTime + length; m_resume = recording->GetResume(); m_numFrames = recording->NumFrames(); m_archived = RecordingsManager::GetArchiveDescr(recording); InitializeScraperVideo(nullptr, recording); } if (caption) { m_caption = caption; } else if (recording) { m_caption = Name(); } } void EpgInfo::CreateEpgInfo(cSv id, cSv caption, cSv info) { m_eventId = id; m_caption = caption; m_title = info; } void AppendScraperData(cToSvConcat<0> &target, cScraperVideo *scraperVideo) { cTvMedia s_image; std::string s_title, s_episode_name, s_IMDB_ID, s_release_date; if (scraperVideo == NULL) { AppendScraperData(target, s_IMDB_ID, s_image, tNone, s_title, 0, 0, s_episode_name, 0, s_release_date); return; } int s_runtime; scraperVideo->getOverview(&s_title, &s_episode_name, &s_release_date, &s_runtime, &s_IMDB_ID, NULL); s_image = scraperVideo->getImage( cImageLevels(eImageLevel::episodeMovie, eImageLevel::seasonMovie, eImageLevel::tvShowCollection, eImageLevel::anySeasonCollection), cOrientations(eOrientation::landscape, eOrientation::portrait, eOrientation::banner), false); AppendScraperData(target, s_IMDB_ID, s_image, scraperVideo->getVideoType(), s_title, scraperVideo->getSeasonNumber(), scraperVideo->getEpisodeNumber(), s_episode_name, s_runtime, s_release_date); } // first call with lastDay = "" // before first call: open with "[" // after last call: cloes with "]]]" // [ // ["Day1", [ // [[event1], [rec1]], [[event2]], [[event3]] // ] // ], // ["Day2", [] // ] // ] std::string appendEpgItemWithRecItem(cToSvConcat<0> &epg_item, cSv lastDay, const cEvent *Event, const cChannel *Channel, bool withChannel, const cTimers *Timers) { // return current day cToSvDateTime day(tr("%A, %b %d %Y"), Event->StartTime()); if (lastDay != cSv(day)) { if (!lastDay.empty()) epg_item.concat("]],\n"); epg_item.concat("[\"", day, "\",["); } else epg_item.concat(','); RecordingsItemRec *recItem; epg_item.concat('['); if (appendEpgItem(epg_item, recItem, Event, Channel, withChannel, Timers)) { epg_item.concat(",["); recItem->AppendAsJSArray(epg_item); epg_item.concat(']'); } epg_item.concat(']'); return std::string(day); } bool appendEpgItem(cToSvConcat<0> &epg_item, RecordingsItemRec *&recItem, const cEvent *Event, const cChannel *Channel, bool withChannel, const cTimers *Timers) { cGetScraperVideo getScraperVideo(Event, nullptr); getScraperVideo.call(LiveSetup().GetPluginTvscraper()); RecordingsTreePtr recordingsTree(RecordingsManager::GetRecordingsTree()); const std::vector *recItems = recordingsTree->allRecordings(RecordingsManager::eSortOrder::duplicatesLanguage); bool recItemFound = searchNameDesc(recItem, recItems, Event, getScraperVideo.m_scraperVideo.get()); epg_item.append("[\""); // [0] : EPG ID (without event_) // epg_item.append(EpgEvents::EncodeDomId(Channel->GetChannelID(), Event->EventID()).c_str() + 6); stringAppendChannel(epg_item, Channel->GetChannelID(), 'p', 'm'); epg_item.concat('_', Event->EventID()); epg_item.append("\",\""); // [1] : Timer ID const cTimer* timer = TimerManager::GetTimer(Event, Channel, Timers); if (timer) { epg_item.append(vdrlive::EncodeDomId(SortedTimers::GetTimerId(*timer), ".-:", "pmc")); if (timer->Recording()) { epg_item.append("&ts=r"); // do not show a recording that is underway recItemFound = false; } else if (!(timer->Flags() & tfActive)) epg_item.append("&ts=i"); } epg_item.append("\","); // scraper data AppendScraperData(epg_item, getScraperVideo.m_scraperVideo.get() ); epg_item.append(","); // [9] : channelnr if (withChannel) { epg_item.concat(Channel->Number()); epg_item.append(",\""); // [10] : channelname AppendHtmlEscapedAndCorrectNonUTF8(epg_item, Channel->Name() ); } else epg_item.append("0,\""); epg_item.append("\",\""); // [11] : Name AppendQuoteEscapedAndCorrectNonUTF8(epg_item, Event->Title() ); epg_item.append("\",\""); // [12] : Shorttext AppendHtmlEscapedAndCorrectNonUTF8(epg_item, Event->ShortText() ); epg_item.append("\",\""); // [13] : Description AppendTextTruncateOnWord(epg_item, Event->Description(), LiveSetup().GetMaxTooltipChars(), true); epg_item.append("\",\""); // [14] : Day, time & duration of event epg_item.appendDateTime(tr("%I:%M %p"), Event->StartTime() ); epg_item.append(" - "); epg_item.appendDateTime(tr("%I:%M %p"), Event->EndTime() ); epg_item.append(" "); AppendDuration(epg_item, tr("(%d:%02d)"), Event->Duration()); epg_item.append("\"]"); return recItemFound; } }; // namespace vdrlive vdr-plugin-live-3.5.0/epg_events.h000066400000000000000000000133461477677561600171140ustar00rootroot00000000000000#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 "services.h" #include "recman.h" #include #include #include namespace vdrlive { class EpgInfo; typedef EpgInfo* EpgInfoPtr; // ------------------------------------------------------------------------- namespace EpgEvents { std::string EncodeDomId(tChannelID const &chanId, tEventID eventId); void DecodeDomId(cSv epgid, tChannelID &chanId, tEventID &eventId); const cEvent *GetEventByEpgId(cSv epgid, const cSchedules *Schedules); bool GetEventChannelByEpgId(const cEvent *&event, const cChannel *&channel, cSv epgid, const cChannels *Channels, const cSchedules *Schedules); bool PosterTvscraper(cTvMedia &media, const cEvent *event, const cRecording *recording); /** * Return a list of EpgImage paths for a given epgid. */ std::list EpgImages(cSv epgid); /** * Return a list of RecImages in the given folder. */ std::list RecImages(cSv epgid, cSv recfolder); /** * Calculate the duration. A duration can be zero or * positive. 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 { public: EpgInfo() { m_contents.fill(uchar(0)); } EpgInfo(cChannel const *chan, cEvent const *event) { m_contents.fill(uchar(0)); CreateEpgInfo(chan, event); } void Clear(); /** * Allocate and initialize an epgEvent instance with the * passed channel and event information. * Never call this function with a NULL chan pointer */ void CreateEpgInfo(cChannel const *chan, cEvent const *event, cSv idOverride = cSv()); /** * This is the inverse creator for epgInfos to the creator above. */ void CreateEpgInfo(cSv epgid); /** * Allocate and initialize an epgEvent instance with the * passed recording information. */ void CreateEpgInfo(cSv recid, cRecording const *recording, char const *caption = 0); /** * Allocate and initialize an epgEvent instance with the * passed string informations */ void CreateEpgInfo(cSv id, cSv caption, cSv info); bool isRecording() const { return m_type == 2;} cSv Id() const { return m_eventId; } cSv Caption() const { return m_caption; } cSv Title() const { return m_title; } cSv ShortDescr() { return m_shortText; } cSv LongDescr() { return m_description; } cSv ChannelName() { return m_channelName; } int ChannelNumber() { return m_channelNumber; } std::string const StartTime(const char* format) const; std::string const EndTime(const char* format) const; static std::string const CurrentTime(const char* format); time_t GetStartTime() const { return m_startTime; } time_t GetEndTime() const { return m_endTime; } int Duration() const { return m_endTime-m_startTime; } // for recordings: recording duration int EventDuration() const { return m_eventDuration; } // this is always the event duration int Elapsed() const; uchar Contents(int i = 0) const { return (0 <= i && i < MaxEventContents) ? m_contents[i] : uchar(0); } int ParentalRating() const { return m_parentalRating; } bool ScraperVideoAvailable() const { return bool(m_scraperVideo) ; } cScraperVideo *GetScraperVideo() const { return m_scraperVideo.get() ; } // only for recordings: cSv Archived() const { return m_archived; } cSv FileName() const { return m_fileName; } private: int m_type = 0; // o -> none, 1 -> event, 2 -> recording std::string m_eventId; std::string m_caption; std::string m_title; std::string m_shortText; std::string m_description; time_t m_startTime = 0; time_t m_endTime = 0; tChannelID m_channelId = tChannelID(); std::string m_channelName; int m_channelNumber = 0; int m_eventDuration = 0; // this is always the event duration std::array m_contents; int m_parentalRating = 0; void InitializeScraperVideo(cEvent const *event, cRecording const *recording); std::unique_ptr m_scraperVideo; // for recordings: int m_recordingId = 0; int m_resume = 0; int m_numFrames = 0; std::string m_archived; std::string m_fileName; std::string m_name; // as returned by cRecording:Name() cSv Name() const; // Last Part of m_name }; // ------------------------------------------------------------------------- bool appendEpgItem(cToSvConcat<0> &epg_item, RecordingsItemRec *&recItem, const cEvent *Event, const cChannel *Channel, bool withChannel, const cTimers *Timers); std::string appendEpgItemWithRecItem(cToSvConcat<0> &epg_item, cSv lastDay, const cEvent *Event, const cChannel *Channel, bool withChannel, const cTimers *Timers); }; // namespace vdrlive #endif // VDR_LIVE_EPG_EVENTS_H vdr-plugin-live-3.5.0/epgsearch.cpp000066400000000000000000000506451477677561600172540ustar00rootroot00000000000000 #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 = parse_int(EPGSearchSetupValues::ReadValue("DefPriority")); m_lifetime = parse_int(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 = parse_int(EPGSearchSetupValues::ReadValue("DefMarginStart")); m_marginstop = parse_int(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(); cSplit parts(data, ':'); auto part = parts.begin(); for (int i = 0; part != parts.end(); ++i, ++part ) { switch ( i ) { case 0: m_id = parse_int( *part ); break; case 1: m_search = cToSvReplace( *part, "|", ":" ).replaceAll("!^pipe^!", "|" ); break; case 2: m_useTime = lexical_cast( *part ); break; case 3: if ( m_useTime ) m_startTime = parse_int( *part ); break; case 4: if ( m_useTime ) m_stopTime = parse_int( *part ); break; case 5: m_useChannel = parse_int( *part ); break; case 6: ParseChannel( *part ); break; case 7: m_useCase = parse_int( *part ); break; case 8: m_mode = parse_int( *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 = parse_int( *part ); break; case 14: if ( m_useDuration ) m_maxDuration = parse_int( *part ); break; case 15: m_useAsSearchtimer = parse_int( *part ); break; case 16: m_useDayOfWeek = lexical_cast( *part ); break; case 17: m_dayOfWeek = parse_int( *part ); break; case 18: m_useEpisode = lexical_cast( *part ); break; case 19: m_directory = cToSvReplace( *part, "|", ":" ).replaceAll("!^pipe^!", "|" ); break; case 20: m_priority = parse_int( *part ); break; case 21: m_lifetime = parse_int( *part ); break; case 22: m_marginstart = parse_int( *part ); break; case 23: m_marginstop = parse_int( *part ); break; case 24: m_useVPS = lexical_cast( *part ); break; case 25: m_action = parse_int( *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 = parse_int( *part ); break; case 30: m_compareTitle = lexical_cast( *part ); break; case 31: m_compareSubtitle = parse_int( *part ); break; case 32: m_compareSummary = lexical_cast( *part ); break; case 33: m_catvaluesAvoidRepeat = parse_int< unsigned long >( *part ); break; case 34: m_repeatsWithinDays = parse_int( *part ); break; case 35: m_delAfterDays = parse_int( *part ); break; case 36: m_recordingsKeep = parse_int( *part ); break; case 37: m_switchMinBefore = parse_int( *part ); break; case 38: m_pauseOnNrRecordings = parse_int( *part ); break; case 39: m_blacklistmode = parse_int( *part ); break; case 40: ParseBlacklist( *part ); break; case 41: m_fuzzytolerance = parse_int( *part ); break; case 42: m_useInFavorites = lexical_cast( *part ); break; case 43: m_menuTemplate = parse_int( *part ); break; case 44: m_delMode = parse_int( *part ); break; case 45: m_delAfterCountRecs = parse_int( *part ); break; case 46: m_delAfterDaysOfFirstRec = parse_int( *part ); break; case 47: m_useAsSearchTimerFrom = parse_int( *part ); break; case 48: m_useAsSearchTimerTil = parse_int( *part ); break; case 49: m_ignoreMissingEPGCats = lexical_cast( *part ); break; } } } std::string SearchTimer::ToText() { cToSvConcat os; os << m_id << ':' << cToSvReplace(m_search, "|", "!^pipe^!").replaceAll(":", "|") << ':'; if (m_useTime) { os << "1:"; os.appendInt<4>(m_startTime) << ':'; os.appendInt<4>(m_stopTime) << ':'; } else { os << "0:::"; } os << m_useChannel << ':'; if (m_useChannel==1) { LOCK_CHANNELS_READ; cChannel const* channelMin = Channels->GetByChannelID( m_channelMin ); cChannel const* channelMax = Channels->GetByChannelID( m_channelMax ); if (channelMax && channelMin->Number() < channelMax->Number()) os << m_channelMin << '|' << m_channelMax; else os << m_channelMin; } else if (m_useChannel==2) { os << m_channels; } else { os << "0"; } os<< ':'; os<< m_useCase << ':' << m_mode << ':' << m_useTitle << ':' << m_useSubtitle << ':' << m_useDescription << ':'; if (m_useDuration) { os << "1:"; os.appendInt<4>(m_minDuration) << ':'; os.appendInt<4>(m_maxDuration) << ':'; } else { os << "0:::"; } os<< m_useAsSearchtimer << ':' << m_useDayOfWeek << ':' << m_dayOfWeek << ':' << m_useEpisode << ':' << cToSvReplace(m_directory, "|", "!^pipe^!").replaceAll(":", "|") << ':' << m_priority << ':' << m_lifetime << ':' << m_marginstart << ':' << m_marginstop << ':' << m_useVPS << ':' << m_action << ':' << m_useExtEPGInfo << ':'; if (m_useExtEPGInfo) { for(unsigned int i=0; i 0) os << '|'; os << cToSvReplace(m_ExtEPGInfo[i], ":", "!^colon^!").replaceAll("|", "!^pipe^!"); } } os << ':'; os<< m_avoidrepeats << ':' << m_allowedrepeats << ':' << m_compareTitle << ':' << m_compareSubtitle << ':' << m_compareSummary << ':' << m_catvaluesAvoidRepeat << ':' << m_repeatsWithinDays << ':' << m_delAfterDays << ':' << m_recordingsKeep << ':' << m_switchMinBefore << ':' << m_pauseOnNrRecordings << ':' << m_blacklistmode << ':'; if (m_blacklistmode == 1) { for (unsigned int i=0; i 0) os << '|'; os << m_blacklistIDs[i]; } } os << ':'; os<< m_fuzzytolerance << ':' << m_useInFavorites << ':' << m_menuTemplate << ':' << m_delMode << ':' << m_delAfterCountRecs << ':' << m_delAfterDaysOfFirstRec << ':' << m_useAsSearchTimerFrom << ':' << m_useAsSearchTimerTil << ':' << m_ignoreMissingEPGCats; return std::string(cSv(os)); } void SearchTimer::ParseChannel(cSv data) { switch ( m_useChannel ) { case NoChannel: m_channels = tr("All"); break; case Interval: ParseChannelIDs( data ); break; case Group: m_channels = std::string(data); break; case FTAOnly: m_channels = tr("FTA"); break; } } void SearchTimer::ParseChannelIDs(cSv data) { cSplit parts(data, '|'); auto part = parts.begin(); m_channelMin = lexical_cast(*part); LOCK_CHANNELS_READ; const cChannel *channel = Channels->GetByChannelID( m_channelMin ); if (channel) m_channels = channel->Name(); if (++part == parts.end()) return; m_channelMax = lexical_cast(*part); channel = Channels->GetByChannelID( m_channelMax ); if (channel) m_channels += cSv(cToSvConcat(" - ", channel->Name())); } void SearchTimer::ParseExtEPGInfo(cSv data) { m_ExtEPGInfo = std::vector(cSplit(data, '|').begin(), cSplit::s_end()); } void SearchTimer::ParseBlacklist(cSv data) { m_blacklistIDs = std::vector(cSplit(data, '|').begin(), cSplit::s_end()); } std::string SearchTimer::StartTimeFormatted() { time_t start = cTimer::SetTime(time(NULL), (((StartTime() / 100 ) % 100) * 60 * 60) + (StartTime() % 100 * 60)); return std::string(cToSvDateTime(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 std::string(cToSvDateTime(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.") ); std::list timers = service.handler->SearchTimerList(); m_timers.assign( timers.begin(), timers.end() ); std::sort(m_timers.begin(), m_timers.end()); // m_timers.sort(); return true; } bool SearchTimers::Save(SearchTimer* searchtimer) { if (!searchtimer) return false; Epgsearch_services_v1_0 service; if ( !CheckEpgsearchVersion() || cPluginManager::CallFirstService(ServiceInterface, &service) == 0 ) throw HtmlError( tr("EPGSearch version outdated! Please update.") ); 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() == parse_int(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(parse_int( 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(m_blacklistIDs[i]) == id) return true; return false; } ExtEPGInfo::ExtEPGInfo( std::string const& data ) { m_id = -1; m_searchmode = 0; cSplit parts(data, '|'); auto part = parts.begin(); for ( int i = 0; part != parts.end(); ++i, ++part ) { switch ( i ) { case 0: m_id = parse_int( *part ); break; case 1: m_name = *part; break; case 2: m_menuname = *part; break; case 3: ParseValues( *part ); break; case 4: m_searchmode = parse_int( *part ); break; } } } void ExtEPGInfo::ParseValues(cSv data) { m_values = std::vector(cSplit(data, ',').begin(), cSplit::s_end()); } bool ExtEPGInfo::Selected(unsigned int index, cSv values) { if (index >= m_values.size()) return false; std::string extepgvalue(StringTrim(m_values[index])); for (cSv part: cSplit(values, ',')) if (StringTrim(part) == extepgvalue) return true; for (cSv part: cSplit(values, ';')) if (StringTrim(part) == extepgvalue) return true; for (cSv part: cSplit(values, '|')) if (StringTrim(part) == extepgvalue) return true; for (cSv part: cSplit(values, '~')) if (StringTrim(part) == extepgvalue) return true; return false; } ExtEPGInfos::ExtEPGInfos() { Epgsearch_services_v1_0 service; if ( !CheckEpgsearchVersion() || cPluginManager::CallFirstService(ServiceInterface, &service) == 0 ) throw HtmlError( tr("EPGSearch version outdated! Please update.") ); std::list infos = service.handler->ExtEPGInfoList(); m_infos.assign( infos.begin(), infos.end() ); } ChannelGroup::ChannelGroup( std::string const& data ) { cSplit parts(data, '|'); auto part = parts.begin(); for (int i = 0; part != parts.end(); ++i, ++part) { switch ( i ) { case 0: m_name = *part; break; } } } 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 ) { cSplit parts(data, ':'); auto part = parts.begin(); for ( int i = 0; part != parts.end(); ++i, ++part ) { switch ( i ) { case 0: m_id = parse_int( *part ); break; case 1: m_search = cToSvReplace( *part, "|", ":" ).replaceAll("!^pipe^!", "|" ); break; } } } 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 ) { cSplit parts(data, ':'); auto part = parts.begin(); for ( int i = 0; part != parts.end(); ++i, ++part ) { switch ( i ) { case 0: m_searchId = parse_int( *part ); break; case 1: m_eventId = parse_int( *part ); break; case 2: m_title = cToSvReplace( *part, "|", ":" ); break; case 3: m_shorttext = cToSvReplace( *part, "|", ":" ); break; case 4: m_description = cToSvReplace( *part, "|", ":" ); break; case 5: m_starttime = parse_int( *part ); break; case 6: m_stoptime = parse_int( *part ); break; case 7: m_channel = lexical_cast(*part); break; case 8: m_timerstart = parse_int( *part ); break; case 9: m_timerstop = parse_int( *part ); break; case 10: m_file = *part; break; case 11: m_timerMode = parse_int( *part ); break; } } } const cEvent* SearchResult::GetEvent(const cChannel* Channel, const cSchedules *Schedules) { if (!Channel) return nullptr; const cSchedule *Schedule = Schedules->GetSchedule(Channel); if (!Schedule) return nullptr; #if APIVERSNUM >= 20502 return Schedule->GetEventById(m_eventId); #else return Schedule->GetEvent(m_eventId); #endif } std::vector SearchResults::queryList; 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(cSv query) { for (auto it = queryList.begin(); it != queryList.end(); ++it) if (it->Value() == query) { it->Used(); return std::string(cToSvXxHash128(query)); } queryList.emplace_back(query); return std::string(cToSvXxHash128(query)); } std::string SearchResults::GetQuery(cSv md5) { if (md5.empty()) return std::string(); std::string query; for (auto it = queryList.begin(); it != queryList.end(); ++it) { if(md5 == cSv(cToSvXxHash128(it->Value() ))) { query = it->Value() ; it->Used(); break; } } return query; } void SearchResults::CleanQuery() { time_t now = time(NULL); size_t old_s = queryList.size(); for (auto it = queryList.begin(); it != queryList.end();) { if(it->IsOutdated(now)) it = queryList.erase(it); else ++it; } if (old_s != queryList.size() ) { size_t mem = 0; for (auto it = queryList.begin(); it != queryList.end(); ++it) mem += it->Value().length(); dsyslog("live, cleanup queryList, size was %zu, is %zu, requ. mem %zu", old_s, queryList.size(), mem); } } 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.5.0/epgsearch.h000066400000000000000000000362151477677561600167160ustar00rootroot00000000000000#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 #include "stringhelpers.h" 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(cSv 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(cSv data); void ParseChannelIDs(cSv data); void ParseExtEPGInfo(cSv 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, cSv values); private: int m_id; std::string m_name; std::string m_menuname; std::vector m_values; int m_searchmode; void ParseValues(cSv 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::vector 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, const cSchedules *Schedules); const cChannel* GetChannel(const cChannels *Channels) { return Channels->GetByChannelID(m_channel); } 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; }; #define QUERYLIFETIME 60*20 // seconds. If a query is not used in this timeframe, it is deleted class cQueryEntry { public: cQueryEntry(cSv queryString): value(queryString) { Used(); } bool IsOutdated(time_t now) { return now > outdated_at; } cSv Value() { return value; } void Used() { outdated_at = time(NULL) + QUERYLIFETIME; } private: time_t outdated_at; std::string value; }; class SearchResults { static std::vector queryList; 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(cSv query); static std::string GetQuery(cSv md5); static void CleanQuery(); 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.5.0/epgsearch/000077500000000000000000000000001477677561600165365ustar00rootroot00000000000000vdr-plugin-live-3.5.0/epgsearch/services.h000066400000000000000000000154261477677561600205420ustar00rootroot00000000000000/* 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.5.0/exception.h000066400000000000000000000005151477677561600167450ustar00rootroot00000000000000#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.5.0/ffmpeg.cpp000066400000000000000000000147261477677561600165570ustar00rootroot00000000000000#include "ffmpeg.h" #include "setup.h" #include #include #include #include #include #include #include namespace vdrlive { FFmpegThread::FFmpegThread() :cThread("stream utility handler") { dsyslog("Live: FFmpegTread() created"); } FFmpegThread::~FFmpegThread() { Stop(); dsyslog("Live: FFmpegTread() destructed"); } void FFmpegThread::StartFFmpeg(std::string s, std::string url, std::string tag) { if ( targetUrl.compare(url) || targetTag.compare(tag)) { dsyslog("Live: FFmpegTread::StartFFmpeg() change %s [%s] -> %s [%s]", targetUrl.c_str(), targetTag.c_str(), url.c_str(), tag.c_str()); if ( Active() ) Stop(); targetUrl = url; targetTag = tag; } 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 url = %s [%s]", targetUrl.c_str(), targetTag.c_str()); // read command for tag from FFMPG configuration file std::string packerCmd = LiveSetup().ReadStreamVideoFFmpegCmdFromConfigFile(targetTag); if (packerCmd.empty()) { esyslog("ERROR: live could not find FFMPEG command for tag \"%s\"", targetTag.c_str()); } else { std::stringstream ss; ss.str(""); ss << "\"http://localhost:" << LiveSetup().GetStreamdevPort() << "/" << targetUrl << "\""; packerCmd.replace(packerCmd.find(""), 7, ss.str()); dsyslog("Live: FFmpegTread::Action packetizer cmd: %s", packerCmd.c_str()); try { cPipe2 pp; int retry = 0; int count = 0; do { ss.str(""); ss << "mkdir -p " << tmpHlsBufferDir << session << " && " "cd " << tmpHlsBufferDir << session << " && rm -rf * && " "exec " << packerCmd << " " "-f hls -hls_time 1 -hls_start_number_source datetime -hls_flags delete_segments " "-master_pl_name master_"; ss << targetUrl; ss << ".m3u8 ffmpeg_"; ss << targetUrl; 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 << tmpHlsBufferDir << session << "/master_"; ss << targetUrl; 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.5.0/ffmpeg.h000066400000000000000000000015741477677561600162210ustar00rootroot00000000000000#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, std::string url, std::string tag); void Stop(); void Touch(); protected: void Action(); private: cCondWait cw; bool touch = false; std::string targetUrl; std::string targetTag; 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.5.0/filecache.cpp000066400000000000000000000016471477677561600172140ustar00rootroot00000000000000 #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.5.0/filecache.h000066400000000000000000000032541477677561600166550ustar00rootroot00000000000000#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.5.0/global.mk000066400000000000000000000017111477677561600163660ustar00rootroot00000000000000# # 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.5.0/i18n.cpp000066400000000000000000000014131477677561600160570ustar00rootroot00000000000000/* 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.5.0/i18n.h000066400000000000000000000005711477677561600155300ustar00rootroot00000000000000#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.5.0/live.cpp000066400000000000000000000061671477677561600162520ustar00rootroot00000000000000/* * live.cpp: A plugin for the Video Disk Recorder * * See the README file for copyright information and how to reach the author. */ #include "live.h" #include "setup.h" #include "tools.h" #include "status.h" #include "timers.h" #include "preload.h" #include "users.h" #include "services_live.h" #include "epgsearch.h" namespace vdrlive { const char *Plugin::VERSION = LIVEVERSION; const char *Plugin::DESCRIPTION = LIVESUMMARY; std::string Plugin::m_configDirectory; std::string Plugin::m_resourceDirectory; const std::locale g_locale = std::locale(""); const std::collate& 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::Initialize(void) { m_configDirectory = canonicalize_file_name(cPlugin::ConfigDirectory( PLUGIN_NAME_I18N )); m_resourceDirectory = canonicalize_file_name(cPlugin::ResourceDirectory( PLUGIN_NAME_I18N )); return LiveSetup().Initialize(); } bool Plugin::Start(void) { // 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::Housekeeping(void) { SearchResults::CleanQuery(); } 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 ); } class cLiveImageProviderImp: public cLiveImageProvider { public: virtual std::string getImageUrl(const std::string &imagePath, bool fullPath = true) { if (LiveSetup().GetTvscraperImageDir().empty() || LiveSetup().GetServerUrl().empty()) { if (m_errorMessages) { if (LiveSetup().GetTvscraperImageDir().empty() ) esyslog("live: ERROR plugin tvscraper/scraper2vdr missing or to old. tvscraper 1.2.1 or later is required"); if (LiveSetup().GetServerUrl().empty() ) esyslog("live: ERROR please provide -u URL, --url=URL"); } m_errorMessages = false; return fullPath?imagePath:LiveSetup().GetTvscraperImageDir() + imagePath; } return concat(LiveSetup().GetServerUrlImages(), (fullPath?ScraperImagePath2Live(imagePath):imagePath)); } virtual ~cLiveImageProviderImp() {} private: bool m_errorMessages = true; }; bool Plugin::Service(const char *Id, void *Data) { if (strcmp(Id, "GetLiveImageProvider") == 0) { if (Data == NULL) return true; cGetLiveImageProvider* call = (cGetLiveImageProvider*) Data; call->m_liveImageProvider = std::make_unique(); return true; } return false; } } // namespace vdrlive VDRPLUGINCREATOR(vdrlive::Plugin); // Don't touch this! vdr-plugin-live-3.5.0/live.h000066400000000000000000000030251477677561600157050ustar00rootroot00000000000000#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 cLiveWorker; 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 bool Initialize(void); virtual void Stop(void); virtual void Housekeeping(void); virtual cString Active(void); virtual cMenuSetupPage *SetupMenu(void); virtual bool SetupParse(const char *Name, const char *Value); virtual bool Service(const char *Id, void *Data = NULL); 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.5.0/live/000077500000000000000000000000001477677561600155345ustar00rootroot00000000000000vdr-plugin-live-3.5.0/live/css/000077500000000000000000000000001477677561600163245ustar00rootroot00000000000000vdr-plugin-live-3.5.0/live/css/siteprefs.css000066400000000000000000000140231477677561600210420ustar00rootroot00000000000000/* ###################### # 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 to adjust the width of each column in multicolumn view. In case of ** many columns, min-width will be used in combination with vertical scrolling. */ /* table.mschedule div.content1 { min-width: 15em; max-width: 30em; } */ /* Uncomment the definition below to have all tables expanded to the full ** page width. */ /* table { width: 100%; } */ /* Uncomment the definition below to restrict the width of EPG and recording ** images to 120 pixels. Keeping the comment displays the images at their ** native sizes in the popup windows. */ /* .info-win span.epg_images { max-width: 120px; } */ /* Customize the settings below to match the local image dimensions and the ** desired box shadow. They have no effect unless one of the sections for ** enlarging images upon hovering are uncommented. */ /* EPG images within details */ @property --epg-image-hover-scale { syntax: ""; initial-value: 300%; inherits: false; } @property --epg-image-shadow-size { syntax: ""; initial-value: 4px; inherits: false; } @property --epg-image-shadow-blur { syntax: ""; initial-value: 4px; inherits: false; } @property --epg-image-shadow-color { syntax: ""; initial-value: gray; inherits: false; } /* recording thumbnail images */ @property --thumb-image-hover-scale { syntax: ""; initial-value: 800%; inherits: false; } @property --thumb-image-shadow-size { syntax: ""; initial-value: 3px; inherits: false; } @property --thumb-image-shadow-blur { syntax: ""; initial-value: 3px; inherits: false; } @property --thumb-image-shadow-color { syntax: ""; initial-value: gray; inherits: false; } /* channel logos */ @property --logo-hover-scale { syntax: ""; initial-value: 625%; inherits: false; } @property --logo-shadow-size { syntax: ""; initial-value: 2px; inherits: false; } @property --logo-shadow-blur { syntax: ""; initial-value: 2px; inherits: false; } @property --logo-shadow-color { syntax: ""; initial-value: gray; inherits: false; } @property --logo-background-color { syntax: ""; initial-value: #E9EFEFFF; inherits: false; } /* common hover transition settings */ @property --hover-transition-time { syntax: "