summaryrefslogtreecommitdiff
path: root/src/lib
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib')
-rw-r--r--src/lib/config.cc12
-rw-r--r--src/lib/config.h20
-rw-r--r--src/lib/http_server.cc71
-rw-r--r--src/lib/show_playlist.cc (renamed from src/lib/spl_entry.h)44
-rw-r--r--src/lib/show_playlist.h75
-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.cc103
-rw-r--r--src/lib/show_playlist_entry.h66
-rw-r--r--src/lib/show_playlist_list.cc223
-rw-r--r--src/lib/show_playlist_list.h83
-rw-r--r--src/lib/spl.cc107
-rw-r--r--src/lib/spl.h156
-rw-r--r--src/lib/spl_entry.cc75
-rw-r--r--src/lib/wscript7
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