From f11ad4023c542f0b9012f60c75b0b82d7dc77e93 Mon Sep 17 00:00:00 2001 From: Izalia Mae Date: Thu, 11 Apr 2019 07:23:31 -0400 Subject: add env var for max length of audio uploads --- lib/paperclip/audio_transcoder.rb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'lib') diff --git a/lib/paperclip/audio_transcoder.rb b/lib/paperclip/audio_transcoder.rb index 631ccb0be..323ec7bfe 100644 --- a/lib/paperclip/audio_transcoder.rb +++ b/lib/paperclip/audio_transcoder.rb @@ -3,10 +3,12 @@ module Paperclip class AudioTranscoder < Paperclip::Processor def make + max_aud_len = (ENV['MAX_AUDIO_LENGTH'] || 60.0).to_f + meta = ::Av.cli.identify(@file.path) # {:length=>"0:00:02.14", :duration=>2.14, :audio_encode=>"mp3", :audio_bitrate=>"44100 Hz", :audio_channels=>"mono"} - if meta[:duration] > 60.0 - raise Mastodon::ValidationError, "Audio uploads must be less than 60 seconds in length." + if meta[:duration] > max_aud_len + raise Mastodon::ValidationError, "Audio uploads must be less than #{max_aud_len} seconds in length." end final_file = Paperclip::Transcoder.make(file, options, attachment) -- cgit From 3e6c7f3617b99db616142ebcd9f78094d4c6fca3 Mon Sep 17 00:00:00 2001 From: Sho Kusano Date: Sun, 21 Apr 2019 11:41:34 +0900 Subject: Configrationable repository url (#10600) * config: Add GITHUB_REPOSITORY for repository name * config: Add SOURCE_BASE_URL for repository url * Show source_url and repository name on getting started --- app/javascript/mastodon/features/getting_started/index.js | 4 ++-- app/javascript/mastodon/initial_state.js | 2 ++ app/serializers/initial_state_serializer.rb | 2 ++ lib/mastodon/version.rb | 4 ++-- 4 files changed, 8 insertions(+), 4 deletions(-) (limited to 'lib') diff --git a/app/javascript/mastodon/features/getting_started/index.js b/app/javascript/mastodon/features/getting_started/index.js index e1f84de27..77c27ac6b 100644 --- a/app/javascript/mastodon/features/getting_started/index.js +++ b/app/javascript/mastodon/features/getting_started/index.js @@ -7,7 +7,7 @@ import { connect } from 'react-redux'; import PropTypes from 'prop-types'; import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePureComponent from 'react-immutable-pure-component'; -import { me, invitesEnabled, version, profile_directory } from '../../initial_state'; +import { me, invitesEnabled, version, profile_directory, repository, source_url } from '../../initial_state'; import { fetchFollowRequests } from '../../actions/accounts'; import { List as ImmutableList } from 'immutable'; import { Link } from 'react-router-dom'; @@ -172,7 +172,7 @@ class GettingStarted extends ImmutablePureComponent { tootsuite/mastodon (v{version}) }} + values={{ github: {repository} (v{version}) }} />

