Merge remote-tracking branch 'upstream/main'

This commit is contained in:
dabreadman 2021-10-27 18:07:15 +00:00
commit a17485e0cf
7 changed files with 114 additions and 35 deletions

View File

@ -16,8 +16,8 @@ jobs:
- name: Setup Pylint - name: Setup Pylint
run: | run: |
python -m pip install --upgrade pip python -m pip install --upgrade pip
pip install pylint pip install pylint pylint-exit
pip install -r requirements.txt pip install -r requirements.txt
- name: Run Pylint - name: Run Pylint
run: | run: |
pylint $(git ls-files '*.py') pylint $(git ls-files '*.py') || pylint-exit $?

View File

@ -1,4 +1,10 @@
## **Changelog:** ## **Changelog:**
**v2.4 (27 Oct 20212):**
- Added realtime downloading support to avoid account suspensions.
- Fix for downloading by artist.
- Replace audio conversion method for better quality.
- Fix bug when automatically setting audio bitrate.
**v2.3 (25 Oct 2021):** **v2.3 (25 Oct 2021):**
- Moved changelog to seperate file. - Moved changelog to seperate file.
- Added argument parsing in search function (query results limit and query result types). - Added argument parsing in search function (query results limit and query result types).

View File

@ -70,8 +70,14 @@ Create and run a container from the image:
### Will my account get banned if I use this tool? ### Will my account get banned if I use this tool?
Currently no user has reported their account getting banned after using ZSpotify. ~~Currently no user has reported their account getting banned after using ZSpotify.~~
This isn't to say _you_ won't get banned as it is technically against Spotify's TOS.
**There have been 2-3 reports from users who received account bans from Spotify for using this tool**.
We recommend using ZSpotify with a burner account.
Alternatively, there is a configuration option labled ```DOWNLOAD_REAL_TIME```, this limits the download speed to the duration of the song being downloaded thus not appearing suspicious to Spotify.
This option is much slower and is only recommended for premium users who wish to download songs in 320kbps without buying premium on a burner account.
**Use ZSpotify at your own risk**, the developers of ZSpotify are not responsible if your account gets banned. **Use ZSpotify at your own risk**, the developers of ZSpotify are not responsible if your account gets banned.
### What do I do if I see "Your session has been terminated"? ### What do I do if I see "Your session has been terminated"?

View File

@ -1,6 +1,8 @@
ffmpy
git+https://github.com/kokarare1212/librespot-python git+https://github.com/kokarare1212/librespot-python
music_tag music_tag
pydub
Pillow Pillow
tqdm protobuf
tabulate pydub
tabulate
tqdm

View File

@ -96,14 +96,39 @@ CHUNK_SIZE = 'CHUNK_SIZE'
SPLIT_ALBUM_DISCS = 'SPLIT_ALBUM_DISCS' SPLIT_ALBUM_DISCS = 'SPLIT_ALBUM_DISCS'
DOWNLOAD_REAL_TIME = 'DOWNLOAD_REAL_TIME'
BITRATE = 'BITRATE'
CODEC_MAP = {
'aac': 'aac',
'fdk_aac': 'libfdk_aac',
'm4a': 'aac',
'mp3': 'libmp3lame',
'ogg': 'copy',
'opus': 'libopus',
'vorbis': 'copy',
}
EXT_MAP = {
'aac': 'm4a',
'fdk_aac': 'm4a',
'm4a': 'm4a',
'mp3': 'mp3',
'ogg': 'ogg',
'opus': 'ogg',
'vorbis': 'ogg',
}
CONFIG_DEFAULT_SETTINGS = { CONFIG_DEFAULT_SETTINGS = {
'ROOT_PATH': '../ZSpotify Music/', 'ROOT_PATH': '../ZSpotify Music/',
'ROOT_PODCAST_PATH': '../ZSpotify Podcasts/', 'ROOT_PODCAST_PATH': '../ZSpotify Podcasts/',
'SKIP_EXISTING_FILES': True, 'SKIP_EXISTING_FILES': True,
'DOWNLOAD_FORMAT': 'mp3', 'DOWNLOAD_FORMAT': 'ogg',
'FORCE_PREMIUM': False, 'FORCE_PREMIUM': False,
'ANTI_BAN_WAIT_TIME': 1, 'ANTI_BAN_WAIT_TIME': 1,
'OVERRIDE_AUTO_WAIT': False, 'OVERRIDE_AUTO_WAIT': False,
'CHUNK_SIZE': 50000, 'CHUNK_SIZE': 50000,
'SPLIT_ALBUM_DISCS': False 'SPLIT_ALBUM_DISCS': False,
'DOWNLOAD_REAL_TIME': False
} }

