Compare commits

..

No commits in common. "64a2c38eeabe741818cf87e2912598ee7ddd75dd" and "4eb5eb70b6a968e32bf7ca60b52103e63b8681da" have entirely different histories.

4 changed files with 133 additions and 91 deletions

View File

@ -1,6 +1,6 @@
# Maintainer: Matthias Quintern <matthiasqui@protonmail.com> # Maintainer: Matthias Quintern <matthiasqui@protonmail.com>
pkgname=nicole pkgname=nicole
pkgver=2.1 pkgver=2.0
pkgrel=1 pkgrel=1
pkgdesc="Write lyrics from genius or azlyrics.com to a mp3-tag" pkgdesc="Write lyrics from genius or azlyrics.com to a mp3-tag"
arch=('any') arch=('any')
@ -8,9 +8,7 @@ url="https:/github.com/MatthiasQuintern/nicole"
license=('GPL3') license=('GPL3')
depends=('python-mutagen' 'python-beautifulsoup4') depends=('python-mutagen' 'python-beautifulsoup4')
source=(file://${PWD}/nicole/nicole.py _nicole.compdef.zsh nicole.1.man) source=(file://${PWD}/nicole/nicole.py _nicole.compdef.zsh nicole.1.man)
md5sums=('e7d1a3df7cb96798a7958018b28ed733' md5sums=(93752797fc2bca526fbfd32611518065 f3b46bdcaba5e7fc23bbacc6b2e153c0 8c3b8e5c90afdc8993ebab78d48f5668)
'f3b46bdcaba5e7fc23bbacc6b2e153c0'
'8c3b8e5c90afdc8993ebab78d48f5668')
package() { package() {
mkdir -p "${pkgdir}/usr/bin" mkdir -p "${pkgdir}/usr/bin"

View File

@ -119,8 +119,6 @@ The dependencies will be automatically installed when using the either of the tw
# CHANGELOG # CHANGELOG
## 2.0 ## 2.0
- Nicole now supports lyrics from genius! - Nicole now supports lyrics from genius!
- Added man-page
- Added zsh-completion
## 1.1 ## 1.1
- Lyrics are now properly encoded. - Lyrics are now properly encoded.

View File

@ -10,14 +10,11 @@ import re
from bs4 import BeautifulSoup from bs4 import BeautifulSoup
from difflib import SequenceMatcher from difflib import SequenceMatcher
from json import loads from json import loads
import argparse
from os import path, getcwd, listdir, mkdir from os import path, getcwd, listdir, mkdir
from time import sleep from time import sleep
from sys import argv from sys import argv
version = "2.1"
# Der Name Nicole ist frei erfunden und hat keine Bedeutung. # Der Name Nicole ist frei erfunden und hat keine Bedeutung.
# Jeglicher Zusammenhang mit einer Website der DHL wird hiermit ausdrücklich ausgeschlossen. # Jeglicher Zusammenhang mit einer Website der DHL wird hiermit ausdrücklich ausgeschlossen.
@ -34,13 +31,9 @@ class Nicole:
azlyrics: azlyrics:
Nicole creates a azlyrics url from the title and artist mp3-tags of the file. Nicole creates a azlyrics url from the title and artist mp3-tags of the file.
The lyrics are extracted from the html document using regex. The lyrics are extracted from the html document using regex.
genius:
Nicole searches the song from the title and artist mp3-tags via the genius api.
""" """
allowed_extensions = [".mp3", ".flac"]
def __init__(self, test_run=False, silent=False, write_history=True, ignore_history=False, overwrite_tag=False, recursive=False, rm_explicit=False, lyrics_site="all"): def __init__(self, test_run=False, silent=False, write_history=True, ignore_history=False, overwrite_tag=False, recursive=False, rm_explicit=False, lyrics_site="all"):
self.dry_run = test_run self.test_run = test_run
self.silent = silent self.silent = silent
self.write_history = write_history self.write_history = write_history
@ -89,13 +82,15 @@ class Nicole:
def _write_history(self): def _write_history(self):
config_path = path.expanduser("~") + "/.config/nicole/" config_path = path.expanduser("~") + "/.config/nicole/"
with open(config_path + "history", "w") as history_file: history_file = open(config_path + "history", "w")
for file in self.history: for file in self.history:
history_file.write(file + "\n") history_file.write(file + "\n")
history_file.close()
with open(config_path + "failed", "w") as failed_file: failed_file = open(config_path + "failed", "w")
for file in self.failed: for file in self.failed:
failed_file.write(file + "\n") failed_file.write(file + "\n")
failed_file.close()
def get_urls_azlyrics(self, artist:str, title:str): def get_urls_azlyrics(self, artist:str, title:str):
""" """
@ -240,14 +235,14 @@ class Nicole:
genius_artist_featured = results[i]["result"]["artist_names"] genius_artist_featured = results[i]["result"]["artist_names"]
genius_title = results[i]["result"]["title"] genius_title = results[i]["result"]["title"]
genius_title_featured = results[i]["result"]["title_with_featured"] genius_title_featured = results[i]["result"]["title_with_featured"]
if SequenceMatcher(None, title.lower(), genius_title.lower()).ratio() < self.sanity_min_title_ratio: if SequenceMatcher(None, title, genius_title).ratio() < self.sanity_min_title_ratio:
if SequenceMatcher(None, title.lower(), genius_title_featured.lower()).ratio() < self.sanity_min_title_ratio: if SequenceMatcher(None, title, genius_title_featured).ratio() < self.sanity_min_title_ratio:
message += f"Genius result: titles do not match enough: '{title}' and '{genius_title}'/'{genius_title_featured}'\n " message += f"Genius result: titles do not match enough: '{title}' and '{genius_title}'/'{genius_title_featured}'\n "
i += 1 i += 1
continue continue
if SequenceMatcher(None, artist.lower(), genius_artist.lower()).ratio() < self.sanity_min_artist_ratio: if SequenceMatcher(None, artist, genius_artist).ratio() < self.sanity_min_artist_ratio:
if SequenceMatcher(None, artist.lower(), genius_artist_featured.lower()).ratio() < self.sanity_min_artist_ratio: if SequenceMatcher(None, artist, genius_artist_featured).ratio() < self.sanity_min_artist_ratio:
message += f"Genius result: artists do not match enough: '{artist}' and '{genius_artist}'/'{genius_artist_featured}'\n " message += f"Genius result: artists do not match enough: '{artist}' and '{genius_artist}'/'{genius_artist_featured}'\n "
i += 1 i += 1
continue continue
@ -285,10 +280,6 @@ class Nicole:
return (True, lyrics) return (True, lyrics)
def process_dir(self, directory): def process_dir(self, directory):
f"""
Process all files from <directory> having a {Nicole.allowed_extensions} fileextension.
If recursive, call process_dir for subdirectories.
"""
if not path.isabs(directory): if not path.isabs(directory):
directory = path.normpath(getcwd() + "/" + directory) directory = path.normpath(getcwd() + "/" + directory)
if not path.isdir(directory): if not path.isdir(directory):
@ -307,40 +298,28 @@ class Nicole:
extension = path.splitext(entry)[1] extension = path.splitext(entry)[1]
# if sound file with mp3 tags # if sound file with mp3 tags
if extension in Nicole.allowed_extensions: if extension in [".mp3", ".flac"]:
self.process_file(entry) success, message = self.process_file(entry)
# add to history
if self.write_history:
if entry not in self.history:
self.history.append(entry)
if not success:
self.failed.append(entry)
if not self.silent:
if success:
print(f"{entry}")
else:
print(f"{entry}")
print(" " + message)
elif path.isdir(entry) and self.recursive: elif path.isdir(entry) and self.recursive:
self.process_dir(entry) self.process_dir(entry)
def process_file(self, file): def process_file(self, file):
"""
process a file, append to history and print a message (depending on settings)
"""
success, message = self._process_file(file)
# add to history
if self.write_history:
if file not in self.history:
self.history.append(file)
if not success:
self.failed.append(file)
if not self.silent:
if success:
print(f"{file}")
else:
print(f"{file}")
print(" " + message)
# print("History\n", self.history)
# print("Failed\n", self.failed)
def _process_file(self, file):
"""
search for tags, write them to the file (if not dry run) and return wether successful or not and a message
"""
if not path.isabs(file): if not path.isabs(file):
file = path.normpath(getcwd() + "/" + file) file = path.normpath(getcwd() + "/" + file)
if not path.isfile(file): if not path.isfile(file):
@ -428,7 +407,7 @@ class Nicole:
message += lyrics message += lyrics
# if found lyrics # if found lyrics
if success: if success:
if self.dry_run: if self.test_run:
print(f"\n\n{artist} - {title}:\n{lyrics}\n") print(f"\n\n{artist} - {title}:\n{lyrics}\n")
# write to tags # write to tags
else: else:
@ -439,7 +418,11 @@ class Nicole:
audio["LYRICS"] = lyrics audio["LYRICS"] = lyrics
audio.save() audio.save()
else: else:
return (False, f"Could find lyrics but failed to write the tag (unknown audio type: {type(audio)})") return (False, f"Could find lyrics but failed to write the tag.")
# add to history
if self.write_history and file not in self.history:
self.history.append(file)
message += f"Written lyrics from {site} to {artist} - {title}" message += f"Written lyrics from {site} to {artist} - {title}"
return (True, message) return (True, message)
@ -448,41 +431,106 @@ class Nicole:
def main(): def main():
print(f"Nicole version {version}") print("Nicole version 2.0")
parser = argparse.ArgumentParser("nicole") helpstring = """Command line options:
parser.add_argument("--directory", "-d", action="append", help="process directory [directory]") -d [directory] process directory [directory]
parser.add_argument("--file", "-f", action="append", help="process file [file]") -f [file] process file [file]
parser.add_argument("--recursive", "-r", action="store_true", help="go through directories recursively") -r go through directories recursively
parser.add_argument("--silent", action="store_true", help="silent, no command-line output") -s silent, no command-line output
parser.add_argument("--ignore-history", "-i", action="store_true", help="ignore history") -i ignore history
parser.add_argument("--no-history", "-n", action="store_true", help="do not write to history") -n do not write to history
parser.add_argument("--overwrite", "-o", action="store_true", help="overwrite if the file already has lyrics") -o overwrite if the file already has lyrics
parser.add_argument("--dry-run", "-t", action="store_true", help="test, do not write lyrics to file, but print to console") -t test, do not write lyrics to file, but print to console
parser.add_argument("--rm-explicit", action="store_true", help="remove the \"[Explicit]\" lyrics warning from the songs title tag") -h show this
parser.add_argument("--site", "-s", action="store", help="use only [site]: azlyrics or genius", default="all") --rm_explicit remove the "[Explicit]" lyrics warning from the songs title tag
args = parser.parse_args() --site [site] use only [site]: azlyrics or genius
# Visit https://github.com/MatthiasQuintern/nicole for updates and further help.""" Visit https://github.com/MatthiasQuintern/nicole for updates and further help."""
args = []
if len(argv) > 1:
# iterate over argv list and extract the args
i = 1
while i < len(argv):
arg = argv[i]
if arg[0] == "-":
# check if option with arg, if yes add tuple to args
if len(argv) > i + 1 and argv[i+1][0] != "-":
args.append((arg.replace("-", ""), argv[i+1]))
i += 1
elif not "--" in arg:
for char in arg.replace("-", ""):
args.append(char)
else:
args.append(arg.replace("-", ""))
else:
print(f"Invalid or missing argument: '{arg}'")
print(helpstring)
return 1
if args.file is None and args.directory is None: i += 1
parser.error("Either --directory or --file is required")
options = {
"t": False,
"s": False,
"n": True,
"i": False,
"o": False,
"r": False,
"h": False,
"rm_explicit": False,
}
directory = None
file = None
site = "all"
for arg in args:
if type(arg) == tuple:
if arg[0] == "d": directory = arg[1]
elif arg[0] == "f": file = arg[1]
elif arg[0] == "site":
if arg[1] in ["genius", "azlyrics", "all"]: site = arg[1]
else:
print(f"Invalid site: '{arg[1]}'")
elif arg in options.keys():
# flip the bool associated with the char
if options[arg] == False: options[arg] = True
else: options[arg] = False
else:
print(f"Invalid argument: '{arg}'")
print(helpstring)
return 1
# show help
if options["h"]:
print(helpstring)
return 0
# create nicole instance # create nicole instance
nicole = Nicole(test_run=args.dry_run, silent=args.silent, write_history=not args.no_history, ignore_history=args.ignore_history, overwrite_tag=args.overwrite, nicole = Nicole(test_run=options["t"], silent=options["s"], write_history=options["n"], ignore_history=options["i"], overwrite_tag=options["o"], recursive=options["r"], rm_explicit=options["rm_explicit"], lyrics_site=site)
recursive=args.recursive, rm_explicit=args.rm_explicit, lyrics_site=args.site)
if type(args.file) == list: # start with file or directory
for file in args.file: if file:
try: success, message = nicole.process_file(file)
nicole.process_file(file) if not nicole.silent:
except KeyboardInterrupt: if success:
pass print(f"{file}")
if type(args.directory) == list: else:
for directory in args.directory: print(f"{file}")
try: print(" " + message)
nicole.process_dir(directory) elif directory:
except KeyboardInterrupt: try:
pass nicole.process_dir(directory)
except KeyboardInterrupt:
print("")
else:
use_wdir = input("No file or directory given. Use working directory? (y/n): ")
if use_wdir in "yY":
nicole.process_dir(getcwd())
else:
print(helpstring)
if __name__ == "__main__": if __name__ == "__main__":

View File

@ -87,8 +87,6 @@ The dependencies will be automatically installed when using the either of the tw
## Changelog ## Changelog
### 2.0 ### 2.0
- Nicole now supports lyrics from genius! - Nicole now supports lyrics from genius!
- Added man-page
- Added zsh-completion
### 1.1 ### 1.1
- Lyrics are now properly encoded. - Lyrics are now properly encoded.