using boost::shared_ptr;
using boost::lexical_cast;
-int const ContentProperty::START = 400;
+int const ContentProperty::POSITION = 400;
int const ContentProperty::LENGTH = 401;
+int const ContentProperty::TRIM_START = 402;
+int const ContentProperty::TRIM_END = 403;
-Content::Content (shared_ptr<const Film> f, Time s)
+Content::Content (shared_ptr<const Film> f, Time p)
: _film (f)
- , _start (s)
+ , _position (p)
+ , _trim_start (0)
+ , _trim_end (0)
, _change_signals_frequent (false)
{
Content::Content (shared_ptr<const Film> f, boost::filesystem::path p)
: _film (f)
, _path (p)
- , _start (0)
+ , _position (0)
+ , _trim_start (0)
+ , _trim_end (0)
, _change_signals_frequent (false)
{
{
_path = node->string_child ("Path");
_digest = node->string_child ("Digest");
- _start = node->number_child<Time> ("Start");
+ _position = node->number_child<Time> ("Position");
+ _trim_start = node->number_child<Time> ("TrimStart");
+ _trim_end = node->number_child<Time> ("TrimEnd");
}
void
boost::mutex::scoped_lock lm (_mutex);
node->add_child("Path")->add_child_text (_path.string());
node->add_child("Digest")->add_child_text (_digest);
- node->add_child("Start")->add_child_text (lexical_cast<string> (_start));
+ node->add_child("Position")->add_child_text (lexical_cast<string> (_position));
+ node->add_child("TrimStart")->add_child_text (lexical_cast<string> (_trim_start));
+ node->add_child("TrimEnd")->add_child_text (lexical_cast<string> (_trim_end));
}
void
}
void
-Content::set_start (Time s)
+Content::set_position (Time p)
{
{
boost::mutex::scoped_lock lm (_mutex);
- _start = s;
+ _position = p;
}
- signal_changed (ContentProperty::START);
+ signal_changed (ContentProperty::POSITION);
}
+void
+Content::set_trim_start (Time t)
+{
+ {
+ boost::mutex::scoped_lock lm (_mutex);
+ _trim_start = t;
+ }
+
+ signal_changed (ContentProperty::TRIM_START);
+}
+
+void
+Content::set_trim_end (Time t)
+{
+ {
+ boost::mutex::scoped_lock lm (_mutex);
+ _trim_end = t;
+ }
+
+ signal_changed (ContentProperty::TRIM_END);
+}
+
+
shared_ptr<Content>
Content::clone () const
{
string
Content::technical_summary () const
{
- return String::compose ("%1 %2 %3", path(), digest(), start());
+ return String::compose ("%1 %2 %3", path(), digest(), position());
+}
+
+Time
+Content::length_after_trim () const
+{
+ return full_length () - _trim_start - _trim_end;
+}
+
+/** @param t A time relative to the start of this content (not the position).
+ * @return true if this time is trimmed by our trim settings.
+ */
+bool
+Content::trimmed (Time t) const
+{
+ return (t < trim_start() || t > (full_length() - trim_end ()));
}
class ContentProperty
{
public:
- static int const START;
+ static int const POSITION;
static int const LENGTH;
+ static int const TRIM_START;
+ static int const TRIM_END;
};
class Content : public boost::enable_shared_from_this<Content>, public boost::noncopyable
virtual std::string technical_summary () const;
virtual std::string information () const = 0;
virtual void as_xml (xmlpp::Node *) const;
- virtual Time length () const = 0;
+ virtual Time full_length () const = 0;
boost::shared_ptr<Content> clone () const;
return _digest;
}
- void set_start (Time);
+ void set_position (Time);
- Time start () const {
+ /** Time that this content starts; i.e. the time that the first
+ * bit of the content (trimmed or not) will happen.
+ */
+ Time position () const {
boost::mutex::scoped_lock lm (_mutex);
- return _start;
+ return _position;
}
+ void set_trim_start (Time);
+
+ Time trim_start () const {
+ boost::mutex::scoped_lock lm (_mutex);
+ return _trim_start;
+ }
+
+ void set_trim_end (Time);
+
+ Time trim_end () const {
+ boost::mutex::scoped_lock lm (_mutex);
+ return _trim_end;
+ }
+
Time end () const {
- return start() + length();
+ return position() + length_after_trim();
}
+ Time length_after_trim () const;
+
void set_change_signals_frequent (bool f) {
_change_signals_frequent = f;
}
+ bool trimmed (Time) const;
+
boost::signals2::signal<void (boost::weak_ptr<Content>, int, bool)> Changed;
protected:
/** Path of a file or a directory containing files */
boost::filesystem::path _path;
std::string _digest;
- Time _start;
+ Time _position;
+ Time _trim_start;
+ Time _trim_end;
bool _change_signals_frequent;
};
}
Time
-FFmpegContent::length () const
+FFmpegContent::full_length () const
{
shared_ptr<const Film> film = _film.lock ();
assert (film);
std::string technical_summary () const;
std::string information () const;
void as_xml (xmlpp::Node *) const;
- Time length () const;
+ Time full_length () const;
std::string identifier () const;
{
/* Add video content after any existing content */
if (dynamic_pointer_cast<VideoContent> (c)) {
- c->set_start (_playlist->video_end ());
+ c->set_position (_playlist->video_end ());
}
_playlist->add (c);
}
Time
-MovingImageContent::length () const
+MovingImageContent::full_length () const
{
shared_ptr<const Film> film = _film.lock ();
assert (film);
std::string summary () const;
std::string technical_summary () const;
void as_xml (xmlpp::Node *) const;
- Time length () const;
+ Time full_length () const;
std::string identifier () const;
public:
Piece (shared_ptr<Content> c)
: content (c)
- , video_position (c->start ())
- , audio_position (c->start ())
+ , video_position (c->position ())
+ , audio_position (c->position ())
{}
Piece (shared_ptr<Content> c, shared_ptr<Decoder> d)
: content (c)
, decoder (d)
- , video_position (c->start ())
- , audio_position (c->start ())
+ , video_position (c->position ())
+ , audio_position (c->position ())
{}
shared_ptr<Content> content;
s << "\tsndfile ";
}
- s << " at " << p.content->start() << " until " << p.content->end();
+ s << " at " << p.content->position() << " until " << p.content->end();
return s;
}
return;
}
+ Time const relative_time = (frame * frc.factor() * TIME_HZ / _film->video_frame_rate());
+ if (content->trimmed (relative_time)) {
+ return;
+ }
+
shared_ptr<Image> work_image = image->crop (content->crop(), true);
libdcp::Size const image_size = content->ratio()->size (_video_container_size);
work_image = work_image->scale_and_convert_to_rgb (image_size, _film->scaler(), true);
- Time time = content->start() + (frame * frc.factor() * TIME_HZ / _film->video_frame_rate());
-
+ Time time = content->position() + relative_time - content->trim_start ();
+
if (_film->with_subtitles () && _out_subtitle.image && time >= _out_subtitle.from && time <= _out_subtitle.to) {
work_image->alpha_blend (_out_subtitle.image, _out_subtitle.position);
}
shared_ptr<AudioContent> content = dynamic_pointer_cast<AudioContent> (piece->content);
assert (content);
+ Time const relative_time = _film->audio_frames_to_time (frame)
+ + (content->audio_delay() * TIME_HZ / 1000);
+
+ if (content->trimmed (relative_time)) {
+ return;
+ }
+
+ Time time = content->position() + relative_time;
+
/* Resample */
if (content->content_audio_frame_rate() != content->output_audio_frame_rate()) {
shared_ptr<Resampler> r = resampler (content, true);
audio = dcp_mapped;
- Time time = content->start()
- + _film->audio_frames_to_time (frame)
- + (content->audio_delay() * TIME_HZ / 1000);
-
/* We must cut off anything that comes before the start of all time */
if (time < 0) {
int const frames = - time * _film->audio_frame_rate() / TIME_HZ;
continue;
}
- Time s = t - vc->start ();
+ Time s = t - vc->position ();
s = max (static_cast<Time> (0), s);
- s = min (vc->length(), s);
+ s = min (vc->length_after_trim(), s);
- (*i)->video_position = (*i)->audio_position = vc->start() + s;
+ (*i)->video_position = (*i)->audio_position = vc->position() + s;
FrameRateConversion frc (vc->video_frame_rate(), _film->video_frame_rate());
/* Here we are converting from time (in the DCP) to a frame number in the content.
Hence we need to use the DCP's frame rate and the double/skip correction, not
the source's rate.
*/
- VideoContent::Frame f = s * _film->video_frame_rate() / (frc.factor() * TIME_HZ);
+ VideoContent::Frame f = (s + vc->trim_start ()) * _film->video_frame_rate() / (frc.factor() * TIME_HZ);
dynamic_pointer_cast<VideoDecoder>((*i)->decoder)->seek (f, accurate);
}
}
if (
- property == ContentProperty::START || property == ContentProperty::LENGTH ||
+ property == ContentProperty::POSITION || property == ContentProperty::LENGTH ||
+ property == ContentProperty::TRIM_START || property == ContentProperty::TRIM_END ||
property == VideoContentProperty::VIDEO_CROP || property == VideoContentProperty::VIDEO_RATIO
) {
_out_subtitle.position.y = rint (_video_container_size.height * (in_rect.y + (in_rect.height * (1 - sc->subtitle_scale ()) / 2)));
_out_subtitle.image = _in_subtitle.image->scale (libdcp::Size (scaled_size.width, scaled_size.height), Scaler::from_id ("bicubic"), true);
- _out_subtitle.from = _in_subtitle.from + piece->content->start ();
- _out_subtitle.to = _in_subtitle.to + piece->content->start ();
+ _out_subtitle.from = _in_subtitle.from + piece->content->position ();
+ _out_subtitle.to = _in_subtitle.to + piece->content->position ();
}
ContentChanged (content, property, frequent);
}
-
void
Playlist::maybe_sequence_video ()
{
continue;
}
- (*i)->set_start (last);
+ (*i)->set_position (last);
last = (*i)->end ();
}
bool
ContentSorter::operator() (shared_ptr<Content> a, shared_ptr<Content> b)
{
- return a->start() < b->start();
+ return a->position() < b->position();
}
/** @return content in an undefined order */
{
pair<Time, Time> range (TIME_MAX, 0);
for (ContentList::iterator i = c.begin(); i != c.end(); ++i) {
- range.first = min (range.first, (*i)->start ());
- range.second = max (range.second, (*i)->start ());
+ range.first = min (range.first, (*i)->position ());
+ range.second = max (range.second, (*i)->position ());
range.first = min (range.first, (*i)->end ());
range.second = max (range.second, (*i)->end ());
}
for (int i = 0; i < n; ++i) {
for (ContentList::iterator i = c.begin(); i != c.end(); ++i) {
shared_ptr<Content> copy = (*i)->clone ();
- copy->set_start (pos + copy->start() - range.first);
+ copy->set_position (pos + copy->position() - range.first);
_content.push_back (copy);
}
pos += range.second - range.first;
}
Time
-SndfileContent::length () const
+SndfileContent::full_length () const
{
shared_ptr<const Film> film = _film.lock ();
assert (film);
std::string technical_summary () const;
std::string information () const;
void as_xml (xmlpp::Node *) const;
- Time length () const;
+ Time full_length () const;
/* AudioContent */
int audio_channels () const {
}
Time
-StillImageContent::length () const
+StillImageContent::full_length () const
{
shared_ptr<const Film> film = _film.lock ();
assert (film);
std::string summary () const;
std::string technical_summary () const;
void as_xml (xmlpp::Node *) const;
- Time length () const;
+ Time full_length () const;
std::string identifier () const;
/* All other sensitivity in content panels should be triggered by
one of these.
*/
- film_content_changed (s, ContentProperty::START);
+ film_content_changed (s, ContentProperty::POSITION);
film_content_changed (s, ContentProperty::LENGTH);
+ film_content_changed (s, ContentProperty::TRIM_START);
+ film_content_changed (s, ContentProperty::TRIM_END);
film_content_changed (s, VideoContentProperty::VIDEO_CROP);
film_content_changed (s, VideoContentProperty::VIDEO_RATIO);
film_content_changed (s, VideoContentProperty::VIDEO_FRAME_TYPE);
}
return dcpomatic::Rect<int> (
- time_x (content->start ()) - 8,
+ time_x (content->position ()) - 8,
y_pos (_track) - 8,
- content->length () * _timeline.pixels_per_time_unit() + 16,
+ content->length_after_trim () * _timeline.pixels_per_time_unit() + 16,
_timeline.track_height() + 16
);
}
return;
}
- Time const start = cont->start ();
- Time const len = cont->length ();
+ Time const position = cont->position ();
+ Time const len = cont->length_after_trim ();
wxColour selected (colour().Red() / 2, colour().Green() / 2, colour().Blue() / 2);
}
wxGraphicsPath path = gc->CreatePath ();
- path.MoveToPoint (time_x (start), y_pos (_track) + 4);
- path.AddLineToPoint (time_x (start + len), y_pos (_track) + 4);
- path.AddLineToPoint (time_x (start + len), y_pos (_track + 1) - 4);
- path.AddLineToPoint (time_x (start), y_pos (_track + 1) - 4);
- path.AddLineToPoint (time_x (start), y_pos (_track) + 4);
+ path.MoveToPoint (time_x (position), y_pos (_track) + 4);
+ path.AddLineToPoint (time_x (position + len), y_pos (_track) + 4);
+ path.AddLineToPoint (time_x (position + len), y_pos (_track + 1) - 4);
+ path.AddLineToPoint (time_x (position), y_pos (_track + 1) - 4);
+ path.AddLineToPoint (time_x (position), y_pos (_track) + 4);
gc->StrokePath (path);
gc->FillPath (path);
wxDouble name_leading;
gc->GetTextExtent (name, &name_width, &name_height, &name_descent, &name_leading);
- gc->Clip (wxRegion (time_x (start), y_pos (_track), len * _timeline.pixels_per_time_unit(), _timeline.track_height()));
- gc->DrawText (name, time_x (start) + 12, y_pos (_track + 1) - name_height - 4);
+ gc->Clip (wxRegion (time_x (position), y_pos (_track), len * _timeline.pixels_per_time_unit(), _timeline.track_height()));
+ gc->DrawText (name, time_x (position) + 12, y_pos (_track + 1) - name_height - 4);
gc->ResetClip ();
}
{
ensure_ui_thread ();
- if (p == ContentProperty::START || p == ContentProperty::LENGTH) {
+ if (p == ContentProperty::POSITION || p == ContentProperty::LENGTH) {
force_redraw ();
}
, _tracks (0)
, _pixels_per_time_unit (0)
, _left_down (false)
- , _down_view_start (0)
+ , _down_view_position (0)
, _first_move (false)
, _menu (film, this)
{
if (test && test->track() == t) {
bool const no_overlap =
- (acv_content->start() < test_content->start() && acv_content->end() < test_content->start()) ||
- (acv_content->start() > test_content->end() && acv_content->end() > test_content->end());
+ (acv_content->position() < test_content->position() && acv_content->end() < test_content->position()) ||
+ (acv_content->position() > test_content->end() && acv_content->end() > test_content->end());
if (!no_overlap) {
/* we have an overlap on track `t' */
if (content_view) {
_down_view = content_view;
- _down_view_start = content_view->content()->start ();
+ _down_view_position = content_view->content()->position ();
}
for (ViewList::iterator i = _views.begin(); i != _views.end(); ++i) {
_down_view->content()->set_change_signals_frequent (false);
}
- set_start_from_event (ev);
+ set_position_from_event (ev);
}
void
return;
}
- set_start_from_event (ev);
+ set_position_from_event (ev);
}
void
}
void
-Timeline::set_start_from_event (wxMouseEvent& ev)
+Timeline::set_position_from_event (wxMouseEvent& ev)
{
wxPoint const p = ev.GetPosition();
Time const time_diff = (p.x - _down_point.x) / _pixels_per_time_unit;
if (_down_view) {
- _down_view->content()->set_start (max (static_cast<Time> (0), _down_view_start + time_diff));
+ _down_view->content()->set_position (max (static_cast<Time> (0), _down_view_position + time_diff));
shared_ptr<Film> film = _film.lock ();
assert (film);
void playlist_changed ();
void resized ();
void assign_tracks ();
- void set_start_from_event (wxMouseEvent &);
+ void set_position_from_event (wxMouseEvent &);
void clear_selection ();
typedef std::vector<boost::shared_ptr<View> > ViewList;
bool _left_down;
wxPoint _down_point;
boost::shared_ptr<ContentView> _down_view;
- Time _down_view_start;
+ Time _down_view_position;
bool _first_move;
ContentMenu _menu;
wxFlexGridSizer* grid = new wxFlexGridSizer (2, 4, 4);
_sizer->Add (grid, 0, wxALL, 8);
- add_label_to_sizer (grid, this, _("Start time"), true);
- _start = new Timecode (this);
- grid->Add (_start);
+ add_label_to_sizer (grid, this, _("Position"), true);
+ _position = new Timecode (this);
+ grid->Add (_position);
add_label_to_sizer (grid, this, _("Length"), true);
_length = new Timecode (this);
grid->Add (_length);
+ add_label_to_sizer (grid, this, _("Trim from start"), true);
+ _trim_start = new Timecode (this);
+ grid->Add (_trim_start);
+ add_label_to_sizer (grid, this, _("Trim from end"), true);
+ _trim_end = new Timecode (this);
+ grid->Add (_trim_end);
- _start->Changed.connect (boost::bind (&TimingPanel::start_changed, this));
- _length->Changed.connect (boost::bind (&TimingPanel::length_changed, this));
+ _position->Changed.connect (boost::bind (&TimingPanel::position_changed, this));
+ _length->Changed.connect (boost::bind (&TimingPanel::length_changed, this));
+ _trim_start->Changed.connect (boost::bind (&TimingPanel::trim_start_changed, this));
+ _trim_end->Changed.connect (boost::bind (&TimingPanel::trim_end_changed, this));
}
void
TimingPanel::film_content_changed (shared_ptr<Content> content, int property)
{
- if (property == ContentProperty::START) {
+ if (property == ContentProperty::POSITION) {
if (content) {
- _start->set (content->start (), _editor->film()->video_frame_rate ());
+ _position->set (content->position (), _editor->film()->video_frame_rate ());
} else {
- _start->set (0, 24);
+ _position->set (0, 24);
}
} else if (property == ContentProperty::LENGTH) {
if (content) {
- _length->set (content->length (), _editor->film()->video_frame_rate ());
+ _length->set (content->full_length (), _editor->film()->video_frame_rate ());
} else {
_length->set (0, 24);
}
- }
+ } else if (property == ContentProperty::TRIM_START) {
+ if (content) {
+ _trim_start->set (content->trim_start (), _editor->film()->video_frame_rate ());
+ } else {
+ _trim_start->set (0, 24);
+ }
+ } else if (property == ContentProperty::TRIM_END) {
+ if (content) {
+ _trim_end->set (content->trim_end (), _editor->film()->video_frame_rate ());
+ } else {
+ _trim_end->set (0, 24);
+ }
+ }
_length->set_editable (dynamic_pointer_cast<StillImageContent> (content));
}
void
-TimingPanel::start_changed ()
+TimingPanel::position_changed ()
{
shared_ptr<Content> c = _editor->selected_content ();
if (!c) {
return;
}
- c->set_start (_start->get (_editor->film()->video_frame_rate ()));
+ c->set_position (_position->get (_editor->film()->video_frame_rate ()));
}
void
ic->set_video_length (_length->get (_editor->film()->video_frame_rate()) * ic->video_frame_rate() / TIME_HZ);
}
}
+
+void
+TimingPanel::trim_start_changed ()
+{
+ shared_ptr<Content> c = _editor->selected_content ();
+ if (!c) {
+ return;
+ }
+
+ c->set_trim_start (_trim_start->get (_editor->film()->video_frame_rate ()));
+}
+
+
+void
+TimingPanel::trim_end_changed ()
+{
+ shared_ptr<Content> c = _editor->selected_content ();
+ if (!c) {
+ return;
+ }
+
+ c->set_trim_end (_trim_end->get (_editor->film()->video_frame_rate ()));
+}
void film_content_changed (boost::shared_ptr<Content>, int);
private:
- void start_changed ();
+ void position_changed ();
void length_changed ();
+ void trim_start_changed ();
+ void trim_end_changed ();
- Timecode* _start;
+ Timecode* _position;
Timecode* _length;
+ Timecode* _trim_start;
+ Timecode* _trim_end;
};
wait_for_jobs ();
contentA->set_video_length (3);
- contentA->set_start (film->video_frames_to_time (2));
+ contentA->set_position (film->video_frames_to_time (2));
contentB->set_video_length (1);
- contentB->set_start (film->video_frames_to_time (7));
+ contentB->set_position (film->video_frames_to_time (7));
film->make_dcp ();
/* Film should have been set to 25fps */
BOOST_CHECK_EQUAL (film->video_frame_rate(), 25);
- BOOST_CHECK_EQUAL (A->start(), 0);
+ BOOST_CHECK_EQUAL (A->position(), 0);
/* A is 16 frames long at 25 fps */
- BOOST_CHECK_EQUAL (B->start(), 16 * TIME_HZ / 25);
+ BOOST_CHECK_EQUAL (B->position(), 16 * TIME_HZ / 25);
shared_ptr<Player> player = film->make_player ();
PlayerWrapper wrap (player);