diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..13ae728 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +songs.db + diff --git a/README.md b/README.md index e69de29..0f57a87 100644 --- a/README.md +++ b/README.md @@ -0,0 +1,37 @@ +Google Play Music Library Download += + +This script will download your whole google play music library. Each downloaded song is saved into a database, so if the script fails while running, you won't be downloading songs twice. If a song fails downloading, it will be saved to database with downloaded equals to 0: you should try downloading this again manually. + +The generated directory structure is of the format: + +`[album_artist | artist]/album_name` + +And inside each album, you will get: + +`track_id - track_name` + +Usage +- + +- In a terminal run: + +`python download_library.py` + +- You will get a link to login with the google account where all the music is, google will give you a string that you have to copy and then paste it back into the terminal and hit enter. + +- Let it r(b)u(r)n. + + +Dependencies +- + +- Python >= v3.6 + +### Packages + +For this program to work you will need to (virtual environment recommended) install: + +- (pip install) sqlite3 +- (pip install) gmusicapi + diff --git a/download_library.py b/download_library.py index df29bfe..e59096f 100644 --- a/download_library.py +++ b/download_library.py @@ -6,80 +6,108 @@ from gmusicapi import Musicmanager from os import path -logger = logging.getLogger(__name__) - - -def save_log(text): - log_file = open("log", "a") - log_file.write(text) - log_file.close() - - -def create_db(): +def start_db(): try: sqliteConnection = sqlite3.connect('songs.db') - sqlite_create_table_query = '''CREATE TABLE success_ids ( + sqlite_create_table_query = '''CREATE TABLE IF NOT EXISTS song ( id INTEGER PRIMARY KEY, - google_id TEXT NOT NULL;''' + google_id TEXT NOT NULL, + downloaded INTEGER);''' cursor = sqliteConnection.cursor() cursor.execute(sqlite_create_table_query) - sqlite_create_table_query = '''CREATE TABLE failure_ids ( - id INTEGER PRIMARY KEY, - google_id TEXT NOT NULL;''' - cursor.execute(sqlite_create_table_query) - sqliteConnection.commit() cursor.close() except sqlite3.Error as exc: - logger.error(f'Error creating the database: {exc}') + return + return sqliteConnection -mm = Musicmanager() -mm.perform_oauth() -mm.login() -songs = mm.get_uploaded_songs(incremental=False) +def insert_song(connection, song_id, failure=False): + sql = ''' INSERT INTO song (google_id, downloaded) + VALUES(?,?) ''' + cursor = connection.cursor() + cursor.execute(sql, (song_id, int(not failure))) + connection.commit() + lastrowid = cursor.lastrowid + cursor.close() -logger.info(f'There\'s {len(songs)} songs to download . . .') + return lastrowid -for song in songs: - if song['album_artist']: - level_one = song['album_artist'] - elif song['artist']: - level_one = song['artist'] - else: - level_one = 'Unknown Artist' - if song['album']: - level_two = song['album'] - else: - level_two = 'Unknown Album' - full_path = f'{level_one}/{level_two}' - try: - os.mkdir(level_one) - except FileExistsError: - pass - except Exception as exc: - save_log(f"\n\n{song} \n {exc} \n\n") - continue - try: - os.mkdir(full_path) - except FileExistsError: - pass - except Exception as exc: - save_log(f"\n\n{song} \n {exc} \n\n") - continue - try: - downloaded_song = mm.download_song(song['id']) - except Exception as exc: - save_log(f"\n\n{song} \n {exc} \n\n") - full_path += f"/{downloaded_song[0]}" - file_path = path.relpath(full_path) - file_to_write = open(file_path, "wb") - file_to_write.write(downloaded_song[1]) - file_to_write.close() +def get_all_song_ids(connection): + sql = ''' SELECT google_id from song WHERE downloaded = 1;''' + + cursor = connection.cursor() + cursor.execute(sql) + results = cursor.fetchall() + return {r[0] for r in results} + + +def main(): + mm = Musicmanager() + mm.perform_oauth() + mm.login() + songs = mm.get_uploaded_songs(incremental=False) + + db_connection = start_db() + already_downloaded = get_all_song_ids(db_connection) + + + for song in songs: + song_id = song['id'] + + if song_id in already_downloaded: + continue + + if song['album_artist']: + level_one = song['album_artist'] + elif song['artist']: + level_one = song['artist'] + else: + level_one = 'Unknown Artist' + + if song['album']: + level_two = song['album'] + else: + level_two = 'Unknown Album' + + full_path = f'{level_one}/{level_two}' + + try: + os.mkdir(level_one) + except FileExistsError: + pass + except Exception as exc: + insert_song(db_connection, song_id, True) + continue + + try: + os.mkdir(full_path) + except FileExistsError: + pass + except Exception as exc: + insert_song(db_connection, song_id, True) + continue + + try: + downloaded_song = mm.download_song(song_id) + except Exception as exc: + insert_song(db_connection, song_id, True) + + full_path += f"/{downloaded_song[0]}" + file_path = path.relpath(full_path) + + file_to_write = open(file_path, "wb") + file_to_write.write(downloaded_song[1]) + file_to_write.close() + insert_song(db_connection, song_id) + + +if __name__ == "__main__": + main()