#NEWLINE# @ utils . accept_singleton ( basestring ) #NEWLINE# @ utils . empty_arg_shortcircuit ( return_code = '{}' ) #NEWLINE# def upload( self , filepaths , transcode_quality = '320k' , enable_matching = False ) : #NEWLINE# #INDENT# "Uploads the given filepaths.\n\n        All non-mp3 files will be transcoded before being uploaded.\n        This is a limitation of Google's backend.\n\n        An available installation of ffmpeg or avconv is required in most cases:\n        see `the installation page\n        <https://unofficial-google-music-api.readthedocs.org/en\n        /latest/usage.html?#installation>`__ for details.\n\n        Returns a 3-tuple ``(uploaded, matched, not_uploaded)`` of dictionaries, eg::\n\n            (\n                {'<filepath>': '<new server id>'},               # uploaded\n                {'<filepath>': '<new server id>'},               # matched\n                {'<filepath>': '<reason, eg ALREADY_EXISTS>'}    # not uploaded\n            )\n\n        :param filepaths: a list of filepaths, or a single filepath.\n        :param transcode_quality: if int, pass to ffmpeg/avconv ``-q:a`` for libmp3lame\n          (`lower-better int,\n          <http://trac.ffmpeg.org/wiki/Encoding%20VBR%20(Variable%20Bit%20Rate)%20mp3%20audio>`__).\n          If string, pass to ffmpeg/avconv ``-b:a`` (eg ``'128k'`` for an average bitrate of 128k).\n          The default is 320kbps cbr (the highest possible quality).\n\n        :param enable_matching: if ``True``, attempt to use `scan and match\n          <http://support.google.com/googleplay/bin/answer.py?hl=en&answer=2920799&topic=2450455>`__\n          to avoid uploading every song.\n          This requires ffmpeg or avconv.\n          **WARNING**: currently, mismatched songs can *not* be fixed with the 'Fix Incorrect Match'\n          button nor :py:func:`report_incorrect_match\n          <gmusicapi.clients.Webclient.report_incorrect_match>`.\n          They would have to be deleted and reuploaded with matching disabled\n          (or with the Music Manager).\n          Fixing matches from gmusicapi may be supported in a future release; see issue `#89\n          <https://github.com/simon-weber/gmusicapi/issues/89>`__.\n\n        All Google-supported filetypes are supported; see `Google's documentation\n        <http://support.google.com/googleplay/bin/answer.py?hl=en&answer=1100462>`__.\n\n        If ``PERMANENT_ERROR`` is given as a not_uploaded reason, attempts to reupload will never\n        succeed. The file will need to be changed before the server will reconsider it; the easiest\n        way is to change metadata tags (it's not important that the tag be uploaded, just that the\n        contents of the file change somehow).\n        " #NEWLINE# if ( ( self . uploader_id is None ) or ( self . uploader_name is None ) ) : #NEWLINE# #INDENT# raise NotLoggedIn ( 'Not authenticated as an upload device; run Api.login(...perform_upload_auth=True...) first.' ) #NEWLINE# #UNINDENT# uploaded = { #NEWLINE# #INDENT# #NEWLINE# #UNINDENT# } #NEWLINE# matched = { #NEWLINE# #INDENT# #NEWLINE# #UNINDENT# } #NEWLINE# not_uploaded = { #NEWLINE# #INDENT# #NEWLINE# #UNINDENT# } #NEWLINE# local_info = { #NEWLINE# #INDENT# #NEWLINE# #UNINDENT# } #NEWLINE# for path in filepaths : #NEWLINE# #INDENT# try : #NEWLINE# #INDENT# track = musicmanager . UploadMetadata . fill_track_info ( path ) #NEWLINE# #UNINDENT# except BaseException as e : #NEWLINE# #INDENT# self . logger . exception ( "problem gathering local info of '%r'" , path ) #NEWLINE# user_err_msg = str ( e ) #NEWLINE# if ( 'Non-ASCII strings must be converted to unicode' in str ( e ) ) : #NEWLINE# #INDENT# user_err_msg = ( "nonascii bytestrings must be decoded to unicode (error: '%s')" % user_err_msg ) #NEWLINE# #UNINDENT# not_uploaded [ path ] = user_err_msg #NEWLINE# #UNINDENT# else : #NEWLINE# #INDENT# local_info [ track . client_id ] = ( path , track ) #NEWLINE# #UNINDENT# #UNINDENT# if ( not local_info ) : #NEWLINE# #INDENT# return ( uploaded , matched , not_uploaded ) #NEWLINE# #UNINDENT# res = self . _make_call ( musicmanager . UploadMetadata , [ t for ( path , t ) in local_info . values ( ) ] , self . uploader_id ) #NEWLINE# md_res = res . metadata_response #NEWLINE# responses = [ r for r in md_res . track_sample_response ] #NEWLINE# sample_requests = [ req for req in md_res . signed_challenge_info ] #NEWLINE# for sample_request in sample_requests : #NEWLINE# #INDENT# ( path , track ) = local_info [ sample_request . challenge_info . client_track_id ] #NEWLINE# bogus_sample = None #NEWLINE# if ( not enable_matching ) : #NEWLINE# #INDENT# bogus_sample = b'' #NEWLINE# #UNINDENT# try : #NEWLINE# #INDENT# res = self . _make_call ( musicmanager . ProvideSample , enable_matching , sample_request , track , self . uploader_id , bogus_sample ) #NEWLINE# #UNINDENT# except ( IOError , ValueError ) as e : #NEWLINE# #INDENT# self . logger . warning ( "couldn't create scan and match sample for '%r': %s" , path , str ( e ) ) #NEWLINE# not_uploaded [ path ] = str ( e ) #NEWLINE# #UNINDENT# else : #NEWLINE# #INDENT# responses . extend ( res . sample_response . track_sample_response ) #NEWLINE# #UNINDENT# #UNINDENT# to_upload = { #NEWLINE# #INDENT# #NEWLINE# #UNINDENT# } #NEWLINE# for sample_res in responses : #NEWLINE# #INDENT# ( path , track ) = local_info [ sample_res . client_track_id ] #NEWLINE# if ( sample_res . response_code == upload_pb2 . TrackSampleResponse . MATCHED ) : #NEWLINE# #INDENT# self . logger . info ( "matched '%r' to sid %s" , path , sample_res . server_track_id ) #NEWLINE# matched [ path ] = sample_res . server_track_id #NEWLINE# if ( not enable_matching ) : #NEWLINE# #INDENT# self . logger . error ( "'%r' was matched without matching enabled" , path ) #NEWLINE# #UNINDENT# #UNINDENT# else : #NEWLINE# #INDENT# if ( sample_res . response_code == upload_pb2 . TrackSampleResponse . UPLOAD_REQUESTED ) : #NEWLINE# #INDENT# to_upload [ sample_res . server_track_id ] = ( path , track , False ) #NEWLINE# #UNINDENT# else : #NEWLINE# #INDENT# enum_desc = upload_pb2 . _TRACKSAMPLERESPONSE . enum_types [ 0 ] #NEWLINE# res_name = enum_desc . values_by_number [ sample_res . response_code ] . name #NEWLINE# err_msg = ( 'TrackSampleResponse code %s: %s' % ( sample_res . response_code , res_name ) ) #NEWLINE# if ( res_name == 'ALREADY_EXISTS' ) : #NEWLINE# #INDENT# err_msg += ( '(%s)' % sample_res . server_track_id ) #NEWLINE# #UNINDENT# self . logger . warning ( "upload of '%r' rejected: %s" , path , err_msg ) #NEWLINE# not_uploaded [ path ] = err_msg #NEWLINE# #UNINDENT# #UNINDENT# #UNINDENT# if to_upload : #NEWLINE# #INDENT# self . _make_call ( musicmanager . UpdateUploadState , 'start' , self . uploader_id ) #NEWLINE# for ( server_id , ( path , track , do_not_rematch ) ) in to_upload . items ( ) : #NEWLINE# #INDENT# should_retry = True #NEWLINE# attempts = 0 #NEWLINE# while ( should_retry and ( attempts < 10 ) ) : #NEWLINE# #INDENT# session = self . _make_call ( musicmanager . GetUploadSession , self . uploader_id , len ( uploaded ) , track , path , server_id , do_not_rematch ) #NEWLINE# attempts += 1 #NEWLINE# ( got_session , error_details ) = musicmanager . GetUploadSession . process_session ( session ) #NEWLINE# if got_session : #NEWLINE# #INDENT# self . logger . info ( "got an upload session for '%r'" , path ) #NEWLINE# break #NEWLINE# #UNINDENT# ( should_retry , reason , error_code ) = error_details #NEWLINE# self . logger . debug ( 'problem getting upload session: %s\ncode=%s retrying=%s' , reason , error_code , should_retry ) #NEWLINE# if ( ( error_code == 200 ) and do_not_rematch ) : #NEWLINE# #INDENT# should_retry = True #NEWLINE# #UNINDENT# time . sleep ( 6 ) #NEWLINE# #UNINDENT# else : #NEWLINE# #INDENT# err_msg = ( 'GetUploadSession error %s: %s' % ( error_code , reason ) ) #NEWLINE# self . logger . warning ( "giving up on upload session for '%r': %s" , path , err_msg ) #NEWLINE# not_uploaded [ path ] = err_msg #NEWLINE# continue #NEWLINE# #UNINDENT# session = session [ 'sessionStatus' ] #NEWLINE# external = session [ 'externalFieldTransfers' ] [ 0 ] #NEWLINE# session_url = external [ 'putInfo' ] [ 'url' ] #NEWLINE# content_type = external . get ( 'content_type' , 'audio/mpeg' ) #NEWLINE# if ( track . original_content_type != locker_pb2 . Track . MP3 ) : #NEWLINE# #INDENT# try : #NEWLINE# #INDENT# self . logger . info ( "transcoding '%r' to mp3" , path ) #NEWLINE# contents = utils . transcode_to_mp3 ( path , quality = transcode_quality ) #NEWLINE# #UNINDENT# except ( IOError , ValueError ) as e : #NEWLINE# #INDENT# self . logger . warning ( 'error transcoding %r: %s' , path , e ) #NEWLINE# not_uploaded [ path ] = ( 'transcoding error: %s' % e ) #NEWLINE# continue #NEWLINE# #UNINDENT# #UNINDENT# else : #NEWLINE# #INDENT# with open ( path , 'rb' ) as f : #NEWLINE# #INDENT# contents = f . read ( ) #NEWLINE# #UNINDENT# #UNINDENT# upload_response = self . _make_call ( musicmanager . UploadFile , session_url , content_type , contents ) #NEWLINE# success = upload_response . get ( 'sessionStatus' , { #NEWLINE# #INDENT# #NEWLINE# #UNINDENT# } ) . get ( 'state' ) #NEWLINE# if success : #NEWLINE# #INDENT# uploaded [ path ] = server_id #NEWLINE# #UNINDENT# else : #NEWLINE# #INDENT# self . logger . debug ( "could not finalize upload of '%r'. response: %s" , path , upload_response ) #NEWLINE# not_uploaded [ path ] = 'could not finalize upload; details in log' #NEWLINE# #UNINDENT# #UNINDENT# self . _make_call ( musicmanager . UpdateUploadState , 'stopped' , self . uploader_id ) #NEWLINE# #UNINDENT# return ( uploaded , matched , not_uploaded )