View File

@ -50,11 +50,15 @@ def download_episode(episode_id) -> None:
episode_id = EpisodeId.from_base62(episode_id) episode_id = EpisodeId.from_base62(episode_id)
stream = ZSpotify.get_content_stream(episode_id, ZSpotify.DOWNLOAD_QUALITY) stream = ZSpotify.get_content_stream(episode_id, ZSpotify.DOWNLOAD_QUALITY)
download_directory = os.path.dirname(__file__) + ZSpotify.get_config(ROOT_PODCAST_PATH) + extra_paths download_directory = os.path.join(
os.path.dirname(__file__),
ZSpotify.get_config(ROOT_PODCAST_PATH),
extra_paths,
)
create_download_directory(download_directory) create_download_directory(download_directory)
total_size = stream.input_stream.size total_size = stream.input_stream.size
with open(download_directory + filename + MusicFormat.OGG.value, with open(os.path.join(download_directory, f"{filename}.ogg"),
'wb') as file, tqdm( 'wb') as file, tqdm(
desc=filename, desc=filename,
total=total_size, total=total_size,
@ -67,4 +71,4 @@ def download_episode(episode_id) -> None:
stream.input_stream.stream().read(ZSpotify.get_config(CHUNK_SIZE)))) stream.input_stream.stream().read(ZSpotify.get_config(CHUNK_SIZE))))
# convert_audio_format(ROOT_PODCAST_PATH + # convert_audio_format(ROOT_PODCAST_PATH +
# extra_paths + filename + '.ogg') # extra_paths + filename + '.ogg')

View File

