diff options
author | Matti Lehtimäki <matti.lehtimaki@gmail.com> | 2012-04-12 19:25:00 +0300 |
---|---|---|
committer | Matti Lehtimäki <matti.lehtimaki@gmail.com> | 2012-04-12 19:25:00 +0300 |
commit | c31263388a5dbdc5150595b328d50fa486b4dce5 (patch) | |
tree | 130d8ba9d3f3eb9b29d3070024e4f95ef285ed31 | |
download | vdr-plugin-epgfixer-c31263388a5dbdc5150595b328d50fa486b4dce5.tar.gz vdr-plugin-epgfixer-c31263388a5dbdc5150595b328d50fa486b4dce5.tar.bz2 |
Import version 0.0.4 to git.
-rw-r--r-- | COPYING | 340 | ||||
-rw-r--r-- | HISTORY | 24 | ||||
-rw-r--r-- | Makefile | 126 | ||||
-rw-r--r-- | README | 57 | ||||
-rw-r--r-- | config.c | 66 | ||||
-rw-r--r-- | config.h | 38 | ||||
-rw-r--r-- | epgfixer.c | 165 | ||||
-rw-r--r-- | epgfixer/regexp.conf | 4 | ||||
-rw-r--r-- | epghandler.c | 367 | ||||
-rw-r--r-- | epghandler.h | 25 | ||||
-rw-r--r-- | po/fi_FI.po | 68 | ||||
-rw-r--r-- | regexp.c | 197 | ||||
-rw-r--r-- | regexp.h | 79 | ||||
-rw-r--r-- | setup_menu.c | 246 | ||||
-rw-r--r-- | setup_menu.h | 26 |
15 files changed, 1828 insertions, 0 deletions
@@ -0,0 +1,340 @@ + 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. + + <one line to give the program's name and a brief idea of what it does.> + Copyright (C) <year> <name of author> + + 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. + + <signature of Ty Coon>, 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. @@ -0,0 +1,24 @@ +VDR Plugin 'epgfixer' Revision History +-------------------------------------- + +2012-04-12: Version 0.0.4 + +- Editing of regular expressions from setup menu. + +2012-03-25: Version 0.0.3 + +- Reloading regular expressions and clearing EPG data from setup menu. +- Enable internal optimisation of regular expressions in PCRE. +- Enabling/disabling of regular expressions from setup menu. +- Editing of regular expressions from setup menu. +- Cleaning up code (some cleanup by Rolf Ahrenberg). + +2012-03-18: Version 0.0.2 + +- Rewrite to use regular expressions. +- Include EPG bug fixes from VDR and make them individually selectable + from setup. + +2012-03-17: Version 0.0.1 + +- Initial revision. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..9119f49 --- /dev/null +++ b/Makefile @@ -0,0 +1,126 @@ +# +# Makefile for epgfixer, a Video Disk Recorder plugin +# + +# epgfixer +# 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. +# IMPORTANT: the presence of this macro is important for the Make.config +# file. So it must be defined, even if it is not used here! +# +PLUGIN = epgfixer + +### The version number of this plugin (taken from the main source file): + +VERSION = $(shell grep 'static const char \*VERSION *=' $(PLUGIN).c | awk '{ print $$6 }' | sed -e 's/[";]//g') + +### The C++ compiler and options: + +CXX ?= g++ +CXXFLAGS ?= -g -O3 -Wall -Woverloaded-virtual -Wno-parentheses + +### The directory environment: + +VDRDIR ?= ../../.. +LIBDIR ?= ../../lib +TMPDIR ?= /tmp + +### Regexp +ifeq (exists, $(shell pkg-config libpcre && echo exists)) + REGEXLIB = pcre +else ifeq (exists, $(shell pkg-config tre && echo exists)) + REGEXLIB = tre +endif + +### Make sure that necessary options are included: + +include $(VDRDIR)/Make.global + +### Allow user defined options to overwrite defaults: + +-include $(VDRDIR)/Make.config + +### The version number of VDR's plugin API (taken from VDR's "config.h"): + +APIVERSION = $(shell sed -ne '/define APIVERSION/s/^.*"\(.*\)".*$$/\1/p' $(VDRDIR)/config.h) + +### The name of the distribution archive: + +ARCHIVE = $(PLUGIN)-$(VERSION) +PACKAGE = vdr-$(ARCHIVE) + +### Includes and Defines (add further entries here): + +INCLUDES += -I$(VDRDIR)/include + +DEFINES += -D_GNU_SOURCE -DPLUGIN_NAME_I18N='"$(PLUGIN)"' + +### The object files (add further files here): + +OBJS = $(PLUGIN).o config.o epghandler.o setup_menu.o regexp.o + +ifeq ($(REGEXLIB), pcre) +LIBS += $(shell pcre-config --libs-posix) +INCLUDE += $(shell pcre-config --cflags) +DEFINES += -DHAVE_PCREPOSIX +endif + +### The main target: + +all: libvdr-$(PLUGIN).so i18n + +### Implicit rules: + +%.o: %.c + $(CXX) $(CXXFLAGS) -c $(DEFINES) $(INCLUDES) $< + +### Dependencies: + +MAKEDEP = $(CXX) -MM -MG +DEPFILE = .dependencies +$(DEPFILE): Makefile + @$(MAKEDEP) $(DEFINES) $(INCLUDES) $(OBJS:%.o=%.c) > $@ + +-include $(DEPFILE) + +### Internationalization (I18N): + +PODIR = po +LOCALEDIR = $(VDRDIR)/locale +I18Npo = $(wildcard $(PODIR)/*.po) +I18Nmsgs = $(addprefix $(LOCALEDIR)/, $(addsuffix /LC_MESSAGES/vdr-$(PLUGIN).mo, $(notdir $(foreach file, $(I18Npo), $(basename $(file)))))) +I18Npot = $(PODIR)/$(PLUGIN).pot + +%.mo: %.po + msgfmt -c -o $@ $< + +$(I18Npot): $(wildcard *.c) + xgettext -C -cTRANSLATORS --no-wrap --no-location -k -ktr -ktrNOOP --package-name=vdr-$(PLUGIN) --package-version=$(VERSION) --msgid-bugs-address='<see README>' -o $@ $^ + +%.po: $(I18Npot) + msgmerge -U --no-wrap --no-location --backup=none -q $@ $< + @touch $@ + +$(I18Nmsgs): $(LOCALEDIR)/%/LC_MESSAGES/vdr-$(PLUGIN).mo: $(PODIR)/%.mo + @mkdir -p $(dir $@) + cp $< $@ + +.PHONY: i18n +i18n: $(I18Nmsgs) $(I18Npot) + +### Targets: + +libvdr-$(PLUGIN).so: $(OBJS) + $(CXX) $(CXXFLAGS) $(LDFLAGS) $(LIBS) -shared $(OBJS) -o $@ + @cp --remove-destination $@ $(LIBDIR)/$@.$(APIVERSION) + +dist: $(I18Npo) clean + @-rm -rf $(TMPDIR)/$(ARCHIVE) + @mkdir $(TMPDIR)/$(ARCHIVE) + @cp -a * $(TMPDIR)/$(ARCHIVE) + @tar czf $(PACKAGE).tgz -C $(TMPDIR) $(ARCHIVE) + @-rm -rf $(TMPDIR)/$(ARCHIVE) + @echo Distribution package created as $(PACKAGE).tgz + +clean: + @-rm -f $(OBJS) $(DEPFILE) *.so *.tgz core* *~ $(PODIR)/*.mo $(PODIR)/*.pot @@ -0,0 +1,57 @@ +This is a "plugin" for the Video Disk Recorder (VDR). + +Written by: Matti Lehtimäki <matti.lehtimaki@gmail.com> + +Project's homepage: http://projects.vdr-developer.org/projects/plg-epgfixer + +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. +See the file COPYING for more information. + +Description: + +Epgfixer is a VDR plugin for doing extra fixing of EPG data. Everything is +done using regular expressions. Correctly named back references are placed +to the named EPG field. + +Plugin also has EPG bug fixes included in VDR individually selectable from +setup menu. + +Requirements: + - VDR 1.7.26 or later + - PCRE + +Usage: + +All regular expressions are placed to VDRPLUGINCONFDIR/epgfixer/regexp.conf. + +Syntax of regexp.conf line is "Channel_list:Parsed_epg_field=Regexp" with: +- Lines beginning with # are regarded as comments. +- Lines beginning with ! are regarded as disabled regular expressions. +- Channel_list is optional comma separated list of channels for which the + regular expression is used. If no list of channels is given the regular + expression is used for all channels. +- Parsed_epg_field is the EPG field for which the regular expression is applied + with available field names title, shorttext and description. +- Regular expressions use named backreferences with either title, shorttext, + description or rating (parental rating). +- By prepending "a" or "p" to backreference name (except rating field) the + back referenced string is either appended or prepended to the original + content of the target EPG field, respectively. +- Several regular expressions may be applied to same field. + +Examples of regexp.conf: + +# Example of disabled regexp: +!title=^(?:Movie: |Document: )(?<title>.*)$ + +# Remove "Movie: " or "Document: " from the beginning of title field for +# channels 1, 3 and 5: +1,3,5:title=^(?:Movie: |Document: )(?<title>.*)$ + +# Move parental rating from end of title to correct EPG field: +title=^(?<title>.*)[ ][(](?<rating>[0-9S]{1,2})[)][ ]*$ + + diff --git a/config.c b/config.c new file mode 100644 index 0000000..2be9d26 --- /dev/null +++ b/config.c @@ -0,0 +1,66 @@ +/* + * config.c: Global configuration and user settings + * + * See the README file for copyright information and how to reach the author. + * + */ + +#include "config.h" +#include <string.h> + +/* Global instance */ +cEpgfixerSetup EpgfixerSetup; + +cEpgfixerSetup::cEpgfixerSetup() +{ + quotedshorttext = 0; + blankbeforedescription = 0; + repeatedtitle = 0; + doublequotedshorttext = 0; + removeformatting = 0; + longshorttext = 0; + equalshorttextanddescription = 0; + nobackticks = 0; + components = 0; +} + +cString cEpgfixerSetup::m_ProcessedArgs; + +bool cEpgfixerSetup::ProcessArg(const char *Name, const char *Value) +{ + if (SetupParse(Name, Value)) { + m_ProcessedArgs = cString::sprintf("%s%s ", *m_ProcessedArgs ? *m_ProcessedArgs : " ", Name); + return true; + } + return false; +} + + +bool cEpgfixerSetup::ProcessArgs(int argc, char *argv[]) +{ + return true; +} + +bool cEpgfixerSetup::SetupParse(const char *Name, const char *Value) +{ + const char *pt; + if (*m_ProcessedArgs && NULL != (pt=strstr(m_ProcessedArgs+1, Name)) && + *(pt-1) == ' ' && *(pt+strlen(Name)) == ' ') { + dsyslog("Skipping configuration entry %s=%s (overridden in command line)", Name, Value); + return true; + } + + if (!strcasecmp(Name, "RemoveQuotesFromShortText")) quotedshorttext = atoi(Value); + else if (!strcasecmp(Name, "MoveDescriptionFromShortText")) blankbeforedescription = atoi(Value); + else if (!strcasecmp(Name, "RemoveRepeatedTitleFromShortText")) repeatedtitle = atoi(Value); + else if (!strcasecmp(Name, "RemoveDoubleQuotesFromShortText")) doublequotedshorttext = atoi(Value); + else if (!strcasecmp(Name, "RemoveUselessFormatting")) removeformatting = atoi(Value); + else if (!strcasecmp(Name, "MoveLongShortTextToDescription")) longshorttext = atoi(Value); + else if (!strcasecmp(Name, "PreventEqualShortTextAndDescription")) equalshorttextanddescription = atoi(Value); + else if (!strcasecmp(Name, "ReplaceBackticksWithSingleQuotes")) nobackticks = atoi(Value); + else if (!strcasecmp(Name, "FixStreamComponentDescriptions")) components = atoi(Value); + else + return false; + + return true; +} diff --git a/config.h b/config.h new file mode 100644 index 0000000..d9efe76 --- /dev/null +++ b/config.h @@ -0,0 +1,38 @@ +/* + * config.h: Global configuration and user settings + * + * See the README file for copyright information and how to reach the author. + * + */ + +#ifndef _EPGFIXER_CONFIG_H_ +#define _EPGFIXER_CONFIG_H_ + +#include "regexp.h" + +class cEpgfixerSetup +{ +public: + int quotedshorttext; + int blankbeforedescription; + int repeatedtitle; + int doublequotedshorttext; + int removeformatting; + int longshorttext; + int equalshorttextanddescription; + int nobackticks; + int components; + + cEpgfixerSetup(); + bool SetupParse(const char *Name, const char *Value); + bool ProcessArgs(int argc, char *argv[]); + +protected: + bool ProcessArg(const char *Name, const char *Value); + static cString m_ProcessedArgs; +}; + +// Global instance +extern cEpgfixerSetup EpgfixerSetup; + +#endif //_EPGFIXER_CONFIG_H_ diff --git a/epgfixer.c b/epgfixer.c new file mode 100644 index 0000000..6fa9f3c --- /dev/null +++ b/epgfixer.c @@ -0,0 +1,165 @@ +/* + * epgfixer.c: A plugin for the Video Disk Recorder + * + * See the README file for copyright information and how to reach the author. + * + */ + +#include <vdr/plugin.h> +#include <vdr/i18n.h> +#include "regexp.h" +#include "setup_menu.h" +#include "epghandler.h" + +#if defined(APIVERSNUM) && APIVERSNUM < 10726 +#error "VDR-1.7.26 API version or greater is required!" +#endif + +static const char *VERSION = "0.0.3"; +static const char *DESCRIPTION = trNOOP("Fix bugs in EPG"); + +class cPluginEpgfixer : public cPlugin { +private: + // Add any member variables or functions you may need here. +public: + cPluginEpgfixer(void); + virtual ~cPluginEpgfixer(); + 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 Initialize(void); + virtual bool Start(void); + virtual void Stop(void); + virtual void Housekeeping(void); + virtual void MainThreadHook(void); + virtual cString Active(void); + virtual time_t WakeupTime(void); + virtual const char *MainMenuEntry(void); + virtual cOsdObject *MainMenuAction(void); + virtual cMenuSetupPage *SetupMenu(void); + virtual bool SetupParse(const char *Name, const char *Value); + virtual bool Service(const char *Id, void *Data = NULL); + virtual const char **SVDRPHelpPages(void); + virtual cString SVDRPCommand(const char *Command, const char *Option, int &ReplyCode); + }; + +cPluginEpgfixer::cPluginEpgfixer(void) +{ + // Initialize any member variables here. + // DON'T DO ANYTHING ELSE THAT MAY HAVE SIDE EFFECTS, REQUIRE GLOBAL + // VDR OBJECTS TO EXIST OR PRODUCE ANY OUTPUT! +} + +cPluginEpgfixer::~cPluginEpgfixer() +{ + // Clean up after yourself! +} + +const char *cPluginEpgfixer::CommandLineHelp(void) +{ + // Return a string that describes all known command line options. + return NULL; +} + +bool cPluginEpgfixer::ProcessArgs(int argc, char *argv[]) +{ + // Implement command line argument processing here if applicable. + return true; +} + +bool cPluginEpgfixer::Initialize(void) +{ + // Initialize any background activities the plugin shall perform. + EpgfixerRegexps.SetRegexpConfigFile(AddDirectory(cPlugin::ConfigDirectory(PLUGIN_NAME_I18N), "regexp.conf")); // allowed only via main thread!); + EpgfixerRegexps.ReloadRegexps(); + return new cEpgfixerEpgHandler(); +} + +bool cPluginEpgfixer::Start(void) +{ + // Start any background activities the plugin shall perform. + return true; +} + +void cPluginEpgfixer::Stop(void) +{ + // Stop any background activities the plugin is performing. +} + +void cPluginEpgfixer::Housekeeping(void) +{ + // Perform any cleanup or other regular tasks. +} + +const char *cPluginEpgfixer::MainMenuEntry(void) +{ + return NULL; +} + +void cPluginEpgfixer::MainThreadHook(void) +{ + // Perform actions in the context of the main program thread. + // WARNING: Use with great care - see PLUGINS.html! +} + +cString cPluginEpgfixer::Active(void) +{ + // Return a message string if shutdown should be postponed + return NULL; +} + +time_t cPluginEpgfixer::WakeupTime(void) +{ + // Return custom wakeup time for shutdown script + return 0; +} + +cOsdObject *cPluginEpgfixer::MainMenuAction(void) +{ + // Perform the action when selected from the main VDR menu. + return NULL; +} + +cMenuSetupPage *cPluginEpgfixer::SetupMenu(void) +{ + // Return a setup menu in case the plugin supports one. + return new cMenuSetupEpgfixer(); +} + +bool cPluginEpgfixer::SetupParse(const char *Name, const char *Value) +{ + // Parse your own setup parameters and store their values. + return EpgfixerSetup.SetupParse(Name, Value); +} + +bool cPluginEpgfixer::Service(const char *Id, void *Data) +{ + // Handle custom service requests from other plugins + return false; +} + +const char **cPluginEpgfixer::SVDRPHelpPages(void) +{ + static const char *HelpPages[] = { + "RLRE\n" + " Reload regexp.conf.", + NULL + }; + return HelpPages; +} + +cString cPluginEpgfixer::SVDRPCommand(const char *Command, const char *Option, int &ReplyCode) +{ + if (strcasecmp(Command, "RLRE") == 0) { + if (EpgfixerRegexps.ReloadRegexps()) { + return cString("Reloaded regexp.conf"); + } else { + ReplyCode = 554; // Requested action failed + return cString("Reloading regexp.conf failed"); + } + } + return NULL; +} + +VDRPLUGINCREATOR(cPluginEpgfixer); // Don't touch this! diff --git a/epgfixer/regexp.conf b/epgfixer/regexp.conf new file mode 100644 index 0000000..7495aaa --- /dev/null +++ b/epgfixer/regexp.conf @@ -0,0 +1,4 @@ +# Remove "Movie: " or "Document: " from the beginning of title field: +title=^(?:Movie: |Document: )(?<title>.*)$ +# Move parental rating from end of title to correct EPG field: +title=^(?<title>.*)[ ][(](?<rating>[0-9S]{1,2})[)][ ]*$ diff --git a/epghandler.c b/epghandler.c new file mode 100644 index 0000000..cf84838 --- /dev/null +++ b/epghandler.c @@ -0,0 +1,367 @@ +/* + * epghandler.c: EpgHandler + * + * See the README file for copyright information and how to reach the author. + * + */ + +#include "epghandler.h" +#include "config.h" +#include <vdr/tools.h> +#include <string.h> + +#ifdef HAVE_PCREPOSIX +#include <pcre.h> +#endif + +typedef enum { ATITLE,PTITLE,TITLE,ASHORTTEXT,PSHORTTEXT,SHORTTEXT,ADESCRIPTION,PDESCRIPTION,DESCRIPTION,RATING } backrefs; +const char *strBackrefs[] = { "atitle","ptitle","title","ashorttext","pshorttext","shorttext","adescription","pdescription","description","rating" }; + +void cEpgfixerEpgHandler::FixOriginalEpgBugs(cEvent *event) +{ + if (isempty(event->Title())) { + // we don't want any "(null)" titles + event->SetTitle("No title"); + } + + // Some TV stations apparently have their own idea about how to fill in the + // EPG data. Let's fix their bugs as good as we can: + + // Some channels put the ShortText in quotes and use either the ShortText + // or the Description field, depending on how long the string is: + // + // Title + // "ShortText". Description + // + if (EpgfixerSetup.quotedshorttext && (event->ShortText() == NULL) != (event->Description() == NULL)) { + char *p = event->ShortText() ? strdup(event->ShortText()) : strdup(event->Description()); + if (*p == '"') { + const char *delim = "\"."; + char *e = strstr(p + 1, delim); + if (e) { + *e = 0; + char *s = strdup(p + 1); + char *d = strdup(e + strlen(delim)); + event->SetShortText(s); + event->SetDescription(d); + } + } + free(p); + } + + // Some channels put the Description into the ShortText (preceded + // by a blank) if there is no actual ShortText and the Description + // is short enough: + // + // Title + // Description + // + if (EpgfixerSetup.blankbeforedescription && event->ShortText() && !event->Description()) { + if (*event->ShortText() == ' ') { + char *shortText = strdup(event->ShortText()); + memmove(shortText, shortText + 1, strlen(shortText)); + event->SetDescription(shortText); + event->SetShortText(NULL); + free(shortText); + } + } + + // Sometimes they repeat the Title in the ShortText: + // + // Title + // Title + // + if (EpgfixerSetup.repeatedtitle && event->ShortText() && strcmp(event->Title(), event->ShortText()) == 0) { + event->SetShortText(NULL); + } + + // Some channels put the ShortText between double quotes, which is nothing + // but annoying (some even put a '.' after the closing '"'): + // + // Title + // "ShortText"[.] + // + if (EpgfixerSetup.doublequotedshorttext && event->ShortText() && *event->ShortText() == '"') { + char *shortText = strdup(event->ShortText()); + int l = strlen(shortText); + if (l > 2 && (shortText[l - 1] == '"' || (shortText[l - 1] == '.' && shortText[l - 2] == '"'))) { + memmove(shortText, shortText + 1, l); + char *p = strrchr(shortText, '"'); + if (p) + *p = 0; + } + event->SetShortText(shortText); + free(shortText); + } + + // Some channels apparently try to do some formatting in the texts, + // which is a bad idea because they have no way of knowing the width + // of the window that will actually display the text. + // Remove excess whitespace: + if (EpgfixerSetup.removeformatting) { + char *temp; + temp = strdup(event->Title()); + event->SetTitle(compactspace(temp)); + free(temp); + if (event->ShortText()) { + temp = strdup(event->ShortText()); + event->SetShortText(compactspace(temp)); + free(temp); + } + if (event->Description()) { + temp = strdup(event->Description()); + event->SetDescription(compactspace(temp)); + free(temp); + } + } + +#define MAX_USEFUL_EPISODE_LENGTH 40 + // Some channels put a whole lot of information in the ShortText and leave + // the Description totally empty. So if the ShortText length exceeds + // MAX_USEFUL_EPISODE_LENGTH, let's put this into the Description + // instead: + if (EpgfixerSetup.longshorttext && !isempty(event->ShortText()) && isempty(event->Description())) { + if (strlen(event->ShortText()) > MAX_USEFUL_EPISODE_LENGTH) { + event->SetDescription(event->ShortText()); + event->SetShortText(NULL); + } + } + + // Some channels put the same information into ShortText and Description. + // In that case we delete one of them: + if (EpgfixerSetup.equalshorttextanddescription && event->ShortText() && event->Description() && strcmp(event->ShortText(), event->Description()) == 0) { + if (strlen(event->ShortText()) > MAX_USEFUL_EPISODE_LENGTH) { + event->SetShortText(NULL); + } + else { + event->SetDescription(NULL); + + } + } + + // Some channels use the ` ("backtick") character, where a ' (single quote) + // would be normally used. Actually, "backticks" in normal text don't make + // much sense, so let's replace them: + if (EpgfixerSetup.nobackticks) { + char *temp; + temp = strdup(event->Title()); + event->SetTitle(strreplace(temp, '`', '\'')); + free(temp); + if (event->ShortText()) { + temp = strdup(event->ShortText()); + event->SetShortText(strreplace(temp, '`', '\'')); + free(temp); + } + if (event->Description()) { + temp = strdup(event->Description()); + event->SetDescription(strreplace(temp, '`', '\'')); + free(temp); + } + } + + // The stream components have a "description" field which some channels + // apparently have no idea of how to set correctly: + const cComponents *components = event->Components(); + if (EpgfixerSetup.components && components) { + for (int i = 0; i < components->NumComponents(); i++) { + tComponent *p = components->Component(i); + switch (p->stream) { + case 0x01: { // video + if (p->description) { + if (strcasecmp(p->description, "Video") == 0 || + strcasecmp(p->description, "Bildformat") == 0) { + // Yes, we know it's video - that's what the 'stream' code + // is for! But _which_ video is it? + free(p->description); + p->description = NULL; + } + } + if (!p->description) { + switch (p->type) { + case 0x01: + case 0x05: p->description = strdup("4:3"); break; + case 0x02: + case 0x03: + case 0x06: + case 0x07: p->description = strdup("16:9"); break; + case 0x04: + case 0x08: p->description = strdup(">16:9"); break; + case 0x09: + case 0x0D: p->description = strdup("HD 4:3"); break; + case 0x0A: + case 0x0B: + case 0x0E: + case 0x0F: p->description = strdup("HD 16:9"); break; + case 0x0C: + case 0x10: p->description = strdup("HD >16:9"); break; + default: ; + } + } + } + break; + case 0x02: { // audio + if (p->description) { + if (strcasecmp(p->description, "Audio") == 0) { + // Yes, we know it's audio - that's what the 'stream' code + // is for! But _which_ audio is it? + free(p->description); + p->description = NULL; + } + } + if (!p->description) { + switch (p->type) { + case 0x05: p->description = strdup("Dolby Digital"); break; + default: ; // all others will just display the language + } + } + } + break; + default: ; + } + } + } + + // VDR can't usefully handle newline characters in the title, shortText or component description of EPG + // data, so let's always convert them to blanks (independent of the setting of EPGBugfixLevel): + char *temp = strdup(event->Title()); + event->SetTitle(strreplace(temp, '\n', ' ')); + free(temp); + if (event->ShortText()) { + temp = strdup(event->ShortText()); + event->SetShortText(strreplace(temp, '\n', ' ')); + free(temp); + } + if (components) { + for (int i = 0; i < components->NumComponents(); i++) { + tComponent *p = components->Component(i); + if (p->description) + strreplace(p->description, '\n', ' '); + } + } +} + +bool cEpgfixerEpgHandler::ApplyRegexp(cRegexp *regexp, cEvent *Event, const char *input) +{ + bool active = true; + if (regexp->NumChannels() > 0) { + bool found = false; + int i = 0; + while (i < regexp->NumChannels()) { + if (Channels.GetByChannelID(Event->ChannelID())->Number() == regexp->GetChannel(i)) + found = true; + i++; + } + if (!found) { + active = false; + } + } + if (active && regexp->Enabled() && regexp->GetRe()) { + const char *string; + int ovector[20]; + int rc; + rc = pcre_exec(regexp->GetRe(), regexp->GetSd(), input, strlen(input), 0, 0, ovector, 20); + if (rc > 0) { + int i = 0; + while (i < 10) { + if (pcre_get_named_substring(regexp->GetRe(), input, ovector, rc, strBackrefs[i], &string) != PCRE_ERROR_NOSUBSTRING) { + char *tempstring = 0; + switch (i) { + case TITLE: + Event->SetTitle(string); + break; + case ATITLE: + tempstring = (char *)malloc(strlen(Event->Title())+strlen(string)+1); + strcpy(tempstring, Event->Title()); + strcat(tempstring, string); + Event->SetTitle(tempstring); + break; + case PTITLE: + tempstring = (char *)malloc(strlen(Event->Title())+strlen(string)+1); + strcpy(tempstring, string); + strcat(tempstring, Event->Title()); + Event->SetTitle(tempstring); + break; + case SHORTTEXT: + Event->SetShortText(string); + break; + case ASHORTTEXT: + tempstring = (char *)malloc(strlen(Event->ShortText())+strlen(string)+1); + strcpy(tempstring, Event->ShortText()); + strcat(tempstring, string); + Event->SetShortText(tempstring); + break; + case PSHORTTEXT: + tempstring = (char *)malloc(strlen(Event->ShortText())+strlen(string)+1); + strcpy(tempstring, string); + strcat(tempstring, Event->ShortText()); + Event->SetShortText(tempstring); + break; + case DESCRIPTION: + Event->SetDescription(string); + break; + case ADESCRIPTION: + tempstring = (char *)malloc(strlen(Event->Description())+strlen(string)+1); + strcpy(tempstring, Event->Description()); + strcat(tempstring, string); + Event->SetDescription(tempstring); + break; + case PDESCRIPTION: + tempstring = (char *)malloc(strlen(Event->Description())+strlen(string)+1); + strcpy(tempstring, string); + strcat(tempstring, Event->Description()); + Event->SetDescription(tempstring); + break; + case RATING: + Event->SetParentalRating(atoi(string)); + break; + default: + break; + } + pcre_free_substring(string); + free(tempstring); + } + i++; + } + return true; + } + } + return false; +} + +bool cEpgfixerEpgHandler::FixBugs(cEvent *Event) +{ + int res = false; + char *tmpstring; + cRegexp *regex = (cRegexp *)EpgfixerRegexps.regexps->First(); + while (regex) { + if (regex->Enabled()) { + switch (regex->GetSource()) { + case REGEXP_TITLE: + tmpstring = strdup(Event->Title()); + break; + case REGEXP_SHORTTEXT: + tmpstring = strdup(Event->ShortText()); + break; + case REGEXP_DESCRIPTION: + tmpstring = strdup(Event->Description()); + break; + default: + tmpstring = strdup(""); + break; + } + int ret = ApplyRegexp(regex, Event, tmpstring); + if (ret && !res) + res = true; + free(tmpstring); + } + regex = (cRegexp *)regex->Next(); + } + return res; +} + +bool cEpgfixerEpgHandler::FixEpgBugs(cEvent *Event) +{ + FixOriginalEpgBugs(Event); + FixBugs(Event); + return false; +} diff --git a/epghandler.h b/epghandler.h new file mode 100644 index 0000000..8971f20 --- /dev/null +++ b/epghandler.h @@ -0,0 +1,25 @@ +/* + * epghandler.h: EpgHandler + * + * See the README file for copyright information and how to reach the author. + * + */ + +#ifndef __EPGFIXER_EPGHANDLER_H +#define __EPGFIXER_EPGHANDLER_H + +#include <vdr/epg.h> +#include "regexp.h" + +class cEpgfixerEpgHandler : public cEpgHandler +{ +private: + bool ApplyRegexp(cRegexp *regexp, cEvent *Event, const char *input); + void FixOriginalEpgBugs(cEvent *event); + bool FixBugs(cEvent *Event); +public: + cEpgfixerEpgHandler(void) {}; + virtual bool FixEpgBugs(cEvent *Event); +}; + +#endif //__EPGFIXER_EPGHANDLER_H diff --git a/po/fi_FI.po b/po/fi_FI.po new file mode 100644 index 0000000..963dd15 --- /dev/null +++ b/po/fi_FI.po @@ -0,0 +1,68 @@ +# VDR Epgfixer plugin language source file. +# Copyright (C) 2012 Matti Lehtimäki +# This file is distributed under the same license as the PACKAGE package. +# Matti Lehtimäki, 2012- +# +msgid "" +msgstr "" +"Project-Id-Version: vdr-epgfixer 0.0.3\n" +"Report-Msgid-Bugs-To: <see README>\n" +"POT-Creation-Date: 2012-03-25 14:46+0300\n" +"PO-Revision-Date: 2012-03-25 15:20+0300\n" +"Last-Translator: Matti Lehtimäki <matti.lehtimaki@gmail.com>\n" +"Language-Team: Finnish <vdr@linuxtv.org>\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: fi\n" + +msgid "Fix bugs in EPG" +msgstr "Korjaa virheitä EPG:ssä" + +msgid "Toggle state" +msgstr "Vaihda tila" + +msgid "Add" +msgstr "Lisää" + +msgid "Delete" +msgstr "Poista" + +msgid "Cancel" +msgstr "Peru" + +msgid "Remove quotes from ShortText" +msgstr "Poista lainaukset lyhyestä kuvauksesta" + +msgid "Move Description from ShortText" +msgstr "Siirrä kuvaus lyhyeen kuvaukseen" + +msgid "Remove repeated title from ShortText" +msgstr "Poista nimi lyhyestä kuvauksesta" + +msgid "Remove double quotes from ShortText" +msgstr "Poista lainausmerkit lyhyestä kuvauksesta" + +msgid "Remove useless formatting" +msgstr "Poista ylimääräinen muotoilu" + +msgid "Move long ShortText to Description" +msgstr "Siirrä pitkä lyhyt kuvaus kuvaukseksi" + +msgid "Prevent equal ShortText and Description" +msgstr "Estä identtinen kuvaus ja lyhyt kuvaus" + +msgid "Replace backticks with single quotes" +msgstr "Korvaa gravis heittomerkillä" + +msgid "Fix stream component descriptions" +msgstr "Korjaa lähetekomponenttien kuvaukset" + +msgid "Regular expressions" +msgstr "Säännölliset lausekkeet" + +msgid "Reload regexp.conf" +msgstr "Lataa uudelleen regexp.conf" + +msgid "Clear EPG data" +msgstr "Tyhjennä EPG tiedot" diff --git a/regexp.c b/regexp.c new file mode 100644 index 0000000..ee4073a --- /dev/null +++ b/regexp.c @@ -0,0 +1,197 @@ +/* + * regexp.c: Regular expression list + * + * See the README file for copyright information and how to reach the author. + * + */ + +#include "regexp.h" +#include <unistd.h> + +/* Global instance */ +cEpgfixerRegexps EpgfixerRegexps; + +const char *strSources[] = { "title","shorttext","description","undefined" }; + +cRegexp::cRegexp() +{ + enabled = false; + error = NULL; + erroffset = -1; + regexp = NULL; + string = NULL; + source = REGEXP_UNDEFINED; + re = NULL; + sd = NULL; + numchannels = 0; + channels = NULL; +} + +cRegexp::~cRegexp(void) +{ + Free(); +} + +void cRegexp::Free(void) +{ + free(channels); + free(regexp); + free(string); + channels = NULL; + regexp = NULL; + string = NULL; + FreeCompiled(); +} + +int cRegexp::GetChannel(int index) +{ + if (index >= 0 && index < numchannels) + return channels[index]; + else + return -1; +} + +void cRegexp::ToggleEnabled(void) +{ + enabled = enabled ? 0 : 1; +} + +void cRegexp::SetFromString(char *s, bool Enabled, bool Precompile) +{ + Free(); + string = strdup(s); + enabled = Enabled; + bool precompile = Precompile; + if (s[0] == '!' || s[0] == '#') { + enabled = false; + precompile = false; + } + char *p = (s[0] == '#') ? s : strchr(s, '='); + if (p) { + if (p[0] == '#') + source = REGEXP_UNDEFINED; + else { + *p = 0; + regexp = strdup(p + 1); + char *chanfield = (s[0] == '!') ? s+1 : s; + char *field = chanfield; + if (atoi(chanfield)) { + char *f = strchr(chanfield, ':'); + if (f) { + *f = 0; + field = f+1; + char *c = chanfield; + numchannels = 0; + while (c) { + numchannels++; + c = strchr(c+1, ','); + } + if (numchannels > 0) { + c = chanfield; + channels = (int *)malloc(sizeof(int)*numchannels); + int i = 1; + channels[i] = atoi(c); + while (i < numchannels) { + c = strchr(c+1, ','); + channels[i] = atoi(c+1); + i++; + } + } + } + } + if (strcmp(field, "title") == 0) + source = REGEXP_TITLE; + if (strcmp(field, "shorttext") == 0) + source = REGEXP_SHORTTEXT; + if (strcmp(field, "description") == 0) + source = REGEXP_DESCRIPTION; + if (precompile) + Compile(); + } + } +} + +void cRegexp::FreeCompiled() +{ + if (re) { + pcre_free(re); + re = NULL; + } + if (sd) { +#ifdef PCRE_CONFIG_JIT + pcre_free_study(sd); +#else + pcre_free(sd); +#endif + sd = NULL; + } +} + +void cRegexp::Compile() +{ + FreeCompiled(); + re = pcre_compile(regexp, 0, (const char **)&error, &erroffset, NULL); + if (error) { + esyslog("PCRE compile error: %s at offset %i", error, erroffset); + enabled = false; + } + else { + sd = pcre_study(re, 0, (const char **)&error); + if (error) + esyslog("PCRE study error: %s", error); + } +} + +cEpgfixerRegexps::cEpgfixerRegexps() +{ + fileName = NULL; + regexps = new cRegexpList(); +} + +cEpgfixerRegexps::~cEpgfixerRegexps() +{ + free(fileName); + delete regexps; +} + +void cEpgfixerRegexps::SetRegexpConfigFile(const char *FileName) +{ + fileName = strdup(FileName); +} + +const char *cEpgfixerRegexps::RegexpConfigFile() +{ + return fileName; +} + +bool cEpgfixerRegexps::ReloadRegexps(bool AllowComments, bool Precompile) +{ + regexps->Clear(); + return LoadRegexps(fileName, AllowComments, Precompile); +} + +bool cEpgfixerRegexps::LoadRegexps(const char *FileName, bool AllowComments, bool Precompile) +{ + bool result = false; + if (FileName && access(FileName, F_OK) == 0) { + FILE *f = fopen(FileName, "r"); + if (f) { + char *s; + int line = 0; + cReadLine ReadLine; + while ((s = ReadLine.Read(f)) != NULL) { + line++; + if (!isempty(s)) { + regexps->Add(new cRegexp()); + regexps->Last()->SetFromString(s, true, Precompile); + } + } + fclose(f); + } + else { + LOG_ERROR_STR(FileName); + result = false; + } + } + return result; +} diff --git a/regexp.h b/regexp.h new file mode 100644 index 0000000..1062133 --- /dev/null +++ b/regexp.h @@ -0,0 +1,79 @@ +/* + * regexp.h: Regular expression list + * + * See the README file for copyright information and how to reach the author. + * + */ + +#ifndef _REGEXP_H_ +#define _REGEXP_H_ + +#include <vdr/tools.h> + +#ifdef HAVE_PCREPOSIX +#include <pcre.h> +#endif + +typedef enum { REGEXP_TITLE,REGEXP_SHORTTEXT,REGEXP_DESCRIPTION,REGEXP_UNDEFINED } sources; +extern const char *strSources[]; + +class cRegexp : public cListObject +{ +private: + bool enabled; + char *error; + int erroffset; + char *regexp; + char *string; + int source; + pcre *re; + pcre_extra *sd; + int numchannels; + int *channels; + void Free(); +public: + cRegexp(); + cRegexp(int Source, const char *Regex, bool Enabled, bool Precompile); + virtual ~cRegexp(); + void SetFromString(char *string, bool Enabled, bool Precompile); + const char *GetString() { return string; } + void Compile(); + void FreeCompiled(); + bool Enabled(void) { return enabled; } + void ToggleEnabled(void); + int NumChannels() { return numchannels; } + int GetChannel(int index); + int GetSource(void) { return source; } + pcre *GetRe(void) { return re; } + pcre_extra *GetSd(void) { return sd; } +}; + +class cRegexpList : public cList<cRegexp> +{ +public: + void Clear(void) + { + cList<cRegexp>::Clear(); + } + cRegexpList(void) {} +}; + +class cEpgfixerRegexps +{ +private: + bool LoadRegexps(const char *FileName = NULL, bool AllowComments = true, bool Precompile = true); + char *fileName; +public: + cRegexpList *regexps; + + cEpgfixerRegexps(); + ~cEpgfixerRegexps(); + void SetRegexpConfigFile(const char *FileName); + const char *RegexpConfigFile(); + bool ReloadRegexps(bool AllowComments = true, bool Precompile = true); +}; + +// Global instance +extern cEpgfixerRegexps EpgfixerRegexps; + +#endif //_REGEXP_H_ diff --git a/setup_menu.c b/setup_menu.c new file mode 100644 index 0000000..eb8032d --- /dev/null +++ b/setup_menu.c @@ -0,0 +1,246 @@ +/* + * setup_menu.c: Setup Menu + * + * See the README file for copyright information and how to reach the author. + * + */ + +#include "setup_menu.h" +#include <vdr/config.h> +#include <vdr/i18n.h> +#include "regexp.h" + +//--- cMenuSetupRegexp ------------------------------------------------------ + +#define MAXREGEXPLENGTH 512 + +const char *RegexpChars = + " abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-=.,*%$^<>~:;\\/?!()[]{}#"; + +class cMenuSetupRegexp : public cMenuSetupPage +{ +private: + const char *fileName; + char **lines; + char **numlines; + void LoadRegexpArray(); + void FreeRegexpArray(); + void Save(); +protected: + virtual void Store(void); + void Set(void); +public: + cMenuSetupRegexp(void); + ~cMenuSetupRegexp(void); + virtual eOSState ProcessKey(eKeys Key); +}; + +void cMenuSetupRegexp::LoadRegexpArray() +{ + lines = (char **)malloc(sizeof(char *)*(EpgfixerRegexps.regexps->Count())); + int i = 0; + cRegexp *regex = (cRegexp *)EpgfixerRegexps.regexps->First(); + while (regex) { + lines[i] = (char *)malloc(sizeof(char)*MAXREGEXPLENGTH); + snprintf(lines[i], MAXREGEXPLENGTH, "%s", regex->GetString()); + regex = (cRegexp *)regex->Next(); + i++; + } +} + +cMenuSetupRegexp::cMenuSetupRegexp(void) +{ + cEitFilter::SetDisableUntil(time(NULL) + 1000); + SetCols(2); + fileName = EpgfixerRegexps.RegexpConfigFile(); + LoadRegexpArray(); + Set(); +} + +void cMenuSetupRegexp::FreeRegexpArray(void) +{ + int i = 0; + while (i < EpgfixerRegexps.regexps->Count()) { + free(lines[i]); + i++; + } + free(lines); +} + +cMenuSetupRegexp::~cMenuSetupRegexp(void) +{ + FreeRegexpArray(); + cEitFilter::SetDisableUntil(time(NULL) + 5); +} + +void cMenuSetupRegexp::Set(void) +{ + Clear(); + int i = 0; + cRegexp *regex = (cRegexp *)EpgfixerRegexps.regexps->First(); + while (i < EpgfixerRegexps.regexps->Count()) { + Add(new cMenuEditStrItem(regex->Enabled() ? "+" : "-", lines[i], MAXREGEXPLENGTH, RegexpChars)); + regex = (cRegexp *)regex->Next(); + i++; + } + SetHelp(tr("Toggle state"), tr("Add"), tr("Delete"), tr("Cancel")); + Display(); +} + +void cMenuSetupRegexp::Store(void) +{ + // Store regular expressions back to list + int i = 0; + cRegexp *regex = (cRegexp *)EpgfixerRegexps.regexps->First(); + while (i < EpgfixerRegexps.regexps->Count()) { + regex->SetFromString(lines[i], regex->Enabled(), true); + regex = (cRegexp *)regex->Next(); + i++; + } +} + +void cMenuSetupRegexp::Save(void) +{ + // Store regular expressions to regxep.conf + if (fileName && access(fileName, F_OK) == 0) { + FILE *f = fopen(fileName, "w"); + if (f) { + cRegexp *regex = (cRegexp *)EpgfixerRegexps.regexps->First(); + while (regex) { + if (regex->GetSource() == REGEXP_UNDEFINED) + fprintf(f, "%s\n", regex->GetString()); + else + fprintf(f, "%s%s\n", regex->Enabled() ? "" : "!", regex->GetString()); + regex = (cRegexp *)regex->Next(); + } + fclose(f); + } + } +} + +eOSState cMenuSetupRegexp::ProcessKey(eKeys Key) +{ + eOSState state = cOsdMenu::ProcessKey(Key); + + if (state == osUnknown) { + switch (Key) { + case kRed: + if (EpgfixerRegexps.regexps->Get(Current())->GetSource() != REGEXP_UNDEFINED) { + EpgfixerRegexps.regexps->Get(Current())->ToggleEnabled(); + Set(); + Display(); + } + state = osContinue; + break; + case kGreen: + Store(); + FreeRegexpArray(); + EpgfixerRegexps.regexps->Add(new cRegexp()); + LoadRegexpArray(); + Set(); + Display(); + state = osContinue; + break; + case kYellow: + Store(); + FreeRegexpArray(); + EpgfixerRegexps.regexps->Del(EpgfixerRegexps.regexps->Get(Current()),true); + LoadRegexpArray(); + Set(); + Display(); + state = osContinue; + break; + case kBlue: + EpgfixerRegexps.ReloadRegexps(); + state = osBack; + break; + case kOk: + Store(); + Save(); + EpgfixerRegexps.ReloadRegexps(); + state = osBack; + break; + default: + break; + } + } + return state; +} + +//--- cMenuSetupEpgfixer ------------------------------------------------------ + +cMenuSetupEpgfixer::cMenuSetupEpgfixer(void) +{ + memcpy(&newconfig, &EpgfixerSetup, sizeof(cEpgfixerSetup)); + Set(); +} + +void cMenuSetupEpgfixer::Set(void) +{ + Clear(); + + Add(new cMenuEditBoolItem(tr("Remove quotes from ShortText"), + &newconfig.quotedshorttext)); + Add(new cMenuEditBoolItem(tr("Move Description from ShortText"), + &newconfig.blankbeforedescription)); + Add(new cMenuEditBoolItem(tr("Remove repeated title from ShortText"), + &newconfig.repeatedtitle)); + Add(new cMenuEditBoolItem(tr("Remove double quotes from ShortText"), + &newconfig.doublequotedshorttext)); + Add(new cMenuEditBoolItem(tr("Remove useless formatting"), + &newconfig.removeformatting)); + Add(new cMenuEditBoolItem(tr("Move long ShortText to Description"), + &newconfig.longshorttext)); + Add(new cMenuEditBoolItem(tr("Prevent equal ShortText and Description"), + &newconfig.equalshorttextanddescription)); + Add(new cMenuEditBoolItem(tr("Replace backticks with single quotes"), + &newconfig.nobackticks)); + Add(new cMenuEditBoolItem(tr("Fix stream component descriptions"), + &newconfig.components)); + Add(new cOsdItem(tr("Regular expressions"), osUser1)); + SetHelp(tr("Reload regexp.conf"),NULL,NULL, tr("Clear EPG data")); + Display(); +} + +void cMenuSetupEpgfixer::Store(void) +{ + memcpy(&EpgfixerSetup, &newconfig, sizeof(cEpgfixerSetup)); + + SetupStore("RemoveQuotesFromShortText", EpgfixerSetup.quotedshorttext); + SetupStore("MoveDescriptionFromShortText", EpgfixerSetup.blankbeforedescription); + SetupStore("RemoveRepeatedTitleFromShortText", EpgfixerSetup.repeatedtitle); + SetupStore("RemoveDoubleQuotesFromShortText", EpgfixerSetup.doublequotedshorttext); + SetupStore("RemoveUselessFormatting", EpgfixerSetup.removeformatting); + SetupStore("MoveLongShortTextToDescription", EpgfixerSetup.longshorttext); + SetupStore("PreventEqualShortTextAndDescription", EpgfixerSetup.equalshorttextanddescription); + SetupStore("ReplaceBackticksWithSingleQuotes", EpgfixerSetup.nobackticks); + SetupStore("FixStreamComponentDescriptions", EpgfixerSetup.components); + + Setup.Save(); +} + +eOSState cMenuSetupEpgfixer::ProcessKey(eKeys Key) +{ + eOSState state = cMenuSetupPage::ProcessKey(Key); + + if (state == osUnknown) { + switch (Key) { + case kRed: + EpgfixerRegexps.ReloadRegexps(); + state = osContinue; + break; + case kBlue: + cEitFilter::SetDisableUntil(time(NULL) + 10); + if (cSchedules::ClearAll()) { + cEitFilter::SetDisableUntil(time(NULL) + 10); + } + state = osContinue; + break; + default: + break; + } + } + else if (state == osUser1) + return AddSubMenu(new cMenuSetupRegexp()); + return state; +} diff --git a/setup_menu.h b/setup_menu.h new file mode 100644 index 0000000..53d7996 --- /dev/null +++ b/setup_menu.h @@ -0,0 +1,26 @@ +/* + * setup_menu.h: Setup Menu + * + * See the README file for copyright information and how to reach the author. + * + */ + +#ifndef __EPGFIXER_SETUP_MENU_H +#define __EPGFIXER_SETUP_MENU_H + +#include <vdr/menuitems.h> +#include "config.h" + +class cMenuSetupEpgfixer : public cMenuSetupPage +{ +private: + cEpgfixerSetup newconfig; +protected: + virtual void Store(void); + void Set(void); +public: + cMenuSetupEpgfixer(void); + virtual eOSState ProcessKey(eKeys Key); +}; + +#endif //__EPGFIXER_SETUP_MENU_H |