diff --git a/package.json b/package.json
index 14d6259b..0861b036 100644
--- a/package.json
+++ b/package.json
@@ -9,7 +9,10 @@
"lint": "eslint --ext .js,.vue --ignore-path .gitignore --fix src"
},
"dependencies": {
+ "@vueuse/core": "^8.5.0",
+ "@vueuse/motion": "^2.0.0-beta.18",
"axios": "^0.26.1",
+ "defu": "^6.0.0",
"mitt": "^3.0.0",
"node-vibrant": "^3.2.1-alpha.1",
"pinia": "^2.0.11",
diff --git a/server/app/api/search.py b/server/app/api/search.py
index 18da8342..9c550cec 100644
--- a/server/app/api/search.py
+++ b/server/app/api/search.py
@@ -1,11 +1,10 @@
"""
Contains all the search routes.
"""
-
-from flask import Blueprint, request
-
-from app.lib import searchlib
from app import helpers
+from app.lib import searchlib
+from flask import Blueprint
+from flask import request
search_bp = Blueprint("search", __name__, url_prefix="/")
@@ -16,6 +15,63 @@ SEARCH_RESULTS = {
}
+@search_bp.route("/search/tracks", methods=["GET"])
+def search_tracks():
+ """
+ Searches for tracks.
+ """
+
+ query = request.args.get("q")
+ if not query:
+ return {"error": "No query provided"}, 400
+
+ results = searchlib.SearchTracks(query)()
+ SEARCH_RESULTS["tracks"] = results
+
+ return {
+ "tracks": results[:5],
+ "more": len(results) > 5,
+ }, 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
+
+ results = searchlib.SearchAlbums(query)()
+ SEARCH_RESULTS["albums"] = results
+
+ return {
+ "albums": results[:6],
+ "more": len(results) > 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
+
+ results = searchlib.SearchArtists(query)()
+ SEARCH_RESULTS["artists"] = results
+
+ return {
+ "artists": results[:6],
+ "more": len(results) > 6,
+ }, 200
+
+
@search_bp.route("/search")
def search():
"""
@@ -23,25 +79,14 @@ def search():
"""
query = request.args.get("q") or "Mexican girl"
- albums = searchlib.get_search_albums(query)
- artists_dicts = []
+ albums = searchlib.SearchAlbums(query)()
+ artists_dicts = searchlib.SearchArtists(query)()
- artist_tracks = searchlib.get_artists(query)
+ tracks = searchlib.SearchTracks(query)()
+ top_artist = artists_dicts[0]["name"]
- for song in artist_tracks:
- for artist in song.artists:
- if query.lower() in artist.lower():
-
- artist_obj = {
- "name": artist,
- "image": helpers.check_artist_image(artist),
- }
-
- if artist_obj not in artists_dicts:
- artists_dicts.append(artist_obj)
-
- _tracks = searchlib.get_tracks(query)
- tracks = [*_tracks, *artist_tracks]
+ _tracks = searchlib.GetTopArtistTracks(top_artist)()
+ tracks = [*tracks, *[t for t in _tracks if t not in tracks]]
SEARCH_RESULTS.clear()
SEARCH_RESULTS["tracks"] = tracks
@@ -50,9 +95,18 @@ def search():
return {
"data": [
- {"tracks": tracks[:5], "more": len(tracks) > 5},
- {"albums": albums[:6], "more": len(albums) > 6},
- {"artists": artists_dicts[:6], "more": len(artists_dicts) > 6},
+ {
+ "tracks": tracks[:5],
+ "more": len(tracks) > 5
+ },
+ {
+ "albums": albums[:6],
+ "more": len(albums) > 6
+ },
+ {
+ "artists": artists_dicts[:6],
+ "more": len(artists_dicts) > 6
+ },
]
}
@@ -63,22 +117,25 @@ def search_load_more():
Returns more songs, albums or artists from a search query.
"""
type = request.args.get("type")
- start = int(request.args.get("start"))
+ index = int(request.args.get("index"))
+
+ print(type, index)
+ print(len(SEARCH_RESULTS["tracks"]))
if type == "tracks":
return {
- "tracks": SEARCH_RESULTS["tracks"][start : start + 5],
- "more": len(SEARCH_RESULTS["tracks"]) > start + 5,
+ "tracks": SEARCH_RESULTS["tracks"][index:index + 5],
+ "more": len(SEARCH_RESULTS["tracks"]) > index + 5,
}
elif type == "albums":
return {
- "albums": SEARCH_RESULTS["albums"][start : start + 6],
- "more": len(SEARCH_RESULTS["albums"]) > start + 6,
+ "albums": SEARCH_RESULTS["albums"][index:index + 6],
+ "more": len(SEARCH_RESULTS["albums"]) > index + 6,
}
elif type == "artists":
return {
- "artists": SEARCH_RESULTS["artists"][start : start + 6],
- "more": len(SEARCH_RESULTS["artists"]) > start + 6,
+ "artists": SEARCH_RESULTS["artists"][index:index + 6],
+ "more": len(SEARCH_RESULTS["artists"]) > index + 6,
}
diff --git a/server/app/helpers.py b/server/app/helpers.py
index e6278fee..3cf49327 100644
--- a/server/app/helpers.py
+++ b/server/app/helpers.py
@@ -1,11 +1,11 @@
"""
This module contains mini functions for the server.
"""
-from datetime import datetime
import os
import random
import threading
import time
+from datetime import datetime
from typing import Dict
from typing import List
@@ -27,7 +27,9 @@ def background(func):
return background_func
-def run_fast_scandir(__dir: str, ext: list, full=False) -> Dict[List[str], List[str]]:
+def run_fast_scandir(__dir: str,
+ ext: list,
+ full=False) -> Dict[List[str], List[str]]:
"""
Scans a directory for files with a specific extension. Returns a list of files and folders in the directory.
"""
@@ -60,12 +62,10 @@ def remove_duplicates(tracklist: List[models.Track]) -> List[models.Track]:
while song_num < len(tracklist) - 1:
for index, song in enumerate(tracklist):
- if (
- tracklist[song_num].title == song.title
- and tracklist[song_num].album == song.album
- and tracklist[song_num].artists == song.artists
- and index != song_num
- ):
+ if (tracklist[song_num].title == song.title
+ and tracklist[song_num].album == song.album
+ and tracklist[song_num].artists == song.artists
+ and index != song_num):
tracklist.remove(song)
song_num += 1
@@ -108,7 +108,8 @@ def check_artist_image(image: str) -> str:
"""
img_name = image.replace("/", "::") + ".webp"
- if not os.path.exists(os.path.join(app_dir, "images", "artists", img_name)):
+ if not os.path.exists(os.path.join(app_dir, "images", "artists",
+ img_name)):
return use_memoji()
else:
return img_name
@@ -120,7 +121,15 @@ def create_album_hash(title: str, artist: str) -> str:
"""
return (title + artist).replace(" ", "").lower()
+
def create_new_date():
now = datetime.now()
str = now.strftime("%Y-%m-%d %H:%M:%S")
- return str
\ No newline at end of file
+ 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 not in '/\\:*?"<>|'])
diff --git a/server/app/lib/albumslib.py b/server/app/lib/albumslib.py
index 67193893..58c84669 100644
--- a/server/app/lib/albumslib.py
+++ b/server/app/lib/albumslib.py
@@ -6,13 +6,13 @@ import urllib
from typing import List
from app import api
+from app import helpers
from app import instances
from app import models
from app.lib import taglib
from app.lib import trackslib
from progress.bar import Bar
from tqdm import tqdm
-from app import helpers
def get_all_albums() -> List[models.Album]:
@@ -52,6 +52,10 @@ def create_everything() -> List[models.Track]:
def find_album(albums: List[models.Album], hash: str) -> int | None:
"""
Finds an album by album title and artist.
+
+ :param `albums`: List of album objects.
+ :param `hash`: Hash of album.
+ :return: Index of album in list.
"""
left = 0
@@ -62,8 +66,11 @@ def find_album(albums: List[models.Album], hash: str) -> int | None:
iter += 1
mid = (left + right) // 2
- if albums[mid].hash == hash:
- return mid
+ try:
+ if albums[mid].hash == hash:
+ return mid
+ except AttributeError:
+ print(albums)
if albums[mid].hash < hash:
left = mid + 1
@@ -151,7 +158,7 @@ def get_album_tracks(album: str, artist: str) -> List:
return GetAlbumTracks(album, artist).find_tracks()
-def create_album(track: dict, tracklist: list) -> models.Album:
+def create_album(track: dict, tracklist: list) -> dict:
"""
Generates and returns an album object from a track object.
"""
diff --git a/server/app/lib/populate.py b/server/app/lib/populate.py
index df4c0be3..c5792257 100644
--- a/server/app/lib/populate.py
+++ b/server/app/lib/populate.py
@@ -1,25 +1,26 @@
+import os
+import time
from concurrent.futures import ThreadPoolExecutor
from copy import deepcopy
-
+from multiprocessing import Pool
from os import path
-import time
from typing import List
-from tqdm import tqdm
-
from app import api
from app import settings
-from app.helpers import create_album_hash, run_fast_scandir
+from app.helpers import create_album_hash
+from app.helpers import run_fast_scandir
from app.instances import album_instance
from app.instances import tracks_instance
from app.lib import folderslib
from app.lib.albumslib import create_album
from app.lib.albumslib import find_album
from app.lib.taglib import get_tags
-from app.logger import Log
-from app.models import Album, Track
-
from app.lib.trackslib import find_track
+from app.logger import Log
+from app.models import Album
+from app.models import Track
+from tqdm import tqdm
class Populate:
@@ -46,7 +47,7 @@ class Populate:
def run(self):
self.check_untagged()
- self.tag_all_files()
+ self.get_all_tags()
if len(self.tagged_tracks) == 0:
return
@@ -76,6 +77,17 @@ class Populate:
Log(f"Found {len(self.files)} untagged tracks")
+ def process_tags(self, tags: dict):
+ for t in tags:
+ if t is None:
+ continue
+
+ t["albumhash"] = create_album_hash(t["album"], t["albumartist"])
+ self.tagged_tracks.append(t)
+ api.DB_TRACKS.append(t)
+
+ self.folders.add(t["folder"])
+
def get_tags(self, file: str):
tags = get_tags(file)
@@ -83,19 +95,27 @@ class Populate:
folder = tags["folder"]
self.folders.add(folder)
- tags["albumhash"] = create_album_hash(tags["album"], tags["albumartist"])
+ tags["albumhash"] = create_album_hash(tags["album"],
+ tags["albumartist"])
self.tagged_tracks.append(tags)
api.DB_TRACKS.append(tags)
- def tag_all_files(self):
+ def get_all_tags(self):
"""
Loops through all the untagged files and tags them.
"""
s = time.time()
- print(f"Started tagging files")
- with ThreadPoolExecutor() as executor:
- executor.map(self.get_tags, self.files)
+ # print(f"Started tagging files")
+ # with ThreadPoolExecutor() as executor:
+ # executor.map(self.get_tags, self.files)
+
+ with Pool(maxtasksperchild=10) as p:
+ tags = p.map(get_tags, tqdm(self.files))
+ self.process_tags(tags)
+
+ # for t in tqdm(self.files):
+ # self.get_tags(t)
d = time.time() - s
Log(f"Tagged {len(self.tagged_tracks)} files in {d} seconds")
@@ -149,9 +169,8 @@ class Populate:
for album in tqdm(self.pre_albums, desc="Building albums"):
self.create_album(album)
- Log(
- f"{self.exist_count} of {len(self.pre_albums)} albums were already in the database"
- )
+ Log(f"{self.exist_count} of {len(self.pre_albums)} albums were already in the database"
+ )
def create_track(self, track: dict):
"""
@@ -186,9 +205,8 @@ class Populate:
with ThreadPoolExecutor() as executor:
executor.map(self.create_track, self.tagged_tracks)
- Log(
- f"Added {len(self.tagged_tracks)} new tracks and {len(self.albums)} new albums"
- )
+ Log(f"Added {len(self.tagged_tracks)} new tracks and {len(self.albums)} new albums"
+ )
def save_albums(self):
"""
diff --git a/server/app/lib/searchlib.py b/server/app/lib/searchlib.py
index ae7225c2..6b8ff223 100644
--- a/server/app/lib/searchlib.py
+++ b/server/app/lib/searchlib.py
@@ -1,19 +1,156 @@
"""
This library contains all the functions related to the search functionality.
"""
-
from typing import List
-from app import models, helpers
-from app.lib import albumslib
+
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
-def get_tracks(query: str) -> List[models.Track]:
+class Cutoff:
"""
- Gets all songs with a given title.
+ Holds all the default cutoff values.
"""
- tracks = [track for track in api.TRACKS if query.lower() in track.title.lower()]
- return helpers.remove_duplicates(tracks)
+
+ tracks: int = 70
+ albums: int = 70
+ artists: int = 70
+
+
+class Limit:
+ """
+ Holds all the default limit values.
+ """
+
+ tracks: int = 50
+ albums: int = 50
+ artists: int = 50
+
+
+class SearchTracks:
+
+ def __init__(self, query) -> None:
+ self.query = query
+
+ def __call__(self) -> List[models.Track]:
+ """
+ Gets all songs with a given title.
+ """
+
+ tracks = [track.title for track in api.TRACKS]
+ results = process.extract(
+ self.query,
+ tracks,
+ scorer=fuzz.WRatio,
+ score_cutoff=Cutoff.tracks,
+ limit=Limit.tracks,
+ )
+
+ return [api.TRACKS[i[2]] for i in results]
+
+
+class SearchArtists:
+
+ def __init__(self, query) -> None:
+ self.query = query
+
+ @staticmethod
+ def get_all_artist_names() -> List[str]:
+ """
+ Gets all artist names.
+ """
+
+ artists = [track.artists for track in api.TRACKS]
+
+ f_artists = set()
+
+ for artist in artists:
+ for a in artist:
+ f_artists.add(a)
+
+ return f_artists
+
+ def __call__(self) -> list:
+ """
+ Gets all artists with a given name.
+ """
+
+ artists = self.get_all_artist_names()
+ results = process.extract(
+ self.query,
+ artists,
+ scorer=fuzz.WRatio,
+ score_cutoff=Cutoff.artists,
+ limit=Limit.artists,
+ )
+
+ f_artists = []
+ for artist in results:
+ aa = {
+ "name": artist[0],
+ "image": helpers.create_safe_name(artist[0]) + ".webp",
+ }
+ f_artists.append(aa)
+
+ return f_artists
+
+
+class SearchAlbums:
+
+ def __init__(self, query) -> None:
+ self.query = query
+
+ def get_albums_by_name(self) -> List[models.Album]:
+ """
+ Gets all albums with a given title.
+ """
+
+ albums = [album.title for album in api.ALBUMS]
+ results = process.extract(self.query, albums)
+ return [api.ALBUMS[i[2]] for i in results]
+
+ def __call__(self) -> List[models.Album]:
+ """
+ Gets all albums with a given title.
+ """
+
+ albums = [a.title for a in api.ALBUMS]
+ results = process.extract(
+ self.query,
+ albums,
+ scorer=fuzz.WRatio,
+ score_cutoff=Cutoff.albums,
+ limit=Limit.albums,
+ )
+
+ return [api.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 GetTopArtistTracks:
+
+ def __init__(self, artist: str) -> None:
+ self.artist = artist
+
+ def __call__(self) -> List[models.Track]:
+ """
+ Gets all tracks from a given artist.
+ """
+
+ return [track for track in api.TRACKS if self.artist in track.artists]
def get_search_albums(query: str) -> List[models.Album]:
@@ -27,4 +164,7 @@ def get_artists(artist: str) -> List[models.Track]:
"""
Gets all songs with a given artist.
"""
- return [track for track in api.TRACKS if artist.lower() in str(track.artists).lower()]
+ return [
+ track for track in api.TRACKS
+ if artist.lower() in str(track.artists).lower()
+ ]
diff --git a/server/app/lib/taglib.py b/server/app/lib/taglib.py
index 3a695a51..bee344ff 100644
--- a/server/app/lib/taglib.py
+++ b/server/app/lib/taglib.py
@@ -154,7 +154,7 @@ def parse_disk_number(audio):
return disk_number
-def get_tags(fullpath: str) -> dict:
+def get_tags(fullpath: str) -> dict | None:
"""
Returns a dictionary of tags for a given file.
"""
diff --git a/server/app/lib/watchdoge.py b/server/app/lib/watchdoge.py
index e094d849..91b34196 100644
--- a/server/app/lib/watchdoge.py
+++ b/server/app/lib/watchdoge.py
@@ -7,14 +7,14 @@ import time
from app import api
from app import instances
from app import models
+from app.helpers import create_album_hash
from app.lib import folderslib
-from app.lib.albumslib import create_album, find_album
+from app.lib.albumslib import create_album
+from app.lib.albumslib import find_album
from app.lib.taglib import get_tags
from watchdog.events import PatternMatchingEventHandler
from watchdog.observers import Observer
-from app.helpers import create_album_hash
-
class OnMyWatch:
"""
@@ -50,18 +50,19 @@ def add_track(filepath: str) -> None:
tags = get_tags(filepath)
if tags is not None:
- tags["albumhash"] = create_album_hash(tags["album"], tags["albumartist"])
+ hash = create_album_hash(tags["album"], tags["albumartist"])
+ tags["albumhash"] = hash
api.DB_TRACKS.append(tags)
- albumindex = find_album(tags["album"], tags["albumartist"])
+ albumindex = find_album(api.ALBUMS, hash)
if albumindex is not None:
album = api.ALBUMS[albumindex]
else:
album_data = create_album(tags, api.DB_TRACKS)
- instances.album_instance.insert_album(album_data)
-
album = models.Album(album_data)
+
+ instances.album_instance.insert_album(album)
api.ALBUMS.append(album)
tags["image"] = album.image
@@ -86,7 +87,8 @@ def remove_track(filepath: str) -> None:
fpath = filepath.replace(fname, "")
try:
- trackid = instances.tracks_instance.get_song_by_path(filepath)["_id"]["$oid"]
+ trackid = instances.tracks_instance.get_song_by_path(
+ filepath)["_id"]["$oid"]
except TypeError:
print(f"💙 Watchdog Error: Error removing track {filepath} TypeError")
return
@@ -152,7 +154,14 @@ class Handler(PatternMatchingEventHandler):
Fired when a created file is closed.
"""
print("⚫ closed ~~~")
- self.files_to_process.remove(event.src_path)
+ try:
+ self.files_to_process.remove(event.src_path)
+ except ValueError:
+ """
+ The file was already removed from the list, or it was not in the list to begin with.
+ """
+ pass
+
add_track(event.src_path)
diff --git a/server/app/models.py b/server/app/models.py
index d3d3a35f..27453ff1 100644
--- a/server/app/models.py
+++ b/server/app/models.py
@@ -6,8 +6,8 @@ from dataclasses import field
from typing import List
from app import api
-from app.exceptions import TrackExistsInPlaylist
from app import helpers
+from app.exceptions import TrackExistsInPlaylist
@dataclass(slots=True)
@@ -18,7 +18,7 @@ class Track:
trackid: str
title: str
- artists: str
+ artists: list
albumartist: str
album: str
folder: str
@@ -32,8 +32,11 @@ class Track:
albumhash: str
def __init__(self, tags):
+ try:
+ self.trackid = tags["_id"]["$oid"]
+ except KeyError:
+ print(tags)
- self.trackid = tags["_id"]["$oid"]
self.title = tags["title"]
self.artists = tags["artists"].split(", ")
self.albumartist = tags["albumartist"]
@@ -49,6 +52,22 @@ class Track:
self.albumhash = tags["albumhash"]
+@dataclass(slots=True)
+class Artist:
+ """
+ Artist class
+ """
+
+ artistid: str
+ name: str
+ image: str
+
+ def __init__(self, tags):
+ self.artistid = tags["_id"]["$oid"]
+ self.name = tags["name"]
+ self.image = tags["image"]
+
+
@dataclass
class Album:
"""
@@ -78,11 +97,9 @@ class Album:
def get_p_track(ptrack):
for track in api.TRACKS:
- if (
- track.title == ptrack["title"]
- and track.artists == ptrack["artists"]
- and ptrack["album"] == track.album
- ):
+ if (track.title == ptrack["title"]
+ and track.artists == ptrack["artists"]
+ and ptrack["album"] == track.album):
return track
diff --git a/server/poetry.lock b/server/poetry.lock
index e20a8fe5..92ba5c7c 100644
--- a/server/poetry.lock
+++ b/server/poetry.lock
@@ -118,6 +118,14 @@ category = "main"
optional = false
python-versions = ">=3.6"
+[[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.0.3"
@@ -181,6 +189,20 @@ 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"
@@ -262,7 +284,7 @@ watchdog = ["watchdog"]
[metadata]
lock-version = "1.1"
python-versions = "^3.8"
-content-hash = "a2a0355e78fe2881e226dabda21b6d972a98aa4f1d60bf9f7f74957bb8ba6bea"
+content-hash = "143a7a7f2158b3c3abfcfec207443a90f1a3d31a6935dfdf5e5e7452e226ca58"
[metadata.files]
certifi = [
@@ -309,6 +331,88 @@ itsdangerous = [
{file = "itsdangerous-2.0.1-py3-none-any.whl", hash = "sha256:5174094b9637652bdb841a3029700391451bd092ba3db90600dea710ba28e97c"},
{file = "itsdangerous-2.0.1.tar.gz", hash = "sha256:9e724d68fc22902a1435351f84c3fb8623f303fffcc566a4cb952df8c572cff0"},
]
+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.0.3-py3-none-any.whl", hash = "sha256:077ce6014f7b40d03b47d1f1ca4b0fc8328a692bd284016f806ed0eaca390ad8"},
{file = "Jinja2-3.0.3.tar.gz", hash = "sha256:611bb273cd68f3b993fabdc4064fc858c5b47a973cb5aa7999ec1ba405c87cd7"},
@@ -513,6 +617,54 @@ pymongo = [
{file = "pymongo-4.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:65f8a93816dcb2202710839907759aca9eece94d9f13215686f224fcc8966f9e"},
{file = "pymongo-4.0.1.tar.gz", hash = "sha256:13d0624c13a91da71fa0d960205d93b3d98344481be865ee7cc238c972d41d73"},
]
+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"},
diff --git a/server/pyproject.toml b/server/pyproject.toml
index 3a142c54..a719b9a1 100644
--- a/server/pyproject.toml
+++ b/server/pyproject.toml
@@ -18,6 +18,7 @@ Pillow = "^9.0.1"
Flask-Caching = "^1.10.1"
"colorgram.py" = "^1.2.0"
tqdm = "^4.64.0"
+rapidfuzz = "^2.0.11"
[tool.poetry.dev-dependencies]
diff --git a/server/start.sh b/server/start.sh
index b86f0ca5..2bac5a6a 100755
--- a/server/start.sh
+++ b/server/start.sh
@@ -5,8 +5,18 @@
#python manage.py
gpath=$(poetry run which gunicorn)
-cd app
-"$gpath" -b 0.0.0.0:9877 -w 4 --threads=2 "imgserver:app" &
-echo "Booted image server"
-cd ../
-"$gpath" -b 0.0.0.0:9876 -w 1 --threads=4 "manage:create_app()" #--log-level=debug
+while getopts ':s' opt; do
+ case $opt in
+ s)
+ echo "🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴"
+ 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/src/App.vue b/src/App.vue
index a84e001c..8dbf3dc9 100644
--- a/src/App.vue
+++ b/src/App.vue
@@ -2,13 +2,9 @@