CoverArt Browser  v2.0
Browse your cover-art albums in Rhythmbox
/home/foss/Downloads/coverart-browser/coverart_export.py
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)
 All Classes Functions