unity-scope-audacious-0.1+13.10.20130927.1/0000755000015700001700000000000012221263750020302 5ustar pbuserpbgroup00000000000000unity-scope-audacious-0.1+13.10.20130927.1/po/0000755000015700001700000000000012221263750020720 5ustar pbuserpbgroup00000000000000unity-scope-audacious-0.1+13.10.20130927.1/po/unity-scope-audacious.pot0000644000015700001700000000234112221263420025670 0ustar pbuserpbgroup00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # #: ../src/unity_audacious_daemon.py:41 #, fuzzy msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2013-02-21 01:59+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" "Language: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=CHARSET\n" "Content-Transfer-Encoding: 8bit\n" #: ../src/unity_audacious_daemon.py:37 msgid "Categorytitle" msgstr "" #: ../src/unity_audacious_daemon.py:39 ../data/audacious.scope.in.h:4 msgid "Search Audacious" msgstr "" #: ../src/unity_audacious_daemon.py:40 msgid "Sorry, there are no Audacious results that match your search." msgstr "" #: ../src/unity_audacious_daemon.py:50 msgid "Songs" msgstr "" #: ../src/unity_audacious_daemon.py:54 msgid "Albums" msgstr "" #: ../data/audacious.scope.in.h:1 msgid "audacious;" msgstr "" #: ../data/audacious.scope.in.h:2 msgid "Audacious" msgstr "" #: ../data/audacious.scope.in.h:3 msgid "Find Audacious items" msgstr "" unity-scope-audacious-0.1+13.10.20130927.1/po/POTFILES.in0000644000015700001700000000013312221263420022464 0ustar pbuserpbgroup00000000000000[encoding: UTF-8] src/unity_audacious_daemon.py [type: gettext/ini]data/audacious.scope.in unity-scope-audacious-0.1+13.10.20130927.1/MANIFEST.in0000644000015700001700000000007612221263420022035 0ustar pbuserpbgroup00000000000000include MANIFEST.in include src/* include data/* include po/* unity-scope-audacious-0.1+13.10.20130927.1/tests/0000755000015700001700000000000012221263750021444 5ustar pbuserpbgroup00000000000000unity-scope-audacious-0.1+13.10.20130927.1/tests/test_audacious.py0000644000015700001700000000373012221263420025027 0ustar pbuserpbgroup00000000000000#! /usr/bin/python3 # -*- coding: utf-8 -*- from gi.repository import Unity from unittest import TestCase import imp class ResultSet(Unity.ResultSet): def __init__(self): Unity.ResultSet.__init__(self) self.results = [] def do_add_result(self, result): self.results.append({'uri': result.uri, 'title': result.title, 'comment': result.comment, 'icon': result.icon_hint}) class ScopeTestCase(TestCase): def init_scope(self, scope_path): self.scope_module = imp.load_source('scope', scope_path) self.scope = self.scope_module.load_scope() def perform_query(self, query, filter_set=Unity.FilterSet.new()): result_set = ResultSet() ctx = Unity.SearchContext.create(query, 0, filter_set, {}, result_set, None) s = self.scope.create_search_for_query(ctx) s.run() return result_set class TestAudacious(ScopeTestCase): def setUp(self): self.init_scope('src/unity_audacious_daemon.py') def tearDown(self): self.scope = None self.scope_module = None def test_search(self): self.scope_module.AUDACIOUS_DBFILE = 'tests/data/mock_audacious_pass' expected_results = ["/home/mark/Music/Bell X1/Flock/04. He Said She Said.mp3", "He Said She Said"] results = [] for s in ['flock']: result_set = self.perform_query(s) results.append(result_set.results[0]['uri']) results.append(result_set.results[0]['title']) self.assertEqual(results, expected_results) def test_failing_search(self): self.scope_module.AUDACIOUS_DBFILE = 'tests/data/mock_audacious_fail' for s in ['upnriitnyt']: result_set = self.perform_query(s) self.assertEqual(len(result_set.results), 0) if __name__ == '__main__': unittest.main() unity-scope-audacious-0.1+13.10.20130927.1/tests/__init__.py0000644000015700001700000000000012221263420023535 0ustar pbuserpbgroup00000000000000unity-scope-audacious-0.1+13.10.20130927.1/tests/data/0000755000015700001700000000000012221263750022355 5ustar pbuserpbgroup00000000000000unity-scope-audacious-0.1+13.10.20130927.1/tests/data/mock_audacious_fail/0000755000015700001700000000000012221263750026336 5ustar pbuserpbgroup00000000000000unity-scope-audacious-0.1+13.10.20130927.1/tests/data/mock_audacious_fail/mock_audacious_fail0000644000015700001700000000000612221263420032230 0ustar pbuserpbgroup00000000000000 unity-scope-audacious-0.1+13.10.20130927.1/tests/data/mock_audacious_pass/0000755000015700001700000000000012221263750026371 5ustar pbuserpbgroup00000000000000unity-scope-audacious-0.1+13.10.20130927.1/tests/data/mock_audacious_pass/mock_audacious_pass0000644000015700001700000000056412221263420032327 0ustar pbuserpbgroup00000000000000title=Now%20Playing uri=file:///home/mark/Music/Bell%20X1/Flock/04.%20He%20Said%20She%20Said.mp3 artist=Bell%20X1 title=He%20Said%20She%20Said album=Flock genre=Rock track-number=4 length=265173 year=2006 quality=Stereo%2C%2044100%20Hz codec=MPEG-1%20layer%203 composer=Crosby bitrate=127 uri=file:///home/mark/Music/Bell%20X1/Flock/01.%20Reacharound.mp3 artist=Bell%20X1 unity-scope-audacious-0.1+13.10.20130927.1/src/0000755000015700001700000000000012221263750021071 5ustar pbuserpbgroup00000000000000unity-scope-audacious-0.1+13.10.20130927.1/src/unity_audacious_daemon.py0000644000015700001700000003442312221263420026173 0ustar pbuserpbgroup00000000000000#! /usr/bin/python3 # -*- coding: utf-8 -*- # Copyright(C) 2013 Mark Tully # This program is free software: you can redistribute it and/or modify it # under the terms of the GNU General Public License version 3, as published # by the Free Software Foundation. # # This program is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranties of # MERCHANTABILITY, SATISFACTORY QUALITY, 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 GLib, Gio from gi.repository import Unity import gettext import urllib.parse import dbus import hashlib import unicodedata import os APP_NAME = 'unity-scope-audacious' LOCAL_PATH = '/usr/share/locale/' gettext.bindtextdomain(APP_NAME, LOCAL_PATH) gettext.textdomain(APP_NAME) _ = gettext.gettext GROUP_NAME = 'com.canonical.Unity.Scope.Music.Audacious' UNIQUE_PATH = '/com/canonical/unity/scope/music/audacious' SVG_DIR = '/usr/share/icons/unity-icon-theme/places/svg/' CAT_0_ICON = Gio.ThemedIcon.new(SVG_DIR + 'group-music.svg') CAT_0_TITLE = _('Categorytitle') SEARCH_HINT = _('Search Audacious') NO_RESULTS_HINT = _('Sorry, there are no Audacious results that match your search.') PROVIDER_CREDITS = _('') SVG_DIR = '/usr/share/icons/unity-icon-theme/places/svg/' PROVIDER_ICON = SVG_DIR + 'service-audacious.svg' DEFAULT_RESULT_ICON = SVG_DIR + 'result-audacious.svg' DEFAULT_RESULT_MIMETYPE = 'taglib/mp3' DEFAULT_RESULT_TYPE = Unity.ResultType.PERSONAL AUDACIOUS_DBFILE = os.getenv("HOME") + "/.config/audacious/playlists/" c1 = {'id': 'songs', 'name': _('Songs'), 'icon': SVG_DIR + 'group-installed.svg', 'renderer': Unity.CategoryRenderer.VERTICAL_TILE} c2 = {'id': 'albums', 'name': _('Albums'), 'icon': SVG_DIR + 'group-installed.svg', 'renderer': Unity.CategoryRenderer.VERTICAL_TILE} CATEGORIES = [c1, c2] FILTERS = [] m1 = {'id' :'album', 'type' :'s', 'field':Unity.SchemaFieldType.OPTIONAL} m2 = {'id' :'artist', 'type' :'s', 'field':Unity.SchemaFieldType.OPTIONAL} m3 = {'id' :'genre', 'type' :'s', 'field':Unity.SchemaFieldType.OPTIONAL} m4 = {'id' :'year', 'type' :'i', 'field':Unity.SchemaFieldType.OPTIONAL} m5 = {'id': 'track_length', 'type': 'i', 'field': Unity.SchemaFieldType.OPTIONAL} m6 = {'id': 'track_number', 'type': 'i', 'field': Unity.SchemaFieldType.OPTIONAL} EXTRA_METADATA = [m1, m2, m3, m4, m5, m6] tracks = [] def get_music_from_audacious(): ''' Parses Audacious's database into a form we can use ''' uri = "" title = "" artist = "" album = "" year = 0 genre = "" tracknumber = 0 length = 0 global tracks tracks = [] for collection in os.listdir(AUDACIOUS_DBFILE): dbfile = '%s/%s' % (AUDACIOUS_DBFILE, collection) database = open(dbfile, "r") database = database.read() if not database.startswith("title:Library"): records = database[14:] records = records.split("uri=") else: records = "" for record in records: track = [] lines = record.split("\n") uri = "" year = 0 for line in lines: line = urllib.parse.unquote(line) if line.startswith("file://"): uri = line[7:] if line.startswith("title="): title = line[6:] if line.startswith("artist="): artist = line[7:] if line.startswith("album="): album = line[6:] if line.startswith("year="): year = line[5:] if line.startswith("genre="): genre = line[6:] if line.startswith("track-number="): tracknumber = line[13:] if line.startswith("length="): length = line[7:] track = [title, uri, artist, album, "taglib/mp3", int(year), genre, int(tracknumber), int(length) / 1000] tracks.append(track) tracks.sort(key=lambda track: track[7]) return tracks def get_album_art(track): dirname = dirname = os.path.dirname(track[1]) if os.path.exists(dirname + "/cover.jpg"): return dirname + "/cover.jpg" if os.path.exists(dirname + "/folder.jpg"): return dirname + "/folder.jpg" if os.path.exists(dirname + "/album.jpg"): return dirname + "/album.jpg" # Try thumbnailing any embedded album art and use that hashname = '%s\t%s' % (unicodedata.normalize("NFKD", track[2]), unicodedata.normalize("NFKD", track[3])) file_hash = hashlib.md5(hashname.encode('utf-8')).hexdigest() tb_filename = os.path.join(os.path.expanduser("~/.cache/media-art"), ("album-" + file_hash)) + ".jpg" if os.path.exists(tb_filename): return tb_filename else: try: from mutagen import File trackfile = track[1][7:] trackfile.replace("%20", " ") trackfile = urllib.parse.unquote(trackfile) audio = File(trackfile) if "APIC" in audio: artwork = audio.tags["APIC:"].data if not os.path.exists(os.path.expanduser("~/.cache/media-art")): os.makedirs(os.path.expanduser("~/.cache/media-art")) with open(tb_filename, "wb") as img: img.write(artwork) return tb_filename else: return "media-audio" except: # Otherwise, return a generic audio icon return "media-audio" def search(search, filters): ''' Search for help documents matching the search string ''' results = [] global tracks if len(tracks) == 0: tracks = get_music_from_audacious() trackresults = [] albumresults = [] for track in tracks: title = "" if track[0] is None else track[0] uri = "" if track[1] is None else track[1] artist = "" if track[2] is None else track[2] album = "" if track[3] is None else track[3] mimetype = "" if track[4] is None else track[4] albumartist = "" if track[5] is None else track[2] year = 0 if track[5] is None else int(track[5]) track_length = 0 if track[8] is None else track[8] track_number = 0 if track[7] is None else track[7] genre = "" if track[6] is None else track[6] trackname = title + " - " + album + " - " + artist if search.lower() in trackname.lower(): albumart = get_album_art(track) albumuri = "album://" + albumartist + "/" + album if uri not in trackresults: results.append({'uri': uri, 'icon': albumart, 'category': 0, 'title': title, 'album': GLib.Variant('s', album), 'artist': GLib.Variant('s', artist), 'genre': GLib.Variant('s', genre), 'year': GLib.Variant('i', year), 'track_length': GLib.Variant('i', track_length), 'track_number': GLib.Variant('i', track_number)}) trackresults.append(uri) if albumuri not in albumresults: results.append({'uri': albumuri, 'icon': albumart, 'category': 1, 'title': album, 'album':GLib.Variant('s', album), 'artist':GLib.Variant('s', artist), 'genre':GLib.Variant('s', genre), 'year':GLib.Variant('i', year), 'track_length': GLib.Variant('i', track_length), 'track_number': GLib.Variant('i', track_number)}) albumresults.append(albumuri) return results class Preview(Unity.ResultPreviewer): def do_run(self): global tracks album = self.result.metadata['album'].get_string() artist = self.result.metadata['artist'].get_string() preview = Unity.MusicPreview.new(self.result.title, '', None) preview.props.image_source_uri = 'file://%s' % self.result.icon_hint preview.props.subtitle = self.result.metadata['artist'].get_string() if self.result.uri.startswith("album://"): for track in tracks: if album in track[3] and artist in track[2]: track = Unity.TrackMetadata.full('file://%s' % track[1], int(track[7]), track[0], track[2], track[3], track[8]) preview.add_track(track) else: track = Unity.TrackMetadata.full('file://%s' % self.result.uri, self.result.metadata['track_number'].get_int32(), self.result.title, self.result.metadata['artist'].get_string(), self.result.metadata['album'].get_string(), self.result.metadata['track_length'].get_int32()) preview.add_track(track) view_action = Unity.PreviewAction.new("play", _("Play"), None) preview.add_action(view_action) show_action = Unity.PreviewAction.new("show", _("Show in Folder"), None) preview.add_action(show_action) return preview # Classes below this point establish communication # with Unity, you probably shouldn't modify them. class MySearch(Unity.ScopeSearchBase): def __init__(self, search_context): super(MySearch, self).__init__() self.set_search_context(search_context) def do_run(self): ''' Adds results to the model ''' try: result_set = self.search_context.result_set for i in search(self.search_context.search_query, self.search_context.filter_state): if not 'uri' in i or not i['uri'] or i['uri'] == '': continue if not 'icon' in i or not i['icon'] or i['icon'] == '': i['icon'] = DEFAULT_RESULT_ICON if not 'mimetype' in i or not i['mimetype'] or i['mimetype'] == '': i['mimetype'] = DEFAULT_RESULT_MIMETYPE if not 'result_type' in i or not i['result_type'] or i['result_type'] == '': i['result_type'] = DEFAULT_RESULT_TYPE if not 'category' in i or not i['category'] or i['category'] == '': i['category'] = 0 if not 'title' in i or not i['title']: i['title'] = '' if not 'comment' in i or not i['comment']: i['comment'] = '' if not 'dnd_uri' in i or not i['dnd_uri'] or i['dnd_uri'] == '': i['dnd_uri'] = i['uri'] i['provider_credits'] = GLib.Variant('s', PROVIDER_CREDITS) result_set.add_result(**i) except Exception as error: print(error) class Scope(Unity.AbstractScope): def __init__(self): Unity.AbstractScope.__init__(self) def do_get_search_hint(self): return SEARCH_HINT def do_get_schema(self): ''' Adds specific metadata fields ''' schema = Unity.Schema.new() if EXTRA_METADATA: for m in EXTRA_METADATA: schema.add_field(m['id'], m['type'], m['field']) #FIXME should be REQUIRED for credits schema.add_field('provider_credits', 's', Unity.SchemaFieldType.OPTIONAL) return schema def do_get_categories(self): ''' Adds categories ''' cs = Unity.CategorySet.new() if CATEGORIES: for c in CATEGORIES: cat = Unity.Category.new(c['id'], c['name'], Gio.ThemedIcon.new(c['icon']), c['renderer']) cs.add(cat) return cs def do_get_filters(self): ''' Adds filters ''' fs = Unity.FilterSet.new() #if FILTERS: # return fs def do_get_group_name(self): return GROUP_NAME def do_get_unique_name(self): return UNIQUE_PATH def do_create_search_for_query(self, search_context): se = MySearch(search_context) return se def do_activate(self, result, metadata, id): album = result.metadata['album'].get_string() artist = result.metadata['artist'].get_string() global tracks if id == 'show': if result.uri.startswith("album://"): for track in tracks: if album in track[3] and artist in track[2]: filename = tracks[1] continue else: filename = result.uri dirname = os.path.dirname(filename) os.system("xdg-open '%s'" % str(dirname)) else: albumtracks = '' if result.uri.startswith('album://'): for track in tracks: if album in track[3] and artist in track[2]: albumtracks = albumtracks + ' \'%s\'' % (track[1]) else: albumtracks = '\'%s\'' % result.uri print(albumtracks) os.system('audacious -E %s' % albumtracks) return Unity.ActivationResponse(handled=Unity.HandledType.HIDE_DASH, goto_uri=None) def do_create_previewer(self, result, metadata): rp = Preview() rp.set_scope_result(result) rp.set_search_metadata(metadata) return rp def load_scope(): return Scope() unity-scope-audacious-0.1+13.10.20130927.1/src/__init__.py0000644000015700001700000000000012221263420023162 0ustar pbuserpbgroup00000000000000unity-scope-audacious-0.1+13.10.20130927.1/setup.py0000644000015700001700000000160112221263420022004 0ustar pbuserpbgroup00000000000000#!/usr/bin/env python3 # -*- coding: utf-8 -*- ''' Setup file for yelp dash plugin ''' from distutils.core import setup from DistUtilsExtra.command import build_extra from DistUtilsExtra.command import build_i18n setup(name='unity-scope-audacious', version='0.1', author='Mark Tully', author_email='markjtully@gmail.com', url='http://launchpad.net/ubuntu-scopes', license='GNU General Public License (GPL)', data_files=[('share/dbus-1/services', ['data/unity-scope-audacious.service']), #('share/icons/unity-icon-theme/places/svg', ['data/icons/service-audacious.svg']), ('share/unity-scopes/audacious', ['src/unity_audacious_daemon.py']), ('share/unity-scopes/audacious', ['src/__init__.py']), ], cmdclass={'build': build_extra.build_extra, 'build_i18n': build_i18n.build_i18n, }) unity-scope-audacious-0.1+13.10.20130927.1/setup.cfg0000644000015700001700000000021012221263420022106 0ustar pbuserpbgroup00000000000000[build] i18n=True [build_i18n] domain=unity-scope-audacious desktop_files=[('share/unity/scopes/music', ('data/audacious.scope.in',))] unity-scope-audacious-0.1+13.10.20130927.1/data/0000755000015700001700000000000012221263750021213 5ustar pbuserpbgroup00000000000000unity-scope-audacious-0.1+13.10.20130927.1/data/unity-scope-audacious.service0000644000015700001700000000023312221263420027017 0ustar pbuserpbgroup00000000000000[D-BUS Service] Name=com.canonical.Unity.Scope.Music.Audacious Exec=/usr/bin/python3 /usr/share/unity-scopes/scope-runner-dbus.py -s music/audacious.scope unity-scope-audacious-0.1+13.10.20130927.1/data/audacious.scope.in0000644000015700001700000000145212221263420024624 0ustar pbuserpbgroup00000000000000[Scope] DBusName=com.canonical.Unity.Scope.Music.Audacious DBusPath=/com/canonical/unity/scope/music/audacious Icon=/usr/share/unity/icons/lens-nav-music.svg QueryBinary=audacious _Keywords=audacious; RequiredMetadata= OptionalMetadata=album[s];artist[s];genre[s];year[i];track_length[i];track_number[i] Loader=/usr/share/unity-scopes/audacious/unity_audacious_daemon.py Module=audacious.unity_audacious_daemon ModuleType=python3 RemoteContent=false Type=music _Name=Audacious _Description=This is an Ubuntu search plugin that enables information from Audacious to be searched and displayed in the Dash underneath the Music header. If you do not wish to search this content source, you can disable this search plugin. _SearchHint=Search Audacious [Desktop Entry] X-Ubuntu-Gettext-Domain=unity-scope-audacious unity-scope-audacious-0.1+13.10.20130927.1/data/icons/0000755000015700001700000000000012221263750022326 5ustar pbuserpbgroup00000000000000