From 162adf61f268beac6e178f6f79f203b213055915 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Thu, 2 Jul 2020 16:27:35 +0200 Subject: [PATCH 1/7] Fix audio modals not using blurhash and poster (#14199) --- .../mastodon/features/ui/components/audio_modal.js | 5 +++-- .../mastodon/features/ui/components/focal_point_modal.js | 8 ++++++-- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/app/javascript/mastodon/features/ui/components/audio_modal.js b/app/javascript/mastodon/features/ui/components/audio_modal.js index 2300453d7..1d23925ca 100644 --- a/app/javascript/mastodon/features/ui/components/audio_modal.js +++ b/app/javascript/mastodon/features/ui/components/audio_modal.js @@ -59,8 +59,9 @@ export default class AudioModal extends ImmutablePureComponent { src={media.get('url')} alt={media.get('description')} duration={media.getIn(['meta', 'original', 'duration'], 0)} - height={135} - preload + height={150} + poster={media.get('preview_url') || status.getIn(['account', 'avatar_static'])} + blurhash={media.get('blurhash')} /> diff --git a/app/javascript/mastodon/features/ui/components/focal_point_modal.js b/app/javascript/mastodon/features/ui/components/focal_point_modal.js index 7d1509f71..06d298205 100644 --- a/app/javascript/mastodon/features/ui/components/focal_point_modal.js +++ b/app/javascript/mastodon/features/ui/components/focal_point_modal.js @@ -17,6 +17,7 @@ import CharacterCounter from 'mastodon/features/compose/components/character_cou import { length } from 'stringz'; import { Tesseract as fetchTesseract } from 'mastodon/features/ui/util/async-components'; import GIFV from 'mastodon/components/gifv'; +import { me } from 'mastodon/initial_state'; const messages = defineMessages({ close: { id: 'lightbox.close', defaultMessage: 'Close' }, @@ -26,6 +27,7 @@ const messages = defineMessages({ const mapStateToProps = (state, { id }) => ({ media: state.getIn(['compose', 'media_attachments']).find(item => item.get('id') === id), + account: state.getIn(['accounts', me]), }); const mapDispatchToProps = (dispatch, { id }) => ({ @@ -78,6 +80,7 @@ class FocalPointModal extends ImmutablePureComponent { static propTypes = { media: ImmutablePropTypes.map.isRequired, + account: ImmutablePropTypes.map.isRequired, onClose: PropTypes.func.isRequired, intl: PropTypes.object.isRequired, }; @@ -233,7 +236,7 @@ class FocalPointModal extends ImmutablePureComponent { } render () { - const { media, intl, onClose } = this.props; + const { media, intl, account, onClose } = this.props; const { x, y, dragging, description, dirty, detecting, progress } = this.state; const width = media.getIn(['meta', 'original', 'width']) || null; @@ -325,7 +328,8 @@ class FocalPointModal extends ImmutablePureComponent { src={media.get('url')} duration={media.getIn(['meta', 'original', 'duration'], 0)} height={150} - preload + poster={media.get('preview_url') || account.get('avatar_static')} + blurhash={media.get('blurhash')} editable /> )} From 534da9ba234dc9235bdf18fb31b37842c46305a6 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Fri, 3 Jul 2020 03:05:32 +0200 Subject: [PATCH 2/7] Fix audio uploads without embedded image (#14203) --- lib/paperclip/image_extractor.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/paperclip/image_extractor.rb b/lib/paperclip/image_extractor.rb index f5a54d1a5..aab675a06 100644 --- a/lib/paperclip/image_extractor.rb +++ b/lib/paperclip/image_extractor.rb @@ -43,7 +43,7 @@ module Paperclip begin cli.run - rescue Cocaine::ExitStatusError + rescue Cocaine::ExitStatusError, ::Av::CommandError dst.close(true) return nil end From a80fd8c79bfcf843fcb47f27389e755cc302caaf Mon Sep 17 00:00:00 2001 From: ThibG Date: Fri, 3 Jul 2020 03:06:08 +0200 Subject: [PATCH 3/7] Change the about.instance_actor_flash to be single-line (#14200) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Some translations of that string are single-line, which somehow seems to make Crowdin issue a blank newline at the end of those translations. This, in turns, leads to different results when running “i18n-tasks normalize” depending on the version of libyaml installed, making the CI fail if it runs a different version than whoever ran “i18n-tasks normalize”. Since there is no real reason for that source string to be multi-line (it is only displayed in HTML, without replacing newlines by
tags), attempt to fix Crowdin export by making the source string single-line. --- config/locales/en.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/config/locales/en.yml b/config/locales/en.yml index 2cae0a3e3..23bed812e 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -21,9 +21,7 @@ en: federation_hint_html: With an account on %{instance} you'll be able to follow people on any Mastodon server and beyond. get_apps: Try a mobile app hosted_on: Mastodon hosted on %{domain} - instance_actor_flash: | - This account is a virtual actor used to represent the server itself and not any individual user. - It is used for federation purposes and should not be blocked unless you want to block the whole instance, in which case you should use a domain block. + instance_actor_flash: This account is a virtual actor used to represent the server itself and not any individual user. It is used for federation purposes and should not be blocked unless you want to block the whole instance, in which case you should use a domain block. learn_more: Learn more privacy_policy: Privacy policy see_whats_happening: See what's happening From 6e399b9df99a57edbab8ce84e331cf959ea146b0 Mon Sep 17 00:00:00 2001 From: mayaeh Date: Fri, 3 Jul 2020 20:26:35 +0900 Subject: [PATCH 4/7] Fix-up #13749 (#14204) --- app/javascript/mastodon/locales/en.json | 30 ++++++++++++------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/app/javascript/mastodon/locales/en.json b/app/javascript/mastodon/locales/en.json index 49ce168f6..175320a66 100644 --- a/app/javascript/mastodon/locales/en.json +++ b/app/javascript/mastodon/locales/en.json @@ -109,7 +109,7 @@ "confirmations.block.confirm": "Block", "confirmations.block.message": "Are you sure you want to block {name}?", "confirmations.delete.confirm": "Delete", - "confirmations.delete.message": "Are you sure you want to delete this status?", + "confirmations.delete.message": "Are you sure you want to delete this toot?", "confirmations.delete_list.confirm": "Delete", "confirmations.delete_list.message": "Are you sure you want to permanently delete this list?", "confirmations.domain_block.confirm": "Block entire domain", @@ -120,7 +120,7 @@ "confirmations.mute.explanation": "This will hide posts from them and posts mentioning them, but it will still allow them to see your posts and follow you.", "confirmations.mute.message": "Are you sure you want to mute {name}?", "confirmations.redraft.confirm": "Delete & redraft", - "confirmations.redraft.message": "Are you sure you want to delete this status and re-draft it? Favourites and boosts will be lost, and replies to the original post will be orphaned.", + "confirmations.redraft.message": "Are you sure you want to delete this toot and re-draft it? Favourites and boosts will be lost, and replies to the original post will be orphaned.", "confirmations.reply.confirm": "Reply", "confirmations.reply.message": "Replying now will overwrite the message you are currently composing. Are you sure you want to proceed?", "confirmations.unfollow.confirm": "Unfollow", @@ -133,7 +133,7 @@ "directory.local": "From {domain} only", "directory.new_arrivals": "New arrivals", "directory.recently_active": "Recently active", - "embed.instructions": "Embed this status on your website by copying the code below.", + "embed.instructions": "Embed this toot on your website by copying the code below.", "embed.preview": "Here is what it will look like:", "emoji_button.activity": "Activity", "emoji_button.custom": "Custom", @@ -162,7 +162,7 @@ "empty_column.hashtag": "There is nothing in this hashtag yet.", "empty_column.home": "Your home timeline is empty! Visit {public} or use search to get started and meet other users.", "empty_column.home.public_timeline": "the public timeline", - "empty_column.list": "There is nothing in this list yet. When members of this list post new statuses, they will appear here.", + "empty_column.list": "There is nothing in this list yet. When members of this list post new toots, they will appear here.", "empty_column.lists": "You don't have any lists yet. When you create one, it will show up here.", "empty_column.mutes": "You haven't muted any users yet.", "empty_column.notifications": "You don't have any notifications yet. Interact with others to start the conversation.", @@ -219,12 +219,12 @@ "keyboard_shortcuts.back": "to navigate back", "keyboard_shortcuts.blocked": "to open blocked users list", "keyboard_shortcuts.boost": "to boost", - "keyboard_shortcuts.column": "to focus a status in one of the columns", + "keyboard_shortcuts.column": "to focus a toot in one of the columns", "keyboard_shortcuts.compose": "to focus the compose textarea", "keyboard_shortcuts.description": "Description", "keyboard_shortcuts.direct": "to open direct messages column", "keyboard_shortcuts.down": "to move down in the list", - "keyboard_shortcuts.enter": "to open status", + "keyboard_shortcuts.enter": "to open toot", "keyboard_shortcuts.favourite": "to favourite", "keyboard_shortcuts.favourites": "to open favourites list", "keyboard_shortcuts.federated": "to open federated timeline", @@ -265,7 +265,7 @@ "lists.subheading": "Your lists", "load_pending": "{count, plural, one {# new item} other {# new items}}", "loading_indicator.label": "Loading...", - "media_gallery.toggle_visible": "Hide media", + "media_gallery.toggle_visible": "Hide {number, plural, one {image} other {images}}", "missing_indicator.label": "Not found", "missing_indicator.sublabel": "This resource could not be found", "mute_modal.hide_notifications": "Hide notifications from this user?", @@ -292,13 +292,13 @@ "navigation_bar.preferences": "Preferences", "navigation_bar.public_timeline": "Federated timeline", "navigation_bar.security": "Security", - "notification.favourite": "{name} favourited your status", + "notification.favourite": "{name} favourited your toot", "notification.follow": "{name} followed you", "notification.follow_request": "{name} has requested to follow you", "notification.mention": "{name} mentioned you", "notification.own_poll": "Your poll has ended", "notification.poll": "A poll you have voted in has ended", - "notification.reblog": "{name} boosted your status", + "notification.reblog": "{name} boosted your toot", "notifications.clear": "Clear notifications", "notifications.clear_confirmation": "Are you sure you want to permanently clear all your notifications?", "notifications.column_settings.alert": "Desktop notifications", @@ -329,7 +329,7 @@ "poll.voted": "You voted for this answer", "poll_button.add_poll": "Add a poll", "poll_button.remove_poll": "Remove poll", - "privacy.change": "Adjust status privacy", + "privacy.change": "Adjust toot privacy", "privacy.direct.long": "Visible for mentioned users only", "privacy.direct.short": "Direct", "privacy.private.long": "Visible for followers only", @@ -356,9 +356,9 @@ "report.target": "Reporting {target}", "search.placeholder": "Search", "search_popout.search_format": "Advanced search format", - "search_popout.tips.full_text": "Simple text returns statuses you have written, favourited, boosted, or have been mentioned in, as well as matching usernames, display names, and hashtags.", + "search_popout.tips.full_text": "Simple text returns toots you have written, favourited, boosted, or have been mentioned in, as well as matching usernames, display names, and hashtags.", "search_popout.tips.hashtag": "hashtag", - "search_popout.tips.status": "status", + "search_popout.tips.status": "toot", "search_popout.tips.text": "Simple text returns matching display names, usernames and hashtags", "search_popout.tips.user": "user", "search_results.accounts": "People", @@ -367,7 +367,7 @@ "search_results.statuses_fts_disabled": "Searching toots by their content is not enabled on this Mastodon server.", "search_results.total": "{count, number} {count, plural, one {result} other {results}}", "status.admin_account": "Open moderation interface for @{name}", - "status.admin_status": "Open this status in the moderation interface", + "status.admin_status": "Open this toot in the moderation interface", "status.block": "Block @{name}", "status.bookmark": "Bookmark", "status.cancel_reblog_private": "Unboost", @@ -385,7 +385,7 @@ "status.more": "More", "status.mute": "Mute @{name}", "status.mute_conversation": "Mute conversation", - "status.open": "Expand this status", + "status.open": "Expand this toot", "status.pin": "Pin on profile", "status.pinned": "Pinned toot", "status.read_more": "Read more", @@ -428,7 +428,7 @@ "trends.trending_now": "Trending now", "ui.beforeunload": "Your draft will be lost if you leave Mastodon.", "upload_area.title": "Drag & drop to upload", - "upload_button.label": "Add media ({formats})", + "upload_button.label": "Add images, a video or an audio file", "upload_error.limit": "File upload limit exceeded.", "upload_error.poll": "File upload not allowed with polls.", "upload_form.audio_description": "Describe for people with hearing loss", From 231802725c2de663c73682f9ddfb5e6de6a2039b Mon Sep 17 00:00:00 2001 From: Yamagishi Kazutoshi Date: Fri, 3 Jul 2020 20:27:02 +0900 Subject: [PATCH 5/7] Run `bundle exec i18n-tasks normalize` (#14205) --- config/locales/bn.yml | 2 +- config/locales/eu.yml | 2 +- config/locales/gl.yml | 2 +- config/locales/hu.yml | 2 +- config/locales/id.yml | 2 +- config/locales/ja.yml | 2 +- config/locales/nl.yml | 2 +- config/locales/no.yml | 2 +- config/locales/sv.yml | 2 +- config/locales/uk.yml | 2 +- config/locales/vi.yml | 2 +- config/locales/zh-CN.yml | 2 +- 12 files changed, 12 insertions(+), 12 deletions(-) diff --git a/config/locales/bn.yml b/config/locales/bn.yml index 928d5426f..ad613f721 100644 --- a/config/locales/bn.yml +++ b/config/locales/bn.yml @@ -23,7 +23,7 @@ bn: hosted_on: এই মাস্টাডনটি আছে %{domain} এ instance_actor_flash: 'এই অ্যাকাউন্টটি ভার্চুয়াল এক্টর যা নিজে কোনও সার্ভারের প্রতিনিধিত্ব করতে ব্যবহৃত হয় এবং কোনও পৃথক ব্যবহারকারী নয়। এটি ফেডারেশনের উদ্দেশ্যে ব্যবহৃত হয় এবং আপনি যদি পুরো ইনস্ট্যান্স ব্লক করতে না চান তবে অবরুদ্ধ করা উচিত নয়, সেক্ষেত্রে আপনার ডোমেন ব্লক ব্যবহার করা উচিত। - ' +' learn_more: বিস্তারিত জানুন privacy_policy: গোপনীয়তা নীতি see_whats_happening: কী কী হচ্ছে দেখুন diff --git a/config/locales/eu.yml b/config/locales/eu.yml index 3e92dcd36..1a654ec9b 100644 --- a/config/locales/eu.yml +++ b/config/locales/eu.yml @@ -23,7 +23,7 @@ eu: hosted_on: Mastodon %{domain} domeinuan ostatatua instance_actor_flash: 'Kontu hau zerbitzaria bera adierazten duen aktore birtual bat da, ez norbanako bat. Federaziorako erabiltzen da eta ez zenuke blokeatu behar instantzia osoa blokeatu nahi ez baduzu, kasu horretan domeinua blokeatzea egokia litzateke. - ' +' learn_more: Ikasi gehiago privacy_policy: Pribatutasun politika see_whats_happening: Ikusi zer gertatzen ari den diff --git a/config/locales/gl.yml b/config/locales/gl.yml index 77c5ec911..d2ad1bc45 100644 --- a/config/locales/gl.yml +++ b/config/locales/gl.yml @@ -23,7 +23,7 @@ gl: hosted_on: Mastodon aloxado en %{domain} instance_actor_flash: 'Esta conta é un actor virtual utilizado para representar ao servidor e non a unha usuaria individual. Utilízase para propósitos de federación e non debería estar bloqueada a menos que queiras bloquear a toda a instancia, en tal caso deberías utilizar o bloqueo do dominio. - ' +' learn_more: Saber máis privacy_policy: Política de privacidade see_whats_happening: Ver o que está a acontecer diff --git a/config/locales/hu.yml b/config/locales/hu.yml index 1f5d2a738..32c28c3e5 100644 --- a/config/locales/hu.yml +++ b/config/locales/hu.yml @@ -23,7 +23,7 @@ hu: hosted_on: "%{domain} Mastodon szerver" instance_actor_flash: 'Ez a fiók egy virtuális szereplő, mely magát a szervert reprezentálja, nem egy felhasználót. Ez a föderáció támogatására készült, ezért nem szabad blokkolni, hacsak egy teljes szervert nem akarsz kitiltani, amire persze a domain blokkolása jobb megoldás. - ' +' learn_more: Tudj meg többet privacy_policy: Adatvédelmi szabályzat see_whats_happening: Nézd, mi történik diff --git a/config/locales/id.yml b/config/locales/id.yml index 1c648e28f..d55abce59 100644 --- a/config/locales/id.yml +++ b/config/locales/id.yml @@ -23,7 +23,7 @@ id: hosted_on: Mastodon dihosting di %{domain} instance_actor_flash: 'Akun ini adalah aktor virtual yang dipakai untuk merepresentasikan server, bukan pengguna individu. Ini dipakai untuk tujuan federasi dan jangan diblokir kecuali Anda ingin memblokir seluruh instansi, yang seharusnya Anda pakai blokir domain. - ' +' learn_more: Pelajari selengkapnya privacy_policy: Kebijakan Privasi see_whats_happening: Lihat apa yang sedang terjadi diff --git a/config/locales/ja.yml b/config/locales/ja.yml index 77011e2b4..e3b6bc234 100644 --- a/config/locales/ja.yml +++ b/config/locales/ja.yml @@ -23,7 +23,7 @@ ja: hosted_on: Mastodon hosted on %{domain} instance_actor_flash: 'このアカウントはサーバーそのものを示す仮想的なもので、特定のユーザーを示すものではありません。これはサーバーの連合のために使用されます。サーバー全体をブロックするときは、このアカウントをブロックせずに、ドメインブロックを使用してください。 - ' +' learn_more: もっと詳しく privacy_policy: プライバシーポリシー see_whats_happening: やりとりを見てみる diff --git a/config/locales/nl.yml b/config/locales/nl.yml index 6df792495..7ab26c4ba 100644 --- a/config/locales/nl.yml +++ b/config/locales/nl.yml @@ -23,7 +23,7 @@ nl: hosted_on: Mastodon op %{domain} instance_actor_flash: 'Dit account is een virtuel actor die wordt gebruikt om de server zelf te vertegenwoordigen en is geen individuele gebruiker. Het wordt voor federatiedoeleinden gebruikt en moet niet worden geblokkeerd, tenzij je de hele server wil blokkeren. In zo''n geval dien je echter een domeinblokkade te gebruiken. - ' +' learn_more: Meer leren privacy_policy: Privacybeleid see_whats_happening: Kijk wat er aan de hand is diff --git a/config/locales/no.yml b/config/locales/no.yml index 13834b428..ddd404848 100644 --- a/config/locales/no.yml +++ b/config/locales/no.yml @@ -23,7 +23,7 @@ hosted_on: Mastodon driftet på %{domain} instance_actor_flash: 'Denne brukeren er en virtuell aktør brukt til å representere selve serveren og ingen individuell bruker. Det brukes til foreningsformål og bør ikke blokkeres med mindre du vil blokkere hele instansen, hvor domeneblokkering bør brukes i stedet. - ' +' learn_more: Lær mer privacy_policy: Privatlivsretningslinjer see_whats_happening: Se hva som skjer diff --git a/config/locales/sv.yml b/config/locales/sv.yml index 43c4a2a7a..c2de32f97 100644 --- a/config/locales/sv.yml +++ b/config/locales/sv.yml @@ -23,7 +23,7 @@ sv: hosted_on: Mastodon-värd på %{domain} instance_actor_flash: 'Detta konto är en virtuell agent som används för att representera servern själv och inte någon individuell användare. Det används av sammanslutningsskäl och ska inte blockeras såvitt du inte vill blockera hela instansen, och för detta fall ska domänblockering användas. - ' +' learn_more: Lär dig mer privacy_policy: Integritetspolicy see_whats_happening: Se vad som händer diff --git a/config/locales/uk.yml b/config/locales/uk.yml index 799922272..6ff21b068 100644 --- a/config/locales/uk.yml +++ b/config/locales/uk.yml @@ -23,7 +23,7 @@ uk: hosted_on: Mastodon розміщено на %{domain} instance_actor_flash: 'Цей обліковий запис є віртуальною особою, яка використовується для представлення самого сервера, а не певного користувача. Він використовується для потреб федерації і не повинен бути заблокований, якщо тільки ви не хочете заблокувати весь сервер, у цьому випадку ви повинні скористатися блокуванням домену. - ' +' learn_more: Дізнатися більше privacy_policy: Політика приватності see_whats_happening: Погляньте, що відбувається diff --git a/config/locales/vi.yml b/config/locales/vi.yml index d20902459..853a1ee13 100644 --- a/config/locales/vi.yml +++ b/config/locales/vi.yml @@ -23,7 +23,7 @@ vi: hosted_on: "%{domain} vận hành nhờ Mastodon" instance_actor_flash: 'Tài khoản này là một tác nhân ảo được sử dụng để đại diện cho chính máy chủ chứ không phải bất kỳ người dùng cá nhân nào. Nó được sử dụng cho mục đích liên kết và không nên bị chặn trừ khi bạn muốn chặn toàn bộ máy chủ. - ' +' learn_more: Tìm hiểu thêm privacy_policy: Chính sách bảo mật see_whats_happening: Xem những gì đang xảy ra diff --git a/config/locales/zh-CN.yml b/config/locales/zh-CN.yml index cb55e5bf4..f93f51af3 100644 --- a/config/locales/zh-CN.yml +++ b/config/locales/zh-CN.yml @@ -23,7 +23,7 @@ zh-CN: hosted_on: 一个在 %{domain} 上运行的 Mastodon 实例 instance_actor_flash: '这个账号是个虚拟帐号,不代表任何用户,只用来代表服务器本身。它用于和其它服务器互通,所以不应该被封禁,除非你想封禁整个实例。但是想封禁整个实例的时候,你应该用域名封禁。 - ' +' learn_more: 了解详情 privacy_policy: 隐私政策 see_whats_happening: 看一看现在在发生什么 From 2f2ab48b750ab1fb62a9b7a3ea1c8cc52f3c7366 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Fri, 3 Jul 2020 21:01:39 +0200 Subject: [PATCH 6/7] Add back a cleaner and leaner .env.production.sample (#14206) --- .env.production.sample | 60 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 .env.production.sample diff --git a/.env.production.sample b/.env.production.sample new file mode 100644 index 000000000..558ed1880 --- /dev/null +++ b/.env.production.sample @@ -0,0 +1,60 @@ +# This is a sample configuration file. You can generate your configuration +# with the `rake mastodon:setup` interactive setup wizard, but to customize +# your setup even further, you'll need to edit it manually. This sample does +# not demonstrate all available configuration options. Please look at +# https://docs.joinmastodon/admin/config/ for the full documentation. + +# Federation +# ---------- +# This identifies your server and cannot be changed safely later +# ---------- +LOCAL_DOMAIN=example.com + +# Redis +# ----- +REDIS_HOST=localhost +REDIS_PORT=6379 + +# PostgreSQL +# ---------- +DB_HOST=/var/run/postgresql +DB_USER=mastodon +DB_NAME=mastodon_production +DB_PASS= +DB_PORT=5432 + +# ElasticSearch (optional) +# ------------------------ +ES_ENABLED=true +ES_HOST=localhost +ES_PORT=9200 + +# Secrets +# ------- +# Make sure to use `rake secret` to generate secrets +# ------- +SECRET_KEY_BASE= +OTP_SECRET= + +# Web Push +# -------- +# Generate with `rake mastodon:webpush:generate_vapid_key` +# -------- +VAPID_PRIVATE_KEY= +VAPID_PUBLIC_KEY= + +# Sending mail +# ------------ +SMTP_SERVER=smtp.mailgun.org +SMTP_PORT=587 +SMTP_LOGIN= +SMTP_PASSWORD= +SMTP_FROM_ADDRESS=notificatons@example.com + +# File storage (optional) +# ----------------------- +S3_ENABLED=true +S3_BUCKET=files.example.com +AWS_ACCESS_KEY_ID= +AWS_SECRET_ACCESS_KEY= +S3_ALIAS_HOST=files.example.com From 99f3a5554074d9a12619797c474b3de4c6085f02 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Sun, 5 Jul 2020 18:28:25 +0200 Subject: [PATCH 7/7] Add color extraction for audio thumbnails (#14209) --- Gemfile | 1 + Gemfile.lock | 2 + app/javascript/mastodon/components/status.js | 4 +- .../mastodon/features/audio/index.js | 238 +++--------------- .../status/components/detailed_status.js | 4 +- .../features/ui/components/audio_modal.js | 4 +- .../ui/components/focal_point_modal.js | 4 +- .../styles/mastodon/components.scss | 43 ++-- app/models/media_attachment.rb | 13 +- app/views/media/player.html.haml | 2 +- app/views/statuses/_detailed_status.html.haml | 2 +- app/views/statuses/_simple_status.html.haml | 2 +- app/workers/post_process_media_worker.rb | 2 +- config/application.rb | 1 + lib/paperclip/color_extractor.rb | 189 ++++++++++++++ lib/paperclip/transcoder_extensions.rb | 14 ++ 16 files changed, 283 insertions(+), 242 deletions(-) create mode 100644 lib/paperclip/color_extractor.rb create mode 100644 lib/paperclip/transcoder_extensions.rb diff --git a/Gemfile b/Gemfile index d9415d874..04eb41bdc 100644 --- a/Gemfile +++ b/Gemfile @@ -48,6 +48,7 @@ gem 'omniauth-cas', '~> 1.1' gem 'omniauth-saml', '~> 1.10' gem 'omniauth', '~> 1.9' +gem 'color_diff', '~> 0.1' gem 'discard', '~> 1.2' gem 'doorkeeper', '~> 5.4' gem 'ed25519', '~> 1.2' diff --git a/Gemfile.lock b/Gemfile.lock index fcea81002..bc7106e30 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -165,6 +165,7 @@ GEM cocaine (0.5.8) climate_control (>= 0.0.3, < 1.0) coderay (1.1.3) + color_diff (0.1) concurrent-ruby (1.1.6) connection_pool (2.2.3) crack (0.4.3) @@ -689,6 +690,7 @@ DEPENDENCIES chewy (~> 5.1) cld3 (~> 3.3.0) climate_control (~> 0.2) + color_diff (~> 0.1) concurrent-ruby connection_pool devise (~> 4.7) diff --git a/app/javascript/mastodon/components/status.js b/app/javascript/mastodon/components/status.js index 827b69500..f9f6736e6 100644 --- a/app/javascript/mastodon/components/status.js +++ b/app/javascript/mastodon/components/status.js @@ -353,7 +353,9 @@ class Status extends ImmutablePureComponent { src={attachment.get('url')} alt={attachment.get('description')} poster={attachment.get('preview_url') || status.getIn(['account', 'avatar_static'])} - blurhash={attachment.get('blurhash')} + backgroundColor={attachment.getIn(['meta', 'colors', 'background'])} + foregroundColor={attachment.getIn(['meta', 'colors', 'foreground'])} + accentColor={attachment.getIn(['meta', 'colors', 'accent'])} duration={attachment.getIn(['meta', 'original', 'duration'], 0)} width={this.props.cachedMediaWidth} height={110} diff --git a/app/javascript/mastodon/features/audio/index.js b/app/javascript/mastodon/features/audio/index.js index 99926e52a..686709ac3 100644 --- a/app/javascript/mastodon/features/audio/index.js +++ b/app/javascript/mastodon/features/audio/index.js @@ -5,131 +5,12 @@ import { formatTime } from 'mastodon/features/video'; import Icon from 'mastodon/components/icon'; import classNames from 'classnames'; import { throttle } from 'lodash'; -import { encode, decode } from 'blurhash'; import { getPointerPosition, fileNameFromURL } from 'mastodon/features/video'; import { debounce } from 'lodash'; -const digitCharacters = [ - '0', - '1', - '2', - '3', - '4', - '5', - '6', - '7', - '8', - '9', - 'A', - 'B', - 'C', - 'D', - 'E', - 'F', - 'G', - 'H', - 'I', - 'J', - 'K', - 'L', - 'M', - 'N', - 'O', - 'P', - 'Q', - 'R', - 'S', - 'T', - 'U', - 'V', - 'W', - 'X', - 'Y', - 'Z', - 'a', - 'b', - 'c', - 'd', - 'e', - 'f', - 'g', - 'h', - 'i', - 'j', - 'k', - 'l', - 'm', - 'n', - 'o', - 'p', - 'q', - 'r', - 's', - 't', - 'u', - 'v', - 'w', - 'x', - 'y', - 'z', - '#', - '$', - '%', - '*', - '+', - ',', - '-', - '.', - ':', - ';', - '=', - '?', - '@', - '[', - ']', - '^', - '_', - '{', - '|', - '}', - '~', -]; - -const decode83 = (str) => { - let value = 0; - let c, digit; - - for (let i = 0; i < str.length; i++) { - c = str[i]; - digit = digitCharacters.indexOf(c); - value = value * 83 + digit; - } - - return value; -}; - -const decodeRGB = int => ({ - r: Math.max(0, (int >> 16)), - g: Math.max(0, (int >> 8) & 255), - b: Math.max(0, (int & 255)), -}); - -const luma = ({ r, g, b }) => 0.2126 * r + 0.7152 * g + 0.0722 * b; - -const adjustColor = ({ r, g, b }, lumaThreshold = 100) => { - let delta; - - if (luma({ r, g, b }) >= lumaThreshold) { - delta = -80; - } else { - delta = 80; - } - - return { - r: r + delta, - g: g + delta, - b: b + delta, - }; +const hex2rgba = (hex, alpha = 1) => { + const [r, g, b] = hex.match(/\w\w/g).map(x => parseInt(x, 16)); + return `rgba(${r}, ${g}, ${b}, ${alpha})`; }; const messages = defineMessages({ @@ -157,7 +38,9 @@ class Audio extends React.PureComponent { fullscreen: PropTypes.bool, intl: PropTypes.object.isRequired, cacheWidth: PropTypes.func, - blurhash: PropTypes.string, + backgroundColor: PropTypes.string, + foregroundColor: PropTypes.string, + accentColor: PropTypes.string, }; state = { @@ -169,7 +52,6 @@ class Audio extends React.PureComponent { muted: false, volume: 0.5, dragging: false, - color: { r: 255, g: 255, b: 255 }, }; setPlayerRef = c => { @@ -207,10 +89,6 @@ class Audio extends React.PureComponent { } } - setBlurhashCanvasRef = c => { - this.blurhashCanvas = c; - } - setCanvasRef = c => { this.canvas = c; @@ -222,41 +100,13 @@ class Audio extends React.PureComponent { componentDidMount () { window.addEventListener('scroll', this.handleScroll); window.addEventListener('resize', this.handleResize, { passive: true }); - - if (!this.props.blurhash) { - const img = new Image(); - img.crossOrigin = 'anonymous'; - img.onload = () => this.handlePosterLoad(img); - img.src = this.props.poster; - } else { - this._setColorScheme(); - this._decodeBlurhash(); - } } componentDidUpdate (prevProps, prevState) { - if (prevProps.poster !== this.props.poster && !this.props.blurhash) { - const img = new Image(); - img.crossOrigin = 'anonymous'; - img.onload = () => this.handlePosterLoad(img); - img.src = this.props.poster; - } - - if (prevState.blurhash !== this.state.blurhash || prevProps.blurhash !== this.props.blurhash) { - this._setColorScheme(); - this._decodeBlurhash(); + if (prevProps.src !== this.props.src || this.state.width !== prevState.width || this.state.height !== prevState.height) { + this._clear(); + this._draw(); } - - this._clear(); - this._draw(); - } - - _decodeBlurhash () { - const context = this.blurhashCanvas.getContext('2d'); - const pixels = decode(this.props.blurhash || this.state.blurhash, 32, 32); - const outputImageData = new ImageData(pixels, 32, 32); - - context.putImageData(outputImageData, 0, 0); } componentWillUnmount () { @@ -425,31 +275,6 @@ class Audio extends React.PureComponent { this.analyser = analyser; } - handlePosterLoad = image => { - const canvas = document.createElement('canvas'); - const context = canvas.getContext('2d'); - - canvas.width = image.width; - canvas.height = image.height; - - context.drawImage(image, 0, 0); - - const inputImageData = context.getImageData(0, 0, image.width, image.height); - const blurhash = encode(inputImageData.data, image.width, image.height, 4, 4); - - this.setState({ blurhash }); - } - - _setColorScheme () { - const blurhash = this.props.blurhash || this.state.blurhash; - const averageColor = decodeRGB(decode83(blurhash.slice(2, 6))); - - this.setState({ - color: adjustColor(averageColor), - darkText: luma(averageColor) >= 165, - }); - } - handleDownload = () => { fetch(this.props.src).then(res => res.blob()).then(blob => { const element = document.createElement('a'); @@ -609,8 +434,8 @@ class Audio extends React.PureComponent { const gradient = this.canvasContext.createLinearGradient(dx1, dy1, dx2, dy2); - const mainColor = `rgb(${this.state.color.r}, ${this.state.color.g}, ${this.state.color.b})`; - const lastColor = `rgba(${this.state.color.r}, ${this.state.color.g}, ${this.state.color.b}, 0)`; + const mainColor = this._getAccentColor(); + const lastColor = hex2rgba(mainColor, 0); gradient.addColorStop(0, mainColor); gradient.addColorStop(0.6, mainColor); @@ -632,17 +457,25 @@ class Audio extends React.PureComponent { return Math.floor(this._getRadius() + (PADDING * this._getScaleCoefficient())); } - _getColor () { - return `rgb(${this.state.color.r}, ${this.state.color.g}, ${this.state.color.b})`; + _getAccentColor () { + return this.props.accentColor || '#ffffff'; + } + + _getBackgroundColor () { + return this.props.backgroundColor || '#000000'; + } + + _getForegroundColor () { + return this.props.foregroundColor || '#ffffff'; } render () { const { src, intl, alt, editable } = this.props; - const { paused, muted, volume, currentTime, duration, buffer, darkText, dragging } = this.state; + const { paused, muted, volume, currentTime, duration, buffer, dragging } = this.state; const progress = (currentTime / duration) * 100; return ( -
+