From 6df15fe77fb9fc17eba5320d8c8b52ad89b5dd9e Mon Sep 17 00:00:00 2001 From: geoffrey45 Date: Wed, 3 Aug 2022 16:16:03 +0300 Subject: [PATCH] move server code to alice-core repo ~ remove server code from this repo --- server/app/__init__.py | 30 -- server/app/api/__init__.py | 21 - server/app/api/album.py | 113 ---- server/app/api/artist.py | 57 -- server/app/api/folder.py | 28 - server/app/api/playlist.py | 143 ------ server/app/api/search.py | 216 -------- server/app/api/track.py | 40 -- server/app/db/__init__.py | 214 -------- server/app/db/mongodb/__init__.py | 80 --- server/app/db/mongodb/albums.py | 79 --- server/app/db/mongodb/artists.py | 38 -- server/app/db/mongodb/playlists.py | 85 --- server/app/db/mongodb/trackcolors.py | 30 -- server/app/db/mongodb/tracks.py | 191 ------- server/app/db/sqlite/__init__.py | 0 server/app/exceptions.py | 14 - server/app/functions.py | 180 ------- server/app/helpers.py | 226 -------- server/app/imgserver/__init__.py | 73 --- server/app/instances.py | 12 - server/app/lib/__init__.py | 3 - server/app/lib/albumslib.py | 158 ------ server/app/lib/colorlib.py | 49 -- server/app/lib/folderslib.py | 73 --- server/app/lib/playlistlib.py | 157 ------ server/app/lib/populate.py | 157 ------ server/app/lib/searchlib.py | 128 ----- server/app/lib/taglib.py | 194 ------- server/app/lib/trackslib.py | 25 - server/app/lib/watchdoge.py | 137 ----- server/app/logger.py | 48 -- server/app/models.py | 194 ------- server/app/patches.py | 3 - server/app/prep.py | 60 --- server/app/serializer.py | 80 --- server/app/settings.py | 35 -- server/assets/default.webp | Bin 1218 -> 0 bytes server/manage.py | 5 - server/poetry.lock | 742 --------------------------- server/pyproject.toml | 27 - server/roadmap.md | 39 -- server/setup/setup.sh | 11 - server/start.sh | 22 - server/wsgi.py | 5 - 45 files changed, 4222 deletions(-) delete mode 100644 server/app/__init__.py delete mode 100644 server/app/api/__init__.py delete mode 100644 server/app/api/album.py delete mode 100644 server/app/api/artist.py delete mode 100644 server/app/api/folder.py delete mode 100644 server/app/api/playlist.py delete mode 100644 server/app/api/search.py delete mode 100644 server/app/api/track.py delete mode 100644 server/app/db/__init__.py delete mode 100644 server/app/db/mongodb/__init__.py delete mode 100644 server/app/db/mongodb/albums.py delete mode 100644 server/app/db/mongodb/artists.py delete mode 100644 server/app/db/mongodb/playlists.py delete mode 100644 server/app/db/mongodb/trackcolors.py delete mode 100644 server/app/db/mongodb/tracks.py delete mode 100644 server/app/db/sqlite/__init__.py delete mode 100644 server/app/exceptions.py delete mode 100644 server/app/functions.py delete mode 100644 server/app/helpers.py delete mode 100644 server/app/imgserver/__init__.py delete mode 100644 server/app/instances.py delete mode 100644 server/app/lib/__init__.py delete mode 100644 server/app/lib/albumslib.py delete mode 100644 server/app/lib/colorlib.py delete mode 100644 server/app/lib/folderslib.py delete mode 100644 server/app/lib/playlistlib.py delete mode 100644 server/app/lib/populate.py delete mode 100644 server/app/lib/searchlib.py delete mode 100644 server/app/lib/taglib.py delete mode 100644 server/app/lib/trackslib.py delete mode 100644 server/app/lib/watchdoge.py delete mode 100644 server/app/logger.py delete mode 100644 server/app/models.py delete mode 100644 server/app/patches.py delete mode 100644 server/app/prep.py delete mode 100644 server/app/serializer.py delete mode 100644 server/app/settings.py delete mode 100644 server/assets/default.webp delete mode 100644 server/manage.py delete mode 100644 server/poetry.lock delete mode 100644 server/pyproject.toml delete mode 100644 server/roadmap.md delete mode 100644 server/setup/setup.sh delete mode 100755 server/start.sh delete mode 100644 server/wsgi.py diff --git a/server/app/__init__.py b/server/app/__init__.py deleted file mode 100644 index b74f967d..00000000 --- a/server/app/__init__.py +++ /dev/null @@ -1,30 +0,0 @@ -from flask import Flask -from flask_caching import Cache -from flask_cors import CORS - -config = {"CACHE_TYPE": "SimpleCache", "CACHE_DEFAULT_TIMEOUT": 300} - -cache = Cache(config=config) - - -def create_app(): - """ - Creates the Flask instance, registers modules and registers all the API blueprints. - """ - app = Flask(__name__) - CORS(app) - - app.config.from_mapping(config) - cache.init_app(app) - - with app.app_context(): - from app.api import album, artist, folder, playlist, search, track - - app.register_blueprint(album.album_bp, url_prefix="/") - app.register_blueprint(artist.artist_bp, url_prefix="/") - app.register_blueprint(track.track_bp, url_prefix="/") - app.register_blueprint(search.search_bp, url_prefix="/") - app.register_blueprint(folder.folder_bp, url_prefix="/") - app.register_blueprint(playlist.playlist_bp, url_prefix="/") - - return app diff --git a/server/app/api/__init__.py b/server/app/api/__init__.py deleted file mode 100644 index 5a128083..00000000 --- a/server/app/api/__init__.py +++ /dev/null @@ -1,21 +0,0 @@ -""" -This module contains all the Flask Blueprints and API routes. It also contains all the globals list -that are used through-out the app. It handles the initialization of the watchdog, -checking and creating config dirs and starting the re-indexing process using a background thread. -""" -from app import functions -from app import helpers -from app import prep - - -@helpers.background -def initialize() -> None: - """ - Runs all the necessary setup functions. - """ - functions.start_watchdog() - prep.create_config_dir() - functions.run_checks() - - -initialize() diff --git a/server/app/api/album.py b/server/app/api/album.py deleted file mode 100644 index 80e14b5e..00000000 --- a/server/app/api/album.py +++ /dev/null @@ -1,113 +0,0 @@ -""" -Contains all the album routes. -""" -from pprint import pprint -from typing import List - -from app import api -from app import helpers -from app import instances -from app import models -from app.functions import FetchAlbumBio -from app.lib import albumslib -from flask import Blueprint -from flask import request - -album_bp = Blueprint("album", __name__, url_prefix="") - - -@album_bp.route("/") -def say_hi(): - """Returns some text for the default route""" - return "^ _ ^" - - -@album_bp.route("/albums") -def get_albums(): - """returns all the albums""" - albums = [] - - for song in api.DB_TRACKS: - al_obj = {"name": song["album"], "artist": song["artists"]} - - if al_obj not in albums: - albums.append(al_obj) - - return {"albums": albums} - - -@album_bp.route("/album", methods=["POST"]) -def get_album(): - """Returns all the tracks in the given album.""" - data = request.get_json() - albumhash = data["hash"] - error_msg = {"error": "Album not created yet."} - - tracks = instances.tracks_instance.find_tracks_by_hash(albumhash) - - if len(tracks) == 0: - return error_msg, 204 - - tracks = [models.Track(t) for t in tracks] - tracks = helpers.RemoveDuplicates(tracks)() - - album = instances.album_instance.find_album_by_hash(albumhash) - - if not album: - return error_msg, 204 - - album = models.Album(album) - - album.count = len(tracks) - try: - album.duration = sum([t.length for t in tracks]) - except AttributeError: - album.duration = 0 - - if ( - album.count == 1 - and tracks[0].title == album.title - and tracks[0].tracknumber == 1 - and tracks[0].discnumber == 1 - ): - album.is_single = True - - return {"tracks": tracks, "info": album} - - -@album_bp.route("/album/bio", methods=["POST"]) -def get_album_bio(): - """Returns the album bio for the given album.""" - data = request.get_json() - album_hash = data["hash"] - err_msg = {"bio": "No bio found"} - - album = instances.album_instance.find_album_by_hash(album_hash) - - if album is None: - return err_msg, 404 - - bio = FetchAlbumBio(album["title"], album["artist"])() - - if bio is None: - return err_msg, 404 - - return {"bio": bio} - - -@album_bp.route("/album/artists", methods=["POST"]) -def get_albumartists(): - """ - Returns a list of artists featured in a given album. - """ - - data = request.get_json() - albumhash = data["hash"] - - tracks = instances.tracks_instance.find_tracks_by_hash(albumhash) - tracks = [models.Track(t) for t in tracks] - - artists = [a for t in tracks for a in t.artists] - artists = helpers.get_normalized_artists(artists) - - return {"artists": artists} diff --git a/server/app/api/artist.py b/server/app/api/artist.py deleted file mode 100644 index 08f60aba..00000000 --- a/server/app/api/artist.py +++ /dev/null @@ -1,57 +0,0 @@ -""" -Contains all the artist(s) routes. -""" -import urllib - -from app import cache -from app import helpers -from app import instances -from flask import Blueprint - -artist_bp = Blueprint("artist", __name__, url_prefix="/") - -# @artist_bp.route("/artist/") -# @cache.cached() -# def get_artist_data(artist: str): -# """Returns the artist's data, tracks and albums""" -# artist = urllib.parse.unquote(artist) -# artist_obj = instances.artist_instance.get_artists_by_name(artist) - -# def get_artist_tracks(): -# songs = instances.tracks_instance.find_songs_by_artist(artist) - -# return songs - -# artist_songs = get_artist_tracks() -# songs = helpers.remove_duplicates(artist_songs) - -# def get_artist_albums(): -# artist_albums = [] -# albums_with_count = [] - -# albums = instances.tracks_instance.find_songs_by_albumartist(artist) - -# for song in albums: -# if song["album"] not in artist_albums: -# artist_albums.append(song["album"]) - -# for album in artist_albums: -# count = 0 -# length = 0 - -# for song in artist_songs: -# if song["album"] == album: -# count = count + 1 -# length = length + song["length"] - -# album_ = {"title": album, "count": count, "length": length} - -# albums_with_count.append(album_) - -# return albums_with_count - -# return { -# "artist": artist_obj, -# "songs": songs, -# "albums": get_artist_albums() -# } diff --git a/server/app/api/folder.py b/server/app/api/folder.py deleted file mode 100644 index 1cbb0570..00000000 --- a/server/app/api/folder.py +++ /dev/null @@ -1,28 +0,0 @@ -""" -Contains all the folder routes. -""" -from app import settings -from app.lib.folderslib import getFnF -from flask import Blueprint -from flask import request - -folder_bp = Blueprint("folder", __name__, url_prefix="/") - - -@folder_bp.route("/folder", methods=["POST"]) -def get_folder_tree(): - """ - Returns a list of all the folders and tracks in the given folder. - """ - data = request.get_json() - req_dir: str = data["folder"] - - if req_dir == "$home": - req_dir = settings.HOME_DIR - - tracks, folders = getFnF(req_dir)() - - return { - "tracks": tracks, - "folders": sorted(folders, key=lambda i: i.name), - } diff --git a/server/app/api/playlist.py b/server/app/api/playlist.py deleted file mode 100644 index d6fd19e1..00000000 --- a/server/app/api/playlist.py +++ /dev/null @@ -1,143 +0,0 @@ -""" -Contains all the playlist routes. -""" -from datetime import datetime - -from app import exceptions -from app import instances -from app import models -from app import serializer -from app.helpers import create_new_date -from app.helpers import Get -from app.helpers import UseBisection -from app.lib import playlistlib -from flask import Blueprint -from flask import request - -playlist_bp = Blueprint("playlist", __name__, url_prefix="/") - -PlaylistExists = exceptions.PlaylistExistsError -TrackExistsInPlaylist = exceptions.TrackExistsInPlaylistError - - -@playlist_bp.route("/playlists", methods=["GET"]) -def get_all_playlists(): - """Returns all the playlists.""" - dbplaylists = instances.playlist_instance.get_all_playlists() - dbplaylists = [models.Playlist(p) for p in dbplaylists] - - playlists = [ - serializer.Playlist(p, construct_last_updated=False) for p in dbplaylists - ] - playlists.sort( - key=lambda p: datetime.strptime(p.lastUpdated, "%Y-%m-%d %H:%M:%S"), - reverse=True, - ) - return {"data": playlists} - - -@playlist_bp.route("/playlist/new", methods=["POST"]) -def create_playlist(): - data = request.get_json() - - data = { - "name": data["name"], - "description": "", - "pre_tracks": [], - "lastUpdated": create_new_date(), - "image": "", - "thumb": "", - } - - dbp = instances.playlist_instance.get_playlist_by_name(data["name"]) - - if dbp is not None: - return {"message": "Playlist already exists."}, 409 - - upsert_id = instances.playlist_instance.insert_playlist(data) - p = instances.playlist_instance.get_playlist_by_id(upsert_id) - playlist = models.Playlist(p) - - return {"playlist": playlist}, 201 - - -@playlist_bp.route("/playlist//add", methods=["POST"]) -def add_track_to_playlist(playlist_id: str): - data = request.get_json() - - trackid = data["track"] - - try: - playlistlib.add_track(playlist_id, trackid) - except TrackExistsInPlaylist: - return {"error": "Track already exists in playlist"}, 409 - - return {"msg": "I think It's done"}, 200 - - -@playlist_bp.route("/playlist/") -def get_playlist(playlistid: str): - p = instances.playlist_instance.get_playlist_by_id(playlistid) - if p is None: - return {"info": {}, "tracks": []} - - playlist = models.Playlist(p) - - tracks = playlistlib.create_playlist_tracks(playlist.pretracks) - - duration = sum([t.length for t in tracks]) - playlist = serializer.Playlist(playlist) - playlist.duration = duration - - return {"info": playlist, "tracks": tracks} - - -@playlist_bp.route("/playlist//update", methods=["PUT"]) -def update_playlist(playlistid: str): - image = None - - if "image" in request.files: - image = request.files["image"] - - data = request.form - - playlist = { - "name": str(data.get("name")).strip(), - "description": str(data.get("description").strip()), - "lastUpdated": create_new_date(), - "image": None, - "thumb": None, - } - - playlists = Get.get_all_playlists() - - p = UseBisection(playlists, "playlistid", [playlistid])() - p: models.Playlist = p[0] - - if playlist is not None: - if image: - image_, thumb_ = playlistlib.save_p_image(image, playlistid) - playlist["image"] = image_ - playlist["thumb"] = thumb_ - else: - playlist["image"] = p.image.split("/")[-1] - playlist["thumb"] = p.thumb.split("/")[-1] - - p.update_playlist(playlist) - instances.playlist_instance.update_playlist(playlistid, playlist) - - return { - "data": serializer.Playlist(p), - } - - return {"msg": "Something shady happened"}, 500 - - -@playlist_bp.route("/playlist/artists", methods=["POST"]) -def get_playlist_artists(): - data = request.get_json() - - pid = data["pid"] - artists = playlistlib.GetPlaylistArtists(pid)() - - return {"data": artists} diff --git a/server/app/api/search.py b/server/app/api/search.py deleted file mode 100644 index b745a166..00000000 --- a/server/app/api/search.py +++ /dev/null @@ -1,216 +0,0 @@ -""" -Contains all the search routes. -""" -from pprint import pprint -from typing import List - -from app import helpers -from app import models -from app import serializer -from app.lib import searchlib -from flask import Blueprint -from flask import request - -search_bp = Blueprint("search", __name__, url_prefix="/") - -SEARCH_RESULTS = { - "tracks": [], - "albums": [], - "artists": [], -} - - -class SearchResults: - """ - Holds all the search results. - """ - - query: str = "" - tracks: list[models.Track] = [] - albums: list[models.Album] = [] - playlists: list[models.Playlist] = [] - artists: list[models.Artist] = [] - - -class DoSearch: - """Class containing the methods that perform searching.""" - - def __init__(self, query: str) -> None: - """ - :param :str:`query`: the search query. - """ - self.query = query - SearchResults.query = query - - def search_tracks(self): - """Calls :class:`SearchTracks` which returns the tracks that fuzzily match - the search terms. Then adds them to the `SearchResults` store. - """ - self.tracks = helpers.Get.get_all_tracks() - tracks = searchlib.SearchTracks(self.tracks, self.query)() - tracks = helpers.RemoveDuplicates(tracks)() - SearchResults.tracks = tracks - - return tracks - - def search_artists(self): - """Calls :class:`SearchArtists` which returns the artists that fuzzily match - the search term. Then adds them to the `SearchResults` store. - """ - self.artists = helpers.Get.get_all_artists() - artists = searchlib.SearchArtists(self.artists, self.query)() - SearchResults.artists = artists - - return artists - - def search_albums(self): - """Calls :class:`SearchAlbums` which returns the albums that fuzzily match - the search term. Then adds them to the `SearchResults` store. - """ - albums = helpers.Get.get_all_albums() - albums = searchlib.SearchAlbums(albums, self.query)() - SearchResults.albums = albums - - return albums - - def search_playlists(self): - """Calls :class:`SearchPlaylists` which returns the playlists that fuzzily match - the search term. Then adds them to the `SearchResults` store. - """ - playlists = helpers.Get.get_all_playlists() - playlists = [serializer.Playlist(playlist) for playlist in playlists] - - playlists = searchlib.SearchPlaylists(playlists, self.query)() - SearchResults.playlists = playlists - - return playlists - - def search_all(self): - """Calls all the search methods.""" - self.search_tracks() - self.search_albums() - self.search_artists() - self.search_playlists() - - -@search_bp.route("/search/tracks", methods=["GET"]) -def search_tracks(): - """ - Searches for tracks that match the search query. - """ - - query = request.args.get("q") - if not query: - return {"error": "No query provided"}, 400 - - tracks = DoSearch(query).search_tracks() - - return { - "tracks": tracks[:6], - "more": len(tracks) > 6, - }, 200 - - -@search_bp.route("/search/albums", methods=["GET"]) -def search_albums(): - """ - Searches for albums. - """ - - query = request.args.get("q") - if not query: - return {"error": "No query provided"}, 400 - - tracks = DoSearch(query).search_albums() - - return { - "albums": tracks[:6], - "more": len(tracks) > 6, - }, 200 - - -@search_bp.route("/search/artists", methods=["GET"]) -def search_artists(): - """ - Searches for artists. - """ - - query = request.args.get("q") - if not query: - return {"error": "No query provided"}, 400 - - artists = DoSearch(query).search_artists() - - return { - "artists": artists[:6], - "more": len(artists) > 6, - }, 200 - - -@search_bp.route("/search/playlists", methods=["GET"]) -def search_playlists(): - """ - Searches for playlists. - """ - - query = request.args.get("q") - if not query: - return {"error": "No query provided"}, 400 - - playlists = DoSearch(query).search_playlists() - - return { - "playlists": playlists[:6], - "more": len(playlists) > 6, - }, 200 - - -@search_bp.route("/search/top", methods=["GET"]) -def get_top_results(): - """ - Returns the top results for the search query. - """ - - query = request.args.get("q") - if not query: - return {"error": "No query provided"}, 400 - - DoSearch(query).search_all() - - max = 2 - return { - "tracks": SearchResults.tracks[:max], - "albums": SearchResults.albums[:max], - "artists": SearchResults.artists[:max], - "playlists": SearchResults.playlists[:max], - } - - -@search_bp.route("/search/loadmore") -def search_load_more(): - """ - Returns more songs, albums or artists from a search query. - """ - type = request.args.get("type") - index = int(request.args.get("index")) - - if type == "tracks": - t = SearchResults.tracks - return { - "tracks": t[index:index + 5], - "more": len(t) > index + 5, - } - - elif type == "albums": - a = SearchResults.albums - return { - "albums": a[index:index + 6], - "more": len(a) > index + 6, - } - - elif type == "artists": - a = SearchResults.artists - return { - "artists": a[index:index + 6], - "more": len(a) > index + 6, - } diff --git a/server/app/api/track.py b/server/app/api/track.py deleted file mode 100644 index 924f6aa1..00000000 --- a/server/app/api/track.py +++ /dev/null @@ -1,40 +0,0 @@ -""" -Contains all the track routes. -""" -from app import api -from app import instances -from app import models -from flask import Blueprint -from flask import send_file - -track_bp = Blueprint("track", __name__, url_prefix="/") - - -@track_bp.route("/file/") -def send_track_file(trackid): - """ - Returns an audio file that matches the passed id to the client. - """ - track = instances.tracks_instance.get_track_by_id(trackid) - msg = {"msg": "File Not Found"} - - if track is None: - return msg, 404 - - track = models.Track(track) - type = track.filepath.split(".")[-1] - - try: - return send_file(track.filepath, mimetype=f"audio/{type}") - except FileNotFoundError: - return msg, 404 - - -@track_bp.route("/sample") -def get_sample_track(): - """ - Returns a sample track object. - """ - - return instances.tracks_instance.get_song_by_album("Legends Never Die", - "Juice WRLD") diff --git a/server/app/db/__init__.py b/server/app/db/__init__.py deleted file mode 100644 index 79a76d25..00000000 --- a/server/app/db/__init__.py +++ /dev/null @@ -1,214 +0,0 @@ -class AlbumMethods: - """ - Lists all the methods that can be found in the Albums class. - """ - - def insert_album(): - """ - Inserts a new album object into the database. - """ - pass - - def get_all_albums(): - """ - Returns all the albums in the database. - """ - pass - - def get_album_by_id(): - """ - Returns a single album matching the passed id. - """ - pass - - def get_album_by_name(): - """ - Returns a single album matching the passed name. - """ - pass - - def get_album_by_artist(): - """ - Returns a single album matching the passed artist name. - """ - pass - - -class ArtistMethods: - """ - Lists all the methods that can be found in the Artists class. - """ - - def insert_artist(): - """ - Inserts a new artist object into the database. - """ - pass - - def get_all_artists(): - """ - Returns all the artists in the database. - """ - pass - - def get_artist_by_id(): - """ - Returns an artist matching the mongo Id. - """ - pass - - def get_artists_by_name(): - """ - Returns all the artists matching the query. - """ - pass - - -class PlaylistMethods: - """ - Lists all the methods that can be found in the Playlists class. - """ - - def insert_playlist(): - """ - Inserts a new playlist object into the database. - """ - pass - - def get_all_playlists(): - """ - Returns all the playlists in the database. - """ - pass - - def get_playlist_by_id(): - """ - Returns a single playlist matching the id in the query params. - """ - pass - - def add_track_to_playlist(): - """ - Adds a track to a playlist. - """ - pass - - def get_playlist_by_name(): - """ - Returns a single playlist matching the name in the query params. - """ - pass - - def update_playlist(): - """ - Updates a playlist. - """ - pass - - -class TrackMethods: - """ - Lists all the methods that can be found in the Tracks class. - """ - - def insert_track(): - """ - Inserts a new track object into the database. - """ - pass - - def drop_db(): - """ - Drops the entire database. - """ - pass - - def get_all_tracks(): - """ - Returns all the tracks in the database. - """ - pass - - def get_track_by_id(): - """ - Returns a single track matching the id in the query params. - """ - pass - - def get_track_by_album(): - """ - Returns a single track matching the album in the query params. - """ - pass - - def search_tracks_by_album(): - """ - Returns all the tracks matching the albums in the query params (using regex). - """ - pass - - def search_tracks_by_artist(): - """ - Returns all the tracks matching the artists in the query params. - """ - pass - - def find_track_by_title(): - """ - Finds all the tracks matching the title in the query params. - """ - pass - - def find_tracks_by_album(): - """ - Finds all the tracks matching the album in the query params. - """ - pass - - def find_tracks_by_folder(): - """ - Finds all the tracks matching the folder in the query params. - """ - pass - - def find_tracks_by_artist(): - """ - Finds all the tracks matching the artist in the query params. - """ - pass - - def find_tracks_by_albumartist(): - """ - Finds all the tracks matching the album artist in the query params. - """ - pass - - def get_track_by_path(): - """ - Returns a single track matching the path in the query params. - """ - pass - - def remove_track_by_path(): - """ - Removes a track from the database. Returns a boolean indicating success or failure of the operation. - """ - pass - - def remove_track_by_id(): - """ - Removes a track from the database. Returns a boolean indicating success or failure of the operation. - """ - pass - - def find_tracks_by_hash(): - """ - Returns all the tracks matching the passed hash. - """ - pass - - def get_dir_t_count(): - """ - Returns a list of all the tracks matching the path in the query params. - """ - pass diff --git a/server/app/db/mongodb/__init__.py b/server/app/db/mongodb/__init__.py deleted file mode 100644 index e64c3209..00000000 --- a/server/app/db/mongodb/__init__.py +++ /dev/null @@ -1,80 +0,0 @@ -""" -This module creates and initiliazes a MongoDB instance. It also contains the -`convert_one()` and `conver_many()` methods for converting MongoDB cursors to Python dicts. -""" -import json - -import pymongo -from app.db import AlbumMethods -from app.db import ArtistMethods -from app.db import PlaylistMethods -from app.db import TrackMethods -from bson import json_util - - -class Mongo: - """ - The base class for all mongodb classes. - """ - - def __init__(self, database): - mongo_uri = pymongo.MongoClient() - self.db = mongo_uri[database] - - -class MongoAlbums(Mongo, AlbumMethods): - - def __init__(self): - super(MongoAlbums, self).__init__("ALICE_ALBUMS") - self.collection = self.db["ALL_ALBUMS"] - - -class MongoArtists(Mongo, ArtistMethods): - - def __init__(self): - super(MongoArtists, self).__init__("ALICE_ARTISTS") - self.collection = self.db["ALL_ARTISTS"] - - -class MongoPlaylists(Mongo, PlaylistMethods): - - def __init__(self): - super(MongoPlaylists, self).__init__("ALICE_PLAYLISTS") - self.collection = self.db["ALL_PLAYLISTS"] - - -class MongoTracks(Mongo, TrackMethods): - - def __init__(self): - super(MongoTracks, self).__init__("ALICE_MUSIC_TRACKS") - self.collection = self.db["ALL_TRACKS"] - - -# ====================================================================== # -# cursor convertion methods - - -def convert_one(song): - """ - Converts a single mongodb cursor to a json object. - """ - json_song = json.dumps(song, default=json_util.default) - loaded_song = json.loads(json_song) - - return loaded_song - - -def convert_many(array): - """ - Converts a list of mongodb cursors to a list of json objects. - """ - - songs = [] - - for song in array: - json_song = json.dumps(song, default=json_util.default) - loaded_song = json.loads(json_song) - - songs.append(loaded_song) - - return songs diff --git a/server/app/db/mongodb/albums.py b/server/app/db/mongodb/albums.py deleted file mode 100644 index 7e220760..00000000 --- a/server/app/db/mongodb/albums.py +++ /dev/null @@ -1,79 +0,0 @@ -""" -This file contains the Album class for interacting with -album documents in MongoDB. -""" -from typing import List - -from app.db.mongodb import convert_many -from app.db.mongodb import convert_one -from app.db.mongodb import MongoAlbums -from app.models import Album -from bson import ObjectId - - -class Albums(MongoAlbums): - """ - The class for all album-related database operations. - """ - - def insert_album(self, album: Album) -> None: - """ - Inserts a new album object into the database. - """ - album = album.__dict__ - return self.collection.update_one( - { - "album": album["title"], - "artist": album["artist"] - }, - { - "$set": album - }, - upsert=True, - ).upserted_id - - def insert_many(self, albums: Album): - albums = [a.__dict__ for a in albums] - """ - Inserts multiple albums into the database. - """ - return self.collection.insert_many(albums) - - def get_all_albums(self) -> list: - """ - Returns all the albums in the database. - """ - albums = self.collection.find() - return convert_many(albums) - - def get_album_by_id(self, id: str) -> dict: - """ - Returns a single album matching the id in the query params. - """ - album = self.collection.find_one({"_id": ObjectId(id)}) - return convert_one(album) - - def get_album_by_name(self, name: str, artist: str) -> dict: - """ - Returns a single album matching the name in the query params. - """ - album = self.collection.find_one({"album": name, "artist": artist}) - return convert_one(album) - - def find_album_by_hash(self, hash: str) -> dict: - """ - Returns a single album matching the hash in the query params. - """ - album = self.collection.find_one({"hash": hash}) - return convert_one(album) - - def set_album_colors(self, colors: List[str], hash: str) -> None: - """ - Sets the colors for an album. - """ - self.collection.update_one( - {"hash": hash}, - {"$set": { - "colors": colors - }}, - ) diff --git a/server/app/db/mongodb/artists.py b/server/app/db/mongodb/artists.py deleted file mode 100644 index db449399..00000000 --- a/server/app/db/mongodb/artists.py +++ /dev/null @@ -1,38 +0,0 @@ -""" -This file contains the Artists class for interacting with artist documents in MongoDB. -""" -from app.db.mongodb import MongoArtists -from bson import ObjectId - - -class Artists(MongoArtists): - """ - The artist class for all artist related database operations. - """ - - def insert_artist(self, artist_obj: dict) -> None: - """ - Inserts an artist into the database. - """ - self.collection.update_one(artist_obj, { - "$set": artist_obj - }, - upsert=True).upserted_id - - def get_all_artists(self) -> list: - """ - Returns a list of all artists in the database. - """ - return self.collection.find() - - def get_artist_by_id(self, artist_id: str) -> dict: - """ - Returns an artist matching the mongo Id. - """ - return self.collection.find_one({"_id": ObjectId(artist_id)}) - - def get_artists_by_name(self, query: str): - """ - Returns all the artists matching the query. - """ - return self.collection.find({"name": query}).limit(20) diff --git a/server/app/db/mongodb/playlists.py b/server/app/db/mongodb/playlists.py deleted file mode 100644 index 78941f2c..00000000 --- a/server/app/db/mongodb/playlists.py +++ /dev/null @@ -1,85 +0,0 @@ -""" -This file contains the Playlists class for interacting with the playlist documents in MongoDB. -""" -from app import helpers -from app.db.mongodb import convert_many -from app.db.mongodb import convert_one -from app.db.mongodb import MongoPlaylists -from bson import ObjectId - - -class Playlists(MongoPlaylists): - """ - The class for all playlist-related database operations. - """ - - def insert_playlist(self, playlist: dict) -> None: - """ - Inserts a new playlist object into the database. - """ - return self.collection.update_one( - { - "name": playlist["name"] - }, - { - "$set": playlist - }, - upsert=True, - ).upserted_id - - def get_all_playlists(self) -> list: - """ - Returns all the playlists in the database. - """ - playlists = self.collection.find() - return convert_many(playlists) - - def get_playlist_by_id(self, id: str) -> dict: - """ - Returns a single playlist matching the id in the query params. - """ - playlist = self.collection.find_one({"_id": ObjectId(id)}) - return convert_one(playlist) - - def set_last_updated(self, playlistid: str) -> None: - """ - Sets the lastUpdated field to the current date. - """ - date = helpers.create_new_date() - - return self.collection.update_one( - {"_id": ObjectId(playlistid)}, - {"$set": { - "lastUpdated": date - }}, - ) - - def add_track_to_playlist(self, playlistid: str, track: dict) -> None: - """ - Adds a track to a playlist. - """ - self.collection.update_one( - { - "_id": ObjectId(playlistid), - }, - {"$push": { - "pre_tracks": track - }}, - ) - self.set_last_updated(playlistid) - - def get_playlist_by_name(self, name: str) -> dict: - """ - Returns a single playlist matching the name in the query params. - """ - playlist = self.collection.find_one({"name": name}) - return convert_one(playlist) - - def update_playlist(self, playlistid: str, playlist: dict) -> None: - """ - Updates a playlist. - """ - return self.collection.update_one( - {"_id": ObjectId(playlistid)}, - {"$set": playlist}, - ) diff --git a/server/app/db/mongodb/trackcolors.py b/server/app/db/mongodb/trackcolors.py deleted file mode 100644 index 09f7ef2b..00000000 --- a/server/app/db/mongodb/trackcolors.py +++ /dev/null @@ -1,30 +0,0 @@ -# """ -# This file contains the TrackColors class for interacting with Track colors documents in MongoDB. -# """ -# from app import db -# class TrackColors(db.Mongo): -# """ -# The class for all track-related database operations. -# """ -# def __init__(self): -# super(TrackColors, self).__init__("ALICE_TRACK_COLORS") -# self.collection = self.db["TRACK_COLORS"] -# def insert_track_color(self, track_color: dict) -> None: -# """ -# Inserts a new track object into the database. -# """ -# return self.collection.update_one( -# { -# "filepath": track_color["filepath"] -# }, -# { -# "$set": track_color -# }, -# upsert=True, -# ).upserted_id -# def get_track_color_by_track(self, filepath: str) -> dict: -# """ -# Returns a track color object by its filepath. -# """ -# track_color = self.collection.find_one({"filepath": filepath}) -# return db.convert_one(track_color) diff --git a/server/app/db/mongodb/tracks.py b/server/app/db/mongodb/tracks.py deleted file mode 100644 index 9504b757..00000000 --- a/server/app/db/mongodb/tracks.py +++ /dev/null @@ -1,191 +0,0 @@ -""" -This file contains the AllSongs class for interacting with track documents in MongoDB. -""" -import re - -import pymongo -from app.db.mongodb import convert_many -from app.db.mongodb import convert_one -from app.db.mongodb import MongoTracks -from bson import ObjectId - - -class Tracks(MongoTracks): - """ - The class for all track-related database operations. - """ - - # def drop_db(self): - # self.collection.drop() - - def insert_song(self, song_obj: dict) -> str: - """ - Inserts a new track object into the database. - """ - return self.collection.update_one({ - "filepath": song_obj["filepath"] - }, { - "$set": song_obj - }, - upsert=True).upserted_id - - def insert_many(self, songs: list): - """ - Inserts multiple songs into the database. - """ - return self.collection.insert_many(songs) - - def get_all_tracks(self) -> list: - """ - Returns all tracks in the database. - """ - return convert_many(self.collection.find()) - - def get_track_by_id(self, id: str) -> dict: - """ - Returns a track object by its mongodb id. - """ - song = self.collection.find_one({"_id": ObjectId(id)}) - return convert_one(song) - - def get_song_by_album(self, name: str, artist: str) -> dict: - """ - Returns a single track matching the album in the query params. - """ - song = self.collection.find_one({"album": name, "albumartist": artist}) - return convert_one(song) - - def search_songs_by_album(self, query: str) -> list: - """ - Returns all the songs matching the albums in the query params (using regex). - """ - songs = self.collection.find( - {"album": { - "$regex": query, - "$options": "i" - }}) - return convert_many(songs) - - def search_songs_by_artist(self, query: str) -> list: - """ - Returns all the songs matching the artists in the query params. - """ - songs = self.collection.find( - {"artists": { - "$regex": query, - "$options": "i" - }}) - return convert_many(songs) - - def find_song_by_title(self, query: str) -> list: - """ - Finds all the tracks matching the title in the query params. - """ - song = self.collection.find( - {"title": { - "$regex": query, - "$options": "i" - }}) - return convert_many(song) - - def find_songs_by_album(self, name: str, artist: str) -> list: - """ - Returns all the tracks exactly matching the album in the query params. - """ - songs = self.collection.find({"album": name, "albumartist": artist}) - return convert_many(songs) - - def find_songs_by_folder(self, query: str) -> list: - """ - Returns a sorted list of all the tracks exactly matching the folder in the query params - """ - songs = self.collection.find({ - "folder": query - }).sort("title", pymongo.ASCENDING) - return convert_many(songs) - - def find_songs_by_filenames(self, filenames: list) -> list: - """ - Returns a list of all the tracks matching the filenames in the query params. - """ - songs = self.collection.find({"filepath": {"$in": filenames}}) - return convert_many(songs) - - def find_songs_by_folder_og(self, query: str) -> list: - """ - Returns an unsorted list of all the track matching the folder in the query params - """ - songs = self.collection.find({"folder": query}) - return convert_many(songs) - - def get_dir_t_count(self, path: str) -> int: - """ - Returns a list of all the tracks matching the path in the query params. - """ - regex = re.compile(r"^.*" + re.escape(path) + r".*$") - - return self.collection.count_documents({"filepath": {"$regex": regex}}) - - def find_songs_by_artist(self, query: str) -> list: - """ - Returns a list of all the tracks exactly matching the artists in the query params. - """ - songs = self.collection.find({"artists": query}) - return convert_many(songs) - - def find_songs_by_albumartist(self, query: str): - """ - Returns a list of all the tracks containing the albumartist in the query params. - """ - songs = self.collection.find( - {"albumartist": { - "$regex": query, - "$options": "i" - }}) - return convert_many(songs) - - def get_song_by_path(self, path: str) -> dict: - """ - Returns a single track matching the filepath in the query params. - """ - song = self.collection.find_one({"filepath": path}) - return convert_one(song) - - def remove_song_by_filepath(self, filepath: str): - """ - Removes a single track from the database. Returns a boolean indicating success or failure of the operation. - """ - try: - self.collection.delete_one({"filepath": filepath}) - return True - except: - return False - - def remove_song_by_id(self, id: str): - """ - Removes a single track from the database. Returns a boolean indicating success or failure of the operation. - """ - try: - self.collection.delete_one({"_id": ObjectId(id)}) - return True - except: - return False - - def find_tracks_by_hash(self, hash: str) -> list: - """ - Returns a list of all the tracks matching the hash in the query params. - """ - songs = self.collection.find({"albumhash": hash}) - return convert_many(songs) - - def find_track_by_title_artists_album(self, title: str, artist: str, - album: str) -> dict: - """ - Returns a single track matching the title, artist, and album in the query params. - """ - song = self.collection.find_one({ - "title": title, - "artists": artist, - "album": album - }) - return convert_one(song) diff --git a/server/app/db/sqlite/__init__.py b/server/app/db/sqlite/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/server/app/exceptions.py b/server/app/exceptions.py deleted file mode 100644 index 6e774e9d..00000000 --- a/server/app/exceptions.py +++ /dev/null @@ -1,14 +0,0 @@ -class TrackExistsInPlaylistError(Exception): - """ - Exception raised when a track is already in a playlist. - """ - - pass - - -class PlaylistExistsError(Exception): - """ - Exception raised when a playlist already exists. - """ - - pass diff --git a/server/app/functions.py b/server/app/functions.py deleted file mode 100644 index daf2ae07..00000000 --- a/server/app/functions.py +++ /dev/null @@ -1,180 +0,0 @@ -""" -This module contains functions for the server -""" -import os -import time -from concurrent.futures import ThreadPoolExecutor -from io import BytesIO - -import requests -from app import helpers -from app import settings -from app.lib import trackslib -from app.lib import watchdoge -from app.lib.albumslib import ValidateAlbumThumbs -from app.lib.colorlib import ProcessAlbumColors -from app.lib.playlistlib import ValidatePlaylistThumbs -from app.lib.populate import CreateAlbums -from app.lib.populate import Populate -from app.logger import get_logger -from PIL import Image - -log = get_logger() - - -@helpers.background -def run_checks(): - """ - Checks for new songs every 5 minutes. - """ - ValidateAlbumThumbs() - ValidatePlaylistThumbs() - - while True: - trackslib.validate_tracks() - - Populate() - CreateAlbums() - ProcessAlbumColors() - - if helpers.Ping()(): - CheckArtistImages()() - - time.sleep(300) - - -@helpers.background -def start_watchdog(): - """ - Starts the file watcher. - """ - watchdoge.watch.run() - - -class getArtistImage: - """ - Returns an artist image url. - """ - - def __init__(self, artist: str): - self.artist = artist - - def __call__(self): - try: - url = f"https://api.deezer.com/search/artist?q={self.artist}" - response = requests.get(url) - data = response.json() - - return data["data"][0]["picture_medium"] - except requests.exceptions.ConnectionError: - time.sleep(5) - return None - except (IndexError, KeyError): - return None - - -class useImageDownloader: - def __init__(self, url: str, dest: str) -> None: - self.url = url - self.dest = dest - - def __call__(self) -> None: - try: - img = Image.open(BytesIO(requests.get(self.url).content)) - img.save(self.dest, format="webp") - img.close() - return "fetched image" - except requests.exceptions.ConnectionError: - time.sleep(5) - return "connection error" - - -class CheckArtistImages: - def __init__(self): - self.artists: list[str] = [] - log.info("Checking artist images") - - @staticmethod - def check_if_exists(img_path: str): - """ - Checks if an image exists on c. - """ - - if os.path.exists(img_path): - return True - else: - return False - - @classmethod - def download_image(cls, artistname: str): - """ - Checks if an artist image exists and downloads it if not. - - :param artistname: The artist name - """ - - img_path = ( - settings.APP_DIR - + "/images/artists/" - + helpers.create_safe_name(artistname) - + ".webp" - ) - - if cls.check_if_exists(img_path): - return "exists" - - url = getArtistImage(artistname)() - - if url is None: - return "url is none" - - return useImageDownloader(url, img_path)() - - def __call__(self): - self.artists = helpers.Get.get_all_artists() - - with ThreadPoolExecutor() as pool: - iter = pool.map(self.download_image, self.artists) - [i for i in iter] - - print("Done fetching images") - - -def fetch_album_bio(title: str, albumartist: str) -> str | None: - """ - Returns the album bio for a given album. - """ - last_fm_url = "http://ws.audioscrobbler.com/2.0/?method=album.getinfo&api_key={}&artist={}&album={}&format=json".format( - settings.LAST_FM_API_KEY, albumartist, title - ) - - try: - response = requests.get(last_fm_url) - data = response.json() - except: - return None - - try: - bio = data["album"]["wiki"]["summary"].split(' Dict[List[str], List[str]]: - """ - Scans a directory for files with a specific extension. Returns a list of files and folders in the directory. - """ - - subfolders = [] - files = [] - ext = [".flac", ".mp3"] - - for f in os.scandir(__dir): - if f.is_dir() and not f.name.startswith("."): - subfolders.append(f.path) - if f.is_file(): - if os.path.splitext(f.name)[1].lower() in ext: - files.append(f.path) - - if full or len(files) == 0: - for _dir in list(subfolders): - sf, f = run_fast_scandir(_dir, full=True) - subfolders.extend(sf) - files.extend(f) - - return subfolders, files - - -class RemoveDuplicates: - def __init__(self, tracklist: List[models.Track]) -> None: - self.tracklist = tracklist - - def __call__(self) -> List[models.Track]: - uniq_hashes = [] - [ - uniq_hashes.append(t.uniq_hash) - for t in self.tracklist - if t.uniq_hash not in uniq_hashes - ] - tracks = UseBisection(self.tracklist, "uniq_hash", uniq_hashes)() - - return tracks - - -def is_valid_file(filename: str) -> bool: - """ - Checks if a file is valid. Returns True if it is, False if it isn't. - """ - - if filename.endswith(".flac") or filename.endswith(".mp3"): - return True - else: - return False - - -def create_hash(*args: List[str]) -> str: - """ - Creates a simple hash for an album - """ - string = "".join(a for a in args).replace(" ", "") - return "".join([i for i in string if i.isalnum()]).lower() - - -def create_new_date(): - now = datetime.now() - str = now.strftime("%Y-%m-%d %H:%M:%S") - return str - - -def create_safe_name(name: str) -> str: - """ - Creates a url-safe name from a name. - """ - return "".join([i for i in name if i.isalnum()]).lower() - - -class UseBisection: - """ - Uses bisection to find a list of items in another list. - - returns a list of found items with `None` items being not found - items. - """ - - def __init__(self, source: List, search_from: str, queries: List[str]) -> None: - self.source_list = source - self.queries_list = queries - self.attr = search_from - self.source_list.sort(key=lambda x: getattr(x, search_from)) - - def find(self, query: str): - left = 0 - right = len(self.source_list) - 1 - - while left <= right: - mid = (left + right) // 2 - if self.source_list[mid].__getattribute__(self.attr) == query: - return self.source_list[mid] - elif self.source_list[mid].__getattribute__(self.attr) > query: - right = mid - 1 - else: - left = mid + 1 - - return None - - def __call__(self) -> List: - if len(self.source_list) == 0: - print("🚀🚀🚀🚀🚀🚀🚀") - return [None] - - return [self.find(query) for query in self.queries_list] - - -class Get: - @staticmethod - def get_all_tracks() -> List[models.Track]: - """ - Returns all tracks - """ - t = instances.tracks_instance.get_all_tracks() - return [models.Track(t) for t in t] - - def get_all_albums() -> List[models.Album]: - """ - Returns all albums - """ - a = instances.album_instance.get_all_albums() - return [models.Album(a) for a in a] - - @classmethod - def get_all_artists(cls) -> Set[str]: - tracks = cls.get_all_tracks() - artists: Set[str] = set() - - for track in tracks: - for artist in track.artists: - artists.add(artist) - - return artists - - @staticmethod - def get_all_playlists() -> List[models.Playlist]: - """ - Returns all playlists - """ - p = instances.playlist_instance.get_all_playlists() - return [models.Playlist(p) for p in p] - - -class Ping: - """Checks if there is a connection to the internet by pinging google.com""" - - @staticmethod - def __call__() -> bool: - try: - requests.get("https://google.com", timeout=10) - return True - except (requests.exceptions.ConnectionError, requests.Timeout): - return False - - -def get_normal_artist_name(artists: List[str]) -> str: - """ - Returns the artist name with most capital letters. - """ - if len(artists) == 1: - return artists[0] - - artists.sort() - return artists[0] - - -def get_artist_lists(artists: List[str]) -> List[str]: - """ - Takes in a list of artists and returns a list of lists of an artist's various name variations. - - Example: - >>> get_artist_lists(['Juice WRLD', 'Juice Wrld', 'XXXtentacion', 'XXXTENTACION']) - - >>> [['Juice WRLD', 'Juice Wrld'], ['XXXtentacion', 'XXXTENTACION']] - """ - artist_lists: List[List[str]] = [] - - for artist in artists: - for list in artist_lists: - if artist.lower() == list[0].lower(): - list.append(artist) - break - else: - artist_lists.append([artist]) - - return artist_lists - - -def get_normalized_artists(names: List[str]) -> List[models.Artist]: - """ - Takes in a list of artists and returns a list of models.Artist objects with normalized names. - """ - names = [n.strip() for n in names] - names = get_artist_lists(names) - names = [get_normal_artist_name(a) for a in names] - - return [models.Artist(a) for a in names] diff --git a/server/app/imgserver/__init__.py b/server/app/imgserver/__init__.py deleted file mode 100644 index 04a3b59e..00000000 --- a/server/app/imgserver/__init__.py +++ /dev/null @@ -1,73 +0,0 @@ -from os import path -from typing import Tuple - -from flask import Flask -from flask import send_from_directory - -app = Flask(__name__) - - -def join(*args: Tuple[str]) -> str: - return path.join(*args) - - -HOME = path.expanduser("~") -APP_DIR = join(HOME, ".alice") -IMG_PATH = path.join(APP_DIR, "images") - -ASSETS_PATH = join(APP_DIR, "assets") -THUMB_PATH = join(IMG_PATH, "thumbnails") -ARTIST_PATH = join(IMG_PATH, "artists") -PLAYLIST_PATH = join(IMG_PATH, "playlists") - - -@app.route("/") -def hello(): - return "Hello mf" - - -def send_fallback_img(): - img = join(ASSETS_PATH, "default.webp") - exists = path.exists(img) - - if not exists: - return "", 404 - - return send_from_directory(ASSETS_PATH, "default.webp") - - -@app.route("/t/") -def send_thumbnail(imgpath: str): - fpath = join(THUMB_PATH, imgpath) - exists = path.exists(fpath) - - if exists: - return send_from_directory(THUMB_PATH, imgpath) - - return send_fallback_img() - - -@app.route("/a/") -def send_artist_image(imgpath: str): - fpath = join(ARTIST_PATH, imgpath) - exists = path.exists(fpath) - - if exists: - return send_from_directory(ARTIST_PATH, imgpath) - - return send_fallback_img() - - -@app.route("/p/") -def send_playlist_image(imgpath: str): - fpath = join(PLAYLIST_PATH, imgpath) - exists = path.exists(fpath) - - if exists: - return send_from_directory(PLAYLIST_PATH, imgpath) - - return send_fallback_img() - - -if __name__ == "__main__": - app.run(threaded=True, port=9877) diff --git a/server/app/instances.py b/server/app/instances.py deleted file mode 100644 index 51f8fb41..00000000 --- a/server/app/instances.py +++ /dev/null @@ -1,12 +0,0 @@ -""" -All the MongoDB instances are created here. -""" -from app.db.mongodb import albums -from app.db.mongodb import artists -from app.db.mongodb import playlists -from app.db.mongodb import tracks - -tracks_instance = tracks.Tracks() -artist_instance = artists.Artists() -album_instance = albums.Albums() -playlist_instance = playlists.Playlists() diff --git a/server/app/lib/__init__.py b/server/app/lib/__init__.py deleted file mode 100644 index d1f9259b..00000000 --- a/server/app/lib/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -""" -This module contains all the data processing and non-API libraries -""" \ No newline at end of file diff --git a/server/app/lib/albumslib.py b/server/app/lib/albumslib.py deleted file mode 100644 index efb39446..00000000 --- a/server/app/lib/albumslib.py +++ /dev/null @@ -1,158 +0,0 @@ -""" -This library contains all the functions related to albums. -""" -import os -import random -from dataclasses import dataclass -from typing import List - -from app import helpers -from app import instances -from app import models -from app.lib import taglib -from app.logger import logg -from app.settings import THUMBS_PATH -from tqdm import tqdm - - -@dataclass -class Thumbnail: - filename: str - - -class RipAlbumImage: - """ - Rips a thumbnail for the given album hash. - """ - - def __init__(self, hash: str) -> None: - tracks = instances.tracks_instance.find_tracks_by_hash(hash) - tracks = [models.Track(track) for track in tracks] - - for track in tracks: - ripped = taglib.extract_thumb(track.filepath, hash + ".webp") - - if ripped: - break - - -class ValidateAlbumThumbs: - @staticmethod - def remove_obsolete(): - """ - Removes unreferenced thumbnails from the thumbnails folder. - """ - entries = os.scandir(THUMBS_PATH) - entries = [entry for entry in entries if entry.is_file()] - - albums = helpers.Get.get_all_albums() - thumbs = [Thumbnail(album.hash + ".webp") for album in albums] - - for entry in tqdm(entries, desc="Validating thumbnails"): - e = helpers.UseBisection(thumbs, "filename", [entry.name])() - - if e is None: - os.remove(entry.path) - break - - if os.path.getsize(entry.path) == 0: - os.remove(entry.path) - - @staticmethod - def find_lost_thumbnails(): - """ - Re-rip lost album thumbnails - """ - entries = os.scandir(THUMBS_PATH) - entries = [Thumbnail(entry.name) for entry in entries if entry.is_file()] - - albums = helpers.Get.get_all_albums() - thumbs = [(album.hash + ".webp") for album in albums] - - def rip_image(t_hash: str): - e = helpers.UseBisection(entries, "filename", [t_hash])()[0] - - if e is None: - hash = t_hash.replace(".webp", "") - RipAlbumImage(hash) - - logg.info("Ripping lost album thumbnails") - # with ThreadPoolExecutor() as pool: - # i = pool.map(rip_image, thumbs) - # [a for a in i] - # ⚠️ empty lists are sent to the useBisection function as the source list. - for thumb in thumbs: - rip_image(thumb) - - logg.info("Ripping lost album thumbnails ... ✅") - - def __init__(self) -> None: - self.remove_obsolete() - self.find_lost_thumbnails() - - -def use_defaults() -> str: - """ - Returns a path to a random image in the defaults directory. - """ - path = "defaults/" + str(random.randint(0, 20)) + ".webp" - return path - - -def get_album_image(track: models.Track) -> str: - """ - Gets the image of an album. - """ - - img_p = track.albumhash + ".webp" - - success = taglib.extract_thumb(track.filepath, webp_path=img_p) - - if success: - return img_p - - return None - - -class GetAlbumTracks: - """ - Finds all the tracks that match a specific album, given the album title - and album artist. - """ - - def __init__(self, tracklist: List[models.Track], albumhash: str) -> None: - self.hash = albumhash - self.tracks = tracklist - self.tracks.sort(key=lambda x: x.albumhash) - - def __call__(self): - tracks = helpers.UseBisection(self.tracks, "albumhash", [self.hash])() - - return tracks - - -def get_album_tracks(tracklist: List[models.Track], hash: str) -> List: - return GetAlbumTracks(tracklist, hash)() - - -def create_album(track: models.Track) -> dict: - """ - Generates and returns an album object from a track object. - """ - album = { - "title": track.album, - "artist": track.albumartist, - "hash": track.albumhash, - "copyright": track.copyright, - } - - album["date"] = track.date - - img_p = get_album_image(track) - - if img_p is not None: - album["image"] = img_p - return album - - album["image"] = None - return album diff --git a/server/app/lib/colorlib.py b/server/app/lib/colorlib.py deleted file mode 100644 index 5936759b..00000000 --- a/server/app/lib/colorlib.py +++ /dev/null @@ -1,49 +0,0 @@ -import colorgram -from app import instances -from app import settings -from app.helpers import Get -from app.logger import get_logger -from app.models import Album - -log = get_logger() - - -def get_image_colors(image: str) -> list: - """Extracts 2 of the most dominant colors from an image.""" - try: - colors = sorted(colorgram.extract(image, 4), key=lambda c: c.hsl.h) - except OSError: - return [] - - formatted_colors = [] - - for color in colors: - color = f"rgb({color.rgb.r}, {color.rgb.g}, {color.rgb.b})" - formatted_colors.append(color) - - return formatted_colors - - -class ProcessAlbumColors: - - def __init__(self) -> None: - log.info("Processing album colors") - all_albums = Get.get_all_albums() - - all_albums = [a for a in all_albums if len(a.colors) == 0] - - for a in all_albums: - self.process_color(a) - - log.info("Processing album colors ... ✅") - - @staticmethod - def process_color(album: Album): - img = settings.THUMBS_PATH + "/" + album.image - - colors = get_image_colors(img) - - if len(colors) > 0: - instances.album_instance.set_album_colors(colors, album.hash) - - return colors diff --git a/server/app/lib/folderslib.py b/server/app/lib/folderslib.py deleted file mode 100644 index 795b2ed0..00000000 --- a/server/app/lib/folderslib.py +++ /dev/null @@ -1,73 +0,0 @@ -import time -from concurrent.futures import ThreadPoolExecutor -from dataclasses import dataclass -from os import scandir -from typing import Tuple - -from app import instances -from app.models import Folder -from app.models import Track - - -@dataclass -class Dir: - path: str - is_sym: bool - - -def get_folder_track_count(path: str) -> int: - """ - Returns the number of files associated with a folder. - """ - tracks = instances.tracks_instance.get_dir_t_count(path) - return len(tracks) - - -def create_folder(dir: Dir) -> Folder: - """Create a single Folder object""" - folder = { - "name": dir.path.split("/")[-1], - "path": dir.path, - "is_sym": dir.is_sym, - "trackcount": instances.tracks_instance.get_dir_t_count(dir.path), - } - - return Folder(folder) - - -class getFnF: - """ - Get files and folders from a directory. - """ - - def __init__(self, path: str) -> None: - self.path = path - - def __call__(self) -> Tuple[Track, Folder]: - try: - all = scandir(self.path) - except FileNotFoundError: - return ([], []) - - dirs, files = [], [] - - for entry in all: - if entry.is_dir() and not entry.name.startswith("."): - dir = { - "path": entry.path, - "is_sym": entry.is_symlink(), - } - dirs.append(Dir(**dir)) - elif entry.is_file() and entry.name.endswith((".mp3", ".flac")): - files.append(entry.path) - - tracks = instances.tracks_instance.find_songs_by_filenames(files) - tracks = [Track(track) for track in tracks] - - with ThreadPoolExecutor() as pool: - iter = pool.map(create_folder, dirs) - folders = [i for i in iter if i is not None] - - folders = filter(lambda f: f.trackcount > 0, folders) - - return tracks, folders diff --git a/server/app/lib/playlistlib.py b/server/app/lib/playlistlib.py deleted file mode 100644 index dadfc0e7..00000000 --- a/server/app/lib/playlistlib.py +++ /dev/null @@ -1,157 +0,0 @@ -""" -This library contains all the functions related to playlists. -""" -import os -import random -import string -from datetime import datetime -from typing import List - -from app import exceptions -from app import instances -from app import models -from app import settings -from app.helpers import Get, get_normalized_artists -from app.lib import trackslib -from app.logger import get_logger -from PIL import Image -from PIL import ImageSequence -from werkzeug import datastructures - - -TrackExistsInPlaylist = exceptions.TrackExistsInPlaylistError - -logg = get_logger() - - -def add_track(playlistid: str, trackid: str): - """ - Adds a track to a playlist to the database. - """ - tt = instances.tracks_instance.get_track_by_id(trackid) - - if tt is None: - return - - track = models.Track(tt) - - playlist = instances.playlist_instance.get_playlist_by_id(playlistid) - - track = { - "title": track.title, - "artists": tt["artists"], - "album": track.album, - } - if track in playlist["pre_tracks"]: - raise TrackExistsInPlaylist - - instances.playlist_instance.add_track_to_playlist(playlistid, track) - - -def create_thumbnail(image: any, img_path: str) -> str: - """ - Creates a 250 x 250 thumbnail from a playlist image - """ - thumb_path = "thumb_" + img_path - full_thumb_path = os.path.join(settings.APP_DIR, "images", "playlists", thumb_path) - - aspect_ratio = image.width / image.height - - new_w = round(250 * aspect_ratio) - - thumb = image.resize((new_w, 250), Image.ANTIALIAS) - thumb.save(full_thumb_path, "webp") - - return thumb_path - - -def save_p_image(file: datastructures.FileStorage, pid: str): - """ - Saves the image of a playlist to the database. - """ - img = Image.open(file) - - random_str = "".join(random.choices(string.ascii_letters + string.digits, k=5)) - - img_path = pid + str(random_str) + ".webp" - - full_img_path = os.path.join(settings.APP_DIR, "images", "playlists", img_path) - - if file.content_type == "image/gif": - frames = [] - - for frame in ImageSequence.Iterator(img): - frames.append(frame.copy()) - - frames[0].save(full_img_path, save_all=True, append_images=frames[1:]) - thumb_path = create_thumbnail(img, img_path=img_path) - - return img_path, thumb_path - - img.save(full_img_path, "webp") - thumb_path = create_thumbnail(img, img_path=img_path) - - return img_path, thumb_path - - -class ValidatePlaylistThumbs: - """ - Removes all unused images in the images/playlists folder. - """ - - def __init__(self) -> None: - images = [] - playlists = Get.get_all_playlists() - - logg.info("Validating playlist thumbnails") - for playlist in playlists: - if playlist.image: - img_path = playlist.image.split("/")[-1] - thumb_path = playlist.thumb.split("/")[-1] - - images.append(img_path) - images.append(thumb_path) - - p_path = os.path.join(settings.APP_DIR, "images", "playlists") - - for image in os.listdir(p_path): - if image not in images: - os.remove(os.path.join(p_path, image)) - - logg.info("Validating playlist thumbnails ... ✅") - - -def create_new_date(): - return datetime.now() - - -def create_playlist_tracks(playlist_tracks: List) -> List[models.Track]: - """ - Creates a list of model.Track objects from a list of playlist track dicts. - """ - tracks: List[models.Track] = [] - - for t in playlist_tracks: - track = trackslib.get_p_track(t) - - if track is not None: - tracks.append(models.Track(track)) - - return tracks - - -class GetPlaylistArtists: - """ - Returns a list of artists from a list of playlist tracks. - """ - - def __init__(self, pid: str) -> None: - self.pid = pid - p = instances.playlist_instance.get_playlist_by_id(self.pid) - self.tracks = create_playlist_tracks(p["pre_tracks"]) - - def __call__(self): - artists = set() - - artists = [a for t in self.tracks for a in t.artists] - return get_normalized_artists(artists) diff --git a/server/app/lib/populate.py b/server/app/lib/populate.py deleted file mode 100644 index 811b0787..00000000 --- a/server/app/lib/populate.py +++ /dev/null @@ -1,157 +0,0 @@ -import time -from concurrent.futures import ThreadPoolExecutor -from dataclasses import dataclass -from typing import List - -from app import instances -from app import settings -from app.helpers import create_hash -from app.helpers import Get -from app.helpers import run_fast_scandir -from app.helpers import UseBisection -from app.instances import tracks_instance -from app.lib.albumslib import create_album -from app.lib.taglib import get_tags -from app.logger import logg -from app.models import Album -from app.models import Track -from tqdm import tqdm - - -class Populate: - """ - Populate the database with all songs in the music directory - - checks if the song is in the database, if not, it adds it - also checks if the album art exists in the image path, if not tries to - extract it. - """ - - def __init__(self) -> None: - self.db_tracks = [] - self.tagged_tracks = [] - - self.files = run_fast_scandir(settings.HOME_DIR, full=True)[1] - self.db_tracks = tracks_instance.get_all_tracks() - - self.check_untagged() - self.tag_untagged() - - def check_untagged(self): - """ - Loops through all the tracks in db tracks removing each - from the list of tagged tracks if it exists. - We will now only have untagged tracks left in `files`. - """ - for track in tqdm(self.db_tracks, desc="Checking untagged"): - if track["filepath"] in self.files: - self.files.remove(track["filepath"]) - - def get_tags(self, file: str): - tags = get_tags(file) - - if tags is not None: - hash = create_hash(tags["album"], tags["albumartist"]) - tags["albumhash"] = hash - self.tagged_tracks.append(tags) - - def tag_untagged(self): - """ - Loops through all the untagged files and tags them. - """ - - logg.info("Tagging untagged tracks...") - with ThreadPoolExecutor() as executor: - executor.map(self.get_tags, self.files) - - if len(self.tagged_tracks) > 0: - tracks_instance.insert_many(self.tagged_tracks) - - logg.info(f"Tagged {len(self.tagged_tracks)} tracks.") - - -@dataclass -class PreAlbum: - title: str - artist: str - hash: str - - -class CreateAlbums: - - def __init__(self) -> None: - self.db_tracks = Get.get_all_tracks() - self.db_albums = Get.get_all_albums() - - prealbums = self.create_pre_albums(self.db_tracks) - prealbums = self.filter_processed(self.db_albums, prealbums) - - albums = [] - - for album in tqdm(prealbums, desc="Creating albums"): - a = self.create_album(album) - if a is not None: - albums.append(a) - - # with ThreadPoolExecutor() as pool: - # iterator = pool.map(self.create_album, prealbums) - - # for i in iterator: - # if i is not None: - # albums.append(i) - - if len(albums) > 0: - instances.album_instance.insert_many(albums) - - @staticmethod - def create_pre_albums(tracks: List[Track]) -> List[PreAlbum]: - prealbums = [] - - for track in tqdm(tracks, desc="Creating prealbums"): - album = { - "title": track.album, - "artist": track.albumartist, - "hash": track.albumhash, - } - - album = PreAlbum(**album) - - if album not in prealbums: - prealbums.append(album) - - return prealbums - - @staticmethod - def filter_processed(albums: List[Album], - prealbums: List[PreAlbum]) -> List[dict]: - to_process = [] - - for p in tqdm(prealbums, desc="Filtering processed albums"): - album = UseBisection(albums, "hash", [p.hash])()[0] - - if album is None: - to_process.append(p) - - return to_process - - def create_album(self, album: PreAlbum) -> Album: - hash = album.hash - - album = {"image": None} - iter = 0 - - while album["image"] is None: - track = UseBisection(self.db_tracks, "albumhash", [hash])()[0] - - if track is not None: - iter += 1 - album = create_album(track) - self.db_tracks.remove(track) - else: - album["image"] = hash + ".webp" - try: - album = Album(album) - return album - except KeyError: - print(f"📌 {iter}") - print(album) diff --git a/server/app/lib/searchlib.py b/server/app/lib/searchlib.py deleted file mode 100644 index d8b6b867..00000000 --- a/server/app/lib/searchlib.py +++ /dev/null @@ -1,128 +0,0 @@ -""" -This library contains all the functions related to the search functionality. -""" -from typing import List - -from app import api -from app import helpers -from app import models -from app.lib import albumslib -from rapidfuzz import fuzz -from rapidfuzz import process - -ratio = fuzz.ratio -wratio = fuzz.WRatio - - -class Cutoff: - """ - Holds all the default cutoff values. - """ - - tracks: int = 80 - albums: int = 80 - artists: int = 80 - playlists: int = 80 - - -class Limit: - """ - Holds all the default limit values. - """ - - tracks: int = 50 - albums: int = 50 - artists: int = 50 - playlists: int = 50 - - -class SearchTracks: - def __init__(self, tracks: List[models.Track], query: str) -> None: - self.query = query - self.tracks = tracks - - def __call__(self) -> List[models.Track]: - """ - Gets all songs with a given title. - """ - - tracks = [track.title for track in self.tracks] - results = process.extract( - self.query, - tracks, - scorer=fuzz.WRatio, - score_cutoff=Cutoff.tracks, - limit=Limit.tracks, - ) - - return [self.tracks[i[2]] for i in results] - - -class SearchArtists: - def __init__(self, artists: set[str], query: str) -> None: - self.query = query - self.artists = artists - - def __call__(self) -> list: - """ - Gets all artists with a given name. - """ - - results = process.extract( - self.query, - self.artists, - scorer=fuzz.WRatio, - score_cutoff=Cutoff.artists, - limit=Limit.artists, - ) - - artists = [a[0] for a in results] - return helpers.get_normalized_artists(artists) - - -class SearchAlbums: - def __init__(self, albums: List[models.Album], query: str) -> None: - self.query = query - self.albums = albums - - def __call__(self) -> List[models.Album]: - """ - Gets all albums with a given title. - """ - - albums = [a.title.lower() for a in self.albums] - - results = process.extract( - self.query, - albums, - scorer=fuzz.WRatio, - score_cutoff=Cutoff.albums, - limit=Limit.albums, - ) - - return [self.albums[i[2]] for i in results] - - # get all artists that matched the query - # for get all albums from the artists - # get all albums that matched the query - # return [**artist_albums **albums] - - # recheck next and previous artist on play next or add to playlist - - -class SearchPlaylists: - def __init__(self, playlists: List[models.Playlist], query: str) -> None: - self.playlists = playlists - self.query = query - - def __call__(self) -> List[models.Playlist]: - playlists = [p.name for p in self.playlists] - results = process.extract( - self.query, - playlists, - scorer=fuzz.WRatio, - score_cutoff=Cutoff.playlists, - limit=Limit.playlists, - ) - - return [self.playlists[i[2]] for i in results] diff --git a/server/app/lib/taglib.py b/server/app/lib/taglib.py deleted file mode 100644 index 055c6f14..00000000 --- a/server/app/lib/taglib.py +++ /dev/null @@ -1,194 +0,0 @@ -import os -from io import BytesIO - -import mutagen -from app import settings -from mutagen.flac import FLAC, MutagenError -from mutagen.id3 import ID3 -from PIL import Image - - -def parse_album_art(filepath: str): - """ - Returns the album art for a given audio file. - """ - - if filepath.endswith(".flac"): - try: - audio = FLAC(filepath) - return audio.pictures[0].data - except: - return None - elif filepath.endswith(".mp3"): - try: - audio = ID3(filepath) - return audio.getall("APIC")[0].data - except: - return None - - -def extract_thumb(filepath: str, webp_path: str) -> bool: - """ - Extracts the thumbnail from an audio file. Returns the path to the thumbnail. - """ - img_path = os.path.join(settings.THUMBS_PATH, webp_path) - tsize = settings.THUMB_SIZE - - if os.path.exists(img_path): - img_size = os.path.getsize(filepath) - - if img_size > 0: - return True - - album_art = parse_album_art(filepath) - - if album_art is not None: - img = Image.open(BytesIO(album_art)) - - try: - small_img = img.resize((tsize, tsize), Image.ANTIALIAS) - small_img.save(img_path, format="webp") - except OSError: - try: - png = img.convert("RGB") - small_img = png.resize((tsize, tsize), Image.ANTIALIAS) - small_img.save(webp_path, format="webp") - except: - return False - - return True - else: - return False - - -def parse_artist_tag(tags): - """ - Parses the artist tag from an audio file. - """ - try: - artists = tags["artist"][0] - except (KeyError, IndexError): - artists = "Unknown" - - return artists - - -def parse_title_tag(tags, full_path: str): - """ - Parses the title tag from an audio file. - """ - try: - title = tags["title"][0] - except (KeyError, IndexError): - title = full_path.split("/")[-1] - - return title - - -def parse_album_artist_tag(tags): - """ - Parses the album artist tag from an audio file. - """ - try: - albumartist = tags["albumartist"][0] - except (KeyError, IndexError): - albumartist = "Unknown" - - return albumartist - - -def parse_album_tag(tags, full_path: str): - """ - Parses the album tag from an audio file. - """ - try: - album = tags["album"][0] - except (KeyError, IndexError): - album = full_path.split("/")[-1] - - return album - - -def parse_genre_tag(tags): - """ - Parses the genre tag from an audio file. - """ - try: - genre = tags["genre"][0] - except (KeyError, IndexError): - genre = "Unknown" - - return genre - - -def parse_date_tag(tags): - """ - Parses the date tag from an audio file. - """ - try: - date = tags["date"][0] - except (KeyError, IndexError): - date = "Unknown" - - return date - - -def parse_track_number(tags): - """ - Parses the track number from an audio file. - """ - try: - track_number = int(tags["tracknumber"][0]) - except (KeyError, IndexError, ValueError): - track_number = 1 - - return track_number - - -def parse_disc_number(tags): - """ - Parses the disc number from an audio file. - """ - try: - disc_number = int(tags["discnumber"][0]) - except (KeyError, IndexError, ValueError): - disc_number = 1 - - return disc_number - - -def parse_copyright(tags): - try: - copyright = str(tags["copyright"][0]) - except (KeyError, IndexError, ValueError): - copyright = None - - return copyright - - -def get_tags(fullpath: str) -> dict | None: - """ - Returns a dictionary of tags for a given file. - """ - try: - tags = mutagen.File(fullpath, easy=True) - except MutagenError: - return None - - tags = { - "artists": parse_artist_tag(tags), - "title": parse_title_tag(tags, fullpath), - "albumartist": parse_album_artist_tag(tags), - "album": parse_album_tag(tags, fullpath), - "genre": parse_genre_tag(tags), - "date": parse_date_tag(tags)[:4], - "tracknumber": parse_track_number(tags), - "discnumber": parse_disc_number(tags), - "copyright": parse_copyright(tags), - "length": round(tags.info.length), - "bitrate": round(int(tags.info.bitrate) / 1000), - "filepath": fullpath, - "folder": os.path.dirname(fullpath), - } - - return tags diff --git a/server/app/lib/trackslib.py b/server/app/lib/trackslib.py deleted file mode 100644 index 61e39136..00000000 --- a/server/app/lib/trackslib.py +++ /dev/null @@ -1,25 +0,0 @@ -""" -This library contains all the functions related to tracks. -""" -import os - -from app import instances -from tqdm import tqdm - - -def validate_tracks() -> None: - """ - Gets all songs under the ~/ directory. - """ - entries = instances.tracks_instance.get_all_tracks() - - for track in tqdm(entries, desc="Validating tracks"): - try: - os.chmod(track["filepath"], 0o755) - except FileNotFoundError: - instances.tracks_instance.remove_song_by_id(track["_id"]["$oid"]) - - -def get_p_track(ptrack): - return instances.tracks_instance.find_track_by_title_artists_album( - ptrack["title"], ptrack["artists"], ptrack["album"]) diff --git a/server/app/lib/watchdoge.py b/server/app/lib/watchdoge.py deleted file mode 100644 index eea8816f..00000000 --- a/server/app/lib/watchdoge.py +++ /dev/null @@ -1,137 +0,0 @@ -""" -This library contains the classes and functions related to the watchdog file watcher. -""" -import os -import time -from typing import List - -from app import instances -from app.helpers import create_hash -from app.lib.taglib import get_tags -from app.logger import get_logger -from watchdog.events import PatternMatchingEventHandler -from watchdog.observers import Observer - -log = get_logger() - - -class OnMyWatch: - """ - Contains the methods for initializing and starting watchdog. - """ - - home_dir = os.path.expanduser("~") - dirs = [home_dir] - observers: List[Observer] = [] - - def __init__(self): - self.observer = Observer() - - def run(self): - event_handler = Handler() - - for dir in self.dirs: - print("something") - self.observer.schedule(event_handler, os.path.realpath(dir), recursive=True) - self.observers.append(self.observer) - - try: - self.observer.start() - print("something something") - except OSError: - log.error("Could not start watchdog.") - return - - try: - while True: - time.sleep(1) - except KeyboardInterrupt: - for o in self.observers: - o.unschedule_all() - o.stop() - print("Observer Stopped") - - for o in self.observers: - o.join() - - -def add_track(filepath: str) -> None: - """ - Processes the audio tags for a given file ands add them to the music dict. - - Then creates a folder object for the added track and adds it to api.FOLDERS - """ - tags = get_tags(filepath) - - if tags is not None: - hash = create_hash(tags["album"], tags["albumartist"]) - tags["albumhash"] = hash - instances.tracks_instance.insert_song(tags) - - -def remove_track(filepath: str) -> None: - """ - Removes a track from the music dict. - """ - - instances.tracks_instance.remove_song_by_filepath(filepath) - - -class Handler(PatternMatchingEventHandler): - files_to_process = [] - - def __init__(self): - print("💠 started watchdog 💠") - PatternMatchingEventHandler.__init__( - self, - patterns=["*.flac", "*.mp3"], - ignore_directories=True, - case_sensitive=False, - ) - - def on_created(self, event): - """ - Fired when a supported file is created. - """ - print("💠 created file 💠") - self.files_to_process.append(event.src_path) - - def on_deleted(self, event): - """ - Fired when a delete event occurs on a supported file. - """ - - remove_track(event.src_path) - - def on_moved(self, event): - """ - Fired when a move event occurs on a supported file. - """ - tr = "share/Trash" - - if tr in event.dest_path: - print("trash ++") - remove_track(event.src_path) - - elif tr in event.src_path: - add_track(event.dest_path) - - elif tr not in event.dest_path and tr not in event.src_path: - add_track(event.dest_path) - remove_track(event.src_path) - - def on_closed(self, event): - """ - Fired when a created file is closed. - """ - try: - self.files_to_process.remove(event.src_path) - add_track(event.src_path) - except ValueError: - """ - The file was already removed from the list, or it was not in the list to begin with. - """ - pass - - -watch = OnMyWatch() diff --git a/server/app/logger.py b/server/app/logger.py deleted file mode 100644 index cd030110..00000000 --- a/server/app/logger.py +++ /dev/null @@ -1,48 +0,0 @@ -import logging - - -class CustomFormatter(logging.Formatter): - - grey = "\x1b[38;20m" - yellow = "\x1b[33;20m" - red = "\x1b[31;20m" - bold_red = "\x1b[31;1m" - reset = "\x1b[0m" - # format = ( - # "%(asctime)s - %(name)s - %(levelname)s - %(message)s (%(filename)s:%(lineno)d)" - # ) - format = "[%(asctime)s] [%(levelname)s] [@%(name)s]ℹ️ %(message)s" - - FORMATS = { - logging.DEBUG: grey + format + reset, - logging.INFO: grey + format + reset, - logging.WARNING: yellow + format + reset, - logging.ERROR: red + format + reset, - logging.CRITICAL: bold_red + format + reset, - } - - def format(self, record): - log_fmt = self.FORMATS.get(record.levelno) - formatter = logging.Formatter(log_fmt, "%H:%M:%S") - return formatter.format(record) - - -logg = logging.getLogger("ALICE_MUSIC_SERVER") -logg.setLevel(logging.DEBUG) - -# create console handler with a higher log level -ch = logging.StreamHandler() -ch.setLevel(logging.DEBUG) - -ch.setFormatter(CustomFormatter()) - -logg.addHandler(ch) - - -def get_logger(): - return logg - - -logg = get_logger() - -# copied from: https://stackoverflow.com/a/56944256: diff --git a/server/app/models.py b/server/app/models.py deleted file mode 100644 index 07d1f07c..00000000 --- a/server/app/models.py +++ /dev/null @@ -1,194 +0,0 @@ -""" -Contains all the models for objects generation and typing. -""" -from dataclasses import dataclass -from dataclasses import field -from operator import itemgetter -from typing import List - -from app import helpers - - -@dataclass(slots=True) -class Track: - """ - Track class - """ - - trackid: str - title: str - artists: list[str] - albumartist: str - album: str - folder: str - filepath: str - length: int - genre: str - bitrate: int - tracknumber: int - discnumber: int - albumhash: str - date: str - image: str - uniq_hash: str - copyright: str - - def __init__(self, tags): - ( - self.title, - self.album, - self.albumartist, - self.genre, - self.albumhash, - self.date, - self.folder, - self.filepath, - self.copyright, - ) = itemgetter( - "title", - "album", - "albumartist", - "genre", - "albumhash", - "date", - "folder", - "filepath", - "copyright", - )( - tags - ) - self.trackid = tags["_id"]["$oid"] - self.artists = tags["artists"].split(", ") - self.bitrate = int(tags["bitrate"]) - self.length = int(tags["length"]) - self.discnumber = int(tags["discnumber"]) - self.image = tags["albumhash"] + ".webp" - self.tracknumber = int(tags["tracknumber"]) - - self.uniq_hash = helpers.create_hash( - "".join(self.artists), self.album, self.title - ) - - @staticmethod - def create_unique_hash(*args): - string = "".join(str(a) for a in args).replace(" ", "") - return "".join([i for i in string if i.isalnum()]).lower() - - -@dataclass(slots=True) -class Artist: - """ - Artist class - """ - - name: str - image: str - - def __init__(self, name: str): - self.name = name - self.image = helpers.create_safe_name(name) + ".webp" - - -@dataclass -class Album: - """ - Creates an album object - """ - - title: str - artist: str - hash: str - date: int - image: str - count: int = 0 - duration: int = 0 - copyright: str = field(default="") - is_soundtrack: bool = False - is_compilation: bool = False - is_single: bool = False - colors: List[str] = field(default_factory=list) - - def __init__(self, tags): - ( - self.title, - self.artist, - self.date, - self.image, - self.hash, - self.copyright, - ) = itemgetter("title", "artist", "date", "image", "hash", "copyright")(tags) - - try: - self.colors = tags["colors"] - except KeyError: - self.colors = [] - - @property - def is_soundtrack(self) -> bool: - keywords = ["motion picture", "soundtrack"] - for keyword in keywords: - if keyword in self.title.lower(): - return True - - return False - - @property - def is_compilation(self) -> bool: - return self.artist.lower() == "various artists" - - -@dataclass -class Playlist: - """Creates playlist objects""" - - playlistid: str - name: str - tracks: List[Track] - pretracks: list = field(init=False, repr=False) - lastUpdated: int - image: str - thumb: str - description: str = "" - count: int = 0 - """A list of track objects in the playlist""" - - def __init__(self, data): - self.playlistid = data["_id"]["$oid"] - self.name = data["name"] - self.description = data["description"] - self.image = self.create_img_link(data["image"]) - self.thumb = self.create_img_link(data["thumb"]) - self.pretracks = data["pre_tracks"] - self.tracks = [] - self.lastUpdated = data["lastUpdated"] - self.count = len(self.pretracks) - - def create_img_link(self, image: str): - if image: - return image - - return "default.webp" - - def update_playlist(self, data: dict): - self.name = data["name"] - self.description = data["description"] - self.lastUpdated = data["lastUpdated"] - - if data["image"]: - self.image = self.create_img_link(data["image"]) - self.thumb = self.create_img_link(data["thumb"]) - - -@dataclass -class Folder: - name: str - path: str - trackcount: int - is_sym: bool = False - """The number of tracks in the folder""" - - def __init__(self, data) -> None: - self.name = data["name"] - self.path = data["path"] - self.is_sym = data["is_sym"] - self.trackcount = data["trackcount"] diff --git a/server/app/patches.py b/server/app/patches.py deleted file mode 100644 index caf4314d..00000000 --- a/server/app/patches.py +++ /dev/null @@ -1,3 +0,0 @@ -""" -This module contains patch functions to modify existing data in the database. -""" diff --git a/server/app/prep.py b/server/app/prep.py deleted file mode 100644 index 2ddde835..00000000 --- a/server/app/prep.py +++ /dev/null @@ -1,60 +0,0 @@ -""" -Contains the functions to prepare the server for use. -""" -import os -import shutil - -from app import settings - - -class CopyFiles: - """Copies assets to the app directory.""" - - def __init__(self) -> None: - files = [{ - "src": "assets", - "dest": os.path.join(settings.APP_DIR, "assets"), - "is_dir": True, - }] - - for entry in files: - src = os.path.join(os.getcwd(), entry["src"]) - - if entry["is_dir"]: - shutil.copytree( - src, - entry["dest"], - ignore=shutil.ignore_patterns("*.pyc", ), - copy_function=shutil.copy2, - dirs_exist_ok=True, - ) - break - - shutil.copy2(src, entry["dest"]) - - -def create_config_dir() -> None: - """ - Creates the config directory if it doesn't exist. - """ - - _home_dir = os.path.expanduser("~") - config_folder = os.path.join(_home_dir, settings.CONFIG_FOLDER) - - dirs = [ - "", - "images", - os.path.join("images", "artists"), - os.path.join("images", "thumbnails"), - os.path.join("images", "playlists"), - ] - - for _dir in dirs: - path = os.path.join(config_folder, _dir) - exists = os.path.exists(path) - - if not exists: - os.makedirs(path) - os.chmod(path, 0o755) - - CopyFiles() diff --git a/server/app/serializer.py b/server/app/serializer.py deleted file mode 100644 index cde4e22e..00000000 --- a/server/app/serializer.py +++ /dev/null @@ -1,80 +0,0 @@ -from dataclasses import dataclass -from datetime import datetime - -from app import models - - -def date_string_to_time_passed(prev_date: str) -> str: - """ - Converts a date string to time passed. eg. 2 minutes ago, 1 hour ago, yesterday, 2 days ago, 2 weeks ago, etc. - """ - - now = datetime.now() - then = datetime.strptime(prev_date, "%Y-%m-%d %H:%M:%S") - - diff = now - then - days = diff.days - - if days < 0: - return "in the future" - - elif days == 0: - seconds = diff.seconds - if seconds < 15: - return "now" - elif seconds < 60: - return str(seconds) + " seconds ago" - elif seconds < 3600: - return str(seconds // 60) + " minutes ago" - else: - return str(seconds // 3600) + " hours ago" - - elif days == 1: - return "yesterday" - elif days < 7: - return str(days) + " days ago" - elif days < 30: - if days < 14: - return "1 week ago" - - return str(days // 7) + " weeks ago" - elif days < 365: - if days < 60: - return "1 month ago" - - return str(days // 30) + " months ago" - elif days > 365: - if days < 730: - return "1 year ago" - - return str(days // 365) + " years ago" - - -@dataclass -class Playlist: - playlistid: str - name: str - image: str - thumb: str - lastUpdated: int - description: str - count: int = 0 - duration: int = 0 - - def __init__(self, - p: models.Playlist, - construct_last_updated: bool = True) -> None: - self.playlistid = p.playlistid - self.name = p.name - self.image = p.image - self.thumb = p.thumb - self.lastUpdated = p.lastUpdated - self.description = p.description - self.count = p.count - - if construct_last_updated: - self.lastUpdated = self.get_l_updated(p.lastUpdated) - - @staticmethod - def get_l_updated(date: str) -> str: - return date_string_to_time_passed(date) diff --git a/server/app/settings.py b/server/app/settings.py deleted file mode 100644 index 740f2c9a..00000000 --- a/server/app/settings.py +++ /dev/null @@ -1,35 +0,0 @@ -""" -Contains default configs -""" -import multiprocessing -import os - -# paths -CONFIG_FOLDER = ".alice" -HOME_DIR = os.path.expanduser("~") -APP_DIR = os.path.join(HOME_DIR, CONFIG_FOLDER) -IMG_PATH = os.path.join(APP_DIR, "images") - -THUMBS_PATH = os.path.join(IMG_PATH, "thumbnails") -TEST_DIR = "/home/cwilvx/Music/Link to Music/Chill/Wolftyla Radio" -# HOME_DIR = TEST_DIR - -# URLS -IMG_BASE_URI = "http://127.0.0.1:8900/images/" -IMG_ARTIST_URI = IMG_BASE_URI + "artists/" -IMG_THUMB_URI = IMG_BASE_URI + "thumbnails/" -IMG_PLAYLIST_URI = IMG_BASE_URI + "playlists/" - -# defaults -DEFAULT_ARTIST_IMG = IMG_ARTIST_URI + "0.webp" - -LAST_FM_API_KEY = "762db7a44a9e6fb5585661f5f2bdf23a" - -CPU_COUNT = multiprocessing.cpu_count() - -THUMB_SIZE: int = 400 -""" -The size of extracted in pixels -""" - -LOGGER_ENABLE: bool = True diff --git a/server/assets/default.webp b/server/assets/default.webp deleted file mode 100644 index 8993fcf8c9ad038331c28f217bb571cb559318f5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1218 zcmV;z1U>swNk&Gx1ONb6MM6+kP&gp21ONbVFaVtaDv$w?0X{Jpi$tO!p%4TBAOi%n zw|+h*U~cw;C1wN$2SBfXn{`L#!F@X?@DKm~yZ!&$`~M&l*amboq#77!*pyJL=zGnz z;^y;!e|yn7d=fB#K2<@}4qnWnV%TSAf-oouEdo8HwK9~tVg(1j+L9a{@G3%6NTr<{ z#mS9xm7-$`!`K7gY@B~1Xo9d+6rX?7+cGAANfiU0>-~c5Dg|r?c?KuE)((p^$;gNl zOlm3?Y8@M!UM@~U--MtDy6$Iei|KZ}Z+_Ep@Bd+n2zT=mNukeXeu7aZgo zPBiT4mwTap)|eJcwcLaayEQ{2O`k!?zL#sc26;f+6a_ZT+EfSGI$f_zZW40C0QHJ{ zPi|%Rf>C*AHq8^8b8E&Ef8kTA-j{V!<@n^}2$hk__LzA%{#`ECWjm_7=t%zsFbl;f!xHceIv&u4;^tcJ5gM<{`2IM#B;cEvQ<5UZXW0)N?kN=GAZp8*9I zz0a=_C2ybr{_ezo|EM$n{z0Gr@(lmjQ|UgQ^z|@qmHUh=N}=jgu1sjegHSE#t);Kh zjN}<_kSQh=Fxv!2cPLu!s}CoaRGC|a_)Y9qXCQ{$!Y%3az46_X(adJ@r+Eb5Zk{S|Nyv8@AgxbuvT zj2OANq(Wz45@`^yQ7mDne=mE*ru1~A3%~|o6dfkda5C7-ivS>uL9wh8#a(0LfE{34 zd@{=g>(MS6UvQVf$5W)jvn$;aFH`Xro@tEJ?E@-C?o>v9Leddmue0l5ly7ul&hj%Y z!H=GeW(UbS_LTQ<0}p;5_`UP|Z$)HJ`m%GrO&1+1D<+nYzBq+&3`-3KSOCj+W%e5N zGpN~0E>ws6vc`)E6cQ1wsE<%9@Pd$pq}d@-qrPaV1Pyun6QX4T6XRzr(+HI5bA9 z9o`V9obpfx;Co@L!bw^&JQID44`10V9FWo!i$DZnI$OueXw94zz&<8VL)m}&J_0RN4i2Q6Lshe%}Y*eZ-2VGBo&|F5I!HW3y_ z{$9VBtdT={?he#gk1SO^TBd-pju%Nd{pquvtF(peQeO$me}SO=ZOEykXCTt71(zdp z*8Co9DkhLvgn=3.6.0", markers = "python_version < \"3.10\""} -itsdangerous = ">=2.0" -Jinja2 = ">=3.0" -Werkzeug = ">=2.0" - -[package.extras] -async = ["asgiref (>=3.2)"] -dotenv = ["python-dotenv"] - -[[package]] -name = "flask-caching" -version = "1.11.1" -description = "Adds caching support to Flask applications." -category = "main" -optional = false -python-versions = ">=3.7" - -[package.dependencies] -cachelib = "*" -Flask = "*" - -[[package]] -name = "flask-cors" -version = "3.0.10" -description = "A Flask extension adding a decorator for CORS support" -category = "main" -optional = false -python-versions = "*" - -[package.dependencies] -Flask = ">=0.9" -Six = "*" - -[[package]] -name = "gunicorn" -version = "20.1.0" -description = "WSGI HTTP Server for UNIX" -category = "main" -optional = false -python-versions = ">=3.5" - -[package.extras] -eventlet = ["eventlet (>=0.24.1)"] -gevent = ["gevent (>=1.4.0)"] -setproctitle = ["setproctitle"] -tornado = ["tornado (>=0.2)"] - -[[package]] -name = "idna" -version = "3.3" -description = "Internationalized Domain Names in Applications (IDNA)" -category = "main" -optional = false -python-versions = ">=3.5" - -[[package]] -name = "importlib-metadata" -version = "4.11.4" -description = "Read metadata from Python packages" -category = "main" -optional = false -python-versions = ">=3.7" - -[package.dependencies] -zipp = ">=0.5" - -[package.extras] -docs = ["sphinx", "jaraco.packaging (>=9)", "rst.linker (>=1.9)"] -perf = ["ipython"] -testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "packaging", "pyfakefs", "flufl.flake8", "pytest-perf (>=0.9.2)", "pytest-black (>=0.3.7)", "pytest-mypy (>=0.9.1)", "importlib-resources (>=1.3)"] - -[[package]] -name = "itsdangerous" -version = "2.1.2" -description = "Safely pass data to untrusted environments and back." -category = "main" -optional = false -python-versions = ">=3.7" - -[[package]] -name = "jarowinkler" -version = "1.0.2" -description = "library for fast approximate string matching using Jaro and Jaro-Winkler similarity" -category = "main" -optional = false -python-versions = ">=3.6" - -[[package]] -name = "jinja2" -version = "3.1.2" -description = "A very fast and expressive template engine." -category = "main" -optional = false -python-versions = ">=3.7" - -[package.dependencies] -MarkupSafe = ">=2.0" - -[package.extras] -i18n = ["Babel (>=2.7)"] - -[[package]] -name = "markupsafe" -version = "2.1.1" -description = "Safely add untrusted strings to HTML/XML markup." -category = "main" -optional = false -python-versions = ">=3.7" - -[[package]] -name = "mutagen" -version = "1.45.1" -description = "read and write audio tags for many formats" -category = "main" -optional = false -python-versions = ">=3.5, <4" - -[[package]] -name = "pillow" -version = "9.1.1" -description = "Python Imaging Library (Fork)" -category = "main" -optional = false -python-versions = ">=3.7" - -[package.extras] -docs = ["olefile", "sphinx (>=2.4)", "sphinx-copybutton", "sphinx-issues (>=3.0.1)", "sphinx-removed-in", "sphinx-rtd-theme (>=1.0)", "sphinxext-opengraph"] -tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout"] - -[[package]] -name = "progress" -version = "1.6" -description = "Easy to use progress bars" -category = "main" -optional = false -python-versions = "*" - -[[package]] -name = "pymongo" -version = "4.1.1" -description = "Python driver for MongoDB " -category = "main" -optional = false -python-versions = ">=3.6.2" - -[package.extras] -aws = ["pymongo-auth-aws (<2.0.0)"] -encryption = ["pymongocrypt (>=1.2.0,<2.0.0)"] -gssapi = ["pykerberos"] -ocsp = ["pyopenssl (>=17.2.0)", "requests (<3.0.0)", "service-identity (>=18.1.0)", "certifi"] -snappy = ["python-snappy"] -srv = ["dnspython (>=1.16.0,<3.0.0)"] -zstd = ["zstandard"] - -[[package]] -name = "rapidfuzz" -version = "2.0.11" -description = "rapid fuzzy string matching" -category = "main" -optional = false -python-versions = ">=3.6" - -[package.dependencies] -jarowinkler = ">=1.0.2,<1.1.0" - -[package.extras] -full = ["numpy"] - -[[package]] -name = "requests" -version = "2.27.1" -description = "Python HTTP for Humans." -category = "main" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" - -[package.dependencies] -certifi = ">=2017.4.17" -charset-normalizer = {version = ">=2.0.0,<2.1.0", markers = "python_version >= \"3\""} -idna = {version = ">=2.5,<4", markers = "python_version >= \"3\""} -urllib3 = ">=1.21.1,<1.27" - -[package.extras] -socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"] -use_chardet_on_py3 = ["chardet (>=3.0.2,<5)"] - -[[package]] -name = "six" -version = "1.16.0" -description = "Python 2 and 3 compatibility utilities" -category = "main" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" - -[[package]] -name = "tqdm" -version = "4.64.0" -description = "Fast, Extensible Progress Meter" -category = "main" -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" - -[package.dependencies] -colorama = {version = "*", markers = "platform_system == \"Windows\""} - -[package.extras] -dev = ["py-make (>=0.1.0)", "twine", "wheel"] -notebook = ["ipywidgets (>=6)"] -slack = ["slack-sdk"] -telegram = ["requests"] - -[[package]] -name = "urllib3" -version = "1.26.9" -description = "HTTP library with thread-safe connection pooling, file post, and more." -category = "main" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" - -[package.extras] -brotli = ["brotlicffi (>=0.8.0)", "brotli (>=1.0.9)", "brotlipy (>=0.6.0)"] -secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"] -socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] - -[[package]] -name = "watchdog" -version = "2.1.8" -description = "Filesystem events monitoring" -category = "main" -optional = false -python-versions = ">=3.6" - -[package.extras] -watchmedo = ["PyYAML (>=3.10)"] - -[[package]] -name = "werkzeug" -version = "2.1.2" -description = "The comprehensive WSGI web application library." -category = "main" -optional = false -python-versions = ">=3.7" - -[package.extras] -watchdog = ["watchdog"] - -[[package]] -name = "zipp" -version = "3.8.0" -description = "Backport of pathlib-compatible object wrapper for zip files" -category = "main" -optional = false -python-versions = ">=3.7" - -[package.extras] -docs = ["sphinx", "jaraco.packaging (>=9)", "rst.linker (>=1.9)"] -testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy (>=0.9.1)"] - -[metadata] -lock-version = "1.1" -python-versions = "^3.8" -content-hash = "6247b9ff39d46d078e0f97423034ff5e26542ad74fece6a54547b72877474f11" - -[metadata.files] -cachelib = [ - {file = "cachelib-0.7.0-py3-none-any.whl", hash = "sha256:80fa73dda398672329dab6c8e9e9bad03fd36dc4da40d911d7de308c91e8481e"}, - {file = "cachelib-0.7.0.tar.gz", hash = "sha256:df254f3b900dc8684d8ebdd146c731ddb45edc6233a6cf7e3e834c949f360726"}, -] -certifi = [ - {file = "certifi-2022.5.18.1-py3-none-any.whl", hash = "sha256:f1d53542ee8cbedbe2118b5686372fb33c297fcd6379b050cca0ef13a597382a"}, - {file = "certifi-2022.5.18.1.tar.gz", hash = "sha256:9c5705e395cd70084351dd8ad5c41e65655e08ce46f2ec9cf6c2c08390f71eb7"}, -] -charset-normalizer = [ - {file = "charset-normalizer-2.0.12.tar.gz", hash = "sha256:2857e29ff0d34db842cd7ca3230549d1a697f96ee6d3fb071cfa6c7393832597"}, - {file = "charset_normalizer-2.0.12-py3-none-any.whl", hash = "sha256:6881edbebdb17b39b4eaaa821b438bf6eddffb4468cf344f09f89def34a8b1df"}, -] -click = [ - {file = "click-8.1.3-py3-none-any.whl", hash = "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"}, - {file = "click-8.1.3.tar.gz", hash = "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e"}, -] -colorama = [ - {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"}, - {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"}, -] -"colorgram.py" = [ - {file = "colorgram.py-1.2.0-py2.py3-none-any.whl", hash = "sha256:e990769fa6df7261a450c7d5bef3a1a062f09ba1214bff67b4d6f02970a1a27b"}, - {file = "colorgram.py-1.2.0.tar.gz", hash = "sha256:e77766a5f9de7207bdef8f1c22a702cbf09630eae3bc46a450b9d9f12a7bfdbf"}, -] -flask = [ - {file = "Flask-2.1.2-py3-none-any.whl", hash = "sha256:fad5b446feb0d6db6aec0c3184d16a8c1f6c3e464b511649c8918a9be100b4fe"}, - {file = "Flask-2.1.2.tar.gz", hash = "sha256:315ded2ddf8a6281567edb27393010fe3406188bafbfe65a3339d5787d89e477"}, -] -flask-caching = [ - {file = "Flask-Caching-1.11.1.tar.gz", hash = "sha256:28af189e97defb9e39b43ebe197b54a58aaee81bdeb759f46d969c26d7aa7810"}, - {file = "Flask_Caching-1.11.1-py3-none-any.whl", hash = "sha256:36592812eec6cba86eca48bcda74eff24bfd6c8eaf6056ca0184474bb78c0dc4"}, -] -flask-cors = [ - {file = "Flask-Cors-3.0.10.tar.gz", hash = "sha256:b60839393f3b84a0f3746f6cdca56c1ad7426aa738b70d6c61375857823181de"}, - {file = "Flask_Cors-3.0.10-py2.py3-none-any.whl", hash = "sha256:74efc975af1194fc7891ff5cd85b0f7478be4f7f59fe158102e91abb72bb4438"}, -] -gunicorn = [ - {file = "gunicorn-20.1.0-py3-none-any.whl", hash = "sha256:9dcc4547dbb1cb284accfb15ab5667a0e5d1881cc443e0677b4882a4067a807e"}, - {file = "gunicorn-20.1.0.tar.gz", hash = "sha256:e0a968b5ba15f8a328fdfd7ab1fcb5af4470c28aaf7e55df02a99bc13138e6e8"}, -] -idna = [ - {file = "idna-3.3-py3-none-any.whl", hash = "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff"}, - {file = "idna-3.3.tar.gz", hash = "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d"}, -] -importlib-metadata = [ - {file = "importlib_metadata-4.11.4-py3-none-any.whl", hash = "sha256:c58c8eb8a762858f49e18436ff552e83914778e50e9d2f1660535ffb364552ec"}, - {file = "importlib_metadata-4.11.4.tar.gz", hash = "sha256:5d26852efe48c0a32b0509ffbc583fda1a2266545a78d104a6f4aff3db17d700"}, -] -itsdangerous = [ - {file = "itsdangerous-2.1.2-py3-none-any.whl", hash = "sha256:2c2349112351b88699d8d4b6b075022c0808887cb7ad10069318a8b0bc88db44"}, - {file = "itsdangerous-2.1.2.tar.gz", hash = "sha256:5dbbc68b317e5e42f327f9021763545dc3fc3bfe22e6deb96aaf1fc38874156a"}, -] -jarowinkler = [ - {file = "jarowinkler-1.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:71772fcd787e0286b779de0f1bef1e0a25deb4578328c0fc633bc345f13ffd20"}, - {file = "jarowinkler-1.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:912ee0a465822a8d659413cebc1ab9937ac5850c9cd1e80be478ba209e7c8095"}, - {file = "jarowinkler-1.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0320f7187dced1ad413bf2c3631ec47567e65dfdea92c523aafb2c085ae15035"}, - {file = "jarowinkler-1.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:58bc6a8f01b0dfdf3721f9a4954060addeccf8bbe5e72a71cf23a88ce0d30440"}, - {file = "jarowinkler-1.0.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:679ec7a42f70baa61f3a214d1b59cec90fc036021c759722075efcc8697e7b1f"}, - {file = "jarowinkler-1.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dde57d47962d6a4436d8a3b477bcc8233c6da28e675027eb3a490b0d6dc325be"}, - {file = "jarowinkler-1.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:657f50204970fac8f120c293e52a3451b742c9b26125010405ec7365cb6e2a49"}, - {file = "jarowinkler-1.0.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:04f18a7398766b36ffbe4bcd26d34fcd6ed01f4f2f7eea13e316e6cca0e10c98"}, - {file = "jarowinkler-1.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:33a24b380e2c076eabf2d3e12eee56b6bf10b1f326444e18c36a495387dbf0de"}, - {file = "jarowinkler-1.0.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e1d7d6e6c98fb785026584373240cc4076ad21033f508973faae05e846206e8c"}, - {file = "jarowinkler-1.0.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:e50c750a45c800d91134200d8cbf746258ed357a663e97cc0348ee42a948386a"}, - {file = "jarowinkler-1.0.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:5b380afce6cdc25a4dafd86874f07a393800577c05335c6ad67ccda41db95c60"}, - {file = "jarowinkler-1.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:e73712747ac5d2218af3ed3c1600377f18a0a45af95f22c39576165aea2908b4"}, - {file = "jarowinkler-1.0.2-cp310-cp310-win32.whl", hash = "sha256:9511f4e1f00c822e08dbffeb69e15c75eb294a5f24729815a97807ecf03d22eb"}, - {file = "jarowinkler-1.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a5c44f92e9ac6088286292ecb69e970adc2b98e139b8923bce9bbb9d484e6a0f"}, - {file = "jarowinkler-1.0.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:02b0bf34ffc2995b695d9b10d2f18c1c447fbbdb7c913a84a0a48c186ccca3b8"}, - {file = "jarowinkler-1.0.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:df7a8e45176298a1210c06f8b2328030cc3c93a45dab068ac1fbc9cf075cd95b"}, - {file = "jarowinkler-1.0.2-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:da27a9c206249a50701bfa5cfbbb3a04236e1145b2b0967e825438acb14269bf"}, - {file = "jarowinkler-1.0.2-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:43ea0155379df92021af0f4a32253be3953dfa0f050ec3515f314b8f48a96674"}, - {file = "jarowinkler-1.0.2-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f33b6b1687db1be1abba60850628ee71547501592fcf3504e021274bc5ccb7a"}, - {file = "jarowinkler-1.0.2-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ff304de32ee6acd5387103a0ad584060d8d419aa19cbbeca95204de9c4f01171"}, - {file = "jarowinkler-1.0.2-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:662dd6f59cca536640be0cda32c901989504d95316b192e6aa41d098fa08c795"}, - {file = "jarowinkler-1.0.2-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:01f85abb75fa43e98db34853d35570d98495ee2fcbbf45a93838e0289c162f19"}, - {file = "jarowinkler-1.0.2-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:5b9332dcc8130af4101c9752a03e977c54b8c12982a2a3ca4c2e4cc542accc00"}, - {file = "jarowinkler-1.0.2-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:af765b037404a536c372e33ddd4c430aea28f1d82a8ef51a2955442b8b690577"}, - {file = "jarowinkler-1.0.2-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:aea2c7d66b57c56d00f9c45ae7862d86e3ae84368ecea17f3552c0052a7f3bcf"}, - {file = "jarowinkler-1.0.2-cp36-cp36m-win32.whl", hash = "sha256:8b1288a09a8d100e9bf7cf9ce1329433db73a0d0350d74c2c6f5c31ac69096cf"}, - {file = "jarowinkler-1.0.2-cp36-cp36m-win_amd64.whl", hash = "sha256:ed39199b0e806902347473c65e5c05933549cf7e55ba628c6812782f2c310b19"}, - {file = "jarowinkler-1.0.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:473b057d7e5a0f5e5b8c0e0f7960d3ca2f2954c3c93fd7a9fb2cc4bc3cc940fb"}, - {file = "jarowinkler-1.0.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bdb892dbbbd77b3789a10b2ce5e8acfe5821cc6423e835bae2b489159f3c2211"}, - {file = "jarowinkler-1.0.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:012a8333328ce061cba1ff081843c8d80eb1afe8fa2889ad29d767ea3fdc7562"}, - {file = "jarowinkler-1.0.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3421120c07ee6d3f59c5adde32eb9a050cfd1b3666b0e2d8c337d934a9d091f9"}, - {file = "jarowinkler-1.0.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dad57327cc90f8daa3afb98e2d274d7dd1b60651f32717449be95d3b3366d61a"}, - {file = "jarowinkler-1.0.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e4fd1757eff43df97227fd63d9c8078582267a0b25cefef6f6a64d3e46e80ba2"}, - {file = "jarowinkler-1.0.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:32269ebbcb860f01c055d9bb145b4cc91990f62c7644a85b21458b4868621113"}, - {file = "jarowinkler-1.0.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:3b5a0839e84f5ff914b01b5b94d0273954affce9cc2b2ee2c31fe2fcb9c8ae76"}, - {file = "jarowinkler-1.0.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:6c9d3a9ef008428b5dce2855eebe2b6127ea7a7e433aedf240653fad4bd4baa6"}, - {file = "jarowinkler-1.0.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:a3d7759d8a66ee05595bde012f93da8a63499f38205e2bb47022c52bd6c47108"}, - {file = "jarowinkler-1.0.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:2ba1b1b0bf45042a9bbb95d272fd8b0c559fe8f6806f088ec0372899e1bc6224"}, - {file = "jarowinkler-1.0.2-cp37-cp37m-win32.whl", hash = "sha256:4cb33f4343774d69abf8cf65ad57919e7a171c44ba6ad57b08147c3f0f06b073"}, - {file = "jarowinkler-1.0.2-cp37-cp37m-win_amd64.whl", hash = "sha256:0392b72ddb5ab5d6c1d5df94dbdac7bf229670e5e64b2b9a382d02d6158755e5"}, - {file = "jarowinkler-1.0.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:94f663ad85bc7a89d7e8b6048f93a46d2848a0570ab07fc895a239b9a5d97b93"}, - {file = "jarowinkler-1.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:895a10766ff3db15e7cf2b735e4277bee051eaafb437aaaef2c5de64a5c3f05c"}, - {file = "jarowinkler-1.0.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:0c1a84e770b3ec7385a4f40efb30bdc96f96844564f91f8d3937d54a8969d82c"}, - {file = "jarowinkler-1.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:27defe81d76e02b3929322baea999f5232837e7f308c2dc5b37de7568c2bc583"}, - {file = "jarowinkler-1.0.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:158f117481388f8d23fe4bd2567f37be0ccae0f4631c34e4b0345803147da207"}, - {file = "jarowinkler-1.0.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:427c675b4f3e83c79a4b6af7441f29e30a173c7a0ae72a54f51090eee7a8ae02"}, - {file = "jarowinkler-1.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90a7f3fd173339bc62e52c02f43d50c947cb3af9cda41646e218aea13547e0c2"}, - {file = "jarowinkler-1.0.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3975cbe8b6ae13fc63d74bcbed8dac1577078d8cd8728e60621fe75885d2a8c5"}, - {file = "jarowinkler-1.0.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:141840f33345b00abd611839080edc99d4d31abd2dcf701a3e50c90f9bfb2383"}, - {file = "jarowinkler-1.0.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:f592f9f6179e347a5f518ca7feb9bf3ac068f2fad60ece5a0eef5e5e580d4c8b"}, - {file = "jarowinkler-1.0.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:30565d70396eb9d1eb622e1e707ddc2f3b7a9692558b8bf4ea49415a5ca2f854"}, - {file = "jarowinkler-1.0.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:35fc430c11b80a43ed826879c78c4197ec665d5150745b3668bec961acf8a757"}, - {file = "jarowinkler-1.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:e4cf4b7090f0c4075bec1638717f54b22c3b0fe733dc87146a19574346ed3161"}, - {file = "jarowinkler-1.0.2-cp38-cp38-win32.whl", hash = "sha256:199f4f7edbc49439a97440caa1e244d2e33da3e16d7b0afce4e4dfd307e555c7"}, - {file = "jarowinkler-1.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:b587e8fdd96cc470d6bdf428129c65264731b09b5db442e2d092e983feec4aab"}, - {file = "jarowinkler-1.0.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:4b233180b3e2f2d7967aa570d36984e9d2ec5a9067c0d1c44cd3b805d9da9363"}, - {file = "jarowinkler-1.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2220665a1f52262ae8b76e3baf474ebcd209bfcb6a7cada346ffd62818f5aa3e"}, - {file = "jarowinkler-1.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:08c98387e04e749c84cc967db628e5047843f19f87bf515a35b72f7050bc28ad"}, - {file = "jarowinkler-1.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d710921657442ad3c942de684aba0bdf16b7de5feed3223b12f3b2517cf17f7c"}, - {file = "jarowinkler-1.0.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:401c02ac7245103826f54c816324274f53d50b638ab0f8b359a13055a7a6e793"}, - {file = "jarowinkler-1.0.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0a1929a0029f208cc9244499dc93b4d52ee8e80d2849177d425cf6e0be1ea781"}, - {file = "jarowinkler-1.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4ab25d147be9b04e7de2d28a18e72fadc152698c3e51683c6c61f73ffbae2f9e"}, - {file = "jarowinkler-1.0.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:465cfdff355ec9c55f65fd1e1315260ec20c8cff0eb90d9f1a0ad8d503dc002b"}, - {file = "jarowinkler-1.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:29ef1113697cc74c2f04bc15008abbd726cb2d5b01c040ba87c6cb7abd1d0e0d"}, - {file = "jarowinkler-1.0.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:61b57c8b36361ec889f99f761441bb0fa21b850a5eb3305dea25fef68f6a797b"}, - {file = "jarowinkler-1.0.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:ee9d9af1bbf194d78f4b69c2139807c23451068b27a053a1400d683d6f36c61d"}, - {file = "jarowinkler-1.0.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:a9b33b0ceb472bbc65683467189bd032c162256b2a137586ee3448a9f8f886ec"}, - {file = "jarowinkler-1.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:582f6e213a6744883ced44482a51efcc21ae632defac27f12f6430a8e99b1070"}, - {file = "jarowinkler-1.0.2-cp39-cp39-win32.whl", hash = "sha256:4d1c8f403016d5c0262de7a8588eee370c37a609e1f529f8407e99a70d020af7"}, - {file = "jarowinkler-1.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:ab50ffa66aa201616871c1b90ac0790f56666118db3c8a8fcb3a7a6e03971510"}, - {file = "jarowinkler-1.0.2-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:8e59a289dcf93504ab92795666c39b2dbe98ac18655201992a7e6247de676bf4"}, - {file = "jarowinkler-1.0.2-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c36eccdc866f06a7b35da701bd8f91e0dfc83b35c07aba75ce8c906cbafaf184"}, - {file = "jarowinkler-1.0.2-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:123163f01a5c43f12e4294e7ce567607d859e1446b1a43bd6cd404b3403ffa07"}, - {file = "jarowinkler-1.0.2-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d41fdecd907189e47c7d478e558ad417da38bf3eb34cc20527035cb3fca3e2b8"}, - {file = "jarowinkler-1.0.2-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:e7829368fc91de225f37f6325f8d8ec7ad831dc5b0e9547f1977e2fdc85eccc1"}, - {file = "jarowinkler-1.0.2-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:278595417974553a8fdf3c8cce5c2b4f859335344075b870ecb55cc416eb76cf"}, - {file = "jarowinkler-1.0.2-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:208fc49741db5d3e6bbd4a2f7b32d32644b462bf205e7510eca4e2d530225f03"}, - {file = "jarowinkler-1.0.2-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:924afcab6739c453f1c3492701d185d71dc0e5ba15692bd0bfa6d482c7e8f79e"}, - {file = "jarowinkler-1.0.2.tar.gz", hash = "sha256:788ac33e6ffdbd78fd913b481e37cfa149288575f087a1aae1a4ce219cb1c654"}, -] -jinja2 = [ - {file = "Jinja2-3.1.2-py3-none-any.whl", hash = "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61"}, - {file = "Jinja2-3.1.2.tar.gz", hash = "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852"}, -] -markupsafe = [ - {file = "MarkupSafe-2.1.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:86b1f75c4e7c2ac2ccdaec2b9022845dbb81880ca318bb7a0a01fbf7813e3812"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f121a1420d4e173a5d96e47e9a0c0dcff965afdf1626d28de1460815f7c4ee7a"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a49907dd8420c5685cfa064a1335b6754b74541bbb3706c259c02ed65b644b3e"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10c1bfff05d95783da83491be968e8fe789263689c02724e0c691933c52994f5"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b7bd98b796e2b6553da7225aeb61f447f80a1ca64f41d83612e6139ca5213aa4"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b09bf97215625a311f669476f44b8b318b075847b49316d3e28c08e41a7a573f"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:694deca8d702d5db21ec83983ce0bb4b26a578e71fbdbd4fdcd387daa90e4d5e"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:efc1913fd2ca4f334418481c7e595c00aad186563bbc1ec76067848c7ca0a933"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-win32.whl", hash = "sha256:4a33dea2b688b3190ee12bd7cfa29d39c9ed176bda40bfa11099a3ce5d3a7ac6"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:dda30ba7e87fbbb7eab1ec9f58678558fd9a6b8b853530e176eabd064da81417"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:671cd1187ed5e62818414afe79ed29da836dde67166a9fac6d435873c44fdd02"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3799351e2336dc91ea70b034983ee71cf2f9533cdff7c14c90ea126bfd95d65a"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e72591e9ecd94d7feb70c1cbd7be7b3ebea3f548870aa91e2732960fa4d57a37"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6fbf47b5d3728c6aea2abb0589b5d30459e369baa772e0f37a0320185e87c980"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d5ee4f386140395a2c818d149221149c54849dfcfcb9f1debfe07a8b8bd63f9a"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:bcb3ed405ed3222f9904899563d6fc492ff75cce56cba05e32eff40e6acbeaa3"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:e1c0b87e09fa55a220f058d1d49d3fb8df88fbfab58558f1198e08c1e1de842a"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-win32.whl", hash = "sha256:8dc1c72a69aa7e082593c4a203dcf94ddb74bb5c8a731e4e1eb68d031e8498ff"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:97a68e6ada378df82bc9f16b800ab77cbf4b2fada0081794318520138c088e4a"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e8c843bbcda3a2f1e3c2ab25913c80a3c5376cd00c6e8c4a86a89a28c8dc5452"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0212a68688482dc52b2d45013df70d169f542b7394fc744c02a57374a4207003"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e576a51ad59e4bfaac456023a78f6b5e6e7651dcd383bcc3e18d06f9b55d6d1"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b9fe39a2ccc108a4accc2676e77da025ce383c108593d65cc909add5c3bd601"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:96e37a3dc86e80bf81758c152fe66dbf60ed5eca3d26305edf01892257049925"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6d0072fea50feec76a4c418096652f2c3238eaa014b2f94aeb1d56a66b41403f"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:089cf3dbf0cd6c100f02945abeb18484bd1ee57a079aefd52cffd17fba910b88"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6a074d34ee7a5ce3effbc526b7083ec9731bb3cbf921bbe1d3005d4d2bdb3a63"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-win32.whl", hash = "sha256:421be9fbf0ffe9ffd7a378aafebbf6f4602d564d34be190fc19a193232fd12b1"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:fc7b548b17d238737688817ab67deebb30e8073c95749d55538ed473130ec0c7"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e04e26803c9c3851c931eac40c695602c6295b8d432cbe78609649ad9bd2da8a"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b87db4360013327109564f0e591bd2a3b318547bcef31b468a92ee504d07ae4f"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:99a2a507ed3ac881b975a2976d59f38c19386d128e7a9a18b7df6fff1fd4c1d6"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:56442863ed2b06d19c37f94d999035e15ee982988920e12a5b4ba29b62ad1f77"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3ce11ee3f23f79dbd06fb3d63e2f6af7b12db1d46932fe7bd8afa259a5996603"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:33b74d289bd2f5e527beadcaa3f401e0df0a89927c1559c8566c066fa4248ab7"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:43093fb83d8343aac0b1baa75516da6092f58f41200907ef92448ecab8825135"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8e3dcf21f367459434c18e71b2a9532d96547aef8a871872a5bd69a715c15f96"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-win32.whl", hash = "sha256:d4306c36ca495956b6d568d276ac11fdd9c30a36f1b6eb928070dc5360b22e1c"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:46d00d6cfecdde84d40e572d63735ef81423ad31184100411e6e3388d405e247"}, - {file = "MarkupSafe-2.1.1.tar.gz", hash = "sha256:7f91197cc9e48f989d12e4e6fbc46495c446636dfc81b9ccf50bb0ec74b91d4b"}, -] -mutagen = [ - {file = "mutagen-1.45.1-py3-none-any.whl", hash = "sha256:9c9f243fcec7f410f138cb12c21c84c64fde4195481a30c9bfb05b5f003adfed"}, - {file = "mutagen-1.45.1.tar.gz", hash = "sha256:6397602efb3c2d7baebd2166ed85731ae1c1d475abca22090b7141ff5034b3e1"}, -] -pillow = [ - {file = "Pillow-9.1.1-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:42dfefbef90eb67c10c45a73a9bc1599d4dac920f7dfcbf4ec6b80cb620757fe"}, - {file = "Pillow-9.1.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ffde4c6fabb52891d81606411cbfaf77756e3b561b566efd270b3ed3791fde4e"}, - {file = "Pillow-9.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9c857532c719fb30fafabd2371ce9b7031812ff3889d75273827633bca0c4602"}, - {file = "Pillow-9.1.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:59789a7d06c742e9d13b883d5e3569188c16acb02eeed2510fd3bfdbc1bd1530"}, - {file = "Pillow-9.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4d45dbe4b21a9679c3e8b3f7f4f42a45a7d3ddff8a4a16109dff0e1da30a35b2"}, - {file = "Pillow-9.1.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:e9ed59d1b6ee837f4515b9584f3d26cf0388b742a11ecdae0d9237a94505d03a"}, - {file = "Pillow-9.1.1-cp310-cp310-win32.whl", hash = "sha256:b3fe2ff1e1715d4475d7e2c3e8dabd7c025f4410f79513b4ff2de3d51ce0fa9c"}, - {file = "Pillow-9.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:5b650dbbc0969a4e226d98a0b440c2f07a850896aed9266b6fedc0f7e7834108"}, - {file = "Pillow-9.1.1-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:0b4d5ad2cd3a1f0d1df882d926b37dbb2ab6c823ae21d041b46910c8f8cd844b"}, - {file = "Pillow-9.1.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9370d6744d379f2de5d7fa95cdbd3a4d92f0b0ef29609b4b1687f16bc197063d"}, - {file = "Pillow-9.1.1-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b761727ed7d593e49671d1827044b942dd2f4caae6e51bab144d4accf8244a84"}, - {file = "Pillow-9.1.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a66fe50386162df2da701b3722781cbe90ce043e7d53c1fd6bd801bca6b48d4"}, - {file = "Pillow-9.1.1-cp37-cp37m-win32.whl", hash = "sha256:2b291cab8a888658d72b575a03e340509b6b050b62db1f5539dd5cd18fd50578"}, - {file = "Pillow-9.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:1d4331aeb12f6b3791911a6da82de72257a99ad99726ed6b63f481c0184b6fb9"}, - {file = "Pillow-9.1.1-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:8844217cdf66eabe39567118f229e275f0727e9195635a15e0e4b9227458daaf"}, - {file = "Pillow-9.1.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b6617221ff08fbd3b7a811950b5c3f9367f6e941b86259843eab77c8e3d2b56b"}, - {file = "Pillow-9.1.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:20d514c989fa28e73a5adbddd7a171afa5824710d0ab06d4e1234195d2a2e546"}, - {file = "Pillow-9.1.1-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:088df396b047477dd1bbc7de6e22f58400dae2f21310d9e2ec2933b2ef7dfa4f"}, - {file = "Pillow-9.1.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:53c27bd452e0f1bc4bfed07ceb235663a1df7c74df08e37fd6b03eb89454946a"}, - {file = "Pillow-9.1.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:3f6c1716c473ebd1649663bf3b42702d0d53e27af8b64642be0dd3598c761fb1"}, - {file = "Pillow-9.1.1-cp38-cp38-win32.whl", hash = "sha256:c67db410508b9de9c4694c57ed754b65a460e4812126e87f5052ecf23a011a54"}, - {file = "Pillow-9.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:f054b020c4d7e9786ae0404278ea318768eb123403b18453e28e47cdb7a0a4bf"}, - {file = "Pillow-9.1.1-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:c17770a62a71718a74b7548098a74cd6880be16bcfff5f937f900ead90ca8e92"}, - {file = "Pillow-9.1.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f3f6a6034140e9e17e9abc175fc7a266a6e63652028e157750bd98e804a8ed9a"}, - {file = "Pillow-9.1.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f372d0f08eff1475ef426344efe42493f71f377ec52237bf153c5713de987251"}, - {file = "Pillow-9.1.1-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:09e67ef6e430f90caa093528bd758b0616f8165e57ed8d8ce014ae32df6a831d"}, - {file = "Pillow-9.1.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:66daa16952d5bf0c9d5389c5e9df562922a59bd16d77e2a276e575d32e38afd1"}, - {file = "Pillow-9.1.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d78ca526a559fb84faaaf84da2dd4addef5edb109db8b81677c0bb1aad342601"}, - {file = "Pillow-9.1.1-cp39-cp39-win32.whl", hash = "sha256:55e74faf8359ddda43fee01bffbc5bd99d96ea508d8a08c527099e84eb708f45"}, - {file = "Pillow-9.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:7c150dbbb4a94ea4825d1e5f2c5501af7141ea95825fadd7829f9b11c97aaf6c"}, - {file = "Pillow-9.1.1-pp37-pypy37_pp73-macosx_10_10_x86_64.whl", hash = "sha256:769a7f131a2f43752455cc72f9f7a093c3ff3856bf976c5fb53a59d0ccc704f6"}, - {file = "Pillow-9.1.1-pp37-pypy37_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:488f3383cf5159907d48d32957ac6f9ea85ccdcc296c14eca1a4e396ecc32098"}, - {file = "Pillow-9.1.1-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b525a356680022b0af53385944026d3486fc8c013638cf9900eb87c866afb4c"}, - {file = "Pillow-9.1.1-pp38-pypy38_pp73-macosx_10_10_x86_64.whl", hash = "sha256:6e760cf01259a1c0a50f3c845f9cad1af30577fd8b670339b1659c6d0e7a41dd"}, - {file = "Pillow-9.1.1-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a4165205a13b16a29e1ac57efeee6be2dfd5b5408122d59ef2145bc3239fa340"}, - {file = "Pillow-9.1.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:937a54e5694684f74dcbf6e24cc453bfc5b33940216ddd8f4cd8f0f79167f765"}, - {file = "Pillow-9.1.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:baf3be0b9446a4083cc0c5bb9f9c964034be5374b5bc09757be89f5d2fa247b8"}, - {file = "Pillow-9.1.1.tar.gz", hash = "sha256:7502539939b53d7565f3d11d87c78e7ec900d3c72945d4ee0e2f250d598309a0"}, -] -progress = [ - {file = "progress-1.6.tar.gz", hash = "sha256:c9c86e98b5c03fa1fe11e3b67c1feda4788b8d0fe7336c2ff7d5644ccfba34cd"}, -] -pymongo = [ - {file = "pymongo-4.1.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:eff9818b7671a55f1ce781398607e0d8c304cd430c0581fbe15b868a7a371c27"}, - {file = "pymongo-4.1.1-cp310-cp310-manylinux1_i686.whl", hash = "sha256:7507439cd799295893b5602f438f8b6a0f483efb00720df1aa33a39102b41bcf"}, - {file = "pymongo-4.1.1-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:c759e1e0333664831d8d1d6b26cf59f23f3707758f696c71f506504b33130f81"}, - {file = "pymongo-4.1.1-cp310-cp310-manylinux2014_i686.whl", hash = "sha256:69beffb048de19f7c18617b90e38cbddfac20077b1826c27c3fe2e3ef8ac5a43"}, - {file = "pymongo-4.1.1-cp310-cp310-manylinux2014_ppc64le.whl", hash = "sha256:cbcac9263f500da94405cc9fc7e7a42a3ba6c2fe88b2cd7039737cba44c66889"}, - {file = "pymongo-4.1.1-cp310-cp310-manylinux2014_s390x.whl", hash = "sha256:d4ba5b4f1a0334dbe673f767f28775744e793fcb9ea57a1d72bc622c9f90e6b4"}, - {file = "pymongo-4.1.1-cp310-cp310-manylinux2014_x86_64.whl", hash = "sha256:c575f9499e5f540e034ff87bef894f031ae613a98b0d1d3afcc1f482527d5f1c"}, - {file = "pymongo-4.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:89f32d8450e15b0c11efdc81e2704d68c502c889d48415a50add9fa031144f75"}, - {file = "pymongo-4.1.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a1417cb339a367a5dfd0e50193a1c0e87e31325547a0e7624ee4ff414c0b53b3"}, - {file = "pymongo-4.1.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:56b856a459762a3c052987e28ed2bd4b874f0be6671d2cc4f74c4891f47f997a"}, - {file = "pymongo-4.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a938d4d5b530f8ea988afb80817209eabc150c53b8c7af79d40080313a35e470"}, - {file = "pymongo-4.1.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c604831daf2e7e5979ecd97a90cb8c4a7bae208ff45bc792e32eae09c3281afb"}, - {file = "pymongo-4.1.1-cp310-cp310-win32.whl", hash = "sha256:f9405c02af86850e0a8a8ba777b7e7609e0d07bff46adc4f78892cc2d5456018"}, - {file = "pymongo-4.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:e13ddfe2ead9540e8773cae098f54c5206d6fcef64846a3e5042db47fc3a41ed"}, - {file = "pymongo-4.1.1-cp36-cp36m-macosx_10_6_intel.whl", hash = "sha256:7f36eacc70849d40ce86c85042ecfcbeab810691b1a3b08062ede32a2d6521ac"}, - {file = "pymongo-4.1.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:302ac0f4825501ab0900b8f1a2bb2dc7d28f69c7f15fbc799fb26f9b9ebb1ecb"}, - {file = "pymongo-4.1.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:9ee1b019a4640bf39c0705ab65e934cfe6b89f1a8dc26f389fae3d7c62358d6f"}, - {file = "pymongo-4.1.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:c3637cfce519560e2a2579d05eb81e912d109283b8ddc8de46f57ec20d273d92"}, - {file = "pymongo-4.1.1-cp36-cp36m-manylinux2014_i686.whl", hash = "sha256:a0d7c6d6fbca62508ea525abd869fca78ecf68cd3bcf6ae67ec478aa37cf39c0"}, - {file = "pymongo-4.1.1-cp36-cp36m-manylinux2014_ppc64le.whl", hash = "sha256:f1fba193ab2f25849e24caa4570611aa2f80bc1c1ba791851523734b4ed69e43"}, - {file = "pymongo-4.1.1-cp36-cp36m-manylinux2014_s390x.whl", hash = "sha256:c8a2743dd50629c0222f26c5f55975e45841d985b4b1c7a54b3f03b53de3427d"}, - {file = "pymongo-4.1.1-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:8357aa727094798f1d831339ecfd8b3e388c01db6015a3cbd51790cb75e39994"}, - {file = "pymongo-4.1.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0f7e3872fb7b61ec574b7e04302ea03928b670df583f8691cb1df6e54cd42b19"}, - {file = "pymongo-4.1.1-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4aa4800530782f7d38aeb169476a5bc692aacc394686f0ca3866e4bb85c9aa3f"}, - {file = "pymongo-4.1.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7d69a3d980ecbf7238ab37b9027c87ad3b278bb3742a150fc33b5a8a9d990431"}, - {file = "pymongo-4.1.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:df9084e06efb3d59608a6a443faa9861828585579f0ae8e95f5a4dab70f1a00f"}, - {file = "pymongo-4.1.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:be3ba736aabf856195199208ed37459408c932940cbccd2dc9f6ff2e800b0261"}, - {file = "pymongo-4.1.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8f106468062ac7ff03e3522a66cb7b36c662326d8eb7af1be0f30563740ff002"}, - {file = "pymongo-4.1.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:019a4c13ef1d9accd08de70247068671b116a0383adcd684f6365219f29f41cd"}, - {file = "pymongo-4.1.1-cp36-cp36m-win32.whl", hash = "sha256:a7d1c8830a7bc10420ceb60a256d25ab5b032a6dad12a46af6ab2e470cee9124"}, - {file = "pymongo-4.1.1-cp36-cp36m-win_amd64.whl", hash = "sha256:08a619c92769bd7346434dfc331a3aa8dc63bee80ed0be250bb0e878c69a6f3e"}, - {file = "pymongo-4.1.1-cp37-cp37m-macosx_10_6_intel.whl", hash = "sha256:c1349331fa743eed4042f9652200e60596f8beb957554acbcbb42aad4272c606"}, - {file = "pymongo-4.1.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:8a1de8931cdad8cd12724e12a6167eef8cb478cc3ee5d2c9f4670c934f2975e1"}, - {file = "pymongo-4.1.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:86b18420f00d5977bda477369ac85e04185ef94046a04ae0d85f5a807d1a8eb4"}, - {file = "pymongo-4.1.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:84dc6bfeaeba98fe93fc837b12f9af4842694cdbde18083f150e80aec3de88f9"}, - {file = "pymongo-4.1.1-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:306336dab4537b2343e52ec34017c3051c3aee5a961fff4915ab27f7e6d9b1e9"}, - {file = "pymongo-4.1.1-cp37-cp37m-manylinux2014_ppc64le.whl", hash = "sha256:c481cd1af2a77f58f495f7f87c2d715c6f1179d07c1ec927cca1f7977a2d99aa"}, - {file = "pymongo-4.1.1-cp37-cp37m-manylinux2014_s390x.whl", hash = "sha256:cce1b7a680653e31ff2b252f19a39f1ded578a35a96c419ddb9632c62d2af7d8"}, - {file = "pymongo-4.1.1-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:35d02603c2318676fca5049cdc722bb2e7a378eaccf139ad767365e0eb3bcdbe"}, - {file = "pymongo-4.1.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cf96799b3e5e2e2f6dbca015f72b28e7ae415ce8147472f89a3704a035d6336d"}, - {file = "pymongo-4.1.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7aa40509dd9f75c256f0a7533d5e2ccef711dbbf0d91c13ac937d21d76d71656"}, - {file = "pymongo-4.1.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4d1cdece06156542c18b691511a01fe78a694b9fa287ffd8e15680dbf2beeed5"}, - {file = "pymongo-4.1.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:17df40753085ccba38a0e150001f757910d66440d9b5deced30ed4cc8b45b6f3"}, - {file = "pymongo-4.1.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4516a5ce2beaebddc74d6e304ed520324dda99573c310ef4078284b026f81e93"}, - {file = "pymongo-4.1.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:52c8b7bffd2140818ade2aa28c24cfe47935a7273a3bb976d1d8fb17e716536f"}, - {file = "pymongo-4.1.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:7dae2cf84a09329617b08731b95ad1fc98d50a9b40c2007e351438bd119a2f7a"}, - {file = "pymongo-4.1.1-cp37-cp37m-win32.whl", hash = "sha256:0a3474e6a0df0077a44573727341df6627042df5ca61ea5373c157bb6512ccc7"}, - {file = "pymongo-4.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:deb83cc9f639045e2febcc8d4306d4b83893af8d895f2ed70aa342a3430b534c"}, - {file = "pymongo-4.1.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:298908478d07871dbe17e9ccd37a10a27ad3f37cc1faaf0cc4d205da3c3e8539"}, - {file = "pymongo-4.1.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:5d6ef3fa41f3e3be93483a77f81dea8c7ce5ed4411382a31af2b09b9ec5d9585"}, - {file = "pymongo-4.1.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:d06ed18917dbc7a938c4231cbbec52a7e474be270b2ef9208abb4d5a34f5ceb9"}, - {file = "pymongo-4.1.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:4e4d2babb8737d650250d0fa940ffa1b88aa92b8eb399af093734950a1eeca45"}, - {file = "pymongo-4.1.1-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:303d1b3da2461586379d98b344b529598c8156857285ba5bd156dab1c875d1f6"}, - {file = "pymongo-4.1.1-cp38-cp38-manylinux2014_ppc64le.whl", hash = "sha256:6396f0db060db9d8751167ea08f3a77a41a71cd39236fade4409394e57b377e8"}, - {file = "pymongo-4.1.1-cp38-cp38-manylinux2014_s390x.whl", hash = "sha256:9a4ea87a0401c06b687db29e2ae836b2b58480ab118cb6eea8ac2ef45a4345f8"}, - {file = "pymongo-4.1.1-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:49bb36986f11da2da190a2e777a411c0a28eeb8623850091ea8099b84e3860c7"}, - {file = "pymongo-4.1.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5cae9c935cdc53e4729920543b7d990615a115d85f32144773bc4b2b05144628"}, - {file = "pymongo-4.1.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:baf7546afd27be4f96f23307d7c295497fb512875167743b14a7457b95761294"}, - {file = "pymongo-4.1.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:07f50a3b8a3afb086089abcd9ab562fb2a27b63fd7017ca13dfe7b663c8f3762"}, - {file = "pymongo-4.1.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b8a1c766de29173ddbd316dbd75a97b19a4cf9ac45a39ad4f53426e5df1483b"}, - {file = "pymongo-4.1.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9a2c377106fe01a57bad0f703653de286d56ee5285ed36c6953535cfa11f928"}, - {file = "pymongo-4.1.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:dfb89e92746e4a1e0d091cba73d6cc1e16b4094ebdbb14c2e96a80320feb1ad7"}, - {file = "pymongo-4.1.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:bb21e2f35d6f09aa4a6df0c716f41e036cfcf05a98323b50294f93085ad775e9"}, - {file = "pymongo-4.1.1-cp38-cp38-win32.whl", hash = "sha256:dbe92a8808cefb284e235b8f82933d7d2e24ff929fe5d53f1fd3ca55fced4b58"}, - {file = "pymongo-4.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:e6eecd027b6ba5617ea6af3e12e20d578d8f4ad1bf51a9abe69c6fd4835ea532"}, - {file = "pymongo-4.1.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:fb4445e3721720c5ca14c0650f35c263b3430e6e16df9d2504618df914b3fb99"}, - {file = "pymongo-4.1.1-cp39-cp39-manylinux1_i686.whl", hash = "sha256:f6db4f00d3baad615e99a865539391243d12b113fb628ebda1d7794ce02d5a10"}, - {file = "pymongo-4.1.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:571a3e1ef4abeb4ac719ac381f5aada664627b4ee048d9995e93b4bcd0f70601"}, - {file = "pymongo-4.1.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:c03eb43d15c8af58159e7561076634d565530aaacaf48cf4e070c3501e88a372"}, - {file = "pymongo-4.1.1-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:30d35a8855f328a85e5002f0908b24e500efdf8f5f78b73098995ce111baa2a9"}, - {file = "pymongo-4.1.1-cp39-cp39-manylinux2014_ppc64le.whl", hash = "sha256:33a5693e8d1fbb7743b7e867d43c1095652a0c6fedddab6cefe6020bee2ca393"}, - {file = "pymongo-4.1.1-cp39-cp39-manylinux2014_s390x.whl", hash = "sha256:a35f1937b0560587d478fd2259a6d4f66cf511c9d28e90b52b183745eaa77d95"}, - {file = "pymongo-4.1.1-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:4fd5c4f25d8d488ee5701c3ec786f52907dca653b47ce8709bcc2bfb0f5506ae"}, - {file = "pymongo-4.1.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:db8a9cbe965c7343feab2e2bf9a3771f303f8a7ca401dececb6ef28e06b3b18c"}, - {file = "pymongo-4.1.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7f55a602d55e8f0feafde533c69dfd29bf0e54645ab0996b605613cda6894a85"}, - {file = "pymongo-4.1.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a4a35e83abfdac7095430e1c1476e0871e4b234e936f4a7a7631531b09a4f198"}, - {file = "pymongo-4.1.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:32e785c37f6a0e844788c6085ea2c9c0c528348c22cebe91896705a92f2b1b26"}, - {file = "pymongo-4.1.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bc62ba37bcb42e4146b853940b65a2de31c2962d2b6da9bc3ce28270d13b5c4e"}, - {file = "pymongo-4.1.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d86511ef8217822fb8716460aaa1ece31fe9e8a48900e541cb35acb7c35e9e2e"}, - {file = "pymongo-4.1.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:e4956384340eec7b526149ac126c8aa11d32441cb3ce77a690cb4821d0d0635c"}, - {file = "pymongo-4.1.1-cp39-cp39-win32.whl", hash = "sha256:3139c9ddee379c22a9109a0b3bf4cdb64597db2bbd3909f7a2825b47226977a4"}, - {file = "pymongo-4.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:f0aea377b9dfc166c8fa05bb158c30ee3d53d73f0ed2fc05ba6c638d9563422f"}, - {file = "pymongo-4.1.1.tar.gz", hash = "sha256:d7b8f25c9b0043cbaf77b8b895814e33e7a3c807a097377c07e1bd49946030d5"}, -] -rapidfuzz = [ - {file = "rapidfuzz-2.0.11-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:eb54edd0fa8620d37a7c0762895260bc75a6cc083d161b14d40a562b6f303975"}, - {file = "rapidfuzz-2.0.11-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:8093b5f234be618bb8cfe34d65c072fee362fbd13f6c1b37f80eac0f30c24cfa"}, - {file = "rapidfuzz-2.0.11-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:aea72586c08ba8ce08c37c21bb7c383df740dc7d6e921423e1881570be62ed15"}, - {file = "rapidfuzz-2.0.11-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1ee9a057b7638e91377b217df3724d4adefec3936617180b3df1f64fa64cd995"}, - {file = "rapidfuzz-2.0.11-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bad453a76f6832a99251beb89c352a4f436f4e7687a5843b080c294dba68d8d6"}, - {file = "rapidfuzz-2.0.11-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a545627a7d45ea4ad1cb66fb6ad7b951825b0e97053056cda9d2f5fbb30abe3e"}, - {file = "rapidfuzz-2.0.11-cp310-cp310-win32.whl", hash = "sha256:8e583595efe5afdd68a7b5423cbd5fff0d1870d60cee16af17897f701f39d933"}, - {file = "rapidfuzz-2.0.11-cp310-cp310-win_amd64.whl", hash = "sha256:09efd5a02a33dfb18ec6f28b85f102b51cbac080e624924f3a4f36d3b08962ef"}, - {file = "rapidfuzz-2.0.11-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:c81216ecff325765bb441caf7f50a1f55aa66192aca12ec6d448b509c9387a39"}, - {file = "rapidfuzz-2.0.11-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4b555dbebb413ab66c2cd394338c860094a5464f9b63faeb40ebec44271c460b"}, - {file = "rapidfuzz-2.0.11-cp36-cp36m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a007fe85dfff7a961daba13884629dcd9ab45197a2fc40749a7e8f750e7715a4"}, - {file = "rapidfuzz-2.0.11-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1031420c083681b41346267e26f9f76ef2c1544a0129fa67b07239d7a9ab9fd6"}, - {file = "rapidfuzz-2.0.11-cp36-cp36m-win32.whl", hash = "sha256:4b16147122ec4c5e4a31131b8530e674ba1b3e74e2b43b73aedc6bd0021fcae6"}, - {file = "rapidfuzz-2.0.11-cp36-cp36m-win_amd64.whl", hash = "sha256:de7559765e1da54d8d42495368e0a9852041cf8d4e077fb27811d6f009611a4b"}, - {file = "rapidfuzz-2.0.11-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:ace59b7857e5d5b252564dd60d840667c19c00d357c7ba32e9671b68615dc49a"}, - {file = "rapidfuzz-2.0.11-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:09d83e5ab57fb61a6003a3607494b1f443978e8d6b199fed3094e92f466f3bba"}, - {file = "rapidfuzz-2.0.11-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3a53797613b53e93adbf9c410260aecde5ab1d7cd1b07792be1ee4800716598"}, - {file = "rapidfuzz-2.0.11-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c9084a550719aff3752e5a63e32d381d64b09264cabf35d5e21e6a9f0e91baca"}, - {file = "rapidfuzz-2.0.11-cp37-cp37m-win32.whl", hash = "sha256:35c8f2cae3e2079616fdf90c6b6bcf850d3810c9184c6e89a4826b6d0af88974"}, - {file = "rapidfuzz-2.0.11-cp37-cp37m-win_amd64.whl", hash = "sha256:1da580130b37a007684ab9dc6f85125e3c0d06c9f9df349e7bd52e312811c436"}, - {file = "rapidfuzz-2.0.11-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:757ae64598a93d0f8a21007c1abd6800f38c04e4b89167ca7b833ce30f54aef3"}, - {file = "rapidfuzz-2.0.11-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f5b0fd8f6bde8d89c07b76643c9f3a01e2e089b246a97b721e7fe97fdaa41820"}, - {file = "rapidfuzz-2.0.11-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a13cea3303b444af49417352cd11830ea2245d4e5a82bb06b6895638b81c6029"}, - {file = "rapidfuzz-2.0.11-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d96dba6ef863cba7efd22077ba28e19a8829b523c7c7e41304c568a6ab91fc4d"}, - {file = "rapidfuzz-2.0.11-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9dcecc662df808ad051d9524608a3682fd80d882c93664adbaab4c7b0796e385"}, - {file = "rapidfuzz-2.0.11-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ff74f3abd0ed473f81ba67d3207ca6db74b8b50ebae1a6734ba199a8d90d67b4"}, - {file = "rapidfuzz-2.0.11-cp38-cp38-win32.whl", hash = "sha256:b3e7eea1dd304bac4ac74a1af71da35bb68bf6060f5d6b4ad8a3e4e2c84d5110"}, - {file = "rapidfuzz-2.0.11-cp38-cp38-win_amd64.whl", hash = "sha256:5b203e83adc10dbe961a3000fa09cc47f5672d2c98c3fd2f6bb7b0df225805ee"}, - {file = "rapidfuzz-2.0.11-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:1fe837f8b305c59549e2eec6fe8dbdd7344eeef0033fa4ee90af65f72b32c25f"}, - {file = "rapidfuzz-2.0.11-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:fc2c8aa23de4a0bef2162440f5095f606052c289059fbeb03180740783e25e6b"}, - {file = "rapidfuzz-2.0.11-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ebb03a6a5171233958b6adcada00c7521186ea4b78b6652b99d94d5dbf59c809"}, - {file = "rapidfuzz-2.0.11-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0f21d8754ee49ee8da73ad5e746495a27fd29ab769f5e45ede4d8232955e0237"}, - {file = "rapidfuzz-2.0.11-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:23c79ca555f188445f2e054b40a89c82e5d21b22d34f00da6e7491a6d70feff5"}, - {file = "rapidfuzz-2.0.11-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5b10de1ea834be1f26d1f35f3e1d1f8c003c7951a7475ab9b28b5a62e9f6f0c0"}, - {file = "rapidfuzz-2.0.11-cp39-cp39-win32.whl", hash = "sha256:1bda150aca38c4d4739780c3a99c190c05101839adc10ab7804ed86000440267"}, - {file = "rapidfuzz-2.0.11-cp39-cp39-win_amd64.whl", hash = "sha256:f4ea654ef221a57b47523fe70d7423254dd285f73948b9d8c1215610d2a38e9e"}, - {file = "rapidfuzz-2.0.11-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2542f0a3c4079b15c0485ece589e5a248633de84326e4a3ca63ea024a0b59775"}, - {file = "rapidfuzz-2.0.11-pp37-pypy37_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2dca3c02511d23a58ef14f2fbcd7b311eae1bc40e3d36be493ef22b9572ebed1"}, - {file = "rapidfuzz-2.0.11-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3fbb649a978fab0232cefdeb67321d853c676b3ebb7481b8b80030905a42d799"}, - {file = "rapidfuzz-2.0.11-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:081415323e94e0016109715438d4ccb233ab038b09ba3cf79038e50601a410e9"}, - {file = "rapidfuzz-2.0.11-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a80efb64f1b38c64f04f4ca6881c9684d7912dc9124ecbf953c9b541f935b33c"}, - {file = "rapidfuzz-2.0.11-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6ac8ab8106bc7b0ffab539baa5279a850c61b71ccad86dc11503bc084f6ac1af"}, - {file = "rapidfuzz-2.0.11-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3933a1cfdf6ab4e059d8eb68460fa430a4f6be06431d1a8b05f7fecdd63e586e"}, - {file = "rapidfuzz-2.0.11-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:537b72d954ff395cefc210fc7e41810a26e84ed7f1e93d0dffe3669277d6ea23"}, - {file = "rapidfuzz-2.0.11-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b2d167b1bf92a60eefbaea3abf646baa2a3aa7125595e129c8890072706a80ac"}, - {file = "rapidfuzz-2.0.11.tar.gz", hash = "sha256:934b65fea75e3bd310d74903ec69ff3df061b3058ab5b7f49ab772958109bca8"}, -] -requests = [ - {file = "requests-2.27.1-py2.py3-none-any.whl", hash = "sha256:f22fa1e554c9ddfd16e6e41ac79759e17be9e492b3587efa038054674760e72d"}, - {file = "requests-2.27.1.tar.gz", hash = "sha256:68d7c56fd5a8999887728ef304a6d12edc7be74f1cfa47714fc8b414525c9a61"}, -] -six = [ - {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, - {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, -] -tqdm = [ - {file = "tqdm-4.64.0-py2.py3-none-any.whl", hash = "sha256:74a2cdefe14d11442cedf3ba4e21a3b84ff9a2dbdc6cfae2c34addb2a14a5ea6"}, - {file = "tqdm-4.64.0.tar.gz", hash = "sha256:40be55d30e200777a307a7585aee69e4eabb46b4ec6a4b4a5f2d9f11e7d5408d"}, -] -urllib3 = [ - {file = "urllib3-1.26.9-py2.py3-none-any.whl", hash = "sha256:44ece4d53fb1706f667c9bd1c648f5469a2ec925fcf3a776667042d645472c14"}, - {file = "urllib3-1.26.9.tar.gz", hash = "sha256:aabaf16477806a5e1dd19aa41f8c2b7950dd3c746362d7e3223dbe6de6ac448e"}, -] -watchdog = [ - {file = "watchdog-2.1.8-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:676263bee67b165f16b05abc52acc7a94feac5b5ab2449b491f1a97638a79277"}, - {file = "watchdog-2.1.8-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:aa68d2d9a89d686fae99d28a6edf3b18595e78f5adf4f5c18fbfda549ac0f20c"}, - {file = "watchdog-2.1.8-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5e2e51c53666850c3ecffe9d265fc5d7351db644de17b15e9c685dd3cdcd6f97"}, - {file = "watchdog-2.1.8-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:7721ac736170b191c50806f43357407138c6748e4eb3e69b071397f7f7aaeedd"}, - {file = "watchdog-2.1.8-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:ce7376aed3da5fd777483fe5ebc8475a440c6d18f23998024f832134b2938e7b"}, - {file = "watchdog-2.1.8-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:f9ee4c6bf3a1b2ed6be90a2d78f3f4bbd8105b6390c04a86eb48ed67bbfa0b0b"}, - {file = "watchdog-2.1.8-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:68dbe75e0fa1ba4d73ab3f8e67b21770fbed0651d32ce515cd38919a26873266"}, - {file = "watchdog-2.1.8-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:0c520009b8cce79099237d810aaa19bc920941c268578436b62013b2f0102320"}, - {file = "watchdog-2.1.8-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:efcc8cbc1b43902571b3dce7ef53003f5b97fe4f275fe0489565fc6e2ebe3314"}, - {file = "watchdog-2.1.8-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:746e4c197ec1083581bb1f64d07d1136accf03437badb5ff8fcb862565c193b2"}, - {file = "watchdog-2.1.8-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1ae17b6be788fb8e4d8753d8d599de948f0275a232416e16436363c682c6f850"}, - {file = "watchdog-2.1.8-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:ddde157dc1447d8130cb5b8df102fad845916fe4335e3d3c3f44c16565becbb7"}, - {file = "watchdog-2.1.8-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:4978db33fc0934c92013ee163a9db158ec216099b69fce5aec790aba704da412"}, - {file = "watchdog-2.1.8-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:b962de4d7d92ff78fb2dbc6a0cb292a679dea879a0eb5568911484d56545b153"}, - {file = "watchdog-2.1.8-py3-none-manylinux2014_aarch64.whl", hash = "sha256:1e5d0fdfaa265c29dc12621913a76ae99656cf7587d03950dfeb3595e5a26102"}, - {file = "watchdog-2.1.8-py3-none-manylinux2014_armv7l.whl", hash = "sha256:036ed15f7cd656351bf4e17244447be0a09a61aaa92014332d50719fc5973bc0"}, - {file = "watchdog-2.1.8-py3-none-manylinux2014_i686.whl", hash = "sha256:2962628a8777650703e8f6f2593065884c602df7bae95759b2df267bd89b2ef5"}, - {file = "watchdog-2.1.8-py3-none-manylinux2014_ppc64.whl", hash = "sha256:156ec3a94695ea68cfb83454b98754af6e276031ba1ae7ae724dc6bf8973b92a"}, - {file = "watchdog-2.1.8-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:47598fe6713fc1fee86b1ca85c9cbe77e9b72d002d6adeab9c3b608f8a5ead10"}, - {file = "watchdog-2.1.8-py3-none-manylinux2014_s390x.whl", hash = "sha256:fed4de6e45a4f16e4046ea00917b4fe1700b97244e5d114f594b4a1b9de6bed8"}, - {file = "watchdog-2.1.8-py3-none-manylinux2014_x86_64.whl", hash = "sha256:24dedcc3ce75e150f2a1d704661f6879764461a481ba15a57dc80543de46021c"}, - {file = "watchdog-2.1.8-py3-none-win32.whl", hash = "sha256:6ddf67bc9f413791072e3afb466e46cc72c6799ba73dea18439b412e8f2e3257"}, - {file = "watchdog-2.1.8-py3-none-win_amd64.whl", hash = "sha256:88ef3e8640ef0a64b7ad7394b0f23384f58ac19dd759da7eaa9bc04b2898943f"}, - {file = "watchdog-2.1.8-py3-none-win_ia64.whl", hash = "sha256:0fb60c7d31474b21acba54079ce9ff0136411183e9a591369417cddb1d7d00d7"}, - {file = "watchdog-2.1.8.tar.gz", hash = "sha256:6d03149126864abd32715d4e9267d2754cede25a69052901399356ad3bc5ecff"}, -] -werkzeug = [ - {file = "Werkzeug-2.1.2-py3-none-any.whl", hash = "sha256:72a4b735692dd3135217911cbeaa1be5fa3f62bffb8745c5215420a03dc55255"}, - {file = "Werkzeug-2.1.2.tar.gz", hash = "sha256:1ce08e8093ed67d638d63879fd1ba3735817f7a80de3674d293f5984f25fb6e6"}, -] -zipp = [ - {file = "zipp-3.8.0-py3-none-any.whl", hash = "sha256:c4f6e5bbf48e74f7a38e7cc5b0480ff42b0ae5178957d564d18932525d5cf099"}, - {file = "zipp-3.8.0.tar.gz", hash = "sha256:56bf8aadb83c24db6c4b577e13de374ccfb67da2078beba1d037c17980bf43ad"}, -] diff --git a/server/pyproject.toml b/server/pyproject.toml deleted file mode 100644 index 625425fb..00000000 --- a/server/pyproject.toml +++ /dev/null @@ -1,27 +0,0 @@ -[tool.poetry] -name = "Alice Server" -version = "0.1.0" -description = "" -authors = ["geoffrey45 "] - -[tool.poetry.dependencies] -python = "^3.8" -Flask = "^2.0.2" -Flask-Cors = "^3.0.10" -mutagen = "^1.45.1" -pymongo = "^4.0.1" -requests = "^2.27.1" -watchdog = "^2.1.6" -progress = "^1.6" -gunicorn = "^20.1.0" -Pillow = "^9.0.1" -Flask-Caching = "^1.11.1" -"colorgram.py" = "^1.2.0" -tqdm = "^4.64.0" -rapidfuzz = "^2.0.11" - -[tool.poetry.dev-dependencies] - -[build-system] -requires = ["poetry-core>=1.0.0"] -build-backend = "poetry.core.masonry.api" diff --git a/server/roadmap.md b/server/roadmap.md deleted file mode 100644 index 895a6410..00000000 --- a/server/roadmap.md +++ /dev/null @@ -1,39 +0,0 @@ -# Fixes ! - -- [ ] Click on artist image to go to artist page ⚠ -- [ ] Play next song if current song can't be loaded ⚠ - -- [ ] Removing song duplicates from queries -- [ ] Add support for WAV files -- [ ] Compress thumbnails - -# Features + - -## Needed features - -- [ ] Adding songs to queue - -- [ ] Add keyboard shortcuts -- [ ] Adjust volume -- [ ] Add listening statistics for all songs -- [ ] Extract color from artist image [for use with artist card gradient] -- [ ] Adding songs to favorites -- [ ] Playing song radio - -## Future features - -- [ ] Toggle shuffle -- [ ] Toggle repeat -- [ ] Suggest similar artists -- [ ] Getting artist info -- [ ] Create a Python script to build, bundle and serve the app -- [ ] Getting extra song info (probably from genius) -- [ ] Getting lyrics -- [ ] Sorting songs -- [ ] Suggest undiscorvered artists, albums and songs -- [ ] Remember last played song -- [ ] Add next and previous song transition and progress bar reset animations -- [ ] Add playlist to folder -- [ ] Add functionality to 'Listen now' button -- [ ] Paginated requests for songs -- [ ] Package app as installable PWA diff --git a/server/setup/setup.sh b/server/setup/setup.sh deleted file mode 100644 index 45ba2eac..00000000 --- a/server/setup/setup.sh +++ /dev/null @@ -1,11 +0,0 @@ -# commands to install and start mongodb ubuntu 20.04 -wget -qO - https://www.mongodb.org/static/pgp/server-5.0.asc | sudo apt-key add - -sudo apt-get install gnupg -wget -qO - https://www.mongodb.org/static/pgp/server-5.0.asc | sudo apt-key add - -echo "deb [ arch=amd64,arm64 ] https://repo.mongodb.org/apt/ubuntu focal/mongodb-org/5.0 multiverse" | sudo tee /etc/apt/sources.list.d/mongodb-org-5.0.list -sudo apt-get update -sudo apt-get install -y mongodb - -sudo systemctl start mongodb -sudo systemctl daemon-reload -sudo systemctl enable mongodb diff --git a/server/start.sh b/server/start.sh deleted file mode 100755 index c6c0e46a..00000000 --- a/server/start.sh +++ /dev/null @@ -1,22 +0,0 @@ -# ppath=$(poetry run which python) - -# $ppath manage.py - -#python manage.py - -gpath=$(poetry run which gunicorn) -while getopts ':s' opt; do - case $opt in - s) - echo "Starting Alice server" - cd "./app" - "$gpath" -b 0.0.0.0:9877 -w 4 --threads=2 "imgserver:app" & - cd ../ - ;; - \?) - echo "Invalid option: -$OPTARG" >&2 - ;; - esac -done - -"$gpath" -b 0.0.0.0:9876 -w 1 --threads=4 "manage:create_app()" diff --git a/server/wsgi.py b/server/wsgi.py deleted file mode 100644 index feb84884..00000000 --- a/server/wsgi.py +++ /dev/null @@ -1,5 +0,0 @@ -from app import create_app - -if __name__ == '__main__': - app = create_app() - app.run(debug=True, threaded=True)