Merge branch 'develop'

This commit is contained in:
Eliot Berriot 2019-01-29 10:06:04 +01:00
commit 7df97263e5
No known key found for this signature in database
GPG Key ID: DD6965E2476E5C27
25 changed files with 157 additions and 40 deletions

View File

@ -67,7 +67,7 @@ Instance-level moderation tools
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
This release includes a first set of moderation tools that will give more control This release includes a first set of moderation tools that will give more control
to admins about the way their instance federate with other instance and accounts on the network. to admins about the way their instance federates with other instance and accounts on the network.
Using these tools, it's now possible to: Using these tools, it's now possible to:
- Browse known accounts and domains, and associated data (storage size, software version, etc.) - Browse known accounts and domains, and associated data (storage size, software version, etc.)
@ -75,7 +75,7 @@ Using these tools, it's now possible to:
- Block or partially restrict interactions with any account or domain - Block or partially restrict interactions with any account or domain
All those features are usable using a brand new "moderation" permission, meaning All those features are usable using a brand new "moderation" permission, meaning
you can appoints one or nultiple moderators to help with this task. you can appoint one or multiple moderators to help with this task.
I'd like to thank all Mastodon contributors, because some of the these tools are heavily I'd like to thank all Mastodon contributors, because some of the these tools are heavily
inspired from what's being done in Mastodon. Thank you so much! inspired from what's being done in Mastodon. Thank you so much!
@ -84,7 +84,7 @@ inspired from what's being done in Mastodon. Thank you so much!
Iframe widget to embed public tracks and albums [manual action required] Iframe widget to embed public tracks and albums [manual action required]
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Funkwhale now support embedding a lightweight audio player on external websites Funkwhale now supports embedding a lightweight audio player on external websites
for album and tracks that are available in public libraries. Important pages, for album and tracks that are available in public libraries. Important pages,
such as artist, album and track pages also include OpenGraph tags that will such as artist, album and track pages also include OpenGraph tags that will
enable previews on compatible apps (like sharing a Funkwhale track link on Mastodon enable previews on compatible apps (like sharing a Funkwhale track link on Mastodon
@ -132,13 +132,13 @@ which should be ``/srv/funkwhale/front/dist`` by default, then reload your nginx
Alternative docker deployment method Alternative docker deployment method
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Thanks to the awesome done by @thetarkus at https://github.com/thetarkus/docker-funkwhale, Thanks to the awesome work done by @thetarkus at https://github.com/thetarkus/docker-funkwhale,
we're now able to provide an alternative and easier Docker deployment method! we're now able to provide an alternative and easier Docker deployment method!
In contrast with our current, multi-container offer, this method integrates In contrast with our current, multi-container offer, this method integrates
all Funkwhale processes and services (database, redis, etc.) into a single, easier to deploy container. all Funkwhale processes and services (database, redis, etc.) into a single, easier to deploy container.
Both method will coexist in parallel, as each one has pros and cons. You can learn more Both methods will coexist in parallel, as each one has pros and cons. You can learn more
about this exciting new deployment option by visiting https://docs.funkwhale.audio/installation/docker.html! about this exciting new deployment option by visiting https://docs.funkwhale.audio/installation/docker.html!
Automatically load .env file Automatically load .env file
@ -146,7 +146,7 @@ Automatically load .env file
On non-docker deployments, earlier versions required you to source On non-docker deployments, earlier versions required you to source
the config/.env file before launching any Funkwhale command, with ``export $(cat config/.env | grep -v ^# | xargs)`` the config/.env file before launching any Funkwhale command, with ``export $(cat config/.env | grep -v ^# | xargs)``
This led to more complex and error prode deployment / setup. This led to more complex and error prone deployment / setup.
This is not the case anymore, and Funkwhale will automatically load this file if it's available. This is not the case anymore, and Funkwhale will automatically load this file if it's available.
@ -174,7 +174,7 @@ Enable gzip compression [manual action suggested]
Gzip compression will be enabled on new instances by default Gzip compression will be enabled on new instances by default
and will reduce the amount of bandwidth consumed by your instance. and will reduce the amount of bandwidth consumed by your instance.
If you with to benefit from gzip compression on your instance, If you want to benefit from gzip compression on your instance,
edit your reverse proxy virtualhost file (located at ``/etc/nginx/sites-available/funkwhale.conf``) and add the following snippet edit your reverse proxy virtualhost file (located at ``/etc/nginx/sites-available/funkwhale.conf``) and add the following snippet
in the server block, then reload your nginx server:: in the server block, then reload your nginx server::

View File

@ -41,15 +41,19 @@ Setup front-end only development environment
yarn install yarn install
4. Launch the development server:: 4. Compile the translations::
yarn i18n-compile
5. Launch the development server::
# this will serve the front-end on http://localhost:8000/front/ # this will serve the front-end on http://localhost:8000/front/
VUE_PORT=8000 yarn serve VUE_PORT=8000 yarn serve
5. Make the front-end talk with an existing server (like https://demo.funkwhale.audio), 6. Make the front-end talk with an existing server (like https://demo.funkwhale.audio or https://open.audio),
by clicking on the corresponding link in the footer by clicking on the corresponding link in the footer
6. Start hacking! 7. Start hacking!
Setup your development environment Setup your development environment
---------------------------------- ----------------------------------

View File

@ -7,6 +7,7 @@ import uuid
import markdown import markdown
import pendulum import pendulum
import pydub
from django.conf import settings from django.conf import settings
from django.contrib.postgres.fields import JSONField from django.contrib.postgres.fields import JSONField
from django.core.files.base import ContentFile from django.core.files.base import ContentFile
@ -780,6 +781,15 @@ class Upload(models.Model):
"size": self.get_file_size(), "size": self.get_file_size(),
} }
def get_audio_segment(self):
input = self.get_audio_file()
if not input:
return
input_format = utils.MIMETYPE_TO_EXTENSION[self.mimetype]
audio = pydub.AudioSegment.from_file(input, format=input_format)
return audio
def save(self, **kwargs): def save(self, **kwargs):
if not self.mimetype: if not self.mimetype:
if self.audio_file: if self.audio_file:
@ -824,10 +834,9 @@ class Upload(models.Model):
0 0
] + ".{}".format(format) ] + ".{}".format(format)
version.audio_file.save(new_name, f) version.audio_file.save(new_name, f)
utils.transcode_file( utils.transcode_audio(
input=self.audio_file, audio=self.get_audio_segment(),
output=version.audio_file, output=version.audio_file,
input_format=utils.MIMETYPE_TO_EXTENSION[self.mimetype],
output_format=utils.MIMETYPE_TO_EXTENSION[mimetype], output_format=utils.MIMETYPE_TO_EXTENSION[mimetype],
) )
version.size = version.audio_file.size version.size = version.audio_file.size

