Skip to content

Instantly share code, notes, and snippets.

@0x1d107
Last active February 27, 2025 17:17
Show Gist options
  • Select an option

  • Save 0x1d107/1eecf9c89a0999f18689c85a6e210f82 to your computer and use it in GitHub Desktop.

Select an option

Save 0x1d107/1eecf9c89a0999f18689c85a6e210f82 to your computer and use it in GitHub Desktop.
MTS music downloader. Hashing algorithm was taken from https://github.com/llistochek/yandex-music-downloader
#!/bin/env python
import lxml.html as lh
import urllib.request as rq
import json
import hashlib
from tqdm import tqdm
import sys, os
MTS_MUSIC = "https://music.mts.ru"
"""
How to get ya_token:
1. go to https://music.mts.ru
2. log in to your account with premium
3. open DevTools(ctrl+shift+I)
4. at Storage tab look for Local Storage data for https://music.mts.ru
5. copy string with key ya_token
6. paste it to ya_token.txt
"""
YA_TOKEN = None
try:
with open('ya_token.txt') as f:
YA_TOKEN = f.readline().strip()
except OSError:
print("ya_token.txt not found. Defaulting to preview download")
MD5_SALT = 'XGRlBW9FXlekgbPrRHuSiA'
class TqdmUpTo(tqdm):
"""Alternative Class-based version of the above.
Provides `update_to(n)` which uses `tqdm.update(delta_n)`.
Inspired by [twine#242](https://github.com/pypa/twine/pull/242),
[here](https://github.com/pypa/twine/commit/42e55e06).
"""
def update_to(self, b=1, bsize=1, tsize=None):
"""
b : int, optional
Number of blocks transferred so far [default: 1].
bsize : int, optional
Size of each block (in tqdm units) [default: 1].
tsize : int, optional
Total size (in tqdm units). If [default: None] remains unchanged.
"""
if tsize is not None:
self.total = tsize
return self.update(b * bsize - self.n) # also sets self.n = b * bsize
def download_file(url, filename):
with TqdmUpTo(unit='B', unit_scale=True, unit_divisor=1024, miniters=1,
desc=filename) as t:
rq.urlretrieve(url, filename, reporthook=t.update_to, data=None)
t.total = t.n
def get_album_info(album_id):
tree = lh.parse(rq.urlopen(MTS_MUSIC+f'/album/{album_id}'))
return json.loads(tree.xpath('//script/text()')[0])['props']['pageProps'][
'albumItem']
def get_download_info(track_id, oauth_token):
req = rq.Request(f"{MTS_MUSIC}/ya_api/tracks/{track_id}/download-info")
if oauth_token:
req.add_header("Authorization", f"OAuth {oauth_token}")
dlinfo = json.load(rq.urlopen(req))
return dlinfo['result']
def get_file_download_Link(dlinfo):
url_info = json.load(rq.urlopen(dlinfo+"&format=json"))
url_info['hash'] = hashlib.md5((MD5_SALT+url_info['path'][1:]+url_info['s']
).encode()).hexdigest()
return "https://{host}/get-mp3/{hash}/{ts}{path}".format_map(url_info)
album_id = sys.argv[1] if len(sys.argv) > 1 else "5899311"
album_info = get_album_info(album_id)
tracks = album_info['tracks']
print(album_info)
album_dir = album_info['title']
os.makedirs(album_dir, exist_ok=True)
coverfname = f"{album_dir}/cover.jpg"
download_file(album_info['cover'], coverfname)
coverdata = None
with open(coverfname, 'rb') as f:
coverdata = f.read()
for i, trk in enumerate(tracks):
trkid = trk['id']
trkname = ",".join(a['name'] for a in trk['artists'])+' - '+trk['title']
dlinfos = get_download_info(trkid, YA_TOKEN)
best_info = max(dlinfos,
key=lambda x: x['bitrateInKbps'])
trkformat = best_info['codec']
best_link = best_info['downloadInfoUrl']
filename = f"{album_dir}/{i:02d}. {trkname}.{trkformat}"
download_file(get_file_download_Link(best_link), filename)
try:
import eyed3
file = eyed3.load(filename)
file.initTag()
file.tag.album = album_info['title']
file.tag.artist = ",".join(a['name'] for a in trk['artists'])
file.tag.album_artist = ",".join(a['name']
for a in album_info['artists'])
file.tag.title = trk['title']
file.tag.track_num = i+1
file.tag.images.set(eyed3.id3.frames.ImageFrame.FRONT_COVER, coverdata, 'image/jpeg')
file.tag.save()
except ImportError:
print("eyeD3 python module not found: ID3 tags are not set")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment