diff -Nru playitslowly-1.4.0/bin/playitslowly playitslowly-1.5.0/bin/playitslowly --- playitslowly-1.4.0/bin/playitslowly 2010-12-08 14:05:51.000000000 +0000 +++ playitslowly-1.5.0/bin/playitslowly 2015-12-21 21:44:24.000000000 +0000 @@ -1,2 +1,2 @@ #!/bin/sh -exec python -m playitslowly.app "$@" +exec python3 -m playitslowly.app "$@" diff -Nru playitslowly-1.4.0/CHANGELOG playitslowly-1.5.0/CHANGELOG --- playitslowly-1.4.0/CHANGELOG 2011-07-03 13:27:12.000000000 +0000 +++ playitslowly-1.5.0/CHANGELOG 2015-12-22 17:10:17.000000000 +0000 @@ -1,3 +1,9 @@ +playitslowly 1.5 +================ + * Update to Python3, GTK3 and GStreamer 1.0 + * Minor design cleanup + * Fixed a bug where the file chooser would not show the currently selected file + playitslowly 1.4 ================ * Added now and reset buttons diff -Nru playitslowly-1.4.0/debian/changelog playitslowly-1.5.0/debian/changelog --- playitslowly-1.4.0/debian/changelog 2015-12-14 01:40:46.000000000 +0000 +++ playitslowly-1.5.0/debian/changelog 2015-12-27 18:14:02.000000000 +0000 @@ -1,3 +1,17 @@ +playitslowly (1.5.0-0.1) unstable; urgency=medium + + * Non-Maintainer upload. + * Imported Upstream version 1.5.0. + + Update dependencies after new release, python3 instead of python2, + gst 1.0 instead of 0.10. Closes: #785918 + * Add debian/gbp.conf file to specify the branches. + * debian/rules: + + Build using dh instead of cdbs. + + Build using pybuild. + * debian/copyright: use machine readable copyright format 1.0 + + -- Mattia Rizzolo Sun, 27 Dec 2015 18:12:58 +0000 + playitslowly (1.4.0-1.1) unstable; urgency=medium * Non-maintainer upload. diff -Nru playitslowly-1.4.0/debian/control playitslowly-1.5.0/debian/control --- playitslowly-1.4.0/debian/control 2015-12-14 01:40:34.000000000 +0000 +++ playitslowly-1.5.0/debian/control 2015-12-27 18:00:42.000000000 +0000 @@ -2,16 +2,16 @@ Section: gnome Priority: optional Maintainer: Tiago Bortoletto Vaz -Build-Depends: debhelper (>= 9), dh-python, cdbs (>= 0.4.49), python +Build-Depends: debhelper (>= 9), dh-python, python3 Standards-Version: 3.9.6 Vcs-Browser: https://anonscm.debian.org/cgit/collab-maint/playitslowly.git Vcs-Git: git://anonscm.debian.org/collab-maint/playitslowly.git Homepage: http://29a.ch/playitslowly -X-Python-Version: >= 2.5 +X-Python3-Version: >= 3.4 Package: playitslowly Architecture: all -Depends: ${misc:Depends}, ${python:Depends}, python-gtk2 (>= 2.10), python (>= 2.6), gstreamer0.10-plugins-bad, python-gst0.10, gstreamer0.10-plugins-good +Depends: ${misc:Depends}, ${python3:Depends}, python3-gi, gstreamer1.0-plugins-bad, python3-gst-1.0, gstreamer1.0-plugins-good Description: Plays back audio files at a different speed or pitch Play it slowly is a piece of software to play back audio files at a different speed or pitch. It also allows you to loop over a certain part of a file. diff -Nru playitslowly-1.4.0/debian/copyright playitslowly-1.5.0/debian/copyright --- playitslowly-1.4.0/debian/copyright 2015-12-14 01:25:59.000000000 +0000 +++ playitslowly-1.5.0/debian/copyright 2015-12-27 18:07:02.000000000 +0000 @@ -1,36 +1,29 @@ -This package was debianized by Jonas Wagner on Sat, 18 Oct 2008 20:13:44 +0200. -It has been modified by Tiago Bortoletto Vaz in order to -comply with Debian Policy. - -It was downloaded from http://29a.ch/playitslowy/ - -Upstream Author: - - Jonas Wagner +Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ +Upstream-Name: playitslowly +Upstream-Contact: Jonas Wagner +Source: from http://29a.ch/playitslowy Files: * Copyright: 2009-2010, Jonas Wagner -License: GPL +License: GPL-2+ Files: debian/* -Copyright: - 2010, Jonas Wagner -License: GPL - -License: GPL - This package is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This package 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 package; if not, see . - -On Debian systems, the complete text of the GNU General -Public License can be found in `/usr/share/common-licenses/GPL-3'. +Copyright: 2010 Jonas Wagner + 2010-2012 Tiago Bortoletto Vaz + 2015 Mattia Rizzolo +License: GPL-2+ + +License: GPL-2+ + This package is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + . + This package 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. + . + On Debian systems, the complete text of the GNU General + Public License can be found in `/usr/share/common-licenses/GPL-3'. diff -Nru playitslowly-1.4.0/debian/docs playitslowly-1.5.0/debian/docs --- playitslowly-1.4.0/debian/docs 1970-01-01 00:00:00.000000000 +0000 +++ playitslowly-1.5.0/debian/docs 2015-12-27 18:01:32.000000000 +0000 @@ -0,0 +1 @@ +README diff -Nru playitslowly-1.4.0/debian/gbp.conf playitslowly-1.5.0/debian/gbp.conf --- playitslowly-1.4.0/debian/gbp.conf 1970-01-01 00:00:00.000000000 +0000 +++ playitslowly-1.5.0/debian/gbp.conf 2015-12-27 15:57:10.000000000 +0000 @@ -0,0 +1,3 @@ +[DEFAULT] +debian-branch = debian +upstream-branch = upstream diff -Nru playitslowly-1.4.0/debian/rules playitslowly-1.5.0/debian/rules --- playitslowly-1.4.0/debian/rules 2015-12-14 01:31:51.000000000 +0000 +++ playitslowly-1.5.0/debian/rules 2015-12-27 17:52:23.000000000 +0000 @@ -1,11 +1,7 @@ #!/usr/bin/make -f -# -*- makefile -*- -DEB_PYTHON2_MODULE_PACKAGES := playitslowly +export DH_VERBOSE=1 +export PYBUILD_NAME=playitslowly -include /usr/share/cdbs/1/rules/debhelper.mk -include /usr/share/cdbs/1/class/python-distutils.mk - -clean:: - rm -rf build build-stamp configure-stamp build/ MANIFEST - dh_clean +%: + dh $@ --with python3 --buildsystem=pybuild diff -Nru playitslowly-1.4.0/install.sh playitslowly-1.5.0/install.sh --- playitslowly-1.4.0/install.sh 2010-12-08 14:15:40.000000000 +0000 +++ playitslowly-1.5.0/install.sh 2015-12-22 17:10:17.000000000 +0000 @@ -1,3 +1,4 @@ -#!/bin/sh -gksu -u root -- python setup.py install -zenity --info --text "'Play it slowly' has been installed" +#!/bin/bash +CWD=`pwd` +pkexec bash -c "cd $CWD && python3 setup.py install && desktop-file-install /usr/local/share/applications/playitslowly.desktop" +zenity --info --text "'Play it Slowly' has been installed" diff -Nru playitslowly-1.4.0/PKG-INFO playitslowly-1.5.0/PKG-INFO --- playitslowly-1.4.0/PKG-INFO 2011-07-03 13:51:34.000000000 +0000 +++ playitslowly-1.5.0/PKG-INFO 2015-12-22 17:21:15.000000000 +0000 @@ -1,14 +1,13 @@ -Metadata-Version: 1.0 +Metadata-Version: 1.1 Name: playitslowly -Version: 1.4.0 +Version: 1.5.0 Summary: A tool to help you when transcribing music. It allows you to play a piece of music at a different speed or pitch. Home-page: http://29a.ch/playitslowly/ Author: Jonas Wagner -Author-email: veers@gmx.ch +Author-email: jonas@29a.ch License: GNU GPL v3 Description: UNKNOWN Platform: UNKNOWN -Classifier: Development Status :: 4 - Beta Classifier: Environment :: X11 Applications :: GTK Classifier: Intended Audience :: End Users/Desktop Classifier: License :: OSI Approved :: GNU General Public License (GPL) diff -Nru playitslowly-1.4.0/playitslowly/app.py playitslowly-1.5.0/playitslowly/app.py --- playitslowly-1.4.0/playitslowly/app.py 2011-07-03 13:27:33.000000000 +0000 +++ playitslowly-1.5.0/playitslowly/app.py 2015-12-22 17:10:17.000000000 +0000 @@ -1,11 +1,10 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 # vim: set fileencoding=utf-8 : -from __future__ import with_statement """ Author: Jonas Wagner -Play it slowly -Copyright (C) 2009 - 2011 Jonas Wagner +Play it Slowly +Copyright (C) 2009 - 2015 Jonas Wagner This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -31,26 +30,27 @@ except ImportError: import simplejson as json -import gobject -gobject.threads_init() +import gi +gi.require_version('Gst', '1.0') -import pygtk -pygtk.require('2.0') -import gtk +from gi.repository import Gtk, GObject, Gst, Gio, Gdk + +GObject.threads_init() +Gst.init(None) + +from playitslowly.pipeline import Pipeline # always enable button images -gtk.settings_get_default().set_long_property("gtk-button-images", True, "main") +Gtk.Settings.get_default().set_long_property("gtk-button-images", True, "main") -from playitslowly import mygtk -mygtk.install() +from playitslowly import myGtk +myGtk.install() -from playitslowly.pipeline import Pipeline -import gst # this has to be after the Pipeline _ = lambda s: s # may be add gettext later -NAME = u"Play it slowly" -VERSION = "1.4.0" +NAME = "Play it Slowly" +VERSION = "1.5.0" WEBSITE = "http://29a.ch/playitslowly/" if sys.platform == "win32": @@ -61,7 +61,7 @@ os.mkdir(XDG_CONFIG_HOME) CONFIG_PATH = os.path.join(XDG_CONFIG_HOME, "playitslowly.json") -TIME_FORMAT = gst.Format(gst.FORMAT_TIME) +TIME_FORMAT = Gst.Format(Gst.Format.TIME) def in_pathlist(filename, paths = os.environ.get("PATH").split(os.pathsep)): """check if an application is somewhere in $PATH""" @@ -83,113 +83,116 @@ self.update(data) def save(self): - with open(self.path, "wb") as f: + with open(self.path, "w") as f: json.dump(self, f) -class MainWindow(gtk.Window): +class MainWindow(Gtk.Window): def __init__(self, sink, config): - gtk.Window.__init__(self,gtk.WINDOW_TOPLEVEL) + Gtk.Window.__init__(self, Gtk.WindowType.TOPLEVEL) self.set_title(NAME) try: - self.set_icon(mygtk.iconfactory.get_icon("playitslowly", 128)) - except gobject.GError: - print "could not load playitslowly icon" + self.set_icon(myGtk.iconfactory.get_icon("playitslowly", 128)) + except GObject.GError: + print("could not load playitslowly icon") - self.set_default_size(500, 200) + self.set_default_size(600, 200) self.set_border_width(5) - self.vbox = gtk.VBox() - self.accel_group = gtk.AccelGroup() + self.vbox = Gtk.VBox() + self.accel_group = Gtk.AccelGroup() self.add_accel_group(self.accel_group) self.pipeline = Pipeline(sink) - self.filedialog = mygtk.FileChooserDialog(None, self, gtk.FILE_CHOOSER_ACTION_OPEN) + self.filedialog = myGtk.FileChooserDialog(None, self, Gtk.FileChooserAction.OPEN) self.filedialog.connect("response", self.filechanged) - filechooserhbox = gtk.HBox() - self.filechooser = gtk.FileChooserButton(self.filedialog) - filechooserhbox.pack_start(self.filechooser, True, True) - self.recentbutton = gtk.Button(_("Recent")) + self.filedialog.set_local_only(False) + filechooserhbox = Gtk.HBox() + self.filechooser = Gtk.FileChooserButton.new_with_dialog(self.filedialog) + self.filechooser.set_local_only(False) + filechooserhbox.pack_start(self.filechooser, True, True, 0) + self.recentbutton = Gtk.Button(_("Recent")) self.recentbutton.connect("clicked", self.show_recent) - filechooserhbox.pack_end(self.recentbutton, False, False) + filechooserhbox.pack_end(self.recentbutton, False, False, 0) - self.speedchooser = mygtk.TextScaleReset(gtk.Adjustment(1.00, 0.10, 4.0, 0.05, 0.05)) + self.speedchooser = myGtk.TextScaleReset(Gtk.Adjustment.new(1.00, 0.10, 4.0, 0.05, 0.05, 0)) self.speedchooser.scale.connect("value-changed", self.speedchanged) self.speedchooser.scale.connect("button-press-event", self.speedpress) self.speedchooser.scale.connect("button-release-event", self.speedrelease) self.speedchangeing = False - self.pitchchooser = mygtk.TextScaleReset(gtk.Adjustment(0.0, -24.0, 24.0, 1.0, 1.0, 1.0)) + pitch_adjustment = Gtk.Adjustment.new(0.0, -24.0, 24.0, 1.0, 1.0, 1.0) + self.pitchchooser = myGtk.TextScaleReset(pitch_adjustment) self.pitchchooser.scale.connect("value-changed", self.pitchchanged) - self.pitchchooser_fine = mygtk.TextScaleReset(gtk.Adjustment(0.0, -50, 50, 1.0, 1.0, 1.0)) + self.pitchchooser_fine = myGtk.TextScaleReset(Gtk.Adjustment.new(0.0, -50, 50, 1.0, 1.0, 1.0)) self.pitchchooser_fine.scale.connect("value-changed", self.pitchchanged) - self.positionchooser = mygtk.ClockScale(gtk.Adjustment(0.0, 0.0, 100.0)) + self.positionchooser = myGtk.ClockScale(Gtk.Adjustment.new(0.0, 0.0, 100.0, 0, 0, 0)) self.positionchooser.scale.connect("button-press-event", self.start_seeking) self.positionchooser.scale.connect("button-release-event", self.positionchanged) self.seeking = False - self.startchooser = mygtk.TextScaleWithCurPos(self.positionchooser, gtk.Adjustment(0.0, 0, 100.0)) + self.startchooser = myGtk.TextScaleWithCurPos(self.positionchooser, Gtk.Adjustment.new(0.0, 0, 100.0, 0, 0, 0)) self.startchooser.scale.connect("button-press-event", self.start_seeking) self.startchooser.scale.connect("button-release-event", self.seeked) - self.startchooser.add_accelerator("clicked", self.accel_group, ord('['), gtk.gdk.CONTROL_MASK, ()) - self.startchooser.add_accelerator("clicked", self.accel_group, ord('['), 0, ()) + self.startchooser.add_accelerator("clicked", self.accel_group, ord('['), Gdk.ModifierType.CONTROL_MASK, Gtk.AccelFlags.VISIBLE) + self.startchooser.add_accelerator("clicked", self.accel_group, ord('['), 0, Gtk.AccelFlags.VISIBLE) - self.endchooser = mygtk.TextScaleWithCurPos(self.positionchooser, gtk.Adjustment(1.0, 0, 100.0, 0.01, 0.01)) + self.endchooser = myGtk.TextScaleWithCurPos(self.positionchooser, Gtk.Adjustment.new(1.0, 0, 100.0, 0.01, 0.01, 0)) self.endchooser.scale.connect("button-press-event", self.start_seeking) self.endchooser.scale.connect("button-release-event", self.seeked) - self.endchooser.add_accelerator("clicked", self.accel_group, ord(']'), gtk.gdk.CONTROL_MASK, ()) - self.endchooser.add_accelerator("clicked", self.accel_group, ord(']'), 0, ()) + self.endchooser.add_accelerator("clicked", self.accel_group, ord(']'), Gdk.ModifierType.CONTROL_MASK, Gtk.AccelFlags.VISIBLE) + self.endchooser.add_accelerator("clicked", self.accel_group, ord(']'), 0, Gtk.AccelFlags.VISIBLE) - self.vbox.pack_start(filechooserhbox) - self.vbox.pack_start(self.positionchooser) - self.vbox.pack_start(mygtk.form([(_(u"Speed (times)"), self.speedchooser), - (_(u"Pitch (semitones)"), self.pitchchooser), - (_(u"Fine Pitch (cents)"), self.pitchchooser_fine), - (_(u"Start Position (seconds)"), self.startchooser), - (_(u"End Position (seconds)"), self.endchooser) - ]), False, False) + self.vbox.pack_start(filechooserhbox, False, False, 0) + self.vbox.pack_start(self.positionchooser, True, True, 0) + self.vbox.pack_start(myGtk.form([(_("Speed (times)"), self.speedchooser), + (_("Pitch (semitones)"), self.pitchchooser), + (_("Fine Pitch (cents)"), self.pitchchooser_fine), + (_("Start Position (seconds)"), self.startchooser), + (_("End Position (seconds)"), self.endchooser) + ]), False, False, 0) + + buttonbox = Gtk.HButtonBox() + myGtk.add_style_class(buttonbox, 'buttonBox') + self.vbox.pack_end(buttonbox, False, False, 0) - buttonbox = gtk.HButtonBox() - self.vbox.pack_end(buttonbox, False, False) - - self.play_button = gtk.ToggleButton(gtk.STOCK_MEDIA_PLAY) + self.play_button = Gtk.ToggleButton(stock=Gtk.STOCK_MEDIA_PLAY) self.play_button.connect("toggled", self.play) self.play_button.set_use_stock(True) self.play_button.set_sensitive(False) - buttonbox.pack_start(self.play_button) - # make SPACE a shortcut for play/pause (CTRL-SPC would be better?) - self.play_button.add_accelerator("clicked", self.accel_group, ord(' '), 0, ()) + buttonbox.pack_start(self.play_button, True, True, 0) + self.play_button.add_accelerator("clicked", self.accel_group, ord(' '), 0, Gtk.AccelFlags.VISIBLE) - self.back_button = gtk.Button(gtk.STOCK_MEDIA_REWIND) + self.back_button = Gtk.Button.new_from_stock(Gtk.STOCK_MEDIA_REWIND) self.back_button.connect("clicked", self.back) - self.back_button.set_use_stock(True) + #self.back_button.set_use_stock(True) self.back_button.set_sensitive(False) - buttonbox.pack_start(self.back_button) + buttonbox.pack_start(self.back_button, True, True, 0) - self.volume_button = gtk.VolumeButton() + self.volume_button = Gtk.VolumeButton() self.volume_button.set_value(1.0) - self.volume_button.set_relief(gtk.RELIEF_NORMAL) + self.volume_button.set_relief(Gtk.ReliefStyle.NORMAL) self.volume_button.connect("value-changed", self.volumechanged) - buttonbox.pack_start(self.volume_button) + buttonbox.pack_start(self.volume_button, True, True, 0) - self.save_as_button = gtk.Button(stock=gtk.STOCK_SAVE_AS) + self.save_as_button = Gtk.Button.new_from_stock(Gtk.STOCK_SAVE_AS) self.save_as_button.connect("clicked", self.save) self.save_as_button.set_sensitive(False) - buttonbox.pack_start(self.save_as_button) + buttonbox.pack_start(self.save_as_button, True, True, 0) - button_about = gtk.Button(stock=gtk.STOCK_ABOUT) + button_about = Gtk.Button.new_from_stock(Gtk.STOCK_ABOUT) button_about.connect("clicked", self.about) - buttonbox.pack_end(button_about) + buttonbox.pack_end(button_about, True, True, 0) self.connect("key-release-event", self.key_release) self.add(self.vbox) - self.connect("destroy", gtk.main_quit) + self.connect("destroy", Gtk.main_quit) self.config = config self.config_saving = False @@ -212,46 +215,55 @@ self.pitchchooser_fine.set_value(cents) def add_recent(self, uri): - manager = gtk.recent_manager_get_default() + manager = Gtk.RecentManager.get_default() app_exec = "playitslowly \"%s\"" % uri - mime_type = mimetypes.guess_type(uri)[0] + mime_type, certain = Gio.content_type_guess(uri) if mime_type: - manager.add_full(uri, { - "app_name": "playitslowly", - "app_exec": "playitslowly", - "mime_type": mime_type - }) + recent_data = Gtk.RecentData() + recent_data.app_name = "playitslowly" + recent_data.app_exec = "playitslowly" + recent_data.mime_type = mime_type + manager.add_full(uri, recent_data) + print(app_exec, mime_type) def show_recent(self, sender=None): - dialog = gtk.RecentChooserDialog(_("Recent Files"), self, None, - (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, - gtk.STOCK_OPEN, gtk.RESPONSE_OK)) + dialog = Gtk.RecentChooserDialog(_("Recent Files"), self, None, + (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, + Gtk.STOCK_OPEN, Gtk.ResponseType.OK)) - filter = gtk.RecentFilter() + filter = Gtk.RecentFilter() filter.set_name("playitslowly") filter.add_application("playitslowly") dialog.add_filter(filter) - filter2 = gtk.RecentFilter() + filter2 = Gtk.RecentFilter() filter2.set_name(_("All")) filter2.add_mime_type("audio/*") dialog.add_filter(filter2) + dialog.set_local_only(False) + dialog.set_filter(filter) - if dialog.run() == gtk.RESPONSE_OK and dialog.get_current_item(): + if dialog.run() == Gtk.ResponseType.OK and dialog.get_current_item(): uri = dialog.get_current_item().get_uri() - self.filedialog.set_uri(dialog.get_current_item().get_uri()) - self.filechanged(uri=uri) + if isinstance(uri, bytes): + uri = uri.decode('utf-8') + self.set_uri(uri) dialog.destroy() + def set_uri(self, uri): + print(repr(uri)) + self.filedialog.set_uri(uri) + self.filechooser.set_uri(uri) + self.filechanged(uri=uri) + def load_config(self): self.config_saving = True # do not save while loading lastfile = self.config.get("lastfile") if lastfile: - self.filedialog.set_uri(lastfile) - self.filechanged(uri=lastfile) + self.set_uri(lastfile) self.config_saving = False def reset_settings(self): @@ -264,12 +276,13 @@ self.endchooser.set_value(1.0) def load_file_settings(self, filename): + print("load_file_settings", filename) self.add_recent(filename) if not self.config or not filename in self.config["files"]: self.reset_settings() - self.pipeline.set_file(self.filedialog.get_uri()) + self.pipeline.set_file(filename) self.pipeline.pause() - gobject.timeout_add(100, self.update_position) + GObject.timeout_add(100, self.update_position) return settings = self.config["files"][filename] self.speedchooser.set_value(settings["speed"]) @@ -284,7 +297,7 @@ """saves the config file with a delay""" if self.config_saving: return - gobject.timeout_add(1000, self.save_config_now) + GObject.timeout_add(1000, self.save_config_now) self.config_saving = True def save_config_now(self): @@ -303,7 +316,7 @@ self.config.save() def key_release(self, sender, event): - if not event.state & gtk.gdk.CONTROL_MASK: + if not event.get_state() & Gdk.ModifierType.CONTROL_MASK: return try: val = int(chr(event.keyval)) @@ -316,16 +329,17 @@ self.save_config() def save(self, sender): - dialog = mygtk.FileChooserDialog(_(u"Save modified version as"), - self, gtk.FILE_CHOOSER_ACTION_SAVE) + dialog = myGtk.FileChooserDialog(_("Save modified version as"), + self, Gtk.FileChooserAction.SAVE) dialog.set_current_name("export.wav") - if dialog.run() == gtk.RESPONSE_OK: + if dialog.run() == Gtk.ResponseType.OK: self.pipeline.set_file(self.filedialog.get_uri()) self.foo = self.pipeline.save_file(dialog.get_filename()) dialog.destroy() - def filechanged(self, sender=None, response_id=gtk.RESPONSE_OK, uri=None): - if response_id != gtk.RESPONSE_OK: + def filechanged(self, sender=None, response_id=Gtk.ResponseType.OK, uri=None): + print("file changed", uri) + if response_id != Gtk.ResponseType.OK: return self.play_button.set_sensitive(True) @@ -342,7 +356,7 @@ else: # for what ever reason filedialog.get_uri() is sometimes None until the # mainloop ran through - gobject.timeout_add(1, lambda: self.load_file_settings(self.filedialog.get_uri())) + GObject.timeout_add(1, lambda: self.load_file_settings(self.filedialog.get_uri())) def start_seeking(self, sender, foo): self.seeking = True @@ -356,11 +370,11 @@ self.seeking = False self.save_config() - def seek(self, pos): + def seek(self, pos=0): if self.positionchooser.get_value() != pos: self.positionchooser.set_value(pos) pos = self.pipeline.pipe_time(pos) - self.pipeline.playbin.seek_simple(TIME_FORMAT, gst.SEEK_FLAG_FLUSH, pos) + self.pipeline.playbin.seek_simple(TIME_FORMAT, Gst.SeekFlags.FLUSH, pos or 0) def speedchanged(self, *args): if self.speedchangeing: @@ -376,9 +390,8 @@ self.save_config() def back(self, sender, amount=None): - try: - position, fmt = self.pipeline.playbin.query_position(TIME_FORMAT, None) - except gst.QueryError: + position, fmt = self.pipeline.playbin.query_position(TIME_FORMAT) + if position is None: return if amount: t = self.pipeline.song_time(position)-amount @@ -392,7 +405,7 @@ if sender.get_active(): self.pipeline.set_file(self.filedialog.get_uri()) self.pipeline.play() - gobject.timeout_add(100, self.update_position) + GObject.timeout_add(100, self.update_position) else: self.pipeline.pause() @@ -400,52 +413,61 @@ """update the position of the scales and pipeline""" if self.seeking: return self.play_button.get_active() - try: - position, fmt = self.pipeline.playbin.query_position(TIME_FORMAT, None) - duration, fmt = self.pipeline.playbin.query_duration(TIME_FORMAT, None) - except gst.QueryError: + + _, position = self.pipeline.playbin.query_position(TIME_FORMAT) + _, duration = self.pipeline.playbin.query_duration(TIME_FORMAT) + if position is None or duration is None: return self.play_button.get_active() + position = position + duration = duration position = self.pipeline.song_time(position) duration = self.pipeline.song_time(duration) - start = self.startchooser.get_value() - end = self.endchooser.get_value() - - if end <= start: - self.play_button.set_active(False) - return False - - if position >= end or position < start: - self.seek(start+0.01) - return True if self.positionchooser.get_adjustment().get_property("upper") != duration: self.positionchooser.set_range(0.0, duration) self.save_config() - end = self.endchooser.get_adjustment() - delta = end.value-end.upper + + end_adjustment = self.endchooser.get_adjustment() + delta = end_adjustment.get_value() - end_adjustment.get_upper() + if delta <= -duration: delta = 0 + self.startchooser.set_range(0.0, duration) self.endchooser.set_range(0.0, duration) self.endchooser.set_value(duration+delta) + self.positionchooser.set_value(position) self.positionchooser.queue_draw() + + start = self.startchooser.get_value() + end = self.endchooser.get_value() + + if end <= start: + self.play_button.set_active(False) + return False + + if position >= end or position < start: + self.seek(start+0.01) + return True + return self.play_button.get_active() def about(self, sender): """show an about dialog""" - about = gtk.AboutDialog() + about = Gtk.AboutDialog() about.set_transient_for(self) - about.set_logo(mygtk.iconfactory.get_icon("playitslowly", 128)) + about.set_logo(myGtk.iconfactory.get_icon("playitslowly", 128)) about.set_name(NAME) + about.set_program_name(NAME) about.set_version(VERSION) about.set_authors(["Jonas Wagner", "Elias Dorneles"]) about.set_translator_credits(_("translator-credits")) - about.set_copyright("Copyright (c) 2009 - 2011 Jonas Wagner") + about.set_copyright("Copyright (c) 2009 - 2015 Jonas Wagner") about.set_website(WEBSITE) about.set_website_label(WEBSITE) about.set_license(""" -Copyright (C) 2009 - 2011 Jonas Wagner +Copyright (C) 2009 - 2015 Jonas Wagner This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or @@ -459,6 +481,14 @@ about.run() about.destroy() +css = b""" +.buttonBox GtkButton GtkLabel { padding-left: 4px; } +""" + + + + + def main(): sink = "autoaudiosink" if in_pathlist("gstreamer-properties"): @@ -466,29 +496,38 @@ options, arguments = getopt.getopt(sys.argv[1:], "h", ["help", "sink="]) for option, argument in options: if option in ("-h", "--help"): - print "Usage: playitslowly [OPTIONS]... [FILE]" - print "Options:" - print '--sink=sink specify gstreamer sink for playback' + print("Usage: playitslowly [OPTIONS]... [FILE]") + print("Options:") + print('--sink=sink specify gstreamer sink for playback') sys.exit() elif option == "--sink": - print "sink", argument + print("sink", argument) sink = argument config = Config(CONFIG_PATH) try: config.load() except IOError: pass - sink = gst.parse_bin_from_description(sink, True) + + style_provider = Gtk.CssProvider() + + style_provider.load_from_data(css) + + Gtk.StyleContext.add_provider_for_screen( + Gdk.Screen.get_default(), + style_provider, + Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION + ) + win = MainWindow(sink, config) + if arguments: uri = arguments[0] if not uri.startswith("file://"): uri = "file://" + os.path.abspath(uri) - - win.filechooser.set_uri(uri) - win.filechanged(uri=uri) + win.set_uri(uri) win.show_all() - gtk.main() + Gtk.main() if __name__ == "__main__": main() diff -Nru playitslowly-1.4.0/playitslowly/mygtk.py playitslowly-1.5.0/playitslowly/mygtk.py --- playitslowly-1.4.0/playitslowly/mygtk.py 2011-07-03 13:16:36.000000000 +0000 +++ playitslowly-1.5.0/playitslowly/mygtk.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,416 +0,0 @@ -""" -Author: Jonas Wagner - -Play it slowly -Copyright (C) 2009 Jonas Wagner - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, 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, see . -""" - -import gtk, gobject -import math -import sys -from datetime import timedelta - -_ = lambda s: s - -buttons_ok_cancel = (gtk.STOCK_CANCEL,gtk.RESPONSE_CANCEL,gtk.STOCK_OPEN,gtk.RESPONSE_OK) -class FileChooserDialog(gtk.FileChooserDialog): - """a file chooser dialog which automatically sets the correct buttons!""" - def __init__(self, title=None, parent=None, action=None): - if action == gtk.FILE_CHOOSER_ACTION_SAVE: - buttons = (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_SAVE ,gtk.RESPONSE_OK) - title = title or _(u"Save File") - else: - if action == gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER: - title = title or _(u"Select Folder") - elif action == gtk.FILE_CHOOSER_ACTION_CREATE_FOLDER: - title = title or _(u"Create Folder") - else: - title = title or _(u"Open a File") - buttons = (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_OPEN ,gtk.RESPONSE_OK) - gtk.FileChooserDialog.__init__(self, title, parent, action, buttons) - -class IconFactory: - def __init__(self, icon_theme): - self.cache = {} - self.icon_theme = icon_theme - - def guess_icon(self, filename, size): - return self.get_icon(self.guess_icon_name(filename), size) - - def get_icon(self, name, size): - # damn it! why did no one tell me I have to cache this!!! - if not (name, size) in self.cache: - try: - self.cache[(name, size)] = self.icon_theme.load_icon(name, - size, 0) - except gobject.GError, e: - #logger.exception("Unable to load icon %r probably your " - # "icon theme isn't conforming to the icon naming " - # "convention. You might want to try the tango icon " - # "theme", name) - return None - return self.cache[(name, size)] - - def get_image(self, name, size): - pixbuf = self.get_icon(name, size) - img = gtk.Image() - img.set_from_pixbuf(pixbuf) - return img - - def has_icon(self, name): - return self.icon_theme.has_icon(name) - -icon_theme = gtk.icon_theme_get_default() -iconfactory = IconFactory(icon_theme) - -def idle_do(func, *args): - """wrapper arround idle_add that will always run once""" - def wrapper(*args): - # throw away the result of the function! - func(*args) - gobject.idle_add(wrapper, *args) - -def scrolled(widget, shadow=gtk.SHADOW_NONE): - window = gtk.ScrolledWindow() - window.set_shadow_type(shadow) - window.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) - if widget.set_scroll_adjustments(window.get_hadjustment(), - window.get_vadjustment()): - window.add(widget) - else: - window.add_with_viewport(widget) - return window - - -def make_table(widgets): - """return a gtk.Table containing all the widgets""" - columns = max(map(len, widgets)) - table = gtk.Table(len(widgets), columns, False) - for y, row in enumerate(widgets): - for x, widget in enumerate(row): - if widget: - table.attach(widget, x, x+1, y, y+1, - xoptions=gtk.EXPAND|gtk.FILL, xpadding=4, ypadding=4) - return table - -def register_webbrowser_url_hook(): - """registers pythons webbrowser module as url_hook""" - import webbrowser - def open_url(d, link, data): - webbrowser.open(link) - if sys.platform.startswith("linux"): - webbrowser.register("xdg-open", None, - webbrowser.GenericBrowser('xdg-open'), update_tryorder=-1) - gtk.about_dialog_set_url_hook(open_url, None) - -class gtklock: - """A context manger for the gtk.gdk.threads_* - - can be used like this - >> with gtklock: - >> pass - """ - @staticmethod - def __enter__(): - gtk.gdk.threads_enter() - - @staticmethod - def __exit__(*args): - gtk.gdk.threads_leave() - -def gtk_yield(): - """process all the peding events in the mainloop""" - while gtk.events_pending(): - gtk.main_iteration() - -class IconButton(gtk.Button): - def __init__(self, icon, size=None, label=None): - gtk.Button.__init__(self) - self.size = size or gtk.ICON_SIZE_BUTTON - self.hbox = gtk.HBox() - self.add(self.hbox) - self.img = None - self.set_icon(icon) - self.label = None - self.set_label(label) - - def set_label(self, label): - if self.label: - self.label.set_text(label) - else: - self.label = gtk.Label(label) - self.hbox.pack_end(self.label) - - def set_icon(self, icon): - if self.img: - self.img.set_from_pixbuf(iconfactory.get_icon(icon, self.size)) - else: - self.img = iconfactory.get_image(icon, self.size) - self.hbox.pack_start(self.img) - self._icon = icon - icon = property(lambda self: self._icon, set_icon) - -class IconMenuItem(gtk.ImageMenuItem): - icon_size = gtk.icon_size_lookup(gtk.ICON_SIZE_MENU)[0] - def __init__(self, icon, text): - gtk.ImageMenuItem.__init__(self) - self.set_image(iconfactory.get_image(icon, self.icon_size)) - label = gtk.Label(text) - label.set_alignment(0.0, 0.5) - self.add(label) - self.show_all() - -def show_error(msg): - """Show a 'nice' errbox""" - dialog = gtk.MessageDialog(type=gtk.MESSAGE_ERROR, message_format=str(msg), - buttons=gtk.BUTTONS_OK) - dialog.set_title(_("Error")) - # dialog.run() - this breaks when called from gobject.idle_add - # dialog.hide() - # dialog.destroy - dialog.show() - dialog.connect("response", lambda dialog, response: dialog.destroy()) - return dialog - -def make_menu(entries, menu): - for entry in entries: - if entry is None: - sub = None - item = gtk.SeparatorMenuItem() - else: - key, sub = entry - if isinstance(key, tuple): - item = IconMenuItem(*key) - elif key.startswith("gtk-"): - item = gtk.ImageMenuItem(stock_id=key) - else: - item = gtk.MenuItem(key) - if sub: - if callable(sub): - item.connect("activate", sub) - else: - submenu = gtk.Menu() - item.set_submenu(submenu) - make_menu(sub, submenu) - menu.append(item) - -def form(rows): - table = gtk.Table(len(rows), 2, False) - for y, (text, widget) in enumerate(rows): - label = gtk.Label(text) - label.set_alignment(0.0, 0.5) - table.attach(label, 0, 1, y, y+1, xoptions=gtk.SHRINK|gtk.FILL, xpadding=4) - table.attach(widget, 1, 2, y, y+1, xoptions=gtk.EXPAND|gtk.FILL, xpadding=4) - return table - -def make_table(widgets): - """return a gtk.Table containing all the widgets""" - columns = max(map(len, widgets)) - table = gtk.Table(len(widgets), columns, False) - for y, row in enumerate(widgets): - for x, widget in enumerate(row): - table.attach(widget, x, x+1, y, y+1, xoptions=gtk.EXPAND|gtk.FILL, - xpadding=4, ypadding=4) - return table - -class Scale(object): - """A scale that adheres to increment steps""" - def __init__(self): - self.connect("change-value", self.adjust) - - def adjust(self, range, scroll, value): - adj = self.get_adjustment() - lower = adj.get_property('lower') - upper = adj.get_property('upper') - incr = adj.get_property('step-increment') or 1 - value -= (value % incr) - self.set_value(min(max(lower, value), upper)) - return True - -class VScale(gtk.VScale, Scale): - def __init__(self, *args): - gtk.VScale.__init__(self, *args) - Scale.__init__(self) - -class HScale(gtk.HScale, Scale): - def __init__(self, *args): - gtk.HScale.__init__(self, *args) - Scale.__init__(self) - -class ClockScale(gtk.VBox): - def __init__(self, *args): - gtk.VBox.__init__(self) - self.clocklabel = gtk.Label() - # slider - self.scale = HScale(*args) - self.scale.set_draw_value(False) - self.set_value = self.scale.set_value - self.get_value = self.scale.get_value - self.get_adjustment = self.scale.get_adjustment - self.set_adjustment = self.scale.set_adjustment - self.set_range = self.scale.set_range - - self.update_clock() - self.scale.connect("value-changed", self.update_clock) - - self.pack_start(self.clocklabel) #, True, True) - self.pack_start(self.scale) #, True, True) - def update_clock(self, sender=None): - self.clocklabel.set_markup(self.format(self.get_value())) - def format(self, value): - ms = str(timedelta(seconds=value))[8:11] - value = str(timedelta(seconds=value))[:7] - if ms == '': - ms = '000' - return '%s.%s' % (value, ms) - -class TextScale(gtk.HBox): - format = "%.2f" - size = 6 - def __init__(self, *args): - gtk.HBox.__init__(self) - self.from_text = False - - self.entry = gtk.Entry() - - self.scale = HScale(*args) - self.scale.set_draw_value(False) - self.set_value = self.scale.set_value - self.get_value = self.scale.get_value - self.get_adjustment = self.scale.get_adjustment - self.set_adjustment = self.scale.set_adjustment - self.set_range = self.scale.set_range - - #n = len(self.format % self.scale.get_adjustment().get_upper()) - self.entry.set_width_chars(self.size) - - self.update_text() - self.scale.connect("value-changed", self.update_text) - self.entry.connect("changed", self.update_scale) - - self.pack_start(self.scale, True, True) - self.pack_start(self.entry, False, False) - - self.entry.set_alignment(1.0) - - def update_text(self, sender=None): - if not self.from_text: - self.entry.set_text(self.format % self.get_value()) - - def update_scale(self, sender=None): - self.from_text = True - try: - self.set_value(float(self.entry.get_text())) - except ValueError: - pass - self.from_text = False - -# TODO: substitute for a decorator? -class TextScaleReset(TextScale): - def __init__(self, *args): - TextScale.__init__(self, *args) - self.reset_button = gtk.Button(_('Reset')) - self.reset_button.connect("clicked", self.reset_to_default) - self.pack_start(self.reset_button, False, False) - self.reorder_child(self.reset_button, 1) - self.default_value = self.get_value() - self.add_accelerator = self.reset_button.add_accelerator - def reset_to_default(self, sender=None): - self.set_value(self.default_value) - -# TODO: substitute for a decorator? -class TextScaleWithCurPos(TextScale): - def __init__(self, slider, *args): - TextScale.__init__(self, *args) - self.now_button = gtk.Button(_('Now!')) - self.now_button.connect("clicked", self.update_to_current_position) - self.pack_start(self.now_button, False, False) - self.reorder_child(self.now_button, 1) - self.slider = slider - self.add_accelerator = self.now_button.add_accelerator - def update_to_current_position(self, sender=None): - self.set_value(self.slider.get_value()) - -class ListStore(gtk.ListStore): - class Columns(list): - def __getattr__(self, name): - try: - return self.index(name) - except ValueError: - raise AttributeError, name - - def ordered(self, valuedict): - return [valuedict.get(key) for key in self] - - def __init__(self, **kwargs): - gtk.ListStore.__init__(self, *kwargs.values()) - self.columns = ListStore.Columns(kwargs.keys()) - - def serialize(self): - data = [] - for row in self: - row_dict = {} - for i, column in enumerate(self.columns): - row_dict[column] = row[i] - data.append(row_dict) - return data - - def unserialize(self, data): - for row in data: - self.append(self.columns.ordered(row)) - - - def append(self, *args, **kwargs): - if args: - gtk.ListStore.append(self, *args) - else: - gtk.ListStore.append(self, self.columns.ordered(kwargs)) - - -class ExceptionDialog(gtk.MessageDialog): - def __init__(self, etype, evalue, etb): - gtk.MessageDialog.__init__(self, buttons=gtk.BUTTONS_CLOSE, type=gtk.MESSAGE_ERROR) - self.set_resizable(True) - self.set_markup(_("An error has occured:\n%r\nYou should save your work and restart the application. If the error occurs again please report it to the developer." % evalue)) - import cgitb - text = cgitb.text((etype, evalue, etb), 5) - expander = gtk.Expander(_("Exception Details")) - self.vbox.pack_start(expander) - textview = gtk.TextView() - textview.get_buffer().set_text(text) - expander.add(scrolled(textview)) - self.show_all() - -def install_exception_hook(dialog=ExceptionDialog): - old_hook = sys.excepthook - def new_hook(etype, evalue, etb): - if etype not in (KeyboardInterrupt, SystemExit): - d = dialog(etype, evalue, etb) - d.run() - d.destroy() - old_hook(etype, evalue, etb) - new_hook.old_hook = old_hook - sys.excepthook = new_hook - -def install(): - """install/register all hooks provided by mygtk""" - install_exception_hook() - register_webbrowser_url_hook() - -if __name__ == "__main__": - install_exception_hook() - idle_do(lambda: 1/0) - gtk.main() diff -Nru playitslowly-1.4.0/playitslowly/myGtk.py playitslowly-1.5.0/playitslowly/myGtk.py --- playitslowly-1.4.0/playitslowly/myGtk.py 1970-01-01 00:00:00.000000000 +0000 +++ playitslowly-1.5.0/playitslowly/myGtk.py 2015-12-21 21:50:26.000000000 +0000 @@ -0,0 +1,411 @@ +""" +Author: Jonas Wagner + +Play it Slowly +Copyright (C) 2009 - 2015 Jonas Wagner + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, 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, see . +""" + +from gi.repository import Gtk, GObject +import math +import sys +from datetime import timedelta +import collections + +_ = lambda s: s + +buttons_ok_cancel = (Gtk.STOCK_CANCEL,Gtk.ResponseType.CANCEL,Gtk.STOCK_OPEN,Gtk.ResponseType.OK) +class FileChooserDialog(Gtk.FileChooserDialog): + """a file chooser dialog which automatically sets the correct buttons!""" + def __init__(self, title=None, parent=None, action=None): + if action == Gtk.FileChooserAction.SAVE: + buttons = (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, Gtk.STOCK_SAVE ,Gtk.ResponseType.OK) + title = title or _("Save File") + else: + if action == Gtk.FileChooserAction.SELECT_FOLDER: + title = title or _("Select Folder") + elif action == Gtk.FileChooserAction.CREATE_FOLDER: + title = title or _("Create Folder") + else: + title = title or _("Open a File") + buttons = (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, Gtk.STOCK_OPEN ,Gtk.ResponseType.OK) + Gtk.FileChooserDialog.__init__(self, title, parent, action, buttons) + +class IconFactory: + def __init__(self, icon_theme): + self.cache = {} + self.icon_theme = icon_theme + + def guess_icon(self, filename, size): + return self.get_icon(self.guess_icon_name(filename), size) + + def get_icon(self, name, size): + # damn it! why did no one tell me I have to cache this!!! + if not (name, size) in self.cache: + try: + self.cache[(name, size)] = self.icon_theme.load_icon(name, + size, 0) + except GObject.GError as e: + #logger.exception("Unable to load icon %r probably your " + # "icon theme isn't conforming to the icon naming " + # "convention. You might want to try the tango icon " + # "theme", name) + return None + return self.cache[(name, size)] + + def get_image(self, name, size): + pixbuf = self.get_icon(name, size) + img = Gtk.Image() + img.set_from_pixbuf(pixbuf) + return img + + def has_icon(self, name): + return self.icon_theme.has_icon(name) + +icon_theme = Gtk.IconTheme.get_default() +iconfactory = IconFactory(icon_theme) + +def idle_do(func, *args): + """wrapper arround idle_add that will always run once""" + def wrapper(*args): + # throw away the result of the function! + func(*args) + GObject.idle_add(wrapper, *args) + +def scrolled(widget, shadow=Gtk.ShadowType.NONE): + window = Gtk.ScrolledWindow() + window.set_shadow_type(shadow) + window.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC) + window.add_with_viewport(widget) + return window + + +def make_table(widgets): + """return a Gtk.Table containing all the widgets""" + columns = max(list(map(len, widgets))) + table = Gtk.Table(len(widgets), columns, False) + for y, row in enumerate(widgets): + for x, widget in enumerate(row): + if widget: + table.attach(widget, x, x+1, y, y+1, + xoptions=Gtk.AttachOptions.EXPAND|Gtk.AttachOptions.FILL, xpadding=4, ypadding=4) + return table + +class Gtklock: + """A context manger for the Gdk.threads_* + + can be used like this + >> with Gtklock: + >> pass + """ + @staticmethod + def __enter__(): + Gdk.threads_enter() + + @staticmethod + def __exit__(*args): + Gdk.threads_leave() + +def Gtk_yield(): + """process all the peding events in the mainloop""" + while Gtk.events_pending(): + Gtk.main_iteration() + +class IconButton(Gtk.Button): + def __init__(self, icon, size=None, label=None): + GObject.GObject.__init__(self) + self.size = size or Gtk.IconSize.BUTTON + self.hbox = Gtk.HBox() + self.add(self.hbox) + self.img = None + self.set_icon(icon) + self.label = None + self.set_label(label) + + def set_label(self, label): + if self.label: + self.label.set_text(label) + else: + self.label = Gtk.Label(label=label) + self.hbox.pack_end(self.label, True, True, 0) + + def set_icon(self, icon): + if self.img: + self.img.set_from_pixbuf(iconfactory.get_icon(icon, self.size)) + else: + self.img = iconfactory.get_image(icon, self.size) + self.hbox.pack_start(self.img, True, True, 0) + self._icon = icon + icon = property(lambda self: self._icon, set_icon) + +class IconMenuItem(Gtk.ImageMenuItem): + icon_size = Gtk.icon_size_lookup(Gtk.IconSize.MENU)[0] + def __init__(self, icon, text): + GObject.GObject.__init__(self) + self.set_image(iconfactory.get_image(icon, self.icon_size)) + label = Gtk.Label(label=text) + label.set_alignment(0.0, 0.5) + self.add(label) + self.show_all() + +def show_error(msg): + """Show a 'nice' errbox""" + dialog = Gtk.MessageDialog(type=Gtk.MessageType.ERROR, message_format=str(msg), + buttons=Gtk.ButtonsType.OK) + dialog.set_title(_("Error")) + # dialog.run() - this breaks when called from GObject.idle_add + # dialog.hide() + # dialog.destroy + dialog.show() + dialog.connect("response", lambda dialog, response: dialog.destroy()) + return dialog + +def make_menu(entries, menu): + for entry in entries: + if entry is None: + sub = None + item = Gtk.SeparatorMenuItem() + else: + key, sub = entry + if isinstance(key, tuple): + item = IconMenuItem(*key) + elif key.startswith("Gtk-"): + item = Gtk.ImageMenuItem(stock_id=key) + else: + item = Gtk.MenuItem(key) + if sub: + if isinstance(sub, collections.Callable): + item.connect("activate", sub) + else: + submenu = Gtk.Menu() + item.set_submenu(submenu) + make_menu(sub, submenu) + menu.append(item) + +def form(rows): + table = Gtk.Table(len(rows), 2, False) + for y, (text, widget) in enumerate(rows): + label = Gtk.Label(label=text) + label.set_alignment(0.0, 0.5) + table.attach(label, 0, 1, y, y+1, xoptions=Gtk.AttachOptions.SHRINK|Gtk.AttachOptions.FILL, xpadding=4, ypadding=4) + table.attach(widget, 1, 2, y, y+1, xoptions=Gtk.AttachOptions.EXPAND|Gtk.AttachOptions.FILL, xpadding=4, ypadding=4) + return table + +def make_table(widgets): + """return a Gtk.Table containing all the widgets""" + columns = max(list(map(len, widgets))) + table = Gtk.Table(len(widgets), columns, False) + for y, row in enumerate(widgets): + for x, widget in enumerate(row): + table.attach(widget, x, x+1, y, y+1, xoptions=Gtk.AttachOptions.EXPAND|Gtk.AttachOptions.FILL, + xpadding=4, ypadding=4) + return table + +class Scale(object): + """A scale that adheres to increment steps""" + def __init__(self): + self.connect("change-value", self.adjust) + + def adjust(self, range, scroll, value): + adj = self.get_adjustment() + lower = adj.get_property('lower') + upper = adj.get_property('upper') + incr = adj.get_property('step-increment') or 1 + value -= (value % incr) + self.set_value(min(max(lower, value), upper)) + return True + +class VScale(Gtk.VScale, Scale): + def __init__(self, *args): + Gtk.VScale.__init__(self, *args) + Scale.__init__(self) + +class HScale(Gtk.HScale, Scale): + def __init__(self, *args): + Gtk.HScale.__init__(self) + Scale.__init__(self) + self.set_adjustment(*args) + +class ClockScale(Gtk.VBox): + def __init__(self, *args): + GObject.GObject.__init__(self) + self.clocklabel = Gtk.Label() + # slider + self.scale = HScale(*args) + self.scale.set_draw_value(False) + self.set_value = self.scale.set_value + self.get_value = self.scale.get_value + self.get_adjustment = self.scale.get_adjustment + self.set_adjustment = self.scale.set_adjustment + self.set_range = self.scale.set_range + + self.update_clock() + self.scale.connect("value-changed", self.update_clock) + + self.pack_start(self.clocklabel, True, True, 0) + self.pack_start(self.scale, True, True, 0) + def update_clock(self, sender=None): + self.clocklabel.set_markup(self.format(self.get_value())) + def format(self, value): + ms = str(timedelta(seconds=value))[8:11] + value = str(timedelta(seconds=value))[:7] + if ms == '': + ms = '000' + return '%s.%s' % (value, ms) + +class TextScale(Gtk.HBox): + format = "%.2f" + size = 6 + def __init__(self, *args): + GObject.GObject.__init__(self) + self.from_text = False + + self.entry = Gtk.Entry() + + self.scale = HScale(*args) + self.scale.set_draw_value(False) + self.set_value = self.scale.set_value + self.get_value = self.scale.get_value + self.get_adjustment = self.scale.get_adjustment + self.set_adjustment = self.scale.set_adjustment + self.set_range = self.scale.set_range + + #n = len(self.format % self.scale.get_adjustment().get_upper()) + self.entry.set_width_chars(self.size) + self.entry.set_max_width_chars(self.size) + + self.update_text() + self.scale.connect("value-changed", self.update_text) + self.entry.connect("changed", self.update_scale) + + self.pack_start(self.scale, True, True, 4) + self.pack_start(self.entry, False, False, 4) + + self.entry.set_alignment(1.0) + + def update_text(self, sender=None): + if not self.from_text: + self.entry.set_text(self.format % self.get_value()) + + def update_scale(self, sender=None): + self.from_text = True + try: + self.set_value(float(self.entry.get_text())) + except ValueError: + pass + self.from_text = False + +class TextScaleReset(TextScale): + def __init__(self, *args): + TextScale.__init__(self, *args) + self.reset_button = Gtk.Button.new_with_label(_('Reset')) + self.reset_button.set_size_request(64, -1) + add_style_class(self.reset_button, 'textScaleButton') + self.reset_button.connect("clicked", self.reset_to_default) + self.pack_start(self.reset_button, False, False, 0) + self.reorder_child(self.reset_button, 1) + self.default_value = self.get_value() + self.add_accelerator = self.reset_button.add_accelerator + def reset_to_default(self, sender=None): + self.set_value(self.default_value) + +# TODO: substitute for a decorator? +class TextScaleWithCurPos(TextScale): + def __init__(self, slider, *args): + TextScale.__init__(self, *args) + self.now_button = Gtk.Button(_('Now')) + self.now_button.set_size_request(64, -1) + self.now_button.connect("clicked", self.update_to_current_position) + self.pack_start(self.now_button, False, False, 0) + self.reorder_child(self.now_button, 1) + self.slider = slider + self.add_accelerator = self.now_button.add_accelerator + def update_to_current_position(self, sender=None): + self.set_value(self.slider.get_value()) + +class ListStore(Gtk.ListStore): + class Columns(list): + def __getattr__(self, name): + try: + return self.index(name) + except ValueError: + raise AttributeError(name) + + def ordered(self, valuedict): + return [valuedict.get(key) for key in self] + + def __init__(self, **kwargs): + GObject.GObject.__init__(self, *list(kwargs.values())) + self.columns = ListStore.Columns(list(kwargs.keys())) + + def serialize(self): + data = [] + for row in self: + row_dict = {} + for i, column in enumerate(self.columns): + row_dict[column] = row[i] + data.append(row_dict) + return data + + def unserialize(self, data): + for row in data: + self.append(self.columns.ordered(row)) + + + def append(self, *args, **kwargs): + if args: + Gtk.ListStore.append(self, *args) + else: + Gtk.ListStore.append(self, self.columns.ordered(kwargs)) + + +class ExceptionDialog(Gtk.MessageDialog): + def __init__(self, etype, evalue, etb): + Gtk.MessageDialog.__init__(self, buttons=Gtk.ButtonsType.CLOSE, type=Gtk.MessageType.ERROR) + self.set_resizable(True) + self.set_markup(_("An error has occured:\n%r\nYou should save your work and restart the application. If the error occurs again please report it to the developer." % evalue)) + import cgitb + text = cgitb.text((etype, evalue, etb), 5) + expander = Gtk.Expander() + #_("Exception Details")) + self.vbox.pack_start(expander, True, True, 0) + textview = Gtk.TextView() + textview.get_buffer().set_text(text) + expander.add(scrolled(textview)) + self.show_all() + +def install_exception_hook(dialog=ExceptionDialog): + old_hook = sys.excepthook + def new_hook(etype, evalue, etb): + if etype not in (KeyboardInterrupt, SystemExit): + print(etype) + d = dialog(etype, evalue, etb) + d.run() + d.destroy() + old_hook(etype, evalue, etb) + new_hook.old_hook = old_hook + sys.excepthook = new_hook + +def install(): + """install/register all hooks provided by myGtk""" + install_exception_hook() + +def add_style_class(widget, name): + widget.get_style_context().add_class(name) + +if __name__ == "__main__": + install_exception_hook() + idle_do(lambda: 1/0) + Gtk.main() diff -Nru playitslowly-1.4.0/playitslowly/pipeline.py playitslowly-1.5.0/playitslowly/pipeline.py --- playitslowly-1.4.0/playitslowly/pipeline.py 2010-12-08 14:05:51.000000000 +0000 +++ playitslowly-1.5.0/playitslowly/pipeline.py 2015-12-21 21:58:02.000000000 +0000 @@ -1,57 +1,78 @@ -import sys +""" +Author: Jonas Wagner + +Play it Slowly +Copyright (C) 2009 - 2015 Jonas Wagner -import pygst -pygst.require('0.10') +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, 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, see . +""" + +import sys argv = sys.argv -# work around gstreamer parsing sys.argv! +# work around Gstreamer parsing sys.argv! sys.argv = [] -import gst + +import gi +gi.require_version('Gst', '1.0') + +from gi.repository import Gst sys.argv = argv -from playitslowly import mygtk +from playitslowly import myGtk _ = lambda x: x -class Pipeline(gst.Pipeline): +class Pipeline(Gst.Pipeline): def __init__(self, sink): - gst.Pipeline.__init__(self) - self.playbin = gst.element_factory_make("playbin") + Gst.Pipeline.__init__(self) + self.playbin = Gst.ElementFactory.make("playbin") self.add(self.playbin) - bin = gst.Bin("speed-bin") - try: - self.speedchanger = gst.element_factory_make("pitch") - except gst.ElementNotFoundError: - mygtk.show_error(_(u"You need to install the gstreamer soundtouch elements for " - "play it slowly to. They are part of gstreamer-plugins-bad. Consult the " + bin = Gst.Bin() + self.speedchanger = Gst.ElementFactory.make("pitch") + if self.speedchanger is None: + mygtk.show_error(_("You need to install the Gstreamer soundtouch elements for " + "play it slowly to. They are part of Gstreamer-plugins-bad. Consult the " "README if you need more information.")).run() raise SystemExit() bin.add(self.speedchanger) - self.audiosink = sink + self.audiosink = Gst.parse_launch(sink) + #self.audiosink = Gst.ElementFactory.make(sink, "sink") bin.add(self.audiosink) - convert = gst.element_factory_make("audioconvert") + convert = Gst.ElementFactory.make("audioconvert") bin.add(convert) - gst.element_link_many(self.speedchanger, convert) - gst.element_link_many(convert, self.audiosink) - sink_pad = gst.GhostPad("sink", self.speedchanger.get_pad("sink")) + self.speedchanger.link(convert) + convert.link(self.audiosink) + sink_pad = Gst.GhostPad.new("sink", self.speedchanger.get_static_pad("sink")) bin.add_pad(sink_pad) self.playbin.set_property("audio-sink", bin) - bus = self.playbin.get_bus() - bus.add_signal_watch() - bus.connect("message", self.on_message) + #bus = self.playbin.get_bus() + #bus.add_signal_watch() + #bus.connect("message", self.on_message) self.eos = lambda: None def on_message(self, bus, message): t = message.type - if t == gst.MESSAGE_EOS: + if t == Gst.MESSAGE_EOS: self.eos() - elif t == gst.MESSAGE_ERROR: - mygtk.show_error("gstreamer error: %s - %s" % message.parse_error()) + elif t == Gst.MESSAGE_ERROR: + mygtk.show_error("Gstreamer error: %s - %s" % message.parse_error()) def set_volume(self, volume): self.playbin.set_property("volume", volume) @@ -74,42 +95,38 @@ self.speedchanger.set_property("pitch", pitch) def save_file(self, uri): - pipeline = gst.Pipeline() + pipeline = Gst.Pipeline() - playbin = gst.element_factory_make("playbin") + playbin = Gst.ElementFactory.make("playbin") pipeline.add(playbin) playbin.set_property("uri", self.playbin.get_property("uri")) - bin = gst.Bin("speed-bin") + bin = Gst.Bin() - speedchanger = gst.element_factory_make("pitch") + speedchanger = Gst.ElementFactory.make("pitch") speedchanger.set_property("tempo", self.speedchanger.get_property("tempo")) speedchanger.set_property("pitch", self.speedchanger.get_property("pitch")) bin.add(speedchanger) - audioconvert = gst.element_factory_make("audioconvert") + audioconvert = Gst.ElementFactory.make("audioconvert") bin.add(audioconvert) - encoder = gst.element_factory_make("wavenc") + encoder = Gst.ElementFactory.make("wavenc") bin.add(encoder) - filesink = gst.element_factory_make("filesink") + filesink = Gst.ElementFactory.make("filesink") bin.add(filesink) filesink.set_property("location", uri) - gst.element_link_many(speedchanger, audioconvert) - gst.element_link_many(audioconvert, encoder) - gst.element_link_many(encoder, filesink) + speedchanger.link(audioconvert) + audioconvert.link(encoder) + encoder.link(filesink) - sink_pad = gst.GhostPad("sink", speedchanger.get_pad("sink")) + sink_pad = Gst.GhostPad.new("sink", speedchanger.get_static_pad("sink")) bin.add_pad(sink_pad) playbin.set_property("audio-sink", bin) - bus = playbin.get_bus() - bus.add_signal_watch() - bus.connect("message", self.on_message) - - pipeline.set_state(gst.STATE_PLAYING) + pipeline.set_state(Gst.State.PLAYING) return (pipeline, playbin) @@ -117,12 +134,12 @@ self.playbin.set_property("uri", uri) def play(self): - self.set_state(gst.STATE_PLAYING) + self.set_state(Gst.State.PLAYING) def pause(self): - self.set_state(gst.STATE_PAUSED) + self.set_state(Gst.State.PAUSED) def reset(self): - self.set_state(gst.STATE_READY) + self.set_state(Gst.State.READY) diff -Nru playitslowly-1.4.0/README playitslowly-1.5.0/README --- playitslowly-1.4.0/README 2011-07-03 13:24:02.000000000 +0000 +++ playitslowly-1.5.0/README 2015-12-21 22:10:24.000000000 +0000 @@ -34,10 +34,10 @@ ==================== To install use you need to have the following libraries installed: - * Python 2.6 (or 2.5 with simplejson installed) - * PyGTK >= 2.10 - * PyGST >= 0.10 - * gstreamer >= 0.10 including the soundtouch/pitch element + * Python 3.4 or newer + * PyGI (Python GObject Introspection) + * GTK3 + * gstreamer 1.0 including the soundtouch/pitch element (included in gstreamer-plugins-bad) Normally these libraries are already installed on your system. @@ -50,25 +50,6 @@ Distribution Specific Installation Hints ======================================== -Arch Linux ----------- -(this could be obsolete by now) - -Arch Linux is missing the pitch element, so you'll have to compile -yourself:: - - pacman -S soundtouch - cd /tmp - wget gstreamer.freedesktop.org/src/gst-plugins-bad/gst-plugins-bad-0.10.9.tar.gz - tar xzf gst-plugins-bad-0.10.9.tar.gz - ./configure --prefix=/usr - cd ext/soundtouch - make && make install - -Then test if it worked:: - - gst-inspect-0.10 soundtouch - Fedora ------ Install gstreamer-plugins-bad-extras @@ -94,7 +75,7 @@ License ======= -Copyright (C) 2009 - 2011 Jonas Wagner +Copyright (C) 2009 - 2015 Jonas Wagner This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -123,4 +104,5 @@ Contact ======= -You can reach my by e-mail: veers at gmx dot ch + +http://29a.ch/about diff -Nru playitslowly-1.4.0/setup.py playitslowly-1.5.0/setup.py --- playitslowly-1.4.0/setup.py 2011-07-03 13:27:45.000000000 +0000 +++ playitslowly-1.5.0/setup.py 2015-12-22 17:10:17.000000000 +0000 @@ -5,6 +5,7 @@ from distutils.core import setup from distutils.command.install import install, write_file from distutils.command.install_egg_info import to_filename, safe_name +from functools import reduce class new_install(install): def initialize_options(self): @@ -27,14 +28,12 @@ outputs = self.get_outputs() if self.root: # strip any package prefix root_len = len(self.root) - for counter in xrange(len(outputs)): + for counter in range(len(outputs)): outputs[counter] = outputs[counter][root_len:] self.execute(write_file, (install_info, outputs), "writing install-info to '%s'" % install_info) - - def ls_r(dir): def do_reduce(a, b): files = [] @@ -47,10 +46,10 @@ kwargs = { 'cmdclass': {'install': new_install}, 'name': 'playitslowly', - 'version': "1.4.0", + 'version': "1.5.0", 'description': 'A tool to help you when transcribing music. It allows you to play a piece of music at a different speed or pitch.', 'author': 'Jonas Wagner', - 'author_email': 'veers@gmx.ch', + 'author_email': 'jonas@29a.ch', 'url': 'http://29a.ch/playitslowly/', 'packages': ['playitslowly'], 'scripts': ['bin/playitslowly'], @@ -62,7 +61,7 @@ }}, 'data_files': ls_r('share'), 'license': 'GNU GPL v3', - 'classifiers': ['Development Status :: 4 - Beta', + 'classifiers': [ 'Environment :: X11 Applications :: GTK', 'Intended Audience :: End Users/Desktop', 'License :: OSI Approved :: GNU General Public License (GPL)', diff -Nru playitslowly-1.4.0/share/applications/playitslowly.desktop playitslowly-1.5.0/share/applications/playitslowly.desktop --- playitslowly-1.4.0/share/applications/playitslowly.desktop 2010-12-08 14:05:51.000000000 +0000 +++ playitslowly-1.5.0/share/applications/playitslowly.desktop 2015-12-22 17:10:17.000000000 +0000 @@ -3,6 +3,7 @@ Name=Play it Slowly GenericName=Play it Slowly Comment=A tool to play back a piece of music at a different speed or pitch +Comment[fr]=Un outil pour lire un morceau de musique à différentes vitesses ou tonalités Icon=playitslowly Type=Application Categories=Application;AudioVideo; Binary files /tmp/tmpDrX0zM/0IrD2GKd6H/playitslowly-1.4.0/share/pixmaps/playitslowly.png and /tmp/tmpDrX0zM/ITawGkVNkj/playitslowly-1.5.0/share/pixmaps/playitslowly.png differ