View File

@ -75,5 +75,9 @@ def get_actor_from_request(request):
def transcode_file(input, output, input_format, output_format, **kwargs): def transcode_file(input, output, input_format, output_format, **kwargs):
with input.open("rb"): with input.open("rb"):
audio = pydub.AudioSegment.from_file(input, format=input_format) audio = pydub.AudioSegment.from_file(input, format=input_format)
return transcode_audio(audio, output, output_format, **kwargs)
def transcode_audio(audio, output, output_format, **kwargs):
with output.open("wb"): with output.open("wb"):
return audio.export(output, format=output_format, **kwargs) return audio.export(output, format=output_format, **kwargs)

View File

@ -374,6 +374,32 @@ def test_listen_transcode(factories, now, logged_in_api_client, mocker):
) )
@pytest.mark.parametrize("serve_path", [("/host/music",), ("/app/music",)])
def test_listen_transcode_in_place(
serve_path, factories, now, logged_in_api_client, mocker, settings
):
settings.MUSIC_DIRECTORY_PATH = "/app/music"
settings.MUSIC_DIRECTORY_SERVE_PATH = serve_path
upload = factories["music.Upload"](
import_status="finished",
library__actor__user=logged_in_api_client.user,
audio_file=None,
source="file://" + os.path.join(DATA_DIR, "test.ogg"),
)
assert upload.get_audio_segment()
url = reverse("api:v1:listen-detail", kwargs={"uuid": upload.track.uuid})
handle_serve = mocker.spy(views, "handle_serve")
response = logged_in_api_client.get(url, {"to": "mp3"})
assert response.status_code == 200
handle_serve.assert_called_once_with(
upload, user=logged_in_api_client.user, format="mp3"
)
def test_user_can_create_library(factories, logged_in_api_client): def test_user_can_create_library(factories, logged_in_api_client):
actor = logged_in_api_client.user.create_actor() actor = logged_in_api_client.user.create_actor()
url = reverse("api:v1:libraries-list") url = reverse("api:v1:libraries-list")

