diff --git a/src/gui/moviebrowser.cpp b/src/gui/moviebrowser.cpp index 4191aff2c..fb0b955d7 100644 --- a/src/gui/moviebrowser.cpp +++ b/src/gui/moviebrowser.cpp @@ -64,6 +64,7 @@ #include #include #include +#include #include #include @@ -1084,6 +1085,10 @@ int CMovieBrowser::exec(const char* path) TRACE("[mb] stop: %d start:%d \r\n",m_movieSelectionHandler->bookmarks.lastPlayStop,m_movieSelectionHandler->bookmarks.start); m_currentStartPos = showStartPosSelectionMenu(); // display start menu m_currentStartPos = } + + if (show_mode == MB_SHOW_YT) + cYTCache::getInstance()->useCachedCopy(m_movieSelectionHandler); + if(m_currentStartPos >= 0) { playing_info = m_movieSelectionHandler; TRACE("[mb] start pos: %d s\r\n",m_currentStartPos); @@ -1093,6 +1098,10 @@ int CMovieBrowser::exec(const char* path) refresh(); } } + else if ((show_mode == MB_SHOW_YT) && (msg == CRCInput::RC_record) && m_movieSelectionHandler) + { + cYTCache::getInstance()->addToCache(m_movieSelectionHandler); + } else if (msg == CRCInput::RC_home) { loop = false; @@ -1280,8 +1289,8 @@ std::string CMovieBrowser::getScreenshotName(std::string movie) std::string ext; std::string ret; - size_t found = movie.rfind(".ts"); - if ((found == string::npos) || (found != (movie.length() - 3))) + size_t found = movie.find_last_of("."); + if (found == string::npos) return ""; vector::iterator it = PicExts.begin(); @@ -3692,9 +3701,9 @@ void CMovieBrowser::loadYTitles(int mode, std::string search, std::string id) movieInfo.tfile = ylist[i].tfile; movieInfo.ytdate = ylist[i].published; movieInfo.ytid = ylist[i].id; - movieInfo.file.Name = ylist[i].title; - movieInfo.file.Url = ylist[i].GetUrl(m_settings.ytquality, false); + movieInfo.ytitag = m_settings.ytquality; + movieInfo.file.Url = ylist[i].GetUrl(&movieInfo.ytitag, false); movieInfo.file.Time = toEpoch(movieInfo.ytdate); m_vMovieInfo.push_back(movieInfo); } @@ -4371,7 +4380,7 @@ static void save_info(CMovieInfo * cmovie, MI_MOVIE_INFO * minfo, char * dpart, { MI_MOVIE_INFO ninfo; - cmovie->copy(minfo, &ninfo); + ninfo = *minfo; ninfo.file.Name = dpart; ninfo.file.Size = spos; ninfo.length = spos/secsize/60; diff --git a/src/gui/movieinfo.cpp b/src/gui/movieinfo.cpp index bc7898fcd..c1291722e 100644 --- a/src/gui/movieinfo.cpp +++ b/src/gui/movieinfo.cpp @@ -97,24 +97,13 @@ bool CMovieInfo::convertTs2XmlName(char *char_filename, int size) ************************************************************************/ bool CMovieInfo::convertTs2XmlName(std::string * filename) { - //TRACE("[mi]->convertTs2XmlName\r\n"); - int bytes = filename->find(".ts"); - bool result = false; - - if (bytes != -1) { - if (bytes > 3) { - if ((*filename)[bytes - 4] == '.') { - bytes = bytes - 4; - } - } - *filename = filename->substr(0, bytes) + ".xml"; - result = true; - } else // not a TS file, return!!!!! - { - //TRACE(" not a TS file "); + size_t lastdot = filename->find_last_of("."); + if (lastdot != string::npos) { + filename->erase(lastdot + 1); + filename->append("xml"); + return true; } - - return (result); + return false; } /************************************************************************ @@ -886,6 +875,7 @@ void CMovieInfo::clearMovieInfo(MI_MOVIE_INFO * movie_info) timePlay.tm_mon = 1; movie_info->file.Name = ""; + movie_info->file.Url = ""; movie_info->file.Size = 0; // Megabytes movie_info->file.Time = mktime(&timePlay); movie_info->dateOfLastPlay = mktime(&timePlay); // (date, month, year) @@ -925,6 +915,7 @@ void CMovieInfo::clearMovieInfo(MI_MOVIE_INFO * movie_info) movie_info->tfile.clear(); movie_info->ytdate.clear(); movie_info->ytid.clear(); + movie_info->ytitag = 0; movie_info->marked = false; } @@ -1028,52 +1019,57 @@ bool CMovieInfo::saveFile_vlc(const CFile & /*file*/, const char */*text*/, cons * * */ -void CMovieInfo::copy(MI_MOVIE_INFO * src, MI_MOVIE_INFO * dst) +#if 0 +MI_MOVIE_INFO& MI_MOVIE_INFO::operator=(const MI_MOVIE_INFO& src) { - //TRACE("[mi]->clearMovieInfo \r\n"); + file.Name = src.file.Name; + file.Url = src.file.Url; + file.Size = src.file.Size; + file.Time = src.file.Time; + dateOfLastPlay = src.dateOfLastPlay; + dirItNr = src.dirItNr; + genreMajor = src.genreMajor; + genreMinor = src.genreMinor; + length = src.length; + quality = src.quality; + productionDate = src.productionDate; + parentalLockAge = src.parentalLockAge; + format = src.format; + audio = src.audio; - dst->file.Name = src->file.Name; - dst->file.Size = src->file.Size; - dst->file.Time = src->file.Time; - dst->dateOfLastPlay = src->dateOfLastPlay; - dst->dirItNr = src->dirItNr; - dst->genreMajor = src->genreMajor; - dst->genreMinor = src->genreMinor; - dst->length = src->length; - dst->quality = src->quality; - dst->productionDate = src->productionDate; - dst->parentalLockAge = src->parentalLockAge; - dst->format = src->format; - dst->audio = src->audio; + epgId = src.epgId; + epgEpgId = src.epgEpgId; + epgMode = src.epgMode; + epgVideoPid = src.epgVideoPid; + VideoType = src.VideoType; + epgVTXPID = src.epgVTXPID; - dst->epgId = src->epgId; - dst->epgEpgId = src->epgEpgId; - dst->epgMode = src->epgMode; - dst->epgVideoPid = src->epgVideoPid; - dst->VideoType = src->VideoType; - dst->epgVTXPID = src->epgVTXPID; + productionCountry = src.productionCountry; + epgTitle = src.epgTitle; + epgInfo1 = src.epgInfo1; + epgInfo2 = src.epgInfo2; + epgChannel = src.epgChannel; + serieName = src.serieName; + bookmarks.end = src.bookmarks.end; + bookmarks.start = src.bookmarks.start; + bookmarks.lastPlayStop = src.bookmarks.lastPlayStop; - dst->productionCountry = src->productionCountry; - dst->epgTitle = src->epgTitle; - dst->epgInfo1 = src->epgInfo1; - dst->epgInfo2 = src->epgInfo2; - dst->epgChannel = src->epgChannel; - dst->serieName = src->serieName; - dst->bookmarks.end = src->bookmarks.end; - dst->bookmarks.start = src->bookmarks.start; - dst->bookmarks.lastPlayStop = src->bookmarks.lastPlayStop; - - for (int i = 0; i < MI_MOVIE_BOOK_USER_MAX; i++) { - dst->bookmarks.user[i].pos = src->bookmarks.user[i].pos; - dst->bookmarks.user[i].length = src->bookmarks.user[i].length; - dst->bookmarks.user[i].name = src->bookmarks.user[i].name; + for (unsigned int i = 0; i < MI_MOVIE_BOOK_USER_MAX; i++) { + bookmarks.user[i].pos = src.bookmarks.user[i].pos; + bookmarks.user[i].length = src.bookmarks.user[i].length; + bookmarks.user[i].name = src.bookmarks.user[i].name; } - for (unsigned int i = 0; i < src->audioPids.size(); i++) { + for (unsigned int i = 0; i < src.audioPids.size(); i++) { EPG_AUDIO_PIDS audio_pids; - audio_pids.epgAudioPid = src->audioPids[i].epgAudioPid; - audio_pids.epgAudioPidName = src->audioPids[i].epgAudioPidName; - audio_pids.atype = src->audioPids[i].atype; - dst->audioPids.push_back(audio_pids); + audio_pids.epgAudioPid = src.audioPids[i].epgAudioPid; + audio_pids.epgAudioPidName = src.audioPids[i].epgAudioPidName; + audio_pids.atype = src.audioPids[i].atype; + audioPids.push_back(audio_pids); } + ytdate = src.ytdate; + ytid = src.ytid; + ytitag = src.ytitag; + return *this; } +#endif diff --git a/src/gui/movieinfo.h b/src/gui/movieinfo.h index 8403f058e..09be9e80e 100644 --- a/src/gui/movieinfo.h +++ b/src/gui/movieinfo.h @@ -46,12 +46,15 @@ #ifndef MOVIEINFO_H_ #define MOVIEINFO_H_ +#define __USE_FILE_OFFSET64 1 + #ifdef HAVE_CONFIG_H #include #endif #include #include +#include #include "driver/file.h" /************************************************************************/ @@ -140,8 +143,10 @@ typedef struct /************************************************************************/ /************************************************************************/ -typedef struct +class MI_MOVIE_INFO { + public: +// MI_MOVIE_INFO &operator=(const MI_MOVIE_INFO& src); CFile file; // not stored in xml std::string productionCountry; // user defined Country (not from EPG yet, but might be possible) std::string epgTitle; // plain movie name, usually filled by EPG @@ -173,7 +178,8 @@ typedef struct std::string tfile; // thumbnail/cover file name std::string ytdate; // yt published std::string ytid; // yt published -} MI_MOVIE_INFO; + int ytitag; // youtube quality profile +}; typedef std::vector MI_MOVIE_LIST; typedef std::vector P_MI_MOVIE_LIST; @@ -192,7 +198,6 @@ class CMovieInfo void printDebugMovieInfo(MI_MOVIE_INFO& movie_info); // print movie info on debug channel (RS232) void clearMovieInfo(MI_MOVIE_INFO* movie_info); // Set movie info structure to initial values bool addNewBookmark(MI_MOVIE_INFO* movie_info,MI_BOOKMARK &new_bookmark); // add a new bookmark to the given movie info. If there is no space false is returned - void copy(MI_MOVIE_INFO* src, MI_MOVIE_INFO* dst); private:// Functions bool parseXmlTree (char* text, MI_MOVIE_INFO* movie_info); // this is the 'good' function, but it needs the xmllib which is not currently linked within neutrino. Might be to slow as well. If used, add bookmark parsing diff --git a/src/system/Makefile.am b/src/system/Makefile.am index 2ae9452e5..383330fad 100644 --- a/src/system/Makefile.am +++ b/src/system/Makefile.am @@ -44,5 +44,6 @@ libneutrino_system_a_SOURCES = \ ping.cpp \ settings.cpp \ sysload.cpp \ + ytcache.cpp \ ytparser.cpp \ setting_helpers.cpp diff --git a/src/system/helpers.cpp b/src/system/helpers.cpp index e6548be4b..50b8deb52 100644 --- a/src/system/helpers.cpp +++ b/src/system/helpers.cpp @@ -509,7 +509,7 @@ bool CFileHelpers::copyFile(const char *Src, const char *Dst, mode_t mode) unlink(Dst); if ((fd1 = open(Src, O_RDONLY)) < 0) return false; - if ((fd2 = open(Dst, O_WRONLY | O_CREAT, 0666)) < 0) { + if ((fd2 = open(Dst, O_WRONLY | O_CREAT, mode)) < 0) { close(fd1); return false; } @@ -574,7 +574,6 @@ bool CFileHelpers::copyFile(const char *Src, const char *Dst, mode_t mode) return false; } - chmod(Dst, mode); return true; } diff --git a/src/system/ytcache.cpp b/src/system/ytcache.cpp new file mode 100644 index 000000000..e54d1fd14 --- /dev/null +++ b/src/system/ytcache.cpp @@ -0,0 +1,254 @@ +/* + ytcache.cpp -- cache youtube movies + + Copyright (C) 2013 martii + + License: GPL + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#include "ytcache.h" +#include + +#include +#include +#include +#include +#if LIBCURL_VERSION_NUM < 0x071507 +#include +#endif +#define URL_TIMEOUT 60 + +#include "helpers.h" +#include "settings.h" +#include + +static cYTCache *instance = NULL; + +cYTCache::cYTCache(void) +{ + cancelled = false; + thread = 0; +} + +cYTCache::~cYTCache(void) +{ + instance = NULL; +} + +cYTCache *cYTCache::getInstance(void) +{ + if (!instance) + instance = new cYTCache(); + return instance; +} + +std::string cYTCache::getName(MI_MOVIE_INFO *mi, std::string ext) +{ + char ytitag[10]; + snprintf(ytitag, sizeof(ytitag), "%d", mi->ytitag); + return g_settings.downloadcache_dir + "/" + mi->ytid + "-" + std::string(ytitag) + "." + ext; +} + +bool cYTCache::useCachedCopy(MI_MOVIE_INFO *mi) +{ + std::string cache = getName(mi); + if (!access(cache.c_str(), R_OK)) { + mi->file.Url = cache; + return true; + } + return false; +} + +int cYTCache::curlProgress(void *clientp, double /*dltotal*/, double /*dlnow*/, double /*ultotal*/, double /*ulnow*/) +{ + cYTCache *caller = (cYTCache *) clientp; + if (caller->cancelled) + return 1; + return 0; +} + +bool cYTCache::download(MI_MOVIE_INFO *mi) +{ + std::string file = getName(mi); + std::string tmpfile = file + ".tmp"; + + if (!access(file.c_str(), R_OK) || !access(tmpfile.c_str(), R_OK)) + return true; + + FILE * fp = fopen(file.c_str(), "wb"); + if (!fp) { + perror(file.c_str()); + return false; + } + + CURL *curl = curl_easy_init(); + if (!curl) { + fclose(fp); + return false; + } + + curl_easy_setopt(curl, CURLOPT_URL, mi->file.Url.c_str()); + curl_easy_setopt(curl, CURLOPT_FILE, fp); + curl_easy_setopt(curl, CURLOPT_FAILONERROR, 1); + curl_easy_setopt(curl, CURLOPT_TIMEOUT, URL_TIMEOUT); + curl_easy_setopt(curl, CURLOPT_NOSIGNAL, (long)1); + curl_easy_setopt(curl, CURLOPT_PROGRESSFUNCTION, cYTCache::curlProgress); + curl_easy_setopt(curl, CURLOPT_PROGRESSDATA, this); + curl_easy_setopt(curl, CURLOPT_NOPROGRESS, (long)0); + + char cerror[CURL_ERROR_SIZE]; + curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, cerror); + + CURLcode res = curl_easy_perform(curl); + curl_easy_cleanup(curl); + + fclose(fp); + + if (res) { + fprintf(stderr, "curl error: %s\n", cerror); + unlink(tmpfile.c_str()); + return false; + } + rename (tmpfile.c_str(), file.c_str()); + CMovieInfo cMovieInfo; + CFile File; + File.Name = getName(mi, "xml"); + cMovieInfo.convertTs2XmlName(&File.Name); + cMovieInfo.saveMovieInfo(*mi, &File); + std::string thumbnail_dst = getName(mi, "jpg"); + std::string thumbnail_src = "/tmp/ytparser/" + mi->ytid + ".jpg"; + CFileHelpers::getInstance()->copyFile(thumbnail_src.c_str(), thumbnail_dst.c_str(), 0644); + return true; +} + +void *cYTCache::downloadThread(void *arg) { + cYTCache *caller = (cYTCache *)arg; + + while (caller->thread) { + MI_MOVIE_INFO mi; + { + OpenThreads::ScopedLock m_lock(caller->mutex); + if (caller->pending.empty()) { + caller->cancelled = false; + caller->thread = 0; + continue; + } + mi = caller->pending.front(); + } + + fprintf(stderr, "download start: %s\n", mi.file.Url.c_str()); + + bool res = caller->download(&mi); + + fprintf(stderr, "download end: %s %s\n", mi.file.Url.c_str(), res ? "succeeded" : "failed"); + + { + OpenThreads::ScopedLock m_lock(caller->mutex); + if (res) + caller->done.push_front(mi); + else + caller->failed.push_front(mi); + caller->cancelled = false; + if (caller->pending.empty()) + caller->thread = 0; + else + caller->pending.pop_front(); + } + } + pthread_exit(NULL); +} + +bool cYTCache::addToCache(MI_MOVIE_INFO *mi) +{ + OpenThreads::ScopedLock m_lock(mutex); + pending.push_back(*mi); + + if (!thread) { + if (pthread_create(&thread, NULL, downloadThread, this)) { + perror("pthread_create"); + return false; + } + pthread_detach(thread); + } + return true; +} + +void cYTCache::cancel(MI_MOVIE_INFO *mi) +{ + OpenThreads::ScopedLock m_lock(mutex); + if (pending.empty()) + return; + + if (compareMovieInfo(mi, &pending.front())) { + cancelled = true; + return; + } + for (std::list::iterator it = pending.begin(); it != pending.end(); ++it) + if (compareMovieInfo(&(*it), mi)) { + pending.erase(it); + break; + } +} + +void cYTCache::cancelAll(void) +{ + OpenThreads::ScopedLock m_lock(mutex); + if (pending.empty()) + return; + + cancelled = true; + while (thread) + usleep(100000); + cancelled = false; + pending.clear(); +} + +std::list cYTCache::getFailed(bool clear) +{ + OpenThreads::ScopedLock m_lock(mutex); + std::list res = failed; + if (clear) + failed.clear(); + return res; +} + +std::list cYTCache::getDone(bool clear) +{ + OpenThreads::ScopedLock m_lock(mutex); + std::list res = done; + if (clear) + done.clear(); + return res; +} + +void cYTCache::clearFailed(void) +{ + OpenThreads::ScopedLock m_lock(mutex); + failed.clear(); +} + +void cYTCache::clearDone(void) +{ + OpenThreads::ScopedLock m_lock(mutex); + done.clear(); +} + +bool cYTCache::compareMovieInfo(MI_MOVIE_INFO *a, MI_MOVIE_INFO *b) +{ + return a->ytid == b->ytid && a->ytitag == b->ytitag; + return true; +} diff --git a/src/system/ytcache.h b/src/system/ytcache.h new file mode 100644 index 000000000..b11e0ac39 --- /dev/null +++ b/src/system/ytcache.h @@ -0,0 +1,62 @@ +/* + ytcache.h -- cache youtube movies + + Copyright (C) 2013 martii + + License: GPL + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#ifndef __YTCACHE_H__ +#define __YTCACHE_H__ + +#include +#include +#include + +#include +#include + +#include + +class cYTCache +{ + private: + pthread_t thread; + bool cancelled; + std::list pending; + std::list done; + std::list failed; + OpenThreads::Mutex mutex; + bool download(MI_MOVIE_INFO *mi); + std::string getName(MI_MOVIE_INFO *mi, std::string ext = "mp4"); + static void *downloadThread(void *arg); + static int curlProgress(void *clientp, double dltotal, double dlnow, double ultotal, double ulnow); + bool compareMovieInfo(MI_MOVIE_INFO *a, MI_MOVIE_INFO *b); + public: + static cYTCache *getInstance(); + cYTCache(); + ~cYTCache(); + bool useCachedCopy(MI_MOVIE_INFO *mi); + bool addToCache(MI_MOVIE_INFO *mi); + void cancel(MI_MOVIE_INFO *mi); + void cancelAll(void); + std::list getDone(bool clear = false); + std::list getFailed(bool clear = false); + void clearDone(void); + void clearFailed(void); +}; +#endif diff --git a/src/system/ytparser.cpp b/src/system/ytparser.cpp index 7e7622757..9f4ae2c78 100644 --- a/src/system/ytparser.cpp +++ b/src/system/ytparser.cpp @@ -63,27 +63,29 @@ void cYTVideoInfo::Dump() printf("===================================================================\n"); } -std::string cYTVideoInfo::GetUrl(int fmt, bool mandatory) +std::string cYTVideoInfo::GetUrl(int *fmt, bool mandatory) { + int default_fmt = 0; + if (!*fmt) + fmt = &default_fmt; + yt_urlmap_iterator_t it; - if (fmt) { - if ((it = formats.find(fmt)) != formats.end()) + if (*fmt) { + if ((it = formats.find(*fmt)) != formats.end()) { return it->second.GetUrl(); - if (mandatory) + } + if (mandatory) { + *fmt = 0; return ""; + } } - if ((it = formats.find(37)) != formats.end()) // 1080p MP4 - return it->second.GetUrl(); - if ((it = formats.find(22)) != formats.end()) // 720p MP4 - return it->second.GetUrl(); -#if 0 - if ((it = formats.find(35)) != formats.end()) // 480p FLV - return it->second.GetUrl(); - if ((it = formats.find(34)) != formats.end()) // 360p FLV - return it->second.GetUrl(); -#endif - if ((it = formats.find(18)) != formats.end()) // 270p/360p MP4 - return it->second.GetUrl(); + + int itags[] = { 37 /* 1080p MP4*/, 22 /* 720p MP4 */, 18 /* 270p/360p MP4 */, 0 }; + for (int *fmtp = itags; *fmtp; fmtp++) + if ((it = formats.find(*fmtp)) != formats.end()) { + *fmt = *fmtp; + return it->second.GetUrl(); + } return ""; } diff --git a/src/system/ytparser.h b/src/system/ytparser.h index fd6d26084..22a4920ee 100644 --- a/src/system/ytparser.h +++ b/src/system/ytparser.h @@ -62,7 +62,7 @@ class cYTVideoInfo bool ret; void Dump(); - std::string GetUrl(int fmt = 0, bool mandatory = true); + std::string GetUrl(int *fmt = NULL, bool mandatory = true); };