[PVR][Estuary] Timer settings dialog: Show client name in timer type selection dialog...
[xbmc.git] / xbmc / video / VideoDatabase.cpp
blobeb5285b402222f6138db0fbc967361f1d80981e6
1 /*
2 * Copyright (C) 2016-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
9 #include "VideoDatabase.h"
11 #include "FileItem.h"
12 #include "GUIInfoManager.h"
13 #include "GUIPassword.h"
14 #include "ServiceBroker.h"
15 #include "TextureCache.h"
16 #include "URL.h"
17 #include "Util.h"
18 #include "VideoInfoScanner.h"
19 #include "XBDateTime.h"
20 #include "addons/AddonManager.h"
21 #include "dbwrappers/dataset.h"
22 #include "dialogs/GUIDialogExtendedProgressBar.h"
23 #include "dialogs/GUIDialogKaiToast.h"
24 #include "dialogs/GUIDialogProgress.h"
25 #include "dialogs/GUIDialogYesNo.h"
26 #include "filesystem/Directory.h"
27 #include "filesystem/File.h"
28 #include "filesystem/MultiPathDirectory.h"
29 #include "filesystem/PluginDirectory.h"
30 #include "filesystem/StackDirectory.h"
31 #include "guilib/GUIComponent.h"
32 #include "guilib/GUIWindowManager.h"
33 #include "guilib/LocalizeStrings.h"
34 #include "guilib/guiinfo/GUIInfoLabels.h"
35 #include "interfaces/AnnouncementManager.h"
36 #include "messaging/helpers/DialogOKHelper.h"
37 #include "music/Artist.h"
38 #include "playlists/SmartPlayList.h"
39 #include "profiles/ProfileManager.h"
40 #include "settings/AdvancedSettings.h"
41 #include "settings/MediaSettings.h"
42 #include "settings/MediaSourceSettings.h"
43 #include "settings/Settings.h"
44 #include "settings/SettingsComponent.h"
45 #include "storage/MediaManager.h"
46 #include "utils/FileUtils.h"
47 #include "utils/GroupUtils.h"
48 #include "utils/LabelFormatter.h"
49 #include "utils/StringUtils.h"
50 #include "utils/URIUtils.h"
51 #include "utils/Variant.h"
52 #include "utils/XMLUtils.h"
53 #include "utils/log.h"
54 #include "video/VideoDbUrl.h"
55 #include "video/VideoInfoTag.h"
56 #include "video/VideoLibraryQueue.h"
57 #include "video/windows/GUIWindowVideoBase.h"
59 #include <algorithm>
60 #include <map>
61 #include <memory>
62 #include <string>
63 #include <unordered_set>
64 #include <vector>
66 using namespace dbiplus;
67 using namespace XFILE;
68 using namespace VIDEO;
69 using namespace ADDON;
70 using namespace KODI::MESSAGING;
71 using namespace KODI::GUILIB;
73 //********************************************************************************************************************************
74 CVideoDatabase::CVideoDatabase(void) = default;
76 //********************************************************************************************************************************
77 CVideoDatabase::~CVideoDatabase(void) = default;
79 //********************************************************************************************************************************
80 bool CVideoDatabase::Open()
82 return CDatabase::Open(CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_databaseVideo);
85 void CVideoDatabase::CreateTables()
87 CLog::Log(LOGINFO, "create bookmark table");
88 m_pDS->exec("CREATE TABLE bookmark ( idBookmark integer primary key, idFile integer, timeInSeconds double, totalTimeInSeconds double, thumbNailImage text, player text, playerState text, type integer)\n");
90 CLog::Log(LOGINFO, "create settings table");
91 m_pDS->exec("CREATE TABLE settings ( idFile integer, Deinterlace bool,"
92 "ViewMode integer,ZoomAmount float, PixelRatio float, VerticalShift float, AudioStream integer, SubtitleStream integer,"
93 "SubtitleDelay float, SubtitlesOn bool, Brightness float, Contrast float, Gamma float,"
94 "VolumeAmplification float, AudioDelay float, ResumeTime integer,"
95 "Sharpness float, NoiseReduction float, NonLinStretch bool, PostProcess bool,"
96 "ScalingMethod integer, DeinterlaceMode integer, StereoMode integer, StereoInvert bool, VideoStream integer,"
97 "TonemapMethod integer, TonemapParam float, Orientation integer, CenterMixLevel integer)\n");
99 CLog::Log(LOGINFO, "create stacktimes table");
100 m_pDS->exec("CREATE TABLE stacktimes (idFile integer, times text)\n");
102 CLog::Log(LOGINFO, "create genre table");
103 m_pDS->exec("CREATE TABLE genre ( genre_id integer primary key, name TEXT)\n");
104 m_pDS->exec("CREATE TABLE genre_link (genre_id integer, media_id integer, media_type TEXT)");
106 CLog::Log(LOGINFO, "create country table");
107 m_pDS->exec("CREATE TABLE country ( country_id integer primary key, name TEXT)");
108 m_pDS->exec("CREATE TABLE country_link (country_id integer, media_id integer, media_type TEXT)");
110 CLog::Log(LOGINFO, "create movie table");
111 std::string columns = "CREATE TABLE movie ( idMovie integer primary key, idFile integer";
113 for (int i = 0; i < VIDEODB_MAX_COLUMNS; i++)
114 columns += StringUtils::Format(",c{:02} text", i);
116 columns += ", idSet integer, userrating integer, premiered text)";
117 m_pDS->exec(columns);
119 CLog::Log(LOGINFO, "create actor table");
120 m_pDS->exec("CREATE TABLE actor ( actor_id INTEGER PRIMARY KEY, name TEXT, art_urls TEXT )");
121 m_pDS->exec("CREATE TABLE actor_link(actor_id INTEGER, media_id INTEGER, media_type TEXT, role TEXT, cast_order INTEGER)");
122 m_pDS->exec("CREATE TABLE director_link(actor_id INTEGER, media_id INTEGER, media_type TEXT)");
123 m_pDS->exec("CREATE TABLE writer_link(actor_id INTEGER, media_id INTEGER, media_type TEXT)");
125 CLog::Log(LOGINFO, "create path table");
126 m_pDS->exec(
127 "CREATE TABLE path ( idPath integer primary key, strPath text, strContent text, strScraper "
128 "text, strHash text, scanRecursive integer, useFolderNames bool, strSettings text, noUpdate "
129 "bool, exclude bool, allAudio bool, dateAdded text, idParentPath integer)");
131 CLog::Log(LOGINFO, "create files table");
132 m_pDS->exec("CREATE TABLE files ( idFile integer primary key, idPath integer, strFilename text, playCount integer, lastPlayed text, dateAdded text)");
134 CLog::Log(LOGINFO, "create tvshow table");
135 columns = "CREATE TABLE tvshow ( idShow integer primary key";
137 for (int i = 0; i < VIDEODB_MAX_COLUMNS; i++)
138 columns += StringUtils::Format(",c{:02} text", i);
140 columns += ", userrating integer, duration INTEGER)";
141 m_pDS->exec(columns);
143 CLog::Log(LOGINFO, "create episode table");
144 columns = "CREATE TABLE episode ( idEpisode integer primary key, idFile integer";
145 for (int i = 0; i < VIDEODB_MAX_COLUMNS; i++)
147 std::string column;
148 if ( i == VIDEODB_ID_EPISODE_SEASON || i == VIDEODB_ID_EPISODE_EPISODE || i == VIDEODB_ID_EPISODE_BOOKMARK)
149 column = StringUtils::Format(",c{:02} varchar(24)", i);
150 else
151 column = StringUtils::Format(",c{:02} text", i);
153 columns += column;
155 columns += ", idShow integer, userrating integer, idSeason integer)";
156 m_pDS->exec(columns);
158 CLog::Log(LOGINFO, "create tvshowlinkpath table");
159 m_pDS->exec("CREATE TABLE tvshowlinkpath (idShow integer, idPath integer)\n");
161 CLog::Log(LOGINFO, "create movielinktvshow table");
162 m_pDS->exec("CREATE TABLE movielinktvshow ( idMovie integer, IdShow integer)\n");
164 CLog::Log(LOGINFO, "create studio table");
165 m_pDS->exec("CREATE TABLE studio ( studio_id integer primary key, name TEXT)\n");
166 m_pDS->exec("CREATE TABLE studio_link (studio_id integer, media_id integer, media_type TEXT)");
168 CLog::Log(LOGINFO, "create musicvideo table");
169 columns = "CREATE TABLE musicvideo ( idMVideo integer primary key, idFile integer";
170 for (int i = 0; i < VIDEODB_MAX_COLUMNS; i++)
171 columns += StringUtils::Format(",c{:02} text", i);
173 columns += ", userrating integer, premiered text)";
174 m_pDS->exec(columns);
176 CLog::Log(LOGINFO, "create streaminfo table");
177 m_pDS->exec("CREATE TABLE streamdetails (idFile integer, iStreamType integer, "
178 "strVideoCodec text, fVideoAspect float, iVideoWidth integer, iVideoHeight integer, "
179 "strAudioCodec text, iAudioChannels integer, strAudioLanguage text, "
180 "strSubtitleLanguage text, iVideoDuration integer, strStereoMode text, strVideoLanguage text, "
181 "strHdrType text)");
183 CLog::Log(LOGINFO, "create sets table");
184 m_pDS->exec("CREATE TABLE sets ( idSet integer primary key, strSet text, strOverview text)");
186 CLog::Log(LOGINFO, "create seasons table");
187 m_pDS->exec("CREATE TABLE seasons ( idSeason integer primary key, idShow integer, season integer, name text, userrating integer)");
189 CLog::Log(LOGINFO, "create art table");
190 m_pDS->exec("CREATE TABLE art(art_id INTEGER PRIMARY KEY, media_id INTEGER, media_type TEXT, type TEXT, url TEXT)");
192 CLog::Log(LOGINFO, "create tag table");
193 m_pDS->exec("CREATE TABLE tag (tag_id integer primary key, name TEXT)");
194 m_pDS->exec("CREATE TABLE tag_link (tag_id integer, media_id integer, media_type TEXT)");
196 CLog::Log(LOGINFO, "create rating table");
197 m_pDS->exec("CREATE TABLE rating (rating_id INTEGER PRIMARY KEY, media_id INTEGER, media_type TEXT, rating_type TEXT, rating FLOAT, votes INTEGER)");
199 CLog::Log(LOGINFO, "create uniqueid table");
200 m_pDS->exec("CREATE TABLE uniqueid (uniqueid_id INTEGER PRIMARY KEY, media_id INTEGER, media_type TEXT, value TEXT, type TEXT)");
203 void CVideoDatabase::CreateLinkIndex(const char *table)
205 m_pDS->exec(PrepareSQL("CREATE UNIQUE INDEX ix_%s_1 ON %s (name(255))", table, table));
206 m_pDS->exec(PrepareSQL("CREATE UNIQUE INDEX ix_%s_link_1 ON %s_link (%s_id, media_type(20), media_id)", table, table, table));
207 m_pDS->exec(PrepareSQL("CREATE UNIQUE INDEX ix_%s_link_2 ON %s_link (media_id, media_type(20), %s_id)", table, table, table));
208 m_pDS->exec(PrepareSQL("CREATE INDEX ix_%s_link_3 ON %s_link (media_type(20))", table, table));
211 void CVideoDatabase::CreateForeignLinkIndex(const char *table, const char *foreignkey)
213 m_pDS->exec(PrepareSQL("CREATE UNIQUE INDEX ix_%s_link_1 ON %s_link (%s_id, media_type(20), media_id)", table, table, foreignkey));
214 m_pDS->exec(PrepareSQL("CREATE UNIQUE INDEX ix_%s_link_2 ON %s_link (media_id, media_type(20), %s_id)", table, table, foreignkey));
215 m_pDS->exec(PrepareSQL("CREATE INDEX ix_%s_link_3 ON %s_link (media_type(20))", table, table));
218 void CVideoDatabase::CreateAnalytics()
220 /* indexes should be added on any columns that are used in */
221 /* a where or a join. primary key on a column is the same as a */
222 /* unique index on that column, so there is no need to add any */
223 /* index if no other columns are referred */
225 /* order of indexes are important, for an index to be considered all */
226 /* columns up to the column in question have to have been specified */
227 /* select * from foolink where foo_id = 1, can not take */
228 /* advantage of a index that has been created on ( bar_id, foo_id ) */
229 /* however an index on ( foo_id, bar_id ) will be considered for use */
231 CLog::Log(LOGINFO, "{} - creating indices", __FUNCTION__);
232 m_pDS->exec("CREATE INDEX ix_bookmark ON bookmark (idFile, type)");
233 m_pDS->exec("CREATE UNIQUE INDEX ix_settings ON settings ( idFile )\n");
234 m_pDS->exec("CREATE UNIQUE INDEX ix_stacktimes ON stacktimes ( idFile )\n");
235 m_pDS->exec("CREATE INDEX ix_path ON path ( strPath(255) )");
236 m_pDS->exec("CREATE INDEX ix_path2 ON path ( idParentPath )");
237 m_pDS->exec("CREATE INDEX ix_files ON files ( idPath, strFilename(255) )");
239 m_pDS->exec("CREATE UNIQUE INDEX ix_movie_file_1 ON movie (idFile, idMovie)");
240 m_pDS->exec("CREATE UNIQUE INDEX ix_movie_file_2 ON movie (idMovie, idFile)");
242 m_pDS->exec("CREATE UNIQUE INDEX ix_tvshowlinkpath_1 ON tvshowlinkpath ( idShow, idPath )\n");
243 m_pDS->exec("CREATE UNIQUE INDEX ix_tvshowlinkpath_2 ON tvshowlinkpath ( idPath, idShow )\n");
244 m_pDS->exec("CREATE UNIQUE INDEX ix_movielinktvshow_1 ON movielinktvshow ( idShow, idMovie)\n");
245 m_pDS->exec("CREATE UNIQUE INDEX ix_movielinktvshow_2 ON movielinktvshow ( idMovie, idShow)\n");
247 m_pDS->exec("CREATE UNIQUE INDEX ix_episode_file_1 on episode (idEpisode, idFile)");
248 m_pDS->exec("CREATE UNIQUE INDEX id_episode_file_2 on episode (idFile, idEpisode)");
249 std::string createColIndex =
250 StringUtils::Format("CREATE INDEX ix_episode_season_episode on episode (c{:02}, c{:02})",
251 VIDEODB_ID_EPISODE_SEASON, VIDEODB_ID_EPISODE_EPISODE);
252 m_pDS->exec(createColIndex);
253 createColIndex = StringUtils::Format("CREATE INDEX ix_episode_bookmark on episode (c{:02})",
254 VIDEODB_ID_EPISODE_BOOKMARK);
255 m_pDS->exec(createColIndex);
256 m_pDS->exec("CREATE INDEX ix_episode_show1 on episode(idEpisode,idShow)");
257 m_pDS->exec("CREATE INDEX ix_episode_show2 on episode(idShow,idEpisode)");
259 m_pDS->exec("CREATE UNIQUE INDEX ix_musicvideo_file_1 on musicvideo (idMVideo, idFile)");
260 m_pDS->exec("CREATE UNIQUE INDEX ix_musicvideo_file_2 on musicvideo (idFile, idMVideo)");
262 m_pDS->exec("CREATE INDEX ixMovieBasePath ON movie ( c23(12) )");
263 m_pDS->exec("CREATE INDEX ixMusicVideoBasePath ON musicvideo ( c14(12) )");
264 m_pDS->exec("CREATE INDEX ixEpisodeBasePath ON episode ( c19(12) )");
266 m_pDS->exec("CREATE INDEX ix_streamdetails ON streamdetails (idFile)");
267 m_pDS->exec("CREATE INDEX ix_seasons ON seasons (idShow, season)");
268 m_pDS->exec("CREATE INDEX ix_art ON art(media_id, media_type(20), type(20))");
270 m_pDS->exec("CREATE INDEX ix_rating ON rating(media_id, media_type(20))");
272 m_pDS->exec("CREATE INDEX ix_uniqueid1 ON uniqueid(media_id, media_type(20), type(20))");
273 m_pDS->exec("CREATE INDEX ix_uniqueid2 ON uniqueid(media_type(20), value(20))");
275 m_pDS->exec("CREATE UNIQUE INDEX ix_actor_1 ON actor (name(255))");
276 m_pDS->exec("CREATE UNIQUE INDEX ix_actor_link_1 ON "
277 "actor_link (actor_id, media_type(20), media_id, role(255))");
278 m_pDS->exec("CREATE INDEX ix_actor_link_2 ON "
279 "actor_link (media_id, media_type(20), actor_id)");
280 m_pDS->exec("CREATE INDEX ix_actor_link_3 ON actor_link (media_type(20))");
282 CreateLinkIndex("tag");
283 CreateForeignLinkIndex("director", "actor");
284 CreateForeignLinkIndex("writer", "actor");
285 CreateLinkIndex("studio");
286 CreateLinkIndex("genre");
287 CreateLinkIndex("country");
289 CLog::Log(LOGINFO, "{} - creating triggers", __FUNCTION__);
290 m_pDS->exec("CREATE TRIGGER delete_movie AFTER DELETE ON movie FOR EACH ROW BEGIN "
291 "DELETE FROM genre_link WHERE media_id=old.idMovie AND media_type='movie'; "
292 "DELETE FROM actor_link WHERE media_id=old.idMovie AND media_type='movie'; "
293 "DELETE FROM director_link WHERE media_id=old.idMovie AND media_type='movie'; "
294 "DELETE FROM studio_link WHERE media_id=old.idMovie AND media_type='movie'; "
295 "DELETE FROM country_link WHERE media_id=old.idMovie AND media_type='movie'; "
296 "DELETE FROM writer_link WHERE media_id=old.idMovie AND media_type='movie'; "
297 "DELETE FROM movielinktvshow WHERE idMovie=old.idMovie; "
298 "DELETE FROM art WHERE media_id=old.idMovie AND media_type='movie'; "
299 "DELETE FROM tag_link WHERE media_id=old.idMovie AND media_type='movie'; "
300 "DELETE FROM rating WHERE media_id=old.idMovie AND media_type='movie'; "
301 "DELETE FROM uniqueid WHERE media_id=old.idMovie AND media_type='movie'; "
302 "END");
303 m_pDS->exec("CREATE TRIGGER delete_tvshow AFTER DELETE ON tvshow FOR EACH ROW BEGIN "
304 "DELETE FROM actor_link WHERE media_id=old.idShow AND media_type='tvshow'; "
305 "DELETE FROM director_link WHERE media_id=old.idShow AND media_type='tvshow'; "
306 "DELETE FROM studio_link WHERE media_id=old.idShow AND media_type='tvshow'; "
307 "DELETE FROM tvshowlinkpath WHERE idShow=old.idShow; "
308 "DELETE FROM genre_link WHERE media_id=old.idShow AND media_type='tvshow'; "
309 "DELETE FROM movielinktvshow WHERE idShow=old.idShow; "
310 "DELETE FROM seasons WHERE idShow=old.idShow; "
311 "DELETE FROM art WHERE media_id=old.idShow AND media_type='tvshow'; "
312 "DELETE FROM tag_link WHERE media_id=old.idShow AND media_type='tvshow'; "
313 "DELETE FROM rating WHERE media_id=old.idShow AND media_type='tvshow'; "
314 "DELETE FROM uniqueid WHERE media_id=old.idShow AND media_type='tvshow'; "
315 "END");
316 m_pDS->exec("CREATE TRIGGER delete_musicvideo AFTER DELETE ON musicvideo FOR EACH ROW BEGIN "
317 "DELETE FROM actor_link WHERE media_id=old.idMVideo AND media_type='musicvideo'; "
318 "DELETE FROM director_link WHERE media_id=old.idMVideo AND media_type='musicvideo'; "
319 "DELETE FROM genre_link WHERE media_id=old.idMVideo AND media_type='musicvideo'; "
320 "DELETE FROM studio_link WHERE media_id=old.idMVideo AND media_type='musicvideo'; "
321 "DELETE FROM art WHERE media_id=old.idMVideo AND media_type='musicvideo'; "
322 "DELETE FROM tag_link WHERE media_id=old.idMVideo AND media_type='musicvideo'; "
323 "DELETE FROM uniqueid WHERE media_id=old.idMVideo AND media_type='musicvideo'; "
324 "END");
325 m_pDS->exec("CREATE TRIGGER delete_episode AFTER DELETE ON episode FOR EACH ROW BEGIN "
326 "DELETE FROM actor_link WHERE media_id=old.idEpisode AND media_type='episode'; "
327 "DELETE FROM director_link WHERE media_id=old.idEpisode AND media_type='episode'; "
328 "DELETE FROM writer_link WHERE media_id=old.idEpisode AND media_type='episode'; "
329 "DELETE FROM art WHERE media_id=old.idEpisode AND media_type='episode'; "
330 "DELETE FROM rating WHERE media_id=old.idEpisode AND media_type='episode'; "
331 "DELETE FROM uniqueid WHERE media_id=old.idEpisode AND media_type='episode'; "
332 "END");
333 m_pDS->exec("CREATE TRIGGER delete_season AFTER DELETE ON seasons FOR EACH ROW BEGIN "
334 "DELETE FROM art WHERE media_id=old.idSeason AND media_type='season'; "
335 "END");
336 m_pDS->exec("CREATE TRIGGER delete_set AFTER DELETE ON sets FOR EACH ROW BEGIN "
337 "DELETE FROM art WHERE media_id=old.idSet AND media_type='set'; "
338 "END");
339 m_pDS->exec("CREATE TRIGGER delete_person AFTER DELETE ON actor FOR EACH ROW BEGIN "
340 "DELETE FROM art WHERE media_id=old.actor_id AND media_type IN ('actor','artist','writer','director'); "
341 "END");
342 m_pDS->exec("CREATE TRIGGER delete_tag AFTER DELETE ON tag_link FOR EACH ROW BEGIN "
343 "DELETE FROM tag WHERE tag_id=old.tag_id AND tag_id NOT IN (SELECT DISTINCT tag_id FROM tag_link); "
344 "END");
345 m_pDS->exec("CREATE TRIGGER delete_file AFTER DELETE ON files FOR EACH ROW BEGIN "
346 "DELETE FROM bookmark WHERE idFile=old.idFile; "
347 "DELETE FROM settings WHERE idFile=old.idFile; "
348 "DELETE FROM stacktimes WHERE idFile=old.idFile; "
349 "DELETE FROM streamdetails WHERE idFile=old.idFile; "
350 "END");
352 CreateViews();
355 void CVideoDatabase::CreateViews()
357 CLog::Log(LOGINFO, "create episode_view");
358 std::string episodeview = PrepareSQL("CREATE VIEW episode_view AS SELECT "
359 " episode.*,"
360 " files.strFileName AS strFileName,"
361 " path.strPath AS strPath,"
362 " files.playCount AS playCount,"
363 " files.lastPlayed AS lastPlayed,"
364 " files.dateAdded AS dateAdded,"
365 " tvshow.c%02d AS strTitle,"
366 " tvshow.c%02d AS genre,"
367 " tvshow.c%02d AS studio,"
368 " tvshow.c%02d AS premiered,"
369 " tvshow.c%02d AS mpaa,"
370 " bookmark.timeInSeconds AS resumeTimeInSeconds, "
371 " bookmark.totalTimeInSeconds AS totalTimeInSeconds, "
372 " bookmark.playerState AS playerState, "
373 " rating.rating AS rating, "
374 " rating.votes AS votes, "
375 " rating.rating_type AS rating_type, "
376 " uniqueid.value AS uniqueid_value, "
377 " uniqueid.type AS uniqueid_type "
378 "FROM episode"
379 " JOIN files ON"
380 " files.idFile=episode.idFile"
381 " JOIN tvshow ON"
382 " tvshow.idShow=episode.idShow"
383 " JOIN seasons ON"
384 " seasons.idSeason=episode.idSeason"
385 " JOIN path ON"
386 " files.idPath=path.idPath"
387 " LEFT JOIN bookmark ON"
388 " bookmark.idFile=episode.idFile AND bookmark.type=1"
389 " LEFT JOIN rating ON"
390 " rating.rating_id=episode.c%02d"
391 " LEFT JOIN uniqueid ON"
392 " uniqueid.uniqueid_id=episode.c%02d",
393 VIDEODB_ID_TV_TITLE, VIDEODB_ID_TV_GENRE,
394 VIDEODB_ID_TV_STUDIOS, VIDEODB_ID_TV_PREMIERED,
395 VIDEODB_ID_TV_MPAA, VIDEODB_ID_EPISODE_RATING_ID,
396 VIDEODB_ID_EPISODE_IDENT_ID);
397 m_pDS->exec(episodeview);
399 CLog::Log(LOGINFO, "create tvshowcounts");
400 std::string tvshowcounts = PrepareSQL("CREATE VIEW tvshowcounts AS SELECT "
401 " tvshow.idShow AS idShow,"
402 " MAX(files.lastPlayed) AS lastPlayed,"
403 " NULLIF(COUNT(episode.c12), 0) AS totalCount,"
404 " COUNT(files.playCount) AS watchedcount,"
405 " NULLIF(COUNT(DISTINCT(episode.c12)), 0) AS totalSeasons, "
406 " MAX(files.dateAdded) as dateAdded "
407 " FROM tvshow"
408 " LEFT JOIN episode ON"
409 " episode.idShow=tvshow.idShow"
410 " LEFT JOIN files ON"
411 " files.idFile=episode.idFile "
412 "GROUP BY tvshow.idShow");
413 m_pDS->exec(tvshowcounts);
415 CLog::Log(LOGINFO, "create tvshowlinkpath_minview");
416 // This view only exists to workaround a limitation in MySQL <5.7 which is not able to
417 // perform subqueries in joins.
418 // Also, the correct solution is to remove the path information altogether, since a
419 // TV series can always have multiple paths. It is used in the GUI at the moment, but
420 // such usage should be removed together with this view and the path columns in tvshow_view.
421 //!@todo Remove the hacky selection of a semi-random path for tvshows from the queries and UI
422 std::string tvshowlinkpathview = PrepareSQL("CREATE VIEW tvshowlinkpath_minview AS SELECT "
423 " idShow, "
424 " min(idPath) AS idPath "
425 "FROM tvshowlinkpath "
426 "GROUP BY idShow");
427 m_pDS->exec(tvshowlinkpathview);
429 CLog::Log(LOGINFO, "create tvshow_view");
430 std::string tvshowview = PrepareSQL("CREATE VIEW tvshow_view AS SELECT "
431 " tvshow.*,"
432 " path.idParentPath AS idParentPath,"
433 " path.strPath AS strPath,"
434 " tvshowcounts.dateAdded AS dateAdded,"
435 " lastPlayed, totalCount, watchedcount, totalSeasons, "
436 " rating.rating AS rating, "
437 " rating.votes AS votes, "
438 " rating.rating_type AS rating_type, "
439 " uniqueid.value AS uniqueid_value, "
440 " uniqueid.type AS uniqueid_type "
441 "FROM tvshow"
442 " LEFT JOIN tvshowlinkpath_minview ON "
443 " tvshowlinkpath_minview.idShow=tvshow.idShow"
444 " LEFT JOIN path ON"
445 " path.idPath=tvshowlinkpath_minview.idPath"
446 " INNER JOIN tvshowcounts ON"
447 " tvshow.idShow = tvshowcounts.idShow "
448 " LEFT JOIN rating ON"
449 " rating.rating_id=tvshow.c%02d "
450 " LEFT JOIN uniqueid ON"
451 " uniqueid.uniqueid_id=tvshow.c%02d ",
452 VIDEODB_ID_TV_RATING_ID, VIDEODB_ID_TV_IDENT_ID);
453 m_pDS->exec(tvshowview);
455 CLog::Log(LOGINFO, "create season_view");
456 std::string seasonview = PrepareSQL("CREATE VIEW season_view AS SELECT "
457 " seasons.idSeason AS idSeason,"
458 " seasons.idShow AS idShow,"
459 " seasons.season AS season,"
460 " seasons.name AS name,"
461 " seasons.userrating AS userrating,"
462 " tvshow_view.strPath AS strPath,"
463 " tvshow_view.c%02d AS showTitle,"
464 " tvshow_view.c%02d AS plot,"
465 " tvshow_view.c%02d AS premiered,"
466 " tvshow_view.c%02d AS genre,"
467 " tvshow_view.c%02d AS studio,"
468 " tvshow_view.c%02d AS mpaa,"
469 " count(DISTINCT episode.idEpisode) AS episodes,"
470 " count(files.playCount) AS playCount,"
471 " min(episode.c%02d) AS aired "
472 "FROM seasons"
473 " JOIN tvshow_view ON"
474 " tvshow_view.idShow = seasons.idShow"
475 " JOIN episode ON"
476 " episode.idShow = seasons.idShow AND episode.c%02d = seasons.season"
477 " JOIN files ON"
478 " files.idFile = episode.idFile "
479 "GROUP BY seasons.idSeason,"
480 " seasons.idShow,"
481 " seasons.season,"
482 " seasons.name,"
483 " seasons.userrating,"
484 " tvshow_view.strPath,"
485 " tvshow_view.c%02d,"
486 " tvshow_view.c%02d,"
487 " tvshow_view.c%02d,"
488 " tvshow_view.c%02d,"
489 " tvshow_view.c%02d,"
490 " tvshow_view.c%02d ",
491 VIDEODB_ID_TV_TITLE, VIDEODB_ID_TV_PLOT, VIDEODB_ID_TV_PREMIERED,
492 VIDEODB_ID_TV_GENRE, VIDEODB_ID_TV_STUDIOS, VIDEODB_ID_TV_MPAA,
493 VIDEODB_ID_EPISODE_AIRED, VIDEODB_ID_EPISODE_SEASON,
494 VIDEODB_ID_TV_TITLE, VIDEODB_ID_TV_PLOT, VIDEODB_ID_TV_PREMIERED,
495 VIDEODB_ID_TV_GENRE, VIDEODB_ID_TV_STUDIOS, VIDEODB_ID_TV_MPAA);
496 m_pDS->exec(seasonview);
498 CLog::Log(LOGINFO, "create musicvideo_view");
499 m_pDS->exec(PrepareSQL(
500 "CREATE VIEW musicvideo_view AS SELECT"
501 " musicvideo.*,"
502 " files.strFileName as strFileName,"
503 " path.strPath as strPath,"
504 " files.playCount as playCount,"
505 " files.lastPlayed as lastPlayed,"
506 " files.dateAdded as dateAdded, "
507 " bookmark.timeInSeconds AS resumeTimeInSeconds, "
508 " bookmark.totalTimeInSeconds AS totalTimeInSeconds, "
509 " bookmark.playerState AS playerState, "
510 " uniqueid.value AS uniqueid_value, "
511 " uniqueid.type AS uniqueid_type "
512 "FROM musicvideo"
513 " JOIN files ON"
514 " files.idFile=musicvideo.idFile"
515 " JOIN path ON"
516 " path.idPath=files.idPath"
517 " LEFT JOIN bookmark ON"
518 " bookmark.idFile=musicvideo.idFile AND bookmark.type=1"
519 " LEFT JOIN uniqueid ON"
520 " uniqueid.uniqueid_id=musicvideo.c%02d",
521 VIDEODB_ID_MUSICVIDEO_IDENT_ID));
523 CLog::Log(LOGINFO, "create movie_view");
525 std::string movieview = PrepareSQL("CREATE VIEW movie_view AS SELECT"
526 " movie.*,"
527 " sets.strSet AS strSet,"
528 " sets.strOverview AS strSetOverview,"
529 " files.strFileName AS strFileName,"
530 " path.strPath AS strPath,"
531 " files.playCount AS playCount,"
532 " files.lastPlayed AS lastPlayed, "
533 " files.dateAdded AS dateAdded, "
534 " bookmark.timeInSeconds AS resumeTimeInSeconds, "
535 " bookmark.totalTimeInSeconds AS totalTimeInSeconds, "
536 " bookmark.playerState AS playerState, "
537 " rating.rating AS rating, "
538 " rating.votes AS votes, "
539 " rating.rating_type AS rating_type, "
540 " uniqueid.value AS uniqueid_value, "
541 " uniqueid.type AS uniqueid_type "
542 "FROM movie"
543 " LEFT JOIN sets ON"
544 " sets.idSet = movie.idSet"
545 " JOIN files ON"
546 " files.idFile=movie.idFile"
547 " JOIN path ON"
548 " path.idPath=files.idPath"
549 " LEFT JOIN bookmark ON"
550 " bookmark.idFile=movie.idFile AND bookmark.type=1"
551 " LEFT JOIN rating ON"
552 " rating.rating_id=movie.c%02d"
553 " LEFT JOIN uniqueid ON"
554 " uniqueid.uniqueid_id=movie.c%02d",
555 VIDEODB_ID_RATING_ID, VIDEODB_ID_IDENT_ID);
556 m_pDS->exec(movieview);
559 //********************************************************************************************************************************
560 int CVideoDatabase::GetPathId(const std::string& strPath)
562 std::string strSQL;
565 int idPath=-1;
566 if (nullptr == m_pDB)
567 return -1;
568 if (nullptr == m_pDS)
569 return -1;
571 std::string strPath1(strPath);
572 if (URIUtils::IsStack(strPath) || StringUtils::StartsWithNoCase(strPath, "rar://") || StringUtils::StartsWithNoCase(strPath, "zip://"))
573 URIUtils::GetParentPath(strPath,strPath1);
575 URIUtils::AddSlashAtEnd(strPath1);
577 strSQL=PrepareSQL("select idPath from path where strPath='%s'",strPath1.c_str());
578 m_pDS->query(strSQL);
579 if (!m_pDS->eof())
580 idPath = m_pDS->fv("path.idPath").get_asInt();
582 m_pDS->close();
583 return idPath;
585 catch (...)
587 CLog::Log(LOGERROR, "{} unable to getpath ({})", __FUNCTION__, strSQL);
589 return -1;
592 bool CVideoDatabase::GetPaths(std::set<std::string> &paths)
596 if (nullptr == m_pDB)
597 return false;
598 if (nullptr == m_pDS)
599 return false;
601 paths.clear();
603 // grab all paths with movie content set
604 if (!m_pDS->query("select strPath,noUpdate from path"
605 " where (strContent = 'movies' or strContent = 'musicvideos')"
606 " and strPath NOT like 'multipath://%%'"
607 " order by strPath"))
608 return false;
610 while (!m_pDS->eof())
612 if (!m_pDS->fv("noUpdate").get_asBool())
613 paths.insert(m_pDS->fv("strPath").get_asString());
614 m_pDS->next();
616 m_pDS->close();
618 // then grab all tvshow paths
619 if (!m_pDS->query("select strPath,noUpdate from path"
620 " where ( strContent = 'tvshows'"
621 " or idPath in (select idPath from tvshowlinkpath))"
622 " and strPath NOT like 'multipath://%%'"
623 " order by strPath"))
624 return false;
626 while (!m_pDS->eof())
628 if (!m_pDS->fv("noUpdate").get_asBool())
629 paths.insert(m_pDS->fv("strPath").get_asString());
630 m_pDS->next();
632 m_pDS->close();
634 // finally grab all other paths holding a movie which is not a stack or a rar archive
635 // - this isnt perfect but it should do fine in most situations.
636 // reason we need it to hold a movie is stacks from different directories (cdx folders for instance)
637 // not making mistakes must take priority
638 if (!m_pDS->query("select strPath,noUpdate from path"
639 " where idPath in (select idPath from files join movie on movie.idFile=files.idFile)"
640 " and idPath NOT in (select idPath from tvshowlinkpath)"
641 " and idPath NOT in (select idPath from files where strFileName like 'video_ts.ifo')" // dvd folders get stacked to a single item in parent folder
642 " and idPath NOT in (select idPath from files where strFileName like 'index.bdmv')" // bluray folders get stacked to a single item in parent folder
643 " and strPath NOT like 'multipath://%%'"
644 " and strContent NOT in ('movies', 'tvshows', 'None')" // these have been added above
645 " order by strPath"))
647 return false;
648 while (!m_pDS->eof())
650 if (!m_pDS->fv("noUpdate").get_asBool())
651 paths.insert(m_pDS->fv("strPath").get_asString());
652 m_pDS->next();
654 m_pDS->close();
655 return true;
657 catch (...)
659 CLog::Log(LOGERROR, "{} failed", __FUNCTION__);
661 return false;
664 bool CVideoDatabase::GetPathsLinkedToTvShow(int idShow, std::vector<std::string> &paths)
666 std::string sql;
669 sql = PrepareSQL("SELECT strPath FROM path JOIN tvshowlinkpath ON tvshowlinkpath.idPath=path.idPath WHERE idShow=%i", idShow);
670 m_pDS->query(sql);
671 while (!m_pDS->eof())
673 paths.emplace_back(m_pDS->fv(0).get_asString());
674 m_pDS->next();
676 return true;
678 catch (...)
680 CLog::Log(LOGERROR, "{} error during query: {}", __FUNCTION__, sql);
682 return false;
685 bool CVideoDatabase::GetPathsForTvShow(int idShow, std::set<int>& paths)
687 std::string strSQL;
690 if (nullptr == m_pDB)
691 return false;
692 if (nullptr == m_pDS)
693 return false;
695 // add base path
696 strSQL = PrepareSQL("SELECT strPath FROM tvshow_view WHERE idShow=%i", idShow);
697 if (m_pDS->query(strSQL))
698 paths.insert(GetPathId(m_pDS->fv(0).get_asString()));
700 // add all other known paths
701 strSQL = PrepareSQL("SELECT DISTINCT idPath FROM files JOIN episode ON episode.idFile=files.idFile WHERE episode.idShow=%i",idShow);
702 m_pDS->query(strSQL);
703 while (!m_pDS->eof())
705 paths.insert(m_pDS->fv(0).get_asInt());
706 m_pDS->next();
708 m_pDS->close();
709 return true;
711 catch (...)
713 CLog::Log(LOGERROR, "{} error during query: {}", __FUNCTION__, strSQL);
715 return false;
718 int CVideoDatabase::RunQuery(const std::string &sql)
720 auto start = std::chrono::steady_clock::now();
722 int rows = -1;
723 if (m_pDS->query(sql))
725 rows = m_pDS->num_rows();
726 if (rows == 0)
727 m_pDS->close();
730 auto end = std::chrono::steady_clock::now();
731 auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
733 CLog::Log(LOGDEBUG, LOGDATABASE, "{} took {} ms for {} items query: {}", __FUNCTION__,
734 duration.count(), rows, sql);
736 return rows;
739 bool CVideoDatabase::GetSubPaths(const std::string &basepath, std::vector<std::pair<int, std::string>>& subpaths)
741 std::string sql;
744 if (!m_pDB || !m_pDS)
745 return false;
747 std::string path(basepath);
748 URIUtils::AddSlashAtEnd(path);
749 sql = PrepareSQL("SELECT idPath,strPath FROM path WHERE SUBSTR(strPath,1,%i)='%s'"
750 " AND idPath NOT IN (SELECT idPath FROM files WHERE strFileName LIKE 'video_ts.ifo')"
751 " AND idPath NOT IN (SELECT idPath FROM files WHERE strFileName LIKE 'index.bdmv')"
752 , StringUtils::utf8_strlen(path.c_str()), path.c_str());
754 m_pDS->query(sql);
755 while (!m_pDS->eof())
757 subpaths.emplace_back(m_pDS->fv(0).get_asInt(), m_pDS->fv(1).get_asString());
758 m_pDS->next();
760 m_pDS->close();
761 return true;
763 catch (...)
765 CLog::Log(LOGERROR, "{} error during query: {}", __FUNCTION__, sql);
767 return false;
770 int CVideoDatabase::AddPath(const std::string& strPath, const std::string &parentPath /*= "" */, const CDateTime& dateAdded /* = CDateTime() */)
772 std::string strSQL;
775 int idPath = GetPathId(strPath);
776 if (idPath >= 0)
777 return idPath; // already have the path
779 if (nullptr == m_pDB)
780 return -1;
781 if (nullptr == m_pDS)
782 return -1;
784 std::string strPath1(strPath);
785 if (URIUtils::IsStack(strPath) || StringUtils::StartsWithNoCase(strPath, "rar://") || StringUtils::StartsWithNoCase(strPath, "zip://"))
786 URIUtils::GetParentPath(strPath,strPath1);
788 URIUtils::AddSlashAtEnd(strPath1);
790 int idParentPath = GetPathId(parentPath.empty() ? URIUtils::GetParentPath(strPath1) : parentPath);
792 // add the path
793 if (idParentPath < 0)
795 if (dateAdded.IsValid())
796 strSQL=PrepareSQL("insert into path (idPath, strPath, dateAdded) values (NULL, '%s', '%s')", strPath1.c_str(), dateAdded.GetAsDBDateTime().c_str());
797 else
798 strSQL=PrepareSQL("insert into path (idPath, strPath) values (NULL, '%s')", strPath1.c_str());
800 else
802 if (dateAdded.IsValid())
803 strSQL = PrepareSQL("insert into path (idPath, strPath, dateAdded, idParentPath) values (NULL, '%s', '%s', %i)", strPath1.c_str(), dateAdded.GetAsDBDateTime().c_str(), idParentPath);
804 else
805 strSQL=PrepareSQL("insert into path (idPath, strPath, idParentPath) values (NULL, '%s', %i)", strPath1.c_str(), idParentPath);
807 m_pDS->exec(strSQL);
808 idPath = (int)m_pDS->lastinsertid();
809 return idPath;
811 catch (...)
813 CLog::Log(LOGERROR, "{} unable to addpath ({})", __FUNCTION__, strSQL);
815 return -1;
818 bool CVideoDatabase::GetPathHash(const std::string &path, std::string &hash)
822 if (nullptr == m_pDB)
823 return false;
824 if (nullptr == m_pDS)
825 return false;
827 std::string strSQL=PrepareSQL("select strHash from path where strPath='%s'", path.c_str());
828 m_pDS->query(strSQL);
829 if (m_pDS->num_rows() == 0)
830 return false;
831 hash = m_pDS->fv("strHash").get_asString();
832 return true;
834 catch (...)
836 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, path);
839 return false;
842 bool CVideoDatabase::GetSourcePath(const std::string &path, std::string &sourcePath)
844 SScanSettings dummy;
845 return GetSourcePath(path, sourcePath, dummy);
848 bool CVideoDatabase::GetSourcePath(const std::string &path, std::string &sourcePath, SScanSettings& settings)
852 if (path.empty() || m_pDB == nullptr || m_pDS == nullptr)
853 return false;
855 std::string strPath2;
857 if (URIUtils::IsMultiPath(path))
858 strPath2 = CMultiPathDirectory::GetFirstPath(path);
859 else
860 strPath2 = path;
862 std::string strPath1 = URIUtils::GetDirectory(strPath2);
863 int idPath = GetPathId(strPath1);
865 if (idPath > -1)
867 // check if the given path already is a source itself
868 std::string strSQL = PrepareSQL("SELECT path.useFolderNames, path.scanRecursive, path.noUpdate, path.exclude FROM path WHERE "
869 "path.idPath = %i AND "
870 "path.strContent IS NOT NULL AND path.strContent != '' AND "
871 "path.strScraper IS NOT NULL AND path.strScraper != ''", idPath);
872 if (m_pDS->query(strSQL) && !m_pDS->eof())
874 settings.parent_name_root = settings.parent_name = m_pDS->fv(0).get_asBool();
875 settings.recurse = m_pDS->fv(1).get_asInt();
876 settings.noupdate = m_pDS->fv(2).get_asBool();
877 settings.exclude = m_pDS->fv(3).get_asBool();
879 m_pDS->close();
880 sourcePath = path;
881 return true;
885 // look for parent paths until there is one which is a source
886 std::string strParent;
887 bool found = false;
888 while (URIUtils::GetParentPath(strPath1, strParent))
890 std::string strSQL = PrepareSQL("SELECT path.strContent, path.strScraper, path.scanRecursive, path.useFolderNames, path.noUpdate, path.exclude FROM path WHERE strPath = '%s'", strParent.c_str());
891 if (m_pDS->query(strSQL) && !m_pDS->eof())
893 std::string strContent = m_pDS->fv(0).get_asString();
894 std::string strScraper = m_pDS->fv(1).get_asString();
895 if (!strContent.empty() && !strScraper.empty())
897 settings.parent_name_root = settings.parent_name = m_pDS->fv(2).get_asBool();
898 settings.recurse = m_pDS->fv(3).get_asInt();
899 settings.noupdate = m_pDS->fv(4).get_asBool();
900 settings.exclude = m_pDS->fv(5).get_asBool();
901 found = true;
902 break;
906 strPath1 = strParent;
908 m_pDS->close();
910 if (found)
912 sourcePath = strParent;
913 return true;
916 catch (...)
918 CLog::Log(LOGERROR, "{} failed", __FUNCTION__);
920 return false;
923 //********************************************************************************************************************************
924 int CVideoDatabase::AddFile(const std::string& strFileNameAndPath,
925 const std::string& parentPath /* = "" */,
926 const CDateTime& dateAdded /* = CDateTime() */,
927 int playcount /* = 0 */,
928 const CDateTime& lastPlayed /* = CDateTime() */)
930 std::string strSQL = "";
933 int idFile;
934 if (nullptr == m_pDB)
935 return -1;
936 if (nullptr == m_pDS)
937 return -1;
939 const auto finalDateAdded = GetDateAdded(strFileNameAndPath, dateAdded);
941 std::string strFileName, strPath;
942 SplitPath(strFileNameAndPath,strPath,strFileName);
944 int idPath = AddPath(strPath, parentPath, finalDateAdded);
945 if (idPath < 0)
946 return -1;
948 std::string strSQL=PrepareSQL("select idFile from files where strFileName='%s' and idPath=%i", strFileName.c_str(),idPath);
950 m_pDS->query(strSQL);
951 if (m_pDS->num_rows() > 0)
953 idFile = m_pDS->fv("idFile").get_asInt() ;
954 m_pDS->close();
955 return idFile;
957 m_pDS->close();
959 std::string strPlaycount = "NULL";
960 if (playcount > 0)
961 strPlaycount = std::to_string(playcount);
962 std::string strLastPlayed = "NULL";
963 if (lastPlayed.IsValid())
964 strLastPlayed = "'" + lastPlayed.GetAsDBDateTime() + "'";
966 strSQL = PrepareSQL("INSERT INTO files (idFile, idPath, strFileName, playCount, lastPlayed, dateAdded) "
967 "VALUES(NULL, %i, '%s', " + strPlaycount + ", " + strLastPlayed + ", '%s')",
968 idPath, strFileName.c_str(), finalDateAdded.GetAsDBDateTime().c_str());
969 m_pDS->exec(strSQL);
970 idFile = (int)m_pDS->lastinsertid();
971 return idFile;
973 catch (...)
975 CLog::Log(LOGERROR, "{} unable to addfile ({})", __FUNCTION__, strSQL);
977 return -1;
980 int CVideoDatabase::AddFile(const CFileItem& item)
982 if (item.IsVideoDb() && item.HasVideoInfoTag())
984 const auto videoInfoTag = item.GetVideoInfoTag();
985 if (videoInfoTag->m_iFileId != -1)
986 return videoInfoTag->m_iFileId;
987 else
988 return AddFile(*videoInfoTag);
990 return AddFile(item.GetPath());
993 int CVideoDatabase::AddFile(const CVideoInfoTag& details, const std::string& parentPath /* = "" */)
995 return AddFile(details.GetPath(), parentPath, details.m_dateAdded, details.GetPlayCount(),
996 details.m_lastPlayed);
999 void CVideoDatabase::UpdateFileDateAdded(CVideoInfoTag& details)
1001 if (details.GetPath().empty() || GetAndFillFileId(details) <= 0)
1002 return;
1004 CDateTime finalDateAdded;
1007 if (nullptr == m_pDB)
1008 return;
1009 if (nullptr == m_pDS)
1010 return;
1012 finalDateAdded = GetDateAdded(details.GetPath(), details.m_dateAdded);
1014 m_pDS->exec(PrepareSQL("UPDATE files SET dateAdded='%s' WHERE idFile=%d",
1015 finalDateAdded.GetAsDBDateTime().c_str(), details.m_iFileId));
1017 catch (...)
1019 CLog::Log(LOGERROR, "{}({}, {}) failed", __FUNCTION__, CURL::GetRedacted(details.GetPath()),
1020 finalDateAdded.GetAsDBDateTime());
1024 bool CVideoDatabase::SetPathHash(const std::string &path, const std::string &hash)
1028 if (nullptr == m_pDB)
1029 return false;
1030 if (nullptr == m_pDS)
1031 return false;
1033 int idPath = AddPath(path);
1034 if (idPath < 0) return false;
1036 std::string strSQL=PrepareSQL("update path set strHash='%s' where idPath=%ld", hash.c_str(), idPath);
1037 m_pDS->exec(strSQL);
1039 return true;
1041 catch (...)
1043 CLog::Log(LOGERROR, "{} ({}, {}) failed", __FUNCTION__, path, hash);
1046 return false;
1049 bool CVideoDatabase::LinkMovieToTvshow(int idMovie, int idShow, bool bRemove)
1053 if (nullptr == m_pDB)
1054 return false;
1055 if (nullptr == m_pDS)
1056 return false;
1058 if (bRemove) // delete link
1060 std::string strSQL=PrepareSQL("delete from movielinktvshow where idMovie=%i and idShow=%i", idMovie, idShow);
1061 m_pDS->exec(strSQL);
1062 return true;
1065 std::string strSQL=PrepareSQL("insert into movielinktvshow (idShow,idMovie) values (%i,%i)", idShow,idMovie);
1066 m_pDS->exec(strSQL);
1068 return true;
1070 catch (...)
1072 CLog::Log(LOGERROR, "{} ({}, {}) failed", __FUNCTION__, idMovie, idShow);
1075 return false;
1078 bool CVideoDatabase::IsLinkedToTvshow(int idMovie)
1082 if (nullptr == m_pDB)
1083 return false;
1084 if (nullptr == m_pDS)
1085 return false;
1087 std::string strSQL=PrepareSQL("select * from movielinktvshow where idMovie=%i", idMovie);
1088 m_pDS->query(strSQL);
1089 if (m_pDS->eof())
1091 m_pDS->close();
1092 return false;
1095 m_pDS->close();
1096 return true;
1098 catch (...)
1100 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, idMovie);
1103 return false;
1106 bool CVideoDatabase::GetLinksToTvShow(int idMovie, std::vector<int>& ids)
1110 if (nullptr == m_pDB)
1111 return false;
1112 if (nullptr == m_pDS)
1113 return false;
1115 std::string strSQL=PrepareSQL("select * from movielinktvshow where idMovie=%i", idMovie);
1116 m_pDS2->query(strSQL);
1117 while (!m_pDS2->eof())
1119 ids.push_back(m_pDS2->fv(1).get_asInt());
1120 m_pDS2->next();
1123 m_pDS2->close();
1124 return true;
1126 catch (...)
1128 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, idMovie);
1131 return false;
1135 //********************************************************************************************************************************
1136 int CVideoDatabase::GetFileId(const std::string& strFilenameAndPath)
1140 if (nullptr == m_pDB)
1141 return -1;
1142 if (nullptr == m_pDS)
1143 return -1;
1144 std::string strPath, strFileName;
1145 SplitPath(strFilenameAndPath,strPath,strFileName);
1147 int idPath = GetPathId(strPath);
1148 if (idPath >= 0)
1150 std::string strSQL;
1151 strSQL=PrepareSQL("select idFile from files where strFileName='%s' and idPath=%i", strFileName.c_str(),idPath);
1152 m_pDS->query(strSQL);
1153 if (m_pDS->num_rows() > 0)
1155 int idFile = m_pDS->fv("files.idFile").get_asInt();
1156 m_pDS->close();
1157 return idFile;
1161 catch (...)
1163 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, strFilenameAndPath);
1165 return -1;
1168 int CVideoDatabase::GetFileId(const CFileItem &item)
1170 int fileId = -1;
1171 if (item.HasVideoInfoTag())
1172 fileId = GetFileId(*item.GetVideoInfoTag());
1174 if (fileId == -1)
1175 fileId = GetFileId(item.GetPath());
1177 return fileId;
1180 int CVideoDatabase::GetFileId(const CVideoInfoTag& details)
1182 if (details.m_iFileId > 0)
1183 return details.m_iFileId;
1185 const auto& filePath = details.GetPath();
1186 if (filePath.empty())
1187 return -1;
1189 return GetFileId(filePath);
1192 int CVideoDatabase::GetAndFillFileId(CVideoInfoTag& details)
1194 details.m_iFileId = GetFileId(details);
1195 return details.m_iFileId;
1198 //********************************************************************************************************************************
1199 int CVideoDatabase::GetMovieId(const std::string& strFilenameAndPath)
1203 if (nullptr == m_pDB)
1204 return -1;
1205 if (nullptr == m_pDS)
1206 return -1;
1207 int idMovie = -1;
1209 // needed for query parameters
1210 int idFile = GetFileId(strFilenameAndPath);
1211 int idPath=-1;
1212 std::string strPath;
1213 if (idFile < 0)
1215 std::string strFile;
1216 SplitPath(strFilenameAndPath,strPath,strFile);
1218 // have to join movieinfo table for correct results
1219 idPath = GetPathId(strPath);
1220 if (idPath < 0 && strPath != strFilenameAndPath)
1221 return -1;
1224 if (idFile == -1 && strPath != strFilenameAndPath)
1225 return -1;
1227 std::string strSQL;
1228 if (idFile == -1)
1229 strSQL=PrepareSQL("select idMovie from movie join files on files.idFile=movie.idFile where files.idPath=%i",idPath);
1230 else
1231 strSQL=PrepareSQL("select idMovie from movie where idFile=%i", idFile);
1233 CLog::Log(LOGDEBUG, LOGDATABASE, "{} ({}), query = {}", __FUNCTION__,
1234 CURL::GetRedacted(strFilenameAndPath), strSQL);
1235 m_pDS->query(strSQL);
1236 if (m_pDS->num_rows() > 0)
1237 idMovie = m_pDS->fv("idMovie").get_asInt();
1238 m_pDS->close();
1240 return idMovie;
1242 catch (...)
1244 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, strFilenameAndPath);
1246 return -1;
1249 int CVideoDatabase::GetTvShowId(const std::string& strPath)
1253 if (nullptr == m_pDB)
1254 return -1;
1255 if (nullptr == m_pDS)
1256 return -1;
1257 int idTvShow = -1;
1259 // have to join movieinfo table for correct results
1260 int idPath = GetPathId(strPath);
1261 if (idPath < 0)
1262 return -1;
1264 std::string strSQL;
1265 std::string strPath1=strPath;
1266 std::string strParent;
1267 int iFound=0;
1269 strSQL=PrepareSQL("select idShow from tvshowlinkpath where tvshowlinkpath.idPath=%i",idPath);
1270 m_pDS->query(strSQL);
1271 if (!m_pDS->eof())
1272 iFound = 1;
1274 while (iFound == 0 && URIUtils::GetParentPath(strPath1, strParent))
1276 strSQL=PrepareSQL("SELECT idShow FROM path INNER JOIN tvshowlinkpath ON tvshowlinkpath.idPath=path.idPath WHERE strPath='%s'",strParent.c_str());
1277 m_pDS->query(strSQL);
1278 if (!m_pDS->eof())
1280 int idShow = m_pDS->fv("idShow").get_asInt();
1281 if (idShow != -1)
1282 iFound = 2;
1284 strPath1 = strParent;
1287 if (m_pDS->num_rows() > 0)
1288 idTvShow = m_pDS->fv("idShow").get_asInt();
1289 m_pDS->close();
1291 return idTvShow;
1293 catch (...)
1295 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, strPath);
1297 return -1;
1300 int CVideoDatabase::GetEpisodeId(const std::string& strFilenameAndPath, int idEpisode, int idSeason) // input value is episode/season number hint - for multiparters
1304 if (nullptr == m_pDB)
1305 return -1;
1306 if (nullptr == m_pDS)
1307 return -1;
1309 // need this due to the nested GetEpisodeInfo query
1310 std::unique_ptr<Dataset> pDS;
1311 pDS.reset(m_pDB->CreateDataset());
1312 if (nullptr == pDS)
1313 return -1;
1315 int idFile = GetFileId(strFilenameAndPath);
1316 if (idFile < 0)
1317 return -1;
1319 std::string strSQL=PrepareSQL("select idEpisode from episode where idFile=%i", idFile);
1321 CLog::Log(LOGDEBUG, LOGDATABASE, "{} ({}), query = {}", __FUNCTION__,
1322 CURL::GetRedacted(strFilenameAndPath), strSQL);
1323 pDS->query(strSQL);
1324 if (pDS->num_rows() > 0)
1326 if (idEpisode == -1)
1327 idEpisode = pDS->fv("episode.idEpisode").get_asInt();
1328 else // use the hint!
1330 while (!pDS->eof())
1332 CVideoInfoTag tag;
1333 int idTmpEpisode = pDS->fv("episode.idEpisode").get_asInt();
1334 GetEpisodeBasicInfo(strFilenameAndPath, tag, idTmpEpisode);
1335 if (tag.m_iEpisode == idEpisode && (idSeason == -1 || tag.m_iSeason == idSeason)) {
1336 // match on the episode hint, and there's no season hint or a season hint match
1337 idEpisode = idTmpEpisode;
1338 break;
1340 pDS->next();
1342 if (pDS->eof())
1343 idEpisode = -1;
1346 else
1347 idEpisode = -1;
1349 pDS->close();
1351 return idEpisode;
1353 catch (...)
1355 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, strFilenameAndPath);
1357 return -1;
1360 int CVideoDatabase::GetMusicVideoId(const std::string& strFilenameAndPath)
1364 if (nullptr == m_pDB)
1365 return -1;
1366 if (nullptr == m_pDS)
1367 return -1;
1369 int idFile = GetFileId(strFilenameAndPath);
1370 if (idFile < 0)
1371 return -1;
1373 std::string strSQL=PrepareSQL("select idMVideo from musicvideo where idFile=%i", idFile);
1375 CLog::Log(LOGDEBUG, LOGDATABASE, "{} ({}), query = {}", __FUNCTION__,
1376 CURL::GetRedacted(strFilenameAndPath), strSQL);
1377 m_pDS->query(strSQL);
1378 int idMVideo=-1;
1379 if (m_pDS->num_rows() > 0)
1380 idMVideo = m_pDS->fv("idMVideo").get_asInt();
1381 m_pDS->close();
1383 return idMVideo;
1385 catch (...)
1387 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, strFilenameAndPath);
1389 return -1;
1392 //********************************************************************************************************************************
1393 int CVideoDatabase::AddNewMovie(CVideoInfoTag& details)
1395 const auto filePath = details.GetPath();
1399 if (nullptr == m_pDB)
1400 return -1;
1401 if (nullptr == m_pDS)
1402 return -1;
1404 if (details.m_iFileId <= 0)
1406 details.m_iFileId = AddFile(details);
1407 if (details.m_iFileId <= 0)
1408 return -1;
1411 std::string strSQL =
1412 PrepareSQL("INSERT INTO movie (idMovie, idFile) VALUES (NULL, %i)", details.m_iFileId);
1413 m_pDS->exec(strSQL);
1414 details.m_iDbId = static_cast<int>(m_pDS->lastinsertid());
1416 return details.m_iDbId;
1418 catch (...)
1420 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, filePath);
1422 return -1;
1425 bool CVideoDatabase::AddPathToTvShow(int idShow, const std::string &path, const std::string &parentPath, const CDateTime& dateAdded /* = CDateTime() */)
1427 // Check if this path is already added
1428 int idPath = GetPathId(path);
1429 if (idPath < 0)
1430 idPath = AddPath(path, parentPath, GetDateAdded(path, dateAdded));
1432 return ExecuteQuery(PrepareSQL("REPLACE INTO tvshowlinkpath(idShow, idPath) VALUES (%i,%i)", idShow, idPath));
1435 int CVideoDatabase::AddTvShow()
1437 if (ExecuteQuery("INSERT INTO tvshow(idShow) VALUES(NULL)"))
1438 return (int)m_pDS->lastinsertid();
1439 return -1;
1442 //********************************************************************************************************************************
1443 int CVideoDatabase::AddNewEpisode(int idShow, CVideoInfoTag& details)
1445 const auto filePath = details.GetPath();
1449 if (nullptr == m_pDB || nullptr == m_pDS)
1450 return -1;
1452 if (details.m_iFileId <= 0)
1454 details.m_iFileId = AddFile(details);
1455 if (details.m_iFileId <= 0)
1456 return -1;
1459 std::string strSQL =
1460 PrepareSQL("INSERT INTO episode (idEpisode, idFile, idShow) VALUES (NULL, %i, %i)",
1461 details.m_iFileId, idShow);
1462 m_pDS->exec(strSQL);
1463 details.m_iDbId = static_cast<int>(m_pDS->lastinsertid());
1465 return details.m_iDbId;
1467 catch (...)
1469 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, filePath);
1471 return -1;
1474 int CVideoDatabase::AddNewMusicVideo(CVideoInfoTag& details)
1476 const auto filePath = details.GetPath();
1480 if (nullptr == m_pDB)
1481 return -1;
1482 if (nullptr == m_pDS)
1483 return -1;
1485 if (details.m_iFileId <= 0)
1487 details.m_iFileId = AddFile(details);
1488 if (details.m_iFileId <= 0)
1489 return -1;
1492 std::string strSQL = PrepareSQL("INSERT INTO musicvideo (idMVideo, idFile) VALUES (NULL, %i)",
1493 details.m_iFileId);
1494 m_pDS->exec(strSQL);
1495 details.m_iDbId = static_cast<int>(m_pDS->lastinsertid());
1497 return details.m_iDbId;
1499 catch (...)
1501 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, filePath);
1503 return -1;
1506 //********************************************************************************************************************************
1507 int CVideoDatabase::AddToTable(const std::string& table, const std::string& firstField, const std::string& secondField, const std::string& value)
1511 if (nullptr == m_pDB)
1512 return -1;
1513 if (nullptr == m_pDS)
1514 return -1;
1516 std::string strSQL = PrepareSQL("select %s from %s where %s like '%s'", firstField.c_str(), table.c_str(), secondField.c_str(), value.substr(0, 255).c_str());
1517 m_pDS->query(strSQL);
1518 if (m_pDS->num_rows() == 0)
1520 m_pDS->close();
1521 // doesn't exists, add it
1522 strSQL = PrepareSQL("insert into %s (%s, %s) values(NULL, '%s')", table.c_str(), firstField.c_str(), secondField.c_str(), value.substr(0, 255).c_str());
1523 m_pDS->exec(strSQL);
1524 int id = (int)m_pDS->lastinsertid();
1525 return id;
1527 else
1529 int id = m_pDS->fv(firstField.c_str()).get_asInt();
1530 m_pDS->close();
1531 return id;
1534 catch (...)
1536 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, value);
1539 return -1;
1542 int CVideoDatabase::UpdateRatings(int mediaId, const char *mediaType, const RatingMap& values, const std::string& defaultRating)
1546 if (nullptr == m_pDB)
1547 return -1;
1548 if (nullptr == m_pDS)
1549 return -1;
1551 std::string sql = PrepareSQL("DELETE FROM rating WHERE media_id=%i AND media_type='%s'", mediaId, mediaType);
1552 m_pDS->exec(sql);
1554 return AddRatings(mediaId, mediaType, values, defaultRating);
1556 catch (...)
1558 CLog::Log(LOGERROR, "{} unable to update ratings of ({})", __FUNCTION__, mediaType);
1560 return -1;
1563 int CVideoDatabase::AddRatings(int mediaId, const char *mediaType, const RatingMap& values, const std::string& defaultRating)
1565 int ratingid = -1;
1568 if (nullptr == m_pDB)
1569 return -1;
1570 if (nullptr == m_pDS)
1571 return -1;
1573 for (const auto& i : values)
1575 int id;
1576 std::string strSQL = PrepareSQL("SELECT rating_id FROM rating WHERE media_id=%i AND media_type='%s' AND rating_type = '%s'", mediaId, mediaType, i.first.c_str());
1577 m_pDS->query(strSQL);
1578 if (m_pDS->num_rows() == 0)
1580 m_pDS->close();
1581 // doesn't exists, add it
1582 strSQL = PrepareSQL("INSERT INTO rating (media_id, media_type, rating_type, rating, votes) "
1583 "VALUES (%i, '%s', '%s', %f, %i)",
1584 mediaId, mediaType, i.first.c_str(),
1585 static_cast<double>(i.second.rating), i.second.votes);
1586 m_pDS->exec(strSQL);
1587 id = (int)m_pDS->lastinsertid();
1589 else
1591 id = m_pDS->fv(0).get_asInt();
1592 m_pDS->close();
1593 strSQL = PrepareSQL("UPDATE rating SET rating = %f, votes = %i WHERE rating_id = %i",
1594 static_cast<double>(i.second.rating), i.second.votes, id);
1595 m_pDS->exec(strSQL);
1597 if (i.first == defaultRating)
1598 ratingid = id;
1600 return ratingid;
1603 catch (...)
1605 CLog::Log(LOGERROR, "{} ({} - {}) failed", __FUNCTION__, mediaId, mediaType);
1608 return ratingid;
1611 int CVideoDatabase::UpdateUniqueIDs(int mediaId, const char *mediaType, const CVideoInfoTag& details)
1615 if (nullptr == m_pDB)
1616 return -1;
1617 if (nullptr == m_pDS)
1618 return -1;
1620 std::string sql = PrepareSQL("DELETE FROM uniqueid WHERE media_id=%i AND media_type='%s'", mediaId, mediaType);
1621 m_pDS->exec(sql);
1623 return AddUniqueIDs(mediaId, mediaType, details);
1625 catch (...)
1627 CLog::Log(LOGERROR, "{} unable to update unique ids of ({})", __FUNCTION__, mediaType);
1629 return -1;
1632 int CVideoDatabase::AddUniqueIDs(int mediaId, const char *mediaType, const CVideoInfoTag& details)
1634 int uniqueid = -1;
1637 if (nullptr == m_pDB)
1638 return -1;
1639 if (nullptr == m_pDS)
1640 return -1;
1642 for (const auto& i : details.GetUniqueIDs())
1644 int id;
1645 std::string strSQL = PrepareSQL("SELECT uniqueid_id FROM uniqueid WHERE media_id=%i AND media_type='%s' AND type = '%s'", mediaId, mediaType, i.first.c_str());
1646 m_pDS->query(strSQL);
1647 if (m_pDS->num_rows() == 0)
1649 m_pDS->close();
1650 // doesn't exists, add it
1651 strSQL = PrepareSQL("INSERT INTO uniqueid (media_id, media_type, value, type) VALUES (%i, '%s', '%s', '%s')", mediaId, mediaType, i.second.c_str(), i.first.c_str());
1652 m_pDS->exec(strSQL);
1653 id = (int)m_pDS->lastinsertid();
1655 else
1657 id = m_pDS->fv(0).get_asInt();
1658 m_pDS->close();
1659 strSQL = PrepareSQL("UPDATE uniqueid SET value = '%s', type = '%s' WHERE uniqueid_id = %i", i.second.c_str(), i.first.c_str(), id);
1660 m_pDS->exec(strSQL);
1662 if (i.first == details.GetDefaultUniqueID())
1663 uniqueid = id;
1665 return uniqueid;
1668 catch (...)
1670 CLog::Log(LOGERROR, "{} ({} - {}) failed", __FUNCTION__, mediaId, mediaType);
1673 return uniqueid;
1676 int CVideoDatabase::AddSet(const std::string& strSet, const std::string& strOverview /* = "" */)
1678 if (strSet.empty())
1679 return -1;
1683 if (m_pDB == nullptr || m_pDS == nullptr)
1684 return -1;
1686 std::string strSQL = PrepareSQL("SELECT idSet FROM sets WHERE strSet LIKE '%s'", strSet.c_str());
1687 m_pDS->query(strSQL);
1688 if (m_pDS->num_rows() == 0)
1690 m_pDS->close();
1691 strSQL = PrepareSQL("INSERT INTO sets (idSet, strSet, strOverview) VALUES(NULL, '%s', '%s')", strSet.c_str(), strOverview.c_str());
1692 m_pDS->exec(strSQL);
1693 int id = static_cast<int>(m_pDS->lastinsertid());
1694 return id;
1696 else
1698 int id = m_pDS->fv("idSet").get_asInt();
1699 m_pDS->close();
1700 return id;
1703 catch (...)
1705 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, strSet);
1708 return -1;
1711 int CVideoDatabase::AddTag(const std::string& name)
1713 if (name.empty())
1714 return -1;
1716 return AddToTable("tag", "tag_id", "name", name);
1719 int CVideoDatabase::AddActor(const std::string& name, const std::string& thumbURLs, const std::string &thumb)
1723 if (nullptr == m_pDB)
1724 return -1;
1725 if (nullptr == m_pDS)
1726 return -1;
1727 int idActor = -1;
1729 // ATTENTION: the trimming of actor names should really not be done here but after the scraping / NFO-parsing
1730 std::string trimmedName = name.c_str();
1731 StringUtils::Trim(trimmedName);
1733 std::string strSQL=PrepareSQL("select actor_id from actor where name like '%s'", trimmedName.substr(0, 255).c_str());
1734 m_pDS->query(strSQL);
1735 if (m_pDS->num_rows() == 0)
1737 m_pDS->close();
1738 // doesn't exists, add it
1739 strSQL=PrepareSQL("insert into actor (actor_id, name, art_urls) values(NULL, '%s', '%s')", trimmedName.substr(0,255).c_str(), thumbURLs.c_str());
1740 m_pDS->exec(strSQL);
1741 idActor = (int)m_pDS->lastinsertid();
1743 else
1745 idActor = m_pDS->fv(0).get_asInt();
1746 m_pDS->close();
1747 // update the thumb url's
1748 if (!thumbURLs.empty())
1750 strSQL=PrepareSQL("update actor set art_urls = '%s' where actor_id = %i", thumbURLs.c_str(), idActor);
1751 m_pDS->exec(strSQL);
1754 // add artwork
1755 if (!thumb.empty())
1756 SetArtForItem(idActor, "actor", "thumb", thumb);
1757 return idActor;
1759 catch (...)
1761 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, name);
1763 return -1;
1768 void CVideoDatabase::AddLinkToActor(int mediaId, const char *mediaType, int actorId, const std::string &role, int order)
1770 std::string sql = PrepareSQL("SELECT 1 FROM actor_link WHERE actor_id=%i AND "
1771 "media_id=%i AND media_type='%s' AND role='%s'",
1772 actorId, mediaId, mediaType, role.c_str());
1774 if (GetSingleValue(sql).empty())
1775 { // doesn't exists, add it
1776 sql = PrepareSQL("INSERT INTO actor_link (actor_id, media_id, media_type, role, cast_order) VALUES(%i,%i,'%s','%s',%i)", actorId, mediaId, mediaType, role.c_str(), order);
1777 ExecuteQuery(sql);
1781 void CVideoDatabase::AddToLinkTable(int mediaId, const std::string& mediaType, const std::string& table, int valueId, const char *foreignKey)
1783 const char *key = foreignKey ? foreignKey : table.c_str();
1784 std::string sql = PrepareSQL("SELECT 1 FROM %s_link WHERE %s_id=%i AND media_id=%i AND media_type='%s'", table.c_str(), key, valueId, mediaId, mediaType.c_str());
1786 if (GetSingleValue(sql).empty())
1787 { // doesn't exists, add it
1788 sql = PrepareSQL("INSERT INTO %s_link (%s_id,media_id,media_type) VALUES(%i,%i,'%s')", table.c_str(), key, valueId, mediaId, mediaType.c_str());
1789 ExecuteQuery(sql);
1793 void CVideoDatabase::RemoveFromLinkTable(int mediaId, const std::string& mediaType, const std::string& table, int valueId, const char *foreignKey)
1795 const char *key = foreignKey ? foreignKey : table.c_str();
1796 std::string sql = PrepareSQL("DELETE FROM %s_link WHERE %s_id=%i AND media_id=%i AND media_type='%s'", table.c_str(), key, valueId, mediaId, mediaType.c_str());
1798 ExecuteQuery(sql);
1801 void CVideoDatabase::AddLinksToItem(int mediaId, const std::string& mediaType, const std::string& field, const std::vector<std::string>& values)
1803 for (const auto &i : values)
1805 if (!i.empty())
1807 int idValue = AddToTable(field, field + "_id", "name", i);
1808 if (idValue > -1)
1809 AddToLinkTable(mediaId, mediaType, field, idValue);
1814 void CVideoDatabase::UpdateLinksToItem(int mediaId, const std::string& mediaType, const std::string& field, const std::vector<std::string>& values)
1816 std::string sql = PrepareSQL("DELETE FROM %s_link WHERE media_id=%i AND media_type='%s'", field.c_str(), mediaId, mediaType.c_str());
1817 m_pDS->exec(sql);
1819 AddLinksToItem(mediaId, mediaType, field, values);
1822 void CVideoDatabase::AddActorLinksToItem(int mediaId, const std::string& mediaType, const std::string& field, const std::vector<std::string>& values)
1824 for (const auto &i : values)
1826 if (!i.empty())
1828 int idValue = AddActor(i, "");
1829 if (idValue > -1)
1830 AddToLinkTable(mediaId, mediaType, field, idValue, "actor");
1835 void CVideoDatabase::UpdateActorLinksToItem(int mediaId, const std::string& mediaType, const std::string& field, const std::vector<std::string>& values)
1837 std::string sql = PrepareSQL("DELETE FROM %s_link WHERE media_id=%i AND media_type='%s'", field.c_str(), mediaId, mediaType.c_str());
1838 m_pDS->exec(sql);
1840 AddActorLinksToItem(mediaId, mediaType, field, values);
1843 //****Tags****
1844 void CVideoDatabase::AddTagToItem(int media_id, int tag_id, const std::string &type)
1846 if (type.empty())
1847 return;
1849 AddToLinkTable(media_id, type, "tag", tag_id);
1852 void CVideoDatabase::RemoveTagFromItem(int media_id, int tag_id, const std::string &type)
1854 if (type.empty())
1855 return;
1857 RemoveFromLinkTable(media_id, type, "tag", tag_id);
1860 void CVideoDatabase::RemoveTagsFromItem(int media_id, const std::string &type)
1862 if (type.empty())
1863 return;
1865 m_pDS2->exec(PrepareSQL("DELETE FROM tag_link WHERE media_id=%d AND media_type='%s'", media_id, type.c_str()));
1868 //****Actors****
1869 void CVideoDatabase::AddCast(int mediaId, const char *mediaType, const std::vector< SActorInfo > &cast)
1871 if (cast.empty())
1872 return;
1874 int order = std::max_element(cast.begin(), cast.end())->order;
1875 for (const auto &i : cast)
1877 int idActor = AddActor(i.strName, i.thumbUrl.GetData(), i.thumb);
1878 AddLinkToActor(mediaId, mediaType, idActor, i.strRole, i.order >= 0 ? i.order : ++order);
1882 //********************************************************************************************************************************
1883 bool CVideoDatabase::LoadVideoInfo(const std::string& strFilenameAndPath, CVideoInfoTag& details, int getDetails /* = VideoDbDetailsAll */)
1885 if (GetMovieInfo(strFilenameAndPath, details))
1886 return true;
1887 if (GetEpisodeInfo(strFilenameAndPath, details))
1888 return true;
1889 if (GetMusicVideoInfo(strFilenameAndPath, details))
1890 return true;
1891 if (GetFileInfo(strFilenameAndPath, details))
1892 return true;
1894 return false;
1897 bool CVideoDatabase::HasMovieInfo(const std::string& strFilenameAndPath)
1901 if (nullptr == m_pDB)
1902 return false;
1903 if (nullptr == m_pDS)
1904 return false;
1905 int idMovie = GetMovieId(strFilenameAndPath);
1906 return (idMovie > 0); // index of zero is also invalid
1908 catch (...)
1910 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, strFilenameAndPath);
1912 return false;
1915 bool CVideoDatabase::HasTvShowInfo(const std::string& strPath)
1919 if (nullptr == m_pDB)
1920 return false;
1921 if (nullptr == m_pDS)
1922 return false;
1923 int idTvShow = GetTvShowId(strPath);
1924 return (idTvShow > 0); // index of zero is also invalid
1926 catch (...)
1928 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, strPath);
1930 return false;
1933 bool CVideoDatabase::HasEpisodeInfo(const std::string& strFilenameAndPath)
1937 if (nullptr == m_pDB)
1938 return false;
1939 if (nullptr == m_pDS)
1940 return false;
1941 int idEpisode = GetEpisodeId(strFilenameAndPath);
1942 return (idEpisode > 0); // index of zero is also invalid
1944 catch (...)
1946 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, strFilenameAndPath);
1948 return false;
1951 bool CVideoDatabase::HasMusicVideoInfo(const std::string& strFilenameAndPath)
1955 if (nullptr == m_pDB)
1956 return false;
1957 if (nullptr == m_pDS)
1958 return false;
1959 int idMVideo = GetMusicVideoId(strFilenameAndPath);
1960 return (idMVideo > 0); // index of zero is also invalid
1962 catch (...)
1964 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, strFilenameAndPath);
1966 return false;
1969 void CVideoDatabase::DeleteDetailsForTvShow(int idTvShow)
1973 if (nullptr == m_pDB)
1974 return;
1975 if (nullptr == m_pDS)
1976 return;
1978 std::string strSQL;
1979 strSQL=PrepareSQL("DELETE from genre_link WHERE media_id=%i AND media_type='tvshow'", idTvShow);
1980 m_pDS->exec(strSQL);
1982 strSQL=PrepareSQL("DELETE FROM actor_link WHERE media_id=%i AND media_type='tvshow'", idTvShow);
1983 m_pDS->exec(strSQL);
1985 strSQL=PrepareSQL("DELETE FROM director_link WHERE media_id=%i AND media_type='tvshow'", idTvShow);
1986 m_pDS->exec(strSQL);
1988 strSQL=PrepareSQL("DELETE FROM studio_link WHERE media_id=%i AND media_type='tvshow'", idTvShow);
1989 m_pDS->exec(strSQL);
1991 strSQL = PrepareSQL("DELETE FROM rating WHERE media_id=%i AND media_type='tvshow'", idTvShow);
1992 m_pDS->exec(strSQL);
1994 strSQL = PrepareSQL("DELETE FROM uniqueid WHERE media_id=%i AND media_type='tvshow'", idTvShow);
1995 m_pDS->exec(strSQL);
1997 // remove all info other than the id
1998 // we do this due to the way we have the link between the file + movie tables.
2000 std::vector<std::string> ids;
2001 for (int iType = VIDEODB_ID_TV_MIN + 1; iType < VIDEODB_ID_TV_MAX; iType++)
2002 ids.emplace_back(StringUtils::Format("c{:02}=NULL", iType));
2004 strSQL = "update tvshow set ";
2005 strSQL += StringUtils::Join(ids, ", ");
2006 strSQL += PrepareSQL(" where idShow=%i", idTvShow);
2007 m_pDS->exec(strSQL);
2009 catch (...)
2011 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, idTvShow);
2015 //********************************************************************************************************************************
2016 void CVideoDatabase::GetMoviesByActor(const std::string& name, CFileItemList& items)
2018 Filter filter;
2019 filter.join = "LEFT JOIN actor_link ON actor_link.media_id=movie_view.idMovie AND actor_link.media_type='movie' "
2020 "LEFT JOIN actor a ON a.actor_id=actor_link.actor_id "
2021 "LEFT JOIN director_link ON director_link.media_id=movie_view.idMovie AND director_link.media_type='movie' "
2022 "LEFT JOIN actor d ON d.actor_id=director_link.actor_id";
2023 filter.where = PrepareSQL("a.name='%s' OR d.name='%s'", name.c_str(), name.c_str());
2024 filter.group = "movie_view.idMovie";
2025 GetMoviesByWhere("videodb://movies/titles/", filter, items);
2028 void CVideoDatabase::GetTvShowsByActor(const std::string& name, CFileItemList& items)
2030 Filter filter;
2031 filter.join = "LEFT JOIN actor_link ON actor_link.media_id=tvshow_view.idShow AND actor_link.media_type='tvshow' "
2032 "LEFT JOIN actor a ON a.actor_id=actor_link.actor_id "
2033 "LEFT JOIN director_link ON director_link.media_id=tvshow_view.idShow AND director_link.media_type='tvshow' "
2034 "LEFT JOIN actor d ON d.actor_id=director_link.actor_id";
2035 filter.where = PrepareSQL("a.name='%s' OR d.name='%s'", name.c_str(), name.c_str());
2036 GetTvShowsByWhere("videodb://tvshows/titles/", filter, items);
2039 void CVideoDatabase::GetEpisodesByActor(const std::string& name, CFileItemList& items)
2041 Filter filter;
2042 filter.join = "LEFT JOIN actor_link ON actor_link.media_id=episode_view.idEpisode AND actor_link.media_type='episode' "
2043 "LEFT JOIN actor a ON a.actor_id=actor_link.actor_id "
2044 "LEFT JOIN director_link ON director_link.media_id=episode_view.idEpisode AND director_link.media_type='episode' "
2045 "LEFT JOIN actor d ON d.actor_id=director_link.actor_id";
2046 filter.where = PrepareSQL("a.name='%s' OR d.name='%s'", name.c_str(), name.c_str());
2047 filter.group = "episode_view.idEpisode";
2048 GetEpisodesByWhere("videodb://tvshows/titles/", filter, items);
2051 void CVideoDatabase::GetMusicVideosByArtist(const std::string& strArtist, CFileItemList& items)
2055 items.Clear();
2056 if (nullptr == m_pDB)
2057 return;
2058 if (nullptr == m_pDS)
2059 return;
2061 std::string strSQL;
2062 if (strArtist.empty()) //! @todo SMARTPLAYLISTS what is this here for???
2063 strSQL=PrepareSQL("select distinct * from musicvideo_view join actor_link on actor_link.media_id=musicvideo_view.idMVideo AND actor_link.media_type='musicvideo' join actor on actor.actor_id=actor_link.actor_id");
2064 else // same artist OR same director
2065 strSQL = PrepareSQL(
2066 "select * from musicvideo_view join actor_link on "
2067 "actor_link.media_id=musicvideo_view.idMVideo AND actor_link.media_type='musicvideo' "
2068 "join actor on actor.actor_id=actor_link.actor_id where actor.name='%s' OR "
2069 "musicvideo_view.c05='%s' GROUP BY idMVideo",
2070 strArtist.c_str(), strArtist.c_str());
2071 m_pDS->query( strSQL );
2073 while (!m_pDS->eof())
2075 CVideoInfoTag tag = GetDetailsForMusicVideo(m_pDS);
2076 CFileItemPtr pItem(new CFileItem(tag));
2077 pItem->SetLabel(StringUtils::Join(tag.m_artist, CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_videoItemSeparator));
2078 items.Add(pItem);
2079 m_pDS->next();
2081 m_pDS->close();
2083 catch (...)
2085 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, strArtist);
2089 //********************************************************************************************************************************
2090 bool CVideoDatabase::GetMovieInfo(const std::string& strFilenameAndPath, CVideoInfoTag& details, int idMovie /* = -1 */, int getDetails /* = VideoDbDetailsAll */)
2094 if (m_pDB == nullptr || m_pDS == nullptr)
2095 return false;
2097 if (idMovie < 0)
2098 idMovie = GetMovieId(strFilenameAndPath);
2099 if (idMovie < 0) return false;
2101 std::string sql = PrepareSQL("select * from movie_view where idMovie=%i", idMovie);
2102 if (!m_pDS->query(sql))
2103 return false;
2104 details = GetDetailsForMovie(m_pDS, getDetails);
2105 return !details.IsEmpty();
2107 catch (...)
2109 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, strFilenameAndPath);
2111 return false;
2114 //********************************************************************************************************************************
2115 bool CVideoDatabase::GetTvShowInfo(const std::string& strPath, CVideoInfoTag& details, int idTvShow /* = -1 */, CFileItem *item /* = NULL */, int getDetails /* = VideoDbDetailsAll */)
2119 if (m_pDB == nullptr || m_pDS == nullptr)
2120 return false;
2122 if (idTvShow < 0)
2123 idTvShow = GetTvShowId(strPath);
2124 if (idTvShow < 0) return false;
2126 std::string sql = PrepareSQL("SELECT * FROM tvshow_view WHERE idShow=%i GROUP BY idShow", idTvShow);
2127 if (!m_pDS->query(sql))
2128 return false;
2129 details = GetDetailsForTvShow(m_pDS, getDetails, item);
2130 return !details.IsEmpty();
2132 catch (...)
2134 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, strPath);
2136 return false;
2139 bool CVideoDatabase::GetSeasonInfo(int idSeason, CVideoInfoTag& details, bool allDetails /* = true */)
2141 return GetSeasonInfo(idSeason, details, allDetails, nullptr);
2144 bool CVideoDatabase::GetSeasonInfo(int idSeason, CVideoInfoTag& details, CFileItem* item)
2146 return GetSeasonInfo(idSeason, details, true, item);
2149 bool CVideoDatabase::GetSeasonInfo(int idSeason,
2150 CVideoInfoTag& details,
2151 bool allDetails,
2152 CFileItem* item)
2154 if (idSeason < 0)
2155 return false;
2159 if (!m_pDB || !m_pDS)
2160 return false;
2162 std::string sql = PrepareSQL("SELECT idSeason, idShow, season, name, userrating FROM seasons WHERE idSeason=%i", idSeason);
2163 if (!m_pDS->query(sql))
2164 return false;
2166 if (m_pDS->num_rows() != 1)
2167 return false;
2169 if (allDetails)
2171 int idShow = m_pDS->fv(1).get_asInt();
2173 // close the current result because we are going to query the season view for all details
2174 m_pDS->close();
2176 if (idShow < 0)
2177 return false;
2179 CFileItemList seasons;
2180 if (!GetSeasonsNav(StringUtils::Format("videodb://tvshows/titles/{}/", idShow), seasons, -1,
2181 -1, -1, -1, idShow, false) ||
2182 seasons.Size() <= 0)
2183 return false;
2185 for (int index = 0; index < seasons.Size(); index++)
2187 const CFileItemPtr season = seasons.Get(index);
2188 if (season->HasVideoInfoTag() && season->GetVideoInfoTag()->m_iDbId == idSeason && season->GetVideoInfoTag()->m_iIdShow == idShow)
2190 details = *season->GetVideoInfoTag();
2191 if (item)
2192 *item = *season;
2193 return true;
2197 return false;
2200 const int season = m_pDS->fv(2).get_asInt();
2201 std::string name = m_pDS->fv(3).get_asString();
2203 if (name.empty())
2205 if (season == 0)
2206 name = g_localizeStrings.Get(20381);
2207 else
2208 name = StringUtils::Format(g_localizeStrings.Get(20358), season);
2211 details.m_strTitle = name;
2212 if (!name.empty())
2213 details.m_strSortTitle = name;
2214 details.m_iSeason = season;
2215 details.m_iDbId = m_pDS->fv(0).get_asInt();
2216 details.m_iIdSeason = details.m_iDbId;
2217 details.m_type = MediaTypeSeason;
2218 details.m_iUserRating = m_pDS->fv(4).get_asInt();
2219 details.m_iIdShow = m_pDS->fv(1).get_asInt();
2221 return true;
2223 catch (...)
2225 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, idSeason);
2227 return false;
2230 bool CVideoDatabase::GetEpisodeBasicInfo(const std::string& strFilenameAndPath, CVideoInfoTag& details, int idEpisode /* = -1 */)
2234 if (idEpisode < 0)
2235 idEpisode = GetEpisodeId(strFilenameAndPath);
2237 if (idEpisode < 0)
2238 return false;
2240 std::string sql = PrepareSQL("select * from episode where idEpisode=%i",idEpisode);
2241 if (!m_pDS->query(sql))
2242 return false;
2243 details = GetBasicDetailsForEpisode(m_pDS);
2244 return !details.IsEmpty();
2246 catch (...)
2248 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, strFilenameAndPath);
2250 return false;
2253 bool CVideoDatabase::GetEpisodeInfo(const std::string& strFilenameAndPath, CVideoInfoTag& details, int idEpisode /* = -1 */, int getDetails /* = VideoDbDetailsAll */)
2257 if (m_pDB == nullptr || m_pDS == nullptr)
2258 return false;
2260 if (idEpisode < 0)
2261 idEpisode = GetEpisodeId(strFilenameAndPath, details.m_iEpisode, details.m_iSeason);
2262 if (idEpisode < 0) return false;
2264 std::string sql = PrepareSQL("select * from episode_view where idEpisode=%i",idEpisode);
2265 if (!m_pDS->query(sql))
2266 return false;
2267 details = GetDetailsForEpisode(m_pDS, getDetails);
2268 return !details.IsEmpty();
2270 catch (...)
2272 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, strFilenameAndPath);
2274 return false;
2277 bool CVideoDatabase::GetMusicVideoInfo(const std::string& strFilenameAndPath, CVideoInfoTag& details, int idMVideo /* = -1 */, int getDetails /* = VideoDbDetailsAll */)
2281 if (m_pDB == nullptr || m_pDS == nullptr)
2282 return false;
2284 if (idMVideo < 0)
2285 idMVideo = GetMusicVideoId(strFilenameAndPath);
2286 if (idMVideo < 0) return false;
2288 std::string sql = PrepareSQL("select * from musicvideo_view where idMVideo=%i", idMVideo);
2289 if (!m_pDS->query(sql))
2290 return false;
2291 details = GetDetailsForMusicVideo(m_pDS, getDetails);
2292 return !details.IsEmpty();
2294 catch (...)
2296 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, strFilenameAndPath);
2298 return false;
2301 bool CVideoDatabase::GetSetInfo(int idSet, CVideoInfoTag& details, CFileItem* item /* = nullptr */)
2305 if (idSet < 0)
2306 return false;
2308 Filter filter;
2309 filter.where = PrepareSQL("sets.idSet=%d", idSet);
2310 CFileItemList items;
2311 if (!GetSetsByWhere("videodb://movies/sets/", filter, items) ||
2312 items.Size() != 1 ||
2313 !items[0]->HasVideoInfoTag())
2314 return false;
2316 details = *(items[0]->GetVideoInfoTag());
2317 if (item)
2318 *item = *items[0];
2319 return !details.IsEmpty();
2321 catch (...)
2323 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, idSet);
2325 return false;
2328 bool CVideoDatabase::GetFileInfo(const std::string& strFilenameAndPath, CVideoInfoTag& details, int idFile /* = -1 */)
2332 if (idFile < 0)
2333 idFile = GetFileId(strFilenameAndPath);
2334 if (idFile < 0)
2335 return false;
2337 std::string sql = PrepareSQL("SELECT * FROM files "
2338 "JOIN path ON path.idPath = files.idPath "
2339 "LEFT JOIN bookmark ON bookmark.idFile = files.idFile AND bookmark.type = %i "
2340 "WHERE files.idFile = %i", CBookmark::RESUME, idFile);
2341 if (!m_pDS->query(sql))
2342 return false;
2344 details.m_iFileId = m_pDS->fv("files.idFile").get_asInt();
2345 details.m_strPath = m_pDS->fv("path.strPath").get_asString();
2346 std::string strFileName = m_pDS->fv("files.strFilename").get_asString();
2347 ConstructPath(details.m_strFileNameAndPath, details.m_strPath, strFileName);
2348 details.SetPlayCount(std::max(details.GetPlayCount(), m_pDS->fv("files.playCount").get_asInt()));
2349 if (!details.m_lastPlayed.IsValid())
2350 details.m_lastPlayed.SetFromDBDateTime(m_pDS->fv("files.lastPlayed").get_asString());
2351 if (!details.m_dateAdded.IsValid())
2352 details.m_dateAdded.SetFromDBDateTime(m_pDS->fv("files.dateAdded").get_asString());
2353 if (!details.GetResumePoint().IsSet() ||
2354 (!details.GetResumePoint().HasSavedPlayerState() &&
2355 !m_pDS->fv("bookmark.playerState").get_asString().empty()))
2357 details.SetResumePoint(m_pDS->fv("bookmark.timeInSeconds").get_asDouble(),
2358 m_pDS->fv("bookmark.totalTimeInSeconds").get_asDouble(),
2359 m_pDS->fv("bookmark.playerState").get_asString());
2362 // get streamdetails
2363 GetStreamDetails(details);
2365 return !details.IsEmpty();
2367 catch (...)
2369 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, strFilenameAndPath);
2371 return false;
2374 std::string CVideoDatabase::GetValueString(const CVideoInfoTag &details, int min, int max, const SDbTableOffsets *offsets) const
2376 std::vector<std::string> conditions;
2377 for (int i = min + 1; i < max; ++i)
2379 switch (offsets[i].type)
2381 case VIDEODB_TYPE_STRING:
2382 conditions.emplace_back(PrepareSQL("c%02d='%s'", i, ((const std::string*)(((const char*)&details)+offsets[i].offset))->c_str()));
2383 break;
2384 case VIDEODB_TYPE_INT:
2385 conditions.emplace_back(PrepareSQL("c%02d='%i'", i, *(const int*)(((const char*)&details)+offsets[i].offset)));
2386 break;
2387 case VIDEODB_TYPE_COUNT:
2389 int value = *(const int*)(((const char*)&details)+offsets[i].offset);
2390 if (value)
2391 conditions.emplace_back(PrepareSQL("c%02d=%i", i, value));
2392 else
2393 conditions.emplace_back(PrepareSQL("c%02d=NULL", i));
2395 break;
2396 case VIDEODB_TYPE_BOOL:
2397 conditions.emplace_back(PrepareSQL("c%02d='%s'", i, *(const bool*)(((const char*)&details)+offsets[i].offset)?"true":"false"));
2398 break;
2399 case VIDEODB_TYPE_FLOAT:
2400 conditions.emplace_back(PrepareSQL(
2401 "c%02d='%f'", i, *(const double*)(((const char*)&details) + offsets[i].offset)));
2402 break;
2403 case VIDEODB_TYPE_STRINGARRAY:
2404 conditions.emplace_back(PrepareSQL("c%02d='%s'", i, StringUtils::Join(*((const std::vector<std::string>*)(((const char*)&details)+offsets[i].offset)),
2405 CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_videoItemSeparator).c_str()));
2406 break;
2407 case VIDEODB_TYPE_DATE:
2408 conditions.emplace_back(PrepareSQL("c%02d='%s'", i, ((const CDateTime*)(((const char*)&details)+offsets[i].offset))->GetAsDBDate().c_str()));
2409 break;
2410 case VIDEODB_TYPE_DATETIME:
2411 conditions.emplace_back(PrepareSQL("c%02d='%s'", i, ((const CDateTime*)(((const char*)&details)+offsets[i].offset))->GetAsDBDateTime().c_str()));
2412 break;
2413 case VIDEODB_TYPE_UNUSED: // Skip the unused field to avoid populating unused data
2414 continue;
2417 return StringUtils::Join(conditions, ",");
2420 //********************************************************************************************************************************
2421 int CVideoDatabase::SetDetailsForItem(CVideoInfoTag& details, const std::map<std::string, std::string> &artwork)
2423 return SetDetailsForItem(details.m_iDbId, details.m_type, details, artwork);
2426 int CVideoDatabase::SetDetailsForItem(int id, const MediaType& mediaType, CVideoInfoTag& details, const std::map<std::string, std::string> &artwork)
2428 if (mediaType == MediaTypeNone)
2429 return -1;
2431 if (mediaType == MediaTypeMovie)
2432 return SetDetailsForMovie(details, artwork, id);
2433 else if (mediaType == MediaTypeVideoCollection)
2434 return SetDetailsForMovieSet(details, artwork, id);
2435 else if (mediaType == MediaTypeTvShow)
2437 std::map<int, std::map<std::string, std::string> > seasonArtwork;
2438 if (!UpdateDetailsForTvShow(id, details, artwork, seasonArtwork))
2439 return -1;
2441 return id;
2443 else if (mediaType == MediaTypeSeason)
2444 return SetDetailsForSeason(details, artwork, details.m_iIdShow, id);
2445 else if (mediaType == MediaTypeEpisode)
2446 return SetDetailsForEpisode(details, artwork, details.m_iIdShow, id);
2447 else if (mediaType == MediaTypeMusicVideo)
2448 return SetDetailsForMusicVideo(details, artwork, id);
2450 return -1;
2453 int CVideoDatabase::SetDetailsForMovie(CVideoInfoTag& details,
2454 const std::map<std::string, std::string>& artwork,
2455 int idMovie /* = -1 */)
2457 const auto filePath = details.GetPath();
2461 BeginTransaction();
2463 if (idMovie < 0)
2464 idMovie = GetMovieId(filePath);
2466 if (idMovie > -1)
2467 DeleteMovie(idMovie, true); // true to keep the table entry
2468 else
2470 // only add a new movie if we don't already have a valid idMovie
2471 // (DeleteMovie is called with bKeepId == true so the movie won't
2472 // be removed from the movie table)
2473 idMovie = AddNewMovie(details);
2474 if (idMovie < 0)
2476 RollbackTransaction();
2477 return idMovie;
2481 // update dateadded if it's set
2482 if (details.m_dateAdded.IsValid())
2483 UpdateFileDateAdded(details);
2485 AddCast(idMovie, "movie", details.m_cast);
2486 AddLinksToItem(idMovie, MediaTypeMovie, "genre", details.m_genre);
2487 AddLinksToItem(idMovie, MediaTypeMovie, "studio", details.m_studio);
2488 AddLinksToItem(idMovie, MediaTypeMovie, "country", details.m_country);
2489 AddLinksToItem(idMovie, MediaTypeMovie, "tag", details.m_tags);
2490 AddActorLinksToItem(idMovie, MediaTypeMovie, "director", details.m_director);
2491 AddActorLinksToItem(idMovie, MediaTypeMovie, "writer", details.m_writingCredits);
2493 // add ratingsu
2494 details.m_iIdRating = AddRatings(idMovie, MediaTypeMovie, details.m_ratings, details.GetDefaultRating());
2496 // add unique ids
2497 details.m_iIdUniqueID = AddUniqueIDs(idMovie, MediaTypeMovie, details);
2499 // add set...
2500 int idSet = -1;
2501 if (!details.m_set.title.empty())
2503 idSet = AddSet(details.m_set.title, details.m_set.overview);
2504 // add art if not available
2505 if (!HasArtForItem(idSet, MediaTypeVideoCollection))
2507 for (const auto &it : artwork)
2509 if (StringUtils::StartsWith(it.first, "set."))
2510 SetArtForItem(idSet, MediaTypeVideoCollection, it.first.substr(4), it.second);
2515 if (details.HasStreamDetails())
2516 SetStreamDetailsForFileId(details.m_streamDetails, GetAndFillFileId(details));
2518 SetArtForItem(idMovie, MediaTypeMovie, artwork);
2520 if (!details.HasUniqueID() && details.HasYear())
2521 { // query DB for any movies matching online id and year
2522 std::string strSQL = PrepareSQL("SELECT files.playCount, files.lastPlayed "
2523 "FROM movie "
2524 " INNER JOIN files "
2525 " ON files.idFile=movie.idFile "
2526 " JOIN uniqueid "
2527 " ON movie.idMovie=uniqueid.media_id AND uniqueid.media_type='movie' AND uniqueid.value='%s'"
2528 "WHERE movie.premiered LIKE '%i%%' AND movie.idMovie!=%i AND files.playCount > 0",
2529 details.GetUniqueID().c_str(), details.GetYear(), idMovie);
2530 m_pDS->query(strSQL);
2532 if (!m_pDS->eof())
2534 int playCount = m_pDS->fv("files.playCount").get_asInt();
2536 CDateTime lastPlayed;
2537 lastPlayed.SetFromDBDateTime(m_pDS->fv("files.lastPlayed").get_asString());
2539 // update with playCount and lastPlayed
2540 strSQL =
2541 PrepareSQL("update files set playCount=%i,lastPlayed='%s' where idFile=%i", playCount,
2542 lastPlayed.GetAsDBDateTime().c_str(), GetAndFillFileId(details));
2543 m_pDS->exec(strSQL);
2546 m_pDS->close();
2548 // update our movie table (we know it was added already above)
2549 // and insert the new row
2550 std::string sql = "UPDATE movie SET " + GetValueString(details, VIDEODB_ID_MIN, VIDEODB_ID_MAX, DbMovieOffsets);
2551 if (idSet > 0)
2552 sql += PrepareSQL(", idSet = %i", idSet);
2553 else
2554 sql += ", idSet = NULL";
2555 if (details.m_iUserRating > 0 && details.m_iUserRating < 11)
2556 sql += PrepareSQL(", userrating = %i", details.m_iUserRating);
2557 else
2558 sql += ", userrating = NULL";
2559 if (details.HasPremiered())
2560 sql += PrepareSQL(", premiered = '%s'", details.GetPremiered().GetAsDBDate().c_str());
2561 else
2562 sql += PrepareSQL(", premiered = '%i'", details.GetYear());
2563 sql += PrepareSQL(" where idMovie=%i", idMovie);
2564 m_pDS->exec(sql);
2565 CommitTransaction();
2567 return idMovie;
2569 catch (...)
2571 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, filePath);
2573 RollbackTransaction();
2574 return -1;
2577 int CVideoDatabase::UpdateDetailsForMovie(int idMovie, CVideoInfoTag& details, const std::map<std::string, std::string> &artwork, const std::set<std::string> &updatedDetails)
2579 if (idMovie < 0)
2580 return idMovie;
2584 CLog::Log(LOGINFO, "{}: Starting updates for movie {}", __FUNCTION__, idMovie);
2586 BeginTransaction();
2588 // process the link table updates
2589 if (updatedDetails.find("genre") != updatedDetails.end())
2590 UpdateLinksToItem(idMovie, MediaTypeMovie, "genre", details.m_genre);
2591 if (updatedDetails.find("studio") != updatedDetails.end())
2592 UpdateLinksToItem(idMovie, MediaTypeMovie, "studio", details.m_studio);
2593 if (updatedDetails.find("country") != updatedDetails.end())
2594 UpdateLinksToItem(idMovie, MediaTypeMovie, "country", details.m_country);
2595 if (updatedDetails.find("tag") != updatedDetails.end())
2596 UpdateLinksToItem(idMovie, MediaTypeMovie, "tag", details.m_tags);
2597 if (updatedDetails.find("director") != updatedDetails.end())
2598 UpdateActorLinksToItem(idMovie, MediaTypeMovie, "director", details.m_director);
2599 if (updatedDetails.find("writer") != updatedDetails.end())
2600 UpdateActorLinksToItem(idMovie, MediaTypeMovie, "writer", details.m_writingCredits);
2601 if (updatedDetails.find("art.altered") != updatedDetails.end())
2602 SetArtForItem(idMovie, MediaTypeMovie, artwork);
2603 if (updatedDetails.find("ratings") != updatedDetails.end())
2604 details.m_iIdRating = UpdateRatings(idMovie, MediaTypeMovie, details.m_ratings, details.GetDefaultRating());
2605 if (updatedDetails.find("uniqueid") != updatedDetails.end())
2606 details.m_iIdUniqueID = UpdateUniqueIDs(idMovie, MediaTypeMovie, details);
2607 if (updatedDetails.find("dateadded") != updatedDetails.end() && details.m_dateAdded.IsValid())
2608 UpdateFileDateAdded(details);
2610 // track if the set was updated
2611 int idSet = 0;
2612 if (updatedDetails.find("set") != updatedDetails.end())
2613 { // set
2614 idSet = -1;
2615 if (!details.m_set.title.empty())
2617 idSet = AddSet(details.m_set.title, details.m_set.overview);
2621 if (updatedDetails.find("showlink") != updatedDetails.end())
2623 // remove existing links
2624 std::vector<int> tvShowIds;
2625 GetLinksToTvShow(idMovie, tvShowIds);
2626 for (const auto& idTVShow : tvShowIds)
2627 LinkMovieToTvshow(idMovie, idTVShow, true);
2629 // setup links to shows if the linked shows are in the db
2630 for (const auto& showLink : details.m_showLink)
2632 CFileItemList items;
2633 GetTvShowsByName(showLink, items);
2634 if (!items.IsEmpty())
2635 LinkMovieToTvshow(idMovie, items[0]->GetVideoInfoTag()->m_iDbId, false);
2636 else
2637 CLog::Log(LOGWARNING, "{}: Failed to link movie {} to show {}", __FUNCTION__,
2638 details.m_strTitle, showLink);
2642 // and update the movie table
2643 std::string sql = "UPDATE movie SET " + GetValueString(details, VIDEODB_ID_MIN, VIDEODB_ID_MAX, DbMovieOffsets);
2644 if (idSet > 0)
2645 sql += PrepareSQL(", idSet = %i", idSet);
2646 else if (idSet < 0)
2647 sql += ", idSet = NULL";
2648 if (details.m_iUserRating > 0 && details.m_iUserRating < 11)
2649 sql += PrepareSQL(", userrating = %i", details.m_iUserRating);
2650 else
2651 sql += ", userrating = NULL";
2652 if (details.HasPremiered())
2653 sql += PrepareSQL(", premiered = '%s'", details.GetPremiered().GetAsDBDate().c_str());
2654 else
2655 sql += PrepareSQL(", premiered = '%i'", details.GetYear());
2656 sql += PrepareSQL(" where idMovie=%i", idMovie);
2657 m_pDS->exec(sql);
2659 CommitTransaction();
2661 CLog::Log(LOGINFO, "{}: Finished updates for movie {}", __FUNCTION__, idMovie);
2663 return idMovie;
2665 catch (...)
2667 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, idMovie);
2669 RollbackTransaction();
2670 return -1;
2673 int CVideoDatabase::SetDetailsForMovieSet(const CVideoInfoTag& details, const std::map<std::string, std::string> &artwork, int idSet /* = -1 */)
2675 if (details.m_strTitle.empty())
2676 return -1;
2680 BeginTransaction();
2681 if (idSet < 0)
2683 idSet = AddSet(details.m_strTitle, details.m_strPlot);
2684 if (idSet < 0)
2686 RollbackTransaction();
2687 return -1;
2691 SetArtForItem(idSet, MediaTypeVideoCollection, artwork);
2693 // and insert the new row
2694 std::string sql = PrepareSQL("UPDATE sets SET strSet='%s', strOverview='%s' WHERE idSet=%i", details.m_strTitle.c_str(), details.m_strPlot.c_str(), idSet);
2695 m_pDS->exec(sql);
2696 CommitTransaction();
2698 return idSet;
2700 catch (...)
2702 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, idSet);
2704 RollbackTransaction();
2705 return -1;
2708 int CVideoDatabase::GetMatchingTvShow(const CVideoInfoTag &details)
2710 // first try matching on uniqueid, then on title + year
2711 int id = -1;
2712 if (!details.HasUniqueID())
2713 id = GetDbId(PrepareSQL("SELECT idShow FROM tvshow JOIN uniqueid ON uniqueid.media_id=tvshow.idShow AND uniqueid.media_type='tvshow' WHERE uniqueid.value='%s'", details.GetUniqueID().c_str()));
2714 if (id < 0)
2715 id = GetDbId(PrepareSQL("SELECT idShow FROM tvshow WHERE c%02d='%s' AND c%02d='%s'", VIDEODB_ID_TV_TITLE, details.m_strTitle.c_str(), VIDEODB_ID_TV_PREMIERED, details.GetPremiered().GetAsDBDate().c_str()));
2716 return id;
2719 int CVideoDatabase::SetDetailsForTvShow(const std::vector<std::pair<std::string, std::string> > &paths,
2720 CVideoInfoTag& details, const std::map<std::string, std::string> &artwork,
2721 const std::map<int, std::map<std::string, std::string> > &seasonArt, int idTvShow /*= -1 */)
2725 The steps are as follows.
2726 1. Check if the tvshow is found on any of the given paths. If found, we have the show id.
2727 2. Search for a matching show. If found, we have the show id.
2728 3. If we don't have the id, add a new show.
2729 4. Add the paths to the show.
2730 5. Add details for the show.
2733 if (idTvShow < 0)
2735 for (const auto &i : paths)
2737 idTvShow = GetTvShowId(i.first);
2738 if (idTvShow > -1)
2739 break;
2742 if (idTvShow < 0)
2743 idTvShow = GetMatchingTvShow(details);
2744 if (idTvShow < 0)
2746 idTvShow = AddTvShow();
2747 if (idTvShow < 0)
2748 return -1;
2751 // add any paths to the tvshow
2752 for (const auto &i : paths)
2753 AddPathToTvShow(idTvShow, i.first, i.second, details.m_dateAdded);
2755 UpdateDetailsForTvShow(idTvShow, details, artwork, seasonArt);
2757 return idTvShow;
2760 bool CVideoDatabase::UpdateDetailsForTvShow(int idTvShow, CVideoInfoTag &details,
2761 const std::map<std::string, std::string> &artwork, const std::map<int, std::map<std::string, std::string>> &seasonArt)
2763 BeginTransaction();
2765 DeleteDetailsForTvShow(idTvShow);
2767 AddCast(idTvShow, "tvshow", details.m_cast);
2768 AddLinksToItem(idTvShow, MediaTypeTvShow, "genre", details.m_genre);
2769 AddLinksToItem(idTvShow, MediaTypeTvShow, "studio", details.m_studio);
2770 AddLinksToItem(idTvShow, MediaTypeTvShow, "tag", details.m_tags);
2771 AddActorLinksToItem(idTvShow, MediaTypeTvShow, "director", details.m_director);
2773 // add ratings
2774 details.m_iIdRating = AddRatings(idTvShow, MediaTypeTvShow, details.m_ratings, details.GetDefaultRating());
2776 // add unique ids
2777 details.m_iIdUniqueID = AddUniqueIDs(idTvShow, MediaTypeTvShow, details);
2779 // add "all seasons" - the rest are added in SetDetailsForEpisode
2780 AddSeason(idTvShow, -1);
2782 // add any named seasons
2783 for (const auto& namedSeason : details.m_namedSeasons)
2785 // make sure the named season exists
2786 int seasonId = AddSeason(idTvShow, namedSeason.first, namedSeason.second);
2788 // get any existing details for the named season
2789 CVideoInfoTag season;
2790 if (!GetSeasonInfo(seasonId, season, false) || season.m_strSortTitle == namedSeason.second)
2791 continue;
2793 season.SetSortTitle(namedSeason.second);
2794 SetDetailsForSeason(season, std::map<std::string, std::string>(), idTvShow, seasonId);
2797 SetArtForItem(idTvShow, MediaTypeTvShow, artwork);
2798 for (const auto &i : seasonArt)
2800 int idSeason = AddSeason(idTvShow, i.first);
2801 if (idSeason > -1)
2802 SetArtForItem(idSeason, MediaTypeSeason, i.second);
2805 // and insert the new row
2806 std::string sql = "UPDATE tvshow SET " + GetValueString(details, VIDEODB_ID_TV_MIN, VIDEODB_ID_TV_MAX, DbTvShowOffsets);
2807 if (details.m_iUserRating > 0 && details.m_iUserRating < 11)
2808 sql += PrepareSQL(", userrating = %i", details.m_iUserRating);
2809 else
2810 sql += ", userrating = NULL";
2811 if (details.GetDuration() > 0)
2812 sql += PrepareSQL(", duration = %i", details.GetDuration());
2813 else
2814 sql += ", duration = NULL";
2815 sql += PrepareSQL(" WHERE idShow=%i", idTvShow);
2816 if (ExecuteQuery(sql))
2818 CommitTransaction();
2819 return true;
2821 RollbackTransaction();
2822 return false;
2825 int CVideoDatabase::SetDetailsForSeason(const CVideoInfoTag& details, const std::map<std::string,
2826 std::string> &artwork, int idShow, int idSeason /* = -1 */)
2828 if (idShow < 0 || details.m_iSeason < -1)
2829 return -1;
2833 BeginTransaction();
2834 if (idSeason < 0)
2836 idSeason = AddSeason(idShow, details.m_iSeason);
2837 if (idSeason < 0)
2839 RollbackTransaction();
2840 return -1;
2844 SetArtForItem(idSeason, MediaTypeSeason, artwork);
2846 // and insert the new row
2847 std::string sql = PrepareSQL("UPDATE seasons SET season=%i", details.m_iSeason);
2848 if (!details.m_strSortTitle.empty())
2849 sql += PrepareSQL(", name='%s'", details.m_strSortTitle.c_str());
2850 if (details.m_iUserRating > 0 && details.m_iUserRating < 11)
2851 sql += PrepareSQL(", userrating = %i", details.m_iUserRating);
2852 else
2853 sql += ", userrating = NULL";
2854 sql += PrepareSQL(" WHERE idSeason=%i", idSeason);
2855 m_pDS->exec(sql.c_str());
2856 CommitTransaction();
2858 return idSeason;
2860 catch (...)
2862 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, idSeason);
2864 RollbackTransaction();
2865 return -1;
2868 int CVideoDatabase::SetDetailsForEpisode(CVideoInfoTag& details,
2869 const std::map<std::string, std::string>& artwork,
2870 int idShow,
2871 int idEpisode /* = -1 */)
2873 const auto filePath = details.GetPath();
2877 BeginTransaction();
2878 if (idEpisode < 0)
2879 idEpisode = GetEpisodeId(filePath);
2881 if (idEpisode > 0)
2882 DeleteEpisode(idEpisode, true); // true to keep the table entry
2883 else
2885 // only add a new episode if we don't already have a valid idEpisode
2886 // (DeleteEpisode is called with bKeepId == true so the episode won't
2887 // be removed from the episode table)
2888 idEpisode = AddNewEpisode(idShow, details);
2889 if (idEpisode < 0)
2891 RollbackTransaction();
2892 return -1;
2896 // update dateadded if it's set
2897 if (details.m_dateAdded.IsValid())
2898 UpdateFileDateAdded(details);
2900 AddCast(idEpisode, "episode", details.m_cast);
2901 AddActorLinksToItem(idEpisode, MediaTypeEpisode, "director", details.m_director);
2902 AddActorLinksToItem(idEpisode, MediaTypeEpisode, "writer", details.m_writingCredits);
2904 // add ratings
2905 details.m_iIdRating = AddRatings(idEpisode, MediaTypeEpisode, details.m_ratings, details.GetDefaultRating());
2907 // add unique ids
2908 details.m_iIdUniqueID = AddUniqueIDs(idEpisode, MediaTypeEpisode, details);
2910 if (details.HasStreamDetails())
2911 SetStreamDetailsForFileId(details.m_streamDetails, GetAndFillFileId(details));
2913 // ensure we have this season already added
2914 int idSeason = AddSeason(idShow, details.m_iSeason);
2916 SetArtForItem(idEpisode, MediaTypeEpisode, artwork);
2918 if (details.m_iEpisode != -1 && details.m_iSeason != -1)
2919 { // query DB for any episodes matching idShow, Season and Episode
2920 std::string strSQL = PrepareSQL("SELECT files.playCount, files.lastPlayed "
2921 "FROM episode INNER JOIN files ON files.idFile=episode.idFile "
2922 "WHERE episode.c%02d=%i AND episode.c%02d=%i AND episode.idShow=%i "
2923 "AND episode.idEpisode!=%i AND files.playCount > 0",
2924 VIDEODB_ID_EPISODE_SEASON, details.m_iSeason, VIDEODB_ID_EPISODE_EPISODE,
2925 details.m_iEpisode, idShow, idEpisode);
2926 m_pDS->query(strSQL);
2928 if (!m_pDS->eof())
2930 int playCount = m_pDS->fv("files.playCount").get_asInt();
2932 CDateTime lastPlayed;
2933 lastPlayed.SetFromDBDateTime(m_pDS->fv("files.lastPlayed").get_asString());
2935 // update with playCount and lastPlayed
2936 strSQL =
2937 PrepareSQL("update files set playCount=%i,lastPlayed='%s' where idFile=%i", playCount,
2938 lastPlayed.GetAsDBDateTime().c_str(), GetAndFillFileId(details));
2939 m_pDS->exec(strSQL);
2942 m_pDS->close();
2944 // and insert the new row
2945 std::string sql = "UPDATE episode SET " + GetValueString(details, VIDEODB_ID_EPISODE_MIN, VIDEODB_ID_EPISODE_MAX, DbEpisodeOffsets);
2946 if (details.m_iUserRating > 0 && details.m_iUserRating < 11)
2947 sql += PrepareSQL(", userrating = %i", details.m_iUserRating);
2948 else
2949 sql += ", userrating = NULL";
2950 sql += PrepareSQL(", idSeason = %i", idSeason);
2951 sql += PrepareSQL(" where idEpisode=%i", idEpisode);
2952 m_pDS->exec(sql);
2953 CommitTransaction();
2955 return idEpisode;
2957 catch (...)
2959 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, filePath);
2961 RollbackTransaction();
2962 return -1;
2965 int CVideoDatabase::GetSeasonId(int showID, int season)
2967 std::string sql = PrepareSQL("idShow=%i AND season=%i", showID, season);
2968 std::string id = GetSingleValue("seasons", "idSeason", sql);
2969 if (id.empty())
2970 return -1;
2971 return strtol(id.c_str(), NULL, 10);
2974 int CVideoDatabase::AddSeason(int showID, int season, const std::string& name /* = "" */)
2976 int seasonId = GetSeasonId(showID, season);
2977 if (seasonId < 0)
2979 if (ExecuteQuery(PrepareSQL("INSERT INTO seasons (idShow, season, name) VALUES(%i, %i, '%s')", showID, season, name.c_str())))
2980 seasonId = (int)m_pDS->lastinsertid();
2982 return seasonId;
2985 int CVideoDatabase::SetDetailsForMusicVideo(CVideoInfoTag& details,
2986 const std::map<std::string, std::string>& artwork,
2987 int idMVideo /* = -1 */)
2989 const auto filePath = details.GetPath();
2993 BeginTransaction();
2995 if (idMVideo < 0)
2996 idMVideo = GetMusicVideoId(filePath);
2998 if (idMVideo > -1)
2999 DeleteMusicVideo(idMVideo, true); // Keep id
3000 else
3002 // only add a new musicvideo if we don't already have a valid idMVideo
3003 // (DeleteMusicVideo is called with bKeepId == true so the musicvideo won't
3004 // be removed from the musicvideo table)
3005 idMVideo = AddNewMusicVideo(details);
3006 if (idMVideo < 0)
3008 RollbackTransaction();
3009 return -1;
3013 // update dateadded if it's set
3014 if (details.m_dateAdded.IsValid())
3015 UpdateFileDateAdded(details);
3017 AddCast(idMVideo, MediaTypeMusicVideo, details.m_cast);
3018 AddActorLinksToItem(idMVideo, MediaTypeMusicVideo, "actor", details.m_artist);
3019 AddActorLinksToItem(idMVideo, MediaTypeMusicVideo, "director", details.m_director);
3020 AddLinksToItem(idMVideo, MediaTypeMusicVideo, "genre", details.m_genre);
3021 AddLinksToItem(idMVideo, MediaTypeMusicVideo, "studio", details.m_studio);
3022 AddLinksToItem(idMVideo, MediaTypeMusicVideo, "tag", details.m_tags);
3024 // add unique ids
3025 details.m_iIdUniqueID = UpdateUniqueIDs(idMVideo, MediaTypeMusicVideo, details);
3027 if (details.HasStreamDetails())
3028 SetStreamDetailsForFileId(details.m_streamDetails, GetAndFillFileId(details));
3030 SetArtForItem(idMVideo, MediaTypeMusicVideo, artwork);
3032 // update our movie table (we know it was added already above)
3033 // and insert the new row
3034 std::string sql = "UPDATE musicvideo SET " + GetValueString(details, VIDEODB_ID_MUSICVIDEO_MIN, VIDEODB_ID_MUSICVIDEO_MAX, DbMusicVideoOffsets);
3035 if (details.m_iUserRating > 0 && details.m_iUserRating < 11)
3036 sql += PrepareSQL(", userrating = %i", details.m_iUserRating);
3037 else
3038 sql += ", userrating = NULL";
3039 if (details.HasPremiered())
3040 sql += PrepareSQL(", premiered = '%s'", details.GetPremiered().GetAsDBDate().c_str());
3041 else
3042 sql += PrepareSQL(", premiered = '%i'", details.GetYear());
3043 sql += PrepareSQL(" where idMVideo=%i", idMVideo);
3044 m_pDS->exec(sql);
3045 CommitTransaction();
3047 return idMVideo;
3049 catch (...)
3051 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, filePath);
3053 RollbackTransaction();
3054 return -1;
3057 void CVideoDatabase::SetStreamDetailsForFile(const CStreamDetails& details, const std::string &strFileNameAndPath)
3059 // AddFile checks to make sure the file isn't already in the DB first
3060 int idFile = AddFile(strFileNameAndPath);
3061 if (idFile < 0)
3062 return;
3063 SetStreamDetailsForFileId(details, idFile);
3066 void CVideoDatabase::SetStreamDetailsForFileId(const CStreamDetails& details, int idFile)
3068 if (idFile < 0)
3069 return;
3073 m_pDS->exec(PrepareSQL("DELETE FROM streamdetails WHERE idFile = %i", idFile));
3075 for (int i=1; i<=details.GetVideoStreamCount(); i++)
3077 m_pDS->exec(PrepareSQL("INSERT INTO streamdetails "
3078 "(idFile, iStreamType, strVideoCodec, fVideoAspect, iVideoWidth, "
3079 "iVideoHeight, iVideoDuration, strStereoMode, strVideoLanguage, "
3080 "strHdrType)"
3081 "VALUES (%i,%i,'%s',%f,%i,%i,%i,'%s','%s','%s')",
3082 idFile, (int)CStreamDetail::VIDEO, details.GetVideoCodec(i).c_str(),
3083 static_cast<double>(details.GetVideoAspect(i)),
3084 details.GetVideoWidth(i), details.GetVideoHeight(i),
3085 details.GetVideoDuration(i), details.GetStereoMode(i).c_str(),
3086 details.GetVideoLanguage(i).c_str(),
3087 details.GetVideoHdrType(i).c_str()));
3089 for (int i=1; i<=details.GetAudioStreamCount(); i++)
3091 m_pDS->exec(PrepareSQL("INSERT INTO streamdetails "
3092 "(idFile, iStreamType, strAudioCodec, iAudioChannels, strAudioLanguage) "
3093 "VALUES (%i,%i,'%s',%i,'%s')",
3094 idFile, (int)CStreamDetail::AUDIO,
3095 details.GetAudioCodec(i).c_str(), details.GetAudioChannels(i),
3096 details.GetAudioLanguage(i).c_str()));
3098 for (int i=1; i<=details.GetSubtitleStreamCount(); i++)
3100 m_pDS->exec(PrepareSQL("INSERT INTO streamdetails "
3101 "(idFile, iStreamType, strSubtitleLanguage) "
3102 "VALUES (%i,%i,'%s')",
3103 idFile, (int)CStreamDetail::SUBTITLE,
3104 details.GetSubtitleLanguage(i).c_str()));
3107 // update the runtime information, if empty
3108 if (details.GetVideoDuration())
3110 std::vector<std::pair<std::string, int> > tables;
3111 tables.emplace_back("movie", VIDEODB_ID_RUNTIME);
3112 tables.emplace_back("episode", VIDEODB_ID_EPISODE_RUNTIME);
3113 tables.emplace_back("musicvideo", VIDEODB_ID_MUSICVIDEO_RUNTIME);
3114 for (const auto &i : tables)
3116 std::string sql = PrepareSQL("update %s set c%02d=%d where idFile=%d and c%02d=''",
3117 i.first.c_str(), i.second, details.GetVideoDuration(), idFile, i.second);
3118 m_pDS->exec(sql);
3122 catch (...)
3124 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, idFile);
3128 //********************************************************************************************************************************
3129 void CVideoDatabase::GetFilePathById(int idMovie, std::string& filePath, VideoDbContentType iType)
3133 if (nullptr == m_pDB)
3134 return;
3135 if (nullptr == m_pDS)
3136 return;
3138 if (idMovie < 0) return ;
3140 std::string strSQL;
3141 if (iType == VideoDbContentType::MOVIES)
3142 strSQL=PrepareSQL("SELECT path.strPath, files.strFileName FROM path INNER JOIN files ON path.idPath=files.idPath INNER JOIN movie ON files.idFile=movie.idFile WHERE movie.idMovie=%i ORDER BY strFilename", idMovie );
3143 if (iType == VideoDbContentType::EPISODES)
3144 strSQL=PrepareSQL("SELECT path.strPath, files.strFileName FROM path INNER JOIN files ON path.idPath=files.idPath INNER JOIN episode ON files.idFile=episode.idFile WHERE episode.idEpisode=%i ORDER BY strFilename", idMovie );
3145 if (iType == VideoDbContentType::TVSHOWS)
3146 strSQL=PrepareSQL("SELECT path.strPath FROM path INNER JOIN tvshowlinkpath ON path.idPath=tvshowlinkpath.idPath WHERE tvshowlinkpath.idShow=%i", idMovie );
3147 if (iType == VideoDbContentType::MUSICVIDEOS)
3148 strSQL=PrepareSQL("SELECT path.strPath, files.strFileName FROM path INNER JOIN files ON path.idPath=files.idPath INNER JOIN musicvideo ON files.idFile=musicvideo.idFile WHERE musicvideo.idMVideo=%i ORDER BY strFilename", idMovie );
3150 m_pDS->query( strSQL );
3151 if (!m_pDS->eof())
3153 if (iType != VideoDbContentType::TVSHOWS)
3155 std::string fileName = m_pDS->fv("files.strFilename").get_asString();
3156 ConstructPath(filePath,m_pDS->fv("path.strPath").get_asString(),fileName);
3158 else
3159 filePath = m_pDS->fv("path.strPath").get_asString();
3161 m_pDS->close();
3163 catch (...)
3165 CLog::Log(LOGERROR, "{} failed", __FUNCTION__);
3169 //********************************************************************************************************************************
3170 void CVideoDatabase::GetBookMarksForFile(const std::string& strFilenameAndPath, VECBOOKMARKS& bookmarks, CBookmark::EType type /*= CBookmark::STANDARD*/, bool bAppend, long partNumber)
3174 if (URIUtils::IsStack(strFilenameAndPath) && CFileItem(CStackDirectory::GetFirstStackedFile(strFilenameAndPath),false).IsDiscImage())
3176 CStackDirectory dir;
3177 CFileItemList fileList;
3178 const CURL pathToUrl(strFilenameAndPath);
3179 dir.GetDirectory(pathToUrl, fileList);
3180 if (!bAppend)
3181 bookmarks.clear();
3182 for (int i = fileList.Size() - 1; i >= 0; i--) // put the bookmarks of the highest part first in the list
3183 GetBookMarksForFile(fileList[i]->GetPath(), bookmarks, type, true, (i+1));
3185 else
3187 int idFile = GetFileId(strFilenameAndPath);
3188 if (idFile < 0) return ;
3189 if (!bAppend)
3190 bookmarks.erase(bookmarks.begin(), bookmarks.end());
3191 if (nullptr == m_pDB)
3192 return;
3193 if (nullptr == m_pDS)
3194 return;
3196 std::string strSQL=PrepareSQL("select * from bookmark where idFile=%i and type=%i order by timeInSeconds", idFile, (int)type);
3197 m_pDS->query( strSQL );
3198 while (!m_pDS->eof())
3200 CBookmark bookmark;
3201 bookmark.timeInSeconds = m_pDS->fv("timeInSeconds").get_asDouble();
3202 bookmark.partNumber = partNumber;
3203 bookmark.totalTimeInSeconds = m_pDS->fv("totalTimeInSeconds").get_asDouble();
3204 bookmark.thumbNailImage = m_pDS->fv("thumbNailImage").get_asString();
3205 bookmark.playerState = m_pDS->fv("playerState").get_asString();
3206 bookmark.player = m_pDS->fv("player").get_asString();
3207 bookmark.type = type;
3208 if (type == CBookmark::EPISODE)
3210 std::string strSQL2=PrepareSQL("select c%02d, c%02d from episode where c%02d=%i order by c%02d, c%02d", VIDEODB_ID_EPISODE_EPISODE, VIDEODB_ID_EPISODE_SEASON, VIDEODB_ID_EPISODE_BOOKMARK, m_pDS->fv("idBookmark").get_asInt(), VIDEODB_ID_EPISODE_SORTSEASON, VIDEODB_ID_EPISODE_SORTEPISODE);
3211 m_pDS2->query(strSQL2);
3212 bookmark.episodeNumber = m_pDS2->fv(0).get_asInt();
3213 bookmark.seasonNumber = m_pDS2->fv(1).get_asInt();
3214 m_pDS2->close();
3216 bookmarks.push_back(bookmark);
3217 m_pDS->next();
3219 //sort(bookmarks.begin(), bookmarks.end(), SortBookmarks);
3220 m_pDS->close();
3223 catch (...)
3225 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, strFilenameAndPath);
3229 bool CVideoDatabase::GetResumeBookMark(const std::string& strFilenameAndPath, CBookmark &bookmark)
3231 VECBOOKMARKS bookmarks;
3232 GetBookMarksForFile(strFilenameAndPath, bookmarks, CBookmark::RESUME);
3233 if (!bookmarks.empty())
3235 bookmark = bookmarks[0];
3236 return true;
3238 return false;
3241 void CVideoDatabase::DeleteResumeBookMark(const CFileItem& item)
3243 if (!m_pDB || !m_pDS)
3244 return;
3246 int fileID = item.GetVideoInfoTag()->m_iFileId;
3247 if (fileID < 0)
3249 fileID = GetFileId(item.GetPath());
3250 if (fileID < 0)
3251 return;
3256 std::string sql = PrepareSQL("delete from bookmark where idFile=%i and type=%i", fileID, CBookmark::RESUME);
3257 m_pDS->exec(sql);
3259 VideoDbContentType iType = static_cast<VideoDbContentType>(item.GetVideoContentType());
3260 std::string content;
3261 switch (iType)
3263 case VideoDbContentType::MOVIES:
3264 content = MediaTypeMovie;
3265 break;
3266 case VideoDbContentType::EPISODES:
3267 content = MediaTypeEpisode;
3268 break;
3269 case VideoDbContentType::TVSHOWS:
3270 content = MediaTypeTvShow;
3271 break;
3272 case VideoDbContentType::MUSICVIDEOS:
3273 content = MediaTypeMusicVideo;
3274 break;
3275 default:
3276 break;
3279 if (!content.empty())
3281 AnnounceUpdate(content, item.GetVideoInfoTag()->m_iDbId);
3285 catch(...)
3287 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__,
3288 item.GetVideoInfoTag()->m_strFileNameAndPath);
3292 void CVideoDatabase::GetEpisodesByFile(const std::string& strFilenameAndPath, std::vector<CVideoInfoTag>& episodes)
3296 std::string strSQL = PrepareSQL("select * from episode_view where idFile=%i order by c%02d, c%02d asc", GetFileId(strFilenameAndPath), VIDEODB_ID_EPISODE_SORTSEASON, VIDEODB_ID_EPISODE_SORTEPISODE);
3297 m_pDS->query(strSQL);
3298 while (!m_pDS->eof())
3300 episodes.emplace_back(GetDetailsForEpisode(m_pDS));
3301 m_pDS->next();
3303 m_pDS->close();
3305 catch (...)
3307 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, strFilenameAndPath);
3311 //********************************************************************************************************************************
3312 void CVideoDatabase::AddBookMarkToFile(const std::string& strFilenameAndPath, const CBookmark &bookmark, CBookmark::EType type /*= CBookmark::STANDARD*/)
3316 int idFile = AddFile(strFilenameAndPath);
3317 if (idFile < 0)
3318 return;
3319 if (nullptr == m_pDB)
3320 return;
3321 if (nullptr == m_pDS)
3322 return;
3324 std::string strSQL;
3325 int idBookmark=-1;
3326 if (type == CBookmark::RESUME) // get the same resume mark bookmark each time type
3328 strSQL=PrepareSQL("select idBookmark from bookmark where idFile=%i and type=1", idFile);
3330 else if (type == CBookmark::STANDARD) // get the same bookmark again, and update. not sure here as a dvd can have same time in multiple places, state will differ thou
3332 /* get a bookmark within the same time as previous */
3333 double mintime = bookmark.timeInSeconds - 0.5;
3334 double maxtime = bookmark.timeInSeconds + 0.5;
3335 strSQL=PrepareSQL("select idBookmark from bookmark where idFile=%i and type=%i and (timeInSeconds between %f and %f) and playerState='%s'", idFile, (int)type, mintime, maxtime, bookmark.playerState.c_str());
3338 if (type != CBookmark::EPISODE)
3340 // get current id
3341 m_pDS->query( strSQL );
3342 if (m_pDS->num_rows() != 0)
3343 idBookmark = m_pDS->get_field_value("idBookmark").get_asInt();
3344 m_pDS->close();
3346 // update or insert depending if it existed before
3347 if (idBookmark >= 0 )
3348 strSQL=PrepareSQL("update bookmark set timeInSeconds = %f, totalTimeInSeconds = %f, thumbNailImage = '%s', player = '%s', playerState = '%s' where idBookmark = %i", bookmark.timeInSeconds, bookmark.totalTimeInSeconds, bookmark.thumbNailImage.c_str(), bookmark.player.c_str(), bookmark.playerState.c_str(), idBookmark);
3349 else
3350 strSQL=PrepareSQL("insert into bookmark (idBookmark, idFile, timeInSeconds, totalTimeInSeconds, thumbNailImage, player, playerState, type) values(NULL,%i,%f,%f,'%s','%s','%s', %i)", idFile, bookmark.timeInSeconds, bookmark.totalTimeInSeconds, bookmark.thumbNailImage.c_str(), bookmark.player.c_str(), bookmark.playerState.c_str(), (int)type);
3352 m_pDS->exec(strSQL);
3354 catch (...)
3356 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, strFilenameAndPath);
3360 void CVideoDatabase::ClearBookMarkOfFile(const std::string& strFilenameAndPath, CBookmark& bookmark, CBookmark::EType type /*= CBookmark::STANDARD*/)
3364 int idFile = GetFileId(strFilenameAndPath);
3365 if (idFile < 0) return ;
3366 if (nullptr == m_pDB)
3367 return;
3368 if (nullptr == m_pDS)
3369 return;
3371 /* a little bit ugly, we clear first bookmark that is within one second of given */
3372 /* should be no problem since we never add bookmarks that are closer than that */
3373 double mintime = bookmark.timeInSeconds - 0.5;
3374 double maxtime = bookmark.timeInSeconds + 0.5;
3375 std::string strSQL = PrepareSQL("select idBookmark from bookmark where idFile=%i and type=%i and playerState like '%s' and player like '%s' and (timeInSeconds between %f and %f)", idFile, type, bookmark.playerState.c_str(), bookmark.player.c_str(), mintime, maxtime);
3377 m_pDS->query( strSQL );
3378 if (m_pDS->num_rows() != 0)
3380 int idBookmark = m_pDS->get_field_value("idBookmark").get_asInt();
3381 strSQL=PrepareSQL("delete from bookmark where idBookmark=%i",idBookmark);
3382 m_pDS->exec(strSQL);
3383 if (type == CBookmark::EPISODE)
3385 strSQL=PrepareSQL("update episode set c%02d=-1 where idFile=%i and c%02d=%i", VIDEODB_ID_EPISODE_BOOKMARK, idFile, VIDEODB_ID_EPISODE_BOOKMARK, idBookmark);
3386 m_pDS->exec(strSQL);
3390 m_pDS->close();
3392 catch (...)
3394 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, strFilenameAndPath);
3398 //********************************************************************************************************************************
3399 void CVideoDatabase::ClearBookMarksOfFile(const std::string& strFilenameAndPath, CBookmark::EType type /*= CBookmark::STANDARD*/)
3401 int idFile = GetFileId(strFilenameAndPath);
3402 if (idFile >= 0)
3403 return ClearBookMarksOfFile(idFile, type);
3406 void CVideoDatabase::ClearBookMarksOfFile(int idFile, CBookmark::EType type /*= CBookmark::STANDARD*/)
3408 if (idFile < 0)
3409 return;
3413 if (nullptr == m_pDB)
3414 return;
3415 if (nullptr == m_pDS)
3416 return;
3418 std::string strSQL=PrepareSQL("delete from bookmark where idFile=%i and type=%i", idFile, (int)type);
3419 m_pDS->exec(strSQL);
3420 if (type == CBookmark::EPISODE)
3422 strSQL=PrepareSQL("update episode set c%02d=-1 where idFile=%i", VIDEODB_ID_EPISODE_BOOKMARK, idFile);
3423 m_pDS->exec(strSQL);
3426 catch (...)
3428 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, idFile);
3433 bool CVideoDatabase::GetBookMarkForEpisode(const CVideoInfoTag& tag, CBookmark& bookmark)
3437 std::string strSQL = PrepareSQL("select bookmark.* from bookmark join episode on episode.c%02d=bookmark.idBookmark where episode.idEpisode=%i", VIDEODB_ID_EPISODE_BOOKMARK, tag.m_iDbId);
3438 m_pDS2->query( strSQL );
3439 if (!m_pDS2->eof())
3441 bookmark.timeInSeconds = m_pDS2->fv("timeInSeconds").get_asDouble();
3442 bookmark.totalTimeInSeconds = m_pDS2->fv("totalTimeInSeconds").get_asDouble();
3443 bookmark.thumbNailImage = m_pDS2->fv("thumbNailImage").get_asString();
3444 bookmark.playerState = m_pDS2->fv("playerState").get_asString();
3445 bookmark.player = m_pDS2->fv("player").get_asString();
3446 bookmark.type = (CBookmark::EType)m_pDS2->fv("type").get_asInt();
3448 else
3450 m_pDS2->close();
3451 return false;
3453 m_pDS2->close();
3455 catch (...)
3457 CLog::Log(LOGERROR, "{} failed", __FUNCTION__);
3458 return false;
3460 return true;
3463 void CVideoDatabase::AddBookMarkForEpisode(const CVideoInfoTag& tag, const CBookmark& bookmark)
3467 int idFile = GetFileId(tag.m_strFileNameAndPath);
3468 // delete the current episode for the selected episode number
3469 std::string strSQL = PrepareSQL("delete from bookmark where idBookmark in (select c%02d from episode where c%02d=%i and c%02d=%i and idFile=%i)", VIDEODB_ID_EPISODE_BOOKMARK, VIDEODB_ID_EPISODE_SEASON, tag.m_iSeason, VIDEODB_ID_EPISODE_EPISODE, tag.m_iEpisode, idFile);
3470 m_pDS->exec(strSQL);
3472 AddBookMarkToFile(tag.m_strFileNameAndPath, bookmark, CBookmark::EPISODE);
3473 int idBookmark = (int)m_pDS->lastinsertid();
3474 strSQL = PrepareSQL("update episode set c%02d=%i where c%02d=%i and c%02d=%i and idFile=%i", VIDEODB_ID_EPISODE_BOOKMARK, idBookmark, VIDEODB_ID_EPISODE_SEASON, tag.m_iSeason, VIDEODB_ID_EPISODE_EPISODE, tag.m_iEpisode, idFile);
3475 m_pDS->exec(strSQL);
3477 catch (...)
3479 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, tag.m_iDbId);
3483 void CVideoDatabase::DeleteBookMarkForEpisode(const CVideoInfoTag& tag)
3487 std::string strSQL = PrepareSQL("delete from bookmark where idBookmark in (select c%02d from episode where idEpisode=%i)", VIDEODB_ID_EPISODE_BOOKMARK, tag.m_iDbId);
3488 m_pDS->exec(strSQL);
3489 strSQL = PrepareSQL("update episode set c%02d=-1 where idEpisode=%i", VIDEODB_ID_EPISODE_BOOKMARK, tag.m_iDbId);
3490 m_pDS->exec(strSQL);
3492 catch (...)
3494 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, tag.m_iDbId);
3498 //********************************************************************************************************************************
3499 void CVideoDatabase::DeleteMovie(int idMovie, bool bKeepId /* = false */)
3501 if (idMovie < 0)
3502 return;
3506 if (nullptr == m_pDB)
3507 return;
3508 if (nullptr == m_pDS)
3509 return;
3511 BeginTransaction();
3513 int idFile = GetDbId(PrepareSQL("SELECT idFile FROM movie WHERE idMovie=%i", idMovie));
3514 DeleteStreamDetails(idFile);
3516 // keep the movie table entry, linking to tv shows, and bookmarks
3517 // so we can update the data in place
3518 // the ancillary tables are still purged
3519 if (!bKeepId)
3521 std::string path = GetSingleValue(PrepareSQL("SELECT strPath FROM path JOIN files ON files.idPath=path.idPath WHERE files.idFile=%i", idFile));
3522 if (!path.empty())
3523 InvalidatePathHash(path);
3525 std::string strSQL = PrepareSQL("delete from movie where idMovie=%i", idMovie);
3526 m_pDS->exec(strSQL);
3529 //! @todo move this below CommitTransaction() once UPnP doesn't rely on this anymore
3530 if (!bKeepId)
3531 AnnounceRemove(MediaTypeMovie, idMovie);
3533 CommitTransaction();
3536 catch (...)
3538 CLog::Log(LOGERROR, "{} failed", __FUNCTION__);
3539 RollbackTransaction();
3543 void CVideoDatabase::DeleteTvShow(const std::string& strPath)
3545 int idTvShow = GetTvShowId(strPath);
3546 if (idTvShow >= 0)
3547 DeleteTvShow(idTvShow);
3550 void CVideoDatabase::DeleteTvShow(int idTvShow, bool bKeepId /* = false */)
3552 if (idTvShow < 0)
3553 return;
3557 if (nullptr == m_pDB)
3558 return;
3559 if (nullptr == m_pDS)
3560 return;
3562 BeginTransaction();
3564 std::set<int> paths;
3565 GetPathsForTvShow(idTvShow, paths);
3567 std::string strSQL=PrepareSQL("SELECT episode.idEpisode FROM episode WHERE episode.idShow=%i",idTvShow);
3568 m_pDS2->query(strSQL);
3569 while (!m_pDS2->eof())
3571 DeleteEpisode(m_pDS2->fv(0).get_asInt(), bKeepId);
3572 m_pDS2->next();
3575 DeleteDetailsForTvShow(idTvShow);
3577 strSQL=PrepareSQL("delete from seasons where idShow=%i", idTvShow);
3578 m_pDS->exec(strSQL);
3580 // keep tvshow table and movielink table so we can update data in place
3581 if (!bKeepId)
3583 strSQL=PrepareSQL("delete from tvshow where idShow=%i", idTvShow);
3584 m_pDS->exec(strSQL);
3586 for (const auto &i : paths)
3588 std::string path = GetSingleValue(PrepareSQL("SELECT strPath FROM path WHERE idPath=%i", i));
3589 if (!path.empty())
3590 InvalidatePathHash(path);
3594 //! @todo move this below CommitTransaction() once UPnP doesn't rely on this anymore
3595 if (!bKeepId)
3596 AnnounceRemove(MediaTypeTvShow, idTvShow);
3598 CommitTransaction();
3601 catch (...)
3603 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, idTvShow);
3604 RollbackTransaction();
3608 void CVideoDatabase::DeleteSeason(int idSeason, bool bKeepId /* = false */)
3610 if (idSeason < 0)
3611 return;
3615 if (m_pDB == nullptr || m_pDS == nullptr || m_pDS2 == nullptr)
3616 return;
3618 BeginTransaction();
3620 std::string strSQL = PrepareSQL("SELECT episode.idEpisode FROM episode "
3621 "JOIN seasons ON seasons.idSeason = %d AND episode.idShow = seasons.idShow AND episode.c%02d = seasons.season ",
3622 idSeason, VIDEODB_ID_EPISODE_SEASON);
3623 m_pDS2->query(strSQL);
3624 while (!m_pDS2->eof())
3626 DeleteEpisode(m_pDS2->fv(0).get_asInt(), bKeepId);
3627 m_pDS2->next();
3630 ExecuteQuery(PrepareSQL("DELETE FROM seasons WHERE idSeason = %i", idSeason));
3632 CommitTransaction();
3634 catch (...)
3636 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, idSeason);
3637 RollbackTransaction();
3641 void CVideoDatabase::DeleteEpisode(int idEpisode, bool bKeepId /* = false */)
3643 if (idEpisode < 0)
3644 return;
3648 if (nullptr == m_pDB)
3649 return;
3650 if (nullptr == m_pDS)
3651 return;
3653 //! @todo move this below CommitTransaction() once UPnP doesn't rely on this anymore
3654 if (!bKeepId)
3655 AnnounceRemove(MediaTypeEpisode, idEpisode);
3657 int idFile = GetDbId(PrepareSQL("SELECT idFile FROM episode WHERE idEpisode=%i", idEpisode));
3658 DeleteStreamDetails(idFile);
3660 // keep episode table entry and bookmarks so we can update the data in place
3661 // the ancillary tables are still purged
3662 if (!bKeepId)
3664 std::string path = GetSingleValue(PrepareSQL("SELECT strPath FROM path JOIN files ON files.idPath=path.idPath WHERE files.idFile=%i", idFile));
3665 if (!path.empty())
3666 InvalidatePathHash(path);
3668 std::string strSQL = PrepareSQL("delete from episode where idEpisode=%i", idEpisode);
3669 m_pDS->exec(strSQL);
3673 catch (...)
3675 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, idEpisode);
3679 void CVideoDatabase::DeleteMusicVideo(int idMVideo, bool bKeepId /* = false */)
3681 if (idMVideo < 0)
3682 return;
3686 if (nullptr == m_pDB)
3687 return;
3688 if (nullptr == m_pDS)
3689 return;
3691 BeginTransaction();
3693 int idFile = GetDbId(PrepareSQL("SELECT idFile FROM musicvideo WHERE idMVideo=%i", idMVideo));
3694 DeleteStreamDetails(idFile);
3696 // keep the music video table entry and bookmarks so we can update data in place
3697 // the ancillary tables are still purged
3698 if (!bKeepId)
3700 std::string path = GetSingleValue(PrepareSQL("SELECT strPath FROM path JOIN files ON files.idPath=path.idPath WHERE files.idFile=%i", idFile));
3701 if (!path.empty())
3702 InvalidatePathHash(path);
3704 std::string strSQL = PrepareSQL("delete from musicvideo where idMVideo=%i", idMVideo);
3705 m_pDS->exec(strSQL);
3708 //! @todo move this below CommitTransaction() once UPnP doesn't rely on this anymore
3709 if (!bKeepId)
3710 AnnounceRemove(MediaTypeMusicVideo, idMVideo);
3712 CommitTransaction();
3715 catch (...)
3717 CLog::Log(LOGERROR, "{} failed", __FUNCTION__);
3718 RollbackTransaction();
3722 int CVideoDatabase::GetDbId(const std::string &query)
3724 std::string result = GetSingleValue(query);
3725 if (!result.empty())
3727 int idDb = strtol(result.c_str(), NULL, 10);
3728 if (idDb > 0)
3729 return idDb;
3731 return -1;
3734 void CVideoDatabase::DeleteStreamDetails(int idFile)
3736 m_pDS->exec(PrepareSQL("DELETE FROM streamdetails WHERE idFile = %i", idFile));
3739 void CVideoDatabase::DeleteSet(int idSet)
3743 if (nullptr == m_pDB)
3744 return;
3745 if (nullptr == m_pDS)
3746 return;
3748 std::string strSQL;
3749 strSQL=PrepareSQL("delete from sets where idSet = %i", idSet);
3750 m_pDS->exec(strSQL);
3751 strSQL=PrepareSQL("update movie set idSet = null where idSet = %i", idSet);
3752 m_pDS->exec(strSQL);
3754 catch (...)
3756 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, idSet);
3760 void CVideoDatabase::ClearMovieSet(int idMovie)
3762 SetMovieSet(idMovie, -1);
3765 void CVideoDatabase::SetMovieSet(int idMovie, int idSet)
3767 if (idSet >= 0)
3768 ExecuteQuery(PrepareSQL("update movie set idSet = %i where idMovie = %i", idSet, idMovie));
3769 else
3770 ExecuteQuery(PrepareSQL("update movie set idSet = null where idMovie = %i", idMovie));
3773 void CVideoDatabase::DeleteTag(int idTag, VideoDbContentType mediaType)
3777 if (m_pDB == nullptr || m_pDS == nullptr)
3778 return;
3780 std::string type;
3781 if (mediaType == VideoDbContentType::MOVIES)
3782 type = MediaTypeMovie;
3783 else if (mediaType == VideoDbContentType::TVSHOWS)
3784 type = MediaTypeTvShow;
3785 else if (mediaType == VideoDbContentType::MUSICVIDEOS)
3786 type = MediaTypeMusicVideo;
3787 else
3788 return;
3790 std::string strSQL = PrepareSQL("DELETE FROM tag_link WHERE tag_id = %i AND media_type = '%s'", idTag, type.c_str());
3791 m_pDS->exec(strSQL);
3793 catch (...)
3795 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, idTag);
3799 void CVideoDatabase::GetDetailsFromDB(std::unique_ptr<Dataset> &pDS, int min, int max, const SDbTableOffsets *offsets, CVideoInfoTag &details, int idxOffset)
3801 GetDetailsFromDB(pDS->get_sql_record(), min, max, offsets, details, idxOffset);
3804 void CVideoDatabase::GetDetailsFromDB(const dbiplus::sql_record* const record, int min, int max, const SDbTableOffsets *offsets, CVideoInfoTag &details, int idxOffset)
3806 for (int i = min + 1; i < max; i++)
3808 switch (offsets[i].type)
3810 case VIDEODB_TYPE_STRING:
3811 *(std::string*)(((char*)&details)+offsets[i].offset) = record->at(i+idxOffset).get_asString();
3812 break;
3813 case VIDEODB_TYPE_INT:
3814 case VIDEODB_TYPE_COUNT:
3815 *(int*)(((char*)&details)+offsets[i].offset) = record->at(i+idxOffset).get_asInt();
3816 break;
3817 case VIDEODB_TYPE_BOOL:
3818 *(bool*)(((char*)&details)+offsets[i].offset) = record->at(i+idxOffset).get_asBool();
3819 break;
3820 case VIDEODB_TYPE_FLOAT:
3821 *(float*)(((char*)&details)+offsets[i].offset) = record->at(i+idxOffset).get_asFloat();
3822 break;
3823 case VIDEODB_TYPE_STRINGARRAY:
3825 std::string value = record->at(i+idxOffset).get_asString();
3826 if (!value.empty())
3827 *(std::vector<std::string>*)(((char*)&details)+offsets[i].offset) = StringUtils::Split(value, CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_videoItemSeparator);
3828 break;
3830 case VIDEODB_TYPE_DATE:
3831 ((CDateTime*)(((char*)&details)+offsets[i].offset))->SetFromDBDate(record->at(i+idxOffset).get_asString());
3832 break;
3833 case VIDEODB_TYPE_DATETIME:
3834 ((CDateTime*)(((char*)&details)+offsets[i].offset))->SetFromDBDateTime(record->at(i+idxOffset).get_asString());
3835 break;
3836 case VIDEODB_TYPE_UNUSED: // Skip the unused field to avoid populating unused data
3837 continue;
3842 bool CVideoDatabase::GetDetailsByTypeAndId(CFileItem& item, VideoDbContentType type, int id)
3844 CVideoInfoTag details;
3845 details.Reset();
3847 switch (type)
3849 case VideoDbContentType::MOVIES:
3850 GetMovieInfo("", details, id);
3851 break;
3852 case VideoDbContentType::TVSHOWS:
3853 GetTvShowInfo("", details, id, &item);
3854 break;
3855 case VideoDbContentType::EPISODES:
3856 GetEpisodeInfo("", details, id);
3857 break;
3858 case VideoDbContentType::MUSICVIDEOS:
3859 GetMusicVideoInfo("", details, id);
3860 break;
3861 default:
3862 return false;
3865 item.SetFromVideoInfoTag(details);
3866 return true;
3869 CVideoInfoTag CVideoDatabase::GetDetailsByTypeAndId(VideoDbContentType type, int id)
3871 CFileItem item;
3872 if (GetDetailsByTypeAndId(item, type, id))
3873 return CVideoInfoTag(*item.GetVideoInfoTag());
3875 return {};
3878 bool CVideoDatabase::GetStreamDetails(CFileItem& item)
3880 // Note that this function (possibly) creates VideoInfoTags for items that don't have one yet!
3881 int fileId = -1;
3883 if (item.HasVideoInfoTag())
3884 fileId = item.GetVideoInfoTag()->m_iFileId;
3886 if (fileId < 0)
3887 fileId = GetFileId(item);
3889 if (fileId < 0)
3890 return false;
3892 // Have a file id, get stream details if available (creates tag either way)
3893 item.GetVideoInfoTag()->m_iFileId = fileId;
3894 return GetStreamDetails(*item.GetVideoInfoTag());
3897 bool CVideoDatabase::GetStreamDetails(CVideoInfoTag& tag) const
3899 if (tag.m_iFileId < 0)
3900 return false;
3902 bool retVal = false;
3904 CStreamDetails& details = tag.m_streamDetails;
3905 details.Reset();
3907 std::unique_ptr<Dataset> pDS(m_pDB->CreateDataset());
3910 std::string strSQL = PrepareSQL("SELECT * FROM streamdetails WHERE idFile = %i", tag.m_iFileId);
3911 pDS->query(strSQL);
3913 while (!pDS->eof())
3915 CStreamDetail::StreamType e = (CStreamDetail::StreamType)pDS->fv(1).get_asInt();
3916 switch (e)
3918 case CStreamDetail::VIDEO:
3920 CStreamDetailVideo *p = new CStreamDetailVideo();
3921 p->m_strCodec = pDS->fv(2).get_asString();
3922 p->m_fAspect = pDS->fv(3).get_asFloat();
3923 p->m_iWidth = pDS->fv(4).get_asInt();
3924 p->m_iHeight = pDS->fv(5).get_asInt();
3925 p->m_iDuration = pDS->fv(10).get_asInt();
3926 p->m_strStereoMode = pDS->fv(11).get_asString();
3927 p->m_strLanguage = pDS->fv(12).get_asString();
3928 p->m_strHdrType = pDS->fv(13).get_asString();
3929 details.AddStream(p);
3930 retVal = true;
3931 break;
3933 case CStreamDetail::AUDIO:
3935 CStreamDetailAudio *p = new CStreamDetailAudio();
3936 p->m_strCodec = pDS->fv(6).get_asString();
3937 if (pDS->fv(7).get_isNull())
3938 p->m_iChannels = -1;
3939 else
3940 p->m_iChannels = pDS->fv(7).get_asInt();
3941 p->m_strLanguage = pDS->fv(8).get_asString();
3942 details.AddStream(p);
3943 retVal = true;
3944 break;
3946 case CStreamDetail::SUBTITLE:
3948 CStreamDetailSubtitle *p = new CStreamDetailSubtitle();
3949 p->m_strLanguage = pDS->fv(9).get_asString();
3950 details.AddStream(p);
3951 retVal = true;
3952 break;
3956 pDS->next();
3959 pDS->close();
3961 catch (...)
3963 CLog::Log(LOGERROR, "{}({}) failed", __FUNCTION__, tag.m_iFileId);
3965 details.DetermineBestStreams();
3967 if (details.GetVideoDuration() > 0)
3968 tag.SetDuration(details.GetVideoDuration());
3970 return retVal;
3973 bool CVideoDatabase::GetResumePoint(CVideoInfoTag& tag)
3975 if (tag.m_iFileId < 0)
3976 return false;
3978 bool match = false;
3982 if (URIUtils::IsStack(tag.m_strFileNameAndPath) && CFileItem(CStackDirectory::GetFirstStackedFile(tag.m_strFileNameAndPath),false).IsDiscImage())
3984 CStackDirectory dir;
3985 CFileItemList fileList;
3986 const CURL pathToUrl(tag.m_strFileNameAndPath);
3987 dir.GetDirectory(pathToUrl, fileList);
3988 tag.SetResumePoint(CBookmark());
3989 for (int i = fileList.Size() - 1; i >= 0; i--)
3991 CBookmark bookmark;
3992 if (GetResumeBookMark(fileList[i]->GetPath(), bookmark))
3994 bookmark.partNumber = (i+1); /* store part number in here */
3995 tag.SetResumePoint(bookmark);
3996 match = true;
3997 break;
4001 else
4003 std::string strSQL=PrepareSQL("select timeInSeconds, totalTimeInSeconds from bookmark where idFile=%i and type=%i order by timeInSeconds", tag.m_iFileId, CBookmark::RESUME);
4004 m_pDS2->query( strSQL );
4005 if (!m_pDS2->eof())
4007 tag.SetResumePoint(m_pDS2->fv(0).get_asDouble(), m_pDS2->fv(1).get_asDouble(), "");
4008 match = true;
4010 m_pDS2->close();
4013 catch (...)
4015 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, tag.m_strFileNameAndPath);
4018 return match;
4021 CVideoInfoTag CVideoDatabase::GetDetailsForMovie(std::unique_ptr<Dataset> &pDS, int getDetails /* = VideoDbDetailsNone */)
4023 return GetDetailsForMovie(pDS->get_sql_record(), getDetails);
4026 CVideoInfoTag CVideoDatabase::GetDetailsForMovie(const dbiplus::sql_record* const record, int getDetails /* = VideoDbDetailsNone */)
4028 CVideoInfoTag details;
4030 if (record == NULL)
4031 return details;
4033 int idMovie = record->at(0).get_asInt();
4035 GetDetailsFromDB(record, VIDEODB_ID_MIN, VIDEODB_ID_MAX, DbMovieOffsets, details);
4037 details.m_iDbId = idMovie;
4038 details.m_type = MediaTypeMovie;
4040 details.m_set.id = record->at(VIDEODB_DETAILS_MOVIE_SET_ID).get_asInt();
4041 details.m_set.title = record->at(VIDEODB_DETAILS_MOVIE_SET_NAME).get_asString();
4042 details.m_set.overview = record->at(VIDEODB_DETAILS_MOVIE_SET_OVERVIEW).get_asString();
4043 details.m_iFileId = record->at(VIDEODB_DETAILS_FILEID).get_asInt();
4044 details.m_strPath = record->at(VIDEODB_DETAILS_MOVIE_PATH).get_asString();
4045 std::string strFileName = record->at(VIDEODB_DETAILS_MOVIE_FILE).get_asString();
4046 ConstructPath(details.m_strFileNameAndPath,details.m_strPath,strFileName);
4047 details.SetPlayCount(record->at(VIDEODB_DETAILS_MOVIE_PLAYCOUNT).get_asInt());
4048 details.m_lastPlayed.SetFromDBDateTime(record->at(VIDEODB_DETAILS_MOVIE_LASTPLAYED).get_asString());
4049 details.m_dateAdded.SetFromDBDateTime(record->at(VIDEODB_DETAILS_MOVIE_DATEADDED).get_asString());
4050 details.SetResumePoint(record->at(VIDEODB_DETAILS_MOVIE_RESUME_TIME).get_asInt(),
4051 record->at(VIDEODB_DETAILS_MOVIE_TOTAL_TIME).get_asInt(),
4052 record->at(VIDEODB_DETAILS_MOVIE_PLAYER_STATE).get_asString());
4053 details.m_iUserRating = record->at(VIDEODB_DETAILS_MOVIE_USER_RATING).get_asInt();
4054 details.SetRating(record->at(VIDEODB_DETAILS_MOVIE_RATING).get_asFloat(),
4055 record->at(VIDEODB_DETAILS_MOVIE_VOTES).get_asInt(),
4056 record->at(VIDEODB_DETAILS_MOVIE_RATING_TYPE).get_asString(), true);
4057 details.SetUniqueID(record->at(VIDEODB_DETAILS_MOVIE_UNIQUEID_VALUE).get_asString(), record->at(VIDEODB_DETAILS_MOVIE_UNIQUEID_TYPE).get_asString() ,true);
4058 std::string premieredString = record->at(VIDEODB_DETAILS_MOVIE_PREMIERED).get_asString();
4059 if (premieredString.size() == 4)
4060 details.SetYear(record->at(VIDEODB_DETAILS_MOVIE_PREMIERED).get_asInt());
4061 else
4062 details.SetPremieredFromDBDate(premieredString);
4064 if (getDetails)
4066 GetCast(details.m_iDbId, MediaTypeMovie, details.m_cast);
4068 if (getDetails & VideoDbDetailsTag)
4069 GetTags(details.m_iDbId, MediaTypeMovie, details.m_tags);
4071 if (getDetails & VideoDbDetailsRating)
4072 GetRatings(details.m_iDbId, MediaTypeMovie, details.m_ratings);
4074 if (getDetails & VideoDbDetailsUniqueID)
4075 GetUniqueIDs(details.m_iDbId, MediaTypeMovie, details);
4077 if (getDetails & VideoDbDetailsShowLink)
4079 // create tvshowlink string
4080 std::vector<int> links;
4081 GetLinksToTvShow(idMovie, links);
4082 for (unsigned int i = 0; i < links.size(); ++i)
4084 std::string strSQL = PrepareSQL("select c%02d from tvshow where idShow=%i",
4085 VIDEODB_ID_TV_TITLE, links[i]);
4086 m_pDS2->query(strSQL);
4087 if (!m_pDS2->eof())
4088 details.m_showLink.emplace_back(m_pDS2->fv(0).get_asString());
4090 m_pDS2->close();
4093 if (getDetails & VideoDbDetailsStream)
4094 GetStreamDetails(details);
4096 details.m_parsedDetails = getDetails;
4098 return details;
4101 CVideoInfoTag CVideoDatabase::GetDetailsForTvShow(std::unique_ptr<Dataset> &pDS, int getDetails /* = VideoDbDetailsNone */, CFileItem* item /* = NULL */)
4103 return GetDetailsForTvShow(pDS->get_sql_record(), getDetails, item);
4106 CVideoInfoTag CVideoDatabase::GetDetailsForTvShow(const dbiplus::sql_record* const record, int getDetails /* = VideoDbDetailsNone */, CFileItem* item /* = NULL */)
4108 CVideoInfoTag details;
4110 if (record == NULL)
4111 return details;
4113 int idTvShow = record->at(0).get_asInt();
4115 GetDetailsFromDB(record, VIDEODB_ID_TV_MIN, VIDEODB_ID_TV_MAX, DbTvShowOffsets, details, 1);
4116 details.m_bHasPremiered = details.m_premiered.IsValid();
4117 details.m_iDbId = idTvShow;
4118 details.m_type = MediaTypeTvShow;
4119 details.m_strPath = record->at(VIDEODB_DETAILS_TVSHOW_PATH).get_asString();
4120 details.m_basePath = details.m_strPath;
4121 details.m_parentPathID = record->at(VIDEODB_DETAILS_TVSHOW_PARENTPATHID).get_asInt();
4122 details.m_dateAdded.SetFromDBDateTime(record->at(VIDEODB_DETAILS_TVSHOW_DATEADDED).get_asString());
4123 details.m_lastPlayed.SetFromDBDateTime(record->at(VIDEODB_DETAILS_TVSHOW_LASTPLAYED).get_asString());
4124 details.m_iSeason = record->at(VIDEODB_DETAILS_TVSHOW_NUM_SEASONS).get_asInt();
4125 details.m_iEpisode = record->at(VIDEODB_DETAILS_TVSHOW_NUM_EPISODES).get_asInt();
4126 details.SetPlayCount(record->at(VIDEODB_DETAILS_TVSHOW_NUM_WATCHED).get_asInt());
4127 details.m_strShowTitle = details.m_strTitle;
4128 details.m_iUserRating = record->at(VIDEODB_DETAILS_TVSHOW_USER_RATING).get_asInt();
4129 details.SetRating(record->at(VIDEODB_DETAILS_TVSHOW_RATING).get_asFloat(),
4130 record->at(VIDEODB_DETAILS_TVSHOW_VOTES).get_asInt(),
4131 record->at(VIDEODB_DETAILS_TVSHOW_RATING_TYPE).get_asString(), true);
4132 details.SetUniqueID(record->at(VIDEODB_DETAILS_TVSHOW_UNIQUEID_VALUE).get_asString(), record->at(VIDEODB_DETAILS_TVSHOW_UNIQUEID_TYPE).get_asString(), true);
4133 details.SetDuration(record->at(VIDEODB_DETAILS_TVSHOW_DURATION).get_asInt());
4135 if (getDetails)
4137 if (getDetails & VideoDbDetailsCast)
4139 GetCast(details.m_iDbId, "tvshow", details.m_cast);
4142 if (getDetails & VideoDbDetailsTag)
4143 GetTags(details.m_iDbId, MediaTypeTvShow, details.m_tags);
4145 if (getDetails & VideoDbDetailsRating)
4146 GetRatings(details.m_iDbId, MediaTypeTvShow, details.m_ratings);
4148 if (getDetails & VideoDbDetailsUniqueID)
4149 GetUniqueIDs(details.m_iDbId, MediaTypeTvShow, details);
4151 details.m_parsedDetails = getDetails;
4154 if (item != NULL)
4156 item->m_dateTime = details.GetPremiered();
4157 item->SetProperty("totalseasons", details.m_iSeason);
4158 item->SetProperty("totalepisodes", details.m_iEpisode);
4159 item->SetProperty("numepisodes", details.m_iEpisode); // will be changed later to reflect watchmode setting
4160 item->SetProperty("watchedepisodes", details.GetPlayCount());
4161 item->SetProperty("unwatchedepisodes", details.m_iEpisode - details.GetPlayCount());
4162 item->SetProperty("watchedepisodepercent",
4163 details.m_iEpisode > 0 ? (details.GetPlayCount() * 100 / details.m_iEpisode)
4164 : 0);
4166 details.SetPlayCount((details.m_iEpisode <= details.GetPlayCount()) ? 1 : 0);
4168 return details;
4171 CVideoInfoTag CVideoDatabase::GetBasicDetailsForEpisode(std::unique_ptr<Dataset> &pDS)
4173 return GetBasicDetailsForEpisode(pDS->get_sql_record());
4176 CVideoInfoTag CVideoDatabase::GetBasicDetailsForEpisode(const dbiplus::sql_record* const record)
4178 CVideoInfoTag details;
4180 if (record == nullptr)
4181 return details;
4183 int idEpisode = record->at(0).get_asInt();
4185 GetDetailsFromDB(record, VIDEODB_ID_EPISODE_MIN, VIDEODB_ID_EPISODE_MAX, DbEpisodeOffsets, details);
4186 details.m_iDbId = idEpisode;
4187 details.m_type = MediaTypeEpisode;
4188 details.m_iFileId = record->at(VIDEODB_DETAILS_FILEID).get_asInt();
4189 details.m_iIdShow = record->at(VIDEODB_DETAILS_EPISODE_TVSHOW_ID).get_asInt();
4190 details.m_iIdSeason = record->at(VIDEODB_DETAILS_EPISODE_SEASON_ID).get_asInt();
4191 details.m_iUserRating = record->at(VIDEODB_DETAILS_EPISODE_USER_RATING).get_asInt();
4193 return details;
4196 CVideoInfoTag CVideoDatabase::GetDetailsForEpisode(std::unique_ptr<Dataset> &pDS, int getDetails /* = VideoDbDetailsNone */)
4198 return GetDetailsForEpisode(pDS->get_sql_record(), getDetails);
4201 CVideoInfoTag CVideoDatabase::GetDetailsForEpisode(const dbiplus::sql_record* const record, int getDetails /* = VideoDbDetailsNone */)
4203 CVideoInfoTag details;
4205 if (record == nullptr)
4206 return details;
4208 details = GetBasicDetailsForEpisode(record);
4210 details.m_strPath = record->at(VIDEODB_DETAILS_EPISODE_PATH).get_asString();
4211 std::string strFileName = record->at(VIDEODB_DETAILS_EPISODE_FILE).get_asString();
4212 ConstructPath(details.m_strFileNameAndPath,details.m_strPath,strFileName);
4213 details.SetPlayCount(record->at(VIDEODB_DETAILS_EPISODE_PLAYCOUNT).get_asInt());
4214 details.m_lastPlayed.SetFromDBDateTime(record->at(VIDEODB_DETAILS_EPISODE_LASTPLAYED).get_asString());
4215 details.m_dateAdded.SetFromDBDateTime(record->at(VIDEODB_DETAILS_EPISODE_DATEADDED).get_asString());
4216 details.m_strMPAARating = record->at(VIDEODB_DETAILS_EPISODE_TVSHOW_MPAA).get_asString();
4217 details.m_strShowTitle = record->at(VIDEODB_DETAILS_EPISODE_TVSHOW_NAME).get_asString();
4218 details.m_genre = StringUtils::Split(record->at(VIDEODB_DETAILS_EPISODE_TVSHOW_GENRE).get_asString(), CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_videoItemSeparator);
4219 details.m_studio = StringUtils::Split(record->at(VIDEODB_DETAILS_EPISODE_TVSHOW_STUDIO).get_asString(), CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_videoItemSeparator);
4220 details.SetPremieredFromDBDate(record->at(VIDEODB_DETAILS_EPISODE_TVSHOW_AIRED).get_asString());
4222 details.SetResumePoint(record->at(VIDEODB_DETAILS_EPISODE_RESUME_TIME).get_asInt(),
4223 record->at(VIDEODB_DETAILS_EPISODE_TOTAL_TIME).get_asInt(),
4224 record->at(VIDEODB_DETAILS_EPISODE_PLAYER_STATE).get_asString());
4226 details.SetRating(record->at(VIDEODB_DETAILS_EPISODE_RATING).get_asFloat(),
4227 record->at(VIDEODB_DETAILS_EPISODE_VOTES).get_asInt(),
4228 record->at(VIDEODB_DETAILS_EPISODE_RATING_TYPE).get_asString(), true);
4229 details.SetUniqueID(record->at(VIDEODB_DETAILS_EPISODE_UNIQUEID_VALUE).get_asString(), record->at(VIDEODB_DETAILS_EPISODE_UNIQUEID_TYPE).get_asString(), true);
4231 if (getDetails)
4233 if (getDetails & VideoDbDetailsCast)
4235 GetCast(details.m_iDbId, MediaTypeEpisode, details.m_cast);
4236 GetCast(details.m_iIdShow, MediaTypeTvShow, details.m_cast);
4239 if (getDetails & VideoDbDetailsRating)
4240 GetRatings(details.m_iDbId, MediaTypeEpisode, details.m_ratings);
4242 if (getDetails & VideoDbDetailsUniqueID)
4243 GetUniqueIDs(details.m_iDbId, MediaTypeEpisode, details);
4245 if (getDetails & VideoDbDetailsBookmark)
4246 GetBookMarkForEpisode(details, details.m_EpBookmark);
4248 if (getDetails & VideoDbDetailsStream)
4249 GetStreamDetails(details);
4251 details.m_parsedDetails = getDetails;
4253 return details;
4256 CVideoInfoTag CVideoDatabase::GetDetailsForMusicVideo(std::unique_ptr<Dataset> &pDS, int getDetails /* = VideoDbDetailsNone */)
4258 return GetDetailsForMusicVideo(pDS->get_sql_record(), getDetails);
4261 CVideoInfoTag CVideoDatabase::GetDetailsForMusicVideo(const dbiplus::sql_record* const record, int getDetails /* = VideoDbDetailsNone */)
4263 CVideoInfoTag details;
4264 CArtist artist;
4266 if (record == nullptr)
4267 return details;
4269 int idMVideo = record->at(0).get_asInt();
4271 GetDetailsFromDB(record, VIDEODB_ID_MUSICVIDEO_MIN, VIDEODB_ID_MUSICVIDEO_MAX, DbMusicVideoOffsets, details);
4272 details.m_iDbId = idMVideo;
4273 details.m_type = MediaTypeMusicVideo;
4275 details.m_iFileId = record->at(VIDEODB_DETAILS_FILEID).get_asInt();
4276 details.m_strPath = record->at(VIDEODB_DETAILS_MUSICVIDEO_PATH).get_asString();
4277 std::string strFileName = record->at(VIDEODB_DETAILS_MUSICVIDEO_FILE).get_asString();
4278 ConstructPath(details.m_strFileNameAndPath,details.m_strPath,strFileName);
4279 details.SetPlayCount(record->at(VIDEODB_DETAILS_MUSICVIDEO_PLAYCOUNT).get_asInt());
4280 details.m_lastPlayed.SetFromDBDateTime(record->at(VIDEODB_DETAILS_MUSICVIDEO_LASTPLAYED).get_asString());
4281 details.m_dateAdded.SetFromDBDateTime(record->at(VIDEODB_DETAILS_MUSICVIDEO_DATEADDED).get_asString());
4282 details.SetResumePoint(record->at(VIDEODB_DETAILS_MUSICVIDEO_RESUME_TIME).get_asInt(),
4283 record->at(VIDEODB_DETAILS_MUSICVIDEO_TOTAL_TIME).get_asInt(),
4284 record->at(VIDEODB_DETAILS_MUSICVIDEO_PLAYER_STATE).get_asString());
4285 details.m_iUserRating = record->at(VIDEODB_DETAILS_MUSICVIDEO_USER_RATING).get_asInt();
4286 details.SetUniqueID(record->at(VIDEODB_DETAILS_MUSICVIDEO_UNIQUEID_VALUE).get_asString(),
4287 record->at(VIDEODB_DETAILS_MUSICVIDEO_UNIQUEID_TYPE).get_asString(), true);
4288 std::string premieredString = record->at(VIDEODB_DETAILS_MUSICVIDEO_PREMIERED).get_asString();
4289 if (premieredString.size() == 4)
4290 details.SetYear(record->at(VIDEODB_DETAILS_MUSICVIDEO_PREMIERED).get_asInt());
4291 else
4292 details.SetPremieredFromDBDate(premieredString);
4294 if (getDetails)
4296 if (getDetails & VideoDbDetailsTag)
4297 GetTags(details.m_iDbId, MediaTypeMusicVideo, details.m_tags);
4299 if (getDetails & VideoDbDetailsUniqueID)
4300 GetUniqueIDs(details.m_iDbId, MediaTypeMusicVideo, details);
4302 if (getDetails & VideoDbDetailsStream)
4303 GetStreamDetails(details);
4305 if (getDetails & VideoDbDetailsAll)
4307 GetCast(details.m_iDbId, "musicvideo", details.m_cast);
4310 details.m_parsedDetails = getDetails;
4312 return details;
4315 void CVideoDatabase::GetCast(int media_id, const std::string &media_type, std::vector<SActorInfo> &cast)
4319 if (!m_pDB)
4320 return;
4321 if (!m_pDS2)
4322 return;
4324 std::string sql = PrepareSQL("SELECT actor.name,"
4325 " actor_link.role,"
4326 " actor_link.cast_order,"
4327 " actor.art_urls,"
4328 " art.url "
4329 "FROM actor_link"
4330 " JOIN actor ON"
4331 " actor_link.actor_id=actor.actor_id"
4332 " LEFT JOIN art ON"
4333 " art.media_id=actor.actor_id AND art.media_type='actor' AND art.type='thumb' "
4334 "WHERE actor_link.media_id=%i AND actor_link.media_type='%s'"
4335 "ORDER BY actor_link.cast_order", media_id, media_type.c_str());
4336 m_pDS2->query(sql);
4337 while (!m_pDS2->eof())
4339 SActorInfo info;
4340 info.strName = m_pDS2->fv(0).get_asString();
4341 info.strRole = m_pDS2->fv(1).get_asString();
4343 // ignore identical actors (since cast might already be prefilled)
4344 if (std::none_of(cast.begin(), cast.end(), [info](const SActorInfo& actor) {
4345 return actor.strName == info.strName && actor.strRole == info.strRole;
4348 info.order = m_pDS2->fv(2).get_asInt();
4349 info.thumbUrl.ParseFromData(m_pDS2->fv(3).get_asString());
4350 info.thumb = m_pDS2->fv(4).get_asString();
4351 cast.emplace_back(std::move(info));
4354 m_pDS2->next();
4356 m_pDS2->close();
4358 catch (...)
4360 CLog::Log(LOGERROR, "{}({},{}) failed", __FUNCTION__, media_id, media_type);
4364 void CVideoDatabase::GetTags(int media_id, const std::string &media_type, std::vector<std::string> &tags)
4368 if (!m_pDB)
4369 return;
4370 if (!m_pDS2)
4371 return;
4373 std::string sql = PrepareSQL("SELECT tag.name FROM tag INNER JOIN tag_link ON tag_link.tag_id = tag.tag_id WHERE tag_link.media_id = %i AND tag_link.media_type = '%s' ORDER BY tag.tag_id", media_id, media_type.c_str());
4374 m_pDS2->query(sql);
4375 while (!m_pDS2->eof())
4377 tags.emplace_back(m_pDS2->fv(0).get_asString());
4378 m_pDS2->next();
4380 m_pDS2->close();
4382 catch (...)
4384 CLog::Log(LOGERROR, "{}({},{}) failed", __FUNCTION__, media_id, media_type);
4388 void CVideoDatabase::GetRatings(int media_id, const std::string &media_type, RatingMap &ratings)
4392 if (!m_pDB)
4393 return;
4394 if (!m_pDS2)
4395 return;
4397 std::string sql = PrepareSQL("SELECT rating.rating_type, rating.rating, rating.votes FROM rating WHERE rating.media_id = %i AND rating.media_type = '%s'", media_id, media_type.c_str());
4398 m_pDS2->query(sql);
4399 while (!m_pDS2->eof())
4401 ratings[m_pDS2->fv(0).get_asString()] = CRating(m_pDS2->fv(1).get_asFloat(), m_pDS2->fv(2).get_asInt());
4402 m_pDS2->next();
4404 m_pDS2->close();
4406 catch (...)
4408 CLog::Log(LOGERROR, "{}({},{}) failed", __FUNCTION__, media_id, media_type);
4412 void CVideoDatabase::GetUniqueIDs(int media_id, const std::string &media_type, CVideoInfoTag& details)
4416 if (!m_pDB)
4417 return;
4418 if (!m_pDS2)
4419 return;
4421 std::string sql = PrepareSQL("SELECT type, value FROM uniqueid WHERE media_id = %i AND media_type = '%s'", media_id, media_type.c_str());
4422 m_pDS2->query(sql);
4423 while (!m_pDS2->eof())
4425 details.SetUniqueID(m_pDS2->fv(1).get_asString(), m_pDS2->fv(0).get_asString());
4426 m_pDS2->next();
4428 m_pDS2->close();
4430 catch (...)
4432 CLog::Log(LOGERROR, "{}({},{}) failed", __FUNCTION__, media_id, media_type);
4436 bool CVideoDatabase::GetVideoSettings(const CFileItem &item, CVideoSettings &settings)
4438 return GetVideoSettings(GetFileId(item), settings);
4441 /// \brief GetVideoSettings() obtains any saved video settings for the current file.
4442 /// \retval Returns true if the settings exist, false otherwise.
4443 bool CVideoDatabase::GetVideoSettings(const std::string &filePath, CVideoSettings &settings)
4445 return GetVideoSettings(GetFileId(filePath), settings);
4448 bool CVideoDatabase::GetVideoSettings(int idFile, CVideoSettings &settings)
4452 if (idFile < 0) return false;
4453 if (nullptr == m_pDB)
4454 return false;
4455 if (nullptr == m_pDS)
4456 return false;
4458 std::string strSQL=PrepareSQL("select * from settings where settings.idFile = '%i'", idFile);
4459 m_pDS->query( strSQL );
4461 if (m_pDS->num_rows() > 0)
4462 { // get the video settings info
4463 settings.m_AudioDelay = m_pDS->fv("AudioDelay").get_asFloat();
4464 settings.m_AudioStream = m_pDS->fv("AudioStream").get_asInt();
4465 settings.m_Brightness = m_pDS->fv("Brightness").get_asFloat();
4466 settings.m_Contrast = m_pDS->fv("Contrast").get_asFloat();
4467 settings.m_CustomPixelRatio = m_pDS->fv("PixelRatio").get_asFloat();
4468 settings.m_CustomNonLinStretch = m_pDS->fv("NonLinStretch").get_asBool();
4469 settings.m_NoiseReduction = m_pDS->fv("NoiseReduction").get_asFloat();
4470 settings.m_PostProcess = m_pDS->fv("PostProcess").get_asBool();
4471 settings.m_Sharpness = m_pDS->fv("Sharpness").get_asFloat();
4472 settings.m_CustomZoomAmount = m_pDS->fv("ZoomAmount").get_asFloat();
4473 settings.m_CustomVerticalShift = m_pDS->fv("VerticalShift").get_asFloat();
4474 settings.m_Gamma = m_pDS->fv("Gamma").get_asFloat();
4475 settings.m_SubtitleDelay = m_pDS->fv("SubtitleDelay").get_asFloat();
4476 settings.m_SubtitleOn = m_pDS->fv("SubtitlesOn").get_asBool();
4477 settings.m_SubtitleStream = m_pDS->fv("SubtitleStream").get_asInt();
4478 settings.m_ViewMode = m_pDS->fv("ViewMode").get_asInt();
4479 settings.m_ResumeTime = m_pDS->fv("ResumeTime").get_asInt();
4480 settings.m_InterlaceMethod = (EINTERLACEMETHOD)m_pDS->fv("Deinterlace").get_asInt();
4481 settings.m_VolumeAmplification = m_pDS->fv("VolumeAmplification").get_asFloat();
4482 settings.m_ScalingMethod = (ESCALINGMETHOD)m_pDS->fv("ScalingMethod").get_asInt();
4483 settings.m_StereoMode = m_pDS->fv("StereoMode").get_asInt();
4484 settings.m_StereoInvert = m_pDS->fv("StereoInvert").get_asBool();
4485 settings.m_VideoStream = m_pDS->fv("VideoStream").get_asInt();
4486 settings.m_ToneMapMethod =
4487 static_cast<ETONEMAPMETHOD>(m_pDS->fv("TonemapMethod").get_asInt());
4488 settings.m_ToneMapParam = m_pDS->fv("TonemapParam").get_asFloat();
4489 settings.m_Orientation = m_pDS->fv("Orientation").get_asInt();
4490 settings.m_CenterMixLevel = m_pDS->fv("CenterMixLevel").get_asInt();
4491 m_pDS->close();
4492 return true;
4494 m_pDS->close();
4496 catch (...)
4498 CLog::Log(LOGERROR, "{} failed", __FUNCTION__);
4500 return false;
4503 void CVideoDatabase::SetVideoSettings(const CFileItem &item, const CVideoSettings &settings)
4505 int idFile = AddFile(item);
4506 SetVideoSettings(idFile, settings);
4509 /// \brief Sets the settings for a particular video file
4510 void CVideoDatabase::SetVideoSettings(int idFile, const CVideoSettings &setting)
4514 if (nullptr == m_pDB)
4515 return;
4516 if (nullptr == m_pDS)
4517 return;
4518 if (idFile < 0)
4519 return;
4520 std::string strSQL = PrepareSQL("select * from settings where idFile=%i", idFile);
4521 m_pDS->query( strSQL );
4522 if (m_pDS->num_rows() > 0)
4524 m_pDS->close();
4525 // update the item
4526 strSQL = PrepareSQL(
4527 "update settings set "
4528 "Deinterlace=%i,ViewMode=%i,ZoomAmount=%f,PixelRatio=%f,VerticalShift=%f,"
4529 "AudioStream=%i,SubtitleStream=%i,SubtitleDelay=%f,SubtitlesOn=%i,Brightness=%f,Contrast="
4530 "%f,Gamma=%f,"
4531 "VolumeAmplification=%f,AudioDelay=%f,Sharpness=%f,NoiseReduction=%f,NonLinStretch=%i,"
4532 "PostProcess=%i,ScalingMethod=%i,",
4533 setting.m_InterlaceMethod, setting.m_ViewMode,
4534 static_cast<double>(setting.m_CustomZoomAmount),
4535 static_cast<double>(setting.m_CustomPixelRatio),
4536 static_cast<double>(setting.m_CustomVerticalShift), setting.m_AudioStream,
4537 setting.m_SubtitleStream, static_cast<double>(setting.m_SubtitleDelay),
4538 setting.m_SubtitleOn, static_cast<double>(setting.m_Brightness),
4539 static_cast<double>(setting.m_Contrast), static_cast<double>(setting.m_Gamma),
4540 static_cast<double>(setting.m_VolumeAmplification),
4541 static_cast<double>(setting.m_AudioDelay), static_cast<double>(setting.m_Sharpness),
4542 static_cast<double>(setting.m_NoiseReduction), setting.m_CustomNonLinStretch,
4543 setting.m_PostProcess, setting.m_ScalingMethod);
4544 std::string strSQL2;
4546 strSQL2 = PrepareSQL("ResumeTime=%i,StereoMode=%i,StereoInvert=%i,VideoStream=%i,"
4547 "TonemapMethod=%i,TonemapParam=%f where idFile=%i\n",
4548 setting.m_ResumeTime, setting.m_StereoMode, setting.m_StereoInvert,
4549 setting.m_VideoStream, setting.m_ToneMapMethod,
4550 static_cast<double>(setting.m_ToneMapParam), idFile);
4551 strSQL += strSQL2;
4552 m_pDS->exec(strSQL);
4553 return ;
4555 else
4556 { // add the items
4557 m_pDS->close();
4558 strSQL= "INSERT INTO settings (idFile,Deinterlace,ViewMode,ZoomAmount,PixelRatio, VerticalShift, "
4559 "AudioStream,SubtitleStream,SubtitleDelay,SubtitlesOn,Brightness,"
4560 "Contrast,Gamma,VolumeAmplification,AudioDelay,"
4561 "ResumeTime,"
4562 "Sharpness,NoiseReduction,NonLinStretch,PostProcess,ScalingMethod,StereoMode,StereoInvert,VideoStream,TonemapMethod,TonemapParam,Orientation,CenterMixLevel) "
4563 "VALUES ";
4564 strSQL += PrepareSQL(
4565 "(%i,%i,%i,%f,%f,%f,%i,%i,%f,%i,%f,%f,%f,%f,%f,%i,%f,%f,%i,%i,%i,%i,%i,%i,%i,%f,%i,%i)",
4566 idFile, setting.m_InterlaceMethod, setting.m_ViewMode,
4567 static_cast<double>(setting.m_CustomZoomAmount),
4568 static_cast<double>(setting.m_CustomPixelRatio),
4569 static_cast<double>(setting.m_CustomVerticalShift), setting.m_AudioStream,
4570 setting.m_SubtitleStream, static_cast<double>(setting.m_SubtitleDelay),
4571 setting.m_SubtitleOn, static_cast<double>(setting.m_Brightness),
4572 static_cast<double>(setting.m_Contrast), static_cast<double>(setting.m_Gamma),
4573 static_cast<double>(setting.m_VolumeAmplification),
4574 static_cast<double>(setting.m_AudioDelay), setting.m_ResumeTime,
4575 static_cast<double>(setting.m_Sharpness), static_cast<double>(setting.m_NoiseReduction),
4576 setting.m_CustomNonLinStretch, setting.m_PostProcess, setting.m_ScalingMethod,
4577 setting.m_StereoMode, setting.m_StereoInvert, setting.m_VideoStream,
4578 setting.m_ToneMapMethod, static_cast<double>(setting.m_ToneMapParam),
4579 setting.m_Orientation, setting.m_CenterMixLevel);
4580 m_pDS->exec(strSQL);
4583 catch (...)
4585 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, idFile);
4589 void CVideoDatabase::SetArtForItem(int mediaId, const MediaType &mediaType, const std::map<std::string, std::string> &art)
4591 for (const auto &i : art)
4592 SetArtForItem(mediaId, mediaType, i.first, i.second);
4595 void CVideoDatabase::SetArtForItem(int mediaId, const MediaType &mediaType, const std::string &artType, const std::string &url)
4599 if (nullptr == m_pDB)
4600 return;
4601 if (nullptr == m_pDS)
4602 return;
4604 // don't set <foo>.<bar> art types - these are derivative types from parent items
4605 if (artType.find('.') != std::string::npos)
4606 return;
4608 std::string sql = PrepareSQL("SELECT art_id,url FROM art WHERE media_id=%i AND media_type='%s' AND type='%s'", mediaId, mediaType.c_str(), artType.c_str());
4609 m_pDS->query(sql);
4610 if (!m_pDS->eof())
4611 { // update
4612 int artId = m_pDS->fv(0).get_asInt();
4613 std::string oldUrl = m_pDS->fv(1).get_asString();
4614 m_pDS->close();
4615 if (oldUrl != url)
4617 sql = PrepareSQL("UPDATE art SET url='%s' where art_id=%d", url.c_str(), artId);
4618 m_pDS->exec(sql);
4621 else
4622 { // insert
4623 m_pDS->close();
4624 sql = PrepareSQL("INSERT INTO art(media_id, media_type, type, url) VALUES (%d, '%s', '%s', '%s')", mediaId, mediaType.c_str(), artType.c_str(), url.c_str());
4625 m_pDS->exec(sql);
4628 catch (...)
4630 CLog::Log(LOGERROR, "{}({}, '{}', '{}', '{}') failed", __FUNCTION__, mediaId, mediaType,
4631 artType, url);
4635 bool CVideoDatabase::GetArtForItem(int mediaId, const MediaType &mediaType, std::map<std::string, std::string> &art)
4639 if (nullptr == m_pDB)
4640 return false;
4641 if (nullptr == m_pDS2)
4642 return false; // using dataset 2 as we're likely called in loops on dataset 1
4644 std::string sql = PrepareSQL("SELECT type,url FROM art WHERE media_id=%i AND media_type='%s'", mediaId, mediaType.c_str());
4645 m_pDS2->query(sql);
4646 while (!m_pDS2->eof())
4648 art.insert(make_pair(m_pDS2->fv(0).get_asString(), m_pDS2->fv(1).get_asString()));
4649 m_pDS2->next();
4651 m_pDS2->close();
4652 return !art.empty();
4654 catch (...)
4656 CLog::Log(LOGERROR, "{}({}) failed", __FUNCTION__, mediaId);
4658 return false;
4661 std::string CVideoDatabase::GetArtForItem(int mediaId, const MediaType &mediaType, const std::string &artType)
4663 std::string query = PrepareSQL("SELECT url FROM art WHERE media_id=%i AND media_type='%s' AND type='%s'", mediaId, mediaType.c_str(), artType.c_str());
4664 return GetSingleValue(query, m_pDS2);
4667 bool CVideoDatabase::RemoveArtForItem(int mediaId, const MediaType &mediaType, const std::string &artType)
4669 return ExecuteQuery(PrepareSQL("DELETE FROM art WHERE media_id=%i AND media_type='%s' AND type='%s'", mediaId, mediaType.c_str(), artType.c_str()));
4672 bool CVideoDatabase::RemoveArtForItem(int mediaId, const MediaType &mediaType, const std::set<std::string> &artTypes)
4674 bool result = true;
4675 for (const auto &i : artTypes)
4676 result &= RemoveArtForItem(mediaId, mediaType, i);
4678 return result;
4681 bool CVideoDatabase::HasArtForItem(int mediaId, const MediaType &mediaType)
4685 if (nullptr == m_pDB)
4686 return false;
4687 if (nullptr == m_pDS2)
4688 return false; // using dataset 2 as we're likely called in loops on dataset 1
4690 std::string sql = PrepareSQL("SELECT 1 FROM art WHERE media_id=%i AND media_type='%s' LIMIT 1", mediaId, mediaType.c_str());
4691 m_pDS2->query(sql);
4692 bool result = !m_pDS2->eof();
4693 m_pDS2->close();
4694 return result;
4696 catch (...)
4698 CLog::Log(LOGERROR, "{}({}) failed", __FUNCTION__, mediaId);
4700 return false;
4703 bool CVideoDatabase::GetTvShowSeasons(int showId, std::map<int, int> &seasons)
4707 if (nullptr == m_pDB)
4708 return false;
4709 if (nullptr == m_pDS2)
4710 return false; // using dataset 2 as we're likely called in loops on dataset 1
4712 // get all seasons for this show
4713 std::string sql = PrepareSQL("select idSeason,season from seasons where idShow=%i", showId);
4714 m_pDS2->query(sql);
4716 seasons.clear();
4717 while (!m_pDS2->eof())
4719 seasons.insert(std::make_pair(m_pDS2->fv(1).get_asInt(), m_pDS2->fv(0).get_asInt()));
4720 m_pDS2->next();
4722 m_pDS2->close();
4723 return true;
4725 catch (...)
4727 CLog::Log(LOGERROR, "{}({}) failed", __FUNCTION__, showId);
4729 return false;
4732 bool CVideoDatabase::GetTvShowNamedSeasons(int showId, std::map<int, std::string> &seasons)
4736 if (nullptr == m_pDB)
4737 return false;
4738 if (nullptr == m_pDS2)
4739 return false; // using dataset 2 as we're likely called in loops on dataset 1
4741 // get all named seasons for this show
4742 std::string sql = PrepareSQL("select season, name from seasons where season > 0 and name is not null and name <> '' and idShow = %i", showId);
4743 m_pDS2->query(sql);
4745 seasons.clear();
4746 while (!m_pDS2->eof())
4748 seasons.insert(std::make_pair(m_pDS2->fv(0).get_asInt(), m_pDS2->fv(1).get_asString()));
4749 m_pDS2->next();
4751 m_pDS2->close();
4752 return true;
4754 catch (...)
4756 CLog::Log(LOGERROR, "{}({}) failed", __FUNCTION__, showId);
4758 return false;
4761 bool CVideoDatabase::GetTvShowSeasonArt(int showId, std::map<int, std::map<std::string, std::string> > &seasonArt)
4765 if (nullptr == m_pDB)
4766 return false;
4767 if (nullptr == m_pDS2)
4768 return false; // using dataset 2 as we're likely called in loops on dataset 1
4770 std::map<int, int> seasons;
4771 GetTvShowSeasons(showId, seasons);
4773 for (const auto &i : seasons)
4775 std::map<std::string, std::string> art;
4776 GetArtForItem(i.second, MediaTypeSeason, art);
4777 seasonArt.insert(std::make_pair(i.first,art));
4779 return true;
4781 catch (...)
4783 CLog::Log(LOGERROR, "{}({}) failed", __FUNCTION__, showId);
4785 return false;
4788 bool CVideoDatabase::GetArtTypes(const MediaType &mediaType, std::vector<std::string> &artTypes)
4792 if (nullptr == m_pDB)
4793 return false;
4794 if (nullptr == m_pDS)
4795 return false;
4797 std::string sql = PrepareSQL("SELECT DISTINCT type FROM art WHERE media_type='%s'", mediaType.c_str());
4798 int numRows = RunQuery(sql);
4799 if (numRows <= 0)
4800 return numRows == 0;
4802 while (!m_pDS->eof())
4804 artTypes.emplace_back(m_pDS->fv(0).get_asString());
4805 m_pDS->next();
4807 m_pDS->close();
4808 return true;
4810 catch (...)
4812 CLog::Log(LOGERROR, "{}({}) failed", __FUNCTION__, mediaType);
4814 return false;
4817 namespace
4819 std::vector<std::string> GetBasicItemAvailableArtTypes(int mediaId,
4820 VideoDbContentType dbType,
4821 CVideoDatabase& db)
4823 std::vector<std::string> result;
4824 CVideoInfoTag tag = db.GetDetailsByTypeAndId(dbType, mediaId);
4826 //! @todo artwork: fanart stored separately, doesn't need to be
4827 tag.m_fanart.Unpack();
4828 if (tag.m_fanart.GetNumFanarts() && std::find(result.cbegin(), result.cend(), "fanart") == result.cend())
4829 result.emplace_back("fanart");
4831 // all other images
4832 tag.m_strPictureURL.Parse();
4833 for (const auto& urlEntry : tag.m_strPictureURL.GetUrls())
4835 std::string artType = urlEntry.m_aspect;
4836 if (artType.empty())
4837 artType = tag.m_type == MediaTypeEpisode ? "thumb" : "poster";
4838 if (urlEntry.m_type == CScraperUrl::UrlType::General && // exclude season artwork for TV shows
4839 !StringUtils::StartsWith(artType, "set.") && // exclude movie set artwork for movies
4840 std::find(result.cbegin(), result.cend(), artType) == result.cend())
4842 result.push_back(artType);
4845 return result;
4848 std::vector<std::string> GetSeasonAvailableArtTypes(int mediaId, CVideoDatabase& db)
4850 CVideoInfoTag tag;
4851 db.GetSeasonInfo(mediaId, tag);
4853 std::vector<std::string> result;
4855 CVideoInfoTag sourceShow;
4856 db.GetTvShowInfo("", sourceShow, tag.m_iIdShow);
4857 sourceShow.m_strPictureURL.Parse();
4858 for (const auto& urlEntry : sourceShow.m_strPictureURL.GetUrls())
4860 std::string artType = urlEntry.m_aspect;
4861 if (artType.empty())
4862 artType = "poster";
4863 if (urlEntry.m_type == CScraperUrl::UrlType::Season && urlEntry.m_season == tag.m_iSeason &&
4864 std::find(result.cbegin(), result.cend(), artType) == result.cend())
4866 result.push_back(artType);
4869 return result;
4872 std::vector<std::string> GetMovieSetAvailableArtTypes(int mediaId, CVideoDatabase& db)
4874 std::vector<std::string> result;
4875 CFileItemList items;
4876 std::string baseDir = StringUtils::Format("videodb://movies/sets/{}", mediaId);
4877 if (db.GetMoviesNav(baseDir, items))
4879 for (const auto& item : items)
4881 CVideoInfoTag* pTag = item->GetVideoInfoTag();
4882 pTag->m_strPictureURL.Parse();
4884 for (const auto& urlEntry : pTag->m_strPictureURL.GetUrls())
4886 if (!StringUtils::StartsWith(urlEntry.m_aspect, "set."))
4887 continue;
4889 std::string artType = urlEntry.m_aspect.substr(4);
4890 if (std::find(result.cbegin(), result.cend(), artType) == result.cend())
4891 result.push_back(artType);
4895 return result;
4898 std::vector<CScraperUrl::SUrlEntry> GetBasicItemAvailableArt(int mediaId,
4899 VideoDbContentType dbType,
4900 const std::string& artType,
4901 CVideoDatabase& db)
4903 std::vector<CScraperUrl::SUrlEntry> result;
4904 CVideoInfoTag tag = db.GetDetailsByTypeAndId(dbType, mediaId);
4906 if (artType.empty() || artType == "fanart")
4908 tag.m_fanart.Unpack();
4909 for (unsigned int i = 0; i < tag.m_fanart.GetNumFanarts(); i++)
4911 CScraperUrl::SUrlEntry url(tag.m_fanart.GetImageURL(i));
4912 url.m_preview = tag.m_fanart.GetPreviewURL(i);
4913 url.m_aspect = "fanart";
4914 result.push_back(url);
4917 tag.m_strPictureURL.Parse();
4918 for (auto urlEntry : tag.m_strPictureURL.GetUrls())
4920 if (urlEntry.m_aspect.empty())
4921 urlEntry.m_aspect = tag.m_type == MediaTypeEpisode ? "thumb" : "poster";
4922 if ((urlEntry.m_aspect == artType ||
4923 (artType.empty() && !StringUtils::StartsWith(urlEntry.m_aspect, "set."))) &&
4924 urlEntry.m_type == CScraperUrl::UrlType::General)
4926 result.push_back(urlEntry);
4930 return result;
4933 std::vector<CScraperUrl::SUrlEntry> GetSeasonAvailableArt(
4934 int mediaId, const std::string& artType, CVideoDatabase& db)
4936 CVideoInfoTag tag;
4937 db.GetSeasonInfo(mediaId, tag);
4939 std::vector<CScraperUrl::SUrlEntry> result;
4941 CVideoInfoTag sourceShow;
4942 db.GetTvShowInfo("", sourceShow, tag.m_iIdShow);
4943 sourceShow.m_strPictureURL.Parse();
4944 for (auto urlEntry : sourceShow.m_strPictureURL.GetUrls())
4946 if (urlEntry.m_aspect.empty())
4947 urlEntry.m_aspect = "poster";
4948 if ((artType.empty() || urlEntry.m_aspect == artType) &&
4949 urlEntry.m_type == CScraperUrl::UrlType::Season &&
4950 urlEntry.m_season == tag.m_iSeason)
4952 result.push_back(urlEntry);
4955 return result;
4958 std::vector<CScraperUrl::SUrlEntry> GetMovieSetAvailableArt(
4959 int mediaId, const std::string& artType, CVideoDatabase& db)
4961 std::vector<CScraperUrl::SUrlEntry> result;
4962 CFileItemList items;
4963 std::string baseDir = StringUtils::Format("videodb://movies/sets/{}", mediaId);
4964 std::unordered_set<std::string> addedURLs;
4965 if (db.GetMoviesNav(baseDir, items))
4967 for (const auto& item : items)
4969 CVideoInfoTag* pTag = item->GetVideoInfoTag();
4970 pTag->m_strPictureURL.Parse();
4972 for (auto urlEntry : pTag->m_strPictureURL.GetUrls())
4974 bool isSetArt = !artType.empty() ? urlEntry.m_aspect == "set." + artType :
4975 StringUtils::StartsWith(urlEntry.m_aspect, "set.");
4976 if (isSetArt && addedURLs.insert(urlEntry.m_url).second)
4978 urlEntry.m_aspect = urlEntry.m_aspect.substr(4);
4979 result.push_back(urlEntry);
4984 return result;
4987 VideoDbContentType CovertMediaTypeToContentType(const MediaType& mediaType)
4989 VideoDbContentType dbType{VideoDbContentType::UNKNOWN};
4990 if (mediaType == MediaTypeTvShow)
4991 dbType = VideoDbContentType::TVSHOWS;
4992 else if (mediaType == MediaTypeMovie)
4993 dbType = VideoDbContentType::MOVIES;
4994 else if (mediaType == MediaTypeEpisode)
4995 dbType = VideoDbContentType::EPISODES;
4996 else if (mediaType == MediaTypeMusicVideo)
4997 dbType = VideoDbContentType::MUSICVIDEOS;
4999 return dbType;
5001 } // namespace
5003 std::vector<CScraperUrl::SUrlEntry> CVideoDatabase::GetAvailableArtForItem(
5004 int mediaId, const MediaType& mediaType, const std::string& artType)
5006 VideoDbContentType dbType = CovertMediaTypeToContentType(mediaType);
5008 if (dbType != VideoDbContentType::UNKNOWN)
5009 return GetBasicItemAvailableArt(mediaId, dbType, artType, *this);
5010 if (mediaType == MediaTypeSeason)
5011 return GetSeasonAvailableArt(mediaId, artType, *this);
5012 if (mediaType == MediaTypeVideoCollection)
5013 return GetMovieSetAvailableArt(mediaId, artType, *this);
5014 return {};
5017 std::vector<std::string> CVideoDatabase::GetAvailableArtTypesForItem(int mediaId,
5018 const MediaType& mediaType)
5020 VideoDbContentType dbType = CovertMediaTypeToContentType(mediaType);
5022 if (dbType != VideoDbContentType::UNKNOWN)
5023 return GetBasicItemAvailableArtTypes(mediaId, dbType, *this);
5024 if (mediaType == MediaTypeSeason)
5025 return GetSeasonAvailableArtTypes(mediaId, *this);
5026 if (mediaType == MediaTypeVideoCollection)
5027 return GetMovieSetAvailableArtTypes(mediaId, *this);
5028 return {};
5031 /// \brief GetStackTimes() obtains any saved video times for the stacked file
5032 /// \retval Returns true if the stack times exist, false otherwise.
5033 bool CVideoDatabase::GetStackTimes(const std::string &filePath, std::vector<uint64_t> &times)
5037 // obtain the FileID (if it exists)
5038 int idFile = GetFileId(filePath);
5039 if (idFile < 0) return false;
5040 if (nullptr == m_pDB)
5041 return false;
5042 if (nullptr == m_pDS)
5043 return false;
5044 // ok, now obtain the settings for this file
5045 std::string strSQL=PrepareSQL("select times from stacktimes where idFile=%i\n", idFile);
5046 m_pDS->query( strSQL );
5047 if (m_pDS->num_rows() > 0)
5048 { // get the video settings info
5049 uint64_t timeTotal = 0;
5050 std::vector<std::string> timeString = StringUtils::Split(m_pDS->fv("times").get_asString(), ",");
5051 times.clear();
5052 for (const auto &i : timeString)
5054 uint64_t partTime = static_cast<uint64_t>(atof(i.c_str()) * 1000.0);
5055 times.push_back(partTime); // db stores in secs, convert to msecs
5056 timeTotal += partTime;
5058 m_pDS->close();
5059 return (timeTotal > 0);
5061 m_pDS->close();
5063 catch (...)
5065 CLog::Log(LOGERROR, "{} failed", __FUNCTION__);
5067 return false;
5070 /// \brief Sets the stack times for a particular video file
5071 void CVideoDatabase::SetStackTimes(const std::string& filePath, const std::vector<uint64_t> &times)
5075 if (nullptr == m_pDB)
5076 return;
5077 if (nullptr == m_pDS)
5078 return;
5079 int idFile = AddFile(filePath);
5080 if (idFile < 0)
5081 return;
5083 // delete any existing items
5084 m_pDS->exec( PrepareSQL("delete from stacktimes where idFile=%i", idFile) );
5086 // add the items
5087 std::string timeString = StringUtils::Format("{:.3f}", times[0] / 1000.0f);
5088 for (unsigned int i = 1; i < times.size(); i++)
5089 timeString += StringUtils::Format(",{:.3f}", times[i] / 1000.0f);
5091 m_pDS->exec( PrepareSQL("insert into stacktimes (idFile,times) values (%i,'%s')\n", idFile, timeString.c_str()) );
5093 catch (...)
5095 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, filePath);
5099 void CVideoDatabase::RemoveContentForPath(const std::string& strPath, CGUIDialogProgress *progress /* = NULL */)
5101 if(URIUtils::IsMultiPath(strPath))
5103 std::vector<std::string> paths;
5104 CMultiPathDirectory::GetPaths(strPath, paths);
5106 for(unsigned i=0;i<paths.size();i++)
5107 RemoveContentForPath(paths[i], progress);
5112 if (nullptr == m_pDB)
5113 return;
5114 if (nullptr == m_pDS)
5115 return;
5117 if (progress)
5119 progress->SetHeading(CVariant{700});
5120 progress->SetLine(0, CVariant{""});
5121 progress->SetLine(1, CVariant{313});
5122 progress->SetLine(2, CVariant{330});
5123 progress->SetPercentage(0);
5124 progress->Open();
5125 progress->ShowProgressBar(true);
5127 std::vector<std::pair<int, std::string> > paths;
5128 GetSubPaths(strPath, paths);
5129 int iCurr = 0;
5130 for (const auto &i : paths)
5132 bool bMvidsChecked=false;
5133 if (progress)
5135 progress->SetPercentage((int)((float)(iCurr++)/paths.size()*100.f));
5136 progress->Progress();
5139 const auto tvshowId = GetTvShowId(i.second);
5140 if (tvshowId > 0)
5141 DeleteTvShow(tvshowId);
5142 else
5144 std::string strSQL = PrepareSQL("select files.strFilename from files join movie on movie.idFile=files.idFile where files.idPath=%i", i.first);
5145 m_pDS2->query(strSQL);
5146 if (m_pDS2->eof())
5148 strSQL = PrepareSQL("select files.strFilename from files join musicvideo on musicvideo.idFile=files.idFile where files.idPath=%i", i.first);
5149 m_pDS2->query(strSQL);
5150 bMvidsChecked = true;
5152 while (!m_pDS2->eof())
5154 std::string strMoviePath;
5155 std::string strFileName = m_pDS2->fv("files.strFilename").get_asString();
5156 ConstructPath(strMoviePath, i.second, strFileName);
5157 const auto movieId = GetMovieId(strMoviePath);
5158 if (movieId > 0)
5159 DeleteMovie(movieId);
5160 else
5162 const auto musicvideoId = GetMusicVideoId(strMoviePath);
5163 if (musicvideoId > 0)
5164 DeleteMusicVideo(musicvideoId);
5166 m_pDS2->next();
5167 if (m_pDS2->eof() && !bMvidsChecked)
5169 strSQL =PrepareSQL("select files.strFilename from files join musicvideo on musicvideo.idFile=files.idFile where files.idPath=%i", i.first);
5170 m_pDS2->query(strSQL);
5171 bMvidsChecked = true;
5174 m_pDS2->close();
5175 m_pDS2->exec(PrepareSQL("update path set strContent='', strScraper='', strHash='',strSettings='',useFolderNames=0,scanRecursive=0 where idPath=%i", i.first));
5179 catch (...)
5181 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, strPath);
5183 if (progress)
5184 progress->Close();
5187 void CVideoDatabase::SetScraperForPath(const std::string& filePath, const ScraperPtr& scraper, const VIDEO::SScanSettings& settings)
5189 // if we have a multipath, set scraper for all contained paths
5190 if(URIUtils::IsMultiPath(filePath))
5192 std::vector<std::string> paths;
5193 CMultiPathDirectory::GetPaths(filePath, paths);
5195 for(unsigned i=0;i<paths.size();i++)
5196 SetScraperForPath(paths[i],scraper,settings);
5198 return;
5203 if (nullptr == m_pDB)
5204 return;
5205 if (nullptr == m_pDS)
5206 return;
5208 int idPath = AddPath(filePath);
5209 if (idPath < 0)
5210 return;
5212 // Update
5213 std::string strSQL;
5214 if (settings.exclude)
5215 { //NB See note in ::GetScraperForPath about strContent=='none'
5216 strSQL = PrepareSQL(
5217 "UPDATE path SET strContent='', strScraper='', scanRecursive=0, useFolderNames=0, "
5218 "strSettings='', noUpdate=0, exclude=1, allAudio=%i WHERE idPath=%i",
5219 settings.m_allExtAudio, idPath);
5221 else if(!scraper)
5222 { // catch clearing content, but not excluding
5223 strSQL = PrepareSQL(
5224 "UPDATE path SET strContent='', strScraper='', scanRecursive=0, useFolderNames=0, "
5225 "strSettings='', noUpdate=0, exclude=0, allAudio=%i WHERE idPath=%i",
5226 settings.m_allExtAudio, idPath);
5228 else
5230 std::string content = TranslateContent(scraper->Content());
5231 strSQL = PrepareSQL(
5232 "UPDATE path SET strContent='%s', strScraper='%s', scanRecursive=%i, useFolderNames=%i, "
5233 "strSettings='%s', noUpdate=%i, exclude=0, allAudio=%i WHERE idPath=%i",
5234 content.c_str(), scraper->ID().c_str(), settings.recurse, settings.parent_name,
5235 scraper->GetPathSettings().c_str(), settings.noupdate, settings.m_allExtAudio, idPath);
5237 m_pDS->exec(strSQL);
5239 catch (...)
5241 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, filePath);
5245 bool CVideoDatabase::ScraperInUse(const std::string &scraperID) const
5249 if (nullptr == m_pDB)
5250 return false;
5251 if (nullptr == m_pDS)
5252 return false;
5254 std::string sql = PrepareSQL("select count(1) from path where strScraper='%s'", scraperID.c_str());
5255 if (!m_pDS->query(sql) || m_pDS->num_rows() == 0)
5256 return false;
5257 bool found = m_pDS->fv(0).get_asInt() > 0;
5258 m_pDS->close();
5259 return found;
5261 catch (...)
5263 CLog::Log(LOGERROR, "{}({}) failed", __FUNCTION__, scraperID);
5265 return false;
5268 class CArtItem
5270 public:
5271 CArtItem() { art_id = 0; media_id = 0; };
5272 int art_id;
5273 std::string art_type;
5274 std::string art_url;
5275 int media_id;
5276 std::string media_type;
5279 // used for database update to v83
5280 class CShowItem
5282 public:
5283 bool operator==(const CShowItem &r) const
5285 return (!ident.empty() && ident == r.ident) || (title == r.title && year == r.year);
5287 int id;
5288 int path;
5289 std::string title;
5290 std::string year;
5291 std::string ident;
5294 // used for database update to v84
5295 class CShowLink
5297 public:
5298 int show;
5299 int pathId;
5300 std::string path;
5303 void CVideoDatabase::UpdateTables(int iVersion)
5305 // Important: DO NOT use CREATE TABLE [...] AS SELECT [...] - it does not work on MySQL with GTID consistency enforced
5307 if (iVersion < 76)
5309 m_pDS->exec("ALTER TABLE settings ADD StereoMode integer");
5310 m_pDS->exec("ALTER TABLE settings ADD StereoInvert bool");
5312 if (iVersion < 77)
5313 m_pDS->exec("ALTER TABLE streamdetails ADD strStereoMode text");
5315 if (iVersion < 81)
5316 { // add idParentPath to path table
5317 m_pDS->exec("ALTER TABLE path ADD idParentPath integer");
5318 std::map<std::string, int> paths;
5319 m_pDS->query("select idPath,strPath from path");
5320 while (!m_pDS->eof())
5322 paths.insert(make_pair(m_pDS->fv(1).get_asString(), m_pDS->fv(0).get_asInt()));
5323 m_pDS->next();
5325 m_pDS->close();
5326 // run through these paths figuring out the parent path, and add to the table if found
5327 for (const auto &i : paths)
5329 std::string parent = URIUtils::GetParentPath(i.first);
5330 auto j = paths.find(parent);
5331 if (j != paths.end())
5332 m_pDS->exec(PrepareSQL("UPDATE path SET idParentPath=%i WHERE idPath=%i", j->second, i.second));
5335 if (iVersion < 82)
5337 // drop parent path id and basePath from tvshow table
5338 m_pDS->exec("UPDATE tvshow SET c16=NULL,c17=NULL");
5340 if (iVersion < 83)
5342 // drop duplicates in tvshow table, and update tvshowlinkpath accordingly
5343 std::string sql = PrepareSQL("SELECT tvshow.idShow,idPath,c%02d,c%02d,c%02d FROM tvshow JOIN tvshowlinkpath ON tvshow.idShow = tvshowlinkpath.idShow", VIDEODB_ID_TV_TITLE, VIDEODB_ID_TV_PREMIERED, VIDEODB_ID_TV_IDENT_ID);
5344 m_pDS->query(sql);
5345 std::vector<CShowItem> shows;
5346 while (!m_pDS->eof())
5348 CShowItem show;
5349 show.id = m_pDS->fv(0).get_asInt();
5350 show.path = m_pDS->fv(1).get_asInt();
5351 show.title = m_pDS->fv(2).get_asString();
5352 show.year = m_pDS->fv(3).get_asString();
5353 show.ident = m_pDS->fv(4).get_asString();
5354 shows.emplace_back(std::move(show));
5355 m_pDS->next();
5357 m_pDS->close();
5358 if (!shows.empty())
5360 for (auto i = shows.begin() + 1; i != shows.end(); ++i)
5362 // has this show been found before?
5363 auto j = find(shows.begin(), i, *i);
5364 if (j != i)
5365 { // this is a duplicate
5366 // update the tvshowlinkpath table
5367 m_pDS->exec(PrepareSQL("UPDATE tvshowlinkpath SET idShow = %d WHERE idShow = %d AND idPath = %d", j->id, i->id, i->path));
5368 // update episodes, seasons, movie links
5369 m_pDS->exec(PrepareSQL("UPDATE episode SET idShow = %d WHERE idShow = %d", j->id, i->id));
5370 m_pDS->exec(PrepareSQL("UPDATE seasons SET idShow = %d WHERE idShow = %d", j->id, i->id));
5371 m_pDS->exec(PrepareSQL("UPDATE movielinktvshow SET idShow = %d WHERE idShow = %d", j->id, i->id));
5372 // delete tvshow
5373 m_pDS->exec(PrepareSQL("DELETE FROM genrelinktvshow WHERE idShow=%i", i->id));
5374 m_pDS->exec(PrepareSQL("DELETE FROM actorlinktvshow WHERE idShow=%i", i->id));
5375 m_pDS->exec(PrepareSQL("DELETE FROM directorlinktvshow WHERE idShow=%i", i->id));
5376 m_pDS->exec(PrepareSQL("DELETE FROM studiolinktvshow WHERE idShow=%i", i->id));
5377 m_pDS->exec(PrepareSQL("DELETE FROM tvshow WHERE idShow = %d", i->id));
5380 // cleanup duplicate seasons
5381 m_pDS->exec("DELETE FROM seasons WHERE idSeason NOT IN (SELECT idSeason FROM (SELECT min(idSeason) as idSeason FROM seasons GROUP BY idShow,season) AS sub)");
5384 if (iVersion < 84)
5385 { // replace any multipaths in tvshowlinkpath table
5386 m_pDS->query("SELECT idShow, tvshowlinkpath.idPath, strPath FROM tvshowlinkpath JOIN path ON tvshowlinkpath.idPath=path.idPath WHERE path.strPath LIKE 'multipath://%'");
5387 std::vector<CShowLink> shows;
5388 while (!m_pDS->eof())
5390 CShowLink link;
5391 link.show = m_pDS->fv(0).get_asInt();
5392 link.pathId = m_pDS->fv(1).get_asInt();
5393 link.path = m_pDS->fv(2).get_asString();
5394 shows.emplace_back(std::move(link));
5395 m_pDS->next();
5397 m_pDS->close();
5398 // update these
5399 for (auto i = shows.begin(); i != shows.end(); ++i)
5401 std::vector<std::string> paths;
5402 CMultiPathDirectory::GetPaths(i->path, paths);
5403 for (auto j = paths.begin(); j != paths.end(); ++j)
5405 int idPath = AddPath(*j, URIUtils::GetParentPath(*j));
5406 /* we can't rely on REPLACE INTO here as analytics (indices) aren't online yet */
5407 if (GetSingleValue(PrepareSQL("SELECT 1 FROM tvshowlinkpath WHERE idShow=%i AND idPath=%i", i->show, idPath)).empty())
5408 m_pDS->exec(PrepareSQL("INSERT INTO tvshowlinkpath(idShow, idPath) VALUES(%i,%i)", i->show, idPath));
5410 m_pDS->exec(PrepareSQL("DELETE FROM tvshowlinkpath WHERE idShow=%i AND idPath=%i", i->show, i->pathId));
5413 if (iVersion < 85)
5415 // drop multipaths from the path table - they're not needed for anything at all
5416 m_pDS->exec("DELETE FROM path WHERE strPath LIKE 'multipath://%'");
5418 if (iVersion < 87)
5419 { // due to the tvshow merging above, there could be orphaned season or show art
5420 m_pDS->exec("DELETE from art WHERE media_type='tvshow' AND NOT EXISTS (SELECT 1 FROM tvshow WHERE tvshow.idShow = art.media_id)");
5421 m_pDS->exec("DELETE from art WHERE media_type='season' AND NOT EXISTS (SELECT 1 FROM seasons WHERE seasons.idSeason = art.media_id)");
5423 if (iVersion < 91)
5425 // create actor link table
5426 m_pDS->exec("CREATE TABLE actor_link(actor_id INT, media_id INT, media_type TEXT, role TEXT, cast_order INT)");
5427 m_pDS->exec("INSERT INTO actor_link(actor_id, media_id, media_type, role, cast_order) SELECT DISTINCT idActor, idMovie, 'movie', strRole, iOrder from actorlinkmovie");
5428 m_pDS->exec("INSERT INTO actor_link(actor_id, media_id, media_type, role, cast_order) SELECT DISTINCT idActor, idShow, 'tvshow', strRole, iOrder from actorlinktvshow");
5429 m_pDS->exec("INSERT INTO actor_link(actor_id, media_id, media_type, role, cast_order) SELECT DISTINCT idActor, idEpisode, 'episode', strRole, iOrder from actorlinkepisode");
5430 m_pDS->exec("DROP TABLE IF EXISTS actorlinkmovie");
5431 m_pDS->exec("DROP TABLE IF EXISTS actorlinktvshow");
5432 m_pDS->exec("DROP TABLE IF EXISTS actorlinkepisode");
5433 m_pDS->exec("CREATE TABLE actor(actor_id INTEGER PRIMARY KEY, name TEXT, art_urls TEXT)");
5434 m_pDS->exec("INSERT INTO actor(actor_id, name, art_urls) SELECT idActor,strActor,strThumb FROM actors");
5435 m_pDS->exec("DROP TABLE IF EXISTS actors");
5437 // directors
5438 m_pDS->exec("CREATE TABLE director_link(actor_id INTEGER, media_id INTEGER, media_type TEXT)");
5439 m_pDS->exec("INSERT INTO director_link(actor_id, media_id, media_type) SELECT DISTINCT idDirector, idMovie, 'movie' FROM directorlinkmovie");
5440 m_pDS->exec("INSERT INTO director_link(actor_id, media_id, media_type) SELECT DISTINCT idDirector, idShow, 'tvshow' FROM directorlinktvshow");
5441 m_pDS->exec("INSERT INTO director_link(actor_id, media_id, media_type) SELECT DISTINCT idDirector, idEpisode, 'episode' FROM directorlinkepisode");
5442 m_pDS->exec("INSERT INTO director_link(actor_id, media_id, media_type) SELECT DISTINCT idDirector, idMVideo, 'musicvideo' FROM directorlinkmusicvideo");
5443 m_pDS->exec("DROP TABLE IF EXISTS directorlinkmovie");
5444 m_pDS->exec("DROP TABLE IF EXISTS directorlinktvshow");
5445 m_pDS->exec("DROP TABLE IF EXISTS directorlinkepisode");
5446 m_pDS->exec("DROP TABLE IF EXISTS directorlinkmusicvideo");
5448 // writers
5449 m_pDS->exec("CREATE TABLE writer_link(actor_id INTEGER, media_id INTEGER, media_type TEXT)");
5450 m_pDS->exec("INSERT INTO writer_link(actor_id, media_id, media_type) SELECT DISTINCT idWriter, idMovie, 'movie' FROM writerlinkmovie");
5451 m_pDS->exec("INSERT INTO writer_link(actor_id, media_id, media_type) SELECT DISTINCT idWriter, idEpisode, 'episode' FROM writerlinkepisode");
5452 m_pDS->exec("DROP TABLE IF EXISTS writerlinkmovie");
5453 m_pDS->exec("DROP TABLE IF EXISTS writerlinkepisode");
5455 // music artist
5456 m_pDS->exec("INSERT INTO actor_link(actor_id, media_id, media_type) SELECT DISTINCT idArtist, idMVideo, 'musicvideo' FROM artistlinkmusicvideo");
5457 m_pDS->exec("DROP TABLE IF EXISTS artistlinkmusicvideo");
5459 // studios
5460 m_pDS->exec("CREATE TABLE studio_link(studio_id INTEGER, media_id INTEGER, media_type TEXT)");
5461 m_pDS->exec("INSERT INTO studio_link(studio_id, media_id, media_type) SELECT DISTINCT idStudio, idMovie, 'movie' FROM studiolinkmovie");
5462 m_pDS->exec("INSERT INTO studio_link(studio_id, media_id, media_type) SELECT DISTINCT idStudio, idShow, 'tvshow' FROM studiolinktvshow");
5463 m_pDS->exec("INSERT INTO studio_link(studio_id, media_id, media_type) SELECT DISTINCT idStudio, idMVideo, 'musicvideo' FROM studiolinkmusicvideo");
5464 m_pDS->exec("DROP TABLE IF EXISTS studiolinkmovie");
5465 m_pDS->exec("DROP TABLE IF EXISTS studiolinktvshow");
5466 m_pDS->exec("DROP TABLE IF EXISTS studiolinkmusicvideo");
5467 m_pDS->exec("CREATE TABLE studionew(studio_id INTEGER PRIMARY KEY, name TEXT)");
5468 m_pDS->exec("INSERT INTO studionew(studio_id, name) SELECT idStudio,strStudio FROM studio");
5469 m_pDS->exec("DROP TABLE IF EXISTS studio");
5470 m_pDS->exec("ALTER TABLE studionew RENAME TO studio");
5472 // genres
5473 m_pDS->exec("CREATE TABLE genre_link(genre_id INTEGER, media_id INTEGER, media_type TEXT)");
5474 m_pDS->exec("INSERT INTO genre_link(genre_id, media_id, media_type) SELECT DISTINCT idGenre, idMovie, 'movie' FROM genrelinkmovie");
5475 m_pDS->exec("INSERT INTO genre_link(genre_id, media_id, media_type) SELECT DISTINCT idGenre, idShow, 'tvshow' FROM genrelinktvshow");
5476 m_pDS->exec("INSERT INTO genre_link(genre_id, media_id, media_type) SELECT DISTINCT idGenre, idMVideo, 'musicvideo' FROM genrelinkmusicvideo");
5477 m_pDS->exec("DROP TABLE IF EXISTS genrelinkmovie");
5478 m_pDS->exec("DROP TABLE IF EXISTS genrelinktvshow");
5479 m_pDS->exec("DROP TABLE IF EXISTS genrelinkmusicvideo");
5480 m_pDS->exec("CREATE TABLE genrenew(genre_id INTEGER PRIMARY KEY, name TEXT)");
5481 m_pDS->exec("INSERT INTO genrenew(genre_id, name) SELECT idGenre,strGenre FROM genre");
5482 m_pDS->exec("DROP TABLE IF EXISTS genre");
5483 m_pDS->exec("ALTER TABLE genrenew RENAME TO genre");
5485 // country
5486 m_pDS->exec("CREATE TABLE country_link(country_id INTEGER, media_id INTEGER, media_type TEXT)");
5487 m_pDS->exec("INSERT INTO country_link(country_id, media_id, media_type) SELECT DISTINCT idCountry, idMovie, 'movie' FROM countrylinkmovie");
5488 m_pDS->exec("DROP TABLE IF EXISTS countrylinkmovie");
5489 m_pDS->exec("CREATE TABLE countrynew(country_id INTEGER PRIMARY KEY, name TEXT)");
5490 m_pDS->exec("INSERT INTO countrynew(country_id, name) SELECT idCountry,strCountry FROM country");
5491 m_pDS->exec("DROP TABLE IF EXISTS country");
5492 m_pDS->exec("ALTER TABLE countrynew RENAME TO country");
5494 // tags
5495 m_pDS->exec("CREATE TABLE tag_link(tag_id INTEGER, media_id INTEGER, media_type TEXT)");
5496 m_pDS->exec("INSERT INTO tag_link(tag_id, media_id, media_type) SELECT DISTINCT idTag, idMedia, media_type FROM taglinks");
5497 m_pDS->exec("DROP TABLE IF EXISTS taglinks");
5498 m_pDS->exec("CREATE TABLE tagnew(tag_id INTEGER PRIMARY KEY, name TEXT)");
5499 m_pDS->exec("INSERT INTO tagnew(tag_id, name) SELECT idTag,strTag FROM tag");
5500 m_pDS->exec("DROP TABLE IF EXISTS tag");
5501 m_pDS->exec("ALTER TABLE tagnew RENAME TO tag");
5504 if (iVersion < 93)
5506 // cleanup main tables
5507 std::string valuesSql;
5508 for(int i = 0; i < VIDEODB_MAX_COLUMNS; i++)
5510 valuesSql += StringUtils::Format("c{:02} = TRIM(c{:02})", i, i);
5511 if (i < VIDEODB_MAX_COLUMNS - 1)
5512 valuesSql += ",";
5514 m_pDS->exec("UPDATE episode SET " + valuesSql);
5515 m_pDS->exec("UPDATE movie SET " + valuesSql);
5516 m_pDS->exec("UPDATE musicvideo SET " + valuesSql);
5517 m_pDS->exec("UPDATE tvshow SET " + valuesSql);
5519 // cleanup additional tables
5520 std::map<std::string, std::vector<std::string>> additionalTablesMap = {
5521 {"actor", {"actor_link", "director_link", "writer_link"}},
5522 {"studio", {"studio_link"}},
5523 {"genre", {"genre_link"}},
5524 {"country", {"country_link"}},
5525 {"tag", {"tag_link"}}
5527 for (const auto& additionalTableEntry : additionalTablesMap)
5529 std::string table = additionalTableEntry.first;
5530 std::string tablePk = additionalTableEntry.first + "_id";
5531 std::map<int, std::string> duplicatesMinMap;
5532 std::map<int, std::string> duplicatesMap;
5534 // cleanup name
5535 m_pDS->exec(PrepareSQL("UPDATE %s SET name = TRIM(name)",
5536 table.c_str()));
5538 // shrink name to length 255
5539 m_pDS->exec(PrepareSQL("UPDATE %s SET name = SUBSTR(name, 1, 255) WHERE LENGTH(name) > 255",
5540 table.c_str()));
5542 // fetch main entries
5543 m_pDS->query(PrepareSQL("SELECT MIN(%s), name FROM %s GROUP BY name HAVING COUNT(1) > 1",
5544 tablePk.c_str(), table.c_str()));
5546 while (!m_pDS->eof())
5548 duplicatesMinMap.insert(std::make_pair(m_pDS->fv(0).get_asInt(), m_pDS->fv(1).get_asString()));
5549 m_pDS->next();
5551 m_pDS->close();
5553 // fetch duplicate entries
5554 for (const auto& entry : duplicatesMinMap)
5556 m_pDS->query(PrepareSQL("SELECT %s FROM %s WHERE name = '%s' AND %s <> %i",
5557 tablePk.c_str(), table.c_str(),
5558 entry.second.c_str(), tablePk.c_str(), entry.first));
5560 std::stringstream ids;
5561 while (!m_pDS->eof())
5563 int id = m_pDS->fv(0).get_asInt();
5564 m_pDS->next();
5566 ids << id;
5567 if (!m_pDS->eof())
5568 ids << ",";
5570 m_pDS->close();
5572 duplicatesMap.insert(std::make_pair(entry.first, ids.str()));
5575 // cleanup duplicates in link tables
5576 for (const auto& subTable : additionalTableEntry.second)
5578 // create indexes to speed up things
5579 m_pDS->exec(PrepareSQL("CREATE INDEX ix_%s ON %s (%s)",
5580 subTable.c_str(), subTable.c_str(), tablePk.c_str()));
5582 // migrate every duplicate entry to the main entry
5583 for (const auto& entry : duplicatesMap)
5585 m_pDS->exec(PrepareSQL("UPDATE %s SET %s = %i WHERE %s IN (%s) ",
5586 subTable.c_str(), tablePk.c_str(), entry.first,
5587 tablePk.c_str(), entry.second.c_str()));
5590 // clear all duplicates in the link tables
5591 if (subTable == "actor_link")
5593 // as a distinct won't work because of role and cast_order and a group by kills a
5594 // low powered mysql, we de-dupe it with REPLACE INTO while using the real unique index
5595 m_pDS->exec("CREATE TABLE temp_actor_link(actor_id INT, media_id INT, media_type TEXT, role TEXT, cast_order INT)");
5596 m_pDS->exec("CREATE UNIQUE INDEX ix_temp_actor_link ON temp_actor_link (actor_id, media_type(20), media_id)");
5597 m_pDS->exec("REPLACE INTO temp_actor_link SELECT * FROM actor_link");
5598 m_pDS->exec("DROP INDEX ix_temp_actor_link ON temp_actor_link");
5600 else
5602 m_pDS->exec(PrepareSQL("CREATE TABLE temp_%s AS SELECT DISTINCT * FROM %s",
5603 subTable.c_str(), subTable.c_str()));
5606 m_pDS->exec(PrepareSQL("DROP TABLE IF EXISTS %s",
5607 subTable.c_str()));
5609 m_pDS->exec(PrepareSQL("ALTER TABLE temp_%s RENAME TO %s",
5610 subTable.c_str(), subTable.c_str()));
5613 // delete duplicates in main table
5614 for (const auto& entry : duplicatesMap)
5616 m_pDS->exec(PrepareSQL("DELETE FROM %s WHERE %s IN (%s)",
5617 table.c_str(), tablePk.c_str(), entry.second.c_str()));
5622 if (iVersion < 96)
5624 m_pDS->exec("ALTER TABLE movie ADD userrating integer");
5625 m_pDS->exec("ALTER TABLE episode ADD userrating integer");
5626 m_pDS->exec("ALTER TABLE tvshow ADD userrating integer");
5627 m_pDS->exec("ALTER TABLE musicvideo ADD userrating integer");
5630 if (iVersion < 97)
5631 m_pDS->exec("ALTER TABLE sets ADD strOverview TEXT");
5633 if (iVersion < 98)
5634 m_pDS->exec("ALTER TABLE seasons ADD name text");
5636 if (iVersion < 99)
5638 // Add idSeason to episode table, so we don't have to join via idShow and season in the future
5639 m_pDS->exec("ALTER TABLE episode ADD idSeason integer");
5641 m_pDS->query("SELECT idSeason, idShow, season FROM seasons");
5642 while (!m_pDS->eof())
5644 m_pDS2->exec(PrepareSQL("UPDATE episode "
5645 "SET idSeason = %d "
5646 "WHERE "
5647 "episode.idShow = %d AND "
5648 "episode.c%02d = %d",
5649 m_pDS->fv(0).get_asInt(), m_pDS->fv(1).get_asInt(),
5650 VIDEODB_ID_EPISODE_SEASON, m_pDS->fv(2).get_asInt()));
5652 m_pDS->next();
5655 if (iVersion < 101)
5656 m_pDS->exec("ALTER TABLE seasons ADD userrating INTEGER");
5658 if (iVersion < 102)
5660 m_pDS->exec("CREATE TABLE rating (rating_id INTEGER PRIMARY KEY, media_id INTEGER, media_type TEXT, rating_type TEXT, rating FLOAT, votes INTEGER)");
5662 std::string sql = PrepareSQL("SELECT DISTINCT idMovie, c%02d, c%02d FROM movie", VIDEODB_ID_RATING_ID, VIDEODB_ID_VOTES);
5663 m_pDS->query(sql);
5664 while (!m_pDS->eof())
5666 m_pDS2->exec(PrepareSQL("INSERT INTO rating(media_id, media_type, rating_type, rating, "
5667 "votes) VALUES (%i, 'movie', 'default', %f, %i)",
5668 m_pDS->fv(0).get_asInt(),
5669 strtod(m_pDS->fv(1).get_asString().c_str(), NULL),
5670 StringUtils::ReturnDigits(m_pDS->fv(2).get_asString())));
5671 int idRating = (int)m_pDS2->lastinsertid();
5672 m_pDS2->exec(PrepareSQL("UPDATE movie SET c%02d=%i WHERE idMovie=%i", VIDEODB_ID_RATING_ID, idRating, m_pDS->fv(0).get_asInt()));
5673 m_pDS->next();
5675 m_pDS->close();
5677 sql = PrepareSQL("SELECT DISTINCT idShow, c%02d, c%02d FROM tvshow", VIDEODB_ID_TV_RATING_ID, VIDEODB_ID_TV_VOTES);
5678 m_pDS->query(sql);
5679 while (!m_pDS->eof())
5681 m_pDS2->exec(PrepareSQL("INSERT INTO rating(media_id, media_type, rating_type, rating, "
5682 "votes) VALUES (%i, 'tvshow', 'default', %f, %i)",
5683 m_pDS->fv(0).get_asInt(),
5684 strtod(m_pDS->fv(1).get_asString().c_str(), NULL),
5685 StringUtils::ReturnDigits(m_pDS->fv(2).get_asString())));
5686 int idRating = (int)m_pDS2->lastinsertid();
5687 m_pDS2->exec(PrepareSQL("UPDATE tvshow SET c%02d=%i WHERE idShow=%i", VIDEODB_ID_TV_RATING_ID, idRating, m_pDS->fv(0).get_asInt()));
5688 m_pDS->next();
5690 m_pDS->close();
5692 sql = PrepareSQL("SELECT DISTINCT idEpisode, c%02d, c%02d FROM episode", VIDEODB_ID_EPISODE_RATING_ID, VIDEODB_ID_EPISODE_VOTES);
5693 m_pDS->query(sql);
5694 while (!m_pDS->eof())
5696 m_pDS2->exec(PrepareSQL("INSERT INTO rating(media_id, media_type, rating_type, rating, "
5697 "votes) VALUES (%i, 'episode', 'default', %f, %i)",
5698 m_pDS->fv(0).get_asInt(),
5699 strtod(m_pDS->fv(1).get_asString().c_str(), NULL),
5700 StringUtils::ReturnDigits(m_pDS->fv(2).get_asString())));
5701 int idRating = (int)m_pDS2->lastinsertid();
5702 m_pDS2->exec(PrepareSQL("UPDATE episode SET c%02d=%i WHERE idEpisode=%i", VIDEODB_ID_EPISODE_RATING_ID, idRating, m_pDS->fv(0).get_asInt()));
5703 m_pDS->next();
5705 m_pDS->close();
5708 if (iVersion < 103)
5710 m_pDS->exec("ALTER TABLE settings ADD VideoStream integer");
5711 m_pDS->exec("ALTER TABLE streamdetails ADD strVideoLanguage text");
5714 if (iVersion < 104)
5716 m_pDS->exec("ALTER TABLE tvshow ADD duration INTEGER");
5718 std::string sql = PrepareSQL( "SELECT episode.idShow, MAX(episode.c%02d) "
5719 "FROM episode "
5721 "LEFT JOIN streamdetails "
5722 "ON streamdetails.idFile = episode.idFile "
5723 "AND streamdetails.iStreamType = 0 " // only grab video streams
5725 "WHERE episode.c%02d <> streamdetails.iVideoDuration "
5726 "OR streamdetails.iVideoDuration IS NULL "
5727 "GROUP BY episode.idShow", VIDEODB_ID_EPISODE_RUNTIME, VIDEODB_ID_EPISODE_RUNTIME);
5729 m_pDS->query(sql);
5730 while (!m_pDS->eof())
5732 m_pDS2->exec(PrepareSQL("UPDATE tvshow SET duration=%i WHERE idShow=%i", m_pDS->fv(1).get_asInt(), m_pDS->fv(0).get_asInt()));
5733 m_pDS->next();
5735 m_pDS->close();
5738 if (iVersion < 105)
5740 m_pDS->exec("ALTER TABLE movie ADD premiered TEXT");
5741 m_pDS->exec(PrepareSQL("UPDATE movie SET premiered=c%02d", VIDEODB_ID_YEAR));
5742 m_pDS->exec("ALTER TABLE musicvideo ADD premiered TEXT");
5743 m_pDS->exec(PrepareSQL("UPDATE musicvideo SET premiered=c%02d", VIDEODB_ID_MUSICVIDEO_YEAR));
5746 if (iVersion < 107)
5748 // need this due to the nested GetScraperPath query
5749 std::unique_ptr<Dataset> pDS;
5750 pDS.reset(m_pDB->CreateDataset());
5751 if (nullptr == pDS)
5752 return;
5754 pDS->exec("CREATE TABLE uniqueid (uniqueid_id INTEGER PRIMARY KEY, media_id INTEGER, media_type TEXT, value TEXT, type TEXT)");
5756 for (int i = 0; i < 3; ++i)
5758 std::string mediatype, columnID;
5759 int columnUniqueID;
5760 switch (i)
5762 case (0):
5763 mediatype = "movie";
5764 columnID = "idMovie";
5765 columnUniqueID = VIDEODB_ID_IDENT_ID;
5766 break;
5767 case (1):
5768 mediatype = "tvshow";
5769 columnID = "idShow";
5770 columnUniqueID = VIDEODB_ID_TV_IDENT_ID;
5771 break;
5772 case (2):
5773 mediatype = "episode";
5774 columnID = "idEpisode";
5775 columnUniqueID = VIDEODB_ID_EPISODE_IDENT_ID;
5776 break;
5777 default:
5778 continue;
5780 pDS->query(PrepareSQL("SELECT %s, c%02d FROM %s", columnID.c_str(), columnUniqueID, mediatype.c_str()));
5781 while (!pDS->eof())
5783 std::string uniqueid = pDS->fv(1).get_asString();
5784 if (!uniqueid.empty())
5786 int mediaid = pDS->fv(0).get_asInt();
5787 if (StringUtils::StartsWith(uniqueid, "tt"))
5788 m_pDS2->exec(PrepareSQL("INSERT INTO uniqueid(media_id, media_type, type, value) VALUES (%i, '%s', 'imdb', '%s')", mediaid, mediatype.c_str(), uniqueid.c_str()));
5789 else
5790 m_pDS2->exec(PrepareSQL("INSERT INTO uniqueid(media_id, media_type, type, value) VALUES (%i, '%s', 'unknown', '%s')", mediaid, mediatype.c_str(), uniqueid.c_str()));
5791 m_pDS2->exec(PrepareSQL("UPDATE %s SET c%02d='%i' WHERE %s=%i", mediatype.c_str(), columnUniqueID, (int)m_pDS2->lastinsertid(), columnID.c_str(), mediaid));
5793 pDS->next();
5795 pDS->close();
5799 if (iVersion < 109)
5801 m_pDS->exec("ALTER TABLE settings RENAME TO settingsold");
5802 m_pDS->exec("CREATE TABLE settings ( idFile integer, Deinterlace bool,"
5803 "ViewMode integer,ZoomAmount float, PixelRatio float, VerticalShift float, AudioStream integer, SubtitleStream integer,"
5804 "SubtitleDelay float, SubtitlesOn bool, Brightness float, Contrast float, Gamma float,"
5805 "VolumeAmplification float, AudioDelay float, ResumeTime integer,"
5806 "Sharpness float, NoiseReduction float, NonLinStretch bool, PostProcess bool,"
5807 "ScalingMethod integer, DeinterlaceMode integer, StereoMode integer, StereoInvert bool, VideoStream integer)");
5808 m_pDS->exec("INSERT INTO settings SELECT idFile, Deinterlace, ViewMode, ZoomAmount, PixelRatio, VerticalShift, AudioStream, SubtitleStream, SubtitleDelay, SubtitlesOn, Brightness, Contrast, Gamma, VolumeAmplification, AudioDelay, ResumeTime, Sharpness, NoiseReduction, NonLinStretch, PostProcess, ScalingMethod, DeinterlaceMode, StereoMode, StereoInvert, VideoStream FROM settingsold");
5809 m_pDS->exec("DROP TABLE settingsold");
5812 if (iVersion < 110)
5814 m_pDS->exec("ALTER TABLE settings ADD TonemapMethod integer");
5815 m_pDS->exec("ALTER TABLE settings ADD TonemapParam float");
5818 if (iVersion < 111)
5819 m_pDS->exec("ALTER TABLE settings ADD Orientation integer");
5821 if (iVersion < 112)
5822 m_pDS->exec("ALTER TABLE settings ADD CenterMixLevel integer");
5824 if (iVersion < 113)
5826 // fb9c25f5 and e5f6d204 changed the behavior of path splitting for plugin URIs (previously it would only use the root)
5827 // Re-split paths for plugin files in order to maintain watched state etc.
5828 m_pDS->query("SELECT files.idFile, files.strFilename, path.strPath FROM files LEFT JOIN path ON files.idPath = path.idPath WHERE files.strFilename LIKE 'plugin://%'");
5829 while (!m_pDS->eof())
5831 std::string path, fn;
5832 SplitPath(m_pDS->fv(1).get_asString(), path, fn);
5833 if (path != m_pDS->fv(2).get_asString())
5835 int pathid = -1;
5836 m_pDS2->query(PrepareSQL("SELECT idPath FROM path WHERE strPath='%s'", path.c_str()));
5837 if (!m_pDS2->eof())
5838 pathid = m_pDS2->fv(0).get_asInt();
5839 m_pDS2->close();
5840 if (pathid < 0)
5842 std::string parent = URIUtils::GetParentPath(path);
5843 int parentid = -1;
5844 m_pDS2->query(PrepareSQL("SELECT idPath FROM path WHERE strPath='%s'", parent.c_str()));
5845 if (!m_pDS2->eof())
5846 parentid = m_pDS2->fv(0).get_asInt();
5847 m_pDS2->close();
5848 if (parentid < 0)
5850 m_pDS2->exec(PrepareSQL("INSERT INTO path (strPath) VALUES ('%s')", parent.c_str()));
5851 parentid = (int)m_pDS2->lastinsertid();
5853 m_pDS2->exec(PrepareSQL("INSERT INTO path (strPath, idParentPath) VALUES ('%s', %i)", path.c_str(), parentid));
5854 pathid = (int)m_pDS2->lastinsertid();
5856 m_pDS2->query(PrepareSQL("SELECT idFile FROM files WHERE strFileName='%s' AND idPath=%i", fn.c_str(), pathid));
5857 bool exists = !m_pDS2->eof();
5858 m_pDS2->close();
5859 if (exists)
5860 m_pDS2->exec(PrepareSQL("DELETE FROM files WHERE idFile=%i", m_pDS->fv(0).get_asInt()));
5861 else
5862 m_pDS2->exec(PrepareSQL("UPDATE files SET idPath=%i WHERE idFile=%i", pathid, m_pDS->fv(0).get_asInt()));
5864 m_pDS->next();
5866 m_pDS->close();
5869 if (iVersion < 119)
5870 m_pDS->exec("ALTER TABLE path ADD allAudio bool");
5872 if (iVersion < 120)
5873 m_pDS->exec("ALTER TABLE streamdetails ADD strHdrType text");
5875 if (iVersion < 121)
5877 // https://github.com/xbmc/xbmc/issues/21253 - Kodi picks up wrong "year" for PVR recording.
5879 m_pDS->query("SELECT idFile, strFilename FROM files WHERE strFilename LIKE '% (1969)%.pvr' OR "
5880 "strFilename LIKE '% (1601)%.pvr'");
5881 while (!m_pDS->eof())
5883 std::string fixedFileName = m_pDS->fv(1).get_asString();
5884 size_t pos = fixedFileName.find(" (1969)");
5885 if (pos == std::string::npos)
5886 pos = fixedFileName.find(" (1601)");
5888 if (pos != std::string::npos)
5890 fixedFileName.erase(pos, 7);
5892 m_pDS2->exec(PrepareSQL("UPDATE files SET strFilename='%s' WHERE idFile=%i",
5893 fixedFileName.c_str(), m_pDS->fv(0).get_asInt()));
5895 m_pDS->next();
5897 m_pDS->close();
5901 int CVideoDatabase::GetSchemaVersion() const
5903 return 121;
5906 bool CVideoDatabase::LookupByFolders(const std::string &path, bool shows)
5908 SScanSettings settings;
5909 bool foundDirectly = false;
5910 ScraperPtr scraper = GetScraperForPath(path, settings, foundDirectly);
5911 if (scraper && scraper->Content() == CONTENT_TVSHOWS && !shows)
5912 return false; // episodes
5913 return settings.parent_name_root; // shows, movies, musicvids
5916 bool CVideoDatabase::GetPlayCounts(const std::string &strPath, CFileItemList &items)
5918 if(URIUtils::IsMultiPath(strPath))
5920 std::vector<std::string> paths;
5921 CMultiPathDirectory::GetPaths(strPath, paths);
5923 bool ret = false;
5924 for(unsigned i=0;i<paths.size();i++)
5925 ret |= GetPlayCounts(paths[i], items);
5927 return ret;
5929 int pathID = -1;
5930 if (!URIUtils::IsPlugin(strPath))
5932 pathID = GetPathId(strPath);
5933 if (pathID < 0)
5934 return false; // path (and thus files) aren't in the database
5939 // error!
5940 if (nullptr == m_pDB)
5941 return false;
5942 if (nullptr == m_pDS)
5943 return false;
5945 std::string sql =
5946 "SELECT"
5947 " files.strFilename, files.playCount,"
5948 " bookmark.timeInSeconds, bookmark.totalTimeInSeconds "
5949 "FROM files"
5950 " LEFT JOIN bookmark ON"
5951 " files.idFile = bookmark.idFile AND bookmark.type = %i ";
5953 if (URIUtils::IsPlugin(strPath))
5955 for (auto& item : items)
5957 if (!item || item->m_bIsFolder || !item->GetProperty("IsPlayable").asBoolean())
5958 continue;
5960 std::string path, filename;
5961 SplitPath(item->GetPath(), path, filename);
5962 m_pDS->query(PrepareSQL(sql +
5963 "INNER JOIN path ON files.idPath = path.idPath "
5964 "WHERE files.strFilename='%s' AND path.strPath='%s'",
5965 (int)CBookmark::RESUME, filename.c_str(), path.c_str()));
5967 if (!m_pDS->eof())
5969 if (!item->GetVideoInfoTag()->IsPlayCountSet())
5970 item->GetVideoInfoTag()->SetPlayCount(m_pDS->fv(1).get_asInt());
5971 if (!item->GetVideoInfoTag()->GetResumePoint().IsSet())
5972 item->GetVideoInfoTag()->SetResumePoint(m_pDS->fv(2).get_asInt(), m_pDS->fv(3).get_asInt(), "");
5974 m_pDS->close();
5977 else
5979 //! @todo also test a single query for the above and below
5980 sql = PrepareSQL(sql + "WHERE files.idPath=%i", (int)CBookmark::RESUME, pathID);
5982 if (RunQuery(sql) <= 0)
5983 return false;
5985 items.SetFastLookup(true); // note: it's possibly quicker the other way around (map on db returned items)?
5986 while (!m_pDS->eof())
5988 std::string path;
5989 ConstructPath(path, strPath, m_pDS->fv(0).get_asString());
5990 CFileItemPtr item = items.Get(path);
5991 if (item)
5993 if (!items.IsPlugin() || !item->GetVideoInfoTag()->IsPlayCountSet())
5994 item->GetVideoInfoTag()->SetPlayCount(m_pDS->fv(1).get_asInt());
5996 if (!item->GetVideoInfoTag()->GetResumePoint().IsSet())
5998 item->GetVideoInfoTag()->SetResumePoint(m_pDS->fv(2).get_asInt(), m_pDS->fv(3).get_asInt(), "");
6001 m_pDS->next();
6005 return true;
6007 catch (...)
6009 CLog::Log(LOGERROR, "{} failed", __FUNCTION__);
6011 return false;
6014 int CVideoDatabase::GetPlayCount(int iFileId)
6016 if (iFileId < 0)
6017 return 0; // not in db, so not watched
6021 // error!
6022 if (nullptr == m_pDB)
6023 return -1;
6024 if (nullptr == m_pDS)
6025 return -1;
6027 std::string strSQL = PrepareSQL("select playCount from files WHERE idFile=%i", iFileId);
6028 int count = 0;
6029 if (m_pDS->query(strSQL))
6031 // there should only ever be one row returned
6032 if (m_pDS->num_rows() == 1)
6033 count = m_pDS->fv(0).get_asInt();
6034 m_pDS->close();
6036 return count;
6038 catch (...)
6040 CLog::Log(LOGERROR, "{} failed", __FUNCTION__);
6042 return -1;
6045 int CVideoDatabase::GetPlayCount(const std::string& strFilenameAndPath)
6047 return GetPlayCount(GetFileId(strFilenameAndPath));
6050 int CVideoDatabase::GetPlayCount(const CFileItem &item)
6052 return GetPlayCount(GetFileId(item));
6055 CDateTime CVideoDatabase::GetLastPlayed(int iFileId)
6057 if (iFileId < 0)
6058 return {}; // not in db, so not watched
6062 // error!
6063 if (nullptr == m_pDB)
6064 return {};
6065 if (nullptr == m_pDS)
6066 return {};
6068 std::string strSQL = PrepareSQL("select lastPlayed from files WHERE idFile=%i", iFileId);
6069 CDateTime lastPlayed;
6070 if (m_pDS->query(strSQL))
6072 // there should only ever be one row returned
6073 if (m_pDS->num_rows() == 1)
6074 lastPlayed.SetFromDBDateTime(m_pDS->fv(0).get_asString());
6075 m_pDS->close();
6077 return lastPlayed;
6079 catch (...)
6081 CLog::Log(LOGERROR, "{} failed", __FUNCTION__);
6083 return {};
6086 CDateTime CVideoDatabase::GetLastPlayed(const std::string& strFilenameAndPath)
6088 return GetLastPlayed(GetFileId(strFilenameAndPath));
6091 void CVideoDatabase::UpdateFanart(const CFileItem& item, VideoDbContentType type)
6093 if (nullptr == m_pDB)
6094 return;
6095 if (nullptr == m_pDS)
6096 return;
6097 if (!item.HasVideoInfoTag() || item.GetVideoInfoTag()->m_iDbId < 0) return;
6099 std::string exec;
6100 if (type == VideoDbContentType::TVSHOWS)
6101 exec = PrepareSQL("UPDATE tvshow set c%02d='%s' WHERE idShow=%i", VIDEODB_ID_TV_FANART, item.GetVideoInfoTag()->m_fanart.m_xml.c_str(), item.GetVideoInfoTag()->m_iDbId);
6102 else if (type == VideoDbContentType::MOVIES)
6103 exec = PrepareSQL("UPDATE movie set c%02d='%s' WHERE idMovie=%i", VIDEODB_ID_FANART, item.GetVideoInfoTag()->m_fanart.m_xml.c_str(), item.GetVideoInfoTag()->m_iDbId);
6107 m_pDS->exec(exec);
6109 if (type == VideoDbContentType::TVSHOWS)
6110 AnnounceUpdate(MediaTypeTvShow, item.GetVideoInfoTag()->m_iDbId);
6111 else if (type == VideoDbContentType::MOVIES)
6112 AnnounceUpdate(MediaTypeMovie, item.GetVideoInfoTag()->m_iDbId);
6114 catch (...)
6116 CLog::Log(LOGERROR, "{} - error updating fanart for {}", __FUNCTION__, item.GetPath());
6120 CDateTime CVideoDatabase::SetPlayCount(const CFileItem& item, int count, const CDateTime& date)
6122 int id;
6123 if (item.HasProperty("original_listitem_url") &&
6124 URIUtils::IsPlugin(item.GetProperty("original_listitem_url").asString()))
6126 CFileItem item2(item);
6127 item2.SetPath(item.GetProperty("original_listitem_url").asString());
6128 id = AddFile(item2);
6130 else
6131 id = AddFile(item);
6132 if (id < 0)
6133 return {};
6135 // and mark as watched
6138 const CDateTime lastPlayed(date.IsValid() ? date : CDateTime::GetCurrentDateTime());
6140 if (nullptr == m_pDB)
6141 return {};
6142 if (nullptr == m_pDS)
6143 return {};
6145 std::string strSQL;
6146 if (count)
6148 strSQL = PrepareSQL("update files set playCount=%i,lastPlayed='%s' where idFile=%i", count,
6149 lastPlayed.GetAsDBDateTime().c_str(), id);
6151 else
6153 if (!date.IsValid())
6154 strSQL = PrepareSQL("update files set playCount=NULL,lastPlayed=NULL where idFile=%i", id);
6155 else
6156 strSQL = PrepareSQL("update files set playCount=NULL,lastPlayed='%s' where idFile=%i",
6157 lastPlayed.GetAsDBDateTime().c_str(), id);
6160 m_pDS->exec(strSQL);
6162 // We only need to announce changes to video items in the library
6163 if (item.HasVideoInfoTag() && item.GetVideoInfoTag()->m_iDbId > 0)
6165 CVariant data;
6166 if (CVideoLibraryQueue::GetInstance().IsScanningLibrary())
6167 data["transaction"] = true;
6168 // Only provide the "playcount" value if it has actually changed
6169 if (item.GetVideoInfoTag()->GetPlayCount() != count)
6170 data["playcount"] = count;
6171 CServiceBroker::GetAnnouncementManager()->Announce(ANNOUNCEMENT::VideoLibrary, "OnUpdate",
6172 CFileItemPtr(new CFileItem(item)), data);
6175 return lastPlayed;
6177 catch (...)
6179 CLog::Log(LOGERROR, "{} failed", __FUNCTION__);
6182 return {};
6185 CDateTime CVideoDatabase::IncrementPlayCount(const CFileItem& item)
6187 return SetPlayCount(item, GetPlayCount(item) + 1);
6190 CDateTime CVideoDatabase::UpdateLastPlayed(const CFileItem& item)
6192 return SetPlayCount(item, GetPlayCount(item), CDateTime::GetCurrentDateTime());
6195 void CVideoDatabase::UpdateMovieTitle(int idMovie,
6196 const std::string& strNewMovieTitle,
6197 VideoDbContentType iType)
6201 if (nullptr == m_pDB)
6202 return;
6203 if (nullptr == m_pDS)
6204 return;
6205 std::string content;
6206 if (iType == VideoDbContentType::MOVIES)
6208 CLog::Log(LOGINFO, "Changing Movie:id:{} New Title:{}", idMovie, strNewMovieTitle);
6209 content = MediaTypeMovie;
6211 else if (iType == VideoDbContentType::EPISODES)
6213 CLog::Log(LOGINFO, "Changing Episode:id:{} New Title:{}", idMovie, strNewMovieTitle);
6214 content = MediaTypeEpisode;
6216 else if (iType == VideoDbContentType::TVSHOWS)
6218 CLog::Log(LOGINFO, "Changing TvShow:id:{} New Title:{}", idMovie, strNewMovieTitle);
6219 content = MediaTypeTvShow;
6221 else if (iType == VideoDbContentType::MUSICVIDEOS)
6223 CLog::Log(LOGINFO, "Changing MusicVideo:id:{} New Title:{}", idMovie, strNewMovieTitle);
6224 content = MediaTypeMusicVideo;
6226 else if (iType == VideoDbContentType::MOVIE_SETS)
6228 CLog::Log(LOGINFO, "Changing Movie set:id:{} New Title:{}", idMovie, strNewMovieTitle);
6229 std::string strSQL = PrepareSQL("UPDATE sets SET strSet='%s' WHERE idSet=%i", strNewMovieTitle.c_str(), idMovie );
6230 m_pDS->exec(strSQL);
6233 if (!content.empty())
6235 SetSingleValue(iType, idMovie, FieldTitle, strNewMovieTitle);
6236 AnnounceUpdate(content, idMovie);
6239 catch (...)
6241 CLog::Log(
6242 LOGERROR,
6243 "{} (int idMovie, const std::string& strNewMovieTitle) failed on MovieID:{} and Title:{}",
6244 __FUNCTION__, idMovie, strNewMovieTitle);
6248 bool CVideoDatabase::UpdateVideoSortTitle(int idDb,
6249 const std::string& strNewSortTitle,
6250 VideoDbContentType iType /* = MOVIES */)
6254 if (nullptr == m_pDB || nullptr == m_pDS)
6255 return false;
6256 if (iType != VideoDbContentType::MOVIES && iType != VideoDbContentType::TVSHOWS)
6257 return false;
6259 std::string content = MediaTypeMovie;
6260 if (iType == VideoDbContentType::TVSHOWS)
6261 content = MediaTypeTvShow;
6263 if (SetSingleValue(iType, idDb, FieldSortTitle, strNewSortTitle))
6265 AnnounceUpdate(content, idDb);
6266 return true;
6269 catch (...)
6271 CLog::Log(LOGERROR,
6272 "{} (int idDb, const std::string& strNewSortTitle, VIDEODB_CONTENT_TYPE iType) "
6273 "failed on ID: {} and Sort Title: {}",
6274 __FUNCTION__, idDb, strNewSortTitle);
6277 return false;
6280 /// \brief EraseVideoSettings() Erases the videoSettings table and reconstructs it
6281 void CVideoDatabase::EraseVideoSettings(const CFileItem &item)
6283 int idFile = GetFileId(item);
6284 if (idFile < 0)
6285 return;
6289 std::string sql = PrepareSQL("DELETE FROM settings WHERE idFile=%i", idFile);
6291 CLog::Log(LOGINFO, "Deleting settings information for files {}",
6292 CURL::GetRedacted(item.GetPath()));
6293 m_pDS->exec(sql);
6295 catch (...)
6297 CLog::Log(LOGERROR, "{} failed", __FUNCTION__);
6301 void CVideoDatabase::EraseAllVideoSettings()
6305 std::string sql = "DELETE FROM settings";
6307 CLog::Log(LOGINFO, "Deleting all video settings");
6308 m_pDS->exec(sql);
6310 catch (...)
6312 CLog::Log(LOGERROR, "{} failed", __FUNCTION__);
6316 void CVideoDatabase::EraseAllVideoSettings(const std::string& path)
6318 std::string itemsToDelete;
6322 std::string sql = PrepareSQL("SELECT files.idFile FROM files WHERE idFile IN (SELECT idFile FROM files INNER JOIN path ON path.idPath = files.idPath AND path.strPath LIKE \"%s%%\")", path.c_str());
6323 m_pDS->query(sql);
6324 while (!m_pDS->eof())
6326 std::string file = m_pDS->fv("files.idFile").get_asString() + ",";
6327 itemsToDelete += file;
6328 m_pDS->next();
6330 m_pDS->close();
6332 if (!itemsToDelete.empty())
6334 itemsToDelete = "(" + StringUtils::TrimRight(itemsToDelete, ",") + ")";
6336 sql = "DELETE FROM settings WHERE idFile IN " + itemsToDelete;
6337 m_pDS->exec(sql);
6340 catch (...)
6342 CLog::Log(LOGERROR, "{} failed", __FUNCTION__);
6346 bool CVideoDatabase::GetGenresNav(const std::string& strBaseDir,
6347 CFileItemList& items,
6348 VideoDbContentType idContent /* = UNKNOWN */,
6349 const Filter& filter /* = Filter() */,
6350 bool countOnly /* = false */)
6352 return GetNavCommon(strBaseDir, items, "genre", idContent, filter, countOnly);
6355 bool CVideoDatabase::GetCountriesNav(const std::string& strBaseDir,
6356 CFileItemList& items,
6357 VideoDbContentType idContent /* = UNKNOWN */,
6358 const Filter& filter /* = Filter() */,
6359 bool countOnly /* = false */)
6361 return GetNavCommon(strBaseDir, items, "country", idContent, filter, countOnly);
6364 bool CVideoDatabase::GetStudiosNav(const std::string& strBaseDir,
6365 CFileItemList& items,
6366 VideoDbContentType idContent /* = UNKNOWN */,
6367 const Filter& filter /* = Filter() */,
6368 bool countOnly /* = false */)
6370 return GetNavCommon(strBaseDir, items, "studio", idContent, filter, countOnly);
6373 bool CVideoDatabase::GetNavCommon(const std::string& strBaseDir,
6374 CFileItemList& items,
6375 const char* type,
6376 VideoDbContentType idContent /* = UNKNOWN */,
6377 const Filter& filter /* = Filter() */,
6378 bool countOnly /* = false */)
6382 if (nullptr == m_pDB)
6383 return false;
6384 if (nullptr == m_pDS)
6385 return false;
6387 std::string strSQL;
6388 Filter extFilter = filter;
6389 if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
6391 std::string view, view_id, media_type, extraField, extraJoin;
6392 if (idContent == VideoDbContentType::MOVIES)
6394 view = MediaTypeMovie;
6395 view_id = "idMovie";
6396 media_type = MediaTypeMovie;
6397 extraField = "files.playCount";
6399 else if (idContent == VideoDbContentType::TVSHOWS) //this will not get tvshows with 0 episodes
6401 view = MediaTypeEpisode;
6402 view_id = "idShow";
6403 media_type = MediaTypeTvShow;
6404 // in order to make use of FieldPlaycount in smart playlists we need an extra join
6405 if (StringUtils::EqualsNoCase(type, "tag"))
6406 extraJoin = PrepareSQL("JOIN tvshow_view ON tvshow_view.idShow = tag_link.media_id AND tag_link.media_type='tvshow'");
6408 else if (idContent == VideoDbContentType::MUSICVIDEOS)
6410 view = MediaTypeMusicVideo;
6411 view_id = "idMVideo";
6412 media_type = MediaTypeMusicVideo;
6413 extraField = "files.playCount";
6415 else
6416 return false;
6418 strSQL = "SELECT {} " + PrepareSQL("FROM %s ", type);
6419 extFilter.fields = PrepareSQL("%s.%s_id, %s.name, path.strPath", type, type, type);
6420 extFilter.AppendField(extraField);
6421 extFilter.AppendJoin(PrepareSQL("JOIN %s_link ON %s.%s_id = %s_link.%s_id", type, type, type, type, type));
6422 extFilter.AppendJoin(PrepareSQL("JOIN %s_view ON %s_link.media_id = %s_view.%s AND %s_link.media_type='%s'", view.c_str(), type, view.c_str(), view_id.c_str(), type, media_type.c_str()));
6423 extFilter.AppendJoin(PrepareSQL("JOIN files ON files.idFile = %s_view.idFile", view.c_str()));
6424 extFilter.AppendJoin("JOIN path ON path.idPath = files.idPath");
6425 extFilter.AppendJoin(extraJoin);
6427 else
6429 std::string view, view_id, media_type, extraField, extraJoin;
6430 if (idContent == VideoDbContentType::MOVIES)
6432 view = MediaTypeMovie;
6433 view_id = "idMovie";
6434 media_type = MediaTypeMovie;
6435 extraField = "count(1), count(files.playCount)";
6436 extraJoin = PrepareSQL("JOIN files ON files.idFile = %s_view.idFile", view.c_str());
6438 else if (idContent == VideoDbContentType::TVSHOWS)
6440 view = MediaTypeTvShow;
6441 view_id = "idShow";
6442 media_type = MediaTypeTvShow;
6444 else if (idContent == VideoDbContentType::MUSICVIDEOS)
6446 view = MediaTypeMusicVideo;
6447 view_id = "idMVideo";
6448 media_type = MediaTypeMusicVideo;
6449 extraField = "count(1), count(files.playCount)";
6450 extraJoin = PrepareSQL("JOIN files ON files.idFile = %s_view.idFile", view.c_str());
6452 else
6453 return false;
6455 strSQL = "SELECT {} " + PrepareSQL("FROM %s ", type);
6456 extFilter.fields = PrepareSQL("%s.%s_id, %s.name", type, type, type);
6457 extFilter.AppendField(extraField);
6458 extFilter.AppendJoin(PrepareSQL("JOIN %s_link ON %s.%s_id = %s_link.%s_id", type, type, type, type, type));
6459 extFilter.AppendJoin(PrepareSQL("JOIN %s_view ON %s_link.media_id = %s_view.%s AND %s_link.media_type='%s'",
6460 view.c_str(), type, view.c_str(), view_id.c_str(), type, media_type.c_str()));
6461 extFilter.AppendJoin(extraJoin);
6462 extFilter.AppendGroup(PrepareSQL("%s.%s_id", type, type));
6465 if (countOnly)
6467 extFilter.fields = PrepareSQL("COUNT(DISTINCT %s.%s_id)", type, type);
6468 extFilter.group.clear();
6469 extFilter.order.clear();
6471 strSQL = StringUtils::Format(strSQL, !extFilter.fields.empty() ? extFilter.fields : "*");
6473 CVideoDbUrl videoUrl;
6474 if (!BuildSQL(strBaseDir, strSQL, extFilter, strSQL, videoUrl))
6475 return false;
6477 int iRowsFound = RunQuery(strSQL);
6478 if (iRowsFound <= 0)
6479 return iRowsFound == 0;
6481 if (countOnly)
6483 CFileItemPtr pItem(new CFileItem());
6484 pItem->SetProperty("total", iRowsFound == 1 ? m_pDS->fv(0).get_asInt() : iRowsFound);
6485 items.Add(pItem);
6487 m_pDS->close();
6488 return true;
6491 if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
6493 std::map<int, std::pair<std::string,int> > mapItems;
6494 while (!m_pDS->eof())
6496 int id = m_pDS->fv(0).get_asInt();
6497 std::string str = m_pDS->fv(1).get_asString();
6499 // was this already found?
6500 auto it = mapItems.find(id);
6501 if (it == mapItems.end())
6503 // check path
6504 if (g_passwordManager.IsDatabasePathUnlocked(m_pDS->fv(2).get_asString(),*CMediaSourceSettings::GetInstance().GetSources("video")))
6506 if (idContent == VideoDbContentType::MOVIES ||
6507 idContent == VideoDbContentType::MUSICVIDEOS)
6508 mapItems.insert(std::pair<int, std::pair<std::string,int> >(id, std::pair<std::string, int>(str,m_pDS->fv(3).get_asInt()))); //fv(3) is file.playCount
6509 else if (idContent == VideoDbContentType::TVSHOWS)
6510 mapItems.insert(std::pair<int, std::pair<std::string,int> >(id, std::pair<std::string,int>(str,0)));
6513 m_pDS->next();
6515 m_pDS->close();
6517 for (const auto &i : mapItems)
6519 CFileItemPtr pItem(new CFileItem(i.second.first));
6520 pItem->GetVideoInfoTag()->m_iDbId = i.first;
6521 pItem->GetVideoInfoTag()->m_type = type;
6523 CVideoDbUrl itemUrl = videoUrl;
6524 std::string path = StringUtils::Format("{}/", i.first);
6525 itemUrl.AppendPath(path);
6526 pItem->SetPath(itemUrl.ToString());
6528 pItem->m_bIsFolder = true;
6529 if (idContent == VideoDbContentType::MOVIES || idContent == VideoDbContentType::MUSICVIDEOS)
6530 pItem->GetVideoInfoTag()->SetPlayCount(i.second.second);
6531 if (!items.Contains(pItem->GetPath()))
6533 pItem->SetLabelPreformatted(true);
6534 items.Add(pItem);
6538 else
6540 while (!m_pDS->eof())
6542 CFileItemPtr pItem(new CFileItem(m_pDS->fv(1).get_asString()));
6543 pItem->GetVideoInfoTag()->m_iDbId = m_pDS->fv(0).get_asInt();
6544 pItem->GetVideoInfoTag()->m_type = type;
6546 CVideoDbUrl itemUrl = videoUrl;
6547 std::string path = StringUtils::Format("{}/", m_pDS->fv(0).get_asInt());
6548 itemUrl.AppendPath(path);
6549 pItem->SetPath(itemUrl.ToString());
6551 pItem->m_bIsFolder = true;
6552 pItem->SetLabelPreformatted(true);
6553 if (idContent == VideoDbContentType::MOVIES || idContent == VideoDbContentType::MUSICVIDEOS)
6554 { // fv(3) is the number of videos watched, fv(2) is the total number. We set the playcount
6555 // only if the number of videos watched is equal to the total number (i.e. every video watched)
6556 pItem->GetVideoInfoTag()->SetPlayCount((m_pDS->fv(3).get_asInt() == m_pDS->fv(2).get_asInt()) ? 1 : 0);
6558 items.Add(pItem);
6559 m_pDS->next();
6561 m_pDS->close();
6563 return true;
6565 catch (...)
6567 CLog::Log(LOGERROR, "{} failed", __FUNCTION__);
6569 return false;
6572 bool CVideoDatabase::GetTagsNav(const std::string& strBaseDir,
6573 CFileItemList& items,
6574 VideoDbContentType idContent /* = UNKNOWN */,
6575 const Filter& filter /* = Filter() */,
6576 bool countOnly /* = false */)
6578 return GetNavCommon(strBaseDir, items, "tag", idContent, filter, countOnly);
6581 bool CVideoDatabase::GetSetsNav(const std::string& strBaseDir,
6582 CFileItemList& items,
6583 VideoDbContentType idContent /* = UNKNOWN */,
6584 const Filter& filter /* = Filter() */,
6585 bool ignoreSingleMovieSets /* = false */)
6587 if (idContent != VideoDbContentType::MOVIES)
6588 return false;
6590 return GetSetsByWhere(strBaseDir, filter, items, ignoreSingleMovieSets);
6593 bool CVideoDatabase::GetSetsByWhere(const std::string& strBaseDir, const Filter &filter, CFileItemList& items, bool ignoreSingleMovieSets /* = false */)
6597 if (nullptr == m_pDB)
6598 return false;
6599 if (nullptr == m_pDS)
6600 return false;
6602 CVideoDbUrl videoUrl;
6603 if (!videoUrl.FromString(strBaseDir))
6604 return false;
6606 Filter setFilter = filter;
6607 setFilter.join += " JOIN sets ON movie_view.idSet = sets.idSet";
6608 if (!setFilter.order.empty())
6609 setFilter.order += ",";
6610 setFilter.order += "sets.idSet";
6612 if (!GetMoviesByWhere(strBaseDir, setFilter, items))
6613 return false;
6615 CFileItemList sets;
6616 GroupAttribute groupingAttributes;
6617 const CUrlOptions::UrlOptions& options = videoUrl.GetOptions();
6618 auto option = options.find("ignoreSingleMovieSets");
6620 if (option != options.end())
6622 groupingAttributes =
6623 option->second.asBoolean() ? GroupAttributeIgnoreSingleItems : GroupAttributeNone;
6625 else
6627 groupingAttributes =
6628 ignoreSingleMovieSets ? GroupAttributeIgnoreSingleItems : GroupAttributeNone;
6631 if (!GroupUtils::Group(GroupBySet, strBaseDir, items, sets, groupingAttributes))
6632 return false;
6634 items.ClearItems();
6635 items.Append(sets);
6637 return true;
6639 catch (...)
6641 CLog::Log(LOGERROR, "{} failed", __FUNCTION__);
6643 return false;
6646 bool CVideoDatabase::GetMusicVideoAlbumsNav(const std::string& strBaseDir, CFileItemList& items, int idArtist /* = -1 */, const Filter &filter /* = Filter() */, bool countOnly /* = false */)
6650 if (nullptr == m_pDB)
6651 return false;
6652 if (nullptr == m_pDS)
6653 return false;
6655 CVideoDbUrl videoUrl;
6656 if (!videoUrl.FromString(strBaseDir))
6657 return false;
6659 std::string strSQL = "select {} from musicvideo_view ";
6660 Filter extFilter = filter;
6661 extFilter.fields = PrepareSQL("musicvideo_view.c%02d, musicvideo_view.idMVideo, actor.name, "
6662 "musicvideo_view.c%02d, musicvideo_view.c%02d, musicvideo_view.c%02d ",
6663 VIDEODB_ID_MUSICVIDEO_ALBUM, VIDEODB_ID_MUSICVIDEO_TITLE,
6664 VIDEODB_ID_MUSICVIDEO_PLOT, VIDEODB_ID_MUSICVIDEO_ARTIST);
6665 extFilter.AppendJoin(
6666 PrepareSQL("JOIN actor_link ON actor_link.media_id=musicvideo_view.idMVideo "));
6667 extFilter.AppendJoin(PrepareSQL("JOIN actor ON actor.actor_id = actor_link.actor_id"));
6668 extFilter.fields += ", path.strPath";
6669 extFilter.AppendJoin("join files on files.idFile = musicvideo_view.idFile join path on path.idPath = files.idPath");
6671 if (StringUtils::EndsWith(strBaseDir,"albums/"))
6672 extFilter.AppendWhere(PrepareSQL("musicvideo_view.c%02d != ''", VIDEODB_ID_MUSICVIDEO_ALBUM));
6674 if (idArtist > -1)
6675 videoUrl.AddOption("artistid", idArtist);
6677 extFilter.AppendGroup(PrepareSQL(" CASE WHEN musicvideo_view.c09 !='' THEN musicvideo_view.c09 "
6678 "ELSE musicvideo_view.c00 END"));
6680 if (countOnly)
6682 extFilter.fields = "COUNT(1)";
6683 extFilter.group.clear();
6684 extFilter.order.clear();
6686 strSQL = StringUtils::Format(strSQL, !extFilter.fields.empty() ? extFilter.fields : "*");
6688 if (!BuildSQL(videoUrl.ToString(), strSQL, extFilter, strSQL, videoUrl))
6689 return false;
6691 int iRowsFound = RunQuery(strSQL);
6692 /* fields returned by query are :-
6693 (0) - Album title (if any)
6694 (1) - idMVideo
6695 (2) - Artist name
6696 (3) - Music video title
6697 (4) - Music video plot
6698 (5) - Music Video artist
6699 (6) - Path to video
6701 if (iRowsFound <= 0)
6702 return iRowsFound == 0;
6704 std::string strArtist;
6705 if (idArtist> -1)
6706 strArtist = m_pDS->fv("actor.name").get_asString();
6708 if (countOnly)
6710 CFileItemPtr pItem(new CFileItem());
6711 pItem->SetProperty("total", iRowsFound == 1 ? m_pDS->fv(0).get_asInt() : iRowsFound);
6712 items.Add(pItem);
6714 m_pDS->close();
6715 return true;
6718 std::list <int> idMVideoList;
6719 std::list <std::pair<std::string, std::string>> idData;
6721 while (!m_pDS->eof())
6723 bool isAlbum = true;
6724 std::string strAlbum = m_pDS->fv(0).get_asString(); //Album title
6725 int idMVideo = m_pDS->fv(1).get_asInt();
6726 if (strAlbum.empty())
6728 strAlbum = m_pDS->fv(3).get_asString(); // video title if not an album
6729 isAlbum = false;
6732 CFileItemPtr pItem(new CFileItem(strAlbum));
6734 CVideoDbUrl itemUrl = videoUrl;
6735 std::string path = StringUtils::Format("{}/", idMVideo);
6736 if (!isAlbum)
6738 itemUrl.AddOption("albumid", idMVideo);
6739 path += std::to_string(idMVideo);
6741 strSQL = PrepareSQL(
6742 "SELECT type, url FROM art WHERE media_id = %i AND media_type = 'musicvideo'",
6743 idMVideo);
6744 m_pDS2->query(strSQL);
6745 while (!m_pDS2->eof())
6747 pItem->SetArt(m_pDS2->fv(0).get_asString(), m_pDS2->fv(1).get_asString());
6748 m_pDS2->next();
6750 m_pDS2->close();
6752 itemUrl.AppendPath(path);
6753 pItem->SetPath(itemUrl.ToString());
6754 pItem->m_bIsFolder = isAlbum;
6755 pItem->SetLabelPreformatted(true);
6757 if (!items.Contains(pItem->GetPath()))
6758 if (g_passwordManager.IsDatabasePathUnlocked(
6759 m_pDS->fv("path.strPath").get_asString(),
6760 *CMediaSourceSettings::GetInstance().GetSources("video")))
6762 pItem->GetVideoInfoTag()->m_artist.emplace_back(strArtist);
6763 pItem->GetVideoInfoTag()->m_iDbId = idMVideo;
6764 items.Add(pItem);
6765 idMVideoList.push_back(idMVideo);
6766 idData.push_back(make_pair(m_pDS->fv(0).get_asString(), m_pDS->fv(5).get_asString()));
6768 m_pDS->next();
6770 m_pDS->close();
6772 for (int i = 0; i < items.Size(); i++)
6774 CVideoInfoTag details;
6776 if (items[i]->m_bIsFolder)
6778 details.SetPath(items[i]->GetPath());
6779 details.m_strAlbum = idData.front().first;
6780 details.m_type = MediaTypeAlbum;
6781 details.m_artist.emplace_back(idData.front().second);
6782 details.m_iDbId = idMVideoList.front();
6783 items[i]->SetProperty("musicvideomediatype", MediaTypeAlbum);
6784 items[i]->SetLabel(idData.front().first);
6785 items[i]->SetFromVideoInfoTag(details);
6787 idMVideoList.pop_front();
6788 idData.pop_front();
6789 continue;
6791 else
6793 GetMusicVideoInfo("", details, idMVideoList.front());
6794 items[i]->SetFromVideoInfoTag(details);
6795 idMVideoList.pop_front();
6796 idData.pop_front();
6800 if (!strArtist.empty())
6801 items.SetProperty("customtitle",strArtist); // change displayed path from eg /23 to /Artist
6803 return true;
6805 catch (...)
6807 CLog::Log(LOGERROR, "{} failed", __FUNCTION__);
6809 return false;
6812 bool CVideoDatabase::GetWritersNav(const std::string& strBaseDir,
6813 CFileItemList& items,
6814 VideoDbContentType idContent /* = UNKNOWN */,
6815 const Filter& filter /* = Filter() */,
6816 bool countOnly /* = false */)
6818 return GetPeopleNav(strBaseDir, items, "writer", idContent, filter, countOnly);
6821 bool CVideoDatabase::GetDirectorsNav(const std::string& strBaseDir,
6822 CFileItemList& items,
6823 VideoDbContentType idContent /* = UNKNOWN */,
6824 const Filter& filter /* = Filter() */,
6825 bool countOnly /* = false */)
6827 return GetPeopleNav(strBaseDir, items, "director", idContent, filter, countOnly);
6830 bool CVideoDatabase::GetActorsNav(const std::string& strBaseDir,
6831 CFileItemList& items,
6832 VideoDbContentType idContent /* = UNKNOWN */,
6833 const Filter& filter /* = Filter() */,
6834 bool countOnly /* = false */)
6836 if (GetPeopleNav(strBaseDir, items, "actor", idContent, filter, countOnly))
6837 { // set thumbs - ideally this should be in the normal thumb setting routines
6838 for (int i = 0; i < items.Size() && !countOnly; i++)
6840 CFileItemPtr pItem = items[i];
6841 if (idContent == VideoDbContentType::MUSICVIDEOS)
6842 pItem->SetArt("icon", "DefaultArtist.png");
6843 else
6844 pItem->SetArt("icon", "DefaultActor.png");
6846 return true;
6848 return false;
6851 bool CVideoDatabase::GetPeopleNav(const std::string& strBaseDir,
6852 CFileItemList& items,
6853 const char* type,
6854 VideoDbContentType idContent /* = UNKNOWN */,
6855 const Filter& filter /* = Filter() */,
6856 bool countOnly /* = false */)
6858 if (nullptr == m_pDB)
6859 return false;
6860 if (nullptr == m_pDS)
6861 return false;
6865 //! @todo This routine (and probably others at this same level) use playcount as a reference to filter on at a later
6866 //! point. This means that we *MUST* filter these levels as you'll get double ups. Ideally we'd allow playcount
6867 //! to filter through as we normally do for tvshows to save this happening.
6868 //! Also, we apply this same filtering logic to the locked or unlocked paths to prevent these from showing.
6869 //! Whether or not this should happen is a tricky one - it complicates all the high level categories (everything
6870 //! above titles).
6872 // General routine that the other actor/director/writer routines call
6874 // get primary genres for movies
6875 std::string strSQL;
6876 bool bMainArtistOnly = false;
6877 Filter extFilter = filter;
6878 if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
6880 std::string view, view_id, media_type, extraField, extraJoin, group;
6881 if (idContent == VideoDbContentType::MOVIES)
6883 view = MediaTypeMovie;
6884 view_id = "idMovie";
6885 media_type = MediaTypeMovie;
6886 extraField = "files.playCount";
6888 else if (idContent == VideoDbContentType::TVSHOWS)
6890 view = MediaTypeEpisode;
6891 view_id = "idShow";
6892 media_type = MediaTypeTvShow;
6893 extraField = "count(DISTINCT idShow)";
6894 group = "actor.actor_id";
6896 else if (idContent == VideoDbContentType::EPISODES)
6898 view = MediaTypeEpisode;
6899 view_id = "idEpisode";
6900 media_type = MediaTypeEpisode;
6901 extraField = "files.playCount";
6903 else if (idContent == VideoDbContentType::MUSICVIDEOS)
6905 bMainArtistOnly = !CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(
6906 CSettings::SETTING_VIDEOLIBRARY_SHOWPERFORMERS);
6907 if (StringUtils::EndsWith(strBaseDir, "directors/"))
6908 // only set this to true if getting artists and show all performers is false
6909 bMainArtistOnly = false;
6910 view = MediaTypeMusicVideo;
6911 view_id = "idMVideo";
6912 media_type = MediaTypeMusicVideo;
6913 extraField = "count(1), count(files.playCount)";
6914 if (bMainArtistOnly)
6915 extraJoin =
6916 PrepareSQL(" WHERE actor.name IN (SELECT musicvideo_view.c10 from musicvideo_view)");
6917 group = "actor.actor_id";
6919 else
6920 return false;
6922 strSQL = "SELECT {} FROM actor ";
6923 extFilter.fields = "actor.actor_id, actor.name, actor.art_urls, path.strPath";
6924 extFilter.AppendField(extraField);
6925 extFilter.AppendJoin(PrepareSQL("JOIN %s_link ON actor.actor_id = %s_link.actor_id", type, type));
6926 extFilter.AppendJoin(PrepareSQL("JOIN %s_view ON %s_link.media_id = %s_view.%s AND %s_link.media_type='%s'", view.c_str(), type, view.c_str(), view_id.c_str(), type, media_type.c_str()));
6927 extFilter.AppendJoin(PrepareSQL("JOIN files ON files.idFile = %s_view.idFile", view.c_str()));
6928 extFilter.AppendJoin("JOIN path ON path.idPath = files.idPath");
6929 extFilter.AppendJoin(extraJoin);
6930 extFilter.AppendGroup(group);
6932 else
6934 std::string view, view_id, media_type, extraField, extraJoin;
6935 if (idContent == VideoDbContentType::MOVIES)
6937 view = MediaTypeMovie;
6938 view_id = "idMovie";
6939 media_type = MediaTypeMovie;
6940 extraField = "count(1), count(files.playCount)";
6941 extraJoin = PrepareSQL(" JOIN files ON files.idFile=%s_view.idFile", view.c_str());
6943 else if (idContent == VideoDbContentType::TVSHOWS)
6945 view = MediaTypeTvShow;
6946 view_id = "idShow";
6947 media_type = MediaTypeTvShow;
6948 extraField = "count(idShow)";
6950 else if (idContent == VideoDbContentType::EPISODES)
6952 view = MediaTypeEpisode;
6953 view_id = "idEpisode";
6954 media_type = MediaTypeEpisode;
6955 extraField = "count(1), count(files.playCount)";
6956 extraJoin = PrepareSQL("JOIN files ON files.idFile = %s_view.idFile", view.c_str());
6958 else if (idContent == VideoDbContentType::MUSICVIDEOS)
6960 bMainArtistOnly = !CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(
6961 CSettings::SETTING_VIDEOLIBRARY_SHOWPERFORMERS);
6962 if (StringUtils::EndsWith(strBaseDir, "directors/"))
6963 // only set this to true if getting artists and show all performers is false
6964 bMainArtistOnly = false;
6965 view = MediaTypeMusicVideo;
6966 view_id = "idMVideo";
6967 media_type = MediaTypeMusicVideo;
6968 extraField = "count(1), count(files.playCount)";
6969 extraJoin = PrepareSQL("JOIN files ON files.idFile = %s_view.idFile", view.c_str());
6970 if (bMainArtistOnly)
6971 extraJoin =
6972 extraJoin +
6973 PrepareSQL(" WHERE actor.name IN (SELECT musicvideo_view.c10 from musicvideo_view)");
6975 else
6976 return false;
6978 strSQL = "SELECT {} FROM actor ";
6979 extFilter.fields = "actor.actor_id, actor.name, actor.art_urls";
6980 extFilter.AppendField(extraField);
6981 extFilter.AppendJoin(PrepareSQL("JOIN %s_link on actor.actor_id = %s_link.actor_id", type, type));
6982 extFilter.AppendJoin(PrepareSQL("JOIN %s_view on %s_link.media_id = %s_view.%s AND %s_link.media_type='%s'", view.c_str(), type, view.c_str(), view_id.c_str(), type, media_type.c_str()));
6983 extFilter.AppendJoin(extraJoin);
6984 extFilter.AppendGroup("actor.actor_id");
6987 if (countOnly)
6989 extFilter.fields = "COUNT(1)";
6990 extFilter.group.clear();
6991 extFilter.order.clear();
6993 strSQL = StringUtils::Format(strSQL, !extFilter.fields.empty() ? extFilter.fields : "*");
6995 CVideoDbUrl videoUrl;
6996 if (!BuildSQL(strBaseDir, strSQL, extFilter, strSQL, videoUrl))
6997 return false;
6999 // run query
7000 auto start = std::chrono::steady_clock::now();
7002 if (!m_pDS->query(strSQL)) return false;
7004 auto end = std::chrono::steady_clock::now();
7005 auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
7007 CLog::Log(LOGDEBUG, LOGDATABASE, "{} - query took {} ms", __FUNCTION__, duration.count());
7009 start = std::chrono::steady_clock::now();
7011 int iRowsFound = m_pDS->num_rows();
7012 if (iRowsFound == 0)
7014 m_pDS->close();
7015 return true;
7018 if (countOnly)
7020 CFileItemPtr pItem(new CFileItem());
7021 pItem->SetProperty("total", iRowsFound == 1 ? m_pDS->fv(0).get_asInt() : iRowsFound);
7022 items.Add(pItem);
7024 m_pDS->close();
7025 return true;
7028 if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
7030 std::map<int, CActor> mapActors;
7032 while (!m_pDS->eof())
7034 int idActor = m_pDS->fv(0).get_asInt();
7035 CActor actor;
7036 actor.name = m_pDS->fv(1).get_asString();
7037 actor.thumb = m_pDS->fv(2).get_asString();
7038 if (idContent != VideoDbContentType::TVSHOWS &&
7039 idContent != VideoDbContentType::MUSICVIDEOS)
7041 actor.playcount = m_pDS->fv(3).get_asInt();
7042 actor.appearances = 1;
7044 else actor.appearances = m_pDS->fv(4).get_asInt();
7045 auto it = mapActors.find(idActor);
7046 // is this actor already known?
7047 if (it == mapActors.end())
7049 // check path
7050 if (g_passwordManager.IsDatabasePathUnlocked(m_pDS->fv("path.strPath").get_asString(),*CMediaSourceSettings::GetInstance().GetSources("video")))
7051 mapActors.insert(std::pair<int, CActor>(idActor, actor));
7053 else if (idContent != VideoDbContentType::TVSHOWS &&
7054 idContent != VideoDbContentType::MUSICVIDEOS)
7055 it->second.appearances++;
7056 m_pDS->next();
7058 m_pDS->close();
7060 for (const auto &i : mapActors)
7062 CFileItemPtr pItem(new CFileItem(i.second.name));
7064 CVideoDbUrl itemUrl = videoUrl;
7065 std::string path = StringUtils::Format("{}/", i.first);
7066 itemUrl.AppendPath(path);
7067 pItem->SetPath(itemUrl.ToString());
7069 pItem->m_bIsFolder=true;
7070 pItem->GetVideoInfoTag()->SetPlayCount(i.second.playcount);
7071 pItem->GetVideoInfoTag()->m_strPictureURL.ParseFromData(i.second.thumb);
7072 pItem->GetVideoInfoTag()->m_iDbId = i.first;
7073 pItem->GetVideoInfoTag()->m_type = type;
7074 pItem->GetVideoInfoTag()->m_relevance = i.second.appearances;
7075 if (idContent == VideoDbContentType::MUSICVIDEOS)
7077 // Get artist bio from music db later if available
7078 pItem->GetVideoInfoTag()->m_artist.emplace_back(i.second.name);
7079 pItem->SetProperty("musicvideomediatype", MediaTypeArtist);
7081 items.Add(pItem);
7084 else
7086 while (!m_pDS->eof())
7090 CFileItemPtr pItem(new CFileItem(m_pDS->fv(1).get_asString()));
7092 CVideoDbUrl itemUrl = videoUrl;
7093 std::string path = StringUtils::Format("{}/", m_pDS->fv(0).get_asInt());
7094 itemUrl.AppendPath(path);
7095 pItem->SetPath(itemUrl.ToString());
7097 pItem->m_bIsFolder=true;
7098 pItem->GetVideoInfoTag()->m_strPictureURL.ParseFromData(m_pDS->fv(2).get_asString());
7099 pItem->GetVideoInfoTag()->m_iDbId = m_pDS->fv(0).get_asInt();
7100 pItem->GetVideoInfoTag()->m_type = type;
7101 if (idContent != VideoDbContentType::TVSHOWS)
7103 // fv(4) is the number of videos watched, fv(3) is the total number. We set the playcount
7104 // only if the number of videos watched is equal to the total number (i.e. every video watched)
7105 pItem->GetVideoInfoTag()->SetPlayCount((m_pDS->fv(4).get_asInt() == m_pDS->fv(3).get_asInt()) ? 1 : 0);
7107 pItem->GetVideoInfoTag()->m_relevance = m_pDS->fv(3).get_asInt();
7108 if (idContent == VideoDbContentType::MUSICVIDEOS)
7110 pItem->GetVideoInfoTag()->m_artist.emplace_back(pItem->GetLabel());
7111 pItem->SetProperty("musicvideomediatype", MediaTypeArtist);
7113 items.Add(pItem);
7114 m_pDS->next();
7116 catch (...)
7118 m_pDS->close();
7119 CLog::Log(LOGERROR, "{}: out of memory - retrieved {} items", __FUNCTION__, items.Size());
7120 return items.Size() > 0;
7123 m_pDS->close();
7126 end = std::chrono::steady_clock::now();
7127 duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
7129 CLog::Log(LOGDEBUG, LOGDATABASE, "{} item retrieval took {} ms", __FUNCTION__,
7130 duration.count());
7132 return true;
7134 catch (...)
7136 m_pDS->close();
7137 CLog::Log(LOGERROR, "{} failed", __FUNCTION__);
7139 return false;
7142 bool CVideoDatabase::GetYearsNav(const std::string& strBaseDir,
7143 CFileItemList& items,
7144 VideoDbContentType idContent /* = UNKNOWN */,
7145 const Filter& filter /* = Filter() */)
7149 if (nullptr == m_pDB)
7150 return false;
7151 if (nullptr == m_pDS)
7152 return false;
7154 std::string strSQL;
7155 Filter extFilter = filter;
7156 if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
7158 if (idContent == VideoDbContentType::MOVIES)
7160 strSQL = "select movie_view.premiered, path.strPath, files.playCount from movie_view ";
7161 extFilter.AppendJoin("join files on files.idFile = movie_view.idFile join path on files.idPath = path.idPath");
7163 else if (idContent == VideoDbContentType::TVSHOWS)
7165 strSQL = PrepareSQL("select tvshow_view.c%02d, path.strPath from tvshow_view ", VIDEODB_ID_TV_PREMIERED);
7166 extFilter.AppendJoin("join episode_view on episode_view.idShow = tvshow_view.idShow join files on files.idFile = episode_view.idFile join path on files.idPath = path.idPath");
7168 else if (idContent == VideoDbContentType::MUSICVIDEOS)
7170 strSQL = "select musicvideo_view.premiered, path.strPath, files.playCount from musicvideo_view ";
7171 extFilter.AppendJoin("join files on files.idFile = musicvideo_view.idFile join path on files.idPath = path.idPath");
7173 else
7174 return false;
7176 else
7178 std::string group;
7179 if (idContent == VideoDbContentType::MOVIES)
7181 strSQL = "select movie_view.premiered, count(1), count(files.playCount) from movie_view ";
7182 extFilter.AppendJoin("join files on files.idFile = movie_view.idFile");
7183 extFilter.AppendGroup("movie_view.premiered");
7185 else if (idContent == VideoDbContentType::TVSHOWS)
7187 strSQL = PrepareSQL("select distinct tvshow_view.c%02d from tvshow_view", VIDEODB_ID_TV_PREMIERED);
7188 extFilter.AppendGroup(PrepareSQL("tvshow_view.c%02d", VIDEODB_ID_TV_PREMIERED));
7190 else if (idContent == VideoDbContentType::MUSICVIDEOS)
7192 strSQL = "select musicvideo_view.premiered, count(1), count(files.playCount) from musicvideo_view ";
7193 extFilter.AppendJoin("join files on files.idFile = musicvideo_view.idFile");
7194 extFilter.AppendGroup("musicvideo_view.premiered");
7196 else
7197 return false;
7200 CVideoDbUrl videoUrl;
7201 if (!BuildSQL(strBaseDir, strSQL, extFilter, strSQL, videoUrl))
7202 return false;
7204 int iRowsFound = RunQuery(strSQL);
7205 if (iRowsFound <= 0)
7206 return iRowsFound == 0;
7208 if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
7210 std::map<int, std::pair<std::string,int> > mapYears;
7211 while (!m_pDS->eof())
7213 int lYear = 0;
7214 std::string dateString = m_pDS->fv(0).get_asString();
7215 if (dateString.size() == 4)
7216 lYear = m_pDS->fv(0).get_asInt();
7217 else
7219 CDateTime time;
7220 time.SetFromDateString(dateString);
7221 if (time.IsValid())
7222 lYear = time.GetYear();
7224 auto it = mapYears.find(lYear);
7225 if (it == mapYears.end())
7227 // check path
7228 if (g_passwordManager.IsDatabasePathUnlocked(m_pDS->fv("path.strPath").get_asString(),*CMediaSourceSettings::GetInstance().GetSources("video")))
7230 std::string year = std::to_string(lYear);
7231 if (idContent == VideoDbContentType::MOVIES ||
7232 idContent == VideoDbContentType::MUSICVIDEOS)
7233 mapYears.insert(std::pair<int, std::pair<std::string,int> >(lYear, std::pair<std::string,int>(year,m_pDS->fv(2).get_asInt())));
7234 else
7235 mapYears.insert(std::pair<int, std::pair<std::string,int> >(lYear, std::pair<std::string,int>(year,0)));
7238 m_pDS->next();
7240 m_pDS->close();
7242 for (const auto &i : mapYears)
7244 if (i.first == 0)
7245 continue;
7246 CFileItemPtr pItem(new CFileItem(i.second.first));
7248 CVideoDbUrl itemUrl = videoUrl;
7249 std::string path = StringUtils::Format("{}/", i.first);
7250 itemUrl.AppendPath(path);
7251 pItem->SetPath(itemUrl.ToString());
7253 pItem->m_bIsFolder=true;
7254 if (idContent == VideoDbContentType::MOVIES || idContent == VideoDbContentType::MUSICVIDEOS)
7255 pItem->GetVideoInfoTag()->SetPlayCount(i.second.second);
7256 items.Add(pItem);
7259 else
7261 while (!m_pDS->eof())
7263 int lYear = 0;
7264 std::string strLabel = m_pDS->fv(0).get_asString();
7265 if (strLabel.size() == 4)
7266 lYear = m_pDS->fv(0).get_asInt();
7267 else
7269 CDateTime time;
7270 time.SetFromDateString(strLabel);
7271 if (time.IsValid())
7273 lYear = time.GetYear();
7274 strLabel = std::to_string(lYear);
7277 if (lYear == 0)
7279 m_pDS->next();
7280 continue;
7282 CFileItemPtr pItem(new CFileItem(strLabel));
7284 CVideoDbUrl itemUrl = videoUrl;
7285 std::string path = StringUtils::Format("{}/", lYear);
7286 itemUrl.AppendPath(path);
7287 pItem->SetPath(itemUrl.ToString());
7289 pItem->m_bIsFolder=true;
7290 if (idContent == VideoDbContentType::MOVIES || idContent == VideoDbContentType::MUSICVIDEOS)
7292 // fv(2) is the number of videos watched, fv(1) is the total number. We set the playcount
7293 // only if the number of videos watched is equal to the total number (i.e. every video watched)
7294 pItem->GetVideoInfoTag()->SetPlayCount((m_pDS->fv(2).get_asInt() == m_pDS->fv(1).get_asInt()) ? 1 : 0);
7297 // take care of dupes ..
7298 if (!items.Contains(pItem->GetPath()))
7299 items.Add(pItem);
7301 m_pDS->next();
7303 m_pDS->close();
7306 return true;
7308 catch (...)
7310 CLog::Log(LOGERROR, "{} failed", __FUNCTION__);
7312 return false;
7315 bool CVideoDatabase::GetSeasonsNav(const std::string& strBaseDir, CFileItemList& items, int idActor, int idDirector, int idGenre, int idYear, int idShow, bool getLinkedMovies /* = true */)
7317 // parse the base path to get additional filters
7318 CVideoDbUrl videoUrl;
7319 if (!videoUrl.FromString(strBaseDir))
7320 return false;
7322 if (idShow != -1)
7323 videoUrl.AddOption("tvshowid", idShow);
7324 if (idActor != -1)
7325 videoUrl.AddOption("actorid", idActor);
7326 else if (idDirector != -1)
7327 videoUrl.AddOption("directorid", idDirector);
7328 else if (idGenre != -1)
7329 videoUrl.AddOption("genreid", idGenre);
7330 else if (idYear != -1)
7331 videoUrl.AddOption("year", idYear);
7333 if (!GetSeasonsByWhere(videoUrl.ToString(), Filter(), items, false))
7334 return false;
7336 // now add any linked movies
7337 if (getLinkedMovies && idShow != -1)
7339 Filter movieFilter;
7340 movieFilter.join = PrepareSQL("join movielinktvshow on movielinktvshow.idMovie=movie_view.idMovie");
7341 movieFilter.where = PrepareSQL("movielinktvshow.idShow = %i", idShow);
7342 CFileItemList movieItems;
7343 GetMoviesByWhere("videodb://movies/titles/", movieFilter, movieItems);
7345 if (movieItems.Size() > 0)
7346 items.Append(movieItems);
7349 return true;
7352 bool CVideoDatabase::GetSeasonsByWhere(const std::string& strBaseDir, const Filter &filter, CFileItemList& items, bool appendFullShowPath /* = true */, const SortDescription &sortDescription /* = SortDescription() */)
7356 if (nullptr == m_pDB)
7357 return false;
7358 if (nullptr == m_pDS)
7359 return false;
7361 int total = -1;
7363 std::string strSQL = "SELECT %s FROM season_view ";
7364 CVideoDbUrl videoUrl;
7365 std::string strSQLExtra;
7366 Filter extFilter = filter;
7367 SortDescription sorting = sortDescription;
7368 if (!BuildSQL(strBaseDir, strSQLExtra, extFilter, strSQLExtra, videoUrl, sorting))
7369 return false;
7371 // Apply the limiting directly here if there's no special sorting but limiting
7372 if (extFilter.limit.empty() && sorting.sortBy == SortByNone &&
7373 (sorting.limitStart > 0 || sorting.limitEnd > 0 ||
7374 (sorting.limitStart == 0 && sorting.limitEnd == 0)))
7376 total = (int)strtol(GetSingleValue(PrepareSQL(strSQL, "COUNT(1)") + strSQLExtra, m_pDS).c_str(), NULL, 10);
7377 strSQLExtra += DatabaseUtils::BuildLimitClause(sorting.limitEnd, sorting.limitStart);
7380 strSQL = PrepareSQL(strSQL, !extFilter.fields.empty() ? extFilter.fields.c_str() : "*") + strSQLExtra;
7382 int iRowsFound = RunQuery(strSQL);
7384 // store the total value of items as a property
7385 if (total < iRowsFound)
7386 total = iRowsFound;
7387 items.SetProperty("total", total);
7389 if (iRowsFound <= 0)
7390 return iRowsFound == 0;
7392 std::set<std::pair<int, int>> mapSeasons;
7393 while (!m_pDS->eof())
7395 int id = m_pDS->fv(VIDEODB_ID_SEASON_ID).get_asInt();
7396 int showId = m_pDS->fv(VIDEODB_ID_SEASON_TVSHOW_ID).get_asInt();
7397 int iSeason = m_pDS->fv(VIDEODB_ID_SEASON_NUMBER).get_asInt();
7398 std::string name = m_pDS->fv(VIDEODB_ID_SEASON_NAME).get_asString();
7399 std::string path = m_pDS->fv(VIDEODB_ID_SEASON_TVSHOW_PATH).get_asString();
7401 if (mapSeasons.find(std::make_pair(showId, iSeason)) == mapSeasons.end() &&
7402 (m_profileManager.GetMasterProfile().getLockMode() == LOCK_MODE_EVERYONE || g_passwordManager.bMasterUser ||
7403 g_passwordManager.IsDatabasePathUnlocked(path, *CMediaSourceSettings::GetInstance().GetSources("video"))))
7405 mapSeasons.insert(std::make_pair(showId, iSeason));
7407 std::string strLabel = name;
7408 if (strLabel.empty())
7410 if (iSeason == 0)
7411 strLabel = g_localizeStrings.Get(20381);
7412 else
7413 strLabel = StringUtils::Format(g_localizeStrings.Get(20358), iSeason);
7415 CFileItemPtr pItem(new CFileItem(strLabel));
7417 CVideoDbUrl itemUrl = videoUrl;
7418 std::string strDir;
7419 if (appendFullShowPath)
7420 strDir += StringUtils::Format("{}/", showId);
7421 strDir += StringUtils::Format("{}/", iSeason);
7422 itemUrl.AppendPath(strDir);
7423 pItem->SetPath(itemUrl.ToString());
7425 pItem->m_bIsFolder = true;
7426 pItem->GetVideoInfoTag()->m_strTitle = strLabel;
7427 if (!name.empty())
7428 pItem->GetVideoInfoTag()->m_strSortTitle = name;
7429 pItem->GetVideoInfoTag()->m_iSeason = iSeason;
7430 pItem->GetVideoInfoTag()->m_iDbId = id;
7431 pItem->GetVideoInfoTag()->m_iIdSeason = id;
7432 pItem->GetVideoInfoTag()->m_type = MediaTypeSeason;
7433 pItem->GetVideoInfoTag()->m_strPath = path;
7434 pItem->GetVideoInfoTag()->m_strShowTitle = m_pDS->fv(VIDEODB_ID_SEASON_TVSHOW_TITLE).get_asString();
7435 pItem->GetVideoInfoTag()->m_strPlot = m_pDS->fv(VIDEODB_ID_SEASON_TVSHOW_PLOT).get_asString();
7436 pItem->GetVideoInfoTag()->SetPremieredFromDBDate(m_pDS->fv(VIDEODB_ID_SEASON_TVSHOW_PREMIERED).get_asString());
7437 pItem->GetVideoInfoTag()->m_firstAired.SetFromDBDate(m_pDS->fv(VIDEODB_ID_SEASON_PREMIERED).get_asString());
7438 pItem->GetVideoInfoTag()->m_iUserRating = m_pDS->fv(VIDEODB_ID_SEASON_USER_RATING).get_asInt();
7439 // season premiered date based on first episode airdate associated to the season
7440 // tvshow premiered date is used as a fallback
7441 if (pItem->GetVideoInfoTag()->m_firstAired.IsValid())
7442 pItem->GetVideoInfoTag()->SetPremiered(pItem->GetVideoInfoTag()->m_firstAired);
7443 else if (pItem->GetVideoInfoTag()->HasPremiered())
7444 pItem->GetVideoInfoTag()->SetPremiered(pItem->GetVideoInfoTag()->GetPremiered());
7445 else if (pItem->GetVideoInfoTag()->HasYear())
7446 pItem->GetVideoInfoTag()->SetYear(pItem->GetVideoInfoTag()->GetYear());
7447 pItem->GetVideoInfoTag()->m_genre = StringUtils::Split(m_pDS->fv(VIDEODB_ID_SEASON_TVSHOW_GENRE).get_asString(), CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_videoItemSeparator);
7448 pItem->GetVideoInfoTag()->m_studio = StringUtils::Split(m_pDS->fv(VIDEODB_ID_SEASON_TVSHOW_STUDIO).get_asString(), CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_videoItemSeparator);
7449 pItem->GetVideoInfoTag()->m_strMPAARating = m_pDS->fv(VIDEODB_ID_SEASON_TVSHOW_MPAA).get_asString();
7450 pItem->GetVideoInfoTag()->m_iIdShow = showId;
7452 const int totalEpisodes = m_pDS->fv(VIDEODB_ID_SEASON_EPISODES_TOTAL).get_asInt();
7453 const int watchedEpisodes = m_pDS->fv(VIDEODB_ID_SEASON_EPISODES_WATCHED).get_asInt();
7454 pItem->GetVideoInfoTag()->m_iEpisode = totalEpisodes;
7455 pItem->SetProperty("totalepisodes", totalEpisodes);
7456 pItem->SetProperty("numepisodes", totalEpisodes); // will be changed later to reflect watchmode setting
7457 pItem->SetProperty("watchedepisodes", watchedEpisodes);
7458 pItem->SetProperty("unwatchedepisodes", totalEpisodes - watchedEpisodes);
7459 pItem->SetProperty("watchedepisodepercent",
7460 totalEpisodes > 0 ? (watchedEpisodes * 100 / totalEpisodes) : 0);
7461 if (iSeason == 0)
7462 pItem->SetProperty("isspecial", true);
7463 pItem->GetVideoInfoTag()->SetPlayCount((totalEpisodes == watchedEpisodes) ? 1 : 0);
7464 pItem->SetOverlayImage(CGUIListItem::ICON_OVERLAY_UNWATCHED, (pItem->GetVideoInfoTag()->GetPlayCount() > 0) && (pItem->GetVideoInfoTag()->m_iEpisode > 0));
7466 items.Add(pItem);
7469 m_pDS->next();
7471 m_pDS->close();
7473 return true;
7475 catch (...)
7477 CLog::Log(LOGERROR, "{} failed", __FUNCTION__);
7479 return false;
7482 bool CVideoDatabase::GetSortedVideos(const MediaType &mediaType, const std::string& strBaseDir, const SortDescription &sortDescription, CFileItemList& items, const Filter &filter /* = Filter() */)
7484 if (nullptr == m_pDB || nullptr == m_pDS)
7485 return false;
7487 if (mediaType != MediaTypeMovie && mediaType != MediaTypeTvShow && mediaType != MediaTypeEpisode && mediaType != MediaTypeMusicVideo)
7488 return false;
7490 SortDescription sorting = sortDescription;
7491 if (sortDescription.sortBy == SortByFile || sortDescription.sortBy == SortByTitle ||
7492 sortDescription.sortBy == SortBySortTitle || sortDescription.sortBy == SortByOriginalTitle ||
7493 sortDescription.sortBy == SortByLabel || sortDescription.sortBy == SortByDateAdded ||
7494 sortDescription.sortBy == SortByRating || sortDescription.sortBy == SortByUserRating ||
7495 sortDescription.sortBy == SortByYear || sortDescription.sortBy == SortByLastPlayed ||
7496 sortDescription.sortBy == SortByPlaycount)
7497 sorting.sortAttributes = (SortAttribute)(sortDescription.sortAttributes | SortAttributeIgnoreFolders);
7499 bool success = false;
7500 if (mediaType == MediaTypeMovie)
7501 success = GetMoviesByWhere(strBaseDir, filter, items, sorting);
7502 else if (mediaType == MediaTypeTvShow)
7503 success = GetTvShowsByWhere(strBaseDir, filter, items, sorting);
7504 else if (mediaType == MediaTypeEpisode)
7505 success = GetEpisodesByWhere(strBaseDir, filter, items, true, sorting);
7506 else if (mediaType == MediaTypeMusicVideo)
7507 success = GetMusicVideosByWhere(strBaseDir, filter, items, true, sorting);
7508 else
7509 return false;
7511 items.SetContent(CMediaTypes::ToPlural(mediaType));
7512 return success;
7515 bool CVideoDatabase::GetItems(const std::string &strBaseDir, CFileItemList &items, const Filter &filter /* = Filter() */, const SortDescription &sortDescription /* = SortDescription() */)
7517 CVideoDbUrl videoUrl;
7518 if (!videoUrl.FromString(strBaseDir))
7519 return false;
7521 return GetItems(strBaseDir, videoUrl.GetType(), videoUrl.GetItemType(), items, filter, sortDescription);
7524 bool CVideoDatabase::GetItems(const std::string &strBaseDir, const std::string &mediaType, const std::string &itemType, CFileItemList &items, const Filter &filter /* = Filter() */, const SortDescription &sortDescription /* = SortDescription() */)
7526 VideoDbContentType contentType;
7527 if (StringUtils::EqualsNoCase(mediaType, "movies"))
7528 contentType = VideoDbContentType::MOVIES;
7529 else if (StringUtils::EqualsNoCase(mediaType, "tvshows"))
7531 if (StringUtils::EqualsNoCase(itemType, "episodes"))
7532 contentType = VideoDbContentType::EPISODES;
7533 else
7534 contentType = VideoDbContentType::TVSHOWS;
7536 else if (StringUtils::EqualsNoCase(mediaType, "musicvideos"))
7537 contentType = VideoDbContentType::MUSICVIDEOS;
7538 else
7539 return false;
7541 return GetItems(strBaseDir, contentType, itemType, items, filter, sortDescription);
7544 bool CVideoDatabase::GetItems(const std::string& strBaseDir,
7545 VideoDbContentType mediaType,
7546 const std::string& itemType,
7547 CFileItemList& items,
7548 const Filter& filter /* = Filter() */,
7549 const SortDescription& sortDescription /* = SortDescription() */)
7551 if (StringUtils::EqualsNoCase(itemType, "movies") &&
7552 (mediaType == VideoDbContentType::MOVIES || mediaType == VideoDbContentType::MOVIE_SETS))
7553 return GetMoviesByWhere(strBaseDir, filter, items, sortDescription);
7554 else if (StringUtils::EqualsNoCase(itemType, "tvshows") &&
7555 mediaType == VideoDbContentType::TVSHOWS)
7557 Filter extFilter = filter;
7558 if (!CServiceBroker::GetSettingsComponent()->GetSettings()->
7559 GetBool(CSettings::SETTING_VIDEOLIBRARY_SHOWEMPTYTVSHOWS))
7560 extFilter.AppendWhere("totalCount IS NOT NULL AND totalCount > 0");
7561 return GetTvShowsByWhere(strBaseDir, extFilter, items, sortDescription);
7563 else if (StringUtils::EqualsNoCase(itemType, "musicvideos") &&
7564 mediaType == VideoDbContentType::MUSICVIDEOS)
7565 return GetMusicVideosByWhere(strBaseDir, filter, items, true, sortDescription);
7566 else if (StringUtils::EqualsNoCase(itemType, "episodes") &&
7567 mediaType == VideoDbContentType::EPISODES)
7568 return GetEpisodesByWhere(strBaseDir, filter, items, true, sortDescription);
7569 else if (StringUtils::EqualsNoCase(itemType, "seasons") &&
7570 mediaType == VideoDbContentType::TVSHOWS)
7571 return GetSeasonsNav(strBaseDir, items);
7572 else if (StringUtils::EqualsNoCase(itemType, "genres"))
7573 return GetGenresNav(strBaseDir, items, mediaType, filter);
7574 else if (StringUtils::EqualsNoCase(itemType, "years"))
7575 return GetYearsNav(strBaseDir, items, mediaType, filter);
7576 else if (StringUtils::EqualsNoCase(itemType, "actors"))
7577 return GetActorsNav(strBaseDir, items, mediaType, filter);
7578 else if (StringUtils::EqualsNoCase(itemType, "directors"))
7579 return GetDirectorsNav(strBaseDir, items, mediaType, filter);
7580 else if (StringUtils::EqualsNoCase(itemType, "writers"))
7581 return GetWritersNav(strBaseDir, items, mediaType, filter);
7582 else if (StringUtils::EqualsNoCase(itemType, "studios"))
7583 return GetStudiosNav(strBaseDir, items, mediaType, filter);
7584 else if (StringUtils::EqualsNoCase(itemType, "sets"))
7585 return GetSetsNav(strBaseDir, items, mediaType, filter, !CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_VIDEOLIBRARY_GROUPSINGLEITEMSETS));
7586 else if (StringUtils::EqualsNoCase(itemType, "countries"))
7587 return GetCountriesNav(strBaseDir, items, mediaType, filter);
7588 else if (StringUtils::EqualsNoCase(itemType, "tags"))
7589 return GetTagsNav(strBaseDir, items, mediaType, filter);
7590 else if (StringUtils::EqualsNoCase(itemType, "artists") &&
7591 mediaType == VideoDbContentType::MUSICVIDEOS)
7592 return GetActorsNav(strBaseDir, items, mediaType, filter);
7593 else if (StringUtils::EqualsNoCase(itemType, "albums") &&
7594 mediaType == VideoDbContentType::MUSICVIDEOS)
7595 return GetMusicVideoAlbumsNav(strBaseDir, items, -1, filter);
7597 return false;
7600 std::string CVideoDatabase::GetItemById(const std::string &itemType, int id)
7602 if (StringUtils::EqualsNoCase(itemType, "genres"))
7603 return GetGenreById(id);
7604 else if (StringUtils::EqualsNoCase(itemType, "years"))
7605 return std::to_string(id);
7606 else if (StringUtils::EqualsNoCase(itemType, "actors") ||
7607 StringUtils::EqualsNoCase(itemType, "directors") ||
7608 StringUtils::EqualsNoCase(itemType, "artists"))
7609 return GetPersonById(id);
7610 else if (StringUtils::EqualsNoCase(itemType, "studios"))
7611 return GetStudioById(id);
7612 else if (StringUtils::EqualsNoCase(itemType, "sets"))
7613 return GetSetById(id);
7614 else if (StringUtils::EqualsNoCase(itemType, "countries"))
7615 return GetCountryById(id);
7616 else if (StringUtils::EqualsNoCase(itemType, "tags"))
7617 return GetTagById(id);
7618 else if (StringUtils::EqualsNoCase(itemType, "albums"))
7619 return GetMusicVideoAlbumById(id);
7621 return "";
7624 bool CVideoDatabase::GetMoviesNav(const std::string& strBaseDir, CFileItemList& items,
7625 int idGenre /* = -1 */, int idYear /* = -1 */, int idActor /* = -1 */, int idDirector /* = -1 */,
7626 int idStudio /* = -1 */, int idCountry /* = -1 */, int idSet /* = -1 */, int idTag /* = -1 */,
7627 const SortDescription &sortDescription /* = SortDescription() */, int getDetails /* = VideoDbDetailsNone */)
7629 CVideoDbUrl videoUrl;
7630 if (!videoUrl.FromString(strBaseDir))
7631 return false;
7633 if (idGenre > 0)
7634 videoUrl.AddOption("genreid", idGenre);
7635 else if (idCountry > 0)
7636 videoUrl.AddOption("countryid", idCountry);
7637 else if (idStudio > 0)
7638 videoUrl.AddOption("studioid", idStudio);
7639 else if (idDirector > 0)
7640 videoUrl.AddOption("directorid", idDirector);
7641 else if (idYear > 0)
7642 videoUrl.AddOption("year", idYear);
7643 else if (idActor > 0)
7644 videoUrl.AddOption("actorid", idActor);
7645 else if (idSet > 0)
7646 videoUrl.AddOption("setid", idSet);
7647 else if (idTag > 0)
7648 videoUrl.AddOption("tagid", idTag);
7650 Filter filter;
7651 return GetMoviesByWhere(videoUrl.ToString(), filter, items, sortDescription, getDetails);
7654 bool CVideoDatabase::GetMoviesByWhere(const std::string& strBaseDir, const Filter &filter, CFileItemList& items, const SortDescription &sortDescription /* = SortDescription() */, int getDetails /* = VideoDbDetailsNone */)
7658 if (nullptr == m_pDB)
7659 return false;
7660 if (nullptr == m_pDS)
7661 return false;
7663 // parse the base path to get additional filters
7664 CVideoDbUrl videoUrl;
7665 Filter extFilter = filter;
7666 SortDescription sorting = sortDescription;
7667 if (!videoUrl.FromString(strBaseDir) || !GetFilter(videoUrl, extFilter, sorting))
7668 return false;
7670 int total = -1;
7672 std::string strSQL = "select %s from movie_view ";
7673 std::string strSQLExtra;
7674 if (!CDatabase::BuildSQL(strSQLExtra, extFilter, strSQLExtra))
7675 return false;
7677 // Apply the limiting directly here if there's no special sorting but limiting
7678 if (extFilter.limit.empty() && sorting.sortBy == SortByNone &&
7679 (sorting.limitStart > 0 || sorting.limitEnd > 0 ||
7680 (sorting.limitStart == 0 && sorting.limitEnd == 0)))
7682 total = (int)strtol(GetSingleValue(PrepareSQL(strSQL, "COUNT(1)") + strSQLExtra, m_pDS).c_str(), NULL, 10);
7683 strSQLExtra += DatabaseUtils::BuildLimitClause(sorting.limitEnd, sorting.limitStart);
7686 strSQL = PrepareSQL(strSQL, !extFilter.fields.empty() ? extFilter.fields.c_str() : "*") + strSQLExtra;
7688 int iRowsFound = RunQuery(strSQL);
7690 // store the total value of items as a property
7691 if (total < iRowsFound)
7692 total = iRowsFound;
7693 items.SetProperty("total", total);
7695 if (iRowsFound <= 0)
7696 return iRowsFound == 0;
7698 DatabaseResults results;
7699 results.reserve(iRowsFound);
7701 if (!SortUtils::SortFromDataset(sortDescription, MediaTypeMovie, m_pDS, results))
7702 return false;
7704 // get data from returned rows
7705 items.Reserve(results.size());
7706 const query_data &data = m_pDS->get_result_set().records;
7707 for (const auto &i : results)
7709 unsigned int targetRow = (unsigned int)i.at(FieldRow).asInteger();
7710 const dbiplus::sql_record* const record = data.at(targetRow);
7712 CVideoInfoTag movie = GetDetailsForMovie(record, getDetails);
7713 if (m_profileManager.GetMasterProfile().getLockMode() == LOCK_MODE_EVERYONE ||
7714 g_passwordManager.bMasterUser ||
7715 g_passwordManager.IsDatabasePathUnlocked(movie.m_strPath, *CMediaSourceSettings::GetInstance().GetSources("video")))
7717 CFileItemPtr pItem(new CFileItem(movie));
7719 CVideoDbUrl itemUrl = videoUrl;
7720 std::string path = std::to_string(movie.m_iDbId);
7721 itemUrl.AppendPath(path);
7722 pItem->SetPath(itemUrl.ToString());
7723 pItem->SetDynPath(movie.m_strFileNameAndPath);
7725 pItem->SetOverlayImage(CGUIListItem::ICON_OVERLAY_UNWATCHED,movie.GetPlayCount() > 0);
7726 items.Add(pItem);
7730 // cleanup
7731 m_pDS->close();
7732 return true;
7734 catch (...)
7736 CLog::Log(LOGERROR, "{} failed", __FUNCTION__);
7738 return false;
7741 bool CVideoDatabase::GetTvShowsNav(const std::string& strBaseDir, CFileItemList& items,
7742 int idGenre /* = -1 */, int idYear /* = -1 */, int idActor /* = -1 */, int idDirector /* = -1 */, int idStudio /* = -1 */, int idTag /* = -1 */,
7743 const SortDescription &sortDescription /* = SortDescription() */, int getDetails /* = VideoDbDetailsNone */)
7745 CVideoDbUrl videoUrl;
7746 if (!videoUrl.FromString(strBaseDir))
7747 return false;
7749 if (idGenre != -1)
7750 videoUrl.AddOption("genreid", idGenre);
7751 else if (idStudio != -1)
7752 videoUrl.AddOption("studioid", idStudio);
7753 else if (idDirector != -1)
7754 videoUrl.AddOption("directorid", idDirector);
7755 else if (idYear != -1)
7756 videoUrl.AddOption("year", idYear);
7757 else if (idActor != -1)
7758 videoUrl.AddOption("actorid", idActor);
7759 else if (idTag != -1)
7760 videoUrl.AddOption("tagid", idTag);
7762 Filter filter;
7763 if (!CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_VIDEOLIBRARY_SHOWEMPTYTVSHOWS))
7764 filter.AppendWhere("totalCount IS NOT NULL AND totalCount > 0");
7765 return GetTvShowsByWhere(videoUrl.ToString(), filter, items, sortDescription, getDetails);
7768 bool CVideoDatabase::GetTvShowsByWhere(const std::string& strBaseDir, const Filter &filter, CFileItemList& items, const SortDescription &sortDescription /* = SortDescription() */, int getDetails /* = VideoDbDetailsNone */)
7772 if (nullptr == m_pDB)
7773 return false;
7774 if (nullptr == m_pDS)
7775 return false;
7777 int total = -1;
7779 std::string strSQL = "SELECT %s FROM tvshow_view ";
7780 CVideoDbUrl videoUrl;
7781 std::string strSQLExtra;
7782 Filter extFilter = filter;
7783 SortDescription sorting = sortDescription;
7784 if (!BuildSQL(strBaseDir, strSQLExtra, extFilter, strSQLExtra, videoUrl, sorting))
7785 return false;
7787 // Apply the limiting directly here if there's no special sorting but limiting
7788 if (extFilter.limit.empty() && sorting.sortBy == SortByNone &&
7789 (sorting.limitStart > 0 || sorting.limitEnd > 0 ||
7790 (sorting.limitStart == 0 && sorting.limitEnd == 0)))
7792 total = (int)strtol(GetSingleValue(PrepareSQL(strSQL, "COUNT(1)") + strSQLExtra, m_pDS).c_str(), NULL, 10);
7793 strSQLExtra += DatabaseUtils::BuildLimitClause(sorting.limitEnd, sorting.limitStart);
7796 strSQL = PrepareSQL(strSQL, !extFilter.fields.empty() ? extFilter.fields.c_str() : "*") + strSQLExtra;
7798 int iRowsFound = RunQuery(strSQL);
7800 // store the total value of items as a property
7801 if (total < iRowsFound)
7802 total = iRowsFound;
7803 items.SetProperty("total", total);
7805 if (iRowsFound <= 0)
7806 return iRowsFound == 0;
7808 DatabaseResults results;
7809 results.reserve(iRowsFound);
7810 if (!SortUtils::SortFromDataset(sorting, MediaTypeTvShow, m_pDS, results))
7811 return false;
7813 // get data from returned rows
7814 items.Reserve(results.size());
7815 const query_data &data = m_pDS->get_result_set().records;
7816 for (const auto &i : results)
7818 unsigned int targetRow = (unsigned int)i.at(FieldRow).asInteger();
7819 const dbiplus::sql_record* const record = data.at(targetRow);
7821 CFileItemPtr pItem(new CFileItem());
7822 CVideoInfoTag movie = GetDetailsForTvShow(record, getDetails, pItem.get());
7823 if (m_profileManager.GetMasterProfile().getLockMode() == LOCK_MODE_EVERYONE ||
7824 g_passwordManager.bMasterUser ||
7825 g_passwordManager.IsDatabasePathUnlocked(movie.m_strPath, *CMediaSourceSettings::GetInstance().GetSources("video")))
7827 pItem->SetFromVideoInfoTag(movie);
7829 CVideoDbUrl itemUrl = videoUrl;
7830 std::string path = StringUtils::Format("{}/", record->at(0).get_asInt());
7831 itemUrl.AppendPath(path);
7832 pItem->SetPath(itemUrl.ToString());
7834 pItem->SetOverlayImage(CGUIListItem::ICON_OVERLAY_UNWATCHED, (pItem->GetVideoInfoTag()->GetPlayCount() > 0) && (pItem->GetVideoInfoTag()->m_iEpisode > 0));
7835 items.Add(pItem);
7839 // cleanup
7840 m_pDS->close();
7841 return true;
7843 catch (...)
7845 CLog::Log(LOGERROR, "{} failed", __FUNCTION__);
7847 return false;
7850 bool CVideoDatabase::GetEpisodesNav(const std::string& strBaseDir, CFileItemList& items, int idGenre, int idYear, int idActor, int idDirector, int idShow, int idSeason, const SortDescription &sortDescription /* = SortDescription() */, int getDetails /* = VideoDbDetailsNone */)
7852 CVideoDbUrl videoUrl;
7853 if (!videoUrl.FromString(strBaseDir))
7854 return false;
7856 if (idShow != -1)
7858 videoUrl.AddOption("tvshowid", idShow);
7859 if (idSeason >= 0)
7860 videoUrl.AddOption("season", idSeason);
7862 if (idGenre != -1)
7863 videoUrl.AddOption("genreid", idGenre);
7864 else if (idYear !=-1)
7865 videoUrl.AddOption("year", idYear);
7866 else if (idActor != -1)
7867 videoUrl.AddOption("actorid", idActor);
7869 else if (idYear != -1)
7870 videoUrl.AddOption("year", idYear);
7872 if (idDirector != -1)
7873 videoUrl.AddOption("directorid", idDirector);
7875 Filter filter;
7876 bool ret = GetEpisodesByWhere(videoUrl.ToString(), filter, items, false, sortDescription, getDetails);
7878 if (idSeason == -1 && idShow != -1)
7879 { // add any linked movies
7880 Filter movieFilter;
7881 movieFilter.join = PrepareSQL("join movielinktvshow on movielinktvshow.idMovie=movie_view.idMovie");
7882 movieFilter.where = PrepareSQL("movielinktvshow.idShow = %i", idShow);
7883 CFileItemList movieItems;
7884 GetMoviesByWhere("videodb://movies/titles/", movieFilter, movieItems);
7886 if (movieItems.Size() > 0)
7887 items.Append(movieItems);
7890 return ret;
7893 bool CVideoDatabase::GetEpisodesByWhere(const std::string& strBaseDir, const Filter &filter, CFileItemList& items, bool appendFullShowPath /* = true */, const SortDescription &sortDescription /* = SortDescription() */, int getDetails /* = VideoDbDetailsNone */)
7897 if (nullptr == m_pDB)
7898 return false;
7899 if (nullptr == m_pDS)
7900 return false;
7902 int total = -1;
7904 std::string strSQL = "select %s from episode_view ";
7905 CVideoDbUrl videoUrl;
7906 std::string strSQLExtra;
7907 Filter extFilter = filter;
7908 SortDescription sorting = sortDescription;
7909 if (!BuildSQL(strBaseDir, strSQLExtra, extFilter, strSQLExtra, videoUrl, sorting))
7910 return false;
7912 // Apply the limiting directly here if there's no special sorting but limiting
7913 if (extFilter.limit.empty() && sorting.sortBy == SortByNone &&
7914 (sorting.limitStart > 0 || sorting.limitEnd > 0 ||
7915 (sorting.limitStart == 0 && sorting.limitEnd == 0)))
7917 total = (int)strtol(GetSingleValue(PrepareSQL(strSQL, "COUNT(1)") + strSQLExtra, m_pDS).c_str(), NULL, 10);
7918 strSQLExtra += DatabaseUtils::BuildLimitClause(sorting.limitEnd, sorting.limitStart);
7921 strSQL = PrepareSQL(strSQL, !extFilter.fields.empty() ? extFilter.fields.c_str() : "*") + strSQLExtra;
7923 int iRowsFound = RunQuery(strSQL);
7925 // store the total value of items as a property
7926 if (total < iRowsFound)
7927 total = iRowsFound;
7928 items.SetProperty("total", total);
7930 if (iRowsFound <= 0)
7931 return iRowsFound == 0;
7933 DatabaseResults results;
7934 results.reserve(iRowsFound);
7935 if (!SortUtils::SortFromDataset(sorting, MediaTypeEpisode, m_pDS, results))
7936 return false;
7938 // get data from returned rows
7939 items.Reserve(results.size());
7940 CLabelFormatter formatter("%H. %T", "");
7942 const query_data &data = m_pDS->get_result_set().records;
7943 for (const auto &i : results)
7945 unsigned int targetRow = (unsigned int)i.at(FieldRow).asInteger();
7946 const dbiplus::sql_record* const record = data.at(targetRow);
7948 CVideoInfoTag episode = GetDetailsForEpisode(record, getDetails);
7949 if (m_profileManager.GetMasterProfile().getLockMode() == LOCK_MODE_EVERYONE ||
7950 g_passwordManager.bMasterUser ||
7951 g_passwordManager.IsDatabasePathUnlocked(episode.m_strPath, *CMediaSourceSettings::GetInstance().GetSources("video")))
7953 CFileItemPtr pItem(new CFileItem(episode));
7954 formatter.FormatLabel(pItem.get());
7956 int idEpisode = record->at(0).get_asInt();
7958 CVideoDbUrl itemUrl = videoUrl;
7959 std::string path;
7960 if (appendFullShowPath && videoUrl.GetItemType() != "episodes")
7961 path = StringUtils::Format("{}/{}/{}",
7962 record->at(VIDEODB_DETAILS_EPISODE_TVSHOW_ID).get_asInt(),
7963 episode.m_iSeason, idEpisode);
7964 else
7965 path = std::to_string(idEpisode);
7966 itemUrl.AppendPath(path);
7967 pItem->SetPath(itemUrl.ToString());
7968 pItem->SetDynPath(episode.m_strFileNameAndPath);
7970 pItem->SetOverlayImage(CGUIListItem::ICON_OVERLAY_UNWATCHED, episode.GetPlayCount() > 0);
7971 pItem->m_dateTime = episode.m_firstAired;
7972 items.Add(pItem);
7976 // cleanup
7977 m_pDS->close();
7978 return true;
7980 catch (...)
7982 CLog::Log(LOGERROR, "{} failed", __FUNCTION__);
7984 return false;
7987 bool CVideoDatabase::GetMusicVideosNav(const std::string& strBaseDir, CFileItemList& items, int idGenre, int idYear, int idArtist, int idDirector, int idStudio, int idAlbum, int idTag /* = -1 */, const SortDescription &sortDescription /* = SortDescription() */, int getDetails /* = VideoDbDetailsNone */)
7989 CVideoDbUrl videoUrl;
7990 if (!videoUrl.FromString(strBaseDir))
7991 return false;
7993 if (idGenre != -1)
7994 videoUrl.AddOption("genreid", idGenre);
7995 else if (idStudio != -1)
7996 videoUrl.AddOption("studioid", idStudio);
7997 else if (idDirector != -1)
7998 videoUrl.AddOption("directorid", idDirector);
7999 else if (idYear !=-1)
8000 videoUrl.AddOption("year", idYear);
8001 else if (idArtist != -1)
8002 videoUrl.AddOption("artistid", idArtist);
8003 else if (idTag != -1)
8004 videoUrl.AddOption("tagid", idTag);
8005 if (idAlbum != -1)
8006 videoUrl.AddOption("albumid", idAlbum);
8008 Filter filter;
8009 return GetMusicVideosByWhere(videoUrl.ToString(), filter, items, true, sortDescription, getDetails);
8012 bool CVideoDatabase::GetRecentlyAddedMoviesNav(const std::string& strBaseDir, CFileItemList& items, unsigned int limit /* = 0 */, int getDetails /* = VideoDbDetailsNone */)
8014 Filter filter;
8015 filter.order = "dateAdded desc, idMovie desc";
8016 filter.limit = PrepareSQL("%u", limit ? limit : CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_iVideoLibraryRecentlyAddedItems);
8017 return GetMoviesByWhere(strBaseDir, filter, items, SortDescription(), getDetails);
8020 bool CVideoDatabase::GetRecentlyAddedEpisodesNav(const std::string& strBaseDir, CFileItemList& items, unsigned int limit /* = 0 */, int getDetails /* = VideoDbDetailsNone */)
8022 Filter filter;
8023 filter.order = "dateAdded desc, idEpisode desc";
8024 filter.limit = PrepareSQL("%u", limit ? limit : CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_iVideoLibraryRecentlyAddedItems);
8025 return GetEpisodesByWhere(strBaseDir, filter, items, false, SortDescription(), getDetails);
8028 bool CVideoDatabase::GetRecentlyAddedMusicVideosNav(const std::string& strBaseDir, CFileItemList& items, unsigned int limit /* = 0 */, int getDetails /* = VideoDbDetailsNone */)
8030 Filter filter;
8031 filter.order = "dateAdded desc, idMVideo desc";
8032 filter.limit = PrepareSQL("%u", limit ? limit : CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_iVideoLibraryRecentlyAddedItems);
8033 return GetMusicVideosByWhere(strBaseDir, filter, items, true, SortDescription(), getDetails);
8036 bool CVideoDatabase::GetInProgressTvShowsNav(const std::string& strBaseDir, CFileItemList& items, unsigned int limit /* = 0 */, int getDetails /* = VideoDbDetailsNone */)
8038 Filter filter;
8039 filter.order = PrepareSQL("c%02d", VIDEODB_ID_TV_TITLE);
8040 filter.where = "watchedCount != 0 AND totalCount != watchedCount";
8041 return GetTvShowsByWhere(strBaseDir, filter, items, SortDescription(), getDetails);
8044 std::string CVideoDatabase::GetGenreById(int id)
8046 return GetSingleValue("genre", "name", PrepareSQL("genre_id=%i", id));
8049 std::string CVideoDatabase::GetCountryById(int id)
8051 return GetSingleValue("country", "name", PrepareSQL("country_id=%i", id));
8054 std::string CVideoDatabase::GetSetById(int id)
8056 return GetSingleValue("sets", "strSet", PrepareSQL("idSet=%i", id));
8059 std::string CVideoDatabase::GetTagById(int id)
8061 return GetSingleValue("tag", "name", PrepareSQL("tag_id = %i", id));
8064 std::string CVideoDatabase::GetPersonById(int id)
8066 return GetSingleValue("actor", "name", PrepareSQL("actor_id=%i", id));
8069 std::string CVideoDatabase::GetStudioById(int id)
8071 return GetSingleValue("studio", "name", PrepareSQL("studio_id=%i", id));
8074 std::string CVideoDatabase::GetTvShowTitleById(int id)
8076 return GetSingleValue("tvshow", PrepareSQL("c%02d", VIDEODB_ID_TV_TITLE), PrepareSQL("idShow=%i", id));
8079 std::string CVideoDatabase::GetMusicVideoAlbumById(int id)
8081 return GetSingleValue("musicvideo", PrepareSQL("c%02d", VIDEODB_ID_MUSICVIDEO_ALBUM), PrepareSQL("idMVideo=%i", id));
8084 bool CVideoDatabase::HasSets() const
8088 if (nullptr == m_pDB)
8089 return false;
8090 if (nullptr == m_pDS)
8091 return false;
8093 m_pDS->query("SELECT movie_view.idSet,COUNT(1) AS c FROM movie_view "
8094 "JOIN sets ON sets.idSet = movie_view.idSet "
8095 "GROUP BY movie_view.idSet HAVING c>1");
8097 bool bResult = (m_pDS->num_rows() > 0);
8098 m_pDS->close();
8099 return bResult;
8101 catch (...)
8103 CLog::Log(LOGERROR, "{} failed", __FUNCTION__);
8105 return false;
8108 int CVideoDatabase::GetTvShowForEpisode(int idEpisode)
8112 if (nullptr == m_pDB)
8113 return false;
8114 if (nullptr == m_pDS2)
8115 return false;
8117 // make sure we use m_pDS2, as this is called in loops using m_pDS
8118 std::string strSQL=PrepareSQL("select idShow from episode where idEpisode=%i", idEpisode);
8119 m_pDS2->query( strSQL );
8121 int result=-1;
8122 if (!m_pDS2->eof())
8123 result=m_pDS2->fv(0).get_asInt();
8124 m_pDS2->close();
8126 return result;
8128 catch (...)
8130 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, idEpisode);
8132 return false;
8135 int CVideoDatabase::GetSeasonForEpisode(int idEpisode)
8137 char column[5];
8138 sprintf(column, "c%0d", VIDEODB_ID_EPISODE_SEASON);
8139 std::string id = GetSingleValue("episode", column, PrepareSQL("idEpisode=%i", idEpisode));
8140 if (id.empty())
8141 return -1;
8142 return atoi(id.c_str());
8145 bool CVideoDatabase::HasContent()
8147 return (HasContent(VideoDbContentType::MOVIES) || HasContent(VideoDbContentType::TVSHOWS) ||
8148 HasContent(VideoDbContentType::MUSICVIDEOS));
8151 bool CVideoDatabase::HasContent(VideoDbContentType type)
8153 bool result = false;
8156 if (nullptr == m_pDB)
8157 return false;
8158 if (nullptr == m_pDS)
8159 return false;
8161 std::string sql;
8162 if (type == VideoDbContentType::MOVIES)
8163 sql = "select count(1) from movie";
8164 else if (type == VideoDbContentType::TVSHOWS)
8165 sql = "select count(1) from tvshow";
8166 else if (type == VideoDbContentType::MUSICVIDEOS)
8167 sql = "select count(1) from musicvideo";
8168 m_pDS->query( sql );
8170 if (!m_pDS->eof())
8171 result = (m_pDS->fv(0).get_asInt() > 0);
8173 m_pDS->close();
8175 catch (...)
8177 CLog::Log(LOGERROR, "{} failed", __FUNCTION__);
8179 return result;
8182 ScraperPtr CVideoDatabase::GetScraperForPath( const std::string& strPath )
8184 SScanSettings settings;
8185 return GetScraperForPath(strPath, settings);
8188 ScraperPtr CVideoDatabase::GetScraperForPath(const std::string& strPath, SScanSettings& settings)
8190 bool dummy;
8191 return GetScraperForPath(strPath, settings, dummy);
8194 ScraperPtr CVideoDatabase::GetScraperForPath(const std::string& strPath, SScanSettings& settings, bool& foundDirectly)
8196 foundDirectly = false;
8199 if (strPath.empty() || !m_pDB || !m_pDS)
8200 return ScraperPtr();
8202 ScraperPtr scraper;
8203 std::string strPath2;
8205 if (URIUtils::IsMultiPath(strPath))
8206 strPath2 = CMultiPathDirectory::GetFirstPath(strPath);
8207 else
8208 strPath2 = strPath;
8210 std::string strPath1 = URIUtils::GetDirectory(strPath2);
8211 int idPath = GetPathId(strPath1);
8213 if (idPath > -1)
8215 std::string strSQL = PrepareSQL(
8216 "SELECT path.strContent, path.strScraper, path.scanRecursive, path.useFolderNames, "
8217 "path.strSettings, path.noUpdate, path.exclude, path.allAudio FROM path WHERE idPath=%i",
8218 idPath);
8219 m_pDS->query( strSQL );
8222 int iFound = 1;
8223 CONTENT_TYPE content = CONTENT_NONE;
8224 if (!m_pDS->eof())
8225 { // path is stored in db
8227 settings.m_allExtAudio = m_pDS->fv("path.allAudio").get_asBool();
8229 if (m_pDS->fv("path.exclude").get_asBool())
8231 settings.exclude = true;
8232 m_pDS->close();
8233 return ScraperPtr();
8235 settings.exclude = false;
8237 // try and ascertain scraper for this path
8238 std::string strcontent = m_pDS->fv("path.strContent").get_asString();
8239 StringUtils::ToLower(strcontent);
8240 content = TranslateContent(strcontent);
8242 //FIXME paths stored should not have empty strContent
8243 //assert(content != CONTENT_NONE);
8244 std::string scraperID = m_pDS->fv("path.strScraper").get_asString();
8246 AddonPtr addon;
8247 if (!scraperID.empty() &&
8248 CServiceBroker::GetAddonMgr().GetAddon(scraperID, addon, ADDON::OnlyEnabled::CHOICE_YES))
8250 scraper = std::dynamic_pointer_cast<CScraper>(addon);
8251 if (!scraper)
8252 return ScraperPtr();
8254 // store this path's content & settings
8255 scraper->SetPathSettings(content, m_pDS->fv("path.strSettings").get_asString());
8256 settings.parent_name = m_pDS->fv("path.useFolderNames").get_asBool();
8257 settings.recurse = m_pDS->fv("path.scanRecursive").get_asInt();
8258 settings.noupdate = m_pDS->fv("path.noUpdate").get_asBool();
8262 if (content == CONTENT_NONE)
8263 { // this path is not saved in db
8264 // we must drill up until a scraper is configured
8265 std::string strParent;
8266 while (URIUtils::GetParentPath(strPath1, strParent))
8268 iFound++;
8270 std::string strSQL =
8271 PrepareSQL("SELECT path.strContent, path.strScraper, path.scanRecursive, "
8272 "path.useFolderNames, path.strSettings, path.noUpdate, path.exclude, "
8273 "path.allAudio FROM path WHERE strPath='%s'",
8274 strParent.c_str());
8275 m_pDS->query(strSQL);
8277 CONTENT_TYPE content = CONTENT_NONE;
8278 if (!m_pDS->eof())
8280 settings.m_allExtAudio = m_pDS->fv("path.allAudio").get_asBool();
8281 std::string strcontent = m_pDS->fv("path.strContent").get_asString();
8282 StringUtils::ToLower(strcontent);
8283 if (m_pDS->fv("path.exclude").get_asBool())
8285 settings.exclude = true;
8286 scraper.reset();
8287 m_pDS->close();
8288 break;
8291 content = TranslateContent(strcontent);
8293 AddonPtr addon;
8294 if (content != CONTENT_NONE &&
8295 CServiceBroker::GetAddonMgr().GetAddon(m_pDS->fv("path.strScraper").get_asString(),
8296 addon, ADDON::OnlyEnabled::CHOICE_YES))
8298 scraper = std::dynamic_pointer_cast<CScraper>(addon);
8299 scraper->SetPathSettings(content, m_pDS->fv("path.strSettings").get_asString());
8300 settings.parent_name = m_pDS->fv("path.useFolderNames").get_asBool();
8301 settings.recurse = m_pDS->fv("path.scanRecursive").get_asInt();
8302 settings.noupdate = m_pDS->fv("path.noUpdate").get_asBool();
8303 settings.exclude = false;
8304 break;
8307 strPath1 = strParent;
8310 m_pDS->close();
8312 if (!scraper || scraper->Content() == CONTENT_NONE)
8313 return ScraperPtr();
8315 if (scraper->Content() == CONTENT_TVSHOWS)
8317 settings.recurse = 0;
8318 if(settings.parent_name) // single show
8320 settings.parent_name_root = settings.parent_name = (iFound == 1);
8322 else // show root
8324 settings.parent_name_root = settings.parent_name = (iFound == 2);
8327 else if (scraper->Content() == CONTENT_MOVIES)
8329 settings.recurse = settings.recurse - (iFound-1);
8330 settings.parent_name_root = settings.parent_name && (!settings.recurse || iFound > 1);
8332 else if (scraper->Content() == CONTENT_MUSICVIDEOS)
8334 settings.recurse = settings.recurse - (iFound-1);
8335 settings.parent_name_root = settings.parent_name && (!settings.recurse || iFound > 1);
8337 else
8339 iFound = 0;
8340 return ScraperPtr();
8342 foundDirectly = (iFound == 1);
8343 return scraper;
8345 catch (...)
8347 CLog::Log(LOGERROR, "{} failed", __FUNCTION__);
8349 return ScraperPtr();
8352 bool CVideoDatabase::GetUseAllExternalAudioForVideo(const std::string& videoPath)
8354 // Find longest configured source path for given video path
8355 std::string strSQL = PrepareSQL("SELECT allAudio FROM path WHERE allAudio IS NOT NULL AND "
8356 "instr('%s', strPath) = 1 ORDER BY length(strPath) DESC LIMIT 1",
8357 videoPath.c_str());
8358 m_pDS->query(strSQL);
8360 if (!m_pDS->eof())
8361 return m_pDS->fv("allAudio").get_asBool();
8363 return false;
8366 std::string CVideoDatabase::GetContentForPath(const std::string& strPath)
8368 SScanSettings settings;
8369 bool foundDirectly = false;
8370 ScraperPtr scraper = GetScraperForPath(strPath, settings, foundDirectly);
8371 if (scraper)
8373 if (scraper->Content() == CONTENT_TVSHOWS)
8375 // check for episodes or seasons. Assumptions are:
8376 // 1. if episodes are in the path then we're in episodes.
8377 // 2. if no episodes are found, and content was set directly on this path, then we're in shows.
8378 // 3. if no episodes are found, and content was not set directly on this path, we're in seasons (assumes tvshows/seasons/episodes)
8379 std::string sql = "SELECT COUNT(*) FROM episode_view ";
8381 if (foundDirectly)
8382 sql += PrepareSQL("WHERE strPath = '%s'", strPath.c_str());
8383 else
8384 sql += PrepareSQL("WHERE strPath LIKE '%s%%'", strPath.c_str());
8386 m_pDS->query( sql );
8387 if (m_pDS->num_rows() && m_pDS->fv(0).get_asInt() > 0)
8388 return "episodes";
8389 return foundDirectly ? "tvshows" : "seasons";
8391 return TranslateContent(scraper->Content());
8393 return "";
8396 void CVideoDatabase::GetMovieGenresByName(const std::string& strSearch, CFileItemList& items)
8398 std::string strSQL;
8402 if (nullptr == m_pDB)
8403 return;
8404 if (nullptr == m_pDS)
8405 return;
8407 if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
8408 strSQL=PrepareSQL("SELECT genre.genre_id, genre.name, path.strPath FROM genre INNER JOIN genre_link ON genre_link.genre_id = genre.genre_id INNER JOIN movie ON (genre_link.media_type='movie' = genre_link.media_id=movie.idMovie) INNER JOIN files ON files.idFile=movie.idFile INNER JOIN path ON path.idPath=files.idPath WHERE genre.name LIKE '%%%s%%'",strSearch.c_str());
8409 else
8410 strSQL=PrepareSQL("SELECT DISTINCT genre.genre_id, genre.name FROM genre INNER JOIN genre_link ON genre_link.genre_id=genre.genre_id WHERE genre_link.media_type='movie' AND name LIKE '%%%s%%'", strSearch.c_str());
8411 m_pDS->query( strSQL );
8413 while (!m_pDS->eof())
8415 if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
8416 if (!g_passwordManager.IsDatabasePathUnlocked(m_pDS->fv("path.strPath").get_asString(),
8417 *CMediaSourceSettings::GetInstance().GetSources("video")))
8419 m_pDS->next();
8420 continue;
8423 CFileItemPtr pItem(new CFileItem(m_pDS->fv(1).get_asString()));
8424 std::string strDir = StringUtils::Format("{}/", m_pDS->fv(0).get_asInt());
8425 pItem->SetPath("videodb://movies/genres/"+ strDir);
8426 pItem->m_bIsFolder=true;
8427 items.Add(pItem);
8428 m_pDS->next();
8430 m_pDS->close();
8432 catch (...)
8434 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, strSQL);
8438 void CVideoDatabase::GetMovieCountriesByName(const std::string& strSearch, CFileItemList& items)
8440 std::string strSQL;
8444 if (nullptr == m_pDB)
8445 return;
8446 if (nullptr == m_pDS)
8447 return;
8449 if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
8450 strSQL=PrepareSQL("SELECT country.country_id, country.name, path.strPath FROM country INNER JOIN country_link ON country_link.country_id=country.country_id INNER JOIN movie ON country_link.media_id=movie.idMovie INNER JOIN files ON files.idFile=movie.idFile INNER JOIN path ON path.idPath=files.idPath WHERE country_link.media_type='movie' AND country.name LIKE '%%%s%%'", strSearch.c_str());
8451 else
8452 strSQL=PrepareSQL("SELECT DISTINCT country.country_id, country.name FROM country INNER JOIN country_link ON country_link.country_id=country.country_id WHERE country_link.media_type='movie' AND name like '%%%s%%'", strSearch.c_str());
8453 m_pDS->query( strSQL );
8455 while (!m_pDS->eof())
8457 if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
8458 if (!g_passwordManager.IsDatabasePathUnlocked(m_pDS->fv("path.strPath").get_asString(),
8459 *CMediaSourceSettings::GetInstance().GetSources("video")))
8461 m_pDS->next();
8462 continue;
8465 CFileItemPtr pItem(new CFileItem(m_pDS->fv(1).get_asString()));
8466 std::string strDir = StringUtils::Format("{}/", m_pDS->fv(0).get_asInt());
8467 pItem->SetPath("videodb://movies/genres/"+ strDir);
8468 pItem->m_bIsFolder=true;
8469 items.Add(pItem);
8470 m_pDS->next();
8472 m_pDS->close();
8474 catch (...)
8476 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, strSQL);
8480 void CVideoDatabase::GetTvShowGenresByName(const std::string& strSearch, CFileItemList& items)
8482 std::string strSQL;
8486 if (nullptr == m_pDB)
8487 return;
8488 if (nullptr == m_pDS)
8489 return;
8491 if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
8492 strSQL=PrepareSQL("SELECT genre.genre_id, genre.name, path.strPath FROM genre INNER JOIN genre_link ON genre_link.genre_id=genre.genre_id INNER JOIN tvshow ON genre_link.media_id=tvshow.idShow INNER JOIN tvshowlinkpath ON tvshowlinkpath.idShow=tvshow.idShow INNER JOIN path ON path.idPath=tvshowlinkpath.idPath WHERE genre_link.media_type='tvshow' AND genre.name LIKE '%%%s%%'", strSearch.c_str());
8493 else
8494 strSQL=PrepareSQL("SELECT DISTINCT genre.genre_id, genre.name FROM genre INNER JOIN genre_link ON genre_link.genre_id=genre.genre_id WHERE genre_link.media_type='tvshow' AND name LIKE '%%%s%%'", strSearch.c_str());
8495 m_pDS->query( strSQL );
8497 while (!m_pDS->eof())
8499 if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
8500 if (!g_passwordManager.IsDatabasePathUnlocked(m_pDS->fv("path.strPath").get_asString(),*CMediaSourceSettings::GetInstance().GetSources("video")))
8502 m_pDS->next();
8503 continue;
8506 CFileItemPtr pItem(new CFileItem(m_pDS->fv(1).get_asString()));
8507 std::string strDir = StringUtils::Format("{}/", m_pDS->fv(0).get_asInt());
8508 pItem->SetPath("videodb://tvshows/genres/"+ strDir);
8509 pItem->m_bIsFolder=true;
8510 items.Add(pItem);
8511 m_pDS->next();
8513 m_pDS->close();
8515 catch (...)
8517 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, strSQL);
8521 void CVideoDatabase::GetMovieActorsByName(const std::string& strSearch, CFileItemList& items)
8523 std::string strSQL;
8527 if (nullptr == m_pDB)
8528 return;
8529 if (nullptr == m_pDS)
8530 return;
8532 if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
8533 strSQL=PrepareSQL("SELECT actor.actor_id, actor.name, path.strPath FROM actor INNER JOIN actor_link ON actor_link.actor_id=actor.actor_id INNER JOIN movie ON actor_link.media_id=movie.idMovie INNER JOIN files ON files.idFile=movie.idFile INNER JOIN path ON path.idPath=files.idPath WHERE actor_link.media_type='movie' AND actor.name LIKE '%%%s%%'", strSearch.c_str());
8534 else
8535 strSQL=PrepareSQL("SELECT DISTINCT actor.actor_id, actor.name FROM actor INNER JOIN actor_link ON actor_link.actor_id=actor.actor_id INNER JOIN movie ON actor_link.media_id=movie.idMovie WHERE actor_link.media_type='movie' AND actor.name LIKE '%%%s%%'", strSearch.c_str());
8536 m_pDS->query( strSQL );
8538 while (!m_pDS->eof())
8540 if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
8541 if (!g_passwordManager.IsDatabasePathUnlocked(m_pDS->fv("path.strPath").get_asString(),*CMediaSourceSettings::GetInstance().GetSources("video")))
8543 m_pDS->next();
8544 continue;
8547 CFileItemPtr pItem(new CFileItem(m_pDS->fv(1).get_asString()));
8548 std::string strDir = StringUtils::Format("{}/", m_pDS->fv(0).get_asInt());
8549 pItem->SetPath("videodb://movies/actors/"+ strDir);
8550 pItem->m_bIsFolder=true;
8551 items.Add(pItem);
8552 m_pDS->next();
8554 m_pDS->close();
8556 catch (...)
8558 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, strSQL);
8562 void CVideoDatabase::GetTvShowsActorsByName(const std::string& strSearch, CFileItemList& items)
8564 std::string strSQL;
8568 if (nullptr == m_pDB)
8569 return;
8570 if (nullptr == m_pDS)
8571 return;
8573 if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
8574 strSQL=PrepareSQL("SELECT actor.actor_id, actor.name, path.strPath FROM actor INNER JOIN actor_link ON actor_link.actor_id=actor.actor_id INNER JOIN tvshow ON actor_link.media_id=tvshow.idShow INNER JOIN tvshowlinkpath ON tvshowlinkpath.idPath=tvshow.idShow INNER JOIN path ON path.idPath=tvshowlinkpath.idPath WHERE actor_link.media_type='tvshow' AND actor.name LIKE '%%%s%%'", strSearch.c_str());
8575 else
8576 strSQL=PrepareSQL("SELECT DISTINCT actor.actor_id, actor.name FROM actor INNER JOIN actor_link ON actor_link.actor_id=actor.actor_id INNER JOIN tvshow ON actor_link.media_id=tvshow.idShow WHERE actor_link.media_type='tvshow' AND actor.name LIKE '%%%s%%'",strSearch.c_str());
8577 m_pDS->query( strSQL );
8579 while (!m_pDS->eof())
8581 if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
8582 if (!g_passwordManager.IsDatabasePathUnlocked(m_pDS->fv("path.strPath").get_asString(),*CMediaSourceSettings::GetInstance().GetSources("video")))
8584 m_pDS->next();
8585 continue;
8588 CFileItemPtr pItem(new CFileItem(m_pDS->fv(1).get_asString()));
8589 std::string strDir = StringUtils::Format("{}/", m_pDS->fv(0).get_asInt());
8590 pItem->SetPath("videodb://tvshows/actors/"+ strDir);
8591 pItem->m_bIsFolder=true;
8592 items.Add(pItem);
8593 m_pDS->next();
8595 m_pDS->close();
8597 catch (...)
8599 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, strSQL);
8603 void CVideoDatabase::GetMusicVideoArtistsByName(const std::string& strSearch, CFileItemList& items)
8605 std::string strSQL;
8609 if (nullptr == m_pDB)
8610 return;
8611 if (nullptr == m_pDS)
8612 return;
8614 std::string strLike;
8615 if (!strSearch.empty())
8616 strLike = "and actor.name like '%%%s%%'";
8617 if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
8618 strSQL=PrepareSQL("SELECT actor.actor_id, actor.name, path.strPath FROM actor INNER JOIN actor_link ON actor_link.actor_id=actor.actor_id INNER JOIN musicvideo ON actor_link.media_id=musicvideo.idMVideo INNER JOIN files ON files.idFile=musicvideo.idFile INNER JOIN path ON path.idPath=files.idPath WHERE actor_link.media_type='musicvideo' "+strLike, strSearch.c_str());
8619 else
8620 strSQL=PrepareSQL("SELECT DISTINCT actor.actor_id, actor.name from actor INNER JOIN actor_link ON actor_link.actor_id=actor.actor_id WHERE actor_link.media_type='musicvideo' "+strLike,strSearch.c_str());
8621 m_pDS->query( strSQL );
8623 while (!m_pDS->eof())
8625 if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
8626 if (!g_passwordManager.IsDatabasePathUnlocked(m_pDS->fv("path.strPath").get_asString(),*CMediaSourceSettings::GetInstance().GetSources("video")))
8628 m_pDS->next();
8629 continue;
8632 CFileItemPtr pItem(new CFileItem(m_pDS->fv(1).get_asString()));
8633 std::string strDir = StringUtils::Format("{}/", m_pDS->fv(0).get_asInt());
8634 pItem->SetPath("videodb://musicvideos/artists/"+ strDir);
8635 pItem->m_bIsFolder=true;
8636 items.Add(pItem);
8637 m_pDS->next();
8639 m_pDS->close();
8641 catch (...)
8643 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, strSQL);
8647 void CVideoDatabase::GetMusicVideoGenresByName(const std::string& strSearch, CFileItemList& items)
8649 std::string strSQL;
8653 if (nullptr == m_pDB)
8654 return;
8655 if (nullptr == m_pDS)
8656 return;
8658 if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
8659 strSQL=PrepareSQL("SELECT genre.genre_id, genre.name, path.strPath FROM genre INNER JOIN genre_link ON genre_link.genre_id=genre.genre_id INNER JOIN musicvideo ON genre_link.media_id=musicvideo.idMVideo INNER JOIN files ON files.idFile=musicvideo.idFile INNER JOIN path ON path.idPath=files.idPath WHERE genre_link.media_type='musicvideo' AND genre.name LIKE '%%%s%%'", strSearch.c_str());
8660 else
8661 strSQL=PrepareSQL("SELECT DISTINCT genre.genre_id, genre.name FROM genre INNER JOIN genre_link ON genre_link.genre_id=genre.genre_id WHERE genre_link.media_type='musicvideo' AND genre.name LIKE '%%%s%%'", strSearch.c_str());
8662 m_pDS->query( strSQL );
8664 while (!m_pDS->eof())
8666 if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
8667 if (!g_passwordManager.IsDatabasePathUnlocked(m_pDS->fv("path.strPath").get_asString(),*CMediaSourceSettings::GetInstance().GetSources("video")))
8669 m_pDS->next();
8670 continue;
8673 CFileItemPtr pItem(new CFileItem(m_pDS->fv(1).get_asString()));
8674 std::string strDir = StringUtils::Format("{}/", m_pDS->fv(0).get_asInt());
8675 pItem->SetPath("videodb://musicvideos/genres/"+ strDir);
8676 pItem->m_bIsFolder=true;
8677 items.Add(pItem);
8678 m_pDS->next();
8680 m_pDS->close();
8682 catch (...)
8684 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, strSQL);
8688 void CVideoDatabase::GetMusicVideoAlbumsByName(const std::string& strSearch, CFileItemList& items)
8690 std::string strSQL;
8694 if (nullptr == m_pDB)
8695 return;
8696 if (nullptr == m_pDS)
8697 return;
8699 strSQL = StringUtils::Format("SELECT DISTINCT"
8700 " musicvideo.c{:02},"
8701 " musicvideo.idMVideo,"
8702 " path.strPath"
8703 " FROM"
8704 " musicvideo"
8705 " JOIN files ON"
8706 " files.idFile=musicvideo.idFile"
8707 " JOIN path ON"
8708 " path.idPath=files.idPath",
8709 VIDEODB_ID_MUSICVIDEO_ALBUM);
8710 if (!strSearch.empty())
8711 strSQL += PrepareSQL(" WHERE musicvideo.c%02d like '%%%s%%'",VIDEODB_ID_MUSICVIDEO_ALBUM, strSearch.c_str());
8713 m_pDS->query( strSQL );
8715 while (!m_pDS->eof())
8717 if (m_pDS->fv(0).get_asString().empty())
8719 m_pDS->next();
8720 continue;
8723 if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
8724 if (!g_passwordManager.IsDatabasePathUnlocked(m_pDS->fv(2).get_asString(),*CMediaSourceSettings::GetInstance().GetSources("video")))
8726 m_pDS->next();
8727 continue;
8730 CFileItemPtr pItem(new CFileItem(m_pDS->fv(0).get_asString()));
8731 std::string strDir = std::to_string(m_pDS->fv(1).get_asInt());
8732 pItem->SetPath("videodb://musicvideos/titles/"+ strDir);
8733 pItem->m_bIsFolder=false;
8734 items.Add(pItem);
8735 m_pDS->next();
8737 m_pDS->close();
8739 catch (...)
8741 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, strSQL);
8745 void CVideoDatabase::GetMusicVideosByAlbum(const std::string& strSearch, CFileItemList& items)
8747 std::string strSQL;
8751 if (nullptr == m_pDB)
8752 return;
8753 if (nullptr == m_pDS)
8754 return;
8756 if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
8757 strSQL = PrepareSQL("SELECT musicvideo.idMVideo, musicvideo.c%02d,musicvideo.c%02d, path.strPath FROM musicvideo INNER JOIN files ON files.idFile=musicvideo.idFile INNER JOIN path ON path.idPath=files.idPath WHERE musicvideo.c%02d LIKE '%%%s%%'", VIDEODB_ID_MUSICVIDEO_ALBUM, VIDEODB_ID_MUSICVIDEO_TITLE, VIDEODB_ID_MUSICVIDEO_ALBUM, strSearch.c_str());
8758 else
8759 strSQL = PrepareSQL("select musicvideo.idMVideo,musicvideo.c%02d,musicvideo.c%02d from musicvideo where musicvideo.c%02d like '%%%s%%'",VIDEODB_ID_MUSICVIDEO_ALBUM,VIDEODB_ID_MUSICVIDEO_TITLE,VIDEODB_ID_MUSICVIDEO_ALBUM,strSearch.c_str());
8760 m_pDS->query( strSQL );
8762 while (!m_pDS->eof())
8764 if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
8765 if (!g_passwordManager.IsDatabasePathUnlocked(m_pDS->fv("path.strPath").get_asString(),*CMediaSourceSettings::GetInstance().GetSources("video")))
8767 m_pDS->next();
8768 continue;
8771 CFileItemPtr pItem(new CFileItem(m_pDS->fv(1).get_asString()+" - "+m_pDS->fv(2).get_asString()));
8772 std::string strDir =
8773 StringUtils::Format("3/2/{}", m_pDS->fv("musicvideo.idMVideo").get_asInt());
8775 pItem->SetPath("videodb://"+ strDir);
8776 pItem->m_bIsFolder=false;
8777 items.Add(pItem);
8778 m_pDS->next();
8780 m_pDS->close();
8782 catch (...)
8784 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, strSQL);
8788 bool CVideoDatabase::GetMusicVideosByWhere(const std::string &baseDir, const Filter &filter, CFileItemList &items, bool checkLocks /*= true*/, const SortDescription &sortDescription /* = SortDescription() */, int getDetails /* = VideoDbDetailsNone */)
8793 if (nullptr == m_pDB)
8794 return false;
8795 if (nullptr == m_pDS)
8796 return false;
8798 int total = -1;
8800 std::string strSQL = "select %s from musicvideo_view ";
8801 CVideoDbUrl videoUrl;
8802 if (!videoUrl.FromString(baseDir))
8803 return false;
8805 std::string strSQLExtra;
8806 const CUrlOptions::UrlOptions& options = videoUrl.GetOptions();
8807 std::string strArtist;
8808 int idArtist = -1;
8809 // If we have an artistid then get the artist name and use that to fix up the path displayed in
8810 // the GUI (musicvideos/artist-name instead of musicvideos/artistid)
8811 auto option = options.find("artistid");
8812 if (option != options.end())
8814 idArtist = option->second.asInteger();
8815 strArtist = GetSingleValue(
8816 PrepareSQL("SELECT name FROM actor where actor_id = '%i'", idArtist), m_pDS)
8817 .c_str();
8819 Filter extFilter = filter;
8820 SortDescription sorting = sortDescription;
8821 if (!BuildSQL(baseDir, strSQLExtra, extFilter, strSQLExtra, videoUrl, sorting))
8822 return false;
8824 // Apply the limiting directly here if there's no special sorting but limiting
8825 if (extFilter.limit.empty() && sorting.sortBy == SortByNone &&
8826 (sorting.limitStart > 0 || sorting.limitEnd > 0 ||
8827 (sorting.limitStart == 0 && sorting.limitEnd == 0)))
8829 total = (int)strtol(GetSingleValue(PrepareSQL(strSQL, "COUNT(1)") + strSQLExtra, m_pDS).c_str(), NULL, 10);
8830 strSQLExtra += DatabaseUtils::BuildLimitClause(sorting.limitEnd, sorting.limitStart);
8833 strSQL = PrepareSQL(strSQL, !extFilter.fields.empty() ? extFilter.fields.c_str() : "*") + strSQLExtra;
8835 int iRowsFound = RunQuery(strSQL);
8837 // store the total value of items as a property
8838 if (total < iRowsFound)
8839 total = iRowsFound;
8840 items.SetProperty("total", total);
8842 if (iRowsFound <= 0)
8843 return iRowsFound == 0;
8845 DatabaseResults results;
8846 results.reserve(iRowsFound);
8847 if (!SortUtils::SortFromDataset(sorting, MediaTypeMusicVideo, m_pDS, results))
8848 return false;
8850 // get data from returned rows
8851 items.Reserve(results.size());
8852 // get songs from returned subtable
8853 const query_data &data = m_pDS->get_result_set().records;
8854 for (const auto &i : results)
8856 unsigned int targetRow = (unsigned int)i.at(FieldRow).asInteger();
8857 const dbiplus::sql_record* const record = data.at(targetRow);
8859 CVideoInfoTag musicvideo = GetDetailsForMusicVideo(record, getDetails);
8860 if (!checkLocks || m_profileManager.GetMasterProfile().getLockMode() == LOCK_MODE_EVERYONE || g_passwordManager.bMasterUser ||
8861 g_passwordManager.IsDatabasePathUnlocked(musicvideo.m_strPath, *CMediaSourceSettings::GetInstance().GetSources("video")))
8863 CFileItemPtr item(new CFileItem(musicvideo));
8865 CVideoDbUrl itemUrl = videoUrl;
8866 std::string path = std::to_string(record->at(0).get_asInt());
8867 itemUrl.AppendPath(path);
8868 item->SetPath(itemUrl.ToString());
8870 item->SetOverlayImage(CGUIListItem::ICON_OVERLAY_UNWATCHED, musicvideo.GetPlayCount() > 0);
8871 items.Add(item);
8875 // cleanup
8876 m_pDS->close();
8877 if (!strArtist.empty())
8878 items.SetProperty("customtitle", strArtist);
8879 return true;
8881 catch (...)
8883 CLog::Log(LOGERROR, "{} failed", __FUNCTION__);
8885 return false;
8888 unsigned int CVideoDatabase::GetRandomMusicVideoIDs(const std::string& strWhere, std::vector<std::pair<int,int> > &songIDs)
8892 if (nullptr == m_pDB)
8893 return 0;
8894 if (nullptr == m_pDS)
8895 return 0;
8897 std::string strSQL = "select distinct idMVideo from musicvideo_view";
8898 if (!strWhere.empty())
8899 strSQL += " where " + strWhere;
8900 strSQL += PrepareSQL(" ORDER BY RANDOM()");
8902 if (!m_pDS->query(strSQL)) return 0;
8903 songIDs.clear();
8904 if (m_pDS->num_rows() == 0)
8906 m_pDS->close();
8907 return 0;
8909 songIDs.reserve(m_pDS->num_rows());
8910 while (!m_pDS->eof())
8912 songIDs.push_back(std::make_pair<int,int>(2,m_pDS->fv(0).get_asInt()));
8913 m_pDS->next();
8914 } // cleanup
8915 m_pDS->close();
8916 return songIDs.size();
8918 catch (...)
8920 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, strWhere);
8922 return 0;
8925 int CVideoDatabase::GetMatchingMusicVideo(const std::string& strArtist, const std::string& strAlbum, const std::string& strTitle)
8929 if (nullptr == m_pDB)
8930 return -1;
8931 if (nullptr == m_pDS)
8932 return -1;
8934 std::string strSQL;
8935 if (strAlbum.empty() && strTitle.empty())
8936 { // we want to return matching artists only
8937 if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
8938 strSQL=PrepareSQL("SELECT DISTINCT actor.actor_id, path.strPath FROM actor INNER JOIN actor_link ON actor_link.actor_id=actor.actor_id INNER JOIN musicvideo ON actor_link.media_id=musicvideo.idMVideo INNER JOIN files ON files.idFile=musicvideo.idFile INNER JOIN path ON path.idPath=files.idPath WHERE actor_link.media_type='musicvideo' AND actor.name like '%s'", strArtist.c_str());
8939 else
8940 strSQL=PrepareSQL("SELECT DISTINCT actor.actor_id FROM actor INNER JOIN actor_link ON actor_link.actor_id=actor.actor_id WHERE actor_link.media_type='musicvideo' AND actor.name LIKE '%s'", strArtist.c_str());
8942 else
8943 { // we want to return the matching musicvideo
8944 if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
8945 strSQL = PrepareSQL("SELECT musicvideo.idMVideo FROM actor INNER JOIN actor_link ON actor_link.actor_id=actor.actor_id INNER JOIN musicvideo ON actor_link.media_id=musicvideo.idMVideo INNER JOIN files ON files.idFile=musicvideo.idFile INNER JOIN path ON path.idPath=files.idPath WHERE actor_link.media_type='musicvideo' AND musicvideo.c%02d LIKE '%s' AND musicvideo.c%02d LIKE '%s' AND actor.name LIKE '%s'", VIDEODB_ID_MUSICVIDEO_ALBUM, strAlbum.c_str(), VIDEODB_ID_MUSICVIDEO_TITLE, strTitle.c_str(), strArtist.c_str());
8946 else
8947 strSQL = PrepareSQL("select musicvideo.idMVideo from musicvideo join actor_link on actor_link.media_id=musicvideo.idMVideo AND actor_link.media_type='musicvideo' join actor on actor.actor_id=actor_link.actor_id where musicvideo.c%02d like '%s' and musicvideo.c%02d like '%s' and actor.name like '%s'",VIDEODB_ID_MUSICVIDEO_ALBUM,strAlbum.c_str(),VIDEODB_ID_MUSICVIDEO_TITLE,strTitle.c_str(),strArtist.c_str());
8949 m_pDS->query( strSQL );
8951 if (m_pDS->eof())
8952 return -1;
8954 if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
8955 if (!g_passwordManager.IsDatabasePathUnlocked(m_pDS->fv("path.strPath").get_asString(),*CMediaSourceSettings::GetInstance().GetSources("video")))
8957 m_pDS->close();
8958 return -1;
8961 int lResult = m_pDS->fv(0).get_asInt();
8962 m_pDS->close();
8963 return lResult;
8965 catch (...)
8967 CLog::Log(LOGERROR, "{} failed", __FUNCTION__);
8969 return -1;
8972 void CVideoDatabase::GetMoviesByName(const std::string& strSearch, CFileItemList& items)
8974 std::string strSQL;
8978 if (nullptr == m_pDB)
8979 return;
8980 if (nullptr == m_pDS)
8981 return;
8983 if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
8984 strSQL = PrepareSQL("SELECT movie.idMovie, movie.c%02d, path.strPath, movie.idSet FROM movie "
8985 "INNER JOIN files ON files.idFile=movie.idFile INNER JOIN path ON "
8986 "path.idPath=files.idPath "
8987 "WHERE movie.c%02d LIKE '%%%s%%' OR movie.c%02d LIKE '%%%s%%'",
8988 VIDEODB_ID_TITLE, VIDEODB_ID_TITLE, strSearch.c_str(),
8989 VIDEODB_ID_ORIGINALTITLE, strSearch.c_str());
8990 else
8991 strSQL = PrepareSQL("SELECT movie.idMovie,movie.c%02d, movie.idSet FROM movie WHERE "
8992 "movie.c%02d like '%%%s%%' OR movie.c%02d LIKE '%%%s%%'",
8993 VIDEODB_ID_TITLE, VIDEODB_ID_TITLE, strSearch.c_str(),
8994 VIDEODB_ID_ORIGINALTITLE, strSearch.c_str());
8995 m_pDS->query( strSQL );
8997 while (!m_pDS->eof())
8999 if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
9000 if (!g_passwordManager.IsDatabasePathUnlocked(m_pDS->fv("path.strPath").get_asString(),*CMediaSourceSettings::GetInstance().GetSources("video")))
9002 m_pDS->next();
9003 continue;
9006 int movieId = m_pDS->fv("movie.idMovie").get_asInt();
9007 int setId = m_pDS->fv("movie.idSet").get_asInt();
9008 CFileItemPtr pItem(new CFileItem(m_pDS->fv(1).get_asString()));
9009 std::string path;
9010 if (setId <= 0 || !CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_VIDEOLIBRARY_GROUPMOVIESETS))
9011 path = StringUtils::Format("videodb://movies/titles/{}", movieId);
9012 else
9013 path = StringUtils::Format("videodb://movies/sets/{}/{}", setId, movieId);
9014 pItem->SetPath(path);
9015 pItem->m_bIsFolder=false;
9016 items.Add(pItem);
9017 m_pDS->next();
9019 m_pDS->close();
9021 catch (...)
9023 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, strSQL);
9027 void CVideoDatabase::GetTvShowsByName(const std::string& strSearch, CFileItemList& items)
9029 std::string strSQL;
9033 if (nullptr == m_pDB)
9034 return;
9035 if (nullptr == m_pDS)
9036 return;
9038 if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
9039 strSQL = PrepareSQL("SELECT tvshow.idShow, tvshow.c%02d, path.strPath FROM tvshow INNER JOIN tvshowlinkpath ON tvshowlinkpath.idShow=tvshow.idShow INNER JOIN path ON path.idPath=tvshowlinkpath.idPath WHERE tvshow.c%02d LIKE '%%%s%%'", VIDEODB_ID_TV_TITLE, VIDEODB_ID_TV_TITLE, strSearch.c_str());
9040 else
9041 strSQL = PrepareSQL("select tvshow.idShow,tvshow.c%02d from tvshow where tvshow.c%02d like '%%%s%%'",VIDEODB_ID_TV_TITLE,VIDEODB_ID_TV_TITLE,strSearch.c_str());
9042 m_pDS->query( strSQL );
9044 while (!m_pDS->eof())
9046 if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
9047 if (!g_passwordManager.IsDatabasePathUnlocked(m_pDS->fv("path.strPath").get_asString(),*CMediaSourceSettings::GetInstance().GetSources("video")))
9049 m_pDS->next();
9050 continue;
9053 CFileItemPtr pItem(new CFileItem(m_pDS->fv(1).get_asString()));
9054 std::string strDir =
9055 StringUtils::Format("tvshows/titles/{}/", m_pDS->fv("tvshow.idShow").get_asInt());
9057 pItem->SetPath("videodb://"+ strDir);
9058 pItem->m_bIsFolder=true;
9059 pItem->GetVideoInfoTag()->m_iDbId = m_pDS->fv("tvshow.idShow").get_asInt();
9060 items.Add(pItem);
9061 m_pDS->next();
9063 m_pDS->close();
9065 catch (...)
9067 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, strSQL);
9071 void CVideoDatabase::GetEpisodesByName(const std::string& strSearch, CFileItemList& items)
9073 std::string strSQL;
9077 if (nullptr == m_pDB)
9078 return;
9079 if (nullptr == m_pDS)
9080 return;
9082 if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
9083 strSQL = PrepareSQL("SELECT episode.idEpisode, episode.c%02d, episode.c%02d, episode.idShow, tvshow.c%02d, path.strPath FROM episode INNER JOIN tvshow ON tvshow.idShow=episode.idShow INNER JOIN files ON files.idFile=episode.idFile INNER JOIN path ON path.idPath=files.idPath WHERE episode.c%02d LIKE '%%%s%%'", VIDEODB_ID_EPISODE_TITLE, VIDEODB_ID_EPISODE_SEASON, VIDEODB_ID_TV_TITLE, VIDEODB_ID_EPISODE_TITLE, strSearch.c_str());
9084 else
9085 strSQL = PrepareSQL("SELECT episode.idEpisode, episode.c%02d, episode.c%02d, episode.idShow, tvshow.c%02d FROM episode INNER JOIN tvshow ON tvshow.idShow=episode.idShow WHERE episode.c%02d like '%%%s%%'", VIDEODB_ID_EPISODE_TITLE, VIDEODB_ID_EPISODE_SEASON, VIDEODB_ID_TV_TITLE, VIDEODB_ID_EPISODE_TITLE, strSearch.c_str());
9086 m_pDS->query( strSQL );
9088 while (!m_pDS->eof())
9090 if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
9091 if (!g_passwordManager.IsDatabasePathUnlocked(m_pDS->fv("path.strPath").get_asString(),*CMediaSourceSettings::GetInstance().GetSources("video")))
9093 m_pDS->next();
9094 continue;
9097 CFileItemPtr pItem(new CFileItem(m_pDS->fv(1).get_asString()+" ("+m_pDS->fv(4).get_asString()+")"));
9098 std::string path = StringUtils::Format("videodb://tvshows/titles/{}/{}/{}",
9099 m_pDS->fv("episode.idShow").get_asInt(),
9100 m_pDS->fv(2).get_asInt(), m_pDS->fv(0).get_asInt());
9101 pItem->SetPath(path);
9102 pItem->m_bIsFolder=false;
9103 items.Add(pItem);
9104 m_pDS->next();
9106 m_pDS->close();
9108 catch (...)
9110 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, strSQL);
9114 void CVideoDatabase::GetMusicVideosByName(const std::string& strSearch, CFileItemList& items)
9116 // Alternative searching - not quite as fast though due to
9117 // retrieving all information
9118 // Filter filter(PrepareSQL("c%02d like '%s%%' or c%02d like '%% %s%%'", VIDEODB_ID_MUSICVIDEO_TITLE, strSearch.c_str(), VIDEODB_ID_MUSICVIDEO_TITLE, strSearch.c_str()));
9119 // GetMusicVideosByWhere("videodb://musicvideos/titles/", filter, items);
9120 std::string strSQL;
9124 if (nullptr == m_pDB)
9125 return;
9126 if (nullptr == m_pDS)
9127 return;
9129 if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
9130 strSQL = PrepareSQL("SELECT musicvideo.idMVideo, musicvideo.c%02d, path.strPath FROM musicvideo INNER JOIN files ON files.idFile=musicvideo.idFile INNER JOIN path ON path.idPath=files.idPath WHERE musicvideo.c%02d LIKE '%%%s%%'", VIDEODB_ID_MUSICVIDEO_TITLE, VIDEODB_ID_MUSICVIDEO_TITLE, strSearch.c_str());
9131 else
9132 strSQL = PrepareSQL("select musicvideo.idMVideo,musicvideo.c%02d from musicvideo where musicvideo.c%02d like '%%%s%%'",VIDEODB_ID_MUSICVIDEO_TITLE,VIDEODB_ID_MUSICVIDEO_TITLE,strSearch.c_str());
9133 m_pDS->query( strSQL );
9135 while (!m_pDS->eof())
9137 if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
9138 if (!g_passwordManager.IsDatabasePathUnlocked(m_pDS->fv("path.strPath").get_asString(),*CMediaSourceSettings::GetInstance().GetSources("video")))
9140 m_pDS->next();
9141 continue;
9144 CFileItemPtr pItem(new CFileItem(m_pDS->fv(1).get_asString()));
9145 std::string strDir =
9146 StringUtils::Format("3/2/{}", m_pDS->fv("musicvideo.idMVideo").get_asInt());
9148 pItem->SetPath("videodb://"+ strDir);
9149 pItem->m_bIsFolder=false;
9150 items.Add(pItem);
9151 m_pDS->next();
9153 m_pDS->close();
9155 catch (...)
9157 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, strSQL);
9161 void CVideoDatabase::GetEpisodesByPlot(const std::string& strSearch, CFileItemList& items)
9163 // Alternative searching - not quite as fast though due to
9164 // retrieving all information
9165 // Filter filter;
9166 // filter.where = PrepareSQL("c%02d like '%s%%' or c%02d like '%% %s%%'", VIDEODB_ID_EPISODE_PLOT, strSearch.c_str(), VIDEODB_ID_EPISODE_PLOT, strSearch.c_str());
9167 // filter.where += PrepareSQL("or c%02d like '%s%%' or c%02d like '%% %s%%'", VIDEODB_ID_EPISODE_TITLE, strSearch.c_str(), VIDEODB_ID_EPISODE_TITLE, strSearch.c_str());
9168 // GetEpisodesByWhere("videodb://tvshows/titles/", filter, items);
9169 // return;
9170 std::string strSQL;
9174 if (nullptr == m_pDB)
9175 return;
9176 if (nullptr == m_pDS)
9177 return;
9179 if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
9180 strSQL = PrepareSQL("SELECT episode.idEpisode, episode.c%02d, episode.c%02d, episode.idShow, tvshow.c%02d, path.strPath FROM episode INNER JOIN tvshow ON tvshow.idShow=episode.idShow INNER JOIN files ON files.idFile=episode.idFile INNER JOIN path ON path.idPath=files.idPath WHERE episode.c%02d LIKE '%%%s%%'", VIDEODB_ID_EPISODE_TITLE, VIDEODB_ID_EPISODE_SEASON, VIDEODB_ID_TV_TITLE, VIDEODB_ID_EPISODE_PLOT, strSearch.c_str());
9181 else
9182 strSQL = PrepareSQL("SELECT episode.idEpisode, episode.c%02d, episode.c%02d, episode.idShow, tvshow.c%02d FROM episode INNER JOIN tvshow ON tvshow.idShow=episode.idShow WHERE episode.c%02d LIKE '%%%s%%'", VIDEODB_ID_EPISODE_TITLE, VIDEODB_ID_EPISODE_SEASON, VIDEODB_ID_TV_TITLE, VIDEODB_ID_EPISODE_PLOT, strSearch.c_str());
9183 m_pDS->query( strSQL );
9185 while (!m_pDS->eof())
9187 if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
9188 if (!g_passwordManager.IsDatabasePathUnlocked(m_pDS->fv("path.strPath").get_asString(),*CMediaSourceSettings::GetInstance().GetSources("video")))
9190 m_pDS->next();
9191 continue;
9194 CFileItemPtr pItem(new CFileItem(m_pDS->fv(1).get_asString()+" ("+m_pDS->fv(4).get_asString()+")"));
9195 std::string path = StringUtils::Format("videodb://tvshows/titles/{}/{}/{}",
9196 m_pDS->fv("episode.idShow").get_asInt(),
9197 m_pDS->fv(2).get_asInt(), m_pDS->fv(0).get_asInt());
9198 pItem->SetPath(path);
9199 pItem->m_bIsFolder=false;
9200 items.Add(pItem);
9201 m_pDS->next();
9203 m_pDS->close();
9205 catch (...)
9207 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, strSQL);
9211 void CVideoDatabase::GetMoviesByPlot(const std::string& strSearch, CFileItemList& items)
9213 std::string strSQL;
9217 if (nullptr == m_pDB)
9218 return;
9219 if (nullptr == m_pDS)
9220 return;
9222 if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
9223 strSQL = PrepareSQL("select movie.idMovie, movie.c%02d, path.strPath FROM movie INNER JOIN files ON files.idFile=movie.idFile INNER JOIN path ON path.idPath=files.idPath WHERE movie.c%02d LIKE '%%%s%%' OR movie.c%02d LIKE '%%%s%%' OR movie.c%02d LIKE '%%%s%%'", VIDEODB_ID_TITLE,VIDEODB_ID_PLOT, strSearch.c_str(), VIDEODB_ID_PLOTOUTLINE, strSearch.c_str(), VIDEODB_ID_TAGLINE,strSearch.c_str());
9224 else
9225 strSQL = PrepareSQL("SELECT movie.idMovie, movie.c%02d FROM movie WHERE (movie.c%02d LIKE '%%%s%%' OR movie.c%02d LIKE '%%%s%%' OR movie.c%02d LIKE '%%%s%%')", VIDEODB_ID_TITLE, VIDEODB_ID_PLOT, strSearch.c_str(), VIDEODB_ID_PLOTOUTLINE, strSearch.c_str(), VIDEODB_ID_TAGLINE, strSearch.c_str());
9227 m_pDS->query( strSQL );
9229 while (!m_pDS->eof())
9231 if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
9232 if (!g_passwordManager.IsDatabasePathUnlocked(m_pDS->fv(2).get_asString(),*CMediaSourceSettings::GetInstance().GetSources("video")))
9234 m_pDS->next();
9235 continue;
9238 CFileItemPtr pItem(new CFileItem(m_pDS->fv(1).get_asString()));
9239 std::string path =
9240 StringUtils::Format("videodb://movies/titles/{}", m_pDS->fv(0).get_asInt());
9241 pItem->SetPath(path);
9242 pItem->m_bIsFolder=false;
9244 items.Add(pItem);
9245 m_pDS->next();
9247 m_pDS->close();
9250 catch (...)
9252 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, strSQL);
9256 void CVideoDatabase::GetMovieDirectorsByName(const std::string& strSearch, CFileItemList& items)
9258 std::string strSQL;
9262 if (nullptr == m_pDB)
9263 return;
9264 if (nullptr == m_pDS)
9265 return;
9267 if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
9268 strSQL = PrepareSQL("SELECT DISTINCT director_link.actor_id, actor.name, path.strPath FROM movie INNER JOIN director_link ON (director_link.media_id=movie.idMovie AND director_link.media_type='movie') INNER JOIN actor ON actor.actor_id=director_link.actor_id INNER JOIN files ON files.idFile=movie.idFile INNER JOIN path ON path.idPath=files.idPath WHERE actor.name LIKE '%%%s%%'", strSearch.c_str());
9269 else
9270 strSQL = PrepareSQL("SELECT DISTINCT director_link.actor_id, actor.name FROM actor INNER JOIN director_link ON director_link.actor_id=actor.actor_id INNER JOIN movie ON director_link.media_id=movie.idMovie WHERE director_link.media_type='movie' AND actor.name like '%%%s%%'", strSearch.c_str());
9272 m_pDS->query( strSQL );
9274 while (!m_pDS->eof())
9276 if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
9277 if (!g_passwordManager.IsDatabasePathUnlocked(m_pDS->fv("path.strPath").get_asString(),*CMediaSourceSettings::GetInstance().GetSources("video")))
9279 m_pDS->next();
9280 continue;
9283 std::string strDir = StringUtils::Format("{}/", m_pDS->fv(0).get_asInt());
9284 CFileItemPtr pItem(new CFileItem(m_pDS->fv(1).get_asString()));
9286 pItem->SetPath("videodb://movies/directors/"+ strDir);
9287 pItem->m_bIsFolder=true;
9288 items.Add(pItem);
9289 m_pDS->next();
9291 m_pDS->close();
9293 catch (...)
9295 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, strSQL);
9299 void CVideoDatabase::GetTvShowsDirectorsByName(const std::string& strSearch, CFileItemList& items)
9301 std::string strSQL;
9305 if (nullptr == m_pDB)
9306 return;
9307 if (nullptr == m_pDS)
9308 return;
9310 if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
9311 strSQL = PrepareSQL("SELECT DISTINCT director_link.actor_id, actor.name, path.strPath FROM actor INNER JOIN director_link ON director_link.actor_id=actor.actor_id INNER JOIN tvshow ON director_link.media_id=tvshow.idShow INNER JOIN tvshowlinkpath ON tvshowlinkpath.idShow=tvshow.idShow INNER JOIN path ON path.idPath=tvshowlinkpath.idPath WHERE director_link.media_type='tvshow' AND actor.name LIKE '%%%s%%'", strSearch.c_str());
9312 else
9313 strSQL = PrepareSQL("SELECT DISTINCT director_link.actor_id, actor.name FROM actor INNER JOIN director_link ON director_link.actor_id=actor.actor_id INNER JOIN tvshow ON director_link.media_id=tvshow.idShow WHERE director_link.media_type='tvshow' AND actor.name LIKE '%%%s%%'", strSearch.c_str());
9315 m_pDS->query( strSQL );
9317 while (!m_pDS->eof())
9319 if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
9320 if (!g_passwordManager.IsDatabasePathUnlocked(m_pDS->fv("path.strPath").get_asString(),*CMediaSourceSettings::GetInstance().GetSources("video")))
9322 m_pDS->next();
9323 continue;
9326 std::string strDir = StringUtils::Format("{}/", m_pDS->fv(0).get_asInt());
9327 CFileItemPtr pItem(new CFileItem(m_pDS->fv(1).get_asString()));
9329 pItem->SetPath("videodb://tvshows/directors/"+ strDir);
9330 pItem->m_bIsFolder=true;
9331 items.Add(pItem);
9332 m_pDS->next();
9334 m_pDS->close();
9336 catch (...)
9338 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, strSQL);
9342 void CVideoDatabase::GetMusicVideoDirectorsByName(const std::string& strSearch, CFileItemList& items)
9344 std::string strSQL;
9348 if (nullptr == m_pDB)
9349 return;
9350 if (nullptr == m_pDS)
9351 return;
9353 if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
9354 strSQL = PrepareSQL("SELECT DISTINCT director_link.actor_id, actor.name, path.strPath FROM actor INNER JOIN director_link ON director_link.actor_id=actor.actor_id INNER JOIN musicvideo ON director_link.media_id=musicvideo.idMVideo INNER JOIN files ON files.idFile=musicvideo.idFile INNER JOIN path ON path.idPath=files.idPath WHERE director_link.media_type='musicvideo' AND actor.name LIKE '%%%s%%'", strSearch.c_str());
9355 else
9356 strSQL = PrepareSQL("SELECT DISTINCT director_link.actor_id, actor.name FROM actor INNER JOIN director_link ON director_link.actor_id=actor.actor_id INNER JOIN musicvideo ON director_link.media_id=musicvideo.idMVideo WHERE director_link.media_type='musicvideo' AND actor.name LIKE '%%%s%%'", strSearch.c_str());
9358 m_pDS->query( strSQL );
9360 while (!m_pDS->eof())
9362 if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
9363 if (!g_passwordManager.IsDatabasePathUnlocked(m_pDS->fv("path.strPath").get_asString(),*CMediaSourceSettings::GetInstance().GetSources("video")))
9365 m_pDS->next();
9366 continue;
9369 std::string strDir = StringUtils::Format("{}/", m_pDS->fv(0).get_asInt());
9370 CFileItemPtr pItem(new CFileItem(m_pDS->fv(1).get_asString()));
9372 pItem->SetPath("videodb://musicvideos/albums/"+ strDir);
9373 pItem->m_bIsFolder=true;
9374 items.Add(pItem);
9375 m_pDS->next();
9377 m_pDS->close();
9379 catch (...)
9381 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, strSQL);
9385 void CVideoDatabase::CleanDatabase(CGUIDialogProgressBarHandle* handle,
9386 const std::set<int>& paths,
9387 bool showProgress)
9389 CGUIDialogProgress* progress = NULL;
9392 if (nullptr == m_pDB)
9393 return;
9394 if (nullptr == m_pDS)
9395 return;
9396 if (nullptr == m_pDS2)
9397 return;
9399 auto start = std::chrono::steady_clock::now();
9400 CLog::Log(LOGINFO, "{}: Starting videodatabase cleanup ..", __FUNCTION__);
9401 CServiceBroker::GetAnnouncementManager()->Announce(ANNOUNCEMENT::VideoLibrary,
9402 "OnCleanStarted");
9404 if (handle)
9406 handle->SetTitle(g_localizeStrings.Get(700));
9407 handle->SetText("");
9409 else if (showProgress)
9411 progress = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogProgress>(
9412 WINDOW_DIALOG_PROGRESS);
9413 if (progress)
9415 progress->SetHeading(CVariant{700});
9416 progress->SetLine(0, CVariant{""});
9417 progress->SetLine(1, CVariant{313});
9418 progress->SetLine(2, CVariant{330});
9419 progress->SetPercentage(0);
9420 progress->Open();
9421 progress->ShowProgressBar(true);
9425 BeginTransaction();
9427 // find all the files
9428 std::string sql = "SELECT files.idFile, files.strFileName, path.strPath FROM files "
9429 "INNER JOIN path ON path.idPath=files.idPath";
9430 if (!paths.empty())
9432 std::string strPaths;
9433 for (const auto& i : paths)
9434 strPaths += StringUtils::Format(",{}", i);
9435 sql += PrepareSQL(" AND path.idPath IN (%s)", strPaths.substr(1).c_str());
9438 // For directory caching to work properly, we need to sort the files by path
9439 sql += " ORDER BY path.strPath";
9441 m_pDS2->query(sql);
9442 if (m_pDS2->num_rows() > 0)
9444 std::string filesToTestForDelete;
9445 VECSOURCES videoSources(*CMediaSourceSettings::GetInstance().GetSources("video"));
9446 CServiceBroker::GetMediaManager().GetRemovableDrives(videoSources);
9448 int total = m_pDS2->num_rows();
9449 int current = 0;
9450 std::string lastDir;
9451 bool gotDir = true;
9453 while (!m_pDS2->eof())
9455 std::string path = m_pDS2->fv("path.strPath").get_asString();
9456 std::string fileName = m_pDS2->fv("files.strFileName").get_asString();
9457 std::string fullPath;
9458 ConstructPath(fullPath, path, fileName);
9460 // get the first stacked file
9461 if (URIUtils::IsStack(fullPath))
9462 fullPath = CStackDirectory::GetFirstStackedFile(fullPath);
9464 // get the actual archive path
9465 if (URIUtils::IsInArchive(fullPath))
9466 fullPath = CURL(fullPath).GetHostName();
9468 bool del = true;
9469 if (URIUtils::IsPlugin(fullPath))
9471 SScanSettings settings;
9472 bool foundDirectly = false;
9473 ScraperPtr scraper = GetScraperForPath(fullPath, settings, foundDirectly);
9474 if (scraper &&
9475 CPluginDirectory::CheckExists(TranslateContent(scraper->Content()), fullPath))
9476 del = false;
9478 else
9480 // Only consider keeping this file if not optical and belonging to a (matching) source
9481 bool bIsSource;
9482 if (!URIUtils::IsOnDVD(fullPath) &&
9483 CUtil::GetMatchingSource(fullPath, videoSources, bIsSource) >= 0)
9485 const std::string pathDir = URIUtils::GetDirectory(fullPath);
9487 // Cache file's directory in case it's different from the previous file
9488 if (lastDir != pathDir)
9490 lastDir = pathDir;
9491 CFileItemList items; // Dummy list
9492 gotDir = CDirectory::GetDirectory(pathDir, items, "",
9493 DIR_FLAG_NO_FILE_DIRS | DIR_FLAG_NO_FILE_INFO);
9496 // Keep existing files
9497 if (gotDir && CFile::Exists(fullPath, true))
9498 del = false;
9501 if (del)
9502 filesToTestForDelete += m_pDS2->fv("files.idFile").get_asString() + ",";
9504 if (handle == NULL && progress != NULL)
9506 int percentage = current * 100 / total;
9507 if (percentage > progress->GetPercentage())
9509 progress->SetPercentage(percentage);
9510 progress->Progress();
9512 if (progress->IsCanceled())
9514 progress->Close();
9515 m_pDS2->close();
9516 CServiceBroker::GetAnnouncementManager()->Announce(ANNOUNCEMENT::VideoLibrary,
9517 "OnCleanFinished");
9518 return;
9521 else if (handle != NULL)
9522 handle->SetPercentage(current * 100 / (float)total);
9524 m_pDS2->next();
9525 current++;
9527 m_pDS2->close();
9529 std::string filesToDelete;
9531 // Add any files that don't have a valid idPath entry to the filesToDelete list.
9532 m_pDS->query("SELECT files.idFile FROM files WHERE NOT EXISTS (SELECT 1 FROM path "
9533 "WHERE path.idPath = files.idPath)");
9534 while (!m_pDS->eof())
9536 std::string file = m_pDS->fv("files.idFile").get_asString() + ",";
9537 filesToTestForDelete += file;
9538 filesToDelete += file;
9540 m_pDS->next();
9542 m_pDS->close();
9544 std::map<int, bool> pathsDeleteDecisions;
9545 std::vector<int> movieIDs;
9546 std::vector<int> tvshowIDs;
9547 std::vector<int> episodeIDs;
9548 std::vector<int> musicVideoIDs;
9550 if (!filesToTestForDelete.empty())
9552 StringUtils::TrimRight(filesToTestForDelete, ",");
9554 movieIDs = CleanMediaType(MediaTypeMovie, filesToTestForDelete, pathsDeleteDecisions,
9555 filesToDelete, !showProgress);
9556 episodeIDs = CleanMediaType(MediaTypeEpisode, filesToTestForDelete, pathsDeleteDecisions,
9557 filesToDelete, !showProgress);
9558 musicVideoIDs = CleanMediaType(MediaTypeMusicVideo, filesToTestForDelete,
9559 pathsDeleteDecisions, filesToDelete, !showProgress);
9562 if (progress != NULL)
9564 progress->SetPercentage(100);
9565 progress->Progress();
9568 if (!filesToDelete.empty())
9570 filesToDelete = "(" + StringUtils::TrimRight(filesToDelete, ",") + ")";
9572 // Clean hashes of all paths that files are deleted from
9573 // Otherwise there is a mismatch between the path contents and the hash in the
9574 // database, leading to potentially missed items on re-scan (if deleted files are
9575 // later re-added to a source)
9576 CLog::LogFC(LOGDEBUG, LOGDATABASE, "Cleaning path hashes");
9577 m_pDS->query("SELECT DISTINCT strPath FROM path JOIN files ON files.idPath=path.idPath "
9578 "WHERE files.idFile IN " +
9579 filesToDelete);
9580 int pathHashCount = m_pDS->num_rows();
9581 while (!m_pDS->eof())
9583 InvalidatePathHash(m_pDS->fv("strPath").get_asString());
9584 m_pDS->next();
9586 CLog::LogFC(LOGDEBUG, LOGDATABASE, "Cleaned {} path hashes", pathHashCount);
9588 CLog::Log(LOGDEBUG, LOGDATABASE, "{}: Cleaning files table", __FUNCTION__);
9589 sql = "DELETE FROM files WHERE idFile IN " + filesToDelete;
9590 m_pDS->exec(sql);
9593 if (!movieIDs.empty())
9595 std::string moviesToDelete;
9596 for (const auto& i : movieIDs)
9597 moviesToDelete += StringUtils::Format("{},", i);
9598 moviesToDelete = "(" + StringUtils::TrimRight(moviesToDelete, ",") + ")";
9600 CLog::Log(LOGDEBUG, LOGDATABASE, "{}: Cleaning movie table", __FUNCTION__);
9601 sql = "DELETE FROM movie WHERE idMovie IN " + moviesToDelete;
9602 m_pDS->exec(sql);
9605 if (!episodeIDs.empty())
9607 std::string episodesToDelete;
9608 for (const auto& i : episodeIDs)
9609 episodesToDelete += StringUtils::Format("{},", i);
9610 episodesToDelete = "(" + StringUtils::TrimRight(episodesToDelete, ",") + ")";
9612 CLog::Log(LOGDEBUG, LOGDATABASE, "{}: Cleaning episode table", __FUNCTION__);
9613 sql = "DELETE FROM episode WHERE idEpisode IN " + episodesToDelete;
9614 m_pDS->exec(sql);
9617 CLog::Log(LOGDEBUG, LOGDATABASE,
9618 "{}: Cleaning paths that don't exist and have content set...", __FUNCTION__);
9619 sql = "SELECT path.idPath, path.strPath, path.idParentPath FROM path "
9620 "WHERE NOT ((strContent IS NULL OR strContent = '') "
9621 "AND (strSettings IS NULL OR strSettings = '') "
9622 "AND (strHash IS NULL OR strHash = '') "
9623 "AND (exclude IS NULL OR exclude != 1))";
9624 m_pDS2->query(sql);
9625 std::string strIds;
9626 while (!m_pDS2->eof())
9628 auto pathsDeleteDecision = pathsDeleteDecisions.find(m_pDS2->fv(0).get_asInt());
9629 // Check if we have a decision for the parent path
9630 auto pathsDeleteDecisionByParent = pathsDeleteDecisions.find(m_pDS2->fv(2).get_asInt());
9631 std::string path = m_pDS2->fv(1).get_asString();
9633 bool exists = false;
9634 if (URIUtils::IsPlugin(path))
9636 SScanSettings settings;
9637 bool foundDirectly = false;
9638 ScraperPtr scraper = GetScraperForPath(path, settings, foundDirectly);
9639 if (scraper && CPluginDirectory::CheckExists(TranslateContent(scraper->Content()), path))
9640 exists = true;
9642 else
9643 exists = CDirectory::Exists(path, false);
9645 if (((pathsDeleteDecision != pathsDeleteDecisions.end() && pathsDeleteDecision->second) ||
9646 (pathsDeleteDecision == pathsDeleteDecisions.end() && !exists)) &&
9647 ((pathsDeleteDecisionByParent != pathsDeleteDecisions.end() &&
9648 pathsDeleteDecisionByParent->second) ||
9649 (pathsDeleteDecisionByParent == pathsDeleteDecisions.end())))
9650 strIds += m_pDS2->fv(0).get_asString() + ",";
9652 m_pDS2->next();
9654 m_pDS2->close();
9656 if (!strIds.empty())
9658 sql = PrepareSQL("DELETE FROM path WHERE idPath IN (%s)",
9659 StringUtils::TrimRight(strIds, ",").c_str());
9660 m_pDS->exec(sql);
9661 sql = "DELETE FROM tvshowlinkpath "
9662 "WHERE NOT EXISTS (SELECT 1 FROM path WHERE path.idPath = tvshowlinkpath.idPath)";
9663 m_pDS->exec(sql);
9666 CLog::Log(LOGDEBUG, LOGDATABASE, "{}: Cleaning tvshow table", __FUNCTION__);
9668 std::string tvshowsToDelete;
9669 sql = "SELECT idShow FROM tvshow "
9670 "WHERE NOT EXISTS (SELECT 1 FROM tvshowlinkpath WHERE tvshowlinkpath.idShow = "
9671 "tvshow.idShow)";
9672 m_pDS->query(sql);
9673 while (!m_pDS->eof())
9675 tvshowIDs.push_back(m_pDS->fv(0).get_asInt());
9676 tvshowsToDelete += m_pDS->fv(0).get_asString() + ",";
9677 m_pDS->next();
9679 m_pDS->close();
9680 if (!tvshowsToDelete.empty())
9682 sql = "DELETE FROM tvshow WHERE idShow IN (" +
9683 StringUtils::TrimRight(tvshowsToDelete, ",") + ")";
9684 m_pDS->exec(sql);
9687 if (!musicVideoIDs.empty())
9689 std::string musicVideosToDelete;
9690 for (const auto& i : musicVideoIDs)
9691 musicVideosToDelete += StringUtils::Format("{},", i);
9692 musicVideosToDelete = "(" + StringUtils::TrimRight(musicVideosToDelete, ",") + ")";
9694 CLog::Log(LOGDEBUG, LOGDATABASE, "{}: Cleaning musicvideo table", __FUNCTION__);
9695 sql = "DELETE FROM musicvideo WHERE idMVideo IN " + musicVideosToDelete;
9696 m_pDS->exec(sql);
9699 CLog::Log(LOGDEBUG, LOGDATABASE, "{}: Cleaning path table", __FUNCTION__);
9700 sql = StringUtils::Format(
9701 "DELETE FROM path "
9702 "WHERE (strContent IS NULL OR strContent = '') "
9703 "AND (strSettings IS NULL OR strSettings = '') "
9704 "AND (strHash IS NULL OR strHash = '') "
9705 "AND (exclude IS NULL OR exclude != 1) "
9706 "AND (idParentPath IS NULL OR NOT EXISTS (SELECT 1 FROM (SELECT idPath FROM path) as "
9707 "parentPath WHERE parentPath.idPath = path.idParentPath)) " // MySQL only fix (#5007)
9708 "AND NOT EXISTS (SELECT 1 FROM files WHERE files.idPath = path.idPath) "
9709 "AND NOT EXISTS (SELECT 1 FROM tvshowlinkpath WHERE tvshowlinkpath.idPath = path.idPath) "
9710 "AND NOT EXISTS (SELECT 1 FROM movie WHERE movie.c{:02} = path.idPath) "
9711 "AND NOT EXISTS (SELECT 1 FROM episode WHERE episode.c{:02} = path.idPath) "
9712 "AND NOT EXISTS (SELECT 1 FROM musicvideo WHERE musicvideo.c{:02} = path.idPath)",
9713 VIDEODB_ID_PARENTPATHID, VIDEODB_ID_EPISODE_PARENTPATHID,
9714 VIDEODB_ID_MUSICVIDEO_PARENTPATHID);
9715 m_pDS->exec(sql);
9717 CLog::Log(LOGDEBUG, LOGDATABASE, "{}: Cleaning genre table", __FUNCTION__);
9718 sql =
9719 "DELETE FROM genre "
9720 "WHERE NOT EXISTS (SELECT 1 FROM genre_link WHERE genre_link.genre_id = genre.genre_id)";
9721 m_pDS->exec(sql);
9723 CLog::Log(LOGDEBUG, LOGDATABASE, "{}: Cleaning country table", __FUNCTION__);
9724 sql = "DELETE FROM country WHERE NOT EXISTS (SELECT 1 FROM country_link WHERE "
9725 "country_link.country_id = country.country_id)";
9726 m_pDS->exec(sql);
9728 CLog::Log(LOGDEBUG, LOGDATABASE, "{}: Cleaning actor table of actors, directors and writers",
9729 __FUNCTION__);
9730 sql =
9731 "DELETE FROM actor "
9732 "WHERE NOT EXISTS (SELECT 1 FROM actor_link WHERE actor_link.actor_id = actor.actor_id) "
9733 "AND NOT EXISTS (SELECT 1 FROM director_link WHERE director_link.actor_id = "
9734 "actor.actor_id) "
9735 "AND NOT EXISTS (SELECT 1 FROM writer_link WHERE writer_link.actor_id = actor.actor_id)";
9736 m_pDS->exec(sql);
9738 CLog::Log(LOGDEBUG, LOGDATABASE, "{}: Cleaning studio table", __FUNCTION__);
9739 sql = "DELETE FROM studio "
9740 "WHERE NOT EXISTS (SELECT 1 FROM studio_link WHERE studio_link.studio_id = "
9741 "studio.studio_id)";
9742 m_pDS->exec(sql);
9744 CLog::Log(LOGDEBUG, LOGDATABASE, "{}: Cleaning set table", __FUNCTION__);
9745 sql = "DELETE FROM sets "
9746 "WHERE NOT EXISTS (SELECT 1 FROM movie WHERE movie.idSet = sets.idSet)";
9747 m_pDS->exec(sql);
9749 CommitTransaction();
9751 if (handle)
9752 handle->SetTitle(g_localizeStrings.Get(331));
9754 Compress(false);
9756 CUtil::DeleteVideoDatabaseDirectoryCache();
9758 auto end = std::chrono::steady_clock::now();
9759 auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
9761 CLog::Log(LOGINFO, "{}: Cleaning videodatabase done. Operation took {} ms", __FUNCTION__,
9762 duration.count());
9764 for (const auto& i : movieIDs)
9765 AnnounceRemove(MediaTypeMovie, i, true);
9767 for (const auto& i : episodeIDs)
9768 AnnounceRemove(MediaTypeEpisode, i, true);
9770 for (const auto& i : tvshowIDs)
9771 AnnounceRemove(MediaTypeTvShow, i, true);
9773 for (const auto& i : musicVideoIDs)
9774 AnnounceRemove(MediaTypeMusicVideo, i, true);
9777 catch (...)
9779 CLog::Log(LOGERROR, "{} failed", __FUNCTION__);
9780 RollbackTransaction();
9782 if (progress)
9783 progress->Close();
9785 CServiceBroker::GetAnnouncementManager()->Announce(ANNOUNCEMENT::VideoLibrary, "OnCleanFinished");
9788 std::vector<int> CVideoDatabase::CleanMediaType(const std::string &mediaType, const std::string &cleanableFileIDs,
9789 std::map<int, bool> &pathsDeleteDecisions, std::string &deletedFileIDs, bool silent)
9791 std::vector<int> cleanedIDs;
9792 if (mediaType.empty() || cleanableFileIDs.empty())
9793 return cleanedIDs;
9795 const std::string& table = mediaType;
9796 std::string idField;
9797 std::string parentPathIdField;
9798 bool isEpisode = false;
9799 if (mediaType == MediaTypeMovie)
9801 idField = "idMovie";
9802 parentPathIdField = StringUtils::Format("{}.c{:02}", table, VIDEODB_ID_PARENTPATHID);
9804 else if (mediaType == MediaTypeEpisode)
9806 idField = "idEpisode";
9807 parentPathIdField = "showPath.idParentPath";
9808 isEpisode = true;
9810 else if (mediaType == MediaTypeMusicVideo)
9812 idField = "idMVideo";
9813 parentPathIdField = StringUtils::Format("{}.c{:02}", table, VIDEODB_ID_MUSICVIDEO_PARENTPATHID);
9815 else
9816 return cleanedIDs;
9818 // now grab them media items
9819 std::string sql = PrepareSQL("SELECT %s.%s, %s.idFile, path.idPath, parentPath.strPath FROM %s "
9820 "JOIN files ON files.idFile = %s.idFile "
9821 "JOIN path ON path.idPath = files.idPath ",
9822 table.c_str(), idField.c_str(), table.c_str(), table.c_str(),
9823 table.c_str());
9825 if (isEpisode)
9826 sql += "JOIN tvshowlinkpath ON tvshowlinkpath.idShow = episode.idShow JOIN path AS showPath ON showPath.idPath=tvshowlinkpath.idPath ";
9828 sql += PrepareSQL("LEFT JOIN path as parentPath ON parentPath.idPath = %s "
9829 "WHERE %s.idFile IN (%s)",
9830 parentPathIdField.c_str(),
9831 table.c_str(), cleanableFileIDs.c_str());
9833 VECSOURCES videoSources(*CMediaSourceSettings::GetInstance().GetSources("video"));
9834 CServiceBroker::GetMediaManager().GetRemovableDrives(videoSources);
9836 // map of parent path ID to boolean pair (if not exists and user choice)
9837 std::map<int, std::pair<bool, bool> > sourcePathsDeleteDecisions;
9838 m_pDS2->query(sql);
9839 while (!m_pDS2->eof())
9841 bool del = true;
9842 if (m_pDS2->fv(3).get_isNull() == false)
9844 std::string parentPath = m_pDS2->fv(3).get_asString();
9846 // try to find the source path the parent path belongs to
9847 SScanSettings scanSettings;
9848 std::string sourcePath;
9849 GetSourcePath(parentPath, sourcePath, scanSettings);
9851 bool bIsSourceName;
9852 bool sourceNotFound = (CUtil::GetMatchingSource(parentPath, videoSources, bIsSourceName) < 0);
9854 if (sourceNotFound && sourcePath.empty())
9855 sourcePath = parentPath;
9857 int sourcePathID = GetPathId(sourcePath);
9858 auto sourcePathsDeleteDecision = sourcePathsDeleteDecisions.find(sourcePathID);
9859 if (sourcePathsDeleteDecision == sourcePathsDeleteDecisions.end())
9861 bool sourcePathNotExists = (sourceNotFound || !CDirectory::Exists(sourcePath, false));
9862 // if the parent path exists, the file will be deleted without asking
9863 // if the parent path doesn't exist or does not belong to a valid media source,
9864 // ask the user whether to remove all items it contained
9865 if (sourcePathNotExists)
9867 // in silent mode assume that the files are just temporarily missing
9868 if (silent)
9869 del = false;
9870 else
9872 CGUIDialogYesNo* pDialog = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogYesNo>(WINDOW_DIALOG_YES_NO);
9873 if (pDialog != NULL)
9875 CURL sourceUrl(sourcePath);
9876 pDialog->SetHeading(CVariant{15012});
9877 pDialog->SetText(CVariant{StringUtils::Format(g_localizeStrings.Get(15013),
9878 sourceUrl.GetWithoutUserDetails())});
9879 pDialog->SetChoice(0, CVariant{15015});
9880 pDialog->SetChoice(1, CVariant{15014});
9881 pDialog->Open();
9883 del = !pDialog->IsConfirmed();
9888 sourcePathsDeleteDecisions.insert(std::make_pair(sourcePathID, std::make_pair(sourcePathNotExists, del)));
9889 pathsDeleteDecisions.insert(std::make_pair(sourcePathID, sourcePathNotExists && del));
9891 // the only reason not to delete the file is if the parent path doesn't
9892 // exist and the user decided to delete all the items it contained
9893 else if (sourcePathsDeleteDecision->second.first &&
9894 !sourcePathsDeleteDecision->second.second)
9895 del = false;
9897 if (scanSettings.parent_name)
9898 pathsDeleteDecisions.insert(std::make_pair(m_pDS2->fv(2).get_asInt(), del));
9901 if (del)
9903 deletedFileIDs += m_pDS2->fv(1).get_asString() + ",";
9904 cleanedIDs.push_back(m_pDS2->fv(0).get_asInt());
9907 m_pDS2->next();
9909 m_pDS2->close();
9911 return cleanedIDs;
9914 void CVideoDatabase::DumpToDummyFiles(const std::string &path)
9916 // get all tvshows
9917 CFileItemList items;
9918 GetTvShowsByWhere("videodb://tvshows/titles/", CDatabase::Filter(), items);
9919 std::string showPath = URIUtils::AddFileToFolder(path, "shows");
9920 CDirectory::Create(showPath);
9921 for (int i = 0; i < items.Size(); i++)
9923 // create a folder in this directory
9924 std::string showName = CUtil::MakeLegalFileName(items[i]->GetVideoInfoTag()->m_strShowTitle);
9925 std::string TVFolder = URIUtils::AddFileToFolder(showPath, showName);
9926 if (CDirectory::Create(TVFolder))
9927 { // right - grab the episodes and dump them as well
9928 CFileItemList episodes;
9929 Filter filter(PrepareSQL("idShow=%i", items[i]->GetVideoInfoTag()->m_iDbId));
9930 GetEpisodesByWhere("videodb://tvshows/titles/", filter, episodes);
9931 for (int i = 0; i < episodes.Size(); i++)
9933 CVideoInfoTag *tag = episodes[i]->GetVideoInfoTag();
9934 std::string episode =
9935 StringUtils::Format("{}.s{:02}e{:02}.avi", showName, tag->m_iSeason, tag->m_iEpisode);
9936 // and make a file
9937 std::string episodePath = URIUtils::AddFileToFolder(TVFolder, episode);
9938 CFile file;
9939 if (file.OpenForWrite(episodePath))
9940 file.Close();
9944 // get all movies
9945 items.Clear();
9946 GetMoviesByWhere("videodb://movies/titles/", CDatabase::Filter(), items);
9947 std::string moviePath = URIUtils::AddFileToFolder(path, "movies");
9948 CDirectory::Create(moviePath);
9949 for (int i = 0; i < items.Size(); i++)
9951 CVideoInfoTag *tag = items[i]->GetVideoInfoTag();
9952 std::string movie = StringUtils::Format("{}.avi", tag->m_strTitle);
9953 CFile file;
9954 if (file.OpenForWrite(URIUtils::AddFileToFolder(moviePath, movie)))
9955 file.Close();
9959 void CVideoDatabase::ExportToXML(const std::string &path, bool singleFile /* = true */, bool images /* = false */, bool actorThumbs /* false */, bool overwrite /*=false*/)
9961 int iFailCount = 0;
9962 CGUIDialogProgress *progress=NULL;
9965 if (nullptr == m_pDB)
9966 return;
9967 if (nullptr == m_pDS)
9968 return;
9969 if (nullptr == m_pDS2)
9970 return;
9972 // create a 3rd dataset as well as GetEpisodeDetails() etc. uses m_pDS2, and we need to do 3 nested queries on tv shows
9973 std::unique_ptr<Dataset> pDS;
9974 pDS.reset(m_pDB->CreateDataset());
9975 if (nullptr == pDS)
9976 return;
9978 std::unique_ptr<Dataset> pDS2;
9979 pDS2.reset(m_pDB->CreateDataset());
9980 if (nullptr == pDS2)
9981 return;
9983 // if we're exporting to a single folder, we export thumbs as well
9984 std::string exportRoot = URIUtils::AddFileToFolder(path, "kodi_videodb_" + CDateTime::GetCurrentDateTime().GetAsDBDate());
9985 std::string xmlFile = URIUtils::AddFileToFolder(exportRoot, "videodb.xml");
9986 std::string actorsDir = URIUtils::AddFileToFolder(exportRoot, "actors");
9987 std::string moviesDir = URIUtils::AddFileToFolder(exportRoot, "movies");
9988 std::string movieSetsDir = URIUtils::AddFileToFolder(exportRoot, "moviesets");
9989 std::string musicvideosDir = URIUtils::AddFileToFolder(exportRoot, "musicvideos");
9990 std::string tvshowsDir = URIUtils::AddFileToFolder(exportRoot, "tvshows");
9991 if (singleFile)
9993 images = true;
9994 overwrite = false;
9995 actorThumbs = true;
9996 CDirectory::Remove(exportRoot);
9997 CDirectory::Create(exportRoot);
9998 CDirectory::Create(actorsDir);
9999 CDirectory::Create(moviesDir);
10000 CDirectory::Create(movieSetsDir);
10001 CDirectory::Create(musicvideosDir);
10002 CDirectory::Create(tvshowsDir);
10005 progress = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogProgress>(WINDOW_DIALOG_PROGRESS);
10006 // find all movies
10007 std::string sql = "select * from movie_view";
10009 m_pDS->query(sql);
10011 if (progress)
10013 progress->SetHeading(CVariant{647});
10014 progress->SetLine(0, CVariant{650});
10015 progress->SetLine(1, CVariant{""});
10016 progress->SetLine(2, CVariant{""});
10017 progress->SetPercentage(0);
10018 progress->Open();
10019 progress->ShowProgressBar(true);
10022 int total = m_pDS->num_rows();
10023 int current = 0;
10025 // create our xml document
10026 CXBMCTinyXML xmlDoc;
10027 TiXmlDeclaration decl("1.0", "UTF-8", "yes");
10028 xmlDoc.InsertEndChild(decl);
10029 TiXmlNode *pMain = NULL;
10030 if (!singleFile)
10031 pMain = &xmlDoc;
10032 else
10034 TiXmlElement xmlMainElement("videodb");
10035 pMain = xmlDoc.InsertEndChild(xmlMainElement);
10036 XMLUtils::SetInt(pMain,"version", GetExportVersion());
10039 while (!m_pDS->eof())
10041 CVideoInfoTag movie = GetDetailsForMovie(m_pDS, VideoDbDetailsAll);
10042 // strip paths to make them relative
10043 if (StringUtils::StartsWith(movie.m_strTrailer, movie.m_strPath))
10044 movie.m_strTrailer = movie.m_strTrailer.substr(movie.m_strPath.size());
10045 std::map<std::string, std::string> artwork;
10046 if (GetArtForItem(movie.m_iDbId, movie.m_type, artwork) && singleFile)
10048 TiXmlElement additionalNode("art");
10049 for (const auto &i : artwork)
10050 XMLUtils::SetString(&additionalNode, i.first.c_str(), i.second);
10051 movie.Save(pMain, "movie", true, &additionalNode);
10053 else
10054 movie.Save(pMain, "movie", singleFile);
10056 // reset old skip state
10057 bool bSkip = false;
10059 if (progress)
10061 progress->SetLine(1, CVariant{movie.m_strTitle});
10062 progress->SetPercentage(current * 100 / total);
10063 progress->Progress();
10064 if (progress->IsCanceled())
10066 progress->Close();
10067 m_pDS->close();
10068 return;
10072 CFileItem item(movie.m_strFileNameAndPath,false);
10073 if (!singleFile && CUtil::SupportsWriteFileOperations(movie.m_strFileNameAndPath))
10075 if (!item.Exists(false))
10077 CLog::Log(LOGINFO, "{} - Not exporting item {} as it does not exist", __FUNCTION__,
10078 movie.m_strFileNameAndPath);
10079 bSkip = true;
10081 else
10083 std::string nfoFile(URIUtils::ReplaceExtension(item.GetTBNFile(), ".nfo"));
10085 if (item.IsOpticalMediaFile())
10087 nfoFile = URIUtils::AddFileToFolder(
10088 URIUtils::GetParentPath(nfoFile),
10089 URIUtils::GetFileName(nfoFile));
10092 if (overwrite || !CFile::Exists(nfoFile, false))
10094 if(!xmlDoc.SaveFile(nfoFile))
10096 CLog::Log(LOGERROR, "{}: Movie nfo export failed! ('{}')", __FUNCTION__, nfoFile);
10097 CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Error, g_localizeStrings.Get(20302), nfoFile);
10098 iFailCount++;
10103 if (!singleFile)
10105 xmlDoc.Clear();
10106 TiXmlDeclaration decl("1.0", "UTF-8", "yes");
10107 xmlDoc.InsertEndChild(decl);
10110 if (images && !bSkip)
10112 if (singleFile)
10114 std::string strFileName(movie.m_strTitle);
10115 if (movie.HasYear())
10116 strFileName += StringUtils::Format("_{}", movie.GetYear());
10117 item.SetPath(GetSafeFile(moviesDir, strFileName) + ".avi");
10119 for (const auto &i : artwork)
10121 std::string savedThumb = item.GetLocalArt(i.first, false);
10122 CServiceBroker::GetTextureCache()->Export(i.second, savedThumb, overwrite);
10124 if (actorThumbs)
10125 ExportActorThumbs(actorsDir, movie, !singleFile, overwrite);
10127 m_pDS->next();
10128 current++;
10130 m_pDS->close();
10132 if (!singleFile)
10133 movieSetsDir = CServiceBroker::GetSettingsComponent()->GetSettings()->GetString(
10134 CSettings::SETTING_VIDEOLIBRARY_MOVIESETSFOLDER);
10135 if (images && !movieSetsDir.empty())
10137 // find all movie sets
10138 sql = "select idSet, strSet from sets";
10140 m_pDS->query(sql);
10142 total = m_pDS->num_rows();
10143 current = 0;
10145 while (!m_pDS->eof())
10147 std::string title = m_pDS->fv("strSet").get_asString();
10149 if (progress)
10151 progress->SetLine(1, CVariant{title});
10152 progress->SetPercentage(current * 100 / total);
10153 progress->Progress();
10154 if (progress->IsCanceled())
10156 progress->Close();
10157 m_pDS->close();
10158 return;
10162 std::string itemPath = URIUtils::AddFileToFolder(movieSetsDir,
10163 CUtil::MakeLegalFileName(title, LEGAL_WIN32_COMPAT));
10164 if (CDirectory::Exists(itemPath) || CDirectory::Create(itemPath))
10166 std::map<std::string, std::string> artwork;
10167 GetArtForItem(m_pDS->fv("idSet").get_asInt(), MediaTypeVideoCollection, artwork);
10168 for (const auto& art : artwork)
10170 std::string savedThumb = URIUtils::AddFileToFolder(itemPath, art.first);
10171 CServiceBroker::GetTextureCache()->Export(art.second, savedThumb, overwrite);
10174 else
10175 CLog::Log(
10176 LOGDEBUG,
10177 "CVideoDatabase::{} - Not exporting movie set '{}' as could not create folder '{}'",
10178 __FUNCTION__, title, itemPath);
10179 m_pDS->next();
10180 current++;
10182 m_pDS->close();
10185 // find all musicvideos
10186 sql = "select * from musicvideo_view";
10188 m_pDS->query(sql);
10190 total = m_pDS->num_rows();
10191 current = 0;
10193 while (!m_pDS->eof())
10195 CVideoInfoTag movie = GetDetailsForMusicVideo(m_pDS, VideoDbDetailsAll);
10196 std::map<std::string, std::string> artwork;
10197 if (GetArtForItem(movie.m_iDbId, movie.m_type, artwork) && singleFile)
10199 TiXmlElement additionalNode("art");
10200 for (const auto &i : artwork)
10201 XMLUtils::SetString(&additionalNode, i.first.c_str(), i.second);
10202 movie.Save(pMain, "musicvideo", true, &additionalNode);
10204 else
10205 movie.Save(pMain, "musicvideo", singleFile);
10207 // reset old skip state
10208 bool bSkip = false;
10210 if (progress)
10212 progress->SetLine(1, CVariant{movie.m_strTitle});
10213 progress->SetPercentage(current * 100 / total);
10214 progress->Progress();
10215 if (progress->IsCanceled())
10217 progress->Close();
10218 m_pDS->close();
10219 return;
10223 CFileItem item(movie.m_strFileNameAndPath,false);
10224 if (!singleFile && CUtil::SupportsWriteFileOperations(movie.m_strFileNameAndPath))
10226 if (!item.Exists(false))
10228 CLog::Log(LOGINFO, "{} - Not exporting item {} as it does not exist", __FUNCTION__,
10229 movie.m_strFileNameAndPath);
10230 bSkip = true;
10232 else
10234 std::string nfoFile(URIUtils::ReplaceExtension(item.GetTBNFile(), ".nfo"));
10236 if (overwrite || !CFile::Exists(nfoFile, false))
10238 if(!xmlDoc.SaveFile(nfoFile))
10240 CLog::Log(LOGERROR, "{}: Musicvideo nfo export failed! ('{}')", __FUNCTION__,
10241 nfoFile);
10242 CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Error, g_localizeStrings.Get(20302), nfoFile);
10243 iFailCount++;
10248 if (!singleFile)
10250 xmlDoc.Clear();
10251 TiXmlDeclaration decl("1.0", "UTF-8", "yes");
10252 xmlDoc.InsertEndChild(decl);
10254 if (images && !bSkip)
10256 if (singleFile)
10258 std::string strFileName(StringUtils::Join(movie.m_artist, CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_videoItemSeparator) + "." + movie.m_strTitle);
10259 if (movie.HasYear())
10260 strFileName += StringUtils::Format("_{}", movie.GetYear());
10261 item.SetPath(GetSafeFile(musicvideosDir, strFileName) + ".avi");
10263 for (const auto &i : artwork)
10265 std::string savedThumb = item.GetLocalArt(i.first, false);
10266 CServiceBroker::GetTextureCache()->Export(i.second, savedThumb, overwrite);
10269 m_pDS->next();
10270 current++;
10272 m_pDS->close();
10274 // repeat for all tvshows
10275 sql = "SELECT * FROM tvshow_view";
10276 m_pDS->query(sql);
10278 total = m_pDS->num_rows();
10279 current = 0;
10281 while (!m_pDS->eof())
10283 CVideoInfoTag tvshow = GetDetailsForTvShow(m_pDS, VideoDbDetailsAll);
10284 GetTvShowNamedSeasons(tvshow.m_iDbId, tvshow.m_namedSeasons);
10286 std::map<int, std::map<std::string, std::string> > seasonArt;
10287 GetTvShowSeasonArt(tvshow.m_iDbId, seasonArt);
10289 std::map<std::string, std::string> artwork;
10290 if (GetArtForItem(tvshow.m_iDbId, tvshow.m_type, artwork) && singleFile)
10292 TiXmlElement additionalNode("art");
10293 for (const auto &i : artwork)
10294 XMLUtils::SetString(&additionalNode, i.first.c_str(), i.second);
10295 for (const auto &i : seasonArt)
10297 TiXmlElement seasonNode("season");
10298 seasonNode.SetAttribute("num", i.first);
10299 for (const auto &j : i.second)
10300 XMLUtils::SetString(&seasonNode, j.first.c_str(), j.second);
10301 additionalNode.InsertEndChild(seasonNode);
10303 tvshow.Save(pMain, "tvshow", true, &additionalNode);
10305 else
10306 tvshow.Save(pMain, "tvshow", singleFile);
10308 // reset old skip state
10309 bool bSkip = false;
10311 if (progress)
10313 progress->SetLine(1, CVariant{tvshow.m_strTitle});
10314 progress->SetPercentage(current * 100 / total);
10315 progress->Progress();
10316 if (progress->IsCanceled())
10318 progress->Close();
10319 m_pDS->close();
10320 return;
10324 CFileItem item(tvshow.m_strPath, true);
10325 if (!singleFile && CUtil::SupportsWriteFileOperations(tvshow.m_strPath))
10327 if (!item.Exists(false))
10329 CLog::Log(LOGINFO, "{} - Not exporting item {} as it does not exist", __FUNCTION__,
10330 tvshow.m_strPath);
10331 bSkip = true;
10333 else
10335 std::string nfoFile = URIUtils::AddFileToFolder(tvshow.m_strPath, "tvshow.nfo");
10337 if (overwrite || !CFile::Exists(nfoFile, false))
10339 if(!xmlDoc.SaveFile(nfoFile))
10341 CLog::Log(LOGERROR, "{}: TVShow nfo export failed! ('{}')", __FUNCTION__, nfoFile);
10342 CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Error, g_localizeStrings.Get(20302), nfoFile);
10343 iFailCount++;
10348 if (!singleFile)
10350 xmlDoc.Clear();
10351 TiXmlDeclaration decl("1.0", "UTF-8", "yes");
10352 xmlDoc.InsertEndChild(decl);
10354 if (images && !bSkip)
10356 if (singleFile)
10357 item.SetPath(GetSafeFile(tvshowsDir, tvshow.m_strTitle));
10359 for (const auto &i : artwork)
10361 std::string savedThumb = item.GetLocalArt(i.first, true);
10362 CServiceBroker::GetTextureCache()->Export(i.second, savedThumb, overwrite);
10365 if (actorThumbs)
10366 ExportActorThumbs(actorsDir, tvshow, !singleFile, overwrite);
10368 // export season thumbs
10369 for (const auto &i : seasonArt)
10371 std::string seasonThumb;
10372 if (i.first == -1)
10373 seasonThumb = "season-all";
10374 else if (i.first == 0)
10375 seasonThumb = "season-specials";
10376 else
10377 seasonThumb = StringUtils::Format("season{:02}", i.first);
10378 for (const auto &j : i.second)
10380 std::string savedThumb(item.GetLocalArt(seasonThumb + "-" + j.first, true));
10381 if (!i.second.empty())
10382 CServiceBroker::GetTextureCache()->Export(j.second, savedThumb, overwrite);
10387 // now save the episodes from this show
10388 sql = PrepareSQL("select * from episode_view where idShow=%i order by strFileName, idEpisode",tvshow.m_iDbId);
10389 pDS->query(sql);
10390 std::string showDir(item.GetPath());
10392 while (!pDS->eof())
10394 CVideoInfoTag episode = GetDetailsForEpisode(pDS, VideoDbDetailsAll);
10395 std::map<std::string, std::string> artwork;
10396 if (GetArtForItem(episode.m_iDbId, MediaTypeEpisode, artwork) && singleFile)
10398 TiXmlElement additionalNode("art");
10399 for (const auto &i : artwork)
10400 XMLUtils::SetString(&additionalNode, i.first.c_str(), i.second);
10401 episode.Save(pMain->LastChild(), "episodedetails", true, &additionalNode);
10403 else if (singleFile)
10404 episode.Save(pMain->LastChild(), "episodedetails", singleFile);
10405 else
10406 episode.Save(pMain, "episodedetails", singleFile);
10407 pDS->next();
10408 // multi-episode files need dumping to the same XML
10409 while (!singleFile && !pDS->eof() &&
10410 episode.m_iFileId == pDS->fv("idFile").get_asInt())
10412 episode = GetDetailsForEpisode(pDS, VideoDbDetailsAll);
10413 episode.Save(pMain, "episodedetails", singleFile);
10414 pDS->next();
10417 // reset old skip state
10418 bool bSkip = false;
10420 CFileItem item(episode.m_strFileNameAndPath, false);
10421 if (!singleFile && CUtil::SupportsWriteFileOperations(episode.m_strFileNameAndPath))
10423 if (!item.Exists(false))
10425 CLog::Log(LOGINFO, "{} - Not exporting item {} as it does not exist", __FUNCTION__,
10426 episode.m_strFileNameAndPath);
10427 bSkip = true;
10429 else
10431 std::string nfoFile(URIUtils::ReplaceExtension(item.GetTBNFile(), ".nfo"));
10433 if (overwrite || !CFile::Exists(nfoFile, false))
10435 if(!xmlDoc.SaveFile(nfoFile))
10437 CLog::Log(LOGERROR, "{}: Episode nfo export failed! ('{}')", __FUNCTION__, nfoFile);
10438 CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Error, g_localizeStrings.Get(20302), nfoFile);
10439 iFailCount++;
10444 if (!singleFile)
10446 xmlDoc.Clear();
10447 TiXmlDeclaration decl("1.0", "UTF-8", "yes");
10448 xmlDoc.InsertEndChild(decl);
10451 if (images && !bSkip)
10453 if (singleFile)
10455 std::string epName =
10456 StringUtils::Format("s{:02}e{:02}.avi", episode.m_iSeason, episode.m_iEpisode);
10457 item.SetPath(URIUtils::AddFileToFolder(showDir, epName));
10459 for (const auto &i : artwork)
10461 std::string savedThumb = item.GetLocalArt(i.first, false);
10462 CServiceBroker::GetTextureCache()->Export(i.second, savedThumb, overwrite);
10464 if (actorThumbs)
10465 ExportActorThumbs(actorsDir, episode, !singleFile, overwrite);
10468 pDS->close();
10469 m_pDS->next();
10470 current++;
10472 m_pDS->close();
10474 if (!singleFile && progress)
10476 progress->SetPercentage(100);
10477 progress->Progress();
10480 if (singleFile)
10482 // now dump path info
10483 std::set<std::string> paths;
10484 GetPaths(paths);
10485 TiXmlElement xmlPathElement("paths");
10486 TiXmlNode *pPaths = pMain->InsertEndChild(xmlPathElement);
10487 for (const auto &i : paths)
10489 bool foundDirectly = false;
10490 SScanSettings settings;
10491 ScraperPtr info = GetScraperForPath(i, settings, foundDirectly);
10492 if (info && foundDirectly)
10494 TiXmlElement xmlPathElement2("path");
10495 TiXmlNode *pPath = pPaths->InsertEndChild(xmlPathElement2);
10496 XMLUtils::SetString(pPath,"url", i);
10497 XMLUtils::SetInt(pPath,"scanrecursive", settings.recurse);
10498 XMLUtils::SetBoolean(pPath,"usefoldernames", settings.parent_name);
10499 XMLUtils::SetString(pPath,"content", TranslateContent(info->Content()));
10500 XMLUtils::SetString(pPath,"scraperpath", info->ID());
10503 xmlDoc.SaveFile(xmlFile);
10505 CVariant data;
10506 if (singleFile)
10508 data["root"] = exportRoot;
10509 data["file"] = xmlFile;
10510 if (iFailCount > 0)
10511 data["failcount"] = iFailCount;
10513 CServiceBroker::GetAnnouncementManager()->Announce(ANNOUNCEMENT::VideoLibrary, "OnExport",
10514 data);
10516 catch (...)
10518 CLog::Log(LOGERROR, "{} failed", __FUNCTION__);
10519 iFailCount++;
10522 if (progress)
10523 progress->Close();
10525 if (iFailCount > 0)
10526 HELPERS::ShowOKDialogText(
10527 CVariant{647}, CVariant{StringUtils::Format(g_localizeStrings.Get(15011), iFailCount)});
10530 void CVideoDatabase::ExportActorThumbs(const std::string &strDir, const CVideoInfoTag &tag, bool singleFiles, bool overwrite /*=false*/)
10532 std::string strPath(strDir);
10533 if (singleFiles)
10535 strPath = URIUtils::AddFileToFolder(tag.m_strPath, ".actors");
10536 if (!CDirectory::Exists(strPath))
10538 CDirectory::Create(strPath);
10539 CFile::SetHidden(strPath, true);
10543 for (const auto &i : tag.m_cast)
10545 CFileItem item;
10546 item.SetLabel(i.strName);
10547 if (!i.thumb.empty())
10549 std::string thumbFile(GetSafeFile(strPath, i.strName));
10550 CServiceBroker::GetTextureCache()->Export(i.thumb, thumbFile, overwrite);
10555 void CVideoDatabase::ImportFromXML(const std::string &path)
10557 CGUIDialogProgress *progress=NULL;
10560 if (nullptr == m_pDB)
10561 return;
10562 if (nullptr == m_pDS)
10563 return;
10565 CXBMCTinyXML xmlDoc;
10566 if (!xmlDoc.LoadFile(URIUtils::AddFileToFolder(path, "videodb.xml")))
10567 return;
10569 TiXmlElement *root = xmlDoc.RootElement();
10570 if (!root) return;
10572 progress = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogProgress>(WINDOW_DIALOG_PROGRESS);
10573 if (progress)
10575 progress->SetHeading(CVariant{648});
10576 progress->SetLine(0, CVariant{649});
10577 progress->SetLine(1, CVariant{330});
10578 progress->SetLine(2, CVariant{""});
10579 progress->SetPercentage(0);
10580 progress->Open();
10581 progress->ShowProgressBar(true);
10584 int iVersion = 0;
10585 XMLUtils::GetInt(root, "version", iVersion);
10587 CLog::Log(LOGINFO, "{}: Starting import (export version = {})", __FUNCTION__, iVersion);
10589 TiXmlElement *movie = root->FirstChildElement();
10590 int current = 0;
10591 int total = 0;
10592 // first count the number of items...
10593 while (movie)
10595 if (StringUtils::CompareNoCase(movie->Value(), MediaTypeMovie, 5) == 0 ||
10596 StringUtils::CompareNoCase(movie->Value(), MediaTypeTvShow, 6) == 0 ||
10597 StringUtils::CompareNoCase(movie->Value(), MediaTypeMusicVideo, 10) == 0)
10598 total++;
10599 movie = movie->NextSiblingElement();
10602 std::string actorsDir(URIUtils::AddFileToFolder(path, "actors"));
10603 std::string moviesDir(URIUtils::AddFileToFolder(path, "movies"));
10604 std::string movieSetsDir(URIUtils::AddFileToFolder(path, "moviesets"));
10605 std::string musicvideosDir(URIUtils::AddFileToFolder(path, "musicvideos"));
10606 std::string tvshowsDir(URIUtils::AddFileToFolder(path, "tvshows"));
10607 CVideoInfoScanner scanner;
10608 // add paths first (so we have scraper settings available)
10609 TiXmlElement *path = root->FirstChildElement("paths");
10610 path = path->FirstChildElement();
10611 while (path)
10613 std::string strPath;
10614 if (XMLUtils::GetString(path,"url",strPath) && !strPath.empty())
10615 AddPath(strPath);
10617 std::string content;
10618 if (XMLUtils::GetString(path,"content", content) && !content.empty())
10619 { // check the scraper exists, if so store the path
10620 AddonPtr addon;
10621 std::string id;
10622 XMLUtils::GetString(path,"scraperpath",id);
10623 if (CServiceBroker::GetAddonMgr().GetAddon(id, addon, ADDON::OnlyEnabled::CHOICE_YES))
10625 SScanSettings settings;
10626 ScraperPtr scraper = std::dynamic_pointer_cast<CScraper>(addon);
10627 // FIXME: scraper settings are not exported?
10628 scraper->SetPathSettings(TranslateContent(content), "");
10629 XMLUtils::GetInt(path,"scanrecursive",settings.recurse);
10630 XMLUtils::GetBoolean(path,"usefoldernames",settings.parent_name);
10631 SetScraperForPath(strPath,scraper,settings);
10634 path = path->NextSiblingElement();
10636 movie = root->FirstChildElement();
10637 while (movie)
10639 CVideoInfoTag info;
10640 if (StringUtils::CompareNoCase(movie->Value(), MediaTypeMovie, 5) == 0)
10642 info.Load(movie);
10643 CFileItem item(info);
10644 bool useFolders = info.m_basePath.empty() ? LookupByFolders(item.GetPath()) : false;
10645 std::string filename = info.m_strTitle;
10646 if (info.HasYear())
10647 filename += StringUtils::Format("_{}", info.GetYear());
10648 CFileItem artItem(item);
10649 artItem.SetPath(GetSafeFile(moviesDir, filename) + ".avi");
10650 scanner.GetArtwork(&artItem, CONTENT_MOVIES, useFolders, true, actorsDir);
10651 item.SetArt(artItem.GetArt());
10652 if (!item.GetVideoInfoTag()->m_set.title.empty())
10654 std::string setPath = URIUtils::AddFileToFolder(movieSetsDir,
10655 CUtil::MakeLegalFileName(item.GetVideoInfoTag()->m_set.title, LEGAL_WIN32_COMPAT));
10656 if (CDirectory::Exists(setPath))
10658 CGUIListItem::ArtMap setArt;
10659 CFileItem artItem(setPath, true);
10660 for (const auto& artType : CVideoThumbLoader::GetArtTypes(MediaTypeVideoCollection))
10662 std::string artPath = CVideoThumbLoader::GetLocalArt(artItem, artType, true);
10663 if (!artPath.empty())
10665 setArt[artType] = artPath;
10668 item.AppendArt(setArt, "set");
10671 scanner.AddVideo(&item, CONTENT_MOVIES, useFolders, true, NULL, true);
10672 current++;
10674 else if (StringUtils::CompareNoCase(movie->Value(), MediaTypeMusicVideo, 10) == 0)
10676 info.Load(movie);
10677 CFileItem item(info);
10678 bool useFolders = info.m_basePath.empty() ? LookupByFolders(item.GetPath()) : false;
10679 std::string filename = StringUtils::Join(info.m_artist, CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_videoItemSeparator) + "." + info.m_strTitle;
10680 if (info.HasYear())
10681 filename += StringUtils::Format("_{}", info.GetYear());
10682 CFileItem artItem(item);
10683 artItem.SetPath(GetSafeFile(musicvideosDir, filename) + ".avi");
10684 scanner.GetArtwork(&artItem, CONTENT_MUSICVIDEOS, useFolders, true, actorsDir);
10685 item.SetArt(artItem.GetArt());
10686 scanner.AddVideo(&item, CONTENT_MUSICVIDEOS, useFolders, true, NULL, true);
10687 current++;
10689 else if (StringUtils::CompareNoCase(movie->Value(), MediaTypeTvShow, 6) == 0)
10691 // load the TV show in. NOTE: This deletes all episodes under the TV Show, which may not be
10692 // what we desire. It may make better sense to only delete (or even better, update) the show information
10693 info.Load(movie);
10694 URIUtils::AddSlashAtEnd(info.m_strPath);
10695 DeleteTvShow(info.m_strPath);
10696 CFileItem showItem(info);
10697 bool useFolders = info.m_basePath.empty() ? LookupByFolders(showItem.GetPath(), true) : false;
10698 CFileItem artItem(showItem);
10699 std::string artPath(GetSafeFile(tvshowsDir, info.m_strTitle));
10700 artItem.SetPath(artPath);
10701 scanner.GetArtwork(&artItem, CONTENT_TVSHOWS, useFolders, true, actorsDir);
10702 showItem.SetArt(artItem.GetArt());
10703 int showID = scanner.AddVideo(&showItem, CONTENT_TVSHOWS, useFolders, true, NULL, true);
10704 // season artwork
10705 std::map<int, std::map<std::string, std::string> > seasonArt;
10706 artItem.GetVideoInfoTag()->m_strPath = artPath;
10707 scanner.GetSeasonThumbs(*artItem.GetVideoInfoTag(), seasonArt, CVideoThumbLoader::GetArtTypes(MediaTypeSeason), true);
10708 for (const auto &i : seasonArt)
10710 int seasonID = AddSeason(showID, i.first);
10711 SetArtForItem(seasonID, MediaTypeSeason, i.second);
10713 current++;
10714 // now load the episodes
10715 TiXmlElement *episode = movie->FirstChildElement("episodedetails");
10716 while (episode)
10718 // no need to delete the episode info, due to the above deletion
10719 CVideoInfoTag info;
10720 info.Load(episode);
10721 CFileItem item(info);
10722 std::string filename =
10723 StringUtils::Format("s{:02}e{:02}.avi", info.m_iSeason, info.m_iEpisode);
10724 CFileItem artItem(item);
10725 artItem.SetPath(GetSafeFile(artPath, filename));
10726 scanner.GetArtwork(&artItem, CONTENT_TVSHOWS, useFolders, true, actorsDir);
10727 item.SetArt(artItem.GetArt());
10728 scanner.AddVideo(&item,CONTENT_TVSHOWS, false, false, showItem.GetVideoInfoTag(), true);
10729 episode = episode->NextSiblingElement("episodedetails");
10732 movie = movie->NextSiblingElement();
10733 if (progress && total)
10735 progress->SetPercentage(current * 100 / total);
10736 progress->SetLine(2, CVariant{info.m_strTitle});
10737 progress->Progress();
10738 if (progress->IsCanceled())
10740 progress->Close();
10741 return;
10746 catch (...)
10748 CLog::Log(LOGERROR, "{} failed", __FUNCTION__);
10750 if (progress)
10751 progress->Close();
10754 bool CVideoDatabase::ImportArtFromXML(const TiXmlNode *node, std::map<std::string, std::string> &artwork)
10756 if (!node) return false;
10757 const TiXmlNode *art = node->FirstChild();
10758 while (art && art->FirstChild())
10760 artwork.insert(make_pair(art->ValueStr(), art->FirstChild()->ValueStr()));
10761 art = art->NextSibling();
10763 return !artwork.empty();
10766 void CVideoDatabase::ConstructPath(std::string& strDest, const std::string& strPath, const std::string& strFileName)
10768 if (URIUtils::IsStack(strFileName) ||
10769 URIUtils::IsInArchive(strFileName) || URIUtils::IsPlugin(strPath))
10770 strDest = strFileName;
10771 else
10772 strDest = URIUtils::AddFileToFolder(strPath, strFileName);
10775 void CVideoDatabase::SplitPath(const std::string& strFileNameAndPath, std::string& strPath, std::string& strFileName)
10777 if (URIUtils::IsStack(strFileNameAndPath) || StringUtils::StartsWithNoCase(strFileNameAndPath, "rar://") || StringUtils::StartsWithNoCase(strFileNameAndPath, "zip://"))
10779 URIUtils::GetParentPath(strFileNameAndPath,strPath);
10780 strFileName = strFileNameAndPath;
10782 else if (URIUtils::IsPlugin(strFileNameAndPath))
10784 CURL url(strFileNameAndPath);
10785 strPath = url.GetOptions().empty() ? url.GetWithoutFilename() : url.GetWithoutOptions();
10786 strFileName = strFileNameAndPath;
10788 else
10790 URIUtils::Split(strFileNameAndPath, strPath, strFileName);
10791 // Keep protocol options as part of the path
10792 if (URIUtils::IsURL(strFileNameAndPath))
10794 CURL url(strFileNameAndPath);
10795 if (!url.GetProtocolOptions().empty())
10796 strPath += "|" + url.GetProtocolOptions();
10801 void CVideoDatabase::InvalidatePathHash(const std::string& strPath)
10803 SScanSettings settings;
10804 bool foundDirectly;
10805 ScraperPtr info = GetScraperForPath(strPath,settings,foundDirectly);
10806 SetPathHash(strPath,"");
10807 if (!info)
10808 return;
10809 if (info->Content() == CONTENT_TVSHOWS || (info->Content() == CONTENT_MOVIES && !foundDirectly)) // if we scan by folder name we need to invalidate parent as well
10811 if (info->Content() == CONTENT_TVSHOWS || settings.parent_name_root)
10813 std::string strParent;
10814 if (URIUtils::GetParentPath(strPath, strParent) && (!URIUtils::IsPlugin(strPath) || !CURL(strParent).GetHostName().empty()))
10815 SetPathHash(strParent, "");
10820 bool CVideoDatabase::CommitTransaction()
10822 if (CDatabase::CommitTransaction())
10823 { // number of items in the db has likely changed, so recalculate
10824 GUIINFO::CLibraryGUIInfo& guiInfo = CServiceBroker::GetGUI()->GetInfoManager().GetInfoProviders().GetLibraryInfoProvider();
10825 guiInfo.SetLibraryBool(LIBRARY_HAS_MOVIES, HasContent(VideoDbContentType::MOVIES));
10826 guiInfo.SetLibraryBool(LIBRARY_HAS_TVSHOWS, HasContent(VideoDbContentType::TVSHOWS));
10827 guiInfo.SetLibraryBool(LIBRARY_HAS_MUSICVIDEOS, HasContent(VideoDbContentType::MUSICVIDEOS));
10828 return true;
10830 return false;
10833 bool CVideoDatabase::SetSingleValue(VideoDbContentType type,
10834 int dbId,
10835 int dbField,
10836 const std::string& strValue)
10838 std::string strSQL;
10841 if (nullptr == m_pDB || nullptr == m_pDS)
10842 return false;
10844 std::string strTable, strField;
10845 if (type == VideoDbContentType::MOVIES)
10847 strTable = "movie";
10848 strField = "idMovie";
10850 else if (type == VideoDbContentType::TVSHOWS)
10852 strTable = "tvshow";
10853 strField = "idShow";
10855 else if (type == VideoDbContentType::EPISODES)
10857 strTable = "episode";
10858 strField = "idEpisode";
10860 else if (type == VideoDbContentType::MUSICVIDEOS)
10862 strTable = "musicvideo";
10863 strField = "idMVideo";
10866 if (strTable.empty())
10867 return false;
10869 return SetSingleValue(strTable, StringUtils::Format("c{:02}", dbField), strValue, strField,
10870 dbId);
10872 catch (...)
10874 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, strSQL);
10876 return false;
10879 bool CVideoDatabase::SetSingleValue(VideoDbContentType type,
10880 int dbId,
10881 Field dbField,
10882 const std::string& strValue)
10884 MediaType mediaType = DatabaseUtils::MediaTypeFromVideoContentType(type);
10885 if (mediaType == MediaTypeNone)
10886 return false;
10888 int dbFieldIndex = DatabaseUtils::GetField(dbField, mediaType);
10889 if (dbFieldIndex < 0)
10890 return false;
10892 return SetSingleValue(type, dbId, dbFieldIndex, strValue);
10895 bool CVideoDatabase::SetSingleValue(const std::string &table, const std::string &fieldName, const std::string &strValue,
10896 const std::string &conditionName /* = "" */, int conditionValue /* = -1 */)
10898 if (table.empty() || fieldName.empty())
10899 return false;
10901 std::string sql;
10904 if (nullptr == m_pDB || nullptr == m_pDS)
10905 return false;
10907 sql = PrepareSQL("UPDATE %s SET %s='%s'", table.c_str(), fieldName.c_str(), strValue.c_str());
10908 if (!conditionName.empty())
10909 sql += PrepareSQL(" WHERE %s=%u", conditionName.c_str(), conditionValue);
10910 if (m_pDS->exec(sql) == 0)
10911 return true;
10913 catch (...)
10915 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, sql);
10917 return false;
10920 std::string CVideoDatabase::GetSafeFile(const std::string &dir, const std::string &name) const
10922 std::string safeThumb(name);
10923 StringUtils::Replace(safeThumb, ' ', '_');
10924 return URIUtils::AddFileToFolder(dir, CUtil::MakeLegalFileName(safeThumb));
10927 void CVideoDatabase::AnnounceRemove(const std::string& content, int id, bool scanning /* = false */)
10929 CVariant data;
10930 data["type"] = content;
10931 data["id"] = id;
10932 if (scanning)
10933 data["transaction"] = true;
10934 CServiceBroker::GetAnnouncementManager()->Announce(ANNOUNCEMENT::VideoLibrary, "OnRemove", data);
10937 void CVideoDatabase::AnnounceUpdate(const std::string& content, int id)
10939 CVariant data;
10940 data["type"] = content;
10941 data["id"] = id;
10942 CServiceBroker::GetAnnouncementManager()->Announce(ANNOUNCEMENT::VideoLibrary, "OnUpdate", data);
10945 bool CVideoDatabase::GetItemsForPath(const std::string &content, const std::string &strPath, CFileItemList &items)
10947 const std::string& path(strPath);
10949 if(URIUtils::IsMultiPath(path))
10951 std::vector<std::string> paths;
10952 CMultiPathDirectory::GetPaths(path, paths);
10954 for(unsigned i=0;i<paths.size();i++)
10955 GetItemsForPath(content, paths[i], items);
10957 return items.Size() > 0;
10960 int pathID = GetPathId(path);
10961 if (pathID < 0)
10962 return false;
10964 if (content == "movies")
10966 Filter filter(PrepareSQL("c%02d=%d", VIDEODB_ID_PARENTPATHID, pathID));
10967 GetMoviesByWhere("videodb://movies/titles/", filter, items);
10969 else if (content == "episodes")
10971 Filter filter(PrepareSQL("c%02d=%d", VIDEODB_ID_EPISODE_PARENTPATHID, pathID));
10972 GetEpisodesByWhere("videodb://tvshows/titles/", filter, items);
10974 else if (content == "tvshows")
10976 Filter filter(PrepareSQL("idParentPath=%d", pathID));
10977 GetTvShowsByWhere("videodb://tvshows/titles/", filter, items);
10979 else if (content == "musicvideos")
10981 Filter filter(PrepareSQL("c%02d=%d", VIDEODB_ID_MUSICVIDEO_PARENTPATHID, pathID));
10982 GetMusicVideosByWhere("videodb://musicvideos/titles/", filter, items);
10984 for (int i = 0; i < items.Size(); i++)
10985 items[i]->SetPath(items[i]->GetVideoInfoTag()->m_basePath);
10986 return items.Size() > 0;
10989 void CVideoDatabase::AppendIdLinkFilter(const char* field, const char *table, const MediaType& mediaType, const char *view, const char *viewKey, const CUrlOptions::UrlOptions& options, Filter &filter)
10991 auto option = options.find((std::string)field + "id");
10992 if (option == options.end())
10993 return;
10995 filter.AppendJoin(PrepareSQL("JOIN %s_link ON %s_link.media_id=%s_view.%s AND %s_link.media_type='%s'", field, field, view, viewKey, field, mediaType.c_str()));
10996 filter.AppendWhere(PrepareSQL("%s_link.%s_id = %i", field, table, (int)option->second.asInteger()));
10999 void CVideoDatabase::AppendLinkFilter(const char* field, const char *table, const MediaType& mediaType, const char *view, const char *viewKey, const CUrlOptions::UrlOptions& options, Filter &filter)
11001 auto option = options.find(field);
11002 if (option == options.end())
11003 return;
11005 filter.AppendJoin(PrepareSQL("JOIN %s_link ON %s_link.media_id=%s_view.%s AND %s_link.media_type='%s'", field, field, view, viewKey, field, mediaType.c_str()));
11006 filter.AppendJoin(PrepareSQL("JOIN %s ON %s.%s_id=%s_link.%s_id", table, table, field, table, field));
11007 filter.AppendWhere(PrepareSQL("%s.name like '%s'", table, option->second.asString().c_str()));
11010 bool CVideoDatabase::GetFilter(CDbUrl &videoUrl, Filter &filter, SortDescription &sorting)
11012 if (!videoUrl.IsValid())
11013 return false;
11015 std::string type = videoUrl.GetType();
11016 std::string itemType = ((const CVideoDbUrl &)videoUrl).GetItemType();
11017 const CUrlOptions::UrlOptions& options = videoUrl.GetOptions();
11019 if (type == "movies")
11021 AppendIdLinkFilter("genre", "genre", "movie", "movie", "idMovie", options, filter);
11022 AppendLinkFilter("genre", "genre", "movie", "movie", "idMovie", options, filter);
11024 AppendIdLinkFilter("country", "country", "movie", "movie", "idMovie", options, filter);
11025 AppendLinkFilter("country", "country", "movie", "movie", "idMovie", options, filter);
11027 AppendIdLinkFilter("studio", "studio", "movie", "movie", "idMovie", options, filter);
11028 AppendLinkFilter("studio", "studio", "movie", "movie", "idMovie", options, filter);
11030 AppendIdLinkFilter("director", "actor", "movie", "movie", "idMovie", options, filter);
11031 AppendLinkFilter("director", "actor", "movie", "movie", "idMovie", options, filter);
11033 auto option = options.find("year");
11034 if (option != options.end())
11035 filter.AppendWhere(PrepareSQL("movie_view.premiered like '%i%%'", (int)option->second.asInteger()));
11037 AppendIdLinkFilter("actor", "actor", "movie", "movie", "idMovie", options, filter);
11038 AppendLinkFilter("actor", "actor", "movie", "movie", "idMovie", options, filter);
11040 option = options.find("setid");
11041 if (option != options.end())
11042 filter.AppendWhere(PrepareSQL("movie_view.idSet = %i", (int)option->second.asInteger()));
11044 option = options.find("set");
11045 if (option != options.end())
11046 filter.AppendWhere(PrepareSQL("movie_view.strSet LIKE '%s'", option->second.asString().c_str()));
11048 AppendIdLinkFilter("tag", "tag", "movie", "movie", "idMovie", options, filter);
11049 AppendLinkFilter("tag", "tag", "movie", "movie", "idMovie", options, filter);
11051 else if (type == "tvshows")
11053 if (itemType == "tvshows")
11055 AppendIdLinkFilter("genre", "genre", "tvshow", "tvshow", "idShow", options, filter);
11056 AppendLinkFilter("genre", "genre", "tvshow", "tvshow", "idShow", options, filter);
11058 AppendIdLinkFilter("studio", "studio", "tvshow", "tvshow", "idShow", options, filter);
11059 AppendLinkFilter("studio", "studio", "tvshow", "tvshow", "idShow", options, filter);
11061 AppendIdLinkFilter("director", "actor", "tvshow", "tvshow", "idShow", options, filter);
11063 auto option = options.find("year");
11064 if (option != options.end())
11065 filter.AppendWhere(PrepareSQL("tvshow_view.c%02d like '%%%i%%'", VIDEODB_ID_TV_PREMIERED, (int)option->second.asInteger()));
11067 AppendIdLinkFilter("actor", "actor", "tvshow", "tvshow", "idShow", options, filter);
11068 AppendLinkFilter("actor", "actor", "tvshow", "tvshow", "idShow", options, filter);
11070 AppendIdLinkFilter("tag", "tag", "tvshow", "tvshow", "idShow", options, filter);
11071 AppendLinkFilter("tag", "tag", "tvshow", "tvshow", "idShow", options, filter);
11073 else if (itemType == "seasons")
11075 auto option = options.find("tvshowid");
11076 if (option != options.end())
11077 filter.AppendWhere(PrepareSQL("season_view.idShow = %i", (int)option->second.asInteger()));
11079 AppendIdLinkFilter("genre", "genre", "tvshow", "season", "idShow", options, filter);
11081 AppendIdLinkFilter("director", "actor", "tvshow", "season", "idShow", options, filter);
11083 option = options.find("year");
11084 if (option != options.end())
11085 filter.AppendWhere(PrepareSQL("season_view.premiered like '%%%i%%'", (int)option->second.asInteger()));
11087 AppendIdLinkFilter("actor", "actor", "tvshow", "season", "idShow", options, filter);
11089 else if (itemType == "episodes")
11091 int idShow = -1;
11092 auto option = options.find("tvshowid");
11093 if (option != options.end())
11094 idShow = (int)option->second.asInteger();
11096 int season = -1;
11097 option = options.find("season");
11098 if (option != options.end())
11099 season = (int)option->second.asInteger();
11101 if (idShow > -1)
11103 bool condition = false;
11105 AppendIdLinkFilter("genre", "genre", "tvshow", "episode", "idShow", options, filter);
11106 AppendLinkFilter("genre", "genre", "tvshow", "episode", "idShow", options, filter);
11108 AppendIdLinkFilter("director", "actor", "tvshow", "episode", "idShow", options, filter);
11109 AppendLinkFilter("director", "actor", "tvshow", "episode", "idShow", options, filter);
11111 option = options.find("year");
11112 if (option != options.end())
11114 condition = true;
11115 filter.AppendWhere(PrepareSQL("episode_view.idShow = %i and episode_view.premiered like '%%%i%%'", idShow, (int)option->second.asInteger()));
11118 AppendIdLinkFilter("actor", "actor", "tvshow", "episode", "idShow", options, filter);
11119 AppendLinkFilter("actor", "actor", "tvshow", "episode", "idShow", options, filter);
11121 if (!condition)
11122 filter.AppendWhere(PrepareSQL("episode_view.idShow = %i", idShow));
11124 if (season > -1)
11126 if (season == 0) // season = 0 indicates a special - we grab all specials here (see below)
11127 filter.AppendWhere(PrepareSQL("episode_view.c%02d = %i", VIDEODB_ID_EPISODE_SEASON, season));
11128 else
11129 filter.AppendWhere(PrepareSQL("(episode_view.c%02d = %i or (episode_view.c%02d = 0 and (episode_view.c%02d = 0 or episode_view.c%02d = %i)))",
11130 VIDEODB_ID_EPISODE_SEASON, season, VIDEODB_ID_EPISODE_SEASON, VIDEODB_ID_EPISODE_SORTSEASON, VIDEODB_ID_EPISODE_SORTSEASON, season));
11133 else
11135 option = options.find("year");
11136 if (option != options.end())
11137 filter.AppendWhere(PrepareSQL("episode_view.premiered like '%%%i%%'", (int)option->second.asInteger()));
11139 AppendIdLinkFilter("director", "actor", "episode", "episode", "idEpisode", options, filter);
11140 AppendLinkFilter("director", "actor", "episode", "episode", "idEpisode", options, filter);
11144 else if (type == "musicvideos")
11146 AppendIdLinkFilter("genre", "genre", "musicvideo", "musicvideo", "idMVideo", options, filter);
11147 AppendLinkFilter("genre", "genre", "musicvideo", "musicvideo", "idMVideo", options, filter);
11149 AppendIdLinkFilter("studio", "studio", "musicvideo", "musicvideo", "idMVideo", options, filter);
11150 AppendLinkFilter("studio", "studio", "musicvideo", "musicvideo", "idMVideo", options, filter);
11152 AppendIdLinkFilter("director", "actor", "musicvideo", "musicvideo", "idMVideo", options, filter);
11153 AppendLinkFilter("director", "actor", "musicvideo", "musicvideo", "idMVideo", options, filter);
11155 auto option = options.find("year");
11156 if (option != options.end())
11157 filter.AppendWhere(PrepareSQL("musicvideo_view.premiered like '%i%%'", (int)option->second.asInteger()));
11159 option = options.find("artistid");
11160 if (option != options.end())
11162 if (itemType != "albums")
11163 filter.AppendJoin(PrepareSQL("JOIN actor_link ON actor_link.media_id=musicvideo_view.idMVideo AND actor_link.media_type='musicvideo'"));
11164 filter.AppendWhere(PrepareSQL("actor_link.actor_id = %i", (int)option->second.asInteger()));
11167 option = options.find("artist");
11168 if (option != options.end())
11170 if (itemType != "albums")
11172 filter.AppendJoin(PrepareSQL("JOIN actor_link ON actor_link.media_id=musicvideo_view.idMVideo AND actor_link.media_type='musicvideo'"));
11173 filter.AppendJoin(PrepareSQL("JOIN actor ON actor.actor_id=actor_link.actor_id"));
11175 filter.AppendWhere(PrepareSQL("actor.name LIKE '%s'", option->second.asString().c_str()));
11178 option = options.find("albumid");
11179 if (option != options.end())
11180 filter.AppendWhere(PrepareSQL("musicvideo_view.c%02d = (select c%02d from musicvideo where idMVideo = %i)", VIDEODB_ID_MUSICVIDEO_ALBUM, VIDEODB_ID_MUSICVIDEO_ALBUM, (int)option->second.asInteger()));
11182 AppendIdLinkFilter("tag", "tag", "musicvideo", "musicvideo", "idMVideo", options, filter);
11183 AppendLinkFilter("tag", "tag", "musicvideo", "musicvideo", "idMVideo", options, filter);
11185 else
11186 return false;
11188 auto option = options.find("xsp");
11189 if (option != options.end())
11191 CSmartPlaylist xsp;
11192 if (!xsp.LoadFromJson(option->second.asString()))
11193 return false;
11195 // check if the filter playlist matches the item type
11196 if (xsp.GetType() == itemType ||
11197 (xsp.GetGroup() == itemType && !xsp.IsGroupMixed()) ||
11198 // handle episode listings with videodb://tvshows/titles/ which get the rest
11199 // of the path (season and episodeid) appended later
11200 (xsp.GetType() == "episodes" && itemType == "tvshows"))
11202 std::set<std::string> playlists;
11203 filter.AppendWhere(xsp.GetWhereClause(*this, playlists));
11205 if (xsp.GetLimit() > 0)
11206 sorting.limitEnd = xsp.GetLimit();
11207 if (xsp.GetOrder() != SortByNone)
11208 sorting.sortBy = xsp.GetOrder();
11209 if (xsp.GetOrderDirection() != SortOrderNone)
11210 sorting.sortOrder = xsp.GetOrderDirection();
11211 if (CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_FILELISTS_IGNORETHEWHENSORTING))
11212 sorting.sortAttributes = SortAttributeIgnoreArticle;
11216 option = options.find("filter");
11217 if (option != options.end())
11219 CSmartPlaylist xspFilter;
11220 if (!xspFilter.LoadFromJson(option->second.asString()))
11221 return false;
11223 // check if the filter playlist matches the item type
11224 if (xspFilter.GetType() == itemType)
11226 std::set<std::string> playlists;
11227 filter.AppendWhere(xspFilter.GetWhereClause(*this, playlists));
11229 // remove the filter if it doesn't match the item type
11230 else
11231 videoUrl.RemoveOption("filter");
11234 return true;
11237 bool CVideoDatabase::SetVideoUserRating(int dbId, int rating, const MediaType& mediaType)
11241 if (nullptr == m_pDB)
11242 return false;
11243 if (nullptr == m_pDS)
11244 return false;
11246 if (mediaType == MediaTypeNone)
11247 return false;
11249 std::string sql;
11250 if (mediaType == MediaTypeMovie)
11251 sql = PrepareSQL("UPDATE movie SET userrating=%i WHERE idMovie = %i", rating, dbId);
11252 else if (mediaType == MediaTypeEpisode)
11253 sql = PrepareSQL("UPDATE episode SET userrating=%i WHERE idEpisode = %i", rating, dbId);
11254 else if (mediaType == MediaTypeMusicVideo)
11255 sql = PrepareSQL("UPDATE musicvideo SET userrating=%i WHERE idMVideo = %i", rating, dbId);
11256 else if (mediaType == MediaTypeTvShow)
11257 sql = PrepareSQL("UPDATE tvshow SET userrating=%i WHERE idShow = %i", rating, dbId);
11258 else if (mediaType == MediaTypeSeason)
11259 sql = PrepareSQL("UPDATE seasons SET userrating=%i WHERE idSeason = %i", rating, dbId);
11261 m_pDS->exec(sql);
11262 return true;
11264 catch (...)
11266 CLog::Log(LOGERROR, "{} ({}, {}, {}) failed", __FUNCTION__, dbId, mediaType, rating);
11268 return false;
11271 CDateTime CVideoDatabase::GetDateAdded(const std::string& filename,
11272 CDateTime dateAdded /* = CDateTime() */)
11274 if (!dateAdded.IsValid())
11276 // suppress warnings if we have plugin source
11277 if (!URIUtils::IsPlugin(filename))
11279 const auto dateAddedSetting =
11280 CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_iVideoLibraryDateAdded;
11282 // 1 prefer using the files mtime (if it's valid) and
11283 // only use the file's ctime if mtime isn't valid
11284 if (dateAddedSetting == 1)
11285 dateAdded = CFileUtils::GetModificationDate(filename, false);
11286 // 2 use the newer datetime of the file's mtime and ctime
11287 else if (dateAddedSetting == 2)
11288 dateAdded = CFileUtils::GetModificationDate(filename, true);
11291 // 0 use the current datetime if non of the above match or one returns an invalid datetime
11292 if (!dateAdded.IsValid())
11293 dateAdded = CDateTime::GetCurrentDateTime();
11296 return dateAdded;
11299 void CVideoDatabase::EraseAllForPath(const std::string& path)
11303 std::string itemsToDelete;
11304 std::string sql =
11305 PrepareSQL("SELECT files.idFile FROM files WHERE idFile IN (SELECT idFile FROM files INNER "
11306 "JOIN path ON path.idPath = files.idPath AND path.strPath LIKE \"%s%%\")",
11307 path.c_str());
11309 m_pDS->query(sql);
11310 while (!m_pDS->eof())
11312 std::string file = m_pDS->fv("files.idFile").get_asString() + ",";
11313 itemsToDelete += file;
11314 m_pDS->next();
11316 m_pDS->close();
11318 sql = PrepareSQL("DELETE FROM path WHERE strPath LIKE \"%s%%\"", path.c_str());
11319 m_pDS->exec(sql);
11321 if (!itemsToDelete.empty())
11323 itemsToDelete = "(" + StringUtils::TrimRight(itemsToDelete, ",") + ")";
11325 sql = "DELETE FROM files WHERE idFile IN " + itemsToDelete;
11326 m_pDS->exec(sql);
11328 sql = "DELETE FROM settings WHERE idFile IN " + itemsToDelete;
11329 m_pDS->exec(sql);
11331 sql = "DELETE FROM bookmark WHERE idFile IN " + itemsToDelete;
11332 m_pDS->exec(sql);
11334 sql = "DELETE FROM streamdetails WHERE idFile IN " + itemsToDelete;
11335 m_pDS->exec(sql);
11338 catch (...)
11340 CLog::Log(LOGERROR, "{} failed", __FUNCTION__);