diff options
Diffstat (limited to 'src/lib')
| -rw-r--r-- | src/lib/config.cc | 12 | ||||
| -rw-r--r-- | src/lib/config.h | 20 | ||||
| -rw-r--r-- | src/lib/http_server.cc | 71 | ||||
| -rw-r--r-- | src/lib/show_playlist.cc (renamed from src/lib/spl_entry.h) | 44 | ||||
| -rw-r--r-- | src/lib/show_playlist.h | 75 | ||||
| -rw-r--r-- | src/lib/show_playlist_content_store.cc (renamed from src/lib/content_store.cc) | 23 | ||||
| -rw-r--r-- | src/lib/show_playlist_content_store.h (renamed from src/lib/content_store.h) | 15 | ||||
| -rw-r--r-- | src/lib/show_playlist_entry.cc | 103 | ||||
| -rw-r--r-- | src/lib/show_playlist_entry.h | 66 | ||||
| -rw-r--r-- | src/lib/show_playlist_list.cc | 223 | ||||
| -rw-r--r-- | src/lib/show_playlist_list.h | 83 | ||||
| -rw-r--r-- | src/lib/spl.cc | 107 | ||||
| -rw-r--r-- | src/lib/spl.h | 156 | ||||
| -rw-r--r-- | src/lib/spl_entry.cc | 75 | ||||
| -rw-r--r-- | src/lib/wscript | 7 |
15 files changed, 620 insertions, 460 deletions
diff --git a/src/lib/config.cc b/src/lib/config.cc index 4440aab4d..10c4e4e01 100644 --- a/src/lib/config.cc +++ b/src/lib/config.cc @@ -192,7 +192,7 @@ Config::set_defaults() _respect_kdm_validity_periods = true; _player_debug_log_file = boost::none; _player_content_directory = boost::none; - _player_playlist_directory = boost::none; + _show_playlists_file = read_path("show_playlists.sqlite3"); _player_kdm_directory = boost::none; _audio_mapping = boost::none; _custom_languages.clear(); @@ -617,7 +617,7 @@ try _respect_kdm_validity_periods = f.optional_bool_child("RespectKDMValidityPeriods").get_value_or(true); _player_debug_log_file = f.optional_string_child("PlayerDebugLogFile"); _player_content_directory = f.optional_string_child("PlayerContentDirectory"); - _player_playlist_directory = f.optional_string_child("PlayerPlaylistDirectory"); + _show_playlists_file = f.string_child("ShowPlaylistsFile"); _player_kdm_directory = f.optional_string_child("PlayerKDMDirectory"); if (f.optional_node_child("AudioMapping")) { @@ -1093,10 +1093,8 @@ Config::write_config() const /* [XML] PlayerContentDirectory Directory to use for player content in the dual-screen mode. */ cxml::add_text_child(root, "PlayerContentDirectory", _player_content_directory->string()); } - if (_player_playlist_directory) { - /* [XML] PlayerPlaylistDirectory Directory to use for player playlists in the dual-screen mode. */ - cxml::add_text_child(root, "PlayerPlaylistDirectory", _player_playlist_directory->string()); - } + /* [XML] ShowPlaylistsFile Filename of SQLite3 database containing show playlists for the player dual-screen mode */ + cxml::add_text_child(root, "ShowPlaylistsFile", _show_playlists_file.string()); if (_player_kdm_directory) { /* [XML] PlayerKDMDirectory Directory to use for player KDMs in the dual-screen mode. */ cxml::add_text_child(root, "PlayerKDMDirectory", _player_kdm_directory->string()); @@ -1697,7 +1695,7 @@ Config::load_from_zip(boost::filesystem::path zip_file, CinemasAction action) changed(Property::SOUND); changed(Property::SOUND_OUTPUT); changed(Property::PLAYER_CONTENT_DIRECTORY); - changed(Property::PLAYER_PLAYLIST_DIRECTORY); + changed(Property::SHOW_PLAYLISTS_FILE); changed(Property::PLAYER_DEBUG_LOG); changed(Property::HISTORY); changed(Property::SHOW_EXPERIMENTAL_AUDIO_PROCESSORS); diff --git a/src/lib/config.h b/src/lib/config.h index f598ffbc6..e0727fe57 100644 --- a/src/lib/config.h +++ b/src/lib/config.h @@ -108,7 +108,7 @@ public: SOUND, SOUND_OUTPUT, PLAYER_CONTENT_DIRECTORY, - PLAYER_PLAYLIST_DIRECTORY, + SHOW_PLAYLISTS_FILE, PLAYER_DEBUG_LOG, HISTORY, SHOW_EXPERIMENTAL_AUDIO_PROCESSORS, @@ -580,8 +580,8 @@ public: return _player_content_directory; } - boost::optional<boost::filesystem::path> player_playlist_directory() const { - return _player_playlist_directory; + boost::filesystem::path show_playlists_file() const { + return _show_playlists_file; } boost::optional<boost::filesystem::path> player_kdm_directory() const { @@ -1134,16 +1134,8 @@ public: changed(PLAYER_CONTENT_DIRECTORY); } - void set_player_playlist_directory(boost::filesystem::path p) { - maybe_set(_player_playlist_directory, p, PLAYER_PLAYLIST_DIRECTORY); - } - - void unset_player_playlist_directory() { - if (!_player_playlist_directory) { - return; - } - _player_playlist_directory = boost::none; - changed(PLAYER_PLAYLIST_DIRECTORY); + void set_show_playlists_file(boost::filesystem::path p) { + maybe_set(_show_playlists_file, p, SHOW_PLAYLISTS_FILE); } void set_player_kdm_directory(boost::filesystem::path p) { @@ -1462,7 +1454,7 @@ private: for playback. */ boost::optional<boost::filesystem::path> _player_content_directory; - boost::optional<boost::filesystem::path> _player_playlist_directory; + boost::filesystem::path _show_playlists_file; boost::optional<boost::filesystem::path> _player_kdm_directory; boost::optional<AudioMapping> _audio_mapping; std::vector<dcp::LanguageTag> _custom_languages; diff --git a/src/lib/http_server.cc b/src/lib/http_server.cc index c69a8b496..495532704 100644 --- a/src/lib/http_server.cc +++ b/src/lib/http_server.cc @@ -20,12 +20,12 @@ #include "config.h" -#include "content_store.h" #include "cross.h" #include "dcpomatic_log.h" #include "dcpomatic_socket.h" #include "http_server.h" -#include "spl.h" +#include "show_playlist_content_store.h" +#include "show_playlist_list.h" #include "util.h" #include "variant.h" #include <dcp/raw_convert.h> @@ -98,27 +98,6 @@ Response::send(shared_ptr<Socket> socket) } -vector<shared_ptr<SignalSPL>> -get_playlists() -{ - vector<shared_ptr<SignalSPL>> playlists; - - if (auto path = Config::instance()->player_playlist_directory()) { - try { - for (auto i: dcp::filesystem::directory_iterator(*path)) { - auto spl = make_shared<SignalSPL>(); - try { - spl->read(i, ContentStore::instance()); - playlists.push_back(spl); - } catch (...) {} - } - } catch (...) {} - } - - return playlists; -} - - Response HTTPServer::get(string const& url) { @@ -138,9 +117,10 @@ HTTPServer::get(string const& url) response.set_type(Response::Type::JSON); return response; } else if (url == "/api/v1/playlists") { + ShowPlaylistList spl_list; nlohmann::json json; - for (auto spl: get_playlists()) { - json.push_back(spl->as_json_without_content()); + for (auto const& spl: spl_list.show_playlists()) { + json.push_back(spl.second.as_json()); } auto response = Response(200, json.dump()); response.set_type(Response::Type::JSON); @@ -151,18 +131,20 @@ HTTPServer::get(string const& url) if (parts.size() != 5) { return Response::ERROR_404; } - for (auto spl: get_playlists()) { - if (spl->id() == parts[4]) { - auto response = Response(200, spl->as_json_with_content().dump()); - response.set_type(Response::Type::JSON); - return response; + ShowPlaylistList spl_list; + for (auto const& spl: spl_list.show_playlists()) { + if (spl.second.uuid() == parts[4]) { + // XXX + // auto response = Response(200, spl->as_json_with_content().dump()); + // response.set_type(Response::Type::JSON); + // return response; } } return Response::ERROR_404; } else if (url == "/api/v1/content") { nlohmann::json json; - for (auto i: ContentStore::instance()->all()) { - json.push_back(SPLEntry(i).as_json()); + for (auto i: ShowPlaylistContentStore::instance()->all()) { + json.push_back(i.as_json()); } auto response = Response(200, json.dump()); response.set_type(Response::Type::JSON); @@ -173,11 +155,11 @@ HTTPServer::get(string const& url) if (parts.size() != 5) { return Response::ERROR_404; } - auto content = ContentStore::instance()->get(parts[4]); + auto content = ShowPlaylistContentStore::instance()->get(parts[4]); if (!content) { return Response::ERROR_404; } - auto json = SPLEntry(content).as_json(); + auto json = content->as_json(); auto response = Response(200, json.dump()); response.set_type(Response::Type::JSON); return response; @@ -208,16 +190,18 @@ HTTPServer::post(string const& url, vector<uint8_t> const& body) return Response::ERROR_404; } bool found = false; - for (auto spl: get_playlists()) { - if (spl->id() == parts[4]) { + ShowPlaylistList spl_list; + for (auto const& spl: spl_list.show_playlists()) { + if (spl.second.uuid() == parts[4]) { nlohmann::json details = nlohmann::json::parse(body); - spl->insert( - SPLEntry(ContentStore::instance()->get(details["digest"])), - details["before"].is_null() ? optional<string>() : details["before"].get<string>() - ); - if (auto dir = Config::instance()->player_playlist_directory()) { - spl->write(*dir / (spl->id() + ".xml")); - } + // XXX + // spl->insert( + // SPLEntry(ContentStore::instance()->get(details["digest"])), + // details["before"].is_null() ? optional<string>() : details["before"].get<string>() + // ); + // if (auto dir = Config::instance()->player_playlist_directory()) { + // spl->write(*dir / (spl->id() + ".xml")); + // } found = true; } } @@ -225,7 +209,6 @@ HTTPServer::post(string const& url, vector<uint8_t> const& body) return Response::ERROR_404; } return Response(201); - } else { return Response::ERROR_404; } diff --git a/src/lib/spl_entry.h b/src/lib/show_playlist.cc index 11f2742bf..b10757d5a 100644 --- a/src/lib/spl_entry.h +++ b/src/lib/show_playlist.cc @@ -19,43 +19,15 @@ */ -#ifndef DCPOMATIC_SPL_ENTRY_H -#define DCPOMATIC_SPL_ENTRY_H +#include "show_playlist.h" -#include <libcxml/cxml.h> -#include <dcp/content_kind.h> -#include <nlohmann/json.hpp> - - -namespace xmlpp { - class Element; -} - -class Content; - - -/** An entry on a show playlist (SPL) */ -class SPLEntry +nlohmann::json +ShowPlaylist::as_json() const { -public: - SPLEntry (std::shared_ptr<Content> c); - - void as_xml(xmlpp::Element* e) const; - nlohmann::json as_json() const; - - std::shared_ptr<Content> content; - std::string name; - /** Digest of this content */ - std::string digest; - /** CPL ID */ - std::string id; - boost::optional<dcp::ContentKind> kind; - bool encrypted; - -private: - void construct (std::shared_ptr<Content> content); -}; - + nlohmann::json json; + json["uuid"] = _uuid; + json["name"] = _name; + return json; +} -#endif diff --git a/src/lib/show_playlist.h b/src/lib/show_playlist.h new file mode 100644 index 000000000..e7f145d37 --- /dev/null +++ b/src/lib/show_playlist.h @@ -0,0 +1,75 @@ +/* + Copyright (C) 2018-2020 Carl Hetherington <cth@carlh.net> + + This file is part of DCP-o-matic. + + DCP-o-matic is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + DCP-o-matic is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with DCP-o-matic. If not, see <http://www.gnu.org/licenses/>. + +*/ + + +#ifndef DCPOMATIC_SHOW_PLAYLIST_H +#define DCPOMATIC_SHOW_PLAYLIST_H + + +#include <dcp/util.h> +#include <nlohmann/json.hpp> + + +/** @class ShowPlaylist + * + * @brief A "show playlist": what a projection system might play for an entire cinema "show". + * + * For example, it might contain some adverts, some trailers and a feature. + * Each SPL has unique ID, a name, and some ordered entries (the content). The content + * is not stored in this class, but can be read from the database via SPLList. + */ +class ShowPlaylist +{ +public: + ShowPlaylist() + : _uuid(dcp::make_uuid()) + {} + + explicit ShowPlaylist(std::string name) + : _uuid(dcp::make_uuid()) + , _name(name) + {} + + ShowPlaylist(std::string uuid, std::string name) + : _uuid(uuid) + , _name(name) + {} + + std::string uuid() const { + return _uuid; + } + + std::string name() const { + return _name; + } + + void set_name(std::string name) { + _name = name; + } + + nlohmann::json as_json() const; + +private: + std::string _uuid; + std::string _name; +}; + + +#endif diff --git a/src/lib/content_store.cc b/src/lib/show_playlist_content_store.cc index 10f3fff04..41d9db245 100644 --- a/src/lib/content_store.cc +++ b/src/lib/show_playlist_content_store.cc @@ -21,11 +21,11 @@ #include "config.h" #include "content_factory.h" -#include "content_store.h" #include "cross.h" #include "dcp_content.h" #include "examine_content_job.h" #include "job_manager.h" +#include "show_playlist_content_store.h" #include "util.h" #include <dcp/exceptions.h> #include <dcp/filesystem.h> @@ -36,13 +36,14 @@ using std::pair; using std::shared_ptr; using std::string; using std::vector; +using boost::optional; -ContentStore* ContentStore::_instance = nullptr; +ShowPlaylistContentStore* ShowPlaylistContentStore::_instance = nullptr; vector<pair<string, string>> -ContentStore::update(std::function<bool()> pulse) +ShowPlaylistContentStore::update(std::function<bool()> pulse) { _content.clear(); auto dir = Config::instance()->player_content_directory(); @@ -97,7 +98,7 @@ ContentStore::update(std::function<bool()> pulse) if (i->finished_in_error()) { errors.push_back({String::compose("%1.\n", i->error_summary()), i->error_details()}); } else { - _content.push_back(i->content()); + _content.push_back(ShowPlaylistEntry(i->content())); } } @@ -105,11 +106,11 @@ ContentStore::update(std::function<bool()> pulse) } -shared_ptr<Content> -ContentStore::get(string digest) const +optional<ShowPlaylistEntry> +ShowPlaylistContentStore::get(string uuid) const { - auto iter = std::find_if(_content.begin(), _content.end(), [digest](shared_ptr<const Content> content) { - return content->digest() == digest; + auto iter = std::find_if(_content.begin(), _content.end(), [uuid](ShowPlaylistEntry const& entry) { + return entry.uuid() == uuid; }); if (iter == _content.end()) { @@ -120,11 +121,11 @@ ContentStore::get(string digest) const } -ContentStore* -ContentStore::instance() +ShowPlaylistContentStore* +ShowPlaylistContentStore::instance() { if (!_instance) { - _instance = new ContentStore(); + _instance = new ShowPlaylistContentStore(); } return _instance; diff --git a/src/lib/content_store.h b/src/lib/show_playlist_content_store.h index dcf31b5a4..dcf19cebe 100644 --- a/src/lib/content_store.h +++ b/src/lib/show_playlist_content_store.h @@ -19,6 +19,7 @@ */ +#include "show_playlist_entry.h" #include <functional> #include <memory> #include <vector> @@ -26,13 +27,13 @@ class Content; -/** @class ContentStore +/** @class ShowPlaylistContentStore * @brief Class to maintain details of what content we have available to play */ -class ContentStore +class ShowPlaylistContentStore { public: - std::shared_ptr<Content> get(std::string digest) const; + boost::optional<ShowPlaylistEntry> get(std::string uuid) const; /** Examine content in the configured directory and update our list. * @param pulse Called every so often to indicate progress. Return false to cancel the scan. @@ -40,14 +41,14 @@ public: */ std::vector<std::pair<std::string, std::string>> update(std::function<bool()> pulse); - static ContentStore* instance(); + static ShowPlaylistContentStore* instance(); - std::vector<std::shared_ptr<Content>> const& all() const { + std::vector<ShowPlaylistEntry> const& all() const { return _content; } private: - std::vector<std::shared_ptr<Content>> _content; + std::vector<ShowPlaylistEntry> _content; - static ContentStore* _instance; + static ShowPlaylistContentStore* _instance; }; diff --git a/src/lib/show_playlist_entry.cc b/src/lib/show_playlist_entry.cc new file mode 100644 index 000000000..a684de1d2 --- /dev/null +++ b/src/lib/show_playlist_entry.cc @@ -0,0 +1,103 @@ +/* + Copyright (C) 2018-2021 Carl Hetherington <cth@carlh.net> + + This file is part of DCP-o-matic. + + DCP-o-matic is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + DCP-o-matic is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with DCP-o-matic. If not, see <http://www.gnu.org/licenses/>. + +*/ + + +#include "dcp_content.h" +#include "dcpomatic_assert.h" +#include "show_playlist_entry.h" +#include <fmt/format.h> + + +using std::dynamic_pointer_cast; +using std::shared_ptr; +using std::string; + + +ShowPlaylistEntry::ShowPlaylistEntry(shared_ptr<Content> content) + : _content(content) +{ + +} + + +string +ShowPlaylistEntry::uuid() const +{ + if (auto dcp = dynamic_pointer_cast<DCPContent>(_content)) { + DCPOMATIC_ASSERT(dcp->cpl()); + return *dcp->cpl(); + } else { + return _content->digest(); + } +} + + +string +ShowPlaylistEntry::name() const +{ + if (auto dcp = dynamic_pointer_cast<DCPContent>(_content)) { + return dcp->name(); + } else { + return _content->path(0).filename().string(); + } +} + + +dcp::ContentKind +ShowPlaylistEntry::kind() const +{ + if (auto dcp = dynamic_pointer_cast<DCPContent>(_content)) { + return dcp->content_kind().get_value_or(dcp::ContentKind::FEATURE); + } else { + return dcp::ContentKind::FEATURE; + } +} + + +bool +ShowPlaylistEntry::encrypted() const +{ + if (auto dcp = dynamic_pointer_cast<DCPContent>(_content)) { + return dcp->encrypted(); + } else { + return false; + } +} + + +string +ShowPlaylistEntry::approximate_length() const +{ + auto const hmsf = _content->approximate_length().split(24); + return fmt::format("{:02d}:{:02d}:{:02d}", hmsf.h, hmsf.m, hmsf.s); +} + + +nlohmann::json +ShowPlaylistEntry::as_json() const +{ + nlohmann::json json; + json["uuid"] = uuid(); + json["name"] = name(); + json["kind"] = kind().name(); + json["encrypted"] = encrypted(); + return json; +} + diff --git a/src/lib/show_playlist_entry.h b/src/lib/show_playlist_entry.h new file mode 100644 index 000000000..9a20ca3b7 --- /dev/null +++ b/src/lib/show_playlist_entry.h @@ -0,0 +1,66 @@ +/* + Copyright (C) 2018-2021 Carl Hetherington <cth@carlh.net> + + This file is part of DCP-o-matic. + + DCP-o-matic is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + DCP-o-matic is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with DCP-o-matic. If not, see <http://www.gnu.org/licenses/>. + +*/ + + +#ifndef DCPOMATIC_SHOW_PLAYLIST_ENTRY_H +#define DCPOMATIC_SHOW_PLAYLIST_ENTRY_H + + +#include <dcp/content_kind.h> +#include <nlohmann/json.hpp> + + +class Content; + + +/** @class ShowPlaylistEntry + * + * @brief An entry on a show playlist (SPL). + * + * In the ShowPlaylist database we only store the UUID of the content. This class + * holds a pointer to a Content object, and provides a simpler interface to information + * that is needed by the ShowPlaylist code. + * + * Given a UUID from the database, a ShowPlaylistEntry can be obtained from the + * ShowPlaylistContentStore. + */ +class ShowPlaylistEntry +{ +public: + explicit ShowPlaylistEntry(std::shared_ptr<Content> content); + + nlohmann::json as_json() const; + + std::string uuid() const; + std::string name() const; + dcp::ContentKind kind() const; + std::string approximate_length() const; + bool encrypted() const; + + std::shared_ptr<Content> content() const { + return _content; + } + +private: + std::shared_ptr<Content> _content; +}; + + +#endif diff --git a/src/lib/show_playlist_list.cc b/src/lib/show_playlist_list.cc new file mode 100644 index 000000000..a45d42d12 --- /dev/null +++ b/src/lib/show_playlist_list.cc @@ -0,0 +1,223 @@ +/* + Copyright (C) 2025 Carl Hetherington <cth@carlh.net> + + This file is part of DCP-o-matic. + + DCP-o-matic is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + DCP-o-matic is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with DCP-o-matic. If not, see <http://www.gnu.org/licenses/>. + +*/ + + +#include "config.h" +#include "show_playlist.h" +#include "show_playlist.h" +#include "show_playlist_entry.h" +#include "show_playlist_list.h" +#include "sqlite_statement.h" +#include "sqlite_transaction.h" + + +using std::make_pair; +using std::pair; +using std::string; +using std::vector; +using boost::optional; + + +ShowPlaylistList::ShowPlaylistList() + : _show_playlists("show_playlists") + , _entries("entries") + , _db(Config::instance()->show_playlists_file()) +{ + setup_tables(); + setup(); +} + + +ShowPlaylistList::ShowPlaylistList(boost::filesystem::path db_file) + : _show_playlists("show_playlists") + , _entries("entries") + , _db(db_file) +{ + setup_tables(); + setup(); +} + + +void +ShowPlaylistList::setup_tables() +{ + _show_playlists.add_column("uuid", "TEXT"); + _show_playlists.add_column("name", "TEXT"); + + _entries.add_column("show_playlist", "INTEGER"); + _entries.add_column("uuid", "TEXT"); + _entries.add_column("sort_index", "INTEGER"); +} + + +void +ShowPlaylistList::setup() +{ + SQLiteStatement show_playlists(_db, _show_playlists.create()); + show_playlists.execute(); + + SQLiteStatement entries(_db, _entries.create()); + entries.execute(); +} + + +ShowPlaylistID +ShowPlaylistList::add_show_playlist(ShowPlaylist const& playlist) +{ + SQLiteStatement statement(_db, _show_playlists.insert()); + + statement.bind_text(1, playlist.uuid()); + statement.bind_text(2, playlist.name()); + + statement.execute(); + + return sqlite3_last_insert_rowid(_db.db()); +} + + +void +ShowPlaylistList::remove_show_playlist(ShowPlaylistID id) +{ + SQLiteStatement statement(_db, "DELETE FROM show_playlists WHERE ID=?"); + statement.bind_int64(1, id.get()); + statement.execute(); +} + + +static +vector<pair<ShowPlaylistID, ShowPlaylist>> +show_playlists_from_result(SQLiteStatement& statement) +{ + vector<pair<ShowPlaylistID, ShowPlaylist>> output; + + statement.execute([&output](SQLiteStatement& statement) { + DCPOMATIC_ASSERT(statement.data_count() == 3); + ShowPlaylistID const id = statement.column_int64(0); + auto const uuid = statement.column_text(1); + auto const name = statement.column_text(2); + output.push_back(make_pair(id, ShowPlaylist(uuid, name))); + }); + + return output; +} + + +vector<pair<ShowPlaylistID, ShowPlaylist>> +ShowPlaylistList::show_playlists() const +{ + SQLiteStatement statement(_db, _show_playlists.select("ORDER BY name COLLATE unicode ASC")); + return show_playlists_from_result(statement); +} + + + +vector<string> +ShowPlaylistList::entries(ShowPlaylistID show_playlist_id) const +{ + SQLiteStatement statement(_db, "SELECT entries.uuid FROM entries JOIN show_playlists ON entries.show_playlist=show_playlists.id WHERE show_playlists.id=? ORDER BY entries.sort_index"); + statement.bind_int64(1, show_playlist_id.get()); + + vector<string> output; + + statement.execute([&output](SQLiteStatement& statement) { + DCPOMATIC_ASSERT(statement.data_count() == 1); + output.push_back(statement.column_text(0)); + }); + + return output; +} + + +vector<string> +ShowPlaylistList::entries(string const& show_playlist_uuid) const +{ + SQLiteStatement statement(_db, "SELECT entries.uuid FROM entries JOIN show_playlists ON entries.show_playlist=show_playlists.id WHERE show_playlists.uuid=? ORDER BY entries.sort_index"); + statement.bind_text(1, show_playlist_uuid); + + vector<string> output; + + statement.execute([&output](SQLiteStatement& statement) { + DCPOMATIC_ASSERT(statement.data_count() == 1); + output.push_back(statement.column_text(0)); + }); + + return output; +} + + +void +ShowPlaylistList::add_entry(ShowPlaylistID playlist_id, ShowPlaylistEntry entry) +{ + SQLiteTransaction transaction(_db); + + SQLiteStatement find_last_screen(_db, "SELECT MAX(sort_index) FROM entries WHERE show_playlist=?"); + find_last_screen.bind_int64(1, playlist_id.get()); + + optional<int> highest_index; + find_last_screen.execute([&highest_index](SQLiteStatement& statement) { + if (statement.data_count() == 1) { + highest_index = statement.column_int64(0); + } + }); + + SQLiteStatement add_entry(_db, _entries.insert()); + + add_entry.bind_int64(1, playlist_id.get()); + add_entry.bind_text(2, entry.uuid()); + add_entry.bind_int64(3, highest_index ? *highest_index + 1 : 0); + + add_entry.execute(); + + transaction.commit(); +} + + +void +ShowPlaylistList::move_entry_up(ShowPlaylistID playlist_id, int index) +{ + DCPOMATIC_ASSERT(index >= 1); + + SQLiteTransaction transaction(_db); + + SQLiteStatement find(_db, "SELECT id,sort_index FROM entries WHERE show_playlist=? ORDER BY sort_index LIMIT 2 OFFSET ?"); + find.bind_int64(1, playlist_id.get()); + find.bind_int64(2, index - 1); + + vector<pair<int64_t, int64_t>> rows; + find.execute([&rows](SQLiteStatement& statement) { + DCPOMATIC_ASSERT(statement.data_count() == 2); + rows.push_back({statement.column_int64(0), statement.column_int64(1)}); + }); + + DCPOMATIC_ASSERT(rows.size() == 2); + + SQLiteStatement swap1(_db, "UPDATE entries SET sort_index=? WHERE id=?"); + swap1.bind_int64(1, rows[0].second); + swap1.bind_int64(2, rows[1].first); + swap1.execute(); + + SQLiteStatement swap2(_db, "UPDATE entries SET sort_index=? WHERE id=?"); + swap2.bind_int64(1, rows[1].second); + swap2.bind_int64(2, rows[0].first); + swap2.execute(); + + transaction.commit(); +} + diff --git a/src/lib/show_playlist_list.h b/src/lib/show_playlist_list.h new file mode 100644 index 000000000..08414661d --- /dev/null +++ b/src/lib/show_playlist_list.h @@ -0,0 +1,83 @@ +/* + Copyright (C) 2025 Carl Hetherington <cth@carlh.net> + + This file is part of DCP-o-matic. + + DCP-o-matic is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + DCP-o-matic is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with DCP-o-matic. If not, see <http://www.gnu.org/licenses/>. + +*/ + + +#ifndef DCPOMATIC_SHOW_PLAYLIST_LIST_H +#define DCPOMATIC_SHOW_PLAYLIST_LIST_H + + +#include "id.h" +#include "show_playlist.h" +#include "sqlite_database.h" +#include "sqlite_table.h" + + +/** @class ShowPlaylistID + * + * @brief The SQLite ID (not the UUID) of a ShowPlaylist. + */ +class ShowPlaylistID : public ID +{ +public: + ShowPlaylistID(sqlite3_int64 id) + : ID(id) {} + + bool operator<(ShowPlaylistID const& other) const { + return get() < other.get(); + } +}; + + +/** @class ShowPlaylistList + * + * @brief A list of SPLs (show playlists) stored in a SQLite database. + * + * A SPL (show playlist) is a list of content (and maybe later automation cues) + * that make up a "show" in a cinema/theater. For example, a SPL might contain + * some adverts, some trailers, and a feature. + */ +class ShowPlaylistList +{ +public: + ShowPlaylistList(); + explicit ShowPlaylistList(boost::filesystem::path db_file); + + ShowPlaylistID add_show_playlist(ShowPlaylist const& show_playlist); + void remove_show_playlist(ShowPlaylistID id); + std::vector<std::pair<ShowPlaylistID, ShowPlaylist>> show_playlists() const; + + std::vector<std::string> entries(ShowPlaylistID show_playlist_id) const; + std::vector<std::string> entries(std::string const& show_playlist_uuid) const; + + void add_entry(ShowPlaylistID, ShowPlaylistEntry entry); + void move_entry_up(ShowPlaylistID, int index); + +private: + void setup_tables(); + void setup(); + + SQLiteTable _show_playlists; + SQLiteTable _entries; + mutable SQLiteDatabase _db; +}; + + +#endif + diff --git a/src/lib/spl.cc b/src/lib/spl.cc deleted file mode 100644 index 89cb1d0c5..000000000 --- a/src/lib/spl.cc +++ /dev/null @@ -1,107 +0,0 @@ -/* - Copyright (C) 2018-2021 Carl Hetherington <cth@carlh.net> - - This file is part of DCP-o-matic. - - DCP-o-matic is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - DCP-o-matic is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with DCP-o-matic. If not, see <http://www.gnu.org/licenses/>. - -*/ - - -#include "content_store.h" -#include "spl.h" -#include <libcxml/cxml.h> -#include <dcp/filesystem.h> -#include <dcp/warnings.h> -LIBDCP_DISABLE_WARNINGS -#include <libxml++/libxml++.h> -LIBDCP_ENABLE_WARNINGS -#include <iostream> - - -using std::cout; -using std::string; -using std::shared_ptr; -using boost::optional; - - -void -SPL::read (boost::filesystem::path path, ContentStore* store) -{ - _spl.clear (); - _missing = false; - cxml::Document doc ("SPL"); - doc.read_file(dcp::filesystem::fix_long_path(path)); - _id = doc.string_child("Id"); - _name = doc.string_child("Name"); - for (auto i: doc.node_children("Entry")) { - auto c = store->get(i->string_child("Digest")); - if (c) { - add (SPLEntry(c)); - } else { - _missing = true; - } - } -} - - -void -SPL::write (boost::filesystem::path path) const -{ - xmlpp::Document doc; - auto root = doc.create_root_node ("SPL"); - cxml::add_text_child(root, "Id", _id); - cxml::add_text_child(root, "Name", _name); - for (auto i: _spl) { - i.as_xml(cxml::add_child(root, "Entry")); - } - doc.write_to_file_formatted (path.string()); -} - - -void -SPL::insert(SPLEntry entry, optional<string> before_id) -{ - if (before_id) { - auto iter = std::find_if(_spl.begin(), _spl.end(), [before_id](SPLEntry const& e) { - return e.id == before_id; - }); - if (iter != _spl.end()) { - _spl.insert(iter, entry); - } - } else { - _spl.push_back(entry); - } -} - - -nlohmann::json -SPL::as_json_without_content() const -{ - nlohmann::json json; - json["id"] = _id; - json["name"] = _name; - return json; -} - - -nlohmann::json -SPL::as_json_with_content() const -{ - auto json = as_json_without_content(); - for (auto i: _spl) { - json["content"].push_back(i.as_json()); - } - return json; -} diff --git a/src/lib/spl.h b/src/lib/spl.h deleted file mode 100644 index ca81ff5af..000000000 --- a/src/lib/spl.h +++ /dev/null @@ -1,156 +0,0 @@ -/* - Copyright (C) 2018-2020 Carl Hetherington <cth@carlh.net> - - This file is part of DCP-o-matic. - - DCP-o-matic is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - DCP-o-matic is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with DCP-o-matic. If not, see <http://www.gnu.org/licenses/>. - -*/ - - -#ifndef DCPOMATIC_SPL_H -#define DCPOMATIC_SPL_H - - -#include "spl_entry.h" -#include <dcp/util.h> -#include <nlohmann/json.hpp> -#include <boost/signals2.hpp> -#include <algorithm> - - -class ContentStore; - - -/** @class SPL - * - * @brief A "show playlist": what a projection system might play for an entire cinema "show". - * - * For example, it might contain some adverts, some trailers and a feature. - * Each SPL has unique ID, a name, and some SPLEntry objects. - */ -class SPL -{ -public: - SPL() - : _id(dcp::make_uuid()) - {} - - explicit SPL(std::string name) - : _id(dcp::make_uuid()) - , _name(name) - {} - - - void add(SPLEntry e) { - _spl.push_back(e); - } - - void remove(std::size_t index) { - _spl.erase(_spl.begin() + index); - } - - void insert(SPLEntry entry, boost::optional<std::string> before_id); - - std::vector<SPLEntry> const& get() const { - return _spl; - } - - SPLEntry const& operator[](std::size_t index) const { - return _spl[index]; - } - - void swap(size_t a, size_t b) { - std::iter_swap(_spl.begin() + a, _spl.begin() + b); - } - - void read(boost::filesystem::path path, ContentStore* store); - void write(boost::filesystem::path path) const; - - std::string id() const { - return _id; - } - - std::string name() const { - return _name; - } - - void set_name(std::string name) { - _name = name; - } - - /** @return true if any content was missing when read() was last called on this SPL */ - bool missing() const { - return _missing; - } - - nlohmann::json as_json_without_content() const; - nlohmann::json as_json_with_content() const; - -private: - std::string _id; - std::string _name; - std::vector<SPLEntry> _spl; - /** true if any content was missing when read() was last called on this SPL */ - bool _missing = false; -}; - - -/** @class SignalSPL - * - * @brief A wrapper around SPL that signals changes. - */ -class SignalSPL : public SPL -{ -public: - enum class Change { - NAME, - CONTENT, - }; - - SignalSPL() = default; - - explicit SignalSPL(std::string name) - : SPL(name) - {} - - void set_name(std::string name) { - SPL::set_name (name); - Changed(Change::NAME); - } - - void add(SPLEntry e) { - SPL::add(e); - Changed(Change::CONTENT); - } - - void remove(std::size_t index) { - SPL::remove(index); - Changed(Change::CONTENT); - } - - void insert(SPLEntry e, boost::optional<std::string> before_id) { - SPL::insert(e, before_id); - Changed(Change::CONTENT); - } - - void swap(size_t a, size_t b) { - SPL::swap(a, b); - Changed(Change::CONTENT); - } - - boost::signals2::signal<void (Change)> Changed; -}; - -#endif diff --git a/src/lib/spl_entry.cc b/src/lib/spl_entry.cc deleted file mode 100644 index 476744252..000000000 --- a/src/lib/spl_entry.cc +++ /dev/null @@ -1,75 +0,0 @@ -/* - Copyright (C) 2018-2021 Carl Hetherington <cth@carlh.net> - - This file is part of DCP-o-matic. - - DCP-o-matic is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - DCP-o-matic is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with DCP-o-matic. If not, see <http://www.gnu.org/licenses/>. - -*/ - - -#include "dcp_content.h" -#include "dcpomatic_assert.h" -#include "spl_entry.h" -#include <dcp/warnings.h> -LIBDCP_DISABLE_WARNINGS -#include <libxml++/libxml++.h> -LIBDCP_ENABLE_WARNINGS - - -using std::shared_ptr; -using std::dynamic_pointer_cast; - - -SPLEntry::SPLEntry (shared_ptr<Content> c) - : content (c) - , digest (content->digest()) -{ - auto dcp = dynamic_pointer_cast<DCPContent> (content); - if (dcp) { - name = dcp->name (); - DCPOMATIC_ASSERT (dcp->cpl()); - id = *dcp->cpl(); - kind = dcp->content_kind().get_value_or(dcp::ContentKind::FEATURE); - encrypted = dcp->encrypted (); - } else { - name = content->path(0).filename().string(); - kind = dcp::ContentKind::FEATURE; - } -} - - -void -SPLEntry::as_xml(xmlpp::Element* e) const -{ - cxml::add_text_child(e, "Digest", digest); -} - - -nlohmann::json -SPLEntry::as_json() const -{ - nlohmann::json json; - json["name"] = name; - json["digest"] = digest; - if (!id.empty()) { - json["cpl-id"] = id; - } - if (kind) { - json["kind"] = kind->name(); - } - json["encrypted"] = encrypted; - return json; -} - diff --git a/src/lib/wscript b/src/lib/wscript index f9ee45ed5..250771108 100644 --- a/src/lib/wscript +++ b/src/lib/wscript @@ -57,7 +57,6 @@ sources = """ config.cc content.cc content_factory.cc - content_store.cc combine_dcp_job.cc copy_dcp_details_to_film.cc cpu_j2k_encoder_thread.cc @@ -189,10 +188,12 @@ sources = """ send_notification_email_job.cc send_problem_report_job.cc server.cc + show_playlist.cc + show_playlist_content_store.cc + show_playlist_entry.cc + show_playlist_list.cc shuffler.cc state.cc - spl.cc - spl_entry.cc sqlite_database.cc sqlite_statement.cc sqlite_table.cc |
