Compare commits

...

78 Commits

Author SHA1 Message Date
Yaronzz 8ed83b1672 Merge remote-tracking branch 'remotes/origin/master' into refactor
# Conflicts:
#	README.md
#	TIDALDL-PY/tidal_dl/tidal.py
2022-06-23 11:38:42 +08:00
Yaronzz 3099820956 1. update setup.py 2022-06-23 11:26:33 +08:00
Yaronzz d643de43e9 1. add simple-gui 2022-06-23 10:03:17 +08:00
Yaronzz 39db4730c5 update README.md 2022-06-22 11:44:08 +08:00
Yaronzz 937aa7ca8f Merge pull request #924 from RandomNinjaAtk/patch-1
Bugfix for unable [ERR] {TITLE} parse ts urls failed
2022-06-21 15:22:42 +08:00
RandomNinjaAtk 84b9f6f6bc Bugfix for unable [ERR] {TITLE} parse ts urls failed
See issue: #872 

Tested and verified this change works locally.
2022-06-20 18:05:28 -04:00
Yaronzz b1ebd6f0b2 remove settings: onlyM4a 2022-06-20 17:34:41 +08:00
Yaronzz e1562ba30e 1. rebuild tidal-dl 2022-06-20 15:15:52 +08:00
Yaronzz 8876be13b6 fix bug of #916 2022-06-17 17:06:36 +08:00
Yaronzz 30a6c8c55a Merge pull request #896 from artiescie/master
for playlist dl, perform deletes as well as adds in the target dir
2022-06-09 11:47:09 +08:00
Yaronzz eb01de7f3a Merge pull request #912 from Blackjack200/master
improve chinese translation
2022-06-09 11:43:08 +08:00
Yaronzz e652556de2 skip 2022-06-07 08:41:21 +08:00
AZ1IDJC 00ef2f906f improve chinese translation 2022-06-05 23:28:09 +08:00
Yaronzz aba9493201 Merge pull request #903 from bladeoner/stale
Update stale job increase number of operations
2022-05-18 20:35:59 +08:00
bladeoner c78a56fff0 Update stale job increase number of operations 2022-04-26 10:59:06 +02:00
Richard Curzon 56bf0f9510 if there are .lrc lyric files, and corresponding music is deleted, then delete also the .lrc 2022-04-17 05:53:49 -04:00
Richard Curzon 2ebf80869c For playlist, remove TIDAL songs and redownload PL, the songs will be removed in the mirror image dl folder 2022-04-16 09:09:12 -04:00
Yaronzz bb5be5e5fb Merge pull request #895 from bladeoner/stale 2022-04-16 18:18:31 +08:00
bladeoner 5c2a13d4d1 Update stale messages 2022-04-16 12:02:36 +02:00
Yaronzz c61be4bbf3 update readme 2022-04-02 10:44:26 +08:00
Yaronzz a969583d79 update readme 2022-04-01 17:23:27 +08:00
Yaronzz 9f9a4f9aef update readme 2022-04-01 16:43:59 +08:00
Yaronzz 3b3af6a174 update workflow 2022-04-01 16:27:22 +08:00
Yaronzz 166961297e update workflow 2022-04-01 16:20:37 +08:00
Yaronzz 1f5343569f update workflow 2022-04-01 15:56:17 +08:00
Yaronzz 9e9d47f470 update workflow 2022-04-01 15:40:55 +08:00
Yaronzz 6870a8c37f update workflow 2022-04-01 15:11:27 +08:00
Yaronzz 4d368dd04d update workflow 2022-04-01 15:04:26 +08:00
Yaronzz 64b98ee117 update workflow 2022-04-01 12:34:05 +08:00
Yaronzz 5ec77efb19 update workflow 2022-04-01 12:27:41 +08:00
Yaronzz 65da2f7272 Create stale.yml 2022-04-01 11:52:32 +08:00
Yaronzz a5fb92db92 update workflow 2022-04-01 11:50:36 +08:00
Yaronzz 81e244f230 update workflow 2022-04-01 11:42:03 +08:00
Yaronzz 6a1b931340 update workflow 2022-04-01 11:38:02 +08:00
Yaronzz 8a21b01c1d update workflow 2022-04-01 11:25:21 +08:00
Yaronzz 96f46c2b59 update workflow 2022-04-01 10:32:42 +08:00
Yaronzz ffe335c17e update workflow 2022-04-01 10:18:21 +08:00
Yaronzz 79b537c186 update workflow 2022-04-01 09:56:59 +08:00
Yaronzz a806785609 update workflow 2022-04-01 09:50:11 +08:00
Yaronzz 06ff60ca74 update workflow 2022-04-01 09:47:00 +08:00
Yaronzz b34013129e update workflow 2022-04-01 09:30:38 +08:00
Yaronzz f1a5b1d764 update workflow 2022-04-01 09:27:40 +08:00
Yaronzz d52980506c Merge branch 'master' of https://github.com/yaronzz/Tidal-Media-Downloader into master 2022-04-01 09:27:06 +08:00
Yaronzz 34bab40d3d update 2022-04-01 09:26:55 +08:00
Yaronzz 747d06c7b4 Update util.py 2022-03-31 18:00:42 +08:00
Yaronzz 41573b7b39 Merge pull request #878 from bladeoner/language
Update Dutch language
2022-03-24 08:44:16 +08:00
bladeoner112@gmail.com d7e078db2e Update Dutch Language 2022-03-18 17:35:54 +01:00
392309221 363a854428 add m3u8dl.py 2022-03-14 15:55:22 +08:00
392309221 ad00099c9d Add video-path-format 2022-03-08 09:23:25 +08:00
392309221 6676a380e1 Add track-path-format 'TrackID' 2022-03-08 08:53:24 +08:00
Yaronzz bab13ff718 Merge pull request #877 from Kyo-70/patch-1
Update the Portuguese Translation
2022-03-07 15:14:40 +08:00
Yaronzz 9707c5c276 Merge branch 'master' into patch-1 2022-03-07 15:14:23 +08:00
Yaronzz 48206d675a Merge pull request #875 from PatrykMis/lng-polish
Add Polish translation, global typo fixes
2022-03-07 15:12:14 +08:00
Kyo-70 f877c184bb Update the Portuguese Translation
here is my contribution
2022-03-05 18:40:01 -04:00
Patryk Mis 59b360b285 Fix global typos 2022-03-04 19:59:44 +01:00
Patryk Mis 1a09571788 Add Polish translation 2022-03-04 19:49:47 +01:00
Yaronzz 2484a8d4d8 fix #874 2022-03-04 18:03:38 +08:00
Yaronzz 5e73ad35fa upload new version 2022-03-04 17:12:09 +08:00
Yaronzz 8503a622b0 1. fix #844 2022-03-04 17:02:30 +08:00
Yaronzz ab479300ea Merge pull request #871 from bladeoner/language
Add Dutch language
2022-03-02 10:12:49 +08:00
Yaronzz 96fb847479 Merge pull request #870 from bladeoner/workflow
Update Github Workflow
2022-03-02 10:12:40 +08:00
bladeoner 5484669333 Add Dutch language 2022-03-01 16:20:38 +01:00
bladeoner e366edbbea Update Github Workflow 2022-03-01 10:40:54 +01:00
Yaronzz e8ef25183d Merge pull request #864 from bladeoner/workflow
Add nightly build for tidal-dl and update README.md
2022-02-28 14:52:42 +08:00
Yaronzz c94c5283a7 Merge pull request #868 from bladeoner/requirements
Update requirements, add pydub and fix aigp warning
2022-02-28 09:12:53 +08:00
Yaronzz 77803c0b41 Merge pull request #869 from bladeoner/url
Update link to HTTPS
2022-02-28 09:12:18 +08:00
bladeoner b5bd4af56d Add nightly build for tidal-dl and update README.md 2022-02-27 17:36:09 +01:00
bladeoner 0a28baad64 Update link to HTTPS 2022-02-27 15:29:25 +01:00
bladeoner 6a1cd1d1d1 Update requirements, add pydub and fix aigp warning 2022-02-27 15:24:26 +01:00
392309221 25e72aa45e fix bug of setting path 2022-02-25 09:34:30 +08:00
Yaronzz 90f869fcdb Merge pull request #860 from bladeoner/reports
Replace bug, feature request and question reports
2022-02-23 10:00:47 +08:00
Yaronzz 72a2cb804a Merge pull request #858 from 9uyone/master
Update Ukrainian
2022-02-23 10:00:13 +08:00
bladeoner 752fb9e719 Replace bug, feature request and question reports 2022-02-22 11:14:15 +01:00
9uyone ac745e1434 correction of date in ukrainian.py 2022-02-20 23:02:08 +02:00
9uyone cbb4480d1e Update Ukrainian 2022-02-20 22:36:55 +02:00
Yaronzz a410370f45 update Hungarian #847 2022-02-11 08:40:00 +08:00
Yaronzz b8c4333d4e Merge pull request #846 from CDzungx/master
Update vietnamese.py
2022-02-11 08:37:09 +08:00
CDzungx 7e165f6c17 Update vietnamese.py
Update translations
2022-02-09 11:51:54 +07:00
121 changed files with 2292 additions and 7154 deletions
+64
View File
@@ -0,0 +1,64 @@
name: Bug Report
description: Create a bug report to help us improve.
title: "[BUG]: "
labels: bug
body:
- type: markdown
attributes:
value: |
Please search for this issue in issues-page first. Avoid duplication.
Please read Wiki first.
https://github.com/yaronzz/Tidal-Media-Downloader/wiki
- type: textarea
id: which-tool
attributes:
label: Which tool
description: Describe which tool has been used.
placeholder: |
tidal-gui or tidal-dl
validations:
required: true
- type: textarea
id: version
attributes:
label: Version
description: Describe the version of the tool.
placeholder: |
eg 2019.12.1.0
validations:
required: true
- type: textarea
id: platform
attributes:
label: Platform
description: Describe the platform on which the tool has been used.
placeholder: |
Windows\Linux\Macos\Android
validations:
required: true
- type: textarea
id: describe-bug
attributes:
label: Describe the bug
description: Describe the bug as accurately as possible.
placeholder: |
How to reappear this bug?
Which albumID\trackID\artistID?
Please describe in detail.
validations:
required: true
- type: textarea
id: logs
attributes:
label: Relevant log output
description: Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks.
render: shell
validations:
required: false
- type: textarea
id: screenshots
attributes:
label: Add screenshots
description: Please add screenshots if available.
validations:
required: false
-25
View File
@@ -1,25 +0,0 @@
---
name: Bug report
about: Create a bug report to help us improve
title: "[BUG] xxxxxxxxxxxxx"
labels: bug
---
<!-- Please search for this issue in issues-page first. Avoid duplication. -->
<!-- Please read Wiki first. -->
<!-- https://github.com/yaronzz/Tidal-Media-Downloader/wiki -->
**Which tool**
<!-- tidal-gui or tidal-dl -->
**Version**
<!-- eg 2019.12.1.0 -->
**Platform**
<!-- Windows\Linux\Macos\Android -->
**Bug description**
<!-- How to reappear this bug?
Which albumID\trackID\artistID?
Please describe in detail. -->
**Screenshot**
+1
View File
@@ -0,0 +1 @@
blank_issues_enabled: false
+34
View File
@@ -0,0 +1,34 @@
name: Feature Request
description: Open a feature request to Tidal-Media-Downloader.
title: "[FEATURE]: "
labels: feature request
body:
- type: markdown
attributes:
value: |
Please search for this issue in issues-page first. Avoid duplication.
Please read Wiki first.
https://github.com/yaronzz/Tidal-Media-Downloader/wiki
- type: textarea
id: which-tool
attributes:
label: Which tool
description: Describe for which tool you want a feature to be added.
placeholder: |
tidal-gui or tidal-dl
validations:
required: true
- type: textarea
id: feature
attributes:
label: Describe the feature
description: Describe the feature as accurately as possible.
validations:
required: true
- type: textarea
id: screenshots
attributes:
label: Add screenshots
description: Please add screenshots if available.
validations:
required: false
-18
View File
@@ -1,18 +0,0 @@
---
name: Feature request
about: Open a feature request to Tidal-Media-Downloader
title: "[FEATURE] xxxxxxxxxxxxx"
labels: feature request
---
<!-- Please search for this issue in issues-page first. Avoid duplication. -->
<!-- Please read Wiki first. -->
<!-- https://github.com/yaronzz/Tidal-Media-Downloader/wiki -->
**Which tool**
<!-- tidal-gui or tidal-dl -->
**Which feature**
<!-- eg. Support IOS -->
**Description**
<!-- Please describe in detail. Screenshot-->
@@ -0,0 +1,34 @@
name: Questions
description: Question for the tool.
title: "[QUESTION]: "
labels: question
body:
- type: markdown
attributes:
value: |
Please search for this issue in issues-page first. Avoid duplication.
Please read Wiki first.
https://github.com/yaronzz/Tidal-Media-Downloader/wiki
- type: textarea
id: which-tool
attributes:
label: Which tool
description: Describe which tool the question is about.
placeholder: |
tidal-gui or tidal-dl
validations:
required: true
- type: textarea
id: question
attributes:
label: Describe your question
description: Describe your question as accurately as possible.
validations:
required: true
- type: textarea
id: screenshots
attributes:
label: Add screenshots
description: Please add screenshots if available.
validations:
required: false
-17
View File
@@ -1,17 +0,0 @@
---
name: Question
about: Question for the tool
title: "[QUESTION] xxxxxxxxxxxxx"
labels: question
---
<!-- Please search for this issue in issues-page first. Avoid duplication. -->
<!-- Please read Wiki first. -->
<!--
https://github.com/yaronzz/Tidal-Media-Downloader/wiki
https://yaronzz.top/post/tidal_dl_installation/
-->
**Which tool**
<!-- tidal-gui or tidal-dl -->
**Question**
+28
View File
@@ -0,0 +1,28 @@
# # This workflow warns and then closes issues and PRs that have had no activity for a specified amount of time.
# #
# # You can adjust the behavior by modifying this file.
# # For more information, see:
# # https://github.com/actions/stale
# name: Mark stale issues and pull requests
# on:
# schedule:
# - cron: '45 15 * * *'
# jobs:
# stale:
# runs-on: ubuntu-latest
# permissions:
# issues: write
# pull-requests: write
# steps:
# - uses: actions/stale@v5
# with:
# repo-token: ${{ secrets.GITHUB_TOKEN }}
# stale-issue-message: 'This issue has been automatically marked as stale because it has not had recent activity.'
# stale-pr-message: 'This pull request has been automatically marked as stale because it has not had recent activity.'
# stale-issue-label: 'no-issue-activity'
# stale-pr-label: 'no-pr-activity'
# operations-per-run: 60
+77
View File
@@ -0,0 +1,77 @@
name: Build exe
on: [push, pull_request]
jobs:
build:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [macos-latest, ubuntu-latest, windows-latest]
steps:
- name: Checkout repo
uses: actions/checkout@v2
with:
fetch-depth: 0
- name: Install dependencies
shell: bash
run: |
pip3 install wheel
pip3 install pyinstaller
pip3 install PyQt5
pip3 install -r requirements.txt --upgrade
working-directory: TIDALDL-PY
- name: Clean directory
shell: bash
run: |
rm -rf dist
rm -rf build
rm -rf tidal_dl.egg-info
rm -rf tidal_gui.egg-info
rm -rf MANIFEST.in
working-directory: TIDALDL-PY
- name: Build tidal-dl
run: |
pyinstaller -F tidal_dl/__init__.py -n tidal-dl
working-directory: TIDALDL-PY
# - name: Build tidal-gui-macOs
# shell: bash
# if: ${{ matrix.os == 'macos-latest' }}
# run: |
# cp -rf guiStatic.in MANIFEST.in
# pyinstaller -F tidal_gui/__init__.py -w -n tidal-gui
# cp -rf tidal_gui/resource dist/
# working-directory: TIDALDL-PY
# - name: Build tidal-gui
# shell: bash
# if: ${{ matrix.os != 'macos-latest' }}
# run: |
# cp -rf guiStatic.in MANIFEST.in
# pyinstaller -D tidal_gui/__init__.py -w -n tidal-gui
# working-directory: TIDALDL-PY
# - name: Gzip tidal-gui
# shell: bash
# if: ${{ matrix.os != 'macos-latest' }}
# run: |
# cp -rf ../tidal_gui/resource ./tidal-gui/
# tar -zcvf tidal-gui.tar.gz tidal-gui
# rm -rf tidal-gui
# working-directory: TIDALDL-PY/dist
- name: Upload artifact
uses: actions/upload-artifact@v2
with:
name: tidal-dl-${{ runner.os }}
path: |
TIDALDL-PY/dist/*
+11
View File
@@ -167,3 +167,14 @@ tidal_dl-BAK
/TIDALDL-GUI-CROSS/tidal_gui/viewModel/__pycache__
/TIDALDL-GUI-CROSS/tidal_gui/control/__pycache__
__pycache__
clean.sh
TIDALDL-PY/MANIFEST.in
TIDALDL-PY/exe/resource
TIDALDL-PY/exe/tidal_dl_win.tar.gz
.github/workflows/continuous-integration-workflow copy.yml
TIDALDL-PY/tidal-gui.spec
.history/
.gitignore
/TIDALDL-PY/tidal_dl_old
/TIDALDL-PY/tidal_gui_old
/TIDALDL-PY/tidal_gui
+1
View File
@@ -16,6 +16,7 @@
"env": {
"PYTHONPATH": "${workspaceRoot}/TIDALDL-PY/"
},
"justMyCode": false
},
{
"name": "Python: common line",
+34 -16
View File
@@ -18,6 +18,9 @@
<a href="https://pypi.org/project/tidal-dl/">
<img src="https://img.shields.io/pypi/dm/tidal-dl?label=tidal-dl%20download" alt="">
</a>
<a href="https://github.com/yaronzz/Tidal-Media-Downloader/actions/workflows/build.yml">
<img src="https://github.com/yaronzz/Tidal-Media-Downloader/actions/workflows/build.yml/badge.svg" alt="">
</a>
</div>
<p align="center">
«Tidal-Media-Downloader» is an application that lets you download videos and tracks from Tidal. It supports two version: tidal-dl and tidal-gui. (This repository only contains tidal-dl, and the release isn't the newest gui version.)
@@ -25,26 +28,26 @@
<a href="https://github.com/yaronzz/Tidal-Media-Downloader-PRO/releases">Download</a> |
<a href="https://yaronzz.com/post/tidal_dl_installation/">Documentation</a> |
<a href="https://yaronzz.com/post/tidal_dl_installation_chn/">中文文档</a> |
<a href="https://t.me/Tidal_Media_Downloader">Channel</a>
<br>
</p>
## 📺 Installation
| Name | platform | Install |
| -------------- | --------------------------------- | ------------------------------------------------------------ |
| tidal-gui | Windows | [GUI Repository](https://github.com/yaronzz/Tidal-Media-Downloader-PRO) |
| tidal-dl (cli) | Windows \ Linux \ Macos \ Android | ```pip3 install tidal-dl --upgrade```<br />[Detailed Description](https://yaronzz.com/post/tidal_dl_installation/#Install) |
## 📺 Installation
## 📡 Telegram
| Name | platform | Install |
| ----------------------------------- | --------------------------------- | --------------------------------------------------------------------------------------------------------------------- |
| tidal-dl (cli) | Windows \ Linux \ Macos \ Android | ``pip3 install tidal-dl --upgrade``<br />[Detailed Description](https://yaronzz.com/post/tidal_dl_installation/#Install) |
| tidal-gui | Windows | [GUI Repository](https://github.com/yaronzz/Tidal-Media-Downloader-PRO) |
| tidal-gui(**Cross-platform**) | Windows \ Linux \ Macos | ``pip3 install tidal-gui --upgrade`` |
- [Group](https://t.me/tidal_group) : Feed back
- [Channel](https://t.me/Tidal_Media_Downloader) : Notify the new version
### Nightly Builds
| Download nightly builds from continuous integration: | [Build Status][Actions] |
| ---------------------------------------------------- | ----------------------- |
## 🤖 Features
- Download album \ track \ video \ playlist \ artist-albums
- Add metadata to songs
- Selectable video resolution and track quality
## 💽 User Interface
@@ -83,22 +86,36 @@
| {ArtistName} | The Beatles |
| {ArtistsName} | The Beatles |
| {TrackTitle} | I Saw Her Standing There (Remastered 2009) |
| {ExplicitFlag} | (*Explicit*) |
| {ExplicitFlag} | (*Explicit*) |
| {AlbumYear} | 1963 |
| {AlbumTitle} | Please Please Me (Remastered) |
| {AudioQuality} | LOSSLESS |
| {DurationSeconds} | 173 |
| {Duration} | 02:53 |
| {TrackID} | 55163244 |
### Video
| Tag | Example Value |
| -------------- | ------------------ |
| {VideoNumber} | 00 |
| {ArtistName} | DMX |
| {ArtistsName} | DMX, Westside Gunn |
| {VideoTitle} | Hood Blues |
| {ExplicitFlag} | (*Explicit*) |
| {VideoYear} | 2021 |
| {TrackID} | 188932980 |
## ☕ Support
If you really like my projects and want to support me, you can buy me a coffee and star this project.
If you really like my projects and want to support me, you can buy me a coffee and star this project.
<a href="https://www.buymeacoffee.com/yaronzz" target="_blank"><img src="https://cdn.buymeacoffee.com/buttons/arial-orange.png" alt="Buy Me A Coffee" style="height: 51px !important;width: 217px !important;" ></a>
## 🎂 Contributors
This project exists thanks to all the people who contribute.
<a href="https://github.com/yaronzz/Tidal-Media-Downloader/graphs/contributors"><img src="https://contributors-img.web.app/image?repo=yaronzz/Tidal-Media-Downloader" /></a>
This project exists thanks to all the people who contribute.
`<a href="https://github.com/yaronzz/Tidal-Media-Downloader/graphs/contributors"><img src="https://contributors-img.web.app/image?repo=yaronzz/Tidal-Media-Downloader" />``</a>`
## 🎨 Libraries and reference
@@ -108,8 +125,9 @@ This project exists thanks to all the people who contribute.
- [tidal-wiki](https://github.com/Fokka-Engineering/TIDAL/wiki)
## 📜 Disclaimer
- Private use only.
- Need a Tidal-HIFI subscription.
- Need a Tidal-HIFI subscription.
- You should not use this method to distribute or pirate music.
- It may be illegal to use this in your country, so be informed.
Binary file not shown.
+8 -6
View File
@@ -1,8 +1,10 @@
requests==2.22.0
colorama==0.4.3
prettytable==0.7.2
requests==2.27.1
colorama==0.4.4
prettytable==3.1.1
mutagen==1.45.1
psutil==5.7.3
pycryptodome==3.9.9
aigpy==2022.01.20.1
psutil==5.9.0
pycryptodome==3.14.1
aigpy==2022.6.22.1
lyricsgenius==3.0.1
pydub==0.25.1
PyQt5==5.15.7
+6 -2
View File
@@ -13,7 +13,11 @@ setup(
packages=find_packages(exclude=['tidal_gui*']),
include_package_data=False,
platforms="any",
install_requires=["aigpy>=2022.01.20.1", "requests>=2.22.0",
"pycryptodome", "pydub", "prettytable", "lyricsgenius"],
install_requires=["aigpy>=2022.6.22.1",
"requests>=2.22.0",
"pycryptodome",
"pydub",
"prettytable",
"PyQt5"],
entry_points={'console_scripts': ['tidal-dl = tidal_dl:main', ]}
)
+148 -244
View File
@@ -1,244 +1,148 @@
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
'''
@File : __init__.py
@Time : 2020/11/08
@Author : Yaronzz
@Version : 2.1
@Contact : yaronhuang@foxmail.com
@Desc :
'''
import base64
import getopt
import logging
import ssl
import sys
import time
from aigpy.cmdHelper import green, yellow
from aigpy.pathHelper import mkdirs
from aigpy.pipHelper import getLastVersion
from aigpy.stringHelper import isNull
from aigpy.systemHelper import cmpVersion
from tidal_dl.download import start
from tidal_dl.enums import AudioQuality, VideoQuality, Type
from tidal_dl.lang.language import setLang, initLang, getLangChoicePrint
from tidal_dl.printf import Printf, VERSION
from tidal_dl.settings import Settings, TokenSettings, getLogPath
from tidal_dl.tidal import TidalAPI
from tidal_dl.util import API, CONF, TOKEN, LANG, displayTime, loginByConfig, loginByWeb
import tidal_dl.apiKey as apiKey
ssl._create_default_https_context = ssl._create_unverified_context
def login():
print(LANG.AUTH_START_LOGIN)
msg, check = API.getDeviceCode()
if not check:
Printf.err(msg)
return
# print(LANG.AUTH_LOGIN_CODE.format(green(API.key.userCode)))
print(LANG.AUTH_NEXT_STEP.format(green("http://" + API.key.verificationUrl + "/" + API.key.userCode), yellow(displayTime(API.key.authCheckTimeout))))
print(LANG.AUTH_WAITING)
loginByWeb()
return
def setAccessToken():
while True:
print("-------------AccessToken---------------")
token = Printf.enter("accessToken('0' go back):")
if token == '0':
return
msg, check = API.loginByAccessToken(token, TOKEN.userid)
if not check:
Printf.err(msg)
continue
break
print("-------------RefreshToken---------------")
refreshToken = Printf.enter("refreshToken('0' to skip):")
if refreshToken == '0':
refreshToken = TOKEN.refreshToken
TOKEN.accessToken = token
TOKEN.refreshToken = refreshToken
TOKEN.expiresAfter = 0
TOKEN.countryCode = API.key.countryCode
TokenSettings.save(TOKEN)
def setAPIKey():
global LANG
item = apiKey.getItem(CONF.apiKeyIndex)
ver = apiKey.getVersion()
Printf.info(f'Current APIKeys: {str(CONF.apiKeyIndex)} {item["platform"]}-{item["formats"]}')
Printf.info(f'Current Version: {str(ver)}')
Printf.apikeys(apiKey.getItems())
index = int(Printf.enterLimit("APIKEY index:", LANG.MSG_INPUT_ERR, apiKey.getLimitIndexs()))
if index != CONF.apiKeyIndex:
CONF.apiKeyIndex = index
Settings.save(CONF)
API.apiKey = apiKey.getItem(index)
return True
return False
def checkLogin():
if loginByConfig():
return
login()
return
def checkLogout():
login()
return
def changeSettings():
global LANG
Printf.settings(CONF)
choice = Printf.enter(LANG.CHANGE_START_SETTINGS)
if choice == '0':
return
CONF.downloadPath = Printf.enterPath(LANG.CHANGE_DOWNLOAD_PATH, LANG.MSG_PATH_ERR, '0', CONF.downloadPath)
CONF.audioQuality = AudioQuality(int(Printf.enterLimit(
LANG.CHANGE_AUDIO_QUALITY, LANG.MSG_INPUT_ERR, ['0', '1', '2', '3'])))
CONF.videoQuality = VideoQuality(int(Printf.enterLimit(
LANG.CHANGE_VIDEO_QUALITY, LANG.MSG_INPUT_ERR, ['1080', '720', '480', '360'])))
CONF.onlyM4a = Printf.enter(LANG.CHANGE_ONLYM4A) == '1'
CONF.checkExist = Printf.enter(LANG.CHANGE_CHECK_EXIST) == '1'
CONF.includeEP = Printf.enter(LANG.CHANGE_INCLUDE_EP) == '1'
CONF.saveCovers = Printf.enter(LANG.CHANGE_SAVE_COVERS) == '1'
CONF.showProgress = Printf.enter(LANG.CHANGE_SHOW_PROGRESS) == '1'
CONF.saveAlbumInfo = Printf.enter(LANG.CHANGE_SAVE_ALBUM_INFO) == '1'
CONF.showTrackInfo = Printf.enter(LANG.CHANGE_SHOW_TRACKINFO) == '1'
CONF.usePlaylistFolder = Printf.enter(LANG.SETTING_USE_PLAYLIST_FOLDER + "('0'-No,'1'-Yes):") == '1'
CONF.language = Printf.enter(LANG.CHANGE_LANGUAGE + "(" + getLangChoicePrint() + "):")
CONF.albumFolderFormat = Printf.enterFormat(
LANG.CHANGE_ALBUM_FOLDER_FORMAT, CONF.albumFolderFormat, Settings.getDefaultAlbumFolderFormat())
CONF.trackFileFormat = Printf.enterFormat(LANG.CHANGE_TRACK_FILE_FORMAT,
CONF.trackFileFormat, Settings.getDefaultTrackFileFormat())
CONF.addLyrics = Printf.enter(LANG.CHANGE_ADD_LYRICS) == '1'
CONF.lyricsServerProxy = Printf.enterFormat(
LANG.CHANGE_LYRICS_SERVER_PROXY, CONF.lyricsServerProxy, CONF.lyricsServerProxy)
CONF.lyricFile = Printf.enter(LANG.CHANGE_ADD_LRC_FILE) == '1'
CONF.addTypeFolder = Printf.enter(LANG.CHANGE_ADD_TYPE_FOLDER) == '1'
LANG = setLang(CONF.language)
Settings.save(CONF)
def mainCommand():
try:
opts, args = getopt.getopt(sys.argv[1:], "hvl:o:q:r:", ["help", "version",
"link=", "output=", "quality", "resolution"])
except getopt.GetoptError as errmsg:
Printf.err(vars(errmsg)['msg'] + ". Use 'tidal-dl -h' for useage.")
return
link = None
for opt, val in opts:
if opt in ('-h', '--help'):
Printf.usage()
continue
if opt in ('-v', '--version'):
Printf.logo()
continue
if opt in ('-l', '--link'):
checkLogin()
link = val
continue
if opt in ('-o', '--output'):
CONF.downloadPath = val
Settings.save(CONF)
continue
if opt in ('-q', '--quality'):
CONF.audioQuality = Settings.getAudioQuality(val)
Settings.save(CONF)
continue
if opt in ('-r', '--resolution'):
CONF.videoQuality = Settings.getVideoQuality(val)
Settings.save(CONF)
continue
if not mkdirs(CONF.downloadPath):
Printf.err(LANG.MSG_PATH_ERR + CONF.downloadPath)
return
if link is not None:
Printf.info(LANG.SETTING_DOWNLOAD_PATH + ':' + CONF.downloadPath)
start(TOKEN, CONF, link)
def debug():
checkLogin()
API.key.accessToken = TOKEN.accessToken
API.key.userId = TOKEN.userid
API.key.countryCode = TOKEN.countryCode
# https://api.tidal.com/v1/mixes/{01453963b7dbd41c8b82ccb678d127/items?countryCode={country}
API.getMix("01453963b7dbd41c8b82ccb678d127")
# msg, result = API.search('Mojito', Type.Null, 0, 10)
msg, lyric = API.getLyrics('144909909')
pass
def main():
if len(sys.argv) > 1:
mainCommand()
return
Printf.logo()
Printf.settings(CONF)
checkLogin()
onlineVer = getLastVersion('tidal-dl')
if not isNull(onlineVer):
icmp = cmpVersion(onlineVer, VERSION)
if icmp > 0:
Printf.info(LANG.PRINT_LATEST_VERSION + ' ' + onlineVer)
while True:
Printf.choices()
choice = Printf.enter(LANG.PRINT_ENTER_CHOICE)
if choice == "0":
return
elif choice == "1":
checkLogin()
elif choice == "2":
changeSettings()
elif choice == "3":
checkLogout()
elif choice == "4":
setAccessToken()
elif choice == '5':
if setAPIKey():
checkLogout()
elif choice == "10": # test track
start(TOKEN, CONF, '70973230')
elif choice == "11": # test video
start(TOKEN, CONF, '188932980')
elif choice == "12": # test album
start(TOKEN, CONF, '58138532')
elif choice == "13": # test playlist
start(TOKEN, CONF, '98235845-13e8-43b4-94e2-d9f8e603cee7')
elif choice == "14": # test playlist
start(TOKEN, CONF, '01453963b7dbd41c8b82ccb678d127')
else:
start(TOKEN, CONF, choice)
if __name__ == "__main__":
# debug()
main()
# test example
# track 70973230 77798028 212657
# video 155608351 188932980
# album 58138532 77803199 21993753 79151897 56288918
# playlist 98235845-13e8-43b4-94e2-d9f8e603cee7
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
'''
@File : __init__.py
@Time : 2020/11/08
@Author : Yaronzz
@Version : 3.0
@Contact : yaronhuang@foxmail.com
@Desc :
'''
import sys
import getopt
from tidal_dl.events import *
from tidal_dl.settings import *
from tidal_dl.gui import *
def mainCommand():
try:
opts, args = getopt.getopt(sys.argv[1:],
"hvgl:o:q:r:",
["help", "version", "gui", "link=", "output=", "quality", "resolution"])
except getopt.GetoptError as errmsg:
Printf.err(vars(errmsg)['msg'] + ". Use 'tidal-dl -h' for useage.")
return
link = None
showGui = False
for opt, val in opts:
if opt in ('-h', '--help'):
Printf.usage()
return
if opt in ('-v', '--version'):
Printf.logo()
return
if opt in ('-g', '--gui'):
showGui = True
return
if opt in ('-l', '--link'):
link = val
continue
if opt in ('-o', '--output'):
SETTINGS.downloadPath = val
SETTINGS.save()
continue
if opt in ('-q', '--quality'):
SETTINGS.audioQuality = SETTINGS.getAudioQuality(val)
SETTINGS.save()
continue
if opt in ('-r', '--resolution'):
SETTINGS.videoQuality = SETTINGS.getVideoQuality(val)
SETTINGS.save()
continue
if not aigpy.path.mkdirs(SETTINGS.downloadPath):
Printf.err(LANG.MSG_PATH_ERR + SETTINGS.downloadPath)
return
if showGui:
startGui()
return
if link is not None:
if not loginByConfig():
loginByWeb()
Printf.info(LANG.SETTING_DOWNLOAD_PATH + ':' + SETTINGS.downloadPath)
start(link)
def main():
SETTINGS.read(getProfilePath())
TOKEN.read(getTokenPath())
if len(sys.argv) > 1:
mainCommand()
return
Printf.logo()
Printf.settings()
if not loginByConfig():
loginByWeb()
Printf.checkVersion()
while True:
Printf.choices()
choice = Printf.enter(LANG.PRINT_ENTER_CHOICE)
if choice == "0":
return
elif choice == "1":
if not loginByConfig():
loginByWeb()
elif choice == "2":
loginByWeb()
elif choice == "3":
loginByAccessToken()
elif choice == "4":
changePathSettings()
elif choice == "5":
changeQualitySettings()
elif choice == "6":
changeSettings()
elif choice == "7":
if changeApiKey():
loginByWeb()
else:
start(choice)
def test():
SETTINGS.read(getProfilePath())
TOKEN.read(getTokenPath())
if not loginByConfig():
loginByWeb()
SETTINGS.audioQuality = AudioQuality.Normal
SETTINGS.videoFileFormat = VideoQuality.P240
SETTINGS.checkExist = False
SETTINGS.includeEP = True
SETTINGS.saveCovers = True
SETTINGS.lyricFile = True
SETTINGS.showProgress = True
SETTINGS.showTrackInfo = True
SETTINGS.saveAlbumInfo = True
SETTINGS.downloadPath = "./download/"
SETTINGS.usePlaylistFolder = True
SETTINGS.albumFolderFormat = R"{ArtistName}/{Flag} {AlbumTitle} [{AlbumID}] [{AlbumYear}]"
SETTINGS.trackFileFormat = R"{TrackNumber} - {ArtistName} - {TrackTitle}{ExplicitFlag}"
SETTINGS.videoFileFormat = R"{VideoNumber} - {ArtistName} - {VideoTitle}{ExplicitFlag}"
Printf.settings()
# test example
# https://tidal.com/browse/track/70973230
# track 70973230 77798028 212657
start('70973230')
# album 58138532 77803199 21993753 79151897 56288918
# start('58138532')
# playlist 98235845-13e8-43b4-94e2-d9f8e603cee7
# start('98235845-13e8-43b4-94e2-d9f8e603cee7')
# video 155608351 188932980
# start("155608351")
if __name__ == '__main__':
# test()
main()
+1 -2
View File
@@ -4,7 +4,7 @@
@File : apiKey.py
@Date : 2021/11/30
@Author : Yaronzz
@Version : 1.0
@Version : 3.0
@Contact : yaronhuang@foxmail.com
@Desc :
"""
@@ -68,7 +68,6 @@ __ERROR_KEY__ = {
},
def getNum():
return len(__API_KEYS__['keys'])
+147 -139
View File
@@ -8,177 +8,185 @@
@Contact : yaronhuang@foxmail.com
@Desc :
'''
import requests
import logging
import os
import datetime
import aigpy
import lyricsgenius
from tidal_dl.decryption import decrypt_file
from tidal_dl.decryption import decrypt_security_token
from tidal_dl.enums import Type, AudioQuality
from tidal_dl.model import Track, Video, Lyrics, Mix
from tidal_dl.printf import Printf
from tidal_dl.settings import Settings
from tidal_dl.tidal import TidalAPI
from tidal_dl.util import convert, downloadTrack, downloadVideo, encrypted, getVideoPath, getTrackPath, getAlbumPath, API
import logging
from tidal_dl.paths import *
from tidal_dl.printf import *
from tidal_dl.decryption import *
from tidal_dl.tidal import *
def __loadAPI__(user):
API.key.accessToken = user.accessToken
API.key.userId = user.userid
API.key.countryCode = user.countryCode
def __isSkip__(finalpath, url):
if not SETTINGS.checkExist:
return False
curSize = aigpy.file.getSize(finalpath)
if curSize <= 0:
return False
netSize = aigpy.net.getSize(url)
return curSize >= netSize
def __downloadCover__(conf, album):
if album == None:
def __encrypted__(stream, srcPath, descPath):
if aigpy.string.isNull(stream.encryptionKey):
os.replace(srcPath, descPath)
else:
key, nonce = decrypt_security_token(stream.encryptionKey)
decrypt_file(srcPath, descPath, key, nonce)
os.remove(srcPath)
def __parseContributors__(roleType, Contributors):
if Contributors is None:
return None
try:
ret = []
for item in Contributors['items']:
if item['role'] == roleType:
ret.append(item['name'])
return ret
except:
return None
def __setMetaData__(track: Track, album: Album, filepath, contributors, lyrics):
obj = aigpy.tag.TagTool(filepath)
obj.album = track.album.title
obj.title = track.title
if not aigpy.string.isNull(track.version):
obj.title += ' (' + track.version + ')'
obj.artist = list(map(lambda artist: artist.name, track.artists))
obj.copyright = track.copyRight
obj.tracknumber = track.trackNumber
obj.discnumber = track.volumeNumber
obj.composer = __parseContributors__('Composer', contributors)
obj.isrc = track.isrc
obj.albumartist = list(map(lambda artist: artist.name, album.artists))
obj.date = album.releaseDate
obj.totaldisc = album.numberOfVolumes
obj.lyrics = lyrics
if obj.totaldisc <= 1:
obj.totaltrack = album.numberOfTracks
coverpath = TIDAL_API.getCoverUrl(album.cover, "1280", "1280")
obj.save(coverpath)
def downloadCover(album):
if album is None:
return
path = getAlbumPath(conf, album) + '/cover.jpg'
url = API.getCoverUrl(album.cover, "1280", "1280")
if url is not None:
aigpy.net.downloadFile(url, path)
path = getAlbumPath(album) + '/cover.jpg'
url = TIDAL_API.getCoverUrl(album.cover, "1280", "1280")
aigpy.net.downloadFile(url, path)
def __saveAlbumInfo__(conf, album, tracks):
if album == None:
def downloadAlbumInfo(album, tracks):
if album is None:
return
path = getAlbumPath(conf, album) + '/AlbumInfo.txt'
path = getAlbumPath(album)
aigpy.path.mkdirs(path)
path += '/AlbumInfo.txt'
infos = ""
infos += "[ID] %s\n" % (str(album.id))
infos += "[Title] %s\n" % (str(album.title))
infos += "[Artists] %s\n" % (str(album.artist.name))
infos += "[Artists] %s\n" % (TIDAL_API.getArtistsName(album.artists))
infos += "[ReleaseDate] %s\n" % (str(album.releaseDate))
infos += "[SongNum] %s\n" % (str(album.numberOfTracks))
infos += "[Duration] %s\n" % (str(album.duration))
infos += '\n'
i = 0
while True:
if i >= int(album.numberOfVolumes):
break
i = i + 1
infos += "===========CD %d=============\n" % i
for index in range(0, album.numberOfVolumes):
volumeNumber = index + 1
infos += f"===========CD {volumeNumber}=============\n"
for item in tracks:
if item.volumeNumber != i:
if item.volumeNumber != volumeNumber:
continue
infos += '{:<8}'.format("[%d]" % item.trackNumber)
infos += "%s\n" % item.title
aigpy.file.write(path, infos, "w+")
def __album__(conf, obj):
Printf.album(obj)
msg, tracks, videos = API.getItems(obj.id, Type.Album)
if not aigpy.string.isNull(msg):
Printf.err(msg)
return
if conf.saveAlbumInfo:
__saveAlbumInfo__(conf, obj, tracks)
if conf.saveCovers:
__downloadCover__(conf, obj)
for item in tracks:
downloadTrack(item, obj)
for item in videos:
downloadVideo(item, obj)
def downloadVideo(video: Video, album: Album = None, playlist: Playlist = None):
try:
stream = TIDAL_API.getVideoStreamUrl(video.id, SETTINGS.videoQuality)
path = getVideoPath(video, album, playlist)
Printf.video(video, stream)
logging.info("[DL Video] name=" + aigpy.path.getFileName(path) + "\nurl=" + stream.m3u8Url)
m3u8content = requests.get(stream.m3u8Url).content
if m3u8content is None:
Printf.err(f"DL Video[{video.title}] getM3u8 failed.{str(e)}")
return False
urls = aigpy.m3u8.parseTsUrls(m3u8content)
if len(urls) <= 0:
Printf.err(f"DL Video[{video.title}] getTsUrls failed.{str(e)}")
return False
check, msg = aigpy.m3u8.downloadByTsUrls(urls, path)
if check:
Printf.success(video.title)
return True
else:
Printf.err(f"DL Video[{video.title}] failed.{msg}")
return False
except Exception as e:
Printf.err(f"DL Video[{video.title}] failed.{str(e)}")
return False
def __track__(conf, obj):
msg, album = API.getAlbum(obj.album.id)
if conf.saveCovers:
__downloadCover__(conf, album)
downloadTrack(obj, album)
def downloadTrack(track: Track, album=None, playlist=None, userProgress=None, partSize=1048576):
try:
stream = TIDAL_API.getStreamUrl(track.id, SETTINGS.audioQuality)
path = getTrackPath(track, stream, album, playlist)
if SETTINGS.showTrackInfo:
Printf.track(track, stream)
def __video__(conf, obj):
# Printf.video(obj)
downloadVideo(obj, obj.album)
if userProgress is not None:
userProgress.updateStream(stream)
# check exist
if __isSkip__(path, stream.url):
Printf.success(aigpy.path.getFileName(path) + " (skip:already exists!)")
return True
def __artist__(conf, obj):
msg, albums = API.getArtistAlbums(obj.id, conf.includeEP)
Printf.artist(obj, len(albums))
if not aigpy.string.isNull(msg):
Printf.err(msg)
return
for item in albums:
__album__(conf, item)
# download
logging.info("[DL Track] name=" + aigpy.path.getFileName(path) + "\nurl=" + stream.url)
tool = aigpy.download.DownloadTool(path + '.part', [stream.url])
tool.setUserProgress(userProgress)
tool.setPartSize(partSize)
check, err = tool.start(SETTINGS.showProgress)
if not check:
Printf.err(f"DL Track[{track.title}] failed.{str(err)}")
return False
def __playlist__(conf, obj):
Printf.playlist(obj)
msg, tracks, videos = API.getItems(obj.uuid, Type.Playlist)
if not aigpy.string.isNull(msg):
Printf.err(msg)
return
# encrypted -> decrypt and remove encrypted file
__encrypted__(stream, path + '.part', path)
for index, item in enumerate(tracks):
mag, album = API.getAlbum(item.album.id)
item.trackNumberOnPlaylist = index + 1
downloadTrack(item, album, obj)
if conf.saveCovers and not conf.usePlaylistFolder:
__downloadCover__(conf, album)
for item in videos:
downloadVideo(item, None)
# contributors
try:
contributors = TIDAL_API.getTrackContributors(track.id)
except:
contributors = None
def __mix__(conf, obj: Mix):
Printf.mix(obj)
for index, item in enumerate(obj.tracks):
mag, album = API.getAlbum(item.album.id)
item.trackNumberOnPlaylist = index + 1
downloadTrack(item, album)
if conf.saveCovers and not conf.usePlaylistFolder:
__downloadCover__(conf, album)
for item in obj.videos:
downloadVideo(item, None)
# lyrics
try:
lyrics = TIDAL_API.getLyrics(track.id).subtitles
if SETTINGS.lyricFile:
lrcPath = path.rsplit(".", 1)[0] + '.lrc'
aigpy.fileHelper.write(lrcPath, lyrics, 'w')
except:
lyrics = ''
def file(user, conf, string):
txt = aigpy.file.getContent(string)
if aigpy.string.isNull(txt):
Printf.err("Nothing can read!")
return
array = txt.split('\n')
for item in array:
if aigpy.string.isNull(item):
continue
if item[0] == '#':
continue
if item[0] == '[':
continue
start(user, conf, item)
def start(user, conf, string):
__loadAPI__(user)
if aigpy.string.isNull(string):
Printf.err('Please enter something.')
return
strings = string.split(" ")
for item in strings:
if aigpy.string.isNull(item):
continue
if os.path.exists(item):
file(user, conf, item)
return
msg, etype, obj = API.getByString(item)
if etype == Type.Null or not aigpy.string.isNull(msg):
Printf.err(msg + " [" + item + "]")
return
if etype == Type.Album:
__album__(conf, obj)
if etype == Type.Track:
__track__(conf, obj)
if etype == Type.Video:
__video__(conf, obj)
if etype == Type.Artist:
__artist__(conf, obj)
if etype == Type.Playlist:
__playlist__(conf, obj)
if etype == Type.Mix:
__mix__(conf, obj)
__setMetaData__(track, album, path, contributors, lyrics)
Printf.success(track.title)
return True
except Exception as e:
Printf.err(f"DL Track[{track.title}] failed.{str(e)}")
return False
+1 -1
View File
@@ -4,7 +4,7 @@
@File : enums.py
@Time : 2020/08/08
@Author : Yaronzz
@Version : 2.0
@Version : 3.0
@Contact : yaronhuang@foxmail.com
@Desc :
'''
+333
View File
@@ -0,0 +1,333 @@
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
"""
@File : events.py
@Date : 2022/06/10
@Author : Yaronzz
@Version : 1.0
@Contact : yaronhuang@foxmail.com
@Desc :
"""
import aigpy
import time
from tidal_dl.model import *
from tidal_dl.enums import *
from tidal_dl.tidal import *
from tidal_dl.printf import *
from tidal_dl.download import *
'''
=================================
START DOWNLOAD
=================================
'''
def start_album(obj: Album):
Printf.album(obj)
tracks, videos = TIDAL_API.getItems(obj.id, Type.Album)
if SETTINGS.saveAlbumInfo:
downloadAlbumInfo(obj, tracks)
if SETTINGS.saveCovers:
downloadCover(obj)
for item in tracks:
downloadTrack(item, obj)
for item in videos:
downloadVideo(item, obj)
def start_track(obj: Track):
album = TIDAL_API.getAlbum(obj.album.id)
if SETTINGS.saveCovers:
downloadCover(album)
downloadTrack(obj, album)
def start_video(obj: Video):
# Printf.video(obj)
downloadVideo(obj, obj.album)
def start_artist(obj: Artist):
albums = TIDAL_API.getArtistAlbums(obj.id, SETTINGS.includeEP)
Printf.artist(obj, len(albums))
for item in albums:
start_album(item)
def start_playlist(obj: Playlist):
Printf.playlist(obj)
tracks, videos = TIDAL_API.getItems(obj.uuid, Type.Playlist)
for index, item in enumerate(tracks):
album = TIDAL_API.getAlbum(item.album.id)
item.trackNumberOnPlaylist = index + 1
downloadTrack(item, album, obj)
if SETTINGS.saveCovers and not SETTINGS.usePlaylistFolder:
downloadCover(album)
for item in videos:
downloadVideo(item, None)
def start_mix(obj: Mix):
Printf.mix(obj)
for index, item in enumerate(obj.tracks):
album = TIDAL_API.getAlbum(item.album.id)
item.trackNumberOnPlaylist = index + 1
downloadTrack(item, album)
if SETTINGS.saveCovers and not SETTINGS.usePlaylistFolder:
downloadCover(album)
for item in obj.videos:
downloadVideo(item, None)
def start_file(string):
txt = aigpy.file.getContent(string)
if aigpy.string.isNull(txt):
Printf.err("Nothing can read!")
return
array = txt.split('\n')
for item in array:
if aigpy.string.isNull(item):
continue
if item[0] == '#':
continue
if item[0] == '[':
continue
start(item)
def start_type(etype: Type, obj):
if etype == Type.Album:
start_album(obj)
elif etype == Type.Track:
start_track(obj)
elif etype == Type.Video:
start_video(obj)
elif etype == Type.Artist:
start_artist(obj)
elif etype == Type.Playlist:
start_playlist(obj)
elif etype == Type.Mix:
start_mix(obj)
def start(string):
if aigpy.string.isNull(string):
Printf.err('Please enter something.')
return
strings = string.split(" ")
for item in strings:
if aigpy.string.isNull(item):
continue
if os.path.exists(item):
start_file(item)
return
try:
etype, obj = TIDAL_API.getByString(item)
except Exception as e:
Printf.err(str(e) + " [" + item + "]")
return
try:
start_type(etype, obj)
except Exception as e:
Printf.err(str(e))
'''
=================================
CHANGE SETTINGS
=================================
'''
def changePathSettings():
Printf.settings(SETTINGS)
SETTINGS.downloadPath = Printf.enterPath(
LANG.CHANGE_DOWNLOAD_PATH,
LANG.MSG_PATH_ERR,
'0',
SETTINGS.downloadPath)
SETTINGS.albumFolderFormat = Printf.enterFormat(
LANG.CHANGE_ALBUM_FOLDER_FORMAT,
SETTINGS.albumFolderFormat,
SETTINGS.getDefaultAlbumFolderFormat())
SETTINGS.trackFileFormat = Printf.enterFormat(
LANG.CHANGE_TRACK_FILE_FORMAT,
SETTINGS.trackFileFormat,
SETTINGS.getDefaultTrackFileFormat())
SETTINGS.videoFileFormat = Printf.enterFormat(
LANG.CHANGE_VIDEO_FILE_FORMAT,
SETTINGS.videoFileFormat,
SETTINGS.getDefaultVideoFileFormat())
SETTINGS.save()
def changeQualitySettings():
Printf.settings(SETTINGS)
SETTINGS.audioQuality = AudioQuality(
int(Printf.enterLimit(LANG.CHANGE_AUDIO_QUALITY,
LANG.MSG_INPUT_ERR,
['0', '1', '2', '3'])))
SETTINGS.videoQuality = VideoQuality(
int(Printf.enterLimit(LANG.CHANGE_VIDEO_QUALITY,
LANG.MSG_INPUT_ERR,
['1080', '720', '480', '360'])))
SETTINGS.save()
def changeSettings():
Printf.settings(SETTINGS)
SETTINGS.showProgress = Printf.enterBool(LANG.CHANGE_SHOW_PROGRESS)
SETTINGS.showTrackInfo = Printf.enterBool(LANG.CHANGE_SHOW_TRACKINFO)
SETTINGS.checkExist = Printf.enterBool(LANG.CHANGE_CHECK_EXIST)
SETTINGS.includeEP = Printf.enterBool(LANG.CHANGE_INCLUDE_EP)
SETTINGS.saveCovers = Printf.enterBool(LANG.CHANGE_SAVE_COVERS)
SETTINGS.saveAlbumInfo = Printf.enterBool(LANG.CHANGE_SAVE_ALBUM_INFO)
SETTINGS.lyricFile = Printf.enterBool(LANG.CHANGE_ADD_LRC_FILE)
SETTINGS.usePlaylistFolder = Printf.enterBool(LANG.SETTING_USE_PLAYLIST_FOLDER + "('0'-No,'1'-Yes):")
SETTINGS.language = Printf.enter(LANG.CHANGE_LANGUAGE + "(" + getLangChoicePrint() + "):")
LANG = setLang(SETTINGS.language)
SETTINGS.save()
def changeApiKey():
item = apiKey.getItem(SETTINGS.apiKeyIndex)
ver = apiKey.getVersion()
Printf.info(f'Current APIKeys: {str(SETTINGS.apiKeyIndex)} {item["platform"]}-{item["formats"]}')
Printf.info(f'Current Version: {str(ver)}')
Printf.apikeys(apiKey.getItems())
index = int(Printf.enterLimit("APIKEY index:", LANG.MSG_INPUT_ERR, apiKey.getLimitIndexs()))
if index != SETTINGS.apiKeyIndex:
SETTINGS.apiKeyIndex = index
SETTINGS.save()
TIDAL_API.apiKey = apiKey.getItem(index)
return True
return False
'''
=================================
LOGIN
=================================
'''
def __displayTime__(seconds, granularity=2):
if seconds <= 0:
return "unknown"
result = []
intervals = (
('weeks', 604800),
('days', 86400),
('hours', 3600),
('minutes', 60),
('seconds', 1),
)
for name, count in intervals:
value = seconds // count
if value:
seconds -= value * count
if value == 1:
name = name.rstrip('s')
result.append("{} {}".format(value, name))
return ', '.join(result[:granularity])
def loginByWeb():
try:
print(LANG.AUTH_START_LOGIN)
# get device code
url = TIDAL_API.getDeviceCode()
print(LANG.AUTH_NEXT_STEP.format(
aigpy.cmd.green(url),
aigpy.cmd.yellow(__displayTime__(TIDAL_API.key.authCheckTimeout))))
print(LANG.AUTH_WAITING)
start = time.time()
elapsed = 0
while elapsed < TIDAL_API.key.authCheckTimeout:
elapsed = time.time() - start
if not TIDAL_API.checkAuthStatus():
time.sleep(TIDAL_API.key.authCheckInterval + 1)
continue
Printf.success(LANG.MSG_VALID_ACCESSTOKEN.format(
__displayTime__(int(TIDAL_API.key.expiresIn))))
TOKEN.userid = TIDAL_API.key.userId
TOKEN.countryCode = TIDAL_API.key.countryCode
TOKEN.accessToken = TIDAL_API.key.accessToken
TOKEN.refreshToken = TIDAL_API.key.refreshToken
TOKEN.expiresAfter = time.time() + int(TIDAL_API.key.expiresIn)
TOKEN.save()
return True
raise Exception(LANG.AUTH_TIMEOUT)
except Exception as e:
Printf.err(f"Login failed.{str(e)}")
return False
def loginByConfig():
try:
if aigpy.string.isNull(TOKEN.accessToken):
return False
if TIDAL_API.verifyAccessToken(TOKEN.accessToken):
Printf.info(LANG.MSG_VALID_ACCESSTOKEN.format(
__displayTime__(int(TOKEN.expiresAfter - time.time()))))
TIDAL_API.key.countryCode = TOKEN.countryCode
TIDAL_API.key.userId = TOKEN.userid
TIDAL_API.key.accessToken = TOKEN.accessToken
return True
Printf.info(LANG.MSG_INVALID_ACCESSTOKEN)
if TIDAL_API.refreshAccessToken(TOKEN.refreshToken):
Printf.success(LANG.MSG_VALID_ACCESSTOKEN.format(
__displayTime__(int(TIDAL_API.key.expiresIn))))
TOKEN.userid = TIDAL_API.key.userId
TOKEN.countryCode = TIDAL_API.key.countryCode
TOKEN.accessToken = TIDAL_API.key.accessToken
TOKEN.expiresAfter = time.time() + int(TIDAL_API.key.expiresIn)
TOKEN.save()
return True
else:
TokenSettings().save()
return False
except Exception as e:
return False
def loginByAccessToken():
try:
print("-------------AccessToken---------------")
token = Printf.enter("accessToken('0' go back):")
if token == '0':
return
TIDAL_API.loginByAccessToken(token, TOKEN.userid)
except Exception as e:
Printf.err(str(e))
return
print("-------------RefreshToken---------------")
refreshToken = Printf.enter("refreshToken('0' to skip):")
if refreshToken == '0':
refreshToken = TOKEN.refreshToken
TOKEN.accessToken = token
TOKEN.refreshToken = refreshToken
TOKEN.expiresAfter = 0
TOKEN.countryCode = TIDAL_API.key.countryCode
TOKEN.save()
+180
View File
@@ -0,0 +1,180 @@
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
"""
@File : test.py
@Date : 2022/03/28
@Author : Yaronzz
@Version : 1.0
@Contact : yaronhuang@foxmail.com
@Desc :
"""
import os
import sys
import _thread
from tidal_dl.events import *
from tidal_dl.settings import *
from PyQt5.QtCore import Qt
from PyQt5.QtCore import pyqtSignal
from PyQt5 import QtWidgets
from qt_material import apply_stylesheet
class MainView(QtWidgets.QWidget):
s_downloadEnd = pyqtSignal(str, bool, str)
def __init__(self, ) -> None:
super().__init__()
self.initView()
self.setMinimumSize(600, 500)
self.setWindowTitle("Tidal-dl")
def __info__(self, msg):
QtWidgets.QMessageBox.information(self,
'Info',
msg,
QtWidgets.QMessageBox.Yes)
def initView(self):
self.c_lineSearch = QtWidgets.QLineEdit()
self.c_btnSearch = QtWidgets.QPushButton("Search")
self.c_btnDownload = QtWidgets.QPushButton("Download")
self.m_supportType = [Type.Album, Type.Playlist, Type.Track, Type.Video]
self.c_combType = QtWidgets.QComboBox()
for item in self.m_supportType:
self.c_combType.addItem(item.name, item)
columnNames = ['#', 'Title', 'Artists', 'Quality']
self.c_tableInfo = QtWidgets.QTableWidget()
self.c_tableInfo.setColumnCount(len(columnNames))
self.c_tableInfo.setRowCount(0)
self.c_tableInfo.setShowGrid(False)
self.c_tableInfo.verticalHeader().setVisible(False)
self.c_tableInfo.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows)
self.c_tableInfo.setSelectionMode(QtWidgets.QAbstractItemView.SingleSelection)
self.c_tableInfo.horizontalHeader().setStretchLastSection(True)
self.c_tableInfo.horizontalHeader().setSectionResizeMode(QtWidgets.QHeaderView.ResizeMode.ResizeToContents)
self.c_tableInfo.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers)
self.c_tableInfo.setFocusPolicy(Qt.NoFocus)
for index, name in enumerate(columnNames):
item = QtWidgets.QTableWidgetItem(name)
self.c_tableInfo.setHorizontalHeaderItem(index, item)
self.lineGrid = QtWidgets.QHBoxLayout()
self.lineGrid.addWidget(self.c_combType)
self.lineGrid.addWidget(self.c_lineSearch)
self.lineGrid.addWidget(self.c_btnSearch)
self.mainGrid = QtWidgets.QVBoxLayout(self)
self.mainGrid.addLayout(self.lineGrid)
self.mainGrid.addWidget(self.c_tableInfo)
self.mainGrid.addWidget(self.c_btnDownload)
self.c_btnSearch.clicked.connect(self.search)
self.c_btnDownload.clicked.connect(self.download)
self.s_downloadEnd.connect(self.downloadEnd)
def addItem(self, rowIdx: int, colIdx: int, text):
if isinstance(text, str):
item = QtWidgets.QTableWidgetItem(text)
self.c_tableInfo.setItem(rowIdx, colIdx, item)
def search(self):
self.c_tableInfo.setRowCount(0)
self.s_type = self.c_combType.currentData()
self.s_text = self.c_lineSearch.text()
if self.s_text.startswith('http'):
tmpType, tmpId = TIDAL_API.parseUrl(self.s_text)
if tmpType == Type.Null:
self.__info__('Url not support')
return
elif tmpType not in self.m_supportType:
self.__info__(f'Type[{tmpType.name}] not support')
return
tmpData = TIDAL_API.getTypeData(tmpId, tmpType)
if tmpData is None:
self.__info__('Url is wrong!')
return
self.s_type = tmpType
self.s_array = [tmpData]
self.s_result = None
self.c_combType.setCurrentText(tmpType.name)
else:
self.s_result = TIDAL_API.search(self.s_text, self.s_type)
self.s_array = TIDAL_API.getSearchResultItems(self.s_result, self.s_type)
if len(self.s_array) <= 0:
self.__info__('No result')
return
self.c_tableInfo.setRowCount(len(self.s_array))
for index, item in enumerate(self.s_array):
self.addItem(index, 0, str(index + 1))
if self.s_type in [Type.Album, Type.Track]:
self.addItem(index, 1, item.title)
self.addItem(index, 2, TIDAL_API.getArtistsName(item.artists))
self.addItem(index, 3, item.audioQuality)
elif self.s_type in [Type.Video]:
self.addItem(index, 1, item.title)
self.addItem(index, 2, TIDAL_API.getArtistsName(item.artists))
self.addItem(index, 3, item.quality)
elif self.s_type in [Type.Playlist]:
self.addItem(index, 1, item.title)
self.addItem(index, 2, '')
self.addItem(index, 3, '')
def download(self):
index = self.c_tableInfo.currentIndex().row()
if index < 0:
self.__info__('Please select a row first.')
return
self.c_btnDownload.setEnabled(False)
self.c_btnDownload.setText(f"Downloading [{self.s_array[index].title}]...")
def __thread_download__(model: MainView):
try:
type = model.s_type
item = model.s_array[index]
start_type(type, item)
model.s_downloadEnd.emit(item.title, True, '')
except Exception as e:
model.s_downloadEnd.emit(item.title, False, str(e))
_thread.start_new_thread(__thread_download__, (self, ))
def downloadEnd(self, title, result, msg):
self.c_btnDownload.setEnabled(True)
self.c_btnDownload.setText(f"Download")
if result:
self.__info__(f'Download [{title}] finish')
else:
self.__info__(f'Download [{title}] failed:{msg}')
def checkLogin(self):
if not loginByConfig():
self.__info__('Login failed. Please log in using the command line first.')
def startGui():
os.chdir(sys.path[0])
app = QtWidgets.QApplication(sys.argv)
apply_stylesheet(app, theme='dark_blue.xml')
window = MainView()
window.show()
window.checkLogin()
app.exec_()
if __name__ == '__main__':
SETTINGS.read(getProfilePath())
TOKEN.read(getTokenPath())
startGui()
+4 -2
View File
@@ -31,8 +31,9 @@ class LangArabic(object):
SETTING_MULITHREAD_DOWNLOAD = "Multi thread download"
SETTING_ALBUM_FOLDER_FORMAT = "Album folder format"
SETTING_TRACK_FILE_FORMAT = "Track file format"
SETTING_VIDEO_FILE_FORMAT = "Video file format"
SETTING_SHOW_PROGRESS = "Show progress"
SETTING_SHOW_TRACKIFNO = "Show Track Info"
SETTING_SHOW_TRACKINFO = "Show Track Info"
SETTING_SAVE_ALBUMINFO = "Save AlbumInfo.txt"
SETTING_ADD_LYRICS = "Add lyrics"
SETTING_LYRICS_SERVER_PROXY = "Lyrics server proxy"
@@ -79,6 +80,7 @@ class LangArabic(object):
CHANGE_LANGUAGE = "اختر لغة"
CHANGE_ALBUM_FOLDER_FORMAT = "Album folder format('0' not modify):"
CHANGE_TRACK_FILE_FORMAT = "Track file format('0' not modify):"
CHANGE_VIDEO_FILE_FORMAT = "Video file format('0'-not modify,'default'-to set default):"
CHANGE_SHOW_PROGRESS = "Show progress('0'-No,'1'-Yes):"
CHANGE_SHOW_TRACKINFO = "Show track info('0'-No,'1'-Yes):"
CHANGE_SAVE_ALBUM_INFO = "Save AlbumInfo.txt('0'-No,'1'-Yes):"
@@ -95,7 +97,7 @@ class LangArabic(object):
AUTH_TIMEOUT = "Operation timed out."
MSG_VALID_ACCESSTOKEN = "AccessToken good for {}."
MSG_INVAILD_ACCESSTOKEN = "Expired AccessToken. Attempting to refresh it."
MSG_INVALID_ACCESSTOKEN = "Expired AccessToken. Attempting to refresh it."
MSG_PATH_ERR = "!مجلد التنزيل خاطئ"
MSG_INPUT_ERR = "!ادخال خاطئ"
+8 -6
View File
@@ -15,7 +15,7 @@ class LangChinese(object):
VALUE = ""
SETTING_DOWNLOAD_PATH = "下载目录"
SETTING_ONLY_M4A = "转换mp4为m4a"
SETTING_ADD_EXPLICIT_TAG = "文件名添加脏话标签Explicit"
SETTING_ADD_EXPLICIT_TAG = "文件名添加脏Explicit"
SETTING_ADD_HYPHEN = "文件名用'-'代替空格"
SETTING_ADD_YEAR = "专辑文件夹添加年代标签"
SETTING_USE_TRACK_NUM = "歌曲名称添加序号"
@@ -31,8 +31,9 @@ class LangChinese(object):
SETTING_MULITHREAD_DOWNLOAD = "多线程下载"
SETTING_ALBUM_FOLDER_FORMAT = "专辑目录格式"
SETTING_TRACK_FILE_FORMAT = "歌曲文件名格式"
SETTING_VIDEO_FILE_FORMAT = "视频文件格式"
SETTING_SHOW_PROGRESS = "显示进度条"
SETTING_SHOW_TRACKIFNO = "显示歌曲信息"
SETTING_SHOW_TRACKINFO = "显示歌曲信息"
SETTING_SAVE_ALBUMINFO = "保存AlbumInfo.txt"
SETTING_ADD_LYRICS = "添加歌词"
SETTING_LYRICS_SERVER_PROXY = "歌词服务器代理"
@@ -46,7 +47,7 @@ class LangChinese(object):
CHOICE_ENTER = "输入"
CHOICE_ENTER_URLID = "输入 'Url或ID':"
CHOICE_EXIT = "退出"
CHOICE_LOGIN = "Check AccessToken"
CHOICE_LOGIN = "检查 AccessToken"
CHOICE_SETTINGS = "配置"
CHOICE_SET_ACCESS_TOKEN = "设置AccessToken"
CHOICE_DOWNLOAD_BY_URL = "通过链接或ID下载"
@@ -79,6 +80,7 @@ class LangChinese(object):
CHANGE_LANGUAGE = "选择语言"
CHANGE_ALBUM_FOLDER_FORMAT = "专辑目录格式('0' 不修改):"
CHANGE_TRACK_FILE_FORMAT = "歌曲文件名格式('0' 不修改):"
CHANGE_VIDEO_FILE_FORMAT = "Video file format('0'-not modify,'default'-to set default):"
CHANGE_SHOW_PROGRESS = "显示进度条('0'-不,'1'-是):"
CHANGE_SHOW_TRACKINFO = "显示歌曲信息('0'-否,'1'-是):"
CHANGE_SAVE_ALBUM_INFO = "保存AlbumInfo.txt('0'-否,'1'-是):"
@@ -94,8 +96,8 @@ class LangChinese(object):
AUTH_WAITING = "等待登录验证..."
AUTH_TIMEOUT = "操作超时."
MSG_VALID_ACCESSTOKEN = "AccessToken保质期为 {}."
MSG_INVAILD_ACCESSTOKEN = "AccessToken失效. 正在尝试更新它."
MSG_VALID_ACCESSTOKEN = "AccessToken有效期为 {}."
MSG_INVALID_ACCESSTOKEN = "AccessToken失效. 正在尝试更新它."
MSG_PATH_ERR = "路径错误!"
MSG_INPUT_ERR = "输入错误!"
@@ -110,7 +112,7 @@ class LangChinese(object):
MODEL_VIDEO_NUMBER = '视频数量'
MODEL_RELEASE_DATE = '发布时间'
MODEL_VERSION = '版本'
MODEL_EXPLICIT = '话标志'
MODEL_EXPLICIT = ''
MODEL_ALBUM = '专辑'
MODEL_ID = 'ID'
MODEL_NAME = '名称'
+4 -2
View File
@@ -31,8 +31,9 @@ class LangCroatian(object):
SETTING_MULITHREAD_DOWNLOAD = "Multi thread download"
SETTING_ALBUM_FOLDER_FORMAT = "Album folder format"
SETTING_TRACK_FILE_FORMAT = "Track file format"
SETTING_VIDEO_FILE_FORMAT = "Video file format"
SETTING_SHOW_PROGRESS = "Show progress"
SETTING_SHOW_TRACKIFNO = "Show Track Info"
SETTING_SHOW_TRACKINFO = "Show Track Info"
SETTING_SAVE_ALBUMINFO = "Save AlbumInfo.txt"
SETTING_ADD_LYRICS = "Add lyrics"
SETTING_LYRICS_SERVER_PROXY = "Lyrics server proxy"
@@ -79,6 +80,7 @@ class LangCroatian(object):
CHANGE_LANGUAGE = "Odaberi jezik"
CHANGE_ALBUM_FOLDER_FORMAT = "Album folder format('0' not modify):"
CHANGE_TRACK_FILE_FORMAT = "Track file format('0' not modify):"
CHANGE_VIDEO_FILE_FORMAT = "Video file format('0'-not modify,'default'-to set default):"
CHANGE_SHOW_PROGRESS = "Show progress('0'-No,'1'-Yes):"
CHANGE_SHOW_TRACKINFO = "Show track info('0'-No,'1'-Yes):"
CHANGE_SAVE_ALBUM_INFO = "Save AlbumInfo.txt('0'-No,'1'-Yes):"
@@ -95,7 +97,7 @@ class LangCroatian(object):
AUTH_TIMEOUT = "Operation timed out."
MSG_VALID_ACCESSTOKEN = "AccessToken good for {}."
MSG_INVAILD_ACCESSTOKEN = "Expired AccessToken. Attempting to refresh it."
MSG_INVALID_ACCESSTOKEN = "Expired AccessToken. Attempting to refresh it."
MSG_PATH_ERR = "Pogreska putanje!"
MSG_INPUT_ERR = "Pogreska unosa!"
+4 -2
View File
@@ -31,8 +31,9 @@ class LangCzech(object):
SETTING_MULITHREAD_DOWNLOAD = "Multi thread download"
SETTING_ALBUM_FOLDER_FORMAT = "Album folder format"
SETTING_TRACK_FILE_FORMAT = "Track file format"
SETTING_VIDEO_FILE_FORMAT = "Video file format"
SETTING_SHOW_PROGRESS = "Show progress"
SETTING_SHOW_TRACKIFNO = "Show Track Info"
SETTING_SHOW_TRACKINFO = "Show Track Info"
SETTING_SAVE_ALBUMINFO = "Save AlbumInfo.txt"
SETTING_ADD_LYRICS = "Add lyrics"
SETTING_LYRICS_SERVER_PROXY = "Lyrics server proxy"
@@ -79,6 +80,7 @@ class LangCzech(object):
CHANGE_LANGUAGE = "Zvolit jazyk"
CHANGE_ALBUM_FOLDER_FORMAT = "Album folder format('0' not modify):"
CHANGE_TRACK_FILE_FORMAT = "Track file format('0' not modify):"
CHANGE_VIDEO_FILE_FORMAT = "Video file format('0'-not modify,'default'-to set default):"
CHANGE_SHOW_PROGRESS = "Show progress('0'-No,'1'-Yes):"
CHANGE_SHOW_TRACKINFO = "Show track info('0'-No,'1'-Yes):"
CHANGE_SAVE_ALBUM_INFO = "Save AlbumInfo.txt('0'-No,'1'-Yes):"
@@ -95,7 +97,7 @@ class LangCzech(object):
AUTH_TIMEOUT = "Operation timed out."
MSG_VALID_ACCESSTOKEN = "AccessToken good for {}."
MSG_INVAILD_ACCESSTOKEN = "Expired AccessToken. Attempting to refresh it."
MSG_INVALID_ACCESSTOKEN = "Expired AccessToken. Attempting to refresh it."
MSG_PATH_ERR = "Cesta neexistuje!"
MSG_INPUT_ERR = "Chyba vstupu!"
+4 -2
View File
@@ -31,8 +31,9 @@ class LangDanish(object):
SETTING_MULITHREAD_DOWNLOAD = "Flertråede download"
SETTING_ALBUM_FOLDER_FORMAT = "Albummappens format"
SETTING_TRACK_FILE_FORMAT = "Musiknummerets filformat"
SETTING_VIDEO_FILE_FORMAT = "Video file format"
SETTING_SHOW_PROGRESS = "Vis fremskridt"
SETTING_SHOW_TRACKIFNO = "Show Track Info"
SETTING_SHOW_TRACKINFO = "Show Track Info"
SETTING_SAVE_ALBUMINFO = "Save AlbumInfo.txt"
SETTING_ADD_LYRICS = "Add lyrics"
SETTING_LYRICS_SERVER_PROXY = "Lyrics server proxy"
@@ -79,6 +80,7 @@ class LangDanish(object):
CHANGE_LANGUAGE = "Vælg sprog"
CHANGE_ALBUM_FOLDER_FORMAT = "Albummappeformat('0' Ændrer ikke, 'default' for at indstille som standard):"
CHANGE_TRACK_FILE_FORMAT = "Musiknummerets filformat('0' Ændrer ikke, 'default' for at indstille som standard):"
CHANGE_VIDEO_FILE_FORMAT = "Video file format('0'-not modify,'default'-to set default):"
CHANGE_SHOW_PROGRESS = "Vis fremskridt('0'-Nej,'1'-Ja):"
CHANGE_SHOW_TRACKINFO = "Show track info('0'-No,'1'-Yes):"
CHANGE_SAVE_ALBUM_INFO = "Save AlbumInfo.txt('0'-No,'1'-Yes):"
@@ -95,7 +97,7 @@ class LangDanish(object):
AUTH_TIMEOUT = "Tiden løb ud."
MSG_VALID_ACCESSTOKEN = "AccessToken tilgængelig for {}."
MSG_INVAILD_ACCESSTOKEN = "AccessToken udløb. Forsøger at opdatere"
MSG_INVALID_ACCESSTOKEN = "AccessToken udløb. Forsøger at opdatere"
MSG_PATH_ERR = "Sti fejl!"
MSG_INPUT_ERR = "Indtastningsfejl!"
+119
View File
@@ -0,0 +1,119 @@
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
'''
@File : dutch.py
@Time : 2022/03/01
@Author : bladeoner
@Version : 1.0
@Contact :
@Desc :
'''
class LangDutch(object):
SETTING = "INSTELLINGEN"
VALUE = "WAARDE"
SETTING_DOWNLOAD_PATH = "Download pad"
SETTING_ONLY_M4A = "mp4 naar m4a converteren"
SETTING_ADD_EXPLICIT_TAG = "Expliciete tag toevoegen"
SETTING_ADD_HYPHEN = "Koppelteken toevoegen"
SETTING_ADD_YEAR = "Jaar voor albummap toevoegen"
SETTING_USE_TRACK_NUM = "Tracknummer gebruiker toevoegen"
SETTING_AUDIO_QUALITY = "Audiokwaliteit"
SETTING_VIDEO_QUALITY = "Videokwaliteit"
SETTING_CHECK_EXIST = "Controleer of al bestaat"
SETTING_ARTIST_BEFORE_TITLE = "Artiestnaam voor tracktitel"
SETTING_ALBUMID_BEFORE_FOLDER = "ID voor albummap"
SETTING_INCLUDE_EP = "Inclusief singles en EP's"
SETTING_SAVE_COVERS = "Bewaar covers"
SETTING_LANGUAGE = "Taal"
SETTING_USE_PLAYLIST_FOLDER = "Afspeellijst gebruiken"
SETTING_MULITHREAD_DOWNLOAD = "Downloaden met meerdere threads"
SETTING_ALBUM_FOLDER_FORMAT = "Indeling albummap"
SETTING_TRACK_FILE_FORMAT = "Bestandsindeling bijhouden"
SETTING_VIDEO_FILE_FORMAT = "Videobestandsindeling"
SETTING_SHOW_PROGRESS = "Toon voortgang"
SETTING_SHOW_TRACKINFO = "Toon trackinfo"
SETTING_SAVE_ALBUMINFO = "AlbumInfo.txt opslaan"
SETTING_ADD_LYRICS = "Songtekst toevoegen"
SETTING_LYRICS_SERVER_PROXY = "Tekst server proxy"
SETTINGS_ADD_LRC_FILE = "Getimede songteksten opslaan (.lrc-bestand)"
SETTING_PATH = "Instellingen pad"
SETTING_APIKEY = "APIKey-ondersteuning"
SETTING_ADD_TYPE_FOLDER = "Typemap toevoegen"
CHOICE = "KEUZE"
FUNCTION = "FUNCTIE"
CHOICE_ENTER = "Voer in"
CHOICE_ENTER_URLID = "Vul 'Url/ID' in:"
CHOICE_EXIT = "Verlaten"
CHOICE_LOGIN = "Toegangstoken controleren"
CHOICE_SETTINGS = "Instellingen"
CHOICE_SET_ACCESS_TOKEN = "Toegangstoken instellen"
CHOICE_DOWNLOAD_BY_URL = "Downloaden via url of ID"
CHOICE_LOGOUT = "Uitloggen"
CHOICE_APIKEY = "Selecteer APIKey"
PRINT_ERR = "[FOUT]"
PRINT_INFO = "[INFO]"
PRINT_SUCCESS = "[SUCCESS]"
PRINT_ENTER_CHOICE = "Voer keuze in:"
PRINT_LATEST_VERSION = "Laatste versie:"
# PRINT_USERNAME = "gebruikersnaam:"
# PRINT_PASSWORD = "wachtwoord:"
CHANGE_START_SETTINGS = "Start instellingen('0'-Terugkeren,'1'-Ja):"
CHANGE_DOWNLOAD_PATH = "Downloadpad('0'-niet wijzigen):"
CHANGE_AUDIO_QUALITY = "Audiokwaliteit('0'-Normaal,'1'-Hoog,'2'-HiFi,'3'-Master):"
CHANGE_VIDEO_QUALITY = "Videokwaliteit (1080, 720, 480, 360):"
CHANGE_ONLYM4A = "Converteer mp4 naar m4a('0'-Nee,'1'-Ja):"
CHANGE_ADD_EXPLICIT_TAG = "Expliciete tag toevoegen aan bestandsnamen('0'-Nee,'1'-Ja):"
CHANGE_ADD_HYPHEN = "Gebruik koppeltekens in plaats van spaties in bestandsnamen ('0'-Nee,'1'-Ja):"
CHANGE_ADD_YEAR = "Jaar toevoegen aan albummapnamen ('0'-Nee,'1'-Ja):"
CHANGE_USE_TRACK_NUM = "Voeg tracknummer toe voor bestandsnamen ('0'-Nee,'1'-Ja):"
CHANGE_CHECK_EXIST = "Controleer het bestaande bestand voordat u de track downloadt('0'-Nee,'1'-Ja):"
CHANGE_ARTIST_BEFORE_TITLE = "Voeg artiestnaam toe voor de tracktitel ('0'-Nee,'1'-Ja):"
CHANGE_INCLUDE_EP = "Voeg singles en EP's toe bij het downloaden van de albums van een artiest('0'-Nee,'1'-Ja):"
CHANGE_ALBUMID_BEFORE_FOLDER = "ID toevoegen voor albummap ('0'-Nee,'1'-Ja):"
CHANGE_SAVE_COVERS = "Covers opslaan('0'-Nee,'1'-Ja):"
CHANGE_LANGUAGE = "Selecteer taal"
CHANGE_ALBUM_FOLDER_FORMAT = "Albummapindeling ('0'-niet wijzigen,'standaard'-om standaard in te stellen):"
CHANGE_TRACK_FILE_FORMAT = "Bestandsformaat bijhouden ('0'-niet wijzigen,'standaard'-om standaard in te stellen):"
CHANGE_VIDEO_FILE_FORMAT = "Videobestandsindeling('0'-niet wijzigen,'standaard'-om standaard in te stellen):"
CHANGE_SHOW_PROGRESS = "Voortgang weergeven('0'-Nee,'1'-Ja):"
CHANGE_SHOW_TRACKINFO = "Toon trackinfo('0'-Nee,'1'-Ja):"
CHANGE_SAVE_ALBUM_INFO = "Bewaar AlbumInfo.txt('0'-Nee,'1'-Ja):"
CHANGE_ADD_LYRICS = "Songtekst toevoegen('0'-Nee,'1'-Ja):"
CHANGE_LYRICS_SERVER_PROXY = "Songtekst proxyserver('0'-niet wijzigen):"
CHANGE_ADD_LRC_FILE = "Sla getimede songtekst .lrc-bestand op ('0'-Nee,'1'-Ja):"
CHANGE_ADD_TYPE_FOLDER = "Type-map toevoegen, bijv. Album/Video/Playlist('0'-Nee,'1'-Ja):"
# {} are required in these strings
AUTH_START_LOGIN = "Inlogproces starten..."
AUTH_LOGIN_CODE = "Uw inlogcode is {}"
AUTH_NEXT_STEP = "Ga naar {} in de volgende {} om de installatie te voltooien."
AUTH_WAITING = "Wachten op toestemming..."
AUTH_TIMEOUT = "Operatie time-out."
MSG_VALID_ACCESSTOKEN = "Toegangstoken goed voor {}."
MSG_INVALID_ACCESSTOKEN = "Verlopen AccessToken. Poging om het te vernieuwen."
MSG_PATH_ERR = "Pad is incorrect!"
MSG_INPUT_ERR = "Invoerfout!"
MODEL_ALBUM_PROPERTY = "ALBUM-EIGENSCHAP"
MODEL_TRACK_PROPERTY = "TRACK-EIGENSCHAP"
MODEL_VIDEO_PROPERTY = "VIDEO-EIGENSCHAP"
MODEL_ARTIST_PROPERTY = "ARTIEST-EIGENSCHAP"
MODEL_PLAYLIST_PROPERTY = "AFSPEELLIJST-EIGENSCHAP"
MODEL_TITLE = 'Titel'
MODEL_TRACK_NUMBER = 'Tracknummer'
MODEL_VIDEO_NUMBER = 'Videonummer'
MODEL_RELEASE_DATE = 'Publicatiedatum'
MODEL_VERSION = 'Versie'
MODEL_EXPLICIT = 'Expliciet'
MODEL_ALBUM = 'Album'
MODEL_ID = 'ID'
MODEL_NAME = 'Naam'
MODEL_TYPE = 'Type'
+4 -2
View File
@@ -31,8 +31,9 @@ class LangEnglish(object):
SETTING_MULITHREAD_DOWNLOAD = "Multi thread download"
SETTING_ALBUM_FOLDER_FORMAT = "Album folder format"
SETTING_TRACK_FILE_FORMAT = "Track file format"
SETTING_VIDEO_FILE_FORMAT = "Video file format"
SETTING_SHOW_PROGRESS = "Show progress"
SETTING_SHOW_TRACKIFNO = "Show Track Info"
SETTING_SHOW_TRACKINFO = "Show Track Info"
SETTING_SAVE_ALBUMINFO = "Save AlbumInfo.txt"
SETTING_ADD_LYRICS = "Add lyrics"
SETTING_LYRICS_SERVER_PROXY = "Lyrics server proxy"
@@ -79,6 +80,7 @@ class LangEnglish(object):
CHANGE_LANGUAGE = "Select language"
CHANGE_ALBUM_FOLDER_FORMAT = "Album folder format('0'-not modify,'default'-to set default):"
CHANGE_TRACK_FILE_FORMAT = "Track file format('0'-not modify,'default'-to set default):"
CHANGE_VIDEO_FILE_FORMAT = "Video file format('0'-not modify,'default'-to set default):"
CHANGE_SHOW_PROGRESS = "Show progress('0'-No,'1'-Yes):"
CHANGE_SHOW_TRACKINFO = "Show track info('0'-No,'1'-Yes):"
CHANGE_SAVE_ALBUM_INFO = "Save AlbumInfo.txt('0'-No,'1'-Yes):"
@@ -95,7 +97,7 @@ class LangEnglish(object):
AUTH_TIMEOUT = "Operation timed out."
MSG_VALID_ACCESSTOKEN = "AccessToken good for {}."
MSG_INVAILD_ACCESSTOKEN = "Expired AccessToken. Attempting to refresh it."
MSG_INVALID_ACCESSTOKEN = "Expired AccessToken. Attempting to refresh it."
MSG_PATH_ERR = "Path is error!"
MSG_INPUT_ERR = "Input error!"
+4 -2
View File
@@ -31,8 +31,9 @@ class LangFilipino(object):
SETTING_MULITHREAD_DOWNLOAD = "Multi thread download"
SETTING_ALBUM_FOLDER_FORMAT = "Album folder format"
SETTING_TRACK_FILE_FORMAT = "Track file format"
SETTING_VIDEO_FILE_FORMAT = "Video file format"
SETTING_SHOW_PROGRESS = "Show progress"
SETTING_SHOW_TRACKIFNO = "Show Track Info"
SETTING_SHOW_TRACKINFO = "Show Track Info"
SETTING_SAVE_ALBUMINFO = "Save AlbumInfo.txt"
SETTING_ADD_LYRICS = "Add lyrics"
SETTING_LYRICS_SERVER_PROXY = "Lyrics server proxy"
@@ -79,6 +80,7 @@ class LangFilipino(object):
CHANGE_LANGUAGE = "Pumili ng lenggwahe"
CHANGE_ALBUM_FOLDER_FORMAT = "Album folder format('0' not modify):"
CHANGE_TRACK_FILE_FORMAT = "Track file format('0' not modify):"
CHANGE_VIDEO_FILE_FORMAT = "Video file format('0'-not modify,'default'-to set default):"
CHANGE_SHOW_PROGRESS = "Show progress('0'-No,'1'-Yes):"
CHANGE_SHOW_TRACKINFO = "Show track info('0'-No,'1'-Yes):"
CHANGE_SAVE_ALBUM_INFO = "Save AlbumInfo.txt('0'-No,'1'-Yes):"
@@ -95,7 +97,7 @@ class LangFilipino(object):
AUTH_TIMEOUT = "Operation timed out."
MSG_VALID_ACCESSTOKEN = "AccessToken good for {}."
MSG_INVAILD_ACCESSTOKEN = "Expired AccessToken. Attempting to refresh it."
MSG_INVALID_ACCESSTOKEN = "Expired AccessToken. Attempting to refresh it."
MSG_PATH_ERR = "May error sa paroroonan ng download!"
MSG_INPUT_ERR = "May error sa pag-input!"
+4 -2
View File
@@ -31,8 +31,9 @@ class LangFrench(object):
SETTING_MULITHREAD_DOWNLOAD = "Téléchargement multithread"
SETTING_ALBUM_FOLDER_FORMAT = "Format du dossier d'album"
SETTING_TRACK_FILE_FORMAT = "Format du fichier de tracklist"
SETTING_VIDEO_FILE_FORMAT = "Video file format"
SETTING_SHOW_PROGRESS = "Afficher la Progression"
SETTING_SHOW_TRACKIFNO = "Show Track Info"
SETTING_SHOW_TRACKINFO = "Show Track Info"
SETTING_SAVE_ALBUMINFO = "Save AlbumInfo.txt"
SETTING_ADD_LYRICS = "Add lyrics"
SETTING_LYRICS_SERVER_PROXY = "Lyrics server proxy"
@@ -79,6 +80,7 @@ class LangFrench(object):
CHANGE_LANGUAGE = "Sélectionnez une langue"
CHANGE_ALBUM_FOLDER_FORMAT = "Format du dossier d'album('0' ne pas modifier):"
CHANGE_TRACK_FILE_FORMAT = "Format du fichier de tracklist('0' ne pas modifier):"
CHANGE_VIDEO_FILE_FORMAT = "Video file format('0'-not modify,'default'-to set default):"
CHANGE_SHOW_PROGRESS = "Afficher la progression('0'-Non,'1'-Oui):"
CHANGE_SHOW_TRACKINFO = "Show track info('0'-No,'1'-Yes):"
CHANGE_SAVE_ALBUM_INFO = "Save AlbumInfo.txt('0'-No,'1'-Yes):"
@@ -95,7 +97,7 @@ class LangFrench(object):
AUTH_TIMEOUT = "Operation timed out."
MSG_VALID_ACCESSTOKEN = "AccessToken good for {}."
MSG_INVAILD_ACCESSTOKEN = "Expired AccessToken. Attempting to refresh it."
MSG_INVALID_ACCESSTOKEN = "Expired AccessToken. Attempting to refresh it."
MSG_PATH_ERR = "Erreur du chemin d'accès"
MSG_INPUT_ERR = "Erreur de saisie !"
+4 -2
View File
@@ -31,8 +31,9 @@ class LangGerman(object):
SETTING_MULITHREAD_DOWNLOAD = "Multi-Thread-Download"
SETTING_ALBUM_FOLDER_FORMAT = "Album-Ordnerformat"
SETTING_TRACK_FILE_FORMAT = "Track-Dateiformat"
SETTING_VIDEO_FILE_FORMAT = "Video file format"
SETTING_SHOW_PROGRESS = "Fortschritt anzeigen"
SETTING_SHOW_TRACKIFNO = "Show Track Info"
SETTING_SHOW_TRACKINFO = "Show Track Info"
SETTING_SAVE_ALBUMINFO = "Save AlbumInfo.txt"
SETTING_ADD_LYRICS = "Add lyrics"
SETTING_LYRICS_SERVER_PROXY = "Lyrics server proxy"
@@ -79,6 +80,7 @@ class LangGerman(object):
CHANGE_LANGUAGE = "Sprache auswählen"
CHANGE_ALBUM_FOLDER_FORMAT = "Album-Ordnerformat('0' überspringen):"
CHANGE_TRACK_FILE_FORMAT = "Track-Dateiformat('0' überspringen):"
CHANGE_VIDEO_FILE_FORMAT = "Video file format('0'-not modify,'default'-to set default):"
CHANGE_SHOW_PROGRESS = "Fortschritt anzeigen('0'-Nein,'1'-Ja):"
CHANGE_SHOW_TRACKINFO = "Show track info('0'-No,'1'-Yes):"
CHANGE_SAVE_ALBUM_INFO = "Save AlbumInfo.txt('0'-No,'1'-Yes):"
@@ -95,7 +97,7 @@ class LangGerman(object):
AUTH_TIMEOUT = "Zeitüberschreitung der Operation."
MSG_VALID_ACCESSTOKEN = "AccessToken gültig für {}."
MSG_INVAILD_ACCESSTOKEN = "AccessToken abgelaufen. Versuche zu erneuern."
MSG_INVALID_ACCESSTOKEN = "AccessToken abgelaufen. Versuche zu erneuern."
MSG_PATH_ERR = "Ungültiger Pfad!"
MSG_INPUT_ERR = "Eingabefehler!"
+21 -19
View File
@@ -2,9 +2,9 @@
# -*- encoding: utf-8 -*-
'''
@File : hungarian.py
@Time : 2021/06/20
@Time : 2022/02/10
@Author : Shanahan
@Version : 1.0
@Version : 1.1
@Contact :
@Desc :
'''
@@ -25,21 +25,22 @@ class LangHungarian(object):
SETTING_ARTIST_BEFORE_TITLE = "Előadó neve a szám címe előtt"
SETTING_ALBUMID_BEFORE_FOLDER = "Azonosító (ID) az album-mappa előtt"
SETTING_INCLUDE_EP = "Tartalmazza a single&ep"
SETTING_SAVE_COVERS = "Borító mentése"
SETTING_SAVE_COVERS = "Borítókép mentése"
SETTING_LANGUAGE = "Nyelv"
SETTING_USE_PLAYLIST_FOLDER = "Lejátszási lista mappa használata"
SETTING_MULITHREAD_DOWNLOAD = "Többszálú letöltés"
SETTING_ALBUM_FOLDER_FORMAT = "Album mappa formátum"
SETTING_TRACK_FILE_FORMAT = "Track fájlformátum"
SETTING_VIDEO_FILE_FORMAT = "Video file format"
SETTING_SHOW_PROGRESS = "Haladás megjelenítése"
SETTING_SHOW_TRACKIFNO = "Show Track Info"
SETTING_SHOW_TRACKINFO = "Track infók megjelenítése"
SETTING_SAVE_ALBUMINFO = "AlbumInfo.txt mentése"
SETTING_ADD_LYRICS = "Dalszöveg hozzáadása"
SETTING_LYRICS_SERVER_PROXY = "Dalszöveg kiszolgáló proxy"
SETTING_PATH = "Beállítások elérési útvonala"
SETTINGS_ADD_LRC_FILE = "Save timed lyrics (.lrc file)"
SETTING_APIKEY = "APIKey support"
SETTING_ADD_TYPE_FOLDER = "Add Type-Folder"
SETTING_PATH = "Beállítási útvonal"
SETTINGS_ADD_LRC_FILE = "Dalszövegek mentése (.lrc fájl)"
SETTING_APIKEY = "APIKey támogatás"
SETTING_ADD_TYPE_FOLDER = "Mappa típus hozzáadása"
CHOICE = "VÁLASZTÁS"
FUNCTION = "FUNKCIÓ"
@@ -51,7 +52,7 @@ class LangHungarian(object):
CHOICE_SET_ACCESS_TOKEN = "AccessToken beállítása"
CHOICE_DOWNLOAD_BY_URL = "Letöltés URL vagy ID alapján"
CHOICE_LOGOUT = "Kijelentkezés"
CHOICE_APIKEY = "Select APIKey"
CHOICE_APIKEY = "APIKey kiválasztása"
PRINT_ERR = "[HIBA]"
PRINT_INFO = "[INFÓ]"
@@ -79,13 +80,14 @@ class LangHungarian(object):
CHANGE_LANGUAGE = "Nyelv kiválasztása"
CHANGE_ALBUM_FOLDER_FORMAT = "Album mappa formátum('0' nincs módosítás,'default' az alapértelmezett beállításhoz):"
CHANGE_TRACK_FILE_FORMAT = "Track fájl neve('0' nincs módosítás,'default' az alapértelmezett beállításhoz):"
CHANGE_VIDEO_FILE_FORMAT = "Video file format('0'-not modify,'default'-to set default):"
CHANGE_SHOW_PROGRESS = "Haladás megjelenítése('0'-nem, '1'-igen):"
CHANGE_SHOW_TRACKINFO = "Show track info('0'-No,'1'-Yes):"
CHANGE_SHOW_TRACKINFO = "Track infók megjelenítése('0'-No,'1'-Yes):"
CHANGE_SAVE_ALBUM_INFO = "AlbumInfo.txt mentése('0'-nem, '1'-igen):"
CHANGE_ADD_LYRICS = "Dalszöveg hozzáadása('0'-nem,'1'-igen):"
CHANGE_LYRICS_SERVER_PROXY = "Dalszöveg kiszolgáló prox('0' nincs módosítás):"
CHANGE_ADD_LRC_FILE = "Save timed lyrics .lrc file ('0'-No,'1'-Yes):"
CHANGE_ADD_TYPE_FOLDER = "Add Type-Folder,eg Album/Video/Playlist('0'-No,'1'-Yes):"
CHANGE_LYRICS_SERVER_PROXY = "Dalszöveg kiszolgáló proxy('0' nincs módosítás):"
CHANGE_ADD_LRC_FILE = "Dalszöveg mentése időbélyeggel .lrc fájl('0'-Nem,'1'-Igen):"
CHANGE_ADD_TYPE_FOLDER = "Mappa típus hozzáadása, pl. Album/Video/Playlist('0'-Nem,'1'-Igen):"
# {} are required in these strings
AUTH_START_LOGIN = "Bejelentkezési folyamat elindítása..."
@@ -95,15 +97,15 @@ class LangHungarian(object):
AUTH_TIMEOUT = "A művelet leállt."
MSG_VALID_ACCESSTOKEN = "AccessToken érvényessége {}."
MSG_INVAILD_ACCESSTOKEN = "Lejárt AccessToken. Megpróbálom frissíteni."
MSG_INVALID_ACCESSTOKEN = "Lejárt AccessToken. Megpróbálom frissíteni."
MSG_PATH_ERR = "Az útvonal hibás!"
MSG_INPUT_ERR = "Beviteli hiba!"
MODEL_ALBUM_PROPERTY = "ALBUM-TULAJDONSÁGOK"
MODEL_TRACK_PROPERTY = "TRACK-TULAJDONSÁGOK"
MODEL_VIDEO_PROPERTY = "VIDEO-TULAJDONSÁGOK"
MODEL_ARTIST_PROPERTY = "ELŐADÓ-TULAJDONSÁGOK"
MODEL_PLAYLIST_PROPERTY = "LEJÁTSZÁSI LISTA-TULAJDONSÁGOK"
MODEL_ALBUM_PROPERTY = "ALBUM-TULAJDONSÁG"
MODEL_TRACK_PROPERTY = "TRACK-TULAJDONSÁG"
MODEL_VIDEO_PROPERTY = "VIDEO-TULAJDONSÁG"
MODEL_ARTIST_PROPERTY = "ELŐADÓ-TULAJDONSÁG"
MODEL_PLAYLIST_PROPERTY = "LEJÁTSZÁSI LISTA-TULAJDONSÁG"
MODEL_TITLE = 'Cím'
MODEL_TRACK_NUMBER = 'Track száma'
+4 -2
View File
@@ -31,8 +31,9 @@ class LangItalian(object):
SETTING_MULITHREAD_DOWNLOAD = "Multi thread download"
SETTING_ALBUM_FOLDER_FORMAT = "Album folder format"
SETTING_TRACK_FILE_FORMAT = "Track file format"
SETTING_VIDEO_FILE_FORMAT = "Video file format"
SETTING_SHOW_PROGRESS = "Show progress"
SETTING_SHOW_TRACKIFNO = "Show Track Info"
SETTING_SHOW_TRACKINFO = "Show Track Info"
SETTING_SAVE_ALBUMINFO = "Save AlbumInfo.txt"
SETTING_ADD_LYRICS = "Add lyrics"
SETTING_LYRICS_SERVER_PROXY = "Lyrics server proxy"
@@ -79,6 +80,7 @@ class LangItalian(object):
CHANGE_LANGUAGE = "Selezionare lingua"
CHANGE_ALBUM_FOLDER_FORMAT = "Album folder format('0' not modify):"
CHANGE_TRACK_FILE_FORMAT = "Track file format('0' not modify):"
CHANGE_VIDEO_FILE_FORMAT = "Video file format('0'-not modify,'default'-to set default):"
CHANGE_SHOW_PROGRESS = "Show progress('0'-No,'1'-Yes):"
CHANGE_SHOW_TRACKINFO = "Show track info('0'-No,'1'-Yes):"
CHANGE_SAVE_ALBUM_INFO = "Save AlbumInfo.txt('0'-No,'1'-Yes):"
@@ -95,7 +97,7 @@ class LangItalian(object):
AUTH_TIMEOUT = "Operation timed out."
MSG_VALID_ACCESSTOKEN = "AccessToken good for {}."
MSG_INVAILD_ACCESSTOKEN = "Expired AccessToken. Attempting to refresh it."
MSG_INVALID_ACCESSTOKEN = "Expired AccessToken. Attempting to refresh it."
MSG_PATH_ERR = "Percorso errato!"
MSG_INPUT_ERR = "Inserimento errato!"
+4 -2
View File
@@ -31,8 +31,9 @@ class LangJapanese(object):
SETTING_MULITHREAD_DOWNLOAD = "Multi thread download"
SETTING_ALBUM_FOLDER_FORMAT = "Album folder format"
SETTING_TRACK_FILE_FORMAT = "Track file format"
SETTING_VIDEO_FILE_FORMAT = "Video file format"
SETTING_SHOW_PROGRESS = "Show progress"
SETTING_SHOW_TRACKIFNO = "Show Track Info"
SETTING_SHOW_TRACKINFO = "Show Track Info"
SETTING_SAVE_ALBUMINFO = "Save AlbumInfo.txt"
SETTING_ADD_LYRICS = "歌詞を追加"
SETTING_LYRICS_SERVER_PROXY = "Lyrics server proxy"
@@ -79,6 +80,7 @@ class LangJapanese(object):
CHANGE_LANGUAGE = "言語を選択する"
CHANGE_ALBUM_FOLDER_FORMAT = "Album folder format('0'-変更しない,'default'-デフォルトを設定するには):"
CHANGE_TRACK_FILE_FORMAT = "Track file format('0'-変更しない,'default'-デフォルトを設定するには):"
CHANGE_VIDEO_FILE_FORMAT = "Video file format('0'-not modify,'default'-to set default):"
CHANGE_SHOW_PROGRESS = "Show progress('0'-いいえ,'1'-はい):"
CHANGE_SHOW_TRACKINFO = "Show track info('0'-いいえ,'1'-はい):"
CHANGE_SAVE_ALBUM_INFO = "Save AlbumInfo.txt('0'-いいえ,'1'-はい):"
@@ -95,7 +97,7 @@ class LangJapanese(object):
AUTH_TIMEOUT = "Operation timed out."
MSG_VALID_ACCESSTOKEN = "AccessToken good for {}."
MSG_INVAILD_ACCESSTOKEN = "Expired AccessToken. Attempting to refresh it."
MSG_INVALID_ACCESSTOKEN = "Expired AccessToken. Attempting to refresh it."
MSG_PATH_ERR = "パスはエラーです!"
MSG_INPUT_ERR = "入力エラー!"
+4 -2
View File
@@ -31,8 +31,9 @@ class LangKorean(object):
SETTING_MULITHREAD_DOWNLOAD = "다중 스레드 다운로드"
SETTING_ALBUM_FOLDER_FORMAT = "앨범 폴더 형식"
SETTING_TRACK_FILE_FORMAT = "트랙 파일 형식"
SETTING_VIDEO_FILE_FORMAT = "Video file format"
SETTING_SHOW_PROGRESS = "진행 상태 표시"
SETTING_SHOW_TRACKIFNO = "트랙 정보 표시"
SETTING_SHOW_TRACKINFO = "트랙 정보 표시"
SETTING_SAVE_ALBUMINFO = "AlbumInfo.txt 저장"
SETTING_ADD_LYRICS = "가사 추가"
SETTING_LYRICS_SERVER_PROXY = "가사 서버 프록시"
@@ -79,6 +80,7 @@ class LangKorean(object):
CHANGE_LANGUAGE = "언어 선택"
CHANGE_ALBUM_FOLDER_FORMAT = "앨범 폴더 형식('0'-변경 안 함,'default'-기본 설정):"
CHANGE_TRACK_FILE_FORMAT = "트랙 파일 형식('0'-변경 안 함,'default'-기본 설정):"
CHANGE_VIDEO_FILE_FORMAT = "Video file format('0'-not modify,'default'-to set default):"
CHANGE_SHOW_PROGRESS = "진행 상태 표시('0'-아니요,'1'-예):"
CHANGE_SHOW_TRACKINFO = "트랙 정보 표시('0'-아니요,'1'-예):"
CHANGE_SAVE_ALBUM_INFO = "AlbumInfo.txt 저장('0'-아니요,'1'-예):"
@@ -95,7 +97,7 @@ class LangKorean(object):
AUTH_TIMEOUT = "작업 시간 초과"
MSG_VALID_ACCESSTOKEN = "{}에 대해 액세스 토큰이 유효합니다."
MSG_INVAILD_ACCESSTOKEN = "만료된 액세스 토큰입니다. 새로 고침 중입니다."
MSG_INVALID_ACCESSTOKEN = "만료된 액세스 토큰입니다. 새로 고침 중입니다."
MSG_PATH_ERR = "경로 오류!"
MSG_INPUT_ERR = "입력 오류!"
+11 -11
View File
@@ -14,12 +14,14 @@ from tidal_dl.lang.chinese import LangChinese
from tidal_dl.lang.croatian import LangCroatian
from tidal_dl.lang.czech import LangCzech
from tidal_dl.lang.danish import LangDanish
from tidal_dl.lang.dutch import LangDutch
from tidal_dl.lang.english import LangEnglish
from tidal_dl.lang.filipino import LangFilipino
from tidal_dl.lang.french import LangFrench
from tidal_dl.lang.german import LangGerman
from tidal_dl.lang.hungarian import LangHungarian
from tidal_dl.lang.italian import LangItalian
from tidal_dl.lang.polish import LangPolish
from tidal_dl.lang.portuguese import LangPortuguese
from tidal_dl.lang.russian import LangRussian
from tidal_dl.lang.spanish import LangSpanish
@@ -29,12 +31,7 @@ from tidal_dl.lang.vietnamese import LangVietnamese
from tidal_dl.lang.korean import LangKorean
from tidal_dl.lang.japanese import LangJapanese
LANG = None
def initLang(index): # 初始化
global LANG
return setLang(index)
LANG = LangEnglish()
def setLang(index):
@@ -77,16 +74,15 @@ def setLang(index):
LANG = LangKorean()
elif str(index) == '18':
LANG = LangJapanese()
elif str(index) == '19':
LANG = LangDutch()
elif str(index) == '20':
LANG = LangPolish()
else:
LANG = LangEnglish()
return LANG
def getLang():
global LANG
return LANG
def getLangName(index):
if str(index) == '0':
return "English"
@@ -126,6 +122,10 @@ def getLangName(index):
return "Korean"
if str(index) == '18':
return "Japanese"
if str(index) == '19':
return "Dutch"
if str(index) == '20':
return "Polish"
return ""
+119
View File
@@ -0,0 +1,119 @@
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
'''
@File : polish.py
@Time : 2022/03/04
@Author : PatrykMis
@Version : 1.0
@Contact : patryk.mis@member.fsf.org
@Desc :
'''
class LangPolish(object):
SETTING = "USTAWIENIA"
VALUE = "WARTOŚĆ"
SETTING_DOWNLOAD_PATH = "Ścieżka pobierania"
SETTING_ONLY_M4A = "Konwertuj mp4 do m4a"
SETTING_ADD_EXPLICIT_TAG = "Dodaj tag jednoznaczne"
SETTING_ADD_HYPHEN = "Dodaj myslnik"
SETTING_ADD_YEAR = "Dodaj rok porzed folderem albumu"
SETTING_USE_TRACK_NUM = "Dodaj numer utworu użytkownika"
SETTING_AUDIO_QUALITY = "Jakość audio"
SETTING_VIDEO_QUALITY = "Jakość wideo"
SETTING_CHECK_EXIST = "Sprawdź istnienie"
SETTING_ARTIST_BEFORE_TITLE = "Nazwa artysty przed tytułem utworu"
SETTING_ALBUMID_BEFORE_FOLDER = "Id przed folderem albumu"
SETTING_INCLUDE_EP = "Uwzględnij single i EP"
SETTING_SAVE_COVERS = "Zapisz okładki"
SETTING_LANGUAGE = "Język"
SETTING_USE_PLAYLIST_FOLDER = "Użyj folder playlisty"
SETTING_MULITHREAD_DOWNLOAD = "Pobieranie wielowątkowe"
SETTING_ALBUM_FOLDER_FORMAT = "Format folderu albumu"
SETTING_TRACK_FILE_FORMAT = "Format pliku utworu"
SETTING_VIDEO_FILE_FORMAT = "Video file format"
SETTING_SHOW_PROGRESS = "Pokaż postęp"
SETTING_SHOW_TRACKINFO = "Pokaż informacje o utworze"
SETTING_SAVE_ALBUMINFO = "Zapisz AlbumInfo.txt"
SETTING_ADD_LYRICS = "Dodaj teksty utworów"
SETTING_LYRICS_SERVER_PROXY = "Serwer proxy dla tekstów"
SETTINGS_ADD_LRC_FILE = "Zapisz czasowe teksty utworów (plik .lrc)"
SETTING_PATH = "Ścieżka ustawień"
SETTING_APIKEY = "Obsługa APIKey"
SETTING_ADD_TYPE_FOLDER = "Dodaj folder typu"
CHOICE = "WYBÓR"
FUNCTION = "FUNKCJA"
CHOICE_ENTER = "Wpisz"
CHOICE_ENTER_URLID = "Wpisz 'Url/ID':"
CHOICE_EXIT = "Wyjdź"
CHOICE_LOGIN = "Sprawdź AccessToken"
CHOICE_SETTINGS = "Ustawienia"
CHOICE_SET_ACCESS_TOKEN = "Ustaw AccessToken"
CHOICE_DOWNLOAD_BY_URL = "Pobierz według adresu URL lub ID"
CHOICE_LOGOUT = "Wyloguj"
CHOICE_APIKEY = "Wybierz APIKey"
PRINT_ERR = "[BŁĄD]"
PRINT_INFO = "[INFO]"
PRINT_SUCCESS = "[SUKCES]"
PRINT_ENTER_CHOICE = "Wprowadź Wybór:"
PRINT_LATEST_VERSION = "Najnowsza wersja:"
# PRINT_USERNAME = "nazwa użytkownika:"
# PRINT_PASSWORD = "hasło:"
CHANGE_START_SETTINGS = "Uruchomić ustawienia('0'-Powrót,'1'-Tak):"
CHANGE_DOWNLOAD_PATH = "Ścieżka pobierania('0'-bez zmian):"
CHANGE_AUDIO_QUALITY = "Jakość audio('0'-Normalna,'1'-Wysoka,'2'-HiFi,'3'-Master):"
CHANGE_VIDEO_QUALITY = "Jakość wideo(1080, 720, 480, 360):"
CHANGE_ONLYM4A = "Konwertuj mp4 do m4a('0'-Nie,'1'-Tak):"
CHANGE_ADD_EXPLICIT_TAG = "Dodaj tag jednoznaczne do nazwy pliku('0'-Nie,'1'-Tak):"
CHANGE_ADD_HYPHEN = "Używaj myślników zamiast spacji w nazwach plików('0'-Nie,'1'-Tak):"
CHANGE_ADD_YEAR = "Dodaj rok do nazw folderów albumów('0'-Nie,'1'-Tak):"
CHANGE_USE_TRACK_NUM = "Dodaj numer utworu przed nazwami plików('0'-Nie,'1'-Tak):"
CHANGE_CHECK_EXIST = "Sprawdź istniejący plik przed pobraniem utworu('0'-Nie,'1'-Tak):"
CHANGE_ARTIST_BEFORE_TITLE = "Dodaj nazwę artysty przed tytułem utworu('0'-Nie,'1'-Tak):"
CHANGE_INCLUDE_EP = "Uwzględnij single i EP podczas pobierania albumów wykonawcy('0'-Nie,'1'-Tak):"
CHANGE_ALBUMID_BEFORE_FOLDER = "Dodaj ID przed folderem albumu('0'-Nie,'1'-Tak):"
CHANGE_SAVE_COVERS = "Zapisz okładki('0'-Nie,'1'-Tak):"
CHANGE_LANGUAGE = "Wybierz język"
CHANGE_ALBUM_FOLDER_FORMAT = "Format folderu albumu('0'-nie modyfikuj,'default'-by ustawić domyślny):"
CHANGE_TRACK_FILE_FORMAT = "Format pliku utworu('0'-nie modyfikuj,'default'-by ustawić domyślny):"
CHANGE_VIDEO_FILE_FORMAT = "Video file format('0'-not modify,'default'-to set default):"
CHANGE_SHOW_PROGRESS = "Pokaż postęp('0'-Nie,'1'-Tak):"
CHANGE_SHOW_TRACKINFO = "Pokaż informacje o utworze('0'-Nie,'1'-Tak):"
CHANGE_SAVE_ALBUM_INFO = "Zapisz AlbumInfo.txt('0'-Nie,'1'-Tak):"
CHANGE_ADD_LYRICS = "Dodaj teksty utworów('0'-Nie,'1'-Tak):"
CHANGE_LYRICS_SERVER_PROXY = "Serwer proxy dla tekstów('0'-nie modyfikuj):"
CHANGE_ADD_LRC_FILE = "Zapisz plik .lrc czasowych tekstów utworów ('0'-Nie,'1'-Tak):"
CHANGE_ADD_TYPE_FOLDER = "Dodaj folder typu, np. Album/wideo/lista odtwarzania('0'-Nie,'1'-Tak):"
# {} are required in these strings
AUTH_START_LOGIN = "Rozpoczęcie procesu logowania..."
AUTH_LOGIN_CODE = "Twój kod logowania to {}"
AUTH_NEXT_STEP = "Przejdź do {} w ciągu następnych {} aby zakończyć konfigurację."
AUTH_WAITING = "Oczekiwanie na autoryzację..."
AUTH_TIMEOUT = "Przekroczono limit czasu operacji."
MSG_VALID_ACCESSTOKEN = "AccessToken dobry przez {}."
MSG_INVALID_ACCESSTOKEN = "AccessToken wygasł. Próba jego odświeżenia."
MSG_PATH_ERR = "Ścieżka jest błędna!"
MSG_INPUT_ERR = "Błąd wejścia!"
MODEL_ALBUM_PROPERTY = "ALBUM-WŁAŚCIWOŚĆ"
MODEL_TRACK_PROPERTY = "UTWÓR-WŁAŚCIWOŚĆ"
MODEL_VIDEO_PROPERTY = "WIDEO-WŁAŚCIWOŚĆ"
MODEL_ARTIST_PROPERTY = "ARTYSTA-WŁAŚCIWOŚĆ"
MODEL_PLAYLIST_PROPERTY = "PLAYLISTA-WŁAŚCIWOŚĆ"
MODEL_TITLE = 'Tytuł'
MODEL_TRACK_NUMBER = 'Numer Utworu'
MODEL_VIDEO_NUMBER = 'Numer Wideo'
MODEL_RELEASE_DATE = 'Data Wydania'
MODEL_VERSION = 'Wersja'
MODEL_EXPLICIT = 'Jednoznaczny'
MODEL_ALBUM = 'Album'
MODEL_ID = 'ID'
MODEL_NAME = 'Nazwa'
MODEL_TYPE = 'Typ'
+31 -29
View File
@@ -27,31 +27,32 @@ class LangPortuguese(object):
SETTING_INCLUDE_EP = "Incluir Single & EP"
SETTING_SAVE_COVERS = "Salvar Capas"
SETTING_LANGUAGE = "idioma"
SETTING_USE_PLAYLIST_FOLDER = "Use playlist folder"
SETTING_MULITHREAD_DOWNLOAD = "Multi thread download"
SETTING_ALBUM_FOLDER_FORMAT = "Album folder format"
SETTING_USE_PLAYLIST_FOLDER = "Usar pasta de lista de reprodução"
SETTING_MULITHREAD_DOWNLOAD = "Download de vários tópicos"
SETTING_ALBUM_FOLDER_FORMAT = "Formato da pasta do álbum"
SETTING_TRACK_FILE_FORMAT = "Track file format"
SETTING_SHOW_PROGRESS = "Show progress"
SETTING_SHOW_TRACKIFNO = "Show Track Info"
SETTING_SAVE_ALBUMINFO = "Save AlbumInfo.txt"
SETTING_ADD_LYRICS = "Add lyrics"
SETTING_LYRICS_SERVER_PROXY = "Lyrics server proxy"
SETTING_VIDEO_FILE_FORMAT = "Video file format"
SETTING_SHOW_PROGRESS = "Mostrar progresso"
SETTING_SHOW_TRACKINFO = "Mostrar informações da faixa"
SETTING_SAVE_ALBUMINFO = "Salvar AlbumInfo.txt"
SETTING_ADD_LYRICS = "Adicionar Letra da Música"
SETTING_LYRICS_SERVER_PROXY = "Proxy do servidor de letras"
SETTING_PATH = "Settings path"
SETTINGS_ADD_LRC_FILE = "Save timed lyrics (.lrc file)"
SETTING_APIKEY = "APIKey support"
SETTING_ADD_TYPE_FOLDER = "Add Type-Folder"
SETTINGS_ADD_LRC_FILE = "Salvar letras cronometradas (.lrc file)"
SETTING_APIKEY = "Suporte APIKey"
SETTING_ADD_TYPE_FOLDER = "Adicionar tipo de pasta"
CHOICE = "ESCOLHER"
FUNCTION = "FUNÇÃO"
CHOICE_ENTER = "Entrar"
CHOICE_ENTER_URLID = "ADICIONAR 'Url/ID':"
CHOICE_EXIT = "SAIR"
CHOICE_LOGIN = "Check AccessToken"
CHOICE_LOGIN = "Verificar o token de acesso"
CHOICE_SETTINGS = "Configurações"
CHOICE_SET_ACCESS_TOKEN = "Adicionar Token de Acesso"
CHOICE_DOWNLOAD_BY_URL = "Download Por url ou id"
CHOICE_LOGOUT = "Logout"
CHOICE_APIKEY = "Select APIKey"
CHOICE_APIKEY = "Selecione a APIKey"
PRINT_ERR = "[ERRO]"
PRINT_INFO = "[INFORMAÇÕES]"
@@ -77,25 +78,26 @@ class LangPortuguese(object):
CHANGE_ALBUMID_BEFORE_FOLDER = "Adicionar id antes no nome da pasta do álbum('0'-Não,'1'-Sim):"
CHANGE_SAVE_COVERS = "Salvar Capas('0'-Não,'1'-Sim):"
CHANGE_LANGUAGE = "Selecionar idioma"
CHANGE_ALBUM_FOLDER_FORMAT = "Album folder format('0' not modify):"
CHANGE_TRACK_FILE_FORMAT = "Track file format('0' not modify):"
CHANGE_SHOW_PROGRESS = "Show progress('0'-No,'1'-Yes):"
CHANGE_SHOW_TRACKINFO = "Show track info('0'-No,'1'-Yes):"
CHANGE_SAVE_ALBUM_INFO = "Save AlbumInfo.txt('0'-No,'1'-Yes):"
CHANGE_ADD_LYRICS = "Add lyrics('0'-No,'1'-Yes):"
CHANGE_LYRICS_SERVER_PROXY = "Lyrics server proxy('0' not modify):"
CHANGE_ADD_LRC_FILE = "Save timed lyrics .lrc file ('0'-No,'1'-Yes):"
CHANGE_ADD_TYPE_FOLDER = "Add Type-Folder,eg Album/Video/Playlist('0'-No,'1'-Yes):"
CHANGE_ALBUM_FOLDER_FORMAT = "Formato da pasta do álbum ('0' não modificar)"
CHANGE_TRACK_FILE_FORMAT = "Formato do arquivo de trilha ('0' não modificar):"
CHANGE_VIDEO_FILE_FORMAT = "Video file format('0'-not modify,'default'-to set default):"
CHANGE_SHOW_PROGRESS = "Mostrar progresso('0'-Não,'1'-Sim):"
CHANGE_SHOW_TRACKINFO = "Mostrar informações da faixa('0'-Não,'1'-Sim):"
CHANGE_SAVE_ALBUM_INFO = "Salvar AlbumInfo.txt('0'-Não,'1'-Sim):"
CHANGE_ADD_LYRICS = "Adicionar letras('0'-Não,'1'-Sim):"
CHANGE_LYRICS_SERVER_PROXY = "Proxy do servidor de letras ('0' não modificar):"
CHANGE_ADD_LRC_FILE = "Salvar arquivo .lrc de letras cronometradas ('0'-Não,'1'-Sim):"
CHANGE_ADD_TYPE_FOLDER = "Adicionar Tipo de Pasta, por exemplo, Álbum/Vídeo/Lista de Reprodução('0'-Não,'1'-Sim):"
# {} are required in these strings
AUTH_START_LOGIN = "Starting login process..."
AUTH_LOGIN_CODE = "Your login code is {}"
AUTH_NEXT_STEP = "Go to {} within the next {} to complete setup."
AUTH_WAITING = "Waiting for authorization..."
AUTH_TIMEOUT = "Operation timed out."
AUTH_START_LOGIN = "Iniciando o processo de login..."
AUTH_LOGIN_CODE = "Seu código de login é {}"
AUTH_NEXT_STEP = "Vá para {} no próximo {} para concluir a configuração."
AUTH_WAITING = "Espera da autorização..."
AUTH_TIMEOUT = "A operação expirou."
MSG_VALID_ACCESSTOKEN = "AccessToken good for {}."
MSG_INVAILD_ACCESSTOKEN = "Expired AccessToken. Attempting to refresh it."
MSG_VALID_ACCESSTOKEN = "Token de acesso bom por {}."
MSG_INVALID_ACCESSTOKEN = "Token de acesso expirado. Tentando atualizá-lo."
MSG_PATH_ERR = "Erro no local de salvamento dos arquivos!"
MSG_INPUT_ERR = "Erro de Entrada!"
+4 -2
View File
@@ -31,8 +31,9 @@ class LangRussian(object):
SETTING_MULITHREAD_DOWNLOAD = "Многопоточная загрузка"
SETTING_ALBUM_FOLDER_FORMAT = "Маска имени альбома"
SETTING_TRACK_FILE_FORMAT = "Маска имени трека"
SETTING_VIDEO_FILE_FORMAT = "Video file format"
SETTING_SHOW_PROGRESS = "Показывать процесс загрузки"
SETTING_SHOW_TRACKIFNO = "Show Track Info"
SETTING_SHOW_TRACKINFO = "Show Track Info"
SETTING_SAVE_ALBUMINFO = "Сохранять AlbumInfo.txt"
SETTING_ADD_LYRICS = "Добавлять текст песень"
SETTING_LYRICS_SERVER_PROXY = "Прокси сервер для текстов песен"
@@ -79,6 +80,7 @@ class LangRussian(object):
CHANGE_LANGUAGE = "Выбрать язык"
CHANGE_ALBUM_FOLDER_FORMAT = "Маска имени альбома('0' не менять):"
CHANGE_TRACK_FILE_FORMAT = "Маска имени трека('0' не менять):"
CHANGE_VIDEO_FILE_FORMAT = "Video file format('0'-not modify,'default'-to set default):"
CHANGE_SHOW_PROGRESS = "Показывать процесс загрузки('0'-Нет,'1'-Да):"
CHANGE_SHOW_TRACKINFO = "Show track info('0'-No,'1'-Yes):"
CHANGE_SAVE_ALBUM_INFO = "Сохранять AlbumInfo.txt('0'-Нет,'1'-Да):"
@@ -95,7 +97,7 @@ class LangRussian(object):
AUTH_TIMEOUT = "Закончилось время ожидания."
MSG_VALID_ACCESSTOKEN = "AccessToken успешно применён {}."
MSG_INVAILD_ACCESSTOKEN = "Срок действия AccessToken истек. Попытка обновления."
MSG_INVALID_ACCESSTOKEN = "Срок действия AccessToken истек. Попытка обновления."
MSG_PATH_ERR = "Неверное место!"
MSG_INPUT_ERR = "Ошибка ввода!"
+4 -2
View File
@@ -31,8 +31,9 @@ class LangSpanish(object):
SETTING_MULITHREAD_DOWNLOAD = "Descarga Multi-hilo"
SETTING_ALBUM_FOLDER_FORMAT = "Formato del nombre de carpeta del álbum"
SETTING_TRACK_FILE_FORMAT = "Formato del nombre de archivo de la pista"
SETTING_VIDEO_FILE_FORMAT = "Video file format"
SETTING_SHOW_PROGRESS = "Mostrar progreso"
SETTING_SHOW_TRACKIFNO = "Mostrar información de pista"
SETTING_SHOW_TRACKINFO = "Mostrar información de pista"
SETTING_SAVE_ALBUMINFO = "Guardar AlbumInfo.txt"
SETTING_ADD_LYRICS = "Añadir letras"
SETTING_LYRICS_SERVER_PROXY = "Proxy del servidor de letras"
@@ -79,6 +80,7 @@ class LangSpanish(object):
CHANGE_LANGUAGE = "Seleccione el idioma"
CHANGE_ALBUM_FOLDER_FORMAT = "Formato del nombre de carpeta del álbum ('0' No modificar):"
CHANGE_TRACK_FILE_FORMAT = "Formato del nombre de archivo de la pista ('0' No modificar):"
CHANGE_VIDEO_FILE_FORMAT = "Video file format('0'-not modify,'default'-to set default):"
CHANGE_SHOW_PROGRESS = "¿Mostrar progreso? ('0'-No,'1'-Sí):"
CHANGE_SHOW_TRACKINFO = "¿Mostrar información de pista?('0'-No,'1'-Sí):"
CHANGE_SAVE_ALBUM_INFO = "¿Guardar AlbumInfo.txt?('0'-No,'1'-Sí):"
@@ -95,7 +97,7 @@ class LangSpanish(object):
AUTH_TIMEOUT = "Se superó el tiempo de espera."
MSG_VALID_ACCESSTOKEN = "Token de acceso válido por {}."
MSG_INVAILD_ACCESSTOKEN = "El token de acceso ha expirado. Tratando de renovarlo."
MSG_INVALID_ACCESSTOKEN = "El token de acceso ha expirado. Tratando de renovarlo."
MSG_PATH_ERR = "¡La ruta no es correcta!"
MSG_INPUT_ERR = "¡Error de entrada!"
+4 -2
View File
@@ -31,8 +31,9 @@ class LangTurkish(object):
SETTING_MULITHREAD_DOWNLOAD = "Şarkılar tek tek indirilsin mi?"
SETTING_ALBUM_FOLDER_FORMAT = "Klasör ismi formatı:"
SETTING_TRACK_FILE_FORMAT = "Dosya ismi formatı:"
SETTING_VIDEO_FILE_FORMAT = "Video file format"
SETTING_SHOW_PROGRESS = "İndirme Çubuğu Görüntüleme:"
SETTING_SHOW_TRACKIFNO = "Show Track Info"
SETTING_SHOW_TRACKINFO = "Show Track Info"
SETTING_SAVE_ALBUMINFO = "Save AlbumInfo.txt"
SETTING_ADD_LYRICS = "Add lyrics"
SETTING_LYRICS_SERVER_PROXY = "Lyrics server proxy"
@@ -79,6 +80,7 @@ class LangTurkish(object):
CHANGE_LANGUAGE = ">>> Lisan Seç "
CHANGE_ALBUM_FOLDER_FORMAT = "Albüm Klasör İsmi Formatı('0' aynı kalsın):"
CHANGE_TRACK_FILE_FORMAT = "Dosya İsmi Formatı('0' aynı kalsın):"
CHANGE_VIDEO_FILE_FORMAT = "Video file format('0'-not modify,'default'-to set default):"
CHANGE_SHOW_PROGRESS = "İndirme Çubuğu Görüntülensin mi?('0'-Hayır,'1'-Evet):"
CHANGE_SHOW_TRACKINFO = "Show track info('0'-No,'1'-Yes):"
CHANGE_SAVE_ALBUM_INFO = "Save AlbumInfo.txt('0'-No,'1'-Yes):"
@@ -95,7 +97,7 @@ class LangTurkish(object):
AUTH_TIMEOUT = "Lütfen size verilen süre içerisinde işlemleriniz tamamlayınız."
MSG_VALID_ACCESSTOKEN = "AccessToken good for {}."
MSG_INVAILD_ACCESSTOKEN = "Expired AccessToken. Attempting to refresh it."
MSG_INVALID_ACCESSTOKEN = "Expired AccessToken. Attempting to refresh it."
MSG_PATH_ERR = "İndirme konumu ile alakalı bir sorun var! ('/storage/emulated/0/Download/' şeklinde girebilirsiniz.)"
MSG_INPUT_ERR = "Giriş Hatalı!"
+53 -51
View File
@@ -2,9 +2,9 @@
# -*- encoding: utf-8 -*-
'''
@File : ukrainian.py
@Time : 2022/02/03
@Time : 2022/02/20
@Author : Montyzzz & 9uyone
@Version : 1.1
@Version : 1.2
@Contact : ---
@Desc :
'''
@@ -14,44 +14,45 @@ class LangUkrainian(object):
SETTING = "НАЛАШТУВАННЯ"
VALUE = "ЗНАЧЕННЯ"
SETTING_DOWNLOAD_PATH = "Шлях завантаження"
SETTING_ONLY_M4A = "Перетворити mp4 на m4a"
SETTING_ADD_EXPLICIT_TAG = "Додати тег Нецензурно"
SETTING_ADD_HYPHEN = "Додати дефіс"
SETTING_ADD_YEAR = "Додати рік перед ім'ям папки альбому"
SETTING_USE_TRACK_NUM = "Додати номер треку"
SETTING_ONLY_M4A = "Перетворювати mp4 на m4a"
SETTING_ADD_EXPLICIT_TAG = "Додавати тег Нецензурно"
SETTING_ADD_HYPHEN = "Додавати дефіс"
SETTING_ADD_YEAR = "Додавати рік перед ім'ям папки альбому"
SETTING_USE_TRACK_NUM = "Додавати номер треку"
SETTING_AUDIO_QUALITY = "Якість аудіо"
SETTING_VIDEO_QUALITY = "Якість відео"
SETTING_CHECK_EXIST = "Перевіряти наявність"
SETTING_ARTIST_BEFORE_TITLE = "Виконавець перед назвою треку"
SETTING_ALBUMID_BEFORE_FOLDER = "ID перед назвою теки"
SETTING_INCLUDE_EP = "Включити сингли та міньйони (EP)"
SETTING_ALBUMID_BEFORE_FOLDER = "ID перед назвою папки"
SETTING_INCLUDE_EP = "Включати сингли та міньйони (EP)"
SETTING_SAVE_COVERS = "Зберігати обкладинки"
SETTING_LANGUAGE = "Мова"
SETTING_USE_PLAYLIST_FOLDER = "Використовувати теку плейлиста"
SETTING_USE_PLAYLIST_FOLDER = "Використовувати папку плейлиста"
SETTING_MULITHREAD_DOWNLOAD = "Багатопоточне завантаження"
SETTING_ALBUM_FOLDER_FORMAT = "Формат теки альбому"
SETTING_ALBUM_FOLDER_FORMAT = "Формат папки альбому"
SETTING_TRACK_FILE_FORMAT = "Формат файлу треку"
SETTING_VIDEO_FILE_FORMAT = "Video file format"
SETTING_SHOW_PROGRESS = "Показувати прогрес"
SETTING_SHOW_TRACKIFNO = "Показувати інформацію про трек"
SETTING_SHOW_TRACKINFO = "Показувати інформацію про трек"
SETTING_SAVE_ALBUMINFO = "Зберігати AlbumInfo.txt"
SETTING_ADD_LYRICS = "Додати тексти пісень"
SETTING_LYRICS_SERVER_PROXY = "Проксі-сервер текстів пісень"
SETTING_ADD_LYRICS = "Додавати тексти пісень"
SETTING_LYRICS_SERVER_PROXY = "Проксі для сервера з текстами пісень"
SETTING_PATH = "Шлях налаштувань"
SETTINGS_ADD_LRC_FILE = "Зберігати тексти з відмітками часу (.lrc файл)"
SETTING_APIKEY = "Підтримка API ключа"
SETTING_ADD_TYPE_FOLDER = "Додати теку-тип"
SETTING_APIKEY = "Підтримка ключа API"
SETTING_ADD_TYPE_FOLDER = "Додавати папку-тип"
CHOICE = "ВИБІР"
FUNCTION = "ФУНКЦІЇ"
CHOICE_ENTER = "Ввід"
CHOICE_ENTER_URLID = "Ввід 'Url/ID':"
CHOICE_ENTER_URLID = "Ввід 'URL/ID':"
CHOICE_EXIT = "Вихід"
CHOICE_LOGIN = "Перевірити AccessToken"
CHOICE_SETTINGS = "Налаштування"
CHOICE_SET_ACCESS_TOKEN = "Застосувати власний AccessToken"
CHOICE_DOWNLOAD_BY_URL = "Завантажити за URL-адресою або ідентифікатором"
CHOICE_LOGOUT = "Деавторизація"
CHOICE_APIKEY = "Вибрати API ключ"
CHOICE_APIKEY = "Вибрати ключ API"
PRINT_ERR = "[ПОМИЛКА]"
PRINT_INFO = "[ІНФОРМАЦІЯ]"
@@ -62,48 +63,49 @@ class LangUkrainian(object):
# PRINT_USERNAME = "ім'я користувача:"
# PRINT_PASSWORD = "пароль:"
CHANGE_START_SETTINGS = "Налаштування запуску ('0' Повернення, '1' Так):"
CHANGE_DOWNLOAD_PATH = "Шлях завантаження ('0' не змінювати):"
CHANGE_AUDIO_QUALITY = "Якість аудіо ('0' - Звичайна, '1' - Висока, '2' - HiFi, '3' - MQA):"
CHANGE_VIDEO_QUALITY = "Якість відео (1080, 720, 480, 360):"
CHANGE_ONLYM4A = "Перетворити mp4 на m4a ('0' - Ні, '1' - Так):"
CHANGE_ADD_EXPLICIT_TAG = "Додати тег 'Нецензурно' ('0' - Ні, '1' - Так):"
CHANGE_ADD_HYPHEN = "Використати дефіс замість пробілів в іменах файлів ('0' - Ні, '1' - Так):"
CHANGE_ADD_YEAR = "Додавати рік до назв тек альбомів ('0' - Ні, '1' - Так):"
CHANGE_USE_TRACK_NUM = "Додавати номер доріжки перед назвами файлів ('0' - Ні, '1' - Так):"
CHANGE_CHECK_EXIST = "Перевіряти наявний файл перед завантаженням треку ('0' - Ні, '1' - Так):"
CHANGE_ARTIST_BEFORE_TITLE = "Додати ім’я виконавця перед заголовком треку ('0' - Ні, '1' - Так):"
CHANGE_INCLUDE_EP = "Включати сингли та міньйони під час завантаження альбомів виконавця ('0' - Ні, '1' - Так):"
CHANGE_ALBUMID_BEFORE_FOLDER = "Додати ідентифікатор перед текою альбому ('0' - Ні, '1' - Так):"
CHANGE_SAVE_COVERS = "Зберігати обкладинки ('0' - Ні, '1' - Так):"
CHANGE_START_SETTINGS = "Запуск налаштувань('0'-Повернутися,'1'-Так):"
CHANGE_DOWNLOAD_PATH = "Шлях завантаження('0'-не змінювати):"
CHANGE_AUDIO_QUALITY = "Якість аудіо('0'-Звичайна,'1'-Висока,'2'-HiFi,'3'-MQA):"
CHANGE_VIDEO_QUALITY = "Якість відео(1080,720,480,360):"
CHANGE_ONLYM4A = "Перетворювати mp4 на m4a('0'-Ні,'1'-Так):"
CHANGE_ADD_EXPLICIT_TAG = "Додавати тег 'Нецензурно'('0'-Ні,'1'-Так):"
CHANGE_ADD_HYPHEN = "Використати дефіс замість пробілів в іменах файлів('0'-Ні,'1'-Так):"
CHANGE_ADD_YEAR = "Додавати рік до назв тек альбомів('0'-Ні,'1'-Так):"
CHANGE_USE_TRACK_NUM = "Додавати номер доріжки перед назвами файлів('0'-Ні,'1'-Так):"
CHANGE_CHECK_EXIST = "Перевіряти наявний файл перед завантаженням треку('0'-Ні,'1'-Так):"
CHANGE_ARTIST_BEFORE_TITLE = "Додати ім’я виконавця перед заголовком треку('0'-Ні,'1'-Так):"
CHANGE_INCLUDE_EP = "Включати сингли та міньйони під час завантаження альбомів виконавця('0'-Ні,'1'-Так):"
CHANGE_ALBUMID_BEFORE_FOLDER = "Додати ідентифікатор перед текою альбому('0'-Ні,'1'-Так):"
CHANGE_SAVE_COVERS = "Зберігати обкладинки('0'-Ні,'1'-Так):"
CHANGE_LANGUAGE = "Обрати мову"
CHANGE_ALBUM_FOLDER_FORMAT = "Формат теки альбому ('0' - не змінювати):"
CHANGE_TRACK_FILE_FORMAT = "Формат файлу треку ('0' не змінювати):"
CHANGE_SHOW_PROGRESS = "Показувати прогрес ('0' - Ні, '1' - Так):"
CHANGE_SHOW_TRACKINFO = "Показувати інформацію про трек ('0' - Ні, '1' - Так):"
CHANGE_SAVE_ALBUM_INFO = "Зберігати AlbumInfo.txt ('0' - Ні, '1' - Так):"
CHANGE_ADD_LYRICS = "Додати тексти пісень ('0' - Ні, '1' - Так):"
CHANGE_LYRICS_SERVER_PROXY = "Проксі-сервер текстів пісень ('0' - не змінювати):"
CHANGE_ADD_LRC_FILE = "Зберігати тексти пісень з відмітками часу в .lrc файл ('0' - Ні, '1' - Так):"
CHANGE_ADD_TYPE_FOLDER = "Додати теку-тип, наприклад Альбом/Відео/Плейлист ('0' - Ні, '1' - Так):"
CHANGE_ALBUM_FOLDER_FORMAT = "Формат теки альбому('0'-не змінювати):"
CHANGE_TRACK_FILE_FORMAT = "Формат файлу треку('0'-не змінювати):"
CHANGE_VIDEO_FILE_FORMAT = "Video file format('0'-not modify,'default'-to set default):"
CHANGE_SHOW_PROGRESS = "Показувати прогрес('0'-Ні,'1'-Так):"
CHANGE_SHOW_TRACKINFO = "Показувати інформацію про трек('0'-Ні,'1'-Так):"
CHANGE_SAVE_ALBUM_INFO = "Зберігати AlbumInfo.txt('0'-Ні,'1'-Так):"
CHANGE_ADD_LYRICS = "Додавати тексти пісень('0'-Ні,'1'-Так):"
CHANGE_LYRICS_SERVER_PROXY = "Проксі для сервера з текстами пісень('0'-не змінювати):"
CHANGE_ADD_LRC_FILE = "Зберігати тексти пісень з відмітками часу в .lrc файл('0'-Ні,'1'-Так):"
CHANGE_ADD_TYPE_FOLDER = "Додавати теку-тип,наприклад Альбом/Відео/Плейлист('0'-Ні,'1'-Так):"
# {} are required in these strings
AUTH_START_LOGIN = "Початок процесу авторизації..."
AUTH_LOGIN_CODE = "Ваш логін-код: {}"
AUTH_NEXT_STEP = "Перейдіти на {} протягом наступних {} для завершення налаштування."
AUTH_NEXT_STEP = "Зайдіть на {} протягом наступних {} для завершення налаштування."
AUTH_WAITING = "Очікування авторизації..."
AUTH_TIMEOUT = "Час очікування операції минув."
AUTH_TIMEOUT = "Час очікування минув."
MSG_VALID_ACCESSTOKEN = "AccessToken хороший впродовж {}."
MSG_INVAILD_ACCESSTOKEN = "Термін дії AccessToken'а закінчився. Пробуємо оновити його."
MSG_VALID_ACCESSTOKEN = "AccessToken хороший упродовж {}."
MSG_INVALID_ACCESSTOKEN = "Термін дії AccessToken'а закінчився. Пробуємо оновити його."
MSG_PATH_ERR = "Невірний шлях!"
MSG_INPUT_ERR = "Помилка введення!"
MODEL_ALBUM_PROPERTY = "ALBUM-PROPERTY"
MODEL_TRACK_PROPERTY = "TRACK-PROPERTY"
MODEL_VIDEO_PROPERTY = "VIDEO-PROPERTY"
MODEL_ARTIST_PROPERTY = "ARTIST-PROPERTY"
MODEL_PLAYLIST_PROPERTY = "PLAYLIST-PROPERTY"
MODEL_ALBUM_PROPERTY = "АЛЬБОМ-ВЛАСТИВІСТЬ"
MODEL_TRACK_PROPERTY = "ТРЕК-ВЛАСТИВІСТЬ"
MODEL_VIDEO_PROPERTY = "ВІДЕО-ВЛАСТИВІСТЬ"
MODEL_ARTIST_PROPERTY = "АРТИСТ-ВЛАСТИВІСТЬ"
MODEL_PLAYLIST_PROPERTY = "ПЛЕЙЛИСТ-ВЛАСТИВІСТЬ"
MODEL_TITLE = 'Назва'
MODEL_TRACK_NUMBER = 'Номер доріжки'
@@ -114,4 +116,4 @@ class LangUkrainian(object):
MODEL_ALBUM = 'Альбом'
MODEL_ID = 'ID'
MODEL_NAME = 'Ім\'я'
MODEL_TYPE = 'Тип'
MODEL_TYPE = 'Тип'
+11 -9
View File
@@ -2,7 +2,7 @@
# -*- encoding: utf-8 -*-
'''
@File : vietnamese.py
@Time : 2020/11/12
@Time : 2022/2/9
@Author : MinhNgo, CDzungx
@Version : 1.0
@Contact : iam.minhnc@outlook.com
@@ -31,15 +31,16 @@ class LangVietnamese(object):
SETTING_MULITHREAD_DOWNLOAD = "Tải về đa luồng"
SETTING_ALBUM_FOLDER_FORMAT = "Định dạng thư mục album"
SETTING_TRACK_FILE_FORMAT = "Định dạng tên tệp nhạc"
SETTING_VIDEO_FILE_FORMAT = "Video file format"
SETTING_SHOW_PROGRESS = "Hiện tiến trình"
SETTING_SHOW_TRACKIFNO = "Show Track Info"
SETTING_SHOW_TRACKINFO = "Hiện thông tin bài"
SETTING_SAVE_ALBUMINFO = "Lưu AlbumInfo.txt"
SETTING_ADD_LYRICS = "Thêm lời bài hát"
SETTING_LYRICS_SERVER_PROXY = "Máy chủ proxy cho lyrics"
SETTINGS_ADD_LRC_FILE = "Lưu timed lyrics (tệp .lrc)"
SETTING_PATH = "Đường dẫn cài đặt"
SETTING_APIKEY = "APIKey support"
SETTING_ADD_TYPE_FOLDER = "Add Type-Folder"
SETTING_APIKEY = "Hỗ trợ APIKey"
SETTING_ADD_TYPE_FOLDER = "Thêm Loại-Thư mục"
CHOICE = "LỰA CHỌN"
FUNCTION = "CHỨC NĂNG"
@@ -51,7 +52,7 @@ class LangVietnamese(object):
CHOICE_SET_ACCESS_TOKEN = "Nhập AccessToken"
CHOICE_DOWNLOAD_BY_URL = "Tải về qua url hoặc id"
CHOICE_LOGOUT = "Đăng xuất"
CHOICE_APIKEY = "Select APIKey"
CHOICE_APIKEY = "Chọn APIKey"
PRINT_ERR = "[LỖI]"
PRINT_INFO = "[THÔNG TIN]"
@@ -79,13 +80,14 @@ class LangVietnamese(object):
CHANGE_LANGUAGE = "Chọn ngôn ngữ"
CHANGE_ALBUM_FOLDER_FORMAT = "Định dạng thư mục album('0' không đổi,'default' để đặt về mặc định):"
CHANGE_TRACK_FILE_FORMAT = "Định dạng tên tệp nhạc('0' không đổi,'default' để đặt về mặc định):"
CHANGE_VIDEO_FILE_FORMAT = "Video file format('0'-not modify,'default'-to set default):"
CHANGE_SHOW_PROGRESS = "Hiện tiến trình('0'-Không,'1'-Có):"
CHANGE_SHOW_TRACKINFO = "Hiện thông tin bài('0'-No,'1'-Yes):"
CHANGE_SHOW_TRACKINFO = "Hiện thông tin bài('0'-Không,'1'-):"
CHANGE_SAVE_ALBUM_INFO = "Lưu AlbumInfo.txt('0'-Không,'1'-Có):"
CHANGE_ADD_LYRICS = "Thêm lời bài hát('0'-Không,'1'-Có):"
CHANGE_LYRICS_SERVER_PROXY = "Máy chủ proxy cho lyrics('0' không đổi):"
CHANGE_ADD_LRC_FILE = "Lưu timed lyrics tệp .lrc ('0'-No,'1'-Yes):"
CHANGE_ADD_TYPE_FOLDER = "Add Type-Folder,eg Album/Video/Playlist('0'-No,'1'-Yes):"
CHANGE_ADD_LRC_FILE = "Lưu timed lyrics tệp .lrc ('0'-Không,'1'-):"
CHANGE_ADD_TYPE_FOLDER = "Thêm Loại-Thư mục, ví dụ Album/Video/Playlist('0'-Không,'1'-):"
# {} are required in these strings
AUTH_START_LOGIN = "Đang bắt đầu đăng nhập..."
@@ -95,7 +97,7 @@ class LangVietnamese(object):
AUTH_TIMEOUT = "Đã vượt quá thời gian chờ."
MSG_VALID_ACCESSTOKEN = "AccessToken vẫn tốt trong {}."
MSG_INVAILD_ACCESSTOKEN = "AccessToken hết hạn. Đang cố làm mới."
MSG_INVALID_ACCESSTOKEN = "AccessToken hết hạn. Đang cố làm mới."
MSG_PATH_ERR = "Lỗi đường dẫn!"
MSG_INPUT_ERR = "Lỗi nhập!"
+163 -103
View File
@@ -4,144 +4,204 @@
@File : model.py
@Time : 2020/08/08
@Author : Yaronzz
@Version : 2.0
@Version : 3.0
@Contact : yaronhuang@foxmail.com
@Desc :
'''
from aigpy.modelHelper import ModelBase
import aigpy
class StreamUrl(aigpy.model.ModelBase):
def __init__(self) -> None:
super().__init__()
self.trackid = None
self.url = None
self.codec = None
self.encryptionKey = None
self.soundQuality = None
class StreamUrl(ModelBase):
trackid = None
url = None
codec = None
encryptionKey = None
soundQuality = None
class VideoStreamUrl(aigpy.model.ModelBase):
def __init__(self) -> None:
super().__init__()
self.codec = None
self.resolution = None
self.resolutions = None
self.m3u8Url = None
class VideoStreamUrl(ModelBase):
codec = None
resolution = None
resolutions = None
m3u8Url = None
class Artist(aigpy.model.ModelBase):
def __init__(self) -> None:
super().__init__()
self.id = None
self.name = None
self.type = None
self.picture = None
class Artist(ModelBase):
id = None
name = None
type = None
picture = None
class Album(aigpy.model.ModelBase):
def __init__(self) -> None:
super().__init__()
self.id = None
self.title = None
self.duration = 0
self.numberOfTracks = 0
self.numberOfVideos = 0
self.numberOfVolumes = 0
self.releaseDate = None
self.type = None
self.version = None
self.cover = None
self.explicit = False
self.audioQuality = None
self.audioModes = None
self.artist = Artist()
self.artists = Artist()
class Album(ModelBase):
id = None
title = None
duration = 0
numberOfTracks = 0
numberOfVideos = 0
numberOfVolumes = 0
releaseDate = None
type = None
version = None
cover = None
explicit = False
audioQuality = None
audioModes = None
artist = Artist()
artists = Artist()
class Playlist(aigpy.model.ModelBase):
def __init__(self) -> None:
super().__init__()
self.uuid = None
self.title = None
self.numberOfTracks = 0
self.numberOfVideos = 0
self.description = None
self.duration = 0
self.image = None
self.squareImage = None
class Playlist(ModelBase):
uuid = None
title = None
numberOfTracks = 0
numberOfVideos = 0
description = None
duration = 0
image = None
squareImage = None
class Track(aigpy.model.ModelBase):
def __init__(self) -> None:
super().__init__()
self.id = None
self.title = None
self.duration = 0
self.trackNumber = 0
self.volumeNumber = 0
self.trackNumberOnPlaylist = 0
self.version = None
self.isrc = None
self.explicit = False
self.audioQuality = None
self.copyRight = None
self.artist = Artist()
self.artists = Artist()
self.album = Album()
self.allowStreaming = False
self.playlist = None
class Track(ModelBase):
id = None
title = None
duration = 0
trackNumber = 0
volumeNumber = 0
trackNumberOnPlaylist = 0
version = None
isrc = None
explicit = False
audioQuality = None
copyRight = None
artist = Artist()
artists = Artist()
album = Album()
allowStreaming = False
playlist = None
class Video(aigpy.model.ModelBase):
def __init__(self) -> None:
super().__init__()
self.id = None
self.title = None
self.duration = 0
self.imageID = None
self.trackNumber = 0
self.releaseDate = None
self.version = None
self.quality = None
self.explicit = False
self.artist = Artist()
self.artists = Artist()
self.album = Album()
self.allowStreaming = False
self.playlist = None
class Video(ModelBase):
id = None
title = None
duration = 0
imageID = None
trackNumber = 0
releaseDate = None
version = None
quality = None
explicit = False
artist = Artist()
artists = Artist()
album = Album()
allowStreaming = False
playlist = None
class Mix(aigpy.model.ModelBase):
def __init__(self) -> None:
super().__init__()
self.id = None
self.tracks = Track()
self.videos = Video()
class Mix(ModelBase):
id = None
tracks = Track()
videos = Video()
class Lyrics(aigpy.model.ModelBase):
def __init__(self) -> None:
super().__init__()
self.trackId = None
self.lyricsProvider = None
self.providerCommontrackId = None
self.providerLyricsId = None
self.lyrics = None
self.subtitles = None
class Lyrics(ModelBase):
trackId = None
lyricsProvider = None
providerCommontrackId = None
providerLyricsId = None
lyrics = None
subtitles = None
class SearchDataBase(ModelBase):
limit = 0
offset = 0
totalNumberOfItems = 0
class SearchDataBase(aigpy.model.ModelBase):
def __init__(self) -> None:
super().__init__()
self.limit = 0
self.offset = 0
self.totalNumberOfItems = 0
class SearchAlbums(SearchDataBase):
items = Album()
def __init__(self) -> None:
super().__init__()
self.items = Album()
class SearchArtists(SearchDataBase):
items = Artist()
def __init__(self) -> None:
super().__init__()
self.items = Artist()
class SearchTracks(SearchDataBase):
items = Track()
def __init__(self) -> None:
super().__init__()
self.items = Track()
class SearchVideos(SearchDataBase):
items = Video()
def __init__(self) -> None:
super().__init__()
self.items = Video()
class SearchPlaylists(SearchDataBase):
items = Playlist()
def __init__(self) -> None:
super().__init__()
self.items = Playlist()
class SearchResult(ModelBase):
artists = SearchArtists()
albums = SearchAlbums()
tracks = SearchTracks()
videos = SearchVideos()
playlists = SearchPlaylists()
class SearchResult(aigpy.model.ModelBase):
def __init__(self) -> None:
super().__init__()
self.artists = SearchArtists()
self.albums = SearchAlbums()
self.tracks = SearchTracks()
self.videos = SearchVideos()
self.playlists = SearchPlaylists()
class LoginKey(aigpy.model.ModelBase):
def __init__(self) -> None:
super().__init__()
self.deviceCode = None
self.userCode = None
self.verificationUrl = None
self.authCheckTimeout = None
self.authCheckInterval = None
self.userId = None
self.countryCode = None
self.accessToken = None
self.refreshToken = None
self.expiresIn = None
class StreamRespond(aigpy.model.ModelBase):
def __init__(self) -> None:
super().__init__()
self.trackid = None
self.videoid = None
self.streamType = None
self.assetPresentation = None
self.audioMode = None
self.audioQuality = None
self.videoQuality = None
self.manifestMimeType = None
self.manifest = None
+192
View File
@@ -0,0 +1,192 @@
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
"""
@File : paths.py
@Date : 2022/06/10
@Author : Yaronzz
@Version : 1.0
@Contact : yaronhuang@foxmail.com
@Desc :
"""
import os
import aigpy
import datetime
from tidal_dl.tidal import *
from tidal_dl.settings import *
def __fixPath__(name: str):
return aigpy.path.replaceLimitChar(name, '-').strip()
def __getYear__(releaseDate: str):
if releaseDate is None or releaseDate == '':
return ''
return aigpy.string.getSubOnlyEnd(releaseDate, '-')
def __getDurationStr__(seconds):
time_string = str(datetime.timedelta(seconds=seconds))
if time_string.startswith('0:'):
time_string = time_string[2:]
return time_string
def __getExtension__(stream: StreamUrl):
if '.flac' in stream.url:
return '.flac'
if '.mp4' in stream.url:
if 'ac4' in stream.codec or 'mha1' in stream.codec:
return '.mp4'
return '.m4a'
return '.m4a'
def getAlbumPath(album):
artistName = __fixPath__(TIDAL_API.getArtistsName(album.artists))
albumArtistName = __fixPath__(album.artist.name) if album.artist is not None else ""
# album folder pre: [ME]
flag = TIDAL_API.getFlag(album, Type.Album, True, "")
if SETTINGS.audioQuality != AudioQuality.Master:
flag = flag.replace("M", "")
if flag != "":
flag = "[" + flag + "] "
# album and addyear
albumName = __fixPath__(album.title)
year = __getYear__(album.releaseDate)
# retpath
retpath = SETTINGS.albumFolderFormat
if retpath is None or len(retpath) <= 0:
retpath = SETTINGS.getDefaultAlbumFolderFormat()
retpath = retpath.replace(R"{ArtistName}", artistName)
retpath = retpath.replace(R"{AlbumArtistName}", albumArtistName)
retpath = retpath.replace(R"{Flag}", flag)
retpath = retpath.replace(R"{AlbumID}", str(album.id))
retpath = retpath.replace(R"{AlbumYear}", year)
retpath = retpath.replace(R"{AlbumTitle}", albumName)
retpath = retpath.replace(R"{AudioQuality}", album.audioQuality)
retpath = retpath.replace(R"{DurationSeconds}", str(album.duration))
retpath = retpath.replace(R"{Duration}", __getDurationStr__(album.duration))
retpath = retpath.replace(R"{NumberOfTracks}", str(album.numberOfTracks))
retpath = retpath.replace(R"{NumberOfVideos}", str(album.numberOfVideos))
retpath = retpath.replace(R"{NumberOfVolumes}", str(album.numberOfVolumes))
retpath = retpath.replace(R"{ReleaseDate}", str(album.releaseDate))
retpath = retpath.replace(R"{RecordType}", album.type)
retpath = retpath.replace(R"{None}", "")
retpath = retpath.strip()
return f"{SETTINGS.downloadPath}/{retpath}"
def getPlaylistPath(playlist):
# name
name = __fixPath__(playlist.title)
return f"{SETTINGS.downloadPath}/Playlist/{name}"
def getTrackPath(track, stream, album=None, playlist=None):
base = './'
number = str(track.trackNumber).rjust(2, '0')
if album is not None:
base = getAlbumPath(album)
if album.numberOfVolumes > 1:
base += f'/CD{str(track.volumeNumber)}'
if playlist is not None and SETTINGS.usePlaylistFolder:
base = getPlaylistPath(playlist)
number = str(track.trackNumberOnPlaylist).rjust(2, '0')
# artist
artists = __fixPath__(TIDAL_API.getArtistsName(track.artists))
artist = __fixPath__(track.artist.name) if track.artist is not None else ""
# title
title = __fixPath__(track.title)
if not aigpy.string.isNull(track.version):
title += f' ({__fixPath__(track.version)})'
# explicit
explicit = "(Explicit)" if track.explicit else ''
# album and addyear
albumName = __fixPath__(album.title)
year = __getYear__(album.releaseDate)
# extension
extension = __getExtension__(stream)
retpath = SETTINGS.trackFileFormat
if retpath is None or len(retpath) <= 0:
retpath = SETTINGS.getDefaultTrackFileFormat()
retpath = retpath.replace(R"{TrackNumber}", number)
retpath = retpath.replace(R"{ArtistName}", artist)
retpath = retpath.replace(R"{ArtistsName}", artists)
retpath = retpath.replace(R"{TrackTitle}", title)
retpath = retpath.replace(R"{ExplicitFlag}", explicit)
retpath = retpath.replace(R"{AlbumYear}", year)
retpath = retpath.replace(R"{AlbumTitle}", albumName)
retpath = retpath.replace(R"{AudioQuality}", track.audioQuality)
retpath = retpath.replace(R"{DurationSeconds}", str(track.duration))
retpath = retpath.replace(R"{Duration}", __getDurationStr__(track.duration))
retpath = retpath.replace(R"{TrackID}", str(track.id))
retpath = retpath.strip()
return f"{base}/{retpath}{extension}"
def getVideoPath(video, album=None, playlist=None):
base = SETTINGS.downloadPath + '/Video/'
if album is not None and album.title is not None:
base = getAlbumPath(album)
elif playlist is not None:
base = getPlaylistPath(playlist)
# get number
number = str(video.trackNumber).rjust(2, '0')
# get artist
artists = __fixPath__(TIDAL_API.getArtistsName(video.artists))
artist = __fixPath__(video.artist.name) if video.artist is not None else ""
# explicit
explicit = "(Explicit)" if video.explicit else ''
# title and year and extension
title = __fixPath__(video.title)
year = __getYear__(video.releaseDate)
extension = ".mp4"
retpath = SETTINGS.videoFileFormat
if retpath is None or len(retpath) <= 0:
retpath = SETTINGS.getDefaultVideoFileFormat()
retpath = retpath.replace(R"{VideoNumber}", number)
retpath = retpath.replace(R"{ArtistName}", artist)
retpath = retpath.replace(R"{ArtistsName}", artists)
retpath = retpath.replace(R"{VideoTitle}", title)
retpath = retpath.replace(R"{ExplicitFlag}", explicit)
retpath = retpath.replace(R"{VideoYear}", year)
retpath = retpath.replace(R"{VideoID}", str(video.id))
retpath = retpath.strip()
return f"{base}/{retpath}{extension}"
def __getHomePath__():
if "XDG_CONFIG_HOME" in os.environ:
return os.environ['XDG_CONFIG_HOME']
elif "HOME" in os.environ:
return os.environ['HOME']
elif "HOMEDRIVE" in os.environ and "HOMEPATH" in os.environ:
return os.environ['HOMEDRIVE'] + os.environ['HOMEPATH']
else:
return os.path.abspath("./")
def getLogPath():
return __getHomePath__() + '/.tidal-dl.log'
def getTokenPath():
return __getHomePath__() + '/.tidal-dl.token.json'
def getProfilePath():
return __getHomePath__() + '/.tidal-dl.json'
+125 -127
View File
@@ -4,21 +4,24 @@
@File : printf.py
@Time : 2020/08/16
@Author : Yaronzz
@Version : 1.0
@Version : 3.0
@Contact : yaronhuang@foxmail.com
@Desc :
'''
import logging
import aigpy
import logging
import prettytable
from tidal_dl.lang.language import getLangName, getLang
from tidal_dl.model import Album, Track, Video, Artist, StreamUrl, VideoStreamUrl
from tidal_dl.settings import Settings, getSettingsPath
import tidal_dl.apiKey as apiKey
from tidal_dl.model import *
from tidal_dl.paths import *
from tidal_dl.settings import *
from tidal_dl.lang.language import *
__LOGO__ = '''
VERSION = '2022.06.20.1'
__LOGO__ = f'''
/$$$$$$$$ /$$ /$$ /$$ /$$ /$$
|__ $$__/|__/ | $$ | $$ | $$| $$
| $$ /$$ /$$$$$$$ /$$$$$$ | $$ /$$$$$$$| $$
@@ -29,86 +32,94 @@ __LOGO__ = '''
|__/ |__/ \_______/ \_______/|__/ \_______/|__/
https://github.com/yaronzz/Tidal-Media-Downloader
{VERSION}
'''
VERSION = '2022.02.07.1'
class Printf(object):
@staticmethod
def logo():
string = __LOGO__ + '\n v' + VERSION
print(string)
logging.info(string)
print(__LOGO__)
logging.info(__LOGO__)
@staticmethod
def __gettable__(columns, rows):
tb = prettytable.PrettyTable()
tb.field_names = list(aigpy.cmd.green(item) for item in columns)
tb.align = 'l'
for item in rows:
tb.add_row(item)
return tb
@staticmethod
def usage():
print("=============TIDAL-DL HELP==============")
tb = prettytable.PrettyTable()
tb.field_names = [aigpy.cmd.green("OPTION"), aigpy.cmd.green("DESC")]
tb.align = 'l'
tb.add_row(["-h or --help", "show help-message"])
tb.add_row(["-v or --version", "show version"])
tb.add_row(["-o or --output", "download path"])
tb.add_row(["-l or --link", "url/id/filePath"])
tb.add_row(["-q or --quality", "track quality('Normal','High,'HiFi','Master')"])
tb.add_row(["-r or --resolution", "video resolution('P1080', 'P720', 'P480', 'P360')"])
# tb.add_row(["-u or --username", "account-email"])
# tb.add_row(["-p or --password", "account-password"])
# tb.add_row(["-a or --accessToken", "account-accessToken"])
tb = Printf.__gettable__(["OPTION", "DESC"], [
["-h or --help", "show help-message"],
["-v or --version", "show version"],
["-o or --output", "download path"],
["-l or --link", "url/id/filePath"],
["-q or --quality", "track quality('Normal','High,'HiFi','Master')"],
["-r or --resolution", "video resolution('P1080', 'P720', 'P480', 'P360')"]
])
print(tb)
@staticmethod
def checkVersion():
onlineVer = aigpy.pip.getLastVersion('tidal-dl')
if onlineVer is None:
icmp = aigpy.system.cmpVersion(onlineVer, VERSION)
if icmp > 0:
Printf.info(LANG.PRINT_LATEST_VERSION + ' ' + onlineVer)
@staticmethod
def settings(data: Settings):
LANG = getLang()
tb = prettytable.PrettyTable()
tb.field_names = [aigpy.cmd.green(LANG.SETTING), aigpy.cmd.green(LANG.VALUE)]
tb.align = 'l'
# tb.add_row(["Settings path", getSettingsPath()])
tb.add_row([LANG.SETTING_PATH, getSettingsPath()])
tb.add_row([LANG.SETTING_DOWNLOAD_PATH, data.downloadPath])
tb.add_row([LANG.SETTING_ONLY_M4A, data.onlyM4a])
# tb.add_row([LANG.SETTING_ADD_EXPLICIT_TAG, data.addExplicitTag])
# tb.add_row([LANG.SETTING_ADD_HYPHEN, data.addHyphen])
# tb.add_row([LANG.SETTING_ADD_YEAR, data.addYear])
# tb.add_row([LANG.SETTING_USE_TRACK_NUM, data.useTrackNumber])
tb.add_row([LANG.SETTING_AUDIO_QUALITY, data.audioQuality])
tb.add_row([LANG.SETTING_VIDEO_QUALITY, data.videoQuality])
tb.add_row([LANG.SETTING_CHECK_EXIST, data.checkExist])
tb.add_row([LANG.SETTING_SHOW_PROGRESS, data.showProgress])
tb.add_row([LANG.SETTING_SAVE_ALBUMINFO, data.saveAlbumInfo])
tb.add_row([LANG.SETTING_SHOW_TRACKIFNO, data.showTrackInfo])
# tb.add_row([LANG.SETTING_ARTIST_BEFORE_TITLE, data.artistBeforeTitle])
# tb.add_row([LANG.SETTING_ALBUMID_BEFORE_FOLDER, data.addAlbumIDBeforeFolder])
tb.add_row([LANG.SETTING_INCLUDE_EP, data.includeEP])
tb.add_row([LANG.SETTING_SAVE_COVERS, data.saveCovers])
tb.add_row([LANG.SETTING_LANGUAGE, getLangName(data.language)])
tb.add_row([LANG.SETTING_USE_PLAYLIST_FOLDER, data.usePlaylistFolder])
tb.add_row([LANG.SETTING_MULITHREAD_DOWNLOAD, data.multiThreadDownload])
tb.add_row([LANG.SETTING_ALBUM_FOLDER_FORMAT, data.albumFolderFormat])
tb.add_row([LANG.SETTING_TRACK_FILE_FORMAT, data.trackFileFormat])
tb.add_row([LANG.SETTING_ADD_LYRICS, data.addLyrics])
tb.add_row([LANG.SETTING_LYRICS_SERVER_PROXY, data.lyricsServerProxy])
tb.add_row([LANG.SETTINGS_ADD_LRC_FILE, data.lyricFile])
tb.add_row([LANG.SETTING_ADD_TYPE_FOLDER, data.addTypeFolder])
tb.add_row([LANG.SETTING_APIKEY, apiKey.getItem(data.apiKeyIndex)['formats']])
def settings():
data = SETTINGS
tb = Printf.__gettable__([LANG.SETTING, LANG.VALUE], [
#settings - path and format
[LANG.SETTING_PATH, getProfilePath()],
[LANG.SETTING_DOWNLOAD_PATH, data.downloadPath],
[LANG.SETTING_ALBUM_FOLDER_FORMAT, data.albumFolderFormat],
[LANG.SETTING_TRACK_FILE_FORMAT, data.trackFileFormat],
[LANG.SETTING_VIDEO_FILE_FORMAT, data.videoFileFormat],
#settings - quality
[LANG.SETTING_AUDIO_QUALITY, data.audioQuality],
[LANG.SETTING_VIDEO_QUALITY, data.videoQuality],
#settings - else
[LANG.SETTING_USE_PLAYLIST_FOLDER, data.usePlaylistFolder],
[LANG.SETTING_CHECK_EXIST, data.checkExist],
[LANG.SETTING_SHOW_PROGRESS, data.showProgress],
[LANG.SETTING_SHOW_TRACKINFO, data.showTrackInfo],
[LANG.SETTING_SAVE_ALBUMINFO, data.saveAlbumInfo],
[LANG.SETTING_SAVE_COVERS, data.saveCovers],
[LANG.SETTING_INCLUDE_EP, data.includeEP],
[LANG.SETTING_LANGUAGE, getLangName(data.language)],
[LANG.SETTINGS_ADD_LRC_FILE, data.lyricFile],
[LANG.SETTING_APIKEY, f"[{data.apiKeyIndex}]" + apiKey.getItem(data.apiKeyIndex)['formats']]
])
print(tb)
@staticmethod
def choices():
LANG = getLang()
print("====================================================")
tb = prettytable.PrettyTable()
tb.field_names = [LANG.CHOICE, LANG.FUNCTION]
tb.align = 'l'
tb = Printf.__gettable__([LANG.CHOICE, LANG.FUNCTION], [
[aigpy.cmd.green(LANG.CHOICE_ENTER + " '0':"), LANG.CHOICE_EXIT],
[aigpy.cmd.green(LANG.CHOICE_ENTER + " '1':"), LANG.CHOICE_LOGIN],
[aigpy.cmd.green(LANG.CHOICE_ENTER + " '2':"), LANG.CHOICE_LOGOUT],
[aigpy.cmd.green(LANG.CHOICE_ENTER + " '3':"), LANG.CHOICE_SET_ACCESS_TOKEN],
[aigpy.cmd.green(LANG.CHOICE_ENTER + " '4':"), LANG.CHOICE_SETTINGS + '-Path'],
[aigpy.cmd.green(LANG.CHOICE_ENTER + " '5':"), LANG.CHOICE_SETTINGS + '-Quality'],
[aigpy.cmd.green(LANG.CHOICE_ENTER + " '6':"), LANG.CHOICE_SETTINGS + '-Else'],
[aigpy.cmd.green(LANG.CHOICE_ENTER + " '7':"), LANG.CHOICE_APIKEY],
[aigpy.cmd.green(LANG.CHOICE_ENTER_URLID), LANG.CHOICE_DOWNLOAD_BY_URL],
])
tb.set_style(prettytable.PLAIN_COLUMNS)
tb.add_row([aigpy.cmd.green(LANG.CHOICE_ENTER + " '0':"), LANG.CHOICE_EXIT])
tb.add_row([aigpy.cmd.green(LANG.CHOICE_ENTER + " '1':"), LANG.CHOICE_LOGIN])
tb.add_row([aigpy.cmd.green(LANG.CHOICE_ENTER + " '2':"), LANG.CHOICE_SETTINGS])
tb.add_row([aigpy.cmd.green(LANG.CHOICE_ENTER + " '3':"), LANG.CHOICE_LOGOUT])
tb.add_row([aigpy.cmd.green(LANG.CHOICE_ENTER + " '4':"), LANG.CHOICE_SET_ACCESS_TOKEN])
tb.add_row([aigpy.cmd.green(LANG.CHOICE_ENTER + " '5':"), LANG.CHOICE_APIKEY])
tb.add_row([aigpy.cmd.green(LANG.CHOICE_ENTER_URLID), LANG.CHOICE_DOWNLOAD_BY_URL])
print(tb)
print("====================================================")
@@ -117,10 +128,15 @@ class Printf(object):
aigpy.cmd.colorPrint(string, aigpy.cmd.TextColor.Yellow, None)
ret = input("")
return ret
@staticmethod
def enterBool(string):
aigpy.cmd.colorPrint(string, aigpy.cmd.TextColor.Yellow, None)
ret = input("")
return ret == '1'
@staticmethod
def enterPath(string, errmsg, retWord='0', default=""):
LANG = getLang()
while True:
ret = aigpy.cmd.inputPath(aigpy.cmd.yellow(string), retWord)
if ret == retWord:
@@ -133,7 +149,6 @@ class Printf(object):
@staticmethod
def enterLimit(string, errmsg, limit=[]):
LANG = getLang()
while True:
ret = aigpy.cmd.inputLimit(aigpy.cmd.yellow(string), limit)
if ret is None:
@@ -153,33 +168,28 @@ class Printf(object):
@staticmethod
def err(string):
LANG = getLang()
print(aigpy.cmd.red(LANG.PRINT_ERR + " ") + string)
logging.error(string)
# logging.error(string)
@staticmethod
def info(string):
LANG = getLang()
print(aigpy.cmd.blue(LANG.PRINT_INFO + " ") + string)
@staticmethod
def success(string):
LANG = getLang()
print(aigpy.cmd.green(LANG.PRINT_SUCCESS + " ") + string)
@staticmethod
def album(data: Album):
LANG = getLang()
tb = prettytable.PrettyTable()
tb.field_names = [aigpy.cmd.green(LANG.MODEL_ALBUM_PROPERTY), aigpy.cmd.green(LANG.VALUE)]
tb.align = 'l'
tb.add_row([LANG.MODEL_TITLE, data.title])
tb.add_row(["ID", data.id])
tb.add_row([LANG.MODEL_TRACK_NUMBER, data.numberOfTracks])
tb.add_row([LANG.MODEL_VIDEO_NUMBER, data.numberOfVideos])
tb.add_row([LANG.MODEL_RELEASE_DATE, data.releaseDate])
tb.add_row([LANG.MODEL_VERSION, data.version])
tb.add_row([LANG.MODEL_EXPLICIT, data.explicit])
tb = Printf.__gettable__([LANG.MODEL_ALBUM_PROPERTY, LANG.VALUE], [
[LANG.MODEL_TITLE, data.title],
["ID", data.id],
[LANG.MODEL_TRACK_NUMBER, data.numberOfTracks],
[LANG.MODEL_VIDEO_NUMBER, data.numberOfVideos],
[LANG.MODEL_RELEASE_DATE, data.releaseDate],
[LANG.MODEL_VERSION, data.version],
[LANG.MODEL_EXPLICIT, data.explicit],
])
print(tb)
logging.info("====album " + str(data.id) + "====\n" +
"title:" + data.title + "\n" +
@@ -189,16 +199,14 @@ class Printf(object):
@staticmethod
def track(data: Track, stream: StreamUrl = None):
LANG = getLang()
tb = prettytable.PrettyTable()
tb.field_names = [aigpy.cmd.green(LANG.MODEL_TRACK_PROPERTY), aigpy.cmd.green(LANG.VALUE)]
tb.align = 'l'
tb.add_row([LANG.MODEL_TITLE, data.title])
tb.add_row(["ID", data.id])
tb.add_row([LANG.MODEL_ALBUM, data.album.title])
tb.add_row([LANG.MODEL_VERSION, data.version])
tb.add_row([LANG.MODEL_EXPLICIT, data.explicit])
tb.add_row(["Max-Q", data.audioQuality])
tb = Printf.__gettable__([LANG.MODEL_TRACK_PROPERTY, LANG.VALUE], [
[LANG.MODEL_TITLE, data.title],
["ID", data.id],
[LANG.MODEL_ALBUM, data.album.title],
[LANG.MODEL_VERSION, data.version],
[LANG.MODEL_EXPLICIT, data.explicit],
["Max-Q", data.audioQuality],
])
if stream is not None:
tb.add_row(["Get-Q", str(stream.soundQuality)])
tb.add_row(["Get-Codec", str(stream.codec)])
@@ -210,19 +218,16 @@ class Printf(object):
@staticmethod
def video(data: Video, stream: VideoStreamUrl = None):
LANG = getLang()
tb = prettytable.PrettyTable()
tb.field_names = [aigpy.cmd.green(LANG.MODEL_VIDEO_PROPERTY), aigpy.cmd.green(LANG.VALUE)]
tb.align = 'l'
tb.add_row([LANG.MODEL_TITLE, data.title])
tb.add_row([LANG.MODEL_ALBUM, data.album.title if data.album != None else None])
tb.add_row([LANG.MODEL_VERSION, data.version])
tb.add_row([LANG.MODEL_EXPLICIT, data.explicit])
tb.add_row(["Max-Q", data.quality])
tb = Printf.__gettable__([LANG.MODEL_VIDEO_PROPERTY, LANG.VALUE], [
[LANG.MODEL_TITLE, data.title],
[LANG.MODEL_ALBUM, data.album.title if data.album != None else None],
[LANG.MODEL_VERSION, data.version],
[LANG.MODEL_EXPLICIT, data.explicit],
["Max-Q", data.quality],
])
if stream is not None:
tb.add_row(["Get-Q", str(stream.resolution)])
tb.add_row(["Get-Codec", str(stream.codec)])
print(tb)
logging.info("====video " + str(data.id) + "====\n" +
"title:" + data.title + "\n" +
@@ -231,14 +236,12 @@ class Printf(object):
@staticmethod
def artist(data: Artist, num):
LANG = getLang()
tb = prettytable.PrettyTable()
tb.field_names = [aigpy.cmd.green(LANG.MODEL_ARTIST_PROPERTY), aigpy.cmd.green(LANG.VALUE)]
tb.align = 'l'
tb.add_row([LANG.MODEL_ID, data.id])
tb.add_row([LANG.MODEL_NAME, data.name])
tb.add_row(["Number of albums", num])
tb.add_row([LANG.MODEL_TYPE, str(data.type)])
tb = Printf.__gettable__([LANG.MODEL_ARTIST_PROPERTY, LANG.VALUE], [
[LANG.MODEL_ID, data.id],
[LANG.MODEL_NAME, data.name],
["Number of albums", num],
[LANG.MODEL_TYPE, str(data.type)],
])
print(tb)
logging.info("====artist " + str(data.id) + "====\n" +
"name:" + data.name + "\n" +
@@ -247,13 +250,11 @@ class Printf(object):
@staticmethod
def playlist(data):
LANG = getLang()
tb = prettytable.PrettyTable()
tb.field_names = [aigpy.cmd.green(LANG.MODEL_PLAYLIST_PROPERTY), aigpy.cmd.green(LANG.VALUE)]
tb.align = 'l'
tb.add_row([LANG.MODEL_TITLE, data.title])
tb.add_row([LANG.MODEL_TRACK_NUMBER, data.numberOfTracks])
tb.add_row([LANG.MODEL_VIDEO_NUMBER, data.numberOfVideos])
tb = Printf.__gettable__([LANG.MODEL_PLAYLIST_PROPERTY, LANG.VALUE], [
[LANG.MODEL_TITLE, data.title],
[LANG.MODEL_TRACK_NUMBER, data.numberOfTracks],
[LANG.MODEL_VIDEO_NUMBER, data.numberOfVideos],
])
print(tb)
logging.info("====playlist " + str(data.uuid) + "====\n" +
"title:" + data.title + "\n" +
@@ -263,13 +264,11 @@ class Printf(object):
@staticmethod
def mix(data):
LANG = getLang()
tb = prettytable.PrettyTable()
tb.field_names = [aigpy.cmd.green(LANG.MODEL_PLAYLIST_PROPERTY), aigpy.cmd.green(LANG.VALUE)]
tb.align = 'l'
tb.add_row([LANG.MODEL_ID, data.id])
tb.add_row([LANG.MODEL_TRACK_NUMBER, len(data.tracks)])
tb.add_row([LANG.MODEL_VIDEO_NUMBER, len(data.videos)])
tb = Printf.__gettable__([LANG.MODEL_PLAYLIST_PROPERTY, LANG.VALUE], [
[LANG.MODEL_ID, data.id],
[LANG.MODEL_TRACK_NUMBER, len(data.tracks)],
[LANG.MODEL_VIDEO_NUMBER, len(data.videos)],
])
print(tb)
logging.info("====Mix " + str(data.id) + "====\n" +
"track num:" + str(len(data.tracks)) + "\n" +
@@ -279,7 +278,6 @@ class Printf(object):
@staticmethod
def apikeys(items):
print("-------------API-KEYS---------------")
LANG = getLang()
tb = prettytable.PrettyTable()
tb.field_names = [aigpy.cmd.green('Index'),
aigpy.cmd.green('Valid'),
+96 -141
View File
@@ -4,164 +4,119 @@
@File : settings.py
@Time : 2020/11/08
@Author : Yaronzz
@Version : 2.0
@Version : 3.0
@Contact : yaronhuang@foxmail.com
@Desc :
'''
import base64
import json
import os
import aigpy
import base64
from aigpy.fileHelper import getContent, write
from aigpy.modelHelper import dictToModel, modelToDict, ModelBase
from tidal_dl.enums import AudioQuality, VideoQuality
from tidal_dl.enums import *
def __encode__(string):
sw = bytes(string, 'utf-8')
st = base64.b64encode(sw)
return st
class Settings(aigpy.model.ModelBase):
checkExist = True
includeEP = True
saveCovers = True
language = 0
lyricFile = False
apiKeyIndex = 0
showProgress = True
showTrackInfo = True
saveAlbumInfo = False
downloadPath = "./download/"
audioQuality = AudioQuality.Normal
videoQuality = VideoQuality.P360
usePlaylistFolder = True
albumFolderFormat = R"{ArtistName}/{Flag} {AlbumTitle} [{AlbumID}] [{AlbumYear}]"
trackFileFormat = R"{TrackNumber} - {ArtistName} - {TrackTitle}{ExplicitFlag}"
videoFileFormat = R"{VideoNumber} - {ArtistName} - {VideoTitle}{ExplicitFlag}"
def getDefaultPathFormat(self, type: Type):
if type == Type.Album:
return R"{ArtistName}/{Flag} {AlbumTitle} [{AlbumID}] [{AlbumYear}]"
elif type == Type.Track:
return R"{TrackNumber} - {ArtistName} - {TrackTitle}{ExplicitFlag}"
elif type == Type.Video:
return R"{VideoNumber} - {ArtistName} - {VideoTitle}{ExplicitFlag}"
return ""
def getAudioQuality(self, value):
for item in AudioQuality:
if item.name == value:
return item
return AudioQuality.Normal
def getVideoQuality(self, value):
for item in VideoQuality:
if item.name == value:
return item
return VideoQuality.P360
def read(self, path):
self._path_ = path
txt = aigpy.file.getContent(self._path_)
if len(txt) > 0:
data = json.loads(txt)
if aigpy.model.dictToModel(data, self) is None:
return
self.audioQuality = self.getAudioQuality(self.audioQuality)
self.videoQuality = self.getVideoQuality(self.videoQuality)
if self.albumFolderFormat is None:
self.albumFolderFormat = self.getDefaultPathFormat(Type.Album)
if self.trackFileFormat is None:
self.trackFileFormat = self.getDefaultPathFormat(Type.Track)
if self.videoFileFormat is None:
self.videoFileFormat = self.getDefaultPathFormat(Type.Video)
if self.apiKeyIndex is None:
self.apiKeyIndex = 0
def save(self):
data = aigpy.model.modelToDict(self)
data['audioQuality'] = self.audioQuality.name
data['videoQuality'] = self.videoQuality.name
txt = json.dumps(data)
aigpy.file.write(self._path_, txt, 'w+')
def __decode__(string):
try:
sr = base64.b64decode(string)
st = sr.decode()
return st
except:
return string
def getSettingsPath():
if "XDG_CONFIG_HOME" in os.environ:
return os.environ['XDG_CONFIG_HOME']
elif "HOME" in os.environ:
return os.environ['HOME']
elif "HOMEDRIVE" in os.environ and "HOMEPATH" in os.environ:
return os.environ['HOMEDRIVE'] + os.environ['HOMEPATH']
else:
return os.path._getfullpathname("./")
def getLogPath():
return getSettingsPath() + '/.tidal-dl.log'
class TokenSettings(ModelBase):
class TokenSettings(aigpy.model.ModelBase):
userid = None
countryCode = None
accessToken = None
refreshToken = None
expiresAfter = 0
@staticmethod
def read():
path = TokenSettings.__getFilePath__()
txt = getContent(path)
if txt == "":
return TokenSettings()
txt = __decode__(txt)
data = json.loads(txt)
ret = dictToModel(data, TokenSettings())
if ret is None:
return TokenSettings()
return ret
def __encode__(self, string):
sw = bytes(string, 'utf-8')
st = base64.b64encode(sw)
return st
@staticmethod
def save(model):
data = modelToDict(model)
def __decode__(self, string):
try:
sr = base64.b64decode(string)
st = sr.decode()
return st
except:
return string
def read(self, path):
self._path_ = path
txt = aigpy.file.getContent(self._path_)
if len(txt) > 0:
data = json.loads(self.__decode__(txt))
aigpy.model.dictToModel(data, self)
def save(self):
data = aigpy.model.modelToDict(self)
txt = json.dumps(data)
txt = __encode__(txt)
path = TokenSettings.__getFilePath__()
write(path, txt, 'wb')
@staticmethod
def __getFilePath__():
return getSettingsPath() + '/.tidal-dl.token.json'
aigpy.file.write(self._path_, self.__encode__(txt), 'wb')
class Settings(ModelBase):
addLyrics = False
lyricsServerProxy = ''
downloadPath = "./download/"
onlyM4a = False
addExplicitTag = True
addHyphen = True
addYear = False
useTrackNumber = True
audioQuality = AudioQuality.Normal
videoQuality = VideoQuality.P360
checkExist = True
artistBeforeTitle = False
includeEP = True
addAlbumIDBeforeFolder = False
saveCovers = True
language = 0
usePlaylistFolder = True
multiThreadDownload = True
albumFolderFormat = R"{ArtistName}/{Flag} {AlbumTitle} [{AlbumID}] [{AlbumYear}]"
trackFileFormat = R"{TrackNumber} - {ArtistName} - {TrackTitle}{ExplicitFlag}"
showProgress = True
showTrackInfo = True
saveAlbumInfo = False
lyricFile = False
apiKeyIndex = 0
addTypeFolder = True
@staticmethod
def getDefaultAlbumFolderFormat():
return R"{ArtistName}/{Flag} {AlbumTitle} [{AlbumID}] [{AlbumYear}]"
@staticmethod
def getDefaultTrackFileFormat():
return R"{TrackNumber} - {ArtistName} - {TrackTitle}{ExplicitFlag}"
@staticmethod
def read():
path = Settings.__getFilePath__()
txt = getContent(path)
if txt == "":
return Settings()
data = json.loads(txt)
ret = dictToModel(data, Settings())
if ret is None:
return Settings()
ret.audioQuality = Settings.getAudioQuality(ret.audioQuality)
ret.videoQuality = Settings.getVideoQuality(ret.videoQuality)
ret.usePlaylistFolder = ret.usePlaylistFolder == True or ret.usePlaylistFolder is None
ret.multiThreadDownload = ret.multiThreadDownload == True or ret.multiThreadDownload is None
ret.addTypeFolder = ret.addTypeFolder == True or ret.addTypeFolder is None
if ret.albumFolderFormat is None:
ret.albumFolderFormat = Settings.getDefaultAlbumFolderFormat()
if ret.trackFileFormat is None:
ret.trackFileFormat = Settings.getDefaultTrackFileFormat()
if ret.apiKeyIndex is None:
ret.apiKeyIndex = 0
return ret
@staticmethod
def save(model):
data = modelToDict(model)
data['audioQuality'] = model.audioQuality.name
data['videoQuality'] = model.videoQuality.name
txt = json.dumps(data)
path = Settings.__getFilePath__()
write(path, txt, 'w+')
@staticmethod
def getAudioQuality(value):
for item in AudioQuality:
if item.name == value:
return item
return AudioQuality.Normal
@staticmethod
def getVideoQuality(value):
for item in VideoQuality:
if item.name == value:
return item
return VideoQuality.P360
@staticmethod
def __getFilePath__():
return getSettingsPath() + '/.tidal-dl.json'
# Singleton
SETTINGS = Settings()
TOKEN = TokenSettings()
+168 -297
View File
@@ -4,138 +4,72 @@
@File : tidal.py
@Time : 2019/02/27
@Author : Yaronzz
@VERSION : 2.0
@VERSION : 3.0
@Contact : yaronhuang@foxmail.com
@Desc : tidal api
'''
import base64
import json
import logging
import aigpy.stringHelper as stringHelper
import aigpy
import base64
import requests
from aigpy.modelHelper import dictToModel
from aigpy.stringHelper import isNull
from requests.packages import urllib3
from tidal_dl.enums import Type, AudioQuality, VideoQuality
from tidal_dl.model import Album, Track, Video, Artist, Playlist, StreamUrl, VideoStreamUrl, SearchResult, Lyrics, Mix
import tidal_dl.apiKey as apiKey
__VERSION__ = '1.9.1'
__URL_PRE__ = 'https://api.tidalhifi.com/v1/'
__AUTH_URL__ = 'https://auth.tidal.com/v1/oauth2'
__API_KEY__ = {'clientId': '7m7Ap0JC9j1cOM3n',
'clientSecret': 'vRAdA108tlvkJpTsGZS8rGZ7xTlbJ0qaZ2K9saEzsgY='}
from tidal_dl.model import *
from tidal_dl.enums import *
# SSL Warnings
urllib3.disable_warnings()
# add retry number
# SSL Warnings | retry number
requests.packages.urllib3.disable_warnings()
requests.adapters.DEFAULT_RETRIES = 5
class LoginKey(object):
def __init__(self):
self.deviceCode = None
self.userCode = None
self.verificationUrl = None
self.authCheckTimeout = None
self.authCheckInterval = None
self.userId = None
self.countryCode = None
self.accessToken = None
self.refreshToken = None
self.expiresIn = None
class __StreamRespond__(object):
trackid = None
videoid = None
streamType = None
assetPresentation = None
audioMode = None
audioQuality = None
videoQuality = None
manifestMimeType = None
manifest = None
class TidalAPI(object):
def __init__(self):
self.apiKey = __API_KEY__
self.key = LoginKey()
self.__debugVar = 0
self.apiKey = {'clientId': '7m7Ap0JC9j1cOM3n',
'clientSecret': 'vRAdA108tlvkJpTsGZS8rGZ7xTlbJ0qaZ2K9saEzsgY='}
def __toJson__(self, string: str):
try:
json_object = json.loads(string)
except:
return None
return json_object
def __get__(self, path, params={}, retry=3, urlpre=__URL_PRE__):
# deprecate the sessionId
# header = {'X-Tidal-SessionId': self.key.sessionId}T
def __get__(self, path, params={}, urlpre='https://api.tidalhifi.com/v1/'):
header = {}
if not isNull(self.key.accessToken):
header = {'authorization': 'Bearer {}'.format(self.key.accessToken)}
header = {'authorization': f'Bearer {self.key.accessToken}'}
params['countryCode'] = self.key.countryCode
result = None
respond = None
for index in range(0, retry):
errmsg = "Get operation err!"
for index in range(0, 3):
try:
respond = requests.get(urlpre + path, headers=header, params=params)
result = self.__toJson__(respond.text)
result = json.loads(respond.text)
if 'status' not in result:
return result
if 'userMessage' in result and result['userMessage'] is not None:
errmsg += result['userMessage']
break
except:
continue
except Exception as e:
if index >= 3:
errmsg += respond.text
if result is None:
return "Get operation err!" + respond.text, None
if 'status' in result:
if 'userMessage' in result and result['userMessage'] is not None:
return result['userMessage'], None
else:
logging.error("[Get operation err] path=" + path + ". respon=" + respond.text)
return "Get operation err!", None
return None, result
raise Exception(errmsg)
def __getItems__(self, path, params={}, retry=3):
def __getItems__(self, path, params={}):
params['limit'] = 50
params['offset'] = 0
total = 0
ret = []
while True:
msg, data = self.__get__(path, params, retry)
if msg is not None:
return msg, None
if 'totalNumberOfItems'in data:
data = self.__get__(path, params)
if 'totalNumberOfItems' in data:
total = data['totalNumberOfItems']
if total > 0 and total <= len(ret):
return None, ret
num = 0
for item in data["items"]:
num += 1
ret.append(item)
return ret
ret += data["items"]
num = len(data["items"])
if num < 50:
break
params['offset'] += num
return None, ret
def __getQualityString__(self, quality: AudioQuality):
if quality == AudioQuality.Normal:
return "LOW"
if quality == AudioQuality.High:
return "HIGH"
if quality == AudioQuality.HiFi:
return "LOSSLESS"
return "HI_RES"
return ret
def __getResolutionList__(self, url):
ret = []
txt = requests.get(url).text
txt = requests.get(url).content.decode('utf-8')
# array = txt.split("#EXT-X-STREAM-INF")
array = txt.split("#")
for item in array:
@@ -144,66 +78,53 @@ class TidalAPI(object):
if "EXT-X-STREAM-INF:" not in item:
continue
stream = VideoStreamUrl()
stream.codec = stringHelper.getSub(item, "CODECS=\"", "\"")
stream.m3u8Url = "http" + stringHelper.getSubOnlyStart(item, "http").strip()
stream.resolution = stringHelper.getSub(item, "RESOLUTION=", "http").strip()
stream.codec = aigpy.string.getSub(item, "CODECS=\"", "\"")
stream.m3u8Url = "http" + aigpy.string.getSubOnlyStart(item, "http").strip()
stream.resolution = aigpy.string.getSub(item, "RESOLUTION=", "http").strip()
stream.resolution = stream.resolution.split(',')[0]
stream.resolutions = stream.resolution.split("x")
ret.append(stream)
return ret
def __post__(self, url, data, auth=None):
retry = 3
while retry > 0:
def __post__(self, path, data, auth=None, urlpre='https://auth.tidal.com/v1/oauth2'):
for index in range(0, 3):
try:
result = requests.post(url, data=data, auth=auth, verify=False).json()
except (
requests.ConnectionError,
requests.exceptions.ReadTimeout,
requests.exceptions.Timeout,
requests.exceptions.ConnectTimeout,
) as e:
retry -= 1
if retry <= 0:
return e, None
continue
return None, result
result = requests.post(urlpre+path, data=data, auth=auth, verify=False).json()
return result
except Exception as e:
if index >= 3:
raise e
def getDeviceCode(self):
def getDeviceCode(self) -> str:
data = {
'client_id': self.apiKey['clientId'],
'scope': 'r_usr+w_usr+w_sub'
}
e, result = self.__post__(__AUTH_URL__ + '/device_authorization', data)
if e is not None:
return str(e), False
result = self.__post__('/device_authorization', data)
if 'status' in result and result['status'] != 200:
return "Device authorization failed. Please try again.", False
raise Exception("Device authorization failed. Please choose another apikey.")
self.key.deviceCode = result['deviceCode']
self.key.userCode = result['userCode']
self.key.verificationUrl = result['verificationUri']
self.key.authCheckTimeout = result['expiresIn']
self.key.authCheckInterval = result['interval']
return None, True
return "http://" + self.key.verificationUrl + "/" + self.key.userCode
def checkAuthStatus(self):
def checkAuthStatus(self) -> bool:
data = {
'client_id': self.apiKey['clientId'],
'device_code': self.key.deviceCode,
'grant_type': 'urn:ietf:params:oauth:grant-type:device_code',
'scope': 'r_usr+w_usr+w_sub'
}
e, result = self.__post__(__AUTH_URL__ + '/token', data, (self.apiKey['clientId'], self.apiKey['clientSecret']))
if e is not None:
return str(e), False
auth = (self.apiKey['clientId'], self.apiKey['clientSecret'])
result = self.__post__('/token', data, auth)
if 'status' in result and result['status'] != 200:
if result['status'] == 400 and result['sub_status'] == 1002:
return "pending", False
return False
else:
return "Error while checking for authorization. Trying again...", False
raise Exception("Error while checking for authorization. Trying again...")
# if auth is successful:
self.key.userId = result['user']['userId']
@@ -211,151 +132,154 @@ class TidalAPI(object):
self.key.accessToken = result['access_token']
self.key.refreshToken = result['refresh_token']
self.key.expiresIn = result['expires_in']
return None, True
return True
def verifyAccessToken(self, accessToken):
def verifyAccessToken(self, accessToken) -> bool:
header = {'authorization': 'Bearer {}'.format(accessToken)}
result = requests.get('https://api.tidal.com/v1/sessions', headers=header).json()
if 'status' in result and result['status'] != 200:
return "Login failed!", False
return None, True
return False
return True
def refreshAccessToken(self, refreshToken):
def refreshAccessToken(self, refreshToken) -> bool:
data = {
'client_id': self.apiKey['clientId'],
'refresh_token': refreshToken,
'grant_type': 'refresh_token',
'scope': 'r_usr+w_usr+w_sub'
}
e, result = self.__post__(__AUTH_URL__ + '/token', data, (self.apiKey['clientId'], self.apiKey['clientSecret']))
if e is not None:
return str(e), False
# result = requests.post(__AUTH_URL__ + '/token', data=data, auth=(self.apiKey['clientId'], self.apiKey['clientSecret'])).json()
auth = (self.apiKey['clientId'], self.apiKey['clientSecret'])
result = self.__post__('/token', data, auth)
if 'status' in result and result['status'] != 200:
return "Refresh failed. Please log in again.", False
return False
# if auth is successful:
self.key.userId = result['user']['userId']
self.key.countryCode = result['user']['countryCode']
self.key.accessToken = result['access_token']
self.key.expiresIn = result['expires_in']
return None, True
return True
def loginByAccessToken(self, accessToken, userid=None):
header = {'authorization': 'Bearer {}'.format(accessToken)}
result = requests.get('https://api.tidal.com/v1/sessions', headers=header).json()
if 'status' in result and result['status'] != 200:
return "Login failed!", False
raise Exception("Login failed!")
if not isNull(userid):
if not aigpy.string.isNull(userid):
if str(result['userId']) != str(userid):
return "User mismatch! Please use your own accesstoken.", False
raise Exception("User mismatch! Please use your own accesstoken.",)
self.key.userId = result['userId']
self.key.countryCode = result['countryCode']
self.key.accessToken = accessToken
return None, True
return
def getAlbum(self, id):
msg, data = self.__get__('albums/' + str(id))
return msg, dictToModel(data, Album())
def getAlbum(self, id) -> Album:
return aigpy.model.dictToModel(self.__get__('albums/' + str(id)), Album())
def getPlaylist(self, id):
msg, data = self.__get__('playlists/' + str(id))
return msg, dictToModel(data, Playlist())
def getPlaylist(self, id) -> Playlist:
return aigpy.model.dictToModel(self.__get__('playlists/' + str(id)), Playlist())
def getArtist(self, id):
msg, data = self.__get__('artists/' + str(id))
return msg, dictToModel(data, Artist())
def getArtist(self, id) -> Artist:
return aigpy.model.dictToModel(self.__get__('artists/' + str(id)), Artist())
def getTrack(self, id):
msg, data = self.__get__('tracks/' + str(id))
return msg, dictToModel(data, Track())
def getTrack(self, id) -> Track:
return aigpy.model.dictToModel(self.__get__('tracks/' + str(id)), Track())
def getVideo(self, id):
msg, data = self.__get__('videos/' + str(id))
return msg, dictToModel(data, Video())
def getMix(self, id):
msg, tracks, videos = self.getItems(id, Type.Mix)
if msg is not None:
return msg, None
def getVideo(self, id) -> Video:
return aigpy.model.dictToModel(self.__get__('videos/' + str(id)), Video())
def getMix(self, id) -> Mix:
mix = Mix()
mix.id = id
mix.tracks = tracks
mix.videos = videos
mix.tracks, mix.videos = self.getItems(id, Type.Mix)
return None, mix
def search(self, text: str, type: Type, offset: int, limit: int):
typeStr = "ARTISTS,ALBUMS,TRACKS,VIDEOS,PLAYLISTS"
def getTypeData(self, id, type: Type):
if type == Type.Album:
typeStr = "ALBUMS"
return self.getAlbum(id)
if type == Type.Artist:
typeStr = "ARTISTS"
return self.getArtist(id)
if type == Type.Track:
typeStr = "TRACKS"
return self.getTrack(id)
if type == Type.Video:
typeStr = "VIDEOS"
return self.getVideo(id)
if type == Type.Playlist:
typeStr = "PLAYLISTS"
return self.getPlaylist(id)
if type == Type.Mix:
return self.getMix(id)
return None
def search(self, text: str, type: Type, offset: int = 0, limit: int = 10) -> SearchResult:
typeStr = type.name.upper() + "S"
if type == Type.Null:
typeStr = "ARTISTS,ALBUMS,TRACKS,VIDEOS,PLAYLISTS"
params = {"query": text,
"offset": offset,
"limit": limit,
"types": typeStr}
return aigpy.model.dictToModel(self.__get__('search', params=params), SearchResult())
msg, data = self.__get__('search', params=params)
return msg, dictToModel(data, SearchResult())
def getSearchResultItems(self, result: SearchResult, type: Type):
if type == Type.Track:
return result.tracks.items
if type == Type.Video:
return result.videos.items
if type == Type.Album:
return result.albums.items
if type == Type.Artist:
return result.artists.items
if type == Type.Playlist:
return result.playlists.items
return []
def getLyrics(self, id):
msg, data = self.__get__('tracks/' + str(id) + "/lyrics", urlpre='https://listen.tidal.com/v1/')
return msg, dictToModel(data, Lyrics())
def getLyrics(self, id) -> Lyrics:
data = self.__get__(f'tracks/{str(id)}/lyrics', urlpre='https://listen.tidal.com/v1/')
return aigpy.model.dictToModel(data, Lyrics())
def getItems(self, id, type: Type):
if type == Type.Playlist:
msg, data = self.__getItems__('playlists/' + str(id) + "/items")
data = self.__getItems__('playlists/' + str(id) + "/items")
elif type == Type.Album:
msg, data = self.__getItems__('albums/' + str(id) + "/items")
data = self.__getItems__('albums/' + str(id) + "/items")
elif type == Type.Mix:
msg, data = self.__getItems__('mixes/' + str(id) + '/items')
data = self.__getItems__('mixes/' + str(id) + '/items')
else:
return "invalid Type!", None, None
if msg is not None:
return msg, None, None
raise Exception("invalid Type!")
tracks = []
videos = []
for item in data:
if item['type'] == 'track':
tracks.append(dictToModel(item['item'], Track()))
tracks.append(aigpy.model.dictToModel(item['item'], Track()))
else:
videos.append(dictToModel(item['item'], Video()))
return msg, tracks, videos
videos.append(aigpy.model.dictToModel(item['item'], Video()))
return tracks, videos
def getArtistAlbums(self, id, includeEP=False):
albums = []
msg, data = self.__getItems__('artists/' + str(id) + "/albums")
if msg is not None:
return msg, None
for item in data:
albums.append(dictToModel(item, Album()))
if includeEP == False:
return None, albums
msg, data = self.__getItems__('artists/' + str(id) + "/albums", {"filter": "EPSANDSINGLES"})
if msg is not None:
return msg, None
for item in data:
albums.append(dictToModel(item, Album()))
return None, albums
data = self.__getItems__(f'artists/{str(id)}/albums')
albums = list(aigpy.model.dictToModel(item, Album()) for item in data)
if not includeEP:
return albums
data = self.__getItems__(f'artists/{str(id)}/albums', {"filter": "EPSANDSINGLES"})
albums += list(aigpy.model.dictToModel(item, Album()) for item in data)
return albums
def getStreamUrl(self, id, quality: AudioQuality):
squality = self.__getQualityString__(quality)
squality = "HI_RES"
if quality == AudioQuality.Normal:
squality = "LOW"
elif quality == AudioQuality.High:
squality = "HIGH"
elif quality == AudioQuality.HiFi:
squality = "LOSSLESS"
paras = {"audioquality": squality, "playbackmode": "STREAM", "assetpresentation": "FULL"}
msg, data = self.__get__('tracks/' + str(id) + "/playbackinfopostpaywall", paras)
if msg is not None:
return msg, None
resp = dictToModel(data, __StreamRespond__())
data = self.__get__(f'tracks/{str(id)}/playbackinfopostpaywall', paras)
resp = aigpy.model.dictToModel(data, StreamRespond())
if "vnd.tidal.bt" in resp.manifestMimeType:
manifest = json.loads(base64.b64decode(resp.manifest).decode('utf-8'))
@@ -365,15 +289,13 @@ class TidalAPI(object):
ret.codec = manifest['codecs']
ret.encryptionKey = manifest['keyId'] if 'keyId' in manifest else ""
ret.url = manifest['urls'][0]
return "", ret
return "Can't get the streamUrl, type is " + resp.manifestMimeType, None
return ret
raise Exception("Can't get the streamUrl, type is " + resp.manifestMimeType)
def getVideoStreamUrl(self, id, quality: VideoQuality):
paras = {"videoquality": "HIGH", "playbackmode": "STREAM", "assetpresentation": "FULL"}
msg, data = self.__get__('videos/' + str(id) + "/playbackinfopostpaywall", paras)
if msg is not None:
return msg, None
resp = dictToModel(data, __StreamRespond__())
data = self.__get__(f'videos/{str(id)}/playbackinfopostpaywall', paras)
resp = aigpy.model.dictToModel(data, StreamRespond())
if "vnd.tidal.emu" in resp.manifestMimeType:
manifest = json.loads(base64.b64decode(resp.manifest).decode('utf-8'))
@@ -386,31 +308,25 @@ class TidalAPI(object):
index += 1
if index >= len(array):
index = len(array) - 1
return "", array[index]
return "Can't get the streamUrl, type is " + resp.manifestMimeType, None
return array[index]
raise Exception("Can't get the streamUrl, type is " + resp.manifestMimeType)
def getTrackContributors(self, id):
msg, data = self.__get__('tracks/' + str(id) + "/contributors")
return msg, data
return self.__get__(f'tracks/{str(id)}/contributors')
def getCoverUrl(self, sid, width="320", height="320"):
if sid is None or sid == "":
return None
return "https://resources.tidal.com/images/" + sid.replace("-", "/") + "/" + width + "x" + height + ".jpg"
return f"https://resources.tidal.com/images/{sid.replace('-', '/')}/{width}x{height}.jpg"
def getCoverData(self, sid, width="320", height="320"):
url = self.getCoverUrl(sid, width, height)
try:
respond = requests.get(url)
return respond.content
return requests.get(url).content
except:
return ''
def getArtistsName(self, artists=[]):
array = []
for item in artists:
array.append(item.name)
return " / ".join(array)
array = list(item.name for item in artists)
return ", ".join(array)
def getFlag(self, data, type: Type, short=True, separator=" / "):
master = False
@@ -438,80 +354,35 @@ class TidalAPI(object):
return separator.join(array)
def parseUrl(self, url):
etype = Type.Null
sid = ""
if "tidal.com" not in url:
return etype, sid
return Type.Null, url
url = url.lower()
if 'artist' in url:
etype = Type.Artist
if 'album' in url:
etype = Type.Album
if 'track' in url:
etype = Type.Track
if 'video' in url:
etype = Type.Video
if 'playlist' in url:
etype = Type.Playlist
if 'mix' in url:
etype = Type.Mix
if etype == Type.Null:
return etype, sid
sid = stringHelper.getSub(url, etype.name.lower() + '/', '/')
return etype, sid
for index, item in enumerate(Type):
if item.name.lower() in url:
etype = item
return etype, aigpy.string.getSub(url, etype.name.lower() + '/', '/')
return Type.Null, url
def getByString(self, string):
etype = Type.Null
if aigpy.string.isNull(string):
raise Exception("Please enter something.")
obj = None
if isNull(string):
return "Please enter something.", etype, obj
etype, sid = self.parseUrl(string)
if isNull(sid):
sid = string
for index, item in enumerate(Type):
if etype != Type.Null and etype != item:
continue
if item == Type.Null:
continue
try:
obj = self.getTypeData(sid, item)
return item, obj
except:
continue
if obj is None and (etype == Type.Null or etype == Type.Album):
msg, obj = self.getAlbum(sid)
if obj is None and (etype == Type.Null or etype == Type.Artist):
msg, obj = self.getArtist(sid)
if obj is None and (etype == Type.Null or etype == Type.Track):
msg, obj = self.getTrack(sid)
if obj is None and (etype == Type.Null or etype == Type.Video):
msg, obj = self.getVideo(sid)
if obj is None and (etype == Type.Null or etype == Type.Playlist):
msg, obj = self.getPlaylist(sid)
if obj is None and (etype == Type.Null or etype == Type.Mix):
msg, obj = self.getMix(sid)
raise Exception("No result.")
if obj is None or etype != Type.Null:
return msg, etype, obj
if obj.__class__ == Album:
etype = Type.Album
if obj.__class__ == Artist:
etype = Type.Artist
if obj.__class__ == Track:
etype = Type.Track
if obj.__class__ == Video:
etype = Type.Video
if obj.__class__ == Playlist:
etype = Type.Playlist
if obj.__class__ == Mix:
etype = Type.Mix
return msg, etype, obj
"""
def getToken(self):
token1 = "MbjR4DLXz1ghC4rV"
token2 = "pl4Vc0hemlAXD0mN" # only lossless
try:
msg = requests.get( "https://cdn.jsdelivr.net/gh/yaronzz/CDN@latest/app/tidal/tokens.json", timeout=(20.05, 27.05))
tokens = json.loads(msg.text)
token1 = tokens['token']
token2 = tokens['token2']
except Exception as e:
pass
return token1,token2
"""
# Singleton
TIDAL_API = TidalAPI()
-527
View File
@@ -1,527 +0,0 @@
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
"""
@File : util.py
@Date : 2021/10/09
@Author : Yaronzz
@Version : 1.0
@Contact : yaronhuang@foxmail.com
@Desc :
"""
import logging
import os
import time
import requests
import aigpy
import lyricsgenius
import datetime
from tidal_dl import apiKey
import tidal_dl
from tidal_dl.decryption import decrypt_file
from tidal_dl.decryption import decrypt_security_token
from tidal_dl.enums import Type, AudioQuality
from tidal_dl.lang.language import initLang
from tidal_dl.model import Track, Video, Lyrics, Mix, Album
from tidal_dl.printf import Printf
from tidal_dl.settings import Settings, TokenSettings, getLogPath
from tidal_dl.tidal import TidalAPI
TOKEN = TokenSettings.read()
CONF = Settings.read()
LANG = initLang(CONF.language)
API = TidalAPI()
API.apiKey = apiKey.getItem(CONF.apiKeyIndex)
logging.basicConfig(filename=getLogPath(),
level=logging.INFO,
format='%(asctime)s - %(levelname)s: %(message)s')
def __getIndexStr__(index):
pre = "0"
if index < 10:
return pre + str(index)
if index < 99:
return str(index)
return str(index)
def __getExtension__(url):
if '.flac' in url:
return '.flac'
if '.mp4' in url:
return '.mp4'
return '.m4a'
def __secondsToTimeStr__(seconds):
time_string = str(datetime.timedelta(seconds=seconds))
if time_string.startswith('0:'):
time_string = time_string[2:]
return time_string
def __parseContributors__(roleType, Contributors):
if Contributors is None:
return None
try:
ret = []
for item in Contributors['items']:
if item['role'] == roleType:
ret.append(item['name'])
return ret
except:
return None
GEMIUS = lyricsgenius.Genius('vNKbAWAE3rVY_48nRaiOrDcWNLvsxS-Z8qyG5XfEzTOtZvkTfg6P3pxOVlA2BjaW')
def getLyricsFromGemius(trackName, artistName, proxy):
try:
if not aigpy.string.isNull(proxy):
GEMIUS._session.proxies = {
'http': f'http://{proxy}',
'https': f'http://{proxy}',
}
song = GEMIUS.search_song(trackName, artistName)
return song.lyrics
except:
return ""
def stripPathParts(stripped_path, separator):
result = ""
stripped_path = stripped_path.split(separator)
for stripped_path_part in stripped_path:
result += stripped_path_part.strip()
if not stripped_path.index(stripped_path_part) == len(stripped_path) - 1:
result += separator
return result.strip()
def stripPath(path):
result = stripPathParts(path, "/")
result = stripPathParts(result, "\\")
return result.strip()
def getArtistsName(artists):
return ", ".join(map(lambda artist: artist.name, artists))
# "{ArtistName}/{Flag} [{AlbumID}] [{AlbumYear}] {AlbumTitle}"
def getAlbumPath(conf: Settings, album):
base = conf.downloadPath + '/'
if conf.addTypeFolder:
base = base + 'Album/'
artist = aigpy.path.replaceLimitChar(getArtistsName(album.artists), '-')
albumArtistName = album.artist.name if album.artist is not None else ""
# album folder pre: [ME][ID]
flag = API.getFlag(album, Type.Album, True, "")
if conf.audioQuality != AudioQuality.Master:
flag = flag.replace("M", "")
if not conf.addExplicitTag:
flag = flag.replace("E", "")
if not aigpy.string.isNull(flag):
flag = "[" + flag + "] "
sid = str(album.id)
# album and addyear
albumname = aigpy.path.replaceLimitChar(album.title, '-')
year = ""
if album.releaseDate is not None:
year = aigpy.string.getSubOnlyEnd(album.releaseDate, '-')
# retpath
retpath = conf.albumFolderFormat
if retpath is None or len(retpath) <= 0:
retpath = Settings.getDefaultAlbumFolderFormat()
retpath = retpath.replace(R"{ArtistName}", artist.strip())
retpath = retpath.replace(R"{AlbumArtistName}", albumArtistName.strip())
retpath = retpath.replace(R"{Flag}", flag)
retpath = retpath.replace(R"{AlbumID}", sid)
retpath = retpath.replace(R"{AlbumYear}", year)
retpath = retpath.replace(R"{AlbumTitle}", albumname.strip())
retpath = retpath.replace(R"{AudioQuality}", album.audioQuality)
retpath = retpath.replace(R"{DurationSeconds}", str(album.duration))
retpath = retpath.replace(R"{Duration}", __secondsToTimeStr__(album.duration))
retpath = retpath.replace(R"{NumberOfTracks}", str(album.numberOfTracks))
retpath = retpath.replace(R"{NumberOfVideos}", str(album.numberOfVideos))
retpath = retpath.replace(R"{NumberOfVolumes}", str(album.numberOfVolumes))
retpath = retpath.replace(R"{ReleaseDate}", str(album.releaseDate))
retpath = retpath.replace(R"{RecordType}", album.type)
retpath = retpath.replace(R"{None}", "")
retpath = stripPath(retpath.strip())
return base + retpath
def getPlaylistPath(conf: Settings, playlist):
# outputdir/Playlist/
base = conf.downloadPath + '/'
if conf.addTypeFolder:
base = base + 'Playlist/'
# name
name = aigpy.path.replaceLimitChar(playlist.title, '-')
return base + name + '/'
def getTrackPath(conf: Settings, track, stream, album=None, playlist=None):
base = './'
if album is not None:
base = getAlbumPath(conf, album) + '/'
if album.numberOfVolumes > 1:
base += 'CD' + str(track.volumeNumber) + '/'
if playlist is not None and conf.usePlaylistFolder:
base = getPlaylistPath(conf, playlist)
# number
number = __getIndexStr__(track.trackNumber)
if playlist is not None and conf.usePlaylistFolder:
number = __getIndexStr__(track.trackNumberOnPlaylist)
# artist
artists = aigpy.path.replaceLimitChar(getArtistsName(track.artists), '-')
artist = track.artist.name if track.artist is not None else ""
# title
title = track.title
if not aigpy.string.isNull(track.version):
title += ' (' + track.version + ')'
title = aigpy.path.replaceLimitChar(title, '-')
# get explicit
explicit = "(Explicit)" if conf.addExplicitTag and track.explicit else ''
# album and addyear
albumname = aigpy.path.replaceLimitChar(album.title, '-')
year = ""
if album.releaseDate is not None:
year = aigpy.string.getSubOnlyEnd(album.releaseDate, '-')
# extension
extension = __getExtension__(stream.url)
retpath = conf.trackFileFormat
if retpath is None or len(retpath) <= 0:
retpath = Settings.getDefaultTrackFileFormat()
retpath = retpath.replace(R"{TrackNumber}", number)
retpath = retpath.replace(R"{ArtistName}", artist.strip())
retpath = retpath.replace(R"{ArtistsName}", artists.strip())
retpath = retpath.replace(R"{TrackTitle}", title)
retpath = retpath.replace(R"{ExplicitFlag}", explicit)
retpath = retpath.replace(R"{AlbumYear}", year)
retpath = retpath.replace(R"{AlbumTitle}", albumname.strip())
retpath = retpath.replace(R"{AudioQuality}", track.audioQuality)
retpath = retpath.replace(R"{DurationSeconds}", str(track.duration))
retpath = retpath.replace(R"{Duration}", __secondsToTimeStr__(track.duration))
retpath = retpath.strip()
return base + retpath + extension
def getVideoPath(conf, video, album=None, playlist=None):
if album is not None and album.title is not None:
base = getAlbumPath(conf, album)
elif playlist is not None and conf.usePlaylistFolder:
base = getPlaylistPath(conf, playlist)
else:
base = conf.downloadPath + '/'
if conf.addTypeFolder:
base = base + 'Video/'
# hyphen
hyphen = ' - ' if conf.addHyphen else ' '
# get number
number = ''
if conf.useTrackNumber:
number = __getIndexStr__(video.trackNumber) + hyphen
# get artist
artist = ''
if conf.artistBeforeTitle:
artist = aigpy.path.replaceLimitChar(getArtistsName(video.artists), '-') + hyphen
# get explicit
explicit = "(Explicit)" if conf.addExplicitTag and video.explicit else ''
# title
title = aigpy.path.replaceLimitChar(video.title, '-')
# extension
extension = ".mp4"
return base + number + artist.strip() + title + explicit + extension
def convertToM4a(filepath, codec):
if 'ac4' in codec or 'mha1' in codec:
return filepath
if '.mp4' not in filepath:
return filepath
newpath = filepath.replace('.mp4', '.m4a')
aigpy.path.remove(newpath)
os.rename(filepath, newpath)
return newpath
def setMetaData(track, album, filepath, contributors, lyrics):
obj = aigpy.tag.TagTool(filepath)
obj.album = track.album.title
obj.title = track.title
if not aigpy.string.isNull(track.version):
obj.title += ' (' + track.version + ')'
obj.artist = list(map(lambda artist: artist.name, track.artists)) # __getArtists__(track.artists)
obj.copyright = track.copyRight
obj.tracknumber = track.trackNumber
obj.discnumber = track.volumeNumber
obj.composer = __parseContributors__('Composer', contributors)
obj.isrc = track.isrc
obj.albumartist = list(map(lambda artist: artist.name, album.artists)) # __getArtists__(album.artists)
obj.date = album.releaseDate
obj.totaldisc = album.numberOfVolumes
obj.lyrics = lyrics
if obj.totaldisc <= 1:
obj.totaltrack = album.numberOfTracks
coverpath = API.getCoverUrl(album.cover, "1280", "1280")
obj.save(coverpath)
return
def isNeedDownload(path, url):
curSize = aigpy.file.getSize(path)
if curSize <= 0:
return True
netSize = aigpy.net.getSize(url)
if curSize >= netSize:
return False
return True
def encrypted(stream, srcPath, descPath):
if aigpy.string.isNull(stream.encryptionKey):
os.replace(srcPath, descPath)
else:
key, nonce = decrypt_security_token(stream.encryptionKey)
decrypt_file(srcPath, descPath, key, nonce)
os.remove(srcPath)
def getAudioQualityList():
return map(lambda quality: quality.name, tidal_dl.enums.AudioQuality)
def getCurAudioQuality():
return CONF.audioQuality.name
def setCurAudioQuality(text):
if CONF.audioQuality.name == text:
return
for item in tidal_dl.enums.AudioQuality:
if item.name == text:
CONF.audioQuality = item
break
Settings.save(CONF)
def getVideoQualityList():
return map(lambda quality: quality.name, tidal_dl.enums.VideoQuality)
def getCurVideoQuality():
return CONF.videoQuality.name
def setCurVideoQuality(text):
if CONF.videoQuality.name == text:
return
for item in tidal_dl.enums.VideoQuality:
if item.name == text:
CONF.videoQuality = item
break
Settings.save(CONF)
def skip(path, url):
if CONF.checkExist and isNeedDownload(path, url) is False:
return True
return False
def convert(srcPath, stream):
if CONF.onlyM4a:
return convertToM4a(srcPath, stream.codec)
return srcPath
def downloadTrack(track: Track, album=None, playlist=None, userProgress=None, partSize=1048576):
try:
msg, stream = API.getStreamUrl(track.id, CONF.audioQuality)
if not aigpy.string.isNull(msg) or stream is None:
Printf.err(track.title + "." + msg)
return False, msg
if CONF.showTrackInfo:
Printf.track(track, stream)
if userProgress is not None:
userProgress.updateStream(stream)
path = getTrackPath(CONF, track, stream, album, playlist)
# check exist
if skip(path, stream.url):
Printf.success(aigpy.path.getFileName(path) + " (skip:already exists!)")
return True, ""
# download
logging.info("[DL Track] name=" + aigpy.path.getFileName(path) + "\nurl=" + stream.url)
tool = aigpy.download.DownloadTool(path + '.part', [stream.url])
tool.setUserProgress(userProgress)
tool.setPartSize(partSize)
check, err = tool.start(CONF.showProgress)
if not check:
Printf.err("Download failed! " + aigpy.path.getFileName(path) + ' (' + str(err) + ')')
return False, str(err)
# encrypted -> decrypt and remove encrypted file
encrypted(stream, path + '.part', path)
# convert
path = convert(path, stream)
# contributors
msg, contributors = API.getTrackContributors(track.id)
msg, tidalLyrics = API.getLyrics(track.id)
lyrics = '' if tidalLyrics is None else tidalLyrics.subtitles
if CONF.lyricFile:
if tidalLyrics is None:
Printf.info(f'Failed to get lyrics from tidal!"{track.title}"')
else:
lrcPath = path.rsplit(".", 1)[0] + '.lrc'
aigpy.fileHelper.write(lrcPath, tidalLyrics.subtitles, 'w')
setMetaData(track, album, path, contributors, lyrics)
Printf.success(aigpy.path.getFileName(path))
return True, ""
except Exception as e:
Printf.err("Download failed! " + track.title + ' (' + str(e) + ')')
return False, str(e)
def downloadVideo(video: Video, album=None, playlist=None):
msg, stream = API.getVideoStreamUrl(video.id, CONF.videoQuality)
Printf.video(video, stream)
if not aigpy.string.isNull(msg):
Printf.err(video.title + "." + msg)
return False, msg
path = getVideoPath(CONF, video, album, playlist)
logging.info("[DL Video] name=" + aigpy.path.getFileName(path) + "\nurl=" + stream.m3u8Url)
m3u8content = requests.get(stream.m3u8Url).content
if m3u8content is None:
Printf.err(video.title + ' get m3u8 content failed.')
return False, "Get m3u8 content failed"
urls = aigpy.m3u8.parseTsUrls(m3u8content)
if len(urls) <= 0:
Printf.err(video.title + ' parse ts urls failed.')
logging.info("[DL Video] title=" + video.title + "\m3u8Content=" + str(m3u8content))
return False, 'Parse ts urls failed.'
check, msg = aigpy.m3u8.downloadByTsUrls(urls, path)
# check, msg = aigpy.m3u8.download(stream.m3u8Url, path)
if check is True:
Printf.success(aigpy.path.getFileName(path))
return True, ''
else:
Printf.err("\nDownload failed!" + msg + '(' + aigpy.path.getFileName(path) + ')')
return False, msg
def displayTime(seconds, granularity=2):
if seconds <= 0:
return "unknown"
result = []
intervals = (
('weeks', 604800),
('days', 86400),
('hours', 3600),
('minutes', 60),
('seconds', 1),
)
for name, count in intervals:
value = seconds // count
if value:
seconds -= value * count
if value == 1:
name = name.rstrip('s')
result.append("{} {}".format(value, name))
return ', '.join(result[:granularity])
def loginByConfig():
if aigpy.stringHelper.isNull(TOKEN.accessToken):
return False
msg, check = API.verifyAccessToken(TOKEN.accessToken)
if check:
Printf.info(LANG.MSG_VALID_ACCESSTOKEN.format(displayTime(int(TOKEN.expiresAfter - time.time()))))
API.key.countryCode = TOKEN.countryCode
API.key.userId = TOKEN.userid
API.key.accessToken = TOKEN.accessToken
return True
Printf.info(LANG.MSG_INVAILD_ACCESSTOKEN)
msg, check = API.refreshAccessToken(TOKEN.refreshToken)
if check:
Printf.success(LANG.MSG_VALID_ACCESSTOKEN.format(displayTime(int(API.key.expiresIn))))
TOKEN.userid = API.key.userId
TOKEN.countryCode = API.key.countryCode
TOKEN.accessToken = API.key.accessToken
TOKEN.expiresAfter = time.time() + int(API.key.expiresIn)
TokenSettings.save(TOKEN)
return True
else:
tmp = TokenSettings() # clears saved tokens
TokenSettings.save(tmp)
return False
def loginByWeb():
start = time.time()
elapsed = 0
while elapsed < API.key.authCheckTimeout:
elapsed = time.time() - start
msg, check = API.checkAuthStatus()
if not check:
if msg == "pending":
time.sleep(API.key.authCheckInterval + 1)
continue
return False
if check:
Printf.success(LANG.MSG_VALID_ACCESSTOKEN.format(displayTime(int(API.key.expiresIn))))
TOKEN.userid = API.key.userId
TOKEN.countryCode = API.key.countryCode
TOKEN.accessToken = API.key.accessToken
TOKEN.refreshToken = API.key.refreshToken
TOKEN.expiresAfter = time.time() + int(API.key.expiresIn)
TokenSettings.save(TOKEN)
return True
Printf.err(LANG.AUTH_TIMEOUT)
return False
def getArtistsNames(artists): # : list[tidal_dl.model.Artist]
ret = []
for item in artists:
ret.append(item.name)
return ','.join(ret)
def getDurationString(seconds: int):
m, s = divmod(seconds, 60)
h, m = divmod(m, 60)
return "%02d:%02d:%02d" % (h, m, s)
def getBasePath(model):
if isinstance(model, tidal_dl.model.Album):
return getAlbumPath(CONF, model)
if isinstance(model, tidal_dl.model.Playlist):
return getPlaylistPath(CONF, model)
if isinstance(model, tidal_dl.model.Track):
return getAlbumPath(CONF, model.album)
if isinstance(model, tidal_dl.model.Video):
filePath = getVideoPath(CONF, model, model.album, model.playlist)
return aigpy.pathHelper.getDirName(filePath)
return './'
def getFilePath(model, stream=None):
if isinstance(model, tidal_dl.model.Track):
return getTrackPath(CONF, model, stream, model.album, model.playlist)
if isinstance(model, tidal_dl.model.Video):
return getVideoPath(CONF, model, model.album, model.playlist)
return './'
-34
View File
@@ -1,34 +0,0 @@
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
"""
@File : __init__.py
@Date : 2021/05/08
@Author : Yaronzz
@Version : 1.0
@Contact : yaronhuang@foxmail.com
@Desc :
"""
import sys
from PyQt5.QtWidgets import QApplication
from tidal_gui import theme
from tidal_gui.viewModel.mainModel import MainModel
def main():
qss = theme.getThemeQssContent()
app = QApplication(sys.argv)
app.setStyleSheet(qss)
mainView = MainModel()
mainView.show()
app.exec_()
mainView.uninit()
sys.exit()
if __name__ == '__main__':
main()
-10
View File
@@ -1,10 +0,0 @@
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
"""
@File : __init__.py
@Date : 2021/05/08
@Author : Yaronzz
@Version : 1.0
@Contact : yaronhuang@foxmail.com
@Desc :
"""
-19
View File
@@ -1,19 +0,0 @@
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
"""
@File : checkBox.py
@Date : 2021/05/08
@Author : Yaronzz
@Version : 1.0
@Contact : yaronhuang@foxmail.com
@Desc :
"""
from PyQt5.QtWidgets import QCheckBox
class CheckBox(QCheckBox):
def __init__(self, text: str = "", checked: bool = False):
super(CheckBox, self).__init__()
self.setChecked(checked)
self.setText(text)
-27
View File
@@ -1,27 +0,0 @@
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
"""
@File : comboBox.py
@Date : 2021/8/17
@Author : Yaronzz
@Version : 1.0
@Contact : yaronhuang@foxmail.com
@Desc :
"""
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QComboBox, QListView
class ComboBox(QComboBox):
def __init__(self, items: list, width: int = 200):
super(ComboBox, self).__init__()
self.setItems(items)
self.setFixedWidth(width)
self.setView(QListView())
# remove shadow
self.view().window().setWindowFlags(Qt.Popup | Qt.FramelessWindowHint | Qt.NoDropShadowWindowHint)
self.view().window().setAttribute(Qt.WA_TranslucentBackground)
def setItems(self, items):
for item in items:
self.addItem(str(item))
@@ -1,156 +0,0 @@
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
"""
@File : framelessWidget.py
@Date : 2021/05/08
@Author : Yaronzz
@Version : 1.0
@Contact : yaronhuang@foxmail.com
@Desc :
"""
from PyQt5.QtCore import Qt, QPoint
from PyQt5.QtGui import QMouseEvent
from PyQt5.QtWidgets import QWidget, QGridLayout, QHBoxLayout
from tidal_gui.control.pushButton import PushButton
from tidal_gui.style import ButtonStyle
class FramelessWidget(QWidget):
def __init__(self):
super(FramelessWidget, self).__init__()
self.setWindowFlags(Qt.FramelessWindowHint)
self.BorderWidth = 5
self.borderWidget = QWidget()
self.borderWidget.setObjectName("widgetMain")
self.borderWidget.setStyleSheet("QWidget#widgetMain{border: 1px solid #000000;};")
self.contentGrid = QGridLayout()
self.contentGrid.setContentsMargins(1, 1, 1, 1)
self.windowBtnGrid = self.__createWindowsButtonLayout__()
self.enableMove = True
self.validMoveWidget = None
self.clickPos = None
self.grid = QGridLayout()
self.grid.setSpacing(0)
self.grid.setContentsMargins(0, 0, 0, 0)
self.grid.addWidget(self.borderWidget, 0, 0)
self.grid.addLayout(self.contentGrid, 0, 0)
self.setLayout(self.grid)
def __showMaxWindows__(self):
if self.windowState() == Qt.WindowMaximized:
self.showNormal()
else:
self.showMaximized()
def __createWindowsButtonLayout__(self):
self.closeBtn = PushButton('', ButtonStyle.CloseWindow)
self.maxBtn = PushButton('', ButtonStyle.MaxWindow)
self.minBtn = PushButton('', ButtonStyle.MinWindow)
self.closeBtn.clicked.connect(self.close)
self.minBtn.clicked.connect(self.showMinimized)
self.maxBtn.clicked.connect(self.__showMaxWindows__)
layout = QHBoxLayout()
layout.setSpacing(0)
layout.setContentsMargins(1, 1, 1, 1)
layout.addWidget(self.minBtn)
layout.addWidget(self.maxBtn)
layout.addWidget(self.closeBtn)
return layout
def __clickInValidMoveWidget__(self, x=-1, y=-1) -> bool:
if self.validMoveWidget is None:
return False
if self.clickPos is None:
return False
if x == -1 and y == -1:
x = self.clickPos.x()
y = self.clickPos.y()
pos = self.validMoveWidget.pos()
if x < pos.x() or x > pos.x() + self.validMoveWidget.width():
return False
if y < pos.y() or y > pos.y() + self.validMoveWidget.height():
return False
return True
def nativeEvent(self, eventType, message):
retVal, result = super(FramelessWidget, self).nativeEvent(eventType, message)
# if eventType == "windows_generic_MSG":
# msg = ctypes.wintypes.MSG.from_address(message.__int__())
# if msg.message != win32con.WM_NCHITTEST:
# return retVal, result
# # 获取鼠标移动经过时的坐标
# x = win32api.LOWORD(msg.lParam) - self.frameGeometry().x()
# y = win32api.HIWORD(msg.lParam) - self.frameGeometry().y()
# w, h = self.width(), self.height()
# lx = x < self.BorderWidth
# rx = x > w - self.BorderWidth
# ty = y < self.BorderWidth
# by = y > h - self.BorderWidth
# if (lx and ty):# 左上角
# return True, win32con.HTTOPLEFT
# elif (rx and by):# 右下角
# return True, win32con.HTBOTTOMRIGHT
# elif (rx and ty):# 右上角
# return True, win32con.HTTOPRIGHT
# elif (lx and by):# 左下角
# return True, win32con.HTBOTTOMLEFT
# elif ty:# 上
# return True, win32con.HTTOP
# elif by:# 下
# return True, win32con.HTBOTTOM
# elif lx:# 左
# return True, win32con.HTLEFT
# elif rx:# 右
# return True, win32con.HTRIGHT
return retVal, result
def mousePressEvent(self, e: QMouseEvent):
if e.button() == Qt.LeftButton:
self.clickPos = e.pos()
def mouseReleaseEvent(self, e: QMouseEvent):
if e.button() == Qt.LeftButton:
self.clickPos = QPoint(-1, -1)
def mouseMoveEvent(self, e: QMouseEvent):
if not self.enableMove:
return
if Qt.LeftButton & e.buttons():
if self.__clickInValidMoveWidget__() and self.clickPos:
self.move(e.pos() + self.pos() - self.clickPos)
def mouseDoubleClickEvent(self, e: QMouseEvent):
if self.maxBtn.isHidden():
return
if Qt.LeftButton & e.buttons():
if self.__clickInValidMoveWidget__(e.x(), e.y()):
self.__showMaxWindows__()
def getGrid(self):
return self.contentGrid
def disableMove(self):
self.enableMove = False
def setValidMoveWidget(self, widget):
self.validMoveWidget = widget
def setWindowButton(self, showClose=True, showMin=True, showMax=True):
if not showMax:
self.maxBtn.hide()
if not showMin:
self.minBtn.hide()
if not showClose:
self.closeBtn.hide()
self.grid.addLayout(self.windowBtnGrid, 0, 0, Qt.AlignTop | Qt.AlignRight)
-20
View File
@@ -1,20 +0,0 @@
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
"""
@File : label.py
@Date : 2021/05/08
@Author : Yaronzz
@Version : 1.0
@Contact : yaronhuang@foxmail.com
@Desc :
"""
from PyQt5.QtWidgets import QLabel
from tidal_gui.style import LabelStyle
class Label(QLabel):
def __init__(self, text: str = "", style: LabelStyle = LabelStyle.Default):
super(Label, self).__init__()
self.setText(text)
self.setObjectName(style.name + "Label")
-25
View File
@@ -1,25 +0,0 @@
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
"""
@File : layout.py
@Date : 2021/8/13
@Author : Yaronzz
@Version : 1.0
@Contact : yaronhuang@foxmail.com
@Desc :
"""
from PyQt5.QtWidgets import QHBoxLayout, QVBoxLayout
def createHBoxLayout(widgets):
layout = QHBoxLayout()
for item in widgets:
layout.addWidget(item)
return layout
def createVBoxLayout(widgets):
layout = QVBoxLayout()
for item in widgets:
layout.addWidget(item)
return layout
-20
View File
@@ -1,20 +0,0 @@
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
"""
@File : line.py
@Date : 2021/8/17
@Author : Yaronzz
@Version : 1.0
@Contact : yaronhuang@foxmail.com
@Desc :
"""
from PyQt5.QtWidgets import QFrame
class Line(QFrame):
def __init__(self, shape: str = 'V'):
super(Line, self).__init__()
self.setFrameShape(QFrame.VLine if shape == 'V' else QFrame.HLine)
self.setFrameShadow(QFrame.Sunken)
self.setObjectName('VLineQFrame' if shape == 'V' else 'HLineQFrame')
-23
View File
@@ -1,23 +0,0 @@
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
"""
@File : lineEdit.py
@Date : 2021/05/08
@Author : Yaronzz
@Version : 1.0
@Contact : yaronhuang@foxmail.com
@Desc :
"""
from PyQt5.QtGui import QIcon
from PyQt5.QtWidgets import QLineEdit, QAction
class LineEdit(QLineEdit):
def __init__(self, placeholderText: str = "", iconUrl: str = ''):
super(LineEdit, self).__init__()
self.setPlaceholderText(placeholderText)
if iconUrl != '':
action = QAction(self)
action.setIcon(QIcon(iconUrl))
self.addAction(action, QLineEdit.LeadingPosition)
@@ -1,32 +0,0 @@
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
"""
@File : listWidget.py
@Date : 2021/8/17
@Author : Yaronzz
@Version : 1.0
@Contact : yaronhuang@foxmail.com
@Desc :
"""
from PyQt5.QtGui import QIcon
from PyQt5.QtWidgets import QListWidget, QListWidgetItem, QWidget
from tidal_gui.style import ListWidgetStyle
class ListWidget(QListWidget):
def __init__(self, style: ListWidgetStyle = ListWidgetStyle.Default):
super(ListWidget, self).__init__()
self.setObjectName(style.name + "ListWidget")
def addIConTextItem(self, iconUrl: str, text: str):
self.addItem(QListWidgetItem(QIcon(iconUrl), text))
def addWidgetItem(self, widget: QWidget):
item = QListWidgetItem(self)
# item.setSizeHint(QSize(widget.width(), widget.height()))
self.addItem(item)
self.setItemWidget(item, widget)
def setAdjustMode(self):
self.setResizeMode(QListWidget.Adjust)
@@ -1,31 +0,0 @@
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
"""
@File : pushButton.py
@Date : 2021/05/08
@Author : Yaronzz
@Version : 1.0
@Contact : yaronhuang@foxmail.com
@Desc :
"""
from PyQt5.QtGui import QIcon
from PyQt5.QtWidgets import QPushButton
from tidal_gui.style import ButtonStyle
class PushButton(QPushButton):
def __init__(self,
text: str = '',
style: ButtonStyle = ButtonStyle.Default,
width=0,
iconUrl=''):
super(PushButton, self).__init__()
self.setText(text)
self.setObjectName(style.name + "PushButton")
if width > 0:
self.setFixedWidth(width)
if iconUrl != '':
self.setIcon(QIcon(iconUrl))
@@ -1,42 +0,0 @@
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
"""
@File : scrollWidget.py
@Date : 2021/10/08
@Author : Yaronzz
@Version : 1.0
@Contact : yaronhuang@foxmail.com
@Desc :
"""
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QWidget, QScrollArea, QVBoxLayout
from PyQt5.QtGui import QResizeEvent
class ScrollWidget(QScrollArea):
def __init__(self):
super(ScrollWidget, self).__init__()
self._numWidget = 0
self._layout = QVBoxLayout()
self._layout.addStretch(1)
self._mainW = QWidget()
self._mainW.setLayout(self._layout)
self.setWidget(self._mainW)
self.setWidgetResizable(True)
def addWidgetItem(self, widget: QWidget):
self._layout.insertWidget(self._numWidget, widget)
self._numWidget += 1
def delWidgetItem(self, widget: QWidget):
self._layout.removeWidget(widget)
self._numWidget -= 1
def resizeEvent(self, e: QResizeEvent):
super().resizeEvent(e)
width = e.size().width()
if width > 0:
self._mainW.setMaximumWidth(width)
-48
View File
@@ -1,48 +0,0 @@
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
"""
@File : tableView.py
@Date : 2021/9/10
@Author : Yaronzz
@Version : 1.0
@Contact : yaronhuang@foxmail.com
@Desc :
"""
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QStandardItemModel, QStandardItem
from PyQt5.QtWidgets import QTableView, QAbstractItemView
class TableView(QTableView):
def __init__(self, columnNames: list, rowCount: int = 20):
super(TableView, self).__init__()
self._model = QStandardItemModel()
self._model.setColumnCount(len(columnNames))
self._model.setRowCount(rowCount)
for index, name in enumerate(columnNames):
self._model.setHeaderData(index, Qt.Horizontal, name)
self.setModel(self._model)
# self.setHorizontalHeaderItem(index, QTableWidgetItem(name))
# for index in range(0, rowCount):
# self.setRowHeight(index, 50)
self.setShowGrid(False)
self.verticalHeader().setVisible(False)
self.setSelectionBehavior(QAbstractItemView.SelectRows)
self.setSelectionMode(QAbstractItemView.SingleSelection)
self.horizontalHeader().setStretchLastSection(True)
self.setEditTriggers(QAbstractItemView.NoEditTriggers)
self.setFocusPolicy(Qt.NoFocus)
def addItem(self, rowIdx: int, colIdx: int, text: str):
item = QStandardItem(text)
item.setTextAlignment(Qt.AlignCenter)
self._model.setItem(rowIdx, colIdx, item)
#
# def addWidgetItem(self, rowIdx: int, colIdx: int, widget):
# self.setCellWidget(rowIdx, colIdx, widget)
@@ -1,95 +0,0 @@
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
"""
@File : tableWidget.py
@Date : 2021/8/18
@Author : Yaronzz
@Version : 1.0
@Contact : yaronhuang@foxmail.com
@Desc :
"""
import threading
from PyQt5.QtCore import Qt, QUrl
from PyQt5.QtGui import QPixmap
from PyQt5.QtNetwork import QNetworkAccessManager, QNetworkRequest
from PyQt5.QtWidgets import QTableWidget, QTableWidgetItem, QAbstractItemView
from tidal_gui.control.label import Label, LabelStyle
class TableWidget(QTableWidget):
def __init__(self, columnNames: list, rowCount: int = 20):
super(TableWidget, self).__init__()
self.setColumnCount(len(columnNames))
self.setRowCount(rowCount)
self._lock = threading.Lock()
self._netManager = QNetworkAccessManager()
self.columnAligns = []
for index, name in enumerate(columnNames):
item = QTableWidgetItem(name)
align = Qt.AlignLeft | Qt.AlignVCenter
if name == '#' or name == ' ':
align = Qt.AlignCenter
self.columnAligns.append(align)
item.setTextAlignment(align)
self.setHorizontalHeaderItem(index, item)
for index in range(0, rowCount):
self.setRowHeight(index, 50)
self.setShowGrid(False)
self.verticalHeader().setVisible(False)
self.setSelectionBehavior(QAbstractItemView.SelectRows)
self.setSelectionMode(QAbstractItemView.SingleSelection)
self.horizontalHeader().setStretchLastSection(True)
self.setEditTriggers(QAbstractItemView.NoEditTriggers)
self.setFocusPolicy(Qt.NoFocus)
def changeRowCount(self, rows: int):
if rows != self.rowCount():
self.setRowCount(rows)
for index in range(0, rows):
self.setRowHeight(index, 50)
def addItem(self, rowIdx: int, colIdx: int, text):
if isinstance(text, str):
item = QTableWidgetItem(text)
item.setTextAlignment(self.columnAligns[colIdx])
self.setItem(rowIdx, colIdx, item)
elif isinstance(text, QUrl):
self.__addPicItem__(rowIdx, colIdx, text)
def __picDownload__(self, rowIdx, colIdx):
reply = self.sender()
data = reply.readAll()
if data.size() <= 0:
return
pic = QPixmap()
pic.loadFromData(data)
self._lock.acquire()
self.cellWidget(rowIdx, colIdx).setPixmap(pic.scaled(32, 32))
self._lock.release()
reply.deleteLater()
def __addPicItem__(self, rowIdx: int, colIdx: int, url: QUrl):
icon = Label('', LabelStyle.Icon)
icon.setAlignment(Qt.AlignCenter)
self.setCellWidget(rowIdx, colIdx, icon)
reply = self._netManager.get(QNetworkRequest(url))
reply.finished.connect(lambda: self.__picDownload__(rowIdx, colIdx))
def addWidgetItem(self, rowIdx: int, colIdx: int, widget):
self.setCellWidget(rowIdx, colIdx, widget)
-41
View File
@@ -1,41 +0,0 @@
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
"""
@File : downloader.py
@Date : 2021/09/15
@Author : Yaronzz
@Version : 1.0
@Contact : yaronhuang@foxmail.com
@Desc :
"""
import time
from PyQt5.Qt import QThread
from tidal_gui.viewModel.taskModel import TaskModel
class DownloaderImp(QThread):
def __init__(self):
super(DownloaderImp, self).__init__()
self._taskModel = None
def run(self):
print('DownloadImp start...')
while not self.isInterruptionRequested():
if self._taskModel is not None:
item = self._taskModel.getWaitDownloadItem()
if item is not None:
item.download()
time.sleep(1)
print('DownloadImp stop...')
def setTaskModel(self, model: TaskModel):
self._taskModel = model
def stop(self):
self.requestInterruption()
self.wait()
downloadImp = DownloaderImp()
@@ -1,4 +0,0 @@
File diff suppressed because it is too large Load Diff
-37
View File
@@ -1,37 +0,0 @@
<RCC>
<qresource prefix="/">
<file>svg/buymeacoffee.svg</file>
<file>svg/check.svg</file>
<file>svg/down.svg</file>
<file>svg/downHover.svg</file>
<file>svg/github.svg</file>
<file>svg/left.svg</file>
<file>svg/paypal.svg</file>
<file>svg/right.svg</file>
<file>svg/search.svg</file>
<file>svg/search2.svg</file>
<file>svg/upHover.svg</file>
<file>svg/V.svg</file>
<file>svg/leftTab/download.svg</file>
<file>svg/leftTab/downloadHover.svg</file>
<file>svg/leftTab/info.svg</file>
<file>svg/leftTab/search.svg</file>
<file>svg/leftTab/settings.svg</file>
<file>svg/taskItem/cancel.svg</file>
<file>svg/taskItem/delete.svg</file>
<file>svg/taskItem/expand.svg</file>
<file>svg/taskItem/open.svg</file>
<file>svg/taskItem/retry.svg</file>
<file>svg/taskTab/complete.svg</file>
<file>svg/taskTab/download.svg</file>
<file>svg/taskTab/error.svg</file>
<file>svg/windows/close.svg</file>
<file>svg/windows/closeHover.svg</file>
<file>svg/windows/max.svg</file>
<file>svg/windows/min.svg</file>
</qresource>
</RCC>
-12
View File
@@ -1,12 +0,0 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg t="1629184159878" class="icon" viewBox="0 0 1092 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"
p-id="23123" width="136.5" height="128">
<defs>
<style type="text/css"></style>
</defs>
<path d="M538.624 1017.344L0.580267 405.162667 212.445867 0h652.356266L1076.565333 405.162667z" fill="#46F256"
p-id="23124"></path>
<path d="M538.624 748.6464l-317.44-351.607467 67.618133-61.098666 249.787734 276.718933 249.821866-276.6848 67.618134 61.064533z"
fill="#000000" p-id="23125"></path>
</svg>

Before

Width:  |  Height:  |  Size: 694 B

@@ -1,4 +0,0 @@
@@ -1,5 +0,0 @@
<svg t="1629268466285" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"
p-id="18338" width="128" height="128">
<path d="M294.314667 0L242.432 119.424H165.418667v107.648h29.653333L225.152 418.133333H178.005333l62.293334 351.146667 40.021333-0.426667 40.192 255.146667h380.501333l2.645334-17.066667 37.546666-238.08 37.888 0.426667 62.293334-351.189333h-45.056l30.08-191.018667h32.256V119.466667h-81.834667L724.906667 0H294.314667z m22.528 34.346667h385.834666l32.896 75.946666H283.818667l33.024-75.946666z m-117.333334 119.338666H824.32v39.253334H199.509333v-39.253334z m19.328 298.581334h581.76l-50.176 282.453333-241.024-2.56-240.469333 2.56-50.090667-282.453333z"
p-id="18339" fill="#ffffff"></path>
</svg>

Before

Width:  |  Height:  |  Size: 764 B

@@ -1,5 +0,0 @@
<svg fill="#ffffff" t="1606188206696" class="icon" viewBox="0 0 1024 1024" version="1.1"
xmlns="http://www.w3.org/2000/svg" p-id="2750" width="16" height="16">
<path d="M376.123077 836.923077L51.2 510.030769c-11.815385-11.815385-11.815385-31.507692 0-43.323077l43.323077-43.323077c11.815385-11.815385 31.507692-11.815385 43.323077 0L382.030769 669.538462c7.876923 7.876923 21.661538 7.876923 29.538462 0L890.092308 187.076923c11.815385-11.815385 31.507692-11.815385 43.323077 0l43.323077 43.323077c11.815385 11.815385 11.815385 31.507692 0 43.323077L419.446154 836.923077c-11.815385 13.784615-31.507692 13.784615-43.323077 0z"
p-id="2751"></path>
</svg>

Before

Width:  |  Height:  |  Size: 672 B

@@ -1,10 +0,0 @@
<!-- <svg t="1605685211717" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1511"
width="150" height="150">
<path d="M316.16 366.506667L512 561.92l195.84-195.413333L768 426.666667l-256 256-256-256z" fill="#cbcbcb"
p-id="1512"></path>
</svg> -->
<svg t="1629251448482" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1609"
width="128" height="128">
<path d="M179.758545 374.008242a31.030303 31.030303 0 0 1 43.876849 0L512 662.372848l288.364606-288.364606a31.030303 31.030303 0 0 1 43.876849 43.876849l-310.303031 310.30303a31.030303 31.030303 0 0 1-43.876848 0l-310.303031-310.30303a31.030303 31.030303 0 0 1 0-43.876849z"
p-id="1610" fill="#cbcbcb"></path>
</svg>

Before

Width:  |  Height:  |  Size: 790 B

@@ -1,10 +0,0 @@
<!-- <svg t="1605685211717" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1511"
width="150" height="150">
<path d="M316.16 366.506667L512 561.92l195.84-195.413333L768 426.666667l-256 256-256-256z" fill="#326cf3"
p-id="1512"></path>
</svg> -->
<svg t="1629251448482" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1609"
width="128" height="128">
<path d="M179.758545 374.008242a31.030303 31.030303 0 0 1 43.876849 0L512 662.372848l288.364606-288.364606a31.030303 31.030303 0 0 1 43.876849 43.876849l-310.303031 310.30303a31.030303 31.030303 0 0 1-43.876848 0l-310.303031-310.30303a31.030303 31.030303 0 0 1 0-43.876849z"
p-id="1610" fill="#326cf3"></path>
</svg>

Before

Width:  |  Height:  |  Size: 790 B

@@ -1,5 +0,0 @@
<svg t="1629268481541" class="icon" viewBox="0 0 1025 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"
p-id="19187" width="128" height="128">
<path d="M512.032 831.904c-19.168 0-38.304-9.92-58.144-29.76-7.808-7.808-7.808-20.48 0-28.288s20.48-7.808 28.288 0C494.368 786.08 504.16 792 512.032 792s17.664-5.92 29.856-18.144c7.808-7.808 20.48-7.808 28.288 0s7.808 20.48 0 28.288c-19.84 19.84-38.976 29.76-58.144 29.76z m-512-306.4c0 49.888 4.256 95.136 12.8 135.68s20.544 75.744 36 105.536 35.008 55.904 58.656 78.336 49.344 40.928 77.056 55.456c27.744 14.528 59.456 26.304 95.2 35.264S351.84 951.04 388.8 954.624 466.496 960 510.944 960c44.448 0 85.248-1.792 122.4-5.376s73.6-9.856 109.344-18.848c35.744-8.96 67.552-20.736 95.456-35.264s53.792-33.024 77.6-55.456c23.808-22.432 43.456-48.544 58.944-78.336s27.552-64.96 36.256-105.536c8.704-40.576 13.056-85.792 13.056-135.68 0-89.376-27.744-166.368-83.2-230.976 3.2-8.608 5.952-18.496 8.256-29.6s4.544-26.816 6.656-47.104c2.144-20.288 1.344-43.712-2.4-70.272S942.56 93.888 932.256 66.24l-8-1.632c-5.344-1.088-14.048-0.704-26.144 1.088s-26.208 5.024-42.4 9.696-37.056 13.92-62.656 27.744-52.608 31.328-81.056 52.512c-48.352-14.72-115.008-30.112-200-30.112s-151.808 15.392-200.544 30.112c-28.448-21.184-55.552-38.592-81.344-52.224s-46.4-22.976-61.856-28c-15.456-5.024-29.792-8.256-42.944-9.696s-21.6-1.888-25.344-1.344c-3.744 0.544-6.496 1.152-8.256 1.888-10.304 27.648-17.408 54.752-21.344 81.312s-4.8 49.888-2.656 69.984c2.144 20.096 4.448 35.904 6.944 47.392S80 286.304 83.2 294.56C27.744 358.816 0 435.808 0 525.536z m136.544 113.888c0-58.016 21.344-110.624 64-157.856 12.8-14.4 27.648-25.312 44.544-32.704s36.096-11.616 57.6-12.608 42.048-0.8 61.6 0.608 43.744 3.296 72.544 5.696 53.696 3.616 74.656 3.616c20.96 0 45.856-1.184 74.656-3.616s52.992-4.288 72.544-5.696c19.552-1.408 40.096-1.6 61.6-0.608s40.8 5.216 57.856 12.608c17.056 7.392 32 18.304 44.8 32.704 42.656 47.232 64 99.84 64 157.856 0 34.016-3.552 64.32-10.656 90.944s-16.096 48.928-26.944 66.912c-10.848 18.016-26.048 33.216-45.6 45.632s-38.496 22.016-56.8 28.8c-18.304 6.784-41.952 12.096-70.944 15.904s-54.944 6.112-77.856 6.912c-22.944 0.8-51.808 1.216-86.656 1.216s-63.648-0.416-86.4-1.216c-22.752-0.8-48.608-3.104-77.6-6.912s-52.608-9.12-70.944-15.904c-18.304-6.816-37.248-16.416-56.8-28.8s-34.752-27.616-45.6-45.632c-10.848-18.016-19.84-40.32-26.944-66.912s-10.656-56.928-10.656-90.944zM256.032 608c0-53.024 28.64-96 64-96s64 42.976 64 96-28.64 96-64 96-64-42.976-64-96z m384 0c0-53.024 28.64-96 64-96s64 42.976 64 96-28.64 96-64 96-64-42.976-64-96z"
p-id="19188"></path>
</svg>

Before

Width:  |  Height:  |  Size: 2.6 KiB

@@ -1,5 +0,0 @@
<svg t="1629270772080" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1817"
width="128" height="128">
<path d="M658.059636 187.826424a31.030303 31.030303 0 0 1 0 43.876849l-288.364606 288.364606 288.364606 288.364606a31.030303 31.030303 0 0 1-43.876848 43.876848l-310.30303-310.30303a31.030303 31.030303 0 0 1 0-43.876848l310.30303-310.303031a31.030303 31.030303 0 0 1 43.876848 0z"
p-id="1818"></path>
</svg>

Before

Width:  |  Height:  |  Size: 473 B

@@ -1,10 +0,0 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg t="1629182866847" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"
p-id="22424" width="128" height="128">
<defs>
<style type="text/css"></style>
</defs>
<path d="M938.666667 682.666667v170.666666a85.333333 85.333333 0 0 1-85.333334 85.333334H170.666667a85.333333 85.333333 0 0 1-85.333334-85.333334v-170.666666h85.333334v170.666666h682.666666v-170.666666h85.333334z m-384-145.664l140.501333-140.501334 60.330667 60.330667L512 700.330667l-243.498667-243.498667 60.330667-60.330667L469.333333 537.002667V85.333333h85.333334v451.669334z"
p-id="22425" fill="#ffffff"></path>
</svg>

Before

Width:  |  Height:  |  Size: 798 B

@@ -1,10 +0,0 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg t="1629183863957" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"
p-id="22616" width="128" height="128">
<defs>
<style type="text/css"></style>
</defs>
<path d="M938.666667 682.666667v170.666666a85.333333 85.333333 0 0 1-85.333334 85.333334H170.666667a85.333333 85.333333 0 0 1-85.333334-85.333334v-170.666666h85.333334v170.666666h682.666666v-170.666666h85.333334z m-384-145.664l140.501333-140.501334 60.330667 60.330667L512 700.330667l-243.498667-243.498667 60.330667-60.330667L469.333333 537.002667V85.333333h85.333334v451.669334z"
p-id="22617" fill="#e6e6e6"></path>
</svg>

Before

Width:  |  Height:  |  Size: 798 B

@@ -1,10 +0,0 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg t="1629182847499" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"
p-id="22040" width="128" height="128">
<defs>
<style type="text/css"></style>
</defs>
<path d="M512 981.333333C252.8 981.333333 42.666667 771.2 42.666667 512S252.8 42.666667 512 42.666667s469.333333 210.133333 469.333333 469.333333-210.133333 469.333333-469.333333 469.333333z m0-85.333333a384 384 0 1 0 0-768 384 384 0 0 0 0 768z m42.837333-298.752h42.624v85.333333h-170.666666v-85.333333h42.666666v-85.333333h-42.666666v-85.333334h128v170.666667z m-42.837333-213.333333a42.666667 42.666667 0 1 1 0-85.333334 42.666667 42.666667 0 0 1 0 85.333334z"
p-id="22041" fill="#ffffff"></path>
</svg>

Before

Width:  |  Height:  |  Size: 880 B

@@ -1,10 +0,0 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg t="1629181942259" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"
p-id="21410" width="128" height="128">
<defs>
<style type="text/css"></style>
</defs>
<path d="M696.32 635.989333l229.845333 229.845334-60.330666 60.330666-229.845334-229.845333a341.333333 341.333333 0 1 1 60.330667-60.330667zM426.666667 682.666667a256 256 0 1 0 0-512 256 256 0 0 0 0 512z"
p-id="21411" fill="#ffffff"></path>
</svg>

Before

Width:  |  Height:  |  Size: 621 B

@@ -1,10 +0,0 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg t="1629182857650" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"
p-id="22232" width="128" height="128">
<defs>
<style type="text/css"></style>
</defs>
<path d="M890.581333 797.013333l-94.592 94.592-121.088-33.706666-34.602666 14.250666L578.133333 981.333333h-133.76l-61.824-109.525333-34.56-14.506667-121.088 33.322667-94.549333-94.549333 33.706667-121.088-14.250667-34.602667L42.666667 578.133333v-133.76l109.568-61.824 14.506666-34.56-33.322666-121.088 94.506666-94.506666 121.088 33.749333 34.56-14.250667L445.696 42.666667h133.802667l61.824 109.568 34.56 14.506666 121.045333-33.322666 94.72 94.506666-33.792 121.130667 14.293333 34.56L981.333333 445.738667v133.802666l-109.525333 61.781334-14.506667 34.688 33.28 121.045333z m-123.392-126.805333l37.205334-88.832L896 529.664v-34.304l-91.605333-52.053333-36.650667-88.874667 28.245333-101.333333-24.277333-24.234667-101.674667 27.946667-88.746666-37.205334L529.621333 128h-34.304l-52.053333 91.605333-88.874667 36.650667-101.376-28.288-24.149333 24.149333 27.946667 101.674667-37.205334 88.746667L128 494.208v34.346667l91.52 52.138666 36.650667 88.874667-28.245334 101.376 24.192 24.192 101.674667-27.946667 88.746667 37.205334 51.626666 91.562666h34.346667l52.138667-91.52 88.874666-36.650666 101.376 28.245333 24.234667-24.234667-27.946667-101.589333zM512 682.666667a170.666667 170.666667 0 1 1 0-341.333334 170.666667 170.666667 0 0 1 0 341.333334z m0-85.333334a85.333333 85.333333 0 1 0 0-170.666666 85.333333 85.333333 0 0 0 0 170.666666z"
p-id="22233" fill="#ffffff"></path>
</svg>

Before

Width:  |  Height:  |  Size: 1.7 KiB

@@ -1,5 +0,0 @@
<svg t="1629268420515" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"
p-id="16731" width="128" height="128">
<path d="M350.8 591.8c-7 38.4-34.8 217.4-43 268-0.6 3.6-2 5-6 5H152.6c-15.2 0-26.2-13.2-24.2-27.8L245.6 93.2c3-19.2 20.2-33.8 40-33.8 304.6 0 330.2-7.4 408 22.8 120.2 46.6 131.2 159 88 280.6-43 125.2-145 179-280.2 180.6-86.8 1.4-139-14-150.6 48.4zM842.2 304c-3.6-2.6-5-3.6-6 2.6-4 22.8-10.2 45-17.6 67.2-79.8 227.6-301 207.8-409 207.8-12.2 0-20.2 6.6-21.8 18.8-45.2 280.8-54.2 339.4-54.2 339.4-2 14.2 7 25.8 21.2 25.8h127c17.2 0 31.4-12.6 34.8-29.8 1.4-10.8-2.2 12.2 28.8-182.6 9.2-44 28.6-39.4 58.6-39.4 142 0 252.8-57.6 285.8-224.6 13-69.6 9.2-142.8-47.6-185.2z"
p-id="16732" fill="#ffffff"></path>
</svg>

Before

Width:  |  Height:  |  Size: 774 B

@@ -1,5 +0,0 @@
<svg t="1629270798317" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1968"
width="128" height="128">
<path d="M365.940364 187.826424a31.030303 31.030303 0 0 1 43.876848 0l310.30303 310.303031a31.030303 31.030303 0 0 1 0 43.876848l-310.30303 310.30303a31.030303 31.030303 0 0 1-43.876848-43.876848l288.364606-288.364606-288.364606-288.364606a31.030303 31.030303 0 0 1 0-43.876849z"
p-id="1969"></path>
</svg>

Before

Width:  |  Height:  |  Size: 472 B

@@ -1,5 +0,0 @@
<svg t="1629269306451" class="icon" viewBox="0 0 1057 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"
p-id="15526" width="128" height="128">
<path d="M409.012356 811.965935c-108.676129 0.660645-213.058065-41.851871-289.924129-117.991225A401.242839 401.242839 0 0 1 0.006937 406.594065a401.242839 401.242839 0 0 1 119.015226-287.512775A408.344774 408.344774 0 0 1 409.012356 0.990968a408.344774 408.344774 0 0 1 289.924129 118.05729 401.275871 401.275871 0 0 1 119.08129 287.44671 401.275871 401.275871 0 0 1-119.048258 287.380645 408.344774 408.344774 0 0 1-289.891096 118.090322h-0.066065z m0-695.130838A290.617806 290.617806 0 0 0 201.63584 200.836129a285.563871 285.563871 0 0 0-84.69471 205.625806c0 162.221419 128.495484 289.626839 292.071226 289.626839 163.641806 0 292.13729-127.405419 292.13729-289.560774 0-162.221419-128.495484-289.725935-292.071225-289.725935h-0.066065zM957.942421 1005.799226l-173.980904-171.866839a55.130839 55.130839 0 0 1 0-80.235355c23.221677-22.990452 58.004645-22.990452 81.193291 0l173.947871 171.965936a55.130839 55.130839 0 0 1 0 80.235355 56.419097 56.419097 0 0 1-81.160258 0v-0.099097z"
p-id="15527" fill="#ffffff"></path>
</svg>

Before

Width:  |  Height:  |  Size: 1.2 KiB

@@ -1,5 +0,0 @@
<svg t="1629269306451" class="icon" viewBox="0 0 1057 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"
p-id="15526" width="128" height="128">
<path d="M409.012356 811.965935c-108.676129 0.660645-213.058065-41.851871-289.924129-117.991225A401.242839 401.242839 0 0 1 0.006937 406.594065a401.242839 401.242839 0 0 1 119.015226-287.512775A408.344774 408.344774 0 0 1 409.012356 0.990968a408.344774 408.344774 0 0 1 289.924129 118.05729 401.275871 401.275871 0 0 1 119.08129 287.44671 401.275871 401.275871 0 0 1-119.048258 287.380645 408.344774 408.344774 0 0 1-289.891096 118.090322h-0.066065z m0-695.130838A290.617806 290.617806 0 0 0 201.63584 200.836129a285.563871 285.563871 0 0 0-84.69471 205.625806c0 162.221419 128.495484 289.626839 292.071226 289.626839 163.641806 0 292.13729-127.405419 292.13729-289.560774 0-162.221419-128.495484-289.725935-292.071225-289.725935h-0.066065zM957.942421 1005.799226l-173.980904-171.866839a55.130839 55.130839 0 0 1 0-80.235355c23.221677-22.990452 58.004645-22.990452 81.193291 0l173.947871 171.965936a55.130839 55.130839 0 0 1 0 80.235355 56.419097 56.419097 0 0 1-81.160258 0v-0.099097z"
p-id="15527" fill="#0f0f0f"></path>
</svg>

Before

Width:  |  Height:  |  Size: 1.2 KiB

@@ -1,5 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 48 48">
<path fill-opacity=".01" fill="#fff" d="M0 0h48v48H0z" data-follow-fill="#fff"/>
<path stroke-linejoin="round" stroke-linecap="round" stroke-width="4" stroke="#333" d="m14 14 20 20M14 34l20-20"
data-follow-stroke="#333"/>
</svg>

Before

Width:  |  Height:  |  Size: 319 B

@@ -1,9 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 48 48">
<path fill-opacity=".01" fill="#fff" d="M0 0h48v48H0z" data-follow-fill="#fff"/>
<path stroke-linejoin="round" stroke-width="4" stroke="#333" d="M8 15h32l-3 29H11L8 15z" clip-rule="evenodd"
data-follow-stroke="#333"/>
<path stroke-linecap="round" stroke-width="4" stroke="#333" d="M20.002 25.002v10M28.002 25v9.997"
data-follow-stroke="#333"/>
<path stroke-linejoin="round" stroke-linecap="round" stroke-width="4" stroke="#333" d="M12 15 28.324 3 36 15"
data-follow-stroke="#333"/>
</svg>

Before

Width:  |  Height:  |  Size: 607 B

@@ -1,4 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 48 48">
<path stroke-linejoin="round" stroke-linecap="round" stroke-width="4" stroke="#333" d="M22 42H6V26M26 6h16v16"
data-follow-stroke="#333"/>
</svg>

Before

Width:  |  Height:  |  Size: 232 B

@@ -1,6 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 48 48">
<path stroke-linejoin="round" stroke-width="4" stroke="#333"
d="M5 8a2 2 0 0 1 2-2h12l5 6h17a2 2 0 0 1 2 2v26a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V8z" data-follow-stroke="#333"/>
<path stroke-linejoin="round" stroke-linecap="round" stroke-width="4" stroke="#333"
d="m30 28-6.007 6L18 28.013M24 20v14" data-follow-stroke="#333"/>
</svg>

Before

Width:  |  Height:  |  Size: 429 B

@@ -1,8 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 48 48">
<path fill-opacity=".01" fill="#fff" d="M0 0h48v48H0z" data-follow-fill="#fff"/>
<path stroke-linejoin="round" stroke-linecap="round" stroke-width="4" stroke="#333"
d="M36.728 36.728A17.943 17.943 0 0 1 24 42c-9.941 0-18-8.059-18-18S14.059 6 24 6c4.97 0 9.47 2.015 12.728 5.272C38.386 12.93 42 17 42 17"
data-follow-stroke="#333"/>
<path stroke-linejoin="round" stroke-linecap="round" stroke-width="4" stroke="#333" d="M42 8v9h-9"
data-follow-stroke="#333"/>
</svg>

Before

Width:  |  Height:  |  Size: 580 B

@@ -1,5 +0,0 @@
<svg t="1629255942168" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="8597"
width="128" height="128">
<path d="M512 0C229.248 0 0 229.248 0 512s229.248 512 512 512 512-229.248 512-512S794.752 0 512 0zM432.64 748.256 197.056 512.64l90.496-90.496 145.056 145.12 307.744-307.744 90.496 90.496L432.64 748.256z"
p-id="8598" fill="#2DB84D"></path>
</svg>

Before

Width:  |  Height:  |  Size: 412 B

@@ -1,5 +0,0 @@
<svg t="1629255998157" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="8792"
width="128" height="128">
<path d="M512 0C229.248 0 0 229.248 0 512s229.248 512 512 512 512-229.248 512-512S794.752 0 512 0zM320 768 320 256l512.256 256L320 768z"
p-id="8793" fill="#00BCD4"></path>
</svg>

Before

Width:  |  Height:  |  Size: 344 B

@@ -1,5 +0,0 @@
<svg t="1629256110134" class="icon" viewBox="0 0 1185 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3025"
width="128" height="128">
<path d="M700.365715 60.074208l468.319709 783.01037a118.307323 118.307323 0 0 1-45.345458 164.539234 127.444603 127.444603 0 0 1-62.324434 16.297089H124.376114A122.739586 122.739586 0 0 1 0 903.567918a117.557248 117.557248 0 0 1 16.706221-60.346963L485.02593 60.210586A126.694528 126.694528 0 0 1 655.020257 16.365278a122.739586 122.739586 0 0 1 45.345458 43.845308zM592.764011 916.046443a80.803561 80.803561 0 1 0-80.80356-80.803561 80.803561 80.803561 0 0 0 80.735372 80.803561z m0-673.567572a95.464122 95.464122 0 0 0-94.373104 106.988006l34.09433 270.640786a61.369793 61.369793 0 0 0 121.103058 0l34.094329-270.640786a95.464122 95.464122 0 0 0-94.373103-106.919818z"
p-id="3026" fill="#DB3340"></path>
</svg>

Before

Width:  |  Height:  |  Size: 878 B

@@ -1,5 +0,0 @@
<svg t="1629251564375" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1864"
width="128" height="128">
<path d="M490.061576 311.947636a31.030303 31.030303 0 0 1 43.876848 0l310.303031 310.303031a31.030303 31.030303 0 0 1-43.876849 43.876848L512 377.762909l-288.364606 288.364606a31.030303 31.030303 0 0 1-43.876849-43.876848l310.303031-310.303031z"
p-id="1865" fill="#326cf3"></path>
</svg>

Before

Width:  |  Height:  |  Size: 453 B

@@ -1,5 +0,0 @@
<svg fill="#cbcbcb" t="1605778065489" class="icon" viewBox="0 0 1024 1024" version="1.1"
xmlns="http://www.w3.org/2000/svg" p-id="6682" width="16" height="16">
<path d="M622.276923 508.061538l257.969231-257.96923c11.815385-11.815385 11.815385-29.538462 0-41.353846l-41.353846-41.353847c-11.815385-11.815385-29.538462-11.815385-41.353846 0L539.569231 425.353846c-7.876923 7.876923-19.692308 7.876923-27.569231 0L254.030769 165.415385c-11.815385-11.815385-29.538462-11.815385-41.353846 0l-41.353846 41.353846c-11.815385 11.815385-11.815385 29.538462 0 41.353846l257.969231 257.969231c7.876923 7.876923 7.876923 19.692308 0 27.56923L169.353846 793.6c-11.815385 11.815385-11.815385 29.538462 0 41.353846l41.353846 41.353846c11.815385 11.815385 29.538462 11.815385 41.353846 0L512 618.338462c7.876923-7.876923 19.692308-7.876923 27.569231 0l257.969231 257.96923c11.815385 11.815385 29.538462 11.815385 41.353846 0l41.353846-41.353846c11.815385-11.815385 11.815385-29.538462 0-41.353846L622.276923 535.630769c-5.907692-7.876923-5.907692-19.692308 0-27.569231z"
p-id="6683"></path>
</svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

@@ -1,5 +0,0 @@
<svg fill="#ffffff" t="1605778065489" class="icon" viewBox="0 0 1024 1024" version="1.1"
xmlns="http://www.w3.org/2000/svg" p-id="6682" width="16" height="16">
<path d="M622.276923 508.061538l257.969231-257.96923c11.815385-11.815385 11.815385-29.538462 0-41.353846l-41.353846-41.353847c-11.815385-11.815385-29.538462-11.815385-41.353846 0L539.569231 425.353846c-7.876923 7.876923-19.692308 7.876923-27.569231 0L254.030769 165.415385c-11.815385-11.815385-29.538462-11.815385-41.353846 0l-41.353846 41.353846c-11.815385 11.815385-11.815385 29.538462 0 41.353846l257.969231 257.969231c7.876923 7.876923 7.876923 19.692308 0 27.56923L169.353846 793.6c-11.815385 11.815385-11.815385 29.538462 0 41.353846l41.353846 41.353846c11.815385 11.815385 29.538462 11.815385 41.353846 0L512 618.338462c7.876923-7.876923 19.692308-7.876923 27.569231 0l257.969231 257.96923c11.815385 11.815385 29.538462 11.815385 41.353846 0l41.353846-41.353846c11.815385-11.815385 11.815385-29.538462 0-41.353846L622.276923 535.630769c-5.907692-7.876923-5.907692-19.692308 0-27.569231z"
p-id="6683"></path>
</svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

@@ -1,5 +0,0 @@
<svg fill="#cbcbcb" t="1605778299751" class="icon" viewBox="0 0 1024 1024" version="1.1"
xmlns="http://www.w3.org/2000/svg" p-id="12048" width="16" height="16">
<path d="M170.666667 85.333333h682.666666a85.333333 85.333333 0 0 1 85.333334 85.333334v682.666666a85.333333 85.333333 0 0 1-85.333334 85.333334H170.666667a85.333333 85.333333 0 0 1-85.333334-85.333334V170.666667a85.333333 85.333333 0 0 1 85.333334-85.333334z m0 85.333334v682.666666h682.666666V170.666667H170.666667z"
p-id="12049"></path>
</svg>

Before

Width:  |  Height:  |  Size: 526 B

@@ -1,4 +0,0 @@
<svg fill="#cbcbcb" t="1605777927585" class="icon" viewBox="0 0 1024 1024" version="1.1"
xmlns="http://www.w3.org/2000/svg" p-id="5690" width="16" height="16">
<path d="M938.666667 469.333333v85.333334H85.333333v-85.333334z" p-id="5691"></path>
</svg>

Before

Width:  |  Height:  |  Size: 260 B

@@ -1,810 +0,0 @@
:root {
--Font_Family: Microsoft YaHei UI;
--Font_Size: 12px;
--Font_Size_TabHeader: 12px;
--Font_Size_TableHeader: 12px;
--Font_Size_PageTitle: 15px;
--Font_Size_HugeTitle: 30px;
--Font_Size_MsgLabel: 15px;
--Color_Default: #ffffff;
--Color_DefaultHover: #f0f0f0;
--Color_DefaultPressed: #ececec;
--Color_DefaultText: #212121;
--Color_Primary: #326cf3;
--Color_PrimaryHover: #477bf4;
--Color_PrimaryPressed: #84a7f8;
--Color_PrimaryText: #ffffff;
--Color_Success: #2db84d;
--Color_SuccessHover: #42bf5f;
--Color_SuccessPressed: #81d494;
--Color_Danger: #db3340;
--Color_DangerHover: #df4853;
--Color_DangerPressed: #e9858c;
--Color_Warning: #e9af20;
--Color_WarningHover: #ebb737;
--Color_WarningPressed: #f2cf79;
--Color_Info: #00bcd4;
--Color_InfoHover: #1ac3d8;
--Color_InfoPressed: #66d7e5;
--Color_Border: #cbcbcb;
--Color_WindowsIcon: #cbcbcb;
--Color_Background: #eeeeee;
--Color_Shadow: #AA000000;
--Color_Transparent: transparent;
--Size_InputPaddingLR: 8px;
--Size_InputPaddingTB: 6px;
--Size_HeaderHeight: 45px;
--Size_ItemHeight: 18px;
--Size_ProgressHeight: 26px;
--Size_ControlHeight: 18px;
--Size_ControlPaddingLR: 8px;
--Size_ControlPaddingTB: 6px;
--Size_IconSize: 24px;
--Size_CornerRadius: 4px;
--Size_CalendarWidth: 287px;
--Size_CalendarHeight: 300px;
}
/* #QSS_START */
* {
font-family: var(--Font_Family);
font-size: var(--Font_Size);
background: var(--Color_Default);
color: var(--Color_DefaultText);
}
/* Widget */
QWidget#MainViewLeftWidget {
background: #000000;
}
QWidget#TestWidget {
background: #000000;
}
QWidget#BaseWidget {
background: var(--Color_Default);
}
QWidget#TaskItemView {
background: var(--Color_Default);
border-style: solid;
border-width: 1px;
border-color: var(--Color_Border);
border-radius: var(--Size_CornerRadius);
}
QWidget#AboutWidget {
}
QScrollBar::vertical{
background:transparent;
width: 4px;
border-radius:6px;
}
QScrollBar::handle{
background: lightgray;
border-radius:6px;
}
QScrollBar::handle:hover{background:gray;}
QScrollBar::sub-line{background:transparent;}
QScrollBar::add-line{background:transparent;}
/* PushButton Style */
QPushButton {
border-style: solid;
border-width: 1px;
border-color: var(--Color_DefaultPressed);
padding-left: var(--Size_InputPaddingLR);
padding-right: var(--Size_InputPaddingLR);
padding-top: var(--Size_InputPaddingTB);
padding-bottom: var(--Size_InputPaddingTB);
border-radius: var(--Size_CornerRadius);
color: var(--Color_DefaultText);
background: var(--Color_Default);
}
QPushButton::hover {
color: var(--Color_DefaultText);
background: var(--Color_DefaultHover);
}
QPushButton::pressed {
color: var(--Color_DefaultText);
background: var(--Color_DefaultPressed);
}
QPushButton#PrimaryPushButton {
border-color: var(--Color_PrimaryPressed);
color: var(--Color_PrimaryText);
background: var(--Color_Primary);
}
QPushButton#PrimaryPushButton::hover {
background: var(--Color_PrimaryHover);
}
QPushButton#PrimaryPushButton::pressed {
background: var(--Color_PrimaryPressed);
}
QPushButton#SuccessPushButton {
border-color: var(--Color_SuccessPressed);
color: var(--Color_PrimaryText);
background: var(--Color_Success);
}
QPushButton#SuccessPushButton::hover {
background: var(--Color_SuccessHover);
}
QPushButton#SuccessPushButton::pressed {
background: var(--Color_SuccessPressed);
}
QPushButton#DangerPushButton {
border-color: var(--Color_DangerPressed);
color: var(--Color_PrimaryText);
background: var(--Color_Danger);
}
QPushButton#DangerPushButton::hover {
background: var(--Color_DangerHover);
}
QPushButton#DangerPushButton::pressed {
background: var(--Color_DangerPressed);
}
QPushButton#WarningPushButton {
border-color: var(--Color_WarningPressed);
color: var(--Color_PrimaryText);
background: var(--Color_Warning);
}
QPushButton#WarningPushButton::hover {
background: var(--Color_WarningHover);
}
QPushButton#WarningPushButton::pressed {
background: var(--Color_WarningPressed);
}
QPushButton#InfoPushButton {
border-color: var(--Color_InfoPressed);
color: var(--Color_PrimaryText);
background: var(--Color_Info);
}
QPushButton#InfoPushButton::hover {
background: var(--Color_InfoHover);
}
QPushButton#InfoPushButton::pressed {
background: var(--Color_InfoPressed);
}
/* Software Buton */
QPushButton#SoftwareIconPushButton {
border-style: none;
border-radius: 0px;
height: 26;
width: 26;
background: var(--Color_Transparent);
image: url($RESOURCE_PATH$/svg/V.svg)
}
QPushButton#SoftwareIconPushButton::hover {
image: url($RESOURCE_PATH$/svg/V.svg)
}
QPushButton#SoftwareIconPushButton::pressed {
image: url($RESOURCE_PATH$/svg/V.svg)
}
/* Windows Buton */
QPushButton#CloseWindowPushButton {
border-style: none;
border-radius: 0px;
background: var(--Color_Transparent);
image: url($RESOURCE_PATH$/svg/windows/close.svg)
}
QPushButton#CloseWindowPushButton::hover {
background: #ff1a1a;
image: url($RESOURCE_PATH$/svg/windows/closeHover.svg)
}
QPushButton#CloseWindowPushButton::pressed {
background: #ff6666;
image: url($RESOURCE_PATH$/svg/windows/closeHover.svg)
}
QPushButton#MaxWindowPushButton {
border-style: none;
border-radius: 0px;
background: var(--Color_Transparent);
image: url($RESOURCE_PATH$/svg/windows/max.svg)
}
QPushButton#MaxWindowPushButton::hover {
background: var(--Color_DefaultHover);
}
QPushButton#MaxWindowPushButton::pressed {
background: var(--Color_DefaultPressed);
}
QPushButton#MinWindowPushButton {
border-style: none;
border-radius: 0px;
background: var(--Color_Transparent);
image: url($RESOURCE_PATH$/svg/windows/min.svg)
}
QPushButton#MinWindowPushButton::hover {
background: var(--Color_DefaultHover);
}
QPushButton#MinWindowPushButton::pressed {
background: var(--Color_DefaultPressed);
}
/* Task item tool button */
QPushButton#TaskRetryPushButton {
border-style: none;
background: var(--Color_Default);
padding-left: 4px;
padding-right: 4px;
image: url($RESOURCE_PATH$/svg/taskItem/retry.svg)
}
QPushButton#TaskRetryPushButton::hover {
background: var(--Color_DefaultHover);
}
QPushButton#TaskRetryPushButton::pressed {
background: var(--Color_DefaultPressed);
}
QPushButton#TaskCancelPushButton {
border-style: none;
background: var(--Color_Default);
padding-left: 4px;
padding-right: 4px;
image: url($RESOURCE_PATH$/svg/taskItem/cancel.svg)
}
QPushButton#TaskCancelPushButton::hover {
background: var(--Color_DefaultHover);
}
QPushButton#TaskCancelPushButton::pressed {
background: var(--Color_DefaultPressed);
}
QPushButton#TaskDeletePushButton {
border-style: none;
background: var(--Color_Default);
padding-left: 4px;
padding-right: 4px;
image: url($RESOURCE_PATH$/svg/taskItem/delete.svg)
}
QPushButton#TaskDeletePushButton::hover {
background: var(--Color_DefaultHover);
}
QPushButton#TaskDeletePushButton::pressed {
background: var(--Color_DefaultPressed);
}
QPushButton#TaskOpenPushButton {
border-style: none;
background: var(--Color_Default);
padding-left: 4px;
padding-right: 4px;
image: url($RESOURCE_PATH$/svg/taskItem/open.svg)
}
QPushButton#TaskOpenPushButton::hover {
background: var(--Color_DefaultHover);
}
QPushButton#TaskOpenPushButton::pressed {
background: var(--Color_DefaultPressed);
}
QPushButton#TaskExpandPushButton {
border-style: none;
background: var(--Color_Default);
padding-left: 4px;
padding-right: 4px;
image: url($RESOURCE_PATH$/svg/taskItem/expand.svg)
}
QPushButton#TaskExpandPushButton::hover {
background: var(--Color_DefaultHover);
}
QPushButton#TaskExpandPushButton::pressed {
background: var(--Color_DefaultPressed);
}
/* MainTab Icon Button */
QPushButton#SearchIconPushButton {
border-style: none;
border-radius: 0px;
height: 26;
width: 26;
background: var(--Color_Transparent);
image: url($RESOURCE_PATH$/svg/leftTab/search.svg)
}
QPushButton#SearchIconPushButton::hover {
image: url($RESOURCE_PATH$/svg/leftTab/search.svg)
}
QPushButton#SearchIconPushButton::pressed {
image: url($RESOURCE_PATH$/svg/leftTab/search.svg)
}
QPushButton#TaskIconPushButton {
border-style: none;
border-radius: 0px;
height: 26;
width: 26;
background: var(--Color_Transparent);
image: url($RESOURCE_PATH$/svg/leftTab/download.svg)
}
QPushButton#TaskIconPushButton::hover {
image: url($RESOURCE_PATH$/svg/leftTab/downloadHover.svg)
}
QPushButton#TaskIconPushButton::pressed {
image: url($RESOURCE_PATH$/svg/leftTab/downloadHover.svg)
}
QPushButton#SettingsIconPushButton {
border-style: none;
border-radius: 0px;
height: 26;
width: 26;
background: var(--Color_Transparent);
image: url($RESOURCE_PATH$/svg/leftTab/settings.svg)
}
QPushButton#SettingsIconPushButton::hover {
image: url($RESOURCE_PATH$/svg/leftTab/settings.svg)
}
QPushButton#SettingsIconPushButton::pressed {
image: url($RESOURCE_PATH$/svg/leftTab/settings.svg)
}
QPushButton#AboutIconPushButton {
border-style: none;
border-radius: 0px;
height: 26;
width: 26;
background: var(--Color_Transparent);
image: url($RESOURCE_PATH$/svg/leftTab/info.svg)
}
QPushButton#AboutIconPushButton::hover {
image: url($RESOURCE_PATH$/svg/leftTab/info.svg)
}
QPushButton#AboutIconPushButton::pressed {
image: url($RESOURCE_PATH$/svg/leftTab/info.svg)
}
QPushButton#PrePagePushButton {
border-style: none;
border-radius: 0px;
border-width: 1px;
border-color: var(--Color_Border);
height: 18;
width: 18;
background: var(--Color_DefaultHover);
image: url($RESOURCE_PATH$/svg/left.svg)
}
QPushButton#PrePagePushButton::hover {
border-style: solid;
image: url($RESOURCE_PATH$/svg/left.svg)
}
QPushButton#PrePagePushButton::pressed {
image: url($RESOURCE_PATH$/svg/left.svg)
}
QPushButton#NextPagePushButton {
border-style: none;
border-radius: 0px;
border-width: 1px;
border-color: var(--Color_Border);
height: 18;
width: 18;
background: var(--Color_DefaultHover);
image: url($RESOURCE_PATH$/svg/right.svg)
}
QPushButton#NextPagePushButton::hover {
border-style: solid;
image: url($RESOURCE_PATH$/svg/right.svg)
}
QPushButton#NextPagePushButton::pressed {
image: url($RESOURCE_PATH$/svg/right.svg)
}
/* LineEdit Style */
QLineEdit {
border-style: solid;
border-width: 1px;
border-color: var(--Color_Border);
padding-left: var(--Size_InputPaddingLR);
padding-right: var(--Size_InputPaddingLR);
padding-top: var(--Size_InputPaddingTB);
padding-bottom: var(--Size_InputPaddingTB);
border-radius: var(--Size_CornerRadius);
min-height: var(--Size_ControlHeight);
color: var(--Color_DefaultText);
background: var(--Color_Default);
}
QLineEdit::hover {
border-color: var(--Color_Primary);
}
QLineEdit::focus {
border-color: var(--Color_Primary);
}
QLineEdit::disabled {
background: var(--Color_DefaultHover);
border-style: none;
}
/* CheckBox Style */
QCheckBox::indicator {
width: 14px;
height: 14px;
padding: 2px;
border: 1px solid var(--Color_Border);
border-radius: 2px;
}
QCheckBox::indicator::unchecked:hover {
border: 1px solid var(--Color_PrimaryHover);
}
QCheckBox::indicator::checked {
background: var(--Color_Primary);
image: url($RESOURCE_PATH$/svg/check.svg);
border: 1px solid var(--Color_Primary);
}
/* TabWidget */
QTabWidget::pane {
border:none;
border-top: 1px solid var(--Color_Border);
}
QTabBar::tab {
border:none;
padding-left: 8px;
padding-right: 8px;
padding-bottom: 3px;
font-size: var(--Font_Size_TabHeader);
font-weight: bold;
min-width: 60;
}
QTabBar::tab:selected {
color: var(--Color_Primary);
border-color: var(--Color_Primary);
border-style: solid;
border-bottom-width: 3px;
}
/* Label */
QLabel#PageTitleLabel {
font-size: var(--Font_Size_PageTitle);
font-weight: bold;
}
QLabel#PageSubTitleLabel {
font-size: var(--Font_Size_PageTitle);
}
QLabel#HugeTitleLabel {
font-size: var(--Font_Size_HugeTitle);
font-weight: bold;
}
QLabel#LogoBottomLabel {
font-size: var(--Font_Size_MsgLabel);
color: #ffffff;
}
QLabel#SearchErrLabel {
font-size: var(--Font_Size);
color: var(--Color_Danger);
max-height: var(--Font_Size_MsgLabel);
}
QLabel#IconLabel {
background: var(--Color_Transparent);
}
QLabel#BoldLabel {
font-weight: bold;
}
QLabel#ItalicLabel {
font-style: italic;
}
QLabel#TagLabel {
background: var(--Color_DangerPressed);
}
/* QFrame */
QFrame#VLineQFrame
{
border-top-style: none;
border-bottom-style: none;
border-right-style: none;
border-left: 1px solid var(--Color_Border);
}
QFrame#HLineQFrame
{
border-top: 1px solid var(--Color_Border);
border-bottom-style: none;
border-right-style: none;
border-left-style: none;
}
/* QComboBox */
QComboBox {
border-style: solid;
border-width: 1px;
border-color: var(--Color_Border);
padding-left: var(--Size_InputPaddingLR);
padding-right: var(--Size_InputPaddingLR);
padding-top: var(--Size_InputPaddingTB);
padding-bottom: var(--Size_InputPaddingTB);
border-radius: var(--Size_CornerRadius);
min-height: var(--Size_ControlHeight);
color: var(--Color_DefaultText);
background: var(--Color_Default);
}
QComboBox::hover {
border-color: var(--Color_Primary);
}
QComboBox::focus {
border-color: var(--Color_Primary);
}
QComboBox::drop-down {
subcontrol-position:center right;
margin-right: 15px;
border-left-style: none;
}
QComboBox::down-arrow {
image: url($RESOURCE_PATH$/svg/down.svg);
width : 24px;
height : 24px;
}
QComboBox::down-arrow:hover {
image: url($RESOURCE_PATH$/svg/downHover.svg);
}
QComboBox::down-arrow:on {
image: url($RESOURCE_PATH$/svg/upHover.svg);
}
QComboBox QAbstractItemView {
margin-top:5px;
border:1px solid var(--Color_Primary);
outline:none;
}
QComboBox QAbstractItemView::item {
height: var(--Size_ItemHeight);
padding-top: var(--Size_InputPaddingTB);
padding-bottom: var(--Size_InputPaddingTB);
}
QComboBox QAbstractItemView::item:selected{
color: var(--Color_PrimaryText);
background: var(--Color_Primary);
}
/* QListWidget */
QListWidget#TaskTabListWidget {
border-style: none;
background: var(--Color_Default);
max-width: 150px;
outline:none;
}
QListWidget#TaskTabListWidget::item
{
height:32px;
padding-left: var(--Size_ControlPaddingLR);
border: none;
border-radius: var(--Size_CornerRadius);
margin-top:5px;
}
QListWidget#TaskTabListWidget::item:hover
{
background: var(--Color_DefaultHover);
color: var(--Color_DefaultText);
}
QListWidget#TaskTabListWidget::item:selected
{
background: var(--Color_Primary);
color: var(--Color_PrimaryText);
}
QListWidget#TaskContentListWidget {
border-style: none;
background: var(--Color_Default);
outline:none;
}
QListWidget#TaskContentListWidget::item
{
border-style: solid;
border-width: 1px;
border-radius: var(--Size_CornerRadius);
border-color: var(--Color_Border);
min-height:80px;
margin-top:5px;
background: var(--Color_Default);
}
QListWidget#TaskContentListWidget::item:hover
{
background: var(--Color_Default);
}
QListWidget#TaskContentListWidget::item:selected
{
background: var(--Color_Default);
}
QWidget#DownloadItemsWidget {
border-left: none;
border-right: none;
border-top: 1px solid var(--Color_Border);
border-bottom: none;
background: var(--Color_Default);
outline:none;
}
QWidget#DownloadItemView {
border-style: none;
margin-top: 2px;
background: var(--Color_Default);
}
QListWidget#DownloadItemsListWidget {
border-left: none;
border-right: none;
border-top: 1px solid var(--Color_Border);
border-bottom: none;
background: var(--Color_Default);
outline:none;
}
QListWidget#DownloadItemsListWidget::item
{
border-style: none;
min-height:45px;
margin-top:2px;
background: var(--Color_Default);
}
QListWidget#DownloadItemsListWidget::item:hover
{
background: var(--Color_Default);
}
QListWidget#DownloadItemsListWidget::item:selected
{
background: var(--Color_Default);
}
QListWidget QScrollBar::vertical{
background:transparent;
width: 4px;
border-radius:6px;
}
QListWidget QScrollBar::handle{
background: lightgray;
border-radius:6px;
}
QListWidget QScrollBar::handle:hover{background:gray;}
QListWidget QScrollBar::sub-line{background:transparent;}
QListWidget QScrollBar::add-line{background:transparent;}
/* QTableWidget */
QTableWidget {
border-left: none;
border-right: none;
border-top: none;
border-bottom: 1px solid var(--Color_Border);
text-align: center;
color: var(--Color_DefaultText);
background: var(--Color_DefaultHover);
padding:5px;
}
QTableWidget::item{
margin-top: 5px;
background: var(--Color_Default);
}
QTableWidget::item:selected{
color: var(--Color_PrimaryText);
background: var(--Color_Primary);
}
QTableWidget QHeaderView::section {
color: var(--Color_DefaultText);
background: var(--Color_DefaultHover);
text-align: center;
height: var(--Size_HeaderHeight);
border: none;
font-size: var(--Font_Size_TableHeader);
}
QTableWidget QScrollBar::vertical{
background:transparent;
width: 4px;
border-radius:6px;
}
QTableWidget QScrollBar::handle{
background: lightgray;
border-radius:6px;
}
QTableWidget QScrollBar::handle:hover{background:gray;}
QTableWidget QScrollBar::sub-line{background:transparent;}
QTableWidget QScrollBar::add-line{background:transparent;}
/* QProgressBar */
QProgressBar {
border:none;
background:rgb(230, 230, 230);
border-radius:0px;
text-align:center;
color:gray;
}
QProgressBar::chunk {
background:rgb(71, 137, 250);
border-radius:0px;
}
/* QScrollArea */
QScrollArea {
border: none;
}
-65
View File
@@ -1,65 +0,0 @@
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
'''
@File : enum.py
@Date : 2021/05/08
@Author : Yaronzz
@Version : 1.0
@Contact : yaronhuang@foxmail.com
@Desc :
'''
from enum import Enum
class ButtonStyle(Enum):
Default = 0,
Primary = 1,
Success = 2,
Danger = 3,
Warning = 4,
Info = 5,
CloseWindow = 6,
MaxWindow = 7,
MinWindow = 8,
SearchIcon = 9,
TaskIcon = 10,
SettingsIcon = 11,
AboutIcon = 12,
SoftwareIcon = 13
PrePage = 14
NextPage = 15
TaskRetry = 16,
TaskCancel = 17,
TaskDelete = 18,
TaskOpen = 19,
TaskExpand = 20,
class LabelStyle(Enum):
Default = 0,
PageTitle = 1,
PageSubTitle = 2,
HugeTitle = 3,
LogoBottom = 4,
SearchErr = 5,
Icon = 6,
Bold = 7,
Italic = 9,
Tag = 10
class ThemeStyle(Enum):
Default = 0,
Dark = 1,
class ListWidgetStyle(Enum):
Default = 0,
TaskTab = 1,
DownloadItems = 2,
-66
View File
@@ -1,66 +0,0 @@
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
"""
@File : qssParse.py
@Date : 2021/05/08
@Author : Yaronzz
@Version : 1.0
@Contact : yaronhuang@foxmail.com
@Desc :
"""
import os
import aigpy
from tidal_gui.style import ThemeStyle
_RESOURCE_PATH = './resource'
if os.path.isdir(_RESOURCE_PATH):
_RESOURCE_PATH = os.path.abspath(_RESOURCE_PATH).replace('\\', '/')
else:
_RESOURCE_PATH = aigpy.path.getDirName(__file__).replace('\\', '/') + "resource"
def __getParam__(line: str):
key = aigpy.string.getSub(line, "--", ":")
value = aigpy.string.getSub(line, ":", ";")
return key, value
def __parseParamsList__(content: str) -> dict:
globalStr = aigpy.string.getSub(content, ":root", "}")
lines = globalStr.split("\n")
array = {}
for line in lines:
key, value = __getParam__(line)
if key == "" or value == "":
continue
array[key] = value
return array
def __parseQss__(content: str, params: dict) -> str:
content = aigpy.string.getSub(content, "/* #QSS_START */")
for key in params:
content = content.replace("var(--" + key + ")", params[key])
content = content.replace("$RESOURCE_PATH$", _RESOURCE_PATH)
return content
def __getQss__(filePath: str) -> str:
content = aigpy.file.getContent(filePath)
params = __parseParamsList__(content)
qss = __parseQss__(content, params)
return qss
def getResourcePath():
return _RESOURCE_PATH
def getThemeQssContent(style: ThemeStyle = ThemeStyle.Default):
name = "theme" + style.name + ".qss"
return __getQss__(_RESOURCE_PATH + '/' + name)

Some files were not shown because too many files have changed in this diff Show More