+2014-02-23 Carl Hetherington <cth@carlh.net>
+
+ * Bump ffmpeg library to git head to fix problems with
+ misrecognised frame rates in some MOV files.
+
+2014-02-20 Carl Hetherington <cth@carlh.net>
+
+ * Version 1.64.15 released.
+
+2014-02-20 Carl Hetherington <cth@carlh.net>
+
+ * Basic support for 7.1 / HI/VI audio tracks.
+
+2014-02-19 Carl Hetherington <cth@carlh.net>
+
+ * Add some basic JSON stuff.
+
+2014-02-18 Carl Hetherington <cth@carlh.net>
+
+ * Version 1.64.14 released.
+
+2014-02-18 Carl Hetherington <cth@carlh.net>
+
+ * Version 1.64.13 released.
+
2014-02-12 Carl Hetherington <cth@carlh.net>
* Make the batch converter remember its last directory
print >>f,''
def dependencies(target):
- return (('ffmpeg-cdist', '5ac3a6af077c10f07c31954c372a8f29e4e18e2a'),
+ return (('ffmpeg-cdist', '08827fa4e1d483511e6135c424d2ca9c56a9ed50'),
('libdcp', '1.0'))
def build(target, options):
-dcpomatic (1.64.12-1) UNRELEASED; urgency=low
+dcpomatic (1.64.15-1) UNRELEASED; urgency=low
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
+ * New upstream release.
+ * New upstream release.
+ * New upstream release.
- -- Carl Hetherington <carl@d1stkfactory> Tue, 11 Feb 2014 15:52:44 +0000
+ -- Carl Hetherington <carl@d1stkfactory> Thu, 20 Feb 2014 10:10:09 +0000
dcpomatic (0.87-1) UNRELEASED; urgency=low
elif [ "$1" == "--valgrind" ]; then
shift
valgrind --tool="memcheck" --suppressions=valgrind.supp --leak-check=full --show-reachable=yes build/src/tools/dcpomatic_batch $*
+elif [ "$1" == "--massif" ]; then
+ shift
+ valgrind --tool="massif" build/src/tools/dcpomatic_batch $*
elif [ "$1" == "--callgrind" ]; then
shift
valgrind --tool="callgrind" build/src/tools/dcpomatic_batch $*
return _("Analyse audio");
}
+string
+AnalyseAudioJob::json_name () const
+{
+ return N_("analyse_audio");
+}
+
void
AnalyseAudioJob::run ()
{
AnalyseAudioJob (boost::shared_ptr<const Film>, boost::shared_ptr<AudioContent>);
std::string name () const;
+ std::string json_name () const;
void run ();
private:
xmlpp::Document doc;
xmlpp::Node* node = doc.create_root_node ("Content");
as_xml (node);
- return content_factory (film, cxml::NodePtr (new cxml::Node (node)), Film::state_version);
+ return content_factory (film, cxml::NodePtr (new cxml::Node (node)), Film::current_state_version);
}
string
virtual void examine (boost::shared_ptr<Job>);
virtual std::string summary () const = 0;
+ /** @return Technical details of this content; these are written to logs to
+ * help with debugging.
+ */
virtual std::string technical_summary () const;
virtual std::string information () const = 0;
virtual void as_xml (xmlpp::Node *) const;
return _("Examine content");
}
+string
+ExamineContentJob::json_name () const
+{
+ return N_("examine_content");
+}
+
void
ExamineContentJob::run ()
{
~ExamineContentJob ();
std::string name () const;
+ std::string json_name () const;
void run ();
private:
{
list<cxml::NodePtr> c = node->node_children ("SubtitleStream");
for (list<cxml::NodePtr>::const_iterator i = c.begin(); i != c.end(); ++i) {
- _subtitle_streams.push_back (shared_ptr<FFmpegSubtitleStream> (new FFmpegSubtitleStream (*i, version)));
+ _subtitle_streams.push_back (shared_ptr<FFmpegSubtitleStream> (new FFmpegSubtitleStream (*i)));
if ((*i)->optional_number_child<int> ("Selected")) {
_subtitle_stream = _subtitle_streams.back ();
}
{
string as = "none";
if (_audio_stream) {
- as = String::compose ("id %1", _audio_stream->id);
+ as = _audio_stream->technical_summary ();
}
string ss = "none";
if (_subtitle_stream) {
- ss = String::compose ("id %1", _subtitle_stream->id);
+ ss = _subtitle_stream->technical_summary ();
}
pair<string, string> filt = Filter::ffmpeg_strings (_filters);
}
bool
-operator== (FFmpegSubtitleStream const & a, FFmpegSubtitleStream const & b)
+operator== (FFmpegStream const & a, FFmpegStream const & b)
{
- return a.id == b.id;
+ return a._id == b._id;
}
bool
-operator!= (FFmpegSubtitleStream const & a, FFmpegSubtitleStream const & b)
+operator!= (FFmpegStream const & a, FFmpegStream const & b)
{
- return a.id != b.id;
+ return a._id != b._id;
}
-bool
-operator== (FFmpegAudioStream const & a, FFmpegAudioStream const & b)
-{
- return a.id == b.id;
-}
-
-bool
-operator!= (FFmpegAudioStream const & a, FFmpegAudioStream const & b)
+FFmpegStream::FFmpegStream (shared_ptr<const cxml::Node> node)
+ : name (node->string_child ("Name"))
+ , _id (node->number_child<int> ("Id"))
{
- return a.id != b.id;
-}
-FFmpegStream::FFmpegStream (shared_ptr<const cxml::Node> node, int version)
- : _legacy_id (false)
-{
- name = node->string_child ("Name");
- id = node->number_child<int> ("Id");
- if (version == 4 || node->optional_bool_child ("LegacyId")) {
- _legacy_id = true;
- }
}
void
FFmpegStream::as_xml (xmlpp::Node* root) const
{
root->add_child("Name")->add_child_text (name);
- root->add_child("Id")->add_child_text (lexical_cast<string> (id));
- if (_legacy_id) {
- /* Write this so that version > 4 files are read in correctly
- if the Id came originally from a version <= 4 file.
- */
- root->add_child("LegacyId")->add_child_text ("1");
- }
+ root->add_child("Id")->add_child_text (lexical_cast<string> (_id));
}
FFmpegAudioStream::FFmpegAudioStream (shared_ptr<const cxml::Node> node, int version)
- : FFmpegStream (node, version)
+ : FFmpegStream (node)
, mapping (node->node_child ("Mapping"), version)
{
frame_rate = node->number_child<int> ("FrameRate");
mapping.as_xml (root->add_child("Mapping"));
}
-int
-FFmpegStream::index (AVFormatContext const * fc) const
+bool
+FFmpegStream::uses_index (AVFormatContext const * fc, int index) const
{
- if (_legacy_id) {
- return id;
- }
-
size_t i = 0;
while (i < fc->nb_streams) {
- if (fc->streams[i]->id == id) {
- return i;
+ if (fc->streams[i]->id == _id) {
+ return int (i) == index;
}
++i;
}
- assert (false);
+ return false;
}
AVStream *
FFmpegStream::stream (AVFormatContext const * fc) const
{
- if (_legacy_id) {
- return fc->streams[id];
- }
-
size_t i = 0;
while (i < fc->nb_streams) {
- if (fc->streams[i]->id == id) {
+ if (fc->streams[i]->id == _id) {
return fc->streams[i];
}
++i;
* @param t String returned from to_string().
* @param v State file version.
*/
-FFmpegSubtitleStream::FFmpegSubtitleStream (shared_ptr<const cxml::Node> node, int version)
- : FFmpegStream (node, version)
+FFmpegSubtitleStream::FFmpegSubtitleStream (shared_ptr<const cxml::Node> node)
+ : FFmpegStream (node)
{
}
boost::mutex::scoped_lock lm (_mutex);
if (_subtitle_stream) {
- s << "_" << _subtitle_stream->id;
+ s << "_" << _subtitle_stream->identifier ();
}
for (vector<Filter const *>::const_iterator i = _filters.begin(); i != _filters.end(); ++i) {
public:
FFmpegStream (std::string n, int i)
: name (n)
- , id (i)
- , _legacy_id (false)
+ , _id (i)
{}
- FFmpegStream (boost::shared_ptr<const cxml::Node>, int);
+ FFmpegStream (boost::shared_ptr<const cxml::Node>);
void as_xml (xmlpp::Node *) const;
/** @param c An AVFormatContext.
- * @return Stream index within the AVFormatContext.
+ * @param index A stream index within the AVFormatContext.
+ * @return true if this FFmpegStream uses the given stream index.
*/
- int index (AVFormatContext const * c) const;
+ bool uses_index (AVFormatContext const * c, int index) const;
AVStream* stream (AVFormatContext const * c) const;
+ std::string technical_summary () const {
+ return "id " + boost::lexical_cast<std::string> (_id);
+ }
+
+ std::string identifier () const {
+ return boost::lexical_cast<std::string> (_id);
+ }
+
std::string name;
- int id;
+
+ friend bool operator== (FFmpegStream const & a, FFmpegStream const & b);
+ friend bool operator!= (FFmpegStream const & a, FFmpegStream const & b);
private:
- /** If this is true, id is in fact the index */
- bool _legacy_id;
+ int _id;
};
class FFmpegAudioStream : public FFmpegStream
{}
};
-extern bool operator== (FFmpegAudioStream const & a, FFmpegAudioStream const & b);
-extern bool operator!= (FFmpegAudioStream const & a, FFmpegAudioStream const & b);
-
class FFmpegSubtitleStream : public FFmpegStream
{
public:
: FFmpegStream (n, i)
{}
- FFmpegSubtitleStream (boost::shared_ptr<const cxml::Node>, int);
+ FFmpegSubtitleStream (boost::shared_ptr<const cxml::Node>);
void as_xml (xmlpp::Node *) const;
};
-extern bool operator== (FFmpegSubtitleStream const & a, FFmpegSubtitleStream const & b);
-extern bool operator!= (FFmpegSubtitleStream const & a, FFmpegSubtitleStream const & b);
-
class FFmpegContentProperty : public VideoContentProperty
{
public:
if (si == _video_stream && _decode_video) {
decode_video_packet ();
- } else if (_ffmpeg_content->audio_stream() && si == _ffmpeg_content->audio_stream()->index (_format_context) && _decode_audio) {
+ } else if (_ffmpeg_content->audio_stream() && _ffmpeg_content->audio_stream()->uses_index (_format_context, si) && _decode_audio) {
decode_audio_packet ();
- } else if (_ffmpeg_content->subtitle_stream() && si == _ffmpeg_content->subtitle_stream()->index (_format_context) && film->with_subtitles ()) {
+ } else if (_ffmpeg_content->subtitle_stream() && _ffmpeg_content->subtitle_stream()->uses_index (_format_context, si) && film->with_subtitles ()) {
decode_subtitle_packet ();
}
{
boost::mutex::scoped_lock lm (_mutex);
- if (!_ffmpeg_content->subtitle_stream() || _ffmpeg_content->subtitle_stream()->index (_format_context) >= int (_format_context->nb_streams)) {
+ if (!_ffmpeg_content->subtitle_stream()) {
return;
}
_subtitle_codec_context = _ffmpeg_content->subtitle_stream()->stream(_format_context)->codec;
+ if (_subtitle_codec_context == 0) {
+ throw DecodeError (N_("could not find subtitle stream"));
+ }
+
_subtitle_codec = avcodec_find_decoder (_subtitle_codec_context->codec_id);
if (_subtitle_codec == 0) {
- throw DecodeError (_("could not find subtitle decoder"));
+ throw DecodeError (N_("could not find subtitle decoder"));
}
if (avcodec_open2 (_subtitle_codec_context, _subtitle_codec, 0) < 0) {
if (_packet.stream_index == _video_stream && !_first_video) {
if (avcodec_decode_video2 (context, _frame, &frame_finished, &_packet) >= 0 && frame_finished) {
- _first_video = frame_time (_video_stream);
+ _first_video = frame_time (_format_context->streams[_video_stream]);
}
} else {
for (size_t i = 0; i < _audio_streams.size(); ++i) {
- if (_packet.stream_index == _audio_streams[i]->index (_format_context) && !_audio_streams[i]->first_audio) {
+ if (_audio_streams[i]->uses_index (_format_context, _packet.stream_index) && !_audio_streams[i]->first_audio) {
if (avcodec_decode_audio4 (context, _frame, &frame_finished, &_packet) >= 0 && frame_finished) {
- _audio_streams[i]->first_audio = frame_time (_audio_streams[i]->index (_format_context));
+ _audio_streams[i]->first_audio = frame_time (_audio_streams[i]->stream (_format_context));
}
}
}
}
optional<double>
-FFmpegExaminer::frame_time (int stream) const
+FFmpegExaminer::frame_time (AVStream* s) const
{
optional<double> t;
int64_t const bet = av_frame_get_best_effort_timestamp (_frame);
if (bet != AV_NOPTS_VALUE) {
- t = bet * av_q2d (_format_context->streams[stream]->time_base);
+ t = bet * av_q2d (s->time_base);
}
return t;
std::string stream_name (AVStream* s) const;
std::string audio_stream_name (AVStream* s) const;
std::string subtitle_stream_name (AVStream* s) const;
- boost::optional<double> frame_time (int) const;
+ boost::optional<double> frame_time (AVStream* s) const;
std::vector<boost::shared_ptr<FFmpegSubtitleStream> > _subtitle_streams;
std::vector<boost::shared_ptr<FFmpegAudioStream> > _audio_streams;
* 6 -> 7
* Subtitle offset changed to subtitle y offset, and subtitle x offset added.
*/
-int const Film::state_version = 7;
+int const Film::current_state_version = 7;
/** Construct a Film object in a given directory.
*
* @param dir Film directory.
*/
-Film::Film (boost::filesystem::path dir)
+Film::Film (boost::filesystem::path dir, bool log)
: _playlist (new Playlist)
, _use_dci_name (true)
, _dcp_content_type (Config::instance()->default_dcp_content_type ())
, _j2k_bandwidth (Config::instance()->default_j2k_bandwidth ())
, _dci_metadata (Config::instance()->default_dci_metadata ())
, _video_frame_rate (24)
- , _audio_channels (MAX_AUDIO_CHANNELS)
+ , _audio_channels (6)
, _three_d (false)
, _sequence_video (true)
, _interop (false)
+ , _state_version (current_state_version)
, _dirty (false)
{
set_dci_date_today ();
}
set_directory (result);
- _log.reset (new FileLog (file ("log")));
+ if (log) {
+ _log.reset (new FileLog (file ("log")));
+ } else {
+ _log.reset (new NullLog);
+ }
_playlist->set_sequence_video (_sequence_video);
}
return N;
}
-/** Write state to our `metadata' file */
-void
-Film::write_metadata () const
+shared_ptr<xmlpp::Document>
+Film::metadata () const
{
- if (!boost::filesystem::exists (directory ())) {
- boost::filesystem::create_directory (directory ());
- }
-
LocaleGuard lg;
- boost::filesystem::create_directories (directory ());
-
- xmlpp::Document doc;
- xmlpp::Element* root = doc.create_root_node ("Metadata");
+ shared_ptr<xmlpp::Document> doc (new xmlpp::Document);
+ xmlpp::Element* root = doc->create_root_node ("Metadata");
- root->add_child("Version")->add_child_text (lexical_cast<string> (state_version));
+ root->add_child("Version")->add_child_text (lexical_cast<string> (current_state_version));
root->add_child("Name")->add_child_text (_name);
root->add_child("UseDCIName")->add_child_text (_use_dci_name ? "1" : "0");
root->add_child("Key")->add_child_text (_key.hex ());
_playlist->as_xml (root->add_child ("Playlist"));
- doc.write_to_file_formatted (file("metadata.xml").string ());
-
+ return doc;
+}
+
+/** Write state to our `metadata' file */
+void
+Film::write_metadata () const
+{
+ boost::filesystem::create_directories (directory ());
+ shared_ptr<xmlpp::Document> doc = metadata ();
+ doc->write_to_file_formatted (file("metadata.xml").string ());
_dirty = false;
}
cxml::Document f ("Metadata");
f.read_file (file ("metadata.xml"));
- int const version = f.number_child<int> ("Version");
+ _state_version = f.number_child<int> ("Version");
_name = f.string_child ("Name");
_use_dci_name = f.bool_child ("UseDCIName");
_three_d = f.bool_child ("ThreeD");
_interop = f.bool_child ("Interop");
_key = dcp::Key (f.string_child ("Key"));
- _playlist->set_from_xml (shared_from_this(), f.node_child ("Playlist"), version);
+ _playlist->set_from_xml (shared_from_this(), f.node_child ("Playlist"), _state_version);
_dirty = false;
}
class Film : public boost::enable_shared_from_this<Film>, public boost::noncopyable
{
public:
- Film (boost::filesystem::path);
+ Film (boost::filesystem::path, bool log = true);
boost::filesystem::path info_dir () const;
boost::filesystem::path j2c_path (int, Eyes, bool) const;
void read_metadata ();
void write_metadata () const;
+ boost::shared_ptr<xmlpp::Document> metadata () const;
std::string dci_name (bool if_created_now) const;
std::string dcp_name (bool if_created_now = false) const;
return _key;
}
+ int state_version () const {
+ return _state_version;
+ }
+
/** Identifiers for the parts of our state;
used for signalling changes.
*/
mutable boost::signals2::signal<void (boost::weak_ptr<Content>, int)> ContentChanged;
/** Current version number of the state file */
- static int const state_version;
+ static int const current_state_version;
private:
bool _interop;
dcp::Key _key;
+ int _state_version;
+
/** true if our state has changed since we last saved it */
mutable bool _dirty;
}
}
-/** @return fractional progress of this sub-job, or -1 if not known */
+/** @return fractional progress of the current sub-job, or -1 if not known */
float
Job::progress () const
{
return s.str ();
}
+string
+Job::json_status () const
+{
+ boost::mutex::scoped_lock lm (_state_mutex);
+
+ switch (_state) {
+ case NEW:
+ return N_("new");
+ case RUNNING:
+ return N_("running");
+ case PAUSED:
+ return N_("paused");
+ case FINISHED_OK:
+ return N_("finished_ok");
+ case FINISHED_ERROR:
+ return N_("finished_error");
+ case FINISHED_CANCELLED:
+ return N_("finished_cancelled");
+ }
+
+ return "";
+}
+
/** @return An estimate of the remaining time for this sub-job, in seconds */
int
Job::remaining_time () const
/** @return user-readable name of this job */
virtual std::string name () const = 0;
+ virtual std::string json_name () const = 0;
/** Run this job in the current thread. */
virtual void run () = 0;
int elapsed_time () const;
virtual std::string status () const;
+ std::string json_status () const;
std::string sub_name () const {
return _sub_name;
}
return !_progress;
}
+ boost::shared_ptr<const Film> film () const {
+ return _film;
+ }
+
boost::signals2::signal<void()> Progress;
/** Emitted from the UI thread when the job is finished */
boost::signals2::signal<void()> Finished;
--- /dev/null
+/*
+ Copyright (C) 2014 Carl Hetherington <cth@carlh.net>
+
+ 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.
+
+*/
+
+#include <boost/asio.hpp>
+#include <boost/bind.hpp>
+#include <boost/thread.hpp>
+#include "json_server.h"
+#include "job_manager.h"
+#include "job.h"
+#include "util.h"
+#include "film.h"
+#include "transcode_job.h"
+
+using std::string;
+using std::stringstream;
+using std::cout;
+using std::map;
+using std::list;
+using boost::thread;
+using boost::shared_ptr;
+using boost::dynamic_pointer_cast;
+using boost::asio::ip::tcp;
+
+#define MAX_LENGTH 512
+
+enum State {
+ AWAITING_G,
+ AWAITING_E,
+ AWAITING_T,
+ AWAITING_SPACE,
+ READING_URL,
+};
+
+JSONServer::JSONServer (int port)
+{
+ new thread (boost::bind (&JSONServer::run, this, port));
+}
+
+void
+JSONServer::run (int port)
+try
+{
+ boost::asio::io_service io_service;
+ tcp::acceptor a (io_service, tcp::endpoint (tcp::v4 (), port));
+ while (1) {
+ try {
+ shared_ptr<tcp::socket> s (new tcp::socket (io_service));
+ a.accept (*s);
+ handle (s);
+ }
+ catch (...) {
+
+ }
+ }
+}
+catch (...)
+{
+
+}
+
+void
+JSONServer::handle (shared_ptr<tcp::socket> socket)
+{
+ string url;
+ State state = AWAITING_G;
+
+ while (1) {
+ char data[MAX_LENGTH];
+ boost::system::error_code error;
+ size_t len = socket->read_some (boost::asio::buffer (data), error);
+ if (error) {
+ cout << "error.\n";
+ break;
+ }
+
+ char* p = data;
+ char* e = data + len;
+ while (p != e) {
+
+ State old_state = state;
+ switch (state) {
+ case AWAITING_G:
+ if (*p == 'G') {
+ state = AWAITING_E;
+ }
+ break;
+ case AWAITING_E:
+ if (*p == 'E') {
+ state = AWAITING_T;
+ }
+ break;
+ case AWAITING_T:
+ if (*p == 'T') {
+ state = AWAITING_SPACE;
+ }
+ break;
+ case AWAITING_SPACE:
+ if (*p == ' ') {
+ state = READING_URL;
+ }
+ break;
+ case READING_URL:
+ if (*p == ' ') {
+ request (url, socket);
+ state = AWAITING_G;
+ url = "";
+ } else {
+ url += *p;
+ }
+ break;
+ }
+
+ if (state == old_state && state != READING_URL) {
+ state = AWAITING_G;
+ }
+
+ ++p;
+ }
+ }
+}
+
+void
+JSONServer::request (string url, shared_ptr<tcp::socket> socket)
+{
+ cout << "request: " << url << "\n";
+
+ map<string, string> r = split_get_request (url);
+ for (map<string, string>::iterator i = r.begin(); i != r.end(); ++i) {
+ cout << i->first << " => " << i->second << "\n";
+ }
+
+ string action;
+ if (r.find ("action") != r.end ()) {
+ action = r["action"];
+ }
+
+ stringstream json;
+ if (action == "status") {
+
+ list<shared_ptr<Job> > jobs = JobManager::instance()->get ();
+
+ json << "{ \"jobs\": [";
+ for (list<shared_ptr<Job> >::iterator i = jobs.begin(); i != jobs.end(); ++i) {
+
+ json << "{ ";
+
+ if ((*i)->film()) {
+ json << "\"dcp\": \"" << (*i)->film()->dcp_name() << "\", ";
+ }
+
+ json << "\"name\": \"" << (*i)->json_name() << "\", "
+ << "\"progress\": " << (*i)->progress () << ", "
+ << "\"status\": \"" << (*i)->json_status() << "\"";
+ json << " }";
+
+ list<shared_ptr<Job> >::iterator j = i;
+ ++j;
+ if (j != jobs.end ()) {
+ json << ", ";
+ }
+ }
+ json << "] }";
+
+ if (json.str().empty ()) {
+ json << "{ }";
+ }
+ }
+
+ stringstream reply;
+ reply << "HTTP/1.1 200 OK\r\n"
+ << "Content-Length: " << json.str().length() << "\r\n"
+ << "Content-Type: application/json\r\n"
+ << "\r\n"
+ << json.str () << "\r\n";
+ cout << "reply: " << json.str() << "\n";
+ boost::asio::write (*socket, boost::asio::buffer (reply.str().c_str(), reply.str().length()));
+}
--- /dev/null
+/*
+ Copyright (C) 2014 Carl Hetherington <cth@carlh.net>
+
+ 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.
+
+*/
+
+class JSONServer
+{
+public:
+ JSONServer (int port);
+
+private:
+ void run (int port);
+ void handle (boost::shared_ptr<boost::asio::ip::tcp::socket> socket);
+ void request (std::string url, boost::shared_ptr<boost::asio::ip::tcp::socket> socket);
+};
+
+
return _("Copy DCP to TMS");
}
+string
+SCPDCPJob::json_name () const
+{
+ return N_("scp_dcp");
+}
+
void
SCPDCPJob::run ()
{
boost::mutex::scoped_lock lm (_status_mutex);
_status = s;
}
-
SCPDCPJob (boost::shared_ptr<const Film>);
std::string name () const;
+ std::string json_name () const;
void run ();
std::string status () const;
return String::compose (_("Email KDMs for %1"), _film->name());
}
+string
+SendKDMEmailJob::json_name () const
+{
+ return N_("send_kdm_email");
+}
+
void
SendKDMEmailJob::run ()
{
);
std::string name () const;
+ std::string json_name () const;
void run ();
private:
return String::compose (_("Transcode %1"), _film->name());
}
+string
+TranscodeJob::json_name () const
+{
+ return N_("transcode");
+}
+
void
TranscodeJob::run ()
{
TranscodeJob (boost::shared_ptr<const Film> f);
std::string name () const;
+ std::string json_name () const;
void run ();
std::string status () const;
using std::max;
using std::list;
using std::multimap;
+using std::map;
using std::istream;
using std::numeric_limits;
using std::pair;
string
audio_channel_name (int c)
{
- assert (MAX_AUDIO_CHANNELS == 6);
+ assert (MAX_AUDIO_CHANNELS == 8);
/* TRANSLATORS: these are the names of audio channels; Lfe (sub) is the low-frequency
- enhancement channel (sub-woofer).
+ enhancement channel (sub-woofer). HI is the hearing-impaired audio track and
+ VI is the visually-impaired audio track (audio describe).
*/
string const channels[] = {
_("Left"),
_("Lfe (sub)"),
_("Left surround"),
_("Right surround"),
+ _("HI"),
+ _("VI")
};
return channels[c];
return shared_ptr<const dcp::Signer> (new dcp::Signer (chain, signer_key));
}
-dcp::Size
-fit_ratio_within (float ratio, dcp::Size full_frame)
+map<string, string>
+split_get_request (string url)
+{
+ enum {
+ AWAITING_QUESTION_MARK,
+ KEY,
+ VALUE
+ } state = AWAITING_QUESTION_MARK;
+
+ map<string, string> r;
+ string k;
+ string v;
+ for (size_t i = 0; i < url.length(); ++i) {
+ switch (state) {
+ case AWAITING_QUESTION_MARK:
+ if (url[i] == '?') {
+ state = KEY;
+ }
+ break;
+ case KEY:
+ if (url[i] == '=') {
+ v.clear ();
+ state = VALUE;
+ } else {
+ k += url[i];
+ }
+ break;
+ case VALUE:
+ if (url[i] == '&') {
+ r.insert (make_pair (k, v));
+ k.clear ();
+ state = KEY;
+ } else {
+ v += url[i];
+ }
+ break;
+ }
+ }
+
+ if (state == VALUE) {
+ r.insert (make_pair (k, v));
+ }
+
+ return r;
+}
+
+libdcp::Size
+fit_ratio_within (float ratio, libdcp::Size full_frame)
{
if (ratio < full_frame.ratio ()) {
return dcp::Size (rint (full_frame.height * ratio), full_frame.height);
}
return p;
}
+
+string
+entities_to_text (string e)
+{
+ boost::algorithm::replace_all (e, "%3A", ":");
+ boost::algorithm::replace_all (e, "%2F", "/");
+ return e;
+}
#undef check
/** The maximum number of audio channels that we can cope with */
-#define MAX_AUDIO_CHANNELS 6
+#define MAX_AUDIO_CHANNELS 8
#define DCPOMATIC_HELLO "Boys, you gotta learn not to talk to nuns that way"
extern std::string tidy_for_filename (std::string);
extern boost::shared_ptr<const dcp::Signer> make_signer ();
extern dcp::Size fit_ratio_within (float ratio, dcp::Size);
+extern std::string entities_to_text (std::string e);
+extern std::map<std::string, std::string> split_get_request (std::string url);
struct FrameRateChange
{
job.cc
job_manager.cc
kdm.cc
+ json_server.cc
log.cc
player.cc
playlist.cc
#include "lib/send_kdm_email_job.h"
#include "lib/server_finder.h"
#include "lib/update.h"
+#include "lib/content_factory.h"
using std::cout;
using std::string;
static std::string log_level;
static std::string film_to_load;
static std::string film_to_create;
+static std::string content_to_add;
static wxMenu* jobs_menu = 0;
static void set_menu_sensitivity ();
overall_panel->SetSizer (main_sizer);
}
+ void check_film_state_version (int v)
+ {
+ if (v == 4) {
+ error_dialog (
+ this,
+ _("This film was created with an old version of DVD-o-matic and may not load correctly "
+ "in this version. Please check the film's settings carefully.")
+ );
+ }
+ }
+
private:
void set_film ()
try {
film.reset (new Film (wx_to_std (c->GetPath ())));
film->read_metadata ();
+ check_film_state_version (film->state_version ());
film->log()->set_level (log_level);
set_film ();
} catch (std::exception& e) {
static const wxCmdLineEntryDesc command_line_description[] = {
{ wxCMD_LINE_OPTION, "l", "log", "set log level (silent, verbose or timing)", wxCMD_LINE_VAL_STRING, wxCMD_LINE_PARAM_OPTIONAL },
{ wxCMD_LINE_SWITCH, "n", "new", "create new film", wxCMD_LINE_VAL_NONE, wxCMD_LINE_PARAM_OPTIONAL },
- { wxCMD_LINE_PARAM, 0, 0, "film to load or create", wxCMD_LINE_VAL_STRING, wxCMD_LINE_PARAM_MULTIPLE | wxCMD_LINE_PARAM_OPTIONAL },
+ { wxCMD_LINE_OPTION, "c", "content", "add content file", wxCMD_LINE_VAL_STRING, wxCMD_LINE_PARAM_OPTIONAL },
+ { wxCMD_LINE_PARAM, 0, 0, "film to load or create", wxCMD_LINE_VAL_STRING, wxCMD_LINE_PARAM_OPTIONAL },
{ wxCMD_LINE_NONE, "", "", "", wxCmdLineParamType (0), 0 }
};
film->set_name (boost::filesystem::path (film_to_create).filename().generic_string ());
}
+ if (!content_to_add.empty ()) {
+ film->examine_and_add_content (content_factory (film, content_to_add));
+ }
+
_frame = new Frame (_("DCP-o-matic"));
SetTopWindow (_frame);
_frame->Maximize ();
_timer.reset (new wxTimer (this));
_timer->Start (1000);
+ if (film) {
+ _frame->check_film_state_version (film->state_version ());
+ }
+
UpdateChecker::instance()->StateChanged.connect (boost::bind (&App::update_checker_state_changed, this));
if (Config::instance()->check_for_updates ()) {
UpdateChecker::instance()->run ();
if (parser.Found (wxT ("new"))) {
film_to_create = wx_to_std (parser.GetParam (0));
} else {
- film_to_load = wx_to_std (parser.GetParam(0));
+ film_to_load = wx_to_std (parser.GetParam (0));
}
}
+ wxString content;
+ if (parser.Found (wxT ("content"), &content)) {
+ content_to_add = wx_to_std (content);
+ }
+
wxString log;
if (parser.Found (wxT ("log"), &log)) {
log_level = wx_to_std (log);
}
}
- wxFrame* _frame;
+ Frame* _frame;
shared_ptr<wxTimer> _timer;
};
#include "lib/log.h"
#include "lib/ui_signaller.h"
#include "lib/server_finder.h"
+#include "lib/json_server.h"
using std::string;
using std::cerr;
<< " -f, --flags show flags passed to C++ compiler on build\n"
<< " -n, --no-progress do not print progress to stdout\n"
<< " -r, --no-remote do not use any remote servers\n"
+ << " -j, --json <port> run a JSON server on the specified port\n"
+ << " -k, --keep-going keep running even when the job is complete\n"
<< "\n"
<< "<FILM> is the film directory.\n";
}
bool progress = true;
bool no_remote = false;
int log_level = 0;
+ int json_port = 0;
+ bool keep_going = false;
int option_index = 0;
while (1) {
{ "no-progress", no_argument, 0, 'n'},
{ "no-remote", no_argument, 0, 'r'},
{ "log-level", required_argument, 0, 'l' },
+ { "json", required_argument, 0, 'j' },
+ { "keep-going", no_argument, 0, 'k' },
{ 0, 0, 0, 0 }
};
- int c = getopt_long (argc, argv, "vhdfnrl:", long_options, &option_index);
+ int c = getopt_long (argc, argv, "vhdfnrl:j:k", long_options, &option_index);
if (c == -1) {
break;
case 'l':
log_level = atoi (optarg);
break;
+ case 'j':
+ json_port = atoi (optarg);
+ break;
+ case 'k':
+ keep_going = true;
+ break;
}
}
ServerFinder::instance()->disable ();
}
+ if (json_port) {
+ new JSONServer (json_port);
+ }
+
cout << "DCP-o-matic " << dcpomatic_version << " git " << dcpomatic_git_commit;
char buf[256];
if (gethostname (buf, 256) == 0) {
}
}
+ if (keep_going) {
+ while (1) {
+ dcpomatic_sleep (3600);
+ }
+ }
+
/* This is just to stop valgrind reporting leaks due to JobManager
indirectly holding onto codecs.
*/
#include "lib/job_manager.h"
#include "lib/ui_signaller.h"
#include "lib/job.h"
+#include "lib/dcp_content_type.h"
+#include "lib/ratio.h"
using std::string;
using std::cout;
using std::list;
using std::exception;
using boost::shared_ptr;
+using boost::dynamic_pointer_cast;
static void
help (string n)
{
- cerr << "Create a film directory (ready for making a DCP) from some content files.\n"
+ cerr << "Create a film directory (ready for making a DCP) or metadata file from some content files.\n"
+ << "A film directory will be created if -o or --output is specified, otherwise a metadata file\n"
+ << "will be written to stdout.\n"
<< "Syntax: " << n << " [OPTION] <CONTENT> [<CONTENT> ...]\n"
- << " -v, --version show DCP-o-matic version\n"
- << " -h, --help show this help\n"
- << " -n, --name film name\n"
- << " -o, --output output directory (required)\n";
+ << " -v, --version show DCP-o-matic version\n"
+ << " -h, --help show this help\n"
+ << " -n, --name <name> film name\n"
+ << " -c, --dcp-content-type <type> FTR, SHR, TLR, TST, XSN, RTG, TSR, POL, PSA or ADV\n"
+ << " --container-ratio 119, 133, 137, 138, 166, 178, 185 or 239\n"
+ << " --content-ratio 119, 133, 137, 138, 166, 178, 185 or 239\n"
+ << " -o, --output <dir> output directory\n";
}
int
main (int argc, char* argv[])
{
+ dcpomatic_setup ();
+
string name;
+ DCPContentType const * dcp_content_type = DCPContentType::from_dci_name ("TST");
+ Ratio const * container_ratio = 0;
+ Ratio const * content_ratio = 0;
boost::filesystem::path output;
int option_index = 0;
{ "version", no_argument, 0, 'v'},
{ "help", no_argument, 0, 'h'},
{ "name", required_argument, 0, 'n'},
+ { "dcp-content-type", required_argument, 0, 'c'},
+ { "container-ratio", required_argument, 0, 'A'},
+ { "content-ratio", required_argument, 0, 'B'},
{ "output", required_argument, 0, 'o'},
{ 0, 0, 0, 0}
};
- int c = getopt_long (argc, argv, "vhn:o:", long_options, &option_index);
+ int c = getopt_long (argc, argv, "vhn:c:A:B:o:", long_options, &option_index);
if (c == -1) {
break;
}
case 'n':
name = optarg;
break;
+ case 'c':
+ dcp_content_type = DCPContentType::from_dci_name (optarg);
+ if (dcp_content_type == 0) {
+ cerr << "Bad DCP content type.\n";
+ help (argv[0]);
+ exit (EXIT_FAILURE);
+ }
+ break;
+ case 'A':
+ container_ratio = Ratio::from_id (optarg);
+ if (container_ratio == 0) {
+ cerr << "Bad container ratio.\n";
+ help (argv[0]);
+ exit (EXIT_FAILURE);
+ }
+ break;
+ case 'B':
+ content_ratio = Ratio::from_id (optarg);
+ if (content_ratio == 0) {
+ cerr << "Bad content ratio " << optarg << ".\n";
+ help (argv[0]);
+ exit (EXIT_FAILURE);
+ }
+ break;
case 'o':
output = optarg;
break;
exit (EXIT_FAILURE);
}
- if (output.empty ()) {
- cerr << "Missing required option -o or --output.\n"
- << "Use " << argv[0] << " --help for help.\n";
+ if (!content_ratio) {
+ cerr << "Missing required option --content-ratio.\n";
+ help (argv[0]);
exit (EXIT_FAILURE);
}
- dcpomatic_setup ();
+ if (!container_ratio) {
+ container_ratio = content_ratio;
+ }
+
ui_signaller = new UISignaller ();
try {
- shared_ptr<Film> film (new Film (output));
+ shared_ptr<Film> film (new Film (output, false));
if (!name.empty ()) {
film->set_name (name);
}
+
+ film->set_container (container_ratio);
for (int i = optind; i < argc; ++i) {
- film->examine_and_add_content (content_factory (film, argv[i]));
+ shared_ptr<Content> c = content_factory (film, argv[i]);
+ shared_ptr<VideoContent> vc = dynamic_pointer_cast<VideoContent> (c);
+ if (vc) {
+ vc->set_ratio (content_ratio);
+ }
+ film->examine_and_add_content (c);
}
JobManager* jm = JobManager::instance ();
}
exit (EXIT_FAILURE);
}
-
- film->write_metadata ();
+
+ if (!output.empty ()) {
+ film->write_metadata ();
+ } else {
+ film->metadata()->write_to_stream_formatted (cout);
+ }
} catch (exception& e) {
cerr << argv[0] << ": " << e.what() << "\n";
exit (EXIT_FAILURE);
supported_by.Add (wxT ("Mattias Mattsson"));
supported_by.Add (wxT ("Andrä Steiner"));
supported_by.Add (wxT ("Jonathan Jensen"));
+ supported_by.Add (wxT ("Mike Stiebing"));
supported_by.Add (wxT ("Kjarten Michaelsen"));
supported_by.Add (wxT ("Jussi Siponen"));
supported_by.Add (wxT ("Cinema Clarici"));
supported_by.Add (wxT ("Sylvain Mielle"));
supported_by.Add (wxT ("Ivan Pullman"));
supported_by.Add (wxT ("Aldo Midali"));
+ supported_by.Add (wxT ("Jeff Boot"));
+ supported_by.Add (wxT ("Filip Kovcin"));
+ supported_by.Add (wxT ("Adam Colt"));
add_section (_("Supported by"), supported_by);
+ wxArrayString testers;
+ testers.Add (wxT ("Greg Rooke"));
+ testers.Add (wxT ("Olivier Lemaire"));
+ testers.Add (wxT ("Trever Anderson"));
+ testers.Add (wxT ("Wolfgang Woehl"));
+ testers.Add (wxT ("Jonathan Jensen"));
+ testers.Add (wxT ("Anders Nordentoft-Madsen"));
+ testers.Add (wxT ("Lilian Lefranc"));
+ testers.Add (wxT ("Gérald Maruccia"));
+ testers.Add (wxT ("John Convertino"));
+ testers.Add (wxT ("Mike Blakesley"));
+ testers.Add (wxT ("Simon Kesselman"));
+ testers.Add (wxT ("Gavin Lewarne"));
+ testers.Add (wxT ("Thierry Journet"));
+ testers.Add (wxT ("Carsten Kurz"));
+ testers.Add (wxT ("Karim Senoucci"));
+ testers.Add (wxT ("Paul Willmott"));
+ testers.Add (wxT ("Mattias Mattsson"));
+ testers.Add (wxT ("Andreas Eli"));
+ testers.Add (wxT ("Roop Chand"));
+ testers.Add (wxT ("Peter Puchner"));
+ testers.Add (wxT ("David Booty"));
+ testers.Add (wxT ("Maurizio Giampà"));
+ testers.Add (wxT ("Bill Lam"));
+ testers.Add (wxT ("Pepijn Klijs"));
+ testers.Add (wxT ("Will Meadows"));
+ testers.Add (wxT ("Adam Colt"));
+ testers.Add (wxT ("Markus Raab"));
+ add_section (_("Tested by"), testers);
+
sizer->Add (_notebook, wxSizerFlags().Centre().Border(wxALL, 16).Expand());
overall_sizer->Add (sizer);
int const c = _grid->GetNumberCols ();
_grid->SetColLabelValue (0, _("Content channel"));
+
+#if MAX_AUDIO_CHANNELS != 8
+#warning AudioMappingView::set_column_labels() is expecting the wrong MAX_AUDIO_CHANNELS
+#endif
if (c > 0) {
_grid->SetColLabelValue (1, _("L"));
_grid->SetColLabelValue (6, _("Rs"));
}
+ if (c > 6) {
+ _grid->SetColLabelValue (7, _("HI"));
+ }
+
+ if (c > 7) {
+ _grid->SetColLabelValue (8, _("VI"));
+ }
+
_grid->AutoSize ();
}
if (fcs) {
vector<shared_ptr<FFmpegAudioStream> > a = fcs->audio_streams ();
for (vector<shared_ptr<FFmpegAudioStream> >::iterator i = a.begin(); i != a.end(); ++i) {
- _stream->Append (std_to_wx ((*i)->name), new wxStringClientData (std_to_wx (lexical_cast<string> ((*i)->id))));
+ _stream->Append (std_to_wx ((*i)->name), new wxStringClientData (std_to_wx ((*i)->identifier ())));
}
if (fcs->audio_stream()) {
- checked_set (_stream, lexical_cast<string> (fcs->audio_stream()->id));
+ checked_set (_stream, fcs->audio_stream()->identifier ());
setup_stream_description ();
}
}
vector<shared_ptr<FFmpegAudioStream> > a = fcs->audio_streams ();
vector<shared_ptr<FFmpegAudioStream> >::iterator i = a.begin ();
string const s = string_client_data (_stream->GetClientObject (_stream->GetSelection ()));
- while (i != a.end() && lexical_cast<string> ((*i)->id) != s) {
+ while (i != a.end() && (*i)->identifier () != s) {
++i;
}
if (fcs) {
vector<shared_ptr<FFmpegSubtitleStream> > s = fcs->subtitle_streams ();
for (vector<shared_ptr<FFmpegSubtitleStream> >::iterator i = s.begin(); i != s.end(); ++i) {
- _stream->Append (std_to_wx ((*i)->name), new wxStringClientData (std_to_wx (lexical_cast<string> ((*i)->id))));
+ _stream->Append (std_to_wx ((*i)->name), new wxStringClientData (std_to_wx ((*i)->identifier ())));
}
if (fcs->subtitle_stream()) {
- checked_set (_stream, lexical_cast<string> (fcs->subtitle_stream()->id));
+ checked_set (_stream, fcs->subtitle_stream()->identifier ());
} else {
_stream->SetSelection (wxNOT_FOUND);
}
vector<shared_ptr<FFmpegSubtitleStream> > a = fcs->subtitle_streams ();
vector<shared_ptr<FFmpegSubtitleStream> >::iterator i = a.begin ();
string const s = string_client_data (_stream->GetClientObject (_stream->GetSelection ()));
- while (i != a.end() && lexical_cast<string> ((*i)->id) != s) {
+ while (i != a.end() && (*i)->identifier () != s) {
++i;
}
string name () const {
return "";
}
+
+ string json_name () const {
+ return "";
+ }
};
BOOST_AUTO_TEST_CASE (job_manager_test)
FFmpegAudioStream a (shared_ptr<cxml::Node> (new cxml::Node (root)), 5);
- BOOST_CHECK_EQUAL (a.id, 4);
+ BOOST_CHECK_EQUAL (a.identifier(), "4");
BOOST_CHECK_EQUAL (a.frame_rate, 44100);
BOOST_CHECK_EQUAL (a.channels, 2);
BOOST_CHECK_EQUAL (a.name, "hello there world");
conf.check_cfg(package='libopenjpeg', args='--cflags --libs', max_version='1.5.1', mandatory=True)
def static_dcp(conf, static_boost, static_xmlpp, static_xmlsec, static_ssh):
- conf.check_cfg(package='libdcp', atleast_version='0.92', args='--cflags', uselib_store='DCP', mandatory=True)
+ conf.check_cfg(package='libdcp-1.0', atleast_version='0.92', args='--cflags', uselib_store='DCP', mandatory=True)
conf.env.DEFINES_DCP = [f.replace('\\', '') for f in conf.env.DEFINES_DCP]
- conf.env.STLIB_DCP = ['dcp', 'asdcp-libdcp', 'kumu-libdcp']
+ conf.env.STLIB_DCP = ['dcp-1.0', 'asdcp-libdcp', 'kumu-libdcp']
conf.env.LIB_DCP = ['glibmm-2.4', 'ssl', 'crypto', 'bz2', 'xslt']
if static_boost:
conf.env.LIB_DCP.append('ssh')
def dynamic_dcp(conf):
- conf.check_cfg(package='libdcp', atleast_version='0.92', args='--cflags --libs', uselib_store='DCP', mandatory=True)
+ conf.check_cfg(package='libdcp-1.0', atleast_version='0.92', args='--cflags --libs', uselib_store='DCP', mandatory=True)
conf.env.DEFINES_DCP = [f.replace('\\', '') for f in conf.env.DEFINES_DCP]
def dynamic_ssh(conf):