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 "FileItemList.h"
13 #include "GUIInfoManager.h"
14 #include "GUIPassword.h"
15 #include "ServiceBroker.h"
16 #include "TextureCache.h"
19 #include "VideoInfoScanner.h"
20 #include "XBDateTime.h"
21 #include "addons/AddonManager.h"
22 #include "dbwrappers/dataset.h"
23 #include "dialogs/GUIDialogExtendedProgressBar.h"
24 #include "dialogs/GUIDialogKaiToast.h"
25 #include "dialogs/GUIDialogProgress.h"
26 #include "dialogs/GUIDialogYesNo.h"
27 #include "filesystem/Directory.h"
28 #include "filesystem/File.h"
29 #include "filesystem/MultiPathDirectory.h"
30 #include "filesystem/PluginDirectory.h"
31 #include "filesystem/StackDirectory.h"
32 #include "guilib/GUIComponent.h"
33 #include "guilib/GUIWindowManager.h"
34 #include "guilib/LocalizeStrings.h"
35 #include "guilib/guiinfo/GUIInfoLabels.h"
36 #include "imagefiles/ImageFileURL.h"
37 #include "interfaces/AnnouncementManager.h"
38 #include "messaging/helpers/DialogOKHelper.h"
39 #include "music/Artist.h"
40 #include "playlists/SmartPlayList.h"
41 #include "profiles/ProfileManager.h"
42 #include "settings/AdvancedSettings.h"
43 #include "settings/MediaSettings.h"
44 #include "settings/MediaSourceSettings.h"
45 #include "settings/Settings.h"
46 #include "settings/SettingsComponent.h"
47 #include "storage/MediaManager.h"
48 #include "utils/ArtUtils.h"
49 #include "utils/FileUtils.h"
50 #include "utils/GroupUtils.h"
51 #include "utils/LabelFormatter.h"
52 #include "utils/StringUtils.h"
53 #include "utils/URIUtils.h"
54 #include "utils/Variant.h"
55 #include "utils/XMLUtils.h"
56 #include "utils/log.h"
57 #include "video/VideoDbUrl.h"
58 #include "video/VideoFileItemClassify.h"
59 #include "video/VideoInfoTag.h"
60 #include "video/VideoLibraryQueue.h"
61 #include "video/VideoManagerTypes.h"
62 #include "video/VideoThumbLoader.h"
68 #include <unordered_set>
71 using namespace dbiplus
;
72 using namespace XFILE
;
73 using namespace ADDON
;
75 using namespace KODI::MESSAGING
;
76 using namespace KODI::GUILIB
;
77 using namespace KODI::VIDEO
;
79 //********************************************************************************************************************************
80 CVideoDatabase::CVideoDatabase(void) = default;
82 //********************************************************************************************************************************
83 CVideoDatabase::~CVideoDatabase(void) = default;
85 //********************************************************************************************************************************
86 bool CVideoDatabase::Open()
88 return CDatabase::Open(CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_databaseVideo
);
91 void CVideoDatabase::CreateTables()
93 CLog::Log(LOGINFO
, "create bookmark table");
94 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");
96 CLog::Log(LOGINFO
, "create settings table");
97 m_pDS
->exec("CREATE TABLE settings ( idFile integer, Deinterlace bool,"
98 "ViewMode integer,ZoomAmount float, PixelRatio float, VerticalShift float, AudioStream integer, SubtitleStream integer,"
99 "SubtitleDelay float, SubtitlesOn bool, Brightness float, Contrast float, Gamma float,"
100 "VolumeAmplification float, AudioDelay float, ResumeTime integer,"
101 "Sharpness float, NoiseReduction float, NonLinStretch bool, PostProcess bool,"
102 "ScalingMethod integer, DeinterlaceMode integer, StereoMode integer, StereoInvert bool, VideoStream integer,"
103 "TonemapMethod integer, TonemapParam float, Orientation integer, CenterMixLevel integer)\n");
105 CLog::Log(LOGINFO
, "create stacktimes table");
106 m_pDS
->exec("CREATE TABLE stacktimes (idFile integer, times text)\n");
108 CLog::Log(LOGINFO
, "create genre table");
109 m_pDS
->exec("CREATE TABLE genre ( genre_id integer primary key, name TEXT)\n");
110 m_pDS
->exec("CREATE TABLE genre_link (genre_id integer, media_id integer, media_type TEXT)");
112 CLog::Log(LOGINFO
, "create country table");
113 m_pDS
->exec("CREATE TABLE country ( country_id integer primary key, name TEXT)");
114 m_pDS
->exec("CREATE TABLE country_link (country_id integer, media_id integer, media_type TEXT)");
116 CLog::Log(LOGINFO
, "create movie table");
117 std::string columns
= "CREATE TABLE movie ( idMovie integer primary key, idFile integer";
119 for (int i
= 0; i
< VIDEODB_MAX_COLUMNS
; i
++)
120 columns
+= StringUtils::Format(",c{:02} text", i
);
122 columns
+= ", idSet integer, userrating integer, premiered text)";
123 m_pDS
->exec(columns
);
125 CLog::Log(LOGINFO
, "create actor table");
126 m_pDS
->exec("CREATE TABLE actor ( actor_id INTEGER PRIMARY KEY, name TEXT, art_urls TEXT )");
127 m_pDS
->exec("CREATE TABLE actor_link(actor_id INTEGER, media_id INTEGER, media_type TEXT, role TEXT, cast_order INTEGER)");
128 m_pDS
->exec("CREATE TABLE director_link(actor_id INTEGER, media_id INTEGER, media_type TEXT)");
129 m_pDS
->exec("CREATE TABLE writer_link(actor_id INTEGER, media_id INTEGER, media_type TEXT)");
131 CLog::Log(LOGINFO
, "create path table");
133 "CREATE TABLE path ( idPath integer primary key, strPath text, strContent text, strScraper "
134 "text, strHash text, scanRecursive integer, useFolderNames bool, strSettings text, noUpdate "
135 "bool, exclude bool, allAudio bool, dateAdded text, idParentPath integer)");
137 CLog::Log(LOGINFO
, "create files table");
138 m_pDS
->exec("CREATE TABLE files ( idFile integer primary key, idPath integer, strFilename text, playCount integer, lastPlayed text, dateAdded text)");
140 CLog::Log(LOGINFO
, "create tvshow table");
141 columns
= "CREATE TABLE tvshow ( idShow integer primary key";
143 for (int i
= 0; i
< VIDEODB_MAX_COLUMNS
; i
++)
144 columns
+= StringUtils::Format(",c{:02} text", i
);
146 columns
+= ", userrating integer, duration INTEGER)";
147 m_pDS
->exec(columns
);
149 CLog::Log(LOGINFO
, "create episode table");
150 columns
= "CREATE TABLE episode ( idEpisode integer primary key, idFile integer";
151 for (int i
= 0; i
< VIDEODB_MAX_COLUMNS
; i
++)
154 if ( i
== VIDEODB_ID_EPISODE_SEASON
|| i
== VIDEODB_ID_EPISODE_EPISODE
|| i
== VIDEODB_ID_EPISODE_BOOKMARK
)
155 column
= StringUtils::Format(",c{:02} varchar(24)", i
);
157 column
= StringUtils::Format(",c{:02} text", i
);
161 columns
+= ", idShow integer, userrating integer, idSeason integer)";
162 m_pDS
->exec(columns
);
164 CLog::Log(LOGINFO
, "create tvshowlinkpath table");
165 m_pDS
->exec("CREATE TABLE tvshowlinkpath (idShow integer, idPath integer)\n");
167 CLog::Log(LOGINFO
, "create movielinktvshow table");
168 m_pDS
->exec("CREATE TABLE movielinktvshow ( idMovie integer, IdShow integer)\n");
170 CLog::Log(LOGINFO
, "create studio table");
171 m_pDS
->exec("CREATE TABLE studio ( studio_id integer primary key, name TEXT)\n");
172 m_pDS
->exec("CREATE TABLE studio_link (studio_id integer, media_id integer, media_type TEXT)");
174 CLog::Log(LOGINFO
, "create musicvideo table");
175 columns
= "CREATE TABLE musicvideo ( idMVideo integer primary key, idFile integer";
176 for (int i
= 0; i
< VIDEODB_MAX_COLUMNS
; i
++)
177 columns
+= StringUtils::Format(",c{:02} text", i
);
179 columns
+= ", userrating integer, premiered text)";
180 m_pDS
->exec(columns
);
182 CLog::Log(LOGINFO
, "create streaminfo table");
183 m_pDS
->exec("CREATE TABLE streamdetails (idFile integer, iStreamType integer, "
184 "strVideoCodec text, fVideoAspect float, iVideoWidth integer, iVideoHeight integer, "
185 "strAudioCodec text, iAudioChannels integer, strAudioLanguage text, "
186 "strSubtitleLanguage text, iVideoDuration integer, strStereoMode text, strVideoLanguage text, "
189 CLog::Log(LOGINFO
, "create sets table");
190 m_pDS
->exec("CREATE TABLE sets ( idSet integer primary key, strSet text, strOverview text)");
192 CLog::Log(LOGINFO
, "create seasons table");
193 m_pDS
->exec("CREATE TABLE seasons ( idSeason integer primary key, idShow integer, season integer, name text, userrating integer)");
195 CLog::Log(LOGINFO
, "create art table");
196 m_pDS
->exec("CREATE TABLE art(art_id INTEGER PRIMARY KEY, media_id INTEGER, media_type TEXT, type TEXT, url TEXT)");
198 CLog::Log(LOGINFO
, "create tag table");
199 m_pDS
->exec("CREATE TABLE tag (tag_id integer primary key, name TEXT)");
200 m_pDS
->exec("CREATE TABLE tag_link (tag_id integer, media_id integer, media_type TEXT)");
202 CLog::Log(LOGINFO
, "create rating table");
203 m_pDS
->exec("CREATE TABLE rating (rating_id INTEGER PRIMARY KEY, media_id INTEGER, media_type TEXT, rating_type TEXT, rating FLOAT, votes INTEGER)");
205 CLog::Log(LOGINFO
, "create uniqueid table");
206 m_pDS
->exec("CREATE TABLE uniqueid (uniqueid_id INTEGER PRIMARY KEY, media_id INTEGER, media_type TEXT, value TEXT, type TEXT)");
208 CLog::Log(LOGINFO
, "create videoversiontype table");
209 m_pDS
->exec("CREATE TABLE videoversiontype (id INTEGER PRIMARY KEY, name TEXT, owner INTEGER, "
210 "itemType INTEGER)");
211 CLog::Log(LOGINFO
, "populate videoversiontype table");
212 InitializeVideoVersionTypeTable(GetSchemaVersion());
214 CLog::Log(LOGINFO
, "create videoversion table");
215 m_pDS
->exec("CREATE TABLE videoversion (idFile INTEGER PRIMARY KEY, idMedia INTEGER, media_type "
216 "TEXT, itemType INTEGER, idType INTEGER)");
219 void CVideoDatabase::CreateLinkIndex(const char *table
)
221 m_pDS
->exec(PrepareSQL("CREATE UNIQUE INDEX ix_%s_1 ON %s (name(255))", table
, table
));
222 m_pDS
->exec(PrepareSQL("CREATE UNIQUE INDEX ix_%s_link_1 ON %s_link (%s_id, media_type(20), media_id)", table
, table
, table
));
223 m_pDS
->exec(PrepareSQL("CREATE UNIQUE INDEX ix_%s_link_2 ON %s_link (media_id, media_type(20), %s_id)", table
, table
, table
));
224 m_pDS
->exec(PrepareSQL("CREATE INDEX ix_%s_link_3 ON %s_link (media_type(20))", table
, table
));
227 void CVideoDatabase::CreateForeignLinkIndex(const char *table
, const char *foreignkey
)
229 m_pDS
->exec(PrepareSQL("CREATE UNIQUE INDEX ix_%s_link_1 ON %s_link (%s_id, media_type(20), media_id)", table
, table
, foreignkey
));
230 m_pDS
->exec(PrepareSQL("CREATE UNIQUE INDEX ix_%s_link_2 ON %s_link (media_id, media_type(20), %s_id)", table
, table
, foreignkey
));
231 m_pDS
->exec(PrepareSQL("CREATE INDEX ix_%s_link_3 ON %s_link (media_type(20))", table
, table
));
234 void CVideoDatabase::CreateAnalytics()
236 /* indexes should be added on any columns that are used in */
237 /* a where or a join. primary key on a column is the same as a */
238 /* unique index on that column, so there is no need to add any */
239 /* index if no other columns are referred */
241 /* order of indexes are important, for an index to be considered all */
242 /* columns up to the column in question have to have been specified */
243 /* select * from foolink where foo_id = 1, can not take */
244 /* advantage of a index that has been created on ( bar_id, foo_id ) */
245 /* however an index on ( foo_id, bar_id ) will be considered for use */
247 CLog::Log(LOGINFO
, "{} - creating indices", __FUNCTION__
);
248 m_pDS
->exec("CREATE INDEX ix_bookmark ON bookmark (idFile, type)");
249 m_pDS
->exec("CREATE UNIQUE INDEX ix_settings ON settings ( idFile )\n");
250 m_pDS
->exec("CREATE UNIQUE INDEX ix_stacktimes ON stacktimes ( idFile )\n");
251 m_pDS
->exec("CREATE INDEX ix_path ON path ( strPath(255) )");
252 m_pDS
->exec("CREATE INDEX ix_path2 ON path ( idParentPath )");
253 m_pDS
->exec("CREATE INDEX ix_files ON files ( idPath, strFilename(255) )");
255 m_pDS
->exec("CREATE UNIQUE INDEX ix_movie_file_1 ON movie (idFile, idMovie)");
256 m_pDS
->exec("CREATE UNIQUE INDEX ix_movie_file_2 ON movie (idMovie, idFile)");
258 m_pDS
->exec("CREATE UNIQUE INDEX ix_tvshowlinkpath_1 ON tvshowlinkpath ( idShow, idPath )\n");
259 m_pDS
->exec("CREATE UNIQUE INDEX ix_tvshowlinkpath_2 ON tvshowlinkpath ( idPath, idShow )\n");
260 m_pDS
->exec("CREATE UNIQUE INDEX ix_movielinktvshow_1 ON movielinktvshow ( idShow, idMovie)\n");
261 m_pDS
->exec("CREATE UNIQUE INDEX ix_movielinktvshow_2 ON movielinktvshow ( idMovie, idShow)\n");
263 m_pDS
->exec("CREATE UNIQUE INDEX ix_episode_file_1 on episode (idEpisode, idFile)");
264 m_pDS
->exec("CREATE UNIQUE INDEX id_episode_file_2 on episode (idFile, idEpisode)");
265 std::string createColIndex
=
266 StringUtils::Format("CREATE INDEX ix_episode_season_episode on episode (c{:02}, c{:02})",
267 VIDEODB_ID_EPISODE_SEASON
, VIDEODB_ID_EPISODE_EPISODE
);
268 m_pDS
->exec(createColIndex
);
269 createColIndex
= StringUtils::Format("CREATE INDEX ix_episode_bookmark on episode (c{:02})",
270 VIDEODB_ID_EPISODE_BOOKMARK
);
271 m_pDS
->exec(createColIndex
);
272 m_pDS
->exec("CREATE INDEX ix_episode_show1 on episode(idEpisode,idShow)");
273 m_pDS
->exec("CREATE INDEX ix_episode_show2 on episode(idShow,idEpisode)");
275 m_pDS
->exec("CREATE UNIQUE INDEX ix_musicvideo_file_1 on musicvideo (idMVideo, idFile)");
276 m_pDS
->exec("CREATE UNIQUE INDEX ix_musicvideo_file_2 on musicvideo (idFile, idMVideo)");
278 m_pDS
->exec("CREATE INDEX ixMovieBasePath ON movie ( c23(12) )");
279 m_pDS
->exec("CREATE INDEX ixMusicVideoBasePath ON musicvideo ( c14(12) )");
280 m_pDS
->exec("CREATE INDEX ixEpisodeBasePath ON episode ( c19(12) )");
282 m_pDS
->exec("CREATE INDEX ix_streamdetails ON streamdetails (idFile)");
283 m_pDS
->exec("CREATE INDEX ix_seasons ON seasons (idShow, season)");
284 m_pDS
->exec("CREATE INDEX ix_art ON art(media_id, media_type(20), type(20))");
286 m_pDS
->exec("CREATE INDEX ix_rating ON rating(media_id, media_type(20))");
288 m_pDS
->exec("CREATE INDEX ix_uniqueid1 ON uniqueid(media_id, media_type(20), type(20))");
289 m_pDS
->exec("CREATE INDEX ix_uniqueid2 ON uniqueid(media_type(20), value(20))");
291 m_pDS
->exec("CREATE UNIQUE INDEX ix_actor_1 ON actor (name(255))");
292 m_pDS
->exec("CREATE UNIQUE INDEX ix_actor_link_1 ON "
293 "actor_link (actor_id, media_type(20), media_id, role(255))");
294 m_pDS
->exec("CREATE INDEX ix_actor_link_2 ON "
295 "actor_link (media_id, media_type(20), actor_id)");
296 m_pDS
->exec("CREATE INDEX ix_actor_link_3 ON actor_link (media_type(20))");
298 m_pDS
->exec("CREATE INDEX ix_videoversion ON videoversion (idMedia, media_type(20))");
300 m_pDS
->exec(PrepareSQL("CREATE INDEX ix_movie_title ON movie (c%02d(255))", VIDEODB_ID_TITLE
));
302 CreateLinkIndex("tag");
303 CreateForeignLinkIndex("director", "actor");
304 CreateForeignLinkIndex("writer", "actor");
305 CreateLinkIndex("studio");
306 CreateLinkIndex("genre");
307 CreateLinkIndex("country");
309 CLog::Log(LOGINFO
, "{} - creating triggers", __FUNCTION__
);
310 m_pDS
->exec("CREATE TRIGGER delete_movie AFTER DELETE ON movie FOR EACH ROW BEGIN "
311 "DELETE FROM genre_link WHERE media_id=old.idMovie AND media_type='movie'; "
312 "DELETE FROM actor_link WHERE media_id=old.idMovie AND media_type='movie'; "
313 "DELETE FROM director_link WHERE media_id=old.idMovie AND media_type='movie'; "
314 "DELETE FROM studio_link WHERE media_id=old.idMovie AND media_type='movie'; "
315 "DELETE FROM country_link WHERE media_id=old.idMovie AND media_type='movie'; "
316 "DELETE FROM writer_link WHERE media_id=old.idMovie AND media_type='movie'; "
317 "DELETE FROM movielinktvshow WHERE idMovie=old.idMovie; "
318 "DELETE FROM art WHERE media_id=old.idMovie AND media_type='movie'; "
319 "DELETE FROM tag_link WHERE media_id=old.idMovie AND media_type='movie'; "
320 "DELETE FROM rating WHERE media_id=old.idMovie AND media_type='movie'; "
321 "DELETE FROM uniqueid WHERE media_id=old.idMovie AND media_type='movie'; "
322 "DELETE FROM videoversion "
323 "WHERE idFile=old.idFile AND idMedia=old.idMovie AND media_type='movie'; "
325 m_pDS
->exec("CREATE TRIGGER delete_tvshow AFTER DELETE ON tvshow FOR EACH ROW BEGIN "
326 "DELETE FROM actor_link WHERE media_id=old.idShow AND media_type='tvshow'; "
327 "DELETE FROM director_link WHERE media_id=old.idShow AND media_type='tvshow'; "
328 "DELETE FROM studio_link WHERE media_id=old.idShow AND media_type='tvshow'; "
329 "DELETE FROM tvshowlinkpath WHERE idShow=old.idShow; "
330 "DELETE FROM genre_link WHERE media_id=old.idShow AND media_type='tvshow'; "
331 "DELETE FROM movielinktvshow WHERE idShow=old.idShow; "
332 "DELETE FROM seasons WHERE idShow=old.idShow; "
333 "DELETE FROM art WHERE media_id=old.idShow AND media_type='tvshow'; "
334 "DELETE FROM tag_link WHERE media_id=old.idShow AND media_type='tvshow'; "
335 "DELETE FROM rating WHERE media_id=old.idShow AND media_type='tvshow'; "
336 "DELETE FROM uniqueid WHERE media_id=old.idShow AND media_type='tvshow'; "
338 m_pDS
->exec("CREATE TRIGGER delete_musicvideo AFTER DELETE ON musicvideo FOR EACH ROW BEGIN "
339 "DELETE FROM actor_link WHERE media_id=old.idMVideo AND media_type='musicvideo'; "
340 "DELETE FROM director_link WHERE media_id=old.idMVideo AND media_type='musicvideo'; "
341 "DELETE FROM genre_link WHERE media_id=old.idMVideo AND media_type='musicvideo'; "
342 "DELETE FROM studio_link WHERE media_id=old.idMVideo AND media_type='musicvideo'; "
343 "DELETE FROM art WHERE media_id=old.idMVideo AND media_type='musicvideo'; "
344 "DELETE FROM tag_link WHERE media_id=old.idMVideo AND media_type='musicvideo'; "
345 "DELETE FROM uniqueid WHERE media_id=old.idMVideo AND media_type='musicvideo'; "
347 m_pDS
->exec("CREATE TRIGGER delete_episode AFTER DELETE ON episode FOR EACH ROW BEGIN "
348 "DELETE FROM actor_link WHERE media_id=old.idEpisode AND media_type='episode'; "
349 "DELETE FROM director_link WHERE media_id=old.idEpisode AND media_type='episode'; "
350 "DELETE FROM writer_link WHERE media_id=old.idEpisode AND media_type='episode'; "
351 "DELETE FROM art WHERE media_id=old.idEpisode AND media_type='episode'; "
352 "DELETE FROM rating WHERE media_id=old.idEpisode AND media_type='episode'; "
353 "DELETE FROM uniqueid WHERE media_id=old.idEpisode AND media_type='episode'; "
355 m_pDS
->exec("CREATE TRIGGER delete_season AFTER DELETE ON seasons FOR EACH ROW BEGIN "
356 "DELETE FROM art WHERE media_id=old.idSeason AND media_type='season'; "
358 m_pDS
->exec("CREATE TRIGGER delete_set AFTER DELETE ON sets FOR EACH ROW BEGIN "
359 "DELETE FROM art WHERE media_id=old.idSet AND media_type='set'; "
361 m_pDS
->exec("CREATE TRIGGER delete_person AFTER DELETE ON actor FOR EACH ROW BEGIN "
362 "DELETE FROM art WHERE media_id=old.actor_id AND media_type IN ('actor','artist','writer','director'); "
364 m_pDS
->exec("CREATE TRIGGER delete_tag AFTER DELETE ON tag_link FOR EACH ROW BEGIN "
365 "DELETE FROM tag WHERE tag_id=old.tag_id AND tag_id NOT IN (SELECT DISTINCT tag_id FROM tag_link); "
367 m_pDS
->exec("CREATE TRIGGER delete_file AFTER DELETE ON files FOR EACH ROW BEGIN "
368 "DELETE FROM bookmark WHERE idFile=old.idFile; "
369 "DELETE FROM settings WHERE idFile=old.idFile; "
370 "DELETE FROM stacktimes WHERE idFile=old.idFile; "
371 "DELETE FROM streamdetails WHERE idFile=old.idFile; "
372 "DELETE FROM videoversion WHERE idFile=old.idFile; "
373 "DELETE FROM art WHERE media_id=old.idFile AND media_type='videoversion'; "
375 m_pDS
->exec("CREATE TRIGGER delete_videoversion AFTER DELETE ON videoversion FOR EACH ROW BEGIN "
376 "DELETE FROM art WHERE media_id=old.idFile AND media_type='videoversion'; "
377 "DELETE FROM streamdetails WHERE idFile=old.idFile; "
383 void CVideoDatabase::CreateViews()
385 CLog::Log(LOGINFO
, "create episode_view");
386 std::string episodeview
= PrepareSQL(
387 "CREATE VIEW episode_view AS SELECT "
389 " files.strFileName AS strFileName,"
390 " path.strPath AS strPath,"
391 " files.playCount AS playCount,"
392 " files.lastPlayed AS lastPlayed,"
393 " files.dateAdded AS dateAdded,"
394 " tvshow.c%02d AS strTitle,"
395 " tvshow.c%02d AS genre,"
396 " tvshow.c%02d AS studio,"
397 " tvshow.c%02d AS premiered,"
398 " tvshow.c%02d AS mpaa,"
399 " bookmark.timeInSeconds AS resumeTimeInSeconds, "
400 " bookmark.totalTimeInSeconds AS totalTimeInSeconds, "
401 " bookmark.playerState AS playerState, "
402 " rating.rating AS rating, "
403 " rating.votes AS votes, "
404 " rating.rating_type AS rating_type, "
405 " uniqueid.value AS uniqueid_value, "
406 " uniqueid.type AS uniqueid_type "
409 " files.idFile=episode.idFile"
411 " tvshow.idShow=episode.idShow"
413 " files.idPath=path.idPath"
414 " LEFT JOIN bookmark ON"
415 " bookmark.idFile=episode.idFile AND bookmark.type=1"
416 " LEFT JOIN rating ON"
417 " rating.rating_id=episode.c%02d"
418 " LEFT JOIN uniqueid ON"
419 " uniqueid.uniqueid_id=episode.c%02d",
420 VIDEODB_ID_TV_TITLE
, VIDEODB_ID_TV_GENRE
, VIDEODB_ID_TV_STUDIOS
, VIDEODB_ID_TV_PREMIERED
,
421 VIDEODB_ID_TV_MPAA
, VIDEODB_ID_EPISODE_RATING_ID
, VIDEODB_ID_EPISODE_IDENT_ID
);
422 m_pDS
->exec(episodeview
);
424 CLog::Log(LOGINFO
, "create tvshowcounts");
426 std::string tvshowcounts
= PrepareSQL("CREATE VIEW tvshowcounts AS SELECT "
427 " tvshow.idShow AS idShow,"
428 " MAX(files.lastPlayed) AS lastPlayed,"
429 " NULLIF(COUNT(episode.c12), 0) AS totalCount,"
430 " COUNT(files.playCount) AS watchedcount,"
431 " NULLIF(COUNT(DISTINCT(episode.c12)), 0) AS totalSeasons, "
432 " MAX(files.dateAdded) as dateAdded, "
433 " COUNT(bookmark.type) AS inProgressCount "
435 " LEFT JOIN episode ON"
436 " episode.idShow=tvshow.idShow"
437 " LEFT JOIN files ON"
438 " files.idFile=episode.idFile "
439 " LEFT JOIN bookmark ON"
440 " bookmark.idFile=files.idFile AND bookmark.type=1 "
441 "GROUP BY tvshow.idShow");
443 m_pDS
->exec(tvshowcounts
);
445 CLog::Log(LOGINFO
, "create tvshowlinkpath_minview");
446 // This view only exists to workaround a limitation in MySQL <5.7 which is not able to
447 // perform subqueries in joins.
448 // Also, the correct solution is to remove the path information altogether, since a
449 // TV series can always have multiple paths. It is used in the GUI at the moment, but
450 // such usage should be removed together with this view and the path columns in tvshow_view.
451 //!@todo Remove the hacky selection of a semi-random path for tvshows from the queries and UI
452 std::string tvshowlinkpathview
= PrepareSQL("CREATE VIEW tvshowlinkpath_minview AS SELECT "
454 " min(idPath) AS idPath "
455 "FROM tvshowlinkpath "
457 m_pDS
->exec(tvshowlinkpathview
);
459 CLog::Log(LOGINFO
, "create tvshow_view");
461 std::string tvshowview
= PrepareSQL("CREATE VIEW tvshow_view AS SELECT "
463 " path.idParentPath AS idParentPath,"
464 " path.strPath AS strPath,"
465 " tvshowcounts.dateAdded AS dateAdded,"
466 " lastPlayed, totalCount, watchedcount, totalSeasons, "
467 " rating.rating AS rating, "
468 " rating.votes AS votes, "
469 " rating.rating_type AS rating_type, "
470 " uniqueid.value AS uniqueid_value, "
471 " uniqueid.type AS uniqueid_type, "
472 " tvshowcounts.inProgressCount AS inProgressCount "
474 " LEFT JOIN tvshowlinkpath_minview ON "
475 " tvshowlinkpath_minview.idShow=tvshow.idShow"
477 " path.idPath=tvshowlinkpath_minview.idPath"
478 " INNER JOIN tvshowcounts ON"
479 " tvshow.idShow = tvshowcounts.idShow "
480 " LEFT JOIN rating ON"
481 " rating.rating_id=tvshow.c%02d "
482 " LEFT JOIN uniqueid ON"
483 " uniqueid.uniqueid_id=tvshow.c%02d ",
484 VIDEODB_ID_TV_RATING_ID
, VIDEODB_ID_TV_IDENT_ID
);
486 m_pDS
->exec(tvshowview
);
488 CLog::Log(LOGINFO
, "create season_view");
490 std::string seasonview
= PrepareSQL("CREATE VIEW season_view AS SELECT "
491 " seasons.idSeason AS idSeason,"
492 " seasons.idShow AS idShow,"
493 " seasons.season AS season,"
494 " seasons.name AS name,"
495 " seasons.userrating AS userrating,"
496 " tvshow_view.strPath AS strPath,"
497 " tvshow_view.c%02d AS showTitle,"
498 " tvshow_view.c%02d AS plot,"
499 " tvshow_view.c%02d AS premiered,"
500 " tvshow_view.c%02d AS genre,"
501 " tvshow_view.c%02d AS studio,"
502 " tvshow_view.c%02d AS mpaa,"
503 " count(DISTINCT episode.idEpisode) AS episodes,"
504 " count(files.playCount) AS playCount,"
505 " min(episode.c%02d) AS aired, "
506 " count(bookmark.type) AS inProgressCount "
508 " JOIN tvshow_view ON"
509 " tvshow_view.idShow = seasons.idShow"
511 " episode.idShow = seasons.idShow AND episode.c%02d = seasons.season"
513 " files.idFile = episode.idFile "
514 " LEFT JOIN bookmark ON"
515 " bookmark.idFile = files.idFile AND bookmark.type = 1 "
516 "GROUP BY seasons.idSeason,"
520 " seasons.userrating,"
521 " tvshow_view.strPath,"
522 " tvshow_view.c%02d,"
523 " tvshow_view.c%02d,"
524 " tvshow_view.c%02d,"
525 " tvshow_view.c%02d,"
526 " tvshow_view.c%02d,"
527 " tvshow_view.c%02d ",
528 VIDEODB_ID_TV_TITLE
, VIDEODB_ID_TV_PLOT
, VIDEODB_ID_TV_PREMIERED
,
529 VIDEODB_ID_TV_GENRE
, VIDEODB_ID_TV_STUDIOS
, VIDEODB_ID_TV_MPAA
,
530 VIDEODB_ID_EPISODE_AIRED
, VIDEODB_ID_EPISODE_SEASON
,
531 VIDEODB_ID_TV_TITLE
, VIDEODB_ID_TV_PLOT
, VIDEODB_ID_TV_PREMIERED
,
532 VIDEODB_ID_TV_GENRE
, VIDEODB_ID_TV_STUDIOS
, VIDEODB_ID_TV_MPAA
);
534 m_pDS
->exec(seasonview
);
536 CLog::Log(LOGINFO
, "create musicvideo_view");
537 m_pDS
->exec(PrepareSQL(
538 "CREATE VIEW musicvideo_view AS SELECT"
540 " files.strFileName as strFileName,"
541 " path.strPath as strPath,"
542 " files.playCount as playCount,"
543 " files.lastPlayed as lastPlayed,"
544 " files.dateAdded as dateAdded, "
545 " bookmark.timeInSeconds AS resumeTimeInSeconds, "
546 " bookmark.totalTimeInSeconds AS totalTimeInSeconds, "
547 " bookmark.playerState AS playerState, "
548 " uniqueid.value AS uniqueid_value, "
549 " uniqueid.type AS uniqueid_type "
552 " files.idFile=musicvideo.idFile"
554 " path.idPath=files.idPath"
555 " LEFT JOIN bookmark ON"
556 " bookmark.idFile=musicvideo.idFile AND bookmark.type=1"
557 " LEFT JOIN uniqueid ON"
558 " uniqueid.uniqueid_id=musicvideo.c%02d",
559 VIDEODB_ID_MUSICVIDEO_IDENT_ID
));
561 CLog::Log(LOGINFO
, "create movie_view");
563 std::string movieview
=
564 PrepareSQL("CREATE VIEW movie_view AS SELECT"
566 " sets.strSet AS strSet,"
567 " sets.strOverview AS strSetOverview,"
568 " files.strFileName AS strFileName,"
569 " path.strPath AS strPath,"
570 " files.playCount AS playCount,"
571 " files.lastPlayed AS lastPlayed, "
572 " files.dateAdded AS dateAdded, "
573 " bookmark.timeInSeconds AS resumeTimeInSeconds, "
574 " bookmark.totalTimeInSeconds AS totalTimeInSeconds, "
575 " bookmark.playerState AS playerState, "
576 " rating.rating AS rating, "
577 " rating.votes AS votes, "
578 " rating.rating_type AS rating_type, "
579 " uniqueid.value AS uniqueid_value, "
580 " uniqueid.type AS uniqueid_type, "
583 " FROM videoversion vv "
584 " WHERE vv.idMedia = movie.idMovie "
585 " AND vv.media_type = '%s' "
586 " AND vv.itemType = %i "
587 " AND vv.idFile <> movie.idFile "
588 " ) AS hasVideoVersions, "
591 " FROM videoversion vv "
592 " WHERE vv.idMedia = movie.idMovie "
593 " AND vv.media_type = '%s' "
594 " AND vv.itemType = %i "
595 " ) AS hasVideoExtras, "
597 " WHEN vv.idFile = movie.idFile AND vv.itemType = %i THEN 1 "
599 " END AS isDefaultVersion, "
600 " vv.idFile AS videoVersionIdFile, "
601 " vvt.id AS videoVersionTypeId,"
602 " vvt.name AS videoVersionTypeName,"
603 " vvt.itemType AS videoVersionTypeItemType "
606 " sets.idSet = movie.idSet"
607 " LEFT JOIN rating ON"
608 " rating.rating_id = movie.c%02d"
609 " LEFT JOIN uniqueid ON"
610 " uniqueid.uniqueid_id = movie.c%02d"
611 " LEFT JOIN videoversion vv ON"
612 " vv.idMedia = movie.idMovie AND vv.media_type = '%s' AND vv.itemType = %i"
613 " JOIN videoversiontype vvt ON"
614 " vvt.id = vv.idType AND vvt.itemType = vv.itemType"
616 " files.idFile = vv.idFile"
618 " path.idPath = files.idPath"
619 " LEFT JOIN bookmark ON"
620 " bookmark.idFile = vv.idFile AND bookmark.type = 1",
621 MediaTypeMovie
, VideoAssetType::VERSION
, MediaTypeMovie
, VideoAssetType::EXTRA
,
622 VideoAssetType::VERSION
, VIDEODB_ID_RATING_ID
, VIDEODB_ID_IDENT_ID
, MediaTypeMovie
,
623 VideoAssetType::VERSION
);
624 m_pDS
->exec(movieview
);
627 //********************************************************************************************************************************
628 int CVideoDatabase::GetPathId(const std::string
& strPath
)
634 if (nullptr == m_pDB
)
636 if (nullptr == m_pDS
)
639 std::string
strPath1(strPath
);
640 if (URIUtils::IsStack(strPath
) || StringUtils::StartsWithNoCase(strPath
, "rar://") || StringUtils::StartsWithNoCase(strPath
, "zip://"))
641 URIUtils::GetParentPath(strPath
,strPath1
);
643 URIUtils::AddSlashAtEnd(strPath1
);
645 strSQL
=PrepareSQL("select idPath from path where strPath='%s'",strPath1
.c_str());
646 m_pDS
->query(strSQL
);
648 idPath
= m_pDS
->fv("path.idPath").get_asInt();
655 CLog::Log(LOGERROR
, "{} unable to getpath ({})", __FUNCTION__
, strSQL
);
660 bool CVideoDatabase::GetPaths(std::set
<std::string
> &paths
)
664 if (nullptr == m_pDB
)
666 if (nullptr == m_pDS
)
671 // grab all paths with movie content set
672 if (!m_pDS
->query("select strPath,noUpdate from path"
673 " where (strContent = 'movies' or strContent = 'musicvideos')"
674 " and strPath NOT like 'multipath://%%'"
675 " order by strPath"))
678 while (!m_pDS
->eof())
680 if (!m_pDS
->fv("noUpdate").get_asBool())
681 paths
.insert(m_pDS
->fv("strPath").get_asString());
686 // then grab all tvshow paths
687 if (!m_pDS
->query("select strPath,noUpdate from path"
688 " where ( strContent = 'tvshows'"
689 " or idPath in (select idPath from tvshowlinkpath))"
690 " and strPath NOT like 'multipath://%%'"
691 " order by strPath"))
694 while (!m_pDS
->eof())
696 if (!m_pDS
->fv("noUpdate").get_asBool())
697 paths
.insert(m_pDS
->fv("strPath").get_asString());
702 // finally grab all other paths holding a movie which is not a stack or a rar archive
703 // - this isnt perfect but it should do fine in most situations.
704 // reason we need it to hold a movie is stacks from different directories (cdx folders for instance)
705 // not making mistakes must take priority
706 if (!m_pDS
->query("select strPath,noUpdate from path"
707 " where idPath in (select idPath from files join movie on movie.idFile=files.idFile)"
708 " and idPath NOT in (select idPath from tvshowlinkpath)"
709 " 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
710 " and idPath NOT in (select idPath from files where strFileName like 'index.bdmv')" // bluray folders get stacked to a single item in parent folder
711 " and strPath NOT like 'multipath://%%'"
712 " and strContent NOT in ('movies', 'tvshows', 'None')" // these have been added above
713 " order by strPath"))
716 while (!m_pDS
->eof())
718 if (!m_pDS
->fv("noUpdate").get_asBool())
719 paths
.insert(m_pDS
->fv("strPath").get_asString());
727 CLog::Log(LOGERROR
, "{} failed", __FUNCTION__
);
732 bool CVideoDatabase::GetPathsLinkedToTvShow(int idShow
, std::vector
<std::string
> &paths
)
737 sql
= PrepareSQL("SELECT strPath FROM path JOIN tvshowlinkpath ON tvshowlinkpath.idPath=path.idPath WHERE idShow=%i", idShow
);
739 while (!m_pDS
->eof())
741 paths
.emplace_back(m_pDS
->fv(0).get_asString());
748 CLog::Log(LOGERROR
, "{} error during query: {}", __FUNCTION__
, sql
);
753 bool CVideoDatabase::GetPathsForTvShow(int idShow
, std::set
<int>& paths
)
758 if (nullptr == m_pDB
)
760 if (nullptr == m_pDS
)
764 strSQL
= PrepareSQL("SELECT strPath FROM tvshow_view WHERE idShow=%i", idShow
);
765 if (m_pDS
->query(strSQL
))
766 paths
.insert(GetPathId(m_pDS
->fv(0).get_asString()));
768 // add all other known paths
769 strSQL
= PrepareSQL("SELECT DISTINCT idPath FROM files JOIN episode ON episode.idFile=files.idFile WHERE episode.idShow=%i",idShow
);
770 m_pDS
->query(strSQL
);
771 while (!m_pDS
->eof())
773 paths
.insert(m_pDS
->fv(0).get_asInt());
781 CLog::Log(LOGERROR
, "{} error during query: {}", __FUNCTION__
, strSQL
);
786 int CVideoDatabase::RunQuery(const std::string
&sql
)
788 auto start
= std::chrono::steady_clock::now();
791 if (m_pDS
->query(sql
))
793 rows
= m_pDS
->num_rows();
798 auto end
= std::chrono::steady_clock::now();
799 auto duration
= std::chrono::duration_cast
<std::chrono::milliseconds
>(end
- start
);
801 CLog::Log(LOGDEBUG
, LOGDATABASE
, "{} took {} ms for {} items query: {}", __FUNCTION__
,
802 duration
.count(), rows
, sql
);
807 bool CVideoDatabase::GetSubPaths(const std::string
&basepath
, std::vector
<std::pair
<int, std::string
>>& subpaths
)
812 if (!m_pDB
|| !m_pDS
)
815 std::string
path(basepath
);
816 URIUtils::AddSlashAtEnd(path
);
817 sql
= PrepareSQL("SELECT idPath,strPath FROM path WHERE SUBSTR(strPath,1,%i)='%s'"
818 " AND idPath NOT IN (SELECT idPath FROM files WHERE strFileName LIKE 'video_ts.ifo')"
819 " AND idPath NOT IN (SELECT idPath FROM files WHERE strFileName LIKE 'index.bdmv')"
820 , StringUtils::utf8_strlen(path
.c_str()), path
.c_str());
823 while (!m_pDS
->eof())
825 subpaths
.emplace_back(m_pDS
->fv(0).get_asInt(), m_pDS
->fv(1).get_asString());
833 CLog::Log(LOGERROR
, "{} error during query: {}", __FUNCTION__
, sql
);
838 int CVideoDatabase::AddPath(const std::string
& strPath
, const std::string
&parentPath
/*= "" */, const CDateTime
& dateAdded
/* = CDateTime() */)
843 int idPath
= GetPathId(strPath
);
845 return idPath
; // already have the path
847 if (nullptr == m_pDB
)
849 if (nullptr == m_pDS
)
852 std::string
strPath1(strPath
);
853 if (URIUtils::IsStack(strPath
) || StringUtils::StartsWithNoCase(strPath
, "rar://") || StringUtils::StartsWithNoCase(strPath
, "zip://"))
854 URIUtils::GetParentPath(strPath
,strPath1
);
856 URIUtils::AddSlashAtEnd(strPath1
);
858 int idParentPath
= GetPathId(parentPath
.empty() ? URIUtils::GetParentPath(strPath1
) : parentPath
);
861 if (idParentPath
< 0)
863 if (dateAdded
.IsValid())
864 strSQL
=PrepareSQL("insert into path (idPath, strPath, dateAdded) values (NULL, '%s', '%s')", strPath1
.c_str(), dateAdded
.GetAsDBDateTime().c_str());
866 strSQL
=PrepareSQL("insert into path (idPath, strPath) values (NULL, '%s')", strPath1
.c_str());
870 if (dateAdded
.IsValid())
871 strSQL
= PrepareSQL("insert into path (idPath, strPath, dateAdded, idParentPath) values (NULL, '%s', '%s', %i)", strPath1
.c_str(), dateAdded
.GetAsDBDateTime().c_str(), idParentPath
);
873 strSQL
=PrepareSQL("insert into path (idPath, strPath, idParentPath) values (NULL, '%s', %i)", strPath1
.c_str(), idParentPath
);
876 idPath
= (int)m_pDS
->lastinsertid();
881 CLog::Log(LOGERROR
, "{} unable to addpath ({})", __FUNCTION__
, strSQL
);
886 bool CVideoDatabase::GetPathHash(const std::string
&path
, std::string
&hash
)
890 if (nullptr == m_pDB
)
892 if (nullptr == m_pDS
)
895 std::string strSQL
=PrepareSQL("select strHash from path where strPath='%s'", path
.c_str());
896 m_pDS
->query(strSQL
);
897 if (m_pDS
->num_rows() == 0)
899 hash
= m_pDS
->fv("strHash").get_asString();
904 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, path
);
910 bool CVideoDatabase::GetSourcePath(const std::string
&path
, std::string
&sourcePath
)
913 return GetSourcePath(path
, sourcePath
, dummy
);
916 bool CVideoDatabase::GetSourcePath(const std::string
&path
, std::string
&sourcePath
, SScanSettings
& settings
)
920 if (path
.empty() || m_pDB
== nullptr || m_pDS
== nullptr)
923 std::string strPath2
;
925 if (URIUtils::IsMultiPath(path
))
926 strPath2
= CMultiPathDirectory::GetFirstPath(path
);
930 std::string strPath1
= URIUtils::GetDirectory(strPath2
);
931 int idPath
= GetPathId(strPath1
);
935 // check if the given path already is a source itself
936 std::string strSQL
= PrepareSQL("SELECT path.useFolderNames, path.scanRecursive, path.noUpdate, path.exclude FROM path WHERE "
937 "path.idPath = %i AND "
938 "path.strContent IS NOT NULL AND path.strContent != '' AND "
939 "path.strScraper IS NOT NULL AND path.strScraper != ''", idPath
);
940 if (m_pDS
->query(strSQL
) && !m_pDS
->eof())
942 settings
.parent_name_root
= settings
.parent_name
= m_pDS
->fv(0).get_asBool();
943 settings
.recurse
= m_pDS
->fv(1).get_asInt();
944 settings
.noupdate
= m_pDS
->fv(2).get_asBool();
945 settings
.exclude
= m_pDS
->fv(3).get_asBool();
953 // look for parent paths until there is one which is a source
954 std::string strParent
;
956 while (URIUtils::GetParentPath(strPath1
, strParent
))
958 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());
959 if (m_pDS
->query(strSQL
) && !m_pDS
->eof())
961 std::string strContent
= m_pDS
->fv(0).get_asString();
962 std::string strScraper
= m_pDS
->fv(1).get_asString();
963 if (!strContent
.empty() && !strScraper
.empty())
965 settings
.parent_name_root
= settings
.parent_name
= m_pDS
->fv(2).get_asBool();
966 settings
.recurse
= m_pDS
->fv(3).get_asInt();
967 settings
.noupdate
= m_pDS
->fv(4).get_asBool();
968 settings
.exclude
= m_pDS
->fv(5).get_asBool();
974 strPath1
= strParent
;
980 sourcePath
= strParent
;
986 CLog::Log(LOGERROR
, "{} failed", __FUNCTION__
);
991 //********************************************************************************************************************************
992 int CVideoDatabase::AddFile(const std::string
& strFileNameAndPath
,
993 const std::string
& parentPath
/* = "" */,
994 const CDateTime
& dateAdded
/* = CDateTime() */,
995 int playcount
/* = 0 */,
996 const CDateTime
& lastPlayed
/* = CDateTime() */)
998 std::string strSQL
= "";
1002 if (nullptr == m_pDB
)
1004 if (nullptr == m_pDS
)
1007 const auto finalDateAdded
= GetDateAdded(strFileNameAndPath
, dateAdded
);
1009 std::string strFileName
, strPath
;
1010 SplitPath(strFileNameAndPath
,strPath
,strFileName
);
1012 int idPath
= AddPath(strPath
, parentPath
, finalDateAdded
);
1016 std::string strSQL
=PrepareSQL("select idFile from files where strFileName='%s' and idPath=%i", strFileName
.c_str(),idPath
);
1018 m_pDS
->query(strSQL
);
1019 if (m_pDS
->num_rows() > 0)
1021 idFile
= m_pDS
->fv("idFile").get_asInt() ;
1027 std::string strPlaycount
= "NULL";
1029 strPlaycount
= std::to_string(playcount
);
1030 std::string strLastPlayed
= "NULL";
1031 if (lastPlayed
.IsValid())
1032 strLastPlayed
= "'" + lastPlayed
.GetAsDBDateTime() + "'";
1034 strSQL
= PrepareSQL("INSERT INTO files (idFile, idPath, strFileName, playCount, lastPlayed, dateAdded) "
1035 "VALUES(NULL, %i, '%s', " + strPlaycount
+ ", " + strLastPlayed
+ ", '%s')",
1036 idPath
, strFileName
.c_str(), finalDateAdded
.GetAsDBDateTime().c_str());
1037 m_pDS
->exec(strSQL
);
1038 idFile
= (int)m_pDS
->lastinsertid();
1043 CLog::Log(LOGERROR
, "{} unable to addfile ({})", __FUNCTION__
, strSQL
);
1048 int CVideoDatabase::AddFile(const CFileItem
& item
)
1050 if (IsVideoDb(item
) && item
.HasVideoInfoTag())
1052 const auto videoInfoTag
= item
.GetVideoInfoTag();
1053 if (videoInfoTag
->m_iFileId
!= -1)
1054 return videoInfoTag
->m_iFileId
;
1056 return AddFile(*videoInfoTag
);
1058 return AddFile(item
.GetPath());
1061 int CVideoDatabase::AddFile(const CVideoInfoTag
& details
, const std::string
& parentPath
/* = "" */)
1063 return AddFile(details
.GetPath(), parentPath
, details
.m_dateAdded
, details
.GetPlayCount(),
1064 details
.m_lastPlayed
);
1067 void CVideoDatabase::UpdateFileDateAdded(CVideoInfoTag
& details
)
1069 if (details
.GetPath().empty() || GetAndFillFileId(details
) <= 0)
1072 CDateTime finalDateAdded
;
1075 if (nullptr == m_pDB
)
1077 if (nullptr == m_pDS
)
1080 finalDateAdded
= GetDateAdded(details
.GetPath(), details
.m_dateAdded
);
1082 m_pDS
->exec(PrepareSQL("UPDATE files SET dateAdded='%s' WHERE idFile=%d",
1083 finalDateAdded
.GetAsDBDateTime().c_str(), details
.m_iFileId
));
1087 CLog::Log(LOGERROR
, "{}({}, {}) failed", __FUNCTION__
, CURL::GetRedacted(details
.GetPath()),
1088 finalDateAdded
.GetAsDBDateTime());
1092 bool CVideoDatabase::SetPathHash(const std::string
&path
, const std::string
&hash
)
1096 if (nullptr == m_pDB
)
1098 if (nullptr == m_pDS
)
1101 int idPath
= AddPath(path
);
1102 if (idPath
< 0) return false;
1104 std::string strSQL
=PrepareSQL("update path set strHash='%s' where idPath=%ld", hash
.c_str(), idPath
);
1105 m_pDS
->exec(strSQL
);
1111 CLog::Log(LOGERROR
, "{} ({}, {}) failed", __FUNCTION__
, path
, hash
);
1117 bool CVideoDatabase::LinkMovieToTvshow(int idMovie
, int idShow
, bool bRemove
)
1121 if (nullptr == m_pDB
)
1123 if (nullptr == m_pDS
)
1126 if (bRemove
) // delete link
1128 std::string strSQL
=PrepareSQL("delete from movielinktvshow where idMovie=%i and idShow=%i", idMovie
, idShow
);
1129 m_pDS
->exec(strSQL
);
1133 std::string strSQL
=PrepareSQL("insert into movielinktvshow (idShow,idMovie) values (%i,%i)", idShow
,idMovie
);
1134 m_pDS
->exec(strSQL
);
1140 CLog::Log(LOGERROR
, "{} ({}, {}) failed", __FUNCTION__
, idMovie
, idShow
);
1146 bool CVideoDatabase::IsLinkedToTvshow(int idMovie
)
1150 if (nullptr == m_pDB
)
1152 if (nullptr == m_pDS
)
1155 std::string strSQL
=PrepareSQL("select * from movielinktvshow where idMovie=%i", idMovie
);
1156 m_pDS
->query(strSQL
);
1168 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, idMovie
);
1174 bool CVideoDatabase::GetLinksToTvShow(int idMovie
, std::vector
<int>& ids
)
1178 if (nullptr == m_pDB
)
1180 if (nullptr == m_pDS
)
1183 std::string strSQL
=PrepareSQL("select * from movielinktvshow where idMovie=%i", idMovie
);
1184 m_pDS2
->query(strSQL
);
1185 while (!m_pDS2
->eof())
1187 ids
.push_back(m_pDS2
->fv(1).get_asInt());
1196 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, idMovie
);
1203 //********************************************************************************************************************************
1204 int CVideoDatabase::GetFileId(const std::string
& strFilenameAndPath
)
1208 if (nullptr == m_pDB
)
1210 if (nullptr == m_pDS
)
1212 std::string strPath
, strFileName
;
1213 SplitPath(strFilenameAndPath
,strPath
,strFileName
);
1215 int idPath
= GetPathId(strPath
);
1219 strSQL
=PrepareSQL("select idFile from files where strFileName='%s' and idPath=%i", strFileName
.c_str(),idPath
);
1220 m_pDS
->query(strSQL
);
1221 if (m_pDS
->num_rows() > 0)
1223 int idFile
= m_pDS
->fv("files.idFile").get_asInt();
1231 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, strFilenameAndPath
);
1236 int CVideoDatabase::GetFileId(const CFileItem
&item
)
1239 if (item
.HasVideoInfoTag())
1240 fileId
= GetFileId(*item
.GetVideoInfoTag());
1243 fileId
= GetFileId(item
.GetPath());
1248 int CVideoDatabase::GetFileId(const CVideoInfoTag
& details
)
1250 if (details
.m_iFileId
> 0)
1251 return details
.m_iFileId
;
1253 const auto& filePath
= details
.GetPath();
1254 if (filePath
.empty())
1257 return GetFileId(filePath
);
1260 int CVideoDatabase::GetAndFillFileId(CVideoInfoTag
& details
)
1262 details
.m_iFileId
= GetFileId(details
);
1263 return details
.m_iFileId
;
1266 //********************************************************************************************************************************
1267 int CVideoDatabase::GetMovieId(const std::string
& strFilenameAndPath
)
1271 if (nullptr == m_pDB
)
1273 if (nullptr == m_pDS
)
1277 // needed for query parameters
1278 int idFile
= GetFileId(strFilenameAndPath
);
1280 std::string strPath
;
1283 std::string strFile
;
1284 SplitPath(strFilenameAndPath
,strPath
,strFile
);
1286 // have to join movieinfo table for correct results
1287 idPath
= GetPathId(strPath
);
1288 if (idPath
< 0 && strPath
!= strFilenameAndPath
)
1292 if (idFile
== -1 && strPath
!= strFilenameAndPath
)
1297 strSQL
= PrepareSQL("SELECT idMovie FROM movie "
1298 " JOIN files ON files.idFile=movie.idFile "
1299 "WHERE files.idPath=%i",
1302 strSQL
= PrepareSQL("SELECT idMedia FROM videoversion "
1303 "WHERE idFile = %i AND media_type = '%s' AND itemType = %i",
1304 idFile
, MediaTypeMovie
, VideoAssetType::VERSION
);
1306 CLog::Log(LOGDEBUG
, LOGDATABASE
, "{} ({}), query = {}", __FUNCTION__
,
1307 CURL::GetRedacted(strFilenameAndPath
), strSQL
);
1308 m_pDS
->query(strSQL
);
1309 if (m_pDS
->num_rows() > 0)
1310 idMovie
= m_pDS
->fv(0).get_asInt();
1317 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, strFilenameAndPath
);
1322 int CVideoDatabase::GetTvShowId(const std::string
& strPath
)
1326 if (nullptr == m_pDB
)
1328 if (nullptr == m_pDS
)
1332 // have to join movieinfo table for correct results
1333 int idPath
= GetPathId(strPath
);
1338 std::string strPath1
=strPath
;
1339 std::string strParent
;
1342 strSQL
=PrepareSQL("select idShow from tvshowlinkpath where tvshowlinkpath.idPath=%i",idPath
);
1343 m_pDS
->query(strSQL
);
1347 while (iFound
== 0 && URIUtils::GetParentPath(strPath1
, strParent
))
1349 strSQL
=PrepareSQL("SELECT idShow FROM path INNER JOIN tvshowlinkpath ON tvshowlinkpath.idPath=path.idPath WHERE strPath='%s'",strParent
.c_str());
1350 m_pDS
->query(strSQL
);
1353 int idShow
= m_pDS
->fv("idShow").get_asInt();
1357 strPath1
= strParent
;
1360 if (m_pDS
->num_rows() > 0)
1361 idTvShow
= m_pDS
->fv("idShow").get_asInt();
1368 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, strPath
);
1373 int CVideoDatabase::GetEpisodeId(const std::string
& strFilenameAndPath
, int idEpisode
, int idSeason
) // input value is episode/season number hint - for multiparters
1377 if (nullptr == m_pDB
)
1379 if (nullptr == m_pDS
)
1382 // need this due to the nested GetEpisodeInfo query
1383 std::unique_ptr
<Dataset
> pDS
;
1384 pDS
.reset(m_pDB
->CreateDataset());
1388 int idFile
= GetFileId(strFilenameAndPath
);
1392 std::string strSQL
=PrepareSQL("select idEpisode from episode where idFile=%i", idFile
);
1394 CLog::Log(LOGDEBUG
, LOGDATABASE
, "{} ({}), query = {}", __FUNCTION__
,
1395 CURL::GetRedacted(strFilenameAndPath
), strSQL
);
1397 if (pDS
->num_rows() > 0)
1399 if (idEpisode
== -1)
1400 idEpisode
= pDS
->fv("episode.idEpisode").get_asInt();
1401 else // use the hint!
1406 int idTmpEpisode
= pDS
->fv("episode.idEpisode").get_asInt();
1407 GetEpisodeBasicInfo(strFilenameAndPath
, tag
, idTmpEpisode
);
1408 if (tag
.m_iEpisode
== idEpisode
&& (idSeason
== -1 || tag
.m_iSeason
== idSeason
)) {
1409 // match on the episode hint, and there's no season hint or a season hint match
1410 idEpisode
= idTmpEpisode
;
1428 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, strFilenameAndPath
);
1433 int CVideoDatabase::GetMusicVideoId(const std::string
& strFilenameAndPath
)
1437 if (nullptr == m_pDB
)
1439 if (nullptr == m_pDS
)
1442 int idFile
= GetFileId(strFilenameAndPath
);
1446 std::string strSQL
=PrepareSQL("select idMVideo from musicvideo where idFile=%i", idFile
);
1448 CLog::Log(LOGDEBUG
, LOGDATABASE
, "{} ({}), query = {}", __FUNCTION__
,
1449 CURL::GetRedacted(strFilenameAndPath
), strSQL
);
1450 m_pDS
->query(strSQL
);
1452 if (m_pDS
->num_rows() > 0)
1453 idMVideo
= m_pDS
->fv("idMVideo").get_asInt();
1460 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, strFilenameAndPath
);
1465 //********************************************************************************************************************************
1466 int CVideoDatabase::AddNewMovie(CVideoInfoTag
& details
)
1468 assert(m_pDB
->in_transaction());
1470 const auto filePath
= details
.GetPath();
1474 if (nullptr == m_pDB
)
1476 if (nullptr == m_pDS
)
1479 if (details
.m_iFileId
<= 0)
1481 details
.m_iFileId
= AddFile(details
);
1482 if (details
.m_iFileId
<= 0)
1487 PrepareSQL("INSERT INTO movie (idMovie, idFile) VALUES (NULL, %i)", details
.m_iFileId
));
1488 details
.m_iDbId
= static_cast<int>(m_pDS
->lastinsertid());
1490 PrepareSQL("INSERT INTO videoversion (idFile, idMedia, media_type, itemType, idType) "
1491 "VALUES(%i, %i, '%s', %i, %i)",
1492 details
.m_iFileId
, details
.m_iDbId
, MediaTypeMovie
, VideoAssetType::VERSION
,
1493 VIDEO_VERSION_ID_DEFAULT
));
1495 return details
.m_iDbId
;
1499 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, filePath
);
1504 bool CVideoDatabase::AddPathToTvShow(int idShow
, const std::string
&path
, const std::string
&parentPath
, const CDateTime
& dateAdded
/* = CDateTime() */)
1506 // Check if this path is already added
1507 int idPath
= GetPathId(path
);
1509 idPath
= AddPath(path
, parentPath
, GetDateAdded(path
, dateAdded
));
1511 return ExecuteQuery(PrepareSQL("REPLACE INTO tvshowlinkpath(idShow, idPath) VALUES (%i,%i)", idShow
, idPath
));
1514 int CVideoDatabase::AddTvShow()
1516 if (ExecuteQuery("INSERT INTO tvshow(idShow) VALUES(NULL)"))
1517 return (int)m_pDS
->lastinsertid();
1521 //********************************************************************************************************************************
1522 int CVideoDatabase::AddNewEpisode(int idShow
, CVideoInfoTag
& details
)
1524 const auto filePath
= details
.GetPath();
1528 if (nullptr == m_pDB
|| nullptr == m_pDS
)
1531 if (details
.m_iFileId
<= 0)
1533 details
.m_iFileId
= AddFile(details
);
1534 if (details
.m_iFileId
<= 0)
1538 std::string strSQL
=
1539 PrepareSQL("INSERT INTO episode (idEpisode, idFile, idShow) VALUES (NULL, %i, %i)",
1540 details
.m_iFileId
, idShow
);
1541 m_pDS
->exec(strSQL
);
1542 details
.m_iDbId
= static_cast<int>(m_pDS
->lastinsertid());
1544 return details
.m_iDbId
;
1548 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, filePath
);
1553 int CVideoDatabase::AddNewMusicVideo(CVideoInfoTag
& details
)
1555 const auto filePath
= details
.GetPath();
1559 if (nullptr == m_pDB
)
1561 if (nullptr == m_pDS
)
1564 if (details
.m_iFileId
<= 0)
1566 details
.m_iFileId
= AddFile(details
);
1567 if (details
.m_iFileId
<= 0)
1571 std::string strSQL
= PrepareSQL("INSERT INTO musicvideo (idMVideo, idFile) VALUES (NULL, %i)",
1573 m_pDS
->exec(strSQL
);
1574 details
.m_iDbId
= static_cast<int>(m_pDS
->lastinsertid());
1576 return details
.m_iDbId
;
1580 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, filePath
);
1585 //********************************************************************************************************************************
1586 int CVideoDatabase::AddToTable(const std::string
& table
, const std::string
& firstField
, const std::string
& secondField
, const std::string
& value
)
1590 if (nullptr == m_pDB
)
1592 if (nullptr == m_pDS
)
1595 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());
1596 m_pDS
->query(strSQL
);
1597 if (m_pDS
->num_rows() == 0)
1600 // doesn't exists, add it
1601 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());
1602 m_pDS
->exec(strSQL
);
1603 int id
= (int)m_pDS
->lastinsertid();
1608 int id
= m_pDS
->fv(firstField
.c_str()).get_asInt();
1615 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, value
);
1621 int CVideoDatabase::UpdateRatings(int mediaId
, const char *mediaType
, const RatingMap
& values
, const std::string
& defaultRating
)
1625 if (nullptr == m_pDB
)
1627 if (nullptr == m_pDS
)
1630 std::string sql
= PrepareSQL("DELETE FROM rating WHERE media_id=%i AND media_type='%s'", mediaId
, mediaType
);
1633 return AddRatings(mediaId
, mediaType
, values
, defaultRating
);
1637 CLog::Log(LOGERROR
, "{} unable to update ratings of ({})", __FUNCTION__
, mediaType
);
1642 int CVideoDatabase::AddRatings(int mediaId
, const char *mediaType
, const RatingMap
& values
, const std::string
& defaultRating
)
1647 if (nullptr == m_pDB
)
1649 if (nullptr == m_pDS
)
1652 for (const auto& i
: values
)
1655 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());
1656 m_pDS
->query(strSQL
);
1657 if (m_pDS
->num_rows() == 0)
1660 // doesn't exists, add it
1661 strSQL
= PrepareSQL("INSERT INTO rating (media_id, media_type, rating_type, rating, votes) "
1662 "VALUES (%i, '%s', '%s', %f, %i)",
1663 mediaId
, mediaType
, i
.first
.c_str(),
1664 static_cast<double>(i
.second
.rating
), i
.second
.votes
);
1665 m_pDS
->exec(strSQL
);
1666 id
= (int)m_pDS
->lastinsertid();
1670 id
= m_pDS
->fv(0).get_asInt();
1672 strSQL
= PrepareSQL("UPDATE rating SET rating = %f, votes = %i WHERE rating_id = %i",
1673 static_cast<double>(i
.second
.rating
), i
.second
.votes
, id
);
1674 m_pDS
->exec(strSQL
);
1676 if (i
.first
== defaultRating
)
1684 CLog::Log(LOGERROR
, "{} ({} - {}) failed", __FUNCTION__
, mediaId
, mediaType
);
1690 int CVideoDatabase::UpdateUniqueIDs(int mediaId
, const char *mediaType
, const CVideoInfoTag
& details
)
1694 if (nullptr == m_pDB
)
1696 if (nullptr == m_pDS
)
1699 std::string sql
= PrepareSQL("DELETE FROM uniqueid WHERE media_id=%i AND media_type='%s'", mediaId
, mediaType
);
1702 return AddUniqueIDs(mediaId
, mediaType
, details
);
1706 CLog::Log(LOGERROR
, "{} unable to update unique ids of ({})", __FUNCTION__
, mediaType
);
1711 int CVideoDatabase::AddUniqueIDs(int mediaId
, const char *mediaType
, const CVideoInfoTag
& details
)
1716 if (nullptr == m_pDB
)
1718 if (nullptr == m_pDS
)
1721 for (const auto& i
: details
.GetUniqueIDs())
1724 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());
1725 m_pDS
->query(strSQL
);
1726 if (m_pDS
->num_rows() == 0)
1729 // doesn't exists, add it
1730 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());
1731 m_pDS
->exec(strSQL
);
1732 id
= (int)m_pDS
->lastinsertid();
1736 id
= m_pDS
->fv(0).get_asInt();
1738 strSQL
= PrepareSQL("UPDATE uniqueid SET value = '%s', type = '%s' WHERE uniqueid_id = %i", i
.second
.c_str(), i
.first
.c_str(), id
);
1739 m_pDS
->exec(strSQL
);
1741 if (i
.first
== details
.GetDefaultUniqueID())
1749 CLog::Log(LOGERROR
, "{} ({} - {}) failed", __FUNCTION__
, mediaId
, mediaType
);
1755 int CVideoDatabase::AddSet(const std::string
& strSet
,
1756 const std::string
& strOverview
/* = "" */,
1757 const bool updateOverview
/* = true */)
1764 if (m_pDB
== nullptr || m_pDS
== nullptr)
1767 std::string strSQL
= PrepareSQL("SELECT idSet FROM sets WHERE strSet LIKE '%s'", strSet
.c_str());
1768 m_pDS
->query(strSQL
);
1769 if (m_pDS
->num_rows() == 0)
1772 strSQL
= PrepareSQL("INSERT INTO sets (idSet, strSet, strOverview) VALUES(NULL, '%s', '%s')", strSet
.c_str(), strOverview
.c_str());
1773 m_pDS
->exec(strSQL
);
1774 int id
= static_cast<int>(m_pDS
->lastinsertid());
1779 int id
= m_pDS
->fv("idSet").get_asInt();
1785 strSQL
= PrepareSQL("UPDATE sets SET strOverview = '%s' WHERE idSet = %i",
1786 strOverview
.c_str(), id
);
1787 m_pDS
->exec(strSQL
);
1795 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, strSet
);
1801 int CVideoDatabase::AddTag(const std::string
& name
)
1806 return AddToTable("tag", "tag_id", "name", name
);
1809 int CVideoDatabase::AddActor(const std::string
& name
, const std::string
& thumbURLs
, const std::string
&thumb
)
1813 if (nullptr == m_pDB
)
1815 if (nullptr == m_pDS
)
1819 // ATTENTION: the trimming of actor names should really not be done here but after the scraping / NFO-parsing
1820 std::string trimmedName
= name
;
1821 StringUtils::Trim(trimmedName
);
1823 std::string strSQL
=PrepareSQL("select actor_id from actor where name like '%s'", trimmedName
.substr(0, 255).c_str());
1824 m_pDS
->query(strSQL
);
1825 if (m_pDS
->num_rows() == 0)
1828 // doesn't exists, add it
1829 strSQL
=PrepareSQL("insert into actor (actor_id, name, art_urls) values(NULL, '%s', '%s')", trimmedName
.substr(0,255).c_str(), thumbURLs
.c_str());
1830 m_pDS
->exec(strSQL
);
1831 idActor
= (int)m_pDS
->lastinsertid();
1835 idActor
= m_pDS
->fv(0).get_asInt();
1837 // update the thumb url's
1838 if (!thumbURLs
.empty())
1840 strSQL
=PrepareSQL("update actor set art_urls = '%s' where actor_id = %i", thumbURLs
.c_str(), idActor
);
1841 m_pDS
->exec(strSQL
);
1846 SetArtForItem(idActor
, "actor", "thumb", thumb
);
1851 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, name
);
1858 void CVideoDatabase::AddLinkToActor(int mediaId
, const char *mediaType
, int actorId
, const std::string
&role
, int order
)
1860 std::string sql
= PrepareSQL("SELECT 1 FROM actor_link WHERE actor_id=%i AND "
1861 "media_id=%i AND media_type='%s' AND role='%s'",
1862 actorId
, mediaId
, mediaType
, role
.c_str());
1864 if (GetSingleValue(sql
).empty())
1865 { // doesn't exists, add it
1866 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
);
1871 void CVideoDatabase::AddToLinkTable(int mediaId
, const std::string
& mediaType
, const std::string
& table
, int valueId
, const char *foreignKey
)
1873 const char *key
= foreignKey
? foreignKey
: table
.c_str();
1874 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());
1876 if (GetSingleValue(sql
).empty())
1877 { // doesn't exists, add it
1878 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());
1883 void CVideoDatabase::RemoveFromLinkTable(int mediaId
, const std::string
& mediaType
, const std::string
& table
, int valueId
, const char *foreignKey
)
1885 const char *key
= foreignKey
? foreignKey
: table
.c_str();
1886 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());
1891 void CVideoDatabase::AddLinksToItem(int mediaId
, const std::string
& mediaType
, const std::string
& field
, const std::vector
<std::string
>& values
)
1893 for (const auto &i
: values
)
1897 int idValue
= AddToTable(field
, field
+ "_id", "name", i
);
1899 AddToLinkTable(mediaId
, mediaType
, field
, idValue
);
1904 void CVideoDatabase::UpdateLinksToItem(int mediaId
, const std::string
& mediaType
, const std::string
& field
, const std::vector
<std::string
>& values
)
1906 std::string sql
= PrepareSQL("DELETE FROM %s_link WHERE media_id=%i AND media_type='%s'", field
.c_str(), mediaId
, mediaType
.c_str());
1909 AddLinksToItem(mediaId
, mediaType
, field
, values
);
1912 void CVideoDatabase::AddActorLinksToItem(int mediaId
, const std::string
& mediaType
, const std::string
& field
, const std::vector
<std::string
>& values
)
1914 for (const auto &i
: values
)
1918 int idValue
= AddActor(i
, "");
1920 AddToLinkTable(mediaId
, mediaType
, field
, idValue
, "actor");
1925 void CVideoDatabase::UpdateActorLinksToItem(int mediaId
, const std::string
& mediaType
, const std::string
& field
, const std::vector
<std::string
>& values
)
1927 std::string sql
= PrepareSQL("DELETE FROM %s_link WHERE media_id=%i AND media_type='%s'", field
.c_str(), mediaId
, mediaType
.c_str());
1930 AddActorLinksToItem(mediaId
, mediaType
, field
, values
);
1934 void CVideoDatabase::AddTagToItem(int media_id
, int tag_id
, const std::string
&type
)
1939 AddToLinkTable(media_id
, type
, "tag", tag_id
);
1942 void CVideoDatabase::RemoveTagFromItem(int media_id
, int tag_id
, const std::string
&type
)
1947 RemoveFromLinkTable(media_id
, type
, "tag", tag_id
);
1950 void CVideoDatabase::RemoveTagsFromItem(int media_id
, const std::string
&type
)
1955 m_pDS2
->exec(PrepareSQL("DELETE FROM tag_link WHERE media_id=%d AND media_type='%s'", media_id
, type
.c_str()));
1959 void CVideoDatabase::AddCast(int mediaId
, const char *mediaType
, const std::vector
< SActorInfo
> &cast
)
1964 int order
= std::max_element(cast
.begin(), cast
.end())->order
;
1965 for (const auto &i
: cast
)
1967 int idActor
= AddActor(i
.strName
, i
.thumbUrl
.GetData(), i
.thumb
);
1968 AddLinkToActor(mediaId
, mediaType
, idActor
, i
.strRole
, i
.order
>= 0 ? i
.order
: ++order
);
1972 //********************************************************************************************************************************
1973 bool CVideoDatabase::LoadVideoInfo(const std::string
& strFilenameAndPath
, CVideoInfoTag
& details
, int getDetails
/* = VideoDbDetailsAll */)
1975 if (GetMovieInfo(strFilenameAndPath
, details
))
1977 if (GetEpisodeInfo(strFilenameAndPath
, details
))
1979 if (GetMusicVideoInfo(strFilenameAndPath
, details
))
1981 if (GetFileInfo(strFilenameAndPath
, details
))
1987 bool CVideoDatabase::HasMovieInfo(const std::string
& strFilenameAndPath
)
1991 if (nullptr == m_pDB
)
1993 if (nullptr == m_pDS
)
1995 int idMovie
= GetMovieId(strFilenameAndPath
);
1996 return (idMovie
> 0); // index of zero is also invalid
2000 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, strFilenameAndPath
);
2005 bool CVideoDatabase::HasTvShowInfo(const std::string
& strPath
)
2009 if (nullptr == m_pDB
)
2011 if (nullptr == m_pDS
)
2013 int idTvShow
= GetTvShowId(strPath
);
2014 return (idTvShow
> 0); // index of zero is also invalid
2018 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, strPath
);
2023 bool CVideoDatabase::HasEpisodeInfo(const std::string
& strFilenameAndPath
)
2027 if (nullptr == m_pDB
)
2029 if (nullptr == m_pDS
)
2031 int idEpisode
= GetEpisodeId(strFilenameAndPath
);
2032 return (idEpisode
> 0); // index of zero is also invalid
2036 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, strFilenameAndPath
);
2041 bool CVideoDatabase::HasMusicVideoInfo(const std::string
& strFilenameAndPath
)
2045 if (nullptr == m_pDB
)
2047 if (nullptr == m_pDS
)
2049 int idMVideo
= GetMusicVideoId(strFilenameAndPath
);
2050 return (idMVideo
> 0); // index of zero is also invalid
2054 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, strFilenameAndPath
);
2059 void CVideoDatabase::DeleteDetailsForTvShow(int idTvShow
)
2063 if (nullptr == m_pDB
)
2065 if (nullptr == m_pDS
)
2069 strSQL
=PrepareSQL("DELETE from genre_link WHERE media_id=%i AND media_type='tvshow'", idTvShow
);
2070 m_pDS
->exec(strSQL
);
2072 strSQL
=PrepareSQL("DELETE FROM actor_link WHERE media_id=%i AND media_type='tvshow'", idTvShow
);
2073 m_pDS
->exec(strSQL
);
2075 strSQL
=PrepareSQL("DELETE FROM director_link WHERE media_id=%i AND media_type='tvshow'", idTvShow
);
2076 m_pDS
->exec(strSQL
);
2078 strSQL
=PrepareSQL("DELETE FROM studio_link WHERE media_id=%i AND media_type='tvshow'", idTvShow
);
2079 m_pDS
->exec(strSQL
);
2081 strSQL
= PrepareSQL("DELETE FROM rating WHERE media_id=%i AND media_type='tvshow'", idTvShow
);
2082 m_pDS
->exec(strSQL
);
2084 strSQL
= PrepareSQL("DELETE FROM uniqueid WHERE media_id=%i AND media_type='tvshow'", idTvShow
);
2085 m_pDS
->exec(strSQL
);
2087 // remove all info other than the id
2088 // we do this due to the way we have the link between the file + movie tables.
2090 std::vector
<std::string
> ids
;
2091 for (int iType
= VIDEODB_ID_TV_MIN
+ 1; iType
< VIDEODB_ID_TV_MAX
; iType
++)
2092 ids
.emplace_back(StringUtils::Format("c{:02}=NULL", iType
));
2094 strSQL
= "update tvshow set ";
2095 strSQL
+= StringUtils::Join(ids
, ", ");
2096 strSQL
+= PrepareSQL(" where idShow=%i", idTvShow
);
2097 m_pDS
->exec(strSQL
);
2101 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, idTvShow
);
2105 //********************************************************************************************************************************
2106 void CVideoDatabase::GetMoviesByActor(const std::string
& name
, CFileItemList
& items
)
2109 filter
.join
= "LEFT JOIN actor_link ON actor_link.media_id=movie_view.idMovie AND actor_link.media_type='movie' "
2110 "LEFT JOIN actor a ON a.actor_id=actor_link.actor_id "
2111 "LEFT JOIN director_link ON director_link.media_id=movie_view.idMovie AND director_link.media_type='movie' "
2112 "LEFT JOIN actor d ON d.actor_id=director_link.actor_id";
2113 filter
.where
= PrepareSQL("a.name='%s' OR d.name='%s'", name
.c_str(), name
.c_str());
2114 filter
.group
= "movie_view.idMovie";
2115 GetMoviesByWhere("videodb://movies/titles/", filter
, items
);
2118 void CVideoDatabase::GetTvShowsByActor(const std::string
& name
, CFileItemList
& items
)
2121 filter
.join
= "LEFT JOIN actor_link ON actor_link.media_id=tvshow_view.idShow AND actor_link.media_type='tvshow' "
2122 "LEFT JOIN actor a ON a.actor_id=actor_link.actor_id "
2123 "LEFT JOIN director_link ON director_link.media_id=tvshow_view.idShow AND director_link.media_type='tvshow' "
2124 "LEFT JOIN actor d ON d.actor_id=director_link.actor_id";
2125 filter
.where
= PrepareSQL("a.name='%s' OR d.name='%s'", name
.c_str(), name
.c_str());
2126 GetTvShowsByWhere("videodb://tvshows/titles/", filter
, items
);
2129 void CVideoDatabase::GetEpisodesByActor(const std::string
& name
, CFileItemList
& items
)
2132 filter
.join
= "LEFT JOIN actor_link ON actor_link.media_id=episode_view.idEpisode AND actor_link.media_type='episode' "
2133 "LEFT JOIN actor a ON a.actor_id=actor_link.actor_id "
2134 "LEFT JOIN director_link ON director_link.media_id=episode_view.idEpisode AND director_link.media_type='episode' "
2135 "LEFT JOIN actor d ON d.actor_id=director_link.actor_id";
2136 filter
.where
= PrepareSQL("a.name='%s' OR d.name='%s'", name
.c_str(), name
.c_str());
2137 filter
.group
= "episode_view.idEpisode";
2138 GetEpisodesByWhere("videodb://tvshows/titles/", filter
, items
);
2141 void CVideoDatabase::GetMusicVideosByArtist(const std::string
& strArtist
, CFileItemList
& items
)
2146 if (nullptr == m_pDB
)
2148 if (nullptr == m_pDS
)
2152 if (strArtist
.empty()) //! @todo SMARTPLAYLISTS what is this here for???
2153 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");
2154 else // same artist OR same director
2155 strSQL
= PrepareSQL(
2156 "select * from musicvideo_view join actor_link on "
2157 "actor_link.media_id=musicvideo_view.idMVideo AND actor_link.media_type='musicvideo' "
2158 "join actor on actor.actor_id=actor_link.actor_id where actor.name='%s' OR "
2159 "musicvideo_view.c05='%s' GROUP BY idMVideo",
2160 strArtist
.c_str(), strArtist
.c_str());
2161 m_pDS
->query( strSQL
);
2163 while (!m_pDS
->eof())
2165 CVideoInfoTag tag
= GetDetailsForMusicVideo(m_pDS
);
2166 CFileItemPtr
pItem(new CFileItem(tag
));
2167 pItem
->SetLabel(StringUtils::Join(tag
.m_artist
, CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_videoItemSeparator
));
2175 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, strArtist
);
2179 //********************************************************************************************************************************
2180 bool CVideoDatabase::GetMovieInfo(const std::string
& strFilenameAndPath
,
2181 CVideoInfoTag
& details
,
2182 int idMovie
/* = -1 */,
2183 int idVersion
/* = -1 */,
2184 int getDetails
/* = VideoDbDetailsAll */)
2188 if (m_pDB
== nullptr || m_pDS
== nullptr)
2192 idMovie
= GetMovieId(strFilenameAndPath
);
2200 //! @todo get rid of "videos with versions as folder" hack!
2201 if (idVersion
!= VIDEO_VERSION_ID_ALL
)
2202 sql
= PrepareSQL("SELECT * FROM movie_view WHERE idMovie = %i AND videoVersionTypeId = %i",
2203 idMovie
, idVersion
);
2205 else if (!strFilenameAndPath
.empty())
2207 const int idFile
{GetFileId(strFilenameAndPath
)};
2209 sql
= PrepareSQL("SELECT * FROM movie_view WHERE idMovie = %i AND videoVersionIdFile = %i",
2214 sql
= PrepareSQL("SELECT * FROM movie_view WHERE idMovie = %i AND isDefaultVersion = 1",
2217 if (!m_pDS
->query(sql
))
2220 details
= GetDetailsForMovie(m_pDS
, getDetails
);
2221 return !details
.IsEmpty();
2225 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, strFilenameAndPath
);
2230 std::string
CVideoDatabase::GetMovieTitle(int idMovie
)
2232 if (!m_pDB
|| !m_pDS
)
2235 m_pDS
->query(PrepareSQL("SELECT c%02d from movie where idMovie = %i", VIDEODB_ID_TITLE
, idMovie
));
2238 return m_pDS
->fv(0).get_asString();
2243 //********************************************************************************************************************************
2244 bool CVideoDatabase::GetTvShowInfo(const std::string
& strPath
, CVideoInfoTag
& details
, int idTvShow
/* = -1 */, CFileItem
*item
/* = NULL */, int getDetails
/* = VideoDbDetailsAll */)
2248 if (m_pDB
== nullptr || m_pDS
== nullptr)
2252 idTvShow
= GetTvShowId(strPath
);
2253 if (idTvShow
< 0) return false;
2255 std::string sql
= PrepareSQL("SELECT * FROM tvshow_view WHERE idShow=%i GROUP BY idShow", idTvShow
);
2256 if (!m_pDS
->query(sql
))
2258 details
= GetDetailsForTvShow(m_pDS
, getDetails
, item
);
2259 return !details
.IsEmpty();
2263 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, strPath
);
2268 bool CVideoDatabase::GetSeasonInfo(const std::string
& path
,
2270 CVideoInfoTag
& details
,
2275 const std::string sql
= PrepareSQL("strPath='%s' AND season=%i", path
.c_str(), season
);
2276 const std::string id
= GetSingleValue("season_view", "idSeason", sql
);
2279 CLog::LogF(LOGERROR
, "Failed to obtain seasonId for path={}, season={}", path
, season
);
2283 const int idSeason
= static_cast<int>(std::strtol(id
.c_str(), nullptr, 10));
2284 return GetSeasonInfo(idSeason
, details
, item
);
2289 CLog::LogF(LOGERROR
, "Exception while trying to to obtain seasonId for path={}, season={}",
2295 bool CVideoDatabase::GetSeasonInfo(int idSeason
, CVideoInfoTag
& details
, bool allDetails
/* = true */)
2297 return GetSeasonInfo(idSeason
, details
, allDetails
, nullptr);
2300 bool CVideoDatabase::GetSeasonInfo(int idSeason
, CVideoInfoTag
& details
, CFileItem
* item
)
2302 return GetSeasonInfo(idSeason
, details
, true, item
);
2305 bool CVideoDatabase::GetSeasonInfo(int idSeason
,
2306 CVideoInfoTag
& details
,
2315 if (!m_pDB
|| !m_pDS
)
2318 std::string sql
= PrepareSQL("SELECT idSeason, idShow, season, name, userrating FROM seasons WHERE idSeason=%i", idSeason
);
2319 if (!m_pDS
->query(sql
))
2322 if (m_pDS
->num_rows() != 1)
2327 int idShow
= m_pDS
->fv(1).get_asInt();
2329 // close the current result because we are going to query the season view for all details
2335 CFileItemList seasons
;
2336 if (!GetSeasonsNav(StringUtils::Format("videodb://tvshows/titles/{}/", idShow
), seasons
, -1,
2337 -1, -1, -1, idShow
, false) ||
2338 seasons
.Size() <= 0)
2341 for (int index
= 0; index
< seasons
.Size(); index
++)
2343 const CFileItemPtr season
= seasons
.Get(index
);
2344 if (season
->HasVideoInfoTag() && season
->GetVideoInfoTag()->m_iDbId
== idSeason
&& season
->GetVideoInfoTag()->m_iIdShow
== idShow
)
2346 details
= *season
->GetVideoInfoTag();
2356 const int season
= m_pDS
->fv(2).get_asInt();
2357 std::string name
= m_pDS
->fv(3).get_asString();
2362 name
= g_localizeStrings
.Get(20381);
2364 name
= StringUtils::Format(g_localizeStrings
.Get(20358), season
);
2367 details
.m_strTitle
= name
;
2369 details
.m_strSortTitle
= name
;
2370 details
.m_iSeason
= season
;
2371 details
.m_iDbId
= m_pDS
->fv(0).get_asInt();
2372 details
.m_iIdSeason
= details
.m_iDbId
;
2373 details
.m_type
= MediaTypeSeason
;
2374 details
.m_iUserRating
= m_pDS
->fv(4).get_asInt();
2375 details
.m_iIdShow
= m_pDS
->fv(1).get_asInt();
2381 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, idSeason
);
2386 bool CVideoDatabase::GetEpisodeBasicInfo(const std::string
& strFilenameAndPath
, CVideoInfoTag
& details
, int idEpisode
/* = -1 */)
2391 idEpisode
= GetEpisodeId(strFilenameAndPath
);
2396 std::string sql
= PrepareSQL("select * from episode where idEpisode=%i",idEpisode
);
2397 if (!m_pDS
->query(sql
))
2399 details
= GetBasicDetailsForEpisode(m_pDS
);
2400 return !details
.IsEmpty();
2404 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, strFilenameAndPath
);
2409 bool CVideoDatabase::GetEpisodeInfo(const std::string
& strFilenameAndPath
, CVideoInfoTag
& details
, int idEpisode
/* = -1 */, int getDetails
/* = VideoDbDetailsAll */)
2413 if (m_pDB
== nullptr || m_pDS
== nullptr)
2417 idEpisode
= GetEpisodeId(strFilenameAndPath
, details
.m_iEpisode
, details
.m_iSeason
);
2418 if (idEpisode
< 0) return false;
2420 std::string sql
= PrepareSQL("select * from episode_view where idEpisode=%i",idEpisode
);
2421 if (!m_pDS
->query(sql
))
2423 details
= GetDetailsForEpisode(m_pDS
, getDetails
);
2424 return !details
.IsEmpty();
2428 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, strFilenameAndPath
);
2433 bool CVideoDatabase::GetMusicVideoInfo(const std::string
& strFilenameAndPath
, CVideoInfoTag
& details
, int idMVideo
/* = -1 */, int getDetails
/* = VideoDbDetailsAll */)
2437 if (m_pDB
== nullptr || m_pDS
== nullptr)
2441 idMVideo
= GetMusicVideoId(strFilenameAndPath
);
2442 if (idMVideo
< 0) return false;
2444 std::string sql
= PrepareSQL("select * from musicvideo_view where idMVideo=%i", idMVideo
);
2445 if (!m_pDS
->query(sql
))
2447 details
= GetDetailsForMusicVideo(m_pDS
, getDetails
);
2448 return !details
.IsEmpty();
2452 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, strFilenameAndPath
);
2457 bool CVideoDatabase::GetSetInfo(int idSet
, CVideoInfoTag
& details
, CFileItem
* item
/* = nullptr */)
2465 filter
.where
= PrepareSQL("sets.idSet=%d", idSet
);
2466 CFileItemList items
;
2467 if (!GetSetsByWhere("videodb://movies/sets/", filter
, items
) ||
2468 items
.Size() != 1 ||
2469 !items
[0]->HasVideoInfoTag())
2472 details
= *(items
[0]->GetVideoInfoTag());
2475 return !details
.IsEmpty();
2479 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, idSet
);
2484 bool CVideoDatabase::GetFileInfo(const std::string
& strFilenameAndPath
, CVideoInfoTag
& details
, int idFile
/* = -1 */)
2489 idFile
= GetFileId(strFilenameAndPath
);
2493 std::string sql
= PrepareSQL("SELECT * FROM files "
2494 "JOIN path ON path.idPath = files.idPath "
2495 "LEFT JOIN bookmark ON bookmark.idFile = files.idFile AND bookmark.type = %i "
2496 "WHERE files.idFile = %i", CBookmark::RESUME
, idFile
);
2497 if (!m_pDS
->query(sql
))
2500 details
.m_iFileId
= m_pDS
->fv("files.idFile").get_asInt();
2501 details
.m_strPath
= m_pDS
->fv("path.strPath").get_asString();
2502 std::string strFileName
= m_pDS
->fv("files.strFilename").get_asString();
2503 ConstructPath(details
.m_strFileNameAndPath
, details
.m_strPath
, strFileName
);
2504 details
.SetPlayCount(std::max(details
.GetPlayCount(), m_pDS
->fv("files.playCount").get_asInt()));
2505 if (!details
.m_lastPlayed
.IsValid())
2506 details
.m_lastPlayed
.SetFromDBDateTime(m_pDS
->fv("files.lastPlayed").get_asString());
2507 if (!details
.m_dateAdded
.IsValid())
2508 details
.m_dateAdded
.SetFromDBDateTime(m_pDS
->fv("files.dateAdded").get_asString());
2509 if (!details
.GetResumePoint().IsSet() ||
2510 (!details
.GetResumePoint().HasSavedPlayerState() &&
2511 !m_pDS
->fv("bookmark.playerState").get_asString().empty()))
2513 details
.SetResumePoint(m_pDS
->fv("bookmark.timeInSeconds").get_asDouble(),
2514 m_pDS
->fv("bookmark.totalTimeInSeconds").get_asDouble(),
2515 m_pDS
->fv("bookmark.playerState").get_asString());
2518 // get streamdetails
2519 GetStreamDetails(details
);
2521 return !details
.IsEmpty();
2525 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, strFilenameAndPath
);
2530 std::string
CVideoDatabase::GetValueString(const CVideoInfoTag
&details
, int min
, int max
, const SDbTableOffsets
*offsets
) const
2532 std::vector
<std::string
> conditions
;
2533 for (int i
= min
+ 1; i
< max
; ++i
)
2535 switch (offsets
[i
].type
)
2537 case VIDEODB_TYPE_STRING
:
2538 conditions
.emplace_back(PrepareSQL("c%02d='%s'", i
, ((const std::string
*)(((const char*)&details
)+offsets
[i
].offset
))->c_str()));
2540 case VIDEODB_TYPE_INT
:
2541 conditions
.emplace_back(PrepareSQL("c%02d='%i'", i
, *(const int*)(((const char*)&details
)+offsets
[i
].offset
)));
2543 case VIDEODB_TYPE_COUNT
:
2545 int value
= *(const int*)(((const char*)&details
)+offsets
[i
].offset
);
2547 conditions
.emplace_back(PrepareSQL("c%02d=%i", i
, value
));
2549 conditions
.emplace_back(PrepareSQL("c%02d=NULL", i
));
2552 case VIDEODB_TYPE_BOOL
:
2553 conditions
.emplace_back(PrepareSQL("c%02d='%s'", i
, *(const bool*)(((const char*)&details
)+offsets
[i
].offset
)?"true":"false"));
2555 case VIDEODB_TYPE_FLOAT
:
2556 conditions
.emplace_back(PrepareSQL(
2557 "c%02d='%f'", i
, *(const double*)(((const char*)&details
) + offsets
[i
].offset
)));
2559 case VIDEODB_TYPE_STRINGARRAY
:
2560 conditions
.emplace_back(PrepareSQL("c%02d='%s'", i
, StringUtils::Join(*((const std::vector
<std::string
>*)(((const char*)&details
)+offsets
[i
].offset
)),
2561 CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_videoItemSeparator
).c_str()));
2563 case VIDEODB_TYPE_DATE
:
2564 conditions
.emplace_back(PrepareSQL("c%02d='%s'", i
, ((const CDateTime
*)(((const char*)&details
)+offsets
[i
].offset
))->GetAsDBDate().c_str()));
2566 case VIDEODB_TYPE_DATETIME
:
2567 conditions
.emplace_back(PrepareSQL("c%02d='%s'", i
, ((const CDateTime
*)(((const char*)&details
)+offsets
[i
].offset
))->GetAsDBDateTime().c_str()));
2569 case VIDEODB_TYPE_UNUSED
: // Skip the unused field to avoid populating unused data
2573 return StringUtils::Join(conditions
, ",");
2576 //********************************************************************************************************************************
2577 int CVideoDatabase::SetDetailsForItem(CVideoInfoTag
& details
, const std::map
<std::string
, std::string
> &artwork
)
2579 return SetDetailsForItem(details
.m_iDbId
, details
.m_type
, details
, artwork
);
2582 int CVideoDatabase::SetDetailsForItem(int id
, const MediaType
& mediaType
, CVideoInfoTag
& details
, const std::map
<std::string
, std::string
> &artwork
)
2584 if (mediaType
== MediaTypeNone
)
2587 if (mediaType
== MediaTypeMovie
)
2588 return SetDetailsForMovie(details
, artwork
, id
);
2589 else if (mediaType
== MediaTypeVideoCollection
)
2590 return SetDetailsForMovieSet(details
, artwork
, id
);
2591 else if (mediaType
== MediaTypeTvShow
)
2593 std::map
<int, std::map
<std::string
, std::string
> > seasonArtwork
;
2594 if (!UpdateDetailsForTvShow(id
, details
, artwork
, seasonArtwork
))
2599 else if (mediaType
== MediaTypeSeason
)
2600 return SetDetailsForSeason(details
, artwork
, details
.m_iIdShow
, id
);
2601 else if (mediaType
== MediaTypeEpisode
)
2602 return SetDetailsForEpisode(details
, artwork
, details
.m_iIdShow
, id
);
2603 else if (mediaType
== MediaTypeMusicVideo
)
2604 return SetDetailsForMusicVideo(details
, artwork
, id
);
2609 int CVideoDatabase::SetDetailsForMovie(CVideoInfoTag
& details
,
2610 const std::map
<std::string
, std::string
>& artwork
,
2611 int idMovie
/* = -1 */)
2613 const auto filePath
= details
.GetPath();
2620 idMovie
= GetMovieId(filePath
);
2624 // only add a new movie if we don't already have a valid idMovie
2625 idMovie
= AddNewMovie(details
);
2628 RollbackTransaction();
2633 // update dateadded if it's set
2634 if (details
.m_dateAdded
.IsValid())
2635 UpdateFileDateAdded(details
);
2637 AddCast(idMovie
, "movie", details
.m_cast
);
2638 AddLinksToItem(idMovie
, MediaTypeMovie
, "genre", details
.m_genre
);
2639 AddLinksToItem(idMovie
, MediaTypeMovie
, "studio", details
.m_studio
);
2640 AddLinksToItem(idMovie
, MediaTypeMovie
, "country", details
.m_country
);
2641 AddLinksToItem(idMovie
, MediaTypeMovie
, "tag", details
.m_tags
);
2642 AddActorLinksToItem(idMovie
, MediaTypeMovie
, "director", details
.m_director
);
2643 AddActorLinksToItem(idMovie
, MediaTypeMovie
, "writer", details
.m_writingCredits
);
2646 details
.m_iIdRating
= AddRatings(idMovie
, MediaTypeMovie
, details
.m_ratings
, details
.GetDefaultRating());
2649 details
.m_iIdUniqueID
= AddUniqueIDs(idMovie
, MediaTypeMovie
, details
);
2653 if (!details
.m_set
.title
.empty())
2655 idSet
= AddSet(details
.m_set
.title
, details
.m_set
.overview
, details
.GetUpdateSetOverview());
2656 // add art if not available
2657 if (!HasArtForItem(idSet
, MediaTypeVideoCollection
))
2659 for (const auto &it
: artwork
)
2661 if (StringUtils::StartsWith(it
.first
, "set."))
2662 SetArtForItem(idSet
, MediaTypeVideoCollection
, it
.first
.substr(4), it
.second
);
2667 if (details
.HasStreamDetails())
2668 SetStreamDetailsForFileId(details
.m_streamDetails
, GetAndFillFileId(details
));
2670 SetArtForItem(idMovie
, MediaTypeMovie
, artwork
);
2672 if (!details
.HasUniqueID() && details
.HasYear())
2673 { // query DB for any movies matching online id and year
2674 std::string strSQL
= PrepareSQL("SELECT files.playCount, files.lastPlayed "
2676 " INNER JOIN files "
2677 " ON files.idFile=movie.idFile "
2679 " ON movie.idMovie=uniqueid.media_id AND uniqueid.media_type='movie' AND uniqueid.value='%s'"
2680 "WHERE movie.premiered LIKE '%i%%' AND movie.idMovie!=%i AND files.playCount > 0",
2681 details
.GetUniqueID().c_str(), details
.GetYear(), idMovie
);
2682 m_pDS
->query(strSQL
);
2686 int playCount
= m_pDS
->fv("files.playCount").get_asInt();
2688 CDateTime lastPlayed
;
2689 lastPlayed
.SetFromDBDateTime(m_pDS
->fv("files.lastPlayed").get_asString());
2691 // update with playCount and lastPlayed
2693 PrepareSQL("update files set playCount=%i,lastPlayed='%s' where idFile=%i", playCount
,
2694 lastPlayed
.GetAsDBDateTime().c_str(), GetAndFillFileId(details
));
2695 m_pDS
->exec(strSQL
);
2700 // update our movie table (we know it was added already above)
2701 // and insert the new row
2702 std::string sql
= "UPDATE movie SET " + GetValueString(details
, VIDEODB_ID_MIN
, VIDEODB_ID_MAX
, DbMovieOffsets
);
2704 sql
+= PrepareSQL(", idSet = %i", idSet
);
2706 sql
+= ", idSet = NULL";
2707 if (details
.m_iUserRating
> 0 && details
.m_iUserRating
< 11)
2708 sql
+= PrepareSQL(", userrating = %i", details
.m_iUserRating
);
2710 sql
+= ", userrating = NULL";
2711 if (details
.HasPremiered())
2712 sql
+= PrepareSQL(", premiered = '%s'", details
.GetPremiered().GetAsDBDate().c_str());
2714 sql
+= PrepareSQL(", premiered = '%i'", details
.GetYear());
2715 sql
+= PrepareSQL(" where idMovie=%i", idMovie
);
2717 CommitTransaction();
2723 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, filePath
);
2725 RollbackTransaction();
2729 int CVideoDatabase::UpdateDetailsForMovie(int idMovie
, CVideoInfoTag
& details
, const std::map
<std::string
, std::string
> &artwork
, const std::set
<std::string
> &updatedDetails
)
2736 CLog::Log(LOGINFO
, "{}: Starting updates for movie {}", __FUNCTION__
, idMovie
);
2740 // process the link table updates
2741 if (updatedDetails
.find("genre") != updatedDetails
.end())
2742 UpdateLinksToItem(idMovie
, MediaTypeMovie
, "genre", details
.m_genre
);
2743 if (updatedDetails
.find("studio") != updatedDetails
.end())
2744 UpdateLinksToItem(idMovie
, MediaTypeMovie
, "studio", details
.m_studio
);
2745 if (updatedDetails
.find("country") != updatedDetails
.end())
2746 UpdateLinksToItem(idMovie
, MediaTypeMovie
, "country", details
.m_country
);
2747 if (updatedDetails
.find("tag") != updatedDetails
.end())
2748 UpdateLinksToItem(idMovie
, MediaTypeMovie
, "tag", details
.m_tags
);
2749 if (updatedDetails
.find("director") != updatedDetails
.end())
2750 UpdateActorLinksToItem(idMovie
, MediaTypeMovie
, "director", details
.m_director
);
2751 if (updatedDetails
.find("writer") != updatedDetails
.end())
2752 UpdateActorLinksToItem(idMovie
, MediaTypeMovie
, "writer", details
.m_writingCredits
);
2753 if (updatedDetails
.find("art.altered") != updatedDetails
.end())
2754 SetArtForItem(idMovie
, MediaTypeMovie
, artwork
);
2755 if (updatedDetails
.find("ratings") != updatedDetails
.end())
2756 details
.m_iIdRating
= UpdateRatings(idMovie
, MediaTypeMovie
, details
.m_ratings
, details
.GetDefaultRating());
2757 if (updatedDetails
.find("uniqueid") != updatedDetails
.end())
2758 details
.m_iIdUniqueID
= UpdateUniqueIDs(idMovie
, MediaTypeMovie
, details
);
2759 if (updatedDetails
.find("dateadded") != updatedDetails
.end() && details
.m_dateAdded
.IsValid())
2760 UpdateFileDateAdded(details
);
2762 // track if the set was updated
2764 if (updatedDetails
.find("set") != updatedDetails
.end())
2767 if (!details
.m_set
.title
.empty())
2769 idSet
= AddSet(details
.m_set
.title
, details
.m_set
.overview
);
2773 if (updatedDetails
.find("showlink") != updatedDetails
.end())
2775 // remove existing links
2776 std::vector
<int> tvShowIds
;
2777 GetLinksToTvShow(idMovie
, tvShowIds
);
2778 for (const auto& idTVShow
: tvShowIds
)
2779 LinkMovieToTvshow(idMovie
, idTVShow
, true);
2781 // setup links to shows if the linked shows are in the db
2782 for (const auto& showLink
: details
.m_showLink
)
2784 CFileItemList items
;
2785 GetTvShowsByName(showLink
, items
);
2786 if (!items
.IsEmpty())
2787 LinkMovieToTvshow(idMovie
, items
[0]->GetVideoInfoTag()->m_iDbId
, false);
2789 CLog::Log(LOGWARNING
, "{}: Failed to link movie {} to show {}", __FUNCTION__
,
2790 details
.m_strTitle
, showLink
);
2794 // and update the movie table
2795 std::string sql
= "UPDATE movie SET " + GetValueString(details
, VIDEODB_ID_MIN
, VIDEODB_ID_MAX
, DbMovieOffsets
);
2797 sql
+= PrepareSQL(", idSet = %i", idSet
);
2799 sql
+= ", idSet = NULL";
2800 if (details
.m_iUserRating
> 0 && details
.m_iUserRating
< 11)
2801 sql
+= PrepareSQL(", userrating = %i", details
.m_iUserRating
);
2803 sql
+= ", userrating = NULL";
2804 if (details
.HasPremiered())
2805 sql
+= PrepareSQL(", premiered = '%s'", details
.GetPremiered().GetAsDBDate().c_str());
2807 sql
+= PrepareSQL(", premiered = '%i'", details
.GetYear());
2808 sql
+= PrepareSQL(" where idMovie=%i", idMovie
);
2811 CommitTransaction();
2813 CLog::Log(LOGINFO
, "{}: Finished updates for movie {}", __FUNCTION__
, idMovie
);
2819 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, idMovie
);
2821 RollbackTransaction();
2825 int CVideoDatabase::SetDetailsForMovieSet(const CVideoInfoTag
& details
, const std::map
<std::string
, std::string
> &artwork
, int idSet
/* = -1 */)
2827 if (details
.m_strTitle
.empty())
2835 idSet
= AddSet(details
.m_strTitle
, details
.m_strPlot
);
2838 RollbackTransaction();
2843 SetArtForItem(idSet
, MediaTypeVideoCollection
, artwork
);
2845 // and insert the new row
2846 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
);
2848 CommitTransaction();
2854 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, idSet
);
2856 RollbackTransaction();
2860 int CVideoDatabase::GetMatchingTvShow(const CVideoInfoTag
&details
)
2862 // first try matching on uniqueid, then on title + year
2864 if (!details
.HasUniqueID())
2865 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()));
2867 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()));
2871 int CVideoDatabase::SetDetailsForTvShow(const std::vector
<std::pair
<std::string
, std::string
> > &paths
,
2872 CVideoInfoTag
& details
, const std::map
<std::string
, std::string
> &artwork
,
2873 const std::map
<int, std::map
<std::string
, std::string
> > &seasonArt
, int idTvShow
/*= -1 */)
2877 The steps are as follows.
2878 1. Check if the tvshow is found on any of the given paths. If found, we have the show id.
2879 2. Search for a matching show. If found, we have the show id.
2880 3. If we don't have the id, add a new show.
2881 4. Add the paths to the show.
2882 5. Add details for the show.
2887 for (const auto &i
: paths
)
2889 idTvShow
= GetTvShowId(i
.first
);
2895 idTvShow
= GetMatchingTvShow(details
);
2898 idTvShow
= AddTvShow();
2903 // add any paths to the tvshow
2904 for (const auto &i
: paths
)
2905 AddPathToTvShow(idTvShow
, i
.first
, i
.second
, details
.m_dateAdded
);
2907 UpdateDetailsForTvShow(idTvShow
, details
, artwork
, seasonArt
);
2912 bool CVideoDatabase::UpdateDetailsForTvShow(int idTvShow
, CVideoInfoTag
&details
,
2913 const std::map
<std::string
, std::string
> &artwork
, const std::map
<int, std::map
<std::string
, std::string
>> &seasonArt
)
2917 DeleteDetailsForTvShow(idTvShow
);
2919 AddCast(idTvShow
, "tvshow", details
.m_cast
);
2920 AddLinksToItem(idTvShow
, MediaTypeTvShow
, "genre", details
.m_genre
);
2921 AddLinksToItem(idTvShow
, MediaTypeTvShow
, "studio", details
.m_studio
);
2922 AddLinksToItem(idTvShow
, MediaTypeTvShow
, "tag", details
.m_tags
);
2923 AddActorLinksToItem(idTvShow
, MediaTypeTvShow
, "director", details
.m_director
);
2926 details
.m_iIdRating
= AddRatings(idTvShow
, MediaTypeTvShow
, details
.m_ratings
, details
.GetDefaultRating());
2929 details
.m_iIdUniqueID
= AddUniqueIDs(idTvShow
, MediaTypeTvShow
, details
);
2931 // add "all seasons" - the rest are added in SetDetailsForEpisode
2932 AddSeason(idTvShow
, -1);
2934 // add any named seasons
2935 for (const auto& namedSeason
: details
.m_namedSeasons
)
2937 // make sure the named season exists
2938 int seasonId
= AddSeason(idTvShow
, namedSeason
.first
, namedSeason
.second
);
2940 // get any existing details for the named season
2941 CVideoInfoTag season
;
2942 if (!GetSeasonInfo(seasonId
, season
, false) || season
.m_strSortTitle
== namedSeason
.second
)
2945 season
.SetSortTitle(namedSeason
.second
);
2946 SetDetailsForSeason(season
, std::map
<std::string
, std::string
>(), idTvShow
, seasonId
);
2949 SetArtForItem(idTvShow
, MediaTypeTvShow
, artwork
);
2950 for (const auto &i
: seasonArt
)
2952 int idSeason
= AddSeason(idTvShow
, i
.first
);
2954 SetArtForItem(idSeason
, MediaTypeSeason
, i
.second
);
2957 // and insert the new row
2958 std::string sql
= "UPDATE tvshow SET " + GetValueString(details
, VIDEODB_ID_TV_MIN
, VIDEODB_ID_TV_MAX
, DbTvShowOffsets
);
2959 if (details
.m_iUserRating
> 0 && details
.m_iUserRating
< 11)
2960 sql
+= PrepareSQL(", userrating = %i", details
.m_iUserRating
);
2962 sql
+= ", userrating = NULL";
2963 if (details
.GetDuration() > 0)
2964 sql
+= PrepareSQL(", duration = %i", details
.GetDuration());
2966 sql
+= ", duration = NULL";
2967 sql
+= PrepareSQL(" WHERE idShow=%i", idTvShow
);
2968 if (ExecuteQuery(sql
))
2970 CommitTransaction();
2973 RollbackTransaction();
2977 int CVideoDatabase::SetDetailsForSeason(const CVideoInfoTag
& details
, const std::map
<std::string
,
2978 std::string
> &artwork
, int idShow
, int idSeason
/* = -1 */)
2980 if (idShow
< 0 || details
.m_iSeason
< -1)
2988 idSeason
= AddSeason(idShow
, details
.m_iSeason
);
2991 RollbackTransaction();
2996 SetArtForItem(idSeason
, MediaTypeSeason
, artwork
);
2998 // and insert the new row
2999 std::string sql
= PrepareSQL("UPDATE seasons SET season=%i", details
.m_iSeason
);
3000 if (!details
.m_strSortTitle
.empty())
3001 sql
+= PrepareSQL(", name='%s'", details
.m_strSortTitle
.c_str());
3002 if (details
.m_iUserRating
> 0 && details
.m_iUserRating
< 11)
3003 sql
+= PrepareSQL(", userrating = %i", details
.m_iUserRating
);
3005 sql
+= ", userrating = NULL";
3006 sql
+= PrepareSQL(" WHERE idSeason=%i", idSeason
);
3007 m_pDS
->exec(sql
.c_str());
3008 CommitTransaction();
3014 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, idSeason
);
3016 RollbackTransaction();
3020 bool CVideoDatabase::SetFileForEpisode(const std::string
& fileAndPath
, int idEpisode
, int idFile
)
3024 std::string sql
= PrepareSQL("UPDATE episode SET c18='%s', idFile=%i WHERE idEpisode=%i",
3025 fileAndPath
.c_str(), idFile
, idEpisode
);
3032 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, idEpisode
);
3037 bool CVideoDatabase::SetFileForMovie(const std::string
& fileAndPath
, int idMovie
, int idFile
)
3041 assert(m_pDB
->in_transaction());
3043 std::string sql
= PrepareSQL("UPDATE movie SET c22='%s', idFile=%i WHERE idMovie=%i",
3044 fileAndPath
.c_str(), idFile
, idMovie
);
3046 sql
= PrepareSQL("UPDATE videoversion SET idFile=%i WHERE idMedia=%i AND media_type='movie'",
3054 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, idMovie
);
3059 int CVideoDatabase::SetDetailsForEpisode(CVideoInfoTag
& details
,
3060 const std::map
<std::string
, std::string
>& artwork
,
3062 int idEpisode
/* = -1 */)
3064 const auto filePath
= details
.GetPath();
3070 idEpisode
= GetEpisodeId(filePath
);
3073 DeleteEpisode(idEpisode
, true); // true to keep the table entry
3076 // only add a new episode if we don't already have a valid idEpisode
3077 // (DeleteEpisode is called with bKeepId == true so the episode won't
3078 // be removed from the episode table)
3079 idEpisode
= AddNewEpisode(idShow
, details
);
3082 RollbackTransaction();
3087 // update dateadded if it's set
3088 if (details
.m_dateAdded
.IsValid())
3089 UpdateFileDateAdded(details
);
3091 AddCast(idEpisode
, "episode", details
.m_cast
);
3092 AddActorLinksToItem(idEpisode
, MediaTypeEpisode
, "director", details
.m_director
);
3093 AddActorLinksToItem(idEpisode
, MediaTypeEpisode
, "writer", details
.m_writingCredits
);
3096 details
.m_iIdRating
= AddRatings(idEpisode
, MediaTypeEpisode
, details
.m_ratings
, details
.GetDefaultRating());
3099 details
.m_iIdUniqueID
= AddUniqueIDs(idEpisode
, MediaTypeEpisode
, details
);
3101 if (details
.HasStreamDetails())
3102 SetStreamDetailsForFileId(details
.m_streamDetails
, GetAndFillFileId(details
));
3104 // ensure we have this season already added
3105 int idSeason
= AddSeason(idShow
, details
.m_iSeason
);
3107 SetArtForItem(idEpisode
, MediaTypeEpisode
, artwork
);
3109 if (details
.m_iEpisode
!= -1 && details
.m_iSeason
!= -1)
3110 { // query DB for any episodes matching idShow, Season and Episode
3111 std::string strSQL
= PrepareSQL("SELECT files.playCount, files.lastPlayed "
3112 "FROM episode INNER JOIN files ON files.idFile=episode.idFile "
3113 "WHERE episode.c%02d=%i AND episode.c%02d=%i AND episode.idShow=%i "
3114 "AND episode.idEpisode!=%i AND files.playCount > 0",
3115 VIDEODB_ID_EPISODE_SEASON
, details
.m_iSeason
, VIDEODB_ID_EPISODE_EPISODE
,
3116 details
.m_iEpisode
, idShow
, idEpisode
);
3117 m_pDS
->query(strSQL
);
3121 int playCount
= m_pDS
->fv("files.playCount").get_asInt();
3123 CDateTime lastPlayed
;
3124 lastPlayed
.SetFromDBDateTime(m_pDS
->fv("files.lastPlayed").get_asString());
3126 // update with playCount and lastPlayed
3128 PrepareSQL("update files set playCount=%i,lastPlayed='%s' where idFile=%i", playCount
,
3129 lastPlayed
.GetAsDBDateTime().c_str(), GetAndFillFileId(details
));
3130 m_pDS
->exec(strSQL
);
3135 // and insert the new row
3136 std::string sql
= "UPDATE episode SET " + GetValueString(details
, VIDEODB_ID_EPISODE_MIN
, VIDEODB_ID_EPISODE_MAX
, DbEpisodeOffsets
);
3137 if (details
.m_iUserRating
> 0 && details
.m_iUserRating
< 11)
3138 sql
+= PrepareSQL(", userrating = %i", details
.m_iUserRating
);
3140 sql
+= ", userrating = NULL";
3141 sql
+= PrepareSQL(", idSeason = %i", idSeason
);
3142 sql
+= PrepareSQL(" where idEpisode=%i", idEpisode
);
3144 CommitTransaction();
3150 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, filePath
);
3152 RollbackTransaction();
3156 int CVideoDatabase::GetSeasonId(int showID
, int season
)
3158 std::string sql
= PrepareSQL("idShow=%i AND season=%i", showID
, season
);
3159 std::string id
= GetSingleValue("seasons", "idSeason", sql
);
3162 return strtol(id
.c_str(), NULL
, 10);
3165 int CVideoDatabase::AddSeason(int showID
, int season
, const std::string
& name
/* = "" */)
3167 int seasonId
= GetSeasonId(showID
, season
);
3170 if (ExecuteQuery(PrepareSQL("INSERT INTO seasons (idShow, season, name) VALUES(%i, %i, '%s')", showID
, season
, name
.c_str())))
3171 seasonId
= (int)m_pDS
->lastinsertid();
3176 int CVideoDatabase::SetDetailsForMusicVideo(CVideoInfoTag
& details
,
3177 const std::map
<std::string
, std::string
>& artwork
,
3178 int idMVideo
/* = -1 */)
3180 const auto filePath
= details
.GetPath();
3187 idMVideo
= GetMusicVideoId(filePath
);
3190 DeleteMusicVideo(idMVideo
, true); // Keep id
3193 // only add a new musicvideo if we don't already have a valid idMVideo
3194 // (DeleteMusicVideo is called with bKeepId == true so the musicvideo won't
3195 // be removed from the musicvideo table)
3196 idMVideo
= AddNewMusicVideo(details
);
3199 RollbackTransaction();
3204 // update dateadded if it's set
3205 if (details
.m_dateAdded
.IsValid())
3206 UpdateFileDateAdded(details
);
3208 AddCast(idMVideo
, MediaTypeMusicVideo
, details
.m_cast
);
3209 AddActorLinksToItem(idMVideo
, MediaTypeMusicVideo
, "actor", details
.m_artist
);
3210 AddActorLinksToItem(idMVideo
, MediaTypeMusicVideo
, "director", details
.m_director
);
3211 AddLinksToItem(idMVideo
, MediaTypeMusicVideo
, "genre", details
.m_genre
);
3212 AddLinksToItem(idMVideo
, MediaTypeMusicVideo
, "studio", details
.m_studio
);
3213 AddLinksToItem(idMVideo
, MediaTypeMusicVideo
, "tag", details
.m_tags
);
3216 details
.m_iIdUniqueID
= UpdateUniqueIDs(idMVideo
, MediaTypeMusicVideo
, details
);
3218 if (details
.HasStreamDetails())
3219 SetStreamDetailsForFileId(details
.m_streamDetails
, GetAndFillFileId(details
));
3221 SetArtForItem(idMVideo
, MediaTypeMusicVideo
, artwork
);
3223 // update our movie table (we know it was added already above)
3224 // and insert the new row
3225 std::string sql
= "UPDATE musicvideo SET " + GetValueString(details
, VIDEODB_ID_MUSICVIDEO_MIN
, VIDEODB_ID_MUSICVIDEO_MAX
, DbMusicVideoOffsets
);
3226 if (details
.m_iUserRating
> 0 && details
.m_iUserRating
< 11)
3227 sql
+= PrepareSQL(", userrating = %i", details
.m_iUserRating
);
3229 sql
+= ", userrating = NULL";
3230 if (details
.HasPremiered())
3231 sql
+= PrepareSQL(", premiered = '%s'", details
.GetPremiered().GetAsDBDate().c_str());
3233 sql
+= PrepareSQL(", premiered = '%i'", details
.GetYear());
3234 sql
+= PrepareSQL(" where idMVideo=%i", idMVideo
);
3236 CommitTransaction();
3242 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, filePath
);
3244 RollbackTransaction();
3248 int CVideoDatabase::SetStreamDetailsForFile(const CStreamDetails
& details
,
3249 const std::string
& strFileNameAndPath
)
3251 // AddFile checks to make sure the file isn't already in the DB first
3252 int idFile
= AddFile(strFileNameAndPath
);
3256 //! @todo ugly error return mechanism, fixme
3257 if (SetStreamDetailsForFileId(details
, idFile
))
3263 bool CVideoDatabase::SetStreamDetailsForFileId(const CStreamDetails
& details
, int idFile
)
3270 m_pDS
->exec(PrepareSQL("DELETE FROM streamdetails WHERE idFile = %i", idFile
));
3272 for (int i
=1; i
<=details
.GetVideoStreamCount(); i
++)
3274 m_pDS
->exec(PrepareSQL(
3275 "INSERT INTO streamdetails "
3276 "(idFile, iStreamType, strVideoCodec, fVideoAspect, iVideoWidth, iVideoHeight, "
3277 "iVideoDuration, strStereoMode, strVideoLanguage, strHdrType) "
3278 "VALUES (%i,%i,'%s',%f,%i,%i,%i,'%s','%s','%s')",
3279 idFile
, static_cast<int>(CStreamDetail::VIDEO
), details
.GetVideoCodec(i
).c_str(),
3280 static_cast<double>(details
.GetVideoAspect(i
)), details
.GetVideoWidth(i
),
3281 details
.GetVideoHeight(i
), details
.GetVideoDuration(i
), details
.GetStereoMode(i
).c_str(),
3282 details
.GetVideoLanguage(i
).c_str(), details
.GetVideoHdrType(i
).c_str()));
3284 for (int i
=1; i
<=details
.GetAudioStreamCount(); i
++)
3286 m_pDS
->exec(PrepareSQL(
3287 "INSERT INTO streamdetails "
3288 "(idFile, iStreamType, strAudioCodec, iAudioChannels, strAudioLanguage) "
3289 "VALUES (%i,%i,'%s',%i,'%s')",
3290 idFile
, static_cast<int>(CStreamDetail::AUDIO
), details
.GetAudioCodec(i
).c_str(),
3291 details
.GetAudioChannels(i
), details
.GetAudioLanguage(i
).c_str()));
3293 for (int i
=1; i
<=details
.GetSubtitleStreamCount(); i
++)
3295 m_pDS
->exec(PrepareSQL("INSERT INTO streamdetails "
3296 "(idFile, iStreamType, strSubtitleLanguage) "
3297 "VALUES (%i,%i,'%s')",
3298 idFile
, static_cast<int>(CStreamDetail::SUBTITLE
),
3299 details
.GetSubtitleLanguage(i
).c_str()));
3302 // update the runtime information, if empty
3303 if (details
.GetVideoDuration())
3305 std::vector
<std::pair
<std::string
, int> > tables
;
3306 tables
.emplace_back("movie", VIDEODB_ID_RUNTIME
);
3307 tables
.emplace_back("episode", VIDEODB_ID_EPISODE_RUNTIME
);
3308 tables
.emplace_back("musicvideo", VIDEODB_ID_MUSICVIDEO_RUNTIME
);
3309 for (const auto &i
: tables
)
3311 std::string sql
= PrepareSQL("update %s set c%02d=%d where idFile=%d and c%02d=''",
3312 i
.first
.c_str(), i
.second
, details
.GetVideoDuration(), idFile
, i
.second
);
3320 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, idFile
);
3325 //********************************************************************************************************************************
3326 void CVideoDatabase::GetFilePathById(int idMovie
, std::string
& filePath
, VideoDbContentType iType
)
3330 if (nullptr == m_pDB
)
3332 if (nullptr == m_pDS
)
3335 if (idMovie
< 0) return ;
3338 if (iType
== VideoDbContentType::MOVIES
)
3339 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
);
3340 if (iType
== VideoDbContentType::EPISODES
)
3341 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
);
3342 if (iType
== VideoDbContentType::TVSHOWS
)
3343 strSQL
=PrepareSQL("SELECT path.strPath FROM path INNER JOIN tvshowlinkpath ON path.idPath=tvshowlinkpath.idPath WHERE tvshowlinkpath.idShow=%i", idMovie
);
3344 if (iType
== VideoDbContentType::MUSICVIDEOS
)
3345 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
);
3347 m_pDS
->query( strSQL
);
3350 if (iType
!= VideoDbContentType::TVSHOWS
)
3352 std::string fileName
= m_pDS
->fv("files.strFilename").get_asString();
3353 ConstructPath(filePath
,m_pDS
->fv("path.strPath").get_asString(),fileName
);
3356 filePath
= m_pDS
->fv("path.strPath").get_asString();
3362 CLog::Log(LOGERROR
, "{} failed", __FUNCTION__
);
3366 //********************************************************************************************************************************
3367 void CVideoDatabase::GetBookMarksForFile(const std::string
& strFilenameAndPath
, VECBOOKMARKS
& bookmarks
, CBookmark::EType type
/*= CBookmark::STANDARD*/, bool bAppend
, long partNumber
)
3371 if (URIUtils::IsDiscImageStack(strFilenameAndPath
))
3373 CStackDirectory dir
;
3374 CFileItemList fileList
;
3375 const CURL
pathToUrl(strFilenameAndPath
);
3376 dir
.GetDirectory(pathToUrl
, fileList
);
3379 for (int i
= fileList
.Size() - 1; i
>= 0; i
--) // put the bookmarks of the highest part first in the list
3380 GetBookMarksForFile(fileList
[i
]->GetPath(), bookmarks
, type
, true, (i
+1));
3384 int idFile
= GetFileId(strFilenameAndPath
);
3385 if (idFile
< 0) return ;
3387 bookmarks
.erase(bookmarks
.begin(), bookmarks
.end());
3388 if (nullptr == m_pDB
)
3390 if (nullptr == m_pDS
)
3393 std::string strSQL
=PrepareSQL("select * from bookmark where idFile=%i and type=%i order by timeInSeconds", idFile
, (int)type
);
3394 m_pDS
->query( strSQL
);
3395 while (!m_pDS
->eof())
3398 bookmark
.timeInSeconds
= m_pDS
->fv("timeInSeconds").get_asDouble();
3399 bookmark
.partNumber
= partNumber
;
3400 bookmark
.totalTimeInSeconds
= m_pDS
->fv("totalTimeInSeconds").get_asDouble();
3401 bookmark
.thumbNailImage
= m_pDS
->fv("thumbNailImage").get_asString();
3402 bookmark
.playerState
= m_pDS
->fv("playerState").get_asString();
3403 bookmark
.player
= m_pDS
->fv("player").get_asString();
3404 bookmark
.type
= type
;
3405 if (type
== CBookmark::EPISODE
)
3407 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
);
3408 m_pDS2
->query(strSQL2
);
3409 bookmark
.episodeNumber
= m_pDS2
->fv(0).get_asInt();
3410 bookmark
.seasonNumber
= m_pDS2
->fv(1).get_asInt();
3413 bookmarks
.push_back(bookmark
);
3416 //sort(bookmarks.begin(), bookmarks.end(), SortBookmarks);
3422 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, strFilenameAndPath
);
3426 bool CVideoDatabase::GetResumeBookMark(const std::string
& strFilenameAndPath
, CBookmark
&bookmark
)
3428 VECBOOKMARKS bookmarks
;
3429 GetBookMarksForFile(strFilenameAndPath
, bookmarks
, CBookmark::RESUME
);
3430 if (!bookmarks
.empty())
3432 bookmark
= bookmarks
[0];
3438 void CVideoDatabase::DeleteResumeBookMark(const CFileItem
& item
)
3440 if (!m_pDB
|| !m_pDS
)
3443 int fileID
= item
.GetVideoInfoTag()->m_iFileId
;
3446 fileID
= GetFileId(item
.GetPath());
3453 std::string sql
= PrepareSQL("delete from bookmark where idFile=%i and type=%i", fileID
, CBookmark::RESUME
);
3456 VideoDbContentType iType
= static_cast<VideoDbContentType
>(item
.GetVideoContentType());
3457 std::string content
;
3460 case VideoDbContentType::MOVIES
:
3461 content
= MediaTypeMovie
;
3463 case VideoDbContentType::EPISODES
:
3464 content
= MediaTypeEpisode
;
3466 case VideoDbContentType::TVSHOWS
:
3467 content
= MediaTypeTvShow
;
3469 case VideoDbContentType::MUSICVIDEOS
:
3470 content
= MediaTypeMusicVideo
;
3476 if (!content
.empty())
3478 AnnounceUpdate(content
, item
.GetVideoInfoTag()->m_iDbId
);
3484 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
,
3485 item
.GetVideoInfoTag()->m_strFileNameAndPath
);
3489 void CVideoDatabase::GetEpisodesByFile(const std::string
& strFilenameAndPath
, std::vector
<CVideoInfoTag
>& episodes
)
3493 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
);
3494 m_pDS
->query(strSQL
);
3495 while (!m_pDS
->eof())
3497 episodes
.emplace_back(GetDetailsForEpisode(m_pDS
));
3504 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, strFilenameAndPath
);
3508 //********************************************************************************************************************************
3509 void CVideoDatabase::AddBookMarkToFile(const std::string
& strFilenameAndPath
, const CBookmark
&bookmark
, CBookmark::EType type
/*= CBookmark::STANDARD*/)
3513 int idFile
= AddFile(strFilenameAndPath
);
3516 if (nullptr == m_pDB
)
3518 if (nullptr == m_pDS
)
3523 if (type
== CBookmark::RESUME
) // get the same resume mark bookmark each time type
3525 strSQL
=PrepareSQL("select idBookmark from bookmark where idFile=%i and type=1", idFile
);
3527 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
3529 /* get a bookmark within the same time as previous */
3530 double mintime
= bookmark
.timeInSeconds
- 0.5;
3531 double maxtime
= bookmark
.timeInSeconds
+ 0.5;
3532 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());
3535 if (type
!= CBookmark::EPISODE
)
3538 m_pDS
->query( strSQL
);
3539 if (m_pDS
->num_rows() != 0)
3540 idBookmark
= m_pDS
->get_field_value("idBookmark").get_asInt();
3543 // update or insert depending if it existed before
3544 if (idBookmark
>= 0 )
3545 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
);
3547 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
);
3549 m_pDS
->exec(strSQL
);
3553 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, strFilenameAndPath
);
3557 void CVideoDatabase::ClearBookMarkOfFile(const std::string
& strFilenameAndPath
, CBookmark
& bookmark
, CBookmark::EType type
/*= CBookmark::STANDARD*/)
3561 int idFile
= GetFileId(strFilenameAndPath
);
3562 if (idFile
< 0) return ;
3563 if (nullptr == m_pDB
)
3565 if (nullptr == m_pDS
)
3568 /* a little bit ugly, we clear first bookmark that is within one second of given */
3569 /* should be no problem since we never add bookmarks that are closer than that */
3570 double mintime
= bookmark
.timeInSeconds
- 0.5;
3571 double maxtime
= bookmark
.timeInSeconds
+ 0.5;
3572 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
);
3574 m_pDS
->query( strSQL
);
3575 if (m_pDS
->num_rows() != 0)
3577 int idBookmark
= m_pDS
->get_field_value("idBookmark").get_asInt();
3578 strSQL
=PrepareSQL("delete from bookmark where idBookmark=%i",idBookmark
);
3579 m_pDS
->exec(strSQL
);
3580 if (type
== CBookmark::EPISODE
)
3582 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
);
3583 m_pDS
->exec(strSQL
);
3591 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, strFilenameAndPath
);
3595 //********************************************************************************************************************************
3596 void CVideoDatabase::ClearBookMarksOfFile(const std::string
& strFilenameAndPath
, CBookmark::EType type
/*= CBookmark::STANDARD*/)
3598 int idFile
= GetFileId(strFilenameAndPath
);
3600 return ClearBookMarksOfFile(idFile
, type
);
3603 void CVideoDatabase::ClearBookMarksOfFile(int idFile
, CBookmark::EType type
/*= CBookmark::STANDARD*/)
3610 if (nullptr == m_pDB
)
3612 if (nullptr == m_pDS
)
3615 std::string strSQL
=PrepareSQL("delete from bookmark where idFile=%i and type=%i", idFile
, (int)type
);
3616 m_pDS
->exec(strSQL
);
3617 if (type
== CBookmark::EPISODE
)
3619 strSQL
=PrepareSQL("update episode set c%02d=-1 where idFile=%i", VIDEODB_ID_EPISODE_BOOKMARK
, idFile
);
3620 m_pDS
->exec(strSQL
);
3625 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, idFile
);
3630 bool CVideoDatabase::GetBookMarkForEpisode(const CVideoInfoTag
& tag
, CBookmark
& bookmark
)
3634 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
);
3635 m_pDS2
->query( strSQL
);
3638 bookmark
.timeInSeconds
= m_pDS2
->fv("timeInSeconds").get_asDouble();
3639 bookmark
.totalTimeInSeconds
= m_pDS2
->fv("totalTimeInSeconds").get_asDouble();
3640 bookmark
.thumbNailImage
= m_pDS2
->fv("thumbNailImage").get_asString();
3641 bookmark
.playerState
= m_pDS2
->fv("playerState").get_asString();
3642 bookmark
.player
= m_pDS2
->fv("player").get_asString();
3643 bookmark
.type
= (CBookmark::EType
)m_pDS2
->fv("type").get_asInt();
3654 CLog::Log(LOGERROR
, "{} failed", __FUNCTION__
);
3660 void CVideoDatabase::AddBookMarkForEpisode(const CVideoInfoTag
& tag
, const CBookmark
& bookmark
)
3664 int idFile
= GetFileId(tag
.m_strFileNameAndPath
);
3665 // delete the current episode for the selected episode number
3666 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
);
3667 m_pDS
->exec(strSQL
);
3669 AddBookMarkToFile(tag
.m_strFileNameAndPath
, bookmark
, CBookmark::EPISODE
);
3670 int idBookmark
= (int)m_pDS
->lastinsertid();
3671 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
);
3672 m_pDS
->exec(strSQL
);
3676 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, tag
.m_iDbId
);
3680 void CVideoDatabase::DeleteBookMarkForEpisode(const CVideoInfoTag
& tag
)
3684 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
);
3685 m_pDS
->exec(strSQL
);
3686 strSQL
= PrepareSQL("update episode set c%02d=-1 where idEpisode=%i", VIDEODB_ID_EPISODE_BOOKMARK
, tag
.m_iDbId
);
3687 m_pDS
->exec(strSQL
);
3691 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, tag
.m_iDbId
);
3695 //********************************************************************************************************************************
3696 void CVideoDatabase::DeleteMovie(int idMovie
,
3697 DeleteMovieCascadeAction ca
/* = ALL_ASSETS */,
3698 DeleteMovieHashAction hashAction
/* = HASH_DELETE */)
3705 if (nullptr == m_pDB
)
3707 if (nullptr == m_pDS
)
3712 const int idFile
{GetDbId(PrepareSQL("SELECT idFile FROM movie WHERE idMovie=%i", idMovie
))};
3713 DeleteStreamDetails(idFile
);
3715 if (hashAction
== DeleteMovieHashAction::HASH_DELETE
)
3717 const std::string path
= GetSingleValue(PrepareSQL(
3718 "SELECT strPath FROM path JOIN files ON files.idPath=path.idPath WHERE files.idFile=%i",
3721 InvalidatePathHash(path
);
3724 const std::string strSQL
{PrepareSQL("DELETE FROM movie WHERE idMovie=%i", idMovie
)};
3725 m_pDS
->exec(strSQL
);
3727 if (ca
== DeleteMovieCascadeAction::ALL_ASSETS
)
3729 // The default version of the movie was removed by a delete trigger.
3730 // Clean up the other assets attached to the movie, if any.
3732 // need local dataset due to nested DeleteVideoAsset query
3733 const std::unique_ptr
<Dataset
> pDS
{m_pDB
->CreateDataset()};
3735 pDS
->query(PrepareSQL("SELECT idFile FROM videoversion WHERE idMedia=%i AND media_type='%s'",
3736 idMovie
, MediaTypeMovie
));
3740 if (!DeleteVideoAsset(pDS
->fv(0).get_asInt()))
3742 RollbackTransaction();
3751 //! @todo move this below CommitTransaction() once UPnP doesn't rely on this anymore
3752 AnnounceRemove(MediaTypeMovie
, idMovie
);
3754 CommitTransaction();
3758 CLog::LogF(LOGERROR
, "failed");
3759 RollbackTransaction();
3763 void CVideoDatabase::DeleteTvShow(const std::string
& strPath
)
3765 int idTvShow
= GetTvShowId(strPath
);
3767 DeleteTvShow(idTvShow
);
3770 void CVideoDatabase::DeleteTvShow(int idTvShow
, bool bKeepId
/* = false */)
3777 if (nullptr == m_pDB
)
3779 if (nullptr == m_pDS
)
3784 std::set
<int> paths
;
3785 GetPathsForTvShow(idTvShow
, paths
);
3787 std::string strSQL
=PrepareSQL("SELECT episode.idEpisode FROM episode WHERE episode.idShow=%i",idTvShow
);
3788 m_pDS2
->query(strSQL
);
3789 while (!m_pDS2
->eof())
3791 DeleteEpisode(m_pDS2
->fv(0).get_asInt(), bKeepId
);
3795 DeleteDetailsForTvShow(idTvShow
);
3797 strSQL
=PrepareSQL("delete from seasons where idShow=%i", idTvShow
);
3798 m_pDS
->exec(strSQL
);
3800 // keep tvshow table and movielink table so we can update data in place
3803 strSQL
=PrepareSQL("delete from tvshow where idShow=%i", idTvShow
);
3804 m_pDS
->exec(strSQL
);
3806 for (const auto &i
: paths
)
3808 std::string path
= GetSingleValue(PrepareSQL("SELECT strPath FROM path WHERE idPath=%i", i
));
3810 InvalidatePathHash(path
);
3814 //! @todo move this below CommitTransaction() once UPnP doesn't rely on this anymore
3816 AnnounceRemove(MediaTypeTvShow
, idTvShow
);
3818 CommitTransaction();
3823 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, idTvShow
);
3824 RollbackTransaction();
3828 void CVideoDatabase::DeleteSeason(int idSeason
, bool bKeepId
/* = false */)
3835 if (m_pDB
== nullptr || m_pDS
== nullptr || m_pDS2
== nullptr)
3840 std::string strSQL
= PrepareSQL("SELECT episode.idEpisode FROM episode "
3841 "JOIN seasons ON seasons.idSeason = %d AND episode.idShow = seasons.idShow AND episode.c%02d = seasons.season ",
3842 idSeason
, VIDEODB_ID_EPISODE_SEASON
);
3843 m_pDS2
->query(strSQL
);
3844 while (!m_pDS2
->eof())
3846 DeleteEpisode(m_pDS2
->fv(0).get_asInt(), bKeepId
);
3850 ExecuteQuery(PrepareSQL("DELETE FROM seasons WHERE idSeason = %i", idSeason
));
3852 CommitTransaction();
3856 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, idSeason
);
3857 RollbackTransaction();
3861 void CVideoDatabase::DeleteEpisode(int idEpisode
, bool bKeepId
/* = false */)
3868 if (nullptr == m_pDB
)
3870 if (nullptr == m_pDS
)
3873 //! @todo move this below CommitTransaction() once UPnP doesn't rely on this anymore
3875 AnnounceRemove(MediaTypeEpisode
, idEpisode
);
3877 int idFile
= GetDbId(PrepareSQL("SELECT idFile FROM episode WHERE idEpisode=%i", idEpisode
));
3878 DeleteStreamDetails(idFile
);
3880 // keep episode table entry and bookmarks so we can update the data in place
3881 // the ancillary tables are still purged
3884 std::string path
= GetSingleValue(PrepareSQL("SELECT strPath FROM path JOIN files ON files.idPath=path.idPath WHERE files.idFile=%i", idFile
));
3886 InvalidatePathHash(path
);
3888 std::string strSQL
= PrepareSQL("delete from episode where idEpisode=%i", idEpisode
);
3889 m_pDS
->exec(strSQL
);
3895 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, idEpisode
);
3899 void CVideoDatabase::DeleteMusicVideo(int idMVideo
, bool bKeepId
/* = false */)
3906 if (nullptr == m_pDB
)
3908 if (nullptr == m_pDS
)
3913 int idFile
= GetDbId(PrepareSQL("SELECT idFile FROM musicvideo WHERE idMVideo=%i", idMVideo
));
3914 DeleteStreamDetails(idFile
);
3916 // keep the music video table entry and bookmarks so we can update data in place
3917 // the ancillary tables are still purged
3920 std::string path
= GetSingleValue(PrepareSQL("SELECT strPath FROM path JOIN files ON files.idPath=path.idPath WHERE files.idFile=%i", idFile
));
3922 InvalidatePathHash(path
);
3924 std::string strSQL
= PrepareSQL("delete from musicvideo where idMVideo=%i", idMVideo
);
3925 m_pDS
->exec(strSQL
);
3928 //! @todo move this below CommitTransaction() once UPnP doesn't rely on this anymore
3930 AnnounceRemove(MediaTypeMusicVideo
, idMVideo
);
3932 CommitTransaction();
3937 CLog::Log(LOGERROR
, "{} failed", __FUNCTION__
);
3938 RollbackTransaction();
3942 int CVideoDatabase::GetDbId(const std::string
&query
)
3944 std::string result
= GetSingleValue(query
);
3945 if (!result
.empty())
3947 int idDb
= strtol(result
.c_str(), NULL
, 10);
3954 void CVideoDatabase::DeleteStreamDetails(int idFile
)
3956 m_pDS
->exec(PrepareSQL("DELETE FROM streamdetails WHERE idFile = %i", idFile
));
3959 void CVideoDatabase::DeleteSet(int idSet
)
3963 if (nullptr == m_pDB
)
3965 if (nullptr == m_pDS
)
3969 strSQL
=PrepareSQL("delete from sets where idSet = %i", idSet
);
3970 m_pDS
->exec(strSQL
);
3971 strSQL
=PrepareSQL("update movie set idSet = null where idSet = %i", idSet
);
3972 m_pDS
->exec(strSQL
);
3976 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, idSet
);
3980 void CVideoDatabase::ClearMovieSet(int idMovie
)
3982 SetMovieSet(idMovie
, -1);
3985 void CVideoDatabase::SetMovieSet(int idMovie
, int idSet
)
3988 ExecuteQuery(PrepareSQL("update movie set idSet = %i where idMovie = %i", idSet
, idMovie
));
3990 ExecuteQuery(PrepareSQL("update movie set idSet = null where idMovie = %i", idMovie
));
3993 std::string
CVideoDatabase::GetFileBasePathById(int idFile
)
3995 if (!m_pDB
|| !m_pDS
)
4000 m_pDS
->query(PrepareSQL(
4001 "SELECT strPath FROM path JOIN files ON path.idPath = files.idPath WHERE idFile = %i",
4006 return m_pDS
->fv("strPath").get_asString();
4012 CLog::Log(LOGERROR
, "{} failed for file {}", __FUNCTION__
, idFile
);
4018 int CVideoDatabase::GetFileIdByMovie(int idMovie
)
4020 if (!m_pDB
|| !m_pDS
)
4027 m_pDS
->query(PrepareSQL("SELECT idFile FROM movie WHERE idMovie = %i", idMovie
));
4031 idFile
= m_pDS
->fv("idFile").get_asInt();
4038 CLog::Log(LOGERROR
, "{} failed for movie {}", __FUNCTION__
, idMovie
);
4044 void CVideoDatabase::GetSameVideoItems(const CFileItem
& item
, CFileItemList
& items
)
4046 if (!m_pDB
|| !m_pDS
)
4049 std::vector
<int> itemIds
;
4051 int dbId
= item
.GetVideoInfoTag()->m_iDbId
;
4052 MediaType mediaType
= item
.GetVideoInfoTag()->m_type
;
4056 // get items with same unique ids (imdb, tmdb, etc.) as the specified item, these are
4057 // the different versions of the item
4058 // note: old records may have the type 'unknown'
4059 // note 2: for type 'tmdb' the same value may be used for a movie and a tv episode, only
4060 // distinguished by media_type.
4061 // @todo make the (value,type) pairs truly unique
4062 m_pDS
->query(PrepareSQL("SELECT DISTINCT media_id "
4064 "WHERE (media_type, value, type) IN "
4065 " (SELECT media_type, value, type "
4066 " FROM uniqueid WHERE media_id = %i AND media_type = '%s') ",
4067 dbId
, mediaType
.c_str()));
4069 while (!m_pDS
->eof())
4071 itemIds
.push_back(m_pDS
->fv("media_id").get_asInt());
4077 VideoDbContentType itemType
= item
.GetVideoContentType();
4079 // get items with same title (and year if exists) as the specified item, these are
4080 // potentially different versions of the item
4081 if (itemType
== VideoDbContentType::MOVIES
)
4083 if (item
.GetVideoInfoTag()->HasYear())
4085 PrepareSQL("SELECT idMovie FROM movie WHERE c%02d = '%s' AND premiered LIKE '%i%%'",
4086 VIDEODB_ID_TITLE
, item
.GetVideoInfoTag()->GetTitle().c_str(),
4087 item
.GetVideoInfoTag()->GetYear()));
4090 PrepareSQL("SELECT idMovie FROM movie WHERE c%02d = '%s' AND LENGTH(premiered) < 4",
4091 VIDEODB_ID_TITLE
, item
.GetVideoInfoTag()->GetTitle().c_str()));
4093 while (!m_pDS
->eof())
4095 int movieId
= m_pDS
->fv("idMovie").get_asInt();
4097 // add movieId if not already in itemIds
4098 if (std::find(itemIds
.begin(), itemIds
.end(), movieId
) == itemIds
.end())
4099 itemIds
.push_back(movieId
);
4107 // get video item details
4108 for (const auto id
: itemIds
)
4110 auto current
= std::make_shared
<CFileItem
>();
4111 if (GetDetailsByTypeAndId(*current
.get(), itemType
, id
))
4117 CLog::Log(LOGERROR
, "{} failed for {} {}", __FUNCTION__
, mediaType
, dbId
);
4121 void CVideoDatabase::DeleteTag(int idTag
, VideoDbContentType mediaType
)
4125 if (m_pDB
== nullptr || m_pDS
== nullptr)
4129 if (mediaType
== VideoDbContentType::MOVIES
)
4130 type
= MediaTypeMovie
;
4131 else if (mediaType
== VideoDbContentType::TVSHOWS
)
4132 type
= MediaTypeTvShow
;
4133 else if (mediaType
== VideoDbContentType::MUSICVIDEOS
)
4134 type
= MediaTypeMusicVideo
;
4138 std::string strSQL
= PrepareSQL("DELETE FROM tag_link WHERE tag_id = %i AND media_type = '%s'", idTag
, type
.c_str());
4139 m_pDS
->exec(strSQL
);
4143 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, idTag
);
4147 void CVideoDatabase::GetDetailsFromDB(std::unique_ptr
<Dataset
> &pDS
, int min
, int max
, const SDbTableOffsets
*offsets
, CVideoInfoTag
&details
, int idxOffset
)
4149 GetDetailsFromDB(pDS
->get_sql_record(), min
, max
, offsets
, details
, idxOffset
);
4152 void CVideoDatabase::GetDetailsFromDB(const dbiplus::sql_record
* const record
, int min
, int max
, const SDbTableOffsets
*offsets
, CVideoInfoTag
&details
, int idxOffset
)
4154 for (int i
= min
+ 1; i
< max
; i
++)
4156 switch (offsets
[i
].type
)
4158 case VIDEODB_TYPE_STRING
:
4159 *(std::string
*)(((char*)&details
)+offsets
[i
].offset
) = record
->at(i
+idxOffset
).get_asString();
4161 case VIDEODB_TYPE_INT
:
4162 case VIDEODB_TYPE_COUNT
:
4163 *(int*)(((char*)&details
)+offsets
[i
].offset
) = record
->at(i
+idxOffset
).get_asInt();
4165 case VIDEODB_TYPE_BOOL
:
4166 *(bool*)(((char*)&details
)+offsets
[i
].offset
) = record
->at(i
+idxOffset
).get_asBool();
4168 case VIDEODB_TYPE_FLOAT
:
4169 *(float*)(((char*)&details
)+offsets
[i
].offset
) = record
->at(i
+idxOffset
).get_asFloat();
4171 case VIDEODB_TYPE_STRINGARRAY
:
4173 std::string value
= record
->at(i
+idxOffset
).get_asString();
4175 *(std::vector
<std::string
>*)(((char*)&details
)+offsets
[i
].offset
) = StringUtils::Split(value
, CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_videoItemSeparator
);
4178 case VIDEODB_TYPE_DATE
:
4179 ((CDateTime
*)(((char*)&details
)+offsets
[i
].offset
))->SetFromDBDate(record
->at(i
+idxOffset
).get_asString());
4181 case VIDEODB_TYPE_DATETIME
:
4182 ((CDateTime
*)(((char*)&details
)+offsets
[i
].offset
))->SetFromDBDateTime(record
->at(i
+idxOffset
).get_asString());
4184 case VIDEODB_TYPE_UNUSED
: // Skip the unused field to avoid populating unused data
4190 bool CVideoDatabase::GetDetailsByTypeAndId(CFileItem
& item
, VideoDbContentType type
, int id
)
4192 CVideoInfoTag details
;
4197 case VideoDbContentType::MOVIES
:
4198 GetMovieInfo("", details
, id
);
4200 case VideoDbContentType::TVSHOWS
:
4201 GetTvShowInfo("", details
, id
, &item
);
4203 case VideoDbContentType::EPISODES
:
4204 GetEpisodeInfo("", details
, id
);
4206 case VideoDbContentType::MUSICVIDEOS
:
4207 GetMusicVideoInfo("", details
, id
);
4213 item
.SetFromVideoInfoTag(details
);
4217 CVideoInfoTag
CVideoDatabase::GetDetailsByTypeAndId(VideoDbContentType type
, int id
)
4220 if (GetDetailsByTypeAndId(item
, type
, id
))
4221 return CVideoInfoTag(*item
.GetVideoInfoTag());
4226 bool CVideoDatabase::GetStreamDetails(const std::string
& filenameAndPath
, CStreamDetails
& details
)
4229 tag
.m_iFileId
= GetFileId(filenameAndPath
);
4230 if (GetStreamDetails(tag
))
4232 details
= tag
.m_streamDetails
;
4238 bool CVideoDatabase::GetStreamDetails(CFileItem
& item
)
4240 // Note that this function (possibly) creates VideoInfoTags for items that don't have one yet!
4243 if (item
.HasVideoInfoTag())
4244 fileId
= item
.GetVideoInfoTag()->m_iFileId
;
4246 fileId
= GetFileId(item
);
4251 // Have a file id, get stream details if available (creates tag either way)
4252 item
.GetVideoInfoTag()->m_iFileId
= fileId
;
4253 return GetStreamDetails(*item
.GetVideoInfoTag());
4256 bool CVideoDatabase::GetStreamDetails(CVideoInfoTag
& tag
)
4259 const std::string path
= tag
.m_strFileNameAndPath
;
4261 if (URIUtils::GetExtension(path
) == ".mpls")
4262 fileId
= GetFileId(path
);
4264 fileId
= tag
.m_iFileId
;
4269 bool retVal
= false;
4271 CStreamDetails
& details
= tag
.m_streamDetails
;
4274 std::unique_ptr
<Dataset
> pDS(m_pDB
->CreateDataset());
4277 std::string strSQL
= PrepareSQL("SELECT * FROM streamdetails WHERE idFile = %i", fileId
);
4282 CStreamDetail::StreamType e
= (CStreamDetail::StreamType
)pDS
->fv(1).get_asInt();
4285 case CStreamDetail::VIDEO
:
4287 CStreamDetailVideo
*p
= new CStreamDetailVideo();
4288 p
->m_strCodec
= pDS
->fv(2).get_asString();
4289 p
->m_fAspect
= pDS
->fv(3).get_asFloat();
4290 p
->m_iWidth
= pDS
->fv(4).get_asInt();
4291 p
->m_iHeight
= pDS
->fv(5).get_asInt();
4292 p
->m_iDuration
= pDS
->fv(10).get_asInt();
4293 p
->m_strStereoMode
= pDS
->fv(11).get_asString();
4294 p
->m_strLanguage
= pDS
->fv(12).get_asString();
4295 p
->m_strHdrType
= pDS
->fv(13).get_asString();
4296 details
.AddStream(p
);
4300 case CStreamDetail::AUDIO
:
4302 CStreamDetailAudio
*p
= new CStreamDetailAudio();
4303 p
->m_strCodec
= pDS
->fv(6).get_asString();
4304 if (pDS
->fv(7).get_isNull())
4305 p
->m_iChannels
= -1;
4307 p
->m_iChannels
= pDS
->fv(7).get_asInt();
4308 p
->m_strLanguage
= pDS
->fv(8).get_asString();
4309 details
.AddStream(p
);
4313 case CStreamDetail::SUBTITLE
:
4315 CStreamDetailSubtitle
*p
= new CStreamDetailSubtitle();
4316 p
->m_strLanguage
= pDS
->fv(9).get_asString();
4317 details
.AddStream(p
);
4330 CLog::Log(LOGERROR
, "{}({}) failed", __FUNCTION__
, tag
.m_iFileId
);
4332 details
.DetermineBestStreams();
4334 if (details
.GetVideoDuration() > 0)
4335 tag
.SetDuration(details
.GetVideoDuration());
4340 bool CVideoDatabase::GetResumePoint(CVideoInfoTag
& tag
)
4342 if (tag
.m_iFileId
< 0)
4349 if (URIUtils::IsDiscImageStack(tag
.m_strFileNameAndPath
))
4351 CStackDirectory dir
;
4352 CFileItemList fileList
;
4353 const CURL
pathToUrl(tag
.m_strFileNameAndPath
);
4354 dir
.GetDirectory(pathToUrl
, fileList
);
4355 tag
.SetResumePoint(CBookmark());
4356 for (int i
= fileList
.Size() - 1; i
>= 0; i
--)
4359 if (GetResumeBookMark(fileList
[i
]->GetPath(), bookmark
))
4361 bookmark
.partNumber
= (i
+1); /* store part number in here */
4362 tag
.SetResumePoint(bookmark
);
4370 std::string strSQL
=PrepareSQL("select timeInSeconds, totalTimeInSeconds from bookmark where idFile=%i and type=%i order by timeInSeconds", tag
.m_iFileId
, CBookmark::RESUME
);
4371 m_pDS2
->query( strSQL
);
4374 tag
.SetResumePoint(m_pDS2
->fv(0).get_asDouble(), m_pDS2
->fv(1).get_asDouble(), "");
4382 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, tag
.m_strFileNameAndPath
);
4388 CVideoInfoTag
CVideoDatabase::GetDetailsForMovie(std::unique_ptr
<Dataset
> &pDS
, int getDetails
/* = VideoDbDetailsNone */)
4390 return GetDetailsForMovie(pDS
->get_sql_record(), getDetails
);
4393 CVideoInfoTag
CVideoDatabase::GetDetailsForMovie(const dbiplus::sql_record
* const record
, int getDetails
/* = VideoDbDetailsNone */)
4395 CVideoInfoTag details
;
4400 int idMovie
= record
->at(0).get_asInt();
4402 GetDetailsFromDB(record
, VIDEODB_ID_MIN
, VIDEODB_ID_MAX
, DbMovieOffsets
, details
);
4404 details
.m_iDbId
= idMovie
;
4405 details
.m_type
= MediaTypeMovie
;
4406 details
.SetHasVideoVersions(record
->at(VIDEODB_DETAILS_MOVIE_HASVERSIONS
).get_asBool());
4407 details
.SetHasVideoExtras(record
->at(VIDEODB_DETAILS_MOVIE_HASEXTRAS
).get_asBool());
4408 details
.SetIsDefaultVideoVersion(record
->at(VIDEODB_DETAILS_MOVIE_ISDEFAULTVERSION
).get_asBool());
4409 auto& versionInfo
= details
.GetAssetInfo();
4410 versionInfo
.SetId(record
->at(VIDEODB_DETAILS_MOVIE_VERSION_TYPEID
).get_asInt());
4411 versionInfo
.SetTitle(record
->at(VIDEODB_DETAILS_MOVIE_VERSION_TYPENAME
).get_asString());
4412 versionInfo
.SetType(
4413 static_cast<VideoAssetType
>(record
->at(VIDEODB_DETAILS_MOVIE_VERSION_ITEMTYPE
).get_asInt()));
4414 details
.m_set
.id
= record
->at(VIDEODB_DETAILS_MOVIE_SET_ID
).get_asInt();
4415 details
.m_set
.title
= record
->at(VIDEODB_DETAILS_MOVIE_SET_NAME
).get_asString();
4416 details
.m_set
.overview
= record
->at(VIDEODB_DETAILS_MOVIE_SET_OVERVIEW
).get_asString();
4417 details
.m_iFileId
= record
->at(VIDEODB_DETAILS_MOVIE_VERSION_FILEID
).get_asInt();
4418 details
.m_strPath
= record
->at(VIDEODB_DETAILS_MOVIE_PATH
).get_asString();
4419 std::string strFileName
= record
->at(VIDEODB_DETAILS_MOVIE_FILE
).get_asString();
4420 ConstructPath(details
.m_strFileNameAndPath
,details
.m_strPath
,strFileName
);
4421 details
.SetPlayCount(record
->at(VIDEODB_DETAILS_MOVIE_PLAYCOUNT
).get_asInt());
4422 details
.m_lastPlayed
.SetFromDBDateTime(record
->at(VIDEODB_DETAILS_MOVIE_LASTPLAYED
).get_asString());
4423 details
.m_dateAdded
.SetFromDBDateTime(record
->at(VIDEODB_DETAILS_MOVIE_DATEADDED
).get_asString());
4424 details
.SetResumePoint(record
->at(VIDEODB_DETAILS_MOVIE_RESUME_TIME
).get_asInt(),
4425 record
->at(VIDEODB_DETAILS_MOVIE_TOTAL_TIME
).get_asInt(),
4426 record
->at(VIDEODB_DETAILS_MOVIE_PLAYER_STATE
).get_asString());
4427 details
.m_iUserRating
= record
->at(VIDEODB_DETAILS_MOVIE_USER_RATING
).get_asInt();
4428 details
.SetRating(record
->at(VIDEODB_DETAILS_MOVIE_RATING
).get_asFloat(),
4429 record
->at(VIDEODB_DETAILS_MOVIE_VOTES
).get_asInt(),
4430 record
->at(VIDEODB_DETAILS_MOVIE_RATING_TYPE
).get_asString(), true);
4431 details
.SetUniqueID(record
->at(VIDEODB_DETAILS_MOVIE_UNIQUEID_VALUE
).get_asString(), record
->at(VIDEODB_DETAILS_MOVIE_UNIQUEID_TYPE
).get_asString() ,true);
4432 std::string premieredString
= record
->at(VIDEODB_DETAILS_MOVIE_PREMIERED
).get_asString();
4433 if (premieredString
.size() == 4)
4434 details
.SetYear(record
->at(VIDEODB_DETAILS_MOVIE_PREMIERED
).get_asInt());
4436 details
.SetPremieredFromDBDate(premieredString
);
4440 if (getDetails
& VideoDbDetailsCast
)
4441 GetCast(details
.m_iDbId
, MediaTypeMovie
, details
.m_cast
);
4443 if (getDetails
& VideoDbDetailsTag
)
4444 GetTags(details
.m_iDbId
, MediaTypeMovie
, details
.m_tags
);
4446 if (getDetails
& VideoDbDetailsRating
)
4447 GetRatings(details
.m_iDbId
, MediaTypeMovie
, details
.m_ratings
);
4449 if (getDetails
& VideoDbDetailsUniqueID
)
4450 GetUniqueIDs(details
.m_iDbId
, MediaTypeMovie
, details
);
4452 if (getDetails
& VideoDbDetailsShowLink
)
4454 // create tvshowlink string
4455 std::vector
<int> links
;
4456 GetLinksToTvShow(idMovie
, links
);
4457 for (unsigned int i
= 0; i
< links
.size(); ++i
)
4459 std::string strSQL
= PrepareSQL("select c%02d from tvshow where idShow=%i",
4460 VIDEODB_ID_TV_TITLE
, links
[i
]);
4461 m_pDS2
->query(strSQL
);
4463 details
.m_showLink
.emplace_back(m_pDS2
->fv(0).get_asString());
4468 if (getDetails
& VideoDbDetailsStream
)
4469 GetStreamDetails(details
);
4471 details
.m_parsedDetails
= getDetails
;
4476 CVideoInfoTag
CVideoDatabase::GetDetailsForTvShow(std::unique_ptr
<Dataset
> &pDS
, int getDetails
/* = VideoDbDetailsNone */, CFileItem
* item
/* = NULL */)
4478 return GetDetailsForTvShow(pDS
->get_sql_record(), getDetails
, item
);
4481 CVideoInfoTag
CVideoDatabase::GetDetailsForTvShow(const dbiplus::sql_record
* const record
, int getDetails
/* = VideoDbDetailsNone */, CFileItem
* item
/* = NULL */)
4483 CVideoInfoTag details
;
4488 int idTvShow
= record
->at(0).get_asInt();
4490 GetDetailsFromDB(record
, VIDEODB_ID_TV_MIN
, VIDEODB_ID_TV_MAX
, DbTvShowOffsets
, details
, 1);
4491 details
.m_bHasPremiered
= details
.m_premiered
.IsValid();
4492 details
.m_iDbId
= idTvShow
;
4493 details
.m_type
= MediaTypeTvShow
;
4494 details
.m_strPath
= record
->at(VIDEODB_DETAILS_TVSHOW_PATH
).get_asString();
4495 details
.m_basePath
= details
.m_strPath
;
4496 details
.m_parentPathID
= record
->at(VIDEODB_DETAILS_TVSHOW_PARENTPATHID
).get_asInt();
4497 details
.m_dateAdded
.SetFromDBDateTime(record
->at(VIDEODB_DETAILS_TVSHOW_DATEADDED
).get_asString());
4498 details
.m_lastPlayed
.SetFromDBDateTime(record
->at(VIDEODB_DETAILS_TVSHOW_LASTPLAYED
).get_asString());
4499 details
.m_iSeason
= record
->at(VIDEODB_DETAILS_TVSHOW_NUM_SEASONS
).get_asInt();
4500 details
.m_iEpisode
= record
->at(VIDEODB_DETAILS_TVSHOW_NUM_EPISODES
).get_asInt();
4501 details
.SetPlayCount(record
->at(VIDEODB_DETAILS_TVSHOW_NUM_WATCHED
).get_asInt());
4502 details
.m_strShowTitle
= details
.m_strTitle
;
4503 details
.m_iUserRating
= record
->at(VIDEODB_DETAILS_TVSHOW_USER_RATING
).get_asInt();
4504 details
.SetRating(record
->at(VIDEODB_DETAILS_TVSHOW_RATING
).get_asFloat(),
4505 record
->at(VIDEODB_DETAILS_TVSHOW_VOTES
).get_asInt(),
4506 record
->at(VIDEODB_DETAILS_TVSHOW_RATING_TYPE
).get_asString(), true);
4507 details
.SetUniqueID(record
->at(VIDEODB_DETAILS_TVSHOW_UNIQUEID_VALUE
).get_asString(), record
->at(VIDEODB_DETAILS_TVSHOW_UNIQUEID_TYPE
).get_asString(), true);
4508 details
.SetDuration(record
->at(VIDEODB_DETAILS_TVSHOW_DURATION
).get_asInt());
4510 //! @todo videotag member + guiinfo int needed?
4511 //! -- Currently not needed; having it available as item prop seems sufficient for skinning
4512 const int inProgressEpisodes
= record
->at(VIDEODB_DETAILS_TVSHOW_NUM_INPROGRESS
).get_asInt();
4516 if (getDetails
& VideoDbDetailsCast
)
4518 GetCast(details
.m_iDbId
, "tvshow", details
.m_cast
);
4521 if (getDetails
& VideoDbDetailsTag
)
4522 GetTags(details
.m_iDbId
, MediaTypeTvShow
, details
.m_tags
);
4524 if (getDetails
& VideoDbDetailsRating
)
4525 GetRatings(details
.m_iDbId
, MediaTypeTvShow
, details
.m_ratings
);
4527 if (getDetails
& VideoDbDetailsUniqueID
)
4528 GetUniqueIDs(details
.m_iDbId
, MediaTypeTvShow
, details
);
4530 details
.m_parsedDetails
= getDetails
;
4535 item
->m_dateTime
= details
.GetPremiered();
4536 item
->SetProperty("totalseasons", details
.m_iSeason
);
4537 item
->SetProperty("totalepisodes", details
.m_iEpisode
);
4538 item
->SetProperty("numepisodes", details
.m_iEpisode
); // will be changed later to reflect watchmode setting
4539 item
->SetProperty("watchedepisodes", details
.GetPlayCount());
4540 item
->SetProperty("unwatchedepisodes", details
.m_iEpisode
- details
.GetPlayCount());
4541 item
->SetProperty("inprogressepisodes", inProgressEpisodes
);
4542 item
->SetProperty("watchedepisodepercent",
4543 details
.m_iEpisode
> 0 ? (details
.GetPlayCount() * 100 / details
.m_iEpisode
)
4546 details
.SetPlayCount((details
.m_iEpisode
<= details
.GetPlayCount()) ? 1 : 0);
4551 CVideoInfoTag
CVideoDatabase::GetBasicDetailsForEpisode(std::unique_ptr
<Dataset
> &pDS
)
4553 return GetBasicDetailsForEpisode(pDS
->get_sql_record());
4556 CVideoInfoTag
CVideoDatabase::GetBasicDetailsForEpisode(const dbiplus::sql_record
* const record
)
4558 CVideoInfoTag details
;
4560 if (record
== nullptr)
4563 int idEpisode
= record
->at(0).get_asInt();
4565 GetDetailsFromDB(record
, VIDEODB_ID_EPISODE_MIN
, VIDEODB_ID_EPISODE_MAX
, DbEpisodeOffsets
, details
);
4566 details
.m_iDbId
= idEpisode
;
4567 details
.m_type
= MediaTypeEpisode
;
4568 details
.m_iFileId
= record
->at(VIDEODB_DETAILS_FILEID
).get_asInt();
4569 details
.m_iIdShow
= record
->at(VIDEODB_DETAILS_EPISODE_TVSHOW_ID
).get_asInt();
4570 details
.m_iIdSeason
= record
->at(VIDEODB_DETAILS_EPISODE_SEASON_ID
).get_asInt();
4571 details
.m_iUserRating
= record
->at(VIDEODB_DETAILS_EPISODE_USER_RATING
).get_asInt();
4576 CVideoInfoTag
CVideoDatabase::GetDetailsForEpisode(std::unique_ptr
<Dataset
> &pDS
, int getDetails
/* = VideoDbDetailsNone */)
4578 return GetDetailsForEpisode(pDS
->get_sql_record(), getDetails
);
4581 CVideoInfoTag
CVideoDatabase::GetDetailsForEpisode(const dbiplus::sql_record
* const record
, int getDetails
/* = VideoDbDetailsNone */)
4583 CVideoInfoTag details
;
4585 if (record
== nullptr)
4588 details
= GetBasicDetailsForEpisode(record
);
4590 details
.m_strPath
= record
->at(VIDEODB_DETAILS_EPISODE_PATH
).get_asString();
4591 std::string strFileName
= record
->at(VIDEODB_DETAILS_EPISODE_FILE
).get_asString();
4592 ConstructPath(details
.m_strFileNameAndPath
,details
.m_strPath
,strFileName
);
4593 details
.SetPlayCount(record
->at(VIDEODB_DETAILS_EPISODE_PLAYCOUNT
).get_asInt());
4594 details
.m_lastPlayed
.SetFromDBDateTime(record
->at(VIDEODB_DETAILS_EPISODE_LASTPLAYED
).get_asString());
4595 details
.m_dateAdded
.SetFromDBDateTime(record
->at(VIDEODB_DETAILS_EPISODE_DATEADDED
).get_asString());
4596 details
.m_strMPAARating
= record
->at(VIDEODB_DETAILS_EPISODE_TVSHOW_MPAA
).get_asString();
4597 details
.m_strShowTitle
= record
->at(VIDEODB_DETAILS_EPISODE_TVSHOW_NAME
).get_asString();
4598 details
.m_genre
= StringUtils::Split(record
->at(VIDEODB_DETAILS_EPISODE_TVSHOW_GENRE
).get_asString(), CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_videoItemSeparator
);
4599 details
.m_studio
= StringUtils::Split(record
->at(VIDEODB_DETAILS_EPISODE_TVSHOW_STUDIO
).get_asString(), CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_videoItemSeparator
);
4600 details
.SetPremieredFromDBDate(record
->at(VIDEODB_DETAILS_EPISODE_TVSHOW_AIRED
).get_asString());
4602 details
.SetResumePoint(record
->at(VIDEODB_DETAILS_EPISODE_RESUME_TIME
).get_asInt(),
4603 record
->at(VIDEODB_DETAILS_EPISODE_TOTAL_TIME
).get_asInt(),
4604 record
->at(VIDEODB_DETAILS_EPISODE_PLAYER_STATE
).get_asString());
4606 details
.SetRating(record
->at(VIDEODB_DETAILS_EPISODE_RATING
).get_asFloat(),
4607 record
->at(VIDEODB_DETAILS_EPISODE_VOTES
).get_asInt(),
4608 record
->at(VIDEODB_DETAILS_EPISODE_RATING_TYPE
).get_asString(), true);
4609 details
.SetUniqueID(record
->at(VIDEODB_DETAILS_EPISODE_UNIQUEID_VALUE
).get_asString(), record
->at(VIDEODB_DETAILS_EPISODE_UNIQUEID_TYPE
).get_asString(), true);
4613 if (getDetails
& VideoDbDetailsCast
)
4615 GetCast(details
.m_iDbId
, MediaTypeEpisode
, details
.m_cast
);
4616 GetCast(details
.m_iIdShow
, MediaTypeTvShow
, details
.m_cast
);
4619 if (getDetails
& VideoDbDetailsRating
)
4620 GetRatings(details
.m_iDbId
, MediaTypeEpisode
, details
.m_ratings
);
4622 if (getDetails
& VideoDbDetailsUniqueID
)
4623 GetUniqueIDs(details
.m_iDbId
, MediaTypeEpisode
, details
);
4625 if (getDetails
& VideoDbDetailsBookmark
)
4626 GetBookMarkForEpisode(details
, details
.m_EpBookmark
);
4628 if (getDetails
& VideoDbDetailsStream
)
4629 GetStreamDetails(details
);
4631 details
.m_parsedDetails
= getDetails
;
4636 CVideoInfoTag
CVideoDatabase::GetDetailsForMusicVideo(std::unique_ptr
<Dataset
> &pDS
, int getDetails
/* = VideoDbDetailsNone */)
4638 return GetDetailsForMusicVideo(pDS
->get_sql_record(), getDetails
);
4641 CVideoInfoTag
CVideoDatabase::GetDetailsForMusicVideo(const dbiplus::sql_record
* const record
, int getDetails
/* = VideoDbDetailsNone */)
4643 CVideoInfoTag details
;
4646 if (record
== nullptr)
4649 int idMVideo
= record
->at(0).get_asInt();
4651 GetDetailsFromDB(record
, VIDEODB_ID_MUSICVIDEO_MIN
, VIDEODB_ID_MUSICVIDEO_MAX
, DbMusicVideoOffsets
, details
);
4652 details
.m_iDbId
= idMVideo
;
4653 details
.m_type
= MediaTypeMusicVideo
;
4655 details
.m_iFileId
= record
->at(VIDEODB_DETAILS_FILEID
).get_asInt();
4656 details
.m_strPath
= record
->at(VIDEODB_DETAILS_MUSICVIDEO_PATH
).get_asString();
4657 std::string strFileName
= record
->at(VIDEODB_DETAILS_MUSICVIDEO_FILE
).get_asString();
4658 ConstructPath(details
.m_strFileNameAndPath
,details
.m_strPath
,strFileName
);
4659 details
.SetPlayCount(record
->at(VIDEODB_DETAILS_MUSICVIDEO_PLAYCOUNT
).get_asInt());
4660 details
.m_lastPlayed
.SetFromDBDateTime(record
->at(VIDEODB_DETAILS_MUSICVIDEO_LASTPLAYED
).get_asString());
4661 details
.m_dateAdded
.SetFromDBDateTime(record
->at(VIDEODB_DETAILS_MUSICVIDEO_DATEADDED
).get_asString());
4662 details
.SetResumePoint(record
->at(VIDEODB_DETAILS_MUSICVIDEO_RESUME_TIME
).get_asInt(),
4663 record
->at(VIDEODB_DETAILS_MUSICVIDEO_TOTAL_TIME
).get_asInt(),
4664 record
->at(VIDEODB_DETAILS_MUSICVIDEO_PLAYER_STATE
).get_asString());
4665 details
.m_iUserRating
= record
->at(VIDEODB_DETAILS_MUSICVIDEO_USER_RATING
).get_asInt();
4666 details
.SetUniqueID(record
->at(VIDEODB_DETAILS_MUSICVIDEO_UNIQUEID_VALUE
).get_asString(),
4667 record
->at(VIDEODB_DETAILS_MUSICVIDEO_UNIQUEID_TYPE
).get_asString(), true);
4668 std::string premieredString
= record
->at(VIDEODB_DETAILS_MUSICVIDEO_PREMIERED
).get_asString();
4669 if (premieredString
.size() == 4)
4670 details
.SetYear(record
->at(VIDEODB_DETAILS_MUSICVIDEO_PREMIERED
).get_asInt());
4672 details
.SetPremieredFromDBDate(premieredString
);
4676 if (getDetails
& VideoDbDetailsTag
)
4677 GetTags(details
.m_iDbId
, MediaTypeMusicVideo
, details
.m_tags
);
4679 if (getDetails
& VideoDbDetailsUniqueID
)
4680 GetUniqueIDs(details
.m_iDbId
, MediaTypeMusicVideo
, details
);
4682 if (getDetails
& VideoDbDetailsStream
)
4683 GetStreamDetails(details
);
4685 if (getDetails
& VideoDbDetailsAll
)
4687 GetCast(details
.m_iDbId
, "musicvideo", details
.m_cast
);
4690 details
.m_parsedDetails
= getDetails
;
4695 void CVideoDatabase::GetCast(int media_id
, const std::string
&media_type
, std::vector
<SActorInfo
> &cast
)
4704 std::string sql
= PrepareSQL("SELECT actor.name,"
4706 " actor_link.cast_order,"
4711 " actor_link.actor_id=actor.actor_id"
4713 " art.media_id=actor.actor_id AND art.media_type='actor' AND art.type='thumb' "
4714 "WHERE actor_link.media_id=%i AND actor_link.media_type='%s'"
4715 "ORDER BY actor_link.cast_order", media_id
, media_type
.c_str());
4717 while (!m_pDS2
->eof())
4720 info
.strName
= m_pDS2
->fv(0).get_asString();
4721 info
.strRole
= m_pDS2
->fv(1).get_asString();
4723 // ignore identical actors (since cast might already be prefilled)
4724 if (std::none_of(cast
.begin(), cast
.end(), [info
](const SActorInfo
& actor
) {
4725 return actor
.strName
== info
.strName
&& actor
.strRole
== info
.strRole
;
4728 info
.order
= m_pDS2
->fv(2).get_asInt();
4729 info
.thumbUrl
.ParseFromData(m_pDS2
->fv(3).get_asString());
4730 info
.thumb
= m_pDS2
->fv(4).get_asString();
4731 cast
.emplace_back(std::move(info
));
4740 CLog::Log(LOGERROR
, "{}({},{}) failed", __FUNCTION__
, media_id
, media_type
);
4744 void CVideoDatabase::GetTags(int media_id
, const std::string
&media_type
, std::vector
<std::string
> &tags
)
4753 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());
4755 while (!m_pDS2
->eof())
4757 tags
.emplace_back(m_pDS2
->fv(0).get_asString());
4764 CLog::Log(LOGERROR
, "{}({},{}) failed", __FUNCTION__
, media_id
, media_type
);
4768 void CVideoDatabase::GetRatings(int media_id
, const std::string
&media_type
, RatingMap
&ratings
)
4777 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());
4779 while (!m_pDS2
->eof())
4781 ratings
[m_pDS2
->fv(0).get_asString()] = CRating(m_pDS2
->fv(1).get_asFloat(), m_pDS2
->fv(2).get_asInt());
4788 CLog::Log(LOGERROR
, "{}({},{}) failed", __FUNCTION__
, media_id
, media_type
);
4792 void CVideoDatabase::GetUniqueIDs(int media_id
, const std::string
&media_type
, CVideoInfoTag
& details
)
4801 std::string sql
= PrepareSQL("SELECT type, value FROM uniqueid WHERE media_id = %i AND media_type = '%s'", media_id
, media_type
.c_str());
4803 while (!m_pDS2
->eof())
4805 details
.SetUniqueID(m_pDS2
->fv(1).get_asString(), m_pDS2
->fv(0).get_asString());
4812 CLog::Log(LOGERROR
, "{}({},{}) failed", __FUNCTION__
, media_id
, media_type
);
4816 bool CVideoDatabase::GetVideoSettings(const CFileItem
&item
, CVideoSettings
&settings
)
4818 return GetVideoSettings(GetFileId(item
), settings
);
4821 /// \brief GetVideoSettings() obtains any saved video settings for the current file.
4822 /// \retval Returns true if the settings exist, false otherwise.
4823 bool CVideoDatabase::GetVideoSettings(const std::string
&filePath
, CVideoSettings
&settings
)
4825 return GetVideoSettings(GetFileId(filePath
), settings
);
4828 bool CVideoDatabase::GetVideoSettings(int idFile
, CVideoSettings
&settings
)
4832 if (idFile
< 0) return false;
4833 if (nullptr == m_pDB
)
4835 if (nullptr == m_pDS
)
4838 std::string strSQL
=PrepareSQL("select * from settings where settings.idFile = '%i'", idFile
);
4839 m_pDS
->query( strSQL
);
4841 if (m_pDS
->num_rows() > 0)
4842 { // get the video settings info
4843 settings
.m_AudioDelay
= m_pDS
->fv("AudioDelay").get_asFloat();
4844 settings
.m_AudioStream
= m_pDS
->fv("AudioStream").get_asInt();
4845 settings
.m_Brightness
= m_pDS
->fv("Brightness").get_asFloat();
4846 settings
.m_Contrast
= m_pDS
->fv("Contrast").get_asFloat();
4847 settings
.m_CustomPixelRatio
= m_pDS
->fv("PixelRatio").get_asFloat();
4848 settings
.m_CustomNonLinStretch
= m_pDS
->fv("NonLinStretch").get_asBool();
4849 settings
.m_NoiseReduction
= m_pDS
->fv("NoiseReduction").get_asFloat();
4850 settings
.m_PostProcess
= m_pDS
->fv("PostProcess").get_asBool();
4851 settings
.m_Sharpness
= m_pDS
->fv("Sharpness").get_asFloat();
4852 settings
.m_CustomZoomAmount
= m_pDS
->fv("ZoomAmount").get_asFloat();
4853 settings
.m_CustomVerticalShift
= m_pDS
->fv("VerticalShift").get_asFloat();
4854 settings
.m_Gamma
= m_pDS
->fv("Gamma").get_asFloat();
4855 settings
.m_SubtitleDelay
= m_pDS
->fv("SubtitleDelay").get_asFloat();
4856 settings
.m_SubtitleOn
= m_pDS
->fv("SubtitlesOn").get_asBool();
4857 settings
.m_SubtitleStream
= m_pDS
->fv("SubtitleStream").get_asInt();
4858 settings
.m_ViewMode
= m_pDS
->fv("ViewMode").get_asInt();
4859 settings
.m_ResumeTime
= m_pDS
->fv("ResumeTime").get_asInt();
4860 settings
.m_InterlaceMethod
= (EINTERLACEMETHOD
)m_pDS
->fv("Deinterlace").get_asInt();
4861 settings
.m_VolumeAmplification
= m_pDS
->fv("VolumeAmplification").get_asFloat();
4862 settings
.m_ScalingMethod
= (ESCALINGMETHOD
)m_pDS
->fv("ScalingMethod").get_asInt();
4863 settings
.m_StereoMode
= m_pDS
->fv("StereoMode").get_asInt();
4864 settings
.m_StereoInvert
= m_pDS
->fv("StereoInvert").get_asBool();
4865 settings
.m_VideoStream
= m_pDS
->fv("VideoStream").get_asInt();
4866 settings
.m_ToneMapMethod
=
4867 static_cast<ETONEMAPMETHOD
>(m_pDS
->fv("TonemapMethod").get_asInt());
4868 settings
.m_ToneMapParam
= m_pDS
->fv("TonemapParam").get_asFloat();
4869 settings
.m_Orientation
= m_pDS
->fv("Orientation").get_asInt();
4870 settings
.m_CenterMixLevel
= m_pDS
->fv("CenterMixLevel").get_asInt();
4878 CLog::Log(LOGERROR
, "{} failed", __FUNCTION__
);
4883 void CVideoDatabase::SetVideoSettings(const CFileItem
&item
, const CVideoSettings
&settings
)
4885 int idFile
= AddFile(item
);
4886 SetVideoSettings(idFile
, settings
);
4889 /// \brief Sets the settings for a particular video file
4890 void CVideoDatabase::SetVideoSettings(int idFile
, const CVideoSettings
&setting
)
4894 if (nullptr == m_pDB
)
4896 if (nullptr == m_pDS
)
4900 std::string strSQL
= PrepareSQL("select * from settings where idFile=%i", idFile
);
4901 m_pDS
->query( strSQL
);
4902 if (m_pDS
->num_rows() > 0)
4906 strSQL
= PrepareSQL(
4907 "update settings set "
4908 "Deinterlace=%i,ViewMode=%i,ZoomAmount=%f,PixelRatio=%f,VerticalShift=%f,"
4909 "AudioStream=%i,SubtitleStream=%i,SubtitleDelay=%f,SubtitlesOn=%i,Brightness=%f,Contrast="
4911 "VolumeAmplification=%f,AudioDelay=%f,Sharpness=%f,NoiseReduction=%f,NonLinStretch=%i,"
4912 "PostProcess=%i,ScalingMethod=%i,",
4913 setting
.m_InterlaceMethod
, setting
.m_ViewMode
,
4914 static_cast<double>(setting
.m_CustomZoomAmount
),
4915 static_cast<double>(setting
.m_CustomPixelRatio
),
4916 static_cast<double>(setting
.m_CustomVerticalShift
), setting
.m_AudioStream
,
4917 setting
.m_SubtitleStream
, static_cast<double>(setting
.m_SubtitleDelay
),
4918 setting
.m_SubtitleOn
, static_cast<double>(setting
.m_Brightness
),
4919 static_cast<double>(setting
.m_Contrast
), static_cast<double>(setting
.m_Gamma
),
4920 static_cast<double>(setting
.m_VolumeAmplification
),
4921 static_cast<double>(setting
.m_AudioDelay
), static_cast<double>(setting
.m_Sharpness
),
4922 static_cast<double>(setting
.m_NoiseReduction
), setting
.m_CustomNonLinStretch
,
4923 setting
.m_PostProcess
, setting
.m_ScalingMethod
);
4924 std::string strSQL2
;
4926 strSQL2
= PrepareSQL("ResumeTime=%i,StereoMode=%i,StereoInvert=%i,VideoStream=%i,"
4927 "TonemapMethod=%i,TonemapParam=%f where idFile=%i\n",
4928 setting
.m_ResumeTime
, setting
.m_StereoMode
, setting
.m_StereoInvert
,
4929 setting
.m_VideoStream
, setting
.m_ToneMapMethod
,
4930 static_cast<double>(setting
.m_ToneMapParam
), idFile
);
4932 m_pDS
->exec(strSQL
);
4938 strSQL
= "INSERT INTO settings (idFile,Deinterlace,ViewMode,ZoomAmount,PixelRatio, VerticalShift, "
4939 "AudioStream,SubtitleStream,SubtitleDelay,SubtitlesOn,Brightness,"
4940 "Contrast,Gamma,VolumeAmplification,AudioDelay,"
4942 "Sharpness,NoiseReduction,NonLinStretch,PostProcess,ScalingMethod,StereoMode,StereoInvert,VideoStream,TonemapMethod,TonemapParam,Orientation,CenterMixLevel) "
4944 strSQL
+= PrepareSQL(
4945 "(%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)",
4946 idFile
, setting
.m_InterlaceMethod
, setting
.m_ViewMode
,
4947 static_cast<double>(setting
.m_CustomZoomAmount
),
4948 static_cast<double>(setting
.m_CustomPixelRatio
),
4949 static_cast<double>(setting
.m_CustomVerticalShift
), setting
.m_AudioStream
,
4950 setting
.m_SubtitleStream
, static_cast<double>(setting
.m_SubtitleDelay
),
4951 setting
.m_SubtitleOn
, static_cast<double>(setting
.m_Brightness
),
4952 static_cast<double>(setting
.m_Contrast
), static_cast<double>(setting
.m_Gamma
),
4953 static_cast<double>(setting
.m_VolumeAmplification
),
4954 static_cast<double>(setting
.m_AudioDelay
), setting
.m_ResumeTime
,
4955 static_cast<double>(setting
.m_Sharpness
), static_cast<double>(setting
.m_NoiseReduction
),
4956 setting
.m_CustomNonLinStretch
, setting
.m_PostProcess
, setting
.m_ScalingMethod
,
4957 setting
.m_StereoMode
, setting
.m_StereoInvert
, setting
.m_VideoStream
,
4958 setting
.m_ToneMapMethod
, static_cast<double>(setting
.m_ToneMapParam
),
4959 setting
.m_Orientation
, setting
.m_CenterMixLevel
);
4960 m_pDS
->exec(strSQL
);
4965 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, idFile
);
4969 void CVideoDatabase::UpdateArtForItem(int mediaId
, const MediaType
& mediaType
)
4971 AnnounceUpdate(mediaType
, mediaId
);
4974 void CVideoDatabase::SetArtForItem(int mediaId
, const MediaType
&mediaType
, const std::map
<std::string
, std::string
> &art
)
4976 for (const auto &i
: art
)
4977 SetArtForItem(mediaId
, mediaType
, i
.first
, i
.second
);
4980 void CVideoDatabase::SetArtForItem(int mediaId
, const MediaType
&mediaType
, const std::string
&artType
, const std::string
&url
)
4984 if (nullptr == m_pDB
)
4986 if (nullptr == m_pDS
)
4989 // don't set <foo>.<bar> art types - these are derivative types from parent items
4990 if (artType
.find('.') != std::string::npos
)
4993 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());
4997 int artId
= m_pDS
->fv(0).get_asInt();
4998 std::string oldUrl
= m_pDS
->fv(1).get_asString();
5002 sql
= PrepareSQL("UPDATE art SET url='%s' where art_id=%d", url
.c_str(), artId
);
5009 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());
5015 CLog::Log(LOGERROR
, "{}({}, '{}', '{}', '{}') failed", __FUNCTION__
, mediaId
, mediaType
,
5020 bool CVideoDatabase::GetArtForItem(int mediaId
, const MediaType
&mediaType
, std::map
<std::string
, std::string
> &art
)
5024 if (nullptr == m_pDB
)
5026 if (nullptr == m_pDS2
)
5027 return false; // using dataset 2 as we're likely called in loops on dataset 1
5029 std::string sql
= PrepareSQL("SELECT type,url FROM art WHERE media_id=%i AND media_type='%s'", mediaId
, mediaType
.c_str());
5032 while (!m_pDS2
->eof())
5034 art
.insert(make_pair(m_pDS2
->fv(0).get_asString(), m_pDS2
->fv(1).get_asString()));
5038 return !art
.empty();
5042 CLog::Log(LOGERROR
, "{}({}) failed", __FUNCTION__
, mediaId
);
5047 bool CVideoDatabase::GetArtForAsset(int assetId
,
5048 ArtFallbackOptions fallback
,
5049 std::map
<std::string
, std::string
>& art
)
5053 if (nullptr == m_pDB
)
5055 if (nullptr == m_pDS2
)
5056 return false; // using dataset 2 as we're likely called in loops on dataset 1
5058 std::string sql
{PrepareSQL("SELECT art.media_type, art.type, art.url "
5060 "WHERE media_id = %i AND media_type = '%s' ",
5061 assetId
, MediaTypeVideoVersion
)};
5063 if (fallback
== ArtFallbackOptions::PARENT
)
5064 sql
.append(PrepareSQL("UNION "
5065 "SELECT art.media_type, art.type, art.url "
5067 " JOIN videoversion as vv "
5068 " ON art.media_id = vv.idMedia AND art.media_type = vv.media_type "
5069 "WHERE idFile = %i",
5073 while (!m_pDS2
->eof())
5075 if (m_pDS2
->fv(0).get_asString() == MediaTypeVideoVersion
)
5077 // version data has priority over owner's data
5078 art
[m_pDS2
->fv(1).get_asString()] = m_pDS2
->fv(2).get_asString();
5080 else if (fallback
== ArtFallbackOptions::PARENT
)
5082 // insert if not yet present
5083 art
.insert(make_pair(m_pDS2
->fv(1).get_asString(), m_pDS2
->fv(2).get_asString()));
5088 return !art
.empty();
5092 CLog::LogF(LOGERROR
, "retrieval failed ({})", assetId
);
5097 std::string
CVideoDatabase::GetArtForItem(int mediaId
, const MediaType
&mediaType
, const std::string
&artType
)
5099 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());
5100 return GetSingleValue(query
, m_pDS2
);
5103 bool CVideoDatabase::RemoveArtForItem(int mediaId
, const MediaType
&mediaType
, const std::string
&artType
)
5105 return ExecuteQuery(PrepareSQL("DELETE FROM art WHERE media_id=%i AND media_type='%s' AND type='%s'", mediaId
, mediaType
.c_str(), artType
.c_str()));
5108 bool CVideoDatabase::RemoveArtForItem(int mediaId
, const MediaType
&mediaType
, const std::set
<std::string
> &artTypes
)
5111 for (const auto &i
: artTypes
)
5112 result
&= RemoveArtForItem(mediaId
, mediaType
, i
);
5117 bool CVideoDatabase::HasArtForItem(int mediaId
, const MediaType
&mediaType
)
5121 if (nullptr == m_pDB
)
5123 if (nullptr == m_pDS2
)
5124 return false; // using dataset 2 as we're likely called in loops on dataset 1
5126 std::string sql
= PrepareSQL("SELECT 1 FROM art WHERE media_id=%i AND media_type='%s' LIMIT 1", mediaId
, mediaType
.c_str());
5128 bool result
= !m_pDS2
->eof();
5134 CLog::Log(LOGERROR
, "{}({}) failed", __FUNCTION__
, mediaId
);
5139 bool CVideoDatabase::GetTvShowSeasons(int showId
, std::map
<int, int> &seasons
)
5143 if (nullptr == m_pDB
)
5145 if (nullptr == m_pDS2
)
5146 return false; // using dataset 2 as we're likely called in loops on dataset 1
5148 // get all seasons for this show
5149 std::string sql
= PrepareSQL("select idSeason,season from seasons where idShow=%i", showId
);
5153 while (!m_pDS2
->eof())
5155 seasons
.insert(std::make_pair(m_pDS2
->fv(1).get_asInt(), m_pDS2
->fv(0).get_asInt()));
5163 CLog::Log(LOGERROR
, "{}({}) failed", __FUNCTION__
, showId
);
5168 bool CVideoDatabase::GetTvShowNamedSeasons(int showId
, std::map
<int, std::string
> &seasons
)
5172 if (nullptr == m_pDB
)
5174 if (nullptr == m_pDS2
)
5175 return false; // using dataset 2 as we're likely called in loops on dataset 1
5177 // get all named seasons for this show
5178 std::string sql
= PrepareSQL("select season, name from seasons where season > 0 and name is not null and name <> '' and idShow = %i", showId
);
5182 while (!m_pDS2
->eof())
5184 seasons
.insert(std::make_pair(m_pDS2
->fv(0).get_asInt(), m_pDS2
->fv(1).get_asString()));
5192 CLog::Log(LOGERROR
, "{}({}) failed", __FUNCTION__
, showId
);
5197 std::string
CVideoDatabase::GetTvShowNamedSeasonById(int tvshowId
, int seasonId
)
5199 return GetSingleValue("seasons", "name",
5200 PrepareSQL("season=%i AND idShow=%i", seasonId
, tvshowId
));
5203 bool CVideoDatabase::GetTvShowSeasonArt(int showId
, std::map
<int, std::map
<std::string
, std::string
> > &seasonArt
)
5207 if (nullptr == m_pDB
)
5209 if (nullptr == m_pDS2
)
5210 return false; // using dataset 2 as we're likely called in loops on dataset 1
5212 std::map
<int, int> seasons
;
5213 GetTvShowSeasons(showId
, seasons
);
5215 for (const auto &i
: seasons
)
5217 std::map
<std::string
, std::string
> art
;
5218 GetArtForItem(i
.second
, MediaTypeSeason
, art
);
5219 seasonArt
.insert(std::make_pair(i
.first
,art
));
5225 CLog::Log(LOGERROR
, "{}({}) failed", __FUNCTION__
, showId
);
5230 bool CVideoDatabase::GetArtTypes(const MediaType
&mediaType
, std::vector
<std::string
> &artTypes
)
5234 if (nullptr == m_pDB
)
5236 if (nullptr == m_pDS
)
5239 std::string sql
= PrepareSQL("SELECT DISTINCT type FROM art WHERE media_type='%s'", mediaType
.c_str());
5240 int numRows
= RunQuery(sql
);
5242 return numRows
== 0;
5244 while (!m_pDS
->eof())
5246 artTypes
.emplace_back(m_pDS
->fv(0).get_asString());
5254 CLog::Log(LOGERROR
, "{}({}) failed", __FUNCTION__
, mediaType
);
5261 std::vector
<std::string
> GetBasicItemAvailableArtTypes(int mediaId
,
5262 VideoDbContentType dbType
,
5265 std::vector
<std::string
> result
;
5266 CVideoInfoTag tag
= db
.GetDetailsByTypeAndId(dbType
, mediaId
);
5268 //! @todo artwork: fanart stored separately, doesn't need to be
5269 tag
.m_fanart
.Unpack();
5270 if (tag
.m_fanart
.GetNumFanarts() && std::find(result
.cbegin(), result
.cend(), "fanart") == result
.cend())
5271 result
.emplace_back("fanart");
5274 tag
.m_strPictureURL
.Parse();
5275 for (const auto& urlEntry
: tag
.m_strPictureURL
.GetUrls())
5277 std::string artType
= urlEntry
.m_aspect
;
5278 if (artType
.empty())
5279 artType
= tag
.m_type
== MediaTypeEpisode
? "thumb" : "poster";
5280 if (urlEntry
.m_type
== CScraperUrl::UrlType::General
&& // exclude season artwork for TV shows
5281 !StringUtils::StartsWith(artType
, "set.") && // exclude movie set artwork for movies
5282 std::find(result
.cbegin(), result
.cend(), artType
) == result
.cend())
5284 result
.push_back(artType
);
5290 std::vector
<std::string
> GetSeasonAvailableArtTypes(int mediaId
, CVideoDatabase
& db
)
5293 db
.GetSeasonInfo(mediaId
, tag
);
5295 std::vector
<std::string
> result
;
5297 CVideoInfoTag sourceShow
;
5298 db
.GetTvShowInfo("", sourceShow
, tag
.m_iIdShow
);
5299 sourceShow
.m_strPictureURL
.Parse();
5300 for (const auto& urlEntry
: sourceShow
.m_strPictureURL
.GetUrls())
5302 std::string artType
= urlEntry
.m_aspect
;
5303 if (artType
.empty())
5305 if (urlEntry
.m_type
== CScraperUrl::UrlType::Season
&& urlEntry
.m_season
== tag
.m_iSeason
&&
5306 std::find(result
.cbegin(), result
.cend(), artType
) == result
.cend())
5308 result
.push_back(artType
);
5314 std::vector
<std::string
> GetMovieSetAvailableArtTypes(int mediaId
, CVideoDatabase
& db
)
5316 std::vector
<std::string
> result
;
5317 CFileItemList items
;
5318 std::string baseDir
= StringUtils::Format("videodb://movies/sets/{}", mediaId
);
5319 if (db
.GetMoviesNav(baseDir
, items
))
5321 for (const auto& item
: items
)
5323 CVideoInfoTag
* pTag
= item
->GetVideoInfoTag();
5324 pTag
->m_strPictureURL
.Parse();
5326 for (const auto& urlEntry
: pTag
->m_strPictureURL
.GetUrls())
5328 if (!StringUtils::StartsWith(urlEntry
.m_aspect
, "set."))
5331 std::string artType
= urlEntry
.m_aspect
.substr(4);
5332 if (std::find(result
.cbegin(), result
.cend(), artType
) == result
.cend())
5333 result
.push_back(artType
);
5340 std::vector
<CScraperUrl::SUrlEntry
> GetBasicItemAvailableArt(int mediaId
,
5341 VideoDbContentType dbType
,
5342 const std::string
& artType
,
5345 std::vector
<CScraperUrl::SUrlEntry
> result
;
5346 CVideoInfoTag tag
= db
.GetDetailsByTypeAndId(dbType
, mediaId
);
5348 if (artType
.empty() || artType
== "fanart")
5350 tag
.m_fanart
.Unpack();
5351 for (unsigned int i
= 0; i
< tag
.m_fanart
.GetNumFanarts(); i
++)
5353 CScraperUrl::SUrlEntry
url(tag
.m_fanart
.GetImageURL(i
));
5354 url
.m_preview
= tag
.m_fanart
.GetPreviewURL(i
);
5355 url
.m_aspect
= "fanart";
5356 result
.push_back(url
);
5359 tag
.m_strPictureURL
.Parse();
5360 for (auto urlEntry
: tag
.m_strPictureURL
.GetUrls())
5362 if (urlEntry
.m_aspect
.empty())
5363 urlEntry
.m_aspect
= tag
.m_type
== MediaTypeEpisode
? "thumb" : "poster";
5364 if ((urlEntry
.m_aspect
== artType
||
5365 (artType
.empty() && !StringUtils::StartsWith(urlEntry
.m_aspect
, "set."))) &&
5366 urlEntry
.m_type
== CScraperUrl::UrlType::General
)
5368 result
.push_back(urlEntry
);
5375 std::vector
<CScraperUrl::SUrlEntry
> GetSeasonAvailableArt(
5376 int mediaId
, const std::string
& artType
, CVideoDatabase
& db
)
5379 db
.GetSeasonInfo(mediaId
, tag
);
5381 std::vector
<CScraperUrl::SUrlEntry
> result
;
5383 CVideoInfoTag sourceShow
;
5384 db
.GetTvShowInfo("", sourceShow
, tag
.m_iIdShow
);
5385 sourceShow
.m_strPictureURL
.Parse();
5386 for (auto urlEntry
: sourceShow
.m_strPictureURL
.GetUrls())
5388 if (urlEntry
.m_aspect
.empty())
5389 urlEntry
.m_aspect
= "poster";
5390 if ((artType
.empty() || urlEntry
.m_aspect
== artType
) &&
5391 urlEntry
.m_type
== CScraperUrl::UrlType::Season
&&
5392 urlEntry
.m_season
== tag
.m_iSeason
)
5394 result
.push_back(urlEntry
);
5400 std::vector
<CScraperUrl::SUrlEntry
> GetMovieSetAvailableArt(
5401 int mediaId
, const std::string
& artType
, CVideoDatabase
& db
)
5403 std::vector
<CScraperUrl::SUrlEntry
> result
;
5404 CFileItemList items
;
5405 std::string baseDir
= StringUtils::Format("videodb://movies/sets/{}", mediaId
);
5406 std::unordered_set
<std::string
> addedURLs
;
5407 if (db
.GetMoviesNav(baseDir
, items
))
5409 for (const auto& item
: items
)
5411 CVideoInfoTag
* pTag
= item
->GetVideoInfoTag();
5412 pTag
->m_strPictureURL
.Parse();
5414 for (auto urlEntry
: pTag
->m_strPictureURL
.GetUrls())
5416 bool isSetArt
= !artType
.empty() ? urlEntry
.m_aspect
== "set." + artType
:
5417 StringUtils::StartsWith(urlEntry
.m_aspect
, "set.");
5418 if (isSetArt
&& addedURLs
.insert(urlEntry
.m_url
).second
)
5420 urlEntry
.m_aspect
= urlEntry
.m_aspect
.substr(4);
5421 result
.push_back(urlEntry
);
5429 VideoDbContentType
CovertMediaTypeToContentType(const MediaType
& mediaType
)
5431 VideoDbContentType dbType
{VideoDbContentType::UNKNOWN
};
5432 if (mediaType
== MediaTypeTvShow
)
5433 dbType
= VideoDbContentType::TVSHOWS
;
5434 else if (mediaType
== MediaTypeMovie
)
5435 dbType
= VideoDbContentType::MOVIES
;
5436 else if (mediaType
== MediaTypeEpisode
)
5437 dbType
= VideoDbContentType::EPISODES
;
5438 else if (mediaType
== MediaTypeMusicVideo
)
5439 dbType
= VideoDbContentType::MUSICVIDEOS
;
5445 std::vector
<CScraperUrl::SUrlEntry
> CVideoDatabase::GetAvailableArtForItem(
5446 int mediaId
, const MediaType
& mediaType
, const std::string
& artType
)
5448 VideoDbContentType dbType
= CovertMediaTypeToContentType(mediaType
);
5450 if (dbType
!= VideoDbContentType::UNKNOWN
)
5451 return GetBasicItemAvailableArt(mediaId
, dbType
, artType
, *this);
5452 if (mediaType
== MediaTypeSeason
)
5453 return GetSeasonAvailableArt(mediaId
, artType
, *this);
5454 if (mediaType
== MediaTypeVideoCollection
)
5455 return GetMovieSetAvailableArt(mediaId
, artType
, *this);
5459 std::vector
<std::string
> CVideoDatabase::GetAvailableArtTypesForItem(int mediaId
,
5460 const MediaType
& mediaType
)
5462 VideoDbContentType dbType
= CovertMediaTypeToContentType(mediaType
);
5464 if (dbType
!= VideoDbContentType::UNKNOWN
)
5465 return GetBasicItemAvailableArtTypes(mediaId
, dbType
, *this);
5466 if (mediaType
== MediaTypeSeason
)
5467 return GetSeasonAvailableArtTypes(mediaId
, *this);
5468 if (mediaType
== MediaTypeVideoCollection
)
5469 return GetMovieSetAvailableArtTypes(mediaId
, *this);
5473 /// \brief GetStackTimes() obtains any saved video times for the stacked file
5474 /// \retval Returns true if the stack times exist, false otherwise.
5475 bool CVideoDatabase::GetStackTimes(const std::string
&filePath
, std::vector
<uint64_t> ×
)
5479 // obtain the FileID (if it exists)
5480 int idFile
= GetFileId(filePath
);
5481 if (idFile
< 0) return false;
5482 if (nullptr == m_pDB
)
5484 if (nullptr == m_pDS
)
5486 // ok, now obtain the settings for this file
5487 std::string strSQL
=PrepareSQL("select times from stacktimes where idFile=%i\n", idFile
);
5488 m_pDS
->query( strSQL
);
5489 if (m_pDS
->num_rows() > 0)
5490 { // get the video settings info
5491 uint64_t timeTotal
= 0;
5492 std::vector
<std::string
> timeString
= StringUtils::Split(m_pDS
->fv("times").get_asString(), ",");
5494 for (const auto &i
: timeString
)
5496 uint64_t partTime
= static_cast<uint64_t>(atof(i
.c_str()) * 1000.0);
5497 times
.push_back(partTime
); // db stores in secs, convert to msecs
5498 timeTotal
+= partTime
;
5501 return (timeTotal
> 0);
5507 CLog::Log(LOGERROR
, "{} failed", __FUNCTION__
);
5512 /// \brief Sets the stack times for a particular video file
5513 void CVideoDatabase::SetStackTimes(const std::string
& filePath
, const std::vector
<uint64_t> ×
)
5517 if (nullptr == m_pDB
)
5519 if (nullptr == m_pDS
)
5521 int idFile
= AddFile(filePath
);
5525 // delete any existing items
5526 m_pDS
->exec( PrepareSQL("delete from stacktimes where idFile=%i", idFile
) );
5529 std::string timeString
= StringUtils::Format("{:.3f}", times
[0] / 1000.0f
);
5530 for (unsigned int i
= 1; i
< times
.size(); i
++)
5531 timeString
+= StringUtils::Format(",{:.3f}", times
[i
] / 1000.0f
);
5533 m_pDS
->exec( PrepareSQL("insert into stacktimes (idFile,times) values (%i,'%s')\n", idFile
, timeString
.c_str()) );
5537 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, filePath
);
5541 void CVideoDatabase::RemoveContentForPath(const std::string
& strPath
, CGUIDialogProgress
*progress
/* = NULL */)
5543 if(URIUtils::IsMultiPath(strPath
))
5545 std::vector
<std::string
> paths
;
5546 CMultiPathDirectory::GetPaths(strPath
, paths
);
5548 for(unsigned i
=0;i
<paths
.size();i
++)
5549 RemoveContentForPath(paths
[i
], progress
);
5554 if (nullptr == m_pDB
)
5556 if (nullptr == m_pDS
)
5561 progress
->SetHeading(CVariant
{700});
5562 progress
->SetLine(0, CVariant
{""});
5563 progress
->SetLine(1, CVariant
{313});
5564 progress
->SetLine(2, CVariant
{330});
5565 progress
->SetPercentage(0);
5567 progress
->ShowProgressBar(true);
5569 std::vector
<std::pair
<int, std::string
> > paths
;
5570 GetSubPaths(strPath
, paths
);
5572 for (const auto &i
: paths
)
5574 bool bMvidsChecked
=false;
5577 progress
->SetPercentage((int)((float)(iCurr
++)/paths
.size()*100.f
));
5578 progress
->Progress();
5581 const auto tvshowId
= GetTvShowId(i
.second
);
5583 DeleteTvShow(tvshowId
);
5586 std::string strSQL
= PrepareSQL("select files.strFilename from files join movie on movie.idFile=files.idFile where files.idPath=%i", i
.first
);
5587 m_pDS2
->query(strSQL
);
5590 strSQL
= PrepareSQL("select files.strFilename from files join musicvideo on musicvideo.idFile=files.idFile where files.idPath=%i", i
.first
);
5591 m_pDS2
->query(strSQL
);
5592 bMvidsChecked
= true;
5594 while (!m_pDS2
->eof())
5596 std::string strMoviePath
;
5597 std::string strFileName
= m_pDS2
->fv("files.strFilename").get_asString();
5598 ConstructPath(strMoviePath
, i
.second
, strFileName
);
5599 const auto movieId
= GetMovieId(strMoviePath
);
5601 DeleteMovie(movieId
);
5604 const auto musicvideoId
= GetMusicVideoId(strMoviePath
);
5605 if (musicvideoId
> 0)
5606 DeleteMusicVideo(musicvideoId
);
5609 if (m_pDS2
->eof() && !bMvidsChecked
)
5611 strSQL
=PrepareSQL("select files.strFilename from files join musicvideo on musicvideo.idFile=files.idFile where files.idPath=%i", i
.first
);
5612 m_pDS2
->query(strSQL
);
5613 bMvidsChecked
= true;
5617 m_pDS2
->exec(PrepareSQL("update path set strContent='', strScraper='', strHash='',strSettings='',useFolderNames=0,scanRecursive=0 where idPath=%i", i
.first
));
5623 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, strPath
);
5629 void CVideoDatabase::SetScraperForPath(const std::string
& filePath
, const ScraperPtr
& scraper
, const VIDEO::SScanSettings
& settings
)
5631 // if we have a multipath, set scraper for all contained paths
5632 if(URIUtils::IsMultiPath(filePath
))
5634 std::vector
<std::string
> paths
;
5635 CMultiPathDirectory::GetPaths(filePath
, paths
);
5637 for(unsigned i
=0;i
<paths
.size();i
++)
5638 SetScraperForPath(paths
[i
],scraper
,settings
);
5645 if (nullptr == m_pDB
)
5647 if (nullptr == m_pDS
)
5650 int idPath
= AddPath(filePath
);
5656 if (settings
.exclude
)
5657 { //NB See note in ::GetScraperForPath about strContent=='none'
5658 strSQL
= PrepareSQL(
5659 "UPDATE path SET strContent='', strScraper='', scanRecursive=0, useFolderNames=0, "
5660 "strSettings='', noUpdate=0, exclude=1, allAudio=%i WHERE idPath=%i",
5661 settings
.m_allExtAudio
, idPath
);
5664 { // catch clearing content, but not excluding
5665 strSQL
= PrepareSQL(
5666 "UPDATE path SET strContent='', strScraper='', scanRecursive=0, useFolderNames=0, "
5667 "strSettings='', noUpdate=0, exclude=0, allAudio=%i WHERE idPath=%i",
5668 settings
.m_allExtAudio
, idPath
);
5672 std::string content
= TranslateContent(scraper
->Content());
5673 strSQL
= PrepareSQL(
5674 "UPDATE path SET strContent='%s', strScraper='%s', scanRecursive=%i, useFolderNames=%i, "
5675 "strSettings='%s', noUpdate=%i, exclude=0, allAudio=%i WHERE idPath=%i",
5676 content
.c_str(), scraper
->ID().c_str(), settings
.recurse
, settings
.parent_name
,
5677 scraper
->GetPathSettings().c_str(), settings
.noupdate
, settings
.m_allExtAudio
, idPath
);
5679 m_pDS
->exec(strSQL
);
5683 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, filePath
);
5687 bool CVideoDatabase::ScraperInUse(const std::string
&scraperID
) const
5691 if (nullptr == m_pDB
)
5693 if (nullptr == m_pDS
)
5696 std::string sql
= PrepareSQL("select count(1) from path where strScraper='%s'", scraperID
.c_str());
5697 if (!m_pDS
->query(sql
) || m_pDS
->num_rows() == 0)
5699 bool found
= m_pDS
->fv(0).get_asInt() > 0;
5705 CLog::Log(LOGERROR
, "{}({}) failed", __FUNCTION__
, scraperID
);
5713 CArtItem() { art_id
= 0; media_id
= 0; };
5715 std::string art_type
;
5716 std::string art_url
;
5718 std::string media_type
;
5721 // used for database update to v83
5725 bool operator==(const CShowItem
&r
) const
5727 return (!ident
.empty() && ident
== r
.ident
) || (title
== r
.title
&& year
== r
.year
);
5736 // used for database update to v84
5745 void CVideoDatabase::UpdateTables(int iVersion
)
5747 // Important: DO NOT use CREATE TABLE [...] AS SELECT [...] - it does not work on MySQL with GTID consistency enforced
5751 m_pDS
->exec("ALTER TABLE settings ADD StereoMode integer");
5752 m_pDS
->exec("ALTER TABLE settings ADD StereoInvert bool");
5755 m_pDS
->exec("ALTER TABLE streamdetails ADD strStereoMode text");
5758 { // add idParentPath to path table
5759 m_pDS
->exec("ALTER TABLE path ADD idParentPath integer");
5760 std::map
<std::string
, int> paths
;
5761 m_pDS
->query("select idPath,strPath from path");
5762 while (!m_pDS
->eof())
5764 paths
.insert(make_pair(m_pDS
->fv(1).get_asString(), m_pDS
->fv(0).get_asInt()));
5768 // run through these paths figuring out the parent path, and add to the table if found
5769 for (const auto &i
: paths
)
5771 std::string parent
= URIUtils::GetParentPath(i
.first
);
5772 auto j
= paths
.find(parent
);
5773 if (j
!= paths
.end())
5774 m_pDS
->exec(PrepareSQL("UPDATE path SET idParentPath=%i WHERE idPath=%i", j
->second
, i
.second
));
5779 // drop parent path id and basePath from tvshow table
5780 m_pDS
->exec("UPDATE tvshow SET c16=NULL,c17=NULL");
5784 // drop duplicates in tvshow table, and update tvshowlinkpath accordingly
5785 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
);
5787 std::vector
<CShowItem
> shows
;
5788 while (!m_pDS
->eof())
5791 show
.id
= m_pDS
->fv(0).get_asInt();
5792 show
.path
= m_pDS
->fv(1).get_asInt();
5793 show
.title
= m_pDS
->fv(2).get_asString();
5794 show
.year
= m_pDS
->fv(3).get_asString();
5795 show
.ident
= m_pDS
->fv(4).get_asString();
5796 shows
.emplace_back(std::move(show
));
5802 for (auto i
= shows
.begin() + 1; i
!= shows
.end(); ++i
)
5804 // has this show been found before?
5805 auto j
= find(shows
.begin(), i
, *i
);
5807 { // this is a duplicate
5808 // update the tvshowlinkpath table
5809 m_pDS
->exec(PrepareSQL("UPDATE tvshowlinkpath SET idShow = %d WHERE idShow = %d AND idPath = %d", j
->id
, i
->id
, i
->path
));
5810 // update episodes, seasons, movie links
5811 m_pDS
->exec(PrepareSQL("UPDATE episode SET idShow = %d WHERE idShow = %d", j
->id
, i
->id
));
5812 m_pDS
->exec(PrepareSQL("UPDATE seasons SET idShow = %d WHERE idShow = %d", j
->id
, i
->id
));
5813 m_pDS
->exec(PrepareSQL("UPDATE movielinktvshow SET idShow = %d WHERE idShow = %d", j
->id
, i
->id
));
5815 m_pDS
->exec(PrepareSQL("DELETE FROM genrelinktvshow WHERE idShow=%i", i
->id
));
5816 m_pDS
->exec(PrepareSQL("DELETE FROM actorlinktvshow WHERE idShow=%i", i
->id
));
5817 m_pDS
->exec(PrepareSQL("DELETE FROM directorlinktvshow WHERE idShow=%i", i
->id
));
5818 m_pDS
->exec(PrepareSQL("DELETE FROM studiolinktvshow WHERE idShow=%i", i
->id
));
5819 m_pDS
->exec(PrepareSQL("DELETE FROM tvshow WHERE idShow = %d", i
->id
));
5822 // cleanup duplicate seasons
5823 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)");
5827 { // replace any multipaths in tvshowlinkpath table
5828 m_pDS
->query("SELECT idShow, tvshowlinkpath.idPath, strPath FROM tvshowlinkpath JOIN path ON tvshowlinkpath.idPath=path.idPath WHERE path.strPath LIKE 'multipath://%'");
5829 std::vector
<CShowLink
> shows
;
5830 while (!m_pDS
->eof())
5833 link
.show
= m_pDS
->fv(0).get_asInt();
5834 link
.pathId
= m_pDS
->fv(1).get_asInt();
5835 link
.path
= m_pDS
->fv(2).get_asString();
5836 shows
.emplace_back(std::move(link
));
5841 for (auto i
= shows
.begin(); i
!= shows
.end(); ++i
)
5843 std::vector
<std::string
> paths
;
5844 CMultiPathDirectory::GetPaths(i
->path
, paths
);
5845 for (auto j
= paths
.begin(); j
!= paths
.end(); ++j
)
5847 int idPath
= AddPath(*j
, URIUtils::GetParentPath(*j
));
5848 /* we can't rely on REPLACE INTO here as analytics (indices) aren't online yet */
5849 if (GetSingleValue(PrepareSQL("SELECT 1 FROM tvshowlinkpath WHERE idShow=%i AND idPath=%i", i
->show
, idPath
)).empty())
5850 m_pDS
->exec(PrepareSQL("INSERT INTO tvshowlinkpath(idShow, idPath) VALUES(%i,%i)", i
->show
, idPath
));
5852 m_pDS
->exec(PrepareSQL("DELETE FROM tvshowlinkpath WHERE idShow=%i AND idPath=%i", i
->show
, i
->pathId
));
5857 // drop multipaths from the path table - they're not needed for anything at all
5858 m_pDS
->exec("DELETE FROM path WHERE strPath LIKE 'multipath://%'");
5861 { // due to the tvshow merging above, there could be orphaned season or show art
5862 m_pDS
->exec("DELETE from art WHERE media_type='tvshow' AND NOT EXISTS (SELECT 1 FROM tvshow WHERE tvshow.idShow = art.media_id)");
5863 m_pDS
->exec("DELETE from art WHERE media_type='season' AND NOT EXISTS (SELECT 1 FROM seasons WHERE seasons.idSeason = art.media_id)");
5867 // create actor link table
5868 m_pDS
->exec("CREATE TABLE actor_link(actor_id INT, media_id INT, media_type TEXT, role TEXT, cast_order INT)");
5869 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");
5870 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");
5871 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");
5872 m_pDS
->exec("DROP TABLE IF EXISTS actorlinkmovie");
5873 m_pDS
->exec("DROP TABLE IF EXISTS actorlinktvshow");
5874 m_pDS
->exec("DROP TABLE IF EXISTS actorlinkepisode");
5875 m_pDS
->exec("CREATE TABLE actor(actor_id INTEGER PRIMARY KEY, name TEXT, art_urls TEXT)");
5876 m_pDS
->exec("INSERT INTO actor(actor_id, name, art_urls) SELECT idActor,strActor,strThumb FROM actors");
5877 m_pDS
->exec("DROP TABLE IF EXISTS actors");
5880 m_pDS
->exec("CREATE TABLE director_link(actor_id INTEGER, media_id INTEGER, media_type TEXT)");
5881 m_pDS
->exec("INSERT INTO director_link(actor_id, media_id, media_type) SELECT DISTINCT idDirector, idMovie, 'movie' FROM directorlinkmovie");
5882 m_pDS
->exec("INSERT INTO director_link(actor_id, media_id, media_type) SELECT DISTINCT idDirector, idShow, 'tvshow' FROM directorlinktvshow");
5883 m_pDS
->exec("INSERT INTO director_link(actor_id, media_id, media_type) SELECT DISTINCT idDirector, idEpisode, 'episode' FROM directorlinkepisode");
5884 m_pDS
->exec("INSERT INTO director_link(actor_id, media_id, media_type) SELECT DISTINCT idDirector, idMVideo, 'musicvideo' FROM directorlinkmusicvideo");
5885 m_pDS
->exec("DROP TABLE IF EXISTS directorlinkmovie");
5886 m_pDS
->exec("DROP TABLE IF EXISTS directorlinktvshow");
5887 m_pDS
->exec("DROP TABLE IF EXISTS directorlinkepisode");
5888 m_pDS
->exec("DROP TABLE IF EXISTS directorlinkmusicvideo");
5891 m_pDS
->exec("CREATE TABLE writer_link(actor_id INTEGER, media_id INTEGER, media_type TEXT)");
5892 m_pDS
->exec("INSERT INTO writer_link(actor_id, media_id, media_type) SELECT DISTINCT idWriter, idMovie, 'movie' FROM writerlinkmovie");
5893 m_pDS
->exec("INSERT INTO writer_link(actor_id, media_id, media_type) SELECT DISTINCT idWriter, idEpisode, 'episode' FROM writerlinkepisode");
5894 m_pDS
->exec("DROP TABLE IF EXISTS writerlinkmovie");
5895 m_pDS
->exec("DROP TABLE IF EXISTS writerlinkepisode");
5898 m_pDS
->exec("INSERT INTO actor_link(actor_id, media_id, media_type) SELECT DISTINCT idArtist, idMVideo, 'musicvideo' FROM artistlinkmusicvideo");
5899 m_pDS
->exec("DROP TABLE IF EXISTS artistlinkmusicvideo");
5902 m_pDS
->exec("CREATE TABLE studio_link(studio_id INTEGER, media_id INTEGER, media_type TEXT)");
5903 m_pDS
->exec("INSERT INTO studio_link(studio_id, media_id, media_type) SELECT DISTINCT idStudio, idMovie, 'movie' FROM studiolinkmovie");
5904 m_pDS
->exec("INSERT INTO studio_link(studio_id, media_id, media_type) SELECT DISTINCT idStudio, idShow, 'tvshow' FROM studiolinktvshow");
5905 m_pDS
->exec("INSERT INTO studio_link(studio_id, media_id, media_type) SELECT DISTINCT idStudio, idMVideo, 'musicvideo' FROM studiolinkmusicvideo");
5906 m_pDS
->exec("DROP TABLE IF EXISTS studiolinkmovie");
5907 m_pDS
->exec("DROP TABLE IF EXISTS studiolinktvshow");
5908 m_pDS
->exec("DROP TABLE IF EXISTS studiolinkmusicvideo");
5909 m_pDS
->exec("CREATE TABLE studionew(studio_id INTEGER PRIMARY KEY, name TEXT)");
5910 m_pDS
->exec("INSERT INTO studionew(studio_id, name) SELECT idStudio,strStudio FROM studio");
5911 m_pDS
->exec("DROP TABLE IF EXISTS studio");
5912 m_pDS
->exec("ALTER TABLE studionew RENAME TO studio");
5915 m_pDS
->exec("CREATE TABLE genre_link(genre_id INTEGER, media_id INTEGER, media_type TEXT)");
5916 m_pDS
->exec("INSERT INTO genre_link(genre_id, media_id, media_type) SELECT DISTINCT idGenre, idMovie, 'movie' FROM genrelinkmovie");
5917 m_pDS
->exec("INSERT INTO genre_link(genre_id, media_id, media_type) SELECT DISTINCT idGenre, idShow, 'tvshow' FROM genrelinktvshow");
5918 m_pDS
->exec("INSERT INTO genre_link(genre_id, media_id, media_type) SELECT DISTINCT idGenre, idMVideo, 'musicvideo' FROM genrelinkmusicvideo");
5919 m_pDS
->exec("DROP TABLE IF EXISTS genrelinkmovie");
5920 m_pDS
->exec("DROP TABLE IF EXISTS genrelinktvshow");
5921 m_pDS
->exec("DROP TABLE IF EXISTS genrelinkmusicvideo");
5922 m_pDS
->exec("CREATE TABLE genrenew(genre_id INTEGER PRIMARY KEY, name TEXT)");
5923 m_pDS
->exec("INSERT INTO genrenew(genre_id, name) SELECT idGenre,strGenre FROM genre");
5924 m_pDS
->exec("DROP TABLE IF EXISTS genre");
5925 m_pDS
->exec("ALTER TABLE genrenew RENAME TO genre");
5928 m_pDS
->exec("CREATE TABLE country_link(country_id INTEGER, media_id INTEGER, media_type TEXT)");
5929 m_pDS
->exec("INSERT INTO country_link(country_id, media_id, media_type) SELECT DISTINCT idCountry, idMovie, 'movie' FROM countrylinkmovie");
5930 m_pDS
->exec("DROP TABLE IF EXISTS countrylinkmovie");
5931 m_pDS
->exec("CREATE TABLE countrynew(country_id INTEGER PRIMARY KEY, name TEXT)");
5932 m_pDS
->exec("INSERT INTO countrynew(country_id, name) SELECT idCountry,strCountry FROM country");
5933 m_pDS
->exec("DROP TABLE IF EXISTS country");
5934 m_pDS
->exec("ALTER TABLE countrynew RENAME TO country");
5937 m_pDS
->exec("CREATE TABLE tag_link(tag_id INTEGER, media_id INTEGER, media_type TEXT)");
5938 m_pDS
->exec("INSERT INTO tag_link(tag_id, media_id, media_type) SELECT DISTINCT idTag, idMedia, media_type FROM taglinks");
5939 m_pDS
->exec("DROP TABLE IF EXISTS taglinks");
5940 m_pDS
->exec("CREATE TABLE tagnew(tag_id INTEGER PRIMARY KEY, name TEXT)");
5941 m_pDS
->exec("INSERT INTO tagnew(tag_id, name) SELECT idTag,strTag FROM tag");
5942 m_pDS
->exec("DROP TABLE IF EXISTS tag");
5943 m_pDS
->exec("ALTER TABLE tagnew RENAME TO tag");
5948 // cleanup main tables
5949 std::string valuesSql
;
5950 for(int i
= 0; i
< VIDEODB_MAX_COLUMNS
; i
++)
5952 valuesSql
+= StringUtils::Format("c{:02} = TRIM(c{:02})", i
, i
);
5953 if (i
< VIDEODB_MAX_COLUMNS
- 1)
5956 m_pDS
->exec("UPDATE episode SET " + valuesSql
);
5957 m_pDS
->exec("UPDATE movie SET " + valuesSql
);
5958 m_pDS
->exec("UPDATE musicvideo SET " + valuesSql
);
5959 m_pDS
->exec("UPDATE tvshow SET " + valuesSql
);
5961 // cleanup additional tables
5962 std::map
<std::string
, std::vector
<std::string
>> additionalTablesMap
= {
5963 {"actor", {"actor_link", "director_link", "writer_link"}},
5964 {"studio", {"studio_link"}},
5965 {"genre", {"genre_link"}},
5966 {"country", {"country_link"}},
5967 {"tag", {"tag_link"}}
5969 for (const auto& additionalTableEntry
: additionalTablesMap
)
5971 std::string table
= additionalTableEntry
.first
;
5972 std::string tablePk
= additionalTableEntry
.first
+ "_id";
5973 std::map
<int, std::string
> duplicatesMinMap
;
5974 std::map
<int, std::string
> duplicatesMap
;
5977 m_pDS
->exec(PrepareSQL("UPDATE %s SET name = TRIM(name)",
5980 // shrink name to length 255
5981 m_pDS
->exec(PrepareSQL("UPDATE %s SET name = SUBSTR(name, 1, 255) WHERE LENGTH(name) > 255",
5984 // fetch main entries
5985 m_pDS
->query(PrepareSQL("SELECT MIN(%s), name FROM %s GROUP BY name HAVING COUNT(1) > 1",
5986 tablePk
.c_str(), table
.c_str()));
5988 while (!m_pDS
->eof())
5990 duplicatesMinMap
.insert(std::make_pair(m_pDS
->fv(0).get_asInt(), m_pDS
->fv(1).get_asString()));
5995 // fetch duplicate entries
5996 for (const auto& entry
: duplicatesMinMap
)
5998 m_pDS
->query(PrepareSQL("SELECT %s FROM %s WHERE name = '%s' AND %s <> %i",
5999 tablePk
.c_str(), table
.c_str(),
6000 entry
.second
.c_str(), tablePk
.c_str(), entry
.first
));
6002 std::stringstream ids
;
6003 while (!m_pDS
->eof())
6005 int id
= m_pDS
->fv(0).get_asInt();
6014 duplicatesMap
.insert(std::make_pair(entry
.first
, ids
.str()));
6017 // cleanup duplicates in link tables
6018 for (const auto& subTable
: additionalTableEntry
.second
)
6020 // create indexes to speed up things
6021 m_pDS
->exec(PrepareSQL("CREATE INDEX ix_%s ON %s (%s)",
6022 subTable
.c_str(), subTable
.c_str(), tablePk
.c_str()));
6024 // migrate every duplicate entry to the main entry
6025 for (const auto& entry
: duplicatesMap
)
6027 m_pDS
->exec(PrepareSQL("UPDATE %s SET %s = %i WHERE %s IN (%s) ",
6028 subTable
.c_str(), tablePk
.c_str(), entry
.first
,
6029 tablePk
.c_str(), entry
.second
.c_str()));
6032 // clear all duplicates in the link tables
6033 if (subTable
== "actor_link")
6035 // as a distinct won't work because of role and cast_order and a group by kills a
6036 // low powered mysql, we de-dupe it with REPLACE INTO while using the real unique index
6037 m_pDS
->exec("CREATE TABLE temp_actor_link(actor_id INT, media_id INT, media_type TEXT, role TEXT, cast_order INT)");
6038 m_pDS
->exec("CREATE UNIQUE INDEX ix_temp_actor_link ON temp_actor_link (actor_id, media_type(20), media_id)");
6039 m_pDS
->exec("REPLACE INTO temp_actor_link SELECT * FROM actor_link");
6040 m_pDS
->exec("DROP INDEX ix_temp_actor_link ON temp_actor_link");
6044 m_pDS
->exec(PrepareSQL("CREATE TABLE temp_%s AS SELECT DISTINCT * FROM %s",
6045 subTable
.c_str(), subTable
.c_str()));
6048 m_pDS
->exec(PrepareSQL("DROP TABLE IF EXISTS %s",
6051 m_pDS
->exec(PrepareSQL("ALTER TABLE temp_%s RENAME TO %s",
6052 subTable
.c_str(), subTable
.c_str()));
6055 // delete duplicates in main table
6056 for (const auto& entry
: duplicatesMap
)
6058 m_pDS
->exec(PrepareSQL("DELETE FROM %s WHERE %s IN (%s)",
6059 table
.c_str(), tablePk
.c_str(), entry
.second
.c_str()));
6066 m_pDS
->exec("ALTER TABLE movie ADD userrating integer");
6067 m_pDS
->exec("ALTER TABLE episode ADD userrating integer");
6068 m_pDS
->exec("ALTER TABLE tvshow ADD userrating integer");
6069 m_pDS
->exec("ALTER TABLE musicvideo ADD userrating integer");
6073 m_pDS
->exec("ALTER TABLE sets ADD strOverview TEXT");
6076 m_pDS
->exec("ALTER TABLE seasons ADD name text");
6080 // Add idSeason to episode table, so we don't have to join via idShow and season in the future
6081 m_pDS
->exec("ALTER TABLE episode ADD idSeason integer");
6083 m_pDS
->query("SELECT idSeason, idShow, season FROM seasons");
6084 while (!m_pDS
->eof())
6086 m_pDS2
->exec(PrepareSQL("UPDATE episode "
6087 "SET idSeason = %d "
6089 "episode.idShow = %d AND "
6090 "episode.c%02d = %d",
6091 m_pDS
->fv(0).get_asInt(), m_pDS
->fv(1).get_asInt(),
6092 VIDEODB_ID_EPISODE_SEASON
, m_pDS
->fv(2).get_asInt()));
6098 m_pDS
->exec("ALTER TABLE seasons ADD userrating INTEGER");
6102 m_pDS
->exec("CREATE TABLE rating (rating_id INTEGER PRIMARY KEY, media_id INTEGER, media_type TEXT, rating_type TEXT, rating FLOAT, votes INTEGER)");
6104 std::string sql
= PrepareSQL("SELECT DISTINCT idMovie, c%02d, c%02d FROM movie", VIDEODB_ID_RATING_ID
, VIDEODB_ID_VOTES
);
6106 while (!m_pDS
->eof())
6108 m_pDS2
->exec(PrepareSQL("INSERT INTO rating(media_id, media_type, rating_type, rating, "
6109 "votes) VALUES (%i, 'movie', 'default', %f, %i)",
6110 m_pDS
->fv(0).get_asInt(),
6111 strtod(m_pDS
->fv(1).get_asString().c_str(), NULL
),
6112 StringUtils::ReturnDigits(m_pDS
->fv(2).get_asString())));
6113 int idRating
= (int)m_pDS2
->lastinsertid();
6114 m_pDS2
->exec(PrepareSQL("UPDATE movie SET c%02d=%i WHERE idMovie=%i", VIDEODB_ID_RATING_ID
, idRating
, m_pDS
->fv(0).get_asInt()));
6119 sql
= PrepareSQL("SELECT DISTINCT idShow, c%02d, c%02d FROM tvshow", VIDEODB_ID_TV_RATING_ID
, VIDEODB_ID_TV_VOTES
);
6121 while (!m_pDS
->eof())
6123 m_pDS2
->exec(PrepareSQL("INSERT INTO rating(media_id, media_type, rating_type, rating, "
6124 "votes) VALUES (%i, 'tvshow', 'default', %f, %i)",
6125 m_pDS
->fv(0).get_asInt(),
6126 strtod(m_pDS
->fv(1).get_asString().c_str(), NULL
),
6127 StringUtils::ReturnDigits(m_pDS
->fv(2).get_asString())));
6128 int idRating
= (int)m_pDS2
->lastinsertid();
6129 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()));
6134 sql
= PrepareSQL("SELECT DISTINCT idEpisode, c%02d, c%02d FROM episode", VIDEODB_ID_EPISODE_RATING_ID
, VIDEODB_ID_EPISODE_VOTES
);
6136 while (!m_pDS
->eof())
6138 m_pDS2
->exec(PrepareSQL("INSERT INTO rating(media_id, media_type, rating_type, rating, "
6139 "votes) VALUES (%i, 'episode', 'default', %f, %i)",
6140 m_pDS
->fv(0).get_asInt(),
6141 strtod(m_pDS
->fv(1).get_asString().c_str(), NULL
),
6142 StringUtils::ReturnDigits(m_pDS
->fv(2).get_asString())));
6143 int idRating
= (int)m_pDS2
->lastinsertid();
6144 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()));
6152 m_pDS
->exec("ALTER TABLE settings ADD VideoStream integer");
6153 m_pDS
->exec("ALTER TABLE streamdetails ADD strVideoLanguage text");
6158 m_pDS
->exec("ALTER TABLE tvshow ADD duration INTEGER");
6160 std::string sql
= PrepareSQL( "SELECT episode.idShow, MAX(episode.c%02d) "
6163 "LEFT JOIN streamdetails "
6164 "ON streamdetails.idFile = episode.idFile "
6165 "AND streamdetails.iStreamType = 0 " // only grab video streams
6167 "WHERE episode.c%02d <> streamdetails.iVideoDuration "
6168 "OR streamdetails.iVideoDuration IS NULL "
6169 "GROUP BY episode.idShow", VIDEODB_ID_EPISODE_RUNTIME
, VIDEODB_ID_EPISODE_RUNTIME
);
6172 while (!m_pDS
->eof())
6174 m_pDS2
->exec(PrepareSQL("UPDATE tvshow SET duration=%i WHERE idShow=%i", m_pDS
->fv(1).get_asInt(), m_pDS
->fv(0).get_asInt()));
6182 m_pDS
->exec("ALTER TABLE movie ADD premiered TEXT");
6183 m_pDS
->exec(PrepareSQL("UPDATE movie SET premiered=c%02d", VIDEODB_ID_YEAR
));
6184 m_pDS
->exec("ALTER TABLE musicvideo ADD premiered TEXT");
6185 m_pDS
->exec(PrepareSQL("UPDATE musicvideo SET premiered=c%02d", VIDEODB_ID_MUSICVIDEO_YEAR
));
6190 // need this due to the nested GetScraperPath query
6191 std::unique_ptr
<Dataset
> pDS
;
6192 pDS
.reset(m_pDB
->CreateDataset());
6196 pDS
->exec("CREATE TABLE uniqueid (uniqueid_id INTEGER PRIMARY KEY, media_id INTEGER, media_type TEXT, value TEXT, type TEXT)");
6198 for (int i
= 0; i
< 3; ++i
)
6200 std::string mediatype
, columnID
;
6205 mediatype
= "movie";
6206 columnID
= "idMovie";
6207 columnUniqueID
= VIDEODB_ID_IDENT_ID
;
6210 mediatype
= "tvshow";
6211 columnID
= "idShow";
6212 columnUniqueID
= VIDEODB_ID_TV_IDENT_ID
;
6215 mediatype
= "episode";
6216 columnID
= "idEpisode";
6217 columnUniqueID
= VIDEODB_ID_EPISODE_IDENT_ID
;
6222 pDS
->query(PrepareSQL("SELECT %s, c%02d FROM %s", columnID
.c_str(), columnUniqueID
, mediatype
.c_str()));
6225 std::string uniqueid
= pDS
->fv(1).get_asString();
6226 if (!uniqueid
.empty())
6228 int mediaid
= pDS
->fv(0).get_asInt();
6229 if (StringUtils::StartsWith(uniqueid
, "tt"))
6230 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()));
6232 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()));
6233 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
));
6243 m_pDS
->exec("ALTER TABLE settings RENAME TO settingsold");
6244 m_pDS
->exec("CREATE TABLE settings ( idFile integer, Deinterlace bool,"
6245 "ViewMode integer,ZoomAmount float, PixelRatio float, VerticalShift float, AudioStream integer, SubtitleStream integer,"
6246 "SubtitleDelay float, SubtitlesOn bool, Brightness float, Contrast float, Gamma float,"
6247 "VolumeAmplification float, AudioDelay float, ResumeTime integer,"
6248 "Sharpness float, NoiseReduction float, NonLinStretch bool, PostProcess bool,"
6249 "ScalingMethod integer, DeinterlaceMode integer, StereoMode integer, StereoInvert bool, VideoStream integer)");
6250 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");
6251 m_pDS
->exec("DROP TABLE settingsold");
6256 m_pDS
->exec("ALTER TABLE settings ADD TonemapMethod integer");
6257 m_pDS
->exec("ALTER TABLE settings ADD TonemapParam float");
6261 m_pDS
->exec("ALTER TABLE settings ADD Orientation integer");
6264 m_pDS
->exec("ALTER TABLE settings ADD CenterMixLevel integer");
6268 // fb9c25f5 and e5f6d204 changed the behavior of path splitting for plugin URIs (previously it would only use the root)
6269 // Re-split paths for plugin files in order to maintain watched state etc.
6270 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://%'");
6271 while (!m_pDS
->eof())
6273 std::string path
, fn
;
6274 SplitPath(m_pDS
->fv(1).get_asString(), path
, fn
);
6275 if (path
!= m_pDS
->fv(2).get_asString())
6278 m_pDS2
->query(PrepareSQL("SELECT idPath FROM path WHERE strPath='%s'", path
.c_str()));
6280 pathid
= m_pDS2
->fv(0).get_asInt();
6284 std::string parent
= URIUtils::GetParentPath(path
);
6286 m_pDS2
->query(PrepareSQL("SELECT idPath FROM path WHERE strPath='%s'", parent
.c_str()));
6288 parentid
= m_pDS2
->fv(0).get_asInt();
6292 m_pDS2
->exec(PrepareSQL("INSERT INTO path (strPath) VALUES ('%s')", parent
.c_str()));
6293 parentid
= (int)m_pDS2
->lastinsertid();
6295 m_pDS2
->exec(PrepareSQL("INSERT INTO path (strPath, idParentPath) VALUES ('%s', %i)", path
.c_str(), parentid
));
6296 pathid
= (int)m_pDS2
->lastinsertid();
6298 m_pDS2
->query(PrepareSQL("SELECT idFile FROM files WHERE strFileName='%s' AND idPath=%i", fn
.c_str(), pathid
));
6299 bool exists
= !m_pDS2
->eof();
6302 m_pDS2
->exec(PrepareSQL("DELETE FROM files WHERE idFile=%i", m_pDS
->fv(0).get_asInt()));
6304 m_pDS2
->exec(PrepareSQL("UPDATE files SET idPath=%i WHERE idFile=%i", pathid
, m_pDS
->fv(0).get_asInt()));
6312 m_pDS
->exec("ALTER TABLE path ADD allAudio bool");
6315 m_pDS
->exec("ALTER TABLE streamdetails ADD strHdrType text");
6319 // https://github.com/xbmc/xbmc/issues/21253 - Kodi picks up wrong "year" for PVR recording.
6321 m_pDS
->query("SELECT idFile, strFilename FROM files WHERE strFilename LIKE '% (1969)%.pvr' OR "
6322 "strFilename LIKE '% (1601)%.pvr'");
6323 while (!m_pDS
->eof())
6325 std::string fixedFileName
= m_pDS
->fv(1).get_asString();
6326 size_t pos
= fixedFileName
.find(" (1969)");
6327 if (pos
== std::string::npos
)
6328 pos
= fixedFileName
.find(" (1601)");
6330 if (pos
!= std::string::npos
)
6332 fixedFileName
.erase(pos
, 7);
6334 m_pDS2
->exec(PrepareSQL("UPDATE files SET strFilename='%s' WHERE idFile=%i",
6335 fixedFileName
.c_str(), m_pDS
->fv(0).get_asInt()));
6344 // create videoversiontype table
6345 m_pDS
->exec("CREATE TABLE videoversiontype (id INTEGER PRIMARY KEY, name TEXT, owner INTEGER)");
6346 InitializeVideoVersionTypeTable(iVersion
);
6348 // create videoversion table
6349 m_pDS
->exec("CREATE TABLE videoversion (idFile INTEGER PRIMARY KEY, idMedia INTEGER, mediaType "
6350 "TEXT, itemType INTEGER, idType INTEGER)");
6351 m_pDS
->exec(PrepareSQL(
6352 "INSERT INTO videoversion SELECT idFile, idMovie, 'movie', '%i', '%i' FROM movie",
6353 VideoAssetType::VERSION
, VIDEO_VERSION_ID_DEFAULT
));
6358 m_pDS
->exec("ALTER TABLE videoversiontype ADD itemType INTEGER");
6360 // First, assume all types are video version types
6361 m_pDS
->exec(PrepareSQL("UPDATE videoversiontype SET itemType = %i", VideoAssetType::VERSION
));
6363 // Then, check current extras entries and their assigned item type and migrate it
6365 // get all assets with extras item type
6366 m_pDS
->query("SELECT DISTINCT idType FROM videoversion WHERE itemType = 1");
6367 while (!m_pDS
->eof())
6369 const int idType
{m_pDS
->fv(0).get_asInt()};
6370 if (idType
> VIDEO_VERSION_ID_END
)
6372 // user-added type for extras. change its item type to extras
6373 m_pDS2
->exec(PrepareSQL("UPDATE videoversiontype SET itemType = %i WHERE id = %i",
6374 VideoAssetType::EXTRA
, idType
));
6378 // system type used for an extra. copy as extras item type.
6380 PrepareSQL("SELECT itemType, name FROM videoversiontype WHERE id = %i", idType
));
6381 if (m_pDS2
->fv(0).get_asInt() == 0)
6383 // currently a versions type, create a corresponding user-added type for extras
6384 m_pDS2
->exec(PrepareSQL(
6385 "INSERT INTO videoversiontype (id, name, owner, itemType) VALUES(NULL, '%s', %i, %i)",
6386 m_pDS2
->fv(1).get_asString().c_str(), VideoAssetTypeOwner::USER
,
6387 VideoAssetType::EXTRA
));
6389 // update the respective extras to use the new extras type
6390 const int newId
{static_cast<int>(m_pDS2
->lastinsertid())};
6392 PrepareSQL("UPDATE videoversion SET idType = %i WHERE itemType = 1 AND idType = %i",
6403 m_pDS
->exec("CREATE TABLE videoversion_new "
6404 "(idFile INTEGER PRIMARY KEY, idMedia INTEGER, media_type TEXT, "
6405 " itemType INTEGER, idType INTEGER)");
6406 m_pDS
->exec("INSERT INTO videoversion_new "
6407 " (idFile, idMedia, media_type, itemType, idType) "
6408 "SELECT idFile, idMedia, mediaType, itemType, idType FROM videoversion");
6409 m_pDS
->exec("DROP TABLE videoversion");
6410 m_pDS
->exec("ALTER TABLE videoversion_new RENAME TO videoversion");
6412 // Fix gap in the migration to videodb v127 for unused user-defined video version types.
6413 // Unfortunately due to original design we cannot tell which ones were movie versions or
6414 // extras and now they're all displayed in the version type selection for movies.
6415 // Remove them all as the better fix of providing a GUI to manage version types will not be
6416 // available in Omega v21. That implies the loss of the unused user-defined version names
6417 // created since v21 beta 2.
6418 m_pDS2
->exec(PrepareSQL("DELETE FROM videoversiontype "
6419 "WHERE id NOT IN (SELECT idType FROM videoversion) "
6421 "AND itemType = %i",
6422 VideoAssetTypeOwner::USER
, VideoAssetType::VERSION
));
6427 // Remove quality-like predefined version types
6429 // Retrieve current utilization per type
6430 m_pDS
->query("SELECT vvt.id, vvt.name, count(vv.idType) "
6431 "FROM videoversiontype vvt "
6432 " LEFT JOIN videoversion vv ON vvt.id = vv.idType "
6433 "WHERE vvt.id = 40405 OR vvt.id BETWEEN 40418 AND 40430 "
6436 while (!m_pDS
->eof())
6438 const int typeId
{m_pDS
->fv(0).get_asInt()};
6439 const std::string typeName
{m_pDS
->fv(1).get_asString()};
6440 const int versionsCount
{m_pDS
->fv(2).get_asInt()};
6442 if (versionsCount
> 0)
6444 // type used by some versions, recreate as user type and link the versions to the new id
6445 m_pDS2
->exec(PrepareSQL(
6446 "INSERT INTO videoversiontype (id, name, owner, itemType) VALUES(NULL, '%s', %i, %i)",
6447 typeName
.c_str(), VideoAssetTypeOwner::USER
, VideoAssetType::VERSION
));
6449 const int newId
{static_cast<int>(m_pDS2
->lastinsertid())};
6452 PrepareSQL("UPDATE videoversion SET idType = %i WHERE idType = %i", newId
, typeId
));
6454 m_pDS2
->exec(PrepareSQL("DELETE FROM videoversiontype WHERE id = %i", typeId
));
6462 // Remove episodes with invalid idSeason values.
6463 // Since 2015 they were masked from episode_view and are not going to be missed.
6464 // Those records would be misses in database converted in 2015 (see videodb version 99).
6466 m_pDS
->exec("DELETE FROM episode WHERE idSeason NOT IN (SELECT idSeason from seasons)");
6470 int CVideoDatabase::GetSchemaVersion() const
6475 bool CVideoDatabase::LookupByFolders(const std::string
&path
, bool shows
)
6477 SScanSettings settings
;
6478 bool foundDirectly
= false;
6479 ScraperPtr scraper
= GetScraperForPath(path
, settings
, foundDirectly
);
6480 if (scraper
&& scraper
->Content() == CONTENT_TVSHOWS
&& !shows
)
6481 return false; // episodes
6482 return settings
.parent_name_root
; // shows, movies, musicvids
6485 bool CVideoDatabase::GetPlayCounts(const std::string
&strPath
, CFileItemList
&items
)
6487 if(URIUtils::IsMultiPath(strPath
))
6489 std::vector
<std::string
> paths
;
6490 CMultiPathDirectory::GetPaths(strPath
, paths
);
6493 for(unsigned i
=0;i
<paths
.size();i
++)
6494 ret
|= GetPlayCounts(paths
[i
], items
);
6499 if (!URIUtils::IsPlugin(strPath
))
6501 pathID
= GetPathId(strPath
);
6503 return false; // path (and thus files) aren't in the database
6509 if (nullptr == m_pDB
)
6511 if (nullptr == m_pDS
)
6516 " files.strFilename, files.playCount,"
6517 " bookmark.timeInSeconds, bookmark.totalTimeInSeconds "
6519 " LEFT JOIN bookmark ON"
6520 " files.idFile = bookmark.idFile AND bookmark.type = %i ";
6522 if (URIUtils::IsPlugin(strPath
))
6524 for (auto& item
: items
)
6526 if (!item
|| item
->m_bIsFolder
|| !item
->GetProperty("IsPlayable").asBoolean())
6529 std::string path
, filename
;
6530 SplitPath(item
->GetPath(), path
, filename
);
6531 m_pDS
->query(PrepareSQL(sql
+
6532 "INNER JOIN path ON files.idPath = path.idPath "
6533 "WHERE files.strFilename='%s' AND path.strPath='%s'",
6534 (int)CBookmark::RESUME
, filename
.c_str(), path
.c_str()));
6538 if (!item
->GetVideoInfoTag()->IsPlayCountSet())
6539 item
->GetVideoInfoTag()->SetPlayCount(m_pDS
->fv(1).get_asInt());
6540 if (!item
->GetVideoInfoTag()->GetResumePoint().IsSet())
6541 item
->GetVideoInfoTag()->SetResumePoint(m_pDS
->fv(2).get_asInt(), m_pDS
->fv(3).get_asInt(), "");
6548 //! @todo also test a single query for the above and below
6549 sql
= PrepareSQL(sql
+ "WHERE files.idPath=%i", (int)CBookmark::RESUME
, pathID
);
6551 if (RunQuery(sql
) <= 0)
6554 items
.SetFastLookup(true); // note: it's possibly quicker the other way around (map on db returned items)?
6555 while (!m_pDS
->eof())
6558 ConstructPath(path
, strPath
, m_pDS
->fv(0).get_asString());
6559 CFileItemPtr item
= items
.Get(path
);
6562 if (!items
.IsPlugin() || !item
->GetVideoInfoTag()->IsPlayCountSet())
6563 item
->GetVideoInfoTag()->SetPlayCount(m_pDS
->fv(1).get_asInt());
6565 if (!item
->GetVideoInfoTag()->GetResumePoint().IsSet())
6567 item
->GetVideoInfoTag()->SetResumePoint(m_pDS
->fv(2).get_asInt(), m_pDS
->fv(3).get_asInt(), "");
6578 CLog::Log(LOGERROR
, "{} failed", __FUNCTION__
);
6583 int CVideoDatabase::GetPlayCount(int iFileId
)
6586 return 0; // not in db, so not watched
6591 if (nullptr == m_pDB
)
6593 if (nullptr == m_pDS
)
6596 std::string strSQL
= PrepareSQL("select playCount from files WHERE idFile=%i", iFileId
);
6598 if (m_pDS
->query(strSQL
))
6600 // there should only ever be one row returned
6601 if (m_pDS
->num_rows() == 1)
6602 count
= m_pDS
->fv(0).get_asInt();
6609 CLog::Log(LOGERROR
, "{} failed", __FUNCTION__
);
6614 int CVideoDatabase::GetPlayCount(const std::string
& strFilenameAndPath
)
6616 return GetPlayCount(GetFileId(strFilenameAndPath
));
6619 int CVideoDatabase::GetPlayCount(const CFileItem
&item
)
6621 if (IsBlurayPlaylist(item
))
6622 return GetPlayCount(GetFileId(item
.GetDynPath()));
6624 return GetPlayCount(GetFileId(item
));
6627 CDateTime
CVideoDatabase::GetLastPlayed(int iFileId
)
6630 return {}; // not in db, so not watched
6635 if (nullptr == m_pDB
)
6637 if (nullptr == m_pDS
)
6640 std::string strSQL
= PrepareSQL("select lastPlayed from files WHERE idFile=%i", iFileId
);
6641 CDateTime lastPlayed
;
6642 if (m_pDS
->query(strSQL
))
6644 // there should only ever be one row returned
6645 if (m_pDS
->num_rows() == 1)
6646 lastPlayed
.SetFromDBDateTime(m_pDS
->fv(0).get_asString());
6653 CLog::Log(LOGERROR
, "{} failed", __FUNCTION__
);
6658 CDateTime
CVideoDatabase::GetLastPlayed(const std::string
& strFilenameAndPath
)
6660 return GetLastPlayed(GetFileId(strFilenameAndPath
));
6663 void CVideoDatabase::UpdateFanart(const CFileItem
& item
, VideoDbContentType type
)
6665 if (nullptr == m_pDB
)
6667 if (nullptr == m_pDS
)
6669 if (!item
.HasVideoInfoTag() || item
.GetVideoInfoTag()->m_iDbId
< 0) return;
6672 if (type
== VideoDbContentType::TVSHOWS
)
6673 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
);
6674 else if (type
== VideoDbContentType::MOVIES
)
6675 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
);
6681 if (type
== VideoDbContentType::TVSHOWS
)
6682 AnnounceUpdate(MediaTypeTvShow
, item
.GetVideoInfoTag()->m_iDbId
);
6683 else if (type
== VideoDbContentType::MOVIES
)
6684 AnnounceUpdate(MediaTypeMovie
, item
.GetVideoInfoTag()->m_iDbId
);
6688 CLog::Log(LOGERROR
, "{} - error updating fanart for {}", __FUNCTION__
, item
.GetPath());
6692 CDateTime
CVideoDatabase::SetPlayCount(const CFileItem
& item
, int count
, const CDateTime
& date
)
6695 if (IsBlurayPlaylist(item
))
6696 id
= AddFile(item
.GetDynPath());
6697 else if (item
.HasProperty("original_listitem_url") &&
6698 URIUtils::IsPlugin(item
.GetProperty("original_listitem_url").asString()))
6700 CFileItem
item2(item
);
6701 item2
.SetPath(item
.GetProperty("original_listitem_url").asString());
6702 id
= AddFile(item2
);
6709 // and mark as watched
6712 const CDateTime
lastPlayed(date
.IsValid() ? date
: CDateTime::GetCurrentDateTime());
6714 if (nullptr == m_pDB
)
6716 if (nullptr == m_pDS
)
6722 strSQL
= PrepareSQL("update files set playCount=%i,lastPlayed='%s' where idFile=%i", count
,
6723 lastPlayed
.GetAsDBDateTime().c_str(), id
);
6727 if (!date
.IsValid())
6728 strSQL
= PrepareSQL("update files set playCount=NULL,lastPlayed=NULL where idFile=%i", id
);
6730 strSQL
= PrepareSQL("update files set playCount=NULL,lastPlayed='%s' where idFile=%i",
6731 lastPlayed
.GetAsDBDateTime().c_str(), id
);
6734 m_pDS
->exec(strSQL
);
6736 // We only need to announce changes to video items in the library
6737 if (item
.HasVideoInfoTag() && item
.GetVideoInfoTag()->m_iDbId
> 0)
6740 if (CVideoLibraryQueue::GetInstance().IsScanningLibrary())
6741 data
["transaction"] = true;
6742 // Only provide the "playcount" value if it has actually changed
6743 if (item
.GetVideoInfoTag()->GetPlayCount() != count
)
6744 data
["playcount"] = count
;
6745 CServiceBroker::GetAnnouncementManager()->Announce(ANNOUNCEMENT::VideoLibrary
, "OnUpdate",
6746 std::make_shared
<CFileItem
>(item
), data
);
6753 CLog::Log(LOGERROR
, "{} failed", __FUNCTION__
);
6759 CDateTime
CVideoDatabase::IncrementPlayCount(const CFileItem
& item
)
6761 return SetPlayCount(item
, GetPlayCount(item
) + 1);
6764 CDateTime
CVideoDatabase::UpdateLastPlayed(const CFileItem
& item
)
6766 return SetPlayCount(item
, GetPlayCount(item
), CDateTime::GetCurrentDateTime());
6769 void CVideoDatabase::UpdateMovieTitle(int idMovie
,
6770 const std::string
& strNewMovieTitle
,
6771 VideoDbContentType iType
)
6775 if (nullptr == m_pDB
)
6777 if (nullptr == m_pDS
)
6779 std::string content
;
6780 if (iType
== VideoDbContentType::MOVIES
)
6782 CLog::Log(LOGINFO
, "Changing Movie:id:{} New Title:{}", idMovie
, strNewMovieTitle
);
6783 content
= MediaTypeMovie
;
6785 else if (iType
== VideoDbContentType::EPISODES
)
6787 CLog::Log(LOGINFO
, "Changing Episode:id:{} New Title:{}", idMovie
, strNewMovieTitle
);
6788 content
= MediaTypeEpisode
;
6790 else if (iType
== VideoDbContentType::TVSHOWS
)
6792 CLog::Log(LOGINFO
, "Changing TvShow:id:{} New Title:{}", idMovie
, strNewMovieTitle
);
6793 content
= MediaTypeTvShow
;
6795 else if (iType
== VideoDbContentType::MUSICVIDEOS
)
6797 CLog::Log(LOGINFO
, "Changing MusicVideo:id:{} New Title:{}", idMovie
, strNewMovieTitle
);
6798 content
= MediaTypeMusicVideo
;
6800 else if (iType
== VideoDbContentType::MOVIE_SETS
)
6802 CLog::Log(LOGINFO
, "Changing Movie set:id:{} New Title:{}", idMovie
, strNewMovieTitle
);
6803 std::string strSQL
= PrepareSQL("UPDATE sets SET strSet='%s' WHERE idSet=%i", strNewMovieTitle
.c_str(), idMovie
);
6804 m_pDS
->exec(strSQL
);
6807 if (!content
.empty())
6809 SetSingleValue(iType
, idMovie
, FieldTitle
, strNewMovieTitle
);
6810 AnnounceUpdate(content
, idMovie
);
6817 "{} (int idMovie, const std::string& strNewMovieTitle) failed on MovieID:{} and Title:{}",
6818 __FUNCTION__
, idMovie
, strNewMovieTitle
);
6822 bool CVideoDatabase::UpdateVideoSortTitle(int idDb
,
6823 const std::string
& strNewSortTitle
,
6824 VideoDbContentType iType
/* = MOVIES */)
6828 if (nullptr == m_pDB
|| nullptr == m_pDS
)
6830 if (iType
!= VideoDbContentType::MOVIES
&& iType
!= VideoDbContentType::TVSHOWS
)
6833 std::string content
= MediaTypeMovie
;
6834 if (iType
== VideoDbContentType::TVSHOWS
)
6835 content
= MediaTypeTvShow
;
6837 if (SetSingleValue(iType
, idDb
, FieldSortTitle
, strNewSortTitle
))
6839 AnnounceUpdate(content
, idDb
);
6846 "{} (int idDb, const std::string& strNewSortTitle, VIDEODB_CONTENT_TYPE iType) "
6847 "failed on ID: {} and Sort Title: {}",
6848 __FUNCTION__
, idDb
, strNewSortTitle
);
6854 /// \brief EraseVideoSettings() Erases the videoSettings table and reconstructs it
6855 void CVideoDatabase::EraseVideoSettings(const CFileItem
&item
)
6857 int idFile
= GetFileId(item
);
6863 std::string sql
= PrepareSQL("DELETE FROM settings WHERE idFile=%i", idFile
);
6865 CLog::Log(LOGINFO
, "Deleting settings information for files {}",
6866 CURL::GetRedacted(item
.GetPath()));
6871 CLog::Log(LOGERROR
, "{} failed", __FUNCTION__
);
6875 void CVideoDatabase::EraseAllVideoSettings()
6879 std::string sql
= "DELETE FROM settings";
6881 CLog::Log(LOGINFO
, "Deleting all video settings");
6886 CLog::Log(LOGERROR
, "{} failed", __FUNCTION__
);
6890 void CVideoDatabase::EraseAllVideoSettings(const std::string
& path
)
6892 std::string itemsToDelete
;
6896 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());
6898 while (!m_pDS
->eof())
6900 std::string file
= m_pDS
->fv("files.idFile").get_asString() + ",";
6901 itemsToDelete
+= file
;
6906 if (!itemsToDelete
.empty())
6908 itemsToDelete
= "(" + StringUtils::TrimRight(itemsToDelete
, ",") + ")";
6910 sql
= "DELETE FROM settings WHERE idFile IN " + itemsToDelete
;
6916 CLog::Log(LOGERROR
, "{} failed", __FUNCTION__
);
6920 bool CVideoDatabase::GetGenresNav(const std::string
& strBaseDir
,
6921 CFileItemList
& items
,
6922 VideoDbContentType idContent
/* = UNKNOWN */,
6923 const Filter
& filter
/* = Filter() */,
6924 bool countOnly
/* = false */)
6926 return GetNavCommon(strBaseDir
, items
, "genre", idContent
, filter
, countOnly
);
6929 bool CVideoDatabase::GetCountriesNav(const std::string
& strBaseDir
,
6930 CFileItemList
& items
,
6931 VideoDbContentType idContent
/* = UNKNOWN */,
6932 const Filter
& filter
/* = Filter() */,
6933 bool countOnly
/* = false */)
6935 return GetNavCommon(strBaseDir
, items
, "country", idContent
, filter
, countOnly
);
6938 bool CVideoDatabase::GetStudiosNav(const std::string
& strBaseDir
,
6939 CFileItemList
& items
,
6940 VideoDbContentType idContent
/* = UNKNOWN */,
6941 const Filter
& filter
/* = Filter() */,
6942 bool countOnly
/* = false */)
6944 return GetNavCommon(strBaseDir
, items
, "studio", idContent
, filter
, countOnly
);
6947 bool CVideoDatabase::GetNavCommon(const std::string
& strBaseDir
,
6948 CFileItemList
& items
,
6950 VideoDbContentType idContent
/* = UNKNOWN */,
6951 const Filter
& filter
/* = Filter() */,
6952 bool countOnly
/* = false */)
6956 if (nullptr == m_pDB
)
6958 if (nullptr == m_pDS
)
6962 Filter extFilter
= filter
;
6963 if (m_profileManager
.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE
&& !g_passwordManager
.bMasterUser
)
6965 std::string view
, view_id
, media_type
, extraField
, extraJoin
;
6966 if (idContent
== VideoDbContentType::MOVIES
)
6968 view
= MediaTypeMovie
;
6969 view_id
= "idMovie";
6970 media_type
= MediaTypeMovie
;
6971 extraField
= "files.playCount";
6973 else if (idContent
== VideoDbContentType::TVSHOWS
) //this will not get tvshows with 0 episodes
6975 view
= MediaTypeEpisode
;
6977 media_type
= MediaTypeTvShow
;
6978 // in order to make use of FieldPlaycount in smart playlists we need an extra join
6979 if (StringUtils::EqualsNoCase(type
, "tag"))
6980 extraJoin
= PrepareSQL("JOIN tvshow_view ON tvshow_view.idShow = tag_link.media_id AND tag_link.media_type='tvshow'");
6982 else if (idContent
== VideoDbContentType::MUSICVIDEOS
)
6984 view
= MediaTypeMusicVideo
;
6985 view_id
= "idMVideo";
6986 media_type
= MediaTypeMusicVideo
;
6987 extraField
= "files.playCount";
6992 strSQL
= "SELECT {} " + PrepareSQL("FROM %s ", type
);
6993 extFilter
.fields
= PrepareSQL("%s.%s_id, %s.name, path.strPath", type
, type
, type
);
6994 extFilter
.AppendField(extraField
);
6995 extFilter
.AppendJoin(PrepareSQL("JOIN %s_link ON %s.%s_id = %s_link.%s_id", type
, type
, type
, type
, type
));
6996 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()));
6997 extFilter
.AppendJoin(PrepareSQL("JOIN files ON files.idFile = %s_view.idFile", view
.c_str()));
6998 extFilter
.AppendJoin("JOIN path ON path.idPath = files.idPath");
6999 extFilter
.AppendJoin(extraJoin
);
7003 std::string view
, view_id
, media_type
, extraField
, extraJoin
;
7004 if (idContent
== VideoDbContentType::MOVIES
)
7006 view
= MediaTypeMovie
;
7007 view_id
= "idMovie";
7008 media_type
= MediaTypeMovie
;
7009 extraField
= "count(1), count(files.playCount)";
7010 extraJoin
= PrepareSQL("JOIN files ON files.idFile = %s_view.idFile", view
.c_str());
7012 else if (idContent
== VideoDbContentType::TVSHOWS
)
7014 view
= MediaTypeTvShow
;
7016 media_type
= MediaTypeTvShow
;
7018 else if (idContent
== VideoDbContentType::MUSICVIDEOS
)
7020 view
= MediaTypeMusicVideo
;
7021 view_id
= "idMVideo";
7022 media_type
= MediaTypeMusicVideo
;
7023 extraField
= "count(1), count(files.playCount)";
7024 extraJoin
= PrepareSQL("JOIN files ON files.idFile = %s_view.idFile", view
.c_str());
7029 strSQL
= "SELECT {} " + PrepareSQL("FROM %s ", type
);
7030 extFilter
.fields
= PrepareSQL("%s.%s_id, %s.name", type
, type
, type
);
7031 extFilter
.AppendField(extraField
);
7032 extFilter
.AppendJoin(PrepareSQL("JOIN %s_link ON %s.%s_id = %s_link.%s_id", type
, type
, type
, type
, type
));
7033 extFilter
.AppendJoin(PrepareSQL("JOIN %s_view ON %s_link.media_id = %s_view.%s AND %s_link.media_type='%s'",
7034 view
.c_str(), type
, view
.c_str(), view_id
.c_str(), type
, media_type
.c_str()));
7035 extFilter
.AppendJoin(extraJoin
);
7036 extFilter
.AppendGroup(PrepareSQL("%s.%s_id", type
, type
));
7041 extFilter
.fields
= PrepareSQL("COUNT(DISTINCT %s.%s_id)", type
, type
);
7042 extFilter
.group
.clear();
7043 extFilter
.order
.clear();
7045 strSQL
= StringUtils::Format(strSQL
, !extFilter
.fields
.empty() ? extFilter
.fields
: "*");
7047 CVideoDbUrl videoUrl
;
7048 if (!BuildSQL(strBaseDir
, strSQL
, extFilter
, strSQL
, videoUrl
))
7051 int iRowsFound
= RunQuery(strSQL
);
7052 if (iRowsFound
<= 0)
7053 return iRowsFound
== 0;
7057 CFileItemPtr
pItem(new CFileItem());
7058 pItem
->SetProperty("total", iRowsFound
== 1 ? m_pDS
->fv(0).get_asInt() : iRowsFound
);
7065 if (m_profileManager
.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE
&& !g_passwordManager
.bMasterUser
)
7067 std::map
<int, std::pair
<std::string
,int> > mapItems
;
7068 while (!m_pDS
->eof())
7070 int id
= m_pDS
->fv(0).get_asInt();
7071 std::string str
= m_pDS
->fv(1).get_asString();
7073 // was this already found?
7074 auto it
= mapItems
.find(id
);
7075 if (it
== mapItems
.end())
7078 if (g_passwordManager
.IsDatabasePathUnlocked(m_pDS
->fv(2).get_asString(),*CMediaSourceSettings::GetInstance().GetSources("video")))
7080 if (idContent
== VideoDbContentType::MOVIES
||
7081 idContent
== VideoDbContentType::MUSICVIDEOS
)
7082 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
7083 else if (idContent
== VideoDbContentType::TVSHOWS
)
7084 mapItems
.insert(std::pair
<int, std::pair
<std::string
,int> >(id
, std::pair
<std::string
,int>(str
,0)));
7091 for (const auto &i
: mapItems
)
7093 CFileItemPtr
pItem(new CFileItem(i
.second
.first
));
7094 pItem
->GetVideoInfoTag()->m_iDbId
= i
.first
;
7095 pItem
->GetVideoInfoTag()->m_type
= type
;
7097 CVideoDbUrl itemUrl
= videoUrl
;
7098 std::string path
= StringUtils::Format("{}/", i
.first
);
7099 itemUrl
.AppendPath(path
);
7100 pItem
->SetPath(itemUrl
.ToString());
7102 pItem
->m_bIsFolder
= true;
7103 if (idContent
== VideoDbContentType::MOVIES
|| idContent
== VideoDbContentType::MUSICVIDEOS
)
7104 pItem
->GetVideoInfoTag()->SetPlayCount(i
.second
.second
);
7105 if (!items
.Contains(pItem
->GetPath()))
7107 pItem
->SetLabelPreformatted(true);
7114 while (!m_pDS
->eof())
7116 CFileItemPtr
pItem(new CFileItem(m_pDS
->fv(1).get_asString()));
7117 pItem
->GetVideoInfoTag()->m_iDbId
= m_pDS
->fv(0).get_asInt();
7118 pItem
->GetVideoInfoTag()->m_type
= type
;
7120 CVideoDbUrl itemUrl
= videoUrl
;
7121 std::string path
= StringUtils::Format("{}/", m_pDS
->fv(0).get_asInt());
7122 itemUrl
.AppendPath(path
);
7123 pItem
->SetPath(itemUrl
.ToString());
7125 pItem
->m_bIsFolder
= true;
7126 pItem
->SetLabelPreformatted(true);
7127 if (idContent
== VideoDbContentType::MOVIES
|| idContent
== VideoDbContentType::MUSICVIDEOS
)
7128 { // fv(3) is the number of videos watched, fv(2) is the total number. We set the playcount
7129 // only if the number of videos watched is equal to the total number (i.e. every video watched)
7130 pItem
->GetVideoInfoTag()->SetPlayCount((m_pDS
->fv(3).get_asInt() == m_pDS
->fv(2).get_asInt()) ? 1 : 0);
7141 CLog::Log(LOGERROR
, "{} failed", __FUNCTION__
);
7146 bool CVideoDatabase::GetTagsNav(const std::string
& strBaseDir
,
7147 CFileItemList
& items
,
7148 VideoDbContentType idContent
/* = UNKNOWN */,
7149 const Filter
& filter
/* = Filter() */,
7150 bool countOnly
/* = false */)
7152 return GetNavCommon(strBaseDir
, items
, "tag", idContent
, filter
, countOnly
);
7155 bool CVideoDatabase::GetSetsNav(const std::string
& strBaseDir
,
7156 CFileItemList
& items
,
7157 VideoDbContentType idContent
/* = UNKNOWN */,
7158 const Filter
& filter
/* = Filter() */,
7159 bool ignoreSingleMovieSets
/* = false */)
7161 if (idContent
!= VideoDbContentType::MOVIES
)
7164 return GetSetsByWhere(strBaseDir
, filter
, items
, ignoreSingleMovieSets
);
7167 bool CVideoDatabase::GetSetsByWhere(const std::string
& strBaseDir
, const Filter
&filter
, CFileItemList
& items
, bool ignoreSingleMovieSets
/* = false */)
7171 if (nullptr == m_pDB
)
7173 if (nullptr == m_pDS
)
7176 CVideoDbUrl videoUrl
;
7177 if (!videoUrl
.FromString(strBaseDir
))
7180 Filter setFilter
= filter
;
7181 setFilter
.join
+= " JOIN sets ON movie_view.idSet = sets.idSet";
7182 if (!setFilter
.order
.empty())
7183 setFilter
.order
+= ",";
7184 setFilter
.order
+= "sets.idSet";
7186 if (!GetMoviesByWhere(strBaseDir
, setFilter
, items
))
7190 GroupAttribute groupingAttributes
;
7191 const CUrlOptions::UrlOptions
& options
= videoUrl
.GetOptions();
7192 auto option
= options
.find("ignoreSingleMovieSets");
7194 if (option
!= options
.end())
7196 groupingAttributes
=
7197 option
->second
.asBoolean() ? GroupAttributeIgnoreSingleItems
: GroupAttributeNone
;
7201 groupingAttributes
=
7202 ignoreSingleMovieSets
? GroupAttributeIgnoreSingleItems
: GroupAttributeNone
;
7205 if (!GroupUtils::Group(GroupBySet
, strBaseDir
, items
, sets
, groupingAttributes
))
7215 CLog::Log(LOGERROR
, "{} failed", __FUNCTION__
);
7220 bool CVideoDatabase::GetMusicVideoAlbumsNav(const std::string
& strBaseDir
, CFileItemList
& items
, int idArtist
/* = -1 */, const Filter
&filter
/* = Filter() */, bool countOnly
/* = false */)
7224 if (nullptr == m_pDB
)
7226 if (nullptr == m_pDS
)
7229 CVideoDbUrl videoUrl
;
7230 if (!videoUrl
.FromString(strBaseDir
))
7233 std::string strSQL
= "select {} from musicvideo_view ";
7234 Filter extFilter
= filter
;
7235 extFilter
.fields
= PrepareSQL("musicvideo_view.c%02d, musicvideo_view.idMVideo, actor.name, "
7236 "musicvideo_view.c%02d, musicvideo_view.c%02d, musicvideo_view.c%02d ",
7237 VIDEODB_ID_MUSICVIDEO_ALBUM
, VIDEODB_ID_MUSICVIDEO_TITLE
,
7238 VIDEODB_ID_MUSICVIDEO_PLOT
, VIDEODB_ID_MUSICVIDEO_ARTIST
);
7239 extFilter
.AppendJoin(
7240 PrepareSQL("JOIN actor_link ON actor_link.media_id=musicvideo_view.idMVideo "));
7241 extFilter
.AppendJoin(PrepareSQL("JOIN actor ON actor.actor_id = actor_link.actor_id"));
7242 extFilter
.fields
+= ", path.strPath";
7243 extFilter
.AppendJoin("join files on files.idFile = musicvideo_view.idFile join path on path.idPath = files.idPath");
7245 if (StringUtils::EndsWith(strBaseDir
,"albums/"))
7246 extFilter
.AppendWhere(PrepareSQL("musicvideo_view.c%02d != ''", VIDEODB_ID_MUSICVIDEO_ALBUM
));
7249 videoUrl
.AddOption("artistid", idArtist
);
7251 extFilter
.AppendGroup(PrepareSQL(" CASE WHEN musicvideo_view.c09 !='' THEN musicvideo_view.c09 "
7252 "ELSE musicvideo_view.c00 END"));
7256 extFilter
.fields
= "COUNT(1)";
7257 extFilter
.group
.clear();
7258 extFilter
.order
.clear();
7260 strSQL
= StringUtils::Format(strSQL
, !extFilter
.fields
.empty() ? extFilter
.fields
: "*");
7262 if (!BuildSQL(videoUrl
.ToString(), strSQL
, extFilter
, strSQL
, videoUrl
))
7265 int iRowsFound
= RunQuery(strSQL
);
7266 /* fields returned by query are :-
7267 (0) - Album title (if any)
7270 (3) - Music video title
7271 (4) - Music video plot
7272 (5) - Music Video artist
7275 if (iRowsFound
<= 0)
7276 return iRowsFound
== 0;
7278 std::string strArtist
;
7280 strArtist
= m_pDS
->fv("actor.name").get_asString();
7284 CFileItemPtr
pItem(new CFileItem());
7285 pItem
->SetProperty("total", iRowsFound
== 1 ? m_pDS
->fv(0).get_asInt() : iRowsFound
);
7292 std::list
<int> idMVideoList
;
7293 std::list
<std::pair
<std::string
, std::string
>> idData
;
7295 while (!m_pDS
->eof())
7297 bool isAlbum
= true;
7298 std::string strAlbum
= m_pDS
->fv(0).get_asString(); //Album title
7299 int idMVideo
= m_pDS
->fv(1).get_asInt();
7300 if (strAlbum
.empty())
7302 strAlbum
= m_pDS
->fv(3).get_asString(); // video title if not an album
7306 CFileItemPtr
pItem(new CFileItem(strAlbum
));
7308 CVideoDbUrl itemUrl
= videoUrl
;
7309 std::string path
= StringUtils::Format("{}/", idMVideo
);
7312 itemUrl
.AddOption("albumid", idMVideo
);
7313 path
+= std::to_string(idMVideo
);
7315 strSQL
= PrepareSQL(
7316 "SELECT type, url FROM art WHERE media_id = %i AND media_type = 'musicvideo'",
7318 m_pDS2
->query(strSQL
);
7319 while (!m_pDS2
->eof())
7321 pItem
->SetArt(m_pDS2
->fv(0).get_asString(), m_pDS2
->fv(1).get_asString());
7326 itemUrl
.AppendPath(path
);
7327 pItem
->SetPath(itemUrl
.ToString());
7328 pItem
->m_bIsFolder
= isAlbum
;
7329 pItem
->SetLabelPreformatted(true);
7331 if (!items
.Contains(pItem
->GetPath()))
7332 if (g_passwordManager
.IsDatabasePathUnlocked(
7333 m_pDS
->fv("path.strPath").get_asString(),
7334 *CMediaSourceSettings::GetInstance().GetSources("video")))
7336 pItem
->GetVideoInfoTag()->m_artist
.emplace_back(strArtist
);
7337 pItem
->GetVideoInfoTag()->m_iDbId
= idMVideo
;
7339 idMVideoList
.push_back(idMVideo
);
7340 idData
.emplace_back(m_pDS
->fv(0).get_asString(), m_pDS
->fv(5).get_asString());
7346 for (int i
= 0; i
< items
.Size(); i
++)
7348 CVideoInfoTag details
;
7350 if (items
[i
]->m_bIsFolder
)
7352 details
.SetPath(items
[i
]->GetPath());
7353 details
.m_strAlbum
= idData
.front().first
;
7354 details
.m_type
= MediaTypeAlbum
;
7355 details
.m_artist
.emplace_back(idData
.front().second
);
7356 details
.m_iDbId
= idMVideoList
.front();
7357 items
[i
]->SetProperty("musicvideomediatype", MediaTypeAlbum
);
7358 items
[i
]->SetLabel(idData
.front().first
);
7359 items
[i
]->SetFromVideoInfoTag(details
);
7361 idMVideoList
.pop_front();
7367 GetMusicVideoInfo("", details
, idMVideoList
.front());
7368 items
[i
]->SetFromVideoInfoTag(details
);
7369 idMVideoList
.pop_front();
7374 if (!strArtist
.empty())
7375 items
.SetProperty("customtitle",strArtist
); // change displayed path from eg /23 to /Artist
7381 CLog::Log(LOGERROR
, "{} failed", __FUNCTION__
);
7386 bool CVideoDatabase::GetWritersNav(const std::string
& strBaseDir
,
7387 CFileItemList
& items
,
7388 VideoDbContentType idContent
/* = UNKNOWN */,
7389 const Filter
& filter
/* = Filter() */,
7390 bool countOnly
/* = false */)
7392 return GetPeopleNav(strBaseDir
, items
, "writer", idContent
, filter
, countOnly
);
7395 bool CVideoDatabase::GetDirectorsNav(const std::string
& strBaseDir
,
7396 CFileItemList
& items
,
7397 VideoDbContentType idContent
/* = UNKNOWN */,
7398 const Filter
& filter
/* = Filter() */,
7399 bool countOnly
/* = false */)
7401 return GetPeopleNav(strBaseDir
, items
, "director", idContent
, filter
, countOnly
);
7404 bool CVideoDatabase::GetActorsNav(const std::string
& strBaseDir
,
7405 CFileItemList
& items
,
7406 VideoDbContentType idContent
/* = UNKNOWN */,
7407 const Filter
& filter
/* = Filter() */,
7408 bool countOnly
/* = false */)
7410 if (GetPeopleNav(strBaseDir
, items
, "actor", idContent
, filter
, countOnly
))
7411 { // set thumbs - ideally this should be in the normal thumb setting routines
7412 for (int i
= 0; i
< items
.Size() && !countOnly
; i
++)
7414 CFileItemPtr pItem
= items
[i
];
7415 if (idContent
== VideoDbContentType::MUSICVIDEOS
)
7416 pItem
->SetArt("icon", "DefaultArtist.png");
7418 pItem
->SetArt("icon", "DefaultActor.png");
7425 bool CVideoDatabase::GetPeopleNav(const std::string
& strBaseDir
,
7426 CFileItemList
& items
,
7428 VideoDbContentType idContent
/* = UNKNOWN */,
7429 const Filter
& filter
/* = Filter() */,
7430 bool countOnly
/* = false */)
7432 if (nullptr == m_pDB
)
7434 if (nullptr == m_pDS
)
7439 //! @todo This routine (and probably others at this same level) use playcount as a reference to filter on at a later
7440 //! point. This means that we *MUST* filter these levels as you'll get double ups. Ideally we'd allow playcount
7441 //! to filter through as we normally do for tvshows to save this happening.
7442 //! Also, we apply this same filtering logic to the locked or unlocked paths to prevent these from showing.
7443 //! Whether or not this should happen is a tricky one - it complicates all the high level categories (everything
7446 // General routine that the other actor/director/writer routines call
7448 // get primary genres for movies
7450 bool bMainArtistOnly
= false;
7451 Filter extFilter
= filter
;
7452 if (m_profileManager
.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE
&& !g_passwordManager
.bMasterUser
)
7454 std::string view
, view_id
, media_type
, extraField
, extraJoin
, group
;
7455 if (idContent
== VideoDbContentType::MOVIES
)
7457 view
= MediaTypeMovie
;
7458 view_id
= "idMovie";
7459 media_type
= MediaTypeMovie
;
7460 extraField
= "files.playCount";
7462 else if (idContent
== VideoDbContentType::TVSHOWS
)
7464 view
= MediaTypeEpisode
;
7466 media_type
= MediaTypeTvShow
;
7467 extraField
= "count(DISTINCT idShow)";
7468 group
= "actor.actor_id";
7470 else if (idContent
== VideoDbContentType::EPISODES
)
7472 view
= MediaTypeEpisode
;
7473 view_id
= "idEpisode";
7474 media_type
= MediaTypeEpisode
;
7475 extraField
= "files.playCount";
7477 else if (idContent
== VideoDbContentType::MUSICVIDEOS
)
7479 bMainArtistOnly
= !CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(
7480 CSettings::SETTING_VIDEOLIBRARY_SHOWPERFORMERS
);
7481 if (StringUtils::EndsWith(strBaseDir
, "directors/"))
7482 // only set this to true if getting artists and show all performers is false
7483 bMainArtistOnly
= false;
7484 view
= MediaTypeMusicVideo
;
7485 view_id
= "idMVideo";
7486 media_type
= MediaTypeMusicVideo
;
7487 extraField
= "count(1), count(files.playCount)";
7488 if (bMainArtistOnly
)
7490 PrepareSQL(" WHERE actor.name IN (SELECT musicvideo_view.c10 from musicvideo_view)");
7491 group
= "actor.actor_id";
7496 strSQL
= "SELECT {} FROM actor ";
7497 extFilter
.fields
= "actor.actor_id, actor.name, actor.art_urls, path.strPath";
7498 extFilter
.AppendField(extraField
);
7499 extFilter
.AppendJoin(PrepareSQL("JOIN %s_link ON actor.actor_id = %s_link.actor_id", type
, type
));
7500 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()));
7501 extFilter
.AppendJoin(PrepareSQL("JOIN files ON files.idFile = %s_view.idFile", view
.c_str()));
7502 extFilter
.AppendJoin("JOIN path ON path.idPath = files.idPath");
7503 extFilter
.AppendJoin(extraJoin
);
7504 extFilter
.AppendGroup(group
);
7508 std::string view
, view_id
, media_type
, extraField
, extraJoin
;
7509 if (idContent
== VideoDbContentType::MOVIES
)
7511 view
= MediaTypeMovie
;
7512 view_id
= "idMovie";
7513 media_type
= MediaTypeMovie
;
7514 extraField
= "count(1), count(files.playCount)";
7515 extraJoin
= PrepareSQL(" JOIN files ON files.idFile=%s_view.idFile", view
.c_str());
7517 else if (idContent
== VideoDbContentType::TVSHOWS
)
7519 view
= MediaTypeTvShow
;
7521 media_type
= MediaTypeTvShow
;
7522 extraField
= "count(idShow)";
7524 else if (idContent
== VideoDbContentType::EPISODES
)
7526 view
= MediaTypeEpisode
;
7527 view_id
= "idEpisode";
7528 media_type
= MediaTypeEpisode
;
7529 extraField
= "count(1), count(files.playCount)";
7530 extraJoin
= PrepareSQL("JOIN files ON files.idFile = %s_view.idFile", view
.c_str());
7532 else if (idContent
== VideoDbContentType::MUSICVIDEOS
)
7534 bMainArtistOnly
= !CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(
7535 CSettings::SETTING_VIDEOLIBRARY_SHOWPERFORMERS
);
7536 if (StringUtils::EndsWith(strBaseDir
, "directors/"))
7537 // only set this to true if getting artists and show all performers is false
7538 bMainArtistOnly
= false;
7539 view
= MediaTypeMusicVideo
;
7540 view_id
= "idMVideo";
7541 media_type
= MediaTypeMusicVideo
;
7542 extraField
= "count(1), count(files.playCount)";
7543 extraJoin
= PrepareSQL("JOIN files ON files.idFile = %s_view.idFile", view
.c_str());
7544 if (bMainArtistOnly
)
7547 PrepareSQL(" WHERE actor.name IN (SELECT musicvideo_view.c10 from musicvideo_view)");
7552 strSQL
= "SELECT {} FROM actor ";
7553 extFilter
.fields
= "actor.actor_id, actor.name, actor.art_urls";
7554 extFilter
.AppendField(extraField
);
7555 extFilter
.AppendJoin(PrepareSQL("JOIN %s_link on actor.actor_id = %s_link.actor_id", type
, type
));
7556 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()));
7557 extFilter
.AppendJoin(extraJoin
);
7558 extFilter
.AppendGroup("actor.actor_id");
7563 extFilter
.fields
= "COUNT(1)";
7564 extFilter
.group
.clear();
7565 extFilter
.order
.clear();
7567 strSQL
= StringUtils::Format(strSQL
, !extFilter
.fields
.empty() ? extFilter
.fields
: "*");
7569 CVideoDbUrl videoUrl
;
7570 if (!BuildSQL(strBaseDir
, strSQL
, extFilter
, strSQL
, videoUrl
))
7574 auto start
= std::chrono::steady_clock::now();
7576 if (!m_pDS
->query(strSQL
)) return false;
7578 auto end
= std::chrono::steady_clock::now();
7579 auto duration
= std::chrono::duration_cast
<std::chrono::milliseconds
>(end
- start
);
7581 CLog::Log(LOGDEBUG
, LOGDATABASE
, "{} - query took {} ms", __FUNCTION__
, duration
.count());
7583 start
= std::chrono::steady_clock::now();
7585 int iRowsFound
= m_pDS
->num_rows();
7586 if (iRowsFound
== 0)
7594 CFileItemPtr
pItem(new CFileItem());
7595 pItem
->SetProperty("total", iRowsFound
== 1 ? m_pDS
->fv(0).get_asInt() : iRowsFound
);
7602 if (m_profileManager
.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE
&& !g_passwordManager
.bMasterUser
)
7604 std::map
<int, CActor
> mapActors
;
7606 while (!m_pDS
->eof())
7608 int idActor
= m_pDS
->fv(0).get_asInt();
7610 actor
.name
= m_pDS
->fv(1).get_asString();
7611 actor
.thumb
= m_pDS
->fv(2).get_asString();
7612 if (idContent
!= VideoDbContentType::TVSHOWS
&&
7613 idContent
!= VideoDbContentType::MUSICVIDEOS
)
7615 actor
.playcount
= m_pDS
->fv(3).get_asInt();
7616 actor
.appearances
= 1;
7618 else actor
.appearances
= m_pDS
->fv(4).get_asInt();
7619 auto it
= mapActors
.find(idActor
);
7620 // is this actor already known?
7621 if (it
== mapActors
.end())
7624 if (g_passwordManager
.IsDatabasePathUnlocked(m_pDS
->fv("path.strPath").get_asString(),*CMediaSourceSettings::GetInstance().GetSources("video")))
7625 mapActors
.insert(std::pair
<int, CActor
>(idActor
, actor
));
7627 else if (idContent
!= VideoDbContentType::TVSHOWS
&&
7628 idContent
!= VideoDbContentType::MUSICVIDEOS
)
7629 it
->second
.appearances
++;
7634 for (const auto &i
: mapActors
)
7636 CFileItemPtr
pItem(new CFileItem(i
.second
.name
));
7638 CVideoDbUrl itemUrl
= videoUrl
;
7639 std::string path
= StringUtils::Format("{}/", i
.first
);
7640 itemUrl
.AppendPath(path
);
7641 pItem
->SetPath(itemUrl
.ToString());
7643 pItem
->m_bIsFolder
=true;
7644 pItem
->GetVideoInfoTag()->SetPlayCount(i
.second
.playcount
);
7645 pItem
->GetVideoInfoTag()->m_strPictureURL
.ParseFromData(i
.second
.thumb
);
7646 pItem
->GetVideoInfoTag()->m_iDbId
= i
.first
;
7647 pItem
->GetVideoInfoTag()->m_type
= type
;
7648 pItem
->GetVideoInfoTag()->m_relevance
= i
.second
.appearances
;
7649 if (idContent
== VideoDbContentType::MUSICVIDEOS
)
7651 // Get artist bio from music db later if available
7652 pItem
->GetVideoInfoTag()->m_artist
.emplace_back(i
.second
.name
);
7653 pItem
->SetProperty("musicvideomediatype", MediaTypeArtist
);
7660 while (!m_pDS
->eof())
7664 CFileItemPtr
pItem(new CFileItem(m_pDS
->fv(1).get_asString()));
7666 CVideoDbUrl itemUrl
= videoUrl
;
7667 std::string path
= StringUtils::Format("{}/", m_pDS
->fv(0).get_asInt());
7668 itemUrl
.AppendPath(path
);
7669 pItem
->SetPath(itemUrl
.ToString());
7671 pItem
->m_bIsFolder
=true;
7672 pItem
->GetVideoInfoTag()->m_strPictureURL
.ParseFromData(m_pDS
->fv(2).get_asString());
7673 pItem
->GetVideoInfoTag()->m_iDbId
= m_pDS
->fv(0).get_asInt();
7674 pItem
->GetVideoInfoTag()->m_type
= type
;
7675 if (idContent
!= VideoDbContentType::TVSHOWS
)
7677 // fv(4) is the number of videos watched, fv(3) is the total number. We set the playcount
7678 // only if the number of videos watched is equal to the total number (i.e. every video watched)
7679 pItem
->GetVideoInfoTag()->SetPlayCount((m_pDS
->fv(4).get_asInt() == m_pDS
->fv(3).get_asInt()) ? 1 : 0);
7681 pItem
->GetVideoInfoTag()->m_relevance
= m_pDS
->fv(3).get_asInt();
7682 if (idContent
== VideoDbContentType::MUSICVIDEOS
)
7684 pItem
->GetVideoInfoTag()->m_artist
.emplace_back(pItem
->GetLabel());
7685 pItem
->SetProperty("musicvideomediatype", MediaTypeArtist
);
7693 CLog::Log(LOGERROR
, "{}: out of memory - retrieved {} items", __FUNCTION__
, items
.Size());
7694 return items
.Size() > 0;
7700 end
= std::chrono::steady_clock::now();
7701 duration
= std::chrono::duration_cast
<std::chrono::milliseconds
>(end
- start
);
7703 CLog::Log(LOGDEBUG
, LOGDATABASE
, "{} item retrieval took {} ms", __FUNCTION__
,
7711 CLog::Log(LOGERROR
, "{} failed", __FUNCTION__
);
7716 bool CVideoDatabase::GetYearsNav(const std::string
& strBaseDir
,
7717 CFileItemList
& items
,
7718 VideoDbContentType idContent
/* = UNKNOWN */,
7719 const Filter
& filter
/* = Filter() */)
7723 if (nullptr == m_pDB
)
7725 if (nullptr == m_pDS
)
7729 Filter extFilter
= filter
;
7730 if (m_profileManager
.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE
&& !g_passwordManager
.bMasterUser
)
7732 if (idContent
== VideoDbContentType::MOVIES
)
7734 strSQL
= "select movie_view.premiered, path.strPath, files.playCount from movie_view ";
7735 extFilter
.AppendJoin("join files on files.idFile = movie_view.idFile join path on files.idPath = path.idPath");
7737 else if (idContent
== VideoDbContentType::TVSHOWS
)
7739 strSQL
= PrepareSQL("select tvshow_view.c%02d, path.strPath from tvshow_view ", VIDEODB_ID_TV_PREMIERED
);
7740 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");
7742 else if (idContent
== VideoDbContentType::MUSICVIDEOS
)
7744 strSQL
= "select musicvideo_view.premiered, path.strPath, files.playCount from musicvideo_view ";
7745 extFilter
.AppendJoin("join files on files.idFile = musicvideo_view.idFile join path on files.idPath = path.idPath");
7753 if (idContent
== VideoDbContentType::MOVIES
)
7755 strSQL
= "select movie_view.premiered, count(1), count(files.playCount) from movie_view ";
7756 extFilter
.AppendJoin("join files on files.idFile = movie_view.idFile");
7757 extFilter
.AppendGroup("movie_view.premiered");
7759 else if (idContent
== VideoDbContentType::TVSHOWS
)
7761 strSQL
= PrepareSQL("select distinct tvshow_view.c%02d from tvshow_view", VIDEODB_ID_TV_PREMIERED
);
7762 extFilter
.AppendGroup(PrepareSQL("tvshow_view.c%02d", VIDEODB_ID_TV_PREMIERED
));
7764 else if (idContent
== VideoDbContentType::MUSICVIDEOS
)
7766 strSQL
= "select musicvideo_view.premiered, count(1), count(files.playCount) from musicvideo_view ";
7767 extFilter
.AppendJoin("join files on files.idFile = musicvideo_view.idFile");
7768 extFilter
.AppendGroup("musicvideo_view.premiered");
7774 CVideoDbUrl videoUrl
;
7775 if (!BuildSQL(strBaseDir
, strSQL
, extFilter
, strSQL
, videoUrl
))
7778 int iRowsFound
= RunQuery(strSQL
);
7779 if (iRowsFound
<= 0)
7780 return iRowsFound
== 0;
7782 if (m_profileManager
.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE
&& !g_passwordManager
.bMasterUser
)
7784 std::map
<int, std::pair
<std::string
,int> > mapYears
;
7785 while (!m_pDS
->eof())
7788 std::string dateString
= m_pDS
->fv(0).get_asString();
7789 if (dateString
.size() == 4)
7790 lYear
= m_pDS
->fv(0).get_asInt();
7794 time
.SetFromDateString(dateString
);
7796 lYear
= time
.GetYear();
7798 auto it
= mapYears
.find(lYear
);
7799 if (it
== mapYears
.end())
7802 if (g_passwordManager
.IsDatabasePathUnlocked(m_pDS
->fv("path.strPath").get_asString(),*CMediaSourceSettings::GetInstance().GetSources("video")))
7804 std::string year
= std::to_string(lYear
);
7805 if (idContent
== VideoDbContentType::MOVIES
||
7806 idContent
== VideoDbContentType::MUSICVIDEOS
)
7807 mapYears
.insert(std::pair
<int, std::pair
<std::string
,int> >(lYear
, std::pair
<std::string
,int>(year
,m_pDS
->fv(2).get_asInt())));
7809 mapYears
.insert(std::pair
<int, std::pair
<std::string
,int> >(lYear
, std::pair
<std::string
,int>(year
,0)));
7816 for (const auto &i
: mapYears
)
7820 CFileItemPtr
pItem(new CFileItem(i
.second
.first
));
7822 CVideoDbUrl itemUrl
= videoUrl
;
7823 std::string path
= StringUtils::Format("{}/", i
.first
);
7824 itemUrl
.AppendPath(path
);
7825 pItem
->SetPath(itemUrl
.ToString());
7827 pItem
->m_bIsFolder
=true;
7828 if (idContent
== VideoDbContentType::MOVIES
|| idContent
== VideoDbContentType::MUSICVIDEOS
)
7829 pItem
->GetVideoInfoTag()->SetPlayCount(i
.second
.second
);
7835 while (!m_pDS
->eof())
7838 std::string strLabel
= m_pDS
->fv(0).get_asString();
7839 if (strLabel
.size() == 4)
7840 lYear
= m_pDS
->fv(0).get_asInt();
7844 time
.SetFromDateString(strLabel
);
7847 lYear
= time
.GetYear();
7848 strLabel
= std::to_string(lYear
);
7856 CFileItemPtr
pItem(new CFileItem(strLabel
));
7858 CVideoDbUrl itemUrl
= videoUrl
;
7859 std::string path
= StringUtils::Format("{}/", lYear
);
7860 itemUrl
.AppendPath(path
);
7861 pItem
->SetPath(itemUrl
.ToString());
7863 pItem
->m_bIsFolder
=true;
7864 if (idContent
== VideoDbContentType::MOVIES
|| idContent
== VideoDbContentType::MUSICVIDEOS
)
7866 // fv(2) is the number of videos watched, fv(1) is the total number. We set the playcount
7867 // only if the number of videos watched is equal to the total number (i.e. every video watched)
7868 pItem
->GetVideoInfoTag()->SetPlayCount((m_pDS
->fv(2).get_asInt() == m_pDS
->fv(1).get_asInt()) ? 1 : 0);
7871 // take care of dupes ..
7872 if (!items
.Contains(pItem
->GetPath()))
7884 CLog::Log(LOGERROR
, "{} failed", __FUNCTION__
);
7889 bool CVideoDatabase::GetSeasonsNav(const std::string
& strBaseDir
, CFileItemList
& items
, int idActor
, int idDirector
, int idGenre
, int idYear
, int idShow
, bool getLinkedMovies
/* = true */)
7891 // parse the base path to get additional filters
7892 CVideoDbUrl videoUrl
;
7893 if (!videoUrl
.FromString(strBaseDir
))
7897 videoUrl
.AddOption("tvshowid", idShow
);
7899 videoUrl
.AddOption("actorid", idActor
);
7900 else if (idDirector
!= -1)
7901 videoUrl
.AddOption("directorid", idDirector
);
7902 else if (idGenre
!= -1)
7903 videoUrl
.AddOption("genreid", idGenre
);
7904 else if (idYear
!= -1)
7905 videoUrl
.AddOption("year", idYear
);
7907 if (!GetSeasonsByWhere(videoUrl
.ToString(), Filter(), items
, false))
7910 // now add any linked movies
7911 if (getLinkedMovies
&& idShow
!= -1)
7914 movieFilter
.join
= PrepareSQL("join movielinktvshow on movielinktvshow.idMovie=movie_view.idMovie");
7915 movieFilter
.where
= PrepareSQL("movielinktvshow.idShow = %i", idShow
);
7916 CFileItemList movieItems
;
7917 GetMoviesByWhere("videodb://movies/titles/", movieFilter
, movieItems
);
7919 if (movieItems
.Size() > 0)
7920 items
.Append(movieItems
);
7926 bool CVideoDatabase::GetSeasonsByWhere(const std::string
& strBaseDir
, const Filter
&filter
, CFileItemList
& items
, bool appendFullShowPath
/* = true */, const SortDescription
&sortDescription
/* = SortDescription() */)
7930 if (nullptr == m_pDB
)
7932 if (nullptr == m_pDS
)
7937 std::string strSQL
= "SELECT %s FROM season_view ";
7938 CVideoDbUrl videoUrl
;
7939 std::string strSQLExtra
;
7940 Filter extFilter
= filter
;
7941 SortDescription sorting
= sortDescription
;
7942 if (!BuildSQL(strBaseDir
, strSQLExtra
, extFilter
, strSQLExtra
, videoUrl
, sorting
))
7945 // Apply the limiting directly here if there's no special sorting but limiting
7946 if (extFilter
.limit
.empty() && sorting
.sortBy
== SortByNone
&&
7947 (sorting
.limitStart
> 0 || sorting
.limitEnd
> 0 ||
7948 (sorting
.limitStart
== 0 && sorting
.limitEnd
== 0)))
7950 total
= (int)strtol(GetSingleValue(PrepareSQL(strSQL
, "COUNT(1)") + strSQLExtra
, m_pDS
).c_str(), NULL
, 10);
7951 strSQLExtra
+= DatabaseUtils::BuildLimitClause(sorting
.limitEnd
, sorting
.limitStart
);
7954 strSQL
= PrepareSQL(strSQL
, !extFilter
.fields
.empty() ? extFilter
.fields
.c_str() : "*") + strSQLExtra
;
7956 int iRowsFound
= RunQuery(strSQL
);
7958 // store the total value of items as a property
7959 if (total
< iRowsFound
)
7961 items
.SetProperty("total", total
);
7963 if (iRowsFound
<= 0)
7964 return iRowsFound
== 0;
7966 std::set
<std::pair
<int, int>> mapSeasons
;
7967 while (!m_pDS
->eof())
7969 int id
= m_pDS
->fv(VIDEODB_ID_SEASON_ID
).get_asInt();
7970 int showId
= m_pDS
->fv(VIDEODB_ID_SEASON_TVSHOW_ID
).get_asInt();
7971 int iSeason
= m_pDS
->fv(VIDEODB_ID_SEASON_NUMBER
).get_asInt();
7972 std::string name
= m_pDS
->fv(VIDEODB_ID_SEASON_NAME
).get_asString();
7973 std::string path
= m_pDS
->fv(VIDEODB_ID_SEASON_TVSHOW_PATH
).get_asString();
7975 if (mapSeasons
.find(std::make_pair(showId
, iSeason
)) == mapSeasons
.end() &&
7976 (m_profileManager
.GetMasterProfile().getLockMode() == LOCK_MODE_EVERYONE
|| g_passwordManager
.bMasterUser
||
7977 g_passwordManager
.IsDatabasePathUnlocked(path
, *CMediaSourceSettings::GetInstance().GetSources("video"))))
7979 mapSeasons
.insert(std::make_pair(showId
, iSeason
));
7981 std::string strLabel
= name
;
7982 if (strLabel
.empty())
7985 strLabel
= g_localizeStrings
.Get(20381);
7987 strLabel
= StringUtils::Format(g_localizeStrings
.Get(20358), iSeason
);
7989 CFileItemPtr
pItem(new CFileItem(strLabel
));
7991 CVideoDbUrl itemUrl
= videoUrl
;
7993 if (appendFullShowPath
)
7994 strDir
+= StringUtils::Format("{}/", showId
);
7995 strDir
+= StringUtils::Format("{}/", iSeason
);
7996 itemUrl
.AppendPath(strDir
);
7997 pItem
->SetPath(itemUrl
.ToString());
7999 pItem
->m_bIsFolder
= true;
8000 pItem
->GetVideoInfoTag()->m_strTitle
= strLabel
;
8002 pItem
->GetVideoInfoTag()->m_strSortTitle
= name
;
8003 pItem
->GetVideoInfoTag()->m_iSeason
= iSeason
;
8004 pItem
->GetVideoInfoTag()->m_iDbId
= id
;
8005 pItem
->GetVideoInfoTag()->m_iIdSeason
= id
;
8006 pItem
->GetVideoInfoTag()->m_type
= MediaTypeSeason
;
8007 pItem
->GetVideoInfoTag()->m_strPath
= path
;
8008 pItem
->GetVideoInfoTag()->m_strShowTitle
= m_pDS
->fv(VIDEODB_ID_SEASON_TVSHOW_TITLE
).get_asString();
8009 pItem
->GetVideoInfoTag()->m_strPlot
= m_pDS
->fv(VIDEODB_ID_SEASON_TVSHOW_PLOT
).get_asString();
8010 pItem
->GetVideoInfoTag()->SetPremieredFromDBDate(m_pDS
->fv(VIDEODB_ID_SEASON_TVSHOW_PREMIERED
).get_asString());
8011 pItem
->GetVideoInfoTag()->m_firstAired
.SetFromDBDate(m_pDS
->fv(VIDEODB_ID_SEASON_PREMIERED
).get_asString());
8012 pItem
->GetVideoInfoTag()->m_iUserRating
= m_pDS
->fv(VIDEODB_ID_SEASON_USER_RATING
).get_asInt();
8013 // season premiered date based on first episode airdate associated to the season
8014 // tvshow premiered date is used as a fallback
8015 if (pItem
->GetVideoInfoTag()->m_firstAired
.IsValid())
8016 pItem
->GetVideoInfoTag()->SetPremiered(pItem
->GetVideoInfoTag()->m_firstAired
);
8017 else if (pItem
->GetVideoInfoTag()->HasPremiered())
8018 pItem
->GetVideoInfoTag()->SetPremiered(pItem
->GetVideoInfoTag()->GetPremiered());
8019 else if (pItem
->GetVideoInfoTag()->HasYear())
8020 pItem
->GetVideoInfoTag()->SetYear(pItem
->GetVideoInfoTag()->GetYear());
8021 pItem
->GetVideoInfoTag()->m_genre
= StringUtils::Split(m_pDS
->fv(VIDEODB_ID_SEASON_TVSHOW_GENRE
).get_asString(), CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_videoItemSeparator
);
8022 pItem
->GetVideoInfoTag()->m_studio
= StringUtils::Split(m_pDS
->fv(VIDEODB_ID_SEASON_TVSHOW_STUDIO
).get_asString(), CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_videoItemSeparator
);
8023 pItem
->GetVideoInfoTag()->m_strMPAARating
= m_pDS
->fv(VIDEODB_ID_SEASON_TVSHOW_MPAA
).get_asString();
8024 pItem
->GetVideoInfoTag()->m_iIdShow
= showId
;
8026 const int totalEpisodes
= m_pDS
->fv(VIDEODB_ID_SEASON_EPISODES_TOTAL
).get_asInt();
8027 const int watchedEpisodes
= m_pDS
->fv(VIDEODB_ID_SEASON_EPISODES_WATCHED
).get_asInt();
8028 const int inProgressEpisodes
= m_pDS
->fv(VIDEODB_ID_SEASON_EPISODES_INPROGRESS
).get_asInt();
8030 pItem
->GetVideoInfoTag()->m_iEpisode
= totalEpisodes
;
8031 pItem
->SetProperty("totalepisodes", totalEpisodes
);
8032 pItem
->SetProperty("numepisodes", totalEpisodes
); // will be changed later to reflect watchmode setting
8033 pItem
->SetProperty("watchedepisodes", watchedEpisodes
);
8034 pItem
->SetProperty("unwatchedepisodes", totalEpisodes
- watchedEpisodes
);
8035 pItem
->SetProperty("inprogressepisodes", inProgressEpisodes
);
8036 pItem
->SetProperty("watchedepisodepercent",
8037 totalEpisodes
> 0 ? (watchedEpisodes
* 100 / totalEpisodes
) : 0);
8039 pItem
->SetProperty("isspecial", true);
8040 pItem
->GetVideoInfoTag()->SetPlayCount((totalEpisodes
== watchedEpisodes
) ? 1 : 0);
8041 pItem
->SetOverlayImage((pItem
->GetVideoInfoTag()->GetPlayCount() > 0) &&
8042 (pItem
->GetVideoInfoTag()->m_iEpisode
> 0)
8043 ? CGUIListItem::ICON_OVERLAY_WATCHED
8044 : CGUIListItem::ICON_OVERLAY_UNWATCHED
);
8057 CLog::Log(LOGERROR
, "{} failed", __FUNCTION__
);
8062 bool CVideoDatabase::GetSortedVideos(const MediaType
&mediaType
, const std::string
& strBaseDir
, const SortDescription
&sortDescription
, CFileItemList
& items
, const Filter
&filter
/* = Filter() */)
8064 if (nullptr == m_pDB
|| nullptr == m_pDS
)
8067 if (mediaType
!= MediaTypeMovie
&& mediaType
!= MediaTypeTvShow
&& mediaType
!= MediaTypeEpisode
&& mediaType
!= MediaTypeMusicVideo
)
8070 SortDescription sorting
= sortDescription
;
8071 if (sortDescription
.sortBy
== SortByFile
|| sortDescription
.sortBy
== SortByTitle
||
8072 sortDescription
.sortBy
== SortBySortTitle
|| sortDescription
.sortBy
== SortByOriginalTitle
||
8073 sortDescription
.sortBy
== SortByLabel
|| sortDescription
.sortBy
== SortByDateAdded
||
8074 sortDescription
.sortBy
== SortByRating
|| sortDescription
.sortBy
== SortByUserRating
||
8075 sortDescription
.sortBy
== SortByYear
|| sortDescription
.sortBy
== SortByLastPlayed
||
8076 sortDescription
.sortBy
== SortByPlaycount
)
8077 sorting
.sortAttributes
= (SortAttribute
)(sortDescription
.sortAttributes
| SortAttributeIgnoreFolders
);
8079 bool success
= false;
8080 if (mediaType
== MediaTypeMovie
)
8081 success
= GetMoviesByWhere(strBaseDir
, filter
, items
, sorting
);
8082 else if (mediaType
== MediaTypeTvShow
)
8083 success
= GetTvShowsByWhere(strBaseDir
, filter
, items
, sorting
);
8084 else if (mediaType
== MediaTypeEpisode
)
8085 success
= GetEpisodesByWhere(strBaseDir
, filter
, items
, true, sorting
);
8086 else if (mediaType
== MediaTypeMusicVideo
)
8087 success
= GetMusicVideosByWhere(strBaseDir
, filter
, items
, true, sorting
);
8091 items
.SetContent(CMediaTypes::ToPlural(mediaType
));
8095 bool CVideoDatabase::GetItems(const std::string
&strBaseDir
, CFileItemList
&items
, const Filter
&filter
/* = Filter() */, const SortDescription
&sortDescription
/* = SortDescription() */)
8097 CVideoDbUrl videoUrl
;
8098 if (!videoUrl
.FromString(strBaseDir
))
8101 return GetItems(strBaseDir
, videoUrl
.GetType(), videoUrl
.GetItemType(), items
, filter
, sortDescription
);
8104 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() */)
8106 VideoDbContentType contentType
;
8107 if (StringUtils::EqualsNoCase(mediaType
, "movies"))
8108 contentType
= VideoDbContentType::MOVIES
;
8109 else if (StringUtils::EqualsNoCase(mediaType
, "tvshows"))
8111 if (StringUtils::EqualsNoCase(itemType
, "episodes"))
8112 contentType
= VideoDbContentType::EPISODES
;
8114 contentType
= VideoDbContentType::TVSHOWS
;
8116 else if (StringUtils::EqualsNoCase(mediaType
, "musicvideos"))
8117 contentType
= VideoDbContentType::MUSICVIDEOS
;
8121 return GetItems(strBaseDir
, contentType
, itemType
, items
, filter
, sortDescription
);
8124 bool CVideoDatabase::GetItems(const std::string
& strBaseDir
,
8125 VideoDbContentType mediaType
,
8126 const std::string
& itemType
,
8127 CFileItemList
& items
,
8128 const Filter
& filter
/* = Filter() */,
8129 const SortDescription
& sortDescription
/* = SortDescription() */)
8131 if (StringUtils::EqualsNoCase(itemType
, "movies") &&
8132 (mediaType
== VideoDbContentType::MOVIES
|| mediaType
== VideoDbContentType::MOVIE_SETS
))
8133 return GetMoviesByWhere(strBaseDir
, filter
, items
, sortDescription
);
8134 else if (StringUtils::EqualsNoCase(itemType
, "tvshows") &&
8135 mediaType
== VideoDbContentType::TVSHOWS
)
8137 Filter extFilter
= filter
;
8138 if (!CServiceBroker::GetSettingsComponent()->GetSettings()->
8139 GetBool(CSettings::SETTING_VIDEOLIBRARY_SHOWEMPTYTVSHOWS
))
8140 extFilter
.AppendWhere("totalCount IS NOT NULL AND totalCount > 0");
8141 return GetTvShowsByWhere(strBaseDir
, extFilter
, items
, sortDescription
);
8143 else if (StringUtils::EqualsNoCase(itemType
, "musicvideos") &&
8144 mediaType
== VideoDbContentType::MUSICVIDEOS
)
8145 return GetMusicVideosByWhere(strBaseDir
, filter
, items
, true, sortDescription
);
8146 else if (StringUtils::EqualsNoCase(itemType
, "episodes") &&
8147 mediaType
== VideoDbContentType::EPISODES
)
8148 return GetEpisodesByWhere(strBaseDir
, filter
, items
, true, sortDescription
);
8149 else if (StringUtils::EqualsNoCase(itemType
, "seasons") &&
8150 mediaType
== VideoDbContentType::TVSHOWS
)
8151 return GetSeasonsNav(strBaseDir
, items
);
8152 else if (StringUtils::EqualsNoCase(itemType
, "genres"))
8153 return GetGenresNav(strBaseDir
, items
, mediaType
, filter
);
8154 else if (StringUtils::EqualsNoCase(itemType
, "years"))
8155 return GetYearsNav(strBaseDir
, items
, mediaType
, filter
);
8156 else if (StringUtils::EqualsNoCase(itemType
, "actors"))
8157 return GetActorsNav(strBaseDir
, items
, mediaType
, filter
);
8158 else if (StringUtils::EqualsNoCase(itemType
, "directors"))
8159 return GetDirectorsNav(strBaseDir
, items
, mediaType
, filter
);
8160 else if (StringUtils::EqualsNoCase(itemType
, "writers"))
8161 return GetWritersNav(strBaseDir
, items
, mediaType
, filter
);
8162 else if (StringUtils::EqualsNoCase(itemType
, "studios"))
8163 return GetStudiosNav(strBaseDir
, items
, mediaType
, filter
);
8164 else if (StringUtils::EqualsNoCase(itemType
, "sets"))
8165 return GetSetsNav(strBaseDir
, items
, mediaType
, filter
, !CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_VIDEOLIBRARY_GROUPSINGLEITEMSETS
));
8166 else if (StringUtils::EqualsNoCase(itemType
, "countries"))
8167 return GetCountriesNav(strBaseDir
, items
, mediaType
, filter
);
8168 else if (StringUtils::EqualsNoCase(itemType
, "tags"))
8169 return GetTagsNav(strBaseDir
, items
, mediaType
, filter
);
8170 else if (StringUtils::EqualsNoCase(itemType
, "videoversions"))
8171 return GetVideoVersionsNav(strBaseDir
, items
, mediaType
, filter
);
8172 else if (StringUtils::EqualsNoCase(itemType
, "artists") &&
8173 mediaType
== VideoDbContentType::MUSICVIDEOS
)
8174 return GetActorsNav(strBaseDir
, items
, mediaType
, filter
);
8175 else if (StringUtils::EqualsNoCase(itemType
, "albums") &&
8176 mediaType
== VideoDbContentType::MUSICVIDEOS
)
8177 return GetMusicVideoAlbumsNav(strBaseDir
, items
, -1, filter
);
8182 std::string
CVideoDatabase::GetItemById(const std::string
&itemType
, int id
)
8184 if (StringUtils::EqualsNoCase(itemType
, "genres"))
8185 return GetGenreById(id
);
8186 else if (StringUtils::EqualsNoCase(itemType
, "years"))
8187 return std::to_string(id
);
8188 else if (StringUtils::EqualsNoCase(itemType
, "actors") ||
8189 StringUtils::EqualsNoCase(itemType
, "directors") ||
8190 StringUtils::EqualsNoCase(itemType
, "artists"))
8191 return GetPersonById(id
);
8192 else if (StringUtils::EqualsNoCase(itemType
, "studios"))
8193 return GetStudioById(id
);
8194 else if (StringUtils::EqualsNoCase(itemType
, "sets"))
8195 return GetSetById(id
);
8196 else if (StringUtils::EqualsNoCase(itemType
, "countries"))
8197 return GetCountryById(id
);
8198 else if (StringUtils::EqualsNoCase(itemType
, "tags"))
8199 return GetTagById(id
);
8200 else if (StringUtils::EqualsNoCase(itemType
, "videoversions"))
8201 return GetVideoVersionById(id
);
8202 else if (StringUtils::EqualsNoCase(itemType
, "albums"))
8203 return GetMusicVideoAlbumById(id
);
8208 bool CVideoDatabase::GetMoviesNav(const std::string
& strBaseDir
, CFileItemList
& items
,
8209 int idGenre
/* = -1 */, int idYear
/* = -1 */, int idActor
/* = -1 */, int idDirector
/* = -1 */,
8210 int idStudio
/* = -1 */, int idCountry
/* = -1 */, int idSet
/* = -1 */, int idTag
/* = -1 */,
8211 const SortDescription
&sortDescription
/* = SortDescription() */, int getDetails
/* = VideoDbDetailsNone */)
8213 CVideoDbUrl videoUrl
;
8214 if (!videoUrl
.FromString(strBaseDir
))
8218 videoUrl
.AddOption("genreid", idGenre
);
8219 else if (idCountry
> 0)
8220 videoUrl
.AddOption("countryid", idCountry
);
8221 else if (idStudio
> 0)
8222 videoUrl
.AddOption("studioid", idStudio
);
8223 else if (idDirector
> 0)
8224 videoUrl
.AddOption("directorid", idDirector
);
8225 else if (idYear
> 0)
8226 videoUrl
.AddOption("year", idYear
);
8227 else if (idActor
> 0)
8228 videoUrl
.AddOption("actorid", idActor
);
8230 videoUrl
.AddOption("setid", idSet
);
8232 videoUrl
.AddOption("tagid", idTag
);
8235 return GetMoviesByWhere(videoUrl
.ToString(), filter
, items
, sortDescription
, getDetails
);
8240 std::string
RewriteVideoVersionURL(const std::string
& baseDir
, const CVideoInfoTag
& movie
)
8242 const CURL parentPath
{URIUtils::GetParentPath(baseDir
)};
8243 const std::string versionId
{std::to_string(movie
.GetAssetInfo().GetId())};
8244 const std::string mediaId
{std::to_string(movie
.m_iDbId
)};
8246 url
.FromString(parentPath
.GetWithoutOptions());
8247 url
.AppendPath(versionId
);
8248 url
.AppendPath(mediaId
);
8249 url
.AddOption("videoversionid", versionId
);
8250 url
.AddOption("mediaid", mediaId
);
8251 return url
.ToString();
8253 } // unnamed namespace
8255 bool CVideoDatabase::GetMoviesByWhere(const std::string
& strBaseDir
, const Filter
&filter
, CFileItemList
& items
, const SortDescription
&sortDescription
/* = SortDescription() */, int getDetails
/* = VideoDbDetailsNone */)
8259 if (nullptr == m_pDB
)
8261 if (nullptr == m_pDS
)
8264 // parse the base path to get additional filters
8265 CVideoDbUrl videoUrl
;
8266 Filter extFilter
= filter
;
8267 SortDescription sorting
= sortDescription
;
8268 if (!videoUrl
.FromString(strBaseDir
) || !GetFilter(videoUrl
, extFilter
, sorting
))
8273 std::string strSQL
= "select %s from movie_view ";
8274 std::string strSQLExtra
;
8275 if (!CDatabase::BuildSQL(strSQLExtra
, extFilter
, strSQLExtra
))
8278 // Apply the limiting directly here if there's no special sorting but limiting
8279 if (extFilter
.limit
.empty() && sorting
.sortBy
== SortByNone
&&
8280 (sorting
.limitStart
> 0 || sorting
.limitEnd
> 0 ||
8281 (sorting
.limitStart
== 0 && sorting
.limitEnd
== 0)))
8283 total
= (int)strtol(GetSingleValue(PrepareSQL(strSQL
, "COUNT(1)") + strSQLExtra
, m_pDS
).c_str(), NULL
, 10);
8284 strSQLExtra
+= DatabaseUtils::BuildLimitClause(sorting
.limitEnd
, sorting
.limitStart
);
8287 strSQL
= PrepareSQL(strSQL
, !extFilter
.fields
.empty() ? extFilter
.fields
.c_str() : "*") + strSQLExtra
;
8289 int iRowsFound
= RunQuery(strSQL
);
8291 // store the total value of items as a property
8292 if (total
< iRowsFound
)
8294 items
.SetProperty("total", total
);
8296 if (iRowsFound
<= 0)
8297 return iRowsFound
== 0;
8299 DatabaseResults results
;
8300 results
.reserve(iRowsFound
);
8302 if (!SortUtils::SortFromDataset(sortDescription
, MediaTypeMovie
, m_pDS
, results
))
8305 // get data from returned rows
8306 items
.Reserve(results
.size());
8307 const query_data
&data
= m_pDS
->get_result_set().records
;
8308 for (const auto &i
: results
)
8310 unsigned int targetRow
= (unsigned int)i
.at(FieldRow
).asInteger();
8311 const dbiplus::sql_record
* const record
= data
.at(targetRow
);
8313 CVideoInfoTag movie
= GetDetailsForMovie(record
, getDetails
);
8314 if (m_profileManager
.GetMasterProfile().getLockMode() == LOCK_MODE_EVERYONE
||
8315 g_passwordManager
.bMasterUser
||
8316 g_passwordManager
.IsDatabasePathUnlocked(movie
.m_strPath
, *CMediaSourceSettings::GetInstance().GetSources("video")))
8318 CFileItemPtr
pItem(new CFileItem(movie
));
8321 CVideoDbUrl itemUrl
{videoUrl
};
8323 if (itemUrl
.GetOption("videoversionid", value
))
8325 //! @todo get rid of "videos with versions as folder" hack!
8326 if (value
.asInteger() == VIDEO_VERSION_ID_ALL
)
8328 // all versions for the given media id requested; we need to insert the real video
8329 // version id for this movie into the videodb url
8330 path
= RewriteVideoVersionURL(strBaseDir
, movie
);
8332 // this is a certain version, no need to resolve (e.g. no version chooser on select)
8333 pItem
->SetProperty("has_resolved_video_asset", true);
8338 itemUrl
.AppendPath(std::to_string(movie
.m_iDbId
));
8339 path
= itemUrl
.ToString();
8342 pItem
->SetPath(path
);
8343 pItem
->SetDynPath(movie
.m_strFileNameAndPath
);
8345 pItem
->SetOverlayImage(movie
.GetPlayCount() > 0 ? CGUIListItem::ICON_OVERLAY_WATCHED
8346 : CGUIListItem::ICON_OVERLAY_UNWATCHED
);
8357 CLog::Log(LOGERROR
, "{} failed", __FUNCTION__
);
8362 bool CVideoDatabase::GetTvShowsNav(const std::string
& strBaseDir
, CFileItemList
& items
,
8363 int idGenre
/* = -1 */, int idYear
/* = -1 */, int idActor
/* = -1 */, int idDirector
/* = -1 */, int idStudio
/* = -1 */, int idTag
/* = -1 */,
8364 const SortDescription
&sortDescription
/* = SortDescription() */, int getDetails
/* = VideoDbDetailsNone */)
8366 CVideoDbUrl videoUrl
;
8367 if (!videoUrl
.FromString(strBaseDir
))
8371 videoUrl
.AddOption("genreid", idGenre
);
8372 else if (idStudio
!= -1)
8373 videoUrl
.AddOption("studioid", idStudio
);
8374 else if (idDirector
!= -1)
8375 videoUrl
.AddOption("directorid", idDirector
);
8376 else if (idYear
!= -1)
8377 videoUrl
.AddOption("year", idYear
);
8378 else if (idActor
!= -1)
8379 videoUrl
.AddOption("actorid", idActor
);
8380 else if (idTag
!= -1)
8381 videoUrl
.AddOption("tagid", idTag
);
8384 if (!CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_VIDEOLIBRARY_SHOWEMPTYTVSHOWS
))
8385 filter
.AppendWhere("totalCount IS NOT NULL AND totalCount > 0");
8386 return GetTvShowsByWhere(videoUrl
.ToString(), filter
, items
, sortDescription
, getDetails
);
8389 bool CVideoDatabase::GetTvShowsByWhere(const std::string
& strBaseDir
, const Filter
&filter
, CFileItemList
& items
, const SortDescription
&sortDescription
/* = SortDescription() */, int getDetails
/* = VideoDbDetailsNone */)
8393 if (nullptr == m_pDB
)
8395 if (nullptr == m_pDS
)
8400 std::string strSQL
= "SELECT %s FROM tvshow_view ";
8401 CVideoDbUrl videoUrl
;
8402 std::string strSQLExtra
;
8403 Filter extFilter
= filter
;
8404 SortDescription sorting
= sortDescription
;
8405 if (!BuildSQL(strBaseDir
, strSQLExtra
, extFilter
, strSQLExtra
, videoUrl
, sorting
))
8408 // Apply the limiting directly here if there's no special sorting but limiting
8409 if (extFilter
.limit
.empty() && sorting
.sortBy
== SortByNone
&&
8410 (sorting
.limitStart
> 0 || sorting
.limitEnd
> 0 ||
8411 (sorting
.limitStart
== 0 && sorting
.limitEnd
== 0)))
8413 total
= (int)strtol(GetSingleValue(PrepareSQL(strSQL
, "COUNT(1)") + strSQLExtra
, m_pDS
).c_str(), NULL
, 10);
8414 strSQLExtra
+= DatabaseUtils::BuildLimitClause(sorting
.limitEnd
, sorting
.limitStart
);
8417 strSQL
= PrepareSQL(strSQL
, !extFilter
.fields
.empty() ? extFilter
.fields
.c_str() : "*") + strSQLExtra
;
8419 int iRowsFound
= RunQuery(strSQL
);
8421 // store the total value of items as a property
8422 if (total
< iRowsFound
)
8424 items
.SetProperty("total", total
);
8426 if (iRowsFound
<= 0)
8427 return iRowsFound
== 0;
8429 DatabaseResults results
;
8430 results
.reserve(iRowsFound
);
8431 if (!SortUtils::SortFromDataset(sorting
, MediaTypeTvShow
, m_pDS
, results
))
8434 // get data from returned rows
8435 items
.Reserve(results
.size());
8436 const query_data
&data
= m_pDS
->get_result_set().records
;
8437 for (const auto &i
: results
)
8439 unsigned int targetRow
= (unsigned int)i
.at(FieldRow
).asInteger();
8440 const dbiplus::sql_record
* const record
= data
.at(targetRow
);
8442 CFileItemPtr
pItem(new CFileItem());
8443 CVideoInfoTag movie
= GetDetailsForTvShow(record
, getDetails
, pItem
.get());
8444 if (m_profileManager
.GetMasterProfile().getLockMode() == LOCK_MODE_EVERYONE
||
8445 g_passwordManager
.bMasterUser
||
8446 g_passwordManager
.IsDatabasePathUnlocked(movie
.m_strPath
, *CMediaSourceSettings::GetInstance().GetSources("video")))
8448 pItem
->SetFromVideoInfoTag(movie
);
8450 CVideoDbUrl itemUrl
= videoUrl
;
8451 std::string path
= StringUtils::Format("{}/", record
->at(0).get_asInt());
8452 itemUrl
.AppendPath(path
);
8453 pItem
->SetPath(itemUrl
.ToString());
8455 pItem
->SetOverlayImage((pItem
->GetVideoInfoTag()->GetPlayCount() > 0) &&
8456 (pItem
->GetVideoInfoTag()->m_iEpisode
> 0)
8457 ? CGUIListItem::ICON_OVERLAY_WATCHED
8458 : CGUIListItem::ICON_OVERLAY_UNWATCHED
);
8469 CLog::Log(LOGERROR
, "{} failed", __FUNCTION__
);
8474 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 */)
8476 CVideoDbUrl videoUrl
;
8477 if (!videoUrl
.FromString(strBaseDir
))
8482 videoUrl
.AddOption("tvshowid", idShow
);
8484 videoUrl
.AddOption("season", idSeason
);
8487 videoUrl
.AddOption("genreid", idGenre
);
8488 else if (idYear
!=-1)
8489 videoUrl
.AddOption("year", idYear
);
8490 else if (idActor
!= -1)
8491 videoUrl
.AddOption("actorid", idActor
);
8493 else if (idYear
!= -1)
8494 videoUrl
.AddOption("year", idYear
);
8496 if (idDirector
!= -1)
8497 videoUrl
.AddOption("directorid", idDirector
);
8500 bool ret
= GetEpisodesByWhere(videoUrl
.ToString(), filter
, items
, false, sortDescription
, getDetails
);
8502 if (idSeason
== -1 && idShow
!= -1)
8503 { // add any linked movies
8505 movieFilter
.join
= PrepareSQL("join movielinktvshow on movielinktvshow.idMovie=movie_view.idMovie");
8506 movieFilter
.where
= PrepareSQL("movielinktvshow.idShow = %i", idShow
);
8507 CFileItemList movieItems
;
8508 GetMoviesByWhere("videodb://movies/titles/", movieFilter
, movieItems
);
8510 if (movieItems
.Size() > 0)
8511 items
.Append(movieItems
);
8517 bool CVideoDatabase::GetEpisodesByWhere(const std::string
& strBaseDir
, const Filter
&filter
, CFileItemList
& items
, bool appendFullShowPath
/* = true */, const SortDescription
&sortDescription
/* = SortDescription() */, int getDetails
/* = VideoDbDetailsNone */)
8521 if (nullptr == m_pDB
)
8523 if (nullptr == m_pDS
)
8528 std::string strSQL
= "select %s from episode_view ";
8529 CVideoDbUrl videoUrl
;
8530 std::string strSQLExtra
;
8531 Filter extFilter
= filter
;
8532 SortDescription sorting
= sortDescription
;
8533 if (!BuildSQL(strBaseDir
, strSQLExtra
, extFilter
, strSQLExtra
, videoUrl
, sorting
))
8536 // Apply the limiting directly here if there's no special sorting but limiting
8537 if (extFilter
.limit
.empty() && sorting
.sortBy
== SortByNone
&&
8538 (sorting
.limitStart
> 0 || sorting
.limitEnd
> 0 ||
8539 (sorting
.limitStart
== 0 && sorting
.limitEnd
== 0)))
8541 total
= (int)strtol(GetSingleValue(PrepareSQL(strSQL
, "COUNT(1)") + strSQLExtra
, m_pDS
).c_str(), NULL
, 10);
8542 strSQLExtra
+= DatabaseUtils::BuildLimitClause(sorting
.limitEnd
, sorting
.limitStart
);
8545 strSQL
= PrepareSQL(strSQL
, !extFilter
.fields
.empty() ? extFilter
.fields
.c_str() : "*") + strSQLExtra
;
8547 int iRowsFound
= RunQuery(strSQL
);
8549 // store the total value of items as a property
8550 if (total
< iRowsFound
)
8552 items
.SetProperty("total", total
);
8554 if (iRowsFound
<= 0)
8555 return iRowsFound
== 0;
8557 DatabaseResults results
;
8558 results
.reserve(iRowsFound
);
8559 if (!SortUtils::SortFromDataset(sorting
, MediaTypeEpisode
, m_pDS
, results
))
8562 // get data from returned rows
8563 items
.Reserve(results
.size());
8564 CLabelFormatter
formatter("%H. %T", "");
8566 const query_data
&data
= m_pDS
->get_result_set().records
;
8567 for (const auto &i
: results
)
8569 unsigned int targetRow
= (unsigned int)i
.at(FieldRow
).asInteger();
8570 const dbiplus::sql_record
* const record
= data
.at(targetRow
);
8572 CVideoInfoTag episode
= GetDetailsForEpisode(record
, getDetails
);
8573 if (m_profileManager
.GetMasterProfile().getLockMode() == LOCK_MODE_EVERYONE
||
8574 g_passwordManager
.bMasterUser
||
8575 g_passwordManager
.IsDatabasePathUnlocked(episode
.m_strPath
, *CMediaSourceSettings::GetInstance().GetSources("video")))
8577 CFileItemPtr
pItem(new CFileItem(episode
));
8578 formatter
.FormatLabel(pItem
.get());
8580 int idEpisode
= record
->at(0).get_asInt();
8582 CVideoDbUrl itemUrl
= videoUrl
;
8584 if (appendFullShowPath
&& videoUrl
.GetItemType() != "episodes")
8585 path
= StringUtils::Format("{}/{}/{}",
8586 record
->at(VIDEODB_DETAILS_EPISODE_TVSHOW_ID
).get_asInt(),
8587 episode
.m_iSeason
, idEpisode
);
8589 path
= std::to_string(idEpisode
);
8590 itemUrl
.AppendPath(path
);
8591 pItem
->SetPath(itemUrl
.ToString());
8592 pItem
->SetDynPath(episode
.m_strFileNameAndPath
);
8594 pItem
->SetOverlayImage(episode
.GetPlayCount() > 0 ? CGUIListItem::ICON_OVERLAY_WATCHED
8595 : CGUIListItem::ICON_OVERLAY_UNWATCHED
);
8596 pItem
->m_dateTime
= episode
.m_firstAired
;
8607 CLog::Log(LOGERROR
, "{} failed", __FUNCTION__
);
8612 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 */)
8614 CVideoDbUrl videoUrl
;
8615 if (!videoUrl
.FromString(strBaseDir
))
8619 videoUrl
.AddOption("genreid", idGenre
);
8620 else if (idStudio
!= -1)
8621 videoUrl
.AddOption("studioid", idStudio
);
8622 else if (idDirector
!= -1)
8623 videoUrl
.AddOption("directorid", idDirector
);
8624 else if (idYear
!=-1)
8625 videoUrl
.AddOption("year", idYear
);
8626 else if (idArtist
!= -1)
8627 videoUrl
.AddOption("artistid", idArtist
);
8628 else if (idTag
!= -1)
8629 videoUrl
.AddOption("tagid", idTag
);
8631 videoUrl
.AddOption("albumid", idAlbum
);
8634 return GetMusicVideosByWhere(videoUrl
.ToString(), filter
, items
, true, sortDescription
, getDetails
);
8637 bool CVideoDatabase::GetRecentlyAddedMoviesNav(const std::string
& strBaseDir
, CFileItemList
& items
, unsigned int limit
/* = 0 */, int getDetails
/* = VideoDbDetailsNone */)
8640 filter
.order
= "dateAdded desc, idMovie desc";
8641 filter
.limit
= PrepareSQL("%u", limit
? limit
: CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_iVideoLibraryRecentlyAddedItems
);
8642 return GetMoviesByWhere(strBaseDir
, filter
, items
, SortDescription(), getDetails
);
8645 bool CVideoDatabase::GetRecentlyAddedEpisodesNav(const std::string
& strBaseDir
, CFileItemList
& items
, unsigned int limit
/* = 0 */, int getDetails
/* = VideoDbDetailsNone */)
8648 filter
.order
= "dateAdded desc, idEpisode desc";
8649 filter
.limit
= PrepareSQL("%u", limit
? limit
: CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_iVideoLibraryRecentlyAddedItems
);
8650 return GetEpisodesByWhere(strBaseDir
, filter
, items
, false, SortDescription(), getDetails
);
8653 bool CVideoDatabase::GetRecentlyAddedMusicVideosNav(const std::string
& strBaseDir
, CFileItemList
& items
, unsigned int limit
/* = 0 */, int getDetails
/* = VideoDbDetailsNone */)
8656 filter
.order
= "dateAdded desc, idMVideo desc";
8657 filter
.limit
= PrepareSQL("%u", limit
? limit
: CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_iVideoLibraryRecentlyAddedItems
);
8658 return GetMusicVideosByWhere(strBaseDir
, filter
, items
, true, SortDescription(), getDetails
);
8661 bool CVideoDatabase::GetInProgressTvShowsNav(const std::string
& strBaseDir
, CFileItemList
& items
, unsigned int limit
/* = 0 */, int getDetails
/* = VideoDbDetailsNone */)
8664 filter
.order
= PrepareSQL("c%02d", VIDEODB_ID_TV_TITLE
);
8665 filter
.where
= "totalCount != watchedCount AND (inProgressCount > 0 OR watchedCount != 0)";
8666 return GetTvShowsByWhere(strBaseDir
, filter
, items
, SortDescription(), getDetails
);
8669 std::string
CVideoDatabase::GetGenreById(int id
)
8671 return GetSingleValue("genre", "name", PrepareSQL("genre_id=%i", id
));
8674 std::string
CVideoDatabase::GetCountryById(int id
)
8676 return GetSingleValue("country", "name", PrepareSQL("country_id=%i", id
));
8679 std::string
CVideoDatabase::GetSetById(int id
)
8681 return GetSingleValue("sets", "strSet", PrepareSQL("idSet=%i", id
));
8684 std::string
CVideoDatabase::GetSetByNameLike(const std::string
& nameLike
) const
8686 return GetSingleValue("sets", "strSet", PrepareSQL("strSet LIKE '%s'", nameLike
.c_str()));
8689 std::string
CVideoDatabase::GetTagById(int id
)
8691 return GetSingleValue("tag", "name", PrepareSQL("tag_id = %i", id
));
8694 std::string
CVideoDatabase::GetPersonById(int id
)
8696 return GetSingleValue("actor", "name", PrepareSQL("actor_id=%i", id
));
8699 std::string
CVideoDatabase::GetStudioById(int id
)
8701 return GetSingleValue("studio", "name", PrepareSQL("studio_id=%i", id
));
8704 std::string
CVideoDatabase::GetTvShowTitleById(int id
)
8706 return GetSingleValue("tvshow", PrepareSQL("c%02d", VIDEODB_ID_TV_TITLE
), PrepareSQL("idShow=%i", id
));
8709 std::string
CVideoDatabase::GetMusicVideoAlbumById(int id
)
8711 return GetSingleValue("musicvideo", PrepareSQL("c%02d", VIDEODB_ID_MUSICVIDEO_ALBUM
), PrepareSQL("idMVideo=%i", id
));
8714 bool CVideoDatabase::HasSets() const
8718 if (nullptr == m_pDB
)
8720 if (nullptr == m_pDS
)
8723 m_pDS
->query("SELECT movie_view.idSet,COUNT(1) AS c FROM movie_view "
8724 "JOIN sets ON sets.idSet = movie_view.idSet "
8725 "GROUP BY movie_view.idSet HAVING c>1");
8727 bool bResult
= (m_pDS
->num_rows() > 0);
8733 CLog::Log(LOGERROR
, "{} failed", __FUNCTION__
);
8738 int CVideoDatabase::GetTvShowForEpisode(int idEpisode
)
8742 if (nullptr == m_pDB
)
8744 if (nullptr == m_pDS2
)
8747 // make sure we use m_pDS2, as this is called in loops using m_pDS
8748 std::string strSQL
=PrepareSQL("select idShow from episode where idEpisode=%i", idEpisode
);
8749 m_pDS2
->query( strSQL
);
8753 result
=m_pDS2
->fv(0).get_asInt();
8760 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, idEpisode
);
8765 int CVideoDatabase::GetSeasonForEpisode(int idEpisode
)
8768 snprintf(column
, sizeof(column
), "c%0d", VIDEODB_ID_EPISODE_SEASON
);
8769 std::string id
= GetSingleValue("episode", column
, PrepareSQL("idEpisode=%i", idEpisode
));
8772 return atoi(id
.c_str());
8775 bool CVideoDatabase::HasContent()
8777 return (HasContent(VideoDbContentType::MOVIES
) || HasContent(VideoDbContentType::TVSHOWS
) ||
8778 HasContent(VideoDbContentType::MUSICVIDEOS
));
8781 bool CVideoDatabase::HasContent(VideoDbContentType type
)
8783 bool result
= false;
8786 if (nullptr == m_pDB
)
8788 if (nullptr == m_pDS
)
8792 if (type
== VideoDbContentType::MOVIES
)
8793 sql
= "select count(1) from movie";
8794 else if (type
== VideoDbContentType::TVSHOWS
)
8795 sql
= "select count(1) from tvshow";
8796 else if (type
== VideoDbContentType::MUSICVIDEOS
)
8797 sql
= "select count(1) from musicvideo";
8798 m_pDS
->query( sql
);
8801 result
= (m_pDS
->fv(0).get_asInt() > 0);
8807 CLog::Log(LOGERROR
, "{} failed", __FUNCTION__
);
8812 ScraperPtr
CVideoDatabase::GetScraperForPath( const std::string
& strPath
)
8814 SScanSettings settings
;
8815 return GetScraperForPath(strPath
, settings
);
8818 ScraperPtr
CVideoDatabase::GetScraperForPath(const std::string
& strPath
, SScanSettings
& settings
)
8821 return GetScraperForPath(strPath
, settings
, dummy
);
8824 ScraperPtr
CVideoDatabase::GetScraperForPath(const std::string
& strPath
, SScanSettings
& settings
, bool& foundDirectly
)
8826 foundDirectly
= false;
8829 if (strPath
.empty() || !m_pDB
|| !m_pDS
)
8830 return ScraperPtr();
8833 std::string strPath2
;
8835 if (URIUtils::IsMultiPath(strPath
))
8836 strPath2
= CMultiPathDirectory::GetFirstPath(strPath
);
8840 std::string strPath1
= URIUtils::GetDirectory(strPath2
);
8841 int idPath
= GetPathId(strPath1
);
8845 std::string strSQL
= PrepareSQL(
8846 "SELECT path.strContent, path.strScraper, path.scanRecursive, path.useFolderNames, "
8847 "path.strSettings, path.noUpdate, path.exclude, path.allAudio FROM path WHERE idPath=%i",
8849 m_pDS
->query( strSQL
);
8853 CONTENT_TYPE content
= CONTENT_NONE
;
8855 { // path is stored in db
8857 settings
.m_allExtAudio
= m_pDS
->fv("path.allAudio").get_asBool();
8859 if (m_pDS
->fv("path.exclude").get_asBool())
8861 settings
.exclude
= true;
8863 return ScraperPtr();
8865 settings
.exclude
= false;
8867 // try and ascertain scraper for this path
8868 std::string strcontent
= m_pDS
->fv("path.strContent").get_asString();
8869 StringUtils::ToLower(strcontent
);
8870 content
= TranslateContent(strcontent
);
8872 //FIXME paths stored should not have empty strContent
8873 //assert(content != CONTENT_NONE);
8874 std::string scraperID
= m_pDS
->fv("path.strScraper").get_asString();
8877 if (!scraperID
.empty() &&
8878 CServiceBroker::GetAddonMgr().GetAddon(scraperID
, addon
, ADDON::OnlyEnabled::CHOICE_YES
))
8880 scraper
= std::dynamic_pointer_cast
<CScraper
>(addon
);
8882 return ScraperPtr();
8884 // store this path's content & settings
8885 scraper
->SetPathSettings(content
, m_pDS
->fv("path.strSettings").get_asString());
8886 settings
.parent_name
= m_pDS
->fv("path.useFolderNames").get_asBool();
8887 settings
.recurse
= m_pDS
->fv("path.scanRecursive").get_asInt();
8888 settings
.noupdate
= m_pDS
->fv("path.noUpdate").get_asBool();
8892 if (content
== CONTENT_NONE
)
8893 { // this path is not saved in db
8894 // we must drill up until a scraper is configured
8895 std::string strParent
;
8896 while (URIUtils::GetParentPath(strPath1
, strParent
))
8900 std::string strSQL
=
8901 PrepareSQL("SELECT path.strContent, path.strScraper, path.scanRecursive, "
8902 "path.useFolderNames, path.strSettings, path.noUpdate, path.exclude, "
8903 "path.allAudio FROM path WHERE strPath='%s'",
8905 m_pDS
->query(strSQL
);
8907 CONTENT_TYPE content
= CONTENT_NONE
;
8910 settings
.m_allExtAudio
= m_pDS
->fv("path.allAudio").get_asBool();
8911 std::string strcontent
= m_pDS
->fv("path.strContent").get_asString();
8912 StringUtils::ToLower(strcontent
);
8913 if (m_pDS
->fv("path.exclude").get_asBool())
8915 settings
.exclude
= true;
8921 content
= TranslateContent(strcontent
);
8924 if (content
!= CONTENT_NONE
&&
8925 CServiceBroker::GetAddonMgr().GetAddon(m_pDS
->fv("path.strScraper").get_asString(),
8926 addon
, ADDON::OnlyEnabled::CHOICE_YES
))
8928 scraper
= std::dynamic_pointer_cast
<CScraper
>(addon
);
8929 scraper
->SetPathSettings(content
, m_pDS
->fv("path.strSettings").get_asString());
8930 settings
.parent_name
= m_pDS
->fv("path.useFolderNames").get_asBool();
8931 settings
.recurse
= m_pDS
->fv("path.scanRecursive").get_asInt();
8932 settings
.noupdate
= m_pDS
->fv("path.noUpdate").get_asBool();
8933 settings
.exclude
= false;
8937 strPath1
= strParent
;
8942 if (!scraper
|| scraper
->Content() == CONTENT_NONE
)
8943 return ScraperPtr();
8945 if (scraper
->Content() == CONTENT_TVSHOWS
)
8947 settings
.recurse
= 0;
8948 if(settings
.parent_name
) // single show
8950 settings
.parent_name_root
= settings
.parent_name
= (iFound
== 1);
8954 settings
.parent_name_root
= settings
.parent_name
= (iFound
== 2);
8957 else if (scraper
->Content() == CONTENT_MOVIES
)
8959 settings
.recurse
= settings
.recurse
- (iFound
-1);
8960 settings
.parent_name_root
= settings
.parent_name
&& (!settings
.recurse
|| iFound
> 1);
8962 else if (scraper
->Content() == CONTENT_MUSICVIDEOS
)
8964 settings
.recurse
= settings
.recurse
- (iFound
-1);
8965 settings
.parent_name_root
= settings
.parent_name
&& (!settings
.recurse
|| iFound
> 1);
8970 return ScraperPtr();
8972 foundDirectly
= (iFound
== 1);
8977 CLog::Log(LOGERROR
, "{} failed", __FUNCTION__
);
8979 return ScraperPtr();
8982 bool CVideoDatabase::GetUseAllExternalAudioForVideo(const std::string
& videoPath
)
8984 // Find longest configured source path for given video path
8985 std::string strSQL
= PrepareSQL("SELECT allAudio FROM path WHERE allAudio IS NOT NULL AND "
8986 "instr('%s', strPath) = 1 ORDER BY length(strPath) DESC LIMIT 1",
8988 m_pDS
->query(strSQL
);
8991 return m_pDS
->fv("allAudio").get_asBool();
8996 std::string
CVideoDatabase::GetContentForPath(const std::string
& strPath
)
8998 SScanSettings settings
;
8999 bool foundDirectly
= false;
9000 ScraperPtr scraper
= GetScraperForPath(strPath
, settings
, foundDirectly
);
9003 if (scraper
->Content() == CONTENT_TVSHOWS
)
9005 // check for episodes or seasons. Assumptions are:
9006 // 1. if episodes are in the path then we're in episodes.
9007 // 2. if no episodes are found, and content was set directly on this path, then we're in shows.
9008 // 3. if no episodes are found, and content was not set directly on this path, we're in seasons (assumes tvshows/seasons/episodes)
9009 std::string sql
= "SELECT COUNT(*) FROM episode_view ";
9012 sql
+= PrepareSQL("WHERE strPath = '%s'", strPath
.c_str());
9014 sql
+= PrepareSQL("WHERE strPath LIKE '%s%%'", strPath
.c_str());
9016 m_pDS
->query( sql
);
9017 if (m_pDS
->num_rows() && m_pDS
->fv(0).get_asInt() > 0)
9019 return foundDirectly
? "tvshows" : "seasons";
9021 return TranslateContent(scraper
->Content());
9026 void CVideoDatabase::GetMovieGenresByName(const std::string
& strSearch
, CFileItemList
& items
)
9032 if (nullptr == m_pDB
)
9034 if (nullptr == m_pDS
)
9037 if (m_profileManager
.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE
&& !g_passwordManager
.bMasterUser
)
9038 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());
9040 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());
9041 m_pDS
->query( strSQL
);
9043 while (!m_pDS
->eof())
9045 if (m_profileManager
.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE
&& !g_passwordManager
.bMasterUser
)
9046 if (!g_passwordManager
.IsDatabasePathUnlocked(m_pDS
->fv("path.strPath").get_asString(),
9047 *CMediaSourceSettings::GetInstance().GetSources("video")))
9053 CFileItemPtr
pItem(new CFileItem(m_pDS
->fv(1).get_asString()));
9054 std::string strDir
= StringUtils::Format("{}/", m_pDS
->fv(0).get_asInt());
9055 pItem
->SetPath("videodb://movies/genres/"+ strDir
);
9056 pItem
->m_bIsFolder
=true;
9064 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, strSQL
);
9068 void CVideoDatabase::GetMovieCountriesByName(const std::string
& strSearch
, CFileItemList
& items
)
9074 if (nullptr == m_pDB
)
9076 if (nullptr == m_pDS
)
9079 if (m_profileManager
.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE
&& !g_passwordManager
.bMasterUser
)
9080 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());
9082 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());
9083 m_pDS
->query( strSQL
);
9085 while (!m_pDS
->eof())
9087 if (m_profileManager
.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE
&& !g_passwordManager
.bMasterUser
)
9088 if (!g_passwordManager
.IsDatabasePathUnlocked(m_pDS
->fv("path.strPath").get_asString(),
9089 *CMediaSourceSettings::GetInstance().GetSources("video")))
9095 CFileItemPtr
pItem(new CFileItem(m_pDS
->fv(1).get_asString()));
9096 std::string strDir
= StringUtils::Format("{}/", m_pDS
->fv(0).get_asInt());
9097 pItem
->SetPath("videodb://movies/genres/"+ strDir
);
9098 pItem
->m_bIsFolder
=true;
9106 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, strSQL
);
9110 void CVideoDatabase::GetTvShowGenresByName(const std::string
& strSearch
, CFileItemList
& items
)
9116 if (nullptr == m_pDB
)
9118 if (nullptr == m_pDS
)
9121 if (m_profileManager
.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE
&& !g_passwordManager
.bMasterUser
)
9122 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());
9124 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());
9125 m_pDS
->query( strSQL
);
9127 while (!m_pDS
->eof())
9129 if (m_profileManager
.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE
&& !g_passwordManager
.bMasterUser
)
9130 if (!g_passwordManager
.IsDatabasePathUnlocked(m_pDS
->fv("path.strPath").get_asString(),*CMediaSourceSettings::GetInstance().GetSources("video")))
9136 CFileItemPtr
pItem(new CFileItem(m_pDS
->fv(1).get_asString()));
9137 std::string strDir
= StringUtils::Format("{}/", m_pDS
->fv(0).get_asInt());
9138 pItem
->SetPath("videodb://tvshows/genres/"+ strDir
);
9139 pItem
->m_bIsFolder
=true;
9147 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, strSQL
);
9151 void CVideoDatabase::GetMovieActorsByName(const std::string
& strSearch
, CFileItemList
& items
)
9157 if (nullptr == m_pDB
)
9159 if (nullptr == m_pDS
)
9162 if (m_profileManager
.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE
&& !g_passwordManager
.bMasterUser
)
9163 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());
9165 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());
9166 m_pDS
->query( strSQL
);
9168 while (!m_pDS
->eof())
9170 if (m_profileManager
.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE
&& !g_passwordManager
.bMasterUser
)
9171 if (!g_passwordManager
.IsDatabasePathUnlocked(m_pDS
->fv("path.strPath").get_asString(),*CMediaSourceSettings::GetInstance().GetSources("video")))
9177 CFileItemPtr
pItem(new CFileItem(m_pDS
->fv(1).get_asString()));
9178 std::string strDir
= StringUtils::Format("{}/", m_pDS
->fv(0).get_asInt());
9179 pItem
->SetPath("videodb://movies/actors/"+ strDir
);
9180 pItem
->m_bIsFolder
=true;
9188 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, strSQL
);
9192 void CVideoDatabase::GetTvShowsActorsByName(const std::string
& strSearch
, CFileItemList
& items
)
9198 if (nullptr == m_pDB
)
9200 if (nullptr == m_pDS
)
9203 if (m_profileManager
.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE
&& !g_passwordManager
.bMasterUser
)
9204 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());
9206 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());
9207 m_pDS
->query( strSQL
);
9209 while (!m_pDS
->eof())
9211 if (m_profileManager
.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE
&& !g_passwordManager
.bMasterUser
)
9212 if (!g_passwordManager
.IsDatabasePathUnlocked(m_pDS
->fv("path.strPath").get_asString(),*CMediaSourceSettings::GetInstance().GetSources("video")))
9218 CFileItemPtr
pItem(new CFileItem(m_pDS
->fv(1).get_asString()));
9219 std::string strDir
= StringUtils::Format("{}/", m_pDS
->fv(0).get_asInt());
9220 pItem
->SetPath("videodb://tvshows/actors/"+ strDir
);
9221 pItem
->m_bIsFolder
=true;
9229 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, strSQL
);
9233 void CVideoDatabase::GetMusicVideoArtistsByName(const std::string
& strSearch
, CFileItemList
& items
)
9239 if (nullptr == m_pDB
)
9241 if (nullptr == m_pDS
)
9244 std::string strLike
;
9245 if (!strSearch
.empty())
9246 strLike
= "and actor.name like '%%%s%%'";
9247 if (m_profileManager
.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE
&& !g_passwordManager
.bMasterUser
)
9248 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());
9250 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());
9251 m_pDS
->query( strSQL
);
9253 while (!m_pDS
->eof())
9255 if (m_profileManager
.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE
&& !g_passwordManager
.bMasterUser
)
9256 if (!g_passwordManager
.IsDatabasePathUnlocked(m_pDS
->fv("path.strPath").get_asString(),*CMediaSourceSettings::GetInstance().GetSources("video")))
9262 CFileItemPtr
pItem(new CFileItem(m_pDS
->fv(1).get_asString()));
9263 std::string strDir
= StringUtils::Format("{}/", m_pDS
->fv(0).get_asInt());
9264 pItem
->SetPath("videodb://musicvideos/artists/"+ strDir
);
9265 pItem
->m_bIsFolder
=true;
9273 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, strSQL
);
9277 void CVideoDatabase::GetMusicVideoGenresByName(const std::string
& strSearch
, CFileItemList
& items
)
9283 if (nullptr == m_pDB
)
9285 if (nullptr == m_pDS
)
9288 if (m_profileManager
.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE
&& !g_passwordManager
.bMasterUser
)
9289 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());
9291 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());
9292 m_pDS
->query( strSQL
);
9294 while (!m_pDS
->eof())
9296 if (m_profileManager
.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE
&& !g_passwordManager
.bMasterUser
)
9297 if (!g_passwordManager
.IsDatabasePathUnlocked(m_pDS
->fv("path.strPath").get_asString(),*CMediaSourceSettings::GetInstance().GetSources("video")))
9303 CFileItemPtr
pItem(new CFileItem(m_pDS
->fv(1).get_asString()));
9304 std::string strDir
= StringUtils::Format("{}/", m_pDS
->fv(0).get_asInt());
9305 pItem
->SetPath("videodb://musicvideos/genres/"+ strDir
);
9306 pItem
->m_bIsFolder
=true;
9314 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, strSQL
);
9318 void CVideoDatabase::GetMusicVideoAlbumsByName(const std::string
& strSearch
, CFileItemList
& items
)
9324 if (nullptr == m_pDB
)
9326 if (nullptr == m_pDS
)
9329 strSQL
= StringUtils::Format("SELECT DISTINCT"
9330 " musicvideo.c{:02},"
9331 " musicvideo.idMVideo,"
9336 " files.idFile=musicvideo.idFile"
9338 " path.idPath=files.idPath",
9339 VIDEODB_ID_MUSICVIDEO_ALBUM
);
9340 if (!strSearch
.empty())
9341 strSQL
+= PrepareSQL(" WHERE musicvideo.c%02d like '%%%s%%'",VIDEODB_ID_MUSICVIDEO_ALBUM
, strSearch
.c_str());
9343 m_pDS
->query( strSQL
);
9345 while (!m_pDS
->eof())
9347 if (m_pDS
->fv(0).get_asString().empty())
9353 if (m_profileManager
.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE
&& !g_passwordManager
.bMasterUser
)
9354 if (!g_passwordManager
.IsDatabasePathUnlocked(m_pDS
->fv(2).get_asString(),*CMediaSourceSettings::GetInstance().GetSources("video")))
9360 CFileItemPtr
pItem(new CFileItem(m_pDS
->fv(0).get_asString()));
9361 std::string strDir
= std::to_string(m_pDS
->fv(1).get_asInt());
9362 pItem
->SetPath("videodb://musicvideos/titles/"+ strDir
);
9363 pItem
->m_bIsFolder
=false;
9371 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, strSQL
);
9375 void CVideoDatabase::GetMusicVideosByAlbum(const std::string
& strSearch
, CFileItemList
& items
)
9381 if (nullptr == m_pDB
)
9383 if (nullptr == m_pDS
)
9386 if (m_profileManager
.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE
&& !g_passwordManager
.bMasterUser
)
9387 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());
9389 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());
9390 m_pDS
->query( strSQL
);
9392 while (!m_pDS
->eof())
9394 if (m_profileManager
.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE
&& !g_passwordManager
.bMasterUser
)
9395 if (!g_passwordManager
.IsDatabasePathUnlocked(m_pDS
->fv("path.strPath").get_asString(),*CMediaSourceSettings::GetInstance().GetSources("video")))
9401 CFileItemPtr
pItem(new CFileItem(m_pDS
->fv(1).get_asString()+" - "+m_pDS
->fv(2).get_asString()));
9402 std::string strDir
=
9403 StringUtils::Format("3/2/{}", m_pDS
->fv("musicvideo.idMVideo").get_asInt());
9405 pItem
->SetPath("videodb://"+ strDir
);
9406 pItem
->m_bIsFolder
=false;
9414 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, strSQL
);
9418 bool CVideoDatabase::GetMusicVideosByWhere(const std::string
&baseDir
, const Filter
&filter
, CFileItemList
&items
, bool checkLocks
/*= true*/, const SortDescription
&sortDescription
/* = SortDescription() */, int getDetails
/* = VideoDbDetailsNone */)
9423 if (nullptr == m_pDB
)
9425 if (nullptr == m_pDS
)
9430 std::string strSQL
= "select %s from musicvideo_view ";
9431 CVideoDbUrl videoUrl
;
9432 if (!videoUrl
.FromString(baseDir
))
9435 std::string strSQLExtra
;
9436 const CUrlOptions::UrlOptions
& options
= videoUrl
.GetOptions();
9437 std::string strArtist
;
9439 // If we have an artistid then get the artist name and use that to fix up the path displayed in
9440 // the GUI (musicvideos/artist-name instead of musicvideos/artistid)
9441 auto option
= options
.find("artistid");
9442 if (option
!= options
.end())
9444 idArtist
= option
->second
.asInteger();
9445 strArtist
= GetSingleValue(
9446 PrepareSQL("SELECT name FROM actor where actor_id = '%i'", idArtist
), m_pDS
)
9449 Filter extFilter
= filter
;
9450 SortDescription sorting
= sortDescription
;
9451 if (!BuildSQL(baseDir
, strSQLExtra
, extFilter
, strSQLExtra
, videoUrl
, sorting
))
9454 // Apply the limiting directly here if there's no special sorting but limiting
9455 if (extFilter
.limit
.empty() && sorting
.sortBy
== SortByNone
&&
9456 (sorting
.limitStart
> 0 || sorting
.limitEnd
> 0 ||
9457 (sorting
.limitStart
== 0 && sorting
.limitEnd
== 0)))
9459 total
= (int)strtol(GetSingleValue(PrepareSQL(strSQL
, "COUNT(1)") + strSQLExtra
, m_pDS
).c_str(), NULL
, 10);
9460 strSQLExtra
+= DatabaseUtils::BuildLimitClause(sorting
.limitEnd
, sorting
.limitStart
);
9463 strSQL
= PrepareSQL(strSQL
, !extFilter
.fields
.empty() ? extFilter
.fields
.c_str() : "*") + strSQLExtra
;
9465 int iRowsFound
= RunQuery(strSQL
);
9467 // store the total value of items as a property
9468 if (total
< iRowsFound
)
9470 items
.SetProperty("total", total
);
9472 if (iRowsFound
<= 0)
9473 return iRowsFound
== 0;
9475 DatabaseResults results
;
9476 results
.reserve(iRowsFound
);
9477 if (!SortUtils::SortFromDataset(sorting
, MediaTypeMusicVideo
, m_pDS
, results
))
9480 // get data from returned rows
9481 items
.Reserve(results
.size());
9482 // get songs from returned subtable
9483 const query_data
&data
= m_pDS
->get_result_set().records
;
9484 for (const auto &i
: results
)
9486 unsigned int targetRow
= (unsigned int)i
.at(FieldRow
).asInteger();
9487 const dbiplus::sql_record
* const record
= data
.at(targetRow
);
9489 CVideoInfoTag musicvideo
= GetDetailsForMusicVideo(record
, getDetails
);
9490 if (!checkLocks
|| m_profileManager
.GetMasterProfile().getLockMode() == LOCK_MODE_EVERYONE
|| g_passwordManager
.bMasterUser
||
9491 g_passwordManager
.IsDatabasePathUnlocked(musicvideo
.m_strPath
, *CMediaSourceSettings::GetInstance().GetSources("video")))
9493 CFileItemPtr
item(new CFileItem(musicvideo
));
9495 CVideoDbUrl itemUrl
= videoUrl
;
9496 std::string path
= std::to_string(record
->at(0).get_asInt());
9497 itemUrl
.AppendPath(path
);
9498 item
->SetPath(itemUrl
.ToString());
9499 item
->SetDynPath(musicvideo
.m_strFileNameAndPath
);
9501 item
->SetOverlayImage(musicvideo
.GetPlayCount() > 0 ? CGUIListItem::ICON_OVERLAY_WATCHED
9502 : CGUIListItem::ICON_OVERLAY_UNWATCHED
);
9509 if (!strArtist
.empty())
9510 items
.SetProperty("customtitle", strArtist
);
9515 CLog::Log(LOGERROR
, "{} failed", __FUNCTION__
);
9520 unsigned int CVideoDatabase::GetRandomMusicVideoIDs(const std::string
& strWhere
, std::vector
<std::pair
<int,int> > &songIDs
)
9524 if (nullptr == m_pDB
)
9526 if (nullptr == m_pDS
)
9529 std::string strSQL
= "select distinct idMVideo from musicvideo_view";
9530 if (!strWhere
.empty())
9531 strSQL
+= " where " + strWhere
;
9532 strSQL
+= PrepareSQL(" ORDER BY RANDOM()");
9534 if (!m_pDS
->query(strSQL
)) return 0;
9536 if (m_pDS
->num_rows() == 0)
9541 songIDs
.reserve(m_pDS
->num_rows());
9542 while (!m_pDS
->eof())
9544 songIDs
.push_back(std::make_pair
<int,int>(2,m_pDS
->fv(0).get_asInt()));
9548 return songIDs
.size();
9552 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, strWhere
);
9557 int CVideoDatabase::GetMatchingMusicVideo(const std::string
& strArtist
, const std::string
& strAlbum
, const std::string
& strTitle
)
9561 if (nullptr == m_pDB
)
9563 if (nullptr == m_pDS
)
9567 if (strAlbum
.empty() && strTitle
.empty())
9568 { // we want to return matching artists only
9569 if (m_profileManager
.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE
&& !g_passwordManager
.bMasterUser
)
9570 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());
9572 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());
9575 { // we want to return the matching musicvideo
9576 if (m_profileManager
.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE
&& !g_passwordManager
.bMasterUser
)
9577 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());
9579 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());
9581 m_pDS
->query( strSQL
);
9586 if (m_profileManager
.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE
&& !g_passwordManager
.bMasterUser
)
9587 if (!g_passwordManager
.IsDatabasePathUnlocked(m_pDS
->fv("path.strPath").get_asString(),*CMediaSourceSettings::GetInstance().GetSources("video")))
9593 int lResult
= m_pDS
->fv(0).get_asInt();
9599 CLog::Log(LOGERROR
, "{} failed", __FUNCTION__
);
9604 void CVideoDatabase::GetMoviesByName(const std::string
& strSearch
, CFileItemList
& items
)
9610 if (nullptr == m_pDB
)
9612 if (nullptr == m_pDS
)
9615 if (m_profileManager
.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE
&& !g_passwordManager
.bMasterUser
)
9616 strSQL
= PrepareSQL("SELECT movie.idMovie, movie.c%02d, path.strPath, movie.idSet FROM movie "
9617 "INNER JOIN files ON files.idFile=movie.idFile INNER JOIN path ON "
9618 "path.idPath=files.idPath "
9619 "WHERE movie.c%02d LIKE '%%%s%%' OR movie.c%02d LIKE '%%%s%%'",
9620 VIDEODB_ID_TITLE
, VIDEODB_ID_TITLE
, strSearch
.c_str(),
9621 VIDEODB_ID_ORIGINALTITLE
, strSearch
.c_str());
9623 strSQL
= PrepareSQL("SELECT movie.idMovie,movie.c%02d, movie.idSet FROM movie WHERE "
9624 "movie.c%02d like '%%%s%%' OR movie.c%02d LIKE '%%%s%%'",
9625 VIDEODB_ID_TITLE
, VIDEODB_ID_TITLE
, strSearch
.c_str(),
9626 VIDEODB_ID_ORIGINALTITLE
, strSearch
.c_str());
9627 m_pDS
->query( strSQL
);
9629 while (!m_pDS
->eof())
9631 if (m_profileManager
.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE
&& !g_passwordManager
.bMasterUser
)
9632 if (!g_passwordManager
.IsDatabasePathUnlocked(m_pDS
->fv("path.strPath").get_asString(),*CMediaSourceSettings::GetInstance().GetSources("video")))
9638 int movieId
= m_pDS
->fv("movie.idMovie").get_asInt();
9639 int setId
= m_pDS
->fv("movie.idSet").get_asInt();
9640 CFileItemPtr
pItem(new CFileItem(m_pDS
->fv(1).get_asString()));
9642 if (setId
<= 0 || !CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_VIDEOLIBRARY_GROUPMOVIESETS
))
9643 path
= StringUtils::Format("videodb://movies/titles/{}", movieId
);
9645 path
= StringUtils::Format("videodb://movies/sets/{}/{}", setId
, movieId
);
9646 pItem
->SetPath(path
);
9647 pItem
->m_bIsFolder
=false;
9655 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, strSQL
);
9659 void CVideoDatabase::GetTvShowsByName(const std::string
& strSearch
, CFileItemList
& items
)
9665 if (nullptr == m_pDB
)
9667 if (nullptr == m_pDS
)
9670 if (m_profileManager
.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE
&& !g_passwordManager
.bMasterUser
)
9671 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());
9673 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());
9674 m_pDS
->query( strSQL
);
9676 while (!m_pDS
->eof())
9678 if (m_profileManager
.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE
&& !g_passwordManager
.bMasterUser
)
9679 if (!g_passwordManager
.IsDatabasePathUnlocked(m_pDS
->fv("path.strPath").get_asString(),*CMediaSourceSettings::GetInstance().GetSources("video")))
9685 CFileItemPtr
pItem(new CFileItem(m_pDS
->fv(1).get_asString()));
9686 std::string strDir
=
9687 StringUtils::Format("tvshows/titles/{}/", m_pDS
->fv("tvshow.idShow").get_asInt());
9689 pItem
->SetPath("videodb://"+ strDir
);
9690 pItem
->m_bIsFolder
=true;
9691 pItem
->GetVideoInfoTag()->m_iDbId
= m_pDS
->fv("tvshow.idShow").get_asInt();
9699 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, strSQL
);
9703 void CVideoDatabase::GetEpisodesByName(const std::string
& strSearch
, CFileItemList
& items
)
9709 if (nullptr == m_pDB
)
9711 if (nullptr == m_pDS
)
9714 if (m_profileManager
.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE
&& !g_passwordManager
.bMasterUser
)
9715 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());
9717 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());
9718 m_pDS
->query( strSQL
);
9720 while (!m_pDS
->eof())
9722 if (m_profileManager
.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE
&& !g_passwordManager
.bMasterUser
)
9723 if (!g_passwordManager
.IsDatabasePathUnlocked(m_pDS
->fv("path.strPath").get_asString(),*CMediaSourceSettings::GetInstance().GetSources("video")))
9729 CFileItemPtr
pItem(new CFileItem(m_pDS
->fv(1).get_asString()+" ("+m_pDS
->fv(4).get_asString()+")"));
9730 std::string path
= StringUtils::Format("videodb://tvshows/titles/{}/{}/{}",
9731 m_pDS
->fv("episode.idShow").get_asInt(),
9732 m_pDS
->fv(2).get_asInt(), m_pDS
->fv(0).get_asInt());
9733 pItem
->SetPath(path
);
9734 pItem
->m_bIsFolder
=false;
9742 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, strSQL
);
9746 void CVideoDatabase::GetMusicVideosByName(const std::string
& strSearch
, CFileItemList
& items
)
9748 // Alternative searching - not quite as fast though due to
9749 // retrieving all information
9750 // 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()));
9751 // GetMusicVideosByWhere("videodb://musicvideos/titles/", filter, items);
9756 if (nullptr == m_pDB
)
9758 if (nullptr == m_pDS
)
9761 if (m_profileManager
.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE
&& !g_passwordManager
.bMasterUser
)
9762 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());
9764 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());
9765 m_pDS
->query( strSQL
);
9767 while (!m_pDS
->eof())
9769 if (m_profileManager
.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE
&& !g_passwordManager
.bMasterUser
)
9770 if (!g_passwordManager
.IsDatabasePathUnlocked(m_pDS
->fv("path.strPath").get_asString(),*CMediaSourceSettings::GetInstance().GetSources("video")))
9776 CFileItemPtr
pItem(new CFileItem(m_pDS
->fv(1).get_asString()));
9777 std::string strDir
=
9778 StringUtils::Format("3/2/{}", m_pDS
->fv("musicvideo.idMVideo").get_asInt());
9780 pItem
->SetPath("videodb://"+ strDir
);
9781 pItem
->m_bIsFolder
=false;
9789 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, strSQL
);
9793 void CVideoDatabase::GetEpisodesByPlot(const std::string
& strSearch
, CFileItemList
& items
)
9795 // Alternative searching - not quite as fast though due to
9796 // retrieving all information
9798 // 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());
9799 // 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());
9800 // GetEpisodesByWhere("videodb://tvshows/titles/", filter, items);
9806 if (nullptr == m_pDB
)
9808 if (nullptr == m_pDS
)
9811 if (m_profileManager
.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE
&& !g_passwordManager
.bMasterUser
)
9812 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());
9814 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());
9815 m_pDS
->query( strSQL
);
9817 while (!m_pDS
->eof())
9819 if (m_profileManager
.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE
&& !g_passwordManager
.bMasterUser
)
9820 if (!g_passwordManager
.IsDatabasePathUnlocked(m_pDS
->fv("path.strPath").get_asString(),*CMediaSourceSettings::GetInstance().GetSources("video")))
9826 CFileItemPtr
pItem(new CFileItem(m_pDS
->fv(1).get_asString()+" ("+m_pDS
->fv(4).get_asString()+")"));
9827 std::string path
= StringUtils::Format("videodb://tvshows/titles/{}/{}/{}",
9828 m_pDS
->fv("episode.idShow").get_asInt(),
9829 m_pDS
->fv(2).get_asInt(), m_pDS
->fv(0).get_asInt());
9830 pItem
->SetPath(path
);
9831 pItem
->m_bIsFolder
=false;
9839 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, strSQL
);
9843 void CVideoDatabase::GetMoviesByPlot(const std::string
& strSearch
, CFileItemList
& items
)
9849 if (nullptr == m_pDB
)
9851 if (nullptr == m_pDS
)
9854 if (m_profileManager
.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE
&& !g_passwordManager
.bMasterUser
)
9855 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());
9857 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());
9859 m_pDS
->query( strSQL
);
9861 while (!m_pDS
->eof())
9863 if (m_profileManager
.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE
&& !g_passwordManager
.bMasterUser
)
9864 if (!g_passwordManager
.IsDatabasePathUnlocked(m_pDS
->fv(2).get_asString(),*CMediaSourceSettings::GetInstance().GetSources("video")))
9870 CFileItemPtr
pItem(new CFileItem(m_pDS
->fv(1).get_asString()));
9872 StringUtils::Format("videodb://movies/titles/{}", m_pDS
->fv(0).get_asInt());
9873 pItem
->SetPath(path
);
9874 pItem
->m_bIsFolder
=false;
9884 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, strSQL
);
9888 void CVideoDatabase::GetMovieDirectorsByName(const std::string
& strSearch
, CFileItemList
& items
)
9894 if (nullptr == m_pDB
)
9896 if (nullptr == m_pDS
)
9899 if (m_profileManager
.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE
&& !g_passwordManager
.bMasterUser
)
9900 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());
9902 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());
9904 m_pDS
->query( strSQL
);
9906 while (!m_pDS
->eof())
9908 if (m_profileManager
.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE
&& !g_passwordManager
.bMasterUser
)
9909 if (!g_passwordManager
.IsDatabasePathUnlocked(m_pDS
->fv("path.strPath").get_asString(),*CMediaSourceSettings::GetInstance().GetSources("video")))
9915 std::string strDir
= StringUtils::Format("{}/", m_pDS
->fv(0).get_asInt());
9916 CFileItemPtr
pItem(new CFileItem(m_pDS
->fv(1).get_asString()));
9918 pItem
->SetPath("videodb://movies/directors/"+ strDir
);
9919 pItem
->m_bIsFolder
=true;
9927 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, strSQL
);
9931 void CVideoDatabase::GetTvShowsDirectorsByName(const std::string
& strSearch
, CFileItemList
& items
)
9937 if (nullptr == m_pDB
)
9939 if (nullptr == m_pDS
)
9942 if (m_profileManager
.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE
&& !g_passwordManager
.bMasterUser
)
9943 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());
9945 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());
9947 m_pDS
->query( strSQL
);
9949 while (!m_pDS
->eof())
9951 if (m_profileManager
.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE
&& !g_passwordManager
.bMasterUser
)
9952 if (!g_passwordManager
.IsDatabasePathUnlocked(m_pDS
->fv("path.strPath").get_asString(),*CMediaSourceSettings::GetInstance().GetSources("video")))
9958 std::string strDir
= StringUtils::Format("{}/", m_pDS
->fv(0).get_asInt());
9959 CFileItemPtr
pItem(new CFileItem(m_pDS
->fv(1).get_asString()));
9961 pItem
->SetPath("videodb://tvshows/directors/"+ strDir
);
9962 pItem
->m_bIsFolder
=true;
9970 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, strSQL
);
9974 void CVideoDatabase::GetMusicVideoDirectorsByName(const std::string
& strSearch
, CFileItemList
& items
)
9980 if (nullptr == m_pDB
)
9982 if (nullptr == m_pDS
)
9985 if (m_profileManager
.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE
&& !g_passwordManager
.bMasterUser
)
9986 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());
9988 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());
9990 m_pDS
->query( strSQL
);
9992 while (!m_pDS
->eof())
9994 if (m_profileManager
.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE
&& !g_passwordManager
.bMasterUser
)
9995 if (!g_passwordManager
.IsDatabasePathUnlocked(m_pDS
->fv("path.strPath").get_asString(),*CMediaSourceSettings::GetInstance().GetSources("video")))
10001 std::string strDir
= StringUtils::Format("{}/", m_pDS
->fv(0).get_asInt());
10002 CFileItemPtr
pItem(new CFileItem(m_pDS
->fv(1).get_asString()));
10004 pItem
->SetPath("videodb://musicvideos/albums/"+ strDir
);
10005 pItem
->m_bIsFolder
=true;
10013 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, strSQL
);
10017 void CVideoDatabase::CleanDatabase(CGUIDialogProgressBarHandle
* handle
,
10018 const std::set
<int>& paths
,
10021 CGUIDialogProgress
* progress
= NULL
;
10024 if (nullptr == m_pDB
)
10026 if (nullptr == m_pDS
)
10028 if (nullptr == m_pDS2
)
10031 auto start
= std::chrono::steady_clock::now();
10032 CLog::Log(LOGINFO
, "{}: Starting videodatabase cleanup ..", __FUNCTION__
);
10033 CServiceBroker::GetAnnouncementManager()->Announce(ANNOUNCEMENT::VideoLibrary
,
10038 handle
->SetTitle(g_localizeStrings
.Get(700));
10039 handle
->SetText("");
10041 else if (showProgress
)
10043 progress
= CServiceBroker::GetGUI()->GetWindowManager().GetWindow
<CGUIDialogProgress
>(
10044 WINDOW_DIALOG_PROGRESS
);
10047 progress
->SetHeading(CVariant
{700});
10048 progress
->SetLine(0, CVariant
{""});
10049 progress
->SetLine(1, CVariant
{313});
10050 progress
->SetLine(2, CVariant
{330});
10051 progress
->SetPercentage(0);
10053 progress
->ShowProgressBar(true);
10057 BeginTransaction();
10059 // find all the files
10060 std::string sql
= "SELECT files.idFile, files.strFileName, path.strPath FROM files "
10061 "INNER JOIN path ON path.idPath=files.idPath";
10062 if (!paths
.empty())
10064 std::string strPaths
;
10065 for (const auto& i
: paths
)
10066 strPaths
+= StringUtils::Format(",{}", i
);
10067 sql
+= PrepareSQL(" AND path.idPath IN (%s)", strPaths
.substr(1).c_str());
10070 // For directory caching to work properly, we need to sort the files by path
10071 sql
+= " ORDER BY path.strPath";
10073 m_pDS2
->query(sql
);
10074 if (m_pDS2
->num_rows() > 0)
10076 std::string filesToTestForDelete
;
10077 std::vector
<CMediaSource
> videoSources(
10078 *CMediaSourceSettings::GetInstance().GetSources("video"));
10079 CServiceBroker::GetMediaManager().GetRemovableDrives(videoSources
);
10081 int total
= m_pDS2
->num_rows();
10083 std::string lastDir
;
10084 bool gotDir
= true;
10086 while (!m_pDS2
->eof())
10088 std::string path
= m_pDS2
->fv("path.strPath").get_asString();
10089 std::string fileName
= m_pDS2
->fv("files.strFileName").get_asString();
10090 std::string fullPath
;
10091 ConstructPath(fullPath
, path
, fileName
);
10093 // get the first stacked file
10094 if (URIUtils::IsStack(fullPath
))
10095 fullPath
= CStackDirectory::GetFirstStackedFile(fullPath
);
10097 // get the actual archive path
10098 if (URIUtils::IsInArchive(fullPath
))
10099 fullPath
= CURL(fullPath
).GetHostName();
10102 if (URIUtils::IsPlugin(fullPath
))
10104 SScanSettings settings
;
10105 bool foundDirectly
= false;
10106 ScraperPtr scraper
= GetScraperForPath(fullPath
, settings
, foundDirectly
);
10108 CPluginDirectory::CheckExists(TranslateContent(scraper
->Content()), fullPath
))
10113 // Only consider keeping this file if not optical and belonging to a (matching) source
10115 if (!URIUtils::IsOnDVD(fullPath
) &&
10116 CUtil::GetMatchingSource(fullPath
, videoSources
, bIsSource
) >= 0)
10118 const std::string pathDir
= URIUtils::GetDirectory(fullPath
);
10120 // Cache file's directory in case it's different from the previous file
10121 if (lastDir
!= pathDir
)
10124 CFileItemList items
; // Dummy list
10125 gotDir
= CDirectory::GetDirectory(pathDir
, items
, "",
10126 DIR_FLAG_NO_FILE_DIRS
| DIR_FLAG_NO_FILE_INFO
);
10129 // Keep existing files
10130 if (gotDir
&& CFile::Exists(fullPath
, true))
10135 filesToTestForDelete
+= m_pDS2
->fv("files.idFile").get_asString() + ",";
10137 if (handle
== NULL
&& progress
!= NULL
)
10139 int percentage
= current
* 100 / total
;
10140 if (percentage
> progress
->GetPercentage())
10142 progress
->SetPercentage(percentage
);
10143 progress
->Progress();
10145 if (progress
->IsCanceled())
10149 CServiceBroker::GetAnnouncementManager()->Announce(ANNOUNCEMENT::VideoLibrary
,
10150 "OnCleanFinished");
10154 else if (handle
!= NULL
)
10155 handle
->SetPercentage(current
* 100 / (float)total
);
10162 std::string filesToDelete
;
10164 // Add any files that don't have a valid idPath entry to the filesToDelete list.
10165 m_pDS
->query("SELECT files.idFile FROM files WHERE NOT EXISTS (SELECT 1 FROM path "
10166 "WHERE path.idPath = files.idPath)");
10167 while (!m_pDS
->eof())
10169 std::string file
= m_pDS
->fv("files.idFile").get_asString() + ",";
10170 filesToTestForDelete
+= file
;
10171 filesToDelete
+= file
;
10177 std::map
<int, bool> pathsDeleteDecisions
;
10178 std::vector
<int> movieIDs
;
10179 std::vector
<int> tvshowIDs
;
10180 std::vector
<int> episodeIDs
;
10181 std::vector
<int> musicVideoIDs
;
10182 std::vector
<int> videoVersionIDs
;
10184 if (!filesToTestForDelete
.empty())
10186 StringUtils::TrimRight(filesToTestForDelete
, ",");
10188 movieIDs
= CleanMediaType(MediaTypeMovie
, filesToTestForDelete
, pathsDeleteDecisions
,
10189 filesToDelete
, !showProgress
);
10190 episodeIDs
= CleanMediaType(MediaTypeEpisode
, filesToTestForDelete
, pathsDeleteDecisions
,
10191 filesToDelete
, !showProgress
);
10192 musicVideoIDs
= CleanMediaType(MediaTypeMusicVideo
, filesToTestForDelete
,
10193 pathsDeleteDecisions
, filesToDelete
, !showProgress
);
10194 videoVersionIDs
= CleanMediaType(MediaTypeVideoVersion
, filesToTestForDelete
,
10195 pathsDeleteDecisions
, filesToDelete
, !showProgress
);
10198 if (progress
!= NULL
)
10200 progress
->SetPercentage(100);
10201 progress
->Progress();
10204 if (!filesToDelete
.empty())
10206 filesToDelete
= "(" + StringUtils::TrimRight(filesToDelete
, ",") + ")";
10208 // Clean hashes of all paths that files are deleted from
10209 // Otherwise there is a mismatch between the path contents and the hash in the
10210 // database, leading to potentially missed items on re-scan (if deleted files are
10211 // later re-added to a source)
10212 CLog::LogFC(LOGDEBUG
, LOGDATABASE
, "Cleaning path hashes");
10213 m_pDS
->query("SELECT DISTINCT strPath FROM path JOIN files ON files.idPath=path.idPath "
10214 "WHERE files.idFile IN " +
10216 int pathHashCount
= m_pDS
->num_rows();
10217 while (!m_pDS
->eof())
10219 InvalidatePathHash(m_pDS
->fv("strPath").get_asString());
10222 CLog::LogFC(LOGDEBUG
, LOGDATABASE
, "Cleaned {} path hashes", pathHashCount
);
10224 CLog::Log(LOGDEBUG
, LOGDATABASE
, "{}: Cleaning files table", __FUNCTION__
);
10225 sql
= "DELETE FROM files WHERE idFile IN " + filesToDelete
;
10229 if (!movieIDs
.empty())
10231 std::string moviesToDelete
;
10232 for (const auto& i
: movieIDs
)
10233 moviesToDelete
+= StringUtils::Format("{},", i
);
10234 moviesToDelete
= "(" + StringUtils::TrimRight(moviesToDelete
, ",") + ")";
10236 CLog::Log(LOGDEBUG
, LOGDATABASE
, "{}: Cleaning movie table", __FUNCTION__
);
10237 sql
= "DELETE FROM movie WHERE idMovie IN " + moviesToDelete
;
10241 if (!episodeIDs
.empty())
10243 std::string episodesToDelete
;
10244 for (const auto& i
: episodeIDs
)
10245 episodesToDelete
+= StringUtils::Format("{},", i
);
10246 episodesToDelete
= "(" + StringUtils::TrimRight(episodesToDelete
, ",") + ")";
10248 CLog::Log(LOGDEBUG
, LOGDATABASE
, "{}: Cleaning episode table", __FUNCTION__
);
10249 sql
= "DELETE FROM episode WHERE idEpisode IN " + episodesToDelete
;
10253 CLog::Log(LOGDEBUG
, LOGDATABASE
,
10254 "{}: Cleaning paths that don't exist and have content set...", __FUNCTION__
);
10255 sql
= "SELECT path.idPath, path.strPath, path.idParentPath FROM path "
10256 "WHERE NOT ((strContent IS NULL OR strContent = '') "
10257 "AND (strSettings IS NULL OR strSettings = '') "
10258 "AND (strHash IS NULL OR strHash = '') "
10259 "AND (exclude IS NULL OR exclude != 1))";
10260 m_pDS2
->query(sql
);
10261 std::string strIds
;
10262 while (!m_pDS2
->eof())
10264 auto pathsDeleteDecision
= pathsDeleteDecisions
.find(m_pDS2
->fv(0).get_asInt());
10265 // Check if we have a decision for the parent path
10266 auto pathsDeleteDecisionByParent
= pathsDeleteDecisions
.find(m_pDS2
->fv(2).get_asInt());
10267 std::string path
= m_pDS2
->fv(1).get_asString();
10269 bool exists
= false;
10270 if (URIUtils::IsPlugin(path
))
10272 SScanSettings settings
;
10273 bool foundDirectly
= false;
10274 ScraperPtr scraper
= GetScraperForPath(path
, settings
, foundDirectly
);
10275 if (scraper
&& CPluginDirectory::CheckExists(TranslateContent(scraper
->Content()), path
))
10279 exists
= CDirectory::Exists(path
, false);
10281 if (((pathsDeleteDecision
!= pathsDeleteDecisions
.end() && pathsDeleteDecision
->second
) ||
10282 (pathsDeleteDecision
== pathsDeleteDecisions
.end() && !exists
)) &&
10283 ((pathsDeleteDecisionByParent
!= pathsDeleteDecisions
.end() &&
10284 pathsDeleteDecisionByParent
->second
) ||
10285 (pathsDeleteDecisionByParent
== pathsDeleteDecisions
.end())))
10286 strIds
+= m_pDS2
->fv(0).get_asString() + ",";
10292 if (!strIds
.empty())
10294 sql
= PrepareSQL("DELETE FROM path WHERE idPath IN (%s)",
10295 StringUtils::TrimRight(strIds
, ",").c_str());
10297 sql
= "DELETE FROM tvshowlinkpath "
10298 "WHERE NOT EXISTS (SELECT 1 FROM path WHERE path.idPath = tvshowlinkpath.idPath)";
10302 CLog::Log(LOGDEBUG
, LOGDATABASE
, "{}: Cleaning tvshow table", __FUNCTION__
);
10304 std::string tvshowsToDelete
;
10305 sql
= "SELECT idShow FROM tvshow "
10306 "WHERE NOT EXISTS (SELECT 1 FROM tvshowlinkpath WHERE tvshowlinkpath.idShow = "
10309 while (!m_pDS
->eof())
10311 tvshowIDs
.push_back(m_pDS
->fv(0).get_asInt());
10312 tvshowsToDelete
+= m_pDS
->fv(0).get_asString() + ",";
10316 if (!tvshowsToDelete
.empty())
10318 sql
= "DELETE FROM tvshow WHERE idShow IN (" +
10319 StringUtils::TrimRight(tvshowsToDelete
, ",") + ")";
10323 if (!musicVideoIDs
.empty())
10325 std::string musicVideosToDelete
;
10326 for (const auto& i
: musicVideoIDs
)
10327 musicVideosToDelete
+= StringUtils::Format("{},", i
);
10328 musicVideosToDelete
= "(" + StringUtils::TrimRight(musicVideosToDelete
, ",") + ")";
10330 CLog::Log(LOGDEBUG
, LOGDATABASE
, "{}: Cleaning musicvideo table", __FUNCTION__
);
10331 sql
= "DELETE FROM musicvideo WHERE idMVideo IN " + musicVideosToDelete
;
10335 CLog::Log(LOGDEBUG
, LOGDATABASE
, "{}: Cleaning path table", __FUNCTION__
);
10336 sql
= StringUtils::Format(
10337 "DELETE FROM path "
10338 "WHERE (strContent IS NULL OR strContent = '') "
10339 "AND (strSettings IS NULL OR strSettings = '') "
10340 "AND (strHash IS NULL OR strHash = '') "
10341 "AND (exclude IS NULL OR exclude != 1) "
10342 "AND (idParentPath IS NULL OR NOT EXISTS (SELECT 1 FROM (SELECT idPath FROM path) as "
10343 "parentPath WHERE parentPath.idPath = path.idParentPath)) " // MySQL only fix (#5007)
10344 "AND NOT EXISTS (SELECT 1 FROM files WHERE files.idPath = path.idPath) "
10345 "AND NOT EXISTS (SELECT 1 FROM tvshowlinkpath WHERE tvshowlinkpath.idPath = path.idPath) "
10346 "AND NOT EXISTS (SELECT 1 FROM movie WHERE movie.c{:02} = path.idPath) "
10347 "AND NOT EXISTS (SELECT 1 FROM episode WHERE episode.c{:02} = path.idPath) "
10348 "AND NOT EXISTS (SELECT 1 FROM musicvideo WHERE musicvideo.c{:02} = path.idPath)",
10349 VIDEODB_ID_PARENTPATHID
, VIDEODB_ID_EPISODE_PARENTPATHID
,
10350 VIDEODB_ID_MUSICVIDEO_PARENTPATHID
);
10353 CLog::Log(LOGDEBUG
, LOGDATABASE
, "{}: Cleaning genre table", __FUNCTION__
);
10355 "DELETE FROM genre "
10356 "WHERE NOT EXISTS (SELECT 1 FROM genre_link WHERE genre_link.genre_id = genre.genre_id)";
10359 CLog::Log(LOGDEBUG
, LOGDATABASE
, "{}: Cleaning country table", __FUNCTION__
);
10360 sql
= "DELETE FROM country WHERE NOT EXISTS (SELECT 1 FROM country_link WHERE "
10361 "country_link.country_id = country.country_id)";
10364 CLog::Log(LOGDEBUG
, LOGDATABASE
, "{}: Cleaning actor table of actors, directors and writers",
10367 "DELETE FROM actor "
10368 "WHERE NOT EXISTS (SELECT 1 FROM actor_link WHERE actor_link.actor_id = actor.actor_id) "
10369 "AND NOT EXISTS (SELECT 1 FROM director_link WHERE director_link.actor_id = "
10371 "AND NOT EXISTS (SELECT 1 FROM writer_link WHERE writer_link.actor_id = actor.actor_id)";
10374 CLog::Log(LOGDEBUG
, LOGDATABASE
, "{}: Cleaning studio table", __FUNCTION__
);
10375 sql
= "DELETE FROM studio "
10376 "WHERE NOT EXISTS (SELECT 1 FROM studio_link WHERE studio_link.studio_id = "
10377 "studio.studio_id)";
10380 CLog::Log(LOGDEBUG
, LOGDATABASE
, "{}: Cleaning set table", __FUNCTION__
);
10381 sql
= "DELETE FROM sets "
10382 "WHERE NOT EXISTS (SELECT 1 FROM movie WHERE movie.idSet = sets.idSet)";
10385 CommitTransaction();
10388 handle
->SetTitle(g_localizeStrings
.Get(331));
10392 CUtil::DeleteVideoDatabaseDirectoryCache();
10394 auto end
= std::chrono::steady_clock::now();
10395 auto duration
= std::chrono::duration_cast
<std::chrono::milliseconds
>(end
- start
);
10397 CLog::Log(LOGINFO
, "{}: Cleaning videodatabase done. Operation took {} ms", __FUNCTION__
,
10400 for (const auto& i
: movieIDs
)
10401 AnnounceRemove(MediaTypeMovie
, i
, true);
10403 for (const auto& i
: episodeIDs
)
10404 AnnounceRemove(MediaTypeEpisode
, i
, true);
10406 for (const auto& i
: tvshowIDs
)
10407 AnnounceRemove(MediaTypeTvShow
, i
, true);
10409 for (const auto& i
: musicVideoIDs
)
10410 AnnounceRemove(MediaTypeMusicVideo
, i
, true);
10412 for (const auto& i
: videoVersionIDs
)
10413 AnnounceRemove(MediaTypeVideoVersion
, i
, true);
10418 CLog::Log(LOGERROR
, "{} failed", __FUNCTION__
);
10419 RollbackTransaction();
10424 CServiceBroker::GetAnnouncementManager()->Announce(ANNOUNCEMENT::VideoLibrary
, "OnCleanFinished");
10427 std::vector
<int> CVideoDatabase::CleanMediaType(const std::string
&mediaType
, const std::string
&cleanableFileIDs
,
10428 std::map
<int, bool> &pathsDeleteDecisions
, std::string
&deletedFileIDs
, bool silent
)
10430 std::vector
<int> cleanedIDs
;
10431 if (mediaType
.empty() || cleanableFileIDs
.empty())
10434 const std::string
& table
= mediaType
;
10435 std::string idField
;
10436 std::string parentPathIdField
;
10437 bool isEpisode
= false;
10438 if (mediaType
== MediaTypeMovie
)
10440 idField
= "idMovie";
10441 parentPathIdField
= StringUtils::Format("{}.c{:02}", table
, VIDEODB_ID_PARENTPATHID
);
10443 else if (mediaType
== MediaTypeEpisode
)
10445 idField
= "idEpisode";
10446 parentPathIdField
= "showPath.idParentPath";
10449 else if (mediaType
== MediaTypeMusicVideo
)
10451 idField
= "idMVideo";
10452 parentPathIdField
= StringUtils::Format("{}.c{:02}", table
, VIDEODB_ID_MUSICVIDEO_PARENTPATHID
);
10454 else if (mediaType
== MediaTypeVideoVersion
)
10456 idField
= "idMedia";
10457 parentPathIdField
= "path.idPath";
10462 // now grab them media items
10463 std::string sql
= PrepareSQL("SELECT %s.%s, %s.idFile, path.idPath, parentPath.strPath FROM %s "
10464 "JOIN files ON files.idFile = %s.idFile "
10465 "JOIN path ON path.idPath = files.idPath ",
10466 table
.c_str(), idField
.c_str(), table
.c_str(), table
.c_str(),
10470 sql
+= "JOIN tvshowlinkpath ON tvshowlinkpath.idShow = episode.idShow JOIN path AS showPath ON showPath.idPath=tvshowlinkpath.idPath ";
10472 sql
+= PrepareSQL("LEFT JOIN path as parentPath ON parentPath.idPath = %s "
10473 "WHERE %s.idFile IN (%s)",
10474 parentPathIdField
.c_str(),
10475 table
.c_str(), cleanableFileIDs
.c_str());
10477 std::vector
<CMediaSource
> videoSources(*CMediaSourceSettings::GetInstance().GetSources("video"));
10478 CServiceBroker::GetMediaManager().GetRemovableDrives(videoSources
);
10480 // map of parent path ID to boolean pair (if not exists and user choice)
10481 std::map
<int, std::pair
<bool, bool> > sourcePathsDeleteDecisions
;
10482 m_pDS2
->query(sql
);
10483 while (!m_pDS2
->eof())
10486 if (m_pDS2
->fv(3).get_isNull() == false)
10488 std::string parentPath
= m_pDS2
->fv(3).get_asString();
10490 // try to find the source path the parent path belongs to
10491 SScanSettings scanSettings
;
10492 std::string sourcePath
;
10493 GetSourcePath(parentPath
, sourcePath
, scanSettings
);
10495 bool bIsSourceName
;
10496 bool sourceNotFound
= (CUtil::GetMatchingSource(parentPath
, videoSources
, bIsSourceName
) < 0);
10498 if (sourceNotFound
&& sourcePath
.empty())
10499 sourcePath
= parentPath
;
10501 int sourcePathID
= GetPathId(sourcePath
);
10502 auto sourcePathsDeleteDecision
= sourcePathsDeleteDecisions
.find(sourcePathID
);
10503 if (sourcePathsDeleteDecision
== sourcePathsDeleteDecisions
.end())
10505 bool sourcePathNotExists
= (sourceNotFound
|| !CDirectory::Exists(sourcePath
, false));
10506 // if the parent path exists, the file will be deleted without asking
10507 // if the parent path doesn't exist or does not belong to a valid media source,
10508 // ask the user whether to remove all items it contained
10509 if (sourcePathNotExists
)
10511 // in silent mode assume that the files are just temporarily missing
10516 CGUIDialogYesNo
* pDialog
= CServiceBroker::GetGUI()->GetWindowManager().GetWindow
<CGUIDialogYesNo
>(WINDOW_DIALOG_YES_NO
);
10517 if (pDialog
!= NULL
)
10519 CURL
sourceUrl(sourcePath
);
10520 pDialog
->SetHeading(CVariant
{15012});
10521 pDialog
->SetText(CVariant
{StringUtils::Format(g_localizeStrings
.Get(15013),
10522 sourceUrl
.GetWithoutUserDetails())});
10523 pDialog
->SetChoice(0, CVariant
{15015});
10524 pDialog
->SetChoice(1, CVariant
{15014});
10527 del
= !pDialog
->IsConfirmed();
10532 sourcePathsDeleteDecisions
.insert(std::make_pair(sourcePathID
, std::make_pair(sourcePathNotExists
, del
)));
10533 pathsDeleteDecisions
.insert(std::make_pair(sourcePathID
, sourcePathNotExists
&& del
));
10535 // the only reason not to delete the file is if the parent path doesn't
10536 // exist and the user decided to delete all the items it contained
10537 else if (sourcePathsDeleteDecision
->second
.first
&&
10538 !sourcePathsDeleteDecision
->second
.second
)
10541 if (scanSettings
.parent_name
)
10542 pathsDeleteDecisions
.insert(std::make_pair(m_pDS2
->fv(2).get_asInt(), del
));
10547 deletedFileIDs
+= m_pDS2
->fv(1).get_asString() + ",";
10548 cleanedIDs
.push_back(m_pDS2
->fv(0).get_asInt());
10558 void CVideoDatabase::DumpToDummyFiles(const std::string
&path
)
10561 CFileItemList items
;
10562 GetTvShowsByWhere("videodb://tvshows/titles/", CDatabase::Filter(), items
);
10563 std::string showPath
= URIUtils::AddFileToFolder(path
, "shows");
10564 CDirectory::Create(showPath
);
10565 for (int i
= 0; i
< items
.Size(); i
++)
10567 // create a folder in this directory
10568 std::string showName
= CUtil::MakeLegalFileName(items
[i
]->GetVideoInfoTag()->m_strShowTitle
);
10569 std::string TVFolder
= URIUtils::AddFileToFolder(showPath
, showName
);
10570 if (CDirectory::Create(TVFolder
))
10571 { // right - grab the episodes and dump them as well
10572 CFileItemList episodes
;
10573 Filter
filter(PrepareSQL("idShow=%i", items
[i
]->GetVideoInfoTag()->m_iDbId
));
10574 GetEpisodesByWhere("videodb://tvshows/titles/", filter
, episodes
);
10575 for (int i
= 0; i
< episodes
.Size(); i
++)
10577 CVideoInfoTag
*tag
= episodes
[i
]->GetVideoInfoTag();
10578 std::string episode
=
10579 StringUtils::Format("{}.s{:02}e{:02}.avi", showName
, tag
->m_iSeason
, tag
->m_iEpisode
);
10581 std::string episodePath
= URIUtils::AddFileToFolder(TVFolder
, episode
);
10583 if (file
.OpenForWrite(episodePath
))
10590 GetMoviesByWhere("videodb://movies/titles/", CDatabase::Filter(), items
);
10591 std::string moviePath
= URIUtils::AddFileToFolder(path
, "movies");
10592 CDirectory::Create(moviePath
);
10593 for (int i
= 0; i
< items
.Size(); i
++)
10595 CVideoInfoTag
*tag
= items
[i
]->GetVideoInfoTag();
10596 std::string movie
= StringUtils::Format("{}.avi", tag
->m_strTitle
);
10598 if (file
.OpenForWrite(URIUtils::AddFileToFolder(moviePath
, movie
)))
10603 void CVideoDatabase::ExportToXML(const std::string
&path
, bool singleFile
/* = true */, bool images
/* = false */, bool actorThumbs
/* false */, bool overwrite
/*=false*/)
10605 int iFailCount
= 0;
10606 CGUIDialogProgress
*progress
=NULL
;
10609 if (nullptr == m_pDB
)
10611 if (nullptr == m_pDS
)
10613 if (nullptr == m_pDS2
)
10616 // create a 3rd dataset as well as GetEpisodeDetails() etc. uses m_pDS2, and we need to do 3 nested queries on tv shows
10617 std::unique_ptr
<Dataset
> pDS
;
10618 pDS
.reset(m_pDB
->CreateDataset());
10619 if (nullptr == pDS
)
10622 std::unique_ptr
<Dataset
> pDS2
;
10623 pDS2
.reset(m_pDB
->CreateDataset());
10624 if (nullptr == pDS2
)
10627 // if we're exporting to a single folder, we export thumbs as well
10628 std::string exportRoot
= URIUtils::AddFileToFolder(path
, "kodi_videodb_" + CDateTime::GetCurrentDateTime().GetAsDBDate());
10629 std::string xmlFile
= URIUtils::AddFileToFolder(exportRoot
, "videodb.xml");
10630 std::string actorsDir
= URIUtils::AddFileToFolder(exportRoot
, "actors");
10631 std::string moviesDir
= URIUtils::AddFileToFolder(exportRoot
, "movies");
10632 std::string movieSetsDir
= URIUtils::AddFileToFolder(exportRoot
, "moviesets");
10633 std::string musicvideosDir
= URIUtils::AddFileToFolder(exportRoot
, "musicvideos");
10634 std::string tvshowsDir
= URIUtils::AddFileToFolder(exportRoot
, "tvshows");
10639 actorThumbs
= true;
10640 CDirectory::Remove(exportRoot
);
10641 CDirectory::Create(exportRoot
);
10642 CDirectory::Create(actorsDir
);
10643 CDirectory::Create(moviesDir
);
10644 CDirectory::Create(movieSetsDir
);
10645 CDirectory::Create(musicvideosDir
);
10646 CDirectory::Create(tvshowsDir
);
10649 progress
= CServiceBroker::GetGUI()->GetWindowManager().GetWindow
<CGUIDialogProgress
>(WINDOW_DIALOG_PROGRESS
);
10651 std::string sql
= "select * from movie_view";
10657 progress
->SetHeading(CVariant
{647});
10658 progress
->SetLine(0, CVariant
{650});
10659 progress
->SetLine(1, CVariant
{""});
10660 progress
->SetLine(2, CVariant
{""});
10661 progress
->SetPercentage(0);
10663 progress
->ShowProgressBar(true);
10666 int total
= m_pDS
->num_rows();
10669 // create our xml document
10670 CXBMCTinyXML xmlDoc
;
10671 TiXmlDeclaration
decl("1.0", "UTF-8", "yes");
10672 xmlDoc
.InsertEndChild(decl
);
10673 TiXmlNode
*pMain
= NULL
;
10678 TiXmlElement
xmlMainElement("videodb");
10679 pMain
= xmlDoc
.InsertEndChild(xmlMainElement
);
10680 XMLUtils::SetInt(pMain
,"version", GetExportVersion());
10683 while (!m_pDS
->eof())
10685 CVideoInfoTag movie
= GetDetailsForMovie(m_pDS
, VideoDbDetailsAll
);
10686 // strip paths to make them relative
10687 if (StringUtils::StartsWith(movie
.m_strTrailer
, movie
.m_strPath
))
10688 movie
.m_strTrailer
= movie
.m_strTrailer
.substr(movie
.m_strPath
.size());
10689 std::map
<std::string
, std::string
> artwork
;
10690 if (GetArtForItem(movie
.m_iDbId
, movie
.m_type
, artwork
) && singleFile
)
10692 TiXmlElement
additionalNode("art");
10693 for (const auto &i
: artwork
)
10694 XMLUtils::SetString(&additionalNode
, i
.first
.c_str(), i
.second
);
10695 movie
.Save(pMain
, "movie", true, &additionalNode
);
10698 movie
.Save(pMain
, "movie", singleFile
);
10700 // reset old skip state
10701 bool bSkip
= false;
10705 progress
->SetLine(1, CVariant
{movie
.m_strTitle
});
10706 progress
->SetPercentage(current
* 100 / total
);
10707 progress
->Progress();
10708 if (progress
->IsCanceled())
10716 CFileItem
item(movie
.m_strFileNameAndPath
,false);
10717 if (!singleFile
&& CUtil::SupportsWriteFileOperations(movie
.m_strFileNameAndPath
))
10719 if (!item
.Exists(false))
10721 CLog::Log(LOGINFO
, "{} - Not exporting item {} as it does not exist", __FUNCTION__
,
10722 movie
.m_strFileNameAndPath
);
10727 std::string
nfoFile(URIUtils::ReplaceExtension(ART::GetTBNFile(item
), ".nfo"));
10729 if (item
.IsOpticalMediaFile())
10731 nfoFile
= URIUtils::AddFileToFolder(
10732 URIUtils::GetParentPath(nfoFile
),
10733 URIUtils::GetFileName(nfoFile
));
10736 if (overwrite
|| !CFile::Exists(nfoFile
, false))
10738 if(!xmlDoc
.SaveFile(nfoFile
))
10740 CLog::Log(LOGERROR
, "{}: Movie nfo export failed! ('{}')", __FUNCTION__
, nfoFile
);
10741 CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Error
,
10742 g_localizeStrings
.Get(20302),
10743 CURL::GetRedacted(nfoFile
));
10752 TiXmlDeclaration
decl("1.0", "UTF-8", "yes");
10753 xmlDoc
.InsertEndChild(decl
);
10756 if (images
&& !bSkip
)
10760 std::string
strFileName(movie
.m_strTitle
);
10761 if (movie
.HasYear())
10762 strFileName
+= StringUtils::Format("_{}", movie
.GetYear());
10763 item
.SetPath(GetSafeFile(moviesDir
, strFileName
) + ".avi");
10765 for (const auto &i
: artwork
)
10767 std::string savedThumb
= ART::GetLocalArt(item
, i
.first
, false);
10768 CServiceBroker::GetTextureCache()->Export(i
.second
, savedThumb
, overwrite
);
10771 ExportActorThumbs(actorsDir
, movie
, !singleFile
, overwrite
);
10779 movieSetsDir
= CServiceBroker::GetSettingsComponent()->GetSettings()->GetString(
10780 CSettings::SETTING_VIDEOLIBRARY_MOVIESETSFOLDER
);
10781 if (images
&& !movieSetsDir
.empty())
10783 // find all movie sets
10784 sql
= "select idSet, strSet from sets";
10788 total
= m_pDS
->num_rows();
10791 while (!m_pDS
->eof())
10793 std::string title
= m_pDS
->fv("strSet").get_asString();
10797 progress
->SetLine(1, CVariant
{title
});
10798 progress
->SetPercentage(current
* 100 / total
);
10799 progress
->Progress();
10800 if (progress
->IsCanceled())
10808 std::string itemPath
= URIUtils::AddFileToFolder(movieSetsDir
,
10809 CUtil::MakeLegalFileName(title
, LEGAL_WIN32_COMPAT
));
10810 if (CDirectory::Exists(itemPath
) || CDirectory::Create(itemPath
))
10812 std::map
<std::string
, std::string
> artwork
;
10813 GetArtForItem(m_pDS
->fv("idSet").get_asInt(), MediaTypeVideoCollection
, artwork
);
10814 for (const auto& art
: artwork
)
10816 std::string savedThumb
= URIUtils::AddFileToFolder(itemPath
, art
.first
);
10817 CServiceBroker::GetTextureCache()->Export(art
.second
, savedThumb
, overwrite
);
10823 "CVideoDatabase::{} - Not exporting movie set '{}' as could not create folder '{}'",
10824 __FUNCTION__
, title
, itemPath
);
10831 // find all musicvideos
10832 sql
= "select * from musicvideo_view";
10836 total
= m_pDS
->num_rows();
10839 while (!m_pDS
->eof())
10841 CVideoInfoTag movie
= GetDetailsForMusicVideo(m_pDS
, VideoDbDetailsAll
);
10842 std::map
<std::string
, std::string
> artwork
;
10843 if (GetArtForItem(movie
.m_iDbId
, movie
.m_type
, artwork
) && singleFile
)
10845 TiXmlElement
additionalNode("art");
10846 for (const auto &i
: artwork
)
10847 XMLUtils::SetString(&additionalNode
, i
.first
.c_str(), i
.second
);
10848 movie
.Save(pMain
, "musicvideo", true, &additionalNode
);
10851 movie
.Save(pMain
, "musicvideo", singleFile
);
10853 // reset old skip state
10854 bool bSkip
= false;
10858 progress
->SetLine(1, CVariant
{movie
.m_strTitle
});
10859 progress
->SetPercentage(current
* 100 / total
);
10860 progress
->Progress();
10861 if (progress
->IsCanceled())
10869 CFileItem
item(movie
.m_strFileNameAndPath
,false);
10870 if (!singleFile
&& CUtil::SupportsWriteFileOperations(movie
.m_strFileNameAndPath
))
10872 if (!item
.Exists(false))
10874 CLog::Log(LOGINFO
, "{} - Not exporting item {} as it does not exist", __FUNCTION__
,
10875 movie
.m_strFileNameAndPath
);
10880 std::string
nfoFile(URIUtils::ReplaceExtension(ART::GetTBNFile(item
), ".nfo"));
10882 if (overwrite
|| !CFile::Exists(nfoFile
, false))
10884 if(!xmlDoc
.SaveFile(nfoFile
))
10886 CLog::Log(LOGERROR
, "{}: Musicvideo nfo export failed! ('{}')", __FUNCTION__
,
10888 CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Error
,
10889 g_localizeStrings
.Get(20302),
10890 CURL::GetRedacted(nfoFile
));
10899 TiXmlDeclaration
decl("1.0", "UTF-8", "yes");
10900 xmlDoc
.InsertEndChild(decl
);
10902 if (images
&& !bSkip
)
10906 std::string
strFileName(StringUtils::Join(movie
.m_artist
, CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_videoItemSeparator
) + "." + movie
.m_strTitle
);
10907 if (movie
.HasYear())
10908 strFileName
+= StringUtils::Format("_{}", movie
.GetYear());
10909 item
.SetPath(GetSafeFile(musicvideosDir
, strFileName
) + ".avi");
10911 for (const auto &i
: artwork
)
10913 std::string savedThumb
= ART::GetLocalArt(item
, i
.first
, false);
10914 CServiceBroker::GetTextureCache()->Export(i
.second
, savedThumb
, overwrite
);
10922 // repeat for all tvshows
10923 sql
= "SELECT * FROM tvshow_view";
10926 total
= m_pDS
->num_rows();
10929 while (!m_pDS
->eof())
10931 CVideoInfoTag tvshow
= GetDetailsForTvShow(m_pDS
, VideoDbDetailsAll
);
10932 GetTvShowNamedSeasons(tvshow
.m_iDbId
, tvshow
.m_namedSeasons
);
10934 std::map
<int, std::map
<std::string
, std::string
> > seasonArt
;
10935 GetTvShowSeasonArt(tvshow
.m_iDbId
, seasonArt
);
10937 std::map
<std::string
, std::string
> artwork
;
10938 if (GetArtForItem(tvshow
.m_iDbId
, tvshow
.m_type
, artwork
) && singleFile
)
10940 TiXmlElement
additionalNode("art");
10941 for (const auto &i
: artwork
)
10942 XMLUtils::SetString(&additionalNode
, i
.first
.c_str(), i
.second
);
10943 for (const auto &i
: seasonArt
)
10945 TiXmlElement
seasonNode("season");
10946 seasonNode
.SetAttribute("num", i
.first
);
10947 for (const auto &j
: i
.second
)
10948 XMLUtils::SetString(&seasonNode
, j
.first
.c_str(), j
.second
);
10949 additionalNode
.InsertEndChild(seasonNode
);
10951 tvshow
.Save(pMain
, "tvshow", true, &additionalNode
);
10954 tvshow
.Save(pMain
, "tvshow", singleFile
);
10956 // reset old skip state
10957 bool bSkip
= false;
10961 progress
->SetLine(1, CVariant
{tvshow
.m_strTitle
});
10962 progress
->SetPercentage(current
* 100 / total
);
10963 progress
->Progress();
10964 if (progress
->IsCanceled())
10972 CFileItem
item(tvshow
.m_strPath
, true);
10973 if (!singleFile
&& CUtil::SupportsWriteFileOperations(tvshow
.m_strPath
))
10975 if (!item
.Exists(false))
10977 CLog::Log(LOGINFO
, "{} - Not exporting item {} as it does not exist", __FUNCTION__
,
10983 std::string nfoFile
= URIUtils::AddFileToFolder(tvshow
.m_strPath
, "tvshow.nfo");
10985 if (overwrite
|| !CFile::Exists(nfoFile
, false))
10987 if(!xmlDoc
.SaveFile(nfoFile
))
10989 CLog::Log(LOGERROR
, "{}: TVShow nfo export failed! ('{}')", __FUNCTION__
, nfoFile
);
10990 CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Error
,
10991 g_localizeStrings
.Get(20302),
10992 CURL::GetRedacted(nfoFile
));
11001 TiXmlDeclaration
decl("1.0", "UTF-8", "yes");
11002 xmlDoc
.InsertEndChild(decl
);
11004 if (images
&& !bSkip
)
11007 item
.SetPath(GetSafeFile(tvshowsDir
, tvshow
.m_strTitle
));
11009 for (const auto &i
: artwork
)
11011 std::string savedThumb
= ART::GetLocalArt(item
, i
.first
, true);
11012 CServiceBroker::GetTextureCache()->Export(i
.second
, savedThumb
, overwrite
);
11016 ExportActorThumbs(actorsDir
, tvshow
, !singleFile
, overwrite
);
11018 // export season thumbs
11019 for (const auto &i
: seasonArt
)
11021 std::string seasonThumb
;
11023 seasonThumb
= "season-all";
11024 else if (i
.first
== 0)
11025 seasonThumb
= "season-specials";
11027 seasonThumb
= StringUtils::Format("season{:02}", i
.first
);
11028 for (const auto &j
: i
.second
)
11030 std::string
savedThumb(ART::GetLocalArt(item
, seasonThumb
+ "-" + j
.first
, true));
11031 if (!i
.second
.empty())
11032 CServiceBroker::GetTextureCache()->Export(j
.second
, savedThumb
, overwrite
);
11037 // now save the episodes from this show
11038 sql
= PrepareSQL("select * from episode_view where idShow=%i order by strFileName, idEpisode",tvshow
.m_iDbId
);
11040 std::string
showDir(item
.GetPath());
11042 while (!pDS
->eof())
11044 CVideoInfoTag episode
= GetDetailsForEpisode(pDS
, VideoDbDetailsAll
);
11045 std::map
<std::string
, std::string
> artwork
;
11046 if (GetArtForItem(episode
.m_iDbId
, MediaTypeEpisode
, artwork
) && singleFile
)
11048 TiXmlElement
additionalNode("art");
11049 for (const auto &i
: artwork
)
11050 XMLUtils::SetString(&additionalNode
, i
.first
.c_str(), i
.second
);
11051 episode
.Save(pMain
->LastChild(), "episodedetails", true, &additionalNode
);
11053 else if (singleFile
)
11054 episode
.Save(pMain
->LastChild(), "episodedetails", singleFile
);
11056 episode
.Save(pMain
, "episodedetails", singleFile
);
11058 // multi-episode files need dumping to the same XML
11059 while (!singleFile
&& !pDS
->eof() &&
11060 episode
.m_iFileId
== pDS
->fv("idFile").get_asInt())
11062 episode
= GetDetailsForEpisode(pDS
, VideoDbDetailsAll
);
11063 episode
.Save(pMain
, "episodedetails", singleFile
);
11067 // reset old skip state
11068 bool bSkip
= false;
11070 CFileItem
item(episode
.m_strFileNameAndPath
, false);
11071 if (!singleFile
&& CUtil::SupportsWriteFileOperations(episode
.m_strFileNameAndPath
))
11073 if (!item
.Exists(false))
11075 CLog::Log(LOGINFO
, "{} - Not exporting item {} as it does not exist", __FUNCTION__
,
11076 episode
.m_strFileNameAndPath
);
11081 std::string
nfoFile(URIUtils::ReplaceExtension(ART::GetTBNFile(item
), ".nfo"));
11083 if (overwrite
|| !CFile::Exists(nfoFile
, false))
11085 if(!xmlDoc
.SaveFile(nfoFile
))
11087 CLog::Log(LOGERROR
, "{}: Episode nfo export failed! ('{}')", __FUNCTION__
, nfoFile
);
11088 CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Error
,
11089 g_localizeStrings
.Get(20302),
11090 CURL::GetRedacted(nfoFile
));
11099 TiXmlDeclaration
decl("1.0", "UTF-8", "yes");
11100 xmlDoc
.InsertEndChild(decl
);
11103 if (images
&& !bSkip
)
11107 std::string epName
=
11108 StringUtils::Format("s{:02}e{:02}.avi", episode
.m_iSeason
, episode
.m_iEpisode
);
11109 item
.SetPath(URIUtils::AddFileToFolder(showDir
, epName
));
11111 for (const auto &i
: artwork
)
11113 std::string savedThumb
= ART::GetLocalArt(item
, i
.first
, false);
11114 CServiceBroker::GetTextureCache()->Export(i
.second
, savedThumb
, overwrite
);
11117 ExportActorThumbs(actorsDir
, episode
, !singleFile
, overwrite
);
11126 if (!singleFile
&& progress
)
11128 progress
->SetPercentage(100);
11129 progress
->Progress();
11134 // now dump path info
11135 std::set
<std::string
> paths
;
11137 TiXmlElement
xmlPathElement("paths");
11138 TiXmlNode
*pPaths
= pMain
->InsertEndChild(xmlPathElement
);
11139 for (const auto &i
: paths
)
11141 bool foundDirectly
= false;
11142 SScanSettings settings
;
11143 ScraperPtr info
= GetScraperForPath(i
, settings
, foundDirectly
);
11144 if (info
&& foundDirectly
)
11146 TiXmlElement
xmlPathElement2("path");
11147 TiXmlNode
*pPath
= pPaths
->InsertEndChild(xmlPathElement2
);
11148 XMLUtils::SetString(pPath
,"url", i
);
11149 XMLUtils::SetInt(pPath
,"scanrecursive", settings
.recurse
);
11150 XMLUtils::SetBoolean(pPath
,"usefoldernames", settings
.parent_name
);
11151 XMLUtils::SetString(pPath
,"content", TranslateContent(info
->Content()));
11152 XMLUtils::SetString(pPath
,"scraperpath", info
->ID());
11155 xmlDoc
.SaveFile(xmlFile
);
11160 data
["root"] = exportRoot
;
11161 data
["file"] = xmlFile
;
11162 if (iFailCount
> 0)
11163 data
["failcount"] = iFailCount
;
11165 CServiceBroker::GetAnnouncementManager()->Announce(ANNOUNCEMENT::VideoLibrary
, "OnExport",
11170 CLog::Log(LOGERROR
, "{} failed", __FUNCTION__
);
11177 if (iFailCount
> 0)
11178 HELPERS::ShowOKDialogText(
11179 CVariant
{647}, CVariant
{StringUtils::Format(g_localizeStrings
.Get(15011), iFailCount
)});
11182 void CVideoDatabase::ExportActorThumbs(const std::string
&strDir
, const CVideoInfoTag
&tag
, bool singleFiles
, bool overwrite
/*=false*/)
11184 std::string
strPath(strDir
);
11187 strPath
= URIUtils::AddFileToFolder(tag
.m_strPath
, ".actors");
11188 if (!CDirectory::Exists(strPath
))
11190 CDirectory::Create(strPath
);
11191 CFile::SetHidden(strPath
, true);
11195 for (const auto &i
: tag
.m_cast
)
11198 item
.SetLabel(i
.strName
);
11199 if (!i
.thumb
.empty())
11201 std::string
thumbFile(GetSafeFile(strPath
, i
.strName
));
11202 CServiceBroker::GetTextureCache()->Export(i
.thumb
, thumbFile
, overwrite
);
11207 void CVideoDatabase::ImportFromXML(const std::string
&path
)
11209 CGUIDialogProgress
*progress
=NULL
;
11212 if (nullptr == m_pDB
)
11214 if (nullptr == m_pDS
)
11217 CXBMCTinyXML xmlDoc
;
11218 if (!xmlDoc
.LoadFile(URIUtils::AddFileToFolder(path
, "videodb.xml")))
11221 TiXmlElement
*root
= xmlDoc
.RootElement();
11224 progress
= CServiceBroker::GetGUI()->GetWindowManager().GetWindow
<CGUIDialogProgress
>(WINDOW_DIALOG_PROGRESS
);
11227 progress
->SetHeading(CVariant
{648});
11228 progress
->SetLine(0, CVariant
{649});
11229 progress
->SetLine(1, CVariant
{330});
11230 progress
->SetLine(2, CVariant
{""});
11231 progress
->SetPercentage(0);
11233 progress
->ShowProgressBar(true);
11237 XMLUtils::GetInt(root
, "version", iVersion
);
11239 CLog::Log(LOGINFO
, "{}: Starting import (export version = {})", __FUNCTION__
, iVersion
);
11241 TiXmlElement
*movie
= root
->FirstChildElement();
11244 // first count the number of items...
11247 if (StringUtils::CompareNoCase(movie
->Value(), MediaTypeMovie
, 5) == 0 ||
11248 StringUtils::CompareNoCase(movie
->Value(), MediaTypeTvShow
, 6) == 0 ||
11249 StringUtils::CompareNoCase(movie
->Value(), MediaTypeMusicVideo
, 10) == 0)
11251 movie
= movie
->NextSiblingElement();
11254 std::string
actorsDir(URIUtils::AddFileToFolder(path
, "actors"));
11255 std::string
moviesDir(URIUtils::AddFileToFolder(path
, "movies"));
11256 std::string
movieSetsDir(URIUtils::AddFileToFolder(path
, "moviesets"));
11257 std::string
musicvideosDir(URIUtils::AddFileToFolder(path
, "musicvideos"));
11258 std::string
tvshowsDir(URIUtils::AddFileToFolder(path
, "tvshows"));
11259 CVideoInfoScanner scanner
;
11260 // add paths first (so we have scraper settings available)
11261 TiXmlElement
*path
= root
->FirstChildElement("paths");
11262 path
= path
->FirstChildElement();
11265 std::string strPath
;
11266 if (XMLUtils::GetString(path
,"url",strPath
) && !strPath
.empty())
11269 std::string content
;
11270 if (XMLUtils::GetString(path
,"content", content
) && !content
.empty())
11271 { // check the scraper exists, if so store the path
11274 XMLUtils::GetString(path
,"scraperpath",id
);
11275 if (CServiceBroker::GetAddonMgr().GetAddon(id
, addon
, ADDON::OnlyEnabled::CHOICE_YES
))
11277 SScanSettings settings
;
11278 ScraperPtr scraper
= std::dynamic_pointer_cast
<CScraper
>(addon
);
11279 // FIXME: scraper settings are not exported?
11280 scraper
->SetPathSettings(TranslateContent(content
), "");
11281 XMLUtils::GetInt(path
,"scanrecursive",settings
.recurse
);
11282 XMLUtils::GetBoolean(path
,"usefoldernames",settings
.parent_name
);
11283 SetScraperForPath(strPath
,scraper
,settings
);
11286 path
= path
->NextSiblingElement();
11288 movie
= root
->FirstChildElement();
11291 CVideoInfoTag info
;
11292 if (StringUtils::CompareNoCase(movie
->Value(), MediaTypeMovie
, 5) == 0)
11295 CFileItem
item(info
);
11296 bool useFolders
= info
.m_basePath
.empty() ? LookupByFolders(item
.GetPath()) : false;
11297 std::string filename
= info
.m_strTitle
;
11298 if (info
.HasYear())
11299 filename
+= StringUtils::Format("_{}", info
.GetYear());
11300 CFileItem
artItem(item
);
11301 artItem
.SetPath(GetSafeFile(moviesDir
, filename
) + ".avi");
11302 scanner
.GetArtwork(&artItem
, CONTENT_MOVIES
, useFolders
, true, actorsDir
);
11303 item
.SetArt(artItem
.GetArt());
11304 if (!item
.GetVideoInfoTag()->m_set
.title
.empty())
11306 std::string setPath
= URIUtils::AddFileToFolder(movieSetsDir
,
11307 CUtil::MakeLegalFileName(item
.GetVideoInfoTag()->m_set
.title
, LEGAL_WIN32_COMPAT
));
11308 if (CDirectory::Exists(setPath
))
11310 CGUIListItem::ArtMap setArt
;
11311 CFileItem
artItem(setPath
, true);
11312 for (const auto& artType
: CVideoThumbLoader::GetArtTypes(MediaTypeVideoCollection
))
11314 std::string artPath
= CVideoThumbLoader::GetLocalArt(artItem
, artType
, true);
11315 if (!artPath
.empty())
11317 setArt
[artType
] = artPath
;
11320 item
.AppendArt(setArt
, "set");
11323 scanner
.AddVideo(&item
, CONTENT_MOVIES
, useFolders
, true, NULL
, true);
11326 else if (StringUtils::CompareNoCase(movie
->Value(), MediaTypeMusicVideo
, 10) == 0)
11329 CFileItem
item(info
);
11330 bool useFolders
= info
.m_basePath
.empty() ? LookupByFolders(item
.GetPath()) : false;
11331 std::string filename
= StringUtils::Join(info
.m_artist
, CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_videoItemSeparator
) + "." + info
.m_strTitle
;
11332 if (info
.HasYear())
11333 filename
+= StringUtils::Format("_{}", info
.GetYear());
11334 CFileItem
artItem(item
);
11335 artItem
.SetPath(GetSafeFile(musicvideosDir
, filename
) + ".avi");
11336 scanner
.GetArtwork(&artItem
, CONTENT_MUSICVIDEOS
, useFolders
, true, actorsDir
);
11337 item
.SetArt(artItem
.GetArt());
11338 scanner
.AddVideo(&item
, CONTENT_MUSICVIDEOS
, useFolders
, true, NULL
, true);
11341 else if (StringUtils::CompareNoCase(movie
->Value(), MediaTypeTvShow
, 6) == 0)
11343 // load the TV show in. NOTE: This deletes all episodes under the TV Show, which may not be
11344 // what we desire. It may make better sense to only delete (or even better, update) the show information
11346 URIUtils::AddSlashAtEnd(info
.m_strPath
);
11347 DeleteTvShow(info
.m_strPath
);
11348 CFileItem
showItem(info
);
11349 bool useFolders
= info
.m_basePath
.empty() ? LookupByFolders(showItem
.GetPath(), true) : false;
11350 CFileItem
artItem(showItem
);
11351 std::string
artPath(GetSafeFile(tvshowsDir
, info
.m_strTitle
));
11352 artItem
.SetPath(artPath
);
11353 scanner
.GetArtwork(&artItem
, CONTENT_TVSHOWS
, useFolders
, true, actorsDir
);
11354 showItem
.SetArt(artItem
.GetArt());
11355 int showID
= scanner
.AddVideo(&showItem
, CONTENT_TVSHOWS
, useFolders
, true, NULL
, true);
11357 std::map
<int, std::map
<std::string
, std::string
> > seasonArt
;
11358 artItem
.GetVideoInfoTag()->m_strPath
= artPath
;
11359 scanner
.GetSeasonThumbs(*artItem
.GetVideoInfoTag(), seasonArt
, CVideoThumbLoader::GetArtTypes(MediaTypeSeason
), true);
11360 for (const auto &i
: seasonArt
)
11362 int seasonID
= AddSeason(showID
, i
.first
);
11363 SetArtForItem(seasonID
, MediaTypeSeason
, i
.second
);
11366 // now load the episodes
11367 TiXmlElement
*episode
= movie
->FirstChildElement("episodedetails");
11370 // no need to delete the episode info, due to the above deletion
11371 CVideoInfoTag info
;
11372 info
.Load(episode
);
11373 CFileItem
item(info
);
11374 std::string filename
=
11375 StringUtils::Format("s{:02}e{:02}.avi", info
.m_iSeason
, info
.m_iEpisode
);
11376 CFileItem
artItem(item
);
11377 artItem
.SetPath(GetSafeFile(artPath
, filename
));
11378 scanner
.GetArtwork(&artItem
, CONTENT_TVSHOWS
, useFolders
, true, actorsDir
);
11379 item
.SetArt(artItem
.GetArt());
11380 scanner
.AddVideo(&item
,CONTENT_TVSHOWS
, false, false, showItem
.GetVideoInfoTag(), true);
11381 episode
= episode
->NextSiblingElement("episodedetails");
11384 movie
= movie
->NextSiblingElement();
11385 if (progress
&& total
)
11387 progress
->SetPercentage(current
* 100 / total
);
11388 progress
->SetLine(2, CVariant
{info
.m_strTitle
});
11389 progress
->Progress();
11390 if (progress
->IsCanceled())
11400 CLog::Log(LOGERROR
, "{} failed", __FUNCTION__
);
11406 bool CVideoDatabase::ImportArtFromXML(const TiXmlNode
*node
, std::map
<std::string
, std::string
> &artwork
)
11408 if (!node
) return false;
11409 const TiXmlNode
*art
= node
->FirstChild();
11410 while (art
&& art
->FirstChild())
11412 artwork
.insert(make_pair(art
->ValueStr(), art
->FirstChild()->ValueStr()));
11413 art
= art
->NextSibling();
11415 return !artwork
.empty();
11418 void CVideoDatabase::ConstructPath(std::string
& strDest
, const std::string
& strPath
, const std::string
& strFileName
)
11420 if (URIUtils::IsStack(strFileName
) ||
11421 URIUtils::IsInArchive(strFileName
) || URIUtils::IsPlugin(strPath
))
11422 strDest
= strFileName
;
11424 strDest
= URIUtils::AddFileToFolder(strPath
, strFileName
);
11427 void CVideoDatabase::SplitPath(const std::string
& strFileNameAndPath
, std::string
& strPath
, std::string
& strFileName
)
11429 if (URIUtils::IsStack(strFileNameAndPath
) || StringUtils::StartsWithNoCase(strFileNameAndPath
, "rar://") || StringUtils::StartsWithNoCase(strFileNameAndPath
, "zip://"))
11431 URIUtils::GetParentPath(strFileNameAndPath
,strPath
);
11432 strFileName
= strFileNameAndPath
;
11434 else if (URIUtils::IsPlugin(strFileNameAndPath
))
11436 CURL
url(strFileNameAndPath
);
11437 strPath
= url
.GetOptions().empty() ? url
.GetWithoutFilename() : url
.GetWithoutOptions();
11438 strFileName
= strFileNameAndPath
;
11442 URIUtils::Split(strFileNameAndPath
, strPath
, strFileName
);
11443 // Keep protocol options as part of the path
11444 if (URIUtils::IsURL(strFileNameAndPath
))
11446 CURL
url(strFileNameAndPath
);
11447 if (!url
.GetProtocolOptions().empty())
11448 strPath
+= "|" + url
.GetProtocolOptions();
11453 void CVideoDatabase::InvalidatePathHash(const std::string
& strPath
)
11455 SScanSettings settings
;
11456 bool foundDirectly
;
11457 ScraperPtr info
= GetScraperForPath(strPath
,settings
,foundDirectly
);
11458 SetPathHash(strPath
,"");
11461 if (info
->Content() == CONTENT_TVSHOWS
|| (info
->Content() == CONTENT_MOVIES
&& !foundDirectly
)) // if we scan by folder name we need to invalidate parent as well
11463 if (info
->Content() == CONTENT_TVSHOWS
|| settings
.parent_name_root
)
11465 std::string strParent
;
11466 if (URIUtils::GetParentPath(strPath
, strParent
) && (!URIUtils::IsPlugin(strPath
) || !CURL(strParent
).GetHostName().empty()))
11467 SetPathHash(strParent
, "");
11472 bool CVideoDatabase::CommitTransaction()
11474 if (CDatabase::CommitTransaction())
11475 { // number of items in the db has likely changed, so recalculate
11476 GUIINFO::CLibraryGUIInfo
& guiInfo
= CServiceBroker::GetGUI()->GetInfoManager().GetInfoProviders().GetLibraryInfoProvider();
11477 guiInfo
.SetLibraryBool(LIBRARY_HAS_MOVIES
, HasContent(VideoDbContentType::MOVIES
));
11478 guiInfo
.SetLibraryBool(LIBRARY_HAS_TVSHOWS
, HasContent(VideoDbContentType::TVSHOWS
));
11479 guiInfo
.SetLibraryBool(LIBRARY_HAS_MUSICVIDEOS
, HasContent(VideoDbContentType::MUSICVIDEOS
));
11485 bool CVideoDatabase::SetSingleValue(VideoDbContentType type
,
11488 const std::string
& strValue
)
11490 std::string strSQL
;
11493 if (nullptr == m_pDB
|| nullptr == m_pDS
)
11496 std::string strTable
, strField
;
11497 if (type
== VideoDbContentType::MOVIES
)
11499 strTable
= "movie";
11500 strField
= "idMovie";
11502 else if (type
== VideoDbContentType::TVSHOWS
)
11504 strTable
= "tvshow";
11505 strField
= "idShow";
11507 else if (type
== VideoDbContentType::EPISODES
)
11509 strTable
= "episode";
11510 strField
= "idEpisode";
11512 else if (type
== VideoDbContentType::MUSICVIDEOS
)
11514 strTable
= "musicvideo";
11515 strField
= "idMVideo";
11518 if (strTable
.empty())
11521 return SetSingleValue(strTable
, StringUtils::Format("c{:02}", dbField
), strValue
, strField
,
11526 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, strSQL
);
11531 bool CVideoDatabase::SetSingleValue(VideoDbContentType type
,
11534 const std::string
& strValue
)
11536 MediaType mediaType
= DatabaseUtils::MediaTypeFromVideoContentType(type
);
11537 if (mediaType
== MediaTypeNone
)
11540 int dbFieldIndex
= DatabaseUtils::GetField(dbField
, mediaType
);
11541 if (dbFieldIndex
< 0)
11544 return SetSingleValue(type
, dbId
, dbFieldIndex
, strValue
);
11547 bool CVideoDatabase::SetSingleValue(const std::string
&table
, const std::string
&fieldName
, const std::string
&strValue
,
11548 const std::string
&conditionName
/* = "" */, int conditionValue
/* = -1 */)
11550 if (table
.empty() || fieldName
.empty())
11556 if (nullptr == m_pDB
|| nullptr == m_pDS
)
11559 sql
= PrepareSQL("UPDATE %s SET %s='%s'", table
.c_str(), fieldName
.c_str(), strValue
.c_str());
11560 if (!conditionName
.empty())
11561 sql
+= PrepareSQL(" WHERE %s=%u", conditionName
.c_str(), conditionValue
);
11562 if (m_pDS
->exec(sql
) == 0)
11567 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, sql
);
11572 std::string
CVideoDatabase::GetSafeFile(const std::string
&dir
, const std::string
&name
) const
11574 std::string
safeThumb(name
);
11575 StringUtils::Replace(safeThumb
, ' ', '_');
11576 return URIUtils::AddFileToFolder(dir
, CUtil::MakeLegalFileName(std::move(safeThumb
)));
11579 void CVideoDatabase::AnnounceRemove(const std::string
& content
, int id
, bool scanning
/* = false */)
11582 data
["type"] = content
;
11585 data
["transaction"] = true;
11586 CServiceBroker::GetAnnouncementManager()->Announce(ANNOUNCEMENT::VideoLibrary
, "OnRemove", data
);
11589 void CVideoDatabase::AnnounceUpdate(const std::string
& content
, int id
)
11592 data
["type"] = content
;
11594 CServiceBroker::GetAnnouncementManager()->Announce(ANNOUNCEMENT::VideoLibrary
, "OnUpdate", data
);
11597 bool CVideoDatabase::GetItemsForPath(const std::string
&content
, const std::string
&strPath
, CFileItemList
&items
)
11599 const std::string
& path(strPath
);
11601 if(URIUtils::IsMultiPath(path
))
11603 std::vector
<std::string
> paths
;
11604 CMultiPathDirectory::GetPaths(path
, paths
);
11606 for(unsigned i
=0;i
<paths
.size();i
++)
11607 GetItemsForPath(content
, paths
[i
], items
);
11609 return items
.Size() > 0;
11612 int pathID
= GetPathId(path
);
11616 if (content
== "movies")
11618 Filter
filter(PrepareSQL("c%02d=%d", VIDEODB_ID_PARENTPATHID
, pathID
));
11619 GetMoviesByWhere("videodb://movies/titles/", filter
, items
);
11621 else if (content
== "episodes")
11623 Filter
filter(PrepareSQL("c%02d=%d", VIDEODB_ID_EPISODE_PARENTPATHID
, pathID
));
11624 GetEpisodesByWhere("videodb://tvshows/titles/", filter
, items
);
11626 else if (content
== "tvshows")
11628 Filter
filter(PrepareSQL("idParentPath=%d", pathID
));
11629 GetTvShowsByWhere("videodb://tvshows/titles/", filter
, items
);
11631 else if (content
== "musicvideos")
11633 Filter
filter(PrepareSQL("c%02d=%d", VIDEODB_ID_MUSICVIDEO_PARENTPATHID
, pathID
));
11634 GetMusicVideosByWhere("videodb://musicvideos/titles/", filter
, items
);
11636 for (int i
= 0; i
< items
.Size(); i
++)
11637 items
[i
]->SetPath(items
[i
]->GetVideoInfoTag()->m_basePath
);
11638 return items
.Size() > 0;
11641 void CVideoDatabase::AppendIdLinkFilter(const char* field
, const char *table
, const MediaType
& mediaType
, const char *view
, const char *viewKey
, const CUrlOptions::UrlOptions
& options
, Filter
&filter
)
11643 auto option
= options
.find((std::string
)field
+ "id");
11644 if (option
== options
.end())
11647 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()));
11648 filter
.AppendWhere(PrepareSQL("%s_link.%s_id = %i", field
, table
, (int)option
->second
.asInteger()));
11651 void CVideoDatabase::AppendLinkFilter(const char* field
, const char *table
, const MediaType
& mediaType
, const char *view
, const char *viewKey
, const CUrlOptions::UrlOptions
& options
, Filter
&filter
)
11653 auto option
= options
.find(field
);
11654 if (option
== options
.end())
11657 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()));
11658 filter
.AppendJoin(PrepareSQL("JOIN %s ON %s.%s_id=%s_link.%s_id", table
, table
, field
, table
, field
));
11659 filter
.AppendWhere(PrepareSQL("%s.name like '%s'", table
, option
->second
.asString().c_str()));
11662 bool CVideoDatabase::GetFilter(CDbUrl
&videoUrl
, Filter
&filter
, SortDescription
&sorting
)
11664 if (!videoUrl
.IsValid())
11667 std::string type
= videoUrl
.GetType();
11668 std::string itemType
= ((const CVideoDbUrl
&)videoUrl
).GetItemType();
11669 const CUrlOptions::UrlOptions
& options
= videoUrl
.GetOptions();
11671 if (type
== "movies")
11673 AppendIdLinkFilter("genre", "genre", "movie", "movie", "idMovie", options
, filter
);
11674 AppendLinkFilter("genre", "genre", "movie", "movie", "idMovie", options
, filter
);
11676 AppendIdLinkFilter("country", "country", "movie", "movie", "idMovie", options
, filter
);
11677 AppendLinkFilter("country", "country", "movie", "movie", "idMovie", options
, filter
);
11679 AppendIdLinkFilter("studio", "studio", "movie", "movie", "idMovie", options
, filter
);
11680 AppendLinkFilter("studio", "studio", "movie", "movie", "idMovie", options
, filter
);
11682 AppendIdLinkFilter("director", "actor", "movie", "movie", "idMovie", options
, filter
);
11683 AppendLinkFilter("director", "actor", "movie", "movie", "idMovie", options
, filter
);
11685 auto option
= options
.find("year");
11686 if (option
!= options
.end())
11687 filter
.AppendWhere(PrepareSQL("movie_view.premiered like '%i%%'", (int)option
->second
.asInteger()));
11689 AppendIdLinkFilter("actor", "actor", "movie", "movie", "idMovie", options
, filter
);
11690 AppendLinkFilter("actor", "actor", "movie", "movie", "idMovie", options
, filter
);
11692 option
= options
.find("setid");
11693 if (option
!= options
.end())
11694 filter
.AppendWhere(PrepareSQL("movie_view.idSet = %i", (int)option
->second
.asInteger()));
11696 option
= options
.find("set");
11697 if (option
!= options
.end())
11698 filter
.AppendWhere(PrepareSQL("movie_view.strSet LIKE '%s'", option
->second
.asString().c_str()));
11700 option
= options
.find("videoversionid");
11701 if (option
!= options
.end())
11703 const int idVideoVersion
{static_cast<int>(option
->second
.asInteger())};
11704 if (idVideoVersion
> 0)
11705 filter
.AppendWhere(PrepareSQL("videoVersionTypeId = %i", idVideoVersion
));
11708 option
= options
.find("mediaid");
11709 if (option
!= options
.end())
11711 const int mediaId
{static_cast<int>(option
->second
.asInteger())};
11713 filter
.AppendWhere(PrepareSQL("idMovie = %i", mediaId
));
11719 filter
.AppendWhere("isDefaultVersion = 1");
11722 AppendIdLinkFilter("tag", "tag", "movie", "movie", "idMovie", options
, filter
);
11723 AppendLinkFilter("tag", "tag", "movie", "movie", "idMovie", options
, filter
);
11725 else if (type
== "tvshows")
11727 if (itemType
== "tvshows")
11729 AppendIdLinkFilter("genre", "genre", "tvshow", "tvshow", "idShow", options
, filter
);
11730 AppendLinkFilter("genre", "genre", "tvshow", "tvshow", "idShow", options
, filter
);
11732 AppendIdLinkFilter("studio", "studio", "tvshow", "tvshow", "idShow", options
, filter
);
11733 AppendLinkFilter("studio", "studio", "tvshow", "tvshow", "idShow", options
, filter
);
11735 AppendIdLinkFilter("director", "actor", "tvshow", "tvshow", "idShow", options
, filter
);
11737 auto option
= options
.find("year");
11738 if (option
!= options
.end())
11739 filter
.AppendWhere(PrepareSQL("tvshow_view.c%02d like '%%%i%%'", VIDEODB_ID_TV_PREMIERED
, (int)option
->second
.asInteger()));
11741 AppendIdLinkFilter("actor", "actor", "tvshow", "tvshow", "idShow", options
, filter
);
11742 AppendLinkFilter("actor", "actor", "tvshow", "tvshow", "idShow", options
, filter
);
11744 AppendIdLinkFilter("tag", "tag", "tvshow", "tvshow", "idShow", options
, filter
);
11745 AppendLinkFilter("tag", "tag", "tvshow", "tvshow", "idShow", options
, filter
);
11747 else if (itemType
== "seasons")
11749 auto option
= options
.find("tvshowid");
11750 if (option
!= options
.end())
11751 filter
.AppendWhere(PrepareSQL("season_view.idShow = %i", (int)option
->second
.asInteger()));
11753 AppendIdLinkFilter("genre", "genre", "tvshow", "season", "idShow", options
, filter
);
11755 AppendIdLinkFilter("director", "actor", "tvshow", "season", "idShow", options
, filter
);
11757 option
= options
.find("year");
11758 if (option
!= options
.end())
11759 filter
.AppendWhere(PrepareSQL("season_view.premiered like '%%%i%%'", (int)option
->second
.asInteger()));
11761 AppendIdLinkFilter("actor", "actor", "tvshow", "season", "idShow", options
, filter
);
11763 else if (itemType
== "episodes")
11766 auto option
= options
.find("tvshowid");
11767 if (option
!= options
.end())
11768 idShow
= (int)option
->second
.asInteger();
11771 option
= options
.find("season");
11772 if (option
!= options
.end())
11773 season
= (int)option
->second
.asInteger();
11777 bool condition
= false;
11779 AppendIdLinkFilter("genre", "genre", "tvshow", "episode", "idShow", options
, filter
);
11780 AppendLinkFilter("genre", "genre", "tvshow", "episode", "idShow", options
, filter
);
11782 AppendIdLinkFilter("director", "actor", "tvshow", "episode", "idShow", options
, filter
);
11783 AppendLinkFilter("director", "actor", "tvshow", "episode", "idShow", options
, filter
);
11785 option
= options
.find("year");
11786 if (option
!= options
.end())
11789 filter
.AppendWhere(PrepareSQL("episode_view.idShow = %i and episode_view.premiered like '%%%i%%'", idShow
, (int)option
->second
.asInteger()));
11792 AppendIdLinkFilter("actor", "actor", "tvshow", "episode", "idShow", options
, filter
);
11793 AppendLinkFilter("actor", "actor", "tvshow", "episode", "idShow", options
, filter
);
11796 filter
.AppendWhere(PrepareSQL("episode_view.idShow = %i", idShow
));
11800 if (season
== 0) // season = 0 indicates a special - we grab all specials here (see below)
11801 filter
.AppendWhere(PrepareSQL("episode_view.c%02d = %i", VIDEODB_ID_EPISODE_SEASON
, season
));
11803 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)))",
11804 VIDEODB_ID_EPISODE_SEASON
, season
, VIDEODB_ID_EPISODE_SEASON
, VIDEODB_ID_EPISODE_SORTSEASON
, VIDEODB_ID_EPISODE_SORTSEASON
, season
));
11809 option
= options
.find("year");
11810 if (option
!= options
.end())
11811 filter
.AppendWhere(PrepareSQL("episode_view.premiered like '%%%i%%'", (int)option
->second
.asInteger()));
11813 AppendIdLinkFilter("director", "actor", "episode", "episode", "idEpisode", options
, filter
);
11814 AppendLinkFilter("director", "actor", "episode", "episode", "idEpisode", options
, filter
);
11818 else if (type
== "musicvideos")
11820 AppendIdLinkFilter("genre", "genre", "musicvideo", "musicvideo", "idMVideo", options
, filter
);
11821 AppendLinkFilter("genre", "genre", "musicvideo", "musicvideo", "idMVideo", options
, filter
);
11823 AppendIdLinkFilter("studio", "studio", "musicvideo", "musicvideo", "idMVideo", options
, filter
);
11824 AppendLinkFilter("studio", "studio", "musicvideo", "musicvideo", "idMVideo", options
, filter
);
11826 AppendIdLinkFilter("director", "actor", "musicvideo", "musicvideo", "idMVideo", options
, filter
);
11827 AppendLinkFilter("director", "actor", "musicvideo", "musicvideo", "idMVideo", options
, filter
);
11829 auto option
= options
.find("year");
11830 if (option
!= options
.end())
11831 filter
.AppendWhere(PrepareSQL("musicvideo_view.premiered like '%i%%'", (int)option
->second
.asInteger()));
11833 option
= options
.find("artistid");
11834 if (option
!= options
.end())
11836 if (itemType
!= "albums")
11837 filter
.AppendJoin(PrepareSQL("JOIN actor_link ON actor_link.media_id=musicvideo_view.idMVideo AND actor_link.media_type='musicvideo'"));
11838 filter
.AppendWhere(PrepareSQL("actor_link.actor_id = %i", (int)option
->second
.asInteger()));
11841 option
= options
.find("artist");
11842 if (option
!= options
.end())
11844 if (itemType
!= "albums")
11846 filter
.AppendJoin(PrepareSQL("JOIN actor_link ON actor_link.media_id=musicvideo_view.idMVideo AND actor_link.media_type='musicvideo'"));
11847 filter
.AppendJoin(PrepareSQL("JOIN actor ON actor.actor_id=actor_link.actor_id"));
11849 filter
.AppendWhere(PrepareSQL("actor.name LIKE '%s'", option
->second
.asString().c_str()));
11852 option
= options
.find("albumid");
11853 if (option
!= options
.end())
11854 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()));
11856 AppendIdLinkFilter("tag", "tag", "musicvideo", "musicvideo", "idMVideo", options
, filter
);
11857 AppendLinkFilter("tag", "tag", "musicvideo", "musicvideo", "idMVideo", options
, filter
);
11862 auto option
= options
.find("xsp");
11863 if (option
!= options
.end())
11865 PLAYLIST::CSmartPlaylist xsp
;
11866 if (!xsp
.LoadFromJson(option
->second
.asString()))
11869 // check if the filter playlist matches the item type
11870 if (xsp
.GetType() == itemType
||
11871 (xsp
.GetGroup() == itemType
&& !xsp
.IsGroupMixed()) ||
11872 // handle episode listings with videodb://tvshows/titles/ which get the rest
11873 // of the path (season and episodeid) appended later
11874 (xsp
.GetType() == "episodes" && itemType
== "tvshows"))
11876 std::set
<std::string
> playlists
;
11877 filter
.AppendWhere(xsp
.GetWhereClause(*this, playlists
));
11879 if (xsp
.GetLimit() > 0)
11880 sorting
.limitEnd
= xsp
.GetLimit();
11881 if (xsp
.GetOrder() != SortByNone
)
11882 sorting
.sortBy
= xsp
.GetOrder();
11883 if (xsp
.GetOrderDirection() != SortOrderNone
)
11884 sorting
.sortOrder
= xsp
.GetOrderDirection();
11885 if (CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_FILELISTS_IGNORETHEWHENSORTING
))
11886 sorting
.sortAttributes
= SortAttributeIgnoreArticle
;
11890 option
= options
.find("filter");
11891 if (option
!= options
.end())
11893 PLAYLIST::CSmartPlaylist xspFilter
;
11894 if (!xspFilter
.LoadFromJson(option
->second
.asString()))
11897 // check if the filter playlist matches the item type
11898 if (xspFilter
.GetType() == itemType
)
11900 std::set
<std::string
> playlists
;
11901 filter
.AppendWhere(xspFilter
.GetWhereClause(*this, playlists
));
11903 // remove the filter if it doesn't match the item type
11905 videoUrl
.RemoveOption("filter");
11911 bool CVideoDatabase::SetVideoUserRating(int dbId
, int rating
, const MediaType
& mediaType
)
11915 if (nullptr == m_pDB
)
11917 if (nullptr == m_pDS
)
11920 if (mediaType
== MediaTypeNone
)
11924 if (mediaType
== MediaTypeMovie
)
11925 sql
= PrepareSQL("UPDATE movie SET userrating=%i WHERE idMovie = %i", rating
, dbId
);
11926 else if (mediaType
== MediaTypeEpisode
)
11927 sql
= PrepareSQL("UPDATE episode SET userrating=%i WHERE idEpisode = %i", rating
, dbId
);
11928 else if (mediaType
== MediaTypeMusicVideo
)
11929 sql
= PrepareSQL("UPDATE musicvideo SET userrating=%i WHERE idMVideo = %i", rating
, dbId
);
11930 else if (mediaType
== MediaTypeTvShow
)
11931 sql
= PrepareSQL("UPDATE tvshow SET userrating=%i WHERE idShow = %i", rating
, dbId
);
11932 else if (mediaType
== MediaTypeSeason
)
11933 sql
= PrepareSQL("UPDATE seasons SET userrating=%i WHERE idSeason = %i", rating
, dbId
);
11940 CLog::Log(LOGERROR
, "{} ({}, {}, {}) failed", __FUNCTION__
, dbId
, mediaType
, rating
);
11945 CDateTime
CVideoDatabase::GetDateAdded(const std::string
& filename
,
11946 CDateTime dateAdded
/* = CDateTime() */)
11948 if (!dateAdded
.IsValid())
11950 // suppress warnings if we have plugin source
11951 if (!URIUtils::IsPlugin(filename
))
11953 const auto dateAddedSetting
=
11954 CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_iVideoLibraryDateAdded
;
11956 // 1 prefer using the files mtime (if it's valid) and
11957 // only use the file's ctime if mtime isn't valid
11958 if (dateAddedSetting
== 1)
11959 dateAdded
= CFileUtils::GetModificationDate(filename
, false);
11960 // 2 use the newer datetime of the file's mtime and ctime
11961 else if (dateAddedSetting
== 2)
11962 dateAdded
= CFileUtils::GetModificationDate(filename
, true);
11965 // 0 use the current datetime if non of the above match or one returns an invalid datetime
11966 if (!dateAdded
.IsValid())
11967 dateAdded
= CDateTime::GetCurrentDateTime();
11973 void CVideoDatabase::EraseAllForPath(const std::string
& path
)
11977 std::string itemsToDelete
;
11979 PrepareSQL("SELECT files.idFile FROM files WHERE idFile IN (SELECT idFile FROM files INNER "
11980 "JOIN path ON path.idPath = files.idPath AND path.strPath LIKE \"%s%%\")",
11984 while (!m_pDS
->eof())
11986 std::string file
= m_pDS
->fv("files.idFile").get_asString() + ",";
11987 itemsToDelete
+= file
;
11992 sql
= PrepareSQL("DELETE FROM path WHERE strPath LIKE \"%s%%\"", path
.c_str());
11995 if (!itemsToDelete
.empty())
11997 itemsToDelete
= "(" + StringUtils::TrimRight(itemsToDelete
, ",") + ")";
11999 sql
= "DELETE FROM files WHERE idFile IN " + itemsToDelete
;
12002 sql
= "DELETE FROM settings WHERE idFile IN " + itemsToDelete
;
12005 sql
= "DELETE FROM bookmark WHERE idFile IN " + itemsToDelete
;
12008 sql
= "DELETE FROM streamdetails WHERE idFile IN " + itemsToDelete
;
12014 CLog::Log(LOGERROR
, "{} failed", __FUNCTION__
);
12018 void CVideoDatabase::EraseAllForFile(const std::string
& fileNameAndPath
)
12022 const int fileId
{GetFileId(fileNameAndPath
)};
12025 std::string sql
= PrepareSQL("DELETE FROM settings WHERE idFile = %i", fileId
);
12028 sql
= PrepareSQL("DELETE FROM bookmark WHERE idFile = %i", fileId
);
12031 sql
= PrepareSQL("DELETE FROM streamdetails WHERE idFile = %i", fileId
);
12034 sql
= PrepareSQL("DELETE FROM files WHERE idFile = %i", fileId
);
12038 std::string fileName
;
12039 SplitPath(fileNameAndPath
, path
, fileName
);
12040 const int pathId
{GetPathId(path
)};
12043 sql
= PrepareSQL("DELETE FROM path WHERE idPath = %i "
12044 "AND NOT EXISTS (SELECT 1 FROM files WHERE files.idPath = %i)",
12052 CLog::Log(LOGERROR
, "{} failed", __FUNCTION__
);
12056 std::string
CVideoDatabase::GetVideoItemTitle(VideoDbContentType itemType
, int dbId
)
12060 case VideoDbContentType::MOVIES
:
12061 return GetMovieTitle(dbId
);
12067 void CVideoDatabase::InitializeVideoVersionTypeTable(int schemaVersion
)
12069 assert(m_pDB
->in_transaction());
12073 for (int id
= VIDEO_VERSION_ID_BEGIN
; id
<= VIDEO_VERSION_ID_END
; ++id
)
12075 // Exclude removed pre-populated "quality" values
12076 if (id
== 40405 || (id
>= 40418 && id
<= 40430))
12079 const std::string
& type
{g_localizeStrings
.Get(id
)};
12080 if (schemaVersion
< 127)
12083 PrepareSQL("INSERT INTO videoversiontype (id, name, owner) VALUES(%i, '%s', %i)", id
,
12084 type
.c_str(), VideoAssetTypeOwner::SYSTEM
));
12088 m_pDS
->exec(PrepareSQL(
12089 "INSERT INTO videoversiontype (id, name, owner, itemType) VALUES(%i, '%s', %i, %i)", id
,
12090 type
.c_str(), VideoAssetTypeOwner::SYSTEM
, VideoAssetType::VERSION
));
12096 CLog::LogF(LOGERROR
, "failed");
12101 void CVideoDatabase::UpdateVideoVersionTypeTable()
12105 BeginTransaction();
12107 for (int id
= VIDEO_VERSION_ID_BEGIN
; id
<= VIDEO_VERSION_ID_END
; ++id
)
12109 const std::string
& type
= g_localizeStrings
.Get(id
);
12110 m_pDS
->exec(PrepareSQL("UPDATE videoversiontype SET name = '%s', owner = %i WHERE id = '%i'",
12111 type
.c_str(), VideoAssetTypeOwner::SYSTEM
, id
));
12114 CommitTransaction();
12118 CLog::Log(LOGERROR
, "{} failed", __FUNCTION__
);
12119 RollbackTransaction();
12123 int CVideoDatabase::AddVideoVersionType(const std::string
& typeVideoVersion
,
12124 VideoAssetTypeOwner owner
,
12125 VideoAssetType assetType
)
12127 if (typeVideoVersion
.empty())
12134 if (!m_pDB
|| !m_pDS
)
12137 m_pDS
->query(PrepareSQL(
12138 "SELECT id, owner, itemType FROM videoversiontype WHERE name = '%s' AND itemtype = %i",
12139 typeVideoVersion
.c_str(), assetType
));
12140 if (m_pDS
->num_rows() == 0)
12142 m_pDS
->exec(PrepareSQL("INSERT INTO videoversiontype (id, name, owner, itemType) "
12143 "VALUES(NULL, '%s', %i, %i)",
12144 typeVideoVersion
.c_str(), owner
, assetType
));
12145 id
= static_cast<int>(m_pDS
->lastinsertid());
12149 id
= m_pDS
->fv("id").get_asInt();
12151 // if user is adding an existing version type, overwrite the existing non-system one
12152 VideoAssetTypeOwner oldOwner
=
12153 static_cast<VideoAssetTypeOwner
>(m_pDS
->fv("owner").get_asInt());
12154 if (oldOwner
!= VideoAssetTypeOwner::SYSTEM
&& owner
== VideoAssetTypeOwner::USER
)
12156 m_pDS
->exec(PrepareSQL("UPDATE videoversiontype SET owner = %i WHERE id = %i", owner
, id
));
12162 CLog::Log(LOGERROR
, "{} failed to add video version {}", __FUNCTION__
, typeVideoVersion
);
12168 void CVideoDatabase::GetVideoVersions(VideoDbContentType itemType
,
12170 CFileItemList
& items
,
12171 VideoAssetType videoAssetType
)
12173 if (!m_pDB
|| !m_pDS2
)
12176 MediaType mediaType
;
12178 if (itemType
== VideoDbContentType::MOVIES
)
12179 mediaType
= MediaTypeMovie
;
12185 m_pDS2
->query(PrepareSQL("SELECT videoversiontype.name AS name,"
12186 " videoversiontype.id AS id,"
12187 " videoversion.idFile AS idFile "
12188 "FROM videoversiontype"
12189 " JOIN videoversion ON"
12190 " videoversion.idType = videoversiontype.id "
12191 "WHERE videoversion.idMedia = %i AND videoversion.media_type = '%s' "
12192 "AND videoversion.itemType = %i",
12193 dbId
, mediaType
.c_str(), videoAssetType
));
12195 std::vector
<std::tuple
<std::string
, int, int>> versions
;
12197 while (!m_pDS2
->eof())
12199 versions
.emplace_back(m_pDS2
->fv("name").get_asString(), m_pDS2
->fv("id").get_asInt(),
12200 m_pDS2
->fv("idFile").get_asInt());
12205 CFileItem videoItem
;
12206 GetDetailsByTypeAndId(videoItem
, itemType
, dbId
);
12208 for (auto& version
: versions
)
12210 std::string name
= std::get
<0>(version
);
12211 int id
= std::get
<1>(version
);
12212 int idFile
= std::get
<2>(version
);
12214 CVideoInfoTag infoTag
;
12215 if (GetFileInfo("", infoTag
, idFile
))
12217 infoTag
.m_type
= MediaTypeVideoVersion
;
12218 infoTag
.m_iDbId
= idFile
;
12219 infoTag
.GetAssetInfo().SetId(id
);
12220 infoTag
.GetAssetInfo().SetTitle(name
);
12221 infoTag
.GetAssetInfo().SetType(videoAssetType
);
12222 infoTag
.m_strTitle
= name
;
12224 infoTag
.m_strPictureURL
= videoItem
.GetVideoInfoTag()->m_strPictureURL
;
12225 infoTag
.m_fanart
= videoItem
.GetVideoInfoTag()->m_fanart
;
12227 auto item(std::make_shared
<CFileItem
>(infoTag
));
12228 item
->m_strTitle
= name
;
12229 item
->SetLabel(name
);
12231 CVideoDbUrl itemUrl
;
12232 if (itemUrl
.FromString(StringUtils::Format("videodb://{}/videoversions/{}",
12233 CMediaTypes::ToPlural(mediaType
), id
)))
12235 itemUrl
.AddOption("mediaid", dbId
);
12236 item
->SetPath(itemUrl
.ToString());
12239 item
->SetDynPath(infoTag
.m_strFileNameAndPath
);
12241 item
->SetOverlayImage(GetPlayCount(idFile
) > 0 ? CGUIListItem::ICON_OVERLAY_WATCHED
12242 : CGUIListItem::ICON_OVERLAY_UNWATCHED
);
12250 CLog::Log(LOGERROR
, "{} failed for {} {}", __FUNCTION__
, mediaType
, dbId
);
12254 void CVideoDatabase::GetDefaultVideoVersion(VideoDbContentType itemType
, int dbId
, CFileItem
& item
)
12256 if (!m_pDB
|| !m_pDS
)
12259 MediaType mediaType
;
12260 std::string strSQL
;
12262 if (itemType
== VideoDbContentType::MOVIES
)
12264 mediaType
= MediaTypeMovie
;
12265 strSQL
= PrepareSQL("SELECT videoversiontype.name AS name,"
12266 " videoversiontype.id AS id,"
12267 " videoversion.idFile AS idFile,"
12268 " videoversion.itemType AS itemType "
12269 "FROM videoversiontype"
12270 " JOIN videoversion ON"
12271 " videoversion.idType = videoversiontype.id"
12273 " movie.idFile = videoversion.idFile "
12274 "WHERE movie.idMovie = %i",
12282 m_pDS
->query(strSQL
);
12286 std::string name
= m_pDS
->fv("name").get_asString();
12287 int id
= m_pDS
->fv("id").get_asInt();
12288 int idFile
= m_pDS
->fv("idFile").get_asInt();
12289 const auto videoAssetType
{static_cast<VideoAssetType
>(m_pDS
->fv("itemType").get_asInt())};
12290 CVideoInfoTag infoTag
;
12291 if (GetFileInfo("", infoTag
, idFile
))
12293 infoTag
.m_type
= MediaTypeVideoVersion
;
12294 infoTag
.m_iDbId
= idFile
;
12295 infoTag
.GetAssetInfo().SetId(id
);
12296 infoTag
.GetAssetInfo().SetTitle(name
);
12297 infoTag
.GetAssetInfo().SetType(videoAssetType
);
12298 infoTag
.m_strTitle
= name
;
12300 item
.SetFromVideoInfoTag(infoTag
);
12301 item
.m_strTitle
= name
;
12302 item
.SetLabel(name
);
12309 CLog::Log(LOGERROR
, "{} failed for {} {}", __FUNCTION__
, mediaType
, dbId
);
12313 bool CVideoDatabase::UpdateAssetsOwner(const std::string
& mediaType
, int dbIdSource
, int dbIdTarget
)
12315 if (dbIdSource
!= dbIdTarget
)
12317 return ExecuteQuery(
12318 PrepareSQL("UPDATE videoversion SET idMedia = %i WHERE idMedia = %i AND media_type = '%s'",
12319 dbIdTarget
, dbIdSource
, mediaType
.c_str()));
12324 bool CVideoDatabase::FillMovieItem(std::unique_ptr
<Dataset
>& dataset
, int movieId
, CFileItem
& item
)
12326 CVideoInfoTag infoTag
{GetDetailsForMovie(dataset
)};
12327 if (infoTag
.IsEmpty())
12329 CLog::LogF(LOGERROR
, "Unable to fill movie item with id '{}'!", movieId
);
12333 item
.SetFromVideoInfoTag(infoTag
);
12335 CVideoDbUrl itemUrl
;
12336 itemUrl
.FromString(
12337 StringUtils::Format("videodb://movies/videoversions/{}", infoTag
.GetAssetInfo().GetId()));
12338 itemUrl
.AppendPath(std::to_string(movieId
));
12339 itemUrl
.AddOption("mediaid", movieId
);
12340 item
.SetPath(itemUrl
.ToString());
12341 item
.SetDynPath(infoTag
.m_strFileNameAndPath
);
12345 bool CVideoDatabase::GetAssetsForVideo(VideoDbContentType itemType
,
12347 VideoAssetType assetType
,
12348 CFileItemList
& items
)
12350 if (assetType
!= VideoAssetType::VERSION
)
12352 //! @todo add bool return type to GetVideoVersions
12353 GetVideoVersions(itemType
, mediaId
, items
, assetType
);
12357 if (!m_pDB
|| !m_pDS
)
12360 MediaType mediaType
;
12362 if (itemType
== VideoDbContentType::MOVIES
)
12363 mediaType
= MediaTypeMovie
;
12366 CLog::LogF(LOGERROR
, "Unsupported item type '{}'!", static_cast<int>(itemType
));
12373 PrepareSQL("SELECT * FROM movie_view WHERE idMovie = %i AND videoVersionTypeItemType = %i",
12374 mediaId
, assetType
));
12378 CLog::LogF(LOGERROR
, "Query returned no data!");
12382 while (!m_pDS
->eof())
12384 const auto item
{std::make_shared
<CFileItem
>()};
12385 if (FillMovieItem(m_pDS
, mediaId
, *item
))
12394 CLog::LogF(LOGERROR
, "Execution failed for {} {}", mediaType
, mediaId
);
12400 bool CVideoDatabase::GetDefaultVersionForVideo(VideoDbContentType itemType
,
12404 if (!m_pDB
|| !m_pDS
)
12407 MediaType mediaType
;
12409 if (itemType
== VideoDbContentType::MOVIES
)
12410 mediaType
= MediaTypeMovie
;
12413 CLog::LogF(LOGERROR
, "Unsupported item type '{}'!", static_cast<int>(itemType
));
12419 m_pDS
->query(PrepareSQL("SELECT * FROM movie_view WHERE idMovie = %i AND "
12420 "videoVersionTypeItemType = %i AND isDefaultVersion = 1",
12421 mediaId
, VideoAssetType::VERSION
));
12424 CLog::LogF(LOGERROR
, "Query returned no data!");
12428 if (!FillMovieItem(m_pDS
, mediaId
, item
))
12433 CLog::LogF(LOGERROR
, "Execution failed for {} {}", mediaType
, mediaId
);
12439 bool CVideoDatabase::ConvertVideoToVersion(VideoDbContentType itemType
,
12442 int idVideoVersion
,
12443 VideoAssetType assetType
)
12446 MediaType mediaType
;
12447 VideoContentTypeToString(itemType
, mediaType
);
12449 if (itemType
== VideoDbContentType::MOVIES
)
12451 idFile
= GetFileIdByMovie(dbIdSource
);
12459 BeginTransaction();
12461 if (dbIdSource
!= dbIdTarget
)
12463 // Transfer all assets (versions, extras,...) to the new movie.
12464 UpdateAssetsOwner(mediaType
, dbIdSource
, dbIdTarget
);
12466 // version-level art doesn't need any change.
12467 // 'movie' art is converted to 'videoversion' art.
12468 SetVideoVersionDefaultArt(idFile
, dbIdSource
, itemType
);
12470 if (itemType
== VideoDbContentType::MOVIES
)
12471 DeleteMovie(dbIdSource
, DeleteMovieCascadeAction::ALL_ASSETS
,
12472 DeleteMovieHashAction::HASH_PRESERVE
);
12475 // Rename the default version
12476 ExecuteQuery(PrepareSQL("UPDATE videoversion SET idType = %i, itemType = %i WHERE idFile = %i",
12477 idVideoVersion
, assetType
, idFile
));
12479 CommitTransaction();
12484 void CVideoDatabase::SetDefaultVideoVersion(VideoDbContentType itemType
, int dbId
, int idFile
)
12486 if (!m_pDB
|| !m_pDS
)
12489 std::string path
= GetFileBasePathById(idFile
);
12495 if (itemType
== VideoDbContentType::MOVIES
)
12496 m_pDS
->exec(PrepareSQL("UPDATE movie SET idFile = %i, c%02d = '%s' WHERE idMovie = %i",
12497 idFile
, VIDEODB_ID_BASEPATH
, path
.c_str(), dbId
));
12501 CLog::Log(LOGERROR
, "{} failed for video {}", __FUNCTION__
, dbId
);
12505 bool CVideoDatabase::IsDefaultVideoVersion(int idFile
)
12507 if (!m_pDB
|| !m_pDS
)
12513 PrepareSQL("SELECT idMedia, media_type FROM videoversion WHERE idFile = %i", idFile
));
12514 if (m_pDS
->num_rows() > 0)
12516 int idMedia
= m_pDS
->fv("idMedia").get_asInt();
12517 std::string mediaType
= m_pDS
->fv("media_type").get_asString();
12519 if (mediaType
== MediaTypeMovie
)
12521 m_pDS
->query(PrepareSQL("SELECT idFile FROM movie WHERE idMovie = %i", idMedia
));
12522 if (m_pDS
->num_rows() > 0)
12524 if (m_pDS
->fv("idFile").get_asInt() == idFile
)
12532 CLog::Log(LOGERROR
, "{} failed for {}", __FUNCTION__
, idFile
);
12538 bool CVideoDatabase::DeleteVideoAsset(int idFile
)
12540 if (!m_pDB
|| !m_pDS
)
12543 if (IsDefaultVideoVersion(idFile
))
12546 const bool inTransaction
{m_pDB
->in_transaction()};
12550 if (!inTransaction
)
12551 BeginTransaction();
12553 const std::string path
= GetSingleValue(PrepareSQL(
12554 "SELECT strPath FROM path JOIN files ON files.idPath=path.idPath WHERE files.idFile=%i",
12557 InvalidatePathHash(path
);
12559 m_pDS
->exec(PrepareSQL("DELETE FROM videoversion WHERE idFile=%i", idFile
));
12561 if (!inTransaction
)
12562 CommitTransaction();
12568 CLog::LogF(LOGERROR
, "failed for {}", idFile
);
12569 if (!inTransaction
)
12570 RollbackTransaction();
12575 void CVideoDatabase::SetVideoVersion(int idFile
, int idVideoVersion
)
12577 if (!m_pDB
|| !m_pDS
)
12582 m_pDS
->exec(PrepareSQL("UPDATE videoversion SET idType = %i WHERE idFile = %i", idVideoVersion
,
12587 CLog::Log(LOGERROR
, "{} failed for video {}", __FUNCTION__
, idFile
);
12591 bool CVideoDatabase::AddVideoAsset(VideoDbContentType itemType
,
12594 VideoAssetType videoAssetType
,
12597 if (!m_pDB
|| !m_pDS
)
12600 assert(m_pDB
->in_transaction() == false);
12602 if (itemType
!= VideoDbContentType::MOVIES
)
12605 MediaType mediaType
;
12606 VideoContentTypeToString(itemType
, mediaType
);
12608 int idFile
= AddFile(item
.GetPath());
12614 BeginTransaction();
12616 m_pDS
->query(PrepareSQL("SELECT idFile FROM videoversion WHERE idFile = %i", idFile
));
12618 if (m_pDS
->num_rows() == 0)
12620 PrepareSQL("INSERT INTO videoversion (idFile, idMedia, media_type, itemType, idType) "
12621 "VALUES(%i, %i, '%s', %i, %i)",
12622 idFile
, dbId
, mediaType
.c_str(), videoAssetType
, idVideoAsset
));
12624 m_pDS
->exec(PrepareSQL("UPDATE videoversion "
12625 "SET idMedia = %i, media_type = '%s', itemType = %i, idType = %i "
12626 "WHERE idFile = %i",
12627 dbId
, mediaType
.c_str(), videoAssetType
, idVideoAsset
, idFile
));
12629 if (item
.GetVideoInfoTag()->HasStreamDetails())
12630 SetStreamDetailsForFileId(item
.GetVideoInfoTag()->m_streamDetails
, idFile
);
12632 if (videoAssetType
== VideoAssetType::VERSION
)
12633 SetVideoVersionDefaultArt(idFile
, item
.GetVideoInfoTag()->m_iDbId
, itemType
);
12635 SetArtForItem(idFile
, MediaTypeVideoVersion
, item
.GetArt());
12637 CommitTransaction();
12643 CLog::LogF(LOGERROR
, "failed for video {}", dbId
);
12644 RollbackTransaction();
12649 VideoAssetInfo
CVideoDatabase::GetVideoVersionInfo(const std::string
& filenameAndPath
)
12651 VideoAssetInfo info
;
12653 info
.m_idFile
= GetFileId(filenameAndPath
);
12654 if (info
.m_idFile
< 0)
12657 if (!m_pDB
|| !m_pDS
)
12662 m_pDS
->query(PrepareSQL("SELECT videoversiontype.name,"
12663 " videoversiontype.id,"
12664 " videoversion.idMedia,"
12665 " videoversion.media_type,"
12666 " videoversion.itemType "
12667 "FROM videoversion"
12668 " JOIN videoversiontype ON "
12669 " videoversiontype.id = videoversion.idType "
12670 "WHERE videoversion.idFile = %i",
12673 if (m_pDS
->num_rows() > 0)
12675 info
.m_assetTypeId
= m_pDS
->fv("id").get_asInt();
12676 info
.m_assetTypeName
= m_pDS
->fv("name").get_asString();
12677 info
.m_idMedia
= m_pDS
->fv("idMedia").get_asInt();
12678 info
.m_mediaType
= m_pDS
->fv("media_type").get_asString();
12679 info
.m_assetType
= static_cast<VideoAssetType
>(m_pDS
->fv("itemType").get_asInt());
12686 CLog::LogF(LOGERROR
, "failed for {}", filenameAndPath
);
12692 bool CVideoDatabase::GetVideoVersionsNav(const std::string
& strBaseDir
,
12693 CFileItemList
& items
,
12694 VideoDbContentType idContent
/* = UNKNOWN */,
12695 const Filter
& filter
/* = Filter() */)
12697 if (!m_pDB
|| !m_pDS
)
12700 MediaType mediaType
;
12702 if (idContent
== VideoDbContentType::MOVIES
)
12704 mediaType
= MediaTypeMovie
;
12709 CVideoDbUrl videoUrl
;
12710 if (!videoUrl
.FromString(strBaseDir
))
12715 m_pDS
->query(PrepareSQL(
12716 "SELECT DISTINCT videoversiontype.name AS name,"
12717 " videoversiontype.id AS id "
12718 "FROM videoversiontype"
12719 " JOIN videoversion ON"
12720 " videoversion.idType = videoversiontype.id "
12721 "WHERE name != '' AND owner IN (%i, %i) AND videoversiontype.itemType = %i",
12722 VideoAssetTypeOwner::SYSTEM
, VideoAssetTypeOwner::USER
, VideoAssetType::VERSION
));
12724 while (!m_pDS
->eof())
12726 const int id
{m_pDS
->fv("id").get_asInt()};
12728 CVideoDbUrl itemUrl
{videoUrl
};
12729 itemUrl
.AppendPath(StringUtils::Format("{}/", id
));
12731 const auto item
{std::make_shared
<CFileItem
>(itemUrl
.ToString(), true)};
12732 item
->SetLabel(m_pDS
->fv("name").get_asString());
12733 auto tag
{item
->GetVideoInfoTag()};
12734 tag
->m_type
= MediaTypeVideoVersion
;
12745 CLog::Log(LOGERROR
, "{} failed", __FUNCTION__
);
12750 bool CVideoDatabase::GetVideoVersionTypes(VideoDbContentType idContent
,
12751 VideoAssetType assetType
,
12752 CFileItemList
& items
)
12754 if (!m_pDB
|| !m_pDS
)
12757 MediaType mediaType
;
12759 if (idContent
== VideoDbContentType::MOVIES
)
12761 mediaType
= MediaTypeMovie
;
12769 PrepareSQL("SELECT name, id FROM videoversiontype WHERE name != '' AND itemType = %i "
12770 "AND owner IN (%i, %i)",
12771 assetType
, VideoAssetTypeOwner::SYSTEM
, VideoAssetTypeOwner::USER
));
12773 while (!m_pDS
->eof())
12775 std::string name
= m_pDS
->fv("name").get_asString();
12776 int id
= m_pDS
->fv("id").get_asInt();
12778 const auto item
{std::make_shared
<CFileItem
>(name
)};
12779 item
->GetVideoInfoTag()->m_type
= MediaTypeVideoVersion
;
12780 item
->GetVideoInfoTag()->m_iDbId
= id
;
12781 item
->GetVideoInfoTag()->GetAssetInfo().SetId(id
);
12782 item
->GetVideoInfoTag()->GetAssetInfo().SetTitle(name
);
12783 item
->GetVideoInfoTag()->m_strTitle
= name
;
12785 item
->m_strTitle
= name
;
12786 item
->SetLabel(name
);
12796 CLog::Log(LOGERROR
, "{} failed", __FUNCTION__
);
12801 std::string
CVideoDatabase::GetVideoVersionById(int id
)
12803 return GetSingleValue(PrepareSQL("SELECT name FROM videoversiontype WHERE id=%i", id
), m_pDS2
);
12806 void CVideoDatabase::SetVideoVersionDefaultArt(int dbId
, int idFrom
, VideoDbContentType type
)
12808 MediaType mediaType
;
12809 VideoContentTypeToString(type
, mediaType
);
12811 std::map
<std::string
, std::string
> art
;
12812 if (GetArtForItem(idFrom
, mediaType
, art
))
12814 for (const auto& it
: art
)
12815 SetArtForItem(dbId
, MediaTypeVideoVersion
, it
.first
, it
.second
);
12819 std::vector
<std::string
> CVideoDatabase::GetUsedImages(
12820 const std::vector
<std::string
>& imagesToCheck
)
12824 if (!m_pDB
|| !m_pDS
)
12825 return imagesToCheck
;
12827 if (!imagesToCheck
.size())
12830 int artworkLevel
= CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt(
12831 CSettings::SETTING_VIDEOLIBRARY_ARTWORK_LEVEL
);
12832 if (artworkLevel
== CSettings::VIDEOLIBRARY_ARTWORK_LEVEL_NONE
)
12837 // first check the art table
12839 std::string sql
= "SELECT DISTINCT url FROM art WHERE url IN (";
12840 for (const auto& image
: imagesToCheck
)
12842 sql
+= PrepareSQL("'%s',", image
.c_str());
12844 sql
.pop_back(); // remove last ','
12847 // add arttype filters if not set to "Maximum"
12848 if (artworkLevel
!= CSettings::VIDEOLIBRARY_ARTWORK_LEVEL_ALL
)
12850 static std::array
<std::string
, 7> mediatypes
= {
12851 MediaTypeEpisode
, MediaTypeTvShow
, MediaTypeSeason
, MediaTypeMovie
,
12852 MediaTypeVideoCollection
, MediaTypeMusicVideo
, MediaTypeVideoVersion
};
12854 std::string arttypeSQL
;
12855 for (const auto& mediatype
: mediatypes
)
12857 const auto& arttypes
= CVideoThumbLoader::GetArtTypes(mediatype
);
12858 if (arttypes
.empty())
12861 if (!arttypeSQL
.empty())
12862 arttypeSQL
+= ") OR ";
12863 arttypeSQL
+= PrepareSQL("media_type = '%s' AND (", mediatype
.c_str());
12864 bool workingNext
= false;
12865 for (const auto& arttype
: arttypes
)
12868 arttypeSQL
+= " OR ";
12869 workingNext
= true;
12870 if (artworkLevel
== CSettings::VIDEOLIBRARY_ARTWORK_LEVEL_BASIC
)
12872 // for basic match exact artwork type
12873 arttypeSQL
+= PrepareSQL("type = '%s'", arttype
.c_str());
12877 // otherwise check for arttype 'families', like fanart, fanart1, fanart13;
12878 // still avoid most "happens to start with" like fanartstuff
12880 PrepareSQL("type BETWEEN '%s' AND '%s999'", arttype
.c_str(), arttype
.c_str());
12885 if (CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(
12886 CSettings::SETTING_VIDEOLIBRARY_ACTORTHUMBS
))
12888 if (!arttypeSQL
.empty())
12889 arttypeSQL
+= ") OR ";
12890 arttypeSQL
+= "media_type = 'actor'";
12893 if (!arttypeSQL
.empty())
12894 sql
+= " AND (" + arttypeSQL
+ ")";
12897 std::vector
<std::string
> result
;
12898 if (m_pDS
->query(sql
))
12900 while (!m_pDS
->eof())
12902 result
.push_back(m_pDS
->fv(0).get_asString());
12908 // then check any chapter thumbnails against path and file tables
12910 if (CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(
12911 CSettings::SETTING_MYVIDEOS_EXTRACTCHAPTERTHUMBS
))
12913 std::vector
<std::string
> foundVideoFiles
;
12914 for (const auto& image
: imagesToCheck
)
12916 auto imageFile
= IMAGE_FILES::CImageFileURL(image
);
12917 if (imageFile
.GetSpecialType() == "video" && !imageFile
.GetOption("chapter").empty())
12919 const auto& target
= imageFile
.GetTargetFile();
12920 auto quickFind
= std::find(foundVideoFiles
.begin(), foundVideoFiles
.end(), target
);
12921 if (quickFind
!= foundVideoFiles
.end())
12923 result
.push_back(image
);
12927 int fileId
= GetFileId(target
);
12930 result
.push_back(image
);
12931 foundVideoFiles
.push_back(target
);
12942 CLog::LogF(LOGERROR
, "failed");