pax_global_header00006660000000000000000000000064152135343510014514gustar00rootroot0000000000000052 comment=5df12e38d5cf22a8a264d1cb0ff09b1eeb0caf1b vdr-plugin-live-3.5.6/000077500000000000000000000000001521353435100145535ustar00rootroot00000000000000vdr-plugin-live-3.5.6/.editorconfig000066400000000000000000000005301521353435100172260ustar00rootroot00000000000000# https://editorconfig.org/ # top-most EditorConfig file root = true [*] # Unix-style newlines with a newline ending every file end_of_line = lf insert_final_newline = true # Set default charset charset = utf-8 # 2 space indentation indent_style = space indent_size = 2 # Tab indentation (no size specified) [Makefile] indent_style = tab vdr-plugin-live-3.5.6/.gitignore000066400000000000000000000026461521353435100165530ustar00rootroot00000000000000.#* .vscode CVS # supplemental files .cproject .cvsignore .dependencies .project apply-patches.sh *.BASE *.LOCAL *.REMOTE *~ *.bak *.code-workspace *.new *.old *.orig *.rej *.sav *.tmp # compilation results css/*.cpp javascript/*.cpp pages/*.edep pages/*.cpp gen_version_suffix.h *.a *.o *.so .libs locale # gettext files: see https://stackoverflow.com/questions/50658622/how-to-correctly-use-gettext-with-git/51769371#51769371 # - The .pot PO Template file that contains the master message catalog. This is under version control in every project I know although it is strictly spoken generated and redundant. # - The .po files with the individual translations. They are sometimes generated, sometimes not. When you merge in new strings with msgmerge, they are generated. But when your translators send back a new translation, they are not. You have to put them under version control because they contain the translations. # - The .mo files that the .po files compile to. Do not put them under version control but ship them with releases (source and binary). *.mo # but, see https://stackoverflow.com/questions/57454610/git-and-po-and-pot-files-i-dont-know-how-to-stop-large-diffs-in-my-commits-wo # po/git.pot: don't check in result of "make pot" *.pot # actually, I think most of our translators just use the po file in their language and add missing texts, and don't need the pot file. # -> we don't make the pot file available in git vdr-plugin-live-3.5.6/CONTRIBUTORS000066400000000000000000000022031521353435100164300ustar00rootroot00000000000000Grams 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.6/COPYING000066400000000000000000000431251521353435100156130ustar00rootroot00000000000000 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.6/HISTORY000066400000000000000000000226651521353435100156520ustar00rootroot00000000000000VDR 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 - substantial rework of GUI and style sheets 2026-06-14: Version 3.5.6 - use SVG icons vdr-plugin-live-3.5.6/Makefile000066400000000000000000000212111521353435100162100ustar00rootroot00000000000000# # 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 := \# ifeq ($(VERSION),) VERSION := $(shell awk '/$(HASH)define LIVEVERSION/ { print $$3 }' setup.h | sed -e 's/[";]//g') endif # figure out VERSION_SUFFIX ifeq ($(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) endif endif ifneq ($(shell which quilt),) ifeq ($(shell quilt applied 2>&1 > /dev/null; echo $$?),0) VERSION_SUFFIX += _quilt_$(shell quilt applied | tr '\n' '_') endif endif # $(info VERSION_SUFFIX = "$(VERSION_SUFFIX)") endif 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),) TNTNET_VERSION = $(shell $(PKG_CONFIG) --modversion tntnet) CXXFLAGS += $(shell $(PKG_CONFIG) --cflags tntnet) LIBS += $(shell $(PKG_CONFIG) --libs tntnet) else TNTNET_VERSION = $(shell tntnet-config --version) CXXFLAGS += $(shell tntnet-config --cxxflags) LIBS += $(shell tntnet-config --libs) endif TNTVERSION = $(shell echo $(TNTNET_VERSION) | sed -e's/\.//g' | sed -e's/pre.*//g' | awk '/^..$$/ { print $$1."000"} /^...$$/ { print $$1."00"} /^....$$/ { print $$1."0" } /^.....$$/ { print $$1 }') # $(info $$TNTVERSION is [${TNTVERSION}]) CXXTOOL-CONFIG := $(shell which cxxtools-config 2>/dev/null) ifeq ($(CXXTOOL-CONFIG),) CXXTOOLS_VERSION = $(shell $(PKG_CONFIG) --modversion cxxtools) else CXXTOOLS_VERSION = $(shell cxxtools-config --version) endif CXXTOOLVER := $(shell echo $(CXXTOOLS_VERSION) | sed -e's/\.//g' | sed -e's/pre.*//g' | awk '/^..$$/ { print $$1."000"} /^...$$/ { print $$1."00"} /^....$$/ { print $$1."0" } /^.....$$/ { print $$1 }') # For rough image scaling, used by VDR core anyway LIBS += -ljpeg ### Optional configuration features PLUGINFEATURES := CXXFLAGS += -std=c++17 -Wfatal-errors -Wundef ### 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 livefeatures.o timerconflict.o \ users.o osd_status.o ffmpeg.o xxhash.o content.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: version_suffix tool-versions 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 VERSION=$(VERSION) VERSION_SUFFIX=$(VERSION_SUFFIX) $* ### 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 confirm.h $(I18Npot): $(I18Npot_deps) $(call PRETTY_PRINT,"GT" $@) $(Q)xgettext -C -cTRANSLATORS --no-wrap --no-location -k -ktr -ktrNOOP --package-name=VDR-LIVE --package-version=$(VERSION) -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: version_suffix tool-versions 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: version_suffix version_suffix: @echo "VERSION is $(VERSION)" @echo "VERSION_SUFFIX = \"$(VERSION_SUFFIX)\"" .PHONY: tool-versions tool-versions: @echo "TNTNET_VERSION is ${TNTNET_VERSION}, adding \"-DTNTVERSION=$(TNTVERSION)\"" @echo "CXXTOOLS_VERSION is ${CXXTOOLS_VERSION}, adding \"-DCXXTOOLVER=$(CXXTOOLVER)\"" .PHONY: install-lib install-lib: version_suffix lib recursive-soinst .PHONY: install-web install-web: version_suffix @mkdir -p $(DESTDIR)$(RESDIR)/plugins/$(PLUGIN) @cp -a live/* $(DESTDIR)$(RESDIR)/plugins/$(PLUGIN)/ .PHONY: install-conf install-conf: version_suffix 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.6/README000066400000000000000000000172661521353435100154470ustar00rootroot00000000000000This 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. The intended use is a save local network, where family and friends can access the endpoint provided by live. Make sure to NOT expose this endpoint to the internet. For this use case (exposure to internet) live is NOT secure enough. 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 libjpeg Tntnet provides basic web server functions for LIVE and needs cxxtools. Optional: epgsearch >= 2.4.5, for versatile search timers and searching the EPG on demand streamdev >= 0.6.0, for streaming live video and recordings to browser or player 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 vdr resource directory . This is: - /video: The VDR default - /usr/share/vdr: On debian like distributions - It can be changed with the parameters --resdir=dir, -c dir or -v dir (see 'vdr --help' for details) cp -a /live /plugins/ The default port is 8008. ====== 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: ===================== Use "vdr -Plive --help" to get a compleate list of command line options See "How to make LIVE listen for SSL connections" section below for SSL setup command line options. 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 an 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.6/conf/000077500000000000000000000000001521353435100155005ustar00rootroot00000000000000vdr-plugin-live-3.5.6/conf/ffmpeg.conf000066400000000000000000000030071521353435100176130ustar00rootroot00000000000000# 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.6/confirm.h000066400000000000000000000076301521353435100163670ustar00rootroot00000000000000/* Requirements: * see https://www.vdr-portal.de/forum/thread/137309-live-vereinheitlichung-der-best%C3%A4tigungs-popups/?postID=1391352#post1391352 * * - Distinguish between popup after selection of elements and just a button press (where no elements need to be selected * - Reason: Allow to disable popup in case of selection of elements + button is required * */ #ifndef CONFIRM_H #define CONFIRM_H #include #include "live.h" #include "stringhelpers.h" #include "recman.h" #include "timers.h" #include "epgsearch.h" #include "users.h" namespace vdrlive { // note: list of ids must also be available in live/js/live/infowin.js, action_id //(action_id == "del_" || action_id == "pur_" || action_id == "res_" || action_id == "det_" || action_id == "des_") // "del_" delete recording // "pur_" permanently delete recording // "res_" restore recording // "det_" delete timer // "des_" delete search timer // typedef std::string (*tConfirmationQuestion)(cSv id); typedef std::vector (*tObjectNames)(cSv id); typedef int (*tPerformAction)(cSv id, std::string &message); // return 0 on success; inline std::vector one_object(cSv id) { std::vector result; result.push_back(std::string()); return result; } class cConfirm { public: const char *m_id; // like pur_ for purge_recording eUserRights m_user_rights; const char *m_headline; // text, headline of popup const char *m_warning; // text, warning in popup (can be nullptr) const char *m_prompt; // text for confirmation button, headline if nullptr) tConfirmationQuestion m_confirmation_question; tObjectNames m_objectNames; tPerformAction m_perform_action; const char *get_headline() const { return tr(m_headline); } std::string get_question(cSv id) const { return m_confirmation_question(id.substr(4)); } std::vector get_object_names(cSv id) const { return m_objectNames(id.substr(4)); } std::string get_prompt() const { return tr(m_prompt && *m_prompt ? m_prompt : m_headline); } int perform_action(cSv id, std::string &message) const { return m_perform_action(id.substr(4), message); } bool currentUserHasRight() const { return cUser::CurrentUserHasRightTo(m_user_rights); } }; inline bool operator< (const cConfirm &c1, const cConfirm &c2) { return cSv(c1.m_id) < cSv(c2.m_id); } inline bool operator< (const cConfirm &c1, cSv c2) { return cSv(c1.m_id) < c2; } inline bool operator< (cSv c1 , const cConfirm &c2) { return c1 < cSv(c2.m_id); } inline bool operator==(const cConfirm &c1, const cConfirm &c2) { return cSv(c1.m_id) == cSv(c2.m_id); } inline static const cSortedVector> g_confirm_popups = { { "del_", UR_DELRECS, trNOOP("Delete recording"), nullptr, trNOOP("Delete"), &RecordingsManager_DeleteConfirmationQuestion, &RecordingsManager_object_names, &RecordingsManager_DeleteRecording}, { "res_", UR_DELRECS, trNOOP("Restore recording"), nullptr, trNOOP("Restore"), &RecordingsManager_RestoreConfirmationQuestion, &RecordingsManager_object_names, &RecordingsManager_RestoreRecording}, { "pur_", UR_DELRECS, trNOOP("Permanently delete recording"), trNOOP("Warning: This cannot be undone!"), trNOOP("Delete permanently"), &RecordingsManager_PurgeConfirmationQuestion, &RecordingsManager_object_names, &RecordingsManager_PurgeRecording}, { "det_", UR_DELTIMERS, trNOOP("Delete timer"), nullptr, trNOOP("Delete"), &TimerManager_DeleteConfirmationQuestion, &one_object, &TimerManager_DeleteTimer}, { "des_", UR_DELSTIMERS, trNOOP("Delete search timer"), nullptr, trNOOP("Delete"), &SearchTimers_DeleteConfirmationQuestion, &one_object, &SearchTimers_DeleteSearchTimer} }; inline const cConfirm *get_confirm_popup(cSv id) { auto r = g_confirm_popups.find(id.substr(0,4)); if (r != g_confirm_popups.end()) return &(*r); return nullptr; } } #endif vdr-plugin-live-3.5.6/content.cpp000066400000000000000000000115061521353435100167340ustar00rootroot00000000000000#include "content.h" #include "stringhelpers.h" #include #include #include #define JPEG_QUALITY 85 #define JPEG_BUFFER_SIZE 65536 namespace vdrlive { namespace { struct error_mgr : public jpeg_error_mgr { error_mgr() { jpeg_std_error(this); error_exit = [](j_common_ptr cinfo) { throw cinfo->err; }; } }; class cpp_source_mgr : public jpeg_source_mgr { std::ifstream& in; const std::unique_ptr buffer; void InitSource() { in.rdbuf()->pubsetbuf(nullptr, 0); // avoid double buffering in.seekg(0); } boolean FillInputBuffer() { in.read((char*)buffer.get(), JPEG_BUFFER_SIZE); bytes_in_buffer = in.gcount(); if (!bytes_in_buffer) return FALSE; next_input_byte = buffer.get(); return TRUE; } void SkipInputData(long num) { if (num <= 0) return; if ((unsigned long)num <= bytes_in_buffer) { next_input_byte += num; bytes_in_buffer -= num; } else { in.seekg(num - bytes_in_buffer, in.cur); FillInputBuffer(); } } public: cpp_source_mgr(std::ifstream& in) : in(in), buffer(new JOCTET[JPEG_BUFFER_SIZE]) { bytes_in_buffer = 0; init_source = [](j_decompress_ptr cinfo) { ((cpp_source_mgr*)cinfo->src)->InitSource(); }; fill_input_buffer = [](j_decompress_ptr cinfo) { return ((cpp_source_mgr*)cinfo->src)->FillInputBuffer(); }; skip_input_data = [](j_decompress_ptr cinfo, long num) { ((cpp_source_mgr*)cinfo->src)->SkipInputData(num); }; resync_to_restart = jpeg_resync_to_restart; term_source = [](j_decompress_ptr cinfo) {}; } }; class decompress : public jpeg_decompress_struct { error_mgr Err; cpp_source_mgr Src; decompress(const decompress&) = delete; void operator=(const decompress&) = delete; public: decompress(std::ifstream& in) : Src(in) { jpeg_create_decompress(this); err = &Err; src = &Src; } ~decompress() { jpeg_destroy_decompress(this); } }; class cpp_destination_mgr : public jpeg_destination_mgr { void Enlarge() { size_t s = data.size(); data.resize(s + JPEG_BUFFER_SIZE); next_output_byte = (JOCTET*)data.data() + s; free_in_buffer = JPEG_BUFFER_SIZE; } void Truncate() { data.resize(data.size() - free_in_buffer); } public: std::string data; cpp_destination_mgr() { free_in_buffer = 0; init_destination = [](j_compress_ptr cinfo) { ((cpp_destination_mgr*)cinfo->dest)->Enlarge(); }; empty_output_buffer = [](j_compress_ptr cinfo) { ((cpp_destination_mgr*)cinfo->dest)->Enlarge(); return TRUE; }; term_destination = [](j_compress_ptr cinfo) { ((cpp_destination_mgr*)cinfo->dest)->Truncate(); }; } }; class compress : public jpeg_compress_struct { error_mgr Err; cpp_destination_mgr Dest; public: compress() { jpeg_create_compress(this); err = &Err; dest = &Dest; } ~compress() { jpeg_destroy_compress(this); } std::string& Data() { return Dest.data; } }; } // namespace std::size_t cFileContent::ReduceImageSize(unsigned width, unsigned height) { struct decompress src(file); try { int ret_header = jpeg_read_header(&src, TRUE); if (ret_header != JPEG_HEADER_OK) { esyslog3("jpeg_read_header ret code = ", ret_header); goto nope; } // calculate decimation factor int df = 0; do { width <<= 1; height <<= 1; } while ((src.image_width >= width || src.image_height >= height) && ++df != 3); if (df == 0) // no scaling required goto nope; src.scale_num = 1; src.scale_denom = 1 << df; // init decompress src.data_precision = 8; // no HDR in preview jpeg_start_decompress(&src); // allocate scanline buffer unsigned row_stride = src.output_width * src.output_components; JSAMPARRAY buffer = (*src.mem->alloc_sarray)((j_common_ptr)&src, JPOOL_IMAGE, row_stride, src.rec_outbuf_height); // init compress compress dst; dst.image_width = src.output_width; dst.image_height = src.output_height; dst.input_components = src.output_components; dst.in_color_space = src.out_color_space; dst.data_precision = 8; jpeg_set_defaults(&dst); jpeg_set_quality(&dst, JPEG_QUALITY, TRUE); jpeg_start_compress(&dst, TRUE); // transfer data while (src.output_scanline < src.output_height) { unsigned lines = jpeg_read_scanlines(&src, buffer, src.rec_outbuf_height); jpeg_write_scanlines(&dst, buffer, lines); } jpeg_finish_compress(&dst); jpeg_finish_decompress(&src); // capture result reduced = std::move(dst.Data()); // esyslog3("reduced size = ", reduced.size()); return reduced.size(); } catch (jpeg_error_mgr* l_jpeg_error) { jpeg_abort_decompress(&src); // esyslog3("reached catch"); } // from error_mgr.error_exit nope: //esyslog3("reached nope, reduced.size() = ", reduced.size() ); file.seekg(0); return 0; // keep original file content } } vdr-plugin-live-3.5.6/content.h000066400000000000000000000017061521353435100164020ustar00rootroot00000000000000#ifndef VDR_LIVE_CONTENT_H_ #define VDR_LIVE_CONTENT_H_ #include namespace vdrlive { /// File content class /// @details May represent a reduced image or just a file. class cFileContent { std::ifstream file; std::string reduced; public: cFileContent(const std::string& path) : file(path, std::ifstream::binary) {} /// Check whether the source stream is valid. explicit operator bool() const { return !!file; } /// Scale an image file for a minimum target size. /// @return Size of the reduced data in bytes. /// 0 if failed. In this case the original file content is returned. /// @remarks The scaling uses DCT decimation only and will likely return a larger image. std::size_t ReduceImageSize(unsigned width, unsigned height); /// Write the (reduced) content to a stream. void WriteTo(std::ostream& out) { if (reduced.empty()) out << file.rdbuf(); else out << reduced; } }; } #endif // VDR_LIVE_CONTENT_H_ vdr-plugin-live-3.5.6/doc/000077500000000000000000000000001521353435100153205ustar00rootroot00000000000000vdr-plugin-live-3.5.6/doc/ChangeLog000066400000000000000000000314601521353435100170760ustar00rootroot00000000000000unrecorded 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.6/doc/LOCKs.txt000066400000000000000000000005501521353435100167740ustar00rootroot00000000000000Concept: 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 parameter reduces the chance that the developer will forget this rule ... vdr-plugin-live-3.5.6/doc/TODO.txt000066400000000000000000000022441521353435100166300ustar00rootroot00000000000000This 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) -> VOID, as IE is out of service and modern browsers do not show such misbehavior. - Create a CSS-themeing friendly URL scheme, i.e., something like img//button.png. Where default theme is always taken if is not found. -> UNNECESSARY, as themes are configured through the setup form. URLs not referring a defined scheme should validly fail. - Give users the chance to override the selected style with some user-override settings. -> DONE, as 'siteprefs.css' are are available. - 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. -> DONE, as the EPG box periodically polls the server to update the current show. - Provide a way to get the information on extra 'static' pages. vdr-plugin-live-3.5.6/doc/css-themeing.txt000066400000000000000000000144141521353435100204530ustar00rootroot00000000000000Live themeing in a few steps ============================ - Copy the 'themes' directory from the sources to $VDRCONFIG/plugins/live (default: /video/vdr/plugins/live) - Go to the setup page and select the desired theme from the drop-down menu. You can add themes of your own by creating a subdirectory with the name of your theme in the 'themes' directory. An underscore will be translated to a space in the themes' drop-down menu. Read further for more detailed information about themeing. How to do Live themeing with CSS ================================ Live supports CSS themeing. While the structure and layout of the HTML pages is given by the plugin, it allows changing the look through CSS and theme-specific images. Themable resources ------------------ CSS style sheets and referenced images are themeable. This means that a theme can - adjust or add selected properties (dimensions, scales, etc.) in its 'layout.css' style sheet - refine, amend or add colors and selected formatting rules in its 'theme.css' style sheet - overlay or add distinct icons in its 'img' directory Properties not defining colors (i.e., not of type 'syntax: ""') are located in a style sheet 'layout.css' instead of 'theme.css' to allow sharing property adjustments among themes by means of symbolic links. An example are the "large" theme variants that commonly increase the icon size by 50 percent via sym-linking the centrally provided style sheet 'layout-large.css' and thereby avoiding copy redundancy. Access scheme for the CSS style sheets -------------------------------------- Each Live page requests at least five style sheets in the following order: 1. 'properties.css' (global layout properties) 2. 'styles.css' (built-in styles for page display) 3. the theme's 'layout.css' (adjusted layout properties) 4. the theme's 'theme.css' (adjusted colors and styles) 5. 'siteprefs.css' (site-specific adjustments of properties and styles) Location for the style sheets ----------------------------- The initial style sheets 'properties.css' and 'styles.css' provide a basic layout and coloring. Both are built-in style sheets and can not be altered after Live has been compiled and installed. They are loaded via the following URLs.: css/properties.css css/theme.css The theme's style sheets 'layout.css' and 'theme.css' are loaded via the following URLs: themes//css/layout.css themes//css/theme.css The style sheet for the site-preferences is loaded via this URL: css/siteprefs.css Access scheme for themeable images ---------------------------------- All themeable images in the pages that Live delivers to the browser are accessed via the URL: themes//img/ If an image is not found under that URL, the image is further searched in: img/ If even not found there, an attempt to deliver a built-in image is made. Location of the resources in the file system -------------------------------------------- All themeable content must be present in the directory specified by 'GetConfigDirectory()'. At runtime, 'GetConfigDirectory()' returns the position in the file system where the plugin's configuration files are stored. The location is build from the VDR's configuration path with the plugin's name appended (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 provides distinct colors and icons for the display of the various pages. The general page layout is defined by the global style sheets 'properties.css' (dimensions) and 'styles.css' (coloring and formatting based upon the dimensions). Likewise, a theme's style sheet 'layout.css' adjusts or amends dimensions, whereas its style sheet 'themes.css' provides coloring and possibly refines formatting. A theme package consists of a directory with the theme's name (with underscores translated to spaces for the drop-down menu), containing the subdirectories 'css' and 'img'. Under 'css' and 'img', no other subdirectories are allowed for security reasons (see below). In the subdirectory 'css', two files must be present: - a (possibly empty) style sheet 'layout.css' for adjusted dimensions - a style sheet 'theme.css' for defining color properties referenced by styles in 'styles.css' or supplemental styles in 'theme.css' Additional images referenced by the style sheet and images replacing the default images are located the 'img' subdirectory. Replacement images must have the same name as the image to be replaced. Icons that cannot seamlessly be scaled (i.e., are no SVG images) should have a native dimension of 16x16 pixels for best representation in the page layout, or they will be scaled to match the theme's desired icon size otherwise. The Live distribution comes with a few predefined theme packages. You should 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 theme becomes active. Live detects the available themes dynamically by scanning the 'themes' directory in plugin's configuration directory for available themes and creates the drop-down menu from this information. Thus, the installation of a new theme can easily be done by unpacking a theme archive in the 'themes' directory. This assumes that 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. This means that 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 requested path. The downside of this is that a theme designer cannot use additional directories below the 'img' and 'css' subdirectories. 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.6/doc/dev-conventions.txt000066400000000000000000000106741521353435100212120ustar00rootroot00000000000000Live 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 and devices, using Web 2.0 techniques for improved users experience. Nowadays, major browsers like FireFox, Chrome, Edge, Opera, Safari or Konqueror are available on all platforms (smartphones with Android or iOS and desktops with macOS, Linux or Windows). And as these browsers get updated automatically (or can easily be updated on demand), we can validly expect that they conform to recent standards of HTML, CSS and ECMAScript. Nevertheless, their support of certain features can subtly differ. So compatibility hints as, e.g., denoted in the "MDN web docs" should be obeyed. Consider the use of an adequate alternative if a certain platform lacks support for a given feature. Examples are the use of '@support' checks or the use of '--webkit-*' / '--moz-*' properties in CSS. Another aspect are the varying screen sizes, ranging from smartphones with quite small screens to desktops with rather large screens. Live pages should be designed such that they provide an identical user experience on all these platforms. In particular, Live pages should adapt to the screen size (i.e., use so-called "responsive design") and hide less important information as appropriate. With or without ECMAScript -------------------------- Although all modern browsers support ECMAScript, users may opt to have scripting disabled. We thus need to make sure all functions Live wants to provide are 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 the 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; this will be already done when using the Live page framework. Please note that the 'title' attribute must properly HTML-encode the content, particularly regarding HTML tags to be embedded, which requires substitution of reserved characters by '&', '<', '>' and '"'. For popup windows that asynchronously load their content, 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 loaded, or on small screens, 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 style sheets 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 style sheet. This is the easy part, because style sheets 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="some-image"> For use in supplemental Javascript code, a twin function is available, which is provided by the '<& pageelems.scripts &>' component. The example from above would thus look like: const img = document.getElementById('image-id'); img.src = getThemedLink('img', ''); img.alt = 'some-image'; Note that screen readers cite the image URL if no 'alt' attribute has been assigned. You should thus provide an empty 'alt' attribute if no specific information is associated with the image that should be read out to a user. Please take a look in the existing '*.ecpp' pages for additional usage examples. vdr-plugin-live-3.5.6/doc/keyboard_shortcuts.txt000066400000000000000000000025131521353435100220000ustar00rootroot00000000000000Keyboard shortcuts in the 'Remote' view: Key Legacy Map Extended Map -------- ------------ ------------ Tab Menu Menu Esc Back Back Enter ↵ OK OK Up ▲ Up Up Left ◀ Left Left Right ▶ Right Right Down ▼ Down Down F1 Red Red F2 Green Green F3 Yellow Yellow F4 Blue Blue F5 FastRew Schedule F6 FastFwd Timers F7 — Recordings F8 — Channels F9 Menu Setup F10 Mute Commands F11 Volume+ Volume+ F12 Volume− Volume− PgUp ▢⬆ Channel+ Channel PgDn ▢⬇ Channel− Channel For X & softhddevice, remote.conf: XKeySym.Up Up XKeySym.Down Down XKeySym.Right Right XKeySym.Left Left XKeySym.Menu Tab XKeySym.Menu F9 XKeySym.Ok Return XKeySym.Back Escape XKeySym.Red F1 XKeySym.Green F2 XKeySym.Yellow F3 XKeySym.Blue F4 XKeySym.FastFwd F5 XKeySym.FastRew F6 XKeySym.Channel+ Prior XKeySym.Channel- Next XKeySym.Volume+ F12 XKeySym.Volume- F11 XKeySym.Mute F10 vdr-plugin-live-3.5.6/doc/timer_monitoring.txt000066400000000000000000000050131521353435100214450ustar00rootroot00000000000000See https://www.vdr-portal.de/forum/index.php?thread/135887-live-weiterentwicklung-v3-3-x/&postID=1376226#post1376226 Normally, I want to record an event with a timer. The question now is, how can I clearly identify an event? It's well known that broadcasters occasionally change times. So the options are: 1. VPS: Clearly defined and supported by both the VPS standard and the VDR without plugins: An event is assigned a VPS time, and this VPS time does not change, even if the event is postponed. With a VPS timer, VDR records all events that have this VPS time, even if these events are postponed and the start time of the events no longer has anything to do with the VPS time. The only disadvantage: The broadcaster has to support this. 2. Broadcast ID == "Event ID": The broadcaster sends an ID with each event. Unfortunately, it's not always the same ID, i.e., if the ID for "Avengers" was 4567 yesterday, it could be 1234 today. Some broadcasters change these IDs quite often, while for others they usually remain stable. Experience has shown that if a new event is added to the EPG, the ID is very likely to change. If the event has been in the EPG for 1-2 days, the ID usually remains stable. However, there are also broadcasters for whom this ID never remains stable... 3. Time: EPGsearch remembers the start and duration of the event and uses this data to try to find a postponed event. Advantage: This also works if the broadcaster changes the event ID. It should work very reliably, especially with minor changes to the start time. Broadcasters do not normally change the duration of events. 4. VDR without VPS and without EPGsearch: VDR selects the event with the greatest coverage. That is, the event that falls as completely as possible within the time period (timer start - timer stop). To achieve this as effectively as possible, VDR may shorten the timer margin (front and back) so that when the timer is created, the timer only covers one event. This is certainly the worst option: The irreproducible change to the timer's start and end times makes it more difficult to identify the original event. You can't even determine the length of the event for which the timer was created. The fact that the event might then no longer be correctly identified is less of a concern: VDR records from timer start to timer stop, so the event doesn't matter. The downside is that shortening the timer margins can result in a portion of the program being missing, even though it has only shifted very slightly. See "Pre- and post-run" in recordings. ~ Markus vdr-plugin-live-3.5.6/epg_events.cpp000066400000000000000000000445211521353435100174240ustar00rootroot00000000000000#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)) : ""; } const std::string EpgInfo::VpsTime(const char* format) const { return m_vpsTime ? std::string(cToSvDateTime(format, m_vpsTime)) : ""; } 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_vpsTime = event->Vps(); 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(); m_vpsTime = event->Vps(); } } 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(); 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()); bool recItemFound = searchNameDesc(recItem, 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(tr(" - ")); 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.6/epg_events.h000066400000000000000000000134431521353435100170700ustar00rootroot00000000000000#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; std::string const VpsTime(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; } time_t GetVpsTime() const { return m_vpsTime; } 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 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; time_t m_vpsTime = 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_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.6/epgsearch.cpp000066400000000000000000000624261521353435100172320ustar00rootroot00000000000000 #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_suppliedRecordFields = 0; m_hasInsufficientFields = true; m_hasUnknownFields = false; 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 = 3; m_menuTemplate = 0; m_delMode = 0; m_delAfterCountRecs = 0; m_delAfterDaysOfFirstRec = 0; m_useAsSearchTimerFrom = 0; m_useAsSearchTimerTil = 0; m_catValuesAvoidRepeat = 0; m_extEPGInfoMatchingMode = false; m_unmuteSoundOnSwitch = false; m_compareSummaryMatchInPercent = 90; m_compareDate = 0; m_contentsCategoryMatchingMode = 0; m_contentsCharacteristicsMatchingMode = 0; m_useParentalRating = false; m_minParentalRating = 0; m_maxParentalRating = 18; } SearchTimer::SearchTimer( std::string const& data ) { Init(); m_data = data; cSplit parts(m_data, ':'); auto part = parts.begin(); for (std::size_t 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_extEPGInfoMatchingMode = parse_int( *part ); break; case 50: m_unmuteSoundOnSwitch = lexical_cast( *part ); break; case 51: m_compareSummaryMatchInPercent = parse_int( *part ); break; case 52: m_contentsFilter = cToSvReplace( *part, "|", ":" ).replaceAll("!^pipe^!", "|" ); break; case 53: m_compareDate = parse_int( *part ); break; case 54: m_contentsCategoryMatchingMode = parse_int( *part ); break; case 55: m_contentsCharacteristicsMatchingMode = parse_int( *part ); break; case 56: m_useParentalRating = lexical_cast( *part ); break; case 57: m_minParentalRating = parse_int( *part ); break; case 58: m_maxParentalRating = parse_int( *part ); m_hasInsufficientFields = false; break; default: m_hasUnknownFields = !(*part).empty(); break; } } m_suppliedRecordFields = parts.size(); } std::string SearchTimer::ToText() { std::size_t i; cToSvConcat os; cSplit parts(m_data, ':'); bool update = parts.size() > 1; // for updating a previously parsed record bool atEnd = false; // for creating a new record from scratch // create the record from known fields; note that we can safely send more fields // than the EPGSearch service interface expects auto part = parts.begin(); for (i = 0; !atEnd && !(update && part == parts.end()); ++i, ++part ) { switch (i) { case 0: os << m_id; break; case 1: os << ':' << cToSvReplace(m_search, "|", "!^pipe^!").replaceAll(":", "|"); break; case 2: os << ":" << (m_useTime ? "1" : "0"); break; case 3: os << ':'; if (m_useTime) { os.appendInt<4>(m_startTime); }; break; case 4: os << ':'; if (m_useTime) { os.appendInt<4>(m_stopTime); }; break; case 5: os << ':' << m_useChannel; break; case 6: os << ':'; 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; } break; case 7: os << ':' << m_useCase; break; case 8: os << ':' << m_mode; break; case 9: os << ':' << m_useTitle; break; case 10: os << ':' << m_useSubtitle; break; case 11: os << ':' << m_useDescription; break; case 12: os << ':' << (m_useDuration ? "1" : "0"); break; case 13: os << ':'; if (m_useDuration) { os.appendInt<4>(m_minDuration); }; break; case 14: os << ':'; if (m_useDuration) { os.appendInt<4>(m_maxDuration); }; break; case 15: os << ':' << m_useAsSearchtimer; break; case 16: os << ':' << m_useDayOfWeek; break; case 17: os << ':' << m_dayOfWeek; break; case 18: os << ':' << m_useEpisode; break; case 19: os << ':' << cToSvReplace(m_directory, "|", "!^pipe^!").replaceAll(":", "|"); break; case 20: os << ':' << m_priority; break; case 21: os << ':' << m_lifetime; break; case 22: os << ':' << m_marginStart; break; case 23: os << ':' << m_marginStop; break; case 24: os << ':' << m_useVPS; break; case 25: os << ':' << m_action; break; case 26: os << ':' << m_useExtEPGInfo; break; case 27: os << ':'; if (m_useExtEPGInfo) { for(std::size_t i = 0; i < m_ExtEPGInfo.size(); i++) { if (i > 0) os << '|'; os << cToSvReplace(m_ExtEPGInfo[i], ":", "!^colon^!").replaceAll("|", "!^pipe^!"); } } break; case 28: os << ':' << m_avoidRepeats; break; case 29: os << ':' << m_allowedRepeats; break; case 30: os << ':' << m_compareTitle; break; case 31: os << ':' << m_compareSubtitle; break; case 32: os << ':' << m_compareSummary; break; case 33: os << ':' << m_catValuesAvoidRepeat; break; case 34: os << ':' << m_repeatsWithinDays; break; case 35: os << ':' << m_delAfterDays; break; case 36: os << ':' << m_recordingsKeep; break; case 37: os << ':' << m_switchMinBefore; break; case 38: os << ':' << m_pauseOnNrRecordings; break; case 39: os << ':' << m_blacklistMode; break; case 40: os << ':'; if (m_blacklistMode == 1) { for (std::size_t i = 0; i < m_blacklistIDs.size(); i++) { if (i > 0) os << '|'; os << m_blacklistIDs[i]; } } break; case 41: os << ':' << m_fuzzyTolerance; break; case 42: os << ':' << m_useInFavorites; break; case 43: os << ':' << m_menuTemplate; break; case 44: os << ':' << m_delMode; break; case 45: os << ':' << m_delAfterCountRecs; break; case 46: os << ':' << m_delAfterDaysOfFirstRec; break; case 47: os << ':' << m_useAsSearchTimerFrom; break; case 48: os << ':' << m_useAsSearchTimerTil; break; case 49: os << ':' << m_extEPGInfoMatchingMode; break; case 50: os << ':' << m_unmuteSoundOnSwitch; break; case 51: os << ':' << m_compareSummaryMatchInPercent; break; case 52: os << ':' << m_contentsFilter; break; case 53: os << ':' << m_compareDate; break; case 54: os << ':' << m_contentsCategoryMatchingMode; break; case 55: os << ':' << m_contentsCharacteristicsMatchingMode; break; case 56: os << ':' << m_useParentalRating; break; case 57: os << ':' << m_minParentalRating; break; case 58: os << ':' << m_maxParentalRating; break; default: i--; part--; atEnd = true; break; } } // append yet unknown fields from the original record for ( ; update && part != parts.end(); i++, part++ ) { os << ':' << *part; } return std::string(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.clear(); // should be empty anyway, but cater for future use if (!data.empty() && std::isdigit(data[0])) { for (auto part : cSplit(data, '|')) { m_ExtEPGInfo.push_back(std::string(cToSvReplace( part, "!^colon^!", ":" ).replaceAll("!^pipe^!", "|" ))); } } } void SearchTimer::ParseBlacklist(cSv data) { m_blacklistIDs.clear(); // should be empty anyway, but cater for future use if (!data.empty() && std::isdigit(data[0])) 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(cSv id) { int id_i = parse_int(id); for (SearchTimers::iterator timer = m_timers.begin(); timer != m_timers.end(); ++timer) if (timer->Id() == id_i) return &*timer; return nullptr; } 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(cSv 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; } std::string SearchTimers_DeleteConfirmationQuestion(cSv id) { SearchTimers searchTimers; SearchTimer* search = searchTimers.GetByTimerId(id); if (!search) return tr("Delete search timer [search timer name unavailable]?"); return std::string(cToSvFormatted(tr("Delete search timer \"%s\"?"), search->Search().c_str() )); } int SearchTimers_DeleteSearchTimer(cSv id, std::string &message) { SearchTimers searchTimers; SearchTimer* search = searchTimers.GetByTimerId(id); if (!search) { message = concat("Error deleting search timer: Couldn't find search timer ID ", id); return 1; } std::string name = search->Search(); bool res = searchTimers.Delete(id); if (!res) { message = concat("Error deleting search timer ID ", id, " name ", name); return 2; } message = concat("Sucessfully deleted search timer ID ", id, " name ", name); return 0; } 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.6/epgsearch.h000066400000000000000000000432321521353435100166710ustar00rootroot00000000000000#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 ); // interfacing with the EPGSearch service std::size_t SuppliedRecordFields() { return m_suppliedRecordFields; } bool HasInsufficientFields() const { return m_hasInsufficientFields; } bool HasUnknownFields() const { return m_hasUnknownFields; } // getters/setters for the fields of an EPGSearch record 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); int ExtEPGInfoMatchingMode() const { return m_extEPGInfoMatchingMode; } void SetExtEPGInfoMatchingMode(int extEPGInfoMatchingMode) { m_extEPGInfoMatchingMode = extEPGInfoMatchingMode; } bool UnmuteSoundOnSwitch() const { return m_unmuteSoundOnSwitch; } void SetUnmuteSoundOnSwitch(bool unmuteSoundOnSwitch) { m_unmuteSoundOnSwitch = unmuteSoundOnSwitch; } int CompareSummaryMatchInPercent() const { return m_compareSummaryMatchInPercent; } void SetCompareSummaryMatchInPercent(int compareSummaryMatchInPercent) { m_compareSummaryMatchInPercent = compareSummaryMatchInPercent; } std::string const& ContentsFilter() const { return m_contentsFilter; } void SetContentsFilter(std::string const& contentsFilter) { m_contentsFilter = contentsFilter; } int CompareDate() const { return m_compareDate; } void SetCompareDate(int compareDate) { m_compareDate = compareDate; } int ContentsCategoryMatchingMode() { return m_contentsCategoryMatchingMode; } void SetContentsCategoryMatchingMode( int contentsCategoryMatchingMode ) { m_contentsCategoryMatchingMode = contentsCategoryMatchingMode; } int ContentsCharacteristicsMatchingMode() { return m_contentsCharacteristicsMatchingMode; } void SetContentsCharacteristicsMatchingMode(int contentsCharacteristicsMatchingMode) { m_contentsCharacteristicsMatchingMode = contentsCharacteristicsMatchingMode; } bool UseParentalRating() { return m_useParentalRating; } void SetUseParentalRating(bool useParentalRating) { m_useParentalRating = useParentalRating; } int MinParentalRating() { return m_minParentalRating; } void SetMinParentalRating(int minParentalRating) { m_minParentalRating = minParentalRating; } int MaxParentalRating() { return m_maxParentalRating; } void SetMaxParentalRating(int maxParentalRating) { m_maxParentalRating = maxParentalRating; } private: std::string m_data; // compliance with fields supplied by the EPGSearch service std::size_t m_suppliedRecordFields; bool m_hasInsufficientFields; bool m_hasUnknownFields; // fields parsed from an EPGSearch record 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; int m_extEPGInfoMatchingMode; bool m_unmuteSoundOnSwitch; int m_compareSummaryMatchInPercent; std::string m_contentsFilter; int m_compareDate; int m_contentsCategoryMatchingMode; int m_contentsCharacteristicsMatchingMode; bool m_useParentalRating; int m_minParentalRating; int m_maxParentalRating; 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(cSv id); bool ToggleActive(std::string const& id); bool Delete(cSv id); void TriggerUpdate(); private: TimerList m_timers; }; std::string SearchTimers_DeleteConfirmationQuestion(cSv id); int SearchTimers_DeleteSearchTimer(cSv id, std::string &message); 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.6/epgsearch/000077500000000000000000000000001521353435100165145ustar00rootroot00000000000000vdr-plugin-live-3.5.6/epgsearch/services.h000066400000000000000000000153621521353435100205170ustar00rootroot00000000000000/* 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.6/exception.h000066400000000000000000000006331521353435100167240ustar00rootroot00000000000000#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 ) {} explicit HtmlError( const char *message ): std::runtime_error( message ) {} virtual ~HtmlError() throw() {} }; } // namespace vdrlive #endif // VDR_LIVE_EXCEPTION_H vdr-plugin-live-3.5.6/ffmpeg.cpp000066400000000000000000000147261521353435100165350ustar00rootroot00000000000000#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.6/ffmpeg.h000066400000000000000000000015741521353435100161770ustar00rootroot00000000000000#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.6/global.mk000066400000000000000000000017111521353435100163440ustar00rootroot00000000000000# # 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.6/live.cpp000066400000000000000000000062171521353435100162240ustar00rootroot00000000000000/* * 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 "users.h" #include "services_live.h" #include "epgsearch.h" #include "osd_status.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() {} Plugin::~Plugin() {} 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 LiveOsdStatusMonitor(); // 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.6/live.h000066400000000000000000000030731521353435100156660ustar00rootroot00000000000000#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 #include "stringhelpers.h" namespace vdrlive { //class cLiveWorker; class Plugin : public cPlugin { public: Plugin(); virtual ~Plugin() override; 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 cSv 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.6/live/000077500000000000000000000000001521353435100155125ustar00rootroot00000000000000vdr-plugin-live-3.5.6/live/css/000077500000000000000000000000001521353435100163025ustar00rootroot00000000000000vdr-plugin-live-3.5.6/live/css/layout-large.css000066400000000000000000000027451521353435100214310ustar00rootroot00000000000000/* ########################################################################### # This file overrides properties, which are referenced in 'styles.css' # and possibly in 'theme.css' or 'siteprefs.css', for the page layout of # themes with large icons. To include this file into a specific theme, # it should be referenced by a symbolic link instead of copying. ########################################################################### */ /* icon settings */ @property --icon-width { syntax: ""; initial-value: 24px; inherits: false; } @property --icon-height { syntax: ""; initial-value: 24px; inherits: false; } @property --iconic-button-width { syntax: ""; initial-value: 24px; inherits: false; } @property --iconic-button-height { syntax: ""; initial-value: 24px; inherits: false; } /* icon visual adjustments */ table.listing > tbody > tr > td.rightcol div.event_actions, table.listing > tbody > tr > td.rightcol div.user_actions { padding-top: var(--margin-top-bottom); padding-bottom: var(--margin-top-bottom); } .ok-info-body, .err-info-body { column-gap: calc(2 * var(--icon-column-gap)); } /* OSD settings */ @property --osd-screen-scale { syntax: ""; initial-value: 1.0; inherits: false; } @property --remote-control-scale { syntax: ""; initial-value: 1.5; inherits: false; } @property --osd-button-min-height { syntax: ""; initial-value: 24px; inherits: false; } vdr-plugin-live-3.5.6/live/css/properties.css000066400000000000000000000330511521353435100212120ustar00rootroot00000000000000/* ########################################################################### # This file provides properties, which are referenced in 'styles.css', # for the page layout of all themes. By default, themes only differ in # colors defined in 'theme.css' or customized logos and icons stored in # the theme's 'img' directory. However, selected (or even all) global # properties defined in this file can be refined by a supplemental file # 'layout.css' in a theme's directory or globally in 'siteprefs.css'. ########################################################################### */ /* ############################# # Global properties ############################# */ /* global settings */ @property --margin-top-bottom { /* vertical distance towards box borders */ syntax: ""; initial-value: 5px; inherits: false; } @property --margin-left-right { /* horizontal distance towards box borders */ syntax: ""; initial-value: 5px; inherits: false; } @property --vdr-logo-image-width { /* VDR-logo image width in pixels for use in calculated lengths */ syntax: ""; initial-value: 192; inherits: false; } @property --vdr-logo-image-height { /* VDR-logo image height in pixels for use in calculated lengths */ syntax: ""; initial-value: 80; inherits: false; } @property --logo-image-width { /* Channel-logo image width for use in calculated lengths */ syntax: ""; initial-value: 120px; inherits: false; } @property --logo-image-height { /* Channel-logo image width for use in calculated lengths */ syntax: ""; initial-value: 32px; inherits: false; } @property --thumb-image-width { /* Thumbnail-image width for use in calculated lengths */ syntax: ""; initial-value: 80px; inherits: false; } @property --thumb-image-height { /* Thumbnail-image width for use in calculated lengths */ syntax: ""; initial-value: 40px; inherits: false; } @property --thumb-image-enlargement-scale { /* Scaling factor for enlarged thumbnail images on small screens */ syntax: ""; initial-value: 1.25; inherits: false; } @property --epg-image-width { /* EPG-image width for use in calculated lengths */ syntax: ""; initial-value: 240px; inherits: false; } @property --epg-artwork-width { /* Artwork-image width for use in calculated lengths */ syntax: ""; initial-value: 480px; inherits: false; } @property --epg-photo-width { /* Actor-image width for use in calculated lengths */ syntax: ""; initial-value: 120px; inherits: false; } @property --sdhd-width { /* icon width for use in calculated lengths, height acc. to dimension */ syntax: ""; initial-value: 24px; inherits: false; } @property --iconic-button-width { /* button width for use in calculated lengths */ /* iconic buttons have 12 visible horizontal pixels, thus 2 transparent border pixels */ syntax: ""; initial-value: 16px; inherits: false; } @property --iconic-button-height { /* button height for use in calculated lengths */ /* iconic buttons have 12 visible vertical pixels, thus 2 transparent border pixels */ syntax: ""; initial-value: 16px; inherits: false; } @property --iconic-button-row-gap { /* distance between button rows */ /* should match the icon row gap, taking transparent border pixels into account */ syntax: ""; initial-value: 0px; /* adjacent border pixels already result in a gap of 4 pixels */ inherits: false; } @property --iconic-button-column-gap { /* distance between button columns */ /* should match the icon row gap, taking transparent border pixels into account */ syntax: ""; initial-value: 0px; /* adjacent border pixels already result in a gap of 4 pixels */ inherits: false; } @property --icon-width { /* icon width for use in calculated lengths */ syntax: ""; initial-value: 16px; inherits: false; } @property --icon-height { /* icon height for use in calculated lengths */ syntax: ""; initial-value: 16px; inherits: false; } @property --icon-row-gap { /* distance between icons rows */ syntax: ""; initial-value: 4px; inherits: false; } @property --icon-column-gap { /* distance between icon columns */ syntax: ""; initial-value: 4px; inherits: false; } @property --text-font-size { /* common font size */ syntax: ""; initial-value: 11px; inherits: false; } @property --text-line-height { /* line height w.r.t. font size */ syntax: ""; initial-value: 20px; inherits: false; } @property --text-line-gap { /* distance between lines within a text block */ syntax: ""; initial-value: 2px; inherits: false; } @property --text-block-gap { /* distance between text blocks */ syntax: ""; initial-value: 5px; inherits: false; } /* page-layout settings */ @property --body-margin { syntax: ""; initial-value: 10px; inherits: false; } @property --page-header-gap { syntax: ""; initial-value: 25px; inherits: false; } @property --menubar-gap { syntax: ""; initial-value: 8px; inherits: false; } @property --spacebar-height { syntax: ""; initial-value: 10px; inherits: false; } /* main-menu settings */ @property --menu-height { syntax: ""; initial-value: 27px; inherits: false; } @property --menu-border-width { syntax: ""; initial-value: 1px; inherits: false; } /* message-bar settings */ @property --messagebar-height { syntax: ""; initial-value: 27px; inherits: false; } @property --messagebar-border-width { syntax: ""; initial-value: 1px; inherits: false; } /* page-menu settings */ @property --pagemenu-height { syntax: ""; initial-value: 27px; inherits: false; } @property --pagemenu-border-width { syntax: ""; initial-value: 1px; inherits: false; } @property --pagemenu-shadow-height { syntax: ""; initial-value: 6px; inherits: false; } /* table and box-header settings */ @property --listing-border-width { syntax: ""; initial-value: 1px; inherits: false; } @property --listing-header-border-radius { syntax: ""; initial-value: 8px; inherits: false; } @property --listing-header-height { syntax: ""; initial-value: 20px; inherits: false; } @property --listing-row-background-gradient-height { syntax: ""; initial-value: 6px; inherits: false; } /* user-input settings */ @property --input-border-width { syntax: ""; initial-value: 1px; inherits: false; } @property --input-border-style { syntax: ""; initial-value: solid; inherits: false; } @property --input-accent-border-width { syntax: ""; initial-value: 1px; inherits: false; } @property --input-accent-border-style { syntax: ""; initial-value: dotted; inherits: false; } /* info-box settings */ @property --infobox-border-width { syntax: ""; initial-value: 1px; inherits: false; } @property --infobox-border-radius { syntax: ""; initial-value: 0px; inherits: false; } @property --infobox-header-height { syntax: ""; initial-value: 16px; inherits: false; } @property --infobox-header-border-width { syntax: ""; initial-value: 1px; inherits: false; } @property --infobox-header-background-gradient-height { syntax: ""; initial-value: 5px; inherits: false; } @property --infobox-progressbar-height { syntax: ""; initial-value: 8px; inherits: false; } @property --infobox-progressbar-width { syntax: ""; initial-value: 100px; inherits: false; } @property --infobox-progressbar-border-width { syntax: ""; initial-value: 1px; inherits: false; } @property --infobox-margin { syntax: ""; initial-value: 4px; inherits: false; } /* login- and info-window settings */ @property --infowin-border-width { syntax: ""; initial-value: 1px; inherits: false; } @property --infowin-section-border-width { syntax: ""; initial-value: 1px; inherits: false; } @property --infowin-tools-border-width { syntax: ""; initial-value: 1px; inherits: false; } @property --infowin-title-border-radius { syntax: ""; initial-value: 8px; inherits: false; } @property --infowin-title-height { syntax: ""; initial-value: 20px; inherits: false; } @property --infowin-body-margin { syntax: ""; initial-value: 10px; inherits: false; } @property --infowin-progressbar-height { syntax: ""; initial-value: 8px; inherits: false; } @property --infowin-progressbar-width { syntax: ""; initial-value: 100px; inherits: false; } @property --infowin-progressbar-border-width { syntax: ""; initial-value: 1px; inherits: false; } @property --infowin-label-width { syntax: ""; initial-value: 130px; inherits: false; } @property --infowin-background-gradient-height { syntax: ""; initial-value: 5px; inherits: false; } @property --infowin-shadow-offset { syntax: ""; initial-value: 4px; inherits: false; } @property --infowin-shadow-blur { syntax: ""; initial-value: 16px; inherits: false; } @property --infowin-shadow-spread { syntax: ""; initial-value: 0px; inherits: false; } /* popup settings */ @property --popup-width { syntax: ""; initial-value: 480px; inherits: false; } @property --popup-border-width { syntax: ""; initial-value: 1px; inherits: false; } @property --popup-border-radius { syntax: ""; initial-value: 8px; inherits: false; } @property --popup-body-margin { syntax: ""; initial-value: 10px; inherits: false; } @property --popup-shadow-offset { syntax: ""; initial-value: 4px; inherits: false; } @property --popup-shadow-blur { syntax: ""; initial-value: 16px; inherits: false; } @property --popup-shadow-spread { syntax: ""; initial-value: 0px; inherits: false; } /* error-widget settings */ @property --error-border-width { syntax: ""; initial-value: 1px; inherits: false; } @property --error-title-border-radius { syntax: ""; initial-value: 8px; inherits: false; } @property --error-title-height { syntax: ""; initial-value: 21px; inherits: false; } /* button settings */ @property --button-width { syntax: ""; initial-value: 120px; inherits: false; } @property --button-height { syntax: ""; initial-value: 20px; inherits: false; } @property --button-border-width { syntax: ""; initial-value: 1px; inherits: false; } @property --button-border-style { syntax: ""; initial-value: outset; inherits: false; } @property --button-border-radius { syntax: ""; initial-value: 4px; inherits: false; } @property --button-border-gradient-top { syntax: ""; initial-value: 40%; inherits: false; } @property --button-border-gradient-middle { syntax: ""; initial-value: 60%; inherits: false; } /* OSD settings */ @property --osd-screen-width { /* the width of the TV's native screenshot area */ syntax: ""; initial-value: 640px; inherits: false; } @property --osd-screen-height { /* the height of the TV's native screenshot area */ syntax: ""; initial-value: 360px; inherits: false; } @property --osd-screen-scale { /* for adaptation to small screens; should be a multiple of 0.05 */ syntax: ""; initial-value: 1.0; inherits: false; } @property --osd-screen-border-width { syntax: ""; initial-value: 20px; inherits: false; } @property --osd-screen-border-height { syntax: ""; initial-value: 20px; inherits: false; } @property --osd-screen-base-height { syntax: ""; initial-value: 72px; inherits: false; } @property --osd-font-size { syntax: ""; initial-value: 11px; inherits: false; } @property --osd-title-font-size { syntax: ""; initial-value: 12px; inherits: false; } @property --osd-subtitle-font-size { syntax: ""; initial-value: 9px; inherits: false; } @property --osd-message-border-width { syntax: ""; initial-value: 2px; inherits: false; } @property --osd-button-margin { syntax: ""; initial-value: 10px; inherits: false; } @property --osd-button-min-height { /* button width will be determined by screen width and button gab */ syntax: ""; initial-value: 16px; inherits: false; } @property --osd-button-gap { /* should satisfy (screen-width - 3 * button-gap) % 4 == 0 */ syntax: ""; initial-value: 10px; inherits: false; } /* remote-control settings */ @property --remote-control-width { /* the remote-control's native width */ syntax: ""; initial-value: 160px; inherits: false; } @property --remote-control-height { /* the remote-control's native height */ syntax: ""; initial-value: 420px; inherits: false; } @property --remote-control-scale { /* for adaptation to small screens; should be a multiple of 0.05 */ syntax: ""; initial-value: 1.0; inherits: false; } /* animation settings */ @keyframes desaturating { from { filter: saturate(100%); } to { filter: saturate(40%); } } @keyframes fading { from { filter: opacity(100%); } to { filter: opacity(40%); } } @keyframes row-highlighting { from { color: var(--listing-color); } to { color: var(--listing-color-highlight); } } vdr-plugin-live-3.5.6/live/css/siteprefs.css000066400000000000000000000352361521353435100210310ustar00rootroot00000000000000/* ############################################################## # This file is part of VDR Live and gives users the option # to adapt the default CSS styles for matching their needs. # # By default, this file does not affect the default settings. # But even if you don't intent to apply changes, you must not # delete this file. ############################################################## */ /* 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.multischedule 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%; } */ /* Customize the settings below to match the desired shadow for images ** enlarged upon mouse hovering. They have no effect unless one of the ** sections for enlarging images upon hovering are uncommented. */ /* EPG and recording images within details */ @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-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-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: #e9efef; inherits: false; } /* common hover transition settings */ @property --hover-transition-time { syntax: "