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
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:
- 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
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
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]
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
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,
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
@ -132,13 +132,13 @@ which should be ``/srv/funkwhale/front/dist`` by default, then reload your nginx
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!
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.
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!
Automatically load .env file
@ -146,7 +146,7 @@ Automatically load .env file
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)``
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.
@ -174,7 +174,7 @@ Enable gzip compression [manual action suggested]
Gzip compression will be enabled on new instances by default
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
in the server block, then reload your nginx server::

View File

@ -41,15 +41,19 @@ Setup front-end only development environment
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/
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
6. Start hacking!
7. Start hacking!
Setup your development environment
----------------------------------

View File

@ -7,6 +7,7 @@ import uuid
import markdown
import pendulum
import pydub
from django.conf import settings
from django.contrib.postgres.fields import JSONField
from django.core.files.base import ContentFile
@ -780,6 +781,15 @@ class Upload(models.Model):
"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):
if not self.mimetype:
if self.audio_file:
@ -824,10 +834,9 @@ class Upload(models.Model):
0
] + ".{}".format(format)
version.audio_file.save(new_name, f)
utils.transcode_file(
input=self.audio_file,
utils.transcode_audio(
audio=self.get_audio_segment(),
output=version.audio_file,
input_format=utils.MIMETYPE_TO_EXTENSION[self.mimetype],
output_format=utils.MIMETYPE_TO_EXTENSION[mimetype],
)
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):
with input.open("rb"):
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"):
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):
actor = logged_in_api_client.user.create_actor()
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
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
RequestHeader set X-Forwarded-Proto "https"
DocumentRoot /srv/funkwhale/front/dist
FallbackResource /index.html
# Configure Proxy settings
# ProxyPreserveHost pass the original Host header to the backend server
ProxyVia On
@ -69,14 +65,14 @@ Define MUSIC_DIRECTORY_PATH /srv/funkwhale/data/music
</Proxy>
# 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;'
LimitRequestBody 104857600
ProxyPass ${funkwhale-api}/api
ProxyPassReverse ${funkwhale-api}/api
ProxyPass ${funkwhale-api}/
ProxyPassReverse ${funkwhale-api}/
</Location>
<Location "/federation">
ProxyPass ${funkwhale-api}/federation
@ -94,8 +90,13 @@ Define MUSIC_DIRECTORY_PATH /srv/funkwhale/data/music
ProxyPassReverse ${funkwhale-api}/.well-known/
</Location>
ProxyPass "/front" "!"
Alias /front /srv/funkwhale/front/dist
ProxyPass "/media" "!"
Alias /media /srv/funkwhale/data/media
ProxyPass "/staticfiles" "!"
Alias /staticfiles /srv/funkwhale/data/static
# Setting appropriate access levels to serve frontend

View File

@ -62,6 +62,37 @@ easy:
This is a warning, not an error, and it can be safely ignored.
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

View File

@ -1,5 +1,5 @@
<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
:disabled="current - 1 < 1"
@click.prevent.stop="selectPage(current - 1)"
@ -13,7 +13,7 @@
{{ page }}
</a href>
<div v-else class="disabled item">
...
</div>
</template>
<a href

View File

@ -67,7 +67,7 @@ export default {
},
title () {
if (this.playable) {
return this.$gettext('Play now')
return this.$gettext('Play...')
} else {
if (this.track) {
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>
</div>
<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>
</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)" />
<div class="content">
{{ 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>
</h2>
<div class="ui basic green label">

View File

@ -68,8 +68,7 @@
<translate>Change my password</translate>
</h2>
<div class="ui message">
<translate>Changing your password will also change your Subsonic API password if you have requested one.</translate>
<translate>You will have to update your password on your clients that use this password.</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>
</div>
<form class="ui form" @submit.prevent="submitPassword()">
<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>
</p>
<p>
<translate>Funkwhale is compatible with other music players that support the Subsonic API.</translate>
<translate>You can use those to enjoy your playlist and music in offline mode, on your smartphone or tablet, for instance.</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>
</p>
<p>
<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: {
labels () {
return {
searchPlaceholder: this.$gettext('Search by domain, username, bio...')
searchPlaceholder: this.$gettext('Search by domain, username, bio')
}
},
actionFilters () {

View File

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

View File

@ -22,11 +22,11 @@
<div v-else class="ui list">
<div class="ui item" v-if="object.silence_activity">
<i class="feed icon"></i>
<div class="content"><translate>Silence activity</translate></div>
<div class="content"><translate>Mute activity</translate></div>
</div>
<div class="ui item" v-if="object.silence_notifications">
<i class="bell icon"></i>
<div class="content"><translate>Silence notifications</translate></div>
<div class="content"><translate>Mute notifications</translate></div>
</div>
<div class="ui item" v-if="object.reject_media">
<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.)"),
silenceActivity: {
help: this.$gettext("Hide account or domain content, except from followers."),
label: this.$gettext("Silence activity"),
label: this.$gettext("Mute activity"),
},
silenceNotifications: {
help: this.$gettext("Prevent account or domain from triggering notifications, except from followers."),

View File

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

View File

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

View File

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

View File

@ -1,7 +1,7 @@
<template>
<div class="ui vertical aligned stripe segment">
<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 v-else class="ui text container">
<h1 class="ui header"><translate>Remote libraries</translate></h1>