def uncache_artwork(prefix, suffix, scale): """ Remove info from cache @param prefix as str @param suffix as str @param scale factor as int """ filepath = "%s/%s_%s_%s.jpg" % (InfoCache.CACHE_PATH, escape(prefix), suffix, ArtSize.ARTIST) f = Gio.File.new_for_path(filepath) try: f.delete(None) except: pass for i in [1, 2]: filepath = "%s/%s_%s_%s.jpg" % (InfoCache.CACHE_PATH, escape(prefix), suffix, ArtSize.ARTIST_SMALL*scale*i) f = Gio.File.new_for_path(filepath) try: f.delete(None) except: pass
def _get_tracks_files(self): """ Return children uris for uri @return [str] """ children = [] dir_uris = [self._uri+'/tracks/'] while dir_uris: uri = dir_uris.pop(0) album_name = uri.replace(self._uri+"/tracks/", "") album = escape(album_name) d = Gio.File.new_for_uri(self._uri+"/tracks/"+album) infos = d.enumerate_children( 'standard::name,standard::type', Gio.FileQueryInfoFlags.NOFOLLOW_SYMLINKS, None) for info in infos: if info.get_file_type() == Gio.FileType.DIRECTORY: dir_uris.append(uri+info.get_name()) else: track = escape(info.get_name()) children.append("%s/tracks/%s/%s" % (self._uri, album, track)) return children
def exists(prefix): """ Return True if an info is cached @param prefix as string """ exists = False for (suffix, helper1, helper2) in InfoCache.WEBSERVICES: filepath = "%s/%s_%s.jpg" % (InfoCache._INFO_PATH, escape(prefix), suffix) if path.exists(filepath): exists = True else: # FIXME Remove this code, support for old lollypop versions # old_path = "%s/%s_%s_%s.jpg" % (InfoCache._CACHE_PATH, escape(prefix), suffix, ArtSize.ARTIST) if path.exists(old_path): rename(old_path, filepath) exists = True # ########################################################## return exists
def __search_term(self, term): """ Search term on Wikipdia @param term as str @return pageid as str """ try: for locale in [self.__locale, "en"]: uri = self.__API_SEARCH % (locale, term) (status, data) = App().task_helper.load_uri_content_sync(uri) if status: decode = json.loads(data.decode("utf-8")) for item in decode["query"]["search"]: if escape(item["title"].lower()) ==\ escape(term.lower()): return (locale, item["pageid"]) else: for word in [ _("band"), _("singer"), "band", "singer" ]: if item["snippet"].lower().find(word) != -1: return (locale, item["pageid"]) except Exception as e: print("Wikipedia::__search_term(): %s", e) return ("", None)
def add_artist_artwork(self, artist, data, is_web=False): """ Add artist artwork to store @param artist as str @param data as bytes @param is_web as bool @thread safe """ self.uncache_artist_artwork(artist) if is_web: filepath = "%s/web_%s.jpg" % (self._INFO_PATH, escape(artist)) else: filepath = "%s/%s.jpg" % (self._INFO_PATH, escape(artist)) if data is None: f = Gio.File.new_for_path(filepath) fstream = f.replace(None, False, Gio.FileCreateFlags.REPLACE_DESTINATION, None) fstream.close() else: bytes = GLib.Bytes(data) stream = Gio.MemoryInputStream.new_from_bytes(bytes) pixbuf = GdkPixbuf.Pixbuf.new_from_stream(stream, None) stream.close() pixbuf.savev(filepath, "jpeg", ["quality"], ["100"]) GLib.idle_add(self.emit, "artist-artwork-changed", artist)
def __get_uris_to_copy(self, tracks): """ Get on device URI for all tracks @param tracks as [Track] """ uris = [] art_uris = [] for track in tracks: f = Gio.File.new_for_uri(track.uri) album_device_uri = "%s/%s" % ( self.__uri, self.__get_album_on_device_uri(track)) album_local_uri = f.get_parent().get_uri() src_uri = "%s/%s" % (album_local_uri, GLib.uri_escape_string( f.get_basename(), None, True)) dst_uri = "%s/%s" % (album_device_uri, escape(f.get_basename())) (convertion_needed, dst_uri) = self.__is_convertion_needed(src_uri, dst_uri) uris.append((src_uri, dst_uri)) art_uri = App().art.get_album_artwork_uri(track.album) if art_uri is not None: art_filename = Gio.File.new_for_uri(art_uri).get_basename() art_uris.append( (art_uri, "%s/%s" % (album_device_uri, escape(art_filename)))) return uris + art_uris
def uncache_artwork(prefix, suffix, scale): """ Remove info from cache @param prefix as str @param suffix as str @param scale factor as int """ filepath = "%s/%s_%s.jpg" % (InfoCache.CACHE_PATH, escape(prefix), suffix) f = Gio.File.new_for_path(filepath) try: f.delete(None) except: pass filepath = "%s/%s_%s_%s.jpg" % (InfoCache.CACHE_PATH, escape(prefix), suffix, ArtSize.ARTIST_SMALL*scale) f = Gio.File.new_for_path(filepath) try: f.delete(None) except: pass Lp().art.emit('artist-artwork-changed', prefix)
def get_artwork(prefix, suffix, size=ArtSize.ARTIST): """ Return path for artwork, empty if none @param prefix as string @param suffix as string @param size as int @return path as string/None """ filepath = "%s/%s_%s_%s.jpg" % (InfoCache.CACHE_PATH, escape(prefix), suffix, ArtSize.ARTIST) filepath_at_size = "%s/%s_%s_%s.jpg" % (InfoCache.CACHE_PATH, escape(prefix), suffix, size) if not path.exists(filepath) or path.getsize(filepath) == 0: return None # Make cache for this size if not path.exists(filepath_at_size): pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size(filepath, size, size) if pixbuf.get_height() > pixbuf.get_width(): vertical = True else: vertical = False del pixbuf extract = GdkPixbuf.Pixbuf.new(GdkPixbuf.Colorspace.RGB, True, 8, size, size) if vertical: pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_scale(filepath, size, -1, True) diff = pixbuf.get_height() - size pixbuf.copy_area(0, diff/2, pixbuf.get_width(), size, extract, 0, 0) else: pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_scale(filepath, -1, size, True) diff = pixbuf.get_width() - size pixbuf.copy_area(diff/2, 0, size, pixbuf.get_height(), extract, 0, 0) extract.savev(filepath_at_size, "jpeg", ["quality"], ["90"]) del pixbuf del extract return filepath_at_size
def get_artwork(prefix, suffix, size): """ Return path for artwork @param prefix as string @param suffix as string @param size as int @return path as string/None """ try: for (suffix, helper1, helper2) in InfoCache.WEBSERVICES: extract = None filepath = "%s/%s_%s.jpg" % (InfoCache._INFO_PATH, escape(prefix), suffix) filepath_at_size = "%s/%s_%s_%s.jpg" % ( InfoCache._CACHE_PATH, escape(prefix), suffix, size) if not path.exists(filepath) or path.getsize(filepath) == 0: filepath_at_size = None continue # Make cache for this size if not path.exists(filepath_at_size): pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size( filepath, size, size) if pixbuf.get_height() > pixbuf.get_width(): vertical = True elif pixbuf.get_height() < pixbuf.get_width(): vertical = False else: extract = pixbuf if extract is None: del pixbuf extract = GdkPixbuf.Pixbuf.new( GdkPixbuf.Colorspace.RGB, True, 8, size, size) if vertical: pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_scale( filepath, size, -1, True) diff = pixbuf.get_height() - size pixbuf.copy_area(0, diff / 2, pixbuf.get_width(), size, extract, 0, 0) else: pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_scale( filepath, -1, size, True) diff = pixbuf.get_width() - size pixbuf.copy_area(diff / 2, 0, size, pixbuf.get_height(), extract, 0, 0) del pixbuf extract.savev(filepath_at_size, "jpeg", ["quality"], [ str(Lp().settings.get_value( 'cover-quality').get_int32()) ]) del extract return filepath_at_size except Exception as e: print("InfoCache::get_artwork():", e) return None
def get_artwork_path(artist, size, scale_factor): """ Return path for artwork @param artist as string @param size as int @param scale_factor as int @return path as string/None """ try: size *= scale_factor extract = None filepath = "%s/%s.jpg" % (InformationStore._INFO_PATH, escape(artist)) filepath_at_size = "%s/%s_%s.jpg" % (InformationStore._CACHE_PATH, escape(artist), size) f = Gio.File.new_for_path(filepath) if not f.query_exists(): return None info = f.query_info("standard::size", Gio.FileQueryInfoFlags.NONE) if info.get_size() == 0: return None # Make cache for this size f = Gio.File.new_for_path(filepath_at_size) if not f.query_exists(): pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size( filepath, size, size) if pixbuf.get_height() > pixbuf.get_width(): vertical = True elif pixbuf.get_height() < pixbuf.get_width(): vertical = False else: extract = pixbuf if extract is None: extract = GdkPixbuf.Pixbuf.new(GdkPixbuf.Colorspace.RGB, True, 8, size, size) if vertical: pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_scale( filepath, size, -1, True) diff = pixbuf.get_height() - size pixbuf.copy_area(0, diff / 2, pixbuf.get_width(), size, extract, 0, 0) else: pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_scale( filepath, -1, size, True) diff = pixbuf.get_width() - size pixbuf.copy_area(diff / 2, 0, size, pixbuf.get_height(), extract, 0, 0) extract.savev(filepath_at_size, "jpeg", ["quality"], [ str(App().settings.get_value("cover-quality").get_int32()) ]) return filepath_at_size except Exception as e: Logger.error("InformationStore::get_artwork_path(): %s" % e) return None
def _remove_from_device(self, playlists): """ Delete files not available in playlist """ track_uris = [] tracks_ids = [] # Get tracks ids for playlist in playlists: tracks_ids += Lp().playlists.get_tracks_ids(playlist) # Get tracks uris for track_id in tracks_ids: if not self._syncing: self._fraction = 1.0 self._in_thread = False return track = Track(track_id) album_name = escape(track.album_name.lower()) if track.album_artist_id == Type.COMPILATIONS: artist_name = escape(track.artist.lower()) else: artist_name = escape(track.album_artist.lower()) album_uri = "%s/tracks/%s_%s" % (self._uri, artist_name, album_name) track_name = escape(GLib.basename(track.path)) # Check extension, if not mp3, convert ext = os.path.splitext(track.path)[1] if ext != ".mp3" and self._convert: track_name = track_name.replace(ext, ".mp3") on_disk = Gio.File.new_for_path(track.path) info = on_disk.query_info('time::modified', Gio.FileQueryInfoFlags.NONE, None) # Prefix track with mtime to make sure updating it later mtime = info.get_attribute_as_string('time::modified') dst_uri = "%s/%s_%s" % (album_uri, mtime, track_name) track_uris.append(dst_uri) on_mtp_files = self._get_tracks_files() # Delete file on device and not in playlists for uri in on_mtp_files: if not self._syncing: self._fraction = 1.0 self._in_thread = False return if uri not in track_uris and uri not in self._copied_art_uris: to_delete = Gio.File.new_for_uri(uri) self._retry(to_delete.delete, (None,)) self._done += 1 self._fraction = self._done/self._total
def _remove_from_device(self, playlists): """ Delete files not available in playlist """ track_uris = [] tracks_ids = [] # Get tracks ids for playlist in playlists: tracks_ids += Lp().playlists.get_tracks_ids(playlist) # Get tracks uris for track_id in tracks_ids: if not self._syncing: self._fraction = 1.0 self._in_thread = False return track = Track(track_id) album_name = escape(track.album_name.lower()) if track.album_artist_id == Type.COMPILATIONS: artist_name = escape(track.artist.lower()) else: artist_name = escape(track.album_artist.lower()) album_uri = "%s/tracks/%s_%s" % (self._uri, artist_name, album_name) track_name = escape(GLib.basename(track.path)) # Check extension, if not mp3, convert ext = os.path.splitext(track.path)[1] if ext != ".mp3" and self._convert: track_name = track_name.replace(ext, ".mp3") on_disk = Gio.File.new_for_path(track.path) info = on_disk.query_info('time::modified', Gio.FileQueryInfoFlags.NONE, None) # Prefix track with mtime to make sure updating it later mtime = info.get_attribute_as_string('time::modified') dst_uri = "%s/%s_%s" % (album_uri, mtime, track_name) track_uris.append(dst_uri) on_mtp_files = self._get_tracks_files() # Delete file on device and not in playlists for uri in on_mtp_files: if not self._syncing: self._fraction = 1.0 self._in_thread = False return if uri not in track_uris and uri not in self._copied_art_uris: to_delete = Gio.File.new_for_uri(uri) self._retry(to_delete.delete, (None, )) self._done += 1 self._fraction = self._done / self._total
def __get_album_on_device_uri(self, track): """ Get on device URI for album @param track as Track @return URI as str """ album_name = track.album_name.lower() is_compilation = track.album.artist_ids[0] == Type.COMPILATIONS if is_compilation: string = escape(album_name) else: artists = ", ".join(track.album.artists).lower() string = escape("%s_%s" % (artists, album_name)) return GLib.uri_escape_string(string[:100], None, True)
def add(prefix, content, data, suffix): """ Add info to store @param prefix as str @param content as str @param data as bytes @param suffix as str """ filepath = "%s/%s_%s" % (InfoCache._INFO_PATH, escape(prefix), suffix) if content is not None: f = Gio.File.new_for_path(filepath + ".txt") fstream = f.replace(None, False, Gio.FileCreateFlags.REPLACE_DESTINATION, None) if fstream is not None: fstream.write(content, None) fstream.close() if data is None: f = Gio.File.new_for_path(filepath + ".jpg") fstream = f.replace(None, False, Gio.FileCreateFlags.REPLACE_DESTINATION, None) fstream.close() else: bytes = GLib.Bytes(data) stream = Gio.MemoryInputStream.new_from_bytes(bytes) bytes.unref() pixbuf = GdkPixbuf.Pixbuf.new_from_stream_at_scale( stream, ArtSize.ARTIST, -1, True, None) stream.close() pixbuf.savev( filepath + ".jpg", "jpeg", ["quality"], [str(Lp().settings.get_value("cover-quality").get_int32())])
def get_artist_cache_name(self, artist): """ Get a uniq string for artist @param artist as str """ name = "@ARTIST@_%s" % (escape(artist)[:100]) return name
def __download_genius_lyrics(self): """ Download lyrics from genius """ self.__downloads_running += 1 # Update lyrics if self.__current_track.id == Type.RADIOS: split = App().player.current_track.name.split(" - ") if len(split) < 2: return artist = split[0] title = split[1] else: if self.__current_track.artists: artist = self.__current_track.artists[0] elif self.__current_track.album_artists: artist = self.__current_track.album_artists[0] else: artist = "" title = self.__current_track.name string = escape("%s %s" % (artist, title)) uri = "https://genius.com/%s-lyrics" % string.replace(" ", "-") helper = TaskHelper() helper.load_uri_content(uri, self.__cancellable, self.__on_lyrics_downloaded, "song_body-lyrics", "")
def cache(prefix, content, data, suffix): """ Cache datas @param prefix as str @param content as str @param data as bytes @param suffix as str """ filepath = "%s/%s_%s" % (InfoCache.CACHE_PATH, escape(prefix), suffix) if content is not None: f = Gio.File.new_for_path(filepath+".txt") fstream = f.replace(None, False, Gio.FileCreateFlags.REPLACE_DESTINATION, None) if fstream is not None: fstream.write(content, None) fstream.close() if data is None: f = Gio.File.new_for_path(filepath+".jpg") fstream = f.replace(None, False, Gio.FileCreateFlags.REPLACE_DESTINATION, None) fstream.close() else: stream = Gio.MemoryInputStream.new_from_data(data, None) pixbuf = GdkPixbuf.Pixbuf.new_from_stream_at_scale(stream, ArtSize.ARTIST, -1, True, None) pixbuf.savev(filepath+"_"+str(ArtSize.ARTIST)+".jpg", "jpeg", ["quality"], ["90"]) Lp().art.emit('artist-artwork-changed', prefix) del pixbuf
def __get_radio_art_path(self, name): """ Get radio artwork path @param name as str @return filepath as str """ return "%s/%s.png" % (self._RADIOS_PATH, escape(name))
def __migrate_old_dir(self): """ Migrate old data dir """ try: old_path = GLib.get_user_data_dir() + "/lollypop/info" old_src = Gio.File.new_for_path(old_path) if not old_src.query_exists(): return storage_type = get_default_storage_type() for (artist_id, artist, *ignore) in App().artists.get([], storage_type): for ext in ["jpg", "txt"]: src_path = "%s/%s.%s" % (old_path, escape(artist), ext) src = Gio.File.new_for_path(src_path) if not src.query_exists(): continue encoded = self.encode_artist_name(artist) dst_path = "%s/%s.%s" % (ARTISTS_PATH, encoded, ext) dst = Gio.File.new_for_path(dst_path) src.copy(dst, Gio.FileCopyFlags.OVERWRITE, None, None) old_dst = Gio.File.new_for_path(GLib.get_user_data_dir() + "/lollypop/info-backup") old_src.move(old_dst, Gio.FileCopyFlags.OVERWRITE, None, None) except Exception as e: Logger.error("ArtistArt::__migrate_old_dir(): %s -> %s", e, old_path)
def add(prefix, content, data, suffix): """ Add info to store @param prefix as str @param content as str @param data as bytes @param suffix as str """ filepath = "%s/%s_%s" % (InfoCache._INFO_PATH, escape(prefix), suffix) if content is not None: f = Gio.File.new_for_path(filepath+".txt") fstream = f.replace(None, False, Gio.FileCreateFlags.REPLACE_DESTINATION, None) if fstream is not None: fstream.write(content, None) fstream.close() if data is None: f = Gio.File.new_for_path(filepath+".jpg") fstream = f.replace(None, False, Gio.FileCreateFlags.REPLACE_DESTINATION, None) fstream.close() else: stream = Gio.MemoryInputStream.new_from_data(data, None) pixbuf = GdkPixbuf.Pixbuf.new_from_stream_at_scale(stream, ArtSize.ARTIST, -1, True, None) pixbuf.savev(filepath+".jpg", "jpeg", ["quality"], ["90"]) del pixbuf
def __get_radio_cache_name(self, name): """ Get a uniq string for radio @param album_id as int @param sql as sqlite cursor """ return "@@" + escape(name) + "@@radio@@"
def add(prefix, content, data, suffix): """ Add info to store @param prefix as str @param content as str @param data as bytes @param suffix as str """ filepath = "%s/%s_%s" % (InfoCache._INFO_PATH, escape(prefix), suffix) if content is not None: f = Gio.File.new_for_path(filepath + ".txt") fstream = f.replace(None, False, Gio.FileCreateFlags.REPLACE_DESTINATION, None) if fstream is not None: fstream.write(content, None) fstream.close() if data is None: f = Gio.File.new_for_path(filepath + ".jpg") fstream = f.replace(None, False, Gio.FileCreateFlags.REPLACE_DESTINATION, None) fstream.close() else: stream = Gio.MemoryInputStream.new_from_data(data, None) pixbuf = GdkPixbuf.Pixbuf.new_from_stream_at_scale( stream, ArtSize.ARTIST, -1, True, None) pixbuf.savev(filepath + ".jpg", "jpeg", ["quality"], ["90"]) del pixbuf
def get_album_cache_name(self, album): """ Get a uniq string for album @param album as Album """ name = "_".join(album.artists)[:100] +\ "_" + album.name[:100] + "_" + album.year return escape(name)
def __get_album_cache_name(self, album): """ Get a uniq string for album @param album as Album """ # FIXME special chars name = "_".join(album.artists) + "_" + album.name + "_" + album.year return escape(name)
def __get_jgm_id(self, item): """ Get jmg id @param item as SearchItem @return jpg id as str """ # Try to handle compilations (itunes one) unwanted = ["variout artists", "multi-interprètes"] if item.artists[0].lower() in unwanted: if len(item.artists) > 1: artist = item.artists[1] else: artist = "" else: artist = item.artists[0] unescaped = "%s %s" % (artist, item.name) for c in ['/', '?', '!']: if c in unescaped: unescaped = unescaped.replace(c, ' ') search = GLib.uri_escape_string(unescaped, '', True) try: # Strip /? as API doesn't like it f = Lio.File.new_for_uri("http://app.jgm90.com/cmapi/search/" "%s/1/10" % search.strip('/?')) (status, data, tag) = f.load_contents(None) if status: decode = json.loads(data.decode('utf-8')) for song in decode['result']['songs']: try: song_artist = escape( song['artists'][0]['name'].lower()) if song_artist == escape(artist.lower()): return song['id'] except Exception as e: print("WebJmg90::__get_jmg_id():", e) except IndexError: pass except KeyError: pass except Exception as e: print("WebJmg90::__get_jmg_id():", e) return None
def _get_album_cache_name(self, album): """ Get a uniq string for album @param album as Album """ # FIXME special chars name = "_".join(album.artists) + "_" + album.name + "_" + album.year return escape(name)
def artwork_exists(artist): """ True if artwork exists @param artist as str @return bool """ filepath = "%s/%s.jpg" % (InformationStore._INFO_PATH, escape(artist)) return GLib.file_test(filepath, GLib.FileTest.EXISTS)
def __write_playlists(self, playlist_ids): """ Write playlists on disk @param playlist_ids as [int] """ for playlist_id in playlist_ids: if self.__cancellable.is_cancelled(): break try: # Get tracks if App().playlists.get_smart(playlist_id): request = App().playlists.get_smart_sql(playlist_id) track_ids = App().db.execute(request) else: track_ids = App().playlists.get_track_ids(playlist_id) # Build tracklist tracklist = "#EXTM3U\n" for track_id in track_ids: if self.__cancellable.is_cancelled(): break track = Track(track_id) f = Gio.File.new_for_uri(track.uri) filename = f.get_basename() album_uri = self.__get_album_name(track) uri = "%s/%s" % (album_uri, escape(filename)) (convertion_needed, uri) = self.__is_convertion_needed(track.uri, uri) tracklist += "%s\n" % uri # Write playlist file playlist_name = escape(App().playlists.get_name(playlist_id)) playlist_uri = "%s/%s.m3u" % (self.__uri, playlist_name) Logger.debug("MtpSync::__write_playlists(): %s" % playlist_uri) temp_uri = os.path.join(tempfile.gettempdir(), "lollypop_%s.m3u" % playlist_name) m3u_temp = Gio.File.new_for_path(temp_uri) m3u_temp.replace_contents( tracklist.encode("utf-8"), None, False, Gio.FileCreateFlags.REPLACE_DESTINATION, self.__cancellable) m3u = Gio.File.new_for_uri(playlist_uri) m3u_temp.move(m3u, Gio.FileCopyFlags.OVERWRITE, None, None) except Exception as e: Logger.error("MtpSync::__write_playlists(): %s", e)
def get_album_cache_name(self, album): """ Get a uniq string for album @param album as Album """ name = "%s_%s_%s" % (" ".join(album.artists)[:100], album.name[:100], album.year) return escape(name)
def uncache(prefix, suffix): """ Remove info from cache @param prefix as str @param suffix as str """ filepath = "%s/%s_%s.txt" % (InfoCache.CACHE_PATH, escape(prefix), suffix) f = Gio.File.new_for_path(filepath) try: f.delete(None) except: pass filepath = "%s/%s_%s_%s.jpg" % (InfoCache.CACHE_PATH, escape(prefix), suffix, ArtSize.ARTIST) f = Gio.File.new_for_path(filepath) try: f.delete(None) except: pass
def __sync_playlists(self, playlist_ids): """ Sync file from playlist to device @param playlist_ids as [int] """ for playlist_id in playlist_ids: if self.__cancellable.is_cancelled(): break m3u = None stream = None playlist = App().playlists.get_name(playlist_id) try: # Create playlist m3u = Gio.File.new_for_path("/tmp/lollypop_%s.m3u" % (playlist, )) self.__retry(m3u.replace_contents, (b"#EXTM3U\n", None, False, Gio.FileCreateFlags.REPLACE_DESTINATION, None)) stream = m3u.open_readwrite(None) except Exception as e: Logger.error("DeviceWidget::__sync_playlists(): %s" % e) if App().playlists.get_smart(playlist_id): request = App().playlists.get_smart_sql(playlist_id) track_ids = App().db.execute(request) else: track_ids = App().playlists.get_track_ids(playlist_id) # Start copying for track_id in track_ids: if self.__cancellable.is_cancelled(): break if track_id is None: self.__done += 1 continue track = Track(track_id) (track_name, artists, album_name, is_compilation) =\ self.__sync_track_id(track) if stream is not None: if is_compilation: line = "%s/%s\n" %\ (album_name, track_name) else: line = "%s_%s/%s\n" %\ (artists, album_name, track_name) self.__retry(stream.get_output_stream().write, (line.encode(encoding="UTF-8"), None)) if stream is not None: stream.close() if m3u is not None: playlist = escape(playlist) dst = Gio.File.new_for_uri(self.__uri + "/" + playlist + ".m3u") self.__retry(m3u.move, (dst, Gio.FileCopyFlags.OVERWRITE, None, None))
def __get_jgm_id(self, item): """ Get jmg id @param item as SearchItem @return jpg id as str """ # Try to handle compilations (itunes one) unwanted = ["variout artists", "multi-interprètes"] if item.artists[0].lower() in unwanted: if len(item.artists) > 1: artist = item.artists[1] else: artist = "" else: artist = item.artists[0] unescaped = "%s %s" % (artist, item.name) for c in ["/", "?", "!"]: if c in unescaped: unescaped = unescaped.replace(c, " ") search = GLib.uri_escape_string(unescaped, "", True) try: f = Lio.File.new_for_uri("http://app.jgm90.com/cmapi/search/" "%s/1/10" % search) (status, data, tag) = f.load_contents(None) if status: decode = json.loads(data.decode("utf-8")) for song in decode["result"]["songs"]: try: song_artist = escape( song["artists"][0]["name"].lower()) if song_artist == escape(artist.lower()): return song["id"] except Exception as e: print("WebJmg90::__get_jmg_id():", e) except IndexError: pass except KeyError: pass except Exception as e: print("WebJmg90::__get_jmg_id():", e) return None
def remove(prefix, suffix): """ Remove info from store @param prefix as str @param suffix as str """ filepath = "%s/%s_%s.txt" % (InfoCache._INFO_PATH, escape(prefix), suffix) f = Gio.File.new_for_path(filepath) try: f.delete(None) except: pass filepath = "%s/%s_%s.jpg" % (InfoCache._INFO_PATH, escape(prefix), suffix) f = Gio.File.new_for_path(filepath) try: f.delete(None) except: pass
def exists_in_cache(prefix): """ Return True if an info is cached @param prefix as string """ exists = False for suffix in ["lastfm", "wikipedia", "spotify"]: filepath = "%s/%s_%s_%s.jpg" % (InfoCache.CACHE_PATH, escape(prefix), suffix, ArtSize.ARTIST) if path.exists(filepath): exists = True return exists
def exists(prefix): """ Return True if an info is cached @param prefix as string """ exists = False for (suffix, helper1, helper2) in InfoCache.WEBSERVICES: filepath = "%s/%s_%s.jpg" % (InfoCache._INFO_PATH, escape(prefix), suffix) if path.exists(filepath): exists = True return exists
def uncache_artwork(artist): """ Remove artwork from cache @param artist as str """ try: from pathlib import Path search = "%s*.jpg" % escape(artist) for p in Path(InformationStore._CACHE_PATH).glob(search): p.unlink() except Exception as e: Logger.error("InformationStore::uncache_artwork(): %s" % e)
def get_bio(artist): """ Get content from cache @param artist as str @return content as bytes """ filepath = "%s/%s.txt" % (InformationStore._INFO_PATH, escape(artist)) content = None f = Gio.File.new_for_path(filepath) if f.query_exists(): (status, content, tag) = f.load_contents() return content
def get_information(self, artist): """ Get artist information @param artist as str @return content as bytes """ filepath = "%s/%s.txt" % (App().art._INFO_PATH, escape(artist)) content = None f = Gio.File.new_for_path(filepath) if f.query_exists(): (status, content, tag) = f.load_contents() return content
def __get_jgm_id(self, item): """ Get jmg id @param item as SearchItem @return jpg id as str """ # Try to handle compilations (itunes one) if item.artists[0].lower() == "various artists": if len(item.artists) > 1: artist = item.artists[1] else: artist = "" else: artist = item.artists[0] unescaped = "%s %s" % (artist, item.name) search = GLib.uri_escape_string(unescaped, None, True) try: f = Gio.File.new_for_uri("http://app.jgm90.com/cmapi/search/" "%s/1/10" % search) (status, data, tag) = f.load_contents(None) if status: decode = json.loads(data.decode('utf-8')) for song in decode['result']['songs']: try: song_artist = escape( song['artists'][0]['name'].lower()) if song_artist == escape(artist.lower()): return song['id'] except: pass except IndexError: pass except KeyError: pass except Exception as e: print("WebJmg90::__get_jmg_id():", e) return None
def __on_label_button_release_event(self, button, event): """ Show information cache (for edition) @param button as Gtk.Button @param event as Gdk.Event """ uri = "file://%s/%s.txt" % (App().art._INFO_PATH, escape(self.__artist_name)) f = Gio.File.new_for_uri(uri) if not f.query_exists(): f.replace_contents(b"", None, False, Gio.FileCreateFlags.NONE, None) Gtk.show_uri_on_window(App().window, uri, Gdk.CURRENT_TIME)
def uncache(prefix, suffix): """ Remove info from cache @param prefix as str @param suffix as str """ filepath = "%s/%s_%s.txt" % (InfoCache.CACHE_PATH, escape(prefix), suffix) f = Gio.File.new_for_path(filepath) try: f.delete(None) except: pass filepath = "%s/%s_%s.jpg" % (InfoCache.CACHE_PATH, escape(prefix), suffix) f = Gio.File.new_for_path(filepath) try: f.delete(None) except: pass Lp().art.emit('artist-artwork-changed', prefix)
def __get_youtube_score(self, page_title, title, artist, album): """ Calculate youtube score if page_title looks like (title, artist, album), score is lower @return int """ page_title = escape(page_title.lower(), []) artist = escape(artist.lower(), []) album = escape(album.lower(), []) title = escape(title.lower(), []) # YouTube page title should be at least as long as wanted title if len(page_title) < len(title): return self.__BAD_SCORE # Remove common word for a valid track page_title = page_title.replace('official', '') page_title = page_title.replace('video', '') page_title = page_title.replace('audio', '') # Remove artist name page_title = page_title.replace(artist, '') # Remove album name page_title = page_title.replace(album, '') # Remove title page_title = page_title.replace(title, '') return len(page_title)
def get(prefix, suffix): """ Get content from cache @param prefix as str @param suffix as str @return (content as string, data as bytes) """ filepath = "%s/%s_%s" % (InfoCache.CACHE_PATH, escape(prefix), suffix) content = None data = None if path.exists(filepath + ".txt"): f = Gio.File.new_for_path(filepath + ".txt") (status, content, tag) = f.load_contents() if not status: content = None image_path = filepath + "_" + str(ArtSize.ARTIST) + ".jpg" if path.exists(image_path): f = Gio.File.new_for_path(image_path) (status, data, tag) = f.load_contents() if not status: data = None return (content, data)
def get_artwork(prefix, suffix, size): """ Return path for artwork @param prefix as string @param suffix as string @param size as int @return path as string/None """ try: for (suffix, helper1, helper2) in InfoCache.WEBSERVICES: extract = None filepath = "%s/%s_%s.jpg" % (InfoCache._INFO_PATH, escape(prefix), suffix) filepath_at_size = "%s/%s_%s_%s.jpg" % (InfoCache._CACHE_PATH, escape(prefix), suffix, size) if not path.exists(filepath) or path.getsize(filepath) == 0: filepath_at_size = None continue # Make cache for this size if not path.exists(filepath_at_size): pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size(filepath, size, size) if pixbuf.get_height() > pixbuf.get_width(): vertical = True elif pixbuf.get_height() < pixbuf.get_width(): vertical = False else: extract = pixbuf if extract is None: del pixbuf extract = GdkPixbuf.Pixbuf.new( GdkPixbuf.Colorspace.RGB, True, 8, size, size) if vertical: pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_scale( filepath, size, -1, True) diff = pixbuf.get_height() - size pixbuf.copy_area(0, diff/2, pixbuf.get_width(), size, extract, 0, 0) else: pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_scale( filepath, -1, size, True) diff = pixbuf.get_width() - size pixbuf.copy_area(diff/2, 0, size, pixbuf.get_height(), extract, 0, 0) del pixbuf extract.savev(filepath_at_size, "jpeg", ["quality"], ["90"]) del extract return filepath_at_size except Exception as e: print("InfoCache::get_artwork():", e) return None
def __remove_from_device(self, playlists): """ Delete files not available in playlist """ track_uris = [] track_ids = [] # Get tracks if playlists and playlists[0] == Type.NONE: track_ids = [] album_ids = Lp().albums.get_synced_ids() for album_id in album_ids: track_ids += Lp().albums.get_track_ids(album_id) else: for playlist in playlists: track_ids += Lp().playlists.get_track_ids(playlist) # Get tracks uris for track_id in track_ids: if not self._syncing: self._fraction = 1.0 self.__in_thread = False return track = Track(track_id) if track.uri.startswith('https:'): continue album_name = escape(track.album_name.lower()) if track.album.artist_ids[0] == Type.COMPILATIONS: on_device_album_uri = "%s/%s" % (self._uri, album_name) else: artists = escape(", ".join(track.album.artists).lower()) on_device_album_uri = "%s/%s_%s" % (self._uri, artists, album_name) f = Lio.File.new_for_uri(track.uri) track_name = f.get_basename() # Check extension, if not mp3, convert m = match('.*(\.[^.]*)', track.uri) ext = m.group(1) if ext != ".mp3" and self.__convert: track_name = track_name.replace(ext, ".mp3") on_disk = Lio.File.new_for_uri(track.uri) info = on_disk.query_info('time::modified', Gio.FileQueryInfoFlags.NONE, None) # Prefix track with mtime to make sure updating it later mtime = info.get_attribute_as_string('time::modified') dst_uri = "%s/%s_%s" % (on_device_album_uri, mtime, track_name) # To be sure to get uri correctly escaped for Gio f = Lio.File.new_for_uri(dst_uri) track_uris.append(f.get_uri()) on_mtp_files = self.__get_track_files() # Delete file on device and not in playlists for uri in on_mtp_files: if not self._syncing: self._fraction = 1.0 self.__in_thread = False return debug("MtpSync::__remove_from_device(): %s" % uri) if uri not in track_uris and uri not in self.__copied_art_uris: debug("MtpSync::__remove_from_device(): deleting %s" % uri) to_delete = Lio.File.new_for_uri(uri) self.__retry(to_delete.delete, (None,)) self.__done += 1 self._fraction = self.__done/self.__total
def __copy_to_device(self, playlists): """ Copy file from playlist to device @param playlists as [str] """ for playlist in playlists: m3u = None stream = None if playlist != Type.NONE: try: playlist_name = Lp().playlists.get_name(playlist) # Create playlist m3u = Lio.File.new_for_path( "/tmp/lollypop_%s.m3u" % (playlist_name,)) self.__retry(m3u.replace_contents, (b'#EXTM3U\n', None, False, Gio.FileCreateFlags.REPLACE_DESTINATION, None)) stream = m3u.open_readwrite(None) except Exception as e: print("DeviceWidget::_copy_to_device(): %s" % e) # Get tracks if playlist == Type.NONE: track_ids = [] album_ids = Lp().albums.get_synced_ids() for album_id in album_ids: track_ids += Lp().albums.get_track_ids(album_id) else: track_ids = Lp().playlists.get_track_ids(playlist) # Start copying for track_id in track_ids: if track_id is None: continue if not self._syncing: self._fraction = 1.0 self.__in_thread = False return track = Track(track_id) if track.uri.startswith('https:'): continue debug("MtpSync::__copy_to_device(): %s" % track.uri) album_name = escape(track.album_name.lower()) is_compilation = track.album.artist_ids[0] == Type.COMPILATIONS if is_compilation: on_device_album_uri = "%s/%s" %\ (self._uri, album_name) else: artists = escape(", ".join(track.album.artists).lower()) on_device_album_uri = "%s/%s_%s" %\ (self._uri, artists, album_name) d = Lio.File.new_for_uri(on_device_album_uri) if not d.query_exists(): self.__retry(d.make_directory_with_parents, (None,)) # Copy album art art = Lp().art.get_album_artwork_uri(track.album) debug("MtpSync::__copy_to_device(): %s" % art) if art is not None: src_art = Lio.File.new_for_uri(art) art_uri = "%s/cover.jpg" % on_device_album_uri # To be sure to get uri correctly escaped for Gio f = Lio.File.new_for_uri(art_uri) self.__copied_art_uris.append(f.get_uri()) dst_art = Lio.File.new_for_uri(art_uri) if not dst_art.query_exists(): self.__retry(src_art.copy, (dst_art, Gio.FileCopyFlags.OVERWRITE, None, None)) f = Lio.File.new_for_uri(track.uri) track_name = f.get_basename() # Check extension, if not mp3, convert m = match('.*(\.[^.]*)', track.uri) ext = m.group(1) if (ext != ".mp3" or self.__normalize) and self.__convert: convertion_needed = True track_name = track_name.replace(ext, ".mp3") else: convertion_needed = False src_track = Lio.File.new_for_uri(track.uri) info = src_track.query_info('time::modified', Gio.FileQueryInfoFlags.NONE, None) # Prefix track with mtime to make sure updating it later mtime = info.get_attribute_as_string('time::modified') dst_uri = "%s/%s_%s" % (on_device_album_uri, mtime, track_name) if stream is not None: if is_compilation: line = "%s/%s_%s\n" %\ (album_name, mtime, track_name) else: line = "%s_%s/%s_%s\n" %\ (artists, album_name, mtime, track_name) self.__retry(stream.get_output_stream().write, (line.encode(encoding='UTF-8'), None)) dst_track = Lio.File.new_for_uri(dst_uri) if not dst_track.query_exists(): if convertion_needed: mp3_uri = "file:///tmp/%s" % track_name mp3_file = Lio.File.new_for_uri(mp3_uri) pipeline = self.__convert_to_mp3(src_track, mp3_file) # Check if encoding is finished if pipeline is not None: bus = pipeline.get_bus() bus.add_signal_watch() bus.connect('message::eos', self.__on_bus_eos) self.__encoding = True while self.__encoding and self._syncing: sleep(1) bus.disconnect_by_func(self.__on_bus_eos) pipeline.set_state(Gst.State.PAUSED) pipeline.set_state(Gst.State.READY) pipeline.set_state(Gst.State.NULL) self.__retry( mp3_file.move, (dst_track, Gio.FileCopyFlags.OVERWRITE, None, None)) # To be sure try: mp3_file.delete(None) except: pass else: self.__retry(src_track.copy, (dst_track, Gio.FileCopyFlags.OVERWRITE, None, None)) else: self.__done += 1 self.__done += 1 self._fraction = self.__done/self.__total if stream is not None: stream.close() if m3u is not None: playlist_name = escape(playlist_name) dst = Lio.File.new_for_uri(self._uri+'/'+playlist_name+'.m3u') self.__retry(m3u.move, (dst, Gio.FileCopyFlags.OVERWRITE, None, None))
def _copy_to_device(self, playlists): """ Copy file from playlist to device @param playlists as [str] """ for playlist in playlists: try: playlist_name = Lp().playlists.get_name(playlist) # Create playlist m3u = Gio.File.new_for_path( "/tmp/lollypop_%s.m3u" % (playlist_name,)) self._retry(m3u.replace_contents, (b'#EXTM3U\n', None, False, Gio.FileCreateFlags.REPLACE_DESTINATION, None)) stream = m3u.open_readwrite(None) except Exception as e: print("DeviceWidget::_copy_to_device(): %s" % e) m3u = None stream = None # Start copying tracks_ids = Lp().playlists.get_track_ids(playlist) for track_id in tracks_ids: if track_id is None: continue if not self._syncing: self._fraction = 1.0 self._in_thread = False return track = Track(track_id) album_name = escape(track.album_name.lower()) artists = escape(", ".join(track.artists).lower()) on_device_album_uri = "%s/tracks/%s_%s" %\ (self._uri, artists, album_name) d = Gio.File.new_for_uri(on_device_album_uri) if not d.query_exists(None): self._retry(d.make_directory_with_parents, (None,)) # Copy album art art = Lp().art.get_album_artwork_path(track.album) if art is not None: src_art = Gio.File.new_for_path(art) art_uri = "%s/cover.jpg" % on_device_album_uri self._copied_art_uris.append(art_uri) dst_art = Gio.File.new_for_uri(art_uri) if not dst_art.query_exists(None): self._retry(src_art.copy, (dst_art, Gio.FileCopyFlags.OVERWRITE, None, None)) track_name = escape(GLib.basename(track.path)) # Check extension, if not mp3, convert ext = os.path.splitext(track.path)[1] if ext != ".mp3" and self._convert: convertion_needed = True track_name = track_name.replace(ext, ".mp3") else: convertion_needed = False src_track = Gio.File.new_for_path(track.path) info = src_track.query_info('time::modified', Gio.FileQueryInfoFlags.NONE, None) # Prefix track with mtime to make sure updating it later mtime = info.get_attribute_as_string('time::modified') dst_uri = "%s/%s_%s" % (on_device_album_uri, mtime, track_name) if stream is not None: line = "tracks/%s_%s/%s_%s\n" %\ (artists, album_name, mtime, track_name) self._retry(stream.get_output_stream().write, (line.encode(encoding='UTF-8'), None)) dst_track = Gio.File.new_for_uri(dst_uri) if not dst_track.query_exists(None): if convertion_needed: mp3_uri = "file:///tmp/%s" % track_name mp3_file = Gio.File.new_for_uri(mp3_uri) pipeline = self._convert_to_mp3(src_track, mp3_file) # Check if encoding is finished if pipeline is not None: bus = pipeline.get_bus() bus.add_signal_watch() bus.connect('message::eos', self._on_bus_eos) self._encoding = True while self._encoding and self._sync: sleep(1) bus.disconnect_by_func(self._on_bus_eos) pipeline.set_state(Gst.State.PAUSED) pipeline.set_state(Gst.State.READY) pipeline.set_state(Gst.State.NULL) self._retry( mp3_file.move, (dst_track, Gio.FileCopyFlags.OVERWRITE, None, None)) # To be sure try: mp3_file.delete(None) except: pass else: self._retry(src_track.copy, (dst_track, Gio.FileCopyFlags.OVERWRITE, None, None)) else: self._done += 1 self._done += 1 self._fraction = self._done/self._total if stream is not None: stream.close() if m3u is not None: playlist_name = escape(playlist_name) dst = Gio.File.new_for_uri(self._uri+'/'+playlist_name+'.m3u') self._retry(m3u.move, (dst, Gio.FileCopyFlags.OVERWRITE, None, None))