Fix #431 - convert gif to webm during upload. Web UI treats them like it did
before. In the API, attachments now can be either image, video or gifv. Gifv is to be treated like images in terms of behaviour, but are videos by file type.gh/stable
parent
4cbeb9a7eb
commit
caf5b8e975
|
@ -75,11 +75,16 @@ export const FOLLOW_REQUEST_REJECT_FAIL = 'FOLLOW_REQUEST_REJECT_FAIL';
|
|||
|
||||
export function fetchAccount(id) {
|
||||
return (dispatch, getState) => {
|
||||
dispatch(fetchRelationships([id]));
|
||||
|
||||
if (getState().getIn(['accounts', id], null) !== null) {
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch(fetchAccountRequest(id));
|
||||
|
||||
api(getState).get(`/api/v1/accounts/${id}`).then(response => {
|
||||
dispatch(fetchAccountSuccess(response.data));
|
||||
dispatch(fetchRelationships([id]));
|
||||
}).catch(error => {
|
||||
dispatch(fetchAccountFail(id, error));
|
||||
});
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
import PureRenderMixin from 'react-addons-pure-render-mixin';
|
||||
|
||||
const ExtendedVideoPlayer = React.createClass({
|
||||
|
||||
propTypes: {
|
||||
src: React.PropTypes.string.isRequired
|
||||
},
|
||||
|
||||
mixins: [PureRenderMixin],
|
||||
|
||||
render () {
|
||||
return (
|
||||
<div>
|
||||
<video src={this.props.src} autoPlay muted loop />
|
||||
</div>
|
||||
);
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
export default ExtendedVideoPlayer;
|
|
@ -43,6 +43,141 @@ const spoilerButtonStyle = {
|
|||
zIndex: '100'
|
||||
};
|
||||
|
||||
const itemStyle = {
|
||||
boxSizing: 'border-box',
|
||||
position: 'relative',
|
||||
float: 'left',
|
||||
border: 'none',
|
||||
display: 'block'
|
||||
};
|
||||
|
||||
const thumbStyle = {
|
||||
display: 'block',
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
textDecoration: 'none',
|
||||
backgroundSize: 'cover',
|
||||
cursor: 'zoom-in'
|
||||
};
|
||||
|
||||
const gifvThumbStyle = {
|
||||
position: 'relative',
|
||||
zIndex: '1',
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
objectFit: 'cover',
|
||||
top: '50%',
|
||||
transform: 'translateY(-50%)',
|
||||
cursor: 'zoom-in'
|
||||
};
|
||||
|
||||
const Item = React.createClass({
|
||||
|
||||
propTypes: {
|
||||
attachment: ImmutablePropTypes.map.isRequired,
|
||||
index: React.PropTypes.number.isRequired,
|
||||
size: React.PropTypes.number.isRequired,
|
||||
onClick: React.PropTypes.func.isRequired
|
||||
},
|
||||
|
||||
mixins: [PureRenderMixin],
|
||||
|
||||
handleClick (e) {
|
||||
const { index, onClick } = this.props;
|
||||
|
||||
if (e.button === 0) {
|
||||
e.preventDefault();
|
||||
onClick(index);
|
||||
}
|
||||
|
||||
e.stopPropagation();
|
||||
},
|
||||
|
||||
render () {
|
||||
const { attachment, index, size } = this.props;
|
||||
|
||||
let width = 50;
|
||||
let height = 100;
|
||||
let top = 'auto';
|
||||
let left = 'auto';
|
||||
let bottom = 'auto';
|
||||
let right = 'auto';
|
||||
|
||||
if (size === 1) {
|
||||
width = 100;
|
||||
}
|
||||
|
||||
if (size === 4 || (size === 3 && index > 0)) {
|
||||
height = 50;
|
||||
}
|
||||
|
||||
if (size === 2) {
|
||||
if (index === 0) {
|
||||
right = '2px';
|
||||
} else {
|
||||
left = '2px';
|
||||
}
|
||||
} else if (size === 3) {
|
||||
if (index === 0) {
|
||||
right = '2px';
|
||||
} else if (index > 0) {
|
||||
left = '2px';
|
||||
}
|
||||
|
||||
if (index === 1) {
|
||||
bottom = '2px';
|
||||
} else if (index > 1) {
|
||||
top = '2px';
|
||||
}
|
||||
} else if (size === 4) {
|
||||
if (index === 0 || index === 2) {
|
||||
right = '2px';
|
||||
}
|
||||
|
||||
if (index === 1 || index === 3) {
|
||||
left = '2px';
|
||||
}
|
||||
|
||||
if (index < 2) {
|
||||
bottom = '2px';
|
||||
} else {
|
||||
top = '2px';
|
||||
}
|
||||
}
|
||||
|
||||
let thumbnail = '';
|
||||
|
||||
if (attachment.get('type') === 'image') {
|
||||
thumbnail = (
|
||||
<a
|
||||
href={attachment.get('remote_url') ? attachment.get('remote_url') : attachment.get('url')}
|
||||
onClick={this.handleClick}
|
||||
target='_blank'
|
||||
style={{ background: `url(${attachment.get('preview_url')}) no-repeat center`, ...thumbStyle }}
|
||||
/>
|
||||
);
|
||||
} else if (attachment.get('type') === 'gifv') {
|
||||
thumbnail = (
|
||||
<video
|
||||
src={attachment.get('url')}
|
||||
onClick={this.handleClick}
|
||||
autoPlay={true}
|
||||
loop={true}
|
||||
muted={true}
|
||||
style={gifvThumbStyle}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div key={attachment.get('id')} style={{ ...itemStyle, left: left, top: top, right: right, bottom: bottom, width: `${width}%`, height: `${height}%` }}>
|
||||
{thumbnail}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
const MediaGallery = React.createClass({
|
||||
|
||||
getInitialState () {
|
||||
|
@ -61,17 +196,12 @@ const MediaGallery = React.createClass({
|
|||
|
||||
mixins: [PureRenderMixin],
|
||||
|
||||
handleClick (index, e) {
|
||||
if (e.button === 0) {
|
||||
e.preventDefault();
|
||||
this.props.onOpenMedia(this.props.media, index);
|
||||
}
|
||||
|
||||
e.stopPropagation();
|
||||
handleOpen (e) {
|
||||
this.setState({ visible: !this.state.visible });
|
||||
},
|
||||
|
||||
handleOpen () {
|
||||
this.setState({ visible: !this.state.visible });
|
||||
handleClick (index) {
|
||||
this.props.onOpenMedia(this.props.media, index);
|
||||
},
|
||||
|
||||
render () {
|
||||
|
@ -80,87 +210,31 @@ const MediaGallery = React.createClass({
|
|||
let children;
|
||||
|
||||
if (!this.state.visible) {
|
||||
let warning;
|
||||
|
||||
if (sensitive) {
|
||||
children = (
|
||||
<div style={spoilerStyle} className='media-spoiler' onClick={this.handleOpen}>
|
||||
<span style={spoilerSpanStyle}><FormattedMessage id='status.sensitive_warning' defaultMessage='Sensitive content' /></span>
|
||||
<span style={spoilerSubSpanStyle}><FormattedMessage id='status.sensitive_toggle' defaultMessage='Click to view' /></span>
|
||||
</div>
|
||||
);
|
||||
warning = <FormattedMessage id='status.sensitive_warning' defaultMessage='Sensitive content' />;
|
||||
} else {
|
||||
warning = <FormattedMessage id='status.media_hidden' defaultMessage='Media hidden' />;
|
||||
}
|
||||
|
||||
children = (
|
||||
<div style={spoilerStyle} className='media-spoiler' onClick={this.handleOpen}>
|
||||
<span style={spoilerSpanStyle}><FormattedMessage id='status.media_hidden' defaultMessage='Media hidden' /></span>
|
||||
<span style={spoilerSpanStyle}>{warning}</span>
|
||||
<span style={spoilerSubSpanStyle}><FormattedMessage id='status.sensitive_toggle' defaultMessage='Click to view' /></span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
} else {
|
||||
const size = media.take(4).size;
|
||||
|
||||
children = media.take(4).map((attachment, i) => {
|
||||
let width = 50;
|
||||
let height = 100;
|
||||
let top = 'auto';
|
||||
let left = 'auto';
|
||||
let bottom = 'auto';
|
||||
let right = 'auto';
|
||||
|
||||
if (size === 1) {
|
||||
width = 100;
|
||||
}
|
||||
|
||||
if (size === 4 || (size === 3 && i > 0)) {
|
||||
height = 50;
|
||||
}
|
||||
|
||||
if (size === 2) {
|
||||
if (i === 0) {
|
||||
right = '2px';
|
||||
} else {
|
||||
left = '2px';
|
||||
}
|
||||
} else if (size === 3) {
|
||||
if (i === 0) {
|
||||
right = '2px';
|
||||
} else if (i > 0) {
|
||||
left = '2px';
|
||||
}
|
||||
|
||||
if (i === 1) {
|
||||
bottom = '2px';
|
||||
} else if (i > 1) {
|
||||
top = '2px';
|
||||
}
|
||||
} else if (size === 4) {
|
||||
if (i === 0 || i === 2) {
|
||||
right = '2px';
|
||||
}
|
||||
|
||||
if (i === 1 || i === 3) {
|
||||
left = '2px';
|
||||
}
|
||||
|
||||
if (i < 2) {
|
||||
bottom = '2px';
|
||||
} else {
|
||||
top = '2px';
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div key={attachment.get('id')} style={{ boxSizing: 'border-box', position: 'relative', left: left, top: top, right: right, bottom: bottom, float: 'left', border: 'none', display: 'block', width: `${width}%`, height: `${height}%` }}>
|
||||
<a href={attachment.get('remote_url') ? attachment.get('remote_url') : attachment.get('url')} onClick={this.handleClick.bind(this, i)} target='_blank' style={{ display: 'block', width: '100%', height: '100%', background: `url(${attachment.get('preview_url')}) no-repeat center`, textDecoration: 'none', backgroundSize: 'cover', cursor: 'zoom-in' }} />
|
||||
</div>
|
||||
);
|
||||
});
|
||||
children = media.take(4).map((attachment, i) => <Item key={attachment.get('id')} onClick={this.handleClick} attachment={attachment} index={i} size={size} />);
|
||||
}
|
||||
|
||||
return (
|
||||
<div style={{ ...outerStyle, height: `${this.props.height}px` }}>
|
||||
<div style={spoilerButtonStyle} >
|
||||
<div style={spoilerButtonStyle}>
|
||||
<IconButton title={intl.formatMessage(messages.toggle_visible)} icon={this.state.visible ? 'eye' : 'eye-slash'} onClick={this.handleOpen} />
|
||||
</div>
|
||||
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -74,8 +74,8 @@ const Status = React.createClass({
|
|||
}
|
||||
|
||||
if (status.get('media_attachments').size > 0 && !this.props.muted) {
|
||||
if (status.getIn(['media_attachments', 0, 'type']) === 'video') {
|
||||
media = <VideoPlayer media={status.getIn(['media_attachments', 0])} sensitive={status.get('sensitive')} />;
|
||||
if (status.getIn(['media_attachments', 0, 'type']) === 'video' || (status.get('media_attachments').size === 1 && status.getIn(['media_attachments', 0, 'type']) === 'gifv')) {
|
||||
media = <VideoPlayer media={status.getIn(['media_attachments', 0])} autoplay={status.getIn(['media_attachments', 0, 'type']) === 'gifv'} sensitive={status.get('sensitive')} />;
|
||||
} else {
|
||||
media = <MediaGallery media={status.get('media_attachments')} sensitive={status.get('sensitive')} height={110} onOpenMedia={this.props.onOpenMedia} />;
|
||||
}
|
||||
|
|
|
@ -175,7 +175,7 @@ const VideoPlayer = React.createClass({
|
|||
);
|
||||
} else {
|
||||
return (
|
||||
<div style={{...spoilerStyle, width: `${width}px`, height: `${height}px` }} className='media-spoiler' onClick={this.handleOpen}>
|
||||
<div style={{...spoilerStyle, width: `${width}px`, height: `${height}px` }} className='media-spoiler' onClick={this.handleVisibility}>
|
||||
{spoilerButton}
|
||||
<span style={spoilerSpanStyle}><FormattedMessage id='status.media_hidden' defaultMessage='Media hidden' /></span>
|
||||
<span style={spoilerSubSpanStyle}><FormattedMessage id='status.sensitive_toggle' defaultMessage='Click to view' /></span>
|
||||
|
@ -197,7 +197,7 @@ const VideoPlayer = React.createClass({
|
|||
<div style={{ cursor: 'default', marginTop: '8px', overflow: 'hidden', width: `${width}px`, height: `${height}px`, boxSizing: 'border-box', background: '#000', position: 'relative' }}>
|
||||
{spoilerButton}
|
||||
{muteButton}
|
||||
<video ref={this.setRef} src={media.get('url')} autoPlay='true' loop={true} muted={this.state.muted} style={videoStyle} onClick={this.handleVideoClick} />
|
||||
<video ref={this.setRef} src={media.get('url')} autoPlay={true} loop={true} muted={this.state.muted} style={videoStyle} onClick={this.handleVideoClick} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -38,7 +38,7 @@ const DetailedStatus = React.createClass({
|
|||
let applicationLink = '';
|
||||
|
||||
if (status.get('media_attachments').size > 0) {
|
||||
if (status.getIn(['media_attachments', 0, 'type']) === 'video') {
|
||||
if (status.getIn(['media_attachments', 0, 'type']) === 'video' || (status.get('media_attachments').size === 1 && status.getIn(['media_attachments', 0, 'type']) === 'gifv')) {
|
||||
media = <VideoPlayer sensitive={status.get('sensitive')} media={status.getIn(['media_attachments', 0])} width={300} height={150} autoplay />;
|
||||
} else {
|
||||
media = <MediaGallery sensitive={status.get('sensitive')} media={status.get('media_attachments')} height={300} onOpenMedia={this.props.onOpenMedia} />;
|
||||
|
|
|
@ -9,6 +9,7 @@ import ImageLoader from 'react-imageloader';
|
|||
import LoadingIndicator from '../../../components/loading_indicator';
|
||||
import PureRenderMixin from 'react-addons-pure-render-mixin';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import ExtendedVideoPlayer from '../../../components/extended_video_player';
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
media: state.getIn(['modal', 'media']),
|
||||
|
@ -131,27 +132,34 @@ const Modal = React.createClass({
|
|||
return null;
|
||||
}
|
||||
|
||||
const url = media.get(index).get('url');
|
||||
const attachment = media.get(index);
|
||||
const url = attachment.get('url');
|
||||
|
||||
let leftNav, rightNav;
|
||||
let leftNav, rightNav, content;
|
||||
|
||||
leftNav = rightNav = '';
|
||||
leftNav = rightNav = content = '';
|
||||
|
||||
if (media.size > 1) {
|
||||
leftNav = <div style={leftNavStyle} className='modal-container--nav' onClick={this.handlePrevClick}><i className='fa fa-fw fa-chevron-left' /></div>;
|
||||
rightNav = <div style={rightNavStyle} className='modal-container--nav' onClick={this.handleNextClick}><i className='fa fa-fw fa-chevron-right' /></div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<Lightbox {...other}>
|
||||
{leftNav}
|
||||
|
||||
if (attachment.get('type') === 'image') {
|
||||
content = (
|
||||
<ImageLoader
|
||||
src={url}
|
||||
preloader={preloader}
|
||||
imgProps={{ style: imageStyle }}
|
||||
/>
|
||||
);
|
||||
} else if (attachment.get('type') === 'gifv') {
|
||||
content = <ExtendedVideoPlayer src={url} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<Lightbox {...other}>
|
||||
{leftNav}
|
||||
{content}
|
||||
{rightNav}
|
||||
</Lightbox>
|
||||
);
|
||||
|
|
|
@ -104,8 +104,12 @@
|
|||
overflow: hidden;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
height: 110px;
|
||||
position: relative;
|
||||
|
||||
.status__attachments__inner {
|
||||
display: flex;
|
||||
height: 214px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -184,8 +188,12 @@
|
|||
overflow: hidden;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
height: 300px;
|
||||
position: relative;
|
||||
|
||||
.status__attachments__inner {
|
||||
display: flex;
|
||||
height: 360px;
|
||||
}
|
||||
}
|
||||
|
||||
.video-player {
|
||||
|
@ -231,11 +239,19 @@
|
|||
text-decoration: none;
|
||||
cursor: zoom-in;
|
||||
}
|
||||
|
||||
video {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
}
|
||||
|
||||
.video-item {
|
||||
max-width: 196px;
|
||||
|
||||
a {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
@ -258,6 +274,9 @@
|
|||
width: 100%;
|
||||
height: 100%;
|
||||
cursor: pointer;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
|
|
@ -1,15 +1,32 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class MediaAttachment < ApplicationRecord
|
||||
self.inheritance_column = nil
|
||||
|
||||
enum type: [:image, :gifv, :video]
|
||||
|
||||
IMAGE_MIME_TYPES = ['image/jpeg', 'image/png', 'image/gif'].freeze
|
||||
VIDEO_MIME_TYPES = ['video/webm', 'video/mp4'].freeze
|
||||
|
||||
IMAGE_STYLES = { original: '1280x1280>', small: '400x400>' }.freeze
|
||||
VIDEO_STYLES = {
|
||||
small: {
|
||||
convert_options: {
|
||||
output: {
|
||||
vf: 'scale=\'min(400\, iw):min(400\, ih)\':force_original_aspect_ratio=decrease',
|
||||
},
|
||||
},
|
||||
format: 'png',
|
||||
time: 0,
|
||||
},
|
||||
}.freeze
|
||||
|
||||
belongs_to :account, inverse_of: :media_attachments
|
||||
belongs_to :status, inverse_of: :media_attachments
|
||||
|
||||
has_attached_file :file,
|
||||
styles: -> (f) { file_styles f },
|
||||
processors: -> (f) { f.video? ? [:transcoder] : [:thumbnail] },
|
||||
styles: ->(f) { file_styles f },
|
||||
processors: ->(f) { file_processors f },
|
||||
convert_options: { all: '-quality 90 -strip' }
|
||||
validates_attachment_content_type :file, content_type: IMAGE_MIME_TYPES + VIDEO_MIME_TYPES
|
||||
validates_attachment_size :file, less_than: 8.megabytes
|
||||
|
@ -27,45 +44,45 @@ class MediaAttachment < ApplicationRecord
|
|||
self.file = URI.parse(url)
|
||||
end
|
||||
|
||||
def image?
|
||||
IMAGE_MIME_TYPES.include? file_content_type
|
||||
end
|
||||
|
||||
def video?
|
||||
VIDEO_MIME_TYPES.include? file_content_type
|
||||
end
|
||||
|
||||
def type
|
||||
image? ? 'image' : 'video'
|
||||
end
|
||||
|
||||
def to_param
|
||||
shortcode
|
||||
end
|
||||
|
||||
before_create :set_shortcode
|
||||
before_post_process :set_type
|
||||
|
||||
class << self
|
||||
private
|
||||
|
||||
def file_styles(f)
|
||||
if f.instance.image?
|
||||
if f.instance.file_content_type == 'image/gif'
|
||||
{
|
||||
original: '1280x1280>',
|
||||
small: '400x400>',
|
||||
}
|
||||
else
|
||||
{
|
||||
small: {
|
||||
small: IMAGE_STYLES[:small],
|
||||
original: {
|
||||
format: 'webm',
|
||||
convert_options: {
|
||||
output: {
|
||||
vf: 'scale=\'min(400\, iw):min(400\, ih)\':force_original_aspect_ratio=decrease',
|
||||
'c:v' => 'libvpx',
|
||||
'crf' => 6,
|
||||
'b:v' => '500K',
|
||||
},
|
||||
},
|
||||
format: 'png',
|
||||
time: 1,
|
||||
},
|
||||
}
|
||||
elsif IMAGE_MIME_TYPES.include? f.instance.file_content_type
|
||||
IMAGE_STYLES
|
||||
else
|
||||
VIDEO_STYLES
|
||||
end
|
||||
end
|
||||
|
||||
def file_processors(f)
|
||||
if f.file_content_type == 'image/gif'
|
||||
[:gif_transcoder]
|
||||
elsif VIDEO_MIME_TYPES.include? f.file_content_type
|
||||
[:transcoder]
|
||||
else
|
||||
[:thumbnail]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -80,4 +97,8 @@ class MediaAttachment < ApplicationRecord
|
|||
break if MediaAttachment.find_by(shortcode: shortcode).nil?
|
||||
end
|
||||
end
|
||||
|
||||
def set_type
|
||||
self.type = VIDEO_MIME_TYPES.include?(file_content_type) ? :video : :image
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
object @media
|
||||
attribute :id, :type
|
||||
node(:url) { |media| full_asset_url(media.file.url( :original)) }
|
||||
node(:preview_url) { |media| full_asset_url(media.file.url( :small)) }
|
||||
node(:url) { |media| full_asset_url(media.file.url(:original)) }
|
||||
node(:preview_url) { |media| full_asset_url(media.file.url(:small)) }
|
||||
node(:text_url) { |media| medium_url(media) }
|
||||
|
|
|
@ -22,9 +22,9 @@
|
|||
.detailed-status__attachments
|
||||
- if status.sensitive?
|
||||
= render partial: 'stream_entries/content_spoiler'
|
||||
.status__attachments__inner
|
||||
- status.media_attachments.each do |media|
|
||||
.media-item
|
||||
= link_to '', (media.remote_url.blank? ? media.file.url(:original) : media.remote_url), style: "background-image: url(#{media.file.url(:original)})", target: '_blank', rel: 'noopener', class: "u-#{media.video? ? 'video' : 'photo'}"
|
||||
= render partial: 'stream_entries/media', locals: { media: media }
|
||||
|
||||
%div.detailed-status__meta
|
||||
%data.dt-published{ value: status.created_at.to_time.iso8601 }
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
.media-item
|
||||
= link_to media.remote_url.blank? ? media.file.url(:original) : media.remote_url, style: media.image? ? "background-image: url(#{media.file.url(:original)})" : "", target: '_blank', rel: 'noopener', class: "u-#{media.video? || media.gifv? ? 'video' : 'photo'}" do
|
||||
- unless media.image?
|
||||
%video{ src: media.file.url(:original), autoplay: true, loop: true }/
|
|
@ -22,11 +22,12 @@
|
|||
- if status.sensitive?
|
||||
= render partial: 'stream_entries/content_spoiler'
|
||||
- if status.media_attachments.first.video?
|
||||
.status__attachments__inner
|
||||
.video-item
|
||||
= link_to (status.media_attachments.first.remote_url.blank? ? status.media_attachments.first.file.url(:original) : status.media_attachments.first.remote_url), style: "background-image: url(#{status.media_attachments.first.file.url(:small)})", target: '_blank', rel: 'noopener', class: 'u-video' do
|
||||
.video-item__play
|
||||
= fa_icon('play')
|
||||
- else
|
||||
.status__attachments__inner
|
||||
- status.media_attachments.each do |media|
|
||||
.media-item
|
||||
= link_to '', (media.remote_url.blank? ? media.file.url(:original) : media.remote_url), style: "background-image: url(#{media.file.url(:original)})", target: '_blank', rel: 'noopener', class: "u-#{media.video? ? 'video' : 'photo'}"
|
||||
= render partial: 'stream_entries/media', locals: { media: media }
|
||||
|
|
|
@ -2,12 +2,13 @@ require_relative 'boot'
|
|||
|
||||
require 'rails/all'
|
||||
|
||||
require_relative '../app/lib/exceptions'
|
||||
|
||||
# Require the gems listed in Gemfile, including any gems
|
||||
# you've limited to :test, :development, or :production.
|
||||
Bundler.require(*Rails.groups)
|
||||
|
||||
require_relative '../app/lib/exceptions'
|
||||
require_relative '../lib/paperclip/gif_transcoder'
|
||||
|
||||
Dotenv::Railtie.load
|
||||
|
||||
module Mastodon
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
class AddTypeToMediaAttachments < ActiveRecord::Migration[5.0]
|
||||
def up
|
||||
add_column :media_attachments, :type, :integer, default: 0, null: false
|
||||
|
||||
MediaAttachment.where(file_content_type: MediaAttachment::IMAGE_MIME_TYPES).update_all(type: MediaAttachment.types[:image])
|
||||
MediaAttachment.where(file_content_type: MediaAttachment::VIDEO_MIME_TYPES).update_all(type: MediaAttachment.types[:video])
|
||||
end
|
||||
|
||||
def down
|
||||
remove_column :media_attachments, :type
|
||||
end
|
||||
end
|
|
@ -10,7 +10,7 @@
|
|||
#
|
||||
# It's strongly recommended that you check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema.define(version: 20170303212857) do
|
||||
ActiveRecord::Schema.define(version: 20170304202101) do
|
||||
|
||||
# These are extensions that must be enabled in order to support this database
|
||||
enable_extension "plpgsql"
|
||||
|
@ -98,6 +98,7 @@ ActiveRecord::Schema.define(version: 20170303212857) do
|
|||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.string "shortcode"
|
||||
t.integer "type", default: 0, null: false
|
||||
t.index ["shortcode"], name: "index_media_attachments_on_shortcode", unique: true, using: :btree
|
||||
t.index ["status_id"], name: "index_media_attachments_on_status_id", using: :btree
|
||||
end
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Paperclip
|
||||
# This transcoder is only to be used for the MediaAttachment model
|
||||
# to convert animated gifs to webm
|
||||
class GifTranscoder < Paperclip::Processor
|
||||
def make
|
||||
num_frames = identify('-format %n :file', file: file.path).to_i
|
||||
|
||||
return file unless options[:style] == :original && num_frames > 1
|
||||
|
||||
final_file = Paperclip::Transcoder.make(file, options, attachment)
|
||||
|
||||
attachment.instance.file_file_name = 'media.webm'
|
||||
attachment.instance.file_content_type = 'video/webm'
|
||||
attachment.instance.type = MediaAttachment.types[:gifv]
|
||||
|
||||
final_file
|
||||
end
|
||||
end
|
||||
end
|
Reference in New Issue