├── funding.yml ├── requirements.txt ├── modules ├── config.py.example ├── exceptions.py ├── logger.py ├── client.py └── utils.py ├── .gitignore ├── README.md └── bugs.py /funding.yml: -------------------------------------------------------------------------------- 1 | github: Slyyxp 2 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | requests==2.8.1 2 | mutagen==1.45.1 3 | tqdm==4.45.0 4 | -------------------------------------------------------------------------------- /modules/config.py.example: -------------------------------------------------------------------------------- 1 | prefs = { 2 | "log_directory": "logs", 3 | "downloads_directory": "downloads", 4 | "artist_folders": True, 5 | "cover_size": "500", 6 | "cover_name": "cover.jpg", 7 | "lyrics_type": "N", 8 | "include_contributions": False 9 | } 10 | 11 | credentials = { 12 | "username": "", 13 | "password": "", 14 | "user_agent": "Mobile|Bugs|4.11.30|Android|5.1.1|SM-G965N|samsung|market", 15 | "device_id": "gwAHWlkOYX_T8Sl43N78GiaD6Sg_" 16 | } 17 | -------------------------------------------------------------------------------- /modules/exceptions.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | logger_exceptions = logging.getLogger("Exceptions") 4 | 5 | class AuthenticationError(Exception): 6 | def __init__(self, message): 7 | logger_exceptions.debug(message) 8 | super().__init__(message) 9 | 10 | class InvalidMapType(Exception): 11 | def __init__(self, message): 12 | logger_exceptions.debug(message) 13 | super().__init__(message) 14 | 15 | class MapFailure(Exception): 16 | def __init__(self, message): 17 | logger_exceptions.debug(message) 18 | super().__init__(message) -------------------------------------------------------------------------------- /modules/logger.py: -------------------------------------------------------------------------------- 1 | import os 2 | import logging 3 | from datetime import datetime 4 | from modules.utils import make_dir 5 | from modules.config import prefs 6 | 7 | 8 | def log_setup(): 9 | filename = '{:%H.%M.%S}.log'.format(datetime.now()) 10 | folder_name = os.path.join(prefs['log_directory'], '{:%Y-%m-%d}'.format(datetime.now())) 11 | make_dir(folder_name) 12 | log_path = os.path.join(folder_name, filename) 13 | logging.basicConfig(level=logging.DEBUG, 14 | handlers=[logging.FileHandler(log_path, 'w', 'utf-8')], 15 | format='%(asctime)s %(name)-12s %(levelname)-8s %(message)s', 16 | datefmt='%Y-%m-%d %H:%M:%S') 17 | console = logging.StreamHandler() 18 | console.setLevel(logging.INFO) 19 | formatter = logging.Formatter('%(name)-12s: %(levelname)-8s %(message)s') 20 | console.setFormatter(formatter) 21 | logging.getLogger("").addHandler(console) 22 | # Suppress requests module if level < WARNING 23 | logging.getLogger("requests").setLevel(logging.WARNING) -------------------------------------------------------------------------------- /modules/client.py: -------------------------------------------------------------------------------- 1 | # Standard 2 | import requests 3 | import logging 4 | 5 | # BugsPy 6 | from modules import config, exceptions 7 | 8 | logger = logging.getLogger("Client") 9 | 10 | class Client: 11 | def __init__(self): 12 | self.session = requests.Session() 13 | self.cfg = config.credentials 14 | self.api_key = "b2de0fbe3380408bace96a5d1a76f800" 15 | self.session.headers.update({ 16 | "User-Agent": self.cfg['user_agent'], 17 | "Host": "api.bugs.co.kr", 18 | }) 19 | 20 | def auth(self): 21 | data = { 22 | "device_id": self.cfg['device_id'], 23 | "passwd": self.cfg['password'], 24 | "userid": self.cfg['username'] 25 | } 26 | r = self.make_call("secure", "mbugs/3/login?", data=data) 27 | if r['ret_code'] == 300: 28 | raise exceptions.AuthenticationError("Invalid Credentials") 29 | else: 30 | logger.info("Login Successful") 31 | self.nickname = r['result']['extra_data']['nickname'] 32 | self.connection_info = r['result']['coninfo'] 33 | # Log non-sensitive authentication information for debugging purposes. 34 | logger.debug("Subscription Plan: {}".format(r['result']['right']['product']['name'])) 35 | logger.debug("Stream: {}".format(r['result']['right']['stream'])) 36 | return self.connection_info 37 | 38 | def get_api_key(self): 39 | return self.api_key 40 | 41 | def make_call(self, sub, epoint, data=None, json=None, params=None): 42 | r = self.session.post("https://{}.bugs.co.kr/{}api_key={}".format(sub, epoint, self.api_key), json=json, data=data, params=params) 43 | return r.json() 44 | 45 | def get_meta(self, type, id): 46 | if type == "album": 47 | json=[{"id":"album_info","args":{"albumId":id}}, {"id":"artist_role_info","args":{"contentsId":id,"type":"ALBUM"}}] 48 | elif type == "artist": 49 | json=[{"id":"artist_info","args":{"artistId":id}}, {"id":"artist_album","args":{"artistId":id, "albumType":"main","tracksYn":"Y","page":1,"size":500}}] 50 | elif type == "track": 51 | json=[{"id":"track_detail", "args":{"trackId":id}}] 52 | else: 53 | raise exceptions.InvalidMapType("Invalid invokeMap type.") 54 | r = self.make_call("api", "3/home/invokeMap?", json=json) 55 | if r['ret_code'] != 0: 56 | raise exceptions.MapFailure("Failed to get a map.") 57 | return r -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 95 | __pypackages__/ 96 | 97 | # Celery stuff 98 | celerybeat-schedule 99 | celerybeat.pid 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | .env 106 | .venv 107 | env/ 108 | venv/ 109 | ENV/ 110 | env.bak/ 111 | venv.bak/ 112 | 113 | # Spyder project settings 114 | .spyderproject 115 | .spyproject 116 | 117 | # Rope project settings 118 | .ropeproject 119 | 120 | # mkdocs documentation 121 | /site 122 | 123 | # mypy 124 | .mypy_cache/ 125 | .dmypy.json 126 | dmypy.json 127 | 128 | # Pyre type checker 129 | .pyre/ 130 | 131 | # BugsPy Specific 132 | logs/ 133 | modules/config.py 134 | .idea/ -------------------------------------------------------------------------------- /modules/utils.py: -------------------------------------------------------------------------------- 1 | import os 2 | import re 3 | import logging 4 | from datetime import datetime 5 | import platform 6 | from sys import version 7 | from modules import config 8 | 9 | logger_utilities = logging.getLogger("Utilities") 10 | 11 | def make_dir(dir): 12 | if not os.path.isdir(dir): 13 | os.makedirs(dir) 14 | 15 | def get_id(url): 16 | return re.match( 17 | r'https?://music\.bugs\.co\.kr/(?:(?:album|artist|track|playlist)/|[a-z]{2}-[a-z]{2}-?\w+(?:-\w+)*-?)(\w+)', 18 | url).group(1) 19 | 20 | def contribution_check(artist_id_provided, artist_id_api): 21 | if int(artist_id_provided) == int(artist_id_api): 22 | return False 23 | else: 24 | return True 25 | 26 | def sanitize(fn): 27 | """ 28 | :param fn: Filename 29 | :return: Sanitized string 30 | 31 | Removes invalid characters in the filename dependant on Operating System. 32 | """ 33 | if _is_win(): 34 | return re.sub(r'[\/:*?"><|]', '_', fn) 35 | else: 36 | return re.sub('/', '_', fn) 37 | 38 | def determine_quality(track): 39 | if track['svc_flac_yn'] == 'Y': 40 | return 'flac' 41 | else: 42 | return 'mp3' 43 | 44 | def exist_check(abs): 45 | """ 46 | :param abs: Absolute path 47 | :return: If path exists. 48 | """ 49 | if os.path.isfile(abs): 50 | logger_utilities.debug("{} already exists locally.".format(os.path.basename(abs))) 51 | return True 52 | 53 | def _is_win(): 54 | if platform.system() == 'Windows': 55 | return True 56 | 57 | def log_system_information(): 58 | logger_utilities.debug("System Information: {}".format(platform.uname())) 59 | logger_utilities.debug("Python Version: {}".format(version)) 60 | logger_utilities.debug("Preferences: {}".format(config.prefs)) 61 | 62 | def organize_meta(album, track, lyrics): 63 | meta = { 64 | "ALBUM": track['album_title'], 65 | "ALBUMARTIST": track['artist_disp_nm'], 66 | "ARTIST": track['artist_disp_nm'], 67 | "TITLE": track['track_title'], 68 | "DISCNUMBER": str(track['disc_id']), 69 | "TRACKNUMBER": str(track['track_no']), 70 | "COMMENT": str(track['track_id']), 71 | "DATE": get_date(track['release_ymd']), 72 | "GENRE": album['list'][0]['album_info']['result']['genre_str'].replace(",", "; "), 73 | "LABEL": '; '.join(str(label['label_nm']) for label in album['list'][0]['album_info']['result']['labels']), 74 | "LYRICS": lyrics 75 | } 76 | return meta 77 | 78 | def get_date(date): 79 | # Bugs sometimes does not include the day on the release date for older albums released on the first day of the month. 80 | # We will append it manuallly before date transformation. 81 | if len(date) == 6: 82 | date = date + "01" 83 | date_patterns = ["%Y%m%d", "%Y%m", "%Y"] 84 | for pattern in date_patterns: 85 | try: 86 | return datetime.strptime(date, pattern).strftime('%Y.%m.%d') 87 | except ValueError: 88 | pass 89 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 |

