From a7a43a97c64561cace0cd1cc459ee30ffa1fd956 Mon Sep 17 00:00:00 2001 From: JVT038 <47184046+JVT038@users.noreply.github.com> Date: Sun, 17 Jul 2022 20:30:41 +0200 Subject: [PATCH 1/4] - Fixed bug with multiple artists - Completely finished the file browser, supporting navigation, creation, and removal of directories - fixed bug with the media player when there's one record present - Updated yt-dlp - Got rid of JSGlue - Almost done implementing Genius as a metadata source for lyrics --- README.md | 18 +- fixartists.py | 40 ++ metatube/database.py | 17 +- metatube/genius.py | 23 + metatube/metadata.py | 74 +- metatube/overview/routes.py | 124 +++- metatube/settings/routes.py | 16 +- metatube/sockets.py | 9 + metatube/sponsorblock.py | 1 + metatube/spotify.py | 2 +- metatube/static/CSS/libraries/dark.css | 25 +- metatube/static/CSS/libraries/tagger.min.css | 13 + metatube/static/JS/libraries/tagger.min.js | 14 + metatube/static/JS/overview.js | 698 +++++++++++++++---- metatube/static/JS/settings.js | 36 +- metatube/templates/base.html | 3 +- metatube/templates/metadataform.html | 24 + metatube/templates/overview.html | 73 +- metatube/templates/settings.html | 19 +- metatube/youtube.py | 2 +- requirements.txt | 3 +- 21 files changed, 1064 insertions(+), 170 deletions(-) create mode 100644 fixartists.py create mode 100644 metatube/genius.py create mode 100644 metatube/static/CSS/libraries/tagger.min.css create mode 100644 metatube/static/JS/libraries/tagger.min.js diff --git a/README.md b/README.md index 86e9e1ff..dcc2dd1f 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@

Status

- 🚧 MetaTube πŸš€ Under construction... 🚧
+ :heavy_check_mark: MetaTube πŸš€ Finished! :heavy_check_mark:


