unity-scope-musique-0.1+13.10.20130723/0000755000015700001700000000000012173421307017650 5ustar pbuserpbgroup00000000000000unity-scope-musique-0.1+13.10.20130723/po/0000755000015700001700000000000012173421307020266 5ustar pbuserpbgroup00000000000000unity-scope-musique-0.1+13.10.20130723/po/unity-scope-musique.pot0000644000015700001700000000217512173420334024763 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_musique_daemon.py:39 #, fuzzy msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2013-02-26 00:13+0000\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_musique_daemon.py:37 ../data/musique.scope.in.h:4 msgid "Search Musique" msgstr "" #: ../src/unity_musique_daemon.py:38 msgid "Sorry, there are no Musique results that match your search." msgstr "" #: ../src/unity_musique_daemon.py:49 msgid "Songs" msgstr "" #: ../src/unity_musique_daemon.py:53 msgid "Albums" msgstr "" #: ../data/musique.scope.in.h:1 msgid "musique;" msgstr "" #: ../data/musique.scope.in.h:2 msgid "Musique" msgstr "" #: ../data/musique.scope.in.h:3 msgid "Find Musique items" msgstr "" unity-scope-musique-0.1+13.10.20130723/po/POTFILES.in0000644000015700001700000000012712173420334022042 0ustar pbuserpbgroup00000000000000[encoding: UTF-8] src/unity_musique_daemon.py [type: gettext/ini]data/musique.scope.in unity-scope-musique-0.1+13.10.20130723/MANIFEST.in0000644000015700001700000000007612173420334021410 0ustar pbuserpbgroup00000000000000include MANIFEST.in include src/* include data/* include po/* unity-scope-musique-0.1+13.10.20130723/tests/0000755000015700001700000000000012173421307021012 5ustar pbuserpbgroup00000000000000unity-scope-musique-0.1+13.10.20130723/tests/test_musique.py0000644000015700001700000000422512173420334024115 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, None, result_set, None) s = self.scope.create_search_for_query(ctx) s.run() return result_set class TestAskUbuntu(ScopeTestCase): def setUp(self): self.init_scope('src/unity_musique_daemon.py') def tearDown(self): self.scope = None self.scope_module = None def test_questions_search(self): self.scope_module.MUSIQUE_DBFILE = 'tests/data/mock_musique_pass.db' self.scope_module.MUSIQUE_BACKUP_DBFILE = 'tests/data/mock_musique_backup.db' expected_results = ["/home/mark/music/Youssu N'Dour/Youssou N'Dour/09. Birima.MP3", "Birima"] results = [] for s in ['birima']: 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_questions_failing_search(self): self.scope_module.MUSIQUE_DBFILE = 'tests/data/mock_musique_fail' self.scope_module.MUSIQUE_BACKUP_DBFILE = 'tests/data/mock_musique_backup.db' for s in ['upnriitnyt']: result_set = self.perform_query(s) self.assertEqual(len(result_set.results), 0) if __name__ == '__main__': unittest.main() unity-scope-musique-0.1+13.10.20130723/tests/__init__.py0000644000015700001700000000000012173420334023110 0ustar pbuserpbgroup00000000000000unity-scope-musique-0.1+13.10.20130723/tests/data/0000755000015700001700000000000012173421307021723 5ustar pbuserpbgroup00000000000000unity-scope-musique-0.1+13.10.20130723/tests/data/mock_musique_fail0000644000015700001700000000000612173420334025335 0ustar pbuserpbgroup00000000000000 unity-scope-musique-0.1+13.10.20130723/tests/data/mock_musique_pass.db0000644000015700001700000001400012173420334025753 0ustar pbuserpbgroup00000000000000SQLite format 3@  -â' <Wc<P‚{tabletrackstracksCREATE TABLE tracks (id integer primary key autoincrement,path varchar(255),title varchar(255),duration integer,track integer,year integer,artist integer,album integer,tstamp integer)R!!qtableattributesattributesCREATE TABLE attributes (name varchar(255), value)‚tableartistsartistsCREATE TABLE artists (id integer primary key autoincrement,hash varchar(32),name varchar(255),albumCount integer,trackCount integer)P++Ytablesqlite_sequencesqlite_sequenceCREATE TABLE sqlite_sequence(name,seq)&‚'tablealbumsalbumsCREATE TABLE albums (id integer primary key autoincrement,hash varchar(32),title varchar(255),year integer,artist integer,trackCount integer) ÝÝ!%) dournyoussouYoussou N'Dour ÞôéÞ  tracks  albums  artists ÞÞ %) dournyoussouYoussou N'Dour µóçε-root/home/mark/music!!lastUpdate1350461716 status1 version1 ¼¼B c Youssu N'Dour/Youssou N'Dour/09. Birima.MP3Birimaà  šunity-scope-musique-0.1+13.10.20130723/tests/data/mock_musique_backup.db0000644000015700001700000001400012173420334026252 0ustar pbuserpbgroup00000000000000SQLite format 3@  -â' <Wc<P‚{tabletrackstracksCREATE TABLE tracks (id integer primary key autoincrement,path varchar(255),title varchar(255),duration integer,track integer,year integer,artist integer,album integer,tstamp integer)R!!qtableattributesattributesCREATE TABLE attributes (name varchar(255), value)‚tableartistsartistsCREATE TABLE artists (id integer primary key autoincrement,hash varchar(32),name varchar(255),albumCount integer,trackCount integer)P++Ytablesqlite_sequencesqlite_sequenceCREATE TABLE sqlite_sequence(name,seq)&‚'tablealbumsalbumsCREATE TABLE albums (id integer primary key autoincrement,hash varchar(32),title varchar(255),year integer,artist integer,trackCount integer) ÝÝ!%) dournyoussouYoussou N'Dour ÞôéÞ  tracks  albums  artists ÞÞ %) dournyoussouYoussou N'Dour µóçε-root/home/mark/music!!lastUpdate1350461716 status1 version1 ¼¼B c Youssu N'Dour/Youssou N'Dour/09. Birima.MP3Birimaà  šunity-scope-musique-0.1+13.10.20130723/src/0000755000015700001700000000000012173421307020437 5ustar pbuserpbgroup00000000000000unity-scope-musique-0.1+13.10.20130723/src/unity_musique_daemon.py0000644000015700001700000003210612173420334025255 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 shutil import urllib import os import sqlite3 import hashlib import unicodedata import dbus APP_NAME = 'unity-scope-musique' LOCAL_PATH = '/usr/share/locale/' gettext.bindtextdomain(APP_NAME, LOCAL_PATH) gettext.textdomain(APP_NAME) _ = gettext.gettext GROUP_NAME = 'com.canonical.Unity.Scope.Music.Musique' UNIQUE_PATH = '/com/canonical/unity/scope/music/musique' SEARCH_HINT = _('Search Musique') NO_RESULTS_HINT = _('Sorry, there are no Musique results that match your search.') PROVIDER_CREDITS = _('') SVG_DIR = '/usr/share/icons/unity-icon-theme/places/svg/' PROVIDER_ICON = SVG_DIR + 'service-musique.svg' DEFAULT_RESULT_ICON = SVG_DIR + 'result-musique.svg' DEFAULT_RESULT_MIMETYPE = 'taglib/mp3' DEFAULT_RESULT_TYPE = Unity.ResultType.PERSONAL MUSIQUE_DBFILE = os.getenv("HOME") + "/.local/share/data/Flavio Tordini/Musique/musique.db" MUSIQUE_BACKUP_DBFILE = os.getenv("HOME") + "/.local/share/data/Flavio Tordini/Musique/musiquee-scope-backup.db" 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] ROOT_SQL = '''SELECT value FROM attributes WHERE name = "root"''' SEARCH_SQL = '''SELECT tracks.title, tracks.path, artists.name, albums.title, tracks.year, tracks.track, tracks.duration FROM tracks, artists, albums WHERE tracks.album = albums.id AND albums.artist = artists.id AND (tracks.title LIKE '%%%s%%' OR albums.title LIKE '%%%s%%' OR artists.name LIKE '%%%s%%') ORDER BY track''' ALBUM_SQL = '''SELECT tracks.title, tracks.path, artists.name, albums.title, tracks.year, tracks.track, tracks.duration FROM tracks, artists, albums WHERE tracks.album = albums.id AND albums.artist = artists.id AND (albums.title LIKE '%%%s%%' AND artists.name LIKE '%%%s%%') ORDER BY track''' def get_music_from_musique(query): ''' Parses Musique's database into a form we can use ''' # Copy musique's database to a backup so we can run searches on that rather than the main database tracks = [] if not os.path.exists(MUSIQUE_DBFILE): return tracks shutil.copy2(MUSIQUE_DBFILE, MUSIQUE_BACKUP_DBFILE) # Grab all the data we need from the backup try: conn = sqlite3.connect(MUSIQUE_BACKUP_DBFILE) cursor = conn.cursor() cursor.execute(ROOT_SQL) root = cursor.fetchall() cursor.execute(query) tracks = cursor.fetchall() cursor.close() except: pass print("Updated tracks from Musique database") return root, tracks def get_album_art(track): dirname = dirname = os.path.dirname(track[0]) if os.path.exists(dirname + "/cover.jpg"): return dirname + "/cover.jpg" if os.path.exists(dirname + "/folder.jpg"): return dirname + "/folder.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 "audio-x-generic" except: # Otherwise, return a generic audio icon return "audio-x-generic" def search(search, filters): ''' Search for help documents matching the search string ''' results = [] root, tracks = get_music_from_musique(SEARCH_SQL % (search, search, search)) albumresults = [] for track in tracks: title = "" if track[0] is None else track[0] uri = "" if track[1] is None else root[0][0] + "/" + track[1] artist = "" if track[2] is None else track[2] album = "" if track[3] is None else track[3] albumartist = "" if track[2] is None else track[2] year = 0 if track[4] is None else track[4] track_length = 0 if track[6] is None else track[6] track_number = 0 if track[5] is None else track[5] albumart = get_album_art(track) albumuri = "album://" + albumartist + "/" + album results.append({'uri': uri, 'icon': albumart, 'category': 0, 'title': title, 'album':GLib.Variant('s', album), 'artist':GLib.Variant('s', artist), 'year':GLib.Variant('i', year), 'track_length': GLib.Variant('i', track_length), 'track_number': GLib.Variant('i', track_number)}) if album not in albumresults: results.append({'uri': albumuri, 'icon': albumart, 'category': 1, 'title': album, 'album':GLib.Variant('s', album), 'artist':GLib.Variant('s', artist), 'year':GLib.Variant('i', year), 'track_length': GLib.Variant('i', track_length), 'track_number': GLib.Variant('i', track_number)}) albumresults.append(album) return results class Preview(Unity.ResultPreviewer): def do_run(self): 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://"): root, tracks = get_music_from_musique(ALBUM_SQL % (album, artist)) for track in tracks: track = Unity.TrackMetadata.full('file://%s/%s' % (root[0][0], track[1]), track[5], track[0], track[2], track[3], track[6]) 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() if id == 'show': if result.uri.startswith("album://"): tracks = get_music_from_musique(ALBUM_SQL % (album, artist)) filename = tracks[0][1].decode('utf-8') else: filename = result.uri dirname = os.path.dirname(filename) os.system("xdg-open '%s'" % str(dirname)) else: # musique doesn't take any command line arguments so all we can do is open the player os.system('musique') 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-musique-0.1+13.10.20130723/src/__init__.py0000644000015700001700000000000012173420334022535 0ustar pbuserpbgroup00000000000000unity-scope-musique-0.1+13.10.20130723/setup.py0000644000015700001700000000157012173420334021364 0ustar pbuserpbgroup00000000000000#!/usr/bin/env python3 # -*- coding: utf-8 -*- ''' Setup file for musique dash plugin ''' from distutils.core import setup from DistUtilsExtra.command import build_extra from DistUtilsExtra.command import build_i18n setup(name='unity-scope-musique', 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-musique.service']), # ('share/icons/unity-icon-theme/places/svg', ['data/icons/service-musique.svg']), ('share/unity-scopes/musique', ['src/unity_musique_daemon.py']), ('share/unity-scopes/musique', ['src/__init__.py']), ], cmdclass={'build': build_extra.build_extra, 'build_i18n': build_i18n.build_i18n, }) unity-scope-musique-0.1+13.10.20130723/setup.cfg0000644000015700001700000000020412173420334021464 0ustar pbuserpbgroup00000000000000[build] i18n=True [build_i18n] domain=unity-scope-musique desktop_files=[('share/unity/scopes/music', ('data/musique.scope.in',))] unity-scope-musique-0.1+13.10.20130723/data/0000755000015700001700000000000012173421307020561 5ustar pbuserpbgroup00000000000000unity-scope-musique-0.1+13.10.20130723/data/unity-scope-musique.service0000644000015700001700000000022712173420334026110 0ustar pbuserpbgroup00000000000000[D-BUS Service] Name=com.canonical.Unity.Scope.Music.Musique Exec=/usr/bin/python3 /usr/share/unity-scopes/scope-runner-dbus.py -s music/musique.scope unity-scope-musique-0.1+13.10.20130723/data/musique.scope.in0000644000015700001700000000142212173420334023707 0ustar pbuserpbgroup00000000000000[Scope] DBusName=com.canonical.Unity.Scope.Music.Musique DBusPath=/com/canonical/unity/scope/music/musique Icon=/usr/share/unity/icons/lens-nav-music.svg QueryBinary=musique _Keywords=musique; RequiredMetadata= OptionalMetadata=album[s];artist[s];genre[s];year[i];track_length[i];track_number[i] Loader=/usr/share/unity-scopes/musique/unity_musique_daemon.py Module=musique.unity_musique_daemon ModuleType=python3 RemoteContent=false Type=music _Name=Musique _Description=This is an Ubuntu search plugin that enables information from Musique 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 Musique [Desktop Entry] X-Ubuntu-Gettext-Domain=unity-scope-musique unity-scope-musique-0.1+13.10.20130723/data/icons/0000755000015700001700000000000012173421307021674 5ustar pbuserpbgroup00000000000000