summaryrefslogtreecommitdiff
path: root/src/lib
diff options
context:
space:
mode:
authorCarl Hetherington <cth@carlh.net>2025-10-20 00:45:17 +0200
committerCarl Hetherington <cth@carlh.net>2026-02-16 01:20:38 +0100
commit4cb6ab669032ef0584fde63e62addfe8a71a484c (patch)
tree7f9fbc6d0981b1e247c3c89545f24d5a3a6ffaaa /src/lib
parenteb6464c1099de3967fc8d3b7de1461da85c7e827 (diff)
Use SQLite for show playlists.
Diffstat (limited to 'src/lib')
-rw-r--r--src/lib/config.cc21
-rw-r--r--src/lib/config.h20
-rw-r--r--src/lib/show_playlist.cc46
-rw-r--r--src/lib/show_playlist.h79
-rw-r--r--src/lib/show_playlist_content_store.cc35
-rw-r--r--src/lib/show_playlist_content_store.h9
-rw-r--r--src/lib/show_playlist_entry.cc158
-rw-r--r--src/lib/show_playlist_entry.h78
-rw-r--r--src/lib/show_playlist_id.h (renamed from src/lib/spl_entry.h)46
-rw-r--r--src/lib/show_playlist_list.cc417
-rw-r--r--src/lib/show_playlist_list.h97
-rw-r--r--src/lib/spl.cc76
-rw-r--r--src/lib/spl.h143
-rw-r--r--src/lib/spl_entry.cc70
-rw-r--r--src/lib/sqlite_statement.cc17
-rw-r--r--src/lib/sqlite_statement.h2
-rw-r--r--src/lib/sqlite_table.cc8
-rw-r--r--src/lib/sqlite_table.h1
-rw-r--r--src/lib/wscript5
19 files changed, 966 insertions, 362 deletions
diff --git a/src/lib/config.cc b/src/lib/config.cc
index a2469ed8a..974733e8a 100644
--- a/src/lib/config.cc
+++ b/src/lib/config.cc
@@ -31,6 +31,7 @@
#include "filter.h"
#include "log.h"
#include "ratio.h"
+#include "show_playlist_list.h"
#include "unzipper.h"
#include "variant.h"
#include "zipper.h"
@@ -189,7 +190,7 @@ Config::set_defaults()
_player_debug_log_file = boost::none;
_kdm_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();
@@ -615,7 +616,15 @@ try
_player_debug_log_file = f.optional_string_child("PlayerDebugLogFile");
_kdm_debug_log_file = f.optional_string_child("KDMDebugLogFile");
_player_content_directory = f.optional_string_child("PlayerContentDirectory");
- _player_playlist_directory = f.optional_string_child("PlayerPlaylistDirectory");
+ if (auto spl_dir = f.optional_string_child("PlayerPlaylistDirectory")) {
+ ShowPlaylistList spl;
+ spl.read_legacy(*spl_dir);
+ }
+ if (auto spl_file = f.optional_string_child("ShowPlaylistsFile")) {
+ _show_playlists_file = *spl_file;
+ } else {
+ _show_playlists_file = read_path("show_playlists.sqlite3");
+ }
_player_kdm_directory = f.optional_string_child("PlayerKDMDirectory");
if (f.optional_node_child("AudioMapping")) {
@@ -1101,10 +1110,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());
@@ -1679,7 +1686,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 f5e1e3788..c37829146 100644
--- a/src/lib/config.h
+++ b/src/lib/config.h
@@ -111,7 +111,7 @@ public:
SOUND,
SOUND_OUTPUT,
PLAYER_CONTENT_DIRECTORY,
- PLAYER_PLAYLIST_DIRECTORY,
+ SHOW_PLAYLISTS_FILE,
PLAYER_DEBUG_LOG,
KDM_DEBUG_LOG,
HISTORY,
@@ -582,8 +582,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 {
@@ -1157,16 +1157,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) {
@@ -1489,7 +1481,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/show_playlist.cc b/src/lib/show_playlist.cc
new file mode 100644
index 000000000..85e420d2c
--- /dev/null
+++ b/src/lib/show_playlist.cc
@@ -0,0 +1,46 @@
+/*
+ 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 "show_playlist.h"
+
+
+nlohmann::json
+ShowPlaylist::as_json() const
+{
+ nlohmann::json json;
+ json["uuid"] = _uuid;
+ json["name"] = _name;
+ return json;
+}
+
+
+bool
+operator==(ShowPlaylist const& a, ShowPlaylist const& b)
+{
+ return a.uuid() == b.uuid() && a.name() == b.name();
+}
+
+
+bool
+operator!=(ShowPlaylist const& a, ShowPlaylist const& b)
+{
+ return a.uuid() != b.uuid() || a.name() != b.name();
+}
diff --git a/src/lib/show_playlist.h b/src/lib/show_playlist.h
new file mode 100644
index 000000000..8be2ad380
--- /dev/null
+++ b/src/lib/show_playlist.h
@@ -0,0 +1,79 @@
+/*
+ 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_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 by ShowPlaylistList.
+ */
+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;
+};
+
+
+bool operator==(ShowPlaylist const& a, ShowPlaylist const& b);
+bool operator!=(ShowPlaylist const& a, ShowPlaylist const& b);
+
+
+#endif
diff --git a/src/lib/show_playlist_content_store.cc b/src/lib/show_playlist_content_store.cc
index 97a53bb74..1a9f533af 100644
--- a/src/lib/show_playlist_content_store.cc
+++ b/src/lib/show_playlist_content_store.cc
@@ -26,6 +26,7 @@
#include "examine_content_job.h"
#include "job_manager.h"
#include "show_playlist_content_store.h"
+#include "show_playlist_entry.h"
#include "util.h"
#include <dcp/cpl.h>
#include <dcp/exceptions.h>
@@ -116,27 +117,12 @@ ShowPlaylistContentStore::update(std::function<bool()> pulse)
shared_ptr<Content>
-ShowPlaylistContentStore::get_by_digest(string digest) const
+ShowPlaylistContentStore::get(string const& uuid) const
{
- auto iter = std::find_if(_content.begin(), _content.end(), [digest](shared_ptr<const Content> content) {
- return content->digest() == digest;
- });
-
- if (iter == _content.end()) {
- return {};
- }
-
- return *iter;
-}
-
-
-shared_ptr<Content>
-ShowPlaylistContentStore::get_by_cpl_id(string id) const
-{
- auto iter = std::find_if(_content.begin(), _content.end(), [id](shared_ptr<const Content> content) {
+ auto iter = std::find_if(_content.begin(), _content.end(), [uuid](shared_ptr<const Content> content) {
if (auto dcp = dynamic_pointer_cast<const DCPContent>(content)) {
for (auto cpl: dcp::find_and_resolve_cpls(dcp->directories(), true)) {
- if (cpl->id() == id) {
+ if (cpl->id() == uuid) {
return true;
}
}
@@ -145,6 +131,12 @@ ShowPlaylistContentStore::get_by_cpl_id(string id) const
});
if (iter == _content.end()) {
+ iter = std::find_if(_content.begin(), _content.end(), [uuid](shared_ptr<const Content> content) {
+ return content->digest() == uuid;
+ });
+ }
+
+ if (iter == _content.end()) {
return {};
}
@@ -152,6 +144,13 @@ ShowPlaylistContentStore::get_by_cpl_id(string id) const
}
+shared_ptr<Content>
+ShowPlaylistContentStore::get(ShowPlaylistEntry const& entry) const
+{
+ return get(entry.uuid());
+}
+
+
ShowPlaylistContentStore*
ShowPlaylistContentStore::instance()
{
diff --git a/src/lib/show_playlist_content_store.h b/src/lib/show_playlist_content_store.h
index dbee39902..02e6fc17e 100644
--- a/src/lib/show_playlist_content_store.h
+++ b/src/lib/show_playlist_content_store.h
@@ -25,6 +25,7 @@
class Content;
+class ShowPlaylistEntry;
/** @class ShowPlaylistContentStore
@@ -33,8 +34,12 @@ class Content;
class ShowPlaylistContentStore
{
public:
- std::shared_ptr<Content> get_by_digest(std::string digest) const;
- std::shared_ptr<Content> get_by_cpl_id(std::string id) const;
+ /** @param uuid UUID, which can either be a CPL UUID (for a CPL in a DCP), or
+ * a digest, for other content.
+ * @return Content, or nullptr.
+ */
+ std::shared_ptr<Content> get(std::string const& uuid) const;
+ std::shared_ptr<Content> get(ShowPlaylistEntry const& entry) 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.
diff --git a/src/lib/show_playlist_entry.cc b/src/lib/show_playlist_entry.cc
new file mode 100644
index 000000000..1e16df788
--- /dev/null
+++ b/src/lib/show_playlist_entry.cc
@@ -0,0 +1,158 @@
+/*
+ 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;
+using boost::optional;
+
+
+ShowPlaylistEntry::ShowPlaylistEntry(shared_ptr<Content> content, optional<float> crop_to_ratio)
+ : _kind(dcp::ContentKind::FEATURE)
+ , _crop_to_ratio(crop_to_ratio)
+{
+ if (auto dcp = dynamic_pointer_cast<DCPContent>(content)) {
+ DCPOMATIC_ASSERT(dcp->cpl());
+ _uuid = *dcp->cpl();
+ _name = dcp->name();
+ _kind = dcp->content_kind().get_value_or(dcp::ContentKind::FEATURE);
+ _encrypted = dcp->encrypted();
+ } else {
+ _uuid = content->digest();
+ _name = content->path(0).filename().string();
+ _encrypted = false;
+ }
+
+ auto const hmsf = content->approximate_length().split(24);
+ _approximate_length = fmt::format("{:02d}:{:02d}:{:02d}", hmsf.h, hmsf.m, hmsf.s);
+}
+
+
+ShowPlaylistEntry::ShowPlaylistEntry(
+ string uuid,
+ string name,
+ dcp::ContentKind kind,
+ string approximate_length,
+ bool encrypted,
+ boost::optional<float> crop_to_ratio
+ )
+ : _uuid(std::move(uuid))
+ , _name(std::move(name))
+ , _kind(std::move(kind))
+ , _approximate_length(std::move(approximate_length))
+ , _encrypted(encrypted)
+ , _crop_to_ratio(crop_to_ratio)
+{
+
+}
+
+
+string
+ShowPlaylistEntry::uuid() const
+{
+ return _uuid;
+}
+
+
+string
+ShowPlaylistEntry::name() const
+{
+ return _name;
+}
+
+
+dcp::ContentKind
+ShowPlaylistEntry::kind() const
+{
+ return _kind;
+}
+
+
+bool
+ShowPlaylistEntry::encrypted() const
+{
+ return _encrypted;
+}
+
+
+string
+ShowPlaylistEntry::approximate_length() const
+{
+ return _approximate_length;
+}
+
+
+optional<float>
+ShowPlaylistEntry::crop_to_ratio() const
+{
+ return _crop_to_ratio;
+}
+
+
+void
+ShowPlaylistEntry::set_crop_to_ratio(optional<float> ratio)
+{
+ _crop_to_ratio = ratio;
+}
+
+
+nlohmann::json
+ShowPlaylistEntry::as_json() const
+{
+ nlohmann::json json;
+ json["uuid"] = _uuid;
+ json["name"] = _name;
+ json["kind"] = _kind.name();
+ json["encrypted"] = _encrypted;
+ json["approximate_length"] = _approximate_length;
+ if (_crop_to_ratio) {
+ json["crop_to_ratio"] = static_cast<int>(std::round(*_crop_to_ratio * 100));
+ }
+ return json;
+}
+
+
+
+bool
+operator==(ShowPlaylistEntry const& a, ShowPlaylistEntry const& b)
+{
+ return a.uuid() == b.uuid() &&
+ a.name() == b.name() &&
+ a.kind() == b.kind() &&
+ a.approximate_length() == b.approximate_length() &&
+ a.encrypted() == b.encrypted() &&
+ a.crop_to_ratio() == b.crop_to_ratio();
+
+}
+
+
+bool
+operator!=(ShowPlaylistEntry const& a, ShowPlaylistEntry const& b)
+{
+ return !(a == b);
+}
+
diff --git a/src/lib/show_playlist_entry.h b/src/lib/show_playlist_entry.h
new file mode 100644
index 000000000..77264f878
--- /dev/null
+++ b/src/lib/show_playlist_entry.h
@@ -0,0 +1,78 @@
+/*
+ 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).
+ *
+ * Given a UUID from the database, a ShowPlaylistEntry can be obtained from the
+ * ShowPlaylistList.
+ */
+class ShowPlaylistEntry
+{
+public:
+ ShowPlaylistEntry(std::shared_ptr<Content> content, boost::optional<float> crop_to_ratio);
+ ShowPlaylistEntry(
+ std::string uuid,
+ std::string name,
+ dcp::ContentKind kind,
+ std::string approximate_length,
+ bool encrypted,
+ boost::optional<float> crop_to_ratio
+ );
+
+ 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;
+ boost::optional<float> crop_to_ratio() const;
+
+ void set_crop_to_ratio(boost::optional<float> ratio);
+
+private:
+ std::string _uuid;
+ std::string _name;
+ dcp::ContentKind _kind;
+ std::string _approximate_length;
+ bool _encrypted;
+ boost::optional<float> _crop_to_ratio;
+};
+
+
+bool operator==(ShowPlaylistEntry const& a, ShowPlaylistEntry const& b);
+bool operator!=(ShowPlaylistEntry const& a, ShowPlaylistEntry const& b);
+
+
+#endif
diff --git a/src/lib/spl_entry.h b/src/lib/show_playlist_id.h
index 6ec5c7540..489501a77 100644
--- a/src/lib/spl_entry.h
+++ b/src/lib/show_playlist_id.h
@@ -1,5 +1,5 @@
/*
- Copyright (C) 2018-2021 Carl Hetherington <cth@carlh.net>
+ Copyright (C) 2025 Carl Hetherington <cth@carlh.net>
This file is part of DCP-o-matic.
@@ -19,42 +19,28 @@
*/
-#ifndef DCPOMATIC_SPL_ENTRY_H
-#define DCPOMATIC_SPL_ENTRY_H
+#ifndef DCPOMATIC_SHOW_PLAYLIST_ID
+#define DCPOMATIC_SHOW_PLAYLIST_ID
-#include <libcxml/cxml.h>
-#include <dcp/content_kind.h>
-#include <libcxml/cxml.h>
+#include "id.h"
-namespace xmlpp {
- class Element;
-}
-
-class Content;
-
-
-class SPLEntry
+/** @class ShowPlaylistID
+ *
+ * @brief The SQLite ID (not the UUID) of a ShowPlaylist.
+ */
+class ShowPlaylistID : public ID
{
public:
- SPLEntry(std::shared_ptr<Content> c, cxml::ConstNodePtr node = {});
-
- void as_xml(xmlpp::Element* e) const;
-
- std::shared_ptr<Content> content;
- std::string name;
- /** Digest of this content */
- std::string digest;
- /** CPL ID */
- boost::optional<std::string> id;
- boost::optional<dcp::ContentKind> kind;
- bool encrypted = false;
- boost::optional<float> crop_to_ratio;
-
-private:
- void construct(std::shared_ptr<Content> content);
+ explicit ShowPlaylistID(sqlite3_int64 id)
+ : ID(id) {}
+
+ bool operator<(ShowPlaylistID const& other) const {
+ return get() < other.get();
+ }
};
#endif
+
diff --git a/src/lib/show_playlist_list.cc b/src/lib/show_playlist_list.cc
new file mode 100644
index 000000000..d8ec379ce
--- /dev/null
+++ b/src/lib/show_playlist_list.cc
@@ -0,0 +1,417 @@
+/*
+ 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_content_store.h"
+#include "show_playlist_entry.h"
+#include "show_playlist_list.h"
+#include "sqlite_statement.h"
+#include "sqlite_transaction.h"
+#include <dcp/filesystem.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("name", "TEXT");
+ _entries.add_column("kind", "TEXT");
+ _entries.add_column("approximate_length", "TEXT");
+ _entries.add_column("encrypted", "INTEGER");
+ _entries.add_column("crop_to_ratio", "REAL");
+ _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 ShowPlaylistID(sqlite3_last_insert_rowid(_db.db()));
+}
+
+
+void
+ShowPlaylistList::update_show_playlist(ShowPlaylistID id, ShowPlaylist const& playlist)
+{
+ SQLiteStatement statement(_db, _show_playlists.update("WHERE id=?"));
+
+ statement.bind_text(1, playlist.uuid());
+ statement.bind_text(2, playlist.name());
+ statement.bind_int64(3, id.get());
+
+ statement.execute();
+}
+
+
+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);
+}
+
+
+optional<ShowPlaylist>
+ShowPlaylistList::show_playlist(ShowPlaylistID id) const
+{
+ SQLiteStatement statement(_db, _show_playlists.select("WHERE id=?"));
+ statement.bind_int64(1, id.get());
+ auto results = show_playlists_from_result(statement);
+ if (results.empty()) {
+ return {};
+ }
+
+ return results[0].second;
+}
+
+
+
+vector<ShowPlaylistEntry>
+ShowPlaylistList::entries(std::string const& where, std::function<void (SQLiteStatement&)> bind) const
+{
+ SQLiteStatement statement(
+ _db,
+ fmt::format(
+ "SELECT entries.uuid,entries.name,entries.kind,entries.approximate_length,entries.encrypted,entries.crop_to_ratio "
+ "FROM entries "
+ "JOIN show_playlists ON entries.show_playlist=show_playlists.id "
+ "{} ORDER BY entries.sort_index", where
+ )
+ );
+
+ bind(statement);
+
+ vector<ShowPlaylistEntry> output;
+
+ statement.execute([&output](SQLiteStatement& statement) {
+ DCPOMATIC_ASSERT(statement.data_count() == 6);
+ output.push_back(
+ ShowPlaylistEntry(
+ statement.column_text(0),
+ statement.column_text(1),
+ dcp::ContentKind::from_name(statement.column_text(2)),
+ statement.column_text(3),
+ statement.column_int64(4),
+ statement.column_double(5) > 0 ? optional<float>(statement.column_double(5)) : optional<float>()
+ )
+ );
+ });
+
+ return output;
+}
+
+
+vector<ShowPlaylistEntry>
+ShowPlaylistList::entries(ShowPlaylistID show_playlist_id) const
+{
+ return entries("WHERE show_playlists.id=?", [show_playlist_id](SQLiteStatement& statement) { statement.bind_int64(1, show_playlist_id.get()); });
+}
+
+
+vector<ShowPlaylistEntry>
+ShowPlaylistList::entries(string const& show_playlist_uuid) const
+{
+ return entries("WHERE show_playlists.uuid=?", [show_playlist_uuid](SQLiteStatement& statement) { statement.bind_text(1, show_playlist_uuid); });
+}
+
+
+void
+ShowPlaylistList::add_entry(ShowPlaylistID playlist_id, ShowPlaylistEntry const& entry)
+{
+ SQLiteTransaction transaction(_db);
+
+ SQLiteStatement find_last_entry(_db, "SELECT MAX(sort_index) FROM entries WHERE show_playlist=?");
+ find_last_entry.bind_int64(1, playlist_id.get());
+
+ int highest_index = 0;
+ find_last_entry.execute([&highest_index](SQLiteStatement& statement) {
+ DCPOMATIC_ASSERT(statement.data_count() == 1);
+ highest_index = statement.column_int64(0);
+ });
+
+ SQLiteStatement count_entries(_db, "SELECT COUNT(id) FROM entries WHERE show_playlist=?");
+ count_entries.bind_int64(1, playlist_id.get());
+
+ count_entries.execute([&highest_index](SQLiteStatement& statement) {
+ DCPOMATIC_ASSERT(statement.data_count() == 1);
+ if (statement.column_int64(0) == 0) {
+ highest_index = -1;
+ }
+ });
+
+ SQLiteStatement add_entry(_db, _entries.insert());
+
+ add_entry.bind_int64(1, playlist_id.get());
+ add_entry.bind_text(2, entry.uuid());
+ add_entry.bind_text(3, entry.name());
+ add_entry.bind_text(4, entry.kind().name());
+ add_entry.bind_text(5, entry.approximate_length());
+ add_entry.bind_int64(6, entry.encrypted());
+ add_entry.bind_double(7, entry.crop_to_ratio().get_value_or(0));
+ add_entry.bind_int64(8, highest_index + 1);
+
+ add_entry.execute();
+
+ transaction.commit();
+}
+
+
+void
+ShowPlaylistList::update_entry(ShowPlaylistID playlist_id, int index, ShowPlaylistEntry const& entry)
+{
+ SQLiteStatement update_entry(_db, _entries.update("WHERE show_playlist=? AND sort_index=?"));
+
+ update_entry.bind_int64(1, playlist_id.get());
+ update_entry.bind_text(2, entry.uuid());
+ update_entry.bind_text(3, entry.name());
+ update_entry.bind_text(4, entry.kind().name());
+ update_entry.bind_text(5, entry.approximate_length());
+ update_entry.bind_int64(6, entry.encrypted());
+ update_entry.bind_double(7, entry.crop_to_ratio().get_value_or(0));
+ update_entry.bind_int64(8, index);
+ update_entry.bind_int64(9, playlist_id.get());
+ update_entry.bind_int64(10, index);
+
+ update_entry.execute();
+}
+
+
+void
+ShowPlaylistList::remove_entry(ShowPlaylistID playlist_id, int index)
+{
+ SQLiteTransaction transaction(_db);
+
+ SQLiteStatement delete_entry(_db, _entries.remove("WHERE show_playlist=? AND sort_index=?"));
+ delete_entry.bind_int64(1, playlist_id.get());
+ delete_entry.bind_int64(2, index);
+
+ delete_entry.execute();
+
+ SQLiteStatement find(_db, "SELECT id FROM entries WHERE show_playlist=? ORDER BY sort_index");
+ find.bind_int64(1, playlist_id.get());
+
+ vector<sqlite3_int64> ids;
+ find.execute([&ids](SQLiteStatement& statement) {
+ DCPOMATIC_ASSERT(statement.data_count() == 1);
+ ids.push_back(statement.column_int64(0));
+ });
+
+ int new_index = 0;
+ for (auto id: ids) {
+ SQLiteStatement update(_db, "UPDATE entries SET sort_index=? WHERE id=?");
+ update.bind_int64(1, new_index++);
+ update.bind_int64(2, id);
+ update.execute();
+ }
+
+ transaction.commit();
+}
+
+
+/** Swap the entries at index and index + 1 */
+void
+ShowPlaylistList::swap_entries(ShowPlaylistID playlist_id, int index)
+{
+ 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);
+
+ 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();
+}
+
+
+void
+ShowPlaylistList::move_entry_up(ShowPlaylistID playlist_id, int index)
+{
+ DCPOMATIC_ASSERT(index >= 1);
+ swap_entries(playlist_id, index - 1);
+}
+
+
+void
+ShowPlaylistList::move_entry_down(ShowPlaylistID playlist_id, int index)
+{
+ swap_entries(playlist_id, index);
+}
+
+
+void
+ShowPlaylistList::read_legacy(boost::filesystem::path dir)
+{
+ auto const store = ShowPlaylistContentStore::instance();
+
+ for (auto playlist: boost::filesystem::directory_iterator(dir)) {
+ cxml::Document doc("SPL");
+ doc.read_file(dcp::filesystem::fix_long_path(playlist));
+ auto const spl_id = add_show_playlist({ doc.string_child("Id"), doc.string_child("Name") });
+ for (auto entry: doc.node_children("Entry")) {
+ auto uuid = entry->optional_string_child("CPL");
+ if (!uuid) {
+ uuid = entry->string_child("Digest");
+ }
+ if (auto content = store->get(*uuid)) {
+ add_entry(spl_id, ShowPlaylistEntry(content, entry->optional_number_child<float>("CropToRatio")));
+ }
+ }
+ }
+}
+
+
+bool
+ShowPlaylistList::missing(string const& playlist_uuid) const
+{
+ auto store = ShowPlaylistContentStore::instance();
+ for (auto entry: entries(playlist_uuid)) {
+ if (!store->get(entry)) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+
+bool
+ShowPlaylistList::missing(ShowPlaylistID playlist_id) const
+{
+ auto store = ShowPlaylistContentStore::instance();
+ for (auto entry: entries(playlist_id)) {
+ if (!store->get(entry)) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+
+optional<ShowPlaylistID>
+ShowPlaylistList::get_show_playlist_id(string const& playlist_uuid) const
+{
+ SQLiteStatement statement(_db, "SELECT id FROM show_playlists WHERE uuid=?");
+ statement.bind_text(1, playlist_uuid);
+
+ optional<ShowPlaylistID> id;
+ statement.execute([&id](SQLiteStatement& statement) {
+ DCPOMATIC_ASSERT(statement.data_count() == 1);
+ id = ShowPlaylistID(statement.column_int64(0));
+ });
+
+ return id;
+}
+
diff --git a/src/lib/show_playlist_list.h b/src/lib/show_playlist_list.h
new file mode 100644
index 000000000..c1849f589
--- /dev/null
+++ b/src/lib/show_playlist_list.h
@@ -0,0 +1,97 @@
+/*
+ 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 "show_playlist_entry.h"
+#include "show_playlist_id.h"
+#include "sqlite_database.h"
+#include "sqlite_table.h"
+
+
+class ShowPlaylist;
+class SQLiteStatement;
+
+
+/** @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.
+ *
+ * There are two tables: show_playlists and entries. show_playlists contains
+ * just playlist UUIDs with their names.
+ */
+class ShowPlaylistList
+{
+public:
+ ShowPlaylistList();
+ explicit ShowPlaylistList(boost::filesystem::path db_file);
+
+ /** Write a ShowPlaylist to the database, returning its new SQLite ID */
+ ShowPlaylistID add_show_playlist(ShowPlaylist const& show_playlist);
+ void update_show_playlist(ShowPlaylistID id, ShowPlaylist const& show_playlist);
+ void remove_show_playlist(ShowPlaylistID id);
+
+ std::vector<std::pair<ShowPlaylistID, ShowPlaylist>> show_playlists() const;
+ boost::optional<ShowPlaylist> show_playlist(ShowPlaylistID id) const;
+
+ boost::optional<ShowPlaylistID> get_show_playlist_id(std::string const& show_playlist_uuid) const;
+
+ /** @return The entries on a given show playlist, given the playlist's SQLite ID */
+ std::vector<ShowPlaylistEntry> entries(ShowPlaylistID show_playlist_id) const;
+ /** @return The entries on a given show playlist, given the playlist's UUID */
+ std::vector<ShowPlaylistEntry> entries(std::string const& show_playlist_uuid) const;
+
+ bool missing(std::string const& show_playlist_uuid) const;
+ bool missing(ShowPlaylistID id) const;
+
+ /** Write a playlist entry to the database */
+ void add_entry(ShowPlaylistID, ShowPlaylistEntry const& entry);
+ /** Set the values in the database from entry */
+ void update_entry(ShowPlaylistID, int index, ShowPlaylistEntry const& entry);
+ /** Remove a playlist entry from the database */
+ void remove_entry(ShowPlaylistID, int index);
+ /** Move the given playlist entry one place higher (earlier) */
+ void move_entry_up(ShowPlaylistID, int index);
+ /** Move the given playlist entry one place lower (later) */
+ void move_entry_down(ShowPlaylistID, int index);
+
+ void read_legacy(boost::filesystem::path dir);
+
+private:
+ void setup_tables();
+ void setup();
+ std::vector<ShowPlaylistEntry> entries(std::string const& where, std::function<void (SQLiteStatement&)> bind) const;
+ void swap_entries(ShowPlaylistID playlist_id, int index);
+
+ 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 e958bf7c9..000000000
--- a/src/lib/spl.cc
+++ /dev/null
@@ -1,76 +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 "show_playlist_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;
-
-
-void
-SPL::read(boost::filesystem::path path, ShowPlaylistContentStore* 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")) {
- if (auto cpl = i->optional_string_child("CPL")) {
- if (auto c = store->get_by_cpl_id(*cpl)) {
- add(SPLEntry(c, i));
- } else {
- _missing = true;
- }
- } else {
- if (auto c = store->get_by_digest(i->string_child("Digest"))) {
- add(SPLEntry(c, i));
- } 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());
-}
diff --git a/src/lib/spl.h b/src/lib/spl.h
deleted file mode 100644
index 9d1a9fdbc..000000000
--- a/src/lib/spl.h
+++ /dev/null
@@ -1,143 +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>
-LIBDCP_DISABLE_WARNINGS
-#include <boost/signals2.hpp>
-LIBDCP_ENABLE_WARNINGS
-#include <algorithm>
-
-
-class ShowPlaylistContentStore;
-
-
-class SPL
-{
-public:
- SPL()
- : _id(dcp::make_uuid())
- {}
-
- 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);
- }
-
- std::vector<SPLEntry> const & get() const {
- return _spl;
- }
-
- SPLEntry const& get(std::size_t index) const {
- return _spl[index];
- }
-
- void set(std::size_t index, SPLEntry entry) {
- _spl[index] = std::move(entry);
- }
-
- void swap(size_t a, size_t b) {
- std::iter_swap(_spl.begin() + a, _spl.begin() + b);
- }
-
- void read(boost::filesystem::path path, ShowPlaylistContentStore* 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;
- }
-
- bool missing() const {
- return _missing;
- }
-
-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 : public SPL
-{
-public:
- enum class Change {
- NAME,
- CONTENT,
- };
-
- SignalSPL() {}
-
- 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 swap(size_t a, size_t b) {
- SPL::swap(a, b);
- Changed(Change::CONTENT);
- }
-
- void set(std::size_t index, SPLEntry entry) {
- SPL::set(index, entry);
- 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 2e6860235..000000000
--- a/src/lib/spl_entry.cc
+++ /dev/null
@@ -1,70 +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>
-#include <fmt/format.h>
-LIBDCP_DISABLE_WARNINGS
-#include <libxml++/libxml++.h>
-LIBDCP_ENABLE_WARNINGS
-
-
-using std::dynamic_pointer_cast;
-using std::shared_ptr;
-
-
-SPLEntry::SPLEntry(shared_ptr<Content> c, cxml::ConstNodePtr node)
- : content(c)
- , digest(content->digest())
-{
- if (auto dcp = dynamic_pointer_cast<DCPContent>(content)) {
- 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;
- }
-
- if (node) {
- if (auto crop = node->optional_number_child<float>("CropToRatio")) {
- crop_to_ratio = *crop;
- }
- }
-}
-
-
-void
-SPLEntry::as_xml(xmlpp::Element* e) const
-{
- if (id) {
- cxml::add_text_child(e, "CPL", *id);
- } else {
- cxml::add_text_child(e, "Digest", digest);
- }
- if (crop_to_ratio) {
- cxml::add_text_child(e, "CropToRatio", fmt::to_string(*crop_to_ratio));
- }
-}
diff --git a/src/lib/sqlite_statement.cc b/src/lib/sqlite_statement.cc
index d130da507..72d33d1b4 100644
--- a/src/lib/sqlite_statement.cc
+++ b/src/lib/sqlite_statement.cc
@@ -69,6 +69,16 @@ SQLiteStatement::bind_int64(int index, int64_t value)
void
+SQLiteStatement::bind_double(int index, double value)
+{
+ auto rc = sqlite3_bind_double(_stmt, index, value);
+ if (rc != SQLITE_OK) {
+ throw SQLError(_db, rc);
+ }
+}
+
+
+void
SQLiteStatement::execute(function<void(SQLiteStatement&)> row, function<void()> busy)
{
while (true) {
@@ -104,6 +114,13 @@ SQLiteStatement::column_int64(int index)
}
+double
+SQLiteStatement::column_double(int index)
+{
+ return sqlite3_column_double(_stmt, index);
+}
+
+
string
SQLiteStatement::column_text(int index)
{
diff --git a/src/lib/sqlite_statement.h b/src/lib/sqlite_statement.h
index f1131e899..942348e1a 100644
--- a/src/lib/sqlite_statement.h
+++ b/src/lib/sqlite_statement.h
@@ -38,9 +38,11 @@ public:
void bind_text(int index, std::string const& value);
void bind_int64(int index, int64_t value);
+ void bind_double(int index, double value);
int64_t column_int64(int index);
std::string column_text(int index);
+ double column_double(int index);
void execute(std::function<void(SQLiteStatement&)> row = std::function<void(SQLiteStatement& statement)>(), std::function<void()> busy = std::function<void()>());
diff --git a/src/lib/sqlite_table.cc b/src/lib/sqlite_table.cc
index 81843ee00..79c6f99d0 100644
--- a/src/lib/sqlite_table.cc
+++ b/src/lib/sqlite_table.cc
@@ -76,3 +76,11 @@ SQLiteTable::select(string const& condition) const
{
return fmt::format("SELECT id,{} FROM {} {}", join_strings(_columns, ","), _name, condition);
}
+
+
+string
+SQLiteTable::remove(string const& condition) const
+{
+ DCPOMATIC_ASSERT(!_columns.empty());
+ return fmt::format("DELETE FROM {} {}", _name, condition);
+}
diff --git a/src/lib/sqlite_table.h b/src/lib/sqlite_table.h
index 43c9491ed..218ab4526 100644
--- a/src/lib/sqlite_table.h
+++ b/src/lib/sqlite_table.h
@@ -42,6 +42,7 @@ public:
std::string insert() const;
std::string update(std::string const& condition) const;
std::string select(std::string const& condition) const;
+ std::string remove(std::string const& condition) const;
private:
std::string _name;
diff --git a/src/lib/wscript b/src/lib/wscript
index 4478c2587..ed55ec4d0 100644
--- a/src/lib/wscript
+++ b/src/lib/wscript
@@ -195,10 +195,11 @@ sources = """
send_problem_report_job.cc
server.cc
show_playlist_content_store.cc
+ show_playlist_entry.cc
+ show_playlist_list.cc
+ show_playlist.cc
shuffler.cc
state.cc
- spl.cc
- spl_entry.cc
sqlite_database.cc
sqlite_statement.cc
sqlite_table.cc