simon-weber / Google-Music-Playlist-Importer

a script for importing plaintext playlists into Google Music.
BSD 3-Clause "New" or "Revised" License
16 stars 5 forks source link

Import from a directory of playlists #4

Open knichel opened 10 years ago

knichel commented 10 years ago

I have been working on modifying the script to accept a directory of playlist files. If I make a playlist and run it through the original script, it works. If I copy it to a second file with a different name and run it through my version it works and imports both playlists. However if I I export 2 separate playlists, it works on the first but fails on the second with this error right after the print "Matching Songs..." Traceback (most recent call last): File "test_gm_playlists_importer.py", line 145, in main() File "test_gm_playlists_importer.py", line 115, in main matched_songs = matcher.match(queries) File "/usr/local/lib/python2.7/dist-packages/gmusicapi/gmtools/tools.py", line 393, in match res = self.query_library(query, tie_breaker, auto=auto) File "/usr/local/lib/python2.7/dist-packages/gmusicapi/gmtools/tools.py", line 311, in query_library next_results = self.query_library(query, tie_breaker, current_mods, auto) File "/usr/local/lib/python2.7/dist-packages/gmusicapi/gmtools/tools.py", line 311, in query_library next_results = self.query_library(query, tie_breaker, current_mods, auto) File "/usr/local/lib/python2.7/dist-packages/gmusicapi/gmtools/tools.py", line 309, in query_library raise self.TieBroken(tie_breaker(query, results)) TypeError: 'staticmethod' object is not callable

My code is as follows

#!/home/simon/programming/python/Google-Music-Playlist-Importer/test/bin/python

"""Python script to import local playlists to Google Music."""

import os
import re
import sys
import codecs
from getpass import getpass

import chardet
import gmusicapi.gmtools.tools as gm_tools
from gmusicapi import Mobileclient

def init(max_attempts=3):
    """Makes an instance of the api and attempts to login with it.
    Returns the api after at most max_attempts.

    :param max_attempts:
    """

    api = Mobileclient()

    logged_in = False
    attempts = 0

    print "Log in to Google Music."

    while not logged_in and attempts < max_attempts:
        email = raw_input("Email: ")
        password = getpass()

        logged_in = api.login(email, password)
        attempts += 1

    return api

def guess_encoding(filename):
    """Returns a tuple of (guessed encoding, confidence).

    :param filename:
    """

    res = chardet.detect(open(filename).read())
    return (res['encoding'], res['confidence'])

def main():

    if not len(sys.argv) == 2:
        # -- ORIGINAL --print "usage:", sys.argv[0], "<playlist file>"
        print "usage:", sys.argv[0], "<playlist folder>"
        sys.exit(0)

    #The three md_ lists define the format of the playlist and how matching should be done against the library.
    #They must all have the same number of elements.

    #Where this pattern matches, a query will be formed from the captures.
    #My example matches against a playlist file with lines like:
    # /home/simon/music/library/The Cat Empire/Live on Earth/The Car Song.mp3
    #Make sure it won't match lines that don't contain song info!
    #md_pattern = r"^/home/simon/music/library/(.*)/(.*)/(.*)\..*$"
    md_pattern = r"^File.*/.*/.*/(.*)/(.*)/(.*)\..*$"

    #Identify what metadata each capture represents.
    #These need to be valid fields in the GM json - see protocol_info in the api repo.
    md_cap_types = ('artist', 'album', 'title')

    #The lower-better priority of the capture types above.
    #In this example, I order title first, then artist, then album.
    md_cap_pr = (1,2,3)

    #Build queries from the playlist.
    # -- ORIGINAL --playlist_fname = sys.argv[1]
    #Convert playlist_fname to use a loop/list of files
    playlist_folderName = sys.argv[1]

    api = init()

    if not api.is_authenticated():
        print "Failed to log in."
        sys.exit(0)

    print "Loading library from Google Music..."
    library = api.get_all_songs()

# from here down we can loop over the list of playlists
### Start Loop

    for playlist_name in os.listdir(playlist_folderName):
        #print playlist_fname
        #playlist_fname = playlist_folderName + '/' + playlist_name
        playlist_fname = os.path.join(playlist_folderName, playlist_name)
        print playlist_fname
        pl_encoding, confidence = guess_encoding(playlist_fname)

        queries = None
        with codecs.open(playlist_fname, encoding=pl_encoding, mode='r') as f:
            queries = gm_tools.build_queries_from(f,
                                        re.compile(md_pattern),
                                        md_cap_types,
                                        md_cap_pr,
                                        pl_encoding)

        print "Matching songs..."

        matcher = gm_tools.SongMatcher(library)
        matched_songs = matcher.match(queries)

        res = raw_input("Output matches to file or terminal? (f/t): ")

        if res == "t":
            print matcher.build_log()
        elif res == "f":
            res = raw_input("Filename to write to: ")
            with open(res, mode='w') as f:
                f.write(matcher.build_log())
            print "File written."

        go = raw_input("Create playlist from these matches? (y/n): ")
        if go == "y":
            name = raw_input("playlist name: ")
            p_id = api.create_playlist(name)

            print "Made playlist", name

            res = api.add_songs_to_playlist(p_id,
                                    map(gm_tools.filter_song_md, matched_songs))
            print "Added songs."

    ### End Loop

if __name__ == '__main__':
    main()

simon-weber commented 10 years ago

I think I'm going to do a quick overhaul on this script while on the train =)

I'll definitely include this feature, but probably in the form of multiple input files, so you could do something like $ script.py my-dir/*.

