Compare commits
11 Commits
fix/reinde
...
960d3b74ac
| Author | SHA1 | Date | |
|---|---|---|---|
| 960d3b74ac | |||
| 585e8c9671 | |||
| 4f9261505e | |||
| 08b9492455 | |||
| 9d0dcb412b | |||
| 17c26c5140 | |||
| 0a589d9c64 | |||
| 8abb89ea48 | |||
| 3951394ede | |||
| 73f26e121c | |||
| 3be6c71dcd |
1
.vscode/settings.json
vendored
1
.vscode/settings.json
vendored
@@ -22,6 +22,7 @@
|
||||
"dotenv",
|
||||
"encyclopaedia",
|
||||
"ENDC",
|
||||
"Gitea",
|
||||
"levenshtein",
|
||||
"metallum",
|
||||
"musify",
|
||||
|
||||
228
README.md
228
README.md
@@ -2,61 +2,43 @@
|
||||
|
||||
[](https://ci.elara.ws/repos/59)
|
||||
|
||||
<img src="assets/logo.svg" width=300 alt="music kraken logo"/>
|
||||
<img src="https://gitea.elara.ws/music-kraken/music-kraken-core/media/branch/experimental/assets/logo.svg" width=300 alt="music kraken logo"/>
|
||||
|
||||
- [Music Kraken](#music-kraken)
|
||||
- [Installation](#installation)
|
||||
- [From source](#from-source)
|
||||
- [Notes for WSL](#notes-for-wsl)
|
||||
- [Quick-Guide](#quick-guide)
|
||||
- [Query](#query)
|
||||
- [CONTRIBUTE](#contribute)
|
||||
- [Matrix Space](#matrix-space)
|
||||
- [TODO till the next release](#todo-till-the-next-release)
|
||||
- [Programming Interface / Use as Library](#programming-interface--use-as-library)
|
||||
- [Quick Overview](#quick-overview)
|
||||
- [Data Model](#data-model)
|
||||
- [Data Objects](#data-objects)
|
||||
- [Creation](#creation)
|
||||
- [Installation](#installation)
|
||||
- [Quick-Guide](#quick-guide)
|
||||
- [How to search properly](#query)
|
||||
- [Matrix Space](#matrix-space)
|
||||
|
||||
If you want to use this a library or contribute, check out [the wiki](https://gitea.elara.ws/music-kraken/music-kraken-core/wiki) for more information.
|
||||
|
||||
---
|
||||
|
||||
## Installation
|
||||
|
||||
You can find and get this project from either [PyPI](https://pypi.org/project/music-kraken/) as a Python-Package,
|
||||
or simply the source code from [GitHub](https://github.com/HeIIow2/music-downloader). Note that even though
|
||||
everything **SHOULD** work cross-platform, I have only tested it on Ubuntu.
|
||||
If you enjoy this project, feel free to give it a star on GitHub.
|
||||
You can find and get this project from either [PyPI](https://pypi.org/project/music-kraken/) as a Python-Package,
|
||||
or simply the source code from [Gitea](https://gitea.elara.ws/music-kraken/music-kraken-core). **
|
||||
|
||||
> THE PyPI PACKAGE IS OUTDATED
|
||||
**NOTES**
|
||||
|
||||
- Even though everything **SHOULD** work cross-platform, I have only tested it on Ubuntu.
|
||||
- If you enjoy this project, feel free to give it a star on GitHub.
|
||||
|
||||
### From source
|
||||
|
||||
if you use Debian or Ubuntu:
|
||||
|
||||
```sh
|
||||
git clone https://github.com/HeIIow2/music-downloader
|
||||
sudo apt install pandoc
|
||||
|
||||
cd music-downloader/
|
||||
python3 -m pip install -r requirements.txt
|
||||
git clone https://gitea.elara.ws/music-kraken/music-kraken-core.git
|
||||
python3 -m pip install -e music-kraken-core/
|
||||
```
|
||||
|
||||
then you can add to `~/.bashrc`
|
||||
To update the program, if installed like this, go into the `music-kraken-core` directory and run `git pull`.
|
||||
|
||||
```
|
||||
alias music-kraken='cd your/directory/music-downloader/src; python3 -m music_kraken'
|
||||
alias 🥺='sudo'
|
||||
```
|
||||
### Get it running on other Systems
|
||||
|
||||
```sh
|
||||
source ~/.bashrc
|
||||
music-kraken
|
||||
```
|
||||
Here are the collected issues, that are related to running the program on different systems. If you have any issues, feel free to open a new one.
|
||||
|
||||
### Notes for WSL
|
||||
#### Windows + WSL
|
||||
|
||||
If you choose to run it in WSL, make sure ` ~/.local/bin` is added to your `$PATH` [#2][i2]
|
||||
Add ` ~/.local/bin` to your `$PATH`. [#2][i2]
|
||||
|
||||
## Quick-Guide
|
||||
|
||||
@@ -87,10 +69,6 @@ The escape character is as usual `\`.
|
||||
|
||||
---
|
||||
|
||||
## CONTRIBUTE
|
||||
|
||||
I am happy about every pull request. To contribute look [here](contribute.md).
|
||||
|
||||
## Matrix Space
|
||||
|
||||
<img align="right" alt="music-kraken logo" src="assets/element_logo.png" width=100>
|
||||
@@ -99,171 +77,5 @@ I decided against creating a discord server, due to various communities get ofte
|
||||
|
||||
**Click [this invitation](https://matrix.to/#/#music-kraken:matrix.org) _([https://matrix.to/#/#music-kraken:matrix.org](https://matrix.to/#/#music-kraken:matrix.org))_ to join.**
|
||||
|
||||
## TODO till the next release
|
||||
|
||||
> These Points will most likely be in the changelogs.
|
||||
|
||||
- [x] Migrate away from pandoc, to a more lightweight alternative, that can be installed over PiPY.
|
||||
- [ ] Update the Documentation of the internal structure. _(could be pushed back one release)_
|
||||
|
||||
---
|
||||
|
||||
# Programming Interface / Use as Library
|
||||
|
||||
This application is $100\%$ centered around Data. Thus, the most important thing for working with musik kraken is, to understand how I structured the data.
|
||||
|
||||
## Quick Overview
|
||||
|
||||
- explanation of the [Data Model](#data-model)
|
||||
- how to use the [Data Objects](#data-objects)
|
||||
- further Dokumentation of _hopefully_ [most relevant classes](documentation/objects.md)
|
||||
- the [old implementation](documentation/old_implementation.md)
|
||||
|
||||
```mermaid
|
||||
---
|
||||
title: Quick Overview (outdated)
|
||||
---
|
||||
sequenceDiagram
|
||||
|
||||
participant pg as Page (eg. YouTube, MB, Musify, ...)
|
||||
participant obj as DataObjects (eg. Song, Artist, ...)
|
||||
participant db as DataBase
|
||||
|
||||
obj ->> db: write
|
||||
db ->> obj: read
|
||||
|
||||
pg -> obj: find a source for any page, for object.
|
||||
obj -> pg: add more detailed data from according page.
|
||||
obj -> pg: if available download audio to target.
|
||||
```
|
||||
|
||||
## Data Model
|
||||
|
||||
The Data Structure, that the whole programm is built on looks as follows:
|
||||
|
||||
```mermaid
|
||||
---
|
||||
title: Music Data
|
||||
---
|
||||
erDiagram
|
||||
|
||||
|
||||
|
||||
Target {
|
||||
|
||||
}
|
||||
|
||||
Lyrics {
|
||||
|
||||
}
|
||||
|
||||
Song {
|
||||
|
||||
}
|
||||
|
||||
Album {
|
||||
|
||||
}
|
||||
|
||||
Artist {
|
||||
|
||||
}
|
||||
|
||||
Label {
|
||||
|
||||
}
|
||||
|
||||
Source {
|
||||
|
||||
}
|
||||
|
||||
Source }o--|| Song : ""
|
||||
Source }o--|| Lyrics : ""
|
||||
Source }o--|| Album : ""
|
||||
Source }o--|| Artist : ""
|
||||
Source }o--|| Label : ""
|
||||
|
||||
Song }o--o{ Album : AlbumSong
|
||||
Album }o--o{ Artist : ArtistAlbum
|
||||
Song }o--o{ Artist : "ArtistSong (features)"
|
||||
|
||||
Label }o--o{ Album : LabelAlbum
|
||||
Label }o--o{ Artist : LabelSong
|
||||
|
||||
Song ||--o{ Lyrics : ""
|
||||
Song ||--o{ Target : ""
|
||||
```
|
||||
|
||||
Ok now this **WILL** look intimidating, thus I break it down quickly.
|
||||
*That is also the reason I didn't add all Attributes here.*
|
||||
|
||||
The most important Entities are:
|
||||
|
||||
- Song
|
||||
- Album
|
||||
- Artist
|
||||
- Label
|
||||
|
||||
All of them *(and Lyrics)* can have multiple Sources, and every Source can only Point to one of those Element.
|
||||
|
||||
The `Target` Entity represents the location on the hard drive a Song has. One Song can have multiple download Locations.
|
||||
|
||||
The `Lyrics` Entity simply represents the Lyrics of each Song. One Song can have multiple Lyrics, e.g. Translations.
|
||||
|
||||
Here is the simplified Diagramm without only the main Entities.
|
||||
|
||||
|
||||
```mermaid
|
||||
---
|
||||
title: simplified Music Data
|
||||
---
|
||||
erDiagram
|
||||
|
||||
Song {
|
||||
|
||||
}
|
||||
|
||||
Album {
|
||||
|
||||
}
|
||||
|
||||
Artist {
|
||||
|
||||
}
|
||||
|
||||
Label {
|
||||
|
||||
}
|
||||
|
||||
Song }o--o{ Album : AlbumSong
|
||||
Album }o--o{ Artist : ArtistAlbum
|
||||
Song }o--o{ Artist : "ArtistSong (features)"
|
||||
|
||||
Label }o--o{ Album : LabelAlbum
|
||||
Label }o--o{ Artist : LabelSong
|
||||
|
||||
```
|
||||
|
||||
Looks way more manageable, doesn't it?
|
||||
|
||||
The reason every relation here is a `n:m` *(many to many)* relation is not, that it makes sense in the aspekt of modeling reality, but to be able to put data from many Sources in the same Data Model.
|
||||
Every Service models Data a bit different, and projecting a one-to-many relationship to a many to many relationship without data loss is easy. The other way around it is basically impossible
|
||||
|
||||
## Data Objects
|
||||
|
||||
> Not 100% accurate yet and *might* change slightly
|
||||
|
||||
### Creation
|
||||
|
||||
```python
|
||||
# needs to be added
|
||||
```
|
||||
|
||||
|
||||
|
||||
If you just want to start implementing, then just use the code example I provided, I don't care.
|
||||
For those who don't want any bugs and use it as intended *(which is recommended, cuz I am only one person so there are defs bugs)* continue reading, and read the whole documentation, which may exist in the future xD
|
||||
|
||||
|
||||
[i10]: https://github.com/HeIIow2/music-downloader/issues/10
|
||||
[i2]: https://github.com/HeIIow2/music-downloader/issues/2
|
||||
|
||||
@@ -6,8 +6,8 @@ logging.getLogger().setLevel(logging.DEBUG)
|
||||
|
||||
if __name__ == "__main__":
|
||||
commands = [
|
||||
"s: #a Psychonaut 4",
|
||||
"d: 0"
|
||||
"s: #a Crystal F",
|
||||
"10"
|
||||
]
|
||||
|
||||
|
||||
|
||||
@@ -6,16 +6,18 @@ import re
|
||||
from .utils import cli_function
|
||||
from .options.first_config import initial_config
|
||||
|
||||
from ..utils import output, BColors
|
||||
from ..utils.config import write_config, main_settings
|
||||
from ..utils.shared import URL_PATTERN
|
||||
from ..utils.string_processing import fit_to_file_system
|
||||
from ..utils.support_classes.query import Query
|
||||
from ..utils.support_classes.download_result import DownloadResult
|
||||
from ..utils.exception import MKInvalidInputException
|
||||
from ..utils.exception.download import UrlNotFoundException
|
||||
from ..utils.enums.colors import BColors
|
||||
from .. import console
|
||||
|
||||
from ..download.results import Results, Option, PageResults
|
||||
from ..download.results import Results, Option, PageResults, GoToResults
|
||||
from ..download.page_attributes import Pages
|
||||
from ..pages import Page
|
||||
from ..objects import Song, Album, Artist, DatabaseObject
|
||||
@@ -174,7 +176,7 @@ class Downloader:
|
||||
print()
|
||||
|
||||
page_count = 0
|
||||
for option in self.current_results.formated_generator(max_items_per_page=self.max_displayed_options):
|
||||
for option in self.current_results.formatted_generator():
|
||||
if isinstance(option, Option):
|
||||
_downloadable = self.pages.is_downloadable(option.music_object)
|
||||
|
||||
@@ -249,7 +251,7 @@ class Downloader:
|
||||
f"Recommendations and suggestions on sites to implement appreciated.\n"
|
||||
f"But don't be a bitch if I don't end up implementing it.")
|
||||
return
|
||||
self.set_current_options(PageResults(page, data_object.options))
|
||||
self.set_current_options(PageResults(page, data_object.options, max_items_per_page=self.max_displayed_options))
|
||||
self.print_current_options()
|
||||
return
|
||||
|
||||
@@ -299,95 +301,121 @@ class Downloader:
|
||||
self.set_current_options(self.pages.search(parsed_query))
|
||||
self.print_current_options()
|
||||
|
||||
def goto(self, index: int):
|
||||
def goto(self, data_object: DatabaseObject):
|
||||
page: Type[Page]
|
||||
music_object: DatabaseObject
|
||||
|
||||
try:
|
||||
page, music_object = self.current_results.get_music_object_by_index(index)
|
||||
except KeyError:
|
||||
print()
|
||||
print(f"The option {index} doesn't exist.")
|
||||
print()
|
||||
return
|
||||
self.pages.fetch_details(data_object)
|
||||
|
||||
self.pages.fetch_details(music_object)
|
||||
|
||||
print(music_object)
|
||||
print(music_object.options)
|
||||
self.set_current_options(PageResults(page, music_object.options))
|
||||
print(data_object)
|
||||
print(data_object.options)
|
||||
self.set_current_options(GoToResults(data_object.options, max_items_per_page=self.max_displayed_options))
|
||||
|
||||
self.print_current_options()
|
||||
|
||||
def download(self, download_str: str, download_all: bool = False) -> bool:
|
||||
to_download: List[DatabaseObject] = []
|
||||
|
||||
if re.match(URL_PATTERN, download_str) is not None:
|
||||
_, music_objects = self.pages.fetch_url(download_str)
|
||||
to_download.append(music_objects)
|
||||
|
||||
def download(self, data_objects: List[DatabaseObject], **kwargs) -> bool:
|
||||
output()
|
||||
if len(data_objects) == 1:
|
||||
output(f"Downloading {data_objects[0].option_string}...", color=BColors.BOLD)
|
||||
else:
|
||||
index: str
|
||||
for index in download_str.split(", "):
|
||||
if not index.strip().isdigit():
|
||||
print()
|
||||
print(f"Every download thingie has to be an index, not {index}.")
|
||||
print()
|
||||
return False
|
||||
|
||||
for index in download_str.split(", "):
|
||||
to_download.append(self.current_results.get_music_object_by_index(int(index))[1])
|
||||
|
||||
print()
|
||||
print("Downloading:")
|
||||
for download_object in to_download:
|
||||
print(download_object.option_string)
|
||||
print()
|
||||
output(f"Downloading {len(data_objects)} objects...", *("- " + o.option_string for o in data_objects), color=BColors.BOLD, sep="\n")
|
||||
|
||||
_result_map: Dict[DatabaseObject, DownloadResult] = dict()
|
||||
|
||||
for database_object in to_download:
|
||||
r = self.pages.download(music_object=database_object, genre=self.genre, download_all=download_all,
|
||||
process_metadata_anyway=self.process_metadata_anyway)
|
||||
for database_object in data_objects:
|
||||
r = self.pages.download(
|
||||
music_object=database_object,
|
||||
genre=self.genre,
|
||||
**kwargs
|
||||
)
|
||||
_result_map[database_object] = r
|
||||
|
||||
for music_object, result in _result_map.items():
|
||||
print()
|
||||
print(music_object.option_string)
|
||||
print(result)
|
||||
output()
|
||||
output(music_object.option_string)
|
||||
output(result)
|
||||
|
||||
return True
|
||||
|
||||
def process_input(self, input_str: str) -> bool:
|
||||
input_str = input_str.strip()
|
||||
processed_input: str = input_str.lower()
|
||||
try:
|
||||
input_str = input_str.strip()
|
||||
processed_input: str = input_str.lower()
|
||||
|
||||
if processed_input in EXIT_COMMANDS:
|
||||
return True
|
||||
if processed_input in EXIT_COMMANDS:
|
||||
return True
|
||||
|
||||
if processed_input == ".":
|
||||
self.print_current_options()
|
||||
return False
|
||||
|
||||
if processed_input == "..":
|
||||
if self.previous_option():
|
||||
if processed_input == ".":
|
||||
self.print_current_options()
|
||||
return False
|
||||
|
||||
if processed_input == "..":
|
||||
if self.previous_option():
|
||||
self.print_current_options()
|
||||
return False
|
||||
|
||||
command = ""
|
||||
query = processed_input
|
||||
if ":" in processed_input:
|
||||
_ = processed_input.split(":")
|
||||
command, query = _[0], ":".join(_[1:])
|
||||
|
||||
do_search = "s" in command
|
||||
do_download = "d" in command
|
||||
do_merge = "m" in command
|
||||
|
||||
if do_search and do_download:
|
||||
raise MKInvalidInputException(message="You can't search and download at the same time.")
|
||||
|
||||
if do_search and do_merge:
|
||||
raise MKInvalidInputException(message="You can't search and merge at the same time.")
|
||||
|
||||
if do_search:
|
||||
self.search(":".join(input_str.split(":")[1:]))
|
||||
return False
|
||||
|
||||
indices = []
|
||||
for possible_index in query.split(","):
|
||||
possible_index = possible_index.strip()
|
||||
if possible_index == "":
|
||||
continue
|
||||
|
||||
i = 0
|
||||
if possible_index.isdigit():
|
||||
i = int(possible_index)
|
||||
else:
|
||||
raise MKInvalidInputException(message=f"The index \"{possible_index}\" is not a number.")
|
||||
|
||||
if i < 0 and i >= len(self.current_results):
|
||||
raise MKInvalidInputException(message=f"The index \"{i}\" is not within the bounds of 0-{len(self.current_results)}.")
|
||||
|
||||
indices.append(i)
|
||||
|
||||
selected_objects = [self.current_results[i] for i in indices]
|
||||
|
||||
if do_merge:
|
||||
old_selected_objects = selected_objects
|
||||
|
||||
a = old_selected_objects[0]
|
||||
for b in old_selected_objects[1:]:
|
||||
if type(a) != type(b):
|
||||
raise MKInvalidInputException(message="You can't merge different types of objects.")
|
||||
a.merge(b)
|
||||
|
||||
selected_objects = [a]
|
||||
|
||||
if do_download:
|
||||
self.download(selected_objects)
|
||||
return False
|
||||
|
||||
if len(selected_objects) != 1:
|
||||
raise MKInvalidInputException(message="You can only go to one object at a time without merging.")
|
||||
|
||||
self.goto(selected_objects[0])
|
||||
return False
|
||||
except MKInvalidInputException as e:
|
||||
output("\n" + e.message + "\n", color=BColors.FAIL)
|
||||
help_message()
|
||||
|
||||
if processed_input.startswith("s: "):
|
||||
self.search(input_str[3:])
|
||||
return False
|
||||
|
||||
if processed_input.startswith("d: "):
|
||||
return self.download(input_str[3:])
|
||||
|
||||
if processed_input.isdigit():
|
||||
self.goto(int(processed_input))
|
||||
return False
|
||||
|
||||
if processed_input != "help":
|
||||
print(f"{BColors.WARNING.value}Invalid input.{BColors.ENDC.value}")
|
||||
help_message()
|
||||
return False
|
||||
|
||||
def mainloop(self):
|
||||
|
||||
@@ -13,31 +13,32 @@ class Option:
|
||||
|
||||
|
||||
class Results:
|
||||
def __init__(self) -> None:
|
||||
def __init__(self, max_items_per_page: int = 10, **kwargs) -> None:
|
||||
self._by_index: Dict[int, DatabaseObject] = dict()
|
||||
self._page_by_index: Dict[int: Type[Page]] = dict()
|
||||
|
||||
self.max_items_per_page = max_items_per_page
|
||||
|
||||
def __iter__(self) -> Generator[DatabaseObject, None, None]:
|
||||
for option in self.formated_generator():
|
||||
for option in self.formatted_generator():
|
||||
if isinstance(option, Option):
|
||||
yield option.music_object
|
||||
|
||||
def formated_generator(self, max_items_per_page: int = 10) -> Generator[Union[Type[Page], Option], None, None]:
|
||||
def formatted_generator(self) -> Generator[Union[Type[Page], Option], None, None]:
|
||||
self._by_index = dict()
|
||||
self._page_by_index = dict()
|
||||
|
||||
def get_music_object_by_index(self, index: int) -> Tuple[Type[Page], DatabaseObject]:
|
||||
# if this throws a key error, either the formatted generator needs to be iterated, or the option doesn't exist.
|
||||
return self._page_by_index[index], self._by_index[index]
|
||||
|
||||
def __getitem__(self, index: int):
|
||||
return self._by_index[index]
|
||||
|
||||
|
||||
class SearchResults(Results):
|
||||
def __init__(
|
||||
self,
|
||||
pages: Tuple[Type[Page], ...] = None
|
||||
|
||||
pages: Tuple[Type[Page], ...] = None,
|
||||
**kwargs,
|
||||
) -> None:
|
||||
super().__init__()
|
||||
super().__init__(**kwargs)
|
||||
|
||||
self.pages = pages or []
|
||||
# this would initialize a list for every page, which I don't think I want
|
||||
@@ -54,9 +55,12 @@ class SearchResults(Results):
|
||||
|
||||
def get_page_results(self, page: Type[Page]) -> "PageResults":
|
||||
return PageResults(page, self.results.get(page, []))
|
||||
|
||||
def __len__(self) -> int:
|
||||
return sum(min(self.max_items_per_page, len(results)) for results in self.results.values())
|
||||
|
||||
def formated_generator(self, max_items_per_page: int = 10):
|
||||
super().formated_generator()
|
||||
def formatted_generator(self):
|
||||
super().formatted_generator()
|
||||
i = 0
|
||||
|
||||
for page in self.results:
|
||||
@@ -70,19 +74,37 @@ class SearchResults(Results):
|
||||
i += 1
|
||||
j += 1
|
||||
|
||||
if j >= max_items_per_page:
|
||||
if j >= self.max_items_per_page:
|
||||
break
|
||||
|
||||
|
||||
class GoToResults(Results):
|
||||
def __init__(self, results: List[DatabaseObject], **kwargs):
|
||||
self.results: List[DatabaseObject] = results
|
||||
|
||||
super().__init__(**kwargs)
|
||||
|
||||
def __getitem__(self, index: int):
|
||||
return self.results[index]
|
||||
|
||||
def __len__(self) -> int:
|
||||
return len(self.results)
|
||||
|
||||
def formatted_generator(self):
|
||||
yield from (Option(i, o) for i, o in enumerate(self.results))
|
||||
|
||||
|
||||
|
||||
class PageResults(Results):
|
||||
def __init__(self, page: Type[Page], results: List[DatabaseObject]) -> None:
|
||||
super().__init__()
|
||||
def __init__(self, page: Type[Page], results: List[DatabaseObject], **kwargs) -> None:
|
||||
super().__init__(**kwargs)
|
||||
|
||||
self.page: Type[Page] = page
|
||||
self.results: List[DatabaseObject] = results
|
||||
|
||||
|
||||
def formated_generator(self, max_items_per_page: int = 10):
|
||||
super().formated_generator()
|
||||
def formatted_generator(self, max_items_per_page: int = 10):
|
||||
super().formatted_generator()
|
||||
i = 0
|
||||
|
||||
yield self.page
|
||||
@@ -92,3 +114,6 @@ class PageResults(Results):
|
||||
self._by_index[i] = option
|
||||
self._page_by_index[i] = self.page
|
||||
i += 1
|
||||
|
||||
def __len__(self) -> int:
|
||||
return len(self.results)
|
||||
|
||||
@@ -403,18 +403,20 @@ class Page:
|
||||
self.LOGGER.info(f"{song.option_string} already exists, thus not downloading again.")
|
||||
return r
|
||||
|
||||
skip_intervals = []
|
||||
if not found_on_disc:
|
||||
for source in sources:
|
||||
r = self.download_song_to_target(source=source, target=temp_target, desc=song.option_string)
|
||||
|
||||
if not r.is_fatal_error:
|
||||
skip_intervals = self.get_skip_intervals(song, source)
|
||||
break
|
||||
|
||||
if temp_target.exists:
|
||||
r.merge(self._post_process_targets(
|
||||
song=song,
|
||||
temp_target=temp_target,
|
||||
interval_list=[] if found_on_disc else self.get_skip_intervals(song, source)
|
||||
interval_list=skip_intervals,
|
||||
))
|
||||
|
||||
return r
|
||||
|
||||
@@ -961,7 +961,7 @@ class Musify(Page):
|
||||
source_list=source_list,
|
||||
date=timestamp,
|
||||
album_type=album_type,
|
||||
album_status=album_status
|
||||
album_status=album_status,
|
||||
)
|
||||
|
||||
def _parse_album(self, soup: BeautifulSoup) -> Album:
|
||||
@@ -1054,7 +1054,7 @@ class Musify(Page):
|
||||
date=date
|
||||
)
|
||||
|
||||
def _get_discography(self, url: MusifyUrl, artist_name: str = None, stop_at_level: int = 1) -> Generator[Album, None, None]:
|
||||
def _get_discography(self, artist: Artist, url: MusifyUrl, artist_name: str = None, stop_at_level: int = 1) -> Generator[Album, None, None]:
|
||||
"""
|
||||
POST https://musify.club/artist/filteralbums
|
||||
ArtistID: 280348
|
||||
@@ -1076,7 +1076,10 @@ class Musify(Page):
|
||||
soup: BeautifulSoup = BeautifulSoup(r.content, features="html.parser")
|
||||
|
||||
for card_soup in soup.find_all("div", {"class": "card"}):
|
||||
yield self._parse_album_card(card_soup, artist_name)
|
||||
album = self._parse_album_card(card_soup, artist_name)
|
||||
if album.album_type is AlbumType.COMPILATION_ALBUM or album.album_type is AlbumType.MIXTAPE:
|
||||
continue
|
||||
artist.main_album_collection.append(album)
|
||||
|
||||
def fetch_artist(self, source: Source, stop_at_level: int = 1) -> Artist:
|
||||
"""
|
||||
@@ -1098,7 +1101,7 @@ class Musify(Page):
|
||||
|
||||
artist = self._get_artist_attributes(url)
|
||||
|
||||
artist.main_album_collection.extend(self._get_discography(url, artist.name))
|
||||
self._get_discography(artist, url, artist.name)
|
||||
|
||||
return artist
|
||||
|
||||
|
||||
@@ -19,8 +19,13 @@ def _apply_color(msg: str, color: BColors) -> str:
|
||||
if not isinstance(msg, str):
|
||||
msg = str(msg)
|
||||
|
||||
endc = BColors.ENDC.value
|
||||
|
||||
if color is BColors.ENDC:
|
||||
return msg
|
||||
|
||||
msg = msg.replace(BColors.ENDC.value, BColors.ENDC.value + color.value)
|
||||
|
||||
return color.value + msg + BColors.ENDC.value
|
||||
|
||||
|
||||
|
||||
@@ -1 +1,11 @@
|
||||
__all__ = ["config"]
|
||||
class MKBaseException(Exception):
|
||||
def __init__(self, message: str = None, **kwargs) -> None:
|
||||
self.message = message
|
||||
super().__init__(message, **kwargs)
|
||||
|
||||
|
||||
class MKFrontendException(MKBaseException):
|
||||
pass
|
||||
|
||||
class MKInvalidInputException(MKFrontendException):
|
||||
pass
|
||||
|
||||
Reference in New Issue
Block a user