STARTED IMPLEMENTING DB

STARTED IMPLEMENTING DB
This commit is contained in:
Hellow 2023-02-14 23:07:16 +01:00
parent 021f8a6905
commit 5a699c3937
27 changed files with 990 additions and 755 deletions

View File

@ -24,13 +24,13 @@ Total : 43 files, 2560 codes, 558 comments, 774 blanks, all 3892 lines
| [src/music_kraken/audio_source/sources/source.py](/src/music_kraken/not_used_anymore/sources/source.py) | Python | 11 | 5 | 8 | 24 |
| [src/music_kraken/audio_source/sources/youtube.py](/src/music_kraken/not_used_anymore/sources/youtube.py) | Python | 71 | 4 | 24 | 99 |
| [src/music_kraken/database/__init__.py](/src/music_kraken/database/__init__.py) | Python | 11 | 1 | 4 | 16 |
| [src/music_kraken/database/database.py](/src/music_kraken/database/database.py) | Python | 191 | 102 | 45 | 338 |
| [src/music_kraken/database/database.py](/src/music_kraken/database/old_database.py) | Python | 191 | 102 | 45 | 338 |
| [src/music_kraken/database/get_song.py](/src/music_kraken/database/get_song.py) | Python | 40 | 5 | 11 | 56 |
| [src/music_kraken/database/new_database.py](/src/music_kraken/database/database.py) | Python | 172 | 78 | 55 | 305 |
| [src/music_kraken/database/objects/__init__.py](/src/music_kraken/database/objects/__init__.py) | Python | 11 | 0 | 4 | 15 |
| [src/music_kraken/database/objects/artist.py](/src/music_kraken/database/objects/artist.py) | Python | 18 | 0 | 5 | 23 |
| [src/music_kraken/database/objects/database_object.py](/src/music_kraken/database/objects/database_object.py) | Python | 21 | 5 | 11 | 37 |
| [src/music_kraken/database/objects/song.py](/src/music_kraken/database/objects/song.py) | Python | 179 | 52 | 60 | 291 |
| [src/music_kraken/database/new_database.py](/src/music_kraken/database/old_database.py) | Python | 172 | 78 | 55 | 305 |
| [src/music_kraken/database/objects/__init__.py](/src/music_kraken/objects/__init__.py) | Python | 11 | 0 | 4 | 15 |
| [src/music_kraken/database/objects/artist.py](/src/music_kraken/objects/artist.py) | Python | 18 | 0 | 5 | 23 |
| [src/music_kraken/database/objects/database_object.py](/src/music_kraken/objects/database_object.py) | Python | 21 | 5 | 11 | 37 |
| [src/music_kraken/database/objects/song.py](/src/music_kraken/objects/song.py) | Python | 179 | 52 | 60 | 291 |
| [src/music_kraken/database/song.py](/src/music_kraken/database/song.py) | Python | 125 | 20 | 45 | 190 |
| [src/music_kraken/database/temp_database.py](/src/music_kraken/database/temp_database.py) | Python | 10 | 0 | 7 | 17 |
| [src/music_kraken/lyrics/__init__.py](/src/music_kraken/lyrics/__init__.py) | Python | 0 | 0 | 1 | 1 |

View File

@ -16,14 +16,14 @@ Total : 20 files, 700 codes, 165 comments, 162 blanks, all 1027 lines
| [src/music_kraken/audio_source/fetch_source.py](/src/music_kraken/not_used_anymore/fetch_source.py) | Python | 0 | 0 | -1 | -1 |
| [src/music_kraken/database/__init__.py](/src/music_kraken/database/__init__.py) | Python | -4 | 1 | 1 | -2 |
| [src/music_kraken/database/artist.py](/src/music_kraken/database/artist.py) | Python | -11 | 0 | -5 | -16 |
| [src/music_kraken/database/database.py](/src/music_kraken/database/database.py) | Python | 25 | 22 | 4 | 51 |
| [src/music_kraken/database/database.py](/src/music_kraken/database/old_database.py) | Python | 25 | 22 | 4 | 51 |
| [src/music_kraken/database/get_song.py](/src/music_kraken/database/get_song.py) | Python | 40 | 5 | 11 | 56 |
| [src/music_kraken/database/metadata.py](/src/music_kraken/database/metadata.py) | Python | -13 | 0 | -5 | -18 |
| [src/music_kraken/database/new_database.py](/src/music_kraken/database/database.py) | Python | 172 | 78 | 55 | 305 |
| [src/music_kraken/database/objects/__init__.py](/src/music_kraken/database/objects/__init__.py) | Python | 11 | 0 | 4 | 15 |
| [src/music_kraken/database/objects/artist.py](/src/music_kraken/database/objects/artist.py) | Python | 18 | 0 | 5 | 23 |
| [src/music_kraken/database/objects/database_object.py](/src/music_kraken/database/objects/database_object.py) | Python | 21 | 5 | 11 | 37 |
| [src/music_kraken/database/objects/song.py](/src/music_kraken/database/objects/song.py) | Python | 179 | 52 | 60 | 291 |
| [src/music_kraken/database/new_database.py](/src/music_kraken/database/old_database.py) | Python | 172 | 78 | 55 | 305 |
| [src/music_kraken/database/objects/__init__.py](/src/music_kraken/objects/__init__.py) | Python | 11 | 0 | 4 | 15 |
| [src/music_kraken/database/objects/artist.py](/src/music_kraken/objects/artist.py) | Python | 18 | 0 | 5 | 23 |
| [src/music_kraken/database/objects/database_object.py](/src/music_kraken/objects/database_object.py) | Python | 21 | 5 | 11 | 37 |
| [src/music_kraken/database/objects/song.py](/src/music_kraken/objects/song.py) | Python | 179 | 52 | 60 | 291 |
| [src/music_kraken/database/song.py](/src/music_kraken/database/song.py) | Python | 39 | -5 | 12 | 46 |
| [src/music_kraken/database/source.py](/src/music_kraken/database/source.py) | Python | -5 | 0 | -2 | -7 |
| [src/music_kraken/database/target.py](/src/music_kraken/database/target.py) | Python | -22 | 0 | -9 | -31 |

View File

@ -24,13 +24,13 @@ Total : 45 files, 2886 codes, 594 comments, 854 blanks, all 4334 lines
| [src/music_kraken/audio_source/sources/source.py](/src/music_kraken/not_used_anymore/sources/source.py) | Python | 11 | 5 | 8 | 24 |
| [src/music_kraken/audio_source/sources/youtube.py](/src/music_kraken/not_used_anymore/sources/youtube.py) | Python | 71 | 4 | 24 | 99 |
| [src/music_kraken/database/__init__.py](/src/music_kraken/database/__init__.py) | Python | 11 | 1 | 4 | 16 |
| [src/music_kraken/database/database.py](/src/music_kraken/database/database.py) | Python | 191 | 102 | 45 | 338 |
| [src/music_kraken/database/database.py](/src/music_kraken/database/old_database.py) | Python | 191 | 102 | 45 | 338 |
| [src/music_kraken/database/get_song.py](/src/music_kraken/database/get_song.py) | Python | 40 | 5 | 11 | 56 |
| [src/music_kraken/database/new_database.py](/src/music_kraken/database/database.py) | Python | 327 | 98 | 89 | 514 |
| [src/music_kraken/database/objects/__init__.py](/src/music_kraken/database/objects/__init__.py) | Python | 10 | 0 | 3 | 13 |
| [src/music_kraken/database/objects/artist.py](/src/music_kraken/database/objects/artist.py) | Python | 18 | 0 | 5 | 23 |
| [src/music_kraken/database/objects/database_object.py](/src/music_kraken/database/objects/database_object.py) | Python | 28 | 7 | 13 | 48 |
| [src/music_kraken/database/objects/song.py](/src/music_kraken/database/objects/song.py) | Python | 255 | 61 | 86 | 402 |
| [src/music_kraken/database/new_database.py](/src/music_kraken/database/old_database.py) | Python | 327 | 98 | 89 | 514 |
| [src/music_kraken/database/objects/__init__.py](/src/music_kraken/objects/__init__.py) | Python | 10 | 0 | 3 | 13 |
| [src/music_kraken/database/objects/artist.py](/src/music_kraken/objects/artist.py) | Python | 18 | 0 | 5 | 23 |
| [src/music_kraken/database/objects/database_object.py](/src/music_kraken/objects/database_object.py) | Python | 28 | 7 | 13 | 48 |
| [src/music_kraken/database/objects/song.py](/src/music_kraken/objects/song.py) | Python | 255 | 61 | 86 | 402 |
| [src/music_kraken/database/song.py](/src/music_kraken/database/song.py) | Python | 125 | 20 | 45 | 190 |
| [src/music_kraken/database/temp_database.py](/src/music_kraken/database/temp_database.py) | Python | 12 | 0 | 8 | 20 |
| [src/music_kraken/lyrics/__init__.py](/src/music_kraken/lyrics/__init__.py) | Python | 0 | 0 | 1 | 1 |

View File

