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',
'css' => 'text/css',
'htm|html' => 'text/html',
'vtt' => 'text/vtt',
// Audio formats
'mp3|m4a|m4b' => 'audio/mpeg',
'ra|ram' => 'audio/x-realaudio',

View File

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

View File

@ -1193,41 +1193,16 @@
media.controller.MediaLibrary = media.controller.Library.extend({
defaults: _.defaults({
filterable: 'uploaded',
multiple: false,
priority: 80,
syncSelection: false,
displaySettings: true
displaySettings: false
}, media.controller.Library.prototype.defaults ),
initialize: function( options ) {
var library, comparator;
this.media = options.media;
this.set( 'library', media.query({ type: options.type }) );
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:add-video-source', this.renderAddSourceToolbar, this );
this.on( 'toolbar:render:select-poster-image', this.renderSelectPosterImageToolbar, this );
this.on( 'toolbar:render:add-track', this.renderAddTrackToolbar, this );
},
createStates: function() {
@ -2987,6 +2963,15 @@
toolbar: 'select-poster-image',
media: this.media,
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() );
// 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.
controller.setState( controller.options.state );
controller.reset();
@ -6366,21 +6388,17 @@
this.on( 'media:setting:remove', this.render );
this.on( 'media:setting:remove', this.setPlayer );
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 );
},
prepare: function() {
var attachment = false;
if ( this.model.attachment ) {
attachment = this.model.attachment.toJSON();
}
return _.defaults({
model: this.model.toJSON(),
attachment: attachment
model: this.model.toJSON()
}, this.options );
},
@ -6404,12 +6422,26 @@
},
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 );
},

View File

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

View File

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

View File

@ -690,7 +690,7 @@ function wp_print_media_templates() {
<label class="setting">
<span>SRC</span>
<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>
<# } #>
<?php
@ -700,7 +700,7 @@ function wp_print_media_templates() {
<label class="setting">
<span><?php echo strtoupper( $type ) ?></span>
<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>
<# } #>
<?php endforeach ?>
@ -779,13 +779,14 @@ function wp_print_media_templates() {
?><# if ( data.model.<?php echo $type ?> ) { #>
<source src="{{ data.model.<?php echo $type ?> }}" type="{{ wp.media.view.settings.embedMimes[ '<?php echo $type ?>' ] }}" />
<# } #>
<?php endforeach;
?></video>
<?php endforeach; ?>
{{{ data.model.content }}}
</video>
<# if ( ! _.isEmpty( data.model.src ) ) { #>
<label class="setting">
<span>SRC</span>
<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>
<# } #>
<?php foreach ( $video_types as $type ):
@ -793,7 +794,7 @@ function wp_print_media_templates() {
<label class="setting">
<span><?php echo strtoupper( $type ) ?></span>
<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>
<# } #>
<?php endforeach ?>
@ -802,7 +803,7 @@ function wp_print_media_templates() {
<label class="setting">
<span><?php _e( 'Poster Image' ); ?></span>
<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>
<# } #>
<div class="setting preload">
@ -824,6 +825,25 @@ function wp_print_media_templates() {
<input type="checkbox" data-setting="loop" />
</label>
<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>
</script>

View File

@ -2395,7 +2395,8 @@ function wp_enqueue_media( $args = array() ) {
'cancel' => __( 'Cancel' ),
'update' => __( 'Update' ),
'replace' => __( 'Replace' ),
'back' => __( 'Back' ),
'remove' => __( 'Remove' ),
'back' => __( 'Back' ),
/* 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
lack of plural support here, turn it into "selected: %d" then translate it.
@ -2450,7 +2451,6 @@ function wp_enqueue_media( $args = array() ) {
'audioDetailsCancel' => __( 'Cancel Edit' ),
'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.' ),
'audioRemoveSource' => __( 'Remove Audio Source' ),
// Edit Video
'videoDetailsTitle' => __( 'Video Details' ),
@ -2459,9 +2459,8 @@ function wp_enqueue_media( $args = array() ) {
'videoDetailsCancel' => __( 'Cancel Edit' ),
'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.' ),
'videoRemoveSource' => __( 'Remove Video Source' ),
'videoSelectPosterImageTitle' => _( 'Select Poster Image' ),
'videoRemovePoster' => __( 'Remove Poster Image' ),
'videoAddTrackTitle' => __( 'Add Subtitles' ),
// Playlist
'playlistDragInfo' => __( 'Drag and drop to reorder tracks.' ),