├── .gitignore
├── LICENSE
├── README.MD
├── _config.yml
├── auth.py
├── requirements.txt
├── run.py
├── search.py
├── songs.py
├── songs
└── PUT_SONGS_HERE
└── update.py
/.gitignore:
--------------------------------------------------------------------------------
1 | songs/*.mp3
2 | songs/*.jpg
3 |
4 | # Byte-compiled / optimized / DLL files
5 | __pycache__/
6 | *.py[cod]
7 | *$py.class
8 |
9 | # C extensions
10 | *.so
11 |
12 | # Distribution / packaging
13 | .Python
14 | env/
15 | build/
16 | develop-eggs/
17 | dist/
18 | downloads/
19 | eggs/
20 | .eggs/
21 | lib/
22 | lib64/
23 | parts/
24 | sdist/
25 | var/
26 | wheels/
27 | *.egg-info/
28 | .installed.cfg
29 | *.egg
30 |
31 | # PyInstaller
32 | # Usually these files are written by a python script from a template
33 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
34 | *.manifest
35 | *.spec
36 |
37 | # Installer logs
38 | pip-log.txt
39 | pip-delete-this-directory.txt
40 |
41 | # Unit test / coverage reports
42 | htmlcov/
43 | .tox/
44 | .coverage
45 | .coverage.*
46 | .cache
47 | nosetests.xml
48 | coverage.xml
49 | *.cover
50 | .hypothesis/
51 |
52 | # Translations
53 | *.mo
54 | *.pot
55 |
56 | # Django stuff:
57 | *.log
58 | local_settings.py
59 |
60 | # Flask stuff:
61 | instance/
62 | .webassets-cache
63 |
64 | # Scrapy stuff:
65 | .scrapy
66 |
67 | # Sphinx documentation
68 | docs/_build/
69 |
70 | # PyBuilder
71 | target/
72 |
73 | # Jupyter Notebook
74 | .ipynb_checkpoints
75 |
76 | # pyenv
77 | .python-version
78 |
79 | # celery beat schedule file
80 | celerybeat-schedule
81 |
82 | # SageMath parsed files
83 | *.sage.py
84 |
85 | # dotenv
86 | .env
87 |
88 | # virtualenv
89 | .venv
90 | venv/
91 | ENV/
92 |
93 | # Spyder project settings
94 | .spyderproject
95 | .spyproject
96 |
97 | # Rope project settings
98 | .ropeproject
99 |
100 | # mkdocs documentation
101 | /site
102 |
103 | # mypy
104 | .mypy_cache/
105 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 Reynard
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.MD:
--------------------------------------------------------------------------------
1 |
2 | # [Deprecated]
3 |
4 |
5 | # WHAT'S THIS?
6 | A project aimng at simplify embedding albumn cover for songs downloaded from XIAMI MUSIC or QQ MUSIC, which do not contain full ID3 informations.
7 |
8 | # COVERS FROM?
9 | For now, all the covers are searched, matched and downloaded from NETEASE MUSIC, coz the author is justing doing something related which provided a kind of convenience.
10 |
11 | # HOW TO USE(Tested on Ubuntu 17.04)?
12 | -- `git clone` this repo
13 | -- Open a new terminal windows in the repo, install requirements using `pip install -Ur requirements.txt`
14 | -- Cpoy all your target songs(only mp3 supported) to the folder `songs`
15 | -- run `python3 run.py`
16 | SPEED UP: Configure your NETEASEMUSIC account at `LINE 17` in auth.py to avoid servers' anti-crawl strategies.
17 |
18 | # NOTICE
19 | -- For now, songs are searched by FILE NAME (could be easily modified by pressing F2)
20 | -- ONLY support mp3
21 | -- ONLY fetch album covers from NETEASE MUSIC
22 |
23 | # KNOWN ISSUES
24 | -- Doesn't support QQMUSIC files' naming pattern
25 | -- Songs not in NETEASEMUSIC library might be matched with wrong cover
26 | -- Might not work on Windows(due to the installation of dependency? not tested)
27 |
28 | # TODO
29 | Nope,really...
30 |
--------------------------------------------------------------------------------
/_config.yml:
--------------------------------------------------------------------------------
1 | theme: jekyll-theme-cayman
--------------------------------------------------------------------------------
/auth.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | # -*- coding: utf-8 -*-
3 | #
4 | import requests
5 | import json
6 | import re
7 | import time
8 | import os
9 | import base64
10 | import binascii
11 | from bs4 import BeautifulSoup
12 | from Crypto.Cipher import AES
13 | from pprint import pprint
14 |
15 | # 验证信息
16 | text = {
17 | 'username': '',
18 | 'password': '',
19 | 'rememberLogin': 'true'
20 | }
21 |
22 | # def loadName():
23 | # global BASE_URL, headers
24 | BASE_URL = 'http://music.163.com'
25 | # 定义请求头
26 | headers = {
27 | 'Refer': 'http://music.163.com',
28 | 'Host': 'music.163.com',
29 | 'User-Agent': 'android',
30 | 'Accept-Encoding': 'gzip',
31 | 'Cookie': 'appver=4.1.3; os=android; osver=7.1.1; mobilename=ONEPLUS3010; resolution=1920x1080; channle=netease',
32 | }
33 |
34 | # Default_Header = {
35 | # 'Referer': 'http://music.163.com/',
36 | # 'Host': 'music.163.com',
37 | # 'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64; rv:38.0) Gecko/20100101 Firefox/38.0 Iceweasel/38.3.0',
38 | # 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
39 | # 'Accept-Encoding': 'gzip, deflate'
40 | # }
41 |
42 |
43 | # 加密信息
44 | modulus = '00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b3ece0462db0a22b8e7'
45 | nonce = '0CoJUm6Qyw8W8jud'
46 | pubKey = '010001'
47 |
48 | # 函数部分
49 |
50 |
51 | def aesEncrypt(text, secKey):
52 | pad = 16 - len(text) % 16
53 | text = text + pad * chr(pad)
54 | encryptor = AES.new(secKey, 2, '0102030405060708')
55 | ciphertext = encryptor.encrypt(text)
56 | ciphertext = base64.b64encode(ciphertext)
57 | return ciphertext.decode()
58 |
59 |
60 | def rsaEncrypt(text, pubKey, modulus):
61 | text = text[::-1].encode()
62 | rs = int(binascii.hexlify(text), 16)**int(pubKey, 16) % int(modulus, 16)
63 | return format(rs, 'x').zfill(256)
64 |
65 |
66 | def createSecretKey(size):
67 | return ''.join([hex(x)[2:] for x in os.urandom(size)])[0:16]
68 |
69 |
70 | text = json.dumps(text)
71 | secKey = createSecretKey(16)
72 | encText = aesEncrypt(aesEncrypt(text, nonce), secKey)
73 | encSecKey = rsaEncrypt(secKey, pubKey, modulus)
74 | data = {
75 | 'params': encText,
76 | 'encSecKey': encSecKey
77 | }
78 |
79 | _session = requests.session()
80 | _session.headers.update(headers)
81 |
82 |
83 | if __name__ == '__main__':
84 |
85 | # TESTING!!!!!!!!!!!!111
86 | print('TESTING!!!!!!!!!!!!!!!!!!!!!!')
87 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | requests==2.20.0
2 | pycrypto==2.6.1
3 | Pillow==6.2.0
4 | beautifulsoup4==4.6.0
5 | mutagen==1.38
6 |
--------------------------------------------------------------------------------
/run.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | # -*- coding: utf-8 -*-
3 | #
4 | import songs
5 | import search
6 | import update
7 |
8 |
9 | def main(dir='./songs'):
10 | file_list = songs.list_dir(dir)
11 | for i in file_list:
12 | url = search.get_cover(i)
13 | file_path = './songs/' + str(i) + '.mp3'
14 | cover_path = file_path + '.jpg'
15 | try:
16 | # print(file_path)
17 | # update.update_cover(file_path, url)
18 | update.download_cover(file_path, url)
19 | update.resize_img(cover_path)
20 | update.add_metadata_to_song(file_path, cover_path)
21 | print(str(i), 'Success!')
22 | except Exception as e:
23 | print(e)
24 |
25 | if __name__ == '__main__':
26 | try:
27 | main()
28 | finally:
29 | print('\nDone!')
30 |
--------------------------------------------------------------------------------
/search.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | # -*- coding: utf8 -*-
3 | import requests
4 | import auth
5 | import json
6 |
7 |
8 | def replace_name(music_name):
9 | music_name = music_name.split('[')[0].split(
10 | '(')[0].replace(' - ', ' ').replace('_', ' ')
11 | # print(music_name)
12 | return(music_name)
13 |
14 |
15 | def get_cover(music_name):
16 | music_name = replace_name(music_name)
17 | url = auth.BASE_URL + '/api/search/get'
18 | data = {
19 | 's': music_name,
20 | 'type': 10,
21 | 'offset': 0,
22 | 'total': 'true',
23 | # 'limit': limit
24 | }
25 | req = requests.post(url, headers=auth.headers, data=data)
26 | cover_url = req.json()['result']['albums'][0]['blurPicUrl']
27 | cover_url.replace(' ', '')
28 | # print(cover_url)
29 | return(cover_url)
30 |
31 |
32 | if __name__ == '__main__':
33 |
34 | music_name = 'something just like this'
35 | print(get_cover(music_name))
36 |
37 | # TESTING!!!!!!!!!!!!111
38 | print('TESTING!!!!!!!!!!!!!!!!!!!!!!')
39 |
--------------------------------------------------------------------------------
/songs.py:
--------------------------------------------------------------------------------
1 | import os
2 |
3 |
4 | def list_dir(path="./songs"):
5 |
6 | file_list = []
7 | files = os.listdir(path) # 得到文件夹下的所有文件名称
8 | for file in files: # 遍历文件夹
9 | if not os.path.isdir(file): # 判断是否是文件夹,不是文件夹才打开
10 | if os.path.splitext(file)[1] == '.mp3':
11 | file_name = os.path.splitext(file)[0] # filenames
12 | # file_name = os.path.join(path, file) #filepaths&filenames
13 | file_list.append(file_name)
14 | else:
15 | pass
16 | print(len(file_list), 'songs founded, they are: ', file_list, '\n')
17 | return(file_list)
18 |
19 | if __name__ == '__main__':
20 | list_dir()
21 |
22 | # TESTING!!!!!!!!!!!!111
23 | print('TESTING!!!!!!!!!!!!!!!!!!!!!!')
24 |
--------------------------------------------------------------------------------
/songs/PUT_SONGS_HERE:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reycn/cover4mp3/ebab8e07b59ccca6a24a6f70084cf8dd57b5868b/songs/PUT_SONGS_HERE
--------------------------------------------------------------------------------
/update.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 |
4 | import os
5 |
6 | import requests
7 |
8 |
9 | from mutagen.mp3 import MP3, HeaderNotFoundError
10 | from mutagen.id3 import ID3, APIC, TPE1, TIT2, TALB, error
11 | from PIL import Image
12 |
13 |
14 | def resize_img(file_path, max_size=(640, 640), quality=90):
15 | try:
16 | img = Image.open(file_path)
17 | except IOError:
18 | print('Can\'t open image:', file_path)
19 | return
20 |
21 | if img.size[0] > max_size[0] or img.size[1] > max_size[1]:
22 | img.thumbnail(max_size, Image.ANTIALIAS)
23 | img.save(file_path, quality=quality)
24 |
25 | # def update_cover(mp3_name, pic_url):
26 |
27 |
28 | def add_metadata_to_song(file_path, cover_path):
29 | # If no ID3 tags in mp3 file
30 | try:
31 | audio = MP3(file_path, ID3=ID3)
32 | except HeaderNotFoundError:
33 | print('Can\'t sync to MPEG frame, not an validate MP3 file!')
34 | return
35 |
36 | if audio.tags is None:
37 | print('No ID3 tag, trying to add one!')
38 | try:
39 | audio.add_tags()
40 | audio.save()
41 | except error as e:
42 | print('Error occur when add tags:', str(e))
43 | return
44 |
45 | # Modify ID3 tags
46 | id3 = ID3(file_path)
47 | # Remove old 'APIC' frame
48 | # Because two 'APIC' may exist together with the different description
49 | # For more information visit:
50 | # http://mutagen.readthedocs.io/en/latest/user/id3.html
51 | if id3.getall('APIC'):
52 | id3.delall('APIC')
53 | # add album cover
54 | id3.add(
55 | APIC(
56 | # 3 is for UTF8, but here we use 0 (LATIN1) for 163, orz~~~
57 | encoding=0,
58 | mime='image/jpeg', # image/jpeg or image/png
59 | type=3, # 3 is for the cover(front) image
60 | data=open(cover_path, 'rb').read()
61 | )
62 | )
63 | id3.save(v2_version=3)
64 |
65 |
66 | def download_cover(mp3_name, pic_url):
67 | # edit the ID3 tag to add the title, artist, artwork, date, and genre
68 | req = requests.get(pic_url)
69 | # imagedata = Image.open(BytesIO(req.content))
70 | # imagedata = open('a.jpg', 'rb').read()
71 | # print(im)
72 | f = open(mp3_name+'.jpg', 'wb')
73 | f.write(requests.get(pic_url).content)
74 | f.close()
75 | # print(mp3_name+'.jpg')
76 | return(mp3_name+'.jpg')
77 |
78 | if __name__ == '__main__':
79 | # TESTING!!!!!!!!!!!!111
80 | print('TESTING!!!!!!!!!!!!!!!!!!!!!!')
81 | dir_path = './songs/'
82 | mp3_name = '凡人歌_李宗盛'
83 | mp3_path = dir_path + mp3_name + '.mp3'
84 |
85 | download_cover(mp3_path,
86 | 'http://p3.music.126.net/ggnyubDdMxrhpqYvpZbhEQ==/3302932937412681.jpg')
87 | resize_img(mp3_path + '.jpg')
88 | add_metadata_to_song(mp3_path, mp3_path + '.jpg')
89 |
--------------------------------------------------------------------------------