@ -12,10 +12,10 @@ Total : 10 files, 326 codes, 36 comments, 80 blanks, all 442 lines
| filename | language | code | comment | blank | total |
| :--- | :--- | ---: | ---: | ---: | ---: |
| [src/goof.py](/src/goof.py) | Python | 30 | -1 | 7 | 36 |
| [src/music_kraken/database/new_database.py](/src/music_kraken/database/database.py) | Python | 155 | 20 | 34 | 209 |
| [src/music_kraken/database/objects/__init__.py](/src/music_kraken/database/objects/__init__.py) | Python | -1 | 0 | -1 | -2 |
| [src/music_kraken/database/objects/database_object.py](/src/music_kraken/database/objects/database_object.py) | Python | 7 | 2 | 2 | 11 |
| [src/music_kraken/database/objects/song.py](/src/music_kraken/database/objects/song.py) | Python | 76 | 9 | 26 | 111 |
| [src/music_kraken/database/new_database.py](/src/music_kraken/database/old_database.py) | Python | 155 | 20 | 34 | 209 |
| [src/music_kraken/database/objects/__init__.py](/src/music_kraken/objects/__init__.py) | Python | -1 | 0 | -1 | -2 |
| [src/music_kraken/database/objects/database_object.py](/src/music_kraken/objects/database_object.py) | Python | 7 | 2 | 2 | 11 |
| [src/music_kraken/database/objects/song.py](/src/music_kraken/objects/song.py) | Python | 76 | 9 | 26 | 111 |
| [src/music_kraken/database/temp_database.py](/src/music_kraken/database/temp_database.py) | Python | 2 | 0 | 1 | 3 |
| [src/music_kraken/metadata/sources/__init__.py](/src/music_kraken/not_used_anymore/metadata/sources/__init__.py) | Python | 3 | 0 | 2 | 5 |
| [src/music_kraken/metadata/sources/musicbrainz.py](/src/music_kraken/not_used_anymore/metadata/sources/musicbrainz.py) | Python | 42 | 6 | 9 | 57 |

View File

@ -24,15 +24,15 @@ Total : 49 files, 3402 codes, 663 comments, 973 blanks, all 5038 lines
| [src/music_kraken/audio_source/sources/source.py](/src/music_kraken/not_used_anymore/sources/source.py) | Python | 11 | 5 | 8 | 24 |
| [src/music_kraken/audio_source/sources/youtube.py](/src/music_kraken/not_used_anymore/sources/youtube.py) | Python | 71 | 4 | 24 | 99 |
| [src/music_kraken/database/__init__.py](/src/music_kraken/database/__init__.py) | Python | 12 | 1 | 4 | 17 |
| [src/music_kraken/database/database.py](/src/music_kraken/database/database.py) | Python | 191 | 102 | 45 | 338 |
| [src/music_kraken/database/database.py](/src/music_kraken/database/old_database.py) | Python | 191 | 102 | 45 | 338 |
| [src/music_kraken/database/get_song.py](/src/music_kraken/database/get_song.py) | Python | 40 | 5 | 11 | 56 |
| [src/music_kraken/database/new_database.py](/src/music_kraken/database/database.py) | Python | 401 | 109 | 107 | 617 |
| [src/music_kraken/database/objects/__init__.py](/src/music_kraken/database/objects/__init__.py) | Python | 14 | 0 | 4 | 18 |
| [src/music_kraken/database/objects/artist.py](/src/music_kraken/database/objects/artist.py) | Python | 18 | 0 | 5 | 23 |
| [src/music_kraken/database/objects/metadata.py](/src/music_kraken/database/objects/metadata.py) | Python | 245 | 52 | 50 | 347 |
| [src/music_kraken/database/objects/parents.py](/src/music_kraken/database/objects/parents.py) | Python | 46 | 8 | 23 | 77 |
| [src/music_kraken/database/objects/song.py](/src/music_kraken/database/objects/song.py) | Python | 258 | 52 | 76 | 386 |
| [src/music_kraken/database/objects/source.py](/src/music_kraken/database/objects/source.py) | Python | 46 | 7 | 13 | 66 |
| [src/music_kraken/database/new_database.py](/src/music_kraken/database/old_database.py) | Python | 401 | 109 | 107 | 617 |
| [src/music_kraken/database/objects/__init__.py](/src/music_kraken/objects/__init__.py) | Python | 14 | 0 | 4 | 18 |
| [src/music_kraken/database/objects/artist.py](/src/music_kraken/objects/artist.py) | Python | 18 | 0 | 5 | 23 |
| [src/music_kraken/database/objects/metadata.py](/src/music_kraken/objects/metadata.py) | Python | 245 | 52 | 50 | 347 |
| [src/music_kraken/database/objects/parents.py](/src/music_kraken/objects/parents.py) | Python | 46 | 8 | 23 | 77 |
| [src/music_kraken/database/objects/song.py](/src/music_kraken/objects/song.py) | Python | 258 | 52 | 76 | 386 |
| [src/music_kraken/database/objects/source.py](/src/music_kraken/objects/source.py) | Python | 46 | 7 | 13 | 66 |
| [src/music_kraken/database/song.py](/src/music_kraken/database/song.py) | Python | 125 | 20 | 45 | 190 |
| [src/music_kraken/database/temp_database.py](/src/music_kraken/database/temp_database.py) | Python | 12 | 0 | 8 | 20 |
| [src/music_kraken/lyrics/__init__.py](/src/music_kraken/lyrics/__init__.py) | Python | 0 | 0 | 1 | 1 |

View File

@ -14,13 +14,13 @@ Total : 16 files, 516 codes, 69 comments, 119 blanks, all 704 lines
| [src/goof.py](/src/goof.py) | Python | 42 | 2 | 10 | 54 |
| [src/music_kraken/__init__.py](/src/music_kraken/__init__.py) | Python | 1 | 0 | 0 | 1 |
| [src/music_kraken/database/__init__.py](/src/music_kraken/database/__init__.py) | Python | 1 | 0 | 0 | 1 |
| [src/music_kraken/database/new_database.py](/src/music_kraken/database/database.py) | Python | 74 | 11 | 18 | 103 |
| [src/music_kraken/database/objects/__init__.py](/src/music_kraken/database/objects/__init__.py) | Python | 4 | 0 | 1 | 5 |
| [src/music_kraken/database/objects/database_object.py](/src/music_kraken/database/objects/database_object.py) | Python | -28 | -7 | -13 | -48 |
| [src/music_kraken/database/objects/metadata.py](/src/music_kraken/database/objects/metadata.py) | Python | 245 | 52 | 50 | 347 |
| [src/music_kraken/database/objects/parents.py](/src/music_kraken/database/objects/parents.py) | Python | 46 | 8 | 23 | 77 |
| [src/music_kraken/database/objects/song.py](/src/music_kraken/database/objects/song.py) | Python | 3 | -9 | -10 | -16 |
| [src/music_kraken/database/objects/source.py](/src/music_kraken/database/objects/source.py) | Python | 46 | 7 | 13 | 66 |
| [src/music_kraken/database/new_database.py](/src/music_kraken/database/old_database.py) | Python | 74 | 11 | 18 | 103 |
| [src/music_kraken/database/objects/__init__.py](/src/music_kraken/objects/__init__.py) | Python | 4 | 0 | 1 | 5 |
| [src/music_kraken/database/objects/database_object.py](/src/music_kraken/objects/database_object.py) | Python | -28 | -7 | -13 | -48 |
| [src/music_kraken/database/objects/metadata.py](/src/music_kraken/objects/metadata.py) | Python | 245 | 52 | 50 | 347 |
| [src/music_kraken/database/objects/parents.py](/src/music_kraken/objects/parents.py) | Python | 46 | 8 | 23 | 77 |
| [src/music_kraken/database/objects/song.py](/src/music_kraken/objects/song.py) | Python | 3 | -9 | -10 | -16 |
| [src/music_kraken/database/objects/source.py](/src/music_kraken/objects/source.py) | Python | 46 | 7 | 13 | 66 |
| [src/music_kraken/static_files/new_db.sql](/src/music_kraken/static_files/new_db.sql) | SQLite | 1 | 0 | 0 | 1 |
| [src/music_kraken/tagging/__init__.py](/src/music_kraken/tagging/__init__.py) | Python | 8 | 0 | 1 | 9 |
| [src/music_kraken/tagging/id3.py](/src/music_kraken/tagging/id3.py) | Python | 51 | 4 | 20 | 75 |

View File

