Skip to content

Commit

Permalink
Version 1.6.8.6
Browse files Browse the repository at this point in the history
cli : added options to sort & limit the no. of tracks to be downloaded (#33)
* The sorting can be base on either `hot` (i.e. popularity) and `time` (i.e.chronologicaly)
* The options are accessible via command line options `-n/--count` `--sort-by` `--reverse-sort`
apis/artist : added `GetArtistDetails`
apis/artist : renamed from `apis/artists`
  • Loading branch information
mos9527 committed Feb 24, 2023
1 parent 0fb5674 commit b6ba051
Show file tree
Hide file tree
Showing 6 changed files with 99 additions and 17 deletions.
14 changes: 10 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,19 +14,19 @@
positional arguments:
链接 网易云音乐分享链接

optional arguments:
options:
-h, --help show this help message and exit

下载:
--max-workers 最多同时下载任务数, -m 最多同时下载任务数
--output-name 保存文件名模板, --template 保存文件名模板, -t 保存文件名模板
保存文件名模板
参数:
参数:
id - 网易云音乐资源 ID
year - 出版年份
no - 专辑中编号
album - 专辑标题
track - 单曲标题
track - 单曲标题
title - 完整标题
artists- 艺术家名
例:
Expand All @@ -35,7 +35,7 @@
注:该参数也可使用模板,格式同 保存文件名模板
--quality 音质 音频音质(高音质需要 CVIP)
参数:
hi-res - Hi-Res
hires - Hi-Res
lossless- “无损”
exhigh - 较高
standard- 标准
Expand All @@ -61,6 +61,12 @@
--log-level LOG_LEVEL
日志等级

限量及过滤(注:只适用于*每单个*链接 / ID:
-n 下载总量, --count 下载总量
限制下载歌曲总量,n=0即不限制
--sort-by 歌曲排序 【限制总量时】歌曲排序方式 (hot: 热度高在前 time:发行时间新在前)
--reverse-sort 【限制总量时】倒序排序歌曲

## 环境变量
|变量名|说明|
|-|-|
Expand Down
2 changes: 1 addition & 1 deletion pyncm/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
# 注意事项
- (PR#11) 海外用户可能经历 460 "Cheating" 问题,可通过添加以下 Header 解决: `X-Real-IP = 118.88.88.88`
"""
__version__ = "1.6.8.5"
__version__ = "1.6.8.6"

from threading import current_thread
from typing import Text, Union
Expand Down
51 changes: 41 additions & 10 deletions pyncm/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@
__version__
)
from pyncm.utils.lrcparser import LrcParser
from pyncm.utils.helper import TrackHelper, FuzzyPathHelper, SubstituteWithFullwidth
from pyncm.apis import login, track, playlist, album

from pyncm.utils.helper import TrackHelper,ArtistHelper, FuzzyPathHelper, SubstituteWithFullwidth
from pyncm.apis import artist, login, track, playlist, album
from queue import Queue
from concurrent.futures import ThreadPoolExecutor
from threading import Thread
from time import sleep
Expand Down Expand Up @@ -247,7 +247,7 @@ class Subroutine:
exceptions = None
def __init__(self, args, put_func) -> None:
self.args = args
self.put = put_func
self.put = put_func
self.exceptions = dict()

def result_exception(self,result_id,exception : Exception,desc=None):
Expand Down Expand Up @@ -289,10 +289,22 @@ class MarkerTask(BaseKeyValueClass):

class Playlist(Subroutine):
"""Base routine for ID-based tasks"""
def filter(self, song_list):
# This is only meant to faciliate sorting w/ APIs that doesn't implement them
# The observed behaviors remains more or less the same with the offical ones
if self.args.count >= 0:
sorting = {
'hot': lambda song : float(song['pop']), # [0,100.0]
'time' : lambda song: TrackHelper(song).Album.AlbumPublishTime # in Years
}[self.args.sort_by]
song_list = sorted(song_list,key=sorting,reverse=not self.args.reverse_sort)
return song_list

def forIds(self, ids):
dDetails = [track.GetTrackDetail(ids[index:min(len(ids),index+1000)]).get("songs") for index in range(0,len(ids),1000)]
dDetails = [song for stripe in dDetails for song in stripe]
dDetails = [song for stripe in dDetails for song in stripe]
dDetails = sorted(dDetails, key=lambda song: song["id"])
dDetails = self.filter(dDetails)
index = 0
for index, dDetail in enumerate(dDetails):
try:
Expand Down Expand Up @@ -364,21 +376,30 @@ def __call__(self, ids):
queued += self.forIds([tid["id"] for tid in dList["songs"]])
return queued

class Artist(Playlist):
def __call__(self, ids):
queued = 0
for _id in ids:
dList = artist.GetArtistTracks(_id,limit=self.args.count,order=self.args.sort_by)
logger.info("艺术家 :%s" % ArtistHelper(_id).ArtistName)
queued += self.forIds([tid["id"] for tid in dList["songs"]])
return queued

class Song(Playlist):
def __call__(self, ids):
return self.forIds(ids)

def create_subroutine(sub_type) -> Subroutine:
"""Dynamically creates subroutine callable by string specified"""
return {"song": Song, "playlist": Playlist, "album": Album}[sub_type]
return {"song": Song, "playlist": Playlist, "album": Album, "artist": Artist}[sub_type]


def parse_sharelink(url):
"""Parses (partial) URLs for NE resources and determines its ID and type
e.g.
31140560 (plain song id)
https://greats3an.github.io/pyncmd/?trackId=1818064296 (pyncmd)
https://mos9527.github.io/pyncmd/?trackId=1818064296 (pyncmd)
分享Ali Edwards的单曲《Devil Trigger》: http://music.163.com/song/1353163404/?userid=6483697162 (来自@网易云音乐) (mobile app)
"分享mos9527创建的歌单「東方 PC」: http://music.163.com/playlist?id=72897851187" (desktop app)
"""
Expand All @@ -391,7 +412,8 @@ def parse_sharelink(url):
table = {
"song": ["trackId", "song"],
"playlist": ["playlist"],
"album": ["album"],
"artist": ["artist"],
"album": ["album"]
}
rtype = "song" # Defaults to songs (tracks)
for rtype_, rkeyword in table.items():
Expand Down Expand Up @@ -475,7 +497,16 @@ def parse_args():
)
group.add_argument("--http", action="store_true", help="优先使用 HTTP,不保证不被升级")
group.add_argument("--log-level", help="日志等级", default="NOTSET")

group = parser.add_argument_group("限量及过滤(注:只适用于*每单个*链接 / ID")
group.add_argument("-n","--count",metavar="下载总量",default=0,help="限制下载歌曲总量,n=0即不限制",type=int)
group.add_argument(
"--sort-by",
metavar="歌曲排序",
default="hot",
help="【限制总量时】歌曲排序方式 (hot: 热度高在前 time:发行时间新在前)",
choices=["hot","time"]
)
group.add_argument("--reverse-sort",action="store_true",default=False,help="【限制总量时】倒序排序歌曲")
args = parser.parse_args()

try:
Expand Down Expand Up @@ -547,7 +578,7 @@ def enqueue_task(task):
total_queued = 0
subroutines = []
for rtype,ids in tasks:
task_desc = "ID: %s 类型: %s" % (ids[0],{'album':'专辑','playlist':'歌单®','song':'单曲'}[rtype])
task_desc = "ID: %s 类型: %s" % (ids[0],{'album':'专辑','playlist':'歌单®','song':'单曲','artist':'艺术家'}[rtype])
logger.info("处理任务 %s" % task_desc)
subroutine = create_subroutine(rtype)(args, enqueue_task)
total_queued += subroutine(ids) # Enqueue tasks
Expand Down
2 changes: 1 addition & 1 deletion pyncm/apis/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -185,9 +185,9 @@ def EapiCryptoRequest(url, plain, method):
return request.content

from . import (
artist,
miniprograms,
album,
artists,
cloud,
cloudsearch,
login,
Expand Down
18 changes: 17 additions & 1 deletion pyncm/apis/artists.py → pyncm/apis/artist.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ def GetArtistTracks(artist_id: str,offset=0, total=True, limit=1000,order='hot')
offset (int, optional): 获取偏移数. Defaults to 0.
total (bool, optional): 是否获取全部内容. Defaults to True.
limit (int, optional): 单次获取量. Defaults to 30.
order (str, optional): 歌曲排序方式 (hot/time). Defaults to 'hot'.
order (str, optional): 歌曲排序方式 (hot:热度最高在前/time:时间最新在前). Defaults to 'hot'.
Returns:
dict
"""
Expand All @@ -42,3 +42,19 @@ def GetArtistTracks(artist_id: str,offset=0, total=True, limit=1000,order='hot')
"limit": str(limit),
"order": str(order),
}


@WeapiCryptoRequest
def GetArtistDetails(artist_id: str):
"""网页端 - 获取艺术家详情
Args:
artist_id (str): 艺术家ID
Returns:
dict
"""
return "/weapi/artist/head/info/get", {
"id" : str(artist_id)
}


29 changes: 29 additions & 0 deletions pyncm/utils/helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,35 @@ def AlbumSongCount(self):
def AlbumArtists(self):
"""专辑艺术家"""
return [_ar["name"] for _ar in self.data["album"]["artists"]]

class ArtistHelper(IDCahceHelper):
def __init__(self, item_id):
from pyncm.apis.artist import GetArtistDetails
super().__init__(item_id, GetArtistDetails)

def refresh(self):
logger.debug('Caching artist info %s' % self._item_id)
return super().refresh()

@Default()
def ID(self):
"""艺术家 ID"""
return self.data["data"]["artist"]["id"]

@Default()
def ArtistName(self):
"""艺术家名"""
return self.data["data"]["artist"]["name"]

@Default()
def ArtistTranslatedName(self):
"""艺术家翻译名"""
return self.data["data"]["artist"]["transNames"]

@Default()
def ArtistBrief(self):
"""艺术家简述"""
return self.data["data"]["artist"]["briefDesc"]

class TrackHelper:
"""Helper class for handling generic track objects"""
Expand Down

0 comments on commit b6ba051

Please sign in to comment.