+2014-08-06 Carl Hetherington <cth@carlh.net>
+
+ * Version 2.0.1 released.
+
+2014-07-15 Carl Hetherington <cth@carlh.net>
+
+ * A variety of changes were made on the 2.0 branch
+ but not documented in the ChangeLog. Most sigificantly:
+
+ - DCP import
+ - Creation of DCPs with proper XML subtitles
+ - Import of .srt and .xml subtitles
+ - Audio processing framework (with some basic processors).
+
+2014-03-07 Carl Hetherington <cth@carlh.net>
+
+ * Add subtitle view.
+
+ 2014-08-23 Carl Hetherington <cth@carlh.net>
+
+ * Version 1.72.12 released.
+
+ 2014-08-23 Carl Hetherington <cth@carlh.net>
+
+ * Revert previous use of AVFormatContext::start_time when
+ computing the length of video. I think this is wrong, and
+ causes bits to be missed off the end of videos (and other
+ problems).
+
+ 2014-08-20 Carl Hetherington <cth@carlh.net>
+
+ * Version 1.72.11 released.
+
+ 2014-08-19 Carl Hetherington <cth@carlh.net>
+
+ * Attempt to fix random crashes on OS X (especially during encodes)
+ thought to be caused by multiple threads using (different) stringstreams
+ at the same time; see src/lib/safe_stringstream.
++>>>>>>> origin/master
+
2014-08-09 Carl Hetherington <cth@carlh.net>
* Version 1.72.10 released.
* New upstream release.
* New upstream release.
* New upstream release.
- <<<<<<< HEAD
- * New upstream release.
- * New upstream release.
- * New upstream release.
- * New upstream release.
- -- Carl Hetherington <carl@d1stkfactory> Sat, 23 Aug 2014 18:47:52 +0100
+ -- Carl Hetherington <carl@dalglish> Wed, 06 Aug 2014 18:59:35 +0100
- =======
- * New upstream release.
- * New upstream release.
-
- -- Carl Hetherington <carl@d1stkfactory> Sat, 09 Aug 2014 12:38:18 +0100
- >>>>>>> origin/master
dcpomatic (0.87-1) UNRELEASED; urgency=low
string
Content::identifier () const
{
- stringstream s;
+ SafeStringStream s;
s << Content::digest()
- << "_" << position()
- << "_" << trim_start()
- << "_" << trim_end();
+ << "_" << position().get()
+ << "_" << trim_start().get()
+ << "_" << trim_end().get();
return s.str ();
}
--- /dev/null
- #include <sstream>
+/*
+ Copyright (C) 2012-2014 Carl Hetherington <cth@carlh.net>
+ Taken from code Copyright (C) 2010-2011 Terrence Meiczinger
+
+ 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/dcp_video_frame.cc
+ * @brief A single frame of video destined for a DCP.
+ *
+ * Given an Image and some settings, this class knows how to encode
+ * the image to J2K either on the local host or on a remote server.
+ *
+ * Objects of this class are used for the queue that we keep
+ * of images that require encoding.
+ */
+
+#include <stdint.h>
+#include <cstring>
+#include <cstdlib>
+#include <stdexcept>
+#include <cstdio>
+#include <iomanip>
- using std::stringstream;
+#include <iostream>
+#include <fstream>
+#include <unistd.h>
+#include <errno.h>
+#include <boost/array.hpp>
+#include <boost/asio.hpp>
+#include <boost/filesystem.hpp>
+#include <boost/lexical_cast.hpp>
+#include <dcp/gamma_lut.h>
+#include <dcp/xyz_frame.h>
+#include <dcp/rgb_xyz.h>
+#include <dcp/colour_matrix.h>
+#include <dcp/raw_convert.h>
+#include <libcxml/cxml.h>
+#include "film.h"
+#include "dcp_video.h"
+#include "config.h"
+#include "exceptions.h"
+#include "server.h"
+#include "util.h"
+#include "scaler.h"
+#include "image.h"
+#include "log.h"
+#include "cross.h"
+#include "player_video.h"
+#include "encoded_data.h"
+
+#define LOG_GENERAL(...) _log->log (String::compose (__VA_ARGS__), Log::TYPE_GENERAL);
+
+#include "i18n.h"
+
+using std::string;
- stringstream xml;
- doc.write_to_stream (xml, "UTF-8");
- socket->write (xml.str().length() + 1);
- socket->write ((uint8_t *) xml.str().c_str(), xml.str().length() + 1);
+using std::cout;
+using boost::shared_ptr;
+using boost::lexical_cast;
+using dcp::Size;
+using dcp::raw_convert;
+
+#define DCI_COEFFICENT (48.0 / 52.37)
+
+/** Construct a DCP video frame.
+ * @param frame Input frame.
+ * @param index Index of the frame within the DCP.
+ * @param bw J2K bandwidth to use (see Config::j2k_bandwidth ())
+ * @param l Log to write to.
+ */
+DCPVideo::DCPVideo (
+ shared_ptr<const PlayerVideo> frame, int index, int dcp_fps, int bw, Resolution r, bool b, shared_ptr<Log> l
+ )
+ : _frame (frame)
+ , _index (index)
+ , _frames_per_second (dcp_fps)
+ , _j2k_bandwidth (bw)
+ , _resolution (r)
+ , _burn_subtitles (b)
+ , _log (l)
+{
+
+}
+
+DCPVideo::DCPVideo (shared_ptr<const PlayerVideo> frame, shared_ptr<const cxml::Node> node, shared_ptr<Log> log)
+ : _frame (frame)
+ , _log (log)
+{
+ _index = node->number_child<int> ("Index");
+ _frames_per_second = node->number_child<int> ("FramesPerSecond");
+ _j2k_bandwidth = node->number_child<int> ("J2KBandwidth");
+ _resolution = Resolution (node->optional_number_child<int>("Resolution").get_value_or (RESOLUTION_2K));
+ _burn_subtitles = node->bool_child ("BurnSubtitles");
+}
+
+/** J2K-encode this frame on the local host.
+ * @return Encoded data.
+ */
+shared_ptr<EncodedData>
+DCPVideo::encode_locally ()
+{
+ shared_ptr<dcp::GammaLUT> in_lut = dcp::GammaLUT::cache.get (
+ 12, _frame->colour_conversion().input_gamma, _frame->colour_conversion().input_gamma_linearised
+ );
+
+ /* XXX: libdcp should probably use boost */
+
+ double matrix[3][3];
+ for (int i = 0; i < 3; ++i) {
+ for (int j = 0; j < 3; ++j) {
+ matrix[i][j] = _frame->colour_conversion().matrix (i, j);
+ }
+ }
+
+ shared_ptr<dcp::XYZFrame> xyz = dcp::rgb_to_xyz (
+ _frame->image (_burn_subtitles),
+ in_lut,
+ dcp::GammaLUT::cache.get (16, 1 / _frame->colour_conversion().output_gamma, false),
+ matrix
+ );
+
+ /* Set the max image and component sizes based on frame_rate */
+ int max_cs_len = ((float) _j2k_bandwidth) / 8 / _frames_per_second;
+ if (_frame->eyes() == EYES_LEFT || _frame->eyes() == EYES_RIGHT) {
+ /* In 3D we have only half the normal bandwidth per eye */
+ max_cs_len /= 2;
+ }
+ int const max_comp_size = max_cs_len / 1.25;
+
+ /* get a J2K compressor handle */
+ opj_cinfo_t* cinfo = opj_create_compress (CODEC_J2K);
+ if (cinfo == 0) {
+ throw EncodeError (N_("could not create JPEG2000 encoder"));
+ }
+
+ /* Set encoding parameters to default values */
+ opj_cparameters_t parameters;
+ opj_set_default_encoder_parameters (¶meters);
+
+ /* Set default cinema parameters */
+ parameters.tile_size_on = false;
+ parameters.cp_tdx = 1;
+ parameters.cp_tdy = 1;
+
+ /* Tile part */
+ parameters.tp_flag = 'C';
+ parameters.tp_on = 1;
+
+ /* Tile and Image shall be at (0,0) */
+ parameters.cp_tx0 = 0;
+ parameters.cp_ty0 = 0;
+ parameters.image_offset_x0 = 0;
+ parameters.image_offset_y0 = 0;
+
+ /* Codeblock size = 32x32 */
+ parameters.cblockw_init = 32;
+ parameters.cblockh_init = 32;
+ parameters.csty |= 0x01;
+
+ /* The progression order shall be CPRL */
+ parameters.prog_order = CPRL;
+
+ /* No ROI */
+ parameters.roi_compno = -1;
+
+ parameters.subsampling_dx = 1;
+ parameters.subsampling_dy = 1;
+
+ /* 9-7 transform */
+ parameters.irreversible = 1;
+
+ parameters.tcp_rates[0] = 0;
+ parameters.tcp_numlayers++;
+ parameters.cp_disto_alloc = 1;
+ parameters.cp_rsiz = _resolution == RESOLUTION_2K ? CINEMA2K : CINEMA4K;
+ if (_resolution == RESOLUTION_4K) {
+ parameters.numpocs = 2;
+ parameters.POC[0].tile = 1;
+ parameters.POC[0].resno0 = 0;
+ parameters.POC[0].compno0 = 0;
+ parameters.POC[0].layno1 = 1;
+ parameters.POC[0].resno1 = parameters.numresolution - 1;
+ parameters.POC[0].compno1 = 3;
+ parameters.POC[0].prg1 = CPRL;
+ parameters.POC[1].tile = 1;
+ parameters.POC[1].resno0 = parameters.numresolution - 1;
+ parameters.POC[1].compno0 = 0;
+ parameters.POC[1].layno1 = 1;
+ parameters.POC[1].resno1 = parameters.numresolution;
+ parameters.POC[1].compno1 = 3;
+ parameters.POC[1].prg1 = CPRL;
+ }
+
+ parameters.cp_comment = strdup (N_("DCP-o-matic"));
+ parameters.cp_cinema = _resolution == RESOLUTION_2K ? CINEMA2K_24 : CINEMA4K_24;
+
+ /* 3 components, so use MCT */
+ parameters.tcp_mct = 1;
+
+ /* set max image */
+ parameters.max_comp_size = max_comp_size;
+ parameters.tcp_rates[0] = ((float) (3 * xyz->size().width * xyz->size().height * 12)) / (max_cs_len * 8);
+
+ /* Set event manager to null (openjpeg 1.3 bug) */
+ cinfo->event_mgr = 0;
+
+ /* Setup the encoder parameters using the current image and user parameters */
+ opj_setup_encoder (cinfo, ¶meters, xyz->opj_image ());
+
+ opj_cio_t* cio = opj_cio_open ((opj_common_ptr) cinfo, 0, 0);
+ if (cio == 0) {
+ opj_destroy_compress (cinfo);
+ throw EncodeError (N_("could not open JPEG2000 stream"));
+ }
+
+ int const r = opj_encode (cinfo, cio, xyz->opj_image(), 0);
+ if (r == 0) {
+ opj_cio_close (cio);
+ opj_destroy_compress (cinfo);
+ throw EncodeError (N_("JPEG2000 encoding failed"));
+ }
+
+ switch (_frame->eyes()) {
+ case EYES_BOTH:
+ LOG_GENERAL (N_("Finished locally-encoded frame %1 for mono"), _index);
+ break;
+ case EYES_LEFT:
+ LOG_GENERAL (N_("Finished locally-encoded frame %1 for L"), _index);
+ break;
+ case EYES_RIGHT:
+ LOG_GENERAL (N_("Finished locally-encoded frame %1 for R"), _index);
+ break;
+ default:
+ break;
+ }
+
+ shared_ptr<EncodedData> enc (new LocallyEncodedData (cio->buffer, cio_tell (cio)));
+
+ opj_cio_close (cio);
+ free (parameters.cp_comment);
+ opj_destroy_compress (cinfo);
+
+ return enc;
+}
+
+/** Send this frame to a remote server for J2K encoding, then read the result.
+ * @param serv Server to send to.
+ * @return Encoded data.
+ */
+shared_ptr<EncodedData>
+DCPVideo::encode_remotely (ServerDescription serv)
+{
+ boost::asio::io_service io_service;
+ boost::asio::ip::tcp::resolver resolver (io_service);
+ boost::asio::ip::tcp::resolver::query query (serv.host_name(), raw_convert<string> (Config::instance()->server_port_base ()));
+ boost::asio::ip::tcp::resolver::iterator endpoint_iterator = resolver.resolve (query);
+
+ shared_ptr<Socket> socket (new Socket);
+
+ socket->connect (*endpoint_iterator);
+
+ /* Collect all XML metadata */
+ xmlpp::Document doc;
+ xmlpp::Element* root = doc.create_root_node ("EncodingRequest");
+ root->add_child("Version")->add_child_text (raw_convert<string> (SERVER_LINK_VERSION));
+ add_metadata (root);
+
+ LOG_GENERAL (N_("Sending frame %1 to remote"), _index);
+
+ /* Send XML metadata */
++ string xml = doc.write_to_string ("UTF-8");
++ socket->write (xml.length() + 1);
++ socket->write ((uint8_t *) xml.c_str(), xml.length() + 1);
+
+ /* Send binary data */
+ _frame->send_binary (socket, _burn_subtitles);
+
+ /* Read the response (JPEG2000-encoded data); this blocks until the data
+ is ready and sent back.
+ */
+ shared_ptr<EncodedData> e (new RemotelyEncodedData (socket->read_uint32 ()));
+ socket->read (e->data(), e->size());
+
+ LOG_GENERAL (N_("Finished remotely-encoded frame %1"), _index);
+
+ return e;
+}
+
+void
+DCPVideo::add_metadata (xmlpp::Element* el) const
+{
+ el->add_child("Index")->add_child_text (raw_convert<string> (_index));
+ el->add_child("FramesPerSecond")->add_child_text (raw_convert<string> (_frames_per_second));
+ el->add_child("J2KBandwidth")->add_child_text (raw_convert<string> (_j2k_bandwidth));
+ el->add_child("Resolution")->add_child_text (raw_convert<string> (int (_resolution)));
+ el->add_child("BurnSubtitles")->add_child_text (_burn_subtitles ? "1" : "0");
+ _frame->add_metadata (el, _burn_subtitles);
+}
+
+Eyes
+DCPVideo::eyes () const
+{
+ return _frame->eyes ();
+}
+
return "";
}
- stringstream s;
+ SafeStringStream s;
- s << String::compose (_("%1 frames; %2 frames per second"), video_length_after_3d_combine(), video_frame_rate()) << "\n";
+ s << String::compose (_("%1 frames; %2 frames per second"), video_length_after_3d_combine().frames (video_frame_rate()), video_frame_rate()) << "\n";
s << VideoContent::information ();
return s.str ();
}
#include "ffmpeg_examiner.h"
#include "ffmpeg_content.h"
+#include "ffmpeg_audio_stream.h"
+#include "ffmpeg_subtitle_stream.h"
+#include "util.h"
+ #include "safe_stringstream.h"
#include "i18n.h"
float
FFmpegExaminer::video_frame_rate () const
{
- AVStream* s = _format_context->streams[_video_stream];
-
- if (s->avg_frame_rate.num && s->avg_frame_rate.den) {
- return av_q2d (s->avg_frame_rate);
- }
-
- return av_q2d (s->r_frame_rate);
+ /* This use of r_frame_rate is debateable; there's a few different
+ * frame rates in the format context, but this one seems to be the most
+ * reliable.
+ */
+ return av_q2d (av_stream_get_r_frame_rate (_format_context->streams[_video_stream]));
}
-libdcp::Size
+dcp::Size
FFmpegExaminer::video_size () const
{
- return libdcp::Size (video_codec_context()->width, video_codec_context()->height);
+ return dcp::Size (video_codec_context()->width, video_codec_context()->height);
}
-/** @return Length (in video frames) according to our content's header */
-VideoContent::Frame
+/** @return Length according to our content's header */
+ContentTime
FFmpegExaminer::video_length () const
{
- ContentTime const length = ContentTime::from_seconds (double (_format_context->duration - _format_context->start_time) / AV_TIME_BASE);
- VideoContent::Frame const length = (double (_format_context->duration) / AV_TIME_BASE) * video_frame_rate();
- return max (1, length);
++ ContentTime const length = ContentTime::from_seconds (double (_format_context->duration) / AV_TIME_BASE);
+ return ContentTime (max (ContentTime::Type (1), length.get ()));
}
string
#include "film.h"
#include "job.h"
#include "frame_rate_change.h"
+#include "exceptions.h"
+ #include "safe_stringstream.h"
#include "i18n.h"
string
ImageContent::identifier () const
{
- stringstream s;
+ SafeStringStream s;
s << VideoContent::identifier ();
- s << "_" << video_length();
+ s << "_" << video_length().get();
return s.str ();
}
#include "util.h"
#include "scaler.h"
#include "image.h"
-#include "dcp_video_frame.h"
+#include "dcp_video.h"
#include "config.h"
#include "cross.h"
-#include "player_video_frame.h"
+#include "player_video.h"
+#include "encoded_data.h"
+ #include "safe_stringstream.h"
#include "i18n.h"
#include "i18n.h"
using std::string;
- using std::stringstream;
using std::cout;
using boost::shared_ptr;
-using libdcp::raw_convert;
+using dcp::raw_convert;
SndfileContent::SndfileContent (shared_ptr<const Film> f, boost::filesystem::path p)
: Content (f, p)
#define LOG_ERROR_NC(...) _film->log()->log (__VA_ARGS__, Log::TYPE_ERROR);
using std::string;
- using std::stringstream;
using std::fixed;
using std::setprecision;
+using std::cout;
using boost::shared_ptr;
/** @param s Film to use.
set_state (FAILED);
return;
}
+
+ /* Parse the reply */
_buffer[_offset] = '\0';
- stringstream s;
- s << _buffer;
+ string s (_buffer);
cxml::Document doc ("Update");
- doc.read_stream (s);
+ doc.read_string (s);
{
boost::mutex::scoped_lock lm (_data_mutex);
#include "job.h"
#include "cross.h"
#include "video_content.h"
+#include "rect.h"
#include "md5_digester.h"
+#include "audio_processor.h"
+ #include "safe_stringstream.h"
#ifdef DCPOMATIC_WINDOWS
#include "stack.hpp"
#endif
}
}
- stringstream s;
+/** Return a user-readable string summarising the versions of our dependencies */
+string
+dependency_version_summary ()
+{
++ SafeStringStream s;
+ s << N_("libopenjpeg ") << opj_version () << N_(", ")
+ << N_("libavcodec ") << ffmpeg_version_to_string (avcodec_version()) << N_(", ")
+ << N_("libavfilter ") << ffmpeg_version_to_string (avfilter_version()) << N_(", ")
+ << N_("libavformat ") << ffmpeg_version_to_string (avformat_version()) << N_(", ")
+ << N_("libavutil ") << ffmpeg_version_to_string (avutil_version()) << N_(", ")
+ << N_("libswscale ") << ffmpeg_version_to_string (swscale_version()) << N_(", ")
+ << MagickVersion << N_(", ")
+ << N_("libssh ") << ssh_version (0) << N_(", ")
+ << N_("libdcp ") << dcp::version << N_(" git ") << dcp::git_commit;
+
+ return s.str ();
+}
+
+/** Construct a ScopedTemporary. A temporary filename is decided but the file is not opened
+ * until ::open() is called.
+ */
ScopedTemporary::ScopedTemporary ()
: _open (0)
{
#include "film.h"
#include "exceptions.h"
#include "frame_rate_change.h"
+#include "log.h"
+ #include "safe_stringstream.h"
#include "i18n.h"
job_manager.cc
kdm.cc
log.cc
+ magick_image_proxy.cc
md5_digester.cc
- piece.cc
+ mid_side_decoder.cc
player.cc
- player_video_frame.cc
+ player_video.cc
playlist.cc
ratio.cc
+ raw_image_proxy.cc
+ render_subtitles.cc
resampler.cc
+ safe_stringstream.cc
scp_dcp_job.cc
scaler.cc
send_kdm_email_job.cc
#include "lib/playlist.h"
#include "lib/content.h"
#include "lib/content_factory.h"
+#include "lib/dcp_content.h"
+ #include "lib/safe_stringstream.h"
#include "timecode.h"
#include "wx_util.h"
#include "film_editor.h"
return;
}
- SafeStringStream s;
-
- for (list<FilmEditorPanel*>::iterator i = _panels.begin(); i != _panels.end(); ++i) {
- (*i)->film_changed (p);
- }
-
- switch (p) {
- case Film::NONE:
- break;
- case Film::CONTENT:
- setup_content ();
- break;
- case Film::CONTAINER:
- setup_container ();
- break;
- case Film::NAME:
- checked_set (_name, _film->name());
- setup_dcp_name ();
- break;
- case Film::WITH_SUBTITLES:
- setup_dcp_name ();
- break;
- case Film::DCP_CONTENT_TYPE:
- checked_set (_dcp_content_type, DCPContentType::as_index (_film->dcp_content_type ()));
- setup_dcp_name ();
- break;
- case Film::SCALER:
- checked_set (_scaler, Scaler::as_index (_film->scaler ()));
- break;
- case Film::SIGNED:
- checked_set (_signed, _film->is_signed ());
- break;
- case Film::ENCRYPTED:
- checked_set (_encrypted, _film->encrypted ());
- if (_film->encrypted ()) {
- _film->set_signed (true);
- _signed->Enable (false);
- } else {
- _signed->Enable (_generally_sensitive);
- }
- break;
- case Film::RESOLUTION:
- checked_set (_resolution, _film->resolution() == RESOLUTION_2K ? 0 : 1);
- setup_dcp_name ();
- break;
- case Film::J2K_BANDWIDTH:
- checked_set (_j2k_bandwidth, _film->j2k_bandwidth() / 1000000);
- break;
- case Film::USE_ISDCF_NAME:
- checked_set (_use_isdcf_name, _film->use_isdcf_name ());
- setup_dcp_name ();
- break;
- case Film::ISDCF_METADATA:
- setup_dcp_name ();
- break;
- case Film::VIDEO_FRAME_RATE:
- {
- bool done = false;
- for (unsigned int i = 0; i < _frame_rate_choice->GetCount(); ++i) {
- if (wx_to_std (_frame_rate_choice->GetString(i)) == boost::lexical_cast<string> (_film->video_frame_rate())) {
- checked_set (_frame_rate_choice, i);
- done = true;
- break;
- }
- }
-
- if (!done) {
- checked_set (_frame_rate_choice, -1);
- }
-
- _frame_rate_spin->SetValue (_film->video_frame_rate ());
-
- _best_frame_rate->Enable (_film->best_video_frame_rate () != _film->video_frame_rate ());
- break;
- }
- case Film::AUDIO_CHANNELS:
- checked_set (_audio_channels, _film->audio_channels ());
- setup_dcp_name ();
- break;
- case Film::SEQUENCE_VIDEO:
- checked_set (_sequence_video, _film->sequence_video ());
- break;
- case Film::THREE_D:
- checked_set (_three_d, _film->three_d ());
- setup_dcp_name ();
- break;
- case Film::INTEROP:
- checked_set (_standard, _film->interop() ? 1 : 0);
- break;
- }
+ _content_panel->film_changed (p);
+ _dcp_panel->film_changed (p);
-
}
void
{
set_general_sensitivity (!a);
}
-
-void
-FilmEditor::setup_dcp_name ()
-{
- string s = _film->dcp_name (true);
- if (s.length() > 28) {
- _dcp_name->SetLabel (std_to_wx (s.substr (0, 28)) + N_("..."));
- _dcp_name->SetToolTip (std_to_wx (s));
- } else {
- _dcp_name->SetLabel (std_to_wx (s));
- }
-}
-
-void
-FilmEditor::best_frame_rate_clicked ()
-{
- if (!_film) {
- return;
- }
-
- _film->set_video_frame_rate (_film->best_video_frame_rate ());
-}
-
-void
-FilmEditor::setup_content ()
-{
- string selected_summary;
- int const s = _content->GetNextItem (-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
- if (s != -1) {
- selected_summary = wx_to_std (_content->GetItemText (s));
- }
-
- _content->DeleteAllItems ();
-
- ContentList content = _film->content ();
- sort (content.begin(), content.end(), ContentSorter ());
-
- for (ContentList::iterator i = content.begin(); i != content.end(); ++i) {
- int const t = _content->GetItemCount ();
- bool const valid = (*i)->paths_valid ();
-
- string s = (*i)->summary ();
- if (!valid) {
- s = _("MISSING: ") + s;
- }
-
- _content->InsertItem (t, std_to_wx (s));
-
- if ((*i)->summary() == selected_summary) {
- _content->SetItemState (t, wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED);
- }
-
- if (!valid) {
- _content->SetItemTextColour (t, *wxRED);
- }
- }
-
- if (selected_summary.empty () && !content.empty ()) {
- /* Select the item of content if none was selected before */
- _content->SetItemState (0, wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED);
- }
-}
-
-void
-FilmEditor::content_add_file_clicked ()
-{
- /* The wxFD_CHANGE_DIR here prevents a `could not set working directory' error 123 on Windows when using
- non-Latin filenames or paths.
- */
- wxFileDialog* d = new wxFileDialog (this, _("Choose a file or files"), wxT (""), wxT (""), wxT ("*.*"), wxFD_MULTIPLE | wxFD_CHANGE_DIR);
- int const r = d->ShowModal ();
-
- if (r != wxID_OK) {
- d->Destroy ();
- return;
- }
-
- wxArrayString paths;
- d->GetPaths (paths);
-
- /* XXX: check for lots of files here and do something */
-
- for (unsigned int i = 0; i < paths.GetCount(); ++i) {
- _film->examine_and_add_content (content_factory (_film, wx_to_std (paths[i])));
- }
-
- d->Destroy ();
-}
-
-void
-FilmEditor::content_add_folder_clicked ()
-{
- wxDirDialog* d = new wxDirDialog (this, _("Choose a folder"), wxT (""), wxDD_DIR_MUST_EXIST);
- int const r = d->ShowModal ();
- d->Destroy ();
-
- if (r != wxID_OK) {
- return;
- }
-
- shared_ptr<ImageContent> ic;
-
- try {
- ic.reset (new ImageContent (_film, boost::filesystem::path (wx_to_std (d->GetPath ()))));
- } catch (FileError& e) {
- error_dialog (this, std_to_wx (e.what ()));
- return;
- }
-
- _film->examine_and_add_content (ic);
-}
-
-void
-FilmEditor::content_remove_clicked ()
-{
- ContentList c = selected_content ();
- for (ContentList::iterator i = c.begin(); i != c.end(); ++i) {
- _film->remove_content (*i);
- }
-
- content_selection_changed ();
-}
-
-void
-FilmEditor::content_selection_changed ()
-{
- setup_content_sensitivity ();
-
- for (list<FilmEditorPanel*>::iterator i = _panels.begin(); i != _panels.end(); ++i) {
- (*i)->content_selection_changed ();
- }
-}
-
-/** Set up broad sensitivity based on the type of content that is selected */
-void
-FilmEditor::setup_content_sensitivity ()
-{
- _content_add_file->Enable (_generally_sensitive);
- _content_add_folder->Enable (_generally_sensitive);
-
- ContentList selection = selected_content ();
- VideoContentList video_selection = selected_video_content ();
- AudioContentList audio_selection = selected_audio_content ();
-
- _content_remove->Enable (!selection.empty() && _generally_sensitive);
- _content_earlier->Enable (selection.size() == 1 && _generally_sensitive);
- _content_later->Enable (selection.size() == 1 && _generally_sensitive);
- _content_timeline->Enable (!_film->content().empty() && _generally_sensitive);
-
- _video_panel->Enable (!video_selection.empty() && _generally_sensitive);
- _audio_panel->Enable (!audio_selection.empty() && _generally_sensitive);
- _subtitle_panel->Enable (selection.size() == 1 && dynamic_pointer_cast<FFmpegContent> (selection.front()) && _generally_sensitive);
- _timing_panel->Enable (!selection.empty() && _generally_sensitive);
-}
-
-ContentList
-FilmEditor::selected_content ()
-{
- ContentList sel;
-
- if (!_film) {
- return sel;
- }
-
- /* The list was populated using a sorted content list, so we must sort it here too
- so that we can look up by index and get the right thing.
- */
- ContentList content = _film->content ();
- sort (content.begin(), content.end(), ContentSorter ());
-
- long int s = -1;
- while (true) {
- s = _content->GetNextItem (s, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
- if (s == -1) {
- break;
- }
-
- if (s < int (_film->content().size ())) {
- sel.push_back (content[s]);
- }
- }
-
- return sel;
-}
-
-VideoContentList
-FilmEditor::selected_video_content ()
-{
- ContentList c = selected_content ();
- VideoContentList vc;
-
- for (ContentList::iterator i = c.begin(); i != c.end(); ++i) {
- shared_ptr<VideoContent> t = dynamic_pointer_cast<VideoContent> (*i);
- if (t) {
- vc.push_back (t);
- }
- }
-
- return vc;
-}
-
-AudioContentList
-FilmEditor::selected_audio_content ()
-{
- ContentList c = selected_content ();
- AudioContentList ac;
-
- for (ContentList::iterator i = c.begin(); i != c.end(); ++i) {
- shared_ptr<AudioContent> t = dynamic_pointer_cast<AudioContent> (*i);
- if (t) {
- ac.push_back (t);
- }
- }
-
- return ac;
-}
-
-SubtitleContentList
-FilmEditor::selected_subtitle_content ()
-{
- ContentList c = selected_content ();
- SubtitleContentList sc;
-
- for (ContentList::iterator i = c.begin(); i != c.end(); ++i) {
- shared_ptr<SubtitleContent> t = dynamic_pointer_cast<SubtitleContent> (*i);
- if (t) {
- sc.push_back (t);
- }
- }
-
- return sc;
-}
-
-FFmpegContentList
-FilmEditor::selected_ffmpeg_content ()
-{
- ContentList c = selected_content ();
- FFmpegContentList sc;
-
- for (ContentList::iterator i = c.begin(); i != c.end(); ++i) {
- shared_ptr<FFmpegContent> t = dynamic_pointer_cast<FFmpegContent> (*i);
- if (t) {
- sc.push_back (t);
- }
- }
-
- return sc;
-}
-
-void
-FilmEditor::content_timeline_clicked ()
-{
- if (_timeline_dialog) {
- _timeline_dialog->Destroy ();
- _timeline_dialog = 0;
- }
-
- _timeline_dialog = new TimelineDialog (this, _film);
- _timeline_dialog->Show ();
-}
-
-void
-FilmEditor::set_selection (weak_ptr<Content> wc)
-{
- ContentList content = _film->content ();
- for (size_t i = 0; i < content.size(); ++i) {
- if (content[i] == wc.lock ()) {
- _content->SetItemState (i, wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED);
- } else {
- _content->SetItemState (i, 0, wxLIST_STATE_SELECTED);
- }
- }
-}
-
-void
-FilmEditor::sequence_video_changed ()
-{
- if (!_film) {
- return;
- }
-
- _film->set_sequence_video (_sequence_video->GetValue ());
-}
-
-void
-FilmEditor::content_right_click (wxListEvent& ev)
-{
- _menu.popup (_film, selected_content (), ev.GetPoint ());
-}
-
-void
-FilmEditor::three_d_changed ()
-{
- if (!_film) {
- return;
- }
-
- _film->set_three_d (_three_d->GetValue ());
-}
-
-void
-FilmEditor::content_earlier_clicked ()
-{
- ContentList sel = selected_content ();
- if (sel.size() == 1) {
- _film->move_content_earlier (sel.front ());
- content_selection_changed ();
- }
-}
-
-void
-FilmEditor::content_later_clicked ()
-{
- ContentList sel = selected_content ();
- if (sel.size() == 1) {
- _film->move_content_later (sel.front ());
- content_selection_changed ();
- }
-}
-
-void
-FilmEditor::config_changed ()
-{
- _j2k_bandwidth->SetRange (1, Config::instance()->maximum_j2k_bandwidth() / 1000000);
- setup_frame_rate_widget ();
-}
-
-void
-FilmEditor::setup_frame_rate_widget ()
-{
- if (Config::instance()->allow_any_dcp_frame_rate ()) {
- _frame_rate_choice->Hide ();
- _frame_rate_spin->Show ();
- } else {
- _frame_rate_choice->Show ();
- _frame_rate_spin->Hide ();
- }
--
- _frame_rate_sizer->Layout ();
-}
add (_("Frames already encoded"), true);
_encoded = add (new ThreadedStaticText (this, _("counting..."), boost::bind (&PropertiesDialog::frames_already_encoded, this)));
_encoded->Finished.connect (boost::bind (&PropertiesDialog::layout, this));
-
- _frames->SetLabel (std_to_wx (lexical_cast<string> (_film->time_to_video_frames (_film->length()))));
+ _frames->SetLabel (std_to_wx (lexical_cast<string> (_film->length().frames (_film->video_frame_rate ()))));
double const disk = double (_film->required_disk_space()) / 1073741824.0f;
- stringstream s;
+ SafeStringStream s;
s << fixed << setprecision (1) << disk << wx_to_std (_("Gb"));
_disk->SetLabel (std_to_wx (s.str ()));
*/
- #include <sstream>
+/** @file test/film_metadata_test.cc
+ * @brief Test some basic reading/writing of film metadata.
+ */
+
#include <boost/test/unit_test.hpp>
#include <boost/filesystem.hpp>
#include <boost/date_time.hpp>