knichel commented 10 years ago

My implementation simply took the folder as the input and anything in the folder is used.

On Fri, Oct 11, 2013 at 3:07 PM, Simon Weber notifications@github.comwrote:

I think I'm going to do a quick overhaul on this script while on the train =)

I'll definitely include this feature, but probably in the form of multiple input files, so you could do something like $ script.py my-dir/*.

— Reply to this email directly or view it on GitHubhttps://github.com/simon-weber/Google-Music-Playlist-Importer/issues/4#issuecomment-26163384 .

Michael Knichel

knichel commented 10 years ago

I have started working with this again after having to step away for a while. I am now trying to get the single import to work and I keep getting 0 matches. Can you look to see where I might have an inssue?

"""Python script to import local playlists to Google Music."""

import re
import sys
import codecs
from getpass import getpass

import chardet
import gmusicapi.gmtools.tools as gm_tools
from gmusicapi import Mobileclient

def init(max_attempts=3):
    """Makes an instance of the api and attempts to login with it.
    Returns the api after at most max_attempts.

    :param max_attempts:
    """

    api = Mobileclient()

    logged_in = False
    attempts = 0

    print "Log in to Google Music."

    while not logged_in and attempts < max_attempts:
        email = raw_input("Email: ")
        password = getpass()
        logged_in = api.login(email, password)
        attempts += 1

    return api

def guess_encoding(filename):
    """Returns a tuple of (guessed encoding, confidence).

    :param filename:
    """

    res = chardet.detect(open(filename).read())
    return (res['encoding'], res['confidence'])

def main():

    if not len(sys.argv) == 2:
        print "usage:", sys.argv[0], "<playlist file>"
        sys.exit(0)

    #The three md_ lists define the format of the playlist and how matching
should be done against the library.
    #They must all have the same number of elements.

    #Where this pattern matches, a query will be formed from the captures.
    #My example matches against a playlist file with lines like:
    # /home/simon/music/library/The Cat Empire/Live on Earth/The Car
Song.mp3

    #Make sure it won't match lines that don't contain song info!
    #File4=file:///backup/MUSIC/Asia/Asia/One Step Closer.mp3
    md_pattern = r"^File{[0-9]+}=file:///backup/MUSIC/(.*)/(.*)/(.*)\..*$"

    #Identify what metadata each capture represents.
    #These need to be valid fields in the GM json - see protocol_info in
the api repo.
    md_cap_types = ('artist', 'album', 'title')

    #The lower-better priority of the capture types above.
    #In this example, I order title first, then artist, then album.
    md_cap_pr = (2,3,1)

    #Build queries from the playlist.
    playlist_fname = sys.argv[1]
    pl_encoding, confidence = guess_encoding(playlist_fname)

    queries = None
    with codecs.open(playlist_fname, encoding=pl_encoding, mode='r') as f:
        queries = gm_tools.build_queries_from(f,
                                     re.compile(md_pattern),
                                     md_cap_types,
                                     md_cap_pr,
                                     pl_encoding)

    api = init()

    if not api.is_authenticated():
        print "Failed to log in."
        sys.exit(0)

    print "Loading library from Google Music..."
    library = api.get_all_songs()

    print "Matching songs..."

    matcher = gm_tools.SongMatcher(library)

    matched_songs = matcher.match(queries)

    res = raw_input("Output matches to file or terminal? (f/t): ")

    if res == "t":
        print matcher.build_log()
    elif res == "f":
        res = raw_input("Filename to write to: ")
        with open(res, mode='w') as f:
            f.write(matcher.build_log())
        print "File written."

    go = raw_input("Create playlist from these matches? (y/n): ")
    if go == "y":
        name = raw_input("playlist name: ")
        p_id = api.create_playlist(name)['id']

        print "Made playlist", name

        res = api.add_songs_to_playlist(p_id,
                                map(gm_tools.filter_song_md, matched_songs))
        print "Added songs."

if __name__ == '__main__':
    main()

playlist file:

[playlist]
X-GNOME-Title=Asia
NumberOfEntries=9
File1=file:///backup/MUSIC/Asia/Asia/Cuttin' It Fine.mp3
Title1=Cutting It Fine
File2=file:///backup/MUSIC/Asia/Asia/Heat of the Moment.mp3
Title2=Heat of the Moment
File3=file:///backup/MUSIC/Asia/Asia/Here Comes The Feeling.mp3
Title3=Here Comes The Feeling
File4=file:///backup/MUSIC/Asia/Asia/One Step Closer.mp3
Title4=One Step Closer
File5=file:///backup/MUSIC/Asia/Asia/Only Time Will Tell.mp3
Title5=Only Time Will Tell
File6=file:///backup/MUSIC/Asia/Asia/Sole Survivor.mp3
Title6=Sole Survivor
File7=file:///backup/MUSIC/Asia/Asia/Time Again.mp3
Title7=Time Again
File8=file:///backup/MUSIC/Asia/Asia/Wildest Dreams.mp3
Title8=Wildest Dreams
File9=file:///backup/MUSIC/Asia/Asia/Without You.mp3
Title9=Without You

Here is the output in terminal (I have embedded my login credentials to the code I am running):

Log in to Google Music.
Loading library from Google Music...
Matching songs...
Output matches to file or terminal? (f/t): t
### Starting match of 0 queries ###
Create playlist from these matches? (y/n): n
simon-weber commented 10 years ago

get_all_songs is broken at the moment: https://github.com/simon-weber/Unofficial-Google-Music-API/issues/221.

I'll check this out once it's working again.