Skip to content

Commit

Permalink
Merge branch 'main' into redditEDM
Browse files Browse the repository at this point in the history
  • Loading branch information
sigma67 committed Nov 23, 2024
2 parents b72276a + 04d0084 commit c04abc3
Show file tree
Hide file tree
Showing 8 changed files with 60 additions and 15 deletions.
3 changes: 2 additions & 1 deletion .github/workflows/coverage.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,9 @@ jobs:
coverage run
coverage xml
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v3
uses: codecov/codecov-action@v4
with:
file: coverage.xml
flags: unittests
fail_ci_if_error: true
token: ${{ secrets.CODECOV_TOKEN }}
16 changes: 12 additions & 4 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -27,20 +27,28 @@ spotify_to_ytmusic
A simple command line script to clone a Spotify playlist to YouTube Music.

- Transfer a single Spotify playlist
- Like all the songs in a Spotify playlist
- Update a transferred playlist on YouTube Music
- Transfer all playlists for a Spotify user
- Like all songs from all playlists for a Spotify user
- Remove playlists from YouTube Music


Install
-------

- Python 3 and pip - https://www.python.org
- Install:
- Python 3 - https://www.python.org
- pipx - https://pipx.pypa.io

.. code-block::
pip install spotify_to_ytmusic
pipx ensurepath
- Open a new shell. Install:

.. code-block::
pipx install spotify_to_ytmusic
Setup
Expand Down Expand Up @@ -83,7 +91,7 @@ For migration purposes, it is possible to transfer all public playlists of a use
Transfer liked tracks of the Spotify user
-----------------------------------------

**You must you oAuth authentication for transferring liked songs.**
**You must use oAuth authentication for transferring liked songs.**

.. code-block::
Expand Down
6 changes: 6 additions & 0 deletions spotify_to_ytmusic/controllers.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@ def all(args):
"PUBLIC" if p["public"] else "PRIVATE",
videoIds,
)
if args.like:
for id in videoIds:
ytmusic.rate_song(id, "LIKE")
_print_success(p["name"], playlist_id)
except Exception as ex:
print(f"Could not transfer playlist {p['name']}. {str(ex)}")
Expand All @@ -59,6 +62,9 @@ def _create_ytmusic(args, playlist, ytmusic):
name = args.name + date if args.name else playlist["name"] + date
info = playlist["description"] if (args.info is None) else args.info
videoIds = ytmusic.search_songs(playlist["tracks"])
if args.like:
for id in videoIds:
ytmusic.rate_song(id, "LIKE")

playlistId = ytmusic.create_playlist(
name, info, "PUBLIC" if args.public else "PRIVATE", videoIds
Expand Down
12 changes: 12 additions & 0 deletions spotify_to_ytmusic/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,12 @@ def get_args(args=None):
action="store_true",
help="Make created playlist public. Default: private",
)
spotify_playlist_create.add_argument(
"-l",
"--like",
action="store_true",
help="Like the songs in the specified playlist",
)

create_parser = subparsers.add_parser(
"create",
Expand Down Expand Up @@ -84,6 +90,12 @@ def get_args(args=None):
)
all_parser.add_argument("user", type=str, help="Spotify userid of the specified user.")
all_parser.set_defaults(func=controllers.all)
all_parser.add_argument(
"-l",
"--like",
action="store_true",
help="Like the songs in all of the public playlist",
)

top_parser = subparsers.add_parser("top")
top_parser.set_defaults(func=top.top)
Expand Down
1 change: 1 addition & 0 deletions spotify_to_ytmusic/settings.ini.example
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
[youtube]
headers = headers_json_from_browser
user_id =
auth_type = browser

[spotify]
client_id = id_from_developer_console
Expand Down
18 changes: 15 additions & 3 deletions spotify_to_ytmusic/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,9 @@ def setup(file: Optional[Path] = None):

if not DEFAULT_PATH.is_file():
shutil.copy(EXAMPLE_PATH, DEFAULT_PATH)
choice = input("Choose which API to set up\n" "(1) Spotify\n" "(2) YouTube\n" "(3) both\n" "(4) reddit\n")
choices = ["1", "2", "3", "4"]
choice = input("Choose which API to set up\n(1) Spotify\n(2) YouTube (oAuth)\n(3) Youtube (Browser)\n(4) both \n" "(5) reddit\n")

choices = ["1", "2", "3", "4", "5"]
if choice not in choices:
sys.exit("Invalid choice")

Expand All @@ -27,16 +28,27 @@ def setup(file: Optional[Path] = None):
elif choice == choices[1]:
setup_youtube()
elif choice == choices[2]:
setup_youtube_browser()
elif choice == choices[3]:
setup_spotify()
setup_youtube()
elif choice == choices[3]:
elif choice == choices[4]:
setup_reddit()


def setup_youtube():
settings = Settings()
credentials = ytmusicapi.setup_oauth(open_browser=has_browser())
settings["youtube"]["headers"] = json.dumps(credentials.as_dict())
settings["youtube"]["auth_type"] = "oauth"
settings.save()

def setup_youtube_browser():
settings = Settings()
print('Please see https://ytmusicapi.readthedocs.io/en/stable/setup/browser.html for instructions.')
credentials = ytmusicapi.setup()
settings["youtube"]["headers"] = credentials
settings["youtube"]["auth_type"] = "browser"
settings.save()


Expand Down
17 changes: 11 additions & 6 deletions spotify_to_ytmusic/ytmusic.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,23 +6,28 @@

from spotify_to_ytmusic.utils.match import get_best_fit_song_id
from spotify_to_ytmusic.settings import Settings
from spotify_to_ytmusic.setup import setup_youtube_browser

path = os.path.dirname(os.path.realpath(__file__)) + os.sep


class YTMusicTransfer:
def __init__(self):
settings = Settings()
headers = settings["youtube"]["headers"]
self.settings = Settings()
headers = self.settings["youtube"]["headers"]
assert headers.startswith("{"), "ytmusicapi headers not set or invalid"
self.api = YTMusic(headers, settings["youtube"]["user_id"])
self.api = YTMusic(headers, self.settings["youtube"]["user_id"])

def create_playlist(self, name, info, privacy="PRIVATE", tracks=None):
print(info)
print(privacy)
print(tracks)
if self.settings["youtube"]["auth_type"] == "browser":
setup_youtube_browser()
self.api = YTMusic(self.settings["youtube"]["headers"], self.settings["youtube"]["user_id"])

return self.api.create_playlist(name, info, privacy, video_ids=tracks)

def rate_song(self, id, rating):
return self.api.rate_song(id, rating)

def search_songs(self, tracks):
videoIds = []
songs = list(tracks)
Expand Down
2 changes: 1 addition & 1 deletion tests/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ def test_setup(self):
tmp_path = DEFAULT_PATH.with_suffix(".tmp")
with (
mock.patch("sys.argv", ["", "setup"]),
mock.patch("builtins.input", side_effect=["3", "a", "b", "yes", ""]),
mock.patch("builtins.input", side_effect=["4", "a", "b", "yes", ""]),
mock.patch(
"ytmusicapi.auth.oauth.credentials.OAuthCredentials.token_from_code",
return_value=json.loads(Settings()["youtube"]["headers"]),
Expand Down

0 comments on commit c04abc3

Please sign in to comment.