+/** Call the required functions to set up DVD-o-matic's static arrays, etc.
+ * Must be called from the UI thread, if there is one.
+ */
+void
+dvdomatic_setup ()
+{
+ Format::setup_formats ();
+ DCPContentType::setup_dcp_content_types ();
+ Scaler::setup_scalers ();
+ Filter::setup_filters ();
+ SoundProcessor::setup_sound_processors ();
+
+ ui_thread = this_thread::get_id ();
+}
+
+/** @param start Start position for the crop within the image.
+ * @param size Size of the cropped area.
+ * @return FFmpeg crop filter string.
+ */
+string
+crop_string (Position start, Size size)
+{
+ stringstream s;
+ s << "crop=" << size.width << ":" << size.height << ":" << start.x << ":" << start.y;
+ return s.str ();
+}
+
+/** @param s A string.
+ * @return Parts of the string split at spaces, except when a space is within quotation marks.
+ */
+vector<string>
+split_at_spaces_considering_quotes (string s)
+{
+ vector<string> out;
+ bool in_quotes = false;
+ string c;
+ for (string::size_type i = 0; i < s.length(); ++i) {
+ if (s[i] == ' ' && !in_quotes) {
+ out.push_back (c);
+ c = "";
+ } else if (s[i] == '"') {
+ in_quotes = !in_quotes;
+ } else {
+ c += s[i];
+ }
+ }
+
+ out.push_back (c);
+ return out;
+}
+
+string
+md5_digest (void const * data, int size)
+{
+ MD5_CTX md5_context;
+ MD5_Init (&md5_context);
+ MD5_Update (&md5_context, data, size);
+ unsigned char digest[MD5_DIGEST_LENGTH];
+ MD5_Final (digest, &md5_context);
+
+ stringstream s;
+ for (int i = 0; i < MD5_DIGEST_LENGTH; ++i) {
+ s << hex << setfill('0') << setw(2) << ((int) digest[i]);
+ }
+
+ return s.str ();
+}
+
+/** @param file File name.
+ * @return MD5 digest of file's contents.
+ */
+string
+md5_digest (string file)
+{
+ ifstream f (file.c_str(), ios::binary);
+ if (!f.good ()) {
+ throw OpenFileError (file);
+ }
+
+ f.seekg (0, ios::end);
+ int bytes = f.tellg ();
+ f.seekg (0, ios::beg);
+
+ int const buffer_size = 64 * 1024;
+ char buffer[buffer_size];
+
+ MD5_CTX md5_context;
+ MD5_Init (&md5_context);
+ while (bytes > 0) {
+ int const t = min (bytes, buffer_size);
+ f.read (buffer, t);
+ MD5_Update (&md5_context, buffer, t);
+ bytes -= t;
+ }
+
+ unsigned char digest[MD5_DIGEST_LENGTH];
+ MD5_Final (digest, &md5_context);
+
+ stringstream s;
+ for (int i = 0; i < MD5_DIGEST_LENGTH; ++i) {
+ s << hex << setfill('0') << setw(2) << ((int) digest[i]);
+ }
+
+ return s.str ();
+}
+
+/** @param fps Arbitrary frames-per-second value.
+ * @return DCPFrameRate for this frames-per-second.
+ */
+DCPFrameRate
+dcp_frame_rate (float fps)
+{
+ DCPFrameRate dfr;
+
+ dfr.run_fast = (fps != rint (fps));
+ dfr.frames_per_second = rint (fps);
+ dfr.skip = 1;
+
+ /* XXX: somewhat arbitrary */
+ if (fps == 50) {
+ dfr.frames_per_second = 25;
+ dfr.skip = 2;
+ }
+
+ return dfr;
+}
+
+/** @param An arbitrary sampling rate.
+ * @return The appropriate DCP-approved sampling rate (48kHz or 96kHz).
+ */
+int
+dcp_audio_sample_rate (int fs)
+{
+ if (fs <= 48000) {
+ return 48000;
+ }
+
+ return 96000;
+}
+
+bool operator== (Size const & a, Size const & b)
+{
+ return (a.width == b.width && a.height == b.height);
+}
+
+bool operator== (Crop const & a, Crop const & b)
+{
+ return (a.left == b.left && a.right == b.right && a.top == b.top && a.bottom == b.bottom);
+}
+
+bool operator!= (Crop const & a, Crop const & b)
+{
+ return !(a == b);
+}
+
+/** @param index Colour LUT index.
+ * @return Human-readable name.
+ */
+string
+colour_lut_index_to_name (int index)
+{
+ switch (index) {
+ case 0:
+ return "sRGB";
+ case 1:
+ return "Rec 709";
+ }
+
+ assert (false);
+ return "";
+}
+
+Socket::Socket ()
+ : _deadline (_io_service)
+ , _socket (_io_service)