Video editing in the media modal:

* Add a state: `Add Subititles`
* Add `text/vtt` to the list of allowed mime-types, files end in `.vtt`. `.srt` files are served as `text/plain`.
* The content body of a video shortcode should be used for adding `<track>` elements only. This happens dynamically in the modal. If added by hand, they can still be parsed and managed.

See #27016.



git-svn-id: https://develop.svn.wordpress.org/trunk@27481 602fd350-edb4-49c9-b593-d223f7449a82
This commit is contained in:
Scott Taylor 2014-03-09 10:31:36 +00:00
parent 29e2d9f66a
commit d39e5ecafd
7 changed files with 133 additions and 62 deletions

View File

@ -1983,6 +1983,7 @@ function wp_get_mime_types() {
'rtx' => 'text/richtext', 'rtx' => 'text/richtext',
'css' => 'text/css', 'css' => 'text/css',
'htm|html' => 'text/html', 'htm|html' => 'text/html',
'vtt' => 'text/vtt',
// Audio formats // Audio formats
'mp3|m4a|m4b' => 'audio/mpeg', 'mp3|m4a|m4b' => 'audio/mpeg',
'ra|ram' => 'audio/x-realaudio', 'ra|ram' => 'audio/x-realaudio',

View File

@ -609,26 +609,33 @@
poster : '', poster : '',
loop : false, loop : false,
autoplay : false, autoplay : false,
preload : 'metadata' preload : 'metadata',
content : ''
}, },
edit : function (data) { edit : function (data) {
var frame, shortcode = wp.shortcode.next( 'video', data ).shortcode; var frame,
defaults = this.defaults,
shortcode = wp.shortcode.next( 'video', data ).shortcode,
attrs;
attrs = shortcode.attrs.named;
attrs.content = shortcode.content;
frame = wp.media({ frame = wp.media({
frame: 'video', frame: 'video',
state: 'video-details', state: 'video-details',
metadata: _.defaults( metadata: _.defaults( attrs, defaults )
shortcode.attrs.named,
wp.media.video.defaults
)
}); });
return frame; return frame;
}, },
shortcode : function (shortcode) { shortcode : function (shortcode) {
var self = this; var self = this, content = shortcode.content;
_.each( wp.media.video.defaults, function( value, key ) { delete shortcode.content;
_.each( this.defaults, function( value, key ) {
shortcode[ key ] = self.coerce( shortcode, key ); shortcode[ key ] = self.coerce( shortcode, key );
if ( value === shortcode[ key ] ) { if ( value === shortcode[ key ] ) {
@ -638,7 +645,8 @@
return wp.shortcode.string({ return wp.shortcode.string({
tag: 'video', tag: 'video',
attrs: shortcode attrs: shortcode,
content: content
}); });
} }
}, wp.media.mixin); }, wp.media.mixin);

View File

@ -1193,41 +1193,16 @@
media.controller.MediaLibrary = media.controller.Library.extend({ media.controller.MediaLibrary = media.controller.Library.extend({
defaults: _.defaults({ defaults: _.defaults({
filterable: 'uploaded', filterable: 'uploaded',
multiple: false,
priority: 80, priority: 80,
syncSelection: false, syncSelection: false,
displaySettings: true displaySettings: false
}, media.controller.Library.prototype.defaults ), }, media.controller.Library.prototype.defaults ),
initialize: function( options ) { initialize: function( options ) {
var library, comparator;
this.media = options.media; this.media = options.media;
this.set( 'library', media.query({ type: options.type }) ); this.set( 'library', media.query({ type: options.type }) );
media.controller.Library.prototype.initialize.apply( this, arguments ); media.controller.Library.prototype.initialize.apply( this, arguments );
library = this.get('library');
comparator = library.comparator;
// Overload the library's comparator to push items that are not in
// the mirrored query to the front of the aggregate collection.
library.comparator = function( a, b ) {
var aInQuery = !! this.mirroring.get( a.cid ),
bInQuery = !! this.mirroring.get( b.cid );
if ( ! aInQuery && bInQuery ) {
return -1;
} else if ( aInQuery && ! bInQuery ) {
return 1;
} else {
return comparator.apply( this, arguments );
}
};
// Add all items in the selection to the library, so any featured
// images that are not initially loaded still appear.
library.observe( this.get('selection') );
} }
}); });
@ -2952,6 +2927,7 @@
this.on( 'toolbar:render:replace-video', this.renderReplaceToolbar, this ); this.on( 'toolbar:render:replace-video', this.renderReplaceToolbar, this );
this.on( 'toolbar:render:add-video-source', this.renderAddSourceToolbar, this ); this.on( 'toolbar:render:add-video-source', this.renderAddSourceToolbar, this );
this.on( 'toolbar:render:select-poster-image', this.renderSelectPosterImageToolbar, this ); this.on( 'toolbar:render:select-poster-image', this.renderSelectPosterImageToolbar, this );
this.on( 'toolbar:render:add-track', this.renderAddTrackToolbar, this );
}, },
createStates: function() { createStates: function() {
@ -2987,6 +2963,15 @@
toolbar: 'select-poster-image', toolbar: 'select-poster-image',
media: this.media, media: this.media,
menu: 'video-details' menu: 'video-details'
} ),
new media.controller.MediaLibrary( {
type: 'text',
id: 'add-track',
title: l10n.videoAddTrackTitle,
toolbar: 'add-track',
media: this.media,
menu: 'video-details'
} ) } )
]); ]);
}, },
@ -3010,6 +2995,43 @@
state.trigger( 'set-poster-image', controller.media.toJSON() ); state.trigger( 'set-poster-image', controller.media.toJSON() );
// Restore and reset the default state.
controller.setState( controller.options.state );
controller.reset();
}
}
}
}) );
},
renderAddTrackToolbar: function() {
this.toolbar.set( new media.view.Toolbar({
controller: this,
items: {
replace: {
style: 'primary',
text: l10n.videoAddTrackTitle,
priority: 80,
click: function() {
var controller = this.controller,
state = controller.state(),
selection = state.get( 'selection' ),
attachment = selection.single(),
content = controller.media.get( 'content' );
if ( -1 === content.indexOf( attachment.get( 'url' ) ) ) {
content += [
'<track srclang="en" label="English"kind="subtitles" src="',
attachment.get( 'url' ),
'" />'
].join('');
controller.media.set( 'content', content );
}
state.trigger( 'add-track', controller.media.toJSON() );
// Restore and reset the default state. // Restore and reset the default state.
controller.setState( controller.options.state ); controller.setState( controller.options.state );
controller.reset(); controller.reset();
@ -6366,21 +6388,17 @@
this.on( 'media:setting:remove', this.render ); this.on( 'media:setting:remove', this.render );
this.on( 'media:setting:remove', this.setPlayer ); this.on( 'media:setting:remove', this.setPlayer );
this.events = _.extend( this.events, { this.events = _.extend( this.events, {
'click .remove-setting' : 'removeSetting' 'click .remove-setting' : 'removeSetting',
'change .content-track' : 'setTracks',
'click .remove-track' : 'setTracks'
} ); } );
media.view.Settings.AttachmentDisplay.prototype.initialize.apply( this, arguments ); media.view.Settings.AttachmentDisplay.prototype.initialize.apply( this, arguments );
}, },
prepare: function() { prepare: function() {
var attachment = false;
if ( this.model.attachment ) {
attachment = this.model.attachment.toJSON();
}
return _.defaults({ return _.defaults({
model: this.model.toJSON(), model: this.model.toJSON()
attachment: attachment
}, this.options ); }, this.options );
}, },
@ -6404,12 +6422,26 @@
}, },
removeSetting : function (e) { removeSetting : function (e) {
var setting = $( e.currentTarget ).parent(); var wrap = $( e.currentTarget ).parent(), setting;
this.model.unset( setting.find( 'input' ).data( 'setting' ) ); setting = wrap.find( 'input' ).data( 'setting' );
setting.remove(); if ( setting ) {
this.model.unset( setting );
this.trigger( 'media:setting:remove', this );
}
wrap.remove();
},
setTracks : function () {
var tracks = '';
_.each( this.$('.content-track'), function (track) {
tracks += $( track ).val();
} );
this.model.set( 'content', tracks );
this.trigger( 'media:setting:remove', this ); this.trigger( 'media:setting:remove', this );
}, },

View File

@ -19,6 +19,11 @@
max-width: 400px; max-width: 400px;
} }
.media-embed-details .embed-media-settings .setting span {
max-width: 400px;
width: auto;
}
.media-embed-details .embed-media-settings { .media-embed-details .embed-media-settings {
padding-top: 0; padding-top: 0;
} }
@ -28,8 +33,11 @@
max-width: 600px; max-width: 600px;
} }
.media-embed-details .setting p,
.media-embed-details .setting a { .media-embed-details .setting a {
color: #a00; color: #a00;
font-size: 10px;
text-transform: uppercase;
} }
.media-embed-details .setting a:hover { .media-embed-details .setting a:hover {

View File

@ -97,11 +97,14 @@ tinymce.PluginManager.add('wpgallery', function( editor ) {
frame.on( 'close', function () { frame.on( 'close', function () {
frame.detach(); frame.detach();
} ); } );
frame.state( 'video-details' ).on( 'update replace add-source select-poster-image', function ( selection ) { frame.state( 'video-details' ).on(
'update replace add-source select-poster-image add-track',
function ( selection ) {
var shortcode = wp.media.video.shortcode( selection ); var shortcode = wp.media.video.shortcode( selection );
editor.dom.setAttrib( node, 'data-wp-media', window.encodeURIComponent( shortcode ) ); editor.dom.setAttrib( node, 'data-wp-media', window.encodeURIComponent( shortcode ) );
frame.detach(); frame.detach();
} ); }
);
frame.open(); frame.open();
} else if ( editor.dom.hasClass( node, 'wp-audio' ) ) { } else if ( editor.dom.hasClass( node, 'wp-audio' ) ) {
frame = wp.media.audio.edit( data ); frame = wp.media.audio.edit( data );

View File

@ -690,7 +690,7 @@ function wp_print_media_templates() {
<label class="setting"> <label class="setting">
<span>SRC</span> <span>SRC</span>
<input type="text" disabled="disabled" data-setting="src" value="{{ data.model.src }}" /> <input type="text" disabled="disabled" data-setting="src" value="{{ data.model.src }}" />
<a class="remove-setting">{{{ wp.media.view.l10n.audioRemoveSource }}}</a> <a class="remove-setting">{{{ wp.media.view.l10n.remove }}}</a>
</label> </label>
<# } #> <# } #>
<?php <?php
@ -700,7 +700,7 @@ function wp_print_media_templates() {
<label class="setting"> <label class="setting">
<span><?php echo strtoupper( $type ) ?></span> <span><?php echo strtoupper( $type ) ?></span>
<input type="text" disabled="disabled" data-setting="<?php echo $type ?>" value="{{ data.model.<?php echo $type ?> }}" /> <input type="text" disabled="disabled" data-setting="<?php echo $type ?>" value="{{ data.model.<?php echo $type ?> }}" />
<a class="remove-setting">{{{ wp.media.view.l10n.audioRemoveSource }}}</a> <a class="remove-setting">{{{ wp.media.view.l10n.remove }}}</a>
</label> </label>
<# } #> <# } #>
<?php endforeach ?> <?php endforeach ?>
@ -779,13 +779,14 @@ function wp_print_media_templates() {
?><# if ( data.model.<?php echo $type ?> ) { #> ?><# if ( data.model.<?php echo $type ?> ) { #>
<source src="{{ data.model.<?php echo $type ?> }}" type="{{ wp.media.view.settings.embedMimes[ '<?php echo $type ?>' ] }}" /> <source src="{{ data.model.<?php echo $type ?> }}" type="{{ wp.media.view.settings.embedMimes[ '<?php echo $type ?>' ] }}" />
<# } #> <# } #>
<?php endforeach; <?php endforeach; ?>
?></video> {{{ data.model.content }}}
</video>
<# if ( ! _.isEmpty( data.model.src ) ) { #> <# if ( ! _.isEmpty( data.model.src ) ) { #>
<label class="setting"> <label class="setting">
<span>SRC</span> <span>SRC</span>
<input type="text" disabled="disabled" data-setting="src" value="{{ data.model.src }}" /> <input type="text" disabled="disabled" data-setting="src" value="{{ data.model.src }}" />
<a class="remove-setting">{{{ wp.media.view.l10n.videoRemoveSource }}}</a> <a class="remove-setting">{{{ wp.media.view.l10n.remove }}}</a>
</label> </label>
<# } #> <# } #>
<?php foreach ( $video_types as $type ): <?php foreach ( $video_types as $type ):
@ -793,7 +794,7 @@ function wp_print_media_templates() {
<label class="setting"> <label class="setting">
<span><?php echo strtoupper( $type ) ?></span> <span><?php echo strtoupper( $type ) ?></span>
<input type="text" disabled="disabled" data-setting="<?php echo $type ?>" value="{{ data.model.<?php echo $type ?> }}" /> <input type="text" disabled="disabled" data-setting="<?php echo $type ?>" value="{{ data.model.<?php echo $type ?> }}" />
<a class="remove-setting">{{{ wp.media.view.l10n.videoRemoveSource }}}</a> <a class="remove-setting">{{{ wp.media.view.l10n.remove }}}</a>
</label> </label>
<# } #> <# } #>
<?php endforeach ?> <?php endforeach ?>
@ -802,7 +803,7 @@ function wp_print_media_templates() {
<label class="setting"> <label class="setting">
<span><?php _e( 'Poster Image' ); ?></span> <span><?php _e( 'Poster Image' ); ?></span>
<input type="text" disabled="disabled" data-setting="poster" value="{{ data.model.poster }}" /> <input type="text" disabled="disabled" data-setting="poster" value="{{ data.model.poster }}" />
<a class="remove-setting">{{{ wp.media.view.l10n.videoRemovePoster }}}</a> <a class="remove-setting">{{{ wp.media.view.l10n.remove }}}</a>
</label> </label>
<# } #> <# } #>
<div class="setting preload"> <div class="setting preload">
@ -824,6 +825,25 @@ function wp_print_media_templates() {
<input type="checkbox" data-setting="loop" /> <input type="checkbox" data-setting="loop" />
</label> </label>
<div class="clear"></div> <div class="clear"></div>
<label class="setting" data-setting="content">
<span><?php _e( 'Tracks (subtitles, captions, descriptions, chapters or metadata)' ); ?></span>
<#
var content = '';
if ( ! _.isEmpty( data.model.content ) ) {
var tracks = jQuery( data.model.content ).filter( 'track' );
_.each( tracks.toArray(), function (track) {
content += track.outerHTML; #>
<p>
<input class="content-track" type="text" value="{{ track.outerHTML }}" />
<a class="remove-setting remove-track">{{{ wp.media.view.l10n.remove }}}</a>
</p>
<# } ); #>
<# } else { #>
<em>There are no associated subtitles.</em>
<# } #>
<textarea class="hidden content-setting">{{ content }}</textarea>
</label>
</div> </div>
</div> </div>
</script> </script>

View File

@ -2395,6 +2395,7 @@ function wp_enqueue_media( $args = array() ) {
'cancel' => __( 'Cancel' ), 'cancel' => __( 'Cancel' ),
'update' => __( 'Update' ), 'update' => __( 'Update' ),
'replace' => __( 'Replace' ), 'replace' => __( 'Replace' ),
'remove' => __( 'Remove' ),
'back' => __( 'Back' ), 'back' => __( 'Back' ),
/* translators: This is a would-be plural string used in the media manager. /* translators: This is a would-be plural string used in the media manager.
If there is not a word you can use in your language to avoid issues with the If there is not a word you can use in your language to avoid issues with the
@ -2450,7 +2451,6 @@ function wp_enqueue_media( $args = array() ) {
'audioDetailsCancel' => __( 'Cancel Edit' ), 'audioDetailsCancel' => __( 'Cancel Edit' ),
'audioDetailsText' => __( '"Replace Audio" will remove all associated source files when you update. ' . 'audioDetailsText' => __( '"Replace Audio" will remove all associated source files when you update. ' .
'"Add Audio Source" allows you to specify alternate sources for maximum native HTML5 audio playback.' ), '"Add Audio Source" allows you to specify alternate sources for maximum native HTML5 audio playback.' ),
'audioRemoveSource' => __( 'Remove Audio Source' ),
// Edit Video // Edit Video
'videoDetailsTitle' => __( 'Video Details' ), 'videoDetailsTitle' => __( 'Video Details' ),
@ -2459,9 +2459,8 @@ function wp_enqueue_media( $args = array() ) {
'videoDetailsCancel' => __( 'Cancel Edit' ), 'videoDetailsCancel' => __( 'Cancel Edit' ),
'videoDetailsText' => __( '"Replace Video" will remove all associated source files when you update. ' . 'videoDetailsText' => __( '"Replace Video" will remove all associated source files when you update. ' .
'"Add Video Source" allows you to specify alternate sources for maximum native HTML5 video playback.' ), '"Add Video Source" allows you to specify alternate sources for maximum native HTML5 video playback.' ),
'videoRemoveSource' => __( 'Remove Video Source' ),
'videoSelectPosterImageTitle' => _( 'Select Poster Image' ), 'videoSelectPosterImageTitle' => _( 'Select Poster Image' ),
'videoRemovePoster' => __( 'Remove Poster Image' ), 'videoAddTrackTitle' => __( 'Add Subtitles' ),
// Playlist // Playlist
'playlistDragInfo' => __( 'Drag and drop to reorder tracks.' ), 'playlistDragInfo' => __( 'Drag and drop to reorder tracks.' ),