summaryrefslogtreecommitdiff
path: root/src/lib/util.cc
diff options
context:
space:
mode:
authorCarl Hetherington <cth@carlh.net>2012-07-15 00:14:28 +0100
committerCarl Hetherington <cth@carlh.net>2012-07-15 00:14:28 +0100
commitbb767c7e338414beee132af3e96829c1448e214b (patch)
treebec2858dcc7225a9bcc2acd8170c25508f6df6cb /src/lib/util.cc
parent66c9be6bdb1361e5681e094a0c8170d268aa9518 (diff)
Move things round a bit.
Diffstat (limited to 'src/lib/util.cc')
-rw-r--r--src/lib/util.cc496
1 files changed, 496 insertions, 0 deletions
diff --git a/src/lib/util.cc b/src/lib/util.cc
new file mode 100644
index 000000000..b8531e26b
--- /dev/null
+++ b/src/lib/util.cc
@@ -0,0 +1,496 @@
+/*
+ Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+ Copyright (C) 2000-2007 Paul Davis
+
+ This program 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.
+
+ This program 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 this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+/** @file src/lib/util.cc
+ * @brief Some utility functions and classes.
+ */
+
+#include <sstream>
+#include <iomanip>
+#include <iostream>
+#include <execinfo.h>
+#include <cxxabi.h>
+#include <signal.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <boost/algorithm/string.hpp>
+#include <openjpeg.h>
+#include <magick/MagickCore.h>
+#include <magick/version.h>
+#include <libssh/libssh.h>
+extern "C" {
+#include <libavcodec/avcodec.h>
+#include <libavformat/avformat.h>
+#include <libswscale/swscale.h>
+#include <libswresample/swresample.h>
+#include <libavfilter/avfiltergraph.h>
+#include <libavfilter/avcodec.h>
+#include <libavfilter/buffersink.h>
+#include <libpostproc/postprocess.h>
+#include <libavutil/pixfmt.h>
+}
+#include "util.h"
+#include "exceptions.h"
+#include "scaler.h"
+#include "format.h"
+#include "dcp_content_type.h"
+#include "filter.h"
+#include "screen.h"
+#include "film_state.h"
+#include "player_manager.h"
+
+#ifdef DEBUG_HASH
+#include <mhash.h>
+#endif
+
+using namespace std;
+using namespace boost;
+
+/** Convert some number of seconds to a string representation
+ * in hours, minutes and seconds.
+ *
+ * @param s Seconds.
+ * @return String of the form H:M:S (where H is hours, M
+ * is minutes and S is seconds).
+ */
+string
+seconds_to_hms (int s)
+{
+ int m = s / 60;
+ s -= (m * 60);
+ int h = m / 60;
+ m -= (h * 60);
+
+ stringstream hms;
+ hms << h << ":";
+ hms.width (2);
+ hms << setfill ('0') << m << ":";
+ hms.width (2);
+ hms << setfill ('0') << s;
+
+ return hms.str ();
+}
+
+/** @param s Number of seconds.
+ * @return String containing an approximate description of s (e.g. "about 2 hours")
+ */
+string
+seconds_to_approximate_hms (int s)
+{
+ int m = s / 60;
+ s -= (m * 60);
+ int h = m / 60;
+ m -= (h * 60);
+
+ stringstream ap;
+
+ if (h > 0) {
+ if (m > 30) {
+ ap << (h + 1) << " hours";
+ } else {
+ if (h == 1) {
+ ap << "1 hour";
+ } else {
+ ap << h << " hours";
+ }
+ }
+ } else if (m > 0) {
+ if (m == 1) {
+ ap << "1 minute";
+ } else {
+ ap << m << " minutes";
+ }
+ } else {
+ ap << s << " seconds";
+ }
+
+ return ap.str ();
+}
+
+/** @param l Mangled C++ identifier.
+ * @return Demangled version.
+ */
+static string
+demangle (string l)
+{
+ string::size_type const b = l.find_first_of ("(");
+ if (b == string::npos) {
+ return l;
+ }
+
+ string::size_type const p = l.find_last_of ("+");
+ if (p == string::npos) {
+ return l;
+ }
+
+ if ((p - b) <= 1) {
+ return l;
+ }
+
+ string const fn = l.substr (b + 1, p - b - 1);
+
+ int status;
+ try {
+
+ char* realname = abi::__cxa_demangle (fn.c_str(), 0, 0, &status);
+ string d (realname);
+ free (realname);
+ return d;
+
+ } catch (std::exception) {
+
+ }
+
+ return l;
+}
+
+/** Write a stacktrace to an ostream.
+ * @param out Stream to write to.
+ * @param levels Number of levels to go up the call stack.
+ */
+void
+stacktrace (ostream& out, int levels)
+{
+ void *array[200];
+ size_t size;
+ char **strings;
+ size_t i;
+
+ size = backtrace (array, 200);
+ strings = backtrace_symbols (array, size);
+
+ if (strings) {
+ for (i = 0; i < size && (levels == 0 || i < size_t(levels)); i++) {
+ out << " " << demangle (strings[i]) << endl;
+ }
+
+ free (strings);
+ }
+}
+
+/** @param s Sample format.
+ * @return String representation.
+ */
+string
+audio_sample_format_to_string (AVSampleFormat s)
+{
+ /* Our sample format handling is not exactly complete */
+
+ switch (s) {
+ case AV_SAMPLE_FMT_S16:
+ return "S16";
+ default:
+ break;
+ }
+
+ return "Unknown";
+}
+
+/** @param s String representation of a sample format, as returned from audio_sample_format_to_string().
+ * @return Sample format.
+ */
+AVSampleFormat
+audio_sample_format_from_string (string s)
+{
+ if (s == "S16") {
+ return AV_SAMPLE_FMT_S16;
+ }
+
+ return AV_SAMPLE_FMT_NONE;
+}
+
+/** @return Version of OpenDCP that is on the path (and hence that we will use) */
+static string
+opendcp_version ()
+{
+ FILE* f = popen ("opendcp_xml", "r");
+ if (f == 0) {
+ throw EncodeError ("could not run opendcp_xml to check version");
+ }
+
+ string version = "unknown";
+
+ while (!feof (f)) {
+ char* buf = 0;
+ size_t n = 0;
+ ssize_t const r = getline (&buf, &n, f);
+ if (r > 0) {
+ string s (buf);
+ vector<string> b;
+ split (b, s, is_any_of (" "));
+ if (b.size() >= 3 && b[0] == "OpenDCP" && b[1] == "version") {
+ version = b[2];
+ }
+ free (buf);
+ }
+ }
+
+ pclose (f);
+
+ return version;
+}
+
+/** @return Version of vobcopy that is on the path (and hence that we will use) */
+static string
+vobcopy_version ()
+{
+ FILE* f = popen ("vobcopy -V 2>&1", "r");
+ if (f == 0) {
+ throw EncodeError ("could not run vobcopy to check version");
+ }
+
+ string version = "unknown";
+
+ while (!feof (f)) {
+ char* buf = 0;
+ size_t n = 0;
+ ssize_t const r = getline (&buf, &n, f);
+ if (r > 0) {
+ string s (buf);
+ vector<string> b;
+ split (b, s, is_any_of (" "));
+ if (b.size() >= 2 && b[0] == "Vobcopy") {
+ version = b[1];
+ }
+ free (buf);
+ }
+ }
+
+ pclose (f);
+
+ return version;
+}
+
+/** @param v Version as used by FFmpeg.
+ * @return A string representation of v.
+ */
+static string
+ffmpeg_version_to_string (int v)
+{
+ stringstream s;
+ s << ((v & 0xff0000) >> 16) << "." << ((v & 0xff00) >> 8) << "." << (v & 0xff);
+ return s.str ();
+}
+
+/** Return a user-readable string summarising the versions of our dependencies */
+string
+dependency_version_summary ()
+{
+ stringstream s;
+ s << "libopenjpeg " << opj_version () << ", "
+ << "opendcp " << opendcp_version () << ", "
+ << "vobcopy " << vobcopy_version() << ", "
+ << "libswresample " << ffmpeg_version_to_string (swresample_version()) << ", "
+ << "libavcodec " << ffmpeg_version_to_string (avcodec_version()) << ", "
+ << "libavfilter " << ffmpeg_version_to_string (avfilter_version()) << ", "
+ << "libavformat " << ffmpeg_version_to_string (avformat_version()) << ", "
+ << "libavutil " << ffmpeg_version_to_string (avutil_version()) << ", "
+ << "libpostproc " << ffmpeg_version_to_string (postproc_version()) << ", "
+ << "libswscale " << ffmpeg_version_to_string (swscale_version()) << ", "
+ << MagickVersion << ", "
+ << "libssh " << ssh_version (0);
+
+ return s.str ();
+}
+
+/** Write some data to a socket.
+ * @param fd Socket file descriptor.
+ * @param data Data.
+ * @param size Amount to write, in bytes.
+ */
+void
+socket_write (int fd, uint8_t const * data, int size)
+{
+ uint8_t const * p = data;
+ while (size) {
+ int const n = send (fd, p, size, MSG_NOSIGNAL);
+ if (n < 0) {
+ stringstream s;
+ s << "could not write (" << strerror (errno) << ")";
+ throw NetworkError (s.str ());
+ }
+
+ size -= n;
+ p += n;
+ }
+}
+
+double
+seconds (struct timeval t)
+{
+ return t.tv_sec + (double (t.tv_usec) / 1e6);
+}
+
+/** @param fd File descriptor to read from */
+SocketReader::SocketReader (int fd)
+ : _fd (fd)
+ , _buffer_data (0)
+{
+
+}
+
+/** Mark some data as being `consumed', so that it will not be returned
+ * as data again.
+ * @param size Amount of data to consume, in bytes.
+ */
+void
+SocketReader::consume (int size)
+{
+ assert (_buffer_data >= size);
+
+ _buffer_data -= size;
+ if (_buffer_data > 0) {
+ /* Shift still-valid data to the start of the buffer */
+ memmove (_buffer, _buffer + size, _buffer_data);
+ }
+}
+
+/** Read a definite amount of data from our socket, and mark
+ * it as consumed.
+ * @param data Where to put the data.
+ * @param size Number of bytes to read.
+ */
+void
+SocketReader::read_definite_and_consume (uint8_t* data, int size)
+{
+ int const from_buffer = min (_buffer_data, size);
+ if (from_buffer > 0) {
+ /* Get data from our buffer */
+ memcpy (data, _buffer, from_buffer);
+ consume (from_buffer);
+ /* Update our output state */
+ data += from_buffer;
+ size -= from_buffer;
+ }
+
+ /* read() the rest */
+ while (size > 0) {
+ int const n = ::read (_fd, data, size);
+ if (n <= 0) {
+ throw NetworkError ("could not read");
+ }
+
+ data += n;
+ size -= n;
+ }
+}
+
+/** Read as much data as is available, up to some limit.
+ * @param data Where to put the data.
+ * @param size Maximum amount of data to read.
+ */
+void
+SocketReader::read_indefinite (uint8_t* data, int size)
+{
+ assert (size < int (sizeof (_buffer)));
+
+ /* Amount of extra data we need to read () */
+ int to_read = size - _buffer_data;
+ while (to_read > 0) {
+ /* read as much of it as we can (into our buffer) */
+ int const n = ::read (_fd, _buffer + _buffer_data, to_read);
+ if (n <= 0) {
+ throw NetworkError ("could not read");
+ }
+
+ to_read -= n;
+ _buffer_data += n;
+ }
+
+ assert (_buffer_data >= size);
+
+ /* copy data into the output buffer */
+ assert (size >= _buffer_data);
+ memcpy (data, _buffer, size);
+}
+
+void
+sigchld_handler (int, siginfo_t* info, void *)
+{
+ PlayerManager::instance()->child_exited (info->si_pid);
+}
+
+/** Call the required functions to set up DVD-o-matic's static arrays, etc. */
+void
+dvdomatic_setup ()
+{
+ Format::setup_formats ();
+ DCPContentType::setup_dcp_content_types ();
+ Scaler::setup_scalers ();
+ Filter::setup_filters ();
+
+ struct sigaction sa;
+ sa.sa_flags = SA_SIGINFO;
+ sigemptyset (&sa.sa_mask);
+ sa.sa_sigaction = sigchld_handler;
+ sigaction (SIGCHLD, &sa, 0);
+}
+
+string
+crop_string (Position start, Size size)
+{
+ stringstream s;
+ s << "crop=" << size.width << ":" << size.height << ":" << start.x << ":" << start.y;
+ return s.str ();
+}
+
+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;
+}
+
+#ifdef DEBUG_HASH
+void
+md5_data (string title, void const * data, int size)
+{
+ MHASH ht = mhash_init (MHASH_MD5);
+ if (ht == MHASH_FAILED) {
+ throw EncodeError ("could not create hash thread");
+ }
+
+ mhash (ht, data, size);
+
+ uint8_t hash[16];
+ mhash_deinit (ht, hash);
+
+ printf ("%s [%d]: ", title.c_str (), size);
+ for (int i = 0; i < int (mhash_get_block_size (MHASH_MD5)); ++i) {
+ printf ("%.2x", hash[i]);
+ }
+ printf ("\n");
+}
+#endif
+