@@ -66,10 +66,15 @@ MetaTube will NOT work if you disable JavaScript
@@ -125,7 +130,7 @@
Download song
-
+
@@ -153,6 +158,8 @@
Select an FFmpeg location in settings before downloading
+
+ {% if genius is sameas True %} {% endif %}
@@ -210,4 +217,58 @@
+
+
+
+
+
+
+
+
+
+
+
+ Filename |
+ File size |
+
+ Last modified
+
+ |
+
+
+
+
+ ... |
+
+
+
+
+
Why are some files invisible?
+
+
+ Only files with the following supported extensions are shown: AAC, FLAC, MP3, M4A, OPUS, VORBIS, WAV, MP4, M4A, FLV, WEBM, OGG, MKV & AVI.
+ Additionally, files that are already linked with an item in the database, will be hidden as well, to prevent a file being linked to multiple items.
+
+
+
+
+
+
+
{% endblock %}
\ No newline at end of file
diff --git a/metatube/templates/settings.html b/metatube/templates/settings.html
index 63efed44..6b89949f 100644
--- a/metatube/templates/settings.html
+++ b/metatube/templates/settings.html
@@ -71,6 +71,10 @@ Download settings
+
+
+
+
@@ -87,14 +91,25 @@
Download settings
-
+
+
* An absolute path starts with /
or drive_letter:\
** The root directory is the same directory where metatube.py
is located
-
+
Template settings
diff --git a/metatube/youtube.py b/metatube/youtube.py
index 54eced52..9554cb3a 100644
--- a/metatube/youtube.py
+++ b/metatube/youtube.py
@@ -113,7 +113,7 @@ def postprocessor_hook(d):
'filepath': d['info_dict']['filepath'],
'postprocessor': d["postprocessor"]
})
- logger.info(d["postprocessor"])
+ logger.info("Finished postprocessor %s", d["postprocessor"])
# sockets.downloadprogress({'status': 'finished_ffmpeg', 'filepath': d['info_dict']['filepath'], 'postprocessor': d["postprocessor"]})
def get_options(url, ext, output_folder, type, output_format, bitrate, skipfragments, proxy_data, ffmpeg, hw_transcoding, vaapi_device, width, height, verbose):
diff --git a/requirements.txt b/requirements.txt
index c3bcc8d6..22279931 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,12 +1,11 @@
mutagen==1.45.1
requests==2.28.0
-yt-dlp==2022.6.22.1
+yt-dlp==2022.6.29
gevent==21.8.0
gevent-websocket==0.10.1
Flask==2.1.2
Flask-SocketIO==5.2.0
Flask-Migrate==3.1.0
-Flask-JSGlue2==0.3.3
Flask-SQLAlchemy==2.5.1
musicbrainzngs==0.7.1
sponsorblock.py==0.2.2
From 1282c4120f90141b99d44765a47d9c2ae66da22f Mon Sep 17 00:00:00 2001
From: JVT038 <47184046+JVT038@users.noreply.github.com>
Date: Fri, 30 Dec 2022 20:29:25 +0100
Subject: [PATCH 2/4] Fix #67
---
metatube/templates/downloadform.html | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/metatube/templates/downloadform.html b/metatube/templates/downloadform.html
index ab093b18..a2bc9dcd 100644
--- a/metatube/templates/downloadform.html
+++ b/metatube/templates/downloadform.html
@@ -26,7 +26,7 @@ Download settings
-
\ No newline at end of file
+
From c294938dbaad0703e8d8dafb6f66ca43941f65ba Mon Sep 17 00:00:00 2001
From: JVT038 <47184046+JVT038@users.noreply.github.com>
Date: Sun, 19 Feb 2023 13:32:04 +0100
Subject: [PATCH 3/4] Did some stuff, idk what I did though
---
config.py | 5 +-
metatube/database.py | 1 +
metatube/genius.py | 5 +-
metatube/metadata.py | 29 ++++++----
metatube/overview/routes.py | 9 ++--
metatube/spotify.py | 2 +-
metatube/static/CSS/libraries/dark.css | 8 ++-
metatube/static/JS/overview.js | 73 ++++++++++++++++++++------
metatube/templates/base.html | 4 +-
metatube/templates/downloadform.html | 15 ++++--
metatube/templates/metadataform.html | 14 ++---
metatube/templates/overview.html | 12 +++--
requirements.txt | 6 +--
13 files changed, 125 insertions(+), 58 deletions(-)
diff --git a/config.py b/config.py
index c3996d85..1fe8c473 100644
--- a/config.py
+++ b/config.py
@@ -21,4 +21,7 @@ class Config(object):
PORT = os.environ.get('PORT', 5000)
FFMPEG = os.environ.get('FFMPEG', "")
DOWNLOADS = os.environ.get('DOWNLOADS', os.path.join(basedir, 'downloads'))
- URL_SUBPATH = os.environ.get('URL_SUBPATH', '/')
\ No newline at end of file
+ URL_SUBPATH = os.environ.get('URL_SUBPATH', '/')
+ META_EXTENSIONS = ['MP3', 'OPUS', 'FLAC', 'OGG', 'MP4', 'M4A', 'WAV']
+ VIDEO_EXTENSIONS = ['MP4', 'M4A', 'FLV', 'WEBM', 'OGG', 'MKV', 'AVI']
+ AUDIO_EXTENSIONS = ['AAC', 'FLAC', 'MP3', 'M4A', 'OPUS', 'VORBIS', 'WAV']
\ No newline at end of file
diff --git a/metatube/database.py b/metatube/database.py
index 9e4aa6dc..449498ec 100644
--- a/metatube/database.py
+++ b/metatube/database.py
@@ -32,6 +32,7 @@ def set_amount(self, amount):
def set_spotify(self, spotify):
self.spotify_api = spotify
+ print(spotify)
db.session.commit()
logger.info('Changed the Spotify API settings')
diff --git a/metatube/genius.py b/metatube/genius.py
index c34f324e..1b1cb2c3 100644
--- a/metatube/genius.py
+++ b/metatube/genius.py
@@ -10,7 +10,7 @@ def __init__(self, client_id):
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"])
+ logger.info('Searched Genius for track \'%s\' ', data["title"])
def searchsong(data, token):
genius = Genius(token)
@@ -18,6 +18,9 @@ def searchsong(data, token):
def fetchsong(self, id):
return self.genius.song(id)
+
+ def fetchlyrics(self, url):
+ return self.genius.lyrics(url)
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 568e1429..04455e05 100644
--- a/metatube/metadata.py
+++ b/metatube/metadata.py
@@ -4,7 +4,7 @@
from re import M
from mutagen.id3 import (
# Meaning of the various frames: https://mutagen.readthedocs.io/en/latest/api/id3_frames.html
- ID3, APIC, TIT2, TALB, TCON, TLAN, TRCK, TSRC, TXXX, TPE1
+ ID3, APIC, TIT2, TALB, TCON, TLAN, TRCK, TSRC, TXXX, TPE1, USLT
)
from mutagen.flac import FLAC, Picture
from mutagen.aac import AAC
@@ -224,22 +224,22 @@ def getdeezerdata(filename, metadata_user, metadata_source):
}
return data
- def getgeniusdata(filename, metadata_user, metadata_source):
+ def getgeniusdata(filename, metadata_user, metadata_source, lyrics):
logger.info('Getting Genius metadata')
- album = metadata_source["album"]["name"] if len(metadata_user["album"] < 1) else metadata_user["album"]
+ album = metadata_source["song"]["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"]
+ albumid = metadata_source["song"]["album"]["id"] if len(metadata_user["albumid"]) < 1 else metadata_user["albumid"]
+ release_date = metadata_source["song"]["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"]:
+ cover_path = metadata_source["song"]["song_art_image_thumbnail_url"] if len(metadata_user["cover"]) < 1 else metadata_user["cover"]
+ title = metadata_source["song"]["title"] if len(metadata_user["title"]) < 1 else metadata_user["title"]
+ geniusartists = metadata_source["song"]["primary_artist"]["name"] + "; "
+ for artist in metadata_source["song"]["featured_artists"]:
geniusartists += artist["name"] + "; "
- artists = geniusartists[0:len(geniusartists) - 2] if len(metadata_user["artists"] < 1) else metadata_user["artists"]
+ 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)
@@ -271,7 +271,8 @@ def getgeniusdata(filename, metadata_user, metadata_source):
'cover_mime_type': cover_mime_type,
'image': image,
'title': title,
- 'genres': ""
+ 'genres': "",
+ 'lyrics': lyrics
}
return data
@@ -380,6 +381,10 @@ def mergeaudiodata(data):
elif data.get('source', '') == 'Deezer':
audio.RegisterTXXXKey('deezer_trackid', data["track_id"])
audio.RegisterTXXXKey('deezer_albumid', data["album_id"])
+
+ if 'lyrics' in data:
+ audio.RegisterTextKey('lyrics', "USLT")
+
elif data["extension"] == 'FLAC':
audio = FLAC(data["filename"])
elif data["extension"] == 'AAC':
@@ -397,6 +402,8 @@ def mergeaudiodata(data):
audio["title"] = data["title"]
audio["date"] = data["release_date"]
audio["genre"] = data["genres"]
+ if 'lyrics' in data and data["extension"] != 'MP3':
+ audio['lyrics'] = data['lyrics']
if data.get('source', '') == 'Musicbrainz':
audio["musicbrainz_releasetrackid"] = data["track_id"]
audio["musicbrainz_releasegroupid"] = data["album_id"]
diff --git a/metatube/overview/routes.py b/metatube/overview/routes.py
index e18470cc..cb7e1c3e 100644
--- a/metatube/overview/routes.py
+++ b/metatube/overview/routes.py
@@ -31,7 +31,7 @@ def index():
records = Database.getrecords()
metadata_sources = Config.get_metadata_sources()
metadataform = render_template('metadataform.html', metadata_sources=metadata_sources)
- genius = True if len(Config.get_genius()) != 0 else False
+ genius = True if 'genius' in Config.get_metadata_sources().split(';') else False
return render_template('overview.html', current_page='overview', ffmpeg_path=ffmpeg_path, records=records, metadataview=metadataform, genius=genius)
@socketio.on('searchitem')
@@ -187,7 +187,7 @@ def mergedata(filepath, release_id, metadata, cover, source):
metadata_user = metadata
cover_source = cover if cover != '/static/images/empty_cover.png' else os.path.join(env.BASE_DIR, 'metatube', cover)
extension = filepath.split('.')[len(filepath.split('.')) - 1].upper()
- if extension in ['MP3', 'OPUS', 'FLAC', 'OGG', 'MP4', 'M4A', 'WAV']:
+ if extension in env.META_EXTENSIONS:
if source == 'Spotify':
cred = Config.get_spotify().split(';')
spotify = Spotify(cred[1], cred[0])
@@ -203,7 +203,8 @@ def mergedata(filepath, release_id, metadata, cover, source):
token = Config.get_genius()
genius = Genius(token)
metadata_source = genius.fetchsong(release_id)
- data = MetaData.getgeniusdata(filepath, metadata_user, metadata_source)
+ lyrics = genius.fetchlyrics(metadata_source["song"]["url"])
+ data = MetaData.getgeniusdata(filepath, metadata_user, metadata_source, lyrics)
elif source == 'Unavailable':
data = MetaData.onlyuserdata(filepath, metadata_user)
if data is not False:
@@ -359,7 +360,7 @@ def showfilebrowser(visible, id, target_folder=None):
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']:
+ if extension not in env.AUDIO_EXTENSIONS and extension not in env.VIDEO_EXTENSIONS:
continue
if Database.checkfile(path) is not None:
continue
diff --git a/metatube/spotify.py b/metatube/spotify.py
index 7caa5c56..8ef7d84a 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', data["title"])
+ 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 5b6ab5dc..9faff371 100644
--- a/metatube/static/CSS/libraries/dark.css
+++ b/metatube/static/CSS/libraries/dark.css
@@ -19,8 +19,13 @@
background-color: #333 !important;
}
+[data-theme="dark"] #outputfolderbtn {
+ border-color: #eee;
+}
+
[data-theme="dark"] .bg-white,
-[data-theme="dark"] .dropdown-item:hover {
+[data-theme="dark"] .dropdown-item:hover,
+[data-theme="dark"] #outputfolderbtn:hover {
background-color: #000 !important;
}
@@ -63,6 +68,7 @@
[data-theme="dark"] .input-group-text,
[data-theme="dark"] #switchlabel,
[data-theme="dark"] #item_filepath,
+[data-theme="dark"] #outputfolderbtn,
[data-theme="dark"] .nav-tabs > .nav-item > .nav-link {
background-color: #333;
color: white;
diff --git a/metatube/static/JS/overview.js b/metatube/static/JS/overview.js
index 9095dc2a..f8b1b0ae 100644
--- a/metatube/static/JS/overview.js
+++ b/metatube/static/JS/overview.js
@@ -19,6 +19,7 @@ $(document).ready(function() {
}
function spinner(msg, location) {
let spinner = ''+msg+'
';
+ $(location).remove('div.spinner-border');
$(location).prepend(spinner);
}
// If the user presses Enter or submits the form in some other way, it'll trigger the 'find' button
@@ -1069,6 +1070,14 @@ $(document).ready(function() {
$("#metadataview").find('input').val('');
});
+
+ $(document).on('click', '#outputfolderbtn', function() {
+ $("#downloadmodal").modal('hide');
+ let id = '-1'
+ let visible = ['directories'];
+ socket.emit('showfilebrowser', visible, id);
+ })
+
$(document).on('keyup', '.directory_input', function(e) {
e.preventDefault();
let match = '^[^<>:;,?"*|/]+$';
@@ -1120,12 +1129,16 @@ $(document).ready(function() {
$(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();
- }
- });
+ if(directory == 'new') {
+ $('tr.directory[filepath="new"]').remove();
+ } else {
+ 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() {
@@ -1179,6 +1192,23 @@ $(document).ready(function() {
});
});
+ $("#closefilebrowserbtn").on('click', function() {
+ $("#filebrowsermodal").modal('hide');
+ if($("#filebrowsermodal").attr('item') === '-1') {
+ $("#downloadmodal").modal('show');
+ }
+ })
+
+ $("#selectdirectorybtn").on('click', function() {
+ let directory = $('#filebrowsertitle').children('span').text();
+ $("#output_folder").val(directory);
+ $("#filebrowsermodal").modal('hide');
+ $("#downloadmodal").modal('show');
+ });
+ $('#downloadmodal').on('shown.bs.modal', function() {
+ $("body").addClass("modal-open"); // for some reason it doesn't add this class, making the screen unscrollable, which is why I'm adding this manually
+ });
+
$(".addfolder").on('click', function() {
item = {
'filepath': 'new',
@@ -1207,7 +1237,7 @@ $(document).ready(function() {
$("#submitfilename").removeClass('disabled');
$("#submitfilename").removeAttr('disabled');
}
- })
+ });
$("#nextbtn").on('click', function() {
if($(".timestamp_input").val() == '' && !$("#segments_check").is(':checked')) {
@@ -1302,7 +1332,7 @@ $(document).ready(function() {
spinner("Loading...", $("#defaultview"));
// Reset the modal
$("#progress").attr('aria-valuenow', "0").css('width', '0');
- $("#searchvideomodalfooter, #metadataview, #progressview, #downloadfilebtn, #searchmetadataview").addClass('d-none');
+ $("#searchvideomodalfooter, #metadataview, #progressview, #downloadfilebtn, #searchmetadataview, #geniusbtn").addClass('d-none');
$(".removeperson").parents('.personrow').remove();
$("#metadataview").find('input').val('');
$("#ytcol, #audiocol").empty();
@@ -1361,7 +1391,7 @@ $(document).ready(function() {
}
socket.emit('ytdl_download', data, function(ack) {
if(ack == "OK") {
- $("#editmetadata, #downloadbtn, #searchmetadataview, #404p, #defaultview, #resetviewbtn, #geniusbtn, #audiocol, #savemetadata, #metadataview").addClass('d-none');
+ $("#editmetadata, #downloadbtn, #searchmetadataview, #404p, #defaultview, #resetviewbtn, #geniusbtn, #audiocol, #savemetadata, #metadataview, #geniuscol").addClass('d-none');
$("#progressview").removeClass('d-none');
$("#searchlog").empty();
}
@@ -1418,7 +1448,11 @@ $(document).ready(function() {
$("#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" }
+ let args = {
+ 'title': $("#trackspan").text() != 'Unknown' ? $("#trackspan").text() : $(".media-body").children('h5').text(),
+ 'artist': $("#artistspan").text() != 'Unknown' ? $("#artistspan").text() : $("#channelspan").text(),
+ 'type': 'lyrics'
+ };
spinner('Loading Genius data...', $("#defaultview"));
socket.emit('searchmetadata', args);
$("#searchmetadataview, #searchvideomodalfooter").addClass('d-none');
@@ -1562,7 +1596,7 @@ $(document).ready(function() {
} else if(msg.status == 'error') {
progress_text.text(msg.message);
progress.attr('aria-valuenow', 100);
- progress.html('ERROR ');
+ progress.html('ERROR ');
progress.css('width', '100%');
progress_text.text(msg.message);
if($("#edititemmodal").css('display').toLowerCase() != 'block') {
@@ -1654,7 +1688,7 @@ $(document).ready(function() {
$("#searchvideomodalfooter, #editmetadata, #metadataviewbtn").removeClass('d-none');
}
$("#geniusbtn").addClass('d-none');
- } else if($("#404p").hasClass('d-none')) {
+ } else {
$("#defaultview").children('.spinner-border').remove();
$("#nextbtn, #otherp, #geniusbtn").addClass('d-none');
$("#404p, #searchvideomodalfooter, #editmetadata, #resetviewbtn").removeClass('d-none');
@@ -1889,11 +1923,13 @@ $(document).ready(function() {
} else if(data.msg == 'showfilebrowser') {
$("#filebrowserup").siblings('tr').remove();
if(data.visible.indexOf('files') > -1) {
- $("#filenameform, #submitfilename").addClass('d-none');
+ $("#filenameform, #selectdirectorybtn, #submitfilename").addClass('d-none');
$("#selectedfile, #selectfilebtn").removeClass('d-none');
+ $("#browsermodaltitle").text('Select a file');
} else {
$("#filenameform, #submitfilename").removeClass('d-none');
- $("#selectedfile, #selectfilebtn").addClass('d-none');
+ $("#selectedfile, #selectdirectorybtn, #selectfilebtn").addClass('d-none');
+ $("#browsermodaltitle").text('Select a directory');
}
if(data.files.length > 0) {
for(let i = 0; i < data.files.length; i++) {
@@ -1904,17 +1940,20 @@ $(document).ready(function() {
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';
+ td.innerHTML = data.visible.indexOf('files') > -1 ? 'No files or directories found with any of the following extensions:
AAC, FLAC, MP3, M4A, OPUS, VORBIS, WAV, MP4, M4A, FLV, WEBM, OGG, MKV, AVI' : 'No directories found';
tr.append(td);
$("#filebrowserup").after(tr);
}
+ if(data.id === "-1") {
+ $("#filenameform, #selectfilebtn").addClass('d-none');
+ $("#selectdirectorybtn").removeClass('d-none');
+ }
$("#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);
+ $("#overviewlog").text('File location succesfully updated to ' + data["filepath"]);
} else {
$("#overviewlog").text(data.msg);
}
diff --git a/metatube/templates/base.html b/metatube/templates/base.html
index b554ae7a..390da7a9 100644
--- a/metatube/templates/base.html
+++ b/metatube/templates/base.html
@@ -30,7 +30,7 @@
- Settings
+ Settings
@@ -38,7 +38,7 @@
{% block content %}{% endblock %}
diff --git a/metatube/templates/downloadform.html b/metatube/templates/downloadform.html
index ab093b18..91ff1af4 100644
--- a/metatube/templates/downloadform.html
+++ b/metatube/templates/downloadform.html
@@ -26,7 +26,7 @@ Download settings