diff options
| author | louis <louis.braun@gmx.de> | 2016-01-26 18:32:38 +0100 |
|---|---|---|
| committer | louis <louis.braun@gmx.de> | 2016-01-26 18:32:38 +0100 |
| commit | 809fbda03c5014ba9cd361f5113d1d717cd41ea6 (patch) | |
| tree | 264bbc5640375f1bcb165fc7f4a3e595adcc26ca /extensions | |
| parent | 196dd7eb9965a405bb16b51dc870fbbb31aeef87 (diff) | |
| download | vdr-plugin-skindesigner-809fbda03c5014ba9cd361f5113d1d717cd41ea6.tar.gz vdr-plugin-skindesigner-809fbda03c5014ba9cd361f5113d1d717cd41ea6.tar.bz2 | |
Version 0.8.0 beta
Diffstat (limited to 'extensions')
28 files changed, 4680 insertions, 0 deletions
diff --git a/extensions/cairoimage.c b/extensions/cairoimage.c new file mode 100644 index 0000000..0fc6879 --- /dev/null +++ b/extensions/cairoimage.c @@ -0,0 +1,100 @@ +#include "cairoimage.h" + +cCairoImage::cCairoImage(void) { + surface = NULL; + cr = NULL; +} + +cCairoImage::~cCairoImage() { + if (cr) + cairo_destroy (cr); + if (surface) + cairo_surface_destroy (surface); +} + +void cCairoImage::InitCairoImage(int width, int height) { + this->width = width; + this->height = height; + + surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height); + cr = cairo_create(surface); + cairo_set_antialias(cr, CAIRO_ANTIALIAS_SUBPIXEL); +} + +void cCairoImage::DrawTextVertical(string text, tColor color, string font, int size, int direction) { + + int imgHeight = GetTextWidth(text, font, size); + InitCairoImage(size * 1.2, imgHeight); + + SetColor(color); + cairo_font_weight_t fontWeight = CAIRO_FONT_WEIGHT_NORMAL; + cairo_font_slant_t fontSlant = CAIRO_FONT_SLANT_NORMAL; + cairo_select_font_face (cr, font.c_str(), fontSlant, fontWeight); + cairo_set_font_size (cr, size); + int x = size; + int y = imgHeight; + double rotate = 3*M_PI/2; + if (direction == (int)eDirection::topdown) { + rotate = M_PI/2; + x = size*0.3; + y = 0; + } + cairo_move_to (cr, x, y); + cairo_rotate(cr, rotate); + cairo_show_text (cr, text.c_str()); +} + +cImage *cCairoImage::GetImage(void) { + if (!cr || !surface) + return NULL; + + unsigned char *data = cairo_image_surface_get_data(surface); + cImage *image = new cImage(cSize(width, height), (tColor*)data); + return image; +} + +/********************************************************************************** +* Private Functions +**********************************************************************************/ + +int cCairoImage::GetTextWidth(string text, string font, int size) { + cairo_surface_t *tmpSurface; + cairo_t *tmpCr; + + double width = 300; + double height = (double)size *1.3; + + cairo_font_weight_t fontWeight = CAIRO_FONT_WEIGHT_NORMAL; + cairo_font_slant_t fontSlant = CAIRO_FONT_SLANT_NORMAL; + + tmpSurface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, width, height); + tmpCr = cairo_create (tmpSurface); + + cairo_select_font_face (tmpCr, font.c_str(), fontSlant, fontWeight); + cairo_set_font_size (tmpCr, size); + + cairo_text_extents_t te; + cairo_text_extents (tmpCr, text.c_str(), &te); + int textWidth = te.width; + + cairo_destroy (tmpCr); + cairo_surface_destroy (tmpSurface); + + return (double)textWidth * 1.1; +} + +void cCairoImage::SetColor(tColor color) { + if (!cr || !surface) + return; + tIndex tAlpha = (color & 0xFF000000) >> 24; + tIndex tRed = (color & 0x00FF0000) >> 16; + tIndex tGreen = (color & 0x0000FF00) >> 8; + tIndex tBlue = (color & 0x000000FF); + + double a = (int)tAlpha / (double)255; + double r = (int)tRed / (double)255; + double g = (int)tGreen / (double)255; + double b = (int)tBlue / (double)255; + + cairo_set_source_rgba(cr, r, g, b, a); +} diff --git a/extensions/cairoimage.h b/extensions/cairoimage.h new file mode 100644 index 0000000..4d4788d --- /dev/null +++ b/extensions/cairoimage.h @@ -0,0 +1,27 @@ +#ifndef __CAIROIMAGE_H +#define __CAIROIMAGE_H + +#include <cairo.h> +#include <vdr/osd.h> +#include <string> +#include <sstream> +#include "../coreengine/definitions.h" + +using namespace std; + +class cCairoImage { +private: + int width; + int height; + cairo_surface_t *surface; + cairo_t *cr; + void SetColor(tColor color); + int GetTextWidth(string text, string font, int size); +public: + cCairoImage(void); + virtual ~cCairoImage(); + void InitCairoImage(int width, int height); + void DrawTextVertical(string text, tColor color, string font, int size, int direction); + cImage *GetImage(void); +}; +#endif //__CAIROIMAGE_H
\ No newline at end of file diff --git a/extensions/curlfuncs.c b/extensions/curlfuncs.c new file mode 100644 index 0000000..c6dfee5 --- /dev/null +++ b/extensions/curlfuncs.c @@ -0,0 +1,237 @@ +/* +Copyright (c) 2002, Mayukh Bose +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + +* Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. + +* Neither the name of Mayukh Bose nor the names of other +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +/* + Change History: + 11/23/2004 - Removed the #include <unistd.h> line because I didn't + need it. Wonder why I had it there in the first place :). + 10/20/2004 - Publicly released this code. +*/ +#include <string> +#include <cstdio> +#include <curl/curl.h> +#include <curl/easy.h> +#include <vdr/tools.h> +#include "curlfuncs.h" + +#ifndef TRUE +#define TRUE 1 +#endif + +using namespace std; + +// Local function prototypes +int CurlDoPost(const char *url, string *sOutput, const string &sReferer, + struct curl_httppost *formpost, struct curl_slist *headerlist); + +namespace curlfuncs { + string sBuf; + bool bInitialized = false; + CURL *curl = NULL; +} + +size_t collect_data(void *ptr, size_t size, size_t nmemb, void *stream) +{ + string sTmp; + register size_t actualsize = size * nmemb; + if ((FILE *)stream == NULL) { + sTmp.assign((char *)ptr, actualsize); + curlfuncs::sBuf += sTmp; + } + else { + fwrite(ptr, size, nmemb, (FILE *)stream); + } + return actualsize; +} + +inline void InitCurlLibraryIfNeeded() +{ + if (!curlfuncs::bInitialized) { + curl_global_init(CURL_GLOBAL_ALL); + curlfuncs::curl = curl_easy_init(); + if (!curlfuncs::curl) + throw string("Could not create new curl instance"); + curl_easy_setopt(curlfuncs::curl, CURLOPT_NOPROGRESS, 1); // Do not show progress + curl_easy_setopt(curlfuncs::curl, CURLOPT_WRITEFUNCTION, collect_data); + curl_easy_setopt(curlfuncs::curl, CURLOPT_WRITEDATA, 0); // Set option to write to string + curl_easy_setopt(curlfuncs::curl, CURLOPT_FOLLOWLOCATION, TRUE); + curl_easy_setopt(curlfuncs::curl, CURLOPT_USERAGENT, "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0; Mayukh's libcurl wrapper http://www.mayukhbose.com/)"); + curl_easy_setopt(curlfuncs::curl, CURLOPT_TIMEOUT, 5L); + curlfuncs::bInitialized = true; + } +} + +int CurlGetUrl(const char *url, string *sOutput, const string &sReferer) +{ + InitCurlLibraryIfNeeded(); + + curl_easy_setopt(curlfuncs::curl, CURLOPT_URL, url); // Set the URL to get + if (sReferer != "") + curl_easy_setopt(curlfuncs::curl, CURLOPT_REFERER, sReferer.c_str()); + curl_easy_setopt(curlfuncs::curl, CURLOPT_HTTPGET, TRUE); + curl_easy_setopt(curlfuncs::curl, CURLOPT_WRITEDATA, 0); // Set option to write to string + curlfuncs::sBuf = ""; + if (curl_easy_perform(curlfuncs::curl) == 0) + *sOutput = curlfuncs::sBuf; + else { + // We have an error here mate! + *sOutput = ""; + return 0; + } + + return 1; +} + +int CurlGetUrlFile(const char *url, const char *filename, const string &sReferer) +{ + int nRet = 0; + InitCurlLibraryIfNeeded(); + + // Point the output to a file + FILE *fp; + if ((fp = fopen(filename, "w")) == NULL) + return 0; + + curl_easy_setopt(curlfuncs::curl, CURLOPT_WRITEDATA, fp); // Set option to write to file + curl_easy_setopt(curlfuncs::curl, CURLOPT_URL, url); // Set the URL to get + if (sReferer != "") + curl_easy_setopt(curlfuncs::curl, CURLOPT_REFERER, sReferer.c_str()); + curl_easy_setopt(curlfuncs::curl, CURLOPT_HTTPGET, TRUE); + if (curl_easy_perform(curlfuncs::curl) == 0) + nRet = 1; + else + nRet = 0; + + curl_easy_setopt(curlfuncs::curl, CURLOPT_WRITEDATA, NULL); // Set option back to default (string) + fclose(fp); + return nRet; +} + +int CurlPostUrl(const char *url, const string &sPost, string *sOutput, const string &sReferer) +{ + InitCurlLibraryIfNeeded(); + + int retval = 1; + string::size_type nStart = 0, nEnd, nPos; + string sTmp, sName, sValue; + struct curl_httppost *formpost=NULL; + struct curl_httppost *lastptr=NULL; + struct curl_slist *headerlist=NULL; + + // Add the POST variables here + while ((nEnd = sPost.find("##", nStart)) != string::npos) { + sTmp = sPost.substr(nStart, nEnd - nStart); + if ((nPos = sTmp.find("=")) == string::npos) + return 0; + sName = sTmp.substr(0, nPos); + sValue = sTmp.substr(nPos+1); + curl_formadd(&formpost, &lastptr, CURLFORM_COPYNAME, sName.c_str(), CURLFORM_COPYCONTENTS, sValue.c_str(), CURLFORM_END); + nStart = nEnd + 2; + } + sTmp = sPost.substr(nStart); + if ((nPos = sTmp.find("=")) == string::npos) + return 0; + sName = sTmp.substr(0, nPos); + sValue = sTmp.substr(nPos+1); + curl_formadd(&formpost, &lastptr, CURLFORM_COPYNAME, sName.c_str(), CURLFORM_COPYCONTENTS, sValue.c_str(), CURLFORM_END); + + retval = CurlDoPost(url, sOutput, sReferer, formpost, headerlist); + + curl_formfree(formpost); + curl_slist_free_all(headerlist); + return retval; +} + +int CurlPostRaw(const char *url, const string &sPost, string *sOutput, const string &sReferer) +{ + InitCurlLibraryIfNeeded(); + + int retval; + struct curl_httppost *formpost=NULL; + struct curl_slist *headerlist=NULL; + + curl_easy_setopt(curlfuncs::curl, CURLOPT_POSTFIELDS, sPost.c_str()); + curl_easy_setopt(curlfuncs::curl, CURLOPT_POSTFIELDSIZE, 0); //FIXME: Should this be the size instead, in case this is binary string? + + retval = CurlDoPost(url, sOutput, sReferer, formpost, headerlist); + + curl_formfree(formpost); + curl_slist_free_all(headerlist); + return retval; +} + +int CurlDoPost(const char *url, string *sOutput, const string &sReferer, + struct curl_httppost *formpost, struct curl_slist *headerlist) +{ + headerlist = curl_slist_append(headerlist, "Expect:"); + + // Now do the form post + curl_easy_setopt(curlfuncs::curl, CURLOPT_URL, url); + if (sReferer != "") + curl_easy_setopt(curlfuncs::curl, CURLOPT_REFERER, sReferer.c_str()); + curl_easy_setopt(curlfuncs::curl, CURLOPT_HTTPPOST, formpost); + + curl_easy_setopt(curlfuncs::curl, CURLOPT_WRITEDATA, 0); // Set option to write to string + curlfuncs::sBuf = ""; + if (curl_easy_perform(curlfuncs::curl) == 0) { + *sOutput = curlfuncs::sBuf; + return 1; + } + else { + // We have an error here mate! + *sOutput = ""; + return 0; + } +} + +void FreeCurlLibrary(void) +{ + if (curlfuncs::curl) + curl_easy_cleanup(curlfuncs::curl); + curl_global_cleanup(); + curlfuncs::bInitialized = false; +} + +int CurlSetCookieFile(char *filename) +{ + InitCurlLibraryIfNeeded(); + if (curl_easy_setopt(curlfuncs::curl, CURLOPT_COOKIEFILE, filename) != 0) + return 0; + if (curl_easy_setopt(curlfuncs::curl, CURLOPT_COOKIEJAR, filename) != 0) + return 0; + return 1; +} + +char *CurlEscape(const char *url) { + InitCurlLibraryIfNeeded(); + return curl_escape(url , strlen(url)); +} diff --git a/extensions/curlfuncs.h b/extensions/curlfuncs.h new file mode 100644 index 0000000..e0f76cf --- /dev/null +++ b/extensions/curlfuncs.h @@ -0,0 +1,45 @@ +/* +Copyright (c) 2002, Mayukh Bose +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + +* Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. + +* Neither the name of Mayukh Bose nor the names of other +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef __CURLFUNCS_H_20020513__ +#define __CURLFUNCS_H_20020513__ +#include <string> + using namespace std; + +int CurlGetUrl(const char *url, string *sOutput, const string &sReferer=""); +int CurlGetUrlFile(const char *url, const char *filename, const string &sReferer=""); +void FreeCurlLibrary(void); +int CurlSetCookieFile(char *filename); +int CurlPostUrl(const char *url, const string &sPost, string *sOutput, const string &sReferer = ""); +int CurlPostRaw(const char *url, const string &sPost, string *sOutput, const string &sReferer = ""); +char *CurlEscape(const char *url); +#endif diff --git a/extensions/extrecinfo.c b/extensions/extrecinfo.c new file mode 100644 index 0000000..435d43b --- /dev/null +++ b/extensions/extrecinfo.c @@ -0,0 +1,96 @@ +#include "../config.h" +#include "helpers.h" +#include "extrecinfo.h" + +cExtRecInfo::cExtRecInfo(const char *xml) { + this->xml = xml; +} + +cExtRecInfo::~cExtRecInfo(void) { + +} + +bool cExtRecInfo::Parse(void) { + //read media info + string mediaInfoXml; + StripXmlTag(xml, mediaInfoXml, "mediainfo"); + if (mediaInfoXml.size() == 0) { + return false; + } + StripXmlTag(mediaInfoXml, resWidth, "res_width"); + StripXmlTag(mediaInfoXml, resHeight, "res_height"); + resString = GetScreenResolutionString(resWidth, resHeight, &isHD); + StripXmlTag(mediaInfoXml, aspectratio, "aspectratio"); + isWideScreen = !aspectratio.compare("16:9"); + StripXmlTag(mediaInfoXml, codec, "codec"); + StripXmlTag(mediaInfoXml, format, "format"); + StripXmlTag(mediaInfoXml, framerate, "framerate"); + StripXmlTag(mediaInfoXml, interlace, "interlace"); + + size_t found = 0; + isDolby = false; + do { + string track; + found = StripXmlTag(mediaInfoXml, track, "track", found); + if (found == string::npos) + break; + tAudioTrack sTrack; + StripXmlTag(track, sTrack.codec, "codec"); + StripXmlTag(track, sTrack.bitrate, "bitrate"); + StripXmlTag(track, sTrack.language, "language"); + if (!sTrack.codec.compare("AC-3")) + isDolby = true; + tracks.push_back(sTrack); + } while (found != string::npos); + + return true; +} + +//get content of <tag> ... </tag> inside xml +size_t cExtRecInfo::StripXmlTag(string &xmlstring, string &content, const char *tag, int start) { + // set the search strings + stringstream strStart, strStop; + strStart << "<" << tag << ">"; + strStop << "</" << tag << ">"; + // find the strings + size_t locStart = xmlstring.find(strStart.str(), start); + size_t locStop = xmlstring.find(strStop.str(), start); + if (locStart == string::npos || locStop == string::npos) + return string::npos; + // extract relevant text + int pos = locStart + strStart.str().size(); + int len = locStop - pos; + + content = (len < 0) ? "" : xmlstring.substr(pos, len); + return locStop + strStop.str().size(); +} + +size_t cExtRecInfo::StripXmlTag(string &xmlstring, int &content, const char *tag, int start) { + // set the search strings + stringstream strStart, strStop; + strStart << "<" << tag << ">"; + strStop << "</" << tag << ">"; + // find the strings + size_t locStart = xmlstring.find(strStart.str(), start); + size_t locStop = xmlstring.find(strStop.str(), start); + if (locStart == string::npos || locStop == string::npos) + return string::npos; + // extract relevant text + int pos = locStart + strStart.str().size(); + int len = locStop - pos; + + string value = (len < 0) ? "" : xmlstring.substr(pos, len); + content = atoi(value.c_str()); + return locStop + strStop.str().size(); +} + +void cExtRecInfo::Debug(void) { + dsyslog("skindesigner: extRecInfo xml: %s", xml.c_str()); + dsyslog("skindesigner: : res_width %d, res_height %d, res %s, aspectratio %s, codec %s, format %s, framerate %s, interlace %s, hd %s, widescreen %s", + resWidth, resHeight, resString.c_str(), aspectratio.c_str(), codec.c_str(), format.c_str(), framerate.c_str(), interlace.c_str(), + isHD ? "true": "false", isWideScreen ? "true" : "false"); + int numTrack = 1; + for (vector<tAudioTrack>::iterator it = tracks.begin(); it != tracks.end(); it++) { + dsyslog("skindesigner: audio track %d, codec %s, bitrate %s, language: %s", numTrack++, (*it).codec.c_str(), (*it).bitrate.c_str(), (*it).language.c_str()); + } +} diff --git a/extensions/extrecinfo.h b/extensions/extrecinfo.h new file mode 100644 index 0000000..9fb5bab --- /dev/null +++ b/extensions/extrecinfo.h @@ -0,0 +1,36 @@ +#ifndef __EXTRECINFO_H +#define __EXTRECINFO_H + +#include <vdr/recording.h> + +struct tAudioTrack { + string codec; + string bitrate; + string language; +}; + +class cExtRecInfo { +private: + string xml; + size_t StripXmlTag(string &xmlstring, string &content, const char *tag, int start = 0); + size_t StripXmlTag(string &xmlstring, int &content, const char *tag, int start = 0); +public: + cExtRecInfo(const char *xml); + ~cExtRecInfo(void); + bool Parse(void); + void Debug(void); + int resWidth; + int resHeight; + string resString; + bool isHD; + string aspectratio; + bool isWideScreen; + string codec; + string format; + string framerate; + string interlace; + bool isDolby; + vector< tAudioTrack > tracks; +}; + +#endif // __EXTRECINFO_H
\ No newline at end of file diff --git a/extensions/fontmanager.c b/extensions/fontmanager.c new file mode 100644 index 0000000..47e2885 --- /dev/null +++ b/extensions/fontmanager.c @@ -0,0 +1,181 @@ +#include "fontmanager.h" +#include "../config.h" +#include <ft2build.h> +#include FT_FREETYPE_H + +using namespace std; + +cMutex cFontManager::mutex; + +cFontManager::cFontManager(void) { +} + +cFontManager::~cFontManager(void) { + DeleteFonts(); +} + +void cFontManager::Debug(void) { + dsyslog("skindesigner: fontmanager fonts available:"); + for (map < string, map< int, cFont* > >::iterator fts = fonts.begin(); fts != fonts.end(); fts++) { + dsyslog("skindesigner: FontName %s", fts->first.c_str()); + for (map<int, cFont*>::iterator ftSizes = (fts->second).begin(); ftSizes != (fts->second).end(); ftSizes++) { + int confHeight = ftSizes->first; + int realHeight = (ftSizes->second)->Height(); + dsyslog("skindesigner: fontSize %d, fontHeight %d, ratio %f", confHeight, realHeight, (double)confHeight / (double)realHeight); + } + } +} + +void cFontManager::ListAvailableFonts(void) { + cStringList availableFonts; + cFont::GetAvailableFontNames(&availableFonts); + int numFonts = availableFonts.Size(); + esyslog("skindesigner: %d Fonts available:", numFonts); + for (int i=0; i<numFonts; i++) { + esyslog("skindesigner: font %d: %s", i, availableFonts[i]); + } +} + +void cFontManager::DeleteFonts() { + cMutexLock MutexLock(&mutex); + for(map<string, map<int,cFont*> >::iterator it = fonts.begin(); it != fonts.end(); it++) { + for(map<int,cFont*>::iterator it2 = (it->second).begin(); it2 != (it->second).end(); it2++) { + delete it2->second; + } + } + fonts.clear(); +} + +int cFontManager::Width(string fontName, int fontSize, const char *text) { + cMutexLock MutexLock(&mutex); + if (!text) + return 0; + cFont *font = GetFont(fontName, fontSize); + //if not already cached, load it new + if (!font) + InsertFont(fontName, fontSize); + font = GetFont(fontName, fontSize); + if (!font) + return 0; + int width = font->Width(text); + return width; +} + +int cFontManager::Height(string fontName, int fontSize) { + cMutexLock MutexLock(&mutex); + cFont *font = GetFont(fontName, fontSize); + //if not already cached, load it new + if (!font) + InsertFont(fontName, fontSize); + font = GetFont(fontName, fontSize); + if (!font) + return 0; + return font->Height(); +} + +cFont *cFontManager::Font(string fontName, int fontSize) { + cMutexLock MutexLock(&mutex); + cFont *font = GetFont(fontName, fontSize); + //if not already cached, load it new + if (!font) + InsertFont(fontName, fontSize); + font = GetFont(fontName, fontSize); + return font; +} + +cFont *cFontManager::FontUncached(string fontName, int fontSize) { + cMutexLock MutexLock(&mutex); + cFont *font = CreateFont(fontName, fontSize); + return font; +} + +/******************************************************************************** +* Private Functions +********************************************************************************/ + +cFont *cFontManager::CreateFont(string name, int size) { + cMutexLock MutexLock(&mutex); + cFont *fontTmp = cFont::CreateFont(name.c_str(), size); + if (!fontTmp) + fontTmp = cFont::CreateFont(Setup.FontOsd, size); + int realHeight = fontTmp->Height(); + delete fontTmp; + cFont *font = cFont::CreateFont(name.c_str(), (double)size / (double)realHeight * (double)size); + if (!font) + font = cFont::CreateFont(Setup.FontOsd, (double)size / (double)realHeight * (double)size); + return font; +} + +void cFontManager::InsertFont(string name, int size) { + if (FontExists(name, size)) + return; + cFont *newFont = CreateFont(name, size); + if (!newFont) + return; + map < string, map< int, cFont* > >::iterator hit = fonts.find(name); + if (hit != fonts.end()) { + (hit->second).insert(pair<int, cFont*>(size, newFont)); + } else { + map<int, cFont*> fontsizes; + fontsizes.insert(pair<int, cFont*>(size, newFont)); + fonts.insert(pair<string, map<int, cFont*> >(name, fontsizes)); + } +} + +bool cFontManager::FontExists(string name, int size) { + map < string, map< int, cFont* > >::iterator hit = fonts.find(name); + if (hit == fonts.end()) + return false; + map< int, cFont* >::iterator hit2 = (hit->second).find(size); + if (hit2 == (hit->second).end()) + return false; + return true; +} + +cFont *cFontManager::GetFont(string name, int size) { + map< string, map<int,cFont*> >::iterator hitName = fonts.find(name); + if (hitName == fonts.end()) + return NULL; + map<int,cFont*>::iterator hitSize = (hitName->second).find(size); + if (hitSize == (hitName->second).end()) + return NULL; + return hitSize->second; +} + +int cFontManager::GetFontHeight(const char *name, int height, int charWidth) { + FT_Library library; + FT_Face face; + cString fontFileName = cFont::GetFontFileName(name); + + int descender = 0; + int y_ppem = 0; + int error = FT_Init_FreeType(&library); + if (error) return 0; + error = FT_New_Face(library, fontFileName, 0, &face); + if (error) return 0; + error = FT_Set_Char_Size(face, charWidth * 64, height * 64, 0, 0); + if (error) return 0; + + descender = face->size->metrics.descender/64; + y_ppem = face->size->metrics.y_ppem; + int realHeight = y_ppem + descender; + + FT_Done_Face(face); + FT_Done_FreeType(library); + + return realHeight; +} + +bool cFontManager::FontInstalled(string fontName) { + cStringList availableFonts; + cFont::GetAvailableFontNames(&availableFonts); + int numFonts = availableFonts.Size(); + string compare = fontName + ":"; + for (int i=0; i<numFonts; i++) { + string currentFont = availableFonts[i]; + if (currentFont.find(compare) == 0) { + return true; + } + } + return false; +} diff --git a/extensions/fontmanager.h b/extensions/fontmanager.h new file mode 100644 index 0000000..c71d465 --- /dev/null +++ b/extensions/fontmanager.h @@ -0,0 +1,35 @@ +#ifndef __FONTMANAGER_H +#define __FONTMANAGER_H + +#include <string> +#include <map> +#include <vector> +#include <vdr/skins.h> + +using namespace std; + +class cFontManager { +private: + static cMutex mutex; + map < string, map< int, cFont* > > fonts; + cFont *CreateFont(string name, int size); + void InsertFont(string name, int size); + bool FontExists(string name, int size); + cFont *GetFont(string name, int size); + int GetFontHeight(const char *name, int height, int charWidth = 0); +public: + cFontManager(void); + ~cFontManager(void); + void Lock(void) { mutex.Lock(); }; + void Unlock(void) { mutex.Unlock(); }; + void DeleteFonts(void); + int Width(string fontName, int fontSize, const char *text); + int Height(string fontName, int fontSize); + cFont *Font(string fontName, int fontSize); + cFont *FontUncached(string fontName, int fontSize); + void Debug(void); + void ListAvailableFonts(void); + bool FontInstalled(string fontName); +}; + +#endif //__FONTMANAGER_H
\ No newline at end of file diff --git a/extensions/helpers.c b/extensions/helpers.c new file mode 100644 index 0000000..00c13d2 --- /dev/null +++ b/extensions/helpers.c @@ -0,0 +1,240 @@ +#include <string> +#include <sstream> +#include <vector> +#include <stdlib.h> +#include <fstream> +#include <iostream> +#include "helpers.h" +#include <vdr/skins.h> + +cPlugin *GetScraperPlugin(void) { + static cPlugin *pScraper = cPluginManager::GetPlugin("scraper2vdr"); + if( !pScraper ) // if it doesn't exit, try tvscraper + pScraper = cPluginManager::GetPlugin("tvscraper"); + return pScraper; +} + +cSize ScaleToFit(int widthMax, int heightMax, int widthOriginal, int heightOriginal) { + int width = 1; + int height = 1; + + if ((widthMax == 0)||(heightMax==0)||(widthOriginal==0)||(heightOriginal==0)) + return cSize(width, height); + + if ((widthOriginal <= widthMax) && (heightOriginal <= heightMax)) { + width = widthOriginal; + height = heightOriginal; + } else if ((widthOriginal > widthMax) && (heightOriginal <= heightMax)) { + width = widthMax; + height = (double)width/(double)widthOriginal * heightOriginal; + } else if ((widthOriginal <= widthMax) && (heightOriginal > heightMax)) { + height = heightMax; + width = (double)height/(double)heightOriginal * widthOriginal; + } else { + width = widthMax; + height = (double)width/(double)widthOriginal * heightOriginal; + if (height > heightMax) { + height = heightMax; + width = (double)height/(double)heightOriginal * widthOriginal; + } + } + return cSize(width, height); +} + +int Minimum(int a, int b, int c, int d, int e, int f) { + int min = a; + if (b < min) min = b; + if (c < min) min = c; + if (d < min) min = d; + if (e < min) min = e; + if (f < min) min = f; + return min; +} + +string StrToLowerCase(string str) { + string lowerCase = str; + const int length = lowerCase.length(); + for(int i=0; i < length; ++i) { + lowerCase[i] = std::tolower(lowerCase[i]); + } + return lowerCase; +} + +bool isNumber(const string& s) { + string::const_iterator it = s.begin(); + while (it != s.end() && std::isdigit(*it)) ++it; + return !s.empty() && it == s.end(); +} + +bool IsToken(const string& token) { + if ((token.find("{") == 0) && (token.find("}") == (token.size()-1))) + return true; + return false; +} + +bool FileExists(const string &fullpath) { + struct stat buffer; + if (stat (fullpath.c_str(), &buffer) == 0) { + return true; + } + if (config.debugImageLoading) { + dsyslog("skindesigner: did not find %s", fullpath.c_str()); + } + return false; +} + +bool FileExists(const string &path, const string &name, const string &ext) { + stringstream fileName; + fileName << path << name << "." << ext; + struct stat buffer; + if (stat (fileName.str().c_str(), &buffer) == 0) { + return true; + } + if (config.debugImageLoading) { + dsyslog("skindesigner: did not find %s", fileName.str().c_str()); + } + return false; +} + +bool FolderExists(const string &path) { + struct stat buffer; + return stat(path.c_str(), &buffer) == 0 && S_ISDIR(buffer.st_mode); +} + +bool FirstFileInFolder(string &path, string &extension, string &fileName) { + DIR *folder = NULL; + struct dirent *file; + folder = opendir(path.c_str()); + if (!folder) + return false; + while (file = readdir(folder)) { + if (endswith(file->d_name, extension.c_str())) { + string currentFileName = file->d_name; + int strlength = currentFileName.size(); + if (strlength < 8) + continue; + fileName = currentFileName; + return true; + } + } + return false; +} + +void CreateFolder(string &path) { + cString command = cString::sprintf("mkdir -p %s", path.c_str()); + int ok = system(*command); + if (!ok) {} +} + +// trim from start +string <rim(string &s) { + s.erase(s.begin(), find_if(s.begin(), s.end(), not1(ptr_fun<int, int>(isspace)))); + return s; +} + +// trim from end +string &rtrim(string &s) { + s.erase(find_if(s.rbegin(), s.rend(), not1(ptr_fun<int, int>(isspace))).base(), s.end()); + return s; +} + +// trim from both ends +string &trim(string &s) { + return ltrim(rtrim(s)); +} + +// split: receives a char delimiter; returns a vector of strings +// By default ignores repeated delimiters, unless argument rep == 1. +vector<string>& splitstring::split(char delim, int rep) { + if (!flds.empty()) flds.clear(); // empty vector if necessary + string work = data(); + string buf = ""; + int i = 0; + while (i < (int)work.length()) { + if (work[i] != delim) + buf += work[i]; + else if (rep == 1) { + flds.push_back(buf); + buf = ""; + } else if (buf.length() > 0) { + flds.push_back(buf); + buf = ""; + } + i++; + } + if (!buf.empty()) + flds.push_back(buf); + return flds; +} + +cStopWatch::cStopWatch(const char* message) { + start = cTimeMs::Now(); + last = start; + if (message) { + dsyslog("skindesigner: Starting StopWatch %s", message); + } +} + +void cStopWatch::Report(const char* message) { + dsyslog("skindesigner: %s - needed %d ms", message, (int)(cTimeMs::Now() - last)); + last = cTimeMs::Now(); +} + +void cStopWatch::Stop(const char* message) { + dsyslog("skindesigner: %s - needed %d ms", message, (int)(cTimeMs::Now() - start)); +} + +string GetTimeString(int seconds) { + time_t sec(seconds); + tm *p = gmtime(&sec); + int hours = p->tm_hour; + int mins = p->tm_min; + int secs = p->tm_sec; + if (hours > 0) { + return *cString::sprintf("%d:%02d:%02d", hours, mins, secs); + } + return *cString::sprintf("%02d:%02d", mins, secs);; +} + + +//View Helpers +string GetScreenResolutionString(int width, int height, bool *isHD) { + string name = ""; + switch (width) { + case 1920: + case 1440: + name = "hd1080i"; + *isHD = true; + break; + case 1280: + if (height == 720) + name = "hd720p"; + else + name = "hd1080i"; + *isHD = true; + break; + case 720: + name = "sd576i"; + break; + default: + name = "sd576i"; + break; + } + return name; +} + +string GetScreenAspectString(double aspect, bool *isWideScreen) { + string name = ""; + *isWideScreen = false; + if (aspect == 4.0/3.0) { + name = "4:3"; + *isWideScreen = false; + } else if (aspect == 16.0/9.0) { + name = "16:9"; + *isWideScreen = true; + } else if (aspect == 2.21) { + name = "21:9"; + *isWideScreen = true; + } + return name; +} diff --git a/extensions/helpers.h b/extensions/helpers.h new file mode 100644 index 0000000..b059a16 --- /dev/null +++ b/extensions/helpers.h @@ -0,0 +1,50 @@ +#ifndef __HELPERS_H +#define __HELPERS_H + +#include <string> +#include <algorithm> +#include <vdr/osd.h> +#include <vdr/plugin.h> +#include "../config.h" + +cPlugin *GetScraperPlugin(void); + +cSize ScaleToFit(int widthMax, int heightMax, int widthOriginal, int heightOriginal); +int Minimum(int a, int b, int c, int d, int e, int f); +std::string StrToLowerCase(string str); +bool isNumber(const string& s); +bool IsToken(const string& token); +bool FileExists(const string &fullpath); +bool FileExists(const string &path, const string &name, const string &ext); +bool FolderExists(const string &path); +bool FirstFileInFolder(string &path, string &extension, string &fileName); +void CreateFolder(string &path); + +string <rim(string &s); +string &rtrim(string &s); +string &trim(string &s); + +class splitstring : public std::string { + std::vector<std::string> flds; +public: + splitstring(const char *s) : std::string(s) { }; + std::vector<std::string>& split(char delim, int rep=0); +}; + +class cStopWatch { +private: + uint64_t start; + uint64_t last; +public: + cStopWatch(const char* message = NULL); + ~cStopWatch(void) {}; + void Report(const char* message); + void Stop(const char* message); +}; + +string GetTimeString(int seconds); + +string GetScreenResolutionString(int width, int height, bool *isHD); +string GetScreenAspectString(double aspect, bool *isWideScreen); + +#endif // __HELPERS_H diff --git a/extensions/imagecache.c b/extensions/imagecache.c new file mode 100644 index 0000000..d8c89c8 --- /dev/null +++ b/extensions/imagecache.c @@ -0,0 +1,579 @@ +#include <string> +#include <sstream> +#include <map> +#include <fstream> +#include <iostream> +#include <sys/stat.h> +#include "imagecache.h" +#include "cairoimage.h" +#include "../config.h" +#include "helpers.h" + + +cMutex cImageCache::mutex; + +string cImageCache::items[16] = { "Schedule", "Channels", "Timers", "Recordings", "Setup", "Commands", + "OSD", "EPG", "DVB", "LNB", "CAM", "Recording", "Replay", "Miscellaneous", "Plugins", "Restart"}; + +cImageCache::cImageCache() { + tempStaticLogo = NULL; +} + +cImageCache::~cImageCache() { + Clear(); + if (tempStaticLogo) { + delete tempStaticLogo; + tempStaticLogo = NULL; + } +} + +void cImageCache::SetPathes(void) { + cString skinPath = config.GetSkinPath(Setup.OSDSkin); + + string logoPathSkin = *cString::sprintf("%s%s/themes/%s/logos/", *skinPath, Setup.OSDSkin, Setup.OSDTheme); + if (FolderExists(logoPathSkin)) { + logoPath = logoPathSkin; + } else { + logoPath = *config.logoPath; + } + + iconPathSkin = *cString::sprintf("%s%s/", *skinPath, Setup.OSDSkin); + skinPartsPathSkin = *cString::sprintf("%s%s/skinparts/", *skinPath, Setup.OSDSkin); + + iconPathTheme = *cString::sprintf("%s%s/themes/%s/", *skinPath, Setup.OSDSkin, Setup.OSDTheme); + skinPartsPathTheme = *cString::sprintf("%s%s/themes/%s/skinparts/", *skinPath, Setup.OSDSkin, Setup.OSDTheme); + + svgTemplatePath = *cString::sprintf("%s%s/svgtemplates/", *skinPath, Setup.OSDSkin); + + dsyslog("skindesigner: using channel logo path %s", logoPath.c_str()); + dsyslog("skindesigner: using icon path %s", iconPathTheme.c_str()); + dsyslog("skindesigner: using skinparts path %s", skinPartsPathTheme.c_str()); + dsyslog("skindesigner: using svgtemplate path %s", svgTemplatePath.c_str()); +} + +void cImageCache::CacheLogo(int width, int height) { + if (config.numLogosPerSizeInitial == 0) + return; + if (width == 0 || height == 0) + return; + + int logosCached = 0; + + if (config.numLogosMax && config.numLogosMax < (int)channelLogoCache.size()) + return; + + for (const cChannel *channel = Channels.First(); channel; channel = Channels.Next(channel)) { + if (logosCached >= config.numLogosPerSizeInitial) + break; + if (channel->GroupSep()) { + continue; + } + stringstream logoName; + logoName << *channel->GetChannelID().ToString() << "_" << width << "x" << height; + map<string, cImage*>::iterator hit = channelLogoCache.find(logoName.str()); + if (hit != channelLogoCache.end()) { + continue; + } + bool success = LoadLogo(channel); + if (success) { + logosCached++; + cImage *image = CreateImage(width, height); + channelLogoCache.insert(pair<string, cImage*>(logoName.str(), image)); + } + if (config.numLogosMax && config.numLogosMax < (int)channelLogoCache.size()) + return; + } +} + +cImage *cImageCache::GetLogo(string channelID, int width, int height) { + cMutexLock MutexLock(&mutex); + + stringstream logoName; + logoName << channelID << "_" << width << "x" << height; + + std::map<std::string, cImage*>::iterator hit = channelLogoCache.find(logoName.str()); + + if (hit != channelLogoCache.end()) { + return (cImage*)hit->second; + } else { + tChannelID chanID = tChannelID::FromString(channelID.c_str()); + const cChannel *channel = Channels.GetByChannelID(chanID); + if (!channel) + return NULL; + bool success = LoadLogo(channel); + if (success) { + if (config.limitLogoCache && ((int)channelLogoCache.size() >= config.numLogosMax)) { + //logo cache is full, don't cache anymore + if (tempStaticLogo) { + delete tempStaticLogo; + tempStaticLogo = NULL; + } + tempStaticLogo = CreateImage(width, height); + return tempStaticLogo; + } else { + //add requested logo to cache + cImage *image = CreateImage(width, height); + channelLogoCache.insert(pair<string, cImage*>(logoName.str(), image)); + hit = channelLogoCache.find(logoName.str()); + if (hit != channelLogoCache.end()) { + return (cImage*)hit->second; + } + } + } + } + return NULL; +} + +cImage *cImageCache::GetSeparatorLogo(string name, int width, int height) { + cMutexLock MutexLock(&mutex); + + stringstream logoName; + logoName << name << "_" << width << "x" << height; + + std::map<std::string, cImage*>::iterator hit = channelLogoCache.find(logoName.str()); + + if (hit != channelLogoCache.end()) { + return (cImage*)hit->second; + } else { + bool success = LoadSeparatorLogo(name); + if (success) { + //add requested logo to cache + cImage *image = CreateImage(width, height); + channelLogoCache.insert(pair<string, cImage*>(logoName.str(), image)); + hit = channelLogoCache.find(logoName.str()); + if (hit != channelLogoCache.end()) { + return (cImage*)hit->second; + } + } + } + return NULL; +} + +bool cImageCache::LogoExists(string channelID) { + tChannelID chanID = tChannelID::FromString(channelID.c_str()); + const cChannel *channel = Channels.GetByChannelID(chanID); + if (!channel) + return false; + string logoLower = StrToLowerCase(channel->Name()); + string channelIDLower = StrToLowerCase(channelID.c_str()); + + return (FileExists(logoPath.c_str(), logoLower, "svg") || + FileExists(logoPath.c_str(), logoLower, "png") || + FileExists(logoPath.c_str(), channelIDLower, "svg") || + FileExists(logoPath.c_str(), channelIDLower, "png")); +} + +bool cImageCache::SeparatorLogoExists(string name) { + string separatorPath = *cString::sprintf("%sseparatorlogos/", logoPath.c_str()); + string nameLower = StrToLowerCase(name.c_str()); + + return (FileExists(separatorPath, nameLower, "svg") || + FileExists(separatorPath, nameLower, "png")); +} + +void cImageCache::CacheIcon(eImageType type, string name, int width, int height) { + if (width < 1 || width > 1920 || height < 1 || height > 1080) + return; + GetIcon(type, name, width, height); +} + +cCachedImage *cImageCache::GetIcon(eImageType type, string name, int width, int height) { + if (width < 1 || width > 1920 || height < 1 || height > 1080) + return NULL; + cMutexLock MutexLock(&mutex); + stringstream iconName; + iconName << name << "_" << width << "x" << height; + map<string, cCachedImage*>::iterator hit = iconCache.find(iconName.str()); + if (hit != iconCache.end()) { + return (cCachedImage*)hit->second; + } else { + bool success = LoadIcon(type, name); + if (!success) + return NULL; + cImage *image = CreateImage(width, height, true); + cCachedImage *cachedImg = new cCachedImage(); + cachedImg->size = image->Width() * image->Height() * sizeof(tColor); + int handle = cOsdProvider::StoreImage(*image); + if (handle) { + cachedImg->handle = handle; + delete image; + } else { + cachedImg->image = image; + } + + iconCache.insert(pair<string, cCachedImage*>(iconName.str(), cachedImg)); + hit = iconCache.find(iconName.str()); + if (hit != iconCache.end()) { + return (cCachedImage*)hit->second; + } + } + return NULL; +} + +string cImageCache::GetIconName(string label, eMenuCategory cat, string plugName) { + //if cat is set, use standard menu entries + switch (cat) { + case mcSchedule: + case mcScheduleNow: + case mcScheduleNext: + case mcEvent: + return "standardicons/Schedule"; + case mcChannel: + case mcChannelEdit: + return "standardicons/Channels"; + case mcTimer: + case mcTimerEdit: + return "standardicons/Timers"; + case mcRecording: + case mcRecordingInfo: + case mcSetupRecord: + case mcSetupReplay: + return "standardicons/Recordings"; + case mcPlugin: { + //check for Plugins + for (int i = 0; ; i++) { + cPlugin *p = cPluginManager::GetPlugin(i); + if (p) { + const char *mainMenuEntry = p->MainMenuEntry(); + if (mainMenuEntry) { + string plugMainEntry = mainMenuEntry; + try { + if (label.substr(0, plugMainEntry.size()) == plugMainEntry) { + return *cString::sprintf("pluginicons/%s", p->Name()); + } + } catch (...) {} + } + } else + break; + } + return "standardicons/Plugins"; + } + case mcPluginSetup: + case mcSetupPlugins: + return "standardicons/Plugins"; + case mcSetup: + return "standardicons/Setup"; + case mcSetupOsd: + return "standardicons/OSD"; + case mcSetupEpg: + return "standardicons/EPG"; + case mcSetupDvb: + return "standardicons/DVB"; + case mcSetupLnb: + return "standardicons/LNB"; + case mcSetupCam: + return "standardicons/CAM"; + case mcSetupMisc: + return "standardicons/Miscellaneous"; + case mcCommand: + return "standardicons/Commands"; + default: + break; + } + //check for standard menu entries + for (int i=0; i<16; i++) { + string s = trVDR(items[i].c_str()); + if (s == label) { + return *cString::sprintf("standardicons/%s", items[i].c_str()); + } + } + //check for special main menu entries "stop recording", "stop replay" + string stopRecording = skipspace(trVDR(" Stop recording ")); + string stopReplay = skipspace(trVDR(" Stop replaying")); + try { + if (label.substr(0, stopRecording.size()) == stopRecording) { + return "standardicons/StopRecording"; + } + if (label.substr(0, stopReplay.size()) == stopReplay) { + return "standardicons/StopReplay"; + } + } catch (...) {} + //check for Plugins + if (plugName.size() > 0) { + return *cString::sprintf("pluginicons/%s", plugName.c_str()); + } + for (int i = 0; ; i++) { + cPlugin *p = cPluginManager::GetPlugin(i); + if (p) { + const char *mainMenuEntry = p->MainMenuEntry(); + if (mainMenuEntry) { + string plugMainEntry = mainMenuEntry; + try { + if (label.substr(0, plugMainEntry.size()) == plugMainEntry) { + return *cString::sprintf("pluginicons/%s", p->Name()); + } + } catch (...) {} + } + } else + break; + } + return *cString::sprintf("customicons/%s", label.c_str()); +} + +bool cImageCache::MenuIconExists(string name) { + //first check in theme specific icon folder + cString iconThemePath = cString::sprintf("%smenuicons/", iconPathTheme.c_str()); + if (FileExists(*iconThemePath, name, "svg")) { + return true; + } + if (FileExists(*iconThemePath, name, "png")) { + return true; + } + //then check skin icon folder + cString iconSkinPath = cString::sprintf("%smenuicons/", iconPathSkin.c_str()); + if (FileExists(*iconSkinPath, name, "svg")) { + return true; + } + if (FileExists(*iconSkinPath, name, "png")) { + return true; + } + return false; +} + +void cImageCache::CacheSkinpart(string name, int width, int height) { + if (width < 1 || width > 1920 || height < 1 || height > 1080) + return; + GetSkinpart(name, width, height); +} + +cCachedImage *cImageCache::GetSkinpart(string name, int width, int height) { + if (width < 1 || width > 1920 || height < 1 || height > 1080) + return NULL; + cMutexLock MutexLock(&mutex); + stringstream iconName; + iconName << name << "_" << width << "x" << height; + map<string, cCachedImage*>::iterator hit = skinPartsCache.find(iconName.str()); + if (hit != skinPartsCache.end()) { + return (cCachedImage*)hit->second; + } else { + bool success = LoadSkinpart(name); + if (!success) + return NULL; + cImage *image = CreateImage(width, height, false); + cCachedImage *cachedImg = new cCachedImage(); + cachedImg->size = image->Width() * image->Height() * sizeof(tColor); + int handle = cOsdProvider::StoreImage(*image); + if (handle) { + cachedImg->handle = handle; + delete image; + } else { + cachedImg->image = image; + } + skinPartsCache.insert(pair<string, cCachedImage*>(iconName.str(), cachedImg)); + hit = skinPartsCache.find(iconName.str()); + if (hit != skinPartsCache.end()) { + return (cCachedImage*)hit->second; + } + } + return NULL; +} + +cImage *cImageCache::GetVerticalText(string text, tColor color, string font, int size, int direction) { + cMutexLock MutexLock(&mutex); + stringstream buf; + buf << text << "_" << size << "_" << direction; + string imgName = buf.str(); + map<string, cImage*>::iterator hit = cairoImageCache.find(imgName); + if (hit != cairoImageCache.end()) { + return (cImage*)hit->second; + } else { + cCairoImage c; + c.DrawTextVertical(text, color, font, size, direction); + cImage *image = c.GetImage(); + cairoImageCache.insert(pair<string, cImage*>(imgName, image)); + hit = cairoImageCache.find(imgName); + if (hit != cairoImageCache.end()) { + return (cImage*)hit->second; + } + } + return NULL; +} + + +bool cImageCache::LoadIcon(eImageType type, string name) { + cString subdir(""); + if (type == eImageType::menuicon) + subdir = "menuicons"; + else if (type == eImageType::icon) + subdir = "icons"; + + //first check in theme specific icon path + cString subIconThemePath = cString::sprintf("%s%s/", iconPathTheme.c_str(), *subdir); + + if (FileExists(*subIconThemePath, name, "svg")) + return LoadImage(*subIconThemePath, name, "svg"); + else if (FileExists(*subIconThemePath, name, "png")) + return LoadImage(*subIconThemePath, name, "png"); + + //then check in skin icon path + cString subIconSkinPath = cString::sprintf("%s%s/", iconPathSkin.c_str(), *subdir); + + if (FileExists(*subIconSkinPath, name, "svg")) + return LoadImage(*subIconSkinPath, name, "svg"); + else if (FileExists(*subIconSkinPath, name, "png")) + return LoadImage(*subIconSkinPath, name, "png"); + + //and finally check if a svg template exists + cSVGTemplate svgTemplate(name, svgTemplatePath); + if (!svgTemplate.Exists()) + return false; + svgTemplate.ReadTemplate(); + if (!svgTemplate.ParseTemplate()) + return false; + string tmpImageName = svgTemplate.WriteImage(); + return LoadImage(tmpImageName.c_str()); +} + +bool cImageCache::LoadLogo(const cChannel *channel) { + if (!channel) + return false; + string channelID = StrToLowerCase(*(channel->GetChannelID().ToString())); + string logoLower = StrToLowerCase(channel->Name()); + + if (FileExists(logoPath.c_str(), channelID.c_str(), "svg")) + return LoadImage(logoPath.c_str(), channelID.c_str(), "svg"); + if (FileExists(logoPath.c_str(), channelID.c_str(), "png")) + return LoadImage(logoPath.c_str(), channelID.c_str(), "png"); + if (FileExists(logoPath.c_str(), logoLower.c_str(), "svg")) + return LoadImage(logoPath.c_str(), logoLower.c_str(), "svg"); + if (FileExists(logoPath.c_str(), logoLower.c_str(), "png")) + return LoadImage(logoPath.c_str(), logoLower.c_str(), "png"); + + return false; +} + +bool cImageCache::LoadSeparatorLogo(string name) { + string separatorPath = *cString::sprintf("%sseparatorlogos/", logoPath.c_str()); + string nameLower = StrToLowerCase(name.c_str()); + if (FileExists(separatorPath, nameLower.c_str(), "svg")) + return LoadImage(separatorPath, nameLower.c_str(), "svg"); + else + return LoadImage(separatorPath, nameLower.c_str(), "png"); +} + +bool cImageCache::LoadSkinpart(string name) { + if (FileExists(skinPartsPathTheme.c_str(), name, "svg")) + return LoadImage(skinPartsPathTheme.c_str(), name, "svg"); + + else if (FileExists(skinPartsPathTheme.c_str(), name, "png")) + return LoadImage(skinPartsPathTheme.c_str(), name, "png"); + + else if (FileExists(skinPartsPathSkin.c_str(), name, "svg")) + return LoadImage(skinPartsPathSkin.c_str(), name, "svg"); + + else if (FileExists(skinPartsPathSkin.c_str(), name, "png")) + return LoadImage(skinPartsPathSkin.c_str(), name, "png"); + + //check if a svg template exists + cSVGTemplate svgTemplate(name, svgTemplatePath); + if (!svgTemplate.Exists()) + return false; + svgTemplate.ReadTemplate(); + if (!svgTemplate.ParseTemplate()) + return false; + string tmpImageName = svgTemplate.WriteImage(); + return LoadImage(tmpImageName.c_str()); +} + +void cImageCache::Clear(void) { + for(map<string, cCachedImage*>::const_iterator it = iconCache.begin(); it != iconCache.end(); it++) { + cCachedImage *img = (cCachedImage*)it->second; + delete img; + } + iconCache.clear(); + + for(map<string, cImage*>::const_iterator it = channelLogoCache.begin(); it != channelLogoCache.end(); it++) { + cImage *img = (cImage*)it->second; + delete img; + } + channelLogoCache.clear(); + + for(map<string, cCachedImage*>::const_iterator it = skinPartsCache.begin(); it != skinPartsCache.end(); it++) { + cCachedImage *img = (cCachedImage*)it->second; + delete img; + } + skinPartsCache.clear(); + + for(map<string, cImage*>::const_iterator it = cairoImageCache.begin(); it != cairoImageCache.end(); it++) { + cImage *img = (cImage*)it->second; + delete img; + } + cairoImageCache.clear(); +} + +void cImageCache::Debug(bool full) { + float sizeIconCacheInternal = 0; + float sizeIconCacheExternal = 0; + int numIcons = 0; + GetIconCacheSize(numIcons, sizeIconCacheInternal, sizeIconCacheExternal); + dsyslog("skindesigner: cached %d icons - size internal mem %.2fMB, high level mem %.2fMB", numIcons, sizeIconCacheInternal, sizeIconCacheExternal); + if (full) { + for(std::map<std::string, cCachedImage*>::const_iterator it = iconCache.begin(); it != iconCache.end(); it++) { + string name = it->first; + cCachedImage *img = (cCachedImage*)it->second; + dsyslog("skindesigner: cached icon %s, handle %d", name.c_str(), img->handle); + } + } + + float sizeLogoCache = 0; + int numLogos = 0; + GetLogoCacheSize(numLogos, sizeLogoCache); + dsyslog("skindesigner: cached %d logos - size %.2fMB internal mem", numLogos, sizeLogoCache); + if (full) { + for(std::map<std::string, cImage*>::const_iterator it = channelLogoCache.begin(); it != channelLogoCache.end(); it++) { + string name = it->first; + dsyslog("skindesigner: cached logo %s", name.c_str()); + } + } + float sizeSkinpartCacheInternal = 0; + float sizeSkinpartCacheExternal = 0; + int numSkinparts = 0; + GetSkinpartsCacheSize(numSkinparts, sizeSkinpartCacheInternal, sizeSkinpartCacheExternal); + dsyslog("skindesigner: cached %d skinparts - size internal mem %.2fMB, high level mem %.2fMB", numSkinparts, sizeSkinpartCacheInternal, sizeSkinpartCacheExternal); + if (full) { + for(std::map<std::string, cCachedImage*>::const_iterator it = skinPartsCache.begin(); it != skinPartsCache.end(); it++) { + string name = it->first; + dsyslog("skindesigner: cached skinpart %s", name.c_str()); + } + } +} + +void cImageCache::GetIconCacheSize(int &num, float &sizeInternal, float &sizeExternal) { + num = iconCache.size(); + int sizeByteInternal = 0; + int sizeByteExternal = 0; + for (map<string, cCachedImage*>::iterator icon = iconCache.begin(); icon != iconCache.end(); icon++) { + cCachedImage* img = icon->second; + if (img->image) + sizeByteInternal += img->size; + else + sizeByteExternal += img->size; + } + sizeInternal = sizeByteInternal / 1024.0f / 1024.0f; + sizeExternal = sizeByteExternal / 1024.0f / 1024.0f; +} + +void cImageCache::GetLogoCacheSize(int &num, float &size) { + num = channelLogoCache.size(); + int sizeByte = 0; + for (map<string, cImage*>::iterator logo = channelLogoCache.begin(); logo != channelLogoCache.end(); logo++) { + cImage* img = logo->second; + sizeByte += img->Width() * img->Height() * sizeof(tColor); + } + size = sizeByte / 1024.0f; +} + +void cImageCache::GetSkinpartsCacheSize(int &num, float &sizeInternal, float &sizeExternal) { + num = skinPartsCache.size(); + int sizeByteInternal = 0; + int sizeByteExternal = 0; + for (map<string, cCachedImage*>::iterator skinpart = skinPartsCache.begin(); skinpart != skinPartsCache.end(); skinpart++) { + cCachedImage* img = skinpart->second; + if (img->image) + sizeByteInternal += img->size; + else + sizeByteExternal += img->size; + } + sizeInternal = sizeByteInternal / 1024.0f / 1024.0f; + sizeExternal = sizeByteExternal / 1024.0f / 1024.0f; +} diff --git a/extensions/imagecache.h b/extensions/imagecache.h new file mode 100644 index 0000000..13f882b --- /dev/null +++ b/extensions/imagecache.h @@ -0,0 +1,79 @@ +#ifndef __NOPACITY_IMAGECACHE_H +#define __NOPACITY_IMAGECACHE_H + +#define X_DISPLAY_MISSING + +#include <vdr/osd.h> +#include <vdr/skins.h> +#include <vector> +#include "imageloader.h" +#include "../coreengine/definitions.h" + +class cCachedImage { +public: + int handle; + cImage *image; + int size; + cCachedImage(void) { + handle = 0; + image = NULL; + size = 0; + }; + ~cCachedImage(void) { + if (handle) + cOsdProvider::DropImage(handle); + if (image) + delete image; + }; +}; + +class cImageCache : public cImageLoader { +public: + cImageCache(); + ~cImageCache(); + void Lock(void) { mutex.Lock(); } + void Unlock(void) { mutex.Unlock(); } + void SetPathes(void); + //channel logos + void CacheLogo(int width, int height); + cImage *GetLogo(string channelID, int width, int height); + bool LogoExists(string channelID); + cImage *GetSeparatorLogo(string name, int width, int height); + bool SeparatorLogoExists(string name); + //icons + void CacheIcon(eImageType type, string path, int width, int height); + cCachedImage *GetIcon(eImageType type, string name, int width, int height); + string GetIconName(string label, eMenuCategory cat = mcUndefined, string plugName = ""); + bool MenuIconExists(string name); + //skinparts + void CacheSkinpart(string path, int width, int height); + cCachedImage *GetSkinpart(string name, int width, int height); + //cairo special images + cImage *GetVerticalText(string text, tColor color, string font, int size, int direction); + //helpers + void Clear(void); + void Debug(bool full); + void GetIconCacheSize(int &num, float &sizeInternal, float &sizeExternal); + void GetLogoCacheSize(int &num, float &size); + void GetSkinpartsCacheSize(int &num, float &sizeInternal, float &sizeExternal); +private: + static cMutex mutex; + static string items[16]; + cImage *tempStaticLogo; + string logoPath; + string iconPathSkin; + string skinPartsPathSkin; + string iconPathTheme; + string skinPartsPathTheme; + string svgTemplatePath; + map<string, cCachedImage*> iconCache; + map<string, cImage*> channelLogoCache; + map<string, cCachedImage*> skinPartsCache; + map<string, cImage*> cairoImageCache; + bool LoadIcon(eImageType type, string name); + bool LoadLogo(const cChannel *channel); + bool LoadSeparatorLogo(string name); + bool LoadSkinpart(string name); +}; + +#endif //__NOPACITY_IMAGECACHE_H diff --git a/extensions/imageloader.c b/extensions/imageloader.c new file mode 100644 index 0000000..62d284f --- /dev/null +++ b/extensions/imageloader.c @@ -0,0 +1,507 @@ +#include "../config.h" +#include "helpers.h" +#include "imageloader.h" +#include <string> +#include <dirent.h> +#include <iostream> +#include <fstream> + +cImageLoader::cImageLoader() { + importer = NULL; +} + +cImageLoader::~cImageLoader() { + delete(importer); +} + +cImage *cImageLoader::CreateImage(int width, int height, bool preserveAspect) { + if (!importer) + return NULL; + int w, h; + importer->GetImageSize(w, h); + if (width == 0) + width = w; + if (height == 0) + height = h; + + cairo_surface_t *surface; + surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height); + + cairo_t *cr; + cr = cairo_create(surface); + + double sx = width / (double)w; + double sy = height / (double)h; + if (preserveAspect) { + double tx = 0; + double ty = 0; + if (sx < sy) { + sy = sx; + ty = (height - h * sy) / 2; + } + if (sy < sx) { + sx = sy; + tx = (width - w * sx) / 2; + } + cairo_translate(cr, tx, ty); + } + cairo_scale(cr, sx, sy); + + importer->DrawToCairo(cr); + + cairo_status_t status = cairo_status(cr); + if (status && config.debugImageLoading) + dsyslog("skindesigner: Cairo CreateImage Error %s", cairo_status_to_string(status)); + + unsigned char *data = cairo_image_surface_get_data(surface); + cImage *image = new cImage(cSize(width, height), (tColor*)data); + + cairo_destroy(cr); + cairo_surface_destroy(surface); + + return image; +} + +bool cImageLoader::LoadImage(const char *fullpath) { + if ((fullpath == NULL) || (strlen(fullpath) < 5)) + return false; + + if (config.debugImageLoading) + dsyslog("skindesigner: trying to load: %s", fullpath); + + delete(importer); + importer = NULL; + + if (endswith(fullpath, ".png")) + importer = new cImageImporterPNG; + else if (endswith(fullpath, ".svg")) + importer = new cImageImporterSVG; + else if (endswith(fullpath, ".jpg")) + importer = new cImageImporterJPG; + else { + importer = cImageImporter::CreateImageImporter(fullpath); + if (!importer) + return false; + } + + return importer->LoadImage(fullpath); +} + +// Just a different way to call LoadImage. Calls the above one. +bool cImageLoader::LoadImage(std::string Path, std::string FileName, std::string Extension) { + std::stringstream sstrImgFile; + sstrImgFile << Path << FileName << "." << Extension; + std::string imgFile = sstrImgFile.str(); + return LoadImage(imgFile.c_str()); +} + +cImageImporter* cImageImporter::CreateImageImporter(const char* path) { + char pngSig[] = { char(0x89), char(0x50), char(0x4E), char(0x47), char(0x0D), char(0x0A), char(0x1A), char(0x0A) }; + char jpgSig[] = { char(0xFF), char(0xD8), char(0xFF), char(0xD9) }; + char buffer[8] = { 0 }; + ifstream f(path, ios::in | ios::binary); + f.read(buffer, 8); + if (!f) + return NULL; + if(buffer[0] == jpgSig[0] && buffer[1] == jpgSig[1]) { + f.seekg(-2, f.end); + f.read(buffer, 2); + if(buffer[0] == jpgSig[2] && buffer[1] == jpgSig[3]) { + f.close(); + return new cImageImporterJPG; + } + } else if(buffer[0] == pngSig[0] + && buffer[1] == pngSig[1] + && buffer[2] == pngSig[2] + && buffer[3] == pngSig[3] + && buffer[4] == pngSig[4] + && buffer[5] == pngSig[5] + && buffer[6] == pngSig[6] + && buffer[7] == pngSig[7]) { + f.close(); + return new cImageImporterPNG; + } + f.close(); + return NULL; +} + +// +// Image importer for PNG +// + +cImageImporterPNG::cImageImporterPNG() { + surface = NULL; +} + +cImageImporterPNG::~cImageImporterPNG() { + if (surface) + cairo_surface_destroy(surface); +} + +bool cImageImporterPNG::LoadImage(const char *path) { + if (surface) + cairo_surface_destroy(surface); + + surface = cairo_image_surface_create_from_png(path); + + if (cairo_surface_status(surface)) { + if (config.debugImageLoading) + dsyslog("skindesigner: Cairo LoadImage Error: %s", cairo_status_to_string(cairo_surface_status(surface))); + surface = NULL; + return false; + } + + return true; +} + +void cImageImporterPNG::DrawToCairo(cairo_t *cr) { + if (surface) { + cairo_set_source_surface(cr, surface, 0, 0); + cairo_paint(cr); + } +} + +void cImageImporterPNG::GetImageSize(int &width, int &height) { + if (surface) { + width = cairo_image_surface_get_width(surface); + height = cairo_image_surface_get_height(surface); + } +} + +// +// Image importer for SVG +// + +cImageImporterSVG::cImageImporterSVG() { + handle = NULL; +} + +cImageImporterSVG::~cImageImporterSVG() { + if (handle) { + rsvg_handle_close(handle, NULL); + g_object_unref(handle); + } +} + +bool cImageImporterSVG::LoadImage(const char *path) { + if (handle) { + rsvg_handle_close(handle, NULL); + g_object_unref(handle); + } + + GError *error = NULL; + handle = rsvg_handle_new_from_file(path, &error); + if (!handle) { + if (config.debugImageLoading && error) { + dsyslog("skindesigner: RSVG Error: %s", error->message); + } + return false; + } + + // 90 dpi is the hardcoded default setting of the Inkscape SVG editor + rsvg_handle_set_dpi(handle, 90); + + return true; +} + +void cImageImporterSVG::DrawToCairo(cairo_t *cr) { + if (handle) + rsvg_handle_render_cairo(handle, cr); +} + +void cImageImporterSVG::GetImageSize(int &width, int &height) { + if (handle) { + RsvgDimensionData dim; + rsvg_handle_get_dimensions(handle, &dim); + width = dim.width; + height = dim.height; + } +} + +void cImageImporterSVG::InitLibRSVG() { + #if !GLIB_CHECK_VERSION(2, 35, 0) + g_type_init(); + #endif +} + +// +// Image importer for JPG +// + +struct my_error_mgr { + struct jpeg_error_mgr pub; // "public" fields + jmp_buf setjmp_buffer; // for return to caller +}; + +METHODDEF(void) +my_error_exit(j_common_ptr cinfo) { + // cinfo->err really points to a my_error_mgr struct, so coerce pointer + my_error_mgr *myerr = (my_error_mgr*) cinfo->err; + + // Always display the message. + (*cinfo->err->output_message) (cinfo); + + // Return control to the setjmp point + longjmp(myerr->setjmp_buffer, 1); +} + +METHODDEF(void) +my_output_message(j_common_ptr cinfo) { + char buf[JMSG_LENGTH_MAX]; + cinfo->err->format_message(cinfo, buf); + if (config.debugImageLoading) + dsyslog("skindesigner: libjpeg error: %s", buf); +} + +cImageImporterJPG::cImageImporterJPG() { + cinfo = NULL; +} + +cImageImporterJPG::~cImageImporterJPG() { + if (cinfo) { + jpeg_destroy_decompress(cinfo); + free(cinfo); + fclose(infile); + } +} + +bool cImageImporterJPG::LoadImage(const char *path) { + if (cinfo) { + jpeg_destroy_decompress(cinfo); + free(cinfo); + fclose(infile); + cinfo = NULL; + } + + // Open input file + if ((infile = fopen(path, "rb")) == NULL) { + if (config.debugImageLoading) + dsyslog("skindesigner: Can't open %s", path); + return false; + } + + // Allocate space for our decompress struct + cinfo = (j_decompress_ptr)malloc(sizeof(struct jpeg_decompress_struct)); + + // We set up the normal JPEG error routines, then override error_exit + // and output_message. + struct my_error_mgr jerr; + cinfo->err = jpeg_std_error(&jerr.pub); + jerr.pub.error_exit = my_error_exit; + jerr.pub.output_message = my_output_message; + // Establish the setjmp return context for my_error_exit to use. + if (setjmp(jerr.setjmp_buffer)) { + // If we get here, the JPEG code has signaled an error. + jpeg_destroy_decompress(cinfo); + free(cinfo); + fclose(infile); + cinfo = NULL; + return false; + } + + // Now we can initialize the JPEG decompression object. + jpeg_create_decompress(cinfo); + + // Step 2: specify data source (eg, a file) + jpeg_stdio_src(cinfo, infile); + + // Step 3: read file parameters with jpeg_read_header() + (void) jpeg_read_header(cinfo, TRUE); + return true; +} + +void cImageImporterJPG::DrawToCairo(cairo_t *cr) { + if (!cinfo) + return; + + unsigned char *bmp_buffer = NULL; + + // Re-establish error handling. We have to do this again as the saved + // calling environment of "LoadImage" is invalid if we reach here! + struct my_error_mgr jerr; + cinfo->err = jpeg_std_error(&jerr.pub); + jerr.pub.error_exit = my_error_exit; + jerr.pub.output_message = my_output_message; + if (setjmp(jerr.setjmp_buffer)) { + jpeg_destroy_decompress(cinfo); + free(cinfo); + fclose(infile); + free(bmp_buffer); + cinfo = NULL; + return; + } + + // Step 4: set parameters for decompression + cinfo->out_color_space = JCS_RGB; + + // Step 5: Start decompressor + (void) jpeg_start_decompress(cinfo); + + // Allocate buffer. Directly allocate the space needed for ARGB + unsigned int width = cinfo->output_width; + unsigned int height = cinfo->output_height; + bmp_buffer = (unsigned char*)malloc(width * height * 4); + + // Step 6: while (scan lines remain to be read) + int jpg_stride = width * cinfo->output_components; + while (cinfo->output_scanline < height) { + unsigned char *buffer_array[1]; + buffer_array[0] = bmp_buffer + (cinfo->output_scanline) * jpg_stride; + jpeg_read_scanlines(cinfo, buffer_array, 1); + } + + // Step 7: Finish decompression. + (void)jpeg_finish_decompress(cinfo); + + // Cleanup. In this "ImageImporter" we clean up everything in "DrawToCairo" + // as I'm not really sure whether we are able to draw a second time. + fclose(infile); + jpeg_destroy_decompress(cinfo); + free(cinfo); + cinfo = NULL; + + // --> At this point we have raw RGB data in bmp_buffer + + // Do some ugly byte shifting. + // Byte order in libjpeg: RGB + // Byte order in cairo and VDR: BGRA + unsigned char temp[3]; + for (int index = (width * height) - 1; index >= 0; index--) { + unsigned char *target = bmp_buffer + (index * 4); + unsigned char *source = bmp_buffer + (index * 3); + memcpy(&temp[0], source + 2, 1); + memcpy(&temp[1], source + 1, 1); + memcpy(&temp[2], source, 1); + memcpy(target, &temp, 3); + } + + // Create new Cairo surface from our raw image data + cairo_surface_t *surface; + surface = cairo_image_surface_create_for_data(bmp_buffer, + CAIRO_FORMAT_RGB24, + width, + height, + width * 4); + + // Draw surface to Cairo + if (surface) { + cairo_set_source_surface(cr, surface, 0, 0); + cairo_paint(cr); + cairo_surface_destroy(surface); + } + + // Free our memory + free(bmp_buffer); +} + +void cImageImporterJPG::GetImageSize(int &width, int &height) { + if (cinfo) { + width = cinfo->image_width; + height = cinfo->image_height; + } +} + +// +// SVG Template class +// + +cSVGTemplate::cSVGTemplate(string imageName, string templatePath) { + this->imageName = imageName; + this->templatePath = templatePath; + startTokenColor = "{sdcol("; + startTokenOpac = "{sdopac("; + endToken = ")}"; + filePath = templatePath; + filePath += imageName + ".svg"; +} + +cSVGTemplate::~cSVGTemplate(void) { +} + +bool cSVGTemplate::Exists(void) { + return FileExists(templatePath, imageName, "svg"); +} + +void cSVGTemplate::ReadTemplate(void) { + string line; + ifstream templatefile(filePath.c_str()); + if (templatefile.is_open()) { + while ( getline (templatefile, line) ) { + svgTemplate.push_back(line); + } + templatefile.close(); + } +} + +bool cSVGTemplate::ParseTemplate(void) { + int i = 0; + for (vector<string>::iterator it = svgTemplate.begin(); it != svgTemplate.end(); it++) { + string line = *it; + size_t hit = line.find(startTokenColor); + if (hit == string::npos) { + i++; + continue; + } + while (hit != string::npos) { + size_t hitEnd = line.find(endToken, hit); + if (hitEnd == string::npos) { + esyslog("skindesigner: error in SVG Template %s: invalid tag", imageName.c_str()); + return false; + } + string colorName = GetColorName(line, hit, hitEnd); + tColor replaceColor = 0x0; + if (!config.GetThemeColor(colorName, replaceColor)) { + esyslog("skindesigner: error in SVG Template %s: invalid color %x", imageName.c_str(), replaceColor); + return false; + } + ReplaceTokens(line, hit, hitEnd, replaceColor); + hit = line.find(startTokenColor); + } + svgTemplate[i] = line; + i++; + } + return true; +} + +string cSVGTemplate::WriteImage(void) { + string tempPath = *cString::sprintf("/tmp/skindesigner/svg/%s/%s/", Setup.OSDSkin, Setup.OSDTheme); + CreateFolder(tempPath); + string fileName = tempPath + imageName + ".svg"; + ofstream tmpimg; + tmpimg.open (fileName.c_str(), ios::out | ios::trunc); + if (!tmpimg.is_open()) { + return ""; + } + for (vector<string>::iterator it = svgTemplate.begin(); it != svgTemplate.end(); it++) { + tmpimg << (*it) << "\n"; + } + tmpimg.close(); + return fileName; +} + +string cSVGTemplate::GetColorName(string line, size_t tokenStart, size_t tokenEnd) { + string colorName = line.substr(tokenStart + startTokenColor.size(), tokenEnd - tokenStart - startTokenColor.size()); + if (colorName.size() > 0) { + stringstream name; + name << "{" << colorName << "}"; + return name.str(); + } + return ""; +} + +void cSVGTemplate::ReplaceTokens(string &line, size_t tokenStart, size_t tokenEnd, tColor color) { + string rgbColor = *cString::sprintf("%06x", color & 0x00FFFFFF); + line.replace(tokenStart, tokenEnd - tokenStart + 2, rgbColor); + size_t hitAlpha = line.find(startTokenOpac); + if (hitAlpha == string::npos) { + return; + } + size_t hitAlphaEnd = line.find(endToken, hitAlpha); + if (hitAlphaEnd == string::npos) { + return; + } + tIndex alpha = (color & 0xFF000000) >> 24; + string svgAlpha = *cString::sprintf("%f", (float)(alpha / (float)255)); + std::replace( svgAlpha.begin(), svgAlpha.end(), ',', '.'); + line.replace(hitAlpha, hitAlphaEnd - hitAlpha + 2, svgAlpha); +} diff --git a/extensions/imageloader.h b/extensions/imageloader.h new file mode 100644 index 0000000..12bd8d2 --- /dev/null +++ b/extensions/imageloader.h @@ -0,0 +1,115 @@ +#ifndef __SKINDESIGNER_IMAGELOADER_H +#define __SKINDESIGNER_IMAGELOADER_H + +#include <string> +#include <cairo.h> +#include <librsvg/rsvg.h> +#ifndef LIBRSVG_CHECK_VERSION // Workaround for librsvg < 2.36.2 + #include <librsvg/rsvg-cairo.h> + #include <librsvg/librsvg-features.h> +#endif +#include <jpeglib.h> +#include <setjmp.h> +#include <vdr/osd.h> +#include <vdr/tools.h> + +using namespace std; + +// +// Image importers +// +class cImageImporter { +public: + cImageImporter() {}; + virtual ~cImageImporter() {}; + virtual bool LoadImage(const char *path) { return false; }; + virtual void DrawToCairo(cairo_t *cr) {}; + virtual void GetImageSize(int &width, int &height) {}; + static cImageImporter* CreateImageImporter(const char* path); +}; + +// Image importer for PNG +class cImageImporterPNG : public cImageImporter { +public: + cImageImporterPNG(); + ~cImageImporterPNG(); + bool LoadImage(const char *path); + void DrawToCairo(cairo_t *cr); + void GetImageSize(int &width, int &height); +private: + cairo_surface_t *surface; +}; + +// Image importer for SVG +#if !LIBRSVG_CHECK_VERSION(2, 36, 0) + #error librsvg version 2.36.0 or above required! +#endif + +class cImageImporterSVG : public cImageImporter { +public: + cImageImporterSVG(); + ~cImageImporterSVG(); + bool LoadImage(const char *path); + void DrawToCairo(cairo_t *cr); + void GetImageSize(int &width, int &height); + static void InitLibRSVG(); +private: + RsvgHandle *handle; +}; + +// Image importer for JPG +#if BITS_IN_JSAMPLE != 8 + #error libjpeg has to be compiled with 8-bit samples! +#endif + +class cImageImporterJPG : public cImageImporter { +public: + cImageImporterJPG(); + ~cImageImporterJPG(); + bool LoadImage(const char *path); + void DrawToCairo(cairo_t *cr); + void GetImageSize(int &width, int &height); +private: + j_decompress_ptr cinfo; + FILE *infile; +}; + +// +// Image loader class +// +class cImageLoader { +private: + cImageImporter *importer; +public: + cImageLoader(); + virtual ~cImageLoader(); + cImage *CreateImage(int width, int height, bool preserveAspect = true); + bool LoadImage(std::string Path, std::string FileName, std::string Extension); + bool LoadImage(const char *fullpath); +}; + +// +// SVG Template class +// + +class cSVGTemplate { +private: + string imageName; + string templatePath; + string filePath; + string startTokenColor; + string startTokenOpac; + string endToken; + vector<string> svgTemplate; + string GetColorName(string line, size_t tokenStart, size_t tokenEnd); + void ReplaceTokens(string &line, size_t tokenStart, size_t tokenEnd, tColor color); +public: + cSVGTemplate(string imageName, string templatePath); + virtual ~cSVGTemplate(void); + bool Exists(void); + void ReadTemplate(void); + bool ParseTemplate(void); + string WriteImage(void); +}; + +#endif //__SKINDESIGNER_IMAGELOADER_H diff --git a/extensions/libxmlwrapper.c b/extensions/libxmlwrapper.c new file mode 100644 index 0000000..117815e --- /dev/null +++ b/extensions/libxmlwrapper.c @@ -0,0 +1,189 @@ +#include "helpers.h"
+#include "libxmlwrapper.h"
+
+void SkinDesignerXMLErrorHandler (void * userData, xmlErrorPtr error) {
+ esyslog("skindesigner: Error in XML: %s", error->message);
+}
+
+cLibXMLWrapper::cLibXMLWrapper(void) {
+ ctxt = NULL;
+ doc = NULL;
+ root = NULL;
+
+ initGenericErrorDefaultFunc(NULL);
+ xmlSetStructuredErrorFunc(NULL, SkinDesignerXMLErrorHandler);
+ ctxt = xmlNewParserCtxt();
+}
+
+cLibXMLWrapper::~cLibXMLWrapper() {
+ DeleteDocument();
+ xmlFreeParserCtxt(ctxt);
+}
+
+void cLibXMLWrapper::InitLibXML() {
+ xmlInitParser();
+}
+
+void cLibXMLWrapper::CleanupLibXML() {
+ xmlCleanupParser();
+}
+
+void cLibXMLWrapper::DeleteDocument(void) {
+ if (doc) {
+ xmlFreeDoc(doc);
+ doc = NULL;
+ }
+ while (!nodeStack.empty())
+ nodeStack.pop();
+}
+
+bool cLibXMLWrapper::ReadXMLFile(const char *path, bool validate) {
+ if (!ctxt) {
+ esyslog("skindesigner: Failed to allocate parser context");
+ return false;
+ }
+ if (!FileExists(path)) {
+ dsyslog("skindesigner: reading XML Template %s failed", path);
+ return false;
+ }
+ if (validate)
+ doc = xmlCtxtReadFile(ctxt, path, NULL, XML_PARSE_NOENT | XML_PARSE_DTDVALID);
+ else
+ doc = xmlCtxtReadFile(ctxt, path, NULL, XML_PARSE_NOENT);
+
+ if (!doc) {
+ dsyslog("skindesigner: reading XML Template %s failed", path);
+ return false;
+ }
+ return true;
+}
+
+bool cLibXMLWrapper::SetDocument(void) {
+ if (!doc)
+ return false;
+ root = xmlDocGetRootElement(doc);
+ nodeStack.push(root);
+ if (root == NULL) {
+ esyslog("skindesigner: ERROR: XML File is empty");
+ return false;
+ }
+ return true;
+}
+
+bool cLibXMLWrapper::Validate(void) {
+ if (!ctxt)
+ return false;
+ if (ctxt->valid == 0) {
+ esyslog("skindesigner: Failed to validate XML File");
+ return false;
+ }
+ return true;
+}
+
+bool cLibXMLWrapper::CheckNodeName(const char *name) {
+ if (nodeStack.empty())
+ return false;
+ xmlNodePtr current = nodeStack.top();
+ if (xmlStrcmp(current->name, (const xmlChar *) name)) {
+ return false;
+ }
+ return true;
+}
+
+const char *cLibXMLWrapper::NodeName(void) {
+ xmlNodePtr current = nodeStack.top();
+ return (const char*)current->name;
+}
+
+vector<stringpair> cLibXMLWrapper::ParseAttributes(void) {
+ vector<stringpair> attributes;
+ if (nodeStack.empty())
+ return attributes;
+ xmlNodePtr current = nodeStack.top();
+ xmlAttrPtr attrPtr = current->properties;
+ if (attrPtr == NULL) {
+ return attributes;
+ }
+ while (NULL != attrPtr) {
+ string name = (const char*)attrPtr->name;
+ xmlChar *value = NULL;
+ value = xmlGetProp(current, attrPtr->name);
+ if (value)
+ attributes.push_back(stringpair((const char*)attrPtr->name, (const char*)value));
+ attrPtr = attrPtr->next;
+ if (value)
+ xmlFree(value);
+ }
+ return attributes;
+}
+
+bool cLibXMLWrapper::LevelDown(void) {
+ if (nodeStack.empty())
+ return false;
+ xmlNodePtr current = nodeStack.top();
+ xmlNodePtr child = current->xmlChildrenNode;
+ while (child && child->type != XML_ELEMENT_NODE) {
+ child = child->next;
+ }
+ if (!child)
+ return false;
+ nodeStack.push(child);
+ return true;
+}
+
+bool cLibXMLWrapper::LevelUp(void) {
+ if (nodeStack.size() == 1)
+ return false;
+ nodeStack.pop();
+ return true;
+}
+
+bool cLibXMLWrapper::NextNode(void) {
+ xmlNodePtr current = nodeStack.top();
+ current = current->next;
+ while (current && current->type != XML_ELEMENT_NODE) {
+ current = current->next;
+ }
+ if (!current)
+ return false;
+ nodeStack.pop();
+ nodeStack.push(current);
+ return true;
+}
+
+bool cLibXMLWrapper::GetAttribute(string &name, string &value) {
+ bool ok = false;
+ xmlNodePtr current = nodeStack.top();
+ xmlAttrPtr attr = current->properties;
+ if (attr == NULL) {
+ return ok;
+ }
+ xmlChar *xmlValue = NULL;
+ while (NULL != attr) {
+ if (xmlStrcmp(attr->name, (const xmlChar *) name.c_str())) {
+ attr = attr->next;
+ continue;
+ }
+ ok = true;
+ xmlValue = xmlGetProp(current, attr->name);
+ if (xmlValue) {
+ value = (const char*)xmlValue;
+ xmlFree(xmlValue);
+ }
+ break;
+ }
+ return ok;
+}
+
+bool cLibXMLWrapper::GetNodeValue(string &value) {
+ xmlChar *val = NULL;
+ xmlNodePtr current = nodeStack.top();
+ val = xmlNodeListGetString(doc, current->xmlChildrenNode, 1);
+ if (val) {
+ value = (const char*)val;
+ xmlFree(val);
+ return true;
+ }
+ value = "";
+ return false;
+}
diff --git a/extensions/libxmlwrapper.h b/extensions/libxmlwrapper.h new file mode 100644 index 0000000..3971bb9 --- /dev/null +++ b/extensions/libxmlwrapper.h @@ -0,0 +1,46 @@ +#ifndef __LIBXMLWRAPPER_H
+#define __LIBXMLWRAPPER_H
+
+#include <string>
+#include <vector>
+#include <map>
+#include <set>
+#include <utility>
+#include <stack>
+#include <libxml/parser.h>
+#include <libxml/tree.h>
+#include <libxml/xmlerror.h>
+#include <vdr/plugin.h>
+
+using namespace std;
+typedef pair<string,string> stringpair;
+typedef map<string,string> stringmap;
+
+class cLibXMLWrapper {
+private:
+ xmlParserCtxtPtr ctxt;
+ xmlDocPtr doc;
+ xmlNodePtr root;
+ xmlNodePtr current;
+ stack<xmlNodePtr> nodeStack;
+protected:
+ void DeleteDocument(void);
+ bool ReadXMLFile(const char *path, bool validate = true);
+ bool SetDocument(void);
+ bool Validate(void);
+ bool CheckNodeName(const char *name);
+ const char *NodeName(void);
+ vector<stringpair> ParseAttributes(void);
+ bool LevelDown(void);
+ bool LevelUp(void);
+ bool NextNode(void);
+ bool GetAttribute(string &name, string &value);
+ bool GetNodeValue(string &value);
+public:
+ cLibXMLWrapper(void);
+ virtual ~cLibXMLWrapper(void);
+ static void InitLibXML();
+ static void CleanupLibXML();
+};
+
+#endif //__LIBXMLWRAPPER_H
\ No newline at end of file diff --git a/extensions/pluginmanager.c b/extensions/pluginmanager.c new file mode 100644 index 0000000..2f839f8 --- /dev/null +++ b/extensions/pluginmanager.c @@ -0,0 +1,301 @@ +#include "pluginmanager.h"
+
+cSDPluginManager::cSDPluginManager(void) {
+ lastId = 0;
+ subviewsfound = false;
+}
+
+cSDPluginManager::~cSDPluginManager(void) {
+}
+
+void cSDPluginManager::Reset(void) {
+ subViewMapping.clear();
+}
+
+void cSDPluginManager::RegisterBasicPlugin(skindesignerapi::cPluginStructure *plugStructure) {
+ dsyslog("skindesigner: plugin %s uses libskindesigner API Version %s",
+ plugStructure->name.c_str(),
+ plugStructure->libskindesignerAPIVersion.c_str());
+ plugStructure->id = lastId;
+ registeredPlugins.insert(pair<int,string>(lastId, plugStructure->name));
+ lastId++;
+ map< int, skindesignerapi::sPlugMenu > menusNew;
+ for (map< int, skindesignerapi::sPlugMenu >::iterator it = plugStructure->menus.begin(); it != plugStructure->menus.end(); it++) {
+ int key = it->first;
+ skindesignerapi::sPlugMenu menu = it->second;
+ skindesignerapi::sPlugMenu menuNew;
+ menuNew.type = menu.type;
+ menuNew.tplname = menu.tplname;
+ menuNew.tokenContainer = new skindesignerapi::cTokenContainer(*menu.tokenContainer);
+ menusNew.insert(pair<int, skindesignerapi::sPlugMenu>(key, menuNew));
+ }
+ pluginMenus.insert(pair< int, map < int, skindesignerapi::sPlugMenu > >(plugStructure->id, menusNew));
+
+ if (plugStructure->menus.size() > 0)
+ dsyslog("skindesigner: plugin %s has registered %ld menus", plugStructure->name.c_str(), plugStructure->menus.size());
+
+}
+
+int cSDPluginManager::GetNumPluginMenus(void) {
+ int numMenusTotal = 0;
+ for (map < int, map < int, skindesignerapi::sPlugMenu > >::iterator it = pluginMenus.begin(); it != pluginMenus.end(); it++) {
+ numMenusTotal += (it->second).size();
+ }
+ return numMenusTotal;
+}
+
+void cSDPluginManager::InitPluginMenuIterator(void) {
+ plugMenuIt = pluginMenus.begin();
+}
+
+map <int,skindesignerapi::sPlugMenu> *cSDPluginManager::GetPluginMenus(string &name, int &id) {
+ if (plugMenuIt == pluginMenus.end())
+ return NULL;
+ id = plugMenuIt->first;
+ map<int,string>::iterator hit = registeredPlugins.find(id);
+ if (hit != registeredPlugins.end())
+ name = hit->second;
+ map <int,skindesignerapi::sPlugMenu> *templates = &plugMenuIt->second;
+ plugMenuIt++;
+ return templates;
+}
+
+skindesignerapi::cTokenContainer *cSDPluginManager::GetTokenContainer(int plugId, int plugMenuId) {
+ map <int, map<int, skindesignerapi::sPlugMenu> >::iterator hit = pluginMenus.find(plugId);
+ if (hit == pluginMenus.end())
+ return NULL;
+ map<int, skindesignerapi::sPlugMenu>::iterator hit2 = (hit->second).find(plugMenuId);
+ if (hit2 == (hit->second).end())
+ return NULL;
+ skindesignerapi::cTokenContainer *tk = hit2->second.tokenContainer;
+ return tk;
+}
+
+void cSDPluginManager::AddSubviewMapping(int plugId, int plugMenuId, int subViewId) {
+ map <int, map<int,int> >::iterator hit = subViewMapping.find(plugId);
+ if (hit == subViewMapping.end()) {
+ map<int,int> menus;
+ menus.insert(pair<int,int>(plugMenuId, subViewId));
+ subViewMapping.insert(pair<int, map<int,int> >(plugId, menus));
+ } else {
+ (hit->second).insert(pair<int,int>(plugMenuId, subViewId));
+ }
+}
+
+int cSDPluginManager::GetSubviewId(int plugId, int plugMenuId) {
+ map <int, map<int,int> >::iterator hit = subViewMapping.find(plugId);
+ if (hit == subViewMapping.end())
+ return -1;
+ map<int,int>::iterator hit2 = (hit->second).find(plugMenuId);
+ if (hit2 == (hit->second).end())
+ return -1;
+ return hit2->second;
+}
+
+void cSDPluginManager::RegisterAdvancedPlugin(skindesignerapi::cPluginStructure *plugStructure) {
+ dsyslog("skindesigner: plugin %s uses libskindesigner API Version %s",
+ plugStructure->name.c_str(),
+ plugStructure->libskindesignerAPIVersion.c_str());
+ plugStructure->id = lastId;
+ registeredPlugins.insert(pair<int,string>(lastId, plugStructure->name));
+ lastId++;
+
+ rootviews.insert(pair<int,string>(plugStructure->id, plugStructure->rootview));
+ subviews.insert(pair<int,map<int,string> >(plugStructure->id, plugStructure->subviews));
+
+ multimap< int, skindesignerapi::sPlugViewElement > viewelementsNew;
+ for (map< int, skindesignerapi::sPlugViewElement >::iterator it = plugStructure->viewelements.begin(); it != plugStructure->viewelements.end(); it++) {
+ int key = it->first;
+ skindesignerapi::sPlugViewElement ve = it->second;
+ skindesignerapi::sPlugViewElement veNew;
+ veNew.id = ve.id;
+ veNew.viewId = ve.viewId;
+ veNew.name = ve.name;
+ veNew.tokenContainer = new skindesignerapi::cTokenContainer(*ve.tokenContainer);
+ viewelementsNew.insert(pair<int, skindesignerapi::sPlugViewElement>(key, veNew));
+ }
+ viewelements.insert(pair< int, multimap < int, skindesignerapi::sPlugViewElement > >(plugStructure->id, viewelementsNew));
+
+ multimap< int, skindesignerapi::sPlugViewGrid > viewgridsNew;
+ for (map< int, skindesignerapi::sPlugViewGrid >::iterator it = plugStructure->viewgrids.begin(); it != plugStructure->viewgrids.end(); it++) {
+ int key = it->first;
+ skindesignerapi::sPlugViewGrid vg = it->second;
+ skindesignerapi::sPlugViewGrid vgNew;
+ vgNew.id = vg.id;
+ vgNew.viewId = vg.viewId;
+ vgNew.name = vg.name;
+ vgNew.tokenContainer = new skindesignerapi::cTokenContainer(*vg.tokenContainer);
+ viewgridsNew.insert(pair<int, skindesignerapi::sPlugViewGrid>(key, vgNew));
+ }
+ viewgrids.insert(pair< int, multimap < int, skindesignerapi::sPlugViewGrid > >(plugStructure->id, viewgridsNew));
+
+ map< int, skindesignerapi::cTokenContainer* > viewtabsNew;
+ for (map<int,skindesignerapi::cTokenContainer*>::iterator it = plugStructure->viewtabs.begin(); it != plugStructure->viewtabs.end(); it++) {
+ int id = it->first;
+ skindesignerapi::cTokenContainer *tk = it->second;
+ viewtabsNew.insert(pair<int,skindesignerapi::cTokenContainer*>(id, new skindesignerapi::cTokenContainer(*tk)));
+ }
+ viewtabs.insert(pair< int, map<int,skindesignerapi::cTokenContainer*> >(plugStructure->id, viewtabsNew));
+
+ if (plugStructure->rootview.size() > 0)
+ dsyslog("skindesigner: plugin %s has registered %ld views with %ld viewelements and %ld viewgrids",
+ plugStructure->name.c_str(),
+ 1 + plugStructure->subviews.size(),
+ plugStructure->viewelements.size(),
+ plugStructure->viewgrids.size());
+}
+
+void cSDPluginManager::InitPluginViewIterator(void) {
+ rootViewsIt = rootviews.begin();
+}
+
+bool cSDPluginManager::GetNextPluginView(string &plugName, int &plugId, string &tplName) {
+ if (rootViewsIt == rootviews.end())
+ return false;
+ plugId = rootViewsIt->first;
+ tplName = rootViewsIt->second;
+ map<int,string>::iterator hit = registeredPlugins.find(plugId);
+ if (hit != registeredPlugins.end())
+ plugName = hit->second;
+ rootViewsIt++;
+ return true;
+}
+
+int cSDPluginManager::GetNumSubviews(int plugId) {
+ map< int, map< int, string > >::iterator hit = subviews.find(plugId);
+ if (hit == subviews.end())
+ return 0;
+ return (hit->second).size();
+}
+
+void cSDPluginManager::InitPluginSubviewIterator(int plugId) {
+ map< int, map< int, string > >::iterator hit = subviews.find(plugId);
+ if (hit == subviews.end()) {
+ subviewsfound = false;
+ return;
+ }
+ subviewsCurrent = hit->second;
+ subviewsfound = true;
+ svIt = subviewsCurrent.begin();
+}
+
+bool cSDPluginManager::GetNextSubView(int &id, string &tplname) {
+ if (!subviewsfound)
+ return false;
+ if( svIt == subviewsCurrent.end() ) {
+ return false;
+ }
+ id = svIt->first;
+ tplname = svIt->second;
+ svIt++;
+ return true;
+}
+
+int cSDPluginManager::GetNumViewElements(int plugId, int viewId) {
+ map< int, multimap< int, skindesignerapi::sPlugViewElement > >::iterator hit = viewelements.find(plugId);
+ if (hit == viewelements.end())
+ return 0;
+ multimap<int, skindesignerapi::sPlugViewElement> *plugVEs = &hit->second;
+ pair<multimap<int, skindesignerapi::sPlugViewElement>::iterator, multimap<int, skindesignerapi::sPlugViewElement>::iterator> range;
+ range = plugVEs->equal_range(viewId);
+ int numVEs = 0;
+ for (multimap<int, skindesignerapi::sPlugViewElement>::iterator it=range.first; it!=range.second; ++it) {
+ numVEs++;
+ }
+ return numVEs;
+}
+
+void cSDPluginManager::InitViewElementIterator(int plugId, int viewId) {
+ map< int, multimap< int, skindesignerapi::sPlugViewElement > >::iterator hit = viewelements.find(plugId);
+ if (hit == viewelements.end())
+ return;
+ multimap<int, skindesignerapi::sPlugViewElement> *plugVEs = &hit->second;
+ veRange = plugVEs->equal_range(viewId);
+ veIt = veRange.first;
+}
+
+bool cSDPluginManager::GetNextViewElement(int &veId, string &veName) {
+ if (veIt == veRange.second)
+ return false;
+ skindesignerapi::sPlugViewElement *ve = &veIt->second;
+ veId = ve->id;
+ veName = ve->name;
+ veIt++;
+ return true;
+}
+
+skindesignerapi::cTokenContainer *cSDPluginManager::GetTokenContainerVE(int plugId, int viewId, int veId) {
+ map< int, multimap< int, skindesignerapi::sPlugViewElement > >::iterator hit = viewelements.find(plugId);
+ if (hit == viewelements.end())
+ return NULL;
+ multimap<int, skindesignerapi::sPlugViewElement> *plugVEs = &hit->second;
+ for (multimap<int, skindesignerapi::sPlugViewElement>::iterator it = plugVEs->begin(); it != plugVEs->end(); it++) {
+ int view = it->first;
+ if (view != viewId)
+ continue;
+ skindesignerapi::sPlugViewElement *ve = &it->second;
+ if (ve->id == veId)
+ return ve->tokenContainer;
+ }
+ return NULL;
+}
+
+int cSDPluginManager::GetNumViewGrids(int plugId, int viewId) {
+ map< int, multimap< int, skindesignerapi::sPlugViewGrid > >::iterator hit = viewgrids.find(plugId);
+ if (hit == viewgrids.end())
+ return 0;
+ multimap<int, skindesignerapi::sPlugViewGrid> *plugVGs = &hit->second;
+ pair<multimap<int, skindesignerapi::sPlugViewGrid>::iterator, multimap<int, skindesignerapi::sPlugViewGrid>::iterator> range;
+ range = plugVGs->equal_range(viewId);
+ int numVGs = 0;
+ for (multimap<int, skindesignerapi::sPlugViewGrid>::iterator it=range.first; it!=range.second; ++it) {
+ numVGs++;
+ }
+ return numVGs;
+}
+
+void cSDPluginManager::InitViewGridIterator(int plugId, int viewId) {
+ map< int, multimap< int, skindesignerapi::sPlugViewGrid > >::iterator hit = viewgrids.find(plugId);
+ if (hit == viewgrids.end())
+ return;
+ multimap<int, skindesignerapi::sPlugViewGrid> *plugGEs = &hit->second;
+ gRange = plugGEs->equal_range(viewId);
+ gIt = gRange.first;
+}
+
+bool cSDPluginManager::GetNextViewGrid(int &gId, string &gName) {
+ if (gIt == gRange.second)
+ return false;
+ skindesignerapi::sPlugViewGrid *ge = &gIt->second;
+ gId = ge->id;
+ gName = ge->name;
+ gIt++;
+ return true;
+}
+
+skindesignerapi::cTokenContainer *cSDPluginManager::GetTokenContainerGE(int plugId, int viewId, int gId) {
+ map< int, multimap< int, skindesignerapi::sPlugViewGrid > >::iterator hit = viewgrids.find(plugId);
+ if (hit == viewgrids.end())
+ return NULL;
+ multimap<int, skindesignerapi::sPlugViewGrid> *plugGEs = &hit->second;
+ for (multimap<int, skindesignerapi::sPlugViewGrid>::iterator it = plugGEs->begin(); it != plugGEs->end(); it++) {
+ int view = it->first;
+ if (view != viewId)
+ continue;
+ skindesignerapi::sPlugViewGrid *g = &it->second;
+ if (g->id == gId)
+ return g->tokenContainer;
+ }
+ return NULL;
+}
+
+skindesignerapi::cTokenContainer *cSDPluginManager::GetTokenContainerTab(int plugId, int viewId) {
+ map< int, map< int, skindesignerapi::cTokenContainer* > >::iterator hit = viewtabs.find(plugId);
+ if (hit == viewtabs.end())
+ return NULL;
+ map< int, skindesignerapi::cTokenContainer* > *tabs = &hit->second;
+ map< int, skindesignerapi::cTokenContainer* >::iterator hit2 = tabs->find(viewId);
+ if (hit2 == tabs->end())
+ return NULL;
+ return (hit2->second);
+}
diff --git a/extensions/pluginmanager.h b/extensions/pluginmanager.h new file mode 100644 index 0000000..399c1d8 --- /dev/null +++ b/extensions/pluginmanager.h @@ -0,0 +1,71 @@ +#ifndef __PLUGINMANAGER_H
+#define __PLUGINMANAGER_H
+
+#include <string>
+#include <map>
+#include "libskindesignerapi/skindesignerapi.h"
+
+using namespace std;
+
+class cSDPluginManager {
+private:
+ int lastId;
+ //plugin id --> plugin name
+ map < int, string > registeredPlugins;
+ //Basic Plugin Interface
+ //plugin id --> plugin definition
+ map < int, map < int, skindesignerapi::sPlugMenu > > pluginMenus;
+ map < int, map < int, skindesignerapi::sPlugMenu > >::iterator plugMenuIt;
+ //plugin id - menuId --> subviewid
+ map < int, map<int, int> > subViewMapping;
+
+ //Advanced Plugin Interface
+ //plugin id --> rootview templatename definition
+ map< int, string > rootviews;
+ map< int, string >::iterator rootViewsIt;
+ //plugin id --> subviewid /templatename definition
+ map< int, map< int, string > > subviews;
+ map< int, string> subviewsCurrent;
+ map< int, string>::iterator svIt;
+ bool subviewsfound;
+ //plugin id --> view id --> viewelement definition
+ map< int, multimap< int, skindesignerapi::sPlugViewElement > > viewelements;
+ pair<multimap<int, skindesignerapi::sPlugViewElement>::iterator, multimap<int, skindesignerapi::sPlugViewElement>::iterator> veRange;
+ multimap<int, skindesignerapi::sPlugViewElement>::iterator veIt;
+ //plugin id --> view id --> viewgrid definition
+ map< int, multimap< int, skindesignerapi::sPlugViewGrid > > viewgrids;
+ pair<multimap<int, skindesignerapi::sPlugViewGrid>::iterator, multimap<int, skindesignerapi::sPlugViewGrid>::iterator> gRange;
+ multimap<int, skindesignerapi::sPlugViewGrid>::iterator gIt;
+ //plugin id --> view id --> tokencontainer of detailedview definition
+ map< int, map< int, skindesignerapi::cTokenContainer* > > viewtabs;
+public:
+ cSDPluginManager(void);
+ ~cSDPluginManager(void);
+ void Reset(void);
+ //Basic Plugin Interface
+ void RegisterBasicPlugin(skindesignerapi::cPluginStructure *plugStructure);
+ int GetNumPluginMenus(void);
+ void InitPluginMenuIterator(void);
+ map <int,skindesignerapi::sPlugMenu> *GetPluginMenus(string &name, int &id);
+ skindesignerapi::cTokenContainer *GetTokenContainer(int plugId, int plugMenuId);
+ void AddSubviewMapping(int plugId, int plugMenuId, int subViewId);
+ int GetSubviewId(int plugId, int plugMenuId);
+ //Advanced Plugin Interface
+ void RegisterAdvancedPlugin(skindesignerapi::cPluginStructure *plugStructure);
+ void InitPluginViewIterator(void);
+ bool GetNextPluginView(string &plugName, int &plugId, string &tplName);
+ int GetNumSubviews(int plugId);
+ void InitPluginSubviewIterator(int plugId);
+ bool GetNextSubView(int &id, string &tplname);
+ int GetNumViewElements(int plugId, int viewId);
+ void InitViewElementIterator(int plugId, int viewId);
+ bool GetNextViewElement(int &veId, string &veName);
+ skindesignerapi::cTokenContainer *GetTokenContainerVE(int plugId, int viewId, int veId);
+ int GetNumViewGrids(int plugId, int viewId);
+ void InitViewGridIterator(int plugId, int viewId);
+ bool GetNextViewGrid(int &gId, string &gName);
+ skindesignerapi::cTokenContainer *GetTokenContainerGE(int plugId, int viewId, int gId);
+ skindesignerapi::cTokenContainer *GetTokenContainerTab(int plugId, int viewId);
+};
+
+#endif //__PLUGINMANAGER_H
\ No newline at end of file diff --git a/extensions/recfolderinfo.c b/extensions/recfolderinfo.c new file mode 100644 index 0000000..d03ed6c --- /dev/null +++ b/extensions/recfolderinfo.c @@ -0,0 +1,219 @@ +#include "recfolderinfo.h" + + +class cFolderInfoInternList; + +class cRecordingsFolderInfo::cFolderInfoIntern:public cListObject { +private: + cFolderInfoIntern *_parent; + cList<cFolderInfoIntern> *_subFolders; + + cString _name; + time_t _latest; + int _count; + cString _latestFileName; + + void UpdateData(cRecording *Recording); + cFolderInfoIntern *FindSubFolder(const char *Name) const; + +public: + cFolderInfoIntern(cFolderInfoIntern *Parent, const char *Name); + virtual ~cFolderInfoIntern(void); + + // split Name and find folder-info in tree + // if "Add", missing folders are created + cFolderInfoIntern *Find(const char *Name, bool Add); + + void Add(cRecording *Recording); + + cRecordingsFolderInfo::cFolderInfo *GetInfo(void) const; + + cString FullName(void) const; + cString ToText(void) const; + cString DebugOutput(void) const; +}; + + +cRecordingsFolderInfo::cFolderInfo::cFolderInfo(const char *Name, const char *FullName, time_t Latest, int Count, const char *LatestFileName) +{ + this->Name = Name; + this->FullName = FullName; + this->Latest = Latest; + this->Count = Count; + this->LatestFileName= LatestFileName; +} + + +cRecordingsFolderInfo::cRecordingsFolderInfo(cRecordings &Recordings) +:_recordings(Recordings) +,_root(NULL) +{ + Rebuild(); +} + +cRecordingsFolderInfo::~cRecordingsFolderInfo(void) +{ + delete _root; + _root = NULL; +} + +void cRecordingsFolderInfo::Rebuild(void) +{ + delete _root; + _root = new cFolderInfoIntern(NULL, ""); + + cThreadLock RecordingsLock(&_recordings); + // re-get state with lock held + _recordings.StateChanged(_recState); + cFolderInfoIntern *info; + cString folder; + for (cRecording *rec = _recordings.First(); rec; rec = _recordings.Next(rec)) { +#if APIVERSNUM < 20102 + //cRecording::Folder() first available since VDR 2.1.2 + const char *recName = rec->Name(); + if (const char *s = strrchr(recName, FOLDERDELIMCHAR)) { + folder = recName; + folder.Truncate(s - recName); + } + else + folder = ""; +#else + folder = rec->Folder(); +#endif + info = _root->Find(*folder, true); + info->Add(rec); + } +} + +cRecordingsFolderInfo::cFolderInfo *cRecordingsFolderInfo::Get(const char *Folder) +{ + cMutexLock lock(&_rootLock); + + if (_recordings.StateChanged(_recState) || (_root == NULL)) + Rebuild(); + + cFolderInfoIntern *info = _root->Find(Folder, false); + if (info == NULL) + return NULL; + + return info->GetInfo(); +} + +cString cRecordingsFolderInfo::DebugOutput(void) const +{ + cMutexLock lock(&_rootLock); + + return _root->DebugOutput(); +} + + +cRecordingsFolderInfo::cFolderInfoIntern::cFolderInfoIntern(cFolderInfoIntern *Parent, const char *Name) +:_parent(Parent) +,_name(Name) +,_latest(0) +,_count(0) +,_latestFileName("") +{ + _subFolders = new cList<cFolderInfoIntern>(); +} + +cRecordingsFolderInfo::cFolderInfoIntern::~cFolderInfoIntern(void) +{ + delete _subFolders; + _subFolders = NULL; +} + +cRecordingsFolderInfo::cFolderInfoIntern *cRecordingsFolderInfo::cFolderInfoIntern::Find(const char *Name, bool Add) +{ + cFolderInfoIntern *info = NULL; + if (Add) + info = this; + + if (Name && *Name) { + static char delim[2] = { FOLDERDELIMCHAR, 0 }; + char *strtok_next; + cFolderInfoIntern *next; + char *folder = strdup(Name); + info = this; + for (char *t = strtok_r(folder, delim, &strtok_next); t; t = strtok_r(NULL, delim, &strtok_next)) { + next = info->FindSubFolder(t); + if (next == NULL) { + if (!Add) { + info = NULL; + break; + } + + next = new cFolderInfoIntern(info, t); + info->_subFolders->Add(next); + } + info = next; + } + free(folder); + } + + return info; +} + +void cRecordingsFolderInfo::cFolderInfoIntern::UpdateData(cRecording *Recording) +{ + // count every recording + _count++; + + // update date if newer + time_t recdate = Recording->Start(); + if (_latest < recdate) { + _latest = recdate; + _latestFileName = Recording->FileName(); + } +} + +cRecordingsFolderInfo::cFolderInfoIntern *cRecordingsFolderInfo::cFolderInfoIntern::FindSubFolder(const char *Name) const +{ + for (cRecordingsFolderInfo::cFolderInfoIntern *info = _subFolders->First(); info; info = _subFolders->Next(info)) { + if (strcmp(info->_name, Name) == 0) + return info; + } + return NULL; +} + +void cRecordingsFolderInfo::cFolderInfoIntern::Add(cRecording *Recording) +{ + if (Recording == NULL) + return; + + // update this and all parent folders + for (cFolderInfoIntern *p = this; p; p = p->_parent) + p->UpdateData(Recording); +} + +cRecordingsFolderInfo::cFolderInfo *cRecordingsFolderInfo::cFolderInfoIntern::GetInfo(void) const +{ + return new cRecordingsFolderInfo::cFolderInfo(*_name, *FullName(), _latest, _count, *_latestFileName); +} + +cString cRecordingsFolderInfo::cFolderInfoIntern::FullName(void) const +{ + static char delim[2] = { FOLDERDELIMCHAR, 0 }; + + cString name = _name; + for (cFolderInfoIntern *p = _parent; p; p = p->_parent) { + // don't add FOLDERDELIMCHAR at start of FullName + if (p->_parent == NULL) + break; + name = cString::sprintf("%s%s%s", *p->_name, delim, *name); + } + return name; +} + +cString cRecordingsFolderInfo::cFolderInfoIntern::ToText(void) const +{ + return cString::sprintf("%s (%d recordings, latest: %s)\n", *FullName(), _count, *ShortDateString(_latest)); +} + +cString cRecordingsFolderInfo::cFolderInfoIntern::DebugOutput(void) const +{ + cString output = ToText(); + for (cFolderInfoIntern *i = _subFolders->First(); i; i = _subFolders->Next(i)) + output = cString::sprintf("%s%s", *output, *i->DebugOutput()); + return output; +}
\ No newline at end of file diff --git a/extensions/recfolderinfo.h b/extensions/recfolderinfo.h new file mode 100644 index 0000000..8148ea7 --- /dev/null +++ b/extensions/recfolderinfo.h @@ -0,0 +1,43 @@ +#ifndef __RECFOLDERINFO_H +#define __RECFOLDERINFO_H + +#include <vdr/recording.h> + + +class cRecordingsFolderInfo { +public: + class cFolderInfoIntern; + +private: + cRecordings &_recordings; + int _recState; + cFolderInfoIntern *_root; + mutable cMutex _rootLock; + + void Rebuild(void); + +public: + class cFolderInfo { + public: + cString Name; + cString FullName; + time_t Latest; + int Count; + cString LatestFileName; + + cFolderInfo(const char *Name, const char *FullName, time_t Latest, int Count, const char *LatestFileName); + }; + + cRecordingsFolderInfo(cRecordings &Recordings); + ~cRecordingsFolderInfo(void); + + // caller must delete the cInfo object! + // returns NULL if folder doesn't exists + // will rebuild tree if recordings' state has changed + // is thread-safe + cFolderInfo *Get(const char *Folder); + + cString DebugOutput(void) const; +}; + +#endif // __RECFOLDERINFO_H
\ No newline at end of file diff --git a/extensions/scrapmanager.c b/extensions/scrapmanager.c new file mode 100644 index 0000000..8452d75 --- /dev/null +++ b/extensions/scrapmanager.c @@ -0,0 +1,359 @@ +#include "scrapmanager.h"
+#include "../coreengine/definitions.h"
+#include "helpers.h"
+
+cPlugin *cScrapManager::pScraper = NULL;
+
+cScrapManager::cScrapManager(void) {
+ if (!pScraper) {
+ pScraper = GetScraperPlugin();
+ }
+ movie = NULL;
+ series = NULL;
+}
+
+cScrapManager::~cScrapManager(void) {
+ delete movie;
+ delete series;
+}
+
+bool cScrapManager::LoadFullScrapInfo(const cEvent *event, const cRecording *recording) {
+ if (!pScraper) {
+ return false;
+ }
+ delete movie;
+ movie = NULL;
+ delete series;
+ series = NULL;
+
+ ScraperGetEventType getType;
+ getType.event = event;
+ getType.recording = recording;
+ if (!pScraper->Service("GetEventType", &getType)) {
+ return false;
+ }
+ if (getType.type == tMovie) {
+ movie = new cMovie();
+ movie->movieId = getType.movieId;
+ pScraper->Service("GetMovie", movie);
+ return true;
+ } else if (getType.type == tSeries) {
+ series = new cSeries();
+ series->seriesId = getType.seriesId;
+ series->episodeId = getType.episodeId;
+ pScraper->Service("GetSeries", series);
+ return true;
+ }
+ return false;
+}
+
+void cScrapManager::SetFullScrapInfo(skindesignerapi::cTokenContainer *tk, int actorsIndex) {
+ if (series) {
+ tk->AddIntToken((int)eScraperIT::ismovie, 0);
+ tk->AddIntToken((int)eScraperIT::isseries, 1);
+ SetSeries(tk, actorsIndex);
+ } else if (movie) {
+ tk->AddIntToken((int)eScraperIT::ismovie, 1);
+ tk->AddIntToken((int)eScraperIT::isseries, 0);
+ SetMovie(tk, actorsIndex);
+ } else {
+ tk->AddIntToken((int)eScraperIT::ismovie, 0);
+ tk->AddIntToken((int)eScraperIT::isseries, 0);
+ }
+}
+
+
+int cScrapManager::NumActors(void) {
+ if (series) {
+ return series->actors.size();
+ } else if (movie) {
+ return movie->actors.size();
+ }
+ return 0;
+}
+
+void cScrapManager::SetHeaderScrapInfo(skindesignerapi::cTokenContainer *tk) {
+ if (series) {
+ tk->AddIntToken((int)eScraperHeaderIT::ismovie, 0);
+ tk->AddIntToken((int)eScraperHeaderIT::isseries, 1);
+ vector<cTvMedia>::iterator poster = series->posters.begin();
+ if (poster != series->posters.end()) {
+ tk->AddIntToken((int)eScraperHeaderIT::posteravailable, true);
+ tk->AddIntToken((int)eScraperHeaderIT::posterwidth, (*poster).width);
+ tk->AddIntToken((int)eScraperHeaderIT::posterheight, (*poster).height);
+ tk->AddStringToken((int)eScraperHeaderST::posterpath, (*poster).path.c_str());
+ }
+ vector<cTvMedia>::iterator banner = series->banners.begin();
+ if (banner != series->banners.end()) {
+ tk->AddIntToken((int)eScraperHeaderIT::banneravailable, true);
+ tk->AddIntToken((int)eScraperHeaderIT::bannerwidth, (*banner).width);
+ tk->AddIntToken((int)eScraperHeaderIT::bannerheight, (*banner).height);
+ tk->AddStringToken((int)eScraperHeaderST::bannerpath, (*banner).path.c_str());
+ }
+ } else if (movie) {
+ tk->AddIntToken((int)eScraperHeaderIT::ismovie, 1);
+ tk->AddIntToken((int)eScraperHeaderIT::isseries, 0);
+ tk->AddIntToken((int)eScraperHeaderIT::posteravailable, true);
+ tk->AddIntToken((int)eScraperHeaderIT::banneravailable, false);
+ tk->AddIntToken((int)eScraperHeaderIT::posterwidth, movie->poster.width);
+ tk->AddIntToken((int)eScraperHeaderIT::posterheight, movie->poster.height);
+ tk->AddStringToken((int)eScraperHeaderST::posterpath, movie->poster.path.c_str());
+ } else {
+ tk->AddIntToken((int)eScraperHeaderIT::ismovie, 0);
+ tk->AddIntToken((int)eScraperHeaderIT::isseries, 0);
+ }
+}
+
+void cScrapManager::SetScraperPosterBanner(skindesignerapi::cTokenContainer *tk) {
+ if (movie) {
+ tk->AddIntToken((int)eCeMenuSchedulesIT::hasposter, 1);
+ tk->AddStringToken((int)eCeMenuSchedulesST::posterpath, movie->poster.path.c_str());
+ tk->AddIntToken((int)eCeMenuSchedulesIT::posterwidth, movie->poster.width);
+ tk->AddIntToken((int)eCeMenuSchedulesIT::posterheight, movie->poster.height);
+ } else if (series) {
+ vector<cTvMedia>::iterator poster = series->posters.begin();
+ if (poster != series->posters.end()) {
+ tk->AddIntToken((int)eCeMenuSchedulesIT::hasposter, 1);
+ tk->AddIntToken((int)eCeMenuSchedulesIT::posterwidth, (*poster).width);
+ tk->AddIntToken((int)eCeMenuSchedulesIT::posterheight, (*poster).height);
+ tk->AddStringToken((int)eCeMenuSchedulesST::posterpath, (*poster).path.c_str());
+ }
+ vector<cTvMedia>::iterator banner = series->banners.begin();
+ if (banner != series->banners.end()) {
+ tk->AddIntToken((int)eCeMenuSchedulesIT::hasbanner, 1);
+ tk->AddIntToken((int)eCeMenuSchedulesIT::bannerwidth, (*banner).width);
+ tk->AddIntToken((int)eCeMenuSchedulesIT::bannerheight, (*banner).height);
+ tk->AddStringToken((int)eCeMenuSchedulesST::bannerpath, (*banner).path.c_str());
+ }
+ }
+}
+
+void cScrapManager::SetScraperRecordingPoster(skindesignerapi::cTokenContainer *tk, const cRecording *recording, bool isListElement) {
+ if (!pScraper) {
+ return;
+ }
+ ScraperGetPosterThumb call;
+ call.event = NULL;
+ call.recording = recording;
+ if (pScraper->Service("GetPosterThumb", &call)) {
+ if (isListElement) {
+ tk->AddIntToken((int)eLeMenuRecordingsIT::hasposterthumbnail, FileExists(call.poster.path));
+ tk->AddIntToken((int)eLeMenuRecordingsIT::thumbnailwidth, call.poster.width);
+ tk->AddIntToken((int)eLeMenuRecordingsIT::thumbnailheight, call.poster.height);
+ tk->AddStringToken((int)eLeMenuRecordingsST::thumbnailpath, call.poster.path.c_str());
+ } else {
+ tk->AddIntToken((int)eCeMenuRecordingsIT::hasposterthumbnail, FileExists(call.poster.path));
+ tk->AddIntToken((int)eCeMenuRecordingsIT::thumbnailwidth, call.poster.width);
+ tk->AddIntToken((int)eCeMenuRecordingsIT::thumbnailheight, call.poster.height);
+ tk->AddStringToken((int)eCeMenuRecordingsST::thumbnailpath, call.poster.path.c_str());
+ }
+ }
+
+ ScraperGetPoster call2;
+ call2.event = NULL;
+ call2.recording = recording;
+ if (pScraper->Service("GetPoster", &call2)) {
+ if (isListElement) {
+ tk->AddIntToken((int)eLeMenuRecordingsIT::hasposter, FileExists(call2.poster.path));
+ tk->AddIntToken((int)eLeMenuRecordingsIT::posterwidth, call2.poster.width);
+ tk->AddIntToken((int)eLeMenuRecordingsIT::posterheight, call2.poster.height);
+ tk->AddStringToken((int)eLeMenuRecordingsST::posterpath, call2.poster.path.c_str());
+ } else {
+ tk->AddIntToken((int)eCeMenuRecordingsIT::hasposter, FileExists(call2.poster.path));
+ tk->AddIntToken((int)eCeMenuRecordingsIT::posterwidth, call2.poster.width);
+ tk->AddIntToken((int)eCeMenuRecordingsIT::posterheight, call2.poster.height);
+ tk->AddStringToken((int)eCeMenuRecordingsST::posterpath, call2.poster.path.c_str());
+ }
+ }
+}
+
+cPlugin *cScrapManager::GetScraperPlugin(void) {
+ static cPlugin *pScraper = cPluginManager::GetPlugin("scraper2vdr");
+ if( !pScraper ) // if it doesn't exit, try tvscraper
+ pScraper = cPluginManager::GetPlugin("tvscraper");
+ return pScraper;
+}
+
+void cScrapManager::SetMovie(skindesignerapi::cTokenContainer *tk, int actorsIndex) {
+ tk->AddStringToken((int)eScraperST::movietitle, movie->title.c_str());
+ tk->AddStringToken((int)eScraperST::movieoriginalTitle, movie->originalTitle.c_str());
+ tk->AddStringToken((int)eScraperST::movietagline, movie->tagline.c_str());
+ tk->AddStringToken((int)eScraperST::movieoverview, movie->overview.c_str());
+ tk->AddStringToken((int)eScraperST::moviegenres, movie->genres.c_str());
+ tk->AddStringToken((int)eScraperST::moviehomepage, movie->homepage.c_str());
+ tk->AddStringToken((int)eScraperST::moviereleasedate, movie->releaseDate.c_str());
+ tk->AddStringToken((int)eScraperST::moviepopularity, *cString::sprintf("%f", movie->popularity));
+ tk->AddStringToken((int)eScraperST::movievoteaverage, *cString::sprintf("%f", movie->voteAverage));
+ tk->AddStringToken((int)eScraperST::posterpath, movie->poster.path.c_str());
+ tk->AddStringToken((int)eScraperST::fanartpath, movie->fanart.path.c_str());
+ tk->AddStringToken((int)eScraperST::collectionposterpath, movie->collectionPoster.path.c_str());
+ tk->AddStringToken((int)eScraperST::collectionfanartpath, movie->collectionFanart.path.c_str());
+ tk->AddIntToken((int)eScraperIT::movieadult, movie->adult);
+ tk->AddIntToken((int)eScraperIT::moviebudget, movie->budget);
+ tk->AddIntToken((int)eScraperIT::movierevenue, movie->revenue);
+ tk->AddIntToken((int)eScraperIT::movieruntime, movie->runtime);
+ tk->AddIntToken((int)eScraperIT::posterwidth, movie->poster.width);
+ tk->AddIntToken((int)eScraperIT::posterheight, movie->poster.height);
+ tk->AddIntToken((int)eScraperIT::fanartwidth, movie->fanart.width);
+ tk->AddIntToken((int)eScraperIT::fanartheight, movie->fanart.height);
+ tk->AddIntToken((int)eScraperIT::collectionposterwidth, movie->collectionPoster.width);
+ tk->AddIntToken((int)eScraperIT::collectionposterheight, movie->collectionPoster.height);
+ tk->AddIntToken((int)eScraperIT::collectionfanartwidth, movie->collectionFanart.width);
+ tk->AddIntToken((int)eScraperIT::collectionfanartheight, movie->collectionFanart.height);
+ if (movie->collectionPoster.path.size() > 0)
+ tk->AddIntToken((int)eScraperIT::movieiscollection, 1);
+ int i=0;
+ for (vector<cActor>::iterator act = movie->actors.begin(); act != movie->actors.end(); act++) {
+ tk->AddLoopToken(actorsIndex, i, (int)eScraperLT::name, (*act).name.c_str());
+ tk->AddLoopToken(actorsIndex, i, (int)eScraperLT::role, (*act).role.c_str());
+ tk->AddLoopToken(actorsIndex, i, (int)eScraperLT::thumb, (*act).actorThumb.path.c_str());
+ tk->AddLoopToken(actorsIndex, i, (int)eScraperLT::thumbwidth, *cString::sprintf("%d", (*act).actorThumb.width));
+ tk->AddLoopToken(actorsIndex, i, (int)eScraperLT::thumbheight, *cString::sprintf("%d", (*act).actorThumb.height));
+ i++;
+ }
+}
+
+void cScrapManager::SetSeries(skindesignerapi::cTokenContainer *tk, int actorsIndex) {
+ //Series Basics
+ tk->AddStringToken((int)eScraperST::seriesname, series->name.c_str());
+ tk->AddStringToken((int)eScraperST::seriesoverview, series->overview.c_str());
+ tk->AddStringToken((int)eScraperST::seriesfirstaired, series->firstAired.c_str());
+ tk->AddStringToken((int)eScraperST::seriesnetwork, series->network.c_str());
+ tk->AddStringToken((int)eScraperST::seriesgenre, series->genre.c_str());
+ tk->AddStringToken((int)eScraperST::seriesrating, *cString::sprintf("%f", series->rating));
+ tk->AddStringToken((int)eScraperST::seriesstatus, series->status.c_str());
+ //Episode Information
+ tk->AddIntToken((int)eScraperIT::episodenumber, series->episode.number);
+ tk->AddIntToken((int)eScraperIT::episodeseason, series->episode.season);
+ tk->AddStringToken((int)eScraperST::episodetitle, series->episode.name.c_str());
+ tk->AddStringToken((int)eScraperST::episodefirstaired, series->episode.firstAired.c_str());
+ tk->AddStringToken((int)eScraperST::episodegueststars, series->episode.guestStars.c_str());
+ tk->AddStringToken((int)eScraperST::episodeoverview, series->episode.overview.c_str());
+ tk->AddStringToken((int)eScraperST::episoderating, *cString::sprintf("%f", series->episode.rating));
+ tk->AddIntToken((int)eScraperIT::episodeimagewidth, series->episode.episodeImage.width);
+ tk->AddIntToken((int)eScraperIT::episodeimageheight, series->episode.episodeImage.height);
+ tk->AddStringToken((int)eScraperST::episodeimagepath, series->episode.episodeImage.path.c_str());
+ //Seasonposter
+ tk->AddIntToken((int)eScraperIT::seasonposterwidth, series->seasonPoster.width);
+ tk->AddIntToken((int)eScraperIT::seasonposterheight, series->seasonPoster.height);
+ tk->AddStringToken((int)eScraperST::seasonposterpath, series->seasonPoster.path.c_str());
+ //Posters
+ int indexInt = (int)eScraperIT::seriesposter1width;
+ int indexStr = (int)eScraperST::seriesposter1path;
+ for(vector<cTvMedia>::iterator poster = series->posters.begin(); poster != series->posters.end(); poster++) {
+ tk->AddIntToken(indexInt, (*poster).width);
+ tk->AddIntToken(indexInt+1, (*poster).height);
+ tk->AddStringToken(indexStr, (*poster).path.c_str());
+ indexInt += 2;
+ indexStr++;
+ }
+ //Banners
+ indexInt = (int)eScraperIT::seriesbanner1width;
+ indexStr = (int)eScraperST::seriesbanner1path;
+ for(vector<cTvMedia>::iterator banner = series->banners.begin(); banner != series->banners.end(); banner++) {
+ tk->AddIntToken(indexInt, (*banner).width);
+ tk->AddIntToken(indexInt+1, (*banner).height);
+ tk->AddStringToken(indexStr, (*banner).path.c_str());
+ indexInt += 2;
+ indexStr++;
+ }
+ //Fanarts
+ indexInt = (int)eScraperIT::seriesfanart1width;
+ indexStr = (int)eScraperST::seriesfanart1path;
+ for(vector<cTvMedia>::iterator fanart = series->fanarts.begin(); fanart != series->fanarts.end(); fanart++) {
+ tk->AddIntToken(indexInt, (*fanart).width);
+ tk->AddIntToken(indexInt+1, (*fanart).height);
+ tk->AddStringToken(indexStr, (*fanart).path.c_str());
+ indexInt += 2;
+ indexStr++;
+ }
+ //Actors
+ int i=0;
+ for (vector<cActor>::iterator act = series->actors.begin(); act != series->actors.end(); act++) {
+ tk->AddLoopToken(actorsIndex, i, (int)eScraperLT::name, (*act).name.c_str());
+ tk->AddLoopToken(actorsIndex, i, (int)eScraperLT::role, (*act).role.c_str());
+ tk->AddLoopToken(actorsIndex, i, (int)eScraperLT::thumb, (*act).actorThumb.path.c_str());
+ tk->AddLoopToken(actorsIndex, i, (int)eScraperLT::thumbwidth, *cString::sprintf("%d", (*act).actorThumb.width));
+ tk->AddLoopToken(actorsIndex, i, (int)eScraperLT::thumbheight, *cString::sprintf("%d", (*act).actorThumb.height));
+ i++;
+ }
+}
+
+void cScrapManager::RecPoster(const cRecording *rec, int &posterWidth, int &posterHeight, string &path, bool &hasPoster) {
+ if (!pScraper) {
+ return;
+ }
+ ScraperGetPoster callPoster;
+ callPoster.event = NULL;
+ callPoster.recording = rec;
+ if (pScraper->Service("GetPoster", &callPoster)) {
+ posterWidth = callPoster.poster.width;
+ posterHeight = callPoster.poster.height;
+ path = callPoster.poster.path;
+ if (path.size() > 0)
+ hasPoster = true;
+ }
+}
+
+void cScrapManager::SetPosterBanner(skindesignerapi::cTokenContainer *tk, const cEvent *event, const cRecording *recording) {
+ if (!pScraper) {
+ return;
+ }
+ int mediaWidth = 0;
+ int mediaHeight = 0;
+ string mediaPath = "";
+ bool isBanner = false;
+ int posterWidth = 0;
+ int posterHeight = 0;
+ string posterPath = "";
+ bool hasPoster = false;
+ int bannerWidth = 0;
+ int bannerHeight = 0;
+ string bannerPath = "";
+ bool hasBanner = false;
+
+ ScraperGetPosterBannerV2 call;
+ call.event = event;
+ call.recording = recording;
+ if (pScraper->Service("GetPosterBannerV2", &call)) {
+ if ((call.type == tSeries) && call.banner.path.size() > 0) {
+ mediaWidth = call.banner.width;
+ mediaHeight = call.banner.height;
+ mediaPath = call.banner.path;
+ isBanner = true;
+ bannerWidth = mediaWidth;
+ bannerHeight = mediaHeight;
+ bannerPath = mediaPath;
+ hasBanner = true;
+ ScraperGetPoster callPoster;
+ callPoster.event = event;
+ callPoster.recording = recording;
+ if (pScraper->Service("GetPoster", &callPoster)) {
+ posterWidth = callPoster.poster.width;
+ posterHeight = callPoster.poster.height;
+ posterPath = callPoster.poster.path;
+ hasPoster = true;
+ }
+ } else if (call.type == tMovie && call.poster.path.size() > 0 && call.poster.height > 0) {
+ mediaWidth = call.poster.width;
+ mediaHeight = call.poster.height;
+ mediaPath = call.poster.path;
+ posterWidth = call.poster.width;
+ posterHeight = call.poster.height;
+ posterPath = call.poster.path;
+ hasPoster = true;
+ }
+ }
+ tk->AddIntToken((int)eScraperPosterBannerIT::mediawidth, mediaWidth);
+ tk->AddIntToken((int)eScraperPosterBannerIT::mediaheight, mediaHeight);
+ tk->AddIntToken((int)eScraperPosterBannerIT::isbanner, isBanner);
+ tk->AddStringToken((int)eScraperPosterBannerST::mediapath, mediaPath.c_str());
+ tk->AddIntToken((int)eScraperPosterBannerIT::posterwidth, posterWidth);
+ tk->AddIntToken((int)eScraperPosterBannerIT::posterheight, posterHeight);
+ tk->AddStringToken((int)eScraperPosterBannerST::posterpath, posterPath.c_str());
+ tk->AddIntToken((int)eScraperPosterBannerIT::hasposter, hasPoster);
+ tk->AddIntToken((int)eScraperPosterBannerIT::bannerwidth, bannerWidth);
+ tk->AddIntToken((int)eScraperPosterBannerIT::bannerheight, bannerHeight);
+ tk->AddStringToken((int)eScraperPosterBannerST::bannerpath, bannerPath.c_str());
+ tk->AddIntToken((int)eScraperPosterBannerIT::hasbanner, hasBanner);
+}
\ No newline at end of file diff --git a/extensions/scrapmanager.h b/extensions/scrapmanager.h new file mode 100644 index 0000000..0e026fc --- /dev/null +++ b/extensions/scrapmanager.h @@ -0,0 +1,28 @@ +#ifndef __SCRAPMANAGER_H
+#define __SCRAPMANAGER_H
+
+#include "../services/scraper2vdr.h"
+#include "../libskindesignerapi/tokencontainer.h"
+
+class cScrapManager {
+private:
+ static cPlugin *pScraper;
+ cMovie *movie;
+ cSeries *series;
+ cPlugin *GetScraperPlugin(void);
+ void SetMovie(skindesignerapi::cTokenContainer *tk, int actorsIndex);
+ void SetSeries(skindesignerapi::cTokenContainer *tk, int actorsIndex);
+protected:
+ bool LoadFullScrapInfo(const cEvent *event, const cRecording *recording);
+ void SetFullScrapInfo(skindesignerapi::cTokenContainer *tk, int actorsIndex);
+ int NumActors(void);
+ void SetHeaderScrapInfo(skindesignerapi::cTokenContainer *tk);
+ void SetScraperPosterBanner(skindesignerapi::cTokenContainer *tk);
+ void SetScraperRecordingPoster(skindesignerapi::cTokenContainer *tk, const cRecording *recording, bool isListElement);
+ void RecPoster(const cRecording *rec, int &posterWidth, int &posterHeight, string &path, bool &hasPoster);
+ void SetPosterBanner(skindesignerapi::cTokenContainer *tk, const cEvent *event, const cRecording *recording);
+public:
+ cScrapManager(void);
+ virtual ~cScrapManager(void);
+};
+#endif //__SCRAPMANAGER_H
\ No newline at end of file diff --git a/extensions/skinrepo.c b/extensions/skinrepo.c new file mode 100644 index 0000000..16919e8 --- /dev/null +++ b/extensions/skinrepo.c @@ -0,0 +1,375 @@ +#include <iostream> +#include <fstream> +#include "skinrepo.h" +#include "helpers.h" + +using namespace std; + +// --- cSkinRepo ------------------------------------------------------------- + +cSkinRepo::cSkinRepo(void) { + name = ""; + repoType = rtUndefined; + action = eaUndefined; + url = ""; + author = "unknown"; + minSDVersion = "0.0.1"; + command = ""; + command2 = ""; + tempfile = ""; + result = -1; + skinPath = ""; + themesPath = ""; +} + +cSkinRepo::~cSkinRepo() { +} + +bool cSkinRepo::Valid(void) { + if (!name.size()) + return false; + if (repoType == rtUndefined) + return false; + if (!url.size()) + return false; + return true; +} + +void cSkinRepo::Install(string path, string themesPath) { + if (Running()) + return; + action = eaInstall; + this->skinPath = path + name; + this->themesPath = themesPath; + if (repoType == rtGit) { + + command = *cString::sprintf("git clone --depth=1 --progress %s %s", url.c_str(), skinPath.c_str()); + tempfile = *cString::sprintf("gitclone_%s_%ld.out", name.c_str(), time(0)); + + dsyslog("skindesigner: installing skin from Git, command: %s, logfile: %s", command.c_str(), tempfile.c_str()); + + Start(); + + } else if (repoType == rtZipUrl) { + + size_t hit = url.find_last_of('/'); + if (hit == string::npos) + return; + string filename = url.substr(hit+1); + + command = *cString::sprintf("wget -P /tmp/ %s", url.c_str()); + command2 = *cString::sprintf("unzip /tmp/%s -d %s", filename.c_str(), path.c_str()); + + dsyslog("skindesigner: installing skin from Zip, command: %s, %s", command.c_str(), command2.c_str()); + + Start(); + } +} + +void cSkinRepo::Update(string path) { + if (Running()) + return; + action = eaUpdate; + this->skinPath = path + name; + if (repoType == rtGit) { + + command = *cString::sprintf("cd %s; git pull", skinPath.c_str()); + tempfile = *cString::sprintf("gitpull_%s_%ld.out", name.c_str(), time(0)); + + dsyslog("skindesigner: updating skin from Git, command: %s, logfile: /tmp/%s", command.c_str(), tempfile.c_str()); + + Start(); + + } else if (repoType == rtZipUrl) { + + //TODO + + } +} + +void cSkinRepo::Action(void) { + if (command.size() < 1) + return; + if (tempfile.size() > 0) { + command = *cString::sprintf("%s > /tmp/%s 2>&1", command.c_str(), tempfile.c_str()); + } + + result = system (command.c_str()); + + if (result == 0 && command2.size() > 0) { + result = system (command2.c_str()); + } + + if (result == 0) { + if (action == eaInstall) + CreateThemeFiles(); + dsyslog("skindesigner: %s successfully executed", command.c_str()); + } else { + esyslog("skindesigner: ERROR executing %s", command.c_str()); + } +} + +void cSkinRepo::CreateThemeFiles(void) { + string availableThemesPath = skinPath + "/themes/"; + DIR *folder = NULL; + struct dirent *dirEntry; + folder = opendir(availableThemesPath.c_str()); + if (!folder) { + return; + } + vector<string> skinThemes; + while (dirEntry = readdir(folder)) { + string dirEntryName = dirEntry->d_name; + int dirEntryType = dirEntry->d_type; + if (!dirEntryName.compare(".") || !dirEntryName.compare("..") || dirEntryType != DT_DIR) + continue; + skinThemes.push_back(dirEntryName); + } + for (vector<string>::iterator it = skinThemes.begin(); it != skinThemes.end(); it++) { + string themeName = *it; + string themeFileName = themesPath; + themeFileName += name + "-" + themeName + ".theme"; + if (FileExists(themeFileName)) { + continue; + } + ofstream themeFile (themeFileName.c_str()); + if (themeFile.is_open()) { + themeFile << "Description = "; + themeFile << themeName << "\n"; + themeFile.close(); + } + } +} + +bool cSkinRepo::SuccessfullyUpdated(void) { + string logfilePath = "/tmp/" + tempfile; + bool updated = true; + string line; + ifstream logfile(logfilePath.c_str()); + if (logfile.is_open()) { + while ( getline (logfile, line) ) { + if (line.find("up-to-date") != string::npos) { + updated = false; + break; + } + } + logfile.close(); + } + return updated; +} + +void cSkinRepo::Debug() { + string strRepoType = "Undefined"; + if (repoType == rtGit) + strRepoType = "Git"; + else if (repoType == rtZipUrl) + strRepoType = "ZipUrl"; + dsyslog("skindesigner: --- skinrepo %s, Type %s ---", name.c_str(), strRepoType.c_str()); + dsyslog("skindesigner: url %s", url.c_str()); + dsyslog("skindesigner: author %s", author.c_str()); + dsyslog("skindesigner: minimum Skindesigner Version required %s", minSDVersion.c_str()); + if (specialFonts.size() > 0) { + for (vector<string>::iterator it = specialFonts.begin(); it != specialFonts.end(); it++) { + dsyslog("skindesigner: special font %s", (*it).c_str()); + } + } + if (supportedPlugins.size() > 0) { + for (vector<string>::iterator it = supportedPlugins.begin(); it != supportedPlugins.end(); it++) { + dsyslog("skindesigner: supported plugin %s", (*it).c_str()); + } + } + if (screenshots.size() > 0) { + for (vector<pair<string,string> >::iterator it = screenshots.begin(); it != screenshots.end(); it++) { + string desc = (it->first).c_str(); + string url = (it->second).c_str(); + dsyslog("skindesigner: screenshot \"%s\", url %s", desc.c_str(), url.c_str()); + } + } +} + +// --- cSkinRepos ------------------------------------------------------------- + +cSkinRepos::cSkinRepos(void) { + skinRepoUrl = "https://github.com/louisbraun/skinrepository.git"; + repoFolder = "skinrepositories/"; +} + +cSkinRepos::~cSkinRepos() { + for (vector<cSkinRepo*>::iterator it = repos.begin(); it != repos.end(); it++) { + delete (*it); + } +} + +void cSkinRepos::Init(string path) { + string repoPath = path + repoFolder; + if (FolderExists(repoPath)) { + PullRepoGit(repoPath); + } else { + InitRepoGit(repoPath); + } +} + +void cSkinRepos::Read(string path) { + string repoPath = path + repoFolder; + DIR *folder = NULL; + struct dirent *dirEntry; + folder = opendir(repoPath.c_str()); + if (!folder) { + esyslog("skindesigner: no skinrepository folder available in %s", repoPath.c_str()); + return; + } + while (dirEntry = readdir(folder)) { + string fileName = dirEntry->d_name; + if (!fileName.compare(".") || !fileName.compare("..") || !fileName.compare(".git")) + continue; + string filePath = repoPath + fileName; + if (! ReadXMLFile(filePath.c_str(), false) ) { + esyslog("skindesigner: error reading skinrepo %s", filePath.c_str()); + continue; + } + if (! SetDocument() ) + continue; + if (!ParseRepository()) + esyslog("skindesigner: error parsing skinrepository %s", filePath.c_str()); + DeleteDocument(); + } + +} + +cSkinRepo *cSkinRepos::GetRepo(string name) { + for (vector<cSkinRepo*>::iterator it = repos.begin(); it != repos.end(); it++) { + cSkinRepo *repo = (*it); + if (!name.compare(repo->Name())) + return repo; + } + return NULL; +} + +cSkinRepo *cSkinRepos::GetNextRepo(void) { + if (repoIt == repos.end()) + return NULL; + cSkinRepo *repo = *repoIt; + repoIt++; + return repo; +} + + +void cSkinRepos::Debug(void) { + for (vector<cSkinRepo*>::iterator it = repos.begin(); it != repos.end(); it++) { + (*it)->Debug(); + } +} + +bool cSkinRepos::ParseRepository(void) { + if (!LevelDown()) + return false; + + cSkinRepo *repo = new cSkinRepo(); + + do { + string value = ""; + if (CheckNodeName("name")) { + if (GetNodeValue(value)) { + repo->SetName(value); + } + } else if (CheckNodeName("type")) { + if (GetNodeValue(value)) { + eRepoType repoType = rtUndefined; + if (!value.compare("git")) + repoType = rtGit; + else if (!value.compare("zip")) + repoType = rtZipUrl; + repo->SetRepoType(repoType); + } + } else if (CheckNodeName("url")) { + if (GetNodeValue(value)) { + repo->SetUrl(value); + } + } else if (CheckNodeName("author")) { + if (GetNodeValue(value)) { + repo->SetAuthor(value); + } + } else if (CheckNodeName("minimumskindesignerversion")) { + if (GetNodeValue(value)) { + repo->SetMinSDVersion(value); + } + } else if (CheckNodeName("specialfonts")) { + if (!LevelDown()) + continue; + do { + if (CheckNodeName("font")) { + if (GetNodeValue(value)) { + repo->SetSpecialFont(value); + } + } + } while (NextNode()); + LevelUp(); + } else if (CheckNodeName("supportedplugins")) { + if (!LevelDown()) + continue; + do { + if (CheckNodeName("plugin")) { + if (GetNodeValue(value)) { + repo->SetSupportedPlugin(value); + } + } + } while (NextNode()); + LevelUp(); + } else if (CheckNodeName("screenshots")) { + if (!LevelDown()) { + continue; + } + do { + if (CheckNodeName("screenshot")) { + if (!LevelDown()) { + continue; + } + string desc = ""; + string url = ""; + do { + if (CheckNodeName("description")) { + GetNodeValue(desc); + } else if (CheckNodeName("url")) { + GetNodeValue(url); + } + } while (NextNode()); + LevelUp(); + if (desc.size() && url.size()) + repo->SetScreenshot(desc, url); + } + } while (NextNode()); + LevelUp(); + } + } while (NextNode()); + LevelUp(); + if (repo->Valid()) { + repos.push_back(repo); + return true; + } + return false; +} + +void cSkinRepos::InitRepoGit(string path) { + dsyslog("skindesigner: initiating skin repository %s", path.c_str()); + CreateFolder(path); + + cString command = cString::sprintf("git clone --depth=1 %s %s", skinRepoUrl.c_str(), path.c_str()); + int result = system (*command); + + if (result == 0) { + dsyslog("skindesigner: skinrepository successfully initiated"); + } else { + esyslog("skindesigner: ERROR initiating skinrepository. Command: %s", *command); + } +} + +void cSkinRepos::PullRepoGit(string path) { + dsyslog("skindesigner: updating skin repository %s", path.c_str()); + cString command = *cString::sprintf("cd %s; git pull", path.c_str()); + int result = system (*command); + if (result == 0) { + dsyslog("skindesigner: skinrepository successfully updated"); + } else { + esyslog("skindesigner: ERROR updating skinrepository. Command: %s", *command); + } +} diff --git a/extensions/skinrepo.h b/extensions/skinrepo.h new file mode 100644 index 0000000..2f10929 --- /dev/null +++ b/extensions/skinrepo.h @@ -0,0 +1,98 @@ +#ifndef __SKINREPO_H +#define __SKINREPO_H + +#include <string> +#include <vector> +#include <map> +#include <set> +#include "libxmlwrapper.h" +#include <vdr/plugin.h> + +using namespace std; + +enum eRepoType { + rtUndefined, + rtGit, + rtZipUrl +}; + +enum eAction { + eaUndefined, + eaInstall, + eaUpdate +}; + +// --- cSkinRepo ------------------------------------------------------------- + +class cSkinRepo : public cThread { +private: + string name; + eRepoType repoType; + eAction action; + string url; + string author; + string minSDVersion; + vector<string> specialFonts; + vector<string> supportedPlugins; + vector< pair < string, string > > screenshots; + //helpers for execution + string command; + string command2; + string tempfile; + int result; + string skinPath; + string themesPath; + virtual void Action(void); + void CreateThemeFiles(void); +public: + cSkinRepo(void); + virtual ~cSkinRepo(void); + void SetName(string name) { this->name = name; }; + void SetRepoType(eRepoType type) { this->repoType = type; }; + void SetUrl(string url) { this->url = url; }; + void SetAuthor(string author) { this->author = author; }; + void SetMinSDVersion(string minSDVersion) { this->minSDVersion = minSDVersion; }; + void SetSpecialFont(string font) { specialFonts.push_back(font); }; + void SetSupportedPlugin(string plugin) { supportedPlugins.push_back(plugin); }; + void SetScreenshot(string desc, string url) { screenshots.push_back(pair<string, string>(desc, url)); }; + bool Valid(void); + eRepoType Type(void) { return repoType; }; + string Name(void) { return name; }; + string Author(void) { return author; }; + string MinSDVersion(void) { return minSDVersion; }; + string Url(void) { return url; }; + vector<string> *SpecialFonts(void) { return &specialFonts; }; + vector<string> *SupportedPlugins(void) { return &supportedPlugins; }; + vector< pair < string, string > > *Screenshots(void) { return &screenshots; }; + void Install(string path, string themesPath); + void Update(string path); + bool InstallationFinished(void) { return !(Running()); }; + bool SuccessfullyInstalled(void) { if (result == 0) return true; return false; }; + bool SuccessfullyUpdated(void); + void Debug(void); +}; + +// --- cSkinRepos ------------------------------------------------------------- + +class cSkinRepos : public cLibXMLWrapper { +private: + string skinRepoUrl; + string repoFolder; + vector<cSkinRepo*> repos; + vector<cSkinRepo*>::iterator repoIt; + bool ParseRepository(void); + void InitRepoGit(string path); + void PullRepoGit(string path); +public: + cSkinRepos(void); + virtual ~cSkinRepos(void); + void Init(string path); + void Read(string path); + int Count(void) { return repos.size(); }; + cSkinRepo *GetRepo(string name); + void InitRepoIterator(void) { repoIt = repos.begin(); }; + cSkinRepo *GetNextRepo(void); + void Debug(void); +}; + +#endif //__SKINREPO_H diff --git a/extensions/skinsetup.c b/extensions/skinsetup.c new file mode 100644 index 0000000..f622336 --- /dev/null +++ b/extensions/skinsetup.c @@ -0,0 +1,377 @@ +#include "skinsetup.h" +#include "../config.h" +#include "../coreengine/xmlparser.h" +#include "helpers.h" + +// --- cSkinSetupParameter ----------------------------------------------------------- + +cSkinSetupParameter::cSkinSetupParameter(void) { + type = sptUnknown; + name = ""; + displayText = ""; + helpText = ""; + min = 0; + max = 1000; + value = 0; + options = NULL; + optionsTranslated = NULL; + numOptions = 0; +} + +cSkinSetupParameter::~cSkinSetupParameter(void) { + if (numOptions > 0 && options && optionsTranslated) { + delete[] options; + delete[] optionsTranslated; + } +} + +void cSkinSetupParameter::Debug(void) { + string sType = "unknown"; + if (type == sptBool) + sType = "bool"; + else if (type == sptInt) + sType = "int"; + else if (type == sptString) + sType = "string"; + dsyslog("skindesigner: name \"%s\", type %s, displayText \"%s\", Value: %d", name.c_str(), sType.c_str(), displayText.c_str(), value); + if (type == sptInt) + dsyslog("skindesigner: min %d, max %d", min, max); + if (type == sptString && options) { + for (int i=0; i < numOptions; i++) { + dsyslog("skindesigner: option %d: %s", i+1, options[i]); + } + } +} + +// --- cSkinSetupMenu ----------------------------------------------------------- +cSkinSetupMenu::cSkinSetupMenu(void) { + name = ""; + displayText = ""; + parent = NULL; +} + +cSkinSetupMenu::~cSkinSetupMenu(void) { + for (vector < cSkinSetupParameter* >::iterator p = parameters.begin(); p != parameters.end(); p++) { + delete (*p); + } + for (vector < cSkinSetupMenu* >::iterator s = subMenus.begin(); s != subMenus.end(); s++) { + delete (*s); + } +} + +cSkinSetupParameter *cSkinSetupMenu::GetNextParameter(bool deep) { + cSkinSetupParameter *param = NULL; + if (paramIt != parameters.end()) { + param = *paramIt; + paramIt++; + return param; + } + if (!deep) + return NULL; + + if (subMenuIt != subMenus.end()) { + param = (*subMenuIt)->GetNextParameter(); + if (!param) { + subMenuIt++; + if (subMenuIt != subMenus.end()) { + (*subMenuIt)->InitIterators(); + param = (*subMenuIt)->GetNextParameter(); + } + } + } + return param; +} + +cSkinSetupParameter *cSkinSetupMenu::GetParameter(string name) { + for (vector < cSkinSetupParameter* >::iterator it = parameters.begin(); it != parameters.end(); it++) { + if (!name.compare((*it)->name)) + return *it; + } + + cSkinSetupParameter *paramHit = NULL; + for (vector < cSkinSetupMenu* >::iterator subMenu = subMenus.begin(); subMenu != subMenus.end(); subMenu++) { + paramHit = (*subMenu)->GetParameter(name); + if (paramHit) + return paramHit; + } + return NULL; +} + +void cSkinSetupMenu::InitIterators(void) { + paramIt = parameters.begin(); + subMenuIt = subMenus.begin(); + while (subMenuIt != subMenus.end()) { + (*subMenuIt)->InitIterators(); + subMenuIt++; + } + subMenuIt = subMenus.begin(); +} + +void cSkinSetupMenu::SetParameter(eSetupParameterType paramType, string name, string displayText, string helpText, string min, string max, string value, string options) { + cSkinSetupParameter *param = new cSkinSetupParameter(); + param->type = paramType; + param->name = name; + param->displayText = displayText; + param->helpText = helpText; + + if (min.size() && paramType == sptInt) { + param->min = atoi(min.c_str()); + } + if (max.size() && paramType == sptInt) { + param->max = atoi(max.c_str()); + } + + param->value = atoi(value.c_str()); + + if (paramType == sptString) { + splitstring o(options.c_str()); + vector<string> opts = o.split(',', 1); + int numOpts = opts.size(); + if (numOpts > 0) { + param->numOptions = numOpts; + param->options = new const char*[numOpts]; + int i=0; + for (vector<string>::iterator it = opts.begin(); it != opts.end(); it++) { + string option = trim(*it); + char *s = new char[option.size()]; + strcpy(s, option.c_str()); + param->options[i++] = s; + } + } + } + + parameters.push_back(param); +} + +cSkinSetupMenu *cSkinSetupMenu::GetMenu(string &name) { + for (vector<cSkinSetupMenu*>::iterator m = subMenus.begin(); m != subMenus.end(); m++) { + cSkinSetupMenu *menu = (*m); + if (!name.compare(menu->GetName())) + return menu; + menu = menu->GetMenu(name); + if (menu) + return menu; + } + return NULL; +} + +cSkinSetupMenu *cSkinSetupMenu::GetNextSubMenu(bool deep) { + cSkinSetupMenu *menu = NULL; + if (subMenuIt != subMenus.end()) { + if (deep) { + menu = (*subMenuIt)->GetNextSubMenu(deep); + if (menu) + return menu; + } + menu = *subMenuIt; + subMenuIt++; + return menu; + } + return NULL; +} + + +void cSkinSetupMenu::Debug(bool deep) { + dsyslog("skindesigner: Menu %s Setup Parameters", name.c_str()); + for (vector < cSkinSetupParameter* >::iterator p = parameters.begin(); p != parameters.end(); p++) { + (*p)->Debug(); + } + if (subMenus.empty()) + return; + for (vector < cSkinSetupMenu* >::iterator s = subMenus.begin(); s != subMenus.end(); s++) { + dsyslog("skindesigner: SubMenu %s, Parent %s", ((*s)->GetName()).c_str(), ((*s)->GetParent()->GetName()).c_str()); + if (deep) + (*s)->Debug(); + } +} +// --- cSkinSetup ----------------------------------------------------------- + +cSkinSetup::cSkinSetup(string skin) { + this->skin = skin; + rootMenu = new cSkinSetupMenu(); + rootMenu->SetName("root"); + currentMenu = rootMenu; +} + +cSkinSetup::~cSkinSetup() { + delete rootMenu; +} + +bool cSkinSetup::ReadFromXML(void) { + string xmlPath = *cString::sprintf("%s%s/setup.xml", *config.GetSkinPath(skin), skin.c_str()); + cXmlParser parser; + if (!parser.ReadSkinSetup(this, xmlPath)) { + return false; + } + parser.ParseSkinSetup(skin); + return true; +} + +void cSkinSetup::SetSubMenu(string name, string displayText) { + cSkinSetupMenu *subMenu = new cSkinSetupMenu(); + subMenu->SetName(name); + subMenu->SetDisplayText(displayText); + subMenu->SetParent(currentMenu); + currentMenu->AddSubMenu(subMenu); + currentMenu = subMenu; +} + +void cSkinSetup::SubMenuDone(void) { + cSkinSetupMenu *parent = currentMenu->GetParent(); + if (parent) { + currentMenu = parent; + } +} + +void cSkinSetup::SetParameter(string type, string name, string displayText, string helpText, string min, string max, string value, string options) { + if (!type.size() || !name.size() || !displayText.size() || !value.size()) { + esyslog("skindesigner: invalid setup parameter for skin %s", skin.c_str()); + return; + } + eSetupParameterType paramType = sptUnknown; + if (!type.compare("int")) { + paramType = sptInt; + } else if (!type.compare("bool")) { + paramType = sptBool; + } else if (!type.compare("string")) { + paramType = sptString; + } + if (paramType == sptUnknown) { + esyslog("skindesigner: invalid setup parameter for skin %s", skin.c_str()); + return; + } + currentMenu->SetParameter(paramType, name, displayText, helpText, min, max, value, options); +} + +cSkinSetupParameter *cSkinSetup::GetNextParameter(void) { + return rootMenu->GetNextParameter(); +} + +cSkinSetupParameter *cSkinSetup::GetParameter(string name) { + return rootMenu->GetParameter(name); +} + +void cSkinSetup::SetTranslation(string translationToken, map < string, string > transl) { + translations.insert(pair<string, map < string, string > >(translationToken, transl)); +} + +void cSkinSetup::AddToGlobals(cGlobals *globals) { + if (!globals) + return; + rootMenu->InitIterators(); + cSkinSetupParameter *param = NULL; + while (param = rootMenu->GetNextParameter()) { + if (param->type == sptString) { + string value = param->options[param->value]; + globals->AddString(param->name, value); + string intName = "index" + param->name; + globals->AddInt(intName, param->value); + } else { + globals->AddInt(param->name, param->value); + } + } +} + +void cSkinSetup::TranslateSetup(void) { + rootMenu->InitIterators(); + cSkinSetupParameter *param = NULL; + while (param = rootMenu->GetNextParameter()) { + string transl = ""; + if (Translate(param->displayText, transl)) { + param->displayText = transl; + } + string translHelp = ""; + if (Translate(param->helpText, translHelp)) { + param->helpText = translHelp; + } + if (param->type == sptString && param->numOptions > 0) { + param->optionsTranslated = new const char*[param->numOptions]; + for (int i = 0; i < param->numOptions; i++) { + string option = param->options[i]; + string optionTransToken = "{tr(" + option + ")}"; + string optionTranslated = ""; + if (Translate(optionTransToken, optionTranslated)) { + char *s = new char[optionTranslated.size()]; + strcpy(s, optionTranslated.c_str()); + param->optionsTranslated[i] = s; + } else { + char *s = new char[option.size()]; + strcpy(s, option.c_str()); + param->optionsTranslated[i] = s; + } + } + } + } + + rootMenu->InitIterators(); + cSkinSetupMenu *subMenu = NULL; + while (subMenu = rootMenu->GetNextSubMenu()) { + string transl = ""; + if (Translate(subMenu->GetDisplayText(), transl)) { + subMenu->SetDisplayText(transl); + } + } +} + +cSkinSetupMenu *cSkinSetup::GetMenu(string &name) { + if (name.size() == 0) + return rootMenu; + return rootMenu->GetMenu(name); +} + +bool cSkinSetup::Translate(string text, string &translation) { + string transStart = "{tr("; + string transEnd = ")}"; + size_t foundStart = text.find(transStart); + size_t foundEnd = text.find(transEnd); + bool translated = false; + + while (foundStart != string::npos && foundEnd != string::npos) { + string token = text.substr(foundStart + 1, foundEnd - foundStart); + string transToken = DoTranslate(token); + if (transToken.size() > 0) + translated = true; + else + return false; + text.replace(foundStart, foundEnd - foundStart + 2, transToken); + foundStart = text.find(transStart); + foundEnd = text.find(transEnd); + } + if (translated) + translation = text; + return translated; +} + +string cSkinSetup::DoTranslate(string token) { + string translation = ""; + map <string, map< string, string > >::iterator hit = translations.find(token); + if (hit == translations.end()) { + esyslog("skindesigner: invalid translation token %s", token.c_str()); + return translation; + } + map< string, string > translats = hit->second; + map< string, string >::iterator trans = translats.find(Setup.OSDLanguage); + if (trans != translats.end()) { + translation = trans->second; + } else { + map< string, string >::iterator transDefault = translats.find("en_EN"); + if (transDefault != translats.end()) { + translation = transDefault->second; + } + } + return translation; +} + +void cSkinSetup::Debug(void) { + rootMenu->Debug(); + return; + dsyslog("skindesigner: Skin \"%s\" Setup Parameter Translations", skin.c_str()); + for (map<string, map<string,string> >::iterator trans = translations.begin(); trans != translations.end(); trans++) { + dsyslog("skindesigner: translation token %s", (trans->first).c_str()); + map<string,string> transValues = trans->second; + for (map<string,string>::iterator trans2 = transValues.begin(); trans2 != transValues.end(); trans2++) { + dsyslog("skindesigner: translation language %s value \"%s\"", (trans2->first).c_str(), (trans2->second).c_str()); + } + } + +} diff --git a/extensions/skinsetup.h b/extensions/skinsetup.h new file mode 100644 index 0000000..e3b9cc3 --- /dev/null +++ b/extensions/skinsetup.h @@ -0,0 +1,102 @@ +#ifndef __SKINSETUP_H +#define __SKINSETUP_H + +#include <string> +#include <vector> +#include <map> +#include <set> +#include <sstream> +#include <vdr/plugin.h> +#include <libxml/xmlstring.h> +#include "../coreengine/globals.h" + +using namespace std; + +enum eSetupParameterType { + sptInt, + sptBool, + sptString, + sptUnknown +}; + +// --- cSkinSetupParameter ----------------------------------------------------------- + +class cSkinSetupParameter { +private: +public: + cSkinSetupParameter(void); + virtual ~cSkinSetupParameter(void); + eSetupParameterType type; + string name; + string displayText; + string helpText; + int min; + int max; + int value; + const char* *options; + const char* *optionsTranslated; + int numOptions; + void Debug(void); +}; + +// --- cSkinSetupMenu ----------------------------------------------------------- + +class cSkinSetupMenu { +private: + string name; + string displayText; + cSkinSetupMenu *parent; + vector < cSkinSetupMenu* > subMenus; + vector < cSkinSetupMenu* >::iterator subMenuIt; + vector < cSkinSetupParameter* > parameters; + vector < cSkinSetupParameter* >::iterator paramIt; +public: + cSkinSetupMenu(void); + virtual ~cSkinSetupMenu(void); + void SetName(string name) { this->name = name; }; + void SetDisplayText(string displayText) { this->displayText = displayText; }; + string GetName(void) { return name; }; + string GetDisplayText(void) { return displayText; }; + void SetParent(cSkinSetupMenu *p) { parent = p; }; + cSkinSetupMenu *GetParent(void) { return parent; }; + void AddSubMenu(cSkinSetupMenu *sub) { subMenus.push_back(sub); }; + void SetParameter(eSetupParameterType paramType, string name, string displayText, string helpText, string min, string max, string value, string options); + void InitIterators(void); + void InitParameterIterator(void) { paramIt = parameters.begin(); }; + cSkinSetupParameter *GetNextParameter(bool deep = true); + cSkinSetupParameter *GetParameter(string name); + void InitSubmenuIterator(void) { subMenuIt = subMenus.begin(); }; + cSkinSetupMenu *GetNextSubMenu(bool deep = true); + cSkinSetupMenu *GetMenu(string &name); + void Debug(bool deep = true); +}; + +// --- cSkinSetup ----------------------------------------------------------- + +class cSkinSetup { +private: + string skin; + cSkinSetupMenu *rootMenu; + cSkinSetupMenu *currentMenu; + map < string, map< string, string > > translations; + string DoTranslate(string token); + bool Translate(string text, string &translation); +public: + cSkinSetup(string skin); + virtual ~cSkinSetup(void); + bool ReadFromXML(void); + void SetSubMenu(string name, string displayText); + void SubMenuDone(void); + void SetParameter(string type, string name, string displayText, string helpText, string min, string max, string value, string options); + void InitParameterIterator(void) { rootMenu->InitIterators(); }; + cSkinSetupParameter *GetNextParameter(void); + cSkinSetupParameter *GetParameter(string name); + void SetTranslation(string translationToken, map < string, string > transl); + void AddToGlobals(cGlobals *globals); + void TranslateSetup(void); + string GetSkin(void) { return skin; }; + cSkinSetupMenu *GetMenu(string &name); + void Debug(void); +}; + +#endif //__SKINSETUP_H diff --git a/extensions/timers.c b/extensions/timers.c new file mode 100644 index 0000000..b93aada --- /dev/null +++ b/extensions/timers.c @@ -0,0 +1,121 @@ +#include "timers.h" +#include "../services/epgsearch.h" +#include "../services/remotetimers.h" + +static int CompareTimers(const void *a, const void *b) { + return (*(const cTimer **)a)->Compare(**(const cTimer **)b); +} + +cGlobalSortedTimers::cGlobalSortedTimers(bool forceRefresh) : cVector<const cTimer *>(Timers.Count()) { + static bool initial = true; + static cRemoteTimerRefresh *remoteTimerRefresh = NULL; + localTimer = NULL; + + if (forceRefresh) + initial = true; + //check if remotetimers plugin is available + static cPlugin* pRemoteTimers = cPluginManager::GetPlugin("remotetimers"); + + cSchedulesLock SchedulesLock; + const cSchedules *Schedules = cSchedules::Schedules(SchedulesLock); + + if (pRemoteTimers && initial) { + cString errorMsg; + pRemoteTimers->Service("RemoteTimers::RefreshTimers-v1.0", &errorMsg); + initial = false; + } + + for (cTimer *Timer = Timers.First(); Timer; Timer = Timers.Next(Timer)) { + if (Timer->HasFlags(tfActive)) + Append(Timer); + } + + //if remotetimers plugin is available, take timers also from him + if (pRemoteTimers) { + cTimer* remoteTimer = NULL; + while (pRemoteTimers->Service("RemoteTimers::ForEach-v1.0", &remoteTimer) && remoteTimer != NULL) { + remoteTimer->SetEventFromSchedule(Schedules); // make sure the event is current + if (remoteTimer->HasFlags(tfActive)) + Append(remoteTimer); + } + } + + Sort(CompareTimers); + + int numTimers = Size(); + if (numTimers > 0) { + localTimer = new bool[numTimers]; + for (int i=0; i < numTimers; i++) { + if (!pRemoteTimers) { + localTimer[i] = true; + } else { + localTimer[i] = false; + for (cTimer *Timer = Timers.First(); Timer; Timer = Timers.Next(Timer)) { + if (Timer == At(i)) { + localTimer[i] = true; + break; + } + } + } + } + } + + if (pRemoteTimers && (remoteTimerRefresh == NULL)) + remoteTimerRefresh = new cRemoteTimerRefresh(); +} + +cGlobalSortedTimers::~cGlobalSortedTimers(void) { + if (localTimer) { + delete[] localTimer; + } +} + +bool cGlobalSortedTimers::IsRemoteTimer(int i) { + if (!localTimer) + return true; + if (i >= Size()) + return true; + return !(localTimer[i]); +} + + +int cGlobalSortedTimers::NumTimerConfilicts(void) { + int numConflicts = 0; + cPlugin *p = cPluginManager::GetPlugin("epgsearch"); + if (p) { + Epgsearch_lastconflictinfo_v1_0 *serviceData = new Epgsearch_lastconflictinfo_v1_0; + if (serviceData) { + serviceData->nextConflict = 0; + serviceData->relevantConflicts = 0; + serviceData->totalConflicts = 0; + p->Service("Epgsearch-lastconflictinfo-v1.0", serviceData); + if (serviceData->relevantConflicts > 0) { + numConflicts = serviceData->relevantConflicts; + } + delete serviceData; + } + } + return numConflicts; +} + +cRemoteTimerRefresh::cRemoteTimerRefresh(): cThread("skindesigner: RemoteTimers::RefreshTimers") { + Start(); +} + +cRemoteTimerRefresh::~cRemoteTimerRefresh(void) { + Cancel(-1); + while (Active()) + cCondWait::SleepMs(10); +} + +void cRemoteTimerRefresh::Action(void) { + #define REFESH_INTERVALL_MS 30000 + while (Running()) { + cCondWait::SleepMs(REFESH_INTERVALL_MS); + if (!cOsd::IsOpen()) {//make sure that no timer is currently being edited + cGlobalSortedTimers(true); + Timers.SetModified(); + } + } +} +
\ No newline at end of file diff --git a/extensions/timers.h b/extensions/timers.h new file mode 100644 index 0000000..aba873c --- /dev/null +++ b/extensions/timers.h @@ -0,0 +1,24 @@ +#ifndef __NOPACITY_TIMERS_H +#define __NOPACITY_TIMERS_H + +#include <vdr/timers.h> +#include <vdr/plugin.h> + +class cGlobalSortedTimers : public cVector<const cTimer *> { + private: + bool *localTimer; + public: + cGlobalSortedTimers(bool forceRefresh = false); + virtual ~cGlobalSortedTimers(void); + bool IsRemoteTimer(int i); + int NumTimerConfilicts(void); +}; + +class cRemoteTimerRefresh: public cThread { + protected: + virtual void Action(void); + public: + cRemoteTimerRefresh(void); + virtual ~cRemoteTimerRefresh(void); +}; +#endif //__NOPACITY_TIMERS_H |
