From 319203421d1ad6974d797eebe5a5343471e8af38 Mon Sep 17 00:00:00 2001 From: Markus Ehrnsperger <> Date: Tue, 17 Jan 2023 21:30:43 +0100 Subject: [PATCH] 3.1.11 --- epg_events.cpp | 68 ++++--- live/css/styles.css | 5 + livefeatures.h | 6 + pages/epginfo.ecpp | 21 +- pages/pageelems.ecpp | 298 +++++++++++++++++---------- pages/recordings.ecpp | 34 ++-- po/cs_CZ.po | 27 +-- po/de_DE.po | 64 +++--- po/es_ES.po | 27 +-- po/fi_FI.po | 27 +-- po/fr_FR.po | 27 +-- po/it_IT.po | 27 +-- po/lt_LT.po | 27 +-- po/nl_NL.po | 27 +-- po/pl_PL.po | 27 +-- po/ru_RU.po | 27 +-- po/sk_SK.po | 27 +-- po/sv_SE.po | 27 +-- recman.cpp | 283 ++++++++++---------------- recman.h | 218 ++++++++++---------- services.h | 385 ++++++++++++++--------------------- setup.h | 4 +- tntconfig.cpp | 7 + tools.cpp | 455 +++++++++++++++++++++--------------------- 24 files changed, 1012 insertions(+), 1133 deletions(-) diff --git a/epg_events.cpp b/epg_events.cpp index 624ed17a..df6362bc 100644 --- a/epg_events.cpp +++ b/epg_events.cpp @@ -405,20 +405,20 @@ namespace vdrlive bool PosterTvscraper(cTvMedia &media, const cEvent *event, const cRecording *recording) { - media.path = ""; - media.width = media.height = 0; + media.path = ""; + media.width = media.height = 0; if (LiveSetup().GetTvscraperImageDir().empty() ) return false; - ScraperGetPoster call; - call.event = event; - call.recording = recording; - if (ScraperCallService("GetPoster", &call)) { - media.path = ScraperImagePath2Live(call.poster.path); - media.width = call.poster.width; - media.height = call.poster.height; - return true; - } + ScraperGetPoster call; + call.event = event; + call.recording = recording; + if (ScraperCallService("GetPoster", &call)) { + media.path = ScraperImagePath2Live(call.poster.path); + media.width = call.poster.width; + media.height = call.poster.height; + return true; + } return false; - } + } std::list EpgImages(std::string const &epgid) { @@ -494,17 +494,27 @@ namespace vdrlive } // namespace EpgEvents -bool appendEpgItem(cLargeString &epg_item, RecordingsItemPtr &recItem, const cEvent *Event, const cChannel *Channel, bool withChannel) { - std::string s_title, s_episode_name, s_IMDB_ID, s_releaseDate; +void AppendScraperData(cLargeString &target, cScraperVideo *scraperVideo) { cTvMedia s_image; - cGetScraperOverview scraperOverview (Event, NULL, &s_title, &s_episode_name, &s_IMDB_ID, &s_image, + std::string s_title, s_episode_name, s_IMDB_ID, s_release_date; + if (scraperVideo == NULL) { + AppendScraperData(target, s_IMDB_ID, s_image, tNone, s_title, 0, 0, s_episode_name, 0, s_release_date); + return; + } + int s_runtime; + scraperVideo->getOverview(&s_title, &s_episode_name, &s_release_date, &s_runtime, &s_IMDB_ID, NULL); + s_image = scraperVideo->getImage( cImageLevels(eImageLevel::episodeMovie, eImageLevel::seasonMovie, eImageLevel::tvShowCollection, eImageLevel::anySeasonCollection), - cOrientations(eOrientation::landscape, eOrientation::portrait, eOrientation::banner), &s_releaseDate ); - scraperOverview.call(LiveSetup().GetPluginScraper()); + cOrientations(eOrientation::landscape, eOrientation::portrait, eOrientation::banner), false); + AppendScraperData(target, s_IMDB_ID, s_image, scraperVideo->getVideoType(), s_title, scraperVideo->getSeasonNumber(), scraperVideo->getEpisodeNumber(), s_episode_name, s_runtime, s_release_date); +} +bool appendEpgItem(cLargeString &epg_item, RecordingsItemPtr &recItem, const cEvent *Event, const cChannel *Channel, bool withChannel) { + cGetScraperVideo getScraperVideo(Event, NULL); + getScraperVideo.call(LiveSetup().GetPluginScraper()); RecordingsTreePtr recordingsTree(LiveRecordingsManager()->GetRecordingsTree()); const std::vector *recItems = recordingsTree->allRecordings(eSortOrder::duplicatesLanguage); - bool recItemFound = searchNameDesc(recItem, recItems, Event, &scraperOverview); + bool recItemFound = searchNameDesc(recItem, recItems, Event, getScraperVideo.m_scraperVideo.get() ); epg_item.append("[\""); // [0] : EPG ID (without event_) @@ -513,29 +523,33 @@ bool appendEpgItem(cLargeString &epg_item, RecordingsItemPtr &recItem, const cEv // [1] : Timer ID const cTimer* timer = LiveTimerManager().GetTimer(Event->EventID(), Channel->GetChannelID() ); if (timer) epg_item.append(vdrlive::EncodeDomId(LiveTimerManager().GetTimers().GetTimerId(*timer), ".-:", "pmc")); - epg_item.append("\",\""); -// [2] : Day, time & duration of event - AppendDateTime(epg_item, tr("%I:%M %p"), Event->StartTime() ); - epg_item.append(" - "); - AppendDateTime(epg_item, tr("%I:%M %p"), Event->EndTime() ); - epg_item.append(" "); - AppendDuration(epg_item, tr("(%d:%02d)"), Event->Duration() /60/60, Event->Duration()/60 % 60); epg_item.append("\","); - AppendScraperData(epg_item, s_image, scraperOverview.m_videoType, s_title, scraperOverview.m_seasonNumber, scraperOverview.m_episodeNumber, s_episode_name, scraperOverview.m_runtime, s_releaseDate); +// scraper data + AppendScraperData(epg_item, getScraperVideo.m_scraperVideo.get() ); epg_item.append(","); +// [9] : channelnr if (withChannel) { epg_item.append(Channel->Number()); epg_item.append(",\""); +// [10] : channelname AppendHtmlEscapedAndCorrectNonUTF8(epg_item, Channel->Name() ); } else epg_item.append("0,\""); epg_item.append("\",\""); +// [11] : Name AppendHtmlEscapedAndCorrectNonUTF8(epg_item, Event->Title() ); epg_item.append("\",\""); +// [12] : Shorttext AppendHtmlEscapedAndCorrectNonUTF8(epg_item, Event->ShortText() ); epg_item.append("\",\""); +// [13] : Description AppendTextTruncateOnWord(epg_item, Event->Description(), LiveSetup().GetMaxTooltipChars(), true); epg_item.append("\",\""); - epg_item.append(s_IMDB_ID); +// [14] : Day, time & duration of event + AppendDateTime(epg_item, tr("%I:%M %p"), Event->StartTime() ); + epg_item.append(" - "); + AppendDateTime(epg_item, tr("%I:%M %p"), Event->EndTime() ); + epg_item.append(" "); + AppendDuration(epg_item, tr("(%d:%02d)"), Event->Duration() /60/60, Event->Duration()/60 % 60); epg_item.append("\"]"); return recItemFound; } diff --git a/live/css/styles.css b/live/css/styles.css index 866cd7f9..e0be3a65 100644 --- a/live/css/styles.css +++ b/live/css/styles.css @@ -686,6 +686,11 @@ table td.topaligned { table td.padded { padding: 3px 7px 3px 3px; } +table td.cast_role { + text-align: left; + font-weight: bold; +} + /* ################ # Event (page: whats_on.html) diff --git a/livefeatures.h b/livefeatures.h index c48107d1..c578ca26 100644 --- a/livefeatures.h +++ b/livefeatures.h @@ -72,6 +72,12 @@ namespace features static const char* Plugin() { return "streamdev-server"; } static const char* MinVersion() { return "?"; } }; + + struct tvscraper + { + static const char* Plugin() { return "tvscraper"; } + static const char* MinVersion() { return "1.1.9"; } + }; } // namespace features } // namespace vdrlive diff --git a/pages/epginfo.ecpp b/pages/epginfo.ecpp index 9af2b60b..15ac8980 100644 --- a/pages/epginfo.ecpp +++ b/pages/epginfo.ecpp @@ -81,10 +81,10 @@ using namespace vdrlive; throw HtmlError(tr("Couldn't find recording or no recordings available")); } epgEvent = EpgEvents::CreateEpgInfo(epgid, recording); - cTvMedia image; + cTvMedia image; EpgEvents::PosterTvscraper(image, NULL, recording); epgImage = image.path; - irecording = (uintptr_t)recording; + irecording = (uintptr_t)recording; } // check for event: else if (epgid.compare(0, event_s.length(), event_s) == 0) { @@ -107,10 +107,10 @@ using namespace vdrlive; epgEvent = EpgEvents::CreateEpgInfo(epgid, schedules); #endif const cEvent* event = epgEvent->Event(); - cTvMedia image; + cTvMedia image; EpgEvents::PosterTvscraper(image, event, NULL); epgImage = image.path; - i_event = (uintptr_t)event; + i_event = (uintptr_t)event; } // check for aboutbox: else if (epgid.compare(0, aboutbox.length(), aboutbox) == 0) { @@ -152,14 +152,15 @@ using namespace vdrlive; start += " "; AppendDuration(start, tr("(%d:%02d)"), epgEvent->Duration()/60/60, epgEvent->Duration()/60 % 60); } else { // recording - start += ". "; - start += tr("Recording length"); - start += ": "; + start += " ("; AppendDuration(start, tr("%d:%02d"), epgEvent->Duration()/60/60, epgEvent->Duration()/60 % 60); - start += ". "; - start += tr("Event duration"); - start += ": "; + start += "/"; AppendDuration(start, tr("%d:%02d"), epgEvent->EventDuration()/60/60, epgEvent->EventDuration()/60 % 60); + start += ") ("; + start += tr("Recording length"); + start += "/"; + start += tr("Event duration"); + start += ")"; } std::string tools_component; diff --git a/pages/pageelems.ecpp b/pages/pageelems.ecpp index c946c763..2b937e71 100644 --- a/pages/pageelems.ecpp +++ b/pages/pageelems.ecpp @@ -38,8 +38,9 @@ std::string toString(const std::vector &vec) { if (!result.empty() ) result.append(", "); result.append(actor.name); if (!actor.role.empty() ) { - result.append(" as "); + result.append(" ("); result.append(actor.role); + result.append(")"); } } return result; @@ -69,7 +70,7 @@ function addErrorIcon(s, numErrors, durationDeviation) { if (numErrors <= -100) return s.a += '
' if (durationDeviation > 300 || numErrors > 0) s.a += 'RecordingErrors.png' - else if (durationDeviation > 180) s.a += 'NotCheckedForRecordingErrors.png' + else if (durationDeviation > 60) s.a += 'NotCheckedForRecordingErrors.png' else { if (numErrors == 0) s.a += 'NoRecordingErrors.png' if (numErrors > 0) s.a += 'RecordingErrors.png' @@ -112,7 +113,7 @@ function IMDBS(s, im_db, name) { // [0] : EPG ID (without event_) // [1] : Timer ID = SortedTimers::EncodeDomId(LiveTimerManager().GetTimers().GetTimerId(*timer)) // (without timer_) -// [2] : Day, time & duration of event +// [2] : scraper s_IMDB_ID // [3] : image.path (nach "/tvscraper/") // [4] : "pt" if m_s_image.width <= m_s_image.height, otherwise= "" // [5] : title (scraper) @@ -124,7 +125,7 @@ function IMDBS(s, im_db, name) { // [11] : Name // [12] : Shorttext // [13] : Description -// [14] : scraper s_IMDB_ID +// [14] : Day, time & duration of event #> function addEvent(s, bottomrow_i, obj) { // first column, timer /red or green, ... @@ -146,7 +147,7 @@ function addEvent(s, bottomrow_i, obj) { s.a += obj[11] s.a += '\">\" title=\"<$tr("Search for repeats.")$>\"/>' <%cpp> } if (LiveSetup().GetShowIMDb() ) { - IMDBS(s, obj[14], obj[11]) + IMDBS(s, obj[2], obj[11]) <%cpp> } s.a += '' // second col, dependent on width: channel @@ -164,7 +165,7 @@ function addEvent(s, bottomrow_i, obj) { s.a += '' <%cpp> } s.a += `
` - s.a += obj[2] + s.a += obj[14] s.a += '
' // col with times, channel, name, short text s.a += `
` @@ -176,7 +177,7 @@ function addEvent(s, bottomrow_i, obj) { s.a += '\">' } s.a += '' - s.a += obj[2] + s.a += obj[14] if (obj[9] > 0) { s.a += ' ' s.a += obj[10] @@ -232,7 +233,7 @@ function existingRecordingSR(s, col_span1, bottomrow, imdb_id, id, archiveDescr, addColEventRec(s, day_time_duration, 'recording_', id, name, folder, short_text, description, '<$$lf$>', '<$tr("Click to view details.")$>') s.a += '' } -function existingRecordingString(col_span, bottomrow, imdb_id, id, archiveDescr, image, pt, s_title, s_season_episode, s_runtime, s_date, day_time_duration, errors, hd_sd, channel_name, newR, name, short_text, description, durationDeviation, folder) { +function existingRecordingString(col_span, bottomrow, id, archiveDescr, imdb_id, image, pt, s_title, s_season_episode, s_runtime, s_date, day_time_duration, errors, hd_sd, channel_name, newR, name, short_text, description, durationDeviation, folder) { const s = Object.create(null) s.a = "" existingRecordingSR(s, col_span, bottomrow, imdb_id, id, archiveDescr, image, pt, s_title, s_season_episode, s_runtime, s_date, day_time_duration, errors, hd_sd, channel_name, newR, name, short_text, description, durationDeviation, folder) @@ -602,15 +603,87 @@ if (LiveSetup().GetUseStreamdev() && LiveFeatures().
<%cpp> -cScraperMovieOrTv scraperMovieOrTv; -scraperMovieOrTv.found = false; -scraperMovieOrTv.event = static_cast((void *)ievent); -scraperMovieOrTv.recording = static_cast((void *)irecording); -scraperMovieOrTv.httpImagePaths = false; -scraperMovieOrTv.media = true; -ScraperCallService("GetScraperMovieOrTv", &scraperMovieOrTv); +struct sEpisode2 { + int number; + int season; + int absoluteNumber; + std::string name; + std::string firstAired; + int runtime; + std::string overview; + float vote_average; + int vote_count; + cTvMedia episodeImage; + std::string IMDB_ID; +}; +struct sScraperMovieOrTv { + bool found; + bool movie; + std::string title; + std::string originalTitle; + std::string tagline; + std::string overview; + std::vector genres; + std::string homepage; + std::string releaseDate; // for TV shows: firstAired + bool adult; + int runtime; + float popularity; + float voteAverage; + int voteCount; + std::vector productionCountries; + std::string IMDB_ID; + std::vector posters; + std::vector banners; + std::vector fanarts; +// only for movies + int budget; + int revenue; + int collectionId; + std::string collectionName; +// only for TV Shows + std::string status; + std::vector networks; + int lastSeason; +// episode related + bool episodeFound; + cTvMedia seasonPoster; + sEpisode2 episode; +}; + +sScraperMovieOrTv scraperMovieOrTv; +cGetScraperVideo getScraperVideo(static_cast((void *)ievent), static_cast((void *)irecording)); +scraperMovieOrTv.found = getScraperVideo.call(LiveSetup().GetPluginScraper()); +if (scraperMovieOrTv.found) { + tvType type = getScraperVideo.m_scraperVideo->getVideoType(); +// esyslog("live: type = %i", (int)type); + if (type == tMovie) scraperMovieOrTv.movie = true; + else { + if (type == tSeries) scraperMovieOrTv.movie = false; + else scraperMovieOrTv.found = false; + } +} +if (scraperMovieOrTv.found) { + scraperMovieOrTv.found = getScraperVideo.m_scraperVideo->getMovieOrTv(&scraperMovieOrTv.title, &scraperMovieOrTv.originalTitle, &scraperMovieOrTv.tagline, &scraperMovieOrTv.overview, &scraperMovieOrTv.genres, &scraperMovieOrTv.homepage, &scraperMovieOrTv.releaseDate, &scraperMovieOrTv.adult, &scraperMovieOrTv.runtime, &scraperMovieOrTv.popularity, &scraperMovieOrTv.voteAverage, &scraperMovieOrTv.voteCount, &scraperMovieOrTv.productionCountries, &scraperMovieOrTv.IMDB_ID, &scraperMovieOrTv.budget, &scraperMovieOrTv.revenue, &scraperMovieOrTv.collectionId, &scraperMovieOrTv.collectionName, &scraperMovieOrTv.status, &scraperMovieOrTv.networks, &scraperMovieOrTv.lastSeason); +} +std::vector> scraperCharacters; +if (scraperMovieOrTv.found) { + scraperCharacters = getScraperVideo.m_scraperVideo->getCharacters(false); + scraperMovieOrTv.posters = getScraperVideo.m_scraperVideo->getImages(eOrientation::portrait, 3, false); + scraperMovieOrTv.banners = getScraperVideo.m_scraperVideo->getImages(eOrientation::banner, 3, false); + scraperMovieOrTv.fanarts = getScraperVideo.m_scraperVideo->getImages(eOrientation::landscape, 3, false); + scraperMovieOrTv.episodeFound = getScraperVideo.m_scraperVideo->getEpisode(&scraperMovieOrTv.episode.name, &scraperMovieOrTv.episode.overview, &scraperMovieOrTv.episode.absoluteNumber, &scraperMovieOrTv.episode.firstAired, &scraperMovieOrTv.episode.runtime, &scraperMovieOrTv.episode.vote_average, &scraperMovieOrTv.episode.vote_count, &scraperMovieOrTv.episode.IMDB_ID); + if (scraperMovieOrTv.episodeFound) { + scraperMovieOrTv.episode.number = getScraperVideo.m_scraperVideo->getEpisodeNumber(); + scraperMovieOrTv.episode.season = getScraperVideo.m_scraperVideo->getSeasonNumber(); + scraperMovieOrTv.episode.episodeImage = getScraperVideo.m_scraperVideo->getImage(cImageLevels(eImageLevel::episodeMovie), cOrientations(eOrientation::landscape), false); + scraperMovieOrTv.seasonPoster = getScraperVideo.m_scraperVideo->getImage(cImageLevels(eImageLevel::seasonMovie), cOrientations(eOrientation::portrait), false); + } +} + const bool episodeFound = scraperMovieOrTv.found && (!scraperMovieOrTv.movie) && scraperMovieOrTv.episodeFound; if (scraperMovieOrTv.found) { +// esyslog("live: create caption menu"); +
tvscraper:
+
+% Features< features::tvscraper >& tvscraper = LiveFeatures< features::tvscraper >(); + "/> +% if ( tvscraper.Recent() ) { + <$ tr("active") $>: <$ tvscraper.Version() $> +% } else { + <$ tr("required") $>: <$ tvscraper.MinVersion() $> +% } + (<$ tr("Homepage") $>)
+
<$ tr("Information providers") $>
Movie information provided by TMDB
Information provided by TheTVDB.com. Please consider supporting them
diff --git a/pages/recordings.ecpp b/pages/recordings.ecpp index b0e355c6..19a66a2c 100644 --- a/pages/recordings.ecpp +++ b/pages/recordings.ecpp @@ -146,15 +146,17 @@ function RecordingActionS(s, id, A, Img, Title) { } <# -// [0] : IMDB ID -// [1] : ID (prefix "recording_" removed !!!) -// [2] : ArchiveDescr() +// [0] : ID (prefix "recording_" removed !!!) +// [1] : ArchiveDescr() +// scraper data +// [2] : IMDB ID // [3] : image.path (nach "/tvscraper/") // [4] : "pt" if m_s_image.width <= m_s_image.height, otherwise= "" // [5] : title (scraper) // [6] : season / episode (scraper, for tv shows, if available. Otherwise: Empty) // [7] : runtime (scraper) // [8] : relase date (scraper) +// end scraper data // [9] : Day, time & duration // [10] : Number of recording errors // [11] : SD/HD/UHD s/h/u @@ -175,13 +177,13 @@ function RecordingsSt(s, level, displayFolder, data) { if (level > 0) s.a += ' ' s.a += '' // end of recording_imgs @@ -210,29 +212,29 @@ function RecordingsSt(s, level, displayFolder, data) { if (displayFolder == 1) folder = obj[18] else folder = '' s.a += '
' - addEventRec(s, 'recording_', obj[1], obj[14], folder, obj[15], obj[16], '<$$lf$>', '<$tr("Click to view details.")$>') + addEventRec(s, 'recording_', obj[0], obj[14], folder, obj[15], obj[16], '<$$lf$>', '<$tr("Click to view details.")$>') s.a += '
' // end if recording_name / short description s.a += '
' - if (obj[2].length == 0) { - RecordingActionS(s, obj[1], "vdr_request/play_recording?param=", "play.png", "<$tr("play this recording")$>") + if (obj[1].length == 0) { + RecordingActionS(s, obj[0], "vdr_request/play_recording?param=", "play.png", "<$tr("play this recording")$>") <%cpp> if (LiveSetup().GetShowPlayMediaplayer() ) { - RecordingActionS(s, obj[1], "playlist.m3u?recid=", "playlist.png", "<$tr("Stream this recording into media player.")$>") + RecordingActionS(s, obj[0], "playlist.m3u?recid=", "playlist.png", "<$tr("Stream this recording into media player.")$>") <%cpp> } if (LiveSetup().GetShowIMDb() ) { - IMDBS(s, obj[0], obj[14]) + IMDBS(s, obj[2], obj[14]) <%cpp> } - RecordingActionS(s, obj[1], "edit_recording.html?recid=", "edit.png", "<$tr("Edit recording")$>") + RecordingActionS(s, obj[0], "edit_recording.html?recid=", "edit.png", "<$tr("Edit recording")$>") s.a += '
' - RecordingActionS(s, obj[1], "recordings.html?todel=", "del.png", "<$tr("Delete this recording from hard disc!")$>") + RecordingActionS(s, obj[0], "recordings.html?todel=", "del.png", "<$tr("Delete this recording from hard disc!")$>") s.a += '
' <%cpp> if (LiveSetup().GetShowIMDb() ) { } else { - IMDBS(s, obj[0], obj[14]) + IMDBS(s, obj[2], obj[14]) <%cpp> } } s.a += '
' - if (obj[2].length != 0) { + if (obj[1].length != 0) { s.a += '
' - s.a += obj[2] + s.a += obj[1] s.a += '
' } s.a += '' diff --git a/po/cs_CZ.po b/po/cs_CZ.po index 39121bcd..dc8ffae5 100644 --- a/po/cs_CZ.po +++ b/po/cs_CZ.po @@ -510,13 +510,13 @@ msgstr "Chyba při vytváření zámku pro nahrávky" msgid "Error aquiring schedules" msgstr "Chyba při plánování" -msgid "Recording length" -msgstr "" - #, c-format msgid "%d:%02d" msgstr "" +msgid "Recording length" +msgstr "" + msgid "Event duration" msgstr "" @@ -709,16 +709,16 @@ msgstr "" msgid "Episode first aired:" msgstr "" -msgid "Episode guest stars:" +msgid "IMDB episode" msgstr "" -msgid "Episode director:" +msgid "Guest stars and crew" msgstr "" -msgid "Episode writer:" +msgid "Writer" msgstr "" -msgid "IMDB episode" +msgid "Director" msgstr "" msgid "Tagline:" @@ -736,7 +736,7 @@ msgstr "Domovská stránka" msgid "Release date:" msgstr "" -msgid "Runtimes:" +msgid "Runtime:" msgstr "" msgid "Popularity:" @@ -745,9 +745,6 @@ msgstr "" msgid "Vote average/count:" msgstr "" -msgid "Actors:" -msgstr "" - msgid "IMDB" msgstr "" @@ -760,16 +757,10 @@ msgstr "" msgid "Collection name:" msgstr "" -msgid "Director:" -msgstr "" - -msgid "Writer:" -msgstr "" - msgid "Status:" msgstr "" -msgid "Created by:" +msgid "Cast and crew" msgstr "" msgid "Authors" diff --git a/po/de_DE.po b/po/de_DE.po index 27395fbb..427a56ea 100644 --- a/po/de_DE.po +++ b/po/de_DE.po @@ -503,13 +503,13 @@ msgstr "Fehler beim Zugriffschutz für die Programminfos" msgid "Error aquiring schedules" msgstr "Fehler beim Zugriff auf Programinfos" -msgid "Recording length" -msgstr "Länge der Aufzeichnung" - #, c-format msgid "%d:%02d" msgstr "%d:%02d" +msgid "Recording length" +msgstr "Länge der Aufzeichnung" + msgid "Event duration" msgstr "Länge des EPG Eintrags" @@ -702,17 +702,17 @@ msgstr "Episode Bewertung: Durchschnitt/Anzahl:" msgid "Episode first aired:" msgstr "Episode erstmalig gesendet:" -msgid "Episode guest stars:" -msgstr "Episode Gastschauspieler:" +msgid "IMDB episode" +msgstr "Episode IMDB" -msgid "Episode director:" -msgstr "Episode Regisseur:" +msgid "Guest stars and crew" +msgstr "Gastdarsteller und crew" -msgid "Episode writer:" -msgstr "Episode Autor:" +msgid "Writer" +msgstr "Autor" -msgid "IMDB episode" -msgstr "Episode IMDB" +msgid "Director" +msgstr "Regisseur" msgid "Tagline:" msgstr "Kurze Inhaltsangabe:" @@ -729,8 +729,8 @@ msgstr "Homepage" msgid "Release date:" msgstr "Veröffentlicht am:" -msgid "Runtimes:" -msgstr "Laufzeiten:" +msgid "Runtime:" +msgstr "Länge:" msgid "Popularity:" msgstr "Beliebtheit:" @@ -738,9 +738,6 @@ msgstr "Beliebtheit:" msgid "Vote average/count:" msgstr "Bewertung: Durchschnitt/Anzahl:" -msgid "Actors:" -msgstr "Schauspieler:" - msgid "IMDB" msgstr "IMDB" @@ -753,18 +750,13 @@ msgstr "Einnahmen:" msgid "Collection name:" msgstr "Name der Sammlung:" -msgid "Director:" -msgstr "Regisseur:" - -msgid "Writer:" -msgstr "Autor:" - msgid "Status:" msgstr "Status:" -msgid "Created by:" -msgstr "Created by:" +msgid "Cast and crew" +msgstr "Besetzung und Crew" +# msgstr "Schöpfer" msgid "Authors" msgstr "Autoren" @@ -1072,6 +1064,30 @@ msgstr "Live Interactive VDR Environment" msgid "No EPG information available" msgstr "Keine EPG Daten vorhanden" +#~ msgid "Episode guest stars:" +#~ msgstr "Episode Gastdarsteller:" + +#~ msgid "Episode director:" +#~ msgstr "Episode Regisseur:" + +#~ msgid "Episode writer:" +#~ msgstr "Episode Autor:" + +#~ msgid "Director:" +#~ msgstr "Regisseur:" + +#~ msgid "Writer:" +#~ msgstr "Autor:" + +#~ msgid "Runtimes:" +#~ msgstr "Laufzeiten:" + +#~ msgid "Actors:" +#~ msgstr "Schauspieler:" + +#~ msgid "Created by:" +#~ msgstr "Created by:" + #~ msgid "mm/dd/yyyy" #~ msgstr "dd.mm.yyyy" diff --git a/po/es_ES.po b/po/es_ES.po index 8fe6130b..aacf0c00 100644 --- a/po/es_ES.po +++ b/po/es_ES.po @@ -501,13 +501,13 @@ msgstr "Error adquiriendo el bloqueo de las programaciones" msgid "Error aquiring schedules" msgstr "Error adquiriendo programaciones" -msgid "Recording length" -msgstr "" - #, c-format msgid "%d:%02d" msgstr "" +msgid "Recording length" +msgstr "" + msgid "Event duration" msgstr "" @@ -700,16 +700,16 @@ msgstr "" msgid "Episode first aired:" msgstr "" -msgid "Episode guest stars:" +msgid "IMDB episode" msgstr "" -msgid "Episode director:" +msgid "Guest stars and crew" msgstr "" -msgid "Episode writer:" +msgid "Writer" msgstr "" -msgid "IMDB episode" +msgid "Director" msgstr "" msgid "Tagline:" @@ -727,7 +727,7 @@ msgstr "P msgid "Release date:" msgstr "" -msgid "Runtimes:" +msgid "Runtime:" msgstr "" msgid "Popularity:" @@ -736,9 +736,6 @@ msgstr "" msgid "Vote average/count:" msgstr "" -msgid "Actors:" -msgstr "" - msgid "IMDB" msgstr "" @@ -751,16 +748,10 @@ msgstr "" msgid "Collection name:" msgstr "" -msgid "Director:" -msgstr "" - -msgid "Writer:" -msgstr "" - msgid "Status:" msgstr "" -msgid "Created by:" +msgid "Cast and crew" msgstr "" msgid "Authors" diff --git a/po/fi_FI.po b/po/fi_FI.po index 075cf3c1..5331d455 100644 --- a/po/fi_FI.po +++ b/po/fi_FI.po @@ -501,13 +501,13 @@ msgstr "Ohjelmatietojen lukitus epäonnistui!" msgid "Error aquiring schedules" msgstr "Ohjelmatietojen haku epäonnistui!" -msgid "Recording length" -msgstr "" - #, c-format msgid "%d:%02d" msgstr "" +msgid "Recording length" +msgstr "" + msgid "Event duration" msgstr "" @@ -700,16 +700,16 @@ msgstr "" msgid "Episode first aired:" msgstr "" -msgid "Episode guest stars:" +msgid "IMDB episode" msgstr "" -msgid "Episode director:" +msgid "Guest stars and crew" msgstr "" -msgid "Episode writer:" +msgid "Writer" msgstr "" -msgid "IMDB episode" +msgid "Director" msgstr "" msgid "Tagline:" @@ -727,7 +727,7 @@ msgstr "Kotisivu" msgid "Release date:" msgstr "" -msgid "Runtimes:" +msgid "Runtime:" msgstr "" msgid "Popularity:" @@ -736,9 +736,6 @@ msgstr "" msgid "Vote average/count:" msgstr "" -msgid "Actors:" -msgstr "" - msgid "IMDB" msgstr "" @@ -751,16 +748,10 @@ msgstr "" msgid "Collection name:" msgstr "" -msgid "Director:" -msgstr "" - -msgid "Writer:" -msgstr "" - msgid "Status:" msgstr "" -msgid "Created by:" +msgid "Cast and crew" msgstr "" msgid "Authors" diff --git a/po/fr_FR.po b/po/fr_FR.po index c0600746..f6fc089c 100644 --- a/po/fr_FR.po +++ b/po/fr_FR.po @@ -521,13 +521,13 @@ msgstr "Erreur lors de l'acquisition du verrouillage de programmation de recherc msgid "Error aquiring schedules" msgstr "Erreur lors de l'acquisition de programmation" -msgid "Recording length" -msgstr "" - #, c-format msgid "%d:%02d" msgstr "" +msgid "Recording length" +msgstr "" + msgid "Event duration" msgstr "" @@ -720,16 +720,16 @@ msgstr "" msgid "Episode first aired:" msgstr "" -msgid "Episode guest stars:" +msgid "IMDB episode" msgstr "" -msgid "Episode director:" +msgid "Guest stars and crew" msgstr "" -msgid "Episode writer:" +msgid "Writer" msgstr "" -msgid "IMDB episode" +msgid "Director" msgstr "" msgid "Tagline:" @@ -747,7 +747,7 @@ msgstr "Page d'accueil" msgid "Release date:" msgstr "" -msgid "Runtimes:" +msgid "Runtime:" msgstr "" msgid "Popularity:" @@ -756,9 +756,6 @@ msgstr "" msgid "Vote average/count:" msgstr "" -msgid "Actors:" -msgstr "" - msgid "IMDB" msgstr "" @@ -771,16 +768,10 @@ msgstr "" msgid "Collection name:" msgstr "" -msgid "Director:" -msgstr "" - -msgid "Writer:" -msgstr "" - msgid "Status:" msgstr "" -msgid "Created by:" +msgid "Cast and crew" msgstr "" msgid "Authors" diff --git a/po/it_IT.po b/po/it_IT.po index f16d7473..44f2a3a2 100644 --- a/po/it_IT.po +++ b/po/it_IT.po @@ -507,13 +507,13 @@ msgstr "Errore acquisizione blocco programmi" msgid "Error aquiring schedules" msgstr "Errore acquisizione programmi" -msgid "Recording length" -msgstr "" - #, c-format msgid "%d:%02d" msgstr "" +msgid "Recording length" +msgstr "" + msgid "Event duration" msgstr "" @@ -706,16 +706,16 @@ msgstr "" msgid "Episode first aired:" msgstr "" -msgid "Episode guest stars:" +msgid "IMDB episode" msgstr "" -msgid "Episode director:" +msgid "Guest stars and crew" msgstr "" -msgid "Episode writer:" +msgid "Writer" msgstr "" -msgid "IMDB episode" +msgid "Director" msgstr "" msgid "Tagline:" @@ -733,7 +733,7 @@ msgstr "Pagina principale" msgid "Release date:" msgstr "" -msgid "Runtimes:" +msgid "Runtime:" msgstr "" msgid "Popularity:" @@ -742,9 +742,6 @@ msgstr "" msgid "Vote average/count:" msgstr "" -msgid "Actors:" -msgstr "" - msgid "IMDB" msgstr "" @@ -757,16 +754,10 @@ msgstr "" msgid "Collection name:" msgstr "" -msgid "Director:" -msgstr "" - -msgid "Writer:" -msgstr "" - msgid "Status:" msgstr "" -msgid "Created by:" +msgid "Cast and crew" msgstr "" msgid "Authors" diff --git a/po/lt_LT.po b/po/lt_LT.po index 2edb15ad..a805bcd5 100644 --- a/po/lt_LT.po +++ b/po/lt_LT.po @@ -503,13 +503,13 @@ msgstr "Negali gauti programų užrakto" msgid "Error aquiring schedules" msgstr "Negauna programų aprašų" -msgid "Recording length" -msgstr "" - #, c-format msgid "%d:%02d" msgstr "" +msgid "Recording length" +msgstr "" + msgid "Event duration" msgstr "" @@ -702,16 +702,16 @@ msgstr "" msgid "Episode first aired:" msgstr "" -msgid "Episode guest stars:" +msgid "IMDB episode" msgstr "" -msgid "Episode director:" +msgid "Guest stars and crew" msgstr "" -msgid "Episode writer:" +msgid "Writer" msgstr "" -msgid "IMDB episode" +msgid "Director" msgstr "" msgid "Tagline:" @@ -729,7 +729,7 @@ msgstr "Pradžia" msgid "Release date:" msgstr "" -msgid "Runtimes:" +msgid "Runtime:" msgstr "" msgid "Popularity:" @@ -738,9 +738,6 @@ msgstr "" msgid "Vote average/count:" msgstr "" -msgid "Actors:" -msgstr "" - msgid "IMDB" msgstr "" @@ -753,16 +750,10 @@ msgstr "" msgid "Collection name:" msgstr "" -msgid "Director:" -msgstr "" - -msgid "Writer:" -msgstr "" - msgid "Status:" msgstr "" -msgid "Created by:" +msgid "Cast and crew" msgstr "" msgid "Authors" diff --git a/po/nl_NL.po b/po/nl_NL.po index 8d604cd9..085a9795 100644 --- a/po/nl_NL.po +++ b/po/nl_NL.po @@ -517,13 +517,13 @@ msgstr "" msgid "Error aquiring schedules" msgstr "" -msgid "Recording length" -msgstr "" - #, c-format msgid "%d:%02d" msgstr "" +msgid "Recording length" +msgstr "" + msgid "Event duration" msgstr "" @@ -716,16 +716,16 @@ msgstr "" msgid "Episode first aired:" msgstr "" -msgid "Episode guest stars:" +msgid "IMDB episode" msgstr "" -msgid "Episode director:" +msgid "Guest stars and crew" msgstr "" -msgid "Episode writer:" +msgid "Writer" msgstr "" -msgid "IMDB episode" +msgid "Director" msgstr "" msgid "Tagline:" @@ -743,7 +743,7 @@ msgstr "" msgid "Release date:" msgstr "" -msgid "Runtimes:" +msgid "Runtime:" msgstr "" msgid "Popularity:" @@ -752,9 +752,6 @@ msgstr "" msgid "Vote average/count:" msgstr "" -msgid "Actors:" -msgstr "" - msgid "IMDB" msgstr "" @@ -767,16 +764,10 @@ msgstr "" msgid "Collection name:" msgstr "" -msgid "Director:" -msgstr "" - -msgid "Writer:" -msgstr "" - msgid "Status:" msgstr "" -msgid "Created by:" +msgid "Cast and crew" msgstr "" msgid "Authors" diff --git a/po/pl_PL.po b/po/pl_PL.po index db7f4a5a..3a357ce6 100644 --- a/po/pl_PL.po +++ b/po/pl_PL.po @@ -502,13 +502,13 @@ msgstr "Błąd podczas próby blokady programu" msgid "Error aquiring schedules" msgstr "Błąd podczas ściągania programu" -msgid "Recording length" -msgstr "" - #, c-format msgid "%d:%02d" msgstr "" +msgid "Recording length" +msgstr "" + msgid "Event duration" msgstr "" @@ -701,16 +701,16 @@ msgstr "" msgid "Episode first aired:" msgstr "" -msgid "Episode guest stars:" +msgid "IMDB episode" msgstr "" -msgid "Episode director:" +msgid "Guest stars and crew" msgstr "" -msgid "Episode writer:" +msgid "Writer" msgstr "" -msgid "IMDB episode" +msgid "Director" msgstr "" msgid "Tagline:" @@ -728,7 +728,7 @@ msgstr "Strona domowa" msgid "Release date:" msgstr "" -msgid "Runtimes:" +msgid "Runtime:" msgstr "" msgid "Popularity:" @@ -737,9 +737,6 @@ msgstr "" msgid "Vote average/count:" msgstr "" -msgid "Actors:" -msgstr "" - msgid "IMDB" msgstr "" @@ -752,16 +749,10 @@ msgstr "" msgid "Collection name:" msgstr "" -msgid "Director:" -msgstr "" - -msgid "Writer:" -msgstr "" - msgid "Status:" msgstr "" -msgid "Created by:" +msgid "Cast and crew" msgstr "" msgid "Authors" diff --git a/po/ru_RU.po b/po/ru_RU.po index f407b95a..e687f3a3 100644 --- a/po/ru_RU.po +++ b/po/ru_RU.po @@ -501,13 +501,13 @@ msgstr "Ошибка во время получения расписания" msgid "Error aquiring schedules" msgstr "Ошибка в расписании" -msgid "Recording length" -msgstr "" - #, c-format msgid "%d:%02d" msgstr "" +msgid "Recording length" +msgstr "" + msgid "Event duration" msgstr "" @@ -700,16 +700,16 @@ msgstr "" msgid "Episode first aired:" msgstr "" -msgid "Episode guest stars:" +msgid "IMDB episode" msgstr "" -msgid "Episode director:" +msgid "Guest stars and crew" msgstr "" -msgid "Episode writer:" +msgid "Writer" msgstr "" -msgid "IMDB episode" +msgid "Director" msgstr "" msgid "Tagline:" @@ -727,7 +727,7 @@ msgstr "Домашняя страница" msgid "Release date:" msgstr "" -msgid "Runtimes:" +msgid "Runtime:" msgstr "" msgid "Popularity:" @@ -736,9 +736,6 @@ msgstr "" msgid "Vote average/count:" msgstr "" -msgid "Actors:" -msgstr "" - msgid "IMDB" msgstr "" @@ -751,16 +748,10 @@ msgstr "" msgid "Collection name:" msgstr "" -msgid "Director:" -msgstr "" - -msgid "Writer:" -msgstr "" - msgid "Status:" msgstr "" -msgid "Created by:" +msgid "Cast and crew" msgstr "" msgid "Authors" diff --git a/po/sk_SK.po b/po/sk_SK.po index 0c2a376b..0ecd9356 100644 --- a/po/sk_SK.po +++ b/po/sk_SK.po @@ -502,13 +502,13 @@ msgstr "Uzamknut msgid "Error aquiring schedules" msgstr "Chyba pri zskavan televzneho programu" -msgid "Recording length" -msgstr "" - #, c-format msgid "%d:%02d" msgstr "" +msgid "Recording length" +msgstr "" + msgid "Event duration" msgstr "" @@ -701,16 +701,16 @@ msgstr "" msgid "Episode first aired:" msgstr "" -msgid "Episode guest stars:" +msgid "IMDB episode" msgstr "" -msgid "Episode director:" +msgid "Guest stars and crew" msgstr "" -msgid "Episode writer:" +msgid "Writer" msgstr "" -msgid "IMDB episode" +msgid "Director" msgstr "" msgid "Tagline:" @@ -728,7 +728,7 @@ msgstr "Domovsk msgid "Release date:" msgstr "" -msgid "Runtimes:" +msgid "Runtime:" msgstr "" msgid "Popularity:" @@ -737,9 +737,6 @@ msgstr "" msgid "Vote average/count:" msgstr "" -msgid "Actors:" -msgstr "" - msgid "IMDB" msgstr "" @@ -752,16 +749,10 @@ msgstr "" msgid "Collection name:" msgstr "" -msgid "Director:" -msgstr "" - -msgid "Writer:" -msgstr "" - msgid "Status:" msgstr "" -msgid "Created by:" +msgid "Cast and crew" msgstr "" msgid "Authors" diff --git a/po/sv_SE.po b/po/sv_SE.po index 1de3ef95..52ba3848 100644 --- a/po/sv_SE.po +++ b/po/sv_SE.po @@ -501,13 +501,13 @@ msgstr "Fil vid låsning av scheman" msgid "Error aquiring schedules" msgstr "Fel vid hämtning av scheman" -msgid "Recording length" -msgstr "" - #, c-format msgid "%d:%02d" msgstr "" +msgid "Recording length" +msgstr "" + msgid "Event duration" msgstr "" @@ -700,16 +700,16 @@ msgstr "" msgid "Episode first aired:" msgstr "" -msgid "Episode guest stars:" +msgid "IMDB episode" msgstr "" -msgid "Episode director:" +msgid "Guest stars and crew" msgstr "" -msgid "Episode writer:" +msgid "Writer" msgstr "" -msgid "IMDB episode" +msgid "Director" msgstr "" msgid "Tagline:" @@ -727,7 +727,7 @@ msgstr "Hemsida" msgid "Release date:" msgstr "" -msgid "Runtimes:" +msgid "Runtime:" msgstr "" msgid "Popularity:" @@ -736,9 +736,6 @@ msgstr "" msgid "Vote average/count:" msgstr "" -msgid "Actors:" -msgstr "" - msgid "IMDB" msgstr "" @@ -751,16 +748,10 @@ msgstr "" msgid "Collection name:" msgstr "" -msgid "Director:" -msgstr "" - -msgid "Writer:" -msgstr "" - msgid "Status:" msgstr "" -msgid "Created by:" +msgid "Cast and crew" msgstr "" msgid "Authors" diff --git a/recman.cpp b/recman.cpp index f2f8e827..927de98e 100644 --- a/recman.cpp +++ b/recman.cpp @@ -537,11 +537,11 @@ int firstNonPunct(const std::string &s) { } -bool searchNameDesc(RecordingsItemPtr &RecItem, const std::vector *RecItems, const cEvent *event, cGetScraperOverview *scraperOverview) { +bool searchNameDesc(RecordingsItemPtr &RecItem, const std::vector *RecItems, const cEvent *event, cScraperVideo *scraperVideo) { if(RecItems->empty() ) return false; // there are no recordings // find all recordings with equal name - RecordingsItemPtr dummy (new RecordingsItemDummy(event, scraperOverview)); + RecordingsItemPtr dummy (new RecordingsItemDummy(event, scraperVideo)); const auto equalName = std::equal_range(RecItems->begin(), RecItems->end(), dummy, RecordingsItemPtrCompare::ByDuplicatesName); if ( equalName.first == equalName.second ) return false; // there is no recording with this name @@ -590,23 +590,23 @@ bool searchNameDesc(RecordingsItemPtr &RecItem, const std::vectornumberOfRecordings(); return result; - } + } void RecordingsItem::finishRecordingsTree() { for (auto &item: m_subdirs) item->finishRecordingsTree(); @@ -615,69 +615,68 @@ bool searchNameDesc(RecordingsItemPtr &RecItem, const std::vector::iterator iter = std::lower_bound(m_subdirs.begin(), m_subdirs.end(), dirName); - if (iter != m_subdirs.end() && !(dirName < *iter) ) return *iter; + RecordingsItemPtr RecordingsItem::addDirIfNotExists(const std::string &dirName) { + std::vector::iterator iter = std::lower_bound(m_subdirs.begin(), m_subdirs.end(), dirName); + if (iter != m_subdirs.end() && !(dirName < *iter) ) return *iter; RecordingsItemPtr recPtr (new RecordingsItemDir(dirName, Level() + 1)); m_subdirs.insert(iter, recPtr); - return recPtr; + return recPtr; } - RecordingsItemPtr RecordingsItem::addDirCollectionIfNotExists(int collectionId, const cRecording* recording) { - std::vector::iterator iter = std::lower_bound(m_subdirs.begin(), m_subdirs.end(), collectionId); - if (iter != m_subdirs.end() && !(collectionId < *iter) ) return *iter; + RecordingsItemPtr RecordingsItem::addDirCollectionIfNotExists(int collectionId, const cRecording* recording) { + std::vector::iterator iter = std::lower_bound(m_subdirs.begin(), m_subdirs.end(), collectionId); + if (iter != m_subdirs.end() && !(collectionId < *iter) ) return *iter; RecordingsItemPtr recPtr2 (new RecordingsItemDirCollection(Level() + 1, recording)); m_subdirs.insert(iter, recPtr2); - return recPtr2; + return recPtr2; } - RecordingsItemPtr RecordingsItem::addDirSeasonIfNotExists(int seasonNumber, const cRecording* recording) { -// std::vector::iterator iter = m_subdirs.find(seasonNumber); - std::vector::iterator iter = std::lower_bound(m_subdirs.begin(), m_subdirs.end(), seasonNumber); - if (iter != m_subdirs.end() && !(seasonNumber < *iter) ) return *iter; + RecordingsItemPtr RecordingsItem::addDirSeasonIfNotExists(int seasonNumber, const cRecording* recording) { + std::vector::iterator iter = std::lower_bound(m_subdirs.begin(), m_subdirs.end(), seasonNumber); + if (iter != m_subdirs.end() && !(seasonNumber < *iter) ) return *iter; RecordingsItemPtr recPtr2 (new RecordingsItemDirSeason(Level() + 1, recording)); m_subdirs.insert(iter, recPtr2); - return recPtr2; + return recPtr2; } const std::vector *RecordingsItem::getRecordings(eSortOrder sortOrder) { if (m_cmp_rec) return &m_entries; if (sortOrder == eSortOrder::name) { - if (!m_entriesSorted) { + if (!m_entriesSorted) { std::sort(m_entries.begin(), m_entries.end(), RecordingsItemPtrCompare::getComp(eSortOrder::name)); - m_entriesSorted = true; + m_entriesSorted = true; } - return &m_entries; + return &m_entries; } if (m_sortOrder == sortOrder) return &m_entries_other_sort; - if (m_entries_other_sort.empty() ) m_entries_other_sort = m_entries; + if (m_entries_other_sort.empty() ) m_entries_other_sort = m_entries; std::sort(m_entries_other_sort.begin(), m_entries_other_sort.end(), RecordingsItemPtrCompare::getComp(sortOrder)); m_sortOrder = sortOrder; return &m_entries_other_sort; } - bool RecordingsItem::matchesFilter(const std::string &filter) { - if (filter.empty() ) return true; + bool RecordingsItem::matchesFilter(const std::string &filter) { + if (filter.empty() ) return true; #ifdef HAVE_PCRE2 - StringMatch sm(filter); - return sm.Matches(Name()) or - sm.Matches(ShortText()?ShortText() : "" ) or - sm.Matches(Description()?Description() : ""); + StringMatch sm(filter); + return sm.Matches(Name()) or + sm.Matches(ShortText()?ShortText() : "" ) or + sm.Matches(Description()?Description() : ""); #endif - return true; - } + return true; + } - bool RecordingsItem::checkNew() { + bool RecordingsItem::checkNew() { if (Recording() && Recording()->GetResume() <= 0) return true; for (const auto &rec:m_entries) if (rec->Recording() && rec->Recording()->GetResume() <= 0) return true; for (const auto &subdir:m_subdirs) if (subdir->checkNew() ) return true; return false; } - void RecordingsItem::addDirList(std::vector &dirs, const std::string &basePath) + void RecordingsItem::addDirList(std::vector &dirs, const std::string &basePath) { if (!IsDir() ) return; std::string basePath0 = basePath; @@ -697,22 +696,22 @@ bool searchNameDesc(RecordingsItemPtr &RecItem, const std::vectorgetVideoType(); + m_s_dbid = getScraperVideo.m_scraperVideo->getDbId(); + getScraperVideo.m_scraperVideo->getOverview(&m_s_title, &m_s_episode_name, &m_s_release_date, &m_s_runtime, &m_s_IMDB_ID, &m_s_collection_id, collectionName); + m_s_episode_number = getScraperVideo.m_scraperVideo->getEpisodeNumber(); + m_s_season_number = getScraperVideo.m_scraperVideo->getSeasonNumber(); + m_s_image = getScraperVideo.m_scraperVideo->getImage(imageLevels, cOrientations(eOrientation::landscape, eOrientation::portrait, eOrientation::banner), false); + m_duration_deviation = getScraperVideo.m_scraperVideo->getDurationDeviation(); + m_language = getScraperVideo.m_scraperVideo->getLanguage(); + m_video_SD_HD = getScraperVideo.m_scraperVideo->getHD(); + } else { +// service "GetScraperVideo" is not available, just get the image + m_s_videoType = tNone; + EpgEvents::PosterTvscraper(m_s_image, NULL, recording); + } } int RecordingsItem::CompareTexts(const RecordingsItemPtr &second, int *numEqualChars) const @@ -751,13 +750,13 @@ bool searchNameDesc(RecordingsItemPtr &RecItem, const std::vectorm_s_videoType) return (int)m_s_videoType > (int)second->m_s_videoType; // 0 if no scraper data. Move these to the end, by using > + if (m_s_videoType != second->m_s_videoType) return (int)m_s_videoType < (int)second->m_s_videoType; // 2 if no scraper data. Move these to the end, by using < switch (m_s_videoType) { - case eVideoType::movie: + case tMovie: if (m_s_dbid != second->m_s_dbid) return m_s_dbid < second->m_s_dbid; if (!lang) return false; return m_language < second->m_language; - case eVideoType::tvShow: + case tSeries: if (m_s_dbid != second->m_s_dbid) return m_s_dbid < second->m_s_dbid; if (m_s_season_number != second->m_s_season_number) return m_s_season_number < second->m_s_season_number; if (m_s_episode_number != second->m_s_episode_number) return m_s_episode_number < second->m_s_episode_number; @@ -811,8 +810,8 @@ bool searchNameDesc(RecordingsItemPtr &RecItem, const std::vector %s", name.c_str(), parent->Name().c_str()); m_idI = idI; - m_language = language; - m_video_SD_HD = sdHdUhd; getScraperData(recording, cImageLevels(eImageLevel::episodeMovie, eImageLevel::seasonMovie, eImageLevel::tvShowCollection, eImageLevel::anySeasonCollection)); -// figure out m_duration_deviation: deviation between expected recording length (event+margin) and actual recording length - m_duration_deviation = 1000; // "worst case", in case of no other information - if (m_recording->FileName() ) { - if (m_recording->IsEdited() ) m_duration_deviation = 0; // we assume, who ever edited the recording checked for completeness - else if (m_recording->IsInUse()&&ruTimer == ruTimer) m_duration_deviation = 0; // still recording => incomplete, this check is useless - else { - if (m_recording->Info() && m_recording->Info()->GetEvent() ) { - const cEvent *m_event = m_recording->Info()->GetEvent(); - m_duration_deviation = std::max(0, m_event->Duration() + (::Setup.MarginStart + ::Setup.MarginStop) * 60 - m_recording->LengthInSeconds()); - if (m_event->Vps() ) { -// even if the event supports vps, vps can be used (or not) for the timer. - int vps_length = 0; - int vps_used_markad = RecordingsManager::GetVpsUsed(m_recording, vps_length); - int i_vps = std::abs(m_event->Duration() - m_recording->LengthInSeconds()); - bool vps_used = (vps_used_markad == 1) || (vps_used_markad == 0 && i_vps < m_duration_deviation); - if (vps_used) { - if (vps_length > 0) m_duration_deviation = std::abs(vps_length - m_recording->LengthInSeconds() ); - else if (m_recording->LengthInSeconds() >= m_event->Duration() ) m_duration_deviation = 0; - else { - m_duration_deviation = i_vps > 6*60?i_vps:0; // we assume VPS was used, und report only huge deviations > 6min - m_duration_deviation = std::min(m_duration_deviation, std::abs(m_s_runtime*60 - m_recording->LengthInSeconds())); - } - } - } - } - } - } } RecordingsItemRec::~RecordingsItemRec() @@ -878,7 +848,7 @@ template template void RecordingsItem::AppendShortTextOrDesc(std::string &target) const; template void RecordingsItem::AppendShortTextOrDesc(cLargeString &target) const; - const int RecordingsItemRec::SD_HD() + int RecordingsItemRec::SD_HD() { if ( m_video_SD_HD >= 0 ) return m_video_SD_HD; const cComponents *components = RecInfo()->Components(); @@ -916,9 +886,12 @@ template void RecordingsItem::AppendShortTextOrDesc(cLargeString & return m_video_SD_HD; } -void AppendScraperData(cLargeString &target, const cTvMedia &s_image, eVideoType s_videoType, const std::string &s_title, int s_season_number, int s_episode_number, const std::string &s_episode_name, int s_runtime, const std::string &s_release_date) { - bool scraperDataAvailable = s_videoType == eVideoType::movie || s_videoType == eVideoType::tvShow; +void AppendScraperData(cLargeString &target, const std::string &s_IMDB_ID, const cTvMedia &s_image, tvType s_videoType, const std::string &s_title, int s_season_number, int s_episode_number, const std::string &s_episode_name, int s_runtime, const std::string &s_release_date) { + bool scraperDataAvailable = s_videoType == tMovie || s_videoType == tSeries; target.append("\""); +// [2] IMDB ID + target.append(s_IMDB_ID); + target.append("\",\""); // [3] : image.path (nach "/tvscraper/") target.append(s_image.path); target.append("\",\""); @@ -928,7 +901,7 @@ void AppendScraperData(cLargeString &target, const cTvMedia &s_image, eVideoType // [5] : title (scraper) if (scraperDataAvailable) AppendHtmlEscapedAndCorrectNonUTF8(target, s_title.c_str() ); target.append("\",\""); - if (s_videoType == eVideoType::tvShow && (s_episode_number != 0 || s_season_number != 0)) { + if (s_videoType == tSeries && (s_episode_number != 0 || s_season_number != 0)) { // [6] : season/episode/episode name (scraper) target.append(s_season_number); target.append('E'); @@ -947,24 +920,21 @@ void AppendScraperData(cLargeString &target, const cTvMedia &s_image, eVideoType void RecordingsItemRec::AppendAsJSArray(cLargeString &target, bool displayFolder){ target.append("\""); -// [0] IMDB ID - target.append(m_s_IMDB_ID); - target.append("\", \""); -// [1] : ID +// [0] : ID target.append(Id().c_str() + 10); target.append("\",\""); -// [2] : ArchiveDescr() +// [1] : ArchiveDescr() if (IsArchived()) AppendHtmlEscapedAndCorrectNonUTF8(target, ArchiveDescr().c_str() ); target.append("\","); // scraper data - AppendScraperData(target, m_s_image, m_s_videoType, m_s_title, m_s_season_number, m_s_episode_number, m_s_episode_name, m_s_runtime, m_s_release_date); + AppendScraperData(target, m_s_IMDB_ID, m_s_image, m_s_videoType, m_s_title, m_s_season_number, m_s_episode_number, m_s_episode_name, m_s_runtime, m_s_release_date); // [9] : Day, time & duration target.append(",\""); AppendDateTime(target, tr("%a,"), StartTime()); // day of week target.append(' '); - AppendDateTime(target, tr("%b %d %y"), StartTime()); // date + AppendDateTime(target, tr("%b %d %y"), StartTime()); // date target.append(' '); - AppendDateTime(target, tr("%I:%M %p"), StartTime() ); // time + AppendDateTime(target, tr("%I:%M %p"), StartTime() ); // time if(Duration() >= 0) { target.append(' '); AppendDuration(target, tr("(%d:%02d)"), Duration() / 60, Duration() % 60); @@ -1031,17 +1001,17 @@ void RecordingsItemRec::AppendAsJSArray(cLargeString &target, std::vectorTitle() )), m_event(event) { - if (scraperOverview) { - m_s_videoType = scraperOverview->m_videoType; - m_s_dbid = scraperOverview->m_dbid; - m_s_episode_number = scraperOverview->m_episodeNumber; - m_s_season_number = scraperOverview->m_seasonNumber; + if (scraperVideo) { + m_s_videoType = scraperVideo->getVideoType(); + m_s_dbid = scraperVideo->getDbId(); + m_s_episode_number = scraperVideo->getEpisodeNumber(); + m_s_season_number = scraperVideo->getSeasonNumber(); } else { - m_s_videoType = eVideoType::none; + m_s_videoType = tNone; } } @@ -1058,34 +1028,26 @@ void RecordingsItemRec::AppendAsJSArray(cLargeString &target, std::vectorm_cmp_dir = RecordingsItemPtrCompare::ByReleaseDate; - recPtrTvShows->m_s_release_date = "1"; - m_root->m_subdirs.push_back(recPtrTvShows); - recPtrMovieCollections->m_s_release_date = "2"; - m_root->m_subdirs.push_back(recPtrMovieCollections); - RecordingsItemPtr recPtrOthers (new RecordingsItemDir(tr("File system view"), 1)); - recPtrOthers->m_s_release_date = "3"; - m_rootFileSystem = recPtrOthers; - m_root->m_subdirs.push_back(recPtrOthers); - } else { - m_rootFileSystem = m_root; + m_allRecordings.clear(); + if (scraperDataAvailable) { + m_root->m_cmp_dir = RecordingsItemPtrCompare::ByReleaseDate; + recPtrTvShows->m_s_release_date = "1"; + m_root->m_subdirs.push_back(recPtrTvShows); + recPtrMovieCollections->m_s_release_date = "2"; + m_root->m_subdirs.push_back(recPtrMovieCollections); + RecordingsItemPtr recPtrOthers (new RecordingsItemDir(tr("File system view"), 1)); + recPtrOthers->m_s_release_date = "3"; + m_rootFileSystem = recPtrOthers; + m_root->m_subdirs.push_back(recPtrOthers); + } else { + m_rootFileSystem = m_root; } int idI = -1; // integer id // add all recordings @@ -1099,16 +1061,6 @@ void RecordingsItemRec::AppendAsJSArray(cLargeString &target, std::vectorHierarchyLevels() + 1); else m_maxLevel = std::max(m_maxLevel, recording->HierarchyLevels() ); - int language; - auto lang_found = channelLanguages.m_channelLanguages.find(recording->Info()->ChannelID()); - if (lang_found == channelLanguages.m_channelLanguages.end() ) language = channelLanguages.m_defaultLanguage; - else language = lang_found->second; - int sdHdUhd = -1; - if (sdHdUhdAvailable) { - auto sdHdUhd_found = channelHD.m_channelHD.find(recording->Info()->ChannelID()); - if (sdHdUhd_found == channelHD.m_channelHD.end() ) sdHdUhd = 0; - else sdHdUhd = sdHdUhd_found->second; - } RecordingsItemPtr dir = m_rootFileSystem; std::string name(recording->Name()); @@ -1125,18 +1077,18 @@ void RecordingsItemRec::AppendAsJSArray(cLargeString &target, std::vectorMd5Hash(recording), recName, recording, language, sdHdUhd)); + RecordingsItemPtr recPtr (new RecordingsItemRec(idI, recMan->Md5Hash(recording), recName, recording)); dir->m_entries.push_back(recPtr); m_allRecordings.push_back(recPtr); // esyslog("live: DH: added rec: '%s'", recName.c_str()); if (scraperDataAvailable) { switch (recPtr->scraperVideoType() ) { - case eVideoType::movie: + case tMovie: if (recPtr->m_s_collection_id == 0) break; dir = recPtrMovieCollections->addDirCollectionIfNotExists(recPtr->m_s_collection_id, recording); dir->m_entries.push_back(recPtr); break; - case eVideoType::tvShow: + case tSeries: dir = recPtrTvShows->addDirIfNotExists(recPtr->scraperName() ); dir->setTvShow(recording); if (recPtr->scraperSeasonNumber() != 0 || recPtr->scraperEpisodeNumber() != 0) { @@ -1219,29 +1171,6 @@ void RecordingsItemRec::AppendAsJSArray(cLargeString &target, std::vector": " "; - std::string result = "title=\""; - AppendHtmlEscapedAndCorrectNonUTF8(result, s_title.c_str() ); - bool episodeFound = scraperOverview.m_videoType == eVideoType::tvShow && (scraperOverview.m_episodeNumber != 0 || scraperOverview.m_seasonNumber != 0); - if (episodeFound) { - result += lf; - result += 'S'; - result += std::to_string(scraperOverview.m_seasonNumber); - result += 'E'; - result += std::to_string(scraperOverview.m_episodeNumber); - AppendHtmlEscapedAndCorrectNonUTF8(result, s_episode_name.c_str() ); - } - if (scraperOverview.m_runtime) { - result += lf; - result += FormatDuration(tr("(%d:%02d)"), scraperOverview.m_runtime / 60, scraperOverview.m_runtime % 60); - } - result += "\" "; - return result; -} - // icon with recording errors, and tooltip std::string recordingErrorsHtml(int recordingErrors) { #if VDRVERSNUM >= 20505 @@ -1273,8 +1202,8 @@ std::string recordingErrorsHtml(int recordingErrors) { } // find duplicates -bool ByScraperDataAvailable(const RecordingsItemPtr &first, int videoType) { - return (int)first->scraperVideoType() > videoType; // 0 if no scraper data. Move these to the end, by using > +bool ByScraperDataAvailable(const RecordingsItemPtr &first, tvType videoType) { + return (int)first->scraperVideoType() < (int)videoType; // tNone if no scraper data. Move these to the end, by using < } void addDuplicateRecordingsNoSd(std::vector &DuplicateRecItems, RecordingsTreePtr &RecordingsTree){ @@ -1284,12 +1213,12 @@ void addDuplicateRecordingsNoSd(std::vector &DuplicateRecItem std::vector::const_iterator currentRecItem, recIterUpName, recIterUpShortText, recIterLowShortText; bool isSeries; -// see https://www.fluentcpp.com/2017/08/01/overloaded-functions-stl/ +// see https://www.fluentcpp.com/2017/08/01/overloaded-functions-stl/ // static_cast(f)); // #define RETURNS(...) noexcept(noexcept(__VA_ARGS__)) -> decltype(__VA_ARGS__){ return __VA_ARGS__; } // #define LIFT(f) [](auto&&... xs) RETURNS(f(::std::forward(xs)...)) // std::for_each(begin(numbers), end(numbers), LIFT(f)); - for (currentRecItem = std::lower_bound(recItems->begin(), recItems->end(), 0, ByScraperDataAvailable); + for (currentRecItem = std::lower_bound(recItems->begin(), recItems->end(), tNone, ByScraperDataAvailable); currentRecItem != recItems->end(); ) { // if ( (*currentRecItem)->scraperDataAvailable() ) { currentRecItem++; continue;} recIterUpName = std::upper_bound (currentRecItem , recItems->end(), *currentRecItem, RecordingsItemPtrCompare::ByDuplicatesName); diff --git a/recman.h b/recman.h index 86a33283..1051efbb 100644 --- a/recman.h +++ b/recman.h @@ -168,7 +168,7 @@ namespace vdrlive { static tCompRec getComp(eSortOrder sortOrder); }; // search a recording matching an EPG entry. The EPG entry is given with Name, ShortText, Description, Duration, scraperOverview - bool searchNameDesc(RecordingsItemPtr &RecItem, const std::vector *RecItems, const cEvent *event, cGetScraperOverview *scraperOverview); + bool searchNameDesc(RecordingsItemPtr &RecItem, const std::vector *RecItems, const cEvent *event, cScraperVideo *scraperVideo); /** * Base class for entries in recordings tree and recordings list. @@ -178,40 +178,40 @@ namespace vdrlive { */ class RecordingsItem { - friend class RecordingsTree; + friend class RecordingsTree; - protected: - RecordingsItem(const std::string& name); + protected: + RecordingsItem(const std::string& name); - public: - virtual ~RecordingsItem(); - - virtual time_t StartTime() const = 0; - virtual bool IsDir() const = 0; - virtual int Duration() const = 0; - virtual int DurationDeviation() const { return -1; } // duration deviation in seconds - virtual const std::string& Name() const { return m_name; } - virtual const std::string& NameForSearch() const { return m_name_for_search; } - virtual const char * ShortText() const { return RecInfo()? RecInfo()->ShortText():0; } - virtual const char * Description() const { return RecInfo()? RecInfo()->Description():0; } - virtual const std::string Id() const = 0; - int IdI() const { return m_idI;} + public: + virtual ~RecordingsItem(); + + virtual time_t StartTime() const = 0; + virtual bool IsDir() const = 0; + virtual int Duration() const = 0; + virtual int DurationDeviation() const { return -1; } // duration deviation in seconds + virtual const std::string& Name() const { return m_name; } + virtual const std::string& NameForSearch() const { return m_name_for_search; } + virtual const char * ShortText() const { return RecInfo()? RecInfo()->ShortText():0; } + virtual const char * Description() const { return RecInfo()? RecInfo()->Description():0; } + virtual const std::string Id() const = 0; + int IdI() const { return m_idI;} template - void AppendShortTextOrDesc(T &target) const; + void AppendShortTextOrDesc(T &target) const; - virtual const cRecording* Recording() const { return 0; } - virtual const cRecordingInfo* RecInfo() const { return 0; } + virtual const cRecording* Recording() const { return 0; } + virtual const cRecordingInfo* RecInfo() const { return 0; } void finishRecordingsTree(); // sort recursively, Order: m_cmp_rec (if defined. Otherwise: no sort) - // dirs: Order: m_cmp_dir (if defined. Otherwise: m_name_for_sort) - virtual bool operator< (const RecordingsItemPtr &sec) const { return m_name < sec->m_name; } - virtual bool operator< (const std::string &sec) const { return m_name < sec; } - virtual bool operator> (const std::string &sec) const { return m_name > sec; } - virtual bool operator< (int sec) const { return false; } - virtual bool operator> (int sec) const { return false; } - virtual int Level() { return 0; } - int numberOfRecordings(); - RecordingsItemPtr addDirIfNotExists(const std::string &dirName); + // dirs: Order: m_cmp_dir (if defined. Otherwise: m_name_for_sort) + virtual bool operator< (const RecordingsItemPtr &sec) const { return m_name < sec->m_name; } + virtual bool operator< (const std::string &sec) const { return m_name < sec; } + virtual bool operator> (const std::string &sec) const { return m_name > sec; } + virtual bool operator< (int sec) const { return false; } + virtual bool operator> (int sec) const { return false; } + virtual int Level() { return 0; } + int numberOfRecordings(); + RecordingsItemPtr addDirIfNotExists(const std::string &dirName); RecordingsItemPtr addDirCollectionIfNotExists(int collectionId, const cRecording* recording); RecordingsItemPtr addDirSeasonIfNotExists(int collectionId, const cRecording* recording); const std::vector *getRecordings(eSortOrder sortOrder); @@ -219,58 +219,60 @@ template bool checkNew(); void addDirList(std::vector &dirs, const std::string &basePath); - void setTvShow(const cRecording* recording); + void setTvShow(const cRecording* recording); void getScraperData(const cRecording* recording, const cImageLevels &imageLevels, std::string *collectionName = NULL); - bool scraperDataAvailable() const { return m_s_videoType == eVideoType::movie || m_s_videoType == eVideoType::tvShow; } - eVideoType scraperVideoType() const { return m_s_videoType; } - int scraperCollectionId() const { return m_s_collection_id; } - int scraperEpisodeNumber() const { return m_s_episode_number; } - int scraperSeasonNumber() const { return m_s_season_number; } - const std::string &scraperName() const { return m_s_title; } - const std::string &scraperReleaseDate() const { return m_s_release_date; } - const cTvMedia &scraperImage() const { return m_s_image; } - int language() const { return m_language; } - int CompareTexts(const RecordingsItemPtr &second, int *numEqualChars=NULL) const; - int CompareStD(const RecordingsItemPtr &second, int *numEqualChars=NULL) const; - bool orderDuplicates(const RecordingsItemPtr &second, bool alwaysShortText, bool lang = false) const; - // To display the recording on the UI - bool matchesFilter(const std::string &filter); - virtual const int IsArchived() const { return 0 ; } - virtual const std::string ArchiveDescr() const { return "" ; } - virtual const char *NewR() const { return "" ; } - virtual const int RecordingErrors() const { return -1; } - virtual const int SD_HD() { return 0; } - virtual const char *SD_HD_icon() { return ""; } - virtual void AppendAsJSArray(cLargeString &target, bool displayFolder) { } + bool scraperDataAvailable() const { return m_s_videoType == tMovie || m_s_videoType == tSeries; } + tvType scraperVideoType() const { return m_s_videoType; } + int scraperCollectionId() const { return m_s_collection_id; } + int scraperEpisodeNumber() const { return m_s_episode_number; } + int scraperSeasonNumber() const { return m_s_season_number; } + const std::string &scraperName() const { return m_s_title; } + const std::string &scraperReleaseDate() const { return m_s_release_date; } + const cTvMedia &scraperImage() const { return m_s_image; } + int language() const { return m_language; } + int CompareTexts(const RecordingsItemPtr &second, int *numEqualChars=NULL) const; + int CompareStD(const RecordingsItemPtr &second, int *numEqualChars=NULL) const; + bool orderDuplicates(const RecordingsItemPtr &second, bool alwaysShortText, bool lang = false) const; +// To display the recording on the UI + bool matchesFilter(const std::string &filter); + virtual const int IsArchived() const { return 0 ; } + virtual const std::string ArchiveDescr() const { return "" ; } + virtual const char *NewR() const { return "" ; } + virtual const int RecordingErrors() const { return -1; } + virtual int SD_HD() { return m_video_SD_HD; } + virtual const char *SD_HD_icon() { return ""; } + virtual void AppendAsJSArray(cLargeString &target, bool displayFolder) { } bool recEntriesSorted() { return m_cmp_rec != NULL; } bool dirEntriesSorted() { return m_cmp_dir != NULL; } - private: - std::string GetNameForSearch(std::string const & name); - protected: + private: + std::string GetNameForSearch(std::string const & name); + protected: int m_idI = -1; - std::string m_name; - const std::string m_name_for_search; - std::vector m_subdirs; - std::vector m_entries; + std::string m_name; + const std::string m_name_for_search; + std::vector m_subdirs; + std::vector m_entries; bool m_entriesSorted = false; - std::vector m_entries_other_sort; + std::vector m_entries_other_sort; eSortOrder m_sortOrder = (eSortOrder)-1; bool (*m_cmp_dir)(const RecordingsItemPtr &itemPtr1, const RecordingsItemPtr &itemPtr2) = NULL; bool (*m_cmp_rec)(const RecordingsItemPtr &itemPtr1, const RecordingsItemPtr &itemPtr2) = NULL; // scraper data - eVideoType m_s_videoType = eVideoType::none; - int m_s_dbid = 0; - std::string m_s_title = ""; - std::string m_s_episode_name = ""; - std::string m_s_IMDB_ID = ""; - std::string m_s_release_date = ""; - cTvMedia m_s_image; - int m_s_runtime = 0; - int m_s_collection_id = 0; - int m_s_episode_number = 0; - int m_s_season_number = 0; - int m_language = 0; + tvType m_s_videoType = tNone; + int m_s_dbid = 0; + std::string m_s_title = ""; + std::string m_s_episode_name = ""; + std::string m_s_IMDB_ID = ""; + std::string m_s_release_date = ""; + cTvMedia m_s_image; + int m_s_runtime = 0; + int m_s_collection_id = 0; + int m_s_episode_number = 0; + int m_s_season_number = 0; + int m_language = 0; + int m_video_SD_HD = -1; // 0 is SD, 1 is HD, 2 is UHD + int m_duration_deviation = 0; }; @@ -332,7 +334,7 @@ template class RecordingsItemRec : public RecordingsItem { public: - RecordingsItemRec(int idI, const std::string& id, const std::string& name, const cRecording* recording, int language, int sdHdUhd); + RecordingsItemRec(int idI, const std::string& id, const std::string& name, const cRecording* recording); virtual ~RecordingsItemRec(); @@ -356,7 +358,7 @@ template #endif void AppendRecordingErrorsStr(std::string &target) const; - virtual const int SD_HD(); + virtual int SD_HD(); virtual const char *SD_HD_icon() { return SD_HD() == 0 ? "sd.png": SD_HD() == 1 ? "hd.png":"ud.png"; } virtual void AppendAsJSArray(cLargeString &target, bool displayFolder); static void AppendAsJSArray(cLargeString &target, std::vector::const_iterator recIterFirst, std::vector::const_iterator recIterLast, bool &first, const std::string &filter, bool reverse); @@ -365,8 +367,6 @@ template const cRecording *m_recording; const std::string m_id; const int m_isArchived; - int m_duration_deviation; // between recording length and event length+margin - int m_video_SD_HD = -1; // 0 is SD, 1 is HD, 2 is UHD }; /** @@ -374,18 +374,18 @@ template */ class RecordingsItemDummy: public RecordingsItem { - public: - RecordingsItemDummy(const cEvent *event, cGetScraperOverview *scraperOverview = NULL); - ~RecordingsItemDummy() { }; - - virtual const char * ShortText() const { return m_event->ShortText(); } - virtual const char * Description() const { return m_event->Description(); } - virtual time_t StartTime() const { return m_event->StartTime(); } - virtual int Duration() const { return m_event->Duration() / 60; } // duration in minutes - virtual bool IsDir() const { return false; } - virtual std::string const Id() const { return ""; } - private: - const cEvent *m_event; + public: + RecordingsItemDummy(const cEvent *event, cScraperVideo *scraperVideo); + ~RecordingsItemDummy() { }; + + virtual const char * ShortText() const { return m_event->ShortText(); } + virtual const char * Description() const { return m_event->Description(); } + virtual time_t StartTime() const { return m_event->StartTime(); } + virtual int Duration() const { return m_event->Duration() / 60; } // duration in minutes + virtual bool IsDir() const { return false; } + virtual std::string const Id() const { return ""; } + private: + const cEvent *m_event; }; /** @@ -394,29 +394,29 @@ template */ class RecordingsTree { - friend class RecordingsManager; + friend class RecordingsManager; - private: - RecordingsTree(RecordingsManagerPtr recManPtr); + private: + RecordingsTree(RecordingsManagerPtr recManPtr); - public: - virtual ~RecordingsTree(); + public: + virtual ~RecordingsTree(); - RecordingsItemPtr getRoot() const { return m_root; } - std::vector getAllDirs() { std::vector result; m_rootFileSystem->addDirList(result, ""); return result; } - const std::vector *allRecordings() { return &m_allRecordings;} - const std::vector *allRecordings(eSortOrder sortOrder); + RecordingsItemPtr getRoot() const { return m_root; } + std::vector getAllDirs() { std::vector result; m_rootFileSystem->addDirList(result, ""); return result; } + const std::vector *allRecordings() { return &m_allRecordings;} + const std::vector *allRecordings(eSortOrder sortOrder); - int MaxLevel() const { return m_maxLevel; } + int MaxLevel() const { return m_maxLevel; } - private: - int m_maxLevel; - RecordingsItemPtr m_root; - RecordingsItemPtr m_rootFileSystem; - std::vector m_allRecordings; - bool m_allRecordingsSorted = false; - std::vector m_allRecordings_other_sort; - eSortOrder m_sortOrder = (eSortOrder)-1; + private: + int m_maxLevel; + RecordingsItemPtr m_root; + RecordingsItemPtr m_rootFileSystem; + std::vector m_allRecordings; + bool m_allRecordingsSorted = false; + std::vector m_allRecordings_other_sort; + eSortOrder m_sortOrder = (eSortOrder)-1; }; @@ -452,17 +452,11 @@ template std::cout << t << (a ? a->Name() : "nullptr"); } inline void swap(RecordingsItemPtr &a, RecordingsItemPtr &b) { -// print("swap, first: ", a); -// print(" sec: ", b); a.swap(b); -// print("nach swap, first: ", a); -// print(" sec: ", b); -// std::cout << "\n"; } -void AppendScraperData(cLargeString &target, const cTvMedia &s_image, eVideoType s_videoType, const std::string &s_title, int s_season_number, int s_episode_number, const std::string &s_episode_name, int s_runtime, const std::string &s_release_date); +void AppendScraperData(cLargeString &target, const std::string &s_IMDB_ID, const cTvMedia &s_image, tvType s_videoType, const std::string &s_title, int s_season_number, int s_episode_number, const std::string &s_episode_name, int s_runtime, const std::string &s_release_date); -std::string titleWithScraperData(const cGetScraperOverview &scraperOverview, const std::string &s_title, const std::string &s_episode_name); std::string recordingErrorsHtml(int recordingErrors); void addDuplicateRecordingsNoSd(std::vector &DuplicateRecItems, RecordingsTreePtr &RecordingsTree); diff --git a/services.h b/services.h index 04bee9e2..ccccc40e 100644 --- a/services.h +++ b/services.h @@ -5,7 +5,7 @@ #include #include #include -#include +#include /********************************************************************* * Helper Structures @@ -16,62 +16,11 @@ enum tvType { tNone, }; -enum class eVideoType { - none = 0, - movie = 1, - tvShow = 2, - musicVideo = 3, -}; - -enum class eOrientation { - none = 0, - banner = 1, - landscape = 2, - portrait = 3, -}; - -class cOrientations { - public: - cOrientations(eOrientation first = eOrientation::none, eOrientation second = eOrientation::none, eOrientation third = eOrientation::none) { - m_orientations = (int)first | (int(second)<<3) | (int(third)<<6); - } - private: - friend class cOrientationsInt; - int m_orientations; -}; - - -// for movies: movie (itself) or collection -// for TV Shows: episode, season, TV Show (itself), any season (fallback if on other image is available) -// example: image for event/recording: first = episodeMovie, second = seasonMovie, third = TV_ShowCollection, forth = anySeasonCollection -// if you don't want the episode still: -// first = seasonMovie, second = TV_ShowCollection, third = anySeasonCollection -// for a node with movies: TV_ShowCollection -// for a node representing a single season: first = seasonMovie, second = TV_ShowCollection, third = anySeasonCollection -enum class eImageLevel { - none = 0, - episodeMovie = 1, // for TV Shows: episode. For movies: movie (itself) - seasonMovie = 2, // for TV Shows: season. For movies: movie (itself) - tvShowCollection = 3, // for TV Shows: itself. For movies: collection - anySeasonCollection = 4, -}; - -class cImageLevels { - public: - cImageLevels(eImageLevel first = eImageLevel::none, eImageLevel second = eImageLevel::none, eImageLevel third = eImageLevel::none, eImageLevel forth = eImageLevel::none){ - m_imageLevels = (int)first | (int(second)<<3) | (int(third)<<6) | (int(forth)<<9); - } - private: - friend class cImageLevelsInt; - int m_imageLevels; -}; - - class cTvMedia { public: cTvMedia(void) { - path = ""; - width = height = 0; + path = ""; + width = height = 0; }; std::string path; int width; @@ -80,16 +29,15 @@ class cTvMedia { class cActor { public: - cActor(void) { - name = ""; - role = ""; - }; - std::string name; - std::string role; - cTvMedia actorThumb; + cActor(void) { + name = ""; + role = ""; + }; + std::string name; + std::string role; + cTvMedia actorThumb; }; - class cEpisode { public: cEpisode(void) { @@ -111,42 +59,16 @@ class cEpisode { cTvMedia episodeImage; }; -class cEpisode2 { -public: - int number; - int season; - int absoluteNumber; - std::string name; - std::string firstAired; - std::vector guestStars; - std::string overview; - float vote_average; - int vote_count; - cTvMedia episodeImage; - std::string episodeImageUrl; - std::vector director; - std::vector writer; - std::string IMDB_ID; -}; - /********************************************************************* * Data Structures for Service Calls *********************************************************************/ -// Data structure for service "GetScraperImageDir" -class cGetScraperImageDir { -public: -//in: nothing, no input required -//out - std::string scraperImageDir; // this was given to the plugin with --dir, or is the default cache directory for the plugin -}; - // Data structure for service "GetEventType" class ScraperGetEventType { public: ScraperGetEventType(void) { event = NULL; - recording = NULL; + recording = NULL; type = tNone; movieId = 0; seriesId = 0; @@ -162,7 +84,8 @@ class ScraperGetEventType { int episodeId; }; -//Data structure for full series and episode information +// Data structures for full series and episode information +// service "GetMovie" class cMovie { public: cMovie(void) { @@ -205,7 +128,8 @@ class cMovie { std::vector actors; }; -//Data structure for full series and episode information +// Data structure for full series and episode information +// service "GetSeries" class cSeries { public: cSeries(void) { @@ -238,20 +162,6 @@ class cSeries { cTvMedia seasonPoster; }; -// Data structure for service "GetPosterBanner" -class ScraperGetPosterBanner { -public: - ScraperGetPosterBanner(void) { - type = tNone; - }; -// in - const cEvent *event; // check type for this event -//out - tvType type; //typeSeries or typeMovie - cTvMedia poster; - cTvMedia banner; -}; - // Data structure for service "GetPosterBannerV2" class ScraperGetPosterBannerV2 { public: @@ -288,152 +198,165 @@ class ScraperGetPosterThumb { //out cTvMedia poster; }; -// Data structure for Kodi -// see https://alwinesch.github.io/group__cpp__kodi__addon__pvr___defs__epg___p_v_r_e_p_g_tag.html -// see https://alwinesch.github.io/group__cpp__kodi__addon__pvr___defs___recording___p_v_r_recording.html -// Series link: If set for an epg-based timer rule, matching events will be found by checking strSeriesLink instead of strTitle (and bFullTextEpgSearch) see https://github.com/xbmc/xbmc/pull/12609/files -// tag.SetFlags(PVR_RECORDING_FLAG_IS_SERIES); -// SetSeriesNumber (this is the season number) - -//Data structure for full information -class cScraperMovieOrTv { -public: -//IN - const cEvent *event; // must be NULL for recordings ; provide data for this event - const cRecording *recording; // must be NULL for events ; or for this recording - bool httpImagePaths; // if true, provide http paths to images - bool media; // if true, provide local filenames for media -//OUT -// Note: tvscraper will clear all output parameters, so you don't have to do this before calling tvscraper - bool found; - bool movie; - std::string title; - std::string originalTitle; - std::string tagline; - std::string overview; - std::vector genres; - std::string homepage; - std::string releaseDate; // for TV shows: firstAired - bool adult; - std::vector runtimes; - float popularity; - float voteAverage; - int voteCount; - std::vector productionCountries; - std::vector actors; - std::string IMDB_ID; - std::string posterUrl; // only if httpImagePaths == true - std::string fanartUrl; // only if httpImagePaths == true - std::vector posters; - std::vector banners; - std::vector fanarts; -// only for movies - int budget; - int revenue; - int collectionId; - std::string collectionName; - cTvMedia collectionPoster; - cTvMedia collectionFanart; - std::vector director; - std::vector writer; -// only for TV Shows - std::string status; - std::vector networks; - std::vector createdBy; -// episode related - bool episodeFound; - cTvMedia seasonPoster; - cEpisode2 episode; -}; +// NEW interface, used by live ========================================================= -//Data structure for live, overview information for each recording / event -// to uniquely identify a recording/event: -// movie + dbid + seasonNumber + episodeNumber (for movies, only dbid is required) -// note: if nothing was found, m_videoType = videoType::none will be returned -class cGetScraperOverview { +// Data structure for service "GetScraperImageDir" +class cGetScraperImageDir { public: - cGetScraperOverview (const cEvent *event = NULL, const cRecording *recording = NULL, std::string *title = NULL, std::string *episodeName = NULL, std::string *IMDB_ID = NULL, cTvMedia *image = NULL, cImageLevels imageLevels = cImageLevels(), cOrientations imageOrientations = cOrientations(), std::string *releaseDate = NULL, std::string *collectionName = NULL): - m_event(event), - m_recording(recording), - m_title(title), - m_episodeName(episodeName), - m_IMDB_ID(IMDB_ID), - m_image(image), - m_imageLevels(imageLevels), - m_imageOrientations(imageOrientations), - m_releaseDate(releaseDate), - m_collectionName(collectionName) - { - } - +//in: nothing, no input required +//out + std::string scraperImageDir; // this was given to the plugin with --dir, or is the default cache directory for the plugin. It will always end with a '/' bool call(cPlugin *pScraper) { - m_videoType = eVideoType::none; - if (!pScraper) return false; - else return pScraper->Service("GetScraperOverview", this); + if (!pScraper) return cPluginManager::CallFirstService("GetScraperImageDir", this); + else return pScraper->Service("GetScraperImageDir", this); } -//IN: Use constructor, setRequestedImageFormat and setRequestedImageLevel to set these values - const cEvent *m_event; // must be NULL for recordings ; provide data for this event - const cRecording *m_recording; // must be NULL for events ; or for this recording - std::string *m_title; - std::string *m_episodeName; - std::string *m_IMDB_ID; - cTvMedia *m_image; - cImageLevels m_imageLevels; - cOrientations m_imageOrientations; - std::string *m_releaseDate; - std::string *m_collectionName; -//OUT -// Note: tvscraper will clear all output parameters, so you don't have to do this before calling tvscraper - eVideoType m_videoType; - int m_dbid; - int m_runtime; -// only for movies - int m_collectionId; -// only for TV shows - int m_episodeNumber; - int m_seasonNumber; }; +// Data structure for service "GetScraperUpdateTimes" class cGetScraperUpdateTimes { public: - cGetScraperUpdateTimes() {} +//in: nothing, no input required +//out time_t m_EPG_UpdateTime; time_t m_recordingsUpdateTime; bool call(cPlugin *pScraper) { - if (!pScraper) return false; + if (!pScraper) return cPluginManager::CallFirstService("GetScraperUpdateTimes", this); else return pScraper->Service("GetScraperUpdateTimes", this); } }; -inline bool operator< (const tChannelID &c1, const tChannelID &c2) { - if (c1.Source() != c2.Source() ) return c1.Source() < c2.Source(); - if (c1.Nid() != c2.Nid() ) return c1.Nid() < c2.Nid(); - if (c1.Tid() != c2.Tid() ) return c1.Tid() < c2.Tid(); - if (c1.Sid() != c2.Sid() ) return c1.Sid() < c2.Sid(); - return c1.Rid() < c2.Rid(); -} -class cGetChannelLanguages { -public: - cGetChannelLanguages() {} - std::map m_channelLanguages; // if a channel is not in this map, it has the default language - int m_defaultLanguage; - std::map m_channelNames; - bool call(cPlugin *pScraper) { - if (!pScraper) return false; - else return pScraper->Service("GetChannelLanguages", this); - } +// =========================================================================== +// the following enums & classes are for cScraperVideo (see below) +// =========================================================================== + +enum class eCharacterType { + director = 1, // Regisseur + writer = 2, // Autor + actor = 3, + guestStar = 4, + crew = 5, + creator = 6, + producer = 7, + showrunner = 8, + musicalGuest = 9, + host = 10, + executiveProducer = 11, + screenplay = 21, // Drehbuchautor + originalMusicComposer = 31, // Komponist + others = 51, +}; + +class cCharacter { + public: + virtual eCharacterType getType() = 0; + virtual const std::string &getPersonName() = 0; // "real name" of the person + virtual const std::string &getCharacterName() = 0; // name of character in video + virtual const cTvMedia &getImage() = 0; + virtual ~cCharacter() {} +}; + +// =========================================================================== +// the following enums & classes are for cScraperVideo->getImage(...) (see below) +// you can use them to select the desired orientation and "image level" (see below) of the image returned +// =========================================================================== + +enum class eOrientation { + none = 0, + banner = 1, + landscape = 2, + portrait = 3, +}; + +// class cOrientations: combine orientations, with priorities. +// Example: You want a landscape, but, as fallback, also accept a banner. If none of these is available, even a portrait is better than no image: +// cOrientations(eOrientation::landscape, eOrientation::banner, eOrientation::portrait); +class cOrientations { + public: + cOrientations(eOrientation first = eOrientation::none, eOrientation second = eOrientation::none, eOrientation third = eOrientation::none) { + m_orientations = (int)first | (int(second)<<3) | (int(third)<<6); + } + private: + friend class cOrientationsInt; + int m_orientations; +}; + +enum class eImageLevel { + none = 0, + episodeMovie = 1, // for TV Shows: episode. For movies: movie (itself) + seasonMovie = 2, // for TV Shows: season. For movies: movie (itself) + tvShowCollection = 3, // for TV Shows: itself. For movies: collection + anySeasonCollection = 4, // for TV Shows: any season. For movies: collection }; -class cGetChannelHD { +// class cImageLevels: combine image levels, with priorities. +// example: image for event/recording: cImageLevels(eImageLevel::episodeMovie, eImageLevel::seasonMovie, eImageLevel::TV_ShowCollection, eImageLevel::anySeasonCollection) +// if you don't want the episode still: cImageLevels(eImageLevel::seasonMovie, eImageLevel::TV_ShowCollection, eImageLevel::anySeasonCollection) +class cImageLevels { + public: + cImageLevels(eImageLevel first = eImageLevel::none, eImageLevel second = eImageLevel::none, eImageLevel third = eImageLevel::none, eImageLevel forth = eImageLevel::none){ + m_imageLevels = (int)first | (int(second)<<3) | (int(third)<<6) | (int(forth)<<9); + } + private: + friend class cImageLevelsInt; + int m_imageLevels; +}; + +// The following class will be returned by the service handler method cGetScraperVideo +// note: the event/recording object used to create an instance must be valid during all method calls. +// VDR will grant such validity for about 5 seconds. + +// if you don't need a specific information, just pass NULL +// parameter fullPath: If this is false, the image paths are relative to the path returned by "GetScraperImageDir" +class cScraperVideo +{ + public: +// during creation of this instance, a movie/series/episode is identified +// with the following methods you can request the "IDs of the identified object" + virtual tvType getVideoType() = 0; // if this is tNone, nothing was identified by scraper. Still, some information (image, duration deviation ...) might be available + virtual int getDbId() = 0; // if > 0: TMDB (themoviedb) ID; if < 0: tvdb (thetvdb) ID + virtual int getEpisodeNumber() = 0; // return 0 if episode was not identified + virtual int getSeasonNumber() = 0; // return 0 if episode was not identified + +// getOverview provides the "most important" attributes + virtual bool getOverview(std::string *title, std::string *episodeName, std::string *releaseDate, int *runtime, std::string *imdbId, int *collectionId, std::string *collectionName = NULL) = 0; // return false if no scraper data is available + +// "single image, or several images" + virtual cTvMedia getImage(cImageLevels imageLevels = cImageLevels(), cOrientations imageOrientations = cOrientations(), bool fullPath = true) = 0; + virtual std::vector getImages(eOrientation orientation, int maxImages = 3, bool fullPath = true) = 0; + +// "characters, including actors" + virtual std::vector> getCharacters(bool fullPath = true) = 0; + +// other attributes, available even if getVideoType() == tNone + virtual int getDurationDeviation() = 0; + virtual int getHD() = 0; // 0: SD. 1: HD. 2: UHD + virtual int getLanguage() = 0; // return -1 in case of errors + +// the other attributes of a movie or TV show: +// note: runtime will be provided here only for movies. For tv shows, the runtime is provided with getEpisode + virtual bool getMovieOrTv(std::string *title, std::string *originalTitle, std::string *tagline, std::string *overview, std::vector *genres, std::string *homepage, std::string *releaseDate, bool *adult, int *runtime, float *popularity, float *voteAverage, int *voteCount, std::vector *productionCountries, std::string *imdbId, int *budget, int *revenue, int *collectionId, std::string *collectionName, std::string *status, std::vector *networks, int *lastSeason) = 0; + +// episode attributes. return true if getVideoType() == tSeries && episode is identified + virtual bool getEpisode(std::string *name, std::string *overview, int *absoluteNumber, std::string *firstAired, int *runtime, float *voteAverage, int *voteCount, std::string *imdbId) = 0; + virtual ~cScraperVideo() {} +}; + +// service to return cScraperVideo instance +class cGetScraperVideo { public: - cGetChannelHD() {} - std::map m_channelHD; // if a channel is not in this map, it is SD -// currently, only 0 (SD) and 1 (HD) are supported. More might be added -// note: if this map is empty, the SD/HD information was not maitained + cGetScraperVideo(const cEvent *event = NULL, const cRecording *recording = NULL): + m_event(event), m_recording(recording) { } + bool call(cPlugin *pScraper) { - if (!pScraper) return false; - else return pScraper->Service("GetChannelHD", this); + if (!pScraper) return cPluginManager::CallFirstService("GetScraperVideo", this); + return pScraper->Service("GetScraperVideo", this); } +//IN: Use constructor to set these values + const cEvent *m_event; // must be NULL for recordings ; provide data for this event + const cRecording *m_recording; // must be NULL for events ; or for this recording +//OUT + std::unique_ptr m_scraperVideo; }; + #endif // __TVSCRAPER_SERVICES_H diff --git a/setup.h b/setup.h index 3fde9fc7..f1285f4a 100644 --- a/setup.h +++ b/setup.h @@ -11,8 +11,8 @@ #include -#define LIVEVERSION "3.1.10" -#define LIVEVERSNUM 30110 +#define LIVEVERSION "3.1.11" +#define LIVEVERSNUM 30111 #define LIVESUMMARY trNOOP("Live Interactive VDR Environment") namespace vdrlive { diff --git a/tntconfig.cpp b/tntconfig.cpp index c4fe1e79..12a5807a 100644 --- a/tntconfig.cpp +++ b/tntconfig.cpp @@ -101,6 +101,13 @@ namespace vdrlive { LiveSetup().GetTvscraperImageDir() + "series", "/$1/$2/$3.$4", "image/$4"); + // Images from tvscraper, from external EPG provider (start time, channel, image) + MapUrl(app, + "^/tvscraper/epg/([^/]*)/([^/]*)/([^/]*)\\.([^./]+)", + "content", + LiveSetup().GetTvscraperImageDir() + "epg", + "/$1/$2/$3.$4", + "image/$4"); } void TntConfig::Configure(tnt::Tntnet& app) const { diff --git a/tools.cpp b/tools.cpp index 619ae39b..e7cfefa3 100644 --- a/tools.cpp +++ b/tools.cpp @@ -1,4 +1,5 @@ - +#include +#include #include "tools.h" #include "xxhash32.h" #include "setup.h" @@ -39,104 +40,103 @@ std::istream& operator>>( std::istream& is, tChannelID& ret ) namespace vdrlive { - void AppendHtmlEscaped(std::string &target, const char* s){ + void AppendHtmlEscaped(std::string &target, const char* s){ // append c-string s to target, html escape some chsracters - if(!s) return; - size_t i = 0; - const char* notAppended = s; + if(!s) return; + size_t i = 0; + const char* notAppended = s; // moving forward, notAppended is the position of the first character which is not yet appended, in i the number of not yet appended chars - for (const char* current = s; *current; current++) { - switch(*current) { - case '&': target.append(notAppended, i); target.append("&"); notAppended = notAppended + i + 1; i = 0; break; - case '\"': target.append(notAppended, i); target.append("""); notAppended = notAppended + i + 1; i = 0; break; - case '\'': target.append(notAppended, i); target.append("'"); notAppended = notAppended + i + 1; i = 0; break; - case '<': target.append(notAppended, i); target.append("<"); notAppended = notAppended + i + 1; i = 0; break; - case '>': target.append(notAppended, i); target.append(">"); notAppended = notAppended + i + 1; i = 0; break; - case 10: - case 13: target.append(notAppended, i); target.append("<br/>"); notAppended = notAppended + i + 1; i = 0; break; - default: i++; break; - } - } - target.append(notAppended, i); + for (const char* current = s; *current; current++) { + switch(*current) { + case '&': target.append(notAppended, i); target.append("&"); notAppended = notAppended + i + 1; i = 0; break; + case '\"': target.append(notAppended, i); target.append("""); notAppended = notAppended + i + 1; i = 0; break; + case '\'': target.append(notAppended, i); target.append("'"); notAppended = notAppended + i + 1; i = 0; break; + case '<': target.append(notAppended, i); target.append("<"); notAppended = notAppended + i + 1; i = 0; break; + case '>': target.append(notAppended, i); target.append(">"); notAppended = notAppended + i + 1; i = 0; break; + case 10: + case 13: target.append(notAppended, i); target.append("<br/>"); notAppended = notAppended + i + 1; i = 0; break; + default: i++; break; } + } + target.append(notAppended, i); + } template - void AppendHtmlEscapedAndCorrectNonUTF8(T &target, const char* s, const char *end, bool tooltip){ + void AppendHtmlEscapedAndCorrectNonUTF8(T &target, const char* s, const char *end, bool tooltip){ // append c-string s to target, html escape some characters // replace invalid UTF8 characters with ? - if(!s) return; - if (!end) end = s + strlen(s); - int l = 0; // length of current utf8 codepoint - size_t i = 0; // number of not yet appended chars - const char* notAppended = s; // position of the first character which is not yet appended - for (const char* current = s; *current && current < end; current+=l) { - l = utf8CodepointIsValid(current); - switch(l) { - case 1: - switch(*current) { - case '&': target.append(notAppended, i); target.append("&"); notAppended = notAppended + i + 1; i = 0; break; - case '\"': target.append(notAppended, i); target.append("""); notAppended = notAppended + i + 1; i = 0; break; - case '\'': target.append(notAppended, i); target.append("'"); notAppended = notAppended + i + 1; i = 0; break; - case '<': target.append(notAppended, i); target.append("<"); notAppended = notAppended + i + 1; i = 0; break; - case '>': target.append(notAppended, i); target.append(">"); notAppended = notAppended + i + 1; i = 0; break; - case 10: - case 13: - if (LiveSetup().GetUseAjax() || !tooltip) { - target.append(notAppended, i); target.append("<br/>"); notAppended = notAppended + i + 1; i = 0; break; - } else { - target.append(notAppended, i); target.append(*current==10?"\\n":"\\r"); notAppended = notAppended + i + 1; i = 0; break; - } - default: i++; break; - } - break; - case 2: - case 3: - case 4: - i += l; - break; - default: -// invalid UTF8 - target.append(notAppended, i); - target.append("?"); - notAppended = notAppended + i + 1; - i = 0; - l = 1; - } + if(!s) return; + if (!end) end = s + strlen(s); + int l = 0; // length of current utf8 codepoint + size_t i = 0; // number of not yet appended chars + const char* notAppended = s; // position of the first character which is not yet appended + for (const char* current = s; *current && current < end; current+=l) { +l = utf8CodepointIsValid(current); + switch(l) { + case 1: + switch(*current) { + case '&': target.append(notAppended, i); target.append("&"); notAppended = notAppended + i + 1; i = 0; break; + case '\"': target.append(notAppended, i); target.append("""); notAppended = notAppended + i + 1; i = 0; break; + case '\'': target.append(notAppended, i); target.append("'"); notAppended = notAppended + i + 1; i = 0; break; + case '<': target.append(notAppended, i); target.append("<"); notAppended = notAppended + i + 1; i = 0; break; + case '>': target.append(notAppended, i); target.append(">"); notAppended = notAppended + i + 1; i = 0; break; + case 10: + case 13: + if (LiveSetup().GetUseAjax() || !tooltip) { + target.append(notAppended, i); target.append("<br/>"); notAppended = notAppended + i + 1; i = 0; break; + } else { + target.append(notAppended, i); target.append(*current==10?"\\n":"\\r"); notAppended = notAppended + i + 1; i = 0; break; } - target.append(notAppended, i); - } + default: i++; break; + } + break; + case 2: + case 3: + case 4: + i += l; + break; + default: +// invalid UTF8 + target.append(notAppended, i); + target.append("?"); + notAppended = notAppended + i + 1; + i = 0; + l = 1; + } + } + target.append(notAppended, i); + } template void AppendHtmlEscapedAndCorrectNonUTF8(std::string &target, const char* s, const char *end, bool tooltip); template void AppendHtmlEscapedAndCorrectNonUTF8(cLargeString &target, const char* s, const char *end, bool tooltip); - void AppendCorrectNonUTF8(std::string &target, const char* s){ + void AppendCorrectNonUTF8(std::string &target, const char* s){ // append c-string s to target // replace invalid UTF8 characters with ? - if(!s) return; - int l = 0; // length of current utf8 codepoint - size_t i = 0; // number of not yet appended chars - const char* notAppended = s; // position of the first character which is not yet appended - for (const char* current = s; *current; current+=l) { - l = utf8CodepointIsValid(current); - if( l > 0) { i += l; continue; } + if(!s) return; + int l = 0; // length of current utf8 codepoint + size_t i = 0; // number of not yet appended chars + const char* notAppended = s; // position of the first character which is not yet appended + for (const char* current = s; *current; current+=l) { + l = utf8CodepointIsValid(current); + if( l > 0) { i += l; continue; } // invalid UTF8 - target.append(notAppended, i); - target.append("?"); - notAppended = notAppended + i + 1; - i = 0; - l = 1; - } - target.append(notAppended, i); - } + target.append(notAppended, i); + target.append("?"); + notAppended = notAppended + i + 1; + i = 0; + l = 1; + } + target.append(notAppended, i); + } - wint_t getNextUtfCodepoint(const char *&p){ + wint_t getNextUtfCodepoint(const char *&p) { // get next codepoint, and increment p // 0 is returned at end of string, and p will point to the end of the string (0) - if(!p || !*p) return 0; -// do { l = utf8CodepointIsValid(p); } while ( l == 0 && *(++p)); - int l = utf8CodepointIsValid(p); - if( l == 0 ) { p++; return '?'; } - return Utf8ToUtf32(p, l); - } + if(!p || !*p) return 0; + int l = utf8CodepointIsValid(p); + if( l == 0 ) { p++; return '?'; } + return Utf8ToUtf32(p, l); + } int utf8CodepointIsValid(const char *p){ // In case of invalid UTF8, return 0 @@ -148,48 +148,48 @@ template void AppendHtmlEscapedAndCorrectNonUTF8(cLargeString &tar return len; } - wint_t Utf8ToUtf32(const char *&p, int len){ -// assumes, that uft8 validity checks have already been done. len must be provided. call utf8CodepointIsValid first -// change p to position of next codepoint (p = p + len) - static const uint8_t FF_MSK[] = {0xFF >>0, 0xFF >>0, 0xFF >>3, 0xFF >>4, 0xFF >>5, 0xFF >>0, 0xFF >>0, 0xFF >>0}; - wint_t val = *p & FF_MSK[len]; - const char *q = p + len; - for (p++; p < q; p++) val = (val << 6) | (*p & 0x3F); - return val; - } + wint_t Utf8ToUtf32(const char *&p, int len) { + // assumes, that uft8 validity checks have already been done. len must be provided. call utf8CodepointIsValid first + // change p to position of next codepoint (p = p + len) + static const uint8_t FF_MSK[] = {0xFF >>0, 0xFF >>0, 0xFF >>3, 0xFF >>4, 0xFF >>5, 0xFF >>0, 0xFF >>0, 0xFF >>0}; + wint_t val = *p & FF_MSK[len]; + const char *q = p + len; + for (p++; p < q; p++) val = (val << 6) | (*p & 0x3F); + return val; + } void AppendUtfCodepoint(std::string &target, wint_t codepoint){ if (codepoint <= 0x7F){ - target.push_back( (char) (codepoint) ); - return; + target.push_back( (char) (codepoint) ); + return; } if (codepoint <= 0x07FF) { - target.push_back( (char) (0xC0 | (codepoint >> 6 ) ) ); - target.push_back( (char) (0x80 | (codepoint & 0x3F)) ); - return; + target.push_back( (char) (0xC0 | (codepoint >> 6 ) ) ); + target.push_back( (char) (0x80 | (codepoint & 0x3F)) ); + return; } if (codepoint <= 0xFFFF) { - target.push_back( (char) (0xE0 | ( codepoint >> 12)) ); - target.push_back( (char) (0x80 | ((codepoint >> 6) & 0x3F)) ); - target.push_back( (char) (0x80 | ( codepoint & 0x3F)) ); - return; + target.push_back( (char) (0xE0 | ( codepoint >> 12)) ); + target.push_back( (char) (0x80 | ((codepoint >> 6) & 0x3F)) ); + target.push_back( (char) (0x80 | ( codepoint & 0x3F)) ); + return; } - target.push_back( (char) (0xF0 | ((codepoint >> 18) & 0x07)) ); - target.push_back( (char) (0x80 | ((codepoint >> 12) & 0x3F)) ); - target.push_back( (char) (0x80 | ((codepoint >> 6) & 0x3F)) ); - target.push_back( (char) (0x80 | ( codepoint & 0x3F)) ); - return; + target.push_back( (char) (0xF0 | ((codepoint >> 18) & 0x07)) ); + target.push_back( (char) (0x80 | ((codepoint >> 12) & 0x3F)) ); + target.push_back( (char) (0x80 | ((codepoint >> 6) & 0x3F)) ); + target.push_back( (char) (0x80 | ( codepoint & 0x3F)) ); + return; } - void AppendDuration(cLargeString &target, char const* format, int hours, int minutes ) + void AppendDuration(cLargeString &target, char const* format, int hours, int minutes) { - int numChars = snprintf(target.borrowEnd(32), 32, format, hours, minutes); + int numChars = snprintf(target.borrowEnd(32), 32, format, hours, minutes); if (numChars < 0) { - target.finishBorrow(0); + target.finishBorrow(0); std::stringstream builder; builder << "cannot represent duration " << hours << ":" << minutes << " as requested"; throw std::runtime_error( builder.str() ); } - target.finishBorrow(numChars); + target.finishBorrow(numChars); } void AppendDuration(std::string &target, char const* format, int hours, int minutes ) { @@ -199,14 +199,14 @@ template void AppendHtmlEscapedAndCorrectNonUTF8(cLargeString &tar builder << "cannot represent duration " << hours << ":" << minutes << " as requested"; throw std::runtime_error( builder.str() ); } - target.append(result); + target.append(result); } std::string FormatDuration( char const* format, int hours, int minutes ) - { - std::string result; - AppendDuration(result, format, hours, minutes ); - return result; - } + { + std::string result; + AppendDuration(result, format, hours, minutes ); + return result; + } size_t AppendDateTime(char *target, int target_size, char const* format, time_t time) // writes data to target, make sure that sizeof(target) >= target_size, before calling @@ -218,7 +218,7 @@ template void AppendHtmlEscapedAndCorrectNonUTF8(cLargeString &tar builder << "cannot represent timestamp " << time << " as local time"; throw std::runtime_error( builder.str() ); } - size_t len = strftime(target, target_size, format, &tm_r); + size_t len = strftime(target, target_size, format, &tm_r); if ( len == 0 ) { std::stringstream builder; builder << "representation of timestamp " << time << " exceeds " << (target_size - 1) << " bytes"; @@ -229,19 +229,19 @@ template void AppendHtmlEscapedAndCorrectNonUTF8(cLargeString &tar void AppendDateTime(cLargeString &target, char const* format, time_t time) { int len = AppendDateTime(target.borrowEnd(80), 80, format, time); - target.finishBorrow(len); + target.finishBorrow(len); } void AppendDateTime(std::string &target, char const* format, time_t time ) { char result[80]; AppendDateTime(result, sizeof( result ), format, time); - target.append(result); + target.append(result); } std::string FormatDateTime( char const* format, time_t time ) { - char result[80]; - AppendDateTime(result, sizeof( result ), format, time); - return result; + char result[80]; + AppendDateTime(result, sizeof( result ), format, time); + return result; } std::string StringReplace( std::string const& text, std::string const& substring, std::string const& replacement ) @@ -281,7 +281,7 @@ template void AppendHtmlEscapedAndCorrectNonUTF8(cLargeString &tar { if (input.length() <= maxLen) { - truncated = false; + truncated = false; return input; } truncated = true; @@ -317,50 +317,48 @@ template void AppendHtmlEscapedAndCorrectNonUTF8(cLargeString &tar } - const char *getText(const char *shortText, const char *description) { - if (shortText && *shortText) return shortText; - return description; - } +const char *getText(const char *shortText, const char *description) { + if (shortText && *shortText) return shortText; + return description; +} // Spielfilm Thailand / Deutschland / Großbritannien 2015 (Rak ti Khon Kaen) #define MAX_LEN_ST 70 template - void AppendTextMaxLen(T &target, const char *text) { + void AppendTextMaxLen(T &target, const char *text) { // append text to target, but // stop at line break in text (10 || 13) // only up to MAX_LEN_ST characters. If such truncation is required, truncate at ' ' // escape html characters, and correct invalid utf8 - if (!text || !*text ) return; - int len = strlen(text); - int lb = len; - for (const char *s = text; *s; s++) if (*s == 10 || *s == 13) { lb = s-text; break;} - if (len < MAX_LEN_ST && lb == len) - AppendHtmlEscapedAndCorrectNonUTF8(target, text); - else if (lb < MAX_LEN_ST) { - AppendHtmlEscapedAndCorrectNonUTF8(target, text, text + lb); - target.append("..."); - } else { - const char *end = text + MAX_LEN_ST; - for (; *end && *end != ' ' && *end != 10 && *end != 13; end++); - AppendHtmlEscapedAndCorrectNonUTF8(target, text, end); - if (*end) target.append("..."); - } - } + if (!text || !*text ) return; + int len = strlen(text); + int lb = len; + for (const char *s = text; *s; s++) if (*s == 10 || *s == 13) { lb = s-text; break;} + if (len < MAX_LEN_ST && lb == len) + AppendHtmlEscapedAndCorrectNonUTF8(target, text); + else if (lb < MAX_LEN_ST) { + AppendHtmlEscapedAndCorrectNonUTF8(target, text, text + lb); + target.append("..."); + } else { + const char *end = text + MAX_LEN_ST; + for (; *end && *end != ' ' && *end != 10 && *end != 13; end++); + AppendHtmlEscapedAndCorrectNonUTF8(target, text, end); + if (*end) target.append("..."); + } + } template void AppendTextMaxLen(std::string &target, const char *text); template void AppendTextMaxLen(cLargeString &target, const char *text); template -void AppendTextTruncateOnWord(T &target, const char *text, int max_len, bool tooltip) { -// append text to target, but -// only up to max_len characters. If such truncation is required, truncate at ' ' - + void AppendTextTruncateOnWord(T &target, const char *text, int max_len, bool tooltip) { +// append text to target, but only up to max_len characters. If such truncation is required, truncate at ' ' // escape html characters, and correct invalid utf8 - if (!text || !*text ) return; - const char *end = text + std::min((int)strlen(text), max_len); - for (; *end && *end != ' '; end++); - AppendHtmlEscapedAndCorrectNonUTF8(target, text, end, tooltip); - if (*end) target.append("..."); -} + if (!text || !*text ) return; + const char *end = text + std::min((int)strlen(text), max_len); + for (; *end && *end != ' '; end++); + AppendHtmlEscapedAndCorrectNonUTF8(target, text, end, tooltip); + if (*end) target.append("..."); + } template void AppendTextTruncateOnWord(std::string &target, const char *text, int max_len, bool tooltip); template void AppendTextTruncateOnWord(cLargeString &target, const char *text, int max_len, bool tooltip); @@ -387,13 +385,13 @@ template void AppendTextTruncateOnWord(cLargeString &target, const char res[9]; uint32_t result = XXHash32::hash(str.c_str(), str.length(), 20); res[8] = 0; - for (int i = 7; i >= 0; i--) { + for (int i = 7; i >= 0; i--) { int dig = result % 16; - if (dig < 10) res[i] = dig + '0'; - else res[i] = dig - 10 + 'A'; - result /= 16; - } - return res; + if (dig < 10) res[i] = dig + '0'; + else res[i] = dig - 10 + 'A'; + result /= 16; + } + return res; } #define HOURS(x) ((x)/100) @@ -438,32 +436,31 @@ template void AppendTextTruncateOnWord(cLargeString &target, const struct urlencoder { - std::ostream& ostr_; - - explicit urlencoder( std::ostream& ostr ): ostr_( ostr ) {} - - void operator()( char ch ) - { - static const char allowedChars[] = { - // 0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , A , B , C , D , E , F , - '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', //00 - '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', //10 - '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', 0x2D,0x2E,0x2F, //20 - 0x30,0x31,0x32,0x33,0x34,0x35,0x36,0x37,0x38,0x39,'_', '_', '_', '_', '_', '_', //30 - '_', 0x41,0x42,0x43,0x44,0x45,0x46,0x47,0x48,0x49,0x4A,0x4B,0x4C,0x4D,0x4E,0x4F, //40 - 0x50,0x51,0x52,0x53,0x54,0x55,0x56,0x57,0x58,0x59,0x5A,'_', '_', '_', '_', '_', //50 - '_', 0x61,0x62,0x63,0x64,0x65,0x66,0x67,0x68,0x69,0x6A,0x6B,0x6C,0x6D,0x6E,0x6F, //60 - 0x70,0x71,0x72,0x73,0x74,0x75,0x76,0x77,0x78,0x79,0x7A,'_', '_', '_', '_', '_' //70 - // everything above 127 (for signed char, below zero) is replaced with '_' - }; - - if ( ch == ' ' ) - ostr_ << '+'; - else if ( static_cast(ch) < 0 || allowedChars[ size_t( ch ) ] == '_' ) - ostr_ << '%' << std::setw( 2 ) << std::setfill( '0' ) << std::hex << int( static_cast(ch) ); - else - ostr_ << ch; - } + std::ostream& ostr_; + explicit urlencoder( std::ostream& ostr ): ostr_( ostr ) {} + + void operator()( char ch ) + { + static const char allowedChars[] = { + // 0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , A , B , C , D , E , F , + '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', //00 + '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', //10 + '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', 0x2D,0x2E,0x2F,//20 + 0x30,0x31,0x32,0x33,0x34,0x35,0x36,0x37,0x38,0x39,'_', '_', '_', '_', '_', '_', //30 + '_', 0x41,0x42,0x43,0x44,0x45,0x46,0x47,0x48,0x49,0x4A,0x4B,0x4C,0x4D,0x4E,0x4F,//40 + 0x50,0x51,0x52,0x53,0x54,0x55,0x56,0x57,0x58,0x59,0x5A,'_', '_', '_', '_', '_', //50 + '_', 0x61,0x62,0x63,0x64,0x65,0x66,0x67,0x68,0x69,0x6A,0x6B,0x6C,0x6D,0x6E,0x6F,//60 + 0x70,0x71,0x72,0x73,0x74,0x75,0x76,0x77,0x78,0x79,0x7A,'_', '_', '_', '_', '_' //70 + // everything above 127 (for signed char, below zero) is replaced with '_' + }; + + if ( ch == ' ' ) + ostr_ << '+'; + else if ( static_cast(ch) < 0 || allowedChars[ size_t( ch ) ] == '_' ) + ostr_ << '%' << std::setw( 2 ) << std::setfill( '0' ) << std::hex << int( static_cast(ch) ); + else + ostr_ << ch; + } }; std::string StringUrlEncode( std::string const& input ) @@ -473,18 +470,6 @@ template void AppendTextTruncateOnWord(cLargeString &target, const return ostr.str(); } -// returns the content of ... - std::string GetXMLValue( std::string const& xml, std::string const& element ) - { - std::string start = "<" + element + ">"; - std::string end = ""; - size_t startPos = xml.find(start); - if (startPos == std::string::npos) return ""; - size_t endPos = xml.find(end); - if (endPos == std::string::npos) return ""; - return xml.substr(startPos + start.size(), endPos - startPos - start.size()); - } - // return the time value as time_t from formatted with time_t GetDateFromDatePicker(std::string const& datestring, std::string const& format) { @@ -512,34 +497,34 @@ template void AppendTextTruncateOnWord(cLargeString &target, const cformat = StringReplace(cformat, "yyyy", "%Y"); return FormatDateTime(cformat.c_str(), date); } -int timeStringToInt(const char *t) { -// input: t in xx:xx format -// output: time in epgsearch format (int, 100*h + min) - int h = 0, min = 0; - sscanf(t, "%2d:%2d", &h, &min); - return 100*h + min; -} -int timeStringToInt(const std::string &t) { - return timeStringToInt(t.c_str() ); -} -void intToTimeString(char *t, int tm) { -// sizeof (t) must be >= 6 "hh:mm", + ending 0 -// see int timeStringToInt(const char *t) for formats - unsigned int h = tm / 100; - unsigned int min = tm % 100; - snprintf(t, 6, "%.2u:%.2u", h%100u, min); -} + int timeStringToInt(const char *t) { + // input: t in xx:xx format + // output: time in epgsearch format (int, 100*h + min) + int h = 0, min = 0; + sscanf(t, "%2d:%2d", &h, &min); + return 100*h + min; + } + int timeStringToInt(const std::string &t) { + return timeStringToInt(t.c_str() ); + } + void intToTimeString(char *t, int tm) { + // sizeof (t) must be >= 6 "hh:mm", + ending 0 + // see int timeStringToInt(const char *t) for formats + unsigned int h = tm / 100; + unsigned int min = tm % 100; + snprintf(t, 6, "%.2u:%.2u", h%100u, min); + } -std::string intToTimeString(int tm) { -// see int timeStringToInt(const char *t) for formats - char t[6]; - intToTimeString(t, tm); - return t; -} -std::string charToString(const char *s) { - if (!s) return ""; - return s; -} + std::string intToTimeString(int tm) { + // see int timeStringToInt(const char *t) for formats + char t[6]; + intToTimeString(t, tm); + return t; + } + std::string charToString(const char *s) { + if (!s) return ""; + return s; + } std::string EncodeDomId(std::string const & toEncode, char const * from, char const * to) { @@ -762,12 +747,15 @@ std::string charToString(const char *s) { buffer_end--; *buffer_end = (i%10) + '0'; } - return buffer_end; + return buffer_end; } std::string ScraperImagePath2Live(const std::string &path){ int tvscraperImageDirLength = LiveSetup().GetTvscraperImageDir().length(); if (tvscraperImageDirLength == 0) return ""; - if (path.compare(0, tvscraperImageDirLength, LiveSetup().GetTvscraperImageDir()) != 0) return ""; + if (path.compare(0, tvscraperImageDirLength, LiveSetup().GetTvscraperImageDir()) != 0) { + esyslog("live: ERROR, image path %s does not start with %s", path.c_str(), LiveSetup().GetTvscraperImageDir().c_str()); + return ""; + } return path.substr(tvscraperImageDirLength); } @@ -776,5 +764,18 @@ std::string charToString(const char *s) { if (!pScraper) return false; return pScraper->Service(Id, Data); } - +// XML tools ******************************** + +// returns the content of ... + std::string GetXMLValue( std::string const& xml, std::string const& element ) + { + std::string start = "<" + element + ">"; + std::string end = ""; + size_t startPos = xml.find(start); + if (startPos == std::string::npos) return ""; + size_t endPos = xml.find(end); + if (endPos == std::string::npos) return ""; + return xml.substr(startPos + start.size(), endPos - startPos - start.size()); + } + } // namespace vdrlive