├── 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 |
--------------------------------------------------------------------------------