mirror of
https://github.com/tcsenpai/swingmusic.git
synced 2025-06-06 19:25:34 +00:00
Compare commits
15 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
f66bca67ac | ||
![]() |
f0a8aa02a9 | ||
![]() |
077661b73c | ||
![]() |
5a918d078e | ||
![]() |
bc51150e47 | ||
![]() |
c153247918 | ||
![]() |
632447bdba | ||
![]() |
e0581259a6 | ||
![]() |
d411f04e98 | ||
![]() |
27a277cc5b | ||
![]() |
9bf005668b | ||
![]() |
cb47a4bc6e | ||
![]() |
8c44aeff3d | ||
![]() |
cb1842cc2b | ||
![]() |
77f22c2b84 |
1
.github/workflows/release.yml
vendored
1
.github/workflows/release.yml
vendored
@ -118,6 +118,7 @@ jobs:
|
|||||||
- name: Upload binaries to GitHub Release
|
- name: Upload binaries to GitHub Release
|
||||||
uses: ncipollo/release-action@v1
|
uses: ncipollo/release-action@v1
|
||||||
with:
|
with:
|
||||||
|
allowUpdates: true
|
||||||
artifacts: "./linux/swingmusic, ./win32/swingmusic.exe"
|
artifacts: "./linux/swingmusic, ./win32/swingmusic.exe"
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
tag: ${{ format('v{0}',inputs.tag) }}
|
tag: ${{ format('v{0}',inputs.tag) }}
|
||||||
|
3
.gitignore
vendored
3
.gitignore
vendored
@ -29,4 +29,5 @@ logs.txt
|
|||||||
|
|
||||||
TODO.md
|
TODO.md
|
||||||
testdata.py
|
testdata.py
|
||||||
test.py
|
test.py
|
||||||
|
nohup.out
|
||||||
|
4
.trunk/configs/.hadolint.yaml
Normal file
4
.trunk/configs/.hadolint.yaml
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
# Following source doesn't work in most setups
|
||||||
|
ignored:
|
||||||
|
- SC1090
|
||||||
|
- SC1091
|
2
.trunk/configs/.isort.cfg
Normal file
2
.trunk/configs/.isort.cfg
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
[settings]
|
||||||
|
profile=black
|
10
.trunk/configs/.markdownlint.yaml
Normal file
10
.trunk/configs/.markdownlint.yaml
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
# Autoformatter friendly markdownlint config (all formatting rules disabled)
|
||||||
|
default: true
|
||||||
|
blank_lines: false
|
||||||
|
bullet: false
|
||||||
|
html: false
|
||||||
|
indentation: false
|
||||||
|
line_length: false
|
||||||
|
spaces: false
|
||||||
|
url: false
|
||||||
|
whitespace: false
|
7
.trunk/configs/.shellcheckrc
Normal file
7
.trunk/configs/.shellcheckrc
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
enable=all
|
||||||
|
source-path=SCRIPTDIR
|
||||||
|
disable=SC2154
|
||||||
|
|
||||||
|
# If you're having issues with shellcheck following source, disable the errors via:
|
||||||
|
# disable=SC1090
|
||||||
|
# disable=SC1091
|
10
.trunk/configs/.yamllint.yaml
Normal file
10
.trunk/configs/.yamllint.yaml
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
rules:
|
||||||
|
quoted-strings:
|
||||||
|
required: only-when-needed
|
||||||
|
extra-allowed: ["{|}"]
|
||||||
|
empty-values:
|
||||||
|
forbid-in-block-mappings: true
|
||||||
|
forbid-in-flow-mappings: true
|
||||||
|
key-duplicates: {}
|
||||||
|
octal-values:
|
||||||
|
forbid-implicit-octal: true
|
5
.trunk/configs/ruff.toml
Normal file
5
.trunk/configs/ruff.toml
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
# Generic, formatter-friendly config.
|
||||||
|
select = ["B", "D3", "E", "F"]
|
||||||
|
|
||||||
|
# Never enforce `E501` (line length violations). This should be handled by formatters.
|
||||||
|
ignore = ["E501"]
|
14
.trunk/configs/svgo.config.js
Normal file
14
.trunk/configs/svgo.config.js
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
module.exports = {
|
||||||
|
plugins: [
|
||||||
|
{
|
||||||
|
name: "preset-default",
|
||||||
|
params: {
|
||||||
|
overrides: {
|
||||||
|
removeViewBox: false, // https://github.com/svg/svgo/issues/1128
|
||||||
|
sortAttrs: true,
|
||||||
|
removeOffCanvasPaths: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
46
.trunk/trunk.yaml
Normal file
46
.trunk/trunk.yaml
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
# This file controls the behavior of Trunk: https://docs.trunk.io/cli
|
||||||
|
# To learn more about the format of this file, see https://docs.trunk.io/reference/trunk-yaml
|
||||||
|
version: 0.1
|
||||||
|
cli:
|
||||||
|
version: 1.19.0
|
||||||
|
# Trunk provides extensibility via plugins. (https://docs.trunk.io/plugins)
|
||||||
|
plugins:
|
||||||
|
sources:
|
||||||
|
- id: trunk
|
||||||
|
ref: v1.4.2
|
||||||
|
uri: https://github.com/trunk-io/plugins
|
||||||
|
# Many linters and tools depend on runtimes - configure them here. (https://docs.trunk.io/runtimes)
|
||||||
|
runtimes:
|
||||||
|
enabled:
|
||||||
|
- go@1.21.0
|
||||||
|
- node@18.12.1
|
||||||
|
- python@3.10.8
|
||||||
|
# This is the section where you manage your linters. (https://docs.trunk.io/check/configuration)
|
||||||
|
lint:
|
||||||
|
enabled:
|
||||||
|
- actionlint@1.6.26
|
||||||
|
- bandit@1.7.7
|
||||||
|
- black@24.1.1
|
||||||
|
- checkov@3.2.1
|
||||||
|
- git-diff-check
|
||||||
|
- hadolint@2.12.0
|
||||||
|
- isort@5.13.2
|
||||||
|
- markdownlint@0.39.0
|
||||||
|
- osv-scanner@1.6.1
|
||||||
|
- prettier@3.2.4
|
||||||
|
- ruff@0.1.15
|
||||||
|
- shellcheck@0.9.0
|
||||||
|
- shfmt@3.6.0
|
||||||
|
- svgo@3.2.0
|
||||||
|
- taplo@0.8.1
|
||||||
|
- terrascan@1.18.11
|
||||||
|
- trivy@0.48.3
|
||||||
|
- trufflehog@3.66.1
|
||||||
|
- yamllint@1.33.0
|
||||||
|
actions:
|
||||||
|
disabled:
|
||||||
|
- trunk-announce
|
||||||
|
- trunk-check-pre-push
|
||||||
|
- trunk-fmt-pre-commit
|
||||||
|
enabled:
|
||||||
|
- trunk-upgrade-available
|
@ -6,10 +6,12 @@ COPY ./dist/swingmusic /swingmusic
|
|||||||
|
|
||||||
RUN chmod +x /swingmusic
|
RUN chmod +x /swingmusic
|
||||||
|
|
||||||
|
RUN apt update && apt install -y tzdata
|
||||||
|
|
||||||
EXPOSE 1970/tcp
|
EXPOSE 1970/tcp
|
||||||
|
|
||||||
VOLUME /music
|
VOLUME /music
|
||||||
|
|
||||||
VOLUME /config
|
VOLUME /config
|
||||||
|
|
||||||
ENTRYPOINT ["/swingmusic", "--host", "0.0.0.0", "--config", "/config"]
|
ENTRYPOINT ["/swingmusic", "--host", "0.0.0.0", "--config", "/config"]
|
||||||
|
@ -54,7 +54,8 @@ def rebuild_store(db_dirs: list[str]):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
populate.Populate(instance_key=instance_key)
|
populate.Populate(instance_key=instance_key)
|
||||||
except populate.PopulateCancelledError:
|
except populate.PopulateCancelledError as e:
|
||||||
|
print(e)
|
||||||
reload_everything(instance_key)
|
reload_everything(instance_key)
|
||||||
return
|
return
|
||||||
|
|
||||||
@ -63,7 +64,7 @@ def rebuild_store(db_dirs: list[str]):
|
|||||||
log.info("Rebuilding library... ✅")
|
log.info("Rebuilding library... ✅")
|
||||||
|
|
||||||
|
|
||||||
# I freaking don't know what this function does anymore
|
# I freaking don't know what this function does anymore
|
||||||
def finalize(new_: list[str], removed_: list[str], db_dirs_: list[str]):
|
def finalize(new_: list[str], removed_: list[str], db_dirs_: list[str]):
|
||||||
"""
|
"""
|
||||||
Params:
|
Params:
|
||||||
|
@ -56,8 +56,8 @@ class HandleArgs:
|
|||||||
value = settings.Keys.get(key)
|
value = settings.Keys.get(key)
|
||||||
|
|
||||||
if not value:
|
if not value:
|
||||||
log.error(f"ERROR: {key} not set in environment")
|
log.error(f"WARNING: {key} not set in environment")
|
||||||
sys.exit(0)
|
#sys.exit(0)
|
||||||
|
|
||||||
lines.append(f'{key} = "{value}"\n')
|
lines.append(f'{key} = "{value}"\n')
|
||||||
|
|
||||||
|
@ -104,8 +104,9 @@ class Populate:
|
|||||||
log.error(
|
log.error(
|
||||||
"Internet connection lost. Downloading artist images stopped."
|
"Internet connection lost. Downloading artist images stopped."
|
||||||
)
|
)
|
||||||
|
log.error(e) # REVIEW More informations = good
|
||||||
else:
|
else:
|
||||||
log.warning(f"No internet connection. Downloading artist images stopped!")
|
log.warning("No internet connection. Downloading artist images stopped!")
|
||||||
|
|
||||||
# Re-process the new artist images.
|
# Re-process the new artist images.
|
||||||
if tried_to_download_new_images:
|
if tried_to_download_new_images:
|
||||||
@ -113,6 +114,7 @@ class Populate:
|
|||||||
|
|
||||||
if has_connection():
|
if has_connection():
|
||||||
try:
|
try:
|
||||||
|
print("Attempting to download similar artists...")
|
||||||
FetchSimilarArtistsLastFM(instance_key)
|
FetchSimilarArtistsLastFM(instance_key)
|
||||||
except PopulateCancelledError as e:
|
except PopulateCancelledError as e:
|
||||||
log.warn(e)
|
log.warn(e)
|
||||||
@ -135,6 +137,7 @@ class Populate:
|
|||||||
unmodified_paths.add(track.filepath)
|
unmodified_paths.add(track.filepath)
|
||||||
continue
|
continue
|
||||||
except (FileNotFoundError, OSError) as e:
|
except (FileNotFoundError, OSError) as e:
|
||||||
|
log.warning(e) # REVIEW More informations = good
|
||||||
TrackStore.remove_track_obj(track)
|
TrackStore.remove_track_obj(track)
|
||||||
remove_tracks_by_filepaths(track.filepath)
|
remove_tracks_by_filepaths(track.filepath)
|
||||||
|
|
||||||
@ -286,6 +289,7 @@ def save_similar_artists(_map: tuple[str, Artist]):
|
|||||||
instance_key, artist = _map
|
instance_key, artist = _map
|
||||||
|
|
||||||
if POPULATE_KEY != instance_key:
|
if POPULATE_KEY != instance_key:
|
||||||
|
print("Warning: Populate key changed")
|
||||||
raise PopulateCancelledError(
|
raise PopulateCancelledError(
|
||||||
"'FetchSimilarArtistsLastFM': Populate key changed"
|
"'FetchSimilarArtistsLastFM': Populate key changed"
|
||||||
)
|
)
|
||||||
@ -321,6 +325,7 @@ class FetchSimilarArtistsLastFM:
|
|||||||
|
|
||||||
with ThreadPoolExecutor(max_workers=CPU_COUNT) as executor:
|
with ThreadPoolExecutor(max_workers=CPU_COUNT) as executor:
|
||||||
try:
|
try:
|
||||||
|
print("Processing similar artists")
|
||||||
results = list(
|
results = list(
|
||||||
tqdm(
|
tqdm(
|
||||||
executor.map(save_similar_artists, key_artist_map),
|
executor.map(save_similar_artists, key_artist_map),
|
||||||
|
@ -207,7 +207,7 @@ class Album:
|
|||||||
dates = (int(t.date) for t in tracks if t.date)
|
dates = (int(t.date) for t in tracks if t.date)
|
||||||
try:
|
try:
|
||||||
self.date = datetime.datetime.fromtimestamp(min(dates)).year
|
self.date = datetime.datetime.fromtimestamp(min(dates)).year
|
||||||
except ValueError:
|
except:
|
||||||
self.date = datetime.datetime.now().year
|
self.date = datetime.datetime.now().year
|
||||||
|
|
||||||
def set_count(self, count: int):
|
def set_count(self, count: int):
|
||||||
|
@ -6,7 +6,7 @@ import urllib.parse
|
|||||||
import requests
|
import requests
|
||||||
from requests import ConnectionError, HTTPError, ReadTimeout
|
from requests import ConnectionError, HTTPError, ReadTimeout
|
||||||
|
|
||||||
from app import settings
|
#from app import settings
|
||||||
from app.utils.hashing import create_hash
|
from app.utils.hashing import create_hash
|
||||||
|
|
||||||
|
|
||||||
@ -14,8 +14,11 @@ def fetch_similar_artists(name: str):
|
|||||||
"""
|
"""
|
||||||
Fetches similar artists from Last.fm
|
Fetches similar artists from Last.fm
|
||||||
"""
|
"""
|
||||||
url = f"https://ws.audioscrobbler.com/2.0/?method=artist.getsimilar&artist={urllib.parse.quote_plus(name, safe='')}&api_key={settings.Keys.LASTFM_API_KEY}&format=json&limit=250"
|
|
||||||
|
|
||||||
|
# REVIEW This is the old way of doing it. The new way is to use the Kerve API.
|
||||||
|
#url = f"https://ws.audioscrobbler.com/2.0/?method=artist.getsimilar&artist={urllib.parse.quote_plus(name, safe='')}&api_key={settings.Keys.LASTFM_API_KEY}&format=json&limit=250"
|
||||||
|
# TODO Cannot be tested due to PR message
|
||||||
|
url = f"https://kerve.last.fm/kerve/similarartists?artist={urllib.parse.quote_plus(name, safe='')}&autocorrect=1&tracks=1&image_size=large&limit=250&format=json"
|
||||||
try:
|
try:
|
||||||
response = requests.get(url, timeout=10)
|
response = requests.get(url, timeout=10)
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
@ -25,7 +28,8 @@ def fetch_similar_artists(name: str):
|
|||||||
data = response.json()
|
data = response.json()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
artists = data["similarartists"]["artist"]
|
#artists = data["similarartists"]["artist"]
|
||||||
|
artists = data["results"]["artist"]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
@ -242,6 +242,9 @@ class Keys:
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def load(cls):
|
def load(cls):
|
||||||
|
# TODO Remove this. Just an handy flag to test the app without the API key
|
||||||
|
# IS_BUILD = True
|
||||||
|
|
||||||
if IS_BUILD:
|
if IS_BUILD:
|
||||||
cls.LASTFM_API_KEY = configs.LASTFM_API_KEY
|
cls.LASTFM_API_KEY = configs.LASTFM_API_KEY
|
||||||
cls.PLUGIN_LYRICS_AUTHORITY = configs.PLUGIN_LYRICS_AUTHORITY
|
cls.PLUGIN_LYRICS_AUTHORITY = configs.PLUGIN_LYRICS_AUTHORITY
|
||||||
@ -253,8 +256,9 @@ class Keys:
|
|||||||
@classmethod
|
@classmethod
|
||||||
def verify_keys(cls):
|
def verify_keys(cls):
|
||||||
if not cls.LASTFM_API_KEY:
|
if not cls.LASTFM_API_KEY:
|
||||||
print("ERROR: LASTFM_API_KEY not set in environment")
|
# REVIEW Ideally, this shouldn't be fatal
|
||||||
sys.exit(0)
|
print("WARNING: LASTFM_API_KEY not set in environment. Experimental API calls will be implemented")
|
||||||
|
#sys.exit(0)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get(cls, key: str):
|
def get(cls, key: str):
|
||||||
|
@ -5,7 +5,16 @@ from unidecode import unidecode
|
|||||||
|
|
||||||
def create_hash(*args: str, decode=False, limit=10) -> str:
|
def create_hash(*args: str, decode=False, limit=10) -> str:
|
||||||
"""
|
"""
|
||||||
Creates a simple hash for an album
|
This function creates a case-insensitive, non-alphanumeric chars ignoring hash from the given arguments.
|
||||||
|
|
||||||
|
Example use case:
|
||||||
|
- Creating computable IDs for duplicate artists. eg. Juice WRLD and Juice Wrld should have the same ID.
|
||||||
|
|
||||||
|
:param args: The arguments to hash.
|
||||||
|
:param decode: Whether to decode the arguments before hashing.
|
||||||
|
:param limit: The number of characters to return.
|
||||||
|
|
||||||
|
:return: The hash.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def remove_non_alnum(token: str) -> str:
|
def remove_non_alnum(token: str) -> str:
|
||||||
@ -23,5 +32,11 @@ def create_hash(*args: str, decode=False, limit=10) -> str:
|
|||||||
str_ = unidecode(str_)
|
str_ = unidecode(str_)
|
||||||
|
|
||||||
str_ = str_.encode("utf-8")
|
str_ = str_.encode("utf-8")
|
||||||
str_ = hashlib.sha256(str_).hexdigest()
|
str_ = hashlib.sha1(str_).hexdigest()
|
||||||
return str_[-limit:]
|
# REVIEW Switched to sha1 hashlib.sha256(str_).hexdigest()
|
||||||
|
|
||||||
|
# REVIEW Take the first limit/2 and last limit/2 characters
|
||||||
|
# This is to avoid collisions
|
||||||
|
return str_[:limit // 2] + str_[-limit // 2:] if limit % 2 == 0 else str_[:limit // 2] + str_[-limit // 2 - 1:]
|
||||||
|
|
||||||
|
# return str_[-limit:]
|
||||||
|
@ -1,12 +1,14 @@
|
|||||||
import socket as Socket
|
import socket as Socket
|
||||||
|
|
||||||
|
|
||||||
def has_connection(host="8.8.8.8", port=53, timeout=3):
|
def has_connection(host="google.it", port=80, timeout=3):
|
||||||
"""
|
"""
|
||||||
|
# REVIEW Was:
|
||||||
Host: 8.8.8.8 (google-public-dns-a.google.com)
|
Host: 8.8.8.8 (google-public-dns-a.google.com)
|
||||||
OpenPort: 53/tcp
|
OpenPort: 53/tcp
|
||||||
Service: domain (DNS/TCP)
|
Service: domain (DNS/TCP)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
try:
|
try:
|
||||||
Socket.setdefaulttimeout(timeout)
|
Socket.setdefaulttimeout(timeout)
|
||||||
Socket.socket(Socket.AF_INET, Socket.SOCK_STREAM).connect((host, port))
|
Socket.socket(Socket.AF_INET, Socket.SOCK_STREAM).connect((host, port))
|
||||||
|
18
build.sh
18
build.sh
@ -1,9 +1,19 @@
|
|||||||
#!/bin/zsh
|
#!/bin/bash
|
||||||
|
# REVIEW Above: bash is way more compatible than other shells
|
||||||
|
|
||||||
# builds the latest version of the client and server
|
# builds the latest version of the client and server
|
||||||
|
|
||||||
cd ../swingmusic-client
|
# REVIEW These are not useful if you dont have the source code
|
||||||
yarn build --outDir ../swingmusic/client
|
#cd ../swingmusic-client
|
||||||
|
#yarn build --outDir ../swingmusic/client
|
||||||
|
#../swingmusic
|
||||||
|
|
||||||
cd ../swingmusic
|
# REVIEW Cleaning up
|
||||||
|
rm -rf build dist
|
||||||
|
|
||||||
|
# REVIEW Install poetry & requirements
|
||||||
|
poetry || pip install poetry
|
||||||
|
poetry install --no-root
|
||||||
|
|
||||||
|
# Build the app
|
||||||
poetry run python manage.py --build
|
poetry run python manage.py --build
|
16
dev_build.sh
Normal file
16
dev_build.sh
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# REVIEW Above: bash is way more compatible than other shells
|
||||||
|
|
||||||
|
# builds the latest version of the client and server
|
||||||
|
|
||||||
|
# NOTE Changes directory to the webclient directory and builds it
|
||||||
|
cd ../swingmusic-client || exit # REVIEW Failsafe exit
|
||||||
|
yarn build --outDir ../swingmusic/client
|
||||||
|
cd ../swingmusic || exit # REVIEW Failsafe exit
|
||||||
|
|
||||||
|
# REVIEW Optional cleaning up
|
||||||
|
# rm -rf build dist
|
||||||
|
|
||||||
|
# REVIEW Install poetry & requirements
|
||||||
|
# Build the app
|
||||||
|
poetry run python manage.py --build
|
Loading…
x
Reference in New Issue
Block a user