diff --git a/app/javascript/mastodon/initial_state.js b/app/javascript/mastodon/initial_state.js index 8c2e9d2de..74bcfee58 100644 --- a/app/javascript/mastodon/initial_state.js +++ b/app/javascript/mastodon/initial_state.js @@ -13,6 +13,8 @@ export const deleteModal = getMeta('delete_modal'); export const me = getMeta('me'); export const searchEnabled = getMeta('search_enabled'); export const invitesEnabled = getMeta('invites_enabled'); +export const repository = getMeta('repository'); +export const source_url = getMeta('source_url'); export const version = getMeta('version'); export const mascot = getMeta('mascot'); export const profile_directory = getMeta('profile_directory'); diff --git a/app/serializers/initial_state_serializer.rb b/app/serializers/initial_state_serializer.rb index a7a3d770c..0c9fc625f 100644 --- a/app/serializers/initial_state_serializer.rb +++ b/app/serializers/initial_state_serializer.rb @@ -14,6 +14,8 @@ class InitialStateSerializer < ActiveModel::Serializer domain: Rails.configuration.x.local_domain, admin: object.admin&.id&.to_s, search_enabled: Chewy.enabled?, + repository: Mastodon::Version.repository, + source_url: Mastodon::Version.source_url, version: Mastodon::Version.to_s, invites_enabled: Setting.min_invite_role == 'user', mascot: instance_presenter.mascot&.file&.url, diff --git a/lib/mastodon/version.rb b/lib/mastodon/version.rb index b53205ee4..a656031b1 100644 --- a/lib/mastodon/version.rb +++ b/lib/mastodon/version.rb @@ -33,11 +33,11 @@ module Mastodon end def repository - 'tootsuite/mastodon' + ENV.fetch('GITHUB_REPOSITORY') { 'tootsuite/mastodon' } end def source_base_url - "https://github.com/#{repository}" + ENV.fetch('SOURCE_BASE_URL') { "https://github.com/#{repository}" } end # specify git tag or commit hash here -- cgit From fba96c808d25d2fc35ec63ee6745a1e55a95d707 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Sat, 27 Apr 2019 03:24:09 +0200 Subject: Add blurhash (#10630) * Add blurhash * Use fallback color for spoiler when blurhash missing * Federate the blurhash and accept it as long as it's at most 5x5 * Display unknown media attachments as blurhash placeholders * Improve style of embed actions and spoiler button * Change blurhash resolution from 3x3 to 4x4 * Improve dependency definitions * Fix code style issues --- Gemfile | 1 + Gemfile.lock | 3 + .../mastodon/components/media_gallery.js | 96 ++++++++++++++++------ app/javascript/mastodon/components/status.js | 3 +- .../features/report/components/status_check_box.js | 1 + .../features/status/components/detailed_status.js | 6 +- .../mastodon/features/ui/components/media_modal.js | 1 + .../mastodon/features/ui/components/video_modal.js | 1 + app/javascript/mastodon/features/video/index.js | 49 +++++++++-- app/javascript/styles/mastodon/components.scss | 70 +++++++++++++--- app/lib/activitypub/activity/create.rb | 7 +- app/lib/activitypub/adapter.rb | 1 + app/models/media_attachment.rb | 15 +++- app/serializers/activitypub/note_serializer.rb | 4 +- .../rest/media_attachment_serializer.rb | 2 +- .../stream_entries/_detailed_status.html.haml | 2 +- app/views/stream_entries/_simple_status.html.haml | 2 +- ...0420025523_add_blurhash_to_media_attachments.rb | 5 ++ db/schema.rb | 3 +- lib/paperclip/blurhash_transcoder.rb | 16 ++++ package.json | 1 + yarn.lock | 5 ++ 22 files changed, 234 insertions(+), 60 deletions(-) create mode 100644 db/migrate/20190420025523_add_blurhash_to_media_attachments.rb create mode 100644 lib/paperclip/blurhash_transcoder.rb (limited to 'lib') diff --git a/Gemfile b/Gemfile index 6fe97412b..fa8478d89 100644 --- a/Gemfile +++ b/Gemfile @@ -21,6 +21,7 @@ gem 'fog-openstack', '~> 0.3', require: false gem 'paperclip', '~> 6.0' gem 'paperclip-av-transcoder', '~> 0.6' gem 'streamio-ffmpeg', '~> 3.0' +gem 'blurhash', '~> 0.1' gem 'active_model_serializers', '~> 0.10' gem 'addressable', '~> 2.6' diff --git a/Gemfile.lock b/Gemfile.lock index 66fc8d1f4..0148cb5ea 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -99,6 +99,8 @@ GEM rack (>= 0.9.0) binding_of_caller (0.8.0) debug_inspector (>= 0.0.1) + blurhash (0.1.2) + ffi (~> 1.10.0) bootsnap (1.4.4) msgpack (~> 1.0) brakeman (4.5.0) @@ -661,6 +663,7 @@ DEPENDENCIES aws-sdk-s3 (~> 1.36) better_errors (~> 2.5) binding_of_caller (~> 0.7) + blurhash (~> 0.1) bootsnap (~> 1.4) brakeman (~> 4.5) browser diff --git a/app/javascript/mastodon/components/media_gallery.js b/app/javascript/mastodon/components/media_gallery.js index a2bc95255..f548296d0 100644 --- a/app/javascript/mastodon/components/media_gallery.js +++ b/app/javascript/mastodon/components/media_gallery.js @@ -7,6 +7,7 @@ import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; import { isIOS } from '../is_mobile'; import classNames from 'classnames'; import { autoPlayGif, displayMedia } from '../initial_state'; +import { decode } from 'blurhash'; const messages = defineMessages({ toggle_visible: { id: 'media_gallery.toggle_visible', defaultMessage: 'Toggle visibility' }, @@ -21,6 +22,7 @@ class Item extends React.PureComponent { size: PropTypes.number.isRequired, onClick: PropTypes.func.isRequired, displayWidth: PropTypes.number, + visible: PropTypes.bool.isRequired, }; static defaultProps = { @@ -29,6 +31,10 @@ class Item extends React.PureComponent { size: 1, }; + state = { + loaded: false, + }; + handleMouseEnter = (e) => { if (this.hoverToPlay()) { e.target.play(); @@ -62,8 +68,40 @@ class Item extends React.PureComponent { e.stopPropagation(); } + componentDidMount () { + if (this.props.attachment.get('blurhash')) { + this._decode(); + } + } + + componentDidUpdate (prevProps) { + if (prevProps.attachment.get('blurhash') !== this.props.attachment.get('blurhash') && this.props.attachment.get('blurhash')) { + this._decode(); + } + } + + _decode () { + const hash = this.props.attachment.get('blurhash'); + const pixels = decode(hash, 32, 32); + + if (pixels) { + const ctx = this.canvas.getContext('2d'); + const imageData = new ImageData(pixels, 32, 32); + + ctx.putImageData(imageData, 0, 0); + } + } + + setCanvasRef = c => { + this.canvas = c; + } + + handleImageLoad = () => { + this.setState({ loaded: true }); + } + render () { - const { attachment, index, size, standalone, displayWidth } = this.props; + const { attachment, index, size, standalone, displayWidth, visible } = this.props; let width = 50; let height = 100; @@ -116,12 +154,20 @@ class Item extends React.PureComponent { let thumbnail = ''; - if (attachment.get('type') === 'image') { + if (attachment.get('type') === 'unknown') { + return ( +
+ + + +
+ ); + } else if (attachment.get('type') === 'image') { const previewUrl = attachment.get('preview_url'); const previewWidth = attachment.getIn(['meta', 'small', 'width']); - const originalUrl = attachment.get('url'); - const originalWidth = attachment.getIn(['meta', 'original', 'width']); + const originalUrl = attachment.get('url'); + const originalWidth = attachment.getIn(['meta', 'original', 'width']); const hasSize = typeof originalWidth === 'number' && typeof previewWidth === 'number'; @@ -147,6 +193,7 @@ class Item extends React.PureComponent { alt={attachment.get('description')} title={attachment.get('description')} style={{ objectPosition: `${x}% ${y}%` }} + onLoad={this.handleImageLoad} /> ); @@ -176,7 +223,8 @@ class Item extends React.PureComponent { return (
- {thumbnail} + + {visible && thumbnail}
); } @@ -225,6 +273,7 @@ class MediaGallery extends React.PureComponent { if (node /*&& this.isStandaloneEligible()*/) { // offsetWidth triggers a layout, so only calculate when we need to if (this.props.cacheWidth) this.props.cacheWidth(node.offsetWidth); + this.setState({ width: node.offsetWidth, }); @@ -242,7 +291,7 @@ class MediaGallery extends React.PureComponent { const width = this.state.width || defaultWidth; - let children; + let children, spoilerButton; const style = {}; @@ -256,35 +305,28 @@ class MediaGallery extends React.PureComponent { style.height = height; } - if (!visible) { - let warning; + const size = media.take(4).size; - if (sensitive) { - warning = ; - } else { - warning = ; - } + if (this.isStandaloneEligible()) { + children = ; + } else { + children = media.take(4).map((attachment, i) => ); + } - children = ( - ); - } else { - const size = media.take(4).size; - - if (this.isStandaloneEligible()) { - children = ; - } else { - children = media.take(4).map((attachment, i) => ); - } } return (
-
- +
+ {spoilerButton}
{children} diff --git a/app/javascript/mastodon/components/status.js b/app/javascript/mastodon/components/status.js index cea9a0c2e..95ca4a548 100644 --- a/app/javascript/mastodon/components/status.js +++ b/app/javascript/mastodon/components/status.js @@ -274,7 +274,7 @@ class Status extends ImmutablePureComponent { if (status.get('poll')) { media = ; } else if (status.get('media_attachments').size > 0) { - if (this.props.muted || status.get('media_attachments').some(item => item.get('type') === 'unknown')) { + if (this.props.muted) { media = ( ( ( ; } else if (status.get('media_attachments').size > 0) { - if (status.get('media_attachments').some(item => item.get('type') === 'unknown')) { - media = ; - } else if (status.getIn(['media_attachments', 0, 'type']) === 'video') { + if (status.getIn(['media_attachments', 0, 'type']) === 'video') { const video = status.getIn(['media_attachments', 0]); media = (