@ -24,15 +24,15 @@ Total : 49 files, 3404 codes, 664 comments, 974 blanks, all 5042 lines
| [src/music_kraken/audio_source/sources/source.py](/src/music_kraken/not_used_anymore/sources/source.py) | Python | 11 | 5 | 8 | 24 |
| [src/music_kraken/audio_source/sources/youtube.py](/src/music_kraken/not_used_anymore/sources/youtube.py) | Python | 71 | 4 | 24 | 99 |
| [src/music_kraken/database/__init__.py](/src/music_kraken/database/__init__.py) | Python | 12 | 1 | 4 | 17 |
| [src/music_kraken/database/database.py](/src/music_kraken/database/database.py) | Python | 191 | 102 | 45 | 338 |
| [src/music_kraken/database/database.py](/src/music_kraken/database/old_database.py) | Python | 191 | 102 | 45 | 338 |
| [src/music_kraken/database/get_song.py](/src/music_kraken/database/get_song.py) | Python | 40 | 5 | 11 | 56 |
| [src/music_kraken/database/new_database.py](/src/music_kraken/database/database.py) | Python | 402 | 110 | 107 | 619 |
| [src/music_kraken/database/objects/__init__.py](/src/music_kraken/database/objects/__init__.py) | Python | 15 | 0 | 5 | 20 |
| [src/music_kraken/database/objects/artist.py](/src/music_kraken/database/objects/artist.py) | Python | 18 | 0 | 5 | 23 |
| [src/music_kraken/database/objects/metadata.py](/src/music_kraken/database/objects/metadata.py) | Python | 245 | 52 | 50 | 347 |
| [src/music_kraken/database/objects/parents.py](/src/music_kraken/database/objects/parents.py) | Python | 46 | 8 | 23 | 77 |
| [src/music_kraken/database/objects/song.py](/src/music_kraken/database/objects/song.py) | Python | 258 | 52 | 76 | 386 |
| [src/music_kraken/database/objects/source.py](/src/music_kraken/database/objects/source.py) | Python | 46 | 7 | 13 | 66 |
| [src/music_kraken/database/new_database.py](/src/music_kraken/database/old_database.py) | Python | 402 | 110 | 107 | 619 |
| [src/music_kraken/database/objects/__init__.py](/src/music_kraken/objects/__init__.py) | Python | 15 | 0 | 5 | 20 |
| [src/music_kraken/database/objects/artist.py](/src/music_kraken/objects/artist.py) | Python | 18 | 0 | 5 | 23 |
| [src/music_kraken/database/objects/metadata.py](/src/music_kraken/objects/metadata.py) | Python | 245 | 52 | 50 | 347 |
| [src/music_kraken/database/objects/parents.py](/src/music_kraken/objects/parents.py) | Python | 46 | 8 | 23 | 77 |
| [src/music_kraken/database/objects/song.py](/src/music_kraken/objects/song.py) | Python | 258 | 52 | 76 | 386 |
| [src/music_kraken/database/objects/source.py](/src/music_kraken/objects/source.py) | Python | 46 | 7 | 13 | 66 |
| [src/music_kraken/database/song.py](/src/music_kraken/database/song.py) | Python | 125 | 20 | 45 | 190 |
| [src/music_kraken/database/temp_database.py](/src/music_kraken/database/temp_database.py) | Python | 12 | 0 | 8 | 20 |
| [src/music_kraken/lyrics/__init__.py](/src/music_kraken/lyrics/__init__.py) | Python | 0 | 0 | 1 | 1 |

View File

@ -11,7 +11,7 @@ Total : 2 files, 2 codes, 1 comments, 1 blanks, all 4 lines
## Files
| filename | language | code | comment | blank | total |
| :--- | :--- | ---: | ---: | ---: | ---: |
| [src/music_kraken/database/new_database.py](/src/music_kraken/database/database.py) | Python | 1 | 1 | 0 | 2 |
| [src/music_kraken/database/objects/__init__.py](/src/music_kraken/database/objects/__init__.py) | Python | 1 | 0 | 1 | 2 |
| [src/music_kraken/database/new_database.py](/src/music_kraken/database/old_database.py) | Python | 1 | 1 | 0 | 2 |
| [src/music_kraken/database/objects/__init__.py](/src/music_kraken/objects/__init__.py) | Python | 1 | 0 | 1 | 2 |
[Summary](results.md) / [Details](details.md) / [Diff Summary](diff.md) / Diff Details

View File

@ -17,14 +17,14 @@ Total : 50 files, 3575 codes, 775 comments, 1028 blanks, all 5378 lines
| [src/music_kraken/__init__.py](/src/music_kraken/__init__.py) | Python | 63 | 26 | 33 | 122 |
| [src/music_kraken/__main__.py](/src/music_kraken/__main__.py) | Python | 3 | 2 | 3 | 8 |
| [src/music_kraken/database/__init__.py](/src/music_kraken/database/__init__.py) | Python | 18 | 0 | 5 | 23 |
| [src/music_kraken/database/database.py](/src/music_kraken/database/database.py) | Python | 429 | 112 | 111 | 652 |
| [src/music_kraken/database/objects/__init__.py](/src/music_kraken/database/objects/__init__.py) | Python | 20 | 0 | 7 | 27 |
| [src/music_kraken/database/objects/artist.py](/src/music_kraken/database/objects/artist.py) | Python | 18 | 0 | 5 | 23 |
| [src/music_kraken/database/objects/formatted_text.py](/src/music_kraken/database/objects/formatted_text.py) | Python | 48 | 57 | 16 | 121 |
| [src/music_kraken/database/objects/metadata.py](/src/music_kraken/database/objects/metadata.py) | Python | 251 | 68 | 61 | 380 |
| [src/music_kraken/database/objects/parents.py](/src/music_kraken/database/objects/parents.py) | Python | 40 | 8 | 19 | 67 |
| [src/music_kraken/database/objects/song.py](/src/music_kraken/database/objects/song.py) | Python | 323 | 64 | 85 | 472 |
| [src/music_kraken/database/objects/source.py](/src/music_kraken/database/objects/source.py) | Python | 116 | 38 | 41 | 195 |
| [src/music_kraken/database/database.py](/src/music_kraken/database/old_database.py) | Python | 429 | 112 | 111 | 652 |
| [src/music_kraken/database/objects/__init__.py](/src/music_kraken/objects/__init__.py) | Python | 20 | 0 | 7 | 27 |
| [src/music_kraken/database/objects/artist.py](/src/music_kraken/objects/artist.py) | Python | 18 | 0 | 5 | 23 |
| [src/music_kraken/database/objects/formatted_text.py](/src/music_kraken/objects/formatted_text.py) | Python | 48 | 57 | 16 | 121 |
| [src/music_kraken/database/objects/metadata.py](/src/music_kraken/objects/metadata.py) | Python | 251 | 68 | 61 | 380 |
| [src/music_kraken/database/objects/parents.py](/src/music_kraken/objects/parents.py) | Python | 40 | 8 | 19 | 67 |
| [src/music_kraken/database/objects/song.py](/src/music_kraken/objects/song.py) | Python | 323 | 64 | 85 | 472 |
| [src/music_kraken/database/objects/source.py](/src/music_kraken/objects/source.py) | Python | 116 | 38 | 41 | 195 |
| [src/music_kraken/database/temp_database.py](/src/music_kraken/database/temp_database.py) | Python | 12 | 0 | 8 | 20 |
| [src/music_kraken/not_used_anymore/__init__.py](/src/music_kraken/not_used_anymore/__init__.py) | Python | 0 | 0 | 3 | 3 |
| [src/music_kraken/not_used_anymore/fetch_audio.py](/src/music_kraken/not_used_anymore/fetch_audio.py) | Python | 75 | 12 | 20 | 107 |

View File

@ -24,15 +24,15 @@ Total : 55 files, 171 codes, 111 comments, 54 blanks, all 336 lines
| [src/music_kraken/audio_source/sources/source.py](/src/music_kraken/audio_source/sources/source.py) | Python | -11 | -5 | -8 | -24 |
| [src/music_kraken/audio_source/sources/youtube.py](/src/music_kraken/audio_source/sources/youtube.py) | Python | -71 | -4 | -24 | -99 |
| [src/music_kraken/database/__init__.py](/src/music_kraken/database/__init__.py) | Python | 6 | -1 | 1 | 6 |
| [src/music_kraken/database/database.py](/src/music_kraken/database/database.py) | Python | 238 | 10 | 66 | 314 |
| [src/music_kraken/database/database.py](/src/music_kraken/database/old_database.py) | Python | 238 | 10 | 66 | 314 |
| [src/music_kraken/database/get_song.py](/src/music_kraken/database/get_song.py) | Python | -40 | -5 | -11 | -56 |
| [src/music_kraken/database/new_database.py](/src/music_kraken/database/new_database.py) | Python | -402 | -110 | -107 | -619 |
| [src/music_kraken/database/objects/__init__.py](/src/music_kraken/database/objects/__init__.py) | Python | 5 | 0 | 2 | 7 |
| [src/music_kraken/database/objects/formatted_text.py](/src/music_kraken/database/objects/formatted_text.py) | Python | 48 | 57 | 16 | 121 |
| [src/music_kraken/database/objects/metadata.py](/src/music_kraken/database/objects/metadata.py) | Python | 6 | 16 | 11 | 33 |
| [src/music_kraken/database/objects/parents.py](/src/music_kraken/database/objects/parents.py) | Python | -6 | 0 | -4 | -10 |
| [src/music_kraken/database/objects/song.py](/src/music_kraken/database/objects/song.py) | Python | 65 | 12 | 9 | 86 |
| [src/music_kraken/database/objects/source.py](/src/music_kraken/database/objects/source.py) | Python | 70 | 31 | 28 | 129 |
| [src/music_kraken/database/objects/__init__.py](/src/music_kraken/objects/__init__.py) | Python | 5 | 0 | 2 | 7 |
| [src/music_kraken/database/objects/formatted_text.py](/src/music_kraken/objects/formatted_text.py) | Python | 48 | 57 | 16 | 121 |
| [src/music_kraken/database/objects/metadata.py](/src/music_kraken/objects/metadata.py) | Python | 6 | 16 | 11 | 33 |
| [src/music_kraken/database/objects/parents.py](/src/music_kraken/objects/parents.py) | Python | -6 | 0 | -4 | -10 |
| [src/music_kraken/database/objects/song.py](/src/music_kraken/objects/song.py) | Python | 65 | 12 | 9 | 86 |
| [src/music_kraken/database/objects/source.py](/src/music_kraken/objects/source.py) | Python | 70 | 31 | 28 | 129 |
| [src/music_kraken/database/song.py](/src/music_kraken/database/song.py) | Python | -125 | -20 | -45 | -190 |
| [src/music_kraken/lyrics/__init__.py](/src/music_kraken/lyrics/__init__.py) | Python | 0 | 0 | -1 | -1 |
| [src/music_kraken/lyrics/genius.py](/src/music_kraken/lyrics/genius.py) | Python | -115 | -16 | -42 | -173 |

View File