View File

@ -0,0 +1 @@
Make Apache configuration file work with 0.18 changes (#667)

View File

@ -0,0 +1 @@
Hide pagination when there is only one page of results (#681)

View File

@ -0,0 +1 @@
Fix transcoding of in-place imported tracks (#688)

View File

@ -5,3 +5,38 @@ Next release notes
Those release notes refer to the current development branch and are reset Those release notes refer to the current development branch and are reset
after each release. after each release.
Fix Apache configuration file for 0.18 [manual action required]
----------------------------------------------------------
The way front is served has changed since 0.18. The Apache configuration can't serve 0.18 properly, leading to blank screens.
If you are on an Apache setup, you will have to replace the `<Location "/api">` block with the following::
<Location "/">
# similar to nginx 'client_max_body_size 100M;'
LimitRequestBody 104857600
ProxyPass ${funkwhale-api}/
ProxyPassReverse ${funkwhale-api}/
</Location>
And add some more `ProxyPass` directives so that the `Alias` part of your configuration file looks this way::
ProxyPass "/front" "!"
Alias /front /srv/funkwhale/front/dist
ProxyPass "/media" "!"
Alias /media /srv/funkwhale/data/media
ProxyPass "/staticfiles" "!"
Alias /staticfiles /srv/funkwhale/data/static
In case you are using custom css and theming, you also need to match this block::
ProxyPass "/settings.json" "!"
Alias /settings.json /srv/funkwhale/custom/settings.json
ProxyPass "/custom" "!"
Alias /custom /srv/funkwhale/custom

View File

@ -46,10 +46,6 @@ Define MUSIC_DIRECTORY_PATH /srv/funkwhale/data/music
# Tell the api that the client is using https # Tell the api that the client is using https
RequestHeader set X-Forwarded-Proto "https" RequestHeader set X-Forwarded-Proto "https"
DocumentRoot /srv/funkwhale/front/dist
FallbackResource /index.html
# Configure Proxy settings # Configure Proxy settings
# ProxyPreserveHost pass the original Host header to the backend server # ProxyPreserveHost pass the original Host header to the backend server
ProxyVia On ProxyVia On
@ -71,12 +67,12 @@ Define MUSIC_DIRECTORY_PATH /srv/funkwhale/data/music
# Activating WebSockets # Activating WebSockets
ProxyPass "/api/v1/activity" ${funkwhale-api-ws}/api/v1/activity ProxyPass "/api/v1/activity" ${funkwhale-api-ws}/api/v1/activity
<Location "/api"> <Location "/">
# similar to nginx 'client_max_body_size 100M;' # similar to nginx 'client_max_body_size 100M;'
LimitRequestBody 104857600 LimitRequestBody 104857600
ProxyPass ${funkwhale-api}/api ProxyPass ${funkwhale-api}/
ProxyPassReverse ${funkwhale-api}/api ProxyPassReverse ${funkwhale-api}/
</Location> </Location>
<Location "/federation"> <Location "/federation">
ProxyPass ${funkwhale-api}/federation ProxyPass ${funkwhale-api}/federation
@ -94,8 +90,13 @@ Define MUSIC_DIRECTORY_PATH /srv/funkwhale/data/music
ProxyPassReverse ${funkwhale-api}/.well-known/ ProxyPassReverse ${funkwhale-api}/.well-known/
</Location> </Location>
ProxyPass "/front" "!"
Alias /front /srv/funkwhale/front/dist
ProxyPass "/media" "!"
Alias /media /srv/funkwhale/data/media Alias /media /srv/funkwhale/data/media
ProxyPass "/staticfiles" "!"
Alias /staticfiles /srv/funkwhale/data/static Alias /staticfiles /srv/funkwhale/data/static
# Setting appropriate access levels to serve frontend # Setting appropriate access levels to serve frontend

View File

@ -63,6 +63,37 @@ easy:
This is a warning, not an error, and it can be safely ignored. This is a warning, not an error, and it can be safely ignored.
Never run the ``makemigrations`` command yourself. Never run the ``makemigrations`` command yourself.
Upgrading the Postgres container
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
With some Funkwhale releases, it is recommended to upgrade the version of the
Postgres database server container. For example, Funkwhale 0.17 recommended
Postgres 9.4, but Funkwhale 0.18 recommends Postgres 11. When upgrading
Postgres, it is not sufficient to change the container referenced in
``docker-compose.yml``. New major versions of Postgres cannot read the databases
created by older major versions. The data has to be exported from a running
instance of the old version and imported by the new version.
Thankfully, there is a Docker container available to automate this process. You
can use the following snippet to upgrade your database in ``./postgres``,
keeping a backup of the old version in ``./postgres-old``:
.. parsed-literal::
# Replace "9.4" and "11" with the versions you are migrating between.
export OLD_POSTGRES=9.4
export NEW_POSTGRES=11
docker-compose stop postgres
docker run --rm \
-v `pwd`/data/postgres:/var/lib/postgresql/${OLD_POSTGRES}/data \
-v `pwd`/data/postgres-new:/var/lib/postgresql/${NEW_POSTGRES}/data \
tianon/postgres-upgrade:${OLD_POSTGRES}-to-${NEW_POSTGRES}
# Add back the access control rule that doesn't survive the upgrade
echo "host all all all trust" | sudo tee -a ./postgres-new/pg_hba.conf
# Swap over to the new database
mv ./data/postgres ./data/postgres-old
mv ./data/postgres-new ./data/postgres
Non-docker setup Non-docker setup
---------------- ----------------

View File

@ -1,5 +1,5 @@
<template> <template>
<div class="ui pagination menu" role="navigation" :aria-label="labels.pagination"> <div v-if='maxPage > 1' class="ui pagination menu" role="navigation" :aria-label="labels.pagination">
<a href <a href
:disabled="current - 1 < 1" :disabled="current - 1 < 1"
@click.prevent.stop="selectPage(current - 1)" @click.prevent.stop="selectPage(current - 1)"
@ -13,7 +13,7 @@
{{ page }} {{ page }}
</a href> </a href>
<div v-else class="disabled item"> <div v-else class="disabled item">
...
</div> </div>
</template> </template>
<a href <a href

View File

@ -67,7 +67,7 @@ export default {
}, },
title () { title () {
if (this.playable) { if (this.playable) {
return this.$gettext('Play now') return this.$gettext('Play...')
} else { } else {
if (this.track) { if (this.track) {
return this.$gettext('This track is not available in any library you have access to') return this.$gettext('This track is not available in any library you have access to')

View File

@ -65,7 +65,7 @@
<translate>We cannot load this track</translate> <translate>We cannot load this track</translate>
</div> </div>
<p v-if="hasNext && playing && $store.state.player.errorCount < $store.state.player.maxConsecutiveErrors"> <p v-if="hasNext && playing && $store.state.player.errorCount < $store.state.player.maxConsecutiveErrors">
<translate>The next track will play automatically in a few seconds...</translate> <translate>The next track will play automatically in a few seconds</translate>
<i class="loading spinner icon"></i> <i class="loading spinner icon"></i>
</p> </p>
<p> <p>

View File

@ -10,7 +10,7 @@
<img class="ui big circular image" v-else v-lazy="$store.getters['instance/absoluteUrl'](profile.avatar.square_crop)" /> <img class="ui big circular image" v-else v-lazy="$store.getters['instance/absoluteUrl'](profile.avatar.square_crop)" />
<div class="content"> <div class="content">
{{ profile.username }} {{ profile.username }}
<div class="sub header" v-translate="{date: signupDate}">Registered since %{ date }</div> <div class="sub header" v-translate="{date: signupDate}">Member since %{ date }</div>
</div> </div>
</h2> </h2>
<div class="ui basic green label"> <div class="ui basic green label">

View File

@ -68,8 +68,7 @@
<translate>Change my password</translate> <translate>Change my password</translate>
</h2> </h2>
<div class="ui message"> <div class="ui message">
<translate>Changing your password will also change your Subsonic API password if you have requested one.</translate> <translate>Changing your password will also change your Subsonic API password if you have requested one.</translate>&nbsp;<translate>You will have to update your password on your clients that use this password.</translate>
<translate>You will have to update your password on your clients that use this password.</translate>
</div> </div>
<form class="ui form" @submit.prevent="submitPassword()"> <form class="ui form" @submit.prevent="submitPassword()">
<div v-if="passwordError" class="ui negative message"> <div v-if="passwordError" class="ui negative message">

View File

@ -5,8 +5,7 @@
<translate>The Subsonic API is not available on this Funkwhale instance.</translate> <translate>The Subsonic API is not available on this Funkwhale instance.</translate>
</p> </p>
<p> <p>
<translate>Funkwhale is compatible with other music players that support the Subsonic API.</translate> <translate>Funkwhale is compatible with other music players that support the Subsonic API.</translate>&nbsp;<translate>You can use those to enjoy your playlist and music in offline mode, on your smartphone or tablet, for instance.</translate>
<translate>You can use those to enjoy your playlist and music in offline mode, on your smartphone or tablet, for instance.</translate>
</p> </p>
<p> <p>
<translate>However, accessing Funkwhale from those clients require a separate password you can set below.</translate> <translate>However, accessing Funkwhale from those clients require a separate password you can set below.</translate>

View File

@ -168,7 +168,7 @@ export default {
computed: { computed: {
labels () { labels () {
return { return {
searchPlaceholder: this.$gettext('Search by domain, username, bio...') searchPlaceholder: this.$gettext('Search by domain, username, bio')
} }
}, },
actionFilters () { actionFilters () {

View File

@ -148,7 +148,7 @@ export default {
computed: { computed: {
labels () { labels () {
return { return {
searchPlaceholder: this.$gettext('Search by name...') searchPlaceholder: this.$gettext('Search by name')
} }
}, },
actionFilters () { actionFilters () {

View File

@ -22,11 +22,11 @@
<div v-else class="ui list"> <div v-else class="ui list">
<div class="ui item" v-if="object.silence_activity"> <div class="ui item" v-if="object.silence_activity">
<i class="feed icon"></i> <i class="feed icon"></i>
<div class="content"><translate>Silence activity</translate></div> <div class="content"><translate>Mute activity</translate></div>
</div> </div>
<div class="ui item" v-if="object.silence_notifications"> <div class="ui item" v-if="object.silence_notifications">
<i class="bell icon"></i> <i class="bell icon"></i>
<div class="content"><translate>Silence notifications</translate></div> <div class="content"><translate>Mute notifications</translate></div>
</div> </div>
<div class="ui item" v-if="object.reject_media"> <div class="ui item" v-if="object.reject_media">
<i class="file icon"></i> <i class="file icon"></i>

View File

@ -112,7 +112,7 @@ export default {
blockAllHelp: this.$gettext("Block everything from this account or domain. This will prevent any interaction with the entity, and purge related content (uploads, libraries, follows, etc.)"), blockAllHelp: this.$gettext("Block everything from this account or domain. This will prevent any interaction with the entity, and purge related content (uploads, libraries, follows, etc.)"),
silenceActivity: { silenceActivity: {
help: this.$gettext("Hide account or domain content, except from followers."), help: this.$gettext("Hide account or domain content, except from followers."),
label: this.$gettext("Silence activity"), label: this.$gettext("Mute activity"),
}, },
silenceNotifications: { silenceNotifications: {
help: this.$gettext("Prevent account or domain from triggering notifications, except from followers."), help: this.$gettext("Prevent account or domain from triggering notifications, except from followers."),

View File

@ -38,9 +38,11 @@ export default {
labels () { labels () {
let libraryFollowMessage = this.$gettext('%{ username } followed your library "%{ library }"') let libraryFollowMessage = this.$gettext('%{ username } followed your library "%{ library }"')
let libraryAcceptFollowMessage = this.$gettext('%{ username } accepted your follow on library "%{ library }"') let libraryAcceptFollowMessage = this.$gettext('%{ username } accepted your follow on library "%{ library }"')
let libraryPendingFollowMessage = this.$gettext('%{ username } wants to follow your library "%{ library }"')
return { return {
libraryFollowMessage, libraryFollowMessage,
libraryAcceptFollowMessage, libraryAcceptFollowMessage,
libraryPendingFollowMessage,
markRead: this.$gettext('Mark as read'), markRead: this.$gettext('Mark as read'),
markUnread: this.$gettext('Mark as unread'), markUnread: this.$gettext('Mark as unread'),
@ -55,19 +57,23 @@ export default {
if (a.type === 'Follow') { if (a.type === 'Follow') {
if (a.object && a.object.type === 'music.Library') { if (a.object && a.object.type === 'music.Library') {
let action = null let action = null
let message = null
if (!a.related_object.approved) { if (!a.related_object.approved) {
message = this.labels.libraryPendingFollowMessage
action = { action = {
buttonClass: 'green', buttonClass: 'green',
icon: 'check', icon: 'check',
label: this.$gettext('Approve'), label: this.$gettext('Approve'),
handler: () => { self.approveLibraryFollow(a.related_object) } handler: () => { self.approveLibraryFollow(a.related_object) }
} }
} else {
message = this.labels.libraryFollowMessage
} }
return { return {
action, action,
detailUrl: {name: 'content.libraries.detail', params: {id: a.object.uuid}}, detailUrl: {name: 'content.libraries.detail', params: {id: a.object.uuid}},
message: this.$gettextInterpolate( message: this.$gettextInterpolate(
this.labels.libraryFollowMessage, message,
{username: this.username, library: a.object.name} {username: this.username, library: a.object.name}
) )
} }

View File

@ -160,7 +160,7 @@ export default {
}), }),
labels () { labels () {
return { return {
copyTitle: this.$gettext('Copy tracks from current queue to playlist') copyTitle: this.$gettext('Copy queued tracks to playlist')
} }
}, },
status () { status () {

View File

@ -24,7 +24,7 @@
</tbody> </tbody>
</table> </table>
<p v-else> <p v-else>
<translate>No notifications yet.</translate> <translate>No notification to show.</translate>
</p> </p>
</div> </div>
</section> </section>

View File

@ -1,7 +1,7 @@
<template> <template>
<div class="ui vertical aligned stripe segment"> <div class="ui vertical aligned stripe segment">
<div v-if="isLoading" :class="['ui', {'active': isLoading}, 'inverted', 'dimmer']"> <div v-if="isLoading" :class="['ui', {'active': isLoading}, 'inverted', 'dimmer']">
<div class="ui text loader"><translate>Loading remote libraries...</translate></div> <div class="ui text loader"><translate>Loading remote libraries</translate></div>
</div> </div>
<div v-else class="ui text container"> <div v-else class="ui text container">
<h1 class="ui header"><translate>Remote libraries</translate></h1> <h1 class="ui header"><translate>Remote libraries</translate></h1>