CoverArt Browser
v2.0
Browse your cover-art albums in Rhythmbox
|
00001 # -*- Mode: python; coding: utf-8; tab-width: 4; indent-tabs-mode: nil; -*- 00002 # 00003 # Copyright (C) 2012 - fossfreedom 00004 # Copyright (C) 2012 - Agustin Carrasco 00005 # 00006 # This program is free software; you can redistribute it and/or modify 00007 # it under the terms of thie GNU General Public License as published by 00008 # the Free Software Foundation; either version 2, or (at your option) 00009 # any later version. 00010 # 00011 # This program is distributed in the hope that it will be useful, 00012 # but WITHOUT ANY WARRANTY; without even the implied warranty of 00013 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 00014 # GNU General Public License for more details. 00015 # 00016 # You should have received a copy of the GNU General Public License 00017 # along with this program; if not, write to the Free Software 00018 # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 00019 00020 import shutil 00021 import os 00022 import sys 00023 import subprocess 00024 00025 from gi.repository import GObject 00026 from gi.repository import Gtk 00027 from gi.repository import GLib 00028 from gi.repository import RB 00029 from gi.repository import Gdk 00030 from gi.repository import Peas 00031 from gi.repository import Gst 00032 00033 from coverart_utils import NaturalString 00034 import rb 00035 import coverart_rb3compat as rb3compat 00036 00037 00038 class CoverArtExport(GObject.Object): 00039 ''' 00040 This class provides for various export routines 00041 00042 ''' 00043 TARGET_BITRATE = 128 00044 00045 def __init__(self, plugin, shell, album_manager): 00046 self.plugin = plugin 00047 self.shell = shell 00048 self.album_manager = album_manager 00049 00050 self._gstreamer_has_initialised = False 00051 00052 def is_search_plugin_enabled(self): 00053 peas = Peas.Engine.get_default() 00054 loaded_plugins = peas.get_loaded_plugins() 00055 00056 result = False 00057 if 'coverart_search_providers' in loaded_plugins: 00058 info = peas.get_plugin_info('coverart_search_providers') 00059 version = info.get_version() 00060 00061 if NaturalString(version) >= "0.9": 00062 result = True 00063 00064 return result 00065 00066 def embed_albums(self, selected_albums): 00067 ''' 00068 method to create the menu items for all supported plugins 00069 00070 :selected_albums: `Album` - array of albums 00071 ''' 00072 00073 self._initialise_gstreamer() 00074 00075 from coverart_search_tracks import CoverArtTracks 00076 00077 search_tracks = CoverArtTracks() 00078 playlist_manager = self.shell.props.playlist_manager 00079 playlists_entries = playlist_manager.get_playlists() 00080 00081 ui = Gtk.Builder() 00082 ui.add_from_file(rb.find_plugin_file(self.plugin, 00083 'ui/coverart_exportembed.ui')) 00084 ui.connect_signals(self) 00085 embeddialog = ui.get_object('exportembeddialog') 00086 folderchooserbutton = ui.get_object('folderchooserbutton') 00087 use_album_name_checkbutton = ui.get_object('use_album_name_checkbutton') 00088 open_filemanager_checkbutton = ui.get_object('open_filemanager_checkbutton') 00089 convert_checkbutton = ui.get_object('convert_checkbutton') 00090 bitrate_spinbutton = ui.get_object('bitrate_spinbutton') 00091 resize_checkbutton = ui.get_object('resize_checkbutton') 00092 resize_spinbutton = ui.get_object('resize_spinbutton') 00093 bitrate_spinbutton.set_value(self.TARGET_BITRATE) 00094 resize_spinbutton.set_value(128) 00095 00096 downloads_dir = GLib.get_user_special_dir(GLib.UserDirectory.DIRECTORY_DOWNLOAD) 00097 folderchooserbutton.set_current_folder(downloads_dir) 00098 00099 response = embeddialog.run() 00100 00101 if response != Gtk.ResponseType.OK: 00102 embeddialog.destroy() 00103 return 00104 00105 #ok pressed - now fetch values from the dialog 00106 final_folder_store = folderchooserbutton.get_current_folder() 00107 use_album_name = use_album_name_checkbutton.get_active() 00108 open_filemanager = open_filemanager_checkbutton.get_active() 00109 convert = convert_checkbutton.get_active() 00110 bitrate = bitrate_spinbutton.get_value() 00111 toresize = resize_checkbutton.get_active() 00112 if toresize: 00113 resize = int(resize_spinbutton.get_value()) 00114 else: 00115 resize = -1 00116 00117 embeddialog.destroy() 00118 00119 albums = {} 00120 total = 0 00121 00122 for album in selected_albums: 00123 albums[album] = album.get_tracks() 00124 total = total + len(albums[album]) 00125 00126 self._track_count = 1 00127 00128 def complete(): 00129 self.album_manager.progress = 1 00130 00131 if open_filemanager: 00132 #code taken from http://stackoverflow.com/questions/1795111/is-there-a-cross-platform-way-to-open-a-file-browser-in-python 00133 if sys.platform == 'win32': 00134 import winreg 00135 00136 path = r('SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon') 00137 for root in (winreg.HKEY_CURRENT_USER, winreg.HKEY_LOCAL_MACHINE): 00138 try: 00139 with winreg.OpenKey(root, path) as k: 00140 value, regtype = winreg.QueryValueEx(k, 'Shell') 00141 except WindowsError: 00142 pass 00143 else: 00144 if regtype in (winreg.REG_SZ, winreg.REG_EXPAND_SZ): 00145 shell = value 00146 break 00147 else: 00148 shell = 'Explorer.exe' 00149 subprocess.Popen([shell, final_folder_store]) 00150 00151 elif sys.platform == 'darwin': 00152 subprocess.Popen(['open', final_folder_store]) 00153 00154 else: 00155 subprocess.Popen(['xdg-open', final_folder_store]) 00156 00157 self._albumiter = iter(albums) 00158 self._tracknumber = 0 00159 self._album = next(self._albumiter) 00160 00161 def idle_call(data): 00162 exit_idle = True 00163 00164 track = albums[self._album][self._tracknumber] 00165 00166 if not process_track(self._album, track): 00167 exit_idle = False 00168 00169 self._tracknumber = self._tracknumber + 1 00170 00171 if self._tracknumber >= len(albums[self._album]): 00172 try: 00173 self._tracknumber = 0 00174 self._album = next(self._albumiter) 00175 except StopIteration: 00176 exit_idle = False 00177 00178 if not exit_idle: 00179 complete() 00180 00181 return exit_idle 00182 00183 def process_track(album, track): 00184 self.album_manager.progress = self._track_count / total 00185 self._track_count = self._track_count + 1 00186 00187 key = album.create_ext_db_key() 00188 finalPath = rb3compat.unquote(track.location)[7:] 00189 album_name = RB.search_fold(album.name) 00190 00191 if use_album_name: 00192 folder_store = final_folder_store + '/' + album_name 00193 else: 00194 folder_store = final_folder_store 00195 00196 try: 00197 if not os.path.exists(folder_store): 00198 os.makedirs(folder_store) 00199 00200 if convert: 00201 self.convert_to_mp3(finalPath, folder_store, bitrate) 00202 finalPath = self._calc_mp3_filename(finalPath, folder_store) 00203 print(finalPath) 00204 else: 00205 shutil.copy(finalPath, folder_store) 00206 except IOError as err: 00207 print(err.args[0]) 00208 return False 00209 00210 dest = os.path.join(folder_store, os.path.basename(finalPath)) 00211 desturi = 'file://' + rb3compat.pathname2url(dest) 00212 00213 return search_tracks.embed(desturi, key, resize) 00214 00215 data = None 00216 00217 Gdk.threads_add_idle(GLib.PRIORITY_DEFAULT_IDLE, idle_call, data) 00218 00219 def _initialise_gstreamer(self): 00220 00221 if self._gstreamer_has_initialised: 00222 return 00223 00224 self._gstreamer_has_initialised = True 00225 Gst.init(None) 00226 00227 def on_new_decoded_pad(dbin, pad): 00228 decode = pad.get_parent() 00229 pipeline = decode.get_parent() 00230 convert = pipeline.get_by_name('convert') 00231 decode.link(convert) 00232 00233 #we are going to mimic the following 00234 # gst-launch-1.0 filesrc location="02 - ABBA - Knowing Me, Knowing You.ogg" ! 00235 # decodebin ! audioconvert ! audioresample ! lamemp3enc target=bitrate bitrate=128 ! 00236 # xingmux ! id3v2mux ! filesink location="mytrack.mp3" 00237 00238 converter = Gst.Pipeline.new('converter') 00239 00240 source = Gst.ElementFactory.make('filesrc', None) 00241 00242 decoder = Gst.ElementFactory.make('decodebin', 'decoder') 00243 convert = Gst.ElementFactory.make('audioconvert', 'convert') 00244 sample = Gst.ElementFactory.make('audioresample', 'sample') 00245 encoder = Gst.ElementFactory.make('lamemp3enc', 'encoder') 00246 encoder.set_property('target', 'bitrate') 00247 encoder.set_property('bitrate', self.TARGET_BITRATE) 00248 00249 xing = Gst.ElementFactory.make('xingmux', 'xing') # needed to make bitrate more accurate 00250 mux = Gst.ElementFactory.make('id3v2mux', 'mux') 00251 if not mux: 00252 # use id3mux where not available 00253 mux = Gst.ElementFactory.make('id3mux', 'mux') 00254 00255 sink = Gst.ElementFactory.make('filesink', 'sink') 00256 00257 converter.add(source) 00258 converter.add(decoder) 00259 converter.add(convert) 00260 converter.add(sample) 00261 converter.add(encoder) 00262 converter.add(xing) 00263 converter.add(mux) 00264 converter.add(sink) 00265 00266 Gst.Element.link(source, decoder) 00267 #note - a decodebin cannot be linked at compile since 00268 #it doesnt have source-pads (http://stackoverflow.com/questions/2993777/gstreamer-of-pythons-gst-linkerror-problem) 00269 00270 decoder.connect("pad-added", on_new_decoded_pad) 00271 00272 Gst.Element.link(convert, sample) 00273 Gst.Element.link(sample, encoder) 00274 Gst.Element.link(encoder, xing) 00275 Gst.Element.link(xing, mux) 00276 Gst.Element.link(mux, sink) 00277 00278 self.converter = converter 00279 self.source = source 00280 self.sink = sink 00281 self.encoder = encoder 00282 00283 def _calc_mp3_filename(self, filename, save_folder): 00284 finalname = os.path.basename(filename) 00285 finalname = finalname.rsplit('.')[0] + ".mp3" 00286 return save_folder + "/" + finalname 00287 00288 def convert_to_mp3(self, filename, save_folder, bitrate): 00289 00290 self.source.set_property('location', filename) 00291 self.sink.set_property('location', self._calc_mp3_filename(filename, save_folder)) 00292 print(bitrate) 00293 if bitrate < 32: 00294 bitrate = self.TARGET_BITRATE 00295 00296 self.encoder.set_property('bitrate', int(bitrate)) 00297 print(bitrate) 00298 00299 # Start playing 00300 ret = self.converter.set_state(Gst.State.PLAYING) 00301 00302 if ret == Gst.StateChangeReturn.FAILURE: 00303 print("Unable to set the pipeline to the playing state.", sys.stderr) 00304 exit(-1) 00305 00306 # Wait until error or EOS 00307 bus = self.converter.get_bus() 00308 try: 00309 msg = bus.timed_pop_filtered( 00310 Gst.CLOCK_TIME_NONE, Gst.MessageType.ERROR | Gst.MessageType.EOS) 00311 except: 00312 # for some reason in ubuntu 12.04 Gst.CLOCK_TIME_NONE fails 00313 msg = bus.timed_pop_filtered( 00314 18446744073709551615, Gst.MessageType.ERROR | Gst.MessageType.EOS) 00315 00316 # Parse message 00317 if (msg): 00318 if msg.type == Gst.MessageType.ERROR: 00319 err, debug = msg.parse_error() 00320 print("Error received from element %s: %s" % ( 00321 msg.src.get_name(), err), sys.stderr) 00322 print("Debugging information: %s" % debug, sys.stderr) 00323 elif msg.type == Gst.MessageType.EOS: 00324 print("End-Of-Stream reached.") 00325 else: 00326 print("Unexpected message received.", sys.stderr) 00327 00328 # Free resources 00329 self.converter.set_state(Gst.State.NULL)