@ -4,14 +4,14 @@ from typing import Any, Tuple, List
from librespot.audio.decoders import AudioQuality from librespot.audio.decoders import AudioQuality
from librespot.metadata import TrackId from librespot.metadata import TrackId
from ffmpy import FFmpeg
from pydub import AudioSegment from pydub import AudioSegment
from tqdm import tqdm from tqdm import tqdm
from const import TRACKS, ALBUM, NAME, ITEMS, DISC_NUMBER, TRACK_NUMBER, IS_PLAYABLE, ARTISTS, IMAGES, URL, \ from const import TRACKS, ALBUM, NAME, ITEMS, DISC_NUMBER, TRACK_NUMBER, IS_PLAYABLE, ARTISTS, IMAGES, URL, \
RELEASE_DATE, ID, TRACKS_URL, SAVED_TRACKS_URL, SPLIT_ALBUM_DISCS, ROOT_PATH, DOWNLOAD_FORMAT, CHUNK_SIZE, \ RELEASE_DATE, ID, TRACKS_URL, SAVED_TRACKS_URL, SPLIT_ALBUM_DISCS, ROOT_PATH, DOWNLOAD_FORMAT, CHUNK_SIZE, \
SKIP_EXISTING_FILES, ANTI_BAN_WAIT_TIME, OVERRIDE_AUTO_WAIT SKIP_EXISTING_FILES, ANTI_BAN_WAIT_TIME, OVERRIDE_AUTO_WAIT, BITRATE, CODEC_MAP, EXT_MAP, DOWNLOAD_REAL_TIME
from utils import sanitize_data, set_audio_tags, set_music_thumbnail, create_download_directory, \ from utils import sanitize_data, set_audio_tags, set_music_thumbnail, create_download_directory
MusicFormat
from zspotify import ZSpotify from zspotify import ZSpotify
@ -72,7 +72,7 @@ def download_track(track_id: str, extra_paths='', prefix=False, prefix_value='',
) else f'{prefix_value} - {song_name}' ) else f'{prefix_value} - {song_name}'
filename = os.path.join( filename = os.path.join(
download_directory, f'{song_name}.{ZSpotify.get_config(DOWNLOAD_FORMAT)}') download_directory, f'{song_name}.{EXT_MAP.get(ZSpotify.get_config(DOWNLOAD_FORMAT))}')
except Exception as e: except Exception as e:
print('### SKIPPING SONG - FAILED TO QUERY METADATA ###') print('### SKIPPING SONG - FAILED TO QUERY METADATA ###')
@ -81,11 +81,11 @@ def download_track(track_id: str, extra_paths='', prefix=False, prefix_value='',
try: try:
if not is_playable: if not is_playable:
print('\n### SKIPPING:', song_name, print('\n### SKIPPING:', song_name,
'(SONG IS UNAVAILABLE) ###') '(SONG IS UNAVAILABLE) ###')
else: else:
if os.path.isfile(filename) and os.path.getsize(filename) and ZSpotify.get_config(SKIP_EXISTING_FILES): if os.path.isfile(filename) and os.path.getsize(filename) and ZSpotify.get_config(SKIP_EXISTING_FILES):
print('\n### SKIPPING:', song_name, print('\n### SKIPPING:', song_name,
'(SONG ALREADY EXISTS) ###') '(SONG ALREADY EXISTS) ###')
else: else:
if track_id != scraped_song_id: if track_id != scraped_song_id:
track_id = scraped_song_id track_id = scraped_song_id
@ -103,15 +103,21 @@ def download_track(track_id: str, extra_paths='', prefix=False, prefix_value='',
unit_divisor=1024, unit_divisor=1024,
disable=disable_progressbar disable=disable_progressbar
) as p_bar: ) as p_bar:
for _ in range(int(total_size / ZSpotify.get_config(CHUNK_SIZE)) + 1): for chunk in range(int(total_size / ZSpotify.get_config(CHUNK_SIZE)) + 1):
p_bar.update(file.write( data = stream.input_stream.stream().read(ZSpotify.get_config(CHUNK_SIZE))
stream.input_stream.stream().read(ZSpotify.get_config(CHUNK_SIZE)))) if data == b'':
break
p_bar.update(file.write(data))
if ZSpotify.get_config(DOWNLOAD_REAL_TIME):
if chunk == 0:
pause = get_segment_duration(p_bar)
if pause:
time.sleep(pause)
if ZSpotify.get_config(DOWNLOAD_FORMAT) == 'mp3': convert_audio_format(filename)
convert_audio_format(filename) set_audio_tags(filename, artists, name, album_name,
set_audio_tags(filename, artists, name, album_name, release_year, disc_number, track_number)
release_year, disc_number, track_number) set_music_thumbnail(filename, image_url)
set_music_thumbnail(filename, image_url)
if not ZSpotify.get_config(OVERRIDE_AUTO_WAIT): if not ZSpotify.get_config(OVERRIDE_AUTO_WAIT):
time.sleep(ZSpotify.get_config(ANTI_BAN_WAIT_TIME)) time.sleep(ZSpotify.get_config(ANTI_BAN_WAIT_TIME))
@ -123,14 +129,44 @@ def download_track(track_id: str, extra_paths='', prefix=False, prefix_value='',
os.remove(filename) os.remove(filename)
def get_segment_duration(segment):
""" Returns playback duration of given audio segment """
sound = AudioSegment(
data = segment,
sample_width = 2,
frame_rate = 44100,
channels = 2
)
duration = len(sound) / 5000
return duration
def convert_audio_format(filename) -> None: def convert_audio_format(filename) -> None:
""" Converts raw audio into playable mp3 """ """ Converts raw audio into playable file """
# print('### CONVERTING TO ' + MUSIC_FORMAT.upper() + ' ###') temp_filename = f'{os.path.splitext(filename)[0]}.tmp'
raw_audio = AudioSegment.from_file(filename, format=MusicFormat.OGG.value, os.replace(filename, temp_filename)
frame_rate=44100, channels=2, sample_width=2)
if ZSpotify.DOWNLOAD_QUALITY == AudioQuality.VERY_HIGH: download_format = ZSpotify.get_config(DOWNLOAD_FORMAT)
bitrate = '320k' file_codec = CODEC_MAP.get(download_format, "copy")
if file_codec != 'copy':
bitrate = ZSpotify.get_config(BITRATE)
if not bitrate:
if ZSpotify.DOWNLOAD_QUALITY == AudioQuality.VERY_HIGH:
bitrate = '320k'
else:
bitrate = '160k'
else: else:
bitrate = '160k' bitrate = None
raw_audio.export(filename, format=ZSpotify.get_config(
DOWNLOAD_FORMAT), bitrate=bitrate) output_params = ['-c:a', file_codec]
if bitrate:
output_params += ['-b:a', bitrate]
ff_m = FFmpeg(
global_options=['-y', '-hide_banner', '-loglevel error'],
inputs={temp_filename: None},
outputs={filename: output_params}
)
ff_m.run()
if os.path.exists(temp_filename):
os.remove(temp_filename)