Compare commits

..

No commits in common. "474c24e05affd6452435c471e60c9f1091c12b5a" and "44fc039ac9e1f3b397e6e89fbdcbb28df1c10e58" have entirely different histories.

8 changed files with 14 additions and 122 deletions

View file

@ -13,6 +13,10 @@ class Config:
# Cookies are in Netscape CSV format, see yt-dlp docs
self.cookies_dir = Path(os.getenv('COOKIES_DIR') or 'cookies')
# Note: yt-dlp's path trimmer also counts album_path_tmpl, not only filename
# Why 235? 255 is the ext4 limit. 255 - len("/var/lib/musicdlp/") = 237, rounded down to 235
self.path_length = int(os.getenv('PATH_LENGTH') or 235)
self.tmpl = os.path.join(
# `artists.0` instead of `artist`, because the latter can contain "feat. ..."
os.getenv('ALBUM_PATH_TMPL') or 'music/%(artists.0)s/%(album)s',

View file

@ -16,16 +16,16 @@ lyrics_xpath = etree.XPath('//div[@id="lyrics-root"][1]/div[@data-lyrics-contain
br_xpath = etree.XPath('.//br')
def search(title: str, artist: str) -> tuple[str, str]:
'''Searches for Genius lyrics using SearXNG + Yahoo
and returns the first result as tuple(title, url)'''
def search(title: str, artist: str) -> str:
'''Searches for Genius lyrics using SearXNG + Yahoo and returns the first URL.
Irrelevant texts should be picked manually'''
resp = http_pool.get().request(
'GET',
'https://searx.dc09.ru/search',
fields={
'q': artist + ' ' + title + ' site:genius.com',
'engines': 'brave,yahoo',
'engines': 'brave',
'safesearch': '0',
'format': 'json',
},
@ -34,27 +34,7 @@ def search(title: str, artist: str) -> tuple[str, str]:
result: dict[str, str] = resp.json()['results'][0]
del resp
return (result['title'], result['url'])
def raise_on_irrelevant_result(res_title: str, track_track: str, track_artist: str) -> None:
'''Raises ValueError
if no words from track title are present in search result track title
and no words from artist name are present in search result artist name'''
res_artist, res_track = res_title.lower().split(' \u2013 ', maxsplit=1)
if not (
any(
word.group(0).lower() in res_artist
for word in word_regex.finditer(track_artist)
)
and
any(
word.group(0).lower() in res_track
for word in word_regex.finditer(track_track)
)
):
raise ValueError
return result['url']
def parse(url: str) -> str:

View file

@ -63,8 +63,7 @@ class ID3TagsPP(PostProcessor):
file['TCON'] = id3.TCON(encoding=ENC_UTF8, text=information['genre'])
try:
lyr_title, lyr_url = genius.search(title, artists[0])
genius.raise_on_irrelevant_result(lyr_title, title, artists[0])
lyr_url = genius.search(title, artists[0])
file['USLT'] = id3.USLT(encoding=ENC_UTF8, text=genius.parse(lyr_url))
except:
pass

View file

@ -23,45 +23,14 @@ LYR3 = '''you are gonna get yours
Another day'''
# There is no lyrics for this song on Genius
# Maybe someday TITLE2 and ARTIST2 will need to be changed
# (But really existing song is chosen intentionally)
TITLE2 = 'Паруса'
ARTIST2 = 'PIZZA'
class TestGenius(TestCase):
def setUp(self) -> None:
http_pool.get()
def test_search(self) -> None:
_, url = genius.search(TITLE, ARTIST)
self.assertEqual(url, URL)
def test_search_success(self) -> None:
title, _ = genius.search(TITLE, ARTIST)
genius.raise_on_irrelevant_result(title, TITLE, ARTIST)
def test_search_failure(self) -> None:
title, _ = genius.search(TITLE2, ARTIST2)
with self.assertRaises(ValueError):
genius.raise_on_irrelevant_result(title, TITLE2, ARTIST2)
def test_relevancy_success(self) -> None:
genius.raise_on_irrelevant_result(
'ABC hEllo world!@ \u2013 sOmE artist123',
'Artist123',
'hello World',
)
def test_relevancy_failure(self) -> None:
with self.assertRaises(ValueError):
genius.raise_on_irrelevant_result(
'DEF hEllo world@!15 \u2013 anOther artist456',
'DEF 789',
'ABC irrelevant track title',
)
url = genius.search(TITLE, ARTIST)
self.assertEqual(url, URL)
def test_lyrics_parsing(self) -> None:
lyrics = genius.parse(URL)

View file

@ -63,6 +63,7 @@ class Downloader:
if ydl is None:
ydl = create_ydl_fn[site]()
ydl.params['trim_file_name'] = cfg.path_length # Note: not only filename, but path in outtmpl
ydl.params['outtmpl']['default'] = cfg.tmpl
ydl.add_post_processor(id3pp.ID3TagsPP(), when='post_process')

View file

@ -1,33 +1 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>musicdlp</title>
<link rel="stylesheet" href="/style.css">
<script src="/script.js"></script>
</head>
<body>
<div>
<input type="text" id="url" placeholder="Playlist or track URL">
<button type="button" id="guess-site-btn">Guess site</button>
</div>
<div>
<select id="site-select">
<option value="youtube" selected>YouTube</option>
<option value="yt_proxied">YT proxied</option>
<option value="yandex">Yandex Music</option>
</select>
</div>
<div>
<button type="button" id="items-btn">Get playlist items</button>
</div>
<div id="items-container"></div>
<div>
<button type="button">Download</button>
</div>
<div>
<label>Progress: <span id="progress">not implemented</span></label>
</div>
</body>
</html>
<!-- TODO: simple web ui, websockets -->

View file

@ -1,18 +0,0 @@
addEventListener('DOMContentLoaded', () => {
/** @type{HTMLInputElement} */
const urlField = document.getElementById('url')
/** @type{HTMLSelectElement} */
const site = document.getElementById('site-select')
document.getElementById('guess-site-btn').addEventListener('click', () => {
const url = urlField.value
if (url.includes('/watch?v=') || url.includes('/playlist?list=')) {
if (site.value == 'yt_proxied') {
return
}
site.value = 'youtube'
} else if (url.includes('://music.yandex.')) {
site.value = 'yandex'
}
})
})

View file

@ -1,11 +0,0 @@
body {
margin: 0;
padding: 0.5rem;
display: flex;
flex-direction: column;
align-items: center;
row-gap: 0.25rem;
font-family: 'Noto Sans', 'Roboto', 'Ubuntu', sans-serif;
}