@@ -189,6 +189,13 @@ $ export ENVIRONMENT_VARIABLE = Value Additionally you can create a file called `.flaskenv` and set the environment variables in there. An example is provided in [example.flaskenv](example.flaskenv). You can use that template and rename the file to `.flaskenv`. +## Fix the artist values +So I recently discovered I made a mistake in the process of adding artists to files.
+Some songs have tags multiple artists, and I noticed these tags were misinterpreted by my audio player.
+Basically, the `TPE1` tag contained was like this: `['artist 1; artist 2']`, while it should've been `['artist 1', 'artist 2']`.
+Thanks to [#310](https://github.com/quodlibet/mutagen/issues/310) I discovered this, corrected it in `metadata.py` and wrote a small script in [fixartists.py](fixartists.py) to fix the existing audio files that had the tags in the wrong way.
+Put all the wrong audio files in one directory, run the file and enter the path to the directory containing the incorrect tags, and it should be fixed.
+My apologies for this (annoying) bug. ## :memo: License ## This project is under license from GNUv3. For more details, see the [LICENSE](LICENSE) file.
@@ -204,8 +211,9 @@ Made with :heart: by JVT038< - [X] Add Docker support - [X] Add Docker support for ARM64/v8 devices (such as Raspberry Pi 4) - [X] Add Github action / workflow thing, to automatically create Docker image upon a new commit -- [X] Add support for Spotify -- [X] Add support for Deezer +- [X] Add support for Spotify as a metadata provider +- [X] Add support for Deezer as a metadata provider +- [X] Add support for Genius as a metadata provider - [X] Add support for subpath (such as `localhost:5000/metatube`) - [X] Add a nice progress bar - [X] Add a function to allow users to download the song onto their device @@ -231,7 +239,7 @@ Made with :heart: by JVT038< - [X] Fix error `Synchronous XMLHttpRequest on the main thread is deprecated because of its detrimental effects to the end user’s experience. For more help http://xhr.spec.whatwg.org/` in overview - [X] Make sure the search for downloaded song field works -### Not finished (I probably will never finish this) +### Not finished (I'll probably never finish this) - [ ] Add it to the PyPi library - [ ] Add support for sites other than YouTube @@ -242,6 +250,8 @@ Made with :heart: by JVT038< - [ ] Add support for TheAudioDB - [ ] Add support for YouTube Music - [ ] Add support for Last.fm! +- [ ] Add support for Genius as metadata provider +- [ ] Add support for embedded lyrics (if possible) - [ ] Add translations - [ ] Add in-built file explorer, making manual paths optional - [ ] Add some nice animations diff --git a/fixartists.py b/fixartists.py new file mode 100644 index 00000000..527c5b3b --- /dev/null +++ b/fixartists.py @@ -0,0 +1,40 @@ +################################# +#####How to use this script:##### +#1. python fixartists.py######### +#2. Enter directory in CLI####### +#3. Let it run################### +################################# +from mutagen.easyid3 import EasyID3 +from mutagen.flac import FLAC +from mutagen.oggopus import OggOpus +from mutagen.oggvorbis import OggVorbis +import os +directory = input('Enter relative or absolute path to audio files: ') +files = os.listdir(directory) +for file in files: + extensions = ['MP3', 'OPUS', 'FLAC', 'OGG'] + filepath = os.path.join(directory, file) + extension = filepath.split('.')[len(filepath.split('.')) - 1].upper() + if os.path.isfile(filepath): + if extension.upper() in extensions: + if extension == 'MP3': + audio = EasyID3(filepath) + elif extension == 'OPUS': + audio = OggOpus(filepath) + elif extension == 'FLAC': + audio = FLAC(filepath) + elif extension == 'OGG': + audio = OggVorbis(filepath) + if 'artist' in audio: + old_artists = audio["artist"] + if len(old_artists) == 1 and '; ' in old_artists[0]: + new_artists = old_artists[0].split('; ') + audio["artist"] = new_artists + audio.save() + print(f'Fixed artists of {file}') + else: + print(f'{file} already has the correct artist format, skipping...') + else: + print(f'{file} contains no TPE1 tag, skipping') + else: + print(f'{file} has an unsupported extension, skipping') \ No newline at end of file diff --git a/metatube/database.py b/metatube/database.py index 2c1117ed..9e4aa6dc 100644 --- a/metatube/database.py +++ b/metatube/database.py @@ -9,6 +9,7 @@ class Config(db.Model): hardware_transcoding = db.Column(db.String(16), default="None") metadata_sources = db.Column(db.String(128), default='deezer') spotify_api = db.Column(db.String(128)) + genius_api = db.Column(db.String(128)) auth = db.Column(db.Boolean, server_default=expression.false()) auth_username = db.Column(db.String(128)) auth_password = db.Column(db.String(128)) @@ -34,6 +35,11 @@ def set_spotify(self, spotify): db.session.commit() logger.info('Changed the Spotify API settings') + def set_genius(self, genius): + self.genius_api = genius + db.session.commit() + logger.info('Changed the Genius API settings') + def set_metadata(self, metadata_sources): self.metadata_sources = metadata_sources db.session.commit() @@ -50,6 +56,9 @@ def get_metadata_sources(): def get_spotify(): return Config.query.get(1).spotify_api + def get_genius(): + return Config.query.get(1).genius_api + def get_max(): return Config.query.get(1).amount @@ -177,7 +186,7 @@ def insert(data): row = Database( filepath = data["filepath"], name = data["name"], - artist = data["artist"], + artist = '; '.join(data["artist"]), album = data["album"], date = parser.parse(data["date"]), cover = data["image"], @@ -203,6 +212,12 @@ def update(self, data): logger.info('Updated item %s', data["name"]) data["date"] = data["date"].strftime('%d-%m-%Y') sockets.overview({'msg': 'changed_metadata_db', 'data': data}) + + def updatefilepath(self, filepath): + self.filepath = filepath + db.session.commit() + logger.info('Updated filepath of item %s to %s', self.name, filepath) + sockets.overview({'msg': 'updated_filepath', 'filepath': filepath, 'item': self.id}) def delete(self): db.session.delete(self) diff --git a/metatube/genius.py b/metatube/genius.py new file mode 100644 index 00000000..c34f324e --- /dev/null +++ b/metatube/genius.py @@ -0,0 +1,23 @@ +from lyricsgenius import Genius as geniusobj +from metatube import logger, sockets +class Genius(): + def __init__(self, client_id): + try: + self.genius = geniusobj(client_id) + except TypeError() as e: + logger.error('Genius API failed: %s', str(e)) + + def search(self, data): + search = self.genius.search_songs(data["title"], data["max"]) + sockets.geniussearch(search) + logger.info('Searched Genius for track %s', data["title"]) + + def searchsong(data, token): + genius = Genius(token) + genius.search(data) + + def fetchsong(self, id): + return self.genius.song(id) + + def fetchalbum(self, id): + sockets.foundgeniusalbum(self.genius.album_tracks(id)) \ No newline at end of file diff --git a/metatube/metadata.py b/metatube/metadata.py index a09f8d1e..568e1429 100644 --- a/metatube/metadata.py +++ b/metatube/metadata.py @@ -1,3 +1,5 @@ +import json +from importlib_metadata import metadata from magic import Magic from re import M from mutagen.id3 import ( @@ -32,13 +34,13 @@ def getresponse(data): def getmusicbrainzdata(filename, metadata_user, metadata_source, cover_source): logger.info('Getting Musicbrainz metadata') album = metadata_source["release"]["release-group"]["title"] if len(metadata_user["album"]) < 1 else metadata_user["album"] - artist_list = "" + artist_list = [] for artist in metadata_source["release"]["artist-credit"]: try: - artist_list += artist["artist"]["name"] + "/ " + artist_list.append(artist["artist"]["name"]) except Exception: pass - artist_list = artist_list.strip()[0:len(artist_list.strip()) - 1] if len(metadata_user["artists"]) < 1 else metadata_user["artists"] + artist_list = artist_list if json.loads(metadata_user["artists"]) == [""] else json.loads(metadata_user["artists"]) try: language = metadata_source["release"]["text-representation"]["language"] except Exception: @@ -130,10 +132,13 @@ def getspotifydata(filename, metadata_user, metadata_source): cover_path = metadata_source["album"]["images"][0]["url"] if len(metadata_user["cover"]) < 1 else metadata_user["cover"] title = metadata_source["name"] if len(metadata_user["title"]) < 1 else metadata_user["title"] genres = "" # Spotify API doesn't provide genres with tracks - spotify_artists = "" + spotify_artists = [] for artist in metadata_source["artists"]: - spotify_artists += artist["name"] + "; " - artists = spotify_artists[0:len(spotify_artists) - 2] if len(metadata_user["artists"]) < 1 else metadata_user["artists"] + spotify_artists.append(artist["name"]) + print(spotify_artists) + artists = spotify_artists if json.loads(metadata_user["artists"]) == [""] else json.loads(metadata_user["artists"]) + print(artists) + print(metadata_user["artists"]) if cover_path != default_cover: try: response = requests.get(cover_path) @@ -183,8 +188,8 @@ def getdeezerdata(filename, metadata_user, metadata_source): deezer_artists = "" for contributor in metadata_source["contributors"]: if contributor["type"].lower() == 'artist': - deezer_artists += contributor["name"] + "; " - artists = deezer_artists[0:len(deezer_artists) - 2] if len(metadata_user["artists"]) < 1 else metadata_user["artists"] + deezer_artists.append(contributor["name"]) + artists = deezer_artists if json.loads(metadata_user["artists"]) == [""] else json.loads(metadata_user["artists"]) if cover_path != default_cover: try: response = requests.get(cover_path) @@ -219,6 +224,57 @@ def getdeezerdata(filename, metadata_user, metadata_source): } return data + def getgeniusdata(filename, metadata_user, metadata_source): + logger.info('Getting Genius metadata') + album = metadata_source["album"]["name"] if len(metadata_user["album"] < 1) else metadata_user["album"] + trackid = metadata_source["id"] if len(metadata_user["trackid"] < 1) else metadata_user["trackid"] + albumid = metadata_source["album"]["id"] if len(metadata_user["albumid"] < 1) else metadata_user["albumid"] + release_date = metadata_source["release_date"] if len(metadata_user["album_releasedate"] < 1) else metadata_user["album_releasedate"] + length = 0 + tracknr = metadata_user["album_tracknr"] + total_tracks = 1 + default_cover = os.path.join(Config.BASE_DIR, 'metatube/static/images/empty_cover.png') + cover_path = metadata_source["song_art_image_thumbnail_url"] if len(metadata_user["cover"] < 1) else metadata_user["cover"] + title = metadata_source["title"] if len(metadata_user["title"] < 1) else metadata_user["title"] + geniusartists = metadata_source["primary_artist"]["name"] + "; " + for artist in metadata_source["featured_artists"]: + geniusartists += artist["name"] + "; " + artists = geniusartists[0:len(geniusartists) - 2] if len(metadata_user["artists"] < 1) else metadata_user["artists"] + if cover_path != default_cover: + try: + response = requests.get(cover_path) + image = response.content + magic = Magic(mime=True) + cover_mime_type = magic.from_buffer(image) + except Exception: + sockets.downloadprogress({'status': 'error', 'message': 'Cover URL is invalid!'}) + return False + else: + cover_mime_type = "image/png" + file = open(cover_path, 'rb') + image = file.read() + + data = { + 'filename': filename, + 'album': album, + 'artists': artists, + 'barcode': "", + 'language': "Unknown", + 'track_id': trackid, + 'album_id': albumid, + 'release_date': release_date, + 'tracknr': tracknr, + 'total_tracks': total_tracks, + 'isrc': "", + 'length': length, + 'cover_path': cover_path, + 'cover_mime_type': cover_mime_type, + 'image': image, + 'title': title, + 'genres': "" + } + return data + def onlyuserdata(filename, metadata_user): if metadata_user["cover"] != '': try: @@ -332,7 +388,7 @@ def mergeaudiodata(data): audio = OggOpus(data["filename"]) elif data["extension"] == 'OGG': audio = OggVorbis(data["filename"]) - + audio["album"] = data["album"] audio["artist"] = data["artists"] audio["barcode"] = data["barcode"] diff --git a/metatube/overview/routes.py b/metatube/overview/routes.py index cfc22f40..e18470cc 100644 --- a/metatube/overview/routes.py +++ b/metatube/overview/routes.py @@ -6,6 +6,7 @@ from metatube.metadata import MetaData from metatube.deezer import Deezer from metatube.spotify import spotify_metadata as Spotify +from metatube.genius import Genius from metatube import socketio, sockets from metatube import Config as env from flask import render_template @@ -30,7 +31,8 @@ def index(): records = Database.getrecords() metadata_sources = Config.get_metadata_sources() metadataform = render_template('metadataform.html', metadata_sources=metadata_sources) - return render_template('overview.html', current_page='overview', ffmpeg_path=ffmpeg_path, records=records, metadataview=metadataform) + genius = True if len(Config.get_genius()) != 0 else False + return render_template('overview.html', current_page='overview', ffmpeg_path=ffmpeg_path, records=records, metadataview=metadataform, genius=genius) @socketio.on('searchitem') def searchitem(query): @@ -104,6 +106,9 @@ def searchmetadata(data): socketio.start_background_task(Spotify.searchspotify, data, cred) if 'deezer' in sources: socketio.start_background_task(Deezer.socketsearch, data) + if 'genius' in sources and data["type"] == 'lyrics': + token = Config.get_genius() + socketio.start_background_task(Genius.searchsong, data, token) @socketio.on('ytdl_download') def download(data): @@ -159,6 +164,21 @@ def fetchspotifytrack(input_id): def fetchdeezertrack(input_id): logger.info('Request for Deezer track with id %s', input_id) Deezer.sockets_track(input_id) + +@socketio.on('fetchgeniussong') +def fetchgeniussong(input_id): + logger.info('Request for Genius song with id %s', input_id) + token = Config.get_genius() + genius = Genius(token) + song = genius.fetchsong(input_id) + sockets.foundgeniussong(song) + +@socketio.on('fetchgeniusalbum') +def fetchgeniussong(input_id): + logger.info('Request for Genius album with id %s', input_id) + token = Config.get_genius() + genius = Genius(token) + genius.fetchalbum(input_id) @socketio.on('mergedata') def mergedata(filepath, release_id, metadata, cover, source): @@ -179,6 +199,11 @@ def mergedata(filepath, release_id, metadata, cover, source): elif source == 'Deezer': metadata_source = Deezer.searchid(release_id) data = MetaData.getdeezerdata(filepath, metadata_user, metadata_source) + elif source == 'Genius': + token = Config.get_genius() + genius = Genius(token) + metadata_source = genius.fetchsong(release_id) + data = MetaData.getgeniusdata(filepath, metadata_user, metadata_source) elif source == 'Unavailable': data = MetaData.onlyuserdata(filepath, metadata_user) if data is not False: @@ -318,15 +343,104 @@ def playitem(input): else: sockets.overview({'msg': 'Filepath invalid'}) -@socketio.on('fetchcover') -def fetchcover(id): +@socketio.on('showfilebrowser') +def showfilebrowser(visible, id, target_folder=None): + default = Templates.searchdefault() + if 'parent' in visible and target_folder is not None: + folder = os.path.abspath(os.path.join(target_folder, os.pardir)) + elif target_folder is not None and os.path.isdir(target_folder) and os.path.exists(target_folder): + folder = target_folder + else: + folder = default.output_folder + contents = [x for x in os.listdir(folder) if os.path.isdir(os.path.join(folder, x))] + contents.extend([x for x in os.listdir(folder) if not os.path.isdir(os.path.join(folder, x))]) + files = [] + for file in contents: + path = os.path.join(folder, file) + if os.path.isfile(path): + extension = path.split('.')[len(path.split('.')) - 1].upper() + if extension not in ['AAC', 'FLAC', 'MP3', 'M4A', 'OPUS', 'VORBIS', 'WAV', 'MP4', 'M4A', 'FLV', 'WEBM', 'OGG', 'MKV', 'AVI']: + continue + if Database.checkfile(path) is not None: + continue + if 'files' not in visible: + continue + lastmodified = os.stat(path).st_mtime + filesize = os.path.getsize(path) + pathtype = 'file' if os.path.isfile(path) else 'directory' + item = { + 'filepath': path, + 'filename': file, + 'lastmodified': lastmodified, + 'filesize': filesize, + 'pathtype': pathtype + } + files.append(item) + sockets.overview({'msg': 'showfilebrowser', 'files': files, 'visible': visible, 'directory': folder, 'id': id}) + +@socketio.on('updatefile') +def updatefile(filepath, id): + item = Database.fetchitem(id) + item.updatefilepath(filepath) + +@socketio.on('movefile') +def updatefile(directory, filename, id, overwrite=False): item = Database.fetchitem(id) - sockets.overview({'msg': 'load_cover', 'data': item.cover, 'id': id}) + old_filepath = item.filepath + if os.path.exists(directory): + extension = old_filepath.split('.')[len(old_filepath.split('.')) - 1].lower() + if len(filename.split('.')) > 1 and filename.split('.')[len(filename.split('.')) - 1] == extension: + new_filepath = os.path.join(directory, filename.strip()) + else: + new_filepath = os.path.join(directory, filename.strip() + "." + extension) + if os.path.exists(new_filepath) and overwrite is False: + return 'File already exists' + else: + shutil.move(old_filepath, new_filepath) + item.updatefilepath(new_filepath) + +@socketio.on('createdirectory') +def createdirectory(currentdirectory, directoryname): + if os.path.exists(currentdirectory): + path = os.path.join(currentdirectory, directoryname) + if os.path.exists(path) and os.path.isdir(path): + response = { + 'msg': 'This directory already exists!', + 'status': 500 + } + return response + else: + os.mkdir(path) + response = { + 'msg': f'Created directory {path}', + 'filepath': path, + 'status': 200 + } + logger.info('Created directory %s', path) + return response +@socketio.on('removedirectory') +def removedirectory(directory): + if os.path.exists(directory): + shutil.rmtree(directory) + name = os.path.basename(directory) + response = { + 'msg': 'Removed directory', + 'directory': name, + 'status': 200 + } + logger.info('Removed directory %s', directory) + return response + else: + response = { + 'msg': 'Directory does not exist!', + 'status': 500 + } + return response + @socketio.on('editmetadata') def editmetadata(id): item = Database.fetchitem(id) - extension = item.filepath.split('.')[len(item.filepath.split('.')) - 1].upper() if extension in ['MP3', 'OPUS', 'FLAC', 'OGG']: metadata = MetaData.readaudiometadata(item.filepath) diff --git a/metatube/settings/routes.py b/metatube/settings/routes.py index 37fe8441..4e9d6180 100644 --- a/metatube/settings/routes.py +++ b/metatube/settings/routes.py @@ -15,6 +15,7 @@ def settings(): hw_transcoding = db_config.hardware_transcoding metadata_sources = db_config.metadata_sources.split(';') spotify = db_config.spotify_api.split(';') if db_config.spotify_api is not None else ['', ''] + genius = db_config.genius_api if db_config.genius_api is not None else '' templates = Templates.query.all() return render_template('settings.html', @@ -24,7 +25,8 @@ def settings(): templates=templates, hw_transcoding=hw_transcoding, metadata_sources=metadata_sources, - spotify=spotify) + spotify=spotify, + genius=genius) @socketio.on('updatetemplate') def template(name, output_folder, output_ext, output_name, id, goal, bitrate = 'best', width = 'best', height = 'best', proxy_json = {'status': False,'type': '','address': '','port': '','username': '','password': ''}): @@ -47,7 +49,7 @@ def template(name, output_folder, output_ext, output_name, id, goal, bitrate = ' 'username': proxy['username'], 'password': proxy['password'] } - + print(data["proxy"]["status"]) if len(data["name"]) < 1 or len(data["output_folder"]) < 1 or len(data["ext"]) < 1 or len(goal) < 1 or len(id) < 1 or data["name"] == 'Default' or len(data["output_name"]) < 1: sockets.changetemplate('Enter all fields!') elif data["proxy"]["status"] is True and (len(data["proxy"]["address"]) < 1 or len(data["proxy"]["type"]) < 1 or len(data["proxy"]["port"]) < 1): @@ -141,11 +143,11 @@ def updatesettings(ffmpeg_path, amount, hardware_transcoding, metadata_sources, response += 'Hardware Transcoding setting has succesfully been updated!
' if ';'.join(sorted(metadata_sources)) != ';'.join(sorted(db_config.metadata_sources.split(';'))): - if len(metadata_sources) > 0: + if (len(metadata_sources) == 1 and 'genius' not in metadata_sources) or (len(metadata_sources) > 1): db_config.set_metadata(';'.join(sorted(metadata_sources))) response += 'Metadata setting has succesfully been updated!
' else: - response += 'At least one metadata source must be selected!
' + response += 'At least one metadata source excluding Genius must be selected!
' if 'spotify' in metadata_sources and 'spotifyapi' in extradata: spotifydata = extradata["spotifyapi"]["secret"] + ";" + extradata["spotifyapi"]["id"] @@ -153,4 +155,10 @@ def updatesettings(ffmpeg_path, amount, hardware_transcoding, metadata_sources, db_config.set_spotify(spotifydata) response += 'Spotify API settings have succesfully been updated!
' + if 'genius' in metadata_sources and 'geniusapi' in extradata: + geniusdata = extradata['geniusapi']['token'] + if str(db_config.genius_api) != geniusdata: + db_config.set_genius(geniusdata) + response += 'Genius API settings have succesfully been updated!
' + sockets.downloadsettings(response) \ No newline at end of file diff --git a/metatube/sockets.py b/metatube/sockets.py index ab0b660e..f86979cb 100644 --- a/metatube/sockets.py +++ b/metatube/sockets.py @@ -45,6 +45,15 @@ def youtubesearch(data): def spotifysearch(data): socketio.emit('spotify_response', data) +def geniussearch(data): + socketio.emit('genius_response', data) + +def foundgeniussong(data): + socketio.emit('genius_song', data) + +def foundgeniusalbum(data): + socketio.emit("genius_album", data) + def foundspotifytrack(data): socketio.emit('spotify_track', data) diff --git a/metatube/sponsorblock.py b/metatube/sponsorblock.py index 766be2a1..c6b4bfd1 100644 --- a/metatube/sponsorblock.py +++ b/metatube/sponsorblock.py @@ -2,6 +2,7 @@ from sponsorblock.errors import * from metatube import logger def segments(url): + # return "404" client = sponsorblock.Client() logger.info('Fetching sponsorblock segments for %s', url) try: diff --git a/metatube/spotify.py b/metatube/spotify.py index 63e2b1d9..7caa5c56 100644 --- a/metatube/spotify.py +++ b/metatube/spotify.py @@ -13,7 +13,7 @@ def search(self, data): searchresults = self.spotify.search(f"track:{data['title']}", data["max"]) searchresults["query"] = data["title"] sockets.spotifysearch(searchresults) - logger.info('Searched Spotify for track %s with artist %s', data["title"], data["artist"]) + logger.info('Searched Spotify for track %s', data["title"]) def sockets_track(self, id): sockets.foundspotifytrack(self.spotify.track(id)) diff --git a/metatube/static/CSS/libraries/dark.css b/metatube/static/CSS/libraries/dark.css index 227ed938..5b6ab5dc 100644 --- a/metatube/static/CSS/libraries/dark.css +++ b/metatube/static/CSS/libraries/dark.css @@ -3,6 +3,18 @@ color: #eee; } +[data-theme="dark"] .darkanchor { + color: #8dc6ff; +} + +[data-theme="dark"] .darkanchor:hover { + color: #fff; +} + +[data-theme="dark"] .card-body { + background-color: #352e2e; +} + [data-theme="dark"] .bg-light { background-color: #333 !important; } @@ -26,17 +38,22 @@ [data-theme="dark"] .modal-content, [data-theme="dark"] .dropdown-menu, -[data-theme="dark"] #audioplayer { +[data-theme="dark"] #audioplayer, +[data-theme="dark"] .popover, +[data-theme="dark"] .popover-header { background-color: #333; } -[data-theme="dark"] .navbar-nav > .nav-item > a { - color: #9d9a9a; +[data-theme="dark"] .navbar-nav > .nav-item > a, +[data-theme="dark"] .addfolder:hover, +[data-theme="dark"] .removefolder:hover { + color: #9d9a9a !important; } [data-theme="dark"] .table-hover > tbody > tr:hover, [data-theme="dark"] .aplayer-list > ol > li:hover, -[data-theme="dark"] .aplayer-list-light { +[data-theme="dark"] .aplayer-list-light, +[data-theme="dark"] .selectedrow { background-color: #281f1f !important; } diff --git a/metatube/static/CSS/libraries/tagger.min.css b/metatube/static/CSS/libraries/tagger.min.css new file mode 100644 index 00000000..8535d267 --- /dev/null +++ b/metatube/static/CSS/libraries/tagger.min.css @@ -0,0 +1,13 @@ +/**@license + * _____ + * |_ _|___ ___ ___ ___ ___ + * | | | .'| . | . | -_| _| + * |_| |__,|_ |_ |___|_| + * |___|___| version 0.4.1 + * + * Tagger - Zero dependency, Vanilla JavaScript Tag Editor + * + * Copyright (c) 2018-2021 Jakub T. Jankiewicz + * Released under the MIT license + */ +.tagger{border:1px solid #909497}.tagger input[type="hidden"]{display:none}.tagger > ul{display:flex;width:100%;align-items:center;padding:4px 0;justify-content:space-between;box-sizing:border-box;height:auto}.tagger ul{margin:0;list-style:none}.tagger > ul > li{margin:0.4rem 0;padding-left:10px}.tagger > ul > li:not(.tagger-new) a,.tagger > ul > li:not(.tagger-new) a:visited,.tagger-new ul a,.tagger-new ul a:visited{color:black}.tagger .tagger-new ul,.tagger > ul > li:not(.tagger-new) > a,.tagger li:not(.tagger-new) > span{padding:4px 4px 4px 8px;background:#B1C3D7;border:1px solid #4181ed;border-radius:3px}.tagger li a.close{padding:4px;margin-left:4px;float:none;filter: alpha(opacity=100);opacity:1;font-size:16px;line-height:16px}.tagger li a.close:hover{color:white}.tagger li:not(.tagger-new) a{text-decoration:none}.tagger .tagger-new input{border:none;outline:none;box-shadow:none;width:100%;padding-left:0;background:transparent}.tagger .tagger-new{flex-grow:1;position:relative}.tagger .tagger-new ul{padding:5px}.tagger .tagger-completion{position:absolute;z-index:100}.tagger.wrap > ul{flex-wrap:wrap;justify-content:start} \ No newline at end of file diff --git a/metatube/static/JS/libraries/tagger.min.js b/metatube/static/JS/libraries/tagger.min.js new file mode 100644 index 00000000..784ea33f --- /dev/null +++ b/metatube/static/JS/libraries/tagger.min.js @@ -0,0 +1,14 @@ +/**@license + * _____ + * |_ _|___ ___ ___ ___ ___ + * | | | .'| . | . | -_| _| + * |_| |__,|_ |_ |___|_| + * |___|___| version 0.4.2 + * + * Tagger - Zero dependency, Vanilla JavaScript Tag Editor + * + * Copyright (c) 2018-2021 Jakub T. Jankiewicz + * Released under the MIT license + */ +/* global define, module, global */ +(function(root,factory,undefined){if(typeof define==='function'&&define.amd){define([],factory)}else if(typeof module==='object'&&module.exports){module.exports=factory()}else{root.tagger=factory()}})(typeof window!=='undefined'?window:global,function(undefined){var get_text=(function(){var div=document.createElement('div');var text=('innerText'in div)?'innerText':'textContent';return function(element){return element[text]}})();function tagger(input,options){if(input.length){return Array.from(input).map(function(input){return new tagger(input,options)})}if(!(this instanceof tagger)){return new tagger(input,options)}var settings=merge({},tagger.defaults,options);this.init(input,settings)}function merge(){if(arguments.length<2){return arguments[0]}var target=arguments[0];[].slice.call(arguments).reduce(function(acc,obj){if(is_object(obj)){Object.keys(obj).forEach(function(key){if(is_object(obj[key])){if(is_object(acc[key])){acc[key]=merge({},acc[key],obj[key]);return}}acc[key]=obj[key]})}return acc});return target}function is_object(arg){if(typeof arg!=='object'||arg===null){return false}return Object.prototype.toString.call(arg)==='[object Object]'}function create(tag,attrs,children){tag=document.createElement(tag);Object.keys(attrs).forEach(function(name){if(name==='style'){Object.keys(attrs.style).forEach(function(name){tag.style[name]=attrs.style[name]})}else{tag.setAttribute(name,attrs[name])}});if(children!==undefined){children.forEach(function(child){var node;if(typeof child==='string'){node=document.createTextNode(child)}else{node=create.apply(null,child)}tag.appendChild(node)})}return tag}function escape_regex(str){var special=/([-\\^$[\]()+{}?*.|])/g;return str.replace(special,'\\$1')}var id=0;tagger.defaults={allow_duplicates:false,allow_spaces:true,completion:{list:[],delay:400,min_length:2},tag_limit:-1,add_on_blur:false,link:function(name){return '/tag/'+name}};tagger.fn=tagger.prototype={init:function(input,settings){this._id= ++id;var self=this;this._settings=settings||{};this._ul=document.createElement('ul');this._input=input;var wrapper=document.createElement('div');if(settings.wrap){wrapper.className='tagger wrap'}else{wrapper.className='tagger'}this._input.setAttribute('hidden','hidden');this._input.setAttribute('type','hidden');var li=document.createElement('li');li.className='tagger-new';this._new_input_tag=document.createElement('input');this.tags_from_input();li.appendChild(this._new_input_tag);this._completion=document.createElement('div');this._completion.className='tagger-completion';this._ul.appendChild(li);input.parentNode.replaceChild(wrapper,input);wrapper.appendChild(input);wrapper.appendChild(this._ul);li.appendChild(this._completion);this._add_events();if(this._settings.completion.list instanceof Array){this._build_completion(this._settings.completion.list)}},_add_events:function(){var self=this;this._ul.addEventListener('click',function(event){if(event.target.className.match(/close/)){self._remove_tag(event.target);event.preventDefault()}});if(this._settings.add_on_blur){this._new_input_tag.addEventListener('blur',function(event){if(self.add_tag(self._new_input_tag.value.trim())){self._new_input_tag.value=''}})}this._new_input_tag.addEventListener('keydown',function(event){if(event.keyCode===13||event.keyCode===188||(event.keyCode===32&&!self._settings.allow_spaces)){if(self.add_tag(self._new_input_tag.value.trim())){self._new_input_tag.value=''}event.preventDefault()}else if(event.keyCode===8&&!self._new_input_tag.value){if(self._tags.length>0){var li=self._ul.querySelector('li:nth-last-child(2)');self._ul.removeChild(li);self._tags.pop();self._input.value=self._tags.join(',')}event.preventDefault()}else if(event.keyCode===32&&(event.ctrlKey||event.metaKey)){if(typeof self._settings.completion.list==='function'){self.complete(self._new_input_tag.value)}self._toggle_completion(true);event.preventDefault()}else if(self._tag_limit()&&event.keyCode!==9){event.preventDefault()}});this._new_input_tag.addEventListener('input',function(event){var value=self._new_input_tag.value;if(self._tag_selected(value)){if(self.add_tag(value)){self._toggle_completion(false);self._new_input_tag.value=''}}else{var min=self._settings.completion.min_length;if(typeof self._settings.completion.list==='function'&&value.length>=min){self.complete(value)}self._toggle_completion(value.length>=min)}});this._completion.addEventListener('click',function(event){if(event.target.tagName.toLowerCase()==='a'){self.add_tag(get_text(event.target));self._new_input_tag.value='';self._completion.innerHTML=''}})},_tag_selected:function(tag){if(this._last_completion){if(this._last_completion.includes(tag)){var re=new RegExp('^'+escape_regex(tag));return this._last_completion.filter(function(test_tag){return re.test(test_tag)}).length===1}}return false},_toggle_completion:function(toggle){if(toggle){this._new_input_tag.setAttribute('list','tagger-completion-'+this._id)}else{this._new_input_tag.removeAttribute('list')}},_build_completion:function(list){this._completion.innerHTML='';this._last_completion=list;if(list.length){var id='tagger-completion-'+this._id;if(!this._settings.allow_duplicates){list=list.filter(x=>!this._tags.includes(x))}var datalist=create('datalist',{id:id},list.map(function(tag){return['option',{},[tag]]}));this._completion.appendChild(datalist)}},complete:function(value){if(this._settings.completion){var list=this._settings.completion.list;if(typeof list==='function'){var ret=list(value);if(ret&&typeof ret.then==='function'){ret.then(this._build_completion.bind(this))}else if(ret instanceof Array){this._build_completion(ret)}}else{this._build_completion(list)}}},tags_from_input:function(){this._tags=this._input.value.split(/\s*,\s*/).filter(Boolean);this._tags.forEach(this._new_tag.bind(this))},_new_tag:function(name){var close=['a',{href:'#','class':'close'},['\u00D7']];var label=['span',{'class':'label'},[name]];var href=this._settings.link(name);var li;if(href===false){li=create('li',{},[['span',{},[label,close]]])}else{var a_atts={href:href,target:'_black'};li=create('li',{},[['a',a_atts,[label,close]]])}this._ul.insertBefore(li,this._new_input_tag.parentNode)},_tag_limit:function(){return this._settings.tag_limit>0&&this._tags.length>=this._settings.tag_limit},add_tag:function(name){if(!this._settings.allow_duplicates&&this._tags.indexOf(name)!==-1){return false}if(this._tag_limit()){return false}if(this.is_empty(name)){return false}this._new_tag(name);this._tags.push(name);this._input.value=this._tags.join(',');return true},is_empty:function(value){switch(value){case '':case '""':case "''":case '``':case undefined:case null:return true;default:return false}},remove_tag:function(name,remove_dom=true){this._tags=this._tags.filter(function(tag){return name!==tag});this._input.value=this._tags.join(',');if(remove_dom){var tags=Array.from(this._ul.querySelectorAll('.label'));var re=new RegExp('^\s*'+escape_regex(name)+'\s*$');var span=tags.find(function(node){return node.innerText.match(re)});if(!span){return false}var li=span.closest('li');this._ul.removeChild(li);return true}},_remove_tag:function(close){var li=close.closest('li');var name=li.querySelector('.label').innerText;this._ul.removeChild(li);this.remove_tag(name,false)}};return tagger}); \ No newline at end of file diff --git a/metatube/static/JS/overview.js b/metatube/static/JS/overview.js index 1f6aee5c..9095dc2a 100644 --- a/metatube/static/JS/overview.js +++ b/metatube/static/JS/overview.js @@ -7,7 +7,7 @@ $(document).ready(function() { loop: 'none' }); $("#metadataview").find('input').attr('autocomplete', 'off'); - $("#searchitem").val(''); + $("#searchitem, #filename").val(''); $(".selectitem, #selectall").prop('checked', false); function outputtemplate() { if($("#downloadmodal").css('display') != 'none') { @@ -17,6 +17,10 @@ $(document).ready(function() { socket.emit('ytdl_template', {'template': val, 'url': url, 'info_dict': info_dict}); } } + function spinner(msg, location) { + let spinner = '
'+msg+'
'; + $(location).prepend(spinner); + } // If the user presses Enter or submits the form in some other way, it'll trigger the 'find' button function insertYTcol(response, form) { let downloadform = document.createElement('div'); @@ -94,7 +98,6 @@ $(document).ready(function() { let inputgroup = document.createElement('div'); let checkbox = document.createElement('input'); let list = document.createElement('li'); - let audiocol = document.createElement('div'); ul.classList.add('list-unstyled'); img.classList.add('align-self-center', 'mr-3', 'img-fluid'); @@ -104,9 +107,11 @@ $(document).ready(function() { headeranchor.href = data["url"]; headeranchor.target = '_blank'; headeranchor.innerText = data["title"]; + headeranchor.className = 'darkanchor'; img.src = data["cover"]; img.target = '_blank'; + img.style = "width: 300px; height: 300px;"; paragraph.innerHTML = data["artists"]+'
Type: '+data["type"]+'
Date: '+data["date"]+'
Language: '+data["language"] + "
Source: " + data["source"] + ""; @@ -134,14 +139,26 @@ $(document).ready(function() { inputgroup.appendChild(checkbox); ul.appendChild(list); - audiocol.id = 'audiocol'; - audiocol.setAttribute('style', 'width: 100%;'); - - $("#nextbtn").addClass('d-none'); - $("#editmetadata, #downloadbtn, #resetviewbtn").removeClass('d-none'); - $("#defaultview").append(audiocol); + $("#editmetadata, #downloadbtn, #resetviewbtn, #geniusbtn").removeClass('d-none'); $(".spinner-border").remove(); - $("#audiocol").append(ul); + $("#nextbtn").addClass('d-none'); + if(data["source"] == 'Genius') { + if(!$("#geniuscol").length) { + let geniuscol = document.createElement('div'); + geniuscol.setAttribute('style', 'width: 100%;'); + geniuscol.id = 'geniuscol' + $("#defaultview").append(geniuscol); + } + $("#geniuscol").append(ul); + } else { + if(!$("#audiocol").length) { + let audiocol = document.createElement('div'); + audiocol.setAttribute('style', 'width: 100%;'); + audiocol.id = 'audiocol' + $("#defaultview").append(audiocol); + } + $("#audiocol").append(ul); + } } function insertmusicbrainzdata(mbp_data) { @@ -161,7 +178,7 @@ $(document).ready(function() { } $.each(mbp_data["artist-credit"], function(key_artist, value_artist) { if(typeof(value_artist) == 'object') { - let a = '
'+value_artist.name+'
'; + let a = ''+value_artist.name+'
'; artists+=a; } }); @@ -171,7 +188,7 @@ $(document).ready(function() { if("cover" in mbp_data && mbp_data.cover != "None" && mbp_data.cover != 'error') { mbp_image = mbp_data.cover.images[0].thumbnails.small.replace(/^http:/, 'https:'); } else { - mbp_image = Flask.url_for('static', {"filename": "images/empty_cover.png"}); + mbp_image = "/static/images/empty_cover.png"; } let data = { 'url': mbp_url, @@ -198,7 +215,7 @@ $(document).ready(function() { let cover = ""; for(let i = 0; i < Object.keys(spotifydata["artists"]).length; i++) { if(typeof(spotifydata["artists"][i]) == 'object') { - let link = ""+spotifydata["artists"][i]["name"]+"
"; + let link = ""+spotifydata["artists"][i]["name"]+"
"; artists += link; } } @@ -223,7 +240,7 @@ $(document).ready(function() { function insertdeezerdata(deezerdata) { let title = deezerdata["title"]; - let artists = "Artist: "+deezerdata["artist"]["name"]+""; + let artists = "Artist: "+deezerdata["artist"]["name"]+""; let type = deezerdata["album"]["type"]; let url = deezerdata["link"]; let date = "Unknown"; @@ -244,6 +261,33 @@ $(document).ready(function() { createaudiocol(data); } + function insertgeniusdata(geniusdata) { + let title = geniusdata["title"]; + let artists = geniusdata["featured_artists"].length > 0 ? "Artists: " : "Artist: "; + let type = "Song"; + let url = geniusdata["url"]; + let date = geniusdata["release_date_components"] != null ? geniusdata["release_date_components"]["day"] + geniusdata["release_date_components"]["month"] + geniusdata["release_date_components"]["year"] : "Unknown" + let language = "Unknown"; + let cover = geniusdata["header_image_thumbnail_url"]; + let id = geniusdata["id"]; + artists += ""+geniusdata["primary_artist"]["name"]+"
"; + for(let i = 0; i < geniusdata["featured_artists"].length; i++) { + artists += ""+geniusdata["featured_artists"][i]["name"]+"
"; + } + let data = { + 'url': url, + 'title': title, + 'artists': artists.slice(0, artists.length - 7), + 'type': type, + 'date': date, + 'language': language, + 'source': 'Genius', + 'cover': cover, + 'id': id + } + createaudiocol(data); + } + function addperson() { let addbutton = $(".addperson"); let id = addbutton.parents('.personrow').siblings('.personrow').length > 0 ? parseInt(addbutton.parents('.personrow').siblings('.personrow:last').attr('id').slice(addbutton.parents('.personrow').attr('id').length - 1)) + 1 : parseInt(addbutton.parents('.personrow').attr('id').slice(addbutton.parents('.personrow').attr('id').length - 1)) + 1; @@ -292,6 +336,51 @@ $(document).ready(function() { return $('.personrow:last'); } + function addsegment(id) { + let row = document.createElement('div'); + let startcol = document.createElement('div'); + let endcol = document.createElement('div'); + let startinput = document.createElement('input'); + let endinput = document.createElement('input'); + let input_group = document.createElement('div'); + let input_group_append = document.createElement('div'); + let removebtn = document.createElement('button'); + let removeicon = document.createElement('i'); + + row.classList.add('form-row', 'timestamp_row'); + row.id = 'row_'+id; + + startcol.classList.add('col'); + endcol.classList.add('col'); + + input_group.classList.add('input-group'); + input_group_append.classList.add('input-group-append'); + + removebtn.classList.add('btn', 'btn-danger', 'bg-danger', 'input-group-text', 'removesegment'); + removeicon.classList.add('bi', 'bi-dash'); + removeicon.setAttribute('style', 'color: white'); + + startinput.classList.add('form-control', 'timestamp_input'); + startinput.id = 'segmentstart_'+id; + startinput.type = 'text'; + + endinput.classList.add('form-control', 'timestamp_input'); + endinput.id = 'segmentend_'+id; + endinput.type = 'text'; + + removebtn.appendChild(removeicon); + input_group_append.appendChild(removebtn); + input_group.appendChild(endinput); + input_group.appendChild(input_group_append); + + startcol.appendChild(startinput); + endcol.appendChild(input_group); + row.appendChild(startcol); + row.appendChild(endcol); + + $(".timestamp_row:last").after(row); + } + function additem(data) { function addLeadingZeros(n) { if (n <= 9) { @@ -315,29 +404,17 @@ $(document).ready(function() { let dropdown = document.createElement('div'); let dropdownbtn = document.createElement('button'); let dropdownmenu = document.createElement('div'); - let editfileanchor = document.createElement('a'); - let editmetadataanchor = document.createElement('a'); - let downloadanchor = document.createElement('a'); - let playanchor = document.createElement('a'); - let viewanchor = document.createElement('a'); - let deleteanchor = document.createElement('a'); let cover = document.createElement('img'); let covercol = document.createElement('div'); let namecol = document.createElement('div'); let namespan = document.createElement('span'); let namerow = document.createElement('div'); - td_artist.innerText = itemdata["artist"].replace('/', ';'); + td_artist.innerText = itemdata["artist"].join('; '); td_album.innerText = itemdata["album"]; td_date.innerText = date; td_ext.innerText = itemdata["filepath"].split('.')[itemdata["filepath"].split('.').length - 1].toUpperCase(); dropdownbtn.innerText = 'Select action'; - editfileanchor.innerText = 'Change file data'; - editmetadataanchor.innerText = ['MP3', 'OPUS', 'FLAC', 'OGG', 'MP4', 'M4A', 'WAV'].indexOf(td_ext.innerText) > -1 ? 'Change metadata' : 'Item has been moved or deleted or metadata is not supported'; - downloadanchor.innerText = 'Download item'; - playanchor.innerText = 'Play item'; - viewanchor.innerText = 'View YouTube video'; - deleteanchor.innerText = 'Delete item'; namespan.innerText = itemdata["name"]; namerow.classList.add('row', 'd-flex', 'justify-content-center'); @@ -370,26 +447,7 @@ $(document).ready(function() { dropdown.classList.add('dropdown'); dropdownbtn.classList.add('btn', 'btn-primary', 'dropdown-toggle'); dropdownbtn.setAttribute('data-toggle', 'dropdown'); - dropdownmenu.classList.add('dropdown-menu'); - - editfileanchor.href = "javascript:void(0)"; - editfileanchor.classList.add('dropdown-item', 'editfilebtn'); - - editmetadataanchor.href = "javascript:void(0)"; - editmetadataanchor.classList.add('dropdown-item', 'editmetadatabtn'); - - downloadanchor.href = "javascript:void(0)"; - downloadanchor.classList.add('dropdown-item', 'downloaditembtn'); - - playanchor.href = "javascript:void(0)"; - playanchor.classList.add('dropdown-item', 'playitembtn'); - - viewanchor.href = "https://youtu.be/" + itemdata["ytid"]; - viewanchor.classList.add('dropdown-item'); - viewanchor.setAttribute('target', '_blank'); - - deleteanchor.href = "javascript:void(0)"; - deleteanchor.classList.add('dropdown-item', 'deleteitembtn'); + dropdownmenu.classList.add('dropdown-menu'); tr.id = itemdata["id"]; @@ -399,7 +457,6 @@ $(document).ready(function() { form_check.appendChild(checkbox); - dropdownmenu.append(editfileanchor, editmetadataanchor, downloadanchor, playanchor, viewanchor, deleteanchor); dropdown.append(dropdownbtn, dropdownmenu); input_group.append(dropdown, form_check); td_actions.appendChild(input_group); @@ -412,7 +469,7 @@ $(document).ready(function() { tr.append(td_name, td_artist, td_album, td_date, td_ext, td_actions); $("#emptyrow").remove(); $("#recordstable").children("tbody").append(tr); - + createdropdownmenu(itemdata["id"], itemdata["ytid"]); } function downloadURI(uri, name) { @@ -442,11 +499,13 @@ $(document).ready(function() { header.href = data.link; header.innerText = data.title; header.target = '_blank'; + header.classList.add('youtubelink', 'darkanchor'); img.src = data.thumbnails[0].url; channel.href = data.channel.link; channel.innerText = data.channel.name; channel.target = '_blank'; + channel.className = 'darkanchor' ul.setAttribute('style', 'cursor: pointer'); @@ -469,6 +528,176 @@ $(document).ready(function() { $("#defaultview").append(ul); } + function insertfilebrowseritem(item) { + // Thanks to https://stackoverflow.com/a/18650828 + function formatBytes(bytes, decimals = 2) { + if (bytes === 0) return '0 Bytes'; + + const k = 1024; + const dm = decimals < 0 ? 0 : decimals; + const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']; + + const i = Math.floor(Math.log(bytes) / Math.log(k)); + + return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i]; + } + + let tr = document.createElement('tr'); + let td_filename = document.createElement('td'); + let td_lastmodified = document.createElement('td'); + let td_filesize = document.createElement('td'); + let span = document.createElement('span'); + let itemicon = document.createElement('i'); + let removeicon = document.createElement('i'); + let removeanchor = document.createElement('a'); + let nbsp = document.createTextNode('\u00A0'); + + let extensions = item["filename"].split('.')[item["filename"].split('.').length - 1].toUpperCase(); + let video_extensions = ['MP4', 'M4A', 'FLV', 'WEBM', 'OGG', 'MKV', 'AVI']; + let iconclass = 'bi'; + let timestamp = new Date(item["lastmodified"] * 1000); + + span.innerText = item['filename']; + td_lastmodified.innerText = timestamp.toLocaleString(); + td_filesize.innerText = formatBytes(item["filesize"]); + + td_filename.classList.add('text-dark'); + td_lastmodified.classList.add('text-dark'); + td_filesize.classList.add('text-dark'); + + td_filename.style.width = '40%'; + td_lastmodified.style.width = '30%'; + td_filesize.style.width = '30%'; + + if(item["pathtype"] == 'directory') { + let confirm = document.createElement('button'); + let cancel = document.createElement('button'); + let popovergroup = document.createElement('div'); + + confirm.classList.add('btn', 'btn-danger', 'confirmremoval'); + cancel.classList.add('btn', 'btn-success', 'cancelremoval'); + popovergroup.classList.add('btn-group'); + + confirm.type = 'button'; + cancel.type = 'button'; + confirm.innerText = 'Confirm'; + cancel.innerText = 'Cancel'; + + popovergroup.setAttribute('role', 'group'); + popovergroup.setAttribute('aria-label', 'Button group'); + popovergroup.setAttribute('filepath', item["filepath"]); + popovergroup.append(cancel, confirm); + + removeanchor.href = 'javascript:void(0)'; + removeanchor.classList.add('text-dark', 'removefolder'); + removeanchor.style.textDecoration = 'None'; + removeanchor.style.float = 'right'; + + let popoveroptions = { + 'html': true, + 'placement': 'auto', + 'title': 'Delete directory?', + 'trigger': 'focus', + 'content': popovergroup, + }; + $(removeanchor).popover(popoveroptions); + + removeicon.classList.add('bi', 'bi-folder-minus') + removeanchor.append(removeicon); + td_lastmodified.append(removeanchor); + iconclass += ' bi-folder-fill'; + } else { + if(video_extensions.indexOf(extensions) > -1) { + iconclass += ' bi-camera-video-fill'; + } else { + iconclass += ' bi-file-music-fill'; + } + } + + itemicon.className = iconclass; + + if('createnewfolder' in item) { + let input = document.createElement('input'); + let button = document.createElement('button'); + let check = document.createElement('i'); + let form = document.createElement('span'); + + input.classList.add('form-control', 'directory_input', 'w-75'); + input.setAttribute('placeholder', 'Enter directory name'); + form.classList.add('form-inline'); + form.append(itemicon, nbsp, input); + td_filename.append(form); + + button.classList.add('btn', 'btn-success', 'createdirectorybtn', 'ml-2'); + button.type = 'button'; + button.setAttribute('data-toggle', 'tooltip'); + button.setAttribute('data-placement', 'right'); + button.title = 'Create directory'; + check.classList.add('bi', 'bi-check'); + button.append(check); + + td_lastmodified.append(button); + } else { + td_filename.append(itemicon, nbsp, span); + } + + tr.classList.add('filebrowserrow', item["pathtype"]); + tr.style.cursor = 'pointer'; + tr.setAttribute('filepath', item["filepath"]); + tr.append(td_filename, td_filesize, td_lastmodified); + if($("#filebrowserup").parent().children().length == 1 || !('createnewfolder' in item)) { + $("#filebrowserup").parent().children(':last-child').after(tr); + } + else { + if($(".file").length > 0) { + $('.file:first').before(tr); + } else { + $('.directory:last').after(tr); + } + } + } + + function createdropdownmenu(rowid, youtube_id=null) { + let editfilebtn = document.createElement('a'); + let editmetadatabtn = document.createElement('a'); + let downloaditembtn = document.createElement('a'); + let playitembtn = document.createElement('a'); + let moveitembtn = document.createElement('a'); + let youtubebtn = document.createElement('a'); + let deletebtn = document.createElement('a'); + + let elements = [editfilebtn, editmetadatabtn, downloaditembtn, playitembtn, moveitembtn, youtubebtn, deletebtn]; + let metadata_extensions = ['MP3', 'OPUS', 'FLAC', 'OGG', 'MP4', 'M4A']; + let extension = $("tr#" + rowid).children('.td_ext').text(); + + elements.forEach((button) => { + button.href = 'javascript:void(0)'; + button.className = 'dropdown-item'; + }); + + editfilebtn.innerText = 'Change filedata'; + editmetadatabtn.innerText = metadata_extensions.indexOf(extension) > -1 ? 'Change metadata' : 'Metadata is not supported'; + downloaditembtn.innerText = 'Download item'; + playitembtn.innerText = 'Play item'; + moveitembtn.innerText = 'Move item\'s file location'; + youtubebtn.innerText = 'View YouTube video'; + deletebtn.innerText = 'Delete item'; + + editfilebtn.classList.add('d-none', 'd-md-block', 'editfilebtn'); + editmetadatabtn.classList.add('d-none', 'd-md-block', 'editmetadatabtn'); + moveitembtn.classList.add('d-none', 'd-md-block', 'moveitembtn'); + downloaditembtn.classList.add('downloaditembtn'); + playitembtn.classList.add('playitembtn'); + deletebtn.classList.add('deleteitembtn'); + + if(youtube_id != null) { + youtubebtn.href = youtube_id; + $("tr#" + rowid).find('.dropdown-menu').append(youtubebtn, deletebtn); + } + $("tr#" + rowid).find('.dropdown-menu').children(':first-child').before(editfilebtn, editmetadatabtn, downloaditembtn, playitembtn, moveitembtn); + $("tr#" + rowid).find('.finditembtn').remove(); + } + $(window).resize(function() { if ($(window).width() < 700) { $(".youtuberesult").children('li').removeClass('media'); @@ -532,50 +761,7 @@ $(document).ready(function() { }); $(document).on('click', "#addsegment", function() { let id = $(this).parents('.form-row').siblings('.timestamp_row').length > 0 ? parseInt($(this).parents('.form-row').siblings('.timestamp_row:last').attr('id').slice(4)) + 1 : parseInt($(this).parents('.form-row').attr('id').slice(4)) + 1; - - let row = document.createElement('div'); - let startcol = document.createElement('div'); - let endcol = document.createElement('div'); - let startinput = document.createElement('input'); - let endinput = document.createElement('input'); - let input_group = document.createElement('div'); - let input_group_append = document.createElement('div'); - let removebtn = document.createElement('button'); - let removeicon = document.createElement('i'); - - row.classList.add('form-row', 'timestamp_row'); - row.id = 'row_'+id; - - startcol.classList.add('col'); - endcol.classList.add('col'); - - input_group.classList.add('input-group'); - input_group_append.classList.add('input-group-append'); - - removebtn.classList.add('btn', 'btn-danger', 'bg-danger', 'input-group-text', 'removesegment'); - removeicon.classList.add('bi', 'bi-dash'); - removeicon.setAttribute('style', 'color: white'); - - startinput.classList.add('form-control', 'timestamp_input'); - startinput.id = 'segmentstart_'+id; - startinput.type = 'text'; - - endinput.classList.add('form-control', 'timestamp_input'); - endinput.id = 'segmentend_'+id; - endinput.type = 'text'; - - removebtn.appendChild(removeicon); - input_group_append.appendChild(removebtn); - input_group.appendChild(endinput); - input_group.appendChild(input_group_append); - - startcol.appendChild(startinput); - endcol.appendChild(input_group); - row.appendChild(startcol); - row.appendChild(endcol); - - $(".timestamp_row:last").after(row); - + addsegment(id); }); $(document).on('click', '#segments_check', function() { @@ -617,15 +803,21 @@ $(document).ready(function() { $(this).parent().siblings().addClass('d-none'); } }); + + $(document).on('click', '#metadataviewbtn', function() { + $("#geniuscol, #audiocol, #metadataviewbtn, #geniusbtn").toggleClass('d-none'); + }) $(document).on('click', "#editmetadata", function() { - $("#audiocol, #metadataview, #queryform, #downloadbtn, #resetviewbtn").toggleClass('d-none'); + $("#audiocol, #geniuscol, #queryform, #geniusbtn, #downloadbtn, #resetviewbtn, #metadataviewbtn").addClass('d-none'); + $("#metadataview").removeClass('d-none') $(this).attr('id', 'savemetadata'); $(this).text('Save metadata') }); $(document).on('click', '#savemetadata', function() { - $("#audiocol, #metadataview, #queryform, #downloadbtn, #resetviewbtn").toggleClass('d-none'); + $("#audiocol, #geniuscol, #queryform, #geniusbtn, #downloadbtn, #resetviewbtn, #metadataviewbtn").removeClass('d-none'); + $("#metadataview").addClass('d-none') if($("#audiocol").length < 1) { $("#searchmetadataview").removeClass('d-none'); } @@ -679,12 +871,24 @@ $(document).ready(function() { $(document).on('click', '.downloaditembtn', function() { let id = $(this).parents('tr').attr('id'); - socket.emit('downloaditem', id) + socket.emit('downloaditem', id); }); $(document).on('click', '.playitembtn', function() { let id = $(this).parents('tr').attr('id'); - socket.emit('playitem', id) + socket.emit('playitem', id); + }); + + $(document).on('click', '.finditembtn', function() { + let id = $(this).parents('tr').attr('id'); + let visible = ['files', 'directories']; + socket.emit('showfilebrowser', visible, id); + }); + + $(document).on('click', '.moveitembtn', function() { + let id = $(this).parents('tr').attr('id'); + let visible = ['directories']; + socket.emit('showfilebrowser', visible, id); }); $(document).on('click', "#fetchmbpreleasebtn", function(){ @@ -720,7 +924,25 @@ $(document).ready(function() { if(track_id.length > 0) { socket.emit('fetchdeezertrack', track_id) } else { - $("p:contains('* All input fields with an *, are optional')").text('

Enter a Spotify track ID!

') + $("p:contains('* All input fields with an *, are optional')").text('

Enter a Deezer track ID!

') + } + }); + + $(document).on('click', '#fetchgeniussong', function() { + let song_id = $(this).parent().siblings('input').val(); + if(song_id.length > 0) { + socket.emit('fetchgeniussong', song_id) + } else { + $("p:contains('* All input fields with an *, are optional')").text('

Enter a Genius song ID!

') + } + }); + + $(document).on('click', '#fetchgeniusalbum', function() { + let album_id = $(this).parent().siblings("input").val(); + if(album_id.length > 0) { + socket.emit("fetchgeniusalbum", album_id) + } else { + $("p:contains('* All input fields with an *, are optional')").text('

Enter a Genius song ID!

') } }); @@ -836,11 +1058,10 @@ $(document).ready(function() { let link = $(this).find('.youtubelink').attr('href'); socket.emit('ytdl_search', link); $("#defaultview").children('ul').remove(); - let spinner = '
Loading...
'; $("#searchlog, #progresstext").empty(); $("#defaultview").addClass(['d-none', 'justify-content-center']) $("#defaultview").removeClass('d-none'); - $("#defaultview").empty().prepend(spinner); + spinner('Loading', $("#defaultview").empty()); // Reset the modal $("#progress").attr('aria-valuenow', "0").css('width', '0'); $("#searchvideomodalfooter, #metadataview, #progressview, #downloadfilebtn").addClass('d-none'); @@ -848,6 +1069,146 @@ $(document).ready(function() { $("#metadataview").find('input').val(''); }); + $(document).on('keyup', '.directory_input', function(e) { + e.preventDefault(); + let match = '^[^<>:;,?"*|/]+$'; + if(!$(this).val().match(match) && $(this).val() != '') { + e.preventDefault(); + $("#filebrowserlog").text('Directory contains invalid characters!'); + $(this).parent().parent().siblings(':last-child').children('button').addClass('disabled'); + $(this).parent().parent().siblings(':last-child').children('button').attr('disabled', ''); + } else if($(this).val() == '') { + $("#filebrowserlog").text(''); + $(this).parent().parent().siblings(':last-child').children('button').addClass('disabled'); + $(this).parent().parent().siblings(':last-child').children('button').attr('disabled', ''); + } else { + $("#filebrowserlog").text(''); + $(this).parent().parent().siblings(':last-child').children('button').removeClass('disabled'); + $(this).parent().parent().siblings(':last-child').children('button').removeAttr('disabled'); + } + }); + + $(document).on('click', '.createdirectorybtn', function() { + let directoryname = $(this).parent().siblings(':first-child').find('input.directory_input').val(); + let currentdirectory = $("#filebrowsertitle").children('span').text(); + let newrow = $(this).parents('tr'); + socket.emit('createdirectory', currentdirectory, directoryname, function(response) { + $("#filebrowserlog").text(response.msg); + if(response.status == 200) { + newrow.attr('filepath', response.filepath); + let icon = document.createElement('i'); + let span = document.createElement('span'); + let nbsp = document.createTextNode('\u00A0'); + span.innerText = directoryname; + icon.classList.add('bi', 'bi-folder-fill'); + newrow.children(':first-child').empty(); + newrow.children(':first-child').append(icon, nbsp, span); + newrow.find('button').tooltip('hide'); + newrow.find('button').remove(); + } + }); + }); + + $(document).on('click', '.removefolder', function(e) { + e.stopPropagation(); + $(this).popover('toggle'); + }); + + $(document).on('click', '.cancelremoval', function() { + $(this).parents('a.removefolder').popover('hide'); + }); + + $(document).on('click', '.confirmremoval', function() { + let directory = $(this).parents('.btn-group').attr('filepath'); + socket.emit('removedirectory', directory, function(response) { + $("#filebrowserlog").text(response.msg); + if(response.status == 200) { + $('tr.directory:contains("'+response.directory+'")').remove(); + } + }); + }); + + $(document).on('click', '.filebrowserrow', function() { + if($(this).hasClass('directory')) { + if($(this).find('input.directory_input').length < 1) { + let directorypath = $(this).attr('filepath'); + let id = $("#filebrowsermodal").attr('item'); + let visible = $("#filenameform").hasClass('d-none') ? ['files', 'directories'] : ['directories']; + socket.emit('showfilebrowser', visible, id, directorypath); + } + } else { + if($(this).hasClass('selectedrow')) { + $(this).removeClass('selectedrow'); + $("#selectfilebtn").addClass('disabled'); + $("#selectfilebtn").attr('disabled', ''); + $("#selectedfile").text(''); + } else if($(".selectedrow").length > 0) { + $('.selectedrow').removeClass('selectedrow'); + $(this).addClass('selectedrow'); + $("#selectedfile").text('Selected file ' + $(this).find('span').text()); + } else { + $(this).toggleClass('selectedrow'); + $("#selectfilebtn").removeClass('disabled'); + $("#selectfilebtn").removeAttr('disabled'); + $("#selectedfile").text('Selected file ' + $(this).find('span').text()); + } + } + }); + + $("#filebrowserup").on('click', function() { + let currentdirectory = $("#filebrowsertitle").children('span').text(); + let id = $("#filebrowsermodal").attr('item'); + let visible = $("#filenameform").hasClass('d-none') ? ['files', 'directories', 'parent'] : ['directories', 'parent']; + socket.emit('showfilebrowser', visible, id, currentdirectory); + }); + + $("#selectfilebtn").on('click', function() { + let selectedfile = $('.selectedrow').attr('filepath'); + let id = $("#filebrowsermodal").attr('item'); + socket.emit('updatefile', selectedfile, id); + }); + + $("#filenameform").on('submit', function(e) { + e.preventDefault(); + let filename = $("#filename").val(); + let directory = $("#filebrowsertitle").children('span').text(); + let id = $("#filebrowsermodal").attr('item'); + let overwrite = $("#overwritecheck").is(':checked') ? true : false + socket.emit('movefile', directory, filename, id, overwrite, function(msg) { + $("#filebrowserlog").text(msg); + }); + }); + + $(".addfolder").on('click', function() { + item = { + 'filepath': 'new', + 'filename': '', + 'lastmodified': Date.now() / 1000, + 'filesize': 0, + 'pathtype': 'directory', + 'createnewfolder': true + }; + insertfilebrowseritem(item); + }); + + $("#filename").on('keyup', function(e) { + let match = '^[^<>:;,?"*|/]+$'; + if(!$(this).val().match(match) && $(this).val() != '') { + e.preventDefault(); + $("#filebrowserlog").text('Filename contains invalid characters!'); + $("#submitfilename").addClass('disabled'); + $("#submitfilename").attr('disabled', ''); + } else if($(this).val() == '') { + $("#filebrowserlog").text(''); + $("#submitfilename").addClass('disabled'); + $("#submitfilename").attr('disabled', ''); + } else { + $("#filebrowserlog").text(''); + $("#submitfilename").removeClass('disabled'); + $("#submitfilename").removeAttr('disabled'); + } + }) + $("#nextbtn").on('click', function() { if($(".timestamp_input").val() == '' && !$("#segments_check").is(':checked')) { $("#downloadmodal").animate({ scrollTop: 0 }, 'fast'); @@ -868,10 +1229,9 @@ $(document).ready(function() { 'type': 'webui' }; socket.emit('searchmetadata', args); - let spinner = '
Loading...
'; $("#searchlog, #progresstext").empty(); $("#defaultview").addClass(['d-flex', 'justify-content-center']) - $("#defaultview").prepend(spinner); + spinner("Loading metadata...", $("#defaultview")); $("#searchvideomodalfooter, #ytcol").addClass('d-none'); } }); @@ -935,15 +1295,14 @@ $(document).ready(function() { // Send request to the server let query = $("#query").val(); socket.emit('ytdl_search', query); - let spinner = '
Loading...
'; $("#searchlog, #progresstext").empty(); $("#defaultview").removeClass('d-none'); $("#defaultview").addClass(['d-flex', 'justify-content-center']); $("#defaultview").children('.youtuberesult').remove(); - $("#defaultview").prepend(spinner); + spinner("Loading...", $("#defaultview")); // Reset the modal $("#progress").attr('aria-valuenow', "0").css('width', '0'); - $("#searchvideomodalfooter, #metadataview, #progressview, #downloadfilebtn").addClass('d-none'); + $("#searchvideomodalfooter, #metadataview, #progressview, #downloadfilebtn, #searchmetadataview").addClass('d-none'); $(".removeperson").parents('.personrow').remove(); $("#metadataview").find('input').val(''); $("#ytcol, #audiocol").empty(); @@ -1002,7 +1361,7 @@ $(document).ready(function() { } socket.emit('ytdl_download', data, function(ack) { if(ack == "OK") { - $("#editmetadata, #downloadbtn, #searchmetadataview, #404p, #defaultview, #resetviewbtn, #audiocol, #savemetadata, #metadataview ").addClass('d-none'); + $("#editmetadata, #downloadbtn, #searchmetadataview, #404p, #defaultview, #resetviewbtn, #geniusbtn, #audiocol, #savemetadata, #metadataview").addClass('d-none'); $("#progressview").removeClass('d-none'); $("#searchlog").empty(); } @@ -1046,13 +1405,26 @@ $(document).ready(function() { $("#resetviewbtn").on('click', function() { if($("#ytcol").children().length > 0) { $("#defaultview, #ytcol, #nextbtn").removeClass('d-none'); - $("#progressview, #audiocol, #resetviewbtn, #editmetadata, #downloadbtn, #metadataview").addClass('d-none'); + $("#progressview, #audiocol, #resetviewbtn, #editmetadata, #downloadbtn, #metadataview, #geniusbtn").addClass('d-none'); $("#downloadmodal").animate({ scrollTop: 0 }, 'fast'); } else { $("#defaultview, #searchlog").empty(); } }); + $("#geniusbtn").on('click', function() { + if($(".audiocol-checkbox:checked").length < 1 && $("#audiocol").length > 0) { + $("#downloadmodal").animate({ scrollTop: 0 }, 'fast'); + $("#searchlog").text('Select a release on the right side before searching for lyrics'); + } else { + $("#audiocol").addClass('d-none'); + let args = { "title": $(".audiocol-checkbox:checked").parent().siblings('.media-body').children("h5").text(), "type": "lyrics" } + spinner('Loading Genius data...', $("#defaultview")); + socket.emit('searchmetadata', args); + $("#searchmetadataview, #searchvideomodalfooter").addClass('d-none'); + } + }); + $("#searchmetadatabtn").on('click', function() { let args = { 'title': $("#metadataquery").val(), @@ -1060,6 +1432,8 @@ $(document).ready(function() { 'type': 'webui' } socket.emit('searchmetadata', args); + // $("#defaultview").children('#audiocol').remove(); + spinner('Loading metadata...', $("#defaultview")); }); socket.on('downloadprogress', function(msg) { @@ -1079,11 +1453,16 @@ $(document).ready(function() { if(msg.status == 'downloading') { if(msg.total_bytes != 'Unknown') { - progress_text.text("Downloading..."); - let percentage = Math.round(((msg.downloaded_bytes / msg.total_bytes) * 100) / phases); - setprogress(percentage); + if((msg.downloaded_bytes / msg.total_byes) == 1) { + progress_text.text("Extracting audio..."); + setprogress(100 / phases); + } else { + progress_text.text("Downloading..."); + let percentage = Math.round(((msg.downloaded_bytes / msg.total_bytes) * 100) / phases); + setprogress(percentage); + } } else { - progress_text.text("Downloading... Percentage unknown :("); + progress_text.text("Downloading..."); } } else if(msg.status == 'finished_ytdl') { @@ -1124,6 +1503,8 @@ $(document).ready(function() { } else if(metadata_source == 'Deezer') { var trackid = $("#deezer_trackid").val(); var albumid = $("#deezer_albumid").val(); + } else if(metadata_source == 'Genius') { + var trackid = $("#genius_songid").val(); } $.each($('.artist_relations'), function() { @@ -1139,14 +1520,16 @@ $(document).ready(function() { } } }); - + + let artists = $("#md_artists").val().split(';'); + let albumartists = $("#md_album_artists").val().split(';'); let metadata = { 'trackid': trackid, 'albumid': albumid, 'title': $("#md_title").val(), - 'artists': $("#md_artists").val(), + 'artists': JSON.stringify(artists), 'album': $("#md_album").val(), - 'album_artists': $("#md_album_artists").val(), + 'album_artists': JSON.stringify(albumartists), 'album_tracknr': $("#md_album_tracknr").val(), 'album_releasedate': $("#md_album_releasedate").val(), 'cover': $("#md_cover").val(), @@ -1221,7 +1604,7 @@ $(document).ready(function() { } else if($("#404p").hasClass('d-none')) { $("#defaultview").children('.spinner-border').remove(); $("#nextbtn, #otherp").addClass('d-none'); - $("#404p, #searchvideomodalfooter, #editmetadata, #resetviewbtn").removeClass('d-none'); + $("#404p, #searchvideomodalfooter, #editmetadata, #resetviewbtn, #geniusbtn").removeClass('d-none'); } $("#searchvideomodalfooter").removeClass('d-none'); }); @@ -1230,9 +1613,9 @@ $(document).ready(function() { console.info('Spotify info'); spotifydata = spotify; $("#searchmetadataview").removeClass('d-none'); - if(Object.keys(spotify["tracks"]["items"]).length > 0) { + if(spotify["tracks"]["items"].length > 0) { $("#audiocol").empty(); - for(let i = 0; i < Object.keys(spotify["tracks"]["items"]).length - 2; i++) { + for(let i = 0; i < spotify["tracks"]["items"].length; i++) { insertspotifydata(spotify["tracks"]["items"][i]); } $("#metadataquery").val(spotifydata["query"]); @@ -1240,7 +1623,7 @@ $(document).ready(function() { } else if($("#404p").hasClass('d-none')) { $("#defaultview").children('.spinner-border').remove(); $("#nextbtn, #otherp").addClass('d-none'); - $("#404p, #searchvideomodalfooter, #editmetadata, #resetviewbtn").removeClass('d-none'); + $("#404p, #searchvideomodalfooter, #editmetadata, #resetviewbtn, #geniusbtn").removeClass('d-none'); } }); @@ -1258,6 +1641,22 @@ $(document).ready(function() { } else if($("#404p").hasClass('d-none')) { $("#defaultview").children('.spinner-border').remove(); $("#nextbtn, #otherp").addClass('d-none'); + $("#404p, #searchvideomodalfooter, #editmetadata, #resetviewbtn, #geniusbtn").removeClass('d-none'); + } + }); + + socket.on('genius_response', (genius) => { + console.info('Genius info') + geniusdata = genius; + if(geniusdata["hits"].length > 0) { + for(let i = 0; i < genius["hits"].length; i++) { + insertgeniusdata(genius["hits"][i]["result"]) + $("#searchvideomodalfooter, #editmetadata, #metadataviewbtn").removeClass('d-none'); + } + $("#geniusbtn").addClass('d-none'); + } else if($("#404p").hasClass('d-none')) { + $("#defaultview").children('.spinner-border').remove(); + $("#nextbtn, #otherp, #geniusbtn").addClass('d-none'); $("#404p, #searchvideomodalfooter, #editmetadata, #resetviewbtn").removeClass('d-none'); } }); @@ -1407,6 +1806,38 @@ $(document).ready(function() { $("#md_cover").val(data["album"]["cover_medium"]); }); + socket.on('genius_song', (data) => { + console.log(data); + let songdata = data["song"]; + let artists = songdata["primary_artist"]["name"] + "; "; + for(let i = 0; i < songdata["featured_artists"].length; i++) { + artists += songdata["featured_artists"][i]["name"] + "; "; + } + $("#md_title").val(songdata["title"]); + $("#md_artists").val(artists.slice(0, artists.length - 2)); + $("#md_album").val(songdata["album"]["name"]); + $("#md_cover").val(songdata["song_art_image_thumbnail_url"]); + $("#md_album_releasedate").val(songdata["release_date"]); + $("#genius_albumid").val(songdata["album"]["id"]); + }); + + socket.on('genius_album', (data) => { + console.log(data); + let albumtracks = data["tracks"]; + let albumartists = albumtracks[0]["song"]["primary_artist"]["name"] + "; "; + for(let i = 0; i < albumtracks.length; i++) { + if(albumtracks[i]["song"]["featured_artists"].length > 0) { + for(let j = 0; j < albumtracks[i]["song"]["featured_artists"].length; j++) { + albumartists += albumtracks[i]["song"]["featured_artists"][j]["name"] + "; "; + } + } + if(albumtracks[i]["song"]["title"] == $("#md_title").val()) { + $("#md_album_tracknr").val(i + 1); + } + } + $("#md_album_artists").val(albumartists); + }); + socket.on('searchvideo', (data) => { $("#defaultview").find(".spinner-border").remove(); $("#searchlog").text(data); @@ -1436,7 +1867,7 @@ $(document).ready(function() { cover: itemdata["cover"] }]); ap.play(); - $("#recordstable").parent().css('max-height', '65vh'); + $("#recordstable").parent().css('height', '65vh'); $("#audioplayer").removeClass('d-none') } else if(data.msg == 'changed_metadata') { socket.emit('updateitem', data.data); @@ -1455,6 +1886,35 @@ $(document).ready(function() { $("#bulkactionsrow").css('visibility', 'hidden'); $("#selectall").prop('checked', false); $("#overviewlog").text(data.data); + } else if(data.msg == 'showfilebrowser') { + $("#filebrowserup").siblings('tr').remove(); + if(data.visible.indexOf('files') > -1) { + $("#filenameform, #submitfilename").addClass('d-none'); + $("#selectedfile, #selectfilebtn").removeClass('d-none'); + } else { + $("#filenameform, #submitfilename").removeClass('d-none'); + $("#selectedfile, #selectfilebtn").addClass('d-none'); + } + if(data.files.length > 0) { + for(let i = 0; i < data.files.length; i++) { + insertfilebrowseritem(data.files[i]); + } + } else { + let tr = document.createElement('tr'); + let td = document.createElement('td'); + td.setAttribute('colspan', 3); + td.classList.add('text-dark', 'text-center'); + td.innerHTML = 'No files found with any of the following extensions:
AAC, FLAC, MP3, M4A, OPUS, VORBIS, WAV, MP4, M4A, FLV, WEBM, OGG, MKV, AVI'; + tr.append(td); + $("#filebrowserup").after(tr); + } + $("#filebrowsertitle").children('span').text(data.directory); + $("#filebrowsermodal").attr('item', data.id); + $("#filebrowsermodal").modal('show'); + } else if(data.msg == 'updated_filepath') { + $("#filebrowsermodal").modal('hide'); + $("#overviewlog").text('File location succesfully updated to ' + data["filepath"]) + createdropdownmenu(data.item); } else { $("#overviewlog").text(data.msg); } @@ -1557,7 +2017,7 @@ $(document).ready(function() { } }); ap.on('listclear', function() { - $("#recordstable").parent().css('max-height', '75vh'); + $("#recordstable").parent().css('height', '75vh'); $("#audioplayer").addClass('d-none'); }) }); \ No newline at end of file diff --git a/metatube/static/JS/settings.js b/metatube/static/JS/settings.js index 2b09109a..9183c348 100644 --- a/metatube/static/JS/settings.js +++ b/metatube/static/JS/settings.js @@ -18,6 +18,9 @@ $(document).ready(function() { if($("#spotifycheck").hasAttr('checked')) { $("#spotifyrow").removeClass('d-none'); + } + if($("#geniuscheck").hasAttr('checked')) { + $("#geniusrow").removeClass('d-none'); } function addtemplate(data) { @@ -127,10 +130,6 @@ $(document).ready(function() { tr_visible.append(td_name, td_type, td_extension, td_output_name, td_output_folder, td_buttons); $("#addtemplaterow").before(tr_visible, tr_hidden); } - - function changedtemplate(data) { - - } $(document).on('click', ".templatebtn", function() { let goal = $(this).attr('goal'); @@ -142,9 +141,10 @@ $(document).ready(function() { let bitrate = $("#template_bitrate").val() == '' ? 'best' : $("#template_bitrate").val(); let width = $("#template_resolution").val() == 'best' ? 'best' : $("#template_width").val(); let height = $("#template_resolution").val() == 'best' ? 'best' : $("#template_height").val(); + let proxy_type = $("#proxy_status").val() == 'false' ? 'None' : $("#proxy_type").val(); let proxy_json = JSON.stringify({ 'status': $("#proxy_status").val(), - 'type': $("#proxy_type").val(), + 'type': proxy_type, 'address': $("#proxy_address").val(), 'username': $("#proxy_username").val(), 'password': $("#proxy_password").val(), @@ -251,10 +251,16 @@ $(document).ready(function() { } else { $("#spotifyrow").addClass('d-none'); } + } else if($(this).val() == 'genius') { + if($(this).is(':checked')) { + $("#geniusrow").removeClass('d-none'); + } else { + $("#geniusrow").addClass('d-none'); + } } }); - $("#submitdownloadform").on('click', function() { + $("#submitdownloadsettingsform").on('click', function() { let amount = $("#max_amount").val(); let ffmpeg_path = $("#ffmpeg_path").val(); let hardware_transcoding = $("#hardware_acceleration").val(); @@ -276,6 +282,16 @@ $(document).ready(function() { }; } } + if(metadata_sources.indexOf('genius') > -1) { + if($("#geniusaccesstoken").val() == '') { + $("#downloadsettingslog").find('p').text('Enter the Genius API credentials!'); + return false; + } else { + extradata["geniusapi"] = { + 'token': $("#geniusaccesstoken").val() + }; + } + } socket.emit('updatesettings', ffmpeg_path, amount, hardware_transcoding, metadata_sources, extradata); }); @@ -299,11 +315,11 @@ $(document).ready(function() { } }); - $("#togglesecret").on('click', function() { - if($("#spotifyclientSecret").attr('type') == 'password') { - $("#spotifyclientSecret").attr('type', 'text'); + $(".togglesecret").on('click', function() { + if($(this).parent().siblings('input').attr('type') == 'password') { + $(this).parents().siblings('input').attr('type', 'text'); } else { - $("#spotifyclientSecret").attr('type', 'password'); + $(this).parent().siblings('input').attr('type', 'password'); } }); diff --git a/metatube/templates/base.html b/metatube/templates/base.html index 84d0631b..b554ae7a 100644 --- a/metatube/templates/base.html +++ b/metatube/templates/base.html @@ -13,8 +13,7 @@ - {{ JSGlue.include() }} - +