Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 36 additions & 1 deletion packages/common/src/hooks/useDownloadTrackButtons.ts
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
import { useMemo } from 'react'
import { useEffect, useMemo, useState } from 'react'

import { shallowEqual, useSelector } from 'react-redux'

Expand All@@ -11,6 +11,10 @@ import { getHasAccount } from '../store/account/selectors'
import { getTrack, getTracks } from '../store/cache/tracks/selectors'
import { CommonState } from '../store/commonStore'
import { getCurrentUploads } from '../store/stems-upload/selectors'
import { usePrevious } from 'react-use'
import type { AudiusSdk } from '@audius/sdk'
import { encodeHashId } from '~/utils/hashIds'
import { isEqual } from 'lodash'

export type DownloadButtonConfig = {
state: ButtonState
Expand DownExpand Up@@ -97,6 +101,37 @@ export const useCurrentStems = ({ trackId }: { trackId: ID }) => {
return { stemTracks, track }
}

export const useFileSizes = ({ audiusSdk, trackIds }: {audiusSdk: () => Promise<AudiusSdk>, trackIds: ID[] }) => {
const previousTrackIds = usePrevious(trackIds)
const [sizes, setSizes] = useState<{[trackId: ID]: number}>({})
useEffect(() => {
if (!isEqual(previousTrackIds, trackIds)) {
const asyncFn = async () => {
const sdk = await audiusSdk()
const sizeResults = await Promise.all(trackIds.map(async trackId => {
if (sizes[trackId]) {
return ({ trackId, size: sizes[trackId] })
}
try {
const res = await sdk.tracks.inspectTrack({ trackId: encodeHashId(trackId) })
const size = res?.data?.size ?? null
return ({ trackId, size })
} catch (e) {
console.error(e)
return ({ trackId, size: null })
}
}))
setSizes(sizes => ({ ...sizes, ...sizeResults.reduce((acc, curr) => {
acc[curr.trackId] = curr.size
return acc
}, {} as { trackId: ID, size: number }) }) )
}
asyncFn()
}
}, [trackIds, previousTrackIds, audiusSdk, sizes, setSizes])
return sizes
}

const useUploadingStems = ({ trackId }: { trackId: ID }) => {
const currentUploads = useSelector(
(state: CommonState) => getCurrentUploads(state, trackId),
Expand Down
10 changes: 10 additions & 0 deletions packages/discovery-provider/src/api/v1/helpers.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -36,6 +36,12 @@
COVER_ART_SIZES = ["150x150", "480x480", "1000x1000"]


def camel_to_snake(name):
"""Convert CamelCase to snake_case"""
name = re.sub("(.)([A-Z][a-z]+)", r"\1_\2", name)
return re.sub("([a-z0-9])([A-Z])", r"\1_\2", name).lower()


def make_image(endpoint, cid, width="", height=""):
return f"{endpoint}/content/{cid}/{width}x{height}.jpg"

Expand DownExpand Up@@ -322,6 +328,10 @@ def extend_track_element(track):
return remix_of


def extend_blob_info(blob_info):
return {camel_to_snake(k): v for k, v in blob_info.items()}


def parse_bool_param(param):
if not isinstance(param, str):
return None
Expand Down
8 changes: 8 additions & 0 deletions packages/discovery-provider/src/api/v1/models/tracks.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -119,6 +119,14 @@
},
)

blob_info = ns.model(
"blob_info",
{
"size": fields.Integer(required=True),
"content_type": fields.String(required=True),
},
)

cover_art = ns.model(
"cover_art",
{"150x150": fields.String, "480x480": fields.String, "1000x1000": fields.String},
Expand Down
119 changes: 99 additions & 20 deletions packages/discovery-provider/src/api/v1/tracks.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,7 +3,7 @@
import re
import urllib.parse
from typing import List
from urllib.parse import urljoin
from urllib.parse import parse_qs, urlencode, urljoin, urlparse

import requests
from flask import redirect
Expand All@@ -18,6 +18,7 @@
current_user_parser,
decode_ids_array,
decode_with_abort,
extend_blob_info,
extend_track,
extend_user,
format_limit,
Expand DownExpand Up@@ -84,6 +85,7 @@
from src.utils.redis_metrics import record_metrics
from src.utils.rendezvous import RendezvousHash

from .models.tracks import blob_info
from .models.tracks import remixes_response as remixes_response_model
from .models.tracks import stem_full, track, track_full

Expand DownExpand Up@@ -382,6 +384,100 @@ def get(self):
return marshal(response, full_tracks_response), status


def get_stream_url_from_content_node(content_node: str, path: str):
# Add additional query parameters
joined_url = urljoin(content_node, path)
parsed_url = urlparse(joined_url)
query_params = parse_qs(parsed_url.query)
query_params["skip_play_count"] = ["true"]
stream_url = parsed_url._replace(query=urlencode(query_params, doseq=True)).geturl()

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just cleanup

headers = {"Range": "bytes=0-1"}

try:
response = requests.get(stream_url, headers=headers, timeout=5)
if response.status_code == 206 or response.status_code == 204:
return parsed_url.geturl()
except:
pass


# Inspect
inspect_parser = reqparse.RequestParser(argument_class=DescriptiveArgument)
inspect_parser.add_argument(
"original",
description="""Optional - if set to true inspects the original quality file""",
type=inputs.boolean,
required=False,
default=False,
)
inspect_result = make_response("track_inspect", ns, fields.Nested(blob_info))


@ns.route("/<string:track_id>/inspect")
class TrackInspect(Resource):
@record_metrics
@ns.doc(
id="""Inspect Track""",
description="""Inspect a track""",
params={"track_id": "A Track ID"},
responses={
200: "Success",
400: "Bad request",
500: "Server error",
},
)
@ns.expect(inspect_parser)
@ns.marshal_with(inspect_result)
@cache(ttl_sec=5)
def get(self, track_id):
"""
Inspects the details of the file for a track.
"""
request_args = stream_parser.parse_args()
is_original = request_args.get("is_original")
decoded_id = decode_with_abort(track_id, ns)
info = get_track_access_info(decoded_id)
track = info.get("track")

if not track:
logger.error(
f"tracks.py | stream | Track with id {track_id} may not exist. Please investigate."
)
abort_not_found(track_id, ns)

redis = redis_connection.get_redis()

cid = track.get("orig_file_cid") if is_original else track.get("track_cid")
path = f"internal/blobs/info/{cid}"
redis_key = f"track_cid:{cid}"

cached_content_node = redis.get(redis_key)
if cached_content_node:
cached_content_node = cached_content_node.decode("utf-8")
response = requests.get(urljoin(cached_content_node, path))
blob_info = extend_blob_info(response.json())
return success_response(blob_info)

healthy_nodes = get_all_healthy_content_nodes_cached(redis)
if not healthy_nodes:
logger.error(
f"tracks.py | stream | No healthy Content Nodes found when fetching track ID {track_id}. Please investigate."
)
abort_not_found(track_id, ns)

rendezvous = RendezvousHash(
*[re.sub("/$", "", node["endpoint"].lower()) for node in healthy_nodes]
)
content_nodes = rendezvous.get_n(9999999, cid)
for content_node in content_nodes:
response = requests.get(urljoin(content_node, path))
blob_info = extend_blob_info(response.json())
return success_response(blob_info)

abort_not_found(track_id, ns)


# Stream

stream_parser = reqparse.RequestParser(argument_class=DescriptiveArgument)
Expand DownExpand Up@@ -430,23 +526,6 @@ def get(self):
)


def tranform_stream_cache(stream_url):
return redirect(stream_url)


def get_stream_url_from_content_node(content_node: str, path: str):
stream_url = urljoin(content_node, path)
headers = {"Range": "bytes=0-1"}
try:
response = requests.get(
stream_url + "&skip_play_count=True", headers=headers, timeout=5
)
if response.status_code == 206:
return stream_url
except:
pass


@ns.route("/<string:track_id>/stream")
class TrackStream(Resource):
@record_metrics
Expand All@@ -463,7 +542,7 @@ class TrackStream(Resource):
},
)
@ns.expect(stream_parser)
@cache(ttl_sec=5, transform=tranform_stream_cache)
@cache(ttl_sec=5, transform=redirect)
def get(self, track_id):
"""
Get the streamable MP3 file of a track
Expand DownExpand Up@@ -605,7 +684,7 @@ class TrackDownload(Resource):
},
)
@ns.expect(download_parser)
@cache(ttl_sec=5, transform=tranform_stream_cache)
@cache(ttl_sec=5, transform=redirect)
def get(self, track_id):
"""
Download the original or MP3 file of a track.
Expand Down
12 changes: 6 additions & 6 deletions packages/discovery-provider/src/queries/get_track_signature.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -34,9 +34,9 @@ def get_track_stream_signature(args: GetTrackStreamSignature):
track = args["track"]
is_stream_gated = track["is_stream_gated"]
is_preview = args.get("is_preview", False)
user_data = args["user_data"]
user_signature = args["user_signature"]
nft_access_signature = args["nft_access_signature"]
user_data = args.get("user_data")
user_signature = args.get("user_signature")
nft_access_signature = args.get("nft_access_signature")
cid = track.get("preview_cid") if is_preview else track.get("track_cid")
if not cid:
return None
Expand DownExpand Up@@ -120,9 +120,9 @@ def get_track_download_signature(args: GetTrackDownloadSignature):
filename = (
orig_filename if is_original else f"{orig_name_without_extension}.mp3"
)
user_data = args["user_data"]
user_signature = args["user_signature"]
nft_access_signature = args["nft_access_signature"]
user_data = args.get("user_data")
user_signature = args.get("user_signature")
nft_access_signature = args.get("nft_access_signature")
cid = track.get("orig_file_cid") if is_original else track.get("track_cid")
if not cid:
return None
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -10,6 +10,7 @@ index.ts
models/Activity.ts
models/AuthorizedApp.ts
models/AuthorizedApps.ts
models/BlobInfo.ts
models/ConnectedWallets.ts
models/ConnectedWalletsResponse.ts
models/CoverPhoto.ts
Expand DownExpand Up@@ -42,14 +43,15 @@ models/Supporter.ts
models/Supporting.ts
models/TagsResponse.ts
models/Tip.ts
models/TopListener.ts
models/Track.ts
models/TrackArtwork.ts
models/TrackElement.ts
models/TrackInspect.ts
models/TrackResponse.ts
models/TrackSearch.ts
models/TracksResponse.ts
models/TrendingPlaylistsResponse.ts
models/TxSignature.ts
models/User.ts
models/UserAssociatedWalletResponse.ts
models/UserResponse.ts
Expand Down
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
7.2.0-SNAPSHOT
7.3.0-SNAPSHOT
Loading