+ if (_subtitle) {
+ _subtitle->image->write_to_socket (socket);
+ }
+}
+
+bool
+PlayerVideo::has_j2k () const
+{
+ /* XXX: maybe other things */
+
+ shared_ptr<const J2KImageProxy> j2k = dynamic_pointer_cast<const J2KImageProxy> (_in);
+ if (!j2k) {
+ return false;
+ }
+
+ return _crop == Crop () && _inter_size == j2k->size() && !_subtitle && !_fade && !_colour_conversion;
+}
+
+Data
+PlayerVideo::j2k () const
+{
+ shared_ptr<const J2KImageProxy> j2k = dynamic_pointer_cast<const J2KImageProxy> (_in);
+ DCPOMATIC_ASSERT (j2k);
+ return j2k->j2k ();
+}
+
+Position<int>
+PlayerVideo::inter_position () const
+{
+ return Position<int> ((_out_size.width - _inter_size.width) / 2, (_out_size.height - _inter_size.height) / 2);
+}
+
+/** @return true if this PlayerVideo is definitely the same as another
+ * (apart from _time), false if it is probably not
+ */
+bool
+PlayerVideo::same (shared_ptr<const PlayerVideo> other) const
+{
+ if (_crop != other->_crop ||
+ _fade.get_value_or(0) != other->_fade.get_value_or(0) ||
+ _inter_size != other->_inter_size ||
+ _out_size != other->_out_size ||
+ _eyes != other->_eyes ||
+ _part != other->_part ||
+ _colour_conversion != other->_colour_conversion) {
+ return false;
+ }
+
+ if ((!_subtitle && other->_subtitle) || (_subtitle && !other->_subtitle)) {
+ /* One has a subtitle and the other doesn't */
+ return false;