330 linhas
12 KiB
Python
330 linhas
12 KiB
Python
# -*- Mode: python; coding: utf-8; tab-width: 4; indent-tabs-mode: nil; -*-
|
|
#
|
|
# Copyright (C) 2012 - fossfreedom
|
|
# Copyright (C) 2012 - Agustin Carrasco
|
|
#
|
|
# This program is free software; you can redistribute it and/or modify
|
|
# it under the terms of thie GNU General Public License as published by
|
|
# the Free Software Foundation; either version 2, or (at your option)
|
|
# any later version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with this program; if not, write to the Free Software
|
|
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
|
|
import shutil
|
|
import os
|
|
import sys
|
|
import subprocess
|
|
|
|
from gi.repository import GObject
|
|
from gi.repository import Gtk
|
|
from gi.repository import GLib
|
|
from gi.repository import RB
|
|
from gi.repository import Gdk
|
|
from gi.repository import Peas
|
|
from gi.repository import Gst
|
|
|
|
from coverart_utils import NaturalString
|
|
import rb
|
|
import coverart_rb3compat as rb3compat
|
|
|
|
|
|
class CoverArtExport(GObject.Object):
|
|
'''
|
|
This class provides for various export routines
|
|
|
|
'''
|
|
TARGET_BITRATE = 128
|
|
|
|
def __init__(self, plugin, shell, album_manager):
|
|
self.plugin = plugin
|
|
self.shell = shell
|
|
self.album_manager = album_manager
|
|
|
|
self._gstreamer_has_initialised = False
|
|
|
|
def is_search_plugin_enabled(self):
|
|
peas = Peas.Engine.get_default()
|
|
loaded_plugins = peas.get_loaded_plugins()
|
|
|
|
result = False
|
|
if 'coverart_search_providers' in loaded_plugins:
|
|
info = peas.get_plugin_info('coverart_search_providers')
|
|
version = info.get_version()
|
|
|
|
if NaturalString(version) >= "0.9":
|
|
result = True
|
|
|
|
return result
|
|
|
|
def embed_albums(self, selected_albums):
|
|
'''
|
|
method to create the menu items for all supported plugins
|
|
|
|
:selected_albums: `Album` - array of albums
|
|
'''
|
|
|
|
self._initialise_gstreamer()
|
|
|
|
from coverart_search_tracks import CoverArtTracks
|
|
|
|
search_tracks = CoverArtTracks()
|
|
playlist_manager = self.shell.props.playlist_manager
|
|
playlists_entries = playlist_manager.get_playlists()
|
|
|
|
ui = Gtk.Builder()
|
|
ui.add_from_file(rb.find_plugin_file(self.plugin,
|
|
'ui/coverart_exportembed.ui'))
|
|
ui.connect_signals(self)
|
|
embeddialog = ui.get_object('exportembeddialog')
|
|
folderchooserbutton = ui.get_object('folderchooserbutton')
|
|
use_album_name_checkbutton = ui.get_object('use_album_name_checkbutton')
|
|
open_filemanager_checkbutton = ui.get_object('open_filemanager_checkbutton')
|
|
convert_checkbutton = ui.get_object('convert_checkbutton')
|
|
bitrate_spinbutton = ui.get_object('bitrate_spinbutton')
|
|
resize_checkbutton = ui.get_object('resize_checkbutton')
|
|
resize_spinbutton = ui.get_object('resize_spinbutton')
|
|
bitrate_spinbutton.set_value(self.TARGET_BITRATE)
|
|
resize_spinbutton.set_value(128)
|
|
|
|
downloads_dir = GLib.get_user_special_dir(GLib.UserDirectory.DIRECTORY_DOWNLOAD)
|
|
folderchooserbutton.set_current_folder(downloads_dir)
|
|
|
|
response = embeddialog.run()
|
|
|
|
if response != Gtk.ResponseType.OK:
|
|
embeddialog.destroy()
|
|
return
|
|
|
|
# ok pressed - now fetch values from the dialog
|
|
final_folder_store = folderchooserbutton.get_current_folder()
|
|
use_album_name = use_album_name_checkbutton.get_active()
|
|
open_filemanager = open_filemanager_checkbutton.get_active()
|
|
convert = convert_checkbutton.get_active()
|
|
bitrate = bitrate_spinbutton.get_value()
|
|
toresize = resize_checkbutton.get_active()
|
|
if toresize:
|
|
resize = int(resize_spinbutton.get_value())
|
|
else:
|
|
resize = -1
|
|
|
|
embeddialog.destroy()
|
|
|
|
albums = {}
|
|
total = 0
|
|
|
|
for album in selected_albums:
|
|
albums[album] = album.get_tracks()
|
|
total = total + len(albums[album])
|
|
|
|
self._track_count = 1
|
|
|
|
def complete():
|
|
self.album_manager.progress = 1
|
|
|
|
if open_filemanager:
|
|
#code taken from http://stackoverflow.com/questions/1795111/is-there-a-cross-platform-way-to-open-a-file-browser-in-python
|
|
if sys.platform == 'win32':
|
|
import winreg
|
|
|
|
path = r('SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon')
|
|
for root in (winreg.HKEY_CURRENT_USER, winreg.HKEY_LOCAL_MACHINE):
|
|
try:
|
|
with winreg.OpenKey(root, path) as k:
|
|
value, regtype = winreg.QueryValueEx(k, 'Shell')
|
|
except WindowsError:
|
|
pass
|
|
else:
|
|
if regtype in (winreg.REG_SZ, winreg.REG_EXPAND_SZ):
|
|
shell = value
|
|
break
|
|
else:
|
|
shell = 'Explorer.exe'
|
|
subprocess.Popen([shell, final_folder_store])
|
|
|
|
elif sys.platform == 'darwin':
|
|
subprocess.Popen(['open', final_folder_store])
|
|
|
|
else:
|
|
subprocess.Popen(['xdg-open', final_folder_store])
|
|
|
|
self._albumiter = iter(albums)
|
|
self._tracknumber = 0
|
|
self._album = next(self._albumiter)
|
|
|
|
def idle_call(data):
|
|
exit_idle = True
|
|
|
|
track = albums[self._album][self._tracknumber]
|
|
|
|
if not process_track(self._album, track):
|
|
exit_idle = False
|
|
|
|
self._tracknumber = self._tracknumber + 1
|
|
|
|
if self._tracknumber >= len(albums[self._album]):
|
|
try:
|
|
self._tracknumber = 0
|
|
self._album = next(self._albumiter)
|
|
except StopIteration:
|
|
exit_idle = False
|
|
|
|
if not exit_idle:
|
|
complete()
|
|
|
|
return exit_idle
|
|
|
|
def process_track(album, track):
|
|
self.album_manager.progress = self._track_count / total
|
|
self._track_count = self._track_count + 1
|
|
|
|
key = album.create_ext_db_key()
|
|
finalPath = rb3compat.unquote(track.location)[7:]
|
|
album_name = RB.search_fold(album.name)
|
|
|
|
if use_album_name:
|
|
folder_store = final_folder_store + '/' + album_name
|
|
else:
|
|
folder_store = final_folder_store
|
|
|
|
try:
|
|
if not os.path.exists(folder_store):
|
|
os.makedirs(folder_store)
|
|
|
|
if convert:
|
|
self.convert_to_mp3(finalPath, folder_store, bitrate)
|
|
finalPath = self._calc_mp3_filename(finalPath, folder_store)
|
|
print(finalPath)
|
|
else:
|
|
shutil.copy(finalPath, folder_store)
|
|
except IOError as err:
|
|
print(err.args[0])
|
|
return False
|
|
|
|
dest = os.path.join(folder_store, os.path.basename(finalPath))
|
|
desturi = 'file://' + rb3compat.pathname2url(dest)
|
|
|
|
return search_tracks.embed(desturi, key, resize)
|
|
|
|
data = None
|
|
|
|
Gdk.threads_add_idle(GLib.PRIORITY_DEFAULT_IDLE, idle_call, data)
|
|
|
|
def _initialise_gstreamer(self):
|
|
|
|
if self._gstreamer_has_initialised:
|
|
return
|
|
|
|
self._gstreamer_has_initialised = True
|
|
Gst.init(None)
|
|
|
|
def on_new_decoded_pad(dbin, pad):
|
|
decode = pad.get_parent()
|
|
pipeline = decode.get_parent()
|
|
convert = pipeline.get_by_name('convert')
|
|
decode.link(convert)
|
|
|
|
# we are going to mimic the following
|
|
# gst-launch-1.0 filesrc location="02 - ABBA - Knowing Me, Knowing You.ogg" !
|
|
# decodebin ! audioconvert ! audioresample ! lamemp3enc target=bitrate bitrate=128 !
|
|
# xingmux ! id3v2mux ! filesink location="mytrack.mp3"
|
|
|
|
converter = Gst.Pipeline.new('converter')
|
|
|
|
source = Gst.ElementFactory.make('filesrc', None)
|
|
|
|
decoder = Gst.ElementFactory.make('decodebin', 'decoder')
|
|
convert = Gst.ElementFactory.make('audioconvert', 'convert')
|
|
sample = Gst.ElementFactory.make('audioresample', 'sample')
|
|
encoder = Gst.ElementFactory.make('lamemp3enc', 'encoder')
|
|
encoder.set_property('target', 'bitrate')
|
|
encoder.set_property('bitrate', self.TARGET_BITRATE)
|
|
|
|
xing = Gst.ElementFactory.make('xingmux', 'xing') # needed to make bitrate more accurate
|
|
mux = Gst.ElementFactory.make('id3v2mux', 'mux')
|
|
if not mux:
|
|
# use id3mux where not available
|
|
mux = Gst.ElementFactory.make('id3mux', 'mux')
|
|
|
|
sink = Gst.ElementFactory.make('filesink', 'sink')
|
|
|
|
converter.add(source)
|
|
converter.add(decoder)
|
|
converter.add(convert)
|
|
converter.add(sample)
|
|
converter.add(encoder)
|
|
converter.add(xing)
|
|
converter.add(mux)
|
|
converter.add(sink)
|
|
|
|
Gst.Element.link(source, decoder)
|
|
#note - a decodebin cannot be linked at compile since
|
|
#it doesnt have source-pads (http://stackoverflow.com/questions/2993777/gstreamer-of-pythons-gst-linkerror-problem)
|
|
|
|
decoder.connect("pad-added", on_new_decoded_pad)
|
|
|
|
Gst.Element.link(convert, sample)
|
|
Gst.Element.link(sample, encoder)
|
|
Gst.Element.link(encoder, xing)
|
|
Gst.Element.link(xing, mux)
|
|
Gst.Element.link(mux, sink)
|
|
|
|
self.converter = converter
|
|
self.source = source
|
|
self.sink = sink
|
|
self.encoder = encoder
|
|
|
|
def _calc_mp3_filename(self, filename, save_folder):
|
|
finalname = os.path.basename(filename)
|
|
finalname = finalname.rsplit('.')[0] + ".mp3"
|
|
return save_folder + "/" + finalname
|
|
|
|
def convert_to_mp3(self, filename, save_folder, bitrate):
|
|
|
|
self.source.set_property('location', filename)
|
|
self.sink.set_property('location', self._calc_mp3_filename(filename, save_folder))
|
|
print(bitrate)
|
|
if bitrate < 32:
|
|
bitrate = self.TARGET_BITRATE
|
|
|
|
self.encoder.set_property('bitrate', int(bitrate))
|
|
print(bitrate)
|
|
|
|
# Start playing
|
|
ret = self.converter.set_state(Gst.State.PLAYING)
|
|
|
|
if ret == Gst.StateChangeReturn.FAILURE:
|
|
print("Unable to set the pipeline to the playing state.", sys.stderr)
|
|
exit(-1)
|
|
|
|
# Wait until error or EOS
|
|
bus = self.converter.get_bus()
|
|
try:
|
|
msg = bus.timed_pop_filtered(
|
|
Gst.CLOCK_TIME_NONE, Gst.MessageType.ERROR | Gst.MessageType.EOS)
|
|
except:
|
|
# for some reason in ubuntu 12.04 Gst.CLOCK_TIME_NONE fails
|
|
msg = bus.timed_pop_filtered(
|
|
18446744073709551615, Gst.MessageType.ERROR | Gst.MessageType.EOS)
|
|
|
|
# Parse message
|
|
if (msg):
|
|
if msg.type == Gst.MessageType.ERROR:
|
|
err, debug = msg.parse_error()
|
|
print("Error received from element %s: %s" % (
|
|
msg.src.get_name(), err), sys.stderr)
|
|
print("Debugging information: %s" % debug, sys.stderr)
|
|
elif msg.type == Gst.MessageType.EOS:
|
|
print("End-Of-Stream reached.")
|
|
else:
|
|
print("Unexpected message received.", sys.stderr)
|
|
|
|
# Free resources
|
|
self.converter.set_state(Gst.State.NULL)
|