Merge branch 'develop'
This commit is contained in:
commit
7df97263e5
14
CHANGELOG
14
CHANGELOG
|
@ -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::
|
||||
|
||||
|
|
|
@ -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
|
||||
----------------------------------
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
Make Apache configuration file work with 0.18 changes (#667)
|
|
@ -0,0 +1 @@
|
|||
Hide pagination when there is only one page of results (#681)
|
|
@ -0,0 +1 @@
|
|||
Fix transcoding of in-place imported tracks (#688)
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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> <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">
|
||||
|
|
|
@ -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> <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>
|
||||
|
|
|
@ -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 () {
|
||||
|
|
|
@ -148,7 +148,7 @@ export default {
|
|||
computed: {
|
||||
labels () {
|
||||
return {
|
||||
searchPlaceholder: this.$gettext('Search by name...')
|
||||
searchPlaceholder: this.$gettext('Search by name…')
|
||||
}
|
||||
},
|
||||
actionFilters () {
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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."),
|
||||
|
|
|
@ -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}
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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 () {
|
||||
|
|
|
@ -24,7 +24,7 @@
|
|||
</tbody>
|
||||
</table>
|
||||
<p v-else>
|
||||
<translate>No notifications yet.</translate>
|
||||
<translate>No notification to show.</translate>
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
|
|
@ -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>
|
||||
|
|
Loading…
Reference in New Issue