diff --git a/.eslintrc.js b/.eslintrc.js index 3391da1..bb9ee2f 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,17 +1,11 @@ module.exports = { root: true, env: { - node: true - }, - 'extends': [ - 'plugin:vue/vue3-essential', - 'eslint:recommended' - ], - parserOptions: { - parser: 'babel-eslint' + es2021: true, }, + extends: ["plugin:vue/vue3-essential", "eslint:recommended"], rules: { - 'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off', - 'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off' - } -} + "no-console": process.env.NODE_ENV === "production" ? "warn" : "off", + "no-debugger": process.env.NODE_ENV === "production" ? "warn" : "off", + }, +}; diff --git a/babel.config.js b/babel.config.js deleted file mode 100644 index e955840..0000000 --- a/babel.config.js +++ /dev/null @@ -1,5 +0,0 @@ -module.exports = { - presets: [ - '@vue/cli-plugin-babel/preset' - ] -} diff --git a/index.html b/index.html new file mode 100644 index 0000000..655bfa9 --- /dev/null +++ b/index.html @@ -0,0 +1,21 @@ + + + + + + + + MusicX + + + +
+ + + + diff --git a/package.json b/package.json index 87c2ae5..ec793d1 100644 --- a/package.json +++ b/package.json @@ -3,16 +3,16 @@ "version": "0.1.0", "private": true, "scripts": { - "serve": "vue-cli-service serve", - "build": "vue-cli-service build", - "lint": "vue-cli-service lint" + "dev": "vite", + "serve": "vite preview", + "build": "vite build", + "lint": "eslint --ext .js,.vue --ignore-path .gitignore --fix src" }, "dependencies": { "animate.css": "^4.1.1", - "core-js": "^3.6.5", "mitt": "^3.0.0", "register-service-worker": "^1.7.1", - "sass": "^1.44.0", + "sass": "^1.49.0", "sass-loader": "^10", "vue": "^3.0.0", "vue-debounce": "^3.0.2", @@ -20,14 +20,10 @@ "webpack": "^5.64.4" }, "devDependencies": { - "@vue/cli-plugin-babel": "~4.5.0", - "@vue/cli-plugin-eslint": "~4.5.0", - "@vue/cli-plugin-pwa": "~4.5.0", - "@vue/cli-plugin-router": "~4.5.0", - "@vue/cli-service": "~4.5.0", + "@vitejs/plugin-vue": "^1.6.1", "@vue/compiler-sfc": "^3.0.0", - "babel-eslint": "^10.1.0", - "eslint": "^6.7.2", - "eslint-plugin-vue": "^7.0.0" + "eslint": "^8.7.0", + "eslint-plugin-vue": "^8.3.0", + "vite": "^2.5.4" } } diff --git a/public/index.html b/public/index.html deleted file mode 100644 index 74be66d..0000000 --- a/public/index.html +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - MusicX - - - -
- - - diff --git a/server/app/api.py b/server/app/api.py index 4e975d3..3ad7e11 100644 --- a/server/app/api.py +++ b/server/app/api.py @@ -11,8 +11,11 @@ all_the_f_music = helpers.getAllSongs() def initialize() -> None: + """ + Runs all the necessary setup functions. + """ helpers.create_config_dir() - helpers.check_for_new_songs() + # helpers.check_for_new_songs() initialize() @@ -38,19 +41,19 @@ def search_by_title(): artists_dicts = [] for track in all_the_f_music: - if query.lower() in track['title'].lower(): + if query.lower() in track.title.lower(): tracks.append(track) - if query.lower() in track['album'].lower(): + if query.lower() in track.album.lower(): albums.append(track) - if query.lower() in str(track['artists']).lower(): + if query.lower() in str(track.artists).lower(): artists.append(track) for song in albums: album_obj = { - "name": song["album"], - "artist": song["album_artist"], + "name": song.album, + "artist": song.album_artist, } if album_obj not in albums_dicts: @@ -58,11 +61,11 @@ def search_by_title(): for album in albums_dicts: for track in albums: - if album['name'] == track['album']: - album['image'] = track['image'] - + if album['name'] == track.album: + album['image'] = track.image + for song in artists: - for artist in song["artists"]: + for artist in song.artists: if query.lower() in artist.lower(): artist_obj = { @@ -80,22 +83,23 @@ def search_by_title(): else: more_tracks = False - if len(artists_dicts) > 5: + if len(artists_dicts) > 8: more_artists = True else: more_artists = False - if len(albums_dicts) > 5: + if len(albums_dicts) > 8: more_albums = True else: more_albums = False return {'data': [ {'tracks': tracks[:5], 'more': more_tracks}, - {'albums': albums_dicts[:5], 'more': more_albums}, - {'artists': artists_dicts[:5], 'more': more_artists} + {'albums': albums_dicts[:8], 'more': more_albums}, + {'artists': artists_dicts[:8], 'more': more_artists} ]} + @bp.route('/populate') def x(): functions.populate() @@ -111,13 +115,13 @@ def get_album_artists(album, artist): tracks = [] for track in all_the_f_music: - if track["album"] == album and track["album_artist"] == artist: + if track.album == album and track.album_artist == artist: tracks.append(track) artists = [] for track in tracks: - for artist in track['artists']: + for artist in track.artists: if artist not in artists: artists.append(artist) @@ -213,35 +217,13 @@ def getFolderTree(folder: str): songs = [] - for x in all_the_f_music: - if x['folder'] == req_dir: - songs.append(x) - - for song in songs: - song['image'] = song['image'].replace('127.0.0.1', '0.0.0.0') + for track in all_the_f_music: + if track.folder == req_dir: + songs.append(track) return {"files": helpers.remove_duplicates(songs), "folders": sorted(folders, key=lambda i: i['name'])} -@bp.route('/qwerty') -def populateArtists(): - all_songs = instances.songs_instance.get_all_songs() - - artists = [] - - for song in all_songs: - for a in song['artists']: - a_obj = { - "name": a, - } - - if a_obj not in artists: - artists.append(a_obj) - - instances.artist_instance.insert_artist(a_obj) - - return {'songs': artists} - @bp.route('/albums') def getAlbums(): @@ -310,8 +292,3 @@ def convert_images_to_webp(): '.jpg', '.webp')), format='webp') return "Done" - - -@bp.route('/test') -def test_http_status_response(): - return "OK", 200 diff --git a/server/app/functions.py b/server/app/functions.py index f785725..f451670 100644 --- a/server/app/functions.py +++ b/server/app/functions.py @@ -6,6 +6,7 @@ import time import os import requests import random +import datetime from mutagen.flac import MutagenError from mutagen.mp3 import MP3 @@ -18,6 +19,7 @@ from io import BytesIO from app import helpers from app import instances from app import api +from app import models def populate(): @@ -53,61 +55,63 @@ def populate_images(): bar = Bar('Processing images', max=len(artists)) for artist in artists: file_path = helpers.app_dir + '/images/artists/' + \ - artist.replace('/', '::') + '.jpg' + artist.replace('/', '::') + '.webp' if not os.path.exists(file_path): - url = 'https://api.deezer.com/search/artist?q={}'.format(artist) - - try: + def try_save_image(): + url = 'https://api.deezer.com/search/artist?q={}'.format(artist) response = requests.get(url) - except requests.ConnectionError: - print('\n sleeping for 5 seconds') + data = response.json() + + try: + img_path = data['data'][0]['picture_medium'] + except: + img_path = None + + if img_path is not None: + # save image as webp + img = Image.open(BytesIO(requests.get(img_path).content)) + img.save(file_path, format="webp") + try: + try_save_image() + except requests.exceptions.ConnectionError: time.sleep(5) - response = requests.get(url) - - data = response.json() - - try: - img_data = data['data'][0]['picture_xl'] - except: - img_data = None - - if img_data is not None: - helpers.save_image(img_data, file_path) + try_save_image() bar.next() bar.finish() -def extract_thumb(path: str) -> str: +def extract_thumb(audio_file_path: str = None) -> str: """ Extracts the thumbnail from an audio file. Returns the path to the thumbnail. """ + album_art = None + def use_defaults() -> str: """ Returns a path to a random image in the defaults directory. """ - path = "http://127.0.0.1:8900/images/defaults/" + \ - str(random.randint(0, 10)) + '.webp' + path = str(random.randint(0, 10)) + '.webp' return path - webp_path = path.split('/')[-1] + '.webp' + webp_path = audio_file_path.split('/')[-1] + '.webp' img_path = os.path.join(helpers.app_dir, "images", "thumbnails", webp_path) if os.path.exists(img_path): - return "http://127.0.0.1:8900/images/thumbnails/" + webp_path + return webp_path - if path.endswith('.flac'): + if audio_file_path.endswith('.flac'): try: - audio = FLAC(path) + audio = FLAC(audio_file_path) album_art = audio.pictures[0].data except: album_art = None - elif path.endswith('.mp3'): + elif audio_file_path.endswith('.mp3'): try: - audio = ID3(path) + audio = ID3(audio_file_path) album_art = audio.getall('APIC')[0].data except: album_art = None @@ -128,9 +132,8 @@ def extract_thumb(path: str) -> str: except: return use_defaults() - final_path = "http://0.0.0.0:8900/images/thumbnails/" + webp_path + return webp_path - return final_path def getTags(full_path: str) -> dict: """ @@ -198,8 +201,23 @@ def getTags(full_path: str) -> dict: except IndexError: genre = "Unknown" + try: + date = audio['date'][0] + except KeyError: + try: + date = audio['TDRC'][0] + except: + date = "Unknown" + except IndexError: + date = "Unknown" + img_path = extract_thumb(full_path) + length = str(datetime.timedelta(seconds=round(audio.info.length))) + + if length[:2] == "0:": + length = length.replace('0:', '') + tags = { "filepath": full_path.replace(helpers.home_dir, ''), "folder": os.path.dirname(full_path).replace(helpers.home_dir, ""), @@ -208,8 +226,9 @@ def getTags(full_path: str) -> dict: "album_artist": album_artist, "album": album, "genre": genre, - "length": round(audio.info.length), - "bitrate": audio.info.bitrate, + "length": length, + "bitrate": round(int(audio.info.bitrate)/1000), + "date": str(date)[:4], "image": img_path, } @@ -220,15 +239,16 @@ def getTags(full_path: str) -> dict: def getAlbumBio(title: str, album_artist: str) -> dict: last_fm_url = 'http://ws.audioscrobbler.com/2.0/?method=album.getinfo&api_key={}&artist={}&album={}&format=json'.format( helpers.last_fm_api_key, album_artist, title) - + try: response = requests.get(last_fm_url) data = response.json() except: return "None" - + try: - bio = data['album']['wiki']['summary'].split(' dict: return "None" return bio + + +def create_track_class(tags): + return models.Track( + tags['_id']["$oid"], + tags['title'], + tags['artists'], + tags['album_artist'], + tags['album'], + tags['filepath'], + tags['folder'], + tags['length'], + tags['date'], + tags['genre'], + tags['bitrate'], + tags['image'] + ) diff --git a/server/app/helpers.py b/server/app/helpers.py index 3881b72..f75017c 100644 --- a/server/app/helpers.py +++ b/server/app/helpers.py @@ -17,6 +17,7 @@ home_dir = os.path.expanduser('~') + '/' app_dir = os.path.join(home_dir, '.musicx') last_fm_api_key = "762db7a44a9e6fb5585661f5f2bdf23a" + def background(f): ''' a threading decorator @@ -29,6 +30,9 @@ def background(f): @background def check_for_new_songs(): + """ + Checks for new songs every 5 minutes. + """ flag = False while flag is False: @@ -68,12 +72,12 @@ def remove_duplicates(array: list) -> list: while song_num < len(array) - 1: for index, song in enumerate(array): - try: + if array[song_num].title == song.title and \ + array[song_num].album == song.album and \ + array[song_num].artists == song.artists and \ + index != song_num: + array.remove(song) - if array[song_num]["title"] == song["title"] and array[song_num]["album"] == song["album"] and array[song_num]["artists"] == song["artists"] and index != song_num: - array.remove(song) - except: - print('whe') song_num += 1 return array @@ -113,27 +117,29 @@ def create_config_dir() -> None: for dir in dirs: path = os.path.join(config_folder, dir) - os.chmod(path, 0o755) + try: os.makedirs(path) except FileExistsError: pass + os.chmod(path, 0o755) def getAllSongs() -> None: """ Gets all songs under the ~/ directory. """ - tracks = instances.songs_instance.get_all_songs() + tracks = [] + + for track in instances.songs_instance.get_all_songs(): + track = functions.create_track_class(track) - for track in tracks: try: - os.chmod(os.path.join(home_dir, track['filepath']), 0o755) + os.chmod(os.path.join(home_dir, track.filepath), 0o755) except FileNotFoundError: - instances.songs_instance.remove_song_by_filepath(track['filepath']) + instances.songs_instance.remove_song_by_filepath(track.filepath) - if track['artists'] is not None: - track['artists'] = track['artists'].split(', ') + tracks.append(track) return tracks diff --git a/server/app/models.py b/server/app/models.py index 543e935..17002de 100644 --- a/server/app/models.py +++ b/server/app/models.py @@ -1,3 +1,4 @@ +from dataclasses import dataclass import pymongo import json from bson import ObjectId, json_util @@ -119,3 +120,25 @@ class AllSongs(Mongo): return True except: return False + + +@dataclass +class Track: + id: str + title: str + artists: str + album_artist: str + album: str + filepath: str + folder: str + length: int + date: int + genre: str + bitrate: int + image: str + + def __post_init__(self): + self.artists = self.artists.split(', ') + self.image = "http://127.0.0.1:8900/images/thumbnails/" + self.image + + diff --git a/src/components/FolderView/SongList.vue b/src/components/FolderView/SongList.vue index d6016fa..a8b2964 100644 --- a/src/components/FolderView/SongList.vue +++ b/src/components/FolderView/SongList.vue @@ -1,6 +1,6 @@ -