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.
9 #include "VideoDatabase.h"
12 #include "GUIInfoManager.h"
13 #include "GUIPassword.h"
14 #include "ServiceBroker.h"
15 #include "TextureCache.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"
63 #include <unordered_set>
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");
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
++)
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
);
151 column
= StringUtils::Format(",c{:02} text", i
);
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, "
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'; "
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'; "
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'; "
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'; "
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'; "
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'; "
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'); "
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); "
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; "
355 void CVideoDatabase::CreateViews()
357 CLog::Log(LOGINFO
, "create episode_view");
358 std::string episodeview
= PrepareSQL("CREATE VIEW episode_view AS SELECT "
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 "
380 " files.idFile=episode.idFile"
382 " tvshow.idShow=episode.idShow"
384 " seasons.idSeason=episode.idSeason"
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 "
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 "
424 " min(idPath) AS idPath "
425 "FROM tvshowlinkpath "
427 m_pDS
->exec(tvshowlinkpathview
);
429 CLog::Log(LOGINFO
, "create tvshow_view");
430 std::string tvshowview
= PrepareSQL("CREATE VIEW tvshow_view AS SELECT "
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 "
442 " LEFT JOIN tvshowlinkpath_minview ON "
443 " tvshowlinkpath_minview.idShow=tvshow.idShow"
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 "
473 " JOIN tvshow_view ON"
474 " tvshow_view.idShow = seasons.idShow"
476 " episode.idShow = seasons.idShow AND episode.c%02d = seasons.season"
478 " files.idFile = episode.idFile "
479 "GROUP BY seasons.idSeason,"
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"
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 "
514 " files.idFile=musicvideo.idFile"
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"
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 "
544 " sets.idSet = movie.idSet"
546 " files.idFile=movie.idFile"
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
)
566 if (nullptr == m_pDB
)
568 if (nullptr == m_pDS
)
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
);
580 idPath
= m_pDS
->fv("path.idPath").get_asInt();
587 CLog::Log(LOGERROR
, "{} unable to getpath ({})", __FUNCTION__
, strSQL
);
592 bool CVideoDatabase::GetPaths(std::set
<std::string
> &paths
)
596 if (nullptr == m_pDB
)
598 if (nullptr == m_pDS
)
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"))
610 while (!m_pDS
->eof())
612 if (!m_pDS
->fv("noUpdate").get_asBool())
613 paths
.insert(m_pDS
->fv("strPath").get_asString());
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"))
626 while (!m_pDS
->eof())
628 if (!m_pDS
->fv("noUpdate").get_asBool())
629 paths
.insert(m_pDS
->fv("strPath").get_asString());
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"))
648 while (!m_pDS
->eof())
650 if (!m_pDS
->fv("noUpdate").get_asBool())
651 paths
.insert(m_pDS
->fv("strPath").get_asString());
659 CLog::Log(LOGERROR
, "{} failed", __FUNCTION__
);
664 bool CVideoDatabase::GetPathsLinkedToTvShow(int idShow
, std::vector
<std::string
> &paths
)
669 sql
= PrepareSQL("SELECT strPath FROM path JOIN tvshowlinkpath ON tvshowlinkpath.idPath=path.idPath WHERE idShow=%i", idShow
);
671 while (!m_pDS
->eof())
673 paths
.emplace_back(m_pDS
->fv(0).get_asString());
680 CLog::Log(LOGERROR
, "{} error during query: {}", __FUNCTION__
, sql
);
685 bool CVideoDatabase::GetPathsForTvShow(int idShow
, std::set
<int>& paths
)
690 if (nullptr == m_pDB
)
692 if (nullptr == m_pDS
)
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());
713 CLog::Log(LOGERROR
, "{} error during query: {}", __FUNCTION__
, strSQL
);
718 int CVideoDatabase::RunQuery(const std::string
&sql
)
720 auto start
= std::chrono::steady_clock::now();
723 if (m_pDS
->query(sql
))
725 rows
= m_pDS
->num_rows();
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
);
739 bool CVideoDatabase::GetSubPaths(const std::string
&basepath
, std::vector
<std::pair
<int, std::string
>>& subpaths
)
744 if (!m_pDB
|| !m_pDS
)
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());
755 while (!m_pDS
->eof())
757 subpaths
.emplace_back(m_pDS
->fv(0).get_asInt(), m_pDS
->fv(1).get_asString());
765 CLog::Log(LOGERROR
, "{} error during query: {}", __FUNCTION__
, sql
);
770 int CVideoDatabase::AddPath(const std::string
& strPath
, const std::string
&parentPath
/*= "" */, const CDateTime
& dateAdded
/* = CDateTime() */)
775 int idPath
= GetPathId(strPath
);
777 return idPath
; // already have the path
779 if (nullptr == m_pDB
)
781 if (nullptr == m_pDS
)
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
);
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());
798 strSQL
=PrepareSQL("insert into path (idPath, strPath) values (NULL, '%s')", strPath1
.c_str());
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
);
805 strSQL
=PrepareSQL("insert into path (idPath, strPath, idParentPath) values (NULL, '%s', %i)", strPath1
.c_str(), idParentPath
);
808 idPath
= (int)m_pDS
->lastinsertid();
813 CLog::Log(LOGERROR
, "{} unable to addpath ({})", __FUNCTION__
, strSQL
);
818 bool CVideoDatabase::GetPathHash(const std::string
&path
, std::string
&hash
)
822 if (nullptr == m_pDB
)
824 if (nullptr == m_pDS
)
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)
831 hash
= m_pDS
->fv("strHash").get_asString();
836 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, path
);
842 bool CVideoDatabase::GetSourcePath(const std::string
&path
, std::string
&sourcePath
)
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)
855 std::string strPath2
;
857 if (URIUtils::IsMultiPath(path
))
858 strPath2
= CMultiPathDirectory::GetFirstPath(path
);
862 std::string strPath1
= URIUtils::GetDirectory(strPath2
);
863 int idPath
= GetPathId(strPath1
);
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();
885 // look for parent paths until there is one which is a source
886 std::string strParent
;
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();
906 strPath1
= strParent
;
912 sourcePath
= strParent
;
918 CLog::Log(LOGERROR
, "{} failed", __FUNCTION__
);
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
= "";
934 if (nullptr == m_pDB
)
936 if (nullptr == m_pDS
)
939 const auto finalDateAdded
= GetDateAdded(strFileNameAndPath
, dateAdded
);
941 std::string strFileName
, strPath
;
942 SplitPath(strFileNameAndPath
,strPath
,strFileName
);
944 int idPath
= AddPath(strPath
, parentPath
, finalDateAdded
);
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() ;
959 std::string strPlaycount
= "NULL";
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());
970 idFile
= (int)m_pDS
->lastinsertid();
975 CLog::Log(LOGERROR
, "{} unable to addfile ({})", __FUNCTION__
, strSQL
);
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
;
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)
1004 CDateTime finalDateAdded
;
1007 if (nullptr == m_pDB
)
1009 if (nullptr == m_pDS
)
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
));
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
)
1030 if (nullptr == m_pDS
)
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
);
1043 CLog::Log(LOGERROR
, "{} ({}, {}) failed", __FUNCTION__
, path
, hash
);
1049 bool CVideoDatabase::LinkMovieToTvshow(int idMovie
, int idShow
, bool bRemove
)
1053 if (nullptr == m_pDB
)
1055 if (nullptr == m_pDS
)
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
);
1065 std::string strSQL
=PrepareSQL("insert into movielinktvshow (idShow,idMovie) values (%i,%i)", idShow
,idMovie
);
1066 m_pDS
->exec(strSQL
);
1072 CLog::Log(LOGERROR
, "{} ({}, {}) failed", __FUNCTION__
, idMovie
, idShow
);
1078 bool CVideoDatabase::IsLinkedToTvshow(int idMovie
)
1082 if (nullptr == m_pDB
)
1084 if (nullptr == m_pDS
)
1087 std::string strSQL
=PrepareSQL("select * from movielinktvshow where idMovie=%i", idMovie
);
1088 m_pDS
->query(strSQL
);
1100 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, idMovie
);
1106 bool CVideoDatabase::GetLinksToTvShow(int idMovie
, std::vector
<int>& ids
)
1110 if (nullptr == m_pDB
)
1112 if (nullptr == m_pDS
)
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());
1128 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, idMovie
);
1135 //********************************************************************************************************************************
1136 int CVideoDatabase::GetFileId(const std::string
& strFilenameAndPath
)
1140 if (nullptr == m_pDB
)
1142 if (nullptr == m_pDS
)
1144 std::string strPath
, strFileName
;
1145 SplitPath(strFilenameAndPath
,strPath
,strFileName
);
1147 int idPath
= GetPathId(strPath
);
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();
1163 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, strFilenameAndPath
);
1168 int CVideoDatabase::GetFileId(const CFileItem
&item
)
1171 if (item
.HasVideoInfoTag())
1172 fileId
= GetFileId(*item
.GetVideoInfoTag());
1175 fileId
= GetFileId(item
.GetPath());
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())
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
)
1205 if (nullptr == m_pDS
)
1209 // needed for query parameters
1210 int idFile
= GetFileId(strFilenameAndPath
);
1212 std::string strPath
;
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
)
1224 if (idFile
== -1 && strPath
!= strFilenameAndPath
)
1229 strSQL
=PrepareSQL("select idMovie from movie join files on files.idFile=movie.idFile where files.idPath=%i",idPath
);
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();
1244 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, strFilenameAndPath
);
1249 int CVideoDatabase::GetTvShowId(const std::string
& strPath
)
1253 if (nullptr == m_pDB
)
1255 if (nullptr == m_pDS
)
1259 // have to join movieinfo table for correct results
1260 int idPath
= GetPathId(strPath
);
1265 std::string strPath1
=strPath
;
1266 std::string strParent
;
1269 strSQL
=PrepareSQL("select idShow from tvshowlinkpath where tvshowlinkpath.idPath=%i",idPath
);
1270 m_pDS
->query(strSQL
);
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
);
1280 int idShow
= m_pDS
->fv("idShow").get_asInt();
1284 strPath1
= strParent
;
1287 if (m_pDS
->num_rows() > 0)
1288 idTvShow
= m_pDS
->fv("idShow").get_asInt();
1295 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, strPath
);
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
)
1306 if (nullptr == m_pDS
)
1309 // need this due to the nested GetEpisodeInfo query
1310 std::unique_ptr
<Dataset
> pDS
;
1311 pDS
.reset(m_pDB
->CreateDataset());
1315 int idFile
= GetFileId(strFilenameAndPath
);
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
);
1324 if (pDS
->num_rows() > 0)
1326 if (idEpisode
== -1)
1327 idEpisode
= pDS
->fv("episode.idEpisode").get_asInt();
1328 else // use the hint!
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
;
1355 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, strFilenameAndPath
);
1360 int CVideoDatabase::GetMusicVideoId(const std::string
& strFilenameAndPath
)
1364 if (nullptr == m_pDB
)
1366 if (nullptr == m_pDS
)
1369 int idFile
= GetFileId(strFilenameAndPath
);
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
);
1379 if (m_pDS
->num_rows() > 0)
1380 idMVideo
= m_pDS
->fv("idMVideo").get_asInt();
1387 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, strFilenameAndPath
);
1392 //********************************************************************************************************************************
1393 int CVideoDatabase::AddNewMovie(CVideoInfoTag
& details
)
1395 const auto filePath
= details
.GetPath();
1399 if (nullptr == m_pDB
)
1401 if (nullptr == m_pDS
)
1404 if (details
.m_iFileId
<= 0)
1406 details
.m_iFileId
= AddFile(details
);
1407 if (details
.m_iFileId
<= 0)
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
;
1420 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, filePath
);
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
);
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();
1442 //********************************************************************************************************************************
1443 int CVideoDatabase::AddNewEpisode(int idShow
, CVideoInfoTag
& details
)
1445 const auto filePath
= details
.GetPath();
1449 if (nullptr == m_pDB
|| nullptr == m_pDS
)
1452 if (details
.m_iFileId
<= 0)
1454 details
.m_iFileId
= AddFile(details
);
1455 if (details
.m_iFileId
<= 0)
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
;
1469 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, filePath
);
1474 int CVideoDatabase::AddNewMusicVideo(CVideoInfoTag
& details
)
1476 const auto filePath
= details
.GetPath();
1480 if (nullptr == m_pDB
)
1482 if (nullptr == m_pDS
)
1485 if (details
.m_iFileId
<= 0)
1487 details
.m_iFileId
= AddFile(details
);
1488 if (details
.m_iFileId
<= 0)
1492 std::string strSQL
= PrepareSQL("INSERT INTO musicvideo (idMVideo, idFile) VALUES (NULL, %i)",
1494 m_pDS
->exec(strSQL
);
1495 details
.m_iDbId
= static_cast<int>(m_pDS
->lastinsertid());
1497 return details
.m_iDbId
;
1501 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, filePath
);
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
)
1513 if (nullptr == m_pDS
)
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)
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();
1529 int id
= m_pDS
->fv(firstField
.c_str()).get_asInt();
1536 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, value
);
1542 int CVideoDatabase::UpdateRatings(int mediaId
, const char *mediaType
, const RatingMap
& values
, const std::string
& defaultRating
)
1546 if (nullptr == m_pDB
)
1548 if (nullptr == m_pDS
)
1551 std::string sql
= PrepareSQL("DELETE FROM rating WHERE media_id=%i AND media_type='%s'", mediaId
, mediaType
);
1554 return AddRatings(mediaId
, mediaType
, values
, defaultRating
);
1558 CLog::Log(LOGERROR
, "{} unable to update ratings of ({})", __FUNCTION__
, mediaType
);
1563 int CVideoDatabase::AddRatings(int mediaId
, const char *mediaType
, const RatingMap
& values
, const std::string
& defaultRating
)
1568 if (nullptr == m_pDB
)
1570 if (nullptr == m_pDS
)
1573 for (const auto& i
: values
)
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)
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();
1591 id
= m_pDS
->fv(0).get_asInt();
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
)
1605 CLog::Log(LOGERROR
, "{} ({} - {}) failed", __FUNCTION__
, mediaId
, mediaType
);
1611 int CVideoDatabase::UpdateUniqueIDs(int mediaId
, const char *mediaType
, const CVideoInfoTag
& details
)
1615 if (nullptr == m_pDB
)
1617 if (nullptr == m_pDS
)
1620 std::string sql
= PrepareSQL("DELETE FROM uniqueid WHERE media_id=%i AND media_type='%s'", mediaId
, mediaType
);
1623 return AddUniqueIDs(mediaId
, mediaType
, details
);
1627 CLog::Log(LOGERROR
, "{} unable to update unique ids of ({})", __FUNCTION__
, mediaType
);
1632 int CVideoDatabase::AddUniqueIDs(int mediaId
, const char *mediaType
, const CVideoInfoTag
& details
)
1637 if (nullptr == m_pDB
)
1639 if (nullptr == m_pDS
)
1642 for (const auto& i
: details
.GetUniqueIDs())
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)
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();
1657 id
= m_pDS
->fv(0).get_asInt();
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())
1670 CLog::Log(LOGERROR
, "{} ({} - {}) failed", __FUNCTION__
, mediaId
, mediaType
);
1676 int CVideoDatabase::AddSet(const std::string
& strSet
, const std::string
& strOverview
/* = "" */)
1683 if (m_pDB
== nullptr || m_pDS
== nullptr)
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)
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());
1698 int id
= m_pDS
->fv("idSet").get_asInt();
1705 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, strSet
);
1711 int CVideoDatabase::AddTag(const std::string
& name
)
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
)
1725 if (nullptr == m_pDS
)
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)
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();
1745 idActor
= m_pDS
->fv(0).get_asInt();
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
);
1756 SetArtForItem(idActor
, "actor", "thumb", thumb
);
1761 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, name
);
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
);
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());
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());
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
)
1807 int idValue
= AddToTable(field
, field
+ "_id", "name", i
);
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());
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
)
1828 int idValue
= AddActor(i
, "");
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());
1840 AddActorLinksToItem(mediaId
, mediaType
, field
, values
);
1844 void CVideoDatabase::AddTagToItem(int media_id
, int tag_id
, const std::string
&type
)
1849 AddToLinkTable(media_id
, type
, "tag", tag_id
);
1852 void CVideoDatabase::RemoveTagFromItem(int media_id
, int tag_id
, const std::string
&type
)
1857 RemoveFromLinkTable(media_id
, type
, "tag", tag_id
);
1860 void CVideoDatabase::RemoveTagsFromItem(int media_id
, const std::string
&type
)
1865 m_pDS2
->exec(PrepareSQL("DELETE FROM tag_link WHERE media_id=%d AND media_type='%s'", media_id
, type
.c_str()));
1869 void CVideoDatabase::AddCast(int mediaId
, const char *mediaType
, const std::vector
< SActorInfo
> &cast
)
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
))
1887 if (GetEpisodeInfo(strFilenameAndPath
, details
))
1889 if (GetMusicVideoInfo(strFilenameAndPath
, details
))
1891 if (GetFileInfo(strFilenameAndPath
, details
))
1897 bool CVideoDatabase::HasMovieInfo(const std::string
& strFilenameAndPath
)
1901 if (nullptr == m_pDB
)
1903 if (nullptr == m_pDS
)
1905 int idMovie
= GetMovieId(strFilenameAndPath
);
1906 return (idMovie
> 0); // index of zero is also invalid
1910 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, strFilenameAndPath
);
1915 bool CVideoDatabase::HasTvShowInfo(const std::string
& strPath
)
1919 if (nullptr == m_pDB
)
1921 if (nullptr == m_pDS
)
1923 int idTvShow
= GetTvShowId(strPath
);
1924 return (idTvShow
> 0); // index of zero is also invalid
1928 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, strPath
);
1933 bool CVideoDatabase::HasEpisodeInfo(const std::string
& strFilenameAndPath
)
1937 if (nullptr == m_pDB
)
1939 if (nullptr == m_pDS
)
1941 int idEpisode
= GetEpisodeId(strFilenameAndPath
);
1942 return (idEpisode
> 0); // index of zero is also invalid
1946 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, strFilenameAndPath
);
1951 bool CVideoDatabase::HasMusicVideoInfo(const std::string
& strFilenameAndPath
)
1955 if (nullptr == m_pDB
)
1957 if (nullptr == m_pDS
)
1959 int idMVideo
= GetMusicVideoId(strFilenameAndPath
);
1960 return (idMVideo
> 0); // index of zero is also invalid
1964 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, strFilenameAndPath
);
1969 void CVideoDatabase::DeleteDetailsForTvShow(int idTvShow
)
1973 if (nullptr == m_pDB
)
1975 if (nullptr == m_pDS
)
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
);
2011 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, idTvShow
);
2015 //********************************************************************************************************************************
2016 void CVideoDatabase::GetMoviesByActor(const std::string
& name
, CFileItemList
& items
)
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
)
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
)
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
)
2056 if (nullptr == m_pDB
)
2058 if (nullptr == m_pDS
)
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
));
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)
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
))
2104 details
= GetDetailsForMovie(m_pDS
, getDetails
);
2105 return !details
.IsEmpty();
2109 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, strFilenameAndPath
);
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)
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
))
2129 details
= GetDetailsForTvShow(m_pDS
, getDetails
, item
);
2130 return !details
.IsEmpty();
2134 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, strPath
);
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
,
2159 if (!m_pDB
|| !m_pDS
)
2162 std::string sql
= PrepareSQL("SELECT idSeason, idShow, season, name, userrating FROM seasons WHERE idSeason=%i", idSeason
);
2163 if (!m_pDS
->query(sql
))
2166 if (m_pDS
->num_rows() != 1)
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
2179 CFileItemList seasons
;
2180 if (!GetSeasonsNav(StringUtils::Format("videodb://tvshows/titles/{}/", idShow
), seasons
, -1,
2181 -1, -1, -1, idShow
, false) ||
2182 seasons
.Size() <= 0)
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();
2200 const int season
= m_pDS
->fv(2).get_asInt();
2201 std::string name
= m_pDS
->fv(3).get_asString();
2206 name
= g_localizeStrings
.Get(20381);
2208 name
= StringUtils::Format(g_localizeStrings
.Get(20358), season
);
2211 details
.m_strTitle
= name
;
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();
2225 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, idSeason
);
2230 bool CVideoDatabase::GetEpisodeBasicInfo(const std::string
& strFilenameAndPath
, CVideoInfoTag
& details
, int idEpisode
/* = -1 */)
2235 idEpisode
= GetEpisodeId(strFilenameAndPath
);
2240 std::string sql
= PrepareSQL("select * from episode where idEpisode=%i",idEpisode
);
2241 if (!m_pDS
->query(sql
))
2243 details
= GetBasicDetailsForEpisode(m_pDS
);
2244 return !details
.IsEmpty();
2248 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, strFilenameAndPath
);
2253 bool CVideoDatabase::GetEpisodeInfo(const std::string
& strFilenameAndPath
, CVideoInfoTag
& details
, int idEpisode
/* = -1 */, int getDetails
/* = VideoDbDetailsAll */)
2257 if (m_pDB
== nullptr || m_pDS
== nullptr)
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
))
2267 details
= GetDetailsForEpisode(m_pDS
, getDetails
);
2268 return !details
.IsEmpty();
2272 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, strFilenameAndPath
);
2277 bool CVideoDatabase::GetMusicVideoInfo(const std::string
& strFilenameAndPath
, CVideoInfoTag
& details
, int idMVideo
/* = -1 */, int getDetails
/* = VideoDbDetailsAll */)
2281 if (m_pDB
== nullptr || m_pDS
== nullptr)
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
))
2291 details
= GetDetailsForMusicVideo(m_pDS
, getDetails
);
2292 return !details
.IsEmpty();
2296 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, strFilenameAndPath
);
2301 bool CVideoDatabase::GetSetInfo(int idSet
, CVideoInfoTag
& details
, CFileItem
* item
/* = nullptr */)
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())
2316 details
= *(items
[0]->GetVideoInfoTag());
2319 return !details
.IsEmpty();
2323 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, idSet
);
2328 bool CVideoDatabase::GetFileInfo(const std::string
& strFilenameAndPath
, CVideoInfoTag
& details
, int idFile
/* = -1 */)
2333 idFile
= GetFileId(strFilenameAndPath
);
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
))
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();
2369 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, strFilenameAndPath
);
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()));
2384 case VIDEODB_TYPE_INT
:
2385 conditions
.emplace_back(PrepareSQL("c%02d='%i'", i
, *(const int*)(((const char*)&details
)+offsets
[i
].offset
)));
2387 case VIDEODB_TYPE_COUNT
:
2389 int value
= *(const int*)(((const char*)&details
)+offsets
[i
].offset
);
2391 conditions
.emplace_back(PrepareSQL("c%02d=%i", i
, value
));
2393 conditions
.emplace_back(PrepareSQL("c%02d=NULL", i
));
2396 case VIDEODB_TYPE_BOOL
:
2397 conditions
.emplace_back(PrepareSQL("c%02d='%s'", i
, *(const bool*)(((const char*)&details
)+offsets
[i
].offset
)?"true":"false"));
2399 case VIDEODB_TYPE_FLOAT
:
2400 conditions
.emplace_back(PrepareSQL(
2401 "c%02d='%f'", i
, *(const double*)(((const char*)&details
) + offsets
[i
].offset
)));
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()));
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()));
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()));
2413 case VIDEODB_TYPE_UNUSED
: // Skip the unused field to avoid populating unused data
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
)
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
))
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
);
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();
2464 idMovie
= GetMovieId(filePath
);
2467 DeleteMovie(idMovie
, true); // true to keep the table entry
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
);
2476 RollbackTransaction();
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
);
2494 details
.m_iIdRating
= AddRatings(idMovie
, MediaTypeMovie
, details
.m_ratings
, details
.GetDefaultRating());
2497 details
.m_iIdUniqueID
= AddUniqueIDs(idMovie
, MediaTypeMovie
, details
);
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 "
2524 " INNER JOIN files "
2525 " ON files.idFile=movie.idFile "
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
);
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
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
);
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
);
2552 sql
+= PrepareSQL(", idSet = %i", idSet
);
2554 sql
+= ", idSet = NULL";
2555 if (details
.m_iUserRating
> 0 && details
.m_iUserRating
< 11)
2556 sql
+= PrepareSQL(", userrating = %i", details
.m_iUserRating
);
2558 sql
+= ", userrating = NULL";
2559 if (details
.HasPremiered())
2560 sql
+= PrepareSQL(", premiered = '%s'", details
.GetPremiered().GetAsDBDate().c_str());
2562 sql
+= PrepareSQL(", premiered = '%i'", details
.GetYear());
2563 sql
+= PrepareSQL(" where idMovie=%i", idMovie
);
2565 CommitTransaction();
2571 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, filePath
);
2573 RollbackTransaction();
2577 int CVideoDatabase::UpdateDetailsForMovie(int idMovie
, CVideoInfoTag
& details
, const std::map
<std::string
, std::string
> &artwork
, const std::set
<std::string
> &updatedDetails
)
2584 CLog::Log(LOGINFO
, "{}: Starting updates for movie {}", __FUNCTION__
, idMovie
);
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
2612 if (updatedDetails
.find("set") != updatedDetails
.end())
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);
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
);
2645 sql
+= PrepareSQL(", idSet = %i", idSet
);
2647 sql
+= ", idSet = NULL";
2648 if (details
.m_iUserRating
> 0 && details
.m_iUserRating
< 11)
2649 sql
+= PrepareSQL(", userrating = %i", details
.m_iUserRating
);
2651 sql
+= ", userrating = NULL";
2652 if (details
.HasPremiered())
2653 sql
+= PrepareSQL(", premiered = '%s'", details
.GetPremiered().GetAsDBDate().c_str());
2655 sql
+= PrepareSQL(", premiered = '%i'", details
.GetYear());
2656 sql
+= PrepareSQL(" where idMovie=%i", idMovie
);
2659 CommitTransaction();
2661 CLog::Log(LOGINFO
, "{}: Finished updates for movie {}", __FUNCTION__
, idMovie
);
2667 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, idMovie
);
2669 RollbackTransaction();
2673 int CVideoDatabase::SetDetailsForMovieSet(const CVideoInfoTag
& details
, const std::map
<std::string
, std::string
> &artwork
, int idSet
/* = -1 */)
2675 if (details
.m_strTitle
.empty())
2683 idSet
= AddSet(details
.m_strTitle
, details
.m_strPlot
);
2686 RollbackTransaction();
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
);
2696 CommitTransaction();
2702 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, idSet
);
2704 RollbackTransaction();
2708 int CVideoDatabase::GetMatchingTvShow(const CVideoInfoTag
&details
)
2710 // first try matching on uniqueid, then on title + year
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()));
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()));
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.
2735 for (const auto &i
: paths
)
2737 idTvShow
= GetTvShowId(i
.first
);
2743 idTvShow
= GetMatchingTvShow(details
);
2746 idTvShow
= AddTvShow();
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
);
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
)
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
);
2774 details
.m_iIdRating
= AddRatings(idTvShow
, MediaTypeTvShow
, details
.m_ratings
, details
.GetDefaultRating());
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
)
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
);
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
);
2810 sql
+= ", userrating = NULL";
2811 if (details
.GetDuration() > 0)
2812 sql
+= PrepareSQL(", duration = %i", details
.GetDuration());
2814 sql
+= ", duration = NULL";
2815 sql
+= PrepareSQL(" WHERE idShow=%i", idTvShow
);
2816 if (ExecuteQuery(sql
))
2818 CommitTransaction();
2821 RollbackTransaction();
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)
2836 idSeason
= AddSeason(idShow
, details
.m_iSeason
);
2839 RollbackTransaction();
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
);
2853 sql
+= ", userrating = NULL";
2854 sql
+= PrepareSQL(" WHERE idSeason=%i", idSeason
);
2855 m_pDS
->exec(sql
.c_str());
2856 CommitTransaction();
2862 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, idSeason
);
2864 RollbackTransaction();
2868 int CVideoDatabase::SetDetailsForEpisode(CVideoInfoTag
& details
,
2869 const std::map
<std::string
, std::string
>& artwork
,
2871 int idEpisode
/* = -1 */)
2873 const auto filePath
= details
.GetPath();
2879 idEpisode
= GetEpisodeId(filePath
);
2882 DeleteEpisode(idEpisode
, true); // true to keep the table entry
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
);
2891 RollbackTransaction();
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
);
2905 details
.m_iIdRating
= AddRatings(idEpisode
, MediaTypeEpisode
, details
.m_ratings
, details
.GetDefaultRating());
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
);
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
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
);
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
);
2949 sql
+= ", userrating = NULL";
2950 sql
+= PrepareSQL(", idSeason = %i", idSeason
);
2951 sql
+= PrepareSQL(" where idEpisode=%i", idEpisode
);
2953 CommitTransaction();
2959 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, filePath
);
2961 RollbackTransaction();
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
);
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
);
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();
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();
2996 idMVideo
= GetMusicVideoId(filePath
);
2999 DeleteMusicVideo(idMVideo
, true); // Keep id
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
);
3008 RollbackTransaction();
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
);
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
);
3038 sql
+= ", userrating = NULL";
3039 if (details
.HasPremiered())
3040 sql
+= PrepareSQL(", premiered = '%s'", details
.GetPremiered().GetAsDBDate().c_str());
3042 sql
+= PrepareSQL(", premiered = '%i'", details
.GetYear());
3043 sql
+= PrepareSQL(" where idMVideo=%i", idMVideo
);
3045 CommitTransaction();
3051 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, filePath
);
3053 RollbackTransaction();
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
);
3063 SetStreamDetailsForFileId(details
, idFile
);
3066 void CVideoDatabase::SetStreamDetailsForFileId(const CStreamDetails
& details
, int idFile
)
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, "
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
);
3124 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, idFile
);
3128 //********************************************************************************************************************************
3129 void CVideoDatabase::GetFilePathById(int idMovie
, std::string
& filePath
, VideoDbContentType iType
)
3133 if (nullptr == m_pDB
)
3135 if (nullptr == m_pDS
)
3138 if (idMovie
< 0) return ;
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
);
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
);
3159 filePath
= m_pDS
->fv("path.strPath").get_asString();
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
);
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));
3187 int idFile
= GetFileId(strFilenameAndPath
);
3188 if (idFile
< 0) return ;
3190 bookmarks
.erase(bookmarks
.begin(), bookmarks
.end());
3191 if (nullptr == m_pDB
)
3193 if (nullptr == m_pDS
)
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())
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();
3216 bookmarks
.push_back(bookmark
);
3219 //sort(bookmarks.begin(), bookmarks.end(), SortBookmarks);
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];
3241 void CVideoDatabase::DeleteResumeBookMark(const CFileItem
& item
)
3243 if (!m_pDB
|| !m_pDS
)
3246 int fileID
= item
.GetVideoInfoTag()->m_iFileId
;
3249 fileID
= GetFileId(item
.GetPath());
3256 std::string sql
= PrepareSQL("delete from bookmark where idFile=%i and type=%i", fileID
, CBookmark::RESUME
);
3259 VideoDbContentType iType
= static_cast<VideoDbContentType
>(item
.GetVideoContentType());
3260 std::string content
;
3263 case VideoDbContentType::MOVIES
:
3264 content
= MediaTypeMovie
;
3266 case VideoDbContentType::EPISODES
:
3267 content
= MediaTypeEpisode
;
3269 case VideoDbContentType::TVSHOWS
:
3270 content
= MediaTypeTvShow
;
3272 case VideoDbContentType::MUSICVIDEOS
:
3273 content
= MediaTypeMusicVideo
;
3279 if (!content
.empty())
3281 AnnounceUpdate(content
, item
.GetVideoInfoTag()->m_iDbId
);
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
));
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
);
3319 if (nullptr == m_pDB
)
3321 if (nullptr == m_pDS
)
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
)
3341 m_pDS
->query( strSQL
);
3342 if (m_pDS
->num_rows() != 0)
3343 idBookmark
= m_pDS
->get_field_value("idBookmark").get_asInt();
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
);
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
);
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
)
3368 if (nullptr == m_pDS
)
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
);
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
);
3403 return ClearBookMarksOfFile(idFile
, type
);
3406 void CVideoDatabase::ClearBookMarksOfFile(int idFile
, CBookmark::EType type
/*= CBookmark::STANDARD*/)
3413 if (nullptr == m_pDB
)
3415 if (nullptr == m_pDS
)
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
);
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
);
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();
3457 CLog::Log(LOGERROR
, "{} failed", __FUNCTION__
);
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
);
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
);
3494 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, tag
.m_iDbId
);
3498 //********************************************************************************************************************************
3499 void CVideoDatabase::DeleteMovie(int idMovie
, bool bKeepId
/* = false */)
3506 if (nullptr == m_pDB
)
3508 if (nullptr == m_pDS
)
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
3521 std::string path
= GetSingleValue(PrepareSQL("SELECT strPath FROM path JOIN files ON files.idPath=path.idPath WHERE files.idFile=%i", idFile
));
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
3531 AnnounceRemove(MediaTypeMovie
, idMovie
);
3533 CommitTransaction();
3538 CLog::Log(LOGERROR
, "{} failed", __FUNCTION__
);
3539 RollbackTransaction();
3543 void CVideoDatabase::DeleteTvShow(const std::string
& strPath
)
3545 int idTvShow
= GetTvShowId(strPath
);
3547 DeleteTvShow(idTvShow
);
3550 void CVideoDatabase::DeleteTvShow(int idTvShow
, bool bKeepId
/* = false */)
3557 if (nullptr == m_pDB
)
3559 if (nullptr == m_pDS
)
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
);
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
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
));
3590 InvalidatePathHash(path
);
3594 //! @todo move this below CommitTransaction() once UPnP doesn't rely on this anymore
3596 AnnounceRemove(MediaTypeTvShow
, idTvShow
);
3598 CommitTransaction();
3603 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, idTvShow
);
3604 RollbackTransaction();
3608 void CVideoDatabase::DeleteSeason(int idSeason
, bool bKeepId
/* = false */)
3615 if (m_pDB
== nullptr || m_pDS
== nullptr || m_pDS2
== nullptr)
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
);
3630 ExecuteQuery(PrepareSQL("DELETE FROM seasons WHERE idSeason = %i", idSeason
));
3632 CommitTransaction();
3636 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, idSeason
);
3637 RollbackTransaction();
3641 void CVideoDatabase::DeleteEpisode(int idEpisode
, bool bKeepId
/* = false */)
3648 if (nullptr == m_pDB
)
3650 if (nullptr == m_pDS
)
3653 //! @todo move this below CommitTransaction() once UPnP doesn't rely on this anymore
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
3664 std::string path
= GetSingleValue(PrepareSQL("SELECT strPath FROM path JOIN files ON files.idPath=path.idPath WHERE files.idFile=%i", idFile
));
3666 InvalidatePathHash(path
);
3668 std::string strSQL
= PrepareSQL("delete from episode where idEpisode=%i", idEpisode
);
3669 m_pDS
->exec(strSQL
);
3675 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, idEpisode
);
3679 void CVideoDatabase::DeleteMusicVideo(int idMVideo
, bool bKeepId
/* = false */)
3686 if (nullptr == m_pDB
)
3688 if (nullptr == m_pDS
)
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
3700 std::string path
= GetSingleValue(PrepareSQL("SELECT strPath FROM path JOIN files ON files.idPath=path.idPath WHERE files.idFile=%i", idFile
));
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
3710 AnnounceRemove(MediaTypeMusicVideo
, idMVideo
);
3712 CommitTransaction();
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);
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
)
3745 if (nullptr == m_pDS
)
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
);
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
)
3768 ExecuteQuery(PrepareSQL("update movie set idSet = %i where idMovie = %i", idSet
, idMovie
));
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)
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
;
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
);
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();
3813 case VIDEODB_TYPE_INT
:
3814 case VIDEODB_TYPE_COUNT
:
3815 *(int*)(((char*)&details
)+offsets
[i
].offset
) = record
->at(i
+idxOffset
).get_asInt();
3817 case VIDEODB_TYPE_BOOL
:
3818 *(bool*)(((char*)&details
)+offsets
[i
].offset
) = record
->at(i
+idxOffset
).get_asBool();
3820 case VIDEODB_TYPE_FLOAT
:
3821 *(float*)(((char*)&details
)+offsets
[i
].offset
) = record
->at(i
+idxOffset
).get_asFloat();
3823 case VIDEODB_TYPE_STRINGARRAY
:
3825 std::string value
= record
->at(i
+idxOffset
).get_asString();
3827 *(std::vector
<std::string
>*)(((char*)&details
)+offsets
[i
].offset
) = StringUtils::Split(value
, CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_videoItemSeparator
);
3830 case VIDEODB_TYPE_DATE
:
3831 ((CDateTime
*)(((char*)&details
)+offsets
[i
].offset
))->SetFromDBDate(record
->at(i
+idxOffset
).get_asString());
3833 case VIDEODB_TYPE_DATETIME
:
3834 ((CDateTime
*)(((char*)&details
)+offsets
[i
].offset
))->SetFromDBDateTime(record
->at(i
+idxOffset
).get_asString());
3836 case VIDEODB_TYPE_UNUSED
: // Skip the unused field to avoid populating unused data
3842 bool CVideoDatabase::GetDetailsByTypeAndId(CFileItem
& item
, VideoDbContentType type
, int id
)
3844 CVideoInfoTag details
;
3849 case VideoDbContentType::MOVIES
:
3850 GetMovieInfo("", details
, id
);
3852 case VideoDbContentType::TVSHOWS
:
3853 GetTvShowInfo("", details
, id
, &item
);
3855 case VideoDbContentType::EPISODES
:
3856 GetEpisodeInfo("", details
, id
);
3858 case VideoDbContentType::MUSICVIDEOS
:
3859 GetMusicVideoInfo("", details
, id
);
3865 item
.SetFromVideoInfoTag(details
);
3869 CVideoInfoTag
CVideoDatabase::GetDetailsByTypeAndId(VideoDbContentType type
, int id
)
3872 if (GetDetailsByTypeAndId(item
, type
, id
))
3873 return CVideoInfoTag(*item
.GetVideoInfoTag());
3878 bool CVideoDatabase::GetStreamDetails(CFileItem
& item
)
3880 // Note that this function (possibly) creates VideoInfoTags for items that don't have one yet!
3883 if (item
.HasVideoInfoTag())
3884 fileId
= item
.GetVideoInfoTag()->m_iFileId
;
3887 fileId
= GetFileId(item
);
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)
3902 bool retVal
= false;
3904 CStreamDetails
& details
= tag
.m_streamDetails
;
3907 std::unique_ptr
<Dataset
> pDS(m_pDB
->CreateDataset());
3910 std::string strSQL
= PrepareSQL("SELECT * FROM streamdetails WHERE idFile = %i", tag
.m_iFileId
);
3915 CStreamDetail::StreamType e
= (CStreamDetail::StreamType
)pDS
->fv(1).get_asInt();
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
);
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;
3940 p
->m_iChannels
= pDS
->fv(7).get_asInt();
3941 p
->m_strLanguage
= pDS
->fv(8).get_asString();
3942 details
.AddStream(p
);
3946 case CStreamDetail::SUBTITLE
:
3948 CStreamDetailSubtitle
*p
= new CStreamDetailSubtitle();
3949 p
->m_strLanguage
= pDS
->fv(9).get_asString();
3950 details
.AddStream(p
);
3963 CLog::Log(LOGERROR
, "{}({}) failed", __FUNCTION__
, tag
.m_iFileId
);
3965 details
.DetermineBestStreams();
3967 if (details
.GetVideoDuration() > 0)
3968 tag
.SetDuration(details
.GetVideoDuration());
3973 bool CVideoDatabase::GetResumePoint(CVideoInfoTag
& tag
)
3975 if (tag
.m_iFileId
< 0)
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
--)
3992 if (GetResumeBookMark(fileList
[i
]->GetPath(), bookmark
))
3994 bookmark
.partNumber
= (i
+1); /* store part number in here */
3995 tag
.SetResumePoint(bookmark
);
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
);
4007 tag
.SetResumePoint(m_pDS2
->fv(0).get_asDouble(), m_pDS2
->fv(1).get_asDouble(), "");
4015 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, tag
.m_strFileNameAndPath
);
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
;
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());
4062 details
.SetPremieredFromDBDate(premieredString
);
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
);
4088 details
.m_showLink
.emplace_back(m_pDS2
->fv(0).get_asString());
4093 if (getDetails
& VideoDbDetailsStream
)
4094 GetStreamDetails(details
);
4096 details
.m_parsedDetails
= getDetails
;
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
;
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());
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
;
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
)
4166 details
.SetPlayCount((details
.m_iEpisode
<= details
.GetPlayCount()) ? 1 : 0);
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)
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();
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)
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);
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
;
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
;
4266 if (record
== nullptr)
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());
4292 details
.SetPremieredFromDBDate(premieredString
);
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
;
4315 void CVideoDatabase::GetCast(int media_id
, const std::string
&media_type
, std::vector
<SActorInfo
> &cast
)
4324 std::string sql
= PrepareSQL("SELECT actor.name,"
4326 " actor_link.cast_order,"
4331 " actor_link.actor_id=actor.actor_id"
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());
4337 while (!m_pDS2
->eof())
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
));
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
)
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());
4375 while (!m_pDS2
->eof())
4377 tags
.emplace_back(m_pDS2
->fv(0).get_asString());
4384 CLog::Log(LOGERROR
, "{}({},{}) failed", __FUNCTION__
, media_id
, media_type
);
4388 void CVideoDatabase::GetRatings(int media_id
, const std::string
&media_type
, RatingMap
&ratings
)
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());
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());
4408 CLog::Log(LOGERROR
, "{}({},{}) failed", __FUNCTION__
, media_id
, media_type
);
4412 void CVideoDatabase::GetUniqueIDs(int media_id
, const std::string
&media_type
, CVideoInfoTag
& details
)
4421 std::string sql
= PrepareSQL("SELECT type, value FROM uniqueid WHERE media_id = %i AND media_type = '%s'", media_id
, media_type
.c_str());
4423 while (!m_pDS2
->eof())
4425 details
.SetUniqueID(m_pDS2
->fv(1).get_asString(), m_pDS2
->fv(0).get_asString());
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
)
4455 if (nullptr == m_pDS
)
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();
4498 CLog::Log(LOGERROR
, "{} failed", __FUNCTION__
);
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
)
4516 if (nullptr == m_pDS
)
4520 std::string strSQL
= PrepareSQL("select * from settings where idFile=%i", idFile
);
4521 m_pDS
->query( strSQL
);
4522 if (m_pDS
->num_rows() > 0)
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="
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
);
4552 m_pDS
->exec(strSQL
);
4558 strSQL
= "INSERT INTO settings (idFile,Deinterlace,ViewMode,ZoomAmount,PixelRatio, VerticalShift, "
4559 "AudioStream,SubtitleStream,SubtitleDelay,SubtitlesOn,Brightness,"
4560 "Contrast,Gamma,VolumeAmplification,AudioDelay,"
4562 "Sharpness,NoiseReduction,NonLinStretch,PostProcess,ScalingMethod,StereoMode,StereoInvert,VideoStream,TonemapMethod,TonemapParam,Orientation,CenterMixLevel) "
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
);
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
)
4601 if (nullptr == m_pDS
)
4604 // don't set <foo>.<bar> art types - these are derivative types from parent items
4605 if (artType
.find('.') != std::string::npos
)
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());
4612 int artId
= m_pDS
->fv(0).get_asInt();
4613 std::string oldUrl
= m_pDS
->fv(1).get_asString();
4617 sql
= PrepareSQL("UPDATE art SET url='%s' where art_id=%d", url
.c_str(), artId
);
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());
4630 CLog::Log(LOGERROR
, "{}({}, '{}', '{}', '{}') failed", __FUNCTION__
, mediaId
, mediaType
,
4635 bool CVideoDatabase::GetArtForItem(int mediaId
, const MediaType
&mediaType
, std::map
<std::string
, std::string
> &art
)
4639 if (nullptr == m_pDB
)
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());
4646 while (!m_pDS2
->eof())
4648 art
.insert(make_pair(m_pDS2
->fv(0).get_asString(), m_pDS2
->fv(1).get_asString()));
4652 return !art
.empty();
4656 CLog::Log(LOGERROR
, "{}({}) failed", __FUNCTION__
, mediaId
);
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
)
4675 for (const auto &i
: artTypes
)
4676 result
&= RemoveArtForItem(mediaId
, mediaType
, i
);
4681 bool CVideoDatabase::HasArtForItem(int mediaId
, const MediaType
&mediaType
)
4685 if (nullptr == m_pDB
)
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());
4692 bool result
= !m_pDS2
->eof();
4698 CLog::Log(LOGERROR
, "{}({}) failed", __FUNCTION__
, mediaId
);
4703 bool CVideoDatabase::GetTvShowSeasons(int showId
, std::map
<int, int> &seasons
)
4707 if (nullptr == m_pDB
)
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
);
4717 while (!m_pDS2
->eof())
4719 seasons
.insert(std::make_pair(m_pDS2
->fv(1).get_asInt(), m_pDS2
->fv(0).get_asInt()));
4727 CLog::Log(LOGERROR
, "{}({}) failed", __FUNCTION__
, showId
);
4732 bool CVideoDatabase::GetTvShowNamedSeasons(int showId
, std::map
<int, std::string
> &seasons
)
4736 if (nullptr == m_pDB
)
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
);
4746 while (!m_pDS2
->eof())
4748 seasons
.insert(std::make_pair(m_pDS2
->fv(0).get_asInt(), m_pDS2
->fv(1).get_asString()));
4756 CLog::Log(LOGERROR
, "{}({}) failed", __FUNCTION__
, showId
);
4761 bool CVideoDatabase::GetTvShowSeasonArt(int showId
, std::map
<int, std::map
<std::string
, std::string
> > &seasonArt
)
4765 if (nullptr == m_pDB
)
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
));
4783 CLog::Log(LOGERROR
, "{}({}) failed", __FUNCTION__
, showId
);
4788 bool CVideoDatabase::GetArtTypes(const MediaType
&mediaType
, std::vector
<std::string
> &artTypes
)
4792 if (nullptr == m_pDB
)
4794 if (nullptr == m_pDS
)
4797 std::string sql
= PrepareSQL("SELECT DISTINCT type FROM art WHERE media_type='%s'", mediaType
.c_str());
4798 int numRows
= RunQuery(sql
);
4800 return numRows
== 0;
4802 while (!m_pDS
->eof())
4804 artTypes
.emplace_back(m_pDS
->fv(0).get_asString());
4812 CLog::Log(LOGERROR
, "{}({}) failed", __FUNCTION__
, mediaType
);
4819 std::vector
<std::string
> GetBasicItemAvailableArtTypes(int mediaId
,
4820 VideoDbContentType dbType
,
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");
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
);
4848 std::vector
<std::string
> GetSeasonAvailableArtTypes(int mediaId
, CVideoDatabase
& db
)
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())
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
);
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."))
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
);
4898 std::vector
<CScraperUrl::SUrlEntry
> GetBasicItemAvailableArt(int mediaId
,
4899 VideoDbContentType dbType
,
4900 const std::string
& artType
,
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
);
4933 std::vector
<CScraperUrl::SUrlEntry
> GetSeasonAvailableArt(
4934 int mediaId
, const std::string
& artType
, CVideoDatabase
& db
)
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
);
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
);
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
;
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);
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);
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> ×
)
5037 // obtain the FileID (if it exists)
5038 int idFile
= GetFileId(filePath
);
5039 if (idFile
< 0) return false;
5040 if (nullptr == m_pDB
)
5042 if (nullptr == m_pDS
)
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(), ",");
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
;
5059 return (timeTotal
> 0);
5065 CLog::Log(LOGERROR
, "{} failed", __FUNCTION__
);
5070 /// \brief Sets the stack times for a particular video file
5071 void CVideoDatabase::SetStackTimes(const std::string
& filePath
, const std::vector
<uint64_t> ×
)
5075 if (nullptr == m_pDB
)
5077 if (nullptr == m_pDS
)
5079 int idFile
= AddFile(filePath
);
5083 // delete any existing items
5084 m_pDS
->exec( PrepareSQL("delete from stacktimes where idFile=%i", idFile
) );
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()) );
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
)
5114 if (nullptr == m_pDS
)
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);
5125 progress
->ShowProgressBar(true);
5127 std::vector
<std::pair
<int, std::string
> > paths
;
5128 GetSubPaths(strPath
, paths
);
5130 for (const auto &i
: paths
)
5132 bool bMvidsChecked
=false;
5135 progress
->SetPercentage((int)((float)(iCurr
++)/paths
.size()*100.f
));
5136 progress
->Progress();
5139 const auto tvshowId
= GetTvShowId(i
.second
);
5141 DeleteTvShow(tvshowId
);
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
);
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
);
5159 DeleteMovie(movieId
);
5162 const auto musicvideoId
= GetMusicVideoId(strMoviePath
);
5163 if (musicvideoId
> 0)
5164 DeleteMusicVideo(musicvideoId
);
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;
5175 m_pDS2
->exec(PrepareSQL("update path set strContent='', strScraper='', strHash='',strSettings='',useFolderNames=0,scanRecursive=0 where idPath=%i", i
.first
));
5181 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, strPath
);
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
);
5203 if (nullptr == m_pDB
)
5205 if (nullptr == m_pDS
)
5208 int idPath
= AddPath(filePath
);
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
);
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
);
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
);
5241 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, filePath
);
5245 bool CVideoDatabase::ScraperInUse(const std::string
&scraperID
) const
5249 if (nullptr == m_pDB
)
5251 if (nullptr == m_pDS
)
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)
5257 bool found
= m_pDS
->fv(0).get_asInt() > 0;
5263 CLog::Log(LOGERROR
, "{}({}) failed", __FUNCTION__
, scraperID
);
5271 CArtItem() { art_id
= 0; media_id
= 0; };
5273 std::string art_type
;
5274 std::string art_url
;
5276 std::string media_type
;
5279 // used for database update to v83
5283 bool operator==(const CShowItem
&r
) const
5285 return (!ident
.empty() && ident
== r
.ident
) || (title
== r
.title
&& year
== r
.year
);
5294 // used for database update to v84
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
5309 m_pDS
->exec("ALTER TABLE settings ADD StereoMode integer");
5310 m_pDS
->exec("ALTER TABLE settings ADD StereoInvert bool");
5313 m_pDS
->exec("ALTER TABLE streamdetails ADD strStereoMode text");
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()));
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
));
5337 // drop parent path id and basePath from tvshow table
5338 m_pDS
->exec("UPDATE tvshow SET c16=NULL,c17=NULL");
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
);
5345 std::vector
<CShowItem
> shows
;
5346 while (!m_pDS
->eof())
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
));
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
);
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
));
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)");
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())
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
));
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
));
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://%'");
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)");
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");
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");
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");
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");
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");
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");
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");
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");
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)
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
;
5535 m_pDS
->exec(PrepareSQL("UPDATE %s SET name = TRIM(name)",
5538 // shrink name to length 255
5539 m_pDS
->exec(PrepareSQL("UPDATE %s SET name = SUBSTR(name, 1, 255) WHERE LENGTH(name) > 255",
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()));
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();
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");
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",
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()));
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");
5631 m_pDS
->exec("ALTER TABLE sets ADD strOverview TEXT");
5634 m_pDS
->exec("ALTER TABLE seasons ADD name text");
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 "
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()));
5656 m_pDS
->exec("ALTER TABLE seasons ADD userrating INTEGER");
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
);
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()));
5677 sql
= PrepareSQL("SELECT DISTINCT idShow, c%02d, c%02d FROM tvshow", VIDEODB_ID_TV_RATING_ID
, VIDEODB_ID_TV_VOTES
);
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()));
5692 sql
= PrepareSQL("SELECT DISTINCT idEpisode, c%02d, c%02d FROM episode", VIDEODB_ID_EPISODE_RATING_ID
, VIDEODB_ID_EPISODE_VOTES
);
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()));
5710 m_pDS
->exec("ALTER TABLE settings ADD VideoStream integer");
5711 m_pDS
->exec("ALTER TABLE streamdetails ADD strVideoLanguage text");
5716 m_pDS
->exec("ALTER TABLE tvshow ADD duration INTEGER");
5718 std::string sql
= PrepareSQL( "SELECT episode.idShow, MAX(episode.c%02d) "
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
);
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()));
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
));
5748 // need this due to the nested GetScraperPath query
5749 std::unique_ptr
<Dataset
> pDS
;
5750 pDS
.reset(m_pDB
->CreateDataset());
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
;
5763 mediatype
= "movie";
5764 columnID
= "idMovie";
5765 columnUniqueID
= VIDEODB_ID_IDENT_ID
;
5768 mediatype
= "tvshow";
5769 columnID
= "idShow";
5770 columnUniqueID
= VIDEODB_ID_TV_IDENT_ID
;
5773 mediatype
= "episode";
5774 columnID
= "idEpisode";
5775 columnUniqueID
= VIDEODB_ID_EPISODE_IDENT_ID
;
5780 pDS
->query(PrepareSQL("SELECT %s, c%02d FROM %s", columnID
.c_str(), columnUniqueID
, mediatype
.c_str()));
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()));
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
));
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");
5814 m_pDS
->exec("ALTER TABLE settings ADD TonemapMethod integer");
5815 m_pDS
->exec("ALTER TABLE settings ADD TonemapParam float");
5819 m_pDS
->exec("ALTER TABLE settings ADD Orientation integer");
5822 m_pDS
->exec("ALTER TABLE settings ADD CenterMixLevel integer");
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())
5836 m_pDS2
->query(PrepareSQL("SELECT idPath FROM path WHERE strPath='%s'", path
.c_str()));
5838 pathid
= m_pDS2
->fv(0).get_asInt();
5842 std::string parent
= URIUtils::GetParentPath(path
);
5844 m_pDS2
->query(PrepareSQL("SELECT idPath FROM path WHERE strPath='%s'", parent
.c_str()));
5846 parentid
= m_pDS2
->fv(0).get_asInt();
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();
5860 m_pDS2
->exec(PrepareSQL("DELETE FROM files WHERE idFile=%i", m_pDS
->fv(0).get_asInt()));
5862 m_pDS2
->exec(PrepareSQL("UPDATE files SET idPath=%i WHERE idFile=%i", pathid
, m_pDS
->fv(0).get_asInt()));
5870 m_pDS
->exec("ALTER TABLE path ADD allAudio bool");
5873 m_pDS
->exec("ALTER TABLE streamdetails ADD strHdrType text");
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()));
5901 int CVideoDatabase::GetSchemaVersion() const
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
);
5924 for(unsigned i
=0;i
<paths
.size();i
++)
5925 ret
|= GetPlayCounts(paths
[i
], items
);
5930 if (!URIUtils::IsPlugin(strPath
))
5932 pathID
= GetPathId(strPath
);
5934 return false; // path (and thus files) aren't in the database
5940 if (nullptr == m_pDB
)
5942 if (nullptr == m_pDS
)
5947 " files.strFilename, files.playCount,"
5948 " bookmark.timeInSeconds, bookmark.totalTimeInSeconds "
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())
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()));
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(), "");
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)
5985 items
.SetFastLookup(true); // note: it's possibly quicker the other way around (map on db returned items)?
5986 while (!m_pDS
->eof())
5989 ConstructPath(path
, strPath
, m_pDS
->fv(0).get_asString());
5990 CFileItemPtr item
= items
.Get(path
);
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(), "");
6009 CLog::Log(LOGERROR
, "{} failed", __FUNCTION__
);
6014 int CVideoDatabase::GetPlayCount(int iFileId
)
6017 return 0; // not in db, so not watched
6022 if (nullptr == m_pDB
)
6024 if (nullptr == m_pDS
)
6027 std::string strSQL
= PrepareSQL("select playCount from files WHERE idFile=%i", iFileId
);
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();
6040 CLog::Log(LOGERROR
, "{} failed", __FUNCTION__
);
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
)
6058 return {}; // not in db, so not watched
6063 if (nullptr == m_pDB
)
6065 if (nullptr == m_pDS
)
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());
6081 CLog::Log(LOGERROR
, "{} failed", __FUNCTION__
);
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
)
6095 if (nullptr == m_pDS
)
6097 if (!item
.HasVideoInfoTag() || item
.GetVideoInfoTag()->m_iDbId
< 0) return;
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
);
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
);
6116 CLog::Log(LOGERROR
, "{} - error updating fanart for {}", __FUNCTION__
, item
.GetPath());
6120 CDateTime
CVideoDatabase::SetPlayCount(const CFileItem
& item
, int count
, const CDateTime
& date
)
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
);
6135 // and mark as watched
6138 const CDateTime
lastPlayed(date
.IsValid() ? date
: CDateTime::GetCurrentDateTime());
6140 if (nullptr == m_pDB
)
6142 if (nullptr == m_pDS
)
6148 strSQL
= PrepareSQL("update files set playCount=%i,lastPlayed='%s' where idFile=%i", count
,
6149 lastPlayed
.GetAsDBDateTime().c_str(), id
);
6153 if (!date
.IsValid())
6154 strSQL
= PrepareSQL("update files set playCount=NULL,lastPlayed=NULL where idFile=%i", id
);
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)
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
);
6179 CLog::Log(LOGERROR
, "{} failed", __FUNCTION__
);
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
)
6203 if (nullptr == m_pDS
)
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
);
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
)
6256 if (iType
!= VideoDbContentType::MOVIES
&& iType
!= VideoDbContentType::TVSHOWS
)
6259 std::string content
= MediaTypeMovie
;
6260 if (iType
== VideoDbContentType::TVSHOWS
)
6261 content
= MediaTypeTvShow
;
6263 if (SetSingleValue(iType
, idDb
, FieldSortTitle
, strNewSortTitle
))
6265 AnnounceUpdate(content
, idDb
);
6272 "{} (int idDb, const std::string& strNewSortTitle, VIDEODB_CONTENT_TYPE iType) "
6273 "failed on ID: {} and Sort Title: {}",
6274 __FUNCTION__
, idDb
, strNewSortTitle
);
6280 /// \brief EraseVideoSettings() Erases the videoSettings table and reconstructs it
6281 void CVideoDatabase::EraseVideoSettings(const CFileItem
&item
)
6283 int idFile
= GetFileId(item
);
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()));
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");
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());
6324 while (!m_pDS
->eof())
6326 std::string file
= m_pDS
->fv("files.idFile").get_asString() + ",";
6327 itemsToDelete
+= file
;
6332 if (!itemsToDelete
.empty())
6334 itemsToDelete
= "(" + StringUtils::TrimRight(itemsToDelete
, ",") + ")";
6336 sql
= "DELETE FROM settings WHERE idFile IN " + itemsToDelete
;
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
,
6376 VideoDbContentType idContent
/* = UNKNOWN */,
6377 const Filter
& filter
/* = Filter() */,
6378 bool countOnly
/* = false */)
6382 if (nullptr == m_pDB
)
6384 if (nullptr == m_pDS
)
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
;
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";
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
);
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
;
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());
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
));
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
))
6477 int iRowsFound
= RunQuery(strSQL
);
6478 if (iRowsFound
<= 0)
6479 return iRowsFound
== 0;
6483 CFileItemPtr
pItem(new CFileItem());
6484 pItem
->SetProperty("total", iRowsFound
== 1 ? m_pDS
->fv(0).get_asInt() : iRowsFound
);
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())
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)));
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);
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);
6567 CLog::Log(LOGERROR
, "{} failed", __FUNCTION__
);
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
)
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
)
6599 if (nullptr == m_pDS
)
6602 CVideoDbUrl videoUrl
;
6603 if (!videoUrl
.FromString(strBaseDir
))
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
))
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
;
6627 groupingAttributes
=
6628 ignoreSingleMovieSets
? GroupAttributeIgnoreSingleItems
: GroupAttributeNone
;
6631 if (!GroupUtils::Group(GroupBySet
, strBaseDir
, items
, sets
, groupingAttributes
))
6641 CLog::Log(LOGERROR
, "{} failed", __FUNCTION__
);
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
)
6652 if (nullptr == m_pDS
)
6655 CVideoDbUrl videoUrl
;
6656 if (!videoUrl
.FromString(strBaseDir
))
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
));
6675 videoUrl
.AddOption("artistid", idArtist
);
6677 extFilter
.AppendGroup(PrepareSQL(" CASE WHEN musicvideo_view.c09 !='' THEN musicvideo_view.c09 "
6678 "ELSE musicvideo_view.c00 END"));
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
))
6691 int iRowsFound
= RunQuery(strSQL
);
6692 /* fields returned by query are :-
6693 (0) - Album title (if any)
6696 (3) - Music video title
6697 (4) - Music video plot
6698 (5) - Music Video artist
6701 if (iRowsFound
<= 0)
6702 return iRowsFound
== 0;
6704 std::string strArtist
;
6706 strArtist
= m_pDS
->fv("actor.name").get_asString();
6710 CFileItemPtr
pItem(new CFileItem());
6711 pItem
->SetProperty("total", iRowsFound
== 1 ? m_pDS
->fv(0).get_asInt() : iRowsFound
);
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
6732 CFileItemPtr
pItem(new CFileItem(strAlbum
));
6734 CVideoDbUrl itemUrl
= videoUrl
;
6735 std::string path
= StringUtils::Format("{}/", idMVideo
);
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'",
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());
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
;
6765 idMVideoList
.push_back(idMVideo
);
6766 idData
.push_back(make_pair(m_pDS
->fv(0).get_asString(), m_pDS
->fv(5).get_asString()));
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();
6793 GetMusicVideoInfo("", details
, idMVideoList
.front());
6794 items
[i
]->SetFromVideoInfoTag(details
);
6795 idMVideoList
.pop_front();
6800 if (!strArtist
.empty())
6801 items
.SetProperty("customtitle",strArtist
); // change displayed path from eg /23 to /Artist
6807 CLog::Log(LOGERROR
, "{} failed", __FUNCTION__
);
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");
6844 pItem
->SetArt("icon", "DefaultActor.png");
6851 bool CVideoDatabase::GetPeopleNav(const std::string
& strBaseDir
,
6852 CFileItemList
& items
,
6854 VideoDbContentType idContent
/* = UNKNOWN */,
6855 const Filter
& filter
/* = Filter() */,
6856 bool countOnly
/* = false */)
6858 if (nullptr == m_pDB
)
6860 if (nullptr == m_pDS
)
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
6872 // General routine that the other actor/director/writer routines call
6874 // get primary genres for movies
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
;
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
)
6916 PrepareSQL(" WHERE actor.name IN (SELECT musicvideo_view.c10 from musicvideo_view)");
6917 group
= "actor.actor_id";
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
);
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
;
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
)
6973 PrepareSQL(" WHERE actor.name IN (SELECT musicvideo_view.c10 from musicvideo_view)");
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");
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
))
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)
7020 CFileItemPtr
pItem(new CFileItem());
7021 pItem
->SetProperty("total", iRowsFound
== 1 ? m_pDS
->fv(0).get_asInt() : iRowsFound
);
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();
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())
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
++;
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
);
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
);
7119 CLog::Log(LOGERROR
, "{}: out of memory - retrieved {} items", __FUNCTION__
, items
.Size());
7120 return items
.Size() > 0;
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__
,
7137 CLog::Log(LOGERROR
, "{} failed", __FUNCTION__
);
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
)
7151 if (nullptr == m_pDS
)
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");
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");
7200 CVideoDbUrl videoUrl
;
7201 if (!BuildSQL(strBaseDir
, strSQL
, extFilter
, strSQL
, videoUrl
))
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())
7214 std::string dateString
= m_pDS
->fv(0).get_asString();
7215 if (dateString
.size() == 4)
7216 lYear
= m_pDS
->fv(0).get_asInt();
7220 time
.SetFromDateString(dateString
);
7222 lYear
= time
.GetYear();
7224 auto it
= mapYears
.find(lYear
);
7225 if (it
== mapYears
.end())
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())));
7235 mapYears
.insert(std::pair
<int, std::pair
<std::string
,int> >(lYear
, std::pair
<std::string
,int>(year
,0)));
7242 for (const auto &i
: mapYears
)
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
);
7261 while (!m_pDS
->eof())
7264 std::string strLabel
= m_pDS
->fv(0).get_asString();
7265 if (strLabel
.size() == 4)
7266 lYear
= m_pDS
->fv(0).get_asInt();
7270 time
.SetFromDateString(strLabel
);
7273 lYear
= time
.GetYear();
7274 strLabel
= std::to_string(lYear
);
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()))
7310 CLog::Log(LOGERROR
, "{} failed", __FUNCTION__
);
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
))
7323 videoUrl
.AddOption("tvshowid", idShow
);
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))
7336 // now add any linked movies
7337 if (getLinkedMovies
&& idShow
!= -1)
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
);
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
)
7358 if (nullptr == m_pDS
)
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
))
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
)
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())
7411 strLabel
= g_localizeStrings
.Get(20381);
7413 strLabel
= StringUtils::Format(g_localizeStrings
.Get(20358), iSeason
);
7415 CFileItemPtr
pItem(new CFileItem(strLabel
));
7417 CVideoDbUrl itemUrl
= videoUrl
;
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
;
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);
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));
7477 CLog::Log(LOGERROR
, "{} failed", __FUNCTION__
);
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
)
7487 if (mediaType
!= MediaTypeMovie
&& mediaType
!= MediaTypeTvShow
&& mediaType
!= MediaTypeEpisode
&& mediaType
!= MediaTypeMusicVideo
)
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
);
7511 items
.SetContent(CMediaTypes::ToPlural(mediaType
));
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
))
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
;
7534 contentType
= VideoDbContentType::TVSHOWS
;
7536 else if (StringUtils::EqualsNoCase(mediaType
, "musicvideos"))
7537 contentType
= VideoDbContentType::MUSICVIDEOS
;
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
);
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
);
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
))
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
);
7646 videoUrl
.AddOption("setid", idSet
);
7648 videoUrl
.AddOption("tagid", idTag
);
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
)
7660 if (nullptr == m_pDS
)
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
))
7672 std::string strSQL
= "select %s from movie_view ";
7673 std::string strSQLExtra
;
7674 if (!CDatabase::BuildSQL(strSQLExtra
, extFilter
, strSQLExtra
))
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
)
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
))
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);
7736 CLog::Log(LOGERROR
, "{} failed", __FUNCTION__
);
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
))
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
);
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
)
7774 if (nullptr == m_pDS
)
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
))
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
)
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
))
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));
7845 CLog::Log(LOGERROR
, "{} failed", __FUNCTION__
);
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
))
7858 videoUrl
.AddOption("tvshowid", idShow
);
7860 videoUrl
.AddOption("season", idSeason
);
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
);
7876 bool ret
= GetEpisodesByWhere(videoUrl
.ToString(), filter
, items
, false, sortDescription
, getDetails
);
7878 if (idSeason
== -1 && idShow
!= -1)
7879 { // add any linked movies
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
);
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
)
7899 if (nullptr == m_pDS
)
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
))
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
)
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
))
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
;
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
);
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
;
7982 CLog::Log(LOGERROR
, "{} failed", __FUNCTION__
);
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
))
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
);
8006 videoUrl
.AddOption("albumid", idAlbum
);
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 */)
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 */)
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 */)
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 */)
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
)
8090 if (nullptr == m_pDS
)
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);
8103 CLog::Log(LOGERROR
, "{} failed", __FUNCTION__
);
8108 int CVideoDatabase::GetTvShowForEpisode(int idEpisode
)
8112 if (nullptr == m_pDB
)
8114 if (nullptr == m_pDS2
)
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
);
8123 result
=m_pDS2
->fv(0).get_asInt();
8130 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, idEpisode
);
8135 int CVideoDatabase::GetSeasonForEpisode(int idEpisode
)
8138 sprintf(column
, "c%0d", VIDEODB_ID_EPISODE_SEASON
);
8139 std::string id
= GetSingleValue("episode", column
, PrepareSQL("idEpisode=%i", idEpisode
));
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
)
8158 if (nullptr == m_pDS
)
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
);
8171 result
= (m_pDS
->fv(0).get_asInt() > 0);
8177 CLog::Log(LOGERROR
, "{} failed", __FUNCTION__
);
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
)
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();
8203 std::string strPath2
;
8205 if (URIUtils::IsMultiPath(strPath
))
8206 strPath2
= CMultiPathDirectory::GetFirstPath(strPath
);
8210 std::string strPath1
= URIUtils::GetDirectory(strPath2
);
8211 int idPath
= GetPathId(strPath1
);
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",
8219 m_pDS
->query( strSQL
);
8223 CONTENT_TYPE content
= CONTENT_NONE
;
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;
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();
8247 if (!scraperID
.empty() &&
8248 CServiceBroker::GetAddonMgr().GetAddon(scraperID
, addon
, ADDON::OnlyEnabled::CHOICE_YES
))
8250 scraper
= std::dynamic_pointer_cast
<CScraper
>(addon
);
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
))
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'",
8275 m_pDS
->query(strSQL
);
8277 CONTENT_TYPE content
= CONTENT_NONE
;
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;
8291 content
= TranslateContent(strcontent
);
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;
8307 strPath1
= strParent
;
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);
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);
8340 return ScraperPtr();
8342 foundDirectly
= (iFound
== 1);
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",
8358 m_pDS
->query(strSQL
);
8361 return m_pDS
->fv("allAudio").get_asBool();
8366 std::string
CVideoDatabase::GetContentForPath(const std::string
& strPath
)
8368 SScanSettings settings
;
8369 bool foundDirectly
= false;
8370 ScraperPtr scraper
= GetScraperForPath(strPath
, settings
, foundDirectly
);
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 ";
8382 sql
+= PrepareSQL("WHERE strPath = '%s'", strPath
.c_str());
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)
8389 return foundDirectly
? "tvshows" : "seasons";
8391 return TranslateContent(scraper
->Content());
8396 void CVideoDatabase::GetMovieGenresByName(const std::string
& strSearch
, CFileItemList
& items
)
8402 if (nullptr == m_pDB
)
8404 if (nullptr == m_pDS
)
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());
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")))
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;
8434 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, strSQL
);
8438 void CVideoDatabase::GetMovieCountriesByName(const std::string
& strSearch
, CFileItemList
& items
)
8444 if (nullptr == m_pDB
)
8446 if (nullptr == m_pDS
)
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());
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")))
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;
8476 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, strSQL
);
8480 void CVideoDatabase::GetTvShowGenresByName(const std::string
& strSearch
, CFileItemList
& items
)
8486 if (nullptr == m_pDB
)
8488 if (nullptr == m_pDS
)
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());
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")))
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;
8517 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, strSQL
);
8521 void CVideoDatabase::GetMovieActorsByName(const std::string
& strSearch
, CFileItemList
& items
)
8527 if (nullptr == m_pDB
)
8529 if (nullptr == m_pDS
)
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());
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")))
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;
8558 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, strSQL
);
8562 void CVideoDatabase::GetTvShowsActorsByName(const std::string
& strSearch
, CFileItemList
& items
)
8568 if (nullptr == m_pDB
)
8570 if (nullptr == m_pDS
)
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());
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")))
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;
8599 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, strSQL
);
8603 void CVideoDatabase::GetMusicVideoArtistsByName(const std::string
& strSearch
, CFileItemList
& items
)
8609 if (nullptr == m_pDB
)
8611 if (nullptr == m_pDS
)
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());
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")))
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;
8643 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, strSQL
);
8647 void CVideoDatabase::GetMusicVideoGenresByName(const std::string
& strSearch
, CFileItemList
& items
)
8653 if (nullptr == m_pDB
)
8655 if (nullptr == m_pDS
)
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());
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")))
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;
8684 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, strSQL
);
8688 void CVideoDatabase::GetMusicVideoAlbumsByName(const std::string
& strSearch
, CFileItemList
& items
)
8694 if (nullptr == m_pDB
)
8696 if (nullptr == m_pDS
)
8699 strSQL
= StringUtils::Format("SELECT DISTINCT"
8700 " musicvideo.c{:02},"
8701 " musicvideo.idMVideo,"
8706 " files.idFile=musicvideo.idFile"
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())
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")))
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;
8741 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, strSQL
);
8745 void CVideoDatabase::GetMusicVideosByAlbum(const std::string
& strSearch
, CFileItemList
& items
)
8751 if (nullptr == m_pDB
)
8753 if (nullptr == m_pDS
)
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());
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")))
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;
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
)
8795 if (nullptr == m_pDS
)
8800 std::string strSQL
= "select %s from musicvideo_view ";
8801 CVideoDbUrl videoUrl
;
8802 if (!videoUrl
.FromString(baseDir
))
8805 std::string strSQLExtra
;
8806 const CUrlOptions::UrlOptions
& options
= videoUrl
.GetOptions();
8807 std::string strArtist
;
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
)
8819 Filter extFilter
= filter
;
8820 SortDescription sorting
= sortDescription
;
8821 if (!BuildSQL(baseDir
, strSQLExtra
, extFilter
, strSQLExtra
, videoUrl
, sorting
))
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
)
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
))
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);
8877 if (!strArtist
.empty())
8878 items
.SetProperty("customtitle", strArtist
);
8883 CLog::Log(LOGERROR
, "{} failed", __FUNCTION__
);
8888 unsigned int CVideoDatabase::GetRandomMusicVideoIDs(const std::string
& strWhere
, std::vector
<std::pair
<int,int> > &songIDs
)
8892 if (nullptr == m_pDB
)
8894 if (nullptr == m_pDS
)
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;
8904 if (m_pDS
->num_rows() == 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()));
8916 return songIDs
.size();
8920 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, strWhere
);
8925 int CVideoDatabase::GetMatchingMusicVideo(const std::string
& strArtist
, const std::string
& strAlbum
, const std::string
& strTitle
)
8929 if (nullptr == m_pDB
)
8931 if (nullptr == m_pDS
)
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());
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());
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());
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
);
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")))
8961 int lResult
= m_pDS
->fv(0).get_asInt();
8967 CLog::Log(LOGERROR
, "{} failed", __FUNCTION__
);
8972 void CVideoDatabase::GetMoviesByName(const std::string
& strSearch
, CFileItemList
& items
)
8978 if (nullptr == m_pDB
)
8980 if (nullptr == m_pDS
)
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());
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")))
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()));
9010 if (setId
<= 0 || !CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_VIDEOLIBRARY_GROUPMOVIESETS
))
9011 path
= StringUtils::Format("videodb://movies/titles/{}", movieId
);
9013 path
= StringUtils::Format("videodb://movies/sets/{}/{}", setId
, movieId
);
9014 pItem
->SetPath(path
);
9015 pItem
->m_bIsFolder
=false;
9023 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, strSQL
);
9027 void CVideoDatabase::GetTvShowsByName(const std::string
& strSearch
, CFileItemList
& items
)
9033 if (nullptr == m_pDB
)
9035 if (nullptr == m_pDS
)
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());
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")))
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();
9067 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, strSQL
);
9071 void CVideoDatabase::GetEpisodesByName(const std::string
& strSearch
, CFileItemList
& items
)
9077 if (nullptr == m_pDB
)
9079 if (nullptr == m_pDS
)
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());
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")))
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;
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);
9124 if (nullptr == m_pDB
)
9126 if (nullptr == m_pDS
)
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());
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")))
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;
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
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);
9174 if (nullptr == m_pDB
)
9176 if (nullptr == m_pDS
)
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());
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")))
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;
9207 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, strSQL
);
9211 void CVideoDatabase::GetMoviesByPlot(const std::string
& strSearch
, CFileItemList
& items
)
9217 if (nullptr == m_pDB
)
9219 if (nullptr == m_pDS
)
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());
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")))
9238 CFileItemPtr
pItem(new CFileItem(m_pDS
->fv(1).get_asString()));
9240 StringUtils::Format("videodb://movies/titles/{}", m_pDS
->fv(0).get_asInt());
9241 pItem
->SetPath(path
);
9242 pItem
->m_bIsFolder
=false;
9252 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, strSQL
);
9256 void CVideoDatabase::GetMovieDirectorsByName(const std::string
& strSearch
, CFileItemList
& items
)
9262 if (nullptr == m_pDB
)
9264 if (nullptr == m_pDS
)
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());
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")))
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;
9295 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, strSQL
);
9299 void CVideoDatabase::GetTvShowsDirectorsByName(const std::string
& strSearch
, CFileItemList
& items
)
9305 if (nullptr == m_pDB
)
9307 if (nullptr == m_pDS
)
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());
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")))
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;
9338 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, strSQL
);
9342 void CVideoDatabase::GetMusicVideoDirectorsByName(const std::string
& strSearch
, CFileItemList
& items
)
9348 if (nullptr == m_pDB
)
9350 if (nullptr == m_pDS
)
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());
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")))
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;
9381 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, strSQL
);
9385 void CVideoDatabase::CleanDatabase(CGUIDialogProgressBarHandle
* handle
,
9386 const std::set
<int>& paths
,
9389 CGUIDialogProgress
* progress
= NULL
;
9392 if (nullptr == m_pDB
)
9394 if (nullptr == m_pDS
)
9396 if (nullptr == m_pDS2
)
9399 auto start
= std::chrono::steady_clock::now();
9400 CLog::Log(LOGINFO
, "{}: Starting videodatabase cleanup ..", __FUNCTION__
);
9401 CServiceBroker::GetAnnouncementManager()->Announce(ANNOUNCEMENT::VideoLibrary
,
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
);
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);
9421 progress
->ShowProgressBar(true);
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";
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";
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();
9450 std::string lastDir
;
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();
9469 if (URIUtils::IsPlugin(fullPath
))
9471 SScanSettings settings
;
9472 bool foundDirectly
= false;
9473 ScraperPtr scraper
= GetScraperForPath(fullPath
, settings
, foundDirectly
);
9475 CPluginDirectory::CheckExists(TranslateContent(scraper
->Content()), fullPath
))
9480 // Only consider keeping this file if not optical and belonging to a (matching) source
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
)
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))
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())
9516 CServiceBroker::GetAnnouncementManager()->Announce(ANNOUNCEMENT::VideoLibrary
,
9521 else if (handle
!= NULL
)
9522 handle
->SetPercentage(current
* 100 / (float)total
);
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
;
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 " +
9580 int pathHashCount
= m_pDS
->num_rows();
9581 while (!m_pDS
->eof())
9583 InvalidatePathHash(m_pDS
->fv("strPath").get_asString());
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
;
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
;
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
;
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))";
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
))
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() + ",";
9656 if (!strIds
.empty())
9658 sql
= PrepareSQL("DELETE FROM path WHERE idPath IN (%s)",
9659 StringUtils::TrimRight(strIds
, ",").c_str());
9661 sql
= "DELETE FROM tvshowlinkpath "
9662 "WHERE NOT EXISTS (SELECT 1 FROM path WHERE path.idPath = tvshowlinkpath.idPath)";
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 = "
9673 while (!m_pDS
->eof())
9675 tvshowIDs
.push_back(m_pDS
->fv(0).get_asInt());
9676 tvshowsToDelete
+= m_pDS
->fv(0).get_asString() + ",";
9680 if (!tvshowsToDelete
.empty())
9682 sql
= "DELETE FROM tvshow WHERE idShow IN (" +
9683 StringUtils::TrimRight(tvshowsToDelete
, ",") + ")";
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
;
9699 CLog::Log(LOGDEBUG
, LOGDATABASE
, "{}: Cleaning path table", __FUNCTION__
);
9700 sql
= StringUtils::Format(
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
);
9717 CLog::Log(LOGDEBUG
, LOGDATABASE
, "{}: Cleaning genre table", __FUNCTION__
);
9719 "DELETE FROM genre "
9720 "WHERE NOT EXISTS (SELECT 1 FROM genre_link WHERE genre_link.genre_id = genre.genre_id)";
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)";
9728 CLog::Log(LOGDEBUG
, LOGDATABASE
, "{}: Cleaning actor table of actors, directors and writers",
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 = "
9735 "AND NOT EXISTS (SELECT 1 FROM writer_link WHERE writer_link.actor_id = actor.actor_id)";
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)";
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)";
9749 CommitTransaction();
9752 handle
->SetTitle(g_localizeStrings
.Get(331));
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__
,
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);
9779 CLog::Log(LOGERROR
, "{} failed", __FUNCTION__
);
9780 RollbackTransaction();
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())
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";
9810 else if (mediaType
== MediaTypeMusicVideo
)
9812 idField
= "idMVideo";
9813 parentPathIdField
= StringUtils::Format("{}.c{:02}", table
, VIDEODB_ID_MUSICVIDEO_PARENTPATHID
);
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(),
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
;
9839 while (!m_pDS2
->eof())
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
);
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
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});
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
)
9897 if (scanSettings
.parent_name
)
9898 pathsDeleteDecisions
.insert(std::make_pair(m_pDS2
->fv(2).get_asInt(), del
));
9903 deletedFileIDs
+= m_pDS2
->fv(1).get_asString() + ",";
9904 cleanedIDs
.push_back(m_pDS2
->fv(0).get_asInt());
9914 void CVideoDatabase::DumpToDummyFiles(const std::string
&path
)
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
);
9937 std::string episodePath
= URIUtils::AddFileToFolder(TVFolder
, episode
);
9939 if (file
.OpenForWrite(episodePath
))
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
);
9954 if (file
.OpenForWrite(URIUtils::AddFileToFolder(moviePath
, movie
)))
9959 void CVideoDatabase::ExportToXML(const std::string
&path
, bool singleFile
/* = true */, bool images
/* = false */, bool actorThumbs
/* false */, bool overwrite
/*=false*/)
9962 CGUIDialogProgress
*progress
=NULL
;
9965 if (nullptr == m_pDB
)
9967 if (nullptr == m_pDS
)
9969 if (nullptr == m_pDS2
)
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());
9978 std::unique_ptr
<Dataset
> pDS2
;
9979 pDS2
.reset(m_pDB
->CreateDataset());
9980 if (nullptr == pDS2
)
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");
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
);
10007 std::string sql
= "select * from movie_view";
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);
10019 progress
->ShowProgressBar(true);
10022 int total
= m_pDS
->num_rows();
10025 // create our xml document
10026 CXBMCTinyXML xmlDoc
;
10027 TiXmlDeclaration
decl("1.0", "UTF-8", "yes");
10028 xmlDoc
.InsertEndChild(decl
);
10029 TiXmlNode
*pMain
= NULL
;
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
);
10054 movie
.Save(pMain
, "movie", singleFile
);
10056 // reset old skip state
10057 bool bSkip
= false;
10061 progress
->SetLine(1, CVariant
{movie
.m_strTitle
});
10062 progress
->SetPercentage(current
* 100 / total
);
10063 progress
->Progress();
10064 if (progress
->IsCanceled())
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
);
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
);
10106 TiXmlDeclaration
decl("1.0", "UTF-8", "yes");
10107 xmlDoc
.InsertEndChild(decl
);
10110 if (images
&& !bSkip
)
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
);
10125 ExportActorThumbs(actorsDir
, movie
, !singleFile
, overwrite
);
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";
10142 total
= m_pDS
->num_rows();
10145 while (!m_pDS
->eof())
10147 std::string title
= m_pDS
->fv("strSet").get_asString();
10151 progress
->SetLine(1, CVariant
{title
});
10152 progress
->SetPercentage(current
* 100 / total
);
10153 progress
->Progress();
10154 if (progress
->IsCanceled())
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
);
10177 "CVideoDatabase::{} - Not exporting movie set '{}' as could not create folder '{}'",
10178 __FUNCTION__
, title
, itemPath
);
10185 // find all musicvideos
10186 sql
= "select * from musicvideo_view";
10190 total
= m_pDS
->num_rows();
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
);
10205 movie
.Save(pMain
, "musicvideo", singleFile
);
10207 // reset old skip state
10208 bool bSkip
= false;
10212 progress
->SetLine(1, CVariant
{movie
.m_strTitle
});
10213 progress
->SetPercentage(current
* 100 / total
);
10214 progress
->Progress();
10215 if (progress
->IsCanceled())
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
);
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__
,
10242 CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Error
, g_localizeStrings
.Get(20302), nfoFile
);
10251 TiXmlDeclaration
decl("1.0", "UTF-8", "yes");
10252 xmlDoc
.InsertEndChild(decl
);
10254 if (images
&& !bSkip
)
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
);
10274 // repeat for all tvshows
10275 sql
= "SELECT * FROM tvshow_view";
10278 total
= m_pDS
->num_rows();
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
);
10306 tvshow
.Save(pMain
, "tvshow", singleFile
);
10308 // reset old skip state
10309 bool bSkip
= false;
10313 progress
->SetLine(1, CVariant
{tvshow
.m_strTitle
});
10314 progress
->SetPercentage(current
* 100 / total
);
10315 progress
->Progress();
10316 if (progress
->IsCanceled())
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__
,
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
);
10351 TiXmlDeclaration
decl("1.0", "UTF-8", "yes");
10352 xmlDoc
.InsertEndChild(decl
);
10354 if (images
&& !bSkip
)
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
);
10366 ExportActorThumbs(actorsDir
, tvshow
, !singleFile
, overwrite
);
10368 // export season thumbs
10369 for (const auto &i
: seasonArt
)
10371 std::string seasonThumb
;
10373 seasonThumb
= "season-all";
10374 else if (i
.first
== 0)
10375 seasonThumb
= "season-specials";
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
);
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
);
10406 episode
.Save(pMain
, "episodedetails", singleFile
);
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
);
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
);
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
);
10447 TiXmlDeclaration
decl("1.0", "UTF-8", "yes");
10448 xmlDoc
.InsertEndChild(decl
);
10451 if (images
&& !bSkip
)
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
);
10465 ExportActorThumbs(actorsDir
, episode
, !singleFile
, overwrite
);
10474 if (!singleFile
&& progress
)
10476 progress
->SetPercentage(100);
10477 progress
->Progress();
10482 // now dump path info
10483 std::set
<std::string
> 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
);
10508 data
["root"] = exportRoot
;
10509 data
["file"] = xmlFile
;
10510 if (iFailCount
> 0)
10511 data
["failcount"] = iFailCount
;
10513 CServiceBroker::GetAnnouncementManager()->Announce(ANNOUNCEMENT::VideoLibrary
, "OnExport",
10518 CLog::Log(LOGERROR
, "{} failed", __FUNCTION__
);
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
);
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
)
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
)
10562 if (nullptr == m_pDS
)
10565 CXBMCTinyXML xmlDoc
;
10566 if (!xmlDoc
.LoadFile(URIUtils::AddFileToFolder(path
, "videodb.xml")))
10569 TiXmlElement
*root
= xmlDoc
.RootElement();
10572 progress
= CServiceBroker::GetGUI()->GetWindowManager().GetWindow
<CGUIDialogProgress
>(WINDOW_DIALOG_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);
10581 progress
->ShowProgressBar(true);
10585 XMLUtils::GetInt(root
, "version", iVersion
);
10587 CLog::Log(LOGINFO
, "{}: Starting import (export version = {})", __FUNCTION__
, iVersion
);
10589 TiXmlElement
*movie
= root
->FirstChildElement();
10592 // first count the number of items...
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)
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();
10613 std::string strPath
;
10614 if (XMLUtils::GetString(path
,"url",strPath
) && !strPath
.empty())
10617 std::string content
;
10618 if (XMLUtils::GetString(path
,"content", content
) && !content
.empty())
10619 { // check the scraper exists, if so store the path
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();
10639 CVideoInfoTag info
;
10640 if (StringUtils::CompareNoCase(movie
->Value(), MediaTypeMovie
, 5) == 0)
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);
10674 else if (StringUtils::CompareNoCase(movie
->Value(), MediaTypeMusicVideo
, 10) == 0)
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);
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
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);
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
);
10714 // now load the episodes
10715 TiXmlElement
*episode
= movie
->FirstChildElement("episodedetails");
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())
10748 CLog::Log(LOGERROR
, "{} failed", __FUNCTION__
);
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
;
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
;
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
,"");
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
));
10833 bool CVideoDatabase::SetSingleValue(VideoDbContentType type
,
10836 const std::string
& strValue
)
10838 std::string strSQL
;
10841 if (nullptr == m_pDB
|| nullptr == m_pDS
)
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())
10869 return SetSingleValue(strTable
, StringUtils::Format("c{:02}", dbField
), strValue
, strField
,
10874 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, strSQL
);
10879 bool CVideoDatabase::SetSingleValue(VideoDbContentType type
,
10882 const std::string
& strValue
)
10884 MediaType mediaType
= DatabaseUtils::MediaTypeFromVideoContentType(type
);
10885 if (mediaType
== MediaTypeNone
)
10888 int dbFieldIndex
= DatabaseUtils::GetField(dbField
, mediaType
);
10889 if (dbFieldIndex
< 0)
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())
10904 if (nullptr == m_pDB
|| nullptr == m_pDS
)
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)
10915 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, sql
);
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 */)
10930 data
["type"] = content
;
10933 data
["transaction"] = true;
10934 CServiceBroker::GetAnnouncementManager()->Announce(ANNOUNCEMENT::VideoLibrary
, "OnRemove", data
);
10937 void CVideoDatabase::AnnounceUpdate(const std::string
& content
, int id
)
10940 data
["type"] = content
;
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
);
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())
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())
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())
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")
11092 auto option
= options
.find("tvshowid");
11093 if (option
!= options
.end())
11094 idShow
= (int)option
->second
.asInteger();
11097 option
= options
.find("season");
11098 if (option
!= options
.end())
11099 season
= (int)option
->second
.asInteger();
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())
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
);
11122 filter
.AppendWhere(PrepareSQL("episode_view.idShow = %i", idShow
));
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
));
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
));
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
);
11188 auto option
= options
.find("xsp");
11189 if (option
!= options
.end())
11191 CSmartPlaylist xsp
;
11192 if (!xsp
.LoadFromJson(option
->second
.asString()))
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()))
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
11231 videoUrl
.RemoveOption("filter");
11237 bool CVideoDatabase::SetVideoUserRating(int dbId
, int rating
, const MediaType
& mediaType
)
11241 if (nullptr == m_pDB
)
11243 if (nullptr == m_pDS
)
11246 if (mediaType
== MediaTypeNone
)
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
);
11266 CLog::Log(LOGERROR
, "{} ({}, {}, {}) failed", __FUNCTION__
, dbId
, mediaType
, rating
);
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();
11299 void CVideoDatabase::EraseAllForPath(const std::string
& path
)
11303 std::string itemsToDelete
;
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%%\")",
11310 while (!m_pDS
->eof())
11312 std::string file
= m_pDS
->fv("files.idFile").get_asString() + ",";
11313 itemsToDelete
+= file
;
11318 sql
= PrepareSQL("DELETE FROM path WHERE strPath LIKE \"%s%%\"", path
.c_str());
11321 if (!itemsToDelete
.empty())
11323 itemsToDelete
= "(" + StringUtils::TrimRight(itemsToDelete
, ",") + ")";
11325 sql
= "DELETE FROM files WHERE idFile IN " + itemsToDelete
;
11328 sql
= "DELETE FROM settings WHERE idFile IN " + itemsToDelete
;
11331 sql
= "DELETE FROM bookmark WHERE idFile IN " + itemsToDelete
;
11334 sql
= "DELETE FROM streamdetails WHERE idFile IN " + itemsToDelete
;
11340 CLog::Log(LOGERROR
, "{} failed", __FUNCTION__
);