Resolve "ActivityStreams compliance: duration" (#1566)
This commit is contained in:
parent
2636a3dde7
commit
82a1facdb5
|
@ -1,5 +1,6 @@
|
|||
import logging
|
||||
import os
|
||||
import re
|
||||
import urllib.parse
|
||||
import uuid
|
||||
|
||||
|
@ -1580,6 +1581,50 @@ class TrackSerializer(MusicEntitySerializer):
|
|||
return super().update(obj, validated_data)
|
||||
|
||||
|
||||
def duration_int_to_xml(duration):
|
||||
if not duration:
|
||||
return None
|
||||
|
||||
multipliers = {"S": 1, "M": 60, "H": 3600, "D": 86400}
|
||||
ret = "P"
|
||||
days, seconds = divmod(int(duration), multipliers["D"])
|
||||
ret += f"{days:d}DT" if days > 0 else "T"
|
||||
hours, seconds = divmod(seconds, multipliers["H"])
|
||||
ret += f"{hours:d}H" if hours > 0 else ""
|
||||
minutes, seconds = divmod(seconds, multipliers["M"])
|
||||
ret += f"{minutes:d}M" if minutes > 0 else ""
|
||||
ret += f"{seconds:d}S" if seconds > 0 or ret == "PT" else ""
|
||||
return ret
|
||||
|
||||
|
||||
class DayTimeDurationSerializer(serializers.DurationField):
|
||||
multipliers = {"S": 1, "M": 60, "H": 3600, "D": 86400}
|
||||
|
||||
def to_internal_value(self, value):
|
||||
if isinstance(value, float):
|
||||
return value
|
||||
|
||||
parsed = re.match(
|
||||
r"P([0-9]+D)?T([0-9]+H)?([0-9]+M)?([0-9]+(?:\.[0-9]+)?S)?", str(value)
|
||||
)
|
||||
if parsed is not None:
|
||||
return int(
|
||||
sum(
|
||||
[
|
||||
self.multipliers[s[-1]] * float("0" + s[:-1])
|
||||
for s in parsed.groups()
|
||||
if s is not None
|
||||
]
|
||||
)
|
||||
)
|
||||
self.fail(
|
||||
"invalid", format="https://www.w3.org/TR/xmlschema11-2/#dayTimeDuration"
|
||||
)
|
||||
|
||||
def to_representation(self, value):
|
||||
duration_int_to_xml(value)
|
||||
|
||||
|
||||
class UploadSerializer(jsonld.JsonLdSerializer):
|
||||
type = serializers.ChoiceField(choices=[contexts.AS.Audio])
|
||||
id = serializers.URLField(max_length=500)
|
||||
|
@ -1589,7 +1634,7 @@ class UploadSerializer(jsonld.JsonLdSerializer):
|
|||
updated = serializers.DateTimeField(required=False, allow_null=True)
|
||||
bitrate = serializers.IntegerField(min_value=0)
|
||||
size = serializers.IntegerField(min_value=0)
|
||||
duration = serializers.IntegerField(min_value=0)
|
||||
duration = DayTimeDurationSerializer(min_value=0)
|
||||
|
||||
track = TrackSerializer(required=True)
|
||||
|
||||
|
@ -1701,7 +1746,7 @@ class UploadSerializer(jsonld.JsonLdSerializer):
|
|||
"published": instance.creation_date.isoformat(),
|
||||
"bitrate": instance.bitrate,
|
||||
"size": instance.size,
|
||||
"duration": instance.duration,
|
||||
"duration": duration_int_to_xml(instance.duration),
|
||||
"url": [
|
||||
{
|
||||
"href": utils.full_url(instance.listen_url_no_download),
|
||||
|
@ -1851,7 +1896,7 @@ class ChannelUploadSerializer(jsonld.JsonLdSerializer):
|
|||
url = LinkListSerializer(keep_mediatype=["audio/*"], min_length=1)
|
||||
name = serializers.CharField()
|
||||
published = serializers.DateTimeField(required=False)
|
||||
duration = serializers.IntegerField(min_value=0, required=False)
|
||||
duration = DayTimeDurationSerializer(required=False)
|
||||
position = serializers.IntegerField(min_value=0, allow_null=True, required=False)
|
||||
disc = serializers.IntegerField(min_value=1, allow_null=True, required=False)
|
||||
album = serializers.URLField(max_length=500, required=False)
|
||||
|
@ -1960,7 +2005,7 @@ class ChannelUploadSerializer(jsonld.JsonLdSerializer):
|
|||
if upload.track.local_license:
|
||||
data["license"] = upload.track.local_license["identifiers"][0]
|
||||
|
||||
include_if_not_none(data, upload.duration, "duration")
|
||||
include_if_not_none(data, duration_int_to_xml(upload.duration), "duration")
|
||||
include_if_not_none(data, upload.track.position, "position")
|
||||
include_if_not_none(data, upload.track.disc_number, "disc")
|
||||
include_if_not_none(data, upload.track.copyright, "copyright")
|
||||
|
|
|
@ -719,7 +719,7 @@ class Track(APIModelMixin):
|
|||
@property
|
||||
def listen_url(self) -> str:
|
||||
# Not using reverse because this is slow
|
||||
return f"/api/v1/listen/{self.uuid}/"
|
||||
return f"/api/v2/listen/{self.uuid}/"
|
||||
|
||||
@property
|
||||
def local_license(self):
|
||||
|
|
|
@ -1268,7 +1268,7 @@ def test_activity_pub_upload_serializer_from_ap(factories, mocker, r_mock):
|
|||
"name": "Ignored",
|
||||
"published": published.isoformat(),
|
||||
"updated": updated.isoformat(),
|
||||
"duration": 43,
|
||||
"duration": "PT43S",
|
||||
"bitrate": 42,
|
||||
"size": 66,
|
||||
"url": {"href": "https://audio.file", "type": "Link", "mediaType": "audio/mp3"},
|
||||
|
@ -1337,7 +1337,7 @@ def test_activity_pub_upload_serializer_from_ap(factories, mocker, r_mock):
|
|||
assert track_create.call_count == 1
|
||||
assert upload.fid == data["id"]
|
||||
assert upload.track.fid == data["track"]["id"]
|
||||
assert upload.duration == data["duration"]
|
||||
assert upload.duration == 43
|
||||
assert upload.size == data["size"]
|
||||
assert upload.bitrate == data["bitrate"]
|
||||
assert upload.source == data["url"]["href"]
|
||||
|
@ -1357,7 +1357,7 @@ def test_activity_pub_upload_serializer_from_ap_update(factories, mocker, now, r
|
|||
"name": "Ignored",
|
||||
"published": now.isoformat(),
|
||||
"updated": now.isoformat(),
|
||||
"duration": 42,
|
||||
"duration": "PT42S",
|
||||
"bitrate": 42,
|
||||
"size": 66,
|
||||
"url": {
|
||||
|
@ -1376,7 +1376,7 @@ def test_activity_pub_upload_serializer_from_ap_update(factories, mocker, now, r
|
|||
upload.refresh_from_db()
|
||||
|
||||
assert upload.fid == data["id"]
|
||||
assert upload.duration == data["duration"]
|
||||
assert upload.duration == 42
|
||||
assert upload.size == data["size"]
|
||||
assert upload.bitrate == data["bitrate"]
|
||||
assert upload.source == data["url"]["href"]
|
||||
|
@ -1408,7 +1408,7 @@ def test_activity_pub_audio_serializer_to_ap(factories):
|
|||
"name": upload.track.full_name,
|
||||
"published": upload.creation_date.isoformat(),
|
||||
"updated": upload.modification_date.isoformat(),
|
||||
"duration": upload.duration,
|
||||
"duration": "PT43S",
|
||||
"bitrate": upload.bitrate,
|
||||
"size": upload.size,
|
||||
"to": contexts.AS.Public,
|
||||
|
@ -1777,7 +1777,7 @@ def test_channel_upload_serializer(factories):
|
|||
"content": common_utils.render_html(content.text, content.content_type),
|
||||
"to": "https://www.w3.org/ns/activitystreams#Public",
|
||||
"position": upload.track.position,
|
||||
"duration": upload.duration,
|
||||
"duration": "PT54S",
|
||||
"album": upload.track.album.fid,
|
||||
"disc": upload.track.disc_number,
|
||||
"copyright": upload.track.copyright,
|
||||
|
@ -1826,7 +1826,7 @@ def test_channel_upload_serializer_from_ap_create(factories, now, mocker):
|
|||
"published": now.isoformat(),
|
||||
"mediaType": "text/html",
|
||||
"content": "<p>Hello</p>",
|
||||
"duration": 543,
|
||||
"duration": "PT543S",
|
||||
"position": 4,
|
||||
"disc": 2,
|
||||
"album": album.fid,
|
||||
|
@ -1875,7 +1875,7 @@ def test_channel_upload_serializer_from_ap_create(factories, now, mocker):
|
|||
assert upload.mimetype == payload["url"][1]["mediaType"]
|
||||
assert upload.size == payload["url"][1]["size"]
|
||||
assert upload.bitrate == payload["url"][1]["bitrate"]
|
||||
assert upload.duration == payload["duration"]
|
||||
assert upload.duration == 543
|
||||
assert upload.track.artist_credit.all()[0].artist == channel.artist
|
||||
assert upload.track.position == payload["position"]
|
||||
assert upload.track.disc_number == payload["disc"]
|
||||
|
@ -1909,7 +1909,7 @@ def test_channel_upload_serializer_from_ap_update(factories, now, mocker):
|
|||
"published": now.isoformat(),
|
||||
"mediaType": "text/html",
|
||||
"content": "<p>Hello</p>",
|
||||
"duration": 543,
|
||||
"duration": "PT543S",
|
||||
"position": 4,
|
||||
"disc": 2,
|
||||
"album": album.fid,
|
||||
|
@ -1959,7 +1959,7 @@ def test_channel_upload_serializer_from_ap_update(factories, now, mocker):
|
|||
assert upload.mimetype == payload["url"][1]["mediaType"]
|
||||
assert upload.size == payload["url"][1]["size"]
|
||||
assert upload.bitrate == payload["url"][1]["bitrate"]
|
||||
assert upload.duration == payload["duration"]
|
||||
assert upload.duration == 543
|
||||
assert upload.track.artist_credit.all()[0].artist == channel.artist
|
||||
assert upload.track.position == payload["position"]
|
||||
assert upload.track.disc_number == payload["disc"]
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
ActivityStreams compliance: duration (#1566)
|
|
@ -798,8 +798,7 @@ An `Audio` object is a custom object used to store upload information. It extend
|
|||
- Integer
|
||||
- The bitrate of the audio in bytes/s
|
||||
* - `duration`*
|
||||
- Integer
|
||||
- The duration of the audio in seconds
|
||||
- The duration of the audio as defined in [as](https://www.w3.org/TR/activitystreams-vocabulary/#dfn-duration)
|
||||
* - `library`*
|
||||
- String (URI)
|
||||
- The ID of the audio's containing [`Library` object](#library)
|
||||
|
@ -826,7 +825,7 @@ An `Audio` object is a custom object used to store upload information. It extend
|
|||
"name": "Krav Boca - Mortem",
|
||||
"size": 8656581,
|
||||
"bitrate": 320000,
|
||||
"duration": 213,
|
||||
"duration": "PT1312S,
|
||||
"library": "https://awesome.music/federation/music/libraries/dc702491-f6ce-441b-9da0-cecbed08bcc6",
|
||||
"updated": "2018-10-02T19:49:35.646372+00:00",
|
||||
"published": "2018-10-02T19:49:35.646359+00:00",
|
||||
|
|
Loading…
Reference in New Issue