5 | 6 | 7 | 8 |

9 | 10 | # DEPRECATED 11 | See: https://github.com/Slyyxp/rsack 12 | 13 | ## Overview 14 | **BugsPy** is a tool for downloading streamable tracks from **[Bugs.co.kr](https://music.bugs.co.kr/)** 15 | 16 | Tested on **[Python 3.8.0](https://www.python.org/downloads/release/python-380/)** 17 | 18 | Project structure based on **[GeniePy](https://github.com/Slyyxp/GeniePy)** 19 | 20 | ## Prerequisites 21 | 22 | - Python 3.6+ 23 | - Bugs.co.kr Subscription 24 | 25 | ## Features 26 | 27 | - 16bit Lossless Support 28 | - Artist Batching 29 | - Bugs.co.kr Client 30 | - Extensive Tagging 31 | - Timed Lyrics 32 | 33 | ## Subscribing 34 | ### [Wiki](https://github.com/Slyyxp/BugsPy/wiki/Foreign-Subscriptions.-(iOS)) 35 | 36 | ## Installation & Setup 37 | 38 | ```console 39 | $ git clone https://github.com/Slyyxp/BugsPy.git 40 | $ cd BugsPy 41 | $ pip install -r requirements.txt 42 | ``` 43 | 44 | * Insert username and password into config.py.example 45 | * Optionally add the device id & user agent of your own android device 46 | * Rename config.py.example to config.py 47 | 48 | ## Command Usage 49 | ``` 50 | python bugs.py -u {album_url} 51 | ``` 52 | Command | Description | Example 53 | ------------- | ------------- | ------------- 54 | -u | Bugs Url | `https://music.bugs.co.kr/album/20343816`, `https://music.bugs.co.kr/artist/80327433` 55 | -t | Text File Batch Using File Location | `C:/NotPorn/batch.txt` 56 | 57 | ## config.py 58 | 59 | **credentials:** 60 | 61 | Config | Description | Example 62 | ------------- | ------------- | ------------- 63 | username | Bugs Email | `Slyyxp@domain.com` 64 | password |Bugs Password | `ReallyBadPassword123` 65 | device_id | Android Device ID | `eb9d53a3c424f961` 66 | user_agent | User Agent | `Mobile/Bugs/4.11.30/Android/5.1.1/SM-G965N/samsung/market` 67 | 68 | **prefs:** 69 | 70 | Config | Description | Example 71 | ------------- | ------------- | ------------- 72 | download_directory | Directory to download files to | `Z:/BugsPy/downloads` 73 | log_directory | Directory to save log files to | `Z:/BugsPy/logs` 74 | artist_folders | Whether or not to nest downloads into artist folders | `True/False` 75 | cover_size | Size of cover art to download + embed | `original`, `200`, `140`, `1000`, `350`, `75`, `500` 76 | cover_name | Name of cover art with jpg extension | `cover.jpg`, `folder.jpg` 77 | lyrics_type | Specify normal or timed lyrics | `N`, `T` 78 | include_contributions | Include contributions/participations in artist batches | `True`, `False` 79 | 80 | ## Disclaimer 81 | - The usage of this script **may be** illegal in your country. It's your own responsibility to inform yourself of Copyright Law. 82 | -------------------------------------------------------------------------------- /bugs.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import logging 3 | import os 4 | from datetime import datetime 5 | import requests 6 | from tqdm import tqdm 7 | from modules import logger, client, utils, config 8 | from mutagen.flac import FLAC, Picture, error 9 | import mutagen.id3 as id3 10 | from mutagen.id3 import ID3NoHeaderError 11 | 12 | 13 | def getargs(): 14 | parser = argparse.ArgumentParser() 15 | parser.add_argument('-u', nargs="*", help="URL.", required=False) 16 | parser.add_argument('-t', nargs="?", help="Location of text file to batch from.", required=False) 17 | return parser.parse_args() 18 | 19 | 20 | def download_track(pre_path, track_id, track_title, track_number): 21 | logger_bugs.info("Track: {}. {}".format(track_number, track_title)) 22 | params = { 23 | "ConnectionInfo": connection_info, 24 | "api_key": api_key, 25 | "overwrite_session": "Y", 26 | "track_id": track_id 27 | } 28 | r = requests.get( 29 | "http://api.bugs.co.kr/3/tracks/{}/listen/android/flac".format(track_id), 30 | params=params, stream=True) 31 | r.raise_for_status() 32 | size = int(r.headers.get('content-length', 0)) 33 | with open(pre_path, 'wb') as f: 34 | with tqdm(total=size, unit='B', 35 | unit_scale=True, unit_divisor=1024, 36 | initial=0, miniters=1) as bar: 37 | for chunk in r.iter_content(32 * 1024): 38 | if chunk: 39 | f.write(chunk) 40 | bar.update(len(chunk)) 41 | 42 | 43 | def track_rip(track_id): 44 | meta = client.get_meta(type="track", id=int(track_id)) 45 | track = meta['list'][0]['track_detail']['result']['track'] 46 | logger_bugs.info("Artist: {} | Track: {}".format(track['artist_nm'], 47 | track['track_title'])) 48 | track_quality = utils.determine_quality(track=track) 49 | album_path = os.path.join(config.prefs['downloads_directory']) 50 | pre_path = os.path.join(album_path, "{}. .BugsPy".format(track['track_no'])) 51 | post_path = os.path.join(album_path,utils.sanitize("{}. {}.{}".format(track['track_no'], track['track_title'], 52 | track_quality))) 53 | if utils.exist_check(post_path): 54 | logger_bugs.info("{} already exists.".format(post_path)) 55 | else: 56 | download_track(pre_path=pre_path, track_id=track['track_id'], track_title=track['track_title'], 57 | track_number=track['track_no']) 58 | os.rename(pre_path, post_path) 59 | # Fetch the album meta of the track, this is required for proper tagging. 60 | meta = client.get_meta(type="album", id=int(track['album_id'])) 61 | # Download album artwork 62 | cover_path = os.path.join(album_path, config.prefs['cover_name']) 63 | try: 64 | download_cover(meta['list'][0]['album_info']['result']['img_urls'], cover_path) 65 | # If we're unable to request from the url we'll set the cover_path to False 66 | except requests.exceptions.HTTPError: 67 | cover_path = False 68 | try: 69 | tag(album=meta, track=track, file_path=post_path, cover_path=cover_path) 70 | # TODO: Come back to this exception and implement a better solution on f_file.save() within tag() 71 | except error: 72 | logger_bugs.warning("_writeblock error, falling back to a smaller cover artwork.") 73 | config.prefs['cover_size'] = "500" 74 | cover_path = os.path.join(album_path, "fallback_cover.jpg") 75 | download_cover(meta['list'][0]['album_info']['result']['img_urls'], cover_path) 76 | tag(album=meta, track=track, file_path=post_path, cover_path=cover_path) 77 | 78 | def artist_rip(artist_id): 79 | meta = client.get_meta(type="artist", id=int(artist_id)) 80 | logger_bugs.info("Artist: {} | Album Count: {}".format(meta['list'][0]['artist_info']['result']['artist_nm'], 81 | len(meta['list'][1]['artist_album']['list']))) 82 | for album in meta['list'][1]['artist_album']['list']: 83 | index = meta['list'][1]['artist_album']['list'].index(album) + 1 84 | logger_bugs.info("Downloading Album #{} of #{}.".format(index, len(meta['list'][1]['artist_album']['list']))) 85 | contribution_status = utils.contribution_check(artist_id, album['artist_id']) 86 | if contribution_status: 87 | if config.prefs['include_contributions']: 88 | album_rip(album['album_id']) 89 | else: 90 | logger_bugs.info("{} has been marked as a contribution and skipped.".format(album['title'].strip())) 91 | else: 92 | album_rip(album['album_id']) 93 | 94 | 95 | def album_rip(album_id): 96 | """ 97 | :param id: String (Album ID) 98 | """ 99 | meta = client.get_meta(type="album", id=int(album_id)) 100 | # Check for multiple artists. 101 | if meta['list'][0]['album_info']['result']['multi_artist_yn'] == "Y": 102 | # Label V.A. if amount of artists > 2 103 | if meta['list'][0]['album_info']['result']['artist_disp_nm'].count(",") > 2: 104 | meta['list'][0]['album_info']['result']['artist_disp_nm'] = "Various Artists" 105 | album_directory_name = utils.sanitize( 106 | "{} - {} [{}]".format(meta['list'][0]['album_info']['result']['artist_disp_nm'], 107 | meta['list'][0]['album_info']['result']['title'].strip(), 108 | utils.get_date(meta['list'][0]['album_info']['result']['release_ymd']))) 109 | # Check for availability. 110 | if meta['list'][0]['album_info']['result']['is_album_str_noright']: 111 | logger_bugs.warning('No streaming rights for {}.'.format(album_directory_name)) 112 | else: 113 | logger_bugs.info("Album: {}.".format(album_directory_name)) 114 | if config.prefs['artist_folders']: 115 | album_path = os.path.join(config.prefs['downloads_directory'], 116 | utils.sanitize(meta['list'][0]['album_info']['result']['artist_disp_nm']), 117 | album_directory_name) 118 | else: 119 | album_path = os.path.join(config.prefs['downloads_directory'], album_directory_name) 120 | utils.make_dir(album_path) 121 | cover_path = os.path.join(album_path, config.prefs['cover_name']) 122 | try: 123 | download_cover(meta['list'][0]['album_info']['result']['img_urls'], cover_path) 124 | # If we're unable to request from the url we'll set the cover_path to False 125 | except requests.exceptions.HTTPError: 126 | cover_path = False 127 | for track in meta['list'][0]['album_info']['result']['tracks']: 128 | # Check for availability. 129 | if not track['track_str_rights']: 130 | logger_bugs.warning('No streaming rights for #{} - {}.'.format(track['track_no'], track['track_title'])) 131 | else: 132 | track_quality = utils.determine_quality(track=track) 133 | pre_path = os.path.join(album_path, "{}. .BugsPy".format(track['track_no'])) 134 | post_path = os.path.join(album_path, 135 | utils.sanitize("{}. {}.{}".format(track['track_no'], track['track_title'], 136 | track_quality))) 137 | if utils.exist_check(post_path): 138 | logger_bugs.info("{} already exists.".format(post_path)) 139 | else: 140 | download_track(pre_path=pre_path, track_id=track['track_id'], track_title=track['track_title'], 141 | track_number=track['track_no']) 142 | os.rename(pre_path, post_path) 143 | try: 144 | tag(album=meta, track=track, file_path=post_path, cover_path=cover_path) 145 | # TODO: Come back to this exception and implement a better solution on f_file.save() within tag() 146 | except error: 147 | logger_bugs.warning("_writeblock error, falling back to a smaller cover artwork.") 148 | config.prefs['cover_size'] = "500" 149 | cover_path = os.path.join(album_path, "fallback_cover.jpg") 150 | download_cover(meta['list'][0]['album_info']['result']['img_urls'], cover_path) 151 | tag(album=meta, track=track, file_path=post_path, cover_path=cover_path) 152 | 153 | 154 | def download_cover(imgs, cover_path): 155 | if utils.exist_check(cover_path): 156 | pass 157 | else: 158 | logger_bugs.info("Downloading cover artwork.") 159 | cover_url = imgs[config.prefs['cover_size']] 160 | r = requests.get(cover_url) 161 | r.raise_for_status() 162 | with open(cover_path, 'wb') as f: 163 | f.write(r.content) 164 | 165 | 166 | def get_lyrics(lyrics_tp, track_id): 167 | if lyrics_tp == "T" and config.prefs['lyrics_type']: 168 | url = 'https://music.bugs.co.kr/player/lyrics/T/{}'.format(str(track_id)) 169 | r = requests.get(url) 170 | lyrics = r.json()['lyrics'].replace("#", "\n") 171 | line_split = (line.split('|') for line in lyrics.splitlines()) 172 | lyrics = ("\n".join( 173 | f'[{datetime.fromtimestamp(round(float(a), 2)).strftime("%M:%S.%f")[0:-4]}]{b}' for a, b in 174 | line_split)) 175 | if lyrics_tp == "N" or config.prefs['lyrics_type'] == "N": 176 | url = 'https://music.bugs.co.kr/player/lyrics/N/{}'.format(str(track_id)) 177 | r = requests.get(url) 178 | lyrics = r.json()['lyrics'] 179 | if lyrics_tp is None: 180 | lyrics = "" 181 | return lyrics 182 | 183 | 184 | def tag(album, track, file_path, cover_path): 185 | lyrics = get_lyrics(track['lyrics_tp'], track['track_id']) 186 | meta = utils.organize_meta(album=album, track=track, lyrics=lyrics) 187 | if str(file_path).endswith('.flac'): 188 | if cover_path: 189 | f_file = FLAC(file_path) 190 | f_image = Picture() 191 | f_image.type = 3 192 | f_image.desc = 'Front Cover' 193 | with open(cover_path, 'rb') as f: 194 | f_image.data = f.read() 195 | f_file.add_picture(f_image) 196 | f_file.save() 197 | f = FLAC(file_path) 198 | logger_bugs.debug("Writing tags to {}".format(os.path.basename(file_path))) 199 | for k, v in meta.items(): 200 | f[k] = str(v) 201 | f.save() 202 | if str(file_path).endswith('.mp3'): 203 | legend = { 204 | "ALBUM": id3.TALB, 205 | "ALBUMARTIST": id3.TPE2, 206 | "ARTIST": id3.TPE1, 207 | "TRACKNUMBER": id3.TRCK, 208 | "DISCNUMBER": id3.TPOS, 209 | "COMMENT": id3.COMM, 210 | "COMPOSER": id3.TCOM, 211 | "COPYRIGHT": id3.TCOP, 212 | "DATE": id3.TDRC, 213 | "GENRE": id3.TCON, 214 | "ISRC": id3.TSRC, 215 | "LABEL": id3.TPUB, 216 | "PERFORMER": id3.TOPE, 217 | "TITLE": id3.TIT2, 218 | "LYRICS": id3.USLT 219 | } 220 | try: 221 | audio = id3.ID3(file_path) 222 | except ID3NoHeaderError: 223 | audio = id3.ID3() 224 | logger_bugs.debug("Writing tags to {}".format(os.path.basename(file_path))) 225 | for k, v in meta.items(): 226 | try: 227 | id3tag = legend[k] 228 | audio[id3tag.__name__] = id3tag(encoding=3, text=v) 229 | except KeyError: 230 | pass 231 | if cover_path: 232 | with open(cover_path, 'rb') as cov_obj: 233 | audio.add(id3.APIC(3, 'image/jpg', 3, '', cov_obj.read())) 234 | audio.save(file_path, 'v2_version=3') 235 | 236 | 237 | def main(): 238 | if args.u: 239 | for url in args.u: 240 | id = utils.get_id(url) 241 | logger_bugs.debug("ID: {}. URL: {}.".format(id, url)) 242 | if "album" in url: 243 | album_rip(album_id=id) 244 | elif "artist" in url: 245 | artist_rip(artist_id=id) 246 | elif "track" in url: 247 | track_rip(track_id=id) 248 | if args.t: 249 | with open(args.t) as tf: 250 | # Get each line from the text file and strip /n 251 | lines = [line.rstrip() for line in tf] 252 | for line in lines: 253 | id = utils.get_id(line) 254 | logger_bugs.info("Processing #{} of #{} (Text File)".format(lines.index(line) + 1, len(lines))) 255 | album_rip(album_id=id) 256 | 257 | 258 | if __name__ == "__main__": 259 | logger.log_setup() 260 | logger_bugs = logging.getLogger("Bugs") 261 | utils.log_system_information() 262 | client = client.Client() 263 | connection_info = client.auth() 264 | api_key = client.get_api_key() 265 | args = getargs() 266 | main() 267 | --------------------------------------------------------------------------------