mirror of
https://github.com/Alexander-D-Karpov/akarpov
synced 2024-11-27 15:53:44 +03:00
Compare commits
3 Commits
ae1c9bee6a
...
9ec7a39a83
Author | SHA1 | Date | |
---|---|---|---|
|
9ec7a39a83 | ||
3405b76897 | |||
b02a77ec5e |
|
@ -1,5 +1,8 @@
|
|||
from rest_framework import serializers
|
||||
from rest_framework.pagination import PageNumberPagination
|
||||
from rest_framework.permissions import SAFE_METHODS, BasePermission
|
||||
|
||||
from akarpov.utils.models import get_object_user
|
||||
|
||||
|
||||
class SmallResultsSetPagination(PageNumberPagination):
|
||||
|
@ -24,3 +27,19 @@ class RecursiveField(serializers.Serializer):
|
|||
def to_representation(self, value):
|
||||
serializer = self.parent.parent.__class__(value, context=self.context)
|
||||
return serializer.data
|
||||
|
||||
|
||||
class IsCreatorOrReadOnly(BasePermission):
|
||||
def has_permission(self, request, view):
|
||||
return bool(
|
||||
request.method in SAFE_METHODS
|
||||
or request.user
|
||||
and get_object_user(view.get_object()) == request.user
|
||||
)
|
||||
|
||||
|
||||
class SetUserModelSerializer(serializers.ModelSerializer):
|
||||
def create(self, validated_data):
|
||||
creator = self.context["request"].user
|
||||
obj = self.Meta.model.objects.create(creator=creator, **validated_data)
|
||||
return obj
|
||||
|
|
|
@ -1,11 +1,15 @@
|
|||
from drf_spectacular.utils import extend_schema_field
|
||||
from rest_framework import serializers
|
||||
|
||||
from akarpov.music.models import Album, Author, Song
|
||||
from akarpov.common.api import SetUserModelSerializer
|
||||
from akarpov.music.models import Album, Author, Playlist, Song
|
||||
from akarpov.users.api.serializers import UserPublicInfoSerializer
|
||||
|
||||
|
||||
class AuthorSerializer(serializers.ModelSerializer):
|
||||
url = serializers.SerializerMethodField(method_name="get_url")
|
||||
|
||||
@extend_schema_field(serializers.URLField)
|
||||
def get_url(self, obj):
|
||||
return obj.get_absolute_url()
|
||||
|
||||
|
@ -17,6 +21,7 @@ class Meta:
|
|||
class AlbumSerializer(serializers.ModelSerializer):
|
||||
url = serializers.SerializerMethodField(method_name="get_url")
|
||||
|
||||
@extend_schema_field(serializers.URLField)
|
||||
def get_url(self, obj):
|
||||
return obj.get_absolute_url()
|
||||
|
||||
|
@ -32,7 +37,6 @@ class SongSerializer(serializers.ModelSerializer):
|
|||
class Meta:
|
||||
model = Song
|
||||
fields = [
|
||||
"id",
|
||||
"image",
|
||||
"link",
|
||||
"length",
|
||||
|
@ -42,3 +46,48 @@ class Meta:
|
|||
"authors",
|
||||
"album",
|
||||
]
|
||||
extra_kwargs = {
|
||||
"slug": {"read_only": True},
|
||||
"creator": {"read_only": True},
|
||||
"length": {"read_only": True},
|
||||
"played": {"read_only": True},
|
||||
}
|
||||
|
||||
|
||||
class ListSongSerializer(SetUserModelSerializer):
|
||||
album = serializers.CharField(source="album.name", read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = Song
|
||||
fields = ["name", "slug", "file", "image_cropped", "length", "album"]
|
||||
extra_kwargs = {
|
||||
"slug": {"read_only": True},
|
||||
"image_cropped": {"read_only": True},
|
||||
"length": {"read_only": True},
|
||||
"album": {"read_only": True},
|
||||
}
|
||||
|
||||
|
||||
class PlaylistSerializer(SetUserModelSerializer):
|
||||
creator = UserPublicInfoSerializer()
|
||||
|
||||
class Meta:
|
||||
model = Playlist
|
||||
fields = ["name", "slug", "private", "creator"]
|
||||
extra_kwargs = {
|
||||
"slug": {"read_only": True},
|
||||
"creator": {"read_only": True},
|
||||
}
|
||||
|
||||
|
||||
class FullPlaylistSerializer(serializers.ModelSerializer):
|
||||
songs = ListSongSerializer(many=True, read_only=True)
|
||||
creator = UserPublicInfoSerializer(read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = Playlist
|
||||
fields = ["name", "private", "creator", "songs"]
|
||||
extra_kwargs = {
|
||||
"slug": {"read_only": True},
|
||||
"creator": {"read_only": True},
|
||||
}
|
||||
|
|
17
akarpov/music/api/urls.py
Normal file
17
akarpov/music/api/urls.py
Normal file
|
@ -0,0 +1,17 @@
|
|||
from django.urls import path
|
||||
|
||||
from akarpov.music.api.views import (
|
||||
ListCreatePlaylistAPIView,
|
||||
ListCreateSongAPIView,
|
||||
RetrieveUpdateDestroyPlaylistAPIView,
|
||||
RetrieveUpdateDestroySongAPIView,
|
||||
)
|
||||
|
||||
app_name = "music"
|
||||
|
||||
urlpatterns = [
|
||||
path("playlists/", ListCreatePlaylistAPIView.as_view()),
|
||||
path("playlists/<str:slug>", RetrieveUpdateDestroyPlaylistAPIView.as_view()),
|
||||
path("song/", ListCreateSongAPIView.as_view()),
|
||||
path("song/<str:slug>", RetrieveUpdateDestroySongAPIView.as_view()),
|
||||
]
|
58
akarpov/music/api/views.py
Normal file
58
akarpov/music/api/views.py
Normal file
|
@ -0,0 +1,58 @@
|
|||
from rest_framework import generics, permissions
|
||||
|
||||
from akarpov.common.api import IsCreatorOrReadOnly
|
||||
from akarpov.music.api.serializers import (
|
||||
FullPlaylistSerializer,
|
||||
ListSongSerializer,
|
||||
PlaylistSerializer,
|
||||
SongSerializer,
|
||||
)
|
||||
from akarpov.music.models import Playlist, Song
|
||||
|
||||
|
||||
class ListCreatePlaylistAPIView(generics.ListCreateAPIView):
|
||||
permission_classes = [permissions.IsAuthenticated]
|
||||
serializer_class = PlaylistSerializer
|
||||
|
||||
def get_queryset(self):
|
||||
return Playlist.objects.filter(creator=self.request.user)
|
||||
|
||||
|
||||
class RetrieveUpdateDestroyPlaylistAPIView(generics.RetrieveUpdateDestroyAPIView):
|
||||
lookup_field = "slug"
|
||||
lookup_url_kwarg = "slug"
|
||||
permission_classes = [IsCreatorOrReadOnly]
|
||||
serializer_class = FullPlaylistSerializer
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self.object = None
|
||||
|
||||
def get_object(self):
|
||||
if not self.object:
|
||||
self.object = super().get_object()
|
||||
return self.object
|
||||
|
||||
|
||||
class ListCreateSongAPIView(generics.ListCreateAPIView):
|
||||
serializer_class = ListSongSerializer
|
||||
permission_classes = [IsCreatorOrReadOnly]
|
||||
|
||||
def get_queryset(self):
|
||||
return Song.objects.all()
|
||||
|
||||
|
||||
class RetrieveUpdateDestroySongAPIView(generics.RetrieveUpdateDestroyAPIView):
|
||||
lookup_field = "slug"
|
||||
lookup_url_kwarg = "slug"
|
||||
permission_classes = [IsCreatorOrReadOnly]
|
||||
serializer_class = SongSerializer
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self.object = None
|
||||
|
||||
def get_object(self):
|
||||
if not self.object:
|
||||
self.object = super().get_object()
|
||||
return self.object
|
25
akarpov/music/migrations/0007_song_creator.py
Normal file
25
akarpov/music/migrations/0007_song_creator.py
Normal file
|
@ -0,0 +1,25 @@
|
|||
# Generated by Django 4.2.5 on 2023-09-27 08:06
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
("music", "0006_tempfileupload"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="song",
|
||||
name="creator",
|
||||
field=models.ForeignKey(
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
related_name="songs",
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
),
|
||||
),
|
||||
]
|
17
akarpov/music/migrations/0008_song_meta.py
Normal file
17
akarpov/music/migrations/0008_song_meta.py
Normal file
|
@ -0,0 +1,17 @@
|
|||
# Generated by Django 4.2.5 on 2023-09-29 15:31
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("music", "0007_song_creator"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="song",
|
||||
name="meta",
|
||||
field=models.JSONField(blank=True, null=True),
|
||||
),
|
||||
]
|
|
@ -0,0 +1,22 @@
|
|||
# Generated by Django 4.2.5 on 2023-09-30 11:19
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("music", "0008_song_meta"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name="songinque",
|
||||
name="name",
|
||||
field=models.CharField(blank=True, max_length=500),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="songinque",
|
||||
name="status",
|
||||
field=models.CharField(blank=True, max_length=500, null=True),
|
||||
),
|
||||
]
|
|
@ -4,6 +4,7 @@
|
|||
from akarpov.common.models import BaseImageModel
|
||||
from akarpov.tools.shortener.models import ShortLinkModel
|
||||
from akarpov.users.services.history import UserHistoryModel
|
||||
from akarpov.utils.cache import cache_model_property
|
||||
|
||||
|
||||
class Author(BaseImageModel, ShortLinkModel):
|
||||
|
@ -38,10 +39,44 @@ class Song(BaseImageModel, ShortLinkModel):
|
|||
album = models.ForeignKey(
|
||||
Album, null=True, related_name="songs", on_delete=models.SET_NULL
|
||||
)
|
||||
creator = models.ForeignKey(
|
||||
"users.User", related_name="songs", on_delete=models.SET_NULL, null=True
|
||||
)
|
||||
meta = models.JSONField(blank=True, null=True)
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse("music:song", kwargs={"slug": self.slug})
|
||||
|
||||
@property
|
||||
def full_props(self):
|
||||
if self.album_name and self.artists_names:
|
||||
return f"{self.album_name} - {self.artists_names}"
|
||||
elif self.album_name:
|
||||
return self.album_name
|
||||
elif self.artists_names:
|
||||
return self.artists_names
|
||||
return ""
|
||||
|
||||
@property
|
||||
def _album_name(self):
|
||||
if self.album and self.album.name:
|
||||
return self.album.name
|
||||
return ""
|
||||
|
||||
@property
|
||||
def _authors_names(self):
|
||||
if self.authors:
|
||||
return ", ".join(self.authors.values_list("name", flat=True))
|
||||
return ""
|
||||
|
||||
@property
|
||||
def album_name(self):
|
||||
return cache_model_property(self, "_album_name")
|
||||
|
||||
@property
|
||||
def artists_names(self):
|
||||
return cache_model_property(self, "_authors_names")
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
|
@ -80,8 +115,8 @@ class Meta:
|
|||
|
||||
|
||||
class SongInQue(models.Model):
|
||||
name = models.CharField(blank=True, max_length=250)
|
||||
status = models.CharField(null=True, blank=True, max_length=250)
|
||||
name = models.CharField(blank=True, max_length=500)
|
||||
status = models.CharField(null=True, blank=True, max_length=500)
|
||||
error = models.BooleanField(default=False)
|
||||
|
||||
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
from akarpov.music.tasks import list_tracks, process_dir, process_file
|
||||
|
||||
|
||||
def load_tracks(address: str):
|
||||
def load_tracks(address: str, user_id: int):
|
||||
if address.startswith("/"):
|
||||
process_dir.apply_async(kwargs={"path": address})
|
||||
list_tracks.apply_async(kwargs={"url": address})
|
||||
process_dir.apply_async(kwargs={"path": address, "user_id": user_id})
|
||||
list_tracks.apply_async(kwargs={"url": address, "user_id": user_id})
|
||||
|
||||
|
||||
def load_track_file(file):
|
||||
process_file.apply_async(kwargs={"path": file})
|
||||
def load_track_file(file, user_id: int):
|
||||
process_file.apply_async(kwargs={"path": file, "user_id": user_id})
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
def load_track(
|
||||
path: str,
|
||||
image_path: str | None = None,
|
||||
user_id: int | None = None,
|
||||
authors: list[str] | str | None = None,
|
||||
album: str | None = None,
|
||||
name: str | None = None,
|
||||
|
@ -20,12 +21,19 @@ def load_track(
|
|||
**kwargs,
|
||||
) -> Song:
|
||||
p_name = path.split("/")[-1]
|
||||
|
||||
if album and type(album) is str and album.startswith("['"):
|
||||
album = album.replace("['", "").replace("']", "")
|
||||
|
||||
if authors:
|
||||
authors = [Author.objects.get_or_create(name=x)[0] for x in authors if authors]
|
||||
else:
|
||||
authors = []
|
||||
if album:
|
||||
if type(album) is str:
|
||||
album = Album.objects.get_or_create(name=album)[0]
|
||||
elif type(album) is list:
|
||||
album = Album.objects.get_or_create(name=album[0])[0]
|
||||
else:
|
||||
album = None
|
||||
|
||||
|
@ -45,9 +53,11 @@ def load_track(
|
|||
tag = MP3(path, ID3=ID3)
|
||||
if image_path:
|
||||
if not image_path.endswith(".png"):
|
||||
nm = image_path
|
||||
im = Image.open(image_path)
|
||||
image_path = image_path.replace(image_path.split(".")[-1], "png")
|
||||
im.save(image_path)
|
||||
os.remove(nm)
|
||||
|
||||
song = Song(
|
||||
link=link if link else "",
|
||||
|
@ -56,6 +66,12 @@ def load_track(
|
|||
album=album,
|
||||
)
|
||||
|
||||
if user_id:
|
||||
song.user_id = user_id
|
||||
|
||||
if kwargs:
|
||||
song.meta = kwargs
|
||||
|
||||
if image_path:
|
||||
with open(path, "rb") as file, open(image_path, "rb") as image:
|
||||
song.image = File(image, name=image_path.split("/")[-1])
|
||||
|
@ -96,4 +112,10 @@ def load_track(
|
|||
tag.tags.add(TCON(text=kwargs["genre"]))
|
||||
tag.save()
|
||||
|
||||
if os.path.exists(path):
|
||||
os.remove(path)
|
||||
|
||||
if os.path.exists(image_path):
|
||||
os.remove(image_path)
|
||||
|
||||
return song
|
||||
|
|
|
@ -11,18 +11,18 @@
|
|||
from akarpov.music.services.db import load_track
|
||||
|
||||
|
||||
def load_dir(path: str):
|
||||
def load_dir(path: str, user_id: int):
|
||||
path = Path(path)
|
||||
|
||||
for f in list(path.glob("**/*.mp3")):
|
||||
process_mp3_file(str(f))
|
||||
process_mp3_file(str(f), user_id=user_id)
|
||||
|
||||
|
||||
def load_file(path: str):
|
||||
process_mp3_file(path)
|
||||
def load_file(path: str, user_id: int):
|
||||
process_mp3_file(path, user_id)
|
||||
|
||||
|
||||
def process_mp3_file(path: str) -> None:
|
||||
def process_mp3_file(path: str, user_id: int) -> None:
|
||||
tag = mutagen.File(path, easy=True)
|
||||
if "artist" in tag:
|
||||
author = tag["artist"]
|
||||
|
@ -55,6 +55,6 @@ def process_mp3_file(path: str) -> None:
|
|||
im.save(image_pth)
|
||||
except UnidentifiedImageError:
|
||||
pass
|
||||
load_track(path, image_pth, author, album, name)
|
||||
load_track(path, image_pth, user_id, author, album, name)
|
||||
if image_pth and os.path.exists(image_pth):
|
||||
os.remove(image_pth)
|
||||
|
|
|
@ -1,18 +1,13 @@
|
|||
import os
|
||||
from pathlib import Path
|
||||
from random import randint
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.files import File
|
||||
from django.utils.text import slugify
|
||||
from mutagen.easyid3 import EasyID3
|
||||
from mutagen.id3 import APIC, ID3, TCON, TORY
|
||||
from mutagen.mp3 import MP3
|
||||
from pydub import AudioSegment
|
||||
from yandex_music import Client, Playlist, Search, Track
|
||||
|
||||
from akarpov.music import tasks
|
||||
from akarpov.music.models import Album, Author, Song, SongInQue
|
||||
from akarpov.music.models import Song, SongInQue
|
||||
from akarpov.music.services.db import load_track
|
||||
|
||||
|
||||
def login() -> Client:
|
||||
|
@ -48,9 +43,8 @@ def search_ym(name: str):
|
|||
return info
|
||||
|
||||
|
||||
def load_file_meta(track: int):
|
||||
def load_file_meta(track: int, user_id: int):
|
||||
que = SongInQue.objects.create()
|
||||
try:
|
||||
client = login()
|
||||
track = client.tracks(track)[0] # type: Track
|
||||
que.name = track.title
|
||||
|
@ -67,69 +61,38 @@ def load_file_meta(track: int):
|
|||
return
|
||||
|
||||
filename = slugify(f"{track.artists[0].name} - {track.title}")
|
||||
orig_path = f"{settings.MEDIA_ROOT}/{filename}"
|
||||
orig_path = f"{settings.MEDIA_ROOT}/{filename}.mp3"
|
||||
album = track.albums[0]
|
||||
|
||||
track.download(filename=orig_path, codec="mp3")
|
||||
|
||||
path = orig_path + ".mp3"
|
||||
AudioSegment.from_file(orig_path).export(path)
|
||||
os.remove(orig_path)
|
||||
|
||||
# load album image
|
||||
img_pth = str(settings.MEDIA_ROOT + f"/_{str(randint(10000, 99999))}.png")
|
||||
|
||||
track.download_cover(filename=img_pth)
|
||||
|
||||
album = track.albums[0]
|
||||
|
||||
# set music meta
|
||||
tag = MP3(path, ID3=ID3)
|
||||
tag.tags.add(
|
||||
APIC(
|
||||
encoding=3, # 3 is for utf-8
|
||||
mime="image/png", # image/jpeg or image/png
|
||||
type=3, # 3 is for the cover image
|
||||
desc="Cover",
|
||||
data=open(img_pth, "rb").read(),
|
||||
song = load_track(
|
||||
orig_path,
|
||||
img_pth,
|
||||
user_id,
|
||||
[x.name for x in track.artists],
|
||||
album.title,
|
||||
track.title,
|
||||
release=album.release_date,
|
||||
genre=album.genre,
|
||||
)
|
||||
)
|
||||
tag.tags.add(TORY(text=str(album.year)))
|
||||
tag.tags.add(TCON(text=album.genre))
|
||||
tag.save()
|
||||
|
||||
if os.path.exists(orig_path):
|
||||
os.remove(orig_path)
|
||||
if os.path.exists(img_pth):
|
||||
os.remove(img_pth)
|
||||
tag = EasyID3(path)
|
||||
|
||||
tag["title"] = track.title
|
||||
tag["album"] = album.title
|
||||
tag["artist"] = track.artists[0].name
|
||||
|
||||
tag.save()
|
||||
|
||||
# save track
|
||||
ms_path = Path(path)
|
||||
song = Song(
|
||||
name=track.title,
|
||||
author=Author.objects.get_or_create(name=track.artists[0].name)[0],
|
||||
album=Album.objects.get_or_create(name=album.title)[0],
|
||||
)
|
||||
with ms_path.open(mode="rb") as f:
|
||||
song.file = File(f, name=ms_path.name)
|
||||
song.save()
|
||||
os.remove(path)
|
||||
que.delete()
|
||||
return song
|
||||
except Exception as e:
|
||||
que.name = e
|
||||
que.error = True
|
||||
que.save()
|
||||
return str(song)
|
||||
|
||||
|
||||
def load_playlist(link: str):
|
||||
def load_playlist(link: str, user_id: int):
|
||||
author = link.split("/")[4]
|
||||
playlist_id = link.split("/")[-1]
|
||||
|
||||
client = login()
|
||||
playlist = client.users_playlists(int(playlist_id), author) # type: Playlist
|
||||
for track in playlist.fetch_tracks():
|
||||
tasks.load_ym_file_meta.apply_async(kwargs={"track": track.track.id})
|
||||
tasks.load_ym_file_meta.apply_async(
|
||||
kwargs={"track": track.track.id, "user_id": user_id}
|
||||
)
|
||||
|
|
|
@ -64,7 +64,7 @@ def parse_description(description: str) -> list:
|
|||
return list_of_chapters
|
||||
|
||||
|
||||
def download_from_youtube_link(link: str) -> Song:
|
||||
def download_from_youtube_link(link: str, user_id: int) -> Song:
|
||||
song = None
|
||||
|
||||
with YoutubeDL(ydl_opts) as ydl:
|
||||
|
@ -118,6 +118,7 @@ def download_from_youtube_link(link: str) -> Song:
|
|||
song = load_track(
|
||||
chapter_path,
|
||||
f"{img_pth}.png",
|
||||
user_id,
|
||||
info["artists"],
|
||||
info["album_name"],
|
||||
chapters[i][2],
|
||||
|
@ -127,6 +128,7 @@ def download_from_youtube_link(link: str) -> Song:
|
|||
song = load_track(
|
||||
chapter_path,
|
||||
f"{img_pth}.png",
|
||||
user_id,
|
||||
info["artists"],
|
||||
info["album_name"],
|
||||
chapters[i][2],
|
||||
|
@ -152,6 +154,7 @@ def download_from_youtube_link(link: str) -> Song:
|
|||
song = load_track(
|
||||
path,
|
||||
f"{img_pth}.png",
|
||||
user_id,
|
||||
info["artists"],
|
||||
info["album_name"],
|
||||
title,
|
||||
|
@ -161,6 +164,7 @@ def download_from_youtube_link(link: str) -> Song:
|
|||
song = load_track(
|
||||
path,
|
||||
f"{img_pth}.png",
|
||||
user_id,
|
||||
info["artists"],
|
||||
info["album_name"],
|
||||
title,
|
||||
|
|
|
@ -11,44 +11,44 @@
|
|||
|
||||
|
||||
@shared_task
|
||||
def list_tracks(url):
|
||||
def list_tracks(url, user_id):
|
||||
if "music.yandex.ru" in url:
|
||||
yandex.load_playlist(url)
|
||||
yandex.load_playlist(url, user_id)
|
||||
elif "channel" in url or "/c/" in url:
|
||||
p = Channel(url)
|
||||
for video in p.video_urls:
|
||||
process_yb.apply_async(kwargs={"url": video})
|
||||
process_yb.apply_async(kwargs={"url": video, "user_id": user_id})
|
||||
elif "playlist" in url or "&list=" in url:
|
||||
p = Playlist(url)
|
||||
for video in p.video_urls:
|
||||
process_yb.apply_async(kwargs={"url": video})
|
||||
process_yb.apply_async(kwargs={"url": video, "user_id": user_id})
|
||||
else:
|
||||
process_yb.apply_async(kwargs={"url": url})
|
||||
process_yb.apply_async(kwargs={"url": url, "user_id": user_id})
|
||||
|
||||
return url
|
||||
|
||||
|
||||
@shared_task(max_retries=5)
|
||||
def process_yb(url):
|
||||
youtube.download_from_youtube_link(url)
|
||||
def process_yb(url, user_id):
|
||||
youtube.download_from_youtube_link(url, user_id)
|
||||
return url
|
||||
|
||||
|
||||
@shared_task
|
||||
def process_dir(path):
|
||||
load_dir(path)
|
||||
def process_dir(path, user_id):
|
||||
load_dir(path, user_id)
|
||||
return path
|
||||
|
||||
|
||||
@shared_task
|
||||
def process_file(path):
|
||||
load_file(path)
|
||||
def process_file(path, user_id):
|
||||
load_file(path, user_id)
|
||||
return path
|
||||
|
||||
|
||||
@shared_task
|
||||
def load_ym_file_meta(track):
|
||||
return yandex.load_file_meta(track)
|
||||
def load_ym_file_meta(track, user_id):
|
||||
return yandex.load_file_meta(track, user_id)
|
||||
|
||||
|
||||
@shared_task()
|
||||
|
|
|
@ -12,4 +12,5 @@
|
|||
path("author/<str:slug>", views.author_view, name="author"),
|
||||
path("playlist/<str:slug>", views.playlist_view, name="playlist"),
|
||||
path("radio/", views.radio_main_view, name="radio"),
|
||||
path("player/", views.music_player_view, name="player"),
|
||||
]
|
||||
|
|
|
@ -55,7 +55,7 @@ def get_success_url(self):
|
|||
return ""
|
||||
|
||||
def form_valid(self, form):
|
||||
load_tracks(form.data["address"])
|
||||
load_tracks(form.data["address"], user_id=self.request.user.id)
|
||||
return super().form_valid(form)
|
||||
|
||||
|
||||
|
@ -73,7 +73,7 @@ def get_success_url(self):
|
|||
def form_valid(self, form):
|
||||
for file in form.cleaned_data["file"]:
|
||||
t = TempFileUpload.objects.create(file=file)
|
||||
load_track_file(t.file.path)
|
||||
load_track_file(t.file.path, user_id=self.request.user.id)
|
||||
|
||||
return super().form_valid(form)
|
||||
|
||||
|
@ -86,3 +86,14 @@ class MainRadioView(generic.TemplateView):
|
|||
|
||||
|
||||
radio_main_view = MainRadioView.as_view()
|
||||
|
||||
|
||||
class MusicPlayerView(generic.ListView):
|
||||
template_name = "music/player.html"
|
||||
model = Song
|
||||
|
||||
def get_queryset(self):
|
||||
return Song.objects.all()
|
||||
|
||||
|
||||
music_player_view = MusicPlayerView.as_view()
|
||||
|
|
67
akarpov/static/css/music-player.css
Normal file
67
akarpov/static/css/music-player.css
Normal file
|
@ -0,0 +1,67 @@
|
|||
@import url('https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css');
|
||||
|
||||
*,*:before,*:after{outline:0;-webkit-box-sizing:border-box;box-sizing:border-box;}
|
||||
input,button{outline:none;}
|
||||
a,a:hover,a:visited{color:#ddd;text-decoration:none;}
|
||||
.flex{display:-webkit-flex;display:flex;}
|
||||
.flex-wrap{display:-webkit-flex;display:flex;-webkit-flex-wrap:wrap;flex-wrap:wrap;}
|
||||
.flex-align{-webkit-align-items:center;align-items:center;}
|
||||
.w-full{width:100%;}
|
||||
|
||||
/* HTML5 Audio Player with Playlist, source: https://codepen.io/sekedus/pen/ExxjZEz */
|
||||
#simp button,#simp input,#simp img{border:0;}
|
||||
#simp{max-width:600px;font-size:14px;font-family:"Segoe UI", Tahoma, sans-serif;text-align:initial;line-height:initial;background:#17212b;color:#ddd;margin:0 auto;border-radius:6px;overflow:hidden;}
|
||||
#simp .simp-album{padding:20px 25px 5px;}
|
||||
#simp .simp-album .simp-cover{margin-right:20px;}
|
||||
#simp .simp-album .simp-cover img{max-width:80px;width:100%;margin:0;padding:0;display:block;}
|
||||
#simp .simp-album .simp-title{font-size:120%;font-weight:bold;}
|
||||
#simp .simp-album .simp-artist{font-size:90%;color:#6c7883;}
|
||||
#simp .simp-controls{padding:15px;}
|
||||
#simp .simp-controls button{font-size:130%;width:32px;height:32px;background:none;color:#ddd;padding:7px;cursor:pointer;border:0;border-radius:3px;}
|
||||
#simp .simp-controls button[disabled]{color:#636469;cursor:initial;}
|
||||
#simp .simp-controls button:not([disabled]):hover{background:#4082bc;color:#fff;}
|
||||
#simp .simp-controls .simp-prev,#simp .simp-controls .simp-next{font-size:100%;}
|
||||
#simp .simp-controls .simp-tracker,#simp .simp-controls .simp-volume{flex:1;margin-left:10px;position:relative;}
|
||||
#simp .simp-controls .simp-buffer {position:absolute;top:50%;right:0;left:0;height:5px;margin-top:-2.5px;border-radius:100px;}
|
||||
#simp .simp-controls .simp-loading .simp-buffer {-webkit-animation:audio-progress 1s linear infinite;animation:audio-progress 1s linear infinite;background-image: linear-gradient(-45deg, #000 25%, transparent 25%, transparent 50%, #000 50%, #000 75%, transparent 75%, transparent);background-repeat:repeat-x;background-size:25px 25px;color:transparent;}
|
||||
#simp .simp-controls .simp-time,#simp .simp-controls .simp-others{margin-left:10px;}
|
||||
#simp .simp-controls .simp-volume{max-width:110px;}
|
||||
#simp .simp-controls .simp-volume .simp-mute{margin-right:5px;}
|
||||
#simp .simp-controls .simp-others .simp-active{background:#242f3d;}
|
||||
#simp .simp-controls .simp-others .simp-shide button{font-size:100%;padding:0;width:24px;height:14px;display:block;}
|
||||
#simp .simp-controls input[type=range]{-webkit-appearance:none;background:transparent;height:19px;margin:0;width:100%;display:block;position:relative;z-index:2;}
|
||||
#simp .simp-controls input[type=range]::-webkit-slider-runnable-track{background:rgba(183,197,205,.66);height:5px;border-radius:2.5px;transition:box-shadow .3s ease;position:relative;}
|
||||
#simp .simp-controls input[type=range]::-moz-range-track{background:rgba(183,197,205,.66);height:5px;border-radius:2.5px;transition:box-shadow .3s ease;position:relative;}
|
||||
#simp .simp-controls .simp-load .simp-progress::-webkit-slider-runnable-track{background:#2f3841;}
|
||||
#simp .simp-controls .simp-load .simp-progress::-moz-range-track{background:#2f3841;}
|
||||
#simp .simp-controls .simp-loading .simp-progress::-webkit-slider-runnable-track{background:rgba(255,255,255,.25);}
|
||||
#simp .simp-controls .simp-loading .simp-progress::-moz-range-track{background:rgba(255,255,255,.25);}
|
||||
#simp .simp-controls input[type=range]::-webkit-slider-thumb{-webkit-appearance:none;background:#fff;height:13px;width:13px;margin-top:-4px;cursor:pointer;border-radius:50%;box-shadow:0 1px 1px rgba(0,0,0,.15), 0 0 0 1px rgba(47,52,61,.2);}
|
||||
#simp .simp-controls input[type=range]::-moz-range-thumb{-webkit-appearance:none;background:#fff;height:13px;width:13px;cursor:pointer;border-radius:50%;box-shadow:0 1px 1px rgba(0,0,0,.15), 0 0 0 1px rgba(47,52,61,.2);}
|
||||
#simp .simp-footer{padding:10px 10px 12px;font-size:90%;text-align:center;opacity:.7;}
|
||||
#simp .simp-display{overflow:hidden;max-height:650px;transition:max-height .5s ease-in-out;}
|
||||
#simp .simp-hide{max-height:0;}
|
||||
/* playlist */
|
||||
#simp ul{margin:5px 0 0;padding:0;list-style:none;max-height:245px;}
|
||||
#simp ul li{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;display:block;margin:0;padding:8px 20px;cursor:pointer;}
|
||||
#simp ul li:last-child{padding-bottom:13px;}
|
||||
#simp ul li:nth-child(odd){background:#0e1621;}
|
||||
#simp ul li:hover{background:#242f3d;}
|
||||
#simp ul li.simp-active{background:#4082bc;color:#fff;}
|
||||
#simp ul li .simp-desc{font-size:90%;opacity:.5;margin-left:5px;}
|
||||
/* playlist scrollbar */
|
||||
#simp ul{overflow-y:auto;overflow-x:hidden;scrollbar-color:#73797f #2f3841;}
|
||||
#simp ul::-webkit-scrollbar-track{background-color:#2f3841;}
|
||||
#simp ul::-webkit-scrollbar{width:6px;background-color:#2f3841;}
|
||||
#simp ul::-webkit-scrollbar-thumb{background-color:#73797f;}
|
||||
/* progress animation */
|
||||
@-webkit-keyframes audio-progress{to{background-position:25px 0;}}
|
||||
@keyframes audio-progress{to{background-position:25px 0;}}
|
||||
/* mobile */
|
||||
@media screen and (max-width:480px) {
|
||||
#simp .simp-controls .simp-volume,#simp .simp-controls .simp-others{display:none;}
|
||||
#simp .simp-controls .simp-time{margin-right:10px;}
|
||||
}
|
||||
@media screen and (max-width:370px) {
|
||||
#simp .simp-time .simp-slash,#simp .simp-time .end-time{display:none;}
|
||||
}
|
415
akarpov/static/js/music-player.js
Normal file
415
akarpov/static/js/music-player.js
Normal file
|
@ -0,0 +1,415 @@
|
|||
function addEventListener_multi(element, eventNames, handler) {
|
||||
const events = eventNames.split(' ');
|
||||
events.forEach(e => element.addEventListener(e, handler, false));
|
||||
}
|
||||
|
||||
// Random numbers in a specific range
|
||||
function getRandom(min, max) {
|
||||
min = Math.ceil(min);
|
||||
max = Math.floor(max);
|
||||
return Math.floor(Math.random() * (max - min + 1)) + min;
|
||||
}
|
||||
|
||||
// Position element inside element
|
||||
function getRelativePos(elm) {
|
||||
const pPos = elm.parentNode.getBoundingClientRect(); // parent pos
|
||||
const cPos = elm.getBoundingClientRect(); // target pos
|
||||
const pos = {};
|
||||
|
||||
pos.top = cPos.top - pPos.top + elm.parentNode.scrollTop,
|
||||
pos.right = cPos.right - pPos.right,
|
||||
pos.bottom = cPos.bottom - pPos.bottom,
|
||||
pos.left = cPos.left - pPos.left;
|
||||
|
||||
return pos;
|
||||
}
|
||||
|
||||
function formatTime(val) {
|
||||
let h = 0, m = 0, s;
|
||||
val = parseInt(val, 10);
|
||||
if (val > 60 * 60) {
|
||||
h = parseInt(val / (60 * 60), 10);
|
||||
val -= h * 60 * 60;
|
||||
}
|
||||
if (val > 60) {
|
||||
m = parseInt(val / 60, 10);
|
||||
val -= m * 60;
|
||||
}
|
||||
s = val;
|
||||
val = (h > 0)? h + ':' : '';
|
||||
val += (m > 0)? ((m < 10 && h > 0)? '0' : '') + m + ':' : '0:';
|
||||
val += ((s < 10)? '0' : '') + s;
|
||||
return val;
|
||||
}
|
||||
|
||||
function simp_initTime() {
|
||||
simp_controls.querySelector('.start-time').innerHTML = formatTime(simp_audio.currentTime); //calculate current value time
|
||||
if (!simp_isStream) {
|
||||
simp_controls.querySelector('.end-time').innerHTML = formatTime(simp_audio.duration); //calculate total value time
|
||||
simp_progress.value = simp_audio.currentTime / simp_audio.duration * 100; //progress bar
|
||||
}
|
||||
|
||||
// ended of the audio
|
||||
if (simp_audio.currentTime == simp_audio.duration) {
|
||||
simp_controls.querySelector('.simp-plause').classList.remove('fa-pause');
|
||||
simp_controls.querySelector('.simp-plause').classList.add('fa-play');
|
||||
simp_audio.removeEventListener('timeupdate', simp_initTime);
|
||||
|
||||
if (simp_isNext) { //auto load next audio
|
||||
let elem;
|
||||
simp_a_index++;
|
||||
if (simp_a_index == simp_a_url.length) { //repeat all audio
|
||||
simp_a_index = 0;
|
||||
elem = simp_a_url[0];
|
||||
} else {
|
||||
elem = simp_a_url[simp_a_index];
|
||||
}
|
||||
simp_changeAudio(elem);
|
||||
simp_setAlbum(simp_a_index);
|
||||
} else {
|
||||
simp_isPlaying = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function simp_initAudio() {
|
||||
// if readyState more than 2, audio file has loaded
|
||||
simp_isLoaded = simp_audio.readyState == 4 ? true : false;
|
||||
simp_isStream = simp_audio.duration == 'Infinity' ? true : false;
|
||||
simp_controls.querySelector('.simp-plause').disabled = false;
|
||||
simp_progress.disabled = simp_isStream ? true : false;
|
||||
if (!simp_isStream) {
|
||||
simp_progress.parentNode.classList.remove('simp-load','simp-loading');
|
||||
simp_controls.querySelector('.end-time').innerHTML = formatTime(simp_audio.duration);
|
||||
}
|
||||
simp_audio.addEventListener('timeupdate', simp_initTime); //tracking load progress
|
||||
if (simp_isLoaded && simp_isPlaying) simp_audio.play();
|
||||
|
||||
// progress bar click event
|
||||
addEventListener_multi(simp_progress, 'touchstart mousedown', function(e) {
|
||||
if (simp_isStream) {
|
||||
e.stopPropagation();
|
||||
return false;
|
||||
}
|
||||
if (simp_audio.readyState == 4) {
|
||||
simp_audio.removeEventListener('timeupdate', simp_initTime);
|
||||
simp_audio.pause();
|
||||
}
|
||||
});
|
||||
|
||||
addEventListener_multi(simp_progress, 'touchend mouseup', function(e) {
|
||||
if (simp_isStream) {
|
||||
e.stopPropagation();
|
||||
return false;
|
||||
}
|
||||
if (simp_audio.readyState == 4) {
|
||||
simp_audio.currentTime = simp_progress.value * simp_audio.duration / 100;
|
||||
simp_audio.addEventListener('timeupdate', simp_initTime);
|
||||
if (simp_isPlaying) simp_audio.play();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function simp_loadAudio(elem) {
|
||||
simp_progress.parentNode.classList.add('simp-loading');
|
||||
simp_controls.querySelector('.simp-plause').disabled = true;
|
||||
simp_audio.querySelector('source').src = elem.dataset.src;
|
||||
simp_audio.load();
|
||||
|
||||
simp_audio.volume = parseFloat(simp_v_num / 100); //based on valume input value
|
||||
simp_audio.addEventListener('canplaythrough', simp_initAudio); //play audio without stop for buffering
|
||||
|
||||
// if audio fails to load, only IE/Edge 9.0 or above
|
||||
simp_audio.addEventListener('error', function() {
|
||||
alert('Please reload the page.');
|
||||
});
|
||||
}
|
||||
|
||||
function simp_setAlbum(index) {
|
||||
simp_cover.innerHTML = simp_a_url[index].dataset.cover ? '<div style="background:url(' + simp_a_url[index].dataset.cover + ') no-repeat;background-size:cover;width:80px;height:80px;"></div>' : '<i class="fa fa-music fa-5x"></i>';
|
||||
simp_title.innerHTML = simp_source[index].querySelector('.simp-source').innerHTML;
|
||||
simp_artist.innerHTML = simp_source[index].querySelector('.simp-desc') ? simp_source[index].querySelector('.simp-desc').innerHTML : '';
|
||||
}
|
||||
|
||||
function simp_changeAudio(elem) {
|
||||
simp_isLoaded = false;
|
||||
simp_controls.querySelector('.simp-prev').disabled = simp_a_index == 0 ? true : false;
|
||||
simp_controls.querySelector('.simp-plause').disabled = simp_auto_load ? true : false;
|
||||
simp_controls.querySelector('.simp-next').disabled = simp_a_index == simp_a_url.length-1 ? true : false;
|
||||
simp_progress.parentNode.classList.add('simp-load');
|
||||
simp_progress.disabled = true;
|
||||
simp_progress.value = 0;
|
||||
simp_controls.querySelector('.start-time').innerHTML = '00:00';
|
||||
simp_controls.querySelector('.end-time').innerHTML = '00:00';
|
||||
elem = simp_isRandom && simp_isNext ? simp_a_url[getRandom(0, simp_a_url.length-1)] : elem;
|
||||
|
||||
// playlist, audio is running
|
||||
for (let i = 0; i < simp_a_url.length; i++) {
|
||||
simp_a_url[i].parentNode.classList.remove('simp-active');
|
||||
if (simp_a_url[i] == elem) {
|
||||
simp_a_index = i;
|
||||
simp_a_url[i].parentNode.classList.add('simp-active');
|
||||
}
|
||||
}
|
||||
|
||||
// scrolling to element inside element
|
||||
const simp_active = getRelativePos(simp_source[simp_a_index]);
|
||||
simp_source[simp_a_index].parentNode.scrollTop = simp_active.top;
|
||||
|
||||
if (simp_auto_load || simp_isPlaying) simp_loadAudio(elem);
|
||||
|
||||
if (simp_isPlaying) {
|
||||
simp_controls.querySelector('.simp-plause').classList.remove('fa-play');
|
||||
simp_controls.querySelector('.simp-plause').classList.add('fa-pause');
|
||||
}
|
||||
// set native audio properties
|
||||
if('mediaSession' in navigator) {
|
||||
navigator.mediaSession.metadata = new MediaMetadata({
|
||||
title: elem.textContent,
|
||||
artist: elem.dataset.artists,
|
||||
album: elem.dataset.album,
|
||||
artwork: [
|
||||
{ src: elem.dataset.cover, sizes: '96x96', type: 'image/png' },
|
||||
{ src: elem.dataset.cover, sizes: '128x128', type: 'image/png' },
|
||||
{ src: elem.dataset.cover, sizes: '192x192', type: 'image/png' },
|
||||
{ src: elem.dataset.cover, sizes: '256x256', type: 'image/png' },
|
||||
{ src: elem.dataset.cover, sizes: '384x384', type: 'image/png' },
|
||||
{ src: elem.dataset.cover, sizes: '512x512', type: 'image/png' }
|
||||
]
|
||||
});
|
||||
navigator.mediaSession.setActionHandler('play', () => {
|
||||
let eles = document.getElementById("simp-plause").classList
|
||||
if (simp_audio.paused) {
|
||||
if (!simp_isLoaded) simp_loadAudio(simp_a_url[simp_a_index]);
|
||||
simp_audio.play();
|
||||
simp_isPlaying = true;
|
||||
eles.remove('fa-play');
|
||||
eles.add('fa-pause');
|
||||
} else {
|
||||
simp_audio.pause();
|
||||
simp_isPlaying = false;
|
||||
eles.remove('fa-pause');
|
||||
eles.add('fa-play');
|
||||
}
|
||||
});
|
||||
navigator.mediaSession.setActionHandler('pause', () => {
|
||||
let eles = document.getElementById("simp-plause").classList
|
||||
if (simp_audio.paused) {
|
||||
if (!simp_isLoaded) simp_loadAudio(simp_a_url[simp_a_index]);
|
||||
simp_audio.play();
|
||||
simp_isPlaying = true;
|
||||
eles.remove('fa-play');
|
||||
eles.add('fa-pause');
|
||||
} else {
|
||||
simp_audio.pause();
|
||||
simp_isPlaying = false;
|
||||
eles.remove('fa-pause');
|
||||
eles.add('fa-play');
|
||||
}
|
||||
});
|
||||
navigator.mediaSession.setActionHandler("previoustrack", () => {
|
||||
let eles = document.getElementById("simp-previoustrack")
|
||||
if (simp_a_index !== 0) {
|
||||
simp_a_index = simp_a_index-1;
|
||||
eles.disabled = simp_a_index == 0 ? true : false;
|
||||
}
|
||||
simp_audio.removeEventListener('timeupdate', simp_initTime);
|
||||
simp_changeAudio(simp_a_url[simp_a_index]);
|
||||
simp_setAlbum(simp_a_index);
|
||||
});
|
||||
navigator.mediaSession.setActionHandler("nexttrack", () => {
|
||||
let eles = document.getElementById("simp-nexttrack")
|
||||
if (simp_a_index !== simp_a_url.length-1) {
|
||||
simp_a_index = simp_a_index+1;
|
||||
eles.disabled = simp_a_index == simp_a_url.length-1 ? true : false;
|
||||
}
|
||||
simp_audio.removeEventListener('timeupdate', simp_initTime);
|
||||
simp_changeAudio(simp_a_url[simp_a_index]);
|
||||
simp_setAlbum(simp_a_index);
|
||||
});
|
||||
navigator.mediaSession.setActionHandler('seekbackward', (details) => {
|
||||
simp_audio.currentTime = simp_audio.currentTime - (details.seekOffset || 10);
|
||||
});
|
||||
navigator.mediaSession.setActionHandler('seekforward', (details) => {
|
||||
simp_audio.currentTime = simp_audio.currentTime + (details.seekOffset || 10);
|
||||
});
|
||||
navigator.mediaSession.setActionHandler('seekto', (details) => {
|
||||
if (details.fastSeek && 'fastSeek' in simp_audio) {
|
||||
simp_audio.fastSeek(details.seekTime);
|
||||
return;
|
||||
}
|
||||
simp_audio.currentTime = details.seekTime;
|
||||
});
|
||||
navigator.mediaSession.setActionHandler('stop', () => {
|
||||
let eles = document.getElementById("simp-plause").classList
|
||||
simp_audio.currentTime = 0;
|
||||
simp_controls.querySelector('.start-time').innerHTML = '00:00';
|
||||
if (!simp_isLoaded) simp_loadAudio(simp_a_url[simp_a_index]);
|
||||
simp_audio.play();
|
||||
simp_isPlaying = true;
|
||||
eles.remove('fa-play');
|
||||
eles.add('fa-pause');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function simp_startScript() {
|
||||
ap_simp = document.querySelector('#simp');
|
||||
simp_audio = ap_simp.querySelector('#audio');
|
||||
simp_album = ap_simp.querySelector('.simp-album');
|
||||
simp_cover = simp_album.querySelector('.simp-cover');
|
||||
simp_title = simp_album.querySelector('.simp-title');
|
||||
simp_artist = simp_album.querySelector('.simp-artist');
|
||||
simp_controls = ap_simp.querySelector('.simp-controls');
|
||||
simp_progress = simp_controls.querySelector('.simp-progress');
|
||||
simp_volume = simp_controls.querySelector('.simp-volume');
|
||||
simp_v_slider = simp_volume.querySelector('.simp-v-slider');
|
||||
simp_v_num = simp_v_slider.value; //default volume
|
||||
simp_others = simp_controls.querySelector('.simp-others');
|
||||
simp_auto_load = simp_config.auto_load; //auto load audio file
|
||||
|
||||
if (simp_config.shide_top) simp_album.parentNode.classList.toggle('simp-hide');
|
||||
if (simp_config.shide_btm) {
|
||||
simp_playlist.classList.add('simp-display');
|
||||
simp_playlist.classList.toggle('simp-hide');
|
||||
}
|
||||
|
||||
if (simp_a_url.length <= 1) {
|
||||
simp_controls.querySelector('.simp-prev').style.display = 'none';
|
||||
simp_controls.querySelector('.simp-next').style.display = 'none';
|
||||
simp_others.querySelector('.simp-plext').style.display = 'none';
|
||||
simp_others.querySelector('.simp-random').style.display = 'none';
|
||||
}
|
||||
|
||||
// Playlist listeners
|
||||
simp_source.forEach(function(item, index) {
|
||||
if (item.classList.contains('simp-active')) simp_a_index = index; //playlist contains '.simp-active'
|
||||
item.addEventListener('click', function() {
|
||||
simp_audio.removeEventListener('timeupdate', simp_initTime);
|
||||
simp_a_index = index;
|
||||
simp_changeAudio(this.querySelector('.simp-source'));
|
||||
simp_setAlbum(simp_a_index);
|
||||
});
|
||||
});
|
||||
|
||||
// FIRST AUDIO LOAD =======
|
||||
simp_changeAudio(simp_a_url[simp_a_index]);
|
||||
simp_setAlbum(simp_a_index);
|
||||
// FIRST AUDIO LOAD =======
|
||||
|
||||
// Controls listeners
|
||||
simp_controls.querySelector('.simp-plauseward').addEventListener('click', function(e) {
|
||||
const eles = e.target.classList;
|
||||
if (eles.contains('simp-plause')) {
|
||||
if (simp_audio.paused) {
|
||||
if (!simp_isLoaded) simp_loadAudio(simp_a_url[simp_a_index]);
|
||||
simp_audio.play();
|
||||
simp_isPlaying = true;
|
||||
eles.remove('fa-play');
|
||||
eles.add('fa-pause');
|
||||
} else {
|
||||
simp_audio.pause();
|
||||
simp_isPlaying = false;
|
||||
eles.remove('fa-pause');
|
||||
eles.add('fa-play');
|
||||
}
|
||||
} else {
|
||||
if (eles.contains('simp-prev') && simp_a_index != 0) {
|
||||
simp_a_index = simp_a_index-1;
|
||||
e.target.disabled = simp_a_index == 0 ? true : false;
|
||||
} else if (eles.contains('simp-next') && simp_a_index != simp_a_url.length-1) {
|
||||
simp_a_index = simp_a_index+1;
|
||||
e.target.disabled = simp_a_index == simp_a_url.length-1 ? true : false;
|
||||
}
|
||||
simp_audio.removeEventListener('timeupdate', simp_initTime);
|
||||
simp_changeAudio(simp_a_url[simp_a_index]);
|
||||
simp_setAlbum(simp_a_index);
|
||||
}
|
||||
});
|
||||
|
||||
// Audio volume
|
||||
simp_volume.addEventListener('click', function(e) {
|
||||
const eles = e.target.classList;
|
||||
if (eles.contains('simp-mute')) {
|
||||
if (eles.contains('fa-volume-up')) {
|
||||
eles.remove('fa-volume-up');
|
||||
eles.add('fa-volume-off');
|
||||
simp_v_slider.value = 0;
|
||||
} else {
|
||||
eles.remove('fa-volume-off');
|
||||
eles.add('fa-volume-up');
|
||||
simp_v_slider.value = simp_v_num;
|
||||
}
|
||||
} else {
|
||||
simp_v_num = simp_v_slider.value;
|
||||
if (simp_v_num != 0) {
|
||||
simp_controls.querySelector('.simp-mute').classList.remove('fa-volume-off');
|
||||
simp_controls.querySelector('.simp-mute').classList.add('fa-volume-up');
|
||||
}
|
||||
}
|
||||
simp_audio.volume = parseFloat(simp_v_slider.value / 100);
|
||||
});
|
||||
|
||||
// Others
|
||||
simp_others.addEventListener('click', function(e) {
|
||||
const eles = e.target.classList;
|
||||
if (eles.contains('simp-plext')) {
|
||||
simp_isNext = simp_isNext && !simp_isRandom ? false : true;
|
||||
if (!simp_isRandom) simp_isRanext = simp_isRanext ? false : true;
|
||||
eles.contains('simp-active') && !simp_isRandom ? eles.remove('simp-active') : eles.add('simp-active');
|
||||
} else if (eles.contains('simp-random')) {
|
||||
simp_isRandom = simp_isRandom ? false : true;
|
||||
if (simp_isNext && !simp_isRanext) {
|
||||
simp_isNext = false;
|
||||
simp_others.querySelector('.simp-plext').classList.remove('simp-active');
|
||||
} else {
|
||||
simp_isNext = true;
|
||||
simp_others.querySelector('.simp-plext').classList.add('simp-active');
|
||||
}
|
||||
eles.contains('simp-active') ? eles.remove('simp-active') : eles.add('simp-active');
|
||||
} else if (eles.contains('simp-shide-top')) {
|
||||
simp_album.parentNode.classList.toggle('simp-hide');
|
||||
} else if (eles.contains('simp-shide-bottom')) {
|
||||
simp_playlist.classList.add('simp-display');
|
||||
simp_playlist.classList.toggle('simp-hide');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Start simple player
|
||||
if (document.querySelector('#simp')) {
|
||||
var simp_auto_load, simp_audio, simp_album, simp_cover, simp_title, simp_artist, simp_controls, simp_progress, simp_volume, simp_v_slider, simp_v_num, simp_others;
|
||||
var ap_simp = document.querySelector('#simp');
|
||||
var simp_playlist = ap_simp.querySelector('.simp-playlist');
|
||||
var simp_source = simp_playlist.querySelectorAll('li');
|
||||
var simp_a_url = simp_playlist.querySelectorAll('[data-src]');
|
||||
var simp_a_index = 0;
|
||||
var simp_isPlaying = false;
|
||||
var simp_isNext = true; //auto play
|
||||
var simp_isRandom = false; //play random
|
||||
var simp_isRanext = false; //check if before random starts, simp_isNext value is true
|
||||
var simp_isStream = false; //radio streaming
|
||||
var simp_isLoaded = false; //audio file has loaded
|
||||
var simp_config = ap_simp.dataset.config ? JSON.parse(ap_simp.dataset.config) : {
|
||||
shide_top: false, //show/hide album
|
||||
shide_btm: false, //show/hide playlist
|
||||
auto_load: false //auto load audio file
|
||||
};
|
||||
|
||||
let simp_elem = '';
|
||||
simp_elem += '<audio id="audio" preload><source src="" type="audio/mpeg"></audio>';
|
||||
simp_elem += '<div class="simp-display"><div class="simp-album w-full flex-wrap"><div class="simp-cover"><i class="fa fa-music fa-5x"></i></div><div class="simp-info"><div class="simp-title">Title</div><div class="simp-artist">Artist</div></div></div></div>';
|
||||
simp_elem += '<div class="simp-controls flex-wrap flex-align">';
|
||||
simp_elem += '<div class="simp-plauseward flex flex-align"><button type="button" class="simp-prev fa fa-backward" id="simp-previoustrack" disabled></button><button id="simp-plause" type="button" class="simp-plause fa fa-play" disabled></button><button id="simp-nexttrack" type="button" class="simp-next fa fa-forward" disabled></button></div>';
|
||||
simp_elem += '<div class="simp-tracker simp-load"><input class="simp-progress" type="range" min="0" max="100" value="0" disabled/><div class="simp-buffer"></div></div>';
|
||||
simp_elem += '<div class="simp-time flex flex-align"><span class="start-time">00:00</span><span class="simp-slash"> / </span><span class="end-time">00:00</span></div>';
|
||||
simp_elem += '<div class="simp-volume flex flex-align"><button type="button" class="simp-mute fa fa-volume-up"></button><input class="simp-v-slider" type="range" min="0" max="100" value="100"/></div>';
|
||||
simp_elem += '<div class="simp-others flex flex-align"><button type="button" class="simp-plext fa fa-play-circle simp-active" title="Auto Play" ></button><button type="button" class="simp-random fa fa-random" title="Random"></button><div class="simp-shide"><button type="button" class="simp-shide-top fa fa-caret-up" title="Show/Hide Album"></button><button type="button" class="simp-shide-bottom fa fa-caret-down" title="Show/Hide Playlist"></button></div></div>';
|
||||
simp_elem += '</div>'; //simp-controls
|
||||
|
||||
const simp_player = document.createElement('div');
|
||||
simp_player.classList.add('simp-player');
|
||||
simp_player.innerHTML = simp_elem;
|
||||
ap_simp.insertBefore(simp_player, simp_playlist);
|
||||
simp_startScript();
|
||||
}
|
19
akarpov/templates/music/player.html
Normal file
19
akarpov/templates/music/player.html
Normal file
|
@ -0,0 +1,19 @@
|
|||
{% extends 'base.html' %}
|
||||
{% load static %}
|
||||
{% block css %}
|
||||
<link rel="stylesheet" href="{% static 'css/music-player.css' %}">
|
||||
{% endblock %}
|
||||
{% block content %}
|
||||
<div class="d-flex align-items-center justify-content-center">
|
||||
<div class="simple-audio-player flex-column" id="simp" data-config='{"shide_top":false,"shide_btm":false,"auto_load":true}'>
|
||||
<div class="simp-playlist">
|
||||
<ul>
|
||||
{% for song in song_list %}
|
||||
<li><span class="simp-source" {% if song.image %}data-cover="{{ song.image.url }}"{% endif %} data-artists="{{ song.artists_names }}" data-albumn="{{ song.album_name }}" data-src="{{ song.file.url }}">{{ song.name }}</span><span class="simp-desc">{{ song.full_props }}</span></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script src="{% static 'js/music-player.js' %}"></script>
|
||||
{% endblock content %}
|
|
@ -2,7 +2,7 @@
|
|||
{% load static %}
|
||||
{% load crispy_forms_tags %}
|
||||
|
||||
{% block title %}editing post on akarpov{% endblock %}
|
||||
{% block title %}loading music on akarpov{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<form class="pt-2" enctype="multipart/form-data" method="POST" id="designer-form">
|
||||
|
|
|
@ -60,11 +60,14 @@ def crop_image(image_path: str, length: int = 500):
|
|||
def user_file_upload_mixin(instance, filename):
|
||||
"""stores user uploaded files at their folder in media dir"""
|
||||
username = ""
|
||||
try:
|
||||
if isinstance(instance, get_user_model()):
|
||||
username = instance.username + "/"
|
||||
elif hasattr(instance, "user"):
|
||||
username = instance.user.username + "/"
|
||||
elif hasattr(instance, "creator"):
|
||||
username = instance.creator.username + "/"
|
||||
except AttributeError:
|
||||
username = "__all"
|
||||
|
||||
return os.path.join(f"uploads/{username}", filename)
|
||||
|
|
|
@ -32,6 +32,10 @@
|
|||
"blog/",
|
||||
include("akarpov.blog.api.urls", namespace="blog"),
|
||||
),
|
||||
path(
|
||||
"music/",
|
||||
include("akarpov.music.api.urls", namespace="music"),
|
||||
),
|
||||
path(
|
||||
"tools/",
|
||||
include(
|
||||
|
|
2573
poetry.lock
generated
2573
poetry.lock
generated
File diff suppressed because it is too large
Load Diff
|
@ -82,9 +82,8 @@ xvfbwrapper = "^0.2.9"
|
|||
vtk = "^9.2.6"
|
||||
ffmpeg-python = "^0.2.0"
|
||||
cairosvg = "^2.7.0"
|
||||
textract = "^1.6.5"
|
||||
spotipy = "2.16.1"
|
||||
django-robots = "^5.0"
|
||||
django-robots = "^6.1"
|
||||
django-tables2 = "^2.5.3"
|
||||
django-filter = "^23.2"
|
||||
tablib = "^3.4.0"
|
||||
|
@ -109,6 +108,7 @@ pytest-asyncio = "^0.21.1"
|
|||
pytest-lambda = "^2.2.0"
|
||||
pgvector = "^0.2.2"
|
||||
pycld2 = "^0.41"
|
||||
textract = "^1.6.5"
|
||||
|
||||
|
||||
[build-system]
|
||||
|
|
Loading…
Reference in New Issue
Block a user