@ -6,3 +6,7 @@ pydub~=0.25.1
youtube_dl
beautifulsoup4~=4.11.1
pycountry~=22.3.5
python-dateutil~=2.8.2
pandoc~=2.3
peewee~=3.15.4
setuptools~=60.2.0

View File

@ -17,7 +17,7 @@ from music_kraken.tagging import (
write_many_metadata
)
import music_kraken.database.database as db
import music_kraken.database.old_database as db
import pycountry
import logging

View File

@ -1,8 +1,8 @@
from . import (
temp_database,
objects,
database
old_database
)
from .. import objects
MusicObject = objects.MusicObject

View File

@ -0,0 +1,113 @@
from typing import List, Union, Type
from peewee import (
SqliteDatabase,
PostgresqlDatabase,
MySQLDatabase,
Model,
CharField,
IntegerField,
BooleanField,
ForeignKeyField,
TextField
)
class Album(Model):
"""A class representing an album in the music database."""
title: str = CharField()
label: str = CharField()
album_status: str = CharField()
language: str = CharField()
date: str = CharField()
date_format: str = CharField()
country: str = CharField()
barcode: str = CharField()
albumsort: int = IntegerField()
is_split: bool = BooleanField(default=False)
class Artist(Model):
"""A class representing an artist in the music database."""
name: str = CharField()
class Song(Model):
"""A class representing a song in the music database."""
name: str = CharField()
isrc: str = CharField()
length: int = IntegerField()
tracksort: int = IntegerField()
genre: str = CharField()
album: ForeignKeyField = ForeignKeyField(Album, backref='songs')
class Source(Model):
"""A class representing a source of a song in the music database."""
type: str = CharField()
src: str = CharField()
url: str = CharField()
content_type: str = CharField()
content_id: int = IntegerField()
content: ForeignKeyField = ForeignKeyField('self', backref='content_items', null=True)
@property
def content_object(self) -> Union[Song, Album, Artist, None]:
"""Get the content associated with the source as an object."""
if self.content_type == 'Song':
return Song.get(Song.id == self.content_id)
elif self.content_type == 'Album':
return Album.get(Album.id == self.content_id)
elif self.content_type == 'Artist':
return Artist.get(Artist.id == self.content_id)
else:
return None
@content_object.setter
def content_object(self, value: Union[Song, Album, Artist]) -> None:
"""Set the content associated with the source as an object."""
self.content_type = value.__class__.__name__
self.content_id = value.id
class Target(Model):
"""A class representing a target of a song in the music database."""
file: str = CharField()
path: str = CharField()
song = ForeignKeyField(Song, backref='targets')
class Lyrics(Model):
"""A class representing lyrics of a song in the music database."""
text: str = TextField()
language: str = CharField()
song = ForeignKeyField(Song, backref='lyrics')
class SongArtist(Model):
"""A class representing the relationship between a song and an artist."""
song: ForeignKeyField = ForeignKeyField(Song, backref='song_artists')
artist: ForeignKeyField = ForeignKeyField(Artist, backref='song_artists')
is_feature: bool = BooleanField(default=False)
class AlbumArtist(Model):
"""A class representing the relationship between an album and an artist."""
album: ForeignKeyField = ForeignKeyField(Album, backref='album_artists')
artist: ForeignKeyField = ForeignKeyField(Artist, backref='album_artists')
class Models:
def __init__(self, database: Union[SqliteDatabase, PostgresqlDatabase, MySQLDatabase]):
self.database: Union[SqliteDatabase, PostgresqlDatabase, MySQLDatabase] = database
def get_obj(self, _model: Model):
_model._meta.database = self.database

View File

@ -1,702 +1,79 @@
import sqlite3
import os
import logging
from typing import List, Tuple
from pkg_resources import resource_string
import datetime
import pycountry
from .objects.parents import Reference
from .objects.source import Source
from .objects import (
Song,
Lyrics,
Target,
Artist,
Album,
ID3Timestamp,
SourceTypes,
SourcePages,
SourceAttribute
from typing import Optional, Union
from enum import Enum
from peewee import (
SqliteDatabase,
MySQLDatabase,
PostgresqlDatabase,
)
"""
import peewee
from . import data_models
db = peewee.SqliteDatabase('music.db')
class BaseModel(peewee.Model):
class Meta:
database = db
class Artist(BaseModel):
name = peewee.CharField()
class Song(BaseModel):
title = peewee.CharField()
artist = peewee.ManyToManyField(Artist, backref='songs')
db.connect()
db.create_tables([Artist, Song, Song.artist.get_through_model()], safe=True)
# Adding a song and its artists
beatles = Artist.create(name='The Beatles')
rolling_stones = Artist.create(name='The Rolling Stones')
song = Song.create(title='Hey Jude')
song.artist.add(beatles, rolling_stones)
# Querying songs by artist
songs = Song.select().join(Song.artist).where(Artist.name == 'The Beatles')
for song in songs:
print(song.title)
"""
logger = logging.getLogger("database")
# Due to this not being deployed on a Server **HOPEFULLY**
# I don't need to parameterize stuff like the where and
# use complicated query builder
SONG_QUERY = """
SELECT
Song.id AS song_id, Song.name AS title, Song.isrc AS isrc, Song.length AS length, Song.album_id as album_id, Song.tracksort,
Target.id AS target_id, Target.file AS file, Target.path AS path, Song.genre AS genre
FROM Song
LEFT JOIN Target ON Song.id=Target.song_id
WHERE {where};
"""
SOURCE_QUERY = """
SELECT id, type, src, url, song_id
FROM Source
WHERE {where};
"""
LYRICS_QUERY = """
SELECT id, text, language, song_id
FROM Lyrics
WHERE {where};
"""
ALBUM_QUERY_UNJOINED = """
SELECT Album.id AS album_id, title, label, album_status, language, date, date_format, country, barcode, albumsort, is_split
FROM Album
WHERE {where};
"""
ALBUM_QUERY_JOINED = """
SELECT a.id AS album_id, a.title, a.label, a.album_status, a.language, a.date, a.date_format, a.country, a.barcode, a.albumsort, a.is_split
FROM Song
INNER JOIN Album a ON Song.album_id=a.id
WHERE {where};
"""
ARTIST_QUERY = """
SELECT id as artist_id, name as artist_name
FROM Artist
WHERE {where};
"""
class DatabaseType(Enum):
SQLITE = "sqlite"
POSTGRESQL = "postgresql"
MYSQL = "mysql"
class Database:
def __init__(self, database_file: str):
self.database_file: str = database_file
self.connection, self.cursor = self.reset_cursor()
database: Union[SqliteDatabase, PostgresqlDatabase, MySQLDatabase]
self.cursor = self.connection.cursor()
def __init__(
self,
db_type: DatabaseType,
db_name: str,
db_user: Optional[str] = None,
db_password: Optional[str] = None,
db_host: Optional[str] = None,
db_port: Optional[int] = None
):
self.db_type = db_type
self.db_name = db_name
self.db_user = db_user
self.db_password = db_password
self.db_host = db_host
self.db_port = db_port
def reset(self):
"""
Deletes all Data from the database if it exists
and resets the schema defined in self.structure_file
"""
logger.info(f"resetting the database")
self.initialize_database()
# deleting the database
del self.connection
del self.cursor
os.remove(self.database_file)
def create_database(self) -> Union[SqliteDatabase, PostgresqlDatabase, MySQLDatabase]:
"""Create a database instance based on the configured database type and parameters.
# newly creating the database
self.reset_cursor()
query = resource_string("music_kraken", "static_files/new_db.sql").decode('utf-8')
# fill the database with the schematic
self.cursor.executescript(query)
self.connection.commit()
def reset_cursor(self) -> Tuple[sqlite3.Connection, sqlite3.Cursor]:
self.connection = sqlite3.connect(self.database_file)
# This is necessary that fetching rows returns dicts instead of tuple
self.connection.row_factory = sqlite3.Row
self.cursor = self.connection.cursor()
return self.connection, self.cursor
def push_one(self, db_object: Song | Lyrics | Target | Artist | Source | Album):
if db_object.dynamic:
return
if type(db_object) == Song:
return self.push_song(song=db_object, pushed=set())
"""
if type(db_object) == Lyrics:
return self.push_lyrics(lyrics=db_object)
if type(db_object) == Target:
return self.push_target(target=db_object)
Returns:
The created database instance, or None if an invalid database type was specified.
"""
if type(db_object) == Artist:
return self.push_artist(artist=db_object, pushed=set())
# SQLITE
if self.db_type == DatabaseType.SQLITE:
return SqliteDatabase(self.db_name)
"""
if type(db_object) == Source:
# needs to have the property type_enum or type_str set
return self.push_source(source=db_object)
"""
if type(db_object) == Album:
return self.push_album(album=db_object, pushed=set())
logger.warning(f"type {type(db_object)} isn't yet supported by the db")
def push(self, db_object_list: List[Song | Lyrics | Target | Artist | Source | Album]):
"""
This function is used to Write the data of any db_object to the database
It syncs a whole list of db_objects to the database and is meant
as the primary method to add to the database.
:param db_object_list:
"""
for db_object in db_object_list:
self.push_one(db_object)
def push_album(self, album: Album, pushed: set):
table = "Album"
query = f"INSERT OR REPLACE INTO {table} (id, title, label, album_status, language, date, date_format, country, barcode, albumsort, is_split) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);"
if album.id in pushed:
return
pushed.add(album.id)
date_format, date = album.date.get_timestamp_w_format()
values = (
album.id,
album.title,
album.label,
album.album_status,
album.iso_639_2_language,
date,
date_format,
album.country,
album.barcode,
album.albumsort,
album.is_split
)
self.cursor.execute(query, values)
self.connection.commit()
for song in album.tracklist:
self.push_song(song, pushed=pushed)
for artist in album.artists:
self.push_artist_album(artist_ref=artist.reference, album_ref=album.reference)
self.push_artist(artist, pushed=pushed)
for source in album.source_list:
source.type_enum = SourceTypes.ALBUM
source.add_song(album)
self.push_source(source=source)
def push_song(self, song: Song, pushed: set):
if song.dynamic:
return
if song.id in pushed:
return
pushed.add(song.id)
# ADDING THE DATA FOR THE SONG OBJECT
"""
db_field - object attribute
-------------------------------
id - id
name - title
"""
table = "Song"
values = (
song.id,
song.title,
song.isrc,
song.length,
song.get_album_id(),
song.tracksort,
song.genre
)
query = f"INSERT OR REPLACE INTO {table} (id, name, isrc, length, album_id, tracksort, genre) VALUES (?, ?, ?, ?, ?, ?, ?);"
self.cursor.execute(query, values)
self.connection.commit()
# add sources
for source in song.source_list:
source.add_song(song)
source.type_enum = SourceTypes.SONG
self.push_source(source=source)
# add lyrics
for single_lyrics in song.lyrics:
single_lyrics.add_song(song)
self.push_lyrics(lyrics=single_lyrics)
# add target
song.target.add_song(song)
self.push_target(target=song.target)
for main_artist in song.main_artist_list:
self.push_artist_song(artist_ref=Reference(main_artist.id), song_ref=Reference(song.id), is_feature=False)
self.push_artist(artist=main_artist, pushed=pushed)
for feature_artist in song.feature_artist_list:
self.push_artist_song(artist_ref=Reference(feature_artist.id), song_ref=Reference(song.id), is_feature=True)
self.push_artist(artist=feature_artist, pushed=pushed)
if song.album is not None:
self.push_album(song.album, pushed=pushed)
def push_lyrics(self, lyrics: Lyrics):
if lyrics.dynamic:
return
table = "Lyrics"
query = f"INSERT OR REPLACE INTO {table} (id, song_id, text, language) VALUES (?, ?, ?, ?);"
values = (
lyrics.id,
lyrics.song_ref_id,
lyrics.text,
lyrics.language
# POSTGRES
if self.db_type == DatabaseType.POSTGRESQL:
return PostgresqlDatabase(
self.db_name,
user=self.db_user,
password=self.db_password,
host=self.db_host,
port=self.db_port,
)
self.cursor.execute(query, values)
self.connection.commit()
def push_source(self, source: Source):
if source.dynamic:
return
table = "Source"
query = f"INSERT OR REPLACE INTO {table} (id, type, song_id, src, url) VALUES (?, ?, ?, ?, ?);"
values = (
source.id,
source.type_str,
source.song_ref_id,
source.page_str,
source.url
# MYSQL
if self.db_type == DatabaseType.MYSQL:
return MySQLDatabase(
self.db_name,
user=self.db_user,
password=self.db_password,
host=self.db_host,
port=self.db_port,
)
self.cursor.execute(query, values)
self.connection.commit()
raise ValueError("define a Valid database type")
def push_target(self, target: Target):
if target.dynamic:
return
table = "Target"
query = f"INSERT OR REPLACE INTO {table} (id, song_id, file, path) VALUES (?, ?, ?, ?);"
values = (
target.id,
target.song_ref_id,
target.file,
target.path
)
self.cursor.execute(query, values)
self.connection.commit()
def push_artist_song(self, artist_ref: Reference, song_ref: Reference, is_feature: bool):
table = "SongArtist"
# checking if already exists
query = f"SELECT * FROM {table} WHERE song_id=\"{song_ref.id}\" AND artist_id=\"{artist_ref.id}\""
self.cursor.execute(query)
if len(self.cursor.fetchall()) > 0:
# join already exists
return
query = f"INSERT OR REPLACE INTO {table} (song_id, artist_id, is_feature) VALUES (?, ?, ?);"
values = (
song_ref.id,
artist_ref.id,
is_feature
)
self.cursor.execute(query, values)
self.connection.commit()
def push_artist_album(self, artist_ref: Reference, album_ref: Reference):
table = "AlbumArtist"
# checking if already exists
query = f"SELECT * FROM {table} WHERE album_id=\"{album_ref.id}\" AND artist_id=\"{artist_ref.id}\""
self.cursor.execute(query)
if len(self.cursor.fetchall()) > 0:
# join already exists
return
query = f"INSERT OR REPLACE INTO {table} (album_id, artist_id) VALUES (?, ?);"
values = (
album_ref.id,
artist_ref.id
)
self.cursor.execute(query, values)
self.connection.commit()
def push_artist(self, artist: Artist, pushed: set):
if artist.dynamic:
return
if artist.id in pushed:
return
pushed.add(artist.id)
table = "Artist"
query = f"INSERT OR REPLACE INTO {table} (id, name) VALUES (?, ?);"
values = (
artist.id,
artist.name
)
self.cursor.execute(query, values)
self.connection.commit()
for song in artist.feature_songs:
self.push_artist_song(artist_ref=artist.reference, song_ref=song.reference, is_feature=True)
self.push_song(song=song, pushed=pushed)
for song in artist.main_songs:
self.push_artist_song(artist_ref=artist.reference, song_ref=song.reference, is_feature=False)
self.push_song(song=song, pushed=pushed)
for album in artist.main_albums:
self.push_artist_album(artist_ref=artist.reference, album_ref=album.reference)
for source in artist.source_list:
source.type_enum = SourceTypes.ARTIST
source.add_song(artist)
self.push_source(source)
def pull_lyrics(self, song_ref: Reference = None, lyrics_ref: Reference = None) -> List[Lyrics]:
def initialize_database(self):
"""
Gets a list of sources. if lyrics_ref is passed in the List will most likely only
contain one Element if everything goes accordingly.
**If neither song_ref nor lyrics_ref are passed in it will return ALL lyrics**
:param song_ref:
:param lyrics_ref:
:return:
Connect to the database
initialize the previously defined databases
create tables if they don't exist.
"""
self.database = self.create_database()
where = "1=1"
if song_ref is not None:
where = f"song_id=\"{song_ref.id}\""
elif lyrics_ref is not None:
where = f"id=\"{lyrics_ref.id}\""
query = LYRICS_QUERY.format(where=where)
self.cursor.execute(query)
lyrics_rows = self.cursor.fetchall()
return [Lyrics(
id_=lyrics_row['id'],
text=lyrics_row['text'],
language=lyrics_row['language']
) for lyrics_row in lyrics_rows]
def pull_sources(self, artist_ref: Reference = None, song_ref: Reference = None, source_ref: Reference = None, album_ref: Reference = None) -> List[Source]:
"""
Gets a list of sources. if source_ref is passed in the List will most likely only
contain one Element if everything goes accordingly.
**If neither song_ref nor source_ref are passed in it will return ALL sources**
:param artist_ref:
:param song_ref:
:param source_ref:
:param type_str: the thing the source belongs to like eg. "song" or "album"
:return:
"""
where = "1=1"
if song_ref is not None:
where = f"song_id=\"{song_ref.id}\""
elif source_ref is not None:
where = f"id=\"{source_ref.id}\" AND type=\"{SourceTypes.SONG.value}\""
elif artist_ref is not None:
where = f"song_id=\"{artist_ref.id}\" AND type=\"{SourceTypes.ARTIST.value}\""
elif album_ref is not None:
where = f"song_id=\"{album_ref.id}\" AND type=\"{SourceTypes.ALBUM.value}\""
query = SOURCE_QUERY.format(where=where)
self.cursor.execute(query)
source_rows = self.cursor.fetchall()
return [
Source(
page_enum=SourcePages(source_row['src']),
type_enum=SourceTypes(source_row['type']),
url=source_row['url'],
id_=source_row['id']
) for source_row in source_rows
]
def pull_artist_song(self, song_ref: Reference = None, artist_ref: Reference = None) -> List[tuple]:
table = "SongArtist"
wheres = []
if song_ref is not None:
wheres.append(f"song_id=\"{song_ref.id}\"")
if artist_ref is not None:
wheres.append(f"artist_id=\"{artist_ref.id}\"")
where_str = ""
if len(wheres) > 0:
where_str = "WHERE " + " AND ".join(wheres)
query = f"SELECT * FROM {table} {where_str};"
self.cursor.execute(query)
joins = self.cursor.fetchall()
return [(
Reference(join["song_id"]),
Reference(join["artist_id"]),
bool(join["is_feature"])
) for join in joins]
def pull_artist_album(self, album_ref: Reference = None, artist_ref: Reference = None) -> List[tuple]:
table = "AlbumArtist"
wheres = []
if album_ref is not None:
wheres.append(f"album_id=\"{album_ref.id}\"")
if artist_ref is not None:
wheres.append(f"artist_id=\"{artist_ref.id}\"")
where_str = ""
if len(wheres) > 0:
where_str = "WHERE " + " AND ".join(wheres)
query = f"SELECT * FROM {table} {where_str};"
self.cursor.execute(query)
joins = self.cursor.fetchall()
return [(
Reference(join["album_id"]),
Reference(join["artist_id"])
) for join in joins]
def get_artist_from_row(self, artist_row, exclude_relations: set = None, flat: bool = False) -> Artist:
if exclude_relations is None:
exclude_relations = set()
new_exclude_relations: set = set(exclude_relations)
new_exclude_relations.add(Artist)
artist_id = artist_row['artist_id']
artist_obj = Artist(
id_=artist_id,
name=artist_row['artist_name'],
source_list=self.pull_sources(artist_ref=Reference(id_=artist_id))
)
if flat:
return artist_obj
# fetch songs :D
for song_ref, _, is_feature in self.pull_artist_song(artist_ref=Reference(id_=artist_id)):
new_songs = self.pull_songs(song_ref=song_ref, exclude_relations=new_exclude_relations)
if len(new_songs) < 1:
continue
new_song = new_songs[0]
if is_feature:
artist_obj.feature_songs.append(new_song)
else:
artist_obj.main_songs.append(new_song)
# fetch albums
for album_ref, _ in self.pull_artist_album(artist_ref=Reference(id_=artist_id)):
new_albums = self.pull_albums(album_ref=album_ref, exclude_relations=new_exclude_relations)
if len(new_albums) < 1:
continue
artist_obj.main_albums.append(new_albums[0])
return artist_obj
def pull_artists(self, artist_ref: Reference = None, exclude_relations: set = None, flat: bool = False) -> List[Artist]:
"""
:param artist_ref:
:param exclude_relations:
:param flat: if it is true it ONLY fetches the artist data
:return:
"""
where = "1=1"
if artist_ref is not None:
where = f"Artist.id=\"{artist_ref.id}\""
query = ARTIST_QUERY.format(where=where)
self.cursor.execute(query)
artist_rows = self.cursor.fetchall()
return [(
self.get_artist_from_row(artist_row, exclude_relations=exclude_relations, flat=flat)
) for artist_row in artist_rows]
def get_song_from_row(self, song_result, exclude_relations: set = None) -> Song:
if exclude_relations is None:
exclude_relations = set()
new_exclude_relations: set = set(exclude_relations)
new_exclude_relations.add(Song)
song_id = song_result['song_id']
# maybee fetch album
song_obj = Song(
id_=song_id,
title=song_result['title'],
isrc=song_result['isrc'],
length=song_result['length'],
tracksort=song_result['tracksort'],
genre=song_result['genre'],
target=Target(
id_=song_result['target_id'],
file=song_result['file'],
path=song_result['path']
),
source_list=self.pull_sources(song_ref=Reference(id_=song_id)),
lyrics=self.pull_lyrics(song_ref=Reference(id_=song_id)),
)
if Album not in exclude_relations and song_result['album_id'] is not None:
album_obj = self.pull_albums(album_ref=Reference(song_result['album_id']),
exclude_relations=new_exclude_relations)
if len(album_obj) > 0:
song_obj.album = album_obj[0]
flat_artist = Artist in exclude_relations
main_artists = []
feature_artists = []
for song_ref, artist_ref, is_feature in self.pull_artist_song(song_ref=Reference(song_id)):
if is_feature:
feature_artists.extend(self.pull_artists(artist_ref=artist_ref, flat=flat_artist))
else:
main_artists.extend(self.pull_artists(artist_ref=artist_ref, flat=flat_artist))
song_obj.main_artist_list = main_artists
song_obj.feature_artist_list = feature_artists
return song_obj
def pull_songs(self, song_ref: Reference = None, album_ref: Reference = None, exclude_relations: set = set()) -> \
List[Song]:
"""
This function is used to get one song (including its children like Sources etc)
from one song id (a reference object)
:param exclude_relations:
By default all relations are pulled by this funktion. If the class object of for
example the Artists is in the set it won't get fetched.
This is done to prevent an infinite recursion.
:param song_ref:
:param album_ref:
:return requested_song:
"""
where = "1=1"
if song_ref is not None:
where = f"Song.id=\"{song_ref.id}\""
elif album_ref is not None:
where = f"Song.album_id=\"{album_ref.id}\""
query = SONG_QUERY.format(where=where)
self.cursor.execute(query)
song_rows = self.cursor.fetchall()
return [self.get_song_from_row(
song_result=song_result,
exclude_relations=exclude_relations
) for song_result in song_rows]
def get_album_from_row(self, album_result, exclude_relations=None) -> Album:
if exclude_relations is None:
exclude_relations = set()
new_exclude_relations: set = exclude_relations.copy()
new_exclude_relations.add(Album)
album_id = album_result['album_id']
language = album_result['language']
if language is not None:
language = pycountry.languages.get(alpha_3=album_result['language'])
album_obj = Album(
id_=album_id,
title=album_result['title'],
label=album_result['label'],
album_status=album_result['album_status'],
language=language,
date=ID3Timestamp.strptime(album_result['date'], album_result['date_format']),
country=album_result['country'],
barcode=album_result['barcode'],
is_split=album_result['is_split'],
albumsort=album_result['albumsort'],
source_list=self.pull_sources(album_ref=Reference(id_=album_id))
)
if Song not in exclude_relations:
# getting the tracklist
tracklist: List[Song] = self.pull_songs(
album_ref=Reference(id_=album_id),
exclude_relations=new_exclude_relations
)
album_obj.set_tracklist(tracklist=tracklist)
flat_artist = Artist in exclude_relations
for _, artist_ref in self.pull_artist_album(album_ref=Reference(id_=album_id)):
artists = self.pull_artists(artist_ref, flat=flat_artist, exclude_relations=new_exclude_relations)
if len(artists) < 1:
continue
album_obj.artists.append(artists[0])
return album_obj
def pull_albums(self, album_ref: Reference = None, song_ref: Reference = None, exclude_relations: set = None) -> \
List[Album]:
"""
This function is used to get matching albums/releses
from one song id (a reference object)
:param exclude_relations:
By default all relations are pulled by this funktion. If the class object of for
example the Artists is in the set it won't get fetched.
This is done to prevent an infinite recursion.
:param album_ref:
:return requested_album_list:
"""
if exclude_relations is None:
exclude_relations = set()
query = ALBUM_QUERY_UNJOINED
where = "1=1"
if album_ref is not None:
query = ALBUM_QUERY_UNJOINED
where = f"Album.id=\"{album_ref.id}\""
elif song_ref is not None:
query = ALBUM_QUERY_JOINED
where = f"Song.id=\"{song_ref.id}\""
query = query.format(where=where)
self.cursor.execute(query)
album_rows = self.cursor.fetchall()
return [self.get_album_from_row(
album_result=album_row,
exclude_relations=exclude_relations
) for album_row in album_rows]
if __name__ == "__main__":
cache = Database("")
self.database.connect()

View File

@ -2,7 +2,7 @@ from collections import defaultdict
from typing import Dict, List, Optional
import weakref
from .objects import MusicObject
from src.music_kraken.objects import MusicObject
"""
This is a cache for the objects, that et pulled out of the database.

View File

@ -0,0 +1,700 @@
import sqlite3
import os
import logging
from typing import List, Tuple
from pkg_resources import resource_string
import pycountry
from src.music_kraken.objects.parents import Reference
from src.music_kraken.objects.source import Source
from src.music_kraken.objects import (
Song,
Lyrics,
Target,
Artist,
Album,
ID3Timestamp,
SourceTypes,
SourcePages
)
"""
import peewee
db = peewee.SqliteDatabase('music.db')
class BaseModel(peewee.Model):
class Meta:
database = db
class Artist(BaseModel):
name = peewee.CharField()
class Song(BaseModel):
title = peewee.CharField()
artist = peewee.ManyToManyField(Artist, backref='songs')
db.connect()
db.create_tables([Artist, Song, Song.artist.get_through_model()], safe=True)
# Adding a song and its artists
beatles = Artist.create(name='The Beatles')
rolling_stones = Artist.create(name='The Rolling Stones')
song = Song.create(title='Hey Jude')
song.artist.add(beatles, rolling_stones)
# Querying songs by artist
songs = Song.select().join(Song.artist).where(Artist.name == 'The Beatles')
for song in songs:
print(song.title)
"""
logger = logging.getLogger("database")
# Due to this not being deployed on a Server **HOPEFULLY**
# I don't need to parameterize stuff like the where and
# use complicated query builder
SONG_QUERY = """
SELECT
Song.id AS song_id, Song.name AS title, Song.isrc AS isrc, Song.length AS length, Song.album_id as album_id, Song.tracksort,
Target.id AS target_id, Target.file AS file, Target.path AS path, Song.genre AS genre
FROM Song
LEFT JOIN Target ON Song.id=Target.song_id
WHERE {where};
"""
SOURCE_QUERY = """
SELECT id, type, src, url, song_id
FROM Source
WHERE {where};
"""
LYRICS_QUERY = """
SELECT id, text, language, song_id
FROM Lyrics
WHERE {where};
"""
ALBUM_QUERY_UNJOINED = """
SELECT Album.id AS album_id, title, label, album_status, language, date, date_format, country, barcode, albumsort, is_split
FROM Album
WHERE {where};
"""
ALBUM_QUERY_JOINED = """
SELECT a.id AS album_id, a.title, a.label, a.album_status, a.language, a.date, a.date_format, a.country, a.barcode, a.albumsort, a.is_split
FROM Song
INNER JOIN Album a ON Song.album_id=a.id
WHERE {where};
"""
ARTIST_QUERY = """
SELECT id as artist_id, name as artist_name
FROM Artist
WHERE {where};
"""
class Database:
def __init__(self, database_file: str):
self.database_file: str = database_file
self.connection, self.cursor = self.reset_cursor()
self.cursor = self.connection.cursor()
def reset(self):
"""
Deletes all Data from the database if it exists
and resets the schema defined in self.structure_file
"""
logger.info(f"resetting the database")
# deleting the database
del self.connection
del self.cursor
os.remove(self.database_file)
# newly creating the database
self.reset_cursor()
query = resource_string("music_kraken", "static_files/new_db.sql").decode('utf-8')
# fill the database with the schematic
self.cursor.executescript(query)
self.connection.commit()
def reset_cursor(self) -> Tuple[sqlite3.Connection, sqlite3.Cursor]:
self.connection = sqlite3.connect(self.database_file)
# This is necessary that fetching rows returns dicts instead of tuple
self.connection.row_factory = sqlite3.Row
self.cursor = self.connection.cursor()
return self.connection, self.cursor
def push_one(self, db_object: Song | Lyrics | Target | Artist | Source | Album):
if db_object.dynamic:
return
if type(db_object) == Song:
return self.push_song(song=db_object, pushed=set())
"""
if type(db_object) == Lyrics:
return self.push_lyrics(lyrics=db_object)
if type(db_object) == Target:
return self.push_target(target=db_object)
"""
if type(db_object) == Artist:
return self.push_artist(artist=db_object, pushed=set())
"""
if type(db_object) == Source:
# needs to have the property type_enum or type_str set
return self.push_source(source=db_object)
"""
if type(db_object) == Album:
return self.push_album(album=db_object, pushed=set())
logger.warning(f"type {type(db_object)} isn't yet supported by the db")
def push(self, db_object_list: List[Song | Lyrics | Target | Artist | Source | Album]):
"""
This function is used to Write the data of any db_object to the database
It syncs a whole list of db_objects to the database and is meant
as the primary method to add to the database.
:param db_object_list:
"""
for db_object in db_object_list:
self.push_one(db_object)
def push_album(self, album: Album, pushed: set):
table = "Album"
query = f"INSERT OR REPLACE INTO {table} (id, title, label, album_status, language, date, date_format, country, barcode, albumsort, is_split) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);"
if album.id in pushed:
return
pushed.add(album.id)
date_format, date = album.date.get_timestamp_w_format()
values = (
album.id,
album.title,
album.label,
album.album_status,
album.iso_639_2_language,
date,
date_format,
album.country,
album.barcode,
album.albumsort,
album.is_split
)
self.cursor.execute(query, values)
self.connection.commit()
for song in album.tracklist:
self.push_song(song, pushed=pushed)
for artist in album.artists:
self.push_artist_album(artist_ref=artist.reference, album_ref=album.reference)
self.push_artist(artist, pushed=pushed)
for source in album.source_list:
source.type_enum = SourceTypes.ALBUM
source.add_song(album)
self.push_source(source=source)
def push_song(self, song: Song, pushed: set):
if song.dynamic:
return
if song.id in pushed:
return
pushed.add(song.id)
# ADDING THE DATA FOR THE SONG OBJECT
"""
db_field - object attribute
-------------------------------
id - id
name - title
"""
table = "Song"
values = (
song.id,
song.title,
song.isrc,
song.length,
song.get_album_id(),
song.tracksort,
song.genre
)
query = f"INSERT OR REPLACE INTO {table} (id, name, isrc, length, album_id, tracksort, genre) VALUES (?, ?, ?, ?, ?, ?, ?);"
self.cursor.execute(query, values)
self.connection.commit()
# add sources
for source in song.source_list:
source.add_song(song)
source.type_enum = SourceTypes.SONG
self.push_source(source=source)
# add lyrics
for single_lyrics in song.lyrics:
single_lyrics.add_song(song)
self.push_lyrics(lyrics=single_lyrics)
# add target
song.target.add_song(song)
self.push_target(target=song.target)
for main_artist in song.main_artist_list:
self.push_artist_song(artist_ref=Reference(main_artist.id), song_ref=Reference(song.id), is_feature=False)
self.push_artist(artist=main_artist, pushed=pushed)
for feature_artist in song.feature_artist_list:
self.push_artist_song(artist_ref=Reference(feature_artist.id), song_ref=Reference(song.id), is_feature=True)
self.push_artist(artist=feature_artist, pushed=pushed)
if song.album is not None:
self.push_album(song.album, pushed=pushed)
def push_lyrics(self, lyrics: Lyrics):
if lyrics.dynamic:
return
table = "Lyrics"
query = f"INSERT OR REPLACE INTO {table} (id, song_id, text, language) VALUES (?, ?, ?, ?);"
values = (
lyrics.id,
lyrics.song_ref_id,
lyrics.text,
lyrics.language
)
self.cursor.execute(query, values)
self.connection.commit()
def push_source(self, source: Source):
if source.dynamic:
return
table = "Source"
query = f"INSERT OR REPLACE INTO {table} (id, type, song_id, src, url) VALUES (?, ?, ?, ?, ?);"
values = (
source.id,
source.type_str,
source.song_ref_id,
source.page_str,
source.url
)
self.cursor.execute(query, values)
self.connection.commit()
def push_target(self, target: Target):
if target.dynamic:
return
table = "Target"
query = f"INSERT OR REPLACE INTO {table} (id, song_id, file, path) VALUES (?, ?, ?, ?);"
values = (
target.id,
target.song_ref_id,
target.file,
target.path
)
self.cursor.execute(query, values)
self.connection.commit()
def push_artist_song(self, artist_ref: Reference, song_ref: Reference, is_feature: bool):
table = "SongArtist"
# checking if already exists
query = f"SELECT * FROM {table} WHERE song_id=\"{song_ref.id}\" AND artist_id=\"{artist_ref.id}\""
self.cursor.execute(query)
if len(self.cursor.fetchall()) > 0:
# join already exists
return
query = f"INSERT OR REPLACE INTO {table} (song_id, artist_id, is_feature) VALUES (?, ?, ?);"
values = (
song_ref.id,
artist_ref.id,
is_feature
)
self.cursor.execute(query, values)
self.connection.commit()
def push_artist_album(self, artist_ref: Reference, album_ref: Reference):
table = "AlbumArtist"
# checking if already exists
query = f"SELECT * FROM {table} WHERE album_id=\"{album_ref.id}\" AND artist_id=\"{artist_ref.id}\""
self.cursor.execute(query)
if len(self.cursor.fetchall()) > 0:
# join already exists
return
query = f"INSERT OR REPLACE INTO {table} (album_id, artist_id) VALUES (?, ?);"
values = (
album_ref.id,
artist_ref.id
)
self.cursor.execute(query, values)
self.connection.commit()
def push_artist(self, artist: Artist, pushed: set):
if artist.dynamic:
return
if artist.id in pushed:
return
pushed.add(artist.id)
table = "Artist"
query = f"INSERT OR REPLACE INTO {table} (id, name) VALUES (?, ?);"
values = (
artist.id,
artist.name
)
self.cursor.execute(query, values)
self.connection.commit()
for song in artist.feature_songs:
self.push_artist_song(artist_ref=artist.reference, song_ref=song.reference, is_feature=True)
self.push_song(song=song, pushed=pushed)
for song in artist.main_songs:
self.push_artist_song(artist_ref=artist.reference, song_ref=song.reference, is_feature=False)
self.push_song(song=song, pushed=pushed)
for album in artist.main_albums:
self.push_artist_album(artist_ref=artist.reference, album_ref=album.reference)
for source in artist.source_list:
source.type_enum = SourceTypes.ARTIST
source.add_song(artist)
self.push_source(source)
def pull_lyrics(self, song_ref: Reference = None, lyrics_ref: Reference = None) -> List[Lyrics]:
"""
Gets a list of sources. if lyrics_ref is passed in the List will most likely only
contain one Element if everything goes accordingly.
**If neither song_ref nor lyrics_ref are passed in it will return ALL lyrics**
:param song_ref:
:param lyrics_ref:
:return:
"""
where = "1=1"
if song_ref is not None:
where = f"song_id=\"{song_ref.id}\""
elif lyrics_ref is not None:
where = f"id=\"{lyrics_ref.id}\""
query = LYRICS_QUERY.format(where=where)
self.cursor.execute(query)
lyrics_rows = self.cursor.fetchall()
return [Lyrics(
id_=lyrics_row['id'],
text=lyrics_row['text'],
language=lyrics_row['language']
) for lyrics_row in lyrics_rows]
def pull_sources(self, artist_ref: Reference = None, song_ref: Reference = None, source_ref: Reference = None, album_ref: Reference = None) -> List[Source]:
"""
Gets a list of sources. if source_ref is passed in the List will most likely only
contain one Element if everything goes accordingly.
**If neither song_ref nor source_ref are passed in it will return ALL sources**
:param artist_ref:
:param song_ref:
:param source_ref:
:param type_str: the thing the source belongs to like eg. "song" or "album"
:return:
"""
where = "1=1"
if song_ref is not None:
where = f"song_id=\"{song_ref.id}\""
elif source_ref is not None:
where = f"id=\"{source_ref.id}\" AND type=\"{SourceTypes.SONG.value}\""
elif artist_ref is not None:
where = f"song_id=\"{artist_ref.id}\" AND type=\"{SourceTypes.ARTIST.value}\""
elif album_ref is not None:
where = f"song_id=\"{album_ref.id}\" AND type=\"{SourceTypes.ALBUM.value}\""
query = SOURCE_QUERY.format(where=where)
self.cursor.execute(query)
source_rows = self.cursor.fetchall()
return [
Source(
page_enum=SourcePages(source_row['src']),
type_enum=SourceTypes(source_row['type']),
url=source_row['url'],
id_=source_row['id']
) for source_row in source_rows
]
def pull_artist_song(self, song_ref: Reference = None, artist_ref: Reference = None) -> List[tuple]:
table = "SongArtist"
wheres = []
if song_ref is not None:
wheres.append(f"song_id=\"{song_ref.id}\"")
if artist_ref is not None:
wheres.append(f"artist_id=\"{artist_ref.id}\"")
where_str = ""
if len(wheres) > 0:
where_str = "WHERE " + " AND ".join(wheres)
query = f"SELECT * FROM {table} {where_str};"
self.cursor.execute(query)
joins = self.cursor.fetchall()
return [(
Reference(join["song_id"]),
Reference(join["artist_id"]),
bool(join["is_feature"])
) for join in joins]
def pull_artist_album(self, album_ref: Reference = None, artist_ref: Reference = None) -> List[tuple]:
table = "AlbumArtist"
wheres = []
if album_ref is not None:
wheres.append(f"album_id=\"{album_ref.id}\"")
if artist_ref is not None:
wheres.append(f"artist_id=\"{artist_ref.id}\"")
where_str = ""
if len(wheres) > 0:
where_str = "WHERE " + " AND ".join(wheres)
query = f"SELECT * FROM {table} {where_str};"
self.cursor.execute(query)
joins = self.cursor.fetchall()
return [(
Reference(join["album_id"]),
Reference(join["artist_id"])
) for join in joins]
def get_artist_from_row(self, artist_row, exclude_relations: set = None, flat: bool = False) -> Artist:
if exclude_relations is None:
exclude_relations = set()
new_exclude_relations: set = set(exclude_relations)
new_exclude_relations.add(Artist)
artist_id = artist_row['artist_id']
artist_obj = Artist(
id_=artist_id,
name=artist_row['artist_name'],
source_list=self.pull_sources(artist_ref=Reference(id_=artist_id))
)
if flat:
return artist_obj
# fetch songs :D
for song_ref, _, is_feature in self.pull_artist_song(artist_ref=Reference(id_=artist_id)):
new_songs = self.pull_songs(song_ref=song_ref, exclude_relations=new_exclude_relations)
if len(new_songs) < 1:
continue
new_song = new_songs[0]
if is_feature:
artist_obj.feature_songs.append(new_song)
else:
artist_obj.main_songs.append(new_song)
# fetch albums
for album_ref, _ in self.pull_artist_album(artist_ref=Reference(id_=artist_id)):
new_albums = self.pull_albums(album_ref=album_ref, exclude_relations=new_exclude_relations)
if len(new_albums) < 1:
continue
artist_obj.main_albums.append(new_albums[0])
return artist_obj
def pull_artists(self, artist_ref: Reference = None, exclude_relations: set = None, flat: bool = False) -> List[Artist]:
"""
:param artist_ref:
:param exclude_relations:
:param flat: if it is true it ONLY fetches the artist data
:return:
"""
where = "1=1"
if artist_ref is not None:
where = f"Artist.id=\"{artist_ref.id}\""
query = ARTIST_QUERY.format(where=where)
self.cursor.execute(query)
artist_rows = self.cursor.fetchall()
return [(
self.get_artist_from_row(artist_row, exclude_relations=exclude_relations, flat=flat)
) for artist_row in artist_rows]
def get_song_from_row(self, song_result, exclude_relations: set = None) -> Song:
if exclude_relations is None:
exclude_relations = set()
new_exclude_relations: set = set(exclude_relations)
new_exclude_relations.add(Song)
song_id = song_result['song_id']
# maybee fetch album
song_obj = Song(
id_=song_id,
title=song_result['title'],
isrc=song_result['isrc'],
length=song_result['length'],
tracksort=song_result['tracksort'],
genre=song_result['genre'],
target=Target(
id_=song_result['target_id'],
file=song_result['file'],
path=song_result['path']
),
source_list=self.pull_sources(song_ref=Reference(id_=song_id)),
lyrics=self.pull_lyrics(song_ref=Reference(id_=song_id)),
)
if Album not in exclude_relations and song_result['album_id'] is not None:
album_obj = self.pull_albums(album_ref=Reference(song_result['album_id']),
exclude_relations=new_exclude_relations)
if len(album_obj) > 0:
song_obj.album = album_obj[0]
flat_artist = Artist in exclude_relations
main_artists = []
feature_artists = []
for song_ref, artist_ref, is_feature in self.pull_artist_song(song_ref=Reference(song_id)):
if is_feature:
feature_artists.extend(self.pull_artists(artist_ref=artist_ref, flat=flat_artist))
else:
main_artists.extend(self.pull_artists(artist_ref=artist_ref, flat=flat_artist))
song_obj.main_artist_list = main_artists
song_obj.feature_artist_list = feature_artists
return song_obj
def pull_songs(self, song_ref: Reference = None, album_ref: Reference = None, exclude_relations: set = set()) -> \
List[Song]:
"""
This function is used to get one song (including its children like Sources etc)
from one song id (a reference object)
:param exclude_relations:
By default all relations are pulled by this funktion. If the class object of for
example the Artists is in the set it won't get fetched.
This is done to prevent an infinite recursion.
:param song_ref:
:param album_ref:
:return requested_song:
"""
where = "1=1"
if song_ref is not None:
where = f"Song.id=\"{song_ref.id}\""
elif album_ref is not None:
where = f"Song.album_id=\"{album_ref.id}\""
query = SONG_QUERY.format(where=where)
self.cursor.execute(query)
song_rows = self.cursor.fetchall()
return [self.get_song_from_row(
song_result=song_result,
exclude_relations=exclude_relations
) for song_result in song_rows]
def get_album_from_row(self, album_result, exclude_relations=None) -> Album:
if exclude_relations is None:
exclude_relations = set()
new_exclude_relations: set = exclude_relations.copy()
new_exclude_relations.add(Album)
album_id = album_result['album_id']
language = album_result['language']
if language is not None:
language = pycountry.languages.get(alpha_3=album_result['language'])
album_obj = Album(
id_=album_id,
title=album_result['title'],
label=album_result['label'],
album_status=album_result['album_status'],
language=language,
date=ID3Timestamp.strptime(album_result['date'], album_result['date_format']),
country=album_result['country'],
barcode=album_result['barcode'],
is_split=album_result['is_split'],
albumsort=album_result['albumsort'],
source_list=self.pull_sources(album_ref=Reference(id_=album_id))
)
if Song not in exclude_relations:
# getting the tracklist
tracklist: List[Song] = self.pull_songs(
album_ref=Reference(id_=album_id),
exclude_relations=new_exclude_relations
)
album_obj.set_tracklist(tracklist=tracklist)
flat_artist = Artist in exclude_relations
for _, artist_ref in self.pull_artist_album(album_ref=Reference(id_=album_id)):
artists = self.pull_artists(artist_ref, flat=flat_artist, exclude_relations=new_exclude_relations)
if len(artists) < 1:
continue
album_obj.artists.append(artists[0])
return album_obj
def pull_albums(self, album_ref: Reference = None, song_ref: Reference = None, exclude_relations: set = None) -> \
List[Album]:
"""
This function is used to get matching albums/releses
from one song id (a reference object)
:param exclude_relations:
By default all relations are pulled by this funktion. If the class object of for
example the Artists is in the set it won't get fetched.
This is done to prevent an infinite recursion.
:param album_ref:
:return requested_album_list:
"""
if exclude_relations is None:
exclude_relations = set()
query = ALBUM_QUERY_UNJOINED
where = "1=1"
if album_ref is not None:
query = ALBUM_QUERY_UNJOINED
where = f"Album.id=\"{album_ref.id}\""
elif song_ref is not None:
query = ALBUM_QUERY_JOINED
where = f"Song.id=\"{song_ref.id}\""
query = query.format(where=where)
self.cursor.execute(query)
album_rows = self.cursor.fetchall()
return [self.get_album_from_row(
album_result=album_row,
exclude_relations=exclude_relations
) for album_row in album_rows]
if __name__ == "__main__":
cache = Database("")

View File

@ -1,4 +1,4 @@
from .database import Database
from .old_database import Database
from ..utils.shared import (
TEMP_DATABASE_PATH,

View File

@ -1,4 +1,4 @@
from ...utils.shared import (
from src.music_kraken.utils.shared import (
DATABASE_LOGGER as logger
)
from .parents import (

View File

@ -1,7 +1,7 @@
from typing import List
from .source import SourceAttribute
from ...utils import string_processing
from src.music_kraken.utils import string_processing
class Collection:
"""

View File

@ -1,6 +1,6 @@
import uuid
from ...utils.shared import (
from src.music_kraken.utils.shared import (
SONG_LOGGER as logger
)

View File

@ -8,7 +8,7 @@ from .metadata import (
ID3Timestamp,
MetadataAttribute
)
from ...utils.shared import (
from src.music_kraken.utils.shared import (
MUSIC_DIR,
DATABASE_LOGGER as logger
)

View File

@ -0,0 +1,41 @@
from typing import Optional
class TreeNode:
"""A class representing a binary tree node."""
def __init__(self, val: int = 0, left: Optional['TreeNode'] = None, right: Optional['TreeNode'] = None):
"""
Initializes a new instance of the TreeNode class.
Args:
val: The value of the node.
left: The left child of the node.
right: The right child of the node.
"""
self.val = val
self.left = left
self.right = right
def invert_tree(root: Optional[TreeNode]) -> Optional[TreeNode]:
"""
Inverts a binary tree.
Args:
root: The root node of the binary tree.
Returns:
The root node of the inverted binary tree.
"""
if root is None:
return None
# Swap left and right children of the root node
root.left, root.right = root.right, root.left
# Recursively invert the left and right subtrees
invert_tree(root.left)
invert_